feat: pass OpenClaw artifact dir whitelist
This commit is contained in:
parent
5f3dc974aa
commit
a6879d9d1f
@ -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": ""
|
||||
|
||||
@ -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 可配置
|
||||
|
||||
|
||||
@ -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 | 高 | 简化流管理 |
|
||||
|
||||
## 六、简化后的架构全景
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user