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.
- package/README.md +54 -0
- package/defaults/TEAM-ROLES.starter.yaml +87 -0
- package/defaults/lane-templates/ops.json +32 -0
- package/defaults/lane-templates/workflow.json +32 -0
- package/defaults/reviewer-routing.yaml +34 -0
- package/dist/activationEvents.d.ts +7 -1
- package/dist/activationEvents.d.ts.map +1 -1
- package/dist/activationEvents.js +29 -3
- package/dist/activationEvents.js.map +1 -1
- package/dist/activity-stream-normalizer.d.ts +37 -0
- package/dist/activity-stream-normalizer.d.ts.map +1 -0
- package/dist/activity-stream-normalizer.js +101 -0
- package/dist/activity-stream-normalizer.js.map +1 -0
- package/dist/agent-exec-guardrail.d.ts +9 -0
- package/dist/agent-exec-guardrail.d.ts.map +1 -0
- package/dist/agent-exec-guardrail.js +24 -0
- package/dist/agent-exec-guardrail.js.map +1 -0
- package/dist/agent-exec-guardrail.test.d.ts +2 -0
- package/dist/agent-exec-guardrail.test.d.ts.map +1 -0
- package/dist/agent-exec-guardrail.test.js +55 -0
- package/dist/agent-exec-guardrail.test.js.map +1 -0
- package/dist/agent-interface.d.ts +137 -0
- package/dist/agent-interface.d.ts.map +1 -0
- package/dist/agent-interface.js +463 -0
- package/dist/agent-interface.js.map +1 -0
- package/dist/agent-notifications.d.ts +51 -0
- package/dist/agent-notifications.d.ts.map +1 -0
- package/dist/agent-notifications.js +104 -0
- package/dist/agent-notifications.js.map +1 -0
- package/dist/agent-runs.d.ts +31 -7
- package/dist/agent-runs.d.ts.map +1 -1
- package/dist/agent-runs.js +137 -28
- package/dist/agent-runs.js.map +1 -1
- package/dist/artifact-mirror.d.ts.map +1 -1
- package/dist/artifact-mirror.js +4 -1
- package/dist/artifact-mirror.js.map +1 -1
- package/dist/assignment.d.ts.map +1 -1
- package/dist/assignment.js +54 -2
- package/dist/assignment.js.map +1 -1
- package/dist/boardHealthWorker.d.ts.map +1 -1
- package/dist/boardHealthWorker.js +15 -1
- package/dist/boardHealthWorker.js.map +1 -1
- package/dist/canvas-auto-state.d.ts +58 -0
- package/dist/canvas-auto-state.d.ts.map +1 -0
- package/dist/canvas-auto-state.js +89 -0
- package/dist/canvas-auto-state.js.map +1 -0
- package/dist/canvas-routes.d.ts +36 -0
- package/dist/canvas-routes.d.ts.map +1 -0
- package/dist/canvas-routes.js +47 -0
- package/dist/canvas-routes.js.map +1 -0
- package/dist/capability-readiness.d.ts +28 -0
- package/dist/capability-readiness.d.ts.map +1 -0
- package/dist/capability-readiness.js +162 -0
- package/dist/capability-readiness.js.map +1 -0
- package/dist/channels.d.ts.map +1 -1
- package/dist/channels.js +1 -0
- package/dist/channels.js.map +1 -1
- package/dist/cli.js +179 -4
- package/dist/cli.js.map +1 -1
- package/dist/cloud.d.ts +5 -0
- package/dist/cloud.d.ts.map +1 -1
- package/dist/cloud.js +485 -18
- package/dist/cloud.js.map +1 -1
- package/dist/comms-routing-policy.d.ts +31 -0
- package/dist/comms-routing-policy.d.ts.map +1 -0
- package/dist/comms-routing-policy.js +128 -0
- package/dist/comms-routing-policy.js.map +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/continuity-loop.d.ts.map +1 -1
- package/dist/continuity-loop.js +26 -0
- package/dist/continuity-loop.js.map +1 -1
- package/dist/cost-enforcement.d.ts.map +1 -1
- package/dist/cost-enforcement.js +22 -0
- package/dist/cost-enforcement.js.map +1 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +56 -5
- package/dist/db.js.map +1 -1
- package/dist/doctor.js +2 -2
- package/dist/e2e-loop-proof.test.js +11 -1
- package/dist/e2e-loop-proof.test.js.map +1 -1
- package/dist/events.d.ts +4 -2
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +22 -1
- package/dist/events.js.map +1 -1
- package/dist/executionSweeper.d.ts.map +1 -1
- package/dist/executionSweeper.js +155 -0
- package/dist/executionSweeper.js.map +1 -1
- package/dist/health.d.ts +21 -1
- package/dist/health.d.ts.map +1 -1
- package/dist/health.js +164 -19
- package/dist/health.js.map +1 -1
- package/dist/inbox.d.ts +4 -0
- package/dist/inbox.d.ts.map +1 -1
- package/dist/inbox.js +38 -1
- package/dist/inbox.js.map +1 -1
- package/dist/index.js +90 -14
- package/dist/index.js.map +1 -1
- package/dist/insight-auto-tagger.d.ts +58 -0
- package/dist/insight-auto-tagger.d.ts.map +1 -0
- package/dist/insight-auto-tagger.js +331 -0
- package/dist/insight-auto-tagger.js.map +1 -0
- package/dist/insight-task-bridge.d.ts +9 -0
- package/dist/insight-task-bridge.d.ts.map +1 -1
- package/dist/insight-task-bridge.js +43 -7
- package/dist/insight-task-bridge.js.map +1 -1
- package/dist/insights.d.ts +6 -0
- package/dist/insights.d.ts.map +1 -1
- package/dist/insights.js +13 -0
- package/dist/insights.js.map +1 -1
- package/dist/lane-config.d.ts.map +1 -1
- package/dist/lane-config.js +1 -0
- package/dist/lane-config.js.map +1 -1
- package/dist/lane-template-successor.d.ts +13 -0
- package/dist/lane-template-successor.d.ts.map +1 -0
- package/dist/lane-template-successor.js +132 -0
- package/dist/lane-template-successor.js.map +1 -0
- package/dist/local-whisper.d.ts +21 -0
- package/dist/local-whisper.d.ts.map +1 -0
- package/dist/local-whisper.js +137 -0
- package/dist/local-whisper.js.map +1 -0
- package/dist/macos-accessibility.d.ts +50 -0
- package/dist/macos-accessibility.d.ts.map +1 -0
- package/dist/macos-accessibility.js +185 -0
- package/dist/macos-accessibility.js.map +1 -0
- package/dist/manage.d.ts.map +1 -1
- package/dist/manage.js +47 -1
- package/dist/manage.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +123 -0
- package/dist/mcp.js.map +1 -1
- package/dist/notification-worker.d.ts +66 -0
- package/dist/notification-worker.d.ts.map +1 -0
- package/dist/notification-worker.js +232 -0
- package/dist/notification-worker.js.map +1 -0
- package/dist/openclaw-usage-sync.d.ts +28 -0
- package/dist/openclaw-usage-sync.d.ts.map +1 -0
- package/dist/openclaw-usage-sync.js +161 -0
- package/dist/openclaw-usage-sync.js.map +1 -0
- package/dist/policy.js +1 -1
- package/dist/policy.js.map +1 -1
- package/dist/pr-link-reconciler.d.ts +61 -0
- package/dist/pr-link-reconciler.d.ts.map +1 -0
- package/dist/pr-link-reconciler.js +127 -0
- package/dist/pr-link-reconciler.js.map +1 -0
- package/dist/preflight.js +2 -2
- package/dist/presence-narrator.d.ts +52 -0
- package/dist/presence-narrator.d.ts.map +1 -0
- package/dist/presence-narrator.js +193 -0
- package/dist/presence-narrator.js.map +1 -0
- package/dist/presence.d.ts +2 -0
- package/dist/presence.d.ts.map +1 -1
- package/dist/presence.js +23 -3
- package/dist/presence.js.map +1 -1
- package/dist/product-observation-source.d.ts +52 -0
- package/dist/product-observation-source.d.ts.map +1 -0
- package/dist/product-observation-source.js +326 -0
- package/dist/product-observation-source.js.map +1 -0
- package/dist/reflection-automation.d.ts +25 -0
- package/dist/reflection-automation.d.ts.map +1 -1
- package/dist/reflection-automation.js +163 -42
- package/dist/reflection-automation.js.map +1 -1
- package/dist/review-autoclose.d.ts +62 -0
- package/dist/review-autoclose.d.ts.map +1 -0
- package/dist/review-autoclose.js +75 -0
- package/dist/review-autoclose.js.map +1 -0
- package/dist/routing-enforcement.test.js +32 -56
- package/dist/routing-enforcement.test.js.map +1 -1
- package/dist/sentry-webhook.d.ts +69 -0
- package/dist/sentry-webhook.d.ts.map +1 -0
- package/dist/sentry-webhook.js +88 -0
- package/dist/sentry-webhook.js.map +1 -0
- package/dist/sentry.d.ts +29 -0
- package/dist/sentry.d.ts.map +1 -0
- package/dist/sentry.js +86 -0
- package/dist/sentry.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +5130 -233
- package/dist/server.js.map +1 -1
- package/dist/stale-candidate-reconciler.d.ts +69 -0
- package/dist/stale-candidate-reconciler.d.ts.map +1 -0
- package/dist/stale-candidate-reconciler.js +236 -0
- package/dist/stale-candidate-reconciler.js.map +1 -0
- package/dist/system-loop-state.d.ts +1 -1
- package/dist/system-loop-state.d.ts.map +1 -1
- package/dist/system-loop-state.js +1 -0
- package/dist/system-loop-state.js.map +1 -1
- package/dist/taskPrecheck.d.ts.map +1 -1
- package/dist/taskPrecheck.js +47 -2
- package/dist/taskPrecheck.js.map +1 -1
- package/dist/tasks.d.ts.map +1 -1
- package/dist/tasks.js +130 -0
- package/dist/tasks.js.map +1 -1
- package/dist/trust-events.d.ts +61 -0
- package/dist/trust-events.d.ts.map +1 -0
- package/dist/trust-events.js +148 -0
- package/dist/trust-events.js.map +1 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/usage-tracking.d.ts +6 -0
- package/dist/usage-tracking.d.ts.map +1 -1
- package/dist/usage-tracking.js +14 -0
- package/dist/usage-tracking.js.map +1 -1
- package/dist/voice-sessions.d.ts +51 -0
- package/dist/voice-sessions.d.ts.map +1 -0
- package/dist/voice-sessions.js +143 -0
- package/dist/voice-sessions.js.map +1 -0
- package/dist/workflow-templates.d.ts.map +1 -1
- package/dist/workflow-templates.js +18 -3
- package/dist/workflow-templates.js.map +1 -1
- package/dist/working-contract.d.ts +22 -1
- package/dist/working-contract.d.ts.map +1 -1
- package/dist/working-contract.js +31 -2
- package/dist/working-contract.js.map +1 -1
- package/package.json +4 -4
- package/public/dashboard.js +12 -4
- 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;
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
// ──
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
|
369
|
+
content,
|
|
260
370
|
category: 'watchdog-alert',
|
|
261
371
|
severity: 'info',
|
|
262
|
-
forceChannel:
|
|
263
|
-
});
|
|
372
|
+
forceChannel: channel,
|
|
373
|
+
}).catch(() => { });
|
|
264
374
|
}
|
|
265
|
-
|
|
375
|
+
_nagBatch.clear();
|
|
376
|
+
_batchTimer = null;
|
|
266
377
|
}
|
|
267
|
-
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
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() {
|