qwen serve 多工作区架构 Phase 4:Workspace-Qualified ACP 路由隔离设计与实现解析
2026/9/13 17:43:48 网站建设 项目流程

qwen serve 多工作区架构 Phase 4:Workspace-Qualified ACP 路由隔离设计与实现解析

【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code

qwen serve 是 qwen-code 开源 AI 编码代理内置的守护进程服务。当单个 daemon 同时托管多个工作区运行时(multi-workspace),传统单一 ACP 端点无法区分请求归属的工作区。本文围绕设计文档 daemon-multi-workspace-phase4-acp.md 展开,系统讲解 Phase 4 如何在/workspaces/:workspace/acp挂载按工作区隔离的 ACP 传输端点、如何通过单个 WebSocket upgrade 监听器按 URL 分发、以及设备流注册表、CDP 隧道、信任门控与能力广告的落地方式。读完本文,你将掌握 qwen serve 多工作区 ACP 的完整路由与隔离模型,并能对照仓库源码定位每一处关键实现。

背景:为什么 ACP 需要按工作区隔离

qwen serve的 ACP(Agent Client Protocol)传输是 Web Shell 与桌面客户端连接 daemon 的主要通道,承载 Streamable HTTP 与反向 WebSocket 两种形态。在 Phase 2a/2b 引入多工作区注册与 REST 级 workspace-qualified 路由之后,/acp仍是单一挂载点:一个 daemon 只有一个AcpDispatcher,天然绑定在主工作区(primary runtime)上。这意味着:

  • 非主工作区的会话无法通过 ACP 独立寻址;
  • 不同工作区的文件读写、权限、MCP、记忆等操作共享同一份 dispatcher 状态,无法按工作区隔离;
  • Web Shell 无法针对特定工作区建立独立的 ACP 连接。

