Skip to content

feat(ide): add daemon connection spike - #4199

Merged
wenshao merged 8 commits into
mainfrom
feat/ide-daemon-adapter
May 18, 2026
Merged

feat(ide): add daemon connection spike#4199
wenshao merged 8 commits into
mainfrom
feat/ide-daemon-adapter

Conversation

@chiga0

@chiga0 chiga0 commented May 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • What changed: Added a locally verifiable DaemonIdeConnection spike in the VS Code extension host, plus unit coverage for daemon session creation, SSE event consumption, prompt forwarding, permission responses, cancel, model switch, and session death handling.
  • Why it changed: IDE can start dogfooding Mode B through httpServer + SSE without moving the default VS Code ACP subprocess path yet.
  • Reviewer focus: Extension-host boundary, compatibility with existing ACP callback shapes, default-off behavior, and whether the unsupported gaps are explicit enough before wiring this into QwenAgentManager.

Validation

  • Commands run:
    cd packages/vscode-ide-companion && npx vitest run src/services/daemonIdeConnection.test.ts
    cd packages/core && npm run build
    cd packages/vscode-ide-companion && npm run check-types
    cd packages/vscode-ide-companion && npx eslint src/services/daemonIdeConnection.ts src/services/daemonIdeConnection.test.ts --max-warnings 0
    cd packages/vscode-ide-companion && npx prettier --check src/services/daemonIdeConnection.ts src/services/daemonIdeConnection.test.ts ../../docs/developers/daemon-client-adapters/ide.md
    cd packages/vscode-ide-companion && npm run build
  • Prompts / inputs used: Unit tests use a fake daemon session event queue that emits session_update, permission_request, and session_died frames.
  • Expected result: IDE adapter spike can be verified locally without a live daemon and without changing the existing VS Code default path.
  • Observed result: Targeted vitest passed with 5 tests. Targeted ESLint and Prettier passed. VS Code companion npm run build passed; it still reports the pre-existing src/utils/editorGroupUtils.ts curly warning and stale Browserslist data.
  • Quickest reviewer verification path: Run cd packages/vscode-ide-companion && npx vitest run src/services/daemonIdeConnection.test.ts.
  • Evidence: The targeted test output reports src/services/daemonIdeConnection.test.ts (5 tests) and Test Files 1 passed.

Scope / Risk

  • Main risk or tradeoff: This is intentionally a spike adapter. It proves the extension-host daemon transport surface, but does not yet wire the feature flag, settings/env resolution, or webview flow.
  • Not covered / not validated: No live qwen serve smoke, no QwenAgentManager switch, no session list/load/delete parity, no daemon set-mode route, no IDE file-service boundary, and no reverse RPC for local editor/browser/clipboard.
  • Breaking changes / migration notes: None. Existing VS Code ACP subprocess behavior is unchanged and remains the default.

Testing Matrix

🍏 🪟 🐧
npm run ⚠️ ⚠️
npx ⚠️ ⚠️
Docker N/A N/A N/A
Podman N/A N/A N/A
Seatbelt N/A N/A N/A

Testing matrix notes:

  • Local validation was run on macOS only.
  • This PR does not touch sandbox runtime behavior.

Linked Issues / Bugs

Related to #3803, #4175, and #4201.
Supersedes the docs-only IDE draft #4198 with a locally verifiable adapter spike.

@github-actions

Copy link
Copy Markdown
Contributor

📋 Review Summary

This PR introduces a well-structured DaemonIdeConnection adapter that enables the VS Code extension to dogfood Mode B via HTTP/SSE without disrupting the existing ACP subprocess default path. The implementation is solid, with comprehensive unit tests covering session creation, SSE event consumption, prompt forwarding, permission handling, cancel/model-switch operations, and session death scenarios. The documentation clearly articulates scope, boundaries, and merge safety guarantees.

🔍 General Feedback

  • Clean architecture: The spike adapter is correctly positioned as a sibling to AcpConnection, maintaining backward compatibility while enabling incremental dogfooding.
  • Strong test coverage: Five focused unit tests validate the critical event flows without requiring a live daemon, which is exactly right for a spike.
  • Explicit scoping: The documentation excels at defining what's in scope (extension-host daemon transport surface) versus what's deferred (live smoke tests, QwenAgentManager wiring, file-service boundaries).
  • Defensive design: Unknown events are silently ignored, permission requests have safe fallbacks, and the token never crosses into webview JavaScript.
  • TypeScript quality: Code follows project conventions with proper typing, ESM imports, and clear separation of concerns.

🎯 Specific Feedback

🟡 High

  • packages/vscode-ide-companion/src/services/daemonIdeConnection.ts:107-114 — The onPermissionRequest callback defaults to resolving an empty string optionId: '' when resolvePermissionOptionId returns undefined. This could silently pass an invalid option to the daemon. Consider throwing or logging a warning when no valid option is found, rather than passing an empty string.

  • packages/vscode-ide-companion/src/services/daemonIdeConnection.ts:252-258 — The pumpEvents error handler sets this.session = null on stream failure, but doesn't abort the event controller first. If the error is transient and the controller is still active, this could leave a zombie listener. Consider calling this.eventController?.abort() before clearing the session reference.

🟢 Medium

  • packages/vscode-ide-companion/src/services/daemonIdeConnection.ts:96-104 — The dynamicImport function uses a Function constructor to bypass TypeScript's static import analysis. While this is necessary for lazy-loading the SDK, it bypasses some security policies (CSP). Add a comment explaining why this pattern is required here, or consider documenting the CSP implications for extension-host deployment.

  • packages/vscode-ide-companion/src/services/daemonIdeConnection.ts:146-154 — The sendPrompt method accepts both string and ContentBlock[], converting strings inline. This duplication of the conversion logic is minor, but consider extracting it to a private helper (normalizePrompt(prompt: string | ContentBlock[]): ContentBlock[]) for clarity and testability.

  • packages/vscode-ide-companion/src/services/daemonIdeConnection.test.ts:64-76 — The FakeSession interface casts vi.fn() results to match the real session methods. This works, but the cast hides potential signature mismatches. Consider making the fake session implement DaemonIdeSessionClient directly (or using satisfies) to catch drift if the interface changes.

  • docs/developers/daemon-client-adapters/ide.md:22-27 — The environment fallback example shows QWEN_IDE_DAEMON_URL but the code reads options.token from settings. Document whether the env var is read by the extension settings layer or if this is a future TODO.

🔵 Low

  • packages/vscode-ide-companion/src/services/daemonIdeConnection.ts:1 — Missing a top-level module docblock explaining this file's purpose and relationship to AcpConnection. The PR body has this context, but future maintainers reading the file in isolation would benefit from a 2-3 sentence header comment.

  • packages/vscode-ide-companion/src/services/daemonIdeConnection.ts:115-124 — The resolvePermissionOptionId function has a fallback chain with four tiers. The third tier (options.find((option) => option.optionId.includes('proceed_once'))) is a fuzzy match that could pick an unexpected option if IDs follow unusual naming. Consider adding a comment explaining why this fallback exists (e.g., "Fallback for non-standard option IDs from older daemon versions").

  • packages/vscode-ide-companion/src/services/daemonIdeConnection.test.ts:1 — The test file has a copyright header but no @license tag matching the source file's format. Minor consistency nit.

  • docs/developers/daemon-client-adapters/ide.md:97-104 — The "Validation Plan" checkbox list is great, but it would help to mark which items are done in this PR versus future work. Consider adding [x] for completed items (unit tests for session factory, event consumption, prompt/cancel/permission forwarding) and [ ] for deferred items (settings/env resolution smoke tests).

✅ Highlights

  • Excellent documentation: The ide.md doc is a model for spike adapters—clear goals, explicit non-goals, event mapping table, runtime locality UX warnings, and merge safety checklist. This makes review and future onboarding significantly easier.
  • Smart test design: The EventQueue fake generator elegantly simulates async SSE streams without needing a live daemon. The waitFor helper avoids flaky timing issues. These patterns are reusable for future daemon-related tests.
  • Conservative event handling: Unknown event types are silently ignored (line 268), which is the right choice for forward-compatibility with daemon evolution.
  • Permission safety: The code handles both RequestPermissionRequest and AskUserQuestionRequest paths, with a clear "cancel means reject" policy. The fallback chain for option selection is defensive and explicit.
  • No breaking changes: The PR correctly keeps the default VS Code path on AcpConnection, making this an opt-in experimental feature. This is exactly the right rollout strategy for a spike.

@wenshao wenshao 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.

Additional findings (not mappable to specific diff lines):

[Critical] Critical error paths untested: pumpEvents non-abort error path, handlePermissionRequest malformed data guard, and ensureSession() error throw have zero test coverage — these are the most likely production failure modes.

[Suggestion] Permission logic duplicated from AcpConnection: resolvePermissionOptionId, isCancelledOption, and parts of resolvePermissionResponse are copied verbatim from AcpConnection. Bug fixes or policy changes in one path won't propagate. Extract shared utilities or add a TODO explaining intentional duplication.

[Suggestion] Test gaps: createSdkDaemonSessionFactory (dynamic import failure), AskUserQuestion routing (~15 lines), resolvePermissionOptionId fallback chain (4 tiers), and handleSessionDied default reason are untested.

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Comment thread packages/vscode-ide-companion/src/services/daemonIdeConnection.ts
Comment thread packages/vscode-ide-companion/src/services/daemonIdeConnection.ts
Comment thread packages/vscode-ide-companion/src/services/daemonIdeConnection.ts
Comment thread packages/vscode-ide-companion/src/services/daemonIdeConnection.ts
Comment thread packages/vscode-ide-companion/src/services/daemonIdeConnection.ts
Comment thread packages/vscode-ide-companion/src/services/daemonIdeConnection.ts
Comment thread packages/vscode-ide-companion/src/services/daemonIdeConnection.ts
Comment thread packages/vscode-ide-companion/src/services/daemonIdeConnection.ts
Comment thread packages/vscode-ide-companion/src/services/daemonIdeConnection.ts Outdated
Comment thread packages/vscode-ide-companion/src/services/daemonIdeConnection.ts

@wenshao wenshao 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.

Second-opinion review with glm-5.1 (prior review used DeepSeek/deepseek-v4-pro). Found 3 new high-confidence issues not in the prior review:

  1. [Critical] AskUserQuestion answers silently stripped by ACP schema — the answers field is attached to RequestPermissionResponse but the daemon-side Zod schema only defines _meta and outcome. Answers are stripped, causing silent data loss.

  2. [Critical] Test mock returns bare string instead of { optionId } object — masks that the callback return value is never actually propagated. The test passes only because the fallback chain coincidentally resolves to the same value.

  3. [Critical] AskUserQuestion and isCancelledOption branches have zero test coverage — two security-critical code paths are completely untested.

Comment thread packages/vscode-ide-companion/src/services/daemonIdeConnection.ts
Comment thread packages/vscode-ide-companion/src/services/daemonIdeConnection.ts
@chiga0
chiga0 force-pushed the feat/ide-daemon-adapter branch from 49d5ee4 to 3a76d74 Compare May 16, 2026 15:18
@chiga0

chiga0 commented May 16, 2026

Copy link
Copy Markdown
Collaborator Author

处理了这轮 review:

  • fixed: 默认 onPermissionRequest 不再 fallback 到 allow/proceed,默认返回 cancel,避免调用方忘记覆盖时自动放行
  • fixed: daemon baseUrl 做 http/https scheme 校验并禁止 URL credentials
  • fixed: connect 会等待旧 disconnect;disconnect 会 abort 并等待 event pump 退出
  • fixed: pump 使用 lastEventId/resume,事件处理成功后再更新 lastSeenEventId
  • fixed: 单个 event handler 异常只记录安全 message,不杀掉整条 SSE;stream error 和 stream ended 都会清理连接状态并回调 onDisconnected
  • fixed: console.warn 不再打印原始 error 对象,避免潜在 header/token 泄漏
  • fixed: permission_request 校验 requestId/toolCall/options,daemon 拒绝 permission response 时记录 warning
  • fixed: AskUserQuestion 空 optionId 现在取消,不会 fallback 到 proceed_once;reject/cancel 改为精确匹配 reject/reject_* / cancel,不再任意 substring
  • fixed: dynamic import 增加说明;prompt block 归一化抽成 helper
  • tests: 补了默认 cancel、reject cancel、AskUserQuestion answers/empty selection、malformed permission、stream failure/stream ended、baseUrl validation 等路径;原先 bare string mock 也修成 { optionId }

误报/澄清:

  • AskUserQuestion answers 顶层字段不会被当前 daemon HTTP route strip。server.ts 的 /permission/:requestId 会 spread body 后传给 bridge,SDK PermissionResponse 也允许 passthrough fields,ACP Session 侧读取的是 output.answers。这里保留顶层 answers,并加了注释和测试覆盖。
  • lastEventId 在 disconnect 后保留是有意行为:它是 reconnect/resume 水位,不是活跃 session 状态;清掉反而会让 IDE 断线重连丢 replay 能力。

验证:

  • cd packages/vscode-ide-companion && npx vitest run src/services/daemonIdeConnection.test.ts
  • cd packages/vscode-ide-companion && npm run check-types
  • cd packages/vscode-ide-companion && npx eslint src/services/daemonIdeConnection.ts src/services/daemonIdeConnection.test.ts --max-warnings 0 --no-warn-ignored
  • cd packages/vscode-ide-companion && npm run build 成功;仍有仓库既有 warning:src/utils/editorGroupUtils.ts curly

@chiga0
chiga0 force-pushed the feat/daemon-session-client branch from 4f49825 to 1fe2b04 Compare May 16, 2026 15:29
Comment thread packages/vscode-ide-companion/src/services/daemonIdeConnection.ts Outdated
Comment thread packages/sdk-typescript/src/daemon/DaemonSessionClient.ts
Comment thread packages/vscode-ide-companion/src/services/daemonIdeConnection.ts Outdated
Comment thread packages/vscode-ide-companion/src/services/daemonIdeConnection.ts Outdated
@chiga0
chiga0 force-pushed the feat/daemon-session-client branch from 1fe2b04 to da6a35c Compare May 16, 2026 16:40
@chiga0
chiga0 force-pushed the feat/ide-daemon-adapter branch from 3a76d74 to d0217f1 Compare May 17, 2026 02:03
@chiga0
chiga0 changed the base branch from feat/daemon-session-client to main May 17, 2026 02:03
@chiga0

chiga0 commented May 17, 2026

Copy link
Copy Markdown
Collaborator Author

Follow-up update after rebasing this PR to main:

  • Rebased/retargeted onto main; PR is mergeable and remains draft while CI runs.
  • Addressed the valid IDE adapter review comments:
    • default permission handling now cancels instead of auto-approving;
    • daemon base URL validates http/https and rejects credentials;
    • connect/disconnect waits for the previous event pump;
    • event handler failures are isolated and logged with safe messages;
    • replay cursor now advances in finally, even if an event handler throws;
    • stream failure/normal completion clears connection state and emits disconnect;
    • permission response rejection is surfaced as a safe warning;
    • invalid non-empty preferred option ids now cancel instead of falling back to allow/proceed;
    • @qwen-code/sdk is now an explicit runtime dependency for the dynamic default factory;
    • tests cover default cancel, reject/cancel, AskUserQuestion answers/empty selection, malformed permission events, stream failure/completion, baseUrl validation, handler failure replay, and invalid preferred options.

False-positive / clarified points already covered in code/commentary:

  • answers are intentionally top-level because the daemon permission route preserves passthrough fields and the ACP session consumes output.answers there.
  • lastEventId intentionally survives disconnect as the reconnect/resume cursor.

Local validation passed:

  • cd packages/vscode-ide-companion && npx vitest run src/services/daemonIdeConnection.test.ts
  • cd packages/vscode-ide-companion && npm run check-types
  • cd packages/vscode-ide-companion && npx eslint src/services/daemonIdeConnection.ts src/services/daemonIdeConnection.test.ts --max-warnings 0 --no-warn-ignored
  • cd packages/vscode-ide-companion && npx prettier --check src/services/daemonIdeConnection.ts src/services/daemonIdeConnection.test.ts package.json
  • cd packages/vscode-ide-companion && npm run build (passes; existing unrelated warning remains in src/utils/editorGroupUtils.ts)

I resolved the addressed review threads; waiting for CI before considering this ready to undraft.

@github-actions

github-actions Bot commented May 17, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Summary

