@cydm/pie 2.5.1 → 2.6.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/dist/builtin/extensions/ask-user/index.js +3 -3
- package/dist/builtin/extensions/computer-use/index.js +17 -2
- package/dist/builtin/extensions/computer-use/prebuilt/macos/arm64/pie-computer-use +0 -0
- package/dist/builtin/extensions/computer-use/prebuilt/macos/universal/pie-computer-use.app/Contents/MacOS/pie-computer-use +0 -0
- package/dist/builtin/extensions/computer-use/prebuilt/macos/x64/pie-computer-use +0 -0
- package/dist/builtin/extensions/computer-use/scripts/setup-helper.mjs +90 -9
- package/dist/builtin/extensions/mcp-adapter/index.js +135 -27
- package/dist/builtin/extensions/plan-mode/index.js +3 -3
- package/dist/builtin/extensions/subagent/index.js +8 -5
- package/dist/builtin/extensions/todo/index.js +3 -3
- package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-bridge.js +199 -40
- package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-cli.js +5 -2
- package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-runtime.js +1 -1
- package/dist/builtin/skills/chrome-devtools-axi/{cli-P4VG6ZZG.js → cli-GB37NZHQ.js} +78 -29
- package/dist/builtin/skills/tui-use/SKILL.md +44 -0
- package/dist/builtin/skills/tui-use/tui-use-cli.cjs +40 -16
- package/dist/builtin/skills/tui-use/tui-use-daemon.cjs +7 -1
- package/dist/chunks/{chunk-REP6QANY.js → chunk-AZ336HKF.js} +405 -70
- package/dist/chunks/{chunk-N2MK46AE.js → chunk-G6KCJA6B.js} +7 -2
- package/dist/chunks/{chunk-YRXXRLMQ.js → chunk-O2DKMLNX.js} +152 -8
- package/dist/chunks/{chunk-ZKHMNEXL.js → chunk-Y4CYXJR7.js} +27 -2
- package/dist/chunks/src-VCWCE7YV.js +20 -0
- package/dist/chunks/{test-stream-OD3B57AS.js → test-stream-GOSEWMJ3.js} +1 -1
- package/dist/cli.js +67 -17
- package/package.json +2 -2
- package/dist/chunks/src-SXUQ7OY6.js +0 -14
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
|
-
Agent
|
|
4
|
-
|
|
3
|
+
Agent,
|
|
4
|
+
getToolCallerIdentity
|
|
5
|
+
} from "./chunk-Y4CYXJR7.js";
|
|
5
6
|
import {
|
|
6
7
|
FileSystemGateway,
|
|
7
8
|
completeSimple,
|
|
@@ -10,7 +11,7 @@ import {
|
|
|
10
11
|
getPlatformConfig,
|
|
11
12
|
hashText,
|
|
12
13
|
streamSimple
|
|
13
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-O2DKMLNX.js";
|
|
14
15
|
import {
|
|
15
16
|
Type
|
|
16
17
|
} from "./chunk-XXGYUYXI.js";
|
|
@@ -19,22 +20,31 @@ import {
|
|
|
19
20
|
__toESM
|
|
20
21
|
} from "./chunk-LZQXQOPJ.js";
|
|
21
22
|
|
|
22
|
-
// ../../
|
|
23
|
+
// ../../node_modules/shell-quote/quote.js
|
|
23
24
|
var require_quote = __commonJS({
|
|
24
|
-
"../../
|
|
25
|
+
"../../node_modules/shell-quote/quote.js"(exports, module) {
|
|
25
26
|
"use strict";
|
|
26
27
|
var OPS = (
|
|
27
28
|
/** @type {const} */
|
|
28
29
|
[
|
|
29
30
|
"||",
|
|
30
31
|
"&&",
|
|
32
|
+
";;&",
|
|
31
33
|
";;",
|
|
34
|
+
";&",
|
|
32
35
|
"|&",
|
|
33
36
|
"<(",
|
|
37
|
+
">(",
|
|
34
38
|
"<<<",
|
|
39
|
+
"<<-",
|
|
40
|
+
"<<",
|
|
35
41
|
">>",
|
|
36
42
|
">&",
|
|
43
|
+
">|",
|
|
44
|
+
"&>>",
|
|
45
|
+
"&>",
|
|
37
46
|
"<&",
|
|
47
|
+
"<>",
|
|
38
48
|
"&",
|
|
39
49
|
";",
|
|
40
50
|
"(",
|
|
@@ -45,9 +55,13 @@ var require_quote = __commonJS({
|
|
|
45
55
|
]
|
|
46
56
|
);
|
|
47
57
|
var LINE_TERMINATORS = /[\n\r\u2028\u2029]/;
|
|
48
|
-
var GLOB_SHELL_SPECIAL = /[\s#!"$&'()
|
|
58
|
+
var GLOB_SHELL_SPECIAL = /[\s#!"$&'():;<=>@\\^`|~]/g;
|
|
49
59
|
module.exports = function quote(xs) {
|
|
60
|
+
var sawComment = false;
|
|
50
61
|
return xs.map(function(s) {
|
|
62
|
+
if (sawComment && typeof s === "string" && LINE_TERMINATORS.test(s)) {
|
|
63
|
+
throw new TypeError("a token after a `comment` must not contain line terminators");
|
|
64
|
+
}
|
|
51
65
|
if (s === "") {
|
|
52
66
|
return (
|
|
53
67
|
/** @type {const} */
|
|
@@ -62,6 +76,12 @@ var require_quote = __commonJS({
|
|
|
62
76
|
if (LINE_TERMINATORS.test(s.pattern)) {
|
|
63
77
|
throw new TypeError("glob `pattern` must not contain line terminators");
|
|
64
78
|
}
|
|
79
|
+
if (s.pattern === "") {
|
|
80
|
+
return (
|
|
81
|
+
/** @type {const} */
|
|
82
|
+
"''"
|
|
83
|
+
);
|
|
84
|
+
}
|
|
65
85
|
return s.pattern.replace(GLOB_SHELL_SPECIAL, "\\$&");
|
|
66
86
|
}
|
|
67
87
|
if ("op" in s && typeof s.op === "string") {
|
|
@@ -74,15 +94,19 @@ var require_quote = __commonJS({
|
|
|
74
94
|
if (LINE_TERMINATORS.test(s.comment)) {
|
|
75
95
|
throw new TypeError("`comment` must not contain line terminators");
|
|
76
96
|
}
|
|
97
|
+
sawComment = true;
|
|
77
98
|
return "#" + s.comment;
|
|
78
99
|
}
|
|
79
100
|
throw new TypeError("unrecognized object token shape");
|
|
80
101
|
}
|
|
102
|
+
if (/'/.test(s) && /!/.test(s)) {
|
|
103
|
+
return "'" + s.replace(/'/g, `'"'"'`) + "'";
|
|
104
|
+
}
|
|
81
105
|
if (/["\s\\]/.test(s) && !/'/.test(s)) {
|
|
82
|
-
return "'" + s
|
|
106
|
+
return "'" + s + "'";
|
|
83
107
|
}
|
|
84
108
|
if (/["'\s]/.test(s)) {
|
|
85
|
-
return '"' + s.replace(/(["
|
|
109
|
+
return '"' + s.replace(/(["\\$`])/g, "\\$1") + '"';
|
|
86
110
|
}
|
|
87
111
|
return String(s).replace(/([A-Za-z]:)?([#!"$&'()*,:;<=>?@[\\\]^`{|}~])/g, "$1\\$2");
|
|
88
112
|
}).join(" ");
|
|
@@ -90,23 +114,31 @@ var require_quote = __commonJS({
|
|
|
90
114
|
}
|
|
91
115
|
});
|
|
92
116
|
|
|
93
|
-
// ../../
|
|
117
|
+
// ../../node_modules/shell-quote/parse.js
|
|
94
118
|
var require_parse = __commonJS({
|
|
95
|
-
"../../
|
|
119
|
+
"../../node_modules/shell-quote/parse.js"(exports, module) {
|
|
96
120
|
"use strict";
|
|
97
121
|
var CONTROL = (
|
|
98
122
|
/** @type {const} */
|
|
99
123
|
"(?:" + /** @type {const} */
|
|
100
124
|
[
|
|
101
125
|
"\\|\\|",
|
|
102
|
-
"
|
|
103
|
-
|
|
104
|
-
"
|
|
126
|
+
"\\&(?:\\&|>>?)",
|
|
127
|
+
// `&&`, `&>`, and `&>>`
|
|
128
|
+
";;\\&?",
|
|
129
|
+
// `;;` and `;;&`
|
|
130
|
+
"[;|]\\&",
|
|
131
|
+
// `;&` and `|&`
|
|
105
132
|
"\\<\\(",
|
|
106
133
|
"\\<\\<\\<",
|
|
134
|
+
"\\<\\<-",
|
|
135
|
+
"\\<\\<(?!\\()",
|
|
136
|
+
// `<<(` stays `<` and `<(`, as zsh reads it; other shells reject it
|
|
107
137
|
">>",
|
|
108
|
-
"
|
|
109
|
-
|
|
138
|
+
">[&|(]",
|
|
139
|
+
// `>&`, `>|`, and `>(`
|
|
140
|
+
"<[&>]",
|
|
141
|
+
// `<&` and `<>`
|
|
110
142
|
"[&;()|<>]"
|
|
111
143
|
].join(
|
|
112
144
|
/** @type {const} */
|
|
@@ -123,10 +155,11 @@ var require_parse = __commonJS({
|
|
|
123
155
|
/** @type {const} */
|
|
124
156
|
"'([^']*?)'"
|
|
125
157
|
);
|
|
126
|
-
var
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
158
|
+
var ANSI_C_BODY = "(?:\\\\[\\s\\S]|[^\\\\'])*?";
|
|
159
|
+
var ANSI_C_QUOTE = "\\$'" + ANSI_C_BODY + "'";
|
|
160
|
+
var ansiCAt = new RegExp("\\$'" + ANSI_C_BODY + "(?:(')|\\\\?$)", "g");
|
|
161
|
+
var ANSI_C_LETTERS = "abeEfnrtv";
|
|
162
|
+
var ANSI_C_CHARS = "\x07\b\x1B\x1B\f\n\r \v";
|
|
130
163
|
var hash = /^#$/;
|
|
131
164
|
var SQ = (
|
|
132
165
|
/** @type {const} */
|
|
@@ -175,17 +208,46 @@ var require_parse = __commonJS({
|
|
|
175
208
|
}
|
|
176
209
|
return pre + r;
|
|
177
210
|
}
|
|
211
|
+
var ansiCEscape = /\\([0-7]{1,3}|x[\dA-Fa-f]{1,2}|u[\dA-Fa-f]{1,4}|U[\dA-Fa-f]{1,8}|c(?:\\\\|[\s\S])|[abeEfnrtv\\'"?])/g;
|
|
212
|
+
function expandAnsiCEscape(m, escape2) {
|
|
213
|
+
var kind = escape2.charAt(0);
|
|
214
|
+
if (kind === "c") {
|
|
215
|
+
var ctrl = escape2.charAt(1);
|
|
216
|
+
return ctrl === "?" ? "\x7F" : String.fromCharCode(ctrl.charCodeAt(0) & 31);
|
|
217
|
+
}
|
|
218
|
+
if (kind === "x" || kind === "u" || kind === "U") {
|
|
219
|
+
var cp = parseInt(escape2.slice(1), 16);
|
|
220
|
+
if (cp > 1114111) {
|
|
221
|
+
return m;
|
|
222
|
+
}
|
|
223
|
+
return String.fromCharCode.apply(null, cp > 65535 ? [55232 + (cp >> 10), 56320 + (cp & 1023)] : [cp]);
|
|
224
|
+
}
|
|
225
|
+
if (kind >= "0" && kind <= "7") {
|
|
226
|
+
return String.fromCharCode(parseInt(escape2, 8) & 255);
|
|
227
|
+
}
|
|
228
|
+
var letter = ANSI_C_LETTERS.indexOf(escape2);
|
|
229
|
+
return letter < 0 ? escape2 : ANSI_C_CHARS.charAt(letter);
|
|
230
|
+
}
|
|
231
|
+
function expandAnsiC(body) {
|
|
232
|
+
return body.replace(ansiCEscape, expandAnsiCEscape).split("\0")[0];
|
|
233
|
+
}
|
|
234
|
+
function closesAnsiC(s, i2) {
|
|
235
|
+
ansiCAt.lastIndex = i2;
|
|
236
|
+
return !!/** @type {RegExpExecArray} */
|
|
237
|
+
ansiCAt.exec(s)[1];
|
|
238
|
+
}
|
|
178
239
|
function parseInternal(string, env, opts) {
|
|
179
240
|
if (!opts) {
|
|
180
241
|
opts = {};
|
|
181
242
|
}
|
|
182
243
|
var BS = opts.escape || "\\";
|
|
183
244
|
var ifs = opts.splitUnquoted === true ? " \n" : typeof opts.splitUnquoted === "string" ? opts.splitUnquoted : "";
|
|
184
|
-
var BAREWORD = "(\\" + BS + `['"
|
|
245
|
+
var BAREWORD = "(\\" + BS + `['"$\\` + BS + META + "]|\\$\\$|\\$(?!" + ANSI_C_QUOTE.slice(2) + `)|[^\\s'"$` + META + "])+";
|
|
246
|
+
var DOUBLE_QUOTE = '"(?:\\' + BS + '[\\s\\S]|[^"\\' + BS + '])*"';
|
|
185
247
|
var chunker = new RegExp([
|
|
186
248
|
"(" + CONTROL + ")",
|
|
187
249
|
// control chars
|
|
188
|
-
"(" + BAREWORD + "|" + DOUBLE_QUOTE + "|" + SINGLE_QUOTE + ")+"
|
|
250
|
+
"(" + ANSI_C_QUOTE + "|" + BAREWORD + "|" + DOUBLE_QUOTE + "|" + SINGLE_QUOTE + ")+"
|
|
189
251
|
].join("|"), "g");
|
|
190
252
|
var matches = matchAll(string, chunker);
|
|
191
253
|
if (matches.length === 0) {
|
|
@@ -240,9 +302,8 @@ var require_parse = __commonJS({
|
|
|
240
302
|
varend -= 1;
|
|
241
303
|
varname = s.slice(i2, varend);
|
|
242
304
|
i2 = varend;
|
|
243
|
-
} else if (/[
|
|
305
|
+
} else if (/[*@#?$!-]/.test(char)) {
|
|
244
306
|
varname = char;
|
|
245
|
-
i2 += 1;
|
|
246
307
|
} else {
|
|
247
308
|
var slicedFromI = s.slice(i2);
|
|
248
309
|
varend = slicedFromI.match(/[^\w\d_]/);
|
|
@@ -332,6 +393,11 @@ var require_parse = __commonJS({
|
|
|
332
393
|
);
|
|
333
394
|
} else if (c === BS) {
|
|
334
395
|
esc = true;
|
|
396
|
+
} else if (c === DS && s.charAt(i2 + 1) === SQ && closesAnsiC(s, i2)) {
|
|
397
|
+
flushRun();
|
|
398
|
+
sawQuote = true;
|
|
399
|
+
out += expandAnsiC(s.slice(i2 + 2, ansiCAt.lastIndex - 1));
|
|
400
|
+
i2 = ansiCAt.lastIndex - 1;
|
|
335
401
|
} else if (c === DS) {
|
|
336
402
|
var value = parseEnvVar();
|
|
337
403
|
if (!ifs) {
|
|
@@ -415,9 +481,9 @@ var require_parse = __commonJS({
|
|
|
415
481
|
}
|
|
416
482
|
});
|
|
417
483
|
|
|
418
|
-
// ../../
|
|
484
|
+
// ../../node_modules/shell-quote/index.js
|
|
419
485
|
var require_shell_quote = __commonJS({
|
|
420
|
-
"../../
|
|
486
|
+
"../../node_modules/shell-quote/index.js"(exports) {
|
|
421
487
|
"use strict";
|
|
422
488
|
exports.quote = require_quote();
|
|
423
489
|
exports.parse = require_parse();
|
|
@@ -1392,10 +1458,14 @@ Subagent guarded mode uses the same bash risk policy as the parent agent. Destru
|
|
|
1392
1458
|
}
|
|
1393
1459
|
};
|
|
1394
1460
|
}
|
|
1395
|
-
function filterToolsForSubagent(runtimeTools, parentTools, plan) {
|
|
1461
|
+
function filterToolsForSubagent(runtimeTools, parentTools, plan, mode) {
|
|
1396
1462
|
const parentNames = new Set(parentTools.map((tool) => tool.name));
|
|
1397
1463
|
const filtered = [];
|
|
1398
1464
|
for (const tool of runtimeTools) {
|
|
1465
|
+
if (tool.name === "spawn_subagents_parallel") {
|
|
1466
|
+
plan.toolsExcluded.push({ name: tool.name, reason: "recursive delegation is not allowed" });
|
|
1467
|
+
continue;
|
|
1468
|
+
}
|
|
1399
1469
|
if (!parentNames.has(tool.name)) {
|
|
1400
1470
|
plan.toolsExcluded.push({ name: tool.name, reason: "not available in parent tool set" });
|
|
1401
1471
|
continue;
|
|
@@ -1404,6 +1474,10 @@ function filterToolsForSubagent(runtimeTools, parentTools, plan) {
|
|
|
1404
1474
|
filtered.push(wrapBashGuard(tool, plan));
|
|
1405
1475
|
continue;
|
|
1406
1476
|
}
|
|
1477
|
+
if (mode === "edit" && (tool.name === "write_file" || tool.name === "edit_file")) {
|
|
1478
|
+
filtered.push(tool);
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1407
1481
|
const policy = getSubagentToolPolicyDecision(tool);
|
|
1408
1482
|
if (!policy.allowed) {
|
|
1409
1483
|
plan.toolsExcluded.push({ name: tool.name, reason: policy.reason || "not allowed for subagent" });
|
|
@@ -1413,7 +1487,7 @@ function filterToolsForSubagent(runtimeTools, parentTools, plan) {
|
|
|
1413
1487
|
}
|
|
1414
1488
|
return filtered;
|
|
1415
1489
|
}
|
|
1416
|
-
function buildSubagentPlan(deps, task, name, priority, timeoutSeconds, modelOverride, thinkingLevel = DEFAULT_SUBAGENT_THINKING_LEVEL) {
|
|
1490
|
+
function buildSubagentPlan(deps, task, name, priority, timeoutSeconds, modelOverride, thinkingLevel = DEFAULT_SUBAGENT_THINKING_LEVEL, mode = "read") {
|
|
1417
1491
|
const complexity = inferComplexity(task, priority);
|
|
1418
1492
|
const modelClass = selectModelClass(complexity, priority);
|
|
1419
1493
|
const overrideResolution = modelOverride ? deps.resolveModelRef?.(modelOverride) ?? { model: void 0, fallbackReason: `model override "${modelOverride}" is not supported by this host` } : void 0;
|
|
@@ -1428,6 +1502,7 @@ function buildSubagentPlan(deps, task, name, priority, timeoutSeconds, modelOver
|
|
|
1428
1502
|
task,
|
|
1429
1503
|
name,
|
|
1430
1504
|
priority,
|
|
1505
|
+
mode,
|
|
1431
1506
|
complexity,
|
|
1432
1507
|
modelClass,
|
|
1433
1508
|
modelOverride,
|
|
@@ -1439,8 +1514,11 @@ function buildSubagentPlan(deps, task, name, priority, timeoutSeconds, modelOver
|
|
|
1439
1514
|
timeoutSeconds,
|
|
1440
1515
|
outputContract: "Return a concise answer with evidence, uncertainty, and any parent follow-up needed."
|
|
1441
1516
|
};
|
|
1442
|
-
const runtimeTools = deps.createRuntimeTools();
|
|
1443
|
-
const filteredTools = filterToolsForSubagent(runtimeTools, deps.getParentTools(), partialPlan);
|
|
1517
|
+
const runtimeTools = deps.createRuntimeTools(mode);
|
|
1518
|
+
const filteredTools = filterToolsForSubagent(runtimeTools, deps.getParentTools(), partialPlan, mode);
|
|
1519
|
+
if (mode === "edit" && !filteredTools.some((tool) => tool.name === "write_file" || tool.name === "edit_file")) {
|
|
1520
|
+
throw new Error("Edit delegation requires executable editing tools from the parent's current tool set; it is unavailable in read-only/plan mode.");
|
|
1521
|
+
}
|
|
1444
1522
|
const plan = {
|
|
1445
1523
|
...partialPlan,
|
|
1446
1524
|
toolsAllowed: filteredTools.map((tool) => tool.name)
|
|
@@ -1502,7 +1580,7 @@ function inferUncertainty(text, signals) {
|
|
|
1502
1580
|
return "medium";
|
|
1503
1581
|
}
|
|
1504
1582
|
function isParentHandoffRequired(task) {
|
|
1505
|
-
return task.needsParentWrite || task.status === "timeout" || task.status === "failed" || task.confidenceSignals.some((signal) => signal.type === "tool_error" || signal.type === "no_tool_use");
|
|
1583
|
+
return task.mode === "edit" || task.needsParentWrite || task.status === "timeout" || task.status === "failed" || task.confidenceSignals.some((signal) => signal.type === "tool_error" || signal.type === "no_tool_use");
|
|
1506
1584
|
}
|
|
1507
1585
|
async function runSubagentWithProgress(deps, task, reportProgress, signal) {
|
|
1508
1586
|
const startTime = Date.now();
|
|
@@ -1511,9 +1589,9 @@ async function runSubagentWithProgress(deps, task, reportProgress, signal) {
|
|
|
1511
1589
|
deps.debugLog?.("run", "runSubagentWithProgress START", { taskName: task.name, yoloMode: deps.yoloMode });
|
|
1512
1590
|
try {
|
|
1513
1591
|
reportProgress({ message: `[${task.name}] Starting...`, increment: 5 });
|
|
1514
|
-
const { plan, tools, apiKey, model } = buildSubagentPlan(deps, task.task, task.name, task.priority, task.timeoutSeconds, task.modelOverride, task.thinkingLevel);
|
|
1592
|
+
const { plan, tools, apiKey, model } = buildSubagentPlan(deps, task.task, task.name, task.priority, task.timeoutSeconds, task.modelOverride, task.thinkingLevel, task.mode);
|
|
1515
1593
|
task.plan = plan;
|
|
1516
|
-
task.needsParentWrite = needsParentWrite(task.task);
|
|
1594
|
+
task.needsParentWrite = task.mode === "read" && needsParentWrite(task.task);
|
|
1517
1595
|
for (const excluded of plan.toolsExcluded) {
|
|
1518
1596
|
if (excluded.reason === "unclassified_tool") {
|
|
1519
1597
|
task.confidenceSignals.push({ type: "unclassified_tool", message: `${excluded.name} inherited without risk metadata` });
|
|
@@ -1538,7 +1616,7 @@ async function runSubagentWithProgress(deps, task, reportProgress, signal) {
|
|
|
1538
1616
|
Task boundary:
|
|
1539
1617
|
- Work only on the assigned task.
|
|
1540
1618
|
- Use available tools to gather evidence before making claims.
|
|
1541
|
-
- Do not edit files; if writing is needed, say what the parent agent should change.
|
|
1619
|
+
${task.mode === "edit" ? "- You may edit files and run tests using the inherited parent tools and permissions. This is a shared workspace, not an isolated sandbox. Preserve existing user changes. Do not commit or automatically roll back changes. Report changed files, tests actually run, and remaining work for parent review." : "- Do not edit files; if writing is needed, say what the parent agent should change."}
|
|
1542
1620
|
- Return a concise answer with evidence and uncertainty.
|
|
1543
1621
|
|
|
1544
1622
|
Output:
|
|
@@ -1600,8 +1678,8 @@ ${formatSkillsForPrompt(deps.skills ?? [])}`;
|
|
|
1600
1678
|
}
|
|
1601
1679
|
});
|
|
1602
1680
|
let prompt = task.task;
|
|
1603
|
-
if (task.
|
|
1604
|
-
prompt += "\n\nThis
|
|
1681
|
+
if (task.mode === "read") {
|
|
1682
|
+
prompt += "\n\nThis delegation is read mode. Do not write files. If changes are needed, describe them for the parent.";
|
|
1605
1683
|
}
|
|
1606
1684
|
prompt += "\n\nReturn evidence for your conclusion and state uncertainty as low, medium, or high.";
|
|
1607
1685
|
reportProgress({ message: `[${task.name}] Running plan ${plan.complexity}/${plan.modelClass}...`, increment: 20 });
|
|
@@ -1669,7 +1747,7 @@ function buildParallelResult(tasks, totalDuration) {
|
|
|
1669
1747
|
const statusLabel = task.status === "completed" ? "completed" : task.status;
|
|
1670
1748
|
resultText += `**${task.name}** (${statusLabel}`;
|
|
1671
1749
|
if (task.plan) {
|
|
1672
|
-
resultText += `, ${task.plan.complexity}/${task.plan.modelClass}`;
|
|
1750
|
+
resultText += `, mode=${task.mode}, ${task.plan.complexity}/${task.plan.modelClass}`;
|
|
1673
1751
|
if (task.plan.thinkingLevel !== DEFAULT_SUBAGENT_THINKING_LEVEL) {
|
|
1674
1752
|
resultText += `, thinking=${task.plan.thinkingLevel}`;
|
|
1675
1753
|
}
|
|
@@ -1693,7 +1771,9 @@ function buildParallelResult(tasks, totalDuration) {
|
|
|
1693
1771
|
resultText += `Evidence: ${task.evidence.slice(0, 5).map((entry) => `${entry.type}:${entry.value}`).join("; ")}
|
|
1694
1772
|
`;
|
|
1695
1773
|
}
|
|
1696
|
-
if (task.
|
|
1774
|
+
if (task.mode === "edit") {
|
|
1775
|
+
resultText += "Parent follow-up: review changes and validate reported tests. Partial edits are preserved on failure; no automatic rollback or task retry.\n";
|
|
1776
|
+
} else if (task.needsParentWrite) {
|
|
1697
1777
|
resultText += "Parent follow-up: writing appears to be needed; parent agent must make any edits.\n";
|
|
1698
1778
|
} else if (task.parentHandoffRequired) {
|
|
1699
1779
|
resultText += "Parent follow-up: subagent result needs parent review or continuation.\n";
|
|
@@ -1715,7 +1795,7 @@ function normalizeTaskDefs(args) {
|
|
|
1715
1795
|
return { error: "Invalid spawn_subagents_parallel arguments: tasks must be a non-empty array." };
|
|
1716
1796
|
}
|
|
1717
1797
|
const normalized = [];
|
|
1718
|
-
const allowedFields = [...TASK_FIELD_ALIASES, "name", "priority", "timeout", "model", "thinkingLevel"];
|
|
1798
|
+
const allowedFields = [...TASK_FIELD_ALIASES, "name", "priority", "timeout", "model", "thinkingLevel", "mode"];
|
|
1719
1799
|
for (const [index, def] of taskDefs.entries()) {
|
|
1720
1800
|
if (!def || typeof def !== "object") {
|
|
1721
1801
|
return { error: `Invalid task #${index + 1}: expected an object with the objective in "task" (aliases "description"/"prompt"/"objective" accepted).` };
|
|
@@ -1737,6 +1817,9 @@ function normalizeTaskDefs(args) {
|
|
|
1737
1817
|
if (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0) {
|
|
1738
1818
|
return { error: `Invalid task #${index + 1}: timeout must be a positive number of seconds.` };
|
|
1739
1819
|
}
|
|
1820
|
+
if (def.mode !== void 0 && def.mode !== "read" && def.mode !== "edit") {
|
|
1821
|
+
return { error: `Invalid task #${index + 1}: "mode" must be "read" or "edit".` };
|
|
1822
|
+
}
|
|
1740
1823
|
const priority = def.priority === "thorough" ? "thorough" : "fast";
|
|
1741
1824
|
const modelOverride = typeof def.model === "string" ? def.model.trim() : void 0;
|
|
1742
1825
|
if (def.model !== void 0 && !modelOverride) {
|
|
@@ -1749,11 +1832,15 @@ function normalizeTaskDefs(args) {
|
|
|
1749
1832
|
name: typeof def.name === "string" && def.name.trim() ? def.name.trim() : void 0,
|
|
1750
1833
|
task,
|
|
1751
1834
|
priority,
|
|
1835
|
+
mode: def.mode ?? "read",
|
|
1752
1836
|
timeoutSeconds,
|
|
1753
1837
|
modelOverride,
|
|
1754
1838
|
thinkingLevel: def.thinkingLevel
|
|
1755
1839
|
});
|
|
1756
1840
|
}
|
|
1841
|
+
if (normalized.length !== 1 && normalized.some((task) => task.mode === "edit")) {
|
|
1842
|
+
return { error: "An edit delegation must contain exactly one task. Wait for it to finish before editing or launching another edit task in the shared workspace." };
|
|
1843
|
+
}
|
|
1757
1844
|
return normalized;
|
|
1758
1845
|
}
|
|
1759
1846
|
var DEFAULT_MAX_CONCURRENT_SUBAGENTS = 4;
|
|
@@ -1792,10 +1879,10 @@ function createSubagentCapability(deps) {
|
|
|
1792
1879
|
label: "Spawn Subagents",
|
|
1793
1880
|
description: `Spawn child agents to run bounded sub-tasks in parallel.
|
|
1794
1881
|
|
|
1795
|
-
|
|
1882
|
+
Default mode="read" is for investigation, search, review, or triage and cannot edit source files. Explicit mode="edit" permits file editing and tests with inherited parent permissions. An edit call must contain exactly one task; wait for it to finish before editing or launching another edit task. The workspace is shared, not isolated. Edit tasks are not automatically retried; the parent must review changes and test results. Provide each task as a plain objective. Pie chooses the model class automatically. Use timeout only for unusually long tasks.
|
|
1796
1883
|
|
|
1797
1884
|
Arguments shape (each tasks[] item puts the objective in "task" \u2014 "description"/"prompt"/"objective" are accepted as aliases):
|
|
1798
|
-
{ "tasks": [{ "task": "<objective text>", "name": "<optional short label>", "priority": "fast" | "thorough", "model": "<optional explicit model override>", "thinkingLevel": "<optional off|minimal|low|medium|high|xhigh, default medium>", "timeout": 600 }] }
|
|
1885
|
+
{ "tasks": [{ "task": "<objective text>", "mode": "read" | "edit", "name": "<optional short label>", "priority": "fast" | "thorough", "model": "<optional explicit model override>", "thinkingLevel": "<optional off|minimal|low|medium|high|xhigh, default medium>", "timeout": 600 }] }
|
|
1799
1886
|
|
|
1800
1887
|
"model" overrides the automatic model-class selection for that task when the user explicitly asks for a specific model (e.g. "anthropic/claude-..."). Prefer "provider/model-id"; a bare model id works only when it is unambiguous across providers. When the override cannot be resolved, the task falls back to automatic selection and reports why.
|
|
1801
1888
|
|
|
@@ -1809,6 +1896,7 @@ Arguments shape (each tasks[] item puts the objective in "task" \u2014 "descript
|
|
|
1809
1896
|
prompt: Type.Optional(Type.String({ description: "Alias for task." })),
|
|
1810
1897
|
objective: Type.Optional(Type.String({ description: "Alias for task." })),
|
|
1811
1898
|
name: Type.Optional(Type.String({ description: "Short name for this subagent, shown in progress updates." })),
|
|
1899
|
+
mode: Type.Optional(Type.Union([Type.Literal("read"), Type.Literal("edit")], { description: "Default read. Edit permits inherited file editing and tests in the shared workspace; exactly one task per edit call, parent review required." })),
|
|
1812
1900
|
priority: Type.Optional(Type.Union([Type.Literal("fast"), Type.Literal("thorough")], { description: "fast uses lower-cost defaults; thorough raises model class when needed." })),
|
|
1813
1901
|
model: Type.Optional(Type.String({ description: 'Explicit model override for this task, e.g. "provider/model-id". Only set when the user explicitly requests a specific model; otherwise Pie picks the model class automatically.' })),
|
|
1814
1902
|
thinkingLevel: Type.Optional(Type.Union([Type.Literal("off"), Type.Literal("minimal"), Type.Literal("low"), Type.Literal("medium"), Type.Literal("high"), Type.Literal("xhigh")], { description: `Reasoning level for this task. Default: "medium". Only override when the user asks (e.g. "xhigh" for deep review tasks, "off" for cheap lookups). Ignored by non-reasoning models.` })),
|
|
@@ -1846,6 +1934,7 @@ Arguments shape (each tasks[] item puts the objective in "task" \u2014 "descript
|
|
|
1846
1934
|
status: "pending",
|
|
1847
1935
|
task: def.task,
|
|
1848
1936
|
priority: def.priority,
|
|
1937
|
+
mode: def.mode,
|
|
1849
1938
|
timeoutSeconds: def.timeoutSeconds,
|
|
1850
1939
|
modelOverride: def.modelOverride,
|
|
1851
1940
|
thinkingLevel: def.thinkingLevel,
|
|
@@ -1864,7 +1953,7 @@ Arguments shape (each tasks[] item puts the objective in "task" \u2014 "descript
|
|
|
1864
1953
|
const runTask = async (task) => {
|
|
1865
1954
|
for (let attempt = 0; ; attempt++) {
|
|
1866
1955
|
await runSubagentWithProgress(deps, task, toolContext.reportProgress, signal);
|
|
1867
|
-
const canRetry = task.status === "failed" && isTransientNetworkError(task.error ?? "") && attempt < MAX_TRANSIENT_RETRIES && !signal?.aborted;
|
|
1956
|
+
const canRetry = task.mode === "read" && task.status === "failed" && isTransientNetworkError(task.error ?? "") && attempt < MAX_TRANSIENT_RETRIES && !signal?.aborted;
|
|
1868
1957
|
if (!canRetry) return;
|
|
1869
1958
|
const failureReason = task.error ?? "unknown";
|
|
1870
1959
|
task.evidence.length = 0;
|
|
@@ -1896,6 +1985,7 @@ Arguments shape (each tasks[] item puts the objective in "task" \u2014 "descript
|
|
|
1896
1985
|
id: task.id,
|
|
1897
1986
|
name: task.name,
|
|
1898
1987
|
status: task.status,
|
|
1988
|
+
mode: task.mode,
|
|
1899
1989
|
task: task.task,
|
|
1900
1990
|
timeoutSeconds: task.timeoutSeconds,
|
|
1901
1991
|
plan: task.plan,
|
|
@@ -5244,6 +5334,16 @@ var AgentSessionQueue = class {
|
|
|
5244
5334
|
}
|
|
5245
5335
|
this.emitQueueChanged();
|
|
5246
5336
|
}
|
|
5337
|
+
/** Promote only an input still owned by this queue, never re-send agent-owned inputs. */
|
|
5338
|
+
promoteLatestFollowUp() {
|
|
5339
|
+
const input = this.queuedInputs.slice().reverse().find((queued) => queued.visibility === "user" && queued.mode === "followUp");
|
|
5340
|
+
if (!input || input.deliveredToAgent) return void 0;
|
|
5341
|
+
input.mode = "steer";
|
|
5342
|
+
input.deliverWhileStreaming = true;
|
|
5343
|
+
if (this.getIsStreaming()) this.deliverToAgent(input);
|
|
5344
|
+
this.emitQueueChanged();
|
|
5345
|
+
return cloneQueuedInput(input);
|
|
5346
|
+
}
|
|
5247
5347
|
deliverToAgent(queuedInput) {
|
|
5248
5348
|
if (queuedInput.deliveredToAgent) {
|
|
5249
5349
|
return;
|
|
@@ -6404,6 +6504,12 @@ var AgentSessionController = class {
|
|
|
6404
6504
|
drainQueuedInputsForEditor() {
|
|
6405
6505
|
return this.queue.drainForEditor();
|
|
6406
6506
|
}
|
|
6507
|
+
/** Upgrade the latest visible follow-up that has not been delivered to the agent. */
|
|
6508
|
+
promoteLatestFollowUpToSteer() {
|
|
6509
|
+
const promoted = this.disposed ? void 0 : this.queue.promoteLatestFollowUp();
|
|
6510
|
+
if (promoted && !this.isStreaming) this.requestQueueDispatch();
|
|
6511
|
+
return promoted;
|
|
6512
|
+
}
|
|
6407
6513
|
replaceQueuedInputText(search, replacement) {
|
|
6408
6514
|
this.queue.replaceText(search, replacement);
|
|
6409
6515
|
}
|
|
@@ -8775,7 +8881,7 @@ function createReadTool(cwd, options) {
|
|
|
8775
8881
|
return {
|
|
8776
8882
|
name: "read",
|
|
8777
8883
|
label: "read",
|
|
8778
|
-
description: `Read a file's contents. Use this before editing, and use offset/limit for large files.
|
|
8884
|
+
description: `Read a file's contents. Use this before editing, and use offset/limit for large files. Images are attached for the current model when it supports vision; otherwise a configured file-understanding service analyzes them. Video, audio, and documents require a configured understanding service. Pass question to specify what to focus on; it does not force a separate model request. Example: read_file path='Scripts/PlayerController.cs' or read_file path='Data/config.json'. ${pathResolutionDescription} For text files, output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). When you need the full file, continue with a larger offset until complete.`,
|
|
8779
8885
|
parameters: readSchema,
|
|
8780
8886
|
execute: async (_toolCallId, params, signal) => {
|
|
8781
8887
|
const { path: path4, root, offset, limit, question } = params;
|
|
@@ -8848,7 +8954,7 @@ function createReadTool(cwd, options) {
|
|
|
8848
8954
|
});
|
|
8849
8955
|
return;
|
|
8850
8956
|
}
|
|
8851
|
-
if (magicInfo?.kind === "image" &&
|
|
8957
|
+
if (magicInfo?.kind === "image" && options?.understandFile && (!INLINE_IMAGE_MIME_TYPES.has(magicInfo.mimeType) || stat.size > MAX_INLINE_IMAGE_BYTES)) {
|
|
8852
8958
|
resolve3(await options.understandFile({
|
|
8853
8959
|
absolutePath,
|
|
8854
8960
|
displayPath: path4,
|
|
@@ -8864,7 +8970,7 @@ function createReadTool(cwd, options) {
|
|
|
8864
8970
|
}
|
|
8865
8971
|
const imageInfo = options?.allowBinary === false || magicInfo?.kind !== "image" || !INLINE_IMAGE_MIME_TYPES.has(magicInfo.mimeType) ? void 0 : await readImageInfo(absolutePath, fs2);
|
|
8866
8972
|
if (imageInfo) {
|
|
8867
|
-
if (
|
|
8973
|
+
if (options?.understandFile) {
|
|
8868
8974
|
resolve3(await options.understandFile({
|
|
8869
8975
|
absolutePath,
|
|
8870
8976
|
displayPath: path4,
|
|
@@ -8879,7 +8985,8 @@ function createReadTool(cwd, options) {
|
|
|
8879
8985
|
const dimensionText = imageInfo.width && imageInfo.height ? `, ${imageInfo.width}x${imageInfo.height}` : "";
|
|
8880
8986
|
resolve3({
|
|
8881
8987
|
content: [
|
|
8882
|
-
{ type: "text", text: `Read image file [${imageInfo.mimeType}${dimensionText}]. Original image attached as an image block; for very large images, prefer a smaller crop or resized copy before rereading
|
|
8988
|
+
{ type: "text", text: `Read image file [${imageInfo.mimeType}${dimensionText}]. Original image attached as an image block; for very large images, prefer a smaller crop or resized copy before rereading.${question?.trim() ? `
|
|
8989
|
+
Focus: ${question.trim()}` : ""}` },
|
|
8883
8990
|
{ type: "image", data: imageInfo.base64, mimeType: imageInfo.mimeType }
|
|
8884
8991
|
],
|
|
8885
8992
|
details: {
|
|
@@ -8998,14 +9105,15 @@ function createReadTool(cwd, options) {
|
|
|
8998
9105
|
const readReachedEndOfFile = startLine + range2.selectedLines >= range2.totalLines;
|
|
8999
9106
|
const isCompleteTextKnown = readStartsAtBeginning && readReachedEndOfFile && !truncation.truncated && !truncation.firstLineExceedsLimit;
|
|
9000
9107
|
let callerObservation = fileObservationState;
|
|
9001
|
-
|
|
9108
|
+
const callerKey = getToolCallerIdentity(signal) ?? signal;
|
|
9109
|
+
if (callerKey) {
|
|
9002
9110
|
const generation = fileObservationState.getGeneration();
|
|
9003
|
-
const existing = callerObservations.get(
|
|
9111
|
+
const existing = callerObservations.get(callerKey);
|
|
9004
9112
|
if (existing?.generation === generation) {
|
|
9005
9113
|
callerObservation = existing.state;
|
|
9006
9114
|
} else {
|
|
9007
9115
|
callerObservation = createFileObservationState();
|
|
9008
|
-
callerObservations.set(
|
|
9116
|
+
callerObservations.set(callerKey, { generation, state: callerObservation });
|
|
9009
9117
|
}
|
|
9010
9118
|
}
|
|
9011
9119
|
if (fileObservationState.isUnchangedReadRange(absolutePath, rangeKey, snapshot, range2.selectedContentHash) && callerObservation.isUnchangedReadRange(absolutePath, rangeKey, snapshot, range2.selectedContentHash)) {
|
|
@@ -13451,6 +13559,44 @@ function createReadFileUnderstandingHandler(deps) {
|
|
|
13451
13559
|
async function understandFileWithModel(deps, request) {
|
|
13452
13560
|
const purpose = `read_file.${request.kind}`;
|
|
13453
13561
|
const agentModel = deps.getAgentModel();
|
|
13562
|
+
if (request.kind === "image" && request.imageInfo && agentModel?.input.includes("image")) {
|
|
13563
|
+
if (request.signal?.aborted) throw new Error("Operation aborted");
|
|
13564
|
+
return {
|
|
13565
|
+
content: [
|
|
13566
|
+
{ type: "text", text: `Read image file ${request.displayPath} [${request.mimeType}]. Original image attached for the current model.${request.question?.trim() ? `
|
|
13567
|
+
Focus: ${request.question.trim()}` : ""}` },
|
|
13568
|
+
{ type: "image", data: request.imageInfo.base64, mimeType: request.imageInfo.mimeType }
|
|
13569
|
+
],
|
|
13570
|
+
details: {
|
|
13571
|
+
image: { mimeType: request.imageInfo.mimeType, width: request.imageInfo.width, height: request.imageInfo.height },
|
|
13572
|
+
understanding: { purpose, kind: request.kind, mimeType: request.mimeType, agentModel: getModelLabel(agentModel), routeSource: "current_model", inputMode: "nativeAttachment" }
|
|
13573
|
+
},
|
|
13574
|
+
isError: false
|
|
13575
|
+
};
|
|
13576
|
+
}
|
|
13577
|
+
if ((request.kind === "video" || request.kind === "audio") && agentModel?.api === "openai-completions" && agentModel.input.includes(request.kind)) {
|
|
13578
|
+
const capability = agentModel.fileCapabilities?.[request.kind];
|
|
13579
|
+
const audioSupported = request.kind !== "audio" || ["audio/wav", "audio/mpeg", "audio/mp3"].includes(request.mimeType);
|
|
13580
|
+
if (capability && typeof capability === "object" && capability.supported !== false && capability.nativeInline === true && audioSupported && (!capability.formats || capability.formats.includes(request.mimeType)) && capability.maxDurationSeconds === void 0) {
|
|
13581
|
+
const fs2 = getFileSystem();
|
|
13582
|
+
const size = (await fs2.stat(request.absolutePath)).size;
|
|
13583
|
+
const cap = Math.min(capability.maxFileSizeMb ?? 15, 15) * 1024 * 1024;
|
|
13584
|
+
if (size > 0 && size <= cap) {
|
|
13585
|
+
if (request.signal?.aborted) throw new Error("Operation aborted");
|
|
13586
|
+
const data = await fs2.readFile(request.absolutePath, "base64");
|
|
13587
|
+
if (request.signal?.aborted) throw new Error("Operation aborted");
|
|
13588
|
+
if (data) return {
|
|
13589
|
+
content: [
|
|
13590
|
+
{ type: "text", text: `Read ${request.kind} file ${request.displayPath} [${request.mimeType}]. Original media attached for the current model.${request.question?.trim() ? `
|
|
13591
|
+
Focus: ${request.question.trim()}` : ""}` },
|
|
13592
|
+
{ type: request.kind, data, mimeType: request.mimeType }
|
|
13593
|
+
],
|
|
13594
|
+
details: { understanding: { purpose, kind: request.kind, mimeType: request.mimeType, agentModel: getModelLabel(agentModel), routeSource: "current_model", inputMode: "nativeAttachment", attachmentBytes: size } },
|
|
13595
|
+
isError: false
|
|
13596
|
+
};
|
|
13597
|
+
}
|
|
13598
|
+
}
|
|
13599
|
+
}
|
|
13454
13600
|
const singleDecision = deps.resolveToolModel(purpose);
|
|
13455
13601
|
const candidateDecisions = deps.resolveToolModelCandidates?.(purpose) ?? [singleDecision];
|
|
13456
13602
|
const sessionId = deps.getSessionId() || `read-file-${Date.now()}`;
|
|
@@ -14570,19 +14716,121 @@ var webFetchSchema = Type.Object({
|
|
|
14570
14716
|
var DEFAULT_TIMEOUT_MS = 45e3;
|
|
14571
14717
|
var DEFAULT_MAX_BYTES2 = 2e6;
|
|
14572
14718
|
var DEFAULT_MAX_CHARS = 1e5;
|
|
14719
|
+
var DEFAULT_MAX_REDIRECTS = 5;
|
|
14720
|
+
var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
14573
14721
|
function modelLabel2(model) {
|
|
14574
14722
|
return model ? `${model.provider}/${model.id}` : void 0;
|
|
14575
14723
|
}
|
|
14724
|
+
function parseIpv4Part(part) {
|
|
14725
|
+
if (part === "") return void 0;
|
|
14726
|
+
const lowered = part.toLowerCase();
|
|
14727
|
+
if (lowered.startsWith("0x")) {
|
|
14728
|
+
const digits = lowered.slice(2);
|
|
14729
|
+
return /^[0-9a-f]+$/.test(digits) ? parseInt(digits, 16) : void 0;
|
|
14730
|
+
}
|
|
14731
|
+
if (lowered.length > 1 && lowered.startsWith("0")) {
|
|
14732
|
+
return /^[0-7]+$/.test(lowered) ? parseInt(lowered, 8) : void 0;
|
|
14733
|
+
}
|
|
14734
|
+
if (!/^\d+$/.test(lowered)) return void 0;
|
|
14735
|
+
return parseInt(lowered, 10);
|
|
14736
|
+
}
|
|
14737
|
+
function parseIpv4Literal(host) {
|
|
14738
|
+
const parts = host.split(".");
|
|
14739
|
+
if (parts.length === 0 || parts.length > 4) return void 0;
|
|
14740
|
+
const numbers = [];
|
|
14741
|
+
for (const part of parts) {
|
|
14742
|
+
const value = parseIpv4Part(part);
|
|
14743
|
+
if (value === void 0) return void 0;
|
|
14744
|
+
numbers.push(value);
|
|
14745
|
+
}
|
|
14746
|
+
const count = numbers.length;
|
|
14747
|
+
const last = numbers[count - 1];
|
|
14748
|
+
if (last > 256 ** (5 - count) - 1) return void 0;
|
|
14749
|
+
for (let i = 0; i < count - 1; i += 1) {
|
|
14750
|
+
if (numbers[i] > 255) return void 0;
|
|
14751
|
+
}
|
|
14752
|
+
let address = 0;
|
|
14753
|
+
for (let i = 0; i < count - 1; i += 1) address = address * 256 + numbers[i];
|
|
14754
|
+
address = address * 256 ** (5 - count) + last;
|
|
14755
|
+
return [address >>> 24 & 255, address >>> 16 & 255, address >>> 8 & 255, address & 255];
|
|
14756
|
+
}
|
|
14757
|
+
function isPrivateIpv4(octets) {
|
|
14758
|
+
const [a = 0, b = 0] = octets;
|
|
14759
|
+
if (a === 0) return true;
|
|
14760
|
+
if (a === 10) return true;
|
|
14761
|
+
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
14762
|
+
if (a === 127) return true;
|
|
14763
|
+
if (a === 169 && b === 254) return true;
|
|
14764
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
14765
|
+
if (a === 192 && b === 0 && octets[2] === 0) return true;
|
|
14766
|
+
if (a === 192 && b === 0 && octets[2] === 2) return true;
|
|
14767
|
+
if (a === 192 && b === 88 && octets[2] === 99) return true;
|
|
14768
|
+
if (a === 192 && b === 168) return true;
|
|
14769
|
+
if (a === 198 && (b === 18 || b === 19)) return true;
|
|
14770
|
+
if (a === 198 && b === 51 && octets[2] === 100) return true;
|
|
14771
|
+
if (a === 203 && b === 0 && octets[2] === 113) return true;
|
|
14772
|
+
if (a >= 224) return true;
|
|
14773
|
+
return false;
|
|
14774
|
+
}
|
|
14775
|
+
function parseIpv6Literal(hostname) {
|
|
14776
|
+
let host = hostname.toLowerCase();
|
|
14777
|
+
if (host.startsWith("[")) host = host.slice(1, host.endsWith("]") ? -1 : void 0);
|
|
14778
|
+
const zoneIndex = host.indexOf("%");
|
|
14779
|
+
if (zoneIndex >= 0) host = host.slice(0, zoneIndex);
|
|
14780
|
+
if (!host.includes(":")) return void 0;
|
|
14781
|
+
const halves = host.split("::");
|
|
14782
|
+
if (halves.length > 2) return void 0;
|
|
14783
|
+
const parseTokens = (tokens) => {
|
|
14784
|
+
const groups = [];
|
|
14785
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
14786
|
+
const token = tokens[i];
|
|
14787
|
+
if (token === "") return void 0;
|
|
14788
|
+
if (i === tokens.length - 1 && token.includes(".")) {
|
|
14789
|
+
const v4 = parseIpv4Literal(token);
|
|
14790
|
+
if (!v4) return void 0;
|
|
14791
|
+
groups.push(v4[0] << 8 | v4[1], v4[2] << 8 | v4[3]);
|
|
14792
|
+
continue;
|
|
14793
|
+
}
|
|
14794
|
+
if (!/^[0-9a-f]{1,4}$/.test(token)) return void 0;
|
|
14795
|
+
groups.push(parseInt(token, 16));
|
|
14796
|
+
}
|
|
14797
|
+
return groups;
|
|
14798
|
+
};
|
|
14799
|
+
const head = halves[0] === "" ? [] : parseTokens(halves[0].split(":"));
|
|
14800
|
+
if (head === void 0) return void 0;
|
|
14801
|
+
const tail = halves.length === 2 ? halves[1] === "" ? [] : parseTokens(halves[1].split(":")) : [];
|
|
14802
|
+
if (tail === void 0) return void 0;
|
|
14803
|
+
if (halves.length === 1) return head.length === 8 ? head : void 0;
|
|
14804
|
+
const missing = 8 - head.length - tail.length;
|
|
14805
|
+
if (missing < 1) return void 0;
|
|
14806
|
+
return [...head, ...new Array(missing).fill(0), ...tail];
|
|
14807
|
+
}
|
|
14808
|
+
function isPrivateIpv6(groups) {
|
|
14809
|
+
const first = groups[0];
|
|
14810
|
+
if (groups.every((group) => group === 0)) return true;
|
|
14811
|
+
if (groups.slice(0, 7).every((group) => group === 0) && groups[7] === 1) return true;
|
|
14812
|
+
if ((first & 65024) === 64512) return true;
|
|
14813
|
+
if ((first & 65472) === 65152) return true;
|
|
14814
|
+
const embeddedV4 = [groups[6] >> 8, groups[6] & 255, groups[7] >> 8, groups[7] & 255];
|
|
14815
|
+
if (groups.slice(0, 5).every((group) => group === 0) && groups[5] === 65535) {
|
|
14816
|
+
return isPrivateIpv4(embeddedV4);
|
|
14817
|
+
}
|
|
14818
|
+
if (groups.slice(0, 6).every((group) => group === 0)) {
|
|
14819
|
+
return isPrivateIpv4(embeddedV4);
|
|
14820
|
+
}
|
|
14821
|
+
if (first === 100 && groups[1] === 65435 && groups.slice(2, 6).every((group) => group === 0)) {
|
|
14822
|
+
return isPrivateIpv4(embeddedV4);
|
|
14823
|
+
}
|
|
14824
|
+
return false;
|
|
14825
|
+
}
|
|
14576
14826
|
function isPrivateHostname(hostname) {
|
|
14577
|
-
const host = hostname.toLowerCase();
|
|
14827
|
+
const host = hostname.toLowerCase().trim().replace(/\.+$/, "");
|
|
14578
14828
|
if (host === "localhost" || host.endsWith(".localhost")) return true;
|
|
14579
|
-
if (host === "
|
|
14580
|
-
|
|
14581
|
-
if (
|
|
14582
|
-
|
|
14583
|
-
if (
|
|
14584
|
-
if (host === "::1" || host === "[::1]") return true;
|
|
14585
|
-
if (host === "169.254.169.254") return true;
|
|
14829
|
+
if (host === "local" || host.endsWith(".local")) return true;
|
|
14830
|
+
const v4 = parseIpv4Literal(host);
|
|
14831
|
+
if (v4) return isPrivateIpv4(v4);
|
|
14832
|
+
const v6 = parseIpv6Literal(host);
|
|
14833
|
+
if (v6) return isPrivateIpv6(v6);
|
|
14586
14834
|
return false;
|
|
14587
14835
|
}
|
|
14588
14836
|
function charsetFromContentType(contentType) {
|
|
@@ -14721,6 +14969,24 @@ async function cancelResponseBody(response) {
|
|
|
14721
14969
|
} catch {
|
|
14722
14970
|
}
|
|
14723
14971
|
}
|
|
14972
|
+
var DISCARD_BODY_GRACE_MS = 250;
|
|
14973
|
+
async function discardResponseBody(response) {
|
|
14974
|
+
await new Promise((resolve3) => {
|
|
14975
|
+
let settled = false;
|
|
14976
|
+
const settle = () => {
|
|
14977
|
+
if (settled) return;
|
|
14978
|
+
settled = true;
|
|
14979
|
+
clearTimeout(timer);
|
|
14980
|
+
resolve3();
|
|
14981
|
+
};
|
|
14982
|
+
const timer = setTimeout(settle, DISCARD_BODY_GRACE_MS);
|
|
14983
|
+
try {
|
|
14984
|
+
void cancelResponseBody(response).then(settle, settle);
|
|
14985
|
+
} catch {
|
|
14986
|
+
settle();
|
|
14987
|
+
}
|
|
14988
|
+
});
|
|
14989
|
+
}
|
|
14724
14990
|
var BODY_TRUNCATED_ERROR_CODE = "body_truncated";
|
|
14725
14991
|
function isBodyTruncationError(error) {
|
|
14726
14992
|
return error instanceof Error && error.code === BODY_TRUNCATED_ERROR_CODE;
|
|
@@ -14857,7 +15123,8 @@ function createSharedWebFetchTool(deps = {}) {
|
|
|
14857
15123
|
isError: true
|
|
14858
15124
|
};
|
|
14859
15125
|
}
|
|
14860
|
-
|
|
15126
|
+
const allowPrivate = deps.allowPrivateHosts?.() ?? false;
|
|
15127
|
+
if (!allowPrivate && isPrivateHostname(parsed.hostname)) {
|
|
14861
15128
|
return {
|
|
14862
15129
|
content: [{ type: "text", text: `Blocked private or local URL: ${url}` }],
|
|
14863
15130
|
details: { url, sourceUrl: url, durationMs: Date.now() - start, failureCategory: "blocked_private_host" },
|
|
@@ -14866,22 +15133,91 @@ function createSharedWebFetchTool(deps = {}) {
|
|
|
14866
15133
|
}
|
|
14867
15134
|
const timeout = createTimeoutSignal(signal, deps.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
14868
15135
|
try {
|
|
14869
|
-
const
|
|
15136
|
+
const requestHeaders = {
|
|
15137
|
+
"user-agent": "Pie web_fetch/1.0",
|
|
15138
|
+
accept: "text/html,application/xhtml+xml,text/plain,application/pdf,*/*;q=0.8"
|
|
15139
|
+
};
|
|
15140
|
+
const doFetch = (target) => (deps.fetch ?? fetch)(target, {
|
|
14870
15141
|
method: "GET",
|
|
14871
|
-
|
|
15142
|
+
// Redirects are followed manually in the loop below so that every
|
|
15143
|
+
// hop is validated (scheme + host) BEFORE its request is issued.
|
|
15144
|
+
// A transport that ignores this and follows internally is caught
|
|
15145
|
+
// post-hoc via the response.url guard below.
|
|
15146
|
+
redirect: "manual",
|
|
14872
15147
|
signal: timeout.signal,
|
|
14873
|
-
headers:
|
|
14874
|
-
"user-agent": "Pie web_fetch/1.0",
|
|
14875
|
-
accept: "text/html,application/xhtml+xml,text/plain,application/pdf,*/*;q=0.8"
|
|
14876
|
-
}
|
|
15148
|
+
headers: requestHeaders
|
|
14877
15149
|
});
|
|
15150
|
+
const redirectChain = [url];
|
|
15151
|
+
const maxRedirects = deps.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
|
|
15152
|
+
let currentUrl = url;
|
|
15153
|
+
let response = await doFetch(currentUrl);
|
|
15154
|
+
let redirectsFollowed = 0;
|
|
15155
|
+
while (REDIRECT_STATUSES.has(response.status)) {
|
|
15156
|
+
const location = response.headers.get("location")?.trim();
|
|
15157
|
+
if (!location) break;
|
|
15158
|
+
let next;
|
|
15159
|
+
try {
|
|
15160
|
+
next = new URL(location, currentUrl);
|
|
15161
|
+
} catch {
|
|
15162
|
+
await discardResponseBody(response);
|
|
15163
|
+
return {
|
|
15164
|
+
content: [{ type: "text", text: `web_fetch redirect target is not a valid URL: ${location}` }],
|
|
15165
|
+
details: { url, sourceUrl: url, finalUrl: currentUrl, redirectChain, durationMs: Date.now() - start, failureCategory: "invalid_url" },
|
|
15166
|
+
isError: true
|
|
15167
|
+
};
|
|
15168
|
+
}
|
|
15169
|
+
if (next.protocol !== "http:" && next.protocol !== "https:") {
|
|
15170
|
+
await discardResponseBody(response);
|
|
15171
|
+
return {
|
|
15172
|
+
content: [{ type: "text", text: `web_fetch only follows http(s) redirects: ${next.toString()} (from ${currentUrl})` }],
|
|
15173
|
+
details: { url, sourceUrl: url, finalUrl: next.toString(), redirectChain: [...redirectChain, next.toString()], durationMs: Date.now() - start, failureCategory: "invalid_url" },
|
|
15174
|
+
isError: true
|
|
15175
|
+
};
|
|
15176
|
+
}
|
|
15177
|
+
if (!allowPrivate && isPrivateHostname(next.hostname)) {
|
|
15178
|
+
await discardResponseBody(response);
|
|
15179
|
+
return {
|
|
15180
|
+
content: [{ type: "text", text: `Blocked redirect to private or local URL: ${next.toString()} (while fetching ${url})` }],
|
|
15181
|
+
details: { url, sourceUrl: url, finalUrl: next.toString(), redirectChain: [...redirectChain, next.toString()], durationMs: Date.now() - start, failureCategory: "blocked_private_host" },
|
|
15182
|
+
isError: true
|
|
15183
|
+
};
|
|
15184
|
+
}
|
|
15185
|
+
redirectsFollowed += 1;
|
|
15186
|
+
if (redirectsFollowed > maxRedirects) {
|
|
15187
|
+
await discardResponseBody(response);
|
|
15188
|
+
return {
|
|
15189
|
+
content: [{ type: "text", text: `web_fetch stopped after ${maxRedirects} redirects while fetching ${url} (last hop: ${currentUrl} -> ${location})` }],
|
|
15190
|
+
details: { url, sourceUrl: url, finalUrl: currentUrl, redirectChain, durationMs: Date.now() - start, failureCategory: "too_many_redirects" },
|
|
15191
|
+
isError: true
|
|
15192
|
+
};
|
|
15193
|
+
}
|
|
15194
|
+
await discardResponseBody(response);
|
|
15195
|
+
currentUrl = next.toString();
|
|
15196
|
+
redirectChain.push(currentUrl);
|
|
15197
|
+
response = await doFetch(currentUrl);
|
|
15198
|
+
}
|
|
15199
|
+
const finalUrl = response.url || currentUrl;
|
|
15200
|
+
let transportFinalHostname;
|
|
15201
|
+
try {
|
|
15202
|
+
transportFinalHostname = new URL(finalUrl).hostname;
|
|
15203
|
+
} catch {
|
|
15204
|
+
transportFinalHostname = void 0;
|
|
15205
|
+
}
|
|
15206
|
+
if (!allowPrivate && transportFinalHostname && isPrivateHostname(transportFinalHostname)) {
|
|
15207
|
+
await discardResponseBody(response);
|
|
15208
|
+
return {
|
|
15209
|
+
content: [{ type: "text", text: `Blocked: the fetch transport followed a redirect to private or local URL ${finalUrl} (while fetching ${url})` }],
|
|
15210
|
+
details: { url, sourceUrl: url, finalUrl, redirectChain, durationMs: Date.now() - start, failureCategory: "blocked_private_host" },
|
|
15211
|
+
isError: true
|
|
15212
|
+
};
|
|
15213
|
+
}
|
|
14878
15214
|
const contentType = response.headers.get("content-type") || "";
|
|
14879
15215
|
const maxBytes = deps.maxBytes ?? DEFAULT_MAX_BYTES2;
|
|
14880
15216
|
const bodyResult = await readResponseBody(response, maxBytes);
|
|
14881
15217
|
if (bodyResult.exceededLimit) {
|
|
14882
15218
|
return {
|
|
14883
15219
|
content: [{ type: "text", text: `Fetched content is too large (${bodyResult.observedBytes} bytes; max ${maxBytes}).` }],
|
|
14884
|
-
details: { url, sourceUrl: url, finalUrl
|
|
15220
|
+
details: { url, sourceUrl: url, finalUrl, status: response.status, contentType, bytes: bodyResult.observedBytes, truncated: true, truncationReason: "max_bytes", durationMs: Date.now() - start, failureCategory: "too_large" },
|
|
14885
15221
|
isError: true
|
|
14886
15222
|
};
|
|
14887
15223
|
}
|
|
@@ -14889,7 +15225,7 @@ function createSharedWebFetchTool(deps = {}) {
|
|
|
14889
15225
|
if (!response.ok) {
|
|
14890
15226
|
return {
|
|
14891
15227
|
content: [{ type: "text", text: `web_fetch failed with HTTP ${response.status} for ${url}` }],
|
|
14892
|
-
details: { url, sourceUrl: url, finalUrl
|
|
15228
|
+
details: { url, sourceUrl: url, finalUrl, status: response.status, contentType, bytes: bodyResult.observedBytes, redirectChain, durationMs: Date.now() - start, failureCategory: "http_error" },
|
|
14893
15229
|
isError: true
|
|
14894
15230
|
};
|
|
14895
15231
|
}
|
|
@@ -14907,7 +15243,7 @@ function createSharedWebFetchTool(deps = {}) {
|
|
|
14907
15243
|
details: {
|
|
14908
15244
|
url,
|
|
14909
15245
|
sourceUrl: url,
|
|
14910
|
-
finalUrl
|
|
15246
|
+
finalUrl,
|
|
14911
15247
|
status: response.status,
|
|
14912
15248
|
contentType,
|
|
14913
15249
|
charset: decoded.charset,
|
|
@@ -14926,15 +15262,14 @@ function createSharedWebFetchTool(deps = {}) {
|
|
|
14926
15262
|
if (!text) {
|
|
14927
15263
|
return {
|
|
14928
15264
|
content: [{ type: "text", text: `web_fetch could not extract readable text from ${url}` }],
|
|
14929
|
-
details: { url, sourceUrl: url, finalUrl
|
|
15265
|
+
details: { url, sourceUrl: url, finalUrl, status: response.status, contentType, charset: decoded.charset, decodeWarning: decoded.decodeWarning, bytes: bodyResult.observedBytes, redirectChain, extractionMethod, durationMs: Date.now() - start, failureCategory: "unsupported_content" },
|
|
14930
15266
|
isError: true
|
|
14931
15267
|
};
|
|
14932
15268
|
}
|
|
14933
15269
|
const maxChars = deps.maxChars ?? DEFAULT_MAX_CHARS;
|
|
14934
15270
|
const truncated = text.length > maxChars;
|
|
14935
15271
|
if (truncated) text = text.slice(0, maxChars) + "\n\n[web_fetch content truncated]";
|
|
14936
|
-
const extraction = prompt?.trim() ? await applyPromptWithModel(deps, prompt.trim(),
|
|
14937
|
-
const finalUrl = response.url || url;
|
|
15272
|
+
const extraction = prompt?.trim() ? await applyPromptWithModel(deps, prompt.trim(), finalUrl, text, timeout.signal) : { text, model: modelLabel2(deps.getModel?.()) };
|
|
14938
15273
|
return {
|
|
14939
15274
|
content: [{ type: "text", text: extraction.text }],
|
|
14940
15275
|
details: {
|
|
@@ -14948,7 +15283,7 @@ function createSharedWebFetchTool(deps = {}) {
|
|
|
14948
15283
|
bytes: bodyResult.observedBytes,
|
|
14949
15284
|
title,
|
|
14950
15285
|
truncated,
|
|
14951
|
-
redirectChain
|
|
15286
|
+
redirectChain,
|
|
14952
15287
|
extractionMethod: prompt?.trim() ? "model_extraction" : extractionMethod,
|
|
14953
15288
|
readabilityScore: htmlExtraction?.readabilityScore,
|
|
14954
15289
|
citationAnchors: buildCitationAnchors(finalUrl, title, text),
|