@cydm/pie 1.0.50 → 2.1.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/README.md +32 -15
- package/dist/builtin/extensions/ask-user/index.js +4 -3
- package/dist/builtin/extensions/ask-user/package.json +8 -7
- package/dist/builtin/extensions/changelog/package.json +9 -9
- package/dist/builtin/extensions/computer-use/index.js +5466 -0
- package/dist/builtin/extensions/computer-use/package.json +11 -0
- package/dist/builtin/extensions/computer-use/prebuilt/linux/arm64/linux-bridge +0 -0
- package/dist/builtin/extensions/computer-use/prebuilt/linux/x64/linux-bridge +0 -0
- 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/Info.plist +24 -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/universal/pie-computer-use.app/Contents/_CodeSignature/CodeResources +115 -0
- package/dist/builtin/extensions/computer-use/prebuilt/macos/x64/pie-computer-use +0 -0
- package/dist/builtin/extensions/computer-use/prebuilt/windows/windows-bridge.exe +0 -0
- package/dist/builtin/extensions/computer-use/scripts/build-native.mjs +277 -0
- package/dist/builtin/extensions/computer-use/scripts/helper-path.mjs +48 -0
- package/dist/builtin/extensions/computer-use/scripts/setup-helper.mjs +549 -0
- package/dist/builtin/extensions/document-attachments/index.js +51 -0
- package/dist/builtin/extensions/document-attachments/package.json +9 -9
- package/dist/builtin/extensions/init/package.json +9 -9
- package/dist/builtin/extensions/mcp-adapter/index.js +25250 -0
- package/dist/builtin/extensions/mcp-adapter/package.json +11 -0
- package/dist/builtin/extensions/plan-mode/index.js +4 -3
- package/dist/builtin/extensions/plan-mode/package.json +9 -9
- package/dist/builtin/extensions/subagent/index.js +17 -20
- package/dist/builtin/extensions/subagent/package.json +16 -12
- package/dist/builtin/extensions/todo/index.js +4 -3
- package/dist/builtin/extensions/todo/package.json +9 -9
- package/dist/builtin/skills/chrome-devtools-axi/CHANGELOG.md +5 -0
- package/dist/builtin/skills/chrome-devtools-axi/README.md +17 -0
- package/dist/builtin/skills/chrome-devtools-axi/SKILL.md +31 -0
- package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-cli.js +77 -0
- package/dist/builtin/skills/chrome-devtools-axi/package.json +12 -0
- package/dist/builtin/skills/tui-use/README.md +7 -0
- package/dist/builtin/skills/tui-use/SKILL.md +58 -0
- package/dist/chunks/chunk-5353DEEC.js +130 -0
- package/dist/chunks/{chunk-XBWFDIAU.js → chunk-G2POWIA5.js} +129 -194
- package/dist/chunks/{chunk-K6ZOCJ4V.js → chunk-IM43UYVV.js} +861 -3376
- package/dist/chunks/{chunk-DZS34ACX.js → chunk-PMPD56N2.js} +243 -87
- package/dist/chunks/{chunk-FKAO25XH.js → chunk-QJH6YN2W.js} +1 -1
- package/dist/chunks/chunk-XXGYUYXI.js +2718 -0
- package/dist/chunks/{src-HYZFAW4V.js → src-QSP2OPHU.js} +3 -2
- package/dist/chunks/{test-stream-5T7E7C25.js → test-stream-CVGWFLSX.js} +2 -1
- package/dist/chunks/webapi-MBCKO7ZV.js +2481 -0
- package/dist/cli.js +186 -78
- package/package.json +5 -4
- package/dist/builtin/skills/browser-tools/CHANGELOG.md +0 -5
- package/dist/builtin/skills/browser-tools/README.md +0 -17
- package/dist/builtin/skills/browser-tools/SKILL.md +0 -43
- package/dist/builtin/skills/browser-tools/package.json +0 -12
- package/dist/builtin/skills/browser-tools/playwright-cli.js +0 -45
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
Agent
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-QJH6YN2W.js";
|
|
5
5
|
import {
|
|
6
6
|
FileSystemGateway,
|
|
7
|
-
Type,
|
|
8
7
|
completeSimple,
|
|
9
8
|
detectPlatform,
|
|
10
9
|
getFileSystem,
|
|
11
10
|
getPlatformConfig,
|
|
12
11
|
hashText,
|
|
13
12
|
streamSimple
|
|
14
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-IM43UYVV.js";
|
|
14
|
+
import {
|
|
15
|
+
Type
|
|
16
|
+
} from "./chunk-XXGYUYXI.js";
|
|
15
17
|
import {
|
|
16
18
|
__commonJS,
|
|
17
19
|
__toESM
|
|
@@ -1690,22 +1692,30 @@ function buildParallelResult(tasks, totalDuration) {
|
|
|
1690
1692
|
`;
|
|
1691
1693
|
return resultText;
|
|
1692
1694
|
}
|
|
1695
|
+
var TASK_FIELD_ALIASES = ["task", "description", "prompt", "objective"];
|
|
1693
1696
|
function normalizeTaskDefs(args) {
|
|
1694
1697
|
const taskDefs = args?.tasks;
|
|
1695
1698
|
if (!Array.isArray(taskDefs) || taskDefs.length === 0) {
|
|
1696
1699
|
return { error: "Invalid spawn_subagents_parallel arguments: tasks must be a non-empty array." };
|
|
1697
1700
|
}
|
|
1698
1701
|
const normalized = [];
|
|
1702
|
+
const allowedFields = [...TASK_FIELD_ALIASES, "name", "priority", "timeout"];
|
|
1699
1703
|
for (const [index, def] of taskDefs.entries()) {
|
|
1700
1704
|
if (!def || typeof def !== "object") {
|
|
1701
|
-
return { error: `Invalid task #${index + 1}: expected an object with task.` };
|
|
1705
|
+
return { error: `Invalid task #${index + 1}: expected an object with the objective in "task" (aliases "description"/"prompt"/"objective" accepted).` };
|
|
1702
1706
|
}
|
|
1703
|
-
|
|
1704
|
-
|
|
1707
|
+
const unknownFields = Object.keys(def).filter((key) => !allowedFields.includes(key));
|
|
1708
|
+
if (unknownFields.length > 0) {
|
|
1709
|
+
return { error: `Invalid task #${index + 1}: unknown field(s) ${unknownFields.map((key) => `"${key}"`).join(", ")}; allowed fields: ${allowedFields.map((key) => `"${key}"`).join(", ")}.` };
|
|
1705
1710
|
}
|
|
1706
|
-
const
|
|
1711
|
+
const taskValues = [
|
|
1712
|
+
...new Set(
|
|
1713
|
+
TASK_FIELD_ALIASES.map((key) => typeof def[key] === "string" ? def[key].trim() : "").filter((value) => value.length > 0)
|
|
1714
|
+
)
|
|
1715
|
+
];
|
|
1716
|
+
const task = taskValues.join("\n\n");
|
|
1707
1717
|
if (!task) {
|
|
1708
|
-
return { error: `Invalid task #${index + 1}: task
|
|
1718
|
+
return { error: `Invalid task #${index + 1}: provide the objective in "task" (aliases "description"/"prompt"/"objective" are accepted).` };
|
|
1709
1719
|
}
|
|
1710
1720
|
const timeoutSeconds = "timeout" in def ? Number(def.timeout) : DEFAULT_SUBAGENT_TIMEOUT_SECONDS;
|
|
1711
1721
|
if (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0) {
|
|
@@ -1721,25 +1731,61 @@ function normalizeTaskDefs(args) {
|
|
|
1721
1731
|
}
|
|
1722
1732
|
return normalized;
|
|
1723
1733
|
}
|
|
1734
|
+
var DEFAULT_MAX_CONCURRENT_SUBAGENTS = 4;
|
|
1735
|
+
var MAX_TRANSIENT_RETRIES = 2;
|
|
1736
|
+
var TRANSIENT_RETRY_BACKOFF_MS = 2e3;
|
|
1737
|
+
var TRANSIENT_ERROR_PATTERN = /econnreset|econnaborted|etimedout|timeout|enotfound|eai_again|econnrefused|socket hang up|sse stream aborted|fetch failed|status 5\d\d|bad gateway|service unavailable|overloaded/i;
|
|
1738
|
+
function isTransientNetworkError(message) {
|
|
1739
|
+
return TRANSIENT_ERROR_PATTERN.test(message);
|
|
1740
|
+
}
|
|
1741
|
+
function sleep(ms, signal) {
|
|
1742
|
+
return new Promise((resolve3) => {
|
|
1743
|
+
const timer = setTimeout(resolve3, ms);
|
|
1744
|
+
signal?.addEventListener(
|
|
1745
|
+
"abort",
|
|
1746
|
+
() => {
|
|
1747
|
+
clearTimeout(timer);
|
|
1748
|
+
resolve3();
|
|
1749
|
+
},
|
|
1750
|
+
{ once: true }
|
|
1751
|
+
);
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1754
|
+
async function runWithConcurrencyLimit(jobs, limit) {
|
|
1755
|
+
let next = 0;
|
|
1756
|
+
const workers = Array.from({ length: Math.max(1, Math.min(limit, jobs.length)) }, async () => {
|
|
1757
|
+
while (next < jobs.length) {
|
|
1758
|
+
const current = jobs[next++];
|
|
1759
|
+
await current();
|
|
1760
|
+
}
|
|
1761
|
+
});
|
|
1762
|
+
await Promise.all(workers);
|
|
1763
|
+
}
|
|
1724
1764
|
function createSubagentCapability(deps) {
|
|
1725
1765
|
const spawnParallelTool = {
|
|
1726
1766
|
name: "spawn_subagents_parallel",
|
|
1727
1767
|
label: "Spawn Subagents",
|
|
1728
1768
|
description: `Spawn child agents to run bounded sub-tasks in parallel.
|
|
1729
1769
|
|
|
1730
|
-
Use this for independent investigation, search, review, or triage work. Provide each task as a plain objective. Pie chooses the model class automatically. Use timeout only for unusually long tasks
|
|
1770
|
+
Use this for independent investigation, search, review, or triage work. Provide each task as a plain objective. Pie chooses the model class automatically. Use timeout only for unusually long tasks.
|
|
1771
|
+
|
|
1772
|
+
Arguments shape (each tasks[] item puts the objective in "task" \u2014 "description"/"prompt"/"objective" are accepted as aliases):
|
|
1773
|
+
{ "tasks": [{ "task": "<objective text>", "name": "<optional short label>", "priority": "fast" | "thorough", "timeout": 600 }] }`,
|
|
1731
1774
|
parameters: Type.Object({
|
|
1732
1775
|
tasks: Type.Array(
|
|
1733
1776
|
Type.Object(
|
|
1734
1777
|
{
|
|
1735
|
-
task: Type.String({ description: "The bounded sub-task objective. Be specific and include expected output." }),
|
|
1778
|
+
task: Type.Optional(Type.String({ description: "The bounded sub-task objective. Be specific and include expected output. Preferred field name." })),
|
|
1779
|
+
description: Type.Optional(Type.String({ description: "Alias for task." })),
|
|
1780
|
+
prompt: Type.Optional(Type.String({ description: "Alias for task." })),
|
|
1781
|
+
objective: Type.Optional(Type.String({ description: "Alias for task." })),
|
|
1736
1782
|
name: Type.Optional(Type.String({ description: "Short name for this subagent, shown in progress updates." })),
|
|
1737
1783
|
priority: Type.Optional(Type.Union([Type.Literal("fast"), Type.Literal("thorough")], { description: "fast uses lower-cost defaults; thorough raises model class when needed." })),
|
|
1738
|
-
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds. Default: 1200s (20 minutes). For longer tasks, explicitly set a higher value, e.g.
|
|
1784
|
+
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds. Default: 1200s (20 minutes). For longer tasks, explicitly set a higher value, e.g. 3600." }))
|
|
1739
1785
|
},
|
|
1740
|
-
{ additionalProperties: false }
|
|
1786
|
+
{ additionalProperties: false, description: "Objective goes in 'task' (or an accepted alias); plus optional name/priority/timeout." }
|
|
1741
1787
|
),
|
|
1742
|
-
{ description: "Array of tasks to run in parallel", minItems: 1, maxItems: 10 }
|
|
1788
|
+
{ description: "Array of tasks to run in parallel; at least one of task/description/prompt/objective must be a non-empty string per item.", minItems: 1, maxItems: 10 }
|
|
1743
1789
|
)
|
|
1744
1790
|
}, { additionalProperties: false }),
|
|
1745
1791
|
async execute(first, second, third, fourth) {
|
|
@@ -1782,7 +1828,30 @@ Use this for independent investigation, search, review, or triage work. Provide
|
|
|
1782
1828
|
toolContext.log?.(`Spawning ${tasks.length} parallel subagents: ${tasks.map((task) => task.name).join(", ")}`);
|
|
1783
1829
|
toolContext.reportProgress?.({ message: `Launching ${tasks.length} parallel subagents...`, increment: 5 });
|
|
1784
1830
|
const startTime = Date.now();
|
|
1785
|
-
|
|
1831
|
+
const runTask = async (task) => {
|
|
1832
|
+
for (let attempt = 0; ; attempt++) {
|
|
1833
|
+
await runSubagentWithProgress(deps, task, toolContext.reportProgress, signal);
|
|
1834
|
+
const canRetry = task.status === "failed" && isTransientNetworkError(task.error ?? "") && attempt < MAX_TRANSIENT_RETRIES && !signal?.aborted;
|
|
1835
|
+
if (!canRetry) return;
|
|
1836
|
+
const failureReason = task.error ?? "unknown";
|
|
1837
|
+
task.evidence.length = 0;
|
|
1838
|
+
task.runtimeEvidence.length = 0;
|
|
1839
|
+
task.reportedEvidence.length = 0;
|
|
1840
|
+
task.confidenceSignals.length = 0;
|
|
1841
|
+
task.status = "pending";
|
|
1842
|
+
task.error = void 0;
|
|
1843
|
+
task.completedAt = void 0;
|
|
1844
|
+
const backoffMs = (deps.transientRetryBackoffMs ?? TRANSIENT_RETRY_BACKOFF_MS) * (attempt + 1);
|
|
1845
|
+
toolContext.reportProgress?.({ message: `[${task.name}] Transient error, retrying in ${Math.round(backoffMs / 1e3)}s (attempt ${attempt + 2}/${MAX_TRANSIENT_RETRIES + 1})...` });
|
|
1846
|
+
deps.log(`[subagent] ${task.name} failed with a transient error (${failureReason}); retrying`);
|
|
1847
|
+
await sleep(backoffMs, signal);
|
|
1848
|
+
if (signal?.aborted) return;
|
|
1849
|
+
}
|
|
1850
|
+
};
|
|
1851
|
+
await runWithConcurrencyLimit(
|
|
1852
|
+
tasks.map((task) => () => runTask(task)),
|
|
1853
|
+
Math.max(1, deps.maxConcurrentSubagents ?? DEFAULT_MAX_CONCURRENT_SUBAGENTS)
|
|
1854
|
+
);
|
|
1786
1855
|
const totalDuration = Date.now() - startTime;
|
|
1787
1856
|
const completed = tasks.filter((task) => task.status === "completed").length;
|
|
1788
1857
|
const failed = tasks.filter((task) => task.status === "failed" || task.status === "timeout").length;
|
|
@@ -2582,14 +2651,14 @@ function isTransientFileError(error) {
|
|
|
2582
2651
|
const message = error instanceof Error ? error.message : String(error);
|
|
2583
2652
|
return /^(EPERM|EACCES|EBUSY|ENOTEMPTY)$/.test(code) || /\b(EPERM|EACCES|EBUSY|locked|busy)\b/i.test(message);
|
|
2584
2653
|
}
|
|
2585
|
-
function
|
|
2654
|
+
function sleep2(ms) {
|
|
2586
2655
|
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
2587
2656
|
}
|
|
2588
2657
|
async function retryFileOperation(operation, retryDelaysMs) {
|
|
2589
2658
|
let lastError;
|
|
2590
2659
|
for (const [index, delayMs] of retryDelaysMs.entries()) {
|
|
2591
2660
|
if (delayMs > 0) {
|
|
2592
|
-
await
|
|
2661
|
+
await sleep2(delayMs);
|
|
2593
2662
|
}
|
|
2594
2663
|
try {
|
|
2595
2664
|
operation();
|
|
@@ -3978,7 +4047,8 @@ var SessionManager = class {
|
|
|
3978
4047
|
name: newName ?? `${this.activeSession.metadata.name} (Branch)`,
|
|
3979
4048
|
createdAt: now,
|
|
3980
4049
|
updatedAt: now,
|
|
3981
|
-
tags: this.activeSession.metadata.tags
|
|
4050
|
+
tags: this.activeSession.metadata.tags,
|
|
4051
|
+
cwd: this.activeSession.metadata.cwd
|
|
3982
4052
|
}
|
|
3983
4053
|
);
|
|
3984
4054
|
return newSessionId;
|
|
@@ -4015,7 +4085,8 @@ var SessionManager = class {
|
|
|
4015
4085
|
createdAt: now,
|
|
4016
4086
|
updatedAt: now,
|
|
4017
4087
|
activeEntryId: newActiveEntryId,
|
|
4018
|
-
tags: this.activeSession.metadata.tags
|
|
4088
|
+
tags: this.activeSession.metadata.tags,
|
|
4089
|
+
cwd: this.activeSession.metadata.cwd
|
|
4019
4090
|
}
|
|
4020
4091
|
);
|
|
4021
4092
|
return newSessionId;
|
|
@@ -6185,7 +6256,10 @@ async function askSingleQuestion(args) {
|
|
|
6185
6256
|
if (response.type === "multiline_input" && (response.skipped || response.timedOut)) {
|
|
6186
6257
|
return buildSkippedResult(question, response.timedOut === true);
|
|
6187
6258
|
}
|
|
6188
|
-
const answer = response.type === "multiline_input" ? response.value : void 0;
|
|
6259
|
+
const answer = response.type === "multiline_input" ? response.value?.trim() : void 0;
|
|
6260
|
+
if (answer === "") {
|
|
6261
|
+
return buildSkippedResult(question, false);
|
|
6262
|
+
}
|
|
6189
6263
|
if (answer === void 0) {
|
|
6190
6264
|
return { content: [{ type: "text", text: "User cancelled" }], details: { cancelled: true } };
|
|
6191
6265
|
}
|
|
@@ -6203,7 +6277,12 @@ async function askSingleQuestion(args) {
|
|
|
6203
6277
|
id: `ask-user:select:${Date.now()}`,
|
|
6204
6278
|
prompt: question,
|
|
6205
6279
|
options: displayOptions,
|
|
6206
|
-
timeoutMs: DEFAULT_ASK_TIMEOUT_MS
|
|
6280
|
+
timeoutMs: DEFAULT_ASK_TIMEOUT_MS,
|
|
6281
|
+
// Tab-style navigation for hosts that support it: ← at the first page
|
|
6282
|
+
// selects Back, → at the last page selects Review (row presence mirrors
|
|
6283
|
+
// allowBack/allowReview). Hosts without shortcut support ignore these.
|
|
6284
|
+
leftShortcut: allowBack ? BACK_OPTION : void 0,
|
|
6285
|
+
rightShortcut: allowReview ? REVIEW_OPTION : void 0
|
|
6207
6286
|
});
|
|
6208
6287
|
if (selection.type === "select_one" && (selection.skipped || selection.timedOut)) {
|
|
6209
6288
|
return buildSkippedResult(question, selection.timedOut === true);
|
|
@@ -6228,7 +6307,10 @@ async function askSingleQuestion(args) {
|
|
|
6228
6307
|
if (response.type === "text_input" && (response.skipped || response.timedOut)) {
|
|
6229
6308
|
return buildSkippedResult(question, response.timedOut === true);
|
|
6230
6309
|
}
|
|
6231
|
-
const customAnswer = response.type === "text_input" ? response.value : void 0;
|
|
6310
|
+
const customAnswer = response.type === "text_input" ? response.value?.trim() : void 0;
|
|
6311
|
+
if (customAnswer === "") {
|
|
6312
|
+
return buildSkippedResult(question, false);
|
|
6313
|
+
}
|
|
6232
6314
|
if (customAnswer === void 0) {
|
|
6233
6315
|
return { content: [{ type: "text", text: "User cancelled" }], details: { cancelled: true } };
|
|
6234
6316
|
}
|
|
@@ -6308,8 +6390,11 @@ function createAskUserCapability() {
|
|
|
6308
6390
|
}
|
|
6309
6391
|
answers[q.id] = {
|
|
6310
6392
|
question: q.question,
|
|
6311
|
-
|
|
6312
|
-
|
|
6393
|
+
...result.details,
|
|
6394
|
+
// After the spread: `answer` wins over the same spread key, and
|
|
6395
|
+
// `??` preserves intentional values (`||` let "" re-clobber the
|
|
6396
|
+
// fallback — a latent trap for any falsy answer).
|
|
6397
|
+
answer: result.details?.answer ?? result.content[0].text
|
|
6313
6398
|
};
|
|
6314
6399
|
currentIndex++;
|
|
6315
6400
|
}
|
|
@@ -6318,7 +6403,7 @@ function createAskUserCapability() {
|
|
|
6318
6403
|
const reviewOptions = [
|
|
6319
6404
|
CONFIRM_OPTION,
|
|
6320
6405
|
...questions.map((q2, i) => {
|
|
6321
|
-
const answer = answers[q2.id]?.answer
|
|
6406
|
+
const answer = answers[q2.id]?.answer ?? "(not answered)";
|
|
6322
6407
|
const displayAnswer = answer.length > 20 ? `${answer.substring(0, 20)}...` : answer;
|
|
6323
6408
|
return `\u270F\uFE0F Q${i + 1}: ${displayAnswer}`;
|
|
6324
6409
|
}),
|
|
@@ -6372,8 +6457,8 @@ function createAskUserCapability() {
|
|
|
6372
6457
|
}
|
|
6373
6458
|
answers[q.id] = {
|
|
6374
6459
|
question: q.question,
|
|
6375
|
-
|
|
6376
|
-
|
|
6460
|
+
...result.details,
|
|
6461
|
+
answer: result.details?.answer ?? result.content[0].text
|
|
6377
6462
|
};
|
|
6378
6463
|
}
|
|
6379
6464
|
}
|
|
@@ -11582,6 +11667,13 @@ function getModelLabel(model) {
|
|
|
11582
11667
|
function textFromAssistant(message) {
|
|
11583
11668
|
return message.content.filter((block) => block.type === "text").map((block) => block.text).join("\n").trim();
|
|
11584
11669
|
}
|
|
11670
|
+
function classifyUnderstandingFailure(message) {
|
|
11671
|
+
if (/\b40[123]\b|payment required|insufficient[ _-]?balance|arrearage|余额|quota/i.test(message)) return "auth_or_balance";
|
|
11672
|
+
if (/\b429\b|rate.?limit|too many requests/i.test(message)) return "rate_limited";
|
|
11673
|
+
if (/timed? ?out|timeout|etimedout/i.test(message)) return "timeout";
|
|
11674
|
+
if (/does not support|unsupported|multimodal|modality|not declare/i.test(message)) return "unsupported";
|
|
11675
|
+
return "provider_error";
|
|
11676
|
+
}
|
|
11585
11677
|
function modalityForReadFileUnderstandingKind(kind) {
|
|
11586
11678
|
if (kind === "image") return "image";
|
|
11587
11679
|
if (kind === "video") return "video";
|
|
@@ -11614,83 +11706,147 @@ function createReadFileUnderstandingHandler(deps) {
|
|
|
11614
11706
|
}
|
|
11615
11707
|
async function understandFileWithModel(deps, request) {
|
|
11616
11708
|
const purpose = `read_file.${request.kind}`;
|
|
11617
|
-
const decision = deps.resolveToolModel(purpose);
|
|
11618
11709
|
const agentModel = deps.getAgentModel();
|
|
11619
|
-
const
|
|
11710
|
+
const singleDecision = deps.resolveToolModel(purpose);
|
|
11711
|
+
const candidateDecisions = deps.resolveToolModelCandidates?.(purpose) ?? [singleDecision];
|
|
11712
|
+
const sessionId = deps.getSessionId() || `read-file-${Date.now()}`;
|
|
11713
|
+
const attempts = [];
|
|
11714
|
+
let sendAttemptCount = 0;
|
|
11715
|
+
const baseRouteDetails = {
|
|
11620
11716
|
purpose,
|
|
11621
|
-
requiredCapability:
|
|
11622
|
-
routeSource: decision.routeSource,
|
|
11717
|
+
requiredCapability: singleDecision.requiredCapability,
|
|
11623
11718
|
agentModel: getModelLabel(agentModel),
|
|
11624
|
-
toolModel: getModelLabel(decision.model),
|
|
11625
|
-
fallbackReason: decision.fallbackReason,
|
|
11626
11719
|
kind: request.kind,
|
|
11627
11720
|
mimeType: request.mimeType
|
|
11628
11721
|
};
|
|
11629
|
-
|
|
11722
|
+
for (const decision of candidateDecisions) {
|
|
11723
|
+
const attemptBase = { toolModel: getModelLabel(decision.model), routeSource: decision.routeSource };
|
|
11724
|
+
if (!decision.model || !decision.apiKey) {
|
|
11725
|
+
attempts.push({
|
|
11726
|
+
...attemptBase,
|
|
11727
|
+
failureCategory: "unavailable",
|
|
11728
|
+
fallbackReason: decision.fallbackReason,
|
|
11729
|
+
durationMs: 0
|
|
11730
|
+
});
|
|
11731
|
+
continue;
|
|
11732
|
+
}
|
|
11733
|
+
const startedAt = Date.now();
|
|
11734
|
+
sendAttemptCount += 1;
|
|
11735
|
+
try {
|
|
11736
|
+
const directImageContent = buildDirectImageUnderstandingContent(request);
|
|
11737
|
+
const prepared = directImageContent ? { content: directImageContent, details: { inputMode: "inlineDataUrl" } } : await deps.prepareFileContent({
|
|
11738
|
+
request,
|
|
11739
|
+
model: decision.model,
|
|
11740
|
+
apiKey: decision.apiKey,
|
|
11741
|
+
sessionId
|
|
11742
|
+
});
|
|
11743
|
+
const send = deps.sendUnderstandingRequest ?? completeSimple;
|
|
11744
|
+
const response = await send(
|
|
11745
|
+
decision.model,
|
|
11746
|
+
{
|
|
11747
|
+
systemPrompt: "You are a file-understanding tool. Answer only from the provided file. Keep the answer concise and factual.",
|
|
11748
|
+
messages: [{ role: "user", content: [buildReadFileUnderstandingPrompt(request), ...prepared.content], timestamp: Date.now() }]
|
|
11749
|
+
},
|
|
11750
|
+
{
|
|
11751
|
+
apiKey: decision.apiKey,
|
|
11752
|
+
maxTokens: Math.min(decision.model.maxTokens || 4096, 4096),
|
|
11753
|
+
signal: request.signal
|
|
11754
|
+
}
|
|
11755
|
+
);
|
|
11756
|
+
const errorMessage = response.stopReason === "error" || response.stopReason === "aborted" ? response.errorMessage : void 0;
|
|
11757
|
+
const durationMs = Date.now() - startedAt;
|
|
11758
|
+
if (response.stopReason === "aborted") {
|
|
11759
|
+
attempts.push({ ...attemptBase, failureCategory: "aborted", error: errorMessage, durationMs });
|
|
11760
|
+
return {
|
|
11761
|
+
content: [{ type: "text", text: `read_file ${request.kind} understanding failed: ${errorMessage ?? "aborted"}` }],
|
|
11762
|
+
details: { understanding: { ...baseRouteDetails, ...attemptBase, error: errorMessage, attempts } },
|
|
11763
|
+
isError: true
|
|
11764
|
+
};
|
|
11765
|
+
}
|
|
11766
|
+
if (errorMessage) {
|
|
11767
|
+
attempts.push({
|
|
11768
|
+
...attemptBase,
|
|
11769
|
+
failureCategory: classifyUnderstandingFailure(errorMessage),
|
|
11770
|
+
error: errorMessage,
|
|
11771
|
+
durationMs
|
|
11772
|
+
});
|
|
11773
|
+
continue;
|
|
11774
|
+
}
|
|
11775
|
+
attempts.push({ ...attemptBase, durationMs });
|
|
11776
|
+
const text = textFromAssistant(response) || "(file understanding model returned no text)";
|
|
11777
|
+
return {
|
|
11778
|
+
content: [{ type: "text", text }],
|
|
11779
|
+
details: {
|
|
11780
|
+
understanding: {
|
|
11781
|
+
...baseRouteDetails,
|
|
11782
|
+
// Capability follows the candidate that actually served the request,
|
|
11783
|
+
// not the first decision's.
|
|
11784
|
+
requiredCapability: decision.requiredCapability,
|
|
11785
|
+
...attemptBase,
|
|
11786
|
+
fallbackReason: decision.fallbackReason,
|
|
11787
|
+
responseStopReason: response.stopReason,
|
|
11788
|
+
...prepared.details,
|
|
11789
|
+
attempts
|
|
11790
|
+
}
|
|
11791
|
+
},
|
|
11792
|
+
isError: false
|
|
11793
|
+
};
|
|
11794
|
+
} catch (error) {
|
|
11795
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11796
|
+
const durationMs = Date.now() - startedAt;
|
|
11797
|
+
if (request.signal?.aborted) {
|
|
11798
|
+
attempts.push({ ...attemptBase, failureCategory: "aborted", error: message, durationMs });
|
|
11799
|
+
return {
|
|
11800
|
+
content: [{ type: "text", text: `read_file ${request.kind} understanding failed: ${message}` }],
|
|
11801
|
+
details: { understanding: { ...baseRouteDetails, ...attemptBase, error: message, attempts } },
|
|
11802
|
+
isError: true
|
|
11803
|
+
};
|
|
11804
|
+
}
|
|
11805
|
+
attempts.push({
|
|
11806
|
+
...attemptBase,
|
|
11807
|
+
failureCategory: classifyUnderstandingFailure(message),
|
|
11808
|
+
error: message,
|
|
11809
|
+
durationMs
|
|
11810
|
+
});
|
|
11811
|
+
}
|
|
11812
|
+
}
|
|
11813
|
+
const routedAttempts = attempts.filter((attempt) => attempt.toolModel !== void 0);
|
|
11814
|
+
if (routedAttempts.length === 0 || sendAttemptCount === 0) {
|
|
11630
11815
|
return {
|
|
11631
11816
|
content: [{
|
|
11632
11817
|
type: "text",
|
|
11633
|
-
text:
|
|
11818
|
+
text: singleDecision.fallbackReason || `No configured model declares ${request.kind} capability for ${purpose}.`
|
|
11634
11819
|
}],
|
|
11635
|
-
details: { understanding: routeDetails },
|
|
11636
|
-
isError: true
|
|
11637
|
-
};
|
|
11638
|
-
}
|
|
11639
|
-
const sessionId = deps.getSessionId() || `read-file-${Date.now()}`;
|
|
11640
|
-
try {
|
|
11641
|
-
const directImageContent = buildDirectImageUnderstandingContent(request);
|
|
11642
|
-
const prepared = directImageContent ? { content: directImageContent, details: { inputMode: "inlineDataUrl" } } : await deps.prepareFileContent({
|
|
11643
|
-
request,
|
|
11644
|
-
model: decision.model,
|
|
11645
|
-
apiKey: decision.apiKey,
|
|
11646
|
-
sessionId
|
|
11647
|
-
});
|
|
11648
|
-
const fileContent = prepared.content;
|
|
11649
|
-
const content = [
|
|
11650
|
-
buildReadFileUnderstandingPrompt(request),
|
|
11651
|
-
...fileContent
|
|
11652
|
-
];
|
|
11653
|
-
const response = await completeSimple(
|
|
11654
|
-
decision.model,
|
|
11655
|
-
{
|
|
11656
|
-
systemPrompt: "You are a file-understanding tool. Answer only from the provided file. Keep the answer concise and factual.",
|
|
11657
|
-
messages: [{ role: "user", content, timestamp: Date.now() }]
|
|
11658
|
-
},
|
|
11659
|
-
{
|
|
11660
|
-
apiKey: decision.apiKey,
|
|
11661
|
-
maxTokens: Math.min(decision.model.maxTokens || 4096, 4096),
|
|
11662
|
-
signal: request.signal
|
|
11663
|
-
}
|
|
11664
|
-
);
|
|
11665
|
-
const errorMessage = response.stopReason === "error" || response.stopReason === "aborted" ? response.errorMessage : void 0;
|
|
11666
|
-
const text = textFromAssistant(response) || (errorMessage ? `read_file ${request.kind} understanding failed: ${errorMessage}` : "(file understanding model returned no text)");
|
|
11667
|
-
return {
|
|
11668
|
-
content: [{ type: "text", text }],
|
|
11669
11820
|
details: {
|
|
11670
11821
|
understanding: {
|
|
11671
|
-
...
|
|
11672
|
-
toolModel: getModelLabel(
|
|
11673
|
-
|
|
11674
|
-
|
|
11675
|
-
|
|
11676
|
-
}
|
|
11677
|
-
},
|
|
11678
|
-
isError: response.stopReason === "error" || response.stopReason === "aborted"
|
|
11679
|
-
};
|
|
11680
|
-
} catch (error) {
|
|
11681
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
11682
|
-
return {
|
|
11683
|
-
content: [{ type: "text", text: `read_file ${request.kind} understanding failed: ${message}` }],
|
|
11684
|
-
details: {
|
|
11685
|
-
understanding: {
|
|
11686
|
-
...routeDetails,
|
|
11687
|
-
toolModel: getModelLabel(decision.model),
|
|
11688
|
-
error: message
|
|
11822
|
+
...baseRouteDetails,
|
|
11823
|
+
toolModel: getModelLabel(singleDecision.model),
|
|
11824
|
+
routeSource: singleDecision.routeSource,
|
|
11825
|
+
fallbackReason: singleDecision.fallbackReason,
|
|
11826
|
+
attempts
|
|
11689
11827
|
}
|
|
11690
11828
|
},
|
|
11691
11829
|
isError: true
|
|
11692
11830
|
};
|
|
11693
11831
|
}
|
|
11832
|
+
const lastAttempt = routedAttempts[routedAttempts.length - 1];
|
|
11833
|
+
const viaSegments = attempts.filter((attempt) => attempt.error || attempt.fallbackReason).map((attempt) => `via ${attempt.toolModel ?? "unconfigured route"}: ${attempt.error ?? attempt.fallbackReason}`);
|
|
11834
|
+
const firstFailure = attempts.find((attempt) => attempt.error || attempt.fallbackReason);
|
|
11835
|
+
const combinedError = sendAttemptCount === 1 ? `read_file ${request.kind} understanding failed: ${firstFailure?.error ?? firstFailure?.fallbackReason ?? "unknown error"}` : `read_file ${request.kind} understanding failed after ${sendAttemptCount} route(s): ${viaSegments.join("; ")}`;
|
|
11836
|
+
return {
|
|
11837
|
+
content: [{ type: "text", text: combinedError }],
|
|
11838
|
+
details: {
|
|
11839
|
+
understanding: {
|
|
11840
|
+
...baseRouteDetails,
|
|
11841
|
+
toolModel: lastAttempt.toolModel,
|
|
11842
|
+
routeSource: lastAttempt.routeSource,
|
|
11843
|
+
fallbackReason: lastAttempt.fallbackReason ?? lastAttempt.error,
|
|
11844
|
+
error: combinedError,
|
|
11845
|
+
attempts
|
|
11846
|
+
}
|
|
11847
|
+
},
|
|
11848
|
+
isError: true
|
|
11849
|
+
};
|
|
11694
11850
|
}
|
|
11695
11851
|
|
|
11696
11852
|
// ../../packages/shared-headless-capabilities/src/session-trace.ts
|
|
@@ -13790,7 +13946,7 @@ function createSharedWebFetchTool(deps = {}) {
|
|
|
13790
13946
|
const extractionMethod = isHtml ? "html_readability" : isPdf ? "pdf_text" : "plain_text";
|
|
13791
13947
|
if (isHtml && looksJavaScriptHeavy(raw, text)) {
|
|
13792
13948
|
return {
|
|
13793
|
-
content: [{ type: "text", text: `web_fetch could not read meaningful content from ${url}; this page appears to require JavaScript. Use
|
|
13949
|
+
content: [{ type: "text", text: `web_fetch could not read meaningful content from ${url}; this page appears to require JavaScript. Use the chrome-devtools-axi skill (or computer-use browser tools) for browser-rendered content.` }],
|
|
13794
13950
|
details: {
|
|
13795
13951
|
url,
|
|
13796
13952
|
sourceUrl: url,
|