reflectt-node 0.1.14 → 0.1.16

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 (218) hide show
  1. package/README.md +54 -0
  2. package/defaults/TEAM-ROLES.starter.yaml +87 -0
  3. package/defaults/lane-templates/ops.json +32 -0
  4. package/defaults/lane-templates/workflow.json +32 -0
  5. package/defaults/reviewer-routing.yaml +34 -0
  6. package/dist/activationEvents.d.ts +7 -1
  7. package/dist/activationEvents.d.ts.map +1 -1
  8. package/dist/activationEvents.js +29 -3
  9. package/dist/activationEvents.js.map +1 -1
  10. package/dist/activity-stream-normalizer.d.ts +37 -0
  11. package/dist/activity-stream-normalizer.d.ts.map +1 -0
  12. package/dist/activity-stream-normalizer.js +101 -0
  13. package/dist/activity-stream-normalizer.js.map +1 -0
  14. package/dist/agent-exec-guardrail.d.ts +9 -0
  15. package/dist/agent-exec-guardrail.d.ts.map +1 -0
  16. package/dist/agent-exec-guardrail.js +24 -0
  17. package/dist/agent-exec-guardrail.js.map +1 -0
  18. package/dist/agent-exec-guardrail.test.d.ts +2 -0
  19. package/dist/agent-exec-guardrail.test.d.ts.map +1 -0
  20. package/dist/agent-exec-guardrail.test.js +55 -0
  21. package/dist/agent-exec-guardrail.test.js.map +1 -0
  22. package/dist/agent-interface.d.ts +137 -0
  23. package/dist/agent-interface.d.ts.map +1 -0
  24. package/dist/agent-interface.js +463 -0
  25. package/dist/agent-interface.js.map +1 -0
  26. package/dist/agent-notifications.d.ts +51 -0
  27. package/dist/agent-notifications.d.ts.map +1 -0
  28. package/dist/agent-notifications.js +104 -0
  29. package/dist/agent-notifications.js.map +1 -0
  30. package/dist/agent-runs.d.ts +31 -7
  31. package/dist/agent-runs.d.ts.map +1 -1
  32. package/dist/agent-runs.js +137 -28
  33. package/dist/agent-runs.js.map +1 -1
  34. package/dist/artifact-mirror.d.ts.map +1 -1
  35. package/dist/artifact-mirror.js +4 -1
  36. package/dist/artifact-mirror.js.map +1 -1
  37. package/dist/assignment.d.ts.map +1 -1
  38. package/dist/assignment.js +54 -2
  39. package/dist/assignment.js.map +1 -1
  40. package/dist/boardHealthWorker.d.ts.map +1 -1
  41. package/dist/boardHealthWorker.js +15 -1
  42. package/dist/boardHealthWorker.js.map +1 -1
  43. package/dist/canvas-auto-state.d.ts +58 -0
  44. package/dist/canvas-auto-state.d.ts.map +1 -0
  45. package/dist/canvas-auto-state.js +89 -0
  46. package/dist/canvas-auto-state.js.map +1 -0
  47. package/dist/canvas-routes.d.ts +36 -0
  48. package/dist/canvas-routes.d.ts.map +1 -0
  49. package/dist/canvas-routes.js +47 -0
  50. package/dist/canvas-routes.js.map +1 -0
  51. package/dist/capability-readiness.d.ts +28 -0
  52. package/dist/capability-readiness.d.ts.map +1 -0
  53. package/dist/capability-readiness.js +162 -0
  54. package/dist/capability-readiness.js.map +1 -0
  55. package/dist/channels.d.ts.map +1 -1
  56. package/dist/channels.js +1 -0
  57. package/dist/channels.js.map +1 -1
  58. package/dist/cli.js +179 -4
  59. package/dist/cli.js.map +1 -1
  60. package/dist/cloud.d.ts +5 -0
  61. package/dist/cloud.d.ts.map +1 -1
  62. package/dist/cloud.js +485 -18
  63. package/dist/cloud.js.map +1 -1
  64. package/dist/comms-routing-policy.d.ts +31 -0
  65. package/dist/comms-routing-policy.d.ts.map +1 -0
  66. package/dist/comms-routing-policy.js +128 -0
  67. package/dist/comms-routing-policy.js.map +1 -0
  68. package/dist/config.d.ts.map +1 -1
  69. package/dist/config.js +1 -0
  70. package/dist/config.js.map +1 -1
  71. package/dist/continuity-loop.d.ts.map +1 -1
  72. package/dist/continuity-loop.js +26 -0
  73. package/dist/continuity-loop.js.map +1 -1
  74. package/dist/cost-enforcement.d.ts.map +1 -1
  75. package/dist/cost-enforcement.js +22 -0
  76. package/dist/cost-enforcement.js.map +1 -1
  77. package/dist/db.d.ts.map +1 -1
  78. package/dist/db.js +56 -5
  79. package/dist/db.js.map +1 -1
  80. package/dist/doctor.js +2 -2
  81. package/dist/e2e-loop-proof.test.js +11 -1
  82. package/dist/e2e-loop-proof.test.js.map +1 -1
  83. package/dist/events.d.ts +4 -2
  84. package/dist/events.d.ts.map +1 -1
  85. package/dist/events.js +22 -1
  86. package/dist/events.js.map +1 -1
  87. package/dist/executionSweeper.d.ts.map +1 -1
  88. package/dist/executionSweeper.js +155 -0
  89. package/dist/executionSweeper.js.map +1 -1
  90. package/dist/health.d.ts +21 -1
  91. package/dist/health.d.ts.map +1 -1
  92. package/dist/health.js +164 -19
  93. package/dist/health.js.map +1 -1
  94. package/dist/inbox.d.ts +4 -0
  95. package/dist/inbox.d.ts.map +1 -1
  96. package/dist/inbox.js +38 -1
  97. package/dist/inbox.js.map +1 -1
  98. package/dist/index.js +90 -14
  99. package/dist/index.js.map +1 -1
  100. package/dist/insight-auto-tagger.d.ts +58 -0
  101. package/dist/insight-auto-tagger.d.ts.map +1 -0
  102. package/dist/insight-auto-tagger.js +331 -0
  103. package/dist/insight-auto-tagger.js.map +1 -0
  104. package/dist/insight-task-bridge.d.ts +9 -0
  105. package/dist/insight-task-bridge.d.ts.map +1 -1
  106. package/dist/insight-task-bridge.js +43 -7
  107. package/dist/insight-task-bridge.js.map +1 -1
  108. package/dist/insights.d.ts +6 -0
  109. package/dist/insights.d.ts.map +1 -1
  110. package/dist/insights.js +13 -0
  111. package/dist/insights.js.map +1 -1
  112. package/dist/lane-config.d.ts.map +1 -1
  113. package/dist/lane-config.js +1 -0
  114. package/dist/lane-config.js.map +1 -1
  115. package/dist/lane-template-successor.d.ts +13 -0
  116. package/dist/lane-template-successor.d.ts.map +1 -0
  117. package/dist/lane-template-successor.js +132 -0
  118. package/dist/lane-template-successor.js.map +1 -0
  119. package/dist/local-whisper.d.ts +21 -0
  120. package/dist/local-whisper.d.ts.map +1 -0
  121. package/dist/local-whisper.js +137 -0
  122. package/dist/local-whisper.js.map +1 -0
  123. package/dist/macos-accessibility.d.ts +50 -0
  124. package/dist/macos-accessibility.d.ts.map +1 -0
  125. package/dist/macos-accessibility.js +185 -0
  126. package/dist/macos-accessibility.js.map +1 -0
  127. package/dist/manage.d.ts.map +1 -1
  128. package/dist/manage.js +47 -1
  129. package/dist/manage.js.map +1 -1
  130. package/dist/mcp.d.ts.map +1 -1
  131. package/dist/mcp.js +123 -0
  132. package/dist/mcp.js.map +1 -1
  133. package/dist/notification-worker.d.ts +66 -0
  134. package/dist/notification-worker.d.ts.map +1 -0
  135. package/dist/notification-worker.js +232 -0
  136. package/dist/notification-worker.js.map +1 -0
  137. package/dist/openclaw-usage-sync.d.ts +28 -0
  138. package/dist/openclaw-usage-sync.d.ts.map +1 -0
  139. package/dist/openclaw-usage-sync.js +161 -0
  140. package/dist/openclaw-usage-sync.js.map +1 -0
  141. package/dist/policy.js +1 -1
  142. package/dist/policy.js.map +1 -1
  143. package/dist/pr-link-reconciler.d.ts +61 -0
  144. package/dist/pr-link-reconciler.d.ts.map +1 -0
  145. package/dist/pr-link-reconciler.js +127 -0
  146. package/dist/pr-link-reconciler.js.map +1 -0
  147. package/dist/preflight.js +2 -2
  148. package/dist/presence-narrator.d.ts +52 -0
  149. package/dist/presence-narrator.d.ts.map +1 -0
  150. package/dist/presence-narrator.js +193 -0
  151. package/dist/presence-narrator.js.map +1 -0
  152. package/dist/presence.d.ts +2 -0
  153. package/dist/presence.d.ts.map +1 -1
  154. package/dist/presence.js +23 -3
  155. package/dist/presence.js.map +1 -1
  156. package/dist/product-observation-source.d.ts +52 -0
  157. package/dist/product-observation-source.d.ts.map +1 -0
  158. package/dist/product-observation-source.js +326 -0
  159. package/dist/product-observation-source.js.map +1 -0
  160. package/dist/reflection-automation.d.ts +25 -0
  161. package/dist/reflection-automation.d.ts.map +1 -1
  162. package/dist/reflection-automation.js +163 -42
  163. package/dist/reflection-automation.js.map +1 -1
  164. package/dist/review-autoclose.d.ts +62 -0
  165. package/dist/review-autoclose.d.ts.map +1 -0
  166. package/dist/review-autoclose.js +75 -0
  167. package/dist/review-autoclose.js.map +1 -0
  168. package/dist/routing-enforcement.test.js +32 -56
  169. package/dist/routing-enforcement.test.js.map +1 -1
  170. package/dist/sentry-webhook.d.ts +69 -0
  171. package/dist/sentry-webhook.d.ts.map +1 -0
  172. package/dist/sentry-webhook.js +88 -0
  173. package/dist/sentry-webhook.js.map +1 -0
  174. package/dist/sentry.d.ts +29 -0
  175. package/dist/sentry.d.ts.map +1 -0
  176. package/dist/sentry.js +86 -0
  177. package/dist/sentry.js.map +1 -0
  178. package/dist/server.d.ts.map +1 -1
  179. package/dist/server.js +5130 -233
  180. package/dist/server.js.map +1 -1
  181. package/dist/stale-candidate-reconciler.d.ts +69 -0
  182. package/dist/stale-candidate-reconciler.d.ts.map +1 -0
  183. package/dist/stale-candidate-reconciler.js +236 -0
  184. package/dist/stale-candidate-reconciler.js.map +1 -0
  185. package/dist/system-loop-state.d.ts +1 -1
  186. package/dist/system-loop-state.d.ts.map +1 -1
  187. package/dist/system-loop-state.js +1 -0
  188. package/dist/system-loop-state.js.map +1 -1
  189. package/dist/taskPrecheck.d.ts.map +1 -1
  190. package/dist/taskPrecheck.js +47 -2
  191. package/dist/taskPrecheck.js.map +1 -1
  192. package/dist/tasks.d.ts.map +1 -1
  193. package/dist/tasks.js +130 -0
  194. package/dist/tasks.js.map +1 -1
  195. package/dist/trust-events.d.ts +61 -0
  196. package/dist/trust-events.d.ts.map +1 -0
  197. package/dist/trust-events.js +148 -0
  198. package/dist/trust-events.js.map +1 -0
  199. package/dist/types.d.ts +1 -0
  200. package/dist/types.d.ts.map +1 -1
  201. package/dist/usage-tracking.d.ts +6 -0
  202. package/dist/usage-tracking.d.ts.map +1 -1
  203. package/dist/usage-tracking.js +14 -0
  204. package/dist/usage-tracking.js.map +1 -1
  205. package/dist/voice-sessions.d.ts +51 -0
  206. package/dist/voice-sessions.d.ts.map +1 -0
  207. package/dist/voice-sessions.js +143 -0
  208. package/dist/voice-sessions.js.map +1 -0
  209. package/dist/workflow-templates.d.ts.map +1 -1
  210. package/dist/workflow-templates.js +18 -3
  211. package/dist/workflow-templates.js.map +1 -1
  212. package/dist/working-contract.d.ts +22 -1
  213. package/dist/working-contract.d.ts.map +1 -1
  214. package/dist/working-contract.js +31 -2
  215. package/dist/working-contract.js.map +1 -1
  216. package/package.json +4 -4
  217. package/public/dashboard.js +12 -4
  218. package/public/docs.md +98 -10
