Skip to content

feat(serve): add daemon-stamped client identity#4231

Merged
wenshao merged 2 commits into
mainfrom
feat/daemon-client-identity
May 17, 2026
Merged

feat(serve): add daemon-stamped client identity#4231
wenshao merged 2 commits into
mainfrom
feat/daemon-client-identity

Conversation

@chiga0

@chiga0 chiga0 commented May 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • What changed: adds a daemon-stamped client identity for qwen serve attach/create flows, returns the stamped id in session responses, accepts it back through X-Qwen-Client-Id, and uses trusted ids to stamp relevant daemon events with originatorClientId.
  • Why it changed: this is roadmap PR7 for daemon mode B, giving TUI, channel, web, IDE, and other clients a shared identity primitive before session-scoped permission routing.
  • Reviewer focus: confirm the id is generated/registered by the daemon, echoed only through the transport header, rejected on state-changing routes when unknown, and optional for backward compatibility.

Validation

  • Commands run:
    npm run typecheck --workspace packages/cli
    npm run typecheck --workspace packages/sdk-typescript
    cd packages/cli && npx vitest run src/serve/server.test.ts src/serve/httpAcpBridge.test.ts src/serve/eventBus.test.ts
    cd packages/sdk-typescript && npx vitest run test/unit/DaemonClient.test.ts test/unit/DaemonSessionClient.test.ts test/unit/daemonEvents.test.ts
    npm run lint --workspace packages/cli
    npx eslint packages/sdk-typescript/src/daemon/DaemonClient.ts packages/sdk-typescript/src/daemon/DaemonSessionClient.ts packages/sdk-typescript/src/daemon/types.ts packages/sdk-typescript/test/unit/DaemonClient.test.ts packages/sdk-typescript/test/unit/DaemonSessionClient.test.ts
    npm run build
  • Prompts / inputs used: daemon HTTP route and bridge unit-test requests with and without X-Qwen-Client-Id.
  • Expected result: old clients work without the header; new clients receive a daemon-issued id and can echo it for originator stamping.
  • Observed result: route, bridge, event bus, SDK client, and session client tests passed locally. npm run build passed with the existing vscode companion curly warning in editorGroupUtils.ts.
  • Quickest reviewer verification path: run the two targeted vitest commands above and inspect the new client_identity capability plus originatorClientId assertions in bridge tests.
  • Evidence: local test runs passed for 222 CLI tests and 76 SDK daemon tests.

Scope / Risk

  • Main risk or tradeoff: this does not introduce revocation or durable identity; ids are live-session scoped and reset when the daemon process/session goes away.
  • Not covered / not validated: full client adapters are intentionally not switched over in this PR; this only adds the shared primitive they will use.
  • Breaking changes / migration notes: none expected. The header is optional, old clients continue to work, and SDK clientId is optional for compatibility with older daemons.

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 macOS validation covered targeted typecheck, vitest, lint, and build. Windows/Linux left to CI.
  • npm run lint --workspace packages/sdk-typescript currently fails before reaching this PR's changed files due to an existing ESLint rule-loading error while linting ProcessTransport.test.ts; the changed SDK files were checked with root npx eslint.

Linked Issues / Bugs

@chiga0 chiga0 requested review from doudouOUC and wenshao May 17, 2026 06:56
@github-actions

Copy link
Copy Markdown
Contributor

📋 Review Summary

This PR implements daemon-stamped client identity for qwen serve, adding a new client_identity capability that enables TUI, channel, web, IDE, and other clients to share a common identity primitive. The implementation generates opaque client IDs at the daemon layer, echoes them through session responses, accepts them via the X-Qwen-Client-Id header, and uses trusted IDs to stamp daemon events with originatorClientId. The changes are well-structured, maintain backward compatibility, and follow the established security patterns in the codebase.

🔍 General Feedback

  • Strong security posture: The implementation correctly validates the client ID header with strict regex validation (/^[A-Za-z0-9._:-]+$/), length limits (128 chars), and proper error handling
  • Good backward compatibility: Old clients work without the header, new clients receive and can echo the daemon-issued ID
  • Clean separation of concerns: Client identity is handled at the HTTP transport layer, not embedded in request bodies
  • Comprehensive test coverage: Tests cover the happy path, header validation, permission voting with originator stamping, and rejection of unregistered client IDs
  • Consistent patterns: Follows the same error handling and validation patterns as existing code (e.g., WorkspaceMismatchError, path length validation)

🎯 Specific Feedback

🟡 High

  • File: packages/cli/src/serve/server.ts:~455-640 - The parseClientIdHeader function is called in multiple route handlers but returns null on validation failure. While the pattern of returning null to signal "stop processing" is used consistently, consider whether early-return patterns could be clearer. The current approach requires each caller to remember to check if (clientId === null) return; - a missing check could bypass validation. Consider a middleware-style approach or throwing a typed error instead.

  • File: packages/sdk-typescript/src/daemon/DaemonClient.ts:225-231 - The headers method accepts clientId as an optional parameter but doesn't validate it. While validation happens at the HTTP layer, adding a defensive check (or at least a comment explaining why validation is deferred to the server) would improve defense-in-depth.

🟢 Medium

  • File: packages/cli/src/serve/httpAcpBridge.ts - The BridgeSession interface (around line 93) doesn't include the clientId field, but the test assertions expect it. The actual session object returned by spawnOrAttach includes clientId, but the interface definition appears to be missing this field. This creates a type/documentation gap that could confuse consumers of the bridge API.

  • File: packages/sdk-typescript/src/daemon/types.ts:115-125 - The DaemonSession.clientId is marked optional with a good comment explaining older daemons omit it. However, consider adding a helper method or type guard (similar to requireWorkspaceCwd) for cases where callers need to assert the clientId is present before using it.

  • File: packages/cli/src/serve/capabilities.ts:40 - The new client_identity capability is added with since: 'v1'. This is correct for a backward-compatible addition, but consider whether this capability should have any associated feature flags or if it's truly always-on once the daemon supports it.

🔵 Low

  • File: packages/cli/src/serve/server.ts:~1000 - The CLIENT_ID_HEADER constant is defined as 'x-qwen-client-id' (lowercase). Express normalizes headers to lowercase, so this works, but the SDK uses 'X-Qwen-Client-Id' (mixed case) in the header value. Consider using a single source of truth for the header name to avoid potential confusion or future bugs if case sensitivity ever matters.

  • File: packages/sdk-typescript/src/daemon/DaemonSessionClient.ts:138-141 - The clientId getter returns this.session.clientId, which could be undefined for older daemons. Callers using this.clientId in method calls may inadvertently pass undefined frequently. Consider documenting this behavior more prominently or providing guidance on when to expect the value.

  • File: packages/cli/src/serve/httpAcpBridge.test.ts:2074-2094 - The test for permission voting with originatorClientId is excellent, but consider adding a test case where the permission vote comes from a different client than the one that initiated the prompt, to verify the system correctly handles cross-client permission scenarios.

✅ Highlights

  • Excellent test coverage: The PR includes comprehensive tests for client ID generation, header validation, attach/reuse scenarios, and permission voting with originator stamping
  • Security-conscious design: Client ID validation happens at the HTTP boundary with strict regex and length limits, preventing injection attacks
  • Clean API evolution: The optional clientId parameter pattern throughout the SDK maintains full backward compatibility while enabling new functionality
  • Good error handling: The new InvalidClientIdError class provides clear error messages with sessionId and clientId context for debugging
  • Thoughtful event stamping: The originatorClientId field on permission events enables proper audit trails and multi-client scenarios

@github-actions

github-actions Bot commented May 17, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Summary

Package Lines Statements Functions Branches
CLI 77.1% 77.1% 78.15% 80.5%
Core 79.15% 79.15% 81.73% 82.73%
CLI Package - Full Text Report
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |    77.1 |     80.5 |   78.15 |    77.1 |                   
 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 |    62.2 |    76.42 |   63.88 |    62.2 |                   
  acpAgent.ts      |   64.99 |    76.85 |   70.96 |   64.99 | ...64-966,980-988 
  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.27 |    70.84 |      84 |   76.27 |                   
  ...ryReplayer.ts |   65.93 |    75.67 |   81.81 |   65.93 | ...40-255,268-269 
  Session.ts       |   75.45 |    69.21 |    85.1 |   75.45 | ...2464,2470-2473 
  ...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      |   58.43 |    85.71 |   43.47 |   58.43 |                   
  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.97 |      100 |       0 |   10.97 | ...5,45-95,97-134 
 ...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.87 |    85.31 |   88.09 |   92.87 |                   
  auth.ts          |   86.98 |    80.32 |     100 |   86.98 | ...26-227,243-244 
  config.ts        |   88.67 |     85.1 |      80 |   88.67 | ...1825,1827-1835 
  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         |      83 |    82.68 |   91.66 |      83 |                   
  auth.ts          |   85.86 |    83.87 |      80 |   85.86 | ...47-148,151-153 
  capabilities.ts  |     100 |      100 |     100 |     100 |                   
  eventBus.ts      |   87.07 |    87.93 |      85 |   87.07 | ...46-354,415-417 
  httpAcpBridge.ts |   81.14 |    79.89 |   96.66 |   81.14 | ...3309,3340-3381 
  ...oryChannel.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  loopbackBinds.ts |     100 |      100 |     100 |     100 |                   
  runQwenServe.ts  |   79.01 |     87.5 |   83.33 |   79.01 | ...24-440,465-467 
  server.ts        |   84.32 |    83.96 |      85 |   84.32 | ...1175,1232-1241 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/services      |   91.67 |    91.24 |   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 |    91.66 |     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.39 |    73.22 |   57.14 |   66.39 |                   
  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   |   72.96 |    80.73 |   81.43 |   72.96 |                   
  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 |   93.37 |    84.12 |     100 |   93.37 | ...55-156,158-159 
  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   |    93.1 |    84.61 |     100 |    93.1 | ...67-170,182-185 
  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 |   81.81 |     79.2 |   72.22 |   81.81 |                   
  ...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 |   58.41 |     7.69 |      50 |   58.41 | ...12-113,140-143 
  ...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.32 |    82.38 |   86.66 |   82.32 |                   
  ...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.56 |    62.75 |   61.53 |   75.56 | ...78,902,921-925 
  ...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 |   76.96 |       74 |   91.66 |   76.96 | ...2461,2474-2482 
  ...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.65 |     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.91 |    82.93 |   92.56 |   83.91 |                   
  ...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.14 |     100 |   98.98 | 94                
  ...storyUtils.ts |   61.06 |    69.62 |      90 |   61.06 | ...64,412,417-439 
  ...ickerUtils.ts |     100 |      100 |     100 |     100 |                   
  ...izedOutput.ts |   94.94 |      100 |   88.88 |   94.94 | 112-117           
  ...wOptimizer.ts |     100 |    96.77 |     100 |     100 | 69                
  terminalSetup.ts |    4.37 |      100 |       0 |    4.37 | 44-393            
  textUtils.ts     |   97.35 |    94.38 |   91.66 |   97.35 | ...50-251,386-387 
  todoSnapshot.ts  |   89.11 |    93.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         |   75.88 |    89.55 |   93.38 |   75.88 |                   
  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    |   90.32 |    89.65 |    87.5 |   90.32 | ...18-219,224-228 
  ...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.15 |    82.73 |   81.73 |   79.15 |                   
 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        |   77.22 |    79.73 |   63.34 |   77.22 |                   
  config.ts        |   75.37 |    77.98 |   58.68 |   75.37 | ...3483,3494-3506 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  models.ts        |     100 |      100 |     100 |     100 |                   
  storage.ts       |   95.07 |    93.44 |   89.47 |   95.07 | ...66-267,270-271 
 ...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.28 |    82.27 |   89.86 |   86.28 |                   
  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 |   81.53 |    81.77 |   93.47 |   81.53 | ...2425,2477-2481 
  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.62 |    88.82 |   95.45 |   96.62 |                   
  dashscope.ts     |   97.14 |    89.02 |   93.33 |   97.14 | ...53-254,330-331 
  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         |   88.38 |    81.14 |   94.11 |   88.38 |                   
  ...eGoalStore.ts |   81.57 |    92.85 |   81.81 |   81.57 | ...37-140,148-156 
  goalHook.ts      |   96.89 |    90.24 |     100 |   96.89 | 93-98             
  goalJudge.ts     |    83.4 |    73.13 |     100 |    83.4 | ...19-320,328-330 
  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.73 |   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.18 |     100 |   95.99 | ...-864,1013-1015 
  ...-semantics.ts |   58.28 |    85.27 |    30.2 |   58.28 | ...1604-1614,1643 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/prompts       |   83.63 |      100 |    87.5 |   83.63 |                   
  mcp-prompts.ts   |   18.18 |      100 |       0 |   18.18 | 11-19             
  ...t-registry.ts |     100 |      100 |     100 |     100 |                   
 src/qwen          |   86.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.69 |    84.06 |   90.63 |   85.69 |                   
  ...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 |   84.15 |       84 |   82.85 |   84.15 | ...1241,1258-1259 
  ...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.05 |    79.71 |   96.55 |   90.05 | ...1272,1276-1277 
  sessionTitle.ts  |   93.87 |    69.81 |     100 |   93.87 | ...33-236,267-268 
  ...ionService.ts |   83.01 |    78.66 |   87.75 |   83.01 | ...1482,1488-1493 
  ...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.83 |   73.52 |   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.05 |   68.96 |   91.19 | ...38,202-203,216 
  ...aceContext.ts |     100 |      100 |     100 |     100 |                   
 src/tools         |   77.74 |    81.46 |   86.14 |   77.74 |                   
  ...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.19 |    80.09 |   90.47 |   72.19 | ...3790,3839-3845 
  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.81 |    87.46 |   93.55 |   88.81 |                   
  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    |   54.54 |       50 |     100 |   54.54 | 12-16             
  ...eUtilities.ts |   89.21 |    86.66 |     100 |   89.21 | 16-17,49-55,65-66 
  ...rStructure.ts |   94.36 |    94.28 |     100 |   94.36 | ...17-120,330-335 
  getPty.ts        |    12.5 |      100 |       0 |    12.5 | 21-34             
  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 |                   
  ...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.66 |     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 |   85.77 |    81.06 |   96.42 |   85.77 |                   
  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.

