@cydm/pie 2.5.2 → 2.6.1
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 +10 -3
- package/dist/builtin/extensions/mcp-adapter/index.js +38 -16
- 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 +194 -40
- package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-runtime.js +1 -1
- package/dist/builtin/skills/chrome-devtools-axi/{cli-EJ5MQZNA.js → cli-GB37NZHQ.js} +2 -2
- package/dist/builtin/skills/tui-use/SKILL.md +44 -0
- package/dist/builtin/skills/tui-use/tui-use-cli.cjs +15 -7
- package/dist/chunks/{chunk-M4OJARWJ.js → chunk-3F5ZP2CV.js} +1 -1
- package/dist/chunks/{chunk-LO2V5DZZ.js → chunk-IKLQHWY2.js} +400 -73
- package/dist/chunks/{chunk-UD7KYQ6J.js → chunk-O2DKMLNX.js} +146 -11
- package/dist/chunks/{chunk-RTTGO2A6.js → chunk-Y4CYXJR7.js} +27 -2
- package/dist/chunks/src-VCWCE7YV.js +20 -0
- package/dist/chunks/{test-stream-LFR6GSKQ.js → test-stream-GOSEWMJ3.js} +1 -1
- package/dist/cli.js +26 -21
- package/package.json +2 -2
- package/dist/chunks/src-33WRCQWX.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,14 +1514,20 @@ 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)
|
|
1447
1525
|
};
|
|
1448
1526
|
return { plan, tools: filteredTools, apiKey, model };
|
|
1449
1527
|
}
|
|
1528
|
+
function subagentOutputBudget(model) {
|
|
1529
|
+
return Math.min(model.maxTokens || 8192, 16384);
|
|
1530
|
+
}
|
|
1450
1531
|
function createAgentInstance(deps, systemPrompt, tools, model, apiKey, thinkingLevel) {
|
|
1451
1532
|
if (deps.createAgent) {
|
|
1452
1533
|
return deps.createAgent({
|
|
@@ -1464,7 +1545,14 @@ function createAgentInstance(deps, systemPrompt, tools, model, apiKey, thinkingL
|
|
|
1464
1545
|
tools,
|
|
1465
1546
|
thinkingLevel
|
|
1466
1547
|
},
|
|
1467
|
-
apiKey
|
|
1548
|
+
apiKey,
|
|
1549
|
+
// Subagent turns are single-purpose report generations: request an
|
|
1550
|
+
// explicit output budget (see subagentOutputBudget) instead of leaving
|
|
1551
|
+
// it to endpoint defaults that silently truncate long reports.
|
|
1552
|
+
streamFn: (streamModel, context, options) => streamSimple(streamModel, context, {
|
|
1553
|
+
...options,
|
|
1554
|
+
maxTokens: options?.maxTokens ?? subagentOutputBudget(streamModel)
|
|
1555
|
+
})
|
|
1468
1556
|
});
|
|
1469
1557
|
}
|
|
1470
1558
|
function extractEvidenceFromToolArgs(toolName, args) {
|
|
@@ -1502,7 +1590,7 @@ function inferUncertainty(text, signals) {
|
|
|
1502
1590
|
return "medium";
|
|
1503
1591
|
}
|
|
1504
1592
|
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");
|
|
1593
|
+
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
1594
|
}
|
|
1507
1595
|
async function runSubagentWithProgress(deps, task, reportProgress, signal) {
|
|
1508
1596
|
const startTime = Date.now();
|
|
@@ -1511,9 +1599,9 @@ async function runSubagentWithProgress(deps, task, reportProgress, signal) {
|
|
|
1511
1599
|
deps.debugLog?.("run", "runSubagentWithProgress START", { taskName: task.name, yoloMode: deps.yoloMode });
|
|
1512
1600
|
try {
|
|
1513
1601
|
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);
|
|
1602
|
+
const { plan, tools, apiKey, model } = buildSubagentPlan(deps, task.task, task.name, task.priority, task.timeoutSeconds, task.modelOverride, task.thinkingLevel, task.mode);
|
|
1515
1603
|
task.plan = plan;
|
|
1516
|
-
task.needsParentWrite = needsParentWrite(task.task);
|
|
1604
|
+
task.needsParentWrite = task.mode === "read" && needsParentWrite(task.task);
|
|
1517
1605
|
for (const excluded of plan.toolsExcluded) {
|
|
1518
1606
|
if (excluded.reason === "unclassified_tool") {
|
|
1519
1607
|
task.confidenceSignals.push({ type: "unclassified_tool", message: `${excluded.name} inherited without risk metadata` });
|
|
@@ -1538,7 +1626,7 @@ async function runSubagentWithProgress(deps, task, reportProgress, signal) {
|
|
|
1538
1626
|
Task boundary:
|
|
1539
1627
|
- Work only on the assigned task.
|
|
1540
1628
|
- 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.
|
|
1629
|
+
${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
1630
|
- Return a concise answer with evidence and uncertainty.
|
|
1543
1631
|
|
|
1544
1632
|
Output:
|
|
@@ -1551,6 +1639,7 @@ ${formatSkillsForPrompt(deps.skills ?? [])}`;
|
|
|
1551
1639
|
const subagent = createAgentInstance(deps, systemPrompt, tools, model, apiKey, plan.thinkingLevel);
|
|
1552
1640
|
let assistantTurns = 0;
|
|
1553
1641
|
let toolCalls = 0;
|
|
1642
|
+
let outputTruncated = false;
|
|
1554
1643
|
let rejectTimeout = () => void 0;
|
|
1555
1644
|
let timeout;
|
|
1556
1645
|
let unsubscribe;
|
|
@@ -1597,11 +1686,12 @@ ${formatSkillsForPrompt(deps.skills ?? [])}`;
|
|
|
1597
1686
|
}
|
|
1598
1687
|
if (event.type === "message_end" && event.message?.role === "assistant") {
|
|
1599
1688
|
assistantTurns++;
|
|
1689
|
+
if (event.message.stopReason === "length") outputTruncated = true;
|
|
1600
1690
|
}
|
|
1601
1691
|
});
|
|
1602
1692
|
let prompt = task.task;
|
|
1603
|
-
if (task.
|
|
1604
|
-
prompt += "\n\nThis
|
|
1693
|
+
if (task.mode === "read") {
|
|
1694
|
+
prompt += "\n\nThis delegation is read mode. Do not write files. If changes are needed, describe them for the parent.";
|
|
1605
1695
|
}
|
|
1606
1696
|
prompt += "\n\nReturn evidence for your conclusion and state uncertainty as low, medium, or high.";
|
|
1607
1697
|
reportProgress({ message: `[${task.name}] Running plan ${plan.complexity}/${plan.modelClass}...`, increment: 20 });
|
|
@@ -1623,7 +1713,19 @@ ${formatSkillsForPrompt(deps.skills ?? [])}`;
|
|
|
1623
1713
|
}
|
|
1624
1714
|
const messages = subagent.state.messages;
|
|
1625
1715
|
deps.log(`[Subagent ${task.name}] Total messages: ${messages.length}`);
|
|
1626
|
-
const
|
|
1716
|
+
const lastAssistant = [...messages].reverse().find((m) => m?.role === "assistant");
|
|
1717
|
+
const terminalStopReason = lastAssistant?.stopReason;
|
|
1718
|
+
const terminalError = typeof lastAssistant?.errorMessage === "string" ? lastAssistant.errorMessage : void 0;
|
|
1719
|
+
const collectedResponse = collectLastAssistantText(messages);
|
|
1720
|
+
if (terminalStopReason === "error" || terminalStopReason === "aborted" || terminalError || !collectedResponse) {
|
|
1721
|
+
const detail = terminalError ? `: ${terminalError.slice(0, 200)}` : "";
|
|
1722
|
+
throw new Error(`Subagent stream interrupted (SSE stream aborted before the report was produced, stopReason=${terminalStopReason ?? "none"}${collectedResponse ? ", partial transcript discarded" : ", no text response"})${detail}`);
|
|
1723
|
+
}
|
|
1724
|
+
let response = collectedResponse;
|
|
1725
|
+
if (outputTruncated) {
|
|
1726
|
+
task.confidenceSignals.push({ type: "output_truncated", message: "final response hit the provider's output token limit" });
|
|
1727
|
+
response += "\n\n[Incomplete report: the subagent's final response was cut off by the provider's output token limit (stopReason=length). The items above may be a partial list \u2014 rerun the task with a narrower scope, or ask for the remaining items.]";
|
|
1728
|
+
}
|
|
1627
1729
|
const reportedEvidence = extractEvidenceFromText(response).map((evidence) => ({ ...evidence, source: "reported" }));
|
|
1628
1730
|
task.evidence.push(...reportedEvidence.map(({ source: _source, ...evidence }) => evidence));
|
|
1629
1731
|
task.reportedEvidence.push(...reportedEvidence);
|
|
@@ -1637,9 +1739,10 @@ ${formatSkillsForPrompt(deps.skills ?? [])}`;
|
|
|
1637
1739
|
task.details = {
|
|
1638
1740
|
turns: assistantTurns,
|
|
1639
1741
|
toolCalls,
|
|
1640
|
-
durationMs
|
|
1742
|
+
durationMs,
|
|
1743
|
+
...outputTruncated ? { outputTruncated: true } : {}
|
|
1641
1744
|
};
|
|
1642
|
-
task.parentHandoffRequired = isParentHandoffRequired(task);
|
|
1745
|
+
task.parentHandoffRequired = isParentHandoffRequired(task) || outputTruncated;
|
|
1643
1746
|
task.completedAt = Date.now();
|
|
1644
1747
|
reportProgress({ message: `[${task.name}] Completed (${durationMs}ms, ${toolCalls} tools)`, increment: 100 });
|
|
1645
1748
|
} catch (error) {
|
|
@@ -1669,7 +1772,7 @@ function buildParallelResult(tasks, totalDuration) {
|
|
|
1669
1772
|
const statusLabel = task.status === "completed" ? "completed" : task.status;
|
|
1670
1773
|
resultText += `**${task.name}** (${statusLabel}`;
|
|
1671
1774
|
if (task.plan) {
|
|
1672
|
-
resultText += `, ${task.plan.complexity}/${task.plan.modelClass}`;
|
|
1775
|
+
resultText += `, mode=${task.mode}, ${task.plan.complexity}/${task.plan.modelClass}`;
|
|
1673
1776
|
if (task.plan.thinkingLevel !== DEFAULT_SUBAGENT_THINKING_LEVEL) {
|
|
1674
1777
|
resultText += `, thinking=${task.plan.thinkingLevel}`;
|
|
1675
1778
|
}
|
|
@@ -1693,7 +1796,9 @@ function buildParallelResult(tasks, totalDuration) {
|
|
|
1693
1796
|
resultText += `Evidence: ${task.evidence.slice(0, 5).map((entry) => `${entry.type}:${entry.value}`).join("; ")}
|
|
1694
1797
|
`;
|
|
1695
1798
|
}
|
|
1696
|
-
if (task.
|
|
1799
|
+
if (task.mode === "edit") {
|
|
1800
|
+
resultText += "Parent follow-up: review changes and validate reported tests. Partial edits are preserved on failure; no automatic rollback or task retry.\n";
|
|
1801
|
+
} else if (task.needsParentWrite) {
|
|
1697
1802
|
resultText += "Parent follow-up: writing appears to be needed; parent agent must make any edits.\n";
|
|
1698
1803
|
} else if (task.parentHandoffRequired) {
|
|
1699
1804
|
resultText += "Parent follow-up: subagent result needs parent review or continuation.\n";
|
|
@@ -1715,7 +1820,7 @@ function normalizeTaskDefs(args) {
|
|
|
1715
1820
|
return { error: "Invalid spawn_subagents_parallel arguments: tasks must be a non-empty array." };
|
|
1716
1821
|
}
|
|
1717
1822
|
const normalized = [];
|
|
1718
|
-
const allowedFields = [...TASK_FIELD_ALIASES, "name", "priority", "timeout", "model", "thinkingLevel"];
|
|
1823
|
+
const allowedFields = [...TASK_FIELD_ALIASES, "name", "priority", "timeout", "model", "thinkingLevel", "mode"];
|
|
1719
1824
|
for (const [index, def] of taskDefs.entries()) {
|
|
1720
1825
|
if (!def || typeof def !== "object") {
|
|
1721
1826
|
return { error: `Invalid task #${index + 1}: expected an object with the objective in "task" (aliases "description"/"prompt"/"objective" accepted).` };
|
|
@@ -1737,6 +1842,9 @@ function normalizeTaskDefs(args) {
|
|
|
1737
1842
|
if (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0) {
|
|
1738
1843
|
return { error: `Invalid task #${index + 1}: timeout must be a positive number of seconds.` };
|
|
1739
1844
|
}
|
|
1845
|
+
if (def.mode !== void 0 && def.mode !== "read" && def.mode !== "edit") {
|
|
1846
|
+
return { error: `Invalid task #${index + 1}: "mode" must be "read" or "edit".` };
|
|
1847
|
+
}
|
|
1740
1848
|
const priority = def.priority === "thorough" ? "thorough" : "fast";
|
|
1741
1849
|
const modelOverride = typeof def.model === "string" ? def.model.trim() : void 0;
|
|
1742
1850
|
if (def.model !== void 0 && !modelOverride) {
|
|
@@ -1749,11 +1857,15 @@ function normalizeTaskDefs(args) {
|
|
|
1749
1857
|
name: typeof def.name === "string" && def.name.trim() ? def.name.trim() : void 0,
|
|
1750
1858
|
task,
|
|
1751
1859
|
priority,
|
|
1860
|
+
mode: def.mode ?? "read",
|
|
1752
1861
|
timeoutSeconds,
|
|
1753
1862
|
modelOverride,
|
|
1754
1863
|
thinkingLevel: def.thinkingLevel
|
|
1755
1864
|
});
|
|
1756
1865
|
}
|
|
1866
|
+
if (normalized.length !== 1 && normalized.some((task) => task.mode === "edit")) {
|
|
1867
|
+
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." };
|
|
1868
|
+
}
|
|
1757
1869
|
return normalized;
|
|
1758
1870
|
}
|
|
1759
1871
|
var DEFAULT_MAX_CONCURRENT_SUBAGENTS = 4;
|
|
@@ -1792,10 +1904,10 @@ function createSubagentCapability(deps) {
|
|
|
1792
1904
|
label: "Spawn Subagents",
|
|
1793
1905
|
description: `Spawn child agents to run bounded sub-tasks in parallel.
|
|
1794
1906
|
|
|
1795
|
-
|
|
1907
|
+
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
1908
|
|
|
1797
1909
|
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 }] }
|
|
1910
|
+
{ "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
1911
|
|
|
1800
1912
|
"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
1913
|
|
|
@@ -1809,6 +1921,7 @@ Arguments shape (each tasks[] item puts the objective in "task" \u2014 "descript
|
|
|
1809
1921
|
prompt: Type.Optional(Type.String({ description: "Alias for task." })),
|
|
1810
1922
|
objective: Type.Optional(Type.String({ description: "Alias for task." })),
|
|
1811
1923
|
name: Type.Optional(Type.String({ description: "Short name for this subagent, shown in progress updates." })),
|
|
1924
|
+
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
1925
|
priority: Type.Optional(Type.Union([Type.Literal("fast"), Type.Literal("thorough")], { description: "fast uses lower-cost defaults; thorough raises model class when needed." })),
|
|
1813
1926
|
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
1927
|
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 +1959,7 @@ Arguments shape (each tasks[] item puts the objective in "task" \u2014 "descript
|
|
|
1846
1959
|
status: "pending",
|
|
1847
1960
|
task: def.task,
|
|
1848
1961
|
priority: def.priority,
|
|
1962
|
+
mode: def.mode,
|
|
1849
1963
|
timeoutSeconds: def.timeoutSeconds,
|
|
1850
1964
|
modelOverride: def.modelOverride,
|
|
1851
1965
|
thinkingLevel: def.thinkingLevel,
|
|
@@ -1864,7 +1978,7 @@ Arguments shape (each tasks[] item puts the objective in "task" \u2014 "descript
|
|
|
1864
1978
|
const runTask = async (task) => {
|
|
1865
1979
|
for (let attempt = 0; ; attempt++) {
|
|
1866
1980
|
await runSubagentWithProgress(deps, task, toolContext.reportProgress, signal);
|
|
1867
|
-
const canRetry = task.status === "failed" && isTransientNetworkError(task.error ?? "") && attempt < MAX_TRANSIENT_RETRIES && !signal?.aborted;
|
|
1981
|
+
const canRetry = task.mode === "read" && task.status === "failed" && isTransientNetworkError(task.error ?? "") && attempt < MAX_TRANSIENT_RETRIES && !signal?.aborted;
|
|
1868
1982
|
if (!canRetry) return;
|
|
1869
1983
|
const failureReason = task.error ?? "unknown";
|
|
1870
1984
|
task.evidence.length = 0;
|
|
@@ -1896,6 +2010,7 @@ Arguments shape (each tasks[] item puts the objective in "task" \u2014 "descript
|
|
|
1896
2010
|
id: task.id,
|
|
1897
2011
|
name: task.name,
|
|
1898
2012
|
status: task.status,
|
|
2013
|
+
mode: task.mode,
|
|
1899
2014
|
task: task.task,
|
|
1900
2015
|
timeoutSeconds: task.timeoutSeconds,
|
|
1901
2016
|
plan: task.plan,
|
|
@@ -9015,14 +9130,15 @@ Focus: ${question.trim()}` : ""}` },
|
|
|
9015
9130
|
const readReachedEndOfFile = startLine + range2.selectedLines >= range2.totalLines;
|
|
9016
9131
|
const isCompleteTextKnown = readStartsAtBeginning && readReachedEndOfFile && !truncation.truncated && !truncation.firstLineExceedsLimit;
|
|
9017
9132
|
let callerObservation = fileObservationState;
|
|
9018
|
-
|
|
9133
|
+
const callerKey = getToolCallerIdentity(signal) ?? signal;
|
|
9134
|
+
if (callerKey) {
|
|
9019
9135
|
const generation = fileObservationState.getGeneration();
|
|
9020
|
-
const existing = callerObservations.get(
|
|
9136
|
+
const existing = callerObservations.get(callerKey);
|
|
9021
9137
|
if (existing?.generation === generation) {
|
|
9022
9138
|
callerObservation = existing.state;
|
|
9023
9139
|
} else {
|
|
9024
9140
|
callerObservation = createFileObservationState();
|
|
9025
|
-
callerObservations.set(
|
|
9141
|
+
callerObservations.set(callerKey, { generation, state: callerObservation });
|
|
9026
9142
|
}
|
|
9027
9143
|
}
|
|
9028
9144
|
if (fileObservationState.isUnchangedReadRange(absolutePath, rangeKey, snapshot, range2.selectedContentHash) && callerObservation.isUnchangedReadRange(absolutePath, rangeKey, snapshot, range2.selectedContentHash)) {
|
|
@@ -12753,7 +12869,7 @@ function createGrepTextTool(cwd, options) {
|
|
|
12753
12869
|
Type.Boolean({ description: "Treat pattern as literal string instead of regex (default: false)" })
|
|
12754
12870
|
),
|
|
12755
12871
|
context: Type.Optional(
|
|
12756
|
-
Type.Integer({ minimum: 0, maximum: MAX_GREP_CONTEXT_LINES, description: `Number of lines to show before and after each match (default: 0
|
|
12872
|
+
Type.Integer({ minimum: 0, maximum: MAX_GREP_CONTEXT_LINES * 10, description: `Number of lines to show before and after each match (default: 0; values above ${MAX_GREP_CONTEXT_LINES} are clamped to ${MAX_GREP_CONTEXT_LINES})` })
|
|
12757
12873
|
),
|
|
12758
12874
|
outputMode: Type.Optional(Type.Union([
|
|
12759
12875
|
Type.Literal("content"),
|
|
@@ -12808,8 +12924,7 @@ function createGrepTextTool(cwd, options) {
|
|
|
12808
12924
|
const offset = normalizeNonNegativeInteger(rawOffset, 0, "offset");
|
|
12809
12925
|
const effectiveLimit = normalizeNonNegativeInteger(limit, DEFAULT_LIMIT2, "limit");
|
|
12810
12926
|
if (effectiveLimit < 1 || effectiveLimit > MAX_GREP_RESULT_LIMIT) throw new Error(`Invalid grep_text arguments: limit must be between 1 and ${MAX_GREP_RESULT_LIMIT}.`);
|
|
12811
|
-
const ctx = normalizeNonNegativeInteger(contextLines, 0, "context");
|
|
12812
|
-
if (ctx > MAX_GREP_CONTEXT_LINES) throw new Error(`Invalid grep_text arguments: context must be at most ${MAX_GREP_CONTEXT_LINES}.`);
|
|
12927
|
+
const ctx = Math.min(normalizeNonNegativeInteger(contextLines, 0, "context"), MAX_GREP_CONTEXT_LINES);
|
|
12813
12928
|
let isDir;
|
|
12814
12929
|
try {
|
|
12815
12930
|
const stat = await fs2.stat(searchPath);
|
|
@@ -13483,6 +13598,29 @@ Focus: ${request.question.trim()}` : ""}` },
|
|
|
13483
13598
|
isError: false
|
|
13484
13599
|
};
|
|
13485
13600
|
}
|
|
13601
|
+
if ((request.kind === "video" || request.kind === "audio") && agentModel?.api === "openai-completions" && agentModel.input.includes(request.kind)) {
|
|
13602
|
+
const capability = agentModel.fileCapabilities?.[request.kind];
|
|
13603
|
+
const audioSupported = request.kind !== "audio" || ["audio/wav", "audio/mpeg", "audio/mp3"].includes(request.mimeType);
|
|
13604
|
+
if (capability && typeof capability === "object" && capability.supported !== false && capability.nativeInline === true && audioSupported && (!capability.formats || capability.formats.includes(request.mimeType)) && capability.maxDurationSeconds === void 0) {
|
|
13605
|
+
const fs2 = getFileSystem();
|
|
13606
|
+
const size = (await fs2.stat(request.absolutePath)).size;
|
|
13607
|
+
const cap = Math.min(capability.maxFileSizeMb ?? 15, 15) * 1024 * 1024;
|
|
13608
|
+
if (size > 0 && size <= cap) {
|
|
13609
|
+
if (request.signal?.aborted) throw new Error("Operation aborted");
|
|
13610
|
+
const data = await fs2.readFile(request.absolutePath, "base64");
|
|
13611
|
+
if (request.signal?.aborted) throw new Error("Operation aborted");
|
|
13612
|
+
if (data) return {
|
|
13613
|
+
content: [
|
|
13614
|
+
{ type: "text", text: `Read ${request.kind} file ${request.displayPath} [${request.mimeType}]. Original media attached for the current model.${request.question?.trim() ? `
|
|
13615
|
+
Focus: ${request.question.trim()}` : ""}` },
|
|
13616
|
+
{ type: request.kind, data, mimeType: request.mimeType }
|
|
13617
|
+
],
|
|
13618
|
+
details: { understanding: { purpose, kind: request.kind, mimeType: request.mimeType, agentModel: getModelLabel(agentModel), routeSource: "current_model", inputMode: "nativeAttachment", attachmentBytes: size } },
|
|
13619
|
+
isError: false
|
|
13620
|
+
};
|
|
13621
|
+
}
|
|
13622
|
+
}
|
|
13623
|
+
}
|
|
13486
13624
|
const singleDecision = deps.resolveToolModel(purpose);
|
|
13487
13625
|
const candidateDecisions = deps.resolveToolModelCandidates?.(purpose) ?? [singleDecision];
|
|
13488
13626
|
const sessionId = deps.getSessionId() || `read-file-${Date.now()}`;
|
|
@@ -14602,19 +14740,121 @@ var webFetchSchema = Type.Object({
|
|
|
14602
14740
|
var DEFAULT_TIMEOUT_MS = 45e3;
|
|
14603
14741
|
var DEFAULT_MAX_BYTES2 = 2e6;
|
|
14604
14742
|
var DEFAULT_MAX_CHARS = 1e5;
|
|
14743
|
+
var DEFAULT_MAX_REDIRECTS = 5;
|
|
14744
|
+
var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
14605
14745
|
function modelLabel2(model) {
|
|
14606
14746
|
return model ? `${model.provider}/${model.id}` : void 0;
|
|
14607
14747
|
}
|
|
14748
|
+
function parseIpv4Part(part) {
|
|
14749
|
+
if (part === "") return void 0;
|
|
14750
|
+
const lowered = part.toLowerCase();
|
|
14751
|
+
if (lowered.startsWith("0x")) {
|
|
14752
|
+
const digits = lowered.slice(2);
|
|
14753
|
+
return /^[0-9a-f]+$/.test(digits) ? parseInt(digits, 16) : void 0;
|
|
14754
|
+
}
|
|
14755
|
+
if (lowered.length > 1 && lowered.startsWith("0")) {
|
|
14756
|
+
return /^[0-7]+$/.test(lowered) ? parseInt(lowered, 8) : void 0;
|
|
14757
|
+
}
|
|
14758
|
+
if (!/^\d+$/.test(lowered)) return void 0;
|
|
14759
|
+
return parseInt(lowered, 10);
|
|
14760
|
+
}
|
|
14761
|
+
function parseIpv4Literal(host) {
|
|
14762
|
+
const parts = host.split(".");
|
|
14763
|
+
if (parts.length === 0 || parts.length > 4) return void 0;
|
|
14764
|
+
const numbers = [];
|
|
14765
|
+
for (const part of parts) {
|
|
14766
|
+
const value = parseIpv4Part(part);
|
|
14767
|
+
if (value === void 0) return void 0;
|
|
14768
|
+
numbers.push(value);
|
|
14769
|
+
}
|
|
14770
|
+
const count = numbers.length;
|
|
14771
|
+
const last = numbers[count - 1];
|
|
14772
|
+
if (last > 256 ** (5 - count) - 1) return void 0;
|
|
14773
|
+
for (let i = 0; i < count - 1; i += 1) {
|
|
14774
|
+
if (numbers[i] > 255) return void 0;
|
|
14775
|
+
}
|
|
14776
|
+
let address = 0;
|
|
14777
|
+
for (let i = 0; i < count - 1; i += 1) address = address * 256 + numbers[i];
|
|
14778
|
+
address = address * 256 ** (5 - count) + last;
|
|
14779
|
+
return [address >>> 24 & 255, address >>> 16 & 255, address >>> 8 & 255, address & 255];
|
|
14780
|
+
}
|
|
14781
|
+
function isPrivateIpv4(octets) {
|
|
14782
|
+
const [a = 0, b = 0] = octets;
|
|
14783
|
+
if (a === 0) return true;
|
|
14784
|
+
if (a === 10) return true;
|
|
14785
|
+
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
14786
|
+
if (a === 127) return true;
|
|
14787
|
+
if (a === 169 && b === 254) return true;
|
|
14788
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
14789
|
+
if (a === 192 && b === 0 && octets[2] === 0) return true;
|
|
14790
|
+
if (a === 192 && b === 0 && octets[2] === 2) return true;
|
|
14791
|
+
if (a === 192 && b === 88 && octets[2] === 99) return true;
|
|
14792
|
+
if (a === 192 && b === 168) return true;
|
|
14793
|
+
if (a === 198 && (b === 18 || b === 19)) return true;
|
|
14794
|
+
if (a === 198 && b === 51 && octets[2] === 100) return true;
|
|
14795
|
+
if (a === 203 && b === 0 && octets[2] === 113) return true;
|
|
14796
|
+
if (a >= 224) return true;
|
|
14797
|
+
return false;
|
|
14798
|
+
}
|
|
14799
|
+
function parseIpv6Literal(hostname) {
|
|
14800
|
+
let host = hostname.toLowerCase();
|
|
14801
|
+
if (host.startsWith("[")) host = host.slice(1, host.endsWith("]") ? -1 : void 0);
|
|
14802
|
+
const zoneIndex = host.indexOf("%");
|
|
14803
|
+
if (zoneIndex >= 0) host = host.slice(0, zoneIndex);
|
|
14804
|
+
if (!host.includes(":")) return void 0;
|
|
14805
|
+
const halves = host.split("::");
|
|
14806
|
+
if (halves.length > 2) return void 0;
|
|
14807
|
+
const parseTokens = (tokens) => {
|
|
14808
|
+
const groups = [];
|
|
14809
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
14810
|
+
const token = tokens[i];
|
|
14811
|
+
if (token === "") return void 0;
|
|
14812
|
+
if (i === tokens.length - 1 && token.includes(".")) {
|
|
14813
|
+
const v4 = parseIpv4Literal(token);
|
|
14814
|
+
if (!v4) return void 0;
|
|
14815
|
+
groups.push(v4[0] << 8 | v4[1], v4[2] << 8 | v4[3]);
|
|
14816
|
+
continue;
|
|
14817
|
+
}
|
|
14818
|
+
if (!/^[0-9a-f]{1,4}$/.test(token)) return void 0;
|
|
14819
|
+
groups.push(parseInt(token, 16));
|
|
14820
|
+
}
|
|
14821
|
+
return groups;
|
|
14822
|
+
};
|
|
14823
|
+
const head = halves[0] === "" ? [] : parseTokens(halves[0].split(":"));
|
|
14824
|
+
if (head === void 0) return void 0;
|
|
14825
|
+
const tail = halves.length === 2 ? halves[1] === "" ? [] : parseTokens(halves[1].split(":")) : [];
|
|
14826
|
+
if (tail === void 0) return void 0;
|
|
14827
|
+
if (halves.length === 1) return head.length === 8 ? head : void 0;
|
|
14828
|
+
const missing = 8 - head.length - tail.length;
|
|
14829
|
+
if (missing < 1) return void 0;
|
|
14830
|
+
return [...head, ...new Array(missing).fill(0), ...tail];
|
|
14831
|
+
}
|
|
14832
|
+
function isPrivateIpv6(groups) {
|
|
14833
|
+
const first = groups[0];
|
|
14834
|
+
if (groups.every((group) => group === 0)) return true;
|
|
14835
|
+
if (groups.slice(0, 7).every((group) => group === 0) && groups[7] === 1) return true;
|
|
14836
|
+
if ((first & 65024) === 64512) return true;
|
|
14837
|
+
if ((first & 65472) === 65152) return true;
|
|
14838
|
+
const embeddedV4 = [groups[6] >> 8, groups[6] & 255, groups[7] >> 8, groups[7] & 255];
|
|
14839
|
+
if (groups.slice(0, 5).every((group) => group === 0) && groups[5] === 65535) {
|
|
14840
|
+
return isPrivateIpv4(embeddedV4);
|
|
14841
|
+
}
|
|
14842
|
+
if (groups.slice(0, 6).every((group) => group === 0)) {
|
|
14843
|
+
return isPrivateIpv4(embeddedV4);
|
|
14844
|
+
}
|
|
14845
|
+
if (first === 100 && groups[1] === 65435 && groups.slice(2, 6).every((group) => group === 0)) {
|
|
14846
|
+
return isPrivateIpv4(embeddedV4);
|
|
14847
|
+
}
|
|
14848
|
+
return false;
|
|
14849
|
+
}
|
|
14608
14850
|
function isPrivateHostname(hostname) {
|
|
14609
|
-
const host = hostname.toLowerCase();
|
|
14851
|
+
const host = hostname.toLowerCase().trim().replace(/\.+$/, "");
|
|
14610
14852
|
if (host === "localhost" || host.endsWith(".localhost")) return true;
|
|
14611
|
-
if (host === "
|
|
14612
|
-
|
|
14613
|
-
if (
|
|
14614
|
-
|
|
14615
|
-
if (
|
|
14616
|
-
if (host === "::1" || host === "[::1]") return true;
|
|
14617
|
-
if (host === "169.254.169.254") return true;
|
|
14853
|
+
if (host === "local" || host.endsWith(".local")) return true;
|
|
14854
|
+
const v4 = parseIpv4Literal(host);
|
|
14855
|
+
if (v4) return isPrivateIpv4(v4);
|
|
14856
|
+
const v6 = parseIpv6Literal(host);
|
|
14857
|
+
if (v6) return isPrivateIpv6(v6);
|
|
14618
14858
|
return false;
|
|
14619
14859
|
}
|
|
14620
14860
|
function charsetFromContentType(contentType) {
|
|
@@ -14753,6 +14993,24 @@ async function cancelResponseBody(response) {
|
|
|
14753
14993
|
} catch {
|
|
14754
14994
|
}
|
|
14755
14995
|
}
|
|
14996
|
+
var DISCARD_BODY_GRACE_MS = 250;
|
|
14997
|
+
async function discardResponseBody(response) {
|
|
14998
|
+
await new Promise((resolve3) => {
|
|
14999
|
+
let settled = false;
|
|
15000
|
+
const settle = () => {
|
|
15001
|
+
if (settled) return;
|
|
15002
|
+
settled = true;
|
|
15003
|
+
clearTimeout(timer);
|
|
15004
|
+
resolve3();
|
|
15005
|
+
};
|
|
15006
|
+
const timer = setTimeout(settle, DISCARD_BODY_GRACE_MS);
|
|
15007
|
+
try {
|
|
15008
|
+
void cancelResponseBody(response).then(settle, settle);
|
|
15009
|
+
} catch {
|
|
15010
|
+
settle();
|
|
15011
|
+
}
|
|
15012
|
+
});
|
|
15013
|
+
}
|
|
14756
15014
|
var BODY_TRUNCATED_ERROR_CODE = "body_truncated";
|
|
14757
15015
|
function isBodyTruncationError(error) {
|
|
14758
15016
|
return error instanceof Error && error.code === BODY_TRUNCATED_ERROR_CODE;
|
|
@@ -14889,7 +15147,8 @@ function createSharedWebFetchTool(deps = {}) {
|
|
|
14889
15147
|
isError: true
|
|
14890
15148
|
};
|
|
14891
15149
|
}
|
|
14892
|
-
|
|
15150
|
+
const allowPrivate = deps.allowPrivateHosts?.() ?? false;
|
|
15151
|
+
if (!allowPrivate && isPrivateHostname(parsed.hostname)) {
|
|
14893
15152
|
return {
|
|
14894
15153
|
content: [{ type: "text", text: `Blocked private or local URL: ${url}` }],
|
|
14895
15154
|
details: { url, sourceUrl: url, durationMs: Date.now() - start, failureCategory: "blocked_private_host" },
|
|
@@ -14898,22 +15157,91 @@ function createSharedWebFetchTool(deps = {}) {
|
|
|
14898
15157
|
}
|
|
14899
15158
|
const timeout = createTimeoutSignal(signal, deps.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
14900
15159
|
try {
|
|
14901
|
-
const
|
|
15160
|
+
const requestHeaders = {
|
|
15161
|
+
"user-agent": "Pie web_fetch/1.0",
|
|
15162
|
+
accept: "text/html,application/xhtml+xml,text/plain,application/pdf,*/*;q=0.8"
|
|
15163
|
+
};
|
|
15164
|
+
const doFetch = (target) => (deps.fetch ?? fetch)(target, {
|
|
14902
15165
|
method: "GET",
|
|
14903
|
-
|
|
15166
|
+
// Redirects are followed manually in the loop below so that every
|
|
15167
|
+
// hop is validated (scheme + host) BEFORE its request is issued.
|
|
15168
|
+
// A transport that ignores this and follows internally is caught
|
|
15169
|
+
// post-hoc via the response.url guard below.
|
|
15170
|
+
redirect: "manual",
|
|
14904
15171
|
signal: timeout.signal,
|
|
14905
|
-
headers:
|
|
14906
|
-
"user-agent": "Pie web_fetch/1.0",
|
|
14907
|
-
accept: "text/html,application/xhtml+xml,text/plain,application/pdf,*/*;q=0.8"
|
|
14908
|
-
}
|
|
15172
|
+
headers: requestHeaders
|
|
14909
15173
|
});
|
|
15174
|
+
const redirectChain = [url];
|
|
15175
|
+
const maxRedirects = deps.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
|
|
15176
|
+
let currentUrl = url;
|
|
15177
|
+
let response = await doFetch(currentUrl);
|
|
15178
|
+
let redirectsFollowed = 0;
|
|
15179
|
+
while (REDIRECT_STATUSES.has(response.status)) {
|
|
15180
|
+
const location = response.headers.get("location")?.trim();
|
|
15181
|
+
if (!location) break;
|
|
15182
|
+
let next;
|
|
15183
|
+
try {
|
|
15184
|
+
next = new URL(location, currentUrl);
|
|
15185
|
+
} catch {
|
|
15186
|
+
await discardResponseBody(response);
|
|
15187
|
+
return {
|
|
15188
|
+
content: [{ type: "text", text: `web_fetch redirect target is not a valid URL: ${location}` }],
|
|
15189
|
+
details: { url, sourceUrl: url, finalUrl: currentUrl, redirectChain, durationMs: Date.now() - start, failureCategory: "invalid_url" },
|
|
15190
|
+
isError: true
|
|
15191
|
+
};
|
|
15192
|
+
}
|
|
15193
|
+
if (next.protocol !== "http:" && next.protocol !== "https:") {
|
|
15194
|
+
await discardResponseBody(response);
|
|
15195
|
+
return {
|
|
15196
|
+
content: [{ type: "text", text: `web_fetch only follows http(s) redirects: ${next.toString()} (from ${currentUrl})` }],
|
|
15197
|
+
details: { url, sourceUrl: url, finalUrl: next.toString(), redirectChain: [...redirectChain, next.toString()], durationMs: Date.now() - start, failureCategory: "invalid_url" },
|
|
15198
|
+
isError: true
|
|
15199
|
+
};
|
|
15200
|
+
}
|
|
15201
|
+
if (!allowPrivate && isPrivateHostname(next.hostname)) {
|
|
15202
|
+
await discardResponseBody(response);
|
|
15203
|
+
return {
|
|
15204
|
+
content: [{ type: "text", text: `Blocked redirect to private or local URL: ${next.toString()} (while fetching ${url})` }],
|
|
15205
|
+
details: { url, sourceUrl: url, finalUrl: next.toString(), redirectChain: [...redirectChain, next.toString()], durationMs: Date.now() - start, failureCategory: "blocked_private_host" },
|
|
15206
|
+
isError: true
|
|
15207
|
+
};
|
|
15208
|
+
}
|
|
15209
|
+
redirectsFollowed += 1;
|
|
15210
|
+
if (redirectsFollowed > maxRedirects) {
|
|
15211
|
+
await discardResponseBody(response);
|
|
15212
|
+
return {
|
|
15213
|
+
content: [{ type: "text", text: `web_fetch stopped after ${maxRedirects} redirects while fetching ${url} (last hop: ${currentUrl} -> ${location})` }],
|
|
15214
|
+
details: { url, sourceUrl: url, finalUrl: currentUrl, redirectChain, durationMs: Date.now() - start, failureCategory: "too_many_redirects" },
|
|
15215
|
+
isError: true
|
|
15216
|
+
};
|
|
15217
|
+
}
|
|
15218
|
+
await discardResponseBody(response);
|
|
15219
|
+
currentUrl = next.toString();
|
|
15220
|
+
redirectChain.push(currentUrl);
|
|
15221
|
+
response = await doFetch(currentUrl);
|
|
15222
|
+
}
|
|
15223
|
+
const finalUrl = response.url || currentUrl;
|
|
15224
|
+
let transportFinalHostname;
|
|
15225
|
+
try {
|
|
15226
|
+
transportFinalHostname = new URL(finalUrl).hostname;
|
|
15227
|
+
} catch {
|
|
15228
|
+
transportFinalHostname = void 0;
|
|
15229
|
+
}
|
|
15230
|
+
if (!allowPrivate && transportFinalHostname && isPrivateHostname(transportFinalHostname)) {
|
|
15231
|
+
await discardResponseBody(response);
|
|
15232
|
+
return {
|
|
15233
|
+
content: [{ type: "text", text: `Blocked: the fetch transport followed a redirect to private or local URL ${finalUrl} (while fetching ${url})` }],
|
|
15234
|
+
details: { url, sourceUrl: url, finalUrl, redirectChain, durationMs: Date.now() - start, failureCategory: "blocked_private_host" },
|
|
15235
|
+
isError: true
|
|
15236
|
+
};
|
|
15237
|
+
}
|
|
14910
15238
|
const contentType = response.headers.get("content-type") || "";
|
|
14911
15239
|
const maxBytes = deps.maxBytes ?? DEFAULT_MAX_BYTES2;
|
|
14912
15240
|
const bodyResult = await readResponseBody(response, maxBytes);
|
|
14913
15241
|
if (bodyResult.exceededLimit) {
|
|
14914
15242
|
return {
|
|
14915
15243
|
content: [{ type: "text", text: `Fetched content is too large (${bodyResult.observedBytes} bytes; max ${maxBytes}).` }],
|
|
14916
|
-
details: { url, sourceUrl: url, finalUrl
|
|
15244
|
+
details: { url, sourceUrl: url, finalUrl, status: response.status, contentType, bytes: bodyResult.observedBytes, truncated: true, truncationReason: "max_bytes", durationMs: Date.now() - start, failureCategory: "too_large" },
|
|
14917
15245
|
isError: true
|
|
14918
15246
|
};
|
|
14919
15247
|
}
|
|
@@ -14921,7 +15249,7 @@ function createSharedWebFetchTool(deps = {}) {
|
|
|
14921
15249
|
if (!response.ok) {
|
|
14922
15250
|
return {
|
|
14923
15251
|
content: [{ type: "text", text: `web_fetch failed with HTTP ${response.status} for ${url}` }],
|
|
14924
|
-
details: { url, sourceUrl: url, finalUrl
|
|
15252
|
+
details: { url, sourceUrl: url, finalUrl, status: response.status, contentType, bytes: bodyResult.observedBytes, redirectChain, durationMs: Date.now() - start, failureCategory: "http_error" },
|
|
14925
15253
|
isError: true
|
|
14926
15254
|
};
|
|
14927
15255
|
}
|
|
@@ -14939,7 +15267,7 @@ function createSharedWebFetchTool(deps = {}) {
|
|
|
14939
15267
|
details: {
|
|
14940
15268
|
url,
|
|
14941
15269
|
sourceUrl: url,
|
|
14942
|
-
finalUrl
|
|
15270
|
+
finalUrl,
|
|
14943
15271
|
status: response.status,
|
|
14944
15272
|
contentType,
|
|
14945
15273
|
charset: decoded.charset,
|
|
@@ -14958,15 +15286,14 @@ function createSharedWebFetchTool(deps = {}) {
|
|
|
14958
15286
|
if (!text) {
|
|
14959
15287
|
return {
|
|
14960
15288
|
content: [{ type: "text", text: `web_fetch could not extract readable text from ${url}` }],
|
|
14961
|
-
details: { url, sourceUrl: url, finalUrl
|
|
15289
|
+
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" },
|
|
14962
15290
|
isError: true
|
|
14963
15291
|
};
|
|
14964
15292
|
}
|
|
14965
15293
|
const maxChars = deps.maxChars ?? DEFAULT_MAX_CHARS;
|
|
14966
15294
|
const truncated = text.length > maxChars;
|
|
14967
15295
|
if (truncated) text = text.slice(0, maxChars) + "\n\n[web_fetch content truncated]";
|
|
14968
|
-
const extraction = prompt?.trim() ? await applyPromptWithModel(deps, prompt.trim(),
|
|
14969
|
-
const finalUrl = response.url || url;
|
|
15296
|
+
const extraction = prompt?.trim() ? await applyPromptWithModel(deps, prompt.trim(), finalUrl, text, timeout.signal) : { text, model: modelLabel2(deps.getModel?.()) };
|
|
14970
15297
|
return {
|
|
14971
15298
|
content: [{ type: "text", text: extraction.text }],
|
|
14972
15299
|
details: {
|
|
@@ -14980,7 +15307,7 @@ function createSharedWebFetchTool(deps = {}) {
|
|
|
14980
15307
|
bytes: bodyResult.observedBytes,
|
|
14981
15308
|
title,
|
|
14982
15309
|
truncated,
|
|
14983
|
-
redirectChain
|
|
15310
|
+
redirectChain,
|
|
14984
15311
|
extractionMethod: prompt?.trim() ? "model_extraction" : extractionMethod,
|
|
14985
15312
|
readabilityScore: htmlExtraction?.readabilityScore,
|
|
14986
15313
|
citationAnchors: buildCitationAnchors(finalUrl, title, text),
|