@cydm/pie 2.7.8 → 2.7.10

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-JO6EDWHP.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-2B6VS2NH.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;
@@ -2818,6 +2886,7 @@ var FileSessionStore = class {
2818
2886
  const heads = [];
2819
2887
  for (const name of this.gateway.readdir(this.sessionsDir)) {
2820
2888
  if (name.startsWith(STAGING_SESSION_PREFIX)) continue;
2889
+ if (!name.trim()) continue;
2821
2890
  const dirPath = this.gateway.join(this.sessionsDir, name);
2822
2891
  if (sessionDirIsSymlink(dirPath)) {
2823
2892
  console.warn(`[FileSessionStore] Skipping session directory that is a symbolic link: ${dirPath}`);
@@ -8160,7 +8229,10 @@ function withSubagentRunContext(stream, hooks) {
8160
8229
  ${SUBAGENT_FINALIZATION_HINT}` } : context, options);
8161
8230
  };
8162
8231
  }
8163
- function isRetryableSubagentFailure(message) {
8232
+ function isRetryableSubagentFailureFrom(facts) {
8233
+ if (facts.retryable === false) return false;
8234
+ if (facts.replayClass === "stream_lost" || facts.replayClass === "stream_lost_partial") return true;
8235
+ const message = facts.errorMessage ?? "";
8164
8236
  const remoteClosure = /SSE stream (?:aborted|closed before end|ended before response completed)/i.test(message);
8165
8237
  const classificationText = message.replace(/econnaborted/ig, "").replace(/SSE stream aborted/ig, "remote stream closed");
8166
8238
  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 +8250,13 @@ function cancellableBackoff(ms, signal) {
8178
8250
  if (signal?.aborted) finish();
8179
8251
  });
8180
8252
  }
8253
+ function factsOf(error) {
8254
+ if (error instanceof Error) {
8255
+ const carrier = error;
8256
+ return { errorMessage: error.message, retryable: carrier.retryable, replayClass: carrier.replayClass };
8257
+ }
8258
+ return { errorMessage: String(error) };
8259
+ }
8181
8260
  function withSubagentResponseRecovery(stream, backoffMs = 2e3, onRetry, onActivity, hooks) {
8182
8261
  return async (model, context, options) => {
8183
8262
  for (let attempt = 0; ; attempt++) {
@@ -8225,7 +8304,7 @@ function withSubagentResponseRecovery(stream, backoffMs = 2e3, onRetry, onActivi
8225
8304
  message = terminal;
8226
8305
  } catch (error) {
8227
8306
  finishUsage?.();
8228
- if (!options?.signal?.aborted && attempt < 2 && isRetryableSubagentFailure(error instanceof Error ? error.message : String(error))) {
8307
+ if (!options?.signal?.aborted && attempt < 2 && isRetryableSubagentFailureFrom(factsOf(error))) {
8229
8308
  try {
8230
8309
  onRetry?.();
8231
8310
  } catch {
@@ -8236,7 +8315,7 @@ function withSubagentResponseRecovery(stream, backoffMs = 2e3, onRetry, onActivi
8236
8315
  throw error;
8237
8316
  }
8238
8317
  if (options?.signal?.aborted) throw new Error("Subagent aborted by parent");
8239
- if (message.stopReason === "error" && attempt < 2 && isRetryableSubagentFailure(message.errorMessage ?? "")) {
8318
+ if (message.stopReason === "error" && attempt < 2 && isRetryableSubagentFailureFrom({ errorMessage: message.errorMessage, retryable: message.retryable, replayClass: message.replayClass })) {
8240
8319
  try {
8241
8320
  onRetry?.();
8242
8321
  } catch {
@@ -8385,8 +8464,16 @@ var PROTECTED_POSIX_PATHS = /* @__PURE__ */ new Set([
8385
8464
  "/Volumes"
8386
8465
  ]);
8387
8466
  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" },
8467
+ // PowerShell resolves these aliases before execution. The classifier must
8468
+ // treat the aliases exactly like their canonical cmdlets; otherwise a
8469
+ // guarded shell can run `ri -Recurse -Force` without the confirmation that
8470
+ // `Remove-Item -Recurse -Force` receives.
8471
+ { 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" },
8472
+ { pattern: /\b(?:Remove-Item|ri|rm|rmdir|del|erase|rd)\b[^;\n|]*(?:HKLM:|HKCU:|Registry::)/i, warning: "may remove registry entries", commandClass: "infra_destructive" },
8473
+ // These write aliases are not all destructive in the recursive-delete sense,
8474
+ // but they can overwrite or create arbitrary files. Keep them behind the
8475
+ // same explicit shell confirmation boundary as other PowerShell writes.
8476
+ { 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
8477
  { 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
8478
  { pattern: /\b(Stop-Computer|Restart-Computer)\b/i, warning: "may stop or restart the machine", commandClass: "infra_destructive" },
8392
8479
  { pattern: /(^|[;|&\n])\s*(?:Invoke-Expression|iex)\b/i, warning: "evaluates a dynamically constructed string as code", commandClass: "privilege_escalation" },
@@ -8411,9 +8498,6 @@ function lastPipelineCommand(command) {
8411
8498
  const segments = command.split("|").map((part) => part.trim()).filter(Boolean);
8412
8499
  return segments[segments.length - 1] || command;
8413
8500
  }
8414
- function classifyBashCommand(command, options) {
8415
- return classifyShellCommand(command, "bash", options);
8416
- }
8417
8501
  function splitShellSegments(command) {
8418
8502
  const segments = [];
8419
8503
  let current = "";
@@ -8595,6 +8679,66 @@ function resolveStaticCwdTarget(target, cwd, root) {
8595
8679
  if (resolved !== normalizedRoot && !isStrictDescendant(resolved, normalizedRoot)) return void 0;
8596
8680
  return resolved;
8597
8681
  }
8682
+ var DYNAMIC_EXECUTION_WARNING = "may execute nested shell or interpreter code";
8683
+ function executableName(word) {
8684
+ const normalized = word.replace(/\\/g, "/").toLowerCase();
8685
+ const base = normalized.slice(normalized.lastIndexOf("/") + 1);
8686
+ return base.endsWith(".exe") ? base.slice(0, -4) : base;
8687
+ }
8688
+ function hasDynamicExecutionInvocation(words) {
8689
+ if (words.length === 0) return false;
8690
+ const program = executableName(words[0]);
8691
+ if (program === "env" || program === "command" || program === "exec") {
8692
+ let index = 1;
8693
+ while (index < words.length && (words[index].startsWith("-") || /^[A-Za-z_][A-Za-z0-9_]*=/.test(words[index]))) index += 1;
8694
+ return index < words.length && hasDynamicExecutionInvocation(words.slice(index));
8695
+ }
8696
+ if (["bash", "sh", "zsh", "dash", "ksh", "fish"].includes(program)) {
8697
+ return words.slice(1).some((word) => /^-[^-]*c$/i.test(word) || /^--command(?:=|$)/i.test(word));
8698
+ }
8699
+ if (["pwsh", "powershell"].includes(program)) {
8700
+ return words.slice(1).some((word) => /^-(?:c|command|encodedcommand|e|file)(?:=|$)/i.test(word));
8701
+ }
8702
+ if (program === "cmd") {
8703
+ return words.slice(1).some((word) => /^\/(?:c|k)$/i.test(word));
8704
+ }
8705
+ if (["python", "python3", "python2", "perl", "ruby"].includes(program)) {
8706
+ return words.slice(1).some((word) => /^(?:-c|-e|-m)$/i.test(word));
8707
+ }
8708
+ if (program === "node") {
8709
+ return words.slice(1).some((word) => /^(?:-e|--eval|-p|--print|-r|--require)$/i.test(word));
8710
+ }
8711
+ if (program === "find") {
8712
+ return words.slice(1).some((word) => /^-(?:exec|execdir|ok|okdir)$/.test(word));
8713
+ }
8714
+ if (program === "rg") {
8715
+ return words.slice(1).some((word) => /^--pre(?:=|$)|^--pre-glob(?:=|$)/.test(word));
8716
+ }
8717
+ if (program === "xargs") {
8718
+ const optionsWithValues = /* @__PURE__ */ new Set(["-a", "-d", "-E", "-I", "-L", "-n", "-P", "-s"]);
8719
+ let index = 1;
8720
+ while (index < words.length && words[index].startsWith("-")) {
8721
+ if (optionsWithValues.has(words[index])) index += 1;
8722
+ index += 1;
8723
+ }
8724
+ return index < words.length && hasDynamicExecutionInvocation(words.slice(index));
8725
+ }
8726
+ return false;
8727
+ }
8728
+ function dynamicExecutionWarnings(command) {
8729
+ const warnings = [];
8730
+ for (const segment of splitShellSegments(command)) {
8731
+ const words = splitShellWords(segment);
8732
+ if (words) {
8733
+ if (hasDynamicExecutionInvocation(words)) warnings.push(DYNAMIC_EXECUTION_WARNING);
8734
+ continue;
8735
+ }
8736
+ 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())) {
8737
+ warnings.push(DYNAMIC_EXECUTION_WARNING);
8738
+ }
8739
+ }
8740
+ return [...new Set(warnings)];
8741
+ }
8598
8742
  function isStaticallyScopedRecursiveForceRemove(command, options) {
8599
8743
  if (!options?.cwd) return false;
8600
8744
  const root = options.sandboxRoot || options.cwd;
@@ -8636,6 +8780,12 @@ function classifyShellCommand(command, shell = "bash", options) {
8636
8780
  destructive = true;
8637
8781
  }
8638
8782
  }
8783
+ const dynamicWarnings = dynamicExecutionWarnings(command);
8784
+ if (dynamicWarnings.length > 0) {
8785
+ warnings.push(...dynamicWarnings);
8786
+ commandClass = "unknown_shell";
8787
+ destructive = true;
8788
+ }
8639
8789
  for (const entry of destructivePatterns) {
8640
8790
  if (entry.pattern.test(command)) {
8641
8791
  warnings.push(entry.warning);
@@ -8811,7 +8961,8 @@ function wrapBashGuard(tool, plan) {
8811
8961
  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
8962
  execute: async (toolCallId, params, signal, onUpdate) => {
8813
8963
  const command = typeof params?.command === "string" ? params.command : "";
8814
- const classification = classifyBashCommand(command);
8964
+ const shell = resolveRequestShellKind(params?.shell);
8965
+ const classification = classifyShellCommand(command, shell);
8815
8966
  if (classification.destructive) {
8816
8967
  const reason = `destructive shell command blocked for subagent: ${classification.riskWarnings.join("; ")}`;
8817
8968
  plan.toolsExcluded.push({ name: tool.name, reason });
@@ -9427,7 +9578,11 @@ ${DELEGATION_REPORT_INSTRUCTION}`;
9427
9578
  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
9579
  }
9429
9580
  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}`);
9581
+ 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}`);
9582
+ const carrier = failure;
9583
+ carrier.retryable = lastAssistant?.retryable;
9584
+ carrier.replayClass = lastAssistant?.replayClass;
9585
+ throw failure;
9431
9586
  }
9432
9587
  let response;
9433
9588
  if (!collectedResponse) {
@@ -9445,6 +9600,7 @@ ${DELEGATION_REPORT_INSTRUCTION}`;
9445
9600
  }
9446
9601
  const durationMs = Date.now() - startTime;
9447
9602
  task.status = "completed";
9603
+ task.failure = void 0;
9448
9604
  observeDelegationPhase(task.observation, "completed");
9449
9605
  task.result = response;
9450
9606
  task.details = {
@@ -9471,6 +9627,12 @@ ${DELEGATION_REPORT_INSTRUCTION}`;
9471
9627
  observeDelegationPhase(task.observation, timedOut ? "deadline" : signal?.aborted ? "cancelled" : "failed");
9472
9628
  phaseProgress(task.observation.phase);
9473
9629
  task.error = errorMessage.slice(0, 2e3);
9630
+ const failureCarrier = error;
9631
+ if (failureCarrier && (failureCarrier.retryable !== void 0 || failureCarrier.replayClass !== void 0)) {
9632
+ task.failure = { ...failureCarrier.retryable !== void 0 ? { retryable: failureCarrier.retryable } : {}, ...failureCarrier.replayClass !== void 0 ? { replayClass: failureCarrier.replayClass } : {} };
9633
+ } else {
9634
+ task.failure = void 0;
9635
+ }
9474
9636
  task.report ??= parseDelegationReport(void 0, task.id);
9475
9637
  task.parentHandoffRequired = isParentHandoffRequired(task);
9476
9638
  task.completedAt = Date.now();
@@ -9807,7 +9969,7 @@ ${SPAWN_USAGE_HINT}` }],
9807
9969
  await run(task, toolContext.reportProgress, signal);
9808
9970
  if (task.wasRecall || task.mode !== "read" || task.status !== "failed" || !task.error) return;
9809
9971
  if (signal?.aborted) return;
9810
- if (!isRetryableSubagentFailure(task.error)) return;
9972
+ if (!isRetryableSubagentFailureFrom({ errorMessage: task.error, retryable: task.failure?.retryable, replayClass: task.failure?.replayClass })) return;
9811
9973
  observeDelegationPhase(task.observation, "task_replay");
9812
9974
  const settleMs = Math.max(0, (deps.transientRetryBackoffMs ?? 2e3) * 10);
9813
9975
  toolContext.reportProgress?.({ message: `[${task.name}] Provider/transport failure \u2014 replaying the read task once after ${Math.round(settleMs / 1e3)}s`, increment: 0 });
@@ -9815,6 +9977,7 @@ ${SPAWN_USAGE_HINT}` }],
9815
9977
  if (signal.aborted) {
9816
9978
  task.status = "cancelled";
9817
9979
  task.error = "Subagent cancelled during replay backoff";
9980
+ task.failure = void 0;
9818
9981
  observeDelegationPhase(task.observation, "cancelled");
9819
9982
  task.completedAt = Date.now();
9820
9983
  return;
@@ -9824,6 +9987,7 @@ ${SPAWN_USAGE_HINT}` }],
9824
9987
  task.runtimeEvidence = [];
9825
9988
  task.reportedEvidence = [];
9826
9989
  task.error = void 0;
9990
+ task.failure = void 0;
9827
9991
  task.report = void 0;
9828
9992
  task.result = void 0;
9829
9993
  task.details = void 0;
@@ -9877,6 +10041,10 @@ ${SPAWN_USAGE_HINT}` }],
9877
10041
  observation: task.observation,
