feat(serve): add hooks diagnostic HTTP/ACP surface (issue #4514 T3.9)#4822
Conversation
|
Thanks for the PR @doudouOUC! Template: ✓ — all required headings present (What this PR does, Why it's needed, Reviewer Test Plan with How to verify / Evidence / Tested on, Risk & Scope, Linked Issues). Minor: On direction: this addresses T3.9 from the tracking issue (#4514), adding read-only HTTP/ACP endpoints for hook configuration status. Remote clients (web-shell, SDK consumers) currently can't query hooks — this closes that gap cleanly. The scope (hooks portion only, leaving On approach: +600 lines across 12 files is proportional — types flow through three packages (acp-bridge, cli, sdk-typescript) following the established pattern for status endpoints. The workspace/session endpoint split mirrors MCP, skills, and providers. The idle factory for event metadata and the Moving on to code review. 🔍 中文说明感谢贡献 @doudouOUC! 模板:✓ — 所有必要标题已包含(What this PR does、Why it's needed、Reviewer Test Plan 含验证步骤/对比/测试平台、Risk & Scope、Linked Issues)。小问题:PR 正文仍缺少 方向:本 PR 对应 tracking issue #4514 的 T3.9,为远程客户端(web-shell、SDK 消费者)新增只读 hooks 诊断端点。范围合理(仅 hooks, 方案:12 文件 +600 行与范围成正比——类型在三个包中流转,遵循已有 status endpoint 模式。workspace/session 端点拆分与 MCP、skills、providers 一致。 进入代码审查 🔍 — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
PR body does not follow the required template. Please update to use the headings from .github/pull_request_template.md: What this PR does, Why it's needed, Reviewer Test Plan (with Evidence Before/After + Tested on), Risk & Scope, and Linked Issues. See my comment above for details.
|
Thanks — updated the PR body to follow the required template headings (What this PR does, Why it's needed, Reviewer Test Plan with Evidence/Tested on, Risk & Scope, Linked Issues). |
There was a problem hiding this comment.
Pull request overview
Adds a read-only diagnostic surface for hook configuration/state in qwen serve, exposing both workspace-configured hooks and session-registered (skill) hooks via HTTP/ACP, and wires corresponding SDK client helpers + capability tags.
Changes:
- Introduce
GET /workspace/hooksandGET /session/:id/hooksstatus endpoints (and corresponding ACP ext-methods) to inspect hook state. - Add hook-status schema/types across
acp-bridge,cli, andsdk-typescript, including “idle” placeholders and forward-compatible unions. - Enable
/hooksslash command in ACP and non-interactive modes (text listing vialistaction).
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/acp-bridge/src/status.ts | Adds hook status types, ext-method IDs, and an idle workspace hooks factory with event metadata. |
| packages/acp-bridge/src/bridgeTypes.ts | Extends AcpSessionBridge with workspace/session hook status read methods. |
| packages/acp-bridge/src/bridge.ts | Implements bridge methods to request hook status via ACP ext-methods. |
| packages/cli/src/acp-integration/acpAgent.ts | Implements hook status builders + serialization and dispatches new ext-methods. |
| packages/cli/src/serve/workspace-service/types.ts | Extends DaemonWorkspaceService interface with getWorkspaceHooksStatus. |
| packages/cli/src/serve/workspace-service/index.ts | Implements workspace hooks status via queryWorkspaceStatus + idle fallback. |
| packages/cli/src/serve/server.ts | Registers new REST routes for workspace and session hooks status. |
| packages/cli/src/serve/capabilities.ts | Advertises workspace_hooks and session_hooks capabilities. |
| packages/cli/src/ui/commands/hooksCommand.ts | Enables /hooks (and /hooks list) in non-interactive and ACP modes. |
| packages/sdk-typescript/src/daemon/types.ts | Adds SDK mirror types for hook configs/entries and workspace/session hook status snapshots. |
| packages/sdk-typescript/src/daemon/DaemonClient.ts | Adds workspaceHooks() and sessionHooks(sessionId) client methods. |
| packages/sdk-typescript/src/daemon/index.ts | Re-exports newly added daemon hook status/config types. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
📋 Review SummaryThis PR adds read-only HTTP/ACP endpoints for hook configuration status ( 🔍 General Feedback
🎯 Specific Feedback🟡 High
🟢 Medium
🔵 Low
✅ Highlights
|
Review response summary
|
Code reviewReviewed the full diff (824 lines, 12 files). The implementation is clean and follows established patterns precisely. What works well:
No blockers found. Two observations (not blocking):
TestingThis PR adds new daemon HTTP endpoints (no modification of existing behavior). CI passed (all checks successful). Real-scenario testing requires the full daemon infrastructure ( CI status: All checks successful (1 skipped — Qwen PR Review action). 中文说明代码审查审查了完整 diff(824 行,12 个文件)。实现清晰,严格遵循已有模式。 优点:
未发现阻塞项。 两个观察(不阻塞):
测试本 PR 新增 daemon HTTP 端点(不修改已有行为)。CI 通过。真实场景测试需要完整 daemon 基础设施,当前 CI 环境不具备。PR 的测试计划提供了清晰的 curl 验证步骤。 CI 状态: 全部通过(1 个跳过)。 — Qwen Code · qwen3.7-max |
ReflectionStepping back: this PR does exactly what T3.9 asks for — exposes hook configuration as read-only HTTP/ACP endpoints so remote clients can query them. The implementation is mechanical in the best sense: it follows the established pattern for status endpoints step by step (types → bridge → agent → routes → workspace service → capabilities → SDK mirror → command mode expansion). Nothing surprising, nothing over-engineered. My independent proposal would have been essentially identical — same package split, same type flow, same idle factory approach. The PR matches or exceeds it. The No correctness bugs, no security holes, no regressions. CI passes. The only outstanding Verdict: approve. This is ready to merge once a maintainer dismisses the stale changes-requested review. 中文说明总结本 PR 准确实现了 T3.9 的需求——将 hooks 配置以只读 HTTP/ACP 端点暴露给远程客户端。实现方式是"机械式"的最佳实践:逐步遵循已有的 status endpoint 模式(类型 → bridge → agent → 路由 → workspace service → capabilities → SDK 镜像 → 命令模式扩展)。无意外,无过度设计。 我的独立方案与 PR 基本一致——相同的包拆分、类型流转、idle factory。PR 符合甚至超出预期。
无正确性 bug、无安全漏洞、无回归。CI 通过。唯一的 CHANGES_REQUESTED 来自 bot 的初始模板审查——作者已更新模板,该审查已过时。 结论:通过。 维护者解除过时的 changes-requested 后即可合并。 — Qwen Code · qwen3.7-max |
wenshao
left a comment
There was a problem hiding this comment.
Additional findings (not on diff lines):
[Critical] [build] SDK browser daemon bundle is 108581 bytes, exceeding the 108544 byte limit by 37 bytes. The new DaemonHookEventName union (18 string literals + (string & {})) and 13 new exported type definitions in packages/sdk-typescript/src/daemon/types.ts push the bundle over the limit. The limit needs to be bumped in packages/sdk-typescript/scripts/build.js, or the new types need to be tree-shaken more aggressively.
[Suggestion] [test] fakeBridge() in server.test.ts implements FakeBridge extends AcpSessionBridge (strict, not Partial) but does not provide getWorkspaceHooksStatus() or getSessionHooksStatus(). While this is masked by tsconfig's exclude array, any test that hits /workspace/hooks or /session/:id/hooks will get a runtime TypeError. Add stub implementations to fakeBridge().
[Suggestion] The serve barrel (packages/cli/src/serve/index.ts) does not re-export the new hook types or createIdleWorkspaceHooksStatus, breaking the public-API consistency established by every other workspace/session status surface.
— qwen3.7-max via Qwen Code /review
Review response: wenshao round 1 — all items fixed in
|
| # | File | Comment | Action |
|---|---|---|---|
| 1 | status.ts:8 |
HookEventName import type-only | Fixed |
| 2 | capabilities.ts:217 |
Missing test arrays | Fixed — added to EXPECTED_STAGE1_FEATURES |
| 3 | acpAgent.ts:2195 |
catch block initialized: true |
Fixed → false |
| 4 | acpAgent.ts:2204 |
Session builder no try/catch | Fixed — added try/catch with error cells |
| 5 | acpAgent.ts:2044 |
Duplicate HOOK_* maps | Fixed — consolidated into IDLE_HOOK_EVENTS from status.ts |
| 6 | acpAgent.ts:2220 |
Matcher unconditional spread | Fixed — conditional spread |
| 7 | Review body: SDK bundle 37 bytes over | Fixed — bumped limit 106KB → 108KB | |
| 8 | Review body: fakeBridge stubs | Fixed — added both stubs | |
| 9 | Review body: serve barrel missing | Fixed — added all hook types + IDLE_HOOK_EVENTS |
7d8ed8a to
060e04a
Compare
Review round — wenshao suggestion (post-approval)
Added |
Local Verification ReportBranch: Unit Tests
Test failure analysis (PR branch 17 failed vs base branch 15 failed):
New test failure —
ESLintFiles linted: TypeScript Type Check
CLI typecheck delta (after
Whitespace CheckSummary
Action Items Before Merge
Verified locally by wenshao |
|
Thanks for the thorough local verification! Fixed the test assertion bug in 962c38f:
|
wenshao
left a comment
There was a problem hiding this comment.
[Suggestion] Missing test coverage for new bridge and SDK methods:
packages/acp-bridge/src/bridge.test.ts—getSessionHooksStatus()has no round-trip orSessionNotFoundErrorrejection test (all other session-scoped status methods have both).packages/sdk-typescript/test/unit/DaemonClient.test.ts—workspaceHooks()andsessionHooks()have no unit tests (existing analogous methods likeworkspaceMcp(),workspaceSkills()all have mock HTTP response tests).packages/sdk-typescript/test/unit/daemon-public-surface.test.ts— 11 new type exports added tosrc/daemon/index.tsbut the compile-time fence has no correspondingexpectTypeOfassertions.
— qwen3.7-max via Qwen Code /review
Add read-only endpoints for hook configuration status, enabling remote clients (web-shell, SDK consumers) to query workspace and session hooks. - GET /workspace/hooks — config-sourced hooks (user/project/extensions) - GET /session/:id/hooks — runtime session hooks (skill-registered) Wiring: status types + idle factory (acp-bridge), bridge interface + impl, ACP agent builders + extMethod dispatch, workspace-service facade, REST routes, capability tags, SDK types + client methods, barrel exports. Slash command /hooks enabled for ACP mode (text output via listCommand). 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
Copilot review feedback: DaemonHookEventName was defined but not used on the entry type, so SDK consumers got plain `string` without autocomplete/narrowing. Now uses the forward-compat union type. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
- Fix HookEventName import to type-only (ESLint consistent-type-imports) - Add workspace_hooks + session_hooks to EXPECTED_STAGE1_FEATURES test array - Set initialized: false in catch block (was true, contradicted errors cell) - Add try/catch to buildSessionHooksStatus (matching workspace pattern) - Consolidate HOOK_MATCHER_KINDS + HOOK_EVENT_DESCRIPTIONS into IDLE_HOOK_EVENTS (single source of truth, exported from status.ts) - Use conditional spread for session hook matcher field (consistency) - Bump SDK browser bundle size limit 106KB → 108KB for new hook types - Add fakeBridge stubs for getWorkspaceHooksStatus/getSessionHooksStatus - Add hooks types to serve barrel exports 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
…tags Codex review caught that workspace_hooks and session_hooks in EXPECTED_STAGE1_FEATURES would appear before conditional tags in the EXPECTED_REGISTERED_FEATURES spread, mismatching the registry declaration order. Filter them from the spread and append at the correct position (after non_blocking_prompt, matching the registry). 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
Add FakeBridgeOpts + call counters for workspaceHooksImpl / sessionHooksImpl and happy-path supertest assertions for GET /workspace/hooks and GET /session/:id/hooks, matching the pattern of existing diagnostic endpoints.
…description
queryWorkspaceStatus in fakeBridge was missing the
qwen/status/workspace/hooks case, causing it to fall through to idle()
which returns all 18 events. Also fixes description string to match
IDLE_HOOK_EVENTS ('Before tool execution', not 'Before a tool is executed').
Aligns with the codebase convention of using ':id' placeholder instead of interpolating the actual sessionId value into error labels.
962c38f to
a3a7a41
Compare
Review round — wenshao comments (rebase + fix)
|
Local Verification ReportBranch: TypeScript Compilation (
|
| Package | PR Branch | daemon_mode_b_main | Status |
|---|---|---|---|
packages/core |
0 errors | 0 errors | ✅ Clean |
packages/acp-bridge |
2 errors | 1 error | |
packages/cli |
187 errors | 167 errors | ✅ No new real errors (stale dist/) |
packages/sdk-typescript |
1 error | 1 error | ✅ Same pre-existing error |
acp-bridge note: The +1 error is PostToolBatch does not exist in type 'Record<HookEventName, ...>' in the new IDLE_HOOK_EVENTS constant. PostToolBatch exists in core's source enum (hooks/types.ts:286) but the locally compiled dist/ is stale. CI builds fresh and this resolves. The pre-existing error (DaemonBridgeTelemetryMetrics) is the same on both branches.
Unit Tests (vitest)
| Test Suite | PR Branch | daemon_mode_b_main | Status |
|---|---|---|---|
acp-bridge (8 files) |
376 passed | 376 passed | ✅ All pass |
server.test.ts |
❌ import resolution | ❌ import resolution |
server.test.ts failure: @qwen-code/acp-bridge/mcpTimeouts import resolution fails in vitest on both the PR branch AND daemon_mode_b_main. This is a pre-existing stale dist/ issue — CI builds fresh dist before running tests, so this doesn't reproduce there.
Code Review
This PR adds hooks diagnostic HTTP/ACP surfaces for issue #4514 T3.9:
New types (status.ts):
ServeWorkspaceHooksStatus/ServeSessionHooksStatus— structured status payloadsServeHookEntrywith discriminatedServeHookConfigunion (command / http / function / prompt / unknown)IDLE_HOOK_EVENTS— static event catalog with descriptions and matcher kindscreateIdleWorkspaceHooksStatus()— fallback for pre-channel-ready state
Server routes (server.ts):
GET /workspace/hooks— workspace-level hook configurationGET /session/:id/hooks— session-scoped hook status
ACP integration (acpAgent.ts):
serializeHookConfig()— serializesHookConfigto the wireServeHookConfigtype with undefined-conditional spreading (no nulls sent)buildWorkspaceHooksStatus()/buildSessionHooksStatus()— queryconfig.getHookSystem()with error fallback
SDK types (sdk-typescript/types.ts):
- Mirror types with
Daemonprefix and forward-compat(string & {})arm onDaemonHookEventName DaemonClient.workspaceHooks()/sessionHooks()— typed fetch methods
Other:
/hookscommand now available innon_interactiveandacpmodes- Capability registry entries
workspace_hooks/session_hooks - Browser bundle size cap bumped 106K → 108K
- Server tests: 2 new test cases for workspace/session hooks routes
Verdict
✅ Ready to merge — No regressions. All bridge tests pass. Server test failure and TSC errors are pre-existing stale-dist issues confirmed on daemon_mode_b_main. Clean implementation following existing status surface patterns.
wenshao
left a comment
There was a problem hiding this comment.
No issues found. LGTM! ✅ — qwen3.7-max via Qwen Code /review
Already have 2 approved, 3ks.
What this PR does
Add two read-only HTTP/ACP endpoints for hook configuration status, enabling remote clients (web-shell, SDK consumers) to query workspace and session hooks via the daemon HTTP surface.
GET /workspace/hooks— returns config-sourced hooks (user, project, extension sources) with global disabled state and per-event metadata (descriptions, matcher kinds)GET /session/:id/hooks— returns runtime session hooks registered by skills for a specific session/hooksslash command enabled for ACP mode (text output vialistCommand)Why it's needed
Issue #4514 T3.9 tracks the
/extensions,/settings,/hooksHTTP surface. Remote clients (web-shell, SDK consumers) currently cannot query hook configuration — the/hookscommand is interactive-only. This PR addresses the hooks portion, the simplest of the three (read-only, with an existing non-interactive text output path).Reviewer Test Plan
How to verify
qwen serve --port 7779curl -X POST localhost:7779/session -H 'Content-Type: application/json' -d '{"cwd":"'$(pwd)'"}'— note thesessionIdcurl localhost:7779/workspace/hooks | jq .initialized: true,disabled: false,hooks: [...](populated if you have hooks configured),events: {…}(18 entries with descriptions and matcherKind)curl localhost:7779/session/<sessionId>/hooks | jq .hooks: [](no session hooks registered yet)curl localhost:7779/session/nonexistent/hookscurl localhost:7779/capabilities | jq .features— should includeworkspace_hooksandsession_hooks/hooksvia prompt endpoint — should return text-formatted hook listEvidence (Before & After)
N/A — new endpoints, no UI changes.
Tested on
Risk & Scope
HOOK_EVENT_DESCRIPTIONSandIDLE_HOOK_EVENTSuseRecord<HookEventName, ...>— new enum members trigger compile errorsServeUnknownHookConfigwithout index signature preserves discriminated union narrowing;serializeHookConfigdefault branch handles future hook typesLinked Issues
Closes partial: #4514 (T3.9 hooks portion)
🤖 Generated with Qwen Code