Package Lines Statements Functions Branches
CLI 77.27% 77.27% 78.87% 80.4%
Core 79.27% 79.27% 81.92% 82.75%
CLI Package - Full Text Report
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   77.27 |     80.4 |   78.87 |   77.27 |                   
 src               |   75.73 |    69.15 |   80.55 |   75.73 |                   
  gemini.tsx       |   68.53 |     66.4 |   76.47 |   68.53 | ...29,946-949,957 
  ...ractiveCli.ts |      80 |    68.61 |   78.57 |      80 | ...1020,1058,1161 
  ...liCommands.ts |   74.51 |     72.5 |     100 |   74.51 | ...41-265,290,391 
  ...ActiveAuth.ts |     100 |     87.5 |     100 |     100 | 66-80             
 ...cp-integration |   67.53 |    66.53 |   79.03 |   67.53 |                   
  acpAgent.ts      |   69.38 |    66.66 |   84.21 |   69.38 | ...1691,1705-1713 
  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 |   76.97 |    72.12 |   86.25 |   76.97 |                   
  ...ryReplayer.ts |   67.34 |     75.6 |   81.81 |   67.34 | ...54-269,282-283 
  Session.ts       |   76.32 |    70.86 |   88.46 |   76.32 | ...2537,2543-2546 
  ...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 |   90.36 |    87.83 |   94.11 |   90.36 |                   
  LlmRewriter.ts   |      81 |       84 |     100 |      81 | ...,88-89,155-159 
  ...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/auth          |    97.7 |    94.81 |   95.45 |    97.7 |                   
  allProviders.ts  |     100 |      100 |     100 |     100 |                   
  ...iderConfig.ts |    97.6 |    95.04 |     100 |    97.6 | ...61,411,433-434 
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 src/auth/install  |   98.57 |    88.88 |     100 |   98.57 |                   
  ...nstallPlan.ts |   98.57 |    88.88 |     100 |   98.57 | 80,93             
 ...viders/alibaba |   96.96 |    66.66 |   66.66 |   96.96 |                   
  ...baStandard.ts |     100 |      100 |     100 |     100 |                   
  codingPlan.ts    |   93.67 |    66.66 |   66.66 |   93.67 | 83,87-89,94       
  tokenPlan.ts     |     100 |      100 |     100 |     100 |                   
 ...oviders/custom |     100 |      100 |     100 |     100 |                   
  ...omProvider.ts |     100 |      100 |     100 |     100 |                   
 ...roviders/oauth |    91.5 |    77.03 |   97.05 |    91.5 |                   
  openrouter.ts    |   84.37 |    33.33 |     100 |   84.37 | 43-48             
  ...outerOAuth.ts |    91.9 |    79.06 |   96.87 |    91.9 | ...53-655,699-701 
 ...ers/thirdParty |     100 |      100 |     100 |     100 |                   
  deepseek.ts      |     100 |      100 |     100 |     100 |                   
  idealab.ts       |     100 |      100 |     100 |     100 |                   
  minimax.ts       |     100 |      100 |     100 |     100 |                   
  modelscope.ts    |     100 |      100 |     100 |     100 |                   
  zai.ts           |     100 |      100 |     100 |     100 |                   
 src/commands      |   55.55 |    85.71 |   43.47 |   55.55 |                   
  auth.ts          |     100 |    83.33 |     100 |     100 | 11,14             
  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          
  serve.ts         |    10.3 |      100 |       0 |    10.3 | ...48-123,125-164 
 ...mmands/channel |   39.25 |    79.45 |      50 |   39.25 |                   
  ...l-registry.ts |    8.57 |      100 |       0 |    8.57 | 6-21,24-42        
  config-utils.ts  |      92 |      100 |   66.66 |      92 | 21-26             
  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         |   30.98 |       52 |   69.23 |   30.98 | ...72-475,484-486 
  status.ts        |   17.85 |      100 |       0 |   17.85 | 15-26,32-76       
  stop.ts          |      20 |      100 |       0 |      20 | 14-48             
 ...nds/extensions |    84.5 |    88.95 |   81.81 |    84.5 |                   
  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.89 |    85.31 |   88.09 |   92.89 |                   
  auth.ts          |   86.98 |    80.32 |     100 |   86.98 | ...26-227,243-244 
  config.ts        |   88.68 |     85.1 |      80 |   88.68 | ...1826,1828-1836 
  keyBindings.ts   |   96.55 |       50 |     100 |   96.55 | 193-196           
  ...idersScope.ts |      92 |       90 |     100 |      92 | 11-12             
  sandboxConfig.ts |   61.64 |    71.87 |   66.66 |   61.64 | ...54-68,73,77-89 
  settings.ts      |   85.76 |    87.25 |   89.18 |   85.76 | ...1148,1153-1156 
  ...ingsSchema.ts |     100 |      100 |     100 |     100 |                   
  ...tedFolders.ts |   96.22 |       94 |     100 |   96.22 | ...88-190,205-206 
 ...nfig/migration |   94.89 |    78.94 |   83.33 |   94.89 |                   
  index.ts         |   94.87 |    88.88 |     100 |   94.87 | 91-92             
  scheduler.ts     |   96.55 |    77.77 |     100 |   96.55 | 19-20             
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...ation/versions |   94.74 |       96 |     100 |   94.74 |                   
  ...-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 |                   
  v3-to-v4.ts      |     100 |      100 |     100 |     100 |                   
 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          |   81.47 |    75.94 |   65.71 |   81.47 |                   
  index.ts         |   63.68 |    69.56 |   53.84 |   63.68 | ...70-271,281-286 
  languages.ts     |   96.92 |    86.66 |     100 |   96.92 | 134-135,167,184   
  ...nslateKeys.ts |     100 |      100 |     100 |     100 |                   
  ...lationDict.ts |   93.33 |    66.66 |     100 |   93.33 | 15                
 src/i18n/locales  |     100 |      100 |     100 |     100 |                   
  ca.js            |     100 |      100 |     100 |     100 |                   
  de.js            |     100 |      100 |     100 |     100 |                   
  en.js            |     100 |      100 |     100 |     100 |                   
  fr.js            |     100 |      100 |     100 |     100 |                   
  ja.js            |     100 |      100 |     100 |     100 |                   
  pt.js            |     100 |      100 |     100 |     100 |                   
  ru.js            |     100 |      100 |     100 |     100 |                   
  zh-TW.js         |     100 |      100 |     100 |     100 |                   
  zh.js            |     100 |      100 |     100 |     100 |                   
 ...nonInteractive |   72.57 |    71.12 |   74.07 |   72.57 |                   
  session.ts       |   76.64 |     69.4 |   85.71 |   76.64 | ...23-824,833-843 
  types.ts         |    42.5 |      100 |   33.33 |    42.5 | ...80-581,584-585 
 ...active/control |   77.04 |    88.23 |      80 |   77.04 |                   
  ...rolContext.ts |    7.14 |        0 |       0 |    7.14 | 49-84             
  ...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.98 |    93.72 |   95.18 |   97.98 |                   
  ...putAdapter.ts |   97.89 |    92.82 |   98.07 |   97.89 | ...1303,1398-1399 
  ...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/serve         |   84.45 |    81.94 |   94.11 |   84.45 |                   
  auth.ts          |   88.49 |    88.37 |    87.5 |   88.49 | ...49-150,153-155 
  capabilities.ts  |     100 |     90.9 |     100 |     100 | 153               
  envSnapshot.ts   |    92.3 |       84 |     100 |    92.3 | 108-111,170-177   
  eventBus.ts      |   88.88 |    89.23 |   85.71 |   88.88 | ...38-446,524-526 
  httpAcpBridge.ts |    81.4 |    77.88 |   97.82 |    81.4 | ...4188,4219-4260 
  ...oryChannel.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  loopbackBinds.ts |     100 |      100 |     100 |     100 |                   
  runQwenServe.ts  |   79.74 |    87.09 |   83.33 |   79.74 | ...51-467,492-494 
  server.ts        |   85.63 |    83.51 |    87.5 |   85.63 | ...1529,1594-1603 
  status.ts        |   98.33 |    96.66 |     100 |   98.33 | 365-366           
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/services      |   91.67 |    91.21 |   97.56 |   91.67 |                   
  ...mandLoader.ts |     100 |    93.75 |     100 |     100 | 93                
  ...killLoader.ts |     100 |    96.15 |     100 |     100 | 47                
  ...andService.ts |    98.7 |      100 |     100 |    98.7 | 107               
  ...mandLoader.ts |   86.83 |    83.87 |     100 |   86.83 | ...30-335,340-345 
  ...omptLoader.ts |   75.84 |    80.64 |   83.33 |   75.84 | ...10-211,277-278 
  ...mandLoader.ts |     100 |      100 |     100 |     100 |                   
  ...nd-factory.ts |   91.42 |    91.66 |     100 |   91.42 | 128,137-144       
  ...ation-tool.ts |     100 |    95.45 |     100 |     100 | 125               
  ...ndMetadata.ts |   98.21 |    96.66 |     100 |   98.21 | 83,87             
  commandUtils.ts  |      96 |     90.9 |     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.9 |    85.61 |   90.47 |    85.9 |                   
  DataProcessor.ts |   85.63 |     85.6 |   92.85 |   85.63 | ...1122,1126-1133 
  ...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            |   66.51 |    73.28 |   57.89 |   66.51 |                   
  App.tsx          |     100 |      100 |     100 |     100 |                   
  AppContainer.tsx |   65.03 |    64.98 |   52.94 |   65.03 | ...2951,2955-2959 
  ...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   |   95.91 |    97.05 |     100 |   95.91 | 25-26             
  ...tic-colors.ts |     100 |      100 |     100 |     100 |                   
  ...inePresets.ts |   98.17 |    88.88 |     100 |   98.17 | ...12,239,387-389 
  textConstants.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/ui/auth       |   55.06 |    51.13 |   35.48 |   55.06 |                   
  AuthDialog.tsx   |   64.26 |    44.44 |   16.66 |   64.26 | ...59,366-388,392 
  ...nProgress.tsx |       0 |        0 |       0 |       0 | 1-64              
  ...etupSteps.tsx |    39.5 |       32 |   38.46 |    39.5 | ...69,472,478,481 
  useAuth.ts       |   76.63 |    68.29 |     100 |   76.63 | ...48,493-499,560 
  ...rSetupFlow.ts |   44.61 |    33.33 |      50 |   44.61 | ...57-378,395-438 
 src/ui/commands   |   73.46 |    81.23 |   81.61 |   73.46 |                   
  aboutCommand.ts  |     100 |      100 |     100 |     100 |                   
  agentsCommand.ts |   83.78 |      100 |      60 |   83.78 | 30-32,42-44       
  ...odeCommand.ts |     100 |      100 |     100 |     100 |                   
  arenaCommand.ts  |   62.81 |    58.73 |   65.21 |   62.81 | ...91-596,681-689 
  authCommand.ts   |     100 |      100 |     100 |     100 |                   
  branchCommand.ts |     100 |      100 |     100 |     100 |                   
  btwCommand.ts    |   95.59 |    71.42 |     100 |   95.59 | 72,154-159        
  bugCommand.ts    |   81.13 |    71.42 |     100 |   81.13 | 60-69             
  clearCommand.ts  |      92 |    76.47 |     100 |      92 | 43-44,72-73,91-92 
  ...essCommand.ts |    64.7 |       50 |      75 |    64.7 | ...48-149,163-166 
  ...extCommand.ts |   34.78 |    22.22 |   45.45 |   34.78 | ...86-521,532-533 
  copyCommand.ts   |   98.28 |    94.89 |     100 |   98.28 | ...80,280,321,327 
  deleteCommand.ts |     100 |      100 |     100 |     100 |                   
  diffCommand.ts   |   99.02 |    86.11 |     100 |   99.02 | 222,226           
  ...ryCommand.tsx |   68.09 |    77.77 |   77.77 |   68.09 | ...56-261,315-323 
  docsCommand.ts   |     100 |    88.88 |     100 |     100 | 25                
  doctorCommand.ts |   95.06 |    88.28 |     100 |   95.06 | ...92-293,320-321 
  dreamCommand.ts  |      75 |    66.66 |   66.66 |      75 | 22-27,44-47       
  editorCommand.ts |     100 |      100 |     100 |     100 |                   
  exportCommand.ts |   98.25 |    91.02 |     100 |   98.25 | ...81,198-199,364 
  ...onsCommand.ts |   48.66 |     90.9 |   63.63 |   48.66 | ...05-109,159-211 
  forgetCommand.ts |   26.82 |      100 |      50 |   26.82 | 18-51             
  goalCommand.ts   |   91.25 |    83.33 |      90 |   91.25 | ...83-186,198-201 
  helpCommand.ts   |     100 |      100 |     100 |     100 |                   
  hooksCommand.ts  |    20.4 |       40 |      40 |    20.4 | ...48-180,204-205 
  ideCommand.ts    |   60.75 |    64.28 |   41.17 |   60.75 | ...05-306,310-324 
  initCommand.ts   |   84.33 |    72.72 |     100 |   84.33 | 68,82-87,89-94    
  ...ghtCommand.ts |   74.56 |    68.42 |     100 |   74.56 | ...31-245,250-273 
  ...ageCommand.ts |   92.17 |    82.69 |     100 |   92.17 | ...43,164,173-183 
  lspCommand.ts    |     100 |    86.95 |     100 |     100 | 31,101-102        
  ...elsCommand.ts |     100 |      100 |     100 |     100 |                   
  mcpCommand.ts    |     100 |      100 |     100 |     100 |                   
  memoryCommand.ts |     100 |      100 |     100 |     100 |                   
  modelCommand.ts  |   75.09 |    78.18 |      75 |   75.09 | ...20-225,262-267 
  ...onsCommand.ts |     100 |      100 |     100 |     100 |                   
  planCommand.ts   |   78.82 |    76.92 |     100 |   78.82 | 30-35,51-56,68-73 
  quitCommand.ts   |     100 |      100 |     100 |     100 |                   
  recapCommand.ts  |   21.81 |      100 |      50 |   21.81 | 24-73             
  ...berCommand.ts |   32.43 |      100 |      50 |   32.43 | 23-57             
  renameCommand.ts |   85.71 |    86.04 |     100 |   85.71 | ...02-209,216-221 
  ...oreCommand.ts |    92.3 |    87.87 |     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  |   88.19 |    84.21 |     100 |   88.19 | ...,58-61,143-146 
  ...ineCommand.ts |     100 |      100 |     100 |     100 |                   
  ...aryCommand.ts |    6.46 |      100 |      50 |    6.46 | 31-329            
  tasksCommand.ts  |   77.22 |    72.13 |     100 |   77.22 | ...46-150,172-177 
  ...tupCommand.ts |     100 |      100 |     100 |     100 |                   
  themeCommand.ts  |     100 |      100 |     100 |     100 |                   
  toolsCommand.ts  |     100 |      100 |     100 |     100 |                   
  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 |   65.53 |    75.02 |   70.76 |   65.53 |                   
  AboutBox.tsx     |     100 |      100 |     100 |     100 |                   
  AnsiOutput.tsx   |   65.57 |      100 |      50 |   65.57 | 69-90             
  ApiKeyInput.tsx  |       0 |        0 |       0 |       0 | 1-97              
  AppHeader.tsx    |   89.39 |       75 |     100 |   89.39 | 35,37-42,44       
  ...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 |   77.01 |       76 |     100 |   77.01 | ...20,234-236,263 
  Composer.tsx     |    80.8 |     64.7 |     100 |    80.8 | ...85,103,154,167 
  ...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.2 |      100 |       0 |    12.2 | 64-490            
  ...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.54 |    54.54 |     100 |   79.54 | ...05-109,133-134 
  ...ngSpinner.tsx |   68.42 |       80 |      50 |   68.42 | 35-52,73,80-81    
  GoalPill.tsx     |   76.19 |    81.81 |     100 |   76.19 | 24-30,46-50       
  Header.tsx       |   98.62 |    94.28 |     100 |   98.62 | 162,164           
  Help.tsx         |   98.32 |    89.88 |     100 |   98.32 | ...24,381,447-448 
  ...emDisplay.tsx |    61.7 |       36 |     100 |    61.7 | ...42,345,348-354 
  ...ngeDialog.tsx |     100 |      100 |     100 |     100 |                   
  InputPrompt.tsx  |   82.75 |    78.96 |   83.33 |   82.75 | ...1425,1490,1540 
  ...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  |   81.75 |       75 |     100 |   81.75 | ...70-274,282-286 
  ...elsDialog.tsx |   71.05 |    69.11 |   72.72 |   71.05 | ...77,590,601-603 
  MemoryDialog.tsx |    55.1 |    54.54 |   57.14 |    55.1 | ...56,368,381-383 
  ...geDisplay.tsx |       0 |        0 |       0 |       0 | 1-41              
  ModelDialog.tsx  |   80.12 |    63.55 |     100 |   80.12 | ...39-555,612-616 
  ...tsDisplay.tsx |     100 |    97.22 |     100 |     100 | 270               
  ...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 |   92.64 |    85.71 |     100 |   92.64 | 102-106,134-139   
  PrepareLabel.tsx |   91.66 |    77.27 |     100 |   91.66 | 73-75,77-79,110   
  ...atePrompt.tsx |    8.57 |      100 |       0 |    8.57 | 24-55,58-134      
  ...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 |   41.26 |    61.53 |   71.42 |   41.26 | ...74-472,476-520 
  ...ionPicker.tsx |   78.43 |    66.66 |     100 |   78.43 | ...20-422,444-466 
  ...onPreview.tsx |   92.42 |    84.37 |     100 |   92.42 | ...,70-71,143-145 
  ...ryDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...putPrompt.tsx |   72.56 |       80 |      40 |   72.56 | ...06-109,114-117 
  ...ngsDialog.tsx |   66.92 |    73.21 |     100 |   66.92 | ...12-820,826-827 
  ...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 |                   
  ...ineDialog.tsx |   93.69 |    83.92 |     100 |   93.69 | ...11,273,293-295 
  ...yTodoList.tsx |   94.17 |       80 |     100 |   94.17 | 56-57,131-134     
  ...nsDisplay.tsx |   87.25 |       64 |     100 |   87.25 | ...45-147,154-156 
  ThemeDialog.tsx  |   89.95 |    46.15 |      75 |   89.95 | ...71-173,243-245 
  Tips.tsx         |   93.54 |       75 |     100 |   93.54 | 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 |   38.33 |    70.83 |   36.36 |   38.33 |                   
  ...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  |    87.8 |    27.27 |     100 |    87.8 | ...,85,98-106,124 
  ...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 |   75.63 |    84.44 |   85.29 |   75.63 |                   
  ...sksDialog.tsx |   70.92 |    80.39 |   76.19 |   70.92 | ...1118,1194-1196 
  ...TasksPill.tsx |   63.75 |    86.95 |     100 |   63.75 | 44,86-106,114-122 
  ...gentPanel.tsx |   99.53 |    93.18 |     100 |   99.53 | 123               
 ...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.88 |    94.23 |   66.66 |   54.88 |                   
  ...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.43 |    94.73 |      80 |   88.43 | 52-53,59-72,106   
  ...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 |   68.67 |    69.07 |   69.56 |   68.67 |                   
  ...etailStep.tsx |   74.68 |    66.66 |   66.66 |   74.68 | ...71-184,188-201 
  ...etailStep.tsx |    87.4 |    73.68 |     100 |    87.4 | 41-42,99-113,119  
  ...abledStep.tsx |     100 |      100 |     100 |     100 |                   
  ...sListStep.tsx |     100 |      100 |     100 |     100 |                   
  ...entDialog.tsx |   34.51 |    47.05 |   42.85 |   34.51 | ...78,482-495,499 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-13              
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...components/mcp |   20.98 |    86.36 |   83.33 |   20.98 |                   
  ...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         |   95.83 |    88.88 |     100 |   95.83 | 16,20,109-110     
 ...ents/mcp/steps |   26.74 |    54.54 |   42.85 |   26.74 |                   
  ...icateStep.tsx |    5.88 |      100 |       0 |    5.88 | 40-55,58-296      
  ...electStep.tsx |   10.95 |      100 |       0 |   10.95 | 16-88             
  ...etailStep.tsx |    5.26 |      100 |       0 |    5.26 | 31-247            
  ...rListStep.tsx |   75.18 |    59.37 |     100 |   75.18 | ...53-158,169-173 
  ...etailStep.tsx |   10.41 |      100 |       0 |   10.41 | ...1,67-79,82-139 
  ToolListStep.tsx |   69.02 |       50 |     100 |   69.02 | ...22,125,134-143 
 ...nents/messages |   82.44 |    79.55 |    72.6 |   82.44 |                   
  ...ionDialog.tsx |   80.84 |     77.6 |    62.5 |   80.84 | ...98,516,534-536 
  BtwMessage.tsx   |     100 |      100 |     100 |     100 |                   
  ...upDisplay.tsx |   97.67 |    83.72 |     100 |   97.67 | 119,142,150       
  ...onMessage.tsx |   91.93 |    82.35 |     100 |   91.93 | 57-59,61,63       
  ...nMessages.tsx |   79.06 |      100 |      70 |   79.06 | ...51-264,268-280 
  DiffRenderer.tsx |   93.19 |    86.17 |     100 |   93.19 | ...09,237-238,304 
  ...tsDisplay.tsx |   97.82 |    77.27 |     100 |   97.82 | 87,89             
  ...usMessage.tsx |   76.31 |     42.1 |   66.66 |   76.31 | ...99,101,124,155 
  ...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 |   14.28 |      100 |       0 |   14.28 | 23-62             
  ...onMessage.tsx |   81.02 |    69.23 |   33.33 |   81.02 | ...24-426,433-435 
  ...upMessage.tsx |      84 |    93.61 |     100 |      84 | ...56-383,405-420 
  ToolMessage.tsx  |   88.84 |    75.71 |    92.3 |   88.84 | ...44-749,776-778 
 ...ponents/shared |   85.36 |    78.48 |   95.77 |   85.36 |                   
  ...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  |   84.31 |    74.19 |     100 |   84.31 | ...37,193-195,205 
  ...tonSelect.tsx |     100 |      100 |     100 |     100 |                   
  ...eSelector.tsx |     100 |       60 |     100 |     100 | 40-45             
  TextInput.tsx    |   77.01 |    48.78 |      80 |   77.01 | ...08-212,224-230 
  ...apsedTime.tsx |     100 |      100 |     100 |     100 |                   
  ...Indicator.tsx |     100 |      100 |     100 |     100 |                   
  text-buffer.ts   |   83.68 |    78.55 |   97.61 |   83.68 | ...2270-2272,2368 
  ...er-actions.ts |   86.71 |    67.79 |     100 |   86.71 | ...07-608,809-811 
 ...ents/subagents |   30.87 |        0 |       0 |   30.87 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-11              
  reducers.tsx     |    12.1 |      100 |       0 |    12.1 | 33-190            
  types.ts         |     100 |      100 |     100 |     100 |                   
  utils.ts         |   10.95 |      100 |       0 |   10.95 | ...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 |   21.51 |    59.52 |   27.27 |   21.51 |                   
  ...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 |   35.42 |    59.52 |     100 |   35.42 | ...20-432,437-439 
  ...iewerStep.tsx |   13.72 |      100 |       0 |   13.72 | 18-73             
  ...gerDialog.tsx |    6.74 |      100 |       0 |    6.74 | 35-341            
 ...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   |   77.11 |    77.66 |   80.35 |   77.11 |                   
  ...ewContext.tsx |    64.7 |    85.71 |      50 |    64.7 | ...22-225,231-241 
  AppContext.tsx   |      80 |       50 |     100 |      80 | 19-20             
  ...ewContext.tsx |   95.18 |    67.56 |      50 |   95.18 | ...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    
  ...deContext.tsx |     100 |      100 |      50 |     100 |                   
  ...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             
  ...utContext.tsx |   85.71 |      100 |   66.66 |   85.71 | 13-14             
  ...nsContext.tsx |   88.23 |       50 |     100 |   88.23 | 113-114           
  ...teContext.tsx |   86.66 |       50 |     100 |   86.66 | 177-178           
  ...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      |   82.41 |    82.49 |   86.66 |   82.41 |                   
  ...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 |   75.75 |    63.01 |   61.53 |   75.75 | ...84,908,927-931 
  ...amingState.ts |   12.22 |      100 |       0 |   12.22 | 54-157            
  ...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.21 |    76.08 |     100 |   94.21 | 122-126,213,219   
  ...ketedPaste.ts |    23.8 |      100 |       0 |    23.8 | 19-37             
  ...nchCommand.ts |   94.36 |    74.35 |     100 |   94.36 | ...60,168-169,209 
  ...ompletion.tsx |   95.95 |    82.75 |     100 |   95.95 | ...22-223,225-226 
  ...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 |   78.53 |    88.57 |     100 |   78.53 | ...96-104,112-113 
  ...ialogClose.ts |   15.38 |      100 |     100 |   15.38 | 83-148            
  ...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.47 |       50 |   33.33 |   54.47 | ...69-171,193-194 
  useFocus.ts      |     100 |      100 |     100 |     100 |                   
  ...olderTrust.ts |     100 |      100 |     100 |     100 |                   
  ...ggestions.tsx |   89.15 |     62.5 |      50 |   89.15 | ...22-124,149-150 
  ...miniStream.ts |   77.38 |    74.63 |   91.66 |   77.38 | ...2465,2478-2486 
  ...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  |   63.15 |       75 |      50 |   63.15 | 42-52,64-67       
  ...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 
  ...derUpdates.ts |   86.38 |    77.19 |     100 |   86.38 | ...22,281-293,341 
  useQwenAuth.ts   |     100 |      100 |     100 |     100 |                   
  ...lScheduler.ts |    84.7 |    93.33 |     100 |    84.7 | ...71-276,372-382 
  ...oryCommand.ts |       0 |        0 |       0 |       0 | 1-7               
  ...umeCommand.ts |   97.08 |    83.33 |     100 |   97.08 | 103-104,133       
  ...ompletion.tsx |   90.59 |    83.33 |     100 |   90.59 | ...01,104,137-140 
  ...ectionList.ts |   96.98 |    95.69 |     100 |   96.98 | ...83-184,238-241 
  ...sionPicker.ts |   92.02 |    89.47 |     100 |   92.02 | ...99-501,503-505 
  ...earchInput.ts |     100 |      100 |     100 |     100 |                   
  ...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 |   82.67 |    85.41 |   94.73 |   82.67 | ...68-670,678-714 
  ...tateAndRef.ts |     100 |      100 |     100 |     100 |                   
  useStatusLine.ts |   97.67 |    91.66 |     100 |   97.67 | ...28-332,344-347 
  ...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.72 |     87.5 |     100 |   89.72 |                   
  ...AppLayout.tsx |   89.88 |     87.5 |     100 |   89.88 | 51-53,93-98       
  ...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.58 |     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.96 |     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      |   83.92 |    82.91 |   92.56 |   83.92 |                   
  ...Colorizer.tsx |   79.53 |    83.78 |     100 |   79.53 | ...51-152,249-275 
  ...nRenderer.tsx |   68.83 |    70.14 |      50 |   68.83 | ...52-254,274-293 
  ...wnDisplay.tsx |   86.01 |    87.41 |     100 |   86.01 | ...87,704,729-754 
  ...idDiagram.tsx |   87.79 |    95.34 |     100 |   87.79 | 156-179           
  ...eRenderer.tsx |   92.08 |    80.45 |      95 |   92.08 | ...76-679,723-728 
  ...dWorkUtils.ts |     100 |      100 |     100 |     100 |                   
  ...boardUtils.ts |   59.61 |    58.82 |     100 |   59.61 | ...,86-88,107-149 
  commandUtils.ts  |    95.9 |    88.42 |     100 |    95.9 | ...62,164-165,289 
  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     |     100 |      100 |     100 |     100 |                   
  ...oryMapping.ts |     100 |    94.28 |     100 |     100 | 29,51             
  historyUtils.ts  |   94.11 |       94 |     100 |   94.11 | 94-97             
  isNarrowWidth.ts |     100 |      100 |     100 |     100 |                   
  ...olDetector.ts |    8.23 |      100 |       0 |    8.23 | ...31-132,135-136 
  latexRenderer.ts |   94.95 |     73.8 |     100 |   94.95 | ...76-178,184-187 
  layoutUtils.ts   |     100 |      100 |     100 |     100 |                   
  ...ightLoader.ts |     100 |    89.47 |     100 |     100 | 81,110            
  ...nUtilities.ts |   69.84 |    85.71 |     100 |   69.84 | 75-91,100-101     
  ...ToolGroups.ts |   98.66 |    96.77 |     100 |   98.66 | 48-49             
  ...geRenderer.ts |   86.23 |    69.06 |   95.12 |   86.23 | ...1284,1324-1330 
  ...alRenderer.ts |   86.69 |     71.9 |     100 |   86.69 | ...1476,1513-1519 
  ...lsBySource.ts |     100 |    95.23 |     100 |     100 | 84                
  osc8.ts          |   94.71 |    87.41 |     100 |   94.71 | ...43,428,432-433 
  ...mConstants.ts |     100 |      100 |     100 |     100 |                   
  restoreGoal.ts   |   98.98 |    97.05 |     100 |   98.98 | 98                
  ...storyUtils.ts |   61.89 |    69.87 |      90 |   61.89 | ...76,424,429-451 
  ...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.33 |     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         |   76.06 |    89.52 |   93.82 |   76.06 |                   
  acpModelUtils.ts |     100 |      100 |     100 |     100 |                   
  apiPreconnect.ts |   96.72 |    97.14 |     100 |   96.72 | 165-168           
  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   |   87.17 |     90.9 |     100 |   87.17 | 64-73             
  ...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  |   71.06 |       75 |     100 |   71.06 | ...95-301,325-341 
  ...putCapture.ts |   90.65 |    86.17 |     100 |   90.65 | ...72,370,372-373 
  ...arResolver.ts |   94.28 |       88 |     100 |   94.28 | 28-29,125-126     
  errors.ts        |   98.67 |    96.36 |     100 |   98.67 | 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              
  ...iagnostics.ts |   94.57 |    83.01 |   88.88 |   94.57 | ...05,311,315-317 
  ...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-1047            
  settingsUtils.ts |   82.89 |    90.75 |   89.47 |   82.89 | ...52-663,670-678 
  spawnWrapper.ts  |     100 |      100 |     100 |     100 |                   
  ...upProfiler.ts |   98.46 |    94.52 |     100 |   98.46 | 130-131,305       
  ...upWarnings.ts |     100 |      100 |     100 |     100 |                   
  stdioHelpers.ts  |     100 |       60 |     100 |     100 | 23,32             
  systemInfo.ts    |   95.12 |    89.06 |     100 |   95.12 | ...43-244,249-253 
  ...InfoFields.ts |   87.61 |       65 |     100 |   87.61 | ...22-123,144-145 
  ...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 |   63.15 |    81.25 |     100 |   63.15 | 93,118-157        
