Skip to content

fix(core): address @tanzhenxin's PR-1 review notes (post-merge follow-up to #3842)#3886

Merged
tanzhenxin merged 5 commits into
mainfrom
fix/shell-promote-followup-review-notes
May 7, 2026
Merged

fix(core): address @tanzhenxin's PR-1 review notes (post-merge follow-up to #3842)#3886
tanzhenxin merged 5 commits into
mainfrom
fix/shell-promote-followup-review-notes

Conversation

@wenshao

@wenshao wenshao commented May 7, 2026

Copy link
Copy Markdown
Collaborator

Fast-follow on the PR-1 of #3831 (merged at #3842). @tanzhenxin's approving review left three non-blocking notes; this PR addresses two of them. Note 1 stays for PR-2.

Fixed

Note 2 (real bug) — `getShellAbortReasonKind` Proxy-trap leak

The `hasOwnProperty.call` on the reason runs outside the `try`. A Proxy with a throwing `getOwnPropertyDescriptor` trap would propagate past the helper. Probably worth moving the check inside the try.

Confirmed: `Object.prototype.hasOwnProperty.call(reason, 'kind')` triggers the `[[GetOwnProperty]]` Proxy trap. A Proxy whose `getOwnPropertyDescriptor` handler throws would propagate up past the helper, then through the abort handler's `switch` on `kind`, then out of the (async) abort listener. `addEventListener` doesn't await the listener's returned Promise, so a leaked throw would silently leave the shell process alive instead of being killed on cancel.

Wrapped both the descriptor probe and the value read inside the same try block. Added a regression test that uses a Proxy whose `getOwnPropertyDescriptor` throws — mirror of the existing throwing-`get` Proxy test.

My multi-round self-audit before merge covered 6 attack vectors but missed this one — only `get` trap throws were considered. Worth noting publicly in case anyone else iterates here.

Note 3 (test parity) — PTY handoff-boundary assertion

Asserts `dispose` was called, but doesn't emit data after the abort to verify the foreground handler actually stops firing — the child_process equivalent does. Worth mirroring when convenient.

Mirrored. The PTY post-promotion test now re-invokes the production `onData` callback (via `mockPtyProcess.onData.mock.calls[0][0]`) AFTER the background abort, then awaits two `setImmediate` cycles to let any chain microtask fully settle, and asserts `onOutputEventMock.mock.calls.length === 0` both pre and post — the strict `=== 0` baseline pin guarantees the assertion fails if a future refactor lets the pre-promote chain item emit before `listenersDetached` is set, not just if pre and post counts diverge. Exercises the production `listenersDetached` guard inside the chain callback, which the bare `dispose-was-called` check didn't.

