mixdog 0.9.65 → 0.9.66

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 (31) 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 +79 -18
  10. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +4 -0
  11. package/src/runtime/agent/orchestrator/session/store.mjs +37 -2
  12. package/src/session-runtime/context-status.mjs +2 -1
  13. package/src/session-runtime/provider-request-tools.mjs +6 -0
  14. package/src/session-runtime/tool-catalog-schema.mjs +5 -1
  15. package/src/tui/app/transcript-window.mjs +16 -0
  16. package/src/tui/app/use-transcript-window.mjs +36 -1
  17. package/src/tui/components/Markdown.jsx +15 -1
  18. package/src/tui/components/Message.jsx +1 -1
  19. package/src/tui/components/PromptInput.jsx +7 -0
  20. package/src/tui/dist/index.mjs +307 -79
  21. package/src/tui/engine/frame-batched-store.mjs +5 -3
  22. package/src/tui/engine/render-timing.mjs +28 -0
  23. package/src/tui/engine/session-api-ext.mjs +7 -0
  24. package/src/tui/engine/tui-steering-persist.mjs +25 -4
  25. package/src/tui/engine.mjs +9 -1
  26. package/src/tui/index.jsx +11 -3
  27. package/src/tui/markdown/measure-rendered-rows.mjs +28 -16
  28. package/src/tui/markdown/render-ansi.mjs +34 -1
  29. package/src/tui/markdown/stream-fence.mjs +44 -7
  30. package/src/tui/markdown/streaming-markdown.mjs +95 -27
  31. package/src/ui/stream-finalize.mjs +45 -0
@@ -2588,7 +2588,7 @@ import { render } from "../../../vendor/ink/build/index.js";
2588
2588
  import { closeSync as closeSync3, constants as fsConstants, createWriteStream as createWriteStream2, mkdirSync as mkdirSync9, openSync as openSync3, readSync } from "node:fs";
2589
2589
  import { tmpdir as tmpdir3 } from "node:os";
2590
2590
  import { dirname as dirname12, join as join15 } from "node:path";
2591
- import { performance as performance3 } from "node:perf_hooks";
2591
+ import { performance as performance4 } from "node:perf_hooks";
2592
2592
  import { format } from "node:util";
2593
2593
 
2594
2594
  // src/tui/App.jsx
