Commit Graph

1460 Commits

Author SHA1 Message Date
can1357 076ca4e71d test(hashline/payload-syntax): migrated to inline payload syntax
- Updated hashline parser tests to use inline payload syntax (e.g., `tagvpayload` instead of `tagv\npl(payload)`).
- Removed deprecated test cases for bare-blank-line and explicit-blank-payload syntax.
2026-05-26 15:27:06 +02:00
can1357 5364a9bfd0 refactor(tool-discovery): simplified tool discovery API by removing MCP-specific shims
- Removed deprecated MCP-specific type aliases and functions from tool-discovery module, consolidating to unified generic tool discovery API.
- Migrated session and SDK code to use generic filterBySource() and collectDiscoverableTools() instead of MCP-specific variants.
- Removed deprecated interface members including hasQueuedMessages(), FocusPane, AcpBuiltinCommandRuntime, and legacy settings methods.
- Updated test suites to use renamed generic discovery methods and removed back-compat test coverage for legacy MCP shapes.
2026-05-26 15:27:05 +02:00
can1357 9a2cc3bda0 feat(eval/py): added runtime environment and working directory support to Python kernel execution
- Added `cwd` and `env` optional parameters to kernel execution API for runtime working directory and environment variable control.
- Implemented runtime environment setup in Python runner with `_apply_request_runtime()` to apply cwd and env from request before code execution.
- Enhanced SIGINT handler management with `active_executions` counter and `_begin_exec_sigint()` / `_end_exec_sigint()` functions to prevent state mutation during concurrent execution.
- Changed `SearchRenderArgs.paths` parameter type from `string[]` to `string | string[]` to accept single string paths.
- Added comprehensive test coverage for kernel cwd updates, timeout interruption safety, and SystemExit handling in shared executor sessions.
2026-05-26 15:26:29 +02:00
can1357 bb4c9cae1e feat(coding-agent): added configurable IRC timeout with AbortSignal cancellation
- Added configurable IRC message timeout setting with 120-second default to prevent indefinite hangs.
- Implemented timeout enforcement for IRC send operations using AbortSignal-based cancellation.
- Modified Python tool bridge to route concurrent evaluations using per-run identifiers alongside session IDs.
- Enhanced test coverage for IRC timeout behavior, tool validation, and ephemeral cache key separation.
2026-05-26 14:56:49 +02:00
can1357 30793c1655 refactor: restructured hashline to use file-level hash validation with colon separators
- Replaced per-line hash anchors with file-level hash validation in hashline format, changing anchor syntax from LINE+HASH to bare LINE numbers.
- Simplified hashline line separator from pipe (|) to colon (:) and replaced replace operator (->) with colon, added delete operator (!) for explicit line deletion.
- Implemented file-read snapshot caching with multi-snapshot ring buffer per path and file-hash-based recovery to detect and recover from stale edits.
- Refactored hashline grammar, parser, and execution to support file-level hash binding, anchor-scoped validation, and structural bracket warnings for delete operations.
- Updated documentation and test fixtures to reflect new hashline syntax with file hashes, colon separators, and delete operator throughout.
2026-05-26 13:25:22 +02:00
can1357 ea2f4b2557 feat(coding-agent): removed vim edit mode and migrated configs to hashline
- Removed vim edit mode and automatically map existing vim configurations to hashline mode.
- Deleted VimTool class, VimEngine implementation, and all vim-specific editing logic (2409 lines).
- Removed vim mode from EditMode union type, edit tool strategies, and configuration schemas.
- Deleted vim parser, command handler, buffer manager, and renderer modules.
- Updated documentation and tests to remove vim mode references and add deprecation mapping.
2026-05-26 13:16:59 +02:00
can1357 7f0208ac83 feat(coding-agent/eval): added console.table bridge to runtime text output
- Added a `console.table` helper in the JS prelude that forwards calls to the runtime `__omp_table__` hook.
- Implemented `__omp_table__` in the runtime to render tables through `node:console.Console` and emit text via `onText`.
- Added tests verifying `console.table` produced formatted table output and respected the optional columns filter.
2026-05-26 13:16:59 +02:00
can1357 44f85beb4a feat(ai): added websocket frame forwarding for raw SSE debug events
- Added helpers to synthesize RawSseEvent records for inbound, outbound, and malformed Codex WebSocket traffic.
- Passed onSseEvent through websocket transport and stream setup so frames are forwarded to the raw-SSE debug pipeline during streaming.
- Added a stream test that verifies outbound and inbound websocket frames are emitted with SSE-style raw lines for the debug viewer.
2026-05-26 11:21:02 +02:00
can1357 f9866798c0 feat(coding-agent): added hashline grammar for anchor ranges and payload
- Updated hashline markers to `¶` headers and `^/v/->` operators across constants, prompts, docs, and tests.
- Reworked hashline grammar and parser to support `ANCHOR<SIGIL>[INLINE_PAYLOAD]` with optional inline bodies and `A-B` ranges.
- Changed range and marker syntax from `..`/`"` to `-` and suffix `^/v/->` forms like `7v` and `A->`.
- Aligned `sameLineRange()` output and BOF/EOF handling so `|TEXT` remains cosmetic and payload now follows the op line.
- Centralized op-line detection by replacing local regex helpers with `isHashlineOpLineText` for payload terminator and bad-op checks.
2026-05-26 09:51:13 +02:00
can1357 7fd7397790 fix: resolved Bun HTTP/2 retry matching and thinking-only turn filtering
- Expanded transient error matching for Bun HTTP2StreamReset, RefusedStream, and EnhanceYourCalm.
- Dropped thinking-only/error/aborted turns without text/toolCall, reset aborted tool-call map, and stored timestamps.
- Updated TUI render planning to track scrollback high-water and suppress suffix-scroll artifacts in non-multiplexer sessions.
- Added regression tests and changelog notes for Bun HTTP/2 retry handling, thinking-only filtering, and scrollback regressions.
2026-05-26 08:55:57 +02:00
can1357 3b60edd276 refactor(coding-agent/tools): removed output schema evaluator and simplified validator handling
- Deleted the `ValidationVerdict` type and `evaluateOutputAgainstSchema` API from the output schema validator.
- Updated `yield.ts` to bind `buildOutputValidator`'s error directly to `schemaError` during validator setup.
- Removed the obsolete evaluator tests and adjusted validation success fixture to match the raw summary input shape.
2026-05-26 07:28:42 +02:00
can1357 e0eae43fde feat(tools): added shared output schema validator for YieldTool
- Unified output schema construction and validation by adding buildOutputValidator and using it in YieldTool and task executor.
- Added MAX_SCHEMA_RETRIES so YieldTool now retries schema failures three times with hints before overriding.
- Updated failure handling to use shared summarizeValidationFailure and formatters for required-field reporting.
- Added tests for output-schema-validator and YieldTool covering malformed schemas and nested-array retry edge cases.
2026-05-26 06:34:30 +02:00
can1357 27cc5a077b fix(ai,coding-agent): close OAuth lifecycle gaps from the AuthStorage rework
Centralizing OAuth refresh in AuthStorage (e6893515) introduced five
follow-on bugs surfaced by an audit of the commit; this fixes all of
them and updates the tests that relied on the old refresh seam.

