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.
Files changed (172) hide show
  1. package/AGENTS.md +110 -0
  2. package/DASHBOARD.md +160 -0
  3. package/PRODUCT.md +113 -0
  4. package/README.md +403 -0
  5. package/ally.sh +171 -0
  6. package/bridge/src/approval-rules.ts +360 -0
  7. package/bridge/src/channel-delivery.ts +207 -0
  8. package/bridge/src/channel-types.ts +22 -0
  9. package/bridge/src/channels/fake/adapter.ts +31 -0
  10. package/bridge/src/channels/feishu/adapter.ts +411 -0
  11. package/bridge/src/channels/feishu/approvals.ts +6 -0
  12. package/bridge/src/channels/feishu/formatter.ts +276 -0
  13. package/bridge/src/channels/feishu/normalize.ts +368 -0
  14. package/bridge/src/codex-config.ts +52 -0
  15. package/bridge/src/config.ts +240 -0
  16. package/bridge/src/fake-runtime-client.ts +505 -0
  17. package/bridge/src/handoff-service.ts +494 -0
  18. package/bridge/src/logger.ts +194 -0
  19. package/bridge/src/memory-digest.ts +186 -0
  20. package/bridge/src/receiver-approval-autonomy.ts +158 -0
  21. package/bridge/src/receiver-control-core.ts +140 -0
  22. package/bridge/src/receiver-control-work-session.ts +218 -0
  23. package/bridge/src/receiver-control.ts +83 -0
  24. package/bridge/src/receiver-delivery.ts +136 -0
  25. package/bridge/src/receiver-helpers.ts +96 -0
  26. package/bridge/src/receiver-human-gate.ts +333 -0
  27. package/bridge/src/receiver-inbound-preflight.ts +162 -0
  28. package/bridge/src/receiver-recovery.ts +236 -0
  29. package/bridge/src/receiver-runtime-callbacks.ts +367 -0
  30. package/bridge/src/receiver-runtime-policy.ts +132 -0
  31. package/bridge/src/receiver-runtime-state.ts +124 -0
  32. package/bridge/src/receiver-support-actions.ts +189 -0
  33. package/bridge/src/receiver-thread-start.ts +57 -0
  34. package/bridge/src/receiver-turn-coordination.ts +94 -0
  35. package/bridge/src/receiver-turn-execution.ts +257 -0
  36. package/bridge/src/receiver-turn-failure.ts +143 -0
  37. package/bridge/src/receiver-turn-result.ts +185 -0
  38. package/bridge/src/receiver-turn-steer.ts +70 -0
  39. package/bridge/src/receiver-work-session.ts +76 -0
  40. package/bridge/src/receiver.ts +329 -0
  41. package/bridge/src/router.ts +62 -0
  42. package/bridge/src/runtime-client-agent-messages.ts +150 -0
  43. package/bridge/src/runtime-client-message-dispatch.ts +176 -0
  44. package/bridge/src/runtime-client-protocol.ts +411 -0
  45. package/bridge/src/runtime-client-request-ops.ts +56 -0
  46. package/bridge/src/runtime-client-run-turn.ts +158 -0
  47. package/bridge/src/runtime-client-thread-ops.ts +270 -0
  48. package/bridge/src/runtime-client-transport.ts +309 -0
  49. package/bridge/src/runtime-client-turn-poll.ts +224 -0
  50. package/bridge/src/runtime-client-turn-read.ts +185 -0
  51. package/bridge/src/runtime-client-turn-state.ts +105 -0
  52. package/bridge/src/runtime-client.ts +344 -0
  53. package/bridge/src/runtime-user-input.ts +403 -0
  54. package/bridge/src/scheduler.ts +239 -0
  55. package/bridge/src/server-handoff-command.ts +364 -0
  56. package/bridge/src/server-main.ts +80 -0
  57. package/bridge/src/server-routine-command.ts +60 -0
  58. package/bridge/src/server-routine-execution.ts +222 -0
  59. package/bridge/src/server-runtime-app-support.ts +107 -0
  60. package/bridge/src/server-runtime-app.ts +238 -0
  61. package/bridge/src/server-thread-sync-command.ts +63 -0
  62. package/bridge/src/server.ts +17 -0
  63. package/bridge/src/session-store-delivery.ts +220 -0
  64. package/bridge/src/session-store-human-gate.ts +380 -0
  65. package/bridge/src/session-store-inbound-acceptance.ts +66 -0
  66. package/bridge/src/session-store-meta.ts +134 -0
  67. package/bridge/src/session-store-turn-ledger.ts +272 -0
  68. package/bridge/src/session-store.ts +380 -0
  69. package/bridge/src/system-notify.ts +220 -0
  70. package/bridge/src/thread-sync.ts +200 -0
  71. package/bridge/src/translator.ts +494 -0
  72. package/bridge/src/types.ts +289 -0
  73. package/bridge/src/utils.ts +104 -0
  74. package/bridge/src/work-session-store.ts +471 -0
  75. package/docs/.gitkeep +0 -0
  76. package/docs/architecture/codex-feishu-bridge-proposal.md +2742 -0
  77. package/docs/completed/FEATURE-feishu-markdown-and-reply-support.md +327 -0
  78. package/docs/completed/README.md +21 -0
  79. package/docs/completed/SPEC-approval-autonomy-and-safe-defaults.md +205 -0
  80. package/docs/completed/SPEC-approval-batch-and-strict-reply-shortcuts.md +153 -0
  81. package/docs/completed/SPEC-conversation-noise-reduction-and-busy-input-gate.md +538 -0
  82. package/docs/completed/SPEC-engineering-sop-skillization.md +190 -0
  83. package/docs/completed/SPEC-faithful-bridge-core-thinning-v2.md +376 -0
  84. package/docs/completed/SPEC-faithful-bridge-core-thinning.md +1071 -0
  85. package/docs/completed/SPEC-group-chat-sender-identity.md +301 -0
  86. package/docs/completed/SPEC-middleware-exception-visibility.md +227 -0
  87. package/docs/completed/SPEC-nightly-memory-digest-visibility.md +121 -0
  88. package/docs/completed/SPEC-project-group-chat-human-centered-conversation-mapping.md +326 -0
  89. package/docs/completed/SPEC-remove-cli-persona-bootstrap.md +201 -0
  90. package/docs/developer-workflow.md +49 -0
  91. package/docs/implementation/SPEC-codex-same-machine-session-handoff-implementation.md +239 -0
  92. package/docs/implementation/test-coverage-map.md +363 -0
  93. package/docs/implementation/work-ally-implementation-guide.md +790 -0
  94. package/docs/issues/README.md +10 -0
  95. package/docs/issues/pending/ANALYSIS-ally-premature-recovery-notice-and-task-state-semantics-2026-03-18.md +295 -0
  96. package/docs/issues/resolved/ANALYSIS-approval-waiting-visible-but-approval-artifact-missing-2026-03-16.md +466 -0
  97. package/docs/issues/resolved/ANALYSIS-blocking-state-visible-without-user-actionable-artifact-2026-03-16.md +261 -0
  98. package/docs/issues/resolved/ANALYSIS-codex-app-server-transport-disconnect-semantics-2026-03-14.md +606 -0
  99. package/docs/issues/resolved/ANALYSIS-premature-terminalization-on-fresh-thread-poll-and-object-error-leak-2026-03-16.md +348 -0
  100. package/docs/issues/resolved/ANALYSIS-runtime-turn-delivery-and-recovery-2026-03-14.md +603 -0
  101. package/docs/issues/resolved/ANALYSIS-self-test-gap-approval-waiting-visible-but-approval-artifact-missing-2026-03-16.md +166 -0
  102. package/docs/issues/resolved/ANALYSIS-self-test-gap-blocking-state-visible-without-user-actionable-artifact-2026-03-16.md +186 -0
  103. package/docs/issues/resolved/ANALYSIS-self-test-gap-premature-terminalization-on-fresh-thread-poll-and-object-error-leak-2026-03-16.md +166 -0
  104. package/docs/issues/resolved/REPORT-ally-runtime-turn-delivery-3b42fb8-2026-03-15.md +373 -0
  105. package/docs/manual-acceptance.md +127 -0
  106. package/docs/ops-runbook.md +44 -0
  107. package/docs/planning/FEATURE-memory-system.md +748 -0
  108. package/docs/planning/SPEC-active-turn-steer-and-context-compaction-visibility.md +269 -0
  109. package/docs/planning/SPEC-approval-rules-inheritance-and-local-validation-lane.md +450 -0
  110. package/docs/planning/SPEC-assistant-persona-bootstrap.md +199 -0
  111. package/docs/planning/SPEC-assistant-rename.md +610 -0
  112. package/docs/planning/SPEC-bridge-app-server-protocol-alignment.md +667 -0
  113. package/docs/planning/SPEC-claude-runtime-host-for-work-ally.md +434 -0
  114. package/docs/planning/SPEC-cli-feishu-codex-session-unification.md +236 -0
  115. package/docs/planning/SPEC-codex-same-machine-session-handoff.md +873 -0
  116. package/docs/planning/SPEC-feishu-reaction-shortcuts.md +282 -0
  117. package/docs/planning/SPEC-local-stable-release-boundary.md +166 -0
  118. package/docs/planning/SPEC-managed-thread-entry-and-surface-mobility.md +862 -0
  119. package/docs/planning/SPEC-minimal-bridge-semantics-and-user-visible-surface.md +362 -0
  120. package/docs/planning/SPEC-npm-alpha-distribution-and-install-first-release.md +222 -0
  121. package/docs/planning/SPEC-remove-websocket-runtime-transport.md +364 -0
  122. package/docs/planning/SPEC-runtime-abstraction-phase-1.md +424 -0
  123. package/docs/planning/SPEC-runtime-connection-and-turn-recovery-semantics.md +274 -0
  124. package/docs/planning/SPEC-session-presence-and-state-visibility.md +397 -0
  125. package/docs/planning/SPEC-skill-first-capability-packaging.md +338 -0
  126. package/docs/planning/SPEC-stable-archive-contract.md +456 -0
  127. package/docs/planning/SPEC-supervised-start-boundary.md +127 -0
  128. package/docs/planning/SPEC-user-barrier-reduction-and-activation.md +832 -0
  129. package/docs/planning/ally-next.md +1278 -0
  130. package/docs/planning/assistant-workbench-spec.md +725 -0
  131. package/docs/planning/product-workbench.md +283 -0
  132. package/docs/product-onboarding.md +227 -0
  133. package/docs/product-spec-standard.md +528 -0
  134. package/docs/troubleshooting.md +45 -0
  135. package/docs/user-quickstart.md +46 -0
  136. package/internal/dispatch.sh +95 -0
  137. package/internal/lib/common.sh +1450 -0
  138. package/internal/modules/assistant/manage.sh +1312 -0
  139. package/internal/modules/bootstrap/setup.sh +144 -0
  140. package/internal/modules/config/init-env.sh +10 -0
  141. package/internal/modules/global/manage.sh +154 -0
  142. package/internal/modules/handoff/manage.sh +54 -0
  143. package/internal/modules/mcp/manage.sh +83 -0
  144. package/internal/modules/ops/logs.sh +76 -0
  145. package/internal/modules/routines/manage.sh +55 -0
  146. package/internal/modules/runtime/assistant-autosave.sh +26 -0
  147. package/internal/modules/runtime/restart.sh +6 -0
  148. package/internal/modules/runtime/start.sh +283 -0
  149. package/internal/modules/runtime/status.sh +194 -0
  150. package/internal/modules/runtime/stop.sh +55 -0
  151. package/internal/modules/runtime/supervisor.sh +216 -0
  152. package/internal/modules/runtime/update.sh +26 -0
  153. package/package.json +41 -0
  154. package/runtime/config/.gitkeep +0 -0
  155. package/runtime/host/.gitkeep +0 -0
  156. package/runtime/host/healthcheck-codex-app-server.ts +22 -0
  157. package/runtime/host/ping-pong-codex-app-server.ts +66 -0
  158. package/runtime/host/probe-codex-app-server.ts +115 -0
  159. package/skills/archive-reader/SKILL.md +9 -0
  160. package/skills/feishu-production-debug/SKILL.md +37 -0
  161. package/skills/feishu-production-debug/references/feishu-debug-order.md +49 -0
  162. package/skills/feishu-production-debug/references/platform-permission-baseline.md +23 -0
  163. package/skills/issue-to-spec-triage/SKILL.md +44 -0
  164. package/skills/issue-to-spec-triage/references/triage-rules.md +66 -0
  165. package/skills/memory-digest/SKILL.md +9 -0
  166. package/skills/post-implementation-closure/SKILL.md +39 -0
  167. package/skills/post-implementation-closure/references/closure-checklist.md +45 -0
  168. package/skills/post-implementation-closure/references/doc-drift-map.md +49 -0
  169. package/skills/product-spec/SKILL.md +244 -0
  170. package/templates/env.example +5 -0
  171. package/templates/routines/nightly-memory-digest.yaml +10 -0
  172. package/templates/workspace/AGENTS.md +26 -0
@@ -0,0 +1,873 @@
1
+ # SPEC: Codex 同机双向会话接续
2
+
3
+ 更新时间:2026-03-18
4
+ 状态:已验收通过
5
+ Owner:work-ally product / engineering
6
+ 相关文档:
7
+ - `docs/planning/SPEC-runtime-abstraction-phase-1.md`
8
+ - `docs/planning/SPEC-bridge-app-server-protocol-alignment.md`
9
+ - `docs/planning/SPEC-runtime-connection-and-turn-recovery-semantics.md`
10
+ - `docs/planning/SPEC-remove-websocket-runtime-transport.md`
11
+ - `docs/implementation/SPEC-codex-same-machine-session-handoff-implementation.md`
12
+
13
+ 当前验收结论:
14
+
15
+ - 2026-03-18:本专题已通过产品验收;`/codex`、`handoff codex|attach`、linked / attached `/takeover` 主合同成立。
16
+ - 当前自动化与 shell 验证已成立;真实 Codex runtime smoke 仍受当前环境上游网络请求失败影响,因此不记为“真实链路全绿”。
17
+
18
+ ## 1. Summary
19
+
20
+ 这份 spec 要把 `work-ally` 从“单通道连续对话”升级成一种**同机双向无缝接续工作**能力。
21
+
22
+ 用户可以在同一台电脑上,把同一个 assistant、同一个 workspace、同一个 Codex 连续上下文,在这两类表面之间切换:
23
+
24
+ - `work-ally` 渠道侧(V1 以 Feishu 单聊为主)
25
+ - 官方 `Codex CLI` 本地侧
26
+
27
+ 核心目标不是“多一个兜底命令”,而是:
28
+
29
+ > 当工作场景切换时,用户不需要重新讲背景、不需要人工复制摘要、不需要新开一条假会话,而是继续同一条真实工作线程。
30
+
31
+ 这不是跨设备云同步,也不是多客户端同时写同一线程。V1 只解决**同一台机器上的双向接续**,并坚持**单活 ownership**。
32
+
33
+ ## 2. 第一性原理与核心判断
34
+
35
+ ### 2.1 用户真正要连续的是“工作”,不是“聊天窗口”
36
+
37
+ 用户不在意自己此刻是在桌面终端、桌面 app,还是手机里的 Feishu。
38
+
39
+ 用户真正在意的是:
40
+
41
+ - 还是同一个 assistant
42
+ - 还是同一个项目工作现场
43
+ - 还是同一条正在进行的 Codex 工作上下文
44
+ - 我换个表面以后,不要让我从头再解释一遍
45
+
46
+ 因此,本专题的主对象不是“导出命令”,而是**连续工作的会话本体**。
47
+
48
+ ### 2.2 `work-ally` 不应再造一个会话脑子
49
+
50
+ `work-ally` 的角色不是复制一份对话语义,更不是自建第二套 agent 会话引擎。
51
+
52
+ 正确边界是:
53
+
54
+ - 官方 Codex thread 是底层连续上下文真相
55
+ - `work-ally` 持有产品级映射、ownership、入口、绑定关系与审计账本
56
+ - 渠道只是接入面,不是会话主身份
57
+
58
+ ### 2.3 这件事的本质是“handoff”,不是“backup”
59
+
60
+ 旧心智把它理解为:当 `work-ally` 不稳定时,给用户一个命令行恢复出口。
61
+
62
+ 这个理解现在已经不够了。
63
+
64
+ 现在更准确的产品定义应是:
65
+
66
+ > `work-ally` 负责把“渠道工作”和“官方 Codex 本地工作”接成同一条连续工作流,并允许用户在同机双向切换。
67
+
68
+ ### 2.4 V1 必须明确坚持“单活”
69
+
70
+ 双向接续不等于双端并发协作。
71
+
72
+ 如果 Feishu 和官方 Codex 同时向同一条 thread 发起新 turn:
73
+
74
+ - 用户会失去因果理解
75
+ - bridge 会更难判断 pending request、delivery、stale suppression
76
+ - 产品可信度会显著下降
77
+
78
+ 所以 V1 必须坚持:
79
+
80
+ - 同一时刻只允许一个表面作为当前写入口
81
+ - 另一侧最多处于被动观察或等待接管状态
82
+
83
+ ### 2.5 V1 只承诺“同机”,不承诺“跨机”
84
+
85
+ 这份 spec 的连续性基础,是同一台机器上的:
86
+
87
+ - 同一个 assistant desk
88
+ - 同一个 `CODEX_HOME`
89
+ - 同一个 workspace root
90
+ - 同一份官方 Codex thread 存储边界
91
+
92
+ 跨机器迁移、云端统一会话托管、异地接续,不属于本期范围。
93
+
94
+ ## 3. 背景
95
+
96
+ 当前仓库已经具备一些关键基础:
97
+
98
+ - `work-ally` 已经接入官方 `codex app-server`
99
+ - 当前主路径已经围绕 `thread/start`、`thread/resume`、`thread/read(includeTurns=true)` 工作
100
+ - bridge 稳定性问题的主因,已经越来越清楚地收口到我们自己的协议理解和产品处理逻辑,而不再应简单归因于“官方不稳定”
101
+ - 现有渠道连续性已经可以在同一外部 conversation 中持续复用同一条 Codex thread
102
+
103
+ 但产品层仍有一个明显缺口:
104
+
105
+ - `work-ally` 内部连续性已经存在
106
+ - 官方 Codex 本地侧连续性也存在
107
+ - 但两者之间还没有被定义成同一个正式产品能力
108
+
109
+ 结果就是:
110
+
111
+ - 从 `work-ally` 去官方 Codex,仍像“导出一个恢复命令”
112
+ - 从官方 Codex 回到 `work-ally`,还没有正式、可审计、可验证的接续路径
113
+ - 团队容易把它做成补丁式功能,而不是稳定产品边界
114
+
115
+ ## 3.1 术语表
116
+
117
+ 为避免开发过程继续混用概念,本文术语固定如下:
118
+
119
+ - `assistant desk`
120
+ - 命名 assistant 的长期家目录,包含 desk 可见资产与 `.system/` 运行资产
121
+ - `delivery conversation`
122
+ - 外部聊天入口,例如一个 Feishu 单聊会话
123
+ - `runtime thread`
124
+ - 官方 Codex / App Server 持有的连续上下文对象
125
+ - `cli resume ref`
126
+ - 官方 `codex resume ...` 可直接消费的恢复句柄,可能是 `SESSION_ID`,也可能是 `thread name`
127
+ - `work_session`
128
+ - `work-ally` 自己持有的产品级连续工作对象,用于把 assistant、workspace、delivery conversation 与 runtime thread 绑定起来
129
+ - `surface`
130
+ - 用户当前工作的表面。V1 只有 `work_ally_channel` 与 `official_codex_cli`
131
+ - `owner`
132
+ - 当前被系统承认为“允许继续写入这条 work_session”的 surface
133
+ - `linked session`
134
+ - 已经存在正式 `work_session` 记录,并已与当前 assistant / delivery conversation 建立绑定的会话
135
+ - `unmanaged CLI thread`
136
+ - 在 assistant 对应 `CODEX_HOME` 中真实存在,但尚未被 `work-ally` 建立 `work_session` 记录的官方 Codex thread
137
+
138
+ 硬规则:
139
+
140
+ - 本文后续不再允许把 `delivery conversation`、`runtime thread`、`cli resume ref`、`work_session` 混称为“session”。
141
+ - 工程实现中的类型、注释、文档也应对齐这套术语,避免继续出现“session 其实指 thread / conversation / work session”的混写。
142
+
143
+ ## 4. 问题定义
144
+
145
+ 本专题要解决 5 个问题。
146
+
147
+ ### 4.1 当前“连续工作”的边界被渠道切断了
148
+
149
+ 用户今天能连续地在 Feishu 里聊下去,但不能把同一条工作线程自然切到官方 Codex,再自然切回来。
150
+
151
+ 这与产品目标不一致。
152
+
153
+ ### 4.2 旧的“backup/resume”心智太窄
154
+
155
+ 旧 spec 把需求理解成单向兜底:
156
+
157
+ - `work-ally` 出问题时
158
+ - 用户回到 CLI
159
+ - 继续同一个会话
160
+
161
+ 这会导致设计天然偏向:
162
+
163
+ - 单向导出
164
+ - 事故补救
165
+ - 不覆盖 Codex-originated 工作
166
+
167
+ 而现在真正要做的是:
168
+
169
+ - 正常工作场景下的双向接续
170
+ - `work-ally` 与官方 Codex 作为同一连续工作流的两个表面
171
+
172
+ ### 4.3 当前缺少一个正式的产品级会话对象来承载 handoff
173
+
174
+ 如果没有产品级会话对象,系统就很容易继续把这些对象混在一起:
175
+
176
+ - Feishu conversation
177
+ - bridge 当前 session
178
+ - Codex runtime thread
179
+ - CLI resume 句柄
180
+
181
+ 这会直接造成:
182
+
183
+ - 实现边界模糊
184
+ - 命名不稳定
185
+ - 开发者继续 freestyle
186
+
187
+ ### 4.4 当前没有被讲清楚的 ownership 规则
188
+
189
+ 如果 spec 不明确规定“哪一侧当前能写”,开发实现很容易走向两种错误:
190
+
191
+ - 静默双写
192
+ - 静默抢占
193
+
194
+ 这两种都会让用户失去对系统真相的信任。
195
+
196
+ ### 4.5 当前没有覆盖官方 Codex 发起场景
197
+
198
+ 如果用户先在官方 `Codex CLI` 里工作,再想切回手机侧继续,系统目前没有正式的 attach/import 模型。
199
+
200
+ 这意味着“官方 Codex -> work-ally”这个方向仍然是不完整的。
201
+
202
+ ## 5. 目标
203
+
204
+ 本 feature 必须达成以下目标:
205
+
206
+ 1. 让用户可以从 `work-ally` 渠道侧切到官方 `Codex CLI`,继续同一条真实工作线程。
207
+ 2. 让用户可以从官方 `Codex CLI` 切回 `work-ally` 渠道侧,继续同一条真实工作线程。
208
+ 3. 让 `work-ally` 持有正式的产品级会话对象,用来管理 handoff,而不是继续拿外部 conversation 或 runtime 临时状态凑合。
209
+ 4. 在产品层明确 single-active ownership,避免双写和静默抢占。
210
+ 5. 明确区分 runtime thread identity 与 CLI 可恢复句柄,禁止实现层偷懒把它们写成一个概念。
211
+ 6. 对由官方 Codex 直接启动、但尚未纳入 `work-ally` 管理的线程,提供一次性本地 attach/import 路径。
212
+ 7. 给出清晰、可测、不会让开发者靠猜的主路径、拒绝路径和完成标准。
213
+
214
+ ## 5.1 产品成功标准
215
+
216
+ 这件事完成后,产品层要成立的不是“多了几个命令”,而是下面 4 条用户体验事实:
217
+
218
+ 1. 用户从 Feishu 切到官方 Codex 时,不需要重新解释上下文,也不会被系统偷偷开新会话。
219
+ 2. 用户从官方 Codex 切回 Feishu 时,能继续同一条真实工作线程,而不是一条摘要复制后的伪连续对话。
220
+ 3. 当系统无法安全接续时,会明确拒绝并说明原因,而不是猜测、隐式 attach、隐式抢占。
221
+ 4. 开发者和维护者能稳定回答“当前哪条 work_session 在工作、它归谁写、为什么现在不能接过去”。
222
+
223
+ ## 6. 非目标
224
+
225
+ 本期明确不做:
226
+
227
+ 1. 不做跨机器、跨设备的 thread 迁移或云同步。
228
+ 2. 不做 Feishu 与官方 Codex 的并发双端协作。
229
+ 3. 不承诺所有表面都实时镜像完整转录。
230
+ 4. 不把 `work-ally` 做成官方 Codex 会话存储的替代品。
231
+ 5. 不在 V1 支持群聊里的 `/codex`、`/takeover`。
232
+ 6. 不支持自动接管任意历史 Codex thread;涉及歧义时必须显式 attach。
233
+ 7. 不把 VS Code extension、Codex desktop app 一起纳入 V1 正式合同。
234
+
235
+ 说明:
236
+
237
+ - V1 的官方本地表面,以 `Codex CLI` 作为唯一正式支持对象。
238
+ - 未来如果要扩到其他官方表面,应在本 spec 基础上加专题,不要在本期偷扩 scope。
239
+
240
+ ## 6.1 术语表
241
+
242
+ 为避免开发过程继续混用概念,本文术语固定如下:
243
+
244
+ - `assistant desk`
245
+ - 命名 assistant 的长期家目录,包含 desk 可见资产与 `.system/` 运行资产。
246
+ - `delivery conversation`
247
+ - 外部聊天入口,例如一个 Feishu 单聊会话。
248
+ - `runtime thread`
249
+ - 官方 Codex / App Server 持有的连续上下文对象。
250
+ - `cli resume ref`
251
+ - 官方 `codex resume ...` 可直接消费的恢复句柄,可能是 `SESSION_ID`,也可能是 `thread name`。
252
+ - `work_session`
253
+ - `work-ally` 自己持有的产品级连续工作对象,用于把 assistant、workspace、delivery conversation 与 runtime thread 绑定起来。
254
+ - `surface`
255
+ - 用户当前工作的表面。V1 只有 `work_ally_channel` 与 `official_codex_cli`。
256
+ - `owner`
257
+ - 当前被系统承认为“允许继续写入这条 work_session”的 surface。
258
+ - `linked session`
259
+ - 已经存在正式 `work_session` 记录,并已与当前 assistant / delivery conversation 建立绑定的会话。
260
+ - `unmanaged CLI thread`
261
+ - 在 assistant 对应 `CODEX_HOME` 中真实存在,但尚未被 `work-ally` 建立 `work_session` 记录的官方 Codex thread。
262
+
263
+ 硬规则:
264
+
265
+ - 本文后续不再允许把 `delivery conversation`、`runtime thread`、`cli resume ref`、`work_session` 混称为“session”。
266
+ - 工程实现中的类型、注释、文档也应对齐这套术语,避免继续出现“session 其实指 thread / conversation / work session”的混写。
267
+
268
+ ## 7. 产品定义与用户路径
269
+
270
+ ### 7.1 一句话产品定义
271
+
272
+ `Codex 同机双向会话接续` 是一种产品能力:
273
+
274
+ > 在同一台机器上,把 `work-ally` 渠道侧与官方 `Codex CLI` 接成同一条连续工作流,用户可以双向切换而不重建上下文。
275
+
276
+ ### 7.2 V1 正式支持的两个表面
277
+
278
+ #### A. `work_ally_channel`
279
+
280
+ V1 以 Feishu 单聊为主。
281
+
282
+ 用户通过命名 assistant 的常规聊天入口与 `/codex`、`/takeover`、`/new` 等命令触发连续性能力。
283
+
284
+ #### B. `official_codex_cli`
285
+
286
+ V1 只正式支持官方 `Codex CLI`。
287
+
288
+ 原因:
289
+
290
+ - 它已有稳定 `resume` 能力
291
+ - 它是同机场景中最确定、最可验证、最容易自动化测试的官方表面
292
+ - 它与当前 `work-ally` 的本地运行边界天然一致
293
+
294
+ ### 7.2.1 为什么 V1 不支持其他官方表面
295
+
296
+ 这次不要让开发者自行扩大范围。
297
+
298
+ V1 不把以下对象纳入正式合同:
299
+
300
+ - Codex desktop app
301
+ - VS Code 扩展
302
+ - 任何第三方 shell 包装层
303
+
304
+ 原因不是它们永远不支持,而是:
305
+
306
+ - 当前 spec 的关键边界在于 same-machine handoff、ownership、attach、resume ref 与可自动化验证。
307
+ - `Codex CLI` 是最小且最确定的官方本地表面。
308
+ - 如果把其他表面一起拉进来,开发者很容易提前引入额外存储假设、额外入口判断与额外 UI 语义。
309
+
310
+ 结论:
311
+
312
+ - V1 代码命名允许保留扩展空间。
313
+ - 但产品语义、测试与验收只对 `official_codex_cli` 成立。
314
+
315
+ ### 7.3 主路径 A:`work-ally -> Codex CLI`
316
+
317
+ 场景:
318
+
319
+ - 用户原本在 Feishu 或 `work-ally` 侧工作
320
+ - 现在想回到本机官方 Codex 继续
321
+
322
+ 路径:
323
+
324
+ 1. 用户在单聊中发送 `/codex`,或在本机执行 `./ally.sh handoff codex <assistant>`。
325
+ 2. 系统找到当前 assistant 对应的 active `work_session`。
326
+ 3. 系统返回**精确可执行**的官方 Codex 接续方式:
327
+ - 优先是可直接执行的 `codex resume` 命令
328
+ - 若当前只能打印 handoff 元信息,也必须明确说明为什么还不能 resume
329
+ 4. 用户在本机进入官方 `Codex CLI` 后,继续同一条 runtime thread。
330
+ 5. 此后该 `work_session` 的 owner 可切到 `official_codex_cli`。
331
+
332
+ 关键约束:
333
+
334
+ - `/codex` 是 handoff 出口,不是开新会话。
335
+ - 如果当前没有可恢复句柄,系统必须明确返回 `not_ready`,不能拿不确定 id 硬凑。
336
+
337
+ ### 7.4 主路径 B:`Codex CLI -> work-ally`(已链接会话)
338
+
339
+ 场景:
340
+
341
+ - 当前线程原本就属于 `work-ally` 管理下的 `work_session`
342
+ - 用户中途切到官方 `Codex CLI` 继续工作
343
+ - 之后想回到手机侧继续
344
+
345
+ 路径:
346
+
347
+ 1. 用户在 Feishu 单聊里发送 `/takeover`。
348
+ 2. 系统找到当前 assistant 已链接的 active `work_session`。
349
+ 3. 系统用该 `work_session` 的 `runtimeThreadId` 继续回到同一条 Codex thread。
350
+ 4. 该 `work_session` 的 owner 切回 `work_ally_channel`。
351
+ 5. 后续普通自然语言继续沿这条 thread 工作。
352
+
353
+ 关键约束:
354
+
355
+ - `/takeover` 是“把当前已链接工作线程接回这里”,不是新建 thread。
356
+ - 如果没有已链接 `work_session`,必须明确失败原因,而不是偷偷 `/new`。
357
+
358
+ ### 7.5 主路径 C:`Codex CLI -> work-ally`(未链接线程,先 attach)
359
+
360
+ 场景:
361
+
362
+ - 用户先直接在官方 `Codex CLI` 里开工
363
+ - 这条 thread 还没有被 `work-ally` 纳入管理
364
+ - 之后用户希望手机侧也能继续
365
+
366
+ 路径:
367
+
368
+ 1. 用户在本机执行 `./ally.sh handoff attach <assistant> --last`,或显式指定 `--thread <runtimeThreadId>` / `--resume-ref <cliResumeRef>`。
369
+ 2. 系统在该 assistant desk 的 `CODEX_HOME` 边界内,发现候选官方 Codex thread。
370
+ 3. 如果候选唯一且 workspace 匹配,系统创建新的 `work_session`,并把它绑定到这个 assistant 的渠道连续性入口。
371
+ 4. 之后用户可在 Feishu 中发送 `/takeover`,继续同一条 thread。
372
+
373
+ 关键约束:
374
+
375
+ - `attach` 是显式本地动作。
376
+ - 如果候选不唯一,系统必须拒绝并要求用户明确指定,不能猜。
377
+
378
+ ### 7.6 主路径 D:`/new`
379
+
380
+ 场景:
381
+
382
+ - 用户不想继续当前线程,而是要开一条新的工作上下文
383
+
384
+ 路径:
385
+
386
+ 1. 用户在 `work-ally` 侧执行 `/new`。
387
+ 2. 系统创建新的 `work_session` 与新的 runtime thread。
388
+ 3. 旧 `work_session` 保留为历史,不被覆盖。
389
+
390
+ ### 7.7 拒绝路径
391
+
392
+ V1 至少要明确以下拒绝行为:
393
+
394
+ 1. 群聊内的 `/codex`、`/takeover` 直接拒绝。
395
+ 2. 当前 owner 在 `official_codex_cli` 时,用户在渠道里发普通文本,不得静默抢占;系统应提示先 `/takeover` 或 `/new`。
396
+ 3. `attach --last` 命中多个候选 thread 时,直接拒绝,要求显式指定。
397
+ 4. workspace 不匹配时,拒绝 attach。
398
+ 5. `cliResumeRef` 尚未可用时,`/codex` 返回 `not_ready`,不得拿 `runtimeThreadId` 假冒官方 CLI resume 句柄。
399
+ 6. 当前 assistant 没有 active `work_session` 时,`/codex` 明确返回“当前没有可接续的工作线程”,不得隐式创建。
400
+ 7. `/takeover` 发现 linked `work_session` 已归 `work_ally_channel` 所有时,应直接返回“当前已在这里继续”,而不是重复执行状态切换。
401
+
402
+ ## 8. 机制设计
403
+
404
+ ### 8.1 新的产品对象:`work_session`
405
+
406
+ V1 引入正式产品对象 `work_session`。
407
+
408
+ 定义:
409
+
410
+ > 一条由 `work-ally` 管理、绑定单个 assistant desk 与单个 workspace、映射到单条官方 Codex runtime thread、并可在不同表面之间 handoff 的连续工作单元。
411
+
412
+ ### 8.2 关系模型
413
+
414
+ - 一个 assistant 可以有多条历史 `work_session`
415
+ - V1 中,一条 `work_session` 对应一条 Codex `runtime_thread`
416
+ - 一个 Feishu 单聊入口在任一时刻只指向一条当前 active `work_session`
417
+ - 一条 `work_session` 在任一时刻只允许一个写表面 owner
418
+
419
+ 额外约束:
420
+
421
+ - 一个 `work_session` 只绑定一个 assistant,不能在 assistant 之间横向复用。
422
+ - 一个 `work_session` 创建后,`assistantCodexHome` 与 `workspaceRoot` 视为不可变主绑定。
423
+ - 若用户换项目或换 assistant,正确动作是 `/new` 或新的 attach,而不是改写旧 `work_session`。
424
+
425
+ ### 8.3 `work_session` 最小字段
426
+
427
+ 每条 `work_session` 至少包含:
428
+
429
+ - `workSessionId`
430
+ - `assistantName`
431
+ - `assistantCodexHome`
432
+ - `workspaceRoot`
433
+ - `runtime`
434
+ - 固定为 `codex`
435
+ - `runtimeThreadId`
436
+ - `cliResumeRef`
437
+ - 可空
438
+ - `cliResumeRefType`
439
+ - `session_id` / `thread_name` / `unknown` / `null`
440
+ - `threadName`
441
+ - 可空
442
+ - `origin`
443
+ - `work_ally` / `codex_cli_attach`
444
+ - `deliveryChannel`
445
+ - 例如 `feishu`
446
+ - `deliveryConversationKey`
447
+ - 可空;用于渠道绑定
448
+ - `activeSurface`
449
+ - `work_ally_channel` / `official_codex_cli` / `idle` / `closed`
450
+ - `ownershipSource`
451
+ - `explicit_channel_takeover` / `explicit_codex_launch` / `observed_external_activity` / `manual_attach` / `new_session`
452
+ - `resumeCapability`
453
+ - `ready` / `not_ready`
454
+ - `lastRuntimeActivityAt`
455
+ - `lastSurfaceSwitchAt`
456
+ - `createdAt`
457
+ - `updatedAt`
458
+ - `archivedAt`
459
+ - 可空
460
+
461
+ 补充字段约束:
462
+
463
+ - `assistantName`、`assistantCodexHome`、`workspaceRoot`、`runtime`、`runtimeThreadId` 一经创建后不可被普通更新流程改写。
464
+ - `deliveryConversationKey` 可空,但一旦绑定后,除非显式迁移专题,不允许在 V1 中被静默改绑到另一条 delivery conversation。
465
+ - `activeSurface` 与 `ownershipSource` 必须始终成对更新。
466
+ - `resumeCapability=ready` 的前提是 `cliResumeRef` 非空且可直接导出给用户。
467
+
468
+ 建议但非强制字段:
469
+
470
+ - `lastTurnId`
471
+ - `lastPromptPreview`
472
+ - `lastReplyPreview`
473
+ - `lastHandoffExportAt`
474
+ - `lastAttachAt`
475
+
476
+ ### 8.4 `runtimeThreadId` 与 `cliResumeRef` 必须逻辑分离
477
+
478
+ 这是本 spec 的硬边界。
479
+
480
+ 两者分别表示:
481
+
482
+ - `runtimeThreadId`
483
+ - 给 App Server 的 `thread/read`、`thread/resume`、`turn/start` 使用
484
+ - `cliResumeRef`
485
+ - 给用户执行 `codex resume ...` 使用
486
+
487
+ 禁止实现层做这几件事:
488
+
489
+ - 默认假设两者永远相同
490
+ - 在 `cliResumeRef` 缺失时,直接拿 `runtimeThreadId` 冒充
491
+ - 把 CLI resume 能力是否可用,写成“只要有 thread id 就行”
492
+
493
+ 如果当前官方能力只能稳定拿到其中之一,系统必须把“不知道”写清楚,而不是硬补推断。
494
+
495
+ ### 8.5 ownership 规则
496
+
497
+ V1 采用 single-active ownership。
498
+
499
+ 规则如下:
500
+
501
+ 1. 任一时刻,`work_session.activeSurface` 只能有一个当前 owner。
502
+ 2. 当 owner 为 `work_ally_channel` 时:
503
+ - 渠道侧可以正常发起新 turn
504
+ - 官方 Codex 侧若继续输入,系统无法强行阻止,但会在后续观测到该行为时把 owner 更新为 `official_codex_cli`
505
+ 3. 当 owner 为 `official_codex_cli` 时:
506
+ - `work-ally` 不得静默代用户继续发起普通文本 turn
507
+ - 用户若要切回渠道侧,必须显式 `/takeover`
508
+ 4. `/new` 永远创建新 `work_session`,不接管旧线程。
509
+
510
+ 说明:
511
+
512
+ - `work-ally` 只能约束自己,不可能从产品外部阻止用户在官方 CLI 里继续输入。
513
+ - 因此 V1 的正确策略是:**严管自己,不伪造并发安全。**
514
+
515
+ ### 8.5.1 ownership 状态解释
516
+
517
+ 为防止实现层把 `activeSurface` 当作随便写的标签,V1 明确规定:
518
+
519
+ - `work_ally_channel`
520
+ - 当前允许由渠道侧继续发起 turn。
521
+ - `official_codex_cli`
522
+ - 当前系统认为这条 `work_session` 的主动写入口在官方 CLI。
523
+ - `idle`
524
+ - 当前没有明确 owner,通常只允许显式 `/takeover` 或显式本机 handoff 命令推进,不允许普通渠道文本直接抢占。
525
+ - `closed`
526
+ - 该 `work_session` 已封存,不再承接新 turn。
527
+
528
+ V1 建议:
529
+
530
+ - 新建 `work_session` 后默认进入 `work_ally_channel`。
531
+ - attach 成功后的 imported `work_session` 默认进入 `official_codex_cli`。
532
+ - 不要为了省事完全不使用 `idle`;当系统确实无法安全断言当前 owner 时,宁可落到 `idle` 也不要乱猜。
533
+
534
+ ### 8.6 owner 切换条件
535
+
536
+ 以下动作会改变 `activeSurface`:
537
+
538
+ 1. `work-ally` 侧 `/takeover` 成功:
539
+ - 切为 `work_ally_channel`
540
+ 2. `work-ally` 侧 `/new`:
541
+ - 新建 `work_session`
542
+ - 新会话 owner 为 `work_ally_channel`
543
+ 3. 本机执行 `./ally.sh handoff codex <assistant> --open`:
544
+ - 进入官方 Codex 启动路径
545
+ - owner 可立即切为 `official_codex_cli`
546
+ 4. 已导出 handoff 句柄后,若系统观测到 linked thread 出现外部活动:
547
+ - owner 可切为 `official_codex_cli`
548
+ - `ownershipSource=observed_external_activity`
549
+ 5. 本机执行 `./ally.sh handoff attach ...`:
550
+ - attach 完成后,该会话初始 owner 为 `official_codex_cli`
551
+
552
+ 不允许作为 owner 切换依据的事件:
553
+
554
+ - 单纯收到 delivery conversation 的一条普通文本
555
+ - 单次 transport reconnect
556
+ - 单次 `thread/read` 成功
557
+ - 开发者主观推断“用户现在大概率想切回来了”
558
+
559
+ ### 8.7 渠道侧命令合同
560
+
561
+ V1 新增并收口以下语义:
562
+
563
+ #### `/codex`
564
+
565
+ 作用:
566
+
567
+ - 导出当前 `work_session` 的官方 Codex handoff 方式
568
+
569
+ 规则:
570
+
571
+ - 仅单聊支持
572
+ - 若 `resumeCapability=ready`,返回精确 handoff 命令
573
+ - 若 `resumeCapability=not_ready`,返回明确原因
574
+ - 不创建新 session
575
+ - 不自动切 owner,除非后续检测到本地官方 Codex 活动
576
+
577
+ 用户文案要求:
578
+
579
+ - 若成功,必须让用户一眼看出“这是继续当前工作线程,不是新开会话”。
580
+ - 若失败,必须明确失败属于哪一类:
581
+ - 没有 active `work_session`
582
+ - 当前 work_session 尚未具备 CLI 可恢复句柄
583
+ - 当前入口不允许导出
584
+
585
+ #### `/takeover`
586
+
587
+ 作用:
588
+
589
+ - 把当前已链接 `work_session` 接回当前渠道侧
590
+
591
+ 规则:
592
+
593
+ - 仅单聊支持
594
+ - 若存在 linked active `work_session`,则继续同一 runtime thread
595
+ - 若不存在 linked session,但 assistant 有可 attach 的 unmanaged CLI thread,不自动猜;提示先在本机执行 `ally.sh handoff attach`
596
+ - 不隐式 `/new`
597
+
598
+ 用户文案要求:
599
+
600
+ - 若成功,必须明确提示“已接回当前工作线程”。
601
+ - 若失败,必须区分:
602
+ - 没有 linked `work_session`
603
+ - 有 unmanaged CLI thread,但尚未 attach
604
+ - 当前会话已是 `work_ally_channel`
605
+
606
+ #### `/new`
607
+
608
+ 作用:
609
+
610
+ - 新开一条工作上下文
611
+
612
+ 规则:
613
+
614
+ - 永远创建新的 `work_session`
615
+ - 不覆盖旧 `work_session`
616
+
617
+ ### 8.8 本机命令入口合同
618
+
619
+ 为保证产品用户入口仍统一在 `./ally.sh`,V1 增加本机 handoff 子命令。
620
+
621
+ 建议命令面:
622
+
623
+ #### `./ally.sh handoff codex <assistant>`
624
+
625
+ 职责:
626
+
627
+ - 读取当前 assistant 的 active `work_session`
628
+ - 输出精确的官方 `codex resume` 方式
629
+
630
+ 可扩展模式:
631
+
632
+ - `--print`
633
+ - 只打印命令
634
+ - `--open`
635
+ - 直接以该命令进入官方 Codex,本地 owner 立即切换为 `official_codex_cli`
636
+
637
+ 实现要求:
638
+
639
+ - `--print` 与默认输出的结构必须稳定,便于后续 shell 测试和桌面端复用。
640
+ - 如果未来桌面端要加“Continue in Codex”按钮,应调用同一后端能力,而不是复制命令拼接逻辑。
641
+
642
+ #### `./ally.sh handoff attach <assistant>`
643
+
644
+ 职责:
645
+
646
+ - 把官方 `Codex CLI` 直接启动的一条 thread 纳入 `work-ally` 管理
647
+
648
+ 建议参数:
649
+
650
+ - `--last`
651
+ - `--thread <runtimeThreadId>`
652
+ - `--resume-ref <cliResumeRef>`
653
+
654
+ 规则:
655
+
656
+ - 若 `--last` 命中多个候选,必须拒绝
657
+ - attach 后创建正式 `work_session`
658
+
659
+ 实现要求:
660
+
661
+ - attach 结果必须可机读,便于后续脚本、桌面端或测试消费。
662
+ - attach 失败时要返回稳定错误类型,而不是只打印自由文案。
663
+
664
+ ### 8.9 unmanaged CLI thread 的 attach 规则
665
+
666
+ attach 过程至少满足:
667
+
668
+ 1. 只在目标 assistant 对应的 `CODEX_HOME` 边界内发现候选线程。
669
+ 2. 候选线程必须与 assistant 当前 workspace root 精确匹配。
670
+ 3. `--last` 只能在候选唯一时成立。
671
+ 4. attach 成功后,要生成正式 `work_session` 记录,而不是只改一条临时指针。
672
+ 5. attach 只是把线程纳管,不代表立即切回渠道侧;切回渠道仍由 `/takeover` 完成。
673
+
674
+ V1 不允许的 attach 简化:
675
+
676
+ - 不允许扫描全局所有 `CODEX_HOME` 后拿最近线程直接绑到当前 assistant。
677
+ - 不允许只按“最近修改时间”就忽略 workspace 校验。
678
+ - 不允许 attach 成功后自动替用户把 delivery conversation 改绑到一条完全不同的 assistant 会话。
679
+
680
+ ### 8.10 work-ally 对官方 Codex 活动的观察边界
681
+
682
+ V1 必须支持“知道 linked thread 还在同一条线上”,但不要求做完整 live transcript 镜像。
683
+
684
+ 因此 V1 只要求:
685
+
686
+ - 能在 handoff 后继续识别同一条 runtime thread
687
+ - 能在 `/takeover` 时继续回到同一条 runtime thread
688
+ - 能基于 thread 真相更新 ownership 与基本状态
689
+
690
+ V1 不要求:
691
+
692
+ - 把官方 CLI 里发生的每一轮输出实时镜像回 Feishu
693
+ - 把所有历史 turn 逐条补写回渠道档案
694
+
695
+ ### 8.11 持久化建议
696
+
697
+ 本专题不能继续只挂在 `conversationRef` 下。
698
+
699
+ 建议新增稳定目录:
700
+
701
+ - `.system/runtime/work-sessions/<workSessionId>/meta.json`
702
+
703
+ 并新增最小索引:
704
+
705
+ - `.system/runtime/work-sessions/by-assistant/<assistantName>.json`
706
+ - `.system/runtime/work-sessions/by-delivery/<channel>--<conversationKey>.json`
707
+
708
+ 最小要求:
709
+
710
+ - 每条 `work_session` 有独立 durable meta
711
+ - assistant 当前指向哪条 `work_session` 可单独查询
712
+ - delivery conversation 当前指向哪条 `work_session` 可单独查询
713
+
714
+ ### 8.12 真相源边界
715
+
716
+ 本专题的真相源分工必须明确:
717
+
718
+ #### 官方 Codex / App Server 持有
719
+
720
+ - runtime thread 真正连续性
721
+ - thread 生命周期
722
+ - turn 历史与 runtime 状态
723
+
724
+ #### `work-ally` 持有
725
+
726
+ - `work_session` 产品对象
727
+ - assistant / workspace / delivery conversation 绑定
728
+ - owner / handoff 状态
729
+ - attach 与导出可用性
730
+ - 审计与用户入口
731
+
732
+ ## 9. 明确禁止的实现方向
733
+
734
+ 为了避免实现偏航,本专题明确禁止以下做法:
735
+
736
+ 1. 禁止把“同一条工作线程”实现成“复制摘要到另一边再新开会话”。
737
+ 2. 禁止继续把外部 Feishu conversation id 当作连续工作会话的主键。
738
+ 3. 禁止在 `cliResumeRef` 不确定时,偷用 `runtimeThreadId` 冒充官方 CLI resume 句柄。
739
+ 4. 禁止在 owner 为 `official_codex_cli` 时,渠道普通文本静默抢占会话。
740
+ 5. 禁止自动 attach 多个候选中的“看起来最像”的官方 CLI thread。
741
+ 6. 禁止把 V1 做成“Feishu 与官方 Codex 同时在线双写同一线程”的产品承诺。
742
+ 7. 禁止把“实时镜像完整 transcript”错误提升为本专题的必交项。
743
+
744
+ ## 10. 验收标准
745
+
746
+ 满足以下条件,才算完成。
747
+
748
+ ### 10.1 `work-ally -> Codex CLI`
749
+
750
+ 1. 当 assistant 当前存在 active `work_session` 且 `resumeCapability=ready` 时,`/codex` 与 `./ally.sh handoff codex <assistant>` 都能返回精确可执行的官方接续方式。
751
+ 2. 该接续方式必须显式包含正确的 assistant `CODEX_HOME` 与 `workspaceRoot` 语义。
752
+ 3. 当 `resumeCapability=not_ready` 时,系统返回明确原因,不猜测、不硬补。
753
+
754
+ ### 10.2 `Codex CLI -> work-ally`(已链接)
755
+
756
+ 1. 对一条原本由 `work-ally` 创建的 `work_session`,用户切到官方 `Codex CLI` 工作后,再在 Feishu 单聊中执行 `/takeover`,系统会继续同一条 `runtimeThreadId`。
757
+ 2. `/takeover` 之后,后续普通渠道输入继续进入同一条 thread,而不是新建 thread。
758
+
759
+ ### 10.3 `Codex CLI -> work-ally`(attach)
760
+
761
+ 1. 对一条先由官方 `Codex CLI` 直接创建、但尚未纳管的线程,`./ally.sh handoff attach <assistant> --last` 在候选唯一时可成功创建正式 `work_session`。
762
+ 2. 若候选不唯一,attach 失败并要求显式指定,不允许猜。
763
+ 3. attach 成功后,用户可在 Feishu 单聊中通过 `/takeover` 继续同一条 thread。
764
+
765
+ ### 10.4 ownership
766
+
767
+ 1. 当 `activeSurface=official_codex_cli` 时,用户在渠道发送普通文本,系统必须拒绝静默抢占,并明确提示 `/takeover` 或 `/new`。
768
+ 2. `/new` 必须创建新的 `work_session`,历史会话不被覆盖。
769
+
770
+ ### 10.5 数据合同
771
+
772
+ 1. `runtimeThreadId` 与 `cliResumeRef` 在数据模型中必须独立持久化。
773
+ 2. `work_session` 必须是独立 durable 对象,不能只存在于内存或 conversation 临时状态里。
774
+ 3. assistant 当前 active `work_session` 与 delivery conversation 当前 active `work_session` 都必须可稳定查询。
775
+
776
+ ### 10.6 测试最低要求
777
+
778
+ 至少覆盖:
779
+
780
+ - `tests/unit/session/` 中的 `work_session` 模型与 ownership 规则
781
+ - `tests/integration/session/` 中的 handoff 主路径与拒绝路径
782
+ - 必要的真实 `codex app-server` / `codex resume` smoke 验证
783
+ - shell 层 `./ally.sh handoff ...` 命令语义验证
784
+
785
+ ## 10.1 开发者自测清单
786
+
787
+ 实现完成前,开发者至少要能自己验证下面这些事实:
788
+
789
+ 1. 从 `work-ally` 创建的 active `work_session` 能正确导出官方 Codex handoff 命令。
790
+ 2. 从该 handoff 命令进入官方 CLI 后,再执行 `/takeover`,不会新建 thread。
791
+ 3. 一个未纳管的官方 CLI thread,只有在显式 attach 且 workspace 匹配时才能被纳入管理。
792
+ 4. owner 为 `official_codex_cli` 时,渠道普通文本会被明确拒绝,而不是被桥层吞掉或偷偷继续。
793
+ 5. `runtimeThreadId` 与 `cliResumeRef` 在任何持久化文件中都不是同一字段的别名。
794
+
795
+ 如果自动化无法完整覆盖其中某一项,交付时必须明确写出缺口。
796
+
797
+ ## 11. 风险、假设、待决问题
798
+
799
+ ### 11.1 假设
800
+
801
+ 1. 同一 assistant desk 对应稳定的 `CODEX_HOME`,这是同机会话连续性的前提。
802
+ 2. 官方 `Codex CLI` 的 thread 存储与 `app-server` 可观察边界足以支撑 same-machine attach / resume。
803
+ 3. 用户理解“切回这里”需要显式动作;V1 不追求完全无确认的隐式抢占。
804
+
805
+ ### 11.2 风险
806
+
807
+ 1. `cliResumeRef` 的实际可得性,可能仍会受到官方实现版本差异影响;因此 V1 必须允许 `resumeCapability=not_ready` 的真实存在。
808
+ 2. 对 unmanaged CLI thread 的自动发现,如果候选很多,attach UX 可能不够轻;但这比错误 attach 更可接受。
809
+ 3. 如果实现团队把 transcript 镜像误当成必做项,范围会显著膨胀。
810
+
811
+ ### 11.3 待决问题
812
+
813
+ 1. `./ally.sh handoff codex <assistant> --open` 是否在 V1 就直接拉起官方 Codex,还是先只打印命令。
814
+ 2. `/status` 是否在本专题内补充展示 `activeSurface` 与 handoff readiness。
815
+ 3. desktop control plane 未来若要加“继续到 Codex”按钮,应复用本 spec 的同一 handoff service,而不是重做一套桌面私有逻辑。
816
+
817
+ ## 12. 实施建议
818
+
819
+ 建议实现顺序按用户价值切,而不是按工程模块切。
820
+
821
+ ### 阶段 1:把 `work_session` 与 ownership 合同落稳
822
+
823
+ 交付用户价值:
824
+
825
+ - 连续工作对象正式成立
826
+ - 渠道侧不会再静默乱抢会话
827
+
828
+ ### 阶段 2:打通 `work-ally -> Codex CLI`
829
+
830
+ 交付用户价值:
831
+
832
+ - 用户可从渠道侧精确切回官方 Codex 继续同一条工作线程
833
+
834
+ ### 阶段 3:打通 `Codex CLI -> work-ally`
835
+
836
+ 交付用户价值:
837
+
838
+ - 用户可把官方 Codex 中的工作接回手机侧继续
839
+
840
+ ### 阶段 4:补齐 attach、拒绝路径与真实回归
841
+
842
+ 交付用户价值:
843
+
844
+ - CLI 直启线程也可纳管
845
+ - 产品在歧义和异常场景下不会乱猜
846
+
847
+ ## 13. 给开发者的硬边界
848
+
849
+ 如果你是接手实现的人,这几条必须直接当成约束,而不是建议:
850
+
851
+ 1. 这次做的是“同一条真实工作线程的双向接续”,不是“把摘要复制到另一边继续聊”。
852
+ 2. `work_session` 是新的产品对象;如果你的实现里仍主要围绕 `conversationRef -> threadId` 做临时映射,说明你还没按 spec 重构到位。
853
+ 3. `runtimeThreadId` 与 `cliResumeRef` 必须分开;任何“当前版本看起来一样,所以先共用一个字段”的实现,都是不合格实现。
854
+ 4. 当 spec 没允许你自动猜的时候,就必须拒绝,不要帮用户脑补。
855
+ 5. owner 规则比“看起来方便”更重要;不能为了流畅表象把 single-active 做成静默双写。
856
+ 6. 本专题不是 transcript 镜像工程;不要把 scope 擅自扩成“官方 CLI 所有输出实时同步回 Feishu”。
857
+
858
+ ## 14. 进入实现后的文档纪律
859
+
860
+ 本文件是灯塔,不承担实时施工记录。
861
+
862
+ 一旦进入实现:
863
+
864
+ - 必须在工程根目录维护 `TASK.md`
865
+ - `TASK.md` 必须逐步拆成可执行任务,并为每个任务写清 DoD
866
+ - 实施者每完成一个阶段,都要先更新 `TASK.md`,再进入下一步
867
+ - 若实现过程中发现 spec 失效或边界变化,先回写本 spec,再继续开发
868
+
869
+ 换句话说:
870
+
871
+ - `SPEC` 是灯塔
872
+ - `TASK.md` 是路径
873
+ - 两者缺一不可