@botbotgo/agent-harness 0.0.352 → 0.0.354

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
@@ -1218,9 +1218,10 @@ ACP transport notes:
1218
1218
  - `serveAgUiHttp(runtime)` exposes an AG-UI-compatible HTTP SSE bridge that projects runtime lifecycle, safe progress commentary, text output, upstream thinking, step progress, and tool calls onto `RUN_*`, `STATUS_UPDATE`, `TEXT_MESSAGE_*`, `THINKING_TEXT_MESSAGE_*`, `STEP_*`, and `TOOL_CALL_*` events for UI clients. `botbotgo ag-ui start|stop` now manages that HTTP bridge in the same workspace-local service registry as ACP, A2A, and runtime MCP.
1219
1219
  - `createRuntimeMcpServer(runtime)`, `serveRuntimeMcpOverStdio(runtime)`, and `serveRuntimeMcpOverStreamableHttp(runtime)` expose the persisted runtime control surface itself as MCP tools, including sessions, requests, approvals, artifacts, events, and package export helpers. `botbotgo mcp serve --transport streamable-http --host 127.0.0.1 --port 8090` serves the same tool surface over Streamable HTTP, and `botbotgo mcp start|stop` manages that background endpoint for one workspace.
1220
1220
  - `listRequestEvents(...)`, `listRequestTraceItems(...)`, and `exportRequestPackage(...)` are the request-first inspection helpers.
1221
+ - `analyzeBoundaries(runtime)` returns a workspace boundary report covering agent, subagent, tool, and skill surfaces, including structural findings such as missing subagent references, unreferenced tools or skills, and skill allow-lists that do not match the owning agent's exposed tools.
1221
1222
  - `exportRequestPackage(...)` and `exportSessionPackage(...)` package stable runtime records, transcript, approvals, events, artifacts, and governance evidence for operator tooling without reaching into persistence internals.
1222
1223
  - `runtime/default.governance.remoteMcp` can now deny or allow specific MCP servers, raise approval requirements by transport, and stamp transport-based risk tiers into runtime governance bundles. MCP server catalogs can also declare trust tier, access mode, tenant scope, approval policy, prompt-injection risk, and OAuth scope metadata so governance bundles capture why one remote tool is treated as high-risk. Tool policy overrides can also set `decisionMode: manual | auto-approve | auto-reject | deny-and-continue` so operator evidence and execution behavior stay aligned.
1223
1224
  - Protocol responsibilities stay split on purpose: ACP is the primary editor/client runtime boundary, A2A is the streaming-capable agent-platform bridge with polling compatibility, AG-UI is the UI event surface, and runtime MCP is the operator-facing control plane exported as MCP tools.
1224
1225
  - `runtime/default.observability.tracing` can now describe exporter metadata such as OTLP endpoints and propagation mode, so frozen runtime snapshots keep trace-correlation plus operator-visible export context without exposing backend-private span internals.
1225
- - `agent-harness runtime overview`, `agent-harness runtime health`, `agent-harness runtime approvals list|watch`, `agent-harness runtime requests list|tail`, and `agent-harness runtime export request|session` provide a thin operator CLI over persisted runtime health, queue pressure, governance risk, approval queues, active request state, and audit-ready evidence packages.
1226
+ - `agent-harness runtime overview`, `agent-harness runtime boundaries`, `agent-harness runtime health`, `agent-harness runtime approvals list|watch`, `agent-harness runtime requests list|tail`, and `agent-harness runtime export request|session` provide a thin operator CLI over workspace boundary analysis, persisted runtime health, queue pressure, governance risk, approval queues, active request state, and audit-ready evidence packages.
1226
1227
  - detailed A2A adapter guidance lives in [`docs/a2a-bridge.md`](docs/a2a-bridge.md)
package/README.zh.md CHANGED
@@ -1175,9 +1175,10 @@ ACP transport 说明:
1175
1175
  - `serveAgUiHttp(runtime)` 提供 AG-UI HTTP SSE bridge,把 runtime 生命周期、安全进度播报、文本输出、upstream thinking、step 进度与 tool call 投影成 `RUN_*`、`STATUS_UPDATE`、`TEXT_MESSAGE_*`、`THINKING_TEXT_MESSAGE_*`、`STEP_*` 与 `TOOL_CALL_*` 事件,便于 UI 客户端直接接入。`botbotgo ag-ui start|stop` 现在也会把这条 HTTP bridge 托管进与 ACP、A2A、runtime MCP 相同的 workspace 本地服务注册表中。
1176
1176
  - `createRuntimeMcpServer(runtime)`、`serveRuntimeMcpOverStdio(runtime)` 与 `serveRuntimeMcpOverStreamableHttp(runtime)` 会把持久化 runtime 控制面本身暴露成 MCP tools,包括 sessions、requests、approvals、artifacts、events 与 package export helpers。`botbotgo mcp serve --transport streamable-http --host 127.0.0.1 --port 8090` 会把同一套控制面作为 Streamable HTTP 暴露出去,而 `botbotgo mcp start|stop` 可直接托管该后台 endpoint。
1177
1177
  - `listRequestEvents(...)`、`listRequestTraceItems(...)` 与 `exportRequestPackage(...)` 是 request-first 的检查 helper。
1178
+ - `analyzeBoundaries(runtime)` 会返回 workspace boundary report,覆盖 agent、subagent、tool 与 skill 的可见边界,并指出缺失 subagent 引用、未被引用的 tools/skills、以及 skill allow-list 与所属 agent 暴露工具不匹配等结构性问题。
1178
1179
  - `exportRequestPackage(...)` 与 `exportSessionPackage(...)` 可把稳定 runtime 记录、transcript、approvals、events、artifacts 与 governance evidence 一起打包给管理工具,而不必直接访问 persistence 内部实现。
1179
1180
  - `runtime/default.governance.remoteMcp` 现在可以按 MCP server 或 transport 做 allow/deny、审批升级,并把 transport 风险等级写进 runtime governance bundles。MCP server catalog 也可以声明 trust tier、access mode、tenant scope、approval policy、prompt-injection risk 与 OAuth scope 元数据,让治理快照能解释为什么某个远端工具被视为高风险。tool policy override 也可以声明 `decisionMode: manual | auto-approve | auto-reject | deny-and-continue`,让治理快照与实际执行路径保持一致。
1180
1181
  - 协议分工要继续保持清晰:ACP 是 editor / client 的主运行时边界,A2A 是支持 streaming 且兼容轮询的 agent-platform bridge,AG-UI 是 UI 事件面,runtime MCP 是以 MCP tools 暴露的 operator control plane。
1181
1182
  - `runtime/default.observability.tracing` 现在可描述 OTLP endpoint 和 propagation mode 这类 exporter 元数据,使冻结的 runtime snapshot 在保留 trace correlation 的同时,也能保留有用的导出上下文,而不暴露 backend 私有 span 细节。
1182
- - `agent-harness runtime overview`、`agent-harness runtime health`、`agent-harness runtime approvals list|watch`、`agent-harness runtime requests list|tail` 与 `agent-harness runtime export request|session` 提供了一层轻量 CLI,可直接查看 runtime health、queue pressure、governance risk、审批队列、运行状态与可审计证据包。
1183
+ - `agent-harness runtime overview`、`agent-harness runtime boundaries`、`agent-harness runtime health`、`agent-harness runtime approvals list|watch`、`agent-harness runtime requests list|tail` 与 `agent-harness runtime export request|session` 提供了一层轻量 CLI,可直接查看 workspace boundary analysis、runtime health、queue pressure、governance risk、审批队列、运行状态与可审计证据包。
1183
1184
  - 更详细的 A2A 适配层开发说明见 [`docs/a2a-bridge.md`](docs/a2a-bridge.md)
package/dist/api.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import type { ArtifactListing, CancelOptions, InvocationEnvelope, ListMemoriesInput, ListMemoriesResult, MemoryRecord, MemorizeInput, MemorizeResult, MessageContent, RequestDataEvent, RecallInput, RecallResult, RemoveMemoryInput, RequestEventSnapshot, RequestPlanState, RequestDecisionOptions, RequestRecord, RequestResult as InternalRequestResult, RequestStartOptions, RequestSummary, RuntimeArtifactWriteInput, ResumeOptions, RequestListeners, RuntimeHealthSnapshot, RuntimeGovernanceEvidence, RuntimeGovernanceDiagnostics, RuntimeOperatorOverview, RuntimeQueueDiagnostics, RuntimeAdapterOptions, RuntimeEvaluationExport, RuntimeEvaluationExportInput, RuntimeEvaluationArtifact, RuntimeEvaluationReplayInput, RuntimeEvaluationReplayResult as InternalRuntimeEvaluationReplayResult, RuntimeSessionPackage, RuntimeSessionPackageInput, SessionListSummary, SessionRecord, SessionSummary, TranscriptMessage, UpdateMemoryInput, WorkspaceLoadOptions } from "./contracts/types.js";
2
2
  import { AgentHarnessRuntime } from "./runtime/harness.js";
3
3
  import type { InventoryAgentRecord, InventorySkillRecord } from "./runtime/harness/system/inventory.js";
4
+ import type { BoundaryAnalysisOptions, WorkspaceBoundaryAnalysis } from "./runtime/harness/system/boundary-analysis.js";
5
+ export type { BoundaryAgentSurface, BoundaryAnalysisOptions, BoundaryAnalysisSummary, BoundaryFinding, BoundaryFindingSeverity, WorkspaceBoundaryAnalysis, } from "./runtime/harness/system/boundary-analysis.js";
4
6
  import type { RequirementAssessmentOptions } from "./runtime/harness/system/skill-requirements.js";
5
7
  import type { RuntimeMcpServerOptions, RuntimeMcpStreamableHttpServerOptions, ToolMcpServerOptions } from "./mcp.js";
6
8
  export { AgentHarnessAcpServer, createAcpServer } from "./acp.js";
@@ -202,6 +204,7 @@ export declare function describeInventory(runtime: AgentHarnessRuntime, options?
202
204
  workspaceRoot: string;
203
205
  agents: InventoryAgentRecord[];
204
206
  };
207
+ export declare function analyzeBoundaries(runtime: AgentHarnessRuntime, options?: BoundaryAnalysisOptions): WorkspaceBoundaryAnalysis;
205
208
  export declare function resolveApproval(runtime: AgentHarnessRuntime, options: ResumeOptions): Promise<PublicRequestResult>;
206
209
  export declare function cancelRequest(runtime: AgentHarnessRuntime, options: CancelOptions): Promise<PublicRequestResult>;
207
210
  export declare function stop(runtime: AgentHarnessRuntime): Promise<void>;
