feat(serve): ACP/REST parity wave 2 — agents CRUD (5 methods)#4737
feat(serve): ACP/REST parity wave 2 — agents CRUD (5 methods)#4737chiga0 wants to merge 31 commits into
Conversation
issue #4542 路径 2 的完整实施设计,包含: - L2 分层架构与边界规则 - callback 注入跨切依赖(不抽共享 infra) - facade + 4 子服务内部结构 - WorkspaceRequestContext 最小集 - AcpSessionBridge 瘦身与改名 - /acp northbound ext methods(qwen/workspace/... 命名空间) - 文件变更清单与测试策略
- Remove listWorkspaceSessions/heartbeat from migration list (they access bridge-internal session map, must stay in bridge) - Add readBytes to FileService (GET /file/bytes route exists) - Fix auth routes to match actual device-flow pattern with flowId - Add agents.get (GET /workspace/agents/:agentType) - Add §8 SDK compat + /acp error format specification - Update /acp method table with corrected routes
- Change splitting principle from "child dependency" to "scope-based" (workspace-scoped → service, session-scoped → bridge) - Move 8 workspace methods out of bridge (was 1): initWorkspace, setWorkspaceToolEnabled, 5 status getters, restartMcpServer - Bridge exposes queryWorkspaceStatus + invokeWorkspaceCommand as new public methods for service to delegate child calls through - Fix originatorClientId to optional (matches existing AuditContext) - Add sessionId to WorkspaceRequestContext for audit correlation - Add 8 more /acp northbound methods (tool/toggle, 5 status, mcp/restart) - Update facade interface, deps, and file change list accordingly
…us, memory CRUD, fix callback sigs, add missing deps
…tus + invokeWorkspaceCommand Remove 8 workspace-scoped methods (initWorkspace, setWorkspaceToolEnabled, getWorkspaceMcpStatus, getWorkspaceSkillsStatus, getWorkspaceProvidersStatus, getWorkspaceEnvStatus, getWorkspacePreflightStatus, restartMcpServer) that have been migrated to DaemonWorkspaceService. Add two generic delegation methods (queryWorkspaceStatus, invokeWorkspaceCommand) that the service uses to forward requests through the live ACP channel.
Rename the interface and factory to better reflect their role as session-scoped bridge handles rather than HTTP-specific constructs. - HttpAcpBridge interface → AcpSessionBridge (+ deprecated type alias) - createHttpAcpBridge → createAcpSessionBridge (+ deprecated re-export) - httpAcpBridge.ts → acpSessionBridge.ts (git mv for history) - Updated all imports and type annotations across cli/serve - Updated error messages and JSDoc references - Deprecated aliases preserve backward compatibility
…init routes Rewire workspace-scoped REST routes in server.ts to delegate through DaemonWorkspaceService instead of calling bridge methods directly. The workspace service is constructed in runQwenServe.ts (production) and injected into createServeApp; a default is also constructed inside createServeApp for tests/embeds that don't inject one. Routes rewired: - GET /workspace/mcp - GET /workspace/skills - GET /workspace/providers - GET /workspace/env - GET /workspace/preflight - POST /workspace/init - POST /workspace/tools/:name/enable - POST /workspace/mcp/:server/restart The persistDisabledTools callback is extracted into a named binding in runQwenServe.ts so both the bridge (backward compat) and the workspace service share the same settings-lock implementation.
… deviceFlowRegistry - getWorkspaceEnvStatus now uses statusProvider.getEnvStatus() directly (daemon-local, no ACP query) instead of routing through queryWorkspaceStatus - getWorkspacePreflightStatus stitches daemon cells from statusProvider.getDaemonPreflightCells() with ACP cells from queryWorkspaceStatus, matching the old bridge's behavior - Made deviceFlowRegistry and subagentManager optional in DaemonWorkspaceServiceDeps to eliminate the undefined-as-never crash - Added statusProvider and isChannelLive deps to DaemonWorkspaceServiceDeps - runQwenServe.ts now passes the real statusProvider and a channel liveness callback derived from bridge.sessionCount - Rewrote daemonStatusProvider.test.ts to exercise the workspace service facade instead of removed bridge methods
…factoring Add queryWorkspaceStatus and invokeWorkspaceCommand to the fake bridge so DaemonWorkspaceService can be constructed internally by createServeApp. - queryWorkspaceStatus dispatches to the correct impl based on method name (mcp, skills, providers, preflight) - invokeWorkspaceCommand dispatches for MCP restart commands - Tool toggle tests updated: workspace service now calls persistDisabledTools directly, so bridge.setToolEnabledCalls assertions are replaced with HTTP response body assertions - Init workspace tests rewritten to use real temp directories since the workspace service now performs filesystem operations directly - Env/preflight status tests updated to match idle fallback behavior (workspace service uses statusProvider, not queryWorkspaceStatus for env) - MCP restart client-identity test simplified (originatorClientId flows through workspace request context, not invokeWorkspaceCommand params)
…rovider, simplify event spread
…ity, timeout, runtime guards - bridge.ts: return response as T (not any), stop injecting cwd into invokeWorkspaceCommand - agentsService: rename agent_created/updated/deleted → agent_changed with change/name/level data; add collision preflight before createSubagent - memoryService: rename memory_written/deleted → memory_changed matching workspaceMemory.ts - restartMcpServer: add 300s timeout, translate errorKind into McpServerNotFoundError/McpServerRestartFailedError, replicate pool-mode per-entry event fan-out - initWorkspace: force-overwrite uses O_WRONLY|O_NOFOLLOW + fd-based truncate instead of fs.writeFile; add post-open parent re-verification on both create and overwrite paths - getWorkspaceEnvStatus: hoist acpChannelLive before statusProvider check; add writeStderrLine in catch - getDaemonPreflightCells: add writeStderrLine in catch - preflight error cell: include errorKind via mapDomainErrorToErrorKind - auth/agents sub-service creation: Proxy-based stub when deps are undefined, throws descriptive error at call time - Update all test assertions to match new event names and data shapes
… import, test imports, acpChannelLive catch
…and, fix collision preflight, update stale import
…paceService The ACP dispatch handler called 8 workspace methods that were removed from the bridge interface during the workspace service extraction. Inject DaemonWorkspaceService into AcpDispatcher and route workspace status queries and mutations through it, matching the REST surface in server.ts.
bridge.ts no longer reads opts.contextFilename — the consuming code moved to DaemonWorkspaceService which receives its own copy. Remove the dead field from BridgeOptions and the dead argument spread in runQwenServe.ts.
…dings chiga0 R1 flagged that the File/Auth/Agents/Memory sub-services are built but never wired into any production route — server.ts/dispatch.ts call them 0 times, the old workspaceMemory.ts/workspaceAgents.ts still serve all traffic, and auth/agents are constructed with `undefined` deps behind throwing Proxy stubs. Rather than ship a dead, partially-broken layer alongside 1774 lines of live duplicate logic, drop it from this PR and land it together with the route wiring + e2e tests in a follow-up (R1 option b). Scope removed (deferred to wiring PR): - fileService/authService/agentsService/memoryService + their tests - validation.ts (only used by the sub-services) - file/auth/agents/memory members on the facade + their interfaces - fsFactory/deviceFlowRegistry/subagentManager/knownClientIds deps (no longer consumed by the facade once sub-services are gone) Design doc: add an implemented-vs-deferred scope table so it no longer claims route/ACP parity that hasn't landed (chiga0 R1 #2 / design.md). Live-code review fixes (facade + bridge stay wired): - bridge: add isChannelLive() wrapping liveChannelInfo(); wire the workspace service's acpChannelLive through it instead of `sessionCount > 0`, which mis-reports during the cold-spawn / post-kill windows (wenshao). - bridge: remove the dead `persistDisabledTools` local + BridgeOptions field + the createAcpSessionBridge arg — unread after the workspace methods moved out (fixes TS6133 build blocker; wenshao). - bridge: restore `MCP_RESTART_TIMEOUT_MS` (300s); still consumed by the runtime MCP add/remove paths the base branch added. - bridge: drop the dangling contextFilename comment fragment. - workspace-service: fix stale "Mirrors bridge.ts" JSDoc on canonicalizeExistingAncestor.
…ve, mock alignment, cleanup Code fixes: - initWorkspace: add !lst.isFile() guard to reject FIFOs/sockets/device nodes (DoS via libuv thread pool exhaustion on blocking read) - server.ts fallback: wire isChannelLive so direct createServeApp consumers get accurate acpChannelLive - dispatch.ts: fix stray space before comma (Prettier) - bridgeTypes.ts: remove orphaned JSDoc fragment from conflict resolution - server.test.ts: add isChannelLive() + opts param to fakeBridge mock (matches updated AcpSessionBridge interface) - integration.test.ts: add isChannelLive/queryWorkspaceStatus/ invokeWorkspaceCommand to minimalBridge fake Test coverage migration (TOCTOU/error-translation/pool-mode/bridge delegation) acknowledged and agreed — tracked as next step.
Migrates the test coverage wenshao flagged across 5 review rounds: restartMcpServer (7 new tests): - mcp_server_restart_refused event on restarted:false - mcp_server_not_found errorKind → McpServerNotFoundError - mcp_restart_failed errorKind → McpServerRestartFailedError - non-errorKind errors re-thrown without translation - pool-mode entries fan-out (per-entry events) - malformed pool entries skipped without crash initWorkspace (3 new tests): - FIFO/non-regular-file guard (mkfifo + !isFile()) - EEXIST conflict guard (content exists, force not set) - parent-symlink-outside-workspace rejection Total: 23 → 32 tests in facade.test.ts.
📋 Review SummaryThis PR adds 5 ACP HTTP dispatch methods for workspace agent CRUD operations ( 🔍 General Feedback
🎯 Specific Feedback🔴 CriticalImplementation not yet present: This PR currently contains only a placeholder commit with no code changes. The following critical items must be addressed before merge:
🟡 HighDesign considerations for implementation:
🟢 MediumImplementation suggestions:
🔵 LowNice-to-have improvements:
✅ Highlights
Pre-Merge ChecklistBefore this PR can be reviewed for implementation quality:
|
e1d660e to
c1c4a96
Compare
428377d to
8ec3d77
Compare
c1c4a96 to
205c521
Compare
205c521 to
7fac21b
Compare
…dening
Add 24 `_qwen/*` extension methods to the ACP HTTP dispatch, achieving
near-complete REST parity for the `/acp` transport. After this PR,
ACP-native clients can access session extensions, workspace memory,
file operations, auth device-flow, tools, and MCP management.
Methods added:
- Session (6): recap, btw, shell, detach, context_usage, tasks
- Memory (2): workspace/memory (read), workspace/memory/write
- Files (7): file/{read,read_bytes,stat,list,glob,write,edit}
- Auth (4): workspace/auth/{status,device_flow/start,get,cancel}
- Workspace (5): tools, mcp/tools, mcp/servers/{add,remove}, sessions/delete
Production hardening:
- Protocol compliance: 415 (bad Content-Type), 406 (bad Accept),
501 (batch JSON-RPC), 404 (unknown connection, was 400)
- Error mapping: FsError (13 kinds), WorkspaceMemoryFileTooLarge/WriteTimeout,
TooManyActiveDeviceFlows, UnsupportedDeviceFlowProvider, UpstreamDeviceFlow,
McpServerNotFound — all with structured errorKind in JSON-RPC data
- Input validation: BTW_MAX_INPUT_LENGTH (4096), memory content 1MB limit,
scope/mode reject-on-invalid (was silent default)
- buildInitializeResult advertises all new methods
Files changed:
- dispatch.ts: +600 lines (24 case branches + toRpcError extensions)
- index.ts: +30 lines (415/406/501/404 protocol checks)
- server.ts: +5 lines (wire fsFactory + deviceFlowRegistry)
- workspaceMemory.ts: export collectWorkspaceMemoryStatus
- transport.test.ts: +257 lines (15 test cases)
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
8ec3d77 to
c4d9668
Compare
7fac21b to
884e02d
Compare
c4d9668 to
2f80ee0
Compare
2f80ee0 to
1e442b7
Compare
|
Agents CRUD methods merged into the unified PR #4827 (wave 1+2 combined). |
Summary
Add 5 `_qwen/workspace/agents/*` CRUD methods to the ACP dispatch, completing full REST parity. After wave 1 + wave 2, all REST routes are reachable via `/acp`.
Depends on: #4736 (wave 1)
Methods Added (5)
Implementation
ACP/REST Parity Status (after wave 1 + 2)
Remaining: global permission vote (P4, architectural difference, not a gap).
Test Plan