-------------------|---------|----------|---------|---------|-------------------
Core Package - Full Text Report
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   79.27 |    82.75 |   81.92 |   79.27 |                   
 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        |   87.58 |    79.07 |   91.76 |   87.58 |                   
  ...transcript.ts |   92.25 |    85.71 |     100 |   92.25 | ...87,306-307,438 
  ...ent-resume.ts |    82.5 |     71.5 |   77.41 |    82.5 | ...1035-1039,1042 
  ...ound-tasks.ts |    95.4 |    86.48 |     100 |    95.4 | ...55-756,827-828 
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/agents/arena  |   76.54 |    66.87 |   78.72 |   76.54 |                   
  ...gentClient.ts |   79.47 |    88.88 |   81.81 |   79.47 | ...68-183,189-204 
  ArenaManager.ts  |   75.37 |    63.37 |   78.26 |   75.37 | ...1860,1866-1867 
  arena-events.ts  |   64.44 |      100 |      50 |   64.44 | ...71-175,178-183 
  diff-summary.ts  |    87.5 |    72.34 |     100 |    87.5 | ...32-133,137-138 
  index.ts         |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...gents/backends |   76.29 |    86.15 |   73.04 |   76.29 |                   
  ITermBackend.ts  |   97.97 |    93.93 |     100 |   97.97 | ...78-180,255,307 
  ...essBackend.ts |   91.25 |    90.62 |   86.66 |   91.25 | ...94,249-269,328 
  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 |   81.14 |     76.7 |   71.42 |   81.14 |                   
  agent-context.ts |     100 |      100 |     100 |     100 |                   
  agent-core.ts    |   76.49 |    72.35 |   60.86 |   76.49 | ...1608,1635-1682 
  agent-events.ts  |     100 |      100 |     100 |     100 |                   
  ...t-headless.ts |   81.19 |    71.73 |   60.86 |   81.19 | ...98-399,402-403 
  ...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/agents/tasks  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/config        |   78.16 |    81.13 |      65 |   78.16 |                   
  config.ts        |   75.91 |    79.78 |   60.09 |   75.91 | ...3605,3616-3628 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  models.ts        |     100 |      100 |     100 |     100 |                   
  storage.ts       |   95.01 |     90.9 |   90.47 |   95.01 | ...71-372,375-376 
 ...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/core          |   86.68 |    82.18 |   89.86 |   86.68 |                   
  baseLlmClient.ts |   92.35 |    80.85 |   86.66 |   92.35 | ...34,342-356,495 
  client.ts        |   85.49 |    77.12 |   84.84 |   85.49 | ...1735,1774-1777 
  ...tGenerator.ts |    72.1 |    61.11 |     100 |    72.1 | ...63,365,372-375 
  ...lScheduler.ts |   82.97 |    81.44 |   93.47 |   82.97 | ...2431,2483-2487 
  geminiChat.ts    |   89.32 |     84.8 |   91.48 |   89.32 | ...1454,1521-1522 
  geminiRequest.ts |     100 |      100 |     100 |     100 |                   
  ...htProtocol.ts |    9.09 |      100 |       0 |    9.09 | 34-42,45-49,52-87 
  logger.ts        |   87.33 |    87.02 |     100 |   87.33 | ...61-565,611-625 
  ...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       |   89.16 |    86.41 |   76.92 |   89.16 | ...-965,1168-1169 
  tokenLimits.ts   |     100 |    89.47 |     100 |     100 | 51-52             
  ...okTriggers.ts |   99.31 |    90.41 |     100 |   99.31 | 124,135           
  turn.ts          |   96.42 |    88.88 |     100 |   96.42 | ...00,413-414,462 
 ...ntentGenerator |   94.92 |    82.59 |   93.87 |   94.92 |                   
  ...tGenerator.ts |   96.48 |    84.28 |   92.59 |   96.48 | ...01,919-923,963 
  converter.ts     |   94.51 |    80.72 |     100 |   94.51 | ...06-607,617,823 
  index.ts         |       0 |        0 |       0 |       0 | 1-21              
  usage.ts         |     100 |      100 |     100 |     100 |                   
 ...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 |    92.1 |    80.38 |   90.32 |    92.1 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...tGenerator.ts |   92.08 |    80.38 |   90.32 |   92.08 | ...85,895-896,924 
 ...ntentGenerator |   81.66 |    84.08 |    90.9 |   81.66 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  converter.ts     |   76.88 |    82.25 |    87.5 |   76.88 | ...1589,1610-1616 
  errorHandler.ts  |     100 |      100 |     100 |     100 |                   
  index.ts         |   52.38 |    44.44 |      50 |   52.38 | ...77,81-85,89-93 
  ...tGenerator.ts |    66.4 |    70.58 |   88.88 |    66.4 | ...51-157,168-169 
  pipeline.ts      |   93.67 |     84.9 |     100 |   93.67 | ...80-481,489,554 
  ...ureContext.ts |     100 |      100 |     100 |     100 |                   
  ...ingOptions.ts |       0 |        0 |       0 |       0 | 1                 
  ...CallParser.ts |   90.66 |    88.57 |     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.69 |    89.17 |   95.45 |   96.69 |                   
  dashscope.ts     |   97.29 |    89.77 |   93.33 |   97.29 | ...81-282,358-359 
  deepseek.ts      |   95.55 |    90.56 |     100 |   95.55 | ...31-132,145-146 
  default.ts       |   94.62 |    86.36 |   85.71 |   94.62 | 86-87,157-159     
  index.ts         |     100 |      100 |     100 |     100 |                   
  minimax.ts       |     100 |      100 |     100 |     100 |                   
  mistral.ts       |   96.07 |    73.33 |     100 |   96.07 | 32-33             
  modelscope.ts    |     100 |      100 |     100 |     100 |                   
  openrouter.ts    |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 |                   
 src/extension     |   60.56 |    79.46 |    78.4 |   60.56 |                   
  ...-converter.ts |   62.35 |    47.82 |      90 |   62.35 | ...90-791,800-832 
  ...ionManager.ts |   47.04 |    82.06 |    65.9 |   47.04 | ...1398,1408-1427 
  ...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       |     100 |      100 |     100 |     100 |                   
  ...ableSchema.ts |     100 |      100 |     100 |     100 |                   
  variables.ts     |   88.75 |    83.33 |     100 |   88.75 | ...28-231,234-237 
 src/followup      |   46.91 |     92.3 |   71.87 |   46.91 |                   
  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 |    38.4 |    95.12 |   33.33 |    38.4 | ...16-318,353-383 
 src/generated     |       0 |        0 |       0 |       0 |                   
  git-commit.ts    |       0 |        0 |       0 |       0 | 1-10              
 src/goals         |   89.23 |    82.44 |   94.11 |   89.23 |                   
  ...eGoalStore.ts |   81.57 |    92.85 |   81.81 |   81.57 | ...43-146,154-162 
  goalHook.ts      |   97.26 |    91.48 |     100 |   97.26 | 100-105           
  goalJudge.ts     |   84.33 |    74.28 |     100 |   84.33 | ...57-358,366-368 
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/hooks         |   83.48 |    84.87 |   86.83 |   83.48 |                   
  ...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.4 |    90.78 |     100 |    96.4 | ...91,293-294,367 
  ...entHandler.ts |   94.56 |    83.78 |   93.33 |   94.56 | ...38,795-796,806 
  hookPlanner.ts   |   84.13 |    76.59 |      90 |   84.13 | ...38,144,162-173 
  hookRegistry.ts  |   90.17 |    83.33 |     100 |   90.17 | ...33,352,356,360 
  hookRunner.ts    |   58.56 |    71.26 |   66.66 |   58.56 | ...48-749,758-759 
  hookSystem.ts    |   84.57 |      100 |   65.85 |   84.57 | ...21-622,628-629 
  ...HookRunner.ts |   75.51 |     61.9 |      80 |   75.51 | ...05-406,424-425 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...HookRunner.ts |   93.63 |    89.47 |      90 |   93.63 | ...45-353,427-428 
  ...SkillHooks.ts |   78.75 |       75 |   66.66 |   78.75 | 62-66,137-152     
  ...oksManager.ts |   96.66 |    91.66 |     100 |   96.66 | ...90,209-210,223 
  ssrfGuard.ts     |   77.22 |    85.36 |     100 |   77.22 | ...57,261-267,273 
  stopHookCap.ts   |     100 |      100 |     100 |     100 |                   
  trustedHooks.ts  |       0 |        0 |       0 |       0 | 1-124             
  types.ts         |   91.18 |    92.04 |   85.71 |   91.18 | ...40-441,501-505 
  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           |   41.24 |    52.14 |   51.42 |   41.24 |                   
  ...nfigLoader.ts |   70.27 |    35.89 |   94.73 |   70.27 | ...20-422,426-432 
  ...ionFactory.ts |   42.69 |    79.16 |      50 |   42.69 | ...62-413,419-436 
  ...Normalizer.ts |   23.09 |    13.72 |   30.43 |   23.09 | ...04-905,909-924 
  ...verManager.ts |   25.31 |    62.06 |   41.66 |   25.31 | ...85-704,710-740 
  ...eLspClient.ts |   32.77 |       80 |   17.64 |   32.77 | ...84-288,294-295 
  ...LspService.ts |   48.49 |    67.16 |   65.71 |   48.49 | ...1352,1369-1379 
  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.52 |    86.66 |   86.36 |   79.52 |                   
  ...en-storage.ts |     100 |      100 |     100 |     100 |                   
  ...en-storage.ts |   82.87 |    82.35 |   92.85 |   82.87 | ...63-173,181-182 
  ...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        |   67.43 |       76 |   65.62 |   67.43 |                   
  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       |   63.77 |    79.16 |      50 |   63.77 | ...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        |    45.8 |    61.53 |   44.44 |    45.8 | ...04,211,214-346 
  indexer.ts       |   83.87 |    45.45 |     100 |   83.87 | ...50,56-57,69-70 
  manager.ts       |   75.31 |    81.04 |    75.6 |   75.31 | ...1278,1291-1293 
  memoryAge.ts     |   90.47 |    77.77 |     100 |   90.47 | 50-51             
  paths.ts         |   55.47 |    89.47 |   85.71 |   55.47 | ...,89-90,106-114 
  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 | ...07,109-110,118 
  scan.ts          |   87.91 |    68.42 |     100 |   87.91 | ...47-48,58,82-87 
  ...entPlanner.ts |    11.5 |      100 |       0 |    11.5 | ...57-192,210-298 
  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.31 |    85.55 |    87.5 |   89.31 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...tor-config.ts |   90.24 |    91.42 |     100 |   90.24 | 142,148,151-160   
  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.59 |     100 |     100 | 222               
  modelsConfig.ts  |   84.57 |    82.14 |   81.57 |   84.57 | ...1223,1252-1253 
  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.76 |   48.57 |   71.18 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...on-manager.ts |   81.42 |    86.66 |      80 |   81.42 | ...29-830,837-846 
  rule-parser.ts   |   95.99 |    93.22 |     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.01 |    79.48 |   97.18 |   86.01 |                   
  ...tGenerator.ts |   98.64 |    98.18 |     100 |   98.64 | 105-106           
  qwenOAuth2.ts    |   84.99 |    74.81 |   93.33 |   84.99 | ...,985-1001,1031 
  ...kenManager.ts |   83.76 |    76.22 |     100 |   83.76 | ...62-767,788-793 
 src/services      |   85.27 |    83.54 |   90.86 |   85.27 |                   
  ...ionTrailer.ts |     100 |      100 |     100 |     100 |                   
  ...llRegistry.ts |   98.44 |    91.83 |     100 |   98.44 | 268-269           
  ...ionService.ts |    95.6 |    96.36 |     100 |    95.6 | ...32,400,402-406 
  ...ingService.ts |   83.91 |       83 |   83.33 |   83.91 | ...1267,1284-1285 
  ...ttribution.ts |   91.73 |    87.71 |      90 |   91.73 | ...80-685,826-827 
  ...utSlimming.ts |     100 |    96.77 |     100 |     100 | 133,182           
  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 
  ...oryService.ts |   86.25 |    74.35 |    92.3 |   86.25 | ...46-655,696-699 
  fileReadCache.ts |     100 |      100 |     100 |     100 |                   
  ...temService.ts |      90 |    84.44 |   88.88 |      90 | ...89,191,269-276 
  ...ratedFiles.ts |      96 |    88.23 |     100 |      96 | 119-120,146-147   
  gitInit.ts       |     100 |      100 |     100 |     100 |                   
  gitService.ts    |   68.75 |     92.3 |   55.55 |   68.75 | ...12-122,125-129 
  ...reeService.ts |   73.79 |       70 |   94.87 |   73.79 | ...1365,1393-1394 
  ...ionService.ts |   98.13 |     97.8 |   95.45 |   98.13 | ...32-333,380-381 
  ...orRegistry.ts |   96.54 |    91.73 |     100 |   96.54 | ...70-471,622-623 
  sessionRecap.ts  |   12.04 |      100 |       0 |   12.04 | 49-160            
  ...ionService.ts |   90.19 |     78.7 |   96.66 |   90.19 | ...1285,1289-1290 
  sessionTitle.ts  |   93.87 |    69.81 |     100 |   93.87 | ...33-236,267-268 
  ...ionService.ts |   81.07 |    77.92 |   89.28 |   81.07 | ...1923,1929-1934 
  ...UseSummary.ts |   94.73 |    87.71 |     100 |   94.73 | ...73-175,225-226 
  ...reeCleanup.ts |   14.56 |      100 |   33.33 |   14.56 | 58-185            
 ...icrocompaction |   97.69 |    89.79 |     100 |   97.69 |                   
  microcompact.ts  |   97.69 |    89.79 |     100 |   97.69 | ...68,229,233,314 
 src/skills        |    87.5 |    83.86 |   94.23 |    87.5 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...activation.ts |     100 |     93.1 |     100 |     100 | 93,112            
  skill-load.ts    |   92.94 |    81.63 |     100 |   92.94 | ...06,226,238-240 
  skill-manager.ts |   83.31 |    79.66 |   90.32 |   83.31 | ...1120,1127-1131 
  skill-paths.ts   |   86.74 |    77.77 |     100 |   86.74 | ...00-101,106-107 
  symlinkScope.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/subagents     |   83.13 |    80.24 |   95.23 |   83.13 |                   
  ...tin-agents.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...-selection.ts |     100 |      100 |     100 |     100 |                   
  ...nt-manager.ts |   77.21 |    72.09 |   92.85 |   77.21 | ...1180,1202-1203 
  types.ts         |     100 |      100 |     100 |     100 |                   
  validation.ts    |   92.46 |    95.18 |     100 |   92.46 | 51-56,69-74,78-83 
 src/telemetry     |   74.59 |     85.9 |   78.77 |   74.59 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...attributes.ts |   98.13 |       88 |     100 |   98.13 | 185-187           
  ...-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 |   93.93 |    90.21 |   94.11 |   93.93 | ...75-280,299-300 
  ...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           |   90.45 |    83.56 |   76.92 |   90.45 | ...17-318,338-342 
  ...on-context.ts |     100 |      100 |     100 |     100 |                   
  ...on-tracing.ts |   90.69 |    87.87 |     100 |   90.69 | ...67-471,482-485 
  ...etry-utils.ts |     100 |      100 |     100 |     100 |                   
  ...l-decision.ts |     100 |      100 |     100 |     100 |                   
  ...e-id-utils.ts |     100 |      100 |     100 |     100 |                   
  tracer.ts        |   98.61 |    89.36 |     100 |   98.61 | 53,104            
  types.ts         |   79.17 |    85.83 |   83.33 |   79.17 | ...1149,1152-1181 
  uiTelemetry.ts   |   92.97 |    96.96 |   81.25 |   92.97 | ...93-194,200-207 
 ...ry/qwen-logger |   68.24 |    79.56 |   64.91 |   68.24 |                   
  event-types.ts   |       0 |        0 |       0 |       0 |                   
  qwen-logger.ts   |   68.24 |    79.34 |   64.28 |   68.24 | ...1055,1093-1094 
 src/test-utils    |   93.16 |    95.91 |   76.47 |   93.16 |                   
  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.19 |    97.14 |   72.41 |   91.19 | ...38,202-203,216 
  ...aceContext.ts |     100 |      100 |     100 |     100 |                   
 src/tools         |    77.8 |    81.31 |    86.3 |    77.8 |                   
  ...erQuestion.ts |   88.93 |    76.74 |    90.9 |   88.93 | ...39-340,347-348 
  cron-create.ts   |   97.75 |    88.88 |   83.33 |   97.75 | 30-31             
  cron-delete.ts   |   96.82 |      100 |   83.33 |   96.82 | 26-27             
  cron-list.ts     |   96.66 |      100 |   83.33 |   96.66 | 25-26             
  diffOptions.ts   |     100 |      100 |     100 |     100 |                   
  edit.ts          |   80.52 |    85.98 |   73.33 |   80.52 | ...15-716,803-853 
  ...r-worktree.ts |   82.43 |    68.75 |    87.5 |   82.43 | ...67-170,236-237 
  exit-worktree.ts |   83.47 |       84 |    90.9 |   83.47 | ...80-281,286-299 
  exitPlanMode.ts  |   85.09 |    85.71 |     100 |   85.09 | ...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.77 |    60.09 |   90.32 |   72.77 | ...1211,1213-1214 
  ...nt-manager.ts |   69.73 |    75.29 |   71.42 |   69.73 | ...29-732,749-786 
  mcp-client.ts    |   33.18 |    77.41 |   66.66 |   33.18 | ...1490,1494-1497 
  mcp-tool.ts      |   90.98 |    88.88 |   96.42 |   90.98 | ...95-596,646-647 
  memory-config.ts |       0 |        0 |       0 |       0 | 1-47              
  ...iable-tool.ts |     100 |    84.61 |     100 |     100 | 102,109           
  monitor.ts       |   92.36 |    83.94 |      92 |   92.36 | ...29,558-561,574 
  ...nforcement.ts |   82.44 |       90 |     100 |   82.44 | 174-185,234-247   
  read-file.ts     |   95.07 |     88.6 |      90 |   95.07 | ...99,290-293,296 
  ripGrep.ts       |   94.59 |    85.71 |   93.33 |   94.59 | ...60,463,541-542 
  ...-transport.ts |    6.34 |      100 |       0 |    6.34 | 47-145            
  send-message.ts  |   89.32 |    91.66 |   83.33 |   89.32 | 44-45,68-76       
  shell.ts         |   72.96 |     79.6 |    91.3 |   72.96 | ...4216,4265-4271 
  skill-utils.ts   |     100 |      100 |     100 |     100 |                   
  skill.ts         |   88.11 |    91.17 |   84.61 |   88.11 | ...95,399,422-444 
  ...eticOutput.ts |   95.12 |      100 |      80 |   95.12 | 87-88             
  task-stop.ts     |   93.14 |    96.15 |   85.71 |   93.14 | 39-40,54-64       
  todoWrite.ts     |   89.17 |    82.05 |   92.85 |   89.17 | ...41-546,568-569 
  tool-error.ts    |     100 |      100 |     100 |     100 |                   
  tool-names.ts    |     100 |      100 |     100 |     100 |                   
  tool-registry.ts |   74.79 |       75 |   80.48 |   74.79 | ...92-793,801-802 
  tool-search.ts   |   95.19 |    86.48 |    92.3 |   95.19 | ...47-153,208-213 
  tools.ts         |   91.98 |    90.19 |   88.88 |   91.98 | ...50-451,467-473 
  web-fetch.ts     |   88.59 |    79.48 |    92.3 |   88.59 | ...12-313,315-316 
  write-file.ts    |   82.23 |    81.17 |   83.33 |   82.23 | ...65-668,680-715 
 src/tools/agent   |   75.01 |    82.55 |   74.62 |   75.01 |                   
  agent.ts         |   75.29 |    82.86 |    75.4 |   75.29 | ...2203,2265-2272 
  fork-subagent.ts |   69.62 |    71.42 |   66.66 |   69.62 | ...04-105,140-151 
 src/utils         |   88.98 |    87.55 |   93.68 |   88.98 |                   
  LruCache.ts      |       0 |        0 |       0 |       0 | 1-41              
  ...ssageQueue.ts |     100 |      100 |     100 |     100 |                   
  ...cFileWrite.ts |   77.96 |    80.48 |     100 |   77.96 | ...35,156,173-176 
  bareMode.ts      |   27.27 |      100 |       0 |   27.27 | 9-15,18-19        
  browser.ts       |    7.69 |      100 |       0 |    7.69 | 17-56             
  bundlePaths.ts   |     100 |      100 |     100 |     100 |                   
  ...igResolver.ts |     100 |      100 |     100 |     100 |                   
  ...engthError.ts |   89.11 |    86.66 |     100 |   89.11 | ...28-129,132-133 
  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   |    95.9 |    93.84 |   94.73 |    95.9 | 106-107,214-218   
  editHelper.ts    |   93.63 |    83.52 |     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 |    97.05 |     100 |    97.7 | 72-73             
  ...rReporting.ts |   88.46 |       90 |     100 |   88.46 | 69-74             
  errors.ts        |   70.92 |    79.59 |   53.33 |   70.92 | ...03-219,223-229 
  fetch.ts         |   70.18 |    71.42 |   71.42 |   70.18 | ...42,148,161,186 
  fileUtils.ts     |   91.41 |    86.13 |      95 |   91.41 | ...1182,1186-1192 
  forkedAgent.ts   |    78.5 |    70.73 |   85.71 |    78.5 | ...30-436,441-447 
  formatters.ts    |   81.81 |       75 |     100 |   81.81 | 15-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             
  gitDiff.ts       |   92.36 |    79.53 |     100 |   92.36 | ...55-856,928-929 
  ...noreParser.ts |    92.3 |    89.36 |     100 |    92.3 | ...15-116,186-187 
  gitUtils.ts      |   56.66 |    85.71 |      75 |   56.66 | ...2,72-73,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   |    74.1 |    90.76 |   58.33 |    74.1 | ...23-326,336-342 
  ...-detection.ts |     100 |      100 |     100 |     100 |                   
  ...iagnostics.ts |   96.87 |    91.83 |     100 |   96.87 | 214-219,272       
  ...yDiscovery.ts |    83.9 |    79.36 |     100 |    83.9 | ...16,319,411-414 
  ...tProcessor.ts |   93.63 |       90 |     100 |   93.63 | ...96-302,384-385 
  ...Inspectors.ts |   61.53 |      100 |      50 |   61.53 | 18-23             
  modelId.ts       |   98.55 |    96.87 |     100 |   98.55 | 103               
  ...kerChecker.ts |   88.75 |    85.71 |     100 |   88.75 | 69-70,87-93       
  notebook.ts      |   94.35 |    84.78 |     100 |   94.35 | ...10,122,174-176 
  openaiLogger.ts  |   88.05 |    84.09 |     100 |   88.05 | ...44-146,169-174 
  partUtils.ts     |     100 |    98.61 |     100 |     100 | 206               
  pathReader.ts    |     100 |      100 |     100 |     100 |                   
  paths.ts         |   93.21 |    91.86 |     100 |   93.21 | ...89-390,392-394 
  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     |   92.55 |    85.92 |     100 |   92.55 | ...70-272,309-310 
  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.79 |    84.37 |   66.66 |   46.79 | ...45-246,258-335 
  ...sDiscovery.ts |   97.42 |    92.85 |     100 |   97.42 | ...04,182-183,202 
  ...tchOptions.ts |   81.72 |    85.04 |   95.23 |   81.72 | ...11,536,565-574 
  runtimeStatus.ts |    97.5 |    88.57 |     100 |    97.5 | 167-168           
  safeJsonParse.ts |   74.07 |    83.33 |     100 |   74.07 | 40-46             
  ...nStringify.ts |     100 |      100 |     100 |     100 |                   
  ...aConverter.ts |   90.78 |    88.23 |     100 |   90.78 | ...41-42,93,95-96 
  ...aValidator.ts |   94.57 |    80.26 |     100 |   94.57 | ...04,213-216,270 
  ...r-launcher.ts |   76.92 |     91.3 |   66.66 |   76.92 | ...34,136,157-195 
  ...orageUtils.ts |   96.89 |    85.84 |     100 |   96.89 | ...51,367,447,466 
  shell-utils.ts   |   82.93 |    89.89 |     100 |   82.93 | ...1522,1529-1533 
  ...lAstParser.ts |   95.58 |    85.79 |     100 |   95.58 | ...1059-1061,1071 
  ...nlyChecker.ts |   95.75 |    92.39 |     100 |   95.75 | ...00-301,313-314 
  sideQuery.ts     |   98.73 |    94.59 |     100 |   98.73 | 111               
  ...pEventSink.ts |     100 |       80 |     100 |     100 | 61                
  ...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 |    89.28 |   93.33 |   93.71 | ...24-225,249-251 
  xml.ts           |     100 |      100 |     100 |     100 |                   
  yaml-parser.ts   |      92 |    84.61 |     100 |      92 | 49-53,65-69       
 ...ils/filesearch |   86.21 |    81.61 |   96.42 |   86.21 |                   
  crawlCache.ts    |     100 |      100 |     100 |     100 |                   
  crawler.ts       |   82.84 |    77.49 |   94.82 |   82.84 | ...1451,1485-1486 
  fileSearch.ts    |   93.58 |    87.32 |     100 |   93.58 | ...46-247,249-250 
  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.

}
const optionId =
this.resolvePermissionOptionId(request, askResponse.optionId) ??
askResponse.optionId;

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.