Phase 4(对应 issue #6378)的目标即是在qwen serve上实现workspace-qualified ACP:为每个已注册的工作区运行时挂载独立的 ACP 端点/workspaces/:workspace/acp,每个运行时拥有自己的 dispatcher 与连接状态;同时保持旧有/acp端点绑定主运行时,确保现有 Web Shell 与既有 ACP 客户端不受影响。

需要明确的是,Phase 4 的范围限定在 ACP 传输本身(Streamable HTTP + 反向/acpWebSocket、其镜像的工作区方法、以及反向 MCP/CDP)。语音端点/workspaces/:workspace/voice/stream与 daemon 管理的 channel worker 属于Phase 4b,动态工作区增删属于Phase 5,均不在本文讨论范围内。

核心结论:这是接线与路由改造,而非重写

设计文档在勘测现有代码后给出一个关键判断:Phase 4 大部分工作是wiring and routing(接线与路由),不是重写。依据如下:

  • AcpDispatcher在构造上已经按工作区绑定:其构造参数包含bridgeboundWorkspaceworkspaceworkspaceRememberLanefsFactorydeviceFlowRegistrysessionShellCommandEnabledregistryarchiveCoordinator(见 dispatch.ts L644-656)。dispatcher 服务的每个镜像工作区方法都读取这些字段,因此把 dispatcher 绑定到某个运行时,文件、权限、设置、信任、工具、MCP、记忆、代理、认证天然就被限定在该运行时内。
  • parseRequestedWorkspace(dispatch.ts L694-697)已存在一致性校验:当请求中的workspaceCwd不等于this.boundWorkspace时抛出WorkspaceMismatchError,并映射为INVALID_PARAMS(L577)。
  • Phase 3 已把镜像 REST 表面改为 per-runtime,clientMcpSenderRegistry已是 per-runtime 字段。

真正需要做的工作只有四项:(1)把单一 ACP 挂载拆成"每个运行时一个 dispatcher"(每个都有自己的 remember-lane,但仍是一次mountAcpHttp调用、一个upgrade 监听器;由AcpHttpHandle统一持有所有运行时的注册表);(2)扩展该 WebSocket upgrade 监听器,按 URL 路径分发;(3)保持设备流注册表为 daemon 全局单例,所有挂载共享(并尽力将事件扇出到每个受信任运行时的 bridge);(4)将新的workspace_qualified_acp能力标签同步到 SDK/CLI 能力类型与测试。

系统性重构:八大加固轴

评审过程中发现过一个 Critical 问题:早期迭代把设备流注册表做成 per-runtime,导致次级挂载未认证(报device_flow "not configured")。最终 ACP 挂载沿八个轴重构,形成最终架构:

  1. 运行时 ACP 挂载工厂:一次mountAcpHttp调用持有primaryMountsecondaryMounts映射(每个非主运行时一个RuntimeAcpMount),每个挂载携带primary标记。HTTP 与 WS 两条路径都先按选择器解析挂载,再委托给共享处理器。
  2. 路由 + 连接隔离:复数选择器(plural selector)把主工作区别名到primaryMount,否则解析 per-runtime 挂载。不受信任的非主工作区在 HTTP 与 WS 两条路径上、在派生任何子进程之前即以 403 拒绝。
  3. 原始 request-target 的 WS 解析:upgrade 监听器解析原始request-target(而非new URL().pathname——后者会规范化%2e%2e),使得非规范化的点段/反斜杠选择器在路由之前就被销毁。
  4. Daemon 全局设备流 + 扇出:设备流注册表保持为 daemon 单实例(OAuth 凭据是进程级全局状态)。次级挂载通过opts.deviceFlowRegistry共享它;认证流事件尽力扇出到每个受信任运行时的 bridge(resolveEventBridges)。
  5. 仅主挂载启用 CDP 与客户端 MCP:CDP 隧道声明以activeMount.primary为门槛;复数 POST 返回 dispatch promise。
  6. 已处置生命周期门控dispose()之后,共享 HTTP 处理器返回503 server_disposed,而不是在关停排空期间与已拆除的注册表竞争。dispose()幂等。
  7. 聚合可观测性AcpHttpHandle.getSnapshot()汇总主挂载与每个次级挂载的连接数与 WS 流数,使 daemon 指标报告所有工作区的 ACP 连接,而非仅主工作区。
  8. 能力广告resolveAcpHttpEnabled()是对QWEN_SERVE_ACP_HTTP的唯一解释;workspace_qualified_acp仅在 ACP HTTP 表面已启用多工作区会话激活时广告。

在 index.ts 中,RuntimeAcpMount接口(L540-563)完整体现了这一模型:每个挂载携带primaryworkspaceCwdrouteLabelrateLimitScoperegistrydispatcherworkspaceRememberLane、WebSocket 集合、draining 标记,以及ensureChromeDevToolsMcpRegistered/removeChromeDevToolsMcpIfUnusedclientMcpProviderFactory(后三者对非主挂载为空实现,见下)。

审查后的边界加固:六项缺口收口

挂载架构保持不变的前提下,最终修复轮次在不替换AcpHttpHandle、不引入新路由策略模块的前提下收口了六个边界缺口:

  1. 单一合格路由就绪判定:workspace-qualified ACP 仅在"ACP HTTP 已启用且工作区注册表包含超过一个运行时"时才算就绪。HTTP 路由注册、WebSocket 路径识别、能力广告与外层限流器豁免必须与该判定一致。单工作区 daemon 继续只暴露传统/acp
  2. 单一限流计费:外层 Express 限流器精确豁免已启用的/workspaces/<single-selector>/acp传输路径(含该路由既有的大小写与尾斜杠行为),邻近路径仍受限。ACP 传输仍负责应用 JSON-RPC 方法分层,因此一次合格提示词只消耗提示词桶,而不会同时消耗 mutation 与提示词两个桶。
  3. 结构化畸形路径失败:Express 路由参数解码失败若同时是URIError实例且标记了 HTTP 400 状态,返回结构化400 invalid_request;其他URIError与无关失败保留通用 500 处理。WebSocket 路径保留其现有显式 400 响应。
  4. 日志安全选择器:用于面向运维的 WebSocket 拒绝日志的解码选择器经过现有logSafe清洗器,编码的终端控制字符无法伪造或拆分 stderr 行。对应的回归测试见 workspace-qualified-acp.test.ts L2024。
  5. 终结性处置dispose()是不可逆的生命周期转换。运行后attachServer()无法重建 WebSocket 服务器或 upgrade 监听器;重复调用dispose()attachServer()保持无害。
  6. 带工作区归属的完整诊断:聚合 ACP 快照新增附加连接诊断,装饰workspaceIdworkspaceCwdprimary;汇总计数器不变,公开的主registry为兼容性保留,daemon 的detail=full读取聚合连接列表。现有连接上限保持 per-mount 限制,因为每个挂载都以同一配置上限构造。

每一项契约都在生产变更之前由一条回归测试钉死。验证覆盖聚焦的 ACP、限流、daemon-status 与 serve-server 测试套件,外加构建、类型检查、lint 与 serve fast-path bundle 闭合检查。

架构:per-runtime ACP 挂载

Phase 4 采用"一个 daemon、N 个独立工作区运行时"(Option B)。对 ACP 而言:

  • 每个已注册运行时获得自己的AcpDispatcher+ConnectionRegistry+ 反向 MCP provider 工厂,全部绑定到该运行时的bridge/workspace/routeFileSystemFactory/clientMcpSenderRegistry/env;每个 dispatcher 收到同一个 daemon 全局设备流注册表。
  • /acp保持绑定主运行时的 dispatcher(线上行为不变)。
  • /workspaces/:workspace/acp绑定到解析出的运行时的 dispatcher。
  • 不变量:mountAcpHttp仍恰好调用一次,并恰好安装一个httpServer.on('upgrade', ...)监听器。其入参从"单一 bridge + opts"改为接受WorkspaceRegistry(外加共享的非工作区关注点:token、allowedOrigins、hostname、checkRatesessionShellCommandEnabledcdpTunnelRegistry)。内部构建Map<workspaceId, RuntimeAcpMount>;主条目仍可通过旧/acp路径寻址。
  • 每个RuntimeAcpMount以该运行时自己的bridgeworkspacerouteFileSystemFactoryclientMcpSenderRegistryenv、新的 per-runtimeWorkspaceRememberTaskLane(runtime.bridge)、其AcpDispatcher与其ConnectionRegistry构造。daemon 全局设备流注册表、archiveCoordinatorsessionShellCommandEnabled共享。
  • 四个分发入口都必须选择解析出的运行时的挂载而非主挂载:复数路径上的POSTGET(SSE)与DELETE(Express,经resolveWorkspaceRuntimeFromParam;基线实现各自闭包单一 dispatcher),以及 WS upgrade 分支。旧/acp的 POST/GET/DELETE/upgrade 继续分发到主挂载。

在源码层面,index.ts 的createSecondaryAcpMount(L1343-1439)展示了次级挂载的完整构造:独立ConnectionRegistry(含独立的preAttachBudget与连接上限opts.maxConnections)、以rt.bridge/rt.workspaceCwd新建的WorkspaceRememberTaskLane(L1368-1371)、以rt.bridge+rt.workspaceCwd等运行时字段构造的AcpDispatcher(L1379-1410)、共享的opts.deviceFlowRegistry(L1392)与archiveCoordinator(L1395),以及按opts.clientMcpOverWs条件创建的clientMcpProviderFactory(L1429-1437,闭包rt.clientMcpSenderRegistryrt.bridge)。secondaryMountsMap<string, RuntimeAcpMount>(L1441),mountForWorkspace按 workspaceId 在主挂载与次级挂载间切换(L1444-1449),getOrCreateSecondaryMount仅为受信任、非主、非内部运行时创建挂载(L1450-1465)——不受信任的工作区在 HTTP 与 WS 两条路径上都会先被 403 拒绝,为它们分配 dispatcher/registry/remember-lane 纯属浪费。

会话生命周期方面,复数挂载上的 ACPsession/new/load/resume必须触发同样的 bridge 生命周期register/remove回调,喂给 Phase 2b 的WorkspaceSessionOwnerIndex(见 workspace-registry.ts L48-119)。这样通过/workspaces/B/acp创建的会话必须能被 REST owner-routed 读取(context、stats 等)与resolveLiveSessionOwner发现——ACP 创建也要喂给 owner 索引,实现跨传输的会话归属一致性。

HTTP 路由实现:/workspaces/:workspace/acp 的注册与解析

复数 ACP 路由在 index.ts L1522-1540 注册:当workspaceQualifiedAcpEnabled(即opts.workspaceRegistry !== undefined)为真时,对/workspaces/:workspace/acp分别注册POSTGETDELETE,三者都先经resolvePluralMount(req, res)解析挂载,再把请求委托给共享的handleAcpPost/handleAcpGet/handleAcpDelete

resolvePluralMount(L1480-1519)的解析顺序:

  1. workspaceRegistry时返回400 workspace_mismatch
  2. 调用resolveWorkspaceEntryFromParam(workspace-route-runtime.ts L24-60):先按 workspace id 精确匹配,再按可移植绝对路径匹配(含规范化 canonical cwd 比对),失败返回400 workspace_mismatch
  3. 运行时条目非 active/draining 时返回sendWorkspaceRuntimeUnavailable
  4. 非主且不受信任时返回403 untrusted_workspace(带workspaceCwdworkspaceId);
  5. 主运行时直接返回primaryMount;否则getOrCreateSecondaryMount,失败返回400 workspace_mismatch

这条链同时实现了三种失败语义:未知选择器(400workspace_mismatch)、不受信任运行时(403untrusted_workspace)、未注册挂载(400workspace_mismatch),且从不回退到主挂载

WebSocket upgrade 分发:核心设计

upgrade 监听器是 ACP 路由中唯一不由 Express 驱动的位置,因此需要显式路径处理。设计要点:

  • 共享安全检查前置:loopback / host allowlist / CSRF(origin) / bearer token 检查保持原样,在任何工作区解析之前统一应用。
  • 扩展路径分类:基线是pathname === '/acp' | '/cdp' | extraRoute;Phase 4 增加/workspaces/:workspace/acp分支:
    1. 匹配前缀并提取原始:workspace选择器段;
    2. 用纯函数resolveRegisteredWorkspaceRuntimeByPathSelector(registry, decodeURIComponent(selector))解析(id 优先,然后编码的 canonical cwd,与 REST 解析器一致);
    3. 无匹配:以 400 级关闭拒绝 upgrade(socket.write('HTTP/1.1 400 ...')+destroy()),镜像 REST 的workspace_mismatch,不回退主挂载;
    4. 匹配:对解析出的运行时的 dispatcher +ConnectionRegistry(而非主挂载的)执行 ACP initialize 握手。

源码层面,upgrade 监听器(index.ts L1587 起)先用rawRequestPathname(req.url)原始路径(L1611),再用pluralWorkspaceRawSelector匹配复数 ACP 形状(L1619-1622)。这是刻意的安全设计:WHATWG URL 会规范化点段,/workspaces/%2e%2e/acp会被折叠为/acp而静默绑定主挂载;原始路径解析使其无法绕过。对应的回归测试见 workspace-qualified-acp.test.ts L1855(%2e%2e点段拒绝)与 L2024(日志清洗)。

安全检查通过后,路径分支如下:

  • /cdp分支(L1760-1769):仅在opts.cdpTunnelOverWs === true且提供cdpTunnelRegistry时可达,升级后交给attachCdpClient
  • 复数 Voice 形状(L1771-1817):Phase 4b 预留路径,按 id/路径解析运行时并校验信任后交给opts.workspaceVoiceConnection
  • 复数 ACP 形状(L1824 起):解码选择器(解码放在形状/遍历检查之后,防止解码重新引入/..),workspaceRegistry缺失时 400 拒绝,解析运行时后按信任门控,最终以wss.handleUpgrade升级并接入解析出的运行时的 dispatcher;
  • /acp:保持绑定primaryMount

几个值得注意的细节:(1)%2F编码的 cwd 选择器——daemon 自行解析原始 upgrade URL(new URL(req.url, ...)),不受 Express 路径解码影响,但反向代理仍可能规范化%2F,因此代理部署下推荐使用基于id的选择器(与 Phase 2b/3 REST 的建议一致);HTTP 复数路由复用resolveWorkspaceRuntimeFromParam读取req.params(Express 解码一次),免费继承了 Phase 3 的编码选择器处理。(2)可观测性——WS upgrade 路径与 ACP 分发绕过 Express 中间件,因此 daemon 遥测/日志必须在此显式打上解析出的 workspace 印记(这也是checkRate穿过opts的同一原因);Phase 1 的请求时 workspace 哈希只覆盖 Express 路由。

Dispatcher 镜像表面与运行时绑定

反向/acpWS 镜像了庞大的 REST 表面:文件 read/list/glob/stat、workspace mcp / skills / providers / env / preflight / trust / permissions / voice / tools / agents / memory / auth、会话组、setup-github 等。由于这些方法全部读取 dispatcher 的构造字段,把 dispatcher 绑定到运行时即可免费完成作用域隔离。Phase 4不重新实现它们,只确保每个运行时的 dispatcher 用该运行时的依赖构造——该集合显式包含 per-runtime 的deviceFlowRegistryWorkspaceRememberTaskLane:如果任一项仍用主运行时单例,非主运行时的_qwen/workspace/memory/rememberauth/device_flow调用会静默打到主 bridge 上。

一致性保证:由于每个挂载的 dispatcher 都是运行时绑定的,且parseRequestedWorkspace在请求workspaceCwdboundWorkspace不一致时抛WorkspaceMismatchError,连接到/workspaces/A/acp却发送workspaceCwd: B的客户端会被拒绝。对应的回归测试见 workspace-qualified-acp.test.ts L967。同一守卫也覆盖session/newparseOptionalWorkspaceCwd,dispatch.ts L1059)。

