@botbotgo/agent-harness 0.0.37 → 0.0.39

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 (38) hide show
  1. package/README.md +79 -137
  2. package/dist/api.d.ts +15 -12
  3. package/dist/api.js +25 -41
  4. package/dist/config/embedding-model.yaml +2 -2
  5. package/dist/config/workspace.yaml +37 -3
  6. package/dist/contracts/types.d.ts +17 -7
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.js +1 -1
  9. package/dist/mcp.d.ts +8 -3
  10. package/dist/mcp.js +10 -11
  11. package/dist/package-version.d.ts +1 -1
  12. package/dist/package-version.js +1 -1
  13. package/dist/resource/resource-impl.d.ts +2 -1
  14. package/dist/resource/resource-impl.js +16 -11
  15. package/dist/resource/resource.d.ts +1 -1
  16. package/dist/resource/resource.js +1 -1
  17. package/dist/runtime/agent-runtime-adapter.js +4 -2
  18. package/dist/runtime/event-bus.d.ts +5 -4
  19. package/dist/runtime/event-bus.js +7 -9
  20. package/dist/runtime/event-sink.d.ts +9 -0
  21. package/dist/runtime/event-sink.js +35 -0
  22. package/dist/runtime/harness.d.ts +21 -12
  23. package/dist/runtime/harness.js +81 -31
  24. package/dist/runtime/index.d.ts +3 -1
  25. package/dist/runtime/index.js +2 -1
  26. package/dist/runtime/parsing/stream-event-parsing.d.ts +2 -0
  27. package/dist/runtime/parsing/stream-event-parsing.js +35 -3
  28. package/dist/runtime/support/harness-support.d.ts +1 -0
  29. package/dist/runtime/support/harness-support.js +38 -4
  30. package/dist/runtime/thread-memory-sync.d.ts +4 -2
  31. package/dist/runtime/thread-memory-sync.js +10 -1
  32. package/dist/workspace/agent-binding-compiler.js +0 -3
  33. package/dist/workspace/compile.js +0 -1
  34. package/dist/workspace/support/discovery.js +11 -8
  35. package/dist/workspace/support/source-collectors.js +1 -1
  36. package/dist/workspace/support/workspace-ref-utils.d.ts +19 -0
  37. package/dist/workspace/support/workspace-ref-utils.js +112 -6
  38. package/package.json +3 -3
package/dist/mcp.js CHANGED
@@ -7,14 +7,13 @@ import { loadToolModuleDefinition } from "./tool-modules.js";
7
7
  function asResolvedTool(value) {
8
8
  return typeof value === "object" && value !== null ? value : null;
9
9
  }
10
- async function buildRawShapeForTool(harness, toolId, resolvedTool) {
11
- const tool = harness.getWorkspace().tools.get(toolId);
12
- if (!tool) {
10
+ async function buildRawShapeForTool(sourceTool, resolvedTool) {
11
+ if (!sourceTool) {
13
12
  return undefined;
14
13
  }
15
- if (tool.type === "function") {
16
- const imported = await import(pathToFileURL(tool.sourcePath).href);
17
- const definition = loadToolModuleDefinition(imported, tool.implementationName ?? tool.id);
14
+ if (sourceTool.type === "function") {
15
+ const imported = await import(pathToFileURL(sourceTool.sourcePath).href);
16
+ const definition = loadToolModuleDefinition(imported, sourceTool.implementationName ?? sourceTool.id);
18
17
  return typeof definition.schema.shape === "object" && definition.schema.shape !== null
19
18
  ? definition.schema.shape
20
19
  : undefined;
@@ -84,17 +83,17 @@ function jsonSchemaToZod(schema) {
84
83
  return z.any();
85
84
  }
86
85
  }
87
- export async function createToolMcpServerFromHarness(harness, options) {
86
+ export async function createToolMcpServerFromTools(tools, options) {
88
87
  const server = new McpServer({
89
88
  name: options.serverInfo?.name ?? `agent-harness-${options.agentId}`,
90
89
  version: options.serverInfo?.version ?? AGENT_HARNESS_VERSION,
91
90
  });
92
91
  const allowedNames = options.includeToolNames ? new Set(options.includeToolNames) : null;
93
- for (const { compiledTool, resolvedTool } of harness.resolveAgentTools(options.agentId)) {
92
+ for (const { compiledTool, resolvedTool, sourceTool } of tools) {
94
93
  if (allowedNames && !allowedNames.has(compiledTool.name)) {
95
94
  continue;
96
95
  }
97
- const rawShape = await buildRawShapeForTool(harness, compiledTool.id, resolvedTool);
96
+ const rawShape = await buildRawShapeForTool(sourceTool, resolvedTool);
98
97
  server.tool(compiledTool.name, compiledTool.description, rawShape ?? {}, async (input) => ({
99
98
  content: [
100
99
  {
@@ -106,8 +105,8 @@ export async function createToolMcpServerFromHarness(harness, options) {
106
105
  }
107
106
  return server;
108
107
  }
109
- export async function serveToolsOverStdioFromHarness(harness, options) {
110
- const server = await createToolMcpServerFromHarness(harness, options);
108
+ export async function serveToolsOverStdioFromHarness(tools, options) {
109
+ const server = await createToolMcpServerFromTools(tools, options);
111
110
  await server.connect(new StdioServerTransport());
112
111
  return server;
113
112
  }
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.36";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.38";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.36";
1
+ export const AGENT_HARNESS_VERSION = "0.0.38";
@@ -1,6 +1,6 @@
1
1
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
2
  import type { RuntimeAdapterOptions, WorkspaceBundle } from "../contracts/types.js";
3
- export declare function resolveLocalBuiltinsEntry(currentResourceDir: string, resolveInstalledEntry?: () => string | null): string;
3
+ export declare function resolveLocalResourceProviderEntry(currentResourceDir: string, resolveInstalledEntry?: () => string | null): string;
4
4
  export type ResourceToolInfo = {
5
5
  toolPath: string;
6
6
  backendOperation: string;
@@ -46,3 +46,4 @@ export declare const listBuiltinTools: typeof listResourceTools;
46
46
  export declare const listBuiltinToolsForSource: typeof listResourceToolsForSource;
47
47
  export declare const createBuiltinBackendResolver: typeof createResourceBackendResolver;
48
48
  export declare const createBuiltinToolResolver: typeof createResourceToolResolver;
49
+ export declare const resolveLocalBuiltinsEntry: typeof resolveLocalResourceProviderEntry;
@@ -16,7 +16,13 @@ import { resolveIsolatedResourceModulePath } from "./isolation.js";
16
16
  import { ensureExternalResourceSource, ensureExternalSource, isExternalSourceLocator, parseExternalSourceLocator } from "./sources.js";
17
17
  const resourceDir = path.dirname(fileURLToPath(import.meta.url));
18
18
  const require = createRequire(import.meta.url);
19
- function installedBuiltinsEntry() {
19
+ function installedResourceProviderEntry() {
20
+ try {
21
+ return require.resolve("@botbotgo/agent-harness-resource");
22
+ }
23
+ catch {
24
+ // Fall through to the legacy package name for backward compatibility only.
25
+ }
20
26
  try {
21
27
  return require.resolve("@botbotgo/agent-harness-builtin");
22
28
  }
@@ -24,12 +30,10 @@ function installedBuiltinsEntry() {
24
30
  return null;
25
31
  }
26
32
  }
27
- export function resolveLocalBuiltinsEntry(currentResourceDir, resolveInstalledEntry = installedBuiltinsEntry) {
33
+ export function resolveLocalResourceProviderEntry(currentResourceDir, resolveInstalledEntry = installedResourceProviderEntry) {
28
34
  const candidates = [
29
- path.resolve(currentResourceDir, "../../../builtins/dist/src/index.js"),
30
- path.resolve(currentResourceDir, "../../../agent-harness/packages/builtins/dist/src/index.js"),
31
- path.resolve(currentResourceDir, "../../../builtins/src/index.ts"),
32
- path.resolve(currentResourceDir, "../../../agent-harness/packages/builtins/src/index.ts"),
35
+ path.resolve(currentResourceDir, "../../../resource-package/dist/src/index.js"),
36
+ path.resolve(currentResourceDir, "../../../resource-package/src/index.ts"),
33
37
  resolveInstalledEntry(),
34
38
  ].filter((candidate) => typeof candidate === "string" && candidate.length > 0);
35
39
  for (const candidate of candidates) {
@@ -37,9 +41,9 @@ export function resolveLocalBuiltinsEntry(currentResourceDir, resolveInstalledEn
37
41
  return candidate;
38
42
  }
39
43
  }
40
- return candidates.at(-1) ?? path.resolve(currentResourceDir, "../../../agent-harness/packages/builtins/dist/src/index.js");
44
+ return candidates.at(-1) ?? path.resolve(currentResourceDir, "../../../resource-package/dist/src/index.js");
41
45
  }
42
- const builtinsEntry = resolveLocalBuiltinsEntry(resourceDir);
46
+ const resourceProviderEntry = resolveLocalResourceProviderEntry(resourceDir);
43
47
  async function loadLocalResource(entry) {
44
48
  if (!existsSync(entry)) {
45
49
  return null;
@@ -47,7 +51,7 @@ async function loadLocalResource(entry) {
47
51
  const imported = await import(pathToFileURL(entry).href);
48
52
  return (imported.default ?? imported);
49
53
  }
50
- const localResource = await loadLocalResource(builtinsEntry);
54
+ const localResource = await loadLocalResource(resourceProviderEntry);
51
55
  function listProviderTools(provider) {
52
56
  const rawTools = provider?.listResourceTools?.() ?? provider?.listBuiltinTools?.() ?? [];
53
57
  return rawTools.map((tool) => ({
@@ -509,7 +513,7 @@ export async function listResourceToolsForSource(source, workspaceRoot = process
509
513
  }
510
514
  export function createResourceBackendResolver(workspace) {
511
515
  const localResolver = createProviderBackendResolver(localResource, workspace);
512
- const remoteResolvers = (workspace.resourceSources ?? workspace.builtinSources ?? [])
516
+ const remoteResolvers = (workspace.resourceSources ?? [])
513
517
  .map((source) => remoteResourceCache.get(source))
514
518
  .filter((provider) => Boolean(provider))
515
519
  .map((provider) => createProviderBackendResolver(provider, workspace))
@@ -525,7 +529,7 @@ export function createResourceToolResolver(workspace, options = {}) {
525
529
  const functionResolver = createFunctionToolResolver(workspace);
526
530
  const mcpResolver = createMcpToolResolver(workspace);
527
531
  const localResolver = createProviderToolResolver(localResource, workspace, options);
528
- const remoteResolvers = (workspace.resourceSources ?? workspace.builtinSources ?? [])
532
+ const remoteResolvers = (workspace.resourceSources ?? [])
529
533
  .map((source) => remoteResourceCache.get(source))
530
534
  .filter((provider) => Boolean(provider))
531
535
  .map((provider) => createProviderToolResolver(provider, workspace, options))
@@ -551,3 +555,4 @@ export const listBuiltinTools = listResourceTools;
551
555
  export const listBuiltinToolsForSource = listResourceToolsForSource;
552
556
  export const createBuiltinBackendResolver = createResourceBackendResolver;
553
557
  export const createBuiltinToolResolver = createResourceToolResolver;
558
+ export const resolveLocalBuiltinsEntry = resolveLocalResourceProviderEntry;
@@ -1 +1 @@
1
- export { type ResourceToolInfo, ensureResourceSources, defaultResourceSkillsRoot, defaultResourceConfigRoot, listResourceTools, listResourceToolsForSource, createResourceBackendResolver, createResourceToolResolver, ensureBuiltinSources, builtinSkillsRoot, builtinConfigRoot, listBuiltinTools, listBuiltinToolsForSource, createBuiltinBackendResolver, createBuiltinToolResolver, resolveLocalBuiltinsEntry, } from "./resource-impl.js";
1
+ export { type ResourceToolInfo, ensureResourceSources, defaultResourceSkillsRoot, defaultResourceConfigRoot, listResourceTools, listResourceToolsForSource, createResourceBackendResolver, createResourceToolResolver, resolveLocalResourceProviderEntry, } from "./resource-impl.js";
@@ -1 +1 @@
1
- export { ensureResourceSources, defaultResourceSkillsRoot, defaultResourceConfigRoot, listResourceTools, listResourceToolsForSource, createResourceBackendResolver, createResourceToolResolver, ensureBuiltinSources, builtinSkillsRoot, builtinConfigRoot, listBuiltinTools, listBuiltinToolsForSource, createBuiltinBackendResolver, createBuiltinToolResolver, resolveLocalBuiltinsEntry, } from "./resource-impl.js";
1
+ export { ensureResourceSources, defaultResourceSkillsRoot, defaultResourceConfigRoot, listResourceTools, listResourceToolsForSource, createResourceBackendResolver, createResourceToolResolver, resolveLocalResourceProviderEntry, } from "./resource-impl.js";
@@ -656,6 +656,7 @@ export class AgentRuntimeAdapter {
656
656
  const events = await this.withTimeout(() => runnable.streamEvents(request, { configurable: { thread_id: threadId }, version: "v2" }), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "agent streamEvents start", "stream");
657
657
  const allowVisibleStreamDeltas = Boolean(binding.langchainAgentParams);
658
658
  let emittedOutput = "";
659
+ let emittedToolError = false;
659
660
  const seenTerminalOutputs = new Set();
660
661
  let lastStep = "";
661
662
  for await (const event of this.iterateWithTimeout(events, streamIdleTimeoutMs, "agent streamEvents", streamDeadlineAt, invokeTimeoutMs)) {
@@ -695,7 +696,8 @@ export class AgentRuntimeAdapter {
695
696
  }
696
697
  const toolResult = extractToolResult(event);
697
698
  if (toolResult) {
698
- yield { kind: "tool-result", toolName: toolResult.toolName, output: toolResult.output };
699
+ emittedToolError = emittedToolError || toolResult.isError === true;
700
+ yield { kind: "tool-result", toolName: toolResult.toolName, output: toolResult.output, isError: toolResult.isError };
699
701
  }
700
702
  const output = extractTerminalStreamOutput(event);
701
703
  if (output) {
@@ -713,7 +715,7 @@ export class AgentRuntimeAdapter {
713
715
  }
714
716
  }
715
717
  }
716
- if (emittedOutput) {
718
+ if (emittedOutput || emittedToolError) {
717
719
  return;
718
720
  }
719
721
  }
@@ -1,6 +1,7 @@
1
- import type { HarnessEvent } from "../contracts/types.js";
2
- export declare class EventBus {
3
- private readonly emitter;
1
+ import type { HarnessEvent, HarnessEventListener, HarnessEventProjection, RuntimeEventSink } from "../contracts/types.js";
2
+ export declare class EventBus implements RuntimeEventSink {
3
+ private readonly sink;
4
4
  publish(event: HarnessEvent): void;
5
- subscribe(listener: (event: HarnessEvent) => void): () => void;
5
+ subscribe(listener: HarnessEventListener): () => void;
6
+ registerProjection(projection: HarnessEventProjection): () => void;
6
7
  }
@@ -1,15 +1,13 @@
1
- import { EventEmitter } from "node:events";
2
- import { getEventSubscribers } from "../extensions.js";
1
+ import { createRuntimeEventSink } from "./event-sink.js";
3
2
  export class EventBus {
4
- emitter = new EventEmitter();
3
+ sink = createRuntimeEventSink();
5
4
  publish(event) {
6
- this.emitter.emit("event", event);
7
- for (const subscriber of getEventSubscribers()) {
8
- void Promise.resolve(subscriber.onEvent(event));
9
- }
5
+ this.sink.publish(event);
10
6
  }
11
7
  subscribe(listener) {
12
- this.emitter.on("event", listener);
13
- return () => this.emitter.off("event", listener);
8
+ return this.sink.subscribe(listener);
9
+ }
10
+ registerProjection(projection) {
11
+ return this.sink.registerProjection(projection);
14
12
  }
15
13
  }
@@ -0,0 +1,9 @@
1
+ import type { HarnessEvent, HarnessEventListener, HarnessEventProjection, RuntimeEventSink } from "../contracts/types.js";
2
+ export declare class RuntimeEventSinkImpl implements RuntimeEventSink {
3
+ private readonly emitter;
4
+ private readonly projections;
5
+ publish(event: HarnessEvent): void;
6
+ subscribe(listener: HarnessEventListener): () => void;
7
+ registerProjection(projection: HarnessEventProjection): () => void;
8
+ }
9
+ export declare function createRuntimeEventSink(): RuntimeEventSinkImpl;
@@ -0,0 +1,35 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { getEventSubscribers } from "../extensions.js";
3
+ function dispatchListener(listener, event) {
4
+ void Promise.resolve(listener(event));
5
+ }
6
+ function dispatchProjection(projection, event) {
7
+ if (projection.shouldHandle && !projection.shouldHandle(event)) {
8
+ return;
9
+ }
10
+ void Promise.resolve(projection.handleEvent(event));
11
+ }
12
+ export class RuntimeEventSinkImpl {
13
+ emitter = new EventEmitter();
14
+ projections = new Set();
15
+ publish(event) {
16
+ this.emitter.emit("event", event);
17
+ for (const projection of this.projections) {
18
+ dispatchProjection(projection, event);
19
+ }
20
+ for (const subscriber of getEventSubscribers()) {
21
+ dispatchListener(subscriber.onEvent, event);
22
+ }
23
+ }
24
+ subscribe(listener) {
25
+ this.emitter.on("event", listener);
26
+ return () => this.emitter.off("event", listener);
27
+ }
28
+ registerProjection(projection) {
29
+ this.projections.add(projection);
30
+ return () => this.projections.delete(projection);
31
+ }
32
+ }
33
+ export function createRuntimeEventSink() {
34
+ return new RuntimeEventSinkImpl();
35
+ }
@@ -1,5 +1,6 @@
1
- import type { ApprovalRecord, CompiledTool, HarnessEvent, HarnessStreamItem, MessageContent, RunStartOptions, RestartConversationOptions, RuntimeAdapterOptions, ResumeOptions, RunOptions, RunResult, ThreadSummary, ThreadRecord, WorkspaceBundle } from "../contracts/types.js";
2
- export declare class AgentHarness {
1
+ import type { ApprovalRecord, HarnessEvent, HarnessStreamItem, MessageContent, RunStartOptions, RestartConversationOptions, RuntimeAdapterOptions, ResumeOptions, RunOptions, RunResult, ThreadSummary, ThreadRecord, WorkspaceBundle } from "../contracts/types.js";
2
+ import { type ToolMcpServerOptions } from "../mcp.js";
3
+ export declare class AgentHarnessRuntime {
3
4
  private readonly workspace;
4
5
  private readonly runtimeAdapterOptions;
5
6
  private readonly eventBus;
@@ -12,8 +13,11 @@ export declare class AgentHarness {
12
13
  private readonly vectorStores;
13
14
  private readonly defaultStore;
14
15
  private readonly routingSystemPrompt?;
16
+ private readonly routingRules;
17
+ private readonly routingDefaultAgentId?;
18
+ private readonly modelRoutingEnabled;
15
19
  private readonly threadMemorySync;
16
- private readonly unsubscribeThreadMemorySync;
20
+ private readonly unregisterThreadMemorySync;
17
21
  private readonly resolvedRuntimeAdapterOptions;
18
22
  private readonly checkpointMaintenance;
19
23
  private listHostBindings;
@@ -27,19 +31,22 @@ export declare class AgentHarness {
27
31
  constructor(workspace: WorkspaceBundle, runtimeAdapterOptions?: RuntimeAdapterOptions);
28
32
  initialize(): Promise<void>;
29
33
  subscribe(listener: (event: HarnessEvent) => void): () => void;
30
- getWorkspace(): WorkspaceBundle;
31
- getBinding(agentId: string): WorkspaceBundle["bindings"] extends Map<any, infer T> ? T | undefined : never;
32
- listAgentTools(agentId: string): CompiledTool[];
33
- resolveAgentTools(agentId: string): Array<{
34
- compiledTool: CompiledTool;
35
- resolvedTool: unknown;
36
- }>;
37
- listSessions(filter?: {
34
+ private getBinding;
35
+ private listAgentTools;
36
+ private resolveAgentTools;
37
+ listThreads(filter?: {
38
38
  agentId?: string;
39
39
  }): Promise<ThreadSummary[]>;
40
40
  private getSession;
41
41
  getThread(threadId: string): Promise<ThreadRecord | null>;
42
- listPendingApprovals(): Promise<ApprovalRecord[]>;
42
+ listApprovals(filter?: {
43
+ status?: ApprovalRecord["status"];
44
+ threadId?: string;
45
+ runId?: string;
46
+ }): Promise<ApprovalRecord[]>;
47
+ getApproval(approvalId: string): Promise<ApprovalRecord | null>;
48
+ createToolMcpServer(options: ToolMcpServerOptions): Promise<import("@modelcontextprotocol/sdk/server/mcp.js").McpServer>;
49
+ serveToolsOverStdio(options: ToolMcpServerOptions): Promise<import("@modelcontextprotocol/sdk/server/mcp.js").McpServer>;
43
50
  routeAgent(input: MessageContent, options?: {
44
51
  threadId?: string;
45
52
  }): Promise<string>;
@@ -65,4 +72,6 @@ export declare class AgentHarness {
65
72
  restart: Record<string, string>;
66
73
  }>;
67
74
  close(): Promise<void>;
75
+ stop(): Promise<void>;
68
76
  }
77
+ export { AgentHarnessRuntime as AgentHarness };
@@ -5,8 +5,8 @@ import { AGENT_INTERRUPT_SENTINEL_PREFIX, AgentRuntimeAdapter, RuntimeOperationT
5
5
  import { createResourceBackendResolver, createResourceToolResolver } from "../resource/resource.js";
6
6
  import { EventBus } from "./event-bus.js";
7
7
  import { PolicyEngine } from "./policy-engine.js";
8
- import { getRoutingSystemPrompt } from "../workspace/support/workspace-ref-utils.js";
9
- import { createHarnessEvent, createPendingApproval, heuristicRoute, inferRoutingBindings, resolveDeterministicRouteIntent, renderRuntimeFailure, requiresResearchRoute, } from "./support/harness-support.js";
8
+ import { getRoutingDefaultAgentId, getRoutingRules, getRoutingSystemPrompt, isModelRoutingEnabled, matchRoutingRule, } from "../workspace/support/workspace-ref-utils.js";
9
+ import { createHarnessEvent, createPendingApproval, heuristicRoute, inferRoutingBindings, renderRuntimeFailure, renderToolFailure, } from "./support/harness-support.js";
10
10
  import { createCheckpointerForConfig, createStoreForConfig } from "./support/runtime-factories.js";
11
11
  import { resolveCompiledEmbeddingModel, resolveCompiledEmbeddingModelRef } from "./support/embedding-models.js";
12
12
  import { resolveCompiledVectorStore, resolveCompiledVectorStoreRef } from "./support/vector-stores.js";
@@ -14,7 +14,8 @@ import { ThreadMemorySync } from "./thread-memory-sync.js";
14
14
  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
- export class AgentHarness {
17
+ import { createToolMcpServerFromTools, serveToolsOverStdioFromHarness } from "../mcp.js";
18
+ export class AgentHarnessRuntime {
18
19
  workspace;
19
20
  runtimeAdapterOptions;
20
21
  eventBus = new EventBus();
@@ -27,8 +28,11 @@ export class AgentHarness {
27
28
  vectorStores = new Map();
28
29
  defaultStore;
29
30
  routingSystemPrompt;
31
+ routingRules;
32
+ routingDefaultAgentId;
33
+ modelRoutingEnabled;
30
34
  threadMemorySync;
31
- unsubscribeThreadMemorySync;
35
+ unregisterThreadMemorySync;
32
36
  resolvedRuntimeAdapterOptions;
33
37
  checkpointMaintenance;
34
38
  listHostBindings() {
@@ -144,10 +148,11 @@ export class AgentHarness {
144
148
  };
145
149
  this.runtimeAdapter = new AgentRuntimeAdapter(this.resolvedRuntimeAdapterOptions);
146
150
  this.routingSystemPrompt = getRoutingSystemPrompt(workspace.refs);
151
+ this.routingRules = getRoutingRules(workspace.refs);
152
+ this.routingDefaultAgentId = getRoutingDefaultAgentId(workspace.refs);
153
+ this.modelRoutingEnabled = isModelRoutingEnabled(workspace.refs);
147
154
  this.threadMemorySync = new ThreadMemorySync(this.persistence, this.defaultStore);
148
- this.unsubscribeThreadMemorySync = this.eventBus.subscribe((event) => {
149
- void this.threadMemorySync.handleEvent(event);
150
- });
155
+ this.unregisterThreadMemorySync = this.eventBus.registerProjection(this.threadMemorySync);
151
156
  const checkpointMaintenanceConfig = readCheckpointMaintenanceConfig(workspace);
152
157
  this.checkpointMaintenance = checkpointMaintenanceConfig
153
158
  ? new CheckpointMaintenanceLoop(discoverCheckpointMaintenanceTargets(workspace), checkpointMaintenanceConfig)
@@ -160,9 +165,6 @@ export class AgentHarness {
160
165
  subscribe(listener) {
161
166
  return this.eventBus.subscribe(listener);
162
167
  }
163
- getWorkspace() {
164
- return this.workspace;
165
- }
166
168
  getBinding(agentId) {
167
169
  return this.workspace.bindings.get(agentId);
168
170
  }
@@ -186,7 +188,7 @@ export class AgentHarness {
186
188
  resolvedTool: resolvedTools[index],
187
189
  }));
188
190
  }
189
- async listSessions(filter) {
191
+ async listThreads(filter) {
190
192
  const threadSummaries = await this.persistence.listSessions();
191
193
  if (!filter?.agentId) {
192
194
  return threadSummaries;
@@ -231,30 +233,63 @@ export class AgentHarness {
231
233
  : undefined,
232
234
  };
233
235
  }
234
- async listPendingApprovals() {
235
- return (await this.persistence.listApprovals()).filter((approval) => approval.status === "pending");
236
+ async listApprovals(filter) {
237
+ const approvals = filter?.threadId && filter?.runId
238
+ ? await this.persistence.getRunApprovals(filter.threadId, filter.runId)
239
+ : await this.persistence.listApprovals();
240
+ return approvals.filter((approval) => {
241
+ if (filter?.status && approval.status !== filter.status) {
242
+ return false;
243
+ }
244
+ if (filter?.threadId && approval.threadId !== filter.threadId) {
245
+ return false;
246
+ }
247
+ if (filter?.runId && approval.runId !== filter.runId) {
248
+ return false;
249
+ }
250
+ return true;
251
+ });
252
+ }
253
+ async getApproval(approvalId) {
254
+ return this.persistence.getApproval(approvalId);
255
+ }
256
+ async createToolMcpServer(options) {
257
+ const tools = this.resolveAgentTools(options.agentId).map(({ compiledTool, resolvedTool }) => ({
258
+ compiledTool,
259
+ resolvedTool,
260
+ sourceTool: this.workspace.tools.get(compiledTool.id),
261
+ }));
262
+ return createToolMcpServerFromTools(tools, options);
263
+ }
264
+ async serveToolsOverStdio(options) {
265
+ const tools = this.resolveAgentTools(options.agentId).map(({ compiledTool, resolvedTool }) => ({
266
+ compiledTool,
267
+ resolvedTool,
268
+ sourceTool: this.workspace.tools.get(compiledTool.id),
269
+ }));
270
+ return serveToolsOverStdioFromHarness(tools, options);
236
271
  }
237
272
  async routeAgent(input, options = {}) {
238
273
  const routingInput = await this.buildRoutingInput(input, options.threadId);
239
- const normalized = extractMessageText(input).trim().toLowerCase();
240
- const { primaryBinding, secondaryBinding, researchBinding } = inferRoutingBindings(this.workspace);
241
- const deterministicIntent = resolveDeterministicRouteIntent(normalized);
242
- if (requiresResearchRoute(normalized)) {
243
- const explicitResearchBinding = this.workspace.bindings.get("research-lite") ?? this.workspace.bindings.get("research");
244
- const hostResearchBinding = explicitResearchBinding?.harnessRuntime.hostFacing !== false ? explicitResearchBinding : researchBinding;
245
- return hostResearchBinding?.agent.id ?? secondaryBinding?.agent.id ?? primaryBinding?.agent.id ?? heuristicRoute(extractMessageText(input), primaryBinding, secondaryBinding);
246
- }
247
- if (secondaryBinding?.deepAgentParams) {
248
- return secondaryBinding.agent.id;
249
- }
250
- if (deterministicIntent === "primary") {
251
- return primaryBinding?.agent.id ?? heuristicRoute(extractMessageText(input), primaryBinding, secondaryBinding);
274
+ const rawInput = extractMessageText(input);
275
+ const { primaryBinding, secondaryBinding } = inferRoutingBindings(this.workspace);
276
+ const configuredRule = this.routingRules.find((rule) => matchRoutingRule(rawInput, rule, options));
277
+ if (configuredRule) {
278
+ const configuredBinding = this.workspace.bindings.get(configuredRule.agentId);
279
+ if (configuredBinding && configuredBinding.harnessRuntime.hostFacing !== false) {
280
+ return configuredBinding.agent.id;
281
+ }
252
282
  }
253
- if (deterministicIntent === "secondary") {
254
- return secondaryBinding?.agent.id ?? primaryBinding?.agent.id ?? heuristicRoute(extractMessageText(input), primaryBinding, secondaryBinding);
283
+ if (!this.modelRoutingEnabled) {
284
+ const defaultBinding = this.routingDefaultAgentId
285
+ ? this.workspace.bindings.get(this.routingDefaultAgentId)
286
+ : primaryBinding;
287
+ if (defaultBinding && defaultBinding.harnessRuntime.hostFacing !== false) {
288
+ return defaultBinding.agent.id;
289
+ }
255
290
  }
256
291
  if (!primaryBinding || !secondaryBinding) {
257
- return heuristicRoute(extractMessageText(input), primaryBinding, secondaryBinding);
292
+ return heuristicRoute(rawInput, primaryBinding, secondaryBinding);
258
293
  }
259
294
  try {
260
295
  return await this.runtimeAdapter.route(routingInput, primaryBinding, secondaryBinding, {
@@ -262,7 +297,7 @@ export class AgentHarness {
262
297
  });
263
298
  }
264
299
  catch {
265
- return heuristicRoute(extractMessageText(input), primaryBinding, secondaryBinding);
300
+ return heuristicRoute(rawInput, primaryBinding, secondaryBinding);
266
301
  }
267
302
  }
268
303
  async emit(threadId, runId, sequence, eventType, payload, source = "runtime") {
@@ -429,6 +464,7 @@ export class AgentHarness {
429
464
  await this.notifyListener(listeners.onToolResult, {
430
465
  toolName: item.toolName,
431
466
  output: item.output,
467
+ isError: item.isError,
432
468
  });
433
469
  }
434
470
  }
@@ -543,6 +579,7 @@ export class AgentHarness {
543
579
  try {
544
580
  const priorHistory = await this.loadPriorHistory(threadId, runId);
545
581
  let assistantOutput = "";
582
+ const toolErrors = [];
546
583
  for await (const chunk of this.runtimeAdapter.stream(binding, options.input, threadId, priorHistory)) {
547
584
  if (chunk) {
548
585
  const normalizedChunk = typeof chunk === "string"
@@ -591,6 +628,9 @@ export class AgentHarness {
591
628
  continue;
592
629
  }
593
630
  if (normalizedChunk.kind === "tool-result") {
631
+ if (normalizedChunk.isError) {
632
+ toolErrors.push(renderToolFailure(normalizedChunk.toolName, normalizedChunk.output));
633
+ }
594
634
  yield {
595
635
  type: "tool-result",
596
636
  threadId,
@@ -598,6 +638,7 @@ export class AgentHarness {
598
638
  agentId: selectedAgentId,
599
639
  toolName: normalizedChunk.toolName,
600
640
  output: normalizedChunk.output,
641
+ isError: normalizedChunk.isError,
601
642
  };
602
643
  continue;
603
644
  }
@@ -606,6 +647,11 @@ export class AgentHarness {
606
647
  yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, normalizedChunk.content);
607
648
  }
608
649
  }
650
+ if (!assistantOutput && toolErrors.length > 0) {
651
+ assistantOutput = toolErrors.join("\n\n");
652
+ emitted = true;
653
+ yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, assistantOutput);
654
+ }
609
655
  if (!assistantOutput) {
610
656
  const actual = await this.invokeWithHistory(binding, options.input, threadId, runId);
611
657
  if (actual.output) {
@@ -750,7 +796,11 @@ export class AgentHarness {
750
796
  }
751
797
  async close() {
752
798
  await this.checkpointMaintenance?.stop();
753
- this.unsubscribeThreadMemorySync();
799
+ this.unregisterThreadMemorySync();
754
800
  await this.threadMemorySync.close();
755
801
  }
802
+ async stop() {
803
+ await this.close();
804
+ }
756
805
  }
806
+ export { AgentHarnessRuntime as AgentHarness };
@@ -1,12 +1,14 @@
1
1
  export { AgentRuntimeAdapter, AGENT_INTERRUPT_SENTINEL_PREFIX } from "./agent-runtime-adapter.js";
2
2
  export { EventBus } from "./event-bus.js";
3
+ export { createRuntimeEventSink, RuntimeEventSinkImpl } from "./event-sink.js";
3
4
  export { FileCheckpointSaver } from "./file-checkpoint-saver.js";
4
5
  export { CheckpointMaintenanceLoop, discoverCheckpointMaintenanceTargets, maintainSqliteCheckpoints, readCheckpointMaintenanceConfig, } from "./checkpoint-maintenance.js";
5
6
  export { ManagedSqliteSaver } from "./sqlite-maintained-checkpoint-saver.js";
6
- export { AgentHarness } from "./harness.js";
7
+ export { AgentHarnessRuntime, AgentHarness } from "./harness.js";
7
8
  export { describeWorkspaceInventory, findAgentBinding, listAgentSkills, listAgentTools, listAvailableAgents, listSpecialists, } from "./inventory.js";
8
9
  export * from "./parsing/index.js";
9
10
  export { PolicyEngine } from "./policy-engine.js";
10
11
  export { createInMemoryStore, FileBackedStore } from "./store.js";
11
12
  export * from "./support/index.js";
12
13
  export { ThreadMemorySync } from "./thread-memory-sync.js";
14
+ export type { HarnessEventListener, HarnessEventProjection, RuntimeEventSink } from "../contracts/types.js";
@@ -1,9 +1,10 @@
1
1
  export { AgentRuntimeAdapter, AGENT_INTERRUPT_SENTINEL_PREFIX } from "./agent-runtime-adapter.js";
2
2
  export { EventBus } from "./event-bus.js";
3
+ export { createRuntimeEventSink, RuntimeEventSinkImpl } from "./event-sink.js";
3
4
  export { FileCheckpointSaver } from "./file-checkpoint-saver.js";
4
5
  export { CheckpointMaintenanceLoop, discoverCheckpointMaintenanceTargets, maintainSqliteCheckpoints, readCheckpointMaintenanceConfig, } from "./checkpoint-maintenance.js";
5
6
  export { ManagedSqliteSaver } from "./sqlite-maintained-checkpoint-saver.js";
6
- export { AgentHarness } from "./harness.js";
7
+ export { AgentHarnessRuntime, AgentHarness } from "./harness.js";
7
8
  export { describeWorkspaceInventory, findAgentBinding, listAgentSkills, listAgentTools, listAvailableAgents, listSpecialists, } from "./inventory.js";
8
9
  export * from "./parsing/index.js";
9
10
  export { PolicyEngine } from "./policy-engine.js";
@@ -14,6 +14,7 @@ export type RuntimeStreamChunk = {
14
14
  kind: "tool-result";
15
15
  toolName: string;
16
16
  output: unknown;
17
+ isError?: boolean;
17
18
  };
18
19
  export declare function extractTerminalStreamOutput(event: unknown): string;
19
20
  export declare function extractReasoningStreamOutput(event: unknown): string;
@@ -23,6 +24,7 @@ export declare function extractAgentStep(event: unknown): string | null;
23
24
  export declare function extractToolResult(event: unknown): {
24
25
  toolName: string;
25
26
  output: unknown;
27
+ isError?: boolean;
26
28
  } | null;
27
29
  export declare function extractInterruptPayload(event: unknown): string | null;
28
30
  export declare function normalizeTerminalOutputKey(value: string): string;