@dotdrelle/wiki-manager 0.11.9 → 0.12.0
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 +32 -7
- package/agents.docker-compose.yml +3 -0
- package/docker-compose.yml +1 -0
- package/package.json +3 -2
- package/src/activity/activityAggregator.js +109 -0
- package/src/activity/activityAggregator.test.js +60 -0
- package/src/activity/activityDeduplicator.js +50 -0
- package/src/activity/progressCalculator.js +61 -0
- package/src/activity/runSynthesis.js +15 -0
- package/src/agent/graph.js +65 -30
- package/src/agent/graph.test.js +28 -5
- package/src/cli/wiki-manager.js +22 -0
- package/src/contracts/schemas.js +249 -13
- package/src/contracts/schemas.test.js +145 -0
- package/src/core/activity.js +8 -5
- package/src/core/agentEvents.js +260 -23
- package/src/core/agentEvents.test.js +53 -0
- package/src/core/compose.js +6 -1
- package/src/core/dockerCompose.test.js +12 -0
- package/src/core/documentIntake.js +33 -38
- package/src/core/documentIntake.test.js +25 -0
- package/src/core/env.js +11 -1
- package/src/core/jobQueue.js +26 -2
- package/src/core/mcp.js +1 -1
- package/src/core/plan.js +46 -4
- package/src/core/plan.test.js +16 -1
- package/src/core/planPatch.js +64 -3
- package/src/core/planPatch.test.js +110 -1
- package/src/core/queueStore.test.js +21 -0
- package/src/core/runtimeLog.js +119 -0
- package/src/core/runtimeLog.test.js +84 -0
- package/src/core/wikirc.js +22 -0
- package/src/core/wikirc.test.js +49 -1
- package/src/core/workflow.js +14 -3
- package/src/graph/graphAggregator.js +5 -0
- package/src/graph/graphPatch.js +8 -0
- package/src/graph/graphSnapshot.js +14 -0
- package/src/graph/graphVisibilityPolicy.js +40 -0
- package/src/graph/runGraphProjector.js +87 -0
- package/src/graph/runGraphProjector.test.js +111 -0
- package/src/orchestrator/agentRegistry.js +172 -0
- package/src/orchestrator/agentRegistry.test.js +114 -0
- package/src/orchestrator/approvalPolicy.js +126 -0
- package/src/orchestrator/approvalPolicy.test.js +78 -0
- package/src/orchestrator/assignmentManager.js +80 -0
- package/src/orchestrator/attemptManager.js +168 -0
- package/src/orchestrator/attemptManager.test.js +160 -0
- package/src/orchestrator/budgetManager.js +127 -0
- package/src/orchestrator/capabilityRegistry.js +64 -0
- package/src/orchestrator/capabilityRegistry.test.js +59 -0
- package/src/orchestrator/capabilityResolver.js +151 -0
- package/src/orchestrator/capabilityResolver.test.js +127 -0
- package/src/orchestrator/dependencyResolver.js +112 -0
- package/src/orchestrator/dispatcher.js +302 -0
- package/src/orchestrator/lockManager.js +51 -0
- package/src/orchestrator/planIntegrator.js +268 -0
- package/src/orchestrator/planIntegrator.test.js +213 -0
- package/src/orchestrator/planValidator.js +534 -0
- package/src/orchestrator/planValidator.test.js +262 -0
- package/src/orchestrator/resultAggregator.js +248 -0
- package/src/orchestrator/resultAggregator.test.js +211 -0
- package/src/orchestrator/scheduler.js +103 -0
- package/src/orchestrator/scheduler.test.js +138 -0
- package/src/runtime/approvals.js +130 -1
- package/src/runtime/donna-contract.test.js +293 -30
- package/src/runtime/recoveryManager.js +176 -0
- package/src/runtime/recoveryManager.test.js +162 -0
- package/src/runtime/runner.e2e.test.js +98 -12
- package/src/runtime/runner.js +261 -227
- package/src/runtime/runner.test.js +249 -452
- package/src/runtime/server.js +137 -20
- package/src/runtime/server.test.js +137 -7
- package/src/runtime/store.js +837 -2
- package/src/runtime/store.test.js +245 -4
- package/src/runtime/supervisor.js +33 -1
- package/src/runtime/supervisor.test.js +47 -1
- package/src/shell/RightPane.tsx +8 -5
- package/src/shell/repl.js +4 -9
- package/src/shell/repl.test.js +25 -4
- package/src/shell/tui.tsx +1 -0
- package/src/shell/useSession.ts +38 -10
package/src/runtime/store.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, join, resolve } from 'node:path';
|
|
3
3
|
import { DatabaseSync } from 'node:sqlite';
|
|
4
4
|
import { applyAgentProjectionToSession, dispatchAgentEvent, reduceAgentEvents } from '../core/agentEvents.js';
|
|
5
5
|
import { defaultRuntimeStateDir } from '../core/env.js';
|
|
6
6
|
import { projectQueue } from '../core/jobQueue.js';
|
|
7
7
|
import { projectWorkflow } from '../core/workflow.js';
|
|
8
|
+
import { createCapabilityRegistry } from '../orchestrator/capabilityRegistry.js';
|
|
8
9
|
|
|
9
10
|
export { defaultRuntimeStateDir };
|
|
10
11
|
|
|
@@ -78,6 +79,130 @@ export function openRuntimeStore({ stateDir = defaultRuntimeStateDir(), fileName
|
|
|
78
79
|
finished_at TEXT,
|
|
79
80
|
updated_at TEXT NOT NULL
|
|
80
81
|
);
|
|
82
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
83
|
+
instance_id TEXT PRIMARY KEY,
|
|
84
|
+
agent_type TEXT NOT NULL,
|
|
85
|
+
display_name TEXT,
|
|
86
|
+
contract_version TEXT NOT NULL,
|
|
87
|
+
health TEXT NOT NULL,
|
|
88
|
+
description TEXT NOT NULL,
|
|
89
|
+
first_seen_at TEXT,
|
|
90
|
+
last_seen_at TEXT
|
|
91
|
+
);
|
|
92
|
+
CREATE TABLE IF NOT EXISTS agent_capabilities (
|
|
93
|
+
instance_id TEXT NOT NULL,
|
|
94
|
+
capability_id TEXT NOT NULL,
|
|
95
|
+
version TEXT NOT NULL,
|
|
96
|
+
default_requires_approval INTEGER DEFAULT 0,
|
|
97
|
+
declaration TEXT NOT NULL,
|
|
98
|
+
PRIMARY KEY (instance_id, capability_id, version)
|
|
99
|
+
);
|
|
100
|
+
CREATE TABLE IF NOT EXISTS task_groups (
|
|
101
|
+
run_id TEXT NOT NULL,
|
|
102
|
+
id TEXT NOT NULL,
|
|
103
|
+
local_id TEXT,
|
|
104
|
+
label TEXT NOT NULL,
|
|
105
|
+
recommended_concurrency INTEGER,
|
|
106
|
+
progress_weight REAL,
|
|
107
|
+
declaration TEXT NOT NULL,
|
|
108
|
+
created_at TEXT,
|
|
109
|
+
updated_at TEXT,
|
|
110
|
+
PRIMARY KEY (run_id, id),
|
|
111
|
+
FOREIGN KEY (run_id) REFERENCES runs(id) ON DELETE CASCADE
|
|
112
|
+
);
|
|
113
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
114
|
+
run_id TEXT NOT NULL,
|
|
115
|
+
id TEXT NOT NULL,
|
|
116
|
+
local_id TEXT,
|
|
117
|
+
agent_instance_id TEXT,
|
|
118
|
+
label TEXT NOT NULL,
|
|
119
|
+
required_capability TEXT NOT NULL,
|
|
120
|
+
operation TEXT NOT NULL,
|
|
121
|
+
arguments TEXT NOT NULL,
|
|
122
|
+
group_id TEXT,
|
|
123
|
+
depends_on_group TEXT,
|
|
124
|
+
barrier INTEGER DEFAULT 0,
|
|
125
|
+
parallelizable INTEGER DEFAULT 0,
|
|
126
|
+
recommended_concurrency INTEGER,
|
|
127
|
+
input_refs TEXT NOT NULL,
|
|
128
|
+
expected_output_refs TEXT NOT NULL,
|
|
129
|
+
locks TEXT NOT NULL,
|
|
130
|
+
requires_approval INTEGER DEFAULT 0,
|
|
131
|
+
approval_class TEXT,
|
|
132
|
+
approval_summary TEXT,
|
|
133
|
+
idempotency_key TEXT,
|
|
134
|
+
progress_weight REAL NOT NULL,
|
|
135
|
+
priority REAL,
|
|
136
|
+
retry_policy TEXT,
|
|
137
|
+
declaration TEXT NOT NULL,
|
|
138
|
+
status TEXT NOT NULL,
|
|
139
|
+
created_at TEXT,
|
|
140
|
+
updated_at TEXT,
|
|
141
|
+
PRIMARY KEY (run_id, id),
|
|
142
|
+
FOREIGN KEY (run_id) REFERENCES runs(id) ON DELETE CASCADE
|
|
143
|
+
);
|
|
144
|
+
CREATE TABLE IF NOT EXISTS task_dependencies (
|
|
145
|
+
run_id TEXT NOT NULL,
|
|
146
|
+
task_id TEXT NOT NULL,
|
|
147
|
+
depends_on_task_id TEXT NOT NULL,
|
|
148
|
+
created_at TEXT,
|
|
149
|
+
PRIMARY KEY (run_id, task_id, depends_on_task_id),
|
|
150
|
+
FOREIGN KEY (run_id, task_id) REFERENCES tasks(run_id, id) ON DELETE CASCADE
|
|
151
|
+
);
|
|
152
|
+
CREATE TABLE IF NOT EXISTS plan_revisions (
|
|
153
|
+
run_id TEXT NOT NULL,
|
|
154
|
+
revision INTEGER NOT NULL,
|
|
155
|
+
previous_revision INTEGER,
|
|
156
|
+
reason TEXT,
|
|
157
|
+
task_ids TEXT NOT NULL,
|
|
158
|
+
created_at TEXT,
|
|
159
|
+
PRIMARY KEY (run_id, revision),
|
|
160
|
+
FOREIGN KEY (run_id) REFERENCES runs(id) ON DELETE CASCADE
|
|
161
|
+
);
|
|
162
|
+
CREATE TABLE IF NOT EXISTS task_assignments (
|
|
163
|
+
task_id TEXT NOT NULL,
|
|
164
|
+
attempt_id TEXT NOT NULL,
|
|
165
|
+
agent_instance_id TEXT NOT NULL,
|
|
166
|
+
agent_id TEXT,
|
|
167
|
+
pool_id TEXT,
|
|
168
|
+
assigned_at TEXT NOT NULL,
|
|
169
|
+
PRIMARY KEY (task_id, attempt_id)
|
|
170
|
+
);
|
|
171
|
+
CREATE TABLE IF NOT EXISTS task_attempts (
|
|
172
|
+
attempt_id TEXT PRIMARY KEY,
|
|
173
|
+
task_id TEXT NOT NULL,
|
|
174
|
+
run_id TEXT NOT NULL,
|
|
175
|
+
status TEXT NOT NULL,
|
|
176
|
+
job_id TEXT,
|
|
177
|
+
started_at TEXT,
|
|
178
|
+
finished_at TEXT,
|
|
179
|
+
error TEXT
|
|
180
|
+
);
|
|
181
|
+
CREATE TABLE IF NOT EXISTS task_results (
|
|
182
|
+
attempt_id TEXT PRIMARY KEY,
|
|
183
|
+
task_id TEXT NOT NULL,
|
|
184
|
+
status TEXT NOT NULL,
|
|
185
|
+
summary TEXT,
|
|
186
|
+
output_refs TEXT,
|
|
187
|
+
metrics TEXT,
|
|
188
|
+
error TEXT,
|
|
189
|
+
created_at TEXT
|
|
190
|
+
);
|
|
191
|
+
CREATE TABLE IF NOT EXISTS approval_grants (
|
|
192
|
+
id TEXT PRIMARY KEY,
|
|
193
|
+
run_id TEXT NOT NULL,
|
|
194
|
+
workspace_id TEXT,
|
|
195
|
+
plan_revision INTEGER,
|
|
196
|
+
scope TEXT NOT NULL,
|
|
197
|
+
task_id TEXT,
|
|
198
|
+
group_id TEXT,
|
|
199
|
+
approval_classes TEXT NOT NULL,
|
|
200
|
+
status TEXT NOT NULL,
|
|
201
|
+
reason TEXT,
|
|
202
|
+
created_at TEXT NOT NULL,
|
|
203
|
+
granted_at TEXT,
|
|
204
|
+
rejected_at TEXT
|
|
205
|
+
);
|
|
81
206
|
`);
|
|
82
207
|
ensureColumn(db, 'events', 'sequence', 'INTEGER');
|
|
83
208
|
ensureColumn(db, 'events', 'workspace', 'TEXT');
|
|
@@ -86,6 +211,11 @@ export function openRuntimeStore({ stateDir = defaultRuntimeStateDir(), fileName
|
|
|
86
211
|
backfillEventSequence(db);
|
|
87
212
|
db.exec('CREATE INDEX IF NOT EXISTS idx_events_workspace ON events(workspace)');
|
|
88
213
|
db.exec('CREATE INDEX IF NOT EXISTS idx_events_sequence ON events(sequence)');
|
|
214
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_tasks_run_status ON tasks(run_id, status)');
|
|
215
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_task_attempts_task ON task_attempts(task_id)');
|
|
216
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_task_attempts_run ON task_attempts(run_id)');
|
|
217
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_task_results_task ON task_results(task_id)');
|
|
218
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_approval_grants_run_revision ON approval_grants(run_id, workspace_id, plan_revision, status)');
|
|
89
219
|
db.exec(`PRAGMA user_version = ${RUNTIME_STORE_SCHEMA_VERSION}`);
|
|
90
220
|
purgeOldTerminalRuns(db);
|
|
91
221
|
|
|
@@ -117,6 +247,10 @@ export function openRuntimeStore({ stateDir = defaultRuntimeStateDir(), fileName
|
|
|
117
247
|
input = COALESCE(excluded.input, runs.input),
|
|
118
248
|
updated_at = excluded.updated_at
|
|
119
249
|
`);
|
|
250
|
+
const ensureRun = db.prepare(`
|
|
251
|
+
INSERT OR IGNORE INTO runs (id, workspace, status, input, created_at, updated_at)
|
|
252
|
+
VALUES (?, ?, 'running', NULL, ?, ?)
|
|
253
|
+
`);
|
|
120
254
|
const listRunsStatement = db.prepare(`
|
|
121
255
|
SELECT id, workspace, status, input, created_at, updated_at
|
|
122
256
|
FROM runs
|
|
@@ -198,6 +332,191 @@ export function openRuntimeStore({ stateDir = defaultRuntimeStateDir(), fileName
|
|
|
198
332
|
WHERE workspace IS NOT NULL AND status IN (${RECOVERABLE_QUEUE_STATUSES.map(() => '?').join(', ')})
|
|
199
333
|
ORDER BY workspace
|
|
200
334
|
`);
|
|
335
|
+
const upsertAgentStatement = db.prepare(`
|
|
336
|
+
INSERT INTO agents (
|
|
337
|
+
instance_id, agent_type, display_name, contract_version, health,
|
|
338
|
+
description, first_seen_at, last_seen_at
|
|
339
|
+
)
|
|
340
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
341
|
+
ON CONFLICT(instance_id) DO UPDATE SET
|
|
342
|
+
agent_type = excluded.agent_type,
|
|
343
|
+
display_name = excluded.display_name,
|
|
344
|
+
contract_version = excluded.contract_version,
|
|
345
|
+
health = excluded.health,
|
|
346
|
+
description = excluded.description,
|
|
347
|
+
first_seen_at = COALESCE(agents.first_seen_at, excluded.first_seen_at),
|
|
348
|
+
last_seen_at = excluded.last_seen_at
|
|
349
|
+
`);
|
|
350
|
+
const deleteAgentCapabilitiesStatement = db.prepare('DELETE FROM agent_capabilities WHERE instance_id = ?');
|
|
351
|
+
const upsertAgentCapabilityStatement = db.prepare(`
|
|
352
|
+
INSERT INTO agent_capabilities (
|
|
353
|
+
instance_id, capability_id, version, default_requires_approval, declaration
|
|
354
|
+
)
|
|
355
|
+
VALUES (?, ?, ?, ?, ?)
|
|
356
|
+
ON CONFLICT(instance_id, capability_id, version) DO UPDATE SET
|
|
357
|
+
default_requires_approval = excluded.default_requires_approval,
|
|
358
|
+
declaration = excluded.declaration
|
|
359
|
+
`);
|
|
360
|
+
const listAgentsStatement = db.prepare(`
|
|
361
|
+
SELECT instance_id, agent_type, display_name, contract_version, health,
|
|
362
|
+
description, first_seen_at, last_seen_at
|
|
363
|
+
FROM agents
|
|
364
|
+
ORDER BY instance_id ASC
|
|
365
|
+
`);
|
|
366
|
+
const upsertTaskGroupStatement = db.prepare(`
|
|
367
|
+
INSERT INTO task_groups (
|
|
368
|
+
run_id, id, local_id, label, recommended_concurrency, progress_weight,
|
|
369
|
+
declaration, created_at, updated_at
|
|
370
|
+
)
|
|
371
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
372
|
+
ON CONFLICT(run_id, id) DO UPDATE SET
|
|
373
|
+
label = excluded.label,
|
|
374
|
+
recommended_concurrency = excluded.recommended_concurrency,
|
|
375
|
+
progress_weight = excluded.progress_weight,
|
|
376
|
+
declaration = excluded.declaration,
|
|
377
|
+
updated_at = excluded.updated_at
|
|
378
|
+
`);
|
|
379
|
+
const upsertTaskStatement = db.prepare(`
|
|
380
|
+
INSERT INTO tasks (
|
|
381
|
+
run_id, id, local_id, agent_instance_id, label, required_capability,
|
|
382
|
+
operation, arguments, group_id, depends_on_group, barrier, parallelizable,
|
|
383
|
+
recommended_concurrency, input_refs, expected_output_refs, locks,
|
|
384
|
+
requires_approval, approval_class, approval_summary, idempotency_key,
|
|
385
|
+
progress_weight, priority, retry_policy, declaration, status, created_at,
|
|
386
|
+
updated_at
|
|
387
|
+
)
|
|
388
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
389
|
+
ON CONFLICT(run_id, id) DO UPDATE SET
|
|
390
|
+
agent_instance_id = excluded.agent_instance_id,
|
|
391
|
+
label = excluded.label,
|
|
392
|
+
required_capability = excluded.required_capability,
|
|
393
|
+
operation = excluded.operation,
|
|
394
|
+
arguments = excluded.arguments,
|
|
395
|
+
group_id = excluded.group_id,
|
|
396
|
+
depends_on_group = excluded.depends_on_group,
|
|
397
|
+
barrier = excluded.barrier,
|
|
398
|
+
parallelizable = excluded.parallelizable,
|
|
399
|
+
recommended_concurrency = excluded.recommended_concurrency,
|
|
400
|
+
input_refs = excluded.input_refs,
|
|
401
|
+
expected_output_refs = excluded.expected_output_refs,
|
|
402
|
+
locks = excluded.locks,
|
|
403
|
+
requires_approval = excluded.requires_approval,
|
|
404
|
+
approval_class = excluded.approval_class,
|
|
405
|
+
approval_summary = excluded.approval_summary,
|
|
406
|
+
idempotency_key = excluded.idempotency_key,
|
|
407
|
+
progress_weight = excluded.progress_weight,
|
|
408
|
+
priority = excluded.priority,
|
|
409
|
+
retry_policy = excluded.retry_policy,
|
|
410
|
+
declaration = excluded.declaration,
|
|
411
|
+
status = excluded.status,
|
|
412
|
+
updated_at = excluded.updated_at
|
|
413
|
+
`);
|
|
414
|
+
const deleteTaskDependenciesStatement = db.prepare('DELETE FROM task_dependencies WHERE run_id = ? AND task_id = ?');
|
|
415
|
+
const updateTaskStatusStatement = db.prepare('UPDATE tasks SET status = ?, declaration = ?, updated_at = ? WHERE run_id = ? AND id = ?');
|
|
416
|
+
const insertTaskDependencyStatement = db.prepare(`
|
|
417
|
+
INSERT OR IGNORE INTO task_dependencies (run_id, task_id, depends_on_task_id, created_at)
|
|
418
|
+
VALUES (?, ?, ?, ?)
|
|
419
|
+
`);
|
|
420
|
+
const insertPlanRevisionStatement = db.prepare(`
|
|
421
|
+
INSERT OR REPLACE INTO plan_revisions (
|
|
422
|
+
run_id, revision, previous_revision, reason, task_ids, created_at
|
|
423
|
+
)
|
|
424
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
425
|
+
`);
|
|
426
|
+
const listTasksStatement = db.prepare(`
|
|
427
|
+
SELECT * FROM tasks ORDER BY run_id ASC, id ASC
|
|
428
|
+
`);
|
|
429
|
+
const listTasksByRunStatement = db.prepare(`
|
|
430
|
+
SELECT * FROM tasks WHERE run_id = ? ORDER BY id ASC
|
|
431
|
+
`);
|
|
432
|
+
const listTaskDependenciesStatement = db.prepare(`
|
|
433
|
+
SELECT * FROM task_dependencies ORDER BY run_id ASC, task_id ASC, depends_on_task_id ASC
|
|
434
|
+
`);
|
|
435
|
+
const listTaskDependenciesByRunStatement = db.prepare(`
|
|
436
|
+
SELECT * FROM task_dependencies WHERE run_id = ? ORDER BY task_id ASC, depends_on_task_id ASC
|
|
437
|
+
`);
|
|
438
|
+
const listPlanRevisionsStatement = db.prepare(`
|
|
439
|
+
SELECT * FROM plan_revisions ORDER BY run_id ASC, revision ASC
|
|
440
|
+
`);
|
|
441
|
+
const listPlanRevisionsByRunStatement = db.prepare(`
|
|
442
|
+
SELECT * FROM plan_revisions WHERE run_id = ? ORDER BY revision ASC
|
|
443
|
+
`);
|
|
444
|
+
const upsertTaskAssignmentStatement = db.prepare(`
|
|
445
|
+
INSERT INTO task_assignments (
|
|
446
|
+
task_id, attempt_id, agent_instance_id, agent_id, pool_id, assigned_at
|
|
447
|
+
)
|
|
448
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
449
|
+
ON CONFLICT(task_id, attempt_id) DO UPDATE SET
|
|
450
|
+
agent_instance_id = excluded.agent_instance_id,
|
|
451
|
+
agent_id = excluded.agent_id,
|
|
452
|
+
pool_id = excluded.pool_id,
|
|
453
|
+
assigned_at = excluded.assigned_at
|
|
454
|
+
`);
|
|
455
|
+
const upsertTaskAttemptStatement = db.prepare(`
|
|
456
|
+
INSERT INTO task_attempts (
|
|
457
|
+
attempt_id, task_id, run_id, status, job_id, started_at, finished_at, error
|
|
458
|
+
)
|
|
459
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
460
|
+
ON CONFLICT(attempt_id) DO UPDATE SET
|
|
461
|
+
task_id = excluded.task_id,
|
|
462
|
+
run_id = excluded.run_id,
|
|
463
|
+
status = excluded.status,
|
|
464
|
+
job_id = COALESCE(excluded.job_id, task_attempts.job_id),
|
|
465
|
+
started_at = COALESCE(task_attempts.started_at, excluded.started_at),
|
|
466
|
+
finished_at = COALESCE(excluded.finished_at, task_attempts.finished_at),
|
|
467
|
+
error = COALESCE(excluded.error, task_attempts.error)
|
|
468
|
+
`);
|
|
469
|
+
const upsertTaskResultStatement = db.prepare(`
|
|
470
|
+
INSERT INTO task_results (
|
|
471
|
+
attempt_id, task_id, status, summary, output_refs, metrics, error, created_at
|
|
472
|
+
)
|
|
473
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
474
|
+
ON CONFLICT(attempt_id) DO UPDATE SET
|
|
475
|
+
task_id = excluded.task_id,
|
|
476
|
+
status = excluded.status,
|
|
477
|
+
summary = COALESCE(excluded.summary, task_results.summary),
|
|
478
|
+
output_refs = CASE WHEN excluded.output_refs = '[]' THEN task_results.output_refs ELSE excluded.output_refs END,
|
|
479
|
+
metrics = CASE WHEN excluded.metrics = '{}' THEN task_results.metrics ELSE excluded.metrics END,
|
|
480
|
+
error = COALESCE(excluded.error, task_results.error),
|
|
481
|
+
created_at = excluded.created_at
|
|
482
|
+
`);
|
|
483
|
+
const listTaskAssignmentsStatement = db.prepare(`
|
|
484
|
+
SELECT * FROM task_assignments WHERE task_id = ? ORDER BY assigned_at ASC, attempt_id ASC
|
|
485
|
+
`);
|
|
486
|
+
const listTaskAttemptsStatement = db.prepare(`
|
|
487
|
+
SELECT * FROM task_attempts WHERE task_id = ? ORDER BY COALESCE(started_at, finished_at, attempt_id) ASC, attempt_id ASC
|
|
488
|
+
`);
|
|
489
|
+
const latestTaskAttemptStatement = db.prepare(`
|
|
490
|
+
SELECT * FROM task_attempts WHERE task_id = ? ORDER BY COALESCE(started_at, finished_at, attempt_id) DESC, attempt_id DESC LIMIT 1
|
|
491
|
+
`);
|
|
492
|
+
const latestTaskResultStatement = db.prepare(`
|
|
493
|
+
SELECT * FROM task_results WHERE task_id = ? ORDER BY COALESCE(created_at, attempt_id) DESC, attempt_id DESC LIMIT 1
|
|
494
|
+
`);
|
|
495
|
+
const upsertApprovalGrantStatement = db.prepare(`
|
|
496
|
+
INSERT INTO approval_grants (
|
|
497
|
+
id, run_id, workspace_id, plan_revision, scope, task_id, group_id,
|
|
498
|
+
approval_classes, status, reason, created_at, granted_at, rejected_at
|
|
499
|
+
)
|
|
500
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
501
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
502
|
+
run_id = excluded.run_id,
|
|
503
|
+
workspace_id = excluded.workspace_id,
|
|
504
|
+
plan_revision = excluded.plan_revision,
|
|
505
|
+
scope = excluded.scope,
|
|
506
|
+
task_id = excluded.task_id,
|
|
507
|
+
group_id = excluded.group_id,
|
|
508
|
+
approval_classes = excluded.approval_classes,
|
|
509
|
+
status = excluded.status,
|
|
510
|
+
reason = COALESCE(excluded.reason, approval_grants.reason),
|
|
511
|
+
granted_at = COALESCE(excluded.granted_at, approval_grants.granted_at),
|
|
512
|
+
rejected_at = COALESCE(excluded.rejected_at, approval_grants.rejected_at)
|
|
513
|
+
`);
|
|
514
|
+
const listApprovalGrantsStatement = db.prepare(`
|
|
515
|
+
SELECT * FROM approval_grants ORDER BY created_at ASC, id ASC
|
|
516
|
+
`);
|
|
517
|
+
const listApprovalGrantsByRunStatement = db.prepare(`
|
|
518
|
+
SELECT * FROM approval_grants WHERE run_id = ? ORDER BY created_at ASC, id ASC
|
|
519
|
+
`);
|
|
201
520
|
|
|
202
521
|
function persistEvent(event) {
|
|
203
522
|
if (NON_PERSISTED_EVENT_TYPES.has(event.type)) return event;
|
|
@@ -240,6 +559,10 @@ export function openRuntimeStore({ stateDir = defaultRuntimeStateDir(), fileName
|
|
|
240
559
|
});
|
|
241
560
|
}
|
|
242
561
|
}
|
|
562
|
+
persistAgentFromEvent(event);
|
|
563
|
+
persistPlanFromEvent(event);
|
|
564
|
+
persistTaskLifecycleFromEvent(event);
|
|
565
|
+
persistApprovalGrantFromEvent(event);
|
|
243
566
|
return event;
|
|
244
567
|
}
|
|
245
568
|
|
|
@@ -341,6 +664,324 @@ export function openRuntimeStore({ stateDir = defaultRuntimeStateDir(), fileName
|
|
|
341
664
|
}
|
|
342
665
|
}
|
|
343
666
|
|
|
667
|
+
function persistAgent(agent) {
|
|
668
|
+
if (!agent?.agentInstanceId || !agent.description) return;
|
|
669
|
+
const description = agent.description;
|
|
670
|
+
const capabilities = Array.isArray(description.capabilities) ? description.capabilities : [];
|
|
671
|
+
const firstSeenAt = agent.firstSeenAt ?? agent.lastSeenAt ?? new Date().toISOString();
|
|
672
|
+
const lastSeenAt = agent.lastSeenAt ?? firstSeenAt;
|
|
673
|
+
db.exec('BEGIN');
|
|
674
|
+
try {
|
|
675
|
+
upsertAgentStatement.run(
|
|
676
|
+
agent.agentInstanceId,
|
|
677
|
+
description.agentType ?? agent.serverName ?? 'unknown',
|
|
678
|
+
description.displayName ?? agent.agentInstanceId,
|
|
679
|
+
description.contractVersion ?? 'legacy',
|
|
680
|
+
agent.health ?? description.health?.status ?? 'unavailable',
|
|
681
|
+
JSON.stringify(description),
|
|
682
|
+
firstSeenAt,
|
|
683
|
+
lastSeenAt,
|
|
684
|
+
);
|
|
685
|
+
deleteAgentCapabilitiesStatement.run(agent.agentInstanceId);
|
|
686
|
+
for (const capability of capabilities) {
|
|
687
|
+
upsertAgentCapabilityStatement.run(
|
|
688
|
+
agent.agentInstanceId,
|
|
689
|
+
capability.id,
|
|
690
|
+
capability.version,
|
|
691
|
+
capability.defaultRequiresApproval === true ? 1 : 0,
|
|
692
|
+
JSON.stringify(capability),
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
db.exec('COMMIT');
|
|
696
|
+
} catch (error) {
|
|
697
|
+
db.exec('ROLLBACK');
|
|
698
|
+
throw error;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function persistAgentFromEvent(event) {
|
|
703
|
+
if (event.type !== 'agent.registered' && event.type !== 'agent.health_changed') return;
|
|
704
|
+
persistAgent(event.payload?.agent);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function persistPlanFromEvent(event) {
|
|
708
|
+
if (!['task_group.created', 'task.created', 'plan.revision_changed'].includes(event.type)) return;
|
|
709
|
+
const runId = event.runId ?? event.payload?.runId ?? null;
|
|
710
|
+
if (!runId) return;
|
|
711
|
+
ensureRun.run(runId, event.workspace ?? event.payload?.workspace ?? null, event.ts, event.ts);
|
|
712
|
+
if (event.type === 'task_group.created') persistTaskGroup(runId, event.payload?.group, event.ts);
|
|
713
|
+
else if (event.type === 'task.created') persistTask(runId, event.payload?.task, event.ts);
|
|
714
|
+
else persistPlanRevision(runId, event.payload ?? {}, event.ts);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function persistTaskGroup(runId, group, ts) {
|
|
718
|
+
if (!group?.id) return;
|
|
719
|
+
upsertTaskGroupStatement.run(
|
|
720
|
+
runId,
|
|
721
|
+
group.id,
|
|
722
|
+
group.localId ?? null,
|
|
723
|
+
group.label ?? group.id,
|
|
724
|
+
group.recommendedConcurrency ?? null,
|
|
725
|
+
group.progressWeight ?? null,
|
|
726
|
+
JSON.stringify(group),
|
|
727
|
+
group.createdAt ?? ts,
|
|
728
|
+
group.updatedAt ?? ts,
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function persistTask(runId, task, ts) {
|
|
733
|
+
if (!task?.id) return;
|
|
734
|
+
db.exec('BEGIN');
|
|
735
|
+
try {
|
|
736
|
+
upsertTaskStatement.run(
|
|
737
|
+
runId,
|
|
738
|
+
task.id,
|
|
739
|
+
task.localId ?? null,
|
|
740
|
+
task.agentInstanceId ?? null,
|
|
741
|
+
task.label ?? task.description ?? task.id,
|
|
742
|
+
task.requiredCapability ?? '',
|
|
743
|
+
task.operation ?? '',
|
|
744
|
+
JSON.stringify(task.arguments ?? {}),
|
|
745
|
+
task.groupId ?? null,
|
|
746
|
+
task.dependsOnGroup ?? null,
|
|
747
|
+
task.barrier === true ? 1 : 0,
|
|
748
|
+
task.parallelizable === true ? 1 : 0,
|
|
749
|
+
task.recommendedConcurrency ?? null,
|
|
750
|
+
JSON.stringify(task.inputRefs ?? []),
|
|
751
|
+
JSON.stringify(task.expectedOutputRefs ?? []),
|
|
752
|
+
JSON.stringify(task.locks ?? []),
|
|
753
|
+
task.requiresApproval === true ? 1 : 0,
|
|
754
|
+
task.approvalClass ?? null,
|
|
755
|
+
task.approvalSummary ?? null,
|
|
756
|
+
task.idempotencyKey ?? null,
|
|
757
|
+
task.progressWeight ?? 1,
|
|
758
|
+
task.priority ?? null,
|
|
759
|
+
task.retryPolicy == null ? null : JSON.stringify(task.retryPolicy),
|
|
760
|
+
JSON.stringify(task),
|
|
761
|
+
task.status ?? 'pending',
|
|
762
|
+
task.createdAt ?? ts,
|
|
763
|
+
task.updatedAt ?? ts,
|
|
764
|
+
);
|
|
765
|
+
deleteTaskDependenciesStatement.run(runId, task.id);
|
|
766
|
+
for (const dependencyId of task.dependsOn ?? []) {
|
|
767
|
+
insertTaskDependencyStatement.run(runId, task.id, dependencyId, ts);
|
|
768
|
+
}
|
|
769
|
+
db.exec('COMMIT');
|
|
770
|
+
} catch (error) {
|
|
771
|
+
db.exec('ROLLBACK');
|
|
772
|
+
throw error;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function persistPlanRevision(runId, payload, ts) {
|
|
777
|
+
insertPlanRevisionStatement.run(
|
|
778
|
+
runId,
|
|
779
|
+
Number(payload.planRevision ?? payload.revision ?? 0),
|
|
780
|
+
payload.previousRevision ?? null,
|
|
781
|
+
payload.reason ?? null,
|
|
782
|
+
JSON.stringify(payload.taskIds ?? []),
|
|
783
|
+
ts,
|
|
784
|
+
);
|
|
785
|
+
if (Array.isArray(payload.tasks)) {
|
|
786
|
+
for (const task of payload.tasks) persistTask(runId, task, ts);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function persistTaskLifecycleFromEvent(event) {
|
|
791
|
+
const taskId = lifecycleTaskId(event);
|
|
792
|
+
const runId = event.runId ?? event.payload?.runId ?? event.payload?.result?.runId ?? null;
|
|
793
|
+
if (!taskId || !runId) return;
|
|
794
|
+
|
|
795
|
+
if (event.type === 'plan_step_updated') {
|
|
796
|
+
const task = event.payload?.task;
|
|
797
|
+
const status = event.payload?.status ?? task?.status ?? null;
|
|
798
|
+
if (status) {
|
|
799
|
+
const existing = listTasksByRunStatement.all(runId).map(rowToTask).find((item) => item.id === taskId);
|
|
800
|
+
if (existing) {
|
|
801
|
+
updateTaskStatusStatement.run(
|
|
802
|
+
String(status),
|
|
803
|
+
JSON.stringify({ ...existing.declaration, ...task, status: String(status) }),
|
|
804
|
+
event.ts,
|
|
805
|
+
runId,
|
|
806
|
+
taskId,
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
if (![
|
|
814
|
+
'task.assigned',
|
|
815
|
+
'task.started',
|
|
816
|
+
'task.retry_scheduled',
|
|
817
|
+
'task.result_returned',
|
|
818
|
+
'task.completed',
|
|
819
|
+
'task.failed',
|
|
820
|
+
].includes(event.type)) return;
|
|
821
|
+
const attemptId = lifecycleAttemptId(event, taskId);
|
|
822
|
+
|
|
823
|
+
if (event.type === 'task.assigned') {
|
|
824
|
+
const assignment = event.payload?.assignment ?? {};
|
|
825
|
+
const agentInstanceId = event.payload?.agentInstanceId ?? assignment.agentInstanceId;
|
|
826
|
+
if (agentInstanceId) {
|
|
827
|
+
upsertTaskAssignmentStatement.run(
|
|
828
|
+
taskId,
|
|
829
|
+
attemptId,
|
|
830
|
+
String(agentInstanceId),
|
|
831
|
+
event.payload?.agentId ?? assignment.agentId ?? null,
|
|
832
|
+
event.payload?.poolId ?? assignment.poolId ?? null,
|
|
833
|
+
event.payload?.assignedAt ?? event.ts,
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
persistTaskAttempt({
|
|
837
|
+
attemptId,
|
|
838
|
+
taskId,
|
|
839
|
+
runId,
|
|
840
|
+
status: event.payload?.status ?? 'assigned',
|
|
841
|
+
startedAt: null,
|
|
842
|
+
finishedAt: null,
|
|
843
|
+
jobId: null,
|
|
844
|
+
error: null,
|
|
845
|
+
});
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
if (event.type === 'task.started') {
|
|
850
|
+
persistTaskAttempt({
|
|
851
|
+
attemptId,
|
|
852
|
+
taskId,
|
|
853
|
+
runId,
|
|
854
|
+
status: event.payload?.status ?? 'running',
|
|
855
|
+
startedAt: event.payload?.startedAt ?? event.ts,
|
|
856
|
+
finishedAt: null,
|
|
857
|
+
jobId: event.payload?.jobId ?? event.payload?.result?.jobId ?? null,
|
|
858
|
+
error: null,
|
|
859
|
+
});
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
if (event.type === 'task.retry_scheduled') {
|
|
864
|
+
persistTaskAttempt({
|
|
865
|
+
attemptId,
|
|
866
|
+
taskId,
|
|
867
|
+
runId,
|
|
868
|
+
status: event.payload?.status ?? 'retry_scheduled',
|
|
869
|
+
startedAt: null,
|
|
870
|
+
finishedAt: null,
|
|
871
|
+
jobId: event.payload?.jobId ?? null,
|
|
872
|
+
error: stringifyError(event.payload?.error ?? event.payload?.reason ?? null),
|
|
873
|
+
});
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
const result = event.payload?.result ?? {};
|
|
878
|
+
const status = resultStatusForEvent(event, result);
|
|
879
|
+
persistTaskAttempt({
|
|
880
|
+
attemptId,
|
|
881
|
+
taskId,
|
|
882
|
+
runId,
|
|
883
|
+
status: attemptStatusForResult(status, event.type),
|
|
884
|
+
startedAt: null,
|
|
885
|
+
finishedAt: event.ts,
|
|
886
|
+
jobId: result.jobId ?? event.payload?.jobId ?? null,
|
|
887
|
+
error: stringifyError(result.error ?? event.payload?.error ?? null),
|
|
888
|
+
});
|
|
889
|
+
upsertTaskResultStatement.run(
|
|
890
|
+
attemptId,
|
|
891
|
+
taskId,
|
|
892
|
+
status,
|
|
893
|
+
result.summary ?? event.payload?.summary ?? null,
|
|
894
|
+
JSON.stringify(result.outputRefs ?? result.result?.outputRefs ?? []),
|
|
895
|
+
JSON.stringify(result.metrics ?? result.result?.metrics ?? {}),
|
|
896
|
+
stringifyError(result.error ?? event.payload?.error ?? null),
|
|
897
|
+
event.payload?.createdAt ?? event.ts,
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
function persistTaskAttempt({ attemptId, taskId, runId, status, jobId, startedAt, finishedAt, error }) {
|
|
902
|
+
upsertTaskAttemptStatement.run(
|
|
903
|
+
attemptId,
|
|
904
|
+
taskId,
|
|
905
|
+
runId,
|
|
906
|
+
status,
|
|
907
|
+
jobId,
|
|
908
|
+
startedAt,
|
|
909
|
+
finishedAt,
|
|
910
|
+
error,
|
|
911
|
+
);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
function persistApprovalGrantFromEvent(event) {
|
|
915
|
+
if (!['approval.requested', 'approval.granted', 'approval.rejected'].includes(event.type)) return;
|
|
916
|
+
const payload = event.payload ?? {};
|
|
917
|
+
const runId = event.runId ?? payload.runId ?? null;
|
|
918
|
+
if (!runId) return;
|
|
919
|
+
const id = payload.id ?? payload.approvalId ?? payload.taskId ?? payload.itemId ?? payload.groupId ?? event.id;
|
|
920
|
+
const status = event.type === 'approval.requested'
|
|
921
|
+
? 'pending_approval'
|
|
922
|
+
: event.type === 'approval.rejected'
|
|
923
|
+
? 'rejected'
|
|
924
|
+
: 'approved';
|
|
925
|
+
upsertApprovalGrantStatement.run(
|
|
926
|
+
String(id),
|
|
927
|
+
String(runId),
|
|
928
|
+
event.workspace ?? payload.workspaceId ?? payload.workspace ?? null,
|
|
929
|
+
payload.planRevision == null ? null : Number(payload.planRevision),
|
|
930
|
+
String(payload.scope ?? 'run'),
|
|
931
|
+
payload.taskId ?? payload.itemId ?? event.taskId ?? null,
|
|
932
|
+
payload.groupId ?? null,
|
|
933
|
+
JSON.stringify(normalizeApprovalClasses(payload.approvalClasses ?? payload.approvalClass)),
|
|
934
|
+
status,
|
|
935
|
+
payload.reason ?? null,
|
|
936
|
+
payload.createdAt ?? event.ts,
|
|
937
|
+
status === 'approved' ? (payload.grantedAt ?? event.ts) : null,
|
|
938
|
+
status === 'rejected' ? (payload.rejectedAt ?? event.ts) : null,
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
function lifecycleTaskId(event) {
|
|
943
|
+
const taskId = event.taskId ?? event.payload?.taskId ?? event.payload?.result?.taskId;
|
|
944
|
+
return taskId == null ? null : String(taskId);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
function lifecycleAttemptId(event, taskId) {
|
|
948
|
+
const attemptId = event.payload?.attemptId
|
|
949
|
+
?? event.payload?.assignment?.attemptId
|
|
950
|
+
?? event.payload?.result?.attemptId;
|
|
951
|
+
if (attemptId != null) return String(attemptId);
|
|
952
|
+
return latestTaskAttemptStatement.get(taskId)?.attempt_id ?? `${taskId}:attempt-1`;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
function resultStatusForEvent(event, result) {
|
|
956
|
+
if (result?.status) return String(result.status);
|
|
957
|
+
if (event.type === 'task.completed') return 'succeeded';
|
|
958
|
+
if (event.type === 'task.failed') return 'failed';
|
|
959
|
+
return 'unknown';
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function attemptStatusForResult(status, eventType) {
|
|
963
|
+
const normalized = String(status ?? '').toLowerCase();
|
|
964
|
+
if (['succeeded', 'success', 'done', 'complete', 'completed'].includes(normalized)) return 'done';
|
|
965
|
+
if (['cancelled', 'canceled'].includes(normalized)) return 'cancelled';
|
|
966
|
+
if (eventType === 'task.failed') return 'failed';
|
|
967
|
+
return normalized || 'finished';
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
function listAgents() {
|
|
971
|
+
return listAgentsStatement.all().map((row) => {
|
|
972
|
+
const description = row.description ? JSON.parse(row.description) : null;
|
|
973
|
+
return {
|
|
974
|
+
agentInstanceId: row.instance_id,
|
|
975
|
+
description,
|
|
976
|
+
health: row.health,
|
|
977
|
+
firstSeenAt: row.first_seen_at ?? null,
|
|
978
|
+
lastSeenAt: row.last_seen_at ?? null,
|
|
979
|
+
legacy: description?.contractVersion === 'legacy',
|
|
980
|
+
orchestrable: description?.contractVersion !== 'legacy',
|
|
981
|
+
};
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
|
|
344
985
|
function listQueue({ workspace = null } = {}) {
|
|
345
986
|
const rows = workspace
|
|
346
987
|
? listQueueByWorkspaceStatement.all(workspace)
|
|
@@ -364,6 +1005,54 @@ export function openRuntimeStore({ stateDir = defaultRuntimeStateDir(), fileName
|
|
|
364
1005
|
}));
|
|
365
1006
|
}
|
|
366
1007
|
|
|
1008
|
+
function listTasks({ runId = null } = {}) {
|
|
1009
|
+
const rows = runId ? listTasksByRunStatement.all(runId) : listTasksStatement.all();
|
|
1010
|
+
return rows.map(rowToTask);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
function listTaskAssignments({ taskId }) {
|
|
1014
|
+
if (!taskId) return [];
|
|
1015
|
+
return listTaskAssignmentsStatement.all(String(taskId)).map(rowToTaskAssignment);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
function listTaskAttempts({ taskId }) {
|
|
1019
|
+
if (!taskId) return [];
|
|
1020
|
+
return listTaskAttemptsStatement.all(String(taskId)).map(rowToTaskAttempt);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
function getTaskResult({ taskId }) {
|
|
1024
|
+
if (!taskId) return null;
|
|
1025
|
+
const row = latestTaskResultStatement.get(String(taskId));
|
|
1026
|
+
return row ? rowToTaskResult(row) : null;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
function listApprovalGrants({ runId = null } = {}) {
|
|
1030
|
+
const rows = runId ? listApprovalGrantsByRunStatement.all(String(runId)) : listApprovalGrantsStatement.all();
|
|
1031
|
+
return rows.map(rowToApprovalGrant);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
function listTaskDependencies({ runId = null } = {}) {
|
|
1035
|
+
const rows = runId ? listTaskDependenciesByRunStatement.all(runId) : listTaskDependenciesStatement.all();
|
|
1036
|
+
return rows.map((row) => ({
|
|
1037
|
+
runId: row.run_id,
|
|
1038
|
+
taskId: row.task_id,
|
|
1039
|
+
dependsOnTaskId: row.depends_on_task_id,
|
|
1040
|
+
createdAt: row.created_at ?? null,
|
|
1041
|
+
}));
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
function listPlanRevisions({ runId = null } = {}) {
|
|
1045
|
+
const rows = runId ? listPlanRevisionsByRunStatement.all(runId) : listPlanRevisionsStatement.all();
|
|
1046
|
+
return rows.map((row) => ({
|
|
1047
|
+
runId: row.run_id,
|
|
1048
|
+
revision: row.revision,
|
|
1049
|
+
previousRevision: row.previous_revision ?? null,
|
|
1050
|
+
reason: row.reason ?? null,
|
|
1051
|
+
taskIds: row.task_ids ? JSON.parse(row.task_ids) : [],
|
|
1052
|
+
createdAt: row.created_at ?? null,
|
|
1053
|
+
}));
|
|
1054
|
+
}
|
|
1055
|
+
|
|
367
1056
|
function replayEvents(session, { workspace = null } = {}) {
|
|
368
1057
|
const events = listEvents({ workspace });
|
|
369
1058
|
for (const event of events) {
|
|
@@ -388,10 +1077,17 @@ export function openRuntimeStore({ stateDir = defaultRuntimeStateDir(), fileName
|
|
|
388
1077
|
const controlQueue = Array.isArray(projection.controlQueue)
|
|
389
1078
|
? projection.controlQueue.filter((item) => !workspace || item.workspace === workspace || !item.workspace).map((item) => ({ ...item }))
|
|
390
1079
|
: [];
|
|
1080
|
+
const agents = mergeAgents(
|
|
1081
|
+
listAgents(),
|
|
1082
|
+
projection.agents ?? [],
|
|
1083
|
+
session?.agentRegistry?.snapshot?.() ?? [],
|
|
1084
|
+
);
|
|
391
1085
|
const baseState = {
|
|
392
1086
|
...projection,
|
|
393
1087
|
queue,
|
|
394
1088
|
controlQueue,
|
|
1089
|
+
agents,
|
|
1090
|
+
capabilityRegistry: createCapabilityRegistry({ agents }).snapshot(),
|
|
395
1091
|
eventsCursor: session?.agentEvents?.at(-1)?.sequence ?? events?.at(-1)?.sequence ?? lastEventSequence,
|
|
396
1092
|
};
|
|
397
1093
|
const runs = listRuns({ workspace });
|
|
@@ -427,6 +1123,15 @@ export function openRuntimeStore({ stateDir = defaultRuntimeStateDir(), fileName
|
|
|
427
1123
|
interruptRuns,
|
|
428
1124
|
saveQueue,
|
|
429
1125
|
listQueue,
|
|
1126
|
+
listTasks,
|
|
1127
|
+
listTaskAssignments,
|
|
1128
|
+
listTaskAttempts,
|
|
1129
|
+
getTaskResult,
|
|
1130
|
+
listApprovalGrants,
|
|
1131
|
+
listTaskDependencies,
|
|
1132
|
+
listPlanRevisions,
|
|
1133
|
+
persistAgent,
|
|
1134
|
+
listAgents,
|
|
430
1135
|
replayEvents,
|
|
431
1136
|
hydrateSession,
|
|
432
1137
|
getProjection,
|
|
@@ -436,7 +1141,7 @@ export function openRuntimeStore({ stateDir = defaultRuntimeStateDir(), fileName
|
|
|
436
1141
|
}
|
|
437
1142
|
|
|
438
1143
|
function defaultRuntimeMetaPath(stateDir) {
|
|
439
|
-
return join(
|
|
1144
|
+
return join(stateDir, 'meta.json');
|
|
440
1145
|
}
|
|
441
1146
|
|
|
442
1147
|
function ensureKnownStoreVersion(db, dbPath) {
|
|
@@ -448,6 +1153,7 @@ function ensureKnownStoreVersion(db, dbPath) {
|
|
|
448
1153
|
}
|
|
449
1154
|
|
|
450
1155
|
function ensureRuntimeMeta(metaPath) {
|
|
1156
|
+
migrateLegacyRuntimeMeta(metaPath);
|
|
451
1157
|
mkdirSync(dirname(metaPath), { recursive: true });
|
|
452
1158
|
if (!existsSync(metaPath)) {
|
|
453
1159
|
writeFileSync(metaPath, `${JSON.stringify({ schemaVersion: RUNTIME_STORE_SCHEMA_VERSION }, null, 2)}\n`, 'utf8');
|
|
@@ -468,6 +1174,14 @@ function ensureRuntimeMeta(metaPath) {
|
|
|
468
1174
|
}
|
|
469
1175
|
}
|
|
470
1176
|
|
|
1177
|
+
function migrateLegacyRuntimeMeta(metaPath) {
|
|
1178
|
+
if (existsSync(metaPath)) return;
|
|
1179
|
+
const legacyPath = join(dirname(metaPath), '..', '.wiki', 'meta.json');
|
|
1180
|
+
if (!existsSync(legacyPath)) return;
|
|
1181
|
+
mkdirSync(dirname(metaPath), { recursive: true });
|
|
1182
|
+
renameSync(legacyPath, metaPath);
|
|
1183
|
+
}
|
|
1184
|
+
|
|
471
1185
|
function purgeOldTerminalRuns(db, now = new Date()) {
|
|
472
1186
|
const cutoff = new Date(now.getTime() - RUNTIME_RETENTION_DAYS * 24 * 60 * 60 * 1000).toISOString();
|
|
473
1187
|
const placeholders = TERMINAL_RUN_STATUSES.map(() => '?').join(', ');
|
|
@@ -508,6 +1222,112 @@ function rowToEvent(row) {
|
|
|
508
1222
|
};
|
|
509
1223
|
}
|
|
510
1224
|
|
|
1225
|
+
function rowToTask(row) {
|
|
1226
|
+
return {
|
|
1227
|
+
runId: row.run_id,
|
|
1228
|
+
id: row.id,
|
|
1229
|
+
localId: row.local_id ?? null,
|
|
1230
|
+
agentInstanceId: row.agent_instance_id ?? null,
|
|
1231
|
+
label: row.label,
|
|
1232
|
+
requiredCapability: row.required_capability,
|
|
1233
|
+
operation: row.operation,
|
|
1234
|
+
arguments: row.arguments ? JSON.parse(row.arguments) : {},
|
|
1235
|
+
groupId: row.group_id ?? null,
|
|
1236
|
+
dependsOnGroup: row.depends_on_group ?? null,
|
|
1237
|
+
barrier: Boolean(row.barrier),
|
|
1238
|
+
parallelizable: Boolean(row.parallelizable),
|
|
1239
|
+
recommendedConcurrency: row.recommended_concurrency ?? null,
|
|
1240
|
+
inputRefs: row.input_refs ? JSON.parse(row.input_refs) : [],
|
|
1241
|
+
expectedOutputRefs: row.expected_output_refs ? JSON.parse(row.expected_output_refs) : [],
|
|
1242
|
+
locks: row.locks ? JSON.parse(row.locks) : [],
|
|
1243
|
+
requiresApproval: Boolean(row.requires_approval),
|
|
1244
|
+
approvalClass: row.approval_class ?? null,
|
|
1245
|
+
approvalSummary: row.approval_summary ?? null,
|
|
1246
|
+
idempotencyKey: row.idempotency_key ?? null,
|
|
1247
|
+
progressWeight: row.progress_weight,
|
|
1248
|
+
priority: row.priority ?? null,
|
|
1249
|
+
retryPolicy: row.retry_policy ? JSON.parse(row.retry_policy) : null,
|
|
1250
|
+
declaration: row.declaration ? JSON.parse(row.declaration) : null,
|
|
1251
|
+
status: row.status,
|
|
1252
|
+
createdAt: row.created_at ?? null,
|
|
1253
|
+
updatedAt: row.updated_at ?? null,
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
function rowToTaskAssignment(row) {
|
|
1258
|
+
return {
|
|
1259
|
+
taskId: row.task_id,
|
|
1260
|
+
attemptId: row.attempt_id,
|
|
1261
|
+
agentInstanceId: row.agent_instance_id,
|
|
1262
|
+
agentId: row.agent_id ?? null,
|
|
1263
|
+
poolId: row.pool_id ?? null,
|
|
1264
|
+
assignedAt: row.assigned_at,
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
function rowToTaskAttempt(row) {
|
|
1269
|
+
return {
|
|
1270
|
+
attemptId: row.attempt_id,
|
|
1271
|
+
taskId: row.task_id,
|
|
1272
|
+
runId: row.run_id,
|
|
1273
|
+
status: row.status,
|
|
1274
|
+
jobId: row.job_id ?? null,
|
|
1275
|
+
startedAt: row.started_at ?? null,
|
|
1276
|
+
finishedAt: row.finished_at ?? null,
|
|
1277
|
+
error: row.error ? parseJsonMaybe(row.error) : null,
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
function rowToTaskResult(row) {
|
|
1282
|
+
return {
|
|
1283
|
+
attemptId: row.attempt_id,
|
|
1284
|
+
taskId: row.task_id,
|
|
1285
|
+
status: row.status,
|
|
1286
|
+
summary: row.summary ?? null,
|
|
1287
|
+
outputRefs: row.output_refs ? JSON.parse(row.output_refs) : [],
|
|
1288
|
+
metrics: row.metrics ? JSON.parse(row.metrics) : {},
|
|
1289
|
+
error: row.error ? parseJsonMaybe(row.error) : null,
|
|
1290
|
+
createdAt: row.created_at ?? null,
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
function rowToApprovalGrant(row) {
|
|
1295
|
+
return {
|
|
1296
|
+
id: row.id,
|
|
1297
|
+
runId: row.run_id,
|
|
1298
|
+
workspaceId: row.workspace_id ?? null,
|
|
1299
|
+
planRevision: row.plan_revision ?? null,
|
|
1300
|
+
scope: row.scope,
|
|
1301
|
+
taskId: row.task_id ?? null,
|
|
1302
|
+
groupId: row.group_id ?? null,
|
|
1303
|
+
approvalClasses: row.approval_classes ? JSON.parse(row.approval_classes) : [],
|
|
1304
|
+
status: row.status,
|
|
1305
|
+
reason: row.reason ?? null,
|
|
1306
|
+
createdAt: row.created_at,
|
|
1307
|
+
grantedAt: row.granted_at ?? null,
|
|
1308
|
+
rejectedAt: row.rejected_at ?? null,
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
function normalizeApprovalClasses(value) {
|
|
1313
|
+
if (value == null) return [];
|
|
1314
|
+
const values = Array.isArray(value) ? value : [value];
|
|
1315
|
+
return [...new Set(values.map((item) => String(item).trim()).filter(Boolean))];
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
function parseJsonMaybe(value) {
|
|
1319
|
+
try {
|
|
1320
|
+
return JSON.parse(value);
|
|
1321
|
+
} catch {
|
|
1322
|
+
return value;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
function stringifyError(value) {
|
|
1327
|
+
if (value == null) return null;
|
|
1328
|
+
return typeof value === 'string' ? value : JSON.stringify(value);
|
|
1329
|
+
}
|
|
1330
|
+
|
|
511
1331
|
function eventToAuditEntry(event) {
|
|
512
1332
|
const payload = event.payload ?? {};
|
|
513
1333
|
const activity = payload.activity ?? {};
|
|
@@ -538,6 +1358,21 @@ function auditSummary(event) {
|
|
|
538
1358
|
return event.type;
|
|
539
1359
|
}
|
|
540
1360
|
|
|
1361
|
+
function mergeAgents(...groups) {
|
|
1362
|
+
const byId = new Map();
|
|
1363
|
+
for (const agents of groups) {
|
|
1364
|
+
for (const agent of agents ?? []) {
|
|
1365
|
+
if (!agent?.agentInstanceId) continue;
|
|
1366
|
+
byId.set(agent.agentInstanceId, {
|
|
1367
|
+
...(byId.get(agent.agentInstanceId) ?? {}),
|
|
1368
|
+
...agent,
|
|
1369
|
+
description: agent.description ?? byId.get(agent.agentInstanceId)?.description ?? null,
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
return [...byId.values()].sort((a, b) => a.agentInstanceId.localeCompare(b.agentInstanceId));
|
|
1374
|
+
}
|
|
1375
|
+
|
|
541
1376
|
function ensureColumn(db, table, column, definition) {
|
|
542
1377
|
const existing = db.prepare(`PRAGMA table_info(${table})`).all();
|
|
543
1378
|
if (existing.some((row) => row.name === column)) return;
|