反向 MCP 与 CDP 隔离

  • 反向工具通道:基线的clientMcpProviderFactory闭包主运行时的clientMcpSenderRegistry+primaryBridge(server.ts L1252-1257)。per-runtime 挂载从解析出的运行时clientMcpSenderRegistry+bridge构建工厂,因此/workspaces/B/acp上的 WS 连接只把客户端托管的 MCP 服务器注册进 B 的运行时。回归测试见 workspace-qualified-acp.test.ts("mcp_register 落在 B 的 registry/bridge"一类用例)。
  • 每连接的ClientMcpWsConnectioncdpEndpoint保持每连接属性,只是挂到所属运行时的 dispatcher 上。
  • CDP 隧道cdpTunnelRegistry是进程级作用域,CDP bridge 由clientInfo.name === 'qwen-cdp-bridge'的扩展/acp连接声明。Phase 4 务实地把 CDP 声明保留在旧/acp(主)上,工作区级 CDP 作为 Open Question 而非本阶段解决——单个 loopback puppeteer 客户端 + 一个/cdp端点无法干净地映射到 N 个运行时。具体实现上,非主RuntimeAcpMountensureChromeDevToolsMcpRegistered/removeChromeDevToolsMcpIfUnused为空实现(index.ts L1423-1424),/cdp分支与chrome-devtools运行时 MCP 注册仅由主挂载接线;"次级工作区不能声明 CDP 隧道"有专门回归测试(L2051)。

