mixdog 0.9.65 → 0.9.67

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 (54) hide show
  1. package/package.json +1 -1
  2. package/src/app.mjs +2 -2
  3. package/src/cli.mjs +1 -1
  4. package/src/repl.mjs +72 -6
  5. package/src/runtime/agent/orchestrator/session/context-utils.mjs +76 -21
  6. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +45 -4
  7. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +11 -1
  8. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +36 -13
  9. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +83 -18
  10. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +14 -0
  11. package/src/runtime/agent/orchestrator/session/store.mjs +37 -2
  12. package/src/runtime/channels/lib/config.mjs +7 -0
  13. package/src/runtime/channels/lib/status-snapshot.mjs +6 -30
  14. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +203 -0
  15. package/src/runtime/channels/lib/webhook.mjs +23 -201
  16. package/src/runtime/shared/config.mjs +0 -9
  17. package/src/runtime/shared/time-format.mjs +56 -0
  18. package/src/runtime/shared/tool-card-model.mjs +740 -0
  19. package/src/session-runtime/channel-config-api.mjs +5 -0
  20. package/src/session-runtime/context-status.mjs +2 -1
  21. package/src/session-runtime/lifecycle-api.mjs +5 -0
  22. package/src/session-runtime/prewarm.mjs +13 -7
  23. package/src/session-runtime/provider-request-tools.mjs +6 -0
  24. package/src/session-runtime/runtime-core.mjs +28 -9
  25. package/src/session-runtime/session-text.mjs +6 -0
  26. package/src/session-runtime/settings-api.mjs +0 -12
  27. package/src/session-runtime/tool-catalog-schema.mjs +5 -1
  28. package/src/standalone/channel-admin.mjs +35 -21
  29. package/src/tui/App.jsx +0 -15
  30. package/src/tui/app/channel-pickers.mjs +18 -42
  31. package/src/tui/app/doctor.mjs +0 -1
  32. package/src/tui/app/transcript-window.mjs +16 -0
  33. package/src/tui/app/use-transcript-window.mjs +36 -1
  34. package/src/tui/components/Markdown.jsx +15 -1
  35. package/src/tui/components/Message.jsx +1 -1
  36. package/src/tui/components/PromptInput.jsx +7 -0
  37. package/src/tui/components/ToolExecution.jsx +47 -196
  38. package/src/tui/components/tool-execution/surface-detail.mjs +33 -394
  39. package/src/tui/components/tool-execution/text-format.mjs +27 -104
  40. package/src/tui/dist/index.mjs +613 -264
  41. package/src/tui/engine/frame-batched-store.mjs +5 -3
  42. package/src/tui/engine/live-share.mjs +9 -0
  43. package/src/tui/engine/render-timing.mjs +28 -0
  44. package/src/tui/engine/session-api-ext.mjs +7 -10
  45. package/src/tui/engine/tui-steering-persist.mjs +25 -4
  46. package/src/tui/engine.mjs +9 -1
  47. package/src/tui/index.jsx +11 -3
  48. package/src/tui/markdown/measure-rendered-rows.mjs +28 -16
  49. package/src/tui/markdown/render-ansi.mjs +34 -1
  50. package/src/tui/markdown/stream-fence.mjs +44 -7
  51. package/src/tui/markdown/streaming-markdown.mjs +95 -27
  52. package/src/tui/time-format.mjs +4 -51
  53. package/src/ui/stream-finalize.mjs +45 -0
  54. package/src/runtime/channels/lib/webhook/ngrok.mjs +0 -181
