Commit Graph
9801 Commits
Author SHA1 Message Date
roboomp bef1f5d773 fix(mcp): merge challenge scopes into JSON-body oauth endpoints
`/mcp reauth` and `/mcp add` consume `authResult.oauth` directly when the JSON error body carries endpoints, skipping `discoverOAuthEndpoints`. Leaving `oauth.scopes` empty there meant a challenge-only `scope="…"` still yielded a scope-less grant even though `AuthDetectionResult.scopes` recorded it.

Merge `challengeScopes` into the returned `OAuthEndpoints` inside `analyzeAuthError` so every consumer sees the same scope. Added a regression test where the JSON body advertises endpoints without `scopes` and the challenge is the only source.

Refs #4467
2026-07-03 16:15:01 +00:00
roboomp debce0757b fix(mcp): carry OAuth scopes from challenge and resource metadata
MCP servers such as JIT gateways advertise required scopes via the RFC 6750 `WWW-Authenticate` challenge (`scope="..."`) and via RFC 9728 protected-resource metadata (`scopes_supported` / `scopes` / `scope`), then reject follow-up requests with `insufficient_scope` when a bearer token was issued without them. OMP's discovery only picked up `scopes_supported` from the auth-server metadata document, so `/mcp reauth`, `/mcp add`, and the MCP add wizard silently minted scope-less tokens.

- Extract `scope`/`scopes` from the WWW-Authenticate challenge into a new `AuthDetectionResult.scopes` field via `extractOAuthChallengeScopes`.

- Thread a `protectedScopes` option through `discoverOAuthEndpoints` and its recursion; capture `scopes_supported`/`scopes`/`scope` off resource-metadata documents; use those scopes when the auth-server metadata omits them.

- Pass `authResult.scopes` from `analyzeAuthError` into every discovery call site (`/mcp reauth`, `/mcp add`, MCP add wizard).

- Add regression tests for insufficient_scope + resource_metadata, resource-metadata `scopes_supported` passthrough, and challenge-scope threading.

Fixes #4467
2026-07-03 16:10:21 +00:00
roboomp 065f0da7c2 fix(lsp): threaded tool abort signal through ACP-bridge notify
Bridge-backed writes now respect tool timeout/cancel: routeWriteThroughBridge takes an optional AbortSignal and forwards it to notifyWorkspaceWatchedFiles, so a wedged LSP server no longer hangs the bridge path.

Updated write, replace, patch, and hashline callers to pass the tool signal.

Refs #4459
2026-07-03 15:57:42 +00:00
roboomp 75fc4d65bb fix(lsp): notified servers about harness file writes
Announced harness-authored create, change, and delete operations to active LSP clients with workspace/didChangeWatchedFiles before edit-time diagnostics are read.

Added regression coverage for non-LSP sibling files in a batched write so diagnostics see the workspace state the harness just produced.

Fixes #4459
2026-07-03 15:48:48 +00:00
roboomp cf908b1727 fix(lsp): deduped identical replacement edits
Collapsed duplicate byte-identical non-empty LSP text edits before overlap validation so rename_file accepts idempotent server output while still rejecting real conflicts.

Fixes #4458
2026-07-03 15:40:21 +00:00
roboomp 6976ccca80 fix(mnemopi): used explicit session state type 2026-07-03 15:14:49 +00:00
roboomp 536ecc725a fix(mnemopi): let agents read stored memories in full
Recall silently clipped every result to 500 chars mid-word with no marker,
and memory_edit update replaces content wholesale by id. There was no way
for an agent to inspect the full row before overwriting it: Mnemopi.get
existed but nothing surfaced it, and the advertised URI only served the
file-backed memory summary. The natural recall/inspect/update loop had no
inspect step.

Three fixes across mnemopi and coding-agent:
- recall now appends an ellipsis marker when it clips content and reports
  truncated=true plus full_length. The cap is exposed as
  RecallOptions.contentPreviewChars (default 500, 0 disables). The factLine
  200-char clip used by the enhanced-context sandwich gets the same marker.
