Commit Graph
222 Commits
Author SHA1 Message Date
can1357 31309d6212 fix(agent): removed tool-level abort-signal bypasses for read/write/edit tool execution
- Updated `executeToolCalls` to always pass the active `toolSignal` into `tool.execute` rather than bypassing it for non-abortable tools.
- Removed the `nonAbortable` option from `AgentTool` and from the read, write, and edit tools so they can no longer opt out of abort handling.
- Documented the cancellation behavior change in the affected tool docs and package changelogs.
2026-06-08 05:27:00 +02:00
can1357 b1563e4e80 fix(lsp): adjusted LSP diagnostics polling to settle unversioned publishes
- Updated `waitForDiagnostics` to accept exact document-version matches immediately and otherwise wait for a quiescence window before using the latest publish.
- Removed the old unversioned-acceptance option and applied the settle-based wait logic through inline and deferred diagnostics fetch paths.
- Added an LSP writethrough regression test ensuring stale unversioned diagnostics are ignored in favor of later fresh publishes.
2026-06-08 05:20:30 +02:00
can1357 e13f2de58a fix(coding-agent): mapped auxiliary messages to developer role for compaction
- Updated `convertToLlm` logic to emit `developer` role for custom, hook, and file-mention inputs.
- Simplified OpenAI compact output filtering to retain only `user` and `assistant` messages, removing legacy `system-reminder` pattern checks.
- Adjusted compaction and session tests to match the new developer-role mapping and expected compacted content.
2026-06-08 05:19:57 +02:00
can1357 0e78cac716 fix(coding-agent): removed abort marker and updated continue shortcut prompts
- Removed `<turn-aborted>` guidance injection from `transformMessages`, deleted `turn-aborted-guidance.md`, and dropped the synthetic abort note path for aborted/error turns.
- Updated `c`/`.` continue shortcuts to submit `manual-continue.md` as a hidden synthetic `developer` message via `session.prompt(..., { synthetic: true })` instead of sending an empty user turn.
- Updated tests and changelog notes in AI and coding-agent to reflect the revised abort-context and continue behavior.
2026-06-08 05:02:49 +02:00
WodenJay b6c87233de fix(proxy): preserve custom abort reason in disconnect guard
The !sawTerminalEvent guard must check signal.aborted first and throw
the caller's reason (not a generic disconnect message) so that
Agent.abort('user-interrupt') surfaces the custom reason in
errorMessage instead of overwriting it.
2026-06-07 16:13:43 +08:00
WodenJay 64a4923ad5 fix(proxy): improve comment on ProxyMessageEventStream class 2026-06-07 16:05:58 +08:00
WodenJay aa8b9e785e test(proxy): address review feedback on disconnect tests
- Export ProxyMessageEventStream class (avoid ReturnType<> per AGENTS.md)
- Use hookFetch from @oh-my-pi/pi-utils instead of manual globalThis.fetch mutation
- Use Promise.withResolvers() instead of new Promise for timeout races
- Use typed Context object instead of 'as never' cast
- Add client-initiated abort test (verifies stopReason='aborted' path)
- Remove 'as IteratorResult' cast on timeout sentinel
2026-06-07 16:05:58 +08:00
WodenJay 483fa8df3c fix(proxy): throw error when server disconnects without terminal event
When the proxy SSE stream ends without sending a done or error
terminal event, streamProxy now throws an error instead of silently
calling stream.end() with no arguments. The thrown error is caught by
the existing catch block, which pushes an error event with
stopReason='error' and resolves finalResultPromise.

Previously, server disconnects produced a silent success: the stream
iterator completed normally with stopReason='stop' (the default) and
empty content, and finalResultPromise never resolved, causing
callers that awaited stream.result() to hang indefinitely.

