@cydm/pie 2.7.5 → 2.7.7

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-MLRF72MK.js";
5
+ } from "./chunk-W5B3OXHQ.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-SUPAWIKP.js";
15
+ } from "./chunk-2FEDB5GR.js";
16
16
  import {
17
17
  Type
18
18
  } from "./chunk-XXGYUYXI.js";
@@ -899,11 +899,6 @@ 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
-
907
902
  // ../../packages/shared-headless-capabilities/src/tool-policy.ts
908
903
  var BUILTIN_TOOL_CAPABILITY_METADATA = {
909
904
  search_context: { riskClass: "read_only", concurrencySafe: true, permissionScope: "session", availableInPlanMode: true, allowedForSubagentByDefault: false, maxOutputChars: 2e4 },
@@ -914,6 +909,7 @@ var BUILTIN_TOOL_CAPABILITY_METADATA = {
914
909
  write_file: { riskClass: "write", concurrencySafe: false, requiresPermission: true, permissionScope: "filesystem", writesFile: true, highRisk: true },
915
910
  edit_file: { riskClass: "write", concurrencySafe: false, requiresPermission: true, permissionScope: "filesystem", writesFile: true, highRisk: true },
916
911
  bash: { riskClass: "shell", concurrencySafe: false, requiresPermission: true, permissionScope: "shell", executesShell: true, availableInPlanMode: true, allowedForSubagentByDefault: true, highRisk: true, maxOutputChars: 1e5 },
912
+ shell: { riskClass: "shell", concurrencySafe: false, requiresPermission: true, permissionScope: "shell", executesShell: true, availableInPlanMode: true, allowedForSubagentByDefault: true, highRisk: true, maxOutputChars: 1e5 },
917
913
  web_research: { riskClass: "network", concurrencySafe: true, permissionScope: "network", readsNetwork: true, availableInPlanMode: true, allowedForSubagentByDefault: true, maxOutputChars: 1e5 },
918
914
  web_search: { riskClass: "network", concurrencySafe: true, permissionScope: "network", readsNetwork: true, availableInPlanMode: true, allowedForSubagentByDefault: true, maxOutputChars: 1e5 },
919
915
  web_fetch: { riskClass: "network", concurrencySafe: true, permissionScope: "network", readsNetwork: true, availableInPlanMode: true, allowedForSubagentByDefault: true, maxOutputChars: 1e5 },
@@ -975,6 +971,11 @@ function getSubagentToolPolicyDecision(tool) {
975
971
  }
976
972
  var DEFAULT_PLAN_MODE_TOOL_NAMES = Object.entries(BUILTIN_TOOL_CAPABILITY_METADATA).filter(([, metadata]) => metadata.availableInPlanMode).map(([name]) => name);
977
973
 
974
+ // ../../packages/shared-headless-capabilities/src/capability.ts
975
+ function defineSharedCapability(definition) {
976
+ return definition;
977
+ }
978
+
978
979
  // ../../packages/shared-headless-capabilities/src/plan.ts
979
980
  var import_shell_quote = __toESM(require_shell_quote(), 1);
980
981
  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"]);
@@ -1211,6 +1212,27 @@ function createPlanCapability() {
1211
1212
  });
1212
1213
  }
1213
1214
 
1215
+ // ../../packages/agent-framework/src/session/remote-failure.ts
1216
+ function classifyRemoteFailure(remote, message) {
1217
+ if (!remote || !["transient", "other", "context_overflow"].includes(remote.category)) return void 0;
1218
+ const overflow = remote.category === "context_overflow";
1219
+ const retryAfterMs = remote.retryAfterMs;
1220
+ return {
1221
+ kind: overflow ? "context_overflow" : remote.category === "transient" ? "provider_transient" : "provider_confirmation",
1222
+ retryable: !overflow,
1223
+ compactable: overflow,
1224
+ userAbort: false,
1225
+ terminal: false,
1226
+ message,
1227
+ ...typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs) && retryAfterMs >= 0 ? { retryAfterMs } : {}
1228
+ };
1229
+ }
1230
+ function resolveRetryDelay(backoffMs, maxDelayMs, retryAfterMs = 0) {
1231
+ const maximumWait = Math.min(maxDelayMs ?? 6e4, 2147483647);
1232
+ if (retryAfterMs > maximumWait) return void 0;
1233
+ return Math.max(maxDelayMs === void 0 ? backoffMs : Math.min(backoffMs, maxDelayMs), retryAfterMs);
1234
+ }
1235
+
1214
1236
  // ../../packages/agent-framework/src/session/compaction.ts
1215
1237
  var COMPACT_PROMPT = `You are performing a CONTEXT CHECKPOINT COMPACTION. Create a handoff summary for another LLM that will resume the task.
1216
1238
 
@@ -1577,6 +1599,7 @@ async function createCompactionSummary(messages, options = {}) {
1577
1599
  throwIfAborted();
1578
1600
  return appendFileOperationsToSummary(createSimpleSummary(messages), messages);
1579
1601
  }
1602
+ let confirmationRetries = 0;
1580
1603
  for (let attempt = 0; ; attempt++) {
1581
1604
  throwIfAborted();
1582
1605
  let response;
@@ -1602,17 +1625,22 @@ Return a text-only successor handoff. Do not call any tools.` }],
1602
1625
  }
1603
1626
  ).result();
1604
1627
  throwIfAborted();
1605
- if (response.stopReason === "error" && !response.content.some((block) => block.type === "toolCall")) {
1628
+ if (response.stopReason === "error" && (!response.content.some((block) => block.type === "toolCall") || classifyRemoteFailure(response.remoteFailure, response.errorMessage ?? "") !== void 0)) {
1606
1629
  const providerError = new Error(response.errorMessage || "Compaction provider error");
1607
1630
  const explicitRetryable = response.retryable;
1608
1631
  if (typeof explicitRetryable === "boolean") providerError.retryable = explicitRetryable;
1609
1632
  if (response.replayEligible === true) providerError.replayEligible = true;
1633
+ providerError.remoteFailure = response.remoteFailure;
1610
1634
  throw providerError;
1611
1635
  }
1612
1636
  } catch (error) {
1613
1637
  throwIfAborted();
1614
- if (attempt >= 2 || explicitlyNonRetryable(error) || !isTransientCompactionFailure(error)) throw error;
1615
- await waitForCompactionRetry(250 * 2 ** attempt, signal);
1638
+ const remote = classifyRemoteFailure(error?.remoteFailure, error instanceof Error ? error.message : String(error));
1639
+ if (attempt >= 2 || explicitlyNonRetryable(error) || (remote ? !remote.retryable : !isTransientCompactionFailure(error))) throw error;
1640
+ if (remote?.kind === "provider_confirmation" && confirmationRetries++ >= 1) throw error;
1641
+ const delayMs = resolveRetryDelay(250 * 2 ** attempt, 6e4, remote?.retryAfterMs);
1642
+ if (delayMs === void 0) throw new Error(`${remote?.message} (Retry-After exceeds the allowed wait; automatic recovery paused)`);
1643
+ await waitForCompactionRetry(delayMs, signal);
1616
1644
  continue;
1617
1645
  }
1618
1646
  if (response.stopReason !== "stop" || response.content.some((block) => block.type === "toolCall")) {
@@ -1725,6 +1753,9 @@ function normalizeUserContentBlock(block) {
1725
1753
  if (block.type === "text") {
1726
1754
  return { type: "text", text: block.text };
1727
1755
  }
1756
+ if (block.type === "hostContext") {
1757
+ return { type: "text", text: "x".repeat(512) };
1758
+ }
1728
1759
  if (block.type === "image") {
1729
1760
  return { type: "image", mimeType: block.mimeType };
1730
1761
  }
@@ -1905,6 +1936,107 @@ function getSessionEntryText(entry) {
1905
1936
  }).filter(Boolean).join("\n");
1906
1937
  }
1907
1938
 
1939
+ // ../../packages/agent-framework/src/session/host-envelope.ts
1940
+ import { createHash } from "node:crypto";
1941
+ function createHostEnvelopeProjectorRegistry(projectors = []) {
1942
+ const byKey = /* @__PURE__ */ new Map();
1943
+ const latestByKind = /* @__PURE__ */ new Map();
1944
+ const key = (kind, version) => `${kind}@${version}`;
1945
+ for (const projector of projectors) validateAndIndex(projector);
1946
+ function validateAndIndex(input) {
1947
+ if (!input.kind || typeof input.kind !== "string") throw new Error("Host envelope projector requires a kind.");
1948
+ if (!Number.isInteger(input.version) || input.version < 1) throw new Error(`Host envelope projector ${input.kind} requires an integer version >= 1.`);
1949
+ if (typeof input.project !== "function") throw new Error(`Host envelope projector ${input.kind} requires a project function.`);
1950
+ const projector = Object.freeze({
1951
+ kind: input.kind,
1952
+ version: input.version,
1953
+ project: input.project
1954
+ });
1955
+ const existing = byKey.get(key(projector.kind, projector.version));
1956
+ if (existing && existing.project !== projector.project) {
1957
+ throw new Error(`Duplicate host envelope projector registration for ${projector.kind}@${projector.version}: a version's text must never change after registration.`);
1958
+ }
1959
+ byKey.set(key(projector.kind, projector.version), projector);
1960
+ const currentLatest = latestByKind.get(projector.kind);
1961
+ if (!currentLatest || projector.version > currentLatest.version) latestByKind.set(projector.kind, projector);
1962
+ }
1963
+ return {
1964
+ register: validateAndIndex,
1965
+ resolve: (kind, version) => byKey.get(key(kind, version)),
1966
+ latest: (kind) => latestByKind.get(kind),
1967
+ listPins: () => [...byKey.values()].map(({ kind, version }) => ({ kind, version }))
1968
+ };
1969
+ }
1970
+ function projectHostContextBlock(block, registry) {
1971
+ let hash;
1972
+ try {
1973
+ hash = createHash("sha256").update(JSON.stringify({ kind: block.kind, version: block.version, data: block.data })).digest("hex").slice(0, 16);
1974
+ } catch {
1975
+ hash = "unhashable";
1976
+ }
1977
+ const projector = registry.resolve(block.kind, block.version);
1978
+ if (!projector) {
1979
+ return {
1980
+ text: `runtime state: unknown (projector missing: ${block.kind}@${block.version})`,
1981
+ degraded: true,
1982
+ audit: { kind: block.kind, version: block.version, hash }
1983
+ };
1984
+ }
1985
+ try {
1986
+ const text = projector.project(block.data);
1987
+ if (typeof text !== "string" || text.trim().length === 0) throw new Error("projector returned empty text");
1988
+ return { text, degraded: false, audit: { kind: block.kind, version: block.version, hash } };
1989
+ } catch (error) {
1990
+ return {
1991
+ text: `runtime state: unknown (projector failed: ${block.kind}@${block.version}: ${error instanceof Error ? error.message.slice(0, 120) : String(error).slice(0, 120)})`,
1992
+ degraded: true,
1993
+ audit: { kind: block.kind, version: block.version, hash }
1994
+ };
1995
+ }
1996
+ }
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
+ function projectHostEnvelopes(messages, registry, onProjection) {
2006
+ let changed = false;
2007
+ const next = [];
2008
+ for (const message of messages) {
2009
+ const block = leadingHostContextBlock(message);
2010
+ if (!block) {
2011
+ next.push(message);
2012
+ continue;
2013
+ }
2014
+ const outcome = projectHostContextBlock(block, registry);
2015
+ onProjection?.(outcome);
2016
+ const content = message.content;
2017
+ const rest = content.slice(1);
2018
+ const first = rest[0];
2019
+ let projectedContent;
2020
+ if (first && first.type === "text" && typeof first.text === "string") {
2021
+ const merged = first.text.trim().length > 0 ? `${outcome.text}
2022
+
2023
+ ${first.text}` : outcome.text;
2024
+ projectedContent = [{ ...first, text: merged }, ...rest.slice(1)];
2025
+ } else {
2026
+ projectedContent = [{ type: "text", text: outcome.text }, ...rest];
2027
+ }
2028
+ next.push({ ...message, content: projectedContent });
2029
+ changed = true;
2030
+ }
2031
+ return changed ? next : messages;
2032
+ }
2033
+ function isHostContextBlock(value) {
2034
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
2035
+ const block = value;
2036
+ const version = block.version;
2037
+ return block.type === "hostContext" && typeof block.kind === "string" && block.kind.length > 0 && block.kind.length <= 128 && typeof version === "number" && Number.isInteger(version) && version >= 1 && !!block.data && typeof block.data === "object" && !Array.isArray(block.data);
2038
+ }
2039
+
1908
2040
  // ../../packages/agent-framework/src/session/store.ts
1909
2041
  import { createRequire } from "node:module";
1910
2042
  import { fileURLToPath } from "node:url";
@@ -2945,7 +3077,19 @@ function normalizeSessionDisplayName(raw) {
2945
3077
  }
2946
3078
  function isSyntheticEnvelopePreview(preview) {
2947
3079
  const trimmed = preview.trimStart();
2948
- return trimmed.startsWith("<environment_context>") || trimmed.startsWith("<turn_aborted>") || trimmed.startsWith("# AGENTS.md instructions") || trimmed.startsWith("## Project Context\nProject-specific instructions from AGENTS.md");
3080
+ if (trimmed.startsWith("<environment_context>") || trimmed.startsWith("<turn_aborted>") || trimmed.startsWith("# AGENTS.md instructions") || trimmed.startsWith("## Project Context\nProject-specific instructions from AGENTS.md")) {
3081
+ return true;
3082
+ }
3083
+ const runtimeMatch = trimmed.match(/^<runtime-mode-context>[\s\S]*?<\/runtime-mode-context>/);
3084
+ if (runtimeMatch) {
3085
+ const rest = trimmed.slice(runtimeMatch[0].length).trim();
3086
+ if (rest.length === 0) return true;
3087
+ return false;
3088
+ }
3089
+ if (trimmed.startsWith("<runtime-mode-context>")) {
3090
+ return true;
3091
+ }
3092
+ return /^HE_INPUT kind=(?:USER_REQUEST|QUEUE_WAKE) source=/.test(trimmed);
2949
3093
  }
