agent-working-memory 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -9
- package/dist/adapters/claude-code.d.ts +4 -0
- package/dist/adapters/claude-code.d.ts.map +1 -0
- package/dist/adapters/claude-code.js +218 -0
- package/dist/adapters/claude-code.js.map +1 -0
- package/dist/adapters/codex.d.ts +4 -0
- package/dist/adapters/codex.d.ts.map +1 -0
- package/dist/adapters/codex.js +226 -0
- package/dist/adapters/codex.js.map +1 -0
- package/dist/adapters/common.d.ts +34 -0
- package/dist/adapters/common.d.ts.map +1 -0
- package/dist/adapters/common.js +145 -0
- package/dist/adapters/common.js.map +1 -0
- package/dist/adapters/cursor.d.ts +4 -0
- package/dist/adapters/cursor.d.ts.map +1 -0
- package/dist/adapters/cursor.js +138 -0
- package/dist/adapters/cursor.js.map +1 -0
- package/dist/adapters/http.d.ts +4 -0
- package/dist/adapters/http.d.ts.map +1 -0
- package/dist/adapters/http.js +88 -0
- package/dist/adapters/http.js.map +1 -0
- package/dist/adapters/index.d.ts +7 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +21 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/types.d.ts +65 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +4 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/cli.js +104 -230
- package/dist/cli.js.map +1 -1
- package/dist/coordination/events.d.ts +59 -0
- package/dist/coordination/events.d.ts.map +1 -0
- package/dist/coordination/events.js +28 -0
- package/dist/coordination/events.js.map +1 -0
- package/dist/coordination/index.d.ts +10 -1
- package/dist/coordination/index.d.ts.map +1 -1
- package/dist/coordination/index.js +87 -3
- package/dist/coordination/index.js.map +1 -1
- package/dist/coordination/peer-decisions.d.ts +40 -0
- package/dist/coordination/peer-decisions.d.ts.map +1 -0
- package/dist/coordination/peer-decisions.js +82 -0
- package/dist/coordination/peer-decisions.js.map +1 -0
- package/dist/coordination/plugin-loader.d.ts +18 -0
- package/dist/coordination/plugin-loader.d.ts.map +1 -0
- package/dist/coordination/plugin-loader.js +55 -0
- package/dist/coordination/plugin-loader.js.map +1 -0
- package/dist/coordination/plugin.d.ts +40 -0
- package/dist/coordination/plugin.d.ts.map +1 -0
- package/dist/coordination/plugin.js +22 -0
- package/dist/coordination/plugin.js.map +1 -0
- package/dist/coordination/routes.d.ts +2 -1
- package/dist/coordination/routes.d.ts.map +1 -1
- package/dist/coordination/routes.js +899 -76
- package/dist/coordination/routes.js.map +1 -1
- package/dist/coordination/schema.d.ts.map +1 -1
- package/dist/coordination/schema.js +72 -14
- package/dist/coordination/schema.js.map +1 -1
- package/dist/coordination/schemas.d.ts +84 -3
- package/dist/coordination/schemas.d.ts.map +1 -1
- package/dist/coordination/schemas.js +71 -1
- package/dist/coordination/schemas.js.map +1 -1
- package/dist/coordination/stale.d.ts.map +1 -1
- package/dist/coordination/stale.js +2 -1
- package/dist/coordination/stale.js.map +1 -1
- package/dist/coordination/types.d.ts +252 -0
- package/dist/coordination/types.d.ts.map +1 -0
- package/dist/coordination/types.js +8 -0
- package/dist/coordination/types.js.map +1 -0
- package/dist/coordination/write-mutex.d.ts +26 -0
- package/dist/coordination/write-mutex.d.ts.map +1 -0
- package/dist/coordination/write-mutex.js +63 -0
- package/dist/coordination/write-mutex.js.map +1 -0
- package/dist/core/embeddings.d.ts +2 -0
- package/dist/core/embeddings.d.ts.map +1 -1
- package/dist/core/embeddings.js +4 -0
- package/dist/core/embeddings.js.map +1 -1
- package/dist/engine/activation.d.ts.map +1 -1
- package/dist/engine/activation.js +16 -3
- package/dist/engine/activation.js.map +1 -1
- package/dist/engine/consolidation.d.ts.map +1 -1
- package/dist/engine/consolidation.js +15 -6
- package/dist/engine/consolidation.js.map +1 -1
- package/dist/engine/retraction.d.ts +3 -1
- package/dist/engine/retraction.d.ts.map +1 -1
- package/dist/engine/retraction.js +19 -6
- package/dist/engine/retraction.js.map +1 -1
- package/dist/index.js +6 -18
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +52 -3
- package/dist/mcp.js.map +1 -1
- package/dist/storage/sqlite.d.ts +6 -1
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +39 -3
- package/dist/storage/sqlite.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/claude-code.ts +234 -0
- package/src/adapters/codex.ts +262 -0
- package/src/adapters/common.ts +172 -0
- package/src/adapters/cursor.ts +150 -0
- package/src/adapters/http.ts +100 -0
- package/src/adapters/index.ts +31 -0
- package/src/adapters/types.ts +75 -0
- package/src/cli.ts +107 -238
- package/src/coordination/events.ts +90 -0
- package/src/coordination/index.ts +102 -3
- package/src/coordination/peer-decisions.ts +105 -0
- package/src/coordination/plugin-loader.ts +60 -0
- package/src/coordination/plugin.ts +44 -0
- package/src/coordination/routes.ts +1176 -105
- package/src/coordination/schema.ts +67 -14
- package/src/coordination/schemas.ts +85 -1
- package/src/coordination/stale.ts +3 -2
- package/src/coordination/types.ts +311 -0
- package/src/coordination/write-mutex.ts +69 -0
- package/src/core/embeddings.ts +5 -0
- package/src/engine/activation.ts +13 -3
- package/src/engine/consolidation.ts +15 -6
- package/src/engine/retraction.ts +22 -6
- package/src/index.ts +6 -15
- package/src/mcp.ts +73 -9
- package/src/storage/sqlite.ts +39 -3
|
@@ -11,22 +11,21 @@ import type Database from 'better-sqlite3';
|
|
|
11
11
|
const COORDINATION_TABLES = `
|
|
12
12
|
-- Coordination: agents in the hive
|
|
13
13
|
CREATE TABLE IF NOT EXISTS coord_agents (
|
|
14
|
-
id
|
|
15
|
-
name
|
|
16
|
-
role
|
|
17
|
-
status
|
|
18
|
-
pid
|
|
19
|
-
started_at
|
|
20
|
-
last_seen
|
|
21
|
-
current_task
|
|
22
|
-
metadata
|
|
23
|
-
capabilities
|
|
24
|
-
workspace
|
|
14
|
+
id TEXT PRIMARY KEY,
|
|
15
|
+
name TEXT NOT NULL,
|
|
16
|
+
role TEXT NOT NULL DEFAULT 'worker',
|
|
17
|
+
status TEXT NOT NULL DEFAULT 'idle',
|
|
18
|
+
pid INTEGER,
|
|
19
|
+
started_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
20
|
+
last_seen TEXT NOT NULL DEFAULT (datetime('now')),
|
|
21
|
+
current_task TEXT,
|
|
22
|
+
metadata TEXT,
|
|
23
|
+
capabilities TEXT,
|
|
24
|
+
workspace TEXT,
|
|
25
|
+
session_token TEXT
|
|
25
26
|
);
|
|
26
27
|
|
|
27
|
-
--
|
|
28
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_coord_agents_name_workspace
|
|
29
|
-
ON coord_agents (name, COALESCE(workspace, ''));
|
|
28
|
+
-- unique index on (name, workspace) is created after dedup in initCoordinationTables
|
|
30
29
|
|
|
31
30
|
-- Coordination: assignments
|
|
32
31
|
CREATE TABLE IF NOT EXISTS coord_assignments (
|
|
@@ -43,6 +42,7 @@ CREATE TABLE IF NOT EXISTS coord_assignments (
|
|
|
43
42
|
result TEXT,
|
|
44
43
|
commit_sha TEXT,
|
|
45
44
|
workspace TEXT,
|
|
45
|
+
context TEXT,
|
|
46
46
|
FOREIGN KEY (agent_id) REFERENCES coord_agents(id),
|
|
47
47
|
FOREIGN KEY (blocked_by) REFERENCES coord_assignments(id)
|
|
48
48
|
);
|
|
@@ -96,6 +96,32 @@ CREATE TABLE IF NOT EXISTS coord_decisions (
|
|
|
96
96
|
CREATE INDEX IF NOT EXISTS idx_coord_decisions_assignment
|
|
97
97
|
ON coord_decisions (assignment_id, created_at);
|
|
98
98
|
|
|
99
|
+
-- Coordination: channel sessions (push-based coordination)
|
|
100
|
+
CREATE TABLE IF NOT EXISTS coord_channel_sessions (
|
|
101
|
+
agent_id TEXT PRIMARY KEY REFERENCES coord_agents(id),
|
|
102
|
+
channel_id TEXT NOT NULL,
|
|
103
|
+
connected_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
104
|
+
last_push_at TEXT,
|
|
105
|
+
push_count INTEGER NOT NULL DEFAULT 0,
|
|
106
|
+
status TEXT NOT NULL DEFAULT 'connected'
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
-- Coordination: named mailbox (persistent message queue per worker name)
|
|
110
|
+
-- Messages survive AWM restarts and worker disconnects.
|
|
111
|
+
-- Delivered on next /next poll, then marked delivered.
|
|
112
|
+
CREATE TABLE IF NOT EXISTS coord_mailbox (
|
|
113
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
114
|
+
worker_name TEXT NOT NULL,
|
|
115
|
+
workspace TEXT,
|
|
116
|
+
message TEXT NOT NULL,
|
|
117
|
+
source TEXT DEFAULT 'coordinator',
|
|
118
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
119
|
+
delivered_at TEXT,
|
|
120
|
+
read_at TEXT
|
|
121
|
+
);
|
|
122
|
+
CREATE INDEX IF NOT EXISTS idx_coord_mailbox_worker
|
|
123
|
+
ON coord_mailbox (worker_name, workspace, delivered_at);
|
|
124
|
+
|
|
99
125
|
-- Coordination: event audit trail
|
|
100
126
|
CREATE TABLE IF NOT EXISTS coord_events (
|
|
101
127
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -111,10 +137,37 @@ CREATE TABLE IF NOT EXISTS coord_events (
|
|
|
111
137
|
* Safe to call multiple times (CREATE IF NOT EXISTS).
|
|
112
138
|
*/
|
|
113
139
|
export function initCoordinationTables(db: Database.Database): void {
|
|
140
|
+
// Create tables (unique index is added separately after dedup)
|
|
114
141
|
db.exec(COORDINATION_TABLES);
|
|
115
142
|
|
|
143
|
+
// Deduplicate coord_agents before creating the unique index.
|
|
144
|
+
// Keep only the most recently seen row for each (name, workspace) pair.
|
|
145
|
+
try {
|
|
146
|
+
db.exec(`
|
|
147
|
+
DELETE FROM coord_agents
|
|
148
|
+
WHERE rowid NOT IN (
|
|
149
|
+
SELECT MAX(rowid) FROM coord_agents
|
|
150
|
+
GROUP BY name, COALESCE(workspace, '')
|
|
151
|
+
)
|
|
152
|
+
`);
|
|
153
|
+
} catch { /* table may not exist yet — that's fine */ }
|
|
154
|
+
|
|
155
|
+
// Now create the unique index safely
|
|
156
|
+
try {
|
|
157
|
+
db.exec(`
|
|
158
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_coord_agents_name_workspace
|
|
159
|
+
ON coord_agents (name, COALESCE(workspace, ''));
|
|
160
|
+
`);
|
|
161
|
+
} catch {
|
|
162
|
+
// Index may already exist from a previous run — that's fine
|
|
163
|
+
}
|
|
164
|
+
|
|
116
165
|
// Migrations: add columns to existing coord_assignments tables
|
|
117
166
|
try { db.exec(`ALTER TABLE coord_assignments ADD COLUMN commit_sha TEXT`); } catch { /* exists */ }
|
|
118
167
|
try { db.exec(`ALTER TABLE coord_assignments ADD COLUMN priority INTEGER NOT NULL DEFAULT 0`); } catch { /* exists */ }
|
|
119
168
|
try { db.exec(`ALTER TABLE coord_assignments ADD COLUMN blocked_by TEXT`); } catch { /* exists */ }
|
|
169
|
+
try { db.exec(`ALTER TABLE coord_assignments ADD COLUMN context TEXT`); } catch { /* exists */ }
|
|
170
|
+
|
|
171
|
+
// Migration: session token for hijack prevention (added 2026-03-26)
|
|
172
|
+
try { db.exec(`ALTER TABLE coord_agents ADD COLUMN session_token TEXT`); } catch { /* exists */ }
|
|
120
173
|
}
|
|
@@ -9,7 +9,7 @@ import { z } from 'zod';
|
|
|
9
9
|
|
|
10
10
|
// ─── Enums ──────────────────────────────────────────────────────
|
|
11
11
|
|
|
12
|
-
export const agentRoleEnum = z.enum(['worker', 'orchestrator', 'dev-lead']);
|
|
12
|
+
export const agentRoleEnum = z.enum(['worker', 'orchestrator', 'coordinator', 'dev-lead']);
|
|
13
13
|
export const agentStatusEnum = z.enum(['idle', 'working', 'dead']);
|
|
14
14
|
export const assignmentStatusEnum = z.enum(['in_progress', 'completed', 'failed', 'blocked']);
|
|
15
15
|
export const commandEnum = z.enum(['BUILD_FREEZE', 'PAUSE', 'RESUME', 'SHUTDOWN']);
|
|
@@ -30,6 +30,7 @@ export const checkinSchema = z.object({
|
|
|
30
30
|
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
31
31
|
capabilities: z.array(z.string().max(50)).max(20).optional(),
|
|
32
32
|
workspace: z.string().max(50).optional(),
|
|
33
|
+
channelUrl: z.string().url().max(200).optional(),
|
|
33
34
|
});
|
|
34
35
|
|
|
35
36
|
export const checkoutSchema = z.object({
|
|
@@ -40,11 +41,13 @@ export const checkoutSchema = z.object({
|
|
|
40
41
|
|
|
41
42
|
export const assignCreateSchema = z.object({
|
|
42
43
|
agentId: z.string().uuid().optional(),
|
|
44
|
+
worker_name: z.string().min(1).max(50).optional(),
|
|
43
45
|
task: z.string().min(1).max(1000),
|
|
44
46
|
description: z.string().max(5000).optional(),
|
|
45
47
|
workspace: z.string().max(50).optional(),
|
|
46
48
|
priority: z.number().int().min(0).max(10).default(0),
|
|
47
49
|
blocked_by: z.string().uuid().optional(),
|
|
50
|
+
context: z.string().max(10000).optional(),
|
|
48
51
|
});
|
|
49
52
|
|
|
50
53
|
export const assignmentQuerySchema = z.object({
|
|
@@ -58,12 +61,27 @@ export const nextSchema = z.object({
|
|
|
58
61
|
workspace: z.string().max(50).optional(),
|
|
59
62
|
role: agentRoleEnum.default('worker'),
|
|
60
63
|
capabilities: z.array(z.string().max(50)).max(20).optional(),
|
|
64
|
+
channelUrl: z.string().url().max(200).optional(),
|
|
61
65
|
});
|
|
62
66
|
|
|
63
67
|
export const assignmentClaimSchema = z.object({
|
|
64
68
|
agentId: z.string().uuid(),
|
|
65
69
|
});
|
|
66
70
|
|
|
71
|
+
export const assignmentsListSchema = z.object({
|
|
72
|
+
status: z.enum(['pending', 'assigned', 'in_progress', 'completed', 'failed', 'blocked']).optional(),
|
|
73
|
+
workspace: z.string().max(50).optional(),
|
|
74
|
+
agent_id: z.string().uuid().optional(),
|
|
75
|
+
limit: z.coerce.number().int().min(1).max(100).default(20),
|
|
76
|
+
offset: z.coerce.number().int().min(0).default(0),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
export const reassignSchema = z.object({
|
|
80
|
+
assignmentId: z.string().uuid(),
|
|
81
|
+
targetAgentId: z.string().uuid().optional(),
|
|
82
|
+
target_worker_name: z.string().min(1).max(50).optional(),
|
|
83
|
+
});
|
|
84
|
+
|
|
67
85
|
export const assignmentUpdateSchema = z.object({
|
|
68
86
|
status: assignmentStatusEnum,
|
|
69
87
|
result: z.string().max(10000).optional(),
|
|
@@ -118,6 +136,11 @@ export const findingsQuerySchema = z.object({
|
|
|
118
136
|
limit: z.coerce.number().int().min(1).max(200).default(50),
|
|
119
137
|
});
|
|
120
138
|
|
|
139
|
+
export const findingUpdateSchema = z.object({
|
|
140
|
+
status: findingStatusEnum.optional(),
|
|
141
|
+
suggestion: z.string().max(5000).optional(),
|
|
142
|
+
});
|
|
143
|
+
|
|
121
144
|
// ─── Param Schemas ─────────────────────────────────────────────
|
|
122
145
|
|
|
123
146
|
export const assignmentIdParamSchema = z.object({ id: z.string().uuid() });
|
|
@@ -134,12 +157,23 @@ export const pulseSchema = z.object({
|
|
|
134
157
|
export const decisionsQuerySchema = z.object({
|
|
135
158
|
since_id: z.coerce.number().int().min(0).default(0),
|
|
136
159
|
assignment_id: z.string().max(100).optional(),
|
|
160
|
+
workspace: z.string().max(50).optional(),
|
|
137
161
|
limit: z.coerce.number().int().min(1).max(200).default(20),
|
|
138
162
|
});
|
|
139
163
|
|
|
164
|
+
export const decisionCreateSchema = z.object({
|
|
165
|
+
agentId: z.string().uuid(),
|
|
166
|
+
assignment_id: z.string().max(100).optional(),
|
|
167
|
+
tags: z.string().max(500).optional(),
|
|
168
|
+
summary: z.string().min(1).max(5000),
|
|
169
|
+
});
|
|
170
|
+
|
|
140
171
|
// ─── Status / Events ────────────────────────────────────────────
|
|
141
172
|
|
|
142
173
|
export const eventsQuerySchema = z.object({
|
|
174
|
+
since_id: z.coerce.number().int().min(0).default(0),
|
|
175
|
+
agent_id: z.string().uuid().optional(),
|
|
176
|
+
event_type: z.string().max(50).optional(),
|
|
143
177
|
limit: z.coerce.number().int().min(1).max(200).default(50),
|
|
144
178
|
});
|
|
145
179
|
|
|
@@ -153,3 +187,53 @@ export const workersQuerySchema = z.object({
|
|
|
153
187
|
status: agentStatusEnum.optional(),
|
|
154
188
|
workspace: z.string().max(50).optional(),
|
|
155
189
|
});
|
|
190
|
+
|
|
191
|
+
// ─── Agent Params ─────────────────────────────────────────
|
|
192
|
+
|
|
193
|
+
export const agentIdParamSchema = z.object({ id: z.string().uuid() });
|
|
194
|
+
|
|
195
|
+
// ─── Timeline ─────────────────────────────────────────────
|
|
196
|
+
|
|
197
|
+
export const timelineQuerySchema = z.object({
|
|
198
|
+
limit: z.coerce.number().int().min(1).max(200).default(50),
|
|
199
|
+
since: z.string().max(30).optional(),
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// ─── Channel Sessions ──────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
export const channelRegisterSchema = z.object({
|
|
205
|
+
agentId: z.string().uuid(),
|
|
206
|
+
channelId: z.string().min(1).max(200),
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
export const channelDeregisterSchema = z.object({
|
|
210
|
+
agentId: z.string().uuid(),
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
export const channelPushSchema = z.object({
|
|
214
|
+
agentId: z.string().uuid(),
|
|
215
|
+
message: z.string().min(1).max(10000),
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// ─── Stats ─────────────────────────────────────────────────────
|
|
219
|
+
|
|
220
|
+
export const statsResponseSchema = z.object({
|
|
221
|
+
workers: z.object({
|
|
222
|
+
total: z.number().int(),
|
|
223
|
+
alive: z.number().int(),
|
|
224
|
+
idle: z.number().int(),
|
|
225
|
+
working: z.number().int(),
|
|
226
|
+
}),
|
|
227
|
+
tasks: z.object({
|
|
228
|
+
total_assigned: z.number().int(),
|
|
229
|
+
completed: z.number().int(),
|
|
230
|
+
failed: z.number().int(),
|
|
231
|
+
pending: z.number().int(),
|
|
232
|
+
avg_completion_seconds: z.number().nullable(),
|
|
233
|
+
}),
|
|
234
|
+
decisions: z.object({
|
|
235
|
+
total: z.number().int(),
|
|
236
|
+
last_hour: z.number().int(),
|
|
237
|
+
}),
|
|
238
|
+
uptime_seconds: z.number(),
|
|
239
|
+
});
|
|
@@ -80,6 +80,9 @@ export function purgeDeadAgents(db: Database.Database, maxAgeHours = 24): number
|
|
|
80
80
|
|
|
81
81
|
/** Clean slate on startup: mark all live agents dead, release locks, clear commands. */
|
|
82
82
|
export function cleanSlate(db: Database.Database): void {
|
|
83
|
+
// Always clear commands, even if no alive agents remain
|
|
84
|
+
db.prepare(`UPDATE coord_commands SET cleared_at = datetime('now') WHERE cleared_at IS NULL`).run();
|
|
85
|
+
|
|
83
86
|
const alive = db.prepare(
|
|
84
87
|
`SELECT id, name FROM coord_agents WHERE status != 'dead'`
|
|
85
88
|
).all() as Array<{ id: string; name: string }>;
|
|
@@ -91,7 +94,5 @@ export function cleanSlate(db: Database.Database): void {
|
|
|
91
94
|
db.prepare(`DELETE FROM coord_locks WHERE agent_id = ?`).run(agent.id);
|
|
92
95
|
}
|
|
93
96
|
|
|
94
|
-
db.prepare(`UPDATE coord_commands SET cleared_at = datetime('now') WHERE cleared_at IS NULL`).run();
|
|
95
|
-
|
|
96
97
|
console.log(` Coordination clean slate: marked ${alive.length} agent(s) from previous session as dead`);
|
|
97
98
|
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
// Copyright 2026 Robert Winter / Complete Ideas
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* TypeScript interfaces for coordination API responses.
|
|
5
|
+
* These types support typed API clients consuming the coordination HTTP endpoints.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ─── Shared Primitives ─────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
export type AgentRole = 'worker' | 'orchestrator' | 'coordinator' | 'dev-lead';
|
|
11
|
+
export type AgentStatus = 'idle' | 'working' | 'dead';
|
|
12
|
+
export type AssignmentStatus = 'pending' | 'assigned' | 'in_progress' | 'completed' | 'failed' | 'blocked';
|
|
13
|
+
export type CommandType = 'BUILD_FREEZE' | 'PAUSE' | 'RESUME' | 'SHUTDOWN';
|
|
14
|
+
export type FindingSeverity = 'critical' | 'error' | 'warn' | 'info';
|
|
15
|
+
export type FindingStatus = 'open' | 'resolved';
|
|
16
|
+
|
|
17
|
+
// ─── Agent ─────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
export interface CheckinResponse {
|
|
20
|
+
agentId: string;
|
|
21
|
+
action: 'registered' | 'heartbeat' | 'reconnected';
|
|
22
|
+
status: string;
|
|
23
|
+
workspace: string | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface AgentDetail {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
role: AgentRole;
|
|
30
|
+
status: AgentStatus;
|
|
31
|
+
current_task: string | null;
|
|
32
|
+
pid: number | null;
|
|
33
|
+
capabilities: string | null;
|
|
34
|
+
workspace: string | null;
|
|
35
|
+
metadata: string | null;
|
|
36
|
+
last_seen: string;
|
|
37
|
+
started_at: string | null;
|
|
38
|
+
seconds_since_seen: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface AgentResponse {
|
|
42
|
+
agent: AgentDetail;
|
|
43
|
+
assignment: AssignmentSummary | null;
|
|
44
|
+
locks: LockEntry[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ─── Assignments ───────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
export interface Assignment {
|
|
50
|
+
id: string;
|
|
51
|
+
agent_id: string | null;
|
|
52
|
+
task: string;
|
|
53
|
+
description: string | null;
|
|
54
|
+
status: AssignmentStatus;
|
|
55
|
+
priority: number;
|
|
56
|
+
blocked_by: string | null;
|
|
57
|
+
created_at: string;
|
|
58
|
+
started_at: string | null;
|
|
59
|
+
completed_at: string | null;
|
|
60
|
+
result: string | null;
|
|
61
|
+
commit_sha: string | null;
|
|
62
|
+
workspace: string | null;
|
|
63
|
+
context: string | null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface AssignmentSummary {
|
|
67
|
+
id: string;
|
|
68
|
+
task: string;
|
|
69
|
+
status: AssignmentStatus;
|
|
70
|
+
priority: number;
|
|
71
|
+
created_at: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface AssignmentWithAgent extends Assignment {
|
|
75
|
+
agent_name: string | null;
|
|
76
|
+
is_blocked: 0 | 1;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface AssignCreateResponse {
|
|
80
|
+
assignmentId: string;
|
|
81
|
+
status: 'assigned' | 'pending';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface AssignmentsListResponse {
|
|
85
|
+
assignments: AssignmentWithAgent[];
|
|
86
|
+
total: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ─── Next ──────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
export interface CommandEntry {
|
|
92
|
+
id: number;
|
|
93
|
+
command: CommandType;
|
|
94
|
+
reason: string | null;
|
|
95
|
+
issued_by: string | null;
|
|
96
|
+
issued_at: string;
|
|
97
|
+
workspace: string | null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface NextResponse {
|
|
101
|
+
agentId: string;
|
|
102
|
+
status: string;
|
|
103
|
+
assignment: Assignment | null;
|
|
104
|
+
commands: CommandEntry[];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ─── Locks ─────────────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
export interface LockEntry {
|
|
110
|
+
file_path: string;
|
|
111
|
+
locked_at: string;
|
|
112
|
+
reason: string | null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface LockWithAgent extends LockEntry {
|
|
116
|
+
agent_id: string;
|
|
117
|
+
agent_name: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface LocksResponse {
|
|
121
|
+
locks: LockWithAgent[];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ─── Commands ──────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
export interface CommandResponse {
|
|
127
|
+
active: boolean;
|
|
128
|
+
command?: CommandType;
|
|
129
|
+
reason?: string | null;
|
|
130
|
+
issued_at?: string;
|
|
131
|
+
commands: CommandEntry[];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ─── Workers ───────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
export interface WorkerEntry {
|
|
137
|
+
id: string;
|
|
138
|
+
name: string;
|
|
139
|
+
role: AgentRole;
|
|
140
|
+
status: AgentStatus;
|
|
141
|
+
currentTask: string | null;
|
|
142
|
+
capabilities: string[];
|
|
143
|
+
workspace: string | null;
|
|
144
|
+
lastSeen: string;
|
|
145
|
+
secondsSinceSeen: number;
|
|
146
|
+
alive: boolean;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface WorkersResponse {
|
|
150
|
+
count: number;
|
|
151
|
+
idle: number;
|
|
152
|
+
working: number;
|
|
153
|
+
workers: WorkerEntry[];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ─── Events ────────────────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
export interface EventEntry {
|
|
159
|
+
id: number;
|
|
160
|
+
agent_id: string | null;
|
|
161
|
+
agent_name: string | null;
|
|
162
|
+
event_type: string;
|
|
163
|
+
detail: string | null;
|
|
164
|
+
created_at: string;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface EventsResponse {
|
|
168
|
+
events: EventEntry[];
|
|
169
|
+
last_id: number;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ─── Decisions ─────────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
export interface DecisionEntry {
|
|
175
|
+
id: number;
|
|
176
|
+
author_id: string;
|
|
177
|
+
author_name: string;
|
|
178
|
+
assignment_id: string | null;
|
|
179
|
+
tags: string | null;
|
|
180
|
+
summary: string;
|
|
181
|
+
created_at: string;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface DecisionsResponse {
|
|
185
|
+
decisions: DecisionEntry[];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ─── Findings ──────────────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
export interface FindingEntry {
|
|
191
|
+
id: number;
|
|
192
|
+
category: string;
|
|
193
|
+
severity: FindingSeverity;
|
|
194
|
+
file_path: string | null;
|
|
195
|
+
line_number: number | null;
|
|
196
|
+
description: string;
|
|
197
|
+
suggestion: string | null;
|
|
198
|
+
status: FindingStatus;
|
|
199
|
+
created_at: string;
|
|
200
|
+
agent_name: string;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export interface FindingSeverityCount {
|
|
204
|
+
severity: FindingSeverity;
|
|
205
|
+
count: number;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export interface FindingsResponse {
|
|
209
|
+
findings: FindingEntry[];
|
|
210
|
+
stats: FindingSeverityCount[];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ─── Channel Sessions ─────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
export interface ChannelSession {
|
|
216
|
+
agent_id: string;
|
|
217
|
+
agent_name: string;
|
|
218
|
+
channel_id: string;
|
|
219
|
+
connected_at: string;
|
|
220
|
+
last_push_at: string | null;
|
|
221
|
+
push_count: number;
|
|
222
|
+
status: string;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export interface ChannelSessionsResponse {
|
|
226
|
+
sessions: ChannelSession[];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ─── Status ────────────────────────────────────────────────────
|
|
230
|
+
|
|
231
|
+
export interface StatusResponse {
|
|
232
|
+
agents: Array<{
|
|
233
|
+
id: string;
|
|
234
|
+
name: string;
|
|
235
|
+
role: AgentRole;
|
|
236
|
+
status: AgentStatus;
|
|
237
|
+
current_task: string | null;
|
|
238
|
+
last_seen: string;
|
|
239
|
+
seconds_since_seen: number;
|
|
240
|
+
}>;
|
|
241
|
+
assignments: Array<{
|
|
242
|
+
id: string;
|
|
243
|
+
task: string;
|
|
244
|
+
description: string | null;
|
|
245
|
+
status: AssignmentStatus;
|
|
246
|
+
agent_id: string | null;
|
|
247
|
+
agent_name: string | null;
|
|
248
|
+
created_at: string;
|
|
249
|
+
started_at: string | null;
|
|
250
|
+
completed_at: string | null;
|
|
251
|
+
}>;
|
|
252
|
+
locks: LockWithAgent[];
|
|
253
|
+
stats: {
|
|
254
|
+
alive_agents: number;
|
|
255
|
+
busy_agents: number;
|
|
256
|
+
pending_tasks: number;
|
|
257
|
+
active_tasks: number;
|
|
258
|
+
active_locks: number;
|
|
259
|
+
open_findings: number;
|
|
260
|
+
urgent_findings: number;
|
|
261
|
+
};
|
|
262
|
+
recentFindings: Array<{
|
|
263
|
+
id: number;
|
|
264
|
+
category: string;
|
|
265
|
+
severity: FindingSeverity;
|
|
266
|
+
file_path: string | null;
|
|
267
|
+
description: string;
|
|
268
|
+
agent_name: string;
|
|
269
|
+
created_at: string;
|
|
270
|
+
}>;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ─── Stats ─────────────────────────────────────────────────────
|
|
274
|
+
|
|
275
|
+
export interface StatsResponse {
|
|
276
|
+
workers: {
|
|
277
|
+
total: number;
|
|
278
|
+
alive: number;
|
|
279
|
+
idle: number;
|
|
280
|
+
working: number;
|
|
281
|
+
};
|
|
282
|
+
tasks: {
|
|
283
|
+
total_assigned: number;
|
|
284
|
+
completed: number;
|
|
285
|
+
failed: number;
|
|
286
|
+
pending: number;
|
|
287
|
+
avg_completion_seconds: number | null;
|
|
288
|
+
};
|
|
289
|
+
decisions: {
|
|
290
|
+
total: number;
|
|
291
|
+
last_hour: number;
|
|
292
|
+
};
|
|
293
|
+
uptime_seconds: number;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// ─── Stale ─────────────────────────────────────────────────────
|
|
297
|
+
|
|
298
|
+
export interface StaleAgent {
|
|
299
|
+
id: string;
|
|
300
|
+
name: string;
|
|
301
|
+
role: string;
|
|
302
|
+
status: string;
|
|
303
|
+
last_seen: string;
|
|
304
|
+
seconds_since_seen: number;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export interface StaleResponse {
|
|
308
|
+
stale: StaleAgent[];
|
|
309
|
+
threshold_seconds: number;
|
|
310
|
+
cleaned?: number;
|
|
311
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Copyright 2026 Robert Winter / Complete Ideas
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Promise-based write mutex for SQLite coordination routes.
|
|
5
|
+
*
|
|
6
|
+
* SQLite only allows one writer at a time. Under burst from 5+ concurrent
|
|
7
|
+
* workers, multiple async handlers can race for the write lock, causing
|
|
8
|
+
* SQLITE_BUSY errors. This mutex serializes write operations (POST, PATCH,
|
|
9
|
+
* PUT, DELETE) through a single-concurrency queue while keeping reads (GET)
|
|
10
|
+
* unguarded for full parallelism.
|
|
11
|
+
*
|
|
12
|
+
* Usage: Register as a Fastify preHandler hook in the coordination module.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates a simple single-concurrency mutex.
|
|
17
|
+
* acquire() returns a release function — call it when done.
|
|
18
|
+
*/
|
|
19
|
+
export function createWriteMutex(): {
|
|
20
|
+
acquire(): Promise<() => void>;
|
|
21
|
+
pending: () => number;
|
|
22
|
+
} {
|
|
23
|
+
let queue: Array<(release: () => void) => void> = [];
|
|
24
|
+
let locked = false;
|
|
25
|
+
|
|
26
|
+
function release(): void {
|
|
27
|
+
const next = queue.shift();
|
|
28
|
+
if (next) {
|
|
29
|
+
// Hand lock directly to next waiter (no unlock/relock gap)
|
|
30
|
+
next(release);
|
|
31
|
+
} else {
|
|
32
|
+
locked = false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function acquire(): Promise<() => void> {
|
|
37
|
+
if (!locked) {
|
|
38
|
+
locked = true;
|
|
39
|
+
return Promise.resolve(release);
|
|
40
|
+
}
|
|
41
|
+
return new Promise<() => void>((resolve) => {
|
|
42
|
+
queue.push(resolve);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
acquire,
|
|
48
|
+
pending: () => queue.length,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** HTTP methods that perform writes and need serialization. */
|
|
53
|
+
const WRITE_METHODS = new Set(['POST', 'PATCH', 'PUT', 'DELETE']);
|
|
54
|
+
|
|
55
|
+
/** Routes that are safe to skip the mutex (read-only despite POST method). */
|
|
56
|
+
const READ_ONLY_ROUTES = new Set(['/next']);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Check if a request needs write serialization.
|
|
60
|
+
* GET/HEAD/OPTIONS are always reads. POST /next is a read (checkin is idempotent upsert
|
|
61
|
+
* but low-contention). All other write methods go through the mutex.
|
|
62
|
+
*/
|
|
63
|
+
export function needsWriteLock(method: string, url: string): boolean {
|
|
64
|
+
if (!WRITE_METHODS.has(method)) return false;
|
|
65
|
+
// Strip query string for route matching
|
|
66
|
+
const path = url.split('?')[0];
|
|
67
|
+
if (READ_ONLY_ROUTES.has(path)) return false;
|
|
68
|
+
return true;
|
|
69
|
+
}
|
package/src/core/embeddings.ts
CHANGED
|
@@ -53,6 +53,11 @@ export async function embed(text: string): Promise<number[]> {
|
|
|
53
53
|
return Array.from(result.data as Float32Array).slice(0, DIMENSIONS);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/** Get the current embedding model ID (for version tracking in stored embeddings) */
|
|
57
|
+
export function getModelId(): string {
|
|
58
|
+
return MODEL_ID;
|
|
59
|
+
}
|
|
60
|
+
|
|
56
61
|
/**
|
|
57
62
|
* Generate embeddings for multiple texts in a batch.
|
|
58
63
|
* More efficient than calling embed() in a loop.
|