@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
@@ -0,0 +1,139 @@
1
+ import { normalizeRunPriority } from "./helpers.js";
2
+ export async function recoverQueuedStartupRun(context, thread) {
3
+ if (thread.status !== "queued") {
4
+ return false;
5
+ }
6
+ const runMeta = await context.persistence.getRunMeta(thread.threadId, thread.latestRunId);
7
+ const binding = context.getBinding(runMeta.agentId);
8
+ if (!binding) {
9
+ return true;
10
+ }
11
+ const request = await context.persistence.getRunRequest(thread.threadId, thread.latestRunId);
12
+ if (!request) {
13
+ await context.setRunStateAndEmit(thread.threadId, thread.latestRunId, 100, "failed", {
14
+ previousState: "queued",
15
+ error: "missing persisted run request for queued run recovery",
16
+ });
17
+ return true;
18
+ }
19
+ const releaseRunSlot = await context.acquireRunSlot(thread.threadId, thread.latestRunId, "running", normalizeRunPriority(request.priority));
20
+ try {
21
+ await context.executeQueuedRun(binding, request.input, thread.threadId, thread.latestRunId, runMeta.agentId, {
22
+ context: request.invocation?.context,
23
+ state: request.invocation?.inputs,
24
+ files: request.invocation?.attachments,
25
+ previousState: "queued",
26
+ stateSequence: 103,
27
+ approvalSequence: 104,
28
+ });
29
+ }
30
+ finally {
31
+ await releaseRunSlot();
32
+ }
33
+ return true;
34
+ }
35
+ export async function recoverRunningStartupRun(context, thread) {
36
+ if (thread.status !== "running") {
37
+ return false;
38
+ }
39
+ const isStale = await context.isStaleRunningRun(thread);
40
+ if (!isStale) {
41
+ return true;
42
+ }
43
+ const runMeta = await context.persistence.getRunMeta(thread.threadId, thread.latestRunId);
44
+ const binding = context.getBinding(runMeta.agentId);
45
+ if (!binding) {
46
+ return true;
47
+ }
48
+ if (!context.supportsRunningReplay(binding)) {
49
+ await context.setRunStateAndEmit(thread.threadId, thread.latestRunId, 100, "failed", {
50
+ previousState: "running",
51
+ error: "stale running run cannot be replayed safely",
52
+ });
53
+ await context.persistence.releaseRunClaim(thread.latestRunId);
54
+ return true;
55
+ }
56
+ const request = await context.persistence.getRunRequest(thread.threadId, thread.latestRunId);
57
+ if (!request) {
58
+ await context.setRunStateAndEmit(thread.threadId, thread.latestRunId, 100, "failed", {
59
+ previousState: "running",
60
+ error: "missing persisted run request for stale running run recovery",
61
+ });
62
+ await context.persistence.releaseRunClaim(thread.latestRunId);
63
+ return true;
64
+ }
65
+ const releaseRunSlot = await context.acquireRunSlot(thread.threadId, thread.latestRunId, "running", normalizeRunPriority(request.priority));
66
+ try {
67
+ await context.emit(thread.threadId, thread.latestRunId, 100, "run.resumed", {
68
+ resumeKind: "startup-running-recovery",
69
+ state: "running",
70
+ });
71
+ await context.executeQueuedRun(binding, request.input, thread.threadId, thread.latestRunId, runMeta.agentId, {
72
+ context: request.invocation?.context,
73
+ state: request.invocation?.inputs,
74
+ files: request.invocation?.attachments,
75
+ previousState: "running",
76
+ stateSequence: 103,
77
+ approvalSequence: 104,
78
+ });
79
+ }
80
+ finally {
81
+ await releaseRunSlot();
82
+ }
83
+ return true;
84
+ }
85
+ export async function recoverResumingStartupRun(context, thread) {
86
+ if (thread.status !== "resuming" || !context.recoveryConfig.resumeResumingRunsOnStartup) {
87
+ return false;
88
+ }
89
+ const binding = context.getBinding(thread.agentId);
90
+ if (!binding) {
91
+ return true;
92
+ }
93
+ const recoveryIntent = await context.persistence.getRecoveryIntent(thread.threadId, thread.latestRunId);
94
+ if (!recoveryIntent || recoveryIntent.kind !== "approval-decision") {
95
+ return true;
96
+ }
97
+ if (recoveryIntent.attempts >= context.recoveryConfig.maxRecoveryAttempts) {
98
+ await context.persistence.setRunState(thread.threadId, thread.latestRunId, "failed", recoveryIntent.checkpointRef);
99
+ await context.persistence.clearRecoveryIntent(thread.threadId, thread.latestRunId);
100
+ return true;
101
+ }
102
+ await context.persistence.saveRecoveryIntent(thread.threadId, thread.latestRunId, {
103
+ ...recoveryIntent,
104
+ attempts: recoveryIntent.attempts + 1,
105
+ });
106
+ await context.emit(thread.threadId, thread.latestRunId, 100, "run.resumed", {
107
+ resumeKind: "startup-recovery",
108
+ checkpointRef: recoveryIntent.checkpointRef,
109
+ state: "resuming",
110
+ });
111
+ const history = await context.persistence.listThreadMessages(thread.threadId);
112
+ const priorHistory = history.filter((message) => message.runId !== thread.latestRunId);
113
+ const runInput = await context.loadRunInput(thread.threadId, thread.latestRunId);
114
+ const startedAt = Date.now();
115
+ try {
116
+ const actual = await context.runtimeAdapter.invoke(binding, "", thread.threadId, thread.latestRunId, recoveryIntent.resumePayload, priorHistory);
117
+ context.recordLlmSuccess(startedAt);
118
+ await context.persistence.clearRecoveryIntent(thread.threadId, thread.latestRunId);
119
+ await context.finalizeContinuedRun(binding, thread.threadId, thread.latestRunId, runInput, actual, {
120
+ previousState: "resuming",
121
+ stateSequence: 101,
122
+ approvalSequence: 102,
123
+ });
124
+ }
125
+ catch (error) {
126
+ context.recordLlmFailure(startedAt);
127
+ if (recoveryIntent.attempts + 1 >= context.recoveryConfig.maxRecoveryAttempts) {
128
+ await context.persistence.setRunState(thread.threadId, thread.latestRunId, "failed", recoveryIntent.checkpointRef);
129
+ await context.persistence.clearRecoveryIntent(thread.threadId, thread.latestRunId);
130
+ await context.emit(thread.threadId, thread.latestRunId, 101, "run.state.changed", {
131
+ previousState: "resuming",
132
+ state: "failed",
133
+ checkpointRef: recoveryIntent.checkpointRef,
134
+ error: error instanceof Error ? error.message : String(error),
135
+ });
136
+ }
137
+ }
138
+ return true;
139
+ }
@@ -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,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 {};