@botbotgo/agent-harness 0.0.351 → 0.0.353
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 +2 -1
- package/README.zh.md +2 -1
- package/dist/api.d.ts +3 -0
- package/dist/api.js +3 -0
- package/dist/cli/options.js +1 -0
- package/dist/cli/runtime-commands.js +12 -1
- package/dist/cli/runtime-output.d.ts +1 -0
- package/dist/cli/runtime-output.js +31 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/resources/prompts/runtime/execution-with-tool-evidence-retry.md +5 -1
- package/dist/runtime/adapter/flow/invocation-flow.js +79 -8
- package/dist/runtime/adapter/invocation-result.d.ts +7 -0
- package/dist/runtime/adapter/invocation-result.js +95 -7
- package/dist/runtime/adapter/local-tool-invocation.js +23 -5
- package/dist/runtime/adapter/middleware-assembly.js +29 -2
- package/dist/runtime/adapter/resilience.d.ts +1 -0
- package/dist/runtime/adapter/resilience.js +2 -1
- package/dist/runtime/adapter/terminal-status.js +2 -2
- package/dist/runtime/agent-runtime-adapter.js +13 -3
- package/dist/runtime/harness/events/event-sink.js +19 -2
- package/dist/runtime/harness/system/boundary-analysis.d.ts +42 -0
- package/dist/runtime/harness/system/boundary-analysis.js +234 -0
- package/dist/runtime/harness.d.ts +3 -0
- package/dist/runtime/harness.js +29 -8
- package/dist/runtime/parsing/output-content.js +7 -2
- package/dist/runtime/parsing/output-recovery.js +6 -2
- package/dist/runtime/parsing/output-tool-args.d.ts +4 -0
- package/dist/runtime/parsing/output-tool-args.js +114 -4
- package/package.json +1 -1
|
@@ -10,7 +10,7 @@ import { executeRequestInvocation } from "./adapter/flow/invocation-flow.js";
|
|
|
10
10
|
import { streamRuntimeExecution } from "./adapter/flow/stream-runtime.js";
|
|
11
11
|
import { applyToolRecoveryInstruction as applyToolRecoveryInstructionHelper, applyStrictToolJsonInstruction as applyStrictToolJsonInstructionHelper, callRuntimeWithToolParseRecovery as callRuntimeWithToolParseRecoveryHelper, createModelFallbackRunnable as createModelFallbackRunnableHelper, invokeWithProviderRetry as invokeWithProviderRetryHelper, iterateWithTimeout as iterateWithTimeoutHelper, materializeModelStream as materializeModelStreamHelper, RuntimeOperationTimeoutError, withRuntimeTimeout, } from "./adapter/runtime-shell.js";
|
|
12
12
|
import { extractSubagentRequestText, invokeBuiltinTaskTool as invokeBuiltinTaskToolHelper, materializeAutomaticSummarizationMiddleware as materializeAutomaticSummarizationMiddlewareHelper, resolveBuiltinMiddlewareBackend as resolveBuiltinMiddlewareBackendHelper, resolveBuiltinMiddlewareTools as resolveBuiltinMiddlewareToolsHelper, resolveLangChainRuntimeExtensionMiddleware as resolveLangChainRuntimeExtensionMiddlewareHelper, resolveMiddleware as resolveMiddlewareHelper, resolveSubagents as resolveSubagentsHelper, wrapRequestResultAsSubagentResponse, } from "./adapter/middleware-assembly.js";
|
|
13
|
-
import { resolveBindingTimeout, resolveStreamIdleTimeout, } from "./adapter/resilience.js";
|
|
13
|
+
import { isEmptyFinalAiMessageError, resolveBindingTimeout, resolveStreamIdleTimeout, } from "./adapter/resilience.js";
|
|
14
14
|
import { createResolvedModel } from "./adapter/model/model-providers.js";
|
|
15
15
|
import { renderDirectWorkspaceListing, shouldDirectlyListWorkspaceFiles } from "./adapter/direct-builtin-utility.js";
|
|
16
16
|
import { resolveAdapterTools } from "./adapter/tool-resolution.js";
|
|
@@ -585,7 +585,7 @@ export class AgentRuntimeAdapter {
|
|
|
585
585
|
sessionId,
|
|
586
586
|
requestId,
|
|
587
587
|
});
|
|
588
|
-
|
|
588
|
+
const invokeRequest = () => executeRequestInvocation({
|
|
589
589
|
binding,
|
|
590
590
|
input,
|
|
591
591
|
sessionId,
|
|
@@ -600,7 +600,17 @@ export class AgentRuntimeAdapter {
|
|
|
600
600
|
getToolNameMapping: (currentBinding) => this.getToolNameMapping(currentBinding),
|
|
601
601
|
resolveBuiltinMiddlewareTools: (currentBinding, currentOptions) => this.resolveBuiltinMiddlewareTools(currentBinding, { ...currentOptions, sessionId, requestId }),
|
|
602
602
|
callRuntimeWithToolParseRecovery,
|
|
603
|
-
})
|
|
603
|
+
});
|
|
604
|
+
try {
|
|
605
|
+
return await invokeRequest();
|
|
606
|
+
}
|
|
607
|
+
catch (error) {
|
|
608
|
+
if (!isEmptyFinalAiMessageError(error)) {
|
|
609
|
+
throw error;
|
|
610
|
+
}
|
|
611
|
+
this.invalidateBindingRuntimeCaches(binding);
|
|
612
|
+
return invokeRequest();
|
|
613
|
+
}
|
|
604
614
|
}
|
|
605
615
|
async *stream(binding, input, sessionId, history = [], options = {}) {
|
|
606
616
|
const directListing = await this.tryHandleDirectWorkspaceListing(binding, input, {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import { getEventSubscribers } from "../../../tooling/extensions.js";
|
|
3
|
+
const EVENT_PROJECTION_DRAIN_TIMEOUT_MS = 1_000;
|
|
3
4
|
function dispatchListener(listener, event) {
|
|
4
5
|
void Promise.resolve(listener(event));
|
|
5
6
|
}
|
|
@@ -34,8 +35,24 @@ export class RuntimeEventSinkImpl {
|
|
|
34
35
|
return () => this.projections.delete(projection);
|
|
35
36
|
}
|
|
36
37
|
async drain() {
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
let timeoutHandle;
|
|
39
|
+
try {
|
|
40
|
+
await Promise.race([
|
|
41
|
+
(async () => {
|
|
42
|
+
while (this.inflightProjectionTasks.size > 0) {
|
|
43
|
+
await Promise.allSettled(Array.from(this.inflightProjectionTasks));
|
|
44
|
+
}
|
|
45
|
+
})(),
|
|
46
|
+
new Promise((resolve) => {
|
|
47
|
+
timeoutHandle = setTimeout(resolve, EVENT_PROJECTION_DRAIN_TIMEOUT_MS);
|
|
48
|
+
timeoutHandle.unref?.();
|
|
49
|
+
}),
|
|
50
|
+
]);
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
if (timeoutHandle) {
|
|
54
|
+
clearTimeout(timeoutHandle);
|
|
55
|
+
}
|
|
39
56
|
}
|
|
40
57
|
}
|
|
41
58
|
}
|
|
@@ -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>;
|
|
@@ -133,6 +135,7 @@ export declare class AgentHarnessRuntime {
|
|
|
133
135
|
private trackBackgroundTask;
|
|
134
136
|
private scheduleBackgroundStartupTask;
|
|
135
137
|
private drainBackgroundTasksForClose;
|
|
138
|
+
private closeStageWithTimeout;
|
|
136
139
|
private resolveToolMcpServerTools;
|
|
137
140
|
private loadPriorHistory;
|
|
138
141
|
private loadRequestInput;
|
package/dist/runtime/harness.js
CHANGED
|
@@ -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";
|
|
@@ -47,6 +48,7 @@ import { buildRequestInspectionRecord, buildSessionInspectionRecord, deleteSessi
|
|
|
47
48
|
import { createKnowledgeModule } from "../knowledge/index.js";
|
|
48
49
|
import { createProceduralMemoryManager, ProceduralMemoryFormationSync, readProceduralMemoryRuntimeConfig, } from "../knowledge/procedural/index.js";
|
|
49
50
|
const BACKGROUND_TASK_CLOSE_DRAIN_TIMEOUT_MS = 1_000;
|
|
51
|
+
const CLOSE_STAGE_TIMEOUT_MS = 1_000;
|
|
50
52
|
const ACTIVE_REQUEST_STATES = [
|
|
51
53
|
"queued",
|
|
52
54
|
"claimed",
|
|
@@ -876,6 +878,9 @@ export class AgentHarnessRuntime {
|
|
|
876
878
|
...options,
|
|
877
879
|
});
|
|
878
880
|
}
|
|
881
|
+
analyzeWorkspaceBoundaries(options = {}) {
|
|
882
|
+
return analyzeWorkspaceBoundaries(this.workspace, options);
|
|
883
|
+
}
|
|
879
884
|
async deleteSessionCheckpoints(sessionId) {
|
|
880
885
|
const resolver = this.resolvedRuntimeAdapterOptions.checkpointerResolver;
|
|
881
886
|
if (!resolver) {
|
|
@@ -963,6 +968,22 @@ export class AgentHarnessRuntime {
|
|
|
963
968
|
clearTimeout(timeoutHandle);
|
|
964
969
|
}
|
|
965
970
|
}
|
|
971
|
+
async closeStageWithTimeout(_label, stage) {
|
|
972
|
+
if (!stage) {
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
let timeoutHandle;
|
|
976
|
+
await Promise.race([
|
|
977
|
+
stage().then(() => undefined).catch(() => undefined),
|
|
978
|
+
new Promise((resolve) => {
|
|
979
|
+
timeoutHandle = setTimeout(resolve, CLOSE_STAGE_TIMEOUT_MS);
|
|
980
|
+
timeoutHandle.unref?.();
|
|
981
|
+
}),
|
|
982
|
+
]);
|
|
983
|
+
if (timeoutHandle) {
|
|
984
|
+
clearTimeout(timeoutHandle);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
966
987
|
resolveToolMcpServerTools(agentId) {
|
|
967
988
|
return resolveWorkspaceAgentTools({
|
|
968
989
|
workspace: this.workspace,
|
|
@@ -1386,20 +1407,20 @@ export class AgentHarnessRuntime {
|
|
|
1386
1407
|
return;
|
|
1387
1408
|
}
|
|
1388
1409
|
this.closed = true;
|
|
1389
|
-
await this.healthMonitor?.stop();
|
|
1390
|
-
await this.eventBus.drain();
|
|
1410
|
+
await this.closeStageWithTimeout("healthMonitor.stop", () => this.healthMonitor?.stop() ?? Promise.resolve());
|
|
1411
|
+
await this.closeStageWithTimeout("eventBus.drain", () => this.eventBus.drain());
|
|
1391
1412
|
this.unregisterSessionMemorySync();
|
|
1392
1413
|
this.unregisterRuntimeMemorySync();
|
|
1393
1414
|
this.unregisterMem0IngestionSync();
|
|
1394
1415
|
this.unregisterRuntimeMemoryFormationSync();
|
|
1395
1416
|
this.unregisterProceduralMemoryFormationSync();
|
|
1396
1417
|
await this.drainBackgroundTasksForClose();
|
|
1397
|
-
await this.sessionMemorySync?.close();
|
|
1398
|
-
await this.runtimeMemorySync?.close();
|
|
1399
|
-
await this.mem0IngestionSync?.close();
|
|
1400
|
-
await this.runtimeMemoryFormationSync?.close();
|
|
1401
|
-
await this.proceduralMemoryFormationSync?.close();
|
|
1402
|
-
await closeMcpClientsForWorkspace(this.workspace);
|
|
1418
|
+
await this.closeStageWithTimeout("sessionMemorySync.close", () => this.sessionMemorySync?.close() ?? Promise.resolve());
|
|
1419
|
+
await this.closeStageWithTimeout("runtimeMemorySync.close", () => this.runtimeMemorySync?.close() ?? Promise.resolve());
|
|
1420
|
+
await this.closeStageWithTimeout("mem0IngestionSync.close", () => this.mem0IngestionSync?.close() ?? Promise.resolve());
|
|
1421
|
+
await this.closeStageWithTimeout("runtimeMemoryFormationSync.close", () => this.runtimeMemoryFormationSync?.close() ?? Promise.resolve());
|
|
1422
|
+
await this.closeStageWithTimeout("proceduralMemoryFormationSync.close", () => this.proceduralMemoryFormationSync?.close() ?? Promise.resolve());
|
|
1423
|
+
await this.closeStageWithTimeout("closeMcpClientsForWorkspace", () => closeMcpClientsForWorkspace(this.workspace));
|
|
1403
1424
|
this.initialized = false;
|
|
1404
1425
|
}
|
|
1405
1426
|
async stop() {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AIMessage } from "langchain";
|
|
2
|
-
import { salvageFunctionLikeToolCall, salvageToolArgs, isLikelyToolArgsObject, normalizeKnownToolArgs, tryParseJson } from "./output-tool-args.js";
|
|
2
|
+
import { salvageFunctionLikeToolCall, salvageJsonToolCalls, salvageToolArgs, isLikelyToolArgsObject, normalizeKnownToolArgs, tryParseJson } from "./output-tool-args.js";
|
|
3
3
|
function consumeLeadingFunctionLikeToolCall(value) {
|
|
4
4
|
const match = /^([A-Za-z_][A-Za-z0-9_]*)\(/.exec(value);
|
|
5
5
|
if (!match) {
|
|
@@ -495,8 +495,12 @@ function normalizeAgentMessage(value) {
|
|
|
495
495
|
const functionLikeToolCall = normalizedToolCalls.length === 0 && recoveredToolCalls.length === 0 && typeof normalizedContent === "string"
|
|
496
496
|
? salvageFunctionLikeToolCall(normalizedContent)
|
|
497
497
|
: null;
|
|
498
|
+
const jsonToolCalls = normalizedToolCalls.length === 0 && recoveredToolCalls.length === 0 && !functionLikeToolCall && typeof normalizedContent === "string"
|
|
499
|
+
? salvageJsonToolCalls(normalizedContent)
|
|
500
|
+
: [];
|
|
501
|
+
const hasRecoveredContentToolCalls = Boolean(functionLikeToolCall) || jsonToolCalls.length > 0;
|
|
498
502
|
return new AIMessage({
|
|
499
|
-
content:
|
|
503
|
+
content: hasRecoveredContentToolCalls ? "" : normalizedContent,
|
|
500
504
|
name: typeof typed.name === "string" ? typed.name : undefined,
|
|
501
505
|
additional_kwargs: typeof typed.additional_kwargs === "object" && typed.additional_kwargs ? typed.additional_kwargs : {},
|
|
502
506
|
response_metadata: typeof typed.response_metadata === "object" && typed.response_metadata ? typed.response_metadata : {},
|
|
@@ -505,6 +509,7 @@ function normalizeAgentMessage(value) {
|
|
|
505
509
|
...normalizedToolCalls,
|
|
506
510
|
...recoveredToolCalls,
|
|
507
511
|
...(functionLikeToolCall ? [{ name: functionLikeToolCall.name, args: functionLikeToolCall.args }] : []),
|
|
512
|
+
...jsonToolCalls.map((toolCall) => ({ name: toolCall.name, args: toolCall.args })),
|
|
508
513
|
],
|
|
509
514
|
invalid_tool_calls: normalizedInvalidToolCalls.filter((toolCall) => toolCall.type !== "tool_call"),
|
|
510
515
|
usage_metadata: typeof typed.usage_metadata === "object" && typed.usage_metadata ? typed.usage_metadata : undefined,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION, EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION, INTERNAL_RUNTIME_SPILL_PATH_INSTRUCTION, STRICT_TOOL_JSON_INSTRUCTION, WORKSPACE_RELATIVE_PATH_INSTRUCTION, WRITE_TODOS_DESCRIPTIVE_CONTENT_INSTRUCTION, WRITE_TODOS_FULL_ENTRY_INSTRUCTION, WRITE_TODOS_NON_EMPTY_INITIAL_LIST_INSTRUCTION, WRITE_TODOS_REQUIRED_PLAN_INSTRUCTION, } from "../prompts/runtime-prompts.js";
|
|
2
2
|
import { wrapNormalizedMessage, readTextContent } from "./output-content.js";
|
|
3
|
+
import { salvageJsonToolCalls } from "./output-tool-args.js";
|
|
3
4
|
function collectRequestMessages(request) {
|
|
4
5
|
if (typeof request !== "object" || !request || Array.isArray(request)) {
|
|
5
6
|
return [];
|
|
@@ -145,12 +146,15 @@ export function resolveExecutionWithoutToolEvidenceTextInstruction(request, assi
|
|
|
145
146
|
const hasUnfinishedExecution = resultEvidence.hasIncompletePlanState === true
|
|
146
147
|
|| resultEvidence.hasOpenTaskDelegation === true
|
|
147
148
|
|| resultEvidence.hasMissingDelegatedExecutionEvidence === true;
|
|
148
|
-
if (
|
|
149
|
-
return
|
|
149
|
+
if (salvageJsonToolCalls(normalizedText).length > 0) {
|
|
150
|
+
return STRICT_TOOL_JSON_INSTRUCTION;
|
|
150
151
|
}
|
|
151
152
|
const hasExecutionEvidence = toolCallEvidence
|
|
152
153
|
|| resultEvidence.hasWriteTodosEvidence === true
|
|
153
154
|
|| resultEvidence.hasToolResultEvidence === true;
|
|
155
|
+
if (!normalizedText || !hasUnfinishedExecution) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
154
158
|
return hasExecutionEvidence
|
|
155
159
|
? AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION
|
|
156
160
|
: EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION;
|
|
@@ -4,5 +4,9 @@ export declare function salvageFunctionLikeToolCall(value: unknown): {
|
|
|
4
4
|
args: Record<string, unknown>;
|
|
5
5
|
} | null;
|
|
6
6
|
export declare function salvageToolArgs(value: unknown): Record<string, unknown> | null;
|
|
7
|
+
export declare function salvageJsonToolCalls(value: unknown): Array<{
|
|
8
|
+
name: string;
|
|
9
|
+
args: Record<string, unknown>;
|
|
10
|
+
}>;
|
|
7
11
|
export declare function normalizeKnownToolArgs(toolName: unknown, args: Record<string, unknown>): Record<string, unknown>;
|
|
8
12
|
export declare function isLikelyToolArgsObject(value: unknown): boolean;
|
|
@@ -112,8 +112,8 @@ export function salvageFunctionLikeToolCall(value) {
|
|
|
112
112
|
}
|
|
113
113
|
return { name, args: normalizeKnownToolArgs(name, args) };
|
|
114
114
|
}
|
|
115
|
-
function
|
|
116
|
-
const start = value.indexOf(
|
|
115
|
+
function extractBalancedJsonValue(value, openChar, closeChar) {
|
|
116
|
+
const start = value.indexOf(openChar);
|
|
117
117
|
if (start < 0)
|
|
118
118
|
return null;
|
|
119
119
|
let depth = 0;
|
|
@@ -139,11 +139,11 @@ function extractBalancedJsonObject(value) {
|
|
|
139
139
|
inString = true;
|
|
140
140
|
continue;
|
|
141
141
|
}
|
|
142
|
-
if (char ===
|
|
142
|
+
if (char === openChar) {
|
|
143
143
|
depth += 1;
|
|
144
144
|
continue;
|
|
145
145
|
}
|
|
146
|
-
if (char ===
|
|
146
|
+
if (char === closeChar) {
|
|
147
147
|
depth -= 1;
|
|
148
148
|
if (depth === 0) {
|
|
149
149
|
return value.slice(start, index + 1);
|
|
@@ -152,6 +152,59 @@ function extractBalancedJsonObject(value) {
|
|
|
152
152
|
}
|
|
153
153
|
return null;
|
|
154
154
|
}
|
|
155
|
+
function extractBalancedJsonObject(value) {
|
|
156
|
+
return extractBalancedJsonValue(value, "{", "}");
|
|
157
|
+
}
|
|
158
|
+
function extractBalancedJsonArray(value) {
|
|
159
|
+
return extractBalancedJsonValue(value, "[", "]");
|
|
160
|
+
}
|
|
161
|
+
function closeJsonContainerSuffix(value) {
|
|
162
|
+
const trimmed = value.trim();
|
|
163
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
const stack = [];
|
|
167
|
+
let inString = false;
|
|
168
|
+
let escaping = false;
|
|
169
|
+
for (const char of trimmed) {
|
|
170
|
+
if (inString) {
|
|
171
|
+
if (escaping) {
|
|
172
|
+
escaping = false;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (char === "\\") {
|
|
176
|
+
escaping = true;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (char === "\"") {
|
|
180
|
+
inString = false;
|
|
181
|
+
}
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (char === "\"") {
|
|
185
|
+
inString = true;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (char === "{") {
|
|
189
|
+
stack.push("}");
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (char === "[") {
|
|
193
|
+
stack.push("]");
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (char === "}" || char === "]") {
|
|
197
|
+
const expected = stack.pop();
|
|
198
|
+
if (expected !== char) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (inString || stack.length === 0) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
return `${trimmed}${stack.reverse().join("")}`;
|
|
207
|
+
}
|
|
155
208
|
export function salvageToolArgs(value) {
|
|
156
209
|
if (typeof value === "object" && value && !Array.isArray(value)) {
|
|
157
210
|
return value;
|
|
@@ -174,6 +227,63 @@ export function salvageToolArgs(value) {
|
|
|
174
227
|
const parsed = tryParseJson(embedded);
|
|
175
228
|
return typeof parsed === "object" && parsed && !Array.isArray(parsed) ? parsed : null;
|
|
176
229
|
}
|
|
230
|
+
function normalizeJsonToolCallPayload(payload) {
|
|
231
|
+
if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
const typed = payload;
|
|
235
|
+
const functionPayload = typeof typed.function === "object" && typed.function !== null ? typed.function : undefined;
|
|
236
|
+
const nameCandidate = typed.name ?? typed.tool ?? functionPayload?.name;
|
|
237
|
+
const name = typeof nameCandidate === "string" ? nameCandidate.trim() : "";
|
|
238
|
+
if (!name) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
const argsCandidate = typed.arguments ?? typed.args ?? typed.parameters ?? typed.input ?? functionPayload?.arguments ?? {};
|
|
242
|
+
const args = Array.isArray(argsCandidate)
|
|
243
|
+
? { args: argsCandidate }
|
|
244
|
+
: salvageToolArgs(argsCandidate) ?? {};
|
|
245
|
+
return { name, args: normalizeKnownToolArgs(name, args) };
|
|
246
|
+
}
|
|
247
|
+
export function salvageJsonToolCalls(value) {
|
|
248
|
+
const payload = typeof value === "string"
|
|
249
|
+
? (() => {
|
|
250
|
+
const trimmed = value.trim();
|
|
251
|
+
if (!trimmed) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
const direct = tryParseJson(trimmed);
|
|
255
|
+
if (direct) {
|
|
256
|
+
return direct;
|
|
257
|
+
}
|
|
258
|
+
const closed = closeJsonContainerSuffix(trimmed);
|
|
259
|
+
if (closed) {
|
|
260
|
+
const parsed = tryParseJson(closed);
|
|
261
|
+
if (parsed) {
|
|
262
|
+
return parsed;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const embeddedArray = extractBalancedJsonArray(trimmed);
|
|
266
|
+
if (embeddedArray) {
|
|
267
|
+
const parsed = tryParseJson(embeddedArray);
|
|
268
|
+
if (parsed) {
|
|
269
|
+
return parsed;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const embeddedObject = extractBalancedJsonObject(trimmed);
|
|
273
|
+
if (embeddedObject) {
|
|
274
|
+
const parsed = tryParseJson(embeddedObject);
|
|
275
|
+
if (parsed) {
|
|
276
|
+
return parsed;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return null;
|
|
280
|
+
})()
|
|
281
|
+
: value;
|
|
282
|
+
const candidates = Array.isArray(payload) ? payload : [payload];
|
|
283
|
+
return candidates
|
|
284
|
+
.map((item) => normalizeJsonToolCallPayload(item))
|
|
285
|
+
.filter((item) => item !== null);
|
|
286
|
+
}
|
|
177
287
|
function normalizeWriteTodosArgs(args) {
|
|
178
288
|
if (!Array.isArray(args.todos)) {
|
|
179
289
|
return args;
|