[Critical] The AskUserQuestion path still falls back to the raw callback option when it is not one of the daemon-advertised options. The regular permission path now cancels stale option ids, but here { optionId: 'stale-option' } becomes a selected response with an invalid option. If the daemon rejects that response, the user's answer is dropped and the pending request can remain unresolved.

Suggested change
askResponse.optionId;
const optionId = this.resolvePermissionOptionId(
request,
askResponse.optionId,
);
if (!optionId) {
return { outcome: { outcome: 'cancelled' } };
}

— gpt-5.5 via Qwen Code /review

},
"dependencies": {
"@agentclientprotocol/sdk": "^0.14.1",
"@qwen-code/sdk": "*",

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.

[Critical] Declaring @qwen-code/sdk here does not make the hidden dynamic import available in the packaged VSIX. The extension script still uses vsce package --no-dependencies, .vscodeignore only includes dist/ plus a few assets, and daemonIdeConnection.ts intentionally hides import('@qwen-code/sdk') from esbuild. That means local builds/tests can pass, but an installed extension will not contain node_modules/@qwen-code/sdk, so enabling the daemon adapter fails at runtime before it can connect. Please either bundle the SDK with a normal import/esbuild path or explicitly include the runtime dependency in the VSIX packaging.

— gpt-5.5 via Qwen Code /review

} catch (error) {
if (!signal.aborted) {
console.warn(
'[DaemonIdeConnection] Event stream failed:',

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.

[Critical] Permission response TOCTOU race: handlePermissionRequest calls await this.resolvePermissionResponse(request) (may block on user interaction for seconds), then calls this.ensureSession().respondToPermission(...). If a session_died event arrives during the await, this.session is set to null by clearCurrentSession, and ensureSession() throws 'Not connected to daemon session'. The user's permission decision (allow/deny) is silently lost — the daemon hangs waiting for a response that never arrives.

Suggested change
'[DaemonIdeConnection] Event stream failed:',
private async handlePermissionRequest(data: unknown): Promise<void> {
if (!isPermissionRequestData(data)) {
return;
}
const requestId = data['requestId'];
const request = data;
const session = this.session;
const response = await this.resolvePermissionResponse(request);
if (!session) {
return;
}
const accepted = await session.respondToPermission(requestId, response);
if (!accepted) {
console.warn(
'[DaemonIdeConnection] Permission response rejected by daemon for request:',
requestId,
);
}
}

— DeepSeek/deepseek-v4-pro via Qwen Code /review

const response = await session.prompt({ prompt: promptBlocks });
this.onEndTurn(response.stopReason);
return response;
}

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.

[Critical] disconnect() calls this.eventController?.abort() then await this.eventPump, but the AbortController only aborts the SSE stream — it cannot interrupt an in-flight onPermissionRequest or onAskUserQuestion callback. If the event pump is blocked inside a user-facing permission callback, disconnect() hangs forever, requiring a VS Code restart.

Suggested change
}
if (this.eventPump) {
try {
await Promise.race([
this.eventPump,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Event pump shutdown timed out')), 5000),
),
]);
} catch {
/* pump errors are converted into callbacks */
}
}