@@ -0,0 +1,326 @@
1
+ /**
2
+ * product-observation-source.ts
3
+ *
4
+ * ProductObservationSource — Phase 1 cheap HTTP health probes.
5
+ *
6
+ * Triggered by the continuity loop when an agent's queue empties after a
7
+ * recent ship. Runs lightweight health probes against the local node and
8
+ * emits findings as Reflection objects, which flow through the existing
9
+ * intake pipeline (reflection → insight → task).
10
+ *
11
+ * Phase 1 probes (this file):
12
+ * - HealthProbe: GET /health — checks status + response time
13
+ * - AgentsProbe: GET /health/agents — stale/unhealthy agent detection
14
+ * - TasksProbe: GET /tasks — detects stuck doing/validating tasks
15
+ * - ChatProbe: GET /chat/messages — checks message recency (team comms gap)
16
+ *
17
+ * Phase 2 (browser probe) is out-of-scope for this task — opt-in, policy flag.
18
+ *
19
+ * Integration: call runProductObservation(agent) from tickContinuityLoop()
20
+ * after insight promotion misses, gated on:
21
+ * - queue below floor
22
+ * - agent shipped something in last 4h
23
+ * - cooldown not active (30m per agent)
24
+ */
25
+ import { createReflection } from './reflections.js';
26
+ import { ingestReflection } from './insights.js';
27
+ import { getDb } from './db.js';
28
+ // ── Config ─────────────────────────────────────────────────────────────────
29
+ const NODE_BASE_URL = process.env.REFLECTT_NODE_URL || 'http://127.0.0.1:4445';
30
+ const PROBE_TIMEOUT_MS = 5_000; // 5s max per probe
31
+ const SLOW_THRESHOLD_MS = 2_000; // emit finding if response >2s
32
+ const COOLDOWN_MS = 30 * 60 * 1000; // 30 min cooldown per agent
33
+ const RECENT_SHIP_WINDOW_MS = 4 * 60 * 60 * 1000; // 4h ship recency window
34
+ const KV_PREFIX = 'product_obs:'; // kv key prefix for cooldown tracking
35
+ // ── KV helpers (reuse sqlite kv table already in db.ts) ───────────────────
36
+ function kvGet(key) {
37
+ const db = getDb();
38
+ try {
39
+ const row = db.prepare('SELECT value FROM kv WHERE key = ?').get(key);
40
+ return row?.value ?? null;
41
+ }
42
+ catch {
43
+ return null;
44
+ }
45
+ }
46
+ function kvSet(key, value) {
47
+ const db = getDb();
48
+ try {
49
+ db.prepare(`INSERT INTO kv (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(key, value);
50
+ }
51
+ catch { /* kv table may not exist yet */ }
52
+ }
53
+ // ── Cooldown check ─────────────────────────────────────────────────────────
54
+ function isCoolingDown(agent, now) {
55
+ const key = `${KV_PREFIX}last_run:${agent}`;
56
+ const lastRun = kvGet(key);
57
+ if (!lastRun)
58
+ return false;
59
+ return now - Number(lastRun) < COOLDOWN_MS;
60
+ }
61
+ function recordRun(agent, now) {
62
+ kvSet(`${KV_PREFIX}last_run:${agent}`, String(now));
63
+ }
64
+ // ── Recent ship check ──────────────────────────────────────────────────────
65
+ async function agentShippedRecently(agent, now) {
66
+ try {
67
+ const ctrl = new AbortController();
68
+ const timeout = setTimeout(() => ctrl.abort(), PROBE_TIMEOUT_MS);
69
+ const res = await fetch(`${NODE_BASE_URL}/health/agents`, { signal: ctrl.signal });
70
+ clearTimeout(timeout);
71
+ if (!res.ok)
72
+ return false;
73
+ const data = await res.json();
74
+ const entry = data.agents?.find(a => a.agent === agent);
75
+ if (!entry?.last_shipped_at)
76
+ return false;
77
+ return now - entry.last_shipped_at < RECENT_SHIP_WINDOW_MS;
78
+ }
79
+ catch {
80
+ return false;
81
+ }
82
+ }
83
+ // ── Probe implementations ──────────────────────────────────────────────────
84
+ async function runHealthProbe() {
85
+ const start = Date.now();
86
+ try {
87
+ const ctrl = new AbortController();
88
+ const timeout = setTimeout(() => ctrl.abort(), PROBE_TIMEOUT_MS);
89
+ const res = await fetch(`${NODE_BASE_URL}/health`, { signal: ctrl.signal });
90
+ clearTimeout(timeout);
91
+ const latencyMs = Date.now() - start;
92
+ const data = await res.json();
93
+ const ok = res.ok && data?.status === 'ok';
94
+ const slow = latencyMs > SLOW_THRESHOLD_MS;
95
+ return {
96
+ probe: 'health',
97
+ ok: ok && !slow,
98
+ latencyMs,
99
+ finding: !ok
100
+ ? `Node health check failed: status=${data?.status ?? res.status}`
101
+ : slow ? `Node health endpoint slow: ${latencyMs}ms (threshold: ${SLOW_THRESHOLD_MS}ms)` : undefined,
102
+ };
103
+ }
104
+ catch (err) {
105
+ return {
106
+ probe: 'health',
107
+ ok: false,
108
+ latencyMs: Date.now() - start,
109
+ finding: `Health probe failed: ${err.message}`,
110
+ };
111
+ }
112
+ }
113
+ async function runAgentsProbe() {
114
+ const start = Date.now();
115
+ try {
116
+ const ctrl = new AbortController();
117
+ const timeout = setTimeout(() => ctrl.abort(), PROBE_TIMEOUT_MS);
118
+ const res = await fetch(`${NODE_BASE_URL}/health/agents`, { signal: ctrl.signal });
119
+ clearTimeout(timeout);
120
+ const latencyMs = Date.now() - start;
121
+ if (!res.ok) {
122
+ return { probe: 'agents', ok: false, latencyMs, finding: `/health/agents returned ${res.status}` };
123
+ }
124
+ const data = await res.json();
125
+ const agents = data.agents ?? [];
126
+ const unhealthy = agents.filter(a => a.state && a.state !== 'healthy');
127
+ if (unhealthy.length > 0) {
128
+ const names = unhealthy.map(a => `${a.agent}(${a.state})`).join(', ');
129
+ return {
130
+ probe: 'agents',
131
+ ok: false,
132
+ latencyMs,
133
+ finding: `${unhealthy.length} agent(s) not healthy: ${names}`,
134
+ detail: unhealthy.map(a => `${a.agent}: ${a.stale_reason ?? a.state}`).join('; '),
135
+ };
136
+ }
137
+ const slow = latencyMs > SLOW_THRESHOLD_MS;
138
+ return {
139
+ probe: 'agents',
140
+ ok: !slow,
141
+ latencyMs,
142
+ finding: slow ? `Agents endpoint slow: ${latencyMs}ms` : undefined,
143
+ };
144
+ }
145
+ catch (err) {
146
+ return {
147
+ probe: 'agents',
148
+ ok: false,
149
+ latencyMs: Date.now() - start,
150
+ finding: `Agents probe failed: ${err.message}`,
151
+ };
152
+ }
153
+ }
154
+ async function runTasksProbe() {
155
+ const start = Date.now();
156
+ try {
157
+ const ctrl = new AbortController();
158
+ const timeout = setTimeout(() => ctrl.abort(), PROBE_TIMEOUT_MS);
159
+ const res = await fetch(`${NODE_BASE_URL}/tasks?status=doing&compact=true`, { signal: ctrl.signal });
160
+ clearTimeout(timeout);
161
+ const latencyMs = Date.now() - start;
162
+ if (!res.ok) {
163
+ return { probe: 'tasks', ok: false, latencyMs, finding: `/tasks returned ${res.status}` };
164
+ }
165
+ const data = await res.json();
166
+ const doingTasks = data.tasks ?? [];
167
+ const now = Date.now();
168
+ // Flag tasks doing for >4h with no update
169
+ const stuck = doingTasks.filter(t => t.updatedAt && now - t.updatedAt > 4 * 60 * 60 * 1000);
170
+ if (stuck.length > 0) {
171
+ const summaries = stuck.map(t => `${t.id} (@${t.assignee ?? 'unknown'})`).join(', ');
172
+ return {
173
+ probe: 'tasks',
174
+ ok: false,
175
+ latencyMs,
176
+ finding: `${stuck.length} task(s) stuck in doing for >4h: ${summaries}`,
177
+ };
178
+ }
179
+ const slow = latencyMs > SLOW_THRESHOLD_MS;
180
+ return {
181
+ probe: 'tasks',
182
+ ok: !slow,
183
+ latencyMs,
184
+ finding: slow ? `Tasks endpoint slow: ${latencyMs}ms` : undefined,
185
+ };
186
+ }
187
+ catch (err) {
188
+ return {
189
+ probe: 'tasks',
190
+ ok: false,
191
+ latencyMs: Date.now() - start,
192
+ finding: `Tasks probe failed: ${err.message}`,
193
+ };
194
+ }
195
+ }
196
+ async function runChatProbe() {
197
+ const start = Date.now();
198
+ try {
199
+ const ctrl = new AbortController();
200
+ const timeout = setTimeout(() => ctrl.abort(), PROBE_TIMEOUT_MS);
201
+ const res = await fetch(`${NODE_BASE_URL}/chat/messages?limit=1&compact=true`, { signal: ctrl.signal });
202
+ clearTimeout(timeout);
203
+ const latencyMs = Date.now() - start;
204
+ if (!res.ok) {
205
+ return { probe: 'chat', ok: false, latencyMs, finding: `/chat/messages returned ${res.status}` };
206
+ }
207
+ const data = await res.json();
208
+ const msgs = data.messages ?? [];
209
+ const lastMsgTs = msgs[0]?.timestamp;
210
+ const now = Date.now();
211
+ const commsGapMs = lastMsgTs ? now - lastMsgTs : null;
212
+ // Flag team comms gap >6h as a finding
213
+ const COMMS_GAP_THRESHOLD_MS = 6 * 60 * 60 * 1000;
214
+ if (commsGapMs !== null && commsGapMs > COMMS_GAP_THRESHOLD_MS) {
215
+ const hoursAgo = Math.floor(commsGapMs / 3_600_000);
216
+ return {
217
+ probe: 'chat',
218
+ ok: false,
219
+ latencyMs,
220
+ finding: `Team comms gap: last message was ${hoursAgo}h ago`,
221
+ detail: `No chat activity in #general for ${hoursAgo}h — team may be siloed or blocked.`,
222
+ };
223
+ }
224
+ const slow = latencyMs > SLOW_THRESHOLD_MS;
225
+ return {
226
+ probe: 'chat',
227
+ ok: !slow,
228
+ latencyMs,
229
+ finding: slow ? `Chat endpoint slow: ${latencyMs}ms` : undefined,
230
+ };
231
+ }
232
+ catch (err) {
233
+ return {
234
+ probe: 'chat',
235
+ ok: false,
236
+ latencyMs: Date.now() - start,
237
+ finding: `Chat probe failed: ${err.message}`,
238
+ };
239
+ }
240
+ }
241
+ // ── Reflection injection ───────────────────────────────────────────────────
242
+ function findingToReflection(finding, agent) {
243
+ const severity = finding.latencyMs > SLOW_THRESHOLD_MS && finding.ok === false ? 'high' : 'medium';
244
+ return {
245
+ pain: finding.finding ?? `Probe ${finding.probe} reported an issue`,
246
+ impact: `Detected automatically by product observation probe — ${finding.probe} check failed or degraded`,
247
+ evidence: [`probe:${finding.probe}:${finding.ok ? 'slow' : 'failed'}`, finding.detail ?? finding.finding ?? 'no detail'].filter(Boolean),
248
+ went_well: 'Automated monitoring caught this before a human needed to investigate',
249
+ suspected_why: `${finding.probe} endpoint is either degraded or a background condition is unresolved`,
250
+ proposed_fix: `Investigate ${finding.probe} endpoint and resolve the underlying condition`,
251
+ confidence: 6,
252
+ role_type: 'agent',
253
+ author: `product-observation:${agent}`,
254
+ severity,
255
+ tags: [`probe:${finding.probe}`, 'automated', 'product-observation'],
256
+ metadata: {
257
+ probe: finding.probe,
258
+ latency_ms: finding.latencyMs,
259
+ source: 'product-observation-source',
260
+ agent,
261
+ },
262
+ };
263
+ }
264
+ // ── Main entry point ───────────────────────────────────────────────────────
265
+ /**
266
+ * Run product observation probes for an agent.
267
+ *
268
+ * Gating (all must be true to run):
269
+ * 1. Agent shipped something in the last 4h
270
+ * 2. Cooldown not active (30m since last run for this agent)
271
+ *
272
+ * Queue-empty check is the caller's responsibility (tickContinuityLoop).
273
+ */
274
+ export async function runProductObservation(agent, opts) {
275
+ const now = Date.now();
276
+ // Cooldown gate
277
+ if (!opts?.skipCooldown && isCoolingDown(agent, now)) {
278
+ const key = `${KV_PREFIX}last_run:${agent}`;
279
+ const lastRun = Number(kvGet(key) ?? 0);
280
+ const waitMin = Math.ceil((COOLDOWN_MS - (now - lastRun)) / 60_000);
281
+ return { agent, probesRun: 0, findings: [], reflectionsCreated: 0, skipped: `cooldown active (${waitMin}m remaining)` };
282
+ }
283
+ // Recent ship gate
284
+ if (!opts?.skipShipCheck) {
285
+ const shipped = await agentShippedRecently(agent, now);
286
+ if (!shipped) {
287
+ return { agent, probesRun: 0, findings: [], reflectionsCreated: 0, skipped: 'no recent ship in last 4h' };
288
+ }
289
+ }
290
+ // Record this run for cooldown tracking
291
+ recordRun(agent, now);
292
+ // Run all Phase 1 probes in parallel
293
+ const results = await Promise.allSettled([
294
+ runHealthProbe(),
295
+ runAgentsProbe(),
296
+ runTasksProbe(),
297
+ runChatProbe(),
298
+ ]);
299
+ const probeResults = results.map((r, i) => {
300
+ if (r.status === 'fulfilled')
301
+ return r.value;
302
+ const names = ['health', 'agents', 'tasks', 'chat'];
303
+ return { probe: names[i] ?? String(i), ok: false, latencyMs: 0, finding: `Probe threw: ${r.reason.message}` };
304
+ });
305
+ // Only emit reflections for probes with findings (failed or slow)
306
+ const withFindings = probeResults.filter(p => p.finding);
307
+ let reflectionsCreated = 0;
308
+ for (const finding of withFindings) {
309
+ try {
310
+ const input = findingToReflection(finding, agent);
311
+ const reflection = createReflection(input);
312
+ await ingestReflection(reflection);
313
+ reflectionsCreated++;
314
+ }
315
+ catch (err) {
316
+ console.warn(`[product-observation] failed to ingest reflection for ${finding.probe}:`, err.message);
317
+ }
318
+ }
319
+ return {
320
+ agent,
321
+ probesRun: probeResults.length,
322
+ findings: withFindings,
323
+ reflectionsCreated,
324
+ };
325
+ }
326
+ //# sourceMappingURL=product-observation-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-observation-source.js","sourceRoot":"","sources":["../src/product-observation-source.ts"],"names":[],"mappings":"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"}
@@ -34,6 +34,28 @@ interface PendingNudge {
34
34
  doneAt: number;
35
35
  nudgeAt: number;
36
36
  }