This matches the pattern used by the codex provider
(openai-codex-responses.ts) which already guards against missing
terminal events.
2026-06-07 16:05:58 +08:00
can1357 4080202911 fix(agent): prevented event-loop stalls during oneshot LLM completions
- Added `EventLoopKeepalive` in `instrumentedCompleteSimple` to keep the event loop alive during oneshot completion calls.
- This prevented long-lived completion promises from freezing host spinners in Bun by keeping timer servicing active.
2026-06-07 07:34:11 +02:00
can1357 9bd9e3127e feat(coding-agent): added /tan background forking with prompt cache inheritance
- Added `/tan` slash command registration and interactive handling.
- Added TanCommandController validation and async task scheduling for `/tan` dispatch.
- Added session cloning that suppresses breadcrumbs, copies artifacts, and handles abort cleanup.
- Added `promptCacheKey` support in Agent and inherited `providerPromptCacheKey` in session creation.
2026-06-07 06:52:15 +02:00
can1357 a78f4c6766 feat(coding-agent): enabled abort reasons to propagate and surface through streaming messages
- Added optional `reason` parameters to `Agent.abort` and `AgentSession.abort` APIs.
- Passed abort reasons through interrupt flows into underlying agent cancellation.
- Replaced hard-coded abort text with `resolveAbortLabel` for streaming and replayed messages.
- Fell back to generic `Request was aborted` text when no abort reason was provided.
2026-06-07 06:19:55 +02:00
can1357 cba6641299 feat: enabled resolver-based API key retries with refresh and rotation
- Added `ApiKeyResolver`/`ApiKey` types and exported auth-retry helpers.
- Changed stream and gateway auth retry handling to use resolver steps.
- Added initial-key, force-refresh, and rotate credential retries for auth failures.
- Updated agent and coding-agent integrations to use context-aware API-key resolvers.
2026-06-07 04:20:48 +02:00
can1357 47e34f9ba6 fix(agent): surfaced Anthropic output-blocked stream errors
- Emitted content-filter blocks as normal assistant error lifecycle events.
- Prevented interactive clients from silently dropping the streaming bubble.
- Finalized an existing assistant stream when blocked mid-stream.
2026-06-05 23:56:00 +02:00
can1357 c8b4bf09c7 prompts: undo experiment, update system 2026-06-04 17:41:10 +02:00
can1357 95defa712b docs(compaction): rewrote prompts in terse scratchpad style
- Converted compaction, branch, and handoff prompts to fragment voice.
- Replaced "You MUST" phrasing with bare "MUST" directives.
- Applied same rewrite to autoresearch and turn-aborted prompts.
2026-06-04 14:49:45 +02:00
can1357 dc4aeb7b88 refactor(coding-agent): renamed todo_write tool to todo
- Renamed `TodoWriteTool` to `TodoTool` and its source/prompt files.
- Updated tool registration, schema, renderers, and gating to `todo`.
- Adjusted cursor provider native tool names and tests to match.
- Renamed strike-animation constants and `todo-error-reminder` type.
2026-06-04 02:45:30 +02:00
can1357 e1b2809154 feat(coding-agent): added plan read matching and compaction protection behavior
- Added shared `getReadToolPath` API to extract paired read `path` values for protection matchers.
- Added `createPlanReadMatcher` and session wiring so compaction prune/shake keeps active plan reads intact.
- Updated `todo-write` instructions to initialize every user-supplied plan item as an individual task.
- Added compaction tests validating plan reads are protected from prune and shake while regular reads are still removable.
2026-06-03 16:14:15 +02:00
roboomp e4a31b22bf fix(agent): surfaced max_tokens truncation cause to the model on skipped tool calls
When a tool call (most visibly `write` with >~1000 lines of content) is

truncated by `stop_reason: length` — e.g. OpenCode Zen's claude-3-5-haiku

with its 8192 `max_tokens` cap — the agent loop correctly refuses to

execute the call (its streamed arguments are mid-string) but used to

attach the same generic placeholder result it uses for non-runnable

non-tool turns: "Tool call was not executed because the assistant ended

its turn." The auto-continue loop re-prompted, the model re-emitted the

same oversized payload, and the user saw the file write fail again and

again — perceived as a "write tool crash" with the target file lost.

`createAbortedToolResult` now takes a `length` reason that names

`stop_reason: length` and tells the model to split the work into multiple

smaller tool calls (write the first chunk, append the rest with `edit`

insert ops, or break the file into multiple `write` targets). The skip