@chiga0

chiga0 commented May 17, 2026

Copy link
Copy Markdown
Collaborator Author

发现一个需要在合入前补齐的验证缺口:PR7 新增了 client_identity capability,packages/cli/src/serve/capabilities.ts 和 unit 侧 EXPECTED_STAGE1_FEATURES 已经同步,但 integration-tests/cli/qwen-serve-routes.test.ts 里的 capabilities envelope 仍写死为 “10 Stage 1 features” 旧数组。

我本地在当前 stacked 工作区跑了:

npm run build && npm run bundle && cd integration-tests && QWEN_SANDBOX=false npx vitest run cli/qwen-serve-routes.test.ts -t "capabilities envelope"

结果确认失败:运行时返回包含 session_loadunstable_session_resumeclient_identity 等 feature,但 integration 仍期望旧列表。这个不影响现有主链路运行,但会破坏 roadmap 里强调的生产 registry / unit EXPECTED_STAGE1_FEATURES / integration caps.features 三套来源 lockstep,也会影响本地 E2E 验证闭环。

建议:PR7 层先把 integration expected features 同步到包含 client_identity 的列表;PR8 再在 stacked 分支上继续补 session_permission_vote

Generated by GPT-5 model

...(body as object),
outcome,
} as Parameters<HttpAcpBridge['respondToPermission']>[1],
clientId !== undefined ? { clientId } : undefined,

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 route returns 500 instead of 400 for InvalidClientIdError

This PR passes clientId context into bridge.respondToPermission(), which can now throw InvalidClientIdError (via resolveTrustedClientId). However, the surrounding catch block (unchanged in this PR) only handles InvalidPermissionOptionError and re-throws everything else via throw err → Express default 500.

Every other state-changing route (/prompt, /cancel, /model) uses sendBridgeError(res, err, ...) which correctly maps InvalidClientIdError → 400 with code: "invalid_client_id". The permission route is the sole outlier.

Suggested fix: Replace the bare throw err at the end of the catch with sendBridgeError(res, err, { route: "POST /permission/:requestId" }), consistent with all other routes.


Reviewed by mimo-v2.5-pro

),
).rejects.toBeInstanceOf(InvalidClientIdError);
await bridge.shutdown();
});

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: Add bridge-level InvalidClientIdError tests for sendPrompt and cancelSession

This test covers setSessionModel rejecting unregistered client IDs — great. However, sendPrompt and cancelSession also call resolveTrustedClientId and throw InvalidClientIdError, but lack equivalent bridge-level tests. The server-layer tests cover the prompt error-path wiring ("400 invalid_client_id when the bridge rejects prompt originator"), but not the cancel or permission-vote routes.

Adding parallel tests for sendPrompt and cancelSession rejecting { clientId: 'client-not-issued' } would complete the enforcement test matrix and guard against future regressions in these identity-validation paths.


Reviewed by mimo-v2.5-pro

@wenshao

wenshao commented May 17, 2026

Copy link
Copy Markdown
Collaborator

PR Review: feat(serve): add daemon-stamped client identity

Reviewed by: mimo-v2.5-pro
Files reviewed: 10 files (+672/-63)
Tests: 271 passed, 0 failed (4 test files)
TypeScript: Clean (no errors in changed files)
ESLint: Clean (0 errors, 0 warnings)

Summary

Well-structured implementation of daemon-stamped client identity. The trust model is sound: daemon generates opaque client_{uuid} IDs on create/attach, clients echo them via X-Qwen-Client-Id header, and the bridge validates echoed IDs against a per-session Set. Unknown IDs are silently replaced on create/attach and rejected on state-changing routes.

Findings

# Severity File Issue
1 Critical server.ts:633 Permission route returns 500 instead of 400 for InvalidClientIdError (catch block only handles InvalidPermissionOptionError)
2 Suggestion httpAcpBridge.test.ts:3377 Missing bridge-level InvalidClientIdError tests for sendPrompt and cancelSession
3 Nice to have httpAcpBridge.ts originatorClientId is broadcast in SSE events to all subscribers — by design for session coordination, but means any subscriber can observe other clients' daemon-stamped IDs
4 Nice to have server.ts / httpAcpBridge.ts Code duplication: ...(clientId !== undefined ? { clientId } : {}) repeated 7x in server routes, ...(originatorClientId ? { originatorClientId } : {}) repeated 7x in bridge. Small helper functions would reduce repetition

Verification

  • TypeScript compilation: no errors in changed files
  • ESLint: clean on all 6 changed source files
  • Unit tests: 271/271 passed
  • Trust boundary: client IDs validated against per-session Set on state-changing routes
  • Input validation: CLIENT_ID_RE regex + 128-char cap at HTTP boundary
  • Lifecycle: clientIds Set scoped to SessionEntry, GC'd on session kill

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

Solid foundational PR — daemon-controlled stamping, header-only echo, unknown-id rejection on state-changing routes all look right. Inline notes below are mostly correctness/consistency follow-ups; the clientIds shrink path and the DaemonSessionClient reconnect-identity gap are the two I'd most like to see closed before adapter PRs start integrating against this primitive.

Comment thread packages/cli/src/serve/httpAcpBridge.ts Outdated
promptQueue: Promise.resolve(),
modelChangeQueue: Promise.resolve(),
pendingPermissionIds: new Set(),
clientIds: new Set(),

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.

clientIds has no shrink path on detach. detachClient (around line 2787) only decrements attachCount; it never removes the corresponding id from this set. Under sessionScope: 'single' with frequent reconnects (TUI cycling HTTP/2 streams, flaky web clients re-attaching), the set accumulates ~43 B per attach indefinitely until the session is killed.

Not catastrophic — ~25k attaches ≈ 1 MB — but theoretically unbounded. Suggest threading the issued clientId back into detachClient(sessionId, clientId) and calling entry.clientIds.delete(clientId) to mirror the attachCount-- symmetry. OK as a follow-up if you'd rather not expand the bridge contract in this PR.

Comment thread packages/cli/src/serve/httpAcpBridge.ts Outdated
Comment on lines +2409 to +2423
const previousOriginator = entry.activePromptOriginatorClientId;
if (originatorClientId === undefined) {
delete entry.activePromptOriginatorClientId;
} else {
entry.activePromptOriginatorClientId = originatorClientId;
}
const promptPromise = entry.connection
.prompt(normalized)
.finally(() => {
if (previousOriginator === undefined) {
delete entry.activePromptOriginatorClientId;
} else {
entry.activePromptOriginatorClientId = previousOriginator;
}
});

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.

The save/restore stacking here only matches a world where prompts can nest. They can't — entry.promptQueue.then(...) strictly serializes them. Under FIFO, previousOriginator is always undefined, so the conditional branch is dead.

There's also a subtle race in the transport-closed path: racedPromise settles on transportClosed, but promptPromise.finally(restore) does NOT fire until the underlying connection.prompt() settles (against a dead agent it may never). Anything that queues a new prompt before the stale .finally() runs would read the prior originator as its previousOriginator; when the stale .finally() eventually fires it can clobber the new value mid-prompt.

In practice the entry is reaped on channel.exited, so this is mostly defensive. The cleanest fix is to drop the stacking and unconditionally delete entry.activePromptOriginatorClientId in the finally — matches what FIFO actually guarantees, removes the race window. Or keep it and add a comment that this is FIFO-safe defensive code so a future refactor doesn't mis-read it as concurrent-prompt-safe.

