@dotdrelle/wiki-manager 0.6.47 → 0.8.2
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/.env.example +20 -0
- package/README.md +35 -1
- package/bin/wiki-manager.js +2 -2
- package/docker-compose.yml +2 -0
- package/mcp.endpoints.example.json +13 -0
- package/package.json +2 -2
- package/src/agent/graph.js +101 -15
- package/src/agent/graph.test.js +145 -0
- package/src/cli/wiki-manager.js +459 -131
- package/src/commands/slash.js +1 -1
- package/src/core/activity.js +14 -0
- package/src/core/agentEvents.js +148 -6
- package/src/core/agentEvents.test.js +145 -4
- package/src/core/agentLoop.js +167 -0
- package/src/core/agentLoop.test.js +85 -0
- package/src/core/cacert.js +4 -3
- package/src/core/dockerCompose.test.js +14 -0
- package/src/core/documentIntake.test.js +7 -7
- package/src/core/env.js +6 -0
- package/src/core/jobQueue.js +47 -16
- package/src/core/mcp.js +120 -10
- package/src/core/mcp.test.js +121 -1
- package/src/core/plan.js +33 -0
- package/src/core/queueStore.js +27 -0
- package/src/core/queueStore.test.js +32 -0
- package/src/core/wikiWorkspace.test.js +24 -0
- package/src/core/workspaces.js +8 -1
- package/src/runtime/approvals.js +113 -0
- package/src/runtime/auth.js +35 -0
- package/src/runtime/auth.test.js +29 -0
- package/src/runtime/client.js +154 -0
- package/src/runtime/lifecycle.js +84 -0
- package/src/runtime/queueStore.js +6 -0
- package/src/runtime/runner.js +413 -0
- package/src/runtime/runner.test.js +270 -0
- package/src/runtime/server.js +216 -0
- package/src/runtime/server.test.js +308 -0
- package/src/runtime/store.js +431 -0
- package/src/runtime/store.test.js +437 -0
- package/src/runtime/supervisor.js +104 -0
- package/src/runtime/supervisor.test.js +240 -0
- package/src/shell/RightPane.tsx +1 -1
- package/src/shell/repl.js +148 -18
- package/src/shell/repl.test.js +38 -0
- package/src/shell/tui.tsx +4 -1
- package/src/shell/useAgent.ts +20 -2
- package/src/shell/useSession.ts +153 -13
- package/wiki-workspace +221 -22
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { mkdirSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
4
|
+
import { applyAgentProjectionToSession, dispatchAgentEvent, reduceAgentEvents } from '../core/agentEvents.js';
|
|
5
|
+
import { defaultRuntimeStateDir } from '../core/env.js';
|
|
6
|
+
import { projectQueue } from '../core/jobQueue.js';
|
|
7
|
+
|
|
8
|
+
export { defaultRuntimeStateDir };
|
|
9
|
+
|
|
10
|
+
const NON_PERSISTED_EVENT_TYPES = new Set(['runtime_log']);
|
|
11
|
+
|
|
12
|
+
const RUN_STATUS_BY_EVENT = {
|
|
13
|
+
run_done: 'done',
|
|
14
|
+
run_error: 'error',
|
|
15
|
+
run_cancelled: 'cancelled',
|
|
16
|
+
run_pending_approval: 'pending_approval',
|
|
17
|
+
run_approved: 'running',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const RECOVERABLE_RUN_STATUSES = ['running', 'waiting', 'pending_approval'];
|
|
21
|
+
export const RECOVERABLE_QUEUE_STATUSES = ['waiting', 'queued', 'starting', 'running', 'blocked', 'pending_approval'];
|
|
22
|
+
|
|
23
|
+
export function openRuntimeStore({ stateDir = defaultRuntimeStateDir(), fileName = 'runtime.db' } = {}) {
|
|
24
|
+
const resolvedStateDir = resolve(stateDir);
|
|
25
|
+
mkdirSync(resolvedStateDir, { recursive: true });
|
|
26
|
+
const dbPath = join(resolvedStateDir, fileName);
|
|
27
|
+
const db = new DatabaseSync(dbPath);
|
|
28
|
+
db.exec('PRAGMA journal_mode = WAL');
|
|
29
|
+
db.exec('PRAGMA foreign_keys = ON');
|
|
30
|
+
db.exec(`
|
|
31
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
32
|
+
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
33
|
+
id TEXT NOT NULL UNIQUE,
|
|
34
|
+
ts TEXT NOT NULL,
|
|
35
|
+
type TEXT NOT NULL,
|
|
36
|
+
run_id TEXT,
|
|
37
|
+
turn_id TEXT,
|
|
38
|
+
workspace TEXT,
|
|
39
|
+
origin TEXT,
|
|
40
|
+
payload TEXT NOT NULL
|
|
41
|
+
);
|
|
42
|
+
CREATE INDEX IF NOT EXISTS idx_events_ts ON events(ts);
|
|
43
|
+
CREATE INDEX IF NOT EXISTS idx_events_run_id ON events(run_id);
|
|
44
|
+
CREATE TABLE IF NOT EXISTS runs (
|
|
45
|
+
id TEXT PRIMARY KEY,
|
|
46
|
+
workspace TEXT,
|
|
47
|
+
status TEXT NOT NULL,
|
|
48
|
+
input TEXT,
|
|
49
|
+
created_at TEXT NOT NULL,
|
|
50
|
+
updated_at TEXT NOT NULL
|
|
51
|
+
);
|
|
52
|
+
CREATE TABLE IF NOT EXISTS queue_items (
|
|
53
|
+
id TEXT PRIMARY KEY,
|
|
54
|
+
workspace TEXT,
|
|
55
|
+
server TEXT NOT NULL,
|
|
56
|
+
tool TEXT NOT NULL,
|
|
57
|
+
args TEXT NOT NULL,
|
|
58
|
+
lock_key TEXT,
|
|
59
|
+
status TEXT NOT NULL,
|
|
60
|
+
reason TEXT,
|
|
61
|
+
job_id TEXT,
|
|
62
|
+
activity_key TEXT,
|
|
63
|
+
error TEXT,
|
|
64
|
+
created_at TEXT,
|
|
65
|
+
started_at TEXT,
|
|
66
|
+
finished_at TEXT,
|
|
67
|
+
updated_at TEXT NOT NULL
|
|
68
|
+
);
|
|
69
|
+
`);
|
|
70
|
+
ensureColumn(db, 'events', 'sequence', 'INTEGER');
|
|
71
|
+
ensureColumn(db, 'events', 'workspace', 'TEXT');
|
|
72
|
+
ensureColumn(db, 'runs', 'workspace', 'TEXT');
|
|
73
|
+
backfillEventSequence(db);
|
|
74
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_events_workspace ON events(workspace)');
|
|
75
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_events_sequence ON events(sequence)');
|
|
76
|
+
|
|
77
|
+
let lastEventId = null;
|
|
78
|
+
let lastEventSequence = null;
|
|
79
|
+
|
|
80
|
+
const insertEvent = db.prepare(`
|
|
81
|
+
INSERT OR IGNORE INTO events (sequence, id, ts, type, run_id, turn_id, workspace, origin, payload)
|
|
82
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
83
|
+
`);
|
|
84
|
+
const nextEventSequenceStatement = db.prepare('SELECT COALESCE(MAX(sequence), 0) + 1 AS next_sequence FROM events');
|
|
85
|
+
const listEventsStatement = db.prepare(`
|
|
86
|
+
SELECT sequence, id, ts, type, run_id, turn_id, workspace, origin, payload
|
|
87
|
+
FROM events
|
|
88
|
+
ORDER BY sequence ASC
|
|
89
|
+
`);
|
|
90
|
+
const listEventsByWorkspaceStatement = db.prepare(`
|
|
91
|
+
SELECT sequence, id, ts, type, run_id, turn_id, workspace, origin, payload
|
|
92
|
+
FROM events
|
|
93
|
+
WHERE workspace = ?
|
|
94
|
+
ORDER BY sequence ASC
|
|
95
|
+
`);
|
|
96
|
+
const upsertRun = db.prepare(`
|
|
97
|
+
INSERT INTO runs (id, workspace, status, input, created_at, updated_at)
|
|
98
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
99
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
100
|
+
workspace = COALESCE(excluded.workspace, runs.workspace),
|
|
101
|
+
status = excluded.status,
|
|
102
|
+
input = COALESCE(excluded.input, runs.input),
|
|
103
|
+
updated_at = excluded.updated_at
|
|
104
|
+
`);
|
|
105
|
+
const listRunsStatement = db.prepare(`
|
|
106
|
+
SELECT id, workspace, status, input, created_at, updated_at
|
|
107
|
+
FROM runs
|
|
108
|
+
ORDER BY created_at DESC
|
|
109
|
+
`);
|
|
110
|
+
const listRunsByWorkspaceStatement = db.prepare(`
|
|
111
|
+
SELECT id, workspace, status, input, created_at, updated_at
|
|
112
|
+
FROM runs
|
|
113
|
+
WHERE workspace = ?
|
|
114
|
+
ORDER BY created_at DESC
|
|
115
|
+
`);
|
|
116
|
+
const listRecoverableRunsStatement = db.prepare(`
|
|
117
|
+
SELECT id, workspace, status, input, created_at, updated_at
|
|
118
|
+
FROM runs
|
|
119
|
+
WHERE status IN (${RECOVERABLE_RUN_STATUSES.map(() => '?').join(', ')})
|
|
120
|
+
ORDER BY created_at ASC
|
|
121
|
+
`);
|
|
122
|
+
const listRecoverableRunsByWorkspaceStatement = db.prepare(`
|
|
123
|
+
SELECT id, workspace, status, input, created_at, updated_at
|
|
124
|
+
FROM runs
|
|
125
|
+
WHERE workspace = ? AND status IN (${RECOVERABLE_RUN_STATUSES.map(() => '?').join(', ')})
|
|
126
|
+
ORDER BY created_at ASC
|
|
127
|
+
`);
|
|
128
|
+
const interruptRunsStatement = db.prepare(`
|
|
129
|
+
UPDATE runs
|
|
130
|
+
SET status = 'interrupted', updated_at = ?
|
|
131
|
+
WHERE workspace = ? AND status IN (${RECOVERABLE_RUN_STATUSES.map(() => '?').join(', ')})
|
|
132
|
+
`);
|
|
133
|
+
const upsertQueueItem = db.prepare(`
|
|
134
|
+
INSERT INTO queue_items (
|
|
135
|
+
id, workspace, server, tool, args, lock_key, status, reason, job_id, activity_key,
|
|
136
|
+
error, created_at, started_at, finished_at, updated_at
|
|
137
|
+
)
|
|
138
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
139
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
140
|
+
workspace = excluded.workspace,
|
|
141
|
+
server = excluded.server,
|
|
142
|
+
tool = excluded.tool,
|
|
143
|
+
args = excluded.args,
|
|
144
|
+
lock_key = excluded.lock_key,
|
|
145
|
+
status = excluded.status,
|
|
146
|
+
reason = excluded.reason,
|
|
147
|
+
job_id = excluded.job_id,
|
|
148
|
+
activity_key = excluded.activity_key,
|
|
149
|
+
error = excluded.error,
|
|
150
|
+
created_at = excluded.created_at,
|
|
151
|
+
started_at = excluded.started_at,
|
|
152
|
+
finished_at = excluded.finished_at,
|
|
153
|
+
updated_at = excluded.updated_at
|
|
154
|
+
`);
|
|
155
|
+
const deleteMissingQueueItems = db.prepare(`
|
|
156
|
+
DELETE FROM queue_items
|
|
157
|
+
WHERE id NOT IN (SELECT value FROM json_each(?))
|
|
158
|
+
`);
|
|
159
|
+
const deleteMissingQueueItemsForWorkspace = db.prepare(`
|
|
160
|
+
DELETE FROM queue_items
|
|
161
|
+
WHERE workspace = ? AND id NOT IN (SELECT value FROM json_each(?))
|
|
162
|
+
`);
|
|
163
|
+
const clearQueueItemsForWorkspace = db.prepare('DELETE FROM queue_items WHERE workspace = ?');
|
|
164
|
+
const clearQueueItems = db.prepare('DELETE FROM queue_items');
|
|
165
|
+
const listQueueStatement = db.prepare(`
|
|
166
|
+
SELECT id, workspace, server, tool, args, lock_key, status, reason, job_id, activity_key,
|
|
167
|
+
error, created_at, started_at, finished_at, updated_at
|
|
168
|
+
FROM queue_items
|
|
169
|
+
ORDER BY COALESCE(created_at, updated_at) ASC, id ASC
|
|
170
|
+
`);
|
|
171
|
+
const listQueueByWorkspaceStatement = db.prepare(`
|
|
172
|
+
SELECT id, workspace, server, tool, args, lock_key, status, reason, job_id, activity_key,
|
|
173
|
+
error, created_at, started_at, finished_at, updated_at
|
|
174
|
+
FROM queue_items
|
|
175
|
+
WHERE workspace = ?
|
|
176
|
+
ORDER BY COALESCE(created_at, updated_at) ASC, id ASC
|
|
177
|
+
`);
|
|
178
|
+
const listRecoverableWorkspacesStatement = db.prepare(`
|
|
179
|
+
SELECT DISTINCT workspace FROM runs
|
|
180
|
+
WHERE workspace IS NOT NULL AND status IN (${RECOVERABLE_RUN_STATUSES.map(() => '?').join(', ')})
|
|
181
|
+
UNION
|
|
182
|
+
SELECT DISTINCT workspace FROM queue_items
|
|
183
|
+
WHERE workspace IS NOT NULL AND status IN (${RECOVERABLE_QUEUE_STATUSES.map(() => '?').join(', ')})
|
|
184
|
+
ORDER BY workspace
|
|
185
|
+
`);
|
|
186
|
+
|
|
187
|
+
function persistEvent(event) {
|
|
188
|
+
if (NON_PERSISTED_EVENT_TYPES.has(event.type)) return event;
|
|
189
|
+
const ws = event.workspace ?? event.payload?.workspace ?? null;
|
|
190
|
+
const sequence = nextEventSequenceStatement.get().next_sequence;
|
|
191
|
+
const result = insertEvent.run(
|
|
192
|
+
sequence,
|
|
193
|
+
event.id,
|
|
194
|
+
event.ts,
|
|
195
|
+
event.type,
|
|
196
|
+
event.runId ?? null,
|
|
197
|
+
event.turnId ?? null,
|
|
198
|
+
ws,
|
|
199
|
+
event.origin ?? null,
|
|
200
|
+
JSON.stringify(event.payload ?? {}),
|
|
201
|
+
);
|
|
202
|
+
if (Number(result.changes ?? 0) > 0) {
|
|
203
|
+
event.sequence = sequence;
|
|
204
|
+
lastEventId = event.id;
|
|
205
|
+
lastEventSequence = sequence;
|
|
206
|
+
}
|
|
207
|
+
if (event.type === 'run_started') {
|
|
208
|
+
persistRun({
|
|
209
|
+
id: event.runId ?? event.payload?.runId ?? event.id,
|
|
210
|
+
status: 'running',
|
|
211
|
+
input: event.payload?.input ?? null,
|
|
212
|
+
workspace: ws,
|
|
213
|
+
createdAt: event.ts,
|
|
214
|
+
updatedAt: event.ts,
|
|
215
|
+
});
|
|
216
|
+
} else if (RUN_STATUS_BY_EVENT[event.type]) {
|
|
217
|
+
const runId = event.runId ?? event.payload?.runId ?? null;
|
|
218
|
+
if (runId) {
|
|
219
|
+
persistRun({
|
|
220
|
+
id: runId,
|
|
221
|
+
status: RUN_STATUS_BY_EVENT[event.type],
|
|
222
|
+
workspace: ws,
|
|
223
|
+
updatedAt: event.ts,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return event;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function persistRun({ id, status, input = null, workspace = null, createdAt = null, updatedAt = null }) {
|
|
231
|
+
if (!id) return;
|
|
232
|
+
const now = new Date().toISOString();
|
|
233
|
+
upsertRun.run(id, workspace, status, input, createdAt ?? now, updatedAt ?? now);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function listEvents({ workspace = null } = {}) {
|
|
237
|
+
const rows = workspace
|
|
238
|
+
? listEventsByWorkspaceStatement.all(workspace)
|
|
239
|
+
: listEventsStatement.all();
|
|
240
|
+
return rows.map(rowToEvent);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function listRuns({ workspace = null } = {}) {
|
|
244
|
+
const rows = workspace
|
|
245
|
+
? listRunsByWorkspaceStatement.all(workspace)
|
|
246
|
+
: listRunsStatement.all();
|
|
247
|
+
return rows.map((row) => ({
|
|
248
|
+
id: row.id,
|
|
249
|
+
workspace: row.workspace ?? null,
|
|
250
|
+
status: row.status,
|
|
251
|
+
input: row.input,
|
|
252
|
+
createdAt: row.created_at,
|
|
253
|
+
updatedAt: row.updated_at,
|
|
254
|
+
}));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function listRecoverableRuns({ workspace = null } = {}) {
|
|
258
|
+
const rows = workspace
|
|
259
|
+
? listRecoverableRunsByWorkspaceStatement.all(workspace, ...RECOVERABLE_RUN_STATUSES)
|
|
260
|
+
: listRecoverableRunsStatement.all(...RECOVERABLE_RUN_STATUSES);
|
|
261
|
+
return rows.map((row) => ({
|
|
262
|
+
id: row.id,
|
|
263
|
+
workspace: row.workspace ?? null,
|
|
264
|
+
status: row.status,
|
|
265
|
+
input: row.input,
|
|
266
|
+
createdAt: row.created_at,
|
|
267
|
+
updatedAt: row.updated_at,
|
|
268
|
+
}));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function listRecoverableWorkspaces() {
|
|
272
|
+
return listRecoverableWorkspacesStatement
|
|
273
|
+
.all(...RECOVERABLE_RUN_STATUSES, ...RECOVERABLE_QUEUE_STATUSES)
|
|
274
|
+
.map((row) => row.workspace);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function interruptRuns({ workspace, reason = 'Runtime restart recovery failed.' } = {}) {
|
|
278
|
+
if (!workspace) return 0;
|
|
279
|
+
const now = new Date().toISOString();
|
|
280
|
+
const result = interruptRunsStatement.run(now, workspace, ...RECOVERABLE_RUN_STATUSES);
|
|
281
|
+
return Number(result.changes ?? 0);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function saveQueue(queue = [], { workspace = null } = {}) {
|
|
285
|
+
const items = Array.isArray(queue) ? queue : [];
|
|
286
|
+
const now = new Date().toISOString();
|
|
287
|
+
db.exec('BEGIN');
|
|
288
|
+
try {
|
|
289
|
+
if (items.length === 0) {
|
|
290
|
+
if (workspace) clearQueueItemsForWorkspace.run(workspace);
|
|
291
|
+
else clearQueueItems.run();
|
|
292
|
+
} else {
|
|
293
|
+
if (workspace) deleteMissingQueueItemsForWorkspace.run(workspace, JSON.stringify(items.map((item) => item.id)));
|
|
294
|
+
else deleteMissingQueueItems.run(JSON.stringify(items.map((item) => item.id)));
|
|
295
|
+
for (const item of items) {
|
|
296
|
+
upsertQueueItem.run(
|
|
297
|
+
item.id,
|
|
298
|
+
item.workspace ?? workspace ?? null,
|
|
299
|
+
item.server ?? 'production',
|
|
300
|
+
item.tool ?? 'production_start_job',
|
|
301
|
+
JSON.stringify(item.args ?? {}),
|
|
302
|
+
item.lockKey ?? null,
|
|
303
|
+
item.status ?? 'waiting',
|
|
304
|
+
item.reason ?? null,
|
|
305
|
+
item.jobId ?? null,
|
|
306
|
+
item.activityKey ?? null,
|
|
307
|
+
item.error ?? null,
|
|
308
|
+
item.createdAt ?? now,
|
|
309
|
+
item.startedAt ?? null,
|
|
310
|
+
item.finishedAt ?? null,
|
|
311
|
+
now,
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
db.exec('COMMIT');
|
|
316
|
+
} catch (err) {
|
|
317
|
+
db.exec('ROLLBACK');
|
|
318
|
+
throw err;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function listQueue({ workspace = null } = {}) {
|
|
323
|
+
const rows = workspace
|
|
324
|
+
? listQueueByWorkspaceStatement.all(workspace)
|
|
325
|
+
: listQueueStatement.all();
|
|
326
|
+
return rows.map((row) => ({
|
|
327
|
+
id: row.id,
|
|
328
|
+
workspace: row.workspace ?? null,
|
|
329
|
+
server: row.server,
|
|
330
|
+
tool: row.tool,
|
|
331
|
+
args: row.args ? JSON.parse(row.args) : {},
|
|
332
|
+
lockKey: row.lock_key ?? null,
|
|
333
|
+
status: row.status,
|
|
334
|
+
reason: row.reason ?? null,
|
|
335
|
+
jobId: row.job_id ?? undefined,
|
|
336
|
+
activityKey: row.activity_key ?? undefined,
|
|
337
|
+
error: row.error ?? undefined,
|
|
338
|
+
createdAt: row.created_at ?? undefined,
|
|
339
|
+
startedAt: row.started_at ?? undefined,
|
|
340
|
+
finishedAt: row.finished_at ?? undefined,
|
|
341
|
+
updatedAt: row.updated_at,
|
|
342
|
+
}));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function replayEvents(session, { workspace = null } = {}) {
|
|
346
|
+
const events = listEvents({ workspace });
|
|
347
|
+
for (const event of events) {
|
|
348
|
+
dispatchAgentEvent(session, event);
|
|
349
|
+
}
|
|
350
|
+
if (events.length > 0) {
|
|
351
|
+
lastEventId = events.at(-1).id;
|
|
352
|
+
lastEventSequence = events.at(-1).sequence ?? null;
|
|
353
|
+
}
|
|
354
|
+
return session.agentProjection ?? reduceAgentEvents([]);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function getProjection({ workspace = null } = {}) {
|
|
358
|
+
return reduceAgentEvents(listEvents({ workspace }));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function getState(session = null, { workspace = null } = {}) {
|
|
362
|
+
const events = session?.agentProjection ? null : listEvents({ workspace });
|
|
363
|
+
const projection = session?.agentProjection ?? reduceAgentEvents(events);
|
|
364
|
+
const rawQueue = session?.queueStore?.list() ?? listQueue({ workspace });
|
|
365
|
+
return {
|
|
366
|
+
...projection,
|
|
367
|
+
runs: listRuns({ workspace }),
|
|
368
|
+
queue: projectQueue(projection.plan, rawQueue, { workspace }),
|
|
369
|
+
eventsCursor: session?.agentEvents?.at(-1)?.sequence ?? events?.at(-1)?.sequence ?? lastEventSequence,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function hydrateSession(session, { workspace = null } = {}) {
|
|
374
|
+
const projection = replayEvents(session, { workspace });
|
|
375
|
+
applyAgentProjectionToSession(session, projection);
|
|
376
|
+
session.jobQueue = listQueue({ workspace });
|
|
377
|
+
return projection;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function close() {
|
|
381
|
+
db.close();
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return {
|
|
385
|
+
db,
|
|
386
|
+
dbPath,
|
|
387
|
+
stateDir: resolvedStateDir,
|
|
388
|
+
persistEvent,
|
|
389
|
+
persistRun,
|
|
390
|
+
listEvents,
|
|
391
|
+
listRuns,
|
|
392
|
+
listRecoverableRuns,
|
|
393
|
+
listRecoverableWorkspaces,
|
|
394
|
+
interruptRuns,
|
|
395
|
+
saveQueue,
|
|
396
|
+
listQueue,
|
|
397
|
+
replayEvents,
|
|
398
|
+
hydrateSession,
|
|
399
|
+
getProjection,
|
|
400
|
+
getState,
|
|
401
|
+
close,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function rowToEvent(row) {
|
|
406
|
+
return {
|
|
407
|
+
sequence: row.sequence ?? null,
|
|
408
|
+
id: row.id,
|
|
409
|
+
ts: row.ts,
|
|
410
|
+
type: row.type,
|
|
411
|
+
origin: row.origin ?? 'system',
|
|
412
|
+
runId: row.run_id ?? null,
|
|
413
|
+
turnId: row.turn_id ?? null,
|
|
414
|
+
workspace: row.workspace ?? null,
|
|
415
|
+
payload: row.payload ? JSON.parse(row.payload) : {},
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function ensureColumn(db, table, column, definition) {
|
|
420
|
+
const existing = db.prepare(`PRAGMA table_info(${table})`).all();
|
|
421
|
+
if (existing.some((row) => row.name === column)) return;
|
|
422
|
+
db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function backfillEventSequence(db) {
|
|
426
|
+
db.exec(`
|
|
427
|
+
UPDATE events
|
|
428
|
+
SET sequence = rowid
|
|
429
|
+
WHERE sequence IS NULL
|
|
430
|
+
`);
|
|
431
|
+
}
|