@botbotgo/agent-harness 0.0.346 → 0.0.348
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/contracts/runtime-requests.d.ts +1 -0
- package/dist/contracts/workspace.d.ts +4 -0
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/resource/backend/workspace-scoped-backend.d.ts +9 -2
- package/dist/resource/backend/workspace-scoped-backend.js +42 -22
- package/dist/resources/prompts/runtime/delegated-task-failure-recovery.md +8 -0
- package/dist/runtime/adapter/flow/stream-runtime.js +52 -18
- package/dist/runtime/adapter/invocation-result.js +49 -5
- package/dist/runtime/adapter/local-tool-invocation.js +5 -0
- package/dist/runtime/adapter/model/model-providers.js +108 -12
- package/dist/runtime/adapter/stream-event-projection.js +3 -1
- package/dist/runtime/adapter/terminal-status.d.ts +4 -0
- package/dist/runtime/adapter/terminal-status.js +67 -0
- package/dist/runtime/agent-runtime-adapter.js +51 -37
- package/dist/runtime/agent-runtime-assembly.d.ts +10 -0
- package/dist/runtime/agent-runtime-assembly.js +68 -0
- package/dist/runtime/harness/run/stream-run.js +17 -31
- package/dist/runtime/parsing/output-recovery.d.ts +2 -1
- package/dist/runtime/parsing/output-recovery.js +2 -25
- package/dist/runtime/prompts/runtime-prompts.d.ts +1 -0
- package/dist/runtime/prompts/runtime-prompts.js +1 -0
- package/dist/workspace/agent-binding-compiler.js +11 -0
- package/dist/workspace/framework-contract-validation.js +126 -26
- package/dist/workspace/object-loader.js +3 -0
- package/package.json +1 -1
|
@@ -17,6 +17,7 @@ export type RequestResult = {
|
|
|
17
17
|
artifacts?: ArtifactRecord[];
|
|
18
18
|
metadata?: Record<string, unknown>;
|
|
19
19
|
};
|
|
20
|
+
export type TerminalExecutionStatus = "completed" | "blocked" | "failed" | "refused";
|
|
20
21
|
export type UpstreamRuntimeEvent = unknown;
|
|
21
22
|
export type UpstreamRuntimeEventItem = {
|
|
22
23
|
sessionId: string;
|
|
@@ -203,6 +203,9 @@ export type CompiledBuiltinToolsConfig = {
|
|
|
203
203
|
todos?: boolean;
|
|
204
204
|
modelExposed?: boolean | string[];
|
|
205
205
|
};
|
|
206
|
+
export type CompiledExecutionContract = {
|
|
207
|
+
requiresPlan?: boolean;
|
|
208
|
+
};
|
|
206
209
|
export type LangChainAgentParams = {
|
|
207
210
|
model: CompiledModel;
|
|
208
211
|
tools: CompiledTool[];
|
|
@@ -287,6 +290,7 @@ export type CompiledAgentBinding = {
|
|
|
287
290
|
resilience?: Record<string, unknown>;
|
|
288
291
|
governance?: Record<string, unknown>;
|
|
289
292
|
observability?: Record<string, unknown>;
|
|
293
|
+
executionContract?: CompiledExecutionContract;
|
|
290
294
|
deepagent?: {
|
|
291
295
|
description?: string;
|
|
292
296
|
passthrough?: Record<string, unknown>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export declare const AGENT_HARNESS_VERSION = "0.0.348";
|
|
2
2
|
export declare const AGENT_HARNESS_RELEASE_DATE = "2026-04-24";
|
package/dist/package-version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export const AGENT_HARNESS_VERSION = "0.0.348";
|
|
2
2
|
export const AGENT_HARNESS_RELEASE_DATE = "2026-04-24";
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import { CompositeBackend } from "deepagents";
|
|
2
2
|
import type { RuntimeAdapterOptions, WorkspaceBundle } from "../../contracts/types.js";
|
|
3
|
-
export declare function normalizeWorkspaceScopedPath(rootDir: string, inputPath: string
|
|
3
|
+
export declare function normalizeWorkspaceScopedPath(rootDir: string, inputPath: string, options?: {
|
|
4
|
+
allowVirtualAbsolutePath?: boolean;
|
|
5
|
+
}): string;
|
|
4
6
|
export declare class WorkspaceScopedBackend {
|
|
5
7
|
private readonly backend;
|
|
8
|
+
private readonly options;
|
|
6
9
|
readonly id?: string;
|
|
7
10
|
readonly cwd: string;
|
|
8
11
|
readonly rootDir: string;
|
|
9
12
|
readonly root: string;
|
|
10
13
|
readonly virtualMode?: boolean;
|
|
11
14
|
readonly execute?: (command: string) => Promise<unknown>;
|
|
12
|
-
constructor(backend: Record<string, unknown>, rootDir: string
|
|
15
|
+
constructor(backend: Record<string, unknown>, rootDir: string, options?: {
|
|
16
|
+
allowVirtualAbsolutePath?: boolean;
|
|
17
|
+
});
|
|
13
18
|
ls(filePath: string): unknown;
|
|
14
19
|
read(filePath: string, offset?: number, limit?: number): unknown;
|
|
15
20
|
readRaw(filePath: string): unknown;
|
|
@@ -25,7 +30,9 @@ declare class CompatibleCompositeBackend {
|
|
|
25
30
|
readonly id?: string;
|
|
26
31
|
readonly execute?: (command: string) => ReturnType<CompositeBackend["execute"]>;
|
|
27
32
|
private readonly composite;
|
|
33
|
+
private readonly routePrefixes;
|
|
28
34
|
constructor(defaultBackend: unknown, routes: Record<string, unknown>);
|
|
35
|
+
private normalizeCompositePath;
|
|
29
36
|
ls(filePath: string): ReturnType<CompositeBackend["ls"]>;
|
|
30
37
|
read(filePath: string, offset?: number, limit?: number): ReturnType<CompositeBackend["read"]>;
|
|
31
38
|
readRaw(filePath: string): ReturnType<CompositeBackend["readRaw"]>;
|
|
@@ -30,7 +30,7 @@ function normalizeVirtualExecuteCommand(command, rootDir) {
|
|
|
30
30
|
return `${prefix}${quote}${translatedPath}${quote}`;
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
|
-
export function normalizeWorkspaceScopedPath(rootDir, inputPath) {
|
|
33
|
+
export function normalizeWorkspaceScopedPath(rootDir, inputPath, options = {}) {
|
|
34
34
|
if (typeof inputPath !== "string" || inputPath.length === 0 || !path.isAbsolute(inputPath)) {
|
|
35
35
|
return inputPath;
|
|
36
36
|
}
|
|
@@ -53,21 +53,26 @@ export function normalizeWorkspaceScopedPath(rootDir, inputPath) {
|
|
|
53
53
|
if (normalizedInputPath === normalizedRootDir || normalizedInputPath.startsWith(`${normalizedRootDir}${path.sep}`)) {
|
|
54
54
|
return path.relative(normalizedRootDir, normalizedInputPath) || ".";
|
|
55
55
|
}
|
|
56
|
+
if (options.allowVirtualAbsolutePath === true) {
|
|
57
|
+
return inputPath.replace(/\/+/g, "/");
|
|
58
|
+
}
|
|
56
59
|
throw new Error(`Path '${inputPath}' is outside the workspace root '${normalizedRootDir}'. Use a workspace-relative path instead.`);
|
|
57
60
|
}
|
|
58
|
-
function normalizeWorkspaceScopedNullablePath(rootDir, inputPath) {
|
|
59
|
-
return typeof inputPath === "string" ? normalizeWorkspaceScopedPath(rootDir, inputPath) : inputPath;
|
|
61
|
+
function normalizeWorkspaceScopedNullablePath(rootDir, inputPath, options = {}) {
|
|
62
|
+
return typeof inputPath === "string" ? normalizeWorkspaceScopedPath(rootDir, inputPath, options) : inputPath;
|
|
60
63
|
}
|
|
61
64
|
export class WorkspaceScopedBackend {
|
|
62
65
|
backend;
|
|
66
|
+
options;
|
|
63
67
|
id;
|
|
64
68
|
cwd;
|
|
65
69
|
rootDir;
|
|
66
70
|
root;
|
|
67
71
|
virtualMode;
|
|
68
72
|
execute;
|
|
69
|
-
constructor(backend, rootDir) {
|
|
73
|
+
constructor(backend, rootDir, options = {}) {
|
|
70
74
|
this.backend = backend;
|
|
75
|
+
this.options = options;
|
|
71
76
|
this.rootDir = path.resolve(rootDir);
|
|
72
77
|
this.root = this.rootDir;
|
|
73
78
|
this.cwd = this.rootDir;
|
|
@@ -78,34 +83,36 @@ export class WorkspaceScopedBackend {
|
|
|
78
83
|
: undefined;
|
|
79
84
|
}
|
|
80
85
|
ls(filePath) {
|
|
81
|
-
return this.backend.ls(normalizeWorkspaceScopedPath(this.rootDir, filePath));
|
|
86
|
+
return this.backend.ls(normalizeWorkspaceScopedPath(this.rootDir, filePath, this.options));
|
|
82
87
|
}
|
|
83
88
|
read(filePath, offset, limit) {
|
|
84
|
-
return this.backend.read(normalizeWorkspaceScopedPath(this.rootDir, filePath), offset, limit);
|
|
89
|
+
return this.backend.read(normalizeWorkspaceScopedPath(this.rootDir, filePath, this.options), offset, limit);
|
|
85
90
|
}
|
|
86
91
|
readRaw(filePath) {
|
|
87
|
-
return this.backend.readRaw(normalizeWorkspaceScopedPath(this.rootDir, filePath));
|
|
92
|
+
return this.backend.readRaw(normalizeWorkspaceScopedPath(this.rootDir, filePath, this.options));
|
|
88
93
|
}
|
|
89
94
|
grep(pattern, filePath, glob) {
|
|
90
|
-
return this.backend.grep(pattern, normalizeWorkspaceScopedNullablePath(this.rootDir, filePath), glob);
|
|
95
|
+
return this.backend.grep(pattern, normalizeWorkspaceScopedNullablePath(this.rootDir, filePath, this.options), glob);
|
|
91
96
|
}
|
|
92
97
|
grepRaw(pattern, filePath, glob) {
|
|
93
|
-
return this.backend.grepRaw(pattern, normalizeWorkspaceScopedNullablePath(this.rootDir, filePath), glob);
|
|
98
|
+
return this.backend.grepRaw(pattern, normalizeWorkspaceScopedNullablePath(this.rootDir, filePath, this.options), glob);
|
|
94
99
|
}
|
|
95
100
|
glob(pattern, filePath) {
|
|
96
|
-
return this.backend.glob(pattern, typeof filePath === "string" ? normalizeWorkspaceScopedPath(this.rootDir, filePath) : filePath);
|
|
101
|
+
return this.backend.glob(pattern, typeof filePath === "string" ? normalizeWorkspaceScopedPath(this.rootDir, filePath, this.options) : filePath);
|
|
97
102
|
}
|
|
98
103
|
write(filePath, content) {
|
|
99
|
-
return this.backend.write(normalizeWorkspaceScopedPath(this.rootDir, filePath), content);
|
|
104
|
+
return this.backend.write(normalizeWorkspaceScopedPath(this.rootDir, filePath, this.options), content);
|
|
100
105
|
}
|
|
101
106
|
edit(filePath, oldString, newString, replaceAll) {
|
|
102
|
-
return this.backend.edit(normalizeWorkspaceScopedPath(this.rootDir, filePath), oldString, newString, replaceAll);
|
|
107
|
+
return this.backend.edit(normalizeWorkspaceScopedPath(this.rootDir, filePath, this.options), oldString, newString, replaceAll);
|
|
103
108
|
}
|
|
104
109
|
uploadFiles(files) {
|
|
105
110
|
return this.backend.uploadFiles(files);
|
|
106
111
|
}
|
|
107
112
|
downloadFiles(paths) {
|
|
108
|
-
const normalizedPaths = Array.isArray(paths)
|
|
113
|
+
const normalizedPaths = Array.isArray(paths)
|
|
114
|
+
? paths.map((currentPath) => normalizeWorkspaceScopedPath(this.rootDir, currentPath, this.options))
|
|
115
|
+
: paths;
|
|
109
116
|
return this.backend.downloadFiles(normalizedPaths);
|
|
110
117
|
}
|
|
111
118
|
}
|
|
@@ -113,8 +120,10 @@ class CompatibleCompositeBackend {
|
|
|
113
120
|
id;
|
|
114
121
|
execute;
|
|
115
122
|
composite;
|
|
123
|
+
routePrefixes;
|
|
116
124
|
constructor(defaultBackend, routes) {
|
|
117
125
|
this.composite = new CompositeBackend(defaultBackend, routes);
|
|
126
|
+
this.routePrefixes = Object.keys(routes).filter((route) => route.startsWith("/"));
|
|
118
127
|
const sandboxLike = defaultBackend;
|
|
119
128
|
if (typeof sandboxLike.id === "string" && typeof sandboxLike.execute === "function") {
|
|
120
129
|
this.id = sandboxLike.id;
|
|
@@ -125,32 +134,43 @@ class CompatibleCompositeBackend {
|
|
|
125
134
|
: (command) => this.composite.execute(command);
|
|
126
135
|
}
|
|
127
136
|
}
|
|
137
|
+
normalizeCompositePath(filePath) {
|
|
138
|
+
if (!path.isAbsolute(filePath)) {
|
|
139
|
+
return filePath;
|
|
140
|
+
}
|
|
141
|
+
const normalized = filePath.replace(/\/+/g, "/");
|
|
142
|
+
if (this.routePrefixes.some((route) => normalized === route || normalized.startsWith(route))) {
|
|
143
|
+
return normalized;
|
|
144
|
+
}
|
|
145
|
+
return path.join(...normalized.split("/").filter(Boolean));
|
|
146
|
+
}
|
|
128
147
|
ls(filePath) {
|
|
129
|
-
return this.composite.ls(filePath);
|
|
148
|
+
return this.composite.ls(this.normalizeCompositePath(filePath));
|
|
130
149
|
}
|
|
131
150
|
read(filePath, offset, limit) {
|
|
132
|
-
return this.composite.read(filePath, offset, limit);
|
|
151
|
+
return this.composite.read(this.normalizeCompositePath(filePath), offset, limit);
|
|
133
152
|
}
|
|
134
153
|
readRaw(filePath) {
|
|
135
|
-
return this.composite.readRaw(filePath);
|
|
154
|
+
return this.composite.readRaw(this.normalizeCompositePath(filePath));
|
|
136
155
|
}
|
|
137
156
|
grep(pattern, filePath, glob) {
|
|
138
|
-
return this.composite.grep(pattern, filePath
|
|
157
|
+
return this.composite.grep(pattern, filePath ? this.normalizeCompositePath(filePath) : undefined, glob ?? undefined);
|
|
139
158
|
}
|
|
140
159
|
glob(pattern, filePath) {
|
|
141
|
-
return this.composite.glob(pattern, filePath);
|
|
160
|
+
return this.composite.glob(pattern, filePath ? this.normalizeCompositePath(filePath) : filePath);
|
|
142
161
|
}
|
|
143
162
|
write(filePath, content) {
|
|
144
|
-
return this.composite.write(filePath, content);
|
|
163
|
+
return this.composite.write(this.normalizeCompositePath(filePath), content);
|
|
145
164
|
}
|
|
146
165
|
edit(filePath, oldString, newString, replaceAll) {
|
|
147
|
-
return this.composite.edit(filePath, oldString, newString, replaceAll);
|
|
166
|
+
return this.composite.edit(this.normalizeCompositePath(filePath), oldString, newString, replaceAll);
|
|
148
167
|
}
|
|
149
168
|
uploadFiles(files) {
|
|
150
169
|
return this.composite.uploadFiles(files);
|
|
151
170
|
}
|
|
152
171
|
downloadFiles(paths) {
|
|
153
|
-
|
|
172
|
+
const normalizedPaths = Array.isArray(paths) ? paths.map((currentPath) => this.normalizeCompositePath(currentPath)) : paths;
|
|
173
|
+
return this.composite.downloadFiles(normalizedPaths);
|
|
154
174
|
}
|
|
155
175
|
}
|
|
156
176
|
function omitKind(config) {
|
|
@@ -289,7 +309,7 @@ export function createInlineBackendResolver(workspace) {
|
|
|
289
309
|
const routeKind = typeof routeConfig?.kind === "string" ? routeConfig.kind : "StoreBackend";
|
|
290
310
|
return [route, createInlineBackendInstance(workspace.workspaceRoot, routeKind, routeConfig, runtimeLike)];
|
|
291
311
|
}));
|
|
292
|
-
return new WorkspaceScopedBackend(new CompatibleCompositeBackend(createInlineBackendInstance(workspace.workspaceRoot, defaultBackendKind, stateConfig, runtimeLike), mappedRoutes), workspace.workspaceRoot);
|
|
312
|
+
return new WorkspaceScopedBackend(new CompatibleCompositeBackend(createInlineBackendInstance(workspace.workspaceRoot, defaultBackendKind, stateConfig, runtimeLike), mappedRoutes), workspace.workspaceRoot, { allowVirtualAbsolutePath: true });
|
|
293
313
|
}
|
|
294
314
|
default:
|
|
295
315
|
return unsupportedInlineBackend(kind);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
The delegated task failed. You are the routing/delegation parent agent, so you must not switch into local execution or start a new local plan.
|
|
2
|
+
|
|
3
|
+
Your next response has only two valid forms:
|
|
4
|
+
|
|
5
|
+
1. Call the `task` tool again, preserving the user's original request and delegating to the same specialist or another explicit specialist whose configured responsibility clearly matches the original request.
|
|
6
|
+
2. Return a final blocker report to the user explaining that delegated execution failed.
|
|
7
|
+
|
|
8
|
+
Do not call local execution tools, repository tools, web tools, shell tools, or `write_todos` from the parent agent after this delegated failure. Do not invent a new topic or downgrade the original request. If you continue execution, it must be through `task`.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { extractVisibleOutput, isToolCallRecoveryFailure, isRetrySafeInvalidToolSelectionError, resolveMissingPlanRecoveryInstruction, resolveExecutionWithoutToolEvidenceTextInstruction, shouldValidateExecutionWithoutToolEvidence, resolveToolCallRecoveryInstruction, sanitizeVisibleText, EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION, INVALID_TOOL_SELECTION_RECOVERY_INSTRUCTION, } from "../../parsing/output-parsing.js";
|
|
2
|
+
import { DELEGATED_TASK_FAILURE_RECOVERY_INSTRUCTION } from "../../prompts/runtime-prompts.js";
|
|
2
3
|
import { buildInvocationRequest } from "../model/invocation-request.js";
|
|
3
4
|
import { buildRawModelMessages } from "../model/message-assembly.js";
|
|
4
5
|
import { projectRuntimeStreamEvent, createStreamEventProjectionState } from "../stream-event-projection.js";
|
|
@@ -55,6 +56,18 @@ function hasSuccessfulNonTodoToolEvidence(executedToolResults) {
|
|
|
55
56
|
function hasSuccessfulTaskToolEvidence(executedToolResults) {
|
|
56
57
|
return executedToolResults.some((item) => item.isError !== true && item.toolName === "task");
|
|
57
58
|
}
|
|
59
|
+
function requiresPlanEvidence(binding) {
|
|
60
|
+
return binding.harnessRuntime?.executionContract?.requiresPlan === true;
|
|
61
|
+
}
|
|
62
|
+
function hasParentLocalToolExecutionAfterDelegationFailure(originalEvidence, executedToolResults) {
|
|
63
|
+
return originalEvidence.hasFailedTaskDelegation
|
|
64
|
+
&& executedToolResults.some((item) => item.toolName !== "task");
|
|
65
|
+
}
|
|
66
|
+
function isDelegationFailureFinalReport(originalEvidence, executedToolResults, visibleOutput) {
|
|
67
|
+
return originalEvidence.hasFailedTaskDelegation
|
|
68
|
+
&& executedToolResults.length === 0
|
|
69
|
+
&& visibleOutput.trim().length > 0;
|
|
70
|
+
}
|
|
58
71
|
function buildExecutionRecoveryEvidence(params) {
|
|
59
72
|
const { projectionState, executedToolResults = [] } = params;
|
|
60
73
|
return {
|
|
@@ -65,7 +78,8 @@ function buildExecutionRecoveryEvidence(params) {
|
|
|
65
78
|
hasIncompletePlanState: projectionState.hasIncompletePlanState || hasIncompletePlanStateInExecutedToolResults(executedToolResults),
|
|
66
79
|
hasPlanStateEvidence: projectionState.sawPlanState || hasIncompletePlanStateInExecutedToolResults(executedToolResults),
|
|
67
80
|
hasOpenTaskDelegation: projectionState.openTaskDelegations > 0,
|
|
68
|
-
hasFailedTaskDelegation: projectionState.hasFailedTaskDelegation
|
|
81
|
+
hasFailedTaskDelegation: projectionState.hasFailedTaskDelegation
|
|
82
|
+
|| executedToolResults.some((item) => item.toolName === "task" && item.isError === true),
|
|
69
83
|
hasDelegatedAgentWithConfiguredTools: projectionState.sawDelegatedAgentWithConfiguredTools,
|
|
70
84
|
hasDelegatedExecutionToolEvidence: projectionState.emittedDelegatedExecutionToolResult,
|
|
71
85
|
hasOnlyPlaceholderTaskCompletion: projectionState.emittedSuccessfulTaskResult
|
|
@@ -98,6 +112,9 @@ function resolveStreamedRuntimeFailureRecoveryInstruction(output, evidence) {
|
|
|
98
112
|
return hasExecutionEvidence ? null : EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION;
|
|
99
113
|
}
|
|
100
114
|
function resolveDelegatedExecutionRecoveryInstruction(evidence) {
|
|
115
|
+
if (evidence.hasFailedTaskDelegation) {
|
|
116
|
+
return DELEGATED_TASK_FAILURE_RECOVERY_INSTRUCTION;
|
|
117
|
+
}
|
|
101
118
|
if (hasMissingDelegatedFindings(evidence)
|
|
102
119
|
|| (evidence.hasOpenTaskDelegation
|
|
103
120
|
&& evidence.hasDelegatedAgentWithConfiguredTools
|
|
@@ -400,6 +417,7 @@ export async function* streamRuntimeExecution(options) {
|
|
|
400
417
|
? resolveMissingPlanRecoveryInstruction({
|
|
401
418
|
request,
|
|
402
419
|
assistantText: terminalVisibleOutput,
|
|
420
|
+
requiresPlan: requiresPlanEvidence(options.binding),
|
|
403
421
|
hasPlanStateEvidence: terminalExecutionEvidence.hasPlanStateEvidence,
|
|
404
422
|
hasWriteTodosEvidence: terminalExecutionEvidence.hasPlanStateEvidence,
|
|
405
423
|
hasToolResultEvidence: terminalExecutionEvidence.hasSuccessfulNonTodoToolResultEvidence,
|
|
@@ -443,8 +461,9 @@ export async function* streamRuntimeExecution(options) {
|
|
|
443
461
|
throw error;
|
|
444
462
|
}
|
|
445
463
|
const streamedExecutionEvidence = buildExecutionRecoveryEvidence({ projectionState });
|
|
446
|
-
const
|
|
447
|
-
|
|
464
|
+
const streamedDelegatedRecoveryInstruction = resolveDelegatedExecutionRecoveryInstruction(streamedExecutionEvidence);
|
|
465
|
+
const delegatedExecutionRecoveryInstruction = !emittedUnsafeStreamSideEffects || streamedDelegatedRecoveryInstruction
|
|
466
|
+
? streamedDelegatedRecoveryInstruction
|
|
448
467
|
: null;
|
|
449
468
|
if (hasUnresolvedExecution(streamedExecutionEvidence) && !delegatedExecutionRecoveryInstruction) {
|
|
450
469
|
throw createUnresolvedExecutionError(streamedExecutionEvidence);
|
|
@@ -453,6 +472,7 @@ export async function* streamRuntimeExecution(options) {
|
|
|
453
472
|
? resolveExecutionWithoutToolEvidenceTextInstruction(request, projectionState.emittedOutput, false, {
|
|
454
473
|
...streamedExecutionEvidence,
|
|
455
474
|
hasMissingDelegatedExecutionEvidence: hasMissingDelegatedExecutionEvidence(streamedExecutionEvidence),
|
|
475
|
+
requiresPlan: requiresPlanEvidence(options.binding),
|
|
456
476
|
})
|
|
457
477
|
: null;
|
|
458
478
|
const streamedRuntimeFailureRecoveryInstruction = projectionState.emittedOutput
|
|
@@ -462,6 +482,7 @@ export async function* streamRuntimeExecution(options) {
|
|
|
462
482
|
? resolveMissingPlanRecoveryInstruction({
|
|
463
483
|
request,
|
|
464
484
|
assistantText: projectionState.emittedOutput,
|
|
485
|
+
requiresPlan: requiresPlanEvidence(options.binding),
|
|
465
486
|
hasPlanStateEvidence: streamedExecutionEvidence.hasPlanStateEvidence,
|
|
466
487
|
hasWriteTodosEvidence: streamedExecutionEvidence.hasPlanStateEvidence,
|
|
467
488
|
hasToolResultEvidence: streamedExecutionEvidence.hasSuccessfulNonTodoToolResultEvidence,
|
|
@@ -484,17 +505,22 @@ export async function* streamRuntimeExecution(options) {
|
|
|
484
505
|
projectionState: createStreamEventProjectionState(),
|
|
485
506
|
executedToolResults,
|
|
486
507
|
});
|
|
508
|
+
if (hasParentLocalToolExecutionAfterDelegationFailure(originalExecutionEvidence, executedToolResults)) {
|
|
509
|
+
throw new ExecutionReconciliationError("Agent attempted parent-local tool execution after delegated task failure; it must report a blocker or re-delegate with task.");
|
|
510
|
+
}
|
|
487
511
|
const retriedVisibleOutput = retried.output ? toVisibleContent(retried.output) : "";
|
|
512
|
+
const retriedIsDelegationFailureFinalReport = isDelegationFailureFinalReport(originalExecutionEvidence, executedToolResults, retriedVisibleOutput);
|
|
488
513
|
const retriedCarriesExecutionEvidence = retriedExecutionEvidence.hasToolResultEvidence
|
|
489
514
|
|| retriedExecutionEvidence.hasOpenTaskDelegation
|
|
490
515
|
|| retriedExecutionEvidence.hasDelegatedExecutionToolEvidence;
|
|
491
|
-
const retriedHasUnresolvedExecution =
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
516
|
+
const retriedHasUnresolvedExecution = !retriedIsDelegationFailureFinalReport
|
|
517
|
+
&& (hasUnresolvedExecution(retriedExecutionEvidence)
|
|
518
|
+
|| hasMissingDelegatedExecutionEvidence(retriedExecutionEvidence)
|
|
519
|
+
|| hasMissingDelegatedFindings(retriedExecutionEvidence)
|
|
520
|
+
|| (!retriedCarriesExecutionEvidence
|
|
521
|
+
&& (hasUnresolvedExecution(originalExecutionEvidence)
|
|
522
|
+
|| hasMissingDelegatedExecutionEvidence(originalExecutionEvidence)
|
|
523
|
+
|| hasMissingDelegatedFindings(originalExecutionEvidence))));
|
|
498
524
|
const effectiveRecoveryEvidence = retriedCarriesExecutionEvidence
|
|
499
525
|
? retriedExecutionEvidence
|
|
500
526
|
: {
|
|
@@ -666,18 +692,21 @@ export async function* streamRuntimeExecution(options) {
|
|
|
666
692
|
? resolveExecutionWithoutToolEvidenceTextInstruction(request, result.output, false, {
|
|
667
693
|
...invokeExecutionEvidence,
|
|
668
694
|
hasMissingDelegatedExecutionEvidence: hasMissingDelegatedExecutionEvidence(invokeExecutionEvidence),
|
|
695
|
+
requiresPlan: requiresPlanEvidence(options.binding),
|
|
669
696
|
})
|
|
670
697
|
: resolveDelegatedExecutionRecoveryInstruction(invokeExecutionEvidence);
|
|
671
698
|
const invokeFallbackMissingPlanRecoveryInstruction = !hasUnresolvedExecution(invokeExecutionEvidence) && !invokeFallbackRecoveryInstruction
|
|
672
699
|
? resolveMissingPlanRecoveryInstruction({
|
|
673
700
|
request,
|
|
674
701
|
assistantText: typeof result.output === "string" ? result.output : "",
|
|
702
|
+
requiresPlan: requiresPlanEvidence(options.binding),
|
|
675
703
|
hasPlanStateEvidence: invokeExecutionEvidence.hasPlanStateEvidence,
|
|
676
704
|
hasWriteTodosEvidence: invokeExecutionEvidence.hasPlanStateEvidence,
|
|
677
705
|
hasToolResultEvidence: invokeExecutionEvidence.hasSuccessfulNonTodoToolResultEvidence,
|
|
678
706
|
})
|
|
679
707
|
: null;
|
|
680
|
-
const effectiveInvokeFallbackRecoveryInstruction = invokeFallbackMissingPlanRecoveryInstruction
|
|
708
|
+
const effectiveInvokeFallbackRecoveryInstruction = invokeFallbackMissingPlanRecoveryInstruction
|
|
709
|
+
?? invokeFallbackRecoveryInstruction;
|
|
681
710
|
if (effectiveInvokeFallbackRecoveryInstruction) {
|
|
682
711
|
const recovered = await options.invoke(options.applyToolRecoveryInstruction(options.binding, effectiveInvokeFallbackRecoveryInstruction), options.input, options.sessionId, options.runtimeOptions.requestId ?? options.sessionId, undefined, options.history, options.runtimeOptions);
|
|
683
712
|
const recoveredToolResults = Array.isArray(recovered.metadata?.executedToolResults)
|
|
@@ -688,17 +717,22 @@ export async function* streamRuntimeExecution(options) {
|
|
|
688
717
|
projectionState: createStreamEventProjectionState(),
|
|
689
718
|
executedToolResults: recoveredToolResults,
|
|
690
719
|
});
|
|
720
|
+
if (hasParentLocalToolExecutionAfterDelegationFailure(originalExecutionEvidence, recoveredToolResults)) {
|
|
721
|
+
throw new ExecutionReconciliationError("Agent attempted parent-local tool execution after delegated task failure; it must report a blocker or re-delegate with task.");
|
|
722
|
+
}
|
|
691
723
|
const recoveredVisibleOutput = recovered.output ? toVisibleContent(recovered.output) : "";
|
|
724
|
+
const recoveredIsDelegationFailureFinalReport = isDelegationFailureFinalReport(originalExecutionEvidence, recoveredToolResults, recoveredVisibleOutput);
|
|
692
725
|
const recoveredCarriesExecutionEvidence = recoveredExecutionEvidence.hasToolResultEvidence
|
|
693
726
|
|| recoveredExecutionEvidence.hasOpenTaskDelegation
|
|
694
727
|
|| recoveredExecutionEvidence.hasDelegatedExecutionToolEvidence;
|
|
695
|
-
const recoveredHasUnresolvedExecution =
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
728
|
+
const recoveredHasUnresolvedExecution = !recoveredIsDelegationFailureFinalReport
|
|
729
|
+
&& (hasUnresolvedExecution(recoveredExecutionEvidence)
|
|
730
|
+
|| hasMissingDelegatedExecutionEvidence(recoveredExecutionEvidence)
|
|
731
|
+
|| hasMissingDelegatedFindings(recoveredExecutionEvidence)
|
|
732
|
+
|| (!recoveredCarriesExecutionEvidence
|
|
733
|
+
&& (hasUnresolvedExecution(originalExecutionEvidence)
|
|
734
|
+
|| hasMissingDelegatedExecutionEvidence(originalExecutionEvidence)
|
|
735
|
+
|| hasMissingDelegatedFindings(originalExecutionEvidence))));
|
|
702
736
|
const effectiveRecoveredEvidence = recoveredCarriesExecutionEvidence
|
|
703
737
|
? recoveredExecutionEvidence
|
|
704
738
|
: {
|
|
@@ -3,6 +3,7 @@ import { salvageFunctionLikeToolCall } from "../parsing/output-tool-args.js";
|
|
|
3
3
|
import { buildStateSnapshot } from "./model/message-assembly.js";
|
|
4
4
|
import { asRecord } from "./tool/resolved-tool.js";
|
|
5
5
|
import { renderToolFailure } from "../support/harness-support.js";
|
|
6
|
+
import { mapTerminalStatusToRequestState, readTerminalExecutionStatus } from "./terminal-status.js";
|
|
6
7
|
function looksLikeLeakedToolCallText(value) {
|
|
7
8
|
const normalized = sanitizeVisibleText(value).trim();
|
|
8
9
|
if (!normalized) {
|
|
@@ -168,6 +169,29 @@ function extractDeterministicToolFailureReport(executedToolResults) {
|
|
|
168
169
|
"- none",
|
|
169
170
|
].join("\n");
|
|
170
171
|
}
|
|
172
|
+
function hasEmptyFinalMessage(result) {
|
|
173
|
+
const messages = Array.isArray(result.messages) ? result.messages : [];
|
|
174
|
+
const lastMessage = messages.at(-1);
|
|
175
|
+
if (!lastMessage || typeof lastMessage !== "object") {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
const direct = lastMessage;
|
|
179
|
+
return direct.content === "" || direct.kwargs?.content === "" || direct.lc_kwargs?.content === "";
|
|
180
|
+
}
|
|
181
|
+
function hasFinalMessageToolCalls(result) {
|
|
182
|
+
const messages = Array.isArray(result.messages) ? result.messages : [];
|
|
183
|
+
const lastMessage = messages.at(-1);
|
|
184
|
+
if (!lastMessage || typeof lastMessage !== "object") {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
const direct = lastMessage;
|
|
188
|
+
return Array.isArray(direct.tool_calls) && direct.tool_calls.length > 0
|
|
189
|
+
|| Array.isArray(direct.invalid_tool_calls) && direct.invalid_tool_calls.length > 0
|
|
190
|
+
|| Array.isArray(direct.kwargs?.tool_calls) && direct.kwargs.tool_calls.length > 0
|
|
191
|
+
|| Array.isArray(direct.kwargs?.invalid_tool_calls) && direct.kwargs.invalid_tool_calls.length > 0
|
|
192
|
+
|| Array.isArray(direct.lc_kwargs?.tool_calls) && direct.lc_kwargs.tool_calls.length > 0
|
|
193
|
+
|| Array.isArray(direct.lc_kwargs?.invalid_tool_calls) && direct.lc_kwargs.invalid_tool_calls.length > 0;
|
|
194
|
+
}
|
|
171
195
|
export function resolveDeterministicFinalOutput(params) {
|
|
172
196
|
const visibleOutput = params.visibleOutput ?? "";
|
|
173
197
|
const toolFallback = params.toolFallback ?? "";
|
|
@@ -178,6 +202,9 @@ export function resolveDeterministicFinalOutput(params) {
|
|
|
178
202
|
const deterministicFailureReport = extractDeterministicToolFailureReport(executedToolResults);
|
|
179
203
|
const delegatedTaskOutput = extractLatestSuccessfulTaskResultText(executedToolResults);
|
|
180
204
|
const successfulToolOutput = extractLatestSuccessfulNonTodoToolResultText(executedToolResults);
|
|
205
|
+
if (sanitizedVisibleOutput && deterministicFailureReport && hasDelegationBlocker(executedToolResults) && !successfulToolOutput) {
|
|
206
|
+
return deterministicFailureReport;
|
|
207
|
+
}
|
|
181
208
|
if (sanitizedVisibleOutput && successfulToolOutput && hasDelegationBlocker(executedToolResults)) {
|
|
182
209
|
return deterministicFailureReport || delegatedTaskOutput || successfulToolOutput;
|
|
183
210
|
}
|
|
@@ -215,9 +242,26 @@ export function finalizeRequestResult(params) {
|
|
|
215
242
|
const visibleOutput = extractedOutput && !isLikelyToolArgsObject(tryParseJson(extractedOutput)) ? extractedOutput : "";
|
|
216
243
|
const emptyAssistantMessageFailure = extractEmptyAssistantMessageFailure(result);
|
|
217
244
|
const toolFallback = extractToolFallbackContext(result);
|
|
245
|
+
const outputContent = extractOutputContent(result);
|
|
246
|
+
const contentBlocks = extractContentBlocks(result);
|
|
247
|
+
const structuredResponse = result.structuredResponse;
|
|
248
|
+
const structuredTerminalStatus = readTerminalExecutionStatus(structuredResponse) ?? readTerminalExecutionStatus(result);
|
|
249
|
+
const files = asRecord(result.files);
|
|
218
250
|
if (!visibleOutput && !toolFallback && emptyAssistantMessageFailure) {
|
|
219
251
|
throw new Error(emptyAssistantMessageFailure);
|
|
220
252
|
}
|
|
253
|
+
if (!visibleOutput
|
|
254
|
+
&& !toolFallback
|
|
255
|
+
&& interruptContent === undefined
|
|
256
|
+
&& outputContent === undefined
|
|
257
|
+
&& contentBlocks.length === 0
|
|
258
|
+
&& structuredResponse === undefined
|
|
259
|
+
&& !files
|
|
260
|
+
&& executedToolResults.length === 0
|
|
261
|
+
&& hasEmptyFinalMessage(result)
|
|
262
|
+
&& !hasFinalMessageToolCalls(result)) {
|
|
263
|
+
throw new Error("empty_final_output");
|
|
264
|
+
}
|
|
221
265
|
const serializedResult = JSON.stringify(result, null, 2);
|
|
222
266
|
const output = resolveDeterministicFinalOutput({
|
|
223
267
|
visibleOutput,
|
|
@@ -226,17 +270,16 @@ export function finalizeRequestResult(params) {
|
|
|
226
270
|
})
|
|
227
271
|
|| (containsLikelySkillDocument(result) ? "" : serializedResult);
|
|
228
272
|
const finalMessageText = sanitizeVisibleText(output);
|
|
229
|
-
const
|
|
230
|
-
const contentBlocks = extractContentBlocks(result);
|
|
231
|
-
const structuredResponse = result.structuredResponse;
|
|
232
|
-
const files = asRecord(result.files);
|
|
273
|
+
const terminalStatus = structuredTerminalStatus ?? readTerminalExecutionStatus(finalMessageText);
|
|
233
274
|
const stateSnapshot = buildStateSnapshot(result);
|
|
234
275
|
const memoryCandidates = executedToolResults.flatMap((toolResult) => toolResult.memoryCandidates ?? []);
|
|
235
276
|
return {
|
|
236
277
|
sessionId,
|
|
237
278
|
requestId,
|
|
238
279
|
agentId: bindingAgentId,
|
|
239
|
-
state: Array.isArray(result.__interrupt__) && result.__interrupt__.length > 0
|
|
280
|
+
state: Array.isArray(result.__interrupt__) && result.__interrupt__.length > 0
|
|
281
|
+
? "waiting_for_approval"
|
|
282
|
+
: mapTerminalStatusToRequestState(terminalStatus),
|
|
240
283
|
interruptContent,
|
|
241
284
|
output: finalMessageText,
|
|
242
285
|
finalMessageText,
|
|
@@ -247,6 +290,7 @@ export function finalizeRequestResult(params) {
|
|
|
247
290
|
...(executedToolResults.length > 0 ? { executedToolResults } : {}),
|
|
248
291
|
...(memoryCandidates.length > 0 ? { memoryCandidates } : {}),
|
|
249
292
|
...(structuredResponse !== undefined ? { structuredResponse } : {}),
|
|
293
|
+
...(terminalStatus ? { terminalStatus } : {}),
|
|
250
294
|
...(outputContent !== undefined ? { outputContent } : {}),
|
|
251
295
|
...(contentBlocks.length > 0 ? { contentBlocks } : {}),
|
|
252
296
|
...(files ? { files } : {}),
|
|
@@ -43,6 +43,9 @@ function hasNonTodoToolEvidence(executedToolResults) {
|
|
|
43
43
|
function hasPlanStateEvidence(executedToolResults) {
|
|
44
44
|
return executedToolResults.some((item) => item.toolName === "write_todos" || item.toolName === "read_todos" || readPlanStateSummary(item.output) !== null);
|
|
45
45
|
}
|
|
46
|
+
function requiresPlanEvidence(binding) {
|
|
47
|
+
return binding.harnessRuntime.executionContract?.requiresPlan === true;
|
|
48
|
+
}
|
|
46
49
|
function extractLatestUserInput(request) {
|
|
47
50
|
const typedRequest = request;
|
|
48
51
|
const messages = Array.isArray(typedRequest.messages) ? typedRequest.messages : [];
|
|
@@ -87,6 +90,7 @@ export async function runLocalToolInvocationLoop({ binding, request, primaryTool
|
|
|
87
90
|
hasToolResultEvidence: hasExecutionBeyondTodoPlanning,
|
|
88
91
|
hasPlanStateEvidence: hasPlanStateEvidence(executedToolResults),
|
|
89
92
|
hasIncompletePlanState: hasExecutionBeyondTodoPlanning && hasIncompletePlanState,
|
|
93
|
+
requiresPlan: requiresPlanEvidence(binding),
|
|
90
94
|
})
|
|
91
95
|
: hasIncompletePlanState && hasExecutionBeyondTodoPlanning
|
|
92
96
|
? AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION
|
|
@@ -102,6 +106,7 @@ export async function runLocalToolInvocationLoop({ binding, request, primaryTool
|
|
|
102
106
|
}
|
|
103
107
|
const missingPlanRecoveryInstruction = resolveMissingPlanRecoveryInstruction({
|
|
104
108
|
request: activeRequest,
|
|
109
|
+
requiresPlan: requiresPlanEvidence(binding),
|
|
105
110
|
hasPlanStateEvidence: hasPlanStateEvidence(executedToolResults),
|
|
106
111
|
hasWriteTodosEvidence: executedToolResults.some((item) => item.toolName === "write_todos"),
|
|
107
112
|
hasToolResultEvidence: executedToolResults.length > 0 || toolCalls.length > 0,
|