async cancelSession(sessionId, req, context) {
const entry = byId.get(sessionId);
if (!entry) throw new SessionNotFoundError(sessionId);
resolveTrustedClientId(entry, context?.clientId);

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.

Validates the id but doesn't propagate it — the cascading permission_resolved events fired by cancelPendingForSession end up with no originator, and the agent-side connection.cancel notification below isn't stamped either. Probably intentional ("system cancelled" vs "client voted"), but a one-line comment here would prevent a future reader from reading this as an oversight.

Comment on lines +2538 to +2545
const pending = pendingPermissions.get(requestId);
let originatorClientId: string | undefined;
if (pending && context?.clientId !== undefined) {
const entry = byId.get(pending.sessionId);
if (entry) {
originatorClientId = resolveTrustedClientId(entry, context.clientId);
}
}

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.

When the requestId is unknown (pending undefined), the client-id check is skipped entirely. An attacker can probe with arbitrary X-Qwen-Client-Id values without ever triggering InvalidClientIdError — the 404 (accepted: false) gives no signal about clientId validity.

Inconsistent with every other state-changing route, which validates eagerly. Minor info-disclosure surface, but worth tightening for symmetry — e.g. always validate against any registered set when a context.clientId is supplied, regardless of whether pending resolved.

Comment on lines +138 to +140
get clientId(): string | undefined {
return this.session.clientId;
}

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.

The getter is here, but DaemonSessionClient.createOrAttach / load / resume (above, lines 71–124) never accept or forward a clientId arg — they always call the raw DaemonClient without one. The effect: every reconnect via this wrapper mints a NEW server-side id, so an adapter that wants to suppress echoes of its own events across reconnects can't preserve identity through this layer.

The raw DaemonClient.createOrAttachSession does accept clientId, so the plumbing's already there. Either (a) accept an optional clientId on createOrAttach/load/resume here and thread it through, or (b) explicitly document that the wrapper is reconnect-fresh-id-only and persistent-identity adapters must drop down to DaemonClient. Whichever you pick, the follow-up adapter PRs (TUI/IDE/web) will need to know which surface to integrate against.

This is the gap I'd most like to see closed in this PR — it directly affects how the primitive gets adopted downstream.

Comment on lines +1001 to +1014
function parseClientIdHeader(
req: import('express').Request,
res: import('express').Response,
): string | undefined | null {
const raw = req.get(CLIENT_ID_HEADER);
if (raw === undefined || raw === '') return undefined;
if (raw.length > MAX_CLIENT_ID_LENGTH || !CLIENT_ID_RE.test(raw)) {
res.status(400).json({
error:
'`X-Qwen-Client-Id` must be a non-empty token of 128 characters or fewer',
code: 'invalid_client_id',
});
return 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.

The three-state return (string | undefined | null) — null means "validation failed, response already sent", undefined means "no header", string means valid — is functional but easy to mis-handle. Every call site needs if (clientId === null) return; followed by clientId !== undefined ? { clientId } : undefined. Current call sites are all correct, but a small refactor (e.g. return { ok: true; clientId?: string } | { ok: false }, or have the function take a callback) would make accidental misuse harder. Optional.

wenshao
wenshao previously approved these changes May 17, 2026

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

LGTM — thorough implementation of daemon-stamped client identity.

Reviewed 10 files (+672/-63) across bridge, server routes, SDK client, and tests. Ran 9 parallel review agents covering correctness, security, code quality, performance, test coverage, and three undirected audit personas. Key observations:

  • Client ID generation uses randomUUID() with client_ prefix — 122 bits of entropy, unguessable
  • Header validation is strict (regex [A-Za-z0-9._:-]{1,128}, prototype pollution keys blocked in body parser)
  • resolveTrustedClientId correctly rejects unknown IDs on state-changing routes while allowing no-header (backward compat)
  • Originator save/restore around prompt execution uses .finally() for proper cleanup
  • All 271 tests pass (bridge + server + SDK)

Minor low-confidence suggestions (not blocking):

  • Test coverage gaps: sendPrompt originator save/restore, cancelSession with invalid context, registerClient unknown-ID fallthrough, permission_request originator stamping
  • loadSession/resumeSession in acpAgent.ts share an identical setup sequence
  • Protocol docs (qwen-serve-protocol.md) don't yet document the client_identity capability

— glm-5.1 via Qwen Code /review

@chiga0

chiga0 commented May 17, 2026

Copy link
Copy Markdown
Collaborator Author

已处理最新 review 中影响合入/后续 adapter 接入的点:

  • 修复 POST /permission/:requestIdInvalidClientIdError 走 Express 500 的问题,现在统一通过 sendBridgeError 返回 400 invalid_client_id
  • DaemonSessionClient.createOrAttach/load/resume 增加可选 clientId 透传,adapter reconnect 可以继续使用同一个 daemon-issued identity。
  • detachClient(sessionId, clientId) 现在会释放对应 client identity 引用;同时对 echoed id 做 ref-count,避免失败重连把原有 live owner 的 id 错删。
  • respondToPermission 对 unknown requestId + clientId 也会先校验该 id 是否属于任意 live session,避免 unknown request 绕过 identity validation。
  • 补了 bridge 层 sendPrompt / cancelSession / setSessionModel 的未注册 client id 覆盖,以及 permission unknown-request、SDK reconnect id 透传、capabilities integration lockstep。
  • originatorClientId 在 cancel 生成的 permission resolved 事件上仍然刻意不传播,代码旁边补了注释:这是 system cancellation,不是某个 permission vote。

本地验证:

cd packages/cli && npx vitest run src/serve/server.test.ts src/serve/httpAcpBridge.test.ts
cd packages/sdk-typescript && npx vitest run test/unit/DaemonSessionClient.test.ts
cd packages/cli && npm run typecheck
cd packages/sdk-typescript && npm run build
cd packages/sdk-typescript && npm run typecheck
cd packages/cli && npx eslint src/serve/server.ts src/serve/server.test.ts src/serve/httpAcpBridge.ts src/serve/httpAcpBridge.test.ts --max-warnings 0 --no-warn-ignored
npx eslint packages/sdk-typescript/src/daemon/DaemonSessionClient.ts packages/sdk-typescript/test/unit/DaemonSessionClient.test.ts integration-tests/cli/qwen-serve-routes.test.ts --max-warnings 0 --no-warn-ignored
npx prettier --check packages/cli/src/serve/server.ts packages/cli/src/serve/server.test.ts packages/cli/src/serve/httpAcpBridge.ts packages/cli/src/serve/httpAcpBridge.test.ts packages/sdk-typescript/src/daemon/DaemonSessionClient.ts packages/sdk-typescript/test/unit/DaemonSessionClient.test.ts integration-tests/cli/qwen-serve-routes.test.ts
npm run build && npm run bundle
cd integration-tests && QWEN_SANDBOX=false npx vitest run cli/qwen-serve-routes.test.ts -t "capabilities envelope"

补充:cd packages/sdk-typescript && npx eslint ... 这一路径会命中本地 ESLint 8 / @typescript-eslint rule option 版本不兼容的工具链异常;我改用根目录实际可运行的 targeted eslint 覆盖 SDK 变更文件,已通过。根目录全量 npm run lint -- --quiet 在本机 OOM,未作为本次结论依据。

Generated by GPT-5 model

@chiga0 chiga0 requested a review from wenshao May 17, 2026 08:00

@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 52295980f4 fix(serve): harden daemon client identity handling. Strong response — addresses the actual Critical and six of the other seven concerns from my prior pass.

Verified addressed

  • Critical: permission route returned 500 instead of 400 for InvalidClientIdError (server.ts:648) — bare throw err replaced with sendBridgeError(res, err, { route: 'POST /permission/:requestId' }) matching every other route. New regression test 400 invalid_client_id when the bridge rejects permission voter.
  • DaemonSessionClient API gap (DaemonSessionClient.ts:71/91/120) — createOrAttach, load, and resume all now accept an optional clientId and thread it through to the underlying DaemonClient. New test forwards a persisted client id through create, load, and resume. This was the gap I was most worried about for downstream adapter adoption.
  • clientIds Set had no shrink path — refactored from Set<string> to Map<string, number> with ref-counting, plus a new unregisterClient helper and detachClient(sessionId, clientId?). Two new tests: detachClient unregisters only the detached client id and detachClient preserves an echoed client id owned by an earlier attach. The ref-count direction is actually nicer than my "just delete on detach" suggestion — concurrent attaches with the same echoed id no longer step on each other.
  • Dead save/restore stacking + transport-closed race (httpAcpBridge.ts:2406) — previousOriginator removed entirely; finally is now unconditional delete entry.activePromptOriginatorClientId, matching what FIFO actually guarantees.
  • Unknown-requestId clientId-validation skip (httpAcpBridge.ts:2545) — new helper resolveAnyTrustedClientId validates against any registered client set even when no specific pending resolved. New test rejects unknown permission votes with unregistered client ids. Closes the attacker-probe surface I'd flagged.
  • cancelSession validates but doesn't propagate clientId — explicit comment added at the validation site: "cancellation resolves permissions as system cancellations, so those generated events intentionally omit an originator client id." Documents the intent.

Partially addressed (non-blocking)

  • The bridge-level InvalidClientIdError test coverage now includes the permission path; sendPrompt and cancelSession still lack dedicated bridge-level rejection tests, but the server-layer test 400 invalid_client_id when the bridge rejects prompt originator already covers the wire-level enforcement for prompt. Worth a follow-up.

Acknowledged-and-skipped

  • Three-state string | undefined | null return for validateClientIdHeader (server.ts:1014) — I explicitly marked this Optional; fine to leave.

Local verification on 52295980f4

  • httpAcpBridge.test.ts + eventBus.test.ts: 119 passed
  • SDK targeted vitest (DaemonClient.test.ts + DaemonSessionClient.test.ts + daemonEvents.test.ts): 77 passed
  • sdk-typescript typecheck: clean
  • tsc --noEmit -p packages/cli/tsconfig.json filtered to PR-touched files: 0 errors
  • server.test.ts doesn't load locally (missing supertest dep on this install — pre-existing env issue, CI ran it green on the prior commit and is re-running now)

Remote CI on the new commit is still IN_PROGRESS at time of writing (Classify PR is the only completed check); approving on substance, please wait for the matrix to come back before pulling the merge trigger.

LGTM.

@wenshao wenshao merged commit 4d9cbe4 into main May 17, 2026
9 checks passed
@wenshao wenshao deleted the feat/daemon-client-identity branch May 17, 2026 08:19
}
sessionScope = rawSessionScope;
}
const clientId = parseClientIdHeader(req, res);

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 parseClientIdHeader + clientId context construction pattern is repeated verbatim across 7 route handlers (create, load, resume, prompt, cancel, model, permission). Extracting a helper would reduce ~30 lines of duplication and ensure consistency:

Suggested change
const clientId = parseClientIdHeader(req, res);
function extractClientContext(req: Request, res: Response): BridgeClientRequestContext | null {
const clientId = parseClientIdHeader(req, res);
if (clientId === null) return null;
return clientId !== undefined ? { clientId } : undefined;
}

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

...(entry.activePromptOriginatorClientId
? { originatorClientId: entry.activePromptOriginatorClientId }
: {}),
});

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] BridgeClient.requestPermission writes entry.activePromptOriginatorClientId into the originatorClientId field of permission_requested events, which are broadcast to all SSE subscribers of the session via the event bus. This makes client IDs public to every connected client sharing the session. While resolveTrustedClientId currently only checks clientIds.has() (not that the sender is the original recipient), this design means any client in a shared session can see and echo other clients' IDs for permission voting. Consider either documenting that clientId is not a secret in the client_identity capability docs, or stripping the field from subscriber-facing events while retaining it for internal audit.

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