— DeepSeek/deepseek-v4-pro via Qwen Code /review

case 'session_died':
this.handleSessionDied(event.data);
break;
default:

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.

[Critical] In the AskUserQuestion branch of resolvePermissionResponse, when resolvePermissionOptionId returns undefined (callback's optionId doesn't match any in request.options), the code falls back to ?? askResponse.optionId — sending an unvalidated optionId to the daemon. The non-AUQ path correctly returns { outcome: 'cancelled' } in this case. Inconsistent behavior between the two paths.

Suggested change
default:
const optionId = this.resolvePermissionOptionId(request, askResponse.optionId);
if (!optionId) {
return { outcome: { outcome: 'cancelled' } };
}
return {
outcome: {
outcome: 'selected',
optionId,
},
answers: askResponse.answers,
} as RequestPermissionResponse;

— DeepSeek/deepseek-v4-pro via Qwen Code /review

if (!signal.aborted) {
this.clearCurrentSession(session, 'stream_ended');
}
} catch (error) {

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.

[Suggestion] handleEvent only handles session_update, permission_request, and session_died. The design doc (ide.md Event Mapping Contract) lists permission_resolved ("Close/update approval UI") and model_switched ("Existing model-state callback") as expected events, but they fall into default: break; and are silently dropped. At minimum, log them so they're discoverable during dogfood testing.

Suggested change
} catch (error) {
case 'permission_resolved':
// TODO: Close/update approval UI when wired into AgentManager
console.debug('[DaemonIdeConnection] Unhandled event:', event.type);
break;
case 'model_switched':
// TODO: Fire model-state callback when wired into AgentManager
console.debug('[DaemonIdeConnection] Unhandled event:', event.type);
break;
default:
break;

— DeepSeek/deepseek-v4-pro via Qwen Code /review

}
await session.cancel();
}

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.

[Suggestion] disconnect() sets this.session = null but does not clear this.lastSeenEventId. After disconnect, the lastEventId getter returns a stale event ID from the dead session, which could confuse code that reads it between disconnect and reconnect.

Suggested change
this.eventController = null;
this.eventPump = null;
this.session = null;
this.lastSeenEventId = undefined;

— DeepSeek/deepseek-v4-pro via Qwen Code /review

onSessionUpdate: (data: SessionNotification) => void = () => {};
onPermissionRequest: (data: RequestPermissionRequest) => Promise<{
optionId: string;
}> = () => Promise.resolve({ optionId: 'cancel' });

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.

[Suggestion] The default onPermissionRequest handler silently returns { optionId: 'cancel' }. If a consumer forgets to wire this callback, all tool calls are silently denied with zero diagnostic feedback — making integration bugs extremely hard to diagnose.

Suggested change
}> = () => Promise.resolve({ optionId: 'cancel' });
onPermissionRequest: (data: RequestPermissionRequest) => Promise<{
optionId: string;
}> = () => {
console.warn(
'[DaemonIdeConnection] onPermissionRequest not wired — permission will be cancelled',
);
return Promise.resolve({ optionId: 'cancel' });
};

— DeepSeek/deepseek-v4-pro via Qwen Code /review

});
if (
!askResponse.optionId ||
this.isCancelledOption(askResponse.optionId)

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.

[Suggestion] resolvePermissionOptionId in DaemonIdeConnection is a near-verbatim copy of the same method in AcpConnection (same fallback chain: allow_once kind → proceed_once optionId → namespaced proceed_once → first option). The two implementations will inevitably diverge. Extract to a shared utility (e.g., packages/vscode-ide-companion/src/utils/permissionOptions.ts).

— DeepSeek/deepseek-v4-pro via Qwen Code /review

private lastSeenEventId: number | undefined;

onSessionUpdate: (data: SessionNotification) => void = () => {};
onPermissionRequest: (data: RequestPermissionRequest) => Promise<{

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.

[Suggestion] AcpConnection exposes onInitialized, onAuthenticateUpdate, and onSlashCommandNotification callbacks that QwenAgentManager wires. DaemonIdeConnection omits all three. When this adapter is wired into QwenAgentManager, assigning to these missing properties will silently create new JS properties instead of surfacing an error. Add at minimum onInitialized as a no-op placeholder to surface the gap.

Suggested change
onPermissionRequest: (data: RequestPermissionRequest) => Promise<{
onInitialized: (init: unknown) => void = () => {};
// Not yet mapped from daemon events — see design doc §Event Mapping Contract.
onAuthenticateUpdate: (data: unknown) => void = () => {};
onSlashCommandNotification: (data: unknown) => void = () => {};

— DeepSeek/deepseek-v4-pro via Qwen Code /review

if (url.username || url.password) {
throw new Error('Daemon baseUrl must not contain credentials');
}
return baseUrl;

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.

[Suggestion] validateDaemonBaseUrl parses via new URL(baseUrl) but returns the original un-normalized baseUrl instead of url.href. A URL like http://127.0.0.1:4170/api/../../admin passes validation yet the path traversal remains in the stored value. Since DaemonClient uses template-literal URL construction (${this.baseUrl}/session/...), downstream code that adds allowlisting or prefix checks operates on the non-canonical form.

Suggested change
return baseUrl;
return url.href;

— glm-5.1 via Qwen Code /review

this.lastSeenEventId = options.lastEventId ?? this.session.lastEventId;

this.eventController = new AbortController();
this.eventPump = this.pumpEvents(this.session, this.eventController.signal);

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.

[Suggestion] connect() assigns this.session and starts pumpEvents() as fire-and-forget — it resolves before confirming the SSE stream is healthy. If session.events() throws on the first generator call (auth failure, daemon down), the pump catches it asynchronously and calls clearCurrentSession. Between connect() resolving and the pump's async error, isConnected === true and consumers may call sendPrompt(). Consider awaiting the first SSE event or a health-check round-trip before returning, or documenting this transient window.

— glm-5.1 via Qwen Code /review

this.eventPump = this.pumpEvents(this.session, this.eventController.signal);
}

async sendPrompt(

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.

[Critical] sendPrompt() has no try/finally around session.prompt(). If the prompt HTTP request rejects (network error, session expired), onEndTurn() is never called. The webview UI enters a permanent "thinking" state with no timeout or recovery. Add a try/finally that calls this.onEndTurn('error') in the catch/rejection path.

Suggested change
async sendPrompt(
async sendPrompt(
prompt: string | ContentBlock[],
): Promise<DaemonIdePromptResult> {
const session = this.ensureSession();
const promptBlocks = normalizePrompt(prompt);
try {
const response = await session.prompt({ prompt: promptBlocks });
this.onEndTurn(response.stopReason);
return response;
} catch (error) {
this.onEndTurn('error');
throw error;
}
}

— glm-5.1 via Qwen Code /review

): Promise<DaemonIdePromptResult> {
const session = this.ensureSession();
const promptBlocks = normalizePrompt(prompt);
const response = await session.prompt({ prompt: promptBlocks });

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.

[Suggestion] session.prompt() accepts an optional AbortSignal but sendPrompt() never passes one. If the session dies or disconnect is called while a prompt is in-flight, the HTTP request hangs until the daemon responds or times out. After disconnect, the orphaned prompt continues consuming daemon resources.

Suggested change
const response = await session.prompt({ prompt: promptBlocks });
const response = await session.prompt(
{ prompt: promptBlocks },
this.eventController?.signal,
);

— glm-5.1 via Qwen Code /review

const session = this.ensureSession();
const promptBlocks = normalizePrompt(prompt);
const response = await session.prompt({ prompt: promptBlocks });
this.onEndTurn(response.stopReason);

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.

[Suggestion] onEndTurn is called from sendPrompt() (HTTP POST response) while streaming events arrive through the independent SSE pump. In AcpConnection, both travel through a single JSON-RPC stream with FIFO ordering — onEndTurn always fires after the last onSessionUpdate. Here, onEndTurn can fire before the pump has delivered buffered agent_message_chunk events, causing the UI to render "turn complete" then continue receiving content. Consider deferring onEndTurn into the pump (detect end-of-turn from a session_update event) or documenting this as a known semantic gap.

— glm-5.1 via Qwen Code /review

return await this.ensureSession().setModel(modelId);
}

async disconnect(): Promise<void> {

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.

[Critical] Two lifecycle issues with disconnect():

  1. onDisconnected is never called. The method nulls session/eventController/eventPump but never invokes this.onDisconnected(...). The involuntary disconnect paths (clearCurrentSession) correctly call it, but user-initiated disconnect silently drops the session without notifying the webview. After disconnect, the UI remains in a "connected" state while the session is dead.

  2. async vs AcpConnection sync mismatch. AcpConnection.disconnect() returns void (synchronous). DaemonIdeConnection.disconnect() returns Promise<void>. All existing callers in QwenAgentManager and WebViewProvider call .disconnect() without await. When this adapter is wired in, every disconnect call returns immediately while the event pump is still running — the connection appears to never actually disconnect.

Suggested change
async disconnect(): Promise<void> {
disconnect(): void {
const session = this.session;
this.eventController?.abort();
this.eventController = null;
this.eventPump = null;
this.session = null;
if (session) {
this.onDisconnected(null, 'disconnected');
}
}

— glm-5.1 via Qwen Code /review

return this.session !== null;
}

get hasActiveSession(): boolean {

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.

[Suggestion] hasActiveSession and isConnected both return this.session !== null — they are semantically identical. In AcpConnection, isConnected checks child process liveness while hasActiveSession checks sessionId !== null (orthogonal states: connected-without-session vs session-loaded-on-dead-transport). When QwenAgentManager branches on these two properties to distinguish transport state from session state, DaemonIdeConnection gives wrong answers. Consider tracking sessionId separately so the two getters report independently.

— glm-5.1 via Qwen Code /review

@wenshao wenshao 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.

Qwen Code Review — PR #4199 (feat: add daemon connection spike)

Model: mimo-v2.5-pro | Date: 2026-05-17

Deterministic Analysis

  • TypeScript strict: 0 findings in changed files
  • ESLint: clean
  • Build: pass
  • Tests: 11/11 pass (22ms)

Review Stats

  • 9 parallel agents + 3 reverse audit rounds
  • 14 raw findings → 14 confirmed (6 Critical, 8 Suggestion)

Critical (6)

# Finding File Line
C1 AskUserQuestion sends unvalidated optionId daemonIdeConnection.ts 359
C2 Unknown event types silently discarded daemonIdeConnection.ts 315
C3 Malformed permission_request silently dropped daemonIdeConnection.ts 320
C4 sendPrompt has no timeout/AbortSignal daemonIdeConnection.ts 206
C5 respondToPermission error burns event daemonIdeConnection.ts 281
C6 disconnect() hangs on pending permission daemonIdeConnection.ts 228

Suggestion (8)

# Finding File Line
S1 session_update cast without validation daemonIdeConnection.ts 308
S2 options[0] fallback depends on ordering daemonIdeConnection.ts 435
S3 isConnected/hasActiveSession identical daemonIdeConnection.ts 237
S4 Dead validateDaemonBaseUrl in connect() daemonIdeConnection.ts 196
S5 resolvePermissionOptionId diverges from AcpConnection daemonIdeConnection.ts 413
S6 Callbacks not cleared on disconnect daemonIdeConnection.ts 170
S7 validateDaemonBaseUrl throws raw TypeError daemonIdeConnection.ts 112
S8 onEndTurn exception masks success daemonIdeConnection.ts 207

) {
return { outcome: { outcome: 'cancelled' } };
}
const optionId =

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.

[Critical] C1: AskUserQuestion path sends unvalidated optionId to daemon (confidence: 8/10)

When resolvePermissionOptionId returns undefined (user's choice not in daemon's option list), the ?? fallback sends the raw unvalidated askResponse.optionId to the daemon via selected outcome. The regular permission path at line 408 correctly returns cancelled in this case. A mismatched optionId causes the daemon to silently ignore the response and hang indefinitely.

Suggested change
const optionId =
const resolvedOptionId =
this.resolvePermissionOptionId(request, askResponse.optionId);
if (!resolvedOptionId) {
return { outcome: { outcome: 'cancelled' } };
}
return {
outcome: {
outcome: 'selected',
optionId: resolvedOptionId,
},
answers: askResponse.answers,
} as RequestPermissionResponse;

case 'session_died':
this.handleSessionDied(event.data);
break;
default:

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.

[Critical] C2: Unknown event types silently discarded (confidence: 9/10)

The default: break branch drops unrecognized event types with zero logging. The design doc states unknown events should be "logged as debug metadata." Without trace logging, event mismatches between daemon and extension are invisible to developers.

Suggested change
default:
default:
console.debug('[DaemonIdeConnection] Unknown event type:', event.type, event.id);
break;

}
}

private async handlePermissionRequest(data: unknown): Promise<void> {

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.

[Critical] C3: Malformed permission_request silently dropped (confidence: 9/10)

When isPermissionRequestData returns false, handlePermissionRequest returns without logging. The daemon is waiting for a response via respondToPermission that will never arrive — the daemon session hangs indefinitely.

Suggested change
private async handlePermissionRequest(data: unknown): Promise<void> {
if (!isPermissionRequestData(data)) {
console.warn('[DaemonIdeConnection] Malformed permission_request event, ignoring:', data);
return;
}

): Promise<DaemonIdePromptResult> {
const session = this.ensureSession();
const promptBlocks = normalizePrompt(prompt);
const response = await session.prompt({ prompt: promptBlocks });

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.

[Critical] C4: sendPrompt has no timeout/AbortSignal (confidence: 8/10)

session.prompt() is called without any signal or timeout. If the daemon hangs, the promise never settles and the UI shows a running prompt forever with no recovery path.

Consider adding a timeout mechanism or forwarding the session.close() abort signal to the prompt call.

});
} finally {
if (event.id !== undefined) {
this.lastSeenEventId = event.id;

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.

[Critical] C5: respondToPermission error burns event permanently (confidence: 8/10)

lastSeenEventId is unconditionally advanced in the finally block. If respondToPermission throws (HTTP timeout, network error), the permission event is skipped forever — on reconnect the pump resumes from the next event. The daemon session hangs waiting for a response that will never come.

Consider only advancing lastSeenEventId after successful response, or retrying failed permission responses on reconnect.

lastEventId: options.lastEventId,
});
this.lastSeenEventId = options.lastEventId ?? this.session.lastEventId;

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.

[Suggestion] S4: Dead validateDaemonBaseUrl in connect() (confidence: 7/10)

connect() calls validateDaemonBaseUrl but discards the return value, passing the original options.baseUrl to the factory. The factory validates again via its own createClient path. This is either dead code or should use the validated result.

);
}

