@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.
Files changed (51) hide show
  1. package/README.md +32 -15
  2. package/dist/builtin/extensions/ask-user/index.js +4 -3
  3. package/dist/builtin/extensions/ask-user/package.json +8 -7
  4. package/dist/builtin/extensions/changelog/package.json +9 -9
  5. package/dist/builtin/extensions/computer-use/index.js +5466 -0
  6. package/dist/builtin/extensions/computer-use/package.json +11 -0
  7. package/dist/builtin/extensions/computer-use/prebuilt/linux/arm64/linux-bridge +0 -0
  8. package/dist/builtin/extensions/computer-use/prebuilt/linux/x64/linux-bridge +0 -0
  9. package/dist/builtin/extensions/computer-use/prebuilt/macos/arm64/pie-computer-use +0 -0
  10. package/dist/builtin/extensions/computer-use/prebuilt/macos/universal/pie-computer-use.app/Contents/Info.plist +24 -0
  11. package/dist/builtin/extensions/computer-use/prebuilt/macos/universal/pie-computer-use.app/Contents/MacOS/pie-computer-use +0 -0
  12. package/dist/builtin/extensions/computer-use/prebuilt/macos/universal/pie-computer-use.app/Contents/_CodeSignature/CodeResources +115 -0
  13. package/dist/builtin/extensions/computer-use/prebuilt/macos/x64/pie-computer-use +0 -0
  14. package/dist/builtin/extensions/computer-use/prebuilt/windows/windows-bridge.exe +0 -0
  15. package/dist/builtin/extensions/computer-use/scripts/build-native.mjs +277 -0
  16. package/dist/builtin/extensions/computer-use/scripts/helper-path.mjs +48 -0
  17. package/dist/builtin/extensions/computer-use/scripts/setup-helper.mjs +549 -0
  18. package/dist/builtin/extensions/document-attachments/index.js +51 -0
  19. package/dist/builtin/extensions/document-attachments/package.json +9 -9
  20. package/dist/builtin/extensions/init/package.json +9 -9
  21. package/dist/builtin/extensions/mcp-adapter/index.js +25250 -0
  22. package/dist/builtin/extensions/mcp-adapter/package.json +11 -0
  23. package/dist/builtin/extensions/plan-mode/index.js +4 -3
  24. package/dist/builtin/extensions/plan-mode/package.json +9 -9
  25. package/dist/builtin/extensions/subagent/index.js +17 -20
  26. package/dist/builtin/extensions/subagent/package.json +16 -12
  27. package/dist/builtin/extensions/todo/index.js +4 -3
  28. package/dist/builtin/extensions/todo/package.json +9 -9
  29. package/dist/builtin/skills/chrome-devtools-axi/CHANGELOG.md +5 -0
  30. package/dist/builtin/skills/chrome-devtools-axi/README.md +17 -0
  31. package/dist/builtin/skills/chrome-devtools-axi/SKILL.md +31 -0
  32. package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-cli.js +77 -0
  33. package/dist/builtin/skills/chrome-devtools-axi/package.json +12 -0
  34. package/dist/builtin/skills/tui-use/README.md +7 -0
  35. package/dist/builtin/skills/tui-use/SKILL.md +58 -0
  36. package/dist/chunks/chunk-5353DEEC.js +130 -0
  37. package/dist/chunks/{chunk-XBWFDIAU.js → chunk-G2POWIA5.js} +129 -194
  38. package/dist/chunks/{chunk-K6ZOCJ4V.js → chunk-IM43UYVV.js} +861 -3376
  39. package/dist/chunks/{chunk-DZS34ACX.js → chunk-PMPD56N2.js} +243 -87
  40. package/dist/chunks/{chunk-FKAO25XH.js → chunk-QJH6YN2W.js} +1 -1
  41. package/dist/chunks/chunk-XXGYUYXI.js +2718 -0
  42. package/dist/chunks/{src-HYZFAW4V.js → src-QSP2OPHU.js} +3 -2
  43. package/dist/chunks/{test-stream-5T7E7C25.js → test-stream-CVGWFLSX.js} +2 -1
  44. package/dist/chunks/webapi-MBCKO7ZV.js +2481 -0
  45. package/dist/cli.js +186 -78
  46. package/package.json +5 -4
  47. package/dist/builtin/skills/browser-tools/CHANGELOG.md +0 -5
  48. package/dist/builtin/skills/browser-tools/README.md +0 -17
  49. package/dist/builtin/skills/browser-tools/SKILL.md +0 -43
  50. package/dist/builtin/skills/browser-tools/package.json +0 -12
  51. 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-FKAO25XH.js";
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-K6ZOCJ4V.js";
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
- if ("prompt" in def || "kind" in def || "max_turns" in def) {
1704
- return { error: "Invalid spawn_subagents_parallel arguments: use tasks[].task plus optional name/priority; prompt, kind, and max_turns are no longer supported." };
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 task = typeof def.task === "string" ? def.task.trim() : "";
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 cannot be empty.` };
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. { timeout: 3600 }." }))
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
- await Promise.all(tasks.map((task) => runSubagentWithProgress(deps, task, toolContext.reportProgress, signal)));
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 sleep(ms) {
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 sleep(delayMs);
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
- answer: result.details?.answer || result.content[0].text,
6312
- ...result.details
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 || "(not answered)";
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
- answer: result.details?.answer || result.content[0].text,
6376
- ...result.details
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 routeDetails = {
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: decision.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
- if (!decision.model || !decision.apiKey) {
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: decision.fallbackReason || `No configured model declares ${request.kind} capability for ${purpose}.`
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
- ...routeDetails,
11672
- toolModel: getModelLabel(decision.model),
11673
- responseStopReason: response.stopReason,
11674
- ...prepared.details,
11675
- ...errorMessage ? { error: errorMessage } : {}
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 Playwright browser tools for browser-rendered content.` }],
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,
@@ -3,7 +3,7 @@ import {
3
3
  EventStream,
4
4
  streamSimple,
5
5
  validateToolArguments
6
- } from "./chunk-K6ZOCJ4V.js";
6
+ } from "./chunk-IM43UYVV.js";
7
7
 
8
8
  // ../../packages/agent-core/src/agent-loop.ts
9
9
  function createLoopErrorMessage(error, config, signal) {