信任门控(Trust Gate)

  • 不受信任的已注册工作区保持可见/只读,但不得派生子进程。在/workspaces/:workspace/acp上,授予所有权的操作(session/newsession/loadsession/resume;dispatch.tsCONN_ROUTED_METHODSL239-243)必须以untrusted_workspace错误拒绝且不派生,匹配 REST 中已实现的 403untrusted_workspace语义(session-runtime.ts L39-53 与 session.ts 的 session create/load/resume 信任门与session_workspace_conflict)。
  • HTTP ACP 路由复用 Phase 3 经requireTrustedWorkspaceRuntime暴露的信任判定;WS 路径的等价检查在握手授予会话之前对解析出的运行时的trusted标志执行。
  • 启动冻结信任是 Phase 2a 基线;运行期信任翻转(吊销时排空/停止该工作区的 ACP 子进程并清空其会话索引)与信任变更阶段保持一致,本阶段不重新实现。

在 index.ts L1499-1507 的resolvePluralMount与 WS 分支(L1824 起的runtime.trusted检查)中可以看到 HTTP/WS 两侧信任门的具体落点。

能力广告与 Web Shell 选择器

  • 在 capabilities.ts 中新增 ACP 特性标志workspace_qualified_acp(声明于 L474),仅在注册运行时超过一个且 ACP 已启用时广告(L738-746 的谓词:toggles.acpHttpEnabled === true && toggles.multiWorkspaceSessionsEnabled === true),镜像multi_workspace_sessions的门控。如果 Phase 4 跨多个 PR 落地,在完整复数 ACP 回路(HTTP + WS + device-flow + owner-index 接线)完成前不要广告该标签,避免客户端针对半接线的表面构建/workspaces/:id/acpURL。
  • 同步该标签是 Phase 4 的必需工作而非可选:涉及/capabilities响应构造(routes/capabilities.ts)、SDK 能力类型(sdk-typescript/src/daemon/types.ts,其中workspaces?: DaemonWorkspaceCapability[]于 L591)、CLI serve 类型(packages/cli/src/serve/types.ts),以及 server.test.ts L3724-3744 的特性集断言(广告仅在 ACP HTTP + 多工作区会话同时启用时成立)。
  • workspaces[]已由 Phase 2a 提供:在 routes/capabilities.ts L79-84 与 daemon-status.ts L432-437 构造,每个运行时带id/cwd/primary/trusted。Web Shell 读取它并构建/workspaces/:id/acp连接 URL;选择器禁用(或只读标记)不受信任条目。
  • SDK 的DaemonClient(Phase 3 引入)已读取caps.workspaces[].cwd用于会话路由(DaemonClient.ts L612 附近提及multi_workspace_sessions广告),workspace-qualified ACP 连接辅助函数是自然的扩展方向:能力类型同步是必需的,辅助函数本身可以随后跟进。

