@botbotgo/agent-harness 0.0.1

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 (87) hide show
  1. package/dist/api.d.ts +22 -0
  2. package/dist/api.js +48 -0
  3. package/dist/config/direct.yaml +48 -0
  4. package/dist/config/embedding-model.yaml +30 -0
  5. package/dist/config/model.yaml +44 -0
  6. package/dist/config/orchestra.yaml +92 -0
  7. package/dist/config/runtime.yaml +47 -0
  8. package/dist/config/vector-store.yaml +26 -0
  9. package/dist/contracts/types.d.ts +359 -0
  10. package/dist/contracts/types.js +1 -0
  11. package/dist/extensions.d.ts +16 -0
  12. package/dist/extensions.js +251 -0
  13. package/dist/index.d.ts +7 -0
  14. package/dist/index.js +7 -0
  15. package/dist/persistence/file-store.d.ts +39 -0
  16. package/dist/persistence/file-store.js +282 -0
  17. package/dist/presentation.d.ts +4 -0
  18. package/dist/presentation.js +175 -0
  19. package/dist/runtime/agent-runtime-adapter.d.ts +33 -0
  20. package/dist/runtime/agent-runtime-adapter.js +445 -0
  21. package/dist/runtime/event-bus.d.ts +6 -0
  22. package/dist/runtime/event-bus.js +15 -0
  23. package/dist/runtime/file-checkpoint-saver.d.ts +20 -0
  24. package/dist/runtime/file-checkpoint-saver.js +91 -0
  25. package/dist/runtime/harness.d.ts +57 -0
  26. package/dist/runtime/harness.js +696 -0
  27. package/dist/runtime/index.d.ts +10 -0
  28. package/dist/runtime/index.js +10 -0
  29. package/dist/runtime/inventory.d.ts +25 -0
  30. package/dist/runtime/inventory.js +62 -0
  31. package/dist/runtime/parsing/index.d.ts +2 -0
  32. package/dist/runtime/parsing/index.js +2 -0
  33. package/dist/runtime/parsing/output-parsing.d.ts +12 -0
  34. package/dist/runtime/parsing/output-parsing.js +424 -0
  35. package/dist/runtime/parsing/stream-event-parsing.d.ts +27 -0
  36. package/dist/runtime/parsing/stream-event-parsing.js +161 -0
  37. package/dist/runtime/policy-engine.d.ts +9 -0
  38. package/dist/runtime/policy-engine.js +23 -0
  39. package/dist/runtime/store.d.ts +50 -0
  40. package/dist/runtime/store.js +118 -0
  41. package/dist/runtime/support/embedding-models.d.ts +4 -0
  42. package/dist/runtime/support/embedding-models.js +33 -0
  43. package/dist/runtime/support/harness-support.d.ts +27 -0
  44. package/dist/runtime/support/harness-support.js +116 -0
  45. package/dist/runtime/support/index.d.ts +4 -0
  46. package/dist/runtime/support/index.js +4 -0
  47. package/dist/runtime/support/llamaindex.d.ts +24 -0
  48. package/dist/runtime/support/llamaindex.js +108 -0
  49. package/dist/runtime/support/runtime-factories.d.ts +3 -0
  50. package/dist/runtime/support/runtime-factories.js +39 -0
  51. package/dist/runtime/support/skill-metadata.d.ts +1 -0
  52. package/dist/runtime/support/skill-metadata.js +34 -0
  53. package/dist/runtime/support/vector-stores.d.ts +7 -0
  54. package/dist/runtime/support/vector-stores.js +130 -0
  55. package/dist/runtime/thread-memory-sync.d.ts +14 -0
  56. package/dist/runtime/thread-memory-sync.js +88 -0
  57. package/dist/runtime/tool-hitl.d.ts +5 -0
  58. package/dist/runtime/tool-hitl.js +108 -0
  59. package/dist/utils/fs.d.ts +6 -0
  60. package/dist/utils/fs.js +39 -0
  61. package/dist/utils/id.d.ts +1 -0
  62. package/dist/utils/id.js +8 -0
  63. package/dist/vendor/builtins.d.ts +23 -0
  64. package/dist/vendor/builtins.js +103 -0
  65. package/dist/vendor/sources.d.ts +12 -0
  66. package/dist/vendor/sources.js +115 -0
  67. package/dist/workspace/agent-binding-compiler.d.ts +4 -0
  68. package/dist/workspace/agent-binding-compiler.js +181 -0
  69. package/dist/workspace/compile.d.ts +2 -0
  70. package/dist/workspace/compile.js +107 -0
  71. package/dist/workspace/index.d.ts +6 -0
  72. package/dist/workspace/index.js +6 -0
  73. package/dist/workspace/object-loader.d.ts +16 -0
  74. package/dist/workspace/object-loader.js +405 -0
  75. package/dist/workspace/resource-compilers.d.ts +13 -0
  76. package/dist/workspace/resource-compilers.js +182 -0
  77. package/dist/workspace/support/discovery.d.ts +5 -0
  78. package/dist/workspace/support/discovery.js +108 -0
  79. package/dist/workspace/support/index.d.ts +2 -0
  80. package/dist/workspace/support/index.js +2 -0
  81. package/dist/workspace/support/source-collectors.d.ts +3 -0
  82. package/dist/workspace/support/source-collectors.js +30 -0
  83. package/dist/workspace/support/workspace-ref-utils.d.ts +8 -0
  84. package/dist/workspace/support/workspace-ref-utils.js +50 -0
  85. package/dist/workspace/validate.d.ts +3 -0
  86. package/dist/workspace/validate.js +65 -0
  87. package/package.json +32 -0
@@ -0,0 +1,696 @@
1
+ import { Readable } from "node:stream";
2
+ import { AUTO_AGENT_ID } from "../contracts/types.js";
3
+ import { FilePersistence } from "../persistence/file-store.js";
4
+ import { createPersistentId } from "../utils/id.js";
5
+ import { AGENT_INTERRUPT_SENTINEL_PREFIX, AgentRuntimeAdapter } from "./agent-runtime-adapter.js";
6
+ import { createBuiltinBackendResolver, createBuiltinToolResolver } from "../vendor/builtins.js";
7
+ import { EventBus } from "./event-bus.js";
8
+ import { PolicyEngine } from "./policy-engine.js";
9
+ import { getRoutingSystemPrompt } from "../workspace/support/workspace-ref-utils.js";
10
+ import { createHarnessEvent, createPendingApproval, heuristicRoute, inferRoutingBindings, resolveDeterministicRouteIntent, renderRuntimeFailure, requiresResearchRoute, } from "./support/harness-support.js";
11
+ import { createCheckpointerForConfig, createStoreForConfig } from "./support/runtime-factories.js";
12
+ import { resolveCompiledEmbeddingModel, resolveCompiledEmbeddingModelRef } from "./support/embedding-models.js";
13
+ import { resolveCompiledVectorStore, resolveCompiledVectorStoreRef } from "./support/vector-stores.js";
14
+ import { ThreadMemorySync } from "./thread-memory-sync.js";
15
+ import { FileBackedStore } from "./store.js";
16
+ export class AgentHarness {
17
+ workspace;
18
+ runtimeAdapterOptions;
19
+ eventBus = new EventBus();
20
+ persistence;
21
+ policyEngine = new PolicyEngine();
22
+ runtimeAdapter;
23
+ checkpointers = new Map();
24
+ stores = new Map();
25
+ embeddingModels = new Map();
26
+ vectorStores = new Map();
27
+ defaultStore;
28
+ routingSystemPrompt;
29
+ threadMemorySync;
30
+ unsubscribeThreadMemorySync;
31
+ listHostBindings() {
32
+ return inferRoutingBindings(this.workspace).hostBindings;
33
+ }
34
+ defaultRunRoot() {
35
+ return (this.listHostBindings()[0]?.harnessRuntime.runRoot ??
36
+ `${this.workspace.workspaceRoot}/run-data`);
37
+ }
38
+ heuristicRoute(input) {
39
+ const { primaryBinding, secondaryBinding } = inferRoutingBindings(this.workspace);
40
+ return heuristicRoute(input, primaryBinding, secondaryBinding);
41
+ }
42
+ async buildRoutingInput(input, threadId) {
43
+ if (!threadId) {
44
+ return input;
45
+ }
46
+ const history = await this.persistence.listThreadMessages(threadId);
47
+ const priorHistory = history.filter((message) => message.content.trim());
48
+ if (priorHistory.length === 0) {
49
+ return input;
50
+ }
51
+ const recentTurns = priorHistory.slice(-6).map((message) => {
52
+ const role = message.role === "assistant" ? "assistant" : "user";
53
+ const compact = message.content.replace(/\s+/g, " ").trim();
54
+ return `${role}: ${compact.slice(0, 240)}`;
55
+ });
56
+ return [
57
+ "Recent conversation context:",
58
+ ...recentTurns,
59
+ "",
60
+ `Current user request: ${input}`,
61
+ ].join("\n");
62
+ }
63
+ async resolveSelectedAgentId(input, requestedAgentId, threadId) {
64
+ if (!requestedAgentId || requestedAgentId === AUTO_AGENT_ID) {
65
+ return this.routeAgent(input, { threadId });
66
+ }
67
+ return requestedAgentId;
68
+ }
69
+ resolveStore(binding) {
70
+ const storeConfig = binding?.deepAgentParams?.store ?? binding?.harnessRuntime.store;
71
+ const cacheKey = storeConfig ? JSON.stringify(storeConfig) : undefined;
72
+ if (!storeConfig) {
73
+ return this.defaultStore;
74
+ }
75
+ if (!cacheKey) {
76
+ return this.defaultStore;
77
+ }
78
+ const existing = this.stores.get(cacheKey);
79
+ if (existing) {
80
+ return existing;
81
+ }
82
+ const created = createStoreForConfig(storeConfig, binding?.harnessRuntime.runRoot ?? this.defaultRunRoot());
83
+ this.stores.set(cacheKey, created);
84
+ return created;
85
+ }
86
+ async resolveEmbeddingModel(embeddingModelRef) {
87
+ const compiled = resolveCompiledEmbeddingModelRef(this.workspace, embeddingModelRef);
88
+ const existing = this.embeddingModels.get(compiled.id);
89
+ if (existing) {
90
+ return existing;
91
+ }
92
+ const resolved = await resolveCompiledEmbeddingModel(compiled, this.runtimeAdapterOptions.embeddingModelResolver);
93
+ this.embeddingModels.set(compiled.id, resolved);
94
+ return resolved;
95
+ }
96
+ async resolveVectorStore(vectorStoreRef) {
97
+ const compiled = resolveCompiledVectorStoreRef(this.workspace, vectorStoreRef);
98
+ const existing = this.vectorStores.get(compiled.id);
99
+ if (existing) {
100
+ return existing;
101
+ }
102
+ const resolved = await resolveCompiledVectorStore(this.workspace, compiled, {
103
+ embeddingModelResolver: this.runtimeAdapterOptions.embeddingModelResolver,
104
+ vectorStoreResolver: this.runtimeAdapterOptions.vectorStoreResolver,
105
+ });
106
+ this.vectorStores.set(compiled.id, resolved);
107
+ return resolved;
108
+ }
109
+ constructor(workspace, runtimeAdapterOptions = {}) {
110
+ this.workspace = workspace;
111
+ this.runtimeAdapterOptions = runtimeAdapterOptions;
112
+ const runRoot = this.defaultRunRoot();
113
+ this.persistence = new FilePersistence(runRoot);
114
+ const defaultStoreConfig = this.listHostBindings()[0]?.harnessRuntime.store;
115
+ this.defaultStore = defaultStoreConfig ? createStoreForConfig(defaultStoreConfig, runRoot) : new FileBackedStore(`${runRoot}/store.json`);
116
+ this.runtimeAdapter = new AgentRuntimeAdapter({
117
+ ...runtimeAdapterOptions,
118
+ toolResolver: runtimeAdapterOptions.toolResolver ??
119
+ createBuiltinToolResolver(workspace, {
120
+ getStore: (binding) => this.resolveStore(binding),
121
+ getEmbeddingModel: (embeddingModelRef) => this.resolveEmbeddingModel(embeddingModelRef),
122
+ getVectorStore: (vectorStoreRef) => this.resolveVectorStore(vectorStoreRef),
123
+ }),
124
+ checkpointerResolver: runtimeAdapterOptions.checkpointerResolver ??
125
+ ((binding) => {
126
+ const key = `${binding.harnessRuntime.runRoot}:${JSON.stringify(binding.harnessRuntime.checkpointer ?? { kind: "FileCheckpointer", path: "checkpoints.json" })}`;
127
+ const existing = this.checkpointers.get(key);
128
+ if (existing) {
129
+ return existing;
130
+ }
131
+ const resolvedConfig = binding.harnessRuntime.checkpointer ?? { kind: "FileCheckpointer", path: "checkpoints.json" };
132
+ const saver = createCheckpointerForConfig(resolvedConfig, binding.harnessRuntime.runRoot);
133
+ this.checkpointers.set(key, saver);
134
+ return saver;
135
+ }),
136
+ storeResolver: runtimeAdapterOptions.storeResolver ??
137
+ ((binding) => this.resolveStore(binding)),
138
+ backendResolver: runtimeAdapterOptions.backendResolver ??
139
+ ((binding) => createBuiltinBackendResolver(workspace)(binding)),
140
+ });
141
+ this.routingSystemPrompt = getRoutingSystemPrompt(workspace.refs);
142
+ this.threadMemorySync = new ThreadMemorySync(this.persistence, this.defaultStore);
143
+ this.unsubscribeThreadMemorySync = this.eventBus.subscribe((event) => {
144
+ void this.threadMemorySync.handleEvent(event);
145
+ });
146
+ }
147
+ async initialize() {
148
+ await this.persistence.initialize();
149
+ }
150
+ subscribeEvents(listener) {
151
+ return this.eventBus.subscribe(listener);
152
+ }
153
+ subscribe(listener) {
154
+ return this.subscribeEvents(listener);
155
+ }
156
+ async listSessions(filter) {
157
+ const sessions = await this.persistence.listSessions();
158
+ if (!filter?.agentId) {
159
+ return sessions;
160
+ }
161
+ return sessions.filter((session) => session.agentId === filter.agentId);
162
+ }
163
+ async getSession(threadId) {
164
+ return this.persistence.getSession(threadId);
165
+ }
166
+ async getEvents(threadId, runId) {
167
+ const session = await this.getSession(threadId);
168
+ if (!session) {
169
+ throw new Error(`Unknown thread ${threadId}`);
170
+ }
171
+ return this.persistence.listRunEvents(threadId, runId ?? session.latestRunId);
172
+ }
173
+ async listPendingApprovals() {
174
+ return (await this.persistence.listApprovals()).filter((approval) => approval.status === "pending");
175
+ }
176
+ async getApproval(approvalId) {
177
+ return this.persistence.getApproval(approvalId);
178
+ }
179
+ async listDelegations() {
180
+ return this.persistence.listDelegations();
181
+ }
182
+ async routeAgent(input, options = {}) {
183
+ const routingInput = await this.buildRoutingInput(input, options.threadId);
184
+ const normalized = input.trim().toLowerCase();
185
+ const { primaryBinding, secondaryBinding, researchBinding } = inferRoutingBindings(this.workspace);
186
+ const deterministicIntent = resolveDeterministicRouteIntent(normalized);
187
+ if (requiresResearchRoute(normalized)) {
188
+ const explicitResearchBinding = this.workspace.bindings.get("research-lite") ?? this.workspace.bindings.get("research");
189
+ const hostResearchBinding = explicitResearchBinding?.harnessRuntime.hostFacing !== false ? explicitResearchBinding : researchBinding;
190
+ return hostResearchBinding?.agent.id ?? secondaryBinding?.agent.id ?? primaryBinding?.agent.id ?? heuristicRoute(input, primaryBinding, secondaryBinding);
191
+ }
192
+ if (secondaryBinding?.deepAgentParams) {
193
+ return secondaryBinding.agent.id;
194
+ }
195
+ if (deterministicIntent === "primary") {
196
+ return primaryBinding?.agent.id ?? heuristicRoute(input, primaryBinding, secondaryBinding);
197
+ }
198
+ if (deterministicIntent === "secondary") {
199
+ return secondaryBinding?.agent.id ?? primaryBinding?.agent.id ?? heuristicRoute(input, primaryBinding, secondaryBinding);
200
+ }
201
+ if (!primaryBinding || !secondaryBinding) {
202
+ return heuristicRoute(input, primaryBinding, secondaryBinding);
203
+ }
204
+ try {
205
+ return await this.runtimeAdapter.route(routingInput, primaryBinding, secondaryBinding, {
206
+ systemPrompt: this.routingSystemPrompt,
207
+ });
208
+ }
209
+ catch {
210
+ return heuristicRoute(input, primaryBinding, secondaryBinding);
211
+ }
212
+ }
213
+ async artifacts(threadId, runId) {
214
+ const session = await this.getSession(threadId);
215
+ if (!session) {
216
+ throw new Error(`Unknown thread ${threadId}`);
217
+ }
218
+ return this.persistence.listArtifacts(threadId, runId ?? session.latestRunId);
219
+ }
220
+ async emit(threadId, runId, sequence, eventType, payload, source = "runtime") {
221
+ const event = createHarnessEvent(threadId, runId, sequence, eventType, payload, source);
222
+ await this.persistence.appendEvent(event);
223
+ this.eventBus.publish(event);
224
+ return event;
225
+ }
226
+ async persistApproval(threadId, runId, checkpointRef, input, interruptContent) {
227
+ const approval = createPendingApproval(threadId, runId, checkpointRef, input, interruptContent);
228
+ await this.persistence.createApproval(approval);
229
+ const artifact = await this.persistence.createArtifact(threadId, runId, {
230
+ artifactId: `artifact-approval-${runId}`,
231
+ kind: "approval-packet",
232
+ path: `artifacts/approval-${runId}.json`,
233
+ createdAt: approval.requestedAt,
234
+ }, approval);
235
+ await this.emit(threadId, runId, 5, "artifact.created", {
236
+ artifactId: artifact.artifactId,
237
+ kind: artifact.kind,
238
+ path: artifact.path,
239
+ });
240
+ return approval;
241
+ }
242
+ async resolveApprovalRecord(options, session) {
243
+ if (options.approvalId) {
244
+ const approval = await this.persistence.getApproval(options.approvalId);
245
+ if (!approval) {
246
+ throw new Error(`Unknown approval ${options.approvalId}`);
247
+ }
248
+ return approval;
249
+ }
250
+ const runId = options.runId ?? session.latestRunId;
251
+ const approvals = await this.persistence.getRunApprovals(options.threadId ?? session.threadId, runId);
252
+ const approval = approvals
253
+ .filter((candidate) => candidate.status === "pending")
254
+ .sort((left, right) => right.requestedAt.localeCompare(left.requestedAt))[0];
255
+ if (!approval) {
256
+ throw new Error(`No pending approval for run ${runId}`);
257
+ }
258
+ return approval;
259
+ }
260
+ async run(options) {
261
+ const selectedAgentId = await this.resolveSelectedAgentId(options.input, options.agentId, options.threadId);
262
+ const binding = this.workspace.bindings.get(selectedAgentId);
263
+ if (!binding) {
264
+ throw new Error(`Unknown agent ${selectedAgentId}`);
265
+ }
266
+ const policyDecision = this.policyEngine.evaluate(binding);
267
+ if (!policyDecision.allowed) {
268
+ throw new Error(`Policy evaluation blocked agent ${selectedAgentId}: ${policyDecision.reasons.join(", ")}`);
269
+ }
270
+ const threadId = options.threadId ?? createPersistentId();
271
+ const runId = createPersistentId();
272
+ const createdAt = new Date().toISOString();
273
+ if (!options.threadId) {
274
+ await this.persistence.createThread({
275
+ threadId,
276
+ agentId: selectedAgentId,
277
+ runId,
278
+ status: "running",
279
+ createdAt,
280
+ });
281
+ }
282
+ await this.persistence.appendThreadMessage(threadId, {
283
+ role: "user",
284
+ content: options.input,
285
+ runId,
286
+ createdAt,
287
+ });
288
+ await this.persistence.createRun({
289
+ threadId,
290
+ runId,
291
+ agentId: binding.agent.id,
292
+ executionMode: binding.agent.executionMode,
293
+ createdAt,
294
+ });
295
+ await this.emit(threadId, runId, 1, "run.created", {
296
+ agentId: binding.agent.id,
297
+ requestedAgentId: options.agentId ?? AUTO_AGENT_ID,
298
+ selectedAgentId,
299
+ executionMode: binding.agent.executionMode,
300
+ });
301
+ try {
302
+ const history = await this.persistence.listThreadMessages(threadId);
303
+ const priorHistory = history.filter((message) => message.runId !== runId);
304
+ const actual = await this.runtimeAdapter.invoke(binding, options.input, threadId, runId, undefined, priorHistory);
305
+ let approval;
306
+ if (actual.output) {
307
+ await this.persistence.appendThreadMessage(threadId, {
308
+ role: "assistant",
309
+ content: actual.output,
310
+ runId,
311
+ createdAt: new Date().toISOString(),
312
+ });
313
+ }
314
+ await this.persistence.setRunState(threadId, runId, actual.state, actual.state === "waiting_for_approval" ? `checkpoints/${threadId}/${runId}/cp-1` : null);
315
+ if (actual.state === "waiting_for_approval") {
316
+ const checkpointRef = `checkpoints/${threadId}/${runId}/cp-1`;
317
+ approval = await this.persistApproval(threadId, runId, checkpointRef, options.input, actual.interruptContent);
318
+ await this.emit(threadId, runId, 4, "approval.requested", {
319
+ approvalId: approval.approvalId,
320
+ pendingActionId: approval.pendingActionId,
321
+ toolName: approval.toolName,
322
+ toolCallId: approval.toolCallId,
323
+ allowedDecisions: approval.allowedDecisions,
324
+ checkpointRef,
325
+ });
326
+ }
327
+ await this.emit(threadId, runId, 3, "run.state.changed", {
328
+ previousState: null,
329
+ state: actual.state,
330
+ checkpointRef: actual.state === "waiting_for_approval" ? `checkpoints/${threadId}/${runId}/cp-1` : null,
331
+ });
332
+ return {
333
+ ...actual,
334
+ threadId,
335
+ runId,
336
+ agentId: selectedAgentId,
337
+ approvalId: approval?.approvalId ?? actual.approvalId,
338
+ pendingActionId: approval?.pendingActionId ?? actual.pendingActionId,
339
+ };
340
+ }
341
+ catch (error) {
342
+ await this.emit(threadId, runId, 3, "runtime.synthetic_fallback", {
343
+ reason: error instanceof Error ? error.message : String(error),
344
+ selectedAgentId,
345
+ });
346
+ await this.persistence.setRunState(threadId, runId, "failed");
347
+ await this.emit(threadId, runId, 4, "run.state.changed", {
348
+ previousState: null,
349
+ state: "failed",
350
+ checkpointRef: null,
351
+ error: error instanceof Error ? error.message : String(error),
352
+ });
353
+ return {
354
+ threadId,
355
+ runId,
356
+ agentId: selectedAgentId,
357
+ state: "failed",
358
+ output: renderRuntimeFailure(error),
359
+ };
360
+ }
361
+ }
362
+ async *stream(options) {
363
+ for await (const item of this.streamEvents(options)) {
364
+ if (item.type === "content") {
365
+ yield item.content;
366
+ }
367
+ }
368
+ }
369
+ async *streamEvents(options) {
370
+ const selectedAgentId = await this.resolveSelectedAgentId(options.input, options.agentId, options.threadId);
371
+ const binding = this.workspace.bindings.get(selectedAgentId);
372
+ if (!binding) {
373
+ const result = await this.run(options);
374
+ for (const line of result.output.split("\n")) {
375
+ yield {
376
+ type: "content",
377
+ threadId: result.threadId,
378
+ runId: result.runId,
379
+ agentId: result.agentId ?? selectedAgentId,
380
+ content: `${line}\n`,
381
+ };
382
+ }
383
+ return;
384
+ }
385
+ let emitted = false;
386
+ const threadId = options.threadId ?? createPersistentId();
387
+ const runId = createPersistentId();
388
+ const createdAt = new Date().toISOString();
389
+ if (!options.threadId) {
390
+ await this.persistence.createThread({
391
+ threadId,
392
+ agentId: selectedAgentId,
393
+ runId,
394
+ status: "running",
395
+ createdAt,
396
+ });
397
+ }
398
+ await this.persistence.appendThreadMessage(threadId, {
399
+ role: "user",
400
+ content: options.input,
401
+ runId,
402
+ createdAt,
403
+ });
404
+ await this.persistence.createRun({
405
+ threadId,
406
+ runId,
407
+ agentId: selectedAgentId,
408
+ executionMode: binding.agent.executionMode,
409
+ createdAt,
410
+ });
411
+ yield { type: "event", event: await this.emit(threadId, runId, 1, "run.created", {
412
+ agentId: selectedAgentId,
413
+ requestedAgentId: options.agentId ?? AUTO_AGENT_ID,
414
+ selectedAgentId,
415
+ input: options.input,
416
+ state: "running",
417
+ }) };
418
+ try {
419
+ const history = await this.persistence.listThreadMessages(threadId);
420
+ const priorHistory = history.filter((message) => message.runId !== runId);
421
+ let assistantOutput = "";
422
+ for await (const chunk of this.runtimeAdapter.stream(binding, options.input, threadId, priorHistory)) {
423
+ if (chunk) {
424
+ const normalizedChunk = typeof chunk === "string"
425
+ ? chunk.startsWith(AGENT_INTERRUPT_SENTINEL_PREFIX)
426
+ ? { kind: "interrupt", content: chunk.slice(AGENT_INTERRUPT_SENTINEL_PREFIX.length) }
427
+ : { kind: "content", content: chunk }
428
+ : chunk;
429
+ if (normalizedChunk.kind === "interrupt") {
430
+ const checkpointRef = `checkpoints/${threadId}/${runId}/cp-1`;
431
+ await this.persistence.setRunState(threadId, runId, "waiting_for_approval", checkpointRef);
432
+ const approval = await this.persistApproval(threadId, runId, checkpointRef, options.input, normalizedChunk.content);
433
+ yield {
434
+ type: "event",
435
+ event: await this.emit(threadId, runId, 4, "run.state.changed", {
436
+ previousState: null,
437
+ state: "waiting_for_approval",
438
+ checkpointRef,
439
+ }),
440
+ };
441
+ yield {
442
+ type: "event",
443
+ event: await this.emit(threadId, runId, 5, "approval.requested", {
444
+ approvalId: approval.approvalId,
445
+ pendingActionId: approval.pendingActionId,
446
+ toolName: approval.toolName,
447
+ toolCallId: approval.toolCallId,
448
+ allowedDecisions: approval.allowedDecisions,
449
+ checkpointRef,
450
+ }),
451
+ };
452
+ return;
453
+ }
454
+ if (normalizedChunk.kind === "reasoning") {
455
+ await this.emit(threadId, runId, 3, "reasoning.delta", {
456
+ content: normalizedChunk.content,
457
+ });
458
+ yield {
459
+ type: "reasoning",
460
+ threadId,
461
+ runId,
462
+ agentId: selectedAgentId,
463
+ content: normalizedChunk.content,
464
+ };
465
+ continue;
466
+ }
467
+ if (normalizedChunk.kind === "step") {
468
+ yield {
469
+ type: "step",
470
+ threadId,
471
+ runId,
472
+ agentId: selectedAgentId,
473
+ content: normalizedChunk.content,
474
+ };
475
+ continue;
476
+ }
477
+ if (normalizedChunk.kind === "tool-result") {
478
+ yield {
479
+ type: "tool-result",
480
+ threadId,
481
+ runId,
482
+ agentId: selectedAgentId,
483
+ toolName: normalizedChunk.toolName,
484
+ output: normalizedChunk.output,
485
+ };
486
+ continue;
487
+ }
488
+ emitted = true;
489
+ assistantOutput += normalizedChunk.content;
490
+ await this.emit(threadId, runId, 3, "output.delta", {
491
+ content: normalizedChunk.content,
492
+ });
493
+ yield {
494
+ type: "content",
495
+ threadId,
496
+ runId,
497
+ agentId: selectedAgentId,
498
+ content: normalizedChunk.content,
499
+ };
500
+ }
501
+ }
502
+ if (!assistantOutput) {
503
+ const actual = await this.runtimeAdapter.invoke(binding, options.input, threadId, runId, undefined, priorHistory);
504
+ if (actual.output) {
505
+ assistantOutput = actual.output;
506
+ emitted = true;
507
+ await this.emit(threadId, runId, 3, "output.delta", {
508
+ content: actual.output,
509
+ });
510
+ yield {
511
+ type: "content",
512
+ threadId,
513
+ runId,
514
+ agentId: selectedAgentId,
515
+ content: actual.output,
516
+ };
517
+ }
518
+ }
519
+ if (assistantOutput) {
520
+ await this.persistence.appendThreadMessage(threadId, {
521
+ role: "assistant",
522
+ content: assistantOutput,
523
+ runId,
524
+ createdAt: new Date().toISOString(),
525
+ });
526
+ }
527
+ await this.persistence.setRunState(threadId, runId, "completed");
528
+ yield { type: "event", event: await this.emit(threadId, runId, 4, "run.state.changed", {
529
+ previousState: null,
530
+ state: "completed",
531
+ checkpointRef: null,
532
+ }) };
533
+ return;
534
+ }
535
+ catch (error) {
536
+ if (emitted) {
537
+ await this.persistence.setRunState(threadId, runId, "failed");
538
+ yield { type: "event", event: await this.emit(threadId, runId, 4, "run.state.changed", {
539
+ previousState: null,
540
+ state: "failed",
541
+ error: error instanceof Error ? error.message : String(error),
542
+ }) };
543
+ return;
544
+ }
545
+ try {
546
+ const history = await this.persistence.listThreadMessages(threadId);
547
+ const priorHistory = history.filter((message) => message.runId !== runId);
548
+ const actual = await this.runtimeAdapter.invoke(binding, options.input, threadId, runId, undefined, priorHistory);
549
+ if (actual.output) {
550
+ await this.persistence.appendThreadMessage(threadId, {
551
+ role: "assistant",
552
+ content: actual.output,
553
+ runId,
554
+ createdAt: new Date().toISOString(),
555
+ });
556
+ yield {
557
+ type: "content",
558
+ threadId,
559
+ runId,
560
+ agentId: selectedAgentId,
561
+ content: actual.output,
562
+ };
563
+ }
564
+ await this.persistence.setRunState(threadId, runId, actual.state);
565
+ yield { type: "event", event: await this.emit(threadId, runId, 4, "run.state.changed", {
566
+ previousState: null,
567
+ state: actual.state,
568
+ checkpointRef: null,
569
+ }) };
570
+ return;
571
+ }
572
+ catch (invokeError) {
573
+ await this.emit(threadId, runId, 3, "runtime.synthetic_fallback", {
574
+ reason: invokeError instanceof Error ? invokeError.message : String(invokeError),
575
+ selectedAgentId,
576
+ });
577
+ await this.persistence.setRunState(threadId, runId, "failed");
578
+ yield { type: "event", event: await this.emit(threadId, runId, 4, "run.state.changed", {
579
+ previousState: null,
580
+ state: "failed",
581
+ checkpointRef: null,
582
+ error: invokeError instanceof Error ? invokeError.message : String(invokeError),
583
+ }) };
584
+ yield {
585
+ type: "content",
586
+ threadId,
587
+ runId,
588
+ agentId: selectedAgentId,
589
+ content: renderRuntimeFailure(invokeError),
590
+ };
591
+ return;
592
+ }
593
+ }
594
+ }
595
+ async streamReadable(options) {
596
+ return Readable.from(this.stream(options));
597
+ }
598
+ async resume(options) {
599
+ const approvalById = options.approvalId ? await this.persistence.getApproval(options.approvalId) : null;
600
+ const session = options.threadId
601
+ ? await this.getSession(options.threadId)
602
+ : approvalById
603
+ ? await this.getSession(approvalById.threadId)
604
+ : null;
605
+ if (!session) {
606
+ throw new Error("resume requires either threadId or approvalId");
607
+ }
608
+ const approval = approvalById ?? await this.resolveApprovalRecord(options, session);
609
+ const threadId = approval.threadId;
610
+ const runId = approval.runId;
611
+ const binding = this.workspace.bindings.get(session.agentId);
612
+ if (!binding) {
613
+ throw new Error(`Unknown agent ${session.agentId}`);
614
+ }
615
+ await this.persistence.setRunState(threadId, runId, "resuming", `checkpoints/${threadId}/${runId}/cp-1`);
616
+ await this.emit(threadId, runId, 5, "run.resumed", {
617
+ resumeKind: "cross-restart",
618
+ checkpointRef: `checkpoints/${threadId}/${runId}/cp-1`,
619
+ state: "resuming",
620
+ approvalId: approval.approvalId,
621
+ pendingActionId: approval.pendingActionId,
622
+ });
623
+ await this.persistence.resolveApproval(threadId, runId, approval.approvalId, options.decision === "reject" ? "rejected" : options.decision === "edit" ? "edited" : "approved");
624
+ await this.emit(threadId, runId, 6, "approval.resolved", {
625
+ approvalId: approval.approvalId,
626
+ pendingActionId: approval.pendingActionId,
627
+ decision: options.decision ?? "approve",
628
+ toolName: approval.toolName,
629
+ });
630
+ const history = await this.persistence.listThreadMessages(threadId);
631
+ const priorHistory = history.filter((message) => message.runId !== runId);
632
+ const resumeDecision = options.decision === "edit" && options.editedInput
633
+ ? { decision: "edit", editedInput: options.editedInput }
634
+ : (options.decision ?? "approve");
635
+ const actual = await this.runtimeAdapter.invoke(binding, "", threadId, runId, resumeDecision, priorHistory);
636
+ if (actual.output) {
637
+ await this.persistence.appendThreadMessage(threadId, {
638
+ role: "assistant",
639
+ content: actual.output,
640
+ runId,
641
+ createdAt: new Date().toISOString(),
642
+ });
643
+ }
644
+ await this.persistence.setRunState(threadId, runId, actual.state, actual.state === "waiting_for_approval" ? `checkpoints/${threadId}/${runId}/cp-1` : null);
645
+ await this.emit(threadId, runId, 7, "run.state.changed", {
646
+ previousState: "resuming",
647
+ state: actual.state,
648
+ checkpointRef: actual.state === "waiting_for_approval" ? `checkpoints/${threadId}/${runId}/cp-1` : null,
649
+ });
650
+ return {
651
+ ...actual,
652
+ threadId,
653
+ runId,
654
+ approvalId: approval.approvalId,
655
+ pendingActionId: approval.pendingActionId,
656
+ };
657
+ }
658
+ async submitDecision(options) {
659
+ return this.resume(options);
660
+ }
661
+ async approve(threadId, runId) {
662
+ return this.submitDecision({ threadId, runId, decision: "approve" });
663
+ }
664
+ async reject(threadId, runId) {
665
+ return this.submitDecision({ threadId, runId, decision: "reject" });
666
+ }
667
+ async restartConversation(options) {
668
+ const session = await this.getSession(options.threadId);
669
+ if (!session) {
670
+ throw new Error(`Unknown thread ${options.threadId}`);
671
+ }
672
+ const sourceRunId = session.latestRunId;
673
+ const targetThreadId = options.mode === "restart-new-thread" ? createPersistentId() : options.threadId;
674
+ const result = await this.run({
675
+ agentId: session.agentId,
676
+ input: options.input,
677
+ threadId: options.mode === "restart-new-thread" ? undefined : targetThreadId,
678
+ });
679
+ return {
680
+ ...result,
681
+ restart: {
682
+ sourceThreadId: options.threadId,
683
+ sourceRunId,
684
+ restartMode: options.mode,
685
+ restartedBy: "console",
686
+ restartedAt: new Date().toISOString(),
687
+ newThreadId: result.threadId,
688
+ newRunId: result.runId,
689
+ },
690
+ };
691
+ }
692
+ async close() {
693
+ this.unsubscribeThreadMemorySync();
694
+ await this.threadMemorySync.close();
695
+ }
696
+ }