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,301 @@
1
+ # Group Chat Sender Identity Spec
2
+
3
+ ## Status
4
+
5
+ - Target project: `work-ally`
6
+ - Audience: implementation engineer
7
+ - Scope: Feishu channel, for future group-chat enablement
8
+ - Goal: preserve speaker identity in group-chat prompts sent to Codex
9
+ - Status: implemented; group-chat now follows minimal explicit-trigger policy
10
+
11
+ ## Summary
12
+
13
+ This spec defines one feature for future Feishu group-chat support:
14
+
15
+ - private chat: do not add sender identity metadata to the prompt
16
+ - group chat: always add sender identity metadata to the prompt
17
+
18
+ The problem is not whether `work-ally` can see the sender identity. It already can.
19
+ The problem is that the current prompt builder ignores that identity and sends only the raw message text to Codex.
20
+
21
+ That is acceptable in private chat, but it is not sufficient for a future group thread where multiple people may speak in the same topic.
22
+
23
+ ## Background
24
+
25
+ `work-ally` currently targets Feishu private chat only. The inbound Feishu normalization path already captures:
26
+
27
+ - `senderId`
28
+ - `senderName`
29
+ - `conversationRef.chatType`
30
+
31
+ However, current prompt construction still reduces an inbound message to:
32
+
33
+ - `message.text.trim()`
34
+
35
+ That means the bridge currently discards speaker identity at prompt time.
36
+
37
+ For a one-person private chat, this is mostly fine because the human speaker is effectively constant.
38
+
39
+ For a future group-chat thread, this becomes a correctness problem.
40
+
41
+ If two or more humans speak inside the same thread and Codex only sees raw message text, it cannot reliably tell:
42
+
43
+ - who is asking a question
44
+ - who is correcting an earlier statement
45
+ - whether two consecutive messages came from the same person or from different people
46
+
47
+ ## Problem Statement
48
+
49
+ In a future Feishu group-chat thread, multiple humans may participate in the same topic. If `work-ally` forwards only raw message text to Codex, the model loses speaker attribution.
50
+
51
+ Without speaker attribution, these become ambiguous:
52
+
53
+ ```text
54
+ Can you summarize the plan?
55
+ Actually focus on timeline risk.
56
+ ```
57
+
58
+ This could mean:
59
+
60
+ - one user refining their own request
61
+ - a second user redirecting the discussion
62
+ - two stakeholders disagreeing
63
+
64
+ The model needs to know who said what.
65
+
66
+ ## Current State In `work-ally`
67
+
68
+ ### What already exists
69
+
70
+ `work-ally` can already distinguish chat type and sender identity at the channel-normalization layer.
71
+
72
+ Relevant files:
73
+
74
+ - `work-ally/bridge/src/channels/feishu/normalize.ts`
75
+ - `work-ally/bridge/src/types.ts`
76
+ - `work-ally/bridge/src/receiver.ts`
77
+
78
+ Current behavior:
79
+
80
+ - `senderName` is read from Feishu inbound payloads
81
+ - `senderId` is preserved in the normalized message object
82
+ - `chatType` is stored in `conversationRef.chatType`
83
+ - prompt construction ignores all of the above and uses only message text
84
+
85
+ ### Current shipped boundary
86
+
87
+ Feishu normalization now accepts `group`, but the adapter only forwards a minimal safe subset of group traffic: explicit calls, control commands, or direct replies to assistant messages.
88
+
89
+ ## Feature Goal
90
+
91
+ Define a stable prompt format so that every accepted group message delivered to Codex contains speaker identity metadata.
92
+
93
+ The intended rule is:
94
+
95
+ - private chat: unchanged prompt behavior
96
+ - group chat: identity-aware prompt behavior
97
+
98
+ ## Non-goals
99
+
100
+ This feature now participates in shipped group-chat behavior, but only under the minimal explicit-trigger policy described above.
101
+
102
+ This feature does not define:
103
+
104
+ - group access control
105
+ - mention gating
106
+ - group session routing
107
+ - topic/thread selection rules
108
+ - outbound response formatting for groups
109
+
110
+ Those can be handled separately.
111
+
112
+ ## Why This Feature Is Needed
113
+
114
+ ### 1. Group-chat reasoning requires speaker attribution
115
+
116
+ The model must know whether a new message came from:
117
+
118
+ - the same person as before
119
+ - a different person with a different role
120
+ - a participant responding to another participant rather than to the assistant
121
+
122
+ Without speaker identity, the model’s understanding of the thread is materially weaker.
123
+
124
+ ### 2. The required data is already available
125
+
126
+ This is not blocked on platform capability.
127
+
128
+ Feishu already provides sender identity, and `work-ally` already captures it during normalization.
129
+
130
+ So the feature is mainly a prompt-design and plumbing task.
131
+
132
+ ### 3. Private chat does not need the same treatment
133
+
134
+ Always adding sender identity to every prompt would add noise in private chat.
135
+
136
+ The right policy is asymmetric:
137
+
138
+ - private chat stays minimal
139
+ - group chat carries identity metadata
140
+
141
+ ## Product Requirement
142
+
143
+ ### Private chat rule
144
+
145
+ For Feishu `p2p` / `single` conversations:
146
+
147
+ - keep the prompt content-first
148
+ - do not prepend sender identity metadata
149
+
150
+ ### Group chat rule
151
+
152
+ For Feishu `group` conversations:
153
+
154
+ - prepend sender identity metadata before the original message body
155
+ - include both display name and stable sender id when possible
156
+
157
+ This is mandatory for group-chat prompts.
158
+
159
+ ## Recommended Prompt Shape
160
+
161
+ Do not rewrite the message inline as `Allen: <message>`.
162
+
163
+ That approach mixes metadata into user content and is harder to evolve.
164
+
165
+ Instead, use a small structured envelope.
166
+
167
+ Recommended group-chat prompt shape:
168
+
169
+ ```text
170
+ Channel: Feishu
171
+ Chat type: group
172
+ Sender: Allen
173
+ Sender ID: ou_xxx
174
+
175
+ Message:
176
+ 请你总结一下这个方案的风险
177
+ ```
178
+
179
+ This format keeps speaker metadata separate from the user’s original content.
180
+
181
+ ## Sender Identity Rules
182
+
183
+ For group prompts:
184
+
185
+ 1. Use `senderName` when present and non-empty.
186
+ 2. Fall back to `senderId` if no display name is available.
187
+ 3. Always include `senderId` somewhere in the metadata block for disambiguation.
188
+
189
+ Reason:
190
+
191
+ - two people may share the same visible name
192
+ - display names can change
193
+ - some events may not provide a useful human-readable name
194
+
195
+ ## Technical Route
196
+
197
+ ### Step 1. Keep chat type and sender fields in normalized contracts
198
+
199
+ This mostly already exists.
200
+
201
+ The implementation should verify that future group-chat acceptance preserves:
202
+
203
+ - `message.senderId`
204
+ - `message.senderName`
205
+ - `message.conversationRef.chatType`
206
+
207
+ No major redesign is required here.
208
+
209
+ ### Step 2. Implement the policy in `Receiver.buildPrompt()`
210
+
211
+ The current prompt builder is the correct control point.
212
+
213
+ Recommended logic:
214
+
215
+ - if `chatType` is `group`, return the structured identity envelope
216
+ - otherwise return `message.text.trim()`
217
+
218
+ This keeps the feature centralized and avoids channel adapters mutating the message body directly.
219
+
220
+ ### Step 3. Do not overwrite raw inbound text
221
+
222
+ The normalized `message.text` should remain the original extracted inbound text.
223
+
224
+ Identity metadata should be added only at prompt-construction time.
225
+
226
+ This preserves:
227
+
228
+ - archive fidelity
229
+ - debugging clarity
230
+ - future reuse of the raw text for other features
231
+
232
+ ### Step 4. Keep archives and diagnostics understandable
233
+
234
+ Current archives already store inbound `senderId` and `text`.
235
+
236
+ If needed, future work may additionally record the final prompt envelope used for the turn. That is optional for this feature, but the implementation engineer should keep debuggability in mind.
237
+
238
+ ## Acceptance Criteria
239
+
240
+ - private chat prompts remain unchanged
241
+ - group chat prompts always include speaker identity metadata before the message body
242
+ - group chat prompts include `senderName` when available
243
+ - group chat prompts fall back cleanly to `senderId` when `senderName` is absent
244
+ - group chat prompts still include `senderId` even when `senderName` exists
245
+ - the feature does not mutate the raw inbound `message.text`
246
+ - the prompt format is consistent across group turns
247
+
248
+ ## Testing Guidance
249
+
250
+ ### Unit tests to add or revise
251
+
252
+ - `work-ally/tests/unit/channel/feishu-normalize.test.mjs`
253
+ - ensure `senderName`, `senderId`, and `chatType` remain preserved
254
+ - ensure accepted group events keep normalized sender identity intact
255
+
256
+ - receiver-level prompt tests
257
+ - private chat returns `message.text.trim()` only
258
+ - group chat returns structured identity envelope
259
+ - missing `senderName` falls back to `senderId`
260
+ - group prompt contains both display name and stable id when available
261
+
262
+ ### Manual verification checklist
263
+
264
+ - send a private message and confirm the prompt does not include sender metadata
265
+ - simulate a group message from user A and confirm the prompt identifies A
266
+ - simulate a second group message from user B in the same thread and confirm the prompt identifies B distinctly
267
+ - test duplicate display names and confirm `senderId` remains available in the prompt metadata
268
+
269
+ ## Risks And Tradeoffs
270
+
271
+ ### Token overhead
272
+
273
+ Adding sender metadata increases token count slightly.
274
+
275
+ This is acceptable in group chat because the information is necessary for correct multi-speaker reasoning.
276
+
277
+ ### Context pollution risk
278
+
279
+ If implemented as `Name: message`, metadata can blur into the message body.
280
+
281
+ That is why this spec recommends a structured metadata block instead of inline prefix mutation.
282
+
283
+ ### Future compatibility
284
+
285
+ This structured format composes cleanly with future features such as:
286
+
287
+ - quoted-message context
288
+ - mention-based routing
289
+ - per-speaker rules
290
+ - richer group session policies
291
+
292
+ ## Final Recommendation
293
+
294
+ Implement this as a prompt-envelope feature with one strict rule:
295
+
296
+ - private chat: no sender metadata
297
+ - group chat: sender metadata is mandatory
298
+
299
+ The sender metadata should be added in the prompt builder, not baked into normalized raw message text.
300
+
301
+ This gives future group-chat threads the minimum identity fidelity required for Codex to reason correctly about multi-person conversations without unnecessarily polluting private-chat prompts.
@@ -0,0 +1,227 @@
1
+ # Middleware Exception Visibility
2
+
3
+ ## Status
4
+
5
+ - Target project: `work-ally`
6
+ - Audience: product owner / implementation engineer
7
+ - Scope: bridge / channel / runtime abnormal-path visibility for Feishu-facing users
8
+ - Status: shipped baseline / 2026-03-16
9
+ - Goal: ensure the middleware never silently swallows failures that matter to the user
10
+
11
+ ## Summary
12
+
13
+ `work-ally` 作为中间层,正常路径可以轻量,但异常路径必须透明。
14
+
15
+ 最近真实问题已经证明:
16
+
17
+ - 消息可能进桥了
18
+ - 中间层内部可能报错了
19
+ - 用户面前却只看到“没人理我”
20
+
21
+ 这对中间层是不可接受的。
22
+
23
+ 一句话:
24
+
25
+ > 只要用户已经把消息交给中间层,中间层就不能再让用户靠猜来判断发生了什么。
26
+
27
+ ## Background
28
+
29
+ 近期真实使用里暴露出两个典型问题:
30
+
31
+ 1. 消息进入 `handleInbound` 后,因为中间层内部异常,没有成功产出任何对用户可见的反馈
32
+ 2. 某些异常只写进日志,没有及时回告坐在飞书前的人类
33
+
34
+ 这直接暴露出一个产品缺口:
35
+
36
+ - 中间层把异常“吃在内部”了
37
+ - 用户既无法绕过中间层确认真相,也无法判断是消息没收到、处理中、恢复中,还是已经失败
38
+
39
+ ## Problem Statement
40
+
41
+ 当前异常透明度不足主要体现在四类场景。
42
+
43
+ ### 1. Message accepted, but no visible outcome
44
+
45
+ 消息已经被桥接层接住,但随后:
46
+
47
+ - 发送链路异常
48
+ - 状态文件损坏
49
+ - runtime / bridge 局部错误
50
+
51
+ 导致这轮没有对用户产生任何明确结果。
52
+
53
+ ### 2. Failure exists only in logs
54
+
55
+ 中间层知道:
56
+
57
+ - delivery unavailable
58
+ - reaction 发送失败
59
+ - runtime 恢复失败
60
+ - receipt / dedupe 异常
61
+
62
+ 但用户不知道。
63
+
64
+ ### 3. Retry semantics are inconsistent
65
+
66
+ 如果本轮处理中途失败:
67
+
68
+ - 用户期望系统要么明确告知失败
69
+ - 要么保证后续重试还能成功进入
70
+
71
+ 如果既没告知,又把后续重投吞掉,就是严重产品缺陷。
72
+
73
+ ### 4. Abnormal paths are not modeled as first-class product states
74
+
75
+ 当前系统已经有一部分异常提示,但还没有形成稳定判断:
76
+
77
+ - 什么异常必须立刻对用户可见
78
+ - 什么异常可以后台自愈后静默恢复
79
+ - 什么异常必须明确要求用户重发或重试
80
+
81
+ ### 5. Stale redelivery can resurrect old work unexpectedly
82
+
83
+ 如果某条消息第一次进入 bridge 时中途失败,渠道之后可能会重投同一个 `message_id`。
84
+
85
+ 当前如果中间层只把这类失败当成“允许后续重试”,但没有识别“会话其实已经继续前进”,就会出现一种很差的体验:
86
+
87
+ - 用户已经聊到后面了
88
+ - 系统却在几分钟甚至几小时后,突然又把前面那条旧消息重新做一遍
89
+
90
+ 这不是正常恢复,而是**过期工作被意外复活**。
91
+
92
+ ## Product Decision
93
+
94
+ 建立一条硬规则:
95
+
96
+ ### Rule 1
97
+
98
+ 只要用户消息已经进入中间层,系统就必须保证二选一:
99
+
100
+ - 要么给出用户可见结果
101
+ - 要么给出用户可见异常状态
102
+
103
+ 不能既无结果又无异常提示。
104
+
105
+ ### Rule 2
106
+
107
+ 只要异常导致“本轮可能没有被稳定送达”,就必须进入用户可见路径。
108
+
109
+ ### Rule 3
110
+
111
+ 如果异常发生在出站链路,且本轮未成功完成,则不能把该消息静默标记成已完成。
112
+
113
+ ### Rule 4
114
+
115
+ 日志是排障材料,不是用户通知机制。
116
+
117
+ ### Rule 5
118
+
119
+ 如果同一个 inbound message 是平台重投,但当前会话已经被更晚的用户消息推进到下一阶段,中间层必须把这次重投识别为**过期重放**并抑制,而不是把旧任务重新做一遍。
120
+
121
+ ## Goals
122
+
123
+ 1. 杜绝“消息被吞了,但用户不知道”
124
+ 2. 让异常状态成为对话产品的一部分,而不是仅存在于日志中
125
+ 3. 让重试 / 重投语义稳定,不再把失败消息误判成已完成
126
+ 4. 让用户明确知道当前是:处理中、恢复中、失败需重发,还是等待操作
127
+
128
+ ## Non-goals
129
+
130
+ 本次不做:
131
+
132
+ - 完整运维看板
133
+ - 所有内部错误都逐条暴露给用户
134
+ - 把底层堆栈直接发给飞书用户
135
+
136
+ 目标不是“异常细节外泄”,而是“异常语义透明”。
137
+
138
+ ## Scope
139
+
140
+ V1 重点覆盖:
141
+
142
+ 1. inbound 已接收后发生的本地中间层异常
143
+ 2. channel outbound / delivery failure
144
+ 3. runtime recovery failure
145
+ 4. receipt / dedupe 与重投语义
146
+ 5. 中间层自愈与用户可见状态之间的最小合同
147
+ 6. stale inbound redelivery / duplicate replay 的抑制与可追溯性
148
+
149
+ ## Required Product Behavior
150
+
151
+ ### 1. Inbound accepted + handling failed
152
+
153
+ 如果消息已经 accepted,但本轮处理未完成:
154
+
155
+ - 不能静默结束
156
+ - 不能把 receipt 标成已完成
157
+ - 应允许重投再次进入,或明确提示用户重发
158
+
159
+ ### 2. Secondary status send failed
160
+
161
+ 如果像 reaction、progress、status 这类提示发送失败:
162
+
163
+ - 不应让主流程整体无声失败
164
+ - 应记录 delivery issue
165
+ - 如影响用户理解当前状态,应在后续可用时补发系统状态或最终失败说明
166
+
167
+ ### 3. Recovery failed
168
+
169
+ 如果系统尝试恢复但失败:
170
+
171
+ - 不能继续伪装成“正在工作中”
172
+ - 必须明确告诉用户:本轮未恢复,请重发上一条消息
173
+
174
+ ### 4. Health / state corruption
175
+
176
+ 如果本地状态文件损坏:
177
+
178
+ - 中间层应优先自我修复或降级
179
+ - 不允许因此直接吞掉当前用户消息
180
+
181
+ ### 5. Stale redelivery after the user has moved on
182
+
183
+ 如果同一个 `message_id` 被平台再次投递,但系统已经能确认:
184
+
185
+ - 这条消息之前来过
186
+ - 当前会话已经被更新的用户消息推进过
187
+
188
+ 则不应再把这条旧消息当作新任务重新执行。
189
+
190
+ 要求:
191
+
192
+ - 对用户不再突然冒出旧任务回复
193
+ - 对 archive / timeline 仍保留可追溯事件
194
+ - 若会话尚未继续前进,则仍允许同一条消息在失败后再次重试
195
+
196
+ ## Implementation Direction
197
+
198
+ 优先实现方向:
199
+
200
+ 1. 任何 receipt 完成标记,都必须只发生在用户可见结果已稳定产出之后
201
+ 2. health / delivery 状态文件读取应容错,不能反过来拖垮正常消息处理
202
+ 3. delivery unavailable / recovery failed / swallowed-risk 事件应进入统一异常通知语义
203
+ 4. conversation view 与 archive 中应保留这些异常事件,便于之后回溯和解释
204
+
205
+ ## Acceptance Criteria
206
+
207
+ 1. 任一已接收用户消息,不能再出现“日志里知道失败、用户侧完全无感”的吞消息场景
208
+ 2. 出站链路失败时,不会把当前消息误判成 completed,从而吞掉后续重投
209
+ 3. runtime 恢复失败时,用户能收到明确的“需要重发”提示
210
+ 4. 中间层内部状态文件损坏时,消息处理仍可继续,或至少能给出明确异常回告
211
+ 5. 相关异常在 archive / conversation view 中可追溯
212
+
213
+ ## Open Questions
214
+
215
+ 1. 哪些 delivery failure 需要立刻发系统消息,哪些只需写健康状态并等待后续补发
216
+ 2. 是否需要一条统一的“本轮处理中出现异常,但系统仍在尝试恢复”文案合同
217
+ 3. 是否需要把 swallowed-risk 场景单独纳入 `/status` 输出
218
+
219
+ ## Product Judgment
220
+
221
+ 中间层的价值,不是把消息转一下,而是把复杂性吃掉。
222
+
223
+ 如果中间层在异常路径里不透明,那它不是在帮用户,而是在制造新的不确定性。
224
+
225
+ 所以这条专题的判断非常硬:
226
+
227
+ > 中间层可以出错,但不能在用户视角里“无声出错”。
@@ -0,0 +1,121 @@
1
+ # Nightly Memory Digest Visibility
2
+
3
+ ## Status
4
+
5
+ - Target project: `work-ally`
6
+ - Audience: implementation engineer / product owner
7
+ - Scope: nightly memory digest result visibility in Feishu-facing flows
8
+ - Status: implemented in current branch
9
+ - Goal: make the nightly digest execution result visible to the user instead of remaining a background-only mechanism
10
+
11
+ ## Summary
12
+
13
+ 这份专题定义的是一个已经落地的产品补口:
14
+
15
+ - nightly memory digest 不再只是后台写文件
16
+ - 用户需要知道它有没有跑
17
+ - 用户需要知道它跑出了什么结果
18
+ - 即使结论是“无新增稳定记忆”,也应被明确告诉用户
19
+
20
+ 一句话:
21
+
22
+ > nightly memory digest 现在是“后台执行 + 桌面写回 + 对话内结果通知”的闭环,而不是静默后台任务。
23
+
24
+ ## Background
25
+
26
+ 原先 nightly digest 的工程链路已经存在:
27
+
28
+ - scheduler 会按计划触发 `nightly-memory-digest`
29
+ - routine 会读取当天 archive
30
+ - digest 结果会写入 `journal/` 与 `MEMORY.md`
31
+ - 运行记录会进入 `.system/runs/` 与 `.system/runtime/routines-state.json`
32
+
33
+ 但在真实使用里暴露出一个明显问题:
34
+
35
+ - 用户不知道任务到底有没有执行
36
+ - 用户不知道“没有新内容”是因为没跑,还是因为跑了但无新增
37
+ - 用户也不知道本次提炼到底更新了什么
38
+
39
+ 因此,这条链路虽然工程上成立,产品上却还缺最后一公里的可见性。
40
+
41
+ ## Problem Statement
42
+
43
+ nightly digest 如果只在后台写文件,会让用户陷入三种歧义:
44
+
45
+ 1. 今天到底跑没跑
46
+ 2. 跑了是成功、失败,还是没有产出
47
+ 3. 这次提炼影响了什么长期记忆
48
+
49
+ 这类问题不应该要求用户自己去翻文件判断。
50
+
51
+ ## Shipped Model
52
+
53
+ 当前已落地行为如下。
54
+
55
+ ### 1. 默认调度时间仍是 assistant 本地时区 23:00
56
+
57
+ - 默认 routine 定义为 `0 23 * * *`
58
+ - 调度解释使用 assistant 当前配置的本地时区
59
+ - 在中国时区下,就是每天 23:00 触发
60
+
61
+ ### 2. digest 完成后会生成一段面向用户的摘要
62
+
63
+ routine 执行完成后,会从 digest reply 中解析三段内容:
64
+
65
+ - 摘要
66
+ - 当日日记
67
+ - 长期记忆
68
+
69
+ 其中摘要不会原样暴露内部结构,而是转成面向用户的系统消息文案。
70
+
71
+ ### 3. 如存在可用 Feishu 投递目标,会主动推送系统消息
72
+
73
+ 当前投递目标解析顺序是:
74
+
75
+ 1. 最近一次 Feishu 会话
76
+ 2. `defaultPushTarget`
77
+ 3. 仅有一个 allowlisted user 时的兜底目标
78
+
79
+ 只要存在可用目标,digest 完成后就会主动发出一条系统消息,告诉用户本次记忆整理结果。
80
+
81
+ ### 4. “无新增稳定记忆”也会明确告知
82
+
83
+ 如果 digest 结论是本次没有新增稳定记忆,系统消息会明确表达这一点,而不是静默结束。
84
+
85
+ 这能把“没跑”和“跑了但无新增”区分开。
86
+
87
+ ### 5. 运行记录仍保留在文件系统里
88
+
89
+ 除对话内通知外,工程侧仍保留可追溯记录:
90
+
91
+ - `.system/runtime/routines-state.json`:记录最近一次 slot
92
+ - `.system/runs/latest.json`:记录最近一次执行结果
93
+ - `.system/runs/*.json`:保留历史运行结果
94
+
95
+ 也就是说,这个 feature 的方向不是用系统消息替代文件追踪,而是在文件追踪之外补上用户可见结果层。
96
+
97
+ ## Non-goals
98
+
99
+ 本次已落地范围不包括:
100
+
101
+ - 独立的 routine 管理面板
102
+ - digest 失败后的重试中心
103
+ - digest 历史结果列表 UI
104
+ - 对 `NOW.md` 的自动改写
105
+ - 无 Feishu 可投递目标时的额外补发机制
106
+
107
+ ## Product Judgment
108
+
109
+ 这条补口的核心判断有三条:
110
+
111
+ 1. nightly digest 是长期协作能力,不应保持静默后台态
112
+ 2. “无新增稳定记忆”本身也是结果,应该被通知
113
+ 3. 用户侧看到的是结果摘要,不是原始运行日志;原始日志继续留在文件系统里
114
+
115
+ ## Acceptance Criteria
116
+
117
+ - nightly memory digest 在默认配置下按 assistant 本地时区 23:00 调度
118
+ - 执行完成后,如存在可用 Feishu 投递目标,会主动推送系统消息
119
+ - 系统消息能区分“有更新”与“无新增稳定记忆”
120
+ - digest 仍只写回 `journal/` 与 `MEMORY.md`,不改写 `NOW.md`
121
+ - 最近一次执行结果仍可在 `.system/runtime/routines-state.json` 与 `.system/runs/latest.json` 中追溯