- Under the mnemopi backend the read-tool URI scheme now routes an id host
  to Mnemopi.get() across every session's scoped banks, returning the row
  as text/markdown with a YAML-frontmatter header (bank, store, source,
  timestamp, importance, veracity). The root namespace remains for the
  file-backed summary. Miss errors now name the backend explicitly.
- Updated the recall and memory_edit tool prompts to document the
  truncation marker and require reading the full memory before any
  wholesale content update.

Fixes #4443
2026-07-03 15:05:43 +00:00
roboomp 437a871304 fix(eval): surfaced preserved patch path in agent() failure
runEvalAgent short-circuits on subagent error before mergeIsolatedChanges runs, so branch-mode transfer failures previously threw with just result.error and the caller never learned where the preserved patch lived. Enrich the pre-merge failure and reuse a shared recovery-hint builder for the apply/nested-patch failure paths so the captured patch path, branch name, and nested-patch artifacts are always surfaced.

Fixes #4437
2026-07-03 14:51:52 +00:00
roboomp 3b135eead8 style: bun run fix 2026-07-03 14:19:32 +00:00
roboomp cca325d243 fix(task): preserved isolated commits on transfer failure
Capture a patch artifact from the live isolation worktree when branch-mode commit transfer fails, before cleanup can destroy the untransferred objects.

Fixes #4437
2026-07-03 14:19:32 +00:00
roboomp 12acf7e645 fix(task): auto-skipped empty commits during task-branch cherry-pick
An intermediate commit whose net effect is already on HEAD (redundant
change, or 3-way merged to HEAD by "theirs == ours") stopped the
sequencer with "The previous cherry-pick is now empty" and was
treated as a hard conflict. mergeTaskBranches aborted the whole range,
marked the branch failed, and dropped every remaining non-overlapping
commit.

Add cherryPick.skip and cherryPick.isEmptyError to the git namespace,
then in mergeTaskBranches' catch classify the failure before aborting:
loop --skip while the error stderr matches the "now empty" phrase so
consecutive empties advance the sequencer; fall through to abort/fail
on the first non-empty error (genuine conflict with unmerged files).

Fixes #4438
2026-07-03 14:19:16 +00:00
Matt Wilkinson e86a6ede24 fix(session): drop content-less provider-rejection turns from persisted history
A request the provider rejects (e.g. 413 oversized payload) yields a
synthesized assistant turn with empty content and stopReason 'error'.
That turn is written to session.jsonl, so on reload it replays as an
empty assistant turn and re-sends the same rejected context. Keep the
rejection UI-only (pinned error) and out of persisted history so a
reloaded session resumes from the last good turn.
2026-07-03 09:21:53 -04:00
Matt Wilkinson 912176511a feat(statusline): make git segment jj-aware
Under jj the git branch label is unhelpful - a colocated repo parks git
HEAD detached, and a secondary jj workspace has no git at all. The `git`
segment now overlays the jj working-copy label (bookmarks + short
change-id) in both cases, via a read-only `jj log --ignore-working-copy`
query that is throttled and cached (and force-refreshed on a git-HEAD
change). Plain git repos and non-jj dirs are unchanged. Extracted a
testable `jj-info` module (root detection, query, format).

Refs can1357/oh-my-pi#3582
2026-07-03 09:21:51 -04:00
Matt Wilkinson 792645e707 feat(todo): add blocked status with block/unblock ops
A `blocked` status (plus `block`/`unblock` ops and an optional blocker
note) for tasks that are open but waiting on something the agent can't act
on — a user decision, another agent, or an external action. Blocked tasks
stay in the tracker but are excluded from the stop-time incomplete-todo
reminder (via the existing pending/in_progress allowlist) and from
auto-promotion. Syncs the mode-layer TodoStatus, the ACP status map/guard,
the markdown markers, and the renderers.