path in agentLoop forwards the real stop reason so the hint reaches the

model on the very next continuation. Tool execution is still guarded —

the truncated args never run.

Regression test in packages/agent/test/agent-loop.test.ts verifies the

synthetic `write` call is skipped AND the resulting tool-result message

carries the length-specific guidance.

Fixes #1785
2026-06-03 09:40:22 +00:00
can1357 eda1a1056b feat(coding-agent): added matcherDigest hook for TTSR wire-format normalization
- Added optional `AgentTool.matcherDigest(args)` hook so tools can expose plain source text instead of wire-encoded arguments to TTSR rule matchers.
- Implemented `matcherDigest` on edit (all modes: hashline, patch, apply_patch, replace) and write tools, stripping patch prefixes and JSON escaping.
- Added `TtsrManager.checkSnapshot()` to replace the scoped buffer with a tool digest rather than appending raw deltas.
- Fixed TTSR conditions never matching streamed edit/write calls whose wire format obscured real content.
2026-06-03 10:05:24 +02:00
can1357 0bc9bc25b4 fix(agent): gated tool_arg harmony detection on trailing-garbage T co-signal
- Added T co-signal requirement for tool_arg surface so legitimate edits carrying the marker are never hard-aborted.
- Extended detectHarmonyLeakInAssistantMessage with optional toolArgParseEnd resolver; agent loop omits it, keeping tool_arg inert.
- Updated tests to inject a boundary-at-0 helper for corpus cases and added T-gate unit tests.
2026-06-02 08:42:52 +02:00
DarkPhilosophy ad2568cbb4 fix(agent): pop leaked partial before retry in trailing path 2026-06-01 23:49:46 +03:00
DarkPhilosophy 26b3300c76 fix(agent): engage harmony leak detection on the committed assistant message 2026-06-01 23:44:45 +03:00
can1357 14bd572f49 refactor(shake): removed shake-summary mode and local-model compressor
- Dropped `summarizeShakeRegions`, the shake-summary prompt, and related types.
- Removed `shake-summary` compaction strategy and `providers.shakeSummaryModel` setting.
- Migrated existing `shake-summary` configs to plain `shake` on load.
- Simplified `/shake` to `elide` and `images` modes only.
2026-05-31 14:14:37 +02:00
can1357 417a1a1d32 feat(agent): added shake compaction strategy primitives
Introduce the shake compaction strategy in the core agent: exports, summary prompt, implementation, and unit coverage.
2026-05-31 07:39:38 +02:00
can1357 ff9a6826dd fix(compaction): made read tool-results prunable except for skill:// paths
- Replaced flat `protectedTools: string[]` with `ProtectedToolMatcher[]` supporting predicate functions.
- Regular file/URL `read` calls are now eligible for pruning and shake compaction.
- `read` calls whose `path` starts with `skill://` remain protected like native `skill` results.
- Added `collectToolCallsById` to correlate tool results with their originating call arguments.
2026-05-31 07:12:27 +02:00
can1357 5db3bcabad fix(agent): snapshot initial mutable state
Addresses review feedback on #1507.
2026-05-31 04:49:17 +02:00
oldschoolaandcan1357 3a733c480b perf(agent,ai,coding-agent): in-place state mutation + per-delta json-parse throttle + drop hot-path structuredClone
F3 (agent): mutate state.messages and state.pendingToolCalls in place on
appendMessage/popMessage/clearMessages/reset/tool_execution_start/_end
instead of allocating a fresh array/Set on every transition. Subscribers
that capture state.messages by reference now observe updates directly.
Public type signature unchanged.

F5 (ai): add parseStreamingJsonThrottled to utils/json-parse — a per-delta
wrapper around parseStreamingJson that skips the re-parse until the
buffer has grown by minGrowthBytes (default 256). Wired into every
provider's tool-call argument accumulator (anthropic, amazon-bedrock,
openai-completions, openai-codex-responses, openai-responses-shared) so
per-delta cost becomes O(N) in total buffer length instead of O(N²).
Every provider's toolcall_end still runs a final unthrottled parse, so
the published block.arguments is unchanged.

