@botbotgo/agent-harness 0.0.38 → 0.0.40

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/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.37";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.39";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.37";
1
+ export const AGENT_HARNESS_VERSION = "0.0.39";
@@ -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";
@@ -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, renderToolFailure, 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") {
@@ -761,7 +796,11 @@ export class AgentHarness {
761
796
  }
762
797
  async close() {
763
798
  await this.checkpointMaintenance?.stop();
764
- this.unsubscribeThreadMemorySync();
799
+ this.unregisterThreadMemorySync();
765
800
  await this.threadMemorySync.close();
766
801
  }
802
+ async stop() {
803
+ await this.close();
804
+ }
767
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";
@@ -133,11 +133,18 @@ export function inferRoutingBindings(workspace) {
133
133
  const directBinding = hostBindings.find((binding) => binding.agent.id === "direct");
134
134
  const langchainHost = hostBindings.find((binding) => binding.agent.executionMode === "langchain-v1" && binding.agent.id !== researchBinding?.agent.id);
135
135
  const deepagentHosts = hostBindings.filter((binding) => binding.agent.executionMode === "deepagent");
136
+ const defaultDeepagentHost = deepagentHosts.find((binding) => binding.agent.id === "orchestra") ??
137
+ deepagentHosts.find((binding) => (binding.deepAgentParams?.subagents.length ?? 0) > 0) ??
138
+ deepagentHosts[0];
136
139
  const deepagentWithSubagents = deepagentHosts.find((binding) => (binding.deepAgentParams?.subagents.length ?? 0) > 0) ??
137
140
  deepagentHosts[0];
138
- const primaryBinding = directBinding ?? langchainHost ?? hostBindings[0];
139
- const secondaryBinding = deepagentWithSubagents && deepagentWithSubagents.agent.id !== primaryBinding?.agent.id
140
- ? deepagentWithSubagents
141
- : hostBindings.find((binding) => binding.agent.id !== primaryBinding?.agent.id);
141
+ const primaryBinding = defaultDeepagentHost ?? directBinding ?? langchainHost ?? hostBindings[0];
142
+ const secondaryBinding = langchainHost && langchainHost.agent.id !== primaryBinding?.agent.id
143
+ ? langchainHost
144
+ : directBinding && directBinding.agent.id !== primaryBinding?.agent.id
145
+ ? directBinding
146
+ : deepagentWithSubagents && deepagentWithSubagents.agent.id !== primaryBinding?.agent.id
147
+ ? deepagentWithSubagents
148
+ : hostBindings.find((binding) => binding.agent.id !== primaryBinding?.agent.id);
142
149
  return { primaryBinding, secondaryBinding, researchBinding, hostBindings };
143
150
  }
@@ -1,12 +1,14 @@
1
- import type { HarnessEvent } from "../contracts/types.js";
1
+ import type { HarnessEvent, HarnessEventProjection } from "../contracts/types.js";
2
2
  import type { FilePersistence } from "../persistence/file-store.js";