2950
3094
  function isAutoGeneratedSessionName(name) {
2951
3095
  return /^Session (\d|[a-z0-9]{6,8}$)/i.test(name.trim());
@@ -2959,7 +3103,10 @@ function userEntryPreview(entry) {
2959
3103
  if (!message) return "";
2960
3104
  if (typeof message.content === "string") return message.content;
2961
3105
  if (!Array.isArray(message.content)) return "";
2962
- return message.content.map((block) => isRecord2(block) && block.type === "text" && typeof block.text === "string" ? block.text : "").join(" ").trim();
3106
+ return message.content.map((block) => {
3107
+ if (isRecord2(block) && block.type === "hostContext") return "";
3108
+ return isRecord2(block) && block.type === "text" && typeof block.text === "string" ? block.text : "";
3109
+ }).join(" ").trim();
2963
3110
  }
2964
3111
  function entryJsonBytes(entry) {
2965
3112
  try {
@@ -4705,7 +4852,7 @@ var AgentSessionAutoCompactScheduler = class {
4705
4852
  this.resolvePending();
4706
4853
  return;
4707
4854
  }
4708
- if (this.options.isStreaming()) {
4855
+ if (this.options.isStreaming() || this.runningAutoCompact !== void 0) {
4709
4856
  this.pendingAutoCompact = pending;
4710
4857
  this.schedule(pending, 100);
4711
4858
  return;
@@ -4717,7 +4864,11 @@ var AgentSessionAutoCompactScheduler = class {
4717
4864
  if (this.runningAutoCompact === running) {
4718
4865
  this.runningAutoCompact = void 0;
4719
4866
  this.runningAbortController = void 0;
4720
- this.resolvePending();
4867
+ try {
4868
+ this.options.onSettled?.();
4869
+ } finally {
4870
+ this.resolvePending();
4871
+ }
4721
4872
  }
4722
4873
  });
4723
4874
  this.runningAutoCompact = running;
@@ -4742,6 +4893,7 @@ var AgentSessionAutoCompactScheduler = class {
4742
4893
  });
4743
4894
  }
4744
4895
  resolvePending() {
4896
+ if (this.hasPendingAutoCompact) return;
4745
4897
  const resolve2 = this.resolvePendingWait;
4746
4898
  this.pendingWait = void 0;
4747
4899
  this.resolvePendingWait = void 0;
@@ -4885,7 +5037,7 @@ function isUserContentBlock(value) {
4885
5037
  return false;
4886
5038
  }
4887
5039
  const type = value.type;
4888
- return type === "text" || type === "image" || type === "audio" || type === "video" || type === "fileRef";
5040
+ return type === "text" || type === "image" || type === "audio" || type === "video" || type === "fileRef" || type === "hostContext";
4889
5041
  }
4890
5042
  function normalizePromptInput(input) {
4891
5043
  if (typeof input === "string") {
@@ -5276,7 +5428,8 @@ function createFailureClassification(context, retry, classifyFailure) {
5276
5428
  if (context.message?.role === "assistant" && context.message.retryable === false) {
5277
5429
  return { kind: "unknown", retryable: false, compactable: false, userAbort: context.message.stopReason === "aborted", terminal: true, message: context.errorMessage };
5278
5430
  }
5279
- const hostClassification = classifyFailure?.(context);
5431
+ const remote = context.message?.role === "assistant" && context.message.stopReason === "error" ? classifyRemoteFailure(context.message.remoteFailure, context.errorMessage) : void 0;
5432
+ const hostClassification = remote ? retry || remote.compactable ? remote : asTerminalFailure(remote) : classifyFailure?.(context);
5280
5433
  if (hostClassification) {
5281
5434
  return hostClassification;
5282
5435
  }
@@ -5433,22 +5586,23 @@ var AgentSessionRetryController = class {
5433
5586
  this.resolvePending();
5434
5587
  return;
5435
5588
  }
5436
- const maxRetries = this.retry.maxRetriesForFailure?.(failure) ?? this.retry.maxRetries ?? 3;
5589
+ const maxRetries = Math.min(this.retry.maxRetriesForFailure?.(failure) ?? this.retry.maxRetries ?? 3, failure.kind === "provider_confirmation" ? 1 : Infinity);
5437
5590
  const kindAttempts = (this.retryAttempts.get(failure.kind) ?? 0) + 1;
5438
5591
  this.retryAttempts.set(failure.kind, kindAttempts);
5439
5592
  this.totalRetryAttempts += 1;
5440
5593
  const totalBudget = this.retry.maxTotalRetries;
5441
5594
  const kindExhausted = kindAttempts > maxRetries;
5442
5595
  const totalExhausted = totalBudget !== void 0 && this.totalRetryAttempts > totalBudget;
5443
- if (kindExhausted || totalExhausted) {
5444
- const terminalFailure = asTerminalFailure(failure);
5596
+ const delayMs = resolveRetryDelay((this.retry.baseDelayMs ?? 2e3) * 2 ** (kindAttempts - 1), this.retry.maxDelayMs, failure.retryAfterMs);
5597
+ if (kindExhausted || totalExhausted || delayMs === void 0) {
5598
+ const terminalFailure = asTerminalFailure(failure, delayMs === void 0 ? `${failure.message} (Retry-After exceeds the allowed wait; automatic recovery paused)` : failure.message);
5445
5599
  if (failure.replayExempt === true) {
5446
5600
  const outcome = this.replayCircuit.recordReplayExhaustion();
5447
5601
  if (outcome.opened) {
5448
5602
  this.emit({ type: "retry_exhausted", errorMessage: `systemic timeout-stall: ${outcome.consecutive} consecutive turns ended by timeout-replay exhaustion`, maxRetries: totalExhausted ? totalBudget : maxRetries });
5449
5603
  }
5450
5604
  }
5451
- this.emit({ type: "retry_exhausted", errorMessage: failure.message, maxRetries: totalExhausted ? totalBudget : maxRetries });
5605
+ this.emit({ type: "retry_exhausted", errorMessage: terminalFailure.message, maxRetries: totalExhausted ? totalBudget : maxRetries });
5452
5606
  this.emit({ type: "turn_failed_terminal", failure: terminalFailure, message });
5453
5607
  this.emit({ type: "turn_settled" });
5454
5608
  this.resetAttempts();
@@ -5457,8 +5611,6 @@ var AgentSessionRetryController = class {
5457
5611
  return;
5458
5612
  }
5459
5613
  const attempt = kindAttempts;
5460
- const rawDelayMs = (this.retry.baseDelayMs ?? 2e3) * 2 ** (attempt - 1);
5461
- const delayMs = this.retry.maxDelayMs !== void 0 ? Math.min(rawDelayMs, this.retry.maxDelayMs) : rawDelayMs;
5462
5614
  this.ensurePending();
5463
5615
  const epoch = this.retryEpoch;
5464
5616
  const pending = this.pendingRetry;
@@ -5510,7 +5662,7 @@ var AgentSessionRetryController = class {
5510
5662
  userAbort: false,
5511
5663
  terminal: true
5512
5664
  });
5513
- const maxRetries = (failure !== void 0 ? this.retry?.maxRetriesForFailure?.(failure) : void 0) ?? this.retry?.maxRetries ?? 3;
5665
+ const maxRetries = Math.min((failure !== void 0 ? this.retry?.maxRetriesForFailure?.(failure) : void 0) ?? this.retry?.maxRetries ?? 3, failure?.kind === "provider_confirmation" ? 1 : Infinity);
5514
5666
  this.emit({ type: "retry_exhausted", errorMessage: terminalFailure.message, maxRetries });
5515
5667
  this.emit({ type: "turn_failed_terminal", failure: terminalFailure });
5516
5668
  this.emit({ type: "turn_settled" });
@@ -5597,7 +5749,7 @@ function toolOutcomeKind(event) {
5597
5749
  return typeof kind === "string" ? kind : void 0;
5598
5750
  }
5599
5751
  function isCommandNonzero(event) {
5600
- return event.toolName === "bash" && toolOutcomeKind(event) === "command_nonzero";
5752
+ return toolOutcomeKind(event) === "command_nonzero";
5601
5753
  }
5602
5754
  function isStateChangingSuccess(event) {
5603
5755
  if (event.isError) return false;
@@ -5605,7 +5757,7 @@ function isStateChangingSuccess(event) {
5605
5757
  if (outcomeKind === "command_nonzero" || outcomeKind === "timeout" || outcomeKind === "signal") return false;
5606
5758
  if (event.toolName === "edit_file" || event.toolName === "write_file") return true;
5607
5759
  const details = event.result && typeof event.result === "object" ? event.result.details : void 0;
5608
- return event.toolName === "bash" && details?.commandClass === "filesystem_write";
5760
+ return details?.commandClass === "filesystem_write";
5609
5761
  }
5610
5762
  var AgentSessionRuntimeGuard = class {
5611
5763
  constructor(trigger, advise) {
@@ -6211,8 +6363,9 @@ var AgentSessionController = class {
6211
6363
  getRuntimeGuardTriggered: () => this.runtimeGuard.triggered,
6212
6364
  isStreaming: () => this.isStreaming,
6213
6365
  runAutoCompact: async (decision, signal) => {
6214
- await this.runAutoCompact(decision.reason, decision.willRetry, { signal });
6215
- }
6366
+ await this.runAutoCompact(decision.reason, decision.willRetry, { signal }).finally(() => signal.aborted ? void 0 : this.pendingEndOperations.wait());
6367
+ },
6368
+ onSettled: () => this.pendingEndOperations.track(() => this.handleAgentEndHook(this.agent.state.messages.slice()))
6216
6369
  });
6217
6370
  this.autoContinue = new AgentSessionAutoContinueController({
6218
6371
  agent: this.agent,
@@ -7882,14 +8035,22 @@ var SessionTraceSnapshotWriter = class {
7882
8035
 
7883
8036
  // ../../packages/agent-framework/src/delegation.ts
7884
8037
  var DELEGATION_REPORT_MAX_CHARS = 24e3;
7885
- var DELEGATION_REPORT_INSTRUCTION = 'Return your final response once as a JSON object: {"summary":string,"result":string,"remainingWork"?:string[],"evidenceRefs"?:string[]}. Do not make an extra tool call to report. Evidence references are your claims, not runtime verification.';
8038
+ var DELEGATION_REPORT_INSTRUCTION = 'Return your final response once as a JSON object: {"summary":string,"result":string,"remainingWork"?:string[],"evidenceRefs"?:string[]}. Keep the entire JSON response within 24,000 characters: give concise conclusions and bounded evidence references, not repeated drafting notes. Do not make an extra tool call to report. Evidence references are your claims, not runtime verification.';
7886
8039
  function parseDelegationReport(text, runId, truncated = false) {
7887
8040
  const raw = (text ?? "").slice(0, DELEGATION_REPORT_MAX_CHARS);
7888
8041
  const base = { version: 1, runId: runId.slice(0, 160), raw };
7889
- if (truncated || (text?.length ?? 0) > DELEGATION_REPORT_MAX_CHARS) return { ...base, availability: "truncated" };
7890
- if (!raw.trim()) return { ...base, availability: "missing" };
8042
+ const stopReason = typeof truncated === "string" ? truncated.slice(0, 40) : void 0;
8043
+ const reason = stopReason === "length" ? "provider_length" : ["error", "aborted", "deadline", "cancelled", "no_terminal"].includes(stopReason ?? "") ? stopReason : stopReason && stopReason !== "stop" ? "no_terminal" : truncated === true ? "provider_length" : void 0;
8044
+ if (reason) return { ...base, stopReason, reason, availability: raw ? "truncated" : "missing" };
8045
+ if ((text?.length ?? 0) > DELEGATION_REPORT_MAX_CHARS) return { ...base, availability: "truncated", reason: "char_cap" };
8046
+ if (!raw.trim()) return { ...base, availability: "missing", reason: "empty" };
8047
+ let value;
8048
+ try {
8049
+ value = JSON.parse(raw);
8050
+ } catch {
8051
+ return { ...base, availability: "malformed", reason: "invalid_json" };
8052
+ }
7891
8053
  try {
7892
- const value = JSON.parse(raw);
7893
8054
  if (!value || typeof value !== "object" || Array.isArray(value) || typeof value.summary !== "string" || typeof value.result !== "string") throw new Error();
7894
8055
  if (Object.keys(value).some((key) => !["summary", "result", "remainingWork", "evidenceRefs"].includes(key))) throw new Error();
7895
8056
  for (const key of ["remainingWork", "evidenceRefs"]) {
@@ -7897,7 +8058,7 @@ function parseDelegationReport(text, runId, truncated = false) {
7897
8058
  }
7898
8059
  return { ...base, availability: "available", report: value };
7899
8060
  } catch {
7900
- return { ...base, availability: "malformed" };
8061
+ return { ...base, availability: "malformed", reason: "invalid_shape" };
7901
8062
  }
7902
8063
  }
7903
8064
  function observeDelegationPhase(observation, phase, workActivity = true) {
@@ -7926,6 +8087,58 @@ function observeDelegationTool(observation, event) {
7926
8087
  if (observation.receipts.length > 32) observation.receipts.shift();
7927
8088
  }
7928
8089
 
8090
+ // ../../packages/shared-headless-capabilities/src/subagent-usage.ts
8091
+ function createSubagentUsage(sessionId, runId) {
8092
+ return { sessionId, runId, attempts: 0, reportedAttempts: 0, unknownAttempts: 0, pendingAttempts: 0, status: "pending", reportedTokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cacheWriteLong: 0, totalTokens: 0 }, estimatedCost: null };
8093
+ }
8094
+ function settleSubagentUsage(usage, settled) {
8095
+ usage.status = !settled || usage.pendingAttempts > 0 ? "pending" : usage.attempts === 0 || usage.reportedAttempts === 0 ? "unknown" : usage.unknownAttempts > 0 ? "partial" : "complete";
8096
+ }
8097
+ function beginSubagentUsage(usage, model) {
8098
+ usage.attempts++;
8099
+ usage.pendingAttempts++;
8100
+ let ended = false;
8101
+ return (message) => {
8102
+ if (ended) return;
8103
+ ended = true;
8104
+ usage.pendingAttempts--;
8105
+ const values = message?.usage;
8106
+ const known = message?.usageReported === true && values && [values.input, values.output, values.cacheRead, values.cacheWrite, values.totalTokens].every((n) => Number.isFinite(n) && n >= 0);
8107
+ if (known) {
8108
+ usage.reportedAttempts++;
8109
+ for (const key of Object.keys(usage.reportedTokens)) usage.reportedTokens[key] += values[key] ?? 0;
8110
+ } else usage.unknownAttempts++;
8111
+ const priced = known && ["input", "output", "cacheRead", "cacheWrite"].every((key) => typeof model?.cost?.[key] === "number" && Number.isFinite(model.cost[key]) && model.cost[key] >= 0) && Object.values(model.cost).some((value) => typeof value === "number" && value > 0) && Number.isFinite(values.cost?.total) && values.cost.total >= 0;
8112
+ if (usage.attempts === 1 && priced) usage.estimatedCost = values.cost.total;
8113
+ else if (priced && usage.estimatedCost !== null) usage.estimatedCost += values.cost.total;
8114
+ else usage.estimatedCost = null;
8115
+ };
8116
+ }
8117
+ function restoreSubagentUsageRuns(value) {
8118
+ if (!Array.isArray(value)) return void 0;
8119
+ return value.slice(-4).flatMap((item) => {
8120
+ if (!item || typeof item !== "object" || typeof item.sessionId !== "string" || typeof item.runId !== "string" || item.sessionId.length > 160 || item.runId.length > 160 || !["pending", "partial", "complete", "unknown"].includes(item.status)) return [];
8121
+ if (![item.attempts, item.reportedAttempts, item.unknownAttempts, item.pendingAttempts].every((n) => Number.isSafeInteger(n) && n >= 0)) return [];
8122
+ if (item.reportedAttempts + item.unknownAttempts + item.pendingAttempts !== item.attempts) return [];
8123
+ const tokens = item.reportedTokens;
8124
+ const keys = ["input", "output", "cacheRead", "cacheWrite", "cacheWriteLong", "totalTokens"];
8125
+ if (!tokens || !keys.every((key) => Number.isFinite(tokens[key]) && tokens[key] >= 0)) return [];
8126
+ const expectedStatus = item.pendingAttempts > 0 ? "pending" : item.reportedAttempts === 0 ? "unknown" : item.unknownAttempts > 0 ? "partial" : "complete";
8127
+ if (item.status !== expectedStatus && !(item.status === "pending" && item.attempts === 0)) return [];
8128
+ return [{
8129
+ sessionId: item.sessionId,
8130
+ runId: item.runId,
8131
+ attempts: item.attempts,
8132
+ reportedAttempts: item.reportedAttempts,
8133
+ unknownAttempts: item.unknownAttempts,
8134
+ pendingAttempts: item.pendingAttempts,
8135
+ status: item.status,
8136
+ reportedTokens: Object.fromEntries(keys.map((key) => [key, tokens[key]])),
8137
+ estimatedCost: item.reportedAttempts > 0 && item.unknownAttempts === 0 && item.pendingAttempts === 0 && typeof item.estimatedCost === "number" && Number.isFinite(item.estimatedCost) && item.estimatedCost >= 0 ? item.estimatedCost : null
8138
+ }];
8139
+ });
8140
+ }
8141
+
7929
8142
  // ../../packages/shared-headless-capabilities/src/subagent-stream.ts
7930
8143
  var SUBAGENT_FINALIZATION_HINT = "The run's soft deadline has arrived. Do not start new tools. Return the requested final report now, including remaining work; already-running tools may finish.";
7931
8144
  function subagentSoftDeadlineMs(budgetMs) {
@@ -7936,6 +8149,10 @@ function withSubagentRunContext(stream, hooks) {
7936
8149
  if (hooks.hardDeadlineAt !== void 0 && Date.now() >= hooks.hardDeadlineAt) throw new Error("Subagent hard deadline reached");
7937
8150
  if (hooks.softDeadlineAt !== void 0 && Date.now() >= hooks.softDeadlineAt) hooks.finalizing = true;
7938
8151
  if (hooks.observation) observeDelegationPhase(hooks.observation, hooks.finalizing ? "finalizing" : "request");
8152
+ try {
8153
+ hooks.onPhase?.(hooks.finalizing ? "finalizing" : "request");
8154
+ } catch {
8155
+ }
7939
8156
  return stream(model, hooks.finalizing ? { ...context, systemPrompt: `${context.systemPrompt ?? ""}
7940
8157
 
7941
8158
  ${SUBAGENT_FINALIZATION_HINT}` } : context, options);
@@ -7959,11 +8176,17 @@ function cancellableBackoff(ms, signal) {
7959
8176
  if (signal?.aborted) finish();
7960
8177
  });
7961
8178
  }
7962
- function withSubagentResponseRecovery(stream, backoffMs = 2e3, onRetry, onActivity) {
8179
+ function withSubagentResponseRecovery(stream, backoffMs = 2e3, onRetry, onActivity, hooks) {
7963
8180
  return async (model, context, options) => {
7964
8181
  for (let attempt = 0; ; attempt++) {
8182
+ if (hooks) {
8183
+ hooks.terminalText = void 0;
8184
+ hooks.terminalStopReason = void 0;
8185
+ }
7965
8186
  if (options?.signal?.aborted) throw new Error("Subagent aborted by parent");
8187
+ if (hooks?.hardDeadlineAt !== void 0 && Date.now() >= hooks.hardDeadlineAt) throw new Error("Subagent hard deadline reached");
7966
8188
  let message;
8189
+ const finishUsage = hooks?.usage ? beginSubagentUsage(hooks.usage, model) : void 0;
7967
8190
  try {
7968
8191
  const response = await stream(model, context, options);
7969
8192
  let terminal;
@@ -7976,8 +8199,19 @@ function withSubagentResponseRecovery(stream, backoffMs = 2e3, onRetry, onActivi
7976
8199
  onActivity?.(next.value.type);
7977
8200
  } catch {
7978
8201
  }
7979
- if (next.value.type === "done") terminal = next.value.message;
7980
- if (next.value.type === "error") terminal = next.value.error;
8202
+ if (next.value.type === "done") {
8203
+ terminal = next.value.message;
8204
+ finishUsage?.(terminal);
8205
+ }
8206
+ if (next.value.type === "error") {
8207
+ terminal = next.value.error;
8208
+ finishUsage?.(terminal);
8209
+ }
8210
+ if (hooks && (next.value.type === "done" || next.value.type === "error")) {
8211
+ const reportTerminal = terminal && ["stop", "length", "error", "aborted"].includes(terminal.stopReason) && !terminal.content.some((block) => block.type === "toolCall");
8212
+ hooks.terminalStopReason = reportTerminal && terminal ? terminal.stopReason : void 0;
8213
+ hooks.terminalText = reportTerminal && terminal ? terminal.content.filter((block) => block.type === "text").map((block) => block.text).join("\n").slice(0, 24001) : void 0;
8214
+ }
7981
8215
  }
7982
8216
  } finally {
7983
8217
  try {
@@ -7988,6 +8222,7 @@ function withSubagentResponseRecovery(stream, backoffMs = 2e3, onRetry, onActivi
7988
8222
  if (!terminal) throw new Error("Provider protocol error: response ended without a terminal event");
7989
8223
  message = terminal;
7990
8224
  } catch (error) {
8225
+ finishUsage?.();
7991
8226
  if (!options?.signal?.aborted && attempt < 2 && isRetryableSubagentFailure(error instanceof Error ? error.message : String(error))) {
7992
8227
  try {
7993
8228
  onRetry?.();
@@ -8077,6 +8312,7 @@ function toSessionRecord(session, messages) {
8077
8312
  runCount: session.runCount,
8078
8313
  revision: session.revision,
8079
8314
  reports: session.reports?.slice(-4),
8315
+ usageRuns: session.usageRuns?.slice(-4).map((usage) => ({ ...usage, reportedTokens: { ...usage.reportedTokens } })),
8080
8316
  messages
8081
8317
  };
8082
8318
  }
@@ -8151,6 +8387,7 @@ var POWERSHELL_DESTRUCTIVE_PATTERNS = [
8151
8387
  { pattern: /\bRemove-Item\b[^;\n|]*(?:HKLM:|HKCU:|Registry::)/i, warning: "may remove registry entries", commandClass: "infra_destructive" },
8152
8388
  { pattern: /\b(Set-ExecutionPolicy|Start-Process\b[^;\n|]*-Verb\s+RunAs)\b/i, warning: "may change execution policy or request elevation", commandClass: "privilege_escalation" },
8153
8389
  { pattern: /\b(Stop-Computer|Restart-Computer)\b/i, warning: "may stop or restart the machine", commandClass: "infra_destructive" },
8390
+ { pattern: /(^|[;|&\n])\s*(?:Invoke-Expression|iex)\b/i, warning: "evaluates a dynamically constructed string as code", commandClass: "privilege_escalation" },
8154
8391
  { pattern: /\bgit\s+reset\s+--hard\b/i, warning: "may discard uncommitted changes", commandClass: "git_destructive" },
8155
8392
  { pattern: /\bgit\s+push\b[^;&|\n]*[ \t](--force|--force-with-lease|-f)\b/i, warning: "may overwrite remote history", commandClass: "git_destructive" }
8156
8393
  ];
@@ -8569,13 +8806,13 @@ function wrapBashGuard(tool, plan) {
8569
8806
  return {
8570
8807
  ...tool,
8571
8808
  description: `${tool.description}
8572
- Subagent guarded mode uses the same bash risk policy as the parent agent. Destructive commands are blocked; normal diagnostic, search, test, and build commands are allowed.`,
8809
+ 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.`,
8573
8810
  execute: async (toolCallId, params, signal, onUpdate) => {
8574
8811
  const command = typeof params?.command === "string" ? params.command : "";
8575
8812
  const classification = classifyBashCommand(command);
8576
8813
  if (classification.destructive) {
8577
- const reason = `destructive bash command blocked for subagent: ${classification.riskWarnings.join("; ")}`;
8578
- plan.toolsExcluded.push({ name: "bash", reason });
8814
+ const reason = `destructive shell command blocked for subagent: ${classification.riskWarnings.join("; ")}`;
8815
+ plan.toolsExcluded.push({ name: tool.name, reason });
8579
8816
  return {
8580
8817
  content: [{ type: "text", text: reason }],
8581
8818
  details: {
@@ -8603,10 +8840,6 @@ function filterToolsForSubagent(runtimeTools, parentTools, plan, mode) {
8603
8840
  plan.toolsExcluded.push({ name: tool.name, reason: "not available in parent tool set" });
8604
8841
  continue;
8605
8842
  }
8606
- if (tool.name === "bash") {
8607
- filtered.push(wrapBashGuard(tool, plan));
8608
- continue;
8609
- }
8610
8843
  if (mode === "edit" && (tool.name === "write_file" || tool.name === "edit_file")) {
8611
8844
  filtered.push(tool);
8612
8845
  continue;
@@ -8616,7 +8849,7 @@ function filterToolsForSubagent(runtimeTools, parentTools, plan, mode) {
8616
8849
  plan.toolsExcluded.push({ name: tool.name, reason: policy.reason || "not allowed for subagent" });
8617
8850
  continue;
8618
8851
  }
8619
- filtered.push(tool);
8852
+ filtered.push(getToolCapabilityMetadata(tool)?.executesShell === true ? wrapBashGuard(tool, plan) : tool);
8620
8853
  }
8621
8854
  return filtered;
8622
8855
  }
@@ -8712,12 +8945,13 @@ function createAgentInstance(deps, systemPrompt, tools, model, apiKey, thinkingL
8712
8945
  }
8713
8946
  }), hooks), deps.transientRetryBackoffMs, () => {
8714
8947
  if (hooks.observation) observeDelegationPhase(hooks.observation, "response_retry");
8948
+ hooks.onPhase?.("response_retry");
8715
8949
  }, () => {
8716
8950
  if (hooks.observation) {
8717
8951
  hooks.observation.lastWorkAt = Date.now();
8718
8952
  hooks.observation.lastActivity = "response_event";
8719
8953
  }
8720
- });
8954
+ }, hooks);
8721
8955
  if (deps.createAgent) {
8722
8956
  const agent2 = deps.createAgent({
8723
8957
  streamFn,
@@ -8747,7 +8981,7 @@ function createAgentInstance(deps, systemPrompt, tools, model, apiKey, thinkingL
8747
8981
  return agent;
8748
8982
  }
8749
8983
  function isParentHandoffRequired(task) {
8750
- return task.mode === "edit" || task.status === "timeout" || task.status === "failed" || task.confidenceSignals.some((signal) => signal.type === "tool_error" || signal.type === "no_tool_use" || signal.type === "no_text_report");
8984
+ return task.report?.availability !== "available" || task.mode === "edit" || task.status === "timeout" || task.status === "failed" || task.confidenceSignals.some((signal) => signal.type === "tool_error" || signal.type === "no_tool_use" || signal.type === "no_text_report");
8751
8985
  }
8752
8986
  function shellFromRecord(record) {
8753
8987
  return {
@@ -8761,7 +8995,12 @@ function shellFromRecord(record) {
8761
8995
  timeoutSeconds: record.timeoutSeconds,
8762
8996
  agent: null,
8763
8997
  pendingMessages: record.messages,
8764
- reports: Array.isArray(record.reports) ? record.reports.slice(-4).map((report) => parseDelegationReport(typeof report?.raw === "string" ? report.raw : void 0, typeof report?.runId === "string" ? report.runId : "legacy", report?.availability === "truncated")) : void 0,
8998
+ reports: Array.isArray(record.reports) ? record.reports.slice(-4).map((report) => {
8999
+ const parsed = parseDelegationReport(typeof report?.raw === "string" ? report.raw : void 0, typeof report?.runId === "string" ? report.runId : "legacy", report?.stopReason ?? report?.availability === "truncated");
9000
+ if (["empty", "invalid_json", "invalid_shape", "char_cap", "provider_length", "error", "aborted", "deadline", "cancelled", "no_terminal"].includes(report?.reason ?? "")) parsed.reason = report.reason;
9001
+ return parsed;
9002
+ }) : void 0,
9003
+ usageRuns: restoreSubagentUsageRuns(record.usageRuns),
8765
9004
  createdAt: record.createdAt,
8766
9005
  lastUsedAt: record.lastUsedAt,
8767
9006
  runCount: record.runCount,
@@ -8843,7 +9082,7 @@ async function persistSubagentSession(deps, session, task) {
8843
9082
  if (task.status === "completed" && task.result && transcriptChars > SESSION_TRANSCRIPT_WARN_CHARS) {
8844
9083
  task.result += `
8845
9084
 
8846
- [Session growth warning: session ${session.id} now carries ~${Math.round(transcriptChars / 4)} tokens of transcript; every recall pays for the whole history. For the next round, consider a fresh subagent with a compact brief instead of recalling.]`;
9085
+ [Session growth warning: session ${session.id} now carries approximately ${transcriptChars} JSON characters of transcript (not billing tokens); full recall carries that history, report recall excludes it. Cost is unknown. For the next round, consider a fresh subagent with a compact brief instead of recalling.]`;
8847
9086
  }
8848
9087
  if (!deps.sessionStore) return;
8849
9088
  if (transcriptChars > SESSION_RECORD_MAX_CHARS) {
@@ -8875,6 +9114,13 @@ async function runSubagentWithProgress(deps, task, reportProgress, signal, sessi
8875
9114
  let heartbeat;
8876
9115
  let session;
8877
9116
  let runStarted = false;
9117
+ let runUsage;
9118
+ const shownPhases = /* @__PURE__ */ new Set();
9119
+ const phaseProgress = (phase) => {
9120
+ if (shownPhases.has(phase) || shownPhases.size >= 16) return;
9121
+ shownPhases.add(phase);
9122
+ report({ message: `[${task.name}] Phase: ${phase}` });
9123
+ };
8878
9124
  let baseline = 0;
8879
9125
  let hardDeadlineAt;
8880
9126
  let pendingRun;
@@ -8883,7 +9129,9 @@ async function runSubagentWithProgress(deps, task, reportProgress, signal, sessi
8883
9129
  if (!runStarted || !session?.agent) return;
8884
9130
  const last = [...session.agent.state.messages.slice(baseline)].reverse().find((m) => m?.role === "assistant");
8885
9131
  const terminal = isReportTerminal(last) ? last : void 0;
8886
- task.report = parseDelegationReport(terminal ? collectLastAssistantText([terminal]) || void 0 : void 0, `${task.id}:${session.runCount + 1}`, ["length", "error", "aborted"].includes(terminal?.stopReason));
9132
+ const hooks = agentRunHooks.get(session.agent);
9133
+ const capturedText = terminal && hooks?.terminalStopReason === terminal.stopReason ? hooks?.terminalText : void 0;
9134
+ task.report = parseDelegationReport(terminal ? collectLastAssistantText([terminal]) || capturedText || void 0 : void 0, `${task.id}:${session.runCount + 1}`, terminal?.stopReason ?? (timedOut ? "deadline" : signal?.aborted ? "cancelled" : "no_terminal"));
8887
9135
  };
8888
9136
  let agentSettled = true;
8889
9137
  let releaseLease;
@@ -8926,7 +9174,12 @@ async function runSubagentWithProgress(deps, task, reportProgress, signal, sessi
8926
9174
  }
8927
9175
  const nextModelOverride = task.modelOverride ?? session.modelOverride;
8928
9176
  const nextThinking = task.thinkingLevel ?? session.thinkingLevel;
8929
- if (task.resumeMode === "report" && session.reports?.at(-1)?.availability !== "available") throw new Error("Report recall unavailable: the latest run has no valid report.");
9177
+ const latestReport = session.reports?.at(-1);
9178
+ if (task.resumeMode === "report" && latestReport?.availability !== "available") throw new Error(`Report recall unavailable: session=${session.id}, run=${latestReport?.runId ?? session.runCount}, availability=${latestReport?.availability ?? "missing"}, reason=${latestReport?.reason ?? "legacy_or_missing"}. Explicitly choose resumeMode:"full" (full history; cost unknown), or spawn a new bounded task. No older report or transcript fallback was used.`);
9179
+ const history = session.agent?.state.messages ?? session.pendingMessages ?? [];
9180
+ const historyChars = task.resumeMode === "report" ? 0 : estimateTranscriptChars(history);
9181
+ task.recallInput = { mode: task.resumeMode ?? "full", messageCount: task.resumeMode === "report" ? 0 : history.length, jsonChars: historyChars, warning: historyChars > SESSION_TRANSCRIPT_WARN_CHARS, cost: "unknown" };
9182
+ report({ message: task.resumeMode === "report" ? `[${task.name}] Report recall: old transcript is not carried into the prompt; cost unknown.` : `[${task.name}] Full recall input: ${task.recallInput.messageCount} historical messages, approximately ${task.recallInput.jsonChars} JSON characters (not billing tokens); cost unknown.${task.recallInput.warning ? " Large history: context risk; consider report recall or a new bounded task." : ""}` });
8930
9183
  const mustRebuild = task.resumeMode === "report" || session.agent === null || task.requestedMode !== void 0 || task.modelOverride !== void 0 || task.thinkingLevel !== void 0;
8931
9184
  if (!mustRebuild) {
8932
9185
  subagent = session.agent;
@@ -9030,6 +9283,14 @@ async function runSubagentWithProgress(deps, task, reportProgress, signal, sessi
9030
9283
  if (sessions) retainLiveSession(sessions, session, Math.max(1, deps.maxLiveSessions ?? DEFAULT_MAX_LIVE_SESSIONS));
9031
9284
  if (signal?.aborted || sessions?.disposed) throw new Error("Subagent capability disposed or aborted by parent");
9032
9285
  const hooks = agentRunHooks.get(subagent);
9286
+ runUsage = createSubagentUsage(session.id, `${task.id}:${session.runCount + 1}`);
9287
+ (task.usageRuns ??= []).push(runUsage);
9288
+ if (hooks) {
9289
+ hooks.usage = runUsage;
9290
+ hooks.onPhase = phaseProgress;
9291
+ hooks.terminalText = void 0;
9292
+ hooks.terminalStopReason = void 0;
9293
+ }
9033
9294
  hardDeadlineAt = Date.now() + runTimeoutSeconds * 1e3;
9034
9295
  if (hooks) {
9035
9296
  hooks.observation = task.observation;
@@ -9053,6 +9314,7 @@ async function runSubagentWithProgress(deps, task, reportProgress, signal, sessi
9053
9314
  softTimeout = setTimeout(() => {
9054
9315
  if (hooks) hooks.finalizing = true;
9055
9316
  observeDelegationPhase(task.observation, "finalizing", false);
9317
+ phaseProgress("finalizing");
9056
9318
  }, subagentSoftDeadlineMs(runTimeoutSeconds * 1e3));
9057
9319
  timeout = setTimeout(() => {
9058
9320
  timedOut = true;
@@ -9156,7 +9418,7 @@ ${DELEGATION_REPORT_INSTRUCTION}`;
9156
9418
  const terminalStopReason = lastAssistant?.stopReason;
9157
9419
  const terminalError = typeof lastAssistant?.errorMessage === "string" ? lastAssistant.errorMessage : void 0;
9158
9420
  const collectedResponse = isReportTerminal(lastAssistant) ? collectLastAssistantText([lastAssistant]) || void 0 : void 0;
9159
- task.report = parseDelegationReport(collectedResponse, `${task.id}:${(session?.runCount ?? 0) + 1}`, terminalStopReason === "length" || terminalStopReason === "error" || terminalStopReason === "aborted");
9421
+ task.report = parseDelegationReport(collectedResponse, `${task.id}:${(session?.runCount ?? 0) + 1}`, terminalStopReason ?? "no_terminal");
9160
9422
  const streamInterrupted = terminalStopReason === "error" || terminalStopReason === "aborted" || terminalError || !collectedResponse && terminalStopReason !== "stop" || terminalStopReason === "length" && !messageHasOwnText(lastAssistant);
9161
9423
  if (streamInterrupted) {
9162
9424
  if (terminalStopReason === "length") {
@@ -9205,6 +9467,7 @@ ${DELEGATION_REPORT_INSTRUCTION}`;
9205
9467
  refreshReport();
9206
9468
  task.status = timedOut ? "timeout" : signal?.aborted ? "cancelled" : "failed";
9207
9469
  observeDelegationPhase(task.observation, timedOut ? "deadline" : signal?.aborted ? "cancelled" : "failed");
9470
+ phaseProgress(task.observation.phase);
9208
9471
  task.error = errorMessage.slice(0, 2e3);
9209
9472
  task.report ??= parseDelegationReport(void 0, task.id);
9210
9473
  task.parentHandoffRequired = isParentHandoffRequired(task);
@@ -9214,14 +9477,21 @@ ${DELEGATION_REPORT_INSTRUCTION}`;
9214
9477
  if (heartbeat) {
9215
9478
  clearInterval(heartbeat);
9216
9479
  }
9480
+ if (runUsage) settleSubagentUsage(runUsage, agentSettled);
9217
9481
  const finalize = async () => {
9482
+ if (runUsage) settleSubagentUsage(runUsage, true);
9218
9483
  try {
9219
9484
  if (session) {
9220
9485
  const hooks = session.agent ? agentRunHooks.get(session.agent) : void 0;
9221
- if (hooks && hooks.observation === task.observation) hooks.observation = void 0;
9486
+ if (hooks && hooks.observation === task.observation) {
9487
+ hooks.observation = void 0;
9488
+ hooks.onPhase = void 0;
9489
+ hooks.usage = void 0;
9490
+ }
9222
9491
  if (runStarted) {
9223
9492
  refreshReport();
9224
9493
  session.runCount++;
9494
+ if (runUsage) session.usageRuns = [...session.usageRuns ?? [], runUsage].slice(-4);
9225
9495
  session.reports = [...session.reports ?? [], task.report ?? parseDelegationReport(void 0, task.id)].slice(-4);
9226
9496
  }
9227
9497
  session.lastUsedAt = Date.now();
@@ -9272,7 +9542,9 @@ function buildParallelResult(tasks, totalDuration) {
9272
9542
  if (task.details) {
9273
9543
  resultText += `, ${task.details.durationMs}ms, ${task.details.toolCalls} tools`;
9274
9544
  }
9275
- resultText += `, report=${task.report?.availability ?? "missing"})
9545
+ resultText += `, report=${task.report?.availability ?? "missing"}${task.report?.reason ? ` (${task.report.reason})` : ""})
9546
+ `;
9547
+ for (const usage of task.usageRuns ?? []) resultText += `Child usage ${usage.sessionId}/${usage.runId}: ${usage.status}, ${usage.attempts} attempts (${usage.reportedAttempts} reported, ${usage.unknownAttempts} unknown, ${usage.pendingAttempts} pending); reported token subtotal=${usage.reportedTokens.totalTokens}; estimated cost=${usage.estimatedCost ?? "unknown"}. Not added to parent ledger.
9276
9548
  `;
9277
9549
  if (task.observation) resultText += `Phase: ${task.observation.phase}; last activity: ${task.observation.lastActivity} at ${task.observation.lastWorkAt}
9278
9550
  `;
@@ -9468,7 +9740,7 @@ Default mode="read" is for investigation, search, review, or triage and cannot e
9468
9740
  thinkingLevel: Type.Optional(Type.Union([Type.Literal("off"), Type.Literal("minimal"), Type.Literal("low"), Type.Literal("medium"), Type.Literal("high"), Type.Literal("xhigh")], { description: "off|minimal|low|medium|high|xhigh. Default medium; override only when the user asks or for cheap lookups (off)." })),
9469
9741
  timeout: Type.Optional(Type.Number({ description: "Seconds. Default 1200; set higher for long tasks." })),
9470
9742
  resumeMode: Type.Optional(Type.Union([Type.Literal("full"), Type.Literal("report")], { description: "Default full retains prior transcript. Explicit report rebuilds with current tools/model/credentials and only the latest valid historical report as data plus the new task; fails if unavailable." })),
9471
- sessionId: Type.Optional(Type.String({ description: "Continue a previous subagent session by id (from an earlier result's Session line); accumulated context makes iteration cheaper. Optional mode/model/thinkingLevel reconfigure it; one task at a time; recalls never replay on failure and must target an idle session; prefer a fresh task for independent reviews." }))
9743
+ sessionId: Type.Optional(Type.String({ description: "Continue a previous subagent session by id (from an earlier result's Session line); full mode retains historical input with unknown cost. Optional mode/model/thinkingLevel reconfigure it; one task at a time; recalls never replay on failure and must target an idle session; prefer a fresh task for independent reviews." }))
9472
9744
  }
9473
9745
  // No additionalProperties lock: legacy callers may send task aliases
9474
9746
  // (description/prompt/objective); normalizeTaskDefs resolves them at
@@ -9598,6 +9870,8 @@ ${SPAWN_USAGE_HINT}` }],
9598
9870
  reportedEvidenceCount: task.reportedEvidence.length,
9599
9871
  confidenceSignalCount: task.confidenceSignals.length,
9600
9872
  report: task.report,
9873
+ recallInput: task.recallInput,
9874
+ usageRuns: task.usageRuns?.map((usage) => ({ ...usage, reportedTokens: { ...usage.reportedTokens } })),
9601
9875
  observation: task.observation,
9602
9876
  parentHandoffRequired: task.parentHandoffRequired,
9603
9877
  failureReason: task.error,
@@ -10945,7 +11219,7 @@ function createReadTool(cwd, options) {
10945
11219
  label: "read",
10946
11220
  description: `Read a file's contents. Use this before editing, and use offset/limit for large files, or tail=N to read the last N lines when the line count is unknown. Images are attached for the current model when it supports vision; otherwise a configured file-understanding service analyzes them. Video, audio, and documents require a configured understanding service. Pass question to specify what to focus on; it does not force a separate model request. Example: read_file path='Scripts/PlayerController.cs' or read_file path='Data/config.json'. ${pathResolutionDescription} For text files, output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). When you need the full file, continue with a larger offset until complete.`,
10947
11221
  parameters: readSchema,
10948
- execute: async (_toolCallId, params, signal) => {
11222
+ execute: async (_toolCallId, params, signal, _onUpdate) => {
10949
11223
  const { path: path3, root, question } = params;
10950
11224
  const tail = params.tail;
10951
11225
  let offset = params.offset;
@@ -10972,7 +11246,7 @@ function createReadTool(cwd, options) {
10972
11246
  const message = error instanceof Error ? error.message : String(error);
10973
11247
  if (/outside sandbox|outside root|escapes sandbox/i.test(message)) {
10974
11248
  return createReadErrorResult(
10975
- `${message}. read_file is workspace-scoped; for intentional runtime logs or temporary files outside the workspace, use bash with a bounded command such as head, tail, sed -n, or cat on that path.`
11249
+ `${message}. read_file is workspace-scoped; for intentional runtime logs or temporary files outside the workspace, use the shell tool with a bounded command such as head, tail, sed -n, or cat on that path.`
10976
11250
  );
10977
11251
  }
10978
11252
  return createReadErrorResult(message);
@@ -16881,7 +17155,7 @@ function unavailableResult(query, requestedMode, reason) {
16881
17155
  text: [
16882
17156
  `Web search is unavailable for "${query}".`,
16883
17157
  reason,
16884
- "Do not use bash, curl, Python, or DuckDuckGo scraping as a fallback; report that web_search is unavailable or ask the user to configure a provider that supports native web search."
17158
+ "Do not use shell, curl, Python, or DuckDuckGo scraping as a fallback; report that web_search is unavailable or ask the user to configure a provider that supports native web search."
16885
17159
  ].join("\n")
16886
17160
  }],
16887
17161
  details: {
@@ -17389,7 +17663,7 @@ function createSharedWebSearchTool(deps) {
17389
17663
  return {
17390
17664
  name: "web_search",
17391
17665
  label: "web_search",
17392
- description: "Search the public web using a provider-native web search capability. Use for current information, online research, or documentation outside the local workspace. Results include sources. Do not use bash/curl/python as a web-search fallback. Only set domain filters when the user explicitly asks to limit sources.",
17666
+ description: "Search the public web using a provider-native web search capability. Use for current information, online research, or documentation outside the local workspace. Results include sources. Do not use shell/curl/python as a web-search fallback. Only set domain filters when the user explicitly asks to limit sources.",
17393
17667
  parameters: webSearchSchema,
17394
17668
  execute: async (_toolCallId, rawParams, signal) => {
17395
17669
  const input = rawParams;
@@ -17980,7 +18254,7 @@ function createSharedWebFetchTool(deps = {}) {
17980
18254
  return {
17981
18255
  name: "web_fetch",
17982
18256
  label: "web_fetch",
17983
- description: "Fetch and read a specific public web URL. Use web_search to discover sources, then web_fetch to inspect a chosen URL. Do not use bash/curl/Python as a fallback for web page fetching.",
18257
+ description: "Fetch and read a specific public web URL. Use web_search to discover sources, then web_fetch to inspect a chosen URL. Do not use shell/curl/Python as a fallback for web page fetching.",
17984
18258
  parameters: webFetchSchema,
17985
18259
  capabilityMetadata: {
17986
18260
  riskClass: "network",
@@ -18604,7 +18878,7 @@ function createSharedWebResearchTool(deps) {
18604
18878
  return {
18605
18879
  name: "web_research",
18606
18880
  label: "web_research",
18607
- description: "Canonical web tool. For normal research, pass query. To read a known page, pass url. For low-level search results only, pass query with mode='search'. Use for current information, official docs, news, releases, issues, and webpage summaries. Do not use bash/curl/Python as a web fallback.",
18881
+ description: "Canonical web tool. For normal research, pass query. To read a known page, pass url. For low-level search results only, pass query with mode='search'. Use for current information, official docs, news, releases, issues, and webpage summaries. Do not use shell/curl/Python as a web fallback.",
18608
18882
  parameters: webResearchSchema,
18609
18883
  capabilityMetadata: {
18610
18884
  riskClass: "network",
@@ -19175,6 +19449,8 @@ export {
19175
19449
  findFirstKeptEntrySummaryId,
19176
19450
  createCompactionSummary,
19177
19451
  generateEntryId,
19452
+ createHostEnvelopeProjectorRegistry,
19453
+ projectHostEnvelopes,
19178
19454
  FileSessionStore,
19179
19455
  createSessionManager,
19180
19456
  prepareSessionCompaction,
@@ -19215,6 +19491,7 @@ export {
19215
19491
  supersedeExecutionState,
19216
19492
  shouldPreserveExecutionStateForUserText,
19217
19493
  BUILTIN_TOOL_CAPABILITY_METADATA,
19494
+ getToolCapabilityMetadata,
19218
19495
  isToolAllowedInPlanMode,
19219
19496
  extractPlanTodoItems,
19220
19497
  isPlanModeSafeCommand,