@botbotgo/agent-harness 0.0.74 → 0.0.76

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.
Files changed (40) hide show
  1. package/dist/api.d.ts +2 -1
  2. package/dist/api.js +3 -0
  3. package/dist/benchmark/checkpoint-resume-cost-benchmark.d.ts +33 -0
  4. package/dist/benchmark/checkpoint-resume-cost-benchmark.js +55 -0
  5. package/dist/benchmark/deepagent-local-model-benchmark.d.ts +27 -0
  6. package/dist/benchmark/deepagent-local-model-benchmark.js +35 -0
  7. package/dist/config/agents/direct.yaml +1 -1
  8. package/dist/config/agents/orchestra.yaml +1 -2
  9. package/dist/config/workspace.yaml +31 -0
  10. package/dist/contracts/types.d.ts +38 -1
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.js +1 -1
  13. package/dist/init-project.js +22 -4
  14. package/dist/package-version.d.ts +1 -1
  15. package/dist/package-version.js +1 -1
  16. package/dist/persistence/file-store.d.ts +3 -40
  17. package/dist/persistence/file-store.js +5 -2
  18. package/dist/persistence/sqlite-store.d.ts +68 -0
  19. package/dist/persistence/sqlite-store.js +569 -0
  20. package/dist/persistence/types.d.ts +83 -0
  21. package/dist/persistence/types.js +1 -0
  22. package/dist/runtime/agent-runtime-adapter.d.ts +3 -0
  23. package/dist/runtime/agent-runtime-adapter.js +58 -2
  24. package/dist/runtime/checkpoint-maintenance.d.ts +11 -2
  25. package/dist/runtime/checkpoint-maintenance.js +41 -5
  26. package/dist/runtime/harness.d.ts +5 -1
  27. package/dist/runtime/harness.js +45 -3
  28. package/dist/runtime/health-monitor.d.ts +81 -0
  29. package/dist/runtime/health-monitor.js +448 -0
  30. package/dist/runtime/runtime-record-maintenance.d.ts +43 -0
  31. package/dist/runtime/runtime-record-maintenance.js +169 -0
  32. package/dist/runtime/store.d.ts +2 -0
  33. package/dist/runtime/store.js +38 -20
  34. package/dist/runtime/support/embedding-models.js +57 -1
  35. package/dist/runtime/thread-memory-sync.d.ts +3 -2
  36. package/dist/runtime/thread-memory-sync.js +7 -1
  37. package/dist/workspace/agent-binding-compiler.js +3 -1
  38. package/dist/workspace/support/workspace-ref-utils.d.ts +9 -0
  39. package/dist/workspace/support/workspace-ref-utils.js +38 -0
  40. package/package.json +2 -2
@@ -0,0 +1,569 @@
1
+ import path from "node:path";
2
+ import { mkdir, rm } from "node:fs/promises";
3
+ import { createClient } from "@libsql/client";
4
+ import { fileExists, readJson, writeJson } from "../utils/fs.js";
5
+ const RUNTIME_SQLITE_SCHEMA_VERSION = 1;
6
+ const RUNTIME_SQLITE_SCHEMA_FAMILY = "agent-harness-runtime";
7
+ function asRow(value) {
8
+ return value;
9
+ }
10
+ function asString(value) {
11
+ return typeof value === "string" ? value : String(value ?? "");
12
+ }
13
+ function asNullableString(value) {
14
+ return value == null ? null : asString(value);
15
+ }
16
+ function asBoolean(value) {
17
+ return value === true || value === 1 || value === "1";
18
+ }
19
+ function parseJson(value) {
20
+ return JSON.parse(asString(value));
21
+ }
22
+ function toSqliteUrl(filePath) {
23
+ return `file:${filePath}`;
24
+ }
25
+ function nowIso() {
26
+ return new Date(Date.now()).toISOString();
27
+ }
28
+ async function selectProtectedThreadIds(dbPath) {
29
+ if (!(await fileExists(dbPath))) {
30
+ return [];
31
+ }
32
+ const client = createClient({ url: toSqliteUrl(dbPath) });
33
+ const result = await client.execute(`SELECT DISTINCT thread_id
34
+ FROM runs
35
+ WHERE checkpoint_ref IS NOT NULL
36
+ AND checkpoint_ref != ''
37
+ AND (resumable = 1 OR state NOT IN ('completed', 'failed'))`);
38
+ return result.rows.map((row) => asString(asRow(row).thread_id)).filter((value) => value.length > 0);
39
+ }
40
+ export async function listProtectedCheckpointThreadIds(dbPath) {
41
+ return new Set(await selectProtectedThreadIds(dbPath));
42
+ }
43
+ export class SqlitePersistence {
44
+ runRoot;
45
+ dbPath;
46
+ client = null;
47
+ initialized = false;
48
+ initialization = null;
49
+ constructor(runRoot, dbFile = "runtime.sqlite") {
50
+ this.runRoot = runRoot;
51
+ this.dbPath = path.join(runRoot, dbFile);
52
+ }
53
+ async getClient() {
54
+ if (!this.client) {
55
+ await mkdir(this.runRoot, { recursive: true });
56
+ this.client = createClient({ url: toSqliteUrl(this.dbPath) });
57
+ }
58
+ return this.client;
59
+ }
60
+ async rawExecute(sql, args) {
61
+ const client = await this.getClient();
62
+ if (args) {
63
+ await client.execute(sql, args);
64
+ return;
65
+ }
66
+ await client.execute(sql);
67
+ }
68
+ async rawSelectAll(sql, args) {
69
+ const client = await this.getClient();
70
+ const result = args ? await client.execute(sql, args) : await client.execute(sql);
71
+ return result.rows.map((row) => asRow(row));
72
+ }
73
+ async ensureInitialized() {
74
+ if (this.initialized) {
75
+ return;
76
+ }
77
+ if (this.initialization) {
78
+ await this.initialization;
79
+ return;
80
+ }
81
+ this.initialization = (async () => {
82
+ await mkdir(this.runRoot, { recursive: true });
83
+ await this.getClient();
84
+ await this.rawExecute("PRAGMA journal_mode=WAL");
85
+ await this.rawExecute("PRAGMA foreign_keys=ON");
86
+ await this.rawExecute("PRAGMA busy_timeout=5000");
87
+ await this.rawExecute(`
88
+ CREATE TABLE IF NOT EXISTS runtime_metadata (
89
+ key TEXT PRIMARY KEY,
90
+ value TEXT NOT NULL
91
+ )
92
+ `);
93
+ await this.ensureSchemaMetadata();
94
+ await this.rawExecute(`
95
+ CREATE TABLE IF NOT EXISTS threads (
96
+ thread_id TEXT PRIMARY KEY,
97
+ workspace_id TEXT NOT NULL,
98
+ entry_agent_id TEXT NOT NULL,
99
+ status TEXT NOT NULL,
100
+ latest_run_id TEXT NOT NULL,
101
+ created_at TEXT NOT NULL,
102
+ updated_at TEXT NOT NULL
103
+ )
104
+ `);
105
+ await this.rawExecute(`
106
+ CREATE TABLE IF NOT EXISTS runs (
107
+ run_id TEXT PRIMARY KEY,
108
+ thread_id TEXT NOT NULL,
109
+ agent_id TEXT NOT NULL,
110
+ execution_mode TEXT NOT NULL,
111
+ adapter_kind TEXT,
112
+ created_at TEXT NOT NULL,
113
+ updated_at TEXT NOT NULL,
114
+ state TEXT NOT NULL,
115
+ previous_state TEXT,
116
+ state_entered_at TEXT NOT NULL,
117
+ last_transition_at TEXT NOT NULL,
118
+ resumable INTEGER NOT NULL,
119
+ checkpoint_ref TEXT,
120
+ FOREIGN KEY (thread_id) REFERENCES threads(thread_id)
121
+ )
122
+ `);
123
+ await this.rawExecute("CREATE INDEX IF NOT EXISTS runs_thread_updated_idx ON runs(thread_id, updated_at DESC)");
124
+ await this.rawExecute("CREATE INDEX IF NOT EXISTS runs_state_updated_idx ON runs(state, updated_at DESC)");
125
+ await this.rawExecute("CREATE INDEX IF NOT EXISTS runs_agent_updated_idx ON runs(agent_id, updated_at DESC)");
126
+ await this.rawExecute(`
127
+ CREATE TABLE IF NOT EXISTS thread_messages (
128
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
129
+ thread_id TEXT NOT NULL,
130
+ role TEXT NOT NULL,
131
+ content_json TEXT NOT NULL,
132
+ run_id TEXT NOT NULL,
133
+ created_at TEXT NOT NULL,
134
+ FOREIGN KEY (thread_id) REFERENCES threads(thread_id)
135
+ )
136
+ `);
137
+ await this.rawExecute("CREATE INDEX IF NOT EXISTS thread_messages_thread_created_idx ON thread_messages(thread_id, created_at, id)");
138
+ await this.rawExecute(`
139
+ CREATE TABLE IF NOT EXISTS events (
140
+ thread_id TEXT NOT NULL,
141
+ run_id TEXT NOT NULL,
142
+ sequence INTEGER NOT NULL,
143
+ event_json TEXT NOT NULL,
144
+ created_at TEXT NOT NULL,
145
+ PRIMARY KEY (thread_id, run_id, sequence),
146
+ FOREIGN KEY (thread_id) REFERENCES threads(thread_id),
147
+ FOREIGN KEY (run_id) REFERENCES runs(run_id)
148
+ )
149
+ `);
150
+ await this.rawExecute(`
151
+ CREATE TABLE IF NOT EXISTS approvals (
152
+ approval_id TEXT PRIMARY KEY,
153
+ pending_action_id TEXT NOT NULL,
154
+ thread_id TEXT NOT NULL,
155
+ run_id TEXT NOT NULL,
156
+ tool_call_id TEXT NOT NULL,
157
+ tool_name TEXT NOT NULL,
158
+ status TEXT NOT NULL,
159
+ requested_at TEXT NOT NULL,
160
+ resolved_at TEXT,
161
+ allowed_decisions_json TEXT NOT NULL,
162
+ input_preview_json TEXT NOT NULL,
163
+ checkpoint_ref TEXT NOT NULL,
164
+ event_refs_json TEXT NOT NULL,
165
+ FOREIGN KEY (thread_id) REFERENCES threads(thread_id),
166
+ FOREIGN KEY (run_id) REFERENCES runs(run_id)
167
+ )
168
+ `);
169
+ await this.rawExecute("CREATE INDEX IF NOT EXISTS approvals_thread_run_idx ON approvals(thread_id, run_id)");
170
+ await this.rawExecute("CREATE INDEX IF NOT EXISTS approvals_status_idx ON approvals(status, requested_at DESC)");
171
+ await this.rawExecute(`
172
+ CREATE TABLE IF NOT EXISTS run_requests (
173
+ run_id TEXT PRIMARY KEY,
174
+ thread_id TEXT NOT NULL,
175
+ request_json TEXT NOT NULL,
176
+ saved_at TEXT NOT NULL,
177
+ FOREIGN KEY (thread_id) REFERENCES threads(thread_id),
178
+ FOREIGN KEY (run_id) REFERENCES runs(run_id)
179
+ )
180
+ `);
181
+ await this.rawExecute(`
182
+ CREATE TABLE IF NOT EXISTS recovery_intents (
183
+ run_id TEXT PRIMARY KEY,
184
+ thread_id TEXT NOT NULL,
185
+ intent_json TEXT NOT NULL,
186
+ saved_at TEXT NOT NULL,
187
+ FOREIGN KEY (thread_id) REFERENCES threads(thread_id),
188
+ FOREIGN KEY (run_id) REFERENCES runs(run_id)
189
+ )
190
+ `);
191
+ await this.rawExecute(`
192
+ CREATE TABLE IF NOT EXISTS artifacts (
193
+ artifact_id TEXT PRIMARY KEY,
194
+ thread_id TEXT NOT NULL,
195
+ run_id TEXT NOT NULL,
196
+ kind TEXT NOT NULL,
197
+ path TEXT NOT NULL,
198
+ created_at TEXT NOT NULL,
199
+ FOREIGN KEY (thread_id) REFERENCES threads(thread_id),
200
+ FOREIGN KEY (run_id) REFERENCES runs(run_id)
201
+ )
202
+ `);
203
+ await this.rawExecute("CREATE INDEX IF NOT EXISTS artifacts_thread_run_idx ON artifacts(thread_id, run_id, created_at)");
204
+ this.initialized = true;
205
+ })().finally(() => {
206
+ this.initialization = null;
207
+ });
208
+ await this.initialization;
209
+ }
210
+ threadDir(threadId) {
211
+ return path.join(this.runRoot, "threads", threadId);
212
+ }
213
+ runDir(threadId, runId) {
214
+ return path.join(this.threadDir(threadId), "runs", runId);
215
+ }
216
+ async execute(sql, args) {
217
+ await this.ensureInitialized();
218
+ const client = await this.getClient();
219
+ if (args) {
220
+ await client.execute(sql, args);
221
+ return;
222
+ }
223
+ await client.execute(sql);
224
+ }
225
+ async selectOne(sql, args) {
226
+ await this.ensureInitialized();
227
+ const client = await this.getClient();
228
+ const result = args ? await client.execute(sql, args) : await client.execute(sql);
229
+ const first = result.rows[0];
230
+ return first ? asRow(first) : null;
231
+ }
232
+ async selectAll(sql, args) {
233
+ await this.ensureInitialized();
234
+ const client = await this.getClient();
235
+ const result = args ? await client.execute(sql, args) : await client.execute(sql);
236
+ return result.rows.map((row) => asRow(row));
237
+ }
238
+ mapThreadSummary(row) {
239
+ return {
240
+ agentId: asString(row.entry_agent_id),
241
+ threadId: asString(row.thread_id),
242
+ latestRunId: asString(row.latest_run_id),
243
+ createdAt: asString(row.created_at),
244
+ updatedAt: asString(row.updated_at),
245
+ status: asString(row.status),
246
+ };
247
+ }
248
+ mapRunSummary(row) {
249
+ return {
250
+ runId: asString(row.run_id),
251
+ threadId: asString(row.thread_id),
252
+ agentId: asString(row.agent_id),
253
+ executionMode: asString(row.execution_mode),
254
+ adapterKind: asNullableString(row.adapter_kind) ?? undefined,
255
+ createdAt: asString(row.created_at),
256
+ updatedAt: asString(row.updated_at),
257
+ state: asString(row.state),
258
+ checkpointRef: asNullableString(row.checkpoint_ref),
259
+ resumable: asBoolean(row.resumable),
260
+ };
261
+ }
262
+ mapApproval(row) {
263
+ return {
264
+ approvalId: asString(row.approval_id),
265
+ pendingActionId: asString(row.pending_action_id),
266
+ threadId: asString(row.thread_id),
267
+ runId: asString(row.run_id),
268
+ toolCallId: asString(row.tool_call_id),
269
+ toolName: asString(row.tool_name),
270
+ status: asString(row.status),
271
+ requestedAt: asString(row.requested_at),
272
+ resolvedAt: asNullableString(row.resolved_at),
273
+ allowedDecisions: parseJson(row.allowed_decisions_json),
274
+ inputPreview: parseJson(row.input_preview_json),
275
+ checkpointRef: asString(row.checkpoint_ref),
276
+ eventRefs: parseJson(row.event_refs_json),
277
+ };
278
+ }
279
+ async initialize() {
280
+ await this.ensureInitialized();
281
+ }
282
+ async ensureSchemaMetadata() {
283
+ const rows = await this.rawSelectAll("SELECT key, value FROM runtime_metadata WHERE key IN ('schema_family', 'schema_version')");
284
+ const metadata = new Map(rows.map((row) => [asString(row.key), asString(row.value)]));
285
+ const family = metadata.get("schema_family");
286
+ const version = metadata.get("schema_version");
287
+ if (!family && !version) {
288
+ await this.rawExecute("INSERT OR REPLACE INTO runtime_metadata (key, value) VALUES (?, ?)", ["schema_family", RUNTIME_SQLITE_SCHEMA_FAMILY]);
289
+ await this.rawExecute("INSERT OR REPLACE INTO runtime_metadata (key, value) VALUES (?, ?)", ["schema_version", String(RUNTIME_SQLITE_SCHEMA_VERSION)]);
290
+ return;
291
+ }
292
+ if (family !== RUNTIME_SQLITE_SCHEMA_FAMILY) {
293
+ throw new Error(`Unsupported runtime sqlite schema family ${JSON.stringify(family)} in ${this.dbPath}. Expected ${JSON.stringify(RUNTIME_SQLITE_SCHEMA_FAMILY)}.`);
294
+ }
295
+ if (version !== String(RUNTIME_SQLITE_SCHEMA_VERSION)) {
296
+ throw new Error(`Unsupported runtime sqlite schema version ${JSON.stringify(version)} in ${this.dbPath}. Expected ${RUNTIME_SQLITE_SCHEMA_VERSION}.`);
297
+ }
298
+ }
299
+ async createThread(input) {
300
+ await mkdir(this.threadDir(input.threadId), { recursive: true });
301
+ await this.execute(`INSERT OR REPLACE INTO threads
302
+ (thread_id, workspace_id, entry_agent_id, status, latest_run_id, created_at, updated_at)
303
+ VALUES (?, ?, ?, ?, ?, ?, ?)`, [input.threadId, "default", input.agentId, input.status, input.runId, input.createdAt, input.createdAt]);
304
+ }
305
+ async createRun(input) {
306
+ await mkdir(path.join(this.runDir(input.threadId, input.runId), "events"), { recursive: true });
307
+ await this.execute(`INSERT OR REPLACE INTO runs
308
+ (run_id, thread_id, agent_id, execution_mode, adapter_kind, created_at, updated_at, state, previous_state, state_entered_at, last_transition_at, resumable, checkpoint_ref)
309
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
310
+ input.runId,
311
+ input.threadId,
312
+ input.agentId,
313
+ input.executionMode,
314
+ input.adapterKind ?? input.executionMode ?? null,
315
+ input.createdAt,
316
+ input.createdAt,
317
+ "running",
318
+ null,
319
+ input.createdAt,
320
+ input.createdAt,
321
+ 0,
322
+ null,
323
+ ]);
324
+ }
325
+ async setRunState(threadId, runId, state, checkpointRef) {
326
+ const current = await this.getRunLifecycle(threadId, runId);
327
+ const now = nowIso();
328
+ const nextCheckpointRef = checkpointRef === undefined ? current.checkpointRef : checkpointRef;
329
+ const resumable = state === "waiting_for_approval" ? 1 : 0;
330
+ await this.execute(`UPDATE runs
331
+ SET state = ?, previous_state = ?, state_entered_at = ?, last_transition_at = ?, updated_at = ?, resumable = ?, checkpoint_ref = ?
332
+ WHERE run_id = ? AND thread_id = ?`, [state, current.state, now, now, now, resumable, nextCheckpointRef, runId, threadId]);
333
+ await this.execute(`UPDATE threads
334
+ SET status = ?, latest_run_id = ?, updated_at = ?
335
+ WHERE thread_id = ?`, [state, runId, now, threadId]);
336
+ }
337
+ async appendEvent(event) {
338
+ await this.execute(`INSERT OR REPLACE INTO events
339
+ (thread_id, run_id, sequence, event_json, created_at)
340
+ VALUES (?, ?, ?, ?, ?)`, [event.threadId, event.runId, event.sequence, JSON.stringify(event), event.timestamp]);
341
+ }
342
+ async listSessions() {
343
+ const rows = await this.selectAll(`SELECT thread_id, entry_agent_id, latest_run_id, created_at, updated_at, status
344
+ FROM threads
345
+ ORDER BY updated_at DESC`);
346
+ return rows.map((row) => this.mapThreadSummary(row));
347
+ }
348
+ async listRuns() {
349
+ const rows = await this.selectAll(`SELECT run_id, thread_id, agent_id, execution_mode, adapter_kind, created_at, updated_at, state, checkpoint_ref, resumable
350
+ FROM runs
351
+ ORDER BY updated_at DESC`);
352
+ return rows.map((row) => this.mapRunSummary(row));
353
+ }
354
+ async getRun(runId) {
355
+ const row = await this.selectOne(`SELECT run_id, thread_id, agent_id, execution_mode, adapter_kind, created_at, updated_at, state, checkpoint_ref, resumable
356
+ FROM runs
357
+ WHERE run_id = ?`, [runId]);
358
+ return row ? this.mapRunSummary(row) : null;
359
+ }
360
+ async getSession(threadId) {
361
+ const row = await this.selectOne(`SELECT thread_id, entry_agent_id, latest_run_id, created_at, updated_at, status
362
+ FROM threads
363
+ WHERE thread_id = ?`, [threadId]);
364
+ return row ? this.mapThreadSummary(row) : null;
365
+ }
366
+ async getThreadMeta(threadId) {
367
+ const row = await this.selectOne("SELECT * FROM threads WHERE thread_id = ?", [threadId]);
368
+ if (!row) {
369
+ return null;
370
+ }
371
+ return {
372
+ threadId: asString(row.thread_id),
373
+ workspaceId: asString(row.workspace_id),
374
+ entryAgentId: asString(row.entry_agent_id),
375
+ createdAt: asString(row.created_at),
376
+ updatedAt: asString(row.updated_at),
377
+ status: asString(row.status),
378
+ latestRunId: asString(row.latest_run_id),
379
+ };
380
+ }
381
+ async listThreadRuns(threadId) {
382
+ const rows = await this.selectAll(`SELECT run_id, thread_id, agent_id, execution_mode, adapter_kind, created_at, updated_at, state, checkpoint_ref, resumable
383
+ FROM runs
384
+ WHERE thread_id = ?
385
+ ORDER BY created_at DESC`, [threadId]);
386
+ return rows.map((row) => this.mapRunSummary(row));
387
+ }
388
+ async listRunEvents(threadId, runId) {
389
+ const rows = await this.selectAll(`SELECT event_json
390
+ FROM events
391
+ WHERE thread_id = ? AND run_id = ?
392
+ ORDER BY sequence ASC`, [threadId, runId]);
393
+ return rows.map((row) => parseJson(row.event_json));
394
+ }
395
+ async listApprovals() {
396
+ const rows = await this.selectAll("SELECT * FROM approvals ORDER BY requested_at ASC, approval_id ASC");
397
+ return rows.map((row) => this.mapApproval(row));
398
+ }
399
+ async getApproval(approvalId) {
400
+ const row = await this.selectOne("SELECT * FROM approvals WHERE approval_id = ?", [approvalId]);
401
+ return row ? this.mapApproval(row) : null;
402
+ }
403
+ async getRunApprovals(threadId, runId) {
404
+ const rows = await this.selectAll("SELECT * FROM approvals WHERE thread_id = ? AND run_id = ? ORDER BY requested_at ASC, approval_id ASC", [threadId, runId]);
405
+ return rows.map((row) => this.mapApproval(row));
406
+ }
407
+ async getRunMeta(threadId, runId) {
408
+ const row = await this.selectOne(`SELECT run_id, thread_id, agent_id, execution_mode, adapter_kind, created_at, updated_at
409
+ FROM runs
410
+ WHERE thread_id = ? AND run_id = ?`, [threadId, runId]);
411
+ if (!row) {
412
+ throw new Error(`Missing run ${runId} for thread ${threadId}`);
413
+ }
414
+ return {
415
+ runId: asString(row.run_id),
416
+ threadId: asString(row.thread_id),
417
+ agentId: asString(row.agent_id),
418
+ executionMode: asString(row.execution_mode),
419
+ adapterKind: asNullableString(row.adapter_kind) ?? undefined,
420
+ createdAt: asString(row.created_at),
421
+ updatedAt: asString(row.updated_at),
422
+ };
423
+ }
424
+ async getRunLifecycle(threadId, runId) {
425
+ const row = await this.selectOne(`SELECT state, previous_state, state_entered_at, last_transition_at, resumable, checkpoint_ref
426
+ FROM runs
427
+ WHERE thread_id = ? AND run_id = ?`, [threadId, runId]);
428
+ if (!row) {
429
+ throw new Error(`Missing run lifecycle ${runId} for thread ${threadId}`);
430
+ }
431
+ return {
432
+ state: asString(row.state),
433
+ previousState: asNullableString(row.previous_state),
434
+ stateEnteredAt: asString(row.state_entered_at),
435
+ lastTransitionAt: asString(row.last_transition_at),
436
+ resumable: asBoolean(row.resumable),
437
+ checkpointRef: asNullableString(row.checkpoint_ref),
438
+ };
439
+ }
440
+ async deleteThread(threadId) {
441
+ const exists = await this.getSession(threadId);
442
+ if (!exists) {
443
+ return false;
444
+ }
445
+ await this.execute("DELETE FROM artifacts WHERE thread_id = ?", [threadId]);
446
+ await this.execute("DELETE FROM approvals WHERE thread_id = ?", [threadId]);
447
+ await this.execute("DELETE FROM events WHERE thread_id = ?", [threadId]);
448
+ await this.execute("DELETE FROM run_requests WHERE thread_id = ?", [threadId]);
449
+ await this.execute("DELETE FROM recovery_intents WHERE thread_id = ?", [threadId]);
450
+ await this.execute("DELETE FROM thread_messages WHERE thread_id = ?", [threadId]);
451
+ await this.execute("DELETE FROM runs WHERE thread_id = ?", [threadId]);
452
+ await this.execute("DELETE FROM threads WHERE thread_id = ?", [threadId]);
453
+ await rm(this.threadDir(threadId), { recursive: true, force: true });
454
+ return true;
455
+ }
456
+ async saveRunRequest(threadId, runId, request) {
457
+ await this.execute(`INSERT OR REPLACE INTO run_requests
458
+ (run_id, thread_id, request_json, saved_at)
459
+ VALUES (?, ?, ?, ?)`, [runId, threadId, JSON.stringify(request), request.savedAt]);
460
+ }
461
+ async getRunRequest(threadId, runId) {
462
+ const row = await this.selectOne("SELECT request_json FROM run_requests WHERE thread_id = ? AND run_id = ?", [threadId, runId]);
463
+ return row ? parseJson(row.request_json) : null;
464
+ }
465
+ async clearRunRequest(threadId, runId) {
466
+ await this.execute("DELETE FROM run_requests WHERE thread_id = ? AND run_id = ?", [threadId, runId]);
467
+ }
468
+ async createApproval(record) {
469
+ await this.execute(`INSERT OR REPLACE INTO approvals
470
+ (approval_id, pending_action_id, thread_id, run_id, tool_call_id, tool_name, status, requested_at, resolved_at, allowed_decisions_json, input_preview_json, checkpoint_ref, event_refs_json)
471
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
472
+ record.approvalId,
473
+ record.pendingActionId,
474
+ record.threadId,
475
+ record.runId,
476
+ record.toolCallId,
477
+ record.toolName,
478
+ record.status,
479
+ record.requestedAt,
480
+ record.resolvedAt,
481
+ JSON.stringify(record.allowedDecisions),
482
+ JSON.stringify(record.inputPreview),
483
+ record.checkpointRef,
484
+ JSON.stringify(record.eventRefs),
485
+ ]);
486
+ }
487
+ async resolveApproval(threadId, runId, approvalId, status) {
488
+ const current = await this.getApproval(approvalId);
489
+ if (!current || current.threadId !== threadId || current.runId !== runId) {
490
+ throw new Error(`Missing approval ${approvalId} for run ${runId}`);
491
+ }
492
+ const updated = {
493
+ ...current,
494
+ status,
495
+ resolvedAt: nowIso(),
496
+ };
497
+ await this.createApproval(updated);
498
+ return updated;
499
+ }
500
+ async createArtifact(threadId, runId, artifact, content) {
501
+ const filePath = path.join(this.runDir(threadId, runId), artifact.path);
502
+ await mkdir(path.dirname(filePath), { recursive: true });
503
+ await writeJson(filePath, content);
504
+ await this.execute(`INSERT OR REPLACE INTO artifacts
505
+ (artifact_id, thread_id, run_id, kind, path, created_at)
506
+ VALUES (?, ?, ?, ?, ?, ?)`, [artifact.artifactId, threadId, runId, artifact.kind, artifact.path, artifact.createdAt]);
507
+ return artifact;
508
+ }
509
+ async listArtifacts(threadId, runId) {
510
+ const rows = await this.selectAll(`SELECT artifact_id, kind, path, created_at
511
+ FROM artifacts
512
+ WHERE thread_id = ? AND run_id = ?
513
+ ORDER BY created_at ASC, artifact_id ASC`, [threadId, runId]);
514
+ return {
515
+ threadId,
516
+ runId,
517
+ items: rows.map((row) => ({
518
+ artifactId: asString(row.artifact_id),
519
+ kind: asString(row.kind),
520
+ path: asString(row.path),
521
+ createdAt: asString(row.created_at),
522
+ })),
523
+ };
524
+ }
525
+ async appendThreadMessage(threadId, message) {
526
+ await this.execute(`INSERT INTO thread_messages
527
+ (thread_id, role, content_json, run_id, created_at)
528
+ VALUES (?, ?, ?, ?, ?)`, [threadId, message.role, JSON.stringify(message.content), message.runId, message.createdAt]);
529
+ }
530
+ async listThreadMessages(threadId, limit = 12) {
531
+ const rows = await this.selectAll(`SELECT role, content_json, run_id, created_at
532
+ FROM (
533
+ SELECT role, content_json, run_id, created_at, id
534
+ FROM thread_messages
535
+ WHERE thread_id = ?
536
+ ORDER BY created_at DESC, id DESC
537
+ LIMIT ?
538
+ ) recent
539
+ ORDER BY created_at ASC, id ASC`, [threadId, limit]);
540
+ return rows.map((row) => ({
541
+ role: asString(row.role),
542
+ content: parseJson(row.content_json),
543
+ runId: asString(row.run_id),
544
+ createdAt: asString(row.created_at),
545
+ }));
546
+ }
547
+ async saveRecoveryIntent(threadId, runId, intent) {
548
+ const savedAt = typeof intent.savedAt === "string"
549
+ ? (intent.savedAt)
550
+ : nowIso();
551
+ await this.execute(`INSERT OR REPLACE INTO recovery_intents
552
+ (run_id, thread_id, intent_json, saved_at)
553
+ VALUES (?, ?, ?, ?)`, [runId, threadId, JSON.stringify(intent), savedAt]);
554
+ }
555
+ async getRecoveryIntent(threadId, runId) {
556
+ const row = await this.selectOne("SELECT intent_json FROM recovery_intents WHERE thread_id = ? AND run_id = ?", [threadId, runId]);
557
+ return row ? parseJson(row.intent_json) : null;
558
+ }
559
+ async clearRecoveryIntent(threadId, runId) {
560
+ await this.execute("DELETE FROM recovery_intents WHERE thread_id = ? AND run_id = ?", [threadId, runId]);
561
+ }
562
+ async readArtifact(threadId, runId, artifactPath) {
563
+ const filePath = path.join(this.runDir(threadId, runId), artifactPath);
564
+ if (!(await fileExists(filePath))) {
565
+ return null;
566
+ }
567
+ return readJson(filePath);
568
+ }
569
+ }
@@ -0,0 +1,83 @@
1
+ import type { ArtifactListing, ArtifactRecord, HarnessEvent, InternalApprovalRecord, InvocationEnvelope, MessageContent, RunState, RunSummary, ThreadRunRecord, ThreadSummary, TranscriptMessage } from "../contracts/types.js";
2
+ export type PersistenceThreadMeta = {
3
+ threadId: string;
4
+ workspaceId: string;
5
+ entryAgentId: string;
6
+ createdAt: string;
7
+ updatedAt: string;
8
+ status: RunState;
9
+ latestRunId: string;
10
+ };
11
+ export type PersistenceRunMeta = {
12
+ runId: string;
13
+ threadId: string;
14
+ agentId: string;
15
+ executionMode: string;
16
+ adapterKind?: string;
17
+ createdAt: string;
18
+ updatedAt: string;
19
+ };
20
+ export type PersistenceLifecycle = {
21
+ state: RunState;
22
+ previousState: RunState | null;
23
+ stateEnteredAt: string;
24
+ lastTransitionAt: string;
25
+ resumable: boolean;
26
+ checkpointRef: string | null;
27
+ };
28
+ export type PersistedRunRequest = {
29
+ input: MessageContent;
30
+ invocation?: InvocationEnvelope;
31
+ savedAt: string;
32
+ };
33
+ export type RecoveryIntent = {
34
+ kind: "approval-decision";
35
+ savedAt: string;
36
+ checkpointRef: string | null;
37
+ resumePayload: unknown;
38
+ attempts: number;
39
+ };
40
+ export interface RuntimePersistence {
41
+ initialize(): Promise<void>;
42
+ createThread(input: {
43
+ threadId: string;
44
+ agentId: string;
45
+ runId: string;
46
+ status: RunState;
47
+ createdAt: string;
48
+ }): Promise<void>;
49
+ createRun(input: {
50
+ threadId: string;
51
+ runId: string;
52
+ agentId: string;
53
+ executionMode: string;
54
+ adapterKind?: string;
55
+ createdAt: string;
56
+ }): Promise<void>;
57
+ setRunState(threadId: string, runId: string, state: RunState, checkpointRef?: string | null): Promise<void>;
58
+ appendEvent(event: HarnessEvent): Promise<void>;
59
+ listSessions(): Promise<ThreadSummary[]>;
60
+ listRuns(): Promise<RunSummary[]>;
61
+ getRun(runId: string): Promise<RunSummary | null>;
62
+ getSession(threadId: string): Promise<ThreadSummary | null>;
63
+ getThreadMeta(threadId: string): Promise<PersistenceThreadMeta | null>;
64
+ listThreadRuns(threadId: string): Promise<ThreadRunRecord[]>;
65
+ listApprovals(): Promise<InternalApprovalRecord[]>;
66
+ getApproval(approvalId: string): Promise<InternalApprovalRecord | null>;
67
+ getRunApprovals(threadId: string, runId: string): Promise<InternalApprovalRecord[]>;
68
+ getRunMeta(threadId: string, runId: string): Promise<PersistenceRunMeta>;
69
+ getRunLifecycle(threadId: string, runId: string): Promise<PersistenceLifecycle>;
70
+ deleteThread(threadId: string): Promise<boolean>;
71
+ saveRunRequest(threadId: string, runId: string, request: PersistedRunRequest): Promise<void>;
72
+ getRunRequest(threadId: string, runId: string): Promise<PersistedRunRequest | null>;
73
+ clearRunRequest(threadId: string, runId: string): Promise<void>;
74
+ createApproval(record: InternalApprovalRecord): Promise<void>;
75
+ resolveApproval(threadId: string, runId: string, approvalId: string, status: InternalApprovalRecord["status"]): Promise<InternalApprovalRecord>;
76
+ createArtifact(threadId: string, runId: string, artifact: ArtifactRecord, content: unknown): Promise<ArtifactRecord>;
77
+ listArtifacts(threadId: string, runId: string): Promise<ArtifactListing>;
78
+ appendThreadMessage(threadId: string, message: TranscriptMessage): Promise<void>;
79
+ listThreadMessages(threadId: string, limit?: number): Promise<TranscriptMessage[]>;
80
+ saveRecoveryIntent(threadId: string, runId: string, intent: RecoveryIntent): Promise<void>;
81
+ getRecoveryIntent(threadId: string, runId: string): Promise<RecoveryIntent | null>;
82
+ clearRecoveryIntent(threadId: string, runId: string): Promise<void>;
83
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -24,6 +24,9 @@ export declare class AgentRuntimeAdapter {
24
24
  constructor(options?: RuntimeAdapterOptions);
25
25
  private resolveBindingTimeout;
26
26
  private resolveStreamIdleTimeout;
27
+ private resolveProviderRetryPolicy;
28
+ private isRetryableProviderError;
29
+ private invokeWithProviderRetry;
27
30
  private withTimeout;
28
31
  private iterateWithTimeout;
29
32
  private materializeModelStream;