37
+ export type ReflectionTier = 'none' | 'digest' | 'mention' | 'escalate' | 'immediate';
38
+ /**
39
+ * 4-tier reflection reminder decision per SIGNAL-ROUTING Change 2 spec.
40
+ *
41
+ * | Overdue | Tier | Channel |
42
+ * |------------|-----------|---------|
43
+ * | < 14h | none | — |
44
+ * | 14h–24h | digest | #ops (batched, no @mention) |
45
+ * | 24h–48h | mention | #ops with @mention, once per 24h |
46
+ * | 48h+ | escalate | #ops with @kai, once per 48h |
47
+ * | post-task | immediate | #general direct to agent |
48
+ */
49
+ export declare function getReflectionTier(agent: string, lastReflectionAt: number, justCompletedTask: boolean, nowMs?: number): ReflectionTier;
50
+ /** Exposed for tests — reset dedup state between test runs */
51
+ export declare function _resetTierDedupForTest(): void;
52
+ /**
53
+ * Dispatch a reflection reminder based on the computed tier.
54
+ * digest → batchNag (existing batch-before-post gate, Change 4)
55
+ * mention/escalate → immediate routeMessage to #ops
56
+ * immediate → routeMessage to #general direct to agent
57
+ */
58
+ export declare function dispatchReflectionTier(agent: string, tier: ReflectionTier, hoursSince: number, lastReflectionAt: number, config: ReflectionNudgeConfig): Promise<void>;
37
59
  export declare function ensureReflectionTrackingTable(): void;
