Commit Graph
2890 Commits
Author SHA1 Message Date
oldschoola 5abde300a1 Merge remote-tracking branch 'origin/main' into perf/streaming-reveal-throughput 2026-06-29 22:08:03 -07:00
roboomp 64734021a7 fix(tui): deliver buffered double-Esc as two events and re-arm loader after task completion
StdinBuffer held a bare `\x1b\x1b` chunk (or emitted it as one when followed by
a non-CSI byte). `parseKey("\x1b\x1b")` returns undefined, so CustomEditor
fell through to the base editor and never fired the configured `onEscape` —
the double-escape gesture and the second-press single-Esc handler both went
dead whenever the terminal batched the two presses into one stdin read.

Split a bare `\x1b\x1b` into two ESC events at the buffer layer, mirroring
the existing split for ESC + SGR mouse report. Meta-CSI/SS3 chords
(`\x1b\x1b[A`, `\x1b\x1bO…`) still emit as one combined sequence.

EventController.tool_execution_update re-armed the working loader when a
transient overlay (auto-compaction / auto-retry / handoff) had torn it down
mid-tool; tool_execution_end did not. A subagent (`task`) call only fires
_end, so a task result landing after such an overlay left the UI looking
idle even though the session was still streaming. Mirror the reconciler
call in #handleToolExecutionEnd.

Fixes #3857
2026-06-30 03:41:24 +00:00
oldschoola 3f476d5bbc fix(coding-agent): log Esc/Ctrl+C abort failures instead of swallowing them
The Esc/Ctrl+C teardown path had three duplicated try { abortX() } catch {} blocks (compaction/handoff/retry) that silently swallowed any abort error. Replace them with a shared safeAbort helper that logs the failure at debug, so a failing abort stays diagnosable while teardown ordering and the aborted flag are unchanged.
2026-06-29 18:37:05 -07:00
can1357 ce20cfb68e merge #3829: align /extensions MCP status with /mcp list and persist re-enable 2026-06-30 03:01:01 +02:00
can1357 e8090bb48a feat: introduced binary file detection to prevent encoding corruption
- Introduced `isProbablyBinary` utility to sniff file headers for NUL bytes or invalid UTF-8 sequences.
- Updated `ReadTool` to use the binary sniffer, preventing mojibake corruption in output when reading non-text files.
- Refined `file-mentions` auto-reads to skip binary files and mark them as `binary` in the message transcript.
- Added comprehensive unit tests for binary detection logic, covering NUL bytes, truncated multibyte characters, and path-based file sniffing.
2026-06-30 02:59:41 +02:00
oldschoola 718c7cea2f perf(coding-agent): memoize incremental grapheme slicing in streaming reveal
Each 30fps streaming-reveal tick re-segmented the whole revealed prefix via sliceGraphemes (per-tick slice cost grew with the prefix). BlockUnitCounter already memoized per-block grapheme counts; apply the same idea to slicing: cache the slice boundary per block index and re-segment only the per-step delta from the boundary cluster, so per-update cost grows with the delta instead of the rendered prefix.

buildDisplayMessage gains an optional sliceOf seam (default = sliceGraphemes, so existing callers are unchanged); the controller wires its BlockUnitCounter.slice through it via a #build helper that also de-duplicates six previously-repeated buildDisplayMessage call sites.

Only an exact (text, units) hit skips segmentation; the incremental guard (text === cached.text || text.startsWith(cached.text)) && units >= cached.units re-segments from the boundary cluster, so an append that extends the final cluster (e.g. a -> a combining acute, ZWJ family merge) is never stale.

Benchmark (bench/streaming-throughput.bench.ts, 30520-grapheme message, 61 ticks/episode): 23.78ms -> 2.27ms per episode (~10x). Regression tests vs a pure Intl.Segmenter reference cover fixed-text growing units, append growth, boundary-cluster extension, multi-block indices, shrink/regrow, full replace, and a 400-step seeded fuzz.
2026-06-29 17:47:22 -07:00
oldschoola 8288ebb525 perf(tui): reduce streaming reveal + render cost ~2.3x
Three changes to the streaming text-reveal path (the 30fps typewriter animation during model streaming), cutting real per-tick render CPU ~2.3x (measured ~73ms -> ~31ms to animate a 12k-char response; ~1.35ms -> ~0.57ms per tick).

- streaming-reveal: incremental grapheme slicing. BlockUnitCounter.slice resumes segmentation from the cached boundary cluster each tick instead of re-segmenting the whole revealed prefix (O(delta) vs O(revealed)). Handles fixed-text catch-up and append-only live streaming via startsWith + boundary-cluster re-segment.

- assistant-message: fix the transient flag dropped on the fast path. #tryFastPathUpdate(message) was called without opts, so streaming updates always set Markdown.transientRenderCache=false, running code syntax highlighting + L2 cache-key work every tick. Now passes opts through; highlighting applies once at message finalization.

- markdown: render-prefix cache for transient renders. Caches content lines (render+wrap+margin) for the stable streaming-lex prefix tokens [0..frozenCount-2] and re-renders only the changed tail each tick. Byte-identical to the full pipeline (OSC66 margin state resets per token; nextTokenType boundary handled; content-identity guard).

Correctness: byte-identity verified (transient split == full render across 57 reveal steps); BlockUnitCounter.slice verified against a pure Intl.Segmenter reference across fixed/append/cluster-extend/ZWJ-merge/shrink/replace/fuzz cases. Regression tests added in both packages.
2026-06-29 16:59:10 -07:00
Mathews-Tom 1620d6e456 Merge remote-tracking branch 'upstream/main' into feat/secret-friendly-names 2026-06-30 04:28:22 +05:30
roboomp e34f2a81e9 fix(tui): force-enabled MCP from tool-owned sources via enabledServers
Codex review on #3829: when an MCP server lives in a non-writable
source config such as opencode.json with enabled:false, the dashboard
re-enable had nowhere to write to — the writable mcp.json fallback
did not own the server, so setMcpServerEnabled fell through to the
denylist and the source's enabled:false kept the row disabled.

Added a parallel allowlist to the user-level mcp.json that overrides
a non-writable source's enabled:false flag without ever mutating the
foreign config:

- types + schema: new enabledServers array (mirrors disabledServers).
- config-writer: readEnabledServers + setServerForceEnabled helpers,
  and setMcpServerEnabled now writes to enabledServers on enable
  when no writable mcp.json owns the server, clears it whenever a
  writable source becomes the source of truth, and always clears the
  override on disable so a force-enabled server can be turned off.
- mcp/config (runtime loader) and state-manager (dashboard read):
  honor enabledServers as an override on enabled:false, while still
  letting disabledServers win.
- Added a regression test that walks the full lifecycle for an
  opencode.json server: enabled:false is surfaced as disabled, the
  dashboard re-enable force-enables via enabledServers without
  touching opencode.json, then disable clears the override and
  populates disabledServers.

Fixes #3827
2026-06-29 20:39:50 +00:00
roboomp 16ef3c54f4 fix(tui): re-enabled MCP alternate config sources
Codex review on #3829: the dashboard re-enable path still missed MCP
servers loaded from supported non-primary native config files such as
.omp/.mcp.json or user .mcp.json. Those rows carry enabled:false from
their source file, so falling back to the user disabledServers denylist
could not make the row active again.

- setMcpServerEnabled now accepts the loaded row's sourcePath and checks
  it before the primary project/user mcp.json paths.
- extension-dashboard passes the source path for writable MCP providers
  (native and mcp-json), avoiding accidental edits to third-party tool
  configs while still updating .omp/.mcp.json and standalone MCP JSON
  sources.
- Added a regression test for a server loaded from .omp/.mcp.json with
  enabled:false; re-enable flips that file to enabled:true and does not
  write the denylist.

Fixes #3827
2026-06-29 20:26:16 +00:00
roboomp 812b246e7e fix(tui): dashboard re-enable flips enabled:false in mcp.json
Codex review on #3829: when an MCP server's mcp.json entry carries
enabled:false, the dashboard toggle previously only removed the name
from the user-level disabledServers denylist. state-manager's new
`server.enabled === false` check (state-manager.ts:156) then still
marked the row disabled, leaving such servers impossible to re-enable
from /extensions.

Extracted setMcpServerEnabled() into mcp/config-writer.ts mirroring
/mcp enable | /mcp disable semantics:

- Server defined in project mcp.json -> update enabled on that entry.
- Else server defined in user mcp.json -> update enabled on that entry.
- Else (discovered third-party server) -> use the user-level disabledServers denylist.
- On re-enable, always clear any stale denylist entry.

extension-dashboard.ts routes mcp:* toggles through this helper. Added
four new regression tests covering: enabled:false re-enable, mixed
flag+denylist re-enable, disable on a config-resident server writing
enabled:false (not denylist), and discovered-server denylist round-trip.

Fixes #3827
2026-06-29 20:13:15 +00:00
roboomp 6256f97eaf fix(tui): aligned /extensions MCP status with /mcp list
Two read paths previously diverged on whether an MCP server was active or
disabled. /mcp list (slash-commands/helpers/mcp.ts:388) treats a server
as disabled when config.enabled === false OR the name is in the
user-level disabledServers denylist; the runtime MCP loader does the
same in mcp/config.ts:115. The /extensions dashboard only consulted
the dashboard-private settings.disabledExtensions array, so a server
disabled via /mcp disable or enabled:false kept showing as active.

Toggling MCP servers from the dashboard had the mirror problem: it only
wrote to settings.disabledExtensions, so /mcp list never noticed.

- state-manager: read user-level disabledServers from mcp.json once and
  consider enabled:false / denylist membership when deriving each MCP
  extension's state, matching /mcp list semantics.
- extension-dashboard: route mcp:* toggles through setServerDisabled
  against the canonical mcp.json denylist, and clean any legacy
  settings.disabledExtensions entry on re-enable so it doesn't keep the
  server marked disabled.
- Added a regression test exercising both read signals and the
  setServerDisabled round-trip the dashboard's MCP toggle now uses.

Fixes #3827
2026-06-29 20:05:04 +00:00
Mathews-Tom b2ede40e7a Merge remote-tracking branch 'upstream/main' into feat/error-notify 2026-06-30 01:21:59 +05:30
Jeff Scott Ward f2b760a163 fix: clear todo reminder on btw branch 2026-06-29 11:58:40 -04:00
Jeff Scott Ward 3854b0ec16 fix: describe hidden thinking pulse 2026-06-29 11:57:59 -04:00
Mathews-Tom e7161c96f2 Merge remote-tracking branch 'upstream/main' into feat/error-notify 2026-06-29 16:43:36 +05:30
can1357 68285aa9d5 fix(coding-agent): throttled tui tool argument parsing and updates
- Optimized TUI tool argument previews by throttling JSON re-parsing to prevent frame starvation during high-frequency streaming.
- Suppressed redundant component updates for unchanged parsed fields while maintaining raw preview integrity for bash and patch renderers.
- Added adaptive parsing logic to `ToolArgsRevealController` that distinguishes between renderers requiring continuous raw JSON streams and those consuming parsed arguments.
- Updated `EventController` to dynamically determine exposure requirements based on tool type and wire-format metadata.
2026-06-29 07:11:18 +02:00
Mathews-Tom 2a9ec3a7aa Merge remote-tracking branch 'upstream/main' into feat/secret-friendly-names
# Conflicts:
#	packages/coding-agent/src/eval/__tests__/julia-prelude.test.ts
2026-06-29 02:48:29 +05:30
can1357 4db3d68bdb feat(coding-agent): implemented git worktree detection and rendering
- Added `git.repo.linkedWorktreeSync` to identify and resolve git worktree metadata without spawning subprocesses.
- Updated `StatusLineComponent` to detect linked worktrees and resolve project/worktree context names.
- Modified path segment rendering to collapse nested git worktree paths and display the worktree name when it diverges from the active branch.
- Introduced `icon.worktree` symbol across themes to visually distinguish git worktree paths.
2026-06-28 22:50:30 +02:00
can1357 6e3d401239 Merge remote-tracking branch 'origin/farm/04d319fb/ctrl-q-follow-up-queue-sends-the-literal' 2026-06-28 21:51:22 +02:00
Mathews-Tom 98edcb4388 Merge remote-tracking branch 'upstream/main' into feat/secret-friendly-names 2026-06-28 22:33:27 +05:30
can1357 6dab2b3196 Merge PR #3604: fix stale todo HUD rows (@jeffscottward) 2026-06-28 18:55:26 +02:00
can1357 0ffe6c8e1f Merge PR #3675: keep moved sessions resumable (@riverpilot)
# Conflicts:
#	packages/coding-agent/src/modes/controllers/command-controller.ts
2026-06-28 18:46:19 +02:00
roboomp 404a43e02b fix(coding-agent): expand paste markers in Ctrl+Q follow-ups
InputController.handleFollowUp read raw editor text via getText(),
bypassing the paste-store expansion the Enter path applies through
Editor.getExpandedText(). A large paste collapsed into a [Paste #N, +X
lines] marker was therefore sent verbatim to the model when queued with
Ctrl+Q / Ctrl+Enter, silently dropping the pasted content.

Switch the follow-up path to getExpandedText() so queued submissions
match the Enter path. Image markers are untouched; pendingImages
forwarding is unchanged.

Updated existing input-controller stubs (skill-queue, followup-image,
keybindings) to implement getExpandedText, matching the production
CustomEditor surface.

Fixes #3737
2026-06-28 16:42:39 +00:00
roboomp 482091dc73 fix(coding-agent): replan title refresh honors TITLE_SYSTEM.md override
The replan-driven title refresh (title.refreshOnReplan, fired after a
`todo init`) called `generateSessionTitle()` without the user's
`TITLE_SYSTEM.md` override, silently falling back to the bundled
`prompts/system/title-system.md` and overwriting auto titles with the
default policy. The override was only ever discovered by main.ts and
passed into the first-input title path on InteractiveMode, never into
`AgentSession.#refreshTitleAfterReplan`. Most visible in Plan Mode,
which initializes todos early.

`AgentSession` now owns the resolved title prompt:
- New `CreateAgentSessionOptions.titleSystemPrompt` threaded by
  `createAgentSession()` into the constructor.
- New `AgentSessionConfig.titleSystemPrompt` stored on
  `#titleSystemPrompt` with a `get titleSystemPrompt` /
  `setTitleSystemPrompt(...)` pair.
- `#refreshTitleAfterReplan` passes `#titleSystemPrompt` as
  `customSystemPrompt` to `generateSessionTitle()`.
- `input-controller.ts` reads from `session.titleSystemPrompt`, and
  the duplicate `InteractiveMode.titleSystemPrompt` field /
  constructor arg / `InteractiveModeContext` field / `runInteractiveMode`
  parameter are removed. `InteractiveMode.refreshTitleSystemPrompt`
  now calls `session.setTitleSystemPrompt(...)` so a `/move`-style cwd
  change keeps the override in sync.

Regression test asserts the prompt handed to `completeSimple()` from
`#refreshTitleAfterReplan` is the configured override, not the bundled
`title-system.md`.

Fixes #3734
2026-06-28 16:32:15 +00:00
Mathews-Tom 693c13a85b Merge remote-tracking branch 'upstream/main' into feat/error-notify
# Conflicts:
#	packages/coding-agent/src/modes/controllers/event-controller.ts
2026-06-28 21:50:13 +05:30
Jeff Scott Ward 917d2834a3 fix: address todo reminder review feedback 2026-06-28 12:03:06 -04:00
Jeff Scott Ward 9a9dc88f43 fix: address todo HUD review feedback 2026-06-28 11:16:30 -04:00
can1357 8185fdbfa9 feat(coding-agent): queued tool argument updates to prevent edit loss
- Implemented a queueing mechanism in `ToolExecutionComponent` to prevent starvation of edit previews during high-frequency argument updates.
- Replaced eager cancellation of in-flight diff computations with a drain loop that ensures every update is processed once the current compute settles.
- Added `partialJsonOf` helper to safely narrow streamed JSON buffers from tool arguments.
- Added regression test to verify that slow diff computations are not aborted by incoming stream chunks and instead queue a subsequent re-run.
2026-06-28 17:11:43 +02:00
Jeff Scott Ward 54f83e794e fix: preserve subagent hint API 2026-06-28 11:09:17 -04:00
Jeff Scott Ward dd917046d8 fix: anchor todo reminder HUD 2026-06-28 11:09:16 -04:00
can1357 fbad280b57 feat: implemented multi-advisor concurrent runtime with tui management
- Introduced comprehensive support for multiple concurrent, independently-configured advisors via `WATCHDOG.yml` files.
- Implemented a full-screen TUI overlay for managing advisor rosters, models, tools, and instructions.
- Added session-wide advisor initialization, telemetry aggregation, and named transcript isolation.
- Enhanced advisor security and observability with secret redaction in tool results and secure XML attribute encoding.
2026-06-28 12:55:09 +02:00
can1357 51a2a0342f test(coding-agent): implemented guest reconciliation and expanded testing for collaboration
- Introduced guest snapshot reconciliation to maintain host state consistency during session switching.
- Improved yield tool reliability by implementing incremental schema validation and strict parameter enforcement.
- Fixed a calculation edge case in the status line to prevent negative time values during activity tracking.
- Expanded the test suite with new validation for session interruption, collab state synchronization, and process error handling.
2026-06-28 09:52:44 +02:00
can1357 267926c8f7 feat(agent): enabled image attachments and draft restoration for skill commands
- Updated invokeSkillCommand to accept image and link attachments.
- Added draft restoration logic to preserve input state if command dispatch fails.
- Modified invocation path to pass image data to invokeSkillCommandFromText for processing.
- Refactored command dispatch flow to handle errors by restoring the user's composer draft.
2026-06-28 09:52:44 +02:00
roboomp f82086f805 refactor(coding-agent): moved Alt+M context gate into setModel
Replaced the controller-side switchActiveModel flag with a currentContextTokens hint on AgentSession.setModel, so the over-context decision is computed against the refreshed candidate metadata. setModel returns whether the live switch happened, and the Alt+M default-role path uses that to gate the live side effects.
2026-06-28 07:13:03 +00:00
roboomp 3765d80cbc fix(coding-agent): fixed alt-m default over context
Decoupled default-role persistence from live model switching when the selected model is below the current session context window.

Updated the model selector regression coverage so the Alt+M Default action remains selectable and advances to thinking selection.

Fixes #3708
2026-06-28 07:04:31 +00:00
can1357 96a1aed196 feat(coding-agent/modes): replaced static idle recap with LLM-generated summary
- Replace the static "Goal/Next" status line with an ephemeral LLM-generated summary triggered after idle periods.
- Hook the recap into the agent's side-channel pipeline, using live goal and task state as context anchors for meaningful recaps.
- Implement abort logic so that active user interactions immediately cancel pending recaps and discard late-arriving responses.
2026-06-28 08:15:24 +02:00
can1357 1852329c8c feat(coding-agent): added compact status line thinking level setting
- Added `statusLine.compactThinkingLevel` setting to render the thinking level as a leading icon.
- Replaced the verbose ` · <level>` suffix with a single glyph when compact mode is enabled.
- Updated the status line controller and component to resolve and propagate the new configuration.
2026-06-28 08:04:55 +02:00
can1357 2592b9dfe3 merge #3684: track active processing time for time_spent segment
# Conflicts:
#	packages/coding-agent/src/modes/controllers/event-controller.ts
2026-06-28 07:52:36 +02:00
can1357 5cdbf67cf3 fix focused stream preservation on rebuild 2026-06-28 07:49:55 +02:00
can1357 a3f4abd777 merge #3658: preserve in-flight assistant turn across /shake rebuilds 2026-06-28 07:49:49 +02:00
can1357 00f6468e9b merge #3672: allow thinking toggle after streamed reasoning 2026-06-28 07:49:41 +02:00
can1357 443195a81f merge #3700: preserve queued skill invocations during compaction
# Conflicts:
#	packages/coding-agent/src/session/messages.ts
#	packages/coding-agent/test/session-messages.test.ts
2026-06-28 07:49:33 +02:00
can1357 d18e2c2f4d feat(agent): introduced idle progress recaps and optional session titles
- Added idle recap functionality to EventController to display goals and next actions when the agent is inactive.
- Updated session parsing in gc-cli and memories to correctly support optional title entries in session files.
2026-06-28 07:27:01 +02:00
roboomp 796bf51f46 fix(coding-agent): preserved queued skill images
Kept image content attached to compaction-queued skill prompts when they are rebuilt as custom messages.
2026-06-28 04:46:26 +00:00
roboomp 9cfbece323 fix(coding-agent): queued retry-drained skill prompts
Kept compaction-queued skill prompts in the agent queue during retry drains instead of allowing them to start a fresh turn after compaction unwinds.
2026-06-28 04:34:48 +00:00
roboomp 80e772ba4d fix(coding-agent): preserved queued skill invocations
Rebuilt compaction-queued /skill: commands as user-attributed skill prompts when the queue drains.

Fixes #3697
2026-06-28 04:21:07 +00:00
roboomp 3c97ad1e15 fix(tui): reset time_spent meter on AgentSession.switchSession file swap
Address PR review: switchSession (/resume, /move, ACP fork/load,
RPC switch_session, extension switchSession) mutates the loaded
session file in place under the same AgentSession ref, so a WeakMap
keyed only on the AgentSession ref carried the previous
conversation's meter into the resumed one — the footer kept showing
the previous total after resuming a different idle session.

Snapshot the loaded sessionFile path in the per-session meter and
detect a real-to-real transition inside #meter(): on a swap, drop
the old meter and start a fresh one. The undefined → real first-save
transition only refreshes the snapshot (same conversation, same
identity, accumulated time preserved). #closeStaleActiveWindow now
routes through #meter() so the file-change check applies there too.

Adds two regression tests covering the real-to-real swap and the
first-save no-reset.
2026-06-27 21:12:10 +00:00
roboomp 8c6b2db1f7 fix(tui): track time_spent meters per session
Address PR review: SessionFocusController synthesizes agent_start on
mid-turn attach but does not pair it with a synthetic agent_end on
unfocus. With a single shared StatusLineComponent meter, returning to
the main session while a subagent was still streaming left
#activeStartedAt open, so the main status line kept ticking through
idle time after the subagent finished.

Replace the single #activeMs / #activeStartedAt fields with a WeakMap
keyed on AgentSession. markActivityStart / markActivityEnd /
getActiveMs / resetActiveTime all operate on the currently-attached
session's meter, so detaching from a subagent never bleeds its open
window into main. setSession closes a stale window (in-flight + new
session not streaming) on re-focus so a subagent that finished while
we were detached does not credit the detached gap.

Adds two regression tests covering both cases.
2026-06-27 21:01:14 +00:00
roboomp 68db2ce649 fix(tui): track active processing time for time_spent status segment
The time_spent segment rendered Date.now() - sessionStartTime, so an
idle session displayed hours of "time spent" while the agent did
nothing — the only inputs were wall-clock and the unmoving session
start.

Replace sessionStartTime with activeMs in SegmentContext and accumulate
inside StatusLineComponent across agent_start -> agent_end windows.
markActivityStart/markActivityEnd are idempotent (reentrant agent_start
events and superseded agent_end events never double-count); the segment
ticks live during an open window and freezes when the agent yields.
The session-boundary hook drops the now-meaningless wall-clock argument
and is renamed setSessionStartTime -> resetActiveTime; it zeroes the
accumulator and drops any in-flight window so /clear / fresh-session /
joined-collab paths start the meter at zero.

Fixes #3681
2026-06-27 20:50:19 +00:00