@cydm/pie 2.7.8 → 2.7.9

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.
@@ -2,7 +2,7 @@ import { createRequire as __createRequire } from "node:module"; const require =
2
2
  import {
3
3
  Agent,
4
4
  getToolCallerIdentity
5
- } from "./chunk-W5B3OXHQ.js";
5
+ } from "./chunk-5SP2H5QK.js";
6
6
  import {
7
7
  AssistantMessageEventStream,
8
8
  FileSystemGateway,
@@ -12,7 +12,7 @@ import {
12
12
  getPlatformConfig,
13
13
  hashText,
14
14
  streamSimple
15
- } from "./chunk-2FEDB5GR.js";
15
+ } from "./chunk-SMGBZQI5.js";
16
16
  import {
17
17
  Type
18
18
  } from "./chunk-XXGYUYXI.js";
@@ -899,6 +899,11 @@ function shouldPreserveExecutionStateForUserText(text) {
899
899
  return CONTINUE_PATTERNS.some((pattern) => pattern.test(trimmed));
900
900
  }
901
901
 
902
+ // ../../packages/shared-headless-capabilities/src/capability.ts
903
+ function defineSharedCapability(definition) {
904
+ return definition;
905
+ }
906
+
902
907
  // ../../packages/shared-headless-capabilities/src/tool-policy.ts