38
60
  /**
39
61
  * Called when a task transitions to done. Queues a reflection nudge.
@@ -53,6 +75,9 @@ export declare function tickReflectionNudges(): Promise<{
53
75
  total: number;
54
76
  }>;
55
77
  export declare function getReflectionSLAs(): ReflectionSLA[];
78
+ export declare function getBatchWindowMs(): number;
79
+ export declare const _nagBatch: Map<string, string[]>;
80
+ export declare function _flushNagBatch(): void;
56
81
  export declare function _clearReflectionTracking(): void;
57
82
  export declare function _getPendingNudges(): PendingNudge[];
58
83
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"reflection-automation.d.ts","sourceRoot":"","sources":["../src/reflection-automation.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAOtC,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAA;IAChB,4EAA4E;IAC5E,gBAAgB,EAAE,MAAM,CAAA;IACxB,yDAAyD;IACzD,mBAAmB,EAAE,MAAM,CAAA;IAC3B,iDAAiD;IACjD,WAAW,EAAE,MAAM,CAAA;IACnB,oDAAoD;IACpD,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAA;IACf,mEAAmE;IACnE,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACxC,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,4DAA4D;IAC5D,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAC9B;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,4BAA4B,EAAE,MAAM,CAAA;IACpC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,MAAM,EAAE,SAAS,GAAG,KAAK,GAAG,SAAS,CAAA;CACtC;AAED,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;CAChB;AAgCD,wBAAgB,6BAA6B,IAAI,IAAI,CAWpD;AAID;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CA6B3C;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAWzD;AAID;;;GAGG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC;IACpD,cAAc,EAAE,MAAM,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;CACd,CAAC,CAcD;AA8FD,wBAAgB,iBAAiB,IAAI,aAAa,EAAE,CA0DnD;AAwJD,wBAAgB,wBAAwB,IAAI,IAAI,CAO/C;AAED,wBAAgB,iBAAiB,IAAI,YAAY,EAAE,CAElD"}
1
+ {"version":3,"file":"reflection-automation.d.ts","sourceRoot":"","sources":["../src/reflection-automation.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAOtC,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAA;IAChB,4EAA4E;IAC5E,gBAAgB,EAAE,MAAM,CAAA;IACxB,yDAAyD;IACzD,mBAAmB,EAAE,MAAM,CAAA;IAC3B,iDAAiD;IACjD,WAAW,EAAE,MAAM,CAAA;IACnB,oDAAoD;IACpD,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAA;IACf,mEAAmE;IACnE,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACxC,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,4DAA4D;IAC5D,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAC9B;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,4BAA4B,EAAE,MAAM,CAAA;IACpC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,MAAM,EAAE,SAAS,GAAG,KAAK,GAAG,SAAS,CAAA;CACtC;AAED,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;CAChB;AAeD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,CAAA;AAErF;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,gBAAgB,EAAE,MAAM,EACxB,iBAAiB,EAAE,OAAO,EAC1B,KAAK,SAAa,GACjB,cAAc,CAsBhB;AAED,8DAA8D;AAC9D,wBAAgB,sBAAsB,IAAI,IAAI,CAG7C;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,cAAc,EACpB,UAAU,EAAE,MAAM,EAClB,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAkCf;AA2BD,wBAAgB,6BAA6B,IAAI,IAAI,CAWpD;AAID;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CA6B3C;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAWzD;AAID;;;GAGG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC;IACpD,cAAc,EAAE,MAAM,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;CACd,CAAC,CAcD;AA2GD,wBAAgB,iBAAiB,IAAI,aAAa,EAAE,CA0DnD;AAYD,wBAAgB,gBAAgB,IAAI,MAAM,CAIzC;AAGD,eAAO,MAAM,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAa,CAAA;AAGzD,wBAAgB,cAAc,IAAI,IAAI,CAcrC;AAgJD,wBAAgB,wBAAwB,IAAI,IAAI,CAO/C;AAED,wBAAgB,iBAAiB,IAAI,YAAY,EAAE,CAElD"}
@@ -8,6 +8,91 @@ import { policyManager } from './policy.js';
8
8
  // ── State ──
9
9
  const pendingNudges = [];
10
10
  const lastNudgeAt = {};
11
+ // Dedup guards for tiered escalation (SIGNAL-ROUTING Change 2)
12
+ // mention fires at most once per 24h per agent; escalate at most once per 48h.
13
+ // task-1773525631162-cjxch4mrz
14
+ const mentionLastAt = {};
15
+ const escalateLastAt = {};
16
+ const MENTION_DEDUP_MS = 24 * 60 * 60 * 1000;
17
+ const ESCALATE_DEDUP_MS = 48 * 60 * 60 * 1000;
18
+ /**
19
+ * 4-tier reflection reminder decision per SIGNAL-ROUTING Change 2 spec.
20
+ *
21
+ * | Overdue | Tier | Channel |
22
+ * |------------|-----------|---------|
23
+ * | < 14h | none | — |
24
+ * | 14h–24h | digest | #ops (batched, no @mention) |
25
+ * | 24h–48h | mention | #ops with @mention, once per 24h |
26
+ * | 48h+ | escalate | #ops with @kai, once per 48h |
27
+ * | post-task | immediate | #general direct to agent |
28
+ */
29
+ export function getReflectionTier(agent, lastReflectionAt, justCompletedTask, nowMs = Date.now()) {
30
+ if (justCompletedTask)
31
+ return 'immediate';
32
+ const overdueMs = nowMs - lastReflectionAt;
33
+ const overdueHours = overdueMs / (1000 * 60 * 60);
34
+ if (overdueHours < 14)
35
+ return 'none';
36
+ if (overdueHours < 24)
37
+ return 'digest';
38
+ if (overdueHours < 48) {
39
+ // mention: dedup to once per 24h
40
+ const lastMention = mentionLastAt[agent] ?? 0;
41
+ if (nowMs - lastMention < MENTION_DEDUP_MS)
42
+ return 'none';
43
+ mentionLastAt[agent] = nowMs; // record on selection so dispatch doesn't double-set
44
+ return 'mention';
45
+ }
46
+ // escalate: dedup to once per 48h
47
+ const lastEscalate = escalateLastAt[agent] ?? 0;
48
+ if (nowMs - lastEscalate < ESCALATE_DEDUP_MS)
49
+ return 'none';
50
+ escalateLastAt[agent] = nowMs; // record on selection
51
+ return 'escalate';
52
+ }
53
+ /** Exposed for tests — reset dedup state between test runs */
54
+ export function _resetTierDedupForTest() {
55
+ for (const k of Object.keys(mentionLastAt))
56
+ delete mentionLastAt[k];
57
+ for (const k of Object.keys(escalateLastAt))
58
+ delete escalateLastAt[k];
59
+ }
60
+ /**
61
+ * Dispatch a reflection reminder based on the computed tier.
62
+ * digest → batchNag (existing batch-before-post gate, Change 4)
63
+ * mention/escalate → immediate routeMessage to #ops
64
+ * immediate → routeMessage to #general direct to agent
65
+ */
66
+ export async function dispatchReflectionTier(agent, tier, hoursSince, lastReflectionAt, config) {
67
+ const opsChannel = 'ops';
68
+ const now = Date.now();
69
+ switch (tier) {
70
+ case 'none':
71
+ return;
72
+ case 'digest':
73
+ // Add to ops batch — batchNag handles flush (Change 4)
74
+ batchNag(opsChannel, `@${agent}: reflection ${hoursSince}h overdue — submit when you can`);
75
+ return;
76
+ case 'mention': {
77
+ // mentionLastAt already set in getReflectionTier
78
+ const msg = `🪞 @${agent} reflection overdue ${hoursSince}h — submit when you have a moment. POST /reflections.`;
79
+ await routeMessage({ from: 'system', content: msg, category: 'watchdog-alert', severity: 'warning', forceChannel: opsChannel }).catch(() => { });
80
+ return;
81
+ }
82
+ case 'escalate': {
83
+ // escalateLastAt already set in getReflectionTier
84
+ const lastDate = lastReflectionAt > 0 ? new Date(lastReflectionAt).toISOString().slice(0, 10) : 'never';
85
+ const msg = `🚨 @kai @${agent} reflection overdue ${hoursSince}h. Last reflection: ${lastDate}. Needs attention.`;
86
+ await routeMessage({ from: 'system', content: msg, category: 'watchdog-alert', severity: 'critical', forceChannel: opsChannel }).catch(() => { });
87
+ return;
88
+ }
89
+ case 'immediate': {
90
+ const msg = `🪞 @${agent} task complete — good moment to reflect. POST /reflections.`;
91
+ batchNag('ops', msg);
92
+ return;
93
+ }
94
+ }
95
+ }
11
96
  /** Running guard — prevents concurrent tick calls from firing duplicate nudges */
12
97
  let _tickRunning = false;
13
98
  /**
@@ -171,19 +256,32 @@ async function _doTick() {
171
256
  }
172
257
  }
173
258
  if (shouldNudge) {
174
- await sendIdleNudge(agent, hoursSinceDisplay, tracking?.tasks_done_since_reflection || 0, config);
175
- lastNudgeAt[agent] = now;
176
- // Record nudge in DB
177
- ensureReflectionTrackingTable();
178
- const db = getDb();
179
- db.prepare(`
180
- INSERT INTO reflection_tracking (agent, last_nudge_at, tasks_done_since_reflection, updated_at)
181
- VALUES (?, ?, 0, ?)
182
- ON CONFLICT(agent) DO UPDATE SET
183
- last_nudge_at = ?,
184
- updated_at = ?
185
- `).run(agent, now, now, now, now);
186
- idleNudges++;
259
+ // Lane-aware suppression: skip nudge if agent has no unblocked todo tasks.
260
+ // Agents whose in-lane queue is genuinely empty cannot take new work —
261
+ // nudging them is a false positive (they're compliant, not idle).
262
+ const unblockedTodo = taskManager.listTasks({
263
+ status: 'todo',
264
+ assigneeIn: [agent],
265
+ }).filter(t => t.status === 'todo');
266
+ if (unblockedTodo.length === 0)
267
+ continue;
268
+ // 4-tier dispatch per SIGNAL-ROUTING Change 2
269
+ const tier = getReflectionTier(agent, lastReflection, false, now);
270
+ if (tier !== 'none') {
271
+ await dispatchReflectionTier(agent, tier, hoursSinceDisplay, lastReflection, config);
272
+ lastNudgeAt[agent] = now;
273
+ // Record nudge in DB
274
+ ensureReflectionTrackingTable();
275
+ const db = getDb();
276
+ db.prepare(`
277
+ INSERT INTO reflection_tracking (agent, last_nudge_at, tasks_done_since_reflection, updated_at)
278
+ VALUES (?, ?, 0, ?)
279
+ ON CONFLICT(agent) DO UPDATE SET
280
+ last_nudge_at = ?,
281
+ updated_at = ?
282
+ `).run(agent, now, now, now, now);
283
+ idleNudges++;
284
+ }
187
285
  }
188
286
  }
189
287
  return { postTaskNudges, idleNudges, total: postTaskNudges + idleNudges };
@@ -243,41 +341,64 @@ export function getReflectionSLAs() {
243
341
  return order[a.status] - order[b.status];
244
342
  });
245
343
  }
246
- // ── Nudge messages ──
247
- async function sendPostTaskNudge(agent, taskId, taskTitle, config, taskStatus) {
248
- const isBlocked = taskStatus === 'blocked';
249
- const msg = isBlocked
250
- ? `🪞 Reflection nudge: @${agent}, "${taskTitle}" (${taskId}) is blocked. ` +
251
- `Take 2 min to reflect what's blocking you, what did you try, and what would unblock it? ` +
252
- `Submit via POST /reflections with your observations.`
253
- : `🪞 Reflection nudge: @${agent}, you just completed "${taskTitle}" (${taskId}). ` +
254
- `Take 2 min to reflect — what went well, what was painful, and what would you change? ` +
255
- `Submit via POST /reflections with your observations.`;
256
- try {
257
- await routeMessage({
344
+ // ── Batch-before-post gate (SIGNAL-ROUTING Change 4) ─────────────────────────
345
+ // All per-agent nags (reflection reminders, idle alerts) go through batchNag()
346
+ // before any channel post. A 5-minute batch window accumulates messages; the
347
+ // flush posts a single Noise Budget Digest instead of N individual posts.
348
+ //
349
+ // Window duration controlled by WATCHDOG_BATCH_WINDOW_MS env var (default 5min).
350
+ // Tests can set WATCHDOG_BATCH_WINDOW_MS to a small value (e.g. 50ms).
351
+ //
352
+ // task-1773525646527-rgpsta72u
353
+ export function getBatchWindowMs() {
354
+ const envVal = process.env.WATCHDOG_BATCH_WINDOW_MS;
355
+ if (envVal)
356
+ return Number(envVal);
357
+ return 5 * 60 * 1000; // 5 minutes (production default)
358
+ }
359
+ // Exported for testing — allows tests to flush the batch manually
360
+ export const _nagBatch = new Map(); // channel → messages
361
+ let _batchTimer = null;
362
+ export function _flushNagBatch() {
363
+ for (const [channel, messages] of _nagBatch.entries()) {
364
+ if (messages.length === 0)
365
+ continue;
366
+ const content = `📋 **Reflection & Idle Digest** (${messages.length} reminder${messages.length !== 1 ? 's' : ''}):\n${messages.map(m => `• ${m}`).join('\n')}`;
367
+ routeMessage({
258
368
  from: 'system',
259
- content: msg,
369
+ content,
260
370
  category: 'watchdog-alert',
261
371
  severity: 'info',
262
- forceChannel: config.channel || 'general',
263
- });
372
+ forceChannel: channel,
373
+ }).catch(() => { });
264
374
  }
265
- catch { /* chat may not be available */ }
375
+ _nagBatch.clear();
376
+ _batchTimer = null;
266
377
  }
