# 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` 快照误判成空终态。
