switchroom 0.19.25 → 0.19.27

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 (84) hide show
  1. package/bin/git-agent-attribution-hook.sh +144 -0
  2. package/dist/agent-scheduler/index.js +61 -2
  3. package/dist/auth-broker/index.js +125 -8
  4. package/dist/cli/notion-write-pretool.mjs +61 -2
  5. package/dist/cli/switchroom.js +2347 -1104
  6. package/dist/host-control/main.js +126 -9
  7. package/dist/vault/approvals/kernel-server.js +124 -8
  8. package/dist/vault/broker/server.js +124 -8
  9. package/package.json +6 -2
  10. package/profiles/_base/cron-session.sh.hbs +14 -0
  11. package/profiles/_base/start.sh.hbs +145 -4
  12. package/telegram-plugin/card-layout.ts +328 -0
  13. package/telegram-plugin/dist/bridge/bridge.js +93 -1
  14. package/telegram-plugin/dist/gateway/gateway.js +2213 -1204
  15. package/telegram-plugin/dist/server.js +96 -1
  16. package/telegram-plugin/edit-flood-fuse.ts +637 -56
  17. package/telegram-plugin/flood-429-ledger.ts +526 -0
  18. package/telegram-plugin/flood-circuit-breaker.ts +18 -0
  19. package/telegram-plugin/gateway/flood-reply-queue.ts +168 -0
  20. package/telegram-plugin/gateway/gateway.ts +103 -112
  21. package/telegram-plugin/gateway/narrative-lane.ts +14 -0
  22. package/telegram-plugin/gateway/outbound-send-path.ts +36 -0
  23. package/telegram-plugin/gateway/outbox-sweep.ts +183 -6
  24. package/telegram-plugin/gateway/periodic-sweep-guard.ts +86 -0
  25. package/telegram-plugin/gateway/pinned-message-handler.ts +12 -16
  26. package/telegram-plugin/gateway/status-pin-retarget.ts +180 -0
  27. package/telegram-plugin/gateway/status-pin-store.ts +58 -9
  28. package/telegram-plugin/gateway/worker-pin-reaper.ts +56 -7
  29. package/telegram-plugin/llm-error-present.ts +61 -2
  30. package/telegram-plugin/model-unavailable.ts +8 -0
  31. package/telegram-plugin/operator-events.ts +72 -5
  32. package/telegram-plugin/outbound-class.ts +81 -0
  33. package/telegram-plugin/provider-credit.ts +237 -0
  34. package/telegram-plugin/scripts/bun-test-ci.sh +36 -6
  35. package/telegram-plugin/send-gate.ts +24 -2
  36. package/telegram-plugin/status-no-truncate.ts +11 -0
  37. package/telegram-plugin/status-pin-driver.ts +33 -17
  38. package/telegram-plugin/status-pin.ts +51 -5
  39. package/telegram-plugin/tests/card-golden.test.ts +69 -0
  40. package/telegram-plugin/tests/card-lifecycle-render.test.ts +362 -0
  41. package/telegram-plugin/tests/card-type-distinguishability.test.ts +291 -0
  42. package/telegram-plugin/tests/card-variants.golden.txt +211 -0
  43. package/telegram-plugin/tests/card-variants.ts +366 -0
  44. package/telegram-plugin/tests/edit-flood-fuse-ban-awareness.test.ts +316 -0
  45. package/telegram-plugin/tests/edit-flood-fuse-default-deny.test.ts +319 -0
  46. package/telegram-plugin/tests/edit-flood-fuse.test.ts +11 -2
  47. package/telegram-plugin/tests/feed-edit-rate-ceiling.test.ts +462 -0
  48. package/telegram-plugin/tests/fixtures/real-429-stream.ts +220 -0
  49. package/telegram-plugin/tests/flood-429-ledger.test.ts +278 -0
  50. package/telegram-plugin/tests/flood-429-recorder-wiring.test.ts +128 -0
  51. package/telegram-plugin/tests/flood-reply-queue.test.ts +418 -0
  52. package/telegram-plugin/tests/outbox-sweep-flood-breaker.test.ts +221 -0
  53. package/telegram-plugin/tests/periodic-sweep-guard.test.ts +151 -0
  54. package/telegram-plugin/tests/pinned-card-collapse.test.ts +24 -18
  55. package/telegram-plugin/tests/pinned-message-handler.test.ts +15 -15
  56. package/telegram-plugin/tests/provider-credit-402.test.ts +243 -0
  57. package/telegram-plugin/tests/status-pin-api.test.ts +11 -11
  58. package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +36 -37
  59. package/telegram-plugin/tests/status-pin-lifecycle.test.ts +602 -0
  60. package/telegram-plugin/tests/status-pin-retarget.test.ts +244 -0
  61. package/telegram-plugin/tests/status-pin-service-message-suppression.test.ts +7 -3
  62. package/telegram-plugin/tests/status-pin-shutdown-wiring.test.ts +94 -0
  63. package/telegram-plugin/tests/status-pin-store.test.ts +179 -64
  64. package/telegram-plugin/tests/status-pin.test.ts +184 -7
  65. package/telegram-plugin/tests/test-runner-coverage.test.ts +133 -0
  66. package/telegram-plugin/tests/worker-activity-feed.test.ts +12 -10
  67. package/telegram-plugin/tests/worker-feed-coalesce.test.ts +29 -19
  68. package/telegram-plugin/tests/worker-feed-pin-persistence.test.ts +56 -59
  69. package/telegram-plugin/tests/worker-feed-terminal-edit-class.test.ts +335 -0
  70. package/telegram-plugin/tests/worker-visibility-prose-silent-harness.test.ts +1 -1
  71. package/telegram-plugin/tier-downgrade.ts +3 -2
  72. package/telegram-plugin/tool-activity-summary.ts +239 -322
  73. package/telegram-plugin/uat/assertions.ts +33 -3
  74. package/telegram-plugin/uat/feed-matcher.test.ts +36 -0
  75. package/telegram-plugin/uat/scenarios/jtbd-liveness-narration-channel.test.ts +9 -2
  76. package/telegram-plugin/uat/scenarios/jtbd-liveness-narration-dm.test.ts +9 -2
  77. package/telegram-plugin/worker-activity-feed.ts +109 -30
  78. package/vendor/hindsight-memory/CLAUDE.md +45 -0
  79. package/vendor/hindsight-memory/scripts/lib/config.py +33 -0
  80. package/vendor/hindsight-memory/scripts/recall.py +176 -7
  81. package/vendor/hindsight-memory/scripts/tests/test_config_recall_passthrough_env.py +170 -0
  82. package/vendor/hindsight-memory/scripts/tests/test_recall_min_score.py +464 -0
  83. package/vendor/hindsight-memory/scripts/tests/test_recall_request_timeout.py +241 -0
  84. package/vendor/hindsight-memory/settings.json +1 -1
