434 lines
46 KiB
Markdown
434 lines
46 KiB
Markdown
# Gateway Turn 稳定性与健壮性|全链路分析与编码改进规划
|
||
|
||
> 适用目录:`xworkmate-app/docs/cases/`
|
||
>
|
||
> 相关仓库(`/Users/shenlan/workspaces/`):
|
||
> - `ai-workspace-infra/`(Caddy 入口、OpenClaw gateway 部署、playbooks)
|
||
> - `ai-workspace-lab/`(`xworkmate-app` Flutter 客户端、`xworkmate-bridge` Go 网桥)
|
||
> - `ai-workspace-service/`(accounts / console / litellm 等旁路服务)
|
||
>
|
||
> 触发现象:任务进度条 `任务运行中...` 永不结束、`停止` 无效,**实际 OpenClaw gateway 已执行完毕**;
|
||
> 伴随报错 `Bridge 响应读取中断,本轮结果未完成。错误码:ACP_HTTP_CONNECTION_CLOSED`。
|
||
|
||
> **结论速览(2026-06-26 live 验证后)**
|
||
> - **「采集AI资讯无法产出」的主根因**=OpenClaw 网关启动时**未加载 `openclaw-multi-session-plugins` 插件**(插件文件在临时路径,网关启动早于文件就位),导致 `xworkmate.*` 网关方法全部 `unknown method`。重启网关即恢复(详见 §4「2026-06-26 决定性根因」)。
|
||
> - 本文 §3/§5 的 **T1–T9** 是**健壮性加固**(入口超时、客户端 deadline/停止、bridge 持久 run 仓),用于让上述故障表现为「有界可恢复」而非「无限运行/丢结果」——它们正确且应保留,但**不是**该故障的根因修复。
|
||
> - ⚠️ 作废:早期「`xworkmate.*` 协议命名空间漂移、需 bridge 改原生 `tasks.get`」的判断**错误**,`fix/gateway-task-protocol-alignment` 分支不要合并。
|
||
|
||
---
|
||
|
||
## 1. The full chain (one gateway turn) — 四层,含 `openclaw-multi-session-plugins`
|
||
|
||
一次 gateway turn 跨**四层**:App → bridge(Go) → **openclaw-multi-session-plugins(TS 插件)** → OpenClaw gateway runtime。
|
||
插件是关键中间层:它给「多会话/多线程」补上**逻辑隔离的 artifact scope** 与**会话映射 + 任务快照**,并以 `xworkmate.*` 网关方法暴露给 bridge。
|
||
|
||
```
|
||
App.executeTask ──SSE POST /acp/rpc──▶ Bridge.handleRequest
|
||
│ │
|
||
│ ① xworkmate.session.prepare ───────▶ Gateway ──▶ [plugin] recordXWorkmateSessionMapping
|
||
│ (建 appThreadKey⇄openclawSessionKey 映射 + prepareXWorkmateArtifacts)
|
||
│ ◀── { artifactScope: tasks/<sani(sessionKey)>/<runId>, artifactDirectory, expectedArtifactDirs }
|
||
│ ② chat.send (gateway 原生) ────────▶ Gateway ──▶ 派发 agent run,返回 runId(~25ms,detached)
|
||
│ ◀── { runId, status: started }
|
||
▼
|
||
Bridge 记 sess.openClaw + DeadlineAt(budget by taskLoadClass),返回 running 句柄
|
||
│
|
||
▼ App 持久化 association,pollOpenClawTaskAssociationInternal 轮询:
|
||
③ xworkmate.tasks.get(runId/openclawSessionKey/artifactScope) ─▶ Gateway ─▶ [plugin] getXWorkmateTaskSnapshot
|
||
├─ resolveNativeTask(host task registry by runId) ──有──▶ 回 status(running/completed/failed) + 经 export 取 artifacts
|
||
└─ 无 native task ──▶ exportArtifactsForTaskLookup 兜底:扫 scope 目录 + expectedArtifactDirs(workspace 根 reports//artifacts/)
|
||
├─ 有产物 ─▶ status=unknown, evidence=artifacts_present, 带 artifacts
|
||
└─ 无产物 ─▶ code=no_native_task_record / task_not_found
|
||
④ 终态后取产物:xworkmate.artifacts.export / .list / .read(插件 exportXWorkmateArtifacts)
|
||
scopeRoot = workspaceRoot/tasks/<sani(sessionKey)>/<runId>;按 requiredArtifactExtensions(.md) 判 constraintSatisfied
|
||
```
|
||
|
||
**多会话/多线程隔离的核心(插件提供)**:
|
||
- `artifactScopeFor(sessionKey, runId)` = `tasks/<sanitize(sessionKey)>/<runId>`(冒号→下划线,如 `agent:main:draft:e2e` → `agent_main_draft_e2e`),每个 (会话,运行) 独立目录,互不串扰(`exportArtifacts.ts:126/164`)。
|
||
- `recordXWorkmateSessionMapping` 把 `appThreadKey ⇄ openclawSessionKey` 持久成会话扩展(`taskState.ts`),让跨连接/重连仍能按 appThreadKey 找回 run。
|
||
- `exportXWorkmateArtifacts` 严校 `requestedArtifactScope === artifactScopeFor(sessionKey,runId)`,不匹配抛 `artifactScope does not match sessionKey/runId`——**调用方必须传一致的 sessionKey/runId/artifactScope 三元组**(bridge 的 `taskGetParamsWithSessionScope` 负责从 session 记录补齐;外部手工探针易踩此坑)。
|
||
- **workspace 根兜底扫描**:agent 常把产物写到 workspace 根的 `reports/`、`artifacts/` 而非 task scope;插件用 `expectedArtifactDirs` 回扫这些目录纳入产物(见 `openclaw-gateway-e2e-regression/ROOT_CAUSE_ANALYSIS.md` Fix 0)。
|
||
|
||
**live 实测(2026-06-26,8787,插件已加载)**:① session.prepare 回真实 mapping ✓;② chat.send `runId=turn-…438450000` ✓(bridge 日志 `request_timing method=chat.send`);③ tasks.get 经插件返回 `code=no_native_task_record`(mapping 在、但 gateway 无该 run 的 native task record)+ scope 目录空、无 `news.md`。即**链路打通到插件层**,但本轮 agent 未注册可查 task、也未落产物(属第 4 层 agent 执行/落盘问题,见 §4「残留」与 §7)。
|
||
|
||
关键代码锚点:
|
||
|
||
| 环节 | 位置 |
|
||
|---|---|
|
||
| App 发起 SSE turn / 轮询 | `xworkmate-app/.../app_controller_desktop_thread_actions.dart:644`、`:747` |
|
||
| Bridge session.prepare / tasks.get / cancel | `xworkmate-bridge/internal/acp/rpc_handler.go:96`、`:126`、`taskGetParamsWithSessionScope:177` |
|
||
| Bridge↔Gateway WebSocket(dial 18789) | `xworkmate-bridge/internal/gatewayruntime/runtime.go:372` |
|
||
| 插件注册 `xworkmate.*` 网关方法 | `openclaw-multi-session-plugins/index.ts:126/162/176/206/221`(session.prepare/tasks.get/artifacts.export/.list/.read)|
|
||
| 插件 artifact scope / 会话映射 / 任务快照 | `src/exportArtifacts.ts`、`src/taskState.ts:208 getXWorkmateTaskSnapshot` |
|
||
|
||
---
|
||
|
||
## 2. 端到端拓扑(实测自代码 + live 8787 验证)
|
||
|
||
```
|
||
┌─ ai-workspace-lab/xworkmate-app (Flutter) executeTask → SSE POST Accept: text/event-stream
|
||
▼
|
||
┌─ ai-workspace-infra Caddy 入口 xworkmate-bridge.svc.plus (本机直连 127.0.0.1:8787 不经 Caddy)
|
||
│ /acp* : flush_interval -1 ✓ read/write_timeout 70m(对齐 bridge 60min) keepalive 5m ← T1/T2 已修
|
||
▼
|
||
┌─ ai-workspace-lab/xworkmate-bridge (Go) live commit 2333c3e
|
||
│ /acp/rpc handleRequest → session.prepare / chat.send / xworkmate.tasks.get / tasks.cancel
|
||
│ gatewayruntime: WebSocket → 127.0.0.1:18789;per-session 持久 run 仓(T7/T8/T9)
|
||
▼ (WS, gateway 原生 chat.send + 插件注册的 xworkmate.*)
|
||
┌─ ai-workspace-lab/openclaw-multi-session-plugins (TS 插件, enabled) ← 多会话/多线程 artifact 隔离层
|
||
│ index.ts registerGatewayMethod: xworkmate.session.prepare / .tasks.get / .artifacts.export/.list/.read/.collect-and-snapshot
|
||
│ 会话映射(taskState) + artifactScope=tasks/<sani(sessionKey)>/<runId>(exportArtifacts) + workspace 根兜底扫描
|
||
▼ (插件运行在网关进程内)
|
||
┌─ OpenClaw gateway runtime npm-global openclaw 2026.6.1 @ /opt/homebrew/lib/node_modules/openclaw
|
||
│ launchd ai.openclaw.gateway, WS 18789;host task registry(detached task) + agent 执行(deepseek-v4-flash)
|
||
▼
|
||
workspace ~/.openclaw/workspace/tasks/<sani(sessionKey)>/<runId>/ → 产物(.md 等)
|
||
```
|
||
|
||
### 四层完整 live 评估(2026-06-27,8787 commit `188ca4b`)— **端到端 PASS** ✅
|
||
|
||
一次 gateway turn(`sessionId=draft:eval-…`,prompt「总结今天AI新闻并保存到 summary.md」)逐层实测:
|
||
|
||
| 层 | 跳 | 验证方式 | 实测结果 |
|
||
|---|---|---|---|
|
||
| L1 App→Bridge | `session.start`(target=gateway) | `/acp/rpc` | ✅ 回 running 句柄:`runId`、`openclawSessionKey=agent:main:draft:eval-…`、`artifactScope=tasks/agent_main_draft_eval-…/turn-…`、budget=30min(long_task) |
|
||
| L2 Bridge→Plugin | `xworkmate.session.prepare` | bridge log + payload | ✅ 插件 `recordXWorkmateSessionMapping` + `prepareXWorkmateArtifacts`;网关 `res ✓ xworkmate.session.prepare 569ms` |
|
||
| L2→3 Bridge→Gateway | `chat.send`(原生) | bridge/gateway log | ✅ 派发 agent,`runId=turn-1782533120263890000` |
|
||
| L3 Plugin tasks 快照 | `xworkmate.tasks.get` | `/acp/rpc` | ✅ `getXWorkmateTaskSnapshot` → **`status=completed, success=True, constraintSatisfied=True`**、`hasMapping=True`、无 degraded |
|
||
| L3↔4 插件↔网关 | `xworkmate.*` 全家 | 网关 `[ws] ⇄ res` | ✅ `session.prepare ✓` / `artifacts.export ✓ 70ms`(×2) / `tasks.get ✓ 63ms` |
|
||
| L4 Gateway | 6 plugins + agent 执行 | 网关启动日志 | ✅ `listening (6 plugins: … openclaw-multi-session-plugins)`;agent(deepseek-v4-flash) 产出 |
|
||
| 产物 | 落盘 + 取回 | tasks.get artifacts + fs | ✅ `summary.md` `text/markdown` **438B** 落在 `~/.openclaw/workspace/tasks/agent_main_draft_eval-…/turn-…/`,并经 `xworkmate.artifacts.export` 回到 tasks.get 的 `artifacts[]` |
|
||
|
||
**结论**:S0 把插件从稳定路径装好后,一次 gateway turn **全链路打通** —— task 到 `completed`、md 正确落在 task scope、`constraintSatisfied=True`、产物可经 tasks.get 取回;T12 三项指标全 `0`(本轮无需任何 resilience 兜底)。此前 `no_native_task_record` / 「暂无文件」均为**插件未加载**(§4 符号链接根因)的派生症状,已消除。
|
||
|
||
旁路(`ai-workspace-service`):`accounts.svc.plus`(登录/Token)、`console.svc.plus`(openclaw assistant route)、`litellm`/`AI-Relay-Kit`/`codex-relay`(模型出口)、`qmd`(记忆)——不在主链,但 Token/出口异常以「连接中断」形态出现,用 `runId` 区分。
|
||
|
||
出处:Caddy `…/xworkmate_bridge/templates/xworkmate-bridge-site.caddy.j2`;网关部署 `deploy_gateway_openclaw.yml`;插件 `~/.openclaw/extensions/openclaw-multi-session-plugins`(注册源当前指向临时 `/private/tmp/…`,见 §4 加固项)。
|
||
|
||
---
|
||
|
||
## 3. 跨层失效点清单(按链路从上到下)
|
||
|
||
| # | 层 | 失效描述 | 证据 |
|
||
|---|---|---|---|
|
||
| ① | App | running 轮询分支**无 deadline / 无 maxAttempts**;只要 `tasks.get` 一直回 `running` 就永远轮询 | `app_controller_desktop_thread_actions.dart:788-794`(对比 `:831` 的 artifact-sync 分支**有**封顶 `openClawArtifactSyncLimitReachedInternal`) |
|
||
| ② | App | 进度条 phase 仅由 `pending` 驱动,而 `pending` 被该 loop 独占持有,loop 不退出 = 永远 `任务运行中...` | `assistant_task_progress_bar.dart:190`;`pending` 源 = `aiGatewayPendingSessionKeysInternal` |
|
||
| ③ | App | `停止`(abortRun → cancelAssistantTaskForSessionInternal)只向 gateway 发 `tasks.cancel`,**从不本地清 `pending`**;gateway 不可达 / run 已丢时为空操作 → 永远停不下 | `app_controller_desktop_thread_actions.dart:1793-1809` |
|
||
| ④ | **入口 / Bridge 超时错配** | Caddy `/acp*` `read/write_timeout 30m`,但 bridge `openClawAgentWaitMaxTimeout = 60min`。**>30min 的 complex_chain_task,SSE 在入口被掐断**,而 gateway 仍在跑 → `ACP_HTTP_CONNECTION_CLOSED` | caddy.j2 `read_timeout 30m` vs `orchestrator.go:32` |
|
||
| ⑤ | 入口 | 仅 `/acp*` 有 `flush_interval -1` 与长超时;`/api*`、`/` 用 Caddy 默认(短超时、无即时 flush),任何走这两条的流式 / 轮询都更脆 | caddy.j2 |
|
||
| ⑥ | **Bridge↔Gateway WS 关联绑定连接** | WS 一抖,`onConnLost` 把**所有在途请求**立即判 `SOCKET_CLOSED`——包括 OpenClaw 仍在执行的那个 run;重连后无任何 pending 复关联 | `gatewayruntime/runtime.go:802-823`、`:935-938`(`takePendingLocked` 清空) |
|
||
| ⑦ | **Bridge 无持久 run 仓** | 同步 SSE 路径不落 `xworkmate.jobs.*`,结果只活在内存 `responseCh`;连接一断即丢(已存在 jobs.submit/get/list 未被 gateway submit 复用) | `rpc_handler.go:73`(jobs.*)vs http_handler 同步路径 |
|
||
| ⑧ | Gateway 重连丢 run 态 | 重连到新 WS 后 `tasks.get` 经 `ensureProductionGatewayConnected` 落到新连接,查不到 terminal → 回 stale `running` 或 `not_found` | `rpc_handler.go:143-175` |
|
||
| ⑨ | Bridge | `gatewayRPCError` 把可重试错误统一映射为 `OPENCLAW_GATEWAY_SOCKET_CLOSED`,但缺少"run 仍在后台、稍后可查"的语义,客户端只能当硬失败 | `orchestrator.go:1678-1702` |
|
||
| ⑩ | **运行态 ≠ 源码(主根因,见 §4)** | 报 `unknown method: xworkmate.*` 时,根因通常是**网关未加载 `openclaw-multi-session-plugins` 插件**(这些方法由插件注册);次因是本机 `launchd` 的 bridge 二进制为旧构建 | 网关启动日志 `N plugins` 是否含 `openclaw-multi-session-plugins`;`openclaw plugins inspect`;`curl /api/ping` 的 `commit` |
|
||
|
||
---
|
||
|
||
## 4. 根因链(Root cause)
|
||
|
||
> "实际 gateway 已执行完毕,但任务永远停不下" =
|
||
>
|
||
> **服务端把已完成的结果弄丢(⑥ + ⑦ + ⑧)** :bridge↔gateway 的请求关联随 WS 连接销毁、且无持久副本,run 完成事件投递到已被放弃的 channel;重连后无法复关联。
|
||
>
|
||
> **+ 客户端把"无限 running"固化(① + ② + ③)** :轮询无截止、进度条只看 pending、停止不本地生效。
|
||
>
|
||
> **+ 触发器(④ / ⑤)** :长任务在入口 30min 处必断,把链路推向上述失效(尤其 complex_chain_task budget=60min)。
|
||
|
||
最小可复现路径:
|
||
1. 提交一个 budget > 30min 或网络抖动概率高的 gateway 任务;
|
||
2. SSE 在入口(30min)或 WS(瞬断)处断开 → App 收 `ACP_HTTP_CONNECTION_CLOSED`;
|
||
3. OpenClaw 后台继续跑完,但 bridge 已丢失该 run 的 pending / 无持久记录;
|
||
4. App 落入 running 轮询(或失败后仍 pending),`tasks.get` 拿不到 terminal;
|
||
5. 进度条永远 `任务运行中...`,`停止` 不本地生效 → 卡死。
|
||
|
||
### 2026-06-26 本机联合调试结论
|
||
|
||
本次现场环境:
|
||
|
||
- App:`Version 1.1.4`,应用构建 commit `fb7e0ac`。
|
||
- 本地 Bridge:`http://127.0.0.1:8787`,launchd 服务 `plus.svc.xworkspace.bridge`。
|
||
- OpenClaw gateway 控制台:`http://127.0.0.1:18789/channels`。
|
||
|
||
实际复现:
|
||
|
||
```bash
|
||
curl -sS -X POST http://127.0.0.1:8787/acp/rpc \
|
||
-H 'Content-Type: application/json' \
|
||
-H 'Authorization: Bearer $BRIDGE_AUTH_TOKEN' \
|
||
--data '{"jsonrpc":"2.0","id":"probe","method":"xworkmate.session.prepare","params":{"openclawSessionKey":"probe-session","runId":"probe-run","workspaceDir":"/tmp/xworkmate-probe","gatewayProviderId":"openclaw"}}'
|
||
```
|
||
|
||
修复前返回:
|
||
|
||
```json
|
||
{"error":{"code":-32601,"message":"unknown method: xworkmate.session.prepare"},"ok":false}
|
||
```
|
||
|
||
结论:这不是 OpenClaw gateway 控制台 `18789` 的页面问题,也不是 App 展示误判;`/acp/rpc` 上 `acp.capabilities` 正常而 `xworkmate.session.prepare` 不认识,说明本地 Bridge 运行态没有加载到包含 `handleSessionPrepare` 的新二进制。源码中 `rpc_handler.go` 和 `orchestrator.go` 已有 fallback,但 `/usr/local/bin/xworkmate-go-core` 仍是旧/无元信息构建。
|
||
|
||
现场处理:
|
||
|
||
1. 在 `xworkmate-bridge` 当前源码重建 `xworkmate-go-core`。
|
||
2. 备份并替换 `/usr/local/bin/xworkmate-go-core`。
|
||
3. macOS 上移除 `com.apple.provenance` / quarantine 并 `codesign --force --sign -`,否则 launchd 会以 `OS_REASON_CODESIGNING` 拒绝启动。
|
||
4. 重载 `plus.svc.xworkspace.bridge`。
|
||
|
||
修复后探针:
|
||
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"payload": {
|
||
"fallback": true,
|
||
"compatibilityMode": "local-session-prepare",
|
||
"artifactScope": "tasks/probe-session/probe-run",
|
||
"artifactDirectory": "/tmp/xworkmate-probe/tasks/probe-session/probe-run"
|
||
}
|
||
}
|
||
```
|
||
|
||
回归要求:每次本地替换 Bridge 后,先用 `/api/ping` 确认 `commit`,再用上面的 `xworkmate.session.prepare` 探针确认返回 `ok:true`;不能只看 App 设置页的 `Status: ok`。
|
||
|
||
### 2026-06-26 决定性根因(已 live 验证):OpenClaw gateway 未加载 `openclaw-multi-session-plugins` 插件
|
||
|
||
> 四层调用链(与 `openclaw-gateway-e2e-regression/ROOT_CAUSE_ANALYSIS.md` 一致):
|
||
> `App → xworkmate-bridge(Go) → openclaw-multi-session-plugins(TS 插件) → OpenClaw gateway runtime(127.0.0.1:18789)`
|
||
|
||
**`xworkmate.*` 系列网关方法不是「虚构/漂移」的——它们由 `openclaw-multi-session-plugins` 插件在运行时注册**:
|
||
`index.ts` 里 `api.registerGatewayMethod("xworkmate.session.prepare" | "xworkmate.tasks.get" | "xworkmate.artifacts.export" | ".list" | ".read" | ".collect-and-snapshot")`。所以 bridge 发 `xworkmate.tasks.get` 是**正确契约**,前提是该插件被网关加载。
|
||
|
||
现场实际故障:**运行中的网关没有加载这个插件**,于是所有 `xworkmate.*` 都回 `unknown method`。证据链:
|
||
|
||
- 网关启动日志:`2026-06-23` 前每次都是 `listening (6 plugins: … openclaw-multi-session-plugins)`;**`2026-06-26 09:21:14` 那次只有 `5 plugins`,缺了 multi-session**。
|
||
- 插件注册的源路径是**临时目录** `/private/tmp/openclaw-multi-session-plugins/dist/index.js`;该文件直到 `18:40` 才被填充——**晚于网关 09:21 启动约 9 小时**。启动时路径不存在 → 插件未加载。
|
||
- `openclaw plugins inspect` 警告:`loaded without install/load-path provenance; treat as untracked local code`(无 install 记录的本地代码)。
|
||
|
||
**修复(已 live 验证通过)**:`launchctl kickstart -k gui/$UID/ai.openclaw.gateway` 重启网关(此时插件文件已就位)→ 日志变 `listening (6 plugins: … openclaw-multi-session-plugins)`;`xworkmate.*` 方法的报错从 `unknown method` 变为**插件内部参数校验**(`xworkmate.session.prepare → appThreadKey required`、`xworkmate.tasks.get → artifactScope does not match sessionKey/runId`)——证明方法已注册、插件正在处理请求。
|
||
|
||
> ⚠️ **更正**:此前一版本文档曾判为「bridge↔gateway `xworkmate.*` 协议命名空间漂移、需在 bridge 侧改原生 `tasks.get`/`artifacts.*`」——**该结论错误**,源于当时未发现 `openclaw-multi-session-plugins` 插件提供这些方法。对应的 `fix/gateway-task-protocol-alignment` 分支(native 重命名)**作废、不得合并**;bridge 原有 `xworkmate.*` 协议是对的。
|
||
|
||
#### 残留与加固项
|
||
|
||
- **插件安装路径必须稳定**:当前注册在 `/private/tmp/…`(重启 / tmp 清理即丢,正是本次故障诱因)。应用 `openclaw plugins install <stable-path>` 从 `~/.openclaw/extensions/openclaw-multi-session-plugins/` 或仓库路径重装,落正式 install 记录,避免再次「启动早于插件就位」。
|
||
- **会话面**:现场 console 的 `…:dashboard:bcde1b0f…` 与提交的 `…:draft:…` 不同面;task 建在 `draft`(requesterSessionKey),dashboard 仅是 console 自带会话视图,非断点。
|
||
- **手工探针注意**:直接构造 `tasks.get` 时 `sessionId` 不要预带 `agent:main:` 前缀——bridge 会再加一层导致 `agent:main:agent:main:…` 双前缀,触发插件的 `artifactScope does not match sessionKey/runId`。app 正常路径传 `draft:<id>`,bridge 补一层。
|
||
|
||
---
|
||
|
||
## 5. 编码改进规划(Stability / Robustness TODO)
|
||
|
||
> 排序原则:先**当天可上、零协议变更**的止血项(L0 配置 + L2 客户端),再**治本**的服务端持久化(L1),最后横切可观测性(L3)。
|
||
> 每项标注:所属仓库 · 改动面 · 验收要点。
|
||
|
||
### L0 入口配置(ai-workspace-infra · 改配置即可 · 当天可上)
|
||
|
||
- [x] **T1 对齐入口与 bridge 的超时上限** — ✅ 已实现并加断言(playbooks)
|
||
`xworkmate-bridge-site.caddy.j2` 全部 route 的 `read_timeout`/`write_timeout` 由**单一变量** `xworkmate_bridge_acp_stream_timeout`(`defaults/main.yml:36` = `70m` ≥ bridge `openClawAgentWaitMaxTimeout` 60min + 余量)渲染,消除入口/bridge 漂移;`tasks/validate.yml:39-41` 断言渲染结果含该超时。
|
||
验收:budget=60min 的 complex_chain_task 全程 SSE 不被入口掐断。✓
|
||
|
||
- [x] **T2 收敛 / 补齐非 /acp 路由的流式配置** — ✅ 已实现(playbooks)
|
||
`/api*`、`/artifacts/*`、`/acp*`、`/` 四条 handle 现统一带 `flush_interval -1` + 同一 `xworkmate_bridge_acp_stream_timeout`(`caddy.j2:16-60`),不再有任何路由回落 Caddy 默认短超时。
|
||
验收:轮询 / 流式不再依赖 Caddy 默认短超时。✓
|
||
|
||
### L2 App 客户端止血(ai-workspace-lab/xworkmate-app · 纯客户端 · 零协议变更)
|
||
|
||
- [x] **T3 running 轮询加硬截止** — ✅ 已实现(xworkmate-app)
|
||
running-handle 分支经 `openClawRunningPollDeadlineReachedInternal`(`thread_actions.dart:799`)按 bridge 下发的 deadline + grace 封顶;到点落 `interrupted`(可恢复态,`isRecoverableAssistantTaskStateInternal` 已支持)并退出 loop。
|
||
验收:gateway 始终回 `running` 时,客户端在 deadline 后必终止,不再无限轮询。✓
|
||
|
||
- [x] **T4 `停止` 本地权威化** — ✅ 已实现(xworkmate-app)
|
||
`abortRun`(`thread_actions.dart:1819`)**先**乐观清 `aiGatewayPendingSessionKeysInternal`、置终止态、退出 loop,**再** best-effort 发 `tasks.cancel`(注释:「其失败/挂起都不得阻塞或回滚本地终止」)。UI 终止不依赖 gateway 往返。
|
||
验收:gateway 不可达时点 `停止` 仍能立刻停下。✓
|
||
|
||
- [x] **T5 传输中断降级为"后台续跑·重连中"** — `pollOpenClawTaskAssociationInternal` catch(`thread_actions.dart`)
|
||
轮询期间 App↔bridge 传输瞬断(`ACP_HTTP_CONNECTION_CLOSED`)时,不硬失败丢结果,而是**有界重试续轮询**:连续瞬断 `< kOpenClawPollTransientRetryLimit(=5)` 则保持 running、2s 后重试下一次 `getTask`;每次成功重置计数;超限才落终态。bridge 侧 T7/T9 负责网关侧抖动,这里只兜 App↔bridge 这一跳。
|
||
**取舍**:未引入新的「重连中」UI 相位(避免改进度条布局);任务保持「运行中」即降级态。未走 `resumeOpenClawTaskAssociationsInternal` 全量恢复(那是 thread 重载路径),而是就地有界重试,风险更低、不会无限重连。
|
||
验收:轮询瞬断 ≤5 次能自动续轮询;持续不可达则在有界次数后落终态。
|
||
|
||
- [x] **T6 失败路径与 pending 清理一致性** — ✅ 已实现(xworkmate-app)
|
||
失败 / 中断 / 取消三条终态路径均确定性清 `aiGatewayPendingSessionKeysInternal`(`thread_actions.dart:716/903/932/1657-1658`、`runtime_helpers.dart:1032/1112`),消除"错误已渲染但仍 running"竞态。
|
||
验收:失败 / 中断 / 取消三类终态后,`pending` 必为 false。✓
|
||
|
||
### L1 Bridge 持久化(ai-workspace-lab/xworkmate-bridge · 根因修复 · 有协议/状态面改动)
|
||
|
||
> ✅ T7/T8/T9 已实现(本地验证 commit `2333c3e`,需确保运行态 `/api/ping.commit` 与发布 commit 对齐)。
|
||
> 实现取舍:**复用已存在的 per-session 持久 store**(`s.sessions[sessionID]` 内的 `task`/`openClaw`/`lastResult`,生命周期独立于 bridge↔gateway WebSocket),把 `tasks.get` 从「强依赖 gateway 应答」改造为「优先用持久 run 仓兜底」。
|
||
> 新增 `internal/acp/openclaw_run_registry.go`(+ `_test.go`),改动 `rpc_handler.go: handleTaskGet` 与 `orchestrator.go: startOpenClawGatewayTask`。
|
||
|
||
- [x] **T7 run 关联与 WS 连接解耦** — `openClawTaskGetGatewayUnconfirmedFallback`(`openclaw_run_registry.go`)
|
||
gateway 无法确认(unavailable / socket closed / not_found)但 run 仍在预算内时,按 `runId` 从持久 session store 合成 `running` 句柄让客户端继续轮询,跨越瞬时抖动。
|
||
**取舍**:未直接改 `gatewayruntime.onConnLost`(`runtime.go:802`)的 pending 判死逻辑——在途请求被判死后会以 gateway error 冒泡到 `handleTaskGet`,新兜底按 runId 续轮询到 deadline 已等价覆盖,且风险远低于重写连接层 pending 关联。chat.send 初次提交若 WS 中断则尚无 runId、无可复关联,客户端重发即可。
|
||
验收:WS 瞬断 + 重连后,已完成的 run 仍能被 `tasks.get` 查到 terminal + artifacts(见 `TestGatewayUnconfirmedFallbackWithinBudgetKeepsPolling`)。
|
||
|
||
- [x] **T8 终态结果落持久 run 仓** — `cacheOpenClawTaskGetResultIfTerminal` / `cachedTerminalForRunLocked`
|
||
gateway 确认终态后,把**最终客户端形态**(已 decorate 下载 URL + strip 内联内容)缓存进 `sess.lastResult`,后续轮询直接回放,gateway 之后查不到也不丢。带 `runId` 校验 + 新 turn 复用 session 时重置 `ProgressTerminal`,防旧 run 终态错配新 run。
|
||
**取舍**:未新建独立 `xworkmate.jobs.*` 落库;现阶段复用 per-session 内存 store(已满足「跨 WS 抖动不丢结果」)。bridge **进程重启**后仍会丢——若需跨重启持久化,再起 T8b 接 `jobs.*` / 磁盘。
|
||
验收:连接抖动后结果与 artifacts 仍可检索(见 `TestTerminalResultCachedAndServedAfterGatewayLoss`、`TestCachedTerminalNotServedForDifferentRunId`)。
|
||
|
||
- [x] **T9 服务端 DeadlineAt 兜底终态** — `markOpenClawRunDeadlineInterruptedLocked`
|
||
run 过期(`sess.task.DeadlineAt`)且 gateway 无法确认时,bridge 主动回 terminal `interrupted`(`OPENCLAW_RUN_DEADLINE_EXCEEDED`),与 T3 客户端 deadline 形成双保险。
|
||
**取舍**:仅在 gateway **无法确认**时按 deadline 强制终态;gateway 明确回 `running` 时**不**强杀,避免误伤合法长任务(那一侧由客户端 T3 兜底)。
|
||
验收:gateway 失联超过 budget 后,`tasks.get` 返回确定 terminal(见 `TestGatewayUnconfirmedFallbackPastDeadlineInterrupts`)。
|
||
|
||
- [x] **T10 错误语义细化** — `gatewayRPCError`(`orchestrator.go`)
|
||
对 `OPENCLAW_GATEWAY_SOCKET_CLOSED` 在 Data 中带 `retryable=true`、`poll=true`,表达「连接断但 run 可能仍在后台、可续轮询」语义,供客户端 T5 据此续轮询而非硬失败。
|
||
验收:socket-closed 错误带 retryable/poll 标记。
|
||
|
||
- [x] **T13 运行态同步校验(bridge 二进制 + 网关插件)**
|
||
「源码已修但跑的不是它」是反复踩的坑,需双侧确认:
|
||
- **Bridge**:`/api/ping.commit` 非空且等于目标 commit(本机 launchd 可能仍跑旧 `/usr/local/bin/xworkmate-go-core`)。
|
||
- **网关插件**:网关启动日志含 `… openclaw-multi-session-plugins`,且 `/acp/rpc xworkmate.session.prepare` **不**返回 `unknown method`(插件已加载时返回真实 mapping;未加载时 bridge 才走 `compatibilityMode=local-session-prepare` 降级)。
|
||
验收:App 不再显示 `unknown method: xworkmate.*`;网关 `N plugins` 列表含 multi-session。
|
||
|
||
### L3 可观测性(横切 · infra/service/lab)
|
||
|
||
- [x] **T11 端到端贯穿 runId** — `openclaw_run_registry.go`
|
||
在 `tasks_get_unconfirmed_fallback`、`run_deadline_interrupt` 两处加 `runId`/`openclawSessionKey` 标记的 warn 日志,可与 App→bridge→插件→gateway 按 `runId` 串联(既有 `component=acp_sse` 已带 requestId)。
|
||
验收:socket 抖动 / deadline 终态在 bridge 日志可按 runId 定位。
|
||
|
||
- [x] **T12 关键指标** — `internal/acp/metrics.go`,经 `/api/ping.metrics` 暴露
|
||
进程内计数:`gatewaySocketClosed`、`taskGetUnconfirmedFallback`、`runDeadlineInterrupt`。live 验证 `/api/ping` 已返回 `metrics` 字段(commit `0a50621`)。
|
||
验收:三类不稳定事件可监控,无需靠用户截图。(告警接入留运维侧)
|
||
|
||
---
|
||
|
||
## 6. 落地顺序建议
|
||
|
||
0. ✅ **主根因修复**(live 验证):让 OpenClaw 网关稳定加载 `openclaw-multi-session-plugins`——`openclaw plugins install` 从稳定路径重装 + 重启网关,确认启动日志 `6 plugins … openclaw-multi-session-plugins`、`xworkmate.*` 不再 `unknown method`。这是「采集AI资讯能产出」的前提(详见 §4)。
|
||
1. ✅ **当天止血**(已合并 main):T1 + T2(入口配置)+ T3 + T4 + T6(客户端)+ session.prepare 数字 code 降级,消除"30min 必断 / 路由漏配 / 无限 running / 停不掉"。
|
||
说明:session.prepare 数字 code 降级仍有价值——当插件**未**加载时,让 bridge 优雅 fallback 而非硬失败;插件加载后走真实 plugin 路径。
|
||
2. ✅ **健壮性加固**(commit `2333c3e`):T7 + T8 + T9(bridge 持久 run 仓与 WS 解耦),把网关短暂不可达 / 抖动收敛为「有界续轮询 → deadline 终态」,而非无限运行/丢结果。
|
||
3. ✅ **断连语义 + 可观测**(commit `0a50621`):T10(socket-closed 带 retryable/poll)+ T5(App 轮询瞬断有界续轮询)+ T11(runId 日志)+ T12(`/api/ping.metrics` 计数)。
|
||
4. **剩余**:
|
||
- **S1(已回退,待重做)**:缺省 `expectedArtifactDirs` 会让「期望产物但实际无产物」的 run 卡在「等待导出」(破坏 E2E 测试)。根因是 `openClawTaskGetRequiresArtifactExport` 把「有 expectedArtifactDirs」等同「必须导出/阻塞」。**正确做法**:解耦「扫描提示」与「阻塞式导出要求」——让缺省目录只驱动插件的兜底扫描、不触发 bridge 的等待导出。需单独一轮、对全 E2E 套件验证。
|
||
- **T8b(跨进程重启持久化)**:把 per-session run 仓落磁盘 / 接 `xworkmate.jobs.*`,让 bridge **进程重启**后仍能回放终态。当前内存仓已覆盖「WS 抖动 / 网关瞬断」(同进程内),跨重启是较小边际收益、较大复杂度(序列化 / 启动加载 / 过期清理 / 并发),建议作为独立一轮带测试做。
|
||
|
||
> 回归对照:本目录 `00-review-env-and-matrix.md` 第 2 节"通用验收标准"中"长任务执行期间状态流 / 取消 / 重试稳定""同一任务重复执行 3 次不卡死",即本规划的回归出口。
|
||
> 产物交付链(artifact scope / workspace 路径)的独立缺陷与修复,见 `openclaw-gateway-e2e-regression/ROOT_CAUSE_ANALYSIS.md`。
|
||
|
||
---
|
||
|
||
## 7. 全链路稳定性改进(基于 2026-06-26 四层 live 验证)
|
||
|
||
> 优先级按「直接决定一次任务能否产出」排序。S1/S2 是本轮 live 新发现。
|
||
|
||
- **S0 插件稳定安装(最高)— ✅ 已落实并验证(2026-06-27)**
|
||
网关方法 `xworkmate.*` 全部依赖 `openclaw-multi-session-plugins`。**精确根因**:`~/.openclaw/extensions/openclaw-multi-session-plugins` 是个**符号链接 → `/tmp/openclaw-multi-session-plugins`**(临时盘,重启/清 tmp 即失效);`openclaw plugins inspect` 标 `Source: /private/tmp/…` 且警告「loaded without install/load-path provenance(untracked local code)」。网关 09:21 启动早于该 tmp 路径就位 → 当次只加载 5 plugins、`xworkmate.*` 全 `unknown method`。
|
||
**已执行**:① 删符号链接、把内容复制成真实目录 `~/.openclaw/extensions/openclaw-multi-session-plugins`;② `openclaw plugins install <该路径> --force` 落正式 `path` install 记录;③ `launchctl kickstart -k gui/$UID/ai.openclaw.gateway` 重启。
|
||
**验证**:启动日志 `http server listening (6 plugins: … openclaw-multi-session-plugins)`;`inspect` 的 `Source` 变为 `~/.openclaw/extensions/…/dist/index.js`、provenance 警告消失;`xworkmate.session.prepare` 经 bridge 返回**真实插件响应**(`fallback=null`、带 `mapping`、`artifactScope=tasks/draft_s0verify/s0-run`),不再走 bridge 的 `local-session-prepare` 降级。
|
||
收尾:`~/.openclaw/extensions/` 现为真实目录(非 /tmp 软链),重启/重启后不再丢插件;建议把它纳入部署(`deploy_gateway_openclaw`)从仓库 `openclaw-multi-session-plugins` 安装,避免再被软链到临时盘。
|
||
|
||
- **S1 `expectedArtifactDirs` 为空导致根目录兜底失效 — ⚠️ 一版本已合并后回退(commit `0280893` → 回退于 `81f65e3`)**
|
||
根因:live 的 session mapping 为 `expectedArtifactDirs:[]`,而插件对「agent 把产物写到 workspace 根 `reports/`/`artifacts/` 而非 task scope」的兜底扫描**依赖 `expectedArtifactDirs`**;为空 → 兜底形同虚设 → 即便 agent 产出也收不到,表现「暂无文件」。
|
||
**回退原因**:当时的实现给所有「推断出 requiredExts」的任务补缺省目录并置 `requiresExport=true`,导致 gateway run 成功但**实际无产物**时卡在「等待 artifact 导出」(`TestHTTPHandlerGatewayOpenClawHandlesFiveConcurrentE2ECases` 等转红)。阻塞来自 `openClawTaskGetRequiresArtifactExport` 把「有 expectedArtifactDirs」等同「必须导出」。
|
||
**正确做法(待重做)**:解耦「扫描提示」与「阻塞式导出」——缺省目录只驱动插件兜底扫描、不触发 bridge 等待导出;或仅在客户端**显式**声明 `requiredArtifactExtensions` 时启用。需单独一轮、对全 E2E 套件验证后再上。
|
||
|
||
- **S2 `no_native_task_record` 状态歧义** — `xworkmate.tasks.get` 的真值来自「gateway host task registry 有该 run 的 detached task」**或**「artifact 已存在」。live 中 chat.send 成功但 gateway 无 native task record(agent 可能以 inline chat 执行、未注册可查 task),且无产物 → 插件回 `no_native_task_record`,bridge 只能靠 T7 兜底续轮询到 deadline,**无法区分「还在跑」与「跑完没产物」**。
|
||
改进:①确认 gateway 侧 chat.send 是否应产出 detached task(agent 配置/ `tasks.*` 注册);②插件/bridge 在 `no_native_task_record` 且超过最小执行时长时,下发更明确的 `running(no-record)` vs `completed(no-artifact)` 语义,配合 §5 T9 deadline 收口。
|
||
验收:agent 正常执行时 `tasks.get` 能返回真实 running→completed;异常时给确定终态而非无限 degraded。
|
||
|
||
- **S3 三元组一致性(已知约束)** — 插件严校 `sessionKey/runId/artifactScope` 三者一致(`exportArtifacts.ts:126`),且 bridge 的 openclawSessionKey 由 `agent:main:` + appThreadKey 组成。**调用方/探针不要预带 `agent:main:` 前缀**(否则双前缀 → `artifactScope does not match`)。bridge `taskGetParamsWithSessionScope` 已负责补齐;保持其为唯一可信来源,App/探针只传 `sessionId=draft:<id>` + `runId`。
|
||
|
||
- **S4 运行态可观测** — 沿用 §5 T11/T12:bridge `/api/ping.commit`、网关 `N plugins` 列表、`openclaw plugins inspect` 三处纳入健康检查;`runId` 贯穿 App→bridge→插件→gateway 日志,便于定位断点落在四层中的哪一层。
|
||
|
||
- **S5 工作区上下文精简(2026-06-27 已实现)— 避免多个冲突路径阻断对话续跑** ✅
|
||
现象:每个 gateway turn 的 prompt 前缀 `TaskThread workspace context` 同时塞 `currentTaskWorkspace` + `localWorkspace` + `remoteWorkspaceHint` 三个近似重复的绝对路径,其中 **`localWorkspace` 是 App 本机线程目录(`~/.xworkmate/threads/…`),网关 agent 的文件系统根本访问不到**;`remoteWorkspaceHint` 又与 `currentTaskWorkspace` 重复。多个相互冲突的路径让 agent 不知该在哪工作 → 对话任务可能无法继续执行。
|
||
改进(`app_controller_desktop_thread_actions.dart taskWorkspaceContextPromptInternal`):网关任务**只给 `currentTaskWorkspace` 一个工作目录**(artifact scope 由插件托管);`localWorkspace` 仅在非网关(本地 agent 实际运行于此)保留;`remoteWorkspaceHint` 与 `currentTaskWorkspace` 相同则去重。`sessionKey` 保留(短、非路径、不致冲突)。
|
||
不破坏 UI:聊天气泡显示原始用户消息,prompt 调试视图只特判 `Execution context`/`Preferred skills`/`Attached files` 块,工作区块按正文渲染。验收:`assistant_execution_target_test.dart` 全绿(网关 prompt 断言 `isNot(localWorkspace)` / `isNot(remoteWorkspaceHint)`,保留 `currentTaskWorkspace`/`sessionKey`)。
|
||
|
||
---
|
||
|
||
## 8. 2026-06-27 Cases 00–05 全面验收执行日志(进行中)
|
||
|
||
> 执行计划:`docs/plans/2026-06-27-cases-00-05-gateway-turn-acceptance.md`。本节只记录脱敏后的运行证据;API Key、Bridge Token、账号密码不写入仓库。
|
||
> 追溯参考:`.xcodeinsight/context/repo-summary.md`、`.xcodeinsight/index/risk-index.md`、`.xcodeinsight/index/callchain-index.md`,用于对齐 `xworkmate-app` / `xworkmate-bridge` / `openclaw-multi-session-plugins` / `playbooks` 的调用链与风险边界。
|
||
|
||
### 8.1 当前目标与状态
|
||
|
||
| 阶段 | 状态 | 当前证据 / 下一步 |
|
||
|---|---|---|
|
||
| 仓库与运行态基线 | ✅ 已完成 | App 基线 `ca9cba6` 已纳入回归;本轮修复最终提交为 `66fd0e4` |
|
||
| 本地 all-in-one 部署 | ✅ 已完成 | 稳定插件目录幂等迁移修复已提交 `xworkspace-console` main(`50c2d85` + `5093e21`),本地修复版已验证通过 |
|
||
| Gateway Turn 定向回归 | 🟢 已通过 | T5 两条新增定向测试通过;完整 `assistant_execution_target_test.dart` 74 条通过 |
|
||
| Cases 00–05 真实任务 | ✅ 已完成 | 任务跑完后已整理为本节日志;后续若有新增回归再追加 |
|
||
| 提交 / push / CI | ✅ 已完成 | `xworkmate-app` 已提交并推送 `main -> 66fd0e4`;相关支撑仓也已推送完成 |
|
||
|
||
### 8.2 08:47 CST 基线快照
|
||
|
||
- Bridge:`127.0.0.1:8787` 正在监听,launchd `plus.svc.xworkspace.bridge` 为 running;匿名 `/api/ping` 返回 `401`,符合鉴权启用预期,后续用本机 token 脱敏核验 commit/metrics。
|
||
- Gateway:`127.0.0.1:18789` 正在监听,launchd `ai.openclaw.gateway` 为 running。
|
||
- 插件:`openclaw-multi-session-plugins` 为 `loaded`,Source/Install path 均为稳定目录 `~/.openclaw/extensions/openclaw-multi-session-plugins`,Recorded version `2026.6.1`;S0 的临时目录问题当前未复发。
|
||
- 仓库:`xworkmate-app`、`xworkmate-bridge`、`xworkspace-console` 均在 `main`;`openclaw-multi-session-plugins` 本地 `main` 比远端 ahead 1,验收过程不得误带该仓库已有提交。
|
||
- 安全边界:用户提供的三类模型 API Key 仅作为安装子进程环境变量传入,不落文档、不纳入 Git;首轮暴露出远端脚本会打印 provider key 的缺陷,已在 §8.3 记录并修复本地源码。
|
||
|
||
### 8.3 08:54 CST 首轮发现与修复
|
||
|
||
- **T5 测试缺口已补**:旧测试仍断言 OpenClaw `tasks.get` 第一次 `ACP_HTTP_CONNECTION_CLOSED` 就立即失败,与「有界续轮询」新契约冲突。现拆为:①一次瞬断后第二次快照成功,pending 清理且 lifecycle=`ready/success`;②连续 `kOpenClawPollTransientRetryLimit + 1`(当前 6)次瞬断后,确定性落 `ACP_HTTP_CONNECTION_CLOSED`、清 pending/association。两条定向测试均 `All tests passed!`。
|
||
- **测试速度可控**:`pollOpenClawTaskAssociationInternal` 新增默认仍为 2 秒的 `pollInterval` 可选参数,仅测试注入 `Duration.zero`,生产重试节奏不变。
|
||
- **安装日志泄密缺口**:托管 bootstrap 把 provider API Key 走普通 `append_var`,因此会打印明文;统一 auth token 则已脱敏。这不是模型调用失败原因,但违反安装安全边界。已在 `xworkspace-console` 本地改为六类 provider key 全走 `append_secret_var`,并新增 bootstrap 回归;`bash tests/setup-ai-workspace-all-in-one-test.sh` 全部通过。当前正在运行的脚本来自修复前远端,最终文档不记录任何 key 值。
|
||
|
||
### 8.4 08:59 CST 部署幂等修复与 App 完整定向回归
|
||
|
||
- 首轮 all-in-one 在 `Link openclaw-multi-session-plugins to extensions (macOS)` 失败:S0 已把目标改成稳定真实目录,而旧 patch 仍强制 `state: link` 指向 `/tmp`/源码目录;Ansible 正确拒绝 directory→symlink。自动重跑无法修复结构性错误,因此中止第二轮。
|
||
- `xworkspace-console` 修复:macOS patch 现在会识别并移除旧临时 symlink、确保 `~/.openclaw/extensions/openclaw-multi-session-plugins` 为真实目录、只复制构建产物/manifest,并执行 `openclaw plugins install <stable-path> --force` 记录 provenance;不再把 S0 修复倒退成临时链接。
|
||
- bootstrap 本地执行优先采用同 checkout 的 `patch-macos-playbooks.py`,远端 fallback 增加 cache-busting,避免 main 刚提交后又下载到 5 分钟 CDN 旧版本。
|
||
- 上述 installer 修复已分两次提交并 push 到 `xworkspace-console/main`:`50c2d85`、`5093e21`;bootstrap tests、`bash -n`、Python compile 均通过。
|
||
- App 完整定向回归:`flutter test test/runtime/assistant_execution_target_test.dart` → **74 tests / All tests passed**。覆盖 T3 running deadline、T4 本地停止、T5 断线恢复/耗尽、T6 pending 清理以及五类代表性 E2E admission/isolation 测试。
|
||
|
||
### 8.5 09:xx CST 收尾结果
|
||
|
||
- `xworkmate-app` 已完成最终提交并推送:`66fd0e4 fix(gateway): harden OpenClaw polling and acceptance notes`。
|
||
- `xworkspace-console` 与 `playbooks` 的相关修复提交也已分别推送到 `main`;`playbooks` 仍保留用户原有的 `roles/cloudflare_dns/tasks/main.yml` 本地改动,未触碰。
|
||
- `.xcodeinsight` 里的 repo-summary / risk / callchain 已用于对齐调用链、风险边界和收尾验证,后续同类问题可继续沿此索引追溯。
|
||
|
||
---
|
||
|
||
## 9. 2026-06-27 全链路状态总览与剩余稳定性 backlog(跨 4 仓 main 已合并)
|
||
|
||
> 本节是「重新梳理」的收口:把 §3 失效点 → §5 编码 TODO → §7 live 改进三条线索,对齐到**当前各仓 main 的真实代码状态**,并给出剩余项的精确验收口径。前文 §5 个别 checkbox 曾滞后于 §2/§6,本节为权威状态。
|
||
|
||
### 9.1 四仓 main 基线(全部已提交并推送)
|
||
|
||
| 仓库 | main HEAD | 本轮关键内容 |
|
||
|---|---|---|
|
||
| `xworkmate-app` | `82d25de` docs(case06): close out acceptance log(代码基线 `66fd0e4`) | T3/T4/T5/T6 客户端止血;本案文档 |
|
||
| `xworkmate-bridge` | `0a50621` fix(acp): remove orphaned S1 test — keep main compiling | T7/T8/T9 持久 run 仓、T10 错误语义、T11/T12 可观测;**S1 已回退** |
|
||
| `openclaw-multi-session-plugins` | `1fe544c` ci(runtime-release): publish stable runtime-latest tag | 插件 `xworkmate.*` 方法源;CI 改 npm 构建 + `runtime-latest` 稳定发布 URL |
|
||
| `ai-workspace-infra/playbooks` | `5c74feb` fix(cloudflare_dns): prefer CLOUDFLARE_API_TOKEN | T1/T2 入口超时+flush 全 route 对齐(`70m`,validate 断言);cloudflare token 优先级 |
|
||
|
||
> 旁路修复另在 `xworkspace-console/main`(`50c2d85` + `5093e21`):macOS 稳定插件目录幂等迁移、provider key 走 `append_secret_var`。
|
||
|
||
### 9.2 已完成(一次任务能否「有界、可恢复、能产出」的全部止血+加固)
|
||
|
||
四层链路(App → bridge → 插件 → gateway)的稳定性闭环已落地:
|
||
|
||
- **入口层(T1/T2,playbooks)**:所有 route `flush_interval -1` + 单源 `70m` 超时 ≥ bridge 60min,长任务不再 30min 必断。
|
||
- **客户端层(T3/T4/T5/T6,app)**:running 轮询有 deadline 封顶、`停止`本地权威、传输瞬断有界续轮询(≤5)、三终态确定清 pending。「无限运行 / 停不掉 / 瞬断丢结果」根除。
|
||
- **bridge 层(T7/T8/T9/T10,bridge)**:per-session 持久 run 仓与 WS 解耦,网关瞬断/抖动收敛为「有界续轮询 → deadline 终态」,终态结果回放不丢;socket-closed 带 `retryable/poll` 语义。
|
||
- **可观测(T11/T12/T13)**:`runId` 贯穿四层日志;`/api/ping.metrics` 暴露三类不稳定计数;运行态双侧校验(bridge `commit` + 网关 `N plugins`)。
|
||
- **主根因(S0)**:插件稳定安装到 `~/.openclaw/extensions/` 真实目录(非 /tmp 软链)+ `plugins install --force` provenance,重启不再丢插件、`xworkmate.*` 不再 `unknown method`。
|
||
|
||
### 9.3 剩余 backlog(按「直接决定产出」排序)
|
||
|
||
| 项 | 仓库 | 状态 | 问题 | 正确做法 / 验收 |
|
||
|---|---|---|---|---|
|
||
| **S1** 缺省 `expectedArtifactDirs` 兜底扫描 | bridge(+插件) | ⚠️ 已回退(`0280893`→`81f65e3`),**待重做** | 旧实现把「有 expectedArtifactDirs」等同「必须阻塞导出」,无产物的成功 run 卡在「等待导出」,E2E 转红 | **解耦**「扫描提示」与「阻塞式导出」:缺省目录只驱动插件兜底扫描、**不**触发 bridge 等待导出;或仅当客户端显式声明 `requiredArtifactExtensions` 才启用阻塞。验收:全 E2E 套件绿 + 「agent 写产物到 workspace 根」能被收集 |
|
||
| **S2** `no_native_task_record` 状态歧义 | 插件 + bridge | 🔬 待改进 | chat.send 成功但 gateway 无 detached task 且无产物时,无法区分「还在跑」与「跑完没产物」,只能 degraded 续轮询到 deadline | ①核对 chat.send 是否应注册 detached task(agent/`tasks.*` 配置);②超过最小执行时长后下发明确 `running(no-record)` vs `completed(no-artifact)`,配合 T9 deadline 收口。验收:正常执行 `tasks.get` 能 running→completed;异常给确定终态 |
|
||
| **T8b** 跨进程重启持久化 | bridge | ⏸️ 已知边际项 | 现 per-session run 仓为内存,覆盖「WS 抖动/网关瞬断」,但 bridge **进程重启**仍丢终态 | 把 run 仓落磁盘 / 接 `xworkmate.jobs.*`(序列化 + 启动加载 + 过期清理 + 并发)。独立一轮带测试。验收:bridge 重启后 `tasks.get` 仍能回放终态 |
|
||
|
||
### 9.4 稳定性改进建议(防回归 / 运维侧)
|
||
|
||
1. **运行态≠源码 的常态化校验**:把 §5 T13 的双侧检查做成健康探针/CI gate——bridge `/api/ping.commit` 等于发布 commit、网关启动日志 `N plugins` 含 `openclaw-multi-session-plugins`、`openclaw plugins inspect` 无 provenance 警告。三者任一不满足即告警,避免再次「修了但跑的不是它」。
|
||
2. **插件安装纳入部署**:把 `openclaw-multi-session-plugins` 的稳定目录安装收敛进 `deploy_gateway_openclaw`(从仓库构建产物 install,非软链 /tmp),让 S0 修复不依赖手工。
|
||
3. **超时同源不可漂移**:入口 `xworkmate_bridge_acp_stream_timeout` 与 bridge `openClawAgentWaitMaxTimeout` 已分两侧定义,建议在 validate/CI 加一条「入口 ≥ bridge + 余量」的交叉断言,防未来单侧改值再漂移。
|
||
4. **S1 重做前先补测**:先写「有 expectedArtifactDirs 但 run 无产物」与「agent 写产物到 workspace 根」两类对照 E2E,再改实现,避免重蹈 `0280893` 回退。
|
||
5. **`/api/ping.metrics` 接告警**:`gatewaySocketClosed`/`taskGetUnconfirmedFallback`/`runDeadlineInterrupt` 三计数接监控,使「不稳定」可被观测而非靠用户截图。
|
||
|
||
### 9.5 本次验收摘要
|
||
|
||
这次 case 的结论可以压缩成三句话:
|
||
|
||
1. 不是 `LiteLLM` 余量问题,而是 gateway-turn 的契约链路里,插件加载、运行态快照和结果回传先后顺序出了偏差。
|
||
2. `openclaw-multi-session-plugins` 稳定加载后,`xworkmate.tasks.get` 能回到可持续轮询的终态语义,`GoTaskService 没有返回可显示的输出。` 也随之恢复为可显示结果。
|
||
3. 当前验收标准是:任务能完成、能产出 `.md`、`tasks.get` 能返回 `completed + durable output + artifacts`,并且 App 不再把 undecorated `running` 快照误判成空终态。
|