mixdog 0.9.37 → 0.9.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/package.json +9 -4
  2. package/scripts/abort-recovery-test.mjs +43 -2
  3. package/scripts/agent-tag-reuse-smoke.mjs +146 -6
  4. package/scripts/agent-terminal-reap-test.mjs +127 -0
  5. package/scripts/agent-trace-io-test.mjs +69 -0
  6. package/scripts/code-graph-disk-hit-test.mjs +224 -0
  7. package/scripts/dispatch-persist-recovery-test.mjs +141 -0
  8. package/scripts/execution-completion-dedup-test.mjs +157 -0
  9. package/scripts/execution-pending-resume-kick-test.mjs +57 -2
  10. package/scripts/execution-resume-esc-integration-test.mjs +174 -0
  11. package/scripts/explore-bench.mjs +101 -8
  12. package/scripts/explore-prompt-policy-test.mjs +156 -11
  13. package/scripts/find-fuzzy-hidden-test.mjs +122 -0
  14. package/scripts/internal-comms-bench-test.mjs +226 -0
  15. package/scripts/internal-comms-bench.mjs +185 -58
  16. package/scripts/internal-comms-smoke.mjs +171 -23
  17. package/scripts/live-worker-smoke.mjs +38 -2
  18. package/scripts/memory-cycle-routing-test.mjs +111 -0
  19. package/scripts/memory-rule-contract-test.mjs +93 -0
  20. package/scripts/notify-completion-mirror-test.mjs +73 -0
  21. package/scripts/output-style-smoke.mjs +2 -2
  22. package/scripts/rg-runner-test.mjs +240 -0
  23. package/scripts/routing-corpus-test.mjs +349 -0
  24. package/scripts/routing-corpus.mjs +211 -32
  25. package/scripts/session-orphan-sweep-test.mjs +83 -0
  26. package/scripts/session-sweep.mjs +266 -0
  27. package/scripts/steering-drain-buckets-test.mjs +179 -0
  28. package/scripts/tool-smoke.mjs +21 -13
  29. package/scripts/tool-tui-presentation-test.mjs +202 -0
  30. package/src/agents/heavy-worker/AGENT.md +10 -7
  31. package/src/agents/reviewer/AGENT.md +6 -4
  32. package/src/agents/worker/AGENT.md +7 -5
  33. package/src/rules/agent/00-common.md +4 -4
  34. package/src/rules/agent/00-core.md +11 -14
  35. package/src/rules/agent/20-skip-protocol.md +3 -3
  36. package/src/rules/agent/30-explorer.md +56 -48
  37. package/src/rules/agent/40-cycle1-agent.md +15 -24
  38. package/src/rules/agent/41-cycle2-agent.md +33 -57
  39. package/src/rules/agent/42-cycle3-agent.md +28 -42
  40. package/src/rules/lead/01-general.md +7 -10
  41. package/src/rules/lead/lead-brief.md +11 -14
  42. package/src/rules/lead/lead-tool.md +6 -5
  43. package/src/rules/shared/01-tool.md +44 -41
  44. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +37 -1
  45. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +20 -5
  46. package/src/runtime/agent/orchestrator/dispatch-persist.mjs +31 -8
  47. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +13 -7
  48. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +21 -1
  49. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +6 -6
  50. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +17 -38
  51. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +5 -4
  52. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +119 -3
  53. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +17 -8
  54. package/src/runtime/agent/orchestrator/session/context-utils.mjs +270 -78
  55. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +2 -1
  56. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +18 -52
  57. package/src/runtime/agent/orchestrator/session/manager/delivered-completions.mjs +123 -0
  58. package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +15 -2
  59. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +41 -2
  60. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +1 -1
  61. package/src/runtime/agent/orchestrator/session/store.mjs +339 -63
  62. package/src/runtime/agent/orchestrator/stall-policy.mjs +37 -0
  63. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +12 -1
  64. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +25 -20
  65. package/src/runtime/agent/orchestrator/tools/builtin/fs-reachability.mjs +6 -2
  66. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +118 -53
  67. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +106 -29
  68. package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +8 -1
  69. package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +4 -2
  70. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +3 -1
  71. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +4 -1
  72. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +33 -2
  73. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +151 -64
  74. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +37 -13
  75. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +84 -49
  76. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +172 -23
  77. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +3 -0
  78. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +68 -5
  79. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +17 -3
  80. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +24 -10
  81. package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +6 -3
  82. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +4 -7
  83. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +1 -0
  84. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +1 -1
  85. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +4 -4
  86. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +4 -3
  87. package/src/runtime/memory/lib/memory-cycle3.mjs +12 -2
  88. package/src/runtime/shared/tool-primitives.mjs +4 -1
  89. package/src/runtime/shared/tool-status.mjs +27 -0
  90. package/src/runtime/shared/tool-surface.mjs +6 -3
  91. package/src/session-runtime/config-helpers.mjs +14 -0
  92. package/src/session-runtime/context-status.mjs +1 -0
  93. package/src/session-runtime/effort.mjs +6 -2
  94. package/src/session-runtime/model-recency.mjs +5 -2
  95. package/src/session-runtime/provider-models.mjs +3 -3
  96. package/src/session-runtime/runtime-core.mjs +78 -10
  97. package/src/session-runtime/tool-catalog.mjs +34 -0
  98. package/src/session-runtime/warmup-schedulers.mjs +7 -1
  99. package/src/standalone/agent-tool/notify.mjs +13 -0
  100. package/src/standalone/agent-tool.mjs +45 -69
  101. package/src/standalone/explore-tool.mjs +6 -7
  102. package/src/tui/App.jsx +31 -0
  103. package/src/tui/app/model-options.mjs +5 -3
  104. package/src/tui/app/model-picker.mjs +12 -24
  105. package/src/tui/app/transcript-window.mjs +1 -0
  106. package/src/tui/components/ToolExecution.jsx +11 -6
  107. package/src/tui/components/TranscriptItem.jsx +1 -1
  108. package/src/tui/components/tool-execution/surface-detail.mjs +24 -10
  109. package/src/tui/components/tool-execution/text-format.mjs +10 -19
  110. package/src/tui/dist/index.mjs +533 -143
  111. package/src/tui/engine/agent-job-feed.mjs +153 -16
  112. package/src/tui/engine/agent-response-tail.mjs +68 -0
  113. package/src/tui/engine/notification-plan.mjs +16 -0
  114. package/src/tui/engine/queue-helpers.mjs +8 -0
  115. package/src/tui/engine/session-api.mjs +8 -2
  116. package/src/tui/engine/session-flow.mjs +34 -2
  117. package/src/tui/engine/tool-card-results.mjs +54 -32
  118. package/src/tui/engine/tool-result-status.mjs +75 -21
  119. package/src/tui/engine/turn.mjs +83 -43
  120. package/src/tui/engine.mjs +63 -2
  121. package/src/workflows/bench/WORKFLOW.md +25 -35
  122. package/src/workflows/default/WORKFLOW.md +38 -32
  123. package/src/workflows/solo/WORKFLOW.md +19 -22
  124. package/scripts/_jitter-fuzz.mjs +0 -44410
  125. package/scripts/_jitter-fuzz2.mjs +0 -44400
  126. package/scripts/_jitter-probe.mjs +0 -44397
  127. package/scripts/_jp2.mjs +0 -45614
@@ -1976,10 +1976,11 @@ function bridgeAgentModelSummary(args) {
1976
1976
  ]);
1977
1977
  }