903
908
  var BUILTIN_TOOL_CAPABILITY_METADATA = {
904
909
  search_context: { riskClass: "read_only", concurrencySafe: true, permissionScope: "session", availableInPlanMode: true, allowedForSubagentByDefault: false, maxOutputChars: 2e4 },
@@ -971,13 +976,51 @@ function getSubagentToolPolicyDecision(tool) {
971
976
  }
972
977
  var DEFAULT_PLAN_MODE_TOOL_NAMES = Object.entries(BUILTIN_TOOL_CAPABILITY_METADATA).filter(([, metadata]) => metadata.availableInPlanMode).map(([name]) => name);
973
978
 
974
- // ../../packages/shared-headless-capabilities/src/capability.ts
975
- function defineSharedCapability(definition) {
976
- return definition;
979
+ // ../../packages/shared-headless-capabilities/src/plan.ts
980
+ var import_shell_quote = __toESM(require_shell_quote(), 1);
981
+
982
+ // ../../packages/shared-headless-capabilities/src/shell-resolution.ts
983
+ function defaultShellKindForPlatform(platform = process.platform) {
984
+ return platform === "win32" ? "powershell" : "bash";
985
+ }
986
+ function resolveRequestShellKind(shell, platform = process.platform) {
987
+ if (shell === "bash" || shell === "powershell" || shell === "cmd") return shell;
988
+ return defaultShellKindForPlatform(platform);
977
989
  }
978
990
 
979
991
  // ../../packages/shared-headless-capabilities/src/plan.ts
980
- var import_shell_quote = __toESM(require_shell_quote(), 1);
992
+ var PLAN_POWERSHELL_READ_COMMANDS = new Set([
993
+ "Get-Content",
994
+ "Get-ChildItem",
995
+ "Get-Item",
996
+ "Get-Location",
997
+ "Get-Date",
998
+ "Get-Command",
999
+ "Select-String",
1000
+ "Measure-Object",
1001
+ "Compare-Object",
1002
+ "Test-Path"
1003
+ ].map((name) => name.toLowerCase()));
1004
+ var PLAN_POWERSHELL_READ_ALIASES = /* @__PURE__ */ new Set(["cat", "ls", "pwd", "echo", "sort"]);
1005
+ var PLAN_POWERSHELL_SAFE_PARAMETERS = /^(?:-(?:Raw|TotalCount|Head|Tail|First|Last|Wait|LiteralPath|Path|Name|Pattern|Recurse|Filter|Include|Exclude|SimpleMatch|CaseSensitive|Quiet|Word|Line|Character|Encoding|ErrorAction|WarningAction))$/i;
1006
+ var PLAN_POWERSHELL_SEPARATORS = /[;\n\r\t]|[<>]{1,2}|\||&&|\|\|/;
1007
+ var PLAN_POWERSHELL_NON_ASCII = /[^\x20-\x7E]/;
1008
+ var PLAN_POWERSHELL_UNSAFE_TOKENS = /\$\(|`|%\{|[(){}\[\]]|\bsource\b|\.\s+[/~]|\b(bash|sh|zsh|pwsh|powershell|cmd|npm|npx|node|python3?|git)\b/i;
1009
+ function isPlanModeSafePowerShellCommand(command) {
1010
+ const trimmed = command.trim();
1011
+ if (!trimmed) return false;
1012
+ if (PLAN_POWERSHELL_NON_ASCII.test(command)) return false;
1013
+ if (PLAN_POWERSHELL_SEPARATORS.test(trimmed)) return false;
1014
+ if (PLAN_POWERSHELL_UNSAFE_TOKENS.test(trimmed)) return false;
1015
+ const tokens = trimmed.split(/\s+/).filter(Boolean);
1016
+ const head = tokens[0].toLowerCase();
1017
+ if (!head) return false;
1018
+ if (!PLAN_POWERSHELL_READ_COMMANDS.has(head) && !PLAN_POWERSHELL_READ_ALIASES.has(head)) return false;
1019
+ for (const token of tokens.slice(1)) {
1020
+ if (token.startsWith("-") && !PLAN_POWERSHELL_SAFE_PARAMETERS.test(token)) return false;
1021
+ }
1022
+ return true;
1023
+ }
981
1024
  var SIMPLE_READ_COMMANDS = /* @__PURE__ */ new Set(["pwd", "ls", "cat", "head", "tail", "wc", "grep", "rg", "which", "type", "echo", "printf", "cd", "true", "false", "tree", "jq", "stat", "file", "du", "df", "basename", "dirname", "date", "uname", "sort", "uniq", "cut", "column", "shasum", "printenv"]);
982
1025
  var GIT_READ_SUBCOMMANDS = /* @__PURE__ */ new Set(["status", "log", "diff", "show", "rev-parse", "ls-files", "ls-tree", "blame", "describe", "shortlog", "merge-base"]);
983
1026
  var UNSAFE_GIT_FLAGS = /^(?:-c|--config-env|--exec|--output|--ext-diff|--textconv)(?:=|$)/;
@@ -1095,7 +1138,21 @@ function isReadOnlySegment(words) {
1095
1138
  }
1096
1139
  if (program === "uniq") return isReadOnlyUniq(args);
1097
1140
  if (program === "date") {
1098
- return args.every((arg) => arg.startsWith("-") || arg.startsWith("+"));
1141
+ const safeFlags = /* @__PURE__ */ new Set(["-u", "--utc", "--universal", "--help", "--version", "--debug"]);
1142
+ const valueFlags = /* @__PURE__ */ new Set(["-d", "--date", "-f", "--file", "-r", "--reference"]);
1143
+ const valueLongFlags = /^(?:--date|--file|--reference|--iso-8601|--rfc-email|--rfc-2822|--rfc-3339)=/;
1144
+ for (let index = 0; index < args.length; index += 1) {
1145
+ const arg = args[index];
1146
+ if (arg.startsWith("+")) continue;
1147
+ if (safeFlags.has(arg) || valueLongFlags.test(arg)) continue;
1148
+ if (valueFlags.has(arg)) {
1149
+ if (index + 1 >= args.length) return false;
1150
+ index += 1;
1151
+ continue;
1152
+ }
1153
+ return false;
1154
+ }
1155
+ return true;
1099
1156
  }
1100
1157
  if (program === "jq") {
1101
1158
  return true;
@@ -1154,7 +1211,9 @@ ${inlineMatch[1]}`;
1154
1211
  }
1155
1212
  return items;
1156
1213
  }
1157
- function isPlanModeSafeCommand(command) {
1214
+ function isPlanModeSafeCommand(command, shell = "bash") {
1215
+ if (shell === "powershell") return isPlanModeSafePowerShellCommand(command);
1216
+ if (shell === "cmd") return false;
1158
1217
  const trimmed = command.trim();
1159
1218
  if (!trimmed || /[$`\n\r\0]/.test(trimmed)) return false;
1160
1219
  if (/\{[^{}]*\}/.test(trimmed) || /\{\s*,/.test(trimmed)) return false;
@@ -1994,14 +2053,6 @@ function projectHostContextBlock(block, registry) {
1994
2053
  };
1995
2054
  }
1996
2055
  }
1997
- function leadingHostContextBlock(message) {
1998
- if (message.role !== "user") return void 0;
1999
- const content = message.content;
2000
- if (!Array.isArray(content) || content.length === 0) return void 0;
2001
- const first = content[0];
2002
- if (!isHostContextBlock(first)) return void 0;
2003
- return first;
2004
- }
2005
2056
  function projectionSeparator(projectionText) {
2006
2057
  return projectionText.endsWith("\n") && !projectionText.endsWith("\n\n") ? "" : "\n\n";
2007
2058
  }
@@ -2009,23 +2060,40 @@ function projectHostEnvelopes(messages, registry, onProjection) {
2009
2060
  let changed = false;
2010
2061
  const next = [];
2011
2062
  for (const message of messages) {
2012
- const block = leadingHostContextBlock(message);
2013
- if (!block) {
2063
+ if (message.role !== "user") {
2014
2064
  next.push(message);
2015
2065
  continue;
2016
2066
  }
2017
- const outcome = projectHostContextBlock(block, registry);
2018
- onProjection?.(outcome);
2019
2067
  const content = message.content;
2020
- const rest = content.slice(1);
2021
- const separator = projectionSeparator(outcome.text);
2068
+ if (!Array.isArray(content)) {
2069
+ next.push(message);
2070
+ continue;
2071
+ }
2072
+ let blockCount = 0;
2073
+ while (blockCount < content.length && isHostContextBlock(content[blockCount])) blockCount += 1;
2074
+ if (blockCount === 0) {
2075
+ next.push(message);
2076
+ continue;
2077
+ }
2078
+ const outcomes = [];
2079
+ for (let i = 0; i < blockCount; i++) {
2080
+ const outcome = projectHostContextBlock(content[i], registry);
2081
+ outcomes.push(outcome);
2082
+ onProjection?.(outcome);
2083
+ }
2084
+ const rest = content.slice(blockCount);
2085
+ let text = outcomes[outcomes.length - 1].text;
2086
+ for (let i = outcomes.length - 2; i >= 0; i--) {
2087
+ text = `${outcomes[i].text}${projectionSeparator(outcomes[i].text)}${text}`;
2088
+ }
2089
+ const separator = projectionSeparator(outcomes[outcomes.length - 1].text);
2022
2090
  const first = rest[0];
2023
2091
  let projectedContent;
2024
2092
  if (first && first.type === "text" && typeof first.text === "string") {
2025
- const merged = first.text.trim().length > 0 ? `${outcome.text}${separator}${first.text}` : outcome.text;
2093
+ const merged = first.text.trim().length > 0 ? `${text}${separator}${first.text}` : text;
2026
2094
  projectedContent = [{ ...first, text: merged }, ...rest.slice(1)];
2027
2095
  } else {
2028
- projectedContent = [{ type: "text", text: outcome.text }, ...rest];
2096
+ projectedContent = [{ type: "text", text }, ...rest];
2029
2097
  }
2030
2098
  next.push({ ...message, content: projectedContent });
2031
2099
  changed = true;
@@ -8160,7 +8228,10 @@ function withSubagentRunContext(stream, hooks) {
8160
8228
  ${SUBAGENT_FINALIZATION_HINT}` } : context, options);
8161
8229
  };
8162
8230
  }
8163
- function isRetryableSubagentFailure(message) {
8231
+ function isRetryableSubagentFailureFrom(facts) {
8232
+ if (facts.retryable === false) return false;
8233
+ if (facts.replayClass === "stream_lost" || facts.replayClass === "stream_lost_partial") return true;
8234
+ const message = facts.errorMessage ?? "";
8164
8235
  const remoteClosure = /SSE stream (?:aborted|closed before end|ended before response completed)/i.test(message);
8165
8236
  const classificationText = message.replace(/econnaborted/ig, "").replace(/SSE stream aborted/ig, "remote stream closed");
8166
8237
  if (/\b(?:400|401|403|404|422|429)\b|quota|weekly|monthly|limit reached|exhausted|unauthorized|authentication|invalid|protocol|stopReason=length|token limit|abort/i.test(classificationText)) return false;
@@ -8178,6 +8249,13 @@ function cancellableBackoff(ms, signal) {
8178
8249
  if (signal?.aborted) finish();
8179
8250
  });
8180
8251
  }
8252
+ function factsOf(error) {
8253
+ if (error instanceof Error) {
8254
+ const carrier = error;
8255
+ return { errorMessage: error.message, retryable: carrier.retryable, replayClass: carrier.replayClass };
8256
+ }
8257
+ return { errorMessage: String(error) };
8258
+ }
8181
8259
  function withSubagentResponseRecovery(stream, backoffMs = 2e3, onRetry, onActivity, hooks) {
8182
8260
  return async (model, context, options) => {
8183
8261
  for (let attempt = 0; ; attempt++) {
@@ -8225,7 +8303,7 @@ function withSubagentResponseRecovery(stream, backoffMs = 2e3, onRetry, onActivi
8225
8303
  message = terminal;
8226
8304
  } catch (error) {
8227
8305
  finishUsage?.();
8228
- if (!options?.signal?.aborted && attempt < 2 && isRetryableSubagentFailure(error instanceof Error ? error.message : String(error))) {
8306
+ if (!options?.signal?.aborted && attempt < 2 && isRetryableSubagentFailureFrom(factsOf(error))) {
8229
8307
  try {
8230
8308
  onRetry?.();
8231
8309
  } catch {
@@ -8236,7 +8314,7 @@ function withSubagentResponseRecovery(stream, backoffMs = 2e3, onRetry, onActivi
8236
8314
  throw error;
8237
8315
  }
8238
8316
  if (options?.signal?.aborted) throw new Error("Subagent aborted by parent");
8239
- if (message.stopReason === "error" && attempt < 2 && isRetryableSubagentFailure(message.errorMessage ?? "")) {
8317
+ if (message.stopReason === "error" && attempt < 2 && isRetryableSubagentFailureFrom({ errorMessage: message.errorMessage, retryable: message.retryable, replayClass: message.replayClass })) {
8240
8318
  try {
8241
8319
  onRetry?.();
8242
8320
  } catch {
@@ -8385,8 +8463,16 @@ var PROTECTED_POSIX_PATHS = /* @__PURE__ */ new Set([
8385
8463
  "/Volumes"
8386
8464
  ]);
8387
8465
  var POWERSHELL_DESTRUCTIVE_PATTERNS = [
8388
- { pattern: /\bRemove-Item\b[^;\n|]*(?:-Recurse|-Force)/i, warning: "may recursively or forcefully remove files", commandClass: "filesystem_write" },
8389
- { pattern: /\bRemove-Item\b[^;\n|]*(?:HKLM:|HKCU:|Registry::)/i, warning: "may remove registry entries", commandClass: "infra_destructive" },
8466
+ // PowerShell resolves these aliases before execution. The classifier must
8467
+ // treat the aliases exactly like their canonical cmdlets; otherwise a
8468
+ // guarded shell can run `ri -Recurse -Force` without the confirmation that
8469
+ // `Remove-Item -Recurse -Force` receives.
8470
+ { pattern: /\b(?:Remove-Item|ri|rm|rmdir|del|erase|rd)\b[^;\n|]*(?:-Recurse|-Force)\b/i, warning: "may recursively or forcefully remove files", commandClass: "filesystem_write" },
8471
+ { pattern: /\b(?:Remove-Item|ri|rm|rmdir|del|erase|rd)\b[^;\n|]*(?:HKLM:|HKCU:|Registry::)/i, warning: "may remove registry entries", commandClass: "infra_destructive" },
8472
+ // These write aliases are not all destructive in the recursive-delete sense,
8473
+ // but they can overwrite or create arbitrary files. Keep them behind the
8474
+ // same explicit shell confirmation boundary as other PowerShell writes.
8475
+ { pattern: /\b(?:Set-Content|sc|set|Add-Content|ac|add|New-Item|ni|md|mkdir|Copy-Item|cpi|cp|copy|Move-Item|mi|mv|move|Rename-Item|rni|ren)\b/i, warning: "may write or overwrite files", commandClass: "filesystem_write" },
8390
8476
  { pattern: /\b(Set-ExecutionPolicy|Start-Process\b[^;\n|]*-Verb\s+RunAs)\b/i, warning: "may change execution policy or request elevation", commandClass: "privilege_escalation" },
8391
8477
  { pattern: /\b(Stop-Computer|Restart-Computer)\b/i, warning: "may stop or restart the machine", commandClass: "infra_destructive" },
8392
8478
  { pattern: /(^|[;|&\n])\s*(?:Invoke-Expression|iex)\b/i, warning: "evaluates a dynamically constructed string as code", commandClass: "privilege_escalation" },
@@ -8411,9 +8497,6 @@ function lastPipelineCommand(command) {
8411
8497
  const segments = command.split("|").map((part) => part.trim()).filter(Boolean);
8412
8498
  return segments[segments.length - 1] || command;
8413
8499
  }
8414
- function classifyBashCommand(command, options) {
8415
- return classifyShellCommand(command, "bash", options);
8416
- }
8417
8500
  function splitShellSegments(command) {
8418
8501
  const segments = [];
8419
8502
  let current = "";
@@ -8595,6 +8678,66 @@ function resolveStaticCwdTarget(target, cwd, root) {
8595
8678
  if (resolved !== normalizedRoot && !isStrictDescendant(resolved, normalizedRoot)) return void 0;
8596
8679
  return resolved;
8597
8680
  }
8681
+ var DYNAMIC_EXECUTION_WARNING = "may execute nested shell or interpreter code";
8682
+ function executableName(word) {
8683
+ const normalized = word.replace(/\\/g, "/").toLowerCase();
8684
+ const base = normalized.slice(normalized.lastIndexOf("/") + 1);
8685
+ return base.endsWith(".exe") ? base.slice(0, -4) : base;
8686
+ }
8687
+ function hasDynamicExecutionInvocation(words) {
8688
+ if (words.length === 0) return false;
8689
+ const program = executableName(words[0]);
8690
+ if (program === "env" || program === "command" || program === "exec") {
8691
+ let index = 1;
8692
+ while (index < words.length && (words[index].startsWith("-") || /^[A-Za-z_][A-Za-z0-9_]*=/.test(words[index]))) index += 1;
8693
+ return index < words.length && hasDynamicExecutionInvocation(words.slice(index));
8694
+ }
8695
+ if (["bash", "sh", "zsh", "dash", "ksh", "fish"].includes(program)) {
8696
+ return words.slice(1).some((word) => /^-[^-]*c$/i.test(word) || /^--command(?:=|$)/i.test(word));
8697
+ }
8698
+ if (["pwsh", "powershell"].includes(program)) {
8699
+ return words.slice(1).some((word) => /^-(?:c|command|encodedcommand|e|file)(?:=|$)/i.test(word));
8700
+ }
8701
+ if (program === "cmd") {
8702
+ return words.slice(1).some((word) => /^\/(?:c|k)$/i.test(word));
8703
+ }
8704
+ if (["python", "python3", "python2", "perl", "ruby"].includes(program)) {
8705
+ return words.slice(1).some((word) => /^(?:-c|-e|-m)$/i.test(word));
8706
+ }
8707
+ if (program === "node") {
8708
+ return words.slice(1).some((word) => /^(?:-e|--eval|-p|--print|-r|--require)$/i.test(word));
8709
+ }
8710
+ if (program === "find") {
8711
+ return words.slice(1).some((word) => /^-(?:exec|execdir|ok|okdir)$/.test(word));
8712
+ }
8713
+ if (program === "rg") {
8714
+ return words.slice(1).some((word) => /^--pre(?:=|$)|^--pre-glob(?:=|$)/.test(word));
8715
+ }
8716
+ if (program === "xargs") {
8717
+ const optionsWithValues = /* @__PURE__ */ new Set(["-a", "-d", "-E", "-I", "-L", "-n", "-P", "-s"]);
8718
+ let index = 1;
8719
+ while (index < words.length && words[index].startsWith("-")) {
8720
+ if (optionsWithValues.has(words[index])) index += 1;
8721
+ index += 1;
8722
+ }
8723
+ return index < words.length && hasDynamicExecutionInvocation(words.slice(index));
8724
+ }
8725
+ return false;
8726
+ }
8727
+ function dynamicExecutionWarnings(command) {
8728
+ const warnings = [];
8729
+ for (const segment of splitShellSegments(command)) {
8730
+ const words = splitShellWords(segment);
8731
+ if (words) {
8732
+ if (hasDynamicExecutionInvocation(words)) warnings.push(DYNAMIC_EXECUTION_WARNING);
8733
+ continue;
8734
+ }
8735
+ if (/^(?:[A-Za-z]:[\\/]|[./])?(?:bash|sh|zsh|dash|ksh|fish|pwsh|powershell|cmd|python\d*|node|perl|ruby)\b[^|;&\n]*(?:\s-c(?:\s|$)|\s--?(?:command|encodedcommand|eval|execute|file)(?:[=\s]|$)|\s\/(?:c|k)(?:\s|$))/i.test(segment.trim())) {
8736
+ warnings.push(DYNAMIC_EXECUTION_WARNING);
8737
+ }
8738
+ }
8739
+ return [...new Set(warnings)];
8740
+ }
8598
8741
  function isStaticallyScopedRecursiveForceRemove(command, options) {
8599
8742
  if (!options?.cwd) return false;
8600
8743
  const root = options.sandboxRoot || options.cwd;
@@ -8636,6 +8779,12 @@ function classifyShellCommand(command, shell = "bash", options) {
8636
8779
  destructive = true;
8637
8780
  }
8638
8781
  }
8782
+ const dynamicWarnings = dynamicExecutionWarnings(command);
8783
+ if (dynamicWarnings.length > 0) {
8784
+ warnings.push(...dynamicWarnings);
8785
+ commandClass = "unknown_shell";
8786
+ destructive = true;
8787
+ }
8639
8788
  for (const entry of destructivePatterns) {
8640
8789
  if (entry.pattern.test(command)) {
8641
8790
  warnings.push(entry.warning);
@@ -8811,7 +8960,8 @@ function wrapBashGuard(tool, plan) {
8811
8960
  Subagent guarded mode blocks destructive shell commands while preserving the parent's execution checks. Destructive commands are blocked; normal diagnostic, search, test, and build commands are allowed.`,
8812
8961
  execute: async (toolCallId, params, signal, onUpdate) => {
8813
8962
  const command = typeof params?.command === "string" ? params.command : "";
8814
- const classification = classifyBashCommand(command);
8963
+ const shell = resolveRequestShellKind(params?.shell);
8964
+ const classification = classifyShellCommand(command, shell);
8815
8965
  if (classification.destructive) {
8816
8966
  const reason = `destructive shell command blocked for subagent: ${classification.riskWarnings.join("; ")}`;
8817
8967
  plan.toolsExcluded.push({ name: tool.name, reason });
@@ -9427,7 +9577,11 @@ ${DELEGATION_REPORT_INSTRUCTION}`;
9427
9577
  throw new Error(`Subagent output token limit reached (stopReason=length): the provider cut the final response before the report was produced${messages.some(messageHasOwnText) ? " (earlier-turn narration discarded)" : ", no text response"}. The output/thinking budget was too small for this task at its reasoning level. Rerun with a narrower task or a lower thinkingLevel.`);
9428
9578
  }
9429
9579
  const detail = terminalError ? `: ${terminalError.slice(0, 200)}` : "";
9430
- throw new Error(`Subagent response failed (provider/transport failure before the report was produced, stopReason=${terminalStopReason ?? "none"}${collectedResponse ? ", partial transcript discarded" : ", no text response"})${detail}`);
9580
+ const failure = new Error(`Subagent response failed (provider/transport failure before the report was produced, stopReason=${terminalStopReason ?? "none"}${collectedResponse ? ", partial transcript discarded" : ", no text response"})${detail}`);
9581
+ const carrier = failure;
9582
+ carrier.retryable = lastAssistant?.retryable;
9583
+ carrier.replayClass = lastAssistant?.replayClass;
9584
+ throw failure;
9431
9585
  }
9432
9586
  let response;
9433
9587
  if (!collectedResponse) {
@@ -9445,6 +9599,7 @@ ${DELEGATION_REPORT_INSTRUCTION}`;
9445
9599
  }
9446
9600
  const durationMs = Date.now() - startTime;
9447
9601
  task.status = "completed";
9602
+ task.failure = void 0;
9448
9603
  observeDelegationPhase(task.observation, "completed");
9449
9604
  task.result = response;
9450
9605
  task.details = {
@@ -9471,6 +9626,12 @@ ${DELEGATION_REPORT_INSTRUCTION}`;
9471
9626
  observeDelegationPhase(task.observation, timedOut ? "deadline" : signal?.aborted ? "cancelled" : "failed");
9472
9627
  phaseProgress(task.observation.phase);
9473
9628
  task.error = errorMessage.slice(0, 2e3);
9629
+ const failureCarrier = error;
9630
+ if (failureCarrier && (failureCarrier.retryable !== void 0 || failureCarrier.replayClass !== void 0)) {
9631
+ task.failure = { ...failureCarrier.retryable !== void 0 ? { retryable: failureCarrier.retryable } : {}, ...failureCarrier.replayClass !== void 0 ? { replayClass: failureCarrier.replayClass } : {} };
9632
+ } else {
9633
+ task.failure = void 0;
9634
+ }
9474
9635
  task.report ??= parseDelegationReport(void 0, task.id);
9475
9636
  task.parentHandoffRequired = isParentHandoffRequired(task);
9476
9637
  task.completedAt = Date.now();
@@ -9807,7 +9968,7 @@ ${SPAWN_USAGE_HINT}` }],
9807
9968
  await run(task, toolContext.reportProgress, signal);
9808
9969
  if (task.wasRecall || task.mode !== "read" || task.status !== "failed" || !task.error) return;
9809
9970
  if (signal?.aborted) return;
9810
- if (!isRetryableSubagentFailure(task.error)) return;
9971
+ if (!isRetryableSubagentFailureFrom({ errorMessage: task.error, retryable: task.failure?.retryable, replayClass: task.failure?.replayClass })) return;
9811
9972
  observeDelegationPhase(task.observation, "task_replay");
9812
9973
  const settleMs = Math.max(0, (deps.transientRetryBackoffMs ?? 2e3) * 10);
9813
9974
  toolContext.reportProgress?.({ message: `[${task.name}] Provider/transport failure \u2014 replaying the read task once after ${Math.round(settleMs / 1e3)}s`, increment: 0 });
@@ -9815,6 +9976,7 @@ ${SPAWN_USAGE_HINT}` }],
9815
9976
  if (signal.aborted) {
9816
9977
  task.status = "cancelled";
9817
9978
  task.error = "Subagent cancelled during replay backoff";
9979
+ task.failure = void 0;
9818
9980
  observeDelegationPhase(task.observation, "cancelled");
9819
9981
  task.completedAt = Date.now();
9820
9982
  return;
@@ -9824,6 +9986,7 @@ ${SPAWN_USAGE_HINT}` }],
9824
9986
  task.runtimeEvidence = [];
9825
9987
  task.reportedEvidence = [];
9826
9988
  task.error = void 0;
9989
+ task.failure = void 0;
9827
9990
  task.report = void 0;
9828
9991
  task.result = void 0;
9829
9992
  task.details = void 0;
@@ -9877,6 +10040,10 @@ ${SPAWN_USAGE_HINT}` }],
9877
10040
  observation: task.observation,
9878
10041
  parentHandoffRequired: task.parentHandoffRequired,
9879
10042
  failureReason: task.error,
10043
+ // Structured provider verdict so the parent model can distinguish
10044
+ // stream loss (safe to re-dispatch; no tool calls executed) from hard
10045
+ // provider vetoes (auth/quota/config — not recoverable here).
10046
+ failure: task.failure,
9880
10047
  details: task.details
9881
10048
  })),
9882
10049
  totalDuration,
@@ -10534,6 +10701,164 @@ function createAskUserCapability() {
10534
10701
  };
10535
10702
  }
10536
10703
 
10704
+ // ../../packages/shared-headless-capabilities/src/policy-enforcer.ts
10705
+ var POLICY_ENFORCED = /* @__PURE__ */ Symbol.for("pie.capabilityPolicy.enforced");
10706
+ function evaluateToolPermission(tool, context = {}) {
10707
+ const mode = context.mode ?? "normal";
10708
+ const metadata = getToolCapabilityMetadata(tool);
10709
+ const base = describeToolPolicyDecision(tool, mode);
10710
+ if (!metadata && mode === "normal") {
10711
+ return {
10712
+ disposition: "allow",
10713
+ allowed: true,
10714
+ reason: "unclassified_tool",
10715
+ policyReason: "unclassified_tool",
10716
+ mode,
10717
+ yoloMode: context.yoloMode
10718
+ };
10719
+ }
10720
+ if (!metadata || !base.allowed) {
10721
+ return {
10722
+ disposition: "deny",
10723
+ allowed: false,
10724
+ reason: base.reason ?? "tool not allowed by runtime policy",
10725
+ policyReason: base.reason ?? "tool not allowed by runtime policy",
10726
+ mode,
10727
+ yoloMode: context.yoloMode,
10728
+ riskClass: metadata?.riskClass,
10729
+ permissionScope: metadata?.permissionScope,
10730
+ requiresPermission: metadata?.requiresPermission
10731
+ };
10732
+ }
10733
+ const requiresConfirmation = Boolean(metadata.highRisk || metadata.requiresPermission);
10734
+ if (!requiresConfirmation || context.yoloMode || context.confirmationPolicy === "allow") {
10735
+ return {
10736
+ disposition: "allow",
10737
+ allowed: true,
10738
+ mode,
10739
+ yoloMode: context.yoloMode,
10740
+ riskClass: metadata.riskClass,
10741
+ permissionScope: metadata.permissionScope,
10742
+ requiresPermission: requiresConfirmation
10743
+ };
10744
+ }
10745
+ if (context.confirmationPolicy === "deny" || mode === "subagent") {
10746
+ const reason = mode === "subagent" ? "subagent cannot request interactive permission" : "permission confirmation is unavailable";
10747
+ return {
10748
+ disposition: "deny",
10749
+ allowed: false,
10750
+ reason,
10751
+ policyReason: reason,
10752
+ mode,
10753
+ yoloMode: context.yoloMode,
10754
+ riskClass: metadata.riskClass,
10755
+ permissionScope: metadata.permissionScope,
10756
+ requiresPermission: true
10757
+ };
10758
+ }
10759
+ return {
10760
+ disposition: "confirm",
10761
+ allowed: false,
10762
+ reason: "permission confirmation required",
10763
+ policyReason: "permission confirmation required",
10764
+ mode,
10765
+ yoloMode: context.yoloMode,
10766
+ riskClass: metadata.riskClass,
10767
+ permissionScope: metadata.permissionScope,
10768
+ requiresPermission: true
10769
+ };
10770
+ }
10771
+ function blockedResult(toolName, decision) {
10772
+ return {
10773
+ content: [
10774
+ {
10775
+ type: "text",
10776
+ text: `Blocked ${toolName}: ${decision.reason ?? decision.policyReason ?? "permission denied"}`
10777
+ }
10778
+ ],
10779
+ details: { permissionDecision: decision },
10780
+ isError: true
10781
+ };
10782
+ }
10783
+ function planModeShellCommandBlocked(target, args) {
10784
+ const metadata = getToolCapabilityMetadata(target);
10785
+ if (!metadata?.executesShell) return void 0;
10786
+ const command = args?.command;
10787
+ if (typeof command !== "string") {
10788
+ return `Plan mode: shell tool received no command to validate. Use plan_scratch for temporary drafts, submit the finished plan with plan_submit, or toggle Plan mode off (/tools/plan) to execute.`;
10789
+ }
10790
+ const shell = resolveRequestShellKind(args?.shell);
10791
+ if (isPlanModeSafeCommand(command, shell)) return void 0;
10792
+ return `Plan mode: command "${command}" is not a read-only ${shell} command. Use plan_scratch for temporary drafts, submit the finished plan with plan_submit, or toggle Plan mode off (/tools/plan) to execute.`;
10793
+ }
10794
+ function mergePermissionDetails(details, decision) {
10795
+ if (details && typeof details === "object" && !Array.isArray(details)) {
10796
+ const record = details;
10797
+ if (record.permissionDecision) {
10798
+ return { ...record, capabilityPolicyDecision: decision };
10799
+ }
10800
+ return { ...record, permissionDecision: decision };
10801
+ }
10802
+ return { permissionDecision: decision };
10803
+ }
10804
+ function createPolicyEnforcedTool(tool, options = {}) {
10805
+ const marker = tool[POLICY_ENFORCED];
10806
+ const intactWrapper = !!marker && marker.wrapperExecute === tool.execute;
10807
+ if (intactWrapper && marker.getContext === options.getContext && marker.permissionProvider === options.permissionProvider) {
10808
+ return tool;
10809
+ }
10810
+ const target = intactWrapper ? marker.base : tool;
10811
+ const wrapped = {
10812
+ ...target,
10813
+ execute: async (toolCallId, args, signal, onUpdate) => {
10814
+ let decision = evaluateToolPermission(target, options.getContext?.());
10815
+ if (decision.disposition === "confirm") {
10816
+ const approved = await options.permissionProvider?.confirm({ toolName: target.name, args, decision }).catch(() => false);
10817
+ if (!approved) {
10818
+ return blockedResult(target.name, {
10819
+ ...decision,
10820
+ disposition: "deny",
10821
+ reason: "permission denied by user",
10822
+ policyReason: "permission denied by user"
10823
+ });
10824
+ }
10825
+ decision = {
10826
+ ...decision,
10827
+ disposition: "allow",
10828
+ allowed: true,
10829
+ reason: void 0,
10830
+ policyReason: void 0
10831
+ };
10832
+ }
10833
+ if (!decision.allowed) return blockedResult(target.name, decision);
10834
+ const planShellReason = (options.getContext?.().mode ?? "normal") === "plan" ? planModeShellCommandBlocked(target, args) : void 0;
10835
+ if (planShellReason) {
10836
+ return blockedResult(target.name, {
10837
+ ...decision,
10838
+ allowed: false,
10839
+ reason: planShellReason,
10840
+ policyReason: "plan_mode_non_read_only_shell_command"
10841
+ });
10842
+ }
10843
+ const result = await target.execute(toolCallId, args, signal, onUpdate);
10844
+ return {
10845
+ ...result,
10846
+ details: mergePermissionDetails(result.details, decision)
10847
+ };
10848
+ }
10849
+ };
10850
+ wrapped[POLICY_ENFORCED] = {
10851
+ getContext: options.getContext,
10852
+ permissionProvider: options.permissionProvider,
10853
+ wrapperExecute: wrapped.execute,
10854
+ base: target
10855
+ };
10856
+ return wrapped;
10857
+ }
10858
+ function createPolicyEnforcedTools(tools, options = {}) {
10859
+ return tools.map((tool) => createPolicyEnforcedTool(tool, options));
10860
+ }
10861
+
10537
10862
  // ../../packages/shared-headless-capabilities/src/builtin/fs/read.ts
10538
10863
  import * as nodePath from "node:path";
10539
10864
 
@@ -16937,144 +17262,6 @@ function maybeAdvanceTodoExecutionState(state, signal) {
16937
17262
  };
16938
17263
  }
16939
17264
 
16940
- // ../../packages/shared-headless-capabilities/src/policy-enforcer.ts
16941
- var POLICY_ENFORCED = /* @__PURE__ */ Symbol.for("pie.capabilityPolicy.enforced");
16942
- function evaluateToolPermission(tool, context = {}) {
16943
- const mode = context.mode ?? "normal";
16944
- const metadata = getToolCapabilityMetadata(tool);
16945
- const base = describeToolPolicyDecision(tool, mode);
16946
- if (!metadata && mode === "normal") {
16947
- return {
16948
- disposition: "allow",
16949
- allowed: true,
16950
- reason: "unclassified_tool",
16951
- policyReason: "unclassified_tool",
16952
- mode,
16953
- yoloMode: context.yoloMode
16954
- };
16955
- }
16956
- if (!metadata || !base.allowed) {
16957
- return {
16958
- disposition: "deny",
16959
- allowed: false,
16960
- reason: base.reason ?? "tool not allowed by runtime policy",
16961
- policyReason: base.reason ?? "tool not allowed by runtime policy",
16962
- mode,
16963
- yoloMode: context.yoloMode,
16964
- riskClass: metadata?.riskClass,
16965
- permissionScope: metadata?.permissionScope,
16966
- requiresPermission: metadata?.requiresPermission
16967
- };
16968
- }
16969
- const requiresConfirmation = Boolean(metadata.highRisk || metadata.requiresPermission);
16970
- if (!requiresConfirmation || context.yoloMode || context.confirmationPolicy === "allow") {
16971
- return {
16972
- disposition: "allow",
16973
- allowed: true,
16974
- mode,
16975
- yoloMode: context.yoloMode,
16976
- riskClass: metadata.riskClass,
16977
- permissionScope: metadata.permissionScope,
16978
- requiresPermission: requiresConfirmation
16979
- };
16980
- }
16981
- if (context.confirmationPolicy === "deny" || mode === "subagent") {
16982
- const reason = mode === "subagent" ? "subagent cannot request interactive permission" : "permission confirmation is unavailable";
16983
- return {
16984
- disposition: "deny",
16985
- allowed: false,
16986
- reason,
16987
- policyReason: reason,
16988
- mode,
16989
- yoloMode: context.yoloMode,
16990
- riskClass: metadata.riskClass,
16991
- permissionScope: metadata.permissionScope,
16992
- requiresPermission: true
16993
- };
16994
- }
16995
- return {
16996
- disposition: "confirm",
16997
- allowed: false,
16998
- reason: "permission confirmation required",
16999
- policyReason: "permission confirmation required",
17000
- mode,
17001
- yoloMode: context.yoloMode,
17002
- riskClass: metadata.riskClass,
17003
- permissionScope: metadata.permissionScope,
17004
- requiresPermission: true
17005
- };
17006
- }
17007
- function blockedResult(toolName, decision) {
17008
- return {
17009
- content: [
17010
- {
17011
- type: "text",
17012
- text: `Blocked ${toolName}: ${decision.reason ?? decision.policyReason ?? "permission denied"}`
17013
- }
17014
- ],
17015
- details: { permissionDecision: decision },
17016
- isError: true
17017
- };
17018
- }
17019
- function mergePermissionDetails(details, decision) {
17020
- if (details && typeof details === "object" && !Array.isArray(details)) {
17021
- const record = details;
17022
- if (record.permissionDecision) {
17023
- return { ...record, capabilityPolicyDecision: decision };
17024
- }
17025
- return { ...record, permissionDecision: decision };
17026
- }
17027
- return { permissionDecision: decision };
17028
- }
17029
- function createPolicyEnforcedTool(tool, options = {}) {
17030
- const marker = tool[POLICY_ENFORCED];
17031
- const intactWrapper = !!marker && marker.wrapperExecute === tool.execute;
17032
- if (intactWrapper && marker.getContext === options.getContext && marker.permissionProvider === options.permissionProvider) {
17033
- return tool;
17034
- }
17035
- const target = intactWrapper ? marker.base : tool;
17036
- const wrapped = {
17037
- ...target,
17038
- execute: async (toolCallId, args, signal, onUpdate) => {
17039
- let decision = evaluateToolPermission(target, options.getContext?.());
17040
- if (decision.disposition === "confirm") {
17041
- const approved = await options.permissionProvider?.confirm({ toolName: target.name, args, decision }).catch(() => false);
17042
- if (!approved) {
17043
- return blockedResult(target.name, {
17044
- ...decision,
17045
- disposition: "deny",
17046
- reason: "permission denied by user",
17047
- policyReason: "permission denied by user"
17048
- });
17049
- }
17050
- decision = {
17051
- ...decision,
17052
- disposition: "allow",
17053
- allowed: true,
17054
- reason: void 0,
17055
- policyReason: void 0
17056
- };
17057
- }
17058
- if (!decision.allowed) return blockedResult(target.name, decision);
17059
- const result = await target.execute(toolCallId, args, signal, onUpdate);
17060
- return {
17061
- ...result,
17062
- details: mergePermissionDetails(result.details, decision)
17063
- };
17064
- }
17065
- };
17066
- wrapped[POLICY_ENFORCED] = {
17067
- getContext: options.getContext,
17068
- permissionProvider: options.permissionProvider,
17069
- wrapperExecute: wrapped.execute,
17070
- base: target
17071
- };
17072
- return wrapped;
17073
- }
17074
- function createPolicyEnforcedTools(tools, options = {}) {
17075
- return tools.map((tool) => createPolicyEnforcedTool(tool, options));
17076
- }
17077
-
17078
17265
  // ../../packages/shared-headless-capabilities/src/system-prompt-tools.ts
17079
17266
  function normalizeDescription(description) {
17080
17267
  const text = String(description || "").replace(/\s+/g, " ").trim();
@@ -19494,10 +19681,9 @@ export {
19494
19681
  supersedeExecutionState,
19495
19682
  shouldPreserveExecutionStateForUserText,
19496
19683
  BUILTIN_TOOL_CAPABILITY_METADATA,
19497
- getToolCapabilityMetadata,
19498
19684
  isToolAllowedInPlanMode,
19685
+ defaultShellKindForPlatform,
19499
19686
  extractPlanTodoItems,
19500
- isPlanModeSafeCommand,
19501
19687
  markCompletedPlanSteps,
19502
19688
  createPlanCapability,
19503
19689
  modalityForReadFileUnderstandingKind,