@mono-agent/agent-runtime 0.20.11 → 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 +288 -26
- package/README.md +352 -477
- package/package.json +13 -44
- package/src/agent/tool-bloat.js +145 -9
- package/src/agent/tools/agent-tool.js +108 -9
- package/src/agent/tools/bash.js +11 -26
- package/src/agent/tools/codex-subscription-search.js +123 -29
- package/src/agent/tools/exec.js +10 -2
- package/src/agent/tools/index.js +7 -0
- package/src/agent/tools/monitor.js +149 -0
- package/src/agent/tools/pi-bridge.js +123 -19
- package/src/agent/tools/shared/bash-environment.js +31 -0
- package/src/agent/tools/shared/monitors.js +293 -0
- 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 +26 -6
- 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/cost.js +13 -68
- package/src/ai/failure.js +3 -3
- package/src/ai/index.js +5 -17
- package/src/ai/observer.js +8 -0
- package/src/ai/pi-interop.js +221 -1
- package/src/ai/pi-oauth-compat.js +1 -1
- package/src/ai/provider-check.js +131 -0
- package/src/ai/providers/codex/app-server-client.js +592 -0
- package/src/ai/providers/pi-models.js +18 -10
- package/src/ai/providers/pi-native/compaction-driver.js +94 -42
- package/src/ai/providers/pi-native/compaction-summary.js +140 -0
- package/src/ai/providers/pi-native/harness-adapter.js +376 -0
- 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 +38 -14
- package/src/ai/providers/pi-native/session-lifecycle.js +253 -55
- package/src/ai/providers/pi-native/stream-subscriber.js +52 -6
- package/src/ai/providers/pi-native/terminal-recovery.js +40 -0
- package/src/ai/providers/pi-native/turn-runner.js +279 -28
- package/src/ai/providers/pi-native.js +206 -61
- package/src/ai/runtime/capabilities.js +11 -56
- package/src/ai/runtime/live-input-events.js +250 -54
- package/src/ai/runtime/model-refs.js +118 -153
- package/src/ai/runtime/registry.js +22 -56
- package/src/ai/runtime/router.js +76 -417
- package/src/ai/runtime/session-liveness.js +3 -4
- package/src/ai/runtime/sessions.js +4 -5
- package/src/ai/runtime/tool-policy.js +0 -2
- package/src/ai/tool-lifecycle.js +32 -18
- package/src/ai/types.js +37 -112
- package/src/index.js +0 -6
- package/src/runtime.js +29 -16
- package/types/agent/tool-bloat.d.ts +1 -1
- package/types/agent/tools/agent-tool.d.ts +4 -2
- package/types/agent/tools/bash.d.ts +5 -3
- package/types/agent/tools/codex-subscription-search.d.ts +7 -3
- package/types/agent/tools/exec.d.ts +5 -3
- package/types/agent/tools/index.d.ts +1 -0
- package/types/agent/tools/monitor.d.ts +47 -0
- package/types/agent/tools/pi-bridge.d.ts +7 -4
- package/types/agent/tools/shared/bash-environment.d.ts +4 -0
- package/types/agent/tools/shared/monitors.d.ts +98 -0
- package/types/agent/tools/shared/process-jobs.d.ts +5 -1
- package/types/agent/tools/shared/process-runner.d.ts +14 -4
- 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/cost.d.ts +1 -2
- package/types/ai/index.d.ts +2 -4
- package/types/ai/observer.d.ts +6 -0
- package/types/ai/pi-interop.d.ts +81 -0
- package/types/ai/provider-check.d.ts +53 -0
- package/types/ai/providers/codex/app-server-client.d.ts +37 -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 +58 -0
- 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 +14 -4
- package/types/ai/providers/pi-native/session-lifecycle.d.ts +25 -6
- package/types/ai/providers/pi-native/stream-subscriber.d.ts +2 -2
- package/types/ai/providers/pi-native/terminal-recovery.d.ts +2 -0
- package/types/ai/providers/pi-native/turn-runner.d.ts +68 -10
- package/types/ai/providers/pi-native.d.ts +21 -4
- package/types/ai/runtime/capabilities.d.ts +21 -70
- package/types/ai/runtime/live-input-events.d.ts +32 -8
- package/types/ai/runtime/model-refs.d.ts +0 -24
- package/types/ai/runtime/router.d.ts +3 -10
- package/types/ai/runtime/tool-policy.d.ts +0 -2
- package/types/ai/tool-lifecycle.d.ts +4 -3
- package/types/ai/types.d.ts +162 -256
- package/types/index.d.ts +0 -1
- package/src/ai/providers/acp-client.js +0 -1149
- package/src/ai/providers/acp-privacy.js +0 -124
- package/src/ai/providers/acp-public.js +0 -21
- package/src/ai/providers/acp-session-tokens.js +0 -282
- package/src/ai/providers/acp-transport.js +0 -356
- package/src/ai/providers/acp.js +0 -543
- package/src/ai/providers/claude-cli.js +0 -883
- package/src/ai/providers/claude-sandbox.js +0 -71
- package/src/ai/providers/claude-sdk-discovery-worker.js +0 -53
- package/src/ai/providers/claude-sdk-discovery.js +0 -352
- package/src/ai/providers/claude-sdk.js +0 -1127
- package/src/ai/providers/claude-subagent-activity.js +0 -719
- package/src/ai/providers/claude-subagents.js +0 -88
- package/src/ai/providers/codex-app.js +0 -2946
- package/src/ai/providers/opencode-app.js +0 -1109
- package/src/ai/providers/opencode-discovery.js +0 -39
- package/src/ai/providers/opencode-server.js +0 -508
- package/src/ai/runtime/context-windows.js +0 -46
- package/src/ai/runtime/fast-mode.js +0 -8
- package/src/ai/streaming/codex-events.js +0 -146
- package/src/ai/streaming/opencode-events.js +0 -59
- package/types/ai/providers/acp-client.d.ts +0 -227
- package/types/ai/providers/acp-privacy.d.ts +0 -25
- package/types/ai/providers/acp-public.d.ts +0 -7
- package/types/ai/providers/acp-session-tokens.d.ts +0 -41
- package/types/ai/providers/acp-transport.d.ts +0 -45
- package/types/ai/providers/acp.d.ts +0 -93
- package/types/ai/providers/claude-cli.d.ts +0 -305
- package/types/ai/providers/claude-sandbox.d.ts +0 -79
- package/types/ai/providers/claude-sdk-discovery-worker.d.ts +0 -1
- package/types/ai/providers/claude-sdk-discovery.d.ts +0 -97
- package/types/ai/providers/claude-sdk.d.ts +0 -138
- package/types/ai/providers/claude-subagent-activity.d.ts +0 -53
- package/types/ai/providers/claude-subagents.d.ts +0 -18
- package/types/ai/providers/codex-app.d.ts +0 -151
- package/types/ai/providers/opencode-app.d.ts +0 -96
- package/types/ai/providers/opencode-discovery.d.ts +0 -4
- package/types/ai/providers/opencode-server.d.ts +0 -20
- package/types/ai/runtime/context-windows.d.ts +0 -9
- package/types/ai/runtime/fast-mode.d.ts +0 -2
- package/types/ai/streaming/codex-events.d.ts +0 -40
- package/types/ai/streaming/opencode-events.d.ts +0 -42
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mono-agent/agent-runtime",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Agent runtime
|
|
3
|
+
"version": "0.21.0",
|
|
4
|
+
"description": "Agent runtime backed by the Pi SDK provider catalog",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "GPL-3.0-only",
|
|
7
7
|
"repository": {
|
|
@@ -47,18 +47,6 @@
|
|
|
47
47
|
"types": "./types/ai/runtime/registry.d.ts",
|
|
48
48
|
"default": "./src/ai/runtime/registry.js"
|
|
49
49
|
},
|
|
50
|
-
"./ai/runtime/context-windows.js": {
|
|
51
|
-
"types": "./types/ai/runtime/context-windows.d.ts",
|
|
52
|
-
"default": "./src/ai/runtime/context-windows.js"
|
|
53
|
-
},
|
|
54
|
-
"./ai/runtime/fast-mode.js": {
|
|
55
|
-
"types": "./types/ai/runtime/fast-mode.d.ts",
|
|
56
|
-
"default": "./src/ai/runtime/fast-mode.js"
|
|
57
|
-
},
|
|
58
|
-
"./ai/streaming/codex-events.js": {
|
|
59
|
-
"types": "./types/ai/streaming/codex-events.d.ts",
|
|
60
|
-
"default": "./src/ai/streaming/codex-events.js"
|
|
61
|
-
},
|
|
62
50
|
"./ai/live-input-prompt.js": {
|
|
63
51
|
"types": "./types/ai/live-input-prompt.d.ts",
|
|
64
52
|
"default": "./src/ai/live-input-prompt.js"
|
|
@@ -67,29 +55,9 @@
|
|
|
67
55
|
"types": "./types/ai/file-change-stats.d.ts",
|
|
68
56
|
"default": "./src/ai/file-change-stats.js"
|
|
69
57
|
},
|
|
70
|
-
"./ai/providers/
|
|
71
|
-
"types": "./types/ai/providers/
|
|
72
|
-
"default": "./src/ai/providers/
|
|
73
|
-
},
|
|
74
|
-
"./ai/providers/claude-sdk-discovery.js": {
|
|
75
|
-
"types": "./types/ai/providers/claude-sdk-discovery.d.ts",
|
|
76
|
-
"default": "./src/ai/providers/claude-sdk-discovery.js"
|
|
77
|
-
},
|
|
78
|
-
"./ai/providers/claude-cli.js": {
|
|
79
|
-
"types": "./types/ai/providers/claude-cli.d.ts",
|
|
80
|
-
"default": "./src/ai/providers/claude-cli.js"
|
|
81
|
-
},
|
|
82
|
-
"./ai/providers/codex-app.js": {
|
|
83
|
-
"types": "./types/ai/providers/codex-app.d.ts",
|
|
84
|
-
"default": "./src/ai/providers/codex-app.js"
|
|
85
|
-
},
|
|
86
|
-
"./ai/providers/opencode-discovery.js": {
|
|
87
|
-
"types": "./types/ai/providers/opencode-discovery.d.ts",
|
|
88
|
-
"default": "./src/ai/providers/opencode-discovery.js"
|
|
89
|
-
},
|
|
90
|
-
"./ai/providers/acp.js": {
|
|
91
|
-
"types": "./types/ai/providers/acp.d.ts",
|
|
92
|
-
"default": "./src/ai/providers/acp.js"
|
|
58
|
+
"./ai/providers/codex/app-server-client.js": {
|
|
59
|
+
"types": "./types/ai/providers/codex/app-server-client.d.ts",
|
|
60
|
+
"default": "./src/ai/providers/codex/app-server-client.js"
|
|
93
61
|
},
|
|
94
62
|
"./agent/tools/index.js": {
|
|
95
63
|
"types": "./types/agent/tools/index.d.ts",
|
|
@@ -132,26 +100,27 @@
|
|
|
132
100
|
"LICENSE"
|
|
133
101
|
],
|
|
134
102
|
"engines": {
|
|
135
|
-
"node": ">=
|
|
103
|
+
"node": ">=24.15.0"
|
|
136
104
|
},
|
|
137
105
|
"dependencies": {
|
|
138
|
-
"@
|
|
139
|
-
"@
|
|
140
|
-
"@anthropic-ai/sdk": "^0.110.0",
|
|
141
|
-
"@earendil-works/pi-agent-core": "0.83.0",
|
|
142
|
-
"@earendil-works/pi-ai": "0.84.3",
|
|
106
|
+
"@earendil-works/pi-agent-core": "0.85.1",
|
|
107
|
+
"@earendil-works/pi-ai": "0.85.1",
|
|
143
108
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
144
109
|
"@mozilla/readability": "0.6.0",
|
|
145
|
-
"@opencode-ai/sdk": "^1.15.13",
|
|
146
110
|
"@vscode/ripgrep": "1.18.0",
|
|
147
111
|
"bmp-ts": "1.0.9",
|
|
148
112
|
"cross-spawn": "^7.0.6",
|
|
149
113
|
"defuddle": "0.19.2",
|
|
114
|
+
"fast-xml-parser": "5.7.3",
|
|
150
115
|
"linkedom": "0.18.13",
|
|
151
116
|
"sharp": "0.35.3",
|
|
117
|
+
"turndown": "7.2.4",
|
|
152
118
|
"unpdf": "1.8.0",
|
|
153
119
|
"zod": "^4.3.6"
|
|
154
120
|
},
|
|
121
|
+
"devDependencies": {
|
|
122
|
+
"@types/turndown": "5.0.6"
|
|
123
|
+
},
|
|
155
124
|
"scripts": {
|
|
156
125
|
"build": "tsc -p tsconfig.types.json",
|
|
157
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?: *,
|
|
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 = {}) {
|
|
@@ -291,9 +291,10 @@ export function createAgentTool(subagents, context = {}) {
|
|
|
291
291
|
label: "Agent",
|
|
292
292
|
description: toolDescription(subagents, definitions, ceiling),
|
|
293
293
|
parameters,
|
|
294
|
-
// MUST stay undefined.
|
|
295
|
-
//
|
|
296
|
-
//
|
|
294
|
+
// MUST stay undefined. Agent-only batches can overlap when the offered tool
|
|
295
|
+
// set contains no sequential tool. Pi 0.85 exposes only a global harness
|
|
296
|
+
// mode, however, so offering Bash/Write/MCP/etc. serializes Agent calls too;
|
|
297
|
+
// see the documented provider limitation rather than marking Agent itself.
|
|
297
298
|
executionMode: undefined,
|
|
298
299
|
/**
|
|
299
300
|
* @param {string} toolCallId
|
|
@@ -398,7 +399,6 @@ export function createAgentTool(subagents, context = {}) {
|
|
|
398
399
|
prompt: params.prompt,
|
|
399
400
|
definition: profile,
|
|
400
401
|
...(context.model === undefined ? {} : { model: context.model }),
|
|
401
|
-
...(context.executionMode === undefined ? {} : { executionMode: context.executionMode }),
|
|
402
402
|
...(context.cwd === undefined ? {} : { cwd: context.cwd }),
|
|
403
403
|
...(context.parentRunId === undefined ? {} : { parentRunId: context.parentRunId }),
|
|
404
404
|
// Inherited, never widened: a profile cannot loosen confinement.
|
|
@@ -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
|
@@ -14,22 +14,12 @@ import {
|
|
|
14
14
|
DEFAULT_PROCESS_BUFFER_BYTES,
|
|
15
15
|
runPreparedProcess,
|
|
16
16
|
} from "./shared/process-runner.js";
|
|
17
|
+
import { cleanBashEnvironment } from "./shared/bash-environment.js";
|
|
17
18
|
import { handOffProcessJob } from "./shared/process-jobs.js";
|
|
18
19
|
import { readToolRuntime } from "./shared/runtime-context.js";
|
|
19
20
|
import { requestToolProcessEnvironment, resolveSandboxPolicy } from "./shared/tool-context.js";
|
|
20
21
|
|
|
21
22
|
const DEFAULT_BASH_TIMEOUT_MS = 120_000;
|
|
22
|
-
const BASH_STARTUP_ENV_KEYS = new Set([
|
|
23
|
-
"BASHOPTS",
|
|
24
|
-
"BASH_COMPAT",
|
|
25
|
-
"BASH_XTRACEFD",
|
|
26
|
-
"CDPATH",
|
|
27
|
-
"GLOBIGNORE",
|
|
28
|
-
"POSIXLY_CORRECT",
|
|
29
|
-
"PROMPT_COMMAND",
|
|
30
|
-
"PS4",
|
|
31
|
-
"SHELLOPTS",
|
|
32
|
-
]);
|
|
33
23
|
|
|
34
24
|
/**
|
|
35
25
|
* Legacy Bash timeout normalization. Values up to 600 are seconds; larger
|
|
@@ -82,7 +72,7 @@ export function normalizeBackgroundBashTimeoutMs(value) {
|
|
|
82
72
|
/**
|
|
83
73
|
* Compatibility wrapper retained for direct callers and tests.
|
|
84
74
|
*
|
|
85
|
-
* @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
|
|
86
76
|
* @param {{signal?: AbortSignal, sandboxPolicy?: any, sandboxEngine?: any, ctx?: any, processJobsController?: import("./shared/process-jobs.js").ProcessJobsController}} [options]
|
|
87
77
|
*/
|
|
88
78
|
export async function bashToolImpl(params, options = {}) {
|
|
@@ -92,7 +82,7 @@ export async function bashToolImpl(params, options = {}) {
|
|
|
92
82
|
/**
|
|
93
83
|
* Structured Bash execution used by the Pi bridge.
|
|
94
84
|
*
|
|
95
|
-
* @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
|
|
96
86
|
* @param {{signal?: AbortSignal, sandboxPolicy?: any, sandboxEngine?: any, ctx?: any, processJobsController?: import("./shared/process-jobs.js").ProcessJobsController}} [options]
|
|
97
87
|
*/
|
|
98
88
|
export async function bashToolRun(
|
|
@@ -104,6 +94,7 @@ export async function bashToolRun(
|
|
|
104
94
|
max_output_chars,
|
|
105
95
|
workdir,
|
|
106
96
|
background,
|
|
97
|
+
wake_on_completion,
|
|
107
98
|
},
|
|
108
99
|
{
|
|
109
100
|
signal,
|
|
@@ -114,6 +105,12 @@ export async function bashToolRun(
|
|
|
114
105
|
} = {},
|
|
115
106
|
) {
|
|
116
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
|
+
}
|
|
117
114
|
if (typeof command !== "string") {
|
|
118
115
|
return failed("Error: Bash command must be a string.", "invalid_command", startedAt);
|
|
119
116
|
}
|
|
@@ -168,6 +165,7 @@ export async function bashToolRun(
|
|
|
168
165
|
prepared,
|
|
169
166
|
summary: `Bash command (${command.length} characters; content redacted)`,
|
|
170
167
|
description,
|
|
168
|
+
wakeOnCompletion: wake_on_completion,
|
|
171
169
|
timeoutMs: requestedTimeoutMs,
|
|
172
170
|
maxOutputChars: max_output_chars === undefined ? undefined : maxChars,
|
|
173
171
|
startedAt,
|
|
@@ -260,19 +258,6 @@ export async function bashToolRun(
|
|
|
260
258
|
return completed(partial, baseOutcome, maxChars, "Bash", resolvedCtx);
|
|
261
259
|
}
|
|
262
260
|
|
|
263
|
-
function cleanBashEnvironment() {
|
|
264
|
-
const env = {
|
|
265
|
-
BASH_ENV: "/dev/null",
|
|
266
|
-
ENV: "/dev/null",
|
|
267
|
-
};
|
|
268
|
-
for (const key of Object.keys(process.env)) {
|
|
269
|
-
if (BASH_STARTUP_ENV_KEYS.has(key) || key.startsWith("BASH_FUNC_")) {
|
|
270
|
-
env[key] = undefined;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
return env;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
261
|
function finitePositiveInteger(value, fallback) {
|
|
277
262
|
const number = Number(value);
|
|
278
263
|
return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback;
|