(PTY's `handleOutput` is async via `processingChain` — child_process's equivalent is sync, so the mirror needed extra microtask flush + an explicit `=== 0` baseline rather than just asserting the count didn't change between two synchronous reads.)

Deferred to PR-2

Note 1 — `aborted` + `promoted` shape

Both promote branches resolve with `aborted: true, promoted: true`. The existing `tools/shell.ts` consumer checks `aborted` first and emits "cancelled" copy. PR-2 callers will need to branch on `promoted` before `aborted`. Might be cleaner to set `aborted: false` when `promoted: true` so existing branches just work.

This is a contract simplification that affects PR-2's branching logic. Doing it here would change the result shape AGAIN before PR-2 lands, and PR-2 may want to weigh `aborted: false` vs an explicit `if (result.promoted)` check upstream of the cancel/timeout copy. Tracked as design question 7 in #3831 — PR-2 will resolve this along with the caller-side branching logic.

Test plan

  • `vitest run packages/core/src/services/shellExecutionService.test.ts`: 70 / 70 pass (69 baseline + 1 new for the throwing-`getOwnPropertyDescriptor` case)
  • `tsc --build packages/cli` (CI-equivalent full mode): clean
  • ESLint: clean
  • Manual (deferred — same as PR-1, no user-visible surface in this fast-follow): real `@lydell/node-pty` handoff exercised by PR-2.

Related

cc @tanzhenxin

wenshao added 4 commits May 7, 2026 10:36
…ata assertion

Two non-blocking review notes from @tanzhenxin's PR-1 approval
(#3842, post-merge follow-up):

- **Note 2 (real bug)**: `getShellAbortReasonKind` had
  `Object.prototype.hasOwnProperty.call(reason, 'kind')` outside the
  try/catch. `hasOwnProperty.call` triggers the `[[GetOwnProperty]]`
  Proxy trap (`getOwnPropertyDescriptor` handler). A Proxy whose
  `getOwnPropertyDescriptor` throws — separate from a throwing `get`
  trap, which the prior commit already covered — would propagate past
  the helper, leaving the abort handler's switch on `kind` to throw
  through `addEventListener` (which doesn't await async listener
  return values), so the shell process would stay alive instead of
  being killed on cancel. Moved the descriptor probe inside the same
  try block as the value read.

  My own multi-round audit covered six attack vectors but missed this
  one — only `get` trap throws were considered. The reviewer caught
  it on first pass.

- **Note 3 (test parity)**: the PTY post-promotion handoff test
  asserted `dispose` was called but never re-invoked the data
  callback to verify the foreground `onOutputEvent` actually stops
  firing — the child_process equivalent has that assertion. Mirrored
  it: emit data AFTER abort by re-invoking the captured `dataCallback`
  reference, and assert `onOutputEventMock.mock.calls.length` does NOT
  increase past the moment of promote. Exercises the production
  `listenersDetached` guard inside the chain callback, which the
  bare dispose-was-called check didn't.

Note 1 from the same review (the `aborted: true + promoted: true`
shape forcing PR-2 callers to check `promoted` before `aborted`) is
deliberately NOT addressed here — it's a contract simplification that
affects PR-2's branching, so it belongs in PR-2 along with the
caller-side decision on whether to flip `aborted` for promoted
results. Added a TODO upstream in #3831 (PR-2 design) to track.

70 / 70 tests pass (69 baseline + 1 new helper boundary for the
throwing-getOwnPropertyDescriptor case). tsc + ESLint clean.
…-up)

The PTY post-promotion handoff test added in the previous commit
copied the child_process equivalent's pattern verbatim — sync
\`expect(count).toBe(countAtPromote)\` immediately after dataCallback
returns. That works for child_process because its \`handleOutput\` is
fully synchronous (sniff → decoder → emit, all on the same call
stack), so the count change happens BEFORE the assertion.

PTY's \`handleOutput\` is async — \`processingChain.then(...)\` queues a
microtask that does the sniff + write + render-then-emit work. The
sync assertion captures both \`countAtPromote\` and the post-emit count
BEFORE the chain microtask ever runs, so both reads return whatever
happened before the assert (typically 0). The test would
tautologically pass even if the production \`listenersDetached\` guard
were removed — i.e., it didn't actually verify the guard.

Restructured to:
1. Drive the PTY through \`simulateExecution\` so \`await handle.result\`
   forces all queued microtasks (including pre-promote chain items
   AND the abort handler's drain) to settle.
2. Capture \`eventCountAfterSettle\` once everything has stabilized.
3. Re-invoke the captured \`dataCallback\` with post-promote data,
   await two more macrotask boundaries to let the new chain item
   fully run.
4. Assert the count hasn't moved.

If the production \`listenersDetached\` guard is removed, the
post-promote chain item emits, count increases past
\`eventCountAfterSettle\`, and this assertion fails. So the test
actually exercises the guard now.

Found in self-audit while reviewing my own follow-up commit. Caught
because audit was paranoid about *whether the test verifies what it
claims to verify*, not just whether it passes.

70 / 70 tests pass; tsc + ESLint clean.
The previous fix asserted post-promote count equals the
\`eventCountAfterSettle\` baseline, but didn't pin the baseline
itself. With the production \`listenersDetached\` guard intact, both
halves (pre-promote chain and post-promote chain) suppress emit, so
\`eventCountAfterSettle === 0 === post-count\` and the relative
comparison is vacuously true.

If a future refactor changed the production guard semantics so the
pre-promote chain item DID emit (count becomes 1+ after settle), the
relative-comparison test would still pass as long as post-promote
also emitted the same number — that's a regression the test should
catch. Adding \`expect(eventCountAfterSettle).toBe(0)\` makes the
contract explicit: once \`listenersDetached\` is set during the abort
handler's sync part, BOTH the in-flight chain item (pre-promote) and
the future chain item (post-promote) skip emit.

Found in another self-audit pass — even after fixing the tautological
assertion, the test could still mask certain future regressions.
70 / 70 tests pass; tsc + ESLint clean.
The dataCallbackHolder pattern (capturing the onData callback inside
simulateExecution then invoking it after via a closure-shared object)
was unnecessary indirection — \`mockPtyProcess.onData.mock.calls[0][0]\`
reads the same callback reference whether you read it inside or outside
the simulation closure. Vi's mock.calls array is per-mock-instance and
beforeEach re-creates mockPtyProcess + .onData freshly, so there's no
stale-reference risk in the simpler form.

No behavior change in what's tested. 70 / 70 pass.
// (`getOwnPropertyDescriptor` handler), so a Proxy whose
// `getOwnPropertyDescriptor` throws — separate from a throwing
// `get` trap — would otherwise propagate past the helper. (Caught
// by @tanzhenxin in the PR-1 review; my own audit only covered

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] This explanation is useful, but the parenthetical ties long-lived production code to ephemeral review-process context (@tanzhenxin, PR-1 review, my own audit). Future readers only need the durable technical reason: hasOwnProperty.call can trigger the Proxy getOwnPropertyDescriptor trap, so both the descriptor probe and value read must stay inside the try.

Consider keeping the Proxy-trap rationale and dropping the attribution/review-history sentence.

— gpt-5.5 via Qwen Code /review

expect(getShellAbortReasonKind(proxyReason)).toBe('cancel');
});

it("returns 'cancel' when the `getOwnPropertyDescriptor` Proxy trap throws (regression for @tanzhenxin's PR-1 review note)", () => {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The test covers the right regression, but the name points to reviewer/review-thread history instead of the behavior under test. Test names are long-lived documentation, so this will be easier to maintain if it describes the boundary directly.

For example, this could be shortened to something behavior-focused like:

it("returns 'cancel' when the getOwnPropertyDescriptor Proxy trap throws", () => {

— gpt-5.5 via Qwen Code /review

…ndler

Two cosmetic cleanups found in another self-audit pass:

- **Helper comment**: removed the parenthetical attribution ("Caught
  by @tanzhenxin in the PR-1 review; my own audit only covered \`get\`
  trap throws."). The technical content of the comment — explaining
  why both the descriptor probe and the value read live inside the
  try — stands on its own. The reviewer credit lives in commit
  history / PR description, where it belongs; an in-source @-mention
  ages poorly (handles change, the relevant person may move on) and
  doesn't help future readers reason about the code.

- **Test Proxy**: \`throwingDescriptorProxy\` declared a \`get()\` handler
  that always returned \`undefined\`. The descriptor probe throws
  before the helper ever reaches the value read, so the \`get\`
  handler is unreachable — dropped it and added a one-line comment
  explaining why no \`get\` handler is needed for this test. Mirror
  test (\`throwingReason\` with throwing accessor + \`proxyReason\` with
  throwing \`get\`) keeps the symmetric "throwing-`get`" coverage.

70 / 70 tests pass; tsc + ESLint clean.
@github-actions

github-actions Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Summary

Package Lines Statements Functions Branches
CLI 55.99% 55.99% 71.91% 79.28%
Core 76.57% 76.57% 79.14% 82.14%
CLI Package - Full Text Report
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   55.99 |    79.28 |   71.91 |   55.99 |                   
 src               |   67.99 |    62.34 |   74.19 |   67.99 |                   
  gemini.tsx       |   59.52 |    58.88 |   66.66 |   59.52 | ...62,770-773,781 
  ...ractiveCli.ts |   69.53 |    57.42 |   72.72 |   69.53 | ...21-768,776-783 
  ...liCommands.ts |   73.92 |     72.5 |     100 |   73.92 | ...40-264,289,389 
  ...ActiveAuth.ts |     100 |     87.5 |     100 |     100 | 66-80             
 ...cp-integration |    46.3 |    63.01 |   55.88 |    46.3 |                   
  acpAgent.ts      |   48.12 |    63.38 |   62.06 |   48.12 | ...91-793,807-815 
  authMethods.ts   |   12.19 |      100 |       0 |   12.19 | 11-31,34-38,41-50 
  errorCodes.ts    |       0 |        0 |       0 |       0 | 1-22              
  ...DirContext.ts |     100 |      100 |     100 |     100 |                   
 ...ration/service |   68.65 |    83.33 |   66.66 |   68.65 |                   
  filesystem.ts    |   68.65 |    83.33 |   66.66 |   68.65 | ...32,77-94,97-98 
 ...ration/session |   74.86 |    68.57 |    82.6 |   74.86 |                   
  ...ryReplayer.ts |   65.93 |    75.67 |   81.81 |   65.93 | ...40-255,268-269 
  Session.ts       |   73.52 |    66.12 |   82.92 |   73.52 | ...2322,2328-2331 
  ...entTracker.ts |   90.85 |    84.84 |      90 |   90.85 | ...35,199,251-260 
  index.ts         |       0 |        0 |       0 |       0 | 1-40              
  ...ssionUtils.ts |   84.21 |    77.77 |     100 |   84.21 | ...37-153,209-211 
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...ssion/emitters |   96.01 |    90.75 |    92.3 |   96.01 |                   
  BaseEmitter.ts   |   76.92 |    66.66 |      80 |   76.92 | 23-24,39-40,55-56 
  ...ageEmitter.ts |     100 |    89.47 |     100 |     100 | 109,111           
  PlanEmitter.ts   |     100 |      100 |     100 |     100 |                   
  ...allEmitter.ts |   98.06 |     92.3 |     100 |   98.06 | 227-228,327,335   
  index.ts         |       0 |        0 |       0 |       0 | 1-10              
 ...ession/rewrite |   89.69 |    85.89 |   94.11 |   89.69 |                   
  LlmRewriter.ts   |   80.53 |    79.31 |     100 |   80.53 | ...17-119,170-174 
  ...Middleware.ts |   95.83 |    85.71 |     100 |   95.83 | 119,127-129       
  TurnBuffer.ts    |     100 |      100 |     100 |     100 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 src/commands      |   62.18 |      100 |    9.52 |   62.18 |                   
  auth.ts          |   46.91 |      100 |       0 |   46.91 | ...,91-98,101-102 
  channel.ts       |   56.66 |      100 |       0 |   56.66 | 15-19,27-34       
  extensions.tsx   |   96.55 |      100 |      50 |   96.55 | 37                
  hooks.tsx        |   66.66 |      100 |       0 |   66.66 | 20-24             
  mcp.ts           |   94.73 |      100 |      50 |   94.73 | 28                
  review.ts        |   51.85 |      100 |       0 |   51.85 | 24-35,38          
 src/commands/auth |   66.16 |    79.82 |   78.94 |   66.16 |                   
  handler.ts       |   47.07 |    74.68 |   35.29 |   47.07 | ...-968,1058-1068 
  ...veSelector.ts |     100 |    96.66 |     100 |     100 | 58                
  ...outerOAuth.ts |   89.02 |    78.99 |   96.87 |   89.02 | ...18-622,716-718 
 ...mmands/channel |    39.2 |    79.45 |      50 |    39.2 |                   
  ...l-registry.ts |    8.57 |      100 |       0 |    8.57 | 6-21,24-42        
  config-utils.ts  |   91.89 |      100 |   66.66 |   91.89 | 20-25             
  configure.ts     |    14.7 |      100 |       0 |    14.7 | 18-21,23-84       
  pairing.ts       |   26.31 |      100 |       0 |   26.31 | ...30,40-50,52-65 
  pidfile.ts       |   96.34 |    86.95 |     100 |   96.34 | 49,59,91          
  start.ts         |   31.15 |       52 |   69.23 |   31.15 | ...73-476,485-487 
  status.ts        |   17.54 |      100 |       0 |   17.54 | 15-26,32-77       
  stop.ts          |      20 |      100 |       0 |      20 | 14-48             
 ...nds/extensions |   84.53 |    88.95 |   81.81 |   84.53 |                   
  consent.ts       |   71.65 |    89.28 |   42.85 |   71.65 | ...85-141,156-162 
  disable.ts       |     100 |      100 |     100 |     100 |                   
  enable.ts        |     100 |      100 |     100 |     100 |                   
  install.ts       |    75.6 |    66.66 |   66.66 |    75.6 | ...39-142,145-153 
  link.ts          |     100 |      100 |     100 |     100 |                   
  list.ts          |     100 |      100 |     100 |     100 |                   
  new.ts           |     100 |      100 |     100 |     100 |                   
  settings.ts      |   99.15 |      100 |   83.33 |   99.15 | 151               
  uninstall.ts     |    37.5 |      100 |   33.33 |    37.5 | 23-45,57-64,67-70 
  update.ts        |   96.32 |      100 |     100 |   96.32 | 101-105           
  utils.ts         |   60.24 |    28.57 |     100 |   60.24 | ...81,83-87,89-93 
 ...les/mcp-server |       0 |        0 |       0 |       0 |                   
  example.ts       |       0 |        0 |       0 |       0 | 1-60              
 src/commands/mcp  |   92.29 |    86.08 |   88.88 |   92.29 |                   
  add.ts           |     100 |    98.03 |     100 |     100 | 293               
  list.ts          |   91.22 |    80.76 |      80 |   91.22 | ...19-121,146-147 
  reconnect.ts     |   76.72 |    71.42 |   85.71 |   76.72 | 35-48,153-175     
  remove.ts        |     100 |       80 |     100 |     100 | 21-25             
 ...ommands/review |   11.57 |      100 |       0 |   11.57 |                   
  cleanup.ts       |   17.94 |      100 |       0 |   17.94 | ...01-106,108-109 
  deterministic.ts |   13.75 |      100 |       0 |   13.75 | ...22-738,740-741 
  fetch-pr.ts      |   11.36 |      100 |       0 |   11.36 | ...80-201,203-204 
  load-rules.ts    |   11.32 |      100 |       0 |   11.32 | ...41-153,155-156 
  pr-context.ts    |    6.22 |      100 |       0 |    6.22 | ...97-312,314-315 
  presubmit.ts     |    9.35 |      100 |       0 |    9.35 | ...62-287,289-290 
 ...nds/review/lib |      30 |      100 |       0 |      30 |                   
  gh.ts            |   22.58 |      100 |       0 |   22.58 | ...49,53-54,62-69 
  git.ts           |   22.72 |      100 |       0 |   22.72 | 15-18,29-39,43-44 
  paths.ts         |   52.94 |      100 |       0 |   52.94 | ...26,37-38,42-43 
 src/config        |   92.19 |    82.54 |   84.72 |   92.19 |                   
  auth.ts          |   87.87 |    81.35 |     100 |   87.87 | ...20-221,237-238 
  config.ts        |   86.36 |    82.53 |   72.72 |   86.36 | ...1339,1361-1362 
  keyBindings.ts   |   95.95 |       50 |     100 |   95.95 | 160-163           
  ...idersScope.ts |      92 |       90 |     100 |      92 | 11-12             
  sandboxConfig.ts |    58.9 |    61.53 |   66.66 |    58.9 | ...54-68,73,77-89 
  settings.ts      |   83.13 |    82.55 |   85.71 |   83.13 | ...35-936,941-944 
  ...ingsSchema.ts |     100 |      100 |     100 |     100 |                   
  ...tedFolders.ts |   96.29 |       94 |     100 |   96.29 | ...88-190,205-206 
 ...nfig/migration |   94.56 |    78.94 |   83.33 |   94.56 |                   
  index.ts         |   93.93 |    88.88 |     100 |   93.93 | 85-86             
  scheduler.ts     |   96.55 |    77.77 |     100 |   96.55 | 19-20             
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...ation/versions |   93.63 |     94.5 |     100 |   93.63 |                   
  ...-v2-shared.ts |     100 |      100 |     100 |     100 |                   
  v1-to-v2.ts      |   81.75 |    90.19 |     100 |   81.75 | ...28-229,231-247 
  v2-to-v3.ts      |     100 |      100 |     100 |     100 |                   
 src/constants     |   11.97 |     87.5 |   16.66 |   11.97 |                   
  ...dardApiKey.ts |     100 |      100 |     100 |     100 |                   
  codingPlan.ts    |    8.75 |     87.5 |   16.66 |    8.75 | ...22-327,335-347 
 src/core          |     100 |      100 |     100 |     100 |                   
  auth.ts          |     100 |      100 |     100 |     100 |                   
  initializer.ts   |     100 |      100 |     100 |     100 |                   
  theme.ts         |     100 |      100 |     100 |     100 |                   
 src/dualOutput    |   63.09 |    64.51 |   55.55 |   63.09 |                   
  ...tputBridge.ts |   62.94 |    65.51 |   56.25 |   62.94 | ...22-323,331-334 
  ...utContext.tsx |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-8               
 src/export        |       0 |        0 |       0 |       0 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-7               
 src/generated     |     100 |      100 |     100 |     100 |                   
  git-commit.ts    |     100 |      100 |     100 |     100 |                   
 src/i18n          |   48.26 |    76.19 |   38.88 |   48.26 |                   
  index.ts         |   26.92 |    76.92 |   26.66 |   26.92 | ...38-239,249-260 
  languages.ts     |    98.7 |       75 |     100 |    98.7 | 110               
 src/i18n/locales  |       0 |        0 |       0 |       0 |                   
  ca.js            |       0 |        0 |       0 |       0 | 1-2146            
  de.js            |       0 |        0 |       0 |       0 | 1-2069            
  en.js            |       0 |        0 |       0 |       0 | 1-2119            
  fr.js            |       0 |        0 |       0 |       0 | 1-2102            
  ja.js            |       0 |        0 |       0 |       0 | 1-1560            
  pt.js            |       0 |        0 |       0 |       0 | 1-2060            
  ru.js            |       0 |        0 |       0 |       0 | 1-2065            
  zh-TW.js         |       0 |        0 |       0 |       0 | 1-1681            
  zh.js            |       0 |        0 |       0 |       0 | 1-1920            
 ...nonInteractive |   72.67 |    72.14 |   74.07 |   72.67 |                   
  session.ts       |   76.86 |    70.45 |   85.71 |   76.86 | ...78-779,787-797 
  types.ts         |    42.5 |      100 |   33.33 |    42.5 | ...80-581,584-585 
 ...active/control |   77.55 |    88.23 |      80 |   77.55 |                   
  ...rolContext.ts |    7.69 |        0 |       0 |    7.69 | 47-79             
  ...Dispatcher.ts |   91.66 |    91.83 |   88.88 |   91.66 | ...54-372,388,391 
  ...rolService.ts |       8 |        0 |       0 |       8 | 46-179            
 ...ol/controllers |    7.04 |       80 |   13.33 |    7.04 |                   
  ...Controller.ts |   19.32 |      100 |      60 |   19.32 | 81-118,127-210    
  ...Controller.ts |       0 |        0 |       0 |       0 | 1-56              
  ...Controller.ts |    3.96 |      100 |   11.11 |    3.96 | ...61-379,389-494 
  ...Controller.ts |   14.06 |      100 |       0 |   14.06 | ...82-117,130-133 
  ...Controller.ts |    5.21 |      100 |       0 |    5.21 | ...21-433,442-471 
 .../control/types |       0 |        0 |       0 |       0 |                   
  serviceAPIs.ts   |       0 |        0 |       0 |       0 | 1                 
 ...Interactive/io |   97.59 |    93.06 |   95.18 |   97.59 |                   
  ...putAdapter.ts |   97.33 |    91.89 |   98.07 |   97.33 | ...1343,1368-1369 
  ...putAdapter.ts |      96 |    91.66 |   85.71 |      96 | 51-52             
  ...nputReader.ts |     100 |    94.73 |     100 |     100 | 67                
  ...putAdapter.ts |   98.28 |      100 |      90 |   98.28 | 81-82,122-123     
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/patches       |       0 |        0 |       0 |       0 |                   
  is-in-ci.ts      |       0 |        0 |       0 |       0 | 1-17              
 src/remoteInput   |   86.98 |       75 |   85.71 |   86.98 |                   
  ...utContext.tsx |     100 |      100 |     100 |     100 |                   
  ...putWatcher.ts |   88.12 |    76.08 |   91.66 |   88.12 | ...21-222,233-236 
  index.ts         |       0 |        0 |       0 |       0 | 1-8               
 src/services      |   90.37 |    89.75 |   94.28 |   90.37 |                   
  ...mandLoader.ts |     100 |     92.3 |     100 |     100 | 89                
  ...killLoader.ts |     100 |    96.29 |     100 |     100 | 44                
  ...andService.ts |    93.5 |      100 |      80 |    93.5 | 107,150-153       
  ...mandLoader.ts |   86.83 |    83.87 |     100 |   86.83 | ...30-335,340-345 
  ...omptLoader.ts |   75.32 |    80.64 |   83.33 |   75.32 | ...05-206,272-273 
  ...mandLoader.ts |     100 |      100 |     100 |     100 |                   
  ...nd-factory.ts |      91 |     90.9 |     100 |      91 | 123,132-139       
  ...ation-tool.ts |     100 |    95.45 |     100 |     100 | 125               
  commandUtils.ts  |      96 |       90 |     100 |      96 | 48                
  ...and-parser.ts |   90.69 |    85.71 |     100 |   90.69 | 63-66             
  ...ionService.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...ght/generators |   85.95 |    86.42 |   90.47 |   85.95 |                   
  DataProcessor.ts |   85.68 |    86.46 |   92.85 |   85.68 | ...1110,1114-1121 
  ...tGenerator.ts |   98.21 |    85.71 |     100 |   98.21 | 46                
  ...teRenderer.ts |   45.45 |      100 |       0 |   45.45 | 13-51             
 .../insight/types |       0 |       50 |      50 |       0 |                   
  ...sightTypes.ts |       0 |        0 |       0 |       0 |                   
  ...sightTypes.ts |       0 |        0 |       0 |       0 | 1                 
 ...mpt-processors |   97.27 |    94.04 |     100 |   97.27 |                   
  ...tProcessor.ts |     100 |      100 |     100 |     100 |                   
  ...eProcessor.ts |   94.52 |    84.21 |     100 |   94.52 | 46-47,93-94       
  ...tionParser.ts |     100 |      100 |     100 |     100 |                   
  ...lProcessor.ts |   97.41 |    95.65 |     100 |   97.41 | 95-98             
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/services/tips |   97.35 |    83.07 |     100 |   97.35 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  tipHistory.ts    |   92.45 |       70 |     100 |   92.45 | ...22,144,151,160 
  tipRegistry.ts   |     100 |    95.23 |     100 |     100 | 33                
  tipScheduler.ts  |     100 |    91.66 |     100 |     100 | 55                
 src/test-utils    |   93.75 |    83.33 |      80 |   93.75 |                   
  ...omMatchers.ts |   69.69 |       50 |      50 |   69.69 | 32-35,37-39,45-47 
  ...andContext.ts |     100 |      100 |     100 |     100 |                   
  render.tsx       |     100 |      100 |     100 |     100 |                   
 src/ui            |   63.21 |    68.42 |   51.28 |   63.21 |                   
  App.tsx          |     100 |      100 |     100 |     100 |                   
  AppContainer.tsx |   65.87 |    62.67 |   66.66 |   65.87 | ...2279,2283-2287 
  ...tionNudge.tsx |    9.58 |      100 |       0 |    9.58 | 24-94             
  ...ackDialog.tsx |   29.23 |      100 |       0 |   29.23 | 25-75             
  ...tionNudge.tsx |    7.69 |      100 |       0 |    7.69 | 25-103            
  colors.ts        |   52.72 |      100 |   23.52 |   52.72 | ...52,54-55,60-61 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  keyMatchers.ts   |   91.83 |       90 |     100 |   91.83 | 25-26,54-55       
  ...tic-colors.ts |     100 |      100 |     100 |     100 |                   
  textConstants.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/ui/auth       |   53.26 |    65.51 |      68 |   53.26 |                   
  AuthDialog.tsx   |   67.75 |    64.95 |    65.9 |   67.75 | ...1271,1273,1275 
  ...nProgress.tsx |       0 |        0 |       0 |       0 | 1-64              
  useAuth.ts       |    34.3 |    70.37 |     100 |    34.3 | ...14-920,922-937 
 src/ui/commands   |   59.85 |    77.91 |   60.78 |   59.85 |                   
  aboutCommand.ts  |     100 |    85.71 |     100 |     100 | 36                
  agentsCommand.ts |   72.97 |      100 |      20 |   72.97 | ...32,37-38,42-44 
  ...odeCommand.ts |     100 |      100 |     100 |     100 |                   
  arenaCommand.ts  |   33.13 |    67.64 |    37.5 |   33.13 | ...60-565,644-649 
  authCommand.ts   |     100 |      100 |     100 |     100 |                   
  btwCommand.ts    |   95.59 |    71.42 |     100 |   95.59 | 72,154-159        
  bugCommand.ts    |   77.35 |    66.66 |      50 |   77.35 | 21-22,60-69       
  clearCommand.ts  |   90.58 |    73.68 |      50 |   90.58 | ...46,74-75,93-94 
  ...essCommand.ts |   63.39 |       48 |      50 |   63.39 | ...48-149,163-166 
  ...extCommand.ts |    6.17 |      100 |      10 |    6.17 | ...21-522,527-528 
  copyCommand.ts   |     100 |      100 |     100 |     100 |                   
  deleteCommand.ts |     100 |      100 |     100 |     100 |                   
  ...ryCommand.tsx |   66.11 |    76.74 |   55.55 |   66.11 | ...05-306,315-323 
  docsCommand.ts   |   96.07 |     87.5 |      50 |   96.07 | 20-21             
  doctorCommand.ts |     100 |    93.33 |     100 |     100 | 21                
  dreamCommand.ts  |      75 |    66.66 |   66.66 |      75 | 22-27,44-47       
  editorCommand.ts |     100 |      100 |     100 |     100 |                   
  exportCommand.ts |   56.93 |    91.66 |   33.33 |   56.93 | ...52-353,361-362 
  ...onsCommand.ts |   45.08 |    85.71 |   27.27 |   45.08 | ...37-238,247-248 
  forgetCommand.ts |   26.82 |      100 |      50 |   26.82 | 18-51             
  helpCommand.ts   |     100 |      100 |     100 |     100 |                   
  hooksCommand.ts  |   19.04 |       25 |      20 |   19.04 | ...86-187,204-205 
  ideCommand.ts    |   57.33 |    57.69 |   35.29 |   57.33 | ...05-306,310-324 
  initCommand.ts   |   84.33 |    72.72 |     100 |   84.33 | 68,82-87,89-94    
  ...ghtCommand.ts |    72.8 |    66.66 |   83.33 |    72.8 | ...31-245,250-273 
  ...ageCommand.ts |   89.39 |    82.35 |   76.92 |   89.39 | ...22-325,348-349 
  ...elsCommand.ts |     100 |      100 |     100 |     100 |                   
  mcpCommand.ts    |   86.66 |      100 |      50 |   86.66 | 14-15             
  memoryCommand.ts |   86.66 |      100 |      50 |   86.66 | 14-15             
  modelCommand.ts  |   42.19 |       65 |      50 |   42.19 | ...35-168,175-193 
  ...onsCommand.ts |     100 |      100 |     100 |     100 |                   
  planCommand.ts   |   78.82 |    76.92 |     100 |   78.82 | 30-35,51-56,68-73 
  quitCommand.ts   |   93.93 |      100 |      50 |   93.93 | 15-16             
  recapCommand.ts  |   21.81 |      100 |      50 |   21.81 | 24-73             
  ...berCommand.ts |   32.43 |      100 |      50 |   32.43 | 23-57             
  renameCommand.ts |   85.61 |    78.18 |     100 |   85.61 | ...15-322,329-334 
  ...oreCommand.ts |    92.3 |     87.5 |     100 |    92.3 | ...,83-88,129-130 
  resumeCommand.ts |     100 |      100 |     100 |     100 |                   
  rewindCommand.ts |      80 |      100 |      50 |      80 | 19-21             
  ...ngsCommand.ts |     100 |      100 |     100 |     100 |                   
  ...hubCommand.ts |   81.43 |    65.21 |      80 |   81.43 | ...70-173,176-179 
  skillsCommand.ts |   15.04 |      100 |      25 |   15.04 | ...90-106,109-136 
  statsCommand.ts  |   83.91 |    81.25 |      50 |   83.91 | ...31-132,142-145 
  ...ineCommand.ts |     100 |      100 |     100 |     100 |                   
  ...aryCommand.ts |    6.51 |      100 |      50 |    6.51 | 28-323            
  tasksCommand.ts  |   77.45 |    73.43 |     100 |   77.45 | ...55-159,181-186 
  ...tupCommand.ts |     100 |      100 |     100 |     100 |                   
  themeCommand.ts  |     100 |      100 |     100 |     100 |                   
  toolsCommand.ts  |   95.23 |      100 |      50 |   95.23 | 18-19             
  trustCommand.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
  vimCommand.ts    |   54.54 |      100 |      50 |   54.54 | 19-29             
 src/ui/components |   59.75 |    73.87 |   62.82 |   59.75 |                   
  AboutBox.tsx     |     100 |      100 |     100 |     100 |                   
  AnsiOutput.tsx   |   65.57 |      100 |      50 |   65.57 | 69-90             
  ApiKeyInput.tsx  |   18.91 |      100 |       0 |   18.91 | 30-95             
  AppHeader.tsx    |   88.88 |    81.81 |     100 |   88.88 | 34-40,42          
  ...odeDialog.tsx |     9.7 |      100 |       0 |     9.7 | 35-47,50-182      
  AsciiArt.ts      |     100 |      100 |     100 |     100 |                   
  ...Indicator.tsx |   14.63 |      100 |       0 |   14.63 | 18-56             
  ...TextInput.tsx |   67.83 |    72.09 |      50 |   67.83 | ...30-232,250,259 
  Composer.tsx     |   79.31 |    57.14 |     100 |   79.31 | ...-77,95,133,146 
  ...entPrompt.tsx |     100 |      100 |     100 |     100 |                   
  ...ryDisplay.tsx |   75.89 |    62.06 |     100 |   75.89 | ...,88,93-108,113 
  ...geDisplay.tsx |   68.42 |    57.14 |     100 |   68.42 | 16-17,31-32,42-50 
  ...ification.tsx |   28.57 |      100 |       0 |   28.57 | 16-36             
  ...gProfiler.tsx |       0 |        0 |       0 |       0 | 1-36              
  ...ogManager.tsx |    12.4 |      100 |       0 |    12.4 | 61-457            
  ...ngsDialog.tsx |    8.44 |      100 |       0 |    8.44 | 37-195            
  ExitWarning.tsx  |     100 |      100 |     100 |     100 |                   
  ...hProgress.tsx |    87.8 |    33.33 |     100 |    87.8 | 28-31,56          
  ...ustDialog.tsx |     100 |      100 |     100 |     100 |                   
  Footer.tsx       |   79.67 |    58.06 |     100 |   79.67 | ...98-102,104-108 
  ...ngSpinner.tsx |   54.28 |       50 |      50 |   54.28 | 31-48,61          
  Header.tsx       |   98.38 |    92.59 |     100 |   98.38 | 139,141           
  Help.tsx         |   98.74 |    68.75 |     100 |   98.74 | 74,129            
  ...emDisplay.tsx |   62.72 |     37.5 |     100 |   62.72 | ...18-327,330,333 
  ...ngeDialog.tsx |     100 |      100 |     100 |     100 |                   
  InputPrompt.tsx  |   81.63 |    76.68 |   83.33 |   81.63 | ...1339,1404,1454 
  ...Shortcuts.tsx |   20.87 |      100 |       0 |   20.87 | ...6,49-51,67-125 
  ...Indicator.tsx |     100 |    91.42 |     100 |     100 | 65,74             
  ...firmation.tsx |   91.42 |      100 |      50 |   91.42 | 26-31             
  MainContent.tsx  |   57.66 |    54.54 |     100 |   57.66 | ...89-200,209-223 
  ...elsDialog.tsx |   16.07 |    89.18 |      50 |   16.07 | ...58-159,162-648 
  MemoryDialog.tsx |   53.35 |    51.21 |   57.14 |   53.35 | ...55,367,380-382 
  ...geDisplay.tsx |       0 |        0 |       0 |       0 | 1-41              
  ModelDialog.tsx  |   76.59 |    54.54 |     100 |   76.59 | ...60-476,533-537 
  ...tsDisplay.tsx |     100 |    96.96 |     100 |     100 | 234               
  ...fications.tsx |   18.18 |      100 |       0 |   18.18 | 15-58             
  ...onsDialog.tsx |    2.13 |      100 |       0 |    2.13 | 62-133,148-1004   
  ...ryDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...icePrompt.tsx |   88.14 |    83.87 |     100 |   88.14 | ...01-105,133-138 
  PrepareLabel.tsx |   91.66 |    76.19 |     100 |   91.66 | 73-75,77-79,110   
  ...geDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...ngDisplay.tsx |   21.42 |      100 |       0 |   21.42 | 13-39             
  ...hProgress.tsx |   85.25 |    88.46 |     100 |   85.25 | 121-147           
  ...dSelector.tsx |    4.45 |      100 |       0 |    4.45 | 28-92,100-328     
  ...ionPicker.tsx |   94.76 |    87.17 |     100 |   94.76 | 99,132,253-261    
  ...onPreview.tsx |   91.73 |    78.26 |     100 |   91.73 | ...,70-71,126-128 
  ...ryDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...putPrompt.tsx |   72.56 |       80 |      40 |   72.56 | ...06-109,114-117 
  ...ngsDialog.tsx |   66.88 |    73.52 |     100 |   66.88 | ...11-819,825-826 
  ...ionDialog.tsx |    87.8 |      100 |   33.33 |    87.8 | 36-39,44-51       
  ...putPrompt.tsx |    15.9 |      100 |       0 |    15.9 | 20-63             
  ...Indicator.tsx |   57.14 |      100 |       0 |   57.14 | 12-15             
  ...MoreLines.tsx |      28 |      100 |       0 |      28 | 18-40             
  ...ionPicker.tsx |   17.59 |      100 |       0 |   17.59 | 55-172            
  StatsDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...yTodoList.tsx |   94.17 |       80 |     100 |   94.17 | 56-57,131-134     
  ...nsDisplay.tsx |   84.09 |    57.14 |     100 |   84.09 | ...16-118,125-127 
  ThemeDialog.tsx  |   89.95 |    46.15 |      75 |   89.95 | ...71-173,243-245 
  Tips.tsx         |   93.75 |       75 |     100 |   93.75 | 39-40             
  TodoDisplay.tsx  |     100 |      100 |     100 |     100 |                   
  ...tsDisplay.tsx |     100 |     87.5 |     100 |     100 | 31-32             
  TrustDialog.tsx  |     100 |    81.81 |     100 |     100 | 71-86             
  ...ification.tsx |   36.36 |      100 |       0 |   36.36 | 15-22             
  ...ackDialog.tsx |    7.84 |      100 |       0 |    7.84 | 24-134            
 ...nts/agent-view |    25.2 |       90 |      10 |    25.2 |                   
  ...atContent.tsx |    8.79 |      100 |       0 |    8.79 | 53-265,271-273    
  ...tChatView.tsx |   21.05 |      100 |       0 |   21.05 | 21-39             
  ...tComposer.tsx |    9.95 |      100 |       0 |    9.95 | 57-308            
  AgentFooter.tsx  |   17.07 |      100 |       0 |   17.07 | 28-66             
  AgentHeader.tsx  |   15.38 |      100 |       0 |   15.38 | 27-64             
  AgentTabBar.tsx  |    8.13 |      100 |       0 |    8.13 | 39-59,64-187      
  ...oryAdapter.ts |     100 |    91.83 |     100 |     100 | 103,109-110,138   
  index.ts         |       0 |        0 |       0 |       0 | 1-12              
 ...mponents/arena |   45.72 |    70.53 |   60.86 |   45.72 |                   
  ArenaCards.tsx   |   73.06 |    71.79 |   85.71 |   73.06 | ...83-185,321-326 
  ...ectDialog.tsx |   83.48 |    69.86 |   88.88 |   83.48 | ...88-392,409-410 
  ...artDialog.tsx |   10.15 |      100 |       0 |   10.15 | 27-161            
  ...tusDialog.tsx |    5.63 |      100 |       0 |    5.63 | 33-75,80-288      
  ...topDialog.tsx |    6.17 |      100 |       0 |    6.17 | 33-213            
 ...ackground-view |    71.2 |    79.71 |   78.94 |    71.2 |                   
  ...sksDialog.tsx |   71.23 |    78.83 |   73.33 |   71.23 | ...1063,1137-1139 
  ...TasksPill.tsx |   70.83 |    86.95 |     100 |   70.83 | 44,77-89,97-105   
 ...nts/extensions |   45.28 |    33.33 |      60 |   45.28 |                   
  ...gerDialog.tsx |   44.31 |    34.14 |      75 |   44.31 | ...71-480,483-488 
  index.ts         |       0 |        0 |       0 |       0 | 1-9               
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...tensions/steps |   54.77 |    94.23 |   66.66 |   54.77 |                   
  ...ctionStep.tsx |   95.12 |    92.85 |   85.71 |   95.12 | 84-86,89          
  ...etailStep.tsx |    6.18 |      100 |       0 |    6.18 | 17-128            
  ...nListStep.tsx |   88.35 |    94.73 |      80 |   88.35 | 51-52,58-71,105   
  ...electStep.tsx |   13.46 |      100 |       0 |   13.46 | 20-70             
  ...nfirmStep.tsx |   19.56 |      100 |       0 |   19.56 | 23-65             
  index.ts         |     100 |      100 |     100 |     100 |                   
 ...mponents/hooks |   72.24 |    70.52 |      80 |   72.24 |                   
  ...etailStep.tsx |   96.52 |       75 |     100 |   96.52 | 33,37,50,59       
  ...etailStep.tsx |   93.27 |    73.68 |     100 |   93.27 | 41-42,99-104,110  
  ...abledStep.tsx |     100 |      100 |     100 |     100 |                   
  ...sListStep.tsx |     100 |      100 |     100 |     100 |                   
  ...entDialog.tsx |   36.09 |    47.05 |      50 |   36.09 | ...49,453-466,470 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-13              
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...components/mcp |    20.2 |    84.61 |   81.81 |    20.2 |                   
  ...ealthPill.tsx |   68.42 |    85.71 |     100 |   68.42 | 40-46             
  ...entDialog.tsx |    3.64 |      100 |       0 |    3.64 | 41-717            
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-30              
  types.ts         |     100 |      100 |     100 |     100 |                   
  utils.ts         |   96.42 |    87.09 |     100 |   96.42 | 21,96-97          
 ...ents/mcp/steps |    6.65 |      100 |       0 |    6.65 |                   
  ...icateStep.tsx |     5.1 |      100 |       0 |     5.1 | 34-95,98-334      
  ...electStep.tsx |   10.95 |      100 |       0 |   10.95 | 16-88             
  ...etailStep.tsx |    5.26 |      100 |       0 |    5.26 | 31-247            
  ...rListStep.tsx |    5.88 |      100 |       0 |    5.88 | 20-176            
  ...etailStep.tsx |   10.41 |      100 |       0 |   10.41 | ...1,67-79,82-139 
  ToolListStep.tsx |    7.14 |      100 |       0 |    7.14 | 16-146            
 ...nents/messages |   79.88 |    78.93 |   69.84 |   79.88 |                   
  ...ionDialog.tsx |   77.35 |    74.54 |    62.5 |   77.35 | ...90,508,526-528 
  BtwMessage.tsx   |     100 |      100 |     100 |     100 |                   
  ...upDisplay.tsx |   97.67 |    83.33 |     100 |   97.67 | 119,142,150       
  ...onMessage.tsx |   91.93 |    82.35 |     100 |   91.93 | 57-59,61,63       
  ...nMessages.tsx |   77.35 |      100 |      70 |   77.35 | ...31-244,248-260 
  DiffRenderer.tsx |   93.19 |    86.17 |     100 |   93.19 | ...09,237-238,304 
  ...ssMessage.tsx |    12.5 |      100 |       0 |    12.5 | 18-59             
  ...edMessage.tsx |   16.66 |      100 |       0 |   16.66 | 22-38             
  ...sMessages.tsx |   55.67 |       40 |   28.57 |   55.67 | ...20-125,133-145 
  ...ryMessage.tsx |   12.82 |      100 |       0 |   12.82 | 22-59             
  ...onMessage.tsx |   73.55 |    55.81 |   33.33 |   73.55 | ...41-443,450-452 
  ...upMessage.tsx |   76.95 |    82.08 |     100 |   76.95 | ...24-251,273-288 
  ToolMessage.tsx  |   90.68 |    81.35 |   91.66 |   90.68 | ...58-663,690-692 
 ...ponents/shared |    82.5 |    77.29 |   92.64 |    82.5 |                   
  ...ctionList.tsx |   99.03 |    95.65 |     100 |   99.03 | 85                
  ...tonSelect.tsx |     100 |      100 |     100 |     100 |                   
  EnumSelector.tsx |     100 |    96.42 |     100 |     100 | 58                
  MaxSizedBox.tsx  |   83.01 |    86.25 |   88.88 |   83.01 | ...12-513,618-619 
  MultiSelect.tsx  |    6.29 |      100 |       0 |    6.29 | 35-42,45-176      
  ...tonSelect.tsx |     100 |      100 |     100 |     100 |                   
  ...eSelector.tsx |     100 |       60 |     100 |     100 | 40-45             
  TextInput.tsx    |   74.84 |    57.14 |      75 |   74.84 | ...90-194,206-212 
  ...apsedTime.tsx |     100 |      100 |     100 |     100 |                   
  ...Indicator.tsx |     100 |      100 |     100 |     100 |                   
  text-buffer.ts   |   83.62 |    75.62 |   97.61 |   83.62 | ...2272,2300,2368 
  ...er-actions.ts |   86.71 |    67.79 |     100 |   86.71 | ...07-608,809-811 
 ...ents/subagents |   32.77 |    33.33 |    12.5 |   32.77 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  reducers.tsx     |    12.1 |      100 |       0 |    12.1 | 33-190            
  types.ts         |     100 |      100 |     100 |     100 |                   
  utils.ts         |   13.69 |    33.33 |   16.66 |   13.69 | ...1,56-57,60-102 
 ...bagents/create |    9.13 |      100 |       0 |    9.13 |                   
  ...ionWizard.tsx |    7.28 |      100 |       0 |    7.28 | 34-299            
  ...rSelector.tsx |   14.75 |      100 |       0 |   14.75 | 26-85             
  ...onSummary.tsx |    4.26 |      100 |       0 |    4.26 | 27-331            
  ...tionInput.tsx |    8.63 |      100 |       0 |    8.63 | 23-177            
  ...dSelector.tsx |   33.33 |      100 |       0 |   33.33 | 20-21,26-27,36-63 
  ...nSelector.tsx |    37.5 |      100 |       0 |    37.5 | 20-21,26-27,36-58 
  ...EntryStep.tsx |   12.76 |      100 |       0 |   12.76 | 34-78             
  ToolSelector.tsx |    4.16 |      100 |       0 |    4.16 | 31-253            
 ...bagents/manage |    8.39 |      100 |       0 |    8.39 |                   
  ...ctionStep.tsx |   10.25 |      100 |       0 |   10.25 | 21-103            
  ...eleteStep.tsx |   20.93 |      100 |       0 |   20.93 | 23-62             
  ...tEditStep.tsx |   25.53 |      100 |       0 |   25.53 | ...2,37-38,51-124 
  ...ctionStep.tsx |    2.29 |      100 |       0 |    2.29 | 28-449            
  ...iewerStep.tsx |   13.72 |      100 |       0 |   13.72 | 18-73             
  ...gerDialog.tsx |    6.74 |      100 |       0 |    6.74 | 35-341            
 ...agents/runtime |   81.76 |    58.24 |   92.85 |   81.76 |                   
  ...onDisplay.tsx |   81.76 |    58.24 |   92.85 |   81.76 | ...14-716,719-722 
 ...mponents/views |   42.16 |    69.23 |   21.42 |   42.16 |                   
  ContextUsage.tsx |     4.7 |      100 |       0 |     4.7 | ...52-167,170-456 
  DoctorReport.tsx |     9.8 |      100 |       0 |     9.8 | 25-54,57-131      
  ...sionsList.tsx |   87.69 |    73.68 |     100 |   87.69 | 65-72             
  McpStatus.tsx    |   89.53 |    60.52 |     100 |   89.53 | ...72,175-177,262 
  SkillsList.tsx   |   27.27 |      100 |       0 |   27.27 | 18-35             
  ToolsList.tsx    |     100 |      100 |     100 |     100 |                   
 src/ui/contexts   |   76.84 |    78.03 |   84.31 |   76.84 |                   
  ...ewContext.tsx |   65.77 |      100 |      75 |   65.77 | ...22-225,231-241 
  AppContext.tsx   |      80 |       50 |     100 |      80 | 19-20             
  ...ewContext.tsx |   93.37 |    68.57 |      50 |   93.37 | ...94-195,222-226 
  ...deContext.tsx |     100 |      100 |     100 |     100 |                   
  ...igContext.tsx |   81.81 |       50 |     100 |   81.81 | 15-16             
  ...ssContext.tsx |   81.88 |    82.26 |     100 |   81.88 | ...1153,1159-1161 
  ...owContext.tsx |   89.28 |       80 |   66.66 |   89.28 | 34,47-48,60-62    
  ...onContext.tsx |   43.28 |     62.5 |    62.5 |   43.28 | ...56-259,263-266 
  ...gsContext.tsx |   83.33 |       50 |     100 |   83.33 | 17-18             
  ...usContext.tsx |     100 |      100 |     100 |     100 |                   
  ...ngContext.tsx |   71.42 |       50 |     100 |   71.42 | 17-20             
  ...nsContext.tsx |   88.88 |       50 |     100 |   88.88 | 145-146           
  ...teContext.tsx |   85.71 |       50 |     100 |   85.71 | 175-176           
  ...deContext.tsx |   76.08 |    72.72 |     100 |   76.08 | 47-48,52-59,77-78 
 src/ui/editors    |   93.33 |    85.71 |   66.66 |   93.33 |                   
  ...ngsManager.ts |   93.33 |    85.71 |   66.66 |   93.33 | 49,63-64          
 src/ui/hooks      |    80.8 |    81.38 |   85.56 |    80.8 |                   
  ...dProcessor.ts |   83.12 |    82.56 |     100 |   83.12 | ...88-389,408-435 
  keyToAnsi.ts     |    3.92 |      100 |       0 |    3.92 | 19-77             
  ...dProcessor.ts |    94.8 |    70.58 |     100 |    94.8 | ...76-277,282-283 
  ...dProcessor.ts |   72.85 |    57.85 |   61.53 |   72.85 | ...84,808,827-831 
  ...amingState.ts |   12.22 |      100 |       0 |   12.22 | 54-158            
  ...agerDialog.ts |   88.23 |      100 |     100 |   88.23 | 20,24             
  ...ationFrame.ts |      32 |       60 |     100 |      32 | 42-44,51-90       
  ...odeCommand.ts |   58.82 |      100 |     100 |   58.82 | 28,33-48          
  ...enaCommand.ts |      85 |      100 |     100 |      85 | 23-24,29          
  ...aInProcess.ts |   19.81 |    66.66 |      25 |   19.81 | 57-175            
  ...Completion.ts |   92.77 |    89.09 |     100 |   92.77 | ...86-187,220-223 
  ...ifications.ts |   92.07 |    96.29 |     100 |   92.07 | 116-124           
  ...tIndicator.ts |     100 |    93.75 |     100 |     100 | 63                
  ...waySummary.ts |   96.22 |    69.69 |     100 |   96.22 | 125-127,169       
  ...ndTaskView.ts |   94.11 |    76.92 |     100 |   94.11 | 119-123,216,222   
  ...ketedPaste.ts |    23.8 |      100 |       0 |    23.8 | 19-37             
  ...lanUpdates.ts |     100 |       92 |     100 |     100 | 59,158            
  ...ompletion.tsx |   91.28 |    79.59 |     100 |   91.28 | ...20-221,259-269 
  ...dMigration.ts |   90.62 |       75 |     100 |   90.62 | 38-40             
  useCompletion.ts |    92.4 |     87.5 |     100 |    92.4 | 68-69,93-94,98-99 
  ...nitMessage.ts |     100 |      100 |     100 |     100 |                   
  ...extualTips.ts |   76.92 |       50 |     100 |   76.92 | 55,68,71-75,88-96 
  ...eteCommand.ts |   33.33 |       50 |     100 |   33.33 | 30,34,41-90       
  ...ialogClose.ts |   18.18 |      100 |     100 |   18.18 | 75-130            
  ...oublePress.ts |   53.12 |       75 |     100 |   53.12 | 33-35,41-54       
  ...orSettings.ts |     100 |      100 |     100 |     100 |                   
  ...Completion.ts |   99.12 |     97.7 |     100 |   99.12 | 182-183           
  ...ionUpdates.ts |   93.45 |     92.3 |     100 |   93.45 | ...83-287,300-306 
  ...agerDialog.ts |   88.88 |      100 |     100 |   88.88 | 21,25             
  ...backDialog.ts |   54.88 |       50 |   33.33 |   54.88 | ...71-173,195-196 
  useFocus.ts      |     100 |      100 |     100 |     100 |                   
  ...olderTrust.ts |     100 |      100 |     100 |     100 |                   
  ...ggestions.tsx |   67.46 |       90 |      50 |   67.46 | ...09-130,149-150 
  ...miniStream.ts |   75.84 |    72.89 |   91.66 |   75.84 | ...2293,2306-2314 
  ...BranchName.ts |    90.9 |     92.3 |     100 |    90.9 | 19-20,55-58       
  ...oryManager.ts |   93.15 |    93.75 |     100 |   93.15 | 44,107-110        
  ...ooksDialog.ts |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...stListener.ts |     100 |      100 |     100 |     100 |                   
  ...nAuthError.ts |   76.19 |       50 |     100 |   76.19 | 39-40,43-45       
  ...putHistory.ts |   92.59 |    85.71 |     100 |   92.59 | 63-64,72,94-96    
  ...storyStore.ts |     100 |    94.11 |     100 |     100 | 69                
  useKeypress.ts   |     100 |      100 |     100 |     100 |                   
  ...rdProtocol.ts |   36.36 |      100 |       0 |   36.36 | 24-31             
  ...unchEditor.ts |    9.67 |      100 |       0 |    9.67 | 11-32,39-90       
  ...gIndicator.ts |     100 |      100 |     100 |     100 |                   
  useLogger.ts     |   21.05 |      100 |       0 |   21.05 | 15-37             
  useMCPHealth.ts  |   70.58 |       75 |      50 |   70.58 | 42-47,59-62       
  ...elsCommand.ts |     100 |      100 |     100 |     100 |                   
  useMcpDialog.ts  |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...moryDialog.ts |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...oryMonitor.ts |     100 |      100 |     100 |     100 |                   
  ...ssageQueue.ts |     100 |      100 |     100 |     100 |                   
  ...delCommand.ts |     100 |       75 |     100 |     100 | 22                
  ...raseCycler.ts |   84.74 |    76.47 |     100 |   84.74 | ...49,52-53,69-71 
  useQwenAuth.ts   |     100 |      100 |     100 |     100 |                   
  ...lScheduler.ts |   84.52 |    93.33 |     100 |   84.52 | ...27-232,328-338 
  ...oryCommand.ts |       0 |        0 |       0 |       0 | 1-7               
  ...umeCommand.ts |   97.24 |    76.92 |     100 |   97.24 | 104-105,145       
  ...ompletion.tsx |   90.59 |    83.33 |     100 |   90.59 | ...01,104,137-140 
  ...ectionList.ts |   96.96 |    95.69 |     100 |   96.96 | ...82-183,237-240 
  ...sionPicker.ts |   90.23 |    71.69 |     100 |   90.23 | ...78-279,283-284 
  ...ngsCommand.ts |   18.75 |      100 |       0 |   18.75 | 10-25             
  ...ellHistory.ts |   91.74 |    79.41 |     100 |   91.74 | ...74,122-123,133 
  ...oryCommand.ts |       0 |        0 |       0 |       0 | 1-73              
  ...Completion.ts |   78.99 |    81.48 |   94.11 |   78.99 | ...77-579,587-624 
  ...tateAndRef.ts |     100 |      100 |     100 |     100 |                   
  useStatusLine.ts |     100 |    98.79 |     100 |     100 | 257               
  ...eateDialog.ts |   88.23 |      100 |     100 |   88.23 | 14,18             
  ...tification.ts |     100 |    85.71 |     100 |     100 | 47                
  ...alProgress.ts |   53.06 |       50 |   66.66 |   53.06 | ...53,61-68,79-85 
  ...rminalSize.ts |   76.19 |      100 |      50 |   76.19 | 21-25             
  ...emeCommand.ts |   67.01 |    29.41 |     100 |   67.01 | ...10-111,115-116 
  useTimer.ts      |   88.09 |    85.71 |     100 |   88.09 | 44-45,51-53       
  ...lMigration.ts |       0 |        0 |       0 |       0 |                   
  ...rustModify.ts |     100 |      100 |     100 |     100 |                   
  ...elcomeBack.ts |   87.36 |     90.9 |     100 |   87.36 | ...,94-96,114-115 
  vim.ts           |   83.77 |    80.31 |     100 |   83.77 | ...55,759-767,776 
 src/ui/layouts    |   89.51 |    86.95 |     100 |   89.51 |                   
  ...AppLayout.tsx |   89.53 |    86.66 |     100 |   89.53 | 50-52,92-97       
  ...AppLayout.tsx |   89.47 |     87.5 |     100 |   89.47 | 58-63             
 ...i/manageModels |   93.61 |       48 |     100 |   93.61 |                   
  manageModels.ts  |   93.61 |       48 |     100 |   93.61 | ...63-166,179,209 
 src/ui/models     |   80.24 |    79.16 |   71.42 |   80.24 |                   
  ...ableModels.ts |   80.24 |    79.16 |   71.42 |   80.24 | ...,61-71,123-125 
 ...noninteractive |     100 |      100 |    7.14 |     100 |                   
  ...eractiveUi.ts |     100 |      100 |    7.14 |     100 |                   
 src/ui/state      |   94.91 |    81.81 |     100 |   94.91 |                   
  extensions.ts    |   94.91 |    81.81 |     100 |   94.91 | 68-69,88          
 src/ui/themes     |   98.53 |    70.31 |     100 |   98.53 |                   
  ansi-light.ts    |     100 |      100 |     100 |     100 |                   
  ansi.ts          |     100 |      100 |     100 |     100 |                   
  atom-one-dark.ts |     100 |      100 |     100 |     100 |                   
  ayu-light.ts     |     100 |      100 |     100 |     100 |                   
  ayu.ts           |     100 |      100 |     100 |     100 |                   
  color-utils.ts   |     100 |      100 |     100 |     100 |                   
  default-light.ts |     100 |      100 |     100 |     100 |                   
  default.ts       |     100 |      100 |     100 |     100 |                   
  ...inal-theme.ts |   88.59 |    85.45 |     100 |   88.59 | ...57-261,266-270 
  dracula.ts       |     100 |      100 |     100 |     100 |                   
  github-dark.ts   |     100 |      100 |     100 |     100 |                   
  github-light.ts  |     100 |      100 |     100 |     100 |                   
  googlecode.ts    |     100 |      100 |     100 |     100 |                   
  no-color.ts      |     100 |      100 |     100 |     100 |                   
  qwen-dark.ts     |     100 |      100 |     100 |     100 |                   
  qwen-light.ts    |     100 |      100 |     100 |     100 |                   
  ...tic-tokens.ts |     100 |      100 |     100 |     100 |                   
  ...-of-purple.ts |     100 |      100 |     100 |     100 |                   
  theme-manager.ts |   87.98 |    82.89 |     100 |   87.98 | ...48-357,362-363 
  theme.ts         |     100 |    38.02 |     100 |     100 | ...34-449,457-461 
  xcode.ts         |     100 |      100 |     100 |     100 |                   
 src/ui/utils      |   78.17 |    86.91 |   86.87 |   78.17 |                   
  ...Colorizer.tsx |   82.78 |    88.23 |     100 |   82.78 | ...10-111,197-223 
  ...nRenderer.tsx |   52.41 |    36.36 |      50 |   52.41 | ...49-151,171-180 
  ...wnDisplay.tsx |   86.79 |    88.88 |     100 |   86.79 | ...06-315,348-373 
  ...eRenderer.tsx |   94.45 |    81.25 |   94.11 |   94.45 | ...65,477,480-483 
  ...dWorkUtils.ts |     100 |      100 |     100 |     100 |                   
  ...boardUtils.ts |   59.61 |    58.82 |     100 |   59.61 | ...,86-88,107-149 
  commandUtils.ts  |    84.7 |    88.13 |      90 |    84.7 | ...63-164,260-279 
  computeStats.ts  |     100 |      100 |     100 |     100 |                   
  customBanner.ts  |   90.68 |    91.22 |     100 |   90.68 | ...13,324-327,334 
  displayUtils.ts  |   88.37 |    72.22 |     100 |   88.37 | 23,25,29,31,33    
  formatters.ts    |   95.23 |    98.27 |     100 |   95.23 | 117-120           
  gradientUtils.ts |     100 |      100 |     100 |     100 |                   
  highlight.ts     |   98.63 |       95 |     100 |   98.63 | 93                
  ...oryMapping.ts |     100 |    94.28 |     100 |     100 | 34,56             
  isNarrowWidth.ts |     100 |      100 |     100 |     100 |                   
  ...olDetector.ts |    8.23 |      100 |       0 |    8.23 | ...31-132,135-136 
  layoutUtils.ts   |     100 |      100 |     100 |     100 |                   
  ...nUtilities.ts |   69.84 |    85.71 |     100 |   69.84 | 75-91,100-101     
  ...ToolGroups.ts |    98.3 |    95.65 |     100 |    98.3 | 48-49             
  ...lsBySource.ts |     100 |    95.23 |     100 |     100 | 84                
  ...mConstants.ts |     100 |      100 |     100 |     100 |                   
  ...storyUtils.ts |   57.81 |    67.14 |      90 |   57.81 | ...64,412,417-439 
  ...ickerUtils.ts |     100 |      100 |     100 |     100 |                   
  ...izedOutput.ts |   94.94 |      100 |   88.88 |   94.94 | 112-117           
  ...wOptimizer.ts |     100 |    96.77 |     100 |     100 | 69                
  terminalSetup.ts |    4.37 |      100 |       0 |    4.37 | 44-393            
  textUtils.ts     |   97.35 |    94.38 |   91.66 |   97.35 | ...50-251,386-387 
  todoSnapshot.ts  |   89.11 |    93.18 |     100 |   89.11 | ...,66-78,180-181 
  updateCheck.ts   |     100 |    80.95 |     100 |     100 | 30-42             
 ...i/utils/export |   56.77 |     40.8 |   79.41 |   56.77 |                   
  collect.ts       |   55.92 |    50.58 |   86.36 |   55.92 | ...25-640,642-647 
  index.ts         |     100 |      100 |     100 |     100 |                   
  normalize.ts     |   57.47 |    20.51 |      80 |   57.47 | ...09-310,324-359 
  types.ts         |       0 |        0 |       0 |       0 | 1                 
  utils.ts         |      40 |      100 |       0 |      40 | 11-13             
 ...ort/formatters |    3.38 |      100 |       0 |    3.38 |                   
  html.ts          |    9.61 |      100 |       0 |    9.61 | ...28,34-76,82-84 
  json.ts          |      50 |      100 |       0 |      50 | 14-15             
  jsonl.ts         |     3.5 |      100 |       0 |     3.5 | 14-76             
  markdown.ts      |    0.94 |      100 |       0 |    0.94 | 13-295            
 src/utils         |   73.76 |    89.53 |   94.55 |   73.76 |                   
  acpModelUtils.ts |     100 |      100 |     100 |     100 |                   
  apiPreconnect.ts |   96.52 |    97.05 |     100 |   96.52 | 166-169           
  checks.ts        |   33.33 |      100 |       0 |   33.33 | 23-28             
  cleanup.ts       |   84.12 |    93.33 |      80 |   84.12 | 75,106-115        
  commands.ts      |     100 |      100 |     100 |     100 |                   
  commentJson.ts   |   85.29 |    89.47 |     100 |   85.29 | 48-57             
  ...Calculator.ts |     100 |      100 |     100 |     100 |                   
  deepMerge.ts     |     100 |       90 |     100 |     100 | 41-43,49          
  ...ScopeUtils.ts |   97.56 |    88.88 |     100 |   97.56 | 67                
  doctorChecks.ts  |   68.59 |    64.28 |     100 |   68.59 | ...63-269,293-309 
  ...putCapture.ts |   90.65 |    86.17 |     100 |   90.65 | ...72,370,372-373 
  ...arResolver.ts |   94.28 |    88.46 |     100 |   94.28 | 28-29,125-126     
  errors.ts        |   98.63 |    96.15 |     100 |   98.63 | 67-68             
  events.ts        |     100 |      100 |     100 |     100 |                   
  gitUtils.ts      |   91.91 |    84.61 |     100 |   91.91 | 78-81,124-127     
  ...AutoUpdate.ts |   90.76 |    93.33 |   88.88 |   90.76 | 103-114           
  ...lationInfo.ts |     100 |      100 |     100 |     100 |                   
  languageUtils.ts |   97.89 |    96.42 |     100 |   97.89 | 132-133           
  math.ts          |       0 |        0 |       0 |       0 | 1-15              
  ...onfigUtils.ts |     100 |      100 |     100 |     100 |                   
  ...iveHelpers.ts |   96.79 |    93.28 |     100 |   96.79 | ...76-477,575,588 
  osc.ts           |    97.5 |      100 |   88.88 |    97.5 | 195-196           
  package.ts       |   88.88 |       80 |     100 |   88.88 | 33-34             
  processUtils.ts  |     100 |      100 |     100 |     100 |                   
  readStdin.ts     |   79.62 |       90 |      80 |   79.62 | 33-40,52-54       
  relaunch.ts      |   98.07 |    76.92 |     100 |   98.07 | 70                
  resolvePath.ts   |   66.66 |       25 |     100 |   66.66 | 12-13,16,18-19    
  sandbox.ts       |       0 |        0 |       0 |       0 | 1-980             
  settingsUtils.ts |   86.32 |    90.59 |   94.44 |   86.32 | ...38,569,632-644 
  spawnWrapper.ts  |     100 |      100 |     100 |     100 |                   
  ...upProfiler.ts |     100 |    95.83 |     100 |     100 | 110               
  ...upWarnings.ts |     100 |      100 |     100 |     100 |                   
  stdioHelpers.ts  |     100 |       60 |     100 |     100 | 23,32             
  systemInfo.ts    |   92.52 |     90.9 |   83.33 |   92.52 | 63-69,184         
  ...InfoFields.ts |   86.91 |    65.78 |     100 |   86.91 | ...16-117,138-139 
  ...iffPreview.ts |   94.11 |    83.33 |     100 |   94.11 | 13                
  ...entEmitter.ts |     100 |      100 |     100 |     100 |                   
  ...upWarnings.ts |   91.17 |    82.35 |     100 |   91.17 | 67-68,73-74,77-78 
  version.ts       |     100 |       50 |     100 |     100 | 11                
  windowTitle.ts   |     100 |      100 |     100 |     100 |                   
  ...WithBackup.ts |    62.1 |    77.77 |     100 |    62.1 | 93,107,118-157    
-------------------|---------|----------|---------|---------|-------------------
Core Package - Full Text Report
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   76.57 |    82.14 |   79.14 |   76.57 |                   
 src               |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/__mocks__/fs  |       0 |        0 |       0 |       0 |                   
  promises.ts      |       0 |        0 |       0 |       0 | 1-48              
 src/agents        |   84.07 |    76.02 |   90.66 |   84.07 |                   
  ...transcript.ts |   88.76 |    75.43 |     100 |   88.76 | ...82,306-307,434 
  ...ent-resume.ts |   78.67 |    69.51 |   76.66 |   78.67 | ...88-992,995-997 
  ...ound-tasks.ts |   94.19 |    86.17 |     100 |   94.19 | ...15-616,633-634 
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/agents/arena  |    76.9 |    66.66 |   78.94 |    76.9 |                   
  ...gentClient.ts |   79.47 |    88.88 |   81.81 |   79.47 | ...68-183,189-204 
  ArenaManager.ts  |   75.84 |     62.9 |   78.57 |   75.84 | ...1889,1895-1896 
  arena-events.ts  |   64.44 |      100 |      50 |   64.44 | ...71-175,178-183 
  diff-summary.ts  |    87.5 |    73.46 |     100 |    87.5 | ...32-133,137-138 
  index.ts         |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...gents/backends |    76.3 |    86.23 |   72.41 |    76.3 |                   
  ITermBackend.ts  |   97.97 |    93.93 |     100 |   97.97 | ...78-180,255,307 
  ...essBackend.ts |   91.06 |     90.9 |   82.35 |   91.06 | ...51-271,330,430 
  TmuxBackend.ts   |    90.7 |    76.55 |   97.36 |    90.7 | ...87,697,743-747 
  detect.ts        |   31.25 |      100 |       0 |   31.25 | 34-88             
  index.ts         |     100 |      100 |     100 |     100 |                   
  iterm-it2.ts     |     100 |     92.1 |     100 |     100 | 37-38,106         
  tmux-commands.ts |    6.64 |      100 |    3.03 |    6.64 | ...93-363,386-503 
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...agents/runtime |   79.81 |    75.21 |      66 |   79.81 |                   
  agent-core.ts    |   73.97 |    69.03 |   48.48 |   73.97 | ...1299,1326-1372 
  agent-events.ts  |     100 |      100 |     100 |     100 |                   
  ...t-headless.ts |   79.09 |    69.76 |   52.38 |   79.09 | ...78-379,382-383 
  ...nteractive.ts |   79.71 |    79.62 |      75 |   79.71 | ...54,456,458,461 
  ...statistics.ts |   98.19 |    82.35 |     100 |   98.19 | 127,151,192,225   
  agent-types.ts   |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/config        |    74.7 |    76.92 |    61.2 |    74.7 |                   
  config.ts        |   72.35 |     74.3 |   55.61 |   72.35 | ...2888,2892-2904 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  models.ts        |     100 |      100 |     100 |     100 |                   
  storage.ts       |   95.72 |     93.1 |   91.66 |   95.72 | ...06-207,241-242 
 ...nfirmation-bus |   98.29 |    97.14 |     100 |   98.29 |                   
  message-bus.ts   |   98.14 |    97.05 |     100 |   98.14 | 42-43             
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/constants     |    4.95 |      100 |       0 |    4.95 |                   
  codingPlan.ts    |    4.95 |      100 |       0 |    4.95 | ...79-291,299-309 
 src/core          |   82.72 |    82.31 |   87.91 |   82.72 |                   
  baseLlmClient.ts |   96.77 |    96.42 |      80 |   96.77 | 123-126           
  client.ts        |   77.17 |    77.88 |    86.2 |   77.17 | ...1264,1268-1284 
  ...tGenerator.ts |    72.1 |    61.11 |     100 |    72.1 | ...63,365,372-375 
  ...lScheduler.ts |   77.12 |    81.01 |   92.68 |   77.12 | ...2200,2252-2256 
  geminiChat.ts    |   88.73 |    84.23 |   83.33 |   88.73 | ...1113,1180-1181 
  geminiRequest.ts |     100 |      100 |     100 |     100 |                   
  ...htProtocol.ts |    9.09 |      100 |       0 |    9.09 | 34-42,45-49,52-87 
  logger.ts        |   82.25 |    81.81 |     100 |   82.25 | ...57-361,407-421 
  ...tyDefaults.ts |     100 |      100 |     100 |     100 |                   
  ...olExecutor.ts |   92.59 |       75 |      50 |   92.59 | 41-42             
  ...on-helpers.ts |   85.71 |    70.58 |     100 |   85.71 | ...90-191,205-214 
  ...issionFlow.ts |   98.59 |    94.73 |     100 |   98.59 | 93                
  prompts.ts       |    88.8 |    88.05 |      75 |    88.8 | ...-898,1101-1102 
  tokenLimits.ts   |     100 |    89.47 |     100 |     100 | 51-52             
  ...okTriggers.ts |   99.31 |    91.02 |     100 |   99.31 | 124,135           
  turn.ts          |   96.42 |    88.88 |     100 |   96.42 | ...00,413-414,462 
 ...ntentGenerator |    94.6 |    79.35 |   92.68 |    94.6 |                   
  ...tGenerator.ts |   96.49 |    79.35 |      90 |   96.49 | ...24,481,637,693 
  converter.ts     |   94.38 |    79.78 |     100 |   94.38 | ...40-541,551,734 
  index.ts         |       0 |        0 |       0 |       0 | 1-21              
 ...ntentGenerator |   91.53 |    71.64 |   93.33 |   91.53 |                   
  ...tGenerator.ts |      90 |    70.96 |   92.85 |      90 | ...80-286,304-305 
  index.ts         |     100 |       80 |     100 |     100 | 50                
 ...ntentGenerator |   91.08 |    76.14 |   85.71 |   91.08 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...tGenerator.ts |   91.04 |    76.14 |   85.71 |   91.04 | ...23,533-534,562 
 ...ntentGenerator |   79.48 |    83.73 |   89.47 |   79.48 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  converter.ts     |   74.92 |    80.85 |   86.95 |   74.92 | ...1410,1431-1437 
  errorHandler.ts  |     100 |      100 |     100 |     100 |                   
  index.ts         |   43.85 |    14.28 |      50 |   43.85 | ...,87-91,102-103 
  ...tGenerator.ts |   48.78 |    91.66 |   77.77 |   48.78 | ...10-163,166-167 
  pipeline.ts      |   93.62 |    84.76 |     100 |   93.62 | ...78-479,487,547 
  ...ingOptions.ts |       0 |        0 |       0 |       0 | 1                 
  ...CallParser.ts |   90.66 |     88.4 |     100 |   90.66 | ...15-319,349-350 
  ...kingParser.ts |     100 |    96.87 |     100 |     100 | 42                
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...rator/provider |   96.67 |    89.74 |   94.87 |   96.67 |                   
  dashscope.ts     |   97.22 |    87.69 |   93.33 |   97.22 | ...10-211,287-288 
  deepseek.ts      |   95.55 |    90.56 |     100 |   95.55 | ...31-132,145-146 
  default.ts       |   94.62 |    86.36 |   85.71 |   94.62 | 85-86,156-158     
  index.ts         |     100 |      100 |     100 |     100 |                   
  minimax.ts       |     100 |      100 |     100 |     100 |                   
  modelscope.ts    |     100 |      100 |     100 |     100 |                   
  openrouter.ts    |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 |                   
 src/extension     |   60.62 |    79.43 |   79.03 |   60.62 |                   
  ...-converter.ts |   62.35 |    47.82 |      90 |   62.35 | ...90-791,800-832 
  ...ionManager.ts |   46.92 |    82.19 |   67.44 |   46.92 | ...1386,1396-1415 
  ...onSettings.ts |   93.46 |    93.05 |     100 |   93.46 | ...17-221,228-232 
  ...-converter.ts |   54.88 |    94.44 |      60 |   54.88 | ...35-146,158-192 
  github.ts        |   44.94 |    88.52 |      60 |   44.94 | ...53-359,398-451 
  index.ts         |     100 |      100 |     100 |     100 |                   
  marketplace.ts   |   97.29 |    93.75 |     100 |   97.29 | ...64,184-185,274 
  npm.ts           |   48.66 |    76.08 |      75 |   48.66 | ...18-420,427-431 
  override.ts      |   94.11 |    88.88 |     100 |   94.11 | 63-64,81-82       
  settings.ts      |   66.26 |      100 |      50 |   66.26 | 81-108,143-149    
  storage.ts       |   94.73 |       90 |     100 |   94.73 | 41-42             
  ...ableSchema.ts |     100 |      100 |     100 |     100 |                   
  variables.ts     |   88.75 |    83.33 |     100 |   88.75 | ...28-231,234-237 
 src/followup      |   46.35 |     92.3 |   71.87 |   46.35 |                   
  followupState.ts |      96 |    89.74 |     100 |      96 | 159-161,218-219   
  index.ts         |     100 |      100 |     100 |     100 |                   
  overlayFs.ts     |   95.06 |       84 |     100 |   95.06 | 78,108,122,133    
  speculation.ts   |   13.22 |      100 |   16.66 |   13.22 | 88-458,518-568    
  ...onToolGate.ts |     100 |    96.29 |     100 |     100 | 93                
  ...nGenerator.ts |   36.67 |    95.12 |   33.33 |   36.67 | ...24-326,361-391 
 src/generated     |       0 |        0 |       0 |       0 |                   
  git-commit.ts    |       0 |        0 |       0 |       0 | 1-10              
 src/hooks         |    80.6 |    84.37 |   84.16 |    80.6 |                   
  ...okRegistry.ts |   86.48 |    77.08 |     100 |   86.48 | ...41-344,362-369 
  ...bortSignal.ts |     100 |      100 |     100 |     100 |                   
  ...terpolator.ts |   96.66 |    93.33 |     100 |   96.66 | 66-67             
  ...HookRunner.ts |   96.68 |    87.23 |     100 |   96.68 | 110-112,231-233   
  ...Aggregator.ts |   96.37 |    90.54 |     100 |   96.37 | ...89,291-292,365 
  ...entHandler.ts |   95.58 |    84.37 |   92.59 |   95.58 | ...29,682-683,693 
  hookPlanner.ts   |   84.13 |    76.59 |      90 |   84.13 | ...38,144,162-173 
  hookRegistry.ts  |   88.83 |    86.36 |     100 |   88.83 | ...21,326,330,334 
  hookRunner.ts    |   53.63 |    72.22 |   61.11 |   53.63 | ...23-724,733-734 
  hookSystem.ts    |   75.47 |      100 |   56.41 |   75.47 | ...75-576,582-583 
  ...HookRunner.ts |   75.51 |     61.9 |      80 |   75.51 | ...05-406,424-425 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...SkillHooks.ts |   78.75 |       75 |   66.66 |   78.75 | 62-66,137-152     
  ...oksManager.ts |    96.5 |     91.8 |     100 |    96.5 | ...90,209-210,223 
  ssrfGuard.ts     |   77.22 |    85.36 |     100 |   77.22 | ...57,261-267,273 
  trustedHooks.ts  |       0 |        0 |       0 |       0 | 1-124             
  types.ts         |   90.15 |    91.02 |   85.18 |   90.15 | ...91-392,452-456 
  urlValidator.ts  |     100 |      100 |     100 |     100 |                   
 src/ide           |   74.28 |    83.39 |   78.33 |   74.28 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  detect-ide.ts    |     100 |      100 |     100 |     100 |                   
  ide-client.ts    |    64.2 |    81.48 |   66.66 |    64.2 | ...9-970,999-1007 
  ide-installer.ts |   89.06 |    79.31 |     100 |   89.06 | ...36,143-147,160 
  ideContext.ts    |     100 |      100 |     100 |     100 |                   
  process-utils.ts |   84.84 |    71.79 |     100 |   84.84 | ...37,151,193-194 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/lsp           |   33.92 |    45.16 |   45.76 |   33.92 |                   
  ...nfigLoader.ts |   70.27 |    35.89 |   94.73 |   70.27 | ...20-422,426-432 
  ...ionFactory.ts |    4.29 |      100 |       0 |    4.29 | ...20-371,377-394 
  ...Normalizer.ts |   23.09 |    13.72 |   30.43 |   23.09 | ...04-905,909-924 
  ...verManager.ts |   13.52 |    81.25 |   29.16 |   13.52 | ...75-694,700-730 
  ...eLspClient.ts |   17.89 |      100 |       0 |   17.89 | ...37-244,254-258 
  ...LspService.ts |   45.87 |    62.13 |   66.66 |   45.87 | ...1282,1299-1309 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/mcp           |   78.69 |    75.34 |   75.92 |   78.69 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...h-provider.ts |   86.95 |      100 |   33.33 |   86.95 | ...,93,97,101-102 
  ...h-provider.ts |   73.82 |    53.92 |     100 |   73.82 | ...88-895,902-904 
  ...en-storage.ts |   98.62 |    97.72 |     100 |   98.62 | 87-88             
  oauth-utils.ts   |   70.58 |    85.29 |    90.9 |   70.58 | ...70-290,315-344 
  ...n-provider.ts |   89.83 |    95.83 |   45.45 |   89.83 | ...43,147,151-152 
 .../token-storage |   79.48 |    86.66 |   86.36 |   79.48 |                   
  ...en-storage.ts |     100 |      100 |     100 |     100 |                   
  ...en-storage.ts |   82.75 |    82.35 |   92.85 |   82.75 | ...62-172,180-181 
  ...en-storage.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...en-storage.ts |   68.14 |    82.35 |   64.28 |   68.14 | ...81-295,298-314 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/memory        |   61.59 |    74.87 |   66.44 |   61.59 |                   
  const.ts         |     100 |      100 |     100 |     100 |                   
  dream.ts         |   65.65 |    73.33 |      50 |   65.65 | 50,107-148        
  ...entPlanner.ts |   57.84 |    72.72 |   33.33 |   57.84 | ...35,140-147,152 
  entries.ts       |   59.84 |       70 |      50 |   59.84 | ...72-180,183-189 
  extract.ts       |    95.2 |    79.16 |     100 |    95.2 | 81-86,125         
  ...entPlanner.ts |   63.08 |    65.71 |   41.17 |   63.08 | ...17,222-223,332 
  ...ionPlanner.ts |       0 |        0 |       0 |       0 | 1                 
  forget.ts        |    8.04 |      100 |       0 |    8.04 | 67-342            
  governance.ts    |       0 |        0 |       0 |       0 | 1-352             
  indexer.ts       |   83.87 |    45.45 |     100 |   83.87 | ...50,56-57,69-70 
  manager.ts       |   73.32 |    78.94 |   74.35 |   73.32 | ...1163,1176-1178 
  memoryAge.ts     |   90.47 |    77.77 |     100 |   90.47 | 50-51             
  paths.ts         |   55.47 |    89.47 |   85.71 |   55.47 | ...,88-89,105-113 
  prompt.ts        |   93.36 |    71.42 |     100 |   93.36 | ...58,161,228-229 
  recall.ts        |   79.56 |    69.38 |   88.88 |   79.56 | ...40-245,269-280 
  ...ceSelector.ts |   91.86 |    77.27 |     100 |   91.86 | ...04,106-107,115 
  scan.ts          |   87.91 |    68.42 |     100 |   87.91 | ...47-48,58,82-87 
  status.ts        |   10.52 |      100 |       0 |   10.52 | 41-98             
  store.ts         |   94.44 |    83.33 |     100 |   94.44 | 56-57,92-93       
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/mocks         |       0 |        0 |       0 |       0 |                   
  msw.ts           |       0 |        0 |       0 |       0 | 1-9               
 src/models        |   89.49 |    85.58 |   87.14 |   89.49 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...tor-config.ts |   88.67 |     90.9 |     100 |   88.67 | 112,118,121-130   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...nfigErrors.ts |   74.22 |       44 |   84.61 |   74.22 | ...,67-74,106-117 
  ...igResolver.ts |   98.63 |    92.53 |     100 |   98.63 | 161,323,329       
  modelRegistry.ts |     100 |    98.21 |     100 |     100 | 182               
  modelsConfig.ts  |   85.37 |    83.54 |   81.57 |   85.37 | ...1210,1239-1240 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/output        |     100 |      100 |     100 |     100 |                   
  ...-formatter.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/permissions   |   71.18 |    88.73 |   48.57 |   71.18 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...on-manager.ts |   81.42 |    86.66 |      80 |   81.42 | ...19-820,827-836 
  rule-parser.ts   |   95.99 |    93.18 |     100 |   95.99 | ...-864,1013-1015 
  ...-semantics.ts |   58.28 |    85.27 |    30.2 |   58.28 | ...1604-1614,1643 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/prompts       |   83.63 |      100 |    87.5 |   83.63 |                   
  mcp-prompts.ts   |   18.18 |      100 |       0 |   18.18 | 11-19             
  ...t-registry.ts |     100 |      100 |     100 |     100 |                   
 src/qwen          |   86.03 |    79.48 |   97.18 |   86.03 |                   
  ...tGenerator.ts |   98.64 |    98.18 |     100 |   98.64 | 105-106           
  qwenOAuth2.ts    |   85.01 |    74.81 |   93.33 |   85.01 | ...,986-1002,1032 
  ...kenManager.ts |   83.79 |    76.22 |     100 |   83.79 | ...63-768,789-794 
 src/services      |   84.83 |    83.66 |   89.14 |   84.83 |                   
  ...llRegistry.ts |   97.82 |    94.73 |     100 |   97.82 | 172-173           
  ...ionService.ts |   95.42 |       94 |     100 |   95.42 | ...79,336,338-342 
  ...ingService.ts |   79.37 |     82.5 |   78.12 |   79.37 | ...1066,1083-1084 
  cronScheduler.ts |   97.56 |    92.98 |     100 |   97.56 | 62-63,77,155      
  ...eryService.ts |   80.43 |    95.45 |      75 |   80.43 | ...19-134,140-141 
  fileReadCache.ts |     100 |      100 |     100 |     100 |                   
  ...temService.ts |   89.76 |     85.1 |   88.88 |   89.76 | ...89,191,266-273 
  gitInit.ts       |     100 |      100 |     100 |     100 |                   
  gitService.ts    |   68.75 |     92.3 |   55.55 |   68.75 | ...12-122,125-129 
  ...reeService.ts |   71.83 |    68.47 |    91.3 |   71.83 | ...89-790,806,822 
  ...ionService.ts |   98.13 |     97.8 |   95.45 |   98.13 | ...32-333,380-381 
  ...orRegistry.ts |   96.76 |    92.15 |     100 |   96.76 | ...95-396,449-450 
  sessionRecap.ts  |   10.71 |      100 |       0 |   10.71 | 48-161            
  ...ionService.ts |   85.48 |    71.62 |      96 |   85.48 | ...73-983,987-988 
  sessionTitle.ts  |   93.95 |    70.37 |     100 |   93.95 | ...36-239,270-271 
  ...ionService.ts |   83.01 |    78.66 |   87.75 |   83.01 | ...1458,1464-1469 
  ...UseSummary.ts |    94.7 |    88.67 |     100 |    94.7 | ...69-171,221-222 
 ...icrocompaction |   98.62 |    86.44 |     100 |   98.62 |                   
  microcompact.ts  |   98.62 |    86.44 |     100 |   98.62 | 138,142           
 src/skills        |    86.7 |    83.88 |   93.61 |    86.7 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...activation.ts |     100 |     93.1 |     100 |     100 | 93,112            
  skill-load.ts    |   89.72 |    80.76 |     100 |   89.72 | ...28,248,260-262 
  skill-manager.ts |   82.68 |    79.32 |   90.32 |   82.68 | ...1135,1142-1146 
  symlinkScope.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/subagents     |   82.78 |    80.18 |   91.11 |   82.78 |                   
  ...tin-agents.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...-selection.ts |     100 |      100 |     100 |     100 |                   
  ...nt-manager.ts |   76.75 |    72.09 |   87.09 |   76.75 | ...1176,1198-1199 
  types.ts         |     100 |      100 |     100 |     100 |                   
  validation.ts    |   92.46 |    95.18 |     100 |   92.46 | 51-56,69-74,78-83 
 src/telemetry     |   70.05 |       83 |   75.11 |   70.05 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...-exporters.ts |   46.37 |      100 |   44.44 |   46.37 | ...85,88-89,92-93 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...t.circular.ts |       0 |        0 |       0 |       0 | 1-111             
  ...-processor.ts |   91.28 |    83.67 |   92.85 |   91.28 | ...66-171,186-187 
  ...t.circular.ts |       0 |        0 |       0 |       0 | 1-128             
  loggers.ts       |    51.9 |       64 |   57.77 |    51.9 | ...1214,1231-1251 
  metrics.ts       |    74.9 |    82.95 |   74.54 |    74.9 | ...58-978,981-992 
  sanitize.ts      |      80 |    83.33 |     100 |      80 | 35-36,41-42       
  sdk.ts           |   88.97 |    77.77 |     100 |   88.97 | ...80-281,300-304 
  ...etry-utils.ts |     100 |      100 |     100 |     100 |                   
  ...l-decision.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |   79.09 |    85.59 |   83.33 |   79.09 | ...1134,1137-1166 
  uiTelemetry.ts   |   92.97 |    96.96 |   81.25 |   92.97 | ...93-194,200-207 
 ...ry/qwen-logger |   68.01 |    80.21 |   64.91 |   68.01 |                   
  event-types.ts   |       0 |        0 |       0 |       0 |                   
  qwen-logger.ts   |   68.01 |       80 |   64.28 |   68.01 | ...1042,1080-1081 
 src/test-utils    |   93.07 |    95.55 |   73.52 |   93.07 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  ...st-helpers.ts |   94.11 |       90 |     100 |   94.11 | 69-70             
  index.ts         |     100 |      100 |     100 |     100 |                   
  mock-tool.ts     |   91.02 |    96.77 |   68.96 |   91.02 | ...32,196-197,210 
  ...aceContext.ts |     100 |      100 |     100 |     100 |                   
 src/tools         |   76.39 |    80.77 |   81.66 |   76.39 |                   
  ...erQuestion.ts |    88.8 |    76.74 |    90.9 |    88.8 | ...36-337,344-345 
  cron-create.ts   |   97.61 |    88.88 |   83.33 |   97.61 | 30-31             
  cron-delete.ts   |   96.55 |      100 |   83.33 |   96.55 | 26-27             
  cron-list.ts     |   96.36 |      100 |   83.33 |   96.36 | 25-26             
  diffOptions.ts   |     100 |      100 |     100 |     100 |                   
  edit.ts          |   77.69 |    84.46 |   73.33 |   77.69 | ...76-677,764-814 
  exitPlanMode.ts  |   84.61 |    85.71 |     100 |   84.61 | ...60-163,177-189 
  glob.ts          |   90.63 |    88.33 |   84.61 |   90.63 | ...28,171,302,305 
  grep.ts          |   79.19 |    85.71 |   78.94 |   79.19 | ...20,560,569-576 
  ls.ts            |   96.74 |    90.27 |     100 |   96.74 | 176-181,212,216   
  lsp.ts           |   72.69 |    60.09 |   90.32 |   72.69 | ...1208,1210-1211 
  ...nt-manager.ts |   52.09 |     65.9 |   47.36 |   52.09 | ...02-520,523-560 
  mcp-client.ts    |   29.65 |    71.05 |   46.87 |   29.65 | ...1434,1438-1441 
  mcp-tool.ts      |   90.92 |    88.88 |   96.42 |   90.92 | ...89-590,640-641 
  memory-config.ts |       0 |        0 |       0 |       0 | 1-48              
  ...iable-tool.ts |     100 |    84.61 |     100 |     100 | 102,109           
  monitor.ts       |   92.16 |    83.45 |      92 |   92.16 | ...15,544-547,560 
  ...nforcement.ts |   82.03 |    89.47 |     100 |   82.03 | 137-148,197-210   
  read-file.ts     |   95.09 |     88.6 |      90 |   95.09 | ...99,271-274,277 
  ripGrep.ts       |   94.59 |    85.71 |   93.33 |   94.59 | ...60,463,541-542 
  ...-transport.ts |    6.34 |        0 |       0 |    6.34 | 47-145            
  send-message.ts  |   88.77 |    91.66 |   83.33 |   88.77 | 44-45,68-76       
  shell.ts         |   81.42 |    80.74 |    90.9 |   81.42 | ...1243,1292-1298 
  skill-utils.ts   |     100 |      100 |     100 |     100 |                   
  skill.ts         |   88.11 |    91.17 |   84.61 |   88.11 | ...95,399,422-444 
  task-stop.ts     |   92.94 |    96.15 |   85.71 |   92.94 | 39-40,54-64       
  todoWrite.ts     |   85.42 |    84.09 |   84.61 |   85.42 | ...05-410,432-433 
  tool-error.ts    |     100 |      100 |     100 |     100 |                   
  tool-names.ts    |     100 |      100 |     100 |     100 |                   
  tool-registry.ts |   67.94 |    68.42 |   68.57 |   67.94 | ...59-660,668-669 
  tools.ts         |    87.6 |    89.79 |   88.23 |    87.6 | ...31-432,448-454 
  web-fetch.ts     |   88.44 |    76.92 |    92.3 |   88.44 | ...05-306,308-309 
  write-file.ts    |   78.82 |     78.2 |   83.33 |   78.82 | ...13-616,628-663 
 src/tools/agent   |   83.08 |    84.15 |   83.33 |   83.08 |                   
  agent-context.ts |     100 |      100 |     100 |     100 |                   
  agent.ts         |    83.2 |    84.29 |   82.92 |    83.2 | ...1519,1528-1532 
  fork-subagent.ts |   78.26 |    71.42 |      80 |   78.26 | 54-72,104-105     
 src/utils         |   87.31 |    87.32 |    91.7 |   87.31 |                   
  LruCache.ts      |       0 |        0 |       0 |       0 | 1-41              
  ...ssageQueue.ts |     100 |      100 |     100 |     100 |                   
  ...cFileWrite.ts |   76.08 |    44.44 |     100 |   76.08 | 61-70,72          
  bareMode.ts      |   27.27 |      100 |       0 |   27.27 | 9-15,18-19        
  browser.ts       |    7.69 |      100 |       0 |    7.69 | 17-56             
  ...igResolver.ts |     100 |      100 |     100 |     100 |                   
  cronDisplay.ts   |   42.85 |    23.07 |     100 |   42.85 | 26-31,33-45,47-54 
  cronParser.ts    |   89.74 |    85.71 |     100 |   89.74 | ...,63-64,183-186 
  debugLogger.ts   |   96.12 |    93.75 |   93.75 |   96.12 | 164-168           
  editHelper.ts    |   93.63 |     83.9 |     100 |   93.63 | ...28-429,463-464 
  editor.ts        |   97.61 |    95.71 |     100 |   97.61 | ...70-271,273-274 
  ...arResolver.ts |   94.28 |    88.88 |     100 |   94.28 | 28-29,125-126     
  ...entContext.ts |     100 |    95.45 |     100 |     100 | 83                
  errorParsing.ts  |    97.7 |    96.87 |     100 |    97.7 | 72-73             
  ...rReporting.ts |   88.46 |       90 |     100 |   88.46 | 69-74             
  errors.ts        |   70.92 |    80.39 |   53.33 |   70.92 | ...03-219,223-229 
  fetch.ts         |   70.18 |    71.42 |   71.42 |   70.18 | ...42,148,161,186 
  fileUtils.ts     |   89.18 |    85.06 |   94.73 |   89.18 | ...91-898,902-908 
  forkedAgent.ts   |   62.98 |    54.54 |      75 |   62.98 | ...23-432,434-447 
  formatters.ts    |   54.54 |       50 |     100 |   54.54 | 12-16             
  ...eUtilities.ts |   89.21 |    86.66 |     100 |   89.21 | 16-17,49-55,65-66 
  ...rStructure.ts |   94.36 |    94.28 |     100 |   94.36 | ...17-120,330-335 
  getPty.ts        |    12.5 |      100 |       0 |    12.5 | 21-34             
  ...noreParser.ts |    92.3 |    89.36 |     100 |    92.3 | ...15-116,186-187 
  gitUtils.ts      |   38.88 |    84.61 |      50 |   38.88 | ...2,51-74,97-148 
  iconvHelper.ts   |     100 |      100 |     100 |     100 |                   
  ...rePatterns.ts |     100 |      100 |     100 |     100 |                   
  ...ionManager.ts |     100 |     90.9 |     100 |     100 | 26                
  ...lPromptIds.ts |     100 |      100 |     100 |     100 |                   
  jsonl-utils.ts   |   59.57 |    89.74 |   45.45 |   59.57 | ...53-286,292-298 
  ...-detection.ts |     100 |      100 |     100 |     100 |                   
  ...yDiscovery.ts |   83.85 |    79.36 |     100 |   83.85 | ...15,318,410-413 
  ...tProcessor.ts |   93.63 |       90 |     100 |   93.63 | ...96-302,384-385 
  ...Inspectors.ts |   61.53 |      100 |      50 |   61.53 | 18-23             
  ...kerChecker.ts |   82.55 |    78.57 |     100 |   82.55 | 68-69,79-84,92-98 
  notebook.ts      |   94.35 |    84.78 |     100 |   94.35 | ...10,122,174-176 
  openaiLogger.ts  |   86.27 |    82.14 |     100 |   86.27 | ...05-107,130-135 
  partUtils.ts     |     100 |      100 |     100 |     100 |                   
  pathReader.ts    |     100 |      100 |     100 |     100 |                   
  paths.ts         |   92.82 |    91.02 |     100 |   92.82 | ...71-372,374-376 
  pdf.ts           |   93.68 |    87.05 |     100 |   93.68 | ...96-297,321-325 
  projectPath.ts   |     100 |      100 |     100 |     100 |                   
  ...ectSummary.ts |   89.39 |    72.41 |     100 |   89.39 | ...37-142,193-196 
  ...tIdContext.ts |     100 |      100 |     100 |     100 |                   
  proxyUtils.ts    |     100 |      100 |     100 |     100 |                   
  ...rDetection.ts |   58.57 |       76 |     100 |   58.57 | ...4,88-89,95-100 
  ...noreParser.ts |   85.45 |    85.18 |     100 |   85.45 | ...59,65-66,72-73 
  rateLimit.ts     |   91.48 |    94.11 |     100 |   91.48 | 80,93-95          
  readManyFiles.ts |   87.96 |    86.95 |     100 |   87.96 | ...05-207,223-234 
  retry.ts         |   89.81 |    88.05 |     100 |   89.81 | ...29,350,357-358 
  ripgrepUtils.ts  |   46.53 |    84.37 |   66.66 |   46.53 | ...32-233,245-322 
  ...sDiscovery.ts |   97.42 |    92.85 |     100 |   97.42 | ...04,182-183,202 
  ...tchOptions.ts |   63.85 |    64.28 |   83.33 |   63.85 | ...29-130,187-188 
  safeJsonParse.ts |   74.07 |    83.33 |     100 |   74.07 | 40-46             
  ...nStringify.ts |     100 |      100 |     100 |     100 |                   
  ...aConverter.ts |   90.78 |    87.87 |     100 |   90.78 | ...41-42,93,95-96 
  ...aValidator.ts |   93.43 |    77.04 |     100 |   93.43 | ...46,155-158,212 
  ...r-launcher.ts |   76.92 |     91.3 |   66.66 |   76.92 | ...34,136,157-195 
  ...orageUtils.ts |   92.41 |    82.82 |     100 |   92.41 | ...39,423-430,441 
  shell-utils.ts   |   82.93 |     89.5 |     100 |   82.93 | ...1522,1529-1533 
  ...lAstParser.ts |   95.58 |    85.79 |     100 |   95.58 | ...1059-1061,1071 
  ...nlyChecker.ts |   95.75 |    92.47 |     100 |   95.75 | ...00-301,313-314 
  sideQuery.ts     |     100 |    92.85 |     100 |     100 | 43                
  ...tGenerator.ts |     100 |      100 |     100 |     100 |                   
  ...ameContext.ts |     100 |      100 |     100 |     100 |                   
  symlink.ts       |   77.77 |       50 |     100 |   77.77 | 44,54-59          
  ...emEncoding.ts |   96.36 |    91.17 |     100 |   96.36 | 59-60,124-125     
  terminalSafe.ts  |     100 |      100 |     100 |     100 |                   
  ...Serializer.ts |   98.72 |       90 |     100 |   98.72 | 42-43,134,201-203 
  testUtils.ts     |   53.33 |      100 |   33.33 |   53.33 | ...53,59-64,70-72 
  textUtils.ts     |      60 |      100 |   66.66 |      60 | 36-55             
  thoughtUtils.ts  |     100 |    92.85 |     100 |     100 | 71                
  ...-converter.ts |   94.59 |    85.71 |     100 |   94.59 | 35-36             
  tool-utils.ts    |    93.6 |     91.3 |     100 |    93.6 | ...58-159,162-163 
  truncation.ts    |     100 |       92 |     100 |     100 | 52,71             
  windowsPath.ts   |   89.47 |    79.31 |     100 |   89.47 | ...57-58,62,90-91 
  ...aceContext.ts |   93.71 |    88.88 |   93.33 |   93.71 | ...24-225,249-251 
  xml.ts           |     100 |      100 |     100 |     100 |                   
  yaml-parser.ts   |      92 |    84.31 |     100 |      92 | 49-53,65-69       
 ...ils/filesearch |   96.34 |    91.66 |     100 |   96.34 |                   
  crawlCache.ts    |     100 |      100 |     100 |     100 |                   
  crawler.ts       |   96.87 |    94.44 |     100 |   96.87 | 83-84             
  fileSearch.ts    |   93.29 |    86.76 |     100 |   93.29 | ...40-241,243-244 
  ignore.ts        |     100 |      100 |     100 |     100 |                   
  result-cache.ts  |     100 |     92.3 |     100 |     100 | 46                
 ...uest-tokenizer |   56.63 |    74.52 |   74.19 |   56.63 |                   
  ...eTokenizer.ts |   41.86 |    76.47 |   69.23 |   41.86 | ...70-443,453-507 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...tTokenizer.ts |   68.39 |    69.49 |    90.9 |   68.39 | ...24-325,327-328 
  ...ageFormats.ts |      76 |      100 |   33.33 |      76 | 45-48,55-56       
  textTokenizer.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 | 1                 
-------------------|---------|----------|---------|---------|-------------------

For detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run.

@tanzhenxin tanzhenxin added the type/bug Something isn't working as expected label May 7, 2026

@tanzhenxin tanzhenxin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

Two narrow fixes addressing the PR-1 review notes. The Proxy-trap close is correct: wrapping hasOwnProperty.call and the kind read in the same try covers the getOwnPropertyDescriptor-throw path, and the new test deliberately omits a get handler so hasOwnProperty.call throws before kind is ever read — pinning the bug as the descriptor-probe leak rather than the already-tested get trap leak.

The PTY post-promotion test now actually exercises the listenersDetached guard rather than tautologically passing. I traced the abort-dispatch microtask ordering (sync abort → guard set inside performBackgroundPromote's pre-await prelude → pre-promote chain .then runs after) and confirmed the strict === 0 baseline meaningfully fails if the guard is removed. Two setImmediate cycles is the right drain primitive — render() runs sync when no render timer is pending.

Note 1 deferral to PR-2 is the right call; re-shaping aborted/promoted twice would just churn callers.

Verdict

APPROVE — both fixes do what they claim, no new bugs.

@tanzhenxin tanzhenxin merged commit ddd21b5 into main May 7, 2026
13 checks passed
TaimoorSiddiquiOfficial pushed a commit to TaimoorSiddiquiOfficial/HopCode that referenced this pull request May 7, 2026
DragonnZhang pushed a commit that referenced this pull request May 8, 2026
…-up to #3842) (#3886)

* fix(core): wrap hasOwnProperty.call inside try + add post-abort PTY data assertion

Two non-blocking review notes from @tanzhenxin's PR-1 approval
(#3842, post-merge follow-up):

- **Note 2 (real bug)**: `getShellAbortReasonKind` had
  `Object.prototype.hasOwnProperty.call(reason, 'kind')` outside the
  try/catch. `hasOwnProperty.call` triggers the `[[GetOwnProperty]]`
  Proxy trap (`getOwnPropertyDescriptor` handler). A Proxy whose
  `getOwnPropertyDescriptor` throws — separate from a throwing `get`
  trap, which the prior commit already covered — would propagate past
  the helper, leaving the abort handler's switch on `kind` to throw
  through `addEventListener` (which doesn't await async listener
  return values), so the shell process would stay alive instead of
  being killed on cancel. Moved the descriptor probe inside the same
  try block as the value read.

  My own multi-round audit covered six attack vectors but missed this
  one — only `get` trap throws were considered. The reviewer caught
  it on first pass.

- **Note 3 (test parity)**: the PTY post-promotion handoff test
  asserted `dispose` was called but never re-invoked the data
  callback to verify the foreground `onOutputEvent` actually stops
  firing — the child_process equivalent has that assertion. Mirrored
  it: emit data AFTER abort by re-invoking the captured `dataCallback`
  reference, and assert `onOutputEventMock.mock.calls.length` does NOT
  increase past the moment of promote. Exercises the production
  `listenersDetached` guard inside the chain callback, which the
  bare dispose-was-called check didn't.

Note 1 from the same review (the `aborted: true + promoted: true`
shape forcing PR-2 callers to check `promoted` before `aborted`) is
deliberately NOT addressed here — it's a contract simplification that
affects PR-2's branching, so it belongs in PR-2 along with the
caller-side decision on whether to flip `aborted` for promoted
results. Added a TODO upstream in #3831 (PR-2 design) to track.

70 / 70 tests pass (69 baseline + 1 new helper boundary for the
throwing-getOwnPropertyDescriptor case). tsc + ESLint clean.

* test(core): fix tautological PTY post-promote assertion (audit follow-up)

The PTY post-promotion handoff test added in the previous commit
copied the child_process equivalent's pattern verbatim — sync
\`expect(count).toBe(countAtPromote)\` immediately after dataCallback
returns. That works for child_process because its \`handleOutput\` is
fully synchronous (sniff → decoder → emit, all on the same call
stack), so the count change happens BEFORE the assertion.

PTY's \`handleOutput\` is async — \`processingChain.then(...)\` queues a
microtask that does the sniff + write + render-then-emit work. The
sync assertion captures both \`countAtPromote\` and the post-emit count
BEFORE the chain microtask ever runs, so both reads return whatever
happened before the assert (typically 0). The test would
tautologically pass even if the production \`listenersDetached\` guard
were removed — i.e., it didn't actually verify the guard.

Restructured to:
1. Drive the PTY through \`simulateExecution\` so \`await handle.result\`
   forces all queued microtasks (including pre-promote chain items
   AND the abort handler's drain) to settle.
2. Capture \`eventCountAfterSettle\` once everything has stabilized.
3. Re-invoke the captured \`dataCallback\` with post-promote data,
   await two more macrotask boundaries to let the new chain item
   fully run.
4. Assert the count hasn't moved.

If the production \`listenersDetached\` guard is removed, the
post-promote chain item emits, count increases past
\`eventCountAfterSettle\`, and this assertion fails. So the test
actually exercises the guard now.

Found in self-audit while reviewing my own follow-up commit. Caught
because audit was paranoid about *whether the test verifies what it
claims to verify*, not just whether it passes.

70 / 70 tests pass; tsc + ESLint clean.

* test(core): pin eventCountAfterSettle === 0 in PTY post-promote test

The previous fix asserted post-promote count equals the
\`eventCountAfterSettle\` baseline, but didn't pin the baseline
itself. With the production \`listenersDetached\` guard intact, both
halves (pre-promote chain and post-promote chain) suppress emit, so
\`eventCountAfterSettle === 0 === post-count\` and the relative
comparison is vacuously true.

If a future refactor changed the production guard semantics so the
pre-promote chain item DID emit (count becomes 1+ after settle), the
relative-comparison test would still pass as long as post-promote
also emitted the same number — that's a regression the test should
catch. Adding \`expect(eventCountAfterSettle).toBe(0)\` makes the
contract explicit: once \`listenersDetached\` is set during the abort
handler's sync part, BOTH the in-flight chain item (pre-promote) and
the future chain item (post-promote) skip emit.

Found in another self-audit pass — even after fixing the tautological
assertion, the test could still mask certain future regressions.
70 / 70 tests pass; tsc + ESLint clean.

* test(core): drop dataCallbackHolder pattern, read mock.calls directly

The dataCallbackHolder pattern (capturing the onData callback inside
simulateExecution then invoking it after via a closure-shared object)
was unnecessary indirection — \`mockPtyProcess.onData.mock.calls[0][0]\`
reads the same callback reference whether you read it inside or outside
the simulation closure. Vi's mock.calls array is per-mock-instance and
beforeEach re-creates mockPtyProcess + .onData freshly, so there's no
stale-reference risk in the simpler form.

No behavior change in what's tested. 70 / 70 pass.

* chore(core): drop in-source @-mention attribution + dead Proxy.get handler

Two cosmetic cleanups found in another self-audit pass:

- **Helper comment**: removed the parenthetical attribution ("Caught
  by @tanzhenxin in the PR-1 review; my own audit only covered \`get\`
  trap throws."). The technical content of the comment — explaining
  why both the descriptor probe and the value read live inside the
  try — stands on its own. The reviewer credit lives in commit
  history / PR description, where it belongs; an in-source @-mention
  ages poorly (handles change, the relevant person may move on) and
  doesn't help future readers reason about the code.

- **Test Proxy**: \`throwingDescriptorProxy\` declared a \`get()\` handler
  that always returned \`undefined\`. The descriptor probe throws
  before the helper ever reaches the value read, so the \`get\`
  handler is unreachable — dropped it and added a one-line comment
  explaining why no \`get\` handler is needed for this test. Mirror
  test (\`throwingReason\` with throwing accessor + \`proxyReason\` with
  throwing \`get\`) keeps the symmetric "throwing-`get`" coverage.

70 / 70 tests pass; tsc + ESLint clean.
wenshao added a commit that referenced this pull request May 8, 2026
…f 3) (#3894)

* feat(core): foreground → background promote integration (#3831 PR-2 of 3)

Builds on the \`signal.reason\` foundation merged in #3842 / #3886. Wires
the foreground \`shell\` tool to detect a background-promote abort, snapshot
the captured output to a \`bg_xxx.output\` file, register a
\`BackgroundShellEntry\` in the existing \`BackgroundShellRegistry\`, and
return a model-facing \`ToolResult\` pointing at \`/tasks\` / the dialog /
\`task_stop\`. Also resolves design question 7 from #3831 (raised by
@tanzhenxin in the PR-1 review): set \`result.aborted: false\` when
\`result.promoted: true\` so existing \`if (result.aborted)\` consumer
branches fall through naturally.

## Changes

**\`shellExecutionService.ts\`** — both \`executeWithPty\` and
\`childProcessFallback\` background-promote branches now resolve with
\`aborted: false, promoted: true\` (was \`aborted: true\`). The flag now
answers "should the caller emit a cancel/timeout message?" rather than
"did the abort signal fire?" — and a promoted shell is neither
cancelled nor timed out (the child is still running, ownership simply
transferred). \`ShellExecutionResult.promoted?\` JSDoc updated to
document this contract.

**\`shell.ts\`** — \`ShellToolInvocation.execute()\` gains a 5th optional
parameter \`setPromoteAbortControllerCallback?: (ac: AbortController)
=> void\`. The foreground path now creates an internal
\`promoteAbortController\` and combines its signal into the existing
\`signal + timeoutSignal\` AbortSignal.any() chain. Right after
\`setPidCallback\` fires, \`setPromoteAbortControllerCallback\` exposes
the controller to the scheduler so a UI surface (PR-3 Ctrl+B keybind)
can find it by callId and trigger \`abort({ kind: 'background',
shellId })\`.

When \`result.promoted\` is observed after \`await resultPromise\`, a new
\`handlePromotedForeground\` private method:
  1. Generates \`bg_xxx\` shellId + on-disk \`outputPath\` under the same
     project temp dir \`executeBackground\` uses.
  2. Writes \`result.output\` (the snapshot the service flushed at
     promote time) as the file's initial content (best-effort —
     ENOSPC / EACCES logged + swallowed; the registry entry is
     valuable on its own).
  3. Constructs a \`BackgroundShellEntry\` with the running pid + the
     same \`promoteAbortController\` already wired into the live child
     — \`task_stop bg_xxx\` and the dialog's \`x\` key both abort via
     \`entry.abortController\` and will land on the still-running
     process.
  4. Returns a model-facing \`ToolResult\` pointing at \`/tasks\` / the
     Background tasks dialog / \`task_stop\` for follow-up.

**\`coreToolScheduler.ts\`** — \`TrackedExecutingToolCall\` gains an
optional \`promoteAbortController?: AbortController\` field, populated
when the shell tool's \`setPromoteAbortControllerCallback\` fires. The
scheduler routes only the shell-tool branch to pass this callback,
matching the existing \`setPidCallback\` pattern.

## Limitations (deferred to PR-2.5)

Two follow-up items intentionally NOT in scope here. Scope discipline
keeps PR-2 reviewable while still delivering the user-facing promote
flow end-to-end (PR-3's Ctrl+B keybind can wire to this PR's
\`promoteAbortController\` to ship a working feature).

- **Post-promote stream redirect**: today the \`outputPath\` content is
  FROZEN at the promote moment. The service detached its data
  listener as part of PR-1's ownership-transfer contract, so
  post-promote bytes from the still-running child don't reach the
  file. \`Read\`-ing the output via \`/tasks\` shows what was captured
  before promote, not live updates. PR-2.5 will add caller-side
  \`onPostPromoteData\` callback (or equivalent) so post-promote bytes
  stream to the file like a normal background shell.

- **Natural-exit registry settle**: the registry entry stays
  \`'running'\` until \`task_stop bg_xxx\` or session-end \`abortAll\`
  clears it. The service's exit listener was disposed at promote, so
  there's no observation point for natural child exit. PR-2.5 will
  keep the exit listener attached post-promote (with a separate
  \`onPostPromoteSettle\` callback) so the entry transitions to
  \`completed\` / \`failed\` like a normal background shell.

These limitations are visible to users (output frozen, entry stays
running until task_stop/session end) but don't break the core promote
contract: the agent unblocks, the registry entry is observable, the
process stays alive, cancel via \`task_stop\` works.

## Tests

**\`shellExecutionService.test.ts\`** — two existing promote tests now
assert \`aborted: false\` (per design question 7) instead of \`true\`.
\`70 / 70 pass\`.

**\`shell.test.ts\`** — three new tests in a \`foreground → background
promote (#3831 PR-2)\` describe block:
  1. \`setPromoteAbortControllerCallback\` exposes a real
     \`AbortController\` after spawn.
  2. On \`result.promoted: true\`, the registry receives a \`bg_xxx\`
     entry with pid + abortController + outputPath, the snapshot is
     written via \`fs.writeFileSync\`, and the model-facing copy
     references \`/tasks\` + \`task_stop\` + the dialog.
  3. A snapshot-write failure (mocked ENOSPC) doesn't break promote —
     the registry entry still gets registered with the running pid.
\`96 / 96 pass\`.

**\`coreToolScheduler.test.ts\`** — \`98 / 98 pass\` (no new tests; the
new \`promoteAbortController\` field is exercised end-to-end via
shell.test.ts).

Total: \`264 / 264 affected tests pass\`; tsc + ESLint clean.

## Related

- #3831 (Phase D part b — design + 3-PR sequencing; question 7
  resolved here)
- #3842 (PR-1 — \`signal.reason\` foundation)
- #3886 (PR-1 follow-up — Proxy-trap fix + handoff test parity)
- #3634 (Background task management roadmap)

cc @tanzhenxin

* fix(core): give promoted shell entry a FRESH AbortController so task_stop kills the child

Real bug found in self-audit of #3894 PR-2: \`entry.abortController\`
was being set to the same \`promoteAbortController\` that triggered the
promote — which is **already aborted** by the time we reach
\`handlePromotedForeground\`. Two consequences:

1. \`task_stop bg_xxx\` calls \`entry.abortController.abort()\`. On an
   already-aborted controller this is a no-op (the abort event was
   dispatched once when the controller fired; the second \`abort()\`
   doesn't re-fire listeners per WHATWG spec).
2. \`ShellExecutionService\` has already detached its own abort
   listener as part of the PR-1 ownership-transfer contract, so even
   if the abort COULD re-fire, there's nobody left listening to
   translate the signal into an actual SIGTERM/SIGKILL on the still-
   running child.

Net effect: a promoted shell would survive \`task_stop\` forever — the
agent would think it cancelled, the registry entry would stay
\`'running'\`, and the OS process would keep running until the user
killed the CLI session.

Fix: \`handlePromotedForeground\` now creates a fresh \`AbortController\`
for the registry entry and wires its abort listener to:
1. Send SIGTERM → SIGKILL to the still-running child via
   \`process.kill(-pid, …)\` (Linux/Mac process group, mirroring the
   \`detached: !isWindows\` spawn the foreground path uses) or
   \`taskkill /pid /f /t\` (Windows). Reuses the same SIGTERM-then-
   timeout-then-SIGKILL pattern \`ShellExecutionService.execute()\`
   uses on the non-promote cancel path; new constant
   \`PROMOTE_CANCEL_SIGKILL_TIMEOUT_MS = 200ms\` (intentionally
   separate from the service's \`SIGKILL_TIMEOUT_MS\` so tuning one
   doesn't silently change the other).
2. Sync-mark the registry entry \`cancelled\` via \`registry.cancel()\`
   so \`/tasks\` and the dialog reflect the user intent immediately.

Added a regression test pinning \`entry.abortController.signal.aborted
=== false\` at registration time. Without the fix, this asserts
\`true\` and the test fails — which is the visible canary for the
silent-task_stop-failure mode.

97 / 97 shell.test.ts pass; tsc + ESLint clean.

* fix(core): add 'error' listener on Windows taskkill spawn (audit follow-up)

Reverse-audit found a Windows-specific crash mode: \`cpSpawn('taskkill',
…)\` returns a \`ChildProcess\` whose 'error' event (emitted when the
spawn fails — taskkill binary missing, EACCES, etc.) crashes Node by
default if no 'error' listener is attached. Same pattern as PR-1's
\`@lydell/node-pty\` IPty incident — Web/Node spec quirk easy to miss
without specifically thinking about Windows + spawn-failure.

Also wrapped the \`cpSpawn\` call itself in try/catch for the rarer
sync-throw mode (EMFILE / ENOMEM at spawn-time). Recovery in both
cases: log via debugLogger.warn + continue; \`registry.cancel\` below
still transitions the entry, and the still-running child becomes an
orphan that Windows reaps when the CLI session ends.

97 / 97 shell.test.ts pass; tsc + ESLint clean.

* test(core): close 3 test gaps from #3894 review

Three [Suggestion] threads from the @tanzhenxin-style review on PR-2,
all real test gaps that would have let silent regressions through:

1. **\`setPromoteAbortControllerCallback\` test was too weak.** The old
   test only asserted that the callback received an \`AbortController\`
   instance, not that the controller's signal was actually wired into
   the \`AbortSignal.any(...)\` chain handed to ShellExecutionService.
   If \`shell.ts\` exposed the controller but forgot to combine its
   signal, Ctrl+B promotion would never reach the service while the
   bare-instance test still passed. Strengthened: capture the
   AbortSignal handed to ShellExecutionService.execute (4th arg),
   abort the promote controller, and assert the captured signal goes
   from \`aborted: false\` → \`true\`.

2. **The post-promote cancellation kill path was unverified.** The
   prior commit added a real-bug fix (fresh \`entryAc\` + abort
   listener that sends SIGTERM/SIGKILL + sync-marks the registry
   entry cancelled) but the only test it had was "the controller is
   fresh, signal not aborted". Reviewer rightly noted that this is
   the **core operational guarantee** for promoted shells — \`task_stop
   bg_xxx\` must actually stop the child. Added a test that uses
   fake timers + a \`process.kill\` spy: register a promoted entry,
   abort \`entry.abortController\`, flush microtasks (SIGTERM dispatch),
   advance fake time past \`PROMOTE_CANCEL_SIGKILL_TIMEOUT_MS\`
   (SIGKILL dispatch + \`registry.cancel\` mark). Pins the entire
   kill chain.

3. **Scheduler-side wiring of \`promoteAbortController\` was untested.**
   PR-3's Ctrl+B keybind looks up the executing tool call by callId
   and aborts \`tc.promoteAbortController\` — if \`CoreToolScheduler\`
   stops populating that field, the keybind silently breaks. Added
   a test in \`coreToolScheduler.test.ts\` that uses a
   \`TestShellInvocation extends ShellToolInvocation\` (so the
   scheduler's \`instanceof ShellToolInvocation\` check still routes
   the call through the shell-specific branch that wires the
   callback) and asserts that an \`onToolCallsUpdate\` batch emitted
   during the executing window contains a tool call where
   \`tc.promoteAbortController\` matches the controller the test
   exposed.

98 / 98 shell.test.ts pass; 99 / 99 coreToolScheduler.test.ts pass; tsc + ESLint clean.

* fix(core): use commandToExecute in promoted entry + try/catch register

Resolves 3 #3894 review threads:

- **Critical**: `entry.command` and `llmContent` for the promoted
  foreground shell now use `commandToExecute` (post-co-author-rewrite
  form) instead of raw `this.params.command`. For `git commit -m`
  invocations that `addCoAuthorToGitCommit()` rewrote, the registry
  entry now mirrors what actually ran — matching `executeBackground`'s
  long-standing convention (line 1234).

- Defensive try/catch around `registry.register(entry)`: today the
  call is internally safe (Map.set + emit), but a future
  implementation that throws would leave a zombie child detached from
  service listeners with no kill path. Catch path logs, fires
  `entryAc.abort()` for best-effort kill via the wired listener, and
  re-throws so the scheduler surfaces the failure.

- Updates the misleading comment (line 748) that claimed the registry
  entry uses "the same `promoteAbortController`" — actual impl uses a
  fresh `entryAc` (the audit-fix from the previous push).

Tests:
- `entry.command` git-commit case pinning post-rewrite form
- register-throw rejection + SIGTERM/SIGKILL kill via fake timers
- 100/100 shell.test.ts pass; tsc + ESLint clean

* fix(core): close 2 #3894 review findings — promote refused-race + mkdir orphan

Resolves @tanzhenxin's CHANGES_REQUESTED review on #3894.

1. **Refused-promote race no longer reported as "Command timed out"**

The combined-abort signal folds in `signal | timeoutSignal |
promoteAbortController.signal`, but the timeout discriminator only
excluded the user-cancel signal — not the promote signal. When the
user fires Ctrl+B (PR-3's keybind) but the service's race guard
refuses promotion (the child terminated a beat earlier), the result
lands `aborted: true, promoted: false` and the foreground path
falsely reported `Command timed out after 120000ms`. Both the agent
and the user would see a timeout that didn't happen.

Fix: extend the discriminator to ALSO exclude
`promoteAbortController.signal.aborted`. Add a `wasPromoteRefused`
branch that surfaces the actual cause: "Command finished before the
background-promote request could be honoured (the child had already
exited)." Same fix applied to both the llmContent path and the
returnDisplay path so the model and the visible UI agree.

Latent in PR-2 itself (no in-tree caller fires the promote yet), but
PR-3's keybind would expose it on first ship.

2. **Unguarded mkdirSync orphans the promoted child**

After `result.promoted: true`, ownership of the still-running child
has transferred and the service's kill path is detached. The promote
handler creates the snapshot output directory next, but the original
`fs.mkdirSync(outputDir, { recursive: true })` had no guard — read-
only temp mounts, sandboxed perms, full disk on inode/metadata
exhaustion would reject the handler BEFORE the registry's kill
listener was wired. The still-running child became an orphan zombie
with no kill path until the OS reaped it on session end.

Fix: wrap mkdirSync in try/catch (matches the safety pattern around
`registry.register`). On failure, log + best-effort kill the child
(SIGTERM via process.kill(-pid) on POSIX, taskkill /f /t on Windows
with an `error` listener so a spawn failure doesn't crash Node) +
re-throw so the scheduler surfaces the failure to the agent.

Tests: 2 new regressions in `shell.test.ts`:
- `mkdirSync(outputDir) throws → child gets SIGTERM, error re-raised`
- `promote-refused race (aborted: true, promoted: false after promote
  signal) is NOT reported as "Command timed out"`

171/171 shell.test.ts pass; tsc + ESLint clean.
TaimoorSiddiquiOfficial pushed a commit to TaimoorSiddiquiOfficial/HopCode that referenced this pull request May 8, 2026
…PR-2 of 3) (QwenLM#3894)

* feat(core): foreground → background promote integration (QwenLM#3831 PR-2 of 3)

Builds on the \`signal.reason\` foundation merged in QwenLM#3842 / QwenLM#3886. Wires
the foreground \`shell\` tool to detect a background-promote abort, snapshot
the captured output to a \`bg_xxx.output\` file, register a
\`BackgroundShellEntry\` in the existing \`BackgroundShellRegistry\`, and
return a model-facing \`ToolResult\` pointing at \`/tasks\` / the dialog /
\`task_stop\`. Also resolves design question 7 from QwenLM#3831 (raised by
@tanzhenxin in the PR-1 review): set \`result.aborted: false\` when
\`result.promoted: true\` so existing \`if (result.aborted)\` consumer
branches fall through naturally.

**\`shellExecutionService.ts\`** — both \`executeWithPty\` and
\`childProcessFallback\` background-promote branches now resolve with
\`aborted: false, promoted: true\` (was \`aborted: true\`). The flag now
answers "should the caller emit a cancel/timeout message?" rather than
"did the abort signal fire?" — and a promoted shell is neither
cancelled nor timed out (the child is still running, ownership simply
transferred). \`ShellExecutionResult.promoted?\` JSDoc updated to
document this contract.

**\`shell.ts\`** — \`ShellToolInvocation.execute()\` gains a 5th optional
parameter \`setPromoteAbortControllerCallback?: (ac: AbortController)
=> void\`. The foreground path now creates an internal
\`promoteAbortController\` and combines its signal into the existing
\`signal + timeoutSignal\` AbortSignal.any() chain. Right after
\`setPidCallback\` fires, \`setPromoteAbortControllerCallback\` exposes
the controller to the scheduler so a UI surface (PR-3 Ctrl+B keybind)
can find it by callId and trigger \`abort({ kind: 'background',
shellId })\`.

When \`result.promoted\` is observed after \`await resultPromise\`, a new
\`handlePromotedForeground\` private method:
  1. Generates \`bg_xxx\` shellId + on-disk \`outputPath\` under the same
     project temp dir \`executeBackground\` uses.
  2. Writes \`result.output\` (the snapshot the service flushed at
     promote time) as the file's initial content (best-effort —
     ENOSPC / EACCES logged + swallowed; the registry entry is
     valuable on its own).
  3. Constructs a \`BackgroundShellEntry\` with the running pid + the
     same \`promoteAbortController\` already wired into the live child
     — \`task_stop bg_xxx\` and the dialog's \`x\` key both abort via
     \`entry.abortController\` and will land on the still-running
     process.
  4. Returns a model-facing \`ToolResult\` pointing at \`/tasks\` / the
     Background tasks dialog / \`task_stop\` for follow-up.

**\`coreToolScheduler.ts\`** — \`TrackedExecutingToolCall\` gains an
optional \`promoteAbortController?: AbortController\` field, populated
when the shell tool's \`setPromoteAbortControllerCallback\` fires. The
scheduler routes only the shell-tool branch to pass this callback,
matching the existing \`setPidCallback\` pattern.

Two follow-up items intentionally NOT in scope here. Scope discipline
keeps PR-2 reviewable while still delivering the user-facing promote
flow end-to-end (PR-3's Ctrl+B keybind can wire to this PR's
\`promoteAbortController\` to ship a working feature).

- **Post-promote stream redirect**: today the \`outputPath\` content is
  FROZEN at the promote moment. The service detached its data
  listener as part of PR-1's ownership-transfer contract, so
  post-promote bytes from the still-running child don't reach the
  file. \`Read\`-ing the output via \`/tasks\` shows what was captured
  before promote, not live updates. PR-2.5 will add caller-side
  \`onPostPromoteData\` callback (or equivalent) so post-promote bytes
  stream to the file like a normal background shell.

- **Natural-exit registry settle**: the registry entry stays
  \`'running'\` until \`task_stop bg_xxx\` or session-end \`abortAll\`
  clears it. The service's exit listener was disposed at promote, so
  there's no observation point for natural child exit. PR-2.5 will
  keep the exit listener attached post-promote (with a separate
  \`onPostPromoteSettle\` callback) so the entry transitions to
  \`completed\` / \`failed\` like a normal background shell.

These limitations are visible to users (output frozen, entry stays
running until task_stop/session end) but don't break the core promote
contract: the agent unblocks, the registry entry is observable, the
process stays alive, cancel via \`task_stop\` works.

**\`shellExecutionService.test.ts\`** — two existing promote tests now
assert \`aborted: false\` (per design question 7) instead of \`true\`.
\`70 / 70 pass\`.

**\`shell.test.ts\`** — three new tests in a \`foreground → background
promote (QwenLM#3831 PR-2)\` describe block:
  1. \`setPromoteAbortControllerCallback\` exposes a real
     \`AbortController\` after spawn.
  2. On \`result.promoted: true\`, the registry receives a \`bg_xxx\`
     entry with pid + abortController + outputPath, the snapshot is
     written via \`fs.writeFileSync\`, and the model-facing copy
     references \`/tasks\` + \`task_stop\` + the dialog.
  3. A snapshot-write failure (mocked ENOSPC) doesn't break promote —
     the registry entry still gets registered with the running pid.
\`96 / 96 pass\`.

**\`coreToolScheduler.test.ts\`** — \`98 / 98 pass\` (no new tests; the
new \`promoteAbortController\` field is exercised end-to-end via
shell.test.ts).

Total: \`264 / 264 affected tests pass\`; tsc + ESLint clean.

- QwenLM#3831 (Phase D part b — design + 3-PR sequencing; question 7
  resolved here)
- QwenLM#3842 (PR-1 — \`signal.reason\` foundation)
- QwenLM#3886 (PR-1 follow-up — Proxy-trap fix + handoff test parity)
- QwenLM#3634 (Background task management roadmap)

cc @tanzhenxin

* fix(core): give promoted shell entry a FRESH AbortController so task_stop kills the child

Real bug found in self-audit of QwenLM#3894 PR-2: \`entry.abortController\`
was being set to the same \`promoteAbortController\` that triggered the
promote — which is **already aborted** by the time we reach
\`handlePromotedForeground\`. Two consequences:

1. \`task_stop bg_xxx\` calls \`entry.abortController.abort()\`. On an
   already-aborted controller this is a no-op (the abort event was
   dispatched once when the controller fired; the second \`abort()\`
   doesn't re-fire listeners per WHATWG spec).
2. \`ShellExecutionService\` has already detached its own abort
   listener as part of the PR-1 ownership-transfer contract, so even
   if the abort COULD re-fire, there's nobody left listening to
   translate the signal into an actual SIGTERM/SIGKILL on the still-
   running child.

Net effect: a promoted shell would survive \`task_stop\` forever — the
agent would think it cancelled, the registry entry would stay
\`'running'\`, and the OS process would keep running until the user
killed the CLI session.

Fix: \`handlePromotedForeground\` now creates a fresh \`AbortController\`
for the registry entry and wires its abort listener to:
1. Send SIGTERM → SIGKILL to the still-running child via
   \`process.kill(-pid, …)\` (Linux/Mac process group, mirroring the
   \`detached: !isWindows\` spawn the foreground path uses) or
   \`taskkill /pid /f /t\` (Windows). Reuses the same SIGTERM-then-
   timeout-then-SIGKILL pattern \`ShellExecutionService.execute()\`
   uses on the non-promote cancel path; new constant
   \`PROMOTE_CANCEL_SIGKILL_TIMEOUT_MS = 200ms\` (intentionally
   separate from the service's \`SIGKILL_TIMEOUT_MS\` so tuning one
   doesn't silently change the other).
2. Sync-mark the registry entry \`cancelled\` via \`registry.cancel()\`
   so \`/tasks\` and the dialog reflect the user intent immediately.

Added a regression test pinning \`entry.abortController.signal.aborted
=== false\` at registration time. Without the fix, this asserts
\`true\` and the test fails — which is the visible canary for the
silent-task_stop-failure mode.

97 / 97 shell.test.ts pass; tsc + ESLint clean.

* fix(core): add 'error' listener on Windows taskkill spawn (audit follow-up)

Reverse-audit found a Windows-specific crash mode: \`cpSpawn('taskkill',
…)\` returns a \`ChildProcess\` whose 'error' event (emitted when the
spawn fails — taskkill binary missing, EACCES, etc.) crashes Node by
default if no 'error' listener is attached. Same pattern as PR-1's
\`@lydell/node-pty\` IPty incident — Web/Node spec quirk easy to miss
without specifically thinking about Windows + spawn-failure.

Also wrapped the \`cpSpawn\` call itself in try/catch for the rarer
sync-throw mode (EMFILE / ENOMEM at spawn-time). Recovery in both
cases: log via debugLogger.warn + continue; \`registry.cancel\` below
still transitions the entry, and the still-running child becomes an
orphan that Windows reaps when the CLI session ends.

97 / 97 shell.test.ts pass; tsc + ESLint clean.

* test(core): close 3 test gaps from QwenLM#3894 review

Three [Suggestion] threads from the @tanzhenxin-style review on PR-2,
all real test gaps that would have let silent regressions through:

1. **\`setPromoteAbortControllerCallback\` test was too weak.** The old
   test only asserted that the callback received an \`AbortController\`
   instance, not that the controller's signal was actually wired into
   the \`AbortSignal.any(...)\` chain handed to ShellExecutionService.
   If \`shell.ts\` exposed the controller but forgot to combine its
   signal, Ctrl+B promotion would never reach the service while the
   bare-instance test still passed. Strengthened: capture the
   AbortSignal handed to ShellExecutionService.execute (4th arg),
   abort the promote controller, and assert the captured signal goes
   from \`aborted: false\` → \`true\`.

2. **The post-promote cancellation kill path was unverified.** The
   prior commit added a real-bug fix (fresh \`entryAc\` + abort
   listener that sends SIGTERM/SIGKILL + sync-marks the registry
   entry cancelled) but the only test it had was "the controller is
   fresh, signal not aborted". Reviewer rightly noted that this is
   the **core operational guarantee** for promoted shells — \`task_stop
   bg_xxx\` must actually stop the child. Added a test that uses
   fake timers + a \`process.kill\` spy: register a promoted entry,
   abort \`entry.abortController\`, flush microtasks (SIGTERM dispatch),
   advance fake time past \`PROMOTE_CANCEL_SIGKILL_TIMEOUT_MS\`
   (SIGKILL dispatch + \`registry.cancel\` mark). Pins the entire
   kill chain.

3. **Scheduler-side wiring of \`promoteAbortController\` was untested.**
   PR-3's Ctrl+B keybind looks up the executing tool call by callId
   and aborts \`tc.promoteAbortController\` — if \`CoreToolScheduler\`
   stops populating that field, the keybind silently breaks. Added
   a test in \`coreToolScheduler.test.ts\` that uses a
   \`TestShellInvocation extends ShellToolInvocation\` (so the
   scheduler's \`instanceof ShellToolInvocation\` check still routes
   the call through the shell-specific branch that wires the
   callback) and asserts that an \`onToolCallsUpdate\` batch emitted
   during the executing window contains a tool call where
   \`tc.promoteAbortController\` matches the controller the test
   exposed.

98 / 98 shell.test.ts pass; 99 / 99 coreToolScheduler.test.ts pass; tsc + ESLint clean.

* fix(core): use commandToExecute in promoted entry + try/catch register

Resolves 3 QwenLM#3894 review threads:

- **Critical**: `entry.command` and `llmContent` for the promoted
  foreground shell now use `commandToExecute` (post-co-author-rewrite
  form) instead of raw `this.params.command`. For `git commit -m`
  invocations that `addCoAuthorToGitCommit()` rewrote, the registry
  entry now mirrors what actually ran — matching `executeBackground`'s
  long-standing convention (line 1234).

- Defensive try/catch around `registry.register(entry)`: today the
  call is internally safe (Map.set + emit), but a future
  implementation that throws would leave a zombie child detached from
  service listeners with no kill path. Catch path logs, fires
  `entryAc.abort()` for best-effort kill via the wired listener, and
  re-throws so the scheduler surfaces the failure.

- Updates the misleading comment (line 748) that claimed the registry
  entry uses "the same `promoteAbortController`" — actual impl uses a
  fresh `entryAc` (the audit-fix from the previous push).

Tests:
- `entry.command` git-commit case pinning post-rewrite form
- register-throw rejection + SIGTERM/SIGKILL kill via fake timers
- 100/100 shell.test.ts pass; tsc + ESLint clean

* fix(core): close 2 QwenLM#3894 review findings — promote refused-race + mkdir orphan

Resolves @tanzhenxin's CHANGES_REQUESTED review on QwenLM#3894.

1. **Refused-promote race no longer reported as "Command timed out"**

The combined-abort signal folds in `signal | timeoutSignal |
promoteAbortController.signal`, but the timeout discriminator only
excluded the user-cancel signal — not the promote signal. When the
user fires Ctrl+B (PR-3's keybind) but the service's race guard
refuses promotion (the child terminated a beat earlier), the result
lands `aborted: true, promoted: false` and the foreground path
falsely reported `Command timed out after 120000ms`. Both the agent
and the user would see a timeout that didn't happen.

Fix: extend the discriminator to ALSO exclude
`promoteAbortController.signal.aborted`. Add a `wasPromoteRefused`
branch that surfaces the actual cause: "Command finished before the
background-promote request could be honoured (the child had already
exited)." Same fix applied to both the llmContent path and the
returnDisplay path so the model and the visible UI agree.

Latent in PR-2 itself (no in-tree caller fires the promote yet), but
PR-3's keybind would expose it on first ship.

2. **Unguarded mkdirSync orphans the promoted child**

After `result.promoted: true`, ownership of the still-running child
has transferred and the service's kill path is detached. The promote
handler creates the snapshot output directory next, but the original
`fs.mkdirSync(outputDir, { recursive: true })` had no guard — read-
only temp mounts, sandboxed perms, full disk on inode/metadata
exhaustion would reject the handler BEFORE the registry's kill
listener was wired. The still-running child became an orphan zombie
with no kill path until the OS reaped it on session end.

Fix: wrap mkdirSync in try/catch (matches the safety pattern around
`registry.register`). On failure, log + best-effort kill the child
(SIGTERM via process.kill(-pid) on POSIX, taskkill /f /t on Windows
with an `error` listener so a spawn failure doesn't crash Node) +
re-throw so the scheduler surfaces the failure to the agent.

Tests: 2 new regressions in `shell.test.ts`:
- `mkdirSync(outputDir) throws → child gets SIGTERM, error re-raised`
- `promote-refused race (aborted: true, promoted: false after promote
  signal) is NOT reported as "Command timed out"`

171/171 shell.test.ts pass; tsc + ESLint clean.
B-A-M-N pushed a commit to B-A-M-N/qwen-code that referenced this pull request May 8, 2026
…PR-2 of 3) (QwenLM#3894)

* feat(core): foreground → background promote integration (QwenLM#3831 PR-2 of 3)

Builds on the \`signal.reason\` foundation merged in QwenLM#3842 / QwenLM#3886. Wires
the foreground \`shell\` tool to detect a background-promote abort, snapshot
the captured output to a \`bg_xxx.output\` file, register a
\`BackgroundShellEntry\` in the existing \`BackgroundShellRegistry\`, and
return a model-facing \`ToolResult\` pointing at \`/tasks\` / the dialog /
\`task_stop\`. Also resolves design question 7 from QwenLM#3831 (raised by
@tanzhenxin in the PR-1 review): set \`result.aborted: false\` when
\`result.promoted: true\` so existing \`if (result.aborted)\` consumer
branches fall through naturally.

## Changes

**\`shellExecutionService.ts\`** — both \`executeWithPty\` and
\`childProcessFallback\` background-promote branches now resolve with
\`aborted: false, promoted: true\` (was \`aborted: true\`). The flag now
answers "should the caller emit a cancel/timeout message?" rather than
"did the abort signal fire?" — and a promoted shell is neither
cancelled nor timed out (the child is still running, ownership simply
transferred). \`ShellExecutionResult.promoted?\` JSDoc updated to
document this contract.

**\`shell.ts\`** — \`ShellToolInvocation.execute()\` gains a 5th optional
parameter \`setPromoteAbortControllerCallback?: (ac: AbortController)
=> void\`. The foreground path now creates an internal
\`promoteAbortController\` and combines its signal into the existing
\`signal + timeoutSignal\` AbortSignal.any() chain. Right after
\`setPidCallback\` fires, \`setPromoteAbortControllerCallback\` exposes
the controller to the scheduler so a UI surface (PR-3 Ctrl+B keybind)
can find it by callId and trigger \`abort({ kind: 'background',
shellId })\`.

When \`result.promoted\` is observed after \`await resultPromise\`, a new
\`handlePromotedForeground\` private method:
  1. Generates \`bg_xxx\` shellId + on-disk \`outputPath\` under the same
     project temp dir \`executeBackground\` uses.
  2. Writes \`result.output\` (the snapshot the service flushed at
     promote time) as the file's initial content (best-effort —
     ENOSPC / EACCES logged + swallowed; the registry entry is
     valuable on its own).
  3. Constructs a \`BackgroundShellEntry\` with the running pid + the
     same \`promoteAbortController\` already wired into the live child
     — \`task_stop bg_xxx\` and the dialog's \`x\` key both abort via
     \`entry.abortController\` and will land on the still-running
     process.
  4. Returns a model-facing \`ToolResult\` pointing at \`/tasks\` / the
     Background tasks dialog / \`task_stop\` for follow-up.

**\`coreToolScheduler.ts\`** — \`TrackedExecutingToolCall\` gains an
optional \`promoteAbortController?: AbortController\` field, populated
when the shell tool's \`setPromoteAbortControllerCallback\` fires. The
scheduler routes only the shell-tool branch to pass this callback,
matching the existing \`setPidCallback\` pattern.

## Limitations (deferred to PR-2.5)

Two follow-up items intentionally NOT in scope here. Scope discipline
keeps PR-2 reviewable while still delivering the user-facing promote
flow end-to-end (PR-3's Ctrl+B keybind can wire to this PR's
\`promoteAbortController\` to ship a working feature).

- **Post-promote stream redirect**: today the \`outputPath\` content is
  FROZEN at the promote moment. The service detached its data
  listener as part of PR-1's ownership-transfer contract, so
  post-promote bytes from the still-running child don't reach the
  file. \`Read\`-ing the output via \`/tasks\` shows what was captured
  before promote, not live updates. PR-2.5 will add caller-side
  \`onPostPromoteData\` callback (or equivalent) so post-promote bytes
  stream to the file like a normal background shell.

- **Natural-exit registry settle**: the registry entry stays
  \`'running'\` until \`task_stop bg_xxx\` or session-end \`abortAll\`
  clears it. The service's exit listener was disposed at promote, so
  there's no observation point for natural child exit. PR-2.5 will
  keep the exit listener attached post-promote (with a separate
  \`onPostPromoteSettle\` callback) so the entry transitions to
  \`completed\` / \`failed\` like a normal background shell.

These limitations are visible to users (output frozen, entry stays
running until task_stop/session end) but don't break the core promote
contract: the agent unblocks, the registry entry is observable, the
process stays alive, cancel via \`task_stop\` works.

## Tests

**\`shellExecutionService.test.ts\`** — two existing promote tests now
assert \`aborted: false\` (per design question 7) instead of \`true\`.
\`70 / 70 pass\`.

**\`shell.test.ts\`** — three new tests in a \`foreground → background
promote (QwenLM#3831 PR-2)\` describe block:
  1. \`setPromoteAbortControllerCallback\` exposes a real
     \`AbortController\` after spawn.
  2. On \`result.promoted: true\`, the registry receives a \`bg_xxx\`
     entry with pid + abortController + outputPath, the snapshot is
     written via \`fs.writeFileSync\`, and the model-facing copy
     references \`/tasks\` + \`task_stop\` + the dialog.
  3. A snapshot-write failure (mocked ENOSPC) doesn't break promote —
     the registry entry still gets registered with the running pid.
\`96 / 96 pass\`.

**\`coreToolScheduler.test.ts\`** — \`98 / 98 pass\` (no new tests; the
new \`promoteAbortController\` field is exercised end-to-end via
shell.test.ts).

Total: \`264 / 264 affected tests pass\`; tsc + ESLint clean.

## Related

- QwenLM#3831 (Phase D part b — design + 3-PR sequencing; question 7
  resolved here)
- QwenLM#3842 (PR-1 — \`signal.reason\` foundation)
- QwenLM#3886 (PR-1 follow-up — Proxy-trap fix + handoff test parity)
- QwenLM#3634 (Background task management roadmap)

cc @tanzhenxin

* fix(core): give promoted shell entry a FRESH AbortController so task_stop kills the child

Real bug found in self-audit of QwenLM#3894 PR-2: \`entry.abortController\`
was being set to the same \`promoteAbortController\` that triggered the
promote — which is **already aborted** by the time we reach
\`handlePromotedForeground\`. Two consequences:

1. \`task_stop bg_xxx\` calls \`entry.abortController.abort()\`. On an
   already-aborted controller this is a no-op (the abort event was
   dispatched once when the controller fired; the second \`abort()\`
   doesn't re-fire listeners per WHATWG spec).
2. \`ShellExecutionService\` has already detached its own abort
   listener as part of the PR-1 ownership-transfer contract, so even
   if the abort COULD re-fire, there's nobody left listening to
   translate the signal into an actual SIGTERM/SIGKILL on the still-
   running child.

Net effect: a promoted shell would survive \`task_stop\` forever — the
agent would think it cancelled, the registry entry would stay
\`'running'\`, and the OS process would keep running until the user
killed the CLI session.

Fix: \`handlePromotedForeground\` now creates a fresh \`AbortController\`
for the registry entry and wires its abort listener to:
1. Send SIGTERM → SIGKILL to the still-running child via
   \`process.kill(-pid, …)\` (Linux/Mac process group, mirroring the
   \`detached: !isWindows\` spawn the foreground path uses) or
   \`taskkill /pid /f /t\` (Windows). Reuses the same SIGTERM-then-
   timeout-then-SIGKILL pattern \`ShellExecutionService.execute()\`
   uses on the non-promote cancel path; new constant
   \`PROMOTE_CANCEL_SIGKILL_TIMEOUT_MS = 200ms\` (intentionally
   separate from the service's \`SIGKILL_TIMEOUT_MS\` so tuning one
   doesn't silently change the other).
2. Sync-mark the registry entry \`cancelled\` via \`registry.cancel()\`
   so \`/tasks\` and the dialog reflect the user intent immediately.

Added a regression test pinning \`entry.abortController.signal.aborted
=== false\` at registration time. Without the fix, this asserts
\`true\` and the test fails — which is the visible canary for the
silent-task_stop-failure mode.

97 / 97 shell.test.ts pass; tsc + ESLint clean.

* fix(core): add 'error' listener on Windows taskkill spawn (audit follow-up)

Reverse-audit found a Windows-specific crash mode: \`cpSpawn('taskkill',
…)\` returns a \`ChildProcess\` whose 'error' event (emitted when the
spawn fails — taskkill binary missing, EACCES, etc.) crashes Node by
default if no 'error' listener is attached. Same pattern as PR-1's
\`@lydell/node-pty\` IPty incident — Web/Node spec quirk easy to miss
without specifically thinking about Windows + spawn-failure.

Also wrapped the \`cpSpawn\` call itself in try/catch for the rarer
sync-throw mode (EMFILE / ENOMEM at spawn-time). Recovery in both
cases: log via debugLogger.warn + continue; \`registry.cancel\` below
still transitions the entry, and the still-running child becomes an
orphan that Windows reaps when the CLI session ends.

97 / 97 shell.test.ts pass; tsc + ESLint clean.

* test(core): close 3 test gaps from QwenLM#3894 review

Three [Suggestion] threads from the @tanzhenxin-style review on PR-2,
all real test gaps that would have let silent regressions through:

1. **\`setPromoteAbortControllerCallback\` test was too weak.** The old
   test only asserted that the callback received an \`AbortController\`
   instance, not that the controller's signal was actually wired into
   the \`AbortSignal.any(...)\` chain handed to ShellExecutionService.
   If \`shell.ts\` exposed the controller but forgot to combine its
   signal, Ctrl+B promotion would never reach the service while the
   bare-instance test still passed. Strengthened: capture the
   AbortSignal handed to ShellExecutionService.execute (4th arg),
   abort the promote controller, and assert the captured signal goes
   from \`aborted: false\` → \`true\`.

2. **The post-promote cancellation kill path was unverified.** The
   prior commit added a real-bug fix (fresh \`entryAc\` + abort
   listener that sends SIGTERM/SIGKILL + sync-marks the registry
   entry cancelled) but the only test it had was "the controller is
   fresh, signal not aborted". Reviewer rightly noted that this is
   the **core operational guarantee** for promoted shells — \`task_stop
   bg_xxx\` must actually stop the child. Added a test that uses
   fake timers + a \`process.kill\` spy: register a promoted entry,
   abort \`entry.abortController\`, flush microtasks (SIGTERM dispatch),
   advance fake time past \`PROMOTE_CANCEL_SIGKILL_TIMEOUT_MS\`
   (SIGKILL dispatch + \`registry.cancel\` mark). Pins the entire
   kill chain.

3. **Scheduler-side wiring of \`promoteAbortController\` was untested.**
   PR-3's Ctrl+B keybind looks up the executing tool call by callId
   and aborts \`tc.promoteAbortController\` — if \`CoreToolScheduler\`
   stops populating that field, the keybind silently breaks. Added
   a test in \`coreToolScheduler.test.ts\` that uses a
   \`TestShellInvocation extends ShellToolInvocation\` (so the
   scheduler's \`instanceof ShellToolInvocation\` check still routes
   the call through the shell-specific branch that wires the
   callback) and asserts that an \`onToolCallsUpdate\` batch emitted
   during the executing window contains a tool call where
   \`tc.promoteAbortController\` matches the controller the test
   exposed.

98 / 98 shell.test.ts pass; 99 / 99 coreToolScheduler.test.ts pass; tsc + ESLint clean.

* fix(core): use commandToExecute in promoted entry + try/catch register

Resolves 3 QwenLM#3894 review threads:

- **Critical**: `entry.command` and `llmContent` for the promoted
  foreground shell now use `commandToExecute` (post-co-author-rewrite
  form) instead of raw `this.params.command`. For `git commit -m`
  invocations that `addCoAuthorToGitCommit()` rewrote, the registry
  entry now mirrors what actually ran — matching `executeBackground`'s
  long-standing convention (line 1234).

- Defensive try/catch around `registry.register(entry)`: today the
  call is internally safe (Map.set + emit), but a future
  implementation that throws would leave a zombie child detached from
  service listeners with no kill path. Catch path logs, fires
  `entryAc.abort()` for best-effort kill via the wired listener, and
  re-throws so the scheduler surfaces the failure.

- Updates the misleading comment (line 748) that claimed the registry
  entry uses "the same `promoteAbortController`" — actual impl uses a
  fresh `entryAc` (the audit-fix from the previous push).

Tests:
- `entry.command` git-commit case pinning post-rewrite form
- register-throw rejection + SIGTERM/SIGKILL kill via fake timers
- 100/100 shell.test.ts pass; tsc + ESLint clean

* fix(core): close 2 QwenLM#3894 review findings — promote refused-race + mkdir orphan

Resolves @tanzhenxin's CHANGES_REQUESTED review on QwenLM#3894.

1. **Refused-promote race no longer reported as "Command timed out"**

The combined-abort signal folds in `signal | timeoutSignal |
promoteAbortController.signal`, but the timeout discriminator only
excluded the user-cancel signal — not the promote signal. When the
user fires Ctrl+B (PR-3's keybind) but the service's race guard
refuses promotion (the child terminated a beat earlier), the result
lands `aborted: true, promoted: false` and the foreground path
falsely reported `Command timed out after 120000ms`. Both the agent
and the user would see a timeout that didn't happen.

Fix: extend the discriminator to ALSO exclude
`promoteAbortController.signal.aborted`. Add a `wasPromoteRefused`
branch that surfaces the actual cause: "Command finished before the
background-promote request could be honoured (the child had already
exited)." Same fix applied to both the llmContent path and the
returnDisplay path so the model and the visible UI agree.

Latent in PR-2 itself (no in-tree caller fires the promote yet), but
PR-3's keybind would expose it on first ship.

2. **Unguarded mkdirSync orphans the promoted child**

After `result.promoted: true`, ownership of the still-running child
has transferred and the service's kill path is detached. The promote
handler creates the snapshot output directory next, but the original
`fs.mkdirSync(outputDir, { recursive: true })` had no guard — read-
only temp mounts, sandboxed perms, full disk on inode/metadata
exhaustion would reject the handler BEFORE the registry's kill
listener was wired. The still-running child became an orphan zombie
with no kill path until the OS reaped it on session end.

Fix: wrap mkdirSync in try/catch (matches the safety pattern around
`registry.register`). On failure, log + best-effort kill the child
(SIGTERM via process.kill(-pid) on POSIX, taskkill /f /t on Windows
with an `error` listener so a spawn failure doesn't crash Node) +
re-throw so the scheduler surfaces the failure to the agent.

Tests: 2 new regressions in `shell.test.ts`:
- `mkdirSync(outputDir) throws → child gets SIGTERM, error re-raised`
- `promote-refused race (aborted: true, promoted: false after promote
  signal) is NOT reported as "Command timed out"`

171/171 shell.test.ts pass; tsc + ESLint clean.
xaelistic pushed a commit to xaelistic/qwen-code that referenced this pull request Jun 7, 2026
xaelistic pushed a commit to xaelistic/qwen-code that referenced this pull request Jun 7, 2026
…-up to QwenLM#3842) (QwenLM#3886)

* fix(core): wrap hasOwnProperty.call inside try + add post-abort PTY data assertion

Two non-blocking review notes from @tanzhenxin's PR-1 approval
(QwenLM#3842, post-merge follow-up):

- **Note 2 (real bug)**: `getShellAbortReasonKind` had
  `Object.prototype.hasOwnProperty.call(reason, 'kind')` outside the
  try/catch. `hasOwnProperty.call` triggers the `[[GetOwnProperty]]`
  Proxy trap (`getOwnPropertyDescriptor` handler). A Proxy whose
  `getOwnPropertyDescriptor` throws — separate from a throwing `get`
  trap, which the prior commit already covered — would propagate past
  the helper, leaving the abort handler's switch on `kind` to throw
  through `addEventListener` (which doesn't await async listener
  return values), so the shell process would stay alive instead of
  being killed on cancel. Moved the descriptor probe inside the same
  try block as the value read.

  My own multi-round audit covered six attack vectors but missed this
  one — only `get` trap throws were considered. The reviewer caught
  it on first pass.

- **Note 3 (test parity)**: the PTY post-promotion handoff test
  asserted `dispose` was called but never re-invoked the data
  callback to verify the foreground `onOutputEvent` actually stops
  firing — the child_process equivalent has that assertion. Mirrored
  it: emit data AFTER abort by re-invoking the captured `dataCallback`
  reference, and assert `onOutputEventMock.mock.calls.length` does NOT
  increase past the moment of promote. Exercises the production
  `listenersDetached` guard inside the chain callback, which the
  bare dispose-was-called check didn't.

Note 1 from the same review (the `aborted: true + promoted: true`
shape forcing PR-2 callers to check `promoted` before `aborted`) is
deliberately NOT addressed here — it's a contract simplification that
affects PR-2's branching, so it belongs in PR-2 along with the
caller-side decision on whether to flip `aborted` for promoted
results. Added a TODO upstream in QwenLM#3831 (PR-2 design) to track.

70 / 70 tests pass (69 baseline + 1 new helper boundary for the
throwing-getOwnPropertyDescriptor case). tsc + ESLint clean.

* test(core): fix tautological PTY post-promote assertion (audit follow-up)

The PTY post-promotion handoff test added in the previous commit
copied the child_process equivalent's pattern verbatim — sync
\`expect(count).toBe(countAtPromote)\` immediately after dataCallback
returns. That works for child_process because its \`handleOutput\` is
fully synchronous (sniff → decoder → emit, all on the same call
stack), so the count change happens BEFORE the assertion.

PTY's \`handleOutput\` is async — \`processingChain.then(...)\` queues a
microtask that does the sniff + write + render-then-emit work. The
sync assertion captures both \`countAtPromote\` and the post-emit count
BEFORE the chain microtask ever runs, so both reads return whatever
happened before the assert (typically 0). The test would
tautologically pass even if the production \`listenersDetached\` guard
were removed — i.e., it didn't actually verify the guard.

Restructured to:
1. Drive the PTY through \`simulateExecution\` so \`await handle.result\`
   forces all queued microtasks (including pre-promote chain items
   AND the abort handler's drain) to settle.
2. Capture \`eventCountAfterSettle\` once everything has stabilized.
3. Re-invoke the captured \`dataCallback\` with post-promote data,
   await two more macrotask boundaries to let the new chain item
   fully run.
4. Assert the count hasn't moved.

If the production \`listenersDetached\` guard is removed, the
post-promote chain item emits, count increases past
\`eventCountAfterSettle\`, and this assertion fails. So the test
actually exercises the guard now.

Found in self-audit while reviewing my own follow-up commit. Caught
because audit was paranoid about *whether the test verifies what it
claims to verify*, not just whether it passes.

70 / 70 tests pass; tsc + ESLint clean.

* test(core): pin eventCountAfterSettle === 0 in PTY post-promote test

The previous fix asserted post-promote count equals the
\`eventCountAfterSettle\` baseline, but didn't pin the baseline
itself. With the production \`listenersDetached\` guard intact, both
halves (pre-promote chain and post-promote chain) suppress emit, so
\`eventCountAfterSettle === 0 === post-count\` and the relative
comparison is vacuously true.

If a future refactor changed the production guard semantics so the
pre-promote chain item DID emit (count becomes 1+ after settle), the
relative-comparison test would still pass as long as post-promote
also emitted the same number — that's a regression the test should
catch. Adding \`expect(eventCountAfterSettle).toBe(0)\` makes the
contract explicit: once \`listenersDetached\` is set during the abort
handler's sync part, BOTH the in-flight chain item (pre-promote) and
the future chain item (post-promote) skip emit.

Found in another self-audit pass — even after fixing the tautological
assertion, the test could still mask certain future regressions.
70 / 70 tests pass; tsc + ESLint clean.

* test(core): drop dataCallbackHolder pattern, read mock.calls directly

The dataCallbackHolder pattern (capturing the onData callback inside
simulateExecution then invoking it after via a closure-shared object)
was unnecessary indirection — \`mockPtyProcess.onData.mock.calls[0][0]\`
reads the same callback reference whether you read it inside or outside
the simulation closure. Vi's mock.calls array is per-mock-instance and
beforeEach re-creates mockPtyProcess + .onData freshly, so there's no
stale-reference risk in the simpler form.

No behavior change in what's tested. 70 / 70 pass.

* chore(core): drop in-source @-mention attribution + dead Proxy.get handler

Two cosmetic cleanups found in another self-audit pass:

- **Helper comment**: removed the parenthetical attribution ("Caught
  by @tanzhenxin in the PR-1 review; my own audit only covered \`get\`
  trap throws."). The technical content of the comment — explaining
  why both the descriptor probe and the value read live inside the
  try — stands on its own. The reviewer credit lives in commit
  history / PR description, where it belongs; an in-source @-mention
  ages poorly (handles change, the relevant person may move on) and
  doesn't help future readers reason about the code.

- **Test Proxy**: \`throwingDescriptorProxy\` declared a \`get()\` handler
  that always returned \`undefined\`. The descriptor probe throws
  before the helper ever reaches the value read, so the \`get\`
  handler is unreachable — dropped it and added a one-line comment
  explaining why no \`get\` handler is needed for this test. Mirror
  test (\`throwingReason\` with throwing accessor + \`proxyReason\` with
  throwing \`get\`) keeps the symmetric "throwing-`get`" coverage.

70 / 70 tests pass; tsc + ESLint clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type/bug Something isn't working as expected

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants