agent-working-memory 0.11.0 → 0.12.3
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 +71 -297
- package/dist/adapters/claude-code.d.ts.map +1 -1
- package/dist/adapters/claude-code.js +63 -3
- package/dist/adapters/claude-code.js.map +1 -1
- package/dist/adapters/common.d.ts.map +1 -1
- package/dist/adapters/common.js +358 -306
- package/dist/adapters/common.js.map +1 -1
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +29 -7
- package/dist/api/routes.js.map +1 -1
- package/dist/coordination/routes.d.ts.map +1 -1
- package/dist/coordination/routes.js +174 -170
- package/dist/coordination/routes.js.map +1 -1
- package/dist/core/embeddings.d.ts.map +1 -1
- package/dist/core/embeddings.js +4 -1
- package/dist/core/embeddings.js.map +1 -1
- package/dist/core/entity-extract.d.ts +3 -0
- package/dist/core/entity-extract.d.ts.map +1 -0
- package/dist/core/entity-extract.js +47 -0
- package/dist/core/entity-extract.js.map +1 -0
- package/dist/core/format-recall.d.ts +16 -0
- package/dist/core/format-recall.d.ts.map +1 -0
- package/dist/core/format-recall.js +24 -0
- package/dist/core/format-recall.js.map +1 -0
- package/dist/core/query-expander.js +1 -1
- package/dist/core/query-expander.js.map +1 -1
- package/dist/core/reranker.js +1 -1
- package/dist/core/reranker.js.map +1 -1
- package/dist/core/salience.d.ts.map +1 -1
- package/dist/core/salience.js +14 -2
- package/dist/core/salience.js.map +1 -1
- package/dist/core/whoami.d.ts +24 -0
- package/dist/core/whoami.d.ts.map +1 -0
- package/dist/core/whoami.js +66 -0
- package/dist/core/whoami.js.map +1 -0
- package/dist/core/write-pipeline.d.ts +9 -0
- package/dist/core/write-pipeline.d.ts.map +1 -1
- package/dist/core/write-pipeline.js +109 -68
- package/dist/core/write-pipeline.js.map +1 -1
- package/dist/core/write-telemetry.d.ts +33 -0
- package/dist/core/write-telemetry.d.ts.map +1 -0
- package/dist/core/write-telemetry.js +110 -0
- package/dist/core/write-telemetry.js.map +1 -0
- package/dist/engine/activation.d.ts +22 -12
- package/dist/engine/activation.d.ts.map +1 -1
- package/dist/engine/activation.js +133 -17
- package/dist/engine/activation.js.map +1 -1
- package/dist/engine/consolidation-scheduler.d.ts +1 -1
- package/dist/engine/consolidation-scheduler.js +1 -1
- package/dist/engine/consolidation.d.ts +1 -0
- package/dist/engine/consolidation.d.ts.map +1 -1
- package/dist/engine/consolidation.js +18 -0
- package/dist/engine/consolidation.js.map +1 -1
- package/dist/engine/eval.d.ts.map +1 -1
- package/dist/engine/eval.js +5 -1
- package/dist/engine/eval.js.map +1 -1
- package/dist/hooks/sidecar.d.ts +26 -0
- package/dist/hooks/sidecar.d.ts.map +1 -1
- package/dist/hooks/sidecar.js +30 -0
- package/dist/hooks/sidecar.js.map +1 -1
- package/dist/index.js +20 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +2 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +222 -108
- package/dist/mcp.js.map +1 -1
- package/dist/recipes/index.d.ts +57 -0
- package/dist/recipes/index.d.ts.map +1 -0
- package/dist/recipes/index.js +81 -0
- package/dist/recipes/index.js.map +1 -0
- package/dist/storage/pglite-schema.d.ts.map +1 -1
- package/dist/storage/pglite-schema.js +27 -0
- package/dist/storage/pglite-schema.js.map +1 -1
- package/dist/storage/pglite.d.ts +5 -0
- package/dist/storage/pglite.d.ts.map +1 -1
- package/dist/storage/pglite.js +180 -138
- package/dist/storage/pglite.js.map +1 -1
- package/dist/storage/postgres.d.ts +5 -0
- package/dist/storage/postgres.d.ts.map +1 -1
- package/dist/storage/postgres.js +180 -138
- package/dist/storage/postgres.js.map +1 -1
- package/dist/storage/sqlite.d.ts +9 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +394 -326
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/types/engram.d.ts +14 -0
- package/dist/types/engram.d.ts.map +1 -1
- package/dist/types/engram.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/claude-code.ts +66 -3
- package/src/adapters/common.ts +567 -515
- package/src/api/routes.ts +999 -971
- package/src/coordination/routes.ts +2155 -2150
- package/src/core/embeddings.ts +4 -1
- package/src/core/entity-extract.ts +47 -0
- package/src/core/format-recall.ts +25 -0
- package/src/core/query-expander.ts +1 -1
- package/src/core/reranker.ts +1 -1
- package/src/core/salience.ts +529 -514
- package/src/core/whoami.ts +92 -0
- package/src/core/write-pipeline.ts +60 -8
- package/src/core/write-telemetry.ts +131 -0
- package/src/engine/activation.ts +1468 -1369
- package/src/engine/consolidation-scheduler.ts +1 -1
- package/src/engine/consolidation.ts +887 -869
- package/src/engine/eval.ts +6 -1
- package/src/hooks/sidecar.ts +55 -0
- package/src/index.ts +248 -227
- package/src/mcp.ts +1387 -1270
- package/src/recipes/index.ts +125 -0
- package/src/storage/pglite-schema.ts +27 -0
- package/src/storage/pglite.ts +1420 -1372
- package/src/storage/postgres.ts +1523 -1475
- package/src/storage/sqlite.ts +1936 -1861
- package/src/types/engram.ts +22 -0
|
@@ -36,8 +36,12 @@ function createChannelMetrics() {
|
|
|
36
36
|
*/
|
|
37
37
|
function sessionTokenOk(db, agentId, req) {
|
|
38
38
|
const provided = req.headers['x-session-token'];
|
|
39
|
-
if (!provided)
|
|
40
|
-
|
|
39
|
+
if (!provided) {
|
|
40
|
+
// D2 (2026-07-30): AWM_COORD_REQUIRE_TOKENS=1 closes the omit-the-header
|
|
41
|
+
// bypass. Default stays backward-compatible (registered workers that never
|
|
42
|
+
// send tokens keep working); the network perimeter is the bind+API-key gate.
|
|
43
|
+
return process.env.AWM_COORD_REQUIRE_TOKENS !== '1';
|
|
44
|
+
}
|
|
41
45
|
const row = db.prepare(`SELECT session_token FROM coord_agents WHERE id = ?`).get(agentId);
|
|
42
46
|
if (!row || !row.session_token)
|
|
43
47
|
return true; // not found or no token stored — backward compat
|
|
@@ -128,15 +132,15 @@ export function registerCoordinationRoutes(app, db, store, eventBus) {
|
|
|
128
132
|
coordLog(`${name} reconnected (reusing UUID ${existing.id.slice(0, 8)})`);
|
|
129
133
|
// Auto-register channel session if channelUrl provided
|
|
130
134
|
if (channelUrl) {
|
|
131
|
-
db.prepare(`
|
|
132
|
-
INSERT INTO coord_channel_sessions (agent_id, channel_id, connected_at, status)
|
|
133
|
-
VALUES (?, ?, datetime('now'), 'connected')
|
|
134
|
-
ON CONFLICT(agent_id) DO UPDATE SET
|
|
135
|
-
channel_id = excluded.channel_id,
|
|
136
|
-
connected_at = datetime('now'),
|
|
137
|
-
status = 'connected',
|
|
138
|
-
push_count = 0,
|
|
139
|
-
last_push_at = NULL
|
|
135
|
+
db.prepare(`
|
|
136
|
+
INSERT INTO coord_channel_sessions (agent_id, channel_id, connected_at, status)
|
|
137
|
+
VALUES (?, ?, datetime('now'), 'connected')
|
|
138
|
+
ON CONFLICT(agent_id) DO UPDATE SET
|
|
139
|
+
channel_id = excluded.channel_id,
|
|
140
|
+
connected_at = datetime('now'),
|
|
141
|
+
status = 'connected',
|
|
142
|
+
push_count = 0,
|
|
143
|
+
last_push_at = NULL
|
|
140
144
|
`).run(existing.id, channelUrl);
|
|
141
145
|
coordLog(`channel auto-registered: ${name} (${existing.id.slice(0, 8)}) → ${channelUrl}`);
|
|
142
146
|
}
|
|
@@ -150,15 +154,15 @@ export function registerCoordinationRoutes(app, db, store, eventBus) {
|
|
|
150
154
|
db.prepare(`INSERT INTO coord_events (agent_id, event_type, detail) VALUES (?, 'registered', ?)`).run(id, `${name} joined as ${role ?? 'worker'}${workspace ? ' [' + workspace + ']' : ''}${capabilities ? ' [' + capabilities.join(', ') + ']' : ''}`);
|
|
151
155
|
// Auto-register channel session if channelUrl provided
|
|
152
156
|
if (channelUrl) {
|
|
153
|
-
db.prepare(`
|
|
154
|
-
INSERT INTO coord_channel_sessions (agent_id, channel_id, connected_at, status)
|
|
155
|
-
VALUES (?, ?, datetime('now'), 'connected')
|
|
156
|
-
ON CONFLICT(agent_id) DO UPDATE SET
|
|
157
|
-
channel_id = excluded.channel_id,
|
|
158
|
-
connected_at = datetime('now'),
|
|
159
|
-
status = 'connected',
|
|
160
|
-
push_count = 0,
|
|
161
|
-
last_push_at = NULL
|
|
157
|
+
db.prepare(`
|
|
158
|
+
INSERT INTO coord_channel_sessions (agent_id, channel_id, connected_at, status)
|
|
159
|
+
VALUES (?, ?, datetime('now'), 'connected')
|
|
160
|
+
ON CONFLICT(agent_id) DO UPDATE SET
|
|
161
|
+
channel_id = excluded.channel_id,
|
|
162
|
+
connected_at = datetime('now'),
|
|
163
|
+
status = 'connected',
|
|
164
|
+
push_count = 0,
|
|
165
|
+
last_push_at = NULL
|
|
162
166
|
`).run(id, channelUrl);
|
|
163
167
|
coordLog(`channel auto-registered: ${name} (${id.slice(0, 8)}) → ${channelUrl}`);
|
|
164
168
|
}
|
|
@@ -266,25 +270,25 @@ export function registerCoordinationRoutes(app, db, store, eventBus) {
|
|
|
266
270
|
}
|
|
267
271
|
// Auto-register channel session if channelUrl provided
|
|
268
272
|
if (channelUrl) {
|
|
269
|
-
db.prepare(`
|
|
270
|
-
INSERT INTO coord_channel_sessions (agent_id, channel_id, connected_at, status)
|
|
271
|
-
VALUES (?, ?, datetime('now'), 'connected')
|
|
272
|
-
ON CONFLICT(agent_id) DO UPDATE SET
|
|
273
|
-
channel_id = excluded.channel_id,
|
|
274
|
-
connected_at = datetime('now'),
|
|
275
|
-
status = 'connected',
|
|
276
|
-
push_count = 0,
|
|
277
|
-
last_push_at = NULL
|
|
273
|
+
db.prepare(`
|
|
274
|
+
INSERT INTO coord_channel_sessions (agent_id, channel_id, connected_at, status)
|
|
275
|
+
VALUES (?, ?, datetime('now'), 'connected')
|
|
276
|
+
ON CONFLICT(agent_id) DO UPDATE SET
|
|
277
|
+
channel_id = excluded.channel_id,
|
|
278
|
+
connected_at = datetime('now'),
|
|
279
|
+
status = 'connected',
|
|
280
|
+
push_count = 0,
|
|
281
|
+
last_push_at = NULL
|
|
278
282
|
`).run(agentId, channelUrl);
|
|
279
283
|
coordLog(`channel auto-registered via /next: ${name} (${agentId.slice(0, 8)}) → ${channelUrl}`);
|
|
280
284
|
}
|
|
281
285
|
// Step 2: Get active commands
|
|
282
286
|
const activeCommands = workspace
|
|
283
|
-
? db.prepare(`SELECT id, command, reason, issued_by, issued_at, workspace
|
|
284
|
-
FROM coord_commands WHERE cleared_at IS NULL AND (workspace = ? OR workspace IS NULL)
|
|
287
|
+
? db.prepare(`SELECT id, command, reason, issued_by, issued_at, workspace
|
|
288
|
+
FROM coord_commands WHERE cleared_at IS NULL AND (workspace = ? OR workspace IS NULL)
|
|
285
289
|
ORDER BY issued_at DESC`).all(workspace)
|
|
286
|
-
: db.prepare(`SELECT id, command, reason, issued_by, issued_at, workspace
|
|
287
|
-
FROM coord_commands WHERE cleared_at IS NULL
|
|
290
|
+
: db.prepare(`SELECT id, command, reason, issued_by, issued_at, workspace
|
|
291
|
+
FROM coord_commands WHERE cleared_at IS NULL
|
|
288
292
|
ORDER BY issued_at DESC`).all();
|
|
289
293
|
// Step 3: Get or auto-claim assignment
|
|
290
294
|
let assignment = db.prepare(`SELECT * FROM coord_assignments WHERE agent_id = ? AND status IN ('assigned', 'in_progress') ORDER BY created_at DESC LIMIT 1`).get(agentId);
|
|
@@ -336,9 +340,9 @@ export function registerCoordinationRoutes(app, db, store, eventBus) {
|
|
|
336
340
|
// Read current agent status after all mutations
|
|
337
341
|
const agentRow = db.prepare(`SELECT status FROM coord_agents WHERE id = ?`).get(agentId);
|
|
338
342
|
// Deliver queued mailbox messages (persistent messages that survived disconnects/restarts)
|
|
339
|
-
const mailbox = db.prepare(`SELECT id, message, source, created_at FROM coord_mailbox
|
|
340
|
-
WHERE worker_name = ? AND delivered_at IS NULL
|
|
341
|
-
AND (workspace = ? OR workspace IS NULL)
|
|
343
|
+
const mailbox = db.prepare(`SELECT id, message, source, created_at FROM coord_mailbox
|
|
344
|
+
WHERE worker_name = ? AND delivered_at IS NULL
|
|
345
|
+
AND (workspace = ? OR workspace IS NULL)
|
|
342
346
|
ORDER BY created_at ASC LIMIT 10`).all(name, workspace ?? null);
|
|
343
347
|
if (mailbox.length > 0) {
|
|
344
348
|
const ids = mailbox.map(m => m.id);
|
|
@@ -664,13 +668,13 @@ export function registerCoordinationRoutes(app, db, store, eventBus) {
|
|
|
664
668
|
}
|
|
665
669
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
666
670
|
const total = db.prepare(`SELECT COUNT(*) AS count FROM coord_assignments a ${where}`).get(...params).count;
|
|
667
|
-
const assignments = db.prepare(`SELECT a.*, g.name AS agent_name,
|
|
668
|
-
CASE WHEN a.blocked_by IS NOT NULL AND a.blocked_by NOT IN (SELECT id FROM coord_assignments WHERE status = 'completed')
|
|
669
|
-
THEN 1 ELSE 0 END AS is_blocked
|
|
670
|
-
FROM coord_assignments a
|
|
671
|
-
LEFT JOIN coord_agents g ON a.agent_id = g.id
|
|
672
|
-
${where}
|
|
673
|
-
ORDER BY a.priority DESC, a.created_at DESC
|
|
671
|
+
const assignments = db.prepare(`SELECT a.*, g.name AS agent_name,
|
|
672
|
+
CASE WHEN a.blocked_by IS NOT NULL AND a.blocked_by NOT IN (SELECT id FROM coord_assignments WHERE status = 'completed')
|
|
673
|
+
THEN 1 ELSE 0 END AS is_blocked
|
|
674
|
+
FROM coord_assignments a
|
|
675
|
+
LEFT JOIN coord_agents g ON a.agent_id = g.id
|
|
676
|
+
${where}
|
|
677
|
+
ORDER BY a.priority DESC, a.created_at DESC
|
|
674
678
|
LIMIT ? OFFSET ?`).all(...params, q.limit, q.offset);
|
|
675
679
|
return reply.send({ assignments, total });
|
|
676
680
|
});
|
|
@@ -809,8 +813,8 @@ export function registerCoordinationRoutes(app, db, store, eventBus) {
|
|
|
809
813
|
return reply.send({ ok: true });
|
|
810
814
|
});
|
|
811
815
|
app.get('/locks', async (_req, reply) => {
|
|
812
|
-
const locks = db.prepare(`SELECT l.file_path, l.agent_id, a.name AS agent_name, l.locked_at, l.reason
|
|
813
|
-
FROM coord_locks l JOIN coord_agents a ON l.agent_id = a.id
|
|
816
|
+
const locks = db.prepare(`SELECT l.file_path, l.agent_id, a.name AS agent_name, l.locked_at, l.reason
|
|
817
|
+
FROM coord_locks l JOIN coord_agents a ON l.agent_id = a.id
|
|
814
818
|
ORDER BY l.locked_at DESC LIMIT 200`).all();
|
|
815
819
|
return reply.send({ locks });
|
|
816
820
|
});
|
|
@@ -841,11 +845,11 @@ export function registerCoordinationRoutes(app, db, store, eventBus) {
|
|
|
841
845
|
app.get('/command', async (req, reply) => {
|
|
842
846
|
const workspace = req.query.workspace;
|
|
843
847
|
const active = workspace
|
|
844
|
-
? db.prepare(`SELECT id, command, reason, issued_by, issued_at, workspace
|
|
845
|
-
FROM coord_commands WHERE cleared_at IS NULL AND (workspace = ? OR workspace IS NULL)
|
|
848
|
+
? db.prepare(`SELECT id, command, reason, issued_by, issued_at, workspace
|
|
849
|
+
FROM coord_commands WHERE cleared_at IS NULL AND (workspace = ? OR workspace IS NULL)
|
|
846
850
|
ORDER BY issued_at DESC`).all(workspace)
|
|
847
|
-
: db.prepare(`SELECT id, command, reason, issued_by, issued_at, workspace
|
|
848
|
-
FROM coord_commands WHERE cleared_at IS NULL
|
|
851
|
+
: db.prepare(`SELECT id, command, reason, issued_by, issued_at, workspace
|
|
852
|
+
FROM coord_commands WHERE cleared_at IS NULL
|
|
849
853
|
ORDER BY issued_at DESC`).all();
|
|
850
854
|
if (active.length === 0) {
|
|
851
855
|
return reply.send({ active: false, commands: [] });
|
|
@@ -875,11 +879,11 @@ export function registerCoordinationRoutes(app, db, store, eventBus) {
|
|
|
875
879
|
const q = commandWaitQuerySchema.safeParse(req.query);
|
|
876
880
|
const { status: targetStatus, workspace } = q.success ? q.data : { status: 'idle', workspace: undefined };
|
|
877
881
|
const agents = workspace
|
|
878
|
-
? db.prepare(`SELECT id, name, role, status, current_task, last_seen
|
|
879
|
-
FROM coord_agents WHERE status NOT IN ('dead') AND workspace = ?
|
|
882
|
+
? db.prepare(`SELECT id, name, role, status, current_task, last_seen
|
|
883
|
+
FROM coord_agents WHERE status NOT IN ('dead') AND workspace = ?
|
|
880
884
|
ORDER BY name`).all(workspace)
|
|
881
|
-
: db.prepare(`SELECT id, name, role, status, current_task, last_seen
|
|
882
|
-
FROM coord_agents WHERE status NOT IN ('dead')
|
|
885
|
+
: db.prepare(`SELECT id, name, role, status, current_task, last_seen
|
|
886
|
+
FROM coord_agents WHERE status NOT IN ('dead')
|
|
883
887
|
ORDER BY name`).all();
|
|
884
888
|
const ready = agents.filter(a => a.status === targetStatus || a.role === 'orchestrator' || a.role === 'coordinator');
|
|
885
889
|
const notReady = agents.filter(a => a.status !== targetStatus && a.role !== 'orchestrator' && a.role !== 'coordinator');
|
|
@@ -898,7 +902,7 @@ export function registerCoordinationRoutes(app, db, store, eventBus) {
|
|
|
898
902
|
const { agentId, category, severity, filePath, lineNumber, description, suggestion } = parsed.data;
|
|
899
903
|
if (!sessionTokenOk(db, agentId, req))
|
|
900
904
|
return reply.code(403).send({ error: 'invalid session token' });
|
|
901
|
-
db.prepare(`INSERT INTO coord_findings (agent_id, category, severity, file_path, line_number, description, suggestion)
|
|
905
|
+
db.prepare(`INSERT INTO coord_findings (agent_id, category, severity, file_path, line_number, description, suggestion)
|
|
902
906
|
VALUES (?, ?, ?, ?, ?, ?, ?)`).run(agentId, category, severity ?? 'info', filePath ?? null, lineNumber ?? null, description, suggestion ?? null);
|
|
903
907
|
db.prepare(`INSERT INTO coord_events (agent_id, event_type, detail) VALUES (?, 'finding', ?)`).run(agentId, `[${severity ?? 'info'}] ${category}: ${description.slice(0, 100)}`);
|
|
904
908
|
return reply.code(201).send({ ok: true });
|
|
@@ -906,12 +910,12 @@ export function registerCoordinationRoutes(app, db, store, eventBus) {
|
|
|
906
910
|
app.get('/findings', async (req, reply) => {
|
|
907
911
|
const q = findingsQuerySchema.safeParse(req.query);
|
|
908
912
|
const { category, severity, status, limit } = q.success ? q.data : { category: undefined, severity: undefined, status: undefined, limit: 50 };
|
|
909
|
-
let sql = `
|
|
910
|
-
SELECT f.id, f.category, f.severity, f.file_path, f.line_number,
|
|
911
|
-
f.description, f.suggestion, f.status, f.created_at,
|
|
912
|
-
a.name AS agent_name
|
|
913
|
-
FROM coord_findings f JOIN coord_agents a ON f.agent_id = a.id
|
|
914
|
-
WHERE 1=1
|
|
913
|
+
let sql = `
|
|
914
|
+
SELECT f.id, f.category, f.severity, f.file_path, f.line_number,
|
|
915
|
+
f.description, f.suggestion, f.status, f.created_at,
|
|
916
|
+
a.name AS agent_name
|
|
917
|
+
FROM coord_findings f JOIN coord_agents a ON f.agent_id = a.id
|
|
918
|
+
WHERE 1=1
|
|
915
919
|
`;
|
|
916
920
|
const params = [];
|
|
917
921
|
if (category) {
|
|
@@ -926,9 +930,9 @@ export function registerCoordinationRoutes(app, db, store, eventBus) {
|
|
|
926
930
|
sql += ` AND f.status = ?`;
|
|
927
931
|
params.push(status);
|
|
928
932
|
}
|
|
929
|
-
sql += ` ORDER BY
|
|
930
|
-
CASE f.severity WHEN 'critical' THEN 0 WHEN 'error' THEN 1 WHEN 'warn' THEN 2 ELSE 3 END,
|
|
931
|
-
f.created_at DESC
|
|
933
|
+
sql += ` ORDER BY
|
|
934
|
+
CASE f.severity WHEN 'critical' THEN 0 WHEN 'error' THEN 1 WHEN 'warn' THEN 2 ELSE 3 END,
|
|
935
|
+
f.created_at DESC
|
|
932
936
|
LIMIT ?`;
|
|
933
937
|
params.push(limit);
|
|
934
938
|
const findings = db.prepare(sql).all(...params);
|
|
@@ -978,10 +982,10 @@ export function registerCoordinationRoutes(app, db, store, eventBus) {
|
|
|
978
982
|
app.get('/decisions', async (req, reply) => {
|
|
979
983
|
const q = decisionsQuerySchema.safeParse(req.query);
|
|
980
984
|
const { since_id, assignment_id, workspace, limit } = q.success ? q.data : { since_id: 0, assignment_id: undefined, workspace: undefined, limit: 20 };
|
|
981
|
-
let sql = `
|
|
982
|
-
SELECT d.id, d.author_id, a.name AS author_name, d.assignment_id, d.tags, d.summary, d.created_at
|
|
983
|
-
FROM coord_decisions d JOIN coord_agents a ON d.author_id = a.id
|
|
984
|
-
WHERE d.id > ?
|
|
985
|
+
let sql = `
|
|
986
|
+
SELECT d.id, d.author_id, a.name AS author_name, d.assignment_id, d.tags, d.summary, d.created_at
|
|
987
|
+
FROM coord_decisions d JOIN coord_agents a ON d.author_id = a.id
|
|
988
|
+
WHERE d.id > ?
|
|
985
989
|
`;
|
|
986
990
|
const params = [since_id];
|
|
987
991
|
if (assignment_id) {
|
|
@@ -1011,30 +1015,30 @@ export function registerCoordinationRoutes(app, db, store, eventBus) {
|
|
|
1011
1015
|
});
|
|
1012
1016
|
// ─── Status ─────────────────────────────────────────────────────
|
|
1013
1017
|
app.get('/status', async (_req, reply) => {
|
|
1014
|
-
const agents = db.prepare(`SELECT id, name, role, status, current_task, last_seen,
|
|
1015
|
-
ROUND((julianday('now') - julianday(last_seen)) * 86400) AS seconds_since_seen
|
|
1016
|
-
FROM coord_agents WHERE status != 'dead'
|
|
1018
|
+
const agents = db.prepare(`SELECT id, name, role, status, current_task, last_seen,
|
|
1019
|
+
ROUND((julianday('now') - julianday(last_seen)) * 86400) AS seconds_since_seen
|
|
1020
|
+
FROM coord_agents WHERE status != 'dead'
|
|
1017
1021
|
ORDER BY role, name LIMIT 200`).all();
|
|
1018
|
-
const assignments = db.prepare(`SELECT a.id, a.task, a.description, a.status, a.agent_id, ag.name AS agent_name,
|
|
1019
|
-
a.created_at, a.started_at, a.completed_at
|
|
1020
|
-
FROM coord_assignments a LEFT JOIN coord_agents ag ON a.agent_id = ag.id
|
|
1021
|
-
WHERE a.status NOT IN ('completed', 'failed')
|
|
1022
|
+
const assignments = db.prepare(`SELECT a.id, a.task, a.description, a.status, a.agent_id, ag.name AS agent_name,
|
|
1023
|
+
a.created_at, a.started_at, a.completed_at
|
|
1024
|
+
FROM coord_assignments a LEFT JOIN coord_agents ag ON a.agent_id = ag.id
|
|
1025
|
+
WHERE a.status NOT IN ('completed', 'failed')
|
|
1022
1026
|
ORDER BY a.created_at LIMIT 200`).all();
|
|
1023
|
-
const locks = db.prepare(`SELECT l.file_path, l.agent_id, a.name AS agent_name, l.locked_at, l.reason
|
|
1027
|
+
const locks = db.prepare(`SELECT l.file_path, l.agent_id, a.name AS agent_name, l.locked_at, l.reason
|
|
1024
1028
|
FROM coord_locks l JOIN coord_agents a ON l.agent_id = a.id LIMIT 200`).all();
|
|
1025
|
-
const stats = db.prepare(`SELECT
|
|
1026
|
-
(SELECT COUNT(*) FROM coord_agents WHERE status != 'dead') AS alive_agents,
|
|
1027
|
-
(SELECT COUNT(*) FROM coord_agents WHERE status = 'working') AS busy_agents,
|
|
1028
|
-
(SELECT COUNT(*) FROM coord_assignments WHERE status = 'pending') AS pending_tasks,
|
|
1029
|
-
(SELECT COUNT(*) FROM coord_assignments WHERE status IN ('assigned', 'in_progress')) AS active_tasks,
|
|
1030
|
-
(SELECT COUNT(*) FROM coord_locks) AS active_locks,
|
|
1031
|
-
(SELECT COUNT(*) FROM coord_findings WHERE status = 'open') AS open_findings,
|
|
1029
|
+
const stats = db.prepare(`SELECT
|
|
1030
|
+
(SELECT COUNT(*) FROM coord_agents WHERE status != 'dead') AS alive_agents,
|
|
1031
|
+
(SELECT COUNT(*) FROM coord_agents WHERE status = 'working') AS busy_agents,
|
|
1032
|
+
(SELECT COUNT(*) FROM coord_assignments WHERE status = 'pending') AS pending_tasks,
|
|
1033
|
+
(SELECT COUNT(*) FROM coord_assignments WHERE status IN ('assigned', 'in_progress')) AS active_tasks,
|
|
1034
|
+
(SELECT COUNT(*) FROM coord_locks) AS active_locks,
|
|
1035
|
+
(SELECT COUNT(*) FROM coord_findings WHERE status = 'open') AS open_findings,
|
|
1032
1036
|
(SELECT COUNT(*) FROM coord_findings WHERE status = 'open' AND severity IN ('critical', 'error')) AS urgent_findings`).get();
|
|
1033
|
-
const recentFindings = db.prepare(`SELECT f.id, f.category, f.severity, f.file_path, f.description, a.name AS agent_name, f.created_at
|
|
1034
|
-
FROM coord_findings f JOIN coord_agents a ON f.agent_id = a.id
|
|
1035
|
-
WHERE f.status = 'open'
|
|
1036
|
-
ORDER BY CASE f.severity WHEN 'critical' THEN 0 WHEN 'error' THEN 1 WHEN 'warn' THEN 2 ELSE 3 END,
|
|
1037
|
-
f.created_at DESC
|
|
1037
|
+
const recentFindings = db.prepare(`SELECT f.id, f.category, f.severity, f.file_path, f.description, a.name AS agent_name, f.created_at
|
|
1038
|
+
FROM coord_findings f JOIN coord_agents a ON f.agent_id = a.id
|
|
1039
|
+
WHERE f.status = 'open'
|
|
1040
|
+
ORDER BY CASE f.severity WHEN 'critical' THEN 0 WHEN 'error' THEN 1 WHEN 'warn' THEN 2 ELSE 3 END,
|
|
1041
|
+
f.created_at DESC
|
|
1038
1042
|
LIMIT 10`).all();
|
|
1039
1043
|
return reply.send({ agents, assignments, locks, stats, recentFindings });
|
|
1040
1044
|
});
|
|
@@ -1049,19 +1053,19 @@ export function registerCoordinationRoutes(app, db, store, eventBus) {
|
|
|
1049
1053
|
// spawns. Channel sessions get probed every 60s (coordination/index.ts:111),
|
|
1050
1054
|
// so a stale channel-server.js gets status='disconnected' within 60-120s.
|
|
1051
1055
|
let workers = workspace
|
|
1052
|
-
? db.prepare(`SELECT a.id, a.name, a.role, a.status, a.current_task, a.capabilities, a.workspace, a.last_seen,
|
|
1053
|
-
ROUND((julianday('now') - julianday(a.last_seen)) * 86400) AS seconds_since_seen,
|
|
1054
|
-
cs.status AS channel_status, cs.last_push_at AS channel_last_push
|
|
1055
|
-
FROM coord_agents a
|
|
1056
|
-
LEFT JOIN coord_channel_sessions cs ON cs.agent_id = a.id
|
|
1057
|
-
WHERE a.status != 'dead' AND a.role NOT IN ('orchestrator', 'coordinator') AND a.workspace = ?
|
|
1056
|
+
? db.prepare(`SELECT a.id, a.name, a.role, a.status, a.current_task, a.capabilities, a.workspace, a.last_seen,
|
|
1057
|
+
ROUND((julianday('now') - julianday(a.last_seen)) * 86400) AS seconds_since_seen,
|
|
1058
|
+
cs.status AS channel_status, cs.last_push_at AS channel_last_push
|
|
1059
|
+
FROM coord_agents a
|
|
1060
|
+
LEFT JOIN coord_channel_sessions cs ON cs.agent_id = a.id
|
|
1061
|
+
WHERE a.status != 'dead' AND a.role NOT IN ('orchestrator', 'coordinator') AND a.workspace = ?
|
|
1058
1062
|
ORDER BY a.name LIMIT 200`).all(workspace)
|
|
1059
|
-
: db.prepare(`SELECT a.id, a.name, a.role, a.status, a.current_task, a.capabilities, a.workspace, a.last_seen,
|
|
1060
|
-
ROUND((julianday('now') - julianday(a.last_seen)) * 86400) AS seconds_since_seen,
|
|
1061
|
-
cs.status AS channel_status, cs.last_push_at AS channel_last_push
|
|
1062
|
-
FROM coord_agents a
|
|
1063
|
-
LEFT JOIN coord_channel_sessions cs ON cs.agent_id = a.id
|
|
1064
|
-
WHERE a.status != 'dead' AND a.role NOT IN ('orchestrator', 'coordinator')
|
|
1063
|
+
: db.prepare(`SELECT a.id, a.name, a.role, a.status, a.current_task, a.capabilities, a.workspace, a.last_seen,
|
|
1064
|
+
ROUND((julianday('now') - julianday(a.last_seen)) * 86400) AS seconds_since_seen,
|
|
1065
|
+
cs.status AS channel_status, cs.last_push_at AS channel_last_push
|
|
1066
|
+
FROM coord_agents a
|
|
1067
|
+
LEFT JOIN coord_channel_sessions cs ON cs.agent_id = a.id
|
|
1068
|
+
WHERE a.status != 'dead' AND a.role NOT IN ('orchestrator', 'coordinator')
|
|
1065
1069
|
ORDER BY a.name LIMIT 200`).all();
|
|
1066
1070
|
if (capability) {
|
|
1067
1071
|
workers = workers.filter(w => {
|
|
@@ -1126,9 +1130,9 @@ export function registerCoordinationRoutes(app, db, store, eventBus) {
|
|
|
1126
1130
|
}
|
|
1127
1131
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
1128
1132
|
params.push(limit);
|
|
1129
|
-
const events = db.prepare(`SELECT e.id, e.agent_id, a.name AS agent_name, e.event_type, e.detail, e.created_at
|
|
1130
|
-
FROM coord_events e LEFT JOIN coord_agents a ON e.agent_id = a.id
|
|
1131
|
-
${where}
|
|
1133
|
+
const events = db.prepare(`SELECT e.id, e.agent_id, a.name AS agent_name, e.event_type, e.detail, e.created_at
|
|
1134
|
+
FROM coord_events e LEFT JOIN coord_agents a ON e.agent_id = a.id
|
|
1135
|
+
${where}
|
|
1132
1136
|
ORDER BY e.id ASC LIMIT ?`).all(...params);
|
|
1133
1137
|
const last_id = events.length > 0 ? events[events.length - 1].id : since_id;
|
|
1134
1138
|
return reply.send({ events, last_id });
|
|
@@ -1156,8 +1160,8 @@ export function registerCoordinationRoutes(app, db, store, eventBus) {
|
|
|
1156
1160
|
if (!params.success)
|
|
1157
1161
|
return reply.code(400).send({ error: params.error.issues[0].message });
|
|
1158
1162
|
const { id } = params.data;
|
|
1159
|
-
const agent = db.prepare(`SELECT id, name, role, status, current_task, pid, capabilities, workspace, metadata, last_seen, started_at,
|
|
1160
|
-
ROUND((julianday('now') - julianday(last_seen)) * 86400) AS seconds_since_seen
|
|
1163
|
+
const agent = db.prepare(`SELECT id, name, role, status, current_task, pid, capabilities, workspace, metadata, last_seen, started_at,
|
|
1164
|
+
ROUND((julianday('now') - julianday(last_seen)) * 86400) AS seconds_since_seen
|
|
1161
1165
|
FROM coord_agents WHERE id = ?`).get(id);
|
|
1162
1166
|
if (!agent)
|
|
1163
1167
|
return reply.code(404).send({ error: 'agent not found' });
|
|
@@ -1177,7 +1181,7 @@ export function registerCoordinationRoutes(app, db, store, eventBus) {
|
|
|
1177
1181
|
if (agent.status === 'dead')
|
|
1178
1182
|
return reply.send({ ok: true, action: 'already_dead', agent_name: agent.name });
|
|
1179
1183
|
// Fail active assignments
|
|
1180
|
-
const failedAssignments = db.prepare(`UPDATE coord_assignments SET status = 'failed', result = 'agent killed by coordinator', completed_at = datetime('now')
|
|
1184
|
+
const failedAssignments = db.prepare(`UPDATE coord_assignments SET status = 'failed', result = 'agent killed by coordinator', completed_at = datetime('now')
|
|
1181
1185
|
WHERE agent_id = ? AND status IN ('assigned', 'in_progress')`).run(id);
|
|
1182
1186
|
if (failedAssignments.changes > 0) {
|
|
1183
1187
|
db.prepare(`INSERT INTO coord_events (agent_id, event_type, detail) VALUES (?, 'assignment_failed', ?)`).run(id, `killed: failed ${failedAssignments.changes} active assignment(s)`);
|
|
@@ -1210,49 +1214,49 @@ export function registerCoordinationRoutes(app, db, store, eventBus) {
|
|
|
1210
1214
|
}
|
|
1211
1215
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
1212
1216
|
params.push(limit);
|
|
1213
|
-
const timeline = db.prepare(`SELECT e.created_at AS timestamp, a.name AS agent_name, e.event_type, e.detail,
|
|
1214
|
-
t.task AS assignment_task
|
|
1215
|
-
FROM coord_events e
|
|
1216
|
-
LEFT JOIN coord_agents a ON e.agent_id = a.id
|
|
1217
|
-
LEFT JOIN coord_assignments t ON a.current_task = t.id
|
|
1218
|
-
${where}
|
|
1219
|
-
ORDER BY e.created_at DESC, e.id DESC
|
|
1217
|
+
const timeline = db.prepare(`SELECT e.created_at AS timestamp, a.name AS agent_name, e.event_type, e.detail,
|
|
1218
|
+
t.task AS assignment_task
|
|
1219
|
+
FROM coord_events e
|
|
1220
|
+
LEFT JOIN coord_agents a ON e.agent_id = a.id
|
|
1221
|
+
LEFT JOIN coord_assignments t ON a.current_task = t.id
|
|
1222
|
+
${where}
|
|
1223
|
+
ORDER BY e.created_at DESC, e.id DESC
|
|
1220
1224
|
LIMIT ?`).all(...params);
|
|
1221
1225
|
return reply.send({ timeline });
|
|
1222
1226
|
});
|
|
1223
1227
|
// ─── Stats ──────────────────────────────────────────────────────
|
|
1224
1228
|
app.get('/stats', async (_req, reply) => {
|
|
1225
|
-
const workers = db.prepare(`
|
|
1226
|
-
SELECT
|
|
1227
|
-
COUNT(*) AS total,
|
|
1228
|
-
SUM(CASE WHEN status != 'dead' THEN 1 ELSE 0 END) AS alive,
|
|
1229
|
-
SUM(CASE WHEN status = 'idle' THEN 1 ELSE 0 END) AS idle,
|
|
1230
|
-
SUM(CASE WHEN status = 'working' THEN 1 ELSE 0 END) AS working
|
|
1231
|
-
FROM coord_agents
|
|
1229
|
+
const workers = db.prepare(`
|
|
1230
|
+
SELECT
|
|
1231
|
+
COUNT(*) AS total,
|
|
1232
|
+
SUM(CASE WHEN status != 'dead' THEN 1 ELSE 0 END) AS alive,
|
|
1233
|
+
SUM(CASE WHEN status = 'idle' THEN 1 ELSE 0 END) AS idle,
|
|
1234
|
+
SUM(CASE WHEN status = 'working' THEN 1 ELSE 0 END) AS working
|
|
1235
|
+
FROM coord_agents
|
|
1232
1236
|
`).get();
|
|
1233
|
-
const tasks = db.prepare(`
|
|
1234
|
-
SELECT
|
|
1235
|
-
COUNT(*) AS total_assigned,
|
|
1236
|
-
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed,
|
|
1237
|
-
SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) AS failed,
|
|
1238
|
-
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending,
|
|
1239
|
-
AVG(CASE
|
|
1240
|
-
WHEN status = 'completed' AND started_at IS NOT NULL AND completed_at IS NOT NULL
|
|
1241
|
-
THEN ROUND((julianday(completed_at) - julianday(started_at)) * 86400)
|
|
1242
|
-
ELSE NULL
|
|
1243
|
-
END) AS avg_completion_seconds
|
|
1244
|
-
FROM coord_assignments
|
|
1237
|
+
const tasks = db.prepare(`
|
|
1238
|
+
SELECT
|
|
1239
|
+
COUNT(*) AS total_assigned,
|
|
1240
|
+
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed,
|
|
1241
|
+
SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) AS failed,
|
|
1242
|
+
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending,
|
|
1243
|
+
AVG(CASE
|
|
1244
|
+
WHEN status = 'completed' AND started_at IS NOT NULL AND completed_at IS NOT NULL
|
|
1245
|
+
THEN ROUND((julianday(completed_at) - julianday(started_at)) * 86400)
|
|
1246
|
+
ELSE NULL
|
|
1247
|
+
END) AS avg_completion_seconds
|
|
1248
|
+
FROM coord_assignments
|
|
1245
1249
|
`).get();
|
|
1246
|
-
const decisions = db.prepare(`
|
|
1247
|
-
SELECT
|
|
1248
|
-
COALESCE(COUNT(*), 0) AS total,
|
|
1249
|
-
COALESCE(SUM(CASE WHEN created_at >= datetime('now', '-1 hour') THEN 1 ELSE 0 END), 0) AS last_hour
|
|
1250
|
-
FROM coord_decisions
|
|
1250
|
+
const decisions = db.prepare(`
|
|
1251
|
+
SELECT
|
|
1252
|
+
COALESCE(COUNT(*), 0) AS total,
|
|
1253
|
+
COALESCE(SUM(CASE WHEN created_at >= datetime('now', '-1 hour') THEN 1 ELSE 0 END), 0) AS last_hour
|
|
1254
|
+
FROM coord_decisions
|
|
1251
1255
|
`).get();
|
|
1252
1256
|
// Uptime = seconds since the earliest non-dead agent started
|
|
1253
|
-
const uptime = db.prepare(`
|
|
1254
|
-
SELECT ROUND((julianday('now') - julianday(MIN(started_at))) * 86400) AS uptime_seconds
|
|
1255
|
-
FROM coord_agents WHERE status != 'dead'
|
|
1257
|
+
const uptime = db.prepare(`
|
|
1258
|
+
SELECT ROUND((julianday('now') - julianday(MIN(started_at))) * 86400) AS uptime_seconds
|
|
1259
|
+
FROM coord_agents WHERE status != 'dead'
|
|
1256
1260
|
`).get();
|
|
1257
1261
|
return reply.send({
|
|
1258
1262
|
workers,
|
|
@@ -1327,11 +1331,11 @@ export function registerCoordinationRoutes(app, db, store, eventBus) {
|
|
|
1327
1331
|
const dbHealthy = store ? store.integrityCheck().ok : true;
|
|
1328
1332
|
const agents = db.prepare(`SELECT COUNT(*) AS alive FROM coord_agents WHERE status != 'dead'`).get();
|
|
1329
1333
|
const staleThreshold = 300;
|
|
1330
|
-
const staleCount = db.prepare(`SELECT COUNT(*) AS c FROM coord_agents
|
|
1331
|
-
WHERE status != 'dead'
|
|
1334
|
+
const staleCount = db.prepare(`SELECT COUNT(*) AS c FROM coord_agents
|
|
1335
|
+
WHERE status != 'dead'
|
|
1332
1336
|
AND (julianday('now') - julianday(last_seen)) * 86400 > ?`).get(staleThreshold).c;
|
|
1333
1337
|
const pending = db.prepare(`SELECT COUNT(*) AS c FROM coord_assignments WHERE status IN ('pending', 'assigned', 'in_progress')`).get().c;
|
|
1334
|
-
const uptimeRow = db.prepare(`SELECT ROUND((julianday('now') - julianday(MIN(started_at))) * 86400) AS s
|
|
1338
|
+
const uptimeRow = db.prepare(`SELECT ROUND((julianday('now') - julianday(MIN(started_at))) * 86400) AS s
|
|
1335
1339
|
FROM coord_agents WHERE status != 'dead'`).get();
|
|
1336
1340
|
// WAL file size and autocheckpoint setting
|
|
1337
1341
|
let walSizeBytes = null;
|
|
@@ -1370,15 +1374,15 @@ export function registerCoordinationRoutes(app, db, store, eventBus) {
|
|
|
1370
1374
|
const agent = db.prepare('SELECT id FROM coord_agents WHERE id = ?').get(agentId);
|
|
1371
1375
|
if (!agent)
|
|
1372
1376
|
return reply.status(404).send({ error: 'Agent not found' });
|
|
1373
|
-
db.prepare(`
|
|
1374
|
-
INSERT INTO coord_channel_sessions (agent_id, channel_id, connected_at, status)
|
|
1375
|
-
VALUES (?, ?, datetime('now'), 'connected')
|
|
1376
|
-
ON CONFLICT(agent_id) DO UPDATE SET
|
|
1377
|
-
channel_id = excluded.channel_id,
|
|
1378
|
-
connected_at = datetime('now'),
|
|
1379
|
-
status = 'connected',
|
|
1380
|
-
push_count = 0,
|
|
1381
|
-
last_push_at = NULL
|
|
1377
|
+
db.prepare(`
|
|
1378
|
+
INSERT INTO coord_channel_sessions (agent_id, channel_id, connected_at, status)
|
|
1379
|
+
VALUES (?, ?, datetime('now'), 'connected')
|
|
1380
|
+
ON CONFLICT(agent_id) DO UPDATE SET
|
|
1381
|
+
channel_id = excluded.channel_id,
|
|
1382
|
+
connected_at = datetime('now'),
|
|
1383
|
+
status = 'connected',
|
|
1384
|
+
push_count = 0,
|
|
1385
|
+
last_push_at = NULL
|
|
1382
1386
|
`).run(agentId, channelId);
|
|
1383
1387
|
db.prepare(`INSERT INTO coord_events (agent_id, event_type, detail) VALUES (?, 'channel_register', ?)`).run(agentId, JSON.stringify({ channelId }));
|
|
1384
1388
|
coordLog(`channel/register: ${agentId} → ${channelId}`);
|
|
@@ -1443,9 +1447,9 @@ export function registerCoordinationRoutes(app, db, store, eventBus) {
|
|
|
1443
1447
|
let { agentId } = parsed.data;
|
|
1444
1448
|
// Role-based addressing — resolve to a concrete agentId
|
|
1445
1449
|
if (!agentId && parsed.data.role && parsed.data.workspace) {
|
|
1446
|
-
const resolved = db.prepare(`SELECT id FROM coord_agents
|
|
1447
|
-
WHERE role = ? AND workspace = ? AND status != 'dead'
|
|
1448
|
-
ORDER BY last_seen DESC
|
|
1450
|
+
const resolved = db.prepare(`SELECT id FROM coord_agents
|
|
1451
|
+
WHERE role = ? AND workspace = ? AND status != 'dead'
|
|
1452
|
+
ORDER BY last_seen DESC
|
|
1449
1453
|
LIMIT 1`).get(parsed.data.role, parsed.data.workspace);
|
|
1450
1454
|
if (!resolved) {
|
|
1451
1455
|
return reply.status(404).send({
|
|
@@ -1491,21 +1495,21 @@ export function registerCoordinationRoutes(app, db, store, eventBus) {
|
|
|
1491
1495
|
});
|
|
1492
1496
|
/** GET /channel/sessions — List all active channel sessions with agent names. */
|
|
1493
1497
|
app.get('/channel/sessions', async (_request, reply) => {
|
|
1494
|
-
const sessions = db.prepare(`
|
|
1495
|
-
SELECT cs.agent_id, a.name AS agent_name, cs.channel_id,
|
|
1496
|
-
cs.connected_at, cs.last_push_at, cs.push_count, cs.status
|
|
1497
|
-
FROM coord_channel_sessions cs
|
|
1498
|
-
JOIN coord_agents a ON a.id = cs.agent_id
|
|
1499
|
-
WHERE cs.status = 'connected'
|
|
1500
|
-
ORDER BY cs.connected_at DESC
|
|
1498
|
+
const sessions = db.prepare(`
|
|
1499
|
+
SELECT cs.agent_id, a.name AS agent_name, cs.channel_id,
|
|
1500
|
+
cs.connected_at, cs.last_push_at, cs.push_count, cs.status
|
|
1501
|
+
FROM coord_channel_sessions cs
|
|
1502
|
+
JOIN coord_agents a ON a.id = cs.agent_id
|
|
1503
|
+
WHERE cs.status = 'connected'
|
|
1504
|
+
ORDER BY cs.connected_at DESC
|
|
1501
1505
|
`).all();
|
|
1502
1506
|
return reply.send({ sessions });
|
|
1503
1507
|
});
|
|
1504
1508
|
/** POST /channel/probe — Probe all connected channel sessions, mark dead ones as disconnected. */
|
|
1505
1509
|
app.post('/channel/probe', async (_request, reply) => {
|
|
1506
|
-
const sessions = db.prepare(`SELECT cs.agent_id, a.name AS agent_name, cs.channel_id
|
|
1507
|
-
FROM coord_channel_sessions cs
|
|
1508
|
-
JOIN coord_agents a ON a.id = cs.agent_id
|
|
1510
|
+
const sessions = db.prepare(`SELECT cs.agent_id, a.name AS agent_name, cs.channel_id
|
|
1511
|
+
FROM coord_channel_sessions cs
|
|
1512
|
+
JOIN coord_agents a ON a.id = cs.agent_id
|
|
1509
1513
|
WHERE cs.status = 'connected'`).all();
|
|
1510
1514
|
const results = [];
|
|
1511
1515
|
for (const session of sessions) {
|
|
@@ -1549,12 +1553,12 @@ export function registerCoordinationRoutes(app, db, store, eventBus) {
|
|
|
1549
1553
|
* }
|
|
1550
1554
|
*/
|
|
1551
1555
|
app.get('/telemetry/channels', async (_request, reply) => {
|
|
1552
|
-
const perAgent = db.prepare(`
|
|
1553
|
-
SELECT a.name AS agent_name, cs.push_count, cs.last_push_at, cs.status,
|
|
1554
|
-
cs.connected_at
|
|
1555
|
-
FROM coord_channel_sessions cs
|
|
1556
|
-
JOIN coord_agents a ON a.id = cs.agent_id
|
|
1557
|
-
ORDER BY cs.push_count DESC, cs.connected_at DESC
|
|
1556
|
+
const perAgent = db.prepare(`
|
|
1557
|
+
SELECT a.name AS agent_name, cs.push_count, cs.last_push_at, cs.status,
|
|
1558
|
+
cs.connected_at
|
|
1559
|
+
FROM coord_channel_sessions cs
|
|
1560
|
+
JOIN coord_agents a ON a.id = cs.agent_id
|
|
1561
|
+
ORDER BY cs.push_count DESC, cs.connected_at DESC
|
|
1558
1562
|
`).all();
|
|
1559
1563
|
const deliveryRate = channelMetrics.attempts > 0
|
|
1560
1564
|
? channelMetrics.delivered / channelMetrics.attempts
|