失败路径与错误语义汇总

场景行为
workspace_mismatch未知 WS/HTTP 选择器 → 400 级拒绝;从不回退主挂载
untrusted_workspace在不受信任运行时上执行授予所有权的 ACP 操作 → 拒绝,不派生子进程
workspaceCwd参数不匹配WorkspaceMismatchErrorINVALID_PARAMS(已有接线)
子进程崩溃隔离在所属运行时内;其他运行时的 dispatcher 与连接不受影响(单 daemon 更大故障半径是已记录的已知局限)
信任吊销当信任变更阶段落地时,吊销运行时须排空/停止其 ACP 子进程并清空会话索引;Phase 4 仅保证 per-runtime ACP 挂载可排空,自身不增加信任变更
全局关停先处置每个运行时的ConnectionRegistry,再一次性处置 daemon 全局设备流注册表
限流ACP HTTP/WS 准入使用按连接/会话键控的checkRate(index.ts L627-641、L1175-1178);复数挂载共享同一限流器,键必须跨运行时无歧义,一个工作区不能耗尽或绕过另一个的预算
容量maxConnections按 per-runtimeConnectionRegistry执行,总 ACP 连接数可扩展到 N ×maxConnections(per-workspace 预算,匹配maxSessionsper-workspace 模型);新会话总数仍受 Phase 2a 在 bridge 接缝处的maxTotalSessions准入约束(ACP 会话创建必经此接缝)

