Commit graph

15905 commits

Author SHA1 Message Date
brooklyn!
c856f36459
perf(desktop): kill the layout-thrash cascade on session switch (#66033)
Follow-up to #65890 (router transitions off) and #65898 (structural
compare + first-paint budget): profiling the switch path on real 1000+-
message sessions with a new CDP harness showed the remaining freeze is
NOT markdown rendering — it's a forced-reflow cascade from mount-time
layout reads interleaved with style writes across the transcript's
layout effects, plus the first-paint budget cut landing too late to
stop the full-budget commit.

Measured on the two largest local sessions (996 and 1363 messages),
main-thread longtask totals per switch: warm 2450ms -> 557ms and
1158ms -> 194ms; first paint 1690ms -> 444ms. Harness:
scripts/profile-session-switch.mjs (same CDP family as
profile-real-stream.mjs).

- use-resize-observer: drop the synchronous initial callback and ride
  the observer's spec-guaranteed first delivery instead (same frame,
  after layout, before paint). The sync call ran while the commit's
  layout was dirty, so every size read in a callback forced a full
  reflow — with one instance per user bubble (measureClamp read
  scrollHeight, then WROTE --human-msg-full, re-dirtying layout for the
  next bubble), the switch commit thrashed for over a second. Inside RO
  timing the same reads are free. Composer metrics (2x
  getBoundingClientRect + documentElement style writes) rides the same
  fix.
- Same class, same fix at the remaining call sites profiling surfaced:
  ExpandableBlock and TerminalOutput (dozens per tool-heavy transcript)
  now measure/pin via RO initial delivery; the tool-window and
  thinking-preview pins drop their sync pin() call; the thread
  timeline's initial active-tick compute joins its existing
  scroll-time rAF batching so back-to-back transcript updates coalesce.
- thread/list: cut the render budget in the RENDER phase (state-from-
  props adjustment) instead of the post-commit layout effect. The
  effect-time cut was too late — on a warm switch React first built and
  committed the full 300-part tree, then re-rendered at 60, then bumped
  back to 300, so the expensive commit still happened (and on a cold
  switch the bump rAF usually fired while the transcript was still
  empty, so the prefetched messages rendered at full budget anyway).
  The render-phase cut restarts the component before any child renders;
  a second trigger handles the cold path where messages land later
  under the same sessionKey.
- thread/list: backfill 60 -> 300 inside startTransition so the older
  turns' markdown+shiki render is interruptible background work instead
  of a synchronous freeze one frame after the switch paints. Functional
  Math.max so an urgent "Show earlier" click can't be rebased back down.
- composer focus: skip the rAF/timeout focus retries when the element
  is already focused — focus() runs the full focusing steps (forcing
  layout) even on the active element, ~585ms per switch on a large
  dirty DOM.
- Replace the tautological render-budget test (it re-declared the
  constants locally and asserted 60 < 300) with behavior tests of the
  now-exported buildGroups + firstVisibleGroupIndex.

Verification: apps/desktop `npx tsc --noEmit` clean; full
`npx vitest run` 210 files / 1763 passed; manual CDP check confirms the
deferred backfill commits the full transcript, stays pinned to bottom,
and "Show earlier" still pages.
2026-07-16 22:46:08 -04:00
liuhao1024
3951d769fb fix(models): add kimi-for-coding-highspeed to kimi-coding provider list 2026-07-16 19:35:16 -07:00
SHL0MS
d57531b1a4 fix(unreal-mcp): pitfall 21b rewritten from live video production — hide sprites at source
Post-hoc sprite removal is a losing battle (three inpainting strategies
failed QC on letter-edge overlap frames). The production answer: sprites
are BillboardComponent/SpriteComponent/ArrowComponent subobjects — set
bVisible:false via ObjectTools (remove_component fails on default
subobjects), swept scene-wide in one ProgrammaticToolset script
(148 actors, 13 sprites, one round-trip, verified).
2026-07-16 19:30:22 -07:00
SHL0MS
665eaf1977 feat(unreal-mcp): video/frame-sequence pitfalls from live orbit production
21c: the viewport axis gizmo survives bShowUI=false — measured extent on
5.8, deterministic ffmpeg post-crop recipe. 21d: frame-sequence discipline
(one session, serial captures, idempotent resumable loop, s/frame budget,
smoothstep easing, VolumetricCloud artifact removal) — all from producing
a real 240-frame orbit through CaptureViewport.
2026-07-16 19:30:22 -07:00
SHL0MS
18694e96d8 feat(unreal-mcp): advanced-workflows layer, live-verified against UE 5.8
New references/advanced-workflows.md covering the sophisticated-workflow
surface, each section exercised against a running editor:

- ProgrammaticToolset batching: full contract (get_execution_environment
  gate, execute_tool fully-qualified names, JSON-string inputs,
  returnValue unwrapping, allowed imports) + a verified worked example
  (12-column colonnade, 36 components in one round-trip vs 37 serial calls)
- Blueprint DSL authoring loop, verified end-to-end: create -> list_graphs
  -> get_graph_dsl_docs -> find_node_types per node -> write_graph_dsl ->
  compile_blueprint -> spawn instance. Every node-ID gotcha hit live is
  recorded (EventTick not Tick, Math|Rotator|MakeRotator, registry
  categories vs doc categories, no (self) node)
- PIE session options schema (bSimulate/playMode/warmupSeconds/
  startTransform, out-of-process downgrade behavior)
- Sequencer orientation: 140-tool surface mapped by capability group +
  sibling keyframing/controlrig/conditions toolsets + minimal cinematic
  skeleton
- LogsToolset self-debugging, AutomationTestToolset CI loop,
  SemanticSearch, ConfigSettings, project AgentSkillToolset precedence
- Per-situation decision table

New pitfalls from this round's live failures: 10b (refPath-object vs
plain-string params; schema-in-error as tiebreaker), 10c (DSL node IDs
must come from find_node_types). SKILL.md: batching exception wired into
the operating loop, reference table row, description updated.
2026-07-16 19:30:22 -07:00
SHL0MS
ab818493ff fix(unreal-mcp): dedupe pitfall numbering (two sections numbered 4) 2026-07-16 19:30:22 -07:00
SHL0MS
d24ab20404 feat(unreal-mcp): live-verify skill against a running UE 5.8 server; encode e2e test findings
Ran the full loop against a real editor (blank project, ModelContextProtocol
+ ToolsetRegistry + AllToolsets enabled): raw MCP handshake, discovery walk,
environment relight for golden hour, primitive monument build, virtual-camera
captures with vision judgment, exposure debugging, annotated spatial capture.
67 toolsets advertised; every dispatch semantic below observed, not inferred.

Corrections and additions from the live run:
- Qualified toolset names (editor_toolset.toolsets.scene.SceneTools) with
  SHORT tool_name; TOptional params must be explicit null; find_actors
  requires ''/[] for its schema-required optionals; ObjectTools values is a
  JSON *string*; refPath object references; returnValue wrapping; per-property
  failure lists with schema-in-error
- HTTP wire contract: initialize=JSON + session header, tools/call=SSE frame
  after game-thread completion (plain-JSON clients read empty body)
- CaptureViewport as virtual camera (captureTransform, meter-unit annotation
  grid + actor callouts) verified with pixel evidence; recipes rewritten to
  use it instead of viewport piloting
- New pitfalls from real failures: template-level environment-actor
  duplication compounding into whiteouts (find-first/spawn-if-missing rule),
  template exposure calibration vs physical lux (12b), objective exposure
  check via ffprobe YAVG (12c), untitled-level Save-As modal deadlock,
  macOS full-Xcode + Metal Toolchain requirement (xcodebuild
  -downloadComponent MetalToolchain)
- Live toolset census (67 on blank project), LogsToolset/ConfigSettings/
  SemanticSearch highlights, UE EULA 6(e) licensing note
2026-07-16 19:30:22 -07:00
SHL0MS
a8b81c56a0 feat(optional-skills): add unreal-mcp companion skill for the unreal-engine MCP catalog entry
Companion to optional-mcps/unreal-engine (Epic's official editor-embedded
MCP server, UE 5.8 experimental). Mirrors the blender-mcp catalog-entry +
companion-skill pattern, sized up for Unreal's discovery-based surface:

- SKILL.md: tool-search discovery contract (list_toolsets/describe_toolset/
  call_tool), serial game-thread call discipline (explicitly overrides the
  parallel-batching default), plain-English->scene translation workflow,
  save/verify hygiene, art-direction loop
- references/tool-surface.md: architecture (Unreal MCP / Toolset Registry /
  AllToolsets), confirmed shipped toolsets, call_tool dispatch semantics,
  project Agent Skills (AgentSkillToolset), capture paths, custom Python/C++
  toolset authoring, config/CVar/console reference, cooked-build notes
- references/scene-craft.md: physically-based lighting values (lux/lumens/
  Kelvin/EV100), mood recipes, Lumen Movable-mobility rule, scale tables,
  content-path conventions, CineCamera framing, editor Python entry points
- references/recipes.md: four end-to-end builds in INTENT/DISCOVER/VALUES/
  VERIFY grammar (exterior, night interior, golden-hour cinematic still,
  import+populate) that stay honest about the project-dependent surface
- references/pitfalls.md: 25+ failure modes with fixes: start order, modal
  deadlocks, Hermes-timeout-vs-editor-completion, _C class suffix, PascalCase
  silent no-op writes, referenced-asset delete crash, async shader compiles,
  editor sprite icons in screenshots, PIE interference

Grounded in Epic's UE 5.8 docs and Epic's agent-facing skill pack for this
server; no fabricated tool names — live describe_toolset schemas are the
contract throughout.
2026-07-16 19:30:22 -07:00
nousbot-eng
56e2ba5e79
fmt(js): npm run fix on merge (#66013)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-17 00:58:06 +00:00
nousbot-eng
36bf3c2673
fmt(js): npm run fix on merge (#66010)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-17 00:51:29 +00:00
HexLab
3f199f5c51
fix(desktop): don't latch remote backend boot failures so remote gateway reconnect recovers (#65756) 2026-07-16 20:45:43 -04:00
brooklyn!
dfb76d36d5
fix(desktop): put Hermes-managed Node on PATH for install/rebuild (#66002)
Desktop launch and the update-chain rebuild install npm deps whose child
scripts shell out to a bare `node` (e.g. electron-winstaller's
select-7z-arch.js). When launched from the desktop updater chain
(Desktop -> hermes-setup -> hermes update) the shell PATH customizations are
lost, so the install dies with `'node' is not recognized` / `node: not found`.

- cmd_gui: wrap the npm-install env with with_hermes_node_path(_nixos_build_env())
  so managed Node is prepended even on a stripped PATH — mirrors the idiom
  already used by the update deps refresh. (The original fix merged nixos_env
  on TOP of the managed env, whose full os.environ copy clobbered the managed
  PATH back to bare; wrapping fixes that merge order.)
- _cmd_update_impl: spawn the `desktop --build-only` subprocess with
  with_hermes_node_path() so the child starts with managed Node from the outset.

Regression test: the desktop install env now prepends the managed Node dir
ahead of a bare updater PATH instead of passing env=None.

Co-authored-by: F4TB0Yz <jfduarte09@gmail.com>
2026-07-16 20:45:05 -04:00
brooklyn!
432fca55a7
fix(desktop): drain queued prompts for background sessions (#66001)
Co-authored-by: Jakub Wolniewicz <4850809+frizikk@users.noreply.github.com>
2026-07-16 20:39:55 -04:00
Devorun
bd00212337
fix(dashboard): drop _HERMES_GATEWAY when spawning hermes actions (#52482)
The web dashboard runs inside the gateway process, so `os.environ` carries
`_HERMES_GATEWAY=1`. `_spawn_hermes_action` spread that into the subprocess env,
so a spawned `hermes gateway restart` (dashboard "Enable webhooks", Telegram QR
apply) tripped the in-process restart-loop guard and exited 1 — the gateway
never restarted, but the dashboard reported `restart_started: true` because it
only checks that the spawn succeeded.

Scrub `_HERMES_GATEWAY` from the spawned action's env, matching what the
gateway's own restart watcher already does (gateway/run.py).

Fixes #52470. Adds a test asserting the spawned env drops the loop-guard var
while keeping HERMES_NONINTERACTIVE.
2026-07-16 19:52:53 -04:00
xxxigm
d4c3f98140
fix(dashboard): unblock basic auth plugin when setting password interactively (#54489) (#63786)
* fix(dashboard): unblock basic auth plugin during interactive password setup

When the dashboard prompts for username/password on a non-loopback bind,
also remove the bundled basic provider from plugins.disabled so
discover_plugins(force=True) can register it (#54489).

* test(dashboard): cover basic auth plugin blocked by plugins.disabled

Regression harness for #54489: credentials in config are not enough when
the bundled basic provider is on the deny-list.
2026-07-16 19:49:39 -04:00
rayoo
921c17af88
fix(dashboard): scope chat attach tokens by session (#60745) 2026-07-16 19:48:47 -04:00
nousbot-eng
10b6d1a910
fmt(js): npm run fix on merge (#65986)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-16 23:19:03 +00:00
ethernet
39a93dc633
fix(desktop): follow compression's stored-id rotation to prevent thread reload (#65984)
Auto-compression ends the SessionDB session and forks a continuation,
rotating the stored session id. The gateway emits `session.info` with
the new `stored_session_id`, and the desktop's cache entry was updated
via `ensureSessionState` — but the URL route and `$selectedStoredSessionId`
never followed the rotation.

On the next send, `getRuntimeIdForStoredSession(oldStoredId)` returned
null (the cache entry's `storedSessionId` no longer matched the old id),
so `routedSessionNeedsResume` evaluated true, triggering a full
`session.resume` + REST transcript prefetch — the whole thread reloaded.

Fix: a new `$activeSessionStoredId` atom is set in `ensureSessionState`
when the active session's stored id changes. A `useEffect` in
`use-session-actions` subscribes to it and re-anchors the route +
selection (`setSelectedStoredSessionId` + `navigate(replace: true)`),
and cleans up the stale stored→runtime mapping.

`replace: true` because it's the same conversation — compression is
transparent to the user, so back-button stays correct.
2026-07-16 23:12:55 +00:00
nousbot-eng
75467998f9
fmt(js): npm run fix on merge (#65974)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-16 22:32:53 +00:00
ethernet
f08b1f3445
feat(desktop): button tooltip keybind hints + keybinds settings tab + unified worktree dialog (#65204)
* feat(desktop): add useKeybindHint hook and TipKeybindLabel

Add a shared hook that reads the current keybind combo for an action
id from the $bindings store (rebindable) or KEYBIND_READONLY (fixed),
returning a formatted string or null when unbound.

Add TipKeybindLabel — a convenience component that auto-reads both
its label (from i18n) and keybind combo from the action registry.
Pass only actionId for the common case; pass text to override when
the tooltip is context-dependent.

* fix(desktop): replace native title= on buttons with themed Tip

Migrate all <button>/<Button> elements using the native HTML title=
attribute to the instant, themed <Tip> component. Native tooltips are
unstyled, delayed (~500ms OS default), and visually inconsistent with
the app's instant themed tooltips.

Also adds <Tip> wrappers to icon-only buttons that were missing
tooltips entirely (dialog close, search clear, overlay close,
master-detail pane controls, keybind panel rebind/reset buttons).

Adds an enforcement test (no-native-title.test.ts) that scans all
.tsx files for <button>/<Button> with title= and fails if any are
found. Updates DESIGN.md with the icon-only button tooltip rule and
keybind hint guidance.

* feat(desktop): wire keybind hints into button tooltips

Add actionId to TitlebarTool, StatusbarItem, and SidebarNavItem so
their tooltips show the current keybind combo via TipKeybindLabel.
Fix the hardcoded NEW_SESSION_KBD in the sidebar to read from
$bindings so it stays live on rebind.

Wired surfaces:
- Titlebar: sidebar toggle, flip panes, keybinds, settings
- Statusbar: terminal toggle (view.showTerminal)
- Status stack: open agents button (nav.agents)
- Sidebar nav: new session, skills, messaging, artifacts

* feat(desktop): move keybind panel to settings tab with search filter

Move the keyboard shortcuts panel from a Radix Dialog into a proper
Settings tab (/settings?tab=keybinds). The ⌘/ shortcut and titlebar
keyboard button now navigate to this settings tab instead of toggling
a dialog. Adds a search filter to filter shortcuts by label.

- New: src/app/settings/keybind-settings.tsx (extracted from keybind-panel.tsx)
- Delete: src/app/shell/keybind-panel.tsx (dialog wrapper removed)
- Remove: $keybindPanelOpen atom and toggle/open/close functions
- Add: IconKeyboard to lib/icons.ts
- i18n: keybinds.search + settings.nav.keybinds (en, zh, zh-hant, ja)

* refactor(desktop): unify worktree dialog into shared WorktreeDialog

Extract the worktree creation dialog from StartWorkButton (sidebar) into a
shared WorktreeDialog component. Both the sidebar's StartWorkButton and the
composer's CodingStatusRow now use the same dialog, eliminating the duplicated
UI.

The shared dialog keeps the sidebar version's full feature set:
- BaseBranchPicker (filterable base branch combobox)
- Convert mode (check out an existing branch into a worktree)
- Sanitized branch name input

The coding row passes repoPath (from cwd) and onOpenWorktree (which carries
the composer draft to the new session) so the unified dialog works everywhere
there's a repo, not just inside an entered project.
2026-07-16 18:26:21 -04:00
nousbot-eng
f1315ae91e
fmt(js): npm run fix on merge (#65971)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-16 22:26:09 +00:00
ethernet
0f05aaa2bf
perf(desktop): make session switching snappy on large transcripts (#65898)
Switching between chat sessions in the desktop app froze for up to ~1–2s on
large transcripts. Profiling the switch path surfaced three main-thread
blockers, fixed here minimally and without changing behavior.

1. JSON.stringify deep-compare (worst case). chatMessagesEquivalent compared
   message parts with JSON.stringify(a) === JSON.stringify(b) on every switch.
   On image-/large-blob-bearing transcripts this serialized every part twice
   and cost well over a second. Replaced with a structural compare that never
   stringifies: array-level identity fast-path, per-part reference fast-path,
   then type-aware field comparison. The compare's only consumer asks "did the
   transcript change, should I setMessages?", so it is deliberately
   conservative — a false-negative just causes one extra idempotent
   setMessages, while a false-positive (the unsafe direction) is avoided.

2. Scroll-settle loop. thread-list ran a requestAnimationFrame settle loop up
   to 90 frames (or 5 stable frames) on every sessionKey change, each frame
   forcing a synchronous layout read + write — racing the markdown paint for
   up to ~1.5s. A normal synchronous switch stabilizes within a couple
   frames, so the ceiling is now 2 stable frames / 15 max.

3. Synchronous first paint of up to 300 parts. On switch, thread-list reset
   the render budget to the full RENDER_BUDGET=300, so up to 300 parts went
   through markdown + shiki syntax-highlighting synchronously on the switch
   commit. It now paints a small FIRST_PAINT_BUDGET=60 first, then bumps to
   the full 300 in a requestAnimationFrame after the first commit.

Salvaged from PR #49807 by professorpalmer — re-applied to the restructured
file layout (use-session-actions/utils.ts, thread/list.tsx) and tests merged
into the existing utils.test.ts.

Co-authored-by: Cary Palmer <professorpalmer@users.noreply.github.com>
2026-07-16 18:19:41 -04:00
ethernet
42bd4368ae fix(desktop): sidebar status indicators lag for background sessions
The sidebar working dot didn't update for background sessions until the
user opened them. Two coupled causes:

1. The gateway's session.info event payload omitted stored_session_id,
   so the desktop app had no way to map a background session's runtime
   id to its stored id. Without the stored id, setSessionWorking(null,
   ...) was a no-op — the $workingSessionIds atom never updated.

2. The running→busy transition in the session.info handler was gated on
   `apply` (active session only). The gate correctly scopes view-only
   side effects (setCurrentModel, setCurrentCwd, etc.) to the focused
   chat, but the per-session busy state drives the sidebar indicator and
   must reach every session. updateSessionState only mutates the
   per-runtime cache entry, and syncSessionStateToView already guards
   the view publish to the active session, so ungating is safe.

Fix: add stored_session_id to _session_info() in tui_gateway/server.py,
add the field to GatewayEventPayload, pass it to updateSessionState in
the session.info handler, and ungate the running→busy transition.
2026-07-16 17:08:08 -04:00
amanning3390
311a5b0a55 feat(kimi): discover K3 on coding endpoint 2026-07-16 13:33:02 -07:00
ethernet
dc7a20cb0e ci(js-autofix): skip apply-patch job when no fixes found
generate-patch now emits a has-fixes job output (true/false).
apply-patch is gated on it via `if: needs.generate-patch.outputs.has-fixes == 'true'`,
so when `npm run fix` produces no diff, the privileged job is skipped
entirely — no runner allocation, no redundant checkout/download/push/PR.
2026-07-16 16:13:50 -04:00
Teknium
75ca29fb21
feat(models): add moonshotai/kimi-k3 to Nous Portal and OpenRouter curated lists, retire kimi-k2.x (#65913)
Replaces moonshotai/kimi-k2.6 (recommended) and moonshotai/kimi-k2.7-code
with moonshotai/kimi-k3 in both curated lists, regenerates the published
model-catalog.json manifest, and updates the docs example manifest (en+zh).

kimi-k3 verified live on both endpoints (Nous Portal /v1/models and
OpenRouter /api/v1/models; 1M context, $3/$15 per Mtok). Family-prefix
matching already covers k3 in moonshot_schema, cache policy, and context
heuristics — no code changes needed there.
2026-07-16 13:08:48 -07:00
nousbot-eng
74fc222f17
fmt(js): npm run fix on merge (#65912)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-16 19:57:39 +00:00
ethernet
0022261234 fix(ci): use autofix-bot PAT 2026-07-16 15:51:11 -04:00
Erosika
8d1c96fd2f fix(memory): align external prefetch guard with fail-open contracts 2026-07-16 12:48:48 -07:00
LeonSGP43
d77c455d7d fix(memory): fail fast on stuck external prefetch 2026-07-16 12:48:48 -07:00
Erosika
2ad6ab17e3 fix(honcho): enforce recall latency and budget contracts 2026-07-16 12:48:48 -07:00
Erosika
ef68ae7ecc docs(honcho): document latency flags and updated tool contracts 2026-07-16 12:48:48 -07:00
Erosika
e8957babf4 feat(honcho): make latency-adding paths configurable
queryRewrite (default off) gates the latest-message rewrite so the
extra auxiliary LLM call is opt-in. firstTurnBaseWait and
firstTurnDialecticWait expose the turn-1 bounded waits in seconds
(0 disables). All three resolve host-block-first like every other
field. Also pins per-host timeout resolution with tests.
2026-07-16 12:48:48 -07:00
Erosika
e7fb51d5ac refactor(memory): make query rewrite provider-agnostic
Move query_rewrite from the honcho plugin to plugins/memory/ and
rename the auxiliary task key honcho_query_rewrite ->
memory_query_rewrite so any memory provider can use the same
rewrite path and model/timeout config block. No behavior change.
2026-07-16 12:48:48 -07:00
Erosika
8ab4cb9d0d fix(honcho): gate the stalled-init prefetch wait to the first turn 2026-07-16 12:48:48 -07:00
vizi0uz
56816f4232 fix(honcho): stop clipping honcho_reasoning tool results to the injection budget
dialecticMaxChars (default 600) is documented as the budget for the dialectic
supplement auto-injected into the system prompt every turn — a small recurring
cost that is correct to bound tightly. But dialectic_query() applied that cap
unconditionally, so explicit honcho_reasoning tool calls — where the model
deliberately spends a turn asking for a synthesized answer — were silently
truncated mid-word to 600 chars with a trailing " …", no error surfaced. The
full answer is returned by Honcho server-side; the clip happens client-side.

The auto-injection path already has its own token-based budget (contextTokens,
enforced in prefetch() via _truncate_to_budget), so the char cap's real job is a
cheap always-on guardrail for that recurring injection. Explicit tool results
are already bounded server-side by Honcho's dialectic MAX_OUTPUT_TOKENS and don't
need the injection cap — sibling tools (honcho_search, honcho_context) don't
post-clip their results either.

Add apply_injection_cap (default True, preserving current behavior) to
dialectic_query(); the honcho_reasoning tool handler passes False so it returns
Honcho's full synthesized answer. Auto-injection is unchanged. Tests cover both
the capped injection path and the uncapped tool path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 12:48:48 -07:00
Erosika
29e0471708 fix(honcho): update SDK and restore CI coverage 2026-07-16 12:48:48 -07:00
Erosika
1c051d1df9 fix(honcho): preserve delayed and rewritten recall context 2026-07-16 12:48:48 -07:00
vizi0uz
f4669f34cf feat(honcho): add list mode to honcho_conclude so delete can resolve a real conclusion id
honcho_conclude's delete action was unreachable in practice: no tool ever
surfaced a real conclusion id for the model to pass as delete_id.
honcho_search only searches the separate Message resource space, and the
SDK's ConclusionScope.list()/.query() (which do return real Conclusion.id
values) were never wired into any tool.

Adds an optional list mode to honcho_conclude (query to search, omit to
browse recent conclusions), backed by a new
HonchoSessionManager.list_conclusions(). No new tool, no changes to the
create/delete signatures or their conclusions_of() routing.
2026-07-16 12:48:48 -07:00
ljy-2000
01d1a663e1 fix(honcho): ground dialectic queries in latest user message 2026-07-16 12:48:48 -07:00
k4z4n0v4
3e4e3db66d fix(honcho): don't let first-turn injection suppress dialectic
injectionFrequency='first-turn' returned empty for the entire
prefetch_context() method on turns 2+, which blocked the dialectic
supplement from being consumed and injected. The dialectic has its
own cadence (dialecticCadence) and must continue to fire and inject
independently of the base context layer.

Now first-turn mode gates only Layer 1 (base context: representation
+ card), letting Layer 2 (dialectic supplement) flow through its
normal consumption path on every turn.

Also fixes all remaining tests that passed dialecticCadence via
cfg_extra={'raw': {...}} to use the typed dialectic_cadence field.
2026-07-16 12:48:48 -07:00
k4z4n0v4
d2b6c21a3c fix(honcho): resolve cost-awareness config from host block
injectionFrequency, contextCadence, and dialecticCadence were read
only via raw.get() which checks root-level keys in honcho.json.
Settings placed inside hosts.<name> (the normal per-host location)
were silently ignored, falling back to defaults.

- Add typed dataclass fields: injection_frequency, context_cadence,
  dialectic_cadence with host-block-first resolution chains matching
  the pattern used by all other config fields.
- Update HonchoMemoryProvider.initialize() to read from typed cfg
  fields instead of cfg.raw directly.
- Fix search_context tests that mocked _honcho directly — the
  .honcho property getter calls get_honcho_client() which overwrites
  the backing field, so use patch.object on the property instead.
- Add injection_frequency and context_cadence config override tests.
2026-07-16 12:48:48 -07:00
k4z4n0v4
111ca88fab fix(honcho): honor per-host timeout in config resolution
HonchoClientConfig timeout/requestTimeout resolution skipped the per-host config
block, silently dropping a host-scoped timeout and falling through to the global
config.yaml value (or the default). Add the host block at the front of the
resolution chain, consistent with every other field (base_url, api_key, etc.).
2026-07-16 12:48:48 -07:00
k4z4n0v4
63288f1d80 fix(honcho): stop dropping dialectic results on trivial turns
Symptom: Honcho logs show a dialectic answer was generated, but Hermes never
injects it — intermittently.

Root cause: the dialectic supplement that queue_prefetch() fires at the end of
turn N is stored pending (fired_at=N) for consumption by turn N+1's prefetch().
But prefetch()'s trivial-prompt guard returned early BEFORE the consumption
block. So when turn N+1's prompt was trivial ('ok', 'yes', 'continue', a slash
command), the ready result was never consumed, and a few turns later the
stale-discard guard dropped it. Generated by Honcho, never seen by the model.
The dependence on 'is the consuming turn trivial?' is why the loss looked random.

Fix: trivial turns now consume and inject a ready, non-stale pending result while
still spending no new work (no base-context fetch, no new dialectic fire). A
trivial ack shouldn't generate context, but it shouldn't destroy an answer
already computed for that exact turn. Extracted the pop+stale-check into a shared
_consume_pending_dialectic() used by both the trivial path and the normal path so
they age-check identically.

Preserved (covered by new tests): genuinely stale results are still discarded on
trivial turns; trivial turns still fire no new work; a trivial turn with nothing
pending still injects nothing.

Adds regression tests for inject-on-trivial and discard-stale-on-trivial.
2026-07-16 12:48:48 -07:00
k4z4n0v4
bef9eea3e6 fix(honcho): inject base context on the first message of a session
A brand-new session injected no Honcho context on the user's first message —
the peer card/representation only showed up from turn 2 onward. The base-context
fetch was fired asynchronously and popped in the same synchronous pass, so it
always lost the race on turn 1 (a background thread can't finish inside one
pass), leaving the first response with zero recalled context.

Fetch the base layer (representation + card + summary) synchronously with a
bounded timeout on turn 1 so the peer card is injected immediately; subsequent
turns still consume the background-refreshed result primed by queue_prefetch().
The wait is bounded by _FIRST_TURN_BASE_TIMEOUT and tightened further by a small
configured request timeout (fail-fast deployments / tests).

Two related first-turn/dialectic reliability fixes ride along:
- first-turn dialectic no longer double-fires: if a prewarm .chat() thread is
  already in flight from session init, turn 1 waits briefly for it instead of
  firing a second (duplicate) call that also blocked the first response. The
  first-turn wait is decoupled from a large host timeout (a 60s host timeout
  must not block the first response for 60s) via _FIRST_TURN_DIALECTIC_CAP,
  while still honoring a tight configured timeout.
- empty-pass propagation guard in multi-pass dialectic: at depth > 1 each pass
  feeds the prior pass's output into the next prompt. If a pass returned empty
  (e.g. a reasoning model that spent its whole budget thinking), the next prompt
  carried a blank assessment (the "empty spot" seen in Honcho request logs). Now
  only non-empty prior results feed dependent passes; if all priors are empty,
  re-issue the base prompt instead of referencing nothing.
2026-07-16 12:48:48 -07:00
k4z4n0v4
a35bc81b3f fix(honcho): honest, non-overlapping tool descriptions + drop dead param
The five Honcho tool schemas had overlapping/misleading descriptions, making it
hard for the model to pick the right one, plus two concrete correctness bugs:

- honcho_context advertised an 'Optional focus query' parameter that the dispatch
  never read. The model could pass query= expecting filtering that never happened.
  Remove the dead parameter; honcho_context is an honest no-query snapshot. Focused
  retrieval now lives in honcho_search (see prior commit).
- honcho_conclude's peer param was described as 'Peer to query' — wrong; it's the
  peer the conclusion is ABOUT. Corrected.

Rewrite all five descriptions to give each tool a distinct mental model and
cross-reference siblings:
  profile  = read/write the compact card (cheapest, no query, no LLM)
  search   = find what was actually said, ranked, cross-session (cheap, no LLM)
  reasoning= ask a question, get a synthesized answer (the only LLM tool; expensive)
  context  = a fixed session snapshot (no query, no LLM)
  conclude = write a durable fact to the profile

Addresses the honcho_context query param half of #29402 (see PR notes for the
divergence from that issue's proposed wire-through approach).
2026-07-16 12:48:48 -07:00
k4z4n0v4
c1c59e3474 fix(honcho): make honcho_search do real cross-session message search
honcho_search routed through search_context() -> peer.context(search_query=),
which returns the peer's standing representation + card. The search_query arg
does not turn that endpoint into a search, so results were effectively
query-independent: the same representation blob regardless of the query. Factual
lookups ('what medication', 'which value did we pick') returned noise.

Rewire search_context() to call the workspace message-search endpoint
(Honcho.search) with a peer_perspective filter: RRF-ranked (hybrid semantic +
full-text) raw message excerpts spanning every session the peer was a member of,
across all authors, membership-time-scoped. This is the cross-session factual
recall primitive.

peer_perspective is chosen over the alternatives because it is the only scope
that is simultaneously (a) cross-session, (b) inclusive of assistant-authored
facts about the peer (peer-author search drops these, and they are a large part
of what you want to recall about yourself), and (c) privacy-scoped to the peer's
own sessions (plain workspace search leaks other peers' sessions).

- snippets are labeled by author so the model can tell user-stated facts from
  assistant-derived ones
- max_tokens is now an enforced budget (was accepted but meaningless)
- graceful fallback to peer-authored search if peer_perspective is unsupported
- query length clamped under the embedding input cap

Replaces 3 change-detector tests that asserted the old representation-dump
behavior with 4 that assert the message-search contract + fallback path.
2026-07-16 12:48:48 -07:00
Teknium
31a3822b80
fix(title): contain auto-title thread exceptions instead of dumping tracebacks to the terminal (#65792)
auto_title_session runs as a bare daemon-thread target. Any exception
escaping it hits the default threading excepthook and sprays a raw
traceback into the user's terminal mid-session. The canonical trigger
is the post-'hermes update' stale-module window: the function's lazy
imports read NEW source from disk while already-imported modules
(agent.portal_tags) are still the OLD cached version, producing an
ImportError that repeats on every auto-title attempt until the
long-running process restarts (seen live after 9ce0e67f2 added
set_conversation_context).

The public entrypoint now wraps the body in a catch-all that logs one
WARNING naming the likely cause ('restart the running Hermes process'),
routes the exception through the existing failure_callback channel
(user-visible warning in CLI, debug-suppressed in gateway per #23246),
and never re-raises. This also makes the function honor its own
docstring contract ('silently skips if title generation fails').
2026-07-16 12:41:56 -07:00
ethernet
f1af945f6c
fix(desktop): slow session switch (#65890)
react-router v7's HashRouter wraps every route state update in
React.startTransition() by default. In React 19's concurrent renderer,
transitions are non-urgent — React can yield mid-render and come back
later. When the app is under load (streaming token deltas, gateway
events, store updates from active sessions), those higher-priority
updates keep interrupting the transition, starving the route change commit.

This matches every symptom of the session-switch lag:
- Main thread is free (animations run, clicks work) — startTransition
  defers, it does not block
- navigate() does not take effect for seconds — the transition keeps
  getting interrupted by higher-priority updates
- Worse under load — more concurrent re-renders = more interruptions
- The whole UI (sidebar + main pane) does not update — the entire route
  change is one transition, so nothing commits until it finishes

Pass useTransitions={false} to HashRouter so route state updates are
synchronous at default React priority instead of deferred transition
priority. navigate() now commits and paints immediately.

See: react-router v7 chunk-BIP66BKV.js HashRouter implementation.
2026-07-16 15:34:53 -04:00
Andry Lloyd Paez
b0ca12192e
fix(desktop): restore closed main window on second launch (#64800)
* fix(desktop): restore closed main window on second launch

* fix(desktop): reset deep-link readiness when main window closes
2026-07-16 15:33:38 -04:00