1. packages/ai/src/auth-storage.ts (#tryOAuthCredential):
   For built-in providers the path went directly to `getOAuthApiKey`
   with the (possibly still-expired) selection.credential when the
   pre-refresh at line 2587 caught a transient error. `getOAuthApiKey`
   then threw the "expired … must be refreshed via AuthStorage"
   precondition error, which the disable classifier matched against
   `/expired.*refresh/` and soft-disabled the row. A single network
   blip during refresh could permanently kill a still-valid Anthropic /
   OpenAI / Gemini-CLI / Copilot credential. Built-in providers now
   route through the broker-aware single-flighted
   `#refreshOAuthCredential` first, so transient failures surface as
   network errors (5-min temp block) instead of definitive auth
   failures.

2. packages/ai/src/auth-storage.ts (#fetchUsageUncached):
   The usage refresh check only fired once `Date.now() >= expiresAt`,
   missing the 60-second skew that `getApiKey` honors. A token
   expiring inside the skew window was posted to the usage endpoint
   and 401'd mid-flight, briefly hiding quota in the UI. Aligned with
   `OAUTH_REFRESH_SKEW_MS`.

3. packages/coding-agent/src/web/search/index.ts (webSearchCustomTool):
   The CustomTool counterpart of WebSearchTool dropped sessionId so
   SDK callers that opted into `web_search` via toolNames lost
   per-session credential stickiness — multi-account users saw the
   provider round-robin between searches in the same session. Threads
   `ctx.sessionManager.getSessionId()` through to `executeSearch`.

4. packages/coding-agent/src/web/search/providers/perplexity.ts
   (findOAuthToken):
   `authStorage.getApiKey("perplexity")` returns runtime/config
   overrides, stored api_key credentials, OAuth bearers, and env keys.
   Filtering only env keys meant a config-pinned `pplx-…` API key was
   POSTed to `www.perplexity.ai/rest/sse/perplexity_ask` (the OAuth
   endpoint) instead of falling through to
   `api.perplexity.ai/chat/completions`, producing 401s. Switched to
   `getOAuthAccess` so only true OAuth bearers reach the OAuth
   branch; api_key credentials/overrides correctly fall through.

5. packages/ai/scripts/generate-models.ts:
   `getOAuthApiKey` was being called directly with possibly-expired
   credentials. The new contract throws on expired, the broad catch
   swallowed it, and the build silently fell back to bundled models
   instead of refreshing. Both helpers now route through
   AuthStorage's `getApiKey` / `getOAuthAccess`, which trigger the
   full broker-aware refresh pipeline.

Test updates:
- auth-storage-credential-disabled-event.test.ts,
  sdk-credential-disabled-bridge.test.ts: the `failOAuthRefresh`
  helper used to spy on `getOAuthApiKey` to inject invalid_grant.
  With refresh now happening before that helper, the spy never fired.
  Switched to spying on `refreshOAuthToken` so the simulated failure
  reaches the disable classifier.
- auth-storage-rotation.test.ts: stub `refreshOAuthToken` so the test
  doesn't hit a real OAuth endpoint when the seeded credential lands
  inside the 60s skew window.
2026-05-26 05:32:47 +02:00
roboomp 0a3a48b92d fix(task): respected parent lsp disable for subagents
Combined task.enableLsp with the parent session enableLsp gate before spawning subagents, so --no-lsp remains authoritative even when subagent LSP is enabled in settings.

Fixes #1385
2026-05-26 03:13:28 +00:00
roboomp 28a0dc3ae4 feat(task): gated subagent LSP behind task.enableLsp setting
Added task.enableLsp (boolean, default false) and routed both regular and isolated subagent dispatch through it. Keeps subagents cheap by default while letting users opt in to LSP-aware delegation. Updated regression tests to cover the default-off, opt-in, plan-mode, and isolated paths.

Fixes #1385
2026-05-26 03:09:09 +00:00
roboomp 8948101e2b fix(task): forwarded parent enableLsp flag to subagents
Subagents now inherit the parent session's enableLsp value, so a top-level --no-lsp invocation propagates into spawned tasks instead of falling back to the executor's default of true.

Fixes #1385
2026-05-26 03:06:14 +00:00
roboomp 5ff1c747ce fix(task): inherited lsp for subagents
Removed the hardcoded subagent LSP disable flag so executor defaults and user settings control LSP availability. Passed the effective plan-mode agent definition into both regular and isolated subagent dispatch so plan-mode tool restrictions apply consistently.

Fixes #1385
2026-05-26 03:01:57 +00:00
can1357 e689351597 fix(coding-agent): resolved OAuth token expiry flow in AuthStorage
- Centralized OAuth access lifecycle in `AuthStorage`, returning identity metadata and new access-result types.
- Added 60-second skew and strict expiry checks, returning undefined/throws for stale or expired OAuth credentials.
- Removed provider-local token refresh flows from Gemini, Gemini CLI, Antigravity, Kimi, and related OAuth helpers.
- Migrated web-search providers from `AgentStorage` to `AuthStorage` session-aware lookup with `authStorage`/`sessionId`/`signal` flow.
- Replaced `findAnthropicAuth`/DB auth lookup with `buildAnthropicAuthConfig` and explicit base-url override/env fallback ordering.
2026-05-26 03:59:13 +02:00
can1357 cfabeeb17c feat(web): added Codex and Gemini web search providers with shared AgentStorage flow
- Added OpenAI Codex and Gemini web search provider options with updated setup/auth descriptions.
- Updated Codex OAuth flow to refresh near-expiry tokens during web_search and persist the refreshed credentials.
- Plumbed AgentStorage through search orchestrator, scrapers, and fetch paths so providers share session credentials.
- Refactored web provider and credential helpers to accept caller-provided AgentStorage and resolve keys synchronously.
2026-05-25 21:21:13 +02:00
Can Bölük d201442a16 Merge pull request #1372 from can1357/farm/e4c67c73/fix-subagent-session-start-busy
fix(agent): prevent subagent session_start busy race
2026-05-25 21:59:38 +03:00
Can Bölük 8b4525e869 Merge branch 'main' into farm/70c1e455/bash 2026-05-25 21:56:06 +03:00
roboomp 23300d0348 fix(bash): quarantined stalled shell sessions
Quarantined persistent session keys only while the native cancellation promise remains unsettled, so healthy cleanup restores persistent mode and stalled cleanup cannot accumulate live shell instances.

Added coverage for both stalled and settled native cleanup paths.

Fixes #1347
2026-05-25 18:48:20 +00:00
roboomp 1217091557 fix(agent): prevented subagent session_start busy race
Queued extension-delivered user messages when deliverAs is set and waited for session_start extension message sends before prompting subagents.

Fixes #1343
2026-05-25 18:48:06 +00:00
Can Bölük 47dab57559 Merge branch 'main' into farm/5c2ff3c3/report-finding-tool-agent-output-schema- 2026-05-25 21:42:35 +03:00
roboomp 8e5c7c9bf1 fix(bash): kept persistent shells after cancel
Stopped marking persistent bash sessions as permanently broken when the JavaScript abort or timeout race wins.

Stopped the Rust descendant kill-wave helper once no cancellation targets remain so later commands are not swept into old cancels.

Fixes #1347
2026-05-25 18:40:22 +00:00
can1357 a7af3900bd feat(coding-agent/task): added parent-aware labels to nested live task snapshots
- Updated nested task-rendering tests to use parent-qualified IDs for completed child task results.
- Updated in-flight nested snapshot expectations to verify parent-aware `Parent>Subtask` labeling.
- Documented the live nested task rendering behavior in the package changelog.
2026-05-25 20:27:22 +02:00
can1357 a5275b17ce feat(coding-agent): added hashline inline |TEXT matching for BOF/EOF
- Added inline `|TEXT` payload parsing for `"/"` before/after inserts, including BOF/EOF usage.
- Fixed inline anchor handling by resolving matching `|TEXT` bodies and handling whitespace-containing payloads.
- Added parser tests for `applyDiff` and `parseHashline` covering whitespace, matching, and non-matching inline `|TEXT`.
- Added nested live task fixtures and snapshot tests for ordered in-flight and completed child rendering.
- Documented hashline inline `|TEXT` behavior updates in CHANGELOG.
2026-05-25 20:24:44 +02:00
roboomp 14c7ddf7d0 fix(bash): returned on stalled cancellation
Raced bash execution against the JavaScript abort signal and timeout so the tool returns even when native shell cleanup does not settle.

Added regression coverage for native cleanup stalls on ESC abort and timeout.

Fixes #1347
2026-05-25 18:03:09 +00:00
roboomp 6e9cf81544 style: bun run fix 2026-05-25 18:01:45 +00:00
roboomp 6b14cf1f55 fix(coding-agent): coerced report_finding string priority to number for reviewer schema
The report_finding tool's priority is exposed as a string enum
("P0"-"P3") for ergonomics, but the reviewer agent and every
custom review agent declare priority as `type: number` in their
JTD output schema. The cast at executor.ts:1473 lied about the
runtime shape, so the auto-injected `findings[].priority` flowed
through as strings and every yield with at least one finding was
rejected with `findings.0.priority: expected number, received string`,
forcing the run into the schema_violation exit path.

Added `toReviewFinding(details)` in tools/review.ts that maps the
priority enum to its numeric ordinal via the existing PRIORITY_INFO
table and use it at the boundary in executor.ts. Render paths still
see the original `ReportFindingDetails` shape (string priority)
through normalizeReportFindings, so display formatting is unaffected.

Fixes #1350
2026-05-25 18:01:38 +00:00
Can Bölük a40864c5b2 Merge branch 'main' into farm/ccf5d9fd/csharp-lsp-plugin-doesn-t-work-with-omp- 2026-05-25 20:37:30 +03:00
roboomp 24b249219e fix(lsp): supported config-only marketplace servers
Loaded marketplace lspServers metadata from Claude plugin caches and embedded it for OMP marketplace installs so config-only plugins register without package code.

Fixes #1352
2026-05-25 17:34:32 +00:00
Can Bölük 29d0e3a470 Merge branch 'main' into farm/b4be9197/task-explore-agent-fails-with-schema-vio 2026-05-25 20:27:22 +03:00
roboomp ef68db17f5 fix(coding-agent/tools): stopped re-walking JTD-converted JSON Schema for nested JTD detection
The JTD-to-JSON-Schema converter post-processed convertSchema's
output with normalizeMixedSchemaNode, which walked back into the
emitted JSON Schema looking for nested JTD forms. Inside a
properties block, user-defined property names whose keys happened
to collide with JTD keywords ('ref', 'elements', 'values',
'optionalProperties', 'discriminator') were misclassified as JTD
forms and re-rewritten - corrupting properties like { ref: { type:
'string' } } into { $ref: '#/$defs/[object Object]' } and breaking
the built-in explore agent's output validator with
schema_violation: files.0.ref: must not be present.

convertSchema is already fully recursive and emits pure JSON Schema,
so the post-walk is both unnecessary and unsafe. Drop it.

Fixes #1345
2026-05-25 17:21:53 +00:00
can1357 079d18a82b fix(model-registry): fixed tp- token-plan baseUrl lost during discovery merge
- Extracted `mergeDiscoveredModel` so discovered baseUrl takes priority over bundled entry, fixing 401s on Xiaomi tp- token-plan streams.
- User providerOverride.baseUrl still wins over both discovered and bundled values.
- Added regression tests covering all merge priority paths.
2026-05-25 14:06:13 +02:00
can1357 d6acef8b76 perf(status-line): replaced index-based token cache with message sidecar cache
- Added Symbol-keyed sidecar on each AgentMessage to memoize estimateTokens, with a cheap content fingerprint to detect in-place mutations.
- Fixed stale cache on same-length replaceMessages, post-hoc error attachment, and branch rebuild edge cases.
- Fixed usage fetch error backoff: stamped fetchedAt on failure so the 5-min TTL also gates retries during outages.
- Extracted computeNonMessageBreakdown as shared helper to prevent drift between status-line and context panel token counts.
2026-05-25 14:06:05 +02:00
can1357 8e3e4fd5f0 fix(slash-commands): captured mode state before handler call for history
- Fixed /plan and /goal history preservation by snapshotting enabled state before handlePlanModeCommand/handleGoalModeCommand executes.
- Previous check read state after the call, missing cases where the handler itself toggled the mode off (e.g., confirmed exit).
- Added tests covering confirm-exit, cancel-exit, and first-activation paths.
2026-05-25 14:05:55 +02:00
can1357 81cec1c38b fix(clipboard): hardened WSL PowerShell fallback for headless environments
- Raised PowerShell timeout to 8s and swallowed reap errors to prevent unhandled throws on WSL interop.
- Fixed fallback logic so arboard is skipped when no display server is present on headless WSL.
- Added test coverage for the headless WSL short-circuit path.
2026-05-25 14:05:48 +02:00
can1357 79f6bf4f76 fix(session-manager): added orphaned backup recovery after EPERM rename
- Added `recoverOrphanedBackups` to promote `.jsonl..bak` files back to their primary path when the primary is missing, preventing data loss after a mid-rename crash.
- Changed backup filename from dot-prefixed to plain `..bak` so the shared `*.bak` glob can find it on both real and in-memory storage backends.
- Surfaced the original EPERM as the error `cause` and included both original and retry messages when rollback also fails.
2026-05-25 14:05:41 +02:00
can1357 975c836015 fix(image-resize): deferred OMP_NO_WEBP evaluation to call time
- Replaced baked module-load value with per-call `isWebPExcluded()` so runtime env changes take effect.
- Only `"1"` and `"true"` (case-insensitive) enable exclusion; empty string and `"0"` are treated as disabled.
- Fast path now bypassed for WebP sources when exclusion is active.
- Explicit error surfaced when decode fails and WebP exclusion cannot be honored.
2026-05-25 14:05:31 +02:00
can1357 e5bc511392 edit(hashline): leniently parse anchors with trailing |TEXT body and read/search decoration
Anchors are formatted by read/search as LINE+HASH|TEXT, and lines may be
prefixed with marker decoration (*, >, +, -). The parser previously required
a bare LINE+HASH and rejected verbatim copy-pasted anchors with:

  line N: expected a full anchor such as "119sr", ...; got "364sp|".

Loosen LID_CAPTURE_RE to allow optional leading decoration and an optional
trailing |... body on each anchor (including each side of a range).
2026-05-25 13:36:44 +02:00
can1357 3801b4ee32 fix(coding-agent): added configurable retry delay cap and surfaced rate-limit failure state
- Added `retry.maxDelayMs` to the settings schema and interfaces, with a default cap for provider backoff delays.
- Updated session auto-retry logic to fail fast when a requested wait exceeds the cap without fallback, emitting terminal auto-retry failure state.
- Propagated retry state and failure data into task progress and rendering so children show retry/wait details and reminder prompts stop after terminal errors.
2026-05-25 12:37:32 +02:00
roboomp 294f0067d0 fix(tui): refreshed model tabs by provider id
Separated model selector provider tab labels from provider ids so human-readable labels like Ollama Cloud refresh and filter the underlying ollama-cloud models.

Fixes #1153
2026-05-25 12:25:12 +02:00
roboomp a2032850e5 fix(legacy-pi-compat): fall back to peer deps when resolveSync fails in binary mode
In a compiled binary, Bun.resolveSync(spec, import.meta.dir) throws
'Cannot find module' because import.meta.dir is inside /$bunfs/root
and the virtual FS exposes no node_modules tree at runtime.

Previously this throw propagated through rewriteLegacyPiImports ->
rewriteLegacyPiImportsForRuntime -> mirrorLegacyPiFile ->
loadLegacyPiModule -> loadExtension, which swallowed it as 'Failed to
load extension' and silently dropped any plugin whose files imported
@mariozechner/pi-ai (or any @mariozechner/pi-* whose bundled
counterpart isn't reachable via resolveSync in the binary).

Fix: wrap the resolution call in rewriteLegacyPiImports in a try/catch
and return the original match on failure. rewriteBareImportsForLegacyExtension
runs immediately afterwards in every call path and already resolves bare
specifiers against the importer's real filesystem directory, so it picks
up @mariozechner/pi-ai from the plugin's installed peer deps instead.

Apply the same fallback to resolveLegacyPiSpecifier (the Bun plugin
shim's onResolve handler) for tool/hook files loaded directly via Bun's
import system rather than through loadLegacyPiModule.

Fixes #1215
2026-05-25 12:22:57 +02:00
can1357 8e74996513 chore: adjust tests 2026-05-25 12:22:43 +02:00
Can Bölük 1084a854f8 Merge pull request #1296 from can1357/farm/93902d04/goal-set-rejects-active-goals-but-clears
fix(cli): allow /goal set to replace active goals
2026-05-25 13:20:46 +03:00
Can Bölük e4165a41e6 Merge branch 'main' into farm/bfe680ee/ctx-ui-notify-during-session-start-is-cl 2026-05-25 13:20:04 +03:00
Can Bölük acd64750e4 Merge pull request #1335 from TommyC81/feature/autoload-skills
feat: add autoloadSkills frontmatter field for agent definitions
2026-05-25 13:19:06 +03:00
Leo Kim 4eb2919674 fix(coding-agent): incremental per-message token cache for status-line context% (avoid 1.1s freeze on long sessions)
Root cause (verified on user's environment):

- User commit `296641213` swapped status-line's context% computation from cheap `calculatePromptTokens(lastAssistantMessage.usage)` to `computeContextBreakdown(session)`, which walks EVERY message and runs native `countTokens` (~0.5 ms per message).

- The 2-second TTL cache helps for steady-state idle but every cache MISS is a full sweep.

- `updateEditorTopBorder()` is invoked on EVERY agent event (event-controller.ts:163 — `agent_start`, `delta`, `agent_end`, `tool_*`). Each delta during streaming can trigger a cache miss.

- User session has 2,312 messages → each full sweep is ~1,120 ms blocking.

- During streaming the UI freezes for ~1.1 s every ~2 s, producing the user-visible 'jittery rendering' ("버벅거림") and 'status bar disappearing' symptoms.

Fix:

`StatusLineComponent.getCachedContextBreakdown()` (renamed from `#getCachedContextBreakdown` so unit tests can exercise it directly) now uses an incremental per-message token cache that exploits the append-only nature of `session.messages`:

  1. Message tokens (the dominant cost): cached per-index. New messages are tokenized as they arrive; previously-cached messages are reused. The LAST message is always recomputed because its content may still be growing during streaming. Compaction (messages.length shrinks) resets the cache.

  2. Non-message tokens (system prompt + tools + skills): cached separately, invalidated only when a cheap inputs-identity fingerprint changes (model swap, skill toggle, tool registration). These rarely change during a session.

Required exposing three helpers from `modes/utils/context-usage.ts` (`estimateSkillsTokens`, `estimateToolSchemaTokens`, `computeNonMessageTokens`) so the status-line cache can call them directly.

Performance (2,300-message synthetic session, measured on user's M-series Mac):

  - COLD warm-up call: ~75 ms (one-time, runs at OMP startup before any streaming)

  - WARM refresh, no new message: ~0.04 ms (20 calls = 0.7 ms total)

  - WARM refresh, 1 new message: ~0.02 ms

vs. prior implementation:

  - Per cache-miss call: ~1,120 ms blocking

  - 28,000× speedup on warm-state refresh

`computeContextBreakdown` itself is untouched — `/context` slash command continues to use it, and its output matches the status-line context% for the same session state (parity preserved).

Tests: 6 new cases in `packages/coding-agent/test/status-line-context-cache.test.ts` covering cold/warm/append/compaction/non-message-invalidation/zero-messages and a perf smoke test asserting 20 warm refreshes on a 200-message session complete in <100 ms.

Full suite: 3,199 tests, 26 pre-existing failures (status-line accent / log_experiment timing-flaky / skills / github tool / workspace-tree / tool path — all unrelated and baseline-confirmed). Lint: 1 pre-existing import-order issue in `event-controller-plan-ready.test.ts` unchanged.
2026-05-25 12:11:15 +02:00
Leo Kim b373c54cd7 fix(coding-agent): guard sendCompletionNotification on aborted/error stopReason
Bug: Ctrl+C on the ask tool selector threw ToolAbortError, the turn
ended with stopReason === "aborted", and handleBackgroundEvent fired
sendCompletionNotification() unconditionally — producing a misleading
"Task complete" desktop toast for a turn that never actually completed.

Fix mirrors the stopReason filter already used by
#currentContextTokens, #handleMessageEnd, and the retry / TTSR /
compaction skip paths across agent-session.ts: check the most recent
assistant message via session.getLastAssistantMessage() and return
early when stopReason is "aborted" or "error".

Test coverage (event-controller-abort-guard.test.ts, 6 cases):
- aborted   -> 0 sendNotification calls
- error     -> 0 calls
- stop      -> 1 call (normal completion)
- no last assistant message -> proceeds (defensive)
- isBackgrounded=false (foreground) -> still 0
- completion.notify=off -> still 0

Matching guard applied to the standalone desktop-notify extension
(~/.omp/agent/extensions/desktop-notify/index.ts) which is currently
the live producer of completion toasts after Phase 1 of
seed_0ca7e1143ac1.
2026-05-25 12:08:36 +02:00