@botbotgo/agent-harness 0.0.101 → 0.0.103

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 +3 -166
  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/middleware-assembly.d.ts +75 -0
  16. package/dist/runtime/adapter/middleware-assembly.js +175 -0
  17. package/dist/runtime/adapter/runtime-shell.d.ts +27 -0
  18. package/dist/runtime/adapter/runtime-shell.js +168 -0
  19. package/dist/runtime/adapter/stream-runtime.d.ts +46 -0
  20. package/dist/runtime/adapter/stream-runtime.js +93 -0
  21. package/dist/runtime/adapter/tool-resolution.d.ts +14 -0
  22. package/dist/runtime/adapter/tool-resolution.js +57 -0
  23. package/dist/runtime/agent-runtime-adapter.d.ts +1 -6
  24. package/dist/runtime/agent-runtime-adapter.js +140 -495
  25. package/dist/runtime/harness/run/run-operations.d.ts +50 -0
  26. package/dist/runtime/harness/run/run-operations.js +113 -0
  27. package/dist/runtime/harness/run/run-slot-acquisition.d.ts +64 -0
  28. package/dist/runtime/harness/run/run-slot-acquisition.js +157 -0
  29. package/dist/runtime/harness/run/startup-runtime.d.ts +37 -0
  30. package/dist/runtime/harness/run/startup-runtime.js +68 -0
  31. package/dist/runtime/harness/run/stream-run.d.ts +53 -0
  32. package/dist/runtime/harness/run/stream-run.js +304 -0
  33. package/dist/runtime/harness/run/thread-records.d.ts +21 -0
  34. package/dist/runtime/harness/run/thread-records.js +59 -0
  35. package/dist/runtime/harness.js +121 -639
  36. package/dist/workspace/object-loader.d.ts +1 -8
  37. package/dist/workspace/object-loader.js +3 -197
  38. package/dist/workspace/yaml-object-reader.d.ts +15 -0
  39. package/dist/workspace/yaml-object-reader.js +202 -0
  40. package/package.json +1 -1
@@ -0,0 +1,50 @@
1
+ import type { CancelOptions, CompiledAgentBinding, InternalApprovalRecord, MessageContent, ResumeOptions, RunResult, ThreadSummary, TranscriptMessage } from "../../../contracts/types.js";
2
+ import type { RecoveryIntent } from "../../../persistence/types.js";
3
+ type ResumeOperationRuntime = {
4
+ getApprovalById: (approvalId: string) => Promise<InternalApprovalRecord | null>;
5
+ getSession: (threadId: string) => Promise<ThreadSummary | null>;
6
+ resolveApprovalRecord: (options: ResumeOptions, thread: ThreadSummary) => Promise<InternalApprovalRecord>;
7
+ getBinding: (agentId: string) => CompiledAgentBinding | undefined;
8
+ buildResumePayload: (binding: CompiledAgentBinding, approval: InternalApprovalRecord, options: ResumeOptions) => unknown;
9
+ getRunCancellation: (runId: string) => Promise<{
10
+ requested: boolean;
11
+ reason?: string;
12
+ }>;
13
+ finalizeCancelledRun: (threadId: string, runId: string, previousState: RunResult["state"] | null, reason?: string) => Promise<RunResult>;
14
+ setRunState: (threadId: string, runId: string, state: RunResult["state"], checkpointRef: string | null) => Promise<void>;
15
+ acquireRunSlot: (threadId?: string, runId?: string, activeState?: RunResult["state"], priority?: number) => Promise<() => Promise<void>>;
16
+ resolvePersistedRunPriority: (threadId: string, runId: string) => Promise<number>;
17
+ saveRecoveryIntent: (threadId: string, runId: string, payload: RecoveryIntent) => Promise<unknown>;
18
+ emit: (threadId: string, runId: string, sequence: number, eventType: string, payload: Record<string, unknown>) => Promise<unknown>;
19
+ resolveApproval: (threadId: string, runId: string, approvalId: string, resolution: "approved" | "edited" | "rejected" | "expired") => Promise<unknown>;
20
+ listThreadMessages: (threadId: string) => Promise<TranscriptMessage[]>;
21
+ loadRunInput: (threadId: string, runId: string) => Promise<MessageContent>;
22
+ invoke: (binding: CompiledAgentBinding, input: MessageContent, threadId: string, runId: string, resumePayload: unknown, priorHistory: TranscriptMessage[]) => Promise<RunResult>;
23
+ recordLlmSuccess: (startedAt: number) => void;
24
+ recordLlmFailure: (startedAt: number) => void;
25
+ clearRecoveryIntent: (threadId: string, runId: string) => Promise<void>;
26
+ finalizeContinuedRun: (binding: CompiledAgentBinding, threadId: string, runId: string, input: MessageContent, actual: RunResult, options: {
27
+ previousState: RunResult["state"] | null;
28
+ stateSequence: number;
29
+ approvalSequence?: number;
30
+ }) => Promise<RunResult>;
31
+ };
32
+ type CancelOperationRuntime = {
33
+ getRun: (runId: string) => Promise<{
34
+ threadId: string;
35
+ runId: string;
36
+ agentId: string;
37
+ state: RunResult["state"];
38
+ } | null>;
39
+ requestRunCancel: (runId: string, reason?: string) => Promise<unknown>;
40
+ dropPendingRunSlot: (runId: string) => boolean;
41
+ finalizeCancelledRun: (threadId: string, runId: string, previousState: RunResult["state"] | null, reason?: string) => Promise<RunResult>;
42
+ setRunStateAndEmit: (threadId: string, runId: string, sequence: number, state: RunResult["state"], options: {
43
+ previousState: string | null;
44
+ checkpointRef?: string | null;
45
+ error?: string;
46
+ }) => Promise<unknown>;
47
+ };
48
+ export declare function resumeRun(runtime: ResumeOperationRuntime, options: ResumeOptions): Promise<RunResult>;
49
+ export declare function cancelRunOperation(runtime: CancelOperationRuntime, options: CancelOptions): Promise<RunResult>;
50
+ export {};
@@ -0,0 +1,113 @@
1
+ import { isTerminalRunState } from "./helpers.js";
2
+ export async function resumeRun(runtime, options) {
3
+ const approvalById = options.approvalId ? await runtime.getApprovalById(options.approvalId) : null;
4
+ const thread = options.threadId
5
+ ? await runtime.getSession(options.threadId)
6
+ : approvalById
7
+ ? await runtime.getSession(approvalById.threadId)
8
+ : null;
9
+ if (!thread) {
10
+ throw new Error("resume requires either threadId or approvalId");
11
+ }
12
+ const approval = approvalById ?? await runtime.resolveApprovalRecord(options, thread);
13
+ const threadId = approval.threadId;
14
+ const runId = approval.runId;
15
+ const binding = runtime.getBinding(thread.agentId);
16
+ if (!binding) {
17
+ throw new Error(`Unknown agent ${thread.agentId}`);
18
+ }
19
+ const resumePayload = runtime.buildResumePayload(binding, approval, options);
20
+ const cancellation = await runtime.getRunCancellation(runId);
21
+ if (cancellation.requested) {
22
+ return runtime.finalizeCancelledRun(threadId, runId, thread.status, cancellation.reason);
23
+ }
24
+ const checkpointRef = `checkpoints/${threadId}/${runId}/cp-1`;
25
+ await runtime.setRunState(threadId, runId, "resuming", checkpointRef);
26
+ const releaseRunSlot = await runtime.acquireRunSlot(threadId, runId, "resuming", await runtime.resolvePersistedRunPriority(threadId, runId));
27
+ try {
28
+ await runtime.saveRecoveryIntent(threadId, runId, {
29
+ kind: "approval-decision",
30
+ savedAt: new Date().toISOString(),
31
+ checkpointRef,
32
+ resumePayload,
33
+ attempts: 0,
34
+ });
35
+ await runtime.emit(threadId, runId, 5, "run.resumed", {
36
+ resumeKind: "cross-restart",
37
+ checkpointRef,
38
+ state: "resuming",
39
+ approvalId: approval.approvalId,
40
+ pendingActionId: approval.pendingActionId,
41
+ });
42
+ await runtime.resolveApproval(threadId, runId, approval.approvalId, options.decision === "reject" ? "rejected" : options.decision === "edit" ? "edited" : "approved");
43
+ await runtime.emit(threadId, runId, 6, "approval.resolved", {
44
+ approvalId: approval.approvalId,
45
+ pendingActionId: approval.pendingActionId,
46
+ decision: options.decision ?? "approve",
47
+ toolName: approval.toolName,
48
+ });
49
+ const history = await runtime.listThreadMessages(threadId);
50
+ const priorHistory = history.filter((message) => message.runId !== runId);
51
+ const runInput = await runtime.loadRunInput(threadId, runId);
52
+ const startedAt = Date.now();
53
+ try {
54
+ const actual = await runtime.invoke(binding, "", threadId, runId, resumePayload, priorHistory);
55
+ runtime.recordLlmSuccess(startedAt);
56
+ const cancelledAfterInvoke = await runtime.getRunCancellation(runId);
57
+ if (cancelledAfterInvoke.requested) {
58
+ return runtime.finalizeCancelledRun(threadId, runId, "resuming", cancelledAfterInvoke.reason);
59
+ }
60
+ await runtime.clearRecoveryIntent(threadId, runId);
61
+ const finalized = await runtime.finalizeContinuedRun(binding, threadId, runId, runInput, actual, {
62
+ previousState: "resuming",
63
+ stateSequence: 7,
64
+ approvalSequence: 8,
65
+ });
66
+ return {
67
+ ...finalized,
68
+ approvalId: finalized.approvalId ?? approval.approvalId,
69
+ pendingActionId: finalized.pendingActionId ?? approval.pendingActionId,
70
+ };
71
+ }
72
+ catch (error) {
73
+ runtime.recordLlmFailure(startedAt);
74
+ throw error;
75
+ }
76
+ }
77
+ finally {
78
+ await releaseRunSlot();
79
+ }
80
+ }
81
+ export async function cancelRunOperation(runtime, options) {
82
+ const run = await runtime.getRun(options.runId);
83
+ if (!run) {
84
+ throw new Error(`Unknown run ${options.runId}`);
85
+ }
86
+ if (isTerminalRunState(run.state)) {
87
+ return {
88
+ threadId: run.threadId,
89
+ runId: run.runId,
90
+ agentId: run.agentId,
91
+ state: run.state,
92
+ output: run.state,
93
+ };
94
+ }
95
+ await runtime.requestRunCancel(run.runId, options.reason);
96
+ if (run.state === "queued" || run.state === "waiting_for_approval" || run.state === "claimed") {
97
+ if (run.state === "queued") {
98
+ runtime.dropPendingRunSlot(run.runId);
99
+ }
100
+ return runtime.finalizeCancelledRun(run.threadId, run.runId, run.state, options.reason);
101
+ }
102
+ await runtime.setRunStateAndEmit(run.threadId, run.runId, 103, "cancelling", {
103
+ previousState: run.state,
104
+ ...(options.reason ? { error: options.reason } : {}),
105
+ });
106
+ return {
107
+ threadId: run.threadId,
108
+ runId: run.runId,
109
+ agentId: run.agentId,
110
+ state: "cancelling",
111
+ output: options.reason ? `cancelling: ${options.reason}` : "cancelling",
112
+ };
113
+ }
@@ -0,0 +1,64 @@
1
+ import type { RunResult } from "../../../contracts/types.js";
2
+ import { type PendingRunSlot } from "./run-queue.js";
3
+ export type RunSlotConcurrencyConfig = {
4
+ maxConcurrentRuns?: number;
5
+ leaseMs: number;
6
+ heartbeatIntervalMs: number;
7
+ };
8
+ type RunClaimPersistence = {
9
+ claimQueuedRun(payload: {
10
+ threadId: string;
11
+ runId: string;
12
+ workerId: string;
13
+ claimedAt: string;
14
+ leaseExpiresAt: string;
15
+ }): Promise<unknown>;
16
+ renewRunLease(payload: {
17
+ runId: string;
18
+ workerId: string;
19
+ heartbeatAt: string;
20
+ leaseExpiresAt: string;
21
+ }): Promise<unknown>;
22
+ releaseRunClaim(runId: string): Promise<unknown>;
23
+ enqueueRun(payload: {
24
+ threadId: string;
25
+ runId: string;
26
+ priority: number;
27
+ }): Promise<unknown>;
28
+ getRun(runId: string): Promise<{
29
+ state: RunResult["state"];
30
+ } | null>;
31
+ };
32
+ type RunSlotRuntime = {
33
+ persistence: RunClaimPersistence;
34
+ workerId: string;
35
+ concurrencyConfig: RunSlotConcurrencyConfig;
36
+ pendingRunSlots: PendingRunSlot[];
37
+ getActiveRunSlots: () => number;
38
+ setActiveRunSlots: (count: number) => void;
39
+ enqueuePendingRunSlot: (entry: {
40
+ threadId?: string;
41
+ runId?: string;
42
+ priority: number;
43
+ activate: () => void | Promise<void>;
44
+ abort: () => void;
45
+ }) => Array<{
46
+ threadId: string;
47
+ runId: string;
48
+ priority: number;
49
+ queuePosition: number;
50
+ }>;
51
+ emit: (threadId: string, runId: string, sequence: number, eventType: string, payload: Record<string, unknown>) => Promise<unknown>;
52
+ setRunStateAndEmit: (threadId: string, runId: string, sequence: number, state: RunResult["state"], options: {
53
+ previousState: string | null;
54
+ checkpointRef?: string | null;
55
+ error?: string;
56
+ }) => Promise<unknown>;
57
+ };
58
+ export declare function acquireRunSlot(runtime: RunSlotRuntime, options?: {
59
+ threadId?: string;
60
+ runId?: string;
61
+ activeState?: RunResult["state"];
62
+ priority?: number;
63
+ }): Promise<() => Promise<void>>;
64
+ export {};
@@ -0,0 +1,157 @@
1
+ import { shiftNextPendingRunSlot } from "./run-queue.js";
2
+ export async function acquireRunSlot(runtime, options = {}) {
3
+ const { threadId, runId } = options;
4
+ const activeState = options.activeState ?? "running";
5
+ const priority = options.priority ?? 0;
6
+ let stopHeartbeat = () => undefined;
7
+ const beginLease = async (mode) => {
8
+ if (!threadId || !runId) {
9
+ return;
10
+ }
11
+ const claimedAt = new Date().toISOString();
12
+ const leaseExpiresAt = new Date(Date.now() + runtime.concurrencyConfig.leaseMs).toISOString();
13
+ if (mode === "queue-claim") {
14
+ await runtime.persistence.claimQueuedRun({
15
+ threadId,
16
+ runId,
17
+ workerId: runtime.workerId,
18
+ claimedAt,
19
+ leaseExpiresAt,
20
+ });
21
+ }
22
+ else {
23
+ await runtime.persistence.renewRunLease({
24
+ runId,
25
+ workerId: runtime.workerId,
26
+ heartbeatAt: claimedAt,
27
+ leaseExpiresAt,
28
+ });
29
+ }
30
+ if (runtime.concurrencyConfig.heartbeatIntervalMs <= 0) {
31
+ return;
32
+ }
33
+ const timer = setInterval(() => {
34
+ void runtime.persistence.renewRunLease({
35
+ runId,
36
+ workerId: runtime.workerId,
37
+ heartbeatAt: new Date().toISOString(),
38
+ leaseExpiresAt: new Date(Date.now() + runtime.concurrencyConfig.leaseMs).toISOString(),
39
+ });
40
+ }, runtime.concurrencyConfig.heartbeatIntervalMs);
41
+ timer.unref?.();
42
+ stopHeartbeat = () => {
43
+ clearInterval(timer);
44
+ };
45
+ };
46
+ const releaseLease = async () => {
47
+ stopHeartbeat();
48
+ if (runId) {
49
+ await runtime.persistence.releaseRunClaim(runId);
50
+ }
51
+ };
52
+ const releaseAndAdvance = async (shiftNext) => {
53
+ await releaseLease();
54
+ runtime.setActiveRunSlots(Math.max(0, runtime.getActiveRunSlots() - 1));
55
+ const next = shiftNext();
56
+ void next?.activate();
57
+ };
58
+ const maxConcurrentRuns = runtime.concurrencyConfig.maxConcurrentRuns;
59
+ if (!maxConcurrentRuns) {
60
+ await beginLease("direct-heartbeat");
61
+ return async () => {
62
+ await releaseLease();
63
+ };
64
+ }
65
+ const canActivateImmediately = runtime.getActiveRunSlots() < maxConcurrentRuns;
66
+ const useDirectHeartbeatFastPath = canActivateImmediately && maxConcurrentRuns > 1;
67
+ if (canActivateImmediately) {
68
+ runtime.setActiveRunSlots(runtime.getActiveRunSlots() + 1);
69
+ if (threadId && runId && !useDirectHeartbeatFastPath) {
70
+ await runtime.persistence.enqueueRun({ threadId, runId, priority });
71
+ }
72
+ await beginLease(useDirectHeartbeatFastPath ? "direct-heartbeat" : "queue-claim");
73
+ let released = false;
74
+ return async () => {
75
+ if (released) {
76
+ return;
77
+ }
78
+ released = true;
79
+ await releaseAndAdvance(() => shiftNextPendingRunSlot(runtime.pendingRunSlots));
80
+ };
81
+ }
82
+ const activateQueuedRun = async () => {
83
+ const currentRun = runId ? await runtime.persistence.getRun(runId) : null;
84
+ if (currentRun?.state === "cancelled") {
85
+ return "abort";
86
+ }
87
+ runtime.setActiveRunSlots(runtime.getActiveRunSlots() + 1);
88
+ if (threadId && runId) {
89
+ await runtime.emit(threadId, runId, 4, "run.dequeued", {
90
+ queuePosition: 0,
91
+ activeRunCount: runtime.getActiveRunSlots(),
92
+ maxConcurrentRuns,
93
+ priority,
94
+ });
95
+ await runtime.setRunStateAndEmit(threadId, runId, 5, activeState, {
96
+ previousState: "queued",
97
+ });
98
+ await beginLease("queue-claim");
99
+ }
100
+ return "activate";
101
+ };
102
+ if (threadId && runId) {
103
+ await runtime.persistence.enqueueRun({ threadId, runId, priority });
104
+ const slotAcquisition = new Promise((resolve, reject) => {
105
+ const displacedEntries = runtime.enqueuePendingRunSlot({
106
+ threadId,
107
+ runId,
108
+ priority,
109
+ activate: async () => {
110
+ try {
111
+ resolve(await activateQueuedRun());
112
+ }
113
+ catch (error) {
114
+ reject(error);
115
+ }
116
+ },
117
+ abort: () => resolve("abort"),
118
+ });
119
+ void Promise.all(displacedEntries.map((candidate) => runtime.emit(candidate.threadId, candidate.runId, 3, "run.queued", {
120
+ queuePosition: candidate.queuePosition,
121
+ activeRunCount: runtime.getActiveRunSlots(),
122
+ maxConcurrentRuns,
123
+ priority: candidate.priority,
124
+ })));
125
+ });
126
+ const queuePosition = runtime.pendingRunSlots.findIndex((entry) => entry.runId === runId) + 1;
127
+ await runtime.setRunStateAndEmit(threadId, runId, 2, "queued", {
128
+ previousState: activeState,
129
+ });
130
+ await runtime.emit(threadId, runId, 3, "run.queued", {
131
+ queuePosition,
132
+ activeRunCount: runtime.getActiveRunSlots(),
133
+ maxConcurrentRuns,
134
+ priority,
135
+ });
136
+ const slotAcquisitionResult = await slotAcquisition;
137
+ if (slotAcquisitionResult === "abort") {
138
+ return async () => undefined;
139
+ }
140
+ let released = false;
141
+ return async () => {
142
+ if (released) {
143
+ return;
144
+ }
145
+ released = true;
146
+ await releaseAndAdvance(() => runtime.pendingRunSlots.shift());
147
+ };
148
+ }
149
+ let released = false;
150
+ return async () => {
151
+ if (released) {
152
+ return;
153
+ }
154
+ released = true;
155
+ await releaseAndAdvance(() => shiftNextPendingRunSlot(runtime.pendingRunSlots));
156
+ };
157
+ }
@@ -0,0 +1,37 @@
1
+ import type { RunResult, ThreadSummary } from "../../../contracts/types.js";
2
+ import type { RuntimePersistence } from "../../../persistence/types.js";
3
+ import type { ConcurrencyConfig, RecoveryConfig } from "../../../workspace/support/workspace-ref-utils.js";
4
+ import { recoverQueuedStartupRun } from "./recovery.js";
5
+ type Startable = {
6
+ start(): Promise<void>;
7
+ };
8
+ type StartupRecoveryContext = Parameters<typeof recoverQueuedStartupRun>[0];
9
+ export declare function initializeHarnessRuntime(input: {
10
+ persistence: RuntimePersistence;
11
+ checkpointMaintenance: Startable | null;
12
+ runtimeRecordMaintenance: Startable | null;
13
+ healthMonitor: Startable | null;
14
+ recoverStartupRuns: () => Promise<void>;
15
+ }): Promise<void>;
16
+ export declare function recoverStartupRuns(input: {
17
+ recoveryConfig: RecoveryConfig;
18
+ persistence: RuntimePersistence;
19
+ createStartupRecoveryContext: () => StartupRecoveryContext;
20
+ reclaimExpiredClaimedRuns: (nowIso?: string) => Promise<void>;
21
+ }): Promise<void>;
22
+ export declare function reclaimExpiredClaimedRuns(input: {
23
+ persistence: RuntimePersistence;
24
+ setRunStateAndEmit: (threadId: string, runId: string, sequence: number, state: RunResult["state"], options: {
25
+ previousState: string | null;
26
+ checkpointRef?: string | null;
27
+ error?: string;
28
+ }) => Promise<unknown>;
29
+ emit: (threadId: string, runId: string, sequence: number, eventType: string, payload: Record<string, unknown>) => Promise<unknown>;
30
+ concurrencyConfig: ConcurrencyConfig;
31
+ getActiveRunSlots: () => number;
32
+ }, nowIso?: string): Promise<void>;
33
+ export declare function isStaleRunningRun(input: {
34
+ persistence: RuntimePersistence;
35
+ concurrencyConfig: ConcurrencyConfig;
36
+ }, thread: ThreadSummary, nowMs?: number): Promise<boolean>;
37
+ export {};
@@ -0,0 +1,68 @@
1
+ import { recoverQueuedStartupRun, recoverResumingStartupRun, recoverRunningStartupRun, } from "./recovery.js";
2
+ export async function initializeHarnessRuntime(input) {
3
+ await input.persistence.initialize();
4
+ await input.checkpointMaintenance?.start();
5
+ await input.runtimeRecordMaintenance?.start();
6
+ await input.healthMonitor?.start();
7
+ await input.recoverStartupRuns();
8
+ }
9
+ export async function recoverStartupRuns(input) {
10
+ if (!input.recoveryConfig.enabled) {
11
+ return;
12
+ }
13
+ await input.reclaimExpiredClaimedRuns();
14
+ const threads = await input.persistence.listSessions();
15
+ const recoveryContext = input.createStartupRecoveryContext();
16
+ for (const thread of threads) {
17
+ const handled = await recoverQueuedStartupRun(recoveryContext, thread) ||
18
+ await recoverRunningStartupRun(recoveryContext, thread) ||
19
+ await recoverResumingStartupRun(recoveryContext, thread);
20
+ if (handled) {
21
+ continue;
22
+ }
23
+ }
24
+ }
25
+ export async function reclaimExpiredClaimedRuns(input, nowIso = new Date().toISOString()) {
26
+ const expiredClaims = await input.persistence.listExpiredClaimedRuns(nowIso);
27
+ for (const claim of expiredClaims) {
28
+ const thread = await input.persistence.getSession(claim.threadId);
29
+ if (!thread) {
30
+ await input.persistence.releaseRunClaim(claim.runId);
31
+ continue;
32
+ }
33
+ const lifecycle = await input.persistence.getRunLifecycle(claim.threadId, claim.runId);
34
+ if (lifecycle.state === "claimed") {
35
+ await input.persistence.enqueueRun({
36
+ threadId: claim.threadId,
37
+ runId: claim.runId,
38
+ priority: claim.priority,
39
+ queueKey: claim.queueKey,
40
+ availableAt: nowIso,
41
+ });
42
+ await input.setRunStateAndEmit(claim.threadId, claim.runId, 99, "queued", {
43
+ previousState: "claimed",
44
+ });
45
+ await input.emit(claim.threadId, claim.runId, 100, "run.queued", {
46
+ queuePosition: 0,
47
+ activeRunCount: input.getActiveRunSlots(),
48
+ maxConcurrentRuns: input.concurrencyConfig.maxConcurrentRuns,
49
+ recoveredOnStartup: true,
50
+ reclaimReason: "expired-lease",
51
+ });
52
+ continue;
53
+ }
54
+ await input.persistence.releaseRunClaim(claim.runId);
55
+ }
56
+ }
57
+ export async function isStaleRunningRun(input, thread, nowMs = Date.now()) {
58
+ const control = await input.persistence.getRunControl(thread.latestRunId);
59
+ const heartbeatAt = control?.heartbeatAt;
60
+ if (!heartbeatAt) {
61
+ return true;
62
+ }
63
+ const heartbeatAtMs = Date.parse(heartbeatAt);
64
+ if (!Number.isFinite(heartbeatAtMs)) {
65
+ return true;
66
+ }
67
+ return nowMs - heartbeatAtMs >= input.concurrencyConfig.heartbeatTimeoutMs;
68
+ }
@@ -0,0 +1,53 @@
1
+ import type { CompiledAgentBinding, HarnessEvent, HarnessStreamItem, MessageContent, RunResult, TranscriptMessage } from "../../../contracts/types.js";
2
+ type RuntimeStreamChunk = string | {
3
+ kind: "content" | "interrupt" | "reasoning" | "step" | "tool-result" | "upstream-event";
4
+ content?: string;
5
+ toolName?: string;
6
+ output?: unknown;
7
+ isError?: boolean;
8
+ event?: {
9
+ format?: string;
10
+ raw?: unknown;
11
+ } | Record<string, unknown>;
12
+ };
13
+ type StreamRunOptions = {
14
+ binding: CompiledAgentBinding;
15
+ input: MessageContent;
16
+ invocation: {
17
+ context?: Record<string, unknown>;
18
+ state?: Record<string, unknown>;
19
+ files?: Record<string, unknown>;
20
+ };
21
+ threadId: string;
22
+ runId: string;
23
+ selectedAgentId: string;
24
+ isNewThread: boolean;
25
+ runCreatedEventPromise: Promise<HarnessEvent>;
26
+ releaseRunSlotPromise: Promise<() => Promise<void>>;
27
+ loadPriorHistory: (threadId: string, runId: string) => Promise<TranscriptMessage[]>;
28
+ stream: (binding: CompiledAgentBinding, input: MessageContent, threadId: string, priorHistory: TranscriptMessage[], options: {
29
+ context?: Record<string, unknown>;
30
+ state?: Record<string, unknown>;
31
+ files?: Record<string, unknown>;
32
+ runId: string;
33
+ }) => AsyncIterable<RuntimeStreamChunk>;
34
+ invokeWithHistory: (binding: CompiledAgentBinding, input: MessageContent, threadId: string, runId: string) => Promise<RunResult>;
35
+ emit: (threadId: string, runId: string, sequence: number, eventType: string, payload: Record<string, unknown>) => Promise<HarnessEvent>;
36
+ setRunStateAndEmit: (threadId: string, runId: string, sequence: number, state: RunResult["state"], options: {
37
+ previousState: string | null;
38
+ checkpointRef?: string | null;
39
+ error?: string;
40
+ }) => Promise<HarnessEvent>;
41
+ requestApprovalAndEmit: (threadId: string, runId: string, input: MessageContent, interruptContent: string | undefined, checkpointRef: string, sequence: number) => Promise<{
42
+ approval: {
43
+ approvalId: string;
44
+ pendingActionId: string;
45
+ };
46
+ event: HarnessEvent;
47
+ }>;
48
+ appendAssistantMessage: (threadId: string, runId: string, content?: string) => Promise<void>;
49
+ clearRunRequest: (threadId: string, runId: string) => Promise<void>;
50
+ emitSyntheticFallback: (threadId: string, runId: string, selectedAgentId: string, error: unknown) => Promise<void>;
51
+ };
52
+ export declare function streamHarnessRun(options: StreamRunOptions): AsyncGenerator<HarnessStreamItem>;
53
+ export {};