@botbotgo/agent-harness 0.0.101 → 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 (29) 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/stream-runtime.d.ts +46 -0
  16. package/dist/runtime/adapter/stream-runtime.js +93 -0
  17. package/dist/runtime/agent-runtime-adapter.js +93 -168
  18. package/dist/runtime/harness/run/run-operations.d.ts +50 -0
  19. package/dist/runtime/harness/run/run-operations.js +113 -0
  20. package/dist/runtime/harness/run/run-slot-acquisition.d.ts +64 -0
  21. package/dist/runtime/harness/run/run-slot-acquisition.js +157 -0
  22. package/dist/runtime/harness/run/stream-run.d.ts +53 -0
  23. package/dist/runtime/harness/run/stream-run.js +304 -0
  24. package/dist/runtime/harness.js +79 -528
  25. package/dist/workspace/object-loader.d.ts +1 -8
  26. package/dist/workspace/object-loader.js +3 -197
  27. package/dist/workspace/yaml-object-reader.d.ts +15 -0
  28. package/dist/workspace/yaml-object-reader.js +202 -0
  29. package/package.json +1 -1
@@ -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 {};
@@ -0,0 +1,304 @@
1
+ import { AGENT_INTERRUPT_SENTINEL_PREFIX, RuntimeOperationTimeoutError } from "../../agent-runtime-adapter.js";
2
+ import { normalizeUpstreamRuntimeEvent } from "../../parsing/stream-event-parsing.js";
3
+ import { renderRuntimeFailure, renderToolFailure } from "../../support/harness-support.js";
4
+ import { createContentBlocksItem, createToolResultKey, emitOutputDeltaAndCreateItem, } from "../events/streaming.js";
5
+ function normalizeStreamChunk(chunk) {
6
+ if (typeof chunk === "string") {
7
+ if (chunk.startsWith(AGENT_INTERRUPT_SENTINEL_PREFIX)) {
8
+ return { kind: "interrupt", content: chunk.slice(AGENT_INTERRUPT_SENTINEL_PREFIX.length) };
9
+ }
10
+ return { kind: "content", content: chunk };
11
+ }
12
+ if (chunk.kind === "upstream-event") {
13
+ return { kind: "upstream-event", event: (chunk.event ?? {}) };
14
+ }
15
+ if (chunk.kind === "interrupt") {
16
+ return { kind: "interrupt", content: chunk.content };
17
+ }
18
+ if (chunk.kind === "reasoning") {
19
+ return { kind: "reasoning", content: chunk.content ?? "" };
20
+ }
21
+ if (chunk.kind === "step") {
22
+ return { kind: "step", content: chunk.content ?? "" };
23
+ }
24
+ if (chunk.kind === "tool-result") {
25
+ return {
26
+ kind: "tool-result",
27
+ toolName: chunk.toolName ?? "unknown_tool",
28
+ output: chunk.output,
29
+ isError: chunk.isError,
30
+ };
31
+ }
32
+ return { kind: "content", content: chunk.content ?? "" };
33
+ }
34
+ function isUpstreamRuntimeEvent(event) {
35
+ return event.format === "langgraph-v2" && "normalized" in event && "streamPart" in event;
36
+ }
37
+ export async function* streamHarnessRun(options) {
38
+ const priorHistoryPromise = Promise.resolve(options.isNewThread ? [] : undefined).then((historyHint) => historyHint ?? options.loadPriorHistory(options.threadId, options.runId));
39
+ yield { type: "event", event: await options.runCreatedEventPromise };
40
+ let releaseRunSlot = async () => undefined;
41
+ let emitted = false;
42
+ let streamActivityObserved = false;
43
+ const emitOutputDelta = (content) => emitOutputDeltaAndCreateItem(options.emit, options.threadId, options.runId, options.selectedAgentId, content);
44
+ try {
45
+ const [priorHistory, acquiredReleaseRunSlot] = await Promise.all([
46
+ priorHistoryPromise,
47
+ options.releaseRunSlotPromise,
48
+ ]).then(([loadedPriorHistory, resolvedReleaseRunSlot]) => [loadedPriorHistory, resolvedReleaseRunSlot]);
49
+ releaseRunSlot = acquiredReleaseRunSlot;
50
+ let assistantOutput = "";
51
+ const toolErrors = [];
52
+ let lastToolResultKey = null;
53
+ for await (const rawChunk of options.stream(options.binding, options.input, options.threadId, priorHistory, {
54
+ context: options.invocation.context,
55
+ state: options.invocation.state,
56
+ files: options.invocation.files,
57
+ runId: options.runId,
58
+ })) {
59
+ if (!rawChunk) {
60
+ continue;
61
+ }
62
+ streamActivityObserved = true;
63
+ const normalizedChunk = normalizeStreamChunk(rawChunk);
64
+ if (normalizedChunk.kind === "upstream-event") {
65
+ const rawEvent = normalizedChunk.event;
66
+ yield {
67
+ type: "upstream-event",
68
+ threadId: options.threadId,
69
+ runId: options.runId,
70
+ agentId: options.selectedAgentId,
71
+ event: isUpstreamRuntimeEvent(rawEvent)
72
+ ? rawEvent
73
+ : normalizeUpstreamRuntimeEvent(rawEvent.raw ?? rawEvent),
74
+ };
75
+ continue;
76
+ }
77
+ if (normalizedChunk.kind === "interrupt") {
78
+ const checkpointRef = `checkpoints/${options.threadId}/${options.runId}/cp-1`;
79
+ const waitingEvent = await options.setRunStateAndEmit(options.threadId, options.runId, 6, "waiting_for_approval", {
80
+ previousState: "running",
81
+ checkpointRef,
82
+ });
83
+ const approvalRequest = await options.requestApprovalAndEmit(options.threadId, options.runId, options.input, normalizedChunk.content, checkpointRef, 7);
84
+ yield { type: "event", event: waitingEvent };
85
+ yield { type: "event", event: approvalRequest.event };
86
+ yield {
87
+ type: "result",
88
+ result: {
89
+ threadId: options.threadId,
90
+ runId: options.runId,
91
+ agentId: options.selectedAgentId,
92
+ state: "waiting_for_approval",
93
+ output: assistantOutput,
94
+ finalMessageText: assistantOutput,
95
+ interruptContent: normalizedChunk.content,
96
+ approvalId: approvalRequest.approval.approvalId,
97
+ pendingActionId: approvalRequest.approval.pendingActionId,
98
+ },
99
+ };
100
+ return;
101
+ }
102
+ if (normalizedChunk.kind === "reasoning") {
103
+ await options.emit(options.threadId, options.runId, 3, "reasoning.delta", {
104
+ content: normalizedChunk.content,
105
+ });
106
+ yield {
107
+ type: "reasoning",
108
+ threadId: options.threadId,
109
+ runId: options.runId,
110
+ agentId: options.selectedAgentId,
111
+ content: normalizedChunk.content,
112
+ };
113
+ continue;
114
+ }
115
+ if (normalizedChunk.kind === "step") {
116
+ yield {
117
+ type: "step",
118
+ threadId: options.threadId,
119
+ runId: options.runId,
120
+ agentId: options.selectedAgentId,
121
+ content: normalizedChunk.content,
122
+ };
123
+ continue;
124
+ }
125
+ if (normalizedChunk.kind === "tool-result") {
126
+ const toolResultKey = createToolResultKey(normalizedChunk.toolName, normalizedChunk.output, normalizedChunk.isError);
127
+ if (toolResultKey === lastToolResultKey) {
128
+ continue;
129
+ }
130
+ lastToolResultKey = toolResultKey;
131
+ if (normalizedChunk.isError) {
132
+ toolErrors.push(renderToolFailure(normalizedChunk.toolName, normalizedChunk.output));
133
+ }
134
+ yield {
135
+ type: "tool-result",
136
+ threadId: options.threadId,
137
+ runId: options.runId,
138
+ agentId: options.selectedAgentId,
139
+ toolName: normalizedChunk.toolName,
140
+ output: normalizedChunk.output,
141
+ isError: normalizedChunk.isError,
142
+ };
143
+ continue;
144
+ }
145
+ emitted = true;
146
+ assistantOutput += normalizedChunk.content;
147
+ yield await emitOutputDelta(normalizedChunk.content);
148
+ }
149
+ if (!assistantOutput && toolErrors.length > 0) {
150
+ assistantOutput = toolErrors.join("\n\n");
151
+ emitted = true;
152
+ yield await emitOutputDelta(assistantOutput);
153
+ }
154
+ if (!assistantOutput) {
155
+ const actual = await options.invokeWithHistory(options.binding, options.input, options.threadId, options.runId);
156
+ if (Array.isArray(actual.contentBlocks) && actual.contentBlocks.length > 0) {
157
+ yield createContentBlocksItem(options.threadId, options.runId, options.selectedAgentId, actual.contentBlocks);
158
+ }
159
+ if (actual.output) {
160
+ assistantOutput = actual.output;
161
+ emitted = true;
162
+ yield await emitOutputDelta(actual.output);
163
+ }
164
+ }
165
+ await options.appendAssistantMessage(options.threadId, options.runId, assistantOutput);
166
+ yield {
167
+ type: "result",
168
+ result: {
169
+ threadId: options.threadId,
170
+ runId: options.runId,
171
+ agentId: options.selectedAgentId,
172
+ state: "completed",
173
+ output: assistantOutput,
174
+ finalMessageText: assistantOutput,
175
+ },
176
+ };
177
+ yield {
178
+ type: "event",
179
+ event: await options.setRunStateAndEmit(options.threadId, options.runId, 6, "completed", {
180
+ previousState: "running",
181
+ }),
182
+ };
183
+ }
184
+ catch (error) {
185
+ if (emitted || streamActivityObserved) {
186
+ const runtimeFailure = renderRuntimeFailure(error);
187
+ yield {
188
+ type: "event",
189
+ event: await options.setRunStateAndEmit(options.threadId, options.runId, 6, "failed", {
190
+ previousState: "running",
191
+ error: error instanceof Error ? error.message : String(error),
192
+ }),
193
+ };
194
+ yield {
195
+ type: "content",
196
+ threadId: options.threadId,
197
+ runId: options.runId,
198
+ agentId: options.selectedAgentId,
199
+ content: runtimeFailure,
200
+ };
201
+ yield {
202
+ type: "result",
203
+ result: {
204
+ threadId: options.threadId,
205
+ runId: options.runId,
206
+ agentId: options.selectedAgentId,
207
+ state: "failed",
208
+ output: runtimeFailure,
209
+ finalMessageText: runtimeFailure,
210
+ },
211
+ };
212
+ return;
213
+ }
214
+ if (error instanceof RuntimeOperationTimeoutError && error.stage === "invoke") {
215
+ const runtimeFailure = renderRuntimeFailure(error);
216
+ yield {
217
+ type: "event",
218
+ event: await options.setRunStateAndEmit(options.threadId, options.runId, 6, "failed", {
219
+ previousState: "running",
220
+ error: error.message,
221
+ }),
222
+ };
223
+ yield {
224
+ type: "content",
225
+ threadId: options.threadId,
226
+ runId: options.runId,
227
+ agentId: options.selectedAgentId,
228
+ content: runtimeFailure,
229
+ };
230
+ yield {
231
+ type: "result",
232
+ result: {
233
+ threadId: options.threadId,
234
+ runId: options.runId,
235
+ agentId: options.selectedAgentId,
236
+ state: "failed",
237
+ output: runtimeFailure,
238
+ finalMessageText: runtimeFailure,
239
+ },
240
+ };
241
+ return;
242
+ }
243
+ try {
244
+ const actual = await options.invokeWithHistory(options.binding, options.input, options.threadId, options.runId);
245
+ await options.appendAssistantMessage(options.threadId, options.runId, actual.output);
246
+ if (Array.isArray(actual.contentBlocks) && actual.contentBlocks.length > 0) {
247
+ yield createContentBlocksItem(options.threadId, options.runId, options.selectedAgentId, actual.contentBlocks);
248
+ }
249
+ if (actual.output) {
250
+ yield await emitOutputDelta(actual.output);
251
+ }
252
+ yield {
253
+ type: "result",
254
+ result: {
255
+ ...actual,
256
+ threadId: options.threadId,
257
+ runId: options.runId,
258
+ agentId: options.selectedAgentId,
259
+ },
260
+ };
261
+ yield {
262
+ type: "event",
263
+ event: await options.setRunStateAndEmit(options.threadId, options.runId, 6, actual.state, {
264
+ previousState: "running",
265
+ }),
266
+ };
267
+ return;
268
+ }
269
+ catch (invokeError) {
270
+ await options.emitSyntheticFallback(options.threadId, options.runId, options.selectedAgentId, invokeError);
271
+ const runtimeFailure = renderRuntimeFailure(invokeError);
272
+ yield {
273
+ type: "event",
274
+ event: await options.setRunStateAndEmit(options.threadId, options.runId, 6, "failed", {
275
+ previousState: "running",
276
+ error: invokeError instanceof Error ? invokeError.message : String(invokeError),
277
+ }),
278
+ };
279
+ yield {
280
+ type: "content",
281
+ threadId: options.threadId,
282
+ runId: options.runId,
283
+ agentId: options.selectedAgentId,
284
+ content: runtimeFailure,
285
+ };
286
+ yield {
287
+ type: "result",
288
+ result: {
289
+ threadId: options.threadId,
290
+ runId: options.runId,
291
+ agentId: options.selectedAgentId,
292
+ state: "failed",
293
+ output: runtimeFailure,
294
+ finalMessageText: runtimeFailure,
295
+ },
296
+ };
297
+ return;
298
+ }
299
+ }
300
+ finally {
301
+ await options.clearRunRequest(options.threadId, options.runId);
302
+ await releaseRunSlot();
303
+ }
304
+ }