生命周期方面,index.ts L2481-2507 的dispose()展示全局关停顺序:置disposed标志 → 移除所有 upgrade 监听器 → 处置主 registry 与主 remember-lane → 遍历secondaryMounts处置每个次级挂载的 remember-lane 与 registry(L2495-2498)→ 关闭所有wss.clients(1012)并关闭 WebSocketServer。工作区级排空则由beginWorkspaceDrain/cancelWorkspaceDrain/disposeWorkspace提供(L2509-2559 附近),配合workspace_draining503 响应(Retry-After: 5)实现平滑移除。

测试策略:契约先行

设计文档为每个契约规定了回归测试,已在 workspace-qualified-acp.test.ts(180 行起)中成规模落地:

  • WS upgrade 分发:路径分类单测——/acp(主)、/workspaces/:id/acp(解析)、未知选择器(拒绝)、%2F编码 cwd 选择器、复数路径仍执行共享安全检查(L1267、L1275、L2005、L1855)。
  • 跨工作区隔离/workspaces/A/acp上的连接看不到也不能驱动 B 拥有的会话;session/list与镜像读取只返回 A 的视图。
  • 跨传输所有权:经/workspaces/B/acp创建的会话可被 REST owner-routed 读取(如GET /session/:id/stats)与resolveLiveSessionOwner发现,确认 ACP 创建喂给了 owner 索引。
  • 一致性:连接 A 但发送workspaceCwd: BWorkspaceMismatchError(L967)。
  • 信任门:在不受信任运行时上session/new|load|resume→ 拒绝、不派生子进程(L1018、L1359)。
  • 设备流:每个挂载都可达 daemon 全局注册表;事件发布扇出到主与受信任次级 bridge,单个 bridge 故障不阻塞其余;关停时一次性处置注册表(L1890)。
  • 反向 MCP/workspaces/B/acp上的mcp_register只落入 B 的clientMcpSenderRegistry与 B 的 bridge。
  • 限流/workspaces/A/acp/workspaces/B/acp上的提示词/mutation 独立计量,任一不能绕过共享限流器(L551、L568)。
  • 能力workspace_qualified_acp仅在运行时 >1 时广告;workspaces[]形状不变(另见 server.test.ts L3724-3744 的谓词断言)。
  • 生命周期:dispose 后不再重建 WS 监听器(L1570)、不重建已处置挂载(L1660)、排空期间拒绝新 upgrade(L1736)、聚合快照跨主+次级挂载(L1579)、次级挂载排空/回滚/处置/重建(L1601)。