3
- export declare class ThreadMemorySync {
3
+ export declare class ThreadMemorySync implements HarnessEventProjection {
4
4
  private readonly persistence;
5
5
  private readonly store?;
6
6
  private readonly pending;
7
+ readonly name = "thread-memory-sync";
7
8
  constructor(persistence: FilePersistence, store?: {
8
9
  put: (namespace: string[], key: string, value: Record<string, any>) => Promise<void>;
9
10
  } | undefined);
11
+ shouldHandle(event: HarnessEvent): boolean;
10
12
  handleEvent(event: HarnessEvent): Promise<void>;
11
13
  private syncThread;
12
14
  close(): Promise<void>;
@@ -42,16 +42,25 @@ function renderOpenApprovalsMarkdown(approvals) {
42
42
  }
43
43
  return lines.join("\n");
44
44
  }
45
+ const THREAD_MEMORY_EVENT_TYPES = new Set([
46
+ "run.state.changed",
47
+ "approval.resolved",
48
+ "approval.requested",
49
+ ]);
45
50
  export class ThreadMemorySync {
46
51
  persistence;
47
52
  store;
48
53
  pending = new Set();
54
+ name = "thread-memory-sync";
49
55
  constructor(persistence, store) {
50
56
  this.persistence = persistence;
51
57
  this.store = store;
52
58
  }
59
+ shouldHandle(event) {
60
+ return THREAD_MEMORY_EVENT_TYPES.has(event.eventType);
61
+ }
53
62
  async handleEvent(event) {
54
- if (event.eventType !== "run.state.changed" && event.eventType !== "approval.resolved" && event.eventType !== "approval.requested") {
63
+ if (!this.shouldHandle(event)) {
55
64
  return;
56
65
  }
57
66
  const task = this.syncThread(event.threadId)
@@ -52,9 +52,6 @@ function resolveAgentRuntimeName(agent) {
52
52
  return baseName;
53
53
  }
54
54
  const normalizedSourcePath = agent.sourcePath.split(path.sep).join("/");
55
- if (normalizedSourcePath.includes("/packages/builtins/")) {
56
- return `builtin.${baseName}`;
57
- }
58
55
  if (normalizedSourcePath.includes("/packages/framework/")) {
59
56
  return `core.${baseName}`;
60
57
  }
@@ -5,7 +5,7 @@ import { validateAgent, validateTopology } from "./validate.js";
5
5
  import { compileBinding } from "./agent-binding-compiler.js";
6
6
  import { discoverSubagents, ensureDiscoverySources } from "./support/discovery.js";
7
7
  import { collectAgentDiscoverySourceRefs, collectToolSourceRefs } from "./support/source-collectors.js";
8
- import { resolveRefId } from "./support/workspace-ref-utils.js";
8
+ import { getRoutingDefaultAgentId, getRoutingRules, resolveRefId, } from "./support/workspace-ref-utils.js";
9
9
  import { hydrateAgentMcpTools, hydrateResourceAndExternalTools } from "./tool-hydration.js";
10
10
  function collectParsedResources(refs) {
11
11
  const embeddings = new Map();
@@ -70,6 +70,20 @@ function compileBindings(workspaceRoot, refs, agentsList, models, tools) {
70
70
  }
71
71
  return bindings;
72
72
  }
73
+ function validateRoutingTargets(refs, agentsList) {
74
+ const hostFacingAgentIds = new Set(agentsList
75
+ .filter((agent) => !agentsList.some((owner) => owner.subagentRefs.some((ref) => resolveRefId(ref) === agent.id)))
76
+ .map((agent) => agent.id));
77
+ const defaultAgentId = getRoutingDefaultAgentId(refs);
78
+ if (defaultAgentId && !hostFacingAgentIds.has(defaultAgentId)) {
79
+ throw new Error(`Runtime routing.defaultAgentId references unknown host-facing agent ${defaultAgentId}`);
80
+ }
81
+ for (const rule of getRoutingRules(refs)) {
82
+ if (!hostFacingAgentIds.has(rule.agentId)) {
83
+ throw new Error(`Runtime routing.rules references unknown host-facing agent ${rule.agentId}`);
84
+ }
85
+ }
86
+ }
73
87
  export async function loadWorkspace(workspaceRoot, options = {}) {
74
88
  const loaded = await loadWorkspaceObjects(workspaceRoot, options);
75
89
  loaded.agents = await discoverSubagents(loaded.agents, workspaceRoot);
@@ -84,10 +98,10 @@ export async function loadWorkspace(workspaceRoot, options = {}) {
84
98
  await ensureResourceSources(toolSourceRefs, workspaceRoot);
85
99
  await hydrateResourceAndExternalTools(tools, toolSourceRefs, workspaceRoot);
86
100
  validateWorkspaceResources(embeddings, mcpServers, models, vectorStores, tools, loaded.agents);
101
+ validateRoutingTargets(loaded.refs, loaded.agents);
87
102
  return {
88
103
  workspaceRoot,
89
104
  resourceSources: [...toolSourceRefs],
90
- builtinSources: [...toolSourceRefs],
91
105
  refs: loaded.refs,
92
106
  embeddings,
93
107
  mcpServers,