F8 (coding-agent): drop the per-delta structuredClone of streaming tool
arguments in ToolExecutionComponent.updateArgs. event-controller.ts and
ui-helpers.ts already spread their input into a fresh object on each
delta, so cloning here was dead work on the rendering hot path. Added a
reference-equality short-circuit so repeat calls with the same args
object skip the preview-diff and display refresh.
2026-05-31 04:46:06 +02:00
can1357 e831c2c758 chore: reformat 2026-05-30 18:08:51 +02:00
can1357 ae905fb3cf feat(agent): added agent tool-call cap enforcement to stream loop
- Added `maxToolCallsPerTurn` support to `AgentOptions` and `AgentLoopConfig`, with Agent getter/setter and serialized state wiring.
- Implemented stream-loop cap handling by normalizing bad values and halting after `toolcall_end` reaches the limit.
- Added `ANTHROPIC_TOOL_CALL_BATCH_CAP`=8 and wired session cap sync on init, model changes, and restore.
- Added tests that truncated a 10-call stream to 8 tool calls, and verified non-Claude models resolve no cap.
2026-05-30 04:47:58 +02:00
can1357 445fe99fbc fix(agent): executed tool calls emitted under end_turn stop reason
- Fixed agent loop abandoning tool_use blocks on `stop`/`end_turn` turns; only `length` (truncation) now skips execution.
- Verified against live Anthropic API: stop_reason is never replayed on wire and doesn't gate continuation validity.
- Added tests pinning the wire-safety contract: thinking blocks with stale/missing signatures are downgraded to text before replay.
2026-05-30 00:27:03 +02:00
can1357 1fe843de2e fix(agent): handled skipped tool calls for non-toolUse assistant turns
- Updated the agent loop to execute tool calls only when the assistant stop reason was `toolUse`.
- Added skipped placeholder `tool_result` messages for leftover `toolCall` blocks when a turn ended without `toolUse`.
- Promoted OpenAI/Ollama `stop` tool-call turns to `toolUse` and stripped thinking signatures on abandoned tool-use turns during message transforms.
2026-05-29 13:31:37 +02:00
can1357 c4f93eca20 fix(agent): patched compaction 401/403 fallback to copy error status
- Centralized compaction stop-reason error throws via createSummarizationError().
- Set compaction thrown errors to copy response.errorStatus into Error.status.
- Expanded compaction auth detection to treat HTTP 401/403 as auth failures with regex fallback preserved.
- Added regression tests for 401/403 status propagation and compaction fallback auth behavior.
- Documented both package fixes in Unreleased Fixed changelog entries.
2026-05-28 12:50:52 +02:00
can1357 c1fa0e9f50 refactor(agent): replaced keepalive utility with disposable EventLoopKeepalive
- Replaced the `keepaliveWhile` Promise wrapper with a new `EventLoopKeepalive` class that registers and disposes an interval timer through `Symbol.dispose`.
- Updated `Agent` to instantiate `EventLoopKeepalive` via `using` during prompt execution instead of manually managing an interval.
- Wrapped interactive mode's await path with the new helper and removed redundant `keepaliveWhile` usage from the CLI entrypoint.
2026-05-28 09:51:57 +02:00
hezhiyang2000 f71e1db0c2 fix: restore #emit listener isolation (isPromise + try/catch)
The previous commit accidentally dropped the try/catch in #emit
because the local agent.ts was based on an older main that lacked
the listener-isolation code. Restore it to match main.
2026-05-28 14:09:01 +08:00
hezhiyang2000 af2011f5a1 fix: use inline setInterval instead of EventLoopKeepalive import
EventLoopKeepalive is not exported from yield.ts on main.
Use setInterval + unref() directly, matching the pattern in
keepaliveWhile(). Biome import order also fixed (type imports
before value imports within the same group).
2026-05-28 11:51:24 +08:00
hezhiyang2000 38d77f819d style: fix Biome import order (type imports before value imports)
Biome organizeImports sorts type imports before value imports within
the same relative-path group. Move EventLoopKeepalive import after
all type imports.
2026-05-28 11:50:15 +08:00
hezhiyang2000 6fb1983fbc fix: add EventLoopKeepalive to Agent.prompt()
Bun 1.3.x event loop busy-waits when the only pending work is an
unresolved Promise. Agent.prompt() sets #runningPromise via
Promise.withResolvers() which stays unresolved during the entire
agent loop execution (LLM calls + tool iterations), causing ~100%
CPU even when the process is idle.

