@botbotgo/agent-harness 0.0.100 → 0.0.102

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/package-version.d.ts +1 -1
  2. package/dist/package-version.js +1 -1
  3. package/dist/persistence/sqlite-run-context-store.d.ts +22 -0
  4. package/dist/persistence/sqlite-run-context-store.js +64 -0
  5. package/dist/persistence/sqlite-run-queue-store.d.ts +41 -0
  6. package/dist/persistence/sqlite-run-queue-store.js +120 -0
  7. package/dist/persistence/sqlite-store.d.ts +2 -2
  8. package/dist/persistence/sqlite-store.js +31 -117
  9. package/dist/resource/mcp-tool-support.d.ts +21 -0
  10. package/dist/resource/mcp-tool-support.js +173 -0
  11. package/dist/resource/resource-impl.d.ts +1 -18
  12. package/dist/resource/resource-impl.js +79 -240
  13. package/dist/runtime/adapter/invoke-runtime.d.ts +22 -0
  14. package/dist/runtime/adapter/invoke-runtime.js +18 -0
  15. package/dist/runtime/adapter/stream-runtime.d.ts +46 -0
  16. package/dist/runtime/adapter/stream-runtime.js +93 -0
  17. package/dist/runtime/agent-runtime-adapter.d.ts +1 -12
  18. package/dist/runtime/agent-runtime-adapter.js +122 -312
  19. package/dist/runtime/harness/run/recovery.d.ts +42 -0
  20. package/dist/runtime/harness/run/recovery.js +139 -0
  21. package/dist/runtime/harness/run/run-operations.d.ts +50 -0
  22. package/dist/runtime/harness/run/run-operations.js +113 -0
  23. package/dist/runtime/harness/run/run-slot-acquisition.d.ts +64 -0
  24. package/dist/runtime/harness/run/run-slot-acquisition.js +157 -0
  25. package/dist/runtime/harness/run/stream-run.d.ts +53 -0
  26. package/dist/runtime/harness/run/stream-run.js +304 -0
  27. package/dist/runtime/harness.d.ts +2 -17
  28. package/dist/runtime/harness.js +157 -773
  29. package/dist/runtime/support/runtime-factories.js +2 -2
  30. package/dist/workspace/object-loader.d.ts +1 -8
  31. package/dist/workspace/object-loader.js +43 -275
  32. package/dist/workspace/yaml-object-reader.d.ts +15 -0
  33. package/dist/workspace/yaml-object-reader.js +202 -0
  34. package/package.json +1 -1
  35. package/dist/runtime/checkpoint-maintenance.d.ts +0 -1
  36. package/dist/runtime/checkpoint-maintenance.js +0 -1
  37. package/dist/runtime/file-checkpoint-saver.d.ts +0 -1
  38. package/dist/runtime/file-checkpoint-saver.js +0 -1
  39. package/dist/runtime/sqlite-maintained-checkpoint-saver.d.ts +0 -1
  40. package/dist/runtime/sqlite-maintained-checkpoint-saver.js +0 -1
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.99";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.101";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.99";
1
+ export const AGENT_HARNESS_VERSION = "0.0.101";
@@ -0,0 +1,22 @@
1
+ import type { InValue } from "@libsql/client";
2
+ import type { TranscriptMessage } from "../contracts/types.js";
3
+ import type { PersistedRunRequest, RecoveryIntent } from "./types.js";
4
+ type SqlRow = Record<string, unknown>;
5
+ type SqliteRunContextStoreDb = {
6
+ execute(sql: string, args?: InValue[]): Promise<void>;
7
+ selectOne(sql: string, args?: InValue[]): Promise<SqlRow | null>;
8
+ selectAll(sql: string, args?: InValue[]): Promise<SqlRow[]>;
9
+ };
10
+ export declare class SqliteRunContextStore {
11
+ private readonly db;
12
+ constructor(db: SqliteRunContextStoreDb);
13
+ saveRunRequest(threadId: string, runId: string, request: PersistedRunRequest): Promise<void>;
14
+ getRunRequest(threadId: string, runId: string): Promise<PersistedRunRequest | null>;
15
+ clearRunRequest(threadId: string, runId: string): Promise<void>;
16
+ appendThreadMessage(threadId: string, message: TranscriptMessage): Promise<void>;
17
+ listThreadMessages(threadId: string, limit?: number): Promise<TranscriptMessage[]>;
18
+ saveRecoveryIntent(threadId: string, runId: string, intent: RecoveryIntent): Promise<void>;
19
+ getRecoveryIntent(threadId: string, runId: string): Promise<RecoveryIntent | null>;
20
+ clearRecoveryIntent(threadId: string, runId: string): Promise<void>;
21
+ }
22
+ export {};
@@ -0,0 +1,64 @@
1
+ function asString(value) {
2
+ return typeof value === "string" ? value : String(value ?? "");
3
+ }
4
+ function parseJson(value) {
5
+ return JSON.parse(asString(value));
6
+ }
7
+ function nowIso() {
8
+ return new Date(Date.now()).toISOString();
9
+ }
10
+ export class SqliteRunContextStore {
11
+ db;
12
+ constructor(db) {
13
+ this.db = db;
14
+ }
15
+ async saveRunRequest(threadId, runId, request) {
16
+ await this.db.execute(`INSERT OR REPLACE INTO run_requests
17
+ (run_id, thread_id, request_json, saved_at)
18
+ VALUES (?, ?, ?, ?)`, [runId, threadId, JSON.stringify(request), request.savedAt]);
19
+ }
20
+ async getRunRequest(threadId, runId) {
21
+ const row = await this.db.selectOne("SELECT request_json FROM run_requests WHERE thread_id = ? AND run_id = ?", [threadId, runId]);
22
+ return row ? parseJson(row.request_json) : null;
23
+ }
24
+ async clearRunRequest(threadId, runId) {
25
+ await this.db.execute("DELETE FROM run_requests WHERE thread_id = ? AND run_id = ?", [threadId, runId]);
26
+ }
27
+ async appendThreadMessage(threadId, message) {
28
+ await this.db.execute(`INSERT INTO thread_messages
29
+ (thread_id, role, content_json, run_id, created_at)
30
+ VALUES (?, ?, ?, ?, ?)`, [threadId, message.role, JSON.stringify(message.content), message.runId, message.createdAt]);
31
+ }
32
+ async listThreadMessages(threadId, limit = 12) {
33
+ const rows = await this.db.selectAll(`SELECT role, content_json, run_id, created_at
34
+ FROM (
35
+ SELECT role, content_json, run_id, created_at, id
36
+ FROM thread_messages
37
+ WHERE thread_id = ?
38
+ ORDER BY created_at DESC, id DESC
39
+ LIMIT ?
40
+ ) recent
41
+ ORDER BY created_at ASC, id ASC`, [threadId, limit]);
42
+ return rows.map((row) => ({
43
+ role: asString(row.role),
44
+ content: parseJson(row.content_json),
45
+ runId: asString(row.run_id),
46
+ createdAt: asString(row.created_at),
47
+ }));
48
+ }
49
+ async saveRecoveryIntent(threadId, runId, intent) {
50
+ const savedAt = typeof intent.savedAt === "string"
51
+ ? (intent.savedAt)
52
+ : nowIso();
53
+ await this.db.execute(`INSERT OR REPLACE INTO recovery_intents
54
+ (run_id, thread_id, intent_json, saved_at)
55
+ VALUES (?, ?, ?, ?)`, [runId, threadId, JSON.stringify(intent), savedAt]);
56
+ }
57
+ async getRecoveryIntent(threadId, runId) {
58
+ const row = await this.db.selectOne("SELECT intent_json FROM recovery_intents WHERE thread_id = ? AND run_id = ?", [threadId, runId]);
59
+ return row ? parseJson(row.intent_json) : null;
60
+ }
61
+ async clearRecoveryIntent(threadId, runId) {
62
+ await this.db.execute("DELETE FROM recovery_intents WHERE thread_id = ? AND run_id = ?", [threadId, runId]);
63
+ }
64
+ }
@@ -0,0 +1,41 @@
1
+ import type { InValue } from "@libsql/client";
2
+ import type { PersistedRunControlRecord, PersistedRunQueueRecord } from "./types.js";
3
+ type SqlRow = Record<string, unknown>;
4
+ type SqliteRunQueueStoreDb = {
5
+ execute(sql: string, args?: InValue[]): Promise<void>;
6
+ selectOne(sql: string, args?: InValue[]): Promise<SqlRow | null>;
7
+ selectAll(sql: string, args?: InValue[]): Promise<SqlRow[]>;
8
+ };
9
+ export declare class SqliteRunQueueStore {
10
+ private readonly db;
11
+ constructor(db: SqliteRunQueueStoreDb);
12
+ private mapQueuedRun;
13
+ private mapRunControl;
14
+ enqueueRun(input: {
15
+ threadId: string;
16
+ runId: string;
17
+ priority?: number;
18
+ queueKey?: string | null;
19
+ availableAt?: string;
20
+ }): Promise<void>;
21
+ getQueuedRun(runId: string): Promise<PersistedRunQueueRecord | null>;
22
+ claimQueuedRun(input: {
23
+ threadId: string;
24
+ runId: string;
25
+ workerId: string;
26
+ claimedAt?: string;
27
+ leaseExpiresAt: string;
28
+ }): Promise<PersistedRunQueueRecord>;
29
+ renewRunLease(input: {
30
+ runId: string;
31
+ workerId: string;
32
+ heartbeatAt?: string;
33
+ leaseExpiresAt: string;
34
+ }): Promise<void>;
35
+ releaseRunClaim(runId: string): Promise<void>;
36
+ listExpiredClaimedRuns(cutoffIso: string): Promise<PersistedRunQueueRecord[]>;
37
+ getRunControl(runId: string): Promise<PersistedRunControlRecord | null>;
38
+ requestRunCancel(runId: string, reason?: string): Promise<PersistedRunControlRecord>;
39
+ clearRunCancel(runId: string): Promise<void>;
40
+ }
41
+ export {};
@@ -0,0 +1,120 @@
1
+ function asString(value) {
2
+ return typeof value === "string" ? value : String(value ?? "");
3
+ }
4
+ function asNullableString(value) {
5
+ return value == null ? null : asString(value);
6
+ }
7
+ function asBoolean(value) {
8
+ return value === true || value === 1 || value === "1";
9
+ }
10
+ function nowIso() {
11
+ return new Date(Date.now()).toISOString();
12
+ }
13
+ export class SqliteRunQueueStore {
14
+ db;
15
+ constructor(db) {
16
+ this.db = db;
17
+ }
18
+ mapQueuedRun(row) {
19
+ return {
20
+ runId: asString(row.run_id),
21
+ threadId: asString(row.thread_id),
22
+ priority: Number(row.priority ?? 0),
23
+ queueKey: asNullableString(row.queue_key),
24
+ enqueuedAt: asString(row.enqueued_at),
25
+ availableAt: asString(row.available_at),
26
+ claimedBy: asNullableString(row.claimed_by),
27
+ claimedAt: asNullableString(row.claimed_at),
28
+ leaseExpiresAt: asNullableString(row.lease_expires_at),
29
+ attemptCount: Number(row.attempt_count ?? 0),
30
+ lastError: asNullableString(row.last_error),
31
+ };
32
+ }
33
+ mapRunControl(row) {
34
+ return {
35
+ runId: asString(row.run_id),
36
+ cancelRequested: asBoolean(row.cancel_requested),
37
+ cancelReason: asNullableString(row.cancel_reason),
38
+ cancelRequestedAt: asNullableString(row.cancel_requested_at),
39
+ heartbeatAt: asNullableString(row.heartbeat_at),
40
+ workerId: asNullableString(row.worker_id),
41
+ workerStartedAt: asNullableString(row.worker_started_at),
42
+ };
43
+ }
44
+ async enqueueRun(input) {
45
+ const enqueuedAt = nowIso();
46
+ await this.db.execute(`INSERT OR REPLACE INTO run_queue
47
+ (run_id, thread_id, priority, queue_key, enqueued_at, available_at, claimed_by, claimed_at, lease_expires_at, attempt_count, last_error)
48
+ VALUES (?, ?, ?, ?, ?, ?, NULL, NULL, NULL, COALESCE((SELECT attempt_count FROM run_queue WHERE run_id = ?), 0), NULL)`, [
49
+ input.runId,
50
+ input.threadId,
51
+ input.priority ?? 0,
52
+ input.queueKey ?? null,
53
+ enqueuedAt,
54
+ input.availableAt ?? enqueuedAt,
55
+ input.runId,
56
+ ]);
57
+ }
58
+ async getQueuedRun(runId) {
59
+ const row = await this.db.selectOne("SELECT * FROM run_queue WHERE run_id = ?", [runId]);
60
+ return row ? this.mapQueuedRun(row) : null;
61
+ }
62
+ async claimQueuedRun(input) {
63
+ const claimedAt = input.claimedAt ?? nowIso();
64
+ await this.db.execute(`UPDATE run_queue
65
+ SET claimed_by = ?, claimed_at = ?, lease_expires_at = ?, attempt_count = attempt_count + 1
66
+ WHERE run_id = ? AND thread_id = ?`, [input.workerId, claimedAt, input.leaseExpiresAt, input.runId, input.threadId]);
67
+ await this.db.execute(`UPDATE run_control
68
+ SET heartbeat_at = ?, worker_id = ?, worker_started_at = COALESCE(worker_started_at, ?)
69
+ WHERE run_id = ?`, [claimedAt, input.workerId, claimedAt, input.runId]);
70
+ const claimed = await this.getQueuedRun(input.runId);
71
+ if (!claimed) {
72
+ throw new Error(`Missing queued run ${input.runId}`);
73
+ }
74
+ return claimed;
75
+ }
76
+ async renewRunLease(input) {
77
+ const heartbeatAt = input.heartbeatAt ?? nowIso();
78
+ await this.db.execute(`UPDATE run_queue
79
+ SET lease_expires_at = ?, claimed_by = COALESCE(claimed_by, ?), claimed_at = COALESCE(claimed_at, ?)
80
+ WHERE run_id = ?`, [input.leaseExpiresAt, input.workerId, heartbeatAt, input.runId]);
81
+ await this.db.execute(`UPDATE run_control
82
+ SET heartbeat_at = ?, worker_id = ?, worker_started_at = COALESCE(worker_started_at, ?)
83
+ WHERE run_id = ?`, [heartbeatAt, input.workerId, heartbeatAt, input.runId]);
84
+ }
85
+ async releaseRunClaim(runId) {
86
+ await this.db.execute("DELETE FROM run_queue WHERE run_id = ?", [runId]);
87
+ await this.db.execute(`UPDATE run_control
88
+ SET heartbeat_at = NULL, worker_id = NULL, worker_started_at = NULL
89
+ WHERE run_id = ?`, [runId]);
90
+ }
91
+ async listExpiredClaimedRuns(cutoffIso) {
92
+ const rows = await this.db.selectAll(`SELECT *
93
+ FROM run_queue
94
+ WHERE claimed_by IS NOT NULL
95
+ AND lease_expires_at IS NOT NULL
96
+ AND lease_expires_at <= ?
97
+ ORDER BY lease_expires_at ASC, run_id ASC`, [cutoffIso]);
98
+ return rows.map((row) => this.mapQueuedRun(row));
99
+ }
100
+ async getRunControl(runId) {
101
+ const row = await this.db.selectOne("SELECT * FROM run_control WHERE run_id = ?", [runId]);
102
+ return row ? this.mapRunControl(row) : null;
103
+ }
104
+ async requestRunCancel(runId, reason) {
105
+ const requestedAt = nowIso();
106
+ await this.db.execute(`UPDATE run_control
107
+ SET cancel_requested = 1, cancel_reason = ?, cancel_requested_at = ?
108
+ WHERE run_id = ?`, [reason ?? null, requestedAt, runId]);
109
+ const updated = await this.getRunControl(runId);
110
+ if (!updated) {
111
+ throw new Error(`Missing run control for ${runId}`);
112
+ }
113
+ return updated;
114
+ }
115
+ async clearRunCancel(runId) {
116
+ await this.db.execute(`UPDATE run_control
117
+ SET cancel_requested = 0, cancel_reason = NULL, cancel_requested_at = NULL
118
+ WHERE run_id = ?`, [runId]);
119
+ }
120
+ }
@@ -4,6 +4,8 @@ export declare function listProtectedCheckpointThreadIds(dbPath: string): Promis
4
4
  export declare class SqlitePersistence implements RuntimePersistence {
5
5
  private readonly runRoot;
6
6
  private readonly dbPath;
7
+ private readonly runContextStore;
8
+ private readonly runQueueStore;
7
9
  private client;
8
10
  private initialized;
9
11
  private initialization;
@@ -77,8 +79,6 @@ export declare class SqlitePersistence implements RuntimePersistence {
77
79
  saveRecoveryIntent(threadId: string, runId: string, intent: RecoveryIntent): Promise<void>;
78
80
  getRecoveryIntent(threadId: string, runId: string): Promise<RecoveryIntent | null>;
79
81
  clearRecoveryIntent(threadId: string, runId: string): Promise<void>;
80
- private mapQueuedRun;
81
- private mapRunControl;
82
82
  enqueueRun(input: {
83
83
  threadId: string;
84
84
  runId: string;
@@ -2,6 +2,8 @@ import path from "node:path";
2
2
  import { mkdir, rm } from "node:fs/promises";
3
3
  import { createClient } from "@libsql/client";
4
4
  import { fileExists, readJson, writeJson } from "../utils/fs.js";
5
+ import { SqliteRunContextStore } from "./sqlite-run-context-store.js";
6
+ import { SqliteRunQueueStore } from "./sqlite-run-queue-store.js";
5
7
  const RUNTIME_SQLITE_SCHEMA_VERSION = 2;
6
8
  const RUNTIME_SQLITE_SCHEMA_FAMILY = "agent-harness-runtime";
7
9
  function asRow(value) {
@@ -53,12 +55,24 @@ export async function listProtectedCheckpointThreadIds(dbPath) {
53
55
  export class SqlitePersistence {
54
56
  runRoot;
55
57
  dbPath;
58
+ runContextStore;
59
+ runQueueStore;
56
60
  client = null;
57
61
  initialized = false;
58
62
  initialization = null;
59
63
  constructor(runRoot, dbFile = "runtime.sqlite") {
60
64
  this.runRoot = runRoot;
61
65
  this.dbPath = path.join(runRoot, dbFile);
66
+ this.runContextStore = new SqliteRunContextStore({
67
+ execute: (sql, args) => this.execute(sql, args),
68
+ selectOne: (sql, args) => this.selectOne(sql, args),
69
+ selectAll: (sql, args) => this.selectAll(sql, args),
70
+ });
71
+ this.runQueueStore = new SqliteRunQueueStore({
72
+ execute: (sql, args) => this.execute(sql, args),
73
+ selectOne: (sql, args) => this.selectOne(sql, args),
74
+ selectAll: (sql, args) => this.selectAll(sql, args),
75
+ });
62
76
  }
63
77
  async getClient() {
64
78
  if (!this.client) {
@@ -597,16 +611,13 @@ export class SqlitePersistence {
597
611
  return true;
598
612
  }
599
613
  async saveRunRequest(threadId, runId, request) {
600
- await this.execute(`INSERT OR REPLACE INTO run_requests
601
- (run_id, thread_id, request_json, saved_at)
602
- VALUES (?, ?, ?, ?)`, [runId, threadId, JSON.stringify(request), request.savedAt]);
614
+ await this.runContextStore.saveRunRequest(threadId, runId, request);
603
615
  }
604
616
  async getRunRequest(threadId, runId) {
605
- const row = await this.selectOne("SELECT request_json FROM run_requests WHERE thread_id = ? AND run_id = ?", [threadId, runId]);
606
- return row ? parseJson(row.request_json) : null;
617
+ return this.runContextStore.getRunRequest(threadId, runId);
607
618
  }
608
619
  async clearRunRequest(threadId, runId) {
609
- await this.execute("DELETE FROM run_requests WHERE thread_id = ? AND run_id = ?", [threadId, runId]);
620
+ await this.runContextStore.clearRunRequest(threadId, runId);
610
621
  }
611
622
  async createApproval(record) {
612
623
  await this.execute(`INSERT OR REPLACE INTO approvals
@@ -666,143 +677,46 @@ export class SqlitePersistence {
666
677
  };
667
678
  }
668
679
  async appendThreadMessage(threadId, message) {
669
- await this.execute(`INSERT INTO thread_messages
670
- (thread_id, role, content_json, run_id, created_at)
671
- VALUES (?, ?, ?, ?, ?)`, [threadId, message.role, JSON.stringify(message.content), message.runId, message.createdAt]);
680
+ await this.runContextStore.appendThreadMessage(threadId, message);
672
681
  }
673
682
  async listThreadMessages(threadId, limit = 12) {
674
- const rows = await this.selectAll(`SELECT role, content_json, run_id, created_at
675
- FROM (
676
- SELECT role, content_json, run_id, created_at, id
677
- FROM thread_messages
678
- WHERE thread_id = ?
679
- ORDER BY created_at DESC, id DESC
680
- LIMIT ?
681
- ) recent
682
- ORDER BY created_at ASC, id ASC`, [threadId, limit]);
683
- return rows.map((row) => ({
684
- role: asString(row.role),
685
- content: parseJson(row.content_json),
686
- runId: asString(row.run_id),
687
- createdAt: asString(row.created_at),
688
- }));
683
+ return this.runContextStore.listThreadMessages(threadId, limit);
689
684
  }
690
685
  async saveRecoveryIntent(threadId, runId, intent) {
691
- const savedAt = typeof intent.savedAt === "string"
692
- ? (intent.savedAt)
693
- : nowIso();
694
- await this.execute(`INSERT OR REPLACE INTO recovery_intents
695
- (run_id, thread_id, intent_json, saved_at)
696
- VALUES (?, ?, ?, ?)`, [runId, threadId, JSON.stringify(intent), savedAt]);
686
+ await this.runContextStore.saveRecoveryIntent(threadId, runId, intent);
697
687
  }
698
688
  async getRecoveryIntent(threadId, runId) {
699
- const row = await this.selectOne("SELECT intent_json FROM recovery_intents WHERE thread_id = ? AND run_id = ?", [threadId, runId]);
700
- return row ? parseJson(row.intent_json) : null;
689
+ return this.runContextStore.getRecoveryIntent(threadId, runId);
701
690
  }
702
691
  async clearRecoveryIntent(threadId, runId) {
703
- await this.execute("DELETE FROM recovery_intents WHERE thread_id = ? AND run_id = ?", [threadId, runId]);
704
- }
705
- mapQueuedRun(row) {
706
- return {
707
- runId: asString(row.run_id),
708
- threadId: asString(row.thread_id),
709
- priority: Number(row.priority ?? 0),
710
- queueKey: asNullableString(row.queue_key),
711
- enqueuedAt: asString(row.enqueued_at),
712
- availableAt: asString(row.available_at),
713
- claimedBy: asNullableString(row.claimed_by),
714
- claimedAt: asNullableString(row.claimed_at),
715
- leaseExpiresAt: asNullableString(row.lease_expires_at),
716
- attemptCount: Number(row.attempt_count ?? 0),
717
- lastError: asNullableString(row.last_error),
718
- };
719
- }
720
- mapRunControl(row) {
721
- return {
722
- runId: asString(row.run_id),
723
- cancelRequested: asBoolean(row.cancel_requested),
724
- cancelReason: asNullableString(row.cancel_reason),
725
- cancelRequestedAt: asNullableString(row.cancel_requested_at),
726
- heartbeatAt: asNullableString(row.heartbeat_at),
727
- workerId: asNullableString(row.worker_id),
728
- workerStartedAt: asNullableString(row.worker_started_at),
729
- };
692
+ await this.runContextStore.clearRecoveryIntent(threadId, runId);
730
693
  }
731
694
  async enqueueRun(input) {
732
- const enqueuedAt = nowIso();
733
- await this.execute(`INSERT OR REPLACE INTO run_queue
734
- (run_id, thread_id, priority, queue_key, enqueued_at, available_at, claimed_by, claimed_at, lease_expires_at, attempt_count, last_error)
735
- VALUES (?, ?, ?, ?, ?, ?, NULL, NULL, NULL, COALESCE((SELECT attempt_count FROM run_queue WHERE run_id = ?), 0), NULL)`, [
736
- input.runId,
737
- input.threadId,
738
- input.priority ?? 0,
739
- input.queueKey ?? null,
740
- enqueuedAt,
741
- input.availableAt ?? enqueuedAt,
742
- input.runId,
743
- ]);
695
+ await this.runQueueStore.enqueueRun(input);
744
696
  }
745
697
  async getQueuedRun(runId) {
746
- const row = await this.selectOne("SELECT * FROM run_queue WHERE run_id = ?", [runId]);
747
- return row ? this.mapQueuedRun(row) : null;
698
+ return this.runQueueStore.getQueuedRun(runId);
748
699
  }
749
700
  async claimQueuedRun(input) {
750
- const claimedAt = input.claimedAt ?? nowIso();
751
- await this.execute(`UPDATE run_queue
752
- SET claimed_by = ?, claimed_at = ?, lease_expires_at = ?, attempt_count = attempt_count + 1
753
- WHERE run_id = ? AND thread_id = ?`, [input.workerId, claimedAt, input.leaseExpiresAt, input.runId, input.threadId]);
754
- await this.execute(`UPDATE run_control
755
- SET heartbeat_at = ?, worker_id = ?, worker_started_at = COALESCE(worker_started_at, ?)
756
- WHERE run_id = ?`, [claimedAt, input.workerId, claimedAt, input.runId]);
757
- const claimed = await this.getQueuedRun(input.runId);
758
- if (!claimed) {
759
- throw new Error(`Missing queued run ${input.runId}`);
760
- }
761
- return claimed;
701
+ return this.runQueueStore.claimQueuedRun(input);
762
702
  }
763
703
  async renewRunLease(input) {
764
- const heartbeatAt = input.heartbeatAt ?? nowIso();
765
- await this.execute(`UPDATE run_queue
766
- SET lease_expires_at = ?, claimed_by = COALESCE(claimed_by, ?), claimed_at = COALESCE(claimed_at, ?)
767
- WHERE run_id = ?`, [input.leaseExpiresAt, input.workerId, heartbeatAt, input.runId]);
768
- await this.execute(`UPDATE run_control
769
- SET heartbeat_at = ?, worker_id = ?, worker_started_at = COALESCE(worker_started_at, ?)
770
- WHERE run_id = ?`, [heartbeatAt, input.workerId, heartbeatAt, input.runId]);
704
+ await this.runQueueStore.renewRunLease(input);
771
705
  }
772
706
  async releaseRunClaim(runId) {
773
- await this.execute("DELETE FROM run_queue WHERE run_id = ?", [runId]);
774
- await this.execute(`UPDATE run_control
775
- SET heartbeat_at = NULL, worker_id = NULL, worker_started_at = NULL
776
- WHERE run_id = ?`, [runId]);
707
+ await this.runQueueStore.releaseRunClaim(runId);
777
708
  }
778
709
  async listExpiredClaimedRuns(cutoffIso) {
779
- const rows = await this.selectAll(`SELECT *
780
- FROM run_queue
781
- WHERE claimed_by IS NOT NULL
782
- AND lease_expires_at IS NOT NULL
783
- AND lease_expires_at <= ?
784
- ORDER BY lease_expires_at ASC, run_id ASC`, [cutoffIso]);
785
- return rows.map((row) => this.mapQueuedRun(row));
710
+ return this.runQueueStore.listExpiredClaimedRuns(cutoffIso);
786
711
  }
787
712
  async getRunControl(runId) {
788
- const row = await this.selectOne("SELECT * FROM run_control WHERE run_id = ?", [runId]);
789
- return row ? this.mapRunControl(row) : null;
713
+ return this.runQueueStore.getRunControl(runId);
790
714
  }
791
715
  async requestRunCancel(runId, reason) {
792
- const now = nowIso();
793
- await this.execute(`UPDATE run_control
794
- SET cancel_requested = 1, cancel_reason = ?, cancel_requested_at = ?
795
- WHERE run_id = ?`, [reason ?? null, now, runId]);
796
- const updated = await this.getRunControl(runId);
797
- if (!updated) {
798
- throw new Error(`Missing run control for ${runId}`);
799
- }
800
- return updated;
716
+ return this.runQueueStore.requestRunCancel(runId, reason);
801
717
  }
802
718
  async clearRunCancel(runId) {
803
- await this.execute(`UPDATE run_control
804
- SET cancel_requested = 0, cancel_reason = NULL, cancel_requested_at = NULL
805
- WHERE run_id = ?`, [runId]);
719
+ await this.runQueueStore.clearRunCancel(runId);
806
720
  }
807
721
  async readArtifact(threadId, runId, artifactPath) {
808
722
  const filePath = path.join(this.runDir(threadId, runId), artifactPath);
@@ -0,0 +1,21 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import type { RuntimeAdapterOptions, WorkspaceBundle } from "../contracts/types.js";
3
+ export type McpServerConfig = {
4
+ transport?: "stdio" | "http" | "sse" | "websocket";
5
+ command?: string;
6
+ args?: string[];
7
+ env?: Record<string, string>;
8
+ cwd?: string;
9
+ url?: string;
10
+ token?: string;
11
+ headers?: Record<string, string>;
12
+ };
13
+ export type McpToolDescriptor = {
14
+ name: string;
15
+ description?: string;
16
+ inputSchema?: Record<string, unknown>;
17
+ };
18
+ export declare function readMcpServerConfig(workspace: WorkspaceBundle, tool: WorkspaceBundle["tools"] extends Map<any, infer T> ? T : never): McpServerConfig | null;
19
+ export declare function getOrCreateMcpClient(config: McpServerConfig): Promise<Client>;
20
+ export declare function listRemoteMcpTools(config: McpServerConfig): Promise<McpToolDescriptor[]>;
21
+ export declare function createMcpToolResolver(workspace: WorkspaceBundle): NonNullable<RuntimeAdapterOptions["toolResolver"]>;