diff --git a/docs/ai-context/architecture-map.md b/docs/ai-context/architecture-map.md index f70f3d4d..d9255b1d 100644 --- a/docs/ai-context/architecture-map.md +++ b/docs/ai-context/architecture-map.md @@ -49,11 +49,11 @@ │ │ (TypeScript, npm 插件) │ │ │ │ │ │ 网关方法: │ +│ │ xworkmate.tasks.get │ │ │ xworkmate.artifacts.* │ -│ │ xworkmate.agents.run │ │ │ │ │ │ Agent 工具: │ -│ │ openclaw_multi_session_* │ +│ │ openclaw_multi_session_artifacts│ │ └─────────────────────────────────┘ │ │ └─────────────────────────────────────────────────────────────┘ @@ -111,8 +111,8 @@ | 属性 | 值 | |------|-----| | 协议 | OpenClaw Plugin SDK (内存调用) | -| 方法 | `xworkmate.artifacts.prepare/export/list/read`, `xworkmate.agents.run` | -| 工具 | `openclaw_multi_session_artifacts`, `openclaw_multi_session_agents` | +| 方法 | `xworkmate.tasks.get`, `xworkmate.artifacts.prepare/export/collect-and-snapshot/list/read` | +| 工具 | `openclaw_multi_session_artifacts` | --- @@ -184,15 +184,14 @@ distributed_nodes: ```json { "gatewayMethods": [ + "xworkmate.tasks.get", "xworkmate.artifacts.prepare", "xworkmate.artifacts.export", + "xworkmate.artifacts.collect-and-snapshot", "xworkmate.artifacts.list", - "xworkmate.artifacts.read", - "xworkmate.agents.run" + "xworkmate.artifacts.read" ], "config": { - "bridgeUrl": "", - "bridgeToken": "", "workspaceDir": "~/.openclaw/workspace", "maxFiles": 1000, "artifactRefSigningSecret": "" diff --git a/docs/ai-context/refactor-notes.md b/docs/ai-context/refactor-notes.md index c4d439d6..70a2f7bb 100644 --- a/docs/ai-context/refactor-notes.md +++ b/docs/ai-context/refactor-notes.md @@ -8,7 +8,7 @@ 三个仓库形成了一个**清晰的 3 层架构**:App → Bridge → (Providers + Gateway + Plugins)。架构在职责拆分上是合理的,但在以下几个方面存在可改进的空间: -1. **循环依赖**: Plugin → Bridge (HTTP) + Bridge → Plugin (Gateway RPC) 形成调用环 +1. **循环依赖已收敛**: Plugin → Bridge (HTTP) 反向调用已从目标架构中移除,Plugin 只保留被 Gateway 调用的 artifact adapter 职责 2. **协议层过多**: Chain 2 经过 4 层跳转 (App → Bridge → Gateway → Plugin) 3. **容错不足**: 多处依赖未处理的状态丢失场景 4. **配置分散**: App/Bridge/Plugin 各自维护连接配置,缺乏统一管理 @@ -27,20 +27,20 @@ Plugin → (HTTP JSON-RPC) → Bridge (session.start, multiAgent) ### 问题 - 两个方向使用不同协议 (Gateway RPC vs HTTP JSON-RPC),增加调试难度 -- 循环引用导致: Bridge 故障 → Plugin 不可用 → Bridge 的 agent 调用失败 → Plugin 的 bridgeAgents 也无法工作 +- 循环引用导致: Bridge 故障 → Plugin 不可用 → Bridge 的 agent 调用失败 → Plugin 的 bridge agent 反向调用也无法工作 - 版本升级需要同步两个方向 ### 建议 **方案 A (推荐): 统一为单向调用** -将 Plugin 中的 `bridgeAgents.ts` 功能移到 Bridge 内部: +删除 Plugin 中的 `bridgeAgents.ts` 反向 HTTP 客户端;多 agent 编排归 Bridge 或 OpenClaw 原生 runtime: ``` Bridge internal/ acp/ - orchestrator.go ← 集成当前 bridgeAgents 逻辑 + orchestrator.go ← 拥有 bridge 侧编排逻辑 gateway.go ← 保留 xworkmate.artifacts.* 调用 ``` @@ -49,7 +49,7 @@ Plugin 变为纯工件管理 (只被调,不反向调用): ``` Plugin (简化后) src/exportArtifacts.ts ← 只保留 prepare/export/list/read - 删除 src/bridgeAgents.ts ← 移到 Bridge + 删除 src/bridgeAgents.ts ← 不再从插件反向调用 Bridge ``` **方案 B: 统一协议方向** @@ -61,7 +61,7 @@ Plugin ↔ Bridge 全部走 Gateway RPC (去掉 Plugin 中的 HTTP 调用): ### 影响范围 -- 方案 A: 改动 Bridge 内部 + 删除 Plugin 的 bridgeAgents.ts +- 方案 A: 删除 Plugin 的 bridgeAgents.ts;Bridge/OpenClaw 原生 runtime 拥有编排 - 方案 B: 改动 Bridge (新增网关方法) + Plugin (改用网关调用) --- @@ -180,7 +180,7 @@ func (pm *ProcessManager) Start() { ### 问题 -- `bridgeUrl` 和 `bridgeToken` 在 App/Bridge/Plugin 三处独立配置 +- 旧版 Plugin 的 `bridgeUrl` 和 `bridgeToken` 已从目标架构中移除;剩余连接配置集中在 App/Bridge/Gateway - 配置不一致导致调试困难 - 无版本化配置管理 @@ -191,7 +191,7 @@ func (pm *ProcessManager) Start() { ``` accounts.svc.plus GET /api/config/bridge → { serverUrl, authToken, providers... } - GET /api/config/plugin → { bridgeUrl, bridgeToken, workspaceDir... } + GET /api/config/plugin → { workspaceDir, artifact policy... } ``` **本地配置缓存**: @@ -244,7 +244,7 @@ if (!isCompatible(caps.minCompatibleVersion)) { | 优先级 | 问题 | 改动量 | 影响 | 建议时序 | |--------|------|--------|------|---------| -| **P0** | Plugin↔Bridge 循环依赖 | 中 (移动 bridgeAgents) | 消除循环 + 简化协议 | 本周 | +| **P0** | Plugin↔Bridge 循环依赖 | 中 (删除 bridgeAgents) | 消除循环 + 简化协议 | 已落实为目标架构 | | **P0** | Gateway 断连任务丢失 | 小 (增加持久化) | 核心可靠性 | 本周 | | **P1** | Gemini/Hermes 进程崩溃 | 小 (增加重启) | 提供商可用性 | 本周 | | **P1** | 配置管理分散 | 大 (集中配置) | 运维效率 | 本月 | @@ -265,7 +265,7 @@ if (!isCompatible(caps.minCompatibleVersion)) { ### xworkmate-bridge -- [ ] `internal/acp/orchestrator.go`: 集成 bridgeAgents 逻辑(方案A) +- [ ] `internal/acp/orchestrator.go`: 保持 Bridge 侧编排归属,不从 Plugin 反向调用 Bridge - [ ] `internal/gatewayruntime/runtime.go`: 增加连接池 + 任务状态持久化 - [ ] `internal/gatewayruntime/pool.go`: 新增连接池 - [ ] `internal/acp/artifact_cache.go`: 新增工件缓存 @@ -274,7 +274,7 @@ if (!isCompatible(caps.minCompatibleVersion)) { ### openclaw-multi-session-plugins -- [ ] `src/bridgeAgents.ts`: 删除或重构为单向网关调用(方案B) +- [x] `src/bridgeAgents.ts`: 删除反向 HTTP 调用边界 - [ ] `openclaw.plugin.json`: 简化配置(从网关继承 bridge 信息) - [ ] `src/exportArtifacts.ts`: 增加 artifactRef TTL 可配置 diff --git a/docs/architecture/multi-session-plugin-optimization-2026-06-05.md b/docs/architecture/multi-session-plugin-optimization-2026-06-05.md index f13745a4..2cfc745d 100644 --- a/docs/architecture/multi-session-plugin-optimization-2026-06-05.md +++ b/docs/architecture/multi-session-plugin-optimization-2026-06-05.md @@ -20,7 +20,7 @@ APP 侧 polling → xworkmate.tasks.get → Bridge → ??? → OpenClaw **具体原因**: -1. **openclaw-multi-session-plugins 没有注册 `xworkmate.tasks.get` gateway method**。插件注册的方法只有 `xworkmate.artifacts.*` 和 `xworkmate.agents.run`,没有 task 查询接口。 +1. **openclaw-multi-session-plugins 必须注册 `xworkmate.tasks.get` gateway method**。插件的目标接口只保留 `xworkmate.tasks.get` 和 `xworkmate.artifacts.*`,不再暴露 bridge-backed `xworkmate.agents.run`。 2. **插件没有在 OpenClaw 原生 task-registry 中创建 TaskRecord**。OpenClaw 的 `task-registry.ts` 是任务状态的唯一权威来源(`TaskRecord { taskId, runId, status, requesterSessionKey, ... }`),但插件完全绕过了它,自己管理制品作用域,没有任何原生任务记录。 @@ -37,7 +37,7 @@ APP (metadata.xworkmateTaskArtifactContract) → session.start params - `scopedGatewayParams()` 只映射 `sessionKey`, `runId`, `workspaceDir`, `artifactScope` - `expectedArtifactDirs` 不在映射中 -- `bridgeAgents.ts` 的 `runXWorkmateBridgeAgents()` 调用 `exportXWorkmateArtifacts` 时也没传这个参数 +- 旧 `bridgeAgents.ts` 反向 HTTP 路径已移除,不能再作为参数补偿路径 - 虽然 Fix 0 在 `exportXWorkmateArtifacts` 中加了回退扫描逻辑(行 263-283),但这个逻辑永远不会被触发,因为参数从未到达 ### 2.3 会话 Key 映射没有双向约定 @@ -51,7 +51,7 @@ APP (metadata.xworkmateTaskArtifactContract) → session.start params 当前插件的职责混合了: - 制品作用域管理(合理的独特价值) -- 通过 bridgeAgents 做 HTTP RPC 调用(应该委托给 Gateway 原生能力) +- 旧版通过 bridgeAgents 做 HTTP RPC 调用(已移除,避免 Plugin→Bridge 循环依赖) - 隐式的任务状态追踪(没有利用原生 task-registry) - 会话上下文传递(绕过了原生 session store) @@ -60,7 +60,7 @@ APP (metadata.xworkmateTaskArtifactContract) → session.start params | 原生能力 | 位置 | 可以替代的当前实现 | |---------|------|-----------------| | Task 注册与状态机 | `src/tasks/task-registry.ts` | APP 侧的 polling 循环、恢复逻辑 | -| Task Flow 编排 | `src/tasks/task-flow-registry.ts` | bridgeAgents 的多代理编排 | +| Task Flow 编排 | `src/tasks/task-flow-registry.ts` | Bridge/OpenClaw 原生多代理编排 | | Session 持久化 | `src/config/sessions/store.ts` | 会话 key 映射、上下文存储 | | Plugin Extensions | `SessionEntry.pluginExtensions` | 零散的上下文传递 | | Session Key 解析 | `src/sessions/session-key-utils.ts` | `safeScopeSegment()` 字符串处理 | @@ -192,7 +192,7 @@ const params = { sessionId: sessionKey, threadId: threadKey, taskPrompt: prompt, - expectedArtifactDirs: artifactContract?.expectedArtifactDirs ?? ["assets/images", "reports", "video"], + expectedArtifactDirs: artifactContract?.expectedArtifactDirs ?? ["artifacts/", "reports/", "exports/", "assets/", "assets/images/", "dist/"], // ...其他参数 }; ``` @@ -203,7 +203,7 @@ const params = { 'xworkmateTaskArtifactContract': { 'version': 1, 'sessionKey': sessionKey, - 'expectedArtifactDirs': ['assets/images', 'reports', 'video'], + 'expectedArtifactDirs': ['artifacts/', 'reports/', 'exports/', 'assets/', 'assets/images/', 'dist/'], // ...existing fields }, ``` @@ -237,50 +237,17 @@ api.registerHook("session.start", async (event: any) => { - 任意方向查询 O(1) - 利用 OpenClaw 原生的 session store 持久化 -### 4.4 简化 bridgeAgents 为纯 plugin 内部调用 +### 4.4 删除 bridgeAgents 反向 HTTP 客户端 -当前 `bridgeAgents.ts` 通过 HTTP fetch 调用外部 bridge: - -```typescript -const bridgeResult = await callBridgeRPC({ - bridgeUrl, - bridgeToken, - body: { jsonrpc: "2.0", method: "session.start", params: {...} }, -}); -``` - -这导致 bridgeAgents 成为 HTTP 客户端而非插件逻辑。建议改为: - -```typescript -// 方案 A: 通过 OpenClaw 原生 subagent.run() 调度 -const result = await api.runtime.subagent.run({ - agentId: providerId, - prompt: step.prompt, - sessionKey: prepared.artifactScope, - workspaceDir: prepared.artifactDirectory, -}); -``` - -或 - -```typescript -// 方案 B: 注册为原生 TaskFlow -const flow = await api.taskFlows.create({ - syncMode: "managed", - ownerKey: sessionKey, - goal: taskPrompt, - steps: steps.map(s => ({ - providerId: s.providerId, - prompt: s.prompt, - outputAs: s.outputAs, - })), -}); -``` +`bridgeAgents.ts`、`xworkmate.agents.run` 和 `openclaw_multi_session_agents` +不再属于插件边界。多 agent 编排归 Bridge 或 OpenClaw 原生 task/subagent +runtime,插件只负责 artifact scope、task snapshot adapter 和 session key +mapping。 **收益**: -- bridgeAgents 不再需要独立的 HTTP 客户端 -- 复用 OpenClaw 的 subagent 调度和 task flow 编排 -- 状态同步自动由 task-registry 管理 +- 消除 Plugin→Bridge→Plugin 循环依赖 +- 发布包不再需要 bridgeUrl/bridgeToken 配置 +- 状态同步继续由 task-registry 管理 ### 4.5 用 Transcript Events 替代 SSE 流管理 @@ -309,7 +276,7 @@ api.session.onTranscriptUpdate(params.sessionKey, (update) => { | **P0** | 打通 expectedArtifactDirs 全链路 | Plugin + Bridge + APP | 低 | 解决制品遗漏问题 | | **P1** | 利用 pluginExtensions 做 Key 映射 | Plugin | 低 | 消除会话 key 歧义 | | **P1** | session.start hook 中创建 TaskRecord | Plugin | 低 | 任务状态有权威来源 | -| **P2** | bridgeAgents 使用原生 subagent.run | Plugin | 高 | 消除 HTTP 客户端依赖 | +| **P2** | 删除 bridgeAgents 反向 HTTP 客户端 | Plugin | 已完成 | 消除 HTTP 客户端依赖 | | **P2** | 用 Transcript Events 替代 SSE 管理 | Plugin + Bridge | 高 | 简化流管理 | ## 六、简化后的架构全景 diff --git a/lib/app/app_controller_desktop_thread_actions.dart b/lib/app/app_controller_desktop_thread_actions.dart index 4845f3fe..0974e5f1 100644 --- a/lib/app/app_controller_desktop_thread_actions.dart +++ b/lib/app/app_controller_desktop_thread_actions.dart @@ -961,7 +961,14 @@ extension AppControllerDesktopThreadActions on AppController { 'finalDeliverableDetection': 'remote-runtime', 'requiresExportBeforeFinalResponse': true, 'rejectTextOnlyFileClaims': true, - 'expectedArtifactDirs': const ['artifacts/'], + 'expectedArtifactDirs': const [ + 'artifacts/', + 'reports/', + 'exports/', + 'assets/', + 'assets/images/', + 'dist/', + ], 'currentTaskWorkspace': executionWorkspace.isNotEmpty ? executionWorkspace : (remoteHint.isNotEmpty ? remoteHint : localWorkspace), diff --git a/test/runtime/assistant_execution_target_test.dart b/test/runtime/assistant_execution_target_test.dart index ab1e9f1a..736c0ea0 100644 --- a/test/runtime/assistant_execution_target_test.dart +++ b/test/runtime/assistant_execution_target_test.dart @@ -1312,6 +1312,17 @@ void main() { .cast(); expect(artifactContract['finalDeliverableDetection'], 'remote-runtime'); expect(artifactContract['requiresExportBeforeFinalResponse'], isTrue); + expect( + artifactContract['expectedArtifactDirs'], + const [ + 'artifacts/', + 'reports/', + 'exports/', + 'assets/', + 'assets/images/', + 'dist/', + ], + ); expect(artifactContract, isNot(contains('expectedArtifactExtensions'))); expect(request.prompt, isNot(contains('Task load classification:'))); expect( @@ -1357,6 +1368,17 @@ void main() { .cast(); expect(artifactContract['scopeKind'], 'task'); expect(artifactContract['rejectTextOnlyFileClaims'], isTrue); + expect( + artifactContract['expectedArtifactDirs'], + const [ + 'artifacts/', + 'reports/', + 'exports/', + 'assets/', + 'assets/images/', + 'dist/', + ], + ); expect( artifactContract['currentTaskWorkspace'], request.workingDirectory,