PR #1419 added keepaliveWhile() to getUserInput() in main.ts, but
session.prompt() callers (interactive mode, resume, etc.) still
await the unresolved #runningPromise, bypassing the keepalive.

Install EventLoopKeepalive directly in Agent.prompt() so all
callers are covered. Dispose in the finally block after the agent
loop completes.
2026-05-28 10:07:22 +08:00
cognitiveandmetaphorics 0455c164f9 fix(agent): compact() propagates thinkingLevel to all three summarizers
The thinking-level fix (e07b47ee4) added SummaryOptions.thinkingLevel
and threaded it from agent-session.ts into compact(), but the
field-by-field rebuild of summaryOptions inside compact() (and a
second inline rebuild for generateShortSummary) silently dropped it.

Effect on every call site that fans through compact():
  generateSummary, generateTurnPrefixSummary, generateShortSummary all
  see options?.thinkingLevel === undefined => resolveCompactionEffort
  falls back to Effort.High => user's /model :off selection is
  silently overridden, and xai-oauth/grok-build still trips on the
  unsupported-effort path even though fix #2 strips it at the wire
  layer of the openai-responses mapper.

Add `thinkingLevel: options?.thinkingLevel` at both rebuild sites in
compact(): the summaryOptions literal feeding generateSummary and
generateTurnPrefixSummary, and the inline options literal feeding
generateShortSummary.

Extend compaction-thinking-level.test.ts with four compact()-level
cases driving isSplitTurn:true so all three summarizers fire:
  - Off                  -> every fan-out call gets reasoning=undefined
  - Low                  -> every fan-out call gets reasoning="low"
  - <unset>              -> every fan-out call gets reasoning="high" (default)
  - grok-build + High    -> every fan-out call gets reasoning=undefined (clamp)

