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,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` 中追溯
|