@@ -1246,7 +1246,6 @@ __export(config_exports, {
1246
1246
  getOpenAIUsageSessionKey: () => getOpenAIUsageSessionKey,
1247
1247
  getOpenCodeGoAuthCookie: () => getOpenCodeGoAuthCookie,
1248
1248
  getTelegramToken: () => getTelegramToken,
1249
- getWebhookAuthtoken: () => getWebhookAuthtoken,
1250
1249
  hasStoredSecret: () => hasStoredSecret,
1251
1250
  invalidateConfigReadCache: () => invalidateConfigReadCache,
1252
1251
  invalidateSecretReadCache: () => invalidateSecretReadCache,
@@ -1563,9 +1562,6 @@ function getDiscordToken() {
1563
1562
  function getTelegramToken() {
1564
1563
  return _readSecret(SECRET_ACCOUNTS.telegramToken);
1565
1564
  }
1566
- function getWebhookAuthtoken() {
1567
- return _readSecret(SECRET_ACCOUNTS.webhookAuth);
1568
- }
1569
1565
  function getOpenAIUsageSessionKey() {
1570
1566
  return process.env.OPENAI_USAGE_SESSION_KEY || process.env.OPENAI_DASHBOARD_SESSION_KEY || process.env.OPENAI_SESSION_KEY || process.env.MIXDOG_OPENAI_USAGE_SESSION_KEY || _readSecret(SECRET_ACCOUNTS.openaiUsageSessionKey);
1571
1567
  }
@@ -1631,7 +1627,6 @@ var init_config = __esm({
1631
1627
  SECRET_ACCOUNTS = Object.freeze({
1632
1628
  discordToken: "discord.token",
1633
1629
  telegramToken: "telegram.token",
1634
- webhookAuth: "webhook.authtoken",
1635
1630
  agentApiKey: (provider) => `agent.${provider}.apiKey`,
1636
1631
  openaiUsageSessionKey: "agent.openai.usageSessionKey",
1637
1632
  opencodeGoAuthCookie: "agent.opencode-go.authCookie"
@@ -2588,7 +2583,7 @@ import { render } from "../../../vendor/ink/build/index.js";
2588
2583
  import { closeSync as closeSync3, constants as fsConstants, createWriteStream as createWriteStream2, mkdirSync as mkdirSync9, openSync as openSync3, readSync } from "node:fs";
2589
2584
  import { tmpdir as tmpdir3 } from "node:os";
2590
2585
  import { dirname as dirname12, join as join15 } from "node:path";
2591
- import { performance as performance3 } from "node:perf_hooks";
2586
+ import { performance as performance4 } from "node:perf_hooks";
2592
2587
  import { format } from "node:util";
2593
2588
 
2594
2589
  // src/tui/App.jsx
@@ -5844,7 +5839,7 @@ var SPINNER_VERBS = [
5844
5839
  ];
5845
5840
  var SPINNER_FRAMES = ["\u25C7", "\u25C6", "\u25C8", "\u25C6", "\u25C7"];
5846
5841
 
5847
- // src/tui/time-format.mjs
5842
+ // src/runtime/shared/time-format.mjs
5848
5843
  function formatDuration(ms, options = {}) {
5849
5844
  if (!Number.isFinite(Number(ms))) return "";
5850
5845
  const value = Math.max(0, Number(ms) || 0);
@@ -7274,6 +7269,7 @@ function PromptInput({
7274
7269
  if (valueRef) valueRef.current = draftRef.current.value;
7275
7270
  const { isRawModeSupported } = useStdin();
7276
7271
  const boxRef = useRef4(null);
7272
+ const inkRootRef = useRef4(null);
7277
7273
  const cursorEnabledRef = useRef4(false);
7278
7274
  const contentWidthRef = useRef4(80);
7279
7275
  const preferredColumnRef = useRef4(null);
@@ -7288,9 +7284,15 @@ function PromptInput({
7288
7284
  }
7289
7285
  const flushThrottleRef = useRef4({ lastAt: 0, timer: null });
7290
7286
  const flushImmediate = () => {
7287
+ const cachedRoot = inkRootRef.current;
7288
+ if (typeof cachedRoot?.onImmediateRender === "function") {
7289
+ cachedRoot.onImmediateRender();
7290
+ return;
7291
+ }
7291
7292
  let node = boxRef.current;
7292
7293
  for (let i = 0; node && i < 64; i++) {
7293
7294
  if (node.nodeName === "ink-root") {
7295
+ inkRootRef.current = node;
7294
7296
  if (typeof node.onImmediateRender === "function") node.onImmediateRender();
7295
7297
  return;
7296
7298
  }
@@ -11289,18 +11291,22 @@ function trimPartialClosingFences(tokens) {
11289
11291
  }
11290
11292
  var OPEN_FENCE_RE = /^( {0,3})(`{3,}|~{3,})(.*)$/;
11291
11293
  var CLOSE_FENCE_RE = /^ {0,3}([`~]+)[ \t]*$/;
11294
+ var OPEN_FENCE_SCAN_LRU_MAX = 32;
11295
+ var openFenceScanByStreamKey = /* @__PURE__ */ new Map();
11292
11296
  function isClosingFence(line, char, openLen) {
11293
11297
  const m = CLOSE_FENCE_RE.exec(line);
11294
11298
  if (!m) return false;
11295
11299
  const run = m[1];
11296
11300
  return run.startsWith(char.repeat(openLen));
11297
11301
  }
11298
- function findOpenFenceStart(text) {
11299
- const value = String(text ?? "");
11300
- let open = null;
11301
- let start = 0;
11302
- for (let i = 0; i <= value.length; i++) {
11302
+ function scanOpenFence(value, startAt = 0, initialOpen = null) {
11303
+ let open = initialOpen;
11304
+ let start = startAt;
11305
+ const checkpointIndex = value.lastIndexOf("\n") + 1;
11306
+ let openBeforeCheckpoint = start === checkpointIndex ? open : null;
11307
+ for (let i = startAt; i <= value.length; i++) {
11303
11308
  if (i !== value.length && value[i] !== "\n") continue;
11309
+ if (start === checkpointIndex) openBeforeCheckpoint = open;
11304
11310
  const line = value.slice(start, i);
11305
11311
  if (!open) {
11306
11312
  const m = OPEN_FENCE_RE.exec(line);
@@ -11316,13 +11322,42 @@ function findOpenFenceStart(text) {
11316
11322
  }
11317
11323
  start = i + 1;
11318
11324
  }
11319
- if (!open || open.indent !== 0) return null;
11320
- return { index: open.index, lang: open.lang };
11325
+ return { open, checkpointIndex, openBeforeCheckpoint };
11326
+ }
11327
+ function touchOpenFenceScan(key, entry) {
11328
+ if (openFenceScanByStreamKey.has(key)) openFenceScanByStreamKey.delete(key);
11329
+ openFenceScanByStreamKey.set(key, entry);
11330
+ while (openFenceScanByStreamKey.size > OPEN_FENCE_SCAN_LRU_MAX) {
11331
+ const oldest = openFenceScanByStreamKey.keys().next().value;
11332
+ if (oldest === void 0) break;
11333
+ openFenceScanByStreamKey.delete(oldest);
11334
+ }
11335
+ }
11336
+ function resetOpenFenceScan(streamKey) {
11337
+ if (streamKey == null || streamKey === "") return;
11338
+ openFenceScanByStreamKey.delete(String(streamKey));
11339
+ }
11340
+ function resetAllOpenFenceScans() {
11341
+ openFenceScanByStreamKey.clear();
11342
+ }
11343
+ function findOpenFenceStart(text, streamKey = null) {
11344
+ const value = String(text ?? "");
11345
+ const key = streamKey == null || streamKey === "" ? null : String(streamKey);
11346
+ const cached = key ? openFenceScanByStreamKey.get(key) : null;
11347
+ if (cached?.text === value) return cached.result;
11348
+ const scanned = cached && value.startsWith(cached.text) ? scanOpenFence(value, cached.checkpointIndex, cached.openBeforeCheckpoint) : scanOpenFence(value);
11349
+ const result = !scanned.open || scanned.open.indent !== 0 ? null : { index: scanned.open.index, lang: scanned.open.lang };
11350
+ if (key) touchOpenFenceScan(key, { text: value, ...scanned, result });
11351
+ return result;
11321
11352
  }
11322
11353
 
11323
11354
  // src/tui/markdown/render-ansi.mjs
11324
11355
  var TOKEN_CACHE_MAX = 500;
11325
11356
  var tokenCache = /* @__PURE__ */ new Map();
11357
+ var renderedSegmentCache = [];
11358
+ var RENDERED_SEGMENT_CACHE_MAX = 12;
11359
+ var RENDERED_SEGMENT_CACHE_MAX_CHARS = 256 * 1024;
11360
+ var renderedSegmentCacheChars = 0;
11326
11361
  var MD_SYNTAX_RE = /[#*`|[>\-_~]|\n\n|^\d+\. |\n\d+\. /;
11327
11362
  var _configured = false;
11328
11363
  function configureMarked() {
@@ -11384,8 +11419,19 @@ function lexMarkdown(content, { trimPartialFences = false } = {}) {
11384
11419
  return tokens;
11385
11420
  }
11386
11421
  function renderTokenAnsiSegments(content, opts = {}) {
11387
- const tokens = lexMarkdown(content, opts);
11422
+ const text = String(content ?? "");
11388
11423
  const width = Number(opts.width) || 0;
11424
+ const trimPartialFences = opts.trimPartialFences === true;
11425
+ const themeVersion = getThemeVersion();
11426
+ for (let index = renderedSegmentCache.length - 1; index >= 0; index -= 1) {
11427
+ const entry = renderedSegmentCache[index];
11428
+ if (entry.text === text && entry.width === width && entry.trimPartialFences === trimPartialFences && entry.themeVersion === themeVersion) {
11429
+ renderedSegmentCache.splice(index, 1);
11430
+ renderedSegmentCache.push(entry);
11431
+ return entry.segments;
11432
+ }
11433
+ }
11434
+ const tokens = lexMarkdown(text, opts);
11389
11435
  const segments = [];
11390
11436
  for (const token of tokens) {
11391
11437
  if (token.type === "table") {
@@ -11398,55 +11444,104 @@ function renderTokenAnsiSegments(content, opts = {}) {
11398
11444
  segments.push({ type: "ansi", ansi, token });
11399
11445
  }
11400
11446
  }
11447
+ if (text.length <= RENDERED_SEGMENT_CACHE_MAX_CHARS) {
11448
+ const entry = { text, width, trimPartialFences, themeVersion, segments };
11449
+ renderedSegmentCache.push(entry);
11450
+ renderedSegmentCacheChars += text.length;
11451
+ while (renderedSegmentCache.length > RENDERED_SEGMENT_CACHE_MAX || renderedSegmentCacheChars > RENDERED_SEGMENT_CACHE_MAX_CHARS) {
11452
+ const removed = renderedSegmentCache.shift();
11453
+ renderedSegmentCacheChars -= removed?.text.length || 0;
11454
+ }
11455
+ }
11401
11456
  return segments;
11402
11457
  }
11403
11458
 
11404
11459
  // src/tui/markdown/streaming-markdown.mjs
11405
11460
  import { marked as marked2 } from "marked";
11406
11461
  var stablePrefixByStreamKey = /* @__PURE__ */ new Map();
11462
+ var markdownSyntaxByStreamKey = /* @__PURE__ */ new Map();
11463
+ var plainWindowSyntaxByStreamKey = /* @__PURE__ */ new Map();
11407
11464
  var resolvedPartsByStreamKey = /* @__PURE__ */ new Map();
11408
11465
  var STABLE_PREFIX_LRU_MAX = 32;
11409
11466
  function streamingLayoutText(text) {
11410
11467
  return String(text ?? "").replace(/^\n+|\n+$/g, "");
11411
11468
  }
11412
- function windowPlainStreamingText(text, columns, maxRows) {
11469
+ function windowPlainStreamingText(text, columns, maxRows, streamKey = null) {
11413
11470
  const value = streamingLayoutText(text);
11414
11471
  const rowBudget = Math.max(0, Math.floor(Number(maxRows) || 0));
11415
- if (!value || rowBudget <= 0 || hasMarkdownSyntax(value)) return value;
11416
- const lines = value.split("\n");
11472
+ const key = streamKey == null || streamKey === "" ? null : String(streamKey);
11473
+ if (!value || rowBudget <= 0 || cachedStreamingHasMarkdownSyntax(
11474
+ plainWindowSyntaxByStreamKey,
11475
+ value,
11476
+ key
11477
+ )) return value;
11417
11478
  const width = Math.max(1, Math.floor(Number(columns) || 80));
11418
11479
  let rows = 0;
11419
- let start = lines.length;
11420
- while (start > 0) {
11421
- const line = lines[start - 1];
11480
+ let end = value.length;
11481
+ let start = end;
11482
+ while (end >= 0) {
11483
+ const newline = value.lastIndexOf("\n", end - 1);
11484
+ const lineStart2 = newline + 1;
11485
+ const line = value.slice(lineStart2, end);
11422
11486
  const lineRows = Math.max(1, Math.ceil(displayWidth(line) / width));
11423
11487
  if (rows > 0 && rows + lineRows > rowBudget) break;
11424
11488
  rows += lineRows;
11425
- start -= 1;
11426
- if (rows >= rowBudget) break;
11489
+ start = lineStart2;
11490
+ if (rows >= rowBudget || newline < 0) break;
11491
+ end = newline;
11427
11492
  }
11428
- return start > 0 ? lines.slice(start).join("\n") : value;
11493
+ return start > 0 ? value.slice(start) : value;
11429
11494
  }
11430
11495
  function isWhitespaceOnlyText(text) {
11431
11496
  return !String(text ?? "").trim();
11432
11497
  }
11433
- function touchStablePrefixKey(key, value) {
11498
+ function touchLruKey(cache, key, value) {
11434
11499
  if (!key) return;
11435
- if (stablePrefixByStreamKey.has(key)) stablePrefixByStreamKey.delete(key);
11436
- stablePrefixByStreamKey.set(key, value);
11437
- while (stablePrefixByStreamKey.size > STABLE_PREFIX_LRU_MAX) {
11438
- const oldest = stablePrefixByStreamKey.keys().next().value;
11500
+ if (cache.has(key)) cache.delete(key);
11501
+ cache.set(key, value);
11502
+ while (cache.size > STABLE_PREFIX_LRU_MAX) {
11503
+ const oldest = cache.keys().next().value;
11439
11504
  if (oldest === void 0) break;
11440
- stablePrefixByStreamKey.delete(oldest);
11505
+ cache.delete(oldest);
11441
11506
  }
11442
11507
  }
11508
+ function touchStablePrefixKey(key, value) {
11509
+ touchLruKey(stablePrefixByStreamKey, key, value);
11510
+ }
11443
11511
  function getStablePrefixKey(key) {
11444
- if (!key || !stablePrefixByStreamKey.has(key)) return "";
11512
+ if (!key || !stablePrefixByStreamKey.has(key)) return { text: "", chunks: [] };
11445
11513
  const value = stablePrefixByStreamKey.get(key);
11446
- stablePrefixByStreamKey.delete(key);
11447
- stablePrefixByStreamKey.set(key, value);
11514
+ touchStablePrefixKey(key, value);
11515
+ return value;
11516
+ }
11517
+ function stableStateForText(text, previous) {
11518
+ if (!text) return { text: "", chunks: [] };
11519
+ if (text.startsWith(previous.text)) {
11520
+ const appended = text.substring(previous.text.length);
11521
+ return appended ? { text, chunks: [...previous.chunks, appended] } : previous;
11522
+ }
11523
+ return { text, chunks: [text] };
11524
+ }
11525
+ function cachedStreamingHasMarkdownSyntax(cache, text, key) {
11526
+ if (!key) return hasMarkdownSyntax(text);
11527
+ const previous = cache.get(key);
11528
+ let value;
11529
+ if (previous && text.startsWith(previous.text)) {
11530
+ if (previous.value) {
11531
+ value = true;
11532
+ } else {
11533
+ const lineStart2 = previous.text.lastIndexOf("\n") + 1;
11534
+ value = hasMarkdownSyntax(text.substring(Math.max(0, lineStart2 - 1)));
11535
+ }
11536
+ } else {
11537
+ value = hasMarkdownSyntax(text);
11538
+ }
11539
+ touchLruKey(cache, key, { text, value });
11448
11540
  return value;
11449
11541
  }
11542
+ function streamingHasMarkdownSyntax(text, key) {
11543
+ return cachedStreamingHasMarkdownSyntax(markdownSyntaxByStreamKey, text, key);
11544
+ }
11450
11545
  function getResolvedPartsKey(key, text) {
11451
11546
  if (!key) return null;
11452
11547
  const entry = resolvedPartsByStreamKey.get(key);
@@ -11518,11 +11613,17 @@ function resetStreamingMarkdownStablePrefix(streamKey) {
11518
11613
  if (streamKey == null || streamKey === "") return;
11519
11614
  const key = String(streamKey);
11520
11615
  stablePrefixByStreamKey.delete(key);
11616
+ markdownSyntaxByStreamKey.delete(key);
11617
+ plainWindowSyntaxByStreamKey.delete(key);
11521
11618
  resolvedPartsByStreamKey.delete(key);
11619
+ resetOpenFenceScan(key);
11522
11620
  }
11523
11621
  function resetAllStreamingMarkdownStablePrefixes() {
11524
11622
  stablePrefixByStreamKey.clear();
11623
+ markdownSyntaxByStreamKey.clear();
11624
+ plainWindowSyntaxByStreamKey.clear();
11525
11625
  resolvedPartsByStreamKey.clear();
11626
+ resetAllOpenFenceScans();
11526
11627
  }
11527
11628
  function resolveStreamingMarkdownParts(text, streamKey) {
11528
11629
  const t = streamingLayoutText(text);
@@ -11534,34 +11635,39 @@ function resolveStreamingMarkdownParts(text, streamKey) {
11534
11635
  return cacheResolvedPartsKey(key, t, {
11535
11636
  plain: true,
11536
11637
  stablePrefix: "",
11638
+ stableChunks: [],
11537
11639
  unstableSuffix: "",
11538
11640
  unstableForRender: ""
11539
11641
  });
11540
11642
  }
11541
- if (!hasMarkdownSyntax(t)) {
11643
+ if (!streamingHasMarkdownSyntax(t, key)) {
11542
11644
  if (key) stablePrefixByStreamKey.delete(key);
11543
11645
  return cacheResolvedPartsKey(key, t, {
11544
11646
  plain: true,
11545
11647
  stablePrefix: "",
11648
+ stableChunks: [],
11546
11649
  unstableSuffix: t,
11547
11650
  unstableForRender: t
11548
11651
  });
11549
11652
  }
11550
- let stablePrefix = key ? getStablePrefixKey(key) : "";
11551
- if (!t.startsWith(stablePrefix)) {
11552
- stablePrefix = "";
11653
+ let stableState = key ? getStablePrefixKey(key) : { text: "", chunks: [] };
11654
+ if (!t.startsWith(stableState.text)) {
11655
+ stableState = { text: "", chunks: [] };
11553
11656
  }
11554
- const open = findOpenFenceStart(t);
11657
+ let stablePrefix = stableState.text;
11658
+ const open = findOpenFenceStart(t, key);
11555
11659
  if (open) {
11556
11660
  let openPrefix = t.substring(0, open.index);
11557
11661
  if (isWhitespaceOnlyText(openPrefix)) openPrefix = "";
11558
- if (key && openPrefix) touchStablePrefixKey(key, openPrefix);
11662
+ stableState = stableStateForText(openPrefix, stableState);
11663
+ if (key && openPrefix) touchStablePrefixKey(key, stableState);
11559
11664
  else if (key) stablePrefixByStreamKey.delete(key);
11560
11665
  const unstableSuffix2 = t.substring(openPrefix.length);
11561
11666
  return cacheResolvedPartsKey(key, t, {
11562
11667
  plain: false,
11563
11668
  openFence: true,
11564
11669
  stablePrefix: openPrefix,
11670
+ stableChunks: stableState.chunks,
11565
11671
  unstableSuffix: unstableSuffix2,
11566
11672
  unstableForRender: unstableSuffix2
11567
11673
  });
@@ -11584,18 +11690,24 @@ function resolveStreamingMarkdownParts(text, streamKey) {
11584
11690
  if (advance > 0) {
11585
11691
  stablePrefix = t.substring(0, boundary + advance);
11586
11692
  if (isWhitespaceOnlyText(stablePrefix)) stablePrefix = "";
11587
- if (key && stablePrefix) touchStablePrefixKey(key, stablePrefix);
11693
+ stableState = stableStateForText(stablePrefix, stableState);
11694
+ if (key && stablePrefix) touchStablePrefixKey(key, stableState);
11588
11695
  else if (key && !stablePrefix) stablePrefixByStreamKey.delete(key);
11589
11696
  }
11590
11697
  } catch {
11591
11698
  stablePrefix = "";
11699
+ stableState = { text: "", chunks: [] };
11592
11700
  if (key) stablePrefixByStreamKey.delete(key);
11593
11701
  }
11594
- if (isWhitespaceOnlyText(stablePrefix)) stablePrefix = "";
11702
+ if (isWhitespaceOnlyText(stablePrefix)) {
11703
+ stablePrefix = "";
11704
+ stableState = { text: "", chunks: [] };
11705
+ }
11595
11706
  const unstableSuffix = t.substring(stablePrefix.length);
11596
11707
  return cacheResolvedPartsKey(key, t, {
11597
11708
  plain: false,
11598
11709
  stablePrefix,
11710
+ stableChunks: stableState.chunks,
11599
11711
  unstableSuffix,
11600
11712
  unstableForRender: balanceStreamingMarkdown(unstableSuffix)
11601
11713
  });
@@ -11721,22 +11833,31 @@ function measureStreamingMarkdownRenderedRows(text, columns, streamKey) {
11721
11833
  let rows = 0;
11722
11834
  let childCount = 0;
11723
11835
  let stableRows = 0;
11724
- if (parts.stablePrefix) {
11725
- stableRows = cached && cached.mode === "markdown" && cached.columns === columns && cached.stablePrefix === parts.stablePrefix ? cached.stableRows : measureMarkdownRenderedRows(parts.stablePrefix, columns, { trimPartialFences: false });
11726
- rows += stableRows;
11727
- childCount += 1;
11728
- }
11836
+ const stableChunks = parts.stableChunks?.length ? parts.stableChunks : parts.stablePrefix ? [parts.stablePrefix] : [];
11837
+ const reusableChunks = cached && cached.mode === "markdown" && cached.columns === columns && Array.isArray(cached.stableChunks) && cached.stableChunks.length <= stableChunks.length && cached.stableChunks.every((chunk, index) => chunk === stableChunks[index]);
11838
+ let measuredStableChunks = 0;
11839
+ if (reusableChunks) {
11840
+ stableRows = cached.stableRows;
11841
+ measuredStableChunks = cached.stableChunks.length;
11842
+ }
11843
+ for (let index = measuredStableChunks; index < stableChunks.length; index += 1) {
11844
+ if (index > 0) stableRows += 1;
11845
+ stableRows += measureMarkdownRenderedRows(stableChunks[index], columns, { trimPartialFences: false });
11846
+ }
11847
+ rows += stableRows;
11848
+ childCount = stableChunks.length;
11729
11849
  if (parts.unstableSuffix) {
11850
+ if (childCount > 0) rows += 1;
11730
11851
  rows += measureMarkdownRenderedRows(parts.unstableForRender, columns, { trimPartialFences: true });
11731
11852
  childCount += 1;
11732
11853
  }
11733
- if (childCount === 2) rows += 1;
11734
11854
  const measuredRows = childCount === 0 ? 1 : Math.max(1, rows);
11735
11855
  cacheStreamingRows(key, {
11736
11856
  text: value,
11737
11857
  columns,
11738
11858
  mode: "markdown",
11739
11859
  stablePrefix: parts.stablePrefix,
11860
+ stableChunks: stableChunks.slice(),
11740
11861
  stableRows,
11741
11862
  rows: measuredRows
11742
11863
  });
@@ -12818,6 +12939,9 @@ function cacheStreamingTailEstimate(id, entry) {
12818
12939
  function hasStreamingRowStateToPrune() {
12819
12940
  return streamingMeasuredRowsById.size > 0 || streamingEstimateHighWaterById.size > 0 || streamingTailEstimateById.size > 0;
12820
12941
  }
12942
+ function transcriptHarvestInputsEqual(left, right) {
12943
+ return !!left && !!right && left.revision === right.revision && left.settledItems === right.settledItems && left.streamingTailItem === right.streamingTailItem && left.startIndex === right.startIndex && left.endIndex === right.endIndex && left.frameColumns === right.frameColumns && left.toolOutputExpanded === right.toolOutputExpanded && left.transcriptContentHeight === right.transcriptContentHeight && left.floatingPanelRows === right.floatingPanelRows && left.overlayHintRequested === right.overlayHintRequested && left.transcriptGuardRows === right.transcriptGuardRows && left.themeEpoch === right.themeEpoch;
12944
+ }
12821
12945
  function pruneStreamingMeasuredRowsById(liveIds) {
12822
12946
  if (!liveIds) return;
12823
12947
  if (streamingMeasuredRowsById.size > 0) {
@@ -13632,13 +13756,30 @@ import { useCallback as useCallback3, useEffect as useEffect8, useRef as useRef8
13632
13756
  // src/tui/engine/render-timing.mjs
13633
13757
  var RENDER_ACK_HANG_GUARD_MS = 250;
13634
13758
  var RENDER_SETTLE_IDLE_MS = 64;
13759
+ var TUI_RENDER_FPS = 60;
13760
+ var TUI_FRAME_MS = Math.ceil(1e3 / TUI_RENDER_FPS);
13635
13761
  var pendingRenderAcks = [];
13636
13762
  var renderAckSeq = 0;
13763
+ var lastRenderFrameAt = 0;
13764
+ var renderFrameDelay = (lastFrameAt, currentTime, frameMs = TUI_FRAME_MS) => {
13765
+ if (!(lastFrameAt > 0)) return frameMs;
13766
+ return Math.max(0, frameMs - Math.max(0, currentTime - lastFrameAt));
13767
+ };
13768
+ var scheduleRenderAlignedStoreFlush = (callback, frameMs = TUI_FRAME_MS) => {
13769
+ const timer2 = setTimeout(
13770
+ callback,
13771
+ renderFrameDelay(lastRenderFrameAt, performance.now(), frameMs)
13772
+ );
13773
+ timer2.unref?.();
13774
+ return timer2;
13775
+ };
13776
+ var cancelRenderAlignedStoreFlush = (timer2) => clearTimeout(timer2);
13637
13777
  var scheduleRenderFrameAck = () => {
13638
13778
  const seq = ++renderAckSeq;
13639
13779
  setImmediate(() => notifyRenderFrame(seq));
13640
13780
  };
13641
13781
  var notifyRenderFrame = (seq = ++renderAckSeq) => {
13782
+ lastRenderFrameAt = performance.now();
13642
13783
  if (pendingRenderAcks.length === 0) return;
13643
13784
  const acks = pendingRenderAcks;
13644
13785
  pendingRenderAcks = [];
@@ -14285,6 +14426,11 @@ function useTranscriptWindow({
14285
14426
  const prevViewportGeomRef = useRef9({ contentHeight: 0, floatingPanelRows: 0 });
14286
14427
  const transcriptItemElsRef = useRef9(/* @__PURE__ */ new Map());
14287
14428
  const transcriptMeasureRefCache = useRef9(/* @__PURE__ */ new Map());
14429
+ const harvestGateRef = useRef9({
14430
+ inputs: null,
14431
+ skippedForDrag: false,
14432
+ forceNext: false
14433
+ });
14288
14434
  const transcriptMeasureItemsRef = useRef9(/* @__PURE__ */ new Map());
14289
14435
  const transcriptMeasureRef = useCallback4((item) => {
14290
14436
  if (!TRANSCRIPT_MEASURED_ROWS || !item || item.id == null) return void 0;
@@ -14516,11 +14662,33 @@ function useTranscriptWindow({
14516
14662
  const transcriptTailPinned = Math.max(0, Number(transcriptWindow.effectiveScrollOffset) || 0) === 0;
14517
14663
  const overlayHintOnLastItem = overlayHintRequested && floatingPanelRows <= 0 && transcriptWindow.bottomSpacerRows === 0 && transcriptTailPinned && overlayHintAttachItemIndex >= 0;
14518
14664
  const overlayHintFallbackRow = overlayHintRequested && floatingPanelRows <= 0 && transcriptGuardRows > 0 && !overlayHintOnLastItem;
14665
+ const harvestInputs = {
14666
+ revision,
14667
+ settledItems,
14668
+ streamingTailItem,
14669
+ startIndex: transcriptWindow.startIndex,
14670
+ endIndex: transcriptWindow.endIndex,
14671
+ frameColumns,
14672
+ toolOutputExpanded,
14673
+ transcriptContentHeight,
14674
+ floatingPanelRows,
14675
+ overlayHintRequested,
14676
+ transcriptGuardRows,
14677
+ themeEpoch
14678
+ };
14519
14679
  useLayoutEffect3(() => {
14520
14680
  if (!TRANSCRIPT_MEASURED_ROWS) return;
14521
- if (dragRef.current.active) return;
14681
+ const gate = harvestGateRef.current;
14682
+ if (dragRef.current.active) {
14683
+ gate.skippedForDrag = true;
14684
+ return;
14685
+ }
14686
+ if (!gate.skippedForDrag && !gate.forceNext && transcriptHarvestInputsEqual(gate.inputs, harvestInputs)) return;
14522
14687
  const els = transcriptItemElsRef.current;
14523
14688
  if (!els || els.size === 0) return;
14689
+ gate.inputs = harvestInputs;
14690
+ gate.skippedForDrag = false;
14691
+ gate.forceNext = false;
14524
14692
  const liveItems = transcriptMeasureItemsRef.current;
14525
14693
  const toolExpandedFlag = toolOutputExpanded ? 1 : 0;
14526
14694
  let changed = false;
@@ -14595,6 +14763,7 @@ function useTranscriptWindow({
14595
14763
  }, 0);
14596
14764
  if (typeof streak.timer?.unref === "function") streak.timer.unref();
14597
14765
  }
14766
+ gate.forceNext = true;
14598
14767
  setMeasuredRowsVersion((v) => (v + 1) % 1e6);
14599
14768
  }
14600
14769
  }
@@ -17227,46 +17396,26 @@ function createChannelPickers({
17227
17396
  }
17228
17397
  if (focus === "webhook-endpoint") {
17229
17398
  const returnTo = typeof options.returnTo === "function" ? options.returnTo : () => setPicker(null);
17230
- const domain = setup.webhook?.ngrokDomain || setup.webhook?.domain || "";
17399
+ const publicUrl = setup.webhook?.publicUrl || "";
17231
17400
  const items2 = [
17232
17401
  {
17233
- value: "endpoint-domain",
17234
- label: "ngrok domain",
17235
- description: domain ? domain : "Not set \xB7 Enter ngrok domain",
17236
- _action: "endpoint-domain"
17237
- },
17238
- {
17239
- value: "endpoint-authtoken",
17240
- label: "authtoken",
17241
- description: setup.webhook?.authenticated === true ? "Set" : "Not set \xB7 Enter authtoken",
17242
- _action: "endpoint-authtoken"
17402
+ value: "endpoint-url",
17403
+ label: "Public URL",
17404
+ description: publicUrl ? `${publicUrl}/webhook/<name>` : "Assigned when the channel worker connects to the relay",
17405
+ _action: "endpoint-url"
17243
17406
  }
17244
17407
  ];
17245
17408
  setPicker({
17246
17409
  title: "Webhook endpoint",
17247
- description: "ngrok domain and authtoken. Toggle individual webhooks in /webhooks.",
17410
+ description: "Served through the Mixdog relay \u2014 no tunnel setup needed. Toggle individual webhooks in /webhooks.",
17248
17411
  help: "\u2191/\u2193 Select \xB7 Enter Edit \xB7 Esc Back",
17249
17412
  indexMode: "always",
17250
17413
  labelWidth: 18,
17251
17414
  items: items2,
17252
17415
  onSelect: (_value, item) => {
17253
17416
  try {
17254
- if (item._action === "endpoint-domain") {
17255
- openChannelPrompt({
17256
- kind: "webhook-domain",
17257
- label: "ngrok domain",
17258
- hint: "Paste the reserved ngrok domain (e.g. my-app.ngrok-free.app).",
17259
- afterSave: () => void openChannelSetupPicker("webhook-endpoint", options)
17260
- });
17261
- return;
17262
- }
17263
- if (item._action === "endpoint-authtoken") {
17264
- openChannelPrompt({
17265
- kind: "webhook-token",
17266
- label: "Webhook/ngrok authtoken",
17267
- hint: "Paste the webhook/ngrok authtoken. It is stored in the OS keychain.",
17268
- afterSave: () => void openChannelSetupPicker("webhook-endpoint", options)
17269
- });
17417
+ if (item._action === "endpoint-url") {
17418
+ store.pushNotice(publicUrl ? `Webhook base: ${publicUrl}/webhook/<name>` : "URL not assigned yet \u2014 start channels once and reopen.", "info");
17270
17419
  }
17271
17420
  } catch (e) {
17272
17421
  store.pushNotice(`webhook endpoint failed: ${e?.message || e}`, "error");
@@ -17401,20 +17550,10 @@ function createChannelPickers({
17401
17550
  {
17402
17551
  value: "webhook-endpoint",
17403
17552
  label: "Webhook endpoint",
17404
- meta: (() => {
17405
- const hasDomain = Boolean(setup.webhook?.ngrokDomain || setup.webhook?.domain);
17406
- const hasAuth = setup.webhook?.authenticated === true;
17407
- return hasDomain && hasAuth ? "On" : "Off";
17408
- })(),
17409
- description: (() => {
17410
- const hasDomain = Boolean(setup.webhook?.ngrokDomain || setup.webhook?.domain);
17411
- const hasAuth = setup.webhook?.authenticated === true;
17412
- const needs = [
17413
- ...hasDomain ? [] : ["domain"],
17414
- ...hasAuth ? [] : ["authtoken"]
17415
- ];
17416
- return needs.length ? `Needs ${needs.join(" + ")}` : "ngrok domain and authtoken set";
17417
- })(),
17553
+ // Relay tunnel: public exposure is automatic, so the endpoint is
17554
+ // "On" whenever the webhook server itself is enabled.
17555
+ meta: setup.webhook?.enabled === false ? "Off" : "On",
17556
+ description: setup.webhook?.publicUrl ? setup.webhook.publicUrl : "Mixdog relay tunnel \u2014 URL assigned on first run",
17418
17557
  _action: "webhook-endpoint"
17419
17558
  }
17420
17559
  ];
@@ -20156,13 +20295,29 @@ function Markdown({ children, themeEpoch = 0, trimPartialFences = false, columns
20156
20295
  }, [children, themeEpoch, trimPartialFences, columns]);
20157
20296
  return /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", gap: 1, children: elements });
20158
20297
  }
20298
+ var StableMarkdownChunk = React13.memo(function StableMarkdownChunk2({
20299
+ text,
20300
+ themeEpoch,
20301
+ columns
20302
+ }) {
20303
+ return /* @__PURE__ */ jsx13(Markdown, { themeEpoch, columns, children: text });
20304
+ });
20159
20305
  function StreamingMarkdown({ children, themeEpoch = 0, columns, streamKey }) {
20160
20306
  const parts = resolveStreamingMarkdownParts(children, streamKey);
20161
20307
  if (parts.plain) {
20162
20308
  return /* @__PURE__ */ jsx13(Text13, { color: theme.text, wrap: "wrap", children: parts.unstableForRender });
20163
20309
  }
20310
+ const stableChunks = parts.stableChunks?.length ? parts.stableChunks : parts.stablePrefix ? [parts.stablePrefix] : [];
20164
20311
  return /* @__PURE__ */ jsxs9(Box11, { flexDirection: "column", gap: 1, children: [
20165
- parts.stablePrefix ? /* @__PURE__ */ jsx13(Markdown, { themeEpoch, columns, children: parts.stablePrefix }) : null,
20312
+ stableChunks.map((text, index) => /* @__PURE__ */ jsx13(
20313
+ StableMarkdownChunk,
20314
+ {
20315
+ text,
20316
+ themeEpoch,
20317
+ columns
20318
+ },
20319
+ `stable-${index}`
20320
+ )),
20166
20321
  parts.unstableSuffix ? /* @__PURE__ */ jsx13(Markdown, { themeEpoch, columns, trimPartialFences: true, children: parts.unstableForRender }) : null
20167
20322
  ] });
20168
20323
  }
@@ -20181,7 +20336,7 @@ var AssistantMessage = React14.memo(function AssistantMessage2({
20181
20336
  if (!streaming && assistantId) resetStreamingMarkdownStablePrefix(assistantId);
20182
20337
  }, [streaming, assistantId]);
20183
20338
  const bodyWidth = assistantBodyWidth(columns);
20184
- const renderText = streaming && streamingWindowRows > 0 ? windowPlainStreamingText(text, bodyWidth, streamingWindowRows) : text;
20339
+ const renderText = streaming && streamingWindowRows > 0 ? windowPlainStreamingText(text, bodyWidth, streamingWindowRows, assistantId) : text;
20185
20340
  return /* @__PURE__ */ jsxs10(Box12, { flexDirection: "row", marginTop: 1, children: [
20186
20341
  /* @__PURE__ */ jsx14(Box12, { flexShrink: 0, minWidth: 2, children: /* @__PURE__ */ jsx14(Text14, { color: theme.text, children: TURN_MARKER }) }),
20187
20342
  /* @__PURE__ */ jsx14(Box12, { flexDirection: "column", flexShrink: 0, width: bodyWidth, children: streaming ? /* @__PURE__ */ jsx14(StreamingMarkdown, { themeEpoch, columns: bodyWidth, streamKey: assistantId, children: renderText }) : /* @__PURE__ */ jsx14(Markdown, { themeEpoch, columns: bodyWidth, children: text }) })
@@ -20210,7 +20365,7 @@ import React16 from "react";
20210
20365
  import { Box as Box14, Text as Text16 } from "../../../vendor/ink/build/index.js";
20211
20366
  import stringWidth9 from "string-width";
20212
20367
 
20213
- // src/tui/components/tool-execution/text-format.mjs
20368
+ // src/runtime/shared/tool-card-model.mjs
20214
20369
  import stripAnsi6 from "strip-ansi";
20215
20370
 
20216
20371
  // src/runtime/shared/tool-status.mjs
@@ -20234,7 +20389,7 @@ function toolResultTerminalStatus(text) {
20234
20389
  return normalizeToolTerminalStatus(inline);
20235
20390
  }
20236
20391
 
20237
- // src/tui/components/tool-execution/text-format.mjs
20392
+ // src/runtime/shared/tool-card-model.mjs
20238
20393
  var MIN_RESULT_LINE_CHARS = 24;
20239
20394
  var RESULT_LINE_HARD_MAX = 80;
20240
20395
  var SUMMARY_MAX_CHARS = 48;
@@ -20258,47 +20413,9 @@ function normalizeCountMap(value = {}) {
20258
20413
  }
20259
20414
  return out;
20260
20415
  }
20261
- function deltaColor(token) {
20262
- return String(token || "").startsWith("+") ? theme.success : theme.error;
20263
- }
20264
- function deltaTextParts(text) {
20265
- const value = String(text ?? "");
20266
- const parts = [];
20267
- const re = /(^|[\s([,{·])([+-]\s*\d+)(?=\s+Lines?\b)/gi;
20268
- let last = 0;
20269
- let match;
20270
- while (match = re.exec(value)) {
20271
- const prefix = match[1] || "";
20272
- const token = (match[2] || "").replace(/\s+/g, "");
20273
- const tokenStart = match.index + prefix.length;
20274
- if (match.index > last) parts.push({ text: value.slice(last, match.index) });
20275
- if (prefix) parts.push({ text: prefix });
20276
- if (token) parts.push({ text: token, color: deltaColor(token) });
20277
- last = tokenStart + (match[2] || "").length;
20278
- }
20279
- if (last < value.length) parts.push({ text: value.slice(last) });
20280
- return parts;
20281
- }
20282
20416
  function plural(count, singular, pluralText = `${singular}s`) {
20283
20417
  return count === 1 ? singular : pluralText;
20284
20418
  }
20285
- function fitResultLine(line, columns) {
20286
- const max = Math.min(RESULT_LINE_HARD_MAX, Math.max(MIN_RESULT_LINE_CHARS, Number(columns || 80) - 7));
20287
- const text = safeInlineText(line);
20288
- return displayWidth(text) > max ? truncateToWidth(text, max) : text;
20289
- }
20290
- function truncateToWidth(text, maxWidth) {
20291
- const str = safeInlineText(text);
20292
- if (maxWidth < 1) return "";
20293
- if (displayWidth(str) <= maxWidth) return str;
20294
- const chars = Array.from(str);
20295
- let out = "";
20296
- for (const ch of chars) {
20297
- if (displayWidth(out + ch + "\u2026") > maxWidth) break;
20298
- out += ch;
20299
- }
20300
- return `${out}\u2026`;
20301
- }
20302
20419
  function shellResultStatus(value) {
20303
20420
  const match = String(value || "").match(/(?:^|\b)status:\s*(running|pending|queued|completed|failed|cancelled|canceled)\b/im);
20304
20421
  return match ? String(match[1] || "").toLowerCase() : "";
@@ -20334,8 +20451,6 @@ function shellResultElapsed(value) {
20334
20451
  const elapsedMs = Number(match[1]);
20335
20452
  return Number.isFinite(elapsedMs) && elapsedMs >= 1e3 ? formatElapsed(elapsedMs) : "";
20336
20453
  }
20337
-
20338
- // src/tui/components/tool-execution/surface-detail.mjs
20339
20454
  function isShellTool(normalizedName, label = "") {
20340
20455
  const n = String(normalizedName || "").toLowerCase();
20341
20456
  const l = String(label || "").toLowerCase();
@@ -20667,6 +20782,248 @@ function clampFailureCount(errorCount, groupCount, isError) {
20667
20782
  if (Number.isFinite(explicit)) return Math.max(0, Math.min(groupCount, Math.floor(explicit)));
20668
20783
  return isError ? groupCount : 0;
20669
20784
  }
20785
+ function clipPlain(text, maxChars) {
20786
+ const value = safeInlineText(text);
20787
+ const max = Math.max(1, Number(maxChars) || 1);
20788
+ if (value.length <= max) return value;
20789
+ return `${value.slice(0, Math.max(0, max - 1))}\u2026`;
20790
+ }
20791
+ function deriveToolCardModel(input = {}, options = {}) {
20792
+ const {
20793
+ name = "",
20794
+ args = {},
20795
+ result = null,
20796
+ rawResult = null,
20797
+ isError = false,
20798
+ errorCount,
20799
+ callErrorCount,
20800
+ exitErrorCount,
20801
+ count = 1,
20802
+ completedCount,
20803
+ startedAt = 0,
20804
+ completedAt = 0,
20805
+ aggregate = false,
20806
+ categories = {},
20807
+ doneCategories = null,
20808
+ headerFinalized = true
20809
+ } = input;
20810
+ const nowMs = Number(input.nowMs || Date.now());
20811
+ const truncate2 = typeof options.truncate === "function" ? options.truncate : clipPlain;
20812
+ const maxResultChars = Math.max(
20813
+ MIN_RESULT_LINE_CHARS,
20814
+ Math.min(RESULT_LINE_HARD_MAX, Number(options.maxResultChars ?? RESULT_LINE_HARD_MAX))
20815
+ );
20816
+ const groupCount = Math.max(1, Number(count || 1));
20817
+ const doneCount = Math.max(0, Math.min(groupCount, Number(completedCount ?? (result == null ? 0 : groupCount))));
20818
+ const rt = result == null ? null : String(result).replace(/\s+$/, "");
20819
+ const rawRt = rawResult == null ? null : String(rawResult).replace(/\s+$/, "");
20820
+ const pending = doneCount < groupCount;
20821
+ const headerPending = pending || headerFinalized === false;
20822
+ const hasResult = result != null && Boolean(String(rt || "").trim());
20823
+ const hasRawResult = rawResult != null && Boolean(String(rawRt || "").trim());
20824
+ const startedAtMs = Number(startedAt || 0);
20825
+ const completedAtMs = Number(completedAt || 0);
20826
+ const elapsedMs = startedAtMs ? Math.max(0, (pending ? nowMs : completedAtMs || nowMs) - startedAtMs) : 0;
20827
+ const elapsed = elapsedMs >= 1e3 ? formatElapsed(elapsedMs) : "";
20828
+ const failedCount = clampFailureCount(errorCount, groupCount, isError);
20829
+ const callFailedCount = clampFailureCount(callErrorCount, groupCount, false);
20830
+ const exitFailedCount = clampFailureCount(exitErrorCount, groupCount, false);
20831
+ if (aggregate) {
20832
+ const displayCategories = normalizeCountMap(categories || {});
20833
+ const normalizedDone = doneCategories ? normalizeCountMap(doneCategories) : displayCategories;
20834
+ const hasDoneCounts = Object.values(normalizedDone || {}).some(
20835
+ (v) => (v && typeof v === "object" ? Number(v.count || 0) : Number(v || 0)) > 0
20836
+ );
20837
+ const displayDone = hasDoneCounts ? normalizedDone : displayCategories;
20838
+ const headerOrder = Array.isArray(args?.categoryOrder) ? args.categoryOrder : null;
20839
+ const labelText2 = safeInlineText(formatAggregateHeader(
20840
+ (headerPending ? displayCategories : displayDone) || {},
20841
+ { pending: headerPending, order: headerOrder }
20842
+ ));
20843
+ const detailText = hasResult ? safeInlineText(rt) : "";
20844
+ const terminalStatus2 = pending ? "running" : resultTerminalStatus(rt) || (isError || failedCount > 0 ? "failed" : "completed");
20845
+ return {
20846
+ aggregate: true,
20847
+ pending,
20848
+ headerPending,
20849
+ groupCount,
20850
+ doneCount,
20851
+ elapsed,
20852
+ failedCount,
20853
+ callFailedCount,
20854
+ exitFailedCount,
20855
+ terminalStatus: terminalStatus2,
20856
+ labelText: labelText2,
20857
+ summaryText: "",
20858
+ headerFailureText: "",
20859
+ detailLine: detailText || (pending ? "Running" : "Finished"),
20860
+ detailIsPlaceholder: !detailText,
20861
+ hasResult,
20862
+ hasRawResult,
20863
+ displayedResultBodyText: rt || "",
20864
+ firstResultLine: detailText,
20865
+ totalLines: detailText ? 1 : 0,
20866
+ resultSummary: detailText || null,
20867
+ toolArgPath: "",
20868
+ normalizedName: "",
20869
+ label: "",
20870
+ parsedArgs: args,
20871
+ isShellSurface: false,
20872
+ isSkillSurface: false,
20873
+ isAgentSurfaceCard: false,
20874
+ isAgentResponse: false,
20875
+ isBackgroundResponse: false,
20876
+ isBackgroundMetadataResult: false,
20877
+ backgroundMeta: null
20878
+ };
20879
+ }
20880
+ const { label, summary, normalizedName, args: parsedArgs } = formatToolSurface(name, args);
20881
+ const isShellSurface = isShellTool(normalizedName, label);
20882
+ const isSkillSurface = SKILL_SURFACE_NAMES2.has(String(normalizedName || "").toLowerCase());
20883
+ const backgroundMeta = !pending && isBackgroundTaskTool(normalizedName) ? resolveBackgroundTaskMeta(parsedArgs, rt || "") : null;
20884
+ const backgroundError = backgroundMeta?.error || parsedArgs?.error || "";
20885
+ const errorOnlyResult = Boolean(rt) && isBackgroundErrorOnlyBody(rt, backgroundError);
20886
+ const backgroundResultText = backgroundMeta?.hasResponse ? backgroundMeta.body : "";
20887
+ const displayedResultText = backgroundResultText || (errorOnlyResult ? "" : rt || "");
20888
+ const hasDisplayResult = Boolean(String(displayedResultText || "").trim());
20889
+ const displayedResultBodyText = stripLeadingStatusMarkerFromText(displayedResultText);
20890
+ const hasDisplayBody = Boolean(String(displayedResultBodyText || "").trim());
20891
+ const lines = displayedResultBodyText ? displayedResultBodyText.split("\n") : [];
20892
+ const totalLines = lines.length;
20893
+ const resultSummary = !pending && hasDisplayBody ? summarizeToolResult(name, args, displayedResultBodyText, isError) : null;
20894
+ const firstResultLine = hasDisplayResult ? String(lines[0] ?? "") : "";
20895
+ const shellStatus = isShellSurface ? shellDisplayStatus({ pending, failedCount, exitFailedCount, isError, result: displayedResultText }) : "";
20896
+ const shellElapsed = isShellSurface ? shellResultElapsed(displayedResultText) || elapsed : "";
20897
+ const backgroundElapsed = backgroundMeta ? backgroundTaskElapsed(backgroundMeta, elapsed) : isBackgroundTaskTool(normalizedName) ? backgroundTaskElapsed(parsedArgs, elapsed) : "";
20898
+ const toolArgPath = parsedArgs?.path ?? parsedArgs?.file_path ?? parsedArgs?.file ?? "";
20899
+ const imageDetail = normalizedName === "view_image" && toolArgPath && !isError ? String(toolArgPath) : "";
20900
+ const isBackgroundResult = !pending && isBackgroundTaskTool(normalizedName) && Boolean(backgroundMeta);
20901
+ const isBackgroundResponse = isBackgroundResult && (backgroundMeta?.hasResponse || isBackgroundTaskResponseArgs(normalizedName, parsedArgs));
20902
+ const isBackgroundMetadataResult = isBackgroundResult && !isBackgroundResponse && Boolean(backgroundMeta);
20903
+ const backgroundMetadataFailureLabel = isBackgroundMetadataResult ? backgroundTaskFailureDetail(backgroundMeta, parsedArgs) : "";
20904
+ const backgroundMetadataHeaderFailure = Boolean(backgroundMetadataFailureLabel) && !hasDisplayResult ? backgroundMetadataFailureLabel : "";
20905
+ const agentHeaderFailure = !pending && isAgentTool(normalizedName) && isError && parsedArgs?.error && !hasDisplayResult ? backgroundTaskFailureStatusLabel(parsedArgs?.status, parsedArgs?.error, { surface: "agent" }) : "";
20906
+ const headerFailureText = backgroundMetadataHeaderFailure || agentHeaderFailure || "";
20907
+ const agentCompletionDetail = !pending && isAgentTool(normalizedName) && !agentHeaderFailure ? agentTerminalDetail(parsedArgs?.status, isError, elapsed, parsedArgs?.error) : "";
20908
+ const agentDetail = !pending && isAgentTool(normalizedName) && !hasDisplayResult ? agentCompletionDetail : "";
20909
+ const genericDetail = !pending && !isShellSurface && !agentDetail && !imageDetail && !resultSummary ? genericCompletedDetail({ normalizedName, label, hasResult, firstResultLine, isError }) : "";
20910
+ const terminalStatus = pending ? "running" : shellStatus || normalizeTerminalStatus(backgroundMeta?.status) || normalizeTerminalStatus(parsedArgs?.status) || resultTerminalStatus(displayedResultText) || (isError || failedCount > 0 ? "failed" : "completed");
20911
+ const backgroundMetadataDetail = isBackgroundMetadataResult && !backgroundMetadataHeaderFailure ? backgroundTaskDetail(backgroundMeta, backgroundElapsed, parsedArgs) : "";
20912
+ const backgroundResponseDetail = isBackgroundResponse && resultSummary ? prefixElapsed(resultSummary, backgroundElapsed) : resultSummary;
20913
+ const syncElapsedDetail = !isBackgroundResponse && shouldPrefixSyncElapsed(normalizedName, label) ? prefixElapsed(backgroundResponseDetail, elapsed) : backgroundResponseDetail;
20914
+ const nonShellDetail = backgroundMetadataDetail || (/^(Cancelled|Failed|Finished)$/i.test(resultSummary || "") && agentCompletionDetail ? agentCompletionDetail : syncElapsedDetail) || agentDetail || imageDetail || genericDetail;
20915
+ const pendingDetailPlaceholder = pending && !isSkillSurface ? elapsed ? `Running \xB7 ${elapsed}` : "Running" : "";
20916
+ const shellCollapsedSummary = isShellSurface && !pending && hasDisplayResult ? resultSummary || truncate2(firstResultLine, Math.min(120, maxResultChars)) : resultSummary;
20917
+ const collapsedDetail = pending ? pendingDetailPlaceholder : isShellSurface ? prefixElapsed(mergeTerminalDetail(shellStatus, shellCollapsedSummary), shellElapsed) : mergeTerminalDetail(terminalStatus, nonShellDetail);
20918
+ const isAgentResult = !isBackgroundResult && !pending && isAgentTool(normalizedName) && hasDisplayResult;
20919
+ const isAgentResponse = isAgentResult && hasAgentResponseResult(rt);
20920
+ const isAgentSurfaceCard = isAgentTool(normalizedName);
20921
+ const agentSurfaceBriefRaw = isAgentSurfaceCard ? summarizeAgentSurfaceBrief(name, parsedArgs, displayedResultText || "", { isError, isResponse: isAgentResponse }) : "";
20922
+ const agentSurfaceBrief = agentSurfaceBriefRaw ? truncate2(agentSurfaceBriefRaw, Math.min(AGENT_SURFACE_BRIEF_MAX, maxResultChars)) : "";
20923
+ let detailLine = collapsedDetail;
20924
+ if (isSkillSurface) {
20925
+ detailLine = isError && collapsedDetail ? collapsedDetail : "";
20926
+ } else if (isBackgroundMetadataResult && backgroundMetadataHeaderFailure) {
20927
+ detailLine = "";
20928
+ } else if (isAgentSurfaceCard) {
20929
+ const agentDetailFallback = collapsedDetail || (pending ? pendingDetailPlaceholder || "Running" : "Finished");
20930
+ const agentDetailLine = agentSurfaceBrief || truncate2(String(agentDetailFallback), Math.min(AGENT_SURFACE_BRIEF_MAX, maxResultChars));
20931
+ const agentFailureText = /\b(Cancelled|Canceled|Failed)\b/i.test(agentSurfaceBrief || collapsedDetail || "");
20932
+ const keepAgentDetail = (isError || agentFailureText) && !(agentHeaderFailure && !agentSurfaceBrief);
20933
+ detailLine = keepAgentDetail ? agentDetailLine : "";
20934
+ }
20935
+ let labelText;
20936
+ if (isAgentResponse) labelText = agentResponseTitle(parsedArgs, groupCount);
20937
+ else if (isBackgroundResponse) labelText = backgroundTaskResultTitle(normalizedName, backgroundMeta || parsedArgs);
20938
+ else if (isBackgroundMetadataResult) labelText = backgroundTaskActionTitle(normalizedName, backgroundMeta);
20939
+ else if (isShellSurface) labelText = shellHeader(shellStatus, groupCount);
20940
+ else labelText = (isAgentTool(normalizedName) ? agentActionTitle(parsedArgs) : "") || formatToolActionHeader(name, args, { pending: headerPending, count: groupCount });
20941
+ labelText = safeInlineText(labelText);
20942
+ const toolSearchSummary = !pending && normalizedName === "load_tool" && hasResult ? toolSearchLoadedSummary(displayedResultText) : "";
20943
+ const rawSummaryText = safeInlineText(isAgentResponse || isBackgroundResponse ? "" : toolSearchSummary || (isAgentTool(normalizedName) ? agentActionSummary(parsedArgs, summary) : summary));
20944
+ const summaryIsHeaderCount = rawSummaryText && /^\d+\s+\S+$/.test(rawSummaryText) && labelText.endsWith(rawSummaryText);
20945
+ const summaryText = summaryIsHeaderCount ? "" : rawSummaryText;
20946
+ return {
20947
+ aggregate: false,
20948
+ pending,
20949
+ headerPending,
20950
+ groupCount,
20951
+ doneCount,
20952
+ elapsed,
20953
+ failedCount,
20954
+ callFailedCount,
20955
+ exitFailedCount,
20956
+ terminalStatus,
20957
+ labelText,
20958
+ summaryText,
20959
+ headerFailureText,
20960
+ detailLine,
20961
+ detailIsPlaceholder: Boolean(pendingDetailPlaceholder) && detailLine === pendingDetailPlaceholder,
20962
+ hasResult,
20963
+ hasRawResult,
20964
+ hasDisplayResult,
20965
+ hasDisplayBody,
20966
+ displayedResultBodyText,
20967
+ firstResultLine,
20968
+ totalLines,
20969
+ resultSummary,
20970
+ shellCollapsedSummary,
20971
+ agentSurfaceBrief,
20972
+ toolArgPath: String(toolArgPath || ""),
20973
+ normalizedName,
20974
+ label,
20975
+ parsedArgs,
20976
+ isShellSurface,
20977
+ isSkillSurface,
20978
+ isAgentSurfaceCard,
20979
+ isAgentResponse,
20980
+ isBackgroundResponse,
20981
+ isBackgroundMetadataResult,
20982
+ backgroundMeta
20983
+ };
20984
+ }
20985
+
20986
+ // src/tui/components/tool-execution/text-format.mjs
20987
+ function deltaColor(token) {
20988
+ return String(token || "").startsWith("+") ? theme.success : theme.error;
20989
+ }
20990
+ function deltaTextParts(text) {
20991
+ const value = String(text ?? "");
20992
+ const parts = [];
20993
+ const re = /(^|[\s([,{·])([+-]\s*\d+)(?=\s+Lines?\b)/gi;
20994
+ let last = 0;
20995
+ let match;
20996
+ while (match = re.exec(value)) {
20997
+ const prefix = match[1] || "";
20998
+ const token = (match[2] || "").replace(/\s+/g, "");
20999
+ const tokenStart = match.index + prefix.length;
21000
+ if (match.index > last) parts.push({ text: value.slice(last, match.index) });
21001
+ if (prefix) parts.push({ text: prefix });
21002
+ if (token) parts.push({ text: token, color: deltaColor(token) });
21003
+ last = tokenStart + (match[2] || "").length;
21004
+ }
21005
+ if (last < value.length) parts.push({ text: value.slice(last) });
21006
+ return parts;
21007
+ }
21008
+ function fitResultLine(line, columns) {
21009
+ const max = Math.min(RESULT_LINE_HARD_MAX, Math.max(MIN_RESULT_LINE_CHARS, Number(columns || 80) - 7));
21010
+ const text = safeInlineText(line);
21011
+ return displayWidth(text) > max ? truncateToWidth(text, max) : text;
21012
+ }
21013
+ function truncateToWidth(text, maxWidth) {
21014
+ const str = safeInlineText(text);
21015
+ if (maxWidth < 1) return "";
21016
+ if (displayWidth(str) <= maxWidth) return str;
21017
+ const chars = Array.from(str);
21018
+ let out = "";
21019
+ for (const ch of chars) {
21020
+ if (displayWidth(out + ch + "\u2026") > maxWidth) break;
21021
+ out += ch;
21022
+ }
21023
+ return `${out}\u2026`;
21024
+ }
21025
+
21026
+ // src/tui/components/tool-execution/surface-detail.mjs
20670
21027
  function toolStatusColor({ pending, groupCount, callFailedCount = 0, exitFailedCount = 0, terminalStatus = "" }) {
20671
21028
  if (pending) return theme.text;
20672
21029
  const status = normalizeTerminalStatus(terminalStatus);
@@ -20705,9 +21062,6 @@ import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
20705
21062
  var TOOL_BLINK_MS = 500;
20706
21063
  var TOOL_PENDING_SHOW_DELAY_MS = 1e3;
20707
21064
  var TOOL_ANIM_TICK_MS = TOOL_BLINK_MS;
20708
- function statusCopy(name, label, count, doneCount, pending, isError, args = {}) {
20709
- return formatToolActionHeader(name, args, { pending, count });
20710
- }
20711
21065
  function ToolExecution({ name, args, result, rawResult, isError, errorCount, callErrorCount, exitErrorCount, expanded, columns = 80, attached = false, count = 1, completedCount = 0, startedAt = 0, completedAt = 0, aggregate = false, categories = {}, doneCategories = null, headerFinalized = true, deferredDisplayReady = false, agentResponseAggregate = false }) {
20712
21066
  const rowWidth = Math.max(1, Number(columns || 80));
20713
21067
  const groupCount = Math.max(1, Number(count || 1));
@@ -20794,87 +21148,60 @@ function ToolExecution({ name, args, result, rawResult, isError, errorCount, cal
20794
21148
  )
20795
21149
  ] });
20796
21150
  }
20797
- const { label, summary, normalizedName, args: parsedArgs } = formatToolSurface(name, args);
20798
- const isShellSurface = isShellTool(normalizedName, label);
20799
- const isSkillSurface = SKILL_SURFACE_NAMES2.has(String(normalizedName || "").toLowerCase());
20800
- const backgroundMeta = !pending && isBackgroundTaskTool(normalizedName) ? resolveBackgroundTaskMeta(parsedArgs, rt || "") : null;
20801
- const backgroundError = backgroundMeta?.error || parsedArgs?.error || "";
20802
- const errorOnlyResult = Boolean(rt) && isBackgroundErrorOnlyBody(rt, backgroundError);
20803
- const backgroundResultText = backgroundMeta?.hasResponse ? backgroundMeta.body : "";
20804
- const displayedResultText = backgroundResultText || (errorOnlyResult ? "" : rt || "");
20805
- const hasDisplayResult = Boolean(String(displayedResultText || "").trim());
20806
- const displayedResultBodyText = stripLeadingStatusMarkerFromText(displayedResultText);
20807
- const hasDisplayBody = Boolean(String(displayedResultBodyText || "").trim());
20808
- const lines = displayedResultBodyText ? displayedResultBodyText.split("\n") : [];
20809
- const totalLines = lines.length;
20810
- const resultSummary = !pending && hasDisplayBody ? summarizeToolResult(name, args, displayedResultBodyText, isError) : null;
20811
21151
  const maxResultChars = Math.min(RESULT_LINE_HARD_MAX, Math.max(MIN_RESULT_LINE_CHARS, Number(columns || 80) - 7));
21152
+ const model = deriveToolCardModel({
21153
+ name,
21154
+ args,
21155
+ result,
21156
+ rawResult,
21157
+ isError,
21158
+ errorCount,
21159
+ callErrorCount,
21160
+ exitErrorCount,
21161
+ count: displayGroupCount,
21162
+ completedCount: doneCount,
21163
+ startedAt,
21164
+ completedAt,
21165
+ headerFinalized,
21166
+ nowMs
21167
+ }, { truncate: truncateToWidth, maxResultChars });
21168
+ const {
21169
+ labelText,
21170
+ summaryText,
21171
+ headerFailureText: headerFailureStatus,
21172
+ detailLine: collapsedDetailLine,
21173
+ detailIsPlaceholder,
21174
+ terminalStatus,
21175
+ normalizedName,
21176
+ isShellSurface,
21177
+ isAgentSurfaceCard,
21178
+ isAgentResponse,
21179
+ isBackgroundMetadataResult,
21180
+ hasDisplayResult,
21181
+ hasDisplayBody,
21182
+ displayedResultBodyText,
21183
+ firstResultLine,
21184
+ totalLines,
21185
+ resultSummary,
21186
+ shellCollapsedSummary,
21187
+ toolArgPath
21188
+ } = model;
21189
+ const lines = displayedResultBodyText ? displayedResultBodyText.split("\n") : [];
20812
21190
  const resultColor = theme.text;
20813
- const firstResultLine = hasDisplayResult ? String(lines[0] ?? "") : "";
20814
21191
  const firstResultLineClipped = hasDisplayBody && stringWidth9(firstResultLine) > maxResultChars;
20815
21192
  const hasHiddenDetail = !pending && hasDisplayBody && (totalLines > 1 || firstResultLineClipped || Boolean(resultSummary));
20816
- const shellStatus = isShellSurface ? shellDisplayStatus({ pending, failedCount, exitFailedCount, isError, result: displayedResultText }) : "";
20817
- const shellElapsed = isShellSurface ? shellResultElapsed(displayedResultText) || elapsed : "";
20818
- const backgroundElapsed = backgroundMeta ? backgroundTaskElapsed(backgroundMeta, elapsed) : isBackgroundTaskTool(normalizedName) ? backgroundTaskElapsed(parsedArgs, elapsed) : "";
20819
- const toolArgPath = parsedArgs?.path ?? parsedArgs?.file_path ?? parsedArgs?.file ?? "";
20820
- const imageDetail = normalizedName === "view_image" && toolArgPath && !isError ? String(toolArgPath) : "";
20821
- const isBackgroundResult = !pending && isBackgroundTaskTool(normalizedName) && Boolean(backgroundMeta);
20822
- const isBackgroundResponse = isBackgroundResult && (backgroundMeta?.hasResponse || isBackgroundTaskResponseArgs(normalizedName, parsedArgs));
20823
- const isBackgroundMetadataResult = isBackgroundResult && !isBackgroundResponse && Boolean(backgroundMeta);
20824
- const backgroundMetadataFailureLabel = isBackgroundMetadataResult ? backgroundTaskFailureDetail(backgroundMeta, parsedArgs) : "";
20825
- const backgroundMetadataHeaderFailure = Boolean(backgroundMetadataFailureLabel) && !hasDisplayResult ? backgroundMetadataFailureLabel : "";
20826
- const agentHeaderFailure = !pending && isAgentTool(normalizedName) && isError && parsedArgs?.error && !hasDisplayResult ? backgroundTaskFailureStatusLabel(parsedArgs?.status, parsedArgs?.error, { surface: "agent" }) : "";
20827
- const headerFailureStatus = backgroundMetadataHeaderFailure || agentHeaderFailure || "";
20828
- const agentCompletionDetail = !pending && isAgentTool(normalizedName) && !agentHeaderFailure ? agentTerminalDetail(parsedArgs?.status, isError, elapsed, parsedArgs?.error) : "";
20829
- const agentDetail = !pending && isAgentTool(normalizedName) && !hasDisplayResult ? agentCompletionDetail : "";
20830
- const genericDetail = !pending && !isShellSurface && !agentDetail && !imageDetail && !resultSummary ? genericCompletedDetail({ normalizedName, label, hasResult, firstResultLine, isError }) : "";
20831
- const terminalStatus = pending ? "running" : shellStatus || normalizeTerminalStatus(backgroundMeta?.status) || normalizeTerminalStatus(parsedArgs?.status) || resultTerminalStatus(displayedResultText) || (isError || failedCount > 0 ? "failed" : "completed");
20832
- const backgroundMetadataDetail = isBackgroundMetadataResult && !backgroundMetadataHeaderFailure ? backgroundTaskDetail(backgroundMeta, backgroundElapsed, parsedArgs) : "";
20833
- const backgroundResponseDetail = isBackgroundResponse && resultSummary ? prefixElapsed(resultSummary, backgroundElapsed) : resultSummary;
20834
- const syncElapsedDetail = !isBackgroundResponse && shouldPrefixSyncElapsed(normalizedName, label) ? prefixElapsed(backgroundResponseDetail, elapsed) : backgroundResponseDetail;
20835
- const nonShellDetail = backgroundMetadataDetail || (/^(Cancelled|Failed|Finished)$/i.test(resultSummary || "") && agentCompletionDetail ? agentCompletionDetail : syncElapsedDetail) || agentDetail || imageDetail || genericDetail;
20836
- const pendingDetailPlaceholder = pending && !isSkillSurface ? elapsed ? `Running \xB7 ${elapsed}` : "Running" : "";
20837
- const shellCollapsedSummary = isShellSurface && !pending && hasDisplayResult ? resultSummary || truncateToWidth(firstResultLine, Math.min(120, maxResultChars)) : resultSummary;
20838
- const collapsedDetail = pending ? pendingDetailPlaceholder : isShellSurface ? prefixElapsed(mergeTerminalDetail(shellStatus, shellCollapsedSummary), shellElapsed) : mergeTerminalDetail(terminalStatus, nonShellDetail);
20839
21193
  const backgroundMetadataExpandable = isBackgroundMetadataResult && hasRawResult && !pending;
20840
21194
  const showRawResult = expanded && (hasDisplayBody || hasRawResult) && (!isBackgroundMetadataResult || hasRawResult);
20841
- const detailLines = showRawResult ? agentResponseAggregate && hasRawResult ? stripLeadingStatusMarkerLines(rawRt.split("\n")) : hasDisplayBody ? lines : rawRt ? stripLeadingStatusMarkerLines(rawRt.split("\n")) : [] : collapsedDetail ? [collapsedDetail] : [];
20842
- const isPendingPlaceholderDetail = !showRawResult && Boolean(pendingDetailPlaceholder);
21195
+ const detailLines = showRawResult ? agentResponseAggregate && hasRawResult ? stripLeadingStatusMarkerLines(rawRt.split("\n")) : hasDisplayBody ? lines : rawRt ? stripLeadingStatusMarkerLines(rawRt.split("\n")) : [] : collapsedDetailLine ? [collapsedDetailLine] : [];
21196
+ const isPendingPlaceholderDetail = !showRawResult && detailIsPlaceholder;
20843
21197
  const detailColor = isPendingPlaceholderDetail ? theme.subtle : theme.text;
20844
- const isAgentResult = !isBackgroundResult && !pending && isAgentTool(normalizedName) && hasDisplayResult;
20845
- const isAgentResponse = isAgentResult && hasAgentResponseResult(rt);
20846
- const isAgentSurfaceCard = isAgentTool(normalizedName);
20847
- const agentSurfaceBriefRaw = isAgentSurfaceCard && !showRawResult ? summarizeAgentSurfaceBrief(name, parsedArgs, displayedResultText || "", { isError, isResponse: isAgentResponse }) : "";
20848
- const agentSurfaceBrief = agentSurfaceBriefRaw ? truncateToWidth(agentSurfaceBriefRaw, Math.min(AGENT_SURFACE_BRIEF_MAX, maxResultChars)) : "";
20849
- let visibleDetailLines = detailLines;
20850
- if (isSkillSurface && !showRawResult) {
20851
- visibleDetailLines = isError && collapsedDetail ? [collapsedDetail] : [];
20852
- } else if (isBackgroundMetadataResult && backgroundMetadataHeaderFailure && !showRawResult) {
20853
- visibleDetailLines = [];
20854
- } else if (isAgentSurfaceCard && !showRawResult) {
20855
- const agentDetailFallback = collapsedDetail || (pending ? pendingDetailPlaceholder || "Running" : "Finished");
20856
- const agentDetailLine = agentSurfaceBrief || truncateToWidth(String(agentDetailFallback), Math.min(AGENT_SURFACE_BRIEF_MAX, maxResultChars));
20857
- const agentFailureText = /\b(Cancelled|Canceled|Failed)\b/i.test(agentSurfaceBrief || collapsedDetail || "");
20858
- const keepAgentDetail = (isError || agentFailureText) && !(agentHeaderFailure && !agentSurfaceBrief);
20859
- visibleDetailLines = keepAgentDetail ? [agentDetailLine] : [];
20860
- }
21198
+ const visibleDetailLines = detailLines;
20861
21199
  const finalStatusColor = toolStatusColor({ pending, groupCount, callFailedCount, exitFailedCount, terminalStatus });
20862
21200
  const dotColor = finalStatusColor;
20863
21201
  const markerGlyph = isAgentResponse ? AGENT_RESPONSE_MARKER : isAgentSurfaceCard ? AGENT_CALL_MARKER : TURN_MARKER;
20864
21202
  const isDirectionalMarker = isAgentResponse || isAgentSurfaceCard;
20865
21203
  const markerText = isDirectionalMarker ? `${markerGlyph} ` : markerGlyph;
20866
21204
  const dotText = pending && !blinkOn ? " " : markerText;
20867
- let labelText;
20868
- if (isAgentResponse) labelText = agentResponseTitle(parsedArgs, displayGroupCount);
20869
- else if (isBackgroundResponse) labelText = backgroundTaskResultTitle(normalizedName, backgroundMeta || parsedArgs);
20870
- else if (isBackgroundMetadataResult) labelText = backgroundTaskActionTitle(normalizedName, backgroundMeta);
20871
- else if (isShellSurface) labelText = shellHeader(shellStatus, displayGroupCount);
20872
- else labelText = (isAgentTool(normalizedName) ? agentActionTitle(parsedArgs) : "") || statusCopy(name, label, displayGroupCount, doneCount, headerPending, isError, parsedArgs);
20873
- labelText = safeInlineText(labelText);
20874
- const toolSearchSummary = !pending && normalizedName === "load_tool" && hasResult ? toolSearchLoadedSummary(displayedResultText) : "";
20875
- const rawSummaryText = safeInlineText(isAgentResponse || isBackgroundResponse ? "" : toolSearchSummary || (isAgentTool(normalizedName) ? agentActionSummary(parsedArgs, summary) : summary));
20876
- const summaryIsHeaderCount = rawSummaryText && /^\d+\s+\S+$/.test(rawSummaryText) && labelText.endsWith(rawSummaryText);
20877
- const summaryText = summaryIsHeaderCount ? "" : rawSummaryText;
20878
21205
  const agentHasExpandableBody = isAgentSurfaceCard && !pending && hasResult && (isAgentResponse || totalLines > 1 || firstResultLineClipped);
20879
21206
  const shellHasExpandableBody = isShellSurface && !pending && hasDisplayResult && hasDisplayBody && (totalLines > 1 || firstResultLineClipped || Boolean(shellCollapsedSummary && shellCollapsedSummary !== firstResultLine));
20880
21207
  const showHeaderExpandHint = (isShellSurface ? shellHasExpandableBody : isAgentSurfaceCard ? agentHasExpandableBody : hasHiddenDetail || backgroundMetadataExpandable) && normalizedName !== "load_tool";
@@ -22491,19 +22818,6 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
22491
22818
  resumeAfterChannelPrompt(channelPrompt);
22492
22819
  return true;
22493
22820
  }
22494
- if (channelPrompt.kind === "webhook-token") {
22495
- if (!commandText) return false;
22496
- store.saveWebhookAuthtoken(commandText);
22497
- resumeAfterChannelPrompt(channelPrompt);
22498
- return true;
22499
- }
22500
- if (channelPrompt.kind === "webhook-domain") {
22501
- if (!commandText) return false;
22502
- Promise.resolve(store.setWebhookConfig?.({ ngrokDomain: commandText })).then(() => resumeAfterChannelPrompt(channelPrompt)).catch((e) => {
22503
- store.pushNotice(`webhook config update failed: ${e?.message || e}`, "error");
22504
- });
22505
- return true;
22506
- }
22507
22821
  const parts = commandText.split("|").map((part) => part.trim());
22508
22822
  if (channelPrompt.kind === "channel-add") {
22509
22823
  const isPipe = parts.length > 1;
@@ -23633,7 +23947,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
23633
23947
  }
23634
23948
 
23635
23949
  // src/tui/engine.mjs
23636
- import { performance as performance2 } from "node:perf_hooks";
23950
+ import { performance as performance3 } from "node:perf_hooks";
23637
23951
  import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync11, readdirSync as readdirSync5, rmSync as rmSync4, statSync as statSync7, watch as watch2, writeFileSync as writeFileSync8 } from "node:fs";
23638
23952
  import { basename as basename7, dirname as dirname11, join as join14 } from "node:path";
23639
23953
 
@@ -23786,6 +24100,12 @@ var SYNTHETIC_SESSION_TEXT_PATTERNS = Object.freeze([
23786
24100
  /^\[mixdog-runtime\]/i,
23787
24101
  /^\[(?:truncated|request interrupted by user)\]$/i,
23788
24102
  /^a previous model worked on this task and produced the compacted handoff summary below\b/i,
24103
+ // Compact/auto-clear re-seed handoff variants: these lead the FIRST user
24104
+ // message of every post-compaction session, so titling from them painted
24105
+ // "Re-attached after compaction…" rows in Recent (user report). Skipping
24106
+ // them titles the session from its first REAL user message instead.
24107
+ /^re-attached after compaction\b/i,
24108
+ /^reference files:\s/i,
23789
24109
  /^the async (?:agent|shell) task\b/i
23790
24110
  ]);
23791
24111
  var LATE_TOOL_ANNOUNCEMENT_SENTINEL = "connected after this session started";
@@ -23916,12 +24236,12 @@ function sessionPath(id) {
23916
24236
  }
23917
24237
 
23918
24238
  // src/tui/engine/boot-profile.mjs
23919
- import { performance } from "node:perf_hooks";
24239
+ import { performance as performance2 } from "node:perf_hooks";
23920
24240
  var BOOT_PROFILE_ENABLED = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_BOOT_PROFILE || ""));
23921
- var BOOT_PROFILE_START = globalThis.__mixdogBootProfileStart || (globalThis.__mixdogBootProfileStart = performance.now());
24241
+ var BOOT_PROFILE_START = globalThis.__mixdogBootProfileStart || (globalThis.__mixdogBootProfileStart = performance2.now());
23922
24242
  function bootProfile(event, fields = {}) {
23923
24243
  if (!BOOT_PROFILE_ENABLED) return;
23924
- const elapsedMs = performance.now() - BOOT_PROFILE_START;
24244
+ const elapsedMs = performance2.now() - BOOT_PROFILE_START;
23925
24245
  const parts = [`[mixdog-boot] +${elapsedMs.toFixed(1)}ms`, `tui:${event}`];
23926
24246
  for (const [key, value] of Object.entries(fields || {})) {
23927
24247
  if (value === void 0 || value === null || value === "") continue;
@@ -25611,6 +25931,7 @@ import { randomBytes as randomBytes3 } from "crypto";
25611
25931
  import { join as join10 } from "path";
25612
25932
  var PENDING_MESSAGES_FILE = "session-pending-messages.json";
25613
25933
  var PENDING_MESSAGES_MODE = 384;
25934
+ var STALE_STEERING_RESTORE_TTL_MS = 30 * 60 * 1e3;
25614
25935
  var _persistChain = Promise.resolve();
25615
25936
  function _serialize(task) {
25616
25937
  const run = _persistChain.then(task, task);
@@ -25637,7 +25958,9 @@ function normalizeTuiSteeringQueueEntry(entry) {
25637
25958
  if (typeof entry.text === "string" && entry.text.trim()) {
25638
25959
  const text = entry.text.trim();
25639
25960
  const id = typeof entry.id === "string" && entry.id.trim() ? entry.id.trim() : null;
25640
- return id ? { id, text } : text;
25961
+ if (!id) return text;
25962
+ const at = Number(entry.at);
25963
+ return Number.isFinite(at) && at > 0 ? { id, text, at } : { id, text };
25641
25964
  }
25642
25965
  return null;
25643
25966
  }
@@ -25689,7 +26012,7 @@ function appendTuiSteeringPersist(leadSessionId, entry) {
25689
26012
  const key = tuiSteeringSessionKey(leadSessionId);
25690
26013
  if (!key) return Promise.resolve();
25691
26014
  if (!entry.steeringPersistId) entry.steeringPersistId = newSteeringPersistId();
25692
- const record = { id: entry.steeringPersistId, text };
26015
+ const record = { id: entry.steeringPersistId, text, at: Date.now() };
25693
26016
  return _serialize(async () => {
25694
26017
  try {
25695
26018
  await updateJsonAtomic(pendingMessagesPath(), (raw) => {
@@ -25761,12 +26084,21 @@ function drainTuiSteeringPersist(leadSessionId) {
25761
26084
  if (!key) return Promise.resolve([]);
25762
26085
  return _serialize(async () => {
25763
26086
  let drained = [];
26087
+ let droppedStale = 0;
25764
26088
  try {
25765
26089
  await updateJsonAtomic(pendingMessagesPath(), (raw) => {
25766
26090
  const next = normalizePendingStore(raw);
25767
26091
  const q = Array.isArray(next.sessions[key]) ? next.sessions[key] : [];
25768
- drained = q.map(drainedRowToRestore).filter(Boolean);
25769
- if (drained.length === 0) return void 0;
26092
+ const now = Date.now();
26093
+ const touchedAt = Number(next.sessionTouchedAt?.[key]) || 0;
26094
+ const fresh = q.filter((row) => {
26095
+ const at = Number(row?.at) || touchedAt;
26096
+ const stale = at > 0 && now - at > STALE_STEERING_RESTORE_TTL_MS;
26097
+ if (stale) droppedStale += 1;
26098
+ return !stale;
26099
+ });
26100
+ drained = fresh.map(drainedRowToRestore).filter(Boolean);
26101
+ if (drained.length === 0 && droppedStale === 0) return void 0;
25770
26102
  delete next.sessions[key];
25771
26103
  if (next.sessionTouchedAt) delete next.sessionTouchedAt[key];
25772
26104
  next.updatedAt = Date.now();
@@ -25775,6 +26107,13 @@ function drainTuiSteeringPersist(leadSessionId) {
25775
26107
  } catch (err) {
25776
26108
  try {
25777
26109
  process.stderr.write(`[tui] steering-queue drain failed sessionId=${leadSessionId}: ${err?.message || err}
26110
+ `);
26111
+ } catch {
26112
+ }
26113
+ }
26114
+ if (droppedStale > 0) {
26115
+ try {
26116
+ process.stderr.write(`[tui] dropped ${droppedStale} stale steering row(s) sessionId=${leadSessionId}
25778
26117
  `);
25779
26118
  } catch {
25780
26119
  }
@@ -28166,16 +28505,6 @@ function createEngineApiB(bag) {
28166
28505
  pushNotice("telegram token forgotten", "info");
28167
28506
  return result;
28168
28507
  },
28169
- saveWebhookAuthtoken: (token) => {
28170
- const result = runtime.saveWebhookAuthtoken(token);
28171
- pushNotice("webhook/ngrok authtoken saved", "info");
28172
- return result;
28173
- },
28174
- forgetWebhookAuthtoken: () => {
28175
- const result = runtime.forgetWebhookAuthtoken();
28176
- pushNotice("webhook/ngrok authtoken forgotten", "info");
28177
- return result;
28178
- },
28179
28508
  setChannel: async (entry) => {
28180
28509
  const result = await runtime.setChannel(entry);
28181
28510
  pushNotice("channel saved", "info");
@@ -28276,6 +28605,17 @@ function createEngineApiB(bag) {
28276
28605
  listSessions: (options) => {
28277
28606
  return runtime.listSessions(options);
28278
28607
  },
28608
+ // Desktop sidebar watcher hook: EngineHost fs.watches this directory so
28609
+ // heartbeat sidecar create/delete pushes instant working/dot updates.
28610
+ // Without it the watcher silently no-ops and the sidebar falls back to
28611
+ // the 60s safety poll (user: spinner kept spinning after the turn ended).
28612
+ sessionStoreDir: () => {
28613
+ try {
28614
+ return runtime.sessionStoreDir?.() || null;
28615
+ } catch {
28616
+ return null;
28617
+ }
28618
+ },
28279
28619
  deleteSession: async (id) => {
28280
28620
  if (getState().commandBusy) return false;
28281
28621
  const deletingCurrent = String(runtime.session?.id || getState().sessionId || "") === String(id || "");
@@ -28581,7 +28921,6 @@ async function buildDoctorReport(runtime = {}, getState = () => ({})) {
28581
28921
  const tokens = [];
28582
28922
  if (setup.discord?.authenticated) tokens.push("discord");
28583
28923
  if (setup.telegram?.authenticated) tokens.push("telegram");
28584
- if (setup.webhook?.authenticated) tokens.push("webhook");
28585
28924
  const running = worker.running === true;
28586
28925
  const detail = `enabled \xB7 worker ${running ? "running" : "stopped"} \xB7 tokens: ${tokens.length ? tokens.join(", ") : "none"}`;
28587
28926
  row(running ? "ok" : "warn", detail);
@@ -29247,7 +29586,9 @@ function createFrameBatchedStorePublisher({
29247
29586
  frameMs = 16,
29248
29587
  setTimer = setTimeout,
29249
29588
  clearTimer = clearTimeout,
29250
- enqueueMicrotask = queueMicrotask
29589
+ enqueueMicrotask = queueMicrotask,
29590
+ scheduleFrame = (callback, delay2) => setTimer(callback, delay2),
29591
+ cancelFrame = (handle) => clearTimer(handle)
29251
29592
  }) {
29252
29593
  let timer2 = null;
29253
29594
  let emitPending = false;
@@ -29255,7 +29596,7 @@ function createFrameBatchedStorePublisher({
29255
29596
  let immediatePending = false;
29256
29597
  const flush = () => {
29257
29598
  if (timer2 !== null) {
29258
- clearTimer(timer2);
29599
+ cancelFrame(timer2);
29259
29600
  timer2 = null;
29260
29601
  }
29261
29602
  immediatePending = false;
@@ -29277,7 +29618,7 @@ function createFrameBatchedStorePublisher({
29277
29618
  const emit = () => {
29278
29619
  emitPending = true;
29279
29620
  if (timer2 !== null || isDisposed()) return;
29280
- timer2 = setTimer(flush, frameMs);
29621
+ timer2 = scheduleFrame(flush, frameMs);
29281
29622
  timer2?.unref?.();
29282
29623
  };
29283
29624
  const markStructureChange = () => {
@@ -29291,7 +29632,7 @@ function createFrameBatchedStorePublisher({
29291
29632
  };
29292
29633
  const dispose = () => {
29293
29634
  if (emitPending && !isDisposed()) flush();
29294
- else if (timer2 !== null) clearTimer(timer2);
29635
+ else if (timer2 !== null) cancelFrame(timer2);
29295
29636
  timer2 = null;
29296
29637
  emitPending = false;
29297
29638
  structureChangePending = false;
@@ -29707,6 +30048,7 @@ function createLiveShare({
29707
30048
  const stopClient = () => {
29708
30049
  const closing = client;
29709
30050
  const closingId = clientId;
30051
+ const wasUp = clientUp;
29710
30052
  client = null;
29711
30053
  clientId = "";
29712
30054
  clientUp = false;
@@ -29718,6 +30060,7 @@ function createLiveShare({
29718
30060
  } catch {
29719
30061
  }
29720
30062
  }
30063
+ if (wasUp) clearMirroredLiveState();
29721
30064
  };
29722
30065
  const startClient = (id) => {
29723
30066
  let socket;
@@ -30313,16 +30656,16 @@ async function createEngineSession({
30313
30656
  cwd,
30314
30657
  desktopSession
30315
30658
  } = {}) {
30316
- const startedAt = performance2.now();
30659
+ const startedAt = performance3.now();
30317
30660
  bootProfile("engine:create:start", { provider: providerName, model, toolMode, remote });
30318
30661
  process.env.MIXDOG_QUIET_PROVIDER_LOG = "1";
30319
30662
  process.env.MIXDOG_QUIET_SESSION_LOG = "1";
30320
30663
  process.env.MIXDOG_QUIET_MCP_LOG = "1";
30321
30664
  process.env.MIXDOG_QUIET_MEMORY_LOG = "1";
30322
30665
  process.env.MIXDOG_PATCH_NATIVE_PREWARM ??= "0";
30323
- const importStartedAt = performance2.now();
30666
+ const importStartedAt = performance3.now();
30324
30667
  const { createMixdogSessionRuntime } = await import(SESSION_RUNTIME_MODULE);
30325
- bootProfile("session-runtime:imported", { ms: (performance2.now() - importStartedAt).toFixed(1) });
30668
+ bootProfile("session-runtime:imported", { ms: (performance3.now() - importStartedAt).toFixed(1) });
30326
30669
  const runtime = await createMixdogSessionRuntime({
30327
30670
  provider: providerName,
30328
30671
  model,
@@ -30331,9 +30674,9 @@ async function createEngineSession({
30331
30674
  ...cwd ? { cwd } : {},
30332
30675
  ...desktopSession ? { desktopSession } : {}
30333
30676
  });
30334
- bootProfile("engine:create:runtime-ready", { ms: (performance2.now() - startedAt).toFixed(1) });
30677
+ bootProfile("engine:create:runtime-ready", { ms: (performance3.now() - startedAt).toFixed(1) });
30335
30678
  const runtimeCwd = runtime.cwd || process.cwd();
30336
- const stateStartedAt = performance2.now();
30679
+ const stateStartedAt = performance3.now();
30337
30680
  const flags = {
30338
30681
  disposed: false,
30339
30682
  draining: false,
@@ -30409,11 +30752,11 @@ async function createEngineSession({
30409
30752
  cwd: runtimeCwd,
30410
30753
  themeEpoch: 0
30411
30754
  };
30412
- bootProfile("engine:route-state-ready", { ms: (performance2.now() - stateStartedAt).toFixed(1) });
30413
- bootProfile("engine:state-ready", { ms: (performance2.now() - stateStartedAt).toFixed(1) });
30414
- const contextStartedAt = performance2.now();
30755
+ bootProfile("engine:route-state-ready", { ms: (performance3.now() - stateStartedAt).toFixed(1) });
30756
+ bootProfile("engine:state-ready", { ms: (performance3.now() - stateStartedAt).toFixed(1) });
30757
+ const contextStartedAt = performance3.now();
30415
30758
  syncContextStats({ allowEstimated: true });
30416
- bootProfile("engine:context-ready", { ms: (performance2.now() - contextStartedAt).toFixed(1) });
30759
+ bootProfile("engine:context-ready", { ms: (performance3.now() - contextStartedAt).toFixed(1) });
30417
30760
  const listeners = /* @__PURE__ */ new Set();
30418
30761
  let publishedState = process.env.NODE_ENV === "production" ? state : Object.freeze(state);
30419
30762
  state = { ...state, stats: { ...state.stats } };
@@ -30424,7 +30767,10 @@ async function createEngineSession({
30424
30767
  state = { ...next, stats: { ...next.stats } };
30425
30768
  },
30426
30769
  listeners,
30427
- isDisposed: () => flags.disposed
30770
+ isDisposed: () => flags.disposed,
30771
+ frameMs: TUI_FRAME_MS,
30772
+ scheduleFrame: scheduleRenderAlignedStoreFlush,
30773
+ cancelFrame: cancelRenderAlignedStoreFlush
30428
30774
  });
30429
30775
  const emit = publisher.emit;
30430
30776
  const flushEmit = publisher.flush;
@@ -31100,7 +31446,7 @@ var XTSHIFTESCAPE_ON = process.env.WT_SESSION ? "" : "\x1B[>1s";
31100
31446
  var MOUSE_TRACKING_ON2 = `\x1B[?1000h\x1B[?1002h\x1B[?1006h\x1B[?1007l${XTSHIFTESCAPE_ON}`;
31101
31447
  var ALT_SCROLL_RESTORE = "\x1B[?1007h";
31102
31448
  var BOOT_PROFILE_ENABLED2 = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_BOOT_PROFILE || ""));
31103
- var BOOT_PROFILE_START2 = globalThis.__mixdogBootProfileStart || (globalThis.__mixdogBootProfileStart = performance3.now());
31449
+ var BOOT_PROFILE_START2 = globalThis.__mixdogBootProfileStart || (globalThis.__mixdogBootProfileStart = performance4.now());
31104
31450
  var EXIT_WAIT_TIMEOUT_MS = positiveIntEnv2("MIXDOG_TUI_EXIT_WAIT_MS", 2500);
31105
31451
  var EXIT_HARD_DELAY_MS = positiveIntEnv2("MIXDOG_TUI_HARD_EXIT_DELAY_MS", 500);
31106
31452
  var EXIT_HARD_ENABLED = !/^(0|false|no|off)$/i.test(String(process.env.MIXDOG_TUI_HARD_EXIT || "1"));
@@ -31116,9 +31462,9 @@ function positiveIntEnv2(name, fallback) {
31116
31462
  function installTuiLoopProbe() {
31117
31463
  if (!LOOP_PROBE_ENABLED) return () => {
31118
31464
  };
31119
- let last = performance3.now();
31465
+ let last = performance4.now();
31120
31466
  const timer2 = setInterval(() => {
31121
- const now = performance3.now();
31467
+ const now = performance4.now();
31122
31468
  const drift = now - last - LOOP_PROBE_INTERVAL_MS;
31123
31469
  last = now;
31124
31470
  if (drift > LOOP_PROBE_DRIFT_THRESHOLD_MS) {
@@ -31143,7 +31489,7 @@ var PERF_STALL_THRESHOLD_MS = positiveIntEnv2("MIXDOG_TUI_PERF_STALL_MS", 80);
31143
31489
  var PERF_RENDER_GAP_MS = positiveIntEnv2("MIXDOG_TUI_PERF_RENDER_GAP_MS", 120);
31144
31490
  function perfLog(event, fields = {}) {
31145
31491
  if (!PERF_ENABLED) return;
31146
- const elapsedMs = performance3.now() - BOOT_PROFILE_START2;
31492
+ const elapsedMs = performance4.now() - BOOT_PROFILE_START2;
31147
31493
  const parts = [`[mixdog-perf] +${elapsedMs.toFixed(1)}ms`, event];
31148
31494
  for (const [key, value] of Object.entries(fields || {})) {
31149
31495
  if (value === void 0 || value === null || value === "") continue;
@@ -31158,7 +31504,7 @@ function perfLog(event, fields = {}) {
31158
31504
  function installTuiPerfProbe() {
31159
31505
  if (!PERF_ENABLED) return () => {
31160
31506
  };
31161
- let last = performance3.now();
31507
+ let last = performance4.now();
31162
31508
  let lastCpu = process.cpuUsage();
31163
31509
  perfLog("probe:start", {
31164
31510
  intervalMs: PERF_STALL_INTERVAL_MS,
@@ -31166,7 +31512,7 @@ function installTuiPerfProbe() {
31166
31512
  renderGapMs: PERF_RENDER_GAP_MS
31167
31513
  });
31168
31514
  const timer2 = setInterval(() => {
31169
- const now = performance3.now();
31515
+ const now = performance4.now();
31170
31516
  const elapsed = now - last;
31171
31517
  const lagMs = elapsed - PERF_STALL_INTERVAL_MS;
31172
31518
  const cpu = process.cpuUsage(lastCpu);
@@ -31204,7 +31550,7 @@ function makeRenderProfiler() {
31204
31550
  let lastFrameAt = 0;
31205
31551
  return ({ renderTime } = {}) => {
31206
31552
  ackRenderedFrame();
31207
- const now = performance3.now();
31553
+ const now = performance4.now();
31208
31554
  const ms = Number(renderTime) || 0;
31209
31555
  const gap = lastFrameAt ? now - lastFrameAt : 0;
31210
31556
  lastFrameAt = now;
@@ -31238,7 +31584,7 @@ function makeRenderProfiler() {
31238
31584
  }
31239
31585
  function bootProfile2(event, fields = {}) {
31240
31586
  if (!BOOT_PROFILE_ENABLED2) return;
31241
- const elapsedMs = performance3.now() - BOOT_PROFILE_START2;
31587
+ const elapsedMs = performance4.now() - BOOT_PROFILE_START2;
31242
31588
  const parts = [`[mixdog-boot] +${elapsedMs.toFixed(1)}ms`, `tui:${event}`];
31243
31589
  for (const [key, value] of Object.entries(fields || {})) {
31244
31590
  if (value === void 0 || value === null || value === "") continue;
@@ -31456,7 +31802,7 @@ function installTuiConsoleGuard() {
31456
31802
  };
31457
31803
  }
31458
31804
  async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {}) {
31459
- const startedAt = performance3.now();
31805
+ const startedAt = performance4.now();
31460
31806
  bootProfile2("run:start", { provider, model, toolMode, remote });
31461
31807
  if (!process.stdin.isTTY) {
31462
31808
  process.stderr.write(
@@ -31487,6 +31833,7 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
31487
31833
  process.stdout.write(`${TERMINAL_MODE_RESET_HIDDEN_CURSOR}\x1B[?1049h${TERMINAL_MODE_RESET_HIDDEN_CURSOR}\x1B[2J\x1B[H`);
31488
31834
  process.stdout.write("\x1B[5 q");
31489
31835
  process.on("exit", restoreTerminal);
31836
+ const storeOutcomePromise = createEngineSession({ provider, model, toolMode, remote }).then((store2) => ({ store: store2, error: null }), (error) => ({ store: null, error }));
31490
31837
  try {
31491
31838
  await loadThemeSettingFromConfig();
31492
31839
  } catch {
@@ -31496,8 +31843,10 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
31496
31843
  } };
31497
31844
  let store;
31498
31845
  try {
31499
- store = await createEngineSession({ provider, model, toolMode, remote });
31500
- bootProfile2("store:ready", { ms: (performance3.now() - startedAt).toFixed(1) });
31846
+ const outcome = await storeOutcomePromise;
31847
+ if (outcome.error) throw outcome.error;
31848
+ store = outcome.store;
31849
+ bootProfile2("store:ready", { ms: (performance4.now() - startedAt).toFixed(1) });
31501
31850
  } catch (error) {
31502
31851
  splash.stop();
31503
31852
  stopPerfProbe();
@@ -31562,8 +31911,8 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
31562
31911
  registerStdioDeath(process.stderr, "error", { requireCode: true });
31563
31912
  registerStdioDeath(process.stderr, "close");
31564
31913
  try {
31565
- const instance = render(/* @__PURE__ */ jsx21(App, { store, forceOnboarding: forceOnboarding === true }), { exitOnCtrlC: false, maxFps: 60, incrementalRendering: true, patchConsole: false, onRender: makeRenderProfiler() });
31566
- bootProfile2("render:mounted", { ms: (performance3.now() - startedAt).toFixed(1) });
31914
+ const instance = render(/* @__PURE__ */ jsx21(App, { store, forceOnboarding: forceOnboarding === true }), { exitOnCtrlC: false, maxFps: TUI_RENDER_FPS, incrementalRendering: true, patchConsole: false, onRender: makeRenderProfiler() });
31915
+ bootProfile2("render:mounted", { ms: (performance4.now() - startedAt).toFixed(1) });
31567
31916
  const { waitUntilExit } = instance;
31568
31917
  if (mouseTracking && typeof instance.setSelection === "function") {
31569
31918
  store.setRenderSelection = instance.setSelection;