TDD red-green verified: stashing the source fix flips the Off and Low
cases to fail with received="high" (exactly the reviewer's prediction);
restoring the fix returns all four to green. Suite: 131 pass / 0 fail
(baseline 127 + 4 new). biome + tsgo --noEmit clean.

Op: correct
Restores: spec:compaction-honors-session-thinking-level
(cherry picked from commit 9b501e369b820cb992345ceb3f2a207ccc9ee233)
2026-05-27 15:01:24 +00:00
cognitiveandmetaphorics 25c6794cd5 fix(agent): compaction honors session thinking level and silent-clamps unsupported-effort models
Triple-stacked failure on the same axis (thinking effort) produced the
user-visible

    Error: Compaction failed: Thinking effort high is not supported by
           xai-oauth/grok-build.
    Supported efforts:

(empty list after the colon) whenever the active model was a curated
xAI catalog entry with compat.supportsReasoningEffort: false.

Three defects lined up. (1) Behavior: compaction at four call sites
in packages/agent/src/compaction/compaction.ts hardcoded
reasoning: Effort.High and never threaded session.thinkingLevel —
the user's /model :off selection (and any explicit low/medium) was
silently overridden. On every other model this was invisible.
(2) Validation: requireSupportedEffort threw at the openai-flavored
mapper layer before the wire-side omitReasoningEffort gate in
providers/xai-responses.ts ever ran; two contradictory guards on the
same wire param. (3) Message: when getSupportedEfforts returned [],
the rendered error tail was 'Supported efforts: ' with nothing after
the colon — disappears as a side-effect of fix #2.

Fix #1 — thread ThinkingLevel | undefined end-to-end. Add
SummaryOptions.thinkingLevel and HandoffOptions.thinkingLevel.
Convert via a single exhaustive switch (effortFromThinkingLevel) in
the new resolveCompactionEffort helper:
  - Off            → undefined  (omit reasoning entirely)
  - undefined/Inherit → Effort.High → clamp per model (preserves the
                                       historical default for users
                                       who never touched the dial)
  - explicit Effort → respect user → clamp per model

resolveCompactionEffort lives in compaction.ts; all four call sites
(generateSummary, generateHandoff, generateShortSummary,
generateTurnPrefixSummary) route through it. agent-session.ts threads
this.thinkingLevel into all three production compaction entry points
(manual /compact at L6201, auto-compaction at L6458 — the most-fired
path, originally missed in plan review — and direct generateHandoff
at L5465). The audit-gate test
(test/agent-session-compaction-thinking-threading.test.ts) scans the
file with a brace-balanced extractor and refuses any unthreaded site.

Fix #2 — silent-clamp at the openai-flavored mapper layer. Extract
exported modelOmitsReasoningEffort(model) in model-thinking.ts as the
single source of truth for compat.supportsReasoningEffort: false on
openai-responses* APIs. getSupportedEfforts now calls it instead of
inlining the check (pure refactor — observable behavior preserved).
resolveOpenAiReasoningEffort in stream.ts early-returns undefined
when the predicate is true, so the wire-side omitReasoningEffort
gate (providers/xai-responses.ts:78) becomes the single source of
truth for the actual strip — no redundant throw.

Three regression tests pin the contract:
  - packages/ai/test/xai-oauth-effort-strip.test.ts (5 tests):
    modelOmitsReasoningEffort returns true for grok-build and
    grok-4.20-0309-reasoning, false for grok-4.3 / Anthropic /
    openai-completions.
  - packages/agent/test/compaction-thinking-level.test.ts (5 tests):
    every ThinkingLevel outcome through generateHandoff — Off stays
    undefined (not coerced to High), Low stays Low, Inherit / undefined
    default to High, grok-build clamps to undefined regardless of
    requested level. Covers the Codex-caught Off-vs-not-provided
    distinction.
  - packages/coding-agent/test/agent-session-compaction-thinking-threading.test.ts
    (2 tests): brace-balanced source scan asserts every direct
    compact() / generateHandoff() in agent-session.ts threads
    'thinkingLevel: this.thinkingLevel'; floor of 3 threaded sites.

TDD red-green verified for fix #1: temporarily reverted the handoff
call-site back to hardcoded Effort.High → compaction-thinking-level
went 2 pass / 3 fail (Off coerced, Low overridden, grok-build throws);
restored → 5 pass / 0 fail.

Verified:
  - packages/agent:  127 pass / 0 fail
  - packages/ai:     1061 pass / 337 skip / 0 fail
  - packages/coding-agent (focused): 179 pass / 5 skip / 0 fail
  - biome + tsgo --noEmit clean across all three packages

Out of scope (follow-ups):
  - branch-summarization.ts:307 already passes no reasoning — no edit.
  - The empty-list error message at model-thinking.ts:296 is now
    structurally unreachable from the openai-responses path.
  - modelOmitsReasoningEffort and grokSupportsReasoningEffort
    (xai-responses.ts:22) overlap; collapse into a single predicate
    in a future commit.

Op: correct
Restores: spec:compaction-honors-session-thinking-level
Restores: spec:xai-oauth-grok-build-compaction-no-throw
(cherry picked from commit e07b47ee46769053c658819437e2478389a4cee0)
2026-05-27 15:01:20 +00:00
can1357 ca86239bda Revert "wip: gentle"
This reverts commit 99bae2ce6c.
2026-05-27 15:01:59 +02:00
can1357 99bae2ce6c wip: gentle 2026-05-27 14:45:02 +02:00
can1357 6643178d6a refactor: replaced instanceof Promise checks with isPromise utility
- Imported isPromise from node:util/types in four modules.
- Replaced four instanceof Promise checks with isPromise calls for more reliable promise detection.
2026-05-27 13:02:52 +02:00
can1357 0e9137a703 refactor(agent/utils): simplified keepaliveWhile implementation using unrefed interval
- Removed the EventLoopKeepalive class and replaced it with a direct setInterval call.
- Added an unref call to the interval timer to prevent blocking the process exit.
2026-05-27 13:01:49 +02:00
Can BölükandGitHub ad8bf18189 Merge pull request #1426 from oldschoola/fix/session-emit-listener-isolation
fix(session): isolate event listener failures in agent fan-out
2026-05-27 13:45:01 +03:00
oldschoola 48d63d4ddf fix(session): satisfy tsgo strict checks on void-return listener
Two TS errors in CI:
1. `agent-session.ts:1317` — `error TS1345: An expression of type 'void'
   cannot be tested for truthiness`. The listener type is
   `(event: AgentSessionEvent) => void`, so the returned value can't be
   directly tested. Same shape in `agent.ts:1079`.
2. `test/session/emit-listener-isolation.test.ts:19` — the test fixture
   for `AgentEvent.tool_execution_start` was missing the required `args`
   field.

Cast the return to `unknown` and check `instanceof Promise` instead of
duck-typing `.then` — type-safe and matches what async functions actually
return. Add `args: {}` to the test fixture.
2026-05-27 01:07:09 -07:00
hezhiyang2000 1126934300 fix: replace ReturnType<typeof setInterval> with NodeJS.Timeout
Project convention (AGENTS.md) prohibits ReturnType<> — use the
concrete type name instead. NodeJS.Timeout matches the existing
pattern used throughout the codebase (e.g. interactive-mode.ts).
2026-05-27 14:59:08 +08:00
hezhiyang2000 2159f56386 fix: add EventLoopKeepalive to eliminate idle busy-wait
Root cause: Bun 1.3.x (JavaScriptCore) busy-waits when the only
pending work is an unresolved Promise. A setInterval keepalive
keeps the event loop in epoll_wait instead of userspace spinning.

- EventLoopKeepalive: setInterval-based keepalive (re-arms after each
  firing, addressing the bot review concern about setTimeout expiry)
- keepaliveWhile(): wrapper to await a Promise with keepalive active
- Applied to getUserInput() in main.ts
- Retains yieldIfDue() and ExponentialYield from #1396

Idle CPU drops from ~100% to ~0% (wchan=do_epoll_wait).
2026-05-27 14:57:46 +08:00
oldschoola 08bccfe0ec fix(session): isolate event listener failures in agent fan-out
Both `AgentSession.#emit` (session/agent-session.ts) and `Agent.#emit`
(packages/agent/src/agent.ts) iterated listeners with no error isolation.
A synchronous throw in any subscriber aborted the for-loop, so later
subscribers (TUI rendering, ACP bridge, task executor progress,
hindsight) silently missed events. Many listeners — see
`modes/controllers/event-controller.ts:141` and
`modes/controllers/input-controller.ts:576` — are registered as
`async (event) => { await this.handleEvent(event); }`; the returned
Promise was dropped, so any rejection became an unhandled rejection.

Wrap each listener invocation in try/catch and attach a `.catch` to any
returned thenable. Errors are logged via `logger.warn` (already imported
in agent-session.ts) and `console.error` (agent.ts has no logger
dependency, keep it that way).

Test: new `test/session/emit-listener-isolation.test.ts` registers two
listeners on both classes; first listener throws (or returns a rejecting
Promise); asserts the second listener still receives the event AND no
`unhandledRejection` fires. 4 cases (sync+async × Agent+AgentSession).
All fail on current main; all pass with the fix.
2026-05-26 23:26:15 -07:00
can1357 e4a16451ec feat(coding-agent): added coding-agent approval types and mode options
- Added `ToolTier`, `ToolApproval`, and `ToolApprovalDecision` types and exported approval APIs.
- Updated approval-mode options from `auto|prompt|custom` to `always-ask|write|yolo` and defaulted mode to `yolo`.
- Changed approval resolution to apply per-tool decisions first, then mode-tier limits, with legacy-mode migration.
- Assigned read/write/exec `approval` and approval-detail prompts across built-in, custom, extension, and MCP tools.
2026-05-26 21:52:16 +02:00
can1357 3078d31a4c chore: reformat 2026-05-26 20:44:09 +02:00