@@ -0,0 +1,211 @@
1
+
2
+ ===== CARD ===== Agent card — running
3
+ 🤖 **Agent** 
4
+ _1m35s · 12 tools · 41.2k tok · opus 4.8_ 
5
+ ~~_✓ Searching memory for card render paths_~~ 
6
+ ~~_✓ Reading telegram-plugin/tool-activity-summary.ts_~~ 
7
+ ~~_✓ Reading telegram-plugin/worker-activity-feed.ts_~~ 
8
+ ~~_✓ Grepping for renderStatusCard callers_~~ 
9
+ **→ Drafting the inventory table**
10
+
11
+ ===== CARD ===== Agent card — final (turn complete)
12
+ 🤖 **Agent** 
13
+ _done · 12 tools · 41.2k tok · 3m34s · opus 4.8_ 
14
+ ~~_✓ Searching memory for card render paths_~~ 
15
+ ~~_✓ Reading telegram-plugin/tool-activity-summary.ts_~~ 
16
+ ~~_✓ Reading telegram-plugin/worker-activity-feed.ts_~~ 
17
+ ~~_✓ Grepping for renderStatusCard callers_~~ 
18
+ ~~_✓ Drafting the inventory table_~~ 
19
+ _✓ 17 steps_
20
+
21
+ ===== CARD ===== Agent card — steps only (no header)
22
+ ~~_✓ Searching memory for card render paths_~~ 
23
+ ~~_✓ Reading telegram-plugin/tool-activity-summary.ts_~~ 
24
+ **→ Reading telegram-plugin/worker-activity-feed.ts**
25
+
26
+ ===== CARD ===== Agent card — with nested foreground sub-agent
27
+ 🤖 **Agent** 
28
+ _1m35s · 12 tools · 41.2k tok · opus 4.8_ 
29
+ ~~_✓ Searching memory for card render paths_~~ 
30
+ ~~_✓ Reading telegram-plugin/tool-activity-summary.ts_~~ 
31
+ ~~_✓ Reading telegram-plugin/worker-activity-feed.ts_~~ 
32
+ ↳ ~~_Reading the repo conventions_~~ 
33
+ ↳ ~~_Running the scoped test suite_~~ 
34
+ ↳ **→ Summarising findings · 24s**
35
+
36
+ ===== CARD ===== Agent card — liveness early-open (no tools yet)
37
+ 🤖 **Agent** 
38
+ _32s · 0 tools · opus 4.8_ 
39
+ **→ Working… · 32s**
40
+
41
+ ===== CARD ===== Agent card — liveness finalised
42
+ 🤖 **Agent** 
43
+ _done · 0 tools · 1m28s · opus 4.8_ 
44
+ ~~_✓ Working…_~~
45
+
46
+ ===== CARD ===== Agent card — post-answer background activity
47
+ 🤖 **Agent** 
48
+ _2m10s · 3 tools · opus 4.8_ 
49
+ **→ Working in background… · 1m10s**
50
+
51
+ ===== CARD ===== Agent card — over-long steps (per-line clip)
52
+ 🤖 **Agent** 
53
+ _1m35s · 12 tools · 41.2k tok · opus 4.8_ 
54
+ ~~_✓ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA…_~~ 
55
+ ~~_✓ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA…_~~ 
56
+ ~~_✓ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA…_~~ 
57
+ **→ Finally a short newest step**
58
+
59
+ ===== CARD ===== Worker card — just dispatched (no steps yet)
60
+ 🛠 **WORKER** · _Audit every card render path and de-indent the worker card_ 
61
+ _1s · 0 tools · 88.4k tok · opus 4.8_ 
62
+ _starting…_
63
+
64
+ ===== CARD ===== Worker card — single worker running
65
+ 🛠 **WORKER** · _Audit every card render path and de-indent the worker card_ 
66
+ _2m23s · 31 tools · 88.4k tok · opus 4.8_ 
67
+ ~~_✓ Fetching origin and branching off main_~~ 
68
+ ~~_✓ Reading telegram-plugin/status-no-truncate.ts_~~ 
69
+ ~~_✓ Mapping renderStatusCard callers_~~ 
70
+ **→ Writing the preview harness · 18s**
71
+
72
+ ===== CARD ===== Worker card — done (with result recap)
73
+ 🛠 **WORKER** · _Audit every card render path and de-indent the worker card_ 
74
+ _done · 31 tools · 88.4k tok · 15m02s · opus 4.8_ 
75
+ ~~_✓ Fetching origin and branching off main_~~ 
76
+ ~~_✓ Reading telegram-plugin/status-no-truncate.ts_~~ 
77
+ ~~_✓ Mapping renderStatusCard callers_~~ 
78
+ ~~_✓ Writing the preview harness_~~ 
79
+ ───── 
80
+ ✅ _Inventoried 14 card variants, found renderCombinedWorkerFeed forks the header + budget logic, and de-indented the worker card. PR #0000._
81
+
82
+ ===== CARD ===== Worker card — failed
83
+ 🛠 **WORKER** · _Audit every card render path and de-indent the worker card_ 
84
+ _failed · 31 tools · 88.4k tok · 1m01s · opus 4.8_ 
85
+ ~~_✓ Fetching origin and branching off main_~~ 
86
+ ~~_✓ Reading telegram-plugin/status-no-truncate.ts_~~ 
87
+ ~~_✓ Mapping renderStatusCard callers_~~ 
88
+ ~~_✓ Writing the preview harness_~~ 
89
+ ───── 
90
+ ⚠️ _Scoped test run failed: 3 assertions in card-type-distinguishability.test.ts._
91
+
92
+ ===== CARD ===== Worker card — incomplete (killed / timed out)
93
+ 🛠 **WORKER** · _Audit every card render path and de-indent the worker card_ 
94
+ _incomplete · 31 tools · 88.4k tok · 30m05s · opus 4.8_ 
95
+ ~~_✓ Fetching origin and branching off main_~~ 
96
+ ~~_✓ Reading telegram-plugin/status-no-truncate.ts_~~ 
97
+ ~~_✓ Mapping renderStatusCard callers_~~ 
98
+ ~~_✓ Writing the preview harness_~~
99
+
100
+ ===== CARD ===== WORKERS card — 2 workers
101
+ 🛠 **WORKERS** · _2 running · oldest 2m15s · 21 tools · 60.0k tok_ 
102
+ **1. Audit every card render path** _· 1m15s · 7 tools · 20.0k tok · opus 4.8_ 
103
+ ⠀⠀⠀~~_✓ audit step 1_~~ 
104
+ ⠀⠀⠀~~_✓ audit step 2_~~ 
105
+ ⠀⠀⠀~~_✓ audit step 3_~~ 
106
+ ⠀⠀⠀~~_✓ audit step 4_~~ 
107
+ ⠀⠀⠀**→ audit step 5** 
108
+ **2. Fix the hindsight recall floor** _· 2m15s · 14 tools · 40.0k tok · opus 4.8_ 
109
+ ⠀⠀⠀~~_✓ recall step 1_~~ 
110
+ ⠀⠀⠀~~_✓ recall step 2_~~ 
111
+ ⠀⠀⠀~~_✓ recall step 3_~~ 
112
+ ⠀⠀⠀~~_✓ recall step 4_~~ 
113
+ ⠀⠀⠀**→ recall step 5**
114
+
115
+ ===== CARD ===== WORKERS card — 4 workers
116
+ 🛠 **WORKERS** · _4 running · oldest 4m15s · 70 tools · 200.0k tok_ 
117
+ **1. Audit every card render path** _· 1m15s · 7 tools · 20.0k tok · opus 4.8_ 
118
+ ⠀⠀⠀~~_✓ audit step 2_~~ 
119
+ ⠀⠀⠀~~_✓ audit step 3_~~ 
120
+ ⠀⠀⠀**→ audit step 4** 
121
+ **2. Fix the hindsight recall floor** _· 2m15s · 14 tools · 40.0k tok · opus 4.8_ 
122
+ ⠀⠀⠀~~_✓ recall step 2_~~ 
123
+ ⠀⠀⠀~~_✓ recall step 3_~~ 
124
+ ⠀⠀⠀**→ recall step 4** 
125
+ **3. Release v0.19.24** _· 3m15s · 21 tools · 60.0k tok · opus 4.8_ 
126
+ ⠀⠀⠀~~_✓ release step 2_~~ 
127
+ ⠀⠀⠀~~_✓ release step 3_~~ 
128
+ ⠀⠀⠀**→ release step 4** 
129
+ **4. Investigate the flaky UAT scenario** _· 4m15s · 28 tools · 80.0k tok · opus 4.8_ 
130
+ ⠀⠀⠀~~_✓ uat step 2_~~ 
131
+ ⠀⠀⠀~~_✓ uat step 3_~~ 
132
+ ⠀⠀⠀**→ uat step 4**
133
+
134
+ ===== CARD ===== WORKERS card — 8 workers (spill)
135
+ 🛠 **WORKERS** · _8 running · oldest 8m15s · 252 tools · 720.0k tok_ 
136
+ **1. Background task number 1** _· 1m15s · 7 tools · 20.0k tok · opus 4.8_ 
137
+ ⠀⠀⠀~~_✓ t1 step 1_~~ 
138
+ ⠀⠀⠀~~_✓ t1 step 2_~~ 
139
+ ⠀⠀⠀**→ t1 step 3** 
140
+ **2. Background task number 2** _· 2m15s · 14 tools · 40.0k tok · opus 4.8_ 
141
+ ⠀⠀⠀~~_✓ t2 step 1_~~ 
142
+ ⠀⠀⠀~~_✓ t2 step 2_~~ 
143
+ ⠀⠀⠀**→ t2 step 3** 
144
+ **3. Background task number 3** _· 3m15s · 21 tools · 60.0k tok · opus 4.8_ 
145
+ ⠀⠀⠀~~_✓ t3 step 1_~~ 
146
+ ⠀⠀⠀~~_✓ t3 step 2_~~ 
147
+ ⠀⠀⠀**→ t3 step 3** 
148
+ **4. Background task number 4** _· 4m15s · 28 tools · 80.0k tok · opus 4.8_ 
149
+ ⠀⠀⠀~~_✓ t4 step 1_~~ 
150
+ ⠀⠀⠀~~_✓ t4 step 2_~~ 
151
+ ⠀⠀⠀**→ t4 step 3** 
152
+ **5. Background task number 5** _· 5m15s · 35 tools · 100.0k tok · opus 4.8_ 
153
+ ⠀⠀⠀~~_✓ t5 step 1_~~ 
154
+ ⠀⠀⠀~~_✓ t5 step 2_~~ 
155
+ ⠀⠀⠀**→ t5 step 3** 
156
+ **6. Background task number 6** _· 6m15s · 42 tools · 120.0k tok · opus 4.8_ 
157
+ ⠀⠀⠀~~_✓ t6 step 1_~~ 
158
+ ⠀⠀⠀~~_✓ t6 step 2_~~ 
159
+ ⠀⠀⠀**→ t6 step 3** 
160
+ _+2 more working…_
161
+
162
+ ===== CARD ===== WORKERS card — survivors keep their ordinals
163
+ 🛠 **WORKERS** · _2 running · oldest 3m15s · 35 tools · 100.0k tok_ 
164
+ **2. Fix the hindsight recall floor** _· 2m15s · 14 tools · 40.0k tok · opus 4.8_ 
165
+ ⠀⠀⠀~~_✓ recall step 1_~~ 
166
+ ⠀⠀⠀~~_✓ recall step 2_~~ 
167
+ ⠀⠀⠀~~_✓ recall step 3_~~ 
168
+ ⠀⠀⠀**→ recall step 4** 
169
+ **3. Release v0.19.24** _· 3m15s · 21 tools · 60.0k tok · opus 4.8_ 
170
+ ⠀⠀⠀~~_✓ release step 1_~~ 
171
+ ⠀⠀⠀~~_✓ release step 2_~~ 
172
+ ⠀⠀⠀~~_✓ release step 3_~~ 
173
+ ⠀⠀⠀**→ release step 4**
174
+
175
+ ===== CARD ===== WORKERS card — a worker with no history yet
176
+ 🛠 **WORKERS** · _2 running · oldest 2m15s · 21 tools · 60.0k tok_ 
177
+ **1. Audit every card render path** _· 1m15s · 7 tools · 20.0k tok · opus 4.8_ 
178
+ ⠀⠀⠀~~_✓ audit step 1_~~ 
179
+ ⠀⠀⠀~~_✓ audit step 2_~~ 
180
+ ⠀⠀⠀~~_✓ audit step 3_~~ 
181
+ ⠀⠀⠀~~_✓ audit step 4_~~ 
182
+ ⠀⠀⠀**→ audit step 5** 
183
+ **2. Freshly dispatched worker** _· 2m15s · 14 tools · 40.0k tok · opus 4.8_ 
184
+ ⠀⠀⠀→ _starting…_
185
+
186
+ ===== CARD ===== Worker card — superseded (group message rotated)
187
+ 🛠 **WORKER** · _continued_
188
+
189
+ _Live progress moved to a fresh card to stay pinned._
190
+
191
+ ===== CARD ===== Agent card — rolling-window overflow
192
+ 🤖 **Agent** 
193
+ _1m35s · 12 tools · 41.2k tok · opus 4.8_ 
194
+ _✓ +9 earlier…_ 
195
+ ~~_✓ Doing thing number 10_~~ 
196
+ ~~_✓ Doing thing number 11_~~ 
197
+ ~~_✓ Doing thing number 12_~~ 
198
+ ~~_✓ Doing thing number 13_~~ 
199
+ **→ Doing thing number 14**
200
+
201
+ ===== CARD ===== Agent card — nested sub-agent with child overflow
202
+ 🤖 **Agent** 
203
+ _1m35s · 12 tools · 41.2k tok · opus 4.8_ 
204
+ ~~_✓ Searching memory for card render paths_~~ 
205
+ ~~_✓ Reading telegram-plugin/tool-activity-summary.ts_~~ 
206
+ ↳ _+6 earlier…_ 
207
+ ↳ ~~_Child step 7_~~ 
208
+ ↳ ~~_Child step 8_~~ 
209
+ ↳ ~~_Child step 9_~~ 
210
+ ↳ ~~_Child step 10_~~ 
211
+ ↳ **→ Child step 11**
@@ -0,0 +1,366 @@
1
+ /**
2
+ * The catalogue of EVERY status/progress card variant the gateway can paint,
3
+ * each rendered through the REAL renderer with realistic inputs.
4
+ *
5
+ * One catalogue, two consumers, so they can never disagree:
6
+ * - `telegram-plugin/tests/card-golden.test.ts` — the golden suite. Every
7
+ * variant's exact wire text is pinned in `card-variants.golden.txt`, so a
8
+ * change to the shared card layout that alters ANY card fails loudly.
9
+ * - `scripts/card-previews.ts` — the human-readable preview doc.
10
+ *
11
+ * Adding a card variant means adding it HERE (and regenerating the golden with
12
+ * `UPDATE_CARD_GOLDEN=1 npx vitest run telegram-plugin/tests/card-golden.test.ts`),
13
+ * which is what keeps the golden suite exhaustive rather than a sample.
14
+ *
15
+ * These are RENDERER-level fixtures. The lifecycle behaviour that decides WHICH
16
+ * variant gets painted (dispatch → running → 2 workers → spill → done → failed
17
+ * → superseded) is covered end-to-end from the feed manager in
18
+ * `card-lifecycle-render.test.ts`; the two suites are complementary, not
19
+ * duplicates.
20
+ */
21
+ import {
22
+ renderActivityFeed,
23
+ renderActivityFeedWithNested,
24
+ renderCombinedWorkerFeed,
25
+ type CombinedWorkerRow,
26
+ type SessionActivityHeader,
27
+ } from '../tool-activity-summary.js'
28
+ import {
29
+ renderWorkerActivity,
30
+ WORKER_CARD_SUPERSEDED_BODY,
31
+ type WorkerActivityView,
32
+ } from '../worker-activity-feed.js'
33
+
34
+ /** Which renderer path a variant exercises, for the preview doc's inventory. */
35
+ export interface CardVariant {
36
+ /** Stable identity — the golden file keys on this, so renaming rewrites it. */
37
+ name: string
38
+ /** The call path, for the preview doc. */
39
+ fn: string
40
+ /** When a user actually sees this card. */
41
+ when: string
42
+ /**
43
+ * How the variant reaches the wire:
44
+ * - `shared` — composed by the one card layout core (`card-layout.ts`).
45
+ * - `constant` — a static notice with no live state to render (see the
46
+ * superseded card); its title line still comes from the shared composer.
47
+ */
48
+ layout?: 'shared' | 'constant'
49
+ /** The real render. `null` means the renderer declined to emit anything. */
50
+ render: () => string | null
51
+ }
52
+
53
+ const agentHeader = (over: Partial<SessionActivityHeader> = {}): SessionActivityHeader => ({
54
+ label: 'Agent',
55
+ elapsedMs: 95_000,
56
+ toolCount: 12,
57
+ state: 'running',
58
+ model: 'claude-opus-4-8',
59
+ totalTokens: 41_200,
60
+ ...over,
61
+ })
62
+
63
+ const AGENT_STEPS = [
64
+ 'Searching memory for card render paths',
65
+ 'Reading telegram-plugin/tool-activity-summary.ts',
66
+ 'Reading telegram-plugin/worker-activity-feed.ts',
67
+ 'Grepping for renderStatusCard callers',
68
+ 'Drafting the inventory table',
69
+ ]
70
+
71
+ const workerView = (over: Partial<WorkerActivityView> = {}): WorkerActivityView => ({
72
+ description: 'Audit every card render path and de-indent the worker card',
73
+ state: 'running',
74
+ elapsedMs: 143_000,
75
+ toolCount: 31,
76
+ lastTool: null,
77
+ latestSummary: '',
78
+ narrativeLines: [
79
+ 'Fetching origin and branching off main',
80
+ 'Reading telegram-plugin/status-no-truncate.ts',
81
+ 'Mapping renderStatusCard callers',
82
+ 'Writing the preview harness',
83
+ ],
84
+ model: 'claude-opus-4-8',
85
+ totalTokens: 88_400,
86
+ ...over,
87
+ })
88
+
89
+ const row = (
90
+ n: number,
91
+ desc: string,
92
+ hist: string[],
93
+ over: Partial<CombinedWorkerRow> = {},
94
+ ): CombinedWorkerRow => ({
95
+ ordinal: n,
96
+ description: desc,
97
+ elapsedMs: 60_000 * n + 15_000,
98
+ toolCount: 7 * n,
99
+ totalTokens: 20_000 * n,
100
+ currentStep: hist[hist.length - 1] ?? 'starting…',
101
+ historyLines: hist,
102
+ model: 'claude-opus-4-8',
103
+ ...over,
104
+ })
105
+
106
+ const H = (k: number, tag: string): string[] =>
107
+ Array.from({ length: k }, (_, i) => `${tag} step ${i + 1}`)
108
+
109
+ /** A 700-char step — long enough to blow the per-line clip and force the backstop. */
110
+ const LONG = 'A'.repeat(700)
111
+
112
+ export const CARD_VARIANTS: CardVariant[] = [
113
+ {
114
+ name: 'Agent card — running',
115
+ fn: 'renderActivityFeed → renderStatusCard',
116
+ when: 'Every turn that labels a tool. Pinned in the session chat, edited in place.',
117
+ render: () => renderActivityFeed(AGENT_STEPS, false, '', undefined, agentHeader()),
118
+ },
119
+ {
120
+ name: 'Agent card — final (turn complete)',
121
+ fn: 'renderActivityFeed → renderStatusCard',
122
+ when: 'The terminal edit when the turn ends: every step struck, `✓ N steps` footer.',
123
+ render: () =>
124
+ renderActivityFeed(AGENT_STEPS, true, '', 17, agentHeader({ state: 'done', elapsedMs: 214_000 })),
125
+ },
126
+ {
127
+ name: 'Agent card — steps only (no header)',
128
+ fn: 'renderActivityFeed → renderStatusCard (header omitted)',
129
+ when: 'Legacy/direct callers (`appendActivityLine`, `appendActivityLabel`) that pass no header.',
130
+ render: () => renderActivityFeed(AGENT_STEPS.slice(0, 3)),
131
+ },
132
+ {
133
+ name: 'Agent card — with nested foreground sub-agent',
134
+ fn: 'renderActivityFeedWithNested → renderStatusCard (childSteps)',
135
+ when: 'A foreground Task/Agent runs inside the turn: parent lines all struck, the live `→` sits in the `↳` child block.',
136
+ render: () =>
137
+ renderActivityFeedWithNested(
138
+ AGENT_STEPS.slice(0, 3),
139
+ ['Reading the repo conventions', 'Running the scoped test suite', 'Summarising findings'],
140
+ false,
141
+ ' · 24s',
142
+ undefined,
143
+ agentHeader(),
144
+ ),
145
+ },
146
+ {
147
+ name: 'Agent card — liveness early-open (no tools yet)',
148
+ fn: 'narrative-lane.ts → renderActivityFeedWithNested → renderStatusCard',
149
+ when: 'A silent turn passes the liveness threshold before any tool is labelled — the card opens on `Working…`.',
150
+ render: () =>
151
+ renderActivityFeedWithNested(['Working…'], [], false, ' · 32s', undefined, {
152
+ label: 'Agent',
153
+ elapsedMs: 32_000,
154
+ toolCount: 0,
155
+ state: 'running',
156
+ model: 'claude-opus-4-8',
157
+ }),
158
+ },
159
+ {
160
+ name: 'Agent card — liveness finalised',
161
+ fn: 'narrative-lane.ts → renderActivityFeedWithNested → renderStatusCard',
162
+ when: 'The liveness-only card at turn end: `→ Working…` becomes a done record instead of freezing live.',
163
+ render: () =>
164
+ renderActivityFeedWithNested(['Working…'], [], true, '', undefined, {
165
+ label: 'Agent',
166
+ elapsedMs: 88_000,
167
+ toolCount: 0,
168
+ state: 'done',
169
+ model: 'claude-opus-4-8',
170
+ }),
171
+ },
172
+ {
173
+ name: 'Agent card — post-answer background activity',
174
+ fn: 'narrative-lane.ts → renderActivityFeedWithNested → renderStatusCard',
175
+ when: 'The reply already went out and a background sub-agent is still running — the card surfaces below the reply.',
176
+ render: () =>
177
+ renderActivityFeedWithNested(['Working in background…'], [], false, ' · 1m10s', undefined, {
178
+ label: 'Agent',
179
+ elapsedMs: 130_000,
180
+ toolCount: 3,
181
+ state: 'running',
182
+ model: 'claude-opus-4-8',
183
+ }),
184
+ },
185
+ {
186
+ name: 'Agent card — over-long steps (per-line clip)',
187
+ fn: 'renderStatusCard → cleanStepLine (STATUS_LINE_MAX)',
188
+ when: 'A step line far exceeds STATUS_LINE_MAX: each is clipped to 200 chars + `…` before escaping. (This is also why the card-wide `fitCardToBudget` backstop is unreachable on the agent card — 5 clipped lines cannot approach 32768. The shrink levels are genuinely exercised by the WORKERS spill variant.)',
189
+ render: () =>
190
+ renderActivityFeed(
191
+ [LONG, LONG, LONG, 'Finally a short newest step'],
192
+ false,
193
+ '',
194
+ undefined,
195
+ agentHeader(),
196
+ ),
197
+ },
198
+ {
199
+ name: 'Worker card — just dispatched (no steps yet)',
200
+ fn: 'renderWorkerActivity → renderStatusCard (emptyPlaceholder)',
201
+ when: 'The first paint of a background worker, before it has produced a narrative line.',
202
+ render: () =>
203
+ renderWorkerActivity(
204
+ workerView({ narrativeLines: [], latestSummary: '', elapsedMs: 1_200, toolCount: 0 }),
205
+ ),
206
+ },
207
+ {
208
+ name: 'Worker card — single worker running',
209
+ fn: 'renderWorkerActivity → renderStatusCard',
210
+ when: 'Exactly one background worker live in the chat. Pinned; edited in place.',
211
+ render: () => renderWorkerActivity(workerView(), ' · 18s'),
212
+ },
213
+ {
214
+ name: 'Worker card — done (with result recap)',
215
+ fn: 'renderWorkerActivity → renderStatusCard (result block)',
216
+ when: 'The worker finished successfully: feed all-struck, rule, then the ✅ result paragraph.',
217
+ render: () =>
218
+ renderWorkerActivity(
219
+ workerView({
220
+ state: 'done',
221
+ elapsedMs: 902_000,
222
+ latestSummary:
223
+ 'Inventoried 14 card variants, found renderCombinedWorkerFeed forks the header + budget logic, and de-indented the worker card. PR #0000.',
224
+ }),
225
+ ),
226
+ },
227
+ {
228
+ name: 'Worker card — failed',
229
+ fn: 'renderWorkerActivity → renderStatusCard (result block, ⚠️)',
230
+ when: 'The worker errored: header line 2 reads `failed`, result block carries the error recap.',
231
+ render: () =>
232
+ renderWorkerActivity(
233
+ workerView({
234
+ state: 'failed',
235
+ elapsedMs: 61_000,
236
+ latestSummary: 'Scoped test run failed: 3 assertions in card-type-distinguishability.test.ts.',
237
+ }),
238
+ ),
239
+ },
240
+ {
241
+ name: 'Worker card — incomplete (killed / timed out)',
242
+ fn: 'renderWorkerActivity → renderStatusCard (result suppressed by invariant)',
243
+ when: 'The worker was terminated before producing a result — the renderer refuses to fabricate a result block.',
244
+ render: () =>
245
+ renderWorkerActivity(
246
+ workerView({
247
+ state: 'incomplete',
248
+ elapsedMs: 1_805_000,
249
+ latestSummary: 'stray text that must not render',
250
+ }),
251
+ ),
252
+ },
253
+ {
254
+ name: 'WORKERS card — 2 workers',
255
+ fn: 'renderCombinedWorkerFeed → the shared card spec',
256
+ when: '2+ background workers live in the same chat/thread coalesce into ONE message. Depth = 5 each.',
257
+ render: () =>
258
+ renderCombinedWorkerFeed(
259
+ [
260
+ row(1, 'Audit every card render path', H(5, 'audit')),
261
+ row(2, 'Fix the hindsight recall floor', H(5, 'recall')),
262
+ ],
263
+ { maxRows: 6 },
264
+ ),
265
+ },
266
+ {
267
+ name: 'WORKERS card — 4 workers',
268
+ fn: 'renderCombinedWorkerFeed → the shared card spec',
269
+ when: "Depth degrades to the floor of Ken's 6/5/4/3 curve.",
270
+ render: () =>
271
+ renderCombinedWorkerFeed(
272
+ [
273
+ row(1, 'Audit every card render path', H(4, 'audit')),
274
+ row(2, 'Fix the hindsight recall floor', H(4, 'recall')),
275
+ row(3, 'Release v0.19.24', H(4, 'release')),
276
+ row(4, 'Investigate the flaky UAT scenario', H(4, 'uat')),
277
+ ],
278
+ { maxRows: 6 },
279
+ ),
280
+ },
281
+ {
282
+ name: 'WORKERS card — 8 workers (spill)',
283
+ fn: 'renderCombinedWorkerFeed → fitCardToBudget (row-count shrink levels)',
284
+ when: 'Beyond the design fan-out the NEWEST rows collapse into a single `+M more working…` line.',
285
+ render: () =>
286
+ renderCombinedWorkerFeed(
287
+ Array.from({ length: 8 }, (_, i) => row(i + 1, `Background task number ${i + 1}`, H(3, `t${i + 1}`))),
288
+ { maxRows: 6 },
289
+ ),
290
+ },
291
+ {
292
+ name: 'WORKERS card — survivors keep their ordinals',
293
+ fn: 'renderCombinedWorkerFeed → the shared card spec',
294
+ when: 'Worker 1 finished; 2 and 3 keep their numbers, so the card shows `2.`/`3.` with no `1.`',
295
+ render: () =>
296
+ renderCombinedWorkerFeed(
297
+ [
298
+ row(2, 'Fix the hindsight recall floor', H(4, 'recall')),
299
+ row(3, 'Release v0.19.24', H(4, 'release')),
300
+ ],
301
+ { maxRows: 6 },
302
+ ),
303
+ },
304
+ {
305
+ name: 'WORKERS card — a worker with no history yet',
306
+ fn: 'renderCombinedWorkerFeed → section placeholder',
307
+ when: 'A freshly dispatched worker joins a group that already has a live card.',
308
+ render: () =>
309
+ renderCombinedWorkerFeed(
310
+ [
311
+ row(1, 'Audit every card render path', H(5, 'audit')),
312
+ row(2, 'Freshly dispatched worker', [], { currentStep: '', historyLines: undefined }),
313
+ ],
314
+ { maxRows: 6 },
315
+ ),
316
+ },
317
+ {
318
+ name: 'Worker card — superseded (group message rotated)',
319
+ layout: 'constant',
320
+ fn: 'WORKER_CARD_SUPERSEDED_BODY (static notice; title line via renderCardTitleLine)',
321
+ when: 'The shared worker message hit its lifetime cap and a fresh card was opened; the retired message is finalised to this static note so it does not read as a stuck worker.',
322
+ render: () => WORKER_CARD_SUPERSEDED_BODY,
323
+ },
324
+ {
325
+ name: 'Agent card — rolling-window overflow',
326
+ fn: 'renderActivityFeed → renderStatusCard → emitSection',
327
+ when: 'More steps than the rolling window: the oldest collapse to a `✓ +N earlier…` header.',
328
+ render: () =>
329
+ renderActivityFeed(
330
+ Array.from({ length: 14 }, (_, i) => `Doing thing number ${i + 1}`),
331
+ false,
332
+ '',
333
+ undefined,
334
+ agentHeader(),
335
+ ),
336
+ },
337
+ {
338
+ name: 'Agent card — nested sub-agent with child overflow',
339
+ fn: 'renderActivityFeedWithNested → renderStatusCard',
340
+ when: 'The foreground sub-agent has produced more steps than the window: a `↳ +N earlier…` header appears in the child block.',
341
+ render: () =>
342
+ renderActivityFeedWithNested(
343
+ AGENT_STEPS.slice(0, 2),
344
+ Array.from({ length: 11 }, (_, i) => `Child step ${i + 1}`),
345
+ false,
346
+ '',
347
+ undefined,
348
+ agentHeader(),
349
+ ),
350
+ },
351
+ ]
352
+
353
+ /** Section delimiter in the golden file. Chosen so no card body can contain it. */
354
+ export const GOLDEN_DELIMITER = '\n===== CARD ====='
355
+
356
+ /**
357
+ * The whole catalogue as ONE deterministic string — what the golden file holds.
358
+ * Every variant contributes its name and its exact rendered bytes.
359
+ */
360
+ export function renderGoldenDocument(): string {
361
+ return (
362
+ CARD_VARIANTS.map(
363
+ (v) => `${GOLDEN_DELIMITER} ${v.name}\n${v.render() ?? '(null — renderer returned nothing)'}`,
364
+ ).join('\n') + '\n'
365
+ )
366
+ }