未决问题与对 Phase 3 的反馈

  1. 保持resolveRegisteredWorkspaceRuntimeByPathSelector为纯函数:WS upgrade 监听器无法使用 Express 绑定的resolveWorkspaceRuntimeFromParam。Phase 4 依赖纯解析器不耦合req/res。若 Phase 3 评审改变该接缝,需保留(registry, selector) => runtime | undefined的纯入口。
  2. 设备流所有权(已解决):注册表保持 daemon 全局(OAuth 凭据为进程级全局);Phase 4 与每个 dispatcher 共享该注册表,并把清洗后的事件扇出到受信任运行时 bridge。
  3. CDP 隧道 per-workspace 模型:一个 loopback puppeteer 客户端 + 一个/cdp端点无法干净映射到 N 个运行时。Phase 4 将 CDP 保留在主挂载;需确认该取舍可接受,或将 workspace-qualified CDP 排入后续。
  4. 语音延后:尽管 ACP dispatcher 已暴露_qwen/workspace/voice读取,确认语音在 Phase 4b 之前保持仅主挂载。
  5. archiveCoordinator作用域:目前是单一SessionArchiveCoordinator(server.ts L596)。需确认跨运行时共享它是否安全(考虑 Phase 3 的 workspace-qualified archive/organization),或改为 per-runtime。
  6. 限流键维度:决定 ACP 复数准入键是否需要显式 workspace 维度,或 per-connection/session 键已跨挂载无歧义(当前实现以workspaceRateLimitKey将 clientKey 与mount.rateLimitScope组合,见 index.ts L565-570,后者即运行时 workspaceId)。

非目标(Phase 4b / 5)

  • /workspaces/:workspace/voice/stream与 per-workspace 语音设置(4b)。
  • Daemon 管理的 channel worker 分组 / pidfile / 状态(4b)。
  • 动态工作区增删与惰性运行时创建(5)。

关键源码索引

  • 设计文档:docs/design/daemon-multi-workspace-phase4-acp.md
  • ACP 挂载核心实现:packages/cli/src/serve/acp-http/index.ts(RuntimeAcpMountL540-563;primaryMountL915-929;createSecondaryAcpMountL1343-1439;resolvePluralMountL1480-1519;复数路由注册 L1522-1540;WS upgrade 监听与分发 L1587-1839;disposeL2481-2507)
  • dispatcher 运行时绑定:packages/cli/src/serve/acp-http/dispatch.ts(构造参数 L644-656;parseRequestedWorkspaceL694-697;parseOptionalWorkspaceCwdL1059)
  • 工作区运行时模型:packages/cli/src/serve/workspace-registry.ts(WorkspaceRuntimeL32-54;WorkspaceSessionOwnerIndex相关 L48-119)
  • 选择器解析与信任门:packages/cli/src/serve/workspace-route-runtime.ts(resolveWorkspaceEntryFromParamL24-60)
  • 能力声明与广告:packages/cli/src/serve/capabilities.ts(flag 声明 L474;广告谓词 L738-746)
  • 服务装配:packages/cli/src/serve/server.ts(setupDeviceFlowRegistryresolveEventBridges扇出 L1264-1284;workspaceRegistry 创建 L1356-1380)
  • 回归测试:packages/cli/src/serve/acp-http/workspace-qualified-acp.test.ts、packages/cli/src/serve/server.test.ts(L3724-3744)
  • SDK 能力类型:packages/sdk-typescript/src/daemon/types.ts(workspaces[]L587-591)

【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询