@mono-agent/agent-runtime 0.20.14 → 0.21.0
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/ARCHITECTURE.md +50 -11
- package/MIGRATION.md +30 -7
- package/README.md +219 -35
- package/package.json +9 -4
- package/src/agent/tool-bloat.js +145 -9
- package/src/agent/tools/agent-tool.js +104 -5
- package/src/agent/tools/bash.js +10 -2
- package/src/agent/tools/codex-subscription-search.js +122 -28
- package/src/agent/tools/exec.js +10 -2
- package/src/agent/tools/monitor.js +11 -2
- package/src/agent/tools/pi-bridge.js +33 -14
- package/src/agent/tools/shared/monitors.js +22 -3
- package/src/agent/tools/shared/path-resolver.js +25 -6
- package/src/agent/tools/shared/process-jobs.js +6 -1
- package/src/agent/tools/shared/process-runner.js +3 -1
- package/src/agent/tools/shared/tool-context.js +8 -0
- package/src/agent/tools/web-access-interstitial.js +70 -0
- package/src/agent/tools/web-browser-render.js +83 -58
- package/src/agent/tools/web-controller.js +112 -21
- package/src/agent/tools/web-document-extractor.js +379 -0
- package/src/agent/tools/web-fetch.js +271 -243
- package/src/agent/tools/web-request.js +65 -0
- package/src/agent/tools/web-search-output.js +165 -0
- package/src/agent/tools/web-search-state.js +75 -0
- package/src/agent/tools/web-search.js +532 -71
- package/src/ai/failure.js +3 -3
- package/src/ai/index.js +1 -0
- package/src/ai/observer.js +8 -0
- package/src/ai/pi-interop.js +156 -0
- package/src/ai/provider-check.js +131 -0
- package/src/ai/providers/pi-native/compaction-driver.js +45 -21
- package/src/ai/providers/pi-native/compaction-summary.js +140 -0
- package/src/ai/providers/pi-native/harness-adapter.js +40 -2
- package/src/ai/providers/pi-native/prompt-cache-diagnostics.js +103 -0
- package/src/ai/providers/pi-native/provider-attribution.js +102 -0
- package/src/ai/providers/pi-native/result-builder.js +28 -4
- package/src/ai/providers/pi-native/session-lifecycle.js +167 -24
- package/src/ai/providers/pi-native/stream-subscriber.js +30 -2
- package/src/ai/providers/pi-native/terminal-recovery.js +40 -0
- package/src/ai/providers/pi-native/turn-runner.js +245 -13
- package/src/ai/providers/pi-native.js +159 -40
- package/src/ai/runtime/live-input-events.js +250 -54
- package/src/ai/runtime/router.js +30 -11
- package/src/ai/tool-lifecycle.js +32 -18
- package/src/ai/types.js +26 -5
- package/src/runtime.js +24 -5
- package/types/agent/tool-bloat.d.ts +1 -1
- package/types/agent/tools/agent-tool.d.ts +4 -1
- package/types/agent/tools/bash.d.ts +5 -3
- package/types/agent/tools/codex-subscription-search.d.ts +6 -2
- package/types/agent/tools/exec.d.ts +5 -3
- package/types/agent/tools/monitor.d.ts +5 -2
- package/types/agent/tools/pi-bridge.d.ts +6 -4
- package/types/agent/tools/shared/monitors.d.ts +17 -2
- package/types/agent/tools/shared/process-jobs.d.ts +5 -1
- package/types/agent/tools/shared/process-runner.d.ts +3 -2
- package/types/agent/tools/shared/tool-context.d.ts +2 -0
- package/types/agent/tools/web-access-interstitial.d.ts +23 -0
- package/types/agent/tools/web-browser-render.d.ts +4 -1
- package/types/agent/tools/web-controller.d.ts +4 -2
- package/types/agent/tools/web-document-extractor.d.ts +27 -0
- package/types/agent/tools/web-fetch.d.ts +19 -24
- package/types/agent/tools/web-request.d.ts +20 -0
- package/types/agent/tools/web-search-output.d.ts +31 -0
- package/types/agent/tools/web-search-state.d.ts +21 -0
- package/types/agent/tools/web-search.d.ts +10 -45
- package/types/ai/index.d.ts +1 -0
- package/types/ai/observer.d.ts +6 -0
- package/types/ai/pi-interop.d.ts +61 -0
- package/types/ai/provider-check.d.ts +53 -0
- package/types/ai/providers/pi-native/compaction-driver.d.ts +2 -1
- package/types/ai/providers/pi-native/compaction-summary.d.ts +19 -0
- package/types/ai/providers/pi-native/harness-adapter.d.ts +3 -1
- package/types/ai/providers/pi-native/prompt-cache-diagnostics.d.ts +3 -0
- package/types/ai/providers/pi-native/provider-attribution.d.ts +26 -0
- package/types/ai/providers/pi-native/result-builder.d.ts +11 -1
- package/types/ai/providers/pi-native/session-lifecycle.d.ts +23 -5
- package/types/ai/providers/pi-native/terminal-recovery.d.ts +2 -0
- package/types/ai/providers/pi-native/turn-runner.d.ts +36 -5
- package/types/ai/runtime/live-input-events.d.ts +32 -8
- package/types/ai/tool-lifecycle.d.ts +4 -3
- package/types/ai/types.d.ts +140 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mono-agent/agent-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Agent runtime backed by the Pi SDK provider catalog",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "GPL-3.0-only",
|
|
@@ -100,22 +100,27 @@
|
|
|
100
100
|
"LICENSE"
|
|
101
101
|
],
|
|
102
102
|
"engines": {
|
|
103
|
-
"node": ">=
|
|
103
|
+
"node": ">=24.15.0"
|
|
104
104
|
},
|
|
105
105
|
"dependencies": {
|
|
106
|
-
"@earendil-works/pi-agent-core": "0.85.
|
|
107
|
-
"@earendil-works/pi-ai": "0.85.
|
|
106
|
+
"@earendil-works/pi-agent-core": "0.85.1",
|
|
107
|
+
"@earendil-works/pi-ai": "0.85.1",
|
|
108
108
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
109
109
|
"@mozilla/readability": "0.6.0",
|
|
110
110
|
"@vscode/ripgrep": "1.18.0",
|
|
111
111
|
"bmp-ts": "1.0.9",
|
|
112
112
|
"cross-spawn": "^7.0.6",
|
|
113
113
|
"defuddle": "0.19.2",
|
|
114
|
+
"fast-xml-parser": "5.7.3",
|
|
114
115
|
"linkedom": "0.18.13",
|
|
115
116
|
"sharp": "0.35.3",
|
|
117
|
+
"turndown": "7.2.4",
|
|
116
118
|
"unpdf": "1.8.0",
|
|
117
119
|
"zod": "^4.3.6"
|
|
118
120
|
},
|
|
121
|
+
"devDependencies": {
|
|
122
|
+
"@types/turndown": "5.0.6"
|
|
123
|
+
},
|
|
119
124
|
"scripts": {
|
|
120
125
|
"build": "tsc -p tsconfig.types.json",
|
|
121
126
|
"typecheck": "tsc -p tsconfig.types.json --noEmit",
|
package/src/agent/tool-bloat.js
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
// still cite the artifact.
|
|
7
7
|
//
|
|
8
8
|
// Persistence is delegated to the host via a `persistArtifact({ filename, buffer,
|
|
9
|
-
// toolName, toolUseId }) -> path | null` callback.
|
|
10
|
-
//
|
|
11
|
-
// Hosts that don't
|
|
9
|
+
// toolName, toolUseId }) -> path | null` callback. The app binds that callback
|
|
10
|
+
// per run and stores payloads under its configured tool-output artifact root.
|
|
11
|
+
// Hosts that don't provide a sink still receive bounded retained text.
|
|
12
12
|
|
|
13
13
|
export const MAX_TOOL_RESULT_BYTES = 262144;
|
|
14
14
|
|
|
@@ -22,6 +22,11 @@ export const DEFAULT_TOOL_BLOAT_CONFIG = Object.freeze({
|
|
|
22
22
|
binaryBloatTools: BINARY_BLOAT_TOOLS,
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
+
const RETAINED_UNTRUSTED_BEGIN = "[BEGIN RETAINED UNTRUSTED TOOL RESULT]";
|
|
26
|
+
const RETAINED_UNTRUSTED_END = "[END RETAINED UNTRUSTED TOOL RESULT]";
|
|
27
|
+
const RETAINED_MIDDLE_NOTICE = "[Omitted middle may contain additional source content; retained tail is not the source ending.]";
|
|
28
|
+
const RETAINED_OMISSION_RE = /\[\.\.\. (\d+) source bytes omitted \.\.\.\]/gu;
|
|
29
|
+
|
|
25
30
|
function blockBytes(block) {
|
|
26
31
|
if (!block || typeof block !== "object") return 0;
|
|
27
32
|
if (block.type === "text") return Buffer.byteLength(String(block.text || ""), "utf8");
|
|
@@ -74,17 +79,144 @@ function persistBlock(toolName, block, idx, idTag, persistArtifact) {
|
|
|
74
79
|
}
|
|
75
80
|
}
|
|
76
81
|
|
|
77
|
-
function
|
|
82
|
+
function controlFree(value) {
|
|
83
|
+
return String(value || "").replace(/[\u0000-\u001f\u007f]+/gu, " ").trim();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function persistenceSummary(savedPaths, includePath = true) {
|
|
87
|
+
if (savedPaths.length === 0) return "persistence unavailable";
|
|
88
|
+
if (!includePath) return `saved_files=${savedPaths.length}`;
|
|
89
|
+
if (savedPaths.length === 1) return `saved_to=${controlFree(savedPaths[0])}`;
|
|
90
|
+
return `saved_to=${controlFree(savedPaths[0])}; saved_files=${savedPaths.length}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function summaryText(toolName, originalBytes, retainedBytes, maxBytes, savedPaths, includePath = true) {
|
|
78
94
|
const parts = [
|
|
79
95
|
`[truncated tool_result: ${originalBytes} bytes exceeded ${maxBytes} byte cap`,
|
|
80
|
-
`
|
|
96
|
+
`retained=${retainedBytes} bytes`,
|
|
97
|
+
`tool=${controlFree(toolName)}`,
|
|
81
98
|
];
|
|
82
|
-
|
|
83
|
-
else if (savedPaths.length > 1) parts.push(`saved_to=[${savedPaths.length} files]`);
|
|
84
|
-
else parts.push("persistence unavailable");
|
|
99
|
+
parts.push(persistenceSummary(savedPaths, includePath));
|
|
85
100
|
return `${parts.join("; ")}]`;
|
|
86
101
|
}
|
|
87
102
|
|
|
103
|
+
function capSummary(text, maxBytes) {
|
|
104
|
+
for (const candidate of [text, "[truncated tool_result]", "[truncated]", "[]"]) {
|
|
105
|
+
if (Buffer.byteLength(candidate, "utf8") <= maxBytes) return candidate;
|
|
106
|
+
}
|
|
107
|
+
return "";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function summaryOnly(toolName, originalBytes, maxBytes, savedPaths) {
|
|
111
|
+
const parts = [
|
|
112
|
+
`[truncated tool_result: ${originalBytes} bytes exceeded ${maxBytes} byte cap`,
|
|
113
|
+
`tool=${controlFree(toolName)}`,
|
|
114
|
+
];
|
|
115
|
+
parts.push(persistenceSummary(savedPaths));
|
|
116
|
+
return `${parts.join("; ")}]`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function neutralizeRetainedFrames(value) {
|
|
120
|
+
return wellFormedText(value)
|
|
121
|
+
.replaceAll(RETAINED_UNTRUSTED_BEGIN, `(BEGIN RETAINED UNTRUSTED TOOL RESULT)`)
|
|
122
|
+
.replaceAll(RETAINED_UNTRUSTED_END, `(END RETAINED UNTRUSTED TOOL RESULT)`)
|
|
123
|
+
.replaceAll(RETAINED_MIDDLE_NOTICE, `(Omitted middle may contain additional source content; retained tail is not the source ending.)`)
|
|
124
|
+
.replace(RETAINED_OMISSION_RE, `(... $1 source bytes omitted ...)`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function wellFormedText(value) {
|
|
128
|
+
let out = "";
|
|
129
|
+
for (const point of String(value || "")) {
|
|
130
|
+
const unit = point.charCodeAt(0);
|
|
131
|
+
out += point.length === 1 && unit >= 0xd800 && unit <= 0xdfff ? "\uFFFD" : point;
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function utf8Head(segments, maxBytes) {
|
|
137
|
+
if (maxBytes <= 0) return { text: "", sourceBytes: 0 };
|
|
138
|
+
let bytes = 0;
|
|
139
|
+
let out = "";
|
|
140
|
+
let sourceBytes = 0;
|
|
141
|
+
outer: for (const segment of segments) {
|
|
142
|
+
for (const point of segment.text) {
|
|
143
|
+
const size = Buffer.byteLength(point, "utf8");
|
|
144
|
+
if (bytes + size > maxBytes) break outer;
|
|
145
|
+
out += point;
|
|
146
|
+
bytes += size;
|
|
147
|
+
if (segment.source) sourceBytes += size;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return { text: out, sourceBytes };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function utf8Tail(segments, maxBytes) {
|
|
154
|
+
if (maxBytes <= 0) return { text: "", sourceBytes: 0 };
|
|
155
|
+
let bytes = 0;
|
|
156
|
+
const out = [];
|
|
157
|
+
let sourceBytes = 0;
|
|
158
|
+
outer: for (const segment of [...segments].reverse()) {
|
|
159
|
+
for (const point of [...segment.text].reverse()) {
|
|
160
|
+
const size = Buffer.byteLength(point, "utf8");
|
|
161
|
+
if (bytes + size > maxBytes) break outer;
|
|
162
|
+
out.push(point);
|
|
163
|
+
bytes += size;
|
|
164
|
+
if (segment.source) sourceBytes += size;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return { text: out.reverse().join(""), sourceBytes };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function retainedTextPayload(toolName, blocks, originalBytes, maxBytes, savedPaths) {
|
|
171
|
+
const segments = blocks.flatMap((block, index) => [
|
|
172
|
+
...(index === 0 ? [] : [{ text: "\n\n", source: false }]),
|
|
173
|
+
{ text: neutralizeRetainedFrames(block.text), source: true },
|
|
174
|
+
]);
|
|
175
|
+
const maximumDigits = String(originalBytes).length;
|
|
176
|
+
const retainedPlaceholder = "9".repeat(maximumDigits);
|
|
177
|
+
const omittedPlaceholder = "9".repeat(maximumDigits);
|
|
178
|
+
let summary = summaryText(toolName, originalBytes, Number(retainedPlaceholder), maxBytes, savedPaths, true);
|
|
179
|
+
const fixedBody = [
|
|
180
|
+
summary,
|
|
181
|
+
RETAINED_UNTRUSTED_BEGIN,
|
|
182
|
+
"",
|
|
183
|
+
`[... ${omittedPlaceholder} source bytes omitted ...]`,
|
|
184
|
+
RETAINED_MIDDLE_NOTICE,
|
|
185
|
+
"",
|
|
186
|
+
RETAINED_UNTRUSTED_END,
|
|
187
|
+
].join("\n");
|
|
188
|
+
if (Buffer.byteLength(fixedBody, "utf8") > maxBytes) {
|
|
189
|
+
summary = summaryText(toolName, originalBytes, 0, maxBytes, savedPaths, false);
|
|
190
|
+
return capSummary(summary, maxBytes);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const sourceBudget = maxBytes - Buffer.byteLength(fixedBody, "utf8");
|
|
194
|
+
const head = utf8Head(segments, Math.floor(sourceBudget * 0.6));
|
|
195
|
+
const tail = utf8Tail(segments, sourceBudget - Buffer.byteLength(head.text, "utf8"));
|
|
196
|
+
const retainedBytes = head.sourceBytes + tail.sourceBytes;
|
|
197
|
+
const omittedBytes = Math.max(0, originalBytes - retainedBytes);
|
|
198
|
+
summary = summaryText(toolName, originalBytes, retainedBytes, maxBytes, savedPaths, true);
|
|
199
|
+
const rendered = [
|
|
200
|
+
summary,
|
|
201
|
+
RETAINED_UNTRUSTED_BEGIN,
|
|
202
|
+
head.text,
|
|
203
|
+
`[... ${omittedBytes} source bytes omitted ...]`,
|
|
204
|
+
RETAINED_MIDDLE_NOTICE,
|
|
205
|
+
tail.text,
|
|
206
|
+
RETAINED_UNTRUSTED_END,
|
|
207
|
+
].join("\n");
|
|
208
|
+
if (Buffer.byteLength(rendered, "utf8") <= maxBytes) return rendered;
|
|
209
|
+
|
|
210
|
+
// A long saved path can make the exact-path summary larger than the reserved
|
|
211
|
+
// placeholder. Preserve the trust frame and source slices by falling back to
|
|
212
|
+
// the honest saved-file count, never by returning a partial path.
|
|
213
|
+
const countSummary = summaryText(toolName, originalBytes, retainedBytes, maxBytes, savedPaths, false);
|
|
214
|
+
const countRendered = [countSummary, ...rendered.split("\n").slice(1)].join("\n");
|
|
215
|
+
return Buffer.byteLength(countRendered, "utf8") <= maxBytes
|
|
216
|
+
? countRendered
|
|
217
|
+
: capSummary(countSummary, maxBytes);
|
|
218
|
+
}
|
|
219
|
+
|
|
88
220
|
export function summarisePayload(toolName, contentBlocks, persistArtifact, options = {}) {
|
|
89
221
|
const {
|
|
90
222
|
maxBytes = MAX_TOOL_RESULT_BYTES,
|
|
@@ -109,8 +241,12 @@ export function summarisePayload(toolName, contentBlocks, persistArtifact, optio
|
|
|
109
241
|
const path = persistBlock(toolName, block, idx, idTag, persistArtifact);
|
|
110
242
|
if (path) savedPaths.push(path);
|
|
111
243
|
});
|
|
244
|
+
const textOnly = blocks.length > 0 && blocks.every((block) => block?.type === "text");
|
|
245
|
+
const rewrittenText = textOnly && otherBytes > maxBytes && imageBytes === 0
|
|
246
|
+
? retainedTextPayload(toolName, blocks, originalBytes, maxBytes, savedPaths)
|
|
247
|
+
: summaryOnly(toolName, originalBytes, maxBytes, savedPaths);
|
|
112
248
|
return {
|
|
113
|
-
rewrittenBlocks: [{ type: "text", text:
|
|
249
|
+
rewrittenBlocks: [{ type: "text", text: rewrittenText }],
|
|
114
250
|
savedPaths,
|
|
115
251
|
originalBytes,
|
|
116
252
|
truncated: true,
|
|
@@ -203,7 +203,7 @@ function positiveInt(value, fallback) {
|
|
|
203
203
|
* Build the `Agent` tool, or null when subagents are unavailable for this run.
|
|
204
204
|
*
|
|
205
205
|
* @param {RuntimeSubagentsOptions|null|undefined} subagents
|
|
206
|
-
* @param {{model?: *, cwd?: string, parentRunId?: string, sandboxPolicy?: *, sandboxEngine?: *, skills?: {name: string, description?: string}[], skillsRoot?: string, toolEnvironment?: *, onEvent?: (event: *) => void}} [context]
|
|
206
|
+
* @param {{model?: *, cwd?: string, parentRunId?: string, sandboxPolicy?: *, sandboxEngine?: *, skills?: {name: string, description?: string}[], skillsRoot?: string, toolEnvironment?: *, webSearchConfig?: *, webRequestCoordinator?: *, webFetchConfig?: *, onEvent?: (event: *) => void}} [context]
|
|
207
207
|
* @returns {*|null}
|
|
208
208
|
*/
|
|
209
209
|
export function createAgentTool(subagents, context = {}) {
|
|
@@ -410,6 +410,9 @@ export function createAgentTool(subagents, context = {}) {
|
|
|
410
410
|
...(context.skills === undefined ? {} : { skills: context.skills }),
|
|
411
411
|
...(context.skillsRoot === undefined ? {} : { skillsRoot: context.skillsRoot }),
|
|
412
412
|
...(context.toolEnvironment === undefined ? {} : { toolEnvironment: context.toolEnvironment }),
|
|
413
|
+
...(context.webSearchConfig === undefined ? {} : { webSearchConfig: context.webSearchConfig }),
|
|
414
|
+
...(context.webRequestCoordinator === undefined ? {} : { webRequestCoordinator: context.webRequestCoordinator }),
|
|
415
|
+
...(context.webFetchConfig === undefined ? {} : { webFetchConfig: context.webFetchConfig }),
|
|
413
416
|
abortSignal: controller.signal,
|
|
414
417
|
maxTurns,
|
|
415
418
|
callId: toolCallId,
|
|
@@ -447,7 +450,7 @@ export function createAgentTool(subagents, context = {}) {
|
|
|
447
450
|
|
|
448
451
|
const durationMs = Date.now() - startedAt;
|
|
449
452
|
const outcome = classifyOutcome({ result, thrown, timedOut, abandoned });
|
|
450
|
-
collector.finished({ status: outcome.status, durationMs });
|
|
453
|
+
collector.finished({ status: outcome.status, durationMs, result });
|
|
451
454
|
// Each result is individually capped, but the parent's context sees the
|
|
452
455
|
// SUM. The description encourages parallel calls, so twenty valid results
|
|
453
456
|
// would otherwise land ~480KB in one batch. Later calls get whatever
|
|
@@ -598,6 +601,22 @@ function normalizeProfile(definition) {
|
|
|
598
601
|
|
|
599
602
|
/** Hard cap on any single forwarded payload, so the operator wire's binary-search reducer never has to run. */
|
|
600
603
|
const WIRE_CONTENT_MAX_CHARS = 2_000;
|
|
604
|
+
const ROUTE_HISTORY_MAX_ENTRIES = 32;
|
|
605
|
+
|
|
606
|
+
function boundedRouteString(value, max = 256) {
|
|
607
|
+
return typeof value === "string" && value.length > 0 ? value.slice(0, max) : undefined;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function boundedRouteIndex(value) {
|
|
611
|
+
return Number.isFinite(value) && value >= 0 ? Math.floor(value) : undefined;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function appendRouteEntry(entries, entry, routeState) {
|
|
615
|
+
entries.push(entry);
|
|
616
|
+
if (entries.length <= ROUTE_HISTORY_MAX_ENTRIES) return;
|
|
617
|
+
entries.splice(1, entries.length - ROUTE_HISTORY_MAX_ENTRIES);
|
|
618
|
+
routeState.truncated = true;
|
|
619
|
+
}
|
|
601
620
|
|
|
602
621
|
/**
|
|
603
622
|
* Translates the child's raw provider events into (a) a bounded per-tool-call
|
|
@@ -624,6 +643,7 @@ function createActivityCollector({ callId, profileName, callIndex, label, emit,
|
|
|
624
643
|
/** What the child reported spending, so the parent run can own it. */
|
|
625
644
|
const usage = emptyUsage();
|
|
626
645
|
const subagent = { id: callId, name: profileName, callIndex, ...(label === undefined ? {} : { label }) };
|
|
646
|
+
const routeState = { requested: {}, attempted: undefined, transitions: [], retries: [], truncated: false };
|
|
627
647
|
|
|
628
648
|
/** @param {*} event */
|
|
629
649
|
const publish = (event) => {
|
|
@@ -646,11 +666,32 @@ function createActivityCollector({ callId, profileName, callIndex, label, emit,
|
|
|
646
666
|
arguments: { name: profileName, ...(label === undefined ? {} : { description: label }) },
|
|
647
667
|
});
|
|
648
668
|
},
|
|
649
|
-
/** @param {{status: string, durationMs: number}} outcome */
|
|
650
|
-
finished({ status, durationMs }) {
|
|
669
|
+
/** @param {{status: string, durationMs: number, result?: *}} outcome */
|
|
670
|
+
finished({ status, durationMs, result }) {
|
|
651
671
|
// Before the bookend, so the run's own usage report can already include
|
|
652
672
|
// it, and so an abandoned child still hands over whatever it spent.
|
|
653
673
|
recordUsage?.(usage);
|
|
674
|
+
const executed = status === "ok" && result && typeof result === "object"
|
|
675
|
+
? {
|
|
676
|
+
...(boundedRouteString(result.model) === undefined ? {} : { model: boundedRouteString(result.model) }),
|
|
677
|
+
...(boundedRouteString(result.effort, 64) === undefined ? {} : { effort: boundedRouteString(result.effort, 64) }),
|
|
678
|
+
...(boundedRouteString(result.effectiveEffort, 64) === undefined ? {} : { effectiveEffort: boundedRouteString(result.effectiveEffort, 64) }),
|
|
679
|
+
}
|
|
680
|
+
: undefined;
|
|
681
|
+
const requestedModel = routeState.requested.model;
|
|
682
|
+
const fallback = routeState.transitions.length > 0
|
|
683
|
+
|| (executed?.model !== undefined && requestedModel !== undefined && executed.model !== requestedModel);
|
|
684
|
+
const attribution = requestedModel === undefined && routeState.attempted === undefined && executed === undefined
|
|
685
|
+
? undefined
|
|
686
|
+
: {
|
|
687
|
+
requested: routeState.requested,
|
|
688
|
+
...(routeState.attempted === undefined ? {} : { attempted: routeState.attempted }),
|
|
689
|
+
...(executed === undefined ? {} : { executed }),
|
|
690
|
+
disposition: requestedModel === undefined ? "unknown" : fallback ? "fallback" : "requested",
|
|
691
|
+
transitions: routeState.transitions,
|
|
692
|
+
retries: routeState.retries,
|
|
693
|
+
...(routeState.truncated ? { truncated: true } : {}),
|
|
694
|
+
};
|
|
654
695
|
publish({
|
|
655
696
|
phase: "agent_completed",
|
|
656
697
|
id: `agent:${callId}`,
|
|
@@ -658,7 +699,13 @@ function createActivityCollector({ callId, profileName, callIndex, label, emit,
|
|
|
658
699
|
// The one place the child's price is knowable per delegation: operator
|
|
659
700
|
// surfaces show it on the row so an expensive one is identifiable, not
|
|
660
701
|
// just visible in the run total it disappears into.
|
|
661
|
-
...(usage.costUsd > 0
|
|
702
|
+
...((usage.costUsd > 0 || attribution !== undefined)
|
|
703
|
+
? { subagent: {
|
|
704
|
+
...subagent,
|
|
705
|
+
...(usage.costUsd > 0 ? { costUsd: usage.costUsd } : {}),
|
|
706
|
+
...(attribution === undefined ? {} : { attribution }),
|
|
707
|
+
} }
|
|
708
|
+
: {}),
|
|
662
709
|
isError: status !== "ok",
|
|
663
710
|
executionMs: durationMs,
|
|
664
711
|
content: `${status} · ${done.length} tool call${done.length === 1 ? "" : "s"}`,
|
|
@@ -681,6 +728,58 @@ function createActivityCollector({ callId, profileName, callIndex, label, emit,
|
|
|
681
728
|
/** @param {*} event */
|
|
682
729
|
observe(event) {
|
|
683
730
|
const type = event?.type;
|
|
731
|
+
if (type === "provider_execution_config") {
|
|
732
|
+
const model = boundedRouteString(event.model);
|
|
733
|
+
const effort = boundedRouteString(event.effort, 64);
|
|
734
|
+
const effectiveEffort = boundedRouteString(event.effectiveEffort, 64);
|
|
735
|
+
routeState.attempted = {
|
|
736
|
+
...(model === undefined ? {} : { model }),
|
|
737
|
+
...(effort === undefined ? {} : { effort }),
|
|
738
|
+
...(effectiveEffort === undefined ? {} : { effectiveEffort }),
|
|
739
|
+
};
|
|
740
|
+
if (routeState.requested.model === undefined && routeState.transitions.length === 0) {
|
|
741
|
+
routeState.requested = {
|
|
742
|
+
...(model === undefined ? {} : { model }),
|
|
743
|
+
...(effort === undefined ? {} : { effort }),
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
if (type === "provider_failover_started") {
|
|
749
|
+
const from = boundedRouteString(event.from);
|
|
750
|
+
const to = boundedRouteString(event.to);
|
|
751
|
+
if (from !== undefined && to !== undefined) {
|
|
752
|
+
if (routeState.requested.model === undefined) routeState.requested = { model: from };
|
|
753
|
+
const attemptIndex = boundedRouteIndex(event.attemptIndex);
|
|
754
|
+
const reason = boundedRouteString(event.reason, 128);
|
|
755
|
+
appendRouteEntry(routeState.transitions, {
|
|
756
|
+
from,
|
|
757
|
+
to,
|
|
758
|
+
...(attemptIndex === undefined ? {} : { attemptIndex }),
|
|
759
|
+
...(reason === undefined ? {} : { reason }),
|
|
760
|
+
}, routeState);
|
|
761
|
+
routeState.attempted = { model: to };
|
|
762
|
+
}
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
if (type === "provider_retry_started") {
|
|
766
|
+
const model = boundedRouteString(event.model);
|
|
767
|
+
const retryIndex = boundedRouteIndex(event.retryIndex);
|
|
768
|
+
const attempts = boundedRouteIndex(event.attempts);
|
|
769
|
+
const reason = boundedRouteString(event.reason, 128);
|
|
770
|
+
appendRouteEntry(routeState.retries, {
|
|
771
|
+
...(model === undefined ? {} : { model }),
|
|
772
|
+
...(retryIndex === undefined ? {} : { retryIndex }),
|
|
773
|
+
...(attempts === undefined ? {} : { attempts }),
|
|
774
|
+
...(reason === undefined ? {} : { reason }),
|
|
775
|
+
}, routeState);
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
if (type === "provider_failover_completed") {
|
|
779
|
+
const model = boundedRouteString(event.model);
|
|
780
|
+
if (model !== undefined) routeState.attempted = { ...(routeState.attempted || {}), model };
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
684
783
|
// Providers legitimately forward multiple blocks in one message; the
|
|
685
784
|
// Claude bridges do routinely. Inspecting only content[0] dropped every
|
|
686
785
|
// tool call after the first.
|
package/src/agent/tools/bash.js
CHANGED
|
@@ -72,7 +72,7 @@ export function normalizeBackgroundBashTimeoutMs(value) {
|
|
|
72
72
|
/**
|
|
73
73
|
* Compatibility wrapper retained for direct callers and tests.
|
|
74
74
|
*
|
|
75
|
-
* @param {{command: string, description?: string, timeout?: number, timeout_ms?: number, max_output_chars?: number, workdir?: string, background?: boolean}} params
|
|
75
|
+
* @param {{command: string, description?: string, timeout?: number, timeout_ms?: number, max_output_chars?: number, workdir?: string, background?: boolean, wake_on_completion?: boolean}} params
|
|
76
76
|
* @param {{signal?: AbortSignal, sandboxPolicy?: any, sandboxEngine?: any, ctx?: any, processJobsController?: import("./shared/process-jobs.js").ProcessJobsController}} [options]
|
|
77
77
|
*/
|
|
78
78
|
export async function bashToolImpl(params, options = {}) {
|
|
@@ -82,7 +82,7 @@ export async function bashToolImpl(params, options = {}) {
|
|
|
82
82
|
/**
|
|
83
83
|
* Structured Bash execution used by the Pi bridge.
|
|
84
84
|
*
|
|
85
|
-
* @param {{command: string, description?: string, timeout?: number, timeout_ms?: number, max_output_chars?: number, workdir?: string, background?: boolean}} params
|
|
85
|
+
* @param {{command: string, description?: string, timeout?: number, timeout_ms?: number, max_output_chars?: number, workdir?: string, background?: boolean, wake_on_completion?: boolean}} params
|
|
86
86
|
* @param {{signal?: AbortSignal, sandboxPolicy?: any, sandboxEngine?: any, ctx?: any, processJobsController?: import("./shared/process-jobs.js").ProcessJobsController}} [options]
|
|
87
87
|
*/
|
|
88
88
|
export async function bashToolRun(
|
|
@@ -94,6 +94,7 @@ export async function bashToolRun(
|
|
|
94
94
|
max_output_chars,
|
|
95
95
|
workdir,
|
|
96
96
|
background,
|
|
97
|
+
wake_on_completion,
|
|
97
98
|
},
|
|
98
99
|
{
|
|
99
100
|
signal,
|
|
@@ -104,6 +105,12 @@ export async function bashToolRun(
|
|
|
104
105
|
} = {},
|
|
105
106
|
) {
|
|
106
107
|
const startedAt = Date.now();
|
|
108
|
+
if (wake_on_completion !== undefined && (background !== true || typeof wake_on_completion !== "boolean")) {
|
|
109
|
+
return failed("Error: wake_on_completion requires background=true and a boolean value.", "process_job_invalid", startedAt);
|
|
110
|
+
}
|
|
111
|
+
if (background === true && !processJobsController) {
|
|
112
|
+
return failed("Error: Background process jobs are unavailable for this request.", "background_unsupported", startedAt);
|
|
113
|
+
}
|
|
107
114
|
if (typeof command !== "string") {
|
|
108
115
|
return failed("Error: Bash command must be a string.", "invalid_command", startedAt);
|
|
109
116
|
}
|
|
@@ -158,6 +165,7 @@ export async function bashToolRun(
|
|
|
158
165
|
prepared,
|
|
159
166
|
summary: `Bash command (${command.length} characters; content redacted)`,
|
|
160
167
|
description,
|
|
168
|
+
wakeOnCompletion: wake_on_completion,
|
|
161
169
|
timeoutMs: requestedTimeoutMs,
|
|
162
170
|
maxOutputChars: max_output_chars === undefined ? undefined : maxChars,
|
|
163
171
|
startedAt,
|