feat: pass OpenClaw artifact dir whitelist

This commit is contained in:
Haitao Pan 2026-06-05 21:25:29 +08:00
parent 5f3dc974aa
commit a6879d9d1f
5 changed files with 63 additions and 68 deletions

View File

@ -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": ""

View File

@ -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.tsBridge/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 可配置

View File

@ -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 | 高 | 简化流管理 |
## 六、简化后的架构全景

View File

@ -961,7 +961,14 @@ extension AppControllerDesktopThreadActions on AppController {
'finalDeliverableDetection': 'remote-runtime',
'requiresExportBeforeFinalResponse': true,
'rejectTextOnlyFileClaims': true,
'expectedArtifactDirs': const <String>['artifacts/'],
'expectedArtifactDirs': const <String>[
'artifacts/',
'reports/',
'exports/',
'assets/',
'assets/images/',
'dist/',
],
'currentTaskWorkspace': executionWorkspace.isNotEmpty
? executionWorkspace
: (remoteHint.isNotEmpty ? remoteHint : localWorkspace),

View File

@ -1312,6 +1312,17 @@ void main() {
.cast<String, dynamic>();
expect(artifactContract['finalDeliverableDetection'], 'remote-runtime');
expect(artifactContract['requiresExportBeforeFinalResponse'], isTrue);
expect(
artifactContract['expectedArtifactDirs'],
const <String>[
'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<String, dynamic>();
expect(artifactContract['scopeKind'], 'task');
expect(artifactContract['rejectTextOnlyFileClaims'], isTrue);
expect(
artifactContract['expectedArtifactDirs'],
const <String>[
'artifacts/',
'reports/',
'exports/',
'assets/',
'assets/images/',
'dist/',
],
);
expect(
artifactContract['currentTaskWorkspace'],
request.workingDirectory,