9878
10042
  parentHandoffRequired: task.parentHandoffRequired,
9879
10043
  failureReason: task.error,
10044
+ // Structured provider verdict so the parent model can distinguish
10045
+ // stream loss (safe to re-dispatch; no tool calls executed) from hard
10046
+ // provider vetoes (auth/quota/config — not recoverable here).
10047
+ failure: task.failure,
9880
10048
  details: task.details
9881
10049
  })),
9882
10050
  totalDuration,
@@ -10534,6 +10702,164 @@ function createAskUserCapability() {
10534
10702
  };
10535
10703
  }
10536
10704
 
10705
+ // ../../packages/shared-headless-capabilities/src/policy-enforcer.ts
10706
+ var POLICY_ENFORCED = /* @__PURE__ */ Symbol.for("pie.capabilityPolicy.enforced");
10707
+ function evaluateToolPermission(tool, context = {}) {
10708
+ const mode = context.mode ?? "normal";
10709
+ const metadata = getToolCapabilityMetadata(tool);
10710
+ const base = describeToolPolicyDecision(tool, mode);
10711
+ if (!metadata && mode === "normal") {
10712
+ return {
10713
+ disposition: "allow",
10714
+ allowed: true,
10715
+ reason: "unclassified_tool",
10716
+ policyReason: "unclassified_tool",
10717
+ mode,
10718
+ yoloMode: context.yoloMode
10719
+ };
10720
+ }
10721
+ if (!metadata || !base.allowed) {
10722
+ return {
10723
+ disposition: "deny",
10724
+ allowed: false,
10725
+ reason: base.reason ?? "tool not allowed by runtime policy",
10726
+ policyReason: base.reason ?? "tool not allowed by runtime policy",
10727
+ mode,
10728
+ yoloMode: context.yoloMode,
10729
+ riskClass: metadata?.riskClass,
10730
+ permissionScope: metadata?.permissionScope,
10731
+ requiresPermission: metadata?.requiresPermission
10732
+ };
10733
+ }
10734
+ const requiresConfirmation = Boolean(metadata.highRisk || metadata.requiresPermission);
10735
+ if (!requiresConfirmation || context.yoloMode || context.confirmationPolicy === "allow") {
10736
+ return {
10737
+ disposition: "allow",
10738
+ allowed: true,
10739
+ mode,
10740
+ yoloMode: context.yoloMode,
10741
+ riskClass: metadata.riskClass,
10742
+ permissionScope: metadata.permissionScope,
10743
+ requiresPermission: requiresConfirmation
10744
+ };
10745
+ }
10746
+ if (context.confirmationPolicy === "deny" || mode === "subagent") {
10747
+ const reason = mode === "subagent" ? "subagent cannot request interactive permission" : "permission confirmation is unavailable";
10748
+ return {
10749
+ disposition: "deny",
10750
+ allowed: false,
10751
+ reason,
10752
+ policyReason: reason,
10753
+ mode,
10754
+ yoloMode: context.yoloMode,
10755
+ riskClass: metadata.riskClass,
10756
+ permissionScope: metadata.permissionScope,
10757
+ requiresPermission: true
10758
+ };
10759
+ }
10760
+ return {
10761
+ disposition: "confirm",
10762
+ allowed: false,
10763
+ reason: "permission confirmation required",
10764
+ policyReason: "permission confirmation required",
10765
+ mode,
10766
+ yoloMode: context.yoloMode,
10767
+ riskClass: metadata.riskClass,
10768
+ permissionScope: metadata.permissionScope,
10769
+ requiresPermission: true
10770
+ };
10771
+ }
10772
+ function blockedResult(toolName, decision) {
10773
+ return {
10774
+ content: [
10775
+ {
10776
+ type: "text",
10777
+ text: `Blocked ${toolName}: ${decision.reason ?? decision.policyReason ?? "permission denied"}`
10778
+ }
10779
+ ],
10780
+ details: { permissionDecision: decision },
10781
+ isError: true
10782
+ };
10783
+ }
10784
+ function planModeShellCommandBlocked(target, args) {
10785
+ const metadata = getToolCapabilityMetadata(target);
10786
+ if (!metadata?.executesShell) return void 0;
10787
+ const command = args?.command;
10788
+ if (typeof command !== "string") {
10789
+ 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.`;
10790
+ }
10791
+ const shell = resolveRequestShellKind(args?.shell);
10792
+ if (isPlanModeSafeCommand(command, shell)) return void 0;
10793
+ 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.`;
10794
+ }
10795
+ function mergePermissionDetails(details, decision) {
10796
+ if (details && typeof details === "object" && !Array.isArray(details)) {
10797
+ const record = details;
10798
+ if (record.permissionDecision) {
10799
+ return { ...record, capabilityPolicyDecision: decision };
10800
+ }
10801
+ return { ...record, permissionDecision: decision };
10802
+ }
10803
+ return { permissionDecision: decision };
10804
+ }
10805
+ function createPolicyEnforcedTool(tool, options = {}) {
10806
+ const marker = tool[POLICY_ENFORCED];
10807
+ const intactWrapper = !!marker && marker.wrapperExecute === tool.execute;
10808
+ if (intactWrapper && marker.getContext === options.getContext && marker.permissionProvider === options.permissionProvider) {
10809
+ return tool;
10810
+ }
10811
+ const target = intactWrapper ? marker.base : tool;
10812
+ const wrapped = {
10813
+ ...target,
10814
+ execute: async (toolCallId, args, signal, onUpdate) => {
10815
+ let decision = evaluateToolPermission(target, options.getContext?.());
10816
+ if (decision.disposition === "confirm") {
10817
+ const approved = await options.permissionProvider?.confirm({ toolName: target.name, args, decision }).catch(() => false);
10818
+ if (!approved) {
10819
+ return blockedResult(target.name, {
10820
+ ...decision,
10821
+ disposition: "deny",
10822
+ reason: "permission denied by user",
10823
+ policyReason: "permission denied by user"
10824
+ });
10825
+ }
10826
+ decision = {
10827
+ ...decision,
10828
+ disposition: "allow",
10829
+ allowed: true,
10830
+ reason: void 0,
10831
+ policyReason: void 0
10832
+ };
10833
+ }
10834
+ if (!decision.allowed) return blockedResult(target.name, decision);
10835
+ const planShellReason = (options.getContext?.().mode ?? "normal") === "plan" ? planModeShellCommandBlocked(target, args) : void 0;
10836
+ if (planShellReason) {
10837
+ return blockedResult(target.name, {
10838
+ ...decision,
10839
+ allowed: false,
10840
+ reason: planShellReason,
10841
+ policyReason: "plan_mode_non_read_only_shell_command"
10842
+ });
10843
+ }
10844
+ const result = await target.execute(toolCallId, args, signal, onUpdate);
10845
+ return {
10846
+ ...result,
10847
+ details: mergePermissionDetails(result.details, decision)
10848
+ };
10849
+ }
10850
+ };
10851
+ wrapped[POLICY_ENFORCED] = {
10852
+ getContext: options.getContext,
10853
+ permissionProvider: options.permissionProvider,
10854
+ wrapperExecute: wrapped.execute,
10855
+ base: target
10856
+ };
10857
+ return wrapped;
10858
+ }
10859
+ function createPolicyEnforcedTools(tools, options = {}) {
10860
+ return tools.map((tool) => createPolicyEnforcedTool(tool, options));
10861
+ }
10862
+
10537
10863
  // ../../packages/shared-headless-capabilities/src/builtin/fs/read.ts
10538
10864
  import * as nodePath from "node:path";
10539
10865
 
@@ -16937,144 +17263,6 @@ function maybeAdvanceTodoExecutionState(state, signal) {
16937
17263
  };
16938
17264
  }
16939
17265
 
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
17266
  // ../../packages/shared-headless-capabilities/src/system-prompt-tools.ts
17079
17267
  function normalizeDescription(description) {
17080
17268
  const text = String(description || "").replace(/\s+/g, " ").trim();
@@ -19494,10 +19682,9 @@ export {
19494
19682
  supersedeExecutionState,
19495
19683
  shouldPreserveExecutionStateForUserText,
19496
19684
  BUILTIN_TOOL_CAPABILITY_METADATA,
19497
- getToolCapabilityMetadata,
19498
19685
  isToolAllowedInPlanMode,
19686
+ defaultShellKindForPlatform,
19499
19687
  extractPlanTodoItems,
19500
- isPlanModeSafeCommand,
19501
19688
  markCompletedPlanSteps,
19502
19689
  createPlanCapability,
19503
19690
  modalityForReadFileUnderstandingKind,