package/dist/api.js CHANGED
@@ -366,6 +366,9 @@ export function getAgent(runtime, agentId, options) {
366
366
  export function describeInventory(runtime, options) {
367
367
  return runtime.describeWorkspaceInventory(options);
368
368
  }
369
+ export function analyzeBoundaries(runtime, options) {
370
+ return runtime.analyzeWorkspaceBoundaries(options);
371
+ }
369
372
  export async function resolveApproval(runtime, options) {
370
373
  return toPublicRequestResult(await runtime.resume(toInternalResumeOptions(options)));
371
374
  }
@@ -16,6 +16,7 @@ export function renderUsage() {
16
16
  agent-harness ag-ui start [--workspace <path>] [--host <hostname>] [--port <port>]
17
17
  agent-harness ag-ui stop [--workspace <path>]
18
18
  agent-harness runtime overview [--workspace <path>] [--limit <n>] [--json]
19
+ agent-harness runtime boundaries [--workspace <path>] [--json]
19
20
  agent-harness runtime health [--workspace <path>] [--json]
20
21
  agent-harness runtime approvals list [--workspace <path>] [--status <pending|approved|edited|rejected|expired>] [--json]
21
22
  agent-harness runtime approvals watch [--workspace <path>] [--status <pending|approved|edited|rejected|expired>] [--poll-ms <ms>] [--once] [--json]
@@ -1,4 +1,4 @@
1
- import { renderApprovalList, renderHealthSnapshot, renderJson, renderOperatorOverview, renderRequestList } from "./runtime-output.js";
1
+ import { renderApprovalList, renderBoundaryAnalysis, renderHealthSnapshot, renderJson, renderOperatorOverview, renderRequestList, } from "./runtime-output.js";
2
2
  import { parseRuntimeExportOptions, parseRuntimeInspectOptions, parseScheduledRunOptions, renderUsage } from "./options.js";
3
3
  import { resolveCliWorkspaceRoot, validateCliWorkspaceRoot } from "./workspace.js";
4
4
  function resolveValidatedWorkspace(cwd, workspaceRoot, stderr) {
@@ -128,6 +128,17 @@ export async function handleRuntimeCommand(subcommandAndArgs, io, deps) {
128
128
  if (!workspacePath) {
129
129
  return 1;
130
130
  }
131
+ if (subcommand === "boundaries") {
132
+ const runtime = await createHarness(workspacePath);
133
+ try {
134
+ const analysis = runtime.analyzeWorkspaceBoundaries();
135
+ stdout(parsed.json ? renderJson(analysis) : renderBoundaryAnalysis(analysis, workspacePath));
136
+ return analysis.summary.errorCount > 0 ? 2 : 0;
137
+ }
138
+ finally {
139
+ await runtime.stop();
140
+ }
141
+ }
131
142
  const shouldUseClient = subcommand === "health"
132
143
  || subcommand === "overview"
133
144
  || (subcommand === "approvals" && (nestedCommand === "list" || nestedCommand === "watch"))
@@ -3,3 +3,4 @@ export declare function renderHealthSnapshot(snapshot: Record<string, unknown>,
3
3
  export declare function renderApprovalList(approvals: Array<Record<string, unknown>>): string;
4
4
  export declare function renderRequestList(requests: Array<Record<string, unknown>>): string;
5
5
  export declare function renderOperatorOverview(overview: Record<string, unknown>, workspacePath: string): string;
6
+ export declare function renderBoundaryAnalysis(analysis: Record<string, unknown>, workspacePath: string): string;
@@ -122,3 +122,34 @@ export function renderOperatorOverview(overview, workspacePath) {
122
122
  }
123
123
  return `${lines.join("\n")}\n`;
124
124
  }
125
+ export function renderBoundaryAnalysis(analysis, workspacePath) {
126
+ const lines = [];
127
+ const summary = isObject(analysis.summary) ? analysis.summary : {};
128
+ const findings = Array.isArray(analysis.findings) ? analysis.findings.filter(isObject) : [];
129
+ const errors = typeof summary.errorCount === "number" ? summary.errorCount : 0;
130
+ const warnings = typeof summary.warningCount === "number" ? summary.warningCount : 0;
131
+ const info = typeof summary.infoCount === "number" ? summary.infoCount : 0;
132
+ lines.push(`Runtime boundary analysis ${workspacePath}`);
133
+ lines.push(`Findings: errors=${errors} warnings=${warnings} info=${info}`);
134
+ const agents = typeof summary.agentCount === "number" ? summary.agentCount : undefined;
135
+ const tools = typeof summary.toolCount === "number" ? summary.toolCount : undefined;
136
+ const skills = typeof summary.skillCount === "number" ? summary.skillCount : undefined;
137
+ if (agents !== undefined || tools !== undefined || skills !== undefined) {
138
+ lines.push(`Inventory: agents=${agents ?? "unknown"} tools=${tools ?? "unknown"} skills=${skills ?? "unknown"}`);
139
+ }
140
+ if (findings.length === 0) {
141
+ lines.push("No boundary findings.");
142
+ return `${lines.join("\n")}\n`;
143
+ }
144
+ lines.push("Boundary findings:");
145
+ for (const finding of findings) {
146
+ const severity = typeof finding.severity === "string" ? finding.severity : "unknown";
147
+ const code = typeof finding.code === "string" ? finding.code : "unknown";
148
+ const message = typeof finding.message === "string" ? finding.message : "";
149
+ const agent = typeof finding.agentId === "string" ? ` agent=${finding.agentId}` : "";
150
+ const tool = typeof finding.toolName === "string" ? ` tool=${finding.toolName}` : "";
151
+ const skill = typeof finding.skillName === "string" ? ` skill=${finding.skillName}` : "";
152
+ lines.push(` - ${severity} ${code}:${agent}${tool}${skill}${message ? ` ${message}` : ""}`);
153
+ }
154
+ return `${lines.join("\n")}\n`;
155
+ }
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- export { AgentHarnessAcpServer, AgentHarnessRuntime, cancelRequest, createAgentHarness, createAcpServer, createAcpStdioClient, createRuntimeMcpServer, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, exportEvaluationBundle, exportFlow, exportSequence, exportRequestPackage, exportSessionPackage, replayEvaluationBundle, getArtifact, getAgent, getApproval, getOperatorOverview, getRequestPlanState, getRequest, getHealth, listMemories, listRequestTraceItems, getSession, listAgentSkills, listRequestArtifacts, listApprovals, listRequests, listRequestEvents, listSessionSummaries, listSessions, memorize, normalizeUserChatInput, recordArtifact, request, recall, removeMemory, resolveApproval, serveA2aHttp, serveAcpHttp, serveAcpStdio, serveAgUiHttp, serveRuntimeMcpOverStdio, serveRuntimeMcpOverStreamableHttp, serveToolsOverStdio, subscribe, stop, updateMemory, } from "./api.js";
1
+ export { AgentHarnessAcpServer, AgentHarnessRuntime, cancelRequest, createAgentHarness, createAcpServer, createAcpStdioClient, createRuntimeMcpServer, createUpstreamTimelineReducer, createToolMcpServer, analyzeBoundaries, deleteSession, describeInventory, exportEvaluationBundle, exportFlow, exportSequence, exportRequestPackage, exportSessionPackage, replayEvaluationBundle, getArtifact, getAgent, getApproval, getOperatorOverview, getRequestPlanState, getRequest, getHealth, listMemories, listRequestTraceItems, getSession, listAgentSkills, listRequestArtifacts, listApprovals, listRequests, listRequestEvents, listSessionSummaries, listSessions, memorize, normalizeUserChatInput, recordArtifact, request, recall, removeMemory, resolveApproval, serveA2aHttp, serveAcpHttp, serveAcpStdio, serveAgUiHttp, serveRuntimeMcpOverStdio, serveRuntimeMcpOverStreamableHttp, serveToolsOverStdio, subscribe, stop, updateMemory, } from "./api.js";
2
2
  export { AcpHarnessClient, InProcessHarnessClient, createAcpHarnessClient, createAcpHttpHarnessClient, createAcpStdioHarnessClient, createAgentHarnessClient, createInProcessHarnessClient, } from "./client.js";
3
3
  export { createKnowledgeModule, readKnowledgeRuntimeConfig } from "./knowledge/index.js";
4
4
  export { readProceduralMemoryRuntimeConfig } from "./knowledge/procedural/index.js";
5
5
  export type { AcpApproval, AcpHttpClient, AcpHttpClientOptions, AcpArtifact, AcpEventNotification, AcpNotification, AcpJsonRpcError, AcpJsonRpcRequest, AcpJsonRpcResponse, AcpJsonRpcSuccess, AcpRequestRecord, AcpRequestParams, AcpServerCapabilities, AcpSessionRecord, AcpStreamNotification, AcpStdioClient, AcpStdioClientOptions, } from "./acp.js";
6
- export type { Approval, CreateAgentHarnessOptions, ListMemoriesInput, ListMemoriesResult, MemoryDecision, MemoryKind, MemoryRecord, MemoryScope, MemorizeInput, MemorizeResult, NormalizeUserChatInputOptions, OperatorOverview, RecordArtifactInput, PublicRequestListeners, RequestData, RequestArtifactListing, RequestEvent, RequestEventType, RequestSnapshot, RequestPlanState, RequestPackage, RequestPackageInput, RequestFlowGraphInput, RequestResult, RequestTraceItem, RecallInput, RecallResult, RemoveMemoryInput, RuntimeEvaluationExport, RuntimeEvaluationExportInput, RuntimeEvaluationReplayInput, RuntimeEvaluationReplayResult, SessionListSummary, RuntimeSessionPackage, RuntimeSessionPackageInput, UpdateMemoryInput, UserChatInput, UserChatMessage, } from "./api.js";
6
+ export type { Approval, BoundaryAgentSurface, BoundaryAnalysisOptions, BoundaryAnalysisSummary, BoundaryFinding, BoundaryFindingSeverity, CreateAgentHarnessOptions, ListMemoriesInput, ListMemoriesResult, MemoryDecision, MemoryKind, MemoryRecord, MemoryScope, MemorizeInput, MemorizeResult, NormalizeUserChatInputOptions, OperatorOverview, RecordArtifactInput, PublicRequestListeners, RequestData, RequestArtifactListing, RequestEvent, RequestEventType, RequestSnapshot, RequestPlanState, RequestPackage, RequestPackageInput, RequestFlowGraphInput, RequestResult, RequestTraceItem, RecallInput, RecallResult, RemoveMemoryInput, RuntimeEvaluationExport, RuntimeEvaluationExportInput, RuntimeEvaluationReplayInput, RuntimeEvaluationReplayResult, SessionListSummary, RuntimeSessionPackage, RuntimeSessionPackageInput, WorkspaceBoundaryAnalysis, UpdateMemoryInput, UserChatInput, UserChatMessage, } from "./api.js";
7
7
  export type { AcpHarnessTransport, HarnessClient, HarnessClientApprovalFilter, HarnessClientRequestFilter, HarnessClientRequestOptions, HarnessClientRequestResult, HarnessClientRequestStartOptions, } from "./client.js";
8
8
  export type { KnowledgeListInput, KnowledgeMemorizeInput, KnowledgeModule, KnowledgeModuleDependencies, KnowledgeRecallInput, KnowledgeRuntimeConfig, KnowledgeRuntimeContext, } from "./knowledge/index.js";
9
9
  export type { ProceduralMemoryBackgroundConfig, ProceduralMemoryFormationConfig, ProceduralMemoryMaintenanceConfig, ProceduralMemoryMaintenanceIdleConfig, ProceduralMemoryMaintenanceScheduleConfig, ProceduralMemoryProviderConfig, ProceduralMemoryRetrievalConfig, ProceduralMemoryRuntimeConfig, } from "./knowledge/procedural/index.js";
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { AgentHarnessAcpServer, AgentHarnessRuntime, cancelRequest, createAgentHarness, createAcpServer, createAcpStdioClient, createRuntimeMcpServer, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, exportEvaluationBundle, exportFlow, exportSequence, exportRequestPackage, exportSessionPackage, replayEvaluationBundle, getArtifact, getAgent, getApproval, getOperatorOverview, getRequestPlanState, getRequest, getHealth, listMemories, listRequestTraceItems, getSession, listAgentSkills, listRequestArtifacts, listApprovals, listRequests, listRequestEvents, listSessionSummaries, listSessions, memorize, normalizeUserChatInput, recordArtifact, request, recall, removeMemory, resolveApproval, serveA2aHttp, serveAcpHttp, serveAcpStdio, serveAgUiHttp, serveRuntimeMcpOverStdio, serveRuntimeMcpOverStreamableHttp, serveToolsOverStdio, subscribe, stop, updateMemory, } from "./api.js";
1
+ export { AgentHarnessAcpServer, AgentHarnessRuntime, cancelRequest, createAgentHarness, createAcpServer, createAcpStdioClient, createRuntimeMcpServer, createUpstreamTimelineReducer, createToolMcpServer, analyzeBoundaries, deleteSession, describeInventory, exportEvaluationBundle, exportFlow, exportSequence, exportRequestPackage, exportSessionPackage, replayEvaluationBundle, getArtifact, getAgent, getApproval, getOperatorOverview, getRequestPlanState, getRequest, getHealth, listMemories, listRequestTraceItems, getSession, listAgentSkills, listRequestArtifacts, listApprovals, listRequests, listRequestEvents, listSessionSummaries, listSessions, memorize, normalizeUserChatInput, recordArtifact, request, recall, removeMemory, resolveApproval, serveA2aHttp, serveAcpHttp, serveAcpStdio, serveAgUiHttp, serveRuntimeMcpOverStdio, serveRuntimeMcpOverStreamableHttp, serveToolsOverStdio, subscribe, stop, updateMemory, } from "./api.js";
2
2
  export { AcpHarnessClient, InProcessHarnessClient, createAcpHarnessClient, createAcpHttpHarnessClient, createAcpStdioHarnessClient, createAgentHarnessClient, createInProcessHarnessClient, } from "./client.js";
3
3
  export { createKnowledgeModule, readKnowledgeRuntimeConfig } from "./knowledge/index.js";
4
4
  export { readProceduralMemoryRuntimeConfig } from "./knowledge/procedural/index.js";
@@ -1,2 +1,2 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.352";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.354";
2
2
  export declare const AGENT_HARNESS_RELEASE_DATE = "2026-04-24";
@@ -1,2 +1,2 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.352";
1
+ export const AGENT_HARNESS_VERSION = "0.0.354";
2
2
  export const AGENT_HARNESS_RELEASE_DATE = "2026-04-24";
@@ -23,6 +23,7 @@ const PROMPTED_JSON_FINAL_TOOL_CALL_REMINDER = [
23
23
  "If a TOOL_RESULT is already present for the requested work, do not repeat that tool call; answer normally.",
24
24
  'Shape: {"name":"tool_name","arguments":{}}',
25
25
  ].join("\n");
26
+ const NO_THINK_CONTROL_TOKEN = "/no_think";
26
27
  function readModelText(value) {
27
28
  if (typeof value === "string") {
28
29
  return value.trim();
@@ -455,16 +456,21 @@ function formatBoundToolInstruction(tool) {
455
456
  `Arguments JSON schema: ${JSON.stringify(schema)}`,
456
457
  ].filter(Boolean).join("\n");
457
458
  }
458
- function withPromptedJsonToolPrompt(input, tools) {
459
+ function withPromptedJsonToolPrompt(input, tools, options = {}) {
459
460
  const toolInstructions = tools.map((tool) => formatBoundToolInstruction(tool)).filter((value) => Boolean(value));
460
461
  if (toolInstructions.length === 0) {
461
462
  return stringifyNodeLlamaCppInput(input);
462
463
  }
463
464
  const systemContent = `${NODE_LLAMA_CPP_TOOL_CALL_INSTRUCTION}\n\n${toolInstructions.join("\n\n")}`;
464
465
  const prompt = stringifyNodeLlamaCppInput(input);
465
- return [systemContent, prompt, PROMPTED_JSON_FINAL_TOOL_CALL_REMINDER].filter(Boolean).join("\n\n");
466
+ return [
467
+ options.suppressThinking ? NO_THINK_CONTROL_TOKEN : "",
468
+ systemContent,
469
+ prompt,
470
+ PROMPTED_JSON_FINAL_TOOL_CALL_REMINDER,
471
+ ].filter(Boolean).join("\n\n");
466
472
  }
467
- function createPromptedJsonToolBindableModel(model, boundTools = []) {
473
+ function createPromptedJsonToolBindableModel(model, boundTools = [], options = {}) {
468
474
  return new Proxy(model, {
469
475
  has(target, prop) {
470
476
  if (prop === "bindTools" || prop === "invoke" || prop === "stream" || prop === "withConfig") {
@@ -474,11 +480,11 @@ function createPromptedJsonToolBindableModel(model, boundTools = []) {
474
480
  },
475
481
  get(target, prop, receiver) {
476
482
  if (prop === "bindTools") {
477
- return (tools) => createPromptedJsonToolBindableModel(target, tools);
483
+ return (tools) => createPromptedJsonToolBindableModel(target, tools, options);
478
484
  }
479
485
  if (prop === "invoke") {
480
486
  return async (input, config) => {
481
- const rawResult = await target.invoke(boundTools.length > 0 ? withPromptedJsonToolPrompt(input, boundTools) : input, config);
487
+ const rawResult = await target.invoke(boundTools.length > 0 ? withPromptedJsonToolPrompt(input, boundTools, options) : input, config);
482
488
  if (boundTools.length === 0) {
483
489
  return rawResult;
484
490
  }
@@ -510,7 +516,7 @@ function createPromptedJsonToolBindableModel(model, boundTools = []) {
510
516
  };
511
517
  }
512
518
  if (prop === "withConfig" && typeof target.withConfig === "function") {
513
- return (config) => createPromptedJsonToolBindableModel(target.withConfig(config), boundTools);
519
+ return (config) => createPromptedJsonToolBindableModel(target.withConfig(config), boundTools, options);
514
520
  }
515
521
  const member = Reflect.get(target, prop, receiver);
516
522
  return typeof member === "function" ? member.bind(target) : member;
@@ -559,7 +565,7 @@ export async function createResolvedModel(model, modelResolver) {
559
565
  const { toolCallingMode, ...init } = model.init ?? {};
560
566
  const resolved = new ChatOllama({ model: model.model, ...init });
561
567
  if (toolCallingMode === "prompted-json") {
562
- return createPromptedJsonToolBindableModel(resolved);
568
+ return createPromptedJsonToolBindableModel(resolved, [], { suppressThinking: init.think === false });
563
569
  }
564
570
  return createProviderToolMessageCompatModel(resolved);
565
571
  }
@@ -42,6 +42,9 @@ export function isEmptyFinalAiMessageError(error) {
42
42
  const message = error instanceof Error ? error.message : String(error);
43
43
  return message.toLowerCase().startsWith("empty_final_ai_message:");
44
44
  }
45
+ function isRuntimeOperationTimeoutError(error) {
46
+ return typeof error === "object" && error !== null && error.name === "RuntimeOperationTimeoutError";
47
+ }
45
48
  function isRetryableHttpStatus(status) {
46
49
  return typeof status === "number" && Number.isInteger(status) && status >= 500 && status <= 599;
47
50
  }
@@ -84,6 +87,9 @@ export function resolveProviderRetryPolicy(binding) {
84
87
  };
85
88
  }
86
89
  export function isRetryableProviderError(binding, error) {
90
+ if (isRuntimeOperationTimeoutError(error)) {
91
+ return false;
92
+ }
87
93
  if (isEmptyFinalAiMessageError(error)) {
88
94
  return true;
89
95
  }
@@ -57,6 +57,7 @@ export declare class AgentRuntimeAdapter {
57
57
  files?: Record<string, unknown>;
58
58
  memoryContext?: string;
59
59
  }): Promise<RequestResult>;
60
+ private tryDelegateWithCompactRouter;
60
61
  stream(binding: CompiledAgentBinding, input: MessageContent, sessionId: string, history?: TranscriptMessage[], options?: {
61
62
  context?: Record<string, unknown>;
62
63
  state?: Record<string, unknown>;
@@ -1,7 +1,8 @@
1
1
  import path from "node:path";
2
2
  import { createAsyncSubAgentMiddleware, createFilesystemMiddleware, createMemoryMiddleware, createPatchToolCallsMiddleware, createSkillsMiddleware, createSummarizationMiddleware, createSubAgentMiddleware, FilesystemBackend, StateBackend, } from "deepagents";
3
3
  import { createAgent, humanInTheLoopMiddleware, todoListMiddleware } from "langchain";
4
- import { wrapResolvedModel, } from "./parsing/output-parsing.js";
4
+ import { tryParseJson, wrapResolvedModel, } from "./parsing/output-parsing.js";
5
+ import { extractMessageText } from "../utils/message-content.js";
5
6
  import { AGENT_INTERRUPT_SENTINEL_PREFIX, buildDeepAgentCreateParams, buildDeepAgentSystemPromptWithCapabilityHierarchy, buildLangChainCreateParams, DEFAULT_DEEPAGENT_RECURSION_LIMIT, materializeModelExposedBuiltinMiddlewareTools, resolveLangChainInvocationConfig, resolveRunnableCheckpointer, resolveRunnableInterruptOn, shouldAttachDeepAgentBackend, shouldAttachDeepAgentCheckpointer, shouldAttachDeepAgentStore, } from "./agent-runtime-assembly.js";
6
7
  import { resolveDeepAgentSkillSourcePaths, } from "./adapter/compat/deepagent-compat.js";
7
8
  import { EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION } from "./prompts/runtime-prompts.js";
@@ -21,7 +22,7 @@ export { materializeDeepAgentSkillSourcePaths, resolveDeepAgentSkillSourcePaths,
21
22
  export { buildAuthOmittingFetch, normalizeOpenAICompatibleInit } from "./adapter/compat/openai-compatible.js";
22
23
  export { buildToolNameMapping, createModelFacingToolNameCandidates, createModelFacingToolNameLookupCandidates, resolveModelFacingToolName, sanitizeToolNameForModel, } from "./adapter/tool/tool-name-mapping.js";
23
24
  export { computeRemainingTimeoutMs, isRetryableProviderError, resolveBindingTimeout, resolveProviderRetryPolicy, resolveStreamIdleTimeout, resolveTimeoutMs, } from "./adapter/resilience.js";
24
- import { getBindingAdapterKind, getBindingBuiltinToolsConfig, getBindingDeepAgentSubagents, getBindingExecutionParams, getBindingExecutionKind, getBindingFilesystemConfig, getBindingMemorySources, getBindingPrimaryModel, getBindingSkills, getBindingToolCount, getBindingPrimaryTools, getBindingSystemPrompt, isDeepAgentBinding, isLangChainBinding, } from "./support/compiled-binding.js";
25
+ import { getBindingAdapterKind, getBindingBuiltinToolsConfig, getBindingDeepAgentSubagents, getBindingExecutionParams, getBindingExecutionKind, getBindingFilesystemConfig, getBindingMemorySources, getBindingPrimaryModel, getBindingSkills, getBindingSubagents, getBindingToolCount, getBindingPrimaryTools, getBindingSystemPrompt, isDeepAgentBinding, isLangChainBinding, } from "./support/compiled-binding.js";
25
26
  class DelegatedExecutionNoToolEvidenceError extends Error {
26
27
  constructor(agentId) {
27
28
  super(`Delegated agent ${agentId} completed without tool execution evidence.`);
@@ -39,6 +40,77 @@ function hasDelegatedExecutionToolEvidence(result) {
39
40
  function shouldUseConfigurableDeepAgentAssembly(binding) {
40
41
  return getBindingExecutionKind(binding) === "deepagent";
41
42
  }
43
+ function readModelText(value) {
44
+ if (typeof value === "string") {
45
+ return value.trim();
46
+ }
47
+ if (typeof value !== "object" || value === null) {
48
+ return "";
49
+ }
50
+ const content = value.content;
51
+ if (typeof content === "string") {
52
+ return content.trim();
53
+ }
54
+ if (Array.isArray(content)) {
55
+ return content
56
+ .map((part) => {
57
+ if (typeof part === "string")
58
+ return part;
59
+ if (typeof part === "object" && part !== null && typeof part.text === "string") {
60
+ return part.text;
61
+ }
62
+ return "";
63
+ })
64
+ .join("")
65
+ .trim();
66
+ }
67
+ return "";
68
+ }
69
+ function parseFirstJsonObject(value) {
70
+ let depth = 0;
71
+ let start = -1;
72
+ let inString = false;
73
+ let escaping = false;
74
+ for (let index = 0; index < value.length; index += 1) {
75
+ const char = value[index];
76
+ if (inString) {
77
+ if (escaping) {
78
+ escaping = false;
79
+ }
80
+ else if (char === "\\") {
81
+ escaping = true;
82
+ }
83
+ else if (char === "\"") {
84
+ inString = false;
85
+ }
86
+ continue;
87
+ }
88
+ if (char === "\"") {
89
+ inString = true;
90
+ continue;
91
+ }
92
+ if (char === "{") {
93
+ if (depth === 0) {
94
+ start = index;
95
+ }
96
+ depth += 1;
97
+ continue;
98
+ }
99
+ if (char === "}" && depth > 0) {
100
+ depth -= 1;
101
+ if (depth === 0 && start >= 0) {
102
+ return tryParseJson(value.slice(start, index + 1));
103
+ }
104
+ }
105
+ }
106
+ return null;
107
+ }
108
+ function isDelegationOnlyDeepAgentBinding(binding) {
109
+ return isDeepAgentBinding(binding)
110
+ && getBindingSubagents(binding).length > 0
111
+ && getBindingPrimaryTools(binding).length === 0
112
+ && getBindingSkills(binding).length === 0;
113
+ }
42
114
  export class AgentRuntimeAdapter {
43
115
  options;
44
116
  modelCache = new Map();
@@ -557,6 +629,36 @@ export class AgentRuntimeAdapter {
557
629
  },
558
630
  };
559
631
  }
632
+ const compactDelegation = await this.tryDelegateWithCompactRouter(binding, input, sessionId, requestId, {
633
+ ...options,
634
+ sessionId,
635
+ requestId,
636
+ });
637
+ if (compactDelegation) {
638
+ const output = typeof compactDelegation.toolOutput === "string"
639
+ ? compactDelegation.toolOutput
640
+ : JSON.stringify(compactDelegation.toolOutput);
641
+ const delegatedToolResults = Array.isArray(compactDelegation.delegatedResult?.metadata?.executedToolResults)
642
+ ? compactDelegation.delegatedResult.metadata.executedToolResults
643
+ : [];
644
+ return {
645
+ sessionId,
646
+ requestId,
647
+ agentId: binding.agent.id,
648
+ state: "completed",
649
+ output,
650
+ finalMessageText: output,
651
+ metadata: {
652
+ executedToolResults: [
653
+ {
654
+ toolName: "task",
655
+ output: compactDelegation.toolOutput,
656
+ },
657
+ ...delegatedToolResults,
658
+ ],
659
+ },
660
+ };
661
+ }
560
662
  const callRuntime = async (activeBinding, activeRequest) => {
561
663
  return this.invokeWithProviderRetry(activeBinding, async () => {
562
664
  const runnable = await this.create(activeBinding, { sessionId });
@@ -612,6 +714,74 @@ export class AgentRuntimeAdapter {
612
714
  return invokeRequest();
613
715
  }
614
716
  }
717
+ async tryDelegateWithCompactRouter(binding, input, sessionId, requestId, options = {}) {
718
+ if (!isDelegationOnlyDeepAgentBinding(binding)) {
719
+ return null;
720
+ }
721
+ if (!this.options.bindingResolver) {
722
+ return null;
723
+ }
724
+ const primaryModel = getBindingPrimaryModel(binding);
725
+ if (!primaryModel) {
726
+ return null;
727
+ }
728
+ const requestText = extractMessageText(input).trim();
729
+ if (!requestText) {
730
+ return null;
731
+ }
732
+ const subagents = getBindingSubagents(binding);
733
+ const subagentCatalog = subagents
734
+ .map((subagent) => `- ${subagent.name}: ${subagent.description}`)
735
+ .join("\n");
736
+ const prompt = [
737
+ primaryModel.init?.think === false ? "/no_think" : "",
738
+ "You are selecting a subagent for a delegation-only agent.",
739
+ "Choose exactly one listed subagent when it can responsibly handle the request.",
740
+ "Return only JSON with this shape:",
741
+ "{\"subagent_type\":\"<listed subagent name>\"}",
742
+ "If no listed subagent can handle the request, return only:",
743
+ "{\"status\":\"refused\",\"reason\":\"No configured subagent can handle the request.\"}",
744
+ "Available subagents:",
745
+ subagentCatalog,
746
+ "User request:",
747
+ requestText,
748
+ ].filter(Boolean).join("\n\n");
749
+ const model = await this.resolveModel(primaryModel);
750
+ if (typeof model.invoke !== "function") {
751
+ return null;
752
+ }
753
+ const raw = await this.withTimeout(() => model.invoke(prompt, resolveLangChainInvocationConfig(binding, {
754
+ sessionId,
755
+ requestId,
756
+ context: options.context,
757
+ toolRuntimeContext: this.buildFunctionToolRuntimeContext(binding, {
758
+ ...options,
759
+ sessionId,
760
+ requestId,
761
+ }),
762
+ })), resolveBindingTimeout(binding), "delegation router invoke", "invoke");
763
+ const parsed = parseFirstJsonObject(readModelText(raw));
764
+ if (typeof parsed !== "object" || parsed === null) {
765
+ return null;
766
+ }
767
+ const subagentType = typeof parsed.subagent_type === "string"
768
+ ? parsed.subagent_type
769
+ : "";
770
+ if (!subagents.some((subagent) => subagent.name === subagentType)) {
771
+ return null;
772
+ }
773
+ const selectedBinding = this.options.bindingResolver(subagentType);
774
+ if (!selectedBinding) {
775
+ return null;
776
+ }
777
+ const delegatedResult = await this.invoke(selectedBinding, requestText, sessionId, `${requestId}:${subagentType}`, undefined, [], {
778
+ context: options.context,
779
+ state: options.state,
780
+ files: options.files,
781
+ memoryContext: options.memoryContext,
782
+ });
783
+ return { toolOutput: delegatedResult.output, delegatedResult };
784
+ }
615
785
  async *stream(binding, input, sessionId, history = [], options = {}) {
616
786
  const directListing = await this.tryHandleDirectWorkspaceListing(binding, input, {
617
787
  ...options,
@@ -630,6 +800,25 @@ export class AgentRuntimeAdapter {
630
800
  };
631
801
  return;
632
802
  }
803
+ const compactDelegation = await this.tryDelegateWithCompactRouter(binding, input, sessionId, options.requestId ?? sessionId, {
804
+ ...options,
805
+ sessionId,
806
+ requestId: options.requestId,
807
+ });
808
+ if (compactDelegation) {
809
+ yield {
810
+ kind: "tool-result",
811
+ toolName: "task",
812
+ output: compactDelegation.toolOutput,
813
+ };
814
+ yield {
815
+ kind: "content",
816
+ content: typeof compactDelegation.toolOutput === "string"
817
+ ? compactDelegation.toolOutput
818
+ : JSON.stringify(compactDelegation.toolOutput),
819
+ };
820
+ return;
821
+ }
633
822
  const invokeTimeoutMs = resolveBindingTimeout(binding);
634
823
  const streamIdleTimeoutMs = resolveStreamIdleTimeout(binding);
635
824
  const streamDeadlineAt = invokeTimeoutMs ? Date.now() + invokeTimeoutMs : undefined;
@@ -517,6 +517,15 @@ function createProfileStepCommentary(step) {
517
517
  if (step.kind === "memory") {
518
518
  return `Checking memory ${name}.`;
519
519
  }
520
+ if (step.kind === "agent" && step.action === "invoke") {
521
+ return "Running model invocation.";
522
+ }
523
+ if (step.kind === "agent" && step.action === "start") {
524
+ return "Starting runtime stream.";
525
+ }
526
+ if (step.kind === "agent" && step.action === "startup") {
527
+ return `Preparing ${name}.`;
528
+ }
520
529
  return null;
521
530
  }
522
531
  function isOpenAICompatibleStreamingCompatibilityError(binding, error) {
@@ -0,0 +1,42 @@
1
+ import type { WorkspaceBundle } from "../../../contracts/types.js";
2
+ export type BoundaryFindingSeverity = "info" | "warning" | "error";
3
+ export type BoundaryFinding = {
4
+ severity: BoundaryFindingSeverity;
5
+ code: string;
6
+ message: string;
7
+ agentId?: string;
8
+ parentAgentId?: string;
9
+ toolName?: string;
10
+ skillName?: string;
11
+ skillPath?: string;
12
+ relatedAgents?: string[];
13
+ sourcePath?: string;
14
+ };
15
+ export type BoundaryAgentSurface = {
16
+ id: string;
17
+ description: string;
18
+ tools: string[];
19
+ skills: string[];
20
+ subagents: string[];
21
+ sourcePath?: string;
22
+ parentAgentId?: string;
23
+ };
24
+ export type BoundaryAnalysisSummary = {
25
+ agentCount: number;
26
+ toolCount: number;
27
+ skillCount: number;
28
+ findingCount: number;
29
+ errorCount: number;
30
+ warningCount: number;
31
+ infoCount: number;
32
+ };
33
+ export type WorkspaceBoundaryAnalysis = {
34
+ workspaceRoot: string;
35
+ summary: BoundaryAnalysisSummary;
36
+ agents: BoundaryAgentSurface[];
37
+ findings: BoundaryFinding[];
38
+ };
39
+ export type BoundaryAnalysisOptions = {
40
+ includeInfo?: boolean;
41
+ };
42
+ export declare function analyzeWorkspaceBoundaries(workspace: WorkspaceBundle, options?: BoundaryAnalysisOptions): WorkspaceBoundaryAnalysis;
@@ -0,0 +1,234 @@
1
+ import { existsSync, readdirSync, statSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { readSkillMetadata } from "../../skills/skill-metadata.js";
4
+ import { getBindingPrimaryTools, getBindingSkills, getBindingSubagents, } from "../../support/compiled-binding.js";
5
+ const BUILTIN_SKILL_TOOLS = new Set(["read_todos", "write_todos"]);
6
+ function uniqueSorted(values) {
7
+ return Array.from(new Set(values.filter((value) => value.trim().length > 0))).sort();
8
+ }
9
+ function addFinding(findings, options, finding) {
10
+ if (finding.severity === "info" && options.includeInfo === false) {
11
+ return;
12
+ }
13
+ findings.push(finding);
14
+ }
15
+ function discoverWorkspaceSkillPaths(workspaceRoot, referencedSkillPaths) {
16
+ const skillPaths = new Set(referencedSkillPaths);
17
+ const skillsRoot = path.join(workspaceRoot, "resources", "skills");
18
+ if (!existsSync(skillsRoot)) {
19
+ return uniqueSorted(Array.from(skillPaths));
20
+ }
21
+ for (const entry of readdirSync(skillsRoot)) {
22
+ const skillPath = path.join(skillsRoot, entry);
23
+ try {
24
+ if (statSync(skillPath).isDirectory() && existsSync(path.join(skillPath, "SKILL.md"))) {
25
+ skillPaths.add(skillPath);
26
+ }
27
+ }
28
+ catch {
29
+ continue;
30
+ }
31
+ }
32
+ return uniqueSorted(Array.from(skillPaths));
33
+ }
34
+ function describeTopLevelSurface(binding) {
35
+ return {
36
+ id: binding.agent.id,
37
+ description: binding.agent.description,
38
+ tools: uniqueSorted(getBindingPrimaryTools(binding).map((tool) => tool.name)),
39
+ skills: uniqueSorted(getBindingSkills(binding)),
40
+ subagents: uniqueSorted(getBindingSubagents(binding).map((subagent) => subagent.name)),
41
+ sourcePath: binding.agent.sourcePath,
42
+ };
43
+ }
44
+ function describeSubagentSurface(subagent, parentAgentId) {
45
+ return {
46
+ id: subagent.name,
47
+ description: subagent.description,
48
+ tools: uniqueSorted((subagent.tools ?? []).map((tool) => tool.name)),
49
+ skills: uniqueSorted(subagent.skills ?? []),
50
+ subagents: [],
51
+ parentAgentId,
52
+ };
53
+ }
54
+ function addOwner(index, key, agentId) {
55
+ const owners = index.get(key) ?? [];
56
+ owners.push(agentId);
57
+ index.set(key, owners);
58
+ }
59
+ function stripAgentRefPrefix(value) {
60
+ const prefix = "agent/";
61
+ return value.startsWith(prefix) ? value.slice(prefix.length) : value;
62
+ }
63
+ export function analyzeWorkspaceBoundaries(workspace, options = {}) {
64
+ const findings = [];
65
+ const agentSurfaces = [];
66
+ const referencedSkillPaths = [];
67
+ const toolOwners = new Map();
68
+ const skillOwners = new Map();
69
+ const skillSurfaceOwners = new Map();
70
+ for (const binding of workspace.bindings.values()) {
71
+ const topLevel = describeTopLevelSurface(binding);
72
+ agentSurfaces.push(topLevel);
73
+ for (const skillPath of topLevel.skills) {
74
+ referencedSkillPaths.push(skillPath);
75
+ }
76
+ for (const subagent of getBindingSubagents(binding)) {
77
+ const surface = describeSubagentSurface(subagent, binding.agent.id);
78
+ agentSurfaces.push(surface);
79
+ for (const skillPath of surface.skills) {
80
+ referencedSkillPaths.push(skillPath);
81
+ }
82
+ }
83
+ }
84
+ for (const surface of agentSurfaces) {
85
+ if (surface.description.trim().length === 0) {
86
+ addFinding(findings, options, {
87
+ severity: "warning",
88
+ code: "agent-missing-description",
89
+ message: `Agent '${surface.id}' has no description, making routing boundaries ambiguous.`,
90
+ agentId: surface.id,
91
+ parentAgentId: surface.parentAgentId,
92
+ sourcePath: surface.sourcePath,
93
+ });
94
+ }
95
+ if (surface.subagents.length > 0 && (surface.tools.length > 0 || surface.skills.length > 0)) {
96
+ addFinding(findings, options, {
97
+ severity: "warning",
98
+ code: "mixed-routing-and-execution-surface",
99
+ message: `Agent '${surface.id}' declares subagents and also exposes local tools or skills.`,
100
+ agentId: surface.id,
101
+ sourcePath: surface.sourcePath,
102
+ });
103
+ }
104
+ if (surface.subagents.length === 0 && surface.tools.length === 0 && surface.skills.length === 0) {
105
+ addFinding(findings, options, {
106
+ severity: "info",
107
+ code: "empty-execution-surface",
108
+ message: `Agent '${surface.id}' has no tools, skills, or subagents declared.`,
109
+ agentId: surface.id,
110
+ parentAgentId: surface.parentAgentId,
111
+ sourcePath: surface.sourcePath,
112
+ });
113
+ }
114
+ for (const toolName of surface.tools) {
115
+ addOwner(toolOwners, toolName, surface.id);
116
+ }
117
+ for (const skillPath of surface.skills) {
118
+ addOwner(skillOwners, skillPath, surface.id);
119
+ skillSurfaceOwners.set(skillPath, [...(skillSurfaceOwners.get(skillPath) ?? []), surface]);
120
+ }
121
+ }
122
+ for (const agent of workspace.agents.values()) {
123
+ for (const subagentRef of agent.subagentRefs) {
124
+ const subagentId = stripAgentRefPrefix(subagentRef);
125
+ if (!workspace.agents.has(subagentId)) {
126
+ addFinding(findings, options, {
127
+ severity: "error",
128
+ code: "missing-subagent-reference",
129
+ message: `Agent '${agent.id}' references missing subagent '${subagentRef}'.`,
130
+ agentId: agent.id,
131
+ sourcePath: agent.sourcePath,
132
+ });
133
+ }
134
+ }
135
+ }
136
+ for (const [toolName, owners] of toolOwners) {
137
+ const uniqueOwners = uniqueSorted(owners);
138
+ if (uniqueOwners.length > 1) {
139
+ addFinding(findings, options, {
140
+ severity: "info",
141
+ code: "shared-tool-surface",
142
+ message: `Tool '${toolName}' is exposed by multiple agents; review whether the shared surface is intentional.`,
143
+ toolName,
144
+ relatedAgents: uniqueOwners,
145
+ });
146
+ }
147
+ }
148
+ for (const [skillPath, owners] of skillOwners) {
149
+ const uniqueOwners = uniqueSorted(owners);
150
+ if (uniqueOwners.length > 1) {
151
+ const metadata = readSkillMetadata(skillPath);
152
+ addFinding(findings, options, {
153
+ severity: "info",
154
+ code: "shared-skill-surface",
155
+ message: `Skill '${metadata.name}' is exposed by multiple agents; review whether the shared surface is intentional.`,
156
+ skillName: metadata.name,
157
+ skillPath,
158
+ relatedAgents: uniqueOwners,
159
+ });
160
+ }
161
+ }
162
+ for (const tool of workspace.tools.values()) {
163
+ if (!toolOwners.has(tool.name)) {
164
+ addFinding(findings, options, {
165
+ severity: "warning",
166
+ code: "unreferenced-tool",
167
+ message: `Tool '${tool.name}' is defined but not exposed by any agent or subagent.`,
168
+ toolName: tool.name,
169
+ sourcePath: tool.sourcePath,
170
+ });
171
+ }
172
+ }
173
+ const discoveredSkillPaths = discoverWorkspaceSkillPaths(workspace.workspaceRoot, referencedSkillPaths);
174
+ for (const skillPath of discoveredSkillPaths) {
175
+ const metadata = readSkillMetadata(skillPath);
176
+ const owners = skillOwners.get(skillPath) ?? [];
177
+ if (owners.length === 0) {
178
+ addFinding(findings, options, {
179
+ severity: "warning",
180
+ code: "unreferenced-skill",
181
+ message: `Skill '${metadata.name}' is present but not exposed by any agent or subagent.`,
182
+ skillName: metadata.name,
183
+ skillPath,
184
+ });
185
+ continue;
186
+ }
187
+ if (!metadata.allowedTools || metadata.allowedTools.length === 0) {
188
+ addFinding(findings, options, {
189
+ severity: "warning",
190
+ code: "skill-missing-allowed-tools",
191
+ message: `Skill '${metadata.name}' does not declare allowed-tools.`,
192
+ skillName: metadata.name,
193
+ skillPath,
194
+ relatedAgents: uniqueSorted(owners),
195
+ });
196
+ continue;
197
+ }
198
+ for (const surface of skillSurfaceOwners.get(skillPath) ?? []) {
199
+ const owner = surface.parentAgentId ? `${surface.parentAgentId}/${surface.id}` : surface.id;
200
+ const availableTools = new Set([...surface.tools, ...BUILTIN_SKILL_TOOLS]);
201
+ for (const allowedTool of metadata.allowedTools) {
202
+ if (!availableTools.has(allowedTool)) {
203
+ addFinding(findings, options, {
204
+ severity: "error",
205
+ code: "skill-allowed-tool-unavailable",
206
+ message: `Skill '${metadata.name}' allows tool '${allowedTool}', but agent '${owner}' does not expose it.`,
207
+ agentId: surface.id,
208
+ parentAgentId: surface.parentAgentId,
209
+ toolName: allowedTool,
210
+ skillName: metadata.name,
211
+ skillPath,
212
+ });
213
+ }
214
+ }
215
+ }
216
+ }
217
+ const errorCount = findings.filter((finding) => finding.severity === "error").length;
218
+ const warningCount = findings.filter((finding) => finding.severity === "warning").length;
219
+ const infoCount = findings.filter((finding) => finding.severity === "info").length;
220
+ return {
221
+ workspaceRoot: workspace.workspaceRoot,
222
+ summary: {
223
+ agentCount: agentSurfaces.length,
224
+ toolCount: workspace.tools.size,
225
+ skillCount: discoveredSkillPaths.length,
226
+ findingCount: findings.length,
227
+ errorCount,
228
+ warningCount,
229
+ infoCount,
230
+ },
231
+ agents: agentSurfaces,
232
+ findings,
233
+ };
234
+ }
@@ -1,6 +1,7 @@
1
1
  import type { ApprovalRecord, ArtifactListing, CancelOptions, HarnessEvent, HarnessStreamItem, RuntimeHealthSnapshot, RuntimeOperatorOverview, ListMemoriesInput, ListMemoriesResult, MessageContent, RemoveMemoryInput, RequestPlanState, RequestOptions, RequestRecord, RequestResult, RequestSummary, RequestStartOptions, RestartConversationOptions, RuntimeAdapterOptions, RuntimeArtifactWriteInput, RuntimeEvaluationExport, RuntimeEvaluationExportInput, RuntimeEvaluationReplayInput, RuntimeEvaluationReplayResult, RuntimeRequestPackage, RuntimeRequestPackageInput, RuntimeSessionPackage, RuntimeSessionPackageInput, ResumeOptions, MemoryRecord, MemorizeInput, MemorizeResult, RecallInput, RecallResult, UpdateMemoryInput, SessionSummary, SessionRecord, SessionListSummary, WorkspaceBundle } from "../contracts/types.js";
2
2
  import { type RuntimeMcpServerOptions, type ToolMcpServerOptions } from "../mcp.js";
3
3
  import { type InventoryAgentRecord, type InventorySkillRecord } from "./harness/system/inventory.js";
4
+ import { type BoundaryAnalysisOptions, type WorkspaceBoundaryAnalysis } from "./harness/system/boundary-analysis.js";
4
5
  import type { RequirementAssessmentOptions } from "./harness/system/skill-requirements.js";
5
6
  import { SystemScheduleManager } from "./scheduling/system-schedule-manager.js";
6
7
  export declare class AgentHarnessRuntime {
@@ -119,6 +120,7 @@ export declare class AgentHarnessRuntime {
119
120
  workspaceRoot: string;
120
121
  agents: InventoryAgentRecord[];
121
122
  };
123
+ analyzeWorkspaceBoundaries(options?: BoundaryAnalysisOptions): WorkspaceBoundaryAnalysis;
122
124
  private deleteSessionCheckpoints;
123
125
  deleteSession(sessionId: string): Promise<boolean>;
124
126
  createToolMcpServer(options: ToolMcpServerOptions): Promise<import("@modelcontextprotocol/sdk/server/mcp").McpServer>;
@@ -30,6 +30,7 @@ import { closeMcpClientsForWorkspace } from "../resource/mcp/tool-support.js";
30
30
  import { getBindingRuntimeExecutionMode, } from "./support/compiled-binding.js";
31
31
  import { bindingSupportsRunningReplay, getWorkspaceBinding, resolveWorkspaceAgentTools, } from "./harness/bindings.js";
32
32
  import { describeWorkspaceInventory, getAgentInventoryRecord, listAgentSkills as listWorkspaceAgentSkills, } from "./harness/system/inventory.js";
33
+ import { analyzeWorkspaceBoundaries, } from "./harness/system/boundary-analysis.js";
33
34
  import { createDefaultHealthSnapshot, isInventoryEnabled, isSessionMemorySyncEnabled, } from "./harness/runtime-defaults.js";
34
35
  import { cloneRequestRecord, cloneSessionRecord, deriveRequestInputFromTranscript, mergeMemoryItems, summarizeApprovalEvidence, toPublicHarnessStreamItem, toPublicRequestResultShape, toSessionListSummary, } from "./harness/public-shapes.js";
35
36
  import { Mem0IngestionSync, Mem0SemanticRecall, readMem0RuntimeConfig, } from "./harness/system/mem0-ingestion-sync.js";
@@ -877,6 +878,9 @@ export class AgentHarnessRuntime {
877
878
  ...options,
878
879
  });
879
880
  }
881
+ analyzeWorkspaceBoundaries(options = {}) {
882
+ return analyzeWorkspaceBoundaries(this.workspace, options);
883
+ }
880
884
  async deleteSessionCheckpoints(sessionId) {
881
885
  const resolver = this.resolvedRuntimeAdapterOptions.checkpointerResolver;
882
886
  if (!resolver) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.352",
3
+ "version": "0.0.354",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",