1978
1978
  function summarizeLineWindow(a) {
1979
+ const hasOffset = a.offset != null;
1979
1980
  const offset = a.offset ?? a.start_line ?? a.startLine ?? a.line;
1980
1981
  const limit = a.limit ?? a.line_count ?? a.lineCount ?? a.lines;
1981
1982
  if (offset == null && limit == null) return "";
1982
- const start = Number(offset);
1983
+ const start = Number(offset) + (hasOffset ? 1 : 0);
1983
1984
  const count = Number(limit);
1984
1985
  if (Number.isFinite(start) && Number.isFinite(count) && count > 0) {
1985
1986
  return `lines ${start}-${Math.max(start, start + count - 1)}`;
@@ -2839,7 +2840,9 @@ function toolWorkUnit(name, args = {}, category = "") {
2839
2840
  case "read_mcp_resource":
2840
2841
  return unitDescriptor("Read", { count: queryCount(a, "uri", "uris") || 1, noun: "resource" });
2841
2842
  case "apply_patch": {
2842
- const creating = a.old_string === "";
2843
+ const patchText = String(a.patch ?? "");
2844
+ const creating = a.old_string === "" || /^\*\*\*\s+Add File:/mi.test(patchText);
2845
+ const deleting = !creating && a.new_string === "" && a.old_string != null || /^\*\*\*\s+Delete File:/mi.test(patchText);
2843
2846
  if (a.dry_run === true) {
2844
2847
  return unitDescriptor("Patch", {
2845
2848
  count: patchFileCount(a) || 1,
@@ -2850,8 +2853,8 @@ function toolWorkUnit(name, args = {}, category = "") {
2850
2853
  }
2851
2854
  return unitDescriptor("Patch", {
2852
2855
  count: patchFileCount(a) || 1,
2853
- active: creating ? "Creating" : "Editing",
2854
- done: creating ? "Created" : "Edited",
2856
+ active: creating ? "Creating" : deleting ? "Deleting" : "Editing",
2857
+ done: creating ? "Created" : deleting ? "Deleted" : "Edited",
2855
2858
  noun: "file"
2856
2859
  });
2857
2860
  }
@@ -10233,6 +10236,7 @@ function toolHasDisplayResultForRows(item) {
10233
10236
  }
10234
10237
  function toolExpandedRawTextForRows(item, rawRt) {
10235
10238
  if (item?.aggregate) return rawRt;
10239
+ if (item?.agentResponseAggregate) return rawRt;
10236
10240
  const hasDisplayResult = toolHasDisplayResultForRows(item);
10237
10241
  if (hasDisplayResult) return toolDisplayedResultTextForRows(item);
10238
10242
  return stripLeadingStatusMarkerFromTextForRows(rawRt || "");
@@ -12383,10 +12387,11 @@ var compareModelRecency = (a, b) => {
12383
12387
  }
12384
12388
  const ta = releaseTime(a);
12385
12389
  const tb = releaseTime(b);
12386
- if (ta !== tb) return tb - ta;
12387
- if (!!a?.latest !== !!b?.latest) return a?.latest ? -1 : 1;
12388
12390
  const versionDelta = compareModelVersion(a, b);
12391
+ if (ta > 0 && tb > 0 && ta !== tb) return tb - ta;
12389
12392
  if (versionDelta) return versionDelta;
12393
+ if (!!a?.latest !== !!b?.latest) return a?.latest ? -1 : 1;
12394
+ if (ta !== tb) return tb - ta;
12390
12395
  return String(a?.display || a?.id || "").localeCompare(String(b?.display || b?.id || ""));
12391
12396
  };
12392
12397
  var modelFamily = (m) => {
@@ -15835,11 +15840,9 @@ function createModelPicker({
15835
15840
  setChannelPrompt(null);
15836
15841
  setHookPrompt(null);
15837
15842
  setSettingsPrompt(null);
15838
- const modelPickerRequest = ++modelPickerRequestRef.current;
15843
+ modelPickerRequestRef.current += 1;
15839
15844
  let modelPickerClosed = false;
15840
- let activeModelProvider = null;
15841
15845
  let providerListHighlightProvider = null;
15842
- const isActiveModelPicker = () => !modelPickerClosed && modelPickerRequestRef.current === modelPickerRequest;
15843
15846
  const returnTo = typeof options.returnTo === "function" ? options.returnTo : null;
15844
15847
  const returnLabel = String(options.returnLabel || "Agents");
15845
15848
  const returnOnNestedCancel = options.returnOnNestedCancel === true;
@@ -15853,7 +15856,6 @@ function createModelPicker({
15853
15856
  let providerModels = Array.isArray(cacheRef.current.models) ? cacheRef.current.models : [];
15854
15857
  let refreshModelsPromise = null;
15855
15858
  let renderedQuickModels = false;
15856
- let renderActiveProviderModels = null;
15857
15859
  if (!providerModels.length || options.refreshModels === true) {
15858
15860
  setPicker({
15859
15861
  title: options.title || "Model",
@@ -15904,16 +15906,12 @@ function createModelPicker({
15904
15906
  providerListHighlightProvider = renderOptions.highlightProvider;
15905
15907
  }
15906
15908
  const highlightProvider = renderOptions.highlightProvider || providerListHighlightProvider || null;
15907
- activeModelProvider = null;
15908
- renderActiveProviderModels = null;
15909
15909
  const openProviderModelsPicker = (provider) => {
15910
15910
  if (!provider) return;
15911
- activeModelProvider = provider;
15912
- renderActiveProviderModels = () => openProviderModelsPicker(provider);
15913
15911
  const providerModels2 = models.filter((model) => model.provider === provider);
15914
15912
  const preferredEffort = (values = []) => {
15915
15913
  const allowed = values.filter(Boolean);
15916
- for (const value of ["high", "medium", "low", "none", "xhigh", "max"]) {
15914
+ for (const value of ["high", "medium", "low", "none", "xhigh", "max", "ultra"]) {
15917
15915
  if (allowed.includes(value)) return value;
15918
15916
  }
15919
15917
  return allowed[0] || null;
@@ -15986,6 +15984,7 @@ ${model?.id || ""}`;
15986
15984
  if (value === "medium") return "\u25D1";
15987
15985
  if (value === "high") return "\u25D5";
15988
15986
  if (value === "max") return "\u25C6";
15987
+ if (value === "ultra") return "\u2726";
15989
15988
  return "\u25CF";
15990
15989
  };
15991
15990
  const effortColor = (value) => {
@@ -15994,6 +15993,7 @@ ${model?.id || ""}`;
15994
15993
  if (value === "medium") return theme.claude;
15995
15994
  if (value === "high") return theme.error;
15996
15995
  if (value === "max") return theme.permission;
15996
+ if (value === "ultra") return theme.permission;
15997
15997
  return theme.error;
15998
15998
  };
15999
15999
  const modelFooter = (model = null) => {
@@ -16128,33 +16128,23 @@ ${model?.id || ""}`;
16128
16128
  });
16129
16129
  };
16130
16130
  renderModelPicker();
16131
- const applyFreshModels = (freshModels) => {
16132
- if (!isActiveModelPicker()) return;
16131
+ const adoptFreshModels = (freshModels) => {
16133
16132
  if (!Array.isArray(freshModels) || freshModels.length === 0) return;
16134
- providerModels = freshModels;
16135
- models = normalizeModelOptions(providerModels);
16136
- cacheRef.current = { models: providerModels, at: Date.now() };
16137
- if (activeModelProvider === null) {
16138
- renderModelPicker();
16139
- } else if (typeof renderActiveProviderModels === "function") {
16140
- renderActiveProviderModels();
16141
- }
16133
+ cacheRef.current = { models: freshModels, at: Date.now() };
16142
16134
  };
16143
16135
  if (renderedQuickModels && refreshModelsPromise) {
16144
- void refreshModelsPromise.then(applyFreshModels).catch(() => {
16136
+ void refreshModelsPromise.then(adoptFreshModels).catch(() => {
16145
16137
  });
16146
16138
  } else if (cacheIsStale) {
16147
16139
  if (!providerModelsTtlRefreshPromise) {
16148
16140
  providerModelsTtlRefreshPromise = Promise.resolve(loadModels({ force: true })).then((freshModels) => {
16149
- if (Array.isArray(freshModels) && freshModels.length > 0) {
16150
- cacheRef.current = { models: freshModels, at: Date.now() };
16151
- }
16141
+ adoptFreshModels(freshModels);
16152
16142
  return freshModels;
16153
16143
  }).finally(() => {
16154
16144
  providerModelsTtlRefreshPromise = null;
16155
16145
  });
16156
16146
  }
16157
- void providerModelsTtlRefreshPromise.then(applyFreshModels).catch(() => {
16147
+ void providerModelsTtlRefreshPromise.catch(() => {
16158
16148
  });
16159
16149
  }
16160
16150
  };
@@ -18531,6 +18521,29 @@ import stringWidth9 from "string-width";
18531
18521
 
18532
18522
  // src/tui/components/tool-execution/text-format.mjs
18533
18523
  import stripAnsi6 from "strip-ansi";
18524
+
18525
+ // src/runtime/shared/tool-status.mjs
18526
+ function normalizeToolTerminalStatus(value) {
18527
+ const raw = String(value || "").trim().toLowerCase();
18528
+ if (!raw) return "";
18529
+ if (/^(running|pending|queued|in_progress|in-progress)$/.test(raw)) return "running";
18530
+ if (/^(completed|complete|done|success|succeeded|ok)$/.test(raw)) return "completed";
18531
+ if (/^(failed|fail|error|errored|timeout|timed_out|killed)$/.test(raw)) return "failed";
18532
+ if (/^(cancelled|canceled|cancel)$/.test(raw)) return "cancelled";
18533
+ if (/^(denied|deny|refused|rejected)$/.test(raw)) return "denied";
18534
+ return "";
18535
+ }
18536
+ function toolResultTerminalStatus(text) {
18537
+ const body = String(text || "");
18538
+ const tagged = body.match(/<status[^>]*>([\s\S]*?)<\/status>/i)?.[1]?.trim();
18539
+ if (tagged) return normalizeToolTerminalStatus(tagged);
18540
+ const bracketed = body.match(/^\[status:\s*([^\]]*)\]/mi)?.[1]?.trim();
18541
+ if (bracketed) return normalizeToolTerminalStatus(bracketed);
18542
+ const inline = body.match(/^(?:status|state):\s*([^\s·,;]+)/mi)?.[1]?.trim();
18543
+ return normalizeToolTerminalStatus(inline);
18544
+ }
18545
+
18546
+ // src/tui/components/tool-execution/text-format.mjs
18534
18547
  var MIN_RESULT_LINE_CHARS = 24;
18535
18548
  var RESULT_LINE_HARD_MAX = 80;
18536
18549
  var SUMMARY_MAX_CHARS = 48;
@@ -18600,30 +18613,20 @@ function shellResultStatus(value) {
18600
18613
  return match ? String(match[1] || "").toLowerCase() : "";
18601
18614
  }
18602
18615
  function normalizeTerminalStatus(value) {
18603
- const raw = String(value || "").trim().toLowerCase();
18604
- if (!raw) return "";
18605
- if (/^(running|pending|queued|in_progress|in-progress)$/.test(raw)) return "running";
18606
- if (/^(completed|complete|done|success|succeeded|ok)$/.test(raw)) return "completed";
18607
- if (/^(failed|fail|error|errored|timeout|timed_out|killed)$/.test(raw)) return "failed";
18608
- if (/^(cancelled|canceled|cancel)$/.test(raw)) return "cancelled";
18609
- return "";
18616
+ return normalizeToolTerminalStatus(value);
18610
18617
  }
18611
18618
  function displayTerminalStatus(value) {
18619
+ if (String(value || "").trim().toLowerCase() === "exit") return "Exit";
18612
18620
  const status = normalizeTerminalStatus(value);
18613
18621
  if (status === "running") return "Running";
18614
18622
  if (status === "completed") return "Finished";
18615
18623
  if (status === "failed") return "Failed";
18616
18624
  if (status === "cancelled") return "Cancelled";
18625
+ if (status === "denied") return "Denied";
18617
18626
  return "";
18618
18627
  }
18619
18628
  function resultTerminalStatus(value) {
18620
- const text = String(value || "");
18621
- const tagged = text.match(/<status[^>]*>([\s\S]*?)<\/status>/i)?.[1]?.trim();
18622
- if (tagged) return normalizeTerminalStatus(tagged);
18623
- const bracketed = text.match(/^\[status:\s*([^\]]*)\]/mi)?.[1]?.trim();
18624
- if (bracketed) return normalizeTerminalStatus(bracketed);
18625
- const inline = text.match(/^(?:status|state):\s*([^\s·,;]+)/mi)?.[1]?.trim();
18626
- return normalizeTerminalStatus(inline);
18629
+ return toolResultTerminalStatus(value);
18627
18630
  }
18628
18631
  var LEADING_STATUS_MARKER_LINE_RE2 = /^\[status:\s*[^\]]*\]\s*$/i;
18629
18632
  function stripLeadingStatusMarkerLines(lines) {
@@ -18647,11 +18650,15 @@ function isShellTool(normalizedName, label = "") {
18647
18650
  const l = String(label || "").toLowerCase();
18648
18651
  return n === "shell" || n === "bash" || n === "bash_session" || n === "shell_command" || n === "job_wait" || l === "run";
18649
18652
  }
18650
- function shellDisplayStatus({ pending = false, failedCount = 0, isError = false, result = "" } = {}) {
18653
+ function shellDisplayStatus({ pending = false, failedCount = 0, exitFailedCount = 0, isError = false, result = "" } = {}) {
18651
18654
  const status = shellResultStatus(result);
18652
18655
  if (pending || /^(running|pending|queued)$/.test(status)) return "running";
18653
18656
  if (/^cancel/.test(status)) return "cancelled";
18654
- if (/^(failed|error|killed|timeout)$/.test(status) || isError || failedCount > 0) return "failed";
18657
+ if (/^(failed|error|killed|timeout)$/.test(status)) return "failed";
18658
+ const realFailed = Math.max(0, Number(failedCount) - Number(exitFailedCount));
18659
+ if (realFailed > 0) return "failed";
18660
+ if (Number(exitFailedCount) > 0) return "exit";
18661
+ if (isError || failedCount > 0) return "failed";
18655
18662
  return "completed";
18656
18663
  }
18657
18664
  function shellHeader(status, count = 1) {
@@ -18707,7 +18714,9 @@ function withModelAndTag(label, args) {
18707
18714
  function joinActionAgent(action, agent) {
18708
18715
  return agent ? `${action} ${agent}` : action;
18709
18716
  }
18710
- function agentResponseTitle(args) {
18717
+ function agentResponseTitle(args, count = 1) {
18718
+ const total = Math.max(1, Number(count) || 1);
18719
+ if (total > 1) return `Responses ${total} agents`;
18711
18720
  const name = titleizeAgentName(args?.agent || args?.subagent_type || args?.name || "");
18712
18721
  return withModelAndTag(joinActionAgent("Response", name), args);
18713
18722
  }
@@ -18962,13 +18971,17 @@ function clampFailureCount(errorCount, groupCount, isError) {
18962
18971
  if (Number.isFinite(explicit)) return Math.max(0, Math.min(groupCount, Math.floor(explicit)));
18963
18972
  return isError ? groupCount : 0;
18964
18973
  }
18965
- function toolStatusColor({ pending, groupCount, callFailedCount = 0, terminalStatus = "" }) {
18974
+ function toolStatusColor({ pending, groupCount, callFailedCount = 0, exitFailedCount = 0, terminalStatus = "" }) {
18966
18975
  if (pending) return theme.text;
18967
18976
  const status = normalizeTerminalStatus(terminalStatus);
18968
18977
  if (status === "cancelled") return theme.warning;
18969
- if (callFailedCount <= 0) return theme.success;
18970
- if (groupCount > 1 && callFailedCount < groupCount) return theme.mixdogOrange || theme.warning;
18971
- return theme.error;
18978
+ if (status === "denied") return theme.warning;
18979
+ if (callFailedCount > 0) {
18980
+ if (groupCount > 1 && callFailedCount < groupCount) return theme.mixdogOrange || theme.warning;
18981
+ return theme.error;
18982
+ }
18983
+ if (exitFailedCount > 0) return theme.warning;
18984
+ return theme.success;
18972
18985
  }
18973
18986
 
18974
18987
  // src/tui/components/tool-execution/ResultBody.jsx
@@ -18999,7 +19012,7 @@ var TOOL_ANIM_TICK_MS = TOOL_BLINK_MS;
18999
19012
  function statusCopy(name, label, count, doneCount, pending, isError, args = {}) {
19000
19013
  return formatToolActionHeader(name, args, { pending, count });
19001
19014
  }
19002
- function ToolExecution({ name, args, result, rawResult, isError, errorCount, callErrorCount, expanded, columns = 80, attached = false, count = 1, completedCount = 0, startedAt = 0, completedAt = 0, aggregate = false, categories = {}, doneCategories = null, headerFinalized = true, deferredDisplayReady = false }) {
19015
+ function ToolExecution({ name, args, result, rawResult, isError, errorCount, callErrorCount, exitErrorCount, expanded, columns = 80, attached = false, count = 1, completedCount = 0, startedAt = 0, completedAt = 0, aggregate = false, categories = {}, doneCategories = null, headerFinalized = true, deferredDisplayReady = false, agentResponseAggregate = false }) {
19003
19016
  const rowWidth = Math.max(1, Number(columns || 80));
19004
19017
  const groupCount = Math.max(1, Number(count || 1));
19005
19018
  const doneCount = Math.max(0, Math.min(groupCount, Number(completedCount || (result == null ? 0 : groupCount))));
@@ -19023,6 +19036,7 @@ function ToolExecution({ name, args, result, rawResult, isError, errorCount, cal
19023
19036
  const elapsed = elapsedMs >= 1e3 ? formatElapsed(elapsedMs) : "";
19024
19037
  const failedCount = clampFailureCount(errorCount, groupCount, isError);
19025
19038
  const callFailedCount = clampFailureCount(callErrorCount, groupCount, false);
19039
+ const exitFailedCount = clampFailureCount(exitErrorCount, groupCount, false);
19026
19040
  const displayGroupCount = groupCount;
19027
19041
  const displayCategories = normalizeCountMap(categories || {});
19028
19042
  const normalizedDoneCategories = doneCategories ? normalizeCountMap(doneCategories) : displayCategories;
@@ -19048,7 +19062,7 @@ function ToolExecution({ name, args, result, rawResult, isError, errorCount, cal
19048
19062
  detailText = "";
19049
19063
  }
19050
19064
  const aggregateTerminalStatus = pending ? "running" : resultTerminalStatus(rt) || (isError || failedCount > 0 ? "failed" : "completed");
19051
- const dotColor2 = toolStatusColor({ pending, groupCount, callFailedCount, terminalStatus: aggregateTerminalStatus });
19065
+ const dotColor2 = toolStatusColor({ pending, groupCount, callFailedCount, exitFailedCount, terminalStatus: aggregateTerminalStatus });
19052
19066
  const dotText2 = pending && !blinkOn ? " " : TURN_MARKER;
19053
19067
  const gutter2 = 2;
19054
19068
  const showHeaderExpandHint2 = hasRawResult;
@@ -19103,7 +19117,7 @@ function ToolExecution({ name, args, result, rawResult, isError, errorCount, cal
19103
19117
  const firstResultLine = hasDisplayResult ? String(lines[0] ?? "") : "";
19104
19118
  const firstResultLineClipped = hasDisplayBody && stringWidth9(firstResultLine) > maxResultChars;
19105
19119
  const hasHiddenDetail = !pending && hasDisplayBody && (totalLines > 1 || firstResultLineClipped || Boolean(resultSummary));
19106
- const shellStatus = isShellSurface ? shellDisplayStatus({ pending, failedCount, isError, result: displayedResultText }) : "";
19120
+ const shellStatus = isShellSurface ? shellDisplayStatus({ pending, failedCount, exitFailedCount, isError, result: displayedResultText }) : "";
19107
19121
  const shellElapsed = isShellSurface ? shellResultElapsed(displayedResultText) || elapsed : "";
19108
19122
  const backgroundElapsed = backgroundMeta ? backgroundTaskElapsed(backgroundMeta, elapsed) : isBackgroundTaskTool(normalizedName) ? backgroundTaskElapsed(parsedArgs, elapsed) : "";
19109
19123
  const toolArgPath = parsedArgs?.path ?? parsedArgs?.file_path ?? parsedArgs?.file ?? "";
@@ -19128,7 +19142,7 @@ function ToolExecution({ name, args, result, rawResult, isError, errorCount, cal
19128
19142
  const collapsedDetail = pending ? pendingDetailPlaceholder : isShellSurface ? prefixElapsed(mergeTerminalDetail(shellStatus, shellCollapsedSummary), shellElapsed) : mergeTerminalDetail(terminalStatus, nonShellDetail);
19129
19143
  const backgroundMetadataExpandable = isBackgroundMetadataResult && hasRawResult && !pending;
19130
19144
  const showRawResult = expanded && (hasDisplayBody || hasRawResult) && (!isBackgroundMetadataResult || hasRawResult);
19131
- const detailLines = showRawResult ? hasDisplayBody ? lines : rawRt ? stripLeadingStatusMarkerLines(rawRt.split("\n")) : [] : collapsedDetail ? [collapsedDetail] : [];
19145
+ const detailLines = showRawResult ? agentResponseAggregate && hasRawResult ? stripLeadingStatusMarkerLines(rawRt.split("\n")) : hasDisplayBody ? lines : rawRt ? stripLeadingStatusMarkerLines(rawRt.split("\n")) : [] : collapsedDetail ? [collapsedDetail] : [];
19132
19146
  const isPendingPlaceholderDetail = !showRawResult && Boolean(pendingDetailPlaceholder);
19133
19147
  const detailColor = isPendingPlaceholderDetail ? theme.subtle : theme.text;
19134
19148
  const isAgentResult = !isBackgroundResult && !pending && isAgentTool(normalizedName) && hasDisplayResult;
@@ -19148,14 +19162,14 @@ function ToolExecution({ name, args, result, rawResult, isError, errorCount, cal
19148
19162
  const keepAgentDetail = (isError || agentFailureText) && !(agentHeaderFailure && !agentSurfaceBrief);
19149
19163
  visibleDetailLines = keepAgentDetail ? [agentDetailLine] : [];
19150
19164
  }
19151
- const finalStatusColor = toolStatusColor({ pending, groupCount, callFailedCount, terminalStatus });
19165
+ const finalStatusColor = toolStatusColor({ pending, groupCount, callFailedCount, exitFailedCount, terminalStatus });
19152
19166
  const dotColor = finalStatusColor;
19153
19167
  const markerGlyph = isAgentResponse ? AGENT_RESPONSE_MARKER : isAgentSurfaceCard ? AGENT_CALL_MARKER : TURN_MARKER;
19154
19168
  const isDirectionalMarker = isAgentResponse || isAgentSurfaceCard;
19155
19169
  const markerText = isDirectionalMarker ? `${markerGlyph} ` : markerGlyph;
19156
19170
  const dotText = pending && !blinkOn ? " " : markerText;
19157
19171
  let labelText;
19158
- if (isAgentResponse) labelText = agentResponseTitle(parsedArgs);
19172
+ if (isAgentResponse) labelText = agentResponseTitle(parsedArgs, displayGroupCount);
19159
19173
  else if (isBackgroundResponse) labelText = backgroundTaskResultTitle(normalizedName, backgroundMeta || parsedArgs);
19160
19174
  else if (isBackgroundMetadataResult) labelText = backgroundTaskActionTitle(normalizedName, backgroundMeta);
19161
19175
  else if (isShellSurface) labelText = shellHeader(shellStatus, displayGroupCount);
@@ -19364,7 +19378,7 @@ var Item = React19.memo(function Item2({ item, prevKind, columns, toolOutputExpa
19364
19378
  node = /* @__PURE__ */ jsx19(ToolHookDenialCard, { item, columns });
19365
19379
  break;
19366
19380
  }
19367
- node = /* @__PURE__ */ jsx19(ToolExecution, { name: item.name, args: item.args, result: item.result, rawResult: item.rawResult, isError: item.isError, errorCount: item.errorCount, callErrorCount: item.callErrorCount, expanded: toolOutputExpanded || item.expanded, columns, attached: false, count: item.count, completedCount: item.completedCount, startedAt: item.startedAt, completedAt: item.completedAt, aggregate: item.aggregate, categories: item.categories, doneCategories: item.doneCategories, headerFinalized: item.headerFinalized, deferredDisplayReady: item.deferredDisplayReady });
19381
+ node = /* @__PURE__ */ jsx19(ToolExecution, { name: item.name, args: item.args, result: item.result, rawResult: item.rawResult, isError: item.isError, errorCount: item.errorCount, callErrorCount: item.callErrorCount, exitErrorCount: item.exitErrorCount, expanded: toolOutputExpanded || item.expanded, columns, attached: false, count: item.count, completedCount: item.completedCount, startedAt: item.startedAt, completedAt: item.completedAt, aggregate: item.aggregate, categories: item.categories, doneCategories: item.doneCategories, headerFinalized: item.headerFinalized, deferredDisplayReady: item.deferredDisplayReady, agentResponseAggregate: item.agentResponseAggregate });
19368
19382
  break;
19369
19383
  }
19370
19384
  case "notice":
@@ -19672,6 +19686,32 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
19672
19686
  searchModelsCacheRef.current = { models: null, at: 0 };
19673
19687
  }
19674
19688
  }, []);
19689
+ useEffect10(() => {
19690
+ let alive = true;
19691
+ const timer2 = setTimeout(async () => {
19692
+ const seq = onboardingPrefetchSeqRef.current;
19693
+ try {
19694
+ const models = await Promise.resolve(store.listProviderModels?.() || []);
19695
+ if (alive && seq === onboardingPrefetchSeqRef.current && Array.isArray(models) && models.length > 0 && !Array.isArray(providerModelsCacheRef.current.models)) {
19696
+ providerModelsCacheRef.current = { models, at: Date.now() };
19697
+ }
19698
+ } catch {
19699
+ }
19700
+ if (!alive) return;
19701
+ try {
19702
+ const searchModels = await Promise.resolve(store.listSearchModels?.() || []);
19703
+ if (alive && Array.isArray(searchModels) && searchModels.length > 0 && !Array.isArray(searchModelsCacheRef.current.models)) {
19704
+ searchModelsCacheRef.current = { models: searchModels, at: Date.now() };
19705
+ }
19706
+ } catch {
19707
+ }
19708
+ }, 1500);
19709
+ timer2.unref?.();
19710
+ return () => {
19711
+ alive = false;
19712
+ clearTimeout(timer2);
19713
+ };
19714
+ }, [store]);
19675
19715
  const { onboardingWarnReopen, openOnboardingAuthStep } = createOnboardingSteps({
19676
19716
  store,
19677
19717
  setPicker,
@@ -22507,18 +22547,6 @@ function bracketField(text, name) {
22507
22547
  const re = new RegExp(`^\\[${name}:\\s*([^\\]]*)\\]`, "mi");
22508
22548
  return re.exec(String(text ?? ""))?.[1]?.trim() || "";
22509
22549
  }
22510
- function toolResultStatus(text) {
22511
- const value = String(text ?? "");
22512
- const tagged = textBetweenTag2(value, "status");
22513
- if (tagged) return tagged.trim();
22514
- const bracketed = bracketField(value, "status");
22515
- if (bracketed) return bracketed.trim();
22516
- const inline = /^(?:status|state):\s*([^\s·,;]+)/mi.exec(value);
22517
- return inline ? inline[1].trim() : "";
22518
- }
22519
- function isErrorToolStatus(status) {
22520
- return /^(failed|error|timeout|cancelled|canceled|killed)$/i.test(String(status || "").trim());
22521
- }
22522
22550
  function parseSyntheticAgentMessage(text) {
22523
22551
  const value = String(text ?? "").trim();
22524
22552
  if (!value) return null;
@@ -22625,6 +22653,7 @@ function agentArgsWithResultMetadata(args, parsed) {
22625
22653
 
22626
22654
  // src/tui/engine/queue-helpers.mjs
22627
22655
  var QUEUE_PRIORITY = { now: 0, next: 1, later: 2 };
22656
+ var STEERING_SUPPRESSED_DISPLAY = "\0mixdog:suppress-steer-display\0";
22628
22657
  function queuePriorityValue(value) {
22629
22658
  return QUEUE_PRIORITY[String(value || "next")] ?? QUEUE_PRIORITY.next;
22630
22659
  }
@@ -22770,6 +22799,13 @@ function notificationQueueKey(event, text, parsed) {
22770
22799
  const hasBody = /\n\s*\n[\s\S]*\S/.test(String(text || "")) ? "b1" : "b0";
22771
22800
  return [id, type || fallbackKind, status, hasBody].filter(Boolean).join(":");
22772
22801
  }
22802
+ function executionCardKey(event, text, parsed) {
22803
+ const meta = event?.meta && typeof event.meta === "object" ? event.meta : {};
22804
+ const executionId = String(meta.execution_id || "").trim();
22805
+ if (!executionId) return notificationQueueKey(event, text, parsed);
22806
+ const hasBody = /\n\s*\n[\s\S]*\S/.test(String(text || "")) ? "b1" : "b0";
22807
+ return `card:${executionId}:${hasBody}`;
22808
+ }
22773
22809
  function isExecutionNotification(event, text, parsed) {
22774
22810
  const meta = event?.meta && typeof event.meta === "object" ? event.meta : {};
22775
22811
  if (meta.execution_id || meta.execution_surface) return true;
@@ -22868,23 +22904,43 @@ var yieldToRenderer = ({ frames = 1 } = {}) => new Promise((resolve5) => {
22868
22904
 
22869
22905
  // src/tui/engine/tool-result-status.mjs
22870
22906
  var CANCELLED_RESULT_STATUS_LINE = "[status: cancelled]";
22907
+ function shellCommandExitCode(text) {
22908
+ const body = String(text || "");
22909
+ if (!/^\s*(?:Error:\s*)?\[shell-run-failed\]/i.test(body)) return null;
22910
+ const header = body.split("\n", 1)[0] || "";
22911
+ if (/\[timeout:|\[signal:|timed out|aborted|interrupted/i.test(header)) return null;
22912
+ const m = header.match(/\[exit code:\s*(\d+)\]/i);
22913
+ if (!m) return null;
22914
+ const code = Number(m[1]);
22915
+ return Number.isFinite(code) ? code : null;
22916
+ }
22917
+ function toolCallOutcome(message, rawText) {
22918
+ const exitCode = shellCommandExitCode(rawText);
22919
+ if (exitCode != null) {
22920
+ return { isCallError: false, isExitError: true, exitCode };
22921
+ }
22922
+ const isCallError = message?.isError === true || message?.toolKind === "error";
22923
+ return {
22924
+ isCallError,
22925
+ isExitError: false,
22926
+ exitCode
22927
+ };
22928
+ }
22929
+ function failureDetailText({ succeeded = 0, realErrors = 0, exitErrors = 0, exitCode } = {}) {
22930
+ const parts = [];
22931
+ if (succeeded > 0) parts.push(`${succeeded} Ok`);
22932
+ if (realErrors > 0) parts.push(`${realErrors} Failed`);
22933
+ if (exitErrors > 0) {
22934
+ const solo = exitErrors === 1 && realErrors === 0 && succeeded === 0;
22935
+ parts.push(solo && Number.isFinite(exitCode) ? `Exit ${exitCode}` : `${exitErrors} Exit`);
22936
+ }
22937
+ return parts.join(" \xB7 ");
22938
+ }
22871
22939
  function normalizedResultStatusToken(value) {
22872
- const raw = String(value || "").trim().toLowerCase();
22873
- if (!raw) return "";
22874
- if (/^(running|pending|queued|in_progress|in-progress)$/.test(raw)) return "running";
22875
- if (/^(completed|complete|done|success|succeeded|ok)$/.test(raw)) return "completed";
22876
- if (/^(failed|fail|error|errored|timeout|timed_out|killed)$/.test(raw)) return "failed";
22877
- if (/^(cancelled|canceled|cancel)$/.test(raw)) return "cancelled";
22878
- return "";
22940
+ return normalizeToolTerminalStatus(value);
22879
22941
  }
22880
22942
  function resultTextTerminalStatus(text) {
22881
- const body = String(text || "");
22882
- const tagged = body.match(/<status[^>]*>([\s\S]*?)<\/status>/i)?.[1]?.trim();
22883
- if (tagged) return normalizedResultStatusToken(tagged);
22884
- const bracketed = body.match(/^\[status:\s*([^\]]*)\]/mi)?.[1]?.trim();
22885
- if (bracketed) return normalizedResultStatusToken(bracketed);
22886
- const inline = body.match(/^(?:status|state):\s*([^\s·,;]+)/mi)?.[1]?.trim();
22887
- return normalizedResultStatusToken(inline);
22943
+ return toolResultTerminalStatus(text);
22888
22944
  }
22889
22945
  function itemHasKnownTerminalStatus(item, texts = []) {
22890
22946
  const settled = (token) => token === "completed" || token === "failed" || token === "cancelled";
@@ -22906,11 +22962,14 @@ ${body}`;
22906
22962
  function groupedToolResultText(group) {
22907
22963
  const completed = Math.min(group.count, group.completed);
22908
22964
  if (group.count <= 1) return group.results.at(-1)?.text ?? "";
22909
- if (group.errors > 0) {
22910
- const succeeded = Math.max(0, completed - group.errors);
22911
- const reasons = group.results.filter((result) => result?.isError).map((result) => firstErrorLine(result?.text)).filter(Boolean);
22965
+ const exitErrors = Number(group.exitErrors || 0);
22966
+ if (group.errors > 0 || exitErrors > 0) {
22967
+ const realErrors = Math.max(0, Number(group.callErrors || group.errors || 0));
22968
+ const succeeded = Math.max(0, completed - group.errors - exitErrors);
22969
+ const exitCode = group.results.find((result) => result?.isExitError)?.exitCode;
22970
+ const reasons = group.results.filter((result) => result?.isError && !result?.isExitError).map((result) => firstErrorLine(result?.text)).filter(Boolean);
22912
22971
  const uniqueReasons = [...new Set(reasons)].slice(0, 2);
22913
- const base = succeeded > 0 ? `${succeeded} Ok \xB7 ${group.errors} Failed` : `${group.errors} Failed`;
22972
+ const base = failureDetailText({ succeeded, realErrors, exitErrors, exitCode });
22914
22973
  return [
22915
22974
  `${base}${uniqueReasons[0] ? ` \xB7 ${uniqueReasons[0]}` : ""}`,
22916
22975
  ...uniqueReasons.slice(1)
@@ -22944,9 +23003,10 @@ ${text}`);
22944
23003
  }
22945
23004
  return chunks.join("\n\n");
22946
23005
  }
22947
- function aggregateBucketForCategory(category) {
23006
+ function aggregateBucketForCategory(category, { agentBatch = "" } = {}) {
22948
23007
  const key = String(category || "").trim();
22949
23008
  if (key === "Read" || key === "Search") return "category:Read+Search";
23009
+ if (key === "Agent") return agentBatch ? `category:Agent:${agentBatch}` : "category:Agent";
22950
23010
  return key ? `category:${key}` : "default";
22951
23011
  }
22952
23012
  function aggregateSummaries(aggregate) {
@@ -23065,6 +23125,12 @@ function createToolCardResults({
23065
23125
  buildAgentJobCardPatch,
23066
23126
  agentStatusState
23067
23127
  }) {
23128
+ function finalizedErrorFallbackBody(body, text, exitCode) {
23129
+ if (String(body || "").trim()) return body;
23130
+ if (String(text || "").trim()) return text;
23131
+ if (exitCode != null) return `Exit ${exitCode}`;
23132
+ return "Failed";
23133
+ }
23068
23134
  function patchToolItem(id, patch) {
23069
23135
  const prev = getState().items.find((it) => it.id === id);
23070
23136
  const ok = patchItem(id, patch);
@@ -23082,10 +23148,8 @@ function createToolCardResults({
23082
23148
  const rawText = toolResultText(message?.content);
23083
23149
  const aggregate = card.aggregate;
23084
23150
  const callRec = aggregate && callId ? aggregate.calls.get(callId) : null;
23085
- const isMemoryCall = classifyToolCategory(callRec?.name || card?.name || "", callRec?.args || {}) === "Memory";
23086
- const isCallError = message?.isError === true || message?.toolKind === "error";
23087
- const isResultError = /^\s*\[?error/i.test(rawText) || isErrorToolStatus(toolResultStatus(rawText)) || isMemoryCall && memoryCoreResultErrorText(rawText) != null;
23088
- const isError = isCallError || isResultError;
23151
+ const { exitCode, isExitError, isCallError } = toolCallOutcome(message, rawText);
23152
+ const isError = isCallError;
23089
23153
  const text = isError ? toolErrorDisplay(rawText, card?.name || "tool") : rawText;
23090
23154
  if (aggregate && card.itemId === aggregate.itemId) {
23091
23155
  if (!callRec) return false;
@@ -23098,14 +23162,17 @@ function createToolCardResults({
23098
23162
  assignAggregateSummaryOrder(aggregate, callRec);
23099
23163
  callRec.isError = isError;
23100
23164
  callRec.isCallError = isCallError;
23165
+ callRec.isExitError = isExitError;
23166
+ callRec.exitCode = exitCode;
23101
23167
  callRec.resultText = text;
23102
23168
  callRec.resolved = true;
23103
23169
  const allCalls = [...aggregate.calls.values()];
23104
23170
  const completed = allCalls.filter((r) => r.resolved).length;
23105
23171
  const errors = allCalls.filter((r) => r.isError).length;
23106
23172
  const callErrors = allCalls.filter((r) => r.isCallError).length;
23107
- const succeeded = completed - errors;
23108
- const detailText = errors > 0 ? succeeded > 0 ? `${succeeded} Ok \xB7 ${errors} Failed` : `${errors} Failed` : formatAggregateDetail(aggregateSummaries(aggregate));
23173
+ const exitErrors = allCalls.filter((r) => r.isExitError).length;
23174
+ const succeeded = Math.max(0, completed - errors - exitErrors);
23175
+ const detailText = errors > 0 || exitErrors > 0 ? failureDetailText({ succeeded, realErrors: callErrors, exitErrors, exitCode: allCalls.find((r) => r.isExitError)?.exitCode }) : formatAggregateDetail(aggregateSummaries(aggregate));
23109
23176
  const currentItem = getState().items.find((it) => it.id === card.itemId);
23110
23177
  const earlyCompleted = allCalls.filter((r) => r.resolved || r.completedEarly).length;
23111
23178
  const visualCompleted = Math.max(completed, earlyCompleted, Math.min(allCalls.length, Number(currentItem?.completedCount || 0)));
@@ -23118,6 +23185,7 @@ function createToolCardResults({
23118
23185
  isError: errors > 0,
23119
23186
  errorCount: errors,
23120
23187
  callErrorCount: callErrors,
23188
+ exitErrorCount: exitErrors,
23121
23189
  count: allCalls.length,
23122
23190
  completedCount: visualCompleted,
23123
23191
  doneCategories: aggregateDoneCategories(allCalls),
@@ -23127,20 +23195,25 @@ function createToolCardResults({
23127
23195
  if (callId) done.add(callId);
23128
23196
  return true;
23129
23197
  }
23130
- const group = toolGroups.get(card.itemId) || { count: 1, completed: 0, errors: 0, callErrors: 0, results: [] };
23198
+ const group = toolGroups.get(card.itemId) || { count: 1, completed: 0, errors: 0, callErrors: 0, exitErrors: 0, results: [] };
23131
23199
  group.completed = Math.min(group.count, group.completed + 1);
23132
23200
  group.errors += isError ? 1 : 0;
23133
23201
  group.callErrors = (group.callErrors || 0) + (isCallError ? 1 : 0);
23134
- group.results.push({ text, isError });
23202
+ group.exitErrors = (group.exitErrors || 0) + (isExitError ? 1 : 0);
23203
+ group.results.push({ text, isError, isExitError, exitCode });
23135
23204
  toolGroups.set(card.itemId, group);
23136
23205
  const resultText = groupedToolResultText(group);
23137
- const displayResult = toolGroupedDisplayFallback(resultText, text, rawText);
23206
+ let displayResult = toolGroupedDisplayFallback(resultText, text, rawText);
23207
+ if (group.errors > 0 && !String(displayResult || "").trim()) {
23208
+ displayResult = finalizedErrorFallbackBody(displayResult, text, exitCode);
23209
+ }
23138
23210
  const patch = {
23139
23211
  result: displayResult,
23140
23212
  text: displayResult,
23141
23213
  isError: group.errors > 0,
23142
23214
  errorCount: group.errors,
23143
23215
  callErrorCount: group.callErrors || 0,
23216
+ exitErrorCount: group.exitErrors || 0,
23144
23217
  count: group.count,
23145
23218
  completedCount: group.completed,
23146
23219
  completedAt: Date.now()
@@ -23153,6 +23226,9 @@ function createToolCardResults({
23153
23226
  set(agentStatusState({ force: true }));
23154
23227
  }
23155
23228
  Object.assign(patch, buildAgentJobCardPatch(card.itemId, rawText, isError));
23229
+ if (group.errors > 0 && !String(patch.result || "").trim()) {
23230
+ patch.result = patch.text = finalizedErrorFallbackBody(patch.result, text, exitCode);
23231
+ }
23156
23232
  }
23157
23233
  patchToolItem(card.itemId, patch);
23158
23234
  card.done = true;
@@ -23203,9 +23279,10 @@ function createToolCardResults({
23203
23279
  const totalCompleted = completed;
23204
23280
  const errors = allCalls.filter((r) => r.isError).length;
23205
23281
  const callErrors = allCalls.filter((r) => r.isCallError).length;
23206
- const succeeded = completed - errors;
23282
+ const exitErrors = allCalls.filter((r) => r.isExitError).length;
23283
+ const succeeded = Math.max(0, completed - errors - exitErrors);
23207
23284
  const rawResult = aggregateRawResult(allCalls);
23208
- let displayDetail = errors > 0 ? succeeded > 0 ? `${succeeded} Ok \xB7 ${errors} Failed` : `${errors} Failed` : formatAggregateDetail(aggregateSummaries(aggregate));
23285
+ let displayDetail = errors > 0 || exitErrors > 0 ? failureDetailText({ succeeded, realErrors: callErrors, exitErrors, exitCode: allCalls.find((r) => r.isExitError)?.exitCode }) : formatAggregateDetail(aggregateSummaries(aggregate));
23209
23286
  if (cancelled) {
23210
23287
  const currentItem = getState().items.find((it) => it.id === card.itemId);
23211
23288
  displayDetail = withCancelledResultMarker(displayDetail, currentItem);
@@ -23217,6 +23294,7 @@ function createToolCardResults({
23217
23294
  isError: errors > 0,
23218
23295
  errorCount: errors,
23219
23296
  callErrorCount: callErrors,
23297
+ exitErrorCount: exitErrors,
23220
23298
  count: allCalls.length,
23221
23299
  completedCount: totalCompleted,
23222
23300
  doneCategories: aggregateDoneCategories(allCalls),
@@ -23229,15 +23307,19 @@ function createToolCardResults({
23229
23307
  }
23230
23308
  continue;
23231
23309
  }
23232
- const group = toolGroups.get(card.itemId) || { count: 1, completed: 0, errors: 0, results: [] };
23310
+ const group = toolGroups.get(card.itemId) || { count: 1, completed: 0, errors: 0, exitErrors: 0, results: [] };
23233
23311
  group.completed = Math.min(group.count, group.completed + 1);
23234
23312
  toolGroups.set(card.itemId, group);
23235
23313
  let resultText = groupedToolResultText(group);
23314
+ if (group.errors > 0 && !String(resultText || "").trim()) {
23315
+ const exitRec = (group.results || []).find((r) => r && r.isExitError);
23316
+ resultText = finalizedErrorFallbackBody(resultText, exitRec?.text, exitRec?.exitCode);
23317
+ }
23236
23318
  if (cancelled) {
23237
23319
  const currentItem = getState().items.find((it) => it.id === card.itemId);
23238
23320
  resultText = withCancelledResultMarker(resultText, currentItem);
23239
23321
  }
23240
- patchToolItem(card.itemId, { result: resultText, text: resultText, isError: group.errors > 0, errorCount: group.errors, callErrorCount: group.callErrors || 0, count: group.count, completedCount: group.completed, completedAt: Date.now() });
23322
+ patchToolItem(card.itemId, { result: resultText, text: resultText, isError: group.errors > 0, errorCount: group.errors, callErrorCount: group.callErrors || 0, exitErrorCount: group.exitErrors || 0, count: group.count, completedCount: group.completed, completedAt: Date.now() });
23241
23323
  card.done = true;
23242
23324
  if (card.callId) done.add(card.callId);
23243
23325
  }
@@ -23245,6 +23327,73 @@ function createToolCardResults({
23245
23327
  return { patchToolCardResult, flushToolResults };
23246
23328
  }
23247
23329
 
23330
+ // src/runtime/agent/orchestrator/session/manager/delivered-completions.mjs
23331
+ import { createHash as createHash3 } from "node:crypto";
23332
+ var DELIVERED_TTL_MS = 6 * 60 * 60 * 1e3;
23333
+ var DELIVERED_MAX_ENTRIES = 512;
23334
+ var _delivered = /* @__PURE__ */ new Map();
23335
+ function hashCompletionText(text) {
23336
+ const value = String(text ?? "").trim();
23337
+ if (!value) return null;
23338
+ return createHash3("sha1").update(value).digest("hex");
23339
+ }
23340
+ function execKey(executionId) {
23341
+ const id = executionId == null ? "" : String(executionId).trim();
23342
+ return id ? `id:${id}` : null;
23343
+ }
23344
+ function textKey(text) {
23345
+ const h = hashCompletionText(text);
23346
+ return h ? `tx:${h}` : null;
23347
+ }
23348
+ function pruneExpired(now) {
23349
+ for (const [key, expiresAt] of _delivered) {
23350
+ if (expiresAt <= now) _delivered.delete(key);
23351
+ }
23352
+ }
23353
+ function enforceBound() {
23354
+ while (_delivered.size > DELIVERED_MAX_ENTRIES) {
23355
+ const oldest = _delivered.keys().next().value;
23356
+ if (oldest === void 0) break;
23357
+ _delivered.delete(oldest);
23358
+ }
23359
+ }
23360
+ function recordDeliveredCompletion({ executionId, text } = {}) {
23361
+ const now = Date.now();
23362
+ pruneExpired(now);
23363
+ const expiresAt = now + DELIVERED_TTL_MS;
23364
+ let recorded = false;
23365
+ for (const key of [execKey(executionId), textKey(text)]) {
23366
+ if (!key) continue;
23367
+ _delivered.delete(key);
23368
+ _delivered.set(key, expiresAt);
23369
+ recorded = true;
23370
+ }
23371
+ if (recorded) enforceBound();
23372
+ return recorded;
23373
+ }
23374
+ function isDeliveredCompletion({ executionId, text } = {}) {
23375
+ const now = Date.now();
23376
+ const keys = [execKey(executionId), textKey(text)].filter(Boolean);
23377
+ let hit = false;
23378
+ for (const key of keys) {
23379
+ const expiresAt2 = _delivered.get(key);
23380
+ if (expiresAt2 === void 0) continue;
23381
+ if (expiresAt2 <= now) {
23382
+ _delivered.delete(key);
23383
+ continue;
23384
+ }
23385
+ hit = true;
23386
+ }
23387
+ if (!hit) return false;
23388
+ const expiresAt = now + DELIVERED_TTL_MS;
23389
+ for (const key of keys) {
23390
+ if (!_delivered.has(key)) continue;
23391
+ _delivered.delete(key);
23392
+ _delivered.set(key, expiresAt);
23393
+ }
23394
+ return true;
23395
+ }
23396
+
23248
23397
  // src/tui/engine/agent-job-feed.mjs
23249
23398
  function parseInboundImagePaths(raw) {
23250
23399
  if (typeof raw !== "string" || !raw) return [];
@@ -23265,16 +23414,58 @@ function createAgentJobFeed({
23265
23414
  enqueue,
23266
23415
  drain,
23267
23416
  pushUserOrSyntheticItem,
23417
+ pushAsyncAgentResponse,
23268
23418
  makeQueueEntry,
23269
23419
  getPending,
23270
23420
  agentStatusState,
23271
23421
  displayedExecutionNotificationKeys,
23272
- pushNotice
23422
+ pushNotice,
23423
+ now = () => Date.now(),
23424
+ executionResumeTombstoneTtlMs = 3e4,
23425
+ executionResumeTombstoneLimit = 128
23273
23426
  }) {
23274
23427
  let executionResumeKickDeferred = false;
23428
+ const discardedExecutionResumeKeys = /* @__PURE__ */ new Map();
23429
+ const displayedExecutionResponseStates = /* @__PURE__ */ new Map();
23275
23430
  const executionResumeKickBodies = [];
23276
- function kickExecutionPendingResume(body = "") {
23277
- if (body) executionResumeKickBodies.push(body);
23431
+ function executionResumeKey(body, completionKey = "") {
23432
+ if (completionKey && typeof completionKey === "object") {
23433
+ completionKey = completionKey.executionId || completionKey.key || "";
23434
+ }
23435
+ const explicitKey = String(completionKey || "").trim();
23436
+ if (explicitKey.startsWith("execution:") || explicitKey.startsWith("body:")) return explicitKey;
23437
+ if (explicitKey) return `execution:${explicitKey}`;
23438
+ const value = String(body || "").trim();
23439
+ return value ? `body:${shortTextFingerprint(value)}` : "";
23440
+ }
23441
+ function pruneDiscardedExecutionResumeKeys() {
23442
+ const nowMs = Number(now()) || Date.now();
23443
+ for (const [key, expiresAt] of discardedExecutionResumeKeys) {
23444
+ if (expiresAt <= nowMs) discardedExecutionResumeKeys.delete(key);
23445
+ }
23446
+ }
23447
+ function isDiscardedExecutionResumeKey(key) {
23448
+ if (!key) return false;
23449
+ pruneDiscardedExecutionResumeKeys();
23450
+ return discardedExecutionResumeKeys.has(key);
23451
+ }
23452
+ function rememberDiscardedExecutionResumeKey(key) {
23453
+ if (!key) return;
23454
+ pruneDiscardedExecutionResumeKeys();
23455
+ const limit = Math.max(1, Number(executionResumeTombstoneLimit) || 128);
23456
+ while (!discardedExecutionResumeKeys.has(key) && discardedExecutionResumeKeys.size >= limit) {
23457
+ const oldest = discardedExecutionResumeKeys.keys().next().value;
23458
+ if (oldest == null) break;
23459
+ discardedExecutionResumeKeys.delete(oldest);
23460
+ }
23461
+ const ttlMs = Math.max(1, Number(executionResumeTombstoneTtlMs) || 3e4);
23462
+ discardedExecutionResumeKeys.delete(key);
23463
+ discardedExecutionResumeKeys.set(key, (Number(now()) || Date.now()) + ttlMs);
23464
+ }
23465
+ function kickExecutionPendingResume(body = "", completionKey = "") {
23466
+ const key = executionResumeKey(body, completionKey);
23467
+ if (body && isDiscardedExecutionResumeKey(key)) return;
23468
+ if (body) executionResumeKickBodies.push({ body, key });
23278
23469
  if (getDisposed()) return;
23279
23470
  if (getState().busy) {
23280
23471
  executionResumeKickDeferred = true;
@@ -23286,16 +23477,34 @@ function createAgentJobFeed({
23286
23477
  return;
23287
23478
  }
23288
23479
  executionResumeKickDeferred = false;
23289
- const resumeBody = executionResumeKickBodies.splice(0).filter(Boolean).join("\n\n");
23290
- pending.push(makeQueueEntry(resumeBody, { mode: "pending-resume", priority: "next" }));
23480
+ const resumeBodies = executionResumeKickBodies.splice(0);
23481
+ const resumeBody = resumeBodies.map(({ body: value }) => value).filter(Boolean).join("\n\n");
23482
+ const resumeCompletionKeys = resumeBodies.map(({ key: key2 }) => key2).filter(Boolean);
23483
+ pending.push(makeQueueEntry(resumeBody, {
23484
+ mode: "pending-resume",
23485
+ priority: "next",
23486
+ abortDiscardOnAbort: true,
23487
+ resumeCompletionKeys
23488
+ }));
23291
23489
  void drain();
23292
23490
  }
23293
23491
  function flushDeferredExecutionPendingResumeKick() {
23294
23492
  if (!executionResumeKickDeferred || getDisposed() || getState().busy) return;
23295
23493
  kickExecutionPendingResume();
23296
23494
  }
23297
- function scheduleExecutionPendingResumeKick(body = "") {
23298
- queueMicrotask(() => kickExecutionPendingResume(body));
23495
+ function scheduleExecutionPendingResumeKick(body = "", completionKey = "") {
23496
+ queueMicrotask(() => kickExecutionPendingResume(body, completionKey));
23497
+ }
23498
+ function discardExecutionPendingResume(completionKeys = []) {
23499
+ const keys = (Array.isArray(completionKeys) ? completionKeys : [completionKeys]).map((key) => executionResumeKey("", key)).filter(Boolean);
23500
+ if (keys.length === 0) return;
23501
+ for (const key of keys) rememberDiscardedExecutionResumeKey(key);
23502
+ for (let i = executionResumeKickBodies.length - 1; i >= 0; i -= 1) {
23503
+ if (isDiscardedExecutionResumeKey(executionResumeKickBodies[i].key)) {
23504
+ executionResumeKickBodies.splice(i, 1);
23505
+ }
23506
+ }
23507
+ executionResumeKickDeferred = executionResumeKickBodies.length > 0;
23299
23508
  }
23300
23509
  function buildAgentJobCardPatch(itemId, text, isError = false) {
23301
23510
  const parsed = parseAgentJob(text);
@@ -23313,6 +23522,12 @@ function createAgentJobFeed({
23313
23522
  function updateAgentJobCard(itemId, text, isError = false) {
23314
23523
  patchItem(itemId, buildAgentJobCardPatch(itemId, text, isError));
23315
23524
  }
23525
+ function refreshAgentStatus(parsed) {
23526
+ if (!parsed?.taskId) return;
23527
+ const status = String(parsed.status || "").toLowerCase();
23528
+ const terminal = /^(completed|complete|done|success|succeeded|ok|failed|error|timeout|killed|cancelled|canceled|denied)$/.test(status);
23529
+ set(agentStatusState(terminal ? { force: true } : void 0));
23530
+ }
23316
23531
  function subscribeRuntimeNotifications() {
23317
23532
  if (typeof runtime.onNotification !== "function") return null;
23318
23533
  return runtime.onNotification((event) => {
@@ -23328,33 +23543,53 @@ function createAgentJobFeed({
23328
23543
  return true;
23329
23544
  }
23330
23545
  if (delivery.action === "status-only") {
23331
- if (parsed?.taskId) set(agentStatusState({ force: true }));
23546
+ refreshAgentStatus(parsed);
23332
23547
  return true;
23333
23548
  }
23334
23549
  if (delivery.action === "execution-ui") {
23335
- const firstDelivery = !notificationKey || !displayedExecutionNotificationKeys.has(notificationKey);
23336
- if (firstDelivery) {
23337
- if (notificationKey) displayedExecutionNotificationKeys.add(notificationKey);
23338
- pushUserOrSyntheticItem(delivery.displayText, nextId2());
23550
+ const cardKey = executionCardKey(event, text, parsed);
23551
+ const firstDelivery = !cardKey || !displayedExecutionNotificationKeys.has(cardKey);
23552
+ const executionId = String(event?.meta?.execution_id || parsed?.taskId || "").trim();
23553
+ const status = String(event?.meta?.status || parsed?.status || "").toLowerCase();
23554
+ const hasBody = /\n\s*\n[\s\S]*\S/.test(text);
23555
+ const isFailure = /^(failed|error|timeout|killed|cancelled|canceled|denied)$/.test(status);
23556
+ const successfulPreview = !hasBody && !isFailure && /^(completed|complete|done|success|succeeded|ok)$/.test(status);
23557
+ const responseState = executionId ? displayedExecutionResponseStates.get(executionId) : "";
23558
+ const bodyAlreadyDisplayed = responseState === "body";
23559
+ if (firstDelivery && !successfulPreview && !bodyAlreadyDisplayed) {
23560
+ if (cardKey) displayedExecutionNotificationKeys.add(cardKey);
23561
+ if (executionId) displayedExecutionResponseStates.set(executionId, hasBody ? "body" : "preview");
23562
+ (pushAsyncAgentResponse || pushUserOrSyntheticItem)(delivery.displayText, nextId2(), "injected", { responseKey: executionId });
23339
23563
  }
23340
- if (parsed?.taskId) set(agentStatusState({ force: true }));
23564
+ refreshAgentStatus(parsed);
23341
23565
  const resumeBody = String(delivery.modelContent || "").trim();
23342
23566
  if (resumeBody) {
23343
- enqueue(resumeBody, {
23567
+ const completionKey = executionResumeKey(resumeBody, executionId);
23568
+ if (isDiscardedExecutionResumeKey(completionKey) || isDeliveredCompletion({ executionId, text: resumeBody })) {
23569
+ if (event && typeof event === "object") event.modelVisibleDelivered = true;
23570
+ return true;
23571
+ }
23572
+ const enqueued = enqueue(resumeBody, {
23344
23573
  mode: "task-notification",
23345
23574
  // Claude Code parity: live execution completions are queued as
23346
23575
  // task notifications so the active loop can attach them after the
23347
23576
  // next tool batch; no special pending-resume bypass.
23348
23577
  priority: "next",
23349
23578
  key: notificationKey || void 0,
23350
- displayText: delivery.displayText || text
23579
+ abortDiscardOnAbort: true,
23580
+ resumeCompletionKeys: completionKey ? [completionKey] : [],
23581
+ displayText: delivery.displayText || text,
23582
+ // The immediate Response card was already pushed above
23583
+ // (pushUserOrSyntheticItem). Keep this queued twin model-visible
23584
+ // but suppress its drain-time transcript card to avoid a duplicate.
23585
+ suppressDisplay: true
23351
23586
  });
23587
+ if (enqueued) recordDeliveredCompletion({ executionId, text: resumeBody });
23588
+ if (event && typeof event === "object") event.modelVisibleDelivered = true;
23352
23589
  }
23353
23590
  return true;
23354
23591
  }
23355
- if (parsed?.taskId) {
23356
- set(agentStatusState({ force: true }));
23357
- }
23592
+ refreshAgentStatus(parsed);
23358
23593
  const modelContent = String(delivery.modelContent ?? delivery.displayText ?? text).trim();
23359
23594
  const imagePaths = parseInboundImagePaths(event?.meta?.image_paths);
23360
23595
  if (!modelContent && imagePaths.length === 0) return true;
@@ -23397,12 +23632,68 @@ function createAgentJobFeed({
23397
23632
  kickExecutionPendingResume,
23398
23633
  flushDeferredExecutionPendingResumeKick,
23399
23634
  scheduleExecutionPendingResumeKick,
23635
+ discardExecutionPendingResume,
23400
23636
  updateAgentJobCard,
23401
23637
  buildAgentJobCardPatch,
23402
23638
  subscribeRuntimeNotifications
23403
23639
  };
23404
23640
  }
23405
23641
 
23642
+ // src/tui/engine/agent-response-tail.mjs
23643
+ function responseEntry(response = {}) {
23644
+ return {
23645
+ key: String(response.key || ""),
23646
+ raw: String(response.rawResult ?? response.raw ?? "").trim(),
23647
+ result: response.result,
23648
+ hasBody: response.hasBody === true,
23649
+ isError: response.isError === true
23650
+ };
23651
+ }
23652
+ function formatAgentResponseRaw(entries = []) {
23653
+ return entries.map((entry, index) => {
23654
+ const raw = String(entry?.raw || "").trim();
23655
+ return raw ? `${index + 1}. agent
23656
+ ${raw}` : "";
23657
+ }).filter(Boolean).join("\n\n");
23658
+ }
23659
+ function priorEntries(previous) {
23660
+ if (Array.isArray(previous?.agentResponseEntries) && previous.agentResponseEntries.length) {
23661
+ return previous.agentResponseEntries.map(responseEntry);
23662
+ }
23663
+ return [responseEntry({
23664
+ key: previous?.agentResponseKey,
23665
+ rawResult: previous?.rawResult ?? previous?.result,
23666
+ result: previous?.result,
23667
+ hasBody: previous?.agentResponseHasBody,
23668
+ isError: previous?.isError
23669
+ })];
23670
+ }
23671
+ function appendAgentResponseTail(previous, response, now = Date.now()) {
23672
+ if (previous?.kind !== "tool" || previous.agentDirection !== "inbound") return null;
23673
+ const next = responseEntry(response);
23674
+ const entries = priorEntries(previous);
23675
+ const existingIndex = next.key ? entries.findIndex((entry) => entry.key === next.key) : -1;
23676
+ if (existingIndex >= 0) {
23677
+ entries[existingIndex] = next;
23678
+ } else {
23679
+ if (previous.agentResponseHasBody !== next.hasBody) return null;
23680
+ entries.push(next);
23681
+ }
23682
+ return {
23683
+ args: response.args,
23684
+ result: response.result,
23685
+ rawResult: formatAgentResponseRaw(entries) || null,
23686
+ isError: entries.some((entry) => entry.isError),
23687
+ count: entries.length,
23688
+ completedCount: entries.length,
23689
+ completedAt: now,
23690
+ agentResponseEntries: entries,
23691
+ agentResponseKeys: entries.map((entry) => entry.key).filter(Boolean),
23692
+ agentResponseHasBody: entries.every((entry) => entry.hasBody),
23693
+ agentResponseAggregate: entries.length > 1
23694
+ };
23695
+ }
23696
+
23406
23697
  // src/tui/engine/tui-steering-persist.mjs
23407
23698
  import { randomBytes as randomBytes3 } from "crypto";
23408
23699
  import { join as join8 } from "path";
@@ -23771,6 +24062,11 @@ function createSessionFlow(bag) {
23771
24062
  key: options.key || null,
23772
24063
  skipSlashCommands: options.skipSlashCommands === true,
23773
24064
  displayText: mode === "task-notification" ? notificationDisplayText(displayText) : String(displayText || ""),
24065
+ suppressDisplay: options.suppressDisplay === true,
24066
+ // Completion resumes are consumed exactly once: Esc abandons their
24067
+ // uncommitted body instead of putting it back at the queue front.
24068
+ abortDiscardOnAbort: options.abortDiscardOnAbort === true,
24069
+ resumeCompletionKeys: Array.isArray(options.resumeCompletionKeys) ? options.resumeCompletionKeys.filter((key) => key != null && String(key).trim()) : [],
23774
24070
  steeringPersistId: options.steeringPersistId || null,
23775
24071
  steeringPersistRestored: options.steeringPersistRestored === true
23776
24072
  };
@@ -23882,9 +24178,17 @@ function createSessionFlow(bag) {
23882
24178
  const merged = mergePromptContents(batch);
23883
24179
  for (const entry of batch) {
23884
24180
  if (entry.mode === "pending-resume") continue;
24181
+ if (entry.suppressDisplay) continue;
23885
24182
  pushUserOrSyntheticItem(entry.text, entry.id, isQueuedEntryEditable(entry) ? "user" : "injected");
23886
24183
  }
23887
24184
  const nonEditable = batch.filter((entry) => !isQueuedEntryEditable(entry));
24185
+ const discardOnAbort = nonEditable.filter(
24186
+ (entry) => entry?.abortDiscardOnAbort === true || entry?.mode === "pending-resume"
24187
+ );
24188
+ const requeueOnAbort = nonEditable.filter((entry) => !discardOnAbort.includes(entry));
24189
+ const discardExecutionPendingResumeKeys = discardOnAbort.flatMap(
24190
+ (entry) => Array.isArray(entry?.resumeCompletionKeys) ? entry.resumeCompletionKeys : []
24191
+ );
23888
24192
  const batchPastedImages = mergePastedImages(batch);
23889
24193
  const batchPastedTexts = mergePastedTexts(batch);
23890
24194
  const turnStatus = await bag.runTurn(merged, {
@@ -23894,7 +24198,8 @@ function createSessionFlow(bag) {
23894
24198
  onCommitted: () => commitSteeringQueueEntries(batch),
23895
24199
  submittedIds: [...ids],
23896
24200
  restorable: nonEditable.length === 0,
23897
- requeueOnAbort: nonEditable
24201
+ requeueOnAbort,
24202
+ discardExecutionPendingResumeKeys
23898
24203
  });
23899
24204
  if (flags.drainEpoch !== drainEpoch) return;
23900
24205
  flushDeferredClearedSessionUi();
@@ -23948,6 +24253,12 @@ function createSessionFlow(bag) {
23948
24253
  if (batch.length === 0) break;
23949
24254
  for (const entry of batch) {
23950
24255
  const content = entry.content;
24256
+ if (entry.suppressDisplay) {
24257
+ if (Array.isArray(content) ? content.length > 0 : String(content ?? "").trim().length > 0) {
24258
+ out.push({ text: STEERING_SUPPRESSED_DISPLAY, content });
24259
+ }
24260
+ continue;
24261
+ }
23951
24262
  const value = typeof content === "string" ? content.trim() : { text: String(entry.text || "").trim(), content };
23952
24263
  if (typeof value === "string") {
23953
24264
  if (value.length > 0) out.push(value);
@@ -24236,7 +24547,8 @@ function createRunTurn(bag) {
24236
24547
  submittedIds,
24237
24548
  reclaimed: false,
24238
24549
  committed: false,
24239
- requeueEntries: Array.isArray(options.requeueOnAbort) ? options.requeueOnAbort.slice() : []
24550
+ requeueEntries: Array.isArray(options.requeueOnAbort) ? options.requeueOnAbort.slice() : [],
24551
+ discardExecutionPendingResumeKeys: Array.isArray(options.discardExecutionPendingResumeKeys) ? options.discardExecutionPendingResumeKeys.slice() : []
24240
24552
  };
24241
24553
  set({ busy: true, lastTurn: null, spinner: { active: true, verb: pickVerb(turnIndex), startedAt, responseLength: 0, inputTokens: 0, outputTokens: 0, mode: "requesting" } });
24242
24554
  let assistantText = "";
@@ -24319,6 +24631,7 @@ function createRunTurn(bag) {
24319
24631
  };
24320
24632
  armWatchdog();
24321
24633
  let currentAssistantText = "";
24634
+ const committedSegments = [];
24322
24635
  let thinkingText = "";
24323
24636
  let thinkingStartedAt = 0;
24324
24637
  let thinkingSegmentStartedAt = 0;
@@ -24334,7 +24647,8 @@ function createRunTurn(bag) {
24334
24647
  const earlyResultBuffer = /* @__PURE__ */ new Map();
24335
24648
  const aggregateCards = [];
24336
24649
  let tailAggregate = null;
24337
- const TOOL_CARD_PUSH_DELAY_MS = 0;
24650
+ let providerToolBatch = 0;
24651
+ const TOOL_CARD_PUSH_DELAY_MS = 1e3;
24338
24652
  let deferredSeqCounter = 0;
24339
24653
  const deferredEntries = [];
24340
24654
  const flushDeferredUpTo = (entry) => {
@@ -24501,10 +24815,11 @@ function createRunTurn(bag) {
24501
24815
  aggregate.ensureVisible?.();
24502
24816
  const errors = allCalls.filter((r) => r.isError).length;
24503
24817
  const callErrors = allCalls.filter((r) => r.isCallError).length;
24818
+ const exitErrors = allCalls.filter((r) => r.isExitError).length;
24504
24819
  const completed = allCalls.filter((r) => r.resolved).length;
24505
- const succeeded = completed - errors;
24820
+ const succeeded = Math.max(0, completed - errors - exitErrors);
24506
24821
  const rawResult = aggregateRawResult(allCalls);
24507
- const displayDetail = errors > 0 ? succeeded > 0 ? `${succeeded} Ok \xB7 ${errors} Failed` : `${errors} Failed` : formatAggregateDetail(aggregateSummaries(aggregate));
24822
+ const displayDetail = errors > 0 || exitErrors > 0 ? failureDetailText({ succeeded, realErrors: callErrors, exitErrors, exitCode: allCalls.find((r) => r.isExitError)?.exitCode }) : formatAggregateDetail(aggregateSummaries(aggregate));
24508
24823
  patchItem(aggregate.itemId, {
24509
24824
  result: displayDetail,
24510
24825
  text: displayDetail,
@@ -24512,6 +24827,7 @@ function createRunTurn(bag) {
24512
24827
  isError: errors > 0,
24513
24828
  errorCount: errors,
24514
24829
  callErrorCount: callErrors,
24830
+ exitErrorCount: exitErrors,
24515
24831
  count: allCalls.length,
24516
24832
  completedCount: allCalls.length,
24517
24833
  doneCategories: aggregateDoneCategories(allCalls),
@@ -24602,6 +24918,7 @@ function createRunTurn(bag) {
24602
24918
  if (sealToolBlock) clearAggregateContinuation();
24603
24919
  const id = currentAssistantId || ensureAssistant(text);
24604
24920
  patchItem(id, { text, streaming: false });
24921
+ committedSegments.push(text);
24605
24922
  closeAssistantSegment();
24606
24923
  return true;
24607
24924
  };
@@ -24718,24 +25035,25 @@ function createRunTurn(bag) {
24718
25035
  if (!callRec || callRec.resolved || callRec.completedEarly) return;
24719
25036
  aggregate.ensureVisible?.();
24720
25037
  const rawText = toolResultText(message?.content);
24721
- const isMemoryCall = classifyToolCategory(callRec.name, callRec.args) === "Memory";
24722
- const isCallError = message?.isError === true || message?.toolKind === "error";
24723
- const isResultError = /^\s*\[?error/i.test(rawText) || isErrorToolStatus(toolResultStatus(rawText)) || isMemoryCall && memoryCoreResultErrorText(rawText) != null;
24724
- const isError = isCallError || isResultError;
25038
+ const { exitCode, isExitError, isCallError } = toolCallOutcome(message, rawText);
25039
+ const isError = isCallError;
24725
25040
  const text = isError ? toolErrorDisplay(rawText, callRec.name || "tool") : rawText;
24726
25041
  callRec.summary = !isError ? summarizeToolResult(callRec.name, callRec.args, rawText, isError) : null;
24727
25042
  assignAggregateSummaryOrder(aggregate, callRec);
24728
25043
  callRec.isError = isError;
24729
25044
  callRec.isCallError = isCallError;
25045
+ callRec.isExitError = isExitError;
25046
+ callRec.exitCode = exitCode;
24730
25047
  callRec.resultText = text;
24731
25048
  callRec.completedEarly = true;
24732
25049
  const allCalls = [...aggregate.calls.values()];
24733
25050
  const completedCount = allCalls.filter((r) => r.resolved || r.completedEarly).length;
24734
25051
  const errors = allCalls.filter((r) => r.isError).length;
24735
25052
  const callErrors = allCalls.filter((r) => r.isCallError).length;
24736
- const succeeded = completedCount - errors;
25053
+ const exitErrors = allCalls.filter((r) => r.isExitError).length;
25054
+ const succeeded = Math.max(0, completedCount - errors - exitErrors);
24737
25055
  const rawResult = aggregateRawResult(allCalls);
24738
- const displayDetail = errors > 0 ? succeeded > 0 ? `${succeeded} Ok \xB7 ${errors} Failed` : `${errors} Failed` : formatAggregateDetail(aggregateSummaries(aggregate));
25056
+ const displayDetail = errors > 0 || exitErrors > 0 ? failureDetailText({ succeeded, realErrors: callErrors, exitErrors, exitCode: allCalls.find((r) => r.isExitError)?.exitCode }) : formatAggregateDetail(aggregateSummaries(aggregate));
24739
25057
  const currentItem = getState().items.find((it) => it.id === card.itemId);
24740
25058
  const visualCompleted = Math.max(
24741
25059
  completedCount,
@@ -24747,6 +25065,7 @@ function createRunTurn(bag) {
24747
25065
  isError: errors > 0,
24748
25066
  errorCount: errors,
24749
25067
  callErrorCount: callErrors,
25068
+ exitErrorCount: exitErrors,
24750
25069
  count: allCalls.length,
24751
25070
  completedCount: visualCompleted
24752
25071
  };
@@ -24770,11 +25089,9 @@ function createRunTurn(bag) {
24770
25089
  drainSteering: (_sessionId, drainOptions) => isCurrentTurn() ? drainPendingSteering(drainOptions) : [],
24771
25090
  onSteerMessage: (text) => {
24772
25091
  if (!markTurnProgress("steer-message")) return;
25092
+ if (text === STEERING_SUPPRESSED_DISPLAY) return;
24773
25093
  flushStreamBatch();
24774
- if (currentAssistantId) {
24775
- patchItem(currentAssistantId, { text: currentAssistantText || assistantText, streaming: false });
24776
- closeAssistantSegment();
24777
- }
25094
+ commitAssistantSegment({ sealToolBlock: true });
24778
25095
  assistantText = "";
24779
25096
  const value = String(text || "").trim();
24780
25097
  if (value) {
@@ -24795,6 +25112,7 @@ function createRunTurn(bag) {
24795
25112
  }
24796
25113
  const batchCalls = (calls || []).filter(Boolean);
24797
25114
  if (batchCalls.length === 0) return;
25115
+ const agentBatch = ++providerToolBatch;
24798
25116
  const committedAssistantSegment = commitAssistantSegment({ sealToolBlock: true });
24799
25117
  if (committedAssistantSegment) {
24800
25118
  await yieldToRenderer({ frames: 2 });
@@ -24807,7 +25125,7 @@ function createRunTurn(bag) {
24807
25125
  const name = toolCallName(c);
24808
25126
  const args = toolCallArgs(c);
24809
25127
  const category = classifyToolCategory(name, args);
24810
- const bucket = category === "Agent" ? null : aggregateBucketForCategory(category);
25128
+ const bucket = aggregateBucketForCategory(category, { agentBatch });
24811
25129
  const callId = toolCallId(c);
24812
25130
  const callKey = callId || `__tool_${toolCards.length}_${i}`;
24813
25131
  const activeCount = Number(aggregateToolCategoryEntry(name, args, category)?.count || 1);
@@ -24850,7 +25168,7 @@ function createRunTurn(bag) {
24850
25168
  ...categoryEntry,
24851
25169
  count: Number(prevCategory?.count || 0) + Number(categoryEntry.count || 1)
24852
25170
  });
24853
- aggregateCard.calls.set(callKey, { name, args, category, summary: null, summarySeq: null, isError: false, isCallError: false, resultText: null, resolved: false, completedEarly: false });
25171
+ aggregateCard.calls.set(callKey, { name, args, category, summary: null, summarySeq: null, isError: false, isCallError: false, isExitError: false, exitCode: null, resultText: null, resolved: false, completedEarly: false });
24854
25172
  touchedAggregates.add(aggregateCard);
24855
25173
  const card = { itemId: aggregateCard.itemId, callId: callKey, done: false, aggregate: aggregateCard };
24856
25174
  if (callId) {
@@ -24998,10 +25316,18 @@ function createRunTurn(bag) {
24998
25316
  flushStreamBatch();
24999
25317
  syncContextStats({ allowEstimated: true });
25000
25318
  const finalText = result?.content != null ? String(result.content) : "";
25001
- if (finalText.trim()) {
25002
- const id = currentAssistantId || ensureAssistant(finalText);
25003
- currentAssistantText = finalText;
25004
- patchItem(id, { text: finalText, streaming: false });
25319
+ let finalRemainder = finalText;
25320
+ for (const seg of committedSegments) {
25321
+ const skipped = finalRemainder.replace(/^\s+/, "");
25322
+ const trimmedSeg = seg ? seg.replace(/^\s+/, "") : "";
25323
+ if (trimmedSeg && skipped.startsWith(trimmedSeg)) {
25324
+ finalRemainder = skipped.slice(trimmedSeg.length);
25325
+ }
25326
+ }
25327
+ if (finalRemainder.trim()) {
25328
+ const id = currentAssistantId || ensureAssistant(finalRemainder);
25329
+ currentAssistantText = finalRemainder;
25330
+ patchItem(id, { text: finalRemainder, streaming: false });
25005
25331
  } else if (currentAssistantId && (currentAssistantText.trim() || assistantText.trim())) {
25006
25332
  const streamedText = currentAssistantText || assistantText;
25007
25333
  patchItem(currentAssistantId, { text: streamedText, streaming: false });
@@ -25792,7 +26118,8 @@ function createEngineApiA(bag) {
25792
26118
  restoreQueued,
25793
26119
  resetStatsAndSyncContext,
25794
26120
  drain,
25795
- flushDeferredExecutionPendingResumeKick
26121
+ flushDeferredExecutionPendingResumeKick,
26122
+ discardExecutionPendingResume
25796
26123
  } = bag;
25797
26124
  return {
25798
26125
  getState: () => getState(),
@@ -26286,9 +26613,14 @@ function createEngineApiA(bag) {
26286
26613
  const restorePastedTexts = canRestore && restoreState?.pastedTexts ? restoreState.pastedTexts : null;
26287
26614
  const discardPastedImages = restoreState?.restorable && hasPendingSteering && restoreState?.pastedImages ? restoreState.pastedImages : null;
26288
26615
  const discardPastedTexts = restoreState?.restorable && hasPendingSteering && restoreState?.pastedTexts ? restoreState.pastedTexts : null;
26289
- const requeueEntries = restoreState && !restoreState.committed && Array.isArray(restoreState.requeueEntries) ? restoreState.requeueEntries.slice() : [];
26616
+ const requeueEntries = restoreState && !restoreState.committed && Array.isArray(restoreState.requeueEntries) ? restoreState.requeueEntries.filter(
26617
+ (entry) => entry?.abortDiscardOnAbort !== true && entry?.mode !== "pending-resume"
26618
+ ) : [];
26290
26619
  const aborted = runtime.abort("cli-react-abort");
26291
26620
  if (restoreState) {
26621
+ if (aborted !== false && Array.isArray(restoreState.discardExecutionPendingResumeKeys)) {
26622
+ discardExecutionPendingResume?.(restoreState.discardExecutionPendingResumeKeys);
26623
+ }
26292
26624
  if ((restoreText || requeueEntries.length > 0) && aborted !== false) {
26293
26625
  restoreState.reclaimed = true;
26294
26626
  const idSet = new Set((restoreState.submittedIds || []).filter((id) => id != null));
@@ -26304,6 +26636,7 @@ function createEngineApiA(bag) {
26304
26636
  }
26305
26637
  restoreState.restorable = false;
26306
26638
  restoreState.requeueEntries = [];
26639
+ restoreState.discardExecutionPendingResumeKeys = [];
26307
26640
  }
26308
26641
  const abortEpoch = flags.leadTurnEpoch;
26309
26642
  const recoveryMs = Number(flags.manualAbortRecoveryMs) > 0 ? Number(flags.manualAbortRecoveryMs) : MANUAL_ABORT_RECOVERY_MS;
@@ -26564,6 +26897,59 @@ async function createEngineSession({
26564
26897
  if (origin === "user") appendPromptHistory(state.cwd, text);
26565
26898
  pushItem({ kind: "user", id, text });
26566
26899
  };
26900
+ const pushAsyncAgentResponse = (text, id = nextId(), origin = "injected", metadata = {}) => {
26901
+ const synthetic = parseSyntheticAgentMessage(text);
26902
+ const isAgent = synthetic?.name === "agent";
26903
+ if (!isAgent) return pushUserOrSyntheticItem(text, id, origin);
26904
+ const responseHasBody = /\n\s*\n[\s\S]*\S/.test(String(text || ""));
26905
+ const rawResult = synthetic.rawResult ?? text;
26906
+ const args = {
26907
+ ...synthetic.args && typeof synthetic.args === "object" ? synthetic.args : {},
26908
+ type: "result"
26909
+ };
26910
+ const responseKey = String(metadata.responseKey || metadata.executionId || args.task_id || "").trim();
26911
+ const previous = state.items.at(-1);
26912
+ if (previous?.kind === "tool" && previous.agentDirection === "inbound") {
26913
+ const patch = appendAgentResponseTail(previous, {
26914
+ key: responseKey,
26915
+ args,
26916
+ result: synthetic.result,
26917
+ rawResult,
26918
+ hasBody: responseHasBody,
26919
+ isError: synthetic.isError === true
26920
+ });
26921
+ if (patch) {
26922
+ patchItem(previous.id, patch);
26923
+ return true;
26924
+ }
26925
+ }
26926
+ pushItem({
26927
+ kind: "tool",
26928
+ id,
26929
+ name: "agent",
26930
+ args,
26931
+ result: synthetic.result,
26932
+ rawResult,
26933
+ isError: synthetic.isError === true,
26934
+ expanded: false,
26935
+ count: 1,
26936
+ completedCount: 1,
26937
+ startedAt: Date.now(),
26938
+ completedAt: Date.now(),
26939
+ agentDirection: "inbound",
26940
+ agentResponseKey: responseKey,
26941
+ agentResponseHasBody: responseHasBody,
26942
+ agentResponseAggregate: false,
26943
+ agentResponseEntries: [{
26944
+ key: responseKey,
26945
+ raw: String(rawResult ?? "").trim(),
26946
+ result: synthetic.result,
26947
+ hasBody: responseHasBody,
26948
+ isError: synthetic.isError === true
26949
+ }]
26950
+ });
26951
+ return true;
26952
+ };
26567
26953
  const pushToast = (text, tone = "info", ttlMs = 3e3) => {
26568
26954
  const id = nextId();
26569
26955
  const value = String(text ?? "").trim();
@@ -26653,6 +27039,7 @@ async function createEngineSession({
26653
27039
  kickExecutionPendingResume,
26654
27040
  flushDeferredExecutionPendingResumeKick,
26655
27041
  scheduleExecutionPendingResumeKick,
27042
+ discardExecutionPendingResume,
26656
27043
  updateAgentJobCard,
26657
27044
  buildAgentJobCardPatch,
26658
27045
  subscribeRuntimeNotifications
@@ -26666,6 +27053,7 @@ async function createEngineSession({
26666
27053
  enqueue: (...args) => bag.enqueue(...args),
26667
27054
  drain: (...args) => bag.drain(...args),
26668
27055
  pushUserOrSyntheticItem,
27056
+ pushAsyncAgentResponse,
26669
27057
  makeQueueEntry: (...args) => bag.makeQueueEntry(...args),
26670
27058
  getPending: () => pending,
26671
27059
  agentStatusState,
@@ -26713,6 +27101,7 @@ async function createEngineSession({
26713
27101
  removeNotice,
26714
27102
  setProgressHint,
26715
27103
  pushUserOrSyntheticItem,
27104
+ pushAsyncAgentResponse,
26716
27105
  upsertSyntheticToolItem,
26717
27106
  markToolCallActive,
26718
27107
  markToolCallDone,
@@ -26732,6 +27121,7 @@ async function createEngineSession({
26732
27121
  kickExecutionPendingResume,
26733
27122
  flushDeferredExecutionPendingResumeKick,
26734
27123
  scheduleExecutionPendingResumeKick,
27124
+ discardExecutionPendingResume,
26735
27125
  updateAgentJobCard,
26736
27126
  subscribeRuntimeNotifications
26737
27127
  });