mixdog 0.9.22 → 0.9.24

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 (132) hide show
  1. package/README.md +1 -4
  2. package/package.json +1 -1
  3. package/scripts/boot-smoke.mjs +1 -1
  4. package/scripts/channel-daemon-smoke.mjs +483 -0
  5. package/scripts/channel-daemon-stub.mjs +80 -0
  6. package/scripts/debounced-skills-async-save-test.mjs +57 -0
  7. package/scripts/explore-bench-tmp.mjs +17 -0
  8. package/scripts/find-fuzzy-hidden-test.mjs +145 -0
  9. package/scripts/mcp-grace-deferred-test.mjs +149 -0
  10. package/scripts/statusline-quota-hysteresis-test.mjs +37 -0
  11. package/scripts/tool-smoke.mjs +68 -47
  12. package/src/rules/agent/30-explorer.md +6 -0
  13. package/src/rules/shared/01-tool.md +11 -4
  14. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +76 -1
  15. package/src/runtime/agent/orchestrator/config.mjs +33 -7
  16. package/src/runtime/agent/orchestrator/context/collect.mjs +43 -8
  17. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +226 -0
  18. package/src/runtime/agent/orchestrator/mcp/client.mjs +184 -33
  19. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +38 -1
  20. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +39 -1
  21. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +1 -1
  22. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +5 -3
  23. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +2 -2
  24. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +2 -2
  25. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1 -1
  26. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +24 -7
  27. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +3 -2
  28. package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +6 -3
  29. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +1 -1
  30. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +37 -4
  31. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +11 -1
  32. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +14 -3
  33. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +40 -6
  34. package/src/runtime/agent/orchestrator/session/store.mjs +30 -14
  35. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +2 -1
  36. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +13 -5
  37. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +62 -24
  38. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -6
  39. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +20 -0
  40. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +34 -3
  41. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +220 -27
  42. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +61 -0
  43. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +1 -1
  44. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +67 -27
  45. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +6 -5
  46. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +54 -5
  47. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +97 -54
  48. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +49 -31
  49. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +35 -2
  50. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +126 -22
  51. package/src/runtime/channels/lib/crash-log.mjs +4 -2
  52. package/src/runtime/channels/lib/output-forwarder.mjs +2 -1
  53. package/src/runtime/channels/lib/owned-runtime.mjs +67 -223
  54. package/src/runtime/channels/lib/owner-heartbeat.mjs +9 -62
  55. package/src/runtime/channels/lib/parent-bridge.mjs +16 -1
  56. package/src/runtime/channels/lib/runtime-paths.mjs +32 -113
  57. package/src/runtime/channels/lib/session-discovery.mjs +2 -1
  58. package/src/runtime/channels/lib/tool-dispatch.mjs +22 -14
  59. package/src/runtime/channels/lib/tool-format.mjs +7 -2
  60. package/src/runtime/channels/lib/worker-main.mjs +42 -30
  61. package/src/runtime/memory/index.mjs +54 -7
  62. package/src/runtime/memory/lib/embedding-warmup.mjs +7 -0
  63. package/src/runtime/memory/lib/pg/process.mjs +85 -40
  64. package/src/runtime/memory/lib/pg/supervisor.mjs +82 -17
  65. package/src/runtime/memory/lib/query-handlers.mjs +4 -1
  66. package/src/runtime/memory/lib/recall-format.mjs +7 -3
  67. package/src/runtime/memory/tool-defs.mjs +1 -1
  68. package/src/runtime/shared/atomic-file.mjs +150 -6
  69. package/src/runtime/shared/background-tasks.mjs +1 -1
  70. package/src/runtime/shared/config.mjs +53 -1
  71. package/src/runtime/shared/tool-execution-contract.mjs +1 -1
  72. package/src/runtime/shared/tool-primitives.mjs +31 -1
  73. package/src/runtime/shared/tool-surface.mjs +42 -13
  74. package/src/runtime/shared/update-checker.mjs +3 -0
  75. package/src/runtime/shared/user-data-guard.mjs +66 -0
  76. package/src/session-runtime/config-lifecycle.mjs +221 -20
  77. package/src/session-runtime/lifecycle-api.mjs +9 -0
  78. package/src/session-runtime/mcp-glue.mjs +93 -1
  79. package/src/session-runtime/resource-api.mjs +62 -8
  80. package/src/session-runtime/runtime-core.mjs +118 -4
  81. package/src/session-runtime/session-text.mjs +41 -0
  82. package/src/session-runtime/session-turn-api.mjs +50 -0
  83. package/src/session-runtime/settings-api.mjs +8 -1
  84. package/src/session-runtime/tool-catalog.mjs +350 -38
  85. package/src/session-runtime/tool-defs.mjs +7 -7
  86. package/src/session-runtime/workflow.mjs +2 -1
  87. package/src/standalone/channel-admin.mjs +32 -3
  88. package/src/standalone/channel-daemon-client.mjs +226 -0
  89. package/src/standalone/channel-daemon-transport.mjs +545 -0
  90. package/src/standalone/channel-daemon.mjs +176 -0
  91. package/src/standalone/channel-worker.mjs +224 -4
  92. package/src/standalone/explore-tool.mjs +87 -15
  93. package/src/standalone/hook-bus.mjs +71 -3
  94. package/src/tui/App.jsx +107 -19
  95. package/src/tui/app/clipboard.mjs +39 -19
  96. package/src/tui/app/doctor.mjs +57 -0
  97. package/src/tui/app/extension-pickers.mjs +53 -9
  98. package/src/tui/app/maintenance-pickers.mjs +0 -5
  99. package/src/tui/app/slash-dispatch.mjs +4 -4
  100. package/src/tui/app/text-layout.mjs +11 -0
  101. package/src/tui/app/use-mouse-input.mjs +235 -51
  102. package/src/tui/app/use-prompt-handlers.mjs +49 -30
  103. package/src/tui/app/use-transcript-scroll.mjs +124 -27
  104. package/src/tui/app/use-transcript-window.mjs +55 -1
  105. package/src/tui/components/Message.jsx +1 -1
  106. package/src/tui/components/PromptInput.jsx +3 -1
  107. package/src/tui/components/QueuedCommands.jsx +21 -10
  108. package/src/tui/components/StatusLine.jsx +3 -3
  109. package/src/tui/components/ToolExecution.jsx +16 -4
  110. package/src/tui/components/TranscriptItem.jsx +1 -1
  111. package/src/tui/dist/index.mjs +820 -326
  112. package/src/tui/engine/agent-job-feed.mjs +5 -0
  113. package/src/tui/engine/notification-plan.mjs +5 -0
  114. package/src/tui/engine/session-api.mjs +23 -8
  115. package/src/tui/engine/session-flow.mjs +6 -0
  116. package/src/tui/engine/tool-card-results.mjs +14 -5
  117. package/src/tui/engine/turn.mjs +9 -2
  118. package/src/tui/engine.mjs +32 -5
  119. package/src/tui/index.jsx +62 -18
  120. package/src/tui/paste-attachments.mjs +26 -0
  121. package/src/ui/statusline-agents.mjs +36 -0
  122. package/src/ui/statusline.mjs +60 -10
  123. package/src/ui/tool-card.mjs +8 -1
  124. package/src/vendor/statusline/bin/statusline-route.mjs +23 -7
  125. package/src/workflows/bench/WORKFLOW.md +46 -0
  126. package/src/workflows/default/WORKFLOW.md +6 -0
  127. package/src/workflows/solo/WORKFLOW.md +5 -0
  128. package/vendor/ink/build/ink.js +23 -1
  129. package/vendor/ink/build/output.js +154 -71
  130. package/vendor/ink/build/render-node-to-output.js +44 -2
  131. package/vendor/ink/build/render.js +4 -0
  132. package/vendor/ink/build/renderer.js +4 -1