private resolvePermissionOptionId(

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.

[Suggestion] S5: resolvePermissionOptionId diverges from AcpConnection (confidence: 7/10)

When preferredOptionId is not found in options, daemon path returns undefined (→ cancel), while AcpConnection falls through to optionallySelectPermissionOptionId. This means options with no allow_once/proceed_once are auto-selected in AcpConnection but cancelled in DaemonIdeConnection — behavioral divergence.

private eventPump: Promise<void> | null = null;
private lastSeenEventId: number | undefined;

onSessionUpdate: (data: SessionNotification) => void = () => {};

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.

[Suggestion] S6: Callbacks not cleared on disconnect (confidence: 6/10)

The 5 on* callbacks retain consumer closures after disconnect. In long-lived extension hosts where close() is not called, this leaks memory.

Consider adding a clearCallbacks() method called from disconnect().

);
}

function validateDaemonBaseUrl(baseUrl: string): string {

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.

[Suggestion] S7: validateDaemonBaseUrl throws raw TypeError (confidence: 6/10)

new URL(baseUrl) throws a raw TypeError: Invalid URL with no context. Consider wrapping in a try-catch that throws a descriptive DaemonConnectionError with the invalid URL value.

const session = this.ensureSession();
const promptBlocks = normalizePrompt(prompt);
const response = await session.prompt({ prompt: promptBlocks });
this.onEndTurn(response.stopReason);

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.

[Suggestion] S8: onEndTurn callback exception masks success (confidence: 6/10)

If onEndTurn throws, sendPrompt rejects even though the prompt completed successfully. The caller may retry, causing duplicate agent actions. Consider wrapping in try-catch and logging the error instead of propagating.

@wenshao wenshao 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.

PR Review: feat(ide): add daemon connection spike

Metric Value
Files reviewed 3 (code + test + design doc)
Lines of code 992 (451 production + 541 test)
Build status clean
Test status pass
Deterministic analysis 0 findings (tsc + eslint clean)
Findings 5 Critical, 3 Suggestion
Model mimo-v2.5-pro
Iterations 2 (reverse audit found 1 additional Critical)

Critical (5)

  1. pumpEvents finally block eventPump cleanup always fails - this.session === session guard always evaluates to false because clearCurrentSession nulls this.session on all three exit paths (session_died, stream_ended, daemon_error) before pumpEvents reaches its finally block. this.eventPump remains a stale resolved promise after the pump is dead.

  2. disconnect() deadlocks when permission callback is in-flight - abort() terminates the SSE generator but cannot interrupt the JS-level await this.onPermissionRequest(request). disconnect() awaits a pump that is blocked on a user callback that will never return.

  3. handleSessionDied doesn't validate sessionId - On reconnect with resume: true, the daemon may replay the old session_died event. No data.sessionId !== this.session.sessionId guard exists, so a stale event kills the freshly connected session.

  4. Concurrent connect() calls orphan the first session - No mutex or connectPromise guard. Two overlapping calls both create sessions. The second overwrites this.session and this.eventController. The first session's pump is orphaned; if it errors, it aborts the second session's controller (stale reference), killing the live stream with isConnected === true and no onDisconnected callback.

  5. resolvePermissionOptionId fallback chain completely untested - The four-stage fallback (allow_once kind -> proceed_once id -> namespaced proceed_once -> options[0]) is never exercised by any test.

Suggestion (3)

  1. event.data cast to SessionNotification without validation - this.onSessionUpdate(event.data as SessionNotification) with no runtime type guard. Malformed SSE data flows unvalidated to webview consumers.

  2. questions/metadata casts without validation - isPermissionRequestData validates outer shape but not rawInput content. questions is only checked as Array.isArray, not element structure.

  3. EventQueue.fail() does not resolve pending waiters - fail() sets this.failure but doesn't drain this.waiters. Tests calling fail() with a pending waiter hang until timeout.


Fix guidance for Critical findings:

  1. Remove the if (this.session === session) guard - this.eventPump = null; unconditionally in the finally block.
  2. Thread the abort signal through permission resolution and race the user callback against it.
  3. Guard with if (data.sessionId !== this.session.sessionId) return; before tearing down.
  4. Add a connectPromise guard to serialize concurrent connect() calls.
  5. Add a test with { optionId: undefined } return and kind: 'allow_once' option.

toSafeErrorMessage(error),
);
this.eventController?.abort();
this.clearCurrentSession(session, 'daemon_error');

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.

[Critical] The if (this.session === session) guard always evaluates to false - clearCurrentSession() sets this.session = null on all three exit paths (session_died, stream_ended, daemon_error) before this finally block runs. this.eventPump remains a stale resolved-promise reference after the pump is dead.

Fix: remove the guard - set this.eventPump = null; unconditionally.

return await this.ensureSession().setModel(modelId);
}