267
- async function sendIdleNudge(agent, hoursSince, tasksDone, config) {
268
- const taskNote = tasksDone > 0 ? ` You've completed ${tasksDone} task(s) since your last reflection.` : '';
269
- const msg = `🪞 Reflection due: @${agent}, it's been ${hoursSince}h since your last reflection.${taskNote} ` +
270
- `Take a moment to capture what you've learned. Submit via POST /reflections.`;
271
- try {
272
- await routeMessage({
273
- from: 'system',
274
- content: msg,
275
- category: 'watchdog-alert',
276
- severity: 'warning',
277
- forceChannel: config.channel || 'general',
278
- });
378
+ function batchNag(channel, message) {
379
+ const existing = _nagBatch.get(channel);
380
+ if (existing) {
381
+ existing.push(message);
279
382
  }
280
- catch { /* chat may not be available */ }
383
+ else {
384
+ _nagBatch.set(channel, [message]);
385
+ }
386
+ if (!_batchTimer) {
387
+ _batchTimer = setTimeout(_flushNagBatch, getBatchWindowMs());
388
+ }
389
+ }
390
+ // ── Nudge messages ──
391
+ async function sendPostTaskNudge(agent, taskId, taskTitle, config, taskStatus) {
392
+ const isBlocked = taskStatus === 'blocked';
393
+ const msg = isBlocked
394
+ ? `🪞 @${agent}: "${taskTitle}" (${taskId}) is blocked — reflect on what's blocking you`
395
+ : `🪞 @${agent}: completed "${taskTitle}" (${taskId}) — what went well, what was painful?`;
396
+ batchNag(config.channel || 'general', msg);
397
+ }
398
+ async function sendIdleNudge(agent, hoursSince, tasksDone, config) {
399
+ const taskNote = tasksDone > 0 ? ` (${tasksDone} task(s) done since last reflection)` : '';
400
+ const msg = `🪞 @${agent}: ${hoursSince}h since last reflection${taskNote} — capture what you've learned`;
401
+ batchNag(config.channel || 'general', msg);
281
402
  }
282
403
  // ── Helpers ──
283
404
  function getConfig() {