@@ -156,10 +156,11 @@ var require_mixdog_debug = __commonJS({
156
156
  // src/tui/index.jsx
157
157
  import React21 from "react";
158
158
  import { render } from "../../../vendor/ink/build/index.js";
159
- import { closeSync as closeSync2, constants as fsConstants, createWriteStream, mkdirSync as mkdirSync7, openSync as openSync2, readSync } from "node:fs";
160
- import { tmpdir as tmpdir3 } from "node:os";
161
- import { dirname as dirname9, join as join11 } from "node:path";
159
+ import { closeSync as closeSync2, constants as fsConstants, createWriteStream, mkdirSync as mkdirSync6, openSync as openSync2, readSync } from "node:fs";
160
+ import { tmpdir as tmpdir2 } from "node:os";
161
+ import { dirname as dirname9, join as join10 } from "node:path";
162
162
  import { performance as performance3 } from "node:perf_hooks";
163
+ import { format } from "node:util";
163
164
 
164
165
  // src/tui/App.jsx
165
166
  import React20, { useState as useState8, useCallback as useCallback6, useEffect as useEffect10, useLayoutEffect as useLayoutEffect4, useMemo as useMemo2, useRef as useRef9 } from "react";
@@ -1788,8 +1789,21 @@ function parseMcpToolName(name) {
1788
1789
  function isMcpToolName(name) {
1789
1790
  return Boolean(parseMcpToolName(name));
1790
1791
  }
1792
+ var SELF_MCP_SERVER = "plugin_mixdog_mixdog";
1793
+ function isSelfMcpToolName(name) {
1794
+ const mcp = parseMcpToolName(name);
1795
+ return Boolean(mcp && mcp.server === SELF_MCP_SERVER);
1796
+ }
1797
+ function isExternalMcpToolName(name) {
1798
+ return isMcpToolName(name) && !isSelfMcpToolName(name);
1799
+ }
1800
+ function titleCaseMcpServer(server) {
1801
+ return String(server || "").split(/[_\s-]+/).filter(Boolean).map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1).toLowerCase()}`).join(" ");
1802
+ }
1803
+ var TOOL_NAME_ALIASES = { tool_search: "load_tool" };
1791
1804
  function normalizeToolName(name) {
1792
- return stripToolPrefix(name).replace(/-/g, "_").toLowerCase();
1805
+ const base = stripToolPrefix(name).replace(/-/g, "_").toLowerCase();
1806
+ return TOOL_NAME_ALIASES[base] || base;
1793
1807
  }
1794
1808
  function truncateToolText(value, max = DEFAULT_SUMMARY_MAX) {
1795
1809
  const text = String(value ?? "").trim();
@@ -1829,16 +1843,6 @@ function displayToolPath(path2) {
1829
1843
  function compactParts(parts) {
1830
1844
  return parts.filter((part) => part != null && String(part).trim()).map((part) => String(part).trim()).join(STATUS_SEPARATOR);
1831
1845
  }
1832
- function compactSlash(left, right) {
1833
- const a = String(left ?? "").trim();
1834
- const b = String(right ?? "").trim();
1835
- return a && b ? `${a}/${b}` : a || b;
1836
- }
1837
- function mcpToolTarget(name, max = DEFAULT_SUMMARY_MAX) {
1838
- const mcp = parseMcpToolName(name);
1839
- if (!mcp) return "";
1840
- return truncateToolText(compactSlash(mcp.server, mcp.tool), max);
1841
- }
1842
1846
  function quoted(value, max) {
1843
1847
  const text = truncateToolText(value || "", max);
1844
1848
  return text ? `"${text}"` : "";
@@ -2435,7 +2439,10 @@ function summarizeAgentSurfaceBrief(name, args, resultText, { isError = false, i
2435
2439
 
2436
2440
  // src/runtime/shared/tool-surface.mjs
2437
2441
  function displayToolName(name, args = {}) {
2438
- if (isMcpToolName(name)) return "MCP";
2442
+ if (isExternalMcpToolName(name)) {
2443
+ const mcp = parseMcpToolName(name);
2444
+ return `MCP ${titleCaseMcpServer(mcp.server)}`;
2445
+ }
2439
2446
  const normalized = normalizeToolName(name);
2440
2447
  switch (normalized) {
2441
2448
  case "read":
@@ -2461,7 +2468,7 @@ function displayToolName(name, args = {}) {
2461
2468
  case "list":
2462
2469
  case "ls":
2463
2470
  return "Search";
2464
- case "tool_search":
2471
+ case "load_tool":
2465
2472
  return toolSearchDisplayLabel(parseToolArgs(args));
2466
2473
  case "search":
2467
2474
  case "search_query":
@@ -2511,10 +2518,10 @@ function summarizeToolArgs(name, args, { max = DEFAULT_SUMMARY_MAX } = {}) {
2511
2518
  const a = parseToolArgs(args);
2512
2519
  if (!a || typeof a !== "object") return "";
2513
2520
  const normalized = normalizeToolName(name);
2514
- const mcpTarget = mcpToolTarget(name, max);
2515
- if (mcpTarget) {
2521
+ if (isExternalMcpToolName(name)) {
2522
+ const mcp = parseMcpToolName(name);
2516
2523
  return compactParts([
2517
- mcpTarget,
2524
+ truncateToolText(mcp.tool, max),
2518
2525
  truncateToolText(firstText(a.query, a.q, a.text, a.prompt, a.path, a.uri, a.name, a.id, a.action), Math.min(max, 80))
2519
2526
  ]);
2520
2527
  }
@@ -2591,8 +2598,8 @@ function summarizeToolArgs(name, args, { max = DEFAULT_SUMMARY_MAX } = {}) {
2591
2598
  return formatCountedUnit(collectionCount(a.query, a.prompt, a.task, a.goal), "query", "queries");
2592
2599
  }
2593
2600
  return truncateSingleLine(firstText(a.query, a.prompt, a.task, a.goal, a.path), Math.min(max, 80));
2594
- case "tool_search": {
2595
- const selected = splitToolSearchSelection(a.select);
2601
+ case "load_tool": {
2602
+ const selected = [...splitToolSearchSelection(a.names), ...splitToolSearchSelection(a.select)];
2596
2603
  if (selected.length) return truncateToolText(selected.map(displayToolSearchTarget).join(", "), max);
2597
2604
  return quoted(firstText(a.query, a.q, a.text), max);
2598
2605
  }
@@ -2695,7 +2702,7 @@ var TOOL_CATEGORY = /* @__PURE__ */ new Map([
2695
2702
  ["glob", "Search"],
2696
2703
  ["list", "Search"],
2697
2704
  ["ls", "Search"],
2698
- ["tool_search", "Load"],
2705
+ ["load_tool", "Load"],
2699
2706
  ["search", "Web Research"],
2700
2707
  ["web_search", "Web Research"],
2701
2708
  ["search_query", "Web Research"],
@@ -2732,7 +2739,7 @@ var TOOL_CATEGORY = /* @__PURE__ */ new Map([
2732
2739
  ["use_skill", "Skill"]
2733
2740
  ]);
2734
2741
  function classifyToolCategory(name, args = {}) {
2735
- if (isMcpToolName(name)) return "MCP";
2742
+ if (isExternalMcpToolName(name)) return "MCP";
2736
2743
  const normalized = normalizeToolName(name);
2737
2744
  if (normalized === "code_graph") {
2738
2745
  const mode = String(args.mode || args.action || "").toLowerCase();
@@ -2780,8 +2787,12 @@ function toolWorkUnit(name, args = {}, category = "") {
2780
2787
  const a = parseToolArgs(args);
2781
2788
  const normalized = normalizeToolName(name);
2782
2789
  const cat = category || classifyToolCategory(name, a);
2783
- if (isMcpToolName(name)) {
2784
- return unitDescriptor("MCP", { count: queryCount(a, "query", "q", "text", "prompt", "path", "uri", "name", "id", "action") || 1 });
2790
+ if (isExternalMcpToolName(name)) {
2791
+ const mcp = parseMcpToolName(name);
2792
+ return unitDescriptor("MCP", {
2793
+ count: queryCount(a, "query", "q", "text", "prompt", "path", "uri", "name", "id", "action") || 1,
2794
+ noun: `${titleCaseMcpServer(mcp.server)} tool`
2795
+ });
2785
2796
  }
2786
2797
  switch (normalized) {
2787
2798
  case "read":
@@ -2816,8 +2827,8 @@ function toolWorkUnit(name, args = {}, category = "") {
2816
2827
  case "list":
2817
2828
  case "ls":
2818
2829
  return unitDescriptor("Search", { count: queryCount(a, "path", "paths", "dir", "dirs", "cwd") || 1, active: "Listing", done: "Listed", noun: "directory", pluralNoun: "directories" });
2819
- case "tool_search": {
2820
- const selected = splitToolSearchSelection(a.select);
2830
+ case "load_tool": {
2831
+ const selected = [...splitToolSearchSelection(a.names), ...splitToolSearchSelection(a.select)];
2821
2832
  if (selected.length) return unitDescriptor("Load", { count: selected.length, noun: "tool" });
2822
2833
  return unitDescriptor("Load", { count: queryCount(a, "query", "q", "text") || 1, noun: "query", pluralNoun: "queries" });
2823
2834
  }
@@ -2913,6 +2924,16 @@ function aggregateToolCategoryEntry(name, args = {}, category = "") {
2913
2924
  count: Math.max(1, Number(unit.count || 1))
2914
2925
  };
2915
2926
  }
2927
+ function aggregateDoneCategories(calls = []) {
2928
+ const map = /* @__PURE__ */ new Map();
2929
+ for (const rec of calls || []) {
2930
+ if (!rec || rec.isError) continue;
2931
+ const entry = aggregateToolCategoryEntry(rec.name, rec.args, rec.category);
2932
+ const prev = map.get(entry.key);
2933
+ map.set(entry.key, { ...entry, count: Number(prev?.count || 0) + Number(entry.count || 1) });
2934
+ }
2935
+ return Object.fromEntries(map);
2936
+ }
2916
2937
  function aggregateCount(value) {
2917
2938
  if (value && typeof value === "object") return Math.max(0, Number(value.count || 0));
2918
2939
  return Math.max(0, Number(value || 0));
@@ -3889,7 +3910,7 @@ function localStatusLineL2({
3889
3910
  }
3890
3911
  if (searchInfo && localNum(searchInfo.count) > 0) {
3891
3912
  const elapsed = localNum(searchInfo.startedAt) > 0 ? localFormatElapsed(now - localNum(searchInfo.startedAt)) : "";
3892
- l2Parts.push(`${spin} ${STATUS}Searching${RESET2}${elapsedSuffix(elapsed)}`);
3913
+ l2Parts.push(`${spin} ${STATUS}Web Searching${RESET2}${elapsedSuffix(elapsed)}`);
3893
3914
  }
3894
3915
  return l2Parts.length ? l2Parts.join(segSep) : "";
3895
3916
  }
@@ -4943,7 +4964,7 @@ function PromptInput({
4943
4964
  return;
4944
4965
  }
4945
4966
  if (!commandPaletteActive && (key.ctrl && inputKey === "v" || key.meta && inputKey === "v")) {
4946
- handleExternalPaste("", { source: "clipboard-image-shortcut" });
4967
+ handleExternalPaste("", { source: "clipboard-shortcut" });
4947
4968
  return;
4948
4969
  }
4949
4970
  if (key.return) {
@@ -5220,13 +5241,17 @@ ${d.value.slice(d.cursor)}`,
5220
5241
  import React4 from "react";
5221
5242
  import { Box as Box4, Text as Text4 } from "../../../vendor/ink/build/index.js";
5222
5243
  import { jsx as jsx4 } from "react/jsx-runtime";
5223
- function QueuedCommands({ queued, columns }) {
5244
+ function QueuedCommands({ queued, columns, compact = false }) {
5224
5245
  if (!queued || queued.length === 0) return null;
5225
5246
  const bandColumns = Math.max(1, columns - 1);
5247
+ const contentWidth = Math.max(1, columns - 4);
5226
5248
  return /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: queued.map((item) => {
5227
- const contentWidth = Math.max(1, columns - 4);
5228
5249
  const sourceText = String(item.displayText || item.text || "");
5229
- const displayText = sourceText.length > contentWidth ? contentWidth <= 1 ? "\u2026".repeat(contentWidth) : sourceText.slice(0, Math.max(1, contentWidth - 1)) + "\u2026" : sourceText;
5250
+ let displayText = sourceText;
5251
+ if (compact) {
5252
+ const oneLine2 = sourceText.replace(/\r?\n/g, " ");
5253
+ displayText = oneLine2.length > contentWidth ? contentWidth <= 1 ? "\u2026".repeat(contentWidth) : oneLine2.slice(0, Math.max(1, contentWidth - 1)) + "\u2026" : oneLine2;
5254
+ }
5230
5255
  return /* @__PURE__ */ jsx4(Box4, { width: bandColumns, backgroundColor: theme.userMessageBackground, paddingLeft: 2, paddingRight: 1, children: /* @__PURE__ */ jsx4(Text4, { wrap: "wrap", children: /* @__PURE__ */ jsx4(Text4, { color: theme.mixdogIvory, children: displayText }) }) }, item.id);
5231
5256
  }) });
5232
5257
  }
@@ -6346,6 +6371,12 @@ function wrappedDetailRows(text, width) {
6346
6371
  const w = Math.max(1, Math.floor(Number(width) || 1));
6347
6372
  return wrapAnsi(value, w, { trim: false, hard: true }).split("\n").length;
6348
6373
  }
6374
+ function queuedBandRows(text, width) {
6375
+ const value = String(text ?? "");
6376
+ if (!value) return 1;
6377
+ const w = Math.max(1, Math.floor(Number(width) || 1));
6378
+ return Math.max(1, wrapAnsi(value, w, { trim: false, hard: true }).split("\n").length);
6379
+ }
6349
6380
  function visualRowStartOffsets(text, width) {
6350
6381
  const value = String(text ?? "");
6351
6382
  const w = Math.max(1, Math.floor(Number(width) || 1));
@@ -6830,6 +6861,7 @@ function TextEntryPanel({
6830
6861
 
6831
6862
  // src/tui/paste-attachments.mjs
6832
6863
  import { execFile } from "node:child_process";
6864
+ import { Buffer as Buffer2 } from "node:buffer";
6833
6865
  import { randomBytes } from "node:crypto";
6834
6866
  import { existsSync, unlinkSync } from "node:fs";
6835
6867
  import { readFile as readFileAsync, stat as statAsync } from "node:fs/promises";
@@ -6983,7 +7015,7 @@ function mimeForPath(path2) {
6983
7015
  return IMAGE_MIME[extname(String(path2 || "")).toLowerCase()] || null;
6984
7016
  }
6985
7017
  function execFileBuffer(cmd, args, options = {}) {
6986
- return new Promise((resolve6) => {
7018
+ return new Promise((resolve5) => {
6987
7019
  execFile(cmd, args, {
6988
7020
  windowsHide: true,
6989
7021
  encoding: "buffer",
@@ -6991,7 +7023,7 @@ function execFileBuffer(cmd, args, options = {}) {
6991
7023
  timeout: 5e3,
6992
7024
  ...options
6993
7025
  }, (error, stdout, stderr) => {
6994
- resolve6({ ok: !error, code: error?.code ?? 0, stdout, stderr, error });
7026
+ resolve5({ ok: !error, code: error?.code ?? 0, stdout, stderr, error });
6995
7027
  });
6996
7028
  });
6997
7029
  }
@@ -7066,7 +7098,7 @@ function splitPastedImagePathCandidates(text) {
7066
7098
  return out;
7067
7099
  }
7068
7100
  async function imageAttachmentFromBuffer(buffer, mimeType, { filename = "Pasted image", sourcePath = "" } = {}) {
7069
- if (!Buffer.isBuffer(buffer) || buffer.length === 0) throw new Error("image is empty");
7101
+ if (!Buffer2.isBuffer(buffer) || buffer.length === 0) throw new Error("image is empty");
7070
7102
  const ext = (mimeType || "image/png").split("/")[1] || "png";
7071
7103
  const resized = await resizeImageBuffer(buffer, ext);
7072
7104
  if (resized) {
@@ -7145,6 +7177,30 @@ async function readClipboardImageAttachment() {
7145
7177
  }
7146
7178
  }
7147
7179
  }
7180
+ async function readClipboardText() {
7181
+ if (process.platform === "win32") {
7182
+ const ps = "$t=Get-Clipboard -Raw; if ($null -eq $t) { exit 2 }; [Console]::Out.Write([Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($t)))";
7183
+ const r = await execFileBuffer("powershell.exe", ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", ps], { timeout: 3e3 });
7184
+ if (!r.ok || !r.stdout?.length) return "";
7185
+ try {
7186
+ return Buffer2.from(r.stdout.toString("ascii").trim(), "base64").toString("utf8");
7187
+ } catch {
7188
+ return "";
7189
+ }
7190
+ }
7191
+ if (process.platform === "darwin") {
7192
+ const r = await execFileBuffer("pbpaste", [], { timeout: 3e3 });
7193
+ return r.ok && r.stdout?.length ? r.stdout.toString("utf8") : "";
7194
+ }
7195
+ if (process.platform === "linux") {
7196
+ const wl = await execFileBuffer("wl-paste", ["--no-newline"], { timeout: 3e3 });
7197
+ if (wl.ok && wl.stdout?.length) return wl.stdout.toString("utf8");
7198
+ const xc = await execFileBuffer("xclip", ["-selection", "clipboard", "-o"], { timeout: 3e3 });
7199
+ if (xc.ok && xc.stdout?.length) return xc.stdout.toString("utf8");
7200
+ return "";
7201
+ }
7202
+ return "";
7203
+ }
7148
7204
 
7149
7205
  // src/standalone/projects.mjs
7150
7206
  import { homedir } from "node:os";
@@ -7302,12 +7358,12 @@ import { spawn } from "node:child_process";
7302
7358
  import path from "node:path";
7303
7359
  var DIALOG_TIMEOUT_MS = 5 * 60 * 1e3;
7304
7360
  function runCapture(cmd, args, { timeoutMs = DIALOG_TIMEOUT_MS } = {}) {
7305
- return new Promise((resolve6) => {
7361
+ return new Promise((resolve5) => {
7306
7362
  let child;
7307
7363
  try {
7308
7364
  child = spawn(cmd, args, { stdio: ["ignore", "pipe", "ignore"], windowsHide: true });
7309
7365
  } catch (error) {
7310
- resolve6({ ok: false, code: -1, stdout: "", error });
7366
+ resolve5({ ok: false, code: -1, stdout: "", error });
7311
7367
  return;
7312
7368
  }
7313
7369
  let stdout = "";
@@ -7319,7 +7375,7 @@ function runCapture(cmd, args, { timeoutMs = DIALOG_TIMEOUT_MS } = {}) {
7319
7375
  child.kill();
7320
7376
  } catch {
7321
7377
  }
7322
- resolve6({ ok: false, code: -1, stdout: "", error: new Error("dialog timed out") });
7378
+ resolve5({ ok: false, code: -1, stdout: "", error: new Error("dialog timed out") });
7323
7379
  }, timeoutMs);
7324
7380
  timer.unref?.();
7325
7381
  child.stdout.on("data", (chunk) => {
@@ -7329,13 +7385,13 @@ function runCapture(cmd, args, { timeoutMs = DIALOG_TIMEOUT_MS } = {}) {
7329
7385
  if (settled) return;
7330
7386
  settled = true;
7331
7387
  clearTimeout(timer);
7332
- resolve6({ ok: false, code: -1, stdout: "", error });
7388
+ resolve5({ ok: false, code: -1, stdout: "", error });
7333
7389
  });
7334
7390
  child.on("close", (code) => {
7335
7391
  if (settled) return;
7336
7392
  settled = true;
7337
7393
  clearTimeout(timer);
7338
- resolve6({ ok: code === 0, code, stdout });
7394
+ resolve5({ ok: code === 0, code, stdout });
7339
7395
  });
7340
7396
  });
7341
7397
  }
@@ -7343,10 +7399,10 @@ function spawnFailed(result) {
7343
7399
  return !!result && result.code === -1;
7344
7400
  }
7345
7401
  function commandExists(cmd) {
7346
- return new Promise((resolve6) => {
7402
+ return new Promise((resolve5) => {
7347
7403
  const probe = process.platform === "win32" ? spawn("where", [cmd], { stdio: "ignore", windowsHide: true }) : spawn("which", [cmd], { stdio: "ignore" });
7348
- probe.on("error", () => resolve6(false));
7349
- probe.on("close", (code) => resolve6(code === 0));
7404
+ probe.on("error", () => resolve5(false));
7405
+ probe.on("close", (code) => resolve5(code === 0));
7350
7406
  });
7351
7407
  }
7352
7408
  function psSingleQuoted(value) {
@@ -7845,64 +7901,67 @@ function memoryCoreResultErrorText(text) {
7845
7901
  }
7846
7902
 
7847
7903
  // src/tui/app/clipboard.mjs
7848
- import { Buffer as Buffer2 } from "node:buffer";
7904
+ import { Buffer as Buffer3 } from "node:buffer";
7849
7905
  import { spawn as spawn2 } from "node:child_process";
7850
7906
  function osc52ClipboardSequence(text) {
7851
- const b64 = Buffer2.from(String(text ?? ""), "utf8").toString("base64");
7907
+ const b64 = Buffer3.from(String(text ?? ""), "utf8").toString("base64");
7852
7908
  const raw = `\x1B]52;c;${b64}\x07`;
7853
7909
  if (!process.env.TMUX) return raw;
7854
7910
  return `\x1BPtmux;${raw.replaceAll("\x1B", "\x1B\x1B")}\x1B\\`;
7855
7911
  }
7912
+ var OSC52_MAX_BYTES = 256 * 1024;
7856
7913
  function writeOsc52Clipboard(text) {
7914
+ const value = String(text ?? "");
7915
+ if (Buffer3.byteLength(value, "utf8") > OSC52_MAX_BYTES) return false;
7857
7916
  try {
7858
- process.stdout.write(osc52ClipboardSequence(text));
7917
+ process.stdout.write(osc52ClipboardSequence(value));
7859
7918
  return true;
7860
7919
  } catch {
7861
7920
  return false;
7862
7921
  }
7863
7922
  }
7864
7923
  function nativeClipboardCommand(text) {
7924
+ const value = String(text ?? "");
7865
7925
  if (process.platform === "win32") {
7866
7926
  return {
7867
- cmd: "powershell.exe",
7868
- args: [
7869
- "-NoLogo",
7870
- "-NoProfile",
7871
- "-NonInteractive",
7872
- "-Command",
7873
- "$b=[Console]::In.ReadToEnd();$t=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($b));Set-Clipboard -Value $t"
7874
- ],
7875
- input: Buffer2.from(String(text ?? ""), "utf8").toString("base64")
7927
+ cmd: "clip.exe",
7928
+ args: [],
7929
+ input: Buffer3.from(value, "utf16le")
7876
7930
  };
7877
7931
  }
7878
- if (process.platform === "darwin") return { cmd: "pbcopy", args: [], input: text };
7879
- if (process.env.WAYLAND_DISPLAY) return { cmd: "wl-copy", args: [], input: text };
7880
- return { cmd: "xclip", args: ["-selection", "clipboard"], input: text };
7932
+ if (process.platform === "darwin") return { cmd: "pbcopy", args: [], input: value };
7933
+ if (process.env.WAYLAND_DISPLAY) return { cmd: "wl-copy", args: [], input: value };
7934
+ return { cmd: "xclip", args: ["-selection", "clipboard"], input: value };
7881
7935
  }
7882
7936
  function copyToClipboard(text) {
7883
7937
  const value = String(text ?? "");
7884
7938
  const wroteOsc52 = writeOsc52Clipboard(value);
7885
- return new Promise((resolve6, reject) => {
7939
+ return new Promise((resolve5, reject) => {
7886
7940
  const { cmd, args, input } = nativeClipboardCommand(value);
7887
7941
  let child;
7888
7942
  try {
7889
7943
  child = spawn2(cmd, args, { stdio: ["pipe", "ignore", "ignore"], windowsHide: true });
7890
7944
  } catch (e) {
7891
- if (wroteOsc52) resolve6();
7945
+ if (wroteOsc52) resolve5();
7892
7946
  else reject(e);
7893
7947
  return;
7894
7948
  }
7895
7949
  child.on("error", (e) => {
7896
- if (wroteOsc52) resolve6();
7897
- else reject(e);
7898
- });
7899
- child.on("close", (code) => {
7900
- if (code === 0 || wroteOsc52) resolve6();
7901
- else reject(new Error(`${cmd} exited with code ${code}`));
7950
+ if (!wroteOsc52) reject(e);
7902
7951
  });
7952
+ if (!wroteOsc52) {
7953
+ child.on("close", (code) => {
7954
+ if (code === 0) resolve5();
7955
+ else reject(new Error(`${cmd} exited with code ${code}`));
7956
+ });
7957
+ }
7903
7958
  child.stdin.on("error", () => {
7904
7959
  });
7905
7960
  child.stdin.end(input);
7961
+ if (wroteOsc52) {
7962
+ child.unref?.();
7963
+ resolve5();
7964
+ }
7906
7965
  });
7907
7966
  }
7908
7967
 
@@ -10382,6 +10441,7 @@ var ALT_SCROLL_ON = "\x1B[?1007h";
10382
10441
  var MOUSE_MODIFIER_MASK = 4 | 8 | 16;
10383
10442
  var MOUSE_CTRL_MASK = 16;
10384
10443
  var MOUSE_SHIFT_MASK = 4;
10444
+ var IS_WINDOWS_TERMINAL = Boolean(process.env.WT_SESSION);
10385
10445
  function useMouseInput({
10386
10446
  inkInput,
10387
10447
  isRawModeSupported,
@@ -10409,7 +10469,8 @@ function useMouseInput({
10409
10469
  setMeasuredRowsVersion,
10410
10470
  clearStitchBuffer
10411
10471
  }) {
10412
- const mouseZoomPassthroughTimerRef = useRef6(null);
10472
+ const zoomTimerRef = useRef6(null);
10473
+ const edgeAutoscrollRef = useRef6({ dir: 0, timer: null, noMove: 0 });
10413
10474
  const passthroughCtrlWheelZoom = useCallback2(() => {
10414
10475
  if (!stdout?.write) return;
10415
10476
  try {
@@ -10417,19 +10478,19 @@ function useMouseInput({
10417
10478
  } catch {
10418
10479
  return;
10419
10480
  }
10420
- if (mouseZoomPassthroughTimerRef.current) clearTimeout(mouseZoomPassthroughTimerRef.current);
10421
- mouseZoomPassthroughTimerRef.current = setTimeout(() => {
10422
- mouseZoomPassthroughTimerRef.current = null;
10481
+ if (zoomTimerRef.current) clearTimeout(zoomTimerRef.current);
10482
+ zoomTimerRef.current = setTimeout(() => {
10483
+ zoomTimerRef.current = null;
10423
10484
  try {
10424
10485
  stdout.write(ALT_SCROLL_ON + MOUSE_TRACKING_ON);
10425
10486
  } catch {
10426
10487
  }
10427
10488
  }, 700);
10428
- mouseZoomPassthroughTimerRef.current.unref?.();
10429
- }, [stdout]);
10489
+ zoomTimerRef.current.unref?.();
10490
+ }, [stdout, zoomTimerRef]);
10430
10491
  useEffect6(() => () => {
10431
- if (mouseZoomPassthroughTimerRef.current) clearTimeout(mouseZoomPassthroughTimerRef.current);
10432
- }, []);
10492
+ if (zoomTimerRef.current) clearTimeout(zoomTimerRef.current);
10493
+ }, [zoomTimerRef]);
10433
10494
  useEffect6(() => {
10434
10495
  if (!inkInput || !isRawModeSupported) return void 0;
10435
10496
  const WHEEL_SGR = /\x1b\[<(\d+);/;
@@ -10494,6 +10555,65 @@ function useMouseInput({
10494
10555
  promptMouseSelectionRef.current?.clear?.();
10495
10556
  applySelectionRect(null);
10496
10557
  };
10558
+ const EDGE_AUTOSCROLL_INTERVAL_MS = 50;
10559
+ const stopEdgeAutoscroll = () => {
10560
+ const st = edgeAutoscrollRef.current;
10561
+ if (st.timer) {
10562
+ clearInterval(st.timer);
10563
+ st.timer = null;
10564
+ }
10565
+ st.dir = 0;
10566
+ st.noMove = 0;
10567
+ };
10568
+ const startEdgeAutoscroll = (dir) => {
10569
+ const st = edgeAutoscrollRef.current;
10570
+ if (st.dir === dir && st.timer) return;
10571
+ stopEdgeAutoscroll();
10572
+ st.dir = dir;
10573
+ st.noMove = 0;
10574
+ st.timer = setInterval(() => {
10575
+ const drag = dragRef.current;
10576
+ const st2 = edgeAutoscrollRef.current;
10577
+ if (!drag.active || drag.region !== "transcript" || st2.dir === 0) {
10578
+ stopEdgeAutoscroll();
10579
+ return;
10580
+ }
10581
+ const before = Number(scrollTargetRef.current) || 0;
10582
+ queueScrollCoalesced(st2.dir * 3);
10583
+ if ((Number(scrollTargetRef.current) || 0) !== before) {
10584
+ st2.noMove = 0;
10585
+ } else if (++st2.noMove >= 3) {
10586
+ stopEdgeAutoscroll();
10587
+ }
10588
+ }, EDGE_AUTOSCROLL_INTERVAL_MS);
10589
+ st.timer.unref?.();
10590
+ };
10591
+ const finalizeActiveDrag = (fx, fy) => {
10592
+ const drag = dragRef.current;
10593
+ if (!drag.active) return;
10594
+ stopEdgeAutoscroll();
10595
+ const region = drag.region;
10596
+ if (region === "prompt") {
10597
+ const offset = promptOffsetAt(fx, fy);
10598
+ drag.active = false;
10599
+ promptMouseSelectionRef.current?.extendTo?.(offset, true);
10600
+ return;
10601
+ }
10602
+ const span = drag.anchorSpan;
10603
+ const finalY = region === "status" ? clampToStatusBand(fy) : clampToTranscriptViewport(fy);
10604
+ drag.active = false;
10605
+ if (span) {
10606
+ const rect = buildSpanRect(span, fx, finalY, region, drag.anchorScroll);
10607
+ applySelectionRect(rect);
10608
+ } else {
10609
+ const anchor = region === "status" ? drag.anchor : selectionPointAtCurrentScroll(drag.anchor, drag.anchorScroll);
10610
+ const rect = linearSelection(anchor, { x: fx, y: finalY });
10611
+ const empty = rect.x1 === rect.x2 && rect.y1 === rect.y2;
10612
+ if (empty) applySelectionRect(null);
10613
+ else applySelectionRect(rect);
10614
+ }
10615
+ if (TRANSCRIPT_MEASURED_ROWS) setMeasuredRowsVersion((v) => (v + 1) % 1e6);
10616
+ };
10497
10617
  const onMouse = (event) => {
10498
10618
  if (!event || typeof event !== "object") return;
10499
10619
  let up = 0;
@@ -10505,15 +10625,20 @@ function useMouseInput({
10505
10625
  const wm = WHEEL_SGR.exec(seq);
10506
10626
  const ctrl = wm ? (Number(wm[1]) & MOUSE_CTRL_MASK) !== 0 : false;
10507
10627
  if (ctrl) {
10628
+ if (dragRef.current.active) {
10629
+ const last = dragRef.current.last || {};
10630
+ finalizeActiveDrag(Number(last.x) || 0, Number(last.y) || 0);
10631
+ } else {
10632
+ stopEdgeAutoscroll();
10633
+ }
10508
10634
  passthroughCtrlWheelZoom();
10509
10635
  return;
10510
10636
  }
10511
10637
  if (name === "wheelup") up += 1;
10512
10638
  else down += 1;
10513
10639
  if (up !== 0 || down !== 0) {
10514
- if (dragRef.current.active) return;
10515
10640
  const palette = slashPaletteRef.current;
10516
- if (palette.open && palette.count > 0) {
10641
+ if (!dragRef.current.active && palette.open && palette.count > 0) {
10517
10642
  const step = down - up;
10518
10643
  if (step !== 0) {
10519
10644
  setSlashIndex((index) => Math.max(0, Math.min(palette.count - 1, index + step)));
@@ -10535,14 +10660,64 @@ function useMouseInput({
10535
10660
  const baseButton = button & 3;
10536
10661
  const isMotion = (button & 32) !== 0;
10537
10662
  const shiftHeld = (button & MOUSE_SHIFT_MASK) !== 0;
10663
+ const ctrlHeld = (button & MOUSE_CTRL_MASK) !== 0;
10664
+ if (IS_WINDOWS_TERMINAL && shiftHeld) return;
10665
+ if (IS_WINDOWS_TERMINAL && press && !isMotion) {
10666
+ store.forceRenderRepaint?.();
10667
+ }
10668
+ const extendHeld = shiftHeld || ctrlHeld;
10669
+ const isRightPress = baseButton === 2 && press && !isMotion;
10670
+ if (isRightPress) {
10671
+ if (isInPromptBox(x, y)) {
10672
+ const ctl = promptMouseSelectionRef.current;
10673
+ if (ctl?.hasSelection?.()) {
10674
+ applySelectionRect(null);
10675
+ const offset = promptOffsetAt(x, y);
10676
+ stopSmoothScroll();
10677
+ dragRef.current = { anchor: { x, y }, anchorScroll: 0, last: { x, y }, active: false, rect: null, region: "prompt", anchorSpan: null };
10678
+ if (offset != null) ctl.extendTo?.(offset, true);
10679
+ lastClickRef.current = { x: -1, y: -1, t: 0 };
10680
+ }
10681
+ return;
10682
+ }
10683
+ const inTranscriptR = isInTranscriptViewport(y);
10684
+ const inStatusR = !inTranscriptR && isInStatusBand(y);
10685
+ if (!inTranscriptR && !inStatusR) return;
10686
+ const regionR = inTranscriptR ? "transcript" : "status";
10687
+ const nowR = Date.now();
10688
+ if (dragRef.current.region === regionR && dragRef.current.anchorSpan) {
10689
+ promptMouseSelectionRef.current?.clear?.();
10690
+ const selectionY = regionR === "status" ? clampToStatusBand(y) : clampToTranscriptViewport(y);
10691
+ const span = dragRef.current.anchorSpan;
10692
+ const rect = buildSpanRect(span, x, selectionY, regionR, dragRef.current.anchorScroll);
10693
+ stopSmoothScroll();
10694
+ dragRef.current = { ...dragRef.current, last: { x, y: selectionY }, active: false, region: regionR };
10695
+ applySelectionRect(rect);
10696
+ lastClickRef.current = { x, y, t: nowR, count: 1 };
10697
+ return;
10698
+ }
10699
+ if (dragRef.current.region === regionR && !dragRef.current.anchorSpan && dragRef.current.anchor && dragRef.current.rect && !(dragRef.current.rect.x1 === dragRef.current.rect.x2 && dragRef.current.rect.y1 === dragRef.current.rect.y2)) {
10700
+ promptMouseSelectionRef.current?.clear?.();
10701
+ const selectionY = regionR === "status" ? clampToStatusBand(y) : clampToTranscriptViewport(y);
10702
+ const anchor = regionR === "status" ? dragRef.current.anchor : selectionPointAtCurrentScroll(dragRef.current.anchor, dragRef.current.anchorScroll);
10703
+ const rect = linearSelection(anchor, { x, y: selectionY });
10704
+ stopSmoothScroll();
10705
+ dragRef.current = { ...dragRef.current, last: { x, y: selectionY }, active: false, region: regionR };
10706
+ applySelectionRect(rect);
10707
+ lastClickRef.current = { x, y, t: nowR, count: 1 };
10708
+ return;
10709
+ }
10710
+ return;
10711
+ }
10538
10712
  if (baseButton === 0 && press && !isMotion) {
10713
+ stopEdgeAutoscroll();
10539
10714
  if (isInPromptBox(x, y)) {
10540
10715
  applySelectionRect(null);
10541
10716
  const offset = promptOffsetAt(x, y);
10542
10717
  stopSmoothScroll();
10543
10718
  dragRef.current = { anchor: { x, y }, anchorScroll: 0, last: { x, y }, active: true, rect: null, region: "prompt", anchorSpan: null };
10544
10719
  const ctl = promptMouseSelectionRef.current;
10545
- if (shiftHeld && ctl?.hasSelection?.()) {
10720
+ if (extendHeld && ctl?.hasSelection?.()) {
10546
10721
  if (offset != null) ctl.extendTo?.(offset, true);
10547
10722
  lastClickRef.current = { x: -1, y: -1, t: 0 };
10548
10723
  return;
@@ -10577,7 +10752,22 @@ function useMouseInput({
10577
10752
  const region = inTranscript ? "transcript" : "status";
10578
10753
  promptMouseSelectionRef.current?.clear?.();
10579
10754
  const now = Date.now();
10580
- if (shiftHeld && dragRef.current.region === region && !dragRef.current.anchorSpan && dragRef.current.anchor && dragRef.current.rect && !(dragRef.current.rect.x1 === dragRef.current.rect.x2 && dragRef.current.rect.y1 === dragRef.current.rect.y2)) {
10755
+ if (extendHeld && dragRef.current.region === region && dragRef.current.anchorSpan) {
10756
+ const selectionY = region === "status" ? clampToStatusBand(y) : clampToTranscriptViewport(y);
10757
+ const span = dragRef.current.anchorSpan;
10758
+ const rect = buildSpanRect(span, x, selectionY, region, dragRef.current.anchorScroll);
10759
+ stopSmoothScroll();
10760
+ dragRef.current = {
10761
+ ...dragRef.current,
10762
+ last: { x, y: selectionY },
10763
+ active: true,
10764
+ region
10765
+ };
10766
+ applySelectionRect(rect);
10767
+ lastClickRef.current = { x, y, t: now, count: 1 };
10768
+ return;
10769
+ }
10770
+ if (extendHeld && dragRef.current.region === region && !dragRef.current.anchorSpan && dragRef.current.anchor && dragRef.current.rect && !(dragRef.current.rect.x1 === dragRef.current.rect.x2 && dragRef.current.rect.y1 === dragRef.current.rect.y2)) {
10581
10771
  const selectionY = region === "status" ? clampToStatusBand(y) : clampToTranscriptViewport(y);
10582
10772
  const anchor = region === "status" ? dragRef.current.anchor : selectionPointAtCurrentScroll(dragRef.current.anchor, dragRef.current.anchorScroll);
10583
10773
  const rect = linearSelection(anchor, { x, y: selectionY });
@@ -10656,41 +10846,23 @@ function useMouseInput({
10656
10846
  const { top, bottom } = transcriptViewport();
10657
10847
  if (y <= 1 && (y < prevDragY || y < top)) {
10658
10848
  queueScrollCoalesced(3);
10849
+ startEdgeAutoscroll(1);
10659
10850
  } else if (y >= frameRows - 5 && (y > prevDragY || y > bottom)) {
10660
10851
  queueScrollCoalesced(-3);
10661
- }
10662
- }
10663
- } else if (!press && dragRef.current.active) {
10664
- const region = dragRef.current.region;
10665
- if (region === "prompt") {
10666
- const offset = promptOffsetAt(x, y);
10667
- dragRef.current.active = false;
10668
- promptMouseSelectionRef.current?.extendTo?.(offset, true);
10669
- return;
10670
- }
10671
- const span = dragRef.current.anchorSpan;
10672
- const releaseY = region === "status" ? clampToStatusBand(y) : clampToTranscriptViewport(y);
10673
- dragRef.current.active = false;
10674
- if (span) {
10675
- const rect = buildSpanRect(span, x, releaseY, region, dragRef.current.anchorScroll);
10676
- applySelectionRect(rect);
10677
- } else {
10678
- const anchor = region === "status" ? dragRef.current.anchor : selectionPointAtCurrentScroll(dragRef.current.anchor, dragRef.current.anchorScroll);
10679
- const rect = linearSelection(anchor, { x, y: releaseY });
10680
- const empty = rect.x1 === rect.x2 && rect.y1 === rect.y2;
10681
- if (empty) {
10682
- applySelectionRect(null);
10852
+ startEdgeAutoscroll(-1);
10683
10853
  } else {
10684
- applySelectionRect(rect);
10854
+ stopEdgeAutoscroll();
10685
10855
  }
10686
10856
  }
10687
- if (TRANSCRIPT_MEASURED_ROWS) setMeasuredRowsVersion((v) => (v + 1) % 1e6);
10857
+ } else if (!press && baseButton === 0 && dragRef.current.active) {
10858
+ finalizeActiveDrag(x, y);
10688
10859
  }
10689
10860
  }
10690
10861
  };
10691
10862
  inkInput.on("mouse", onMouse);
10692
10863
  return () => {
10693
10864
  inkInput.off("mouse", onMouse);
10865
+ stopEdgeAutoscroll();
10694
10866
  };
10695
10867
  }, [inkInput, isRawModeSupported, store, passthroughCtrlWheelZoom, rows, scrollTranscriptRows, queueScrollCoalesced, applySelectionRect, applySelectionRectThrottled, selectionPointAtCurrentScroll, buildSpanRect]);
10696
10868
  }
@@ -10742,26 +10914,53 @@ function useTranscriptScroll({
10742
10914
  const scroll = stitchHarvestScrollRef.current;
10743
10915
  for (const row of rows) {
10744
10916
  if (!row || typeof row.y !== "number") continue;
10745
- stitchBufferRef.current.set(row.y - scroll, typeof row.text === "string" ? row.text : "");
10917
+ stitchBufferRef.current.set(row.y - scroll, {
10918
+ text: typeof row.text === "string" ? row.text : "",
10919
+ sw: row.sw === true
10920
+ });
10746
10921
  }
10747
10922
  }, 0);
10748
10923
  }, [store]);
10924
+ const harvestStitchRowsNow = useCallback3((scroll) => {
10925
+ if (dragRef.current.region !== "transcript") return;
10926
+ const rows = store.getRenderSelectionRows?.();
10927
+ if (!Array.isArray(rows)) return;
10928
+ const s = Number(scroll) || 0;
10929
+ for (const row of rows) {
10930
+ if (!row || typeof row.y !== "number") continue;
10931
+ stitchBufferRef.current.set(row.y - s, {
10932
+ text: typeof row.text === "string" ? row.text : "",
10933
+ sw: row.sw === true
10934
+ });
10935
+ }
10936
+ }, [store]);
10749
10937
  const getStitchedSelectionText = useCallback3(() => {
10938
+ const empty = { text: "", complete: false };
10750
10939
  const buf = stitchBufferRef.current;
10751
- if (!buf.size) return "";
10752
- if (dragRef.current.region !== "transcript") return "";
10940
+ if (!buf.size) return empty;
10941
+ if (dragRef.current.region !== "transcript") return empty;
10753
10942
  const rect = dragRef.current.rect;
10754
- if (!rect) return "";
10943
+ if (!rect) return empty;
10755
10944
  const y1 = Number(rect.y1);
10756
10945
  const y2 = Number(rect.y2);
10757
- if (!Number.isFinite(y1) || !Number.isFinite(y2)) return "";
10946
+ if (!Number.isFinite(y1) || !Number.isFinite(y2)) return empty;
10758
10947
  const scroll = Number(scrollTargetRef.current) || 0;
10759
10948
  const lo = Math.min(y1, y2) - scroll;
10760
10949
  const hi = Math.max(y1, y2) - scroll;
10761
10950
  const keys = [...buf.keys()].filter((k) => k >= lo && k <= hi).sort((a, b) => a - b);
10762
- if (!keys.length) return "";
10763
- const text = keys.map((k) => buf.get(k)).filter((t) => t != null).join("\n");
10764
- return text.trim() ? text : "";
10951
+ if (!keys.length) return empty;
10952
+ const complete = keys.length === hi - lo + 1;
10953
+ const logical = [];
10954
+ for (const k of keys) {
10955
+ const entry = buf.get(k);
10956
+ if (entry == null) continue;
10957
+ const t = typeof entry === "string" ? entry : entry.text ?? "";
10958
+ const sw = typeof entry === "string" ? false : entry.sw === true;
10959
+ if (sw && logical.length > 0) logical[logical.length - 1] += t;
10960
+ else logical.push(t);
10961
+ }
10962
+ const text = logical.map((l) => l.replace(/\s+$/u, "")).join("\n");
10963
+ return text.trim() ? { text, complete } : empty;
10765
10964
  }, []);
10766
10965
  const stopSmoothScroll = useCallback3(() => {
10767
10966
  if (!scrollAnimationRef.current) return;
@@ -10856,6 +11055,25 @@ function useTranscriptScroll({
10856
11055
  if (nextRect) harvestStitchRowsSoon();
10857
11056
  return true;
10858
11057
  }, [store, rememberSelectionTextSoon, harvestStitchRowsSoon]);
11058
+ const cancelPendingSelectionPaint = useCallback3(() => {
11059
+ const state = selectionPaintRef.current;
11060
+ if (state.timer) {
11061
+ clearTimeout(state.timer);
11062
+ state.timer = null;
11063
+ }
11064
+ state.pending = null;
11065
+ }, []);
11066
+ const flushPendingSelectionPaint = useCallback3(() => {
11067
+ const state = selectionPaintRef.current;
11068
+ if (!state.timer && !state.pending) return;
11069
+ const pending = state.pending;
11070
+ if (state.timer) {
11071
+ clearTimeout(state.timer);
11072
+ state.timer = null;
11073
+ }
11074
+ state.pending = null;
11075
+ if (pending) paintSelectionRect(pending, { rememberText: false, immediate: true });
11076
+ }, [paintSelectionRect]);
10859
11077
  const applySelectionRect = useCallback3((rect) => {
10860
11078
  const clippedRect = withSelectionClip(rect);
10861
11079
  dragRef.current.rect = clippedRect || null;
@@ -10863,14 +11081,9 @@ function useTranscriptScroll({
10863
11081
  selectionTextRef.current = "";
10864
11082
  clearStitchBuffer();
10865
11083
  }
10866
- const state = selectionPaintRef.current;
10867
- if (state.timer) {
10868
- clearTimeout(state.timer);
10869
- state.timer = null;
10870
- state.pending = null;
10871
- }
11084
+ cancelPendingSelectionPaint();
10872
11085
  paintSelectionRect(clippedRect, { rememberText: true, immediate: true });
10873
- }, [paintSelectionRect, withSelectionClip, clearStitchBuffer]);
11086
+ }, [paintSelectionRect, withSelectionClip, clearStitchBuffer, cancelPendingSelectionPaint]);
10874
11087
  const applySelectionRectThrottled = useCallback3((rect) => {
10875
11088
  const clippedRect = withSelectionClip(rect, { captureText: false });
10876
11089
  if (selectionRectsEqual(dragRef.current.rect, clippedRect)) return;
@@ -10880,11 +11093,7 @@ function useTranscriptScroll({
10880
11093
  const now = Date.now();
10881
11094
  const elapsed = now - state.t;
10882
11095
  if (elapsed >= SELECTION_PAINT_INTERVAL_MS) {
10883
- if (state.timer) {
10884
- clearTimeout(state.timer);
10885
- state.timer = null;
10886
- state.pending = null;
10887
- }
11096
+ cancelPendingSelectionPaint();
10888
11097
  paintSelectionRect(clippedRect, { rememberText: false });
10889
11098
  return;
10890
11099
  }
@@ -10899,7 +11108,7 @@ function useTranscriptScroll({
10899
11108
  }, Math.max(1, SELECTION_PAINT_INTERVAL_MS - elapsed));
10900
11109
  state.timer.unref?.();
10901
11110
  }
10902
- }, [paintSelectionRect, withSelectionClip]);
11111
+ }, [paintSelectionRect, withSelectionClip, cancelPendingSelectionPaint]);
10903
11112
  const selectionPointAtCurrentScroll = useCallback3((point, pointScroll = 0) => {
10904
11113
  if (!point) return null;
10905
11114
  return {
@@ -10929,14 +11138,14 @@ function useTranscriptScroll({
10929
11138
  mHi = { x: lr.x2, y: lr.y2 };
10930
11139
  } else {
10931
11140
  mLo = { x: 0, y };
10932
- mHi = { x, y };
11141
+ mHi = { x: Math.max(0, frameColumns - 1), y };
10933
11142
  }
10934
11143
  }
10935
11144
  const rect = (a, b) => ({ mode: "linear", x1: a.x, y1: a.y, x2: b.x, y2: b.y });
10936
11145
  if (comparePoints(mHi, spanLo) < 0) return rect(spanHi, mLo);
10937
11146
  if (comparePoints(mLo, spanHi) > 0) return rect(spanLo, mHi);
10938
11147
  return rect(spanLo, spanHi);
10939
- }, [store, selectionPointAtCurrentScroll]);
11148
+ }, [store, frameColumns, selectionPointAtCurrentScroll]);
10940
11149
  const transcriptViewportRows = useCallback3(() => {
10941
11150
  const top = Math.max(0, Number(transcriptViewportRef.current?.top) || 0);
10942
11151
  const bottom = Math.max(top, Number(transcriptViewportRef.current?.bottom) || top);
@@ -10977,6 +11186,10 @@ function useTranscriptScroll({
10977
11186
  const maxTarget = Math.max(0, Number(maxScrollRowsRef.current) || 0);
10978
11187
  const target = Math.max(0, Math.min(maxTarget, scrollTargetRef.current + deltaRows));
10979
11188
  const appliedDelta = target - scrollTargetRef.current;
11189
+ if (appliedDelta !== 0 && dragRef.current.region === "transcript" && dragRef.current.rect) {
11190
+ flushPendingSelectionPaint();
11191
+ harvestStitchRowsNow(Number(scrollTargetRef.current) || 0);
11192
+ }
10980
11193
  if (appliedDelta !== 0) cancelTranscriptFollow();
10981
11194
  scrollTargetRef.current = target;
10982
11195
  if (appliedDelta !== 0) {
@@ -11022,8 +11235,9 @@ function useTranscriptScroll({
11022
11235
  } else {
11023
11236
  rect = shiftSelectionRectY(dragRef.current.rect, appliedDelta);
11024
11237
  }
11025
- const clippedRect = withSelectionClip(rect);
11238
+ const clippedRect = dragRef.current.active ? withSelectionClip(rect, { captureText: false }) : withSelectionClip(rect);
11026
11239
  dragRef.current = { ...dragRef.current, rect: clippedRect };
11240
+ cancelPendingSelectionPaint();
11027
11241
  paintSelectionRect(clippedRect, { rememberText: false });
11028
11242
  }
11029
11243
  if (options.smooth) {
@@ -11033,7 +11247,7 @@ function useTranscriptScroll({
11033
11247
  stopSmoothScroll();
11034
11248
  scrollPositionRef.current = target;
11035
11249
  setScrollOffset(Math.round(target));
11036
- }, [startSmoothScroll, stopSmoothScroll, paintSelectionRect, selectionPointAtCurrentScroll, withSelectionClip, cancelTranscriptFollow, buildSpanRect]);
11250
+ }, [startSmoothScroll, stopSmoothScroll, paintSelectionRect, selectionPointAtCurrentScroll, withSelectionClip, cancelTranscriptFollow, buildSpanRect, harvestStitchRowsNow, cancelPendingSelectionPaint, flushPendingSelectionPaint]);
11037
11251
  const queueScrollCoalesced = useCallback3((deltaRows) => {
11038
11252
  const state = scrollCoalesceRef.current;
11039
11253
  state.pendingRows += deltaRows;
@@ -11189,6 +11403,7 @@ function useTranscriptWindow({
11189
11403
  }) {
11190
11404
  const transcriptTotalRowsRef = useRef8(0);
11191
11405
  const preservedScrollDeltaRef = useRef8(0);
11406
+ const committedMaxScrollRowsRef = useRef8(0);
11192
11407
  const incrementalRowIndexCacheRef = useRef8(null);
11193
11408
  const prevViewportGeomRef = useRef8({ contentHeight: 0, floatingPanelRows: 0 });
11194
11409
  const transcriptItemElsRef = useRef8(/* @__PURE__ */ new Map());
@@ -11358,7 +11573,31 @@ function useTranscriptWindow({
11358
11573
  rowIndex: transcriptRowIndex
11359
11574
  // eslint-disable-next-line react-hooks/exhaustive-deps -- intentional: sig+scroll/viewport capture the relevant changes
11360
11575
  }), [transcriptStructureSig, renderScrollOffset, transcriptContentHeight, transcriptRowIndex]);
11361
- maxScrollRowsRef.current = transcriptWindow.maxScrollRows;
11576
+ const estimateMaxScrollRows = Math.max(0, Number(transcriptWindow.maxScrollRows) || 0);
11577
+ let holdCommittedMax = false;
11578
+ if (TRANSCRIPT_MEASURED_ROWS && estimateMaxScrollRows > committedMaxScrollRowsRef.current) {
11579
+ const toolExpandedFlag = toolOutputExpanded ? 1 : 0;
11580
+ const mountedSlice = transcriptWindow.items || [];
11581
+ for (let i = 0; i < mountedSlice.length; i++) {
11582
+ const it = mountedSlice[i];
11583
+ if (!it || shouldSuppressFullyFailedToolItem(it)) continue;
11584
+ if (it.kind === "assistant" && it.streaming) {
11585
+ const idPrev = streamingMeasuredRowsById.get(it.id);
11586
+ if (!idPrev || idPrev.columns !== frameColumns || idPrev.toolExpanded !== toolExpandedFlag) {
11587
+ holdCommittedMax = true;
11588
+ break;
11589
+ }
11590
+ } else {
11591
+ const prev = transcriptMeasuredRowsCache.get(it);
11592
+ if (!prev || prev.columns !== frameColumns || prev.toolExpanded !== toolExpandedFlag || prev.variantKey !== transcriptItemVariantKey(it)) {
11593
+ holdCommittedMax = true;
11594
+ break;
11595
+ }
11596
+ }
11597
+ }
11598
+ }
11599
+ if (!holdCommittedMax) committedMaxScrollRowsRef.current = estimateMaxScrollRows;
11600
+ maxScrollRowsRef.current = committedMaxScrollRowsRef.current;
11362
11601
  transcriptGeomRef.current = {
11363
11602
  prefixRows: transcriptRowIndex?.prefixRows || null,
11364
11603
  totalRows: Math.max(0, Number(transcriptWindow.totalRows) || 0),
@@ -12576,6 +12815,7 @@ function createExtensionPickers({
12576
12815
  clean: clean3,
12577
12816
  copyToClipboard: copyToClipboard2,
12578
12817
  setPicker,
12818
+ getPicker,
12579
12819
  setProviderPrompt,
12580
12820
  setChannelPrompt,
12581
12821
  setHookPrompt,
@@ -12583,6 +12823,8 @@ function createExtensionPickers({
12583
12823
  getDisabledSkills,
12584
12824
  setDisabledSkills
12585
12825
  }) {
12826
+ let mcpEpoch = 0;
12827
+ let mcpActive = false;
12586
12828
  const mcpStatus = () => {
12587
12829
  let status;
12588
12830
  try {
@@ -12597,6 +12839,7 @@ function createExtensionPickers({
12597
12839
  const status = mcpStatus();
12598
12840
  if (!status) return;
12599
12841
  const servers = status.servers || [];
12842
+ const optimistic = options?.optimistic || null;
12600
12843
  const items = [];
12601
12844
  if (servers.length === 0) {
12602
12845
  items.push({
@@ -12607,13 +12850,14 @@ function createExtensionPickers({
12607
12850
  });
12608
12851
  }
12609
12852
  for (const server of servers) {
12610
- const enabled = server.enabled !== false;
12853
+ const pending = optimistic && optimistic.name === server.name;
12854
+ const enabled = pending ? optimistic.enabled : server.enabled !== false;
12611
12855
  items.push({
12612
12856
  value: `server:${server.name}`,
12613
12857
  label: server.name,
12614
12858
  marker: enabled ? "\u25CF" : "\u25CB",
12615
12859
  markerColor: enabled ? theme2.success : theme2.inactive,
12616
- description: `${server.status || "unknown"} \xB7 ${server.transport || "unknown"} \xB7 ${server.toolCount || 0} tools${server.error ? ` \xB7 ${server.error}` : ""}`,
12860
+ description: pending ? `${optimistic.enabled ? "enabling" : "disabling"}\u2026 \xB7 ${server.transport || "unknown"}` : `${server.status || "unknown"} \xB7 ${server.transport || "unknown"} \xB7 ${server.toolCount || 0} tools${server.error ? ` \xB7 ${server.error}` : ""}`,
12617
12861
  _action: "server",
12618
12862
  _server: server,
12619
12863
  _enabled: enabled
@@ -12625,9 +12869,24 @@ function createExtensionPickers({
12625
12869
  setSettingsPrompt(null);
12626
12870
  const toggleServer = (item) => {
12627
12871
  if (item._action !== "server" || !item._server?.name) return;
12628
- void store.setMcpServerEnabled?.(item._server.name, !item._enabled).then(() => openMcpServersPicker({ highlightValue: `server:${item._server.name}` })).catch((e) => store.pushNotice(`mcp toggle failed: ${e?.message || e}`, "error"));
12872
+ const name = item._server.name;
12873
+ const target = !item._enabled;
12874
+ const highlightValue = `server:${name}`;
12875
+ const token = ++mcpEpoch;
12876
+ const settle = (fn) => {
12877
+ if (token !== mcpEpoch || !mcpActive || getPicker?.()?._kind !== "mcp-servers") return;
12878
+ fn();
12879
+ };
12880
+ openMcpServersPicker({ highlightValue, optimistic: { name, enabled: target } });
12881
+ Promise.resolve(store.setMcpServerEnabled?.(name, target)).then(() => {
12882
+ settle(() => openMcpServersPicker({ highlightValue }));
12883
+ }).catch((e) => {
12884
+ store.pushNotice(`mcp toggle failed: ${e?.message || e}`, "error");
12885
+ settle(() => openMcpServersPicker({ highlightValue }));
12886
+ });
12629
12887
  };
12630
12888
  setPicker({
12889
+ _kind: "mcp-servers",
12631
12890
  title: "MCP servers",
12632
12891
  description: "Enable or disable configured MCP servers.",
12633
12892
  initialIndex: Math.max(0, items.findIndex((entry) => entry.value === options?.highlightValue)),
@@ -12636,9 +12895,12 @@ function createExtensionPickers({
12636
12895
  onLeft: (item) => toggleServer(item),
12637
12896
  onRight: (item) => toggleServer(item),
12638
12897
  onCancel: () => {
12898
+ mcpActive = false;
12899
+ mcpEpoch++;
12639
12900
  setPicker(null);
12640
12901
  }
12641
12902
  });
12903
+ mcpActive = true;
12642
12904
  };
12643
12905
  const openMcpPicker = () => {
12644
12906
  openMcpServersPicker();
@@ -12695,9 +12957,14 @@ function createExtensionPickers({
12695
12957
  });
12696
12958
  };
12697
12959
  const openSkillsPicker = (options = {}) => {
12698
- const status = skillsStatus();
12699
- if (!status) return;
12700
- const skills = status.skills || [];
12960
+ let skills;
12961
+ if (Array.isArray(options.skills)) {
12962
+ skills = options.skills;
12963
+ } else {
12964
+ const status = skillsStatus();
12965
+ if (!status) return;
12966
+ skills = status.skills || [];
12967
+ }
12701
12968
  const disabledSet = options.disabledOverride instanceof Set ? options.disabledOverride : getDisabledSkills();
12702
12969
  const items = [];
12703
12970
  if (skills.length === 0) {
@@ -12736,9 +13003,10 @@ function createExtensionPickers({
12736
13003
  `skill ${item._enabled ? "disabled" : "enabled"}: ${name} (prompt updates next session /clear)`,
12737
13004
  "info"
12738
13005
  );
12739
- openSkillsPicker({ highlightValue: name, disabledOverride: next });
13006
+ openSkillsPicker({ highlightValue: name, disabledOverride: next, skills });
12740
13007
  };
12741
13008
  setPicker({
13009
+ _kind: "skills",
12742
13010
  title: "Skills",
12743
13011
  description: "Enable or disable project skills.",
12744
13012
  initialIndex: Math.max(0, items.findIndex((entry) => entry.value === options.highlightValue)),
@@ -13021,6 +13289,7 @@ ${p.description}` : ""
13021
13289
  }
13022
13290
  };
13023
13291
  setPicker({
13292
+ _kind: "hooks",
13024
13293
  title: "Hooks",
13025
13294
  description: "Before-tool hook rules; Enter toggles a rule.",
13026
13295
  items,
@@ -13249,8 +13518,6 @@ function createMaintenancePickers({
13249
13518
  const current = readCurrent();
13250
13519
  const enabled = current?.enabled !== false;
13251
13520
  const idleMs = Number(current?.idleMs || HOUR_MS);
13252
- const custom = current?.custom === true;
13253
- const providerDefault = Number(current?.providerDefault || HOUR_MS);
13254
13521
  const cacheTtlLabel = !enabled || idleMs >= HOUR_MS ? "1h" : "5m";
13255
13522
  const items = [
13256
13523
  {
@@ -13263,9 +13530,6 @@ function createMaintenancePickers({
13263
13530
  {
13264
13531
  value: "advanced",
13265
13532
  label: "Advanced",
13266
- marker: !custom ? "\u2713" : "",
13267
- markerColor: theme2.success,
13268
- meta: `${current?.provider || "current"} \xB7 ${formatDuration2(providerDefault)}`,
13269
13533
  description: "Edit provider-paired default idle windows as text.",
13270
13534
  _action: "advanced"
13271
13535
  }
@@ -13732,7 +13996,9 @@ import {
13732
13996
  } from "fs";
13733
13997
  import { dirname as dirname3, basename as basename3, join as join3 } from "path";
13734
13998
  import { randomBytes as randomBytes2 } from "crypto";
13735
- import { execFileSync } from "child_process";
13999
+ import { execFile as execFile2, execFileSync } from "child_process";
14000
+ import { promisify } from "util";
14001
+ var _execFileAsync = promisify(execFile2);
13736
14002
  var RETRY_CODES = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY", "EEXIST"]);
13737
14003
  var LOCK_WAIT_CODES = /* @__PURE__ */ new Set(["EEXIST", "EPERM", "EACCES", "EBUSY"]);
13738
14004
  var DEFAULT_BACKOFFS_MS = Object.freeze([25, 50, 100, 200, 400, 800, 1200, 1600]);
@@ -13748,6 +14014,18 @@ function sleepSync(ms) {
13748
14014
  }
13749
14015
  }
13750
14016
  }
14017
+ function _describeLockHolder(lockPath) {
14018
+ try {
14019
+ const st = statSync2(lockPath);
14020
+ const owner = _readLockOwner(lockPath);
14021
+ const ageMs = Math.max(0, Math.round(Date.now() - st.mtimeMs));
14022
+ const live = owner.pid === null ? "unknown" : _ownerIsLive(owner) ? "live" : "dead";
14023
+ const token = owner.token === null ? "?" : String(owner.token).slice(0, 8);
14024
+ return `holder pid=${owner.pid ?? "?"} token=${token} age=${ageMs}ms ${live}`;
14025
+ } catch {
14026
+ return "holder unknown (lock file unreadable/absent)";
14027
+ }
14028
+ }
13751
14029
  function renameWithRetrySync(src, dst, opts = {}) {
13752
14030
  const backoffs = Array.isArray(opts.backoffs) && opts.backoffs.length > 0 ? opts.backoffs : DEFAULT_BACKOFFS_MS;
13753
14031
  let lastErr = null;
@@ -13779,7 +14057,7 @@ function withFileLockSync(lockPath, fn, opts = {}) {
13779
14057
  lastErr = err;
13780
14058
  if (!LOCK_WAIT_CODES.has(err?.code)) throw err;
13781
14059
  if (timeoutMs <= 0) {
13782
- const contErr = new Error(`atomic lock contended (try-once): ${lockPath}`);
14060
+ const contErr = new Error(`atomic lock contended (try-once): ${lockPath} [${_describeLockHolder(lockPath)}]`);
13783
14061
  contErr.code = "ELOCKCONTENDED";
13784
14062
  contErr.cause = err;
13785
14063
  throw contErr;
@@ -13814,7 +14092,7 @@ function withFileLockSync(lockPath, fn, opts = {}) {
13814
14092
  }
13815
14093
  }
13816
14094
  }
13817
- const timeoutErr = new Error(`atomic lock timeout after ${timeoutMs}ms: ${lockPath}`);
14095
+ const timeoutErr = new Error(`atomic lock timeout after ${timeoutMs}ms: ${lockPath} [${_describeLockHolder(lockPath)}]`);
13818
14096
  timeoutErr.code = "ELOCKTIMEOUT";
13819
14097
  timeoutErr.cause = lastErr;
13820
14098
  throw timeoutErr;
@@ -14006,8 +14284,8 @@ function _releaseReclaimGuard(reclaim) {
14006
14284
  }
14007
14285
  }
14008
14286
  function _asyncSleep(ms) {
14009
- return new Promise((resolve6) => {
14010
- setTimeout(resolve6, Math.max(1, Number(ms) || 1));
14287
+ return new Promise((resolve5) => {
14288
+ setTimeout(resolve5, Math.max(1, Number(ms) || 1));
14011
14289
  });
14012
14290
  }
14013
14291
  async function withFileLock(lockPath, fn, opts = {}) {
@@ -14025,7 +14303,7 @@ async function withFileLock(lockPath, fn, opts = {}) {
14025
14303
  lastErr = err;
14026
14304
  if (!LOCK_WAIT_CODES.has(err?.code)) throw err;
14027
14305
  if (timeoutMs <= 0) {
14028
- const contErr = new Error(`atomic lock contended (try-once): ${lockPath}`);
14306
+ const contErr = new Error(`atomic lock contended (try-once): ${lockPath} [${_describeLockHolder(lockPath)}]`);
14029
14307
  contErr.code = "ELOCKCONTENDED";
14030
14308
  contErr.cause = err;
14031
14309
  throw contErr;
@@ -14047,7 +14325,7 @@ async function withFileLock(lockPath, fn, opts = {}) {
14047
14325
  } catch {
14048
14326
  }
14049
14327
  try {
14050
- if (opts.secret === true) _enforceOwnerOnlyAclWin32(lockPath);
14328
+ if (opts.secret === true) await _enforceOwnerOnlyAclWin32Async(lockPath);
14051
14329
  return await fn();
14052
14330
  } finally {
14053
14331
  try {
@@ -14060,7 +14338,7 @@ async function withFileLock(lockPath, fn, opts = {}) {
14060
14338
  }
14061
14339
  }
14062
14340
  }
14063
- const timeoutErr = new Error(`atomic lock timeout after ${timeoutMs}ms: ${lockPath}`);
14341
+ const timeoutErr = new Error(`atomic lock timeout after ${timeoutMs}ms: ${lockPath} [${_describeLockHolder(lockPath)}]`);
14064
14342
  timeoutErr.code = "ELOCKTIMEOUT";
14065
14343
  timeoutErr.cause = lastErr;
14066
14344
  throw timeoutErr;
@@ -14122,6 +14400,52 @@ function _enforceOwnerOnlyAclWin32(targetPath) {
14122
14400
  throw e;
14123
14401
  }
14124
14402
  }
14403
+ async function _resolveCurrentUserPrincipalAsync() {
14404
+ const systemRoot = process.env.SystemRoot || process.env.windir;
14405
+ if (systemRoot) {
14406
+ const whoami = join3(systemRoot, "System32", "whoami.exe");
14407
+ if (existsSync3(whoami)) {
14408
+ try {
14409
+ const { stdout } = await _execFileAsync(whoami, ["/user", "/fo", "csv", "/nh"], {
14410
+ encoding: "utf8",
14411
+ windowsHide: true
14412
+ });
14413
+ const m = String(stdout).match(/S-1-5-[0-9-]+/);
14414
+ if (m) return m[0];
14415
+ } catch {
14416
+ }
14417
+ }
14418
+ }
14419
+ const err = new Error("cannot resolve current Windows user for owner-only ACL enforcement");
14420
+ err.code = "EACLNOUSER";
14421
+ throw err;
14422
+ }
14423
+ async function _enforceOwnerOnlyAclWin32Async(targetPath) {
14424
+ if (process.platform !== "win32") return;
14425
+ const systemRoot = process.env.SystemRoot || process.env.windir;
14426
+ if (!systemRoot) {
14427
+ const err = new Error("SystemRoot not set; cannot locate icacls.exe for owner-only ACL enforcement");
14428
+ err.code = "EACLNOROOT";
14429
+ throw err;
14430
+ }
14431
+ const icacls = join3(systemRoot, "System32", "icacls.exe");
14432
+ if (!existsSync3(icacls)) {
14433
+ const err = new Error(`icacls.exe not found at ${icacls}; refusing to leave secret world-readable`);
14434
+ err.code = "EACLNOICACLS";
14435
+ throw err;
14436
+ }
14437
+ if (_cachedUserSid === null) _cachedUserSid = await _resolveCurrentUserPrincipalAsync();
14438
+ const principal = _icaclsPrincipal(_cachedUserSid);
14439
+ try {
14440
+ await _execFileAsync(icacls, [targetPath, "/reset"], { windowsHide: true });
14441
+ await _execFileAsync(icacls, [targetPath, "/inheritance:r", "/grant:r", `${principal}:(F)`], { windowsHide: true });
14442
+ } catch (err) {
14443
+ const e = new Error(`icacls failed to apply owner-only ACL to ${targetPath}: ${err?.message || err}`);
14444
+ e.code = "EACLFAIL";
14445
+ e.cause = err;
14446
+ throw e;
14447
+ }
14448
+ }
14125
14449
  function writeFileAtomicSync(filePath, data, opts = {}) {
14126
14450
  const run = () => {
14127
14451
  const dir = dirname3(filePath);
@@ -14210,21 +14534,6 @@ function writeFileAtomicSync(filePath, data, opts = {}) {
14210
14534
  function writeJsonAtomicSync(filePath, value, opts = {}) {
14211
14535
  return writeFileAtomicSync(filePath, JSON.stringify(value, null, opts.compact ? 0 : 2) + "\n", opts);
14212
14536
  }
14213
- function updateJsonAtomicSync(filePath, mutator, opts = {}) {
14214
- const { lock: _lock, ...writeOpts } = opts;
14215
- return withFileLockSync(`${filePath}.lock`, () => {
14216
- let cur = null;
14217
- try {
14218
- cur = JSON.parse(readFileSync2(filePath, "utf8"));
14219
- } catch {
14220
- cur = null;
14221
- }
14222
- const next = mutator(cur);
14223
- if (next === void 0) return cur;
14224
- writeJsonAtomicSync(filePath, next, { ...writeOpts, lock: false });
14225
- return next;
14226
- }, opts);
14227
- }
14228
14537
  async function updateJsonAtomic(filePath, mutator, opts = {}) {
14229
14538
  const { lock: _lock, ...writeOpts } = opts;
14230
14539
  return withFileLock(`${filePath}.lock`, () => {
@@ -15174,7 +15483,7 @@ function createModelPicker({
15174
15483
  items: [],
15175
15484
  onCancel: cancelModelPicker
15176
15485
  });
15177
- await new Promise((resolve6) => setTimeout(resolve6, 0));
15486
+ await new Promise((resolve5) => setTimeout(resolve5, 0));
15178
15487
  try {
15179
15488
  if (options.refreshModels !== true && options.cacheRef !== "search") {
15180
15489
  refreshModelsPromise = Promise.resolve(loadModels({ force: false }));
@@ -15500,7 +15809,7 @@ function createProviderSetupPicker({
15500
15809
  }
15501
15810
  });
15502
15811
  try {
15503
- await new Promise((resolve6) => setTimeout(resolve6, 0));
15812
+ await new Promise((resolve5) => setTimeout(resolve5, 0));
15504
15813
  setup = await store.getProviderSetup();
15505
15814
  } catch (e) {
15506
15815
  store.pushNotice(`providers failed: ${e?.message || e}`, "error");
@@ -16886,10 +17195,6 @@ function createSlashDispatch({
16886
17195
  return true;
16887
17196
  }
16888
17197
  case "search":
16889
- if (state.busy) {
16890
- store.pushNotice("wait for the current turn to finish before /search", "warn");
16891
- return false;
16892
- }
16893
17198
  if (arg) store.pushNotice("/search sets the search provider/model; the search tool uses that model when called.", "warn");
16894
17199
  openSearchPicker();
16895
17200
  return true;
@@ -17189,12 +17494,26 @@ function createSlashDispatch({
17189
17494
  import { useCallback as useCallback5 } from "react";
17190
17495
 
17191
17496
  // src/tui/prompt-history-store.mjs
17497
+ import { createHash as createHash2 } from "node:crypto";
17192
17498
  import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5, renameSync as renameSync3, writeFileSync as writeFileSync4 } from "node:fs";
17499
+ import { chmod, mkdir, rename, writeFile } from "node:fs/promises";
17193
17500
  import { dirname as dirname6, join as join6, resolve as resolve4 } from "node:path";
17194
17501
  var PROMPT_HISTORY_LIMIT2 = 50;
17195
17502
  function promptHistoryKey2(value) {
17196
17503
  return String(value || "").trim().replace(/\s+/g, " ");
17197
17504
  }
17505
+ function promptHistoryCwdKey(rawPath) {
17506
+ const text = String(rawPath || "").trim();
17507
+ if (!text) return "";
17508
+ const abs = resolve4(text);
17509
+ return process.platform === "win32" ? abs.replace(/[\\/]+$/, "").toLowerCase() : abs.replace(/\/+$/, "");
17510
+ }
17511
+ function historyFilePath(cwd) {
17512
+ const key = promptHistoryCwdKey(cwd);
17513
+ if (!key) return "";
17514
+ const digest = createHash2("sha256").update(key, "utf8").digest("hex").slice(0, 32);
17515
+ return join6(resolvePluginData(), "tui-prompt-history", `${digest}.json`);
17516
+ }
17198
17517
  function readEntries(filePath) {
17199
17518
  if (!filePath || !existsSync6(filePath)) return [];
17200
17519
  try {
@@ -17225,9 +17544,33 @@ function writeEntriesSync(filePath, entries) {
17225
17544
  return false;
17226
17545
  }
17227
17546
  }
17547
+ async function writeEntriesAsync(filePath, entries) {
17548
+ if (!filePath) return false;
17549
+ try {
17550
+ await mkdir(dirname6(filePath), { recursive: true, mode: 448 });
17551
+ const tmp = `${filePath}.${process.pid}.tmp`;
17552
+ await writeFile(tmp, buildPayload(entries), { encoding: "utf8", mode: 384 });
17553
+ await rename(tmp, filePath);
17554
+ try {
17555
+ await chmod(filePath, 384);
17556
+ } catch {
17557
+ }
17558
+ return true;
17559
+ } catch {
17560
+ return false;
17561
+ }
17562
+ }
17563
+ var WRITE_BEHIND_MS = 250;
17564
+ var FLUSH_CAP = 5;
17228
17565
  var memCache = /* @__PURE__ */ new Map();
17229
17566
  var pendingAppends = /* @__PURE__ */ new Map();
17230
17567
  var pendingTimers = /* @__PURE__ */ new Map();
17568
+ function cachedEntries(filePath) {
17569
+ if (memCache.has(filePath)) return memCache.get(filePath);
17570
+ const entries = readEntries(filePath);
17571
+ memCache.set(filePath, entries);
17572
+ return entries;
17573
+ }
17231
17574
  function applyAppend(base, value) {
17232
17575
  const key = promptHistoryKey2(value);
17233
17576
  const next = base.filter((entry) => promptHistoryKey2(entry) !== key);
@@ -17239,6 +17582,33 @@ function reconcileWithDisk(filePath, pend) {
17239
17582
  for (const value of pend) out = applyAppend(out, value);
17240
17583
  return out;
17241
17584
  }
17585
+ function scheduleWriteBehind(filePath) {
17586
+ if (!filePath) return;
17587
+ if (pendingTimers.has(filePath)) clearTimeout(pendingTimers.get(filePath));
17588
+ const timer = setTimeout(() => {
17589
+ void writeBehindFlush(filePath);
17590
+ }, WRITE_BEHIND_MS);
17591
+ if (typeof timer.unref === "function") timer.unref();
17592
+ pendingTimers.set(filePath, timer);
17593
+ }
17594
+ async function writeBehindFlush(filePath) {
17595
+ const timer = pendingTimers.get(filePath);
17596
+ if (timer) {
17597
+ clearTimeout(timer);
17598
+ pendingTimers.delete(filePath);
17599
+ }
17600
+ const pend = pendingAppends.get(filePath);
17601
+ if (!pend || !pend.length) return;
17602
+ pendingAppends.set(filePath, []);
17603
+ const merged = reconcileWithDisk(filePath, pend);
17604
+ memCache.set(filePath, merged);
17605
+ const ok = await writeEntriesAsync(filePath, merged);
17606
+ if (!ok) {
17607
+ const cur = pendingAppends.get(filePath) || [];
17608
+ pendingAppends.set(filePath, pend.concat(cur));
17609
+ scheduleWriteBehind(filePath);
17610
+ }
17611
+ }
17242
17612
  function flushPromptHistory() {
17243
17613
  for (const timer of pendingTimers.values()) clearTimeout(timer);
17244
17614
  pendingTimers.clear();
@@ -17251,6 +17621,43 @@ function flushPromptHistory() {
17251
17621
  pendingAppends.clear();
17252
17622
  }
17253
17623
  process.once("exit", flushPromptHistory);
17624
+ function buildMergedPromptHistory(sessionTexts, persistedTexts, limit = PROMPT_HISTORY_LIMIT2) {
17625
+ const seen = /* @__PURE__ */ new Set();
17626
+ const history = [];
17627
+ const push = (raw) => {
17628
+ if (history.length >= limit) return;
17629
+ const text = String(raw || "").trim();
17630
+ const key = promptHistoryKey2(text);
17631
+ if (!key || seen.has(key)) return;
17632
+ seen.add(key);
17633
+ history.push(text);
17634
+ };
17635
+ for (const text of sessionTexts) push(text);
17636
+ const persisted = Array.isArray(persistedTexts) ? persistedTexts : [];
17637
+ for (let i = persisted.length - 1; i >= 0; i -= 1) {
17638
+ push(persisted[i]);
17639
+ }
17640
+ return history;
17641
+ }
17642
+ function loadPromptHistory(cwd) {
17643
+ const filePath = historyFilePath(cwd);
17644
+ const entries = cachedEntries(filePath);
17645
+ return entries.length <= PROMPT_HISTORY_LIMIT2 ? entries.slice() : entries.slice(-PROMPT_HISTORY_LIMIT2);
17646
+ }
17647
+ function appendPromptHistory(cwd, value) {
17648
+ const key = promptHistoryKey2(value);
17649
+ if (!key) return null;
17650
+ const filePath = historyFilePath(cwd);
17651
+ if (!filePath) return null;
17652
+ const trimmed = applyAppend(cachedEntries(filePath), value);
17653
+ memCache.set(filePath, trimmed);
17654
+ const pend = pendingAppends.get(filePath) || [];
17655
+ pend.push(String(value).trim());
17656
+ pendingAppends.set(filePath, pend);
17657
+ if (pend.length >= FLUSH_CAP) void writeBehindFlush(filePath);
17658
+ else scheduleWriteBehind(filePath);
17659
+ return trimmed;
17660
+ }
17254
17661
 
17255
17662
  // src/tui/app/use-prompt-handlers.mjs
17256
17663
  function usePromptHandlers({
@@ -17290,57 +17697,64 @@ function usePromptHandlers({
17290
17697
  const handlePromptPaste = useCallback5((text, meta = {}) => {
17291
17698
  const source = String(meta?.source || "paste");
17292
17699
  const value = String(text ?? "");
17293
- if (source === "clipboard-image-shortcut" && !value) {
17294
- return readClipboardImageAttachment().then((image) => {
17295
- if (!image) {
17296
- showPromptHint("no image found on clipboard", "plain");
17297
- return false;
17700
+ const processText = (raw, returnRaw = false) => {
17701
+ const chunks = splitPastedImagePathCandidates(raw);
17702
+ const hasImagePath = chunks.some((chunk) => chunk.imagePath);
17703
+ if (!hasImagePath) {
17704
+ if (shouldFoldPastedText(raw)) return registerPastedText(raw);
17705
+ return returnRaw ? raw : void 0;
17706
+ }
17707
+ return Promise.all(chunks.map(async (chunk) => {
17708
+ if (!chunk.imagePath) return chunk.text;
17709
+ try {
17710
+ const image = await readImageAttachmentFromPath(chunk.text, state.cwd || process.cwd());
17711
+ if (!image) return chunk.text;
17712
+ const ref = registerPastedImage(image);
17713
+ showPromptHint(`attached ${image.filename || "image"}`, "plain");
17714
+ return ref;
17715
+ } catch (e) {
17716
+ showPromptHint(`image attach failed: ${e?.message || e}`, "warn");
17717
+ return chunk.text;
17718
+ }
17719
+ })).then((parts) => {
17720
+ let out = "";
17721
+ let run = "";
17722
+ const flushRun = () => {
17723
+ if (!run) return;
17724
+ out += shouldFoldPastedText(run) ? registerPastedText(run) : run;
17725
+ run = "";
17726
+ };
17727
+ for (let i = 0; i < chunks.length; i += 1) {
17728
+ if (chunks[i].imagePath) {
17729
+ flushRun();
17730
+ out += parts[i];
17731
+ } else {
17732
+ run += parts[i];
17733
+ }
17298
17734
  }
17299
- const ref = registerPastedImage(image);
17300
- showPromptHint(`attached ${image.filename || "clipboard image"}`, "plain");
17301
- return ref;
17735
+ flushRun();
17736
+ return out;
17737
+ });
17738
+ };
17739
+ if (source === "clipboard-shortcut" && !value) {
17740
+ return readClipboardText().then((clip) => {
17741
+ const normalized = String(clip ?? "").replace(/\r\n?/g, "\n");
17742
+ if (normalized) return processText(normalized, true);
17743
+ return readClipboardImageAttachment().then((image) => {
17744
+ if (!image) {
17745
+ showPromptHint("no text or image found on clipboard", "plain");
17746
+ return false;
17747
+ }
17748
+ const ref = registerPastedImage(image);
17749
+ showPromptHint(`attached ${image.filename || "clipboard image"}`, "plain");
17750
+ return ref;
17751
+ });
17302
17752
  }).catch((e) => {
17303
- showPromptHint(`image paste failed: ${e?.message || e}`, "warn");
17753
+ showPromptHint(`paste failed: ${e?.message || e}`, "warn");
17304
17754
  return false;
17305
17755
  });
17306
17756
  }
17307
- const chunks = splitPastedImagePathCandidates(value);
17308
- const hasImagePath = chunks.some((chunk) => chunk.imagePath);
17309
- if (!hasImagePath) {
17310
- if (shouldFoldPastedText(value)) return registerPastedText(value);
17311
- return void 0;
17312
- }
17313
- return Promise.all(chunks.map(async (chunk) => {
17314
- if (!chunk.imagePath) return chunk.text;
17315
- try {
17316
- const image = await readImageAttachmentFromPath(chunk.text, state.cwd || process.cwd());
17317
- if (!image) return chunk.text;
17318
- const ref = registerPastedImage(image);
17319
- showPromptHint(`attached ${image.filename || "image"}`, "plain");
17320
- return ref;
17321
- } catch (e) {
17322
- showPromptHint(`image attach failed: ${e?.message || e}`, "warn");
17323
- return chunk.text;
17324
- }
17325
- })).then((parts) => {
17326
- let out = "";
17327
- let run = "";
17328
- const flushRun = () => {
17329
- if (!run) return;
17330
- out += shouldFoldPastedText(run) ? registerPastedText(run) : run;
17331
- run = "";
17332
- };
17333
- for (let i = 0; i < chunks.length; i += 1) {
17334
- if (chunks[i].imagePath) {
17335
- flushRun();
17336
- out += parts[i];
17337
- } else {
17338
- run += parts[i];
17339
- }
17340
- }
17341
- flushRun();
17342
- return out;
17343
- });
17757
+ return processText(value);
17344
17758
  }, [registerPastedImage, registerPastedText, showPromptHint, state.cwd]);
17345
17759
  const handlePromptHistoryNavigate = useCallback5((direction, currentText = "", meta = {}) => {
17346
17760
  const currentValue = String(currentText || "");
@@ -17689,7 +18103,7 @@ var AssistantMessage = React14.memo(function AssistantMessage2({
17689
18103
  });
17690
18104
  var UserMessage = React14.memo(function UserMessage2({ text, attached = false, columns, themeEpoch = 0 }) {
17691
18105
  const bandColumns = Math.max(1, columns - 1);
17692
- return /* @__PURE__ */ jsx14(Box12, { flexDirection: "column", width: bandColumns, marginTop: attached ? 0 : 1, backgroundColor: theme.userMessageBackground, paddingLeft: 2, paddingRight: 1, children: /* @__PURE__ */ jsx14(Text14, { color: theme.text, wrap: "wrap", children: text }) });
18106
+ return /* @__PURE__ */ jsx14(Box12, { flexDirection: "column", width: bandColumns, marginTop: attached ? 0 : 1, backgroundColor: theme.userMessageBackground, paddingLeft: 2, paddingRight: 1, children: /* @__PURE__ */ jsx14(Text14, { color: theme.mixdogIvory, wrap: "wrap", children: text }) });
17693
18107
  });
17694
18108
  function NoticeMessage({ text, tone, columns = 80 }) {
17695
18109
  const accentColor = tone === "error" ? theme.error : tone === "warn" ? theme.warning : theme.inactive;
@@ -18180,7 +18594,7 @@ var TOOL_PENDING_SHOW_DELAY_MS = 1e3;
18180
18594
  function statusCopy(name, label, count, doneCount, pending, isError, args = {}) {
18181
18595
  return formatToolActionHeader(name, args, { pending, count });
18182
18596
  }
18183
- function ToolExecution({ name, args, result, rawResult, isError, errorCount, expanded, columns = 80, attached = false, count = 1, completedCount = 0, startedAt = 0, completedAt = 0, aggregate = false, categories = {}, headerFinalized = true, deferredDisplayReady = false }) {
18597
+ function ToolExecution({ name, args, result, rawResult, isError, errorCount, expanded, columns = 80, attached = false, count = 1, completedCount = 0, startedAt = 0, completedAt = 0, aggregate = false, categories = {}, doneCategories = null, headerFinalized = true, deferredDisplayReady = false }) {
18184
18598
  const rowWidth = Math.max(1, Number(columns || 80));
18185
18599
  const [blinkOn, setBlinkOn] = useState7(true);
18186
18600
  const [blinkExpired, setBlinkExpired] = useState7(false);
@@ -18204,6 +18618,11 @@ function ToolExecution({ name, args, result, rawResult, isError, errorCount, exp
18204
18618
  const failedCount = clampFailureCount(errorCount, groupCount, isError);
18205
18619
  const displayGroupCount = groupCount;
18206
18620
  const displayCategories = normalizeCountMap(categories || {});
18621
+ const normalizedDoneCategories = doneCategories ? normalizeCountMap(doneCategories) : displayCategories;
18622
+ const hasDoneCounts = Object.values(normalizedDoneCategories || {}).some(
18623
+ (v) => (v && typeof v === "object" ? Number(v.count || 0) : Number(v || 0)) > 0
18624
+ );
18625
+ const displayDoneCategories = hasDoneCounts ? normalizedDoneCategories : displayCategories;
18207
18626
  useEffect9(() => {
18208
18627
  if (!pending) {
18209
18628
  setPendingDelayElapsed(false);
@@ -18261,7 +18680,7 @@ function ToolExecution({ name, args, result, rawResult, isError, errorCount, exp
18261
18680
  }
18262
18681
  if (aggregate) {
18263
18682
  const headerOrder = Array.isArray(args?.categoryOrder) ? args.categoryOrder : null;
18264
- const headerText = safeInlineText(formatAggregateHeader(displayCategories || {}, { pending: headerPending, order: headerOrder }));
18683
+ const headerText = safeInlineText(formatAggregateHeader((headerPending ? displayCategories : displayDoneCategories) || {}, { pending: headerPending, order: headerOrder }));
18265
18684
  let detailText;
18266
18685
  if (hasResult) {
18267
18686
  detailText = safeInlineText(rt);
@@ -18378,13 +18797,13 @@ function ToolExecution({ name, args, result, rawResult, isError, errorCount, exp
18378
18797
  else if (isShellSurface) labelText = shellHeader(shellStatus, displayGroupCount);
18379
18798
  else labelText = (isAgentTool(normalizedName) ? agentActionTitle(parsedArgs) : "") || statusCopy(name, label, displayGroupCount, doneCount, headerPending, isError, parsedArgs);
18380
18799
  labelText = safeInlineText(labelText);
18381
- const toolSearchSummary = !pending && normalizedName === "tool_search" && hasResult ? toolSearchLoadedSummary(displayedResultText) : "";
18800
+ const toolSearchSummary = !pending && normalizedName === "load_tool" && hasResult ? toolSearchLoadedSummary(displayedResultText) : "";
18382
18801
  const rawSummaryText = safeInlineText(isAgentResponse || isBackgroundResponse ? "" : toolSearchSummary || (isAgentTool(normalizedName) ? agentActionSummary(parsedArgs, summary) : summary));
18383
18802
  const summaryIsHeaderCount = rawSummaryText && /^\d+\s+\S+$/.test(rawSummaryText) && labelText.endsWith(rawSummaryText);
18384
18803
  const summaryText = summaryIsHeaderCount ? "" : rawSummaryText;
18385
18804
  const agentHasExpandableBody = isAgentSurfaceCard && !pending && hasResult && (isAgentResponse || totalLines > 1 || firstResultLineClipped);
18386
18805
  const shellHasExpandableBody = isShellSurface && !pending && hasDisplayResult && hasDisplayBody && (totalLines > 1 || firstResultLineClipped || Boolean(shellCollapsedSummary && shellCollapsedSummary !== firstResultLine));
18387
- const showHeaderExpandHint = (isShellSurface ? shellHasExpandableBody : isAgentSurfaceCard ? agentHasExpandableBody : hasHiddenDetail || backgroundMetadataExpandable) && normalizedName !== "tool_search";
18806
+ const showHeaderExpandHint = (isShellSurface ? shellHasExpandableBody : isAgentSurfaceCard ? agentHasExpandableBody : hasHiddenDetail || backgroundMetadataExpandable) && normalizedName !== "load_tool";
18388
18807
  const expandHintColor = theme.subtle;
18389
18808
  const gutter = 2;
18390
18809
  const hintLabel = showHeaderExpandHint ? `ctrl+o ${expanded ? "collapse" : "expand"}` : "";
@@ -18581,7 +19000,7 @@ var Item = React19.memo(function Item2({ item, prevKind, columns, toolOutputExpa
18581
19000
  node = /* @__PURE__ */ jsx19(ToolHookDenialCard, { item, columns });
18582
19001
  break;
18583
19002
  }
18584
- node = /* @__PURE__ */ jsx19(ToolExecution, { name: item.name, args: item.args, result: item.result, rawResult: item.rawResult, isError: item.isError, errorCount: item.errorCount, 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, headerFinalized: item.headerFinalized, deferredDisplayReady: item.deferredDisplayReady, themeEpoch });
19003
+ node = /* @__PURE__ */ jsx19(ToolExecution, { name: item.name, args: item.args, result: item.result, rawResult: item.rawResult, isError: item.isError, errorCount: item.errorCount, 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, themeEpoch });
18585
19004
  break;
18586
19005
  }
18587
19006
  case "notice":
@@ -18616,7 +19035,9 @@ var PANEL_LAYOUT_SIG = {
18616
19035
  TEXT: 5,
18617
19036
  // Prompt-wrap/meta row counts (trailing churn tokens, see token order note
18618
19037
  // below). PROMPT_META is the 2-row live-spinner band slot.
18619
- PROMPT_META: 9
19038
+ PROMPT_META: 9,
19039
+ // Queued steering band rows (full wrapped height, see queuedBandRows).
19040
+ QUEUED: 10
18620
19041
  };
18621
19042
  var PROJECT_TEXT_ENTRY_KINDS = /* @__PURE__ */ new Set(["project-new", "project-create-confirm", "project-rename"]);
18622
19043
  var CORE_MULTILINE_TEXT_ENTRY_KINDS = /* @__PURE__ */ new Set(["core-add", "core-edit"]);
@@ -18678,7 +19099,9 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
18678
19099
  const projectPickerRef = useRef9(null);
18679
19100
  const buildProjectPickerState = (opts) => projectPickerRef.current.buildProjectPickerState(opts);
18680
19101
  const [picker, setPickerState] = useState8(null);
19102
+ const livePickerRef = useRef9(null);
18681
19103
  const setPicker = useCallback6((next) => {
19104
+ livePickerRef.current = typeof next === "function" ? next(livePickerRef.current) : next;
18682
19105
  setPickerState((prev) => {
18683
19106
  const resolved = typeof next === "function" ? next(prev) : next;
18684
19107
  if (resolved && typeof resolved === "object" && pickerOpenedFromEnterRef.current) {
@@ -18689,9 +19112,13 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
18689
19112
  }
18690
19113
  return resolved.indexMode ? resolved : { ...resolved, indexMode: "always" };
18691
19114
  }
19115
+ if (resolved && typeof resolved === "object" && !resolved.indexMode && prev && typeof prev === "object" && prev.indexMode && prev._kind && prev._kind === resolved._kind) {
19116
+ return { ...resolved, indexMode: prev.indexMode };
19117
+ }
18692
19118
  return resolved;
18693
19119
  });
18694
19120
  }, []);
19121
+ livePickerRef.current = picker;
18695
19122
  const [contextPanel, setContextPanel] = useState8(null);
18696
19123
  const [usagePanel, setUsagePanel] = useState8(null);
18697
19124
  const usageRequestRef = useRef9(0);
@@ -18733,7 +19160,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
18733
19160
  } catch {
18734
19161
  }
18735
19162
  if (!onboardingOwnsScreen && state.items.length === 0) {
18736
- setPickerState(projectPicker.buildProjectPickerState({ initialEntry: true }));
19163
+ setPicker(projectPicker.buildProjectPickerState({ initialEntry: true }));
18737
19164
  }
18738
19165
  }
18739
19166
  const {
@@ -18804,6 +19231,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
18804
19231
  clean,
18805
19232
  copyToClipboard,
18806
19233
  setPicker,
19234
+ getPicker: () => livePickerRef.current,
18807
19235
  setProviderPrompt,
18808
19236
  setChannelPrompt,
18809
19237
  setHookPrompt,
@@ -19306,13 +19734,16 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
19306
19734
  const renderText = store.getRenderSelectionText?.();
19307
19735
  const remembered = selectionTextRef.current || "";
19308
19736
  let text = renderText == null ? remembered : remembered.length > renderText.length ? remembered : renderText;
19309
- const stitched = getStitchedSelectionText?.() || "";
19310
- if (stitched.length > text.length) text = stitched;
19737
+ const stitched = getStitchedSelectionText?.() || { text: "", complete: false };
19738
+ if (stitched.complete && stitched.text.length > text.length) text = stitched.text;
19311
19739
  if ((!text || !text.trim()) && attempt < 4) {
19312
19740
  setTimeout(() => copySelection(attempt + 1), attempt === 0 ? 0 : 24);
19313
19741
  return;
19314
19742
  }
19315
- if (!text || !text.trim()) return;
19743
+ if (!text || !text.trim()) {
19744
+ showSelectionCopyHint("nothing to copy \xB7 select text first", "error");
19745
+ return;
19746
+ }
19316
19747
  selectionTextRef.current = text;
19317
19748
  copyToClipboard(text).then(() => {
19318
19749
  const lines = text.split("\n").length;
@@ -19388,8 +19819,8 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
19388
19819
  let timer = null;
19389
19820
  Promise.race([
19390
19821
  Promise.resolve(store.dispose?.("cli-react-exit", { detach: true })),
19391
- new Promise((resolve6) => {
19392
- timer = setTimeout(resolve6, 350);
19822
+ new Promise((resolve5) => {
19823
+ timer = setTimeout(resolve5, 350);
19393
19824
  })
19394
19825
  ]).finally(() => {
19395
19826
  if (timer) clearTimeout(timer);
@@ -20391,12 +20822,12 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
20391
20822
  const cat = v && typeof v === "object" ? v.category : null;
20392
20823
  const c = Math.max(1, Number(v && typeof v === "object" ? v.count : 1) || 1);
20393
20824
  if (cat === "Explore") exploreHits += c;
20394
- else if (cat === "Search") searchHits += c;
20825
+ else if (cat === "Web Research") searchHits += c;
20395
20826
  }
20396
20827
  } else if (it.name) {
20397
20828
  const cat = classifyToolCategory(it.name, it.args || {});
20398
20829
  if (cat === "Explore") exploreHits = count;
20399
- else if (cat === "Search") searchHits = count;
20830
+ else if (cat === "Web Research") searchHits = count;
20400
20831
  }
20401
20832
  if (exploreHits > 0) {
20402
20833
  exploreCount += exploreHits;
@@ -20495,7 +20926,13 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
20495
20926
  const promptMetaRows = promptMetaVisible ? 2 : 0;
20496
20927
  const overlayHintRequested = !inputBoxHidden && !hasFloatingPanel && !liveSpinner && !!inputHint && !queuedVisible;
20497
20928
  const overlayHintRows = 0;
20498
- const queuedRows = queuedVisible ? state.queued.length : 0;
20929
+ const queuedFullRows = queuedVisible ? state.queued.reduce(
20930
+ (sum, item) => sum + queuedBandRows(String(item.displayText || item.text || ""), Math.max(1, frameColumns - 4)),
20931
+ 0
20932
+ ) : 0;
20933
+ const queuedRowBudget = Math.max(3, Math.floor(resizeState.rows / 3));
20934
+ const queuedCompact = queuedFullRows > queuedRowBudget;
20935
+ const queuedRows = queuedVisible ? queuedCompact ? state.queued.length : queuedFullRows : 0;
20499
20936
  const INPUT_BOX_ROWS = promptBoxRows + promptMetaRows + overlayHintRows;
20500
20937
  const baseReserve = WELCOME_ROWS + SCROLL_HINT_ROWS + LIVE_STATUS_ROWS + INPUT_BOX_ROWS + STATUSLINE_ROWS + queuedRows;
20501
20938
  const maxFloatingPanelRows = Math.max(0, resizeState.rows - baseReserve - 1);
@@ -20530,10 +20967,14 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
20530
20967
  const nextMetaRows = Number(String(panelLayoutSignature).split("|")[PANEL_LAYOUT_SIG.PROMPT_META]) || 0;
20531
20968
  const doneTailAppendedThisCommit = latestDoneAtTail && (latestTranscriptItem?.id ?? null) !== panelTransition.tailId;
20532
20969
  const spinnerMetaCollapseRows = doneTailAppendedThisCommit ? Math.max(0, prevMetaRows - nextMetaRows) : 0;
20970
+ const prevQueuedSigRows = Number(String(panelTransition.signature).split("|")[PANEL_LAYOUT_SIG.QUEUED]) || 0;
20971
+ const nextQueuedSigRows = Number(String(panelLayoutSignature).split("|")[PANEL_LAYOUT_SIG.QUEUED]) || 0;
20972
+ const userTailAppendedThisCommit = latestTranscriptItem?.kind === "user" && (latestTranscriptItem?.id ?? null) !== panelTransition.tailId;
20973
+ const queuedPromoteCollapseRows = userTailAppendedThisCommit ? Math.max(0, prevQueuedSigRows - nextQueuedSigRows) : 0;
20533
20974
  const instantPanelClose = panelShrinkRows > 0 && (promptRowsOnlyChange || isInstantPanelCloseTransition(panelTransition.signature, panelLayoutSignature, initialProjectEntryClose));
20534
20975
  const slashOpenOnEmptyTranscript = initialProjectEntryClose && !panelSignatureFlags(panelTransition.signature).slash && panelSignatureFlags(panelLayoutSignature).slash;
20535
20976
  if (instantPanelClose) {
20536
- panelCloseInkMaskRowsRef.current = Math.max(0, panelShrinkRows - spinnerMetaCollapseRows);
20977
+ panelCloseInkMaskRowsRef.current = Math.max(0, panelShrinkRows - spinnerMetaCollapseRows - queuedPromoteCollapseRows);
20537
20978
  panelTransition.clearRows = 0;
20538
20979
  panelTransition.guardRows = 0;
20539
20980
  panelTransition.epoch = panelTransitionEpoch;
@@ -20562,8 +21003,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
20562
21003
  const viewportHeight = Math.max(1, resizeState.rows - bottomReserve);
20563
21004
  const guardCapacityRows = Math.max(0, viewportHeight - 1);
20564
21005
  const baseGuardRows = guardCapacityRows > 0 ? 1 : 0;
20565
- const scrollGuardNearBottom = followingRef.current || scrollTargetRef.current <= baseGuardRows;
20566
- const scrollGuardRows = !scrollGuardNearBottom && scrollOffset > 0 && guardCapacityRows > baseGuardRows ? 1 : 0;
21006
+ const scrollGuardRows = 0;
20567
21007
  const transcriptGuardRows = Math.min(guardCapacityRows, baseGuardRows + panelTransitionGuardRows + scrollGuardRows);
20568
21008
  const welcomePromptHintText = conditionalWelcomePromptHint || welcomePromptHintRef.current || "";
20569
21009
  const welcomePromptHintVisible = Boolean(
@@ -20571,13 +21011,14 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
20571
21011
  );
20572
21012
  const welcomePromptHintRows = welcomePromptHintVisible && viewportHeight - transcriptGuardRows >= 2 ? 1 : 0;
20573
21013
  welcomePromptHintVisibleRef.current = welcomePromptHintRows > 0;
21014
+ const overlayHintBandRows = overlayHintRequested && state.items.length === 0 && viewportHeight - transcriptGuardRows - welcomePromptHintRows >= 2 ? 1 : 0;
20574
21015
  const panelCloseMaskRows = Math.min(
20575
21016
  panelCloseInkMaskRows,
20576
- Math.max(0, viewportHeight - transcriptGuardRows - welcomePromptHintRows - 1)
21017
+ Math.max(0, viewportHeight - transcriptGuardRows - welcomePromptHintRows - overlayHintBandRows - 1)
20577
21018
  );
20578
21019
  const transcriptContentHeight = Math.max(
20579
21020
  1,
20580
- viewportHeight - transcriptGuardRows - panelCloseMaskRows - welcomePromptHintRows
21021
+ viewportHeight - transcriptGuardRows - panelCloseMaskRows - welcomePromptHintRows - overlayHintBandRows
20581
21022
  );
20582
21023
  const transcriptBottomSlackRows = Math.max(0, baseGuardRows);
20583
21024
  transcriptBottomSlackRowsRef.current = transcriptBottomSlackRows;
@@ -20799,9 +21240,13 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
20799
21240
  backgroundColor: surfaceBackground()
20800
21241
  }
20801
21242
  ) : null,
21243
+ overlayHintBandRows > 0 ? /* @__PURE__ */ jsxs16(Box18, { height: 1, flexShrink: 0, backgroundColor: surfaceBackground(), flexDirection: "row", width: "100%", overflow: "hidden", children: [
21244
+ /* @__PURE__ */ jsx20(Box18, { flexGrow: 1, flexShrink: 1, overflow: "hidden" }),
21245
+ /* @__PURE__ */ jsx20(Box18, { flexShrink: 0, width: guardHintWidth || 1, marginLeft: 1, marginRight: 1, justifyContent: "flex-end", overflow: "hidden", children: /* @__PURE__ */ jsx20(Text20, { color: promptStatusColor(inputHintTone), wrap: "truncate", children: inputHint }) })
21246
+ ] }) : null,
20802
21247
  transcriptGuardRows > 0 ? /* @__PURE__ */ jsxs16(Box18, { height: transcriptGuardRows, flexShrink: 0, backgroundColor: surfaceBackground(), flexDirection: "row", width: "100%", overflow: "hidden", children: [
20803
21248
  /* @__PURE__ */ jsx20(Box18, { flexGrow: 1, flexShrink: 1, overflow: "hidden" }),
20804
- overlayHintFallbackRow ? /* @__PURE__ */ jsx20(Box18, { flexShrink: 0, width: guardHintWidth || 1, marginLeft: 1, marginRight: 1, justifyContent: "flex-end", overflow: "hidden", children: /* @__PURE__ */ jsx20(Text20, { color: promptStatusColor(inputHintTone), wrap: "truncate", children: inputHint }) }) : null
21249
+ overlayHintFallbackRow && overlayHintBandRows === 0 ? /* @__PURE__ */ jsx20(Box18, { flexShrink: 0, width: guardHintWidth || 1, marginLeft: 1, marginRight: 1, justifyContent: "flex-end", overflow: "hidden", children: /* @__PURE__ */ jsx20(Text20, { color: promptStatusColor(inputHintTone), wrap: "truncate", children: inputHint }) }) : null
20805
21250
  ] }) : null
20806
21251
  ]
20807
21252
  }
@@ -21000,7 +21445,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
21000
21445
  ),
21001
21446
  /* @__PURE__ */ jsx20(Box18, { height: 1, width: "100%", backgroundColor: surfaceBackground() })
21002
21447
  ] }) : null,
21003
- queuedVisible ? /* @__PURE__ */ jsx20(QueuedCommands, { queued: state.queued, columns: frameColumns }) : null,
21448
+ queuedVisible ? /* @__PURE__ */ jsx20(QueuedCommands, { queued: state.queued, columns: frameColumns, compact: queuedCompact }) : null,
21004
21449
  /* @__PURE__ */ jsx20(
21005
21450
  Box18,
21006
21451
  {
@@ -21188,6 +21633,13 @@ function modelVisibleToolCompletionMessage(text, meta = {}) {
21188
21633
  ].join("\n");
21189
21634
  }
21190
21635
 
21636
+ // src/session-runtime/session-text.mjs
21637
+ var LATE_TOOL_ANNOUNCEMENT_SENTINEL = "connected after this session started";
21638
+ function isLateToolAnnouncement(text) {
21639
+ const value = String(text || "");
21640
+ return value.includes(LATE_TOOL_ANNOUNCEMENT_SENTINEL) && /<available-deferred-tools>/i.test(value);
21641
+ }
21642
+
21191
21643
  // src/tui/engine/boot-profile.mjs
21192
21644
  import { performance } from "node:perf_hooks";
21193
21645
  var BOOT_PROFILE_ENABLED = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_BOOT_PROFILE || ""));
@@ -21957,6 +22409,9 @@ function resolveTuiRuntimeNotificationDelivery(event, text) {
21957
22409
  if (!trimmed) return { action: "ignore" };
21958
22410
  const parsed = parseAgentJob(trimmed);
21959
22411
  const meta = event?.meta && typeof event.meta === "object" ? event.meta : {};
22412
+ if (meta.kind === "update-notice") {
22413
+ return { action: "notice", displayText: trimmed, tone: meta.tone === "warn" ? "warn" : "info" };
22414
+ }
21960
22415
  if (!isExecutionNotification(event, trimmed, parsed)) {
21961
22416
  return { action: "enqueue", displayText: trimmed, modelContent: trimmed };
21962
22417
  }
@@ -21973,8 +22428,8 @@ function resolveTuiRuntimeNotificationDelivery(event, text) {
21973
22428
 
21974
22429
  // src/tui/engine/render-timing.mjs
21975
22430
  var RENDER_THROTTLE_FLUSH_MS = 12;
21976
- var yieldToRenderer = () => new Promise((resolve6) => {
21977
- setTimeout(resolve6, RENDER_THROTTLE_FLUSH_MS);
22431
+ var yieldToRenderer = () => new Promise((resolve5) => {
22432
+ setTimeout(resolve5, RENDER_THROTTLE_FLUSH_MS);
21978
22433
  });
21979
22434
 
21980
22435
  // src/tui/engine/tool-result-status.mjs
@@ -22152,9 +22607,9 @@ function createToolApproval({ getState, set, nextId: nextId2, getDisposed, timeo
22152
22607
  }
22153
22608
  function requestToolApproval(input = {}) {
22154
22609
  if (getDisposed()) return Promise.resolve({ approved: false, reason: "runtime disposed" });
22155
- return new Promise((resolve6) => {
22610
+ return new Promise((resolve5) => {
22156
22611
  const id = nextId2();
22157
- toolApprovalQueue.push({ id, request: normalizeToolApprovalRequest(input, id), resolve: resolve6, timer: null });
22612
+ toolApprovalQueue.push({ id, request: normalizeToolApprovalRequest(input, id), resolve: resolve5, timer: null });
22158
22613
  presentNextToolApproval();
22159
22614
  });
22160
22615
  }
@@ -22190,11 +22645,12 @@ function createToolCardResults({
22190
22645
  markToolCallDone(card.callId);
22191
22646
  (card.aggregate?.ensureVisible || card.ensureVisible)?.();
22192
22647
  const rawText = toolResultText(message?.content);
22193
- const isError = message?.isError === true || message?.toolKind === "error" || /^\s*\[?error/i.test(rawText) || isErrorToolStatus(toolResultStatus(rawText));
22194
- const text = isError ? toolErrorDisplay(rawText, card?.name || "tool") : rawText;
22195
22648
  const aggregate = card.aggregate;
22649
+ const callRec = aggregate && callId ? aggregate.calls.get(callId) : null;
22650
+ const isMemoryCall = classifyToolCategory(callRec?.name || card?.name || "", callRec?.args || {}) === "Memory";
22651
+ const isError = message?.isError === true || message?.toolKind === "error" || /^\s*\[?error/i.test(rawText) || isErrorToolStatus(toolResultStatus(rawText)) || isMemoryCall && memoryCoreResultErrorText(rawText) != null;
22652
+ const text = isError ? toolErrorDisplay(rawText, card?.name || "tool") : rawText;
22196
22653
  if (aggregate && card.itemId === aggregate.itemId) {
22197
- const callRec = callId ? aggregate.calls.get(callId) : null;
22198
22654
  if (!callRec) return false;
22199
22655
  if (callRec.resolved) {
22200
22656
  card.done = true;
@@ -22224,6 +22680,7 @@ function createToolCardResults({
22224
22680
  errorCount: errors,
22225
22681
  count: allCalls.length,
22226
22682
  completedCount: visualCompleted,
22683
+ doneCategories: aggregateDoneCategories(allCalls),
22227
22684
  completedAt: Number(currentItem?.completedAt) || Date.now()
22228
22685
  });
22229
22686
  card.done = true;
@@ -22326,6 +22783,7 @@ function createToolCardResults({
22326
22783
  errorCount: errors,
22327
22784
  count: allCalls.length,
22328
22785
  completedCount: totalCompleted,
22786
+ doneCategories: aggregateDoneCategories(allCalls),
22329
22787
  completedAt: Date.now()
22330
22788
  });
22331
22789
  for (const sibling of toolCards || []) {
@@ -22365,7 +22823,8 @@ function createAgentJobFeed({
22365
22823
  makeQueueEntry,
22366
22824
  getPending,
22367
22825
  agentStatusState,
22368
- displayedExecutionNotificationKeys
22826
+ displayedExecutionNotificationKeys,
22827
+ pushNotice
22369
22828
  }) {
22370
22829
  let executionResumeKickDeferred = false;
22371
22830
  function kickExecutionPendingResume() {
@@ -22413,6 +22872,10 @@ function createAgentJobFeed({
22413
22872
  const notificationKey = notificationQueueKey(event, text, parsed);
22414
22873
  const delivery = resolveTuiRuntimeNotificationDelivery(event, text);
22415
22874
  if (delivery.action === "ignore") return;
22875
+ if (delivery.action === "notice") {
22876
+ pushNotice?.(delivery.displayText, delivery.tone || "info", { transcript: true });
22877
+ return true;
22878
+ }
22416
22879
  if (delivery.action === "status-only") {
22417
22880
  if (parsed?.taskId) set(agentStatusState({ force: true }));
22418
22881
  return true;
@@ -23016,7 +23479,7 @@ function createSessionFlow(bag) {
23016
23479
  const startedAt = Date.now();
23017
23480
  set({ commandBusy: true, commandStatus: { active: true, verb, startedAt, mode: "auto-clear" } });
23018
23481
  try {
23019
- await new Promise((resolve6) => setTimeout(resolve6, 0));
23482
+ await new Promise((resolve5) => setTimeout(resolve5, 0));
23020
23483
  let compactType = null;
23021
23484
  if (useCompaction) {
23022
23485
  const compaction = runtime.getCompactionSettings();
@@ -23473,6 +23936,7 @@ function createRunTurn(bag) {
23473
23936
  isError: errors > 0,
23474
23937
  count: allCalls.length,
23475
23938
  completedCount: allCalls.length,
23939
+ doneCategories: aggregateDoneCategories(allCalls),
23476
23940
  completedAt: Date.now()
23477
23941
  });
23478
23942
  }
@@ -23670,7 +24134,8 @@ function createRunTurn(bag) {
23670
24134
  if (!callRec || callRec.resolved || callRec.completedEarly) return;
23671
24135
  aggregate.ensureVisible?.();
23672
24136
  const rawText = toolResultText(message?.content);
23673
- const isError = message?.isError === true || message?.toolKind === "error" || /^\s*\[?error/i.test(rawText) || isErrorToolStatus(toolResultStatus(rawText));
24137
+ const isMemoryCall = classifyToolCategory(callRec.name, callRec.args) === "Memory";
24138
+ const isError = message?.isError === true || message?.toolKind === "error" || /^\s*\[?error/i.test(rawText) || isErrorToolStatus(toolResultStatus(rawText)) || isMemoryCall && memoryCoreResultErrorText(rawText) != null;
23674
24139
  const text = isError ? toolErrorDisplay(rawText, callRec.name || "tool") : rawText;
23675
24140
  callRec.summary = !isError ? summarizeToolResult(callRec.name, callRec.args, rawText, isError) : null;
23676
24141
  assignAggregateSummaryOrder(aggregate, callRec);
@@ -24529,6 +24994,26 @@ import { readFileSync as readFileSync6 } from "node:fs";
24529
24994
  import { dirname as dirname8, join as join9 } from "node:path";
24530
24995
  import { fileURLToPath as fileURLToPath3 } from "node:url";
24531
24996
  var GLYPH = { ok: "\u2713", warn: "\u26A0", fail: "\u2717" };
24997
+ var CKPT_WRITE_WARN_S = 10;
24998
+ var CKPT_WRITE_MEDIAN_S = 5;
24999
+ function parseCheckpointWriteSeconds(logText) {
25000
+ const lines = String(logText).split(/\r?\n/).slice(-200);
25001
+ const out = [];
25002
+ for (const line of lines) {
25003
+ const m = /checkpoint complete:.*?\bwrite=([\d.]+)\s*s/i.exec(line);
25004
+ if (m) {
25005
+ const v = Number(m[1]);
25006
+ if (Number.isFinite(v)) out.push(v);
25007
+ }
25008
+ }
25009
+ return out;
25010
+ }
25011
+ function median(nums) {
25012
+ if (!nums.length) return 0;
25013
+ const s = [...nums].sort((a, b) => a - b);
25014
+ const mid = s.length >> 1;
25015
+ return s.length % 2 ? s[mid] : (s[mid - 1] + s[mid]) / 2;
25016
+ }
24532
25017
  function readPackageJson() {
24533
25018
  try {
24534
25019
  const dir = dirname8(fileURLToPath3(import.meta.url));
@@ -24662,6 +25147,31 @@ async function buildDoctorReport(runtime = {}, getState = () => ({})) {
24662
25147
  const enabled = h.enabled === true;
24663
25148
  row("ok", `${enabled ? "enabled" : "disabled"} \xB7 ${events.length} event${events.length === 1 ? "" : "s"}`);
24664
25149
  });
25150
+ if (process.platform === "win32") {
25151
+ await check("defender", async (row) => {
25152
+ const dataDir = resolvePluginData();
25153
+ const pgdata = join9(dataDir, "pgdata");
25154
+ let writes;
25155
+ try {
25156
+ writes = parseCheckpointWriteSeconds(readFileSync6(join9(dataDir, "pg.log"), "utf8"));
25157
+ } catch {
25158
+ row("ok", "pg.log unavailable \xB7 check skipped");
25159
+ return;
25160
+ }
25161
+ if (!writes.length) {
25162
+ row("ok", "no checkpoint timings yet");
25163
+ return;
25164
+ }
25165
+ const max = Math.max(...writes);
25166
+ const med = median(writes);
25167
+ if (max <= CKPT_WRITE_WARN_S && med <= CKPT_WRITE_MEDIAN_S) {
25168
+ row("ok", `checkpoint write median ${med.toFixed(1)}s \xB7 max ${max.toFixed(1)}s`);
25169
+ return;
25170
+ }
25171
+ const fix = `Start-Process powershell -Verb RunAs -ArgumentList '-NoProfile','-Command',"Add-MpPreference -ExclusionPath '${pgdata}'"`;
25172
+ row("warn", `slow checkpoints (median ${med.toFixed(1)}s \xB7 max ${max.toFixed(1)}s) suggest Defender real-time scan of ${pgdata}. Fix (run in PowerShell): ${fix}`);
25173
+ });
25174
+ }
24665
25175
  return ["mixdog doctor \u2014 installation health", ...rows].join("\n");
24666
25176
  }
24667
25177
 
@@ -24887,7 +25397,8 @@ function createEngineApiA(bag) {
24887
25397
  },
24888
25398
  setCwd: (path2, options = {}) => {
24889
25399
  const next = runtime.setCwd(path2);
24890
- set({ cwd: next });
25400
+ const sessionList = recomputePromptHistory(getState().items);
25401
+ set({ cwd: next, promptHistoryList: buildMergedPromptHistory(sessionList, loadPromptHistory(next)) });
24891
25402
  if (options?.notice !== false) {
24892
25403
  pushNotice(options?.message || `Project set: ${projectNameFromPath2(next)}`, "info");
24893
25404
  }
@@ -24948,17 +25459,18 @@ function createEngineApiA(bag) {
24948
25459
  }
24949
25460
  },
24950
25461
  setMcpServerEnabled: async (name, enabled) => {
24951
- if (getState().commandBusy) return null;
24952
- set({ commandBusy: true });
24953
- try {
24954
- const status = await runtime.setMcpServerEnabled?.(name, enabled);
25462
+ const status = await runtime.setMcpServerEnabled?.(name, enabled);
25463
+ setImmediate(() => {
24955
25464
  resetStatsAndSyncContext();
24956
25465
  set({ ...routeState(), stats: { ...getState().stats } });
25466
+ });
25467
+ const row = status?.servers?.find((s) => s.name === name);
25468
+ if (enabled && row && (row.status === "failed" || row.error)) {
25469
+ pushNotice(`mcp enable failed: ${name}${row.error ? ` \u2014 ${row.error}` : ""}`, "error");
25470
+ } else {
24957
25471
  pushNotice(`mcp ${enabled ? "enabled" : "disabled"}: ${name}`, "info");
24958
- return status;
24959
- } finally {
24960
- set({ commandBusy: false });
24961
25472
  }
25473
+ return status;
24962
25474
  },
24963
25475
  getDisabledSkills: () => runtime.getDisabledSkills?.() || { disabled: [] },
24964
25476
  setDisabledSkills: (disabled) => runtime.setDisabledSkills?.(disabled) || { disabled: [] },
@@ -25112,7 +25624,7 @@ function createEngineApiA(bag) {
25112
25624
  const startedAt = Date.now();
25113
25625
  set({ commandBusy: true, commandStatus: { active: true, verb: "Running diagnostics", startedAt, mode: "doctor" } });
25114
25626
  try {
25115
- await new Promise((resolve6) => setTimeout(resolve6, 0));
25627
+ await new Promise((resolve5) => setTimeout(resolve5, 0));
25116
25628
  const report = await buildDoctorReport(runtime, getState);
25117
25629
  pushNotice(report, "info");
25118
25630
  return report;
@@ -25132,7 +25644,7 @@ function createEngineApiA(bag) {
25132
25644
  const startedAt = Date.now();
25133
25645
  set({ commandBusy: true, commandStatus: { active: true, verb: "Compacting conversation", startedAt, mode: "compacting" } });
25134
25646
  try {
25135
- await new Promise((resolve6) => setTimeout(resolve6, 0));
25647
+ await new Promise((resolve5) => setTimeout(resolve5, 0));
25136
25648
  const result = await runtime.compact({ recoverAgent: true });
25137
25649
  syncContextStats({ allowEstimated: true });
25138
25650
  set({ ...routeState(), stats: { ...getState().stats } });
@@ -25297,7 +25809,9 @@ async function createEngineSession({
25297
25809
  // - promptHistoryList: newest-first deduped user-prompt history, rebuilt
25298
25810
  // only when a user item is appended (replaces the per-change rescan).
25299
25811
  activeToolSummary: null,
25300
- promptHistoryList: [],
25812
+ // Seed from the persisted cwd-scoped store so up-arrow history is available
25813
+ // on a fresh start, before any bulk swap / first submit republishes it.
25814
+ promptHistoryList: buildMergedPromptHistory([], loadPromptHistory(cwd)),
25301
25815
  ...baseRouteState(),
25302
25816
  displayContextWindow: 0,
25303
25817
  compactBoundaryTokens: 0,
@@ -25346,7 +25860,12 @@ async function createEngineSession({
25346
25860
  if (id != null) itemIndexById.set(id, i);
25347
25861
  }
25348
25862
  activeToolCalls.clear();
25349
- state = { ...state, items: nextItems, promptHistoryList: recomputePromptHistory(nextItems), activeToolSummary: null };
25863
+ state = {
25864
+ ...state,
25865
+ items: nextItems,
25866
+ promptHistoryList: buildMergedPromptHistory(recomputePromptHistory(nextItems), loadPromptHistory(state.cwd)),
25867
+ activeToolSummary: null
25868
+ };
25350
25869
  return nextItems;
25351
25870
  };
25352
25871
  const activeToolCalls = /* @__PURE__ */ new Map();
@@ -25359,7 +25878,7 @@ async function createEngineSession({
25359
25878
  if (rec.category === "Explore") {
25360
25879
  exploreCount += c;
25361
25880
  if (started > 0 && (exploreStart === 0 || started < exploreStart)) exploreStart = started;
25362
- } else if (rec.category === "Search") {
25881
+ } else if (rec.category === "Web Research") {
25363
25882
  searchCount += c;
25364
25883
  if (started > 0 && (searchStart === 0 || started < searchStart)) searchStart = started;
25365
25884
  }
@@ -25369,7 +25888,7 @@ async function createEngineSession({
25369
25888
  if (next !== prev) set({ activeToolSummary: next || null });
25370
25889
  };
25371
25890
  const markToolCallActive = (callKey, category, count, startedAt2) => {
25372
- if (!callKey || category !== "Explore" && category !== "Search") return;
25891
+ if (!callKey || category !== "Explore" && category !== "Web Research") return;
25373
25892
  activeToolCalls.set(callKey, { category, count: Math.max(1, Number(count || 1)), startedAt: Number(startedAt2 || Date.now()) });
25374
25893
  recomputeActiveToolSummary();
25375
25894
  };
@@ -25391,7 +25910,7 @@ async function createEngineSession({
25391
25910
  const items = [...state.items, item];
25392
25911
  if (item?.id != null) itemIndexById.set(item.id, index);
25393
25912
  if (item?.kind === "user") {
25394
- const promptHistoryList = recomputePromptHistory(items);
25913
+ const promptHistoryList = buildMergedPromptHistory(recomputePromptHistory(items), loadPromptHistory(state.cwd));
25395
25914
  set({ items, promptHistoryList });
25396
25915
  } else {
25397
25916
  set({ items });
@@ -25426,7 +25945,9 @@ async function createEngineSession({
25426
25945
  const pushUserOrSyntheticItem = (text, id = nextId(), origin = "user") => {
25427
25946
  if (origin === "injected" && isLikelyToolCompletionWrapper(text)) return;
25428
25947
  if (isModelVisibleToolCompletionWrapper(text)) return;
25948
+ if (isLateToolAnnouncement(text)) return;
25429
25949
  if (upsertSyntheticToolItem(text, id)) return;
25950
+ if (origin === "user") appendPromptHistory(state.cwd, text);
25430
25951
  pushItem({ kind: "user", id, text });
25431
25952
  };
25432
25953
  const pushToast = (text, tone = "info", ttlMs = 3e3) => {
@@ -25533,7 +26054,8 @@ async function createEngineSession({
25533
26054
  makeQueueEntry: (...args) => bag.makeQueueEntry(...args),
25534
26055
  getPending: () => pending,
25535
26056
  agentStatusState,
25536
- displayedExecutionNotificationKeys
26057
+ displayedExecutionNotificationKeys,
26058
+ pushNotice
25537
26059
  });
25538
26060
  lifecycle.unsubscribeRuntimeNotifications = subscribeRuntimeNotifications();
25539
26061
  if (typeof runtime.onRemoteStateChange === "function") {
@@ -25624,7 +26146,7 @@ function signalExitCode(signal, fallback = 1) {
25624
26146
  }
25625
26147
  function waitWithTimeout(promise, timeoutMs, label = "cleanup") {
25626
26148
  const ms = Math.max(1, Math.floor(Number(timeoutMs) || 1));
25627
- return new Promise((resolve6, reject) => {
26149
+ return new Promise((resolve5, reject) => {
25628
26150
  let settled = false;
25629
26151
  const timer = setTimeout(() => {
25630
26152
  if (settled) return;
@@ -25636,7 +26158,7 @@ function waitWithTimeout(promise, timeoutMs, label = "cleanup") {
25636
26158
  if (settled) return;
25637
26159
  settled = true;
25638
26160
  clearTimeout(timer);
25639
- resolve6(value);
26161
+ resolve5(value);
25640
26162
  }).catch((error) => {
25641
26163
  if (settled) return;
25642
26164
  settled = true;
@@ -25750,54 +26272,14 @@ function installProcessSignalCleanup({
25750
26272
  };
25751
26273
  }
25752
26274
 
25753
- // src/runtime/channels/lib/runtime-paths.mjs
25754
- import { tmpdir as tmpdir2 } from "os";
25755
- import { basename as basename4, join as join10, resolve as resolve5 } from "path";
25756
-
25757
- // src/runtime/channels/lib/state-file.mjs
25758
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync7, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5 } from "fs";
25759
- function ensureDir2(dirPath) {
25760
- mkdirSync6(dirPath, { recursive: true });
25761
- }
25762
-
25763
- // src/runtime/channels/lib/runtime-paths.mjs
25764
- var RUNTIME_ROOT = process.env.MIXDOG_RUNTIME_ROOT ? resolve5(process.env.MIXDOG_RUNTIME_ROOT) : join10(tmpdir2(), "mixdog");
25765
- var OWNER_DIR = join10(RUNTIME_ROOT, "owners");
25766
- var ACTIVE_INSTANCE_FILE = join10(RUNTIME_ROOT, "active-instance.json");
25767
- var RUNTIME_STALE_TTL = 24 * 60 * 60 * 1e3;
25768
- var STATUS_FILE_TTL = 6 * 60 * 60 * 1e3;
25769
- var TMP_FILE_TTL = 60 * 60 * 1e3;
25770
- function sanitize(value) {
25771
- return value.replace(/[^a-zA-Z0-9._-]/g, "_");
25772
- }
25773
- function ensureRuntimeDirs() {
25774
- ensureDir2(RUNTIME_ROOT);
25775
- ensureDir2(OWNER_DIR);
25776
- }
25777
- var UI_HEARTBEAT_STALE_MS = 5 * 60 * 1e3;
25778
- function touchUiHeartbeat(instanceId) {
25779
- ensureRuntimeDirs();
25780
- try {
25781
- updateJsonAtomicSync(ACTIVE_INSTANCE_FILE, (curRaw) => {
25782
- if (!curRaw) return void 0;
25783
- if (instanceId && curRaw.instanceId !== instanceId) return void 0;
25784
- return { ...curRaw, ui_heartbeat_at: Date.now(), updatedAt: Date.now() };
25785
- }, { compact: true, fsync: false, fsyncDir: false, renameFallback: "truncate", timeoutMs: 0 });
25786
- } catch {
25787
- }
25788
- }
25789
- var SERVER_PID_FILE = join10(
25790
- RUNTIME_ROOT,
25791
- `server-${sanitize(resolvePluginData() ?? "default")}.pid`
25792
- );
25793
-
25794
26275
  // src/tui/index.jsx
25795
26276
  var import_mixdog_debug = __toESM(require_mixdog_debug(), 1);
25796
26277
  import { jsx as jsx21 } from "react/jsx-runtime";
25797
- var TERMINAL_MODE_RESET = "\x1B[?1006l\x1B[?1005l\x1B[?1015l\x1B[?1003l\x1B[?1002l\x1B[?1000l\x1B[?2004l\x1B[?25h";
26278
+ var TERMINAL_MODE_RESET = "\x1B[?1006l\x1B[?1005l\x1B[?1015l\x1B[?1003l\x1B[?1002l\x1B[?1000l\x1B[?2004l\x1B[>0s\x1B[?25h";
25798
26279
  var TERMINAL_OSC_RESET_BG = "\x1B]111\x07";
25799
26280
  var TERMINAL_MODE_RESET_HIDDEN_CURSOR = TERMINAL_MODE_RESET.replace("\x1B[?25h", "\x1B[?25l");
25800
- var MOUSE_TRACKING_ON2 = "\x1B[?1000h\x1B[?1002h\x1B[?1006h";
26281
+ var XTSHIFTESCAPE_ON = process.env.WT_SESSION ? "" : "\x1B[>1s";
26282
+ var MOUSE_TRACKING_ON2 = `\x1B[?1000h\x1B[?1002h\x1B[?1006h${XTSHIFTESCAPE_ON}`;
25801
26283
  var BOOT_PROFILE_ENABLED2 = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_BOOT_PROFILE || ""));
25802
26284
  var BOOT_PROFILE_START2 = globalThis.__mixdogBootProfileStart || (globalThis.__mixdogBootProfileStart = performance3.now());
25803
26285
  var EXIT_WAIT_TIMEOUT_MS = positiveIntEnv2("MIXDOG_TUI_EXIT_WAIT_MS", 2500);
@@ -25986,8 +26468,8 @@ function waitWithTimeout2(promise, ms) {
25986
26468
  let timer = null;
25987
26469
  return Promise.race([
25988
26470
  Promise.resolve(promise).then(() => true),
25989
- new Promise((resolve6) => {
25990
- timer = setTimeout(() => resolve6(false), ms);
26471
+ new Promise((resolve5) => {
26472
+ timer = setTimeout(() => resolve5(false), ms);
25991
26473
  })
25992
26474
  ]).finally(() => {
25993
26475
  if (timer) clearTimeout(timer);
@@ -26006,7 +26488,7 @@ function scheduleHardExit(code = 0) {
26006
26488
  timer.unref?.();
26007
26489
  }
26008
26490
  function resolveTuiStderrLogPath() {
26009
- return process.env.MIXDOG_TUI_STDERR_LOG || join11(process.env.MIXDOG_RUNTIME_ROOT || join11(tmpdir3(), "mixdog"), "mixdog-tui.stderr.log");
26491
+ return process.env.MIXDOG_TUI_STDERR_LOG || join10(process.env.MIXDOG_RUNTIME_ROOT || join10(tmpdir2(), "mixdog"), "mixdog-tui.stderr.log");
26010
26492
  }
26011
26493
  function ansiFg(rgb) {
26012
26494
  const m = /rgb\((\d+),\s*(\d+),\s*(\d+)\)/.exec(String(rgb || ""));
@@ -26088,7 +26570,7 @@ function installTuiStderrGuard() {
26088
26570
  const originalWrite = process.stderr.write.bind(process.stderr);
26089
26571
  const logPath = resolveTuiStderrLogPath();
26090
26572
  try {
26091
- mkdirSync7(dirname9(logPath), { recursive: true });
26573
+ mkdirSync6(dirname9(logPath), { recursive: true });
26092
26574
  } catch {
26093
26575
  }
26094
26576
  (0, import_mixdog_debug.rotateBoundedLog)(logPath, import_mixdog_debug.PLUGIN_LOG_MAX_BYTES, import_mixdog_debug.PLUGIN_LOG_KEEP_BYTES);
@@ -26133,6 +26615,23 @@ function installTuiStderrGuard() {
26133
26615
  }
26134
26616
  };
26135
26617
  }
26618
+ function installTuiConsoleGuard() {
26619
+ const methods = ["log", "info", "warn", "error", "debug", "trace"];
26620
+ const original = /* @__PURE__ */ new Map();
26621
+ for (const m of methods) {
26622
+ original.set(m, console[m]);
26623
+ console[m] = (...args) => {
26624
+ try {
26625
+ process.stderr.write(`[console.${m}] ${format(...args)}
26626
+ `);
26627
+ } catch {
26628
+ }
26629
+ };
26630
+ }
26631
+ return () => {
26632
+ for (const [m, fn] of original) console[m] = fn;
26633
+ };
26634
+ }
26136
26635
  async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {}) {
26137
26636
  const startedAt = performance3.now();
26138
26637
  bootProfile2("run:start", { provider, model, toolMode, remote });
@@ -26143,6 +26642,7 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
26143
26642
  return 1;
26144
26643
  }
26145
26644
  const restoreStderr = installTuiStderrGuard();
26645
+ const restoreConsole = installTuiConsoleGuard();
26146
26646
  const stopPerfProbe = installTuiPerfProbe();
26147
26647
  const stopLoopProbe = installTuiLoopProbe();
26148
26648
  const restorePrimedInput = () => drainStdin(process.stdin);
@@ -26183,6 +26683,7 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
26183
26683
  process.off("exit", restoreTerminal);
26184
26684
  } catch {
26185
26685
  }
26686
+ restoreConsole();
26186
26687
  restoreStderr();
26187
26688
  process.stderr.write(`mixdog: ${error?.message || error}
26188
26689
  `);
@@ -26208,20 +26709,10 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
26208
26709
  afterCleanup: (reason) => {
26209
26710
  restoreTerminal();
26210
26711
  dumpActiveHandles(`after-${reason}`);
26712
+ restoreConsole();
26211
26713
  restoreStderr();
26212
26714
  }
26213
26715
  });
26214
- const uiHeartbeatTimer = setInterval(() => {
26215
- try {
26216
- touchUiHeartbeat();
26217
- } catch {
26218
- }
26219
- }, 3e4);
26220
- if (typeof uiHeartbeatTimer.unref === "function") uiHeartbeatTimer.unref();
26221
- try {
26222
- touchUiHeartbeat();
26223
- } catch {
26224
- }
26225
26716
  const STDIO_DEATH_FATAL_CODES = /* @__PURE__ */ new Set(["EPIPE", "ERR_STREAM_DESTROYED", "EIO"]);
26226
26717
  const stdioDeathListeners = [];
26227
26718
  const registerStdioDeath = (stream, event, { requireCode = false } = {}) => {
@@ -26245,7 +26736,7 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
26245
26736
  registerStdioDeath(process.stderr, "error", { requireCode: true });
26246
26737
  registerStdioDeath(process.stderr, "close");
26247
26738
  try {
26248
- const instance = render(/* @__PURE__ */ jsx21(App, { store, forceOnboarding: forceOnboarding === true }), { exitOnCtrlC: false, maxFps: 60, incrementalRendering: true, onRender: makeRenderProfiler() });
26739
+ const instance = render(/* @__PURE__ */ jsx21(App, { store, forceOnboarding: forceOnboarding === true }), { exitOnCtrlC: false, maxFps: 60, incrementalRendering: true, patchConsole: false, onRender: makeRenderProfiler() });
26249
26740
  bootProfile2("render:mounted", { ms: (performance3.now() - startedAt).toFixed(1) });
26250
26741
  const { waitUntilExit } = instance;
26251
26742
  if (mouseTracking && typeof instance.setSelection === "function") {
@@ -26263,11 +26754,13 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
26263
26754
  if (mouseTracking && typeof instance.getSelectionRows === "function") {
26264
26755
  store.getRenderSelectionRows = instance.getSelectionRows;
26265
26756
  }
26757
+ if (mouseTracking && typeof instance.forceFullRepaint === "function") {
26758
+ store.forceRenderRepaint = instance.forceFullRepaint;
26759
+ }
26266
26760
  await waitUntilExit();
26267
26761
  } finally {
26268
26762
  stopPerfProbe();
26269
26763
  stopLoopProbe();
26270
- clearInterval(uiHeartbeatTimer);
26271
26764
  for (const [stream, event, handler] of stdioDeathListeners.splice(0)) {
26272
26765
  try {
26273
26766
  stream.off(event, handler);
@@ -26282,6 +26775,7 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
26282
26775
  }
26283
26776
  restoreTerminal();
26284
26777
  dumpActiveHandles("after-restore");
26778
+ restoreConsole();
26285
26779
  restoreStderr();
26286
26780
  }
26287
26781
  scheduleHardExit(0);