Refs can1357/oh-my-pi#3581
2026-07-03 09:21:51 -04:00
roboomp 4409e6cfb5 fix(task): preserved deferred fallback chains
Install subagent retry fallback chains after deferred model patterns resolve so runtime-only candidates keep their ordered fallbacks.\n\nFixes #4421
2026-07-03 09:41:25 +00:00
roboomp 17163a2b9f fix(task): preserved auth fallback for deferred models
Preserve the subagent parent-model auth fallback when an explicit selector resolves only after child runtime provider loading.\n\nFixes #4421
2026-07-03 09:29:11 +00:00
roboomp ddd6ae1182 fix(task): preserved deferred subagent model selectors
Forward unresolved explicit subagent model selectors into child session startup so modelRoles.task cannot disappear during executor preflight and fall through to an unrelated provider default.\n\nFixes #4421
2026-07-03 09:16:30 +00:00
roboomp 721f6d4a08 fix(mcp): make full URL the primary OAuth copy target so SSH sessions work
Codex review flagged that advertising `launchUrl`
(http://localhost:<omp-port>/launch) as the visible `Copy URL:` breaks
SSH/WSL/headless users: their local browser resolves the URL against
the local machine (no OMP listening) and fails before ever hitting the
provider. On terminals without OSC 8 support, they lose the manual
`/login <redirect>` path entirely.

Every OAuth-facing surface now shows the full authorization URL as the
primary copy target and offers `launchUrl` as an additional "Local
shortcut (this machine only)" line for wide-terminal local users who
want the truncation-safe convenience:

- MCPAuthorizationLinkPrompt renders `Copy URL:` with the full URL and
  appends the local-shortcut row only when `launchUrl` differs. OSC 52
  clipboard staging in the MCP onAuth handler switches to the full URL
  (OSC 52 is a wire-level protocol — the terminal writes to the
  caller's LOCAL clipboard even when OMP is on a remote SSH box).
- LoginDialogComponent.showAuth, selector-controller onAuth,
  setup-wizard sign-in, and the auth-broker CLI mirror the pattern:
  full URL first, launchUrl as an optional local shortcut.
- Setup wizard uses `wrapTextWithAnsi`, not truncation, so the RFC
  7636 §4.3 downgrade bug that motivated launchUrl is unreachable
  through it; still surfaces launchUrl for wide-terminal convenience.

Regression tests in
`packages/coding-agent/test/modes/controllers/mcp-authorization-link.test.ts`
now assert:
- Full URL is the primary `Copy URL:` line so SSH sessions can complete.
- launchUrl still appears beneath as `Local shortcut (this machine only): …`
  when it differs from the full URL.
- No shortcut row when launchUrl is absent OR equals the full URL.
2026-07-03 08:57:55 +00:00
roboomp b25775ecbc fix(mcp): resolve /launch route collision and thread launchUrl through RPC client
Codex review flagged two P2s the reporter (@DylanBohlender) confirmed:

1. OAuthCallbackFlow#handleCallback checked LAUNCH_PATH BEFORE the
   `pathname !== this.callbackPath` guard, so an OMP config that pinned
   the provider callback at `/launch` (via `oauth.callbackPath` or a
   matching `oauth.redirectUri`) had the launch route eat its
   `/launch?code=...&state=...` redirect and 302 it back to the
   authorization URL instead of resolving the callback. Reorder so
   `callbackPath` resolution wins the collision, and suppress `launchUrl`
   in that case (also when `redirectUri`'s pathname resolves to `/launch`
   even without an explicit `callbackPath` override) so UIs never
   advertise a self-redirecting copy target.

2. RpcClient.login's `open_url` listener called
   `onOpenUrl(req.url, req.instructions)` and dropped `launchUrl`, so SDK
   hosts built on the public helper couldn't surface the truncation-safe
   copy target. Extend the callback signature to
   `(url, instructions?, launchUrl?)` and forward the field — backward
   compatible for existing 1-2 arg consumers.

Regression tests in packages/ai/test/callback-server-launch-route.test.ts:
- callbackPath = /launch: launchUrl is undefined AND a
  `/launch?code=...&state=...` request resolves via the callback template
  (200/HTML), never 302s to the authorize URL.
- redirectUri pathname = /launch (callbackPath default): launchUrl still
  suppressed defensively by the parsed-pathname guard so the base class
  never advertises a colliding launch route even when callers skip the
  MCPOAuthFlow path-derivation.
2026-07-03 08:46:28 +00:00
roboomp 97c1d08cce fix(mcp): surface a short launch URL and log Windows opener failures for OAuth
Two independent defects broke /mcp reauth against S256-only providers on
Windows boxes whose PATH no longer references System32:

1. openPath spawned bare rundll32 and swallowed the
   `Executable not found in $PATH` throw with a bare `catch {}`, so the MCP
   controller's outer try/catch was dead and the transcript unconditionally
   claimed "Opening browser automatically...".
2. TUI#prepareLine silently truncates any composed row wider than the
   viewport. MCPAuthorizationLinkPrompt rendered `Copy URL: <full URL>` as a
   single ~271-column line whose trailing parameter is
   code_challenge_method=S256. On the reporter's 270-col terminal the cut
   landed inside that parameter, dropping the method while keeping
   code_challenge — which RFC 7636 §4.3 treats as plain PKCE, which Linear
   correctly rejects with "The plain PKCE method is not allowed. Use S256
   instead."

OAuthCallbackFlow now hosts a `GET /launch` route on the same loopback
callback server it already runs; the route 302-redirects to the pending
authorization URL and is advertised as `OAuthAuthInfo.launchUrl` — a
~30-char copy target no viewport can meaningfully truncate. The MCP OAuth
fallback, /login, setup wizard, auth-broker CLI, and login-dialog all
prefer the launch URL for the visible copy target, keep the full URL in
the OSC 8 hyperlink for click-through, and the MCP flow additionally
stages the copy target on the clipboard via OSC 52 (same pattern the
setup wizard uses).

openPath now resolves rundll32.exe through %SystemRoot%\System32 (with a
C:\Windows fallback when SystemRoot is unset) and logs both synchronous
spawn throws and non-zero exits via the shared logger, so silent
misconfigurations show up in ~/.omp/logs/omp.*.log. The dead try/catch
around openPath in the MCP controller is removed.

Fixes #4418
2026-07-03 08:19:14 +00:00
roboomp 712d4e423b docs(tool): documented bash timeout clamp
Documented the 1-3600 second bash timeout clamp in the schema, model-facing prompt, and tool docs, including the async timeout behavior.

Added coverage that the shipped schema and rendered prompt expose the contract.

Fixes #4408
2026-07-03 06:13:52 +00:00
roboomp 136bda2399 fix(lsp): suppressed orphan ts project diagnostics
Filtered TypeScript project-resolution diagnostics for files with no root marker ancestor while preserving syntax diagnostics. Added regression coverage for Bun scratch files outside project roots.\n\nFixes #4401
2026-07-03 05:54:49 +00:00
metaphorics f66e527674 fix(ask): guest multi-select Next gating, body height bottom border
- Omit Next from the guest multi-select ui-request options until at least
  one option is checked or a custom answer exists, mirroring the local
  dialog's disabled-Next gating. The remote select has no disabled-row
  concept, so Next is omitted rather than dimmed (PRRT_kwDOQxs0bc6OFbDW).
- Add the bottomBorder(1) term to the ask dialog's fixed-row budget so the
  rendered dialog no longer overflows the viewport by one row
  (PRRT_kwDOQxs0bc6OFbDY).
- Add focused tests: guest wire-level Next gating round trip, and dialog
  height <= viewport assertion.
2026-07-03 14:17:31 +09:00
roboomp ed715eda7b fix(mnemopi): stripped retention markers from embeddings
Added an embedText projection for remember() so stored transcripts can remain readable while embeddings, FTS indexing, and embedding-model rebuilds use marker-free text. Updated coding-agent retention to pass the marker-free projection and strip retained protocol markers from recall display.

Fixes #4395
2026-07-03 04:59:02 +00:00
roboomp 5b6385df19 fix(mnemopi): retained only new auto-retain turns
Auto-retain now slices the transcript after the last retained user turn before storing an episode, preventing cumulative duplicate session transcripts in mnemopi banks.

Fixes #4396
2026-07-03 04:50:44 +00:00
metaphorics 1d14e262bd fix(ask): tagged guest results, bounded headers, cancel-keeps-open, gated Next
- Replace #requestGuestUiString's string|"unavailable"|undefined channel
  with a tagged GuestUiResult ({answered}|{cancelled}|{unavailable}) so a
  guest answer literally equal to "unavailable" no longer collides with
  the transport-unavailable sentinel (PRRT_kwDOQxs0bc6OE3gN).
- Cap in-body question header rendering to MAX_HEADER_ROWS with ellipsis
  truncation so long/multiline questions cannot push options off-screen
  (PRRT_kwDOQxs0bc6OE3gS).
- Guest Other editor cancellation now continues the loop (multi) / re-shows
  the select (single) instead of cancelling the whole ask
  (PRRT_kwDOQxs0bc6OE3gU).
- Disable the Next row on a single-question multi-select until at least one
  option or custom input is chosen, preventing empty-result submission
  (PRRT_kwDOQxs0bc6OE3gY).
2026-07-03 13:00:26 +09:00
roboomp da4ac01c71 fix(fast): enabled custom openai-compatible providers
- Classified OpenAI-compatible custom relays serving OpenAI model ids into the OpenAI service-tier family.
- Passed the model into OpenAI service-tier wire gating so custom relays emit service_tier when eligible.
- Reported /fast on as unavailable when the active model has no service-tier family.

Fixes #4386
2026-07-03 03:11:26 +00:00
can1357 8c648e4d02 fix(cli): enabled consistent reporting for sibling provider limits
- Added collectProviderLimitTemplates to aggregate limit IDs across all reports.
- Updated formatUsageBreakdown to render placeholders for limits missing from specific providers.
- Standardized label widths across sibling providers to align status bars.
2026-07-03 04:53:28 +02:00
metaphorics 48b2a742c8 fix(ask): distinct chat redirect result, row-specific note prefill
Op: correct
Restores: review:4375

- Widen ExtensionAskDialogResult to a union with { kind: "chat" } variant
  so AskTool can distinguish chat handoff from cancel (undefined).
- AskDialogComponent.#finishChat passes { kind: "chat" } via onChat.
- Controller settles { kind: "chat" } locally and propagates a "chat"
  sentinel through the guest/collab path instead of returning undefined.
- AskTool returns a chat-redirect AgentToolResult (chatRedirect details)
  instead of aborting with ToolAbortError.
- #promptForNote prefills with the existing note only when editing the
  same row (noteRowKey === rowItem.key), preventing cross-row note leaks.
- Add ask-dialog and ask tool tests for chat redirect and row-specific
  note prefill.
2026-07-03 11:52:51 +09:00
metaphorics 69c02c802a fix(ask): reset countdown on input, bound prompt titles, fix scroll viewport
- Reset the inactivity countdown in handleInput after the closed/prompt
  guard, matching HookSelector/HookInput semantics so a user actively
  navigating options/tabs is not auto-submitted by an absolute deadline.
- Add boundPromptTitle helper that flattens whitespace, wraps to the
  terminal content width, and caps at 3 rows with ellipsis truncation;
  apply it to custom-input and note prompt titles in the rich dialog
  and the guest-UI editor path so long/multi-line questions stay usable.
- Drop totalRows from both ScrollView calls so the full allLines array
  is sliced via scrollOffset + row; previously totalRows caused render
  to read lines[row] instead of lines[scrollOffset + row], leaving the
  viewport stuck at the top while the scrollbar thumb moved.
- Add tests for inactivity reset, bounded prompt titles, and scrolling.

Restores: review:4375
Op: correct
2026-07-03 10:16:41 +09:00
roboomp 31ac7e27eb perf(coding-agent/tui): scoped renders + shimmer band fast-path
Timer-driven reveal and spinner ticks (streaming reveal, tool-args reveal,
tool-execution spinner, todo strike animation) now hand the changed
component to `TUI.requestComponentRender(component)` instead of forcing a
full-tree render at 30fps. Every other root subtree reuses its previous
frame rows, cutting the Box/Container tree walk out of the compose
pipeline while the transcript grows.

Shimmer:
- Intern the working-message palette per accent (WeakMap-keyed) so the
  Symbol-slot compiled-ANSI cache in `shimmerSegments.compile` actually
  hits between frames — the fresh palette literal in `renderWorkingMessage`
  guaranteed a per-tick miss.
- Add an `activeBand` fast-path: outside the sweep window the intensity
  is guaranteed zero, so those code points coalesce into a single low-tier
  run without running `intensityFn` or `tierFor`. On the typical ~60-char
  working message the classic band is 12 cells wide, so ~80% of the per-char
  loop disappears.

Widen `ToolExecutionHandle` to extend `Component` (matches every
concrete impl — `ToolExecutionComponent`, `ReadToolGroupComponent` —
which already extend `Container`) so the reveal controller callback
sites are type-checked.

Fixes #4377
2026-07-03 00:50:03 +00:00
roboomp 8886a528dc fix(coding-agent): size title/commit/speech/classifier budgets for backends that ignore disableReasoning
Sizing `maxTokens` off the static `model.reasoning` catalog flag cannot
distinguish a thinking model catalogued `reasoning: false` (e.g. Qwen3
served locally via llama.cpp, whose bundled jinja chat template defaults
`enable_thinking: true`) from a model that never emits thinking. The
tight non-reasoning budget was consumed by the thinking preamble before
the useful output could be emitted, so every affected call silently
failed with `stopReason: "length"`.

Drop the `model.reasoning` conditional across every affected online call
site and always reserve the reasoning-safe budget. `maxTokens` is a hard
cap, not a target — non-thinking completions still return in the tiny
happy-path budget.

Sites fixed:
- utils/title-generator.ts       (30   -> 1024)
- utils/commit-message-generator (60   -> 1024)
- tts/speech-enhancer            (512  -> 1536)
- auto-thinking/classifier       online path (8  -> 1024); classifyLocal
                                  keeps its separate LOCAL_ANSWER_MAX_TOKENS
- session/unexpected-stop-classifier online path (16 -> 1024);
                                  classifyLocal keeps ANSWER_MAX_TOKENS

Fixes #4355
2026-07-03 00:47:20 +00:00
roboomp 72df91c3c8 fix(compaction): route plan-mode guidance via internalGuidance channel
Plan-approval's 'Approve and compact context' used to pass the rendered
plan-mode-compact-instructions prompt as the first positional argument
to handleCompactCommand -> session.compact(), which landed on the
session_before_compact extension hook as customInstructions. Extensions
treating that field as user focus (e.g. to bias a query-focused summary)
would then see plan-mode boilerplate instead of operator intent and
produce query-biased compactions.

Add CompactOptions.internalGuidance: a private summarizer-only channel.
session.compact() reads it into the fallback-model summarizer while the
session_before_compact hook payload still only carries the public
customInstructions arg (undefined for the plan-compact path). The
snapcompact-disable predicate and the /compact rejectsFocus guard cover
both fields so a directed summary is never silently downgraded.

Extend the interactive-mode handleCompactCommand facade + command
controller with a fourth internalGuidance parameter, and switch the
plan-approval callsite in interactive-mode.ts to route the plan prompt
through it.

Fixes #4359
2026-07-03 00:36:38 +00:00
roboomp d168188337 fix(coding-agent): queue approved plan behind turns flushed by compaction
Selecting "Approve and compact context" while a user turn was typed during
compaction surfaced `Failed to finalize approved plan: Agent is already
processing` and silently discarded the operator's queued turn.
`flushCompactionQueue` fires the queued user turn (fire-and-forget) before
`handleCompactCommand` returns, so by the time `#approvePlan` resumed, the
session was streaming. The previous shape aborted the queued turn and still
raced into `AgentBusyError` when `session.prompt()` ran before abort settled.

The finalize path now queues the plan-approved directive as a synthetic
follow-up when the session is streaming, and catches a racing `AgentBusyError`
from `prompt()` with the same fallback. `AgentSession.followUp()` gained a
`{ synthetic, expandPromptTemplates, attribution }` option so the hidden
execution directive lands as an agent-attributed developer message on the
follow-up queue, without flipping advisor auto-resume the user-follow-up path
does.

Fixes #4358
2026-07-03 00:35:06 +00:00
metaphorics 38a5c8a893 feat(ask): add rich interactive dialog
Adds the rich TUI ask dialog, additive schema fields, ask.enabled tool gating, note/preview/header support, chat redirect, and timeout behavior that defers while nested prompts are active instead of discarding user input.

Op: extend
2026-07-03 09:20:29 +09:00
roboomp 337ec0d16c fix(session): harden runEphemeralTurn against dropped provider content
Idle recap crashed with `TypeError: undefined is not an object (evaluating
'H.content.filter')` when the reporter's session_stop block-decision extension
poisoned the transcript with eight persisted `session-stop-continuation`
custom entries. The primary block-reason continuation flow was intact — the
custom message correctly renders as a `role: "developer"` LLM message —
but the follow-on idle recap fired 4 minutes later, and the side-channel
provider stream handed back a `done` event whose `message.content` had been
dropped. `runEphemeralTurn`'s sanitize step at
`assistantMessage.content.filter(block => block.type !== "toolCall")` then
tripped the TypeError, propagated as "Idle recap turn failed" in the debug
log while silently muting the session.

- Normalized the provider "done" event's `message.content` to `[]` when
  the shape is not an array, so a wrapper stream that drops content surfaces
  as an empty recap reply instead of an unrecoverable side-channel crash.
- Guarded `#buildEphemeralSnapshot`'s in-flight-assistant preservation
  branch with `Array.isArray(streaming.content)` for the same reason.
- Added a regression test that seeds the reporter's transcript shape (one
  real turn, eight persisted `session-stop-continuation` customs carrying a
  multi-line block reason with U+2717 glyphs) and drives a recap through a
  side stream that returns `content: undefined` on `done`. The test
  fails on the pre-fix tree with the exact reporter TypeError and passes
  after the guard lands.

Fixes #4323
2026-07-03 00:16:07 +00:00
can1357 79f499e092 feat(coding-agent/modes): requested UI render during loading state
- Triggered a UI render update when transitioning to the loading animation state to ensure the interface reflects the status change immediately.
2026-07-03 00:55:57 +02:00
can1357 16267f4e6a Merge remote-tracking branch 'origin/farm/51e9b851/tui-render-cpu-overhead' 2026-07-03 00:47:33 +02:00
can1357 80eccf99e4 fix(coding-agent/modes): improved rpc client startup error handling
- Prevent premature failure during process startup by waiting for stderr to drain before throwing exit errors.
- Resolve race conditions between stdout closure, process exit, and readiness timeouts by using a local child process reference.
- Ensure proper cleanup of abandoned processes during startup failures to prevent leaks.
2026-07-03 00:46:46 +02:00
can1357 f577f4cb22 ux(coding-agent): visualized advisor notes to distinguish from output
- Introduced a dedicated advisor rail symbol to differentiate note cards from thinking output.
- Applied bold custom header tags and severity-tinted rails to distinguish advisor notes from surrounding text.
- Shifted note body text to the default content color to prevent blending with dimmed thinking-output styles.
2026-07-03 00:46:46 +02:00
roboomp 30527aee0c fix(tui): cut TUI CPU overhead during interactive sessions
Four tightly-scoped hot-path fixes covering the highest-impact items in the
reporter's CPU profile (13.1 s profiled / 30 s window):

1. `event-controller.ts:handleEvent` no longer fires a blanket
   `statusLine.invalidate() + ui.requestRender()` before every session event.
   The pre-render was a leftover from #4145 when `updateEditorTopBorder()`
   still eagerly rebuilt the border; the lazy provider added in #4145 made
   it redundant. It fired on every `message_update`/`tool_execution_update`
   during streaming — the pre-render's frame ran while the handler was
   awaiting, then the handler's own `requestRender` scheduled a second
   identical frame. Every handler that mutates visible state already calls
   `requestRender()`.

2. `shimmer.ts:shimmerSegments` iterates the segment string in place instead
   of building a code-point array with `Array.from(seg.text)` every animation
   frame. Runs of same-tier chars are emitted via a single `slice` per run
   rather than accumulating into `runBuf`. Surrogate pairs stay atomic — the
   code-point index still advances by 1 per emoji. Microbench over 30k
   frames: 45 ms → 18 ms (2.53x), allocation rate down from ~N-per-frame to
   a handful per frame. New tests cover mixed BMP+surrogate and all-emoji
   inputs. `Array.from` was the #1 self-time hotspot in the reporter's
   profile at 10.2%.

3. `Markdown.setText` gains an equality guard mirroring `Text.setText`
   (returns `false` when `text === #text`). Providers re-emit identical text
   on ticks with no delta (throttled frames, reconciled tool-execution
   updates); each of those now short-circuits instead of dropping
   `#cachedLines` and forcing a full lex + wrap on the accumulated paragraph
   (the reporter's #3 hotspot at 8.4%). New test asserts render-reference
   stability + return-value semantics.

4. `SPINNER_RENDER_INTERVAL_MS` aligned with `SPINNER_GLYPH_ADVANCE_MS`
   (both 80 ms). The previous 33 ms cadence emitted ~2.4 paints per glyph
   step; the differential-output dedup only skips the write, not the
   compose walk. Visually identical (glyph advance was already 12.5fps),
   halves paints during tool execution.

Skipped (out of scope for a bug fix, deserve dedicated PRs):
- Freezing streaming prefix on single `\n` boundaries — correctness-bound
  to `\n\n` block separators (CommonMark loose-list continuation).
- Compose-phase idle gate + adaptive-backpressure moving-average — need a
  component-level dirty flag; the 200 ms cap in `#scheduleRender` was set
  for a reason (#4145 tail-latency guard).

Tests updated: two IRC-expiry tests in event-controller-message-start.test.ts
that were asserting the pre-render's second `requestRender` call now expect
one.

Fixes #4353
2026-07-02 22:11:26 +00:00
can1357 3315ef0007 fix(network): bounded smithery and startup fetches 2026-07-02 23:51:18 +02:00
can1357 d0fa896c19 fix(dap): removed per-write exit handlers 2026-07-02 23:51:18 +02:00
can1357 eff714c750 Merge remote-tracking branch 'origin/farm/16f7ef23/ssh-helper-timeouts' 2026-07-02 23:43:11 +02:00
can1357 eb47ce1063 Merge remote-tracking branch 'origin/farm/a23a6b23/drain-plugin-install-pipes' 2026-07-02 23:43:11 +02:00
can1357 8b3d0a7190 Merge remote-tracking branch 'origin/farm/1f41837c/timeout-bare-fetches' 2026-07-02 23:43:11 +02:00
can1357 8172708b10 Merge remote-tracking branch 'origin/farm/df17c0e9/async-clipboard-reads' 2026-07-02 23:43:10 +02:00
can1357 a721e56bf8 Merge remote-tracking branch 'origin/farm/7a7807b2/fix-status-line-gh-pr-lookup-hang' 2026-07-02 23:43:10 +02:00
can1357 f44a6cef81 Merge remote-tracking branch 'origin/farm/70e09bb6/fix-dap-client-flush-and-socket-spawn-leaks' 2026-07-02 23:43:10 +02:00
can1357 1301d5b07a Merge remote-tracking branch 'origin/farm/f5b6b32e/fix-edit-anchor-fresh-read' 2026-07-02 23:43:10 +02:00