async disconnect(): Promise<void> {

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.

[Critical] Deadlock scenario: when the pump is inside handlePermissionRequest -> await this.onPermissionRequest(request), abort() terminates the SSE generator but not the JS-level await on the user callback. disconnect() then hangs on await this.eventPump forever.

Fix: thread the abort signal into permission resolution and race onPermissionRequest against it, or use AbortSignal.any() to combine the permission abort with the controller signal.

this.eventController?.abort();
if (this.session) {
this.clearCurrentSession(this.session, reason);
} else {

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.

[Critical] On reconnect with resume: true, the daemon may replay the old session_died event from the prior session. This handler never checks data.sessionId against this.session.sessionId, so a stale event tears down the freshly connected session.

Fix: guard with if (data.sessionId !== this.session.sessionId) return; before calling clearCurrentSession().

onEndTurn: (reason?: string) => void = () => {};
onDisconnected: (code: number | null, signal: string | null) => void =
() => {};

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.

[Critical] No mutex - concurrent connect() calls both pass the this.session guard, both create sessions and SSE streams. The second call overwrites this.session and this.eventController. The first session's pump is orphaned; if it errors, it reads the stale this.eventController reference (now belonging to session 2) and aborts the live stream. isConnected stays true with no onDisconnected callback.

Fix: add a private connectPromise: Promise<void> | null = null; guard and return/reuse the in-flight promise.

cancel: vi.fn().mockResolvedValue(undefined),
setModel: vi.fn().mockResolvedValue({}),
respondToPermission: vi.fn().mockResolvedValue(true),
};

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.

[Critical] No test exercises the resolvePermissionOptionId fallback chain when onPermissionRequest returns { optionId: undefined }. The four-stage fallback (allow_once kind -> proceed_once id -> namespaced proceed_once -> options[0]) is completely untested.

Fix: add a test that provides options with { kind: "allow_once", id: "test" } and returns { optionId: undefined } from onPermissionRequest, verifying allow_once-test is sent.

private async handleEvent(event: DaemonIdeEvent): Promise<void> {
switch (event.type) {
case 'session_update':
this.onSessionUpdate(event.data as SessionNotification);

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.

[Suggestion] event.data is cast to SessionNotification with as and no runtime validation. Malformed SSE data flows unvalidated to webview consumers. Consider adding a isSessionNotificationData() guard analogous to isPermissionRequestData().


if (isAskUserQuestion) {
const askResponse = await this.onAskUserQuestion({
sessionId: request.sessionId,

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.

[Suggestion] isPermissionRequestData validates the outer shape but not the inner rawInput content. questions is only checked via Array.isArray, not element structure. Consider adding element-level validation before casting.

}

fail(error: unknown): void {
this.failure = error;

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.

[Suggestion] EventQueue.fail() sets this.failure but does not drain this.waiters. If a waiter is pending when fail() is called, the test hangs until timeout. Consider calling this.close() to drain waiters, or rejecting them with the error.

@wenshao wenshao 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.

PR Review: feat(ide): add daemon connection spike

Metric Value
Files reviewed 3 (code + test + design doc)
Lines of code 992 (451 production + 541 test)
Build status clean
Test status pass
Deterministic analysis 0 findings (tsc + eslint clean)
Findings 5 Critical, 3 Suggestion
Model mimo-v2.5-pro
Iterations 2 (reverse audit found 1 additional Critical)

Critical (5)

  1. pumpEvents finally block eventPump cleanup always fails - this.session === session guard always evaluates to false because clearCurrentSession nulls this.session on all three exit paths (session_died, stream_ended, daemon_error) before pumpEvents reaches its finally block. this.eventPump remains a stale resolved promise after the pump is dead.

  2. disconnect() deadlocks when permission callback is in-flight - abort() terminates the SSE generator but cannot interrupt the JS-level await this.onPermissionRequest(request). disconnect() awaits a pump that is blocked on a user callback that will never return.

  3. handleSessionDied doesn't validate sessionId - On reconnect with resume: true, the daemon may replay the old session_died event. No data.sessionId !== this.session.sessionId guard exists, so a stale event kills the freshly connected session.

  4. Concurrent connect() calls orphan the first session - No mutex or connectPromise guard. Two overlapping calls both create sessions. The second overwrites this.session and this.eventController. The first session's pump is orphaned; if it errors, it aborts the second session's controller (stale reference), killing the live stream with isConnected === true and no onDisconnected callback.

  5. resolvePermissionOptionId fallback chain completely untested - The four-stage fallback (allow_once kind -> proceed_once id -> namespaced proceed_once -> options[0]) is never exercised by any test.

Suggestion (3)

  1. event.data cast to SessionNotification without validation - this.onSessionUpdate(event.data as SessionNotification) with no runtime type guard. Malformed SSE data flows unvalidated to webview consumers.

  2. questions/metadata casts without validation - isPermissionRequestData validates outer shape but not rawInput content. questions is only checked as Array.isArray, not element structure.

  3. EventQueue.fail() does not resolve pending waiters - fail() sets this.failure but doesn't drain this.waiters. Tests calling fail() with a pending waiter hang until timeout.


Fix guidance for Critical findings:

  1. Remove the if (this.session === session) guard - this.eventPump = null; unconditionally in the finally block.
  2. Thread the abort signal through permission resolution and race the user callback against it.
  3. Guard with if (data.sessionId !== this.session.sessionId) return; before tearing down.
  4. Add a connectPromise guard to serialize concurrent connect() calls.
  5. Add a test with { optionId: undefined } return and kind: 'allow_once' option.

if (this.session === session) {
this.eventPump = null;
}
}

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.

[Critical] The if (this.session === session) guard always evaluates to false - clearCurrentSession() sets this.session = null on all three exit paths (session_died, stream_ended, daemon_error) before this finally block runs. this.eventPump remains a stale resolved-promise reference after the pump is dead.

Fix: remove the guard - set this.eventPump = null; unconditionally.

try {
await this.eventPump;
} catch {
/* pump errors are converted into callbacks */

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.

[Critical] Deadlock scenario: when the pump is inside handlePermissionRequest -> await this.onPermissionRequest(request), abort() terminates the SSE generator but not the JS-level await on the user callback. disconnect() then hangs on await this.eventPump forever.

Fix: thread the abort signal into permission resolution and race onPermissionRequest against it, or use AbortSignal.any() to combine the permission abort with the controller signal.

}

private isCancelledOption(optionId?: string): boolean {
return (

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.

[Critical] On reconnect with resume: true, the daemon may replay the old session_died event from the prior session. This handler never checks data.sessionId against this.session.sessionId, so a stale event tears down the freshly connected session.

Fix: guard with if (data.sessionId !== this.session.sessionId) return; before calling clearCurrentSession().

await this.disconnect();
}

const factory = options.sessionFactory ?? createSdkDaemonSessionFactory();

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.

[Critical] No mutex - concurrent connect() calls both pass the this.session guard, both create sessions and SSE streams. The second call overwrites this.session and this.eventController. The first session's pump is orphaned; if it errors, it reads the stale this.eventController reference (now belonging to session 2) and aborts the live stream. isConnected stays true with no onDisconnected callback.

Fix: add a private connectPromise: Promise<void> | null = null; guard and return/reuse the in-flight promise.

async function waitFor(assertion: () => void): Promise<void> {
let lastError: unknown;
for (let i = 0; i < 20; i += 1) {
try {

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.

[Critical] No test exercises the resolvePermissionOptionId fallback chain when onPermissionRequest returns { optionId: undefined }. The four-stage fallback (allow_once kind -> proceed_once id -> namespaced proceed_once -> options[0]) is completely untested.

Fix: add a test that provides options with { kind: "allow_once", id: "test" } and returns { optionId: undefined } from onPermissionRequest, verifying allow_once-test is sent.

await this.handlePermissionRequest(event.data);
break;
case 'session_died':
this.handleSessionDied(event.data);

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.

[Suggestion] event.data is cast to SessionNotification with as and no runtime validation. Malformed SSE data flows unvalidated to webview consumers. Consider adding a isSessionNotificationData() guard analogous to isPermissionRequestData().

});
if (
!askResponse.optionId ||
this.isCancelledOption(askResponse.optionId)

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.

[Suggestion] isPermissionRequestData validates the outer shape but not the inner rawInput content. questions is only checked via Array.isArray, not element structure. Consider adding element-level validation before casting.


interface FakeSession extends DaemonIdeSessionClient {
prompt: ReturnType<typeof vi.fn>;
cancel: ReturnType<typeof vi.fn>;

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.

[Suggestion] EventQueue.fail() sets this.failure but does not drain this.waiters. If a waiter is pending when fail() is called, the test hangs until timeout. Consider calling this.close() to drain waiters, or rejecting them with the error.

@wenshao wenshao 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.

PR Review: feat(ide): add daemon connection spike

Metric Value
Files reviewed 3 (code + test + design doc)
Lines of code 992 (451 production + 541 test)
Build status clean
Test status pass
Deterministic analysis 0 findings (tsc + eslint clean)
Findings 5 Critical, 3 Suggestion
Model mimo-v2.5-pro
Iterations 2 (reverse audit found 1 additional Critical)

Critical (5)

  1. pumpEvents finally block eventPump cleanup always fails - this.session === session guard always evaluates to false because clearCurrentSession nulls this.session on all three exit paths (session_died, stream_ended, daemon_error) before pumpEvents reaches its finally block. this.eventPump remains a stale resolved promise after the pump is dead.

  2. disconnect() deadlocks when permission callback is in-flight - abort() terminates the SSE generator but cannot interrupt the JS-level await this.onPermissionRequest(request). disconnect() awaits a pump that is blocked on a user callback that will never return.

  3. handleSessionDied doesn't validate sessionId - On reconnect with resume: true, the daemon may replay the old session_died event. No data.sessionId !== this.session.sessionId guard exists, so a stale event kills the freshly connected session.

  4. Concurrent connect() calls orphan the first session - No mutex or connectPromise guard. Two overlapping calls both create sessions. The second overwrites this.session and this.eventController. The first session's pump is orphaned; if it errors, it aborts the second session's controller (stale reference), killing the live stream with isConnected === true and no onDisconnected callback.

  5. resolvePermissionOptionId fallback chain completely untested - The four-stage fallback (allow_once kind -> proceed_once id -> namespaced proceed_once -> options[0]) is never exercised by any test.

Suggestion (3)

  1. event.data cast to SessionNotification without validation - this.onSessionUpdate(event.data as SessionNotification) with no runtime type guard. Malformed SSE data flows unvalidated to webview consumers.

  2. questions/metadata casts without validation - isPermissionRequestData validates outer shape but not rawInput content. questions is only checked as Array.isArray, not element structure.

  3. EventQueue.fail() does not resolve pending waiters - fail() sets this.failure but doesn't drain this.waiters. Tests calling fail() with a pending waiter hang until timeout.


Fix guidance for Critical findings:

  1. Remove the if (this.session === session) guard - this.eventPump = null; unconditionally in the finally block.
  2. Thread the abort signal through permission resolution and race the user callback against it.
  3. Guard with if (data.sessionId !== this.session.sessionId) return; before tearing down.
  4. Add a connectPromise guard to serialize concurrent connect() calls.
  5. Add a test with { optionId: undefined } return and kind: 'allow_once' option.

toSafeErrorMessage(error),
);
this.eventController?.abort();
this.clearCurrentSession(session, 'daemon_error');

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.

[Critical] The if (this.session === session) guard always evaluates to false - clearCurrentSession() sets this.session = null on all three exit paths (session_died, stream_ended, daemon_error) before this finally block runs. this.eventPump remains a stale resolved-promise reference after the pump is dead.

Fix: remove the guard - set this.eventPump = null; unconditionally.

return await this.ensureSession().setModel(modelId);
}

async disconnect(): Promise<void> {

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.

[Critical] Deadlock scenario: when the pump is inside handlePermissionRequest -> await this.onPermissionRequest(request), abort() terminates the SSE generator but not the JS-level await on the user callback. disconnect() then hangs on await this.eventPump forever.

Fix: thread the abort signal into permission resolution and race onPermissionRequest against it, or use AbortSignal.any() to combine the permission abort with the controller signal.

this.eventController?.abort();
if (this.session) {
this.clearCurrentSession(this.session, reason);
} else {

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.

[Critical] On reconnect with resume: true, the daemon may replay the old session_died event from the prior session. This handler never checks data.sessionId against this.session.sessionId, so a stale event tears down the freshly connected session.

Fix: guard with if (data.sessionId !== this.session.sessionId) return; before calling clearCurrentSession().

onEndTurn: (reason?: string) => void = () => {};
onDisconnected: (code: number | null, signal: string | null) => void =
() => {};

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.

[Critical] No mutex - concurrent connect() calls both pass the this.session guard, both create sessions and SSE streams. The second call overwrites this.session and this.eventController. The first session's pump is orphaned; if it errors, it reads the stale this.eventController reference (now belonging to session 2) and aborts the live stream. isConnected stays true with no onDisconnected callback.

Fix: add a private connectPromise: Promise<void> | null = null; guard and return/reuse the in-flight promise.

cancel: vi.fn().mockResolvedValue(undefined),
setModel: vi.fn().mockResolvedValue({}),
respondToPermission: vi.fn().mockResolvedValue(true),
};

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.

[Critical] No test exercises the resolvePermissionOptionId fallback chain when onPermissionRequest returns { optionId: undefined }. The four-stage fallback (allow_once kind -> proceed_once id -> namespaced proceed_once -> options[0]) is completely untested.

Fix: add a test that provides options with { kind: "allow_once", id: "test" } and returns { optionId: undefined } from onPermissionRequest, verifying allow_once-test is sent.

private async handleEvent(event: DaemonIdeEvent): Promise<void> {
switch (event.type) {
case 'session_update':
this.onSessionUpdate(event.data as SessionNotification);

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.

[Suggestion] event.data is cast to SessionNotification with as and no runtime validation. Malformed SSE data flows unvalidated to webview consumers. Consider adding a isSessionNotificationData() guard analogous to isPermissionRequestData().


if (isAskUserQuestion) {
const askResponse = await this.onAskUserQuestion({
sessionId: request.sessionId,

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.

[Suggestion] isPermissionRequestData validates the outer shape but not the inner rawInput content. questions is only checked via Array.isArray, not element structure. Consider adding element-level validation before casting.

}

fail(error: unknown): void {
this.failure = error;

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.

[Suggestion] EventQueue.fail() sets this.failure but does not drain this.waiters. If a waiter is pending when fail() is called, the test hangs until timeout. Consider calling this.close() to drain waiters, or rejecting them with the error.

toSafeErrorMessage(error),
);
this.eventController?.abort();
this.clearCurrentSession(session, 'daemon_error');

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.

[Critical] The if (this.session === session) guard always evaluates to false - clearCurrentSession() sets this.session = null on all three exit paths (session_died, stream_ended, daemon_error) before this finally block runs. this.eventPump remains a stale resolved-promise reference after the pump is dead.

Fix: remove the guard - set this.eventPump = null; unconditionally.

return await this.ensureSession().setModel(modelId);
}

async disconnect(): Promise<void> {

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.

[Critical] Deadlock scenario: when the pump is inside handlePermissionRequest -> await this.onPermissionRequest(request), abort() terminates the SSE generator but not the JS-level await on the user callback. disconnect() then hangs on await this.eventPump forever.

Fix: thread the abort signal into permission resolution and race onPermissionRequest against it, or use AbortSignal.any() to combine the permission abort with the controller signal.

this.eventController?.abort();
if (this.session) {
this.clearCurrentSession(this.session, reason);
} else {

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.

[Critical] On reconnect with resume: true, the daemon may replay the old session_died event from the prior session. This handler never checks data.sessionId against this.session.sessionId, so a stale event tears down the freshly connected session.

Fix: guard with if (data.sessionId !== this.session.sessionId) return; before calling clearCurrentSession().

onEndTurn: (reason?: string) => void = () => {};
onDisconnected: (code: number | null, signal: string | null) => void =
() => {};

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.

[Critical] No mutex - concurrent connect() calls both pass the this.session guard, both create sessions and SSE streams. The second call overwrites this.session and this.eventController. The first session's pump is orphaned; if it errors, it reads the stale this.eventController reference (now belonging to session 2) and aborts the live stream. isConnected stays true with no onDisconnected callback.

Fix: add a private connectPromise: Promise<void> | null = null; guard and return/reuse the in-flight promise.

cancel: vi.fn().mockResolvedValue(undefined),
setModel: vi.fn().mockResolvedValue({}),
respondToPermission: vi.fn().mockResolvedValue(true),
};

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.

[Critical] No test exercises the resolvePermissionOptionId fallback chain when onPermissionRequest returns { optionId: undefined }. The four-stage fallback (allow_once kind -> proceed_once id -> namespaced proceed_once -> options[0]) is completely untested.

Fix: add a test that provides options with { kind: "allow_once", id: "test" } and returns { optionId: undefined } from onPermissionRequest, verifying allow_once-test is sent.

private async handleEvent(event: DaemonIdeEvent): Promise<void> {
switch (event.type) {
case 'session_update':
this.onSessionUpdate(event.data as SessionNotification);

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.

[Suggestion] event.data is cast to SessionNotification with as and no runtime validation. Malformed SSE data flows unvalidated to webview consumers. Consider adding a isSessionNotificationData() guard analogous to isPermissionRequestData().


if (isAskUserQuestion) {
const askResponse = await this.onAskUserQuestion({
sessionId: request.sessionId,

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.

[Suggestion] isPermissionRequestData validates the outer shape but not the inner rawInput content. questions is only checked via Array.isArray, not element structure. Consider adding element-level validation before casting.

}

fail(error: unknown): void {
this.failure = error;

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.

[Suggestion] EventQueue.fail() sets this.failure but does not drain this.waiters. If a waiter is pending when fail() is called, the test hangs until timeout. Consider calling this.close() to drain waiters, or rejecting them with the error.

}
if (url.username || url.password) {
throw new Error('Daemon baseUrl must not contain credentials');
}

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.

[Suggestion] isPermissionRequestData validates the outer shape (requestId, toolCall, options) but not the inner rawInput content. The rawInput flows into resolvePermissionResponse where rawInput['questions'] is cast to AskUserQuestionRequest['questions'] without checking that each element has the required question and options fields.

A malformed rawInput with {questions: [{}]} would pass the guard and produce a broken permission prompt downstream.

case 'session_update':
this.onSessionUpdate(event.data as SessionNotification);
break;
case 'permission_request':

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.

[Suggestion] event.data as SessionNotification is an unsafe cast on untrusted data received over SSE from the daemon. If the daemon sends a session_update event with a different shape (e.g. during a protocol version mismatch), the cast succeeds at runtime and the caller receives data that doesn't conform to the SDK type — potentially crashing the onSessionUpdate callback.

The existing isPermissionRequestData guard in the same switch statement demonstrates the right pattern. Add a similar shape check for session_update events, or at minimum catch the downstream callback to prevent the event handler from swallowing subsequent events.

},
"dependencies": {
"@agentclientprotocol/sdk": "^0.14.1",
"@qwen-code/sdk": "*",

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.

[Suggestion] "@qwen-code/sdk": "*" resolves to any version. Since the SDK is dynamically imported at runtime, a breaking change in the SDK's export shape would silently produce a runtime "Loaded @qwen-code/sdk does not expose daemon clients" error instead of a clear install-time version mismatch.

Consider pinning to a minimum major version or using a caret range (^1.0.0) to get install-time warnings on breaking changes.

@wenshao wenshao 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.

Code Review: DaemonIdeConnection

Well-structured spike that cleanly mirrors the ACP ProcessConnection shape while swapping the child-process transport for an HTTP/SSE daemon session. The test suite is thorough (17 cases, 766 lines) and the separation of concerns (sessionFactory, event pump lifecycle, pumpGeneration guard) is solid.

Key concerns

Severity Area Issue
Critical SSRF validateDaemonBaseUrl doesn't restrict to loopback - workspace settings.json can redirect the daemon URL and exfiltrate the auth token
Critical Resilience sendPrompt can hang forever if the daemon dies mid-request - no try/catch, no AbortSignal, no timeout
Suggestion Observability pumpEvents error logs lack session ID; clearCurrentSession is completely silent
Suggestion Robustness Unsafe as SessionNotification cast on untrusted data; isPermissionRequestData doesn't validate rawInput inner shape
Suggestion Supply chain @qwen-code/sdk wildcard dependency
Suggestion Debuggability Permission handler catch loses request context

Architecture notes

  • new Function bypass for esbuild is pragmatic for the spike but should be documented with a SECURITY comment.
  • onDisconnected default is a no-op - production consumers MUST override this or silently lose sessions.
  • isConnected vs hasActiveSession are identical - differentiate or deduplicate.

@tanzhenxin tanzhenxin added the type/feature-request New feature or enhancement request label May 17, 2026
@chiga0

chiga0 commented May 17, 2026

Copy link
Copy Markdown
Collaborator Author

Generated by GPT-5 model

Handled the latest valid review comments in b3e660feb:

  • Replaced the hidden dynamic import of @qwen-code/sdk with a normal static import so the SDK is visible to the extension bundle/VSIX packaging path instead of depending on a runtime-only module lookup.
  • Restricted daemon base URLs to loopback hosts and kept scheme/credential validation, so workspace settings cannot redirect the IDE daemon token to a remote endpoint. The normalized URL is now passed downstream.
  • Forwarded the active abort signal into sendPrompt() and now calls onEndTurn('error') when the prompt request rejects, preventing the webview from staying in a running state after daemon/network failure.
  • Captured the session around permission handling so a session death or reconnect during user interaction cannot send the response to a new session. Permission response failures are logged and leave the replay cursor unadvanced so the event can be retried instead of being burned.
  • AskUserQuestion now cancels when the selected option is not advertised by the daemon, rather than sending a stale/raw option id.
  • Missing/empty normal permission option ids now cancel instead of falling through to the allow fallback.
  • session_died without a valid current session id is treated as malformed/stale and no longer tears down the active session.
  • Fixed the optionId.includes() crash by using optional chaining.
  • Added tests for prompt abort/error handling, invalid AskUserQuestion selections, malformed/stale session death, permission response failure replay behavior, and loopback-only URL validation.

Validation run locally:

  • cd packages/vscode-ide-companion && npx vitest run src/services/daemonIdeConnection.test.ts
  • cd packages/vscode-ide-companion && npm run check-types
  • cd packages/vscode-ide-companion && npx eslint src/services/daemonIdeConnection.ts src/services/daemonIdeConnection.test.ts --max-warnings 0 --no-warn-ignored
  • cd packages/vscode-ide-companion && npm run build

Notes / triage:

  • The connect serialization, disconnect while permission UI is pending, EventQueue.fail, and pumpGeneration comments are already covered by current tests/implementation.
  • The HTTP response vs SSE end-of-turn ordering gap still exists as a daemon protocol/waterline issue; this PR now avoids additional local hangs, but full parity still depends on the typed terminal event work rather than being solved entirely in the IDE adapter.

@wenshao wenshao 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.

Code Review: DaemonIdeConnection

Overall: Well-structured code with solid error handling. TypeScript compiles cleanly, 18/18 tests pass. Previous review feedback has been addressed.

Critical Issues

  1. UI Forgery via rawInput.questions injection (L362): The permission routing logic checks rawInput['questions'] to decide whether to invoke onAskUserQuestion vs onPermissionRequest. A malicious daemon can inject questions into any permission_request (e.g., kind: 'execute_command') to replace the real approval UI with a fake survey. The user's answer is then mapped to a permission optionId, silently approving the original tool call. Fix: gate on request.toolCall?.kind === 'ask_user_question' instead of (or in addition to) rawInput.questions.

  2. clearCurrentSession is completely silent (L560): The most critical state transition — nulling out eventController, eventPump, session, and firing onDisconnected — has zero logging. The reason parameter is available but unused.

  3. No sessionId in any log message: All console.warn/console.debug calls use the generic [DaemonIdeConnection] prefix with no session identifier. Under concurrent sessions, logs are uncorrelatable.

Suggestions

  1. Dead fallback code in resolvePermissionOptionId — the allow_once/proceed_once chain is unreachable with the default callback.
  2. handleSessionDied extracts data['reason'] but never logs it.
  3. sendPrompt and connectInternal have zero observability.
  4. Divergent loopback validation (isLoopbackHostname accepts 127.0.0.0/8, server uses fixed allowlist).
  5. Dual replay cursors (connection vs SDK) are correct but undocumented.

Inline comments below with details and suggested fixes.

}
}

private async handlePermissionRequest(

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.

[Critical] UI Forgery via rawInput.questions injection.

This routes on rawInput.questions presence, not request.toolCall.kind. A malicious daemon can inject questions into a permission_request with kind: 'execute_command', replacing the real approval UI with a fake survey. The user's answer maps to a permission optionId, silently approving the original tool call.

Fix — gate on toolCall.kind:

const isAskUserQuestion =
  request.toolCall?.kind === 'ask_user_question' &&
  isRecord(rawInput) &&
  Array.isArray(rawInput['questions']);
if (isAskUserQuestion) {

This matches the discriminator used in WebViewProvider.ts:506.

this.eventController = null;
this.eventPump = null;
this.session = null;
this.onDisconnected(null, reason);

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.

[Critical] clearCurrentSession is completely silent.

This tears down all session state and fires onDisconnected with zero logging. At 3 AM when a user reports "my session disappeared," there is no log entry telling you when, why, or what the sessionId was.

Suggested fix:

private clearCurrentSession(
  session: DaemonIdeSessionClient,
  reason: string,
): void {
  if (this.session !== session) {
    return;
  }
  console.log('[DaemonIdeConnection] Clearing session:', {
    sessionId: session.sessionId,
    reason,
  });
  this.eventController = null;
  this.eventPump = null;
  this.session = null;
  this.onDisconnected(null, reason);
}

}
this.eventController = null;
this.eventPump = null;
if (session && this.session === session) {

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.

[Critical] No sessionId in any log message.

All console.warn/console.debug calls in this file use the generic [DaemonIdeConnection] prefix with no session identifier. When multiple daemon sessions exist (the code explicitly supports concurrent connects), logs from different sessions are indistinguishable.

Suggested fix — add { sessionId } to all log objects, e.g.:

console.warn('[DaemonIdeConnection] Event stream failed:', {
  sessionId: session.sessionId,
  error: toSafeErrorMessage(error),
});

This applies to all ~8 log statements in the file.

}

private resolvePermissionOptionId(
request: RequestPermissionRequest,

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.

[Suggestion] Dead fallback code.

With the default onPermissionRequest callback returning { optionId: 'cancel' }, preferredOptionId is always 'cancel', so the if (preferredOptionId) branch at L521 always returns before reaching this fallback chain. The allow_once/proceed_once logic is unreachable in the default configuration.

If a custom callback returns {} (no optionId), this fallback auto-selects allow_once — which could be a privilege escalation vector if the daemon controls the kind field.

Suggested fix — either remove the dead fallback or document the contract that integrators must NOT return {} from onPermissionRequest.

? data['reason']
: 'session_died';
this.eventController?.abort();
this.clearCurrentSession(this.session, reason);

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.

[Suggestion] handleSessionDied extracts the death reason but never logs it.

The reason variable is computed here and passed to clearCurrentSession, but there's no log statement. When the daemon kills a session (OOM, admin action, crash), the reason is available in the event payload but invisible in the extension host logs.

Suggested fix — add before the abort:

console.log('[DaemonIdeConnection] Session died:', {
  sessionId: this.session.sessionId,
  reason,
});

return typeof value === 'object' && value !== null;
}

function isLoopbackHostname(hostname: string): boolean {

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.

[Suggestion] Divergent loopback validation.

This accepts the entire 127.0.0.0/8 range (127.x.y.z), while the canonical isLoopbackBind in packages/cli/src/serve/loopbackBinds.ts uses a fixed allowlist (127.0.0.1, localhost, ::1, [::1]). The two functions answer the same semantic question with different answers and live in separate packages with no shared import.

If someone adds host.docker.internal to the server-side set, they'll never discover this independent reimplementation. The client will continue to reject URLs the server now accepts.

Suggested fix — add a cross-reference comment: // Keep in sync with packages/cli/src/serve/loopbackBinds.ts

isRecord(value['toolCall']) &&
Array.isArray(value['options'])
);
}

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.

[Suggestion] Dual replay cursors — correct but undocumented.

Both DaemonIdeConnection and DaemonSessionClient maintain independent lastSeenEventId cursors. The connection cursor skips advancing when handleEvent returns false (failed permission response). The SDK cursor always advances. On reconnect, a fresh SDK instance is seeded from the connection's cursor.

This design is correct but a developer reading the SDK's subscribeEvents might "clean up" the redundant-looking connection cursor, silently dropping permission requests that fail.

Suggested fix — add a comment explaining the intentional divergence:

// Authoritative replay cursor. Intentionally diverges from the SDK's
// cursor on permission response failures to ensure at-least-once delivery.
private lastSeenEventId: string | undefined;


async cancelSession(): Promise<void> {
const session = this.session;
if (!session) {

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.

[Suggestion] sendPrompt has zero observability.

No logging on entry, success, or error path. If a user reports "my prompt was sent but nothing happened," there is no log trail showing whether the prompt was dispatched, whether the daemon responded, or what error occurred.

Suggested fix — add entry/exit/error logging with sessionId and stopReason.

const requestId = data['requestId'];
const request = data;
const session = this.session;
if (!session) {

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.

[Suggestion] handlePermissionRequest silently drops permission requests when there is no active session.

When this.session is null (e.g., after a disconnect during permission resolution), the method returns true with no log:

const session = this.session;
if (!session) {
  return true;  // no console.warn, no debug info
}

This makes production debugging impossible — the daemon will timeout waiting for a response, but the IDE side shows no trace of the dropped request.

Suggested change
if (!session) {
if (!session) {
console.warn('[DaemonIdeConnection] Dropping permission request: not connected', {
requestId: data['requestId'],
});
return true;
}

— DeepSeek/deepseek-v4-pro via Qwen Code /review

if (!response) {
return true;
}
if (this.session !== session) {

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.

[Suggestion] Permission response silently dropped when session changes during resolution.

After await this.resolvePermissionResponseUntilAbort(...), the code checks if (this.session !== session) return true; — but the user's permission choice (which may have taken seconds to collect) is discarded without any log. The daemon waits for a response that will never arrive.

Suggested change
if (this.session !== session) {
if (this.session !== session) {
console.warn('[DaemonIdeConnection] Permission response dropped: session changed', {
requestId,
originalSessionId: session?.sessionId,
});
return true;
}

— DeepSeek/deepseek-v4-pro via Qwen Code /review

isRecord(data) && typeof data['sessionId'] === 'string'
? data['sessionId']
: undefined;
if (!this.session) {

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.

[Suggestion] handleSessionDied no longer calls onDisconnected when session is already null.

The old code had a safety net:

if (this.session) {
  this.clearCurrentSession(this.session, reason);
} else {
  this.onDisconnected(null, reason);  // safety net
}

The new code returns early without notifying consumers. If a duplicate session_died event arrives (e.g., replay + real-time), the second one is silently swallowed instead of at least logging the condition.

Suggested change
if (!this.session) {
if (!this.session) {
console.debug('[DaemonIdeConnection] session_died received with no active session', {
eventSessionId,
});
return;
}

— DeepSeek/deepseek-v4-pro via Qwen Code /review

return typeof value === 'object' && value !== null;
}

function isLoopbackHostname(hostname: string): boolean {

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.

[Suggestion] isLoopbackHostname rejects IPv4-mapped IPv6 loopback addresses.

::ffff:127.0.0.1 (common in dual-stack environments and container runtimes) fails all three checks — it's not localhost, not ::1, and doesn't match the 127.x.x.x IPv4 pattern after bracket stripping. This causes a confusing error: "Daemon baseUrl must target a loopback address, got '::ffff:127.0.0.1'" even though the address is functionally equivalent to 127.0.0.1.

Suggested change
function isLoopbackHostname(hostname: string): boolean {
if (normalized.startsWith('::ffff:')) {
const ipv4 = normalized.slice(7);
if (ipv4.startsWith('127.')) {
const parts = ipv4.split('.');
if (parts.length === 4 && parts.every(p => /^\d+$/.test(p) && +p >= 0 && +p <= 255)) {
return true;
}
}
}

— DeepSeek/deepseek-v4-pro via Qwen Code /review

`Daemon baseUrl must target a loopback address, got "${url.hostname}"`,
);
}
return url.href;

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.

[Suggestion] validateDaemonBaseUrl now returns url.href instead of raw baseUrl.

new URL('http://127.0.0.1:4170').href produces 'http://127.0.0.1:4170/' (with trailing slash). If DaemonClient internally uses string concatenation for URL construction (baseUrl + 'api/...'), this creates double-slash paths like http://127.0.0.1:4170//api/....

Verify that @qwen-code/sdk DaemonClient uses new URL(path, baseUrl) for path construction. If it uses string concatenation, strip the trailing slash:

Suggested change
return url.href;
return url.href.replace(/\/$/, '');

— DeepSeek/deepseek-v4-pro via Qwen Code /review

SessionNotification,
} from '@agentclientprotocol/sdk';
import {
DaemonClient,

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.

[Suggestion] Dynamic import bypass removed without preserving rationale comment.

The old code used new Function('specifier', 'return import(specifier)') with an explicit comment: "Uses new Function to bypass esbuild static analysis so @qwen-code/sdk is loaded dynamically at runtime rather than bundled into the extension." Now that the SDK is statically imported, the rationale for why the bypass existed (and what constraints the SDK must satisfy for safe bundling — pure JS, no native addons, no __dirname usage) is lost. Future maintainers upgrading the SDK won't know what can silently break.

Add a comment in esbuild.js or above the import documenting that @qwen-code/sdk is intentionally bundled and must remain pure JS with no native dependencies.

— DeepSeek/deepseek-v4-pro via Qwen Code /review

}
this.eventController = null;
this.eventPump = null;
if (session && this.session === session) {

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.

[Suggestion] disconnect() skips onDisconnected callback when session is already null.

The guard if (session && this.session === session) means callers who invoke disconnect() on an already-disconnected connection never receive the onDisconnected(null, 'disconnected') callback. This is an asymmetric API — the method name implies it always completes disconnection, but the callback only fires if there was an active session.

Suggested change
if (session && this.session === session) {
if (session && this.session === session) {
this.session = null;
this.onDisconnected(null, 'disconnected');
} else if (!session) {
this.onDisconnected(null, 'disconnected');
}

— DeepSeek/deepseek-v4-pro via Qwen Code /review

request,
askResponse.optionId,
);
if (!optionId) {

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.

[Suggestion] Silent cancellation when resolvePermissionOptionId returns undefined.

When askResponse.optionId is provided but doesn't match any option in request.options, resolvePermissionOptionId returns undefined and the response is silently cancelled. The user selected an option, nothing happened, and there is no console warning explaining why. This will be very hard to debug in production.

Suggested change
if (!optionId) {
if (!optionId) {
if (askResponse.optionId) {
console.warn('[DaemonIdeConnection] Preferred optionId not in request options, cancelling:',
askResponse.optionId);
}
return { outcome: { outcome: 'cancelled' } };
}

— DeepSeek/deepseek-v4-pro via Qwen Code /review

this.onEndTurn(response.stopReason);
return response;
} catch (error) {
this.onEndTurn('error');

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.

[Suggestion] sendPrompt conflates AbortError with genuine prompt failures.

When disconnect() aborts the shared eventController, the AbortSignal passed to session.prompt() triggers an AbortError. The catch block calls onEndTurn('error') without distinguishing deliberate disconnections from real failures. Callers listening to onEndTurn will see spurious error states on every clean disconnect while a prompt is in flight.

Suggested change
this.onEndTurn('error');
} catch (error) {
if (!(error instanceof DOMException && error.name === 'AbortError')) {
this.onEndTurn('error');
}
throw error;
}

— DeepSeek/deepseek-v4-pro via Qwen Code /review

@chiga0
chiga0 force-pushed the feat/ide-daemon-adapter branch from b3e660f to fe40ad8 Compare May 17, 2026 15:50
@chiga0

chiga0 commented May 17, 2026

Copy link
Copy Markdown
Collaborator Author

Generated by GPT-5 model

Handled the latest valid IDE daemon adapter review comments in fe40ad852 after rebasing onto main.

Fixes included:

  • Gated AskUserQuestion handling on toolCall.kind === "ask_user_question" as well as rawInput.questions, so a normal tool permission request cannot spoof the question UI by injecting a questions field. Added regression coverage for that forgery path.
  • Added safer diagnostics around prompt dispatch/failure, session clearing, session_died, dropped permission responses, and event handler failures, with session/request context where available.
  • Documented the intentionally static SDK import and the authoritative IDE replay cursor, and added a loopback-policy cross-reference plus IPv4-mapped loopback handling.
  • Kept stale/false-positive items triaged: SDK bundling, prompt abort/error handling, permission response retry waterline, stale session_died, optionId optional chaining, connect recovery, permission handler failures, and pumpGeneration coverage are already handled on current head.

Local validation run after the change:

  • cd packages/vscode-ide-companion && npx vitest run src/services/daemonIdeConnection.test.ts
  • cd packages/vscode-ide-companion && npm run check-types
  • cd packages/vscode-ide-companion && npx eslint src/services/daemonIdeConnection.ts src/services/daemonIdeConnection.test.ts --max-warnings 0 --no-warn-ignored
  • cd packages/vscode-ide-companion && npm run build

CI status at this check: latest Classify PR and Lint are green; macOS/Ubuntu/Windows tests and CodeQL are still running for the new head.

: undefined;
}

return (

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.

Critical: Dead fallback code in resolvePermissionOptionId

The auto-select fallback chain at lines 612–619 (allow_once kind → proceed_once id → namespaced proceed_onceoptions[0]) is unreachable dead code.

Why: Both callers (line 507 for AskUserQuestion, line 537 for regular permission) guard with !response.optionId || isCancelledOption(response.optionId) before calling resolvePermissionOptionId, ensuring preferredOptionId is always truthy when it reaches this function. The if (preferredOptionId) guard at line 606 then always short-circuits, making the fallback block dead code.

This also masks a behavioral divergence from AcpConnection: when preferredOptionId is truthy but absent from request.options, AcpConnection (line 426) falls through to its auto-select chain, while DaemonIdeConnection returns undefined (cancelling the request). This divergence is undocumented.

Suggested fix: Remove the dead fallback chain and add a comment documenting the intentional difference:

private resolvePermissionOptionId(
  request: RequestPermissionRequest,
  preferredOptionId?: string,
): string | undefined {
  const options = Array.isArray(request.options) ? request.options : [];
  if (options.length === 0) {
    return undefined;
  }

  // Unlike AcpConnection, we cancel on mismatch rather than falling
  // through to a daemon-controlled auto-select chain.
  if (preferredOptionId) {
    return options.some((option) => option.optionId === preferredOptionId)
      ? preferredOptionId
      : undefined;
  }

  return undefined;
}

() => {};

async connect(options: DaemonIdeConnectionOptions): Promise<void> {
while (this.connectPromise) {

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.

Suggestion: Untested AbortError path in sendPrompt

The isAbortError guard at line 184 is the primary cancellation behavior — it determines whether onEndTurn('error') is called during user abort. The test suite has no test for session.prompt() rejecting with an AbortError. A regression here would cause spurious end-turn callbacks during abort flows.

Suggested fix: Add a test:

it('does not call onEndTurn when the prompt is aborted', async () => {
  const events = new EventQueue();
  const session = createFakeSession(events);
  const connection = new DaemonIdeConnection();
  const onEndTurn = vi.fn();
  connection.onEndTurn = onEndTurn;

  await connection.connect({
    baseUrl: 'http://127.0.0.1:4170',
    sessionFactory: vi.fn().mockResolvedValue(session),
  });

  const abortError = new DOMException('Aborted', 'AbortError');
  session.prompt.mockRejectedValueOnce(abortError);
  await expect(connection.sendPrompt('abort me')).rejects.toThrow();

  expect(onEndTurn).not.toHaveBeenCalled();

  events.close();
  await connection.disconnect();
});

return undefined;
}

if (preferredOptionId) {

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.

Critical: Behavioral divergence from AcpConnection

When preferredOptionId is truthy but absent from request.options:

  • AcpConnection (line 426): if (preferredOptionId && options.some(...)) → falls through to auto-select fallback chain
  • DaemonIdeConnection (line 606): if (preferredOptionId) → returns undefined (cancels)

The refactoring that restructured this guard produced a subtly different behavior. Since both callers currently guard against empty optionId (making this path dead — see comment on line 612), the divergence has no runtime impact today. However, if a future caller invokes resolvePermissionOptionId(request) without a preferred option (e.g. for a default-allow flow), the AcpConnection version would auto-select while the DaemonIdeConnection version would cancel — a latent correctness risk.

Suggested fix: After removing the dead fallback (see comment on line 612), add a cross-reference comment: // Keep semantics in sync with AcpConnection.resolvePermissionOptionId. If the divergence is intentional, document it explicitly.

@wenshao wenshao 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.

本地完整跑了一遍(19/19 vitest + lint + prettier + build 都过,check-types 重建 core dist 后过;CI 全绿)。13 轮 review 的 critical 我对照 HEAD fe40ad852 源码逐条对账,真有运行时风险的全部修了(详见前次评论里我贴的 inline 表)。

下面 2 处是 spike → wire-up 之前应该收掉的清洁性问题,不是运行时 bug,但 merge 前希望清掉,避免给未来的 wire-up PR 留坑:

  1. resolvePermissionOptionId 的 fallback chain 当前无 caller 走得到(dead code)
  2. SDK static import + extension 入口未引 + tree-shake → VSIX 是否能在 wire-up 后正确包含 SDK 仍未实测

这 2 处收完就 approve。

// explicit allow_once kind first, then tolerate namespaced proceed_once.
options.find((option) => option.optionId?.includes('proceed_once'))
?.optionId ||
options[0]?.optionId

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.

[Cleanup before wire-up] resolvePermissionOptionId 的 fallback chain(allow_once kind → proceed_once id → namespaced proceed_onceoptions[0])当前无 caller 走得到

两个 caller(resolvePermissionResponse 走常规权限路径 L532-537,以及 AskUserQuestion 路径 L507)都遵循同一个模式:先在外层 !response.optionId || isCancelledOption(...) 早退到 cancelled,否则把 response.optionId 作为 preferredOptionId 传进来。preferredOptionId 一旦非空,下面 L606-609 的 strict-match 分支直接 return,永远不会落到 L612-619 的 fallback chain。

之前 round-9/10 review 标的 "fallback chain 未测"——根因不是缺测试,是这段代码根本走不到。要么补一个真用得上的 caller(比如某个流场景允许 caller 不指定优先选项、让 bridge 智能选),要么直接删掉这段 fallback 减少未来维护误读。两条都行,我倾向删(之前 PR 4203 同款判断逻辑你也走的极简)。

import {
DaemonClient,
DaemonSessionClient as SdkDaemonSessionClient,
} from '@qwen-code/sdk';

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.

[Cleanup before wire-up] @qwen-code/sdk 是 static import,esbuild.js 又只把 vscode 列 external,理论上 esbuild 会把 SDK inline 进 extension.cjs。但当前 extension.cjsgrep -c "DaemonIdeConnection\|DaemonClient\|SdkDaemonSessionClient" 都是 0 —— 整个适配器被 tree-shake 掉了(因为 extension 入口还没引)。

这本身合理(PR 描述里写了 default-off + 未 wire 到 QwenAgentManager),但意味着 VSIX 里有没有 SDK 是未经实测的。等做 wire-up 那个 PR 时,本仓库用 vsce package --no-dependencies 打包,.vscodeignore 只放行 dist/,所以 SDK 必须靠 esbuild 完整 inline 进 bundle 才能跑得起来。

建议这个 PR 顺便加一个 build-time assertion 或一个空的 void DaemonIdeConnection; 之类的 retain hint 强制 esbuild 把它打进 extension.cjs,这样 bundle 步骤就能在本 PR 验证;或者明确在 PR 描述里把"wire-up 时验证 VSIX 含 SDK"列成 follow-up 必检项。

@chiga0

chiga0 commented May 18, 2026

Copy link
Copy Markdown
Collaborator Author

Processed the latest review items in #4199 with a narrow adapter-only change:

  • Removed the dead/implicit permission option fallback. The daemon IDE adapter now only selects an option when the client handler returns a non-cancel optionId that is explicitly present in the request options; missing/stale selections still resolve as cancelled.
  • Kept the dormant daemon IDE adapter on the VSIX bundle path by exporting the SDK factory from the extension entry without wiring it into the active extension flow. Verified npm run build:prod includes the daemon SDK/session code in dist/extension.cjs.

Validation run locally:

  • cd packages/vscode-ide-companion && npx vitest run src/services/daemonIdeConnection.test.ts
  • cd packages/vscode-ide-companion && npx vitest run src/extension.test.ts
  • cd packages/vscode-ide-companion && npx eslint src/extension.ts src/services/daemonIdeConnection.ts src/services/daemonIdeConnection.test.ts --max-warnings 0 --no-warn-ignored
  • cd packages/vscode-ide-companion && npm run build:prod
  • cd packages/vscode-ide-companion && npx prettier --check src/extension.ts src/services/daemonIdeConnection.ts src/services/daemonIdeConnection.test.ts

npm run check-types still fails on the existing packages/cli/src/acp-integration/session/HistoryReplayer.ts metadata comparison error, which is outside this PR branch/scope and not introduced by these changes.

I did not change the previously discussed false-positive areas: ask_user_question top-level answers remains intact because the daemon route preserves passthrough fields, and lastEventId still survives disconnect intentionally as the replay cursor.

Generated by GPT-5 Codex

@chiga0
chiga0 requested a review from wenshao May 18, 2026 02:03

@wenshao wenshao 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.

Re-approving after 1d9360f405 fix(ide): tighten daemon adapter review gaps. After the 40-review-deep iteration on this PR, my local re-audit lines up with the commit message — the substantive Critical concerns I raised across the 5/16–5/17 rounds are addressed in the current head.

Verified addressed in current code

  • Concurrent connect() mutex (was: no serialization → two SSE pumps + lost session ref): connectPromise field + while (this.connectPromise) serialization loop at top of connect().
  • validateDaemonBaseUrl hostname restriction (was: any hostname acceptable → SSRF / data exfil to arbitrary daemon): now restricts to loopback explicitly with "Daemon baseUrl must target a loopback address, got X" — daemonIdeConnection.ts:117.
  • onDisconnected callback never fired on disconnect() (was: webview UI never learned the session ended): now invoked at the two cleanup paths (disconnect() and the pump's terminal cleanup).
  • sendPrompt() had no try/catch (was: prompt failure left webview spinning forever): now wraps session.prompt(), calls onEndTurn('error') on non-abort errors.
  • Permission TOCTOU race + abort deadlock (was: await onPermissionRequest couldn't be interrupted by SSE abort): new resolvePermissionResponseUntilAbort(request, signal) handles signal-aware cancellation, and the post-await if (this.session !== session) guard drops responses for stale sessions.
  • if (this.session === session) always-false guard (was: pump cleanup could clobber newer pump's state): replaced with pumpGeneration generational guard.
  • Unknown event types silently discarded (was: zero observability for daemon changes): now console.debug('[DaemonIdeConnection] Ignoring daemon event', ...) records the event type.
  • Malformed permission data silently dropped: now console.warn('[DaemonIdeConnection] Malformed permission request data').

Local verification on 1d9360f405

  • Anti-corruption checks (3850/4253 reflex): daemonIdeConnection.ts 631 lines, daemonIdeConnection.test.ts 917 lines, extension.ts 422 lines. All LF, all /** license headers, 0 mojibake.
  • npx vitest run src/services/daemonIdeConnection.test.ts: 19 tests passed (PR body lists 5; the test suite grew 4× through review iteration).
  • Remote CI: Lint / CodeQL / Classify PR / Test mac · ubuntu SUCCESS; Test windows IN_PROGRESS at time of approval.

Note on the open review threads

GitHub still shows ~30 unresolved Critical-tagged threads. Most of those are stale — the same underlying concern (e.g. the deadlock pattern, the missing mutex) appears across 5+ review rounds, each anchored to a slightly different line as the code shifted. Could you batch-resolve those at your end so the UI state matches reality? I've covered the substantive ones above; if I missed anything that's still actually live in the current code, please leave a fresh comment so it stands out.

Scope reminders

  • This is the spike adapter, default-off, not wired into QwenAgentManager or webview.
  • File scope is contained to vscode-ide-companion/src/services/daemonIdeConnection.{ts,test.ts} plus a 4-line extension.ts registration.
  • Subsequent PRs need to handle: feature flag wiring, settings/env resolution, webview flow, IDE file-service boundary, reverse RPC for editor/browser/clipboard — all called out as out-of-scope in the PR description, which I'm holding you to.

LGTM.

@wenshao
wenshao merged commit 4ab20ff into main May 18, 2026
9 checks passed
@wenshao
wenshao deleted the feat/ide-daemon-adapter branch May 18, 2026 02:38
@yiliang114 yiliang114 added the skip-changelog Exclude from release notes label May 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-changelog Exclude from release notes type/feature-request New feature or enhancement request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants