@botbotgo/agent-harness 0.0.65 → 0.0.67

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.
package/README.md CHANGED
@@ -207,6 +207,8 @@ const result = await run(runtime, {
207
207
 
208
208
  `run(runtime, { ... })` creates or continues a persisted thread and returns `threadId`, `runId`, `state`, and compact text `output`. Richer upstream result shapes stay available through `outputContent`, `contentBlocks`, and `structuredResponse`.
209
209
 
210
+ Use `listRuns(runtime)` and `getRun(runtime, runId)` when a product needs a run-centric operations surface such as a review queue or execution dashboard.
211
+
210
212
  Use `invocation` as the runtime-facing request envelope:
211
213
 
212
214
  - `invocation.context` for request-scoped execution context
@@ -575,6 +577,8 @@ Primary exports:
575
577
  - `run`
576
578
  - `resolveApproval`
577
579
  - `subscribe`
580
+ - `listRuns`
581
+ - `getRun`
578
582
  - `listThreads`
579
583
  - `getThread`
580
584
  - `deleteThread`
package/README.zh.md CHANGED
@@ -206,6 +206,8 @@ const result = await run(runtime, {
206
206
 
207
207
  `run(runtime, { ... })` 会创建或延续持久化线程,并返回 `threadId`、`runId`、`state` 以及紧凑文本 `output`。更丰富的上游结果形态仍可通过 `outputContent`、`contentBlocks`、`structuredResponse` 等获得。
208
208
 
209
+ 如果产品需要 run 视角的操作界面,例如 review queue 或执行看板,可使用 `listRuns(runtime)` 与 `getRun(runtime, runId)`。
210
+
209
211
  将 `invocation` 作为面向运行时的请求信封:
210
212
 
211
213
  - `invocation.context`:请求级执行上下文
@@ -567,6 +569,8 @@ spec:
567
569
  - `run`
568
570
  - `resolveApproval`
569
571
  - `subscribe`
572
+ - `listRuns`
573
+ - `getRun`
570
574
  - `listThreads`
571
575
  - `getThread`
572
576
  - `deleteThread`
package/dist/api.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ApprovalRecord, RunOptions, ResumeOptions, RuntimeAdapterOptions, ThreadSummary, ThreadRecord, WorkspaceLoadOptions } from "./contracts/types.js";
1
+ import type { ApprovalRecord, RunRecord, RunOptions, RunSummary, ResumeOptions, RuntimeAdapterOptions, ThreadSummary, ThreadRecord, WorkspaceLoadOptions } from "./contracts/types.js";
2
2
  import { AgentHarnessRuntime } from "./runtime/harness.js";
3
3
  import type { InventoryAgentRecord, InventorySkillRecord } from "./runtime/inventory.js";
4
4
  import type { RequirementAssessmentOptions } from "./runtime/skill-requirements.js";
@@ -13,7 +13,9 @@ export declare function createAgentHarness(workspaceRoot: string, options?: Crea
13
13
  export declare function run(runtime: AgentHarnessRuntime, options: RunOptions): Promise<import("./contracts/types.js").RunResult>;
14
14
  export declare function subscribe(runtime: AgentHarnessRuntime, listener: Parameters<AgentHarnessRuntime["subscribe"]>[0]): () => void;
15
15
  export declare function listThreads(runtime: AgentHarnessRuntime, filter?: Parameters<AgentHarnessRuntime["listThreads"]>[0]): Promise<ThreadSummary[]>;
16
+ export declare function listRuns(runtime: AgentHarnessRuntime, filter?: Parameters<AgentHarnessRuntime["listRuns"]>[0]): Promise<RunSummary[]>;
16
17
  export declare function getThread(runtime: AgentHarnessRuntime, threadId: string): Promise<ThreadRecord | null>;
18
+ export declare function getRun(runtime: AgentHarnessRuntime, runId: string): Promise<RunRecord | null>;
17
19
  export declare function deleteThread(runtime: AgentHarnessRuntime, threadId: string): Promise<boolean>;
18
20
  export declare function listApprovals(runtime: AgentHarnessRuntime, filter?: Parameters<AgentHarnessRuntime["listApprovals"]>[0]): Promise<ApprovalRecord[]>;
19
21
  export declare function getApproval(runtime: AgentHarnessRuntime, approvalId: string): Promise<ApprovalRecord | null>;
package/dist/api.js CHANGED
@@ -16,9 +16,15 @@ export function subscribe(runtime, listener) {
16
16
  export async function listThreads(runtime, filter) {
17
17
  return runtime.listThreads(filter);
18
18
  }
19
+ export async function listRuns(runtime, filter) {
20
+ return runtime.listRuns(filter);
21
+ }
19
22
  export async function getThread(runtime, threadId) {
20
23
  return runtime.getThread(threadId);
21
24
  }
25
+ export async function getRun(runtime, runId) {
26
+ return runtime.getRun(runId);
27
+ }
22
28
  export async function deleteThread(runtime, threadId) {
23
29
  return runtime.deleteThread(threadId);
24
30
  }
@@ -361,6 +361,7 @@ export type TranscriptMessage = {
361
361
  };
362
362
  export type ThreadRunRecord = {
363
363
  runId: string;
364
+ threadId: string;
364
365
  agentId: string;
365
366
  executionMode: string;
366
367
  adapterKind?: string;
@@ -370,6 +371,8 @@ export type ThreadRunRecord = {
370
371
  checkpointRef: string | null;
371
372
  resumable: boolean;
372
373
  };
374
+ export type RunSummary = ThreadRunRecord;
375
+ export type RunRecord = RunSummary;
373
376
  export type ThreadRecord = {
374
377
  threadId: string;
375
378
  entryAgentId: string;
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { AgentHarnessRuntime, createAgentHarness, createToolMcpServer, deleteThread, describeInventory, getApproval, getThread, listAgentSkills, listApprovals, listThreads, resolveApproval, run, serveToolsOverStdio, subscribe, stop, } from "./api.js";
1
+ export { AgentHarnessRuntime, createAgentHarness, createToolMcpServer, deleteThread, describeInventory, getApproval, getRun, getThread, listAgentSkills, listApprovals, listRuns, listThreads, resolveApproval, run, serveToolsOverStdio, subscribe, stop, } from "./api.js";
2
2
  export type { ToolMcpServerOptions } from "./mcp.js";
3
3
  export { tool } from "./tools.js";
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { AgentHarnessRuntime, createAgentHarness, createToolMcpServer, deleteThread, describeInventory, getApproval, getThread, listAgentSkills, listApprovals, listThreads, resolveApproval, run, serveToolsOverStdio, subscribe, stop, } from "./api.js";
1
+ export { AgentHarnessRuntime, createAgentHarness, createToolMcpServer, deleteThread, describeInventory, getApproval, getRun, getThread, listAgentSkills, listApprovals, listRuns, listThreads, resolveApproval, run, serveToolsOverStdio, subscribe, stop, } from "./api.js";
2
2
  export { tool } from "./tools.js";
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.64";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.66";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.64";
1
+ export const AGENT_HARNESS_VERSION = "0.0.66";
@@ -1,4 +1,4 @@
1
- import type { ArtifactListing, ArtifactRecord, DelegationRecord, HarnessEvent, InternalApprovalRecord, InvocationEnvelope, MessageContent, RunState, ThreadSummary, ThreadRunRecord, TranscriptMessage } from "../contracts/types.js";
1
+ import type { ArtifactListing, ArtifactRecord, DelegationRecord, HarnessEvent, InternalApprovalRecord, InvocationEnvelope, MessageContent, RunSummary, RunState, ThreadSummary, ThreadRunRecord, TranscriptMessage } from "../contracts/types.js";
2
2
  type ThreadMeta = {
3
3
  threadId: string;
4
4
  workspaceId: string;
@@ -73,6 +73,9 @@ export declare class FilePersistence {
73
73
  appendEvent(event: HarnessEvent): Promise<void>;
74
74
  listSessions(): Promise<ThreadSummary[]>;
75
75
  listRunIndexes(): Promise<RunIndexRecord[]>;
76
+ private readRunSummary;
77
+ listRuns(): Promise<RunSummary[]>;
78
+ getRun(runId: string): Promise<RunSummary | null>;
76
79
  getSession(threadId: string): Promise<ThreadSummary | null>;
77
80
  getThreadMeta(threadId: string): Promise<ThreadMeta | null>;
78
81
  listThreadRuns(threadId: string): Promise<ThreadRunRecord[]>;
@@ -190,6 +190,38 @@ export class FilePersistence {
190
190
  const entries = (await readdir(runIndexDir)).sort();
191
191
  return Promise.all(entries.map((entry) => readJson(path.join(runIndexDir, entry))));
192
192
  }
193
+ async readRunSummary(threadId, runId) {
194
+ const runDir = this.runDir(threadId, runId);
195
+ const [meta, lifecycle] = await Promise.all([
196
+ readJson(path.join(runDir, "meta.json")),
197
+ readJson(path.join(runDir, "lifecycle.json")),
198
+ ]);
199
+ return {
200
+ runId: meta.runId,
201
+ threadId: meta.threadId,
202
+ agentId: meta.agentId,
203
+ executionMode: meta.executionMode,
204
+ adapterKind: meta.adapterKind ?? meta.executionMode,
205
+ createdAt: meta.createdAt,
206
+ updatedAt: meta.updatedAt,
207
+ state: lifecycle.state,
208
+ checkpointRef: lifecycle.checkpointRef,
209
+ resumable: lifecycle.resumable,
210
+ };
211
+ }
212
+ async listRuns() {
213
+ const indexes = await this.listRunIndexes();
214
+ const runs = await Promise.all(indexes.map((record) => this.readRunSummary(record.threadId, record.runId)));
215
+ return runs.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
216
+ }
217
+ async getRun(runId) {
218
+ const indexPath = this.runIndexPath(runId);
219
+ if (!(await fileExists(indexPath))) {
220
+ return null;
221
+ }
222
+ const index = await readJson(indexPath);
223
+ return this.readRunSummary(index.threadId, index.runId);
224
+ }
193
225
  async getSession(threadId) {
194
226
  const filePath = path.join(this.runRoot, "indexes", "threads", `${threadId}.json`);
195
227
  if (!(await fileExists(filePath))) {
@@ -221,24 +253,7 @@ export class FilePersistence {
221
253
  return [];
222
254
  }
223
255
  const runIds = (await readdir(runsDir)).sort();
224
- const runs = await Promise.all(runIds.map(async (runId) => {
225
- const runDir = this.runDir(threadId, runId);
226
- const [meta, lifecycle] = await Promise.all([
227
- readJson(path.join(runDir, "meta.json")),
228
- readJson(path.join(runDir, "lifecycle.json")),
229
- ]);
230
- return {
231
- runId: meta.runId,
232
- agentId: meta.agentId,
233
- executionMode: meta.executionMode,
234
- adapterKind: meta.adapterKind ?? meta.executionMode,
235
- createdAt: meta.createdAt,
236
- updatedAt: meta.updatedAt,
237
- state: lifecycle.state,
238
- checkpointRef: lifecycle.checkpointRef,
239
- resumable: lifecycle.resumable,
240
- };
241
- }));
256
+ const runs = await Promise.all(runIds.map(async (runId) => this.readRunSummary(threadId, runId)));
242
257
  return runs.sort((left, right) => right.createdAt.localeCompare(left.createdAt));
243
258
  }
244
259
  async listRunEvents(threadId, runId) {
@@ -42,6 +42,9 @@ export declare class AgentRuntimeAdapter {
42
42
  private compileInterruptOn;
43
43
  private resolveInterruptOn;
44
44
  private resolveFilesystemBackend;
45
+ private resolveBuiltinMiddlewareBackend;
46
+ private invokeBuiltinTaskTool;
47
+ private resolveBuiltinMiddlewareTools;
45
48
  private resolveLangChainAutomaticMiddleware;
46
49
  private resolveMiddleware;
47
50
  private resolveCheckpointer;
@@ -3,7 +3,8 @@ import { existsSync, statSync } from "node:fs";
3
3
  import { cp, mkdir, rm } from "node:fs/promises";
4
4
  import { Command, MemorySaver } from "@langchain/langgraph";
5
5
  import { tool as createLangChainTool } from "@langchain/core/tools";
6
- import { createDeepAgent, createMemoryMiddleware, createSkillsMiddleware, createSubAgentMiddleware, FilesystemBackend, } from "deepagents";
6
+ import { HumanMessage, ToolMessage } from "@langchain/core/messages";
7
+ import { DEFAULT_SUBAGENT_PROMPT, createDeepAgent, createMemoryMiddleware, createPatchToolCallsMiddleware, createSkillsMiddleware, createSummarizationMiddleware, createSubAgentMiddleware, FilesystemBackend, StateBackend, isSandboxBackend, } from "deepagents";
7
8
  import { ChatAnthropic } from "@langchain/anthropic";
8
9
  import { tools as anthropicProviderTools } from "@langchain/anthropic";
9
10
  import { ChatGoogle } from "@langchain/google";
@@ -12,7 +13,7 @@ import { ChatOpenAI } from "@langchain/openai";
12
13
  import { tools as openAIProviderTools } from "@langchain/openai";
13
14
  import { createAgent, humanInTheLoopMiddleware, initChatModel } from "langchain";
14
15
  import { z } from "zod";
15
- import { extractEmptyAssistantMessageFailure, extractContentBlocks, extractOutputContent, extractReasoningText, extractToolFallbackContext, extractVisibleOutput, isLikelyToolArgsObject, isToolCallParseFailure, STRICT_TOOL_JSON_INSTRUCTION, sanitizeVisibleText, tryParseJson, wrapResolvedModel, } from "./parsing/output-parsing.js";
16
+ import { extractEmptyAssistantMessageFailure, extractContentBlocks, extractOutputContent, extractReasoningText, extractToolFallbackContext, extractVisibleOutput, salvageToolArgs, isLikelyToolArgsObject, isToolCallParseFailure, STRICT_TOOL_JSON_INSTRUCTION, sanitizeVisibleText, tryParseJson, wrapResolvedModel, } from "./parsing/output-parsing.js";
16
17
  import { computeIncrementalOutput, extractAgentStep, extractInterruptPayload, extractReasoningStreamOutput, extractStateStreamOutput, extractVisibleStreamOutput, extractTerminalStreamOutput, extractToolResult, normalizeTerminalOutputKey, readStreamDelta, } from "./parsing/stream-event-parsing.js";
17
18
  import { wrapToolForExecution } from "./tool-hitl.js";
18
19
  import { resolveDeclaredMiddleware } from "./declared-middleware.js";
@@ -24,6 +25,17 @@ function countConfiguredTools(binding) {
24
25
  }
25
26
  const AGENT_INTERRUPT_SENTINEL_PREFIX = "__agent_harness_interrupt__:";
26
27
  const MODEL_SAFE_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
28
+ const UPSTREAM_BUILTIN_MIDDLEWARE_TOOL_NAMES = Object.freeze([
29
+ "write_todos",
30
+ "ls",
31
+ "read_file",
32
+ "write_file",
33
+ "edit_file",
34
+ "glob",
35
+ "grep",
36
+ "execute",
37
+ "task",
38
+ ]);
27
39
  class RuntimeOperationTimeoutError extends Error {
28
40
  operation;
29
41
  timeoutMs;
@@ -152,7 +164,8 @@ function normalizeOpenAICompatibleInit(init) {
152
164
  return normalized;
153
165
  }
154
166
  function sanitizeToolNameForModel(name) {
155
- const sanitized = name
167
+ const withoutNamespace = name.includes(".") ? name.split(".").at(-1) ?? name : name;
168
+ const sanitized = withoutNamespace
156
169
  .replaceAll(".", "__")
157
170
  .replace(/[^a-zA-Z0-9_-]+/g, "_")
158
171
  .replace(/^_+|_+$/g, "");
@@ -252,6 +265,193 @@ function normalizeResolvedToolSchema(resolvedTool) {
252
265
  }
253
266
  return z.object({}).passthrough();
254
267
  }
268
+ function isRecord(value) {
269
+ return typeof value === "object" && value !== null && !Array.isArray(value);
270
+ }
271
+ function isObject(value) {
272
+ return isRecord(value);
273
+ }
274
+ function stringifyToolOutput(output) {
275
+ if (typeof output === "string") {
276
+ return output;
277
+ }
278
+ try {
279
+ return JSON.stringify(output);
280
+ }
281
+ catch {
282
+ return `${String(output)}`;
283
+ }
284
+ }
285
+ function normalizeToolArgsForSchema(args, schema) {
286
+ const schemaDef = isObject(schema) ? schema._def : undefined;
287
+ const shape = schemaDef
288
+ ? isRecord(schemaDef.shape)
289
+ ? schemaDef.shape
290
+ : typeof schemaDef.shape === "function"
291
+ ? schemaDef.shape()
292
+ : undefined
293
+ : undefined;
294
+ if (!shape || !isRecord(shape)) {
295
+ return args;
296
+ }
297
+ const keys = Object.keys(shape);
298
+ if (keys.length !== 1) {
299
+ return args;
300
+ }
301
+ const [expectedKey] = keys;
302
+ if (expectedKey in args) {
303
+ return args;
304
+ }
305
+ const aliasesByExpected = {
306
+ city: ["location", "locality", "place"],
307
+ location: ["city", "city_name"],
308
+ };
309
+ const aliases = aliasesByExpected[expectedKey] ?? [];
310
+ const aliasKey = aliases.find((candidate) => candidate in args);
311
+ if (!aliasKey || !(aliasKey in args)) {
312
+ return args;
313
+ }
314
+ return {
315
+ ...args,
316
+ [expectedKey]: args[aliasKey],
317
+ };
318
+ }
319
+ function extractToolCallsFromResult(result) {
320
+ const messages = isRecord(result) && Array.isArray(result.messages) ? result.messages : [];
321
+ const lastMessage = messages.at(-1);
322
+ if (!isObject(lastMessage)) {
323
+ return [];
324
+ }
325
+ const messageKwargs = isObject(lastMessage.kwargs) ? lastMessage.kwargs : undefined;
326
+ const rawToolCalls = Array.isArray(lastMessage.tool_calls)
327
+ ? (lastMessage.tool_calls ?? [])
328
+ : Array.isArray(messageKwargs?.tool_calls)
329
+ ? messageKwargs.tool_calls
330
+ : [];
331
+ return rawToolCalls
332
+ .map((toolCall) => {
333
+ if (!isObject(toolCall)) {
334
+ return null;
335
+ }
336
+ const functionPayload = isObject(toolCall.function) ? toolCall.function : undefined;
337
+ const name = typeof toolCall.name === "string"
338
+ ? toolCall.name
339
+ : typeof functionPayload?.name === "string"
340
+ ? functionPayload.name
341
+ : null;
342
+ if (!name) {
343
+ return null;
344
+ }
345
+ const rawArgs = salvageToolArgs(toolCall.args ?? functionPayload?.arguments) ?? {};
346
+ if (!isObject(rawArgs)) {
347
+ return null;
348
+ }
349
+ const id = typeof toolCall.id === "string" ? toolCall.id : undefined;
350
+ return { id, name, args: rawArgs };
351
+ })
352
+ .filter((item) => item !== null);
353
+ }
354
+ function truncateLines(lines, maxChars = 12_000) {
355
+ const joined = lines.join("\n");
356
+ if (joined.length <= maxChars) {
357
+ return joined;
358
+ }
359
+ return `${joined.slice(0, maxChars - 18)}\n...[truncated]`;
360
+ }
361
+ function summarizeBuiltinWriteTodosArgs(args) {
362
+ const todos = Array.isArray(args.todos) ? args.todos : [];
363
+ const items = todos.flatMap((todo) => {
364
+ if (!isObject(todo)) {
365
+ return [];
366
+ }
367
+ const content = typeof todo.content === "string" && todo.content.trim().length > 0
368
+ ? todo.content.trim()
369
+ : typeof todo.description === "string" && todo.description.trim().length > 0
370
+ ? todo.description.trim()
371
+ : "";
372
+ const status = typeof todo.status === "string" && todo.status.trim().length > 0 ? todo.status.trim() : "pending";
373
+ return content ? [{ content, status }] : [];
374
+ });
375
+ return {
376
+ total: items.length,
377
+ pending: items.filter((item) => item.status !== "completed").length,
378
+ completed: items.filter((item) => item.status === "completed").length,
379
+ items,
380
+ };
381
+ }
382
+ function createModelFacingToolNameCandidates(toolName) {
383
+ const trimmed = toolName.trim();
384
+ if (!trimmed) {
385
+ return [];
386
+ }
387
+ const lastSegment = trimmed.split(".").at(-1) ?? "";
388
+ const candidates = new Set([
389
+ trimmed,
390
+ sanitizeToolNameForModel(trimmed),
391
+ lastSegment,
392
+ lastSegment.trim() ? sanitizeToolNameForModel(lastSegment) : "",
393
+ trimmed.replace(/[^a-zA-Z0-9_]+/g, ""),
394
+ ]);
395
+ candidates.delete("");
396
+ return [...candidates];
397
+ }
398
+ function createModelFacingToolNameLookupCandidates(toolName) {
399
+ const trimmed = toolName.trim();
400
+ if (!trimmed) {
401
+ return [];
402
+ }
403
+ const candidates = new Set();
404
+ const add = (value) => {
405
+ for (const candidate of createModelFacingToolNameCandidates(value)) {
406
+ candidates.add(candidate);
407
+ }
408
+ };
409
+ const addSuffixStripped = (value) => {
410
+ const stripped = value.includes(".") ? value.split(".").at(-1) ?? value : value;
411
+ if (stripped !== value) {
412
+ add(stripped);
413
+ }
414
+ };
415
+ add(trimmed);
416
+ if (trimmed.startsWith("functions.")) {
417
+ const stripped = trimmed.substring("functions.".length);
418
+ add(stripped);
419
+ addSuffixStripped(stripped);
420
+ }
421
+ else if (trimmed.startsWith("function.")) {
422
+ const stripped = trimmed.substring("function.".length);
423
+ add(stripped);
424
+ addSuffixStripped(stripped);
425
+ }
426
+ else {
427
+ addSuffixStripped(trimmed);
428
+ }
429
+ return [...candidates];
430
+ }
431
+ function resolveModelFacingToolName(toolName, mapping, tools) {
432
+ const candidateNames = createModelFacingToolNameLookupCandidates(toolName);
433
+ const candidateSet = new Set(candidateNames);
434
+ for (const candidate of candidateNames) {
435
+ const mappedToolName = mapping.modelFacingToOriginal.get(candidate);
436
+ if (mappedToolName) {
437
+ return mappedToolName;
438
+ }
439
+ }
440
+ for (const candidate of candidateNames) {
441
+ const directMatch = tools.find((tool) => tool.name === candidate);
442
+ if (directMatch) {
443
+ return directMatch.name;
444
+ }
445
+ }
446
+ const candidateMatches = tools.filter((tool) => {
447
+ const modelFacingCandidates = createModelFacingToolNameCandidates(tool.name);
448
+ return modelFacingCandidates.some((candidate) => candidateSet.has(candidate));
449
+ });
450
+ if (candidateMatches.length === 1) {
451
+ return candidateMatches[0].name;
452
+ }
453
+ return toolName;
454
+ }
255
455
  function asStructuredExecutableTool(resolvedTool, modelFacingName, description) {
256
456
  if (!hasCallableToolHandler(resolvedTool)) {
257
457
  return resolvedTool;
@@ -589,6 +789,228 @@ export class AgentRuntimeAdapter {
589
789
  : 10,
590
790
  });
591
791
  }
792
+ resolveBuiltinMiddlewareBackend(binding, options = {}) {
793
+ const runtimeState = {
794
+ ...(options.state ?? {}),
795
+ ...(isRecord(options.files) ? { files: options.files } : {}),
796
+ };
797
+ const runtimeLike = {
798
+ state: runtimeState,
799
+ store: this.options.storeResolver?.(binding),
800
+ };
801
+ const configuredBackend = isDeepAgentBinding(binding)
802
+ ? this.options.backendResolver?.(binding)
803
+ : this.resolveFilesystemBackend(binding);
804
+ if (typeof configuredBackend === "function") {
805
+ return configuredBackend(runtimeLike);
806
+ }
807
+ if (configuredBackend) {
808
+ return configuredBackend;
809
+ }
810
+ return new StateBackend(runtimeLike);
811
+ }
812
+ async invokeBuiltinTaskTool(binding, input, options = {}) {
813
+ if (!isDeepAgentBinding(binding)) {
814
+ throw new Error("The built-in task tool is only available for deepagent bindings.");
815
+ }
816
+ const params = getBindingDeepAgentParams(binding);
817
+ if (!params) {
818
+ throw new Error(`Agent ${binding.agent.id} has no deepagent params`);
819
+ }
820
+ const typedInput = isObject(input) ? input : {};
821
+ const description = typeof typedInput.description === "string" ? typedInput.description : "";
822
+ const subagentType = typeof typedInput.subagent_type === "string" ? typedInput.subagent_type : "";
823
+ const builtinBackend = this.resolveBuiltinMiddlewareBackend(binding, options);
824
+ const resolvedSubagents = await this.resolveSubagents(params.subagents, binding);
825
+ const selectedSubagent = resolvedSubagents.find((subagent) => subagent.name === subagentType);
826
+ if (!selectedSubagent) {
827
+ const allowed = [
828
+ ...resolvedSubagents.map((subagent) => subagent.name),
829
+ ...(params.generalPurposeAgent ? ["general-purpose"] : []),
830
+ ];
831
+ throw new Error(`Error: invoked agent of type ${subagentType}, the only allowed types are ${allowed.map((name) => `\`${name}\``).join(", ")}`);
832
+ }
833
+ const summarizationModel = selectedSubagent.model
834
+ ? await this.resolveModel(selectedSubagent.model)
835
+ : await this.resolveModel(params.model);
836
+ const middleware = [
837
+ ...(selectedSubagent.skills?.length
838
+ ? [createSkillsMiddleware({ backend: builtinBackend, sources: selectedSubagent.skills })]
839
+ : []),
840
+ ...(selectedSubagent.memory?.length
841
+ ? [createMemoryMiddleware({ backend: builtinBackend, sources: selectedSubagent.memory })]
842
+ : []),
843
+ ...(selectedSubagent.middleware ??
844
+ [
845
+ createPatchToolCallsMiddleware(),
846
+ createSummarizationMiddleware({
847
+ model: summarizationModel,
848
+ backend: builtinBackend,
849
+ }),
850
+ ]),
851
+ ...(selectedSubagent.interruptOn
852
+ ? [humanInTheLoopMiddleware({
853
+ interruptOn: this.compileInterruptOn(selectedSubagent.tools ?? [], selectedSubagent.interruptOn),
854
+ })]
855
+ : []),
856
+ ];
857
+ const runnable = createAgent({
858
+ model: (selectedSubagent.model ?? (await this.resolveModel(params.model))),
859
+ tools: (selectedSubagent.tools ?? this.resolveTools(params.tools, binding)),
860
+ systemPrompt: selectedSubagent.systemPrompt ?? DEFAULT_SUBAGENT_PROMPT,
861
+ middleware: middleware,
862
+ responseFormat: selectedSubagent.responseFormat,
863
+ contextSchema: selectedSubagent.contextSchema,
864
+ name: selectedSubagent.name,
865
+ description: selectedSubagent.description,
866
+ });
867
+ const result = await runnable.invoke({ messages: [new HumanMessage({ content: description })] }, { configurable: { thread_id: `${binding.agent.id}:builtin-task` }, ...(options.context ? { context: options.context } : {}) });
868
+ const visibleOutput = extractVisibleOutput(result);
869
+ const fallbackOutput = extractToolFallbackContext(result);
870
+ return visibleOutput || fallbackOutput || JSON.stringify(result);
871
+ }
872
+ async resolveBuiltinMiddlewareTools(binding, options = {}) {
873
+ const tools = new Map();
874
+ const backend = this.resolveBuiltinMiddlewareBackend(binding, options);
875
+ tools.set("write_todos", {
876
+ name: "write_todos",
877
+ schema: z.object({
878
+ todos: z.array(z.object({}).passthrough()).optional(),
879
+ }).passthrough(),
880
+ invoke: async (input) => {
881
+ const args = isObject(input) ? input : {};
882
+ const summary = summarizeBuiltinWriteTodosArgs(args);
883
+ return {
884
+ ok: true,
885
+ tool: "write_todos",
886
+ message: `Tracked ${summary.total} todo item(s).`,
887
+ summary,
888
+ };
889
+ },
890
+ });
891
+ tools.set("ls", {
892
+ name: "ls",
893
+ schema: z.object({ path: z.string().optional().default("/") }).passthrough(),
894
+ invoke: async (input) => {
895
+ const targetPath = isObject(input) && typeof input.path === "string" ? input.path : "/";
896
+ const infos = (await Promise.resolve(backend.lsInfo?.(targetPath))) ?? [];
897
+ if (infos.length === 0) {
898
+ return `No files found in ${targetPath}`;
899
+ }
900
+ return truncateLines(infos.map((info) => info.is_dir ? `${info.path} (directory)` : `${info.path}${info.size ? ` (${info.size} bytes)` : ""}`));
901
+ },
902
+ });
903
+ tools.set("read_file", {
904
+ name: "read_file",
905
+ schema: z.object({
906
+ file_path: z.string(),
907
+ offset: z.number().optional(),
908
+ limit: z.number().optional(),
909
+ }).passthrough(),
910
+ invoke: async (input) => {
911
+ const typed = isObject(input) ? input : {};
912
+ const filePath = typeof typed.file_path === "string" ? typed.file_path : "";
913
+ const offset = typeof typed.offset === "number" ? typed.offset : 0;
914
+ const limit = typeof typed.limit === "number" ? typed.limit : 500;
915
+ return Promise.resolve(backend.read?.(filePath, offset, limit)) ?? "";
916
+ },
917
+ });
918
+ tools.set("write_file", {
919
+ name: "write_file",
920
+ schema: z.object({ file_path: z.string(), content: z.string().optional() }).passthrough(),
921
+ invoke: async (input) => {
922
+ const typed = isObject(input) ? input : {};
923
+ const result = await Promise.resolve(backend.write?.(typeof typed.file_path === "string" ? typed.file_path : "", typeof typed.content === "string" ? typed.content : ""));
924
+ return result?.error ?? `Successfully wrote to '${result?.path ?? (typed.file_path ?? "")}'`;
925
+ },
926
+ });
927
+ tools.set("edit_file", {
928
+ name: "edit_file",
929
+ schema: z.object({
930
+ file_path: z.string(),
931
+ old_string: z.string(),
932
+ new_string: z.string(),
933
+ replace_all: z.boolean().optional(),
934
+ }).passthrough(),
935
+ invoke: async (input) => {
936
+ const typed = isObject(input) ? input : {};
937
+ const result = await Promise.resolve(backend.edit?.(typeof typed.file_path === "string" ? typed.file_path : "", typeof typed.old_string === "string" ? typed.old_string : "", typeof typed.new_string === "string" ? typed.new_string : "", typed.replace_all === true));
938
+ return result?.error ?? `Successfully replaced ${result?.occurrences ?? 0} occurrence(s) in '${result?.path ?? (typed.file_path ?? "")}'`;
939
+ },
940
+ });
941
+ tools.set("glob", {
942
+ name: "glob",
943
+ schema: z.object({ pattern: z.string(), path: z.string().optional().default("/") }).passthrough(),
944
+ invoke: async (input) => {
945
+ const typed = isObject(input) ? input : {};
946
+ const pattern = typeof typed.pattern === "string" ? typed.pattern : "";
947
+ const targetPath = typeof typed.path === "string" ? typed.path : "/";
948
+ const infos = (await Promise.resolve(backend.globInfo?.(pattern, targetPath))) ?? [];
949
+ if (infos.length === 0) {
950
+ return `No files found matching pattern '${pattern}'`;
951
+ }
952
+ return truncateLines(infos.map((info) => info.path));
953
+ },
954
+ });
955
+ tools.set("grep", {
956
+ name: "grep",
957
+ schema: z.object({
958
+ pattern: z.string(),
959
+ path: z.string().optional().default("/"),
960
+ glob: z.string().nullable().optional(),
961
+ }).passthrough(),
962
+ invoke: async (input) => {
963
+ const typed = isObject(input) ? input : {};
964
+ const result = await Promise.resolve(backend.grepRaw?.(typeof typed.pattern === "string" ? typed.pattern : "", typeof typed.path === "string" ? typed.path : "/", typeof typed.glob === "string" ? typed.glob : null));
965
+ if (typeof result === "string") {
966
+ return result;
967
+ }
968
+ if (!result || result.length === 0) {
969
+ return `No matches found for pattern '${typeof typed.pattern === "string" ? typed.pattern : ""}'`;
970
+ }
971
+ const lines = [];
972
+ let currentFile = "";
973
+ for (const match of result) {
974
+ if (match.path !== currentFile) {
975
+ currentFile = match.path;
976
+ lines.push(`\n${currentFile}:`);
977
+ }
978
+ lines.push(` ${match.line}: ${match.text}`);
979
+ }
980
+ return truncateLines(lines);
981
+ },
982
+ });
983
+ tools.set("execute", {
984
+ name: "execute",
985
+ schema: z.object({ command: z.string() }).passthrough(),
986
+ invoke: async (input) => {
987
+ if (!isSandboxBackend(backend) || typeof backend.execute !== "function") {
988
+ return "Error: Execution not available. This agent's backend does not support command execution (SandboxBackendProtocol).";
989
+ }
990
+ const typed = isObject(input) ? input : {};
991
+ const result = await Promise.resolve(backend.execute(typeof typed.command === "string" ? typed.command : ""));
992
+ const parts = [result.output];
993
+ if (result.exitCode !== null) {
994
+ parts.push(`\n[Command ${result.exitCode === 0 ? "succeeded" : "failed"} with exit code ${result.exitCode}]`);
995
+ }
996
+ if (result.truncated) {
997
+ parts.push("\n[Output was truncated due to size limits]");
998
+ }
999
+ return parts.join("");
1000
+ },
1001
+ });
1002
+ if (isDeepAgentBinding(binding)) {
1003
+ tools.set("task", {
1004
+ name: "task",
1005
+ schema: z.object({
1006
+ description: z.string(),
1007
+ subagent_type: z.string(),
1008
+ }).passthrough(),
1009
+ invoke: async (input) => this.invokeBuiltinTaskTool(binding, input, options),
1010
+ });
1011
+ }
1012
+ return tools;
1013
+ }
592
1014
  async resolveLangChainAutomaticMiddleware(binding) {
593
1015
  const params = getBindingLangChainParams(binding);
594
1016
  if (!params) {
@@ -781,17 +1203,117 @@ export class AgentRuntimeAdapter {
781
1203
  ? this.buildInvocationRequest(binding, history, input, options)
782
1204
  : new Command({ resume: resumePayload });
783
1205
  let result;
784
- try {
785
- const runnable = await this.create(binding);
786
- result = (await this.withTimeout(() => runnable.invoke(request, { configurable: { thread_id: threadId }, ...(options.context ? { context: options.context } : {}) }), this.resolveBindingTimeout(binding), "agent invoke", "invoke"));
1206
+ const callRuntime = async (activeBinding, activeRequest) => {
1207
+ const runnable = await this.create(activeBinding);
1208
+ return (await this.withTimeout(() => runnable.invoke(activeRequest, { configurable: { thread_id: threadId }, ...(options.context ? { context: options.context } : {}) }), this.resolveBindingTimeout(activeBinding), "agent invoke", "invoke"));
1209
+ };
1210
+ const callRuntimeWithToolParseRecovery = async (activeRequest) => {
1211
+ try {
1212
+ return await callRuntime(binding, activeRequest);
1213
+ }
1214
+ catch (error) {
1215
+ if (resumePayload !== undefined || !isToolCallParseFailure(error)) {
1216
+ throw error;
1217
+ }
1218
+ return callRuntime(this.applyStrictToolJsonInstruction(binding), activeRequest);
1219
+ }
1220
+ };
1221
+ const executedToolResults = [];
1222
+ if (resumePayload !== undefined) {
1223
+ result = await callRuntimeWithToolParseRecovery(request);
787
1224
  }
788
- catch (error) {
789
- if (resumePayload !== undefined || !isToolCallParseFailure(error)) {
790
- throw error;
1225
+ else {
1226
+ const primaryTools = getBindingPrimaryTools(binding);
1227
+ const defersToUpstreamHitlExecution = primaryTools.some((tool) => tool.hitl?.enabled === true);
1228
+ if (defersToUpstreamHitlExecution) {
1229
+ result = await callRuntimeWithToolParseRecovery(request);
1230
+ }
1231
+ else {
1232
+ const resolvedTools = this.resolveTools(primaryTools, binding);
1233
+ const toolNameMapping = this.buildToolNameMapping(primaryTools);
1234
+ const executableTools = new Map();
1235
+ const builtinExecutableTools = await this.resolveBuiltinMiddlewareTools(binding, options);
1236
+ for (let index = 0; index < primaryTools.length; index += 1) {
1237
+ const compiledTool = primaryTools[index];
1238
+ const resolvedTool = resolvedTools[index];
1239
+ if (!compiledTool || !resolvedTool || !hasCallableToolHandler(resolvedTool)) {
1240
+ continue;
1241
+ }
1242
+ const handler = async (toolInput) => {
1243
+ const callable = typeof resolvedTool.invoke === "function"
1244
+ ? resolvedTool.invoke
1245
+ : typeof resolvedTool.call === "function"
1246
+ ? resolvedTool.call
1247
+ : resolvedTool.func;
1248
+ if (!callable) {
1249
+ throw new Error(`Tool ${compiledTool.name} has no callable handler.`);
1250
+ }
1251
+ return Promise.resolve(callable.call(resolvedTool, toolInput, options.context ? { context: options.context } : undefined));
1252
+ };
1253
+ const modelFacingName = toolNameMapping.originalToModelFacing.get(compiledTool.name) ?? compiledTool.name;
1254
+ const normalizedSchema = normalizeResolvedToolSchema(resolvedTool);
1255
+ executableTools.set(modelFacingName, {
1256
+ name: compiledTool.name,
1257
+ schema: normalizedSchema,
1258
+ invoke: handler,
1259
+ });
1260
+ executableTools.set(compiledTool.name, {
1261
+ name: compiledTool.name,
1262
+ schema: normalizedSchema,
1263
+ invoke: handler,
1264
+ });
1265
+ }
1266
+ let activeRequest = request;
1267
+ let currentMessages = Array.isArray(activeRequest.messages) ? [...activeRequest.messages] : [];
1268
+ const maxToolIterations = 8;
1269
+ for (let iteration = 0; iteration < maxToolIterations; iteration += 1) {
1270
+ result = await callRuntimeWithToolParseRecovery(activeRequest);
1271
+ const toolCalls = extractToolCallsFromResult(result);
1272
+ if (toolCalls.length === 0) {
1273
+ break;
1274
+ }
1275
+ if (iteration + 1 === maxToolIterations) {
1276
+ throw new Error(`Tool-calling loop exceeded the maximum of ${maxToolIterations} iterations`);
1277
+ }
1278
+ const resultMessages = result.messages;
1279
+ const nextMessages = Array.isArray(resultMessages)
1280
+ ? [...resultMessages]
1281
+ : [...currentMessages];
1282
+ for (let toolIndex = 0; toolIndex < toolCalls.length; toolIndex += 1) {
1283
+ const toolCall = toolCalls[toolIndex];
1284
+ const resolvedToolName = resolveModelFacingToolName(toolCall.name, toolNameMapping, primaryTools);
1285
+ const executable = executableTools.get(toolCall.name) ?? executableTools.get(resolvedToolName);
1286
+ const builtinExecutable = builtinExecutableTools.get(toolCall.name) ??
1287
+ builtinExecutableTools.get(resolvedToolName) ??
1288
+ createModelFacingToolNameLookupCandidates(toolCall.name)
1289
+ .map((candidate) => builtinExecutableTools.get(candidate))
1290
+ .find((candidate) => candidate !== undefined);
1291
+ const activeExecutable = executable ?? builtinExecutable;
1292
+ if (!activeExecutable) {
1293
+ throw new Error(`Tool ${toolCall.name} is not configured for this agent.`);
1294
+ }
1295
+ const normalizedArgs = normalizeToolArgsForSchema(toolCall.args, activeExecutable.schema);
1296
+ const toolResult = await activeExecutable.invoke(normalizedArgs);
1297
+ executedToolResults.push({
1298
+ toolName: activeExecutable.name,
1299
+ output: toolResult,
1300
+ });
1301
+ nextMessages.push(new ToolMessage({
1302
+ name: activeExecutable.name,
1303
+ tool_call_id: toolCall.id ?? `tool-${iteration + 1}-${toolIndex + 1}`,
1304
+ content: stringifyToolOutput(toolResult),
1305
+ }));
1306
+ }
1307
+ currentMessages = nextMessages;
1308
+ activeRequest = {
1309
+ ...activeRequest,
1310
+ messages: currentMessages,
1311
+ };
1312
+ }
791
1313
  }
792
- const retriedBinding = this.applyStrictToolJsonInstruction(binding);
793
- const runnable = await this.create(retriedBinding);
794
- result = (await this.withTimeout(() => runnable.invoke(this.buildInvocationRequest(retriedBinding, history, input, options), { configurable: { thread_id: threadId }, ...(options.context ? { context: options.context } : {}) }), this.resolveBindingTimeout(retriedBinding), "agent invoke", "invoke"));
1314
+ }
1315
+ if (!result) {
1316
+ throw new Error("Agent invocation returned no result");
795
1317
  }
796
1318
  const interruptContent = Array.isArray(result.__interrupt__) && result.__interrupt__.length > 0 ? JSON.stringify(result.__interrupt__) : undefined;
797
1319
  const extractedOutput = extractVisibleOutput(result);
@@ -819,6 +1341,7 @@ export class AgentRuntimeAdapter {
819
1341
  ...(contentBlocks.length > 0 ? { contentBlocks } : {}),
820
1342
  ...(structuredResponse !== undefined ? { structuredResponse } : {}),
821
1343
  metadata: {
1344
+ ...(executedToolResults.length > 0 ? { executedToolResults } : {}),
822
1345
  ...(structuredResponse !== undefined ? { structuredResponse } : {}),
823
1346
  ...(outputContent !== undefined ? { outputContent } : {}),
824
1347
  ...(contentBlocks.length > 0 ? { contentBlocks } : {}),
@@ -833,6 +1356,12 @@ export class AgentRuntimeAdapter {
833
1356
  const invokeTimeoutMs = this.resolveBindingTimeout(binding);
834
1357
  const streamIdleTimeoutMs = this.resolveStreamIdleTimeout(binding);
835
1358
  const streamDeadlineAt = invokeTimeoutMs ? Date.now() + invokeTimeoutMs : undefined;
1359
+ const primaryTools = getBindingPrimaryTools(binding);
1360
+ const toolNameMapping = this.buildToolNameMapping(primaryTools);
1361
+ const primaryModel = getBindingPrimaryModel(binding);
1362
+ const forceInvokeFallback = isLangChainBinding(binding) &&
1363
+ primaryTools.length > 0 &&
1364
+ primaryModel?.provider === "openai-compatible";
836
1365
  if (isLangChainBinding(binding)) {
837
1366
  const langchainParams = getBindingLangChainParams(binding);
838
1367
  const resolvedModel = (await this.resolveModel(langchainParams.model));
@@ -867,7 +1396,7 @@ export class AgentRuntimeAdapter {
867
1396
  }
868
1397
  const runnable = await this.create(binding);
869
1398
  const request = this.buildInvocationRequest(binding, history, input, options);
870
- if (typeof runnable.streamEvents === "function") {
1399
+ if (!forceInvokeFallback && typeof runnable.streamEvents === "function") {
871
1400
  const events = await this.withTimeout(() => runnable.streamEvents(request, { configurable: { thread_id: threadId }, version: "v2", ...(options.context ? { context: options.context } : {}) }), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "agent streamEvents start", "stream");
872
1401
  const allowVisibleStreamDeltas = isLangChainBinding(binding);
873
1402
  let emittedOutput = "";
@@ -912,7 +1441,13 @@ export class AgentRuntimeAdapter {
912
1441
  const toolResult = extractToolResult(event);
913
1442
  if (toolResult) {
914
1443
  emittedToolError = emittedToolError || toolResult.isError === true;
915
- yield { kind: "tool-result", toolName: toolResult.toolName, output: toolResult.output, isError: toolResult.isError };
1444
+ const resolvedToolName = resolveModelFacingToolName(toolResult.toolName, toolNameMapping, primaryTools);
1445
+ yield {
1446
+ kind: "tool-result",
1447
+ toolName: resolvedToolName,
1448
+ output: toolResult.output,
1449
+ isError: toolResult.isError,
1450
+ };
916
1451
  }
917
1452
  const output = extractTerminalStreamOutput(event);
918
1453
  if (output) {
@@ -934,7 +1469,7 @@ export class AgentRuntimeAdapter {
934
1469
  return;
935
1470
  }
936
1471
  }
937
- if (isLangChainBinding(binding) && typeof runnable.stream === "function") {
1472
+ if (!forceInvokeFallback && isLangChainBinding(binding) && typeof runnable.stream === "function") {
938
1473
  const stream = await this.withTimeout(() => runnable.stream(request, { configurable: { thread_id: threadId } }), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "agent stream start", "stream");
939
1474
  let emitted = false;
940
1475
  for await (const chunk of this.iterateWithTimeout(stream, streamIdleTimeoutMs, "agent stream", streamDeadlineAt, invokeTimeoutMs)) {
@@ -954,6 +1489,17 @@ export class AgentRuntimeAdapter {
954
1489
  }
955
1490
  }
956
1491
  const result = await this.invoke(binding, input, threadId, threadId);
1492
+ const executedToolResults = Array.isArray(result.metadata?.executedToolResults)
1493
+ ? result.metadata.executedToolResults
1494
+ : [];
1495
+ for (const toolResult of executedToolResults) {
1496
+ yield {
1497
+ kind: "tool-result",
1498
+ toolName: toolResult.toolName,
1499
+ output: toolResult.output,
1500
+ isError: toolResult.isError,
1501
+ };
1502
+ }
957
1503
  if (result.output) {
958
1504
  yield { kind: "content", content: sanitizeVisibleText(result.output) };
959
1505
  }
@@ -1,4 +1,4 @@
1
- import type { ApprovalRecord, HarnessEvent, HarnessStreamItem, MessageContent, RunStartOptions, RestartConversationOptions, RuntimeAdapterOptions, ResumeOptions, RunOptions, RunResult, ThreadSummary, ThreadRecord, WorkspaceBundle } from "../contracts/types.js";
1
+ import type { ApprovalRecord, HarnessEvent, HarnessStreamItem, MessageContent, RunRecord, RunStartOptions, RestartConversationOptions, RuntimeAdapterOptions, ResumeOptions, RunOptions, RunResult, RunSummary, ThreadSummary, ThreadRecord, WorkspaceBundle } from "../contracts/types.js";
2
2
  import { type ToolMcpServerOptions } from "../mcp.js";
3
3
  import { type InventoryAgentRecord, type InventorySkillRecord } from "./inventory.js";
4
4
  import type { RequirementAssessmentOptions } from "./skill-requirements.js";
@@ -49,6 +49,12 @@ export declare class AgentHarnessRuntime {
49
49
  listThreads(filter?: {
50
50
  agentId?: string;
51
51
  }): Promise<ThreadSummary[]>;
52
+ listRuns(filter?: {
53
+ agentId?: string;
54
+ threadId?: string;
55
+ state?: RunSummary["state"];
56
+ }): Promise<RunSummary[]>;
57
+ getRun(runId: string): Promise<RunRecord | null>;
52
58
  private getSession;
53
59
  getThread(threadId: string): Promise<ThreadRecord | null>;
54
60
  listApprovals(filter?: {
@@ -94,6 +100,7 @@ export declare class AgentHarnessRuntime {
94
100
  run(options: RunOptions): Promise<RunResult>;
95
101
  streamEvents(options: RunStartOptions): AsyncGenerator<HarnessStreamItem>;
96
102
  resume(options: ResumeOptions): Promise<RunResult>;
103
+ private buildResumePayload;
97
104
  restartConversation(options: RestartConversationOptions): Promise<RunResult & {
98
105
  restart: Record<string, string>;
99
106
  }>;
@@ -15,7 +15,7 @@ import { FileBackedStore } from "./store.js";
15
15
  import { CheckpointMaintenanceLoop, discoverCheckpointMaintenanceTargets, readCheckpointMaintenanceConfig, } from "./checkpoint-maintenance.js";
16
16
  import { extractMessageText, normalizeMessageContent } from "../utils/message-content.js";
17
17
  import { createToolMcpServerFromTools, serveToolsOverStdioFromHarness } from "../mcp.js";
18
- import { getBindingAdapterKind, getBindingPrimaryTools, getBindingStoreConfig } from "./support/compiled-binding.js";
18
+ import { getBindingAdapterKind, getBindingPrimaryTools, getBindingStoreConfig, isDeepAgentBinding } from "./support/compiled-binding.js";
19
19
  import { describeWorkspaceInventory, listAgentSkills as listWorkspaceAgentSkills, } from "./inventory.js";
20
20
  export class AgentHarnessRuntime {
21
21
  workspace;
@@ -234,6 +234,24 @@ export class AgentHarnessRuntime {
234
234
  }
235
235
  return threadSummaries.filter((thread) => thread.agentId === filter.agentId);
236
236
  }
237
+ async listRuns(filter) {
238
+ const runs = await this.persistence.listRuns();
239
+ return runs.filter((run) => {
240
+ if (filter?.agentId && run.agentId !== filter.agentId) {
241
+ return false;
242
+ }
243
+ if (filter?.threadId && run.threadId !== filter.threadId) {
244
+ return false;
245
+ }
246
+ if (filter?.state && run.state !== filter.state) {
247
+ return false;
248
+ }
249
+ return true;
250
+ });
251
+ }
252
+ async getRun(runId) {
253
+ return this.persistence.getRun(runId);
254
+ }
237
255
  async getSession(threadId) {
238
256
  return this.persistence.getSession(threadId);
239
257
  }
@@ -383,6 +401,9 @@ export class AgentHarnessRuntime {
383
401
  const event = createHarnessEvent(threadId, runId, sequence, eventType, payload, source);
384
402
  await this.persistence.appendEvent(event);
385
403
  this.eventBus.publish(event);
404
+ if (this.threadMemorySync.shouldHandle(event)) {
405
+ await this.threadMemorySync.handleEvent(event);
406
+ }
386
407
  return event;
387
408
  }
388
409
  async ensureThreadStarted(selectedAgentId, binding, input, existingThreadId) {
@@ -1048,6 +1069,7 @@ export class AgentHarnessRuntime {
1048
1069
  if (!binding) {
1049
1070
  throw new Error(`Unknown agent ${thread.agentId}`);
1050
1071
  }
1072
+ const resumePayload = this.buildResumePayload(binding, approval, options);
1051
1073
  await this.persistence.setRunState(threadId, runId, "resuming", `checkpoints/${threadId}/${runId}/cp-1`);
1052
1074
  const releaseRunSlot = await this.acquireRunSlot(threadId, runId, "resuming");
1053
1075
  try {
@@ -1055,9 +1077,7 @@ export class AgentHarnessRuntime {
1055
1077
  kind: "approval-decision",
1056
1078
  savedAt: new Date().toISOString(),
1057
1079
  checkpointRef: `checkpoints/${threadId}/${runId}/cp-1`,
1058
- resumePayload: options.decision === "edit" && options.editedInput
1059
- ? { decision: "edit", editedInput: options.editedInput }
1060
- : (options.decision ?? "approve"),
1080
+ resumePayload,
1061
1081
  attempts: 0,
1062
1082
  });
1063
1083
  await this.emit(threadId, runId, 5, "run.resumed", {
@@ -1077,11 +1097,8 @@ export class AgentHarnessRuntime {
1077
1097
  const history = await this.persistence.listThreadMessages(threadId);
1078
1098
  const priorHistory = history.filter((message) => message.runId !== runId);
1079
1099
  const runInput = await this.loadRunInput(threadId, runId);
1080
- const resumeDecision = options.decision === "edit" && options.editedInput
1081
- ? { decision: "edit", editedInput: options.editedInput }
1082
- : (options.decision ?? "approve");
1083
1100
  try {
1084
- const actual = await this.runtimeAdapter.invoke(binding, "", threadId, runId, resumeDecision, priorHistory);
1101
+ const actual = await this.runtimeAdapter.invoke(binding, "", threadId, runId, resumePayload, priorHistory);
1085
1102
  await this.persistence.clearRecoveryIntent(threadId, runId);
1086
1103
  const finalized = await this.finalizeContinuedRun(threadId, runId, runInput, actual, {
1087
1104
  previousState: "resuming",
@@ -1102,6 +1119,43 @@ export class AgentHarnessRuntime {
1102
1119
  releaseRunSlot();
1103
1120
  }
1104
1121
  }
1122
+ buildResumePayload(binding, approval, options) {
1123
+ if (!isDeepAgentBinding(binding)) {
1124
+ return options.decision === "edit" && options.editedInput
1125
+ ? { decision: "edit", editedInput: options.editedInput }
1126
+ : (options.decision ?? "approve");
1127
+ }
1128
+ const decisionType = options.decision ?? "approve";
1129
+ if (decisionType === "edit" && options.editedInput) {
1130
+ return {
1131
+ decisions: [
1132
+ {
1133
+ type: "edit",
1134
+ editedAction: {
1135
+ name: approval.toolName,
1136
+ args: options.editedInput,
1137
+ },
1138
+ },
1139
+ ],
1140
+ };
1141
+ }
1142
+ if (decisionType === "reject") {
1143
+ return {
1144
+ decisions: [
1145
+ {
1146
+ type: "reject",
1147
+ },
1148
+ ],
1149
+ };
1150
+ }
1151
+ return {
1152
+ decisions: [
1153
+ {
1154
+ type: "approve",
1155
+ },
1156
+ ],
1157
+ };
1158
+ }
1105
1159
  async restartConversation(options) {
1106
1160
  const thread = await this.getSession(options.threadId);
1107
1161
  if (!thread) {
@@ -130,23 +130,26 @@ export function createPendingApproval(threadId, runId, checkpointRef, input, int
130
130
  }
131
131
  export function inferRoutingBindings(workspace) {
132
132
  const hostBindings = Array.from(workspace.bindings.values()).filter((binding) => binding.harnessRuntime.hostFacing);
133
- const researchBinding = hostBindings.find((binding) => binding.agent.id === "research-lite" || binding.agent.id === "research");
134
- const directBinding = hostBindings.find((binding) => binding.agent.id === "direct");
135
- const delegationHosts = hostBindings.filter((binding) => isDelegationCapableBinding(binding));
136
- const lightweightHosts = hostBindings.filter((binding) => !isDelegationCapableBinding(binding));
137
- const defaultOrchestratingHost = hostBindings.find((binding) => binding.agent.id === "orchestra") ??
133
+ const deepAgentHosts = hostBindings.filter((binding) => binding.agent.executionMode === "deepagent" || Boolean(binding.deepAgentParams));
134
+ const routingHosts = deepAgentHosts.length > 0 ? deepAgentHosts : hostBindings;
135
+ const researchBinding = routingHosts.find((binding) => binding.agent.id === "research-lite" || binding.agent.id === "research");
136
+ const directBinding = routingHosts.find((binding) => binding.agent.id === "direct");
137
+ const delegationHosts = routingHosts.filter((binding) => isDelegationCapableBinding(binding));
138
+ const lightweightHosts = routingHosts.filter((binding) => !isDelegationCapableBinding(binding));
139
+ const defaultOrchestratingHost = routingHosts.find((binding) => binding.agent.id === "orchestra") ??
138
140
  delegationHosts.find((binding) => (binding.deepAgentParams?.subagents.length ?? 0) > 0) ??
139
141
  delegationHosts[0];
140
142
  const delegationPreferredSecondary = delegationHosts.find((binding) => (binding.deepAgentParams?.subagents.length ?? 0) > 0) ??
141
143
  delegationHosts[0];
142
144
  const genericLightweightHost = lightweightHosts.find((binding) => binding.agent.id !== researchBinding?.agent.id);
143
- const primaryBinding = defaultOrchestratingHost ?? directBinding ?? genericLightweightHost ?? hostBindings[0];
145
+ const primaryBinding = defaultOrchestratingHost ?? directBinding ?? genericLightweightHost ?? routingHosts[0] ?? hostBindings[0];
144
146
  const secondaryBinding = genericLightweightHost && genericLightweightHost.agent.id !== primaryBinding?.agent.id
145
147
  ? genericLightweightHost
146
148
  : directBinding && directBinding.agent.id !== primaryBinding?.agent.id
147
149
  ? directBinding
148
150
  : delegationPreferredSecondary && delegationPreferredSecondary.agent.id !== primaryBinding?.agent.id
149
151
  ? delegationPreferredSecondary
150
- : hostBindings.find((binding) => binding.agent.id !== primaryBinding?.agent.id);
152
+ : routingHosts.find((binding) => binding.agent.id !== primaryBinding?.agent.id) ??
153
+ (deepAgentHosts.length > 0 ? undefined : hostBindings.find((binding) => binding.agent.id !== primaryBinding?.agent.id));
151
154
  return { primaryBinding, secondaryBinding, researchBinding, hostBindings };
152
155
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.65",
3
+ "version": "0.0.67",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",