@@ -7274,6 +7274,7 @@ function PromptInput({
7274
7274
  if (valueRef) valueRef.current = draftRef.current.value;
7275
7275
  const { isRawModeSupported } = useStdin();
7276
7276
  const boxRef = useRef4(null);
7277
+ const inkRootRef = useRef4(null);
7277
7278
  const cursorEnabledRef = useRef4(false);
7278
7279
  const contentWidthRef = useRef4(80);
7279
7280
  const preferredColumnRef = useRef4(null);
@@ -7288,9 +7289,15 @@ function PromptInput({
7288
7289
  }
7289
7290
  const flushThrottleRef = useRef4({ lastAt: 0, timer: null });
7290
7291
  const flushImmediate = () => {
7292
+ const cachedRoot = inkRootRef.current;
7293
+ if (typeof cachedRoot?.onImmediateRender === "function") {
7294
+ cachedRoot.onImmediateRender();
7295
+ return;
7296
+ }
7291
7297
  let node = boxRef.current;
7292
7298
  for (let i = 0; node && i < 64; i++) {
7293
7299
  if (node.nodeName === "ink-root") {
7300
+ inkRootRef.current = node;
7294
7301
  if (typeof node.onImmediateRender === "function") node.onImmediateRender();
7295
7302
  return;
7296
7303
  }
@@ -11289,18 +11296,22 @@ function trimPartialClosingFences(tokens) {
11289
11296
  }
11290
11297
  var OPEN_FENCE_RE = /^( {0,3})(`{3,}|~{3,})(.*)$/;
11291
11298
  var CLOSE_FENCE_RE = /^ {0,3}([`~]+)[ \t]*$/;
11299
+ var OPEN_FENCE_SCAN_LRU_MAX = 32;
11300
+ var openFenceScanByStreamKey = /* @__PURE__ */ new Map();
11292
11301
  function isClosingFence(line, char, openLen) {
11293
11302
  const m = CLOSE_FENCE_RE.exec(line);
11294
11303
  if (!m) return false;
11295
11304
  const run = m[1];
11296
11305
  return run.startsWith(char.repeat(openLen));
11297
11306
  }
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++) {
11307
+ function scanOpenFence(value, startAt = 0, initialOpen = null) {
11308
+ let open = initialOpen;
11309
+ let start = startAt;
11310
+ const checkpointIndex = value.lastIndexOf("\n") + 1;
11311
+ let openBeforeCheckpoint = start === checkpointIndex ? open : null;
11312
+ for (let i = startAt; i <= value.length; i++) {
11303
11313
  if (i !== value.length && value[i] !== "\n") continue;
11314
+ if (start === checkpointIndex) openBeforeCheckpoint = open;
11304
11315
  const line = value.slice(start, i);
11305
11316
  if (!open) {
11306
11317
  const m = OPEN_FENCE_RE.exec(line);
@@ -11316,13 +11327,42 @@ function findOpenFenceStart(text) {
11316
11327
  }
11317
11328
  start = i + 1;
11318
11329
  }
11319
- if (!open || open.indent !== 0) return null;
11320
- return { index: open.index, lang: open.lang };
11330
+ return { open, checkpointIndex, openBeforeCheckpoint };
11331
+ }
11332
+ function touchOpenFenceScan(key, entry) {
11333
+ if (openFenceScanByStreamKey.has(key)) openFenceScanByStreamKey.delete(key);
11334
+ openFenceScanByStreamKey.set(key, entry);
11335
+ while (openFenceScanByStreamKey.size > OPEN_FENCE_SCAN_LRU_MAX) {
11336
+ const oldest = openFenceScanByStreamKey.keys().next().value;
11337
+ if (oldest === void 0) break;
11338
+ openFenceScanByStreamKey.delete(oldest);
11339
+ }
11340
+ }
11341
+ function resetOpenFenceScan(streamKey) {
11342
+ if (streamKey == null || streamKey === "") return;
11343
+ openFenceScanByStreamKey.delete(String(streamKey));
11344
+ }
11345
+ function resetAllOpenFenceScans() {
11346
+ openFenceScanByStreamKey.clear();
11347
+ }
11348
+ function findOpenFenceStart(text, streamKey = null) {
11349
+ const value = String(text ?? "");
11350
+ const key = streamKey == null || streamKey === "" ? null : String(streamKey);
11351
+ const cached = key ? openFenceScanByStreamKey.get(key) : null;
11352
+ if (cached?.text === value) return cached.result;
11353
+ const scanned = cached && value.startsWith(cached.text) ? scanOpenFence(value, cached.checkpointIndex, cached.openBeforeCheckpoint) : scanOpenFence(value);
11354
+ const result = !scanned.open || scanned.open.indent !== 0 ? null : { index: scanned.open.index, lang: scanned.open.lang };
11355
+ if (key) touchOpenFenceScan(key, { text: value, ...scanned, result });
11356
+ return result;
11321
11357
  }
11322
11358
 
11323
11359
  // src/tui/markdown/render-ansi.mjs
11324
11360
  var TOKEN_CACHE_MAX = 500;
11325
11361
  var tokenCache = /* @__PURE__ */ new Map();
11362
+ var renderedSegmentCache = [];
11363
+ var RENDERED_SEGMENT_CACHE_MAX = 12;
11364
+ var RENDERED_SEGMENT_CACHE_MAX_CHARS = 256 * 1024;
11365
+ var renderedSegmentCacheChars = 0;
11326
11366
  var MD_SYNTAX_RE = /[#*`|[>\-_~]|\n\n|^\d+\. |\n\d+\. /;
11327
11367
  var _configured = false;
11328
11368
  function configureMarked() {
@@ -11384,8 +11424,19 @@ function lexMarkdown(content, { trimPartialFences = false } = {}) {
11384
11424
  return tokens;
11385
11425
  }
11386
11426
  function renderTokenAnsiSegments(content, opts = {}) {
11387
- const tokens = lexMarkdown(content, opts);
11427
+ const text = String(content ?? "");
11388
11428
  const width = Number(opts.width) || 0;
11429
+ const trimPartialFences = opts.trimPartialFences === true;
11430
+ const themeVersion = getThemeVersion();
11431
+ for (let index = renderedSegmentCache.length - 1; index >= 0; index -= 1) {
11432
+ const entry = renderedSegmentCache[index];
11433
+ if (entry.text === text && entry.width === width && entry.trimPartialFences === trimPartialFences && entry.themeVersion === themeVersion) {
11434
+ renderedSegmentCache.splice(index, 1);
11435
+ renderedSegmentCache.push(entry);
11436
+ return entry.segments;
11437
+ }
11438
+ }
11439
+ const tokens = lexMarkdown(text, opts);
11389
11440
  const segments = [];
11390
11441
  for (const token of tokens) {
11391
11442
  if (token.type === "table") {
@@ -11398,55 +11449,104 @@ function renderTokenAnsiSegments(content, opts = {}) {
11398
11449
  segments.push({ type: "ansi", ansi, token });
11399
11450
  }
11400
11451
  }
11452
+ if (text.length <= RENDERED_SEGMENT_CACHE_MAX_CHARS) {
11453
+ const entry = { text, width, trimPartialFences, themeVersion, segments };
11454
+ renderedSegmentCache.push(entry);
11455
+ renderedSegmentCacheChars += text.length;
11456
+ while (renderedSegmentCache.length > RENDERED_SEGMENT_CACHE_MAX || renderedSegmentCacheChars > RENDERED_SEGMENT_CACHE_MAX_CHARS) {
11457
+ const removed = renderedSegmentCache.shift();
11458
+ renderedSegmentCacheChars -= removed?.text.length || 0;
11459
+ }
11460
+ }
11401
11461
  return segments;
11402
11462
  }
11403
11463
 
11404
11464
  // src/tui/markdown/streaming-markdown.mjs
11405
11465
  import { marked as marked2 } from "marked";
11406
11466
  var stablePrefixByStreamKey = /* @__PURE__ */ new Map();
11467
+ var markdownSyntaxByStreamKey = /* @__PURE__ */ new Map();
11468
+ var plainWindowSyntaxByStreamKey = /* @__PURE__ */ new Map();
11407
11469
  var resolvedPartsByStreamKey = /* @__PURE__ */ new Map();
11408
11470
  var STABLE_PREFIX_LRU_MAX = 32;
11409
11471
  function streamingLayoutText(text) {
11410
11472
  return String(text ?? "").replace(/^\n+|\n+$/g, "");
11411
11473
  }
11412
- function windowPlainStreamingText(text, columns, maxRows) {
11474
+ function windowPlainStreamingText(text, columns, maxRows, streamKey = null) {
11413
11475
  const value = streamingLayoutText(text);
11414
11476
  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");
11477
+ const key = streamKey == null || streamKey === "" ? null : String(streamKey);
11478
+ if (!value || rowBudget <= 0 || cachedStreamingHasMarkdownSyntax(
11479
+ plainWindowSyntaxByStreamKey,
11480
+ value,
11481
+ key
11482
+ )) return value;
11417
11483
  const width = Math.max(1, Math.floor(Number(columns) || 80));
11418
11484
  let rows = 0;
11419
- let start = lines.length;
11420
- while (start > 0) {
11421
- const line = lines[start - 1];
11485
+ let end = value.length;
11486
+ let start = end;
11487
+ while (end >= 0) {
11488
+ const newline = value.lastIndexOf("\n", end - 1);
11489
+ const lineStart2 = newline + 1;
11490
+ const line = value.slice(lineStart2, end);
11422
11491
  const lineRows = Math.max(1, Math.ceil(displayWidth(line) / width));
11423
11492
  if (rows > 0 && rows + lineRows > rowBudget) break;
11424
11493
  rows += lineRows;
11425
- start -= 1;
11426
- if (rows >= rowBudget) break;
11494
+ start = lineStart2;
11495
+ if (rows >= rowBudget || newline < 0) break;
11496
+ end = newline;
11427
11497
  }
11428
- return start > 0 ? lines.slice(start).join("\n") : value;
11498
+ return start > 0 ? value.slice(start) : value;
11429
11499
  }
11430
11500
  function isWhitespaceOnlyText(text) {
11431
11501
  return !String(text ?? "").trim();
11432
11502
  }
11433
- function touchStablePrefixKey(key, value) {
11503
+ function touchLruKey(cache, key, value) {
11434
11504
  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;
11505
+ if (cache.has(key)) cache.delete(key);
11506
+ cache.set(key, value);
11507
+ while (cache.size > STABLE_PREFIX_LRU_MAX) {
11508
+ const oldest = cache.keys().next().value;
11439
11509
  if (oldest === void 0) break;
11440
- stablePrefixByStreamKey.delete(oldest);
11510
+ cache.delete(oldest);
11441
11511
  }
11442
11512
  }
11513
+ function touchStablePrefixKey(key, value) {
11514
+ touchLruKey(stablePrefixByStreamKey, key, value);
11515
+ }
11443
11516
  function getStablePrefixKey(key) {
11444
- if (!key || !stablePrefixByStreamKey.has(key)) return "";
11517
+ if (!key || !stablePrefixByStreamKey.has(key)) return { text: "", chunks: [] };
11445
11518
  const value = stablePrefixByStreamKey.get(key);
11446
- stablePrefixByStreamKey.delete(key);
11447
- stablePrefixByStreamKey.set(key, value);
11519
+ touchStablePrefixKey(key, value);
11520
+ return value;
11521
+ }
11522
+ function stableStateForText(text, previous) {
11523
+ if (!text) return { text: "", chunks: [] };
11524
+ if (text.startsWith(previous.text)) {
11525
+ const appended = text.substring(previous.text.length);
11526
+ return appended ? { text, chunks: [...previous.chunks, appended] } : previous;
11527
+ }
11528
+ return { text, chunks: [text] };
11529
+ }
11530
+ function cachedStreamingHasMarkdownSyntax(cache, text, key) {
11531
+ if (!key) return hasMarkdownSyntax(text);
11532
+ const previous = cache.get(key);
11533
+ let value;
11534
+ if (previous && text.startsWith(previous.text)) {
11535
+ if (previous.value) {
11536
+ value = true;
11537
+ } else {
11538
+ const lineStart2 = previous.text.lastIndexOf("\n") + 1;
11539
+ value = hasMarkdownSyntax(text.substring(Math.max(0, lineStart2 - 1)));
11540
+ }
11541
+ } else {
11542
+ value = hasMarkdownSyntax(text);
11543
+ }
11544
+ touchLruKey(cache, key, { text, value });
11448
11545
  return value;
11449
11546
  }
11547
+ function streamingHasMarkdownSyntax(text, key) {
11548
+ return cachedStreamingHasMarkdownSyntax(markdownSyntaxByStreamKey, text, key);
11549
+ }
11450
11550
  function getResolvedPartsKey(key, text) {
11451
11551
  if (!key) return null;
11452
11552
  const entry = resolvedPartsByStreamKey.get(key);
@@ -11518,11 +11618,17 @@ function resetStreamingMarkdownStablePrefix(streamKey) {
11518
11618
  if (streamKey == null || streamKey === "") return;
11519
11619
  const key = String(streamKey);
11520
11620
  stablePrefixByStreamKey.delete(key);
11621
+ markdownSyntaxByStreamKey.delete(key);
11622
+ plainWindowSyntaxByStreamKey.delete(key);
11521
11623
  resolvedPartsByStreamKey.delete(key);
11624
+ resetOpenFenceScan(key);
11522
11625
  }
11523
11626
  function resetAllStreamingMarkdownStablePrefixes() {
11524
11627
  stablePrefixByStreamKey.clear();
11628
+ markdownSyntaxByStreamKey.clear();
11629
+ plainWindowSyntaxByStreamKey.clear();
11525
11630
  resolvedPartsByStreamKey.clear();
11631
+ resetAllOpenFenceScans();
11526
11632
  }
11527
11633
  function resolveStreamingMarkdownParts(text, streamKey) {
11528
11634
  const t = streamingLayoutText(text);
@@ -11534,34 +11640,39 @@ function resolveStreamingMarkdownParts(text, streamKey) {
11534
11640
  return cacheResolvedPartsKey(key, t, {
11535
11641
  plain: true,
11536
11642
  stablePrefix: "",
11643
+ stableChunks: [],
11537
11644
  unstableSuffix: "",
11538
11645
  unstableForRender: ""
11539
11646
  });
11540
11647
  }
11541
- if (!hasMarkdownSyntax(t)) {
11648
+ if (!streamingHasMarkdownSyntax(t, key)) {
11542
11649
  if (key) stablePrefixByStreamKey.delete(key);
11543
11650
  return cacheResolvedPartsKey(key, t, {
11544
11651
  plain: true,
11545
11652
  stablePrefix: "",
11653
+ stableChunks: [],
11546
11654
  unstableSuffix: t,
11547
11655
  unstableForRender: t
11548
11656
  });
11549
11657
  }
11550
- let stablePrefix = key ? getStablePrefixKey(key) : "";
11551
- if (!t.startsWith(stablePrefix)) {
11552
- stablePrefix = "";
11658
+ let stableState = key ? getStablePrefixKey(key) : { text: "", chunks: [] };
11659
+ if (!t.startsWith(stableState.text)) {
11660
+ stableState = { text: "", chunks: [] };
11553
11661
  }
11554
- const open = findOpenFenceStart(t);
11662
+ let stablePrefix = stableState.text;
11663
+ const open = findOpenFenceStart(t, key);
11555
11664
  if (open) {
11556
11665
  let openPrefix = t.substring(0, open.index);
11557
11666
  if (isWhitespaceOnlyText(openPrefix)) openPrefix = "";
11558
- if (key && openPrefix) touchStablePrefixKey(key, openPrefix);
11667
+ stableState = stableStateForText(openPrefix, stableState);
11668
+ if (key && openPrefix) touchStablePrefixKey(key, stableState);
11559
11669
  else if (key) stablePrefixByStreamKey.delete(key);
11560
11670
  const unstableSuffix2 = t.substring(openPrefix.length);
11561
11671
  return cacheResolvedPartsKey(key, t, {
11562
11672
  plain: false,
11563
11673
  openFence: true,
11564
11674
  stablePrefix: openPrefix,
11675
+ stableChunks: stableState.chunks,
11565
11676
  unstableSuffix: unstableSuffix2,
11566
11677
  unstableForRender: unstableSuffix2
11567
11678
  });
@@ -11584,18 +11695,24 @@ function resolveStreamingMarkdownParts(text, streamKey) {
11584
11695
  if (advance > 0) {
11585
11696
  stablePrefix = t.substring(0, boundary + advance);
11586
11697
  if (isWhitespaceOnlyText(stablePrefix)) stablePrefix = "";
11587
- if (key && stablePrefix) touchStablePrefixKey(key, stablePrefix);
11698
+ stableState = stableStateForText(stablePrefix, stableState);
11699
+ if (key && stablePrefix) touchStablePrefixKey(key, stableState);
11588
11700
  else if (key && !stablePrefix) stablePrefixByStreamKey.delete(key);
11589
11701
  }
11590
11702
  } catch {
11591
11703
  stablePrefix = "";
11704
+ stableState = { text: "", chunks: [] };
11592
11705
  if (key) stablePrefixByStreamKey.delete(key);
11593
11706
  }
11594
- if (isWhitespaceOnlyText(stablePrefix)) stablePrefix = "";
11707
+ if (isWhitespaceOnlyText(stablePrefix)) {
11708
+ stablePrefix = "";
11709
+ stableState = { text: "", chunks: [] };
11710
+ }
11595
11711
  const unstableSuffix = t.substring(stablePrefix.length);
11596
11712
  return cacheResolvedPartsKey(key, t, {
11597
11713
  plain: false,
11598
11714
  stablePrefix,
11715
+ stableChunks: stableState.chunks,
11599
11716
  unstableSuffix,
11600
11717
  unstableForRender: balanceStreamingMarkdown(unstableSuffix)
11601
11718
  });
@@ -11721,22 +11838,31 @@ function measureStreamingMarkdownRenderedRows(text, columns, streamKey) {
11721
11838
  let rows = 0;
11722
11839
  let childCount = 0;
11723
11840
  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
- }
11841
+ const stableChunks = parts.stableChunks?.length ? parts.stableChunks : parts.stablePrefix ? [parts.stablePrefix] : [];
11842
+ 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]);
11843
+ let measuredStableChunks = 0;
11844
+ if (reusableChunks) {
11845
+ stableRows = cached.stableRows;
11846
+ measuredStableChunks = cached.stableChunks.length;
11847
+ }
11848
+ for (let index = measuredStableChunks; index < stableChunks.length; index += 1) {
11849
+ if (index > 0) stableRows += 1;
11850
+ stableRows += measureMarkdownRenderedRows(stableChunks[index], columns, { trimPartialFences: false });
11851
+ }
11852
+ rows += stableRows;
11853
+ childCount = stableChunks.length;
11729
11854
  if (parts.unstableSuffix) {
11855
+ if (childCount > 0) rows += 1;
11730
11856
  rows += measureMarkdownRenderedRows(parts.unstableForRender, columns, { trimPartialFences: true });
11731
11857
  childCount += 1;
11732
11858
  }
11733
- if (childCount === 2) rows += 1;
11734
11859
  const measuredRows = childCount === 0 ? 1 : Math.max(1, rows);
11735
11860
  cacheStreamingRows(key, {
11736
11861
  text: value,
11737
11862
  columns,
11738
11863
  mode: "markdown",
11739
11864
  stablePrefix: parts.stablePrefix,
11865
+ stableChunks: stableChunks.slice(),
11740
11866
  stableRows,
11741
11867
  rows: measuredRows
11742
11868
  });
@@ -12818,6 +12944,9 @@ function cacheStreamingTailEstimate(id, entry) {
12818
12944
  function hasStreamingRowStateToPrune() {
12819
12945
  return streamingMeasuredRowsById.size > 0 || streamingEstimateHighWaterById.size > 0 || streamingTailEstimateById.size > 0;
12820
12946
  }
12947
+ function transcriptHarvestInputsEqual(left, right) {
12948
+ 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;
12949
+ }
12821
12950
  function pruneStreamingMeasuredRowsById(liveIds) {
12822
12951
  if (!liveIds) return;
12823
12952
  if (streamingMeasuredRowsById.size > 0) {
@@ -13632,13 +13761,30 @@ import { useCallback as useCallback3, useEffect as useEffect8, useRef as useRef8
13632
13761
  // src/tui/engine/render-timing.mjs
13633
13762
  var RENDER_ACK_HANG_GUARD_MS = 250;
13634
13763
  var RENDER_SETTLE_IDLE_MS = 64;
13764
+ var TUI_RENDER_FPS = 60;
13765
+ var TUI_FRAME_MS = Math.ceil(1e3 / TUI_RENDER_FPS);
13635
13766
  var pendingRenderAcks = [];
13636
13767
  var renderAckSeq = 0;
13768
+ var lastRenderFrameAt = 0;
13769
+ var renderFrameDelay = (lastFrameAt, currentTime, frameMs = TUI_FRAME_MS) => {
13770
+ if (!(lastFrameAt > 0)) return frameMs;
13771
+ return Math.max(0, frameMs - Math.max(0, currentTime - lastFrameAt));
13772
+ };
13773
+ var scheduleRenderAlignedStoreFlush = (callback, frameMs = TUI_FRAME_MS) => {
13774
+ const timer2 = setTimeout(
13775
+ callback,
13776
+ renderFrameDelay(lastRenderFrameAt, performance.now(), frameMs)
13777
+ );
13778
+ timer2.unref?.();
13779
+ return timer2;
13780
+ };
13781
+ var cancelRenderAlignedStoreFlush = (timer2) => clearTimeout(timer2);
13637
13782
  var scheduleRenderFrameAck = () => {
13638
13783
  const seq = ++renderAckSeq;
13639
13784
  setImmediate(() => notifyRenderFrame(seq));
13640
13785
  };
13641
13786
  var notifyRenderFrame = (seq = ++renderAckSeq) => {
13787
+ lastRenderFrameAt = performance.now();
13642
13788
  if (pendingRenderAcks.length === 0) return;
13643
13789
  const acks = pendingRenderAcks;
13644
13790
  pendingRenderAcks = [];
@@ -14285,6 +14431,11 @@ function useTranscriptWindow({
14285
14431
  const prevViewportGeomRef = useRef9({ contentHeight: 0, floatingPanelRows: 0 });
14286
14432
  const transcriptItemElsRef = useRef9(/* @__PURE__ */ new Map());
14287
14433
  const transcriptMeasureRefCache = useRef9(/* @__PURE__ */ new Map());
14434
+ const harvestGateRef = useRef9({
14435
+ inputs: null,
14436
+ skippedForDrag: false,
14437
+ forceNext: false
14438
+ });
14288
14439
  const transcriptMeasureItemsRef = useRef9(/* @__PURE__ */ new Map());
14289
14440
  const transcriptMeasureRef = useCallback4((item) => {
14290
14441
  if (!TRANSCRIPT_MEASURED_ROWS || !item || item.id == null) return void 0;
@@ -14516,11 +14667,33 @@ function useTranscriptWindow({
14516
14667
  const transcriptTailPinned = Math.max(0, Number(transcriptWindow.effectiveScrollOffset) || 0) === 0;
14517
14668
  const overlayHintOnLastItem = overlayHintRequested && floatingPanelRows <= 0 && transcriptWindow.bottomSpacerRows === 0 && transcriptTailPinned && overlayHintAttachItemIndex >= 0;
14518
14669
  const overlayHintFallbackRow = overlayHintRequested && floatingPanelRows <= 0 && transcriptGuardRows > 0 && !overlayHintOnLastItem;
14670
+ const harvestInputs = {
14671
+ revision,
14672
+ settledItems,
14673
+ streamingTailItem,
14674
+ startIndex: transcriptWindow.startIndex,
14675
+ endIndex: transcriptWindow.endIndex,
14676
+ frameColumns,
14677
+ toolOutputExpanded,
14678
+ transcriptContentHeight,
14679
+ floatingPanelRows,
14680
+ overlayHintRequested,
14681
+ transcriptGuardRows,
14682
+ themeEpoch
14683
+ };
14519
14684
  useLayoutEffect3(() => {
14520
14685
  if (!TRANSCRIPT_MEASURED_ROWS) return;
14521
- if (dragRef.current.active) return;
14686
+ const gate = harvestGateRef.current;
14687
+ if (dragRef.current.active) {
14688
+ gate.skippedForDrag = true;
14689
+ return;
14690
+ }
14691
+ if (!gate.skippedForDrag && !gate.forceNext && transcriptHarvestInputsEqual(gate.inputs, harvestInputs)) return;
14522
14692
  const els = transcriptItemElsRef.current;
14523
14693
  if (!els || els.size === 0) return;
14694
+ gate.inputs = harvestInputs;
14695
+ gate.skippedForDrag = false;
14696
+ gate.forceNext = false;
14524
14697
  const liveItems = transcriptMeasureItemsRef.current;
14525
14698
  const toolExpandedFlag = toolOutputExpanded ? 1 : 0;
14526
14699
  let changed = false;
@@ -14595,6 +14768,7 @@ function useTranscriptWindow({
14595
14768
  }, 0);
14596
14769
  if (typeof streak.timer?.unref === "function") streak.timer.unref();
14597
14770
  }
14771
+ gate.forceNext = true;
14598
14772
  setMeasuredRowsVersion((v) => (v + 1) % 1e6);
14599
14773
  }
14600
14774
  }
@@ -20156,13 +20330,29 @@ function Markdown({ children, themeEpoch = 0, trimPartialFences = false, columns
20156
20330
  }, [children, themeEpoch, trimPartialFences, columns]);
20157
20331
  return /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", gap: 1, children: elements });
20158
20332
  }
20333
+ var StableMarkdownChunk = React13.memo(function StableMarkdownChunk2({
20334
+ text,
20335
+ themeEpoch,
20336
+ columns
20337
+ }) {
20338
+ return /* @__PURE__ */ jsx13(Markdown, { themeEpoch, columns, children: text });
20339
+ });
20159
20340
  function StreamingMarkdown({ children, themeEpoch = 0, columns, streamKey }) {
20160
20341
  const parts = resolveStreamingMarkdownParts(children, streamKey);
20161
20342
  if (parts.plain) {
20162
20343
  return /* @__PURE__ */ jsx13(Text13, { color: theme.text, wrap: "wrap", children: parts.unstableForRender });
20163
20344
  }
20345
+ const stableChunks = parts.stableChunks?.length ? parts.stableChunks : parts.stablePrefix ? [parts.stablePrefix] : [];
20164
20346
  return /* @__PURE__ */ jsxs9(Box11, { flexDirection: "column", gap: 1, children: [
20165
- parts.stablePrefix ? /* @__PURE__ */ jsx13(Markdown, { themeEpoch, columns, children: parts.stablePrefix }) : null,
20347
+ stableChunks.map((text, index) => /* @__PURE__ */ jsx13(
20348
+ StableMarkdownChunk,
20349
+ {
20350
+ text,
20351
+ themeEpoch,
20352
+ columns
20353
+ },
20354
+ `stable-${index}`
20355
+ )),
20166
20356
  parts.unstableSuffix ? /* @__PURE__ */ jsx13(Markdown, { themeEpoch, columns, trimPartialFences: true, children: parts.unstableForRender }) : null
20167
20357
  ] });
20168
20358
  }
@@ -20181,7 +20371,7 @@ var AssistantMessage = React14.memo(function AssistantMessage2({
20181
20371
  if (!streaming && assistantId) resetStreamingMarkdownStablePrefix(assistantId);
20182
20372
  }, [streaming, assistantId]);
20183
20373
  const bodyWidth = assistantBodyWidth(columns);
20184
- const renderText = streaming && streamingWindowRows > 0 ? windowPlainStreamingText(text, bodyWidth, streamingWindowRows) : text;
20374
+ const renderText = streaming && streamingWindowRows > 0 ? windowPlainStreamingText(text, bodyWidth, streamingWindowRows, assistantId) : text;
20185
20375
  return /* @__PURE__ */ jsxs10(Box12, { flexDirection: "row", marginTop: 1, children: [
20186
20376
  /* @__PURE__ */ jsx14(Box12, { flexShrink: 0, minWidth: 2, children: /* @__PURE__ */ jsx14(Text14, { color: theme.text, children: TURN_MARKER }) }),
20187
20377
  /* @__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 }) })
@@ -23633,7 +23823,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
23633
23823
  }
23634
23824
 
23635
23825
  // src/tui/engine.mjs
23636
- import { performance as performance2 } from "node:perf_hooks";
23826
+ import { performance as performance3 } from "node:perf_hooks";
23637
23827
  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
23828
  import { basename as basename7, dirname as dirname11, join as join14 } from "node:path";
23639
23829
 
@@ -23916,12 +24106,12 @@ function sessionPath(id) {
23916
24106
  }
23917
24107
 
23918
24108
  // src/tui/engine/boot-profile.mjs
23919
- import { performance } from "node:perf_hooks";
24109
+ import { performance as performance2 } from "node:perf_hooks";
23920
24110
  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());
24111
+ var BOOT_PROFILE_START = globalThis.__mixdogBootProfileStart || (globalThis.__mixdogBootProfileStart = performance2.now());
23922
24112
  function bootProfile(event, fields = {}) {
23923
24113
  if (!BOOT_PROFILE_ENABLED) return;
23924
- const elapsedMs = performance.now() - BOOT_PROFILE_START;
24114
+ const elapsedMs = performance2.now() - BOOT_PROFILE_START;
23925
24115
  const parts = [`[mixdog-boot] +${elapsedMs.toFixed(1)}ms`, `tui:${event}`];
23926
24116
  for (const [key, value] of Object.entries(fields || {})) {
23927
24117
  if (value === void 0 || value === null || value === "") continue;
@@ -25611,6 +25801,7 @@ import { randomBytes as randomBytes3 } from "crypto";
25611
25801
  import { join as join10 } from "path";
25612
25802
  var PENDING_MESSAGES_FILE = "session-pending-messages.json";
25613
25803
  var PENDING_MESSAGES_MODE = 384;
25804
+ var STALE_STEERING_RESTORE_TTL_MS = 30 * 60 * 1e3;
25614
25805
  var _persistChain = Promise.resolve();
25615
25806
  function _serialize(task) {
25616
25807
  const run = _persistChain.then(task, task);
@@ -25637,7 +25828,9 @@ function normalizeTuiSteeringQueueEntry(entry) {
25637
25828
  if (typeof entry.text === "string" && entry.text.trim()) {
25638
25829
  const text = entry.text.trim();
25639
25830
  const id = typeof entry.id === "string" && entry.id.trim() ? entry.id.trim() : null;
25640
- return id ? { id, text } : text;
25831
+ if (!id) return text;
25832
+ const at = Number(entry.at);
25833
+ return Number.isFinite(at) && at > 0 ? { id, text, at } : { id, text };
25641
25834
  }
25642
25835
  return null;
25643
25836
  }
@@ -25689,7 +25882,7 @@ function appendTuiSteeringPersist(leadSessionId, entry) {
25689
25882
  const key = tuiSteeringSessionKey(leadSessionId);
25690
25883
  if (!key) return Promise.resolve();
25691
25884
  if (!entry.steeringPersistId) entry.steeringPersistId = newSteeringPersistId();
25692
- const record = { id: entry.steeringPersistId, text };
25885
+ const record = { id: entry.steeringPersistId, text, at: Date.now() };
25693
25886
  return _serialize(async () => {
25694
25887
  try {
25695
25888
  await updateJsonAtomic(pendingMessagesPath(), (raw) => {
@@ -25761,12 +25954,21 @@ function drainTuiSteeringPersist(leadSessionId) {
25761
25954
  if (!key) return Promise.resolve([]);
25762
25955
  return _serialize(async () => {
25763
25956
  let drained = [];
25957
+ let droppedStale = 0;
25764
25958
  try {
25765
25959
  await updateJsonAtomic(pendingMessagesPath(), (raw) => {
25766
25960
  const next = normalizePendingStore(raw);
25767
25961
  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;
25962
+ const now = Date.now();
25963
+ const touchedAt = Number(next.sessionTouchedAt?.[key]) || 0;
25964
+ const fresh = q.filter((row) => {
25965
+ const at = Number(row?.at) || touchedAt;
25966
+ const stale = at > 0 && now - at > STALE_STEERING_RESTORE_TTL_MS;
25967
+ if (stale) droppedStale += 1;
25968
+ return !stale;
25969
+ });
25970
+ drained = fresh.map(drainedRowToRestore).filter(Boolean);
25971
+ if (drained.length === 0 && droppedStale === 0) return void 0;
25770
25972
  delete next.sessions[key];
25771
25973
  if (next.sessionTouchedAt) delete next.sessionTouchedAt[key];
25772
25974
  next.updatedAt = Date.now();
@@ -25775,6 +25977,13 @@ function drainTuiSteeringPersist(leadSessionId) {
25775
25977
  } catch (err) {
25776
25978
  try {
25777
25979
  process.stderr.write(`[tui] steering-queue drain failed sessionId=${leadSessionId}: ${err?.message || err}
25980
+ `);
25981
+ } catch {
25982
+ }
25983
+ }
25984
+ if (droppedStale > 0) {
25985
+ try {
25986
+ process.stderr.write(`[tui] dropped ${droppedStale} stale steering row(s) sessionId=${leadSessionId}
25778
25987
  `);
25779
25988
  } catch {
25780
25989
  }
@@ -28276,6 +28485,17 @@ function createEngineApiB(bag) {
28276
28485
  listSessions: (options) => {
28277
28486
  return runtime.listSessions(options);
28278
28487
  },
28488
+ // Desktop sidebar watcher hook: EngineHost fs.watches this directory so
28489
+ // heartbeat sidecar create/delete pushes instant working/dot updates.
28490
+ // Without it the watcher silently no-ops and the sidebar falls back to
28491
+ // the 60s safety poll (user: spinner kept spinning after the turn ended).
28492
+ sessionStoreDir: () => {
28493
+ try {
28494
+ return runtime.sessionStoreDir?.() || null;
28495
+ } catch {
28496
+ return null;
28497
+ }
28498
+ },
28279
28499
  deleteSession: async (id) => {
28280
28500
  if (getState().commandBusy) return false;
28281
28501
  const deletingCurrent = String(runtime.session?.id || getState().sessionId || "") === String(id || "");
@@ -29247,7 +29467,9 @@ function createFrameBatchedStorePublisher({
29247
29467
  frameMs = 16,
29248
29468
  setTimer = setTimeout,
29249
29469
  clearTimer = clearTimeout,
29250
- enqueueMicrotask = queueMicrotask
29470
+ enqueueMicrotask = queueMicrotask,
29471
+ scheduleFrame = (callback, delay2) => setTimer(callback, delay2),
29472
+ cancelFrame = (handle) => clearTimer(handle)
29251
29473
  }) {
29252
29474
  let timer2 = null;
29253
29475
  let emitPending = false;
@@ -29255,7 +29477,7 @@ function createFrameBatchedStorePublisher({
29255
29477
  let immediatePending = false;
29256
29478
  const flush = () => {
29257
29479
  if (timer2 !== null) {
29258
- clearTimer(timer2);
29480
+ cancelFrame(timer2);
29259
29481
  timer2 = null;
29260
29482
  }
29261
29483
  immediatePending = false;
@@ -29277,7 +29499,7 @@ function createFrameBatchedStorePublisher({
29277
29499
  const emit = () => {
29278
29500
  emitPending = true;
29279
29501
  if (timer2 !== null || isDisposed()) return;
29280
- timer2 = setTimer(flush, frameMs);
29502
+ timer2 = scheduleFrame(flush, frameMs);
29281
29503
  timer2?.unref?.();
29282
29504
  };
29283
29505
  const markStructureChange = () => {
@@ -29291,7 +29513,7 @@ function createFrameBatchedStorePublisher({
29291
29513
  };
29292
29514
  const dispose = () => {
29293
29515
  if (emitPending && !isDisposed()) flush();
29294
- else if (timer2 !== null) clearTimer(timer2);
29516
+ else if (timer2 !== null) cancelFrame(timer2);
29295
29517
  timer2 = null;
29296
29518
  emitPending = false;
29297
29519
  structureChangePending = false;
@@ -30313,16 +30535,16 @@ async function createEngineSession({
30313
30535
  cwd,
30314
30536
  desktopSession
30315
30537
  } = {}) {
30316
- const startedAt = performance2.now();
30538
+ const startedAt = performance3.now();
30317
30539
  bootProfile("engine:create:start", { provider: providerName, model, toolMode, remote });
30318
30540
  process.env.MIXDOG_QUIET_PROVIDER_LOG = "1";
30319
30541
  process.env.MIXDOG_QUIET_SESSION_LOG = "1";
30320
30542
  process.env.MIXDOG_QUIET_MCP_LOG = "1";
30321
30543
  process.env.MIXDOG_QUIET_MEMORY_LOG = "1";
30322
30544
  process.env.MIXDOG_PATCH_NATIVE_PREWARM ??= "0";
30323
- const importStartedAt = performance2.now();
30545
+ const importStartedAt = performance3.now();
30324
30546
  const { createMixdogSessionRuntime } = await import(SESSION_RUNTIME_MODULE);
30325
- bootProfile("session-runtime:imported", { ms: (performance2.now() - importStartedAt).toFixed(1) });
30547
+ bootProfile("session-runtime:imported", { ms: (performance3.now() - importStartedAt).toFixed(1) });
30326
30548
  const runtime = await createMixdogSessionRuntime({
30327
30549
  provider: providerName,
30328
30550
  model,
@@ -30331,9 +30553,9 @@ async function createEngineSession({
30331
30553
  ...cwd ? { cwd } : {},
30332
30554
  ...desktopSession ? { desktopSession } : {}
30333
30555
  });
30334
- bootProfile("engine:create:runtime-ready", { ms: (performance2.now() - startedAt).toFixed(1) });
30556
+ bootProfile("engine:create:runtime-ready", { ms: (performance3.now() - startedAt).toFixed(1) });
30335
30557
  const runtimeCwd = runtime.cwd || process.cwd();
30336
- const stateStartedAt = performance2.now();
30558
+ const stateStartedAt = performance3.now();
30337
30559
  const flags = {
30338
30560
  disposed: false,
30339
30561
  draining: false,
@@ -30409,11 +30631,11 @@ async function createEngineSession({
30409
30631
  cwd: runtimeCwd,
30410
30632
  themeEpoch: 0
30411
30633
  };
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();
30634
+ bootProfile("engine:route-state-ready", { ms: (performance3.now() - stateStartedAt).toFixed(1) });
30635
+ bootProfile("engine:state-ready", { ms: (performance3.now() - stateStartedAt).toFixed(1) });
30636
+ const contextStartedAt = performance3.now();
30415
30637
  syncContextStats({ allowEstimated: true });
30416
- bootProfile("engine:context-ready", { ms: (performance2.now() - contextStartedAt).toFixed(1) });
30638
+ bootProfile("engine:context-ready", { ms: (performance3.now() - contextStartedAt).toFixed(1) });
30417
30639
  const listeners = /* @__PURE__ */ new Set();
30418
30640
  let publishedState = process.env.NODE_ENV === "production" ? state : Object.freeze(state);
30419
30641
  state = { ...state, stats: { ...state.stats } };
@@ -30424,7 +30646,10 @@ async function createEngineSession({
30424
30646
  state = { ...next, stats: { ...next.stats } };
30425
30647
  },
30426
30648
  listeners,
30427
- isDisposed: () => flags.disposed
30649
+ isDisposed: () => flags.disposed,
30650
+ frameMs: TUI_FRAME_MS,
30651
+ scheduleFrame: scheduleRenderAlignedStoreFlush,
30652
+ cancelFrame: cancelRenderAlignedStoreFlush
30428
30653
  });
30429
30654
  const emit = publisher.emit;
30430
30655
  const flushEmit = publisher.flush;
@@ -31100,7 +31325,7 @@ var XTSHIFTESCAPE_ON = process.env.WT_SESSION ? "" : "\x1B[>1s";
31100
31325
  var MOUSE_TRACKING_ON2 = `\x1B[?1000h\x1B[?1002h\x1B[?1006h\x1B[?1007l${XTSHIFTESCAPE_ON}`;
31101
31326
  var ALT_SCROLL_RESTORE = "\x1B[?1007h";
31102
31327
  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());
31328
+ var BOOT_PROFILE_START2 = globalThis.__mixdogBootProfileStart || (globalThis.__mixdogBootProfileStart = performance4.now());
31104
31329
  var EXIT_WAIT_TIMEOUT_MS = positiveIntEnv2("MIXDOG_TUI_EXIT_WAIT_MS", 2500);
31105
31330
  var EXIT_HARD_DELAY_MS = positiveIntEnv2("MIXDOG_TUI_HARD_EXIT_DELAY_MS", 500);
31106
31331
  var EXIT_HARD_ENABLED = !/^(0|false|no|off)$/i.test(String(process.env.MIXDOG_TUI_HARD_EXIT || "1"));
@@ -31116,9 +31341,9 @@ function positiveIntEnv2(name, fallback) {
31116
31341
  function installTuiLoopProbe() {
31117
31342
  if (!LOOP_PROBE_ENABLED) return () => {
31118
31343
  };
31119
- let last = performance3.now();
31344
+ let last = performance4.now();
31120
31345
  const timer2 = setInterval(() => {
31121
- const now = performance3.now();
31346
+ const now = performance4.now();
31122
31347
  const drift = now - last - LOOP_PROBE_INTERVAL_MS;
31123
31348
  last = now;
31124
31349
  if (drift > LOOP_PROBE_DRIFT_THRESHOLD_MS) {
@@ -31143,7 +31368,7 @@ var PERF_STALL_THRESHOLD_MS = positiveIntEnv2("MIXDOG_TUI_PERF_STALL_MS", 80);
31143
31368
  var PERF_RENDER_GAP_MS = positiveIntEnv2("MIXDOG_TUI_PERF_RENDER_GAP_MS", 120);
31144
31369
  function perfLog(event, fields = {}) {
31145
31370
  if (!PERF_ENABLED) return;
31146
- const elapsedMs = performance3.now() - BOOT_PROFILE_START2;
31371
+ const elapsedMs = performance4.now() - BOOT_PROFILE_START2;
31147
31372
  const parts = [`[mixdog-perf] +${elapsedMs.toFixed(1)}ms`, event];
31148
31373
  for (const [key, value] of Object.entries(fields || {})) {
31149
31374
  if (value === void 0 || value === null || value === "") continue;
@@ -31158,7 +31383,7 @@ function perfLog(event, fields = {}) {
31158
31383
  function installTuiPerfProbe() {
31159
31384
  if (!PERF_ENABLED) return () => {
31160
31385
  };
31161
- let last = performance3.now();
31386
+ let last = performance4.now();
31162
31387
  let lastCpu = process.cpuUsage();
31163
31388
  perfLog("probe:start", {
31164
31389
  intervalMs: PERF_STALL_INTERVAL_MS,
@@ -31166,7 +31391,7 @@ function installTuiPerfProbe() {
31166
31391
  renderGapMs: PERF_RENDER_GAP_MS
31167
31392
  });
31168
31393
  const timer2 = setInterval(() => {
31169
- const now = performance3.now();
31394
+ const now = performance4.now();
31170
31395
  const elapsed = now - last;
31171
31396
  const lagMs = elapsed - PERF_STALL_INTERVAL_MS;
31172
31397
  const cpu = process.cpuUsage(lastCpu);
@@ -31204,7 +31429,7 @@ function makeRenderProfiler() {
31204
31429
  let lastFrameAt = 0;
31205
31430
  return ({ renderTime } = {}) => {
31206
31431
  ackRenderedFrame();
31207
- const now = performance3.now();
31432
+ const now = performance4.now();
31208
31433
  const ms = Number(renderTime) || 0;
31209
31434
  const gap = lastFrameAt ? now - lastFrameAt : 0;
31210
31435
  lastFrameAt = now;
@@ -31238,7 +31463,7 @@ function makeRenderProfiler() {
31238
31463
  }
31239
31464
  function bootProfile2(event, fields = {}) {
31240
31465
  if (!BOOT_PROFILE_ENABLED2) return;
31241
- const elapsedMs = performance3.now() - BOOT_PROFILE_START2;
31466
+ const elapsedMs = performance4.now() - BOOT_PROFILE_START2;
31242
31467
  const parts = [`[mixdog-boot] +${elapsedMs.toFixed(1)}ms`, `tui:${event}`];
31243
31468
  for (const [key, value] of Object.entries(fields || {})) {
31244
31469
  if (value === void 0 || value === null || value === "") continue;
@@ -31456,7 +31681,7 @@ function installTuiConsoleGuard() {
31456
31681
  };
31457
31682
  }
31458
31683
  async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {}) {
31459
- const startedAt = performance3.now();
31684
+ const startedAt = performance4.now();
31460
31685
  bootProfile2("run:start", { provider, model, toolMode, remote });
31461
31686
  if (!process.stdin.isTTY) {
31462
31687
  process.stderr.write(
@@ -31487,6 +31712,7 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
31487
31712
  process.stdout.write(`${TERMINAL_MODE_RESET_HIDDEN_CURSOR}\x1B[?1049h${TERMINAL_MODE_RESET_HIDDEN_CURSOR}\x1B[2J\x1B[H`);
31488
31713
  process.stdout.write("\x1B[5 q");
31489
31714
  process.on("exit", restoreTerminal);
31715
+ const storeOutcomePromise = createEngineSession({ provider, model, toolMode, remote }).then((store2) => ({ store: store2, error: null }), (error) => ({ store: null, error }));
31490
31716
  try {
31491
31717
  await loadThemeSettingFromConfig();
31492
31718
  } catch {
@@ -31496,8 +31722,10 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
31496
31722
  } };
31497
31723
  let store;
31498
31724
  try {
31499
- store = await createEngineSession({ provider, model, toolMode, remote });
31500
- bootProfile2("store:ready", { ms: (performance3.now() - startedAt).toFixed(1) });
31725
+ const outcome = await storeOutcomePromise;
31726
+ if (outcome.error) throw outcome.error;
31727
+ store = outcome.store;
31728
+ bootProfile2("store:ready", { ms: (performance4.now() - startedAt).toFixed(1) });
31501
31729
  } catch (error) {
31502
31730
  splash.stop();
31503
31731
  stopPerfProbe();
@@ -31562,8 +31790,8 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
31562
31790
  registerStdioDeath(process.stderr, "error", { requireCode: true });
31563
31791
  registerStdioDeath(process.stderr, "close");
31564
31792
  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) });
31793
+ const instance = render(/* @__PURE__ */ jsx21(App, { store, forceOnboarding: forceOnboarding === true }), { exitOnCtrlC: false, maxFps: TUI_RENDER_FPS, incrementalRendering: true, patchConsole: false, onRender: makeRenderProfiler() });
31794
+ bootProfile2("render:mounted", { ms: (performance4.now() - startedAt).toFixed(1) });
31567
31795
  const { waitUntilExit } = instance;
31568
31796
  if (mouseTracking && typeof instance.setSelection === "function") {
31569
31797
  store.setRenderSelection = instance.setSelection;