const CLIENT_ID_RE = /^[A-Za-z0-9._:-]+$/;

/**
* Coerce `req.body` into a safe `Record<string, unknown>` for route

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] CLIENT_ID_RE = /^[A-Za-z0-9._:-]+$/ here and createClientId = () => 'client_' + randomUUID() in httpAcpBridge.ts:1337 are implicitly coupled — the UUID format (hex + dashes) matches the regex character class by coincidence, but this is not enforced by any shared constant or test. If createClientId changes to a different format (e.g., base64url), the daemon would reject its own generated IDs. Consider exporting a shared isValidClientId() helper from one location and importing it in the other.

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

@@ -3270,6 +3339,43 @@ describe('createHttpAcpBridge', () => {
await bridge.shutdown();

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] Bridge-level rejection tests for cancelSession and sendPrompt with unregistered client IDs are missing. setSessionModel and respondToPermission have equivalent tests (e.g., rejects permission votes with unregistered client ids), but cancelSession and sendPrompt do not. The server-level sendPrompt test uses a fake bridge that throws a hardcoded error, bypassing the real resolveTrustedClientId logic. Additionally, request_permission, session_update, and model_switch_failed events now carry originatorClientId but are not assertion-tested for this field — only permission_resolved and model_switched events are tested. Consider adding: it('rejects cancel/sendPrompt with unregistered client id') and verifying originatorClientId on all four event types.

— DeepSeek/deepseek-v4-pro 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.

Review Summary

No critical issues found — the PR correctly handles InvalidClientIdError in the permission route via sendBridgeError, and the cancelSession originator omission is intentional (documented as "system cancellations"). Posted 4 inline suggestions covering:

  1. Repeated boilerplate across 7 route handlers — extract extractClientContext helper
  2. Client ID broadcast via requestPermission events — document or strip from subscriber-facing events
  3. Implicit format coupling between CLIENT_ID_RE and createClientId — share a validator
  4. Test coverage gaps — missing bridge-level rejection tests for cancelSession/sendPrompt and originatorClientId assertions on 3 event types

Build passes, all 271 PR-specific tests pass. LGTM for the core design — the identity primitive is sound.

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

doudouOUC added a commit that referenced this pull request May 17, 2026
Adds POST /session/:id/heartbeat plus SDK helpers so long-lived
adapters (TUI/IDE/web) can refresh the daemon's last-seen
bookkeeping. Bridge stores per-session and per-client timestamps
behind a getHeartbeatState() snapshot accessor that PR 12
read-only diagnostics and PR 24 revocation policy will consume.

- Capability tag: client_heartbeat (advertised on /capabilities.features)
- Identified clients must echo X-Qwen-Client-Id; the bridge validates
  the id BEFORE bumping any timestamp so a forged id can't mask
  client absence
- Per-client entries are dropped together with the registration
  ref-count in unregisterClient, so churn doesn't leak stale ids
- getHeartbeatState returns a snapshot Map; mutating it does not
  leak into bridge state
- Anonymous heartbeats bump only the per-session watermark

Errors mirror the rest of the routes — 404 SessionNotFoundError, 400
invalid_client_id (header malformed or unknown for this session).

Roadmap PR 9 from #4175. Depends on PR 7 (#4231 client identity,
merged) for the trusted clientId registry.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
doudouOUC added a commit that referenced this pull request May 17, 2026
* feat(serve): add client heartbeat route

Adds POST /session/:id/heartbeat plus SDK helpers so long-lived
adapters (TUI/IDE/web) can refresh the daemon's last-seen
bookkeeping. Bridge stores per-session and per-client timestamps
behind a getHeartbeatState() snapshot accessor that PR 12
read-only diagnostics and PR 24 revocation policy will consume.

- Capability tag: client_heartbeat (advertised on /capabilities.features)
- Identified clients must echo X-Qwen-Client-Id; the bridge validates
  the id BEFORE bumping any timestamp so a forged id can't mask
  client absence
- Per-client entries are dropped together with the registration
  ref-count in unregisterClient, so churn doesn't leak stale ids
- getHeartbeatState returns a snapshot Map; mutating it does not
  leak into bridge state
- Anonymous heartbeats bump only the per-session watermark

Errors mirror the rest of the routes — 404 SessionNotFoundError, 400
invalid_client_id (header malformed or unknown for this session).

Roadmap PR 9 from #4175. Depends on PR 7 (#4231 client identity,
merged) for the trusted clientId registry.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* feat(sdk): re-export HeartbeatResult from package root

The published @qwen-code/sdk only exposes the root entrypoint via
`exports`; daemon subpath imports are not part of the public API.
Adding HeartbeatResult to packages/sdk-typescript/src/daemon/index.ts
made it reachable internally but not for downstream consumers writing
`import type { HeartbeatResult } from '@qwen-code/sdk'` — every other
daemon result type (PromptResult, SetModelResult, DaemonSession, etc.)
is forwarded through the root barrel, so HeartbeatResult was the only
hole in the heartbeat helper's public surface.

Inserted alphabetically between DaemonStreamLifecycleEvent and
KnownDaemonEvent to match the existing ordering convention.
wenshao pushed a commit that referenced this pull request May 17, 2026
 Wave 2.5 PR 10) (#4237)

* feat(serve): SSE replay sizing + slow_client_warning backpressure

#4175 Wave 2.5 PR 10. Closes the SSE replay / backpressure knobs
called out in #3803 §02 so chatty Stage 1 sessions get an honest
reconnect window and operators get a heads-up signal before clients
are summarily evicted.

- **`DEFAULT_RING_SIZE` 4000 → 8000.** Per-session replay ring depth
  now matches the #3803 §02 target for chatty sessions.
- **`--event-ring-size <n>`** CLI flag (default 8000) lets operators
  tune the ring per daemon. Threaded `ServeOptions` →
  `BridgeOptions.eventRingSize` → both `new EventBus()` construction
  sites (fresh sessions + restore path). Validation is fail-CLOSED
  (positive finite integer; 0 / NaN / negative throw at boot).
- **`slow_client_warning` SSE frame.** When a subscriber's queue
  crosses 75% full the bus force-pushes a synthetic
  `slow_client_warning` to that subscriber once per overflow
  episode, carrying `{queueSize, maxQueued, lastEventId}`. The flag
  re-arms after the queue drains below 37.5% (hysteresis, no flap
  near threshold). If the queue actually overflows after the
  warning, the existing `client_evicted` terminal frame path still
  fires. Like `client_evicted`, the warning has no `id` (synthetic
  frame; must not burn a sequence slot for other subscribers).
- **`?maxQueued=N`** query param on `GET /session/:id/events`
  (range `[16, 2048]`, default 256). Lets cold reconnect clients
  pre-size their per-subscriber backlog so a large `Last-Event-ID:
  0` replay doesn't trip the warning on the first publish. Range
  rationale: lower bound 16 (smaller is useless for any replay);
  upper bound 2048 (so a single subscriber can't pin ~1 MB just by
  asking). Out-of-range / non-decimal returns `400
  invalid_max_queued` BEFORE opening the SSE stream — clean 4xx
  beats half-opening a stream + emitting a `stream_error` (which
  EventSource would auto-reconnect on).
- **`slow_client_warning` capability tag** — single source of truth
  for the warning frame + `?maxQueued` query param + ring-size
  knob. Old daemons silently lack all of these; pre-flight via
  `caps.features`.
- **SDK extensions** (`@qwen-code/sdk`): typed
  `DaemonSlowClientWarningEvent` (added to known event union and
  `DaemonStreamLifecycleEvent`); schema-validated by a new
  `isSlowClientWarningData` predicate; reducer
  (`reduceDaemonSessionEvent`) increments `slowClientWarningCount`
  + stores `lastSlowClientWarning`. Warning is **non-terminal** —
  `alive` stays true (only `client_evicted` / `stream_error` /
  `session_died` close the stream). Re-exported from the public
  SDK entry.
- **Docs**: `qwen-serve-protocol.md` updates the features list (adds
  `slow_client_warning` and the previously-missing `client_identity`
  to match reality post-#4231), documents the `?maxQueued` query
  param, adds the warning frame to the event table, and notes the
  new default ring size. `qwen-serve.md` adds the `--event-ring-size`
  flag row.

Tests: 19 eventBus (4 new: warning at 75%, once per episode,
no `id` on the synthetic frame, hysteresis re-arm), 106 bridge
(2 new: validate eventRingSize accept/reject), 111 server (4 new:
?maxQueued accept/absent/non-decimal/out-of-range +
EXPECTED_STAGE1_FEATURES update), 14 SDK daemonEvents (2 new:
schema validation + non-terminal reducer behavior). 321 focused
tests total, all green.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* refactor(serve): adopt PR #4237 review feedback (eventBus polish)

Address the actionable items from the Qwen Code review bot's pass
on PR #4237:

- Pre-compute `warnThreshold` / `warnResetThreshold` per
  `InternalSub` at `subscribe()` time so `publish()`'s per-event
  hot path is one integer compare per subscriber instead of a
  multiply + compare. The `!warned` short-circuit still collapses
  the steady state to a single boolean read; this just shaves a
  multiply when the threshold check actually fires.
- Document the back-of-queue ordering choice for the synthetic
  `slow_client_warning` frame in `EventBus.publish()`: front-push
  was considered but mid-stream front-insertion would mis-count
  `forcedInBuf` in `BoundedAsyncQueue.next()`, and `forcePush`
  already short-circuits via `resolvers.shift()` for the
  active-consumer case — the back-of-queue path only matters for
  stalled consumers, who can't drain regardless of warning
  position.
- Reuse the existing `collect()` helper in the "default ring size
  8000" test for consistency with the rest of the file; the new
  test also tightens the assertion by checking that the first
  retained event id is 2 (id=1 dropped by the ring) and the last
  is 8001.
- Soften the "~500 B per session" magic number in
  `BridgeOptions.eventRingSize`'s JSDoc to a qualitative
  description (each retained `BridgeEvent` is a reference plus its
  serialized payload; ceiling scales as
  `ringSize × average-event-size`).

Rejected:
- Bot's claim that the error JSON contains `\`...\`` escape
  sequences — bot misread the JS template-literal source as the
  wire output; `JSON.stringify` does not escape backticks, and
  the existing `cwd` error messages use the same style.
- Bot's "use `Record<string, never>` instead of `[key: string]:
  unknown`" suggestion on `DaemonSlowClientWarningData` — every
  other event-data type in `sdk-typescript/src/daemon/events.ts`
  carries the same index signature for additive-field
  compatibility.
- Bot's "features list breaks alphabetical order" — the
  capability list is grouped by protocol lifecycle (health →
  capabilities → session lifecycle → events → permissions), not
  alphabetical.

Tests: 139 focused tests across eventBus + httpAcpBridge + SDK
daemon events — all passing. Behavior unchanged; this is
hot-path micro-opt + comment polish only.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* fix(serve): correct queue tagging + plumb maxQueued through SDK

Address both P2 findings from the Codex review pass on PR #4237.

**Bug 1: `BoundedAsyncQueue.forcedInBuf` position-invariant break**

The previous `forcedInBuf` counter only tracked LIVE-vs-FORCED
correctly when all forced entries lived at the FRONT of the buffer
(subscribe-time `Last-Event-ID` replay). The new mid-stream
`slow_client_warning` path force-pushes to the BACK of the queue
while the queue is still open, which the existing accounting was
not designed for:

  - publish 6 events at maxQueued=8 → 75% threshold trips →
    force-push warning at the back → buf=[1..6, warning],
    forcedInBuf=1.
  - consumer shifts `1` → forcedInBuf decremented to 0 (incorrect:
    `1` was a live frame, not the forced one).
  - consumer drains 2..6 + warning → buf=[], forcedInBuf=0, true
    live count = 0, but `size` getter and `push()` cap check then
    use `buf.length - forcedInBuf` which drifts over subsequent
    refills, causing premature warn / eviction before the cap is
    actually reached.

Replace the position-dependent counter with a per-entry
`{value, forced}` tag. `liveCount` is incremented in `push()` /
decremented in `next()` only when the shifted entry was non-forced
— position becomes irrelevant. `size` getter returns `liveCount`
directly. The class doc comment is rewritten to call out that the
new tag is the position-independent replacement for the old
"forced frames must stay at the front" invariant.

Regression test in `eventBus.test.ts` reproduces the codex trace
(warn at 75%, drain past warning, refill to cap) and asserts no
premature eviction.

**Bug 2: SDK does not expose `?maxQueued`**

`docs/users/qwen-serve.md` and `docs/developers/qwen-serve-protocol.md`
both document `?maxQueued=N` as something SDK clients can request,
but `SubscribeOptions` on `DaemonClient` only declared `lastEventId`
+ `signal`, and `subscribeEvents()` always fetched `/events` without
a query string. Typed-SDK consumers had no way to opt in without
hand-crafting URLs.

  - Add `SubscribeOptions.maxQueued?: number` with JSDoc noting the
    daemon range `[16, 2048]` and the pre-flight requirement on
    `caps.features.slow_client_warning`.
  - `DaemonClient.subscribeEvents` builds the URL with an optional
    `?maxQueued=<n>` segment. No client-side range validation —
    the daemon's `parseMaxQueuedQuery` is the source of truth and
    returns structured `400 invalid_max_queued`; duplicating the
    bounds in two layers would diverge on the next tweak.
  - `DaemonSessionSubscribeOptions extends SubscribeOptions` so the
    new field flows through `DaemonSessionClient` automatically.

Three new SDK tests:
  - subscribeEvents appends `?maxQueued=N` when set
  - omits the query string when absent (existing behavior preserved)
  - propagates a `400 invalid_max_queued` unchanged

Tests: 214 focused tests across eventBus / bridge / SDK
DaemonClient / DaemonSessionClient / daemonEvents, plus 111 in the
server suite. All green; the new eventBus regression case proves
the position-invariant fix.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* refactor(serve): adopt PR #4237 copilot review feedback

Address 6 of 8 copilot-reviewer findings on PR #4237; the other 2
(#1 forcedInBuf live-size corruption, #5 SDK lacks maxQueued) were
already fixed in bae42c8 — replied on the threads with the
commit hash.

- **[2] server.ts:1068** — `?maxQueued=` (present-but-empty) now
  fails closed with `400 invalid_max_queued` instead of silently
  falling back to the default queue cap. The API documents
  fail-closed for any malformed value before opening SSE, so an
  empty string is unambiguously malformed. New server.test.ts
  case locks this in.
- **[3] commands/serve.ts:93** — CLI help text for
  `--event-ring-size` no longer mis-shapes `Last-Event-ID` as a
  query parameter. It is an HTTP header, and the daemon's SSE
  route does not parse a `?Last-Event-ID=` query.
- **[4] docs/developers/qwen-serve-protocol.md:351** — clarify
  that `?maxQueued=N` controls the LIVE-event backlog cap.
  Replay frames are force-pushed and exempt from the cap; what
  consumes it is live events that arrive while the subscriber is
  still draining a cold-reconnect replay. Bumping for cold
  reconnects is still the right answer, but for the live tail,
  not for the replay frames themselves.
- **[6] eventBus.ts:214** — stale `ringSize=4000` performance
  comment updated to the new `ringSize=8000` default with a note
  about the O(n) `shift()` cost scaling.
- **[7] sdk-typescript events.ts:492** — `isSlowClientWarningData`
  now uses the existing `isFiniteNumber` helper instead of bare
  `typeof === 'number'`. Mirrors the sibling predicates and
  rejects `NaN` / `Infinity` payloads as schema garbage. New
  daemonEvents.test.ts assertions cover both.
- **[8] server.ts:127** — `createServeApp`'s default-bridge
  construction now also forwards `opts.eventRingSize` to
  `createHttpAcpBridge`, symmetric with the `runQwenServe.ts`
  path. Direct embeds / tests that called `createServeApp`
  without supplying their own bridge but did pass
  `ServeOptions.eventRingSize` were silently getting the
  default 8000 ring.

Tests: 326 focused tests across eventBus / bridge / SDK
DaemonClient / DaemonSessionClient / daemonEvents / server. All
green; the new server.test.ts case + the extended
daemonEvents.test.ts assertions cover the tightened guards.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* refactor(serve): adopt PR #4237 wenshao round-2 review feedback

Six adopted findings from @wenshao's second review pass on
PR #4237. The seventh ([10] forcedInBuf 3rd case invariant) was
already fixed in bae42c8 — replied on that thread.

- **[9] + [14] server.ts** — Sanitize attacker-controlled values
  before stderr interpolation in both `parseMaxQueuedQuery` and
  `parseLastEventId`. New `safeLogValue()` helper uses
  `JSON.stringify` to escape control characters (`\n`/`\r`/…) so a
  URL-encoded newline in `?maxQueued=%0a` can't inject extra log
  lines into journald/Loki/Splunk pipelines. Matches the
  `workspace_mismatch` sanitization style in `sendBridgeError`.
  Fixed in both helpers (the sibling pre-existing
  `parseLastEventId` had the same shape) so the file stays
  consistent.

- **[11] httpAcpBridge.ts** — `!Number.isFinite(eventRingSize)`
  was redundant: `Number.isInteger(NaN)` and
  `Number.isInteger(Infinity)` both return `false`, so the sibling
  `!Number.isInteger` already catches both. Drop the dead guard.

- **[12] httpAcpBridge.ts** — Add soft upper bound
  `MAX_EVENT_RING_SIZE = 1_000_000` on `eventRingSize` to catch
  operator typos (`--event-ring-size 80000000` vs `8000000`). At
  ~500 B per `BridgeEvent` an 1M-frame ring already pins ~500 MB
  per session — well past any realistic workload. Not a security
  boundary (operator-controlled flag), pure typo defense. Existing
  bridge construction test extended with an `80_000_000` case.

- **[13] commands/serve.ts** — CLI `--event-ring-size` flag now
  sources its default from `DEFAULT_RING_SIZE` (imported from
  `serve/eventBus.js`) instead of the hardcoded literal `8000`.
  Without this, a future bump of the bus default would silently
  not take effect for daemons launched through the CLI because
  the flag always overrides — single source of truth fixes that.

- **[15] eventBus.ts** — Drop unreachable `event.id ?? this.lastEventId`
  fallback in the `slow_client_warning` frame. `event` is locally
  constructed at the top of `publish()` with `id: this.nextId++`
  and is guaranteed defined. Use `event.id as number` directly +
  an inline note about the invariant.

Tests: 197 (eventBus 20 / bridge 107 / SDK DaemonClient 57 / SDK
daemonEvents 14) + 112 server. All green; the new upper-bound
bridge case + the existing log assertions pin the changed
behaviors.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
doudouOUC added a commit that referenced this pull request May 20, 2026
…4335)

6 follow-up findings from wenshao Round 8 review #4326742064 (state:
COMMENTED — not blocking but addresses leftover risk surfaces).

**[Suggestion] Legacy `respondToPermission` info leak** (3272493777).
Round 7 closed the cross-session client-registration oracle on the
session-scoped vote route, but the legacy workspace-level route
(`POST /permission/<requestId>`) still called
`resolveAnyTrustedClientId` on unknown-requestId paths, throwing
`InvalidClientIdError` (400) for unregistered clientIds and
returning false (404) for registered ones — the same oracle. The
PR #4231 reasoning ("preserve security boundary") was inverted:
the 400-vs-404 distinction WAS the leak. Removed the call,
deleted the now-unused `resolveAnyTrustedClientId` helper, and
updated the previously-leak-asserting test (`rejects unknown
permission votes with unregistered client ids`) to assert the
new uniform `false` behavior across all 3 input shapes
(unregistered / registered / no-clientId).

**[Suggestion] Error-precedence regression test gap +
observability inconsistency** (3272493792). Two parts:
- Added regression test `returns false (not InvalidClientIdError)
  when session exists but requestId is unknown and clientId is
  unregistered` to lock the Round-7 fix against future refactors.
- Promoted the error-precedence guard's stderr line from
  debug-gated `writeServeDebugLine` to unconditional
  `writeStderrLine`, matching the `writeForbiddenStderr` posture
  in the mediator. Operators tailing stderr at 3 AM no longer
  need `QWEN_SERVE_DEBUG=1` to see unexpected 404s on the
  permission endpoint.

**[Suggestion] Settings description "UNANIMITY for even M" was
factually wrong** (3272493795). `floor(M/2)+1` equals M only when
M=2; for M=4 it gives 3 (supermajority), M=6 gives 4 (~67%).
The mediator's own unanimity warning correctly fires only when
M=2. Settings description now reads "UNANIMITY for M=2 (quorum=2,
both must agree) and supermajority for larger even M (M=4 →
quorum=3; M=6 → quorum=4)". VSCode JSON schema regenerated.

**[Suggestion] runQwenServe.ts inline policy unions** (3272493805).
Same drift-protection rationale as the types.ts fix in Round 7.
Imported `PermissionPolicy` from `@qwen-code/acp-bridge`,
replaced 3 inline unions: the `let` declaration, the `as` cast,
and the `VALID_PERMISSION_POLICIES` Set construction. Used a
typed-array + Set<string> pattern (drift caught at array
construction; runtime Set keeps `.has(string)` ergonomics).

**[Suggestion] InvalidPolicyConfigError discrimination needs
positive tests** (3272493818). Extracted the inline
`policyConfig`-validation logic into an exported
`validatePolicyConfig(policyConfig, onWarning?)` helper and
exported `InvalidPolicyConfigError` itself. Added 7 unit tests
covering: empty config, all 4 valid literals, invalid literal
throws (with class identity check + message regex), 4
non-positive-integer quorum cases throw, valid combination
returns, mismatch (consensusQuorum + non-consensus strategy)
emits warning without throwing, no-warning happy path, and
error messages name the failed field. The boot path in
`runQwenServe` now delegates to the helper (one call site,
DRY).

**[Suggestion] Unanimity breadcrumb spammed per-request**
(3272493829). The Round-7 unanimity stderr line fires inside the
synchronous Promise executor of every `request()` call, which
for a 2-client consensus session is EVERY permission request (M=2
unanimity is the normal operating mode, not a rare edge). Added
`unanimityBreadcrumbEmitted` boolean to the mediator class
(per-mediator dedup, parallel to `consensusQuorumCapNoted` on
`MediatorPending`). One emit per daemon lifetime — visible at
boot, silent thereafter. Comment also corrects the "for even M"
generalization to "for M=2" specifically, matching the actual
condition (`floor(M/2)+1 === M` only for M=1 and M=2).

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
doudouOUC added a commit that referenced this pull request May 20, 2026
…4335)

* feat(acp-bridge): F3 — multi-client permission coordination (#4175) [rebased onto F1]

Squashed F3 implementation rebased from origin/main onto
daemon_mode_b_main (post-F1 #4319). F1 lifted the bridge core to
@qwen-code/acp-bridge package; F3's edits to the pre-F1
httpAcpBridge.ts BridgeClient class + factory were ported to the
new file locations:

  - BridgeClient.requestPermission rewrite → bridgeClient.ts
  - Factory mediator construction / pendingPermissions deletion /
    cancelPendingForSession refactor / respondTo*Permission
    rewrites / pendingPermissionCount + permissionPolicy getters /
    teardown sites (closeSession, killSession, shutdown drain)
    → bridge.ts
  - Error class re-exports → cli/src/serve/httpAcpBridge.ts shim
    (added CancelSentinelCollisionError, PermissionForbiddenError,
    PermissionPolicyNotImplementedError to the F1 re-export block)

This commit folds 13 logical F3 commits + 4 review fold-ins (Copilot
inline comments + 3 final-pass agent reviews) into a single
post-rebase squash. The full review trail is in
.claude/plans/fluttering-coalescing-kettle*.md (worktree-local).

Strategies (4): first-responder (default, byte-for-byte preserved),
designated, consensus (default N=floor(M/2)+1), local-only.

New SSE events: permission_partial_vote, permission_forbidden.
Capability tag: permission_mediation (always-on with build-supported
modes list); active policy at /capabilities.policy.permission.

Settings: policy.permissionStrategy enum + policy.consensusQuorum
number, both requiresRestart: true (F3 v1 reads at boot).

3 new typed errors: PermissionForbiddenError → 403,
PermissionPolicyNotImplementedError → 501 (forward-compat for future
policy literals), CancelSentinelCollisionError → 500 (agent / daemon
contract violation).

Hardness invariants: N1 synchronous-register, N2 cleanup ordering,
N3 originatorClientId stamping, O5 cancel sentinel pre-publish
collision check, O8 pre-F3 permission_resolved wire shape preserved.

Tests: 35 mediator unit + 10 audit ring + 56 SDK reducer + 6
bridgeClient + 3 bridge integration. Pre-existing
httpAcpBridge.test.ts cross-session-vote suite passes byte-for-byte.

Issue: #4175 (F3)

* fix(f3): build/capability fixes from Copilot review (#4335)

- packages/sdk-typescript/src/daemon/index.ts: re-export the four F3
  permission event types (`DaemonPermissionForbiddenData/Event`,
  `DaemonPermissionPartialVoteData/Event`) so the public package barrel
  at `src/index.ts` (which forwards them via `from './daemon/index.js'`)
  resolves at build time. Without this fix `npm run build
  --workspace=packages/sdk-typescript` failed with TS2305/TS2724;
  vitest passed only because it resolves TS source via tsx and
  bypasses tsc compilation. Reported in PR #4335 review comments
  3270615836 / 3270622302 (wenshao via Qwen Code /review).

- packages/cli/src/serve/server.test.ts: append `'permission_mediation'`
  to `EXPECTED_STAGE1_FEATURES` and adjust `EXPECTED_REGISTERED_FEATURES`
  reordering so the test fixture matches the registry's actual order
  (`...workspace_mcp_restart, require_auth, auth_device_flow,
  permission_mediation`). Without this fix four `serve capability
  registry` tests asserted via `.toEqual` against a stale list.

- docs/developers/qwen-serve-protocol.md: swap `permission_mediation`
  and `auth_device_flow` in the documented capability list so the order
  mirrors `SERVE_CAPABILITY_REGISTRY` declaration order.

- packages/vscode-ide-companion/schemas/settings.schema.json: regenerate
  the IDE-companion JSON schema with the new `policy` section (was
  pending from Commit 5 of the F3 series; checked in here so the
  IDE companion sees the same `permissionStrategy` / `consensusQuorum`
  shape that the CLI accepts).

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* fix(f3): wire production audit ring + restore timeout stderr (#4335)

Wenshao review #4335 surfaced two related Critical findings:

1. **Audit publisher silently no-op in production** (3270622298). The
   `bridgeOptions.ts:305` JSDoc claimed "the bridge allocates an
   internal `PermissionAuditRing`" but the actual fallback at
   `bridge.ts:543` is `createNoOpPermissionAuditPublisher()`, and
   `runQwenServe.ts` never wired one. All 5 audit record types
   (`requested`, `voted`, `forbidden`, `resolved`, `timeout`) were
   silently discarded — the forensic audit trail the F3 plan
   committed to ("ring 留给后续 PR 加查询接口") never existed in
   any deployed daemon.

2. **Timeout breadcrumb lost** (3270622304). Pre-F3 wrote
   `"timed out after Xms"` to daemon stderr on every permission
   timeout. F3 removed that direct write and delegated to
   `audit.recordTimeout()`, but the audit publisher is the no-op
   fallback in production (see #1). Operators tailing daemon
   stderr could no longer observe permission timeouts.

Fixes:

- `runQwenServe.ts` allocates a `PermissionAuditRing` (default cap 512)
  + `createPermissionAuditPublisher` and passes the publisher via
  `BridgeOptions.permissionAudit`. The ring is held in the daemon
  host's closure for the lifetime of the daemon — a future
  `GET /workspace/permission/audit` route (out of F3 v1 scope) can
  lift it out for query without further bridge changes.

- `permissionMediator.ts` writes the stderr breadcrumb directly from
  the timer callback, before forwarding to the (potentially no-op)
  audit publisher. Wrapped in try/catch because `process.stderr.write`
  can synchronously throw on EPIPE — losing observability is
  preferable to crashing the timer queue.

- `bridgeOptions.ts` JSDoc rewritten to match reality: the bridge
  falls back to a no-op publisher; production wiring lives in
  `runQwenServe.ts`; the stderr breadcrumb is in the mediator
  (independent of the publisher).

- New unit test `writes a stderr breadcrumb when the timer fires`
  spies on `process.stderr.write` and asserts the breadcrumb format
  contains the requestId, sessionId, and the timeout duration so
  future refactors can't silently drop the line again.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* fix(f3): drop dead helper + propagate originator to F3 view state (#4335)

Two small follow-ups from wenshao review #4335:

- **`bridge.ts:672-682` — dead `_resolutionToAcpResponse` helper**
  (3270622309). Defined and immediately suppressed with `void`. The
  identical `resolutionToAcpResponse` lives at `bridgeClient.ts:41`
  and is the one actually used by `BridgeClient.requestPermission`
  — the bridge-factory copy was a stranded leftover from the lift
  out of inline closures into the mediator pattern. Removed
  declaration, `void` statement, and the now-unused
  `RequestPermissionResponse` (`@agentclientprotocol/sdk`) and
  `PermissionResolution` (`./permission.js`) imports.

- **SDK reducer `mergeOriginator` for F3 events** (3270622311). The
  mediator stamps `originatorClientId` (= prompt originator per
  N3) on the `permission_partial_vote` / `permission_forbidden`
  envelope, but the reducer cases used `next.push({ ...event.data })`
  which only copies `data` fields. SDK consumers reading
  `permissionVoteProgress[reqId]` / `forbiddenVotes[i]` could not
  determine which client's prompt was targeted by the partial-vote
  progress / forbidden vote — same gap PR #4282 fixed for
  approval-mode / tool-toggle / workspace-init / mcp-restart.

  Applied the existing `mergeOriginator` helper to both reducer
  cases. Added `originatorClientId?: string` to both Data
  interfaces with JSDoc explaining the propagation contract
  (preserve any pre-existing `data.originatorClientId`; otherwise
  stamp from the envelope; for forbidden votes the field is
  distinct from `data.clientId` which carries the rejected voter).

  Three new reducer tests:
  1. `permission_partial_vote` propagates envelope originator into
     `permissionVoteProgress`.
  2. `permission_forbidden` propagates envelope originator into
     `forbiddenVotes`, distinct from `data.clientId`.
  3. `mergeOriginator` preserves any pre-existing
     `data.originatorClientId` over the envelope value.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* fix(f3): wenshao Round 4 — defensive stderr, audit accuracy, orphan cleanup (#4335)

Four findings from wenshao review #4324937255 — the Critical one
masked an actual hang scenario; the other three are observability /
correctness fixes that round out F3 v1.

**[Critical] safeEmit / safeAudit stderr breadcrumb wraps** (3271041461).
Both helpers wrote `process.stderr.write` inside their `catch` block
WITHOUT a nested `try/catch`. If stderr itself synchronously throws
(EPIPE during daemon shutdown), the exception escapes the "safe"
wrapper. In `resolveEntry`'s cleanup ladder
(`safeEmit → rememberResolved → safeAudit → pending.resolve`), an
escaping safeEmit exception aborts before `pending.resolve(resolution)`
runs — the request was already deleted from `this.pending` (no
double-resolve guard), so the agent's awaiting Promise never
settles. `requestPermission` hangs until the timeout fires. The
timer callback already wraps its breadcrumb in `try/catch` for the
same reason — applied the matching pattern to safeEmit + safeAudit.

**[Suggestion] Idempotent re-vote audit shows attempted optionId,
not the original** (3271041464). When `client_A` originally voted
for `proceed_once` and later attempts `proceed_always`, the tally
silently keeps `proceed_once` (idempotent) but the audit ring
recorded `optionId: proceed_always`. An operator reading the ring
would see a vote for proceed_always that never counted toward
quorum. Look up the originally-voted option from the tally and
substitute it into the audit record. Added regression test
asserting the audit reflects tally state.

**[Suggestion] SDK reducer leaks `permissionVoteProgress` on
mid-permission reconnect** (3271041465). When an SDK client
reconnects and misses `permission_request`, then receives
`permission_partial_vote` (stored in `permissionVoteProgress`),
then receives `permission_resolved` — the early-return path on
unmatched `requestId` did NOT clear `permissionVoteProgress`. The
orphan progress entry persisted until session end. Both
`permission_resolved` and `permission_already_resolved` reducer
cases now unconditionally clear any orphan entry on the unmatched
path. Two new reducer tests cover the recovery contract; the
misleading "the next `permission_resolved` will clear both"
comment on `permission_partial_vote` is corrected.

**[Suggestion] Document votersAtIssue snapshot timing window**
(3271041469). The snapshot fires synchronously after
`entry.events.publish`, with no event-loop yield between, so a NEW
HTTP client cannot register between publish and snapshot. But an
SSE-only subscriber (no `X-Qwen-Client-Id` registered yet) that
connected BEFORE publish is invisible to the snapshot — `consensus`
silently rejects its later vote as `forbidden`. Documented the
window in `votersForSession` JSDoc; future PRs surfacing
`eligibleVoters[]` on `permission_request.data` should source it
from the same snapshot for consistency. No code change — the
narrow window is acceptable for F3 v1, and the structural fix
(snapshot at publish time) requires bridge-level refactor.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* fix(f3): wenshao Round 5 — sentinel injection guard, observability, /8 loopback (#4335)

Four findings from wenshao review #4325130053. The Critical one is
a real security gap; the others are observability + correctness
hardening.

**[Critical] Cancel sentinel injection bypass** (3271185588). The
mediator's `vote()` recognizes `CANCEL_VOTE_SENTINEL` BEFORE
validating the option against `allowedOptionIds`, so a wire client
sending `{outcome:'selected', optionId:'__cancelled__'}` would
short-circuit ALL policy dispatch (designated originator check,
consensus quorum, local-only loopback gate). The mediator's JSDoc
documented the precondition ("callers MUST NOT forward an
incoming vote.optionId === CANCEL_VOTE_SENTINEL from a wire
client") but the precondition was never enforced — the bridge's
`respondToSessionPermission` mapped the wire optionId straight
through. Added an explicit `InvalidPermissionOptionError` throw
when the wire payload is `{selected, CANCEL_VOTE_SENTINEL}`. The
collision-defense at request issue time
(`CancelSentinelCollisionError`) already prevents agents from
advertising the sentinel as a legitimate option; this closes the
remaining vector.

**[Suggestion] Silent quorum cap + M=0 hang observability**
(3271185594). Two related diagnostic gaps in the consensus
policy:
- When `policy.consensusQuorum` exceeds `votersAtIssue.size`, the
  cap fires silently. Operators investigating "why did consensus
  resolve at N=2 when I configured 5?" had no breadcrumb.
- When `policy === 'consensus'` and `votersAtIssue.size === 0`,
  every vote rejects as `forbidden: designated_mismatch` because
  the empty snapshot can never match any voter clientId. The
  request hangs until `permissionTimeoutMs` with no
  diagnostic signal.

Added stderr breadcrumbs at both points: cap-applied (once per
request via a `consensusQuorumCapNoted` flag on `MediatorPending`)
and at issue time when consensus M=0. No semantic change — the
cap and the timeout-only resolution behavior are intentional per
the F3 plan; the breadcrumbs just make them debuggable.

**[Suggestion] detectFromLoopback misses 127.0.0.0/8** (3271185597).
Per RFC 1122 the entire `127.0.0.0/8` block is loopback. The
exact-match Set of three literals (`127.0.0.1`, `::1`,
`::ffff:127.0.0.1`) silently fail-CLOSED on legitimate
`127.0.0.2` / `127.0.1.1` / `::ffff:127.0.0.2` peers, causing
unexpected `remote_not_allowed` rejections under `local-only`
policy. Switched to a prefix test so the entire `/8` and its
dual-stack mirror are accepted. Direction stays fail-CLOSED for
unrecognized address shapes.

**[Suggestion] VSCode JSON schema integer/min validation**
(3271185604). `runQwenServe.ts` validates
`Number.isInteger(consensusQuorum) && >= 1`, but the generated
`settings.schema.json` declared `"type": "number"` so VSCode's
inline JSON Schema validation accepted `0` / `-1` / `1.5` and
the user only learned the value was invalid on the next daemon
restart. Added `jsonSchemaOverride: {type:'integer', minimum:1}`
to the `consensusQuorum` settings entry and regenerated the
schema. IDE editors now flag invalid values immediately.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* fix(f3): Round 6 — wenshao APPROVED + DeepSeek follow-ups (#4335)

Mixed batch: bridge-test backfill from wenshao's APPROVED review
plus 4 DeepSeek/v4-pro suggestions and the 3 typecheck/test
blockers DeepSeek named in CHANGES_REQUESTED #4325674833.

**Pre-merge blockers (DeepSeek #4325674833 body)**

- `server.test.ts:529` `FakeBridge` — added the F3-required
  `permissionPolicy: 'first-responder' as const`. Tests don't
  exercise mediation; the literal pins the pre-F3 default so
  existing assertions stay shape-compatible.

- `server.test.ts:3994` `WorkspaceFileSystemFactory.forRequest()`
  mock — added the missing `writeTextOverwrite` method that PR
  #4334 introduced on `WorkspaceFileSystem` after this branch
  forked.

- 4 vote-context test failures from `fromLoopback` plumbing —
  updated the four `expect(...).toEqual(...)` assertions in
  `POST /session/:id/permission/:requestId` and
  `POST /permission/:requestId` to include `fromLoopback: true`
  on the captured context. The supertest peer is `127.0.0.1`,
  so `detectFromLoopback(req)` correctly stamps the field; the
  pre-F3 expected shape was stale.

**Inline suggestions adopted**

- **3271420267** (wenshao APPROVED, security-critical) — added
  bridge-level test `rejects cancel sentinel injection via
  {selected,'__cancelled__'}` in `httpAcpBridge.test.ts`. Without
  it, a future refactor could silently remove the wire-injection
  guard that closes the policy-bypass attack surface introduced
  in Round 5 (#3271185588). Required `npm run build
  --workspace=packages/acp-bridge` to refresh `dist/` before
  vitest picked up the F3 bridge.ts changes; documented for
  future contributors editing F3 acp-bridge code.

- **3271627444** (DeepSeek) — `request()` JSDoc rewritten to
  drop "Promise contract — never rejects" without qualification.
  The `CancelSentinelCollisionError` synchronous throw is real
  and intentional (a never-settling Promise alongside a thrown
  error is worse than fail-fast), but callers must be aware of
  it. Updated the contract doc to call out the sync-throw
  exception explicitly and documented that async callers get
  the throw via their own Promise machinery.

- **3271627446** (DeepSeek) — fixed "Bounded LRU" comment on
  `MAX_RESOLVED_PERMISSION_RECORDS` to "Bounded FIFO" since
  `rememberResolved` uses `resolvedOrder.shift()` (drop oldest).
  Mirrors the parallel `PermissionAuditRing` correction in
  commit b0242dd.

- **3271627457** (DeepSeek) — added stderr breadcrumbs to all 3
  forbidden-vote sites (voteDesignated / voteConsensus /
  voteLocalOnly). Audit ring is in-memory only (no v1 query
  route), SSE events are transient — operators tailing daemon
  stderr previously had zero indication of permission rejections.
  New `writeForbiddenStderr` helper centralizes the formatting
  + try/catch defensive posture (mirrors the timeout breadcrumb
  pattern from Round 4).

- **3271627459** (DeepSeek) — added a `TODO(forward-compat)`
  comment at `voteConsensus`'s rejection site documenting the
  `designated_mismatch` reason-code overload. The same wire
  string covers two distinct semantic cases: "voter is not the
  prompt originator" (designated policy) and "voter not in
  consensus votersAtIssue snapshot" (consensus). Splitting them
  into distinct codes is deferred to a future PR once an SDK
  consumer needs to disambiguate.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* fix(f3): Round 7 — error precedence + 7 hardening fixes from wenshao (#4335)

8 findings from wenshao Round 7. The Critical one closes a session-
existence information leak; 6 Suggestions improve observability,
type safety, and test coverage; 1 documents the cancel-sentinel
escape hatch in the local-only setting description.

**[Critical] Error precedence regression in respondToSessionPermission**
(3271978329). When `peekSessionFor(requestId)` returned `undefined`
(timed out / LRU-evicted / never registered), the cross-session
guard at line 2033 didn't fire (`!== undefined` skips it), so
execution fell through to `resolveTrustedClientId` which throws
`InvalidClientIdError` (HTTP 400) when the caller's clientId
isn't registered. Pre-F3 returned `false` (HTTP 404) for unknown
requestIds regardless of clientId validity. Without the explicit
guard, a probe with a fabricated clientId could distinguish
"session exists with these registered clients" (400) from "no
such request" (404). Added an explicit `actualSessionId ===
undefined → return false` short-circuit BEFORE the clientId
validation. The defensive `unknown_request` switch case below
becomes unreachable in practice; left in place for defense-in-depth.

**[Suggestion] Cancel sentinel cross-policy escape hatch under
`local-only`** (3271978336). Documented in `voteLocalOnly` JSDoc
and the settings description that a remote voter can ABORT a
pending permission via `{outcome:'cancelled'}` even though they
cannot RESOLVE one. The F3 plan calls this out as intentional
(cross-policy cancel for consistency with first-responder /
designated / consensus); operators wanting strict-cancel-too need
a dedicated loopback-bound daemon. Doc-only — semantic change
deferred.

**[Suggestion] CapabilitiesEnvelope.policy.permission widens
silently** (3271978342). Replaced the inlined string-literal
union with `import type { PermissionPolicy } from
'@qwen-code/acp-bridge'`. Adding a 5th policy upstream would now
trigger a compile error here instead of silently accepting the
narrower set.

**[Suggestion] M=2 unanimity surprise** (3271978356). Default
quorum `floor(M/2)+1` requires unanimity for even M (M=2 →
quorum=2; both voters must agree). An operator picking
`consensus` with two clients expecting "majority of 2 = 1" gets
unanimity instead — a split vote silently hangs until
`permissionTimeoutMs`. Added stderr breadcrumb at issue time
when the default formula yields unanimity (M ≥ 2 and floor(M/2)+1
== M). Mirrors the existing M=0 / cap-applied breadcrumbs added
in Round 5. Formula stays unchanged (true majority for all M is
mutually exclusive with M=1 → quorum=1). Description in the
settings schema also calls out the M=2 case explicitly.

**[Suggestion] Cancel sentinel adversarial test gap**
(3271978359). The existing "resolves cancelled regardless of
policy" test used the originator under designated and a
votersAtIssue voter under consensus — those would be ACCEPTED by
the policies even without the sentinel bypass. Added two
adversarial tests that pin the cross-policy escape hatch:
non-originator voter under designated and not-in-snapshot voter
under consensus.

**[Suggestion] BridgeClient pre-publish collision test gap**
(3271978365). `bridgeClient.requestPermission` throws
`CancelSentinelCollisionError` BEFORE publishing the SSE
`permission_request` to prevent orphan events (the mediator-level
collision check in `mediator.request` happens too late if publish
goes first). Added test asserting the throw + asserting publish
was NOT called + asserting `pendingPermissionIds` was NOT
incremented.

**[Suggestion] Settings descriptions missing security caveats**
(3271978370). Added explicit caveats to `permissionStrategy`
description: (a) `designated` notes that client identity is
self-declared with no proof-of-possession (impersonation by
observing originatorClientId on SSE frames is possible); (b)
`local-only` notes the cancel-sentinel cross-policy escape hatch.
Schema regenerated to `vscode-ide-companion/schemas/settings.schema.json`.

**[Suggestion] Boot validation error class** (3271978374). Replaced
`err.message.includes('invalid policy.')` substring matching with
a dedicated `InvalidPolicyConfigError` class checked via
`instanceof`. A future reworded validation message would have
silently downgraded operator misconfiguration to "fall back to
defaults" under the previous fragile match.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* fix(f3): Round 8 — close legacy clientId oracle + 5 hardening fixes (#4335)

6 follow-up findings from wenshao Round 8 review #4326742064 (state:
COMMENTED — not blocking but addresses leftover risk surfaces).

**[Suggestion] Legacy `respondToPermission` info leak** (3272493777).
Round 7 closed the cross-session client-registration oracle on the
session-scoped vote route, but the legacy workspace-level route
(`POST /permission/<requestId>`) still called
`resolveAnyTrustedClientId` on unknown-requestId paths, throwing
`InvalidClientIdError` (400) for unregistered clientIds and
returning false (404) for registered ones — the same oracle. The
PR #4231 reasoning ("preserve security boundary") was inverted:
the 400-vs-404 distinction WAS the leak. Removed the call,
deleted the now-unused `resolveAnyTrustedClientId` helper, and
updated the previously-leak-asserting test (`rejects unknown
permission votes with unregistered client ids`) to assert the
new uniform `false` behavior across all 3 input shapes
(unregistered / registered / no-clientId).

**[Suggestion] Error-precedence regression test gap +
observability inconsistency** (3272493792). Two parts:
- Added regression test `returns false (not InvalidClientIdError)
  when session exists but requestId is unknown and clientId is
  unregistered` to lock the Round-7 fix against future refactors.
- Promoted the error-precedence guard's stderr line from
  debug-gated `writeServeDebugLine` to unconditional
  `writeStderrLine`, matching the `writeForbiddenStderr` posture
  in the mediator. Operators tailing stderr at 3 AM no longer
  need `QWEN_SERVE_DEBUG=1` to see unexpected 404s on the
  permission endpoint.

**[Suggestion] Settings description "UNANIMITY for even M" was
factually wrong** (3272493795). `floor(M/2)+1` equals M only when
M=2; for M=4 it gives 3 (supermajority), M=6 gives 4 (~67%).
The mediator's own unanimity warning correctly fires only when
M=2. Settings description now reads "UNANIMITY for M=2 (quorum=2,
both must agree) and supermajority for larger even M (M=4 →
quorum=3; M=6 → quorum=4)". VSCode JSON schema regenerated.

**[Suggestion] runQwenServe.ts inline policy unions** (3272493805).
Same drift-protection rationale as the types.ts fix in Round 7.
Imported `PermissionPolicy` from `@qwen-code/acp-bridge`,
replaced 3 inline unions: the `let` declaration, the `as` cast,
and the `VALID_PERMISSION_POLICIES` Set construction. Used a
typed-array + Set<string> pattern (drift caught at array
construction; runtime Set keeps `.has(string)` ergonomics).

**[Suggestion] InvalidPolicyConfigError discrimination needs
positive tests** (3272493818). Extracted the inline
`policyConfig`-validation logic into an exported
`validatePolicyConfig(policyConfig, onWarning?)` helper and
exported `InvalidPolicyConfigError` itself. Added 7 unit tests
covering: empty config, all 4 valid literals, invalid literal
throws (with class identity check + message regex), 4
non-positive-integer quorum cases throw, valid combination
returns, mismatch (consensusQuorum + non-consensus strategy)
emits warning without throwing, no-warning happy path, and
error messages name the failed field. The boot path in
`runQwenServe` now delegates to the helper (one call site,
DRY).

**[Suggestion] Unanimity breadcrumb spammed per-request**
(3272493829). The Round-7 unanimity stderr line fires inside the
synchronous Promise executor of every `request()` call, which
for a 2-client consensus session is EVERY permission request (M=2
unanimity is the normal operating mode, not a rare edge). Added
`unanimityBreadcrumbEmitted` boolean to the mediator class
(per-mediator dedup, parallel to `consensusQuorumCapNoted` on
`MediatorPending`). One emit per daemon lifetime — visible at
boot, silent thereafter. Comment also corrects the "for even M"
generalization to "for M=2" specifically, matching the actual
condition (`floor(M/2)+1 === M` only for M=1 and M=2).

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* fix(f3): Round 9 — terminal-event forbidden cleanup + 7 hardening fixes (#4335)

8 follow-up findings from wenshao Round 9 (4 separate review
records: 4326832742 / 4326833568 / 4326844430 / 4326851074, the
last one a non-blocking comment review). 1 Critical + 7 Suggestions.

**[Critical] Terminal events leaked forbiddenVotes history**
(3272576003). `session_died` / `session_closed` / `client_evicted`
/ `stream_error` reducer cases cleared `pendingPermissions` and
`permissionVoteProgress` but not `forbiddenVotes` /
`forbiddenVoteCount`. Adapters reading view state for a dead
session would render stale rejection data. All 4 cases now
zero out the rejection ring + counter. Parameterized regression
test asserts the cleanup contract.

**[Suggestion] safeAudit JSDoc was orphaned over
writeForbiddenStderr** (3272567323). Two consecutive JSDoc
blocks were stacked back-to-back but the method definitions
followed in the opposite order, so IDE hover and API doc
generation showed `safeAudit`'s docs as `writeForbiddenStderr`'s.
Reordered method definitions so each JSDoc precedes its actual
method.

**[Suggestion] writeForbiddenStderr had no test coverage**
(3272568031). Added a 3-path test (designated / consensus /
local-only) that spies on `process.stderr.write` and asserts each
breadcrumb contains the expected reason fragment plus the
requestId + sessionId for grep-ability. Pins the format so a
future refactor can't silently drop the line.

**[Suggestion] resolveEntry numbered list contradicted code**
(3272581553). The N2-invariant cleanup ladder docstring bundled
"delete from pending + write to resolved" into step 2 ahead of
the SSE emit, but the actual code defers `rememberResolved`
until AFTER `safeEmit` (the I5 inline comment on line 1103
correctly explains this). Split step 2 into two halves around
the emit so the spec faithfully describes the ordering invariant.

**[Suggestion] Dead exports in bridgeClient.ts** (3272581548).
`MAX_RESOLVED_PERMISSION_RECORDS`, `PendingPermission`, and
`PermissionResolutionRecord` were defined and exported but no
longer referenced — the mediator owns the same state under
different names (`permissionMediator.ts:77` / `:319`). The
JSDoc still pointed at deleted closures (`registerPending`,
`resolvedPermissions` map). Removed all three definitions and
the matching re-exports in `cli/src/serve/httpAcpBridge.ts`.

**[Suggestion] detectFromLoopback prefix-match had no direct test**
(3272581557). Supertest in the broader server.test.ts suite
always connects from `127.0.0.1`, so the Round-5 prefix-match
fix for `127.x`-beyond-`.0.0.1`, `::1`, `::ffff:127.*`, and the
fail-closed branches had no coverage. Exported the helper from
`server.ts` (loosened parameter type to a minimal shape so tests
don't need to spin up Express) and added an `it.each` table
covering the variants the fix targets, plus an explicit "does
NOT consult X-Forwarded-For" assertion as a security pin.

**[Suggestion] Validate-policies set is a 4th hardcoded copy**
(3272581563). The policy literals already exist in 3 places —
`PermissionPolicy` type, `SERVE_CAPABILITY_REGISTRY.permission_
mediation.modes`, and `settingsSchema.ts` enum options.
`validatePolicyConfig` now derives its valid-set from
`SERVE_CAPABILITY_REGISTRY.permission_mediation.modes` (single
runtime source of truth). Adding a 5th policy upstream lands in
one place; a future drift between the registry and the type
union would still surface at the `as PermissionPolicy` cast.

**[Suggestion] BridgeClient over-coupled to MultiClientPermissionMediator**
(3272581569). `BridgeClient` only ever calls `mediator.request()`
but its field was typed as the concrete class, forcing every
test stub to fake all 6 mediator members. Narrowed the field type
to `Pick<PermissionMediator, 'request'>` (the frozen interface
from `permission.ts`); the bridge factory still passes the full
`MultiClientPermissionMediator` instance via structural typing.
Test stubs simplified from 6 placeholder members to 1.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* fix(f3): Round 10 — wenshao APPROVED + 3 final polish (#4335)

wenshao APPROVED the PR (review 4327485978: "No issues found in
the latest Round 9 changes... LGTM ✅") with 3 minor follow-up
suggestions in a separate COMMENTED review (4327443147). All
adopted; the 4th suggestion (3273077262) was already addressed
in Round 9.

**[Suggestion] Symmetric stderr breadcrumb on legacy
respondToPermission** (3273077256). The session-scoped sibling
already writes an unconditional `writeStderrLine` on its
`actualSessionId === undefined` rejection path (Round 8 /
3272493792); the legacy `POST /permission/<id>` route returned
`false` silently after the Round-8 oracle removal, leaving an
observability gap. Added matching `writeStderrLine`. Operators
tailing stderr at 3 AM now see legacy-route 404s without needing
QWEN_SERVE_DEBUG=1.

**[Suggestion] consensusQuorum contract mismatch** (3273077270).
The warning text told the operator "the override will be
ignored" but the function still propagated `permissionConsensusQuorum`
to BridgeOptions. The downstream mediator only reads it under
the consensus policy, so behavior was correct — but the public
contract contradicted itself. Adopt option (a): drop the value
to `undefined` when the strategy is not 'consensus' so the
returned struct matches what the warning promises. Updated the
existing `validatePolicyConfig` test to assert the new contract.

**[Suggestion] Stderr-breadcrumb assertion missing from
error-precedence regression test** (3273077272). The Round-8
test pinned the return-value behavior (`false`) but not the
unconditional-stderr promotion that was the primary behavioral
change of that hunk. Added `vi.spyOn(process.stderr, 'write')`
+ assertions for both "rejected permission vote" and the literal
requestId in the test. A future refactor that drops or downgrades
the log line is now caught.

**[Suggestion] _validPolicies underscore-prefix misleading**
(3273077262 — already addressed). Round 9's commit 6793b89
replaced the literal `_validPolicies` array with a single Set
derived from `SERVE_CAPABILITY_REGISTRY.permission_mediation.modes`
(per separate suggestion 3272581563). The underscore-prefixed
identifier is gone in current HEAD; replied via PR comment
pointing wenshao at the existing fix.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
@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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants