Commit Graph

679 Commits

Author SHA1 Message Date
can1357 ab7f623ac4 Merge PR #4149: fix(tui): coalesce editor top-border rebuild to render tempo (#4145) (@roboomp) 2026-07-01 21:53:18 +02:00
can1357 edef5c052d Merge PR #4056: fix(tui): smooth write streaming previews (@roboomp) 2026-07-01 21:53:14 +02:00
can1357 cdd6e558e9 Merge PR #4049: fix(collab): enable mobile ask responses (@roboomp)
# Conflicts:
#	packages/coding-agent/src/collab/protocol.ts
2026-07-01 21:53:00 +02:00
can1357 637c1105de Merge PR #3851: fix(coding-agent): log Esc/Ctrl+C abort failures instead of swallowing them (@oldschoola) 2026-07-01 21:47:54 +02:00
can1357 dae0ab3580 Merge PR #3848: perf(coding-agent): memoize incremental grapheme slicing in streaming reveal (@oldschoola) 2026-07-01 21:47:54 +02:00
can1357 b3b4a762ac Merge PR #3736: fix(coding-agent): replan title refresh honors TITLE_SYSTEM.md override (@roboomp) 2026-07-01 21:42:25 +02:00
roboomp 809a8a348d fix(tui): coalesce editor top-border rebuild to render tempo
EventController.handleEvent rebuilt the editor's status-line top border
synchronously on every session event via updateEditorTopBorder(). During
a long-running eval that fires 5-10 events/s, each rebuild ran
StatusLine.getTopBorder → #buildSegmentContext → getCachedContextBreakdown
→ session.getContextUsage → estimateTokens (with JSON.stringify per
toolCall block) — the render pipeline is throttled to ~30 fps, so most
rebuilds were dropped before painting. Combined with a scheduler that
collapsed cadenceDelay to zero whenever a frame overran the 33ms budget,
the TUI busy-looped at ~40-50% CPU.

Fix:
- Editor gains setTopBorderProvider(): a lazy builder invoked once per
  editor render. InteractiveMode installs it in the constructor and on
  setEditorComponent, so the rebuild coalesces to the render tempo
  regardless of event rate.
- Delete updateEditorTopBorder wrapper (now equivalent to
  ui.requestRender) and inline every call site.
- Add adaptive render backpressure: a frame that exceeds
  MIN_RENDER_INTERVAL_MS inflates the next scheduling delay to
  2 * last_frame_cost, capped at 200 ms, targeting a 50% render duty
  cycle instead of pinning the CPU at t=0.

New regression tests:
- editor-top-border-provider.test.ts: provider fires exactly once per
  render, wins over eager setTopBorder, falls back when cleared, gets
  the correct availableWidth.
- adaptive-render-backpressure.test.ts: cheap frames keep the 33 ms
  cadence, a slow frame idles proportionally, pathological frames are
  capped at 200 ms.

Verified with bun test packages/tui/test (all 246 relevant tests pass)
and bun test packages/coding-agent/test/modes (455 tests pass). Three
pre-existing agent-session-handoff snapcompact failures on main are
unrelated (snapcompactSupportedChars binding).

Fixes #4145
2026-07-01 13:51:43 +00:00
roboomp 19f3058c55 fix(tui): extended smooth streaming previews to edit and eval
Registered `edit.input` and `eval.code` alongside `write.content` so the reveal controller decodes those top-level string arguments incrementally between throttled full-JSON parses.
Added a regression test covering multi-key extraction so the wire-up survives future additions.

Refs #4043
2026-07-01 05:32:15 +00:00
roboomp 78fda49cfb fix(tui): smoothed write streaming previews
Added incremental decoding for streamed write content so preview args update below the full JSON parse throttle.
Added a regression test covering sub-throttle content growth in ToolArgsRevealController.

Fixes #4043
2026-07-01 05:25:36 +00:00
roboomp 9d3918bdac fix(collab): enabled mobile ask responses
Routed host UI requests through writable collab web guests and added mobile response controls for ask questions.

Fixes #4035
2026-07-01 05:15:37 +00:00
can1357 763f950b7f fix: streamlined tool-call cloning and validation in the stream pipeline
- Improved the leaked-thinking stream projector to clone and sync native tool-call blocks directly.
- Eliminated the need for placeholder IDs and complex rekeying logic in the event controller and argument reveal module.
- Simplified native tool-call validation in owned-stream processing by requiring only a non-empty name.
- Added comprehensive unit tests to ensure tool-call IDs and partial JSON parameters remain intact during healing.
2026-07-01 05:25:27 +02:00
can1357 ef7636805b feat(coding-agent): removed canonical model variant selection and tracking
- Removed the canonical model variant indexing, selection, and tracking logic from the model registry and resolver.
- Eliminated the `canonical` sub-command, tab view, search tokens, and equivalence configuration structures from the CLI and model selector components.
- Refined model identification, lookup, and provider fallback resolution to bind exclusively to standard, raw model IDs.
- Relocated the equivalence utility script within the catalog package to support script-only policy generation.
2026-07-01 05:22:42 +02:00
can1357 bdb33a184d feat(coding-agent/modes): tracked and migrate stream-key placeholders for tool calls
- Added `#streamTurnNonce` to prevent aborted streaming turns from corrupting content indexes of subsequent messages.
- Implemented temporary stream-key generation using content position and turn nonces for previewing tool calls without native IDs.
- Added migration logic to key pending tool previews by their real ID and rekey `ToolArgsRevealController` once the real ID is parsed.
2026-07-01 00:34:21 +02:00
can1357 112317bc8e fix(coding-agent): resolved duplicate todo reminders in terminal scrollback
- Anchors the incomplete-todo reminder block inside the scrollback transcript instead of a floating live container.
- Eliminates duplicate reminder copies piling up in terminal scrollback during terminal reflows.
- Removes the dedicated `todoReminderContainer` and simplifies state synchronization on todo reload.
- Updates tests to verify sequential reminders commit as separate blocks and are left intact when tools succeed.
2026-06-30 23:32:58 +02:00
can1357 407c44547b feat(coding-agent/modes): opened the in-session resume picker in a fullscreen overlay
- Migrated the `/resume` session selector from an inline component to a fullscreen overlay.
- Enabled alternate screen buffer borrowing and mouse tracking support for the picker.
- Ensured proper cleanup of the fullscreen overlay during normal termination or shutdown.
- Configured the selector layout to pin keybinding hints and the footer to the bottom of the screen.
2026-06-30 20:39:45 +02:00
can1357 e0bacbf2ec feat(coding-agent/modes): deferred handling streamed tool calls without an ID
- Skipped processing tool calls in the event controller streaming message when the tool ID is missing.
- Prevented creating orphaned empty placeholder cards caused by empty IDs during early Anthropic and OpenAI tool block streaming.
2026-06-30 17:38:41 +02:00
roboomp 3106a15f7d fix(mcp): raced oauth login against cancellation
Esc and wizard abort signals now race the MCP OAuth login promise directly, so cancellation wins even before OAuthCallbackFlow reaches its callback wait and registers an abort listener. OAuthCallbackFlow also checks pre-aborted signals before opening/waiting on the callback server and its wait path handles already-aborted signals.

Threaded the abort signal into MCP OAuth fetches so dynamic client registration, metadata discovery, authorization probes, and token exchange unblock promptly when the user cancels.

Added a regression test where MCPOAuthFlow.login never observes ctrl.signal, matching the pre-wait race called out in review.

Fixes #3888
2026-06-30 10:21:26 +00:00
roboomp a6b2bac882 fix(mcp): made Esc cancel /mcp reauth and /mcp add OAuth flow
#handleOAuthFlow now installs an editor.onEscape hook that aborts its
AbortController, and accepts an external abortSignal so the add-wizard
can thread its own controller through (the wizard owns focus and absorbs
Esc itself). Cancellation surfaces as MCPOAuthCancelledError, which the
reauth and add catches translate into a neutral status line instead of
the generic OAuth failure banner. Disambiguated from the existing 5-min
timeout via a userCancelled flag so timeouts still read as errors.

The wizard intercepts Esc/Ctrl+C while #oauthAbort is set so its own
"Press Esc to cancel" advertisement now matches the behaviour, and
renames its error heading + tip when the failure is a user cancel. Also
fixed the misleading "(Press Ctrl+C to cancel)" message in the chat
transcript onAuth block to say "Press Esc" — Ctrl+C is bound to the
editor clear action, not interrupt.

Fixes #3888
2026-06-30 10:07:05 +00:00
roboomp 88be72e4d4 fix(coding-agent): seed tool-args reveal with the partial JSON already in hand
ToolArgsRevealController.setTarget initialized new entries with revealed=0, so the first message_update returned { __partialJson: "" } even when the provider had already parsed a complete chunk. For renderers without exposeRawPartialJson (e.g. write), the throttled re-parse + cached displayArgs short-circuited every subsequent setTarget, leaving the preview body blank until tool_execution_end.

Seed revealed with the full incoming partialJson length on entry creation (clamped to a surrogate-safe boundary). The first frame now carries the parsed path/content immediately; subsequent message_updates extend target and the reveal ticks pace only the newly arrived bytes — no field is ever truncated because the seeded prefix is the longest the entry has seen so far.

Fixes #3881
2026-06-30 08:04:49 +00: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
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
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
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
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
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 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 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 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 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
Alexander Kirilin f209087cfd fix(omp): keep moved sessions resumable 2026-06-27 15:57:09 -04:00
roboomp 9302e17545 fix(tui): updated streaming thinking visibility
Propagated the first observed reasoning-content unlock to the active streaming assistant component before the reveal controller re-renders it. Added a regression that starts a hidden thinking-off stream and verifies the first reasoning delta becomes visible.
2026-06-27 17:30:02 +00:00
roboomp 4d0ef260b6 fix(tui): allowed thinking toggle after streamed reasoning
Tracked received thinking content per interactive session so OpenAI-compatible providers that omit reasoning metadata can still reveal streamed reasoning blocks. Added a Ctrl+T regression covering the unlocked visibility path.

Fixes #3669
2026-06-27 17:18:04 +00:00
can1357 357c29224d feat: implemented symbol-based streaming state for isolated metadata
- Migrated internal streaming state from string-based properties to symbol-keyed properties for improved data isolation and safety.
- Replaced the deprecated `stripVariant` utility with centralized `clearStreamingPartialJson` and symbol-specific helper methods across all provider implementations.
- Implemented `stripStreamingBlockSymbols` and updated deep equality checks to ensure metadata does not interfere with content comparisons.
- Standardized streaming metadata access through a new `block-symbols` utility module.
2026-06-27 12:07:08 +02:00
can1357 c3f7e849e5 refactor: centralized AI error handling into a dedicated module
- Migrated 288 lines of scattered error classification logic from `utils/error-id.ts` into a cohesive `packages/ai/src/error/` module with 13 specialized submodules covering flags, classes, OAuth, providers, rate-limiting, and finalization.
- Replaced 100+ generic `Error` throws across 60+ provider and registry files with semantic `AIError.*` classes (e.g., `AIError.MissingApiKeyError`, `AIError.OAuthError`, `AIError.ProviderResponseError`), improving error diagnostics and retry logic.
- Consolidated error utility imports from `pi-utils` and scattered classification functions into a single `AIError` namespace, reducing coupling and simplifying error handling across all packages.
2026-06-27 10:44:13 +02:00
can1357 3273d0de68 Fix /move session cleanup ownership 2026-06-27 02:04:50 +02:00
can1357 bc2d3c2c86 Merge PR #3401: feat(move): interactive /move overlay with fresh session (@oldschoola) 2026-06-27 02:04:50 +02:00
can1357 cf8ac5b2f1 Merge PR #3496: fix(tui): guard streaming Esc cancellation (@roboomp) 2026-06-27 01:39:35 +02:00
can1357 393d068a22 Merge PR #3343: feat(usage): show expiry dates for banked Codex resets (@oldschoola) 2026-06-27 01:39:33 +02:00