work-ally 0.2.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +110 -0
- package/DASHBOARD.md +160 -0
- package/PRODUCT.md +113 -0
- package/README.md +403 -0
- package/ally.sh +171 -0
- package/bridge/src/approval-rules.ts +360 -0
- package/bridge/src/channel-delivery.ts +207 -0
- package/bridge/src/channel-types.ts +22 -0
- package/bridge/src/channels/fake/adapter.ts +31 -0
- package/bridge/src/channels/feishu/adapter.ts +411 -0
- package/bridge/src/channels/feishu/approvals.ts +6 -0
- package/bridge/src/channels/feishu/formatter.ts +276 -0
- package/bridge/src/channels/feishu/normalize.ts +368 -0
- package/bridge/src/codex-config.ts +52 -0
- package/bridge/src/config.ts +240 -0
- package/bridge/src/fake-runtime-client.ts +505 -0
- package/bridge/src/handoff-service.ts +494 -0
- package/bridge/src/logger.ts +194 -0
- package/bridge/src/memory-digest.ts +186 -0
- package/bridge/src/receiver-approval-autonomy.ts +158 -0
- package/bridge/src/receiver-control-core.ts +140 -0
- package/bridge/src/receiver-control-work-session.ts +218 -0
- package/bridge/src/receiver-control.ts +83 -0
- package/bridge/src/receiver-delivery.ts +136 -0
- package/bridge/src/receiver-helpers.ts +96 -0
- package/bridge/src/receiver-human-gate.ts +333 -0
- package/bridge/src/receiver-inbound-preflight.ts +162 -0
- package/bridge/src/receiver-recovery.ts +236 -0
- package/bridge/src/receiver-runtime-callbacks.ts +367 -0
- package/bridge/src/receiver-runtime-policy.ts +132 -0
- package/bridge/src/receiver-runtime-state.ts +124 -0
- package/bridge/src/receiver-support-actions.ts +189 -0
- package/bridge/src/receiver-thread-start.ts +57 -0
- package/bridge/src/receiver-turn-coordination.ts +94 -0
- package/bridge/src/receiver-turn-execution.ts +257 -0
- package/bridge/src/receiver-turn-failure.ts +143 -0
- package/bridge/src/receiver-turn-result.ts +185 -0
- package/bridge/src/receiver-turn-steer.ts +70 -0
- package/bridge/src/receiver-work-session.ts +76 -0
- package/bridge/src/receiver.ts +329 -0
- package/bridge/src/router.ts +62 -0
- package/bridge/src/runtime-client-agent-messages.ts +150 -0
- package/bridge/src/runtime-client-message-dispatch.ts +176 -0
- package/bridge/src/runtime-client-protocol.ts +411 -0
- package/bridge/src/runtime-client-request-ops.ts +56 -0
- package/bridge/src/runtime-client-run-turn.ts +158 -0
- package/bridge/src/runtime-client-thread-ops.ts +270 -0
- package/bridge/src/runtime-client-transport.ts +309 -0
- package/bridge/src/runtime-client-turn-poll.ts +224 -0
- package/bridge/src/runtime-client-turn-read.ts +185 -0
- package/bridge/src/runtime-client-turn-state.ts +105 -0
- package/bridge/src/runtime-client.ts +344 -0
- package/bridge/src/runtime-user-input.ts +403 -0
- package/bridge/src/scheduler.ts +239 -0
- package/bridge/src/server-handoff-command.ts +364 -0
- package/bridge/src/server-main.ts +80 -0
- package/bridge/src/server-routine-command.ts +60 -0
- package/bridge/src/server-routine-execution.ts +222 -0
- package/bridge/src/server-runtime-app-support.ts +107 -0
- package/bridge/src/server-runtime-app.ts +238 -0
- package/bridge/src/server-thread-sync-command.ts +63 -0
- package/bridge/src/server.ts +17 -0
- package/bridge/src/session-store-delivery.ts +220 -0
- package/bridge/src/session-store-human-gate.ts +380 -0
- package/bridge/src/session-store-inbound-acceptance.ts +66 -0
- package/bridge/src/session-store-meta.ts +134 -0
- package/bridge/src/session-store-turn-ledger.ts +272 -0
- package/bridge/src/session-store.ts +380 -0
- package/bridge/src/system-notify.ts +220 -0
- package/bridge/src/thread-sync.ts +200 -0
- package/bridge/src/translator.ts +494 -0
- package/bridge/src/types.ts +289 -0
- package/bridge/src/utils.ts +104 -0
- package/bridge/src/work-session-store.ts +471 -0
- package/docs/.gitkeep +0 -0
- package/docs/architecture/codex-feishu-bridge-proposal.md +2742 -0
- package/docs/completed/FEATURE-feishu-markdown-and-reply-support.md +327 -0
- package/docs/completed/README.md +21 -0
- package/docs/completed/SPEC-approval-autonomy-and-safe-defaults.md +205 -0
- package/docs/completed/SPEC-approval-batch-and-strict-reply-shortcuts.md +153 -0
- package/docs/completed/SPEC-conversation-noise-reduction-and-busy-input-gate.md +538 -0
- package/docs/completed/SPEC-engineering-sop-skillization.md +190 -0
- package/docs/completed/SPEC-faithful-bridge-core-thinning-v2.md +376 -0
- package/docs/completed/SPEC-faithful-bridge-core-thinning.md +1071 -0
- package/docs/completed/SPEC-group-chat-sender-identity.md +301 -0
- package/docs/completed/SPEC-middleware-exception-visibility.md +227 -0
- package/docs/completed/SPEC-nightly-memory-digest-visibility.md +121 -0
- package/docs/completed/SPEC-project-group-chat-human-centered-conversation-mapping.md +326 -0
- package/docs/completed/SPEC-remove-cli-persona-bootstrap.md +201 -0
- package/docs/developer-workflow.md +49 -0
- package/docs/implementation/SPEC-codex-same-machine-session-handoff-implementation.md +239 -0
- package/docs/implementation/test-coverage-map.md +363 -0
- package/docs/implementation/work-ally-implementation-guide.md +790 -0
- package/docs/issues/README.md +10 -0
- package/docs/issues/pending/ANALYSIS-ally-premature-recovery-notice-and-task-state-semantics-2026-03-18.md +295 -0
- package/docs/issues/resolved/ANALYSIS-approval-waiting-visible-but-approval-artifact-missing-2026-03-16.md +466 -0
- package/docs/issues/resolved/ANALYSIS-blocking-state-visible-without-user-actionable-artifact-2026-03-16.md +261 -0
- package/docs/issues/resolved/ANALYSIS-codex-app-server-transport-disconnect-semantics-2026-03-14.md +606 -0
- package/docs/issues/resolved/ANALYSIS-premature-terminalization-on-fresh-thread-poll-and-object-error-leak-2026-03-16.md +348 -0
- package/docs/issues/resolved/ANALYSIS-runtime-turn-delivery-and-recovery-2026-03-14.md +603 -0
- package/docs/issues/resolved/ANALYSIS-self-test-gap-approval-waiting-visible-but-approval-artifact-missing-2026-03-16.md +166 -0
- package/docs/issues/resolved/ANALYSIS-self-test-gap-blocking-state-visible-without-user-actionable-artifact-2026-03-16.md +186 -0
- package/docs/issues/resolved/ANALYSIS-self-test-gap-premature-terminalization-on-fresh-thread-poll-and-object-error-leak-2026-03-16.md +166 -0
- package/docs/issues/resolved/REPORT-ally-runtime-turn-delivery-3b42fb8-2026-03-15.md +373 -0
- package/docs/manual-acceptance.md +127 -0
- package/docs/ops-runbook.md +44 -0
- package/docs/planning/FEATURE-memory-system.md +748 -0
- package/docs/planning/SPEC-active-turn-steer-and-context-compaction-visibility.md +269 -0
- package/docs/planning/SPEC-approval-rules-inheritance-and-local-validation-lane.md +450 -0
- package/docs/planning/SPEC-assistant-persona-bootstrap.md +199 -0
- package/docs/planning/SPEC-assistant-rename.md +610 -0
- package/docs/planning/SPEC-bridge-app-server-protocol-alignment.md +667 -0
- package/docs/planning/SPEC-claude-runtime-host-for-work-ally.md +434 -0
- package/docs/planning/SPEC-cli-feishu-codex-session-unification.md +236 -0
- package/docs/planning/SPEC-codex-same-machine-session-handoff.md +873 -0
- package/docs/planning/SPEC-feishu-reaction-shortcuts.md +282 -0
- package/docs/planning/SPEC-local-stable-release-boundary.md +166 -0
- package/docs/planning/SPEC-managed-thread-entry-and-surface-mobility.md +862 -0
- package/docs/planning/SPEC-minimal-bridge-semantics-and-user-visible-surface.md +362 -0
- package/docs/planning/SPEC-npm-alpha-distribution-and-install-first-release.md +222 -0
- package/docs/planning/SPEC-remove-websocket-runtime-transport.md +364 -0
- package/docs/planning/SPEC-runtime-abstraction-phase-1.md +424 -0
- package/docs/planning/SPEC-runtime-connection-and-turn-recovery-semantics.md +274 -0
- package/docs/planning/SPEC-session-presence-and-state-visibility.md +397 -0
- package/docs/planning/SPEC-skill-first-capability-packaging.md +338 -0
- package/docs/planning/SPEC-stable-archive-contract.md +456 -0
- package/docs/planning/SPEC-supervised-start-boundary.md +127 -0
- package/docs/planning/SPEC-user-barrier-reduction-and-activation.md +832 -0
- package/docs/planning/ally-next.md +1278 -0
- package/docs/planning/assistant-workbench-spec.md +725 -0
- package/docs/planning/product-workbench.md +283 -0
- package/docs/product-onboarding.md +227 -0
- package/docs/product-spec-standard.md +528 -0
- package/docs/troubleshooting.md +45 -0
- package/docs/user-quickstart.md +46 -0
- package/internal/dispatch.sh +95 -0
- package/internal/lib/common.sh +1450 -0
- package/internal/modules/assistant/manage.sh +1312 -0
- package/internal/modules/bootstrap/setup.sh +144 -0
- package/internal/modules/config/init-env.sh +10 -0
- package/internal/modules/global/manage.sh +154 -0
- package/internal/modules/handoff/manage.sh +54 -0
- package/internal/modules/mcp/manage.sh +83 -0
- package/internal/modules/ops/logs.sh +76 -0
- package/internal/modules/routines/manage.sh +55 -0
- package/internal/modules/runtime/assistant-autosave.sh +26 -0
- package/internal/modules/runtime/restart.sh +6 -0
- package/internal/modules/runtime/start.sh +283 -0
- package/internal/modules/runtime/status.sh +194 -0
- package/internal/modules/runtime/stop.sh +55 -0
- package/internal/modules/runtime/supervisor.sh +216 -0
- package/internal/modules/runtime/update.sh +26 -0
- package/package.json +41 -0
- package/runtime/config/.gitkeep +0 -0
- package/runtime/host/.gitkeep +0 -0
- package/runtime/host/healthcheck-codex-app-server.ts +22 -0
- package/runtime/host/ping-pong-codex-app-server.ts +66 -0
- package/runtime/host/probe-codex-app-server.ts +115 -0
- package/skills/archive-reader/SKILL.md +9 -0
- package/skills/feishu-production-debug/SKILL.md +37 -0
- package/skills/feishu-production-debug/references/feishu-debug-order.md +49 -0
- package/skills/feishu-production-debug/references/platform-permission-baseline.md +23 -0
- package/skills/issue-to-spec-triage/SKILL.md +44 -0
- package/skills/issue-to-spec-triage/references/triage-rules.md +66 -0
- package/skills/memory-digest/SKILL.md +9 -0
- package/skills/post-implementation-closure/SKILL.md +39 -0
- package/skills/post-implementation-closure/references/closure-checklist.md +45 -0
- package/skills/post-implementation-closure/references/doc-drift-map.md +49 -0
- package/skills/product-spec/SKILL.md +244 -0
- package/templates/env.example +5 -0
- package/templates/routines/nightly-memory-digest.yaml +10 -0
- package/templates/workspace/AGENTS.md +26 -0
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
# 分析报告:Ramond 办公桌 2026-03-14 21:47 断连事件与中间层交付语义治理
|
|
2
|
+
|
|
3
|
+
更新时间:2026-03-14
|
|
4
|
+
状态:分析完成 / 供架构评审
|
|
5
|
+
作者:Codex
|
|
6
|
+
|
|
7
|
+
## 1. 文档目的
|
|
8
|
+
|
|
9
|
+
这份报告回答两件事:
|
|
10
|
+
|
|
11
|
+
1. 还原 2026-03-14 21:47 左右 Ramond 办公桌一次真实断连事件的完整事实链,包括背景、日志路径、分析过程、关键证据与根因判断。
|
|
12
|
+
2. 把这次 incident 提升为一类中间层设计问题来讨论,给出面向 `work-ally` 长期演进的整体治理方案,供另一位架构师评审和继续推进。
|
|
13
|
+
|
|
14
|
+
这份报告不包含任何代码改动建议的落地 patch,也不把重点放在某一个函数的小修小补上。重点是把问题从“这次为什么坏了”提升到“这类问题如何从设计层面治理”。
|
|
15
|
+
|
|
16
|
+
## 2. 事件背景
|
|
17
|
+
|
|
18
|
+
### 2.1 用户最初问题
|
|
19
|
+
|
|
20
|
+
本次分析起点不是代码 review,而是一次针对真实对话故障的排查请求。
|
|
21
|
+
|
|
22
|
+
用户明确指出:
|
|
23
|
+
|
|
24
|
+
- 对象是 `Ramond` 办公桌
|
|
25
|
+
- 时间点是 2026-03-14 晚上 `21:47` 左右(Asia/Shanghai)
|
|
26
|
+
- 用户前台看到的对话片段依次为:
|
|
27
|
+
- `【系统消息】 Codex App Server 已断开,当前任务状态待确认。`
|
|
28
|
+
- `【系统消息】 Codex App Server 连接中断,正在确认这一轮是否已完成。`
|
|
29
|
+
- `【系统消息】 Codex App Server 连接已恢复,继续确认上一轮结果。`
|
|
30
|
+
- `【系统消息】 Codex App Server 连接已恢复,但系统未能确认上一轮是否已完成。为避免漏掉结果,请重新发送上一条消息。`
|
|
31
|
+
- 用户重新发送:`进度如何`
|
|
32
|
+
- 系统回:`⏳ 工作中 正在处理,请稍候。`
|
|
33
|
+
- 最后报错:`runtime turn reconciliation failed: idle thread without completed turn event (019cec9b-c8ef-74c2-a56d-4d5d0c3c5d0c)`
|
|
34
|
+
|
|
35
|
+
用户要求先查清这次故障本身,再进一步判断这种问题是否能从本质上被根治。
|
|
36
|
+
|
|
37
|
+
### 2.2 这次排查的核心问题
|
|
38
|
+
|
|
39
|
+
从排障目标上,本次并不是单纯问“为什么 websocket 断了”,而是问四个更关键的问题:
|
|
40
|
+
|
|
41
|
+
1. `21:47` 时到底断了什么。
|
|
42
|
+
2. 断开之后,上一轮任务到底有没有完成。
|
|
43
|
+
3. 用户后来重新问“进度如何”时,为什么 bridge 又给出了一次错误结论。
|
|
44
|
+
4. 这暴露的是单点 bug,还是中间层设计层面的系统性缺口。
|
|
45
|
+
|
|
46
|
+
## 3. 排查范围与证据位置
|
|
47
|
+
|
|
48
|
+
### 3.1 办公桌与运行态位置
|
|
49
|
+
|
|
50
|
+
本次排查对象位于:
|
|
51
|
+
|
|
52
|
+
- Ramond 办公桌根目录:`/Users/allenfeng/.work-ally/assistants/Ramond`
|
|
53
|
+
- 运行态目录:`/Users/allenfeng/.work-ally/assistants/Ramond/.system`
|
|
54
|
+
|
|
55
|
+
### 3.2 本次使用到的关键证据文件
|
|
56
|
+
|
|
57
|
+
#### A. 用户可读对话与稳定原材料
|
|
58
|
+
|
|
59
|
+
- 对话视图:`/Users/allenfeng/.work-ally/assistants/Ramond/conversations/2026-03-14--feishu--oc_6caa5cabdf488d208445714f4e4c472b.md`
|
|
60
|
+
- 原材料账本:`/Users/allenfeng/.work-ally/assistants/Ramond/.system/archive/2026/03/14/feishu--oc_6caa5cabdf488d208445714f4e4c472b.ndjson`
|
|
61
|
+
|
|
62
|
+
#### B. bridge 侧运行日志
|
|
63
|
+
|
|
64
|
+
- 主日志:`/Users/allenfeng/.work-ally/assistants/Ramond/.system/logs/bridge-2026-03-14.log`
|
|
65
|
+
- 时间线日志:`/Users/allenfeng/.work-ally/assistants/Ramond/.system/logs/timeline-2026-03-14.log`
|
|
66
|
+
|
|
67
|
+
#### C. runtime session 台账
|
|
68
|
+
|
|
69
|
+
- session 元信息:`/Users/allenfeng/.work-ally/assistants/Ramond/.system/runtime/sessions/feishu--oc_6caa5cabdf488d208445714f4e4c472b/meta.json`
|
|
70
|
+
- session 事件流:`/Users/allenfeng/.work-ally/assistants/Ramond/.system/runtime/sessions/feishu--oc_6caa5cabdf488d208445714f4e4c472b/events.ndjson`
|
|
71
|
+
- 当前 thread 记录:`/Users/allenfeng/.work-ally/assistants/Ramond/.system/runtime/sessions/feishu--oc_6caa5cabdf488d208445714f4e4c472b/current-thread.json`
|
|
72
|
+
- turn 记录目录:`/Users/allenfeng/.work-ally/assistants/Ramond/.system/runtime/sessions/feishu--oc_6caa5cabdf488d208445714f4e4c472b/turns/`
|
|
73
|
+
|
|
74
|
+
#### D. Codex runtime session 原始轨迹
|
|
75
|
+
|
|
76
|
+
- rollout 会话轨迹:`/Users/allenfeng/.work-ally/assistants/Ramond/.system/codex-home/sessions/2026/03/14/rollout-2026-03-14T15-51-27-019ceb54-2926-7360-9e8f-0d85d7ab1505.jsonl`
|
|
77
|
+
|
|
78
|
+
#### E. 相关实现代码
|
|
79
|
+
|
|
80
|
+
- runtime client:`/Users/allenfeng/Development/Repositories/work-ally/bridge/src/runtime-client.ts`
|
|
81
|
+
- receiver:`/Users/allenfeng/Development/Repositories/work-ally/bridge/src/receiver.ts`
|
|
82
|
+
- session store:`/Users/allenfeng/Development/Repositories/work-ally/bridge/src/session-store.ts`
|
|
83
|
+
- archive store:`/Users/allenfeng/Development/Repositories/work-ally/bridge/src/archive-store.ts`
|
|
84
|
+
|
|
85
|
+
#### F. 现有设计文档
|
|
86
|
+
|
|
87
|
+
- `README.md`
|
|
88
|
+
- `docs/architecture/codex-feishu-bridge-proposal.md`
|
|
89
|
+
- `docs/planning/SPEC-runtime-connection-and-turn-recovery-semantics.md`
|
|
90
|
+
- `docs/completed/SPEC-middleware-exception-visibility.md`
|
|
91
|
+
- `docs/planning/SPEC-session-presence-and-state-visibility.md`
|
|
92
|
+
- `docs/planning/SPEC-stable-archive-contract.md`
|
|
93
|
+
|
|
94
|
+
## 4. 分析过程
|
|
95
|
+
|
|
96
|
+
### 4.1 第一步:确认时间轴与真实时间点
|
|
97
|
+
|
|
98
|
+
用户给的是本地时间 `21:47`。Ramond 办公桌日志里大量时间戳是 UTC,因此第一步先统一时区语义:
|
|
99
|
+
|
|
100
|
+
- `2026-03-14 21:47 Asia/Shanghai`
|
|
101
|
+
- 等于 `2026-03-14T13:47Z`
|
|
102
|
+
|
|
103
|
+
之后所有日志检索都围绕 `13:47Z ~ 13:50Z` 展开。
|
|
104
|
+
|
|
105
|
+
### 4.2 第二步:先从用户可见材料反推内部 turn
|
|
106
|
+
|
|
107
|
+
在 archive 与 conversation view 里,用户确实先后看到了四条系统消息,然后重发了“进度如何”,接着看到“⏳ 工作中”,最后收到 `runtime turn reconciliation failed: idle thread without completed turn event (...)`。
|
|
108
|
+
|
|
109
|
+
这一步确认了两件事:
|
|
110
|
+
|
|
111
|
+
1. 用户反馈与系统黑匣子是一致的,不是回忆误差。
|
|
112
|
+
2. 需要同时追两轮 turn:
|
|
113
|
+
- 断线时的上一轮 turn
|
|
114
|
+
- 用户重发“进度如何”后的新一轮 turn
|
|
115
|
+
|
|
116
|
+
### 4.3 第三步:在 bridge 日志里定位断开点
|
|
117
|
+
|
|
118
|
+
在 `bridge-2026-03-14.log` 中,关键转折发生在 `2026-03-14T13:47:05.323Z`:
|
|
119
|
+
|
|
120
|
+
- `runtime.connect_closed`,reason 为 `runtime websocket closed`
|
|
121
|
+
- 紧接着 `receiver.turn_failed` 把当时正在执行的 turn 标成失败
|
|
122
|
+
- 后续自动重连成功,并进入 turn recovery attempt
|
|
123
|
+
|
|
124
|
+
这证明:
|
|
125
|
+
|
|
126
|
+
- 断开的不是 Feishu 入站,也不是用户消息链路
|
|
127
|
+
- 断的是 `bridge <-> Codex App Server` 的 websocket
|
|
128
|
+
|
|
129
|
+
### 4.4 第四步:确认断线前那一轮是否真的彻底失败
|
|
130
|
+
|
|
131
|
+
如果只看 bridge 当时的判断,会以为上一轮 turn 已失败;但 rollout 原始轨迹显示并非如此。
|
|
132
|
+
|
|
133
|
+
在 Codex runtime 原始 session 里,长命令 `call_j8Fa...` 持续运行到 `2026-03-14T13:50:02Z` 才结束,随后还读取了发布结果文件,最后在 `2026-03-14T13:50:17Z` 生成最终答复:
|
|
134
|
+
|
|
135
|
+
- 蒲公英链接:`https://www.pgyer.com/rapid_kit`
|
|
136
|
+
- Build 号:`178`
|
|
137
|
+
|
|
138
|
+
这说明上一轮从 runtime 角度其实是**完成了**,只是 bridge 在 websocket 断开后没能把这份结果稳定拿回来并交付给用户。
|
|
139
|
+
|
|
140
|
+
### 4.5 第五步:确认用户重新发送“进度如何”后发生了什么
|
|
141
|
+
|
|
142
|
+
bridge 日志显示:
|
|
143
|
+
|
|
144
|
+
- `2026-03-14T13:49:17.871Z` 收到用户消息 `进度如何`
|
|
145
|
+
- `2026-03-14T13:49:18.448Z` 启动新 turn:`019cec9b-c8ef-74c2-a56d-4d5d0c3c5d0c`
|
|
146
|
+
- `2026-03-14T13:49:21.066Z` 发出“⏳ 工作中”
|
|
147
|
+
|
|
148
|
+
到这里看上去一切正常。但 `2026-03-14T13:50:17Z` 左右出现了决定性的异常组合:
|
|
149
|
+
|
|
150
|
+
- thread status 先变成 `idle`
|
|
151
|
+
- 紧跟着到来的 `turn_completed_event`,对应的 turn 不是当前 turn `019cec9b-...`
|
|
152
|
+
- 而是上一轮断线时的 turn `019cec94-...`
|
|
153
|
+
- 随后 bridge 对当前 turn 执行 reconcile,结果失败,抛出:
|
|
154
|
+
- `runtime turn reconciliation failed: idle thread without completed turn event (019cec9b-...)`
|
|
155
|
+
|
|
156
|
+
### 4.6 第六步:把日志行为映射回代码
|
|
157
|
+
|
|
158
|
+
这一步不是为了“找一行错代码”,而是为了确认 bridge 现在的恢复语义到底建立在什么假设上。
|
|
159
|
+
|
|
160
|
+
在 `runtime-client.ts` 中,本次故障路径涉及四个关键点:
|
|
161
|
+
|
|
162
|
+
1. `runTurn()` 会把当前 turn 状态放到内存 `turnStates` 中等待完成。
|
|
163
|
+
2. websocket 关闭时,`handleSocketClosed()` 会直接 `rejectAllTurns()`,把所有活动 turn 从内存里 reject 并清掉。
|
|
164
|
+
3. 后续如果 thread 状态变成 `idle`,会对仍在 `turnStates` 里的 turn 启动 `reconcileIdleTurn()`。
|
|
165
|
+
4. `reconcileIdleTurn()` 的判断逻辑是:
|
|
166
|
+
- 先 `thread/read`
|
|
167
|
+
- 如果读不到对应 turn 的终态结果
|
|
168
|
+
- 且缓存的 thread 状态已经是 `idle`
|
|
169
|
+
- 就直接报 `idle thread without completed turn event`
|
|
170
|
+
|
|
171
|
+
这一点把问题边界暴露得非常清楚:
|
|
172
|
+
|
|
173
|
+
当前实现默认相信一种顺序:
|
|
174
|
+
|
|
175
|
+
- turn 正常完成
|
|
176
|
+
- `turn/completed` 事件按期到达
|
|
177
|
+
- 如果事件没到,`thread/read` 也应该能在短时间里读到同一轮 turn 的终态
|
|
178
|
+
|
|
179
|
+
一旦这个顺序被断线打乱,bridge 就会进入“猜测模式”。
|
|
180
|
+
|
|
181
|
+
## 5. 关键事实链
|
|
182
|
+
|
|
183
|
+
基于上述过程,可以把这次 incident 压缩成下面这条确定事实链。
|
|
184
|
+
|
|
185
|
+
### 5.1 事实 1:21:47 确实发生了真实断开
|
|
186
|
+
|
|
187
|
+
`2026-03-14T13:47:05.323Z`,bridge 记录:
|
|
188
|
+
|
|
189
|
+
- `runtime.connect_closed`
|
|
190
|
+
- reason:`runtime websocket closed`
|
|
191
|
+
|
|
192
|
+
因此用户最先看到的“Codex App Server 已断开,当前任务状态待确认”不是误报,而是一次真实传输层断开。
|
|
193
|
+
|
|
194
|
+
### 5.2 事实 2:断开的是中间层到 runtime 的连接,不是用户消息入口
|
|
195
|
+
|
|
196
|
+
同一时间段内,Feishu 会话还在正常工作,后续用户还能继续发“进度如何”。
|
|
197
|
+
|
|
198
|
+
因此这次故障本质不是“整套系统都挂了”,而是:
|
|
199
|
+
|
|
200
|
+
- 入站聊天窗口仍活着
|
|
201
|
+
- `bridge <-> runtime` 的语义连续性断了
|
|
202
|
+
|
|
203
|
+
### 5.3 事实 3:上一轮任务没有真正失败,而是 bridge 丢失了它的确定性交付
|
|
204
|
+
|
|
205
|
+
rollout 原始轨迹已经证明上一轮任务后来完成了,且产出了用户要的最终答案。
|
|
206
|
+
|
|
207
|
+
因此对用户来说,真正损失的不是“模型没做完”,而是:
|
|
208
|
+
|
|
209
|
+
- 模型做完了
|
|
210
|
+
- 中间层没能在恢复后把结果稳稳送回去
|
|
211
|
+
|
|
212
|
+
### 5.4 事实 4:用户重发后,新 turn 被建立了
|
|
213
|
+
|
|
214
|
+
用户按照系统提示重发“进度如何”后,新 turn `019cec9b-...` 被正常创建并进入处理中。
|
|
215
|
+
|
|
216
|
+
这说明 bridge 并没有彻底卡死,而是继续在同一 thread 上推进了新的回合。
|
|
217
|
+
|
|
218
|
+
### 5.5 事实 5:bridge 把“上一轮补发的完成信号”和“当前轮等待完成的回合”混淆了
|
|
219
|
+
|
|
220
|
+
`2026-03-14T13:50:17.311Z` 的 `turn_completed_event` 对应的是旧 turn `019cec94-...`,不是当前 turn `019cec9b-...`。
|
|
221
|
+
|
|
222
|
+
但随后 thread 状态已经变 `idle`,bridge 对当前 turn 执行了 reconcile,并在没有读到当前 turn 的 completed 结果后判它失败。
|
|
223
|
+
|
|
224
|
+
这说明 bridge 当前的恢复语义里,缺少一层更强的“回合确定性账本”,只能依赖在线事件顺序和短窗口补读结果去猜。
|
|
225
|
+
|
|
226
|
+
## 6. 根因判断
|
|
227
|
+
|
|
228
|
+
### 6.1 表层根因
|
|
229
|
+
|
|
230
|
+
表层根因可以概括为一句话:
|
|
231
|
+
|
|
232
|
+
> runtime websocket 断开后,bridge 在恢复阶段未能正确地区分“上一轮迟到的完成事实”和“当前轮仍待确认的完成事实”,导致 turn reconciliation 错配并误判失败。
|
|
233
|
+
|
|
234
|
+
### 6.2 深层根因
|
|
235
|
+
|
|
236
|
+
深层根因不是“某一个 if 写错了”,而是当前中间层把“turn 是否完成”这件事过度建立在**在线事件顺序**上,而不是建立在**可恢复的权威账本**上。
|
|
237
|
+
|
|
238
|
+
更精确地说,当前模型有三个结构性特征:
|
|
239
|
+
|
|
240
|
+
1. 活跃 turn 的真相主要存在于 bridge 进程内存的 `turnStates` 里。
|
|
241
|
+
2. websocket 一断,bridge 会立刻 reject 并清掉这些活动 turn。
|
|
242
|
+
3. 恢复后主要靠 `thread/status/changed` 与 `thread/read` 做一次有界猜测,而不是围绕某个稳定的 turn 交付记录持续收敛。
|
|
243
|
+
|
|
244
|
+
这意味着系统在断线后天然会进入一个歧义区:
|
|
245
|
+
|
|
246
|
+
- turn 到底没完成
|
|
247
|
+
- 还是已经完成,但完成事件没及时送到 bridge
|
|
248
|
+
- 还是已经完成,bridge 也短暂看见了,但本地状态先被清掉了
|
|
249
|
+
- 还是上一轮结果刚回来,而用户已经开始了下一轮
|
|
250
|
+
|
|
251
|
+
只要系统没有一个“哪一轮是否已完成、结果是否已持久化、结果是否已送达”的持久账本,这个歧义区就无法被真正消灭。
|
|
252
|
+
|
|
253
|
+
## 7. 第一性原理分析:这是不是“快递员问题”
|
|
254
|
+
|
|
255
|
+
用户在对话里给出的判断是:
|
|
256
|
+
|
|
257
|
+
> 这一类问题,本质上就是作为一个中间层,没有正确地作为一个“快递员”,把我对 Codex 说的话以及 Codex 对我的回应进行来回传递。
|
|
258
|
+
|
|
259
|
+
这个判断方向是对的,但还可以更精确。
|
|
260
|
+
|
|
261
|
+
### 7.1 中间层不是普通快递员,而是“带签收系统的快递员”
|
|
262
|
+
|
|
263
|
+
如果只把中间层理解成“把 A 发给 B,再把 B 发给 A”,那会低估它的职责。
|
|
264
|
+
|
|
265
|
+
在 `work-ally` 这类桥接系统里,中间层还必须承担五个语义责任:
|
|
266
|
+
|
|
267
|
+
1. **接单确认**:用户的话是否已经正式被系统接住。
|
|
268
|
+
2. **执行归属**:这句话对应哪一个 thread、哪一个 turn。
|
|
269
|
+
3. **完成确认**:Codex 是否真的已经产出终态结果。
|
|
270
|
+
4. **持久存证**:结果是否已经被中间层稳定记住,而不是只在 websocket 上飘过。
|
|
271
|
+
5. **送达确认**:结果是否真的送回了用户所在渠道。
|
|
272
|
+
|
|
273
|
+
因此它不是普通快递员,而是“快递员 + 运单系统 + 签收系统 + 异常回执系统”。
|
|
274
|
+
|
|
275
|
+
### 7.2 真正的问题不是能不能转发,而是能不能在断线后保持语义确定性
|
|
276
|
+
|
|
277
|
+
只要系统无法在断线后确定下面这五件事,就一定会出现“用户必须自己猜”的问题:
|
|
278
|
+
|
|
279
|
+
- 这条消息有没有被正式接单
|
|
280
|
+
- 这一轮有没有真正开始
|
|
281
|
+
- 这一轮有没有真正完成
|
|
282
|
+
- 结果有没有被中间层稳稳存住
|
|
283
|
+
- 结果有没有真正送达用户
|
|
284
|
+
|
|
285
|
+
本次 incident 证明,当前系统在第 3、4、5 件事上还没有足够强的确定性边界。
|
|
286
|
+
|
|
287
|
+
### 7.3 第一性原理结论
|
|
288
|
+
|
|
289
|
+
因此,用户的“快递员”比喻成立,但需要提升成下面这句更工程化的判断:
|
|
290
|
+
|
|
291
|
+
> 这不是一个简单的消息转发 bug,而是一个“中间层缺少回合级签收账本”的设计问题。
|
|
292
|
+
|
|
293
|
+
## 8. 现有设计的长处与边界
|
|
294
|
+
|
|
295
|
+
为了避免分析滑向“全部推倒重来”,这里也要把当前设计里已经正确的地方讲清楚。
|
|
296
|
+
|
|
297
|
+
### 8.1 已经做对的部分
|
|
298
|
+
|
|
299
|
+
当前系统并不是完全没有设计,而是已经建立了若干正确方向:
|
|
300
|
+
|
|
301
|
+
- 有独立的 `session store`,负责 conversation -> thread 的本地台账。
|
|
302
|
+
- 有稳定 `archive` 合同,能够保留用户可见原材料与系统事件。
|
|
303
|
+
- 有 `turn recovery attempt / required` 这类显式异常文案,而不是完全吞没故障。
|
|
304
|
+
- 已经把“连接恢复”和“任务恢复”区分成不同文案层级。
|
|
305
|
+
- 已经开始意识到 stale redelivery、middleware exception visibility、session presence 这些问题不能只靠日志解决。
|
|
306
|
+
|
|
307
|
+
这些方向都是对的,也与灯塔文档和若干 planning spec 保持一致。
|
|
308
|
+
|
|
309
|
+
### 8.2 当前边界为什么还不够
|
|
310
|
+
|
|
311
|
+
问题在于,现有这些能力还没有形成一个闭合的“回合交付合同”。
|
|
312
|
+
|
|
313
|
+
当前系统有:
|
|
314
|
+
|
|
315
|
+
- 会话台账
|
|
316
|
+
- 原材料归档
|
|
317
|
+
- 异常提示
|
|
318
|
+
|
|
319
|
+
但还缺:
|
|
320
|
+
|
|
321
|
+
- 一个以 `turn` 为中心的、持久可恢复的交付状态机
|
|
322
|
+
- 一个明确区分 `执行完成` 与 `用户已收到` 的持久账本
|
|
323
|
+
- 一个在断线后仍然能继续收敛到唯一真相,而不是转入猜测的恢复闭环
|
|
324
|
+
|
|
325
|
+
## 9. 提升为同类问题:中间层的设计层面缺口
|
|
326
|
+
|
|
327
|
+
如果不把这次事件只看成一个 bug,而是看成一类问题,它暴露的是下面三层设计缺口。
|
|
328
|
+
|
|
329
|
+
### 9.1 缺口 1:把“在线事件”误当成“权威事实”
|
|
330
|
+
|
|
331
|
+
`turn/completed`、`thread/status/changed` 这些事件非常有价值,但它们本质上是**流式通知**,不是天然的最终账本。
|
|
332
|
+
|
|
333
|
+
如果设计上把它们直接当成最终真相,就会在以下场景失稳:
|
|
334
|
+
|
|
335
|
+
- websocket 断开
|
|
336
|
+
- 事件延迟到达
|
|
337
|
+
- 事件乱序到达
|
|
338
|
+
- 用户在上一轮未决时又发了下一轮
|
|
339
|
+
|
|
340
|
+
### 9.2 缺口 2:把“运行中内存态”误当成“可恢复状态”
|
|
341
|
+
|
|
342
|
+
当前 `turnStates` 主要在内存里,进程断开或 socket 断开后,这些状态就先天脆弱。
|
|
343
|
+
|
|
344
|
+
这在单机 happy path 下没问题,但一旦进入“正在跑 + 突然断线 + 稍后恢复”的真实世界,就会暴露出运行态和恢复态不是同一回事。
|
|
345
|
+
|
|
346
|
+
### 9.3 缺口 3:中间层缺少面向用户承诺的交付合同
|
|
347
|
+
|
|
348
|
+
当前 bridge 对用户暴露的是“有时能恢复、有时请重发”的 bounded confirmation 语义。
|
|
349
|
+
|
|
350
|
+
这在 V1 是合理的,但如果目标是让用户把它当成稳定的远程办公入口,仅有 bounded confirmation 不够。还需要更明确地承诺:
|
|
351
|
+
|
|
352
|
+
- 什么叫“已收到”
|
|
353
|
+
- 什么叫“已接单”
|
|
354
|
+
- 什么叫“已完成但未送达”
|
|
355
|
+
- 什么叫“必须重发”
|
|
356
|
+
|
|
357
|
+
否则用户虽然看到了更多系统消息,但仍然无法拥有稳定预期。
|
|
358
|
+
|
|
359
|
+
## 10. 面向架构治理的整体方案
|
|
360
|
+
|
|
361
|
+
下面给出的是一个设计层面的整体方案,不是单点 patch 清单。
|
|
362
|
+
|
|
363
|
+
### 10.1 方案目标
|
|
364
|
+
|
|
365
|
+
把 `work-ally` 中间层从“事件驱动的消息桥”升级为“带回合账本的交付桥”,目标是:
|
|
366
|
+
|
|
367
|
+
1. 网络断开可以继续发生,但不再轻易造成语义不确定。
|
|
368
|
+
2. 用户不再需要通过猜测来判断上一轮是否完成。
|
|
369
|
+
3. bridge 可以明确区分:
|
|
370
|
+
- 执行是否完成
|
|
371
|
+
- 结果是否已持久化
|
|
372
|
+
- 结果是否已送达
|
|
373
|
+
4. 所有异常路径都能最终收敛到一个清晰状态,而不是悬空。
|
|
374
|
+
|
|
375
|
+
### 10.2 设计原则
|
|
376
|
+
|
|
377
|
+
#### 原则 A:事件是线索,账本才是真相
|
|
378
|
+
|
|
379
|
+
`turn/completed`、`thread/status/changed`、`thread/read` 都应被视为收敛账本的输入,而不是最终真相本身。
|
|
380
|
+
|
|
381
|
+
#### 原则 B:conversation、thread、turn、delivery 必须解耦
|
|
382
|
+
|
|
383
|
+
- `conversation`:用户在哪个外部窗口里说话
|
|
384
|
+
- `thread`:官方 runtime 的上下文连续性单元
|
|
385
|
+
- `turn`:一次具体执行
|
|
386
|
+
- `delivery`:某个 turn 的结果是否已成功送达用户
|
|
387
|
+
|
|
388
|
+
当前问题的关键之一就是把 thread idle 与某个具体 turn 完成混得太近。
|
|
389
|
+
|
|
390
|
+
#### 原则 C:断线后的默认状态不应是 failed,而应是 unknown / pending-recovery
|
|
391
|
+
|
|
392
|
+
如果系统还没有权威证据说明 turn 失败,就不应该为了尽快清理内存态而把它直接打成失败。
|
|
393
|
+
|
|
394
|
+
#### 原则 D:结果先持久化,再送达
|
|
395
|
+
|
|
396
|
+
只要 runtime 已经产出终态结果,中间层就应先把这份结果写入本地稳定账本,再尝试发往 Feishu。这样即使发信道再断,语义也只是“延迟送达”,不是“结果丢失”。
|
|
397
|
+
|
|
398
|
+
### 10.3 建议的回合账本模型
|
|
399
|
+
|
|
400
|
+
建议在现有 session store / archive 之上,再明确一层以 turn 为中心的持久账本。最小字段建议至少包括:
|
|
401
|
+
|
|
402
|
+
- `conversation_id`
|
|
403
|
+
- `thread_id`
|
|
404
|
+
- `turn_id`
|
|
405
|
+
- `source_message_id`
|
|
406
|
+
- `source_message_text_preview`
|
|
407
|
+
- `accepted_at`
|
|
408
|
+
- `started_at`
|
|
409
|
+
- `execution_status`
|
|
410
|
+
- `accepted`
|
|
411
|
+
- `running`
|
|
412
|
+
- `waiting_user`
|
|
413
|
+
- `unknown`
|
|
414
|
+
- `completed`
|
|
415
|
+
- `failed`
|
|
416
|
+
- `interrupted`
|
|
417
|
+
- `result_persisted_at`
|
|
418
|
+
- `delivery_status`
|
|
419
|
+
- `not_ready`
|
|
420
|
+
- `pending`
|
|
421
|
+
- `delivered`
|
|
422
|
+
- `delivery_failed`
|
|
423
|
+
- `delivery_attempts`
|
|
424
|
+
- `final_reply_excerpt`
|
|
425
|
+
- `final_error`
|
|
426
|
+
- `superseded_by_turn_id`(如会话已推进)
|
|
427
|
+
|
|
428
|
+
关键点不在字段多少,而在于把“执行状态”和“送达状态”拆开。
|
|
429
|
+
|
|
430
|
+
### 10.4 建议的状态流转
|
|
431
|
+
|
|
432
|
+
#### 正常路径
|
|
433
|
+
|
|
434
|
+
1. 用户消息进入 bridge -> `accepted`
|
|
435
|
+
2. runtime `turn/start` 成功 -> `running`
|
|
436
|
+
3. runtime 返回 approval / user input -> `waiting_user`
|
|
437
|
+
4. runtime 进入终态 -> `completed` 或 `failed`
|
|
438
|
+
5. 终态结果写本地账本 -> `result_persisted`
|
|
439
|
+
6. 结果送达 Feishu -> `delivery_status=delivered`
|
|
440
|
+
|
|
441
|
+
#### 断线路径
|
|
442
|
+
|
|
443
|
+
1. websocket 断开时,正在运行中的 turn 不直接改 `failed`
|
|
444
|
+
2. 改为 `unknown` 或 `pending_recovery`
|
|
445
|
+
3. 恢复后围绕这个 turn 持续收敛:
|
|
446
|
+
- 若补读到终态 -> 写终态与结果
|
|
447
|
+
- 若运行仍在继续 -> 回到 `running`
|
|
448
|
+
- 若在有界窗口后仍无法确认 -> 才转 `failed_unconfirmed` 或 `recovery_required`
|
|
449
|
+
|
|
450
|
+
#### 会话已前进的路径
|
|
451
|
+
|
|
452
|
+
如果用户已经开始下一轮,上一轮恢复出的结果不应再无条件弹给用户,而应进入明确分支:
|
|
453
|
+
|
|
454
|
+
- 如果上一轮结果尚未送达且仍对当前上下文有意义,可作为“迟到结果”挂到系统通知层,由产品规则决定是否回告
|
|
455
|
+
- 如果已被后续回合明确覆盖,应标记 `superseded`,写 archive,但不再在聊天中强行复活旧任务
|
|
456
|
+
|
|
457
|
+
### 10.5 对 runtime 协议的依赖边界
|
|
458
|
+
|
|
459
|
+
这类问题能否“彻底根治”,取决于上游 runtime 提供什么级别的协议保证。
|
|
460
|
+
|
|
461
|
+
#### 如果上游只提供当前级别能力
|
|
462
|
+
|
|
463
|
+
即:
|
|
464
|
+
|
|
465
|
+
- `turn/completed` 事件
|
|
466
|
+
- `thread/status/changed`
|
|
467
|
+
- `thread/read`
|
|
468
|
+
|
|
469
|
+
那么中间层仍然能做到很大程度的治理,但严格意义上的“数学级 exactly-once 交付”仍然做不到。因为系统始终可能遇到这样一个区间:
|
|
470
|
+
|
|
471
|
+
- runtime 已完成
|
|
472
|
+
- 完成事件未送达
|
|
473
|
+
- `thread/read` 在短窗口内也未包含足够信息
|
|
474
|
+
|
|
475
|
+
这种情况下,中间层只能做到“有界不确定 + 不丢证据 + 不误复活旧任务”,而不能凭空创造不存在的上游真相。
|
|
476
|
+
|
|
477
|
+
#### 如果上游能提供更强契约
|
|
478
|
+
|
|
479
|
+
例如:
|
|
480
|
+
|
|
481
|
+
- 按 `turn_id` 直接查询终态结果
|
|
482
|
+
- completed 事件可重放
|
|
483
|
+
- 结果存在稳定持久化 ID
|
|
484
|
+
- delivery independent result fetch
|
|
485
|
+
|
|
486
|
+
那么中间层就能把这类问题治理到用户基本无感。
|
|
487
|
+
|
|
488
|
+
### 10.6 面向 `work-ally` 的产品合同升级
|
|
489
|
+
|
|
490
|
+
基于上述分析,建议把用户可见合同升级为下面这套更稳定的语义。
|
|
491
|
+
|
|
492
|
+
#### 合同 1:已收到 != 已完成
|
|
493
|
+
|
|
494
|
+
中间层必须明确区分:
|
|
495
|
+
|
|
496
|
+
- 已收到
|
|
497
|
+
- 已开始执行
|
|
498
|
+
- 已完成但待送达
|
|
499
|
+
- 已完成并送达
|
|
500
|
+
|
|
501
|
+
#### 合同 2:连接恢复 != 任务恢复
|
|
502
|
+
|
|
503
|
+
这条合同当前文档里已经开始强调,但还需要在内部状态机上真正落实。
|
|
504
|
+
|
|
505
|
+
#### 合同 3:未确认完成前,不直接宣告失败
|
|
506
|
+
|
|
507
|
+
运行基础设施错误与业务执行失败必须分开。
|
|
508
|
+
|
|
509
|
+
#### 合同 4:结果已生成后,不因渠道抖动而丢失
|
|
510
|
+
|
|
511
|
+
只要结果进入中间层,就必须能重新投递或由 `/status` / 系统通知找回。
|
|
512
|
+
|
|
513
|
+
#### 合同 5:旧任务不应在会话已推进后被意外复活
|
|
514
|
+
|
|
515
|
+
这是 `docs/completed/SPEC-middleware-exception-visibility.md` 已经指出的 stale redelivery 问题,应与 turn recovery 一并治理。
|
|
516
|
+
|
|
517
|
+
## 11. 对“能否根治”的架构判断
|
|
518
|
+
|
|
519
|
+
### 11.1 哪一层不能根治
|
|
520
|
+
|
|
521
|
+
“网络永不断、进程永不崩、上游事件永不丢”这件事不能根治。
|
|
522
|
+
|
|
523
|
+
也就是说,传输层事故本身永远存在。
|
|
524
|
+
|
|
525
|
+
### 11.2 哪一层可以根治
|
|
526
|
+
|
|
527
|
+
“因为中间层设计不完善,导致用户必须靠猜来判断结果是否丢了”这件事是可以被根治的。
|
|
528
|
+
|
|
529
|
+
根治方式不是把所有事故消灭,而是把事故从“语义不确定”降级为“送达延迟”或“明确待确认”。
|
|
530
|
+
|
|
531
|
+
### 11.3 结论
|
|
532
|
+
|
|
533
|
+
因此,针对这类问题,最终判断应当是:
|
|
534
|
+
|
|
535
|
+
> 传输故障本身不能根治,但中间层把故障放大成用户语义混乱,这件事可以被根治。
|
|
536
|
+
|
|
537
|
+
前提是 `work-ally` 不再把自己当成“转发器”,而是明确把自己设计成“带回合账本的交付层”。
|
|
538
|
+
|
|
539
|
+
## 12. 对当前仓库的具体启示
|
|
540
|
+
|
|
541
|
+
本次 incident 对当前仓库至少给出六条明确启示:
|
|
542
|
+
|
|
543
|
+
1. `SPEC-runtime-connection-and-turn-recovery-semantics.md` 讨论的是 bounded confirmation,但还需要继续升级为更强的 turn delivery contract。
|
|
544
|
+
2. `docs/completed/SPEC-middleware-exception-visibility.md` 提到的 swallowed-risk、stale redelivery 不是旁支,而是同一类问题。
|
|
545
|
+
3. `SPEC-session-presence-and-state-visibility.md` 中“用户不能靠猜判断 assistant 在做什么”这一点,与 recovery 语义治理是同一主线。
|
|
546
|
+
4. `SPEC-stable-archive-contract.md` 已经提供了事实原材料层;下一步缺的是“从 archive / session / delivery 合同向上收敛真相”的那一层。
|
|
547
|
+
5. `session store` 现在更像 conversation 台账,还不是回合级交付账本。
|
|
548
|
+
6. 当前 `receiver` 与 `runtime-client` 的异常路径设计,仍然偏向“尽快结束内存状态”,而不是“持续收敛回合真相”。
|
|
549
|
+
|
|
550
|
+
## 13. 建议后续专题拆分
|
|
551
|
+
|
|
552
|
+
如果把这件事转成后续架构工作,建议至少拆成下面几个专题,而不是混成一个大而全的“修 recovery”。
|
|
553
|
+
|
|
554
|
+
### 专题 A:Turn Delivery Contract
|
|
555
|
+
|
|
556
|
+
定义:
|
|
557
|
+
|
|
558
|
+
- turn 的执行状态
|
|
559
|
+
- turn 的结果持久化状态
|
|
560
|
+
- turn 的用户送达状态
|
|
561
|
+
- stale / superseded 语义
|
|
562
|
+
|
|
563
|
+
### 专题 B:Runtime Recovery Truth Model
|
|
564
|
+
|
|
565
|
+
定义:
|
|
566
|
+
|
|
567
|
+
- 哪些 runtime 事件是线索
|
|
568
|
+
- 哪些状态是账本真相
|
|
569
|
+
- 哪些条件下可以从 `unknown` 收敛到 `completed` / `failed`
|
|
570
|
+
|
|
571
|
+
### 专题 C:User-visible Incident Semantics
|
|
572
|
+
|
|
573
|
+
定义:
|
|
574
|
+
|
|
575
|
+
- 什么情况下提示“继续等待”
|
|
576
|
+
- 什么情况下提示“结果已生成但尚未送达”
|
|
577
|
+
- 什么情况下提示“请重发上一条消息”
|
|
578
|
+
|
|
579
|
+
### 专题 D:Late Result / Stale Replay Policy
|
|
580
|
+
|
|
581
|
+
定义:
|
|
582
|
+
|
|
583
|
+
- 用户已经聊到后面时,旧结果是否还要回告
|
|
584
|
+
- 旧结果是否只进 archive
|
|
585
|
+
- `/status` 如何暴露迟到结果
|
|
586
|
+
|
|
587
|
+
## 14. 最终结论
|
|
588
|
+
|
|
589
|
+
这次 2026-03-14 21:47 的故障,表面上看是一次 runtime websocket 断开与 turn reconciliation 失败。
|
|
590
|
+
|
|
591
|
+
但从更深层看,它暴露的不是单点 bug,而是一个典型的中间层语义问题:
|
|
592
|
+
|
|
593
|
+
> 中间层当前还没有把“用户消息 -> runtime 执行 -> 结果持久化 -> 结果送达”建模成一笔可恢复的回合交易。
|
|
594
|
+
|
|
595
|
+
因此它在 happy path 下是一个桥,
|
|
596
|
+
但在异常路径下还不是一个足够强的交付层。
|
|
597
|
+
|
|
598
|
+
如果继续把它当成“消息桥”,这类问题只能不断被修补;
|
|
599
|
+
如果把它升级成“带回合账本的交付桥”,这类问题可以从用户视角被系统性治理。
|
|
600
|
+
|
|
601
|
+
一句话收束:
|
|
602
|
+
|
|
603
|
+
> 本次 incident 的根因不是“消息没转发过去”,而是“中间层没有掌握回合级确定性”;真正的长期解法不是补更多异常分支,而是补上这份确定性的账本与合同。
|