mixdog 0.9.64 → 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 (36) 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/runtime/shared/turn-snapshot.mjs +184 -0
  13. package/src/session-runtime/context-status.mjs +2 -1
  14. package/src/session-runtime/provider-request-tools.mjs +6 -0
  15. package/src/session-runtime/runtime-core.mjs +4 -0
  16. package/src/session-runtime/session-turn-api.mjs +7 -0
  17. package/src/session-runtime/tool-catalog-schema.mjs +5 -1
  18. package/src/tui/App.jsx +1 -1
  19. package/src/tui/app/transcript-window.mjs +16 -0
  20. package/src/tui/app/use-transcript-window.mjs +36 -1
  21. package/src/tui/components/Markdown.jsx +15 -1
  22. package/src/tui/components/Message.jsx +1 -1
  23. package/src/tui/components/PromptInput.jsx +7 -0
  24. package/src/tui/dist/index.mjs +431 -81
  25. package/src/tui/engine/frame-batched-store.mjs +5 -3
  26. package/src/tui/engine/live-share.mjs +100 -0
  27. package/src/tui/engine/render-timing.mjs +28 -0
  28. package/src/tui/engine/session-api-ext.mjs +10 -0
  29. package/src/tui/engine/tui-steering-persist.mjs +25 -4
  30. package/src/tui/engine.mjs +36 -1
  31. package/src/tui/index.jsx +11 -3
  32. package/src/tui/markdown/measure-rendered-rows.mjs +28 -16
  33. package/src/tui/markdown/render-ansi.mjs +34 -1
  34. package/src/tui/markdown/stream-fence.mjs +44 -7
  35. package/src/tui/markdown/streaming-markdown.mjs +95 -27
  36. 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);
11448
11520
  return value;
11449
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 });
11545
+ return value;
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 }) })
@@ -23604,7 +23794,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
23604
23794
  StatusLine,
23605
23795
  {
23606
23796
  sessionId: state.sessionId,
23607
- clientHostPid: state.clientHostPid,
23797
+ clientHostPid: state.ownerClientHostPid || state.clientHostPid,
23608
23798
  provider: state.provider,
23609
23799
  model: state.model,
23610
23800
  effort: state.effort,
@@ -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
  }
@@ -28049,6 +28258,9 @@ function createEngineApiB(bag) {
28049
28258
  getUsageDashboard: async (options = {}) => {
28050
28259
  return await runtime.getUsageDashboard?.(options);
28051
28260
  },
28261
+ getTurnReviewDiff: async () => {
28262
+ return await runtime.getTurnReviewDiff?.() ?? { supported: false, files: [], patch: "" };
28263
+ },
28052
28264
  getOnboardingStatus: () => {
28053
28265
  return runtime.getOnboardingStatus?.() || { completed: true, workflowRoutes: {} };
28054
28266
  },
@@ -28273,6 +28485,17 @@ function createEngineApiB(bag) {
28273
28485
  listSessions: (options) => {
28274
28486
  return runtime.listSessions(options);
28275
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
+ },
28276
28499
  deleteSession: async (id) => {
28277
28500
  if (getState().commandBusy) return false;
28278
28501
  const deletingCurrent = String(runtime.session?.id || getState().sessionId || "") === String(id || "");
@@ -29244,7 +29467,9 @@ function createFrameBatchedStorePublisher({
29244
29467
  frameMs = 16,
29245
29468
  setTimer = setTimeout,
29246
29469
  clearTimer = clearTimeout,
29247
- enqueueMicrotask = queueMicrotask
29470
+ enqueueMicrotask = queueMicrotask,
29471
+ scheduleFrame = (callback, delay2) => setTimer(callback, delay2),
29472
+ cancelFrame = (handle) => clearTimer(handle)
29248
29473
  }) {
29249
29474
  let timer2 = null;
29250
29475
  let emitPending = false;
@@ -29252,7 +29477,7 @@ function createFrameBatchedStorePublisher({
29252
29477
  let immediatePending = false;
29253
29478
  const flush = () => {
29254
29479
  if (timer2 !== null) {
29255
- clearTimer(timer2);
29480
+ cancelFrame(timer2);
29256
29481
  timer2 = null;
29257
29482
  }
29258
29483
  immediatePending = false;
@@ -29274,7 +29499,7 @@ function createFrameBatchedStorePublisher({
29274
29499
  const emit = () => {
29275
29500
  emitPending = true;
29276
29501
  if (timer2 !== null || isDisposed()) return;
29277
- timer2 = setTimer(flush, frameMs);
29502
+ timer2 = scheduleFrame(flush, frameMs);
29278
29503
  timer2?.unref?.();
29279
29504
  };
29280
29505
  const markStructureChange = () => {
@@ -29288,7 +29513,7 @@ function createFrameBatchedStorePublisher({
29288
29513
  };
29289
29514
  const dispose = () => {
29290
29515
  if (emitPending && !isDisposed()) flush();
29291
- else if (timer2 !== null) clearTimer(timer2);
29516
+ else if (timer2 !== null) cancelFrame(timer2);
29292
29517
  timer2 = null;
29293
29518
  emitPending = false;
29294
29519
  structureChangePending = false;
@@ -29341,6 +29566,7 @@ function createLiveShare({
29341
29566
  getPublishedState,
29342
29567
  listeners,
29343
29568
  onRemoteSubmit,
29569
+ onRemoteAbort,
29344
29570
  onOwnerClosed,
29345
29571
  viewerApply
29346
29572
  }) {
@@ -29351,6 +29577,7 @@ function createLiveShare({
29351
29577
  let lastItems = null;
29352
29578
  let lastTail = null;
29353
29579
  let lastSpinner = null;
29580
+ let lastLiveSig = "";
29354
29581
  const broadcast = (frame) => {
29355
29582
  if (sockets.size === 0) return;
29356
29583
  const line = frameLine(frame);
@@ -29361,12 +29588,50 @@ function createLiveShare({
29361
29588
  }
29362
29589
  }
29363
29590
  };
29591
+ const LIVE_STATS_KEYS = [
29592
+ "currentContextSource",
29593
+ "currentContextTokens",
29594
+ "currentEstimatedContextTokens",
29595
+ "currentContextUpdatedAt",
29596
+ "costUsd",
29597
+ "turns",
29598
+ "inputTokens",
29599
+ "outputTokens",
29600
+ "latestInputTokens",
29601
+ "latestPromptTokens",
29602
+ "contextTokens"
29603
+ ];
29604
+ const liveStateOf = (st) => {
29605
+ const stats = st.stats && typeof st.stats === "object" ? st.stats : {};
29606
+ const statsSubset = {};
29607
+ for (const key of LIVE_STATS_KEYS) {
29608
+ if (stats[key] !== void 0) statsSubset[key] = stats[key];
29609
+ }
29610
+ return {
29611
+ busy: st.busy === true,
29612
+ commandBusy: st.commandBusy === true,
29613
+ queued: (Array.isArray(st.queued) ? st.queued : []).map((entry) => ({
29614
+ id: entry?.id,
29615
+ text: String(entry?.displayText ?? entry?.text ?? entry?.message ?? "").slice(0, 2e3),
29616
+ ...entry?.enqueuedAt ? { enqueuedAt: entry.enqueuedAt } : {}
29617
+ })),
29618
+ activeToolSummary: st.activeToolSummary || null,
29619
+ agentWorkers: Array.isArray(st.agentWorkers) ? st.agentWorkers : [],
29620
+ agentJobs: Array.isArray(st.agentJobs) ? st.agentJobs : [],
29621
+ ownerClientHostPid: Number(st.clientHostPid) || process.pid,
29622
+ displayContextWindow: Number(st.displayContextWindow) || 0,
29623
+ compactBoundaryTokens: Number(st.compactBoundaryTokens) || 0,
29624
+ autoCompactTokenLimit: Number(st.autoCompactTokenLimit) || 0,
29625
+ stats: statsSubset
29626
+ };
29627
+ };
29364
29628
  const fullFrame = (st) => ({
29365
29629
  t: "full",
29366
29630
  sessionId: serverId,
29367
29631
  items: Array.isArray(st.items) ? st.items : [],
29368
29632
  tail: st.streamingTail || null,
29369
- spinner: st.spinner || null
29633
+ spinner: st.spinner || null,
29634
+ live: liveStateOf(st)
29370
29635
  });
29371
29636
  const onPublish = () => {
29372
29637
  const st = getPublishedState();
@@ -29374,6 +29639,7 @@ function createLiveShare({
29374
29639
  lastItems = st.items;
29375
29640
  lastTail = st.streamingTail;
29376
29641
  lastSpinner = st.spinner;
29642
+ lastLiveSig = "";
29377
29643
  return;
29378
29644
  }
29379
29645
  const frame = { t: "delta" };
@@ -29432,6 +29698,13 @@ function createLiveShare({
29432
29698
  lastSpinner = st.spinner;
29433
29699
  dirty = true;
29434
29700
  }
29701
+ const live = liveStateOf(st);
29702
+ const liveSig = JSON.stringify(live);
29703
+ if (liveSig !== lastLiveSig) {
29704
+ frame.live = live;
29705
+ lastLiveSig = liveSig;
29706
+ dirty = true;
29707
+ }
29435
29708
  if (dirty) broadcast(frame);
29436
29709
  };
29437
29710
  listeners.add(onPublish);
@@ -29480,6 +29753,8 @@ function createLiveShare({
29480
29753
  attachLineReader(socket, (frame) => {
29481
29754
  if (frame.t === "submit" && typeof frame.text === "string" && frame.text.trim()) {
29482
29755
  onRemoteSubmit(frame.text);
29756
+ } else if (frame.t === "abort") {
29757
+ onRemoteAbort?.();
29483
29758
  } else if (frame.t === "sync") {
29484
29759
  try {
29485
29760
  socket.write(frameLine(fullFrame(getPublishedState())));
@@ -29497,6 +29772,7 @@ function createLiveShare({
29497
29772
  lastItems = st.items;
29498
29773
  lastTail = st.streamingTail;
29499
29774
  lastSpinner = st.spinner;
29775
+ lastLiveSig = JSON.stringify(liveStateOf(st));
29500
29776
  socket.write(frameLine(fullFrame(st)));
29501
29777
  } catch {
29502
29778
  try {
@@ -29582,11 +29858,47 @@ function createLiveShare({
29582
29858
  if (exists) viewerApply.patchItem(item.id, item);
29583
29859
  else viewerApply.appendItems([item]);
29584
29860
  };
29861
+ const applyLiveState = (live) => {
29862
+ if (!live || typeof live !== "object") return;
29863
+ const patch = {
29864
+ busy: live.busy === true,
29865
+ commandBusy: live.commandBusy === true,
29866
+ queued: Array.isArray(live.queued) ? live.queued : [],
29867
+ activeToolSummary: live.activeToolSummary || null,
29868
+ agentWorkers: Array.isArray(live.agentWorkers) ? live.agentWorkers : [],
29869
+ agentJobs: Array.isArray(live.agentJobs) ? live.agentJobs : [],
29870
+ ownerClientHostPid: Number(live.ownerClientHostPid) || 0
29871
+ };
29872
+ for (const key of ["displayContextWindow", "compactBoundaryTokens", "autoCompactTokenLimit"]) {
29873
+ if (Number(live[key]) > 0) patch[key] = Number(live[key]);
29874
+ }
29875
+ if (live.stats && typeof live.stats === "object" && Object.keys(live.stats).length > 0) {
29876
+ const current = viewerApply.getState().stats;
29877
+ patch.stats = { ...current && typeof current === "object" ? current : {}, ...live.stats };
29878
+ }
29879
+ viewerApply.set(patch);
29880
+ };
29881
+ const clearMirroredLiveState = () => {
29882
+ try {
29883
+ viewerApply?.set?.({
29884
+ busy: false,
29885
+ commandBusy: false,
29886
+ spinner: null,
29887
+ queued: [],
29888
+ activeToolSummary: null,
29889
+ agentWorkers: [],
29890
+ agentJobs: [],
29891
+ ownerClientHostPid: 0
29892
+ });
29893
+ } catch {
29894
+ }
29895
+ };
29585
29896
  const applyViewerFrame = (frame, socket) => {
29586
29897
  if (frame.t === "full") {
29587
29898
  viewerApply.replaceItems(Array.isArray(frame.items) ? frame.items : []);
29588
29899
  if (frame.tail) viewerApply.updateStreamingTail(frame.tail.id, frame.tail);
29589
29900
  viewerApply.set({ spinner: frame.spinner || null });
29901
+ applyLiveState(frame.live);
29590
29902
  return;
29591
29903
  }
29592
29904
  if (frame.t !== "delta") return;
@@ -29612,6 +29924,7 @@ function createLiveShare({
29612
29924
  else viewerApply.clearStreamingTail();
29613
29925
  }
29614
29926
  if ("spinner" in frame) viewerApply.set({ spinner: frame.spinner || null });
29927
+ if ("live" in frame) applyLiveState(frame.live);
29615
29928
  };
29616
29929
  const stopClient = () => {
29617
29930
  const closing = client;
@@ -29654,6 +29967,7 @@ function createLiveShare({
29654
29967
  socket.destroy();
29655
29968
  } catch {
29656
29969
  }
29970
+ if (wasUp) clearMirroredLiveState();
29657
29971
  if (wasUp) onOwnerClosed?.(id, ownerClosed);
29658
29972
  };
29659
29973
  socket.on("error", () => down(false));
@@ -29698,6 +30012,15 @@ function createLiveShare({
29698
30012
  return false;
29699
30013
  }
29700
30014
  },
30015
+ sendAbort() {
30016
+ if (!clientUp || !client) return false;
30017
+ try {
30018
+ client.write(frameLine({ t: "abort" }));
30019
+ return true;
30020
+ } catch {
30021
+ return false;
30022
+ }
30023
+ },
29701
30024
  dispose() {
29702
30025
  listeners.delete(onPublish);
29703
30026
  stopServer();
@@ -30212,16 +30535,16 @@ async function createEngineSession({
30212
30535
  cwd,
30213
30536
  desktopSession
30214
30537
  } = {}) {
30215
- const startedAt = performance2.now();
30538
+ const startedAt = performance3.now();
30216
30539
  bootProfile("engine:create:start", { provider: providerName, model, toolMode, remote });
30217
30540
  process.env.MIXDOG_QUIET_PROVIDER_LOG = "1";
30218
30541
  process.env.MIXDOG_QUIET_SESSION_LOG = "1";
30219
30542
  process.env.MIXDOG_QUIET_MCP_LOG = "1";
30220
30543
  process.env.MIXDOG_QUIET_MEMORY_LOG = "1";
30221
30544
  process.env.MIXDOG_PATCH_NATIVE_PREWARM ??= "0";
30222
- const importStartedAt = performance2.now();
30545
+ const importStartedAt = performance3.now();
30223
30546
  const { createMixdogSessionRuntime } = await import(SESSION_RUNTIME_MODULE);
30224
- bootProfile("session-runtime:imported", { ms: (performance2.now() - importStartedAt).toFixed(1) });
30547
+ bootProfile("session-runtime:imported", { ms: (performance3.now() - importStartedAt).toFixed(1) });
30225
30548
  const runtime = await createMixdogSessionRuntime({
30226
30549
  provider: providerName,
30227
30550
  model,
@@ -30230,9 +30553,9 @@ async function createEngineSession({
30230
30553
  ...cwd ? { cwd } : {},
30231
30554
  ...desktopSession ? { desktopSession } : {}
30232
30555
  });
30233
- bootProfile("engine:create:runtime-ready", { ms: (performance2.now() - startedAt).toFixed(1) });
30556
+ bootProfile("engine:create:runtime-ready", { ms: (performance3.now() - startedAt).toFixed(1) });
30234
30557
  const runtimeCwd = runtime.cwd || process.cwd();
30235
- const stateStartedAt = performance2.now();
30558
+ const stateStartedAt = performance3.now();
30236
30559
  const flags = {
30237
30560
  disposed: false,
30238
30561
  draining: false,
@@ -30308,11 +30631,11 @@ async function createEngineSession({
30308
30631
  cwd: runtimeCwd,
30309
30632
  themeEpoch: 0
30310
30633
  };
30311
- bootProfile("engine:route-state-ready", { ms: (performance2.now() - stateStartedAt).toFixed(1) });
30312
- bootProfile("engine:state-ready", { ms: (performance2.now() - stateStartedAt).toFixed(1) });
30313
- 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();
30314
30637
  syncContextStats({ allowEstimated: true });
30315
- bootProfile("engine:context-ready", { ms: (performance2.now() - contextStartedAt).toFixed(1) });
30638
+ bootProfile("engine:context-ready", { ms: (performance3.now() - contextStartedAt).toFixed(1) });
30316
30639
  const listeners = /* @__PURE__ */ new Set();
30317
30640
  let publishedState = process.env.NODE_ENV === "production" ? state : Object.freeze(state);
30318
30641
  state = { ...state, stats: { ...state.stats } };
@@ -30323,7 +30646,10 @@ async function createEngineSession({
30323
30646
  state = { ...next, stats: { ...next.stats } };
30324
30647
  },
30325
30648
  listeners,
30326
- isDisposed: () => flags.disposed
30649
+ isDisposed: () => flags.disposed,
30650
+ frameMs: TUI_FRAME_MS,
30651
+ scheduleFrame: scheduleRenderAlignedStoreFlush,
30652
+ cancelFrame: cancelRenderAlignedStoreFlush
30327
30653
  });
30328
30654
  const emit = publisher.emit;
30329
30655
  const flushEmit = publisher.flush;
@@ -30667,6 +30993,10 @@ async function createEngineSession({
30667
30993
  lifecycle.runtimePulseTimer = setInterval(() => {
30668
30994
  if (flags.disposed) return;
30669
30995
  if (flags.pendingSessionReset) return;
30996
+ if (bag.liveShareMirroring?.()) {
30997
+ set({ ...routeState() });
30998
+ return;
30999
+ }
30670
31000
  syncContextStats({ allowEstimated: true });
30671
31001
  set({
30672
31002
  ...routeState(),
@@ -30812,6 +31142,13 @@ async function createEngineSession({
30812
31142
  bag.enqueue(text);
30813
31143
  void bag.drain();
30814
31144
  },
31145
+ onRemoteAbort: () => {
31146
+ if (flags.disposed || state.sessionRemoteAttached) return;
31147
+ try {
31148
+ api.abort?.();
31149
+ } catch {
31150
+ }
31151
+ },
30815
31152
  onOwnerClosed: (id) => {
30816
31153
  const timer2 = setTimeout(() => {
30817
31154
  if (flags.disposed || !state.sessionRemoteAttached) return;
@@ -30837,6 +31174,7 @@ async function createEngineSession({
30837
31174
  } catch {
30838
31175
  }
30839
31176
  };
31177
+ bag.liveShareMirroring = () => state.sessionRemoteAttached && liveShare.viewerConnected();
30840
31178
  if (typeof api.submit === "function") {
30841
31179
  const baseSubmit = api.submit;
30842
31180
  api.submit = (prompt, options = {}) => {
@@ -30847,6 +31185,15 @@ async function createEngineSession({
30847
31185
  return baseSubmit(prompt, options);
30848
31186
  };
30849
31187
  }
31188
+ if (typeof api.abort === "function") {
31189
+ const baseAbort = api.abort;
31190
+ api.abort = (...args) => {
31191
+ if (state.sessionRemoteAttached && liveShare.viewerConnected() && liveShare.sendAbort()) {
31192
+ return true;
31193
+ }
31194
+ return baseAbort(...args);
31195
+ };
31196
+ }
30850
31197
  const reconcileLiveShareNow = () => {
30851
31198
  try {
30852
31199
  liveShare.ensure();
@@ -30978,7 +31325,7 @@ var XTSHIFTESCAPE_ON = process.env.WT_SESSION ? "" : "\x1B[>1s";
30978
31325
  var MOUSE_TRACKING_ON2 = `\x1B[?1000h\x1B[?1002h\x1B[?1006h\x1B[?1007l${XTSHIFTESCAPE_ON}`;
30979
31326
  var ALT_SCROLL_RESTORE = "\x1B[?1007h";
30980
31327
  var BOOT_PROFILE_ENABLED2 = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_BOOT_PROFILE || ""));
30981
- var BOOT_PROFILE_START2 = globalThis.__mixdogBootProfileStart || (globalThis.__mixdogBootProfileStart = performance3.now());
31328
+ var BOOT_PROFILE_START2 = globalThis.__mixdogBootProfileStart || (globalThis.__mixdogBootProfileStart = performance4.now());
30982
31329
  var EXIT_WAIT_TIMEOUT_MS = positiveIntEnv2("MIXDOG_TUI_EXIT_WAIT_MS", 2500);
30983
31330
  var EXIT_HARD_DELAY_MS = positiveIntEnv2("MIXDOG_TUI_HARD_EXIT_DELAY_MS", 500);
30984
31331
  var EXIT_HARD_ENABLED = !/^(0|false|no|off)$/i.test(String(process.env.MIXDOG_TUI_HARD_EXIT || "1"));
@@ -30994,9 +31341,9 @@ function positiveIntEnv2(name, fallback) {
30994
31341
  function installTuiLoopProbe() {
30995
31342
  if (!LOOP_PROBE_ENABLED) return () => {
30996
31343
  };
30997
- let last = performance3.now();
31344
+ let last = performance4.now();
30998
31345
  const timer2 = setInterval(() => {
30999
- const now = performance3.now();
31346
+ const now = performance4.now();
31000
31347
  const drift = now - last - LOOP_PROBE_INTERVAL_MS;
31001
31348
  last = now;
31002
31349
  if (drift > LOOP_PROBE_DRIFT_THRESHOLD_MS) {
@@ -31021,7 +31368,7 @@ var PERF_STALL_THRESHOLD_MS = positiveIntEnv2("MIXDOG_TUI_PERF_STALL_MS", 80);
31021
31368
  var PERF_RENDER_GAP_MS = positiveIntEnv2("MIXDOG_TUI_PERF_RENDER_GAP_MS", 120);
31022
31369
  function perfLog(event, fields = {}) {
31023
31370
  if (!PERF_ENABLED) return;
31024
- const elapsedMs = performance3.now() - BOOT_PROFILE_START2;
31371
+ const elapsedMs = performance4.now() - BOOT_PROFILE_START2;
31025
31372
  const parts = [`[mixdog-perf] +${elapsedMs.toFixed(1)}ms`, event];
31026
31373
  for (const [key, value] of Object.entries(fields || {})) {
31027
31374
  if (value === void 0 || value === null || value === "") continue;
@@ -31036,7 +31383,7 @@ function perfLog(event, fields = {}) {
31036
31383
  function installTuiPerfProbe() {
31037
31384
  if (!PERF_ENABLED) return () => {
31038
31385
  };
31039
- let last = performance3.now();
31386
+ let last = performance4.now();
31040
31387
  let lastCpu = process.cpuUsage();
31041
31388
  perfLog("probe:start", {
31042
31389
  intervalMs: PERF_STALL_INTERVAL_MS,
@@ -31044,7 +31391,7 @@ function installTuiPerfProbe() {
31044
31391
  renderGapMs: PERF_RENDER_GAP_MS
31045
31392
  });
31046
31393
  const timer2 = setInterval(() => {
31047
- const now = performance3.now();
31394
+ const now = performance4.now();
31048
31395
  const elapsed = now - last;
31049
31396
  const lagMs = elapsed - PERF_STALL_INTERVAL_MS;
31050
31397
  const cpu = process.cpuUsage(lastCpu);
@@ -31082,7 +31429,7 @@ function makeRenderProfiler() {
31082
31429
  let lastFrameAt = 0;
31083
31430
  return ({ renderTime } = {}) => {
31084
31431
  ackRenderedFrame();
31085
- const now = performance3.now();
31432
+ const now = performance4.now();
31086
31433
  const ms = Number(renderTime) || 0;
31087
31434
  const gap = lastFrameAt ? now - lastFrameAt : 0;
31088
31435
  lastFrameAt = now;
@@ -31116,7 +31463,7 @@ function makeRenderProfiler() {
31116
31463
  }
31117
31464
  function bootProfile2(event, fields = {}) {
31118
31465
  if (!BOOT_PROFILE_ENABLED2) return;
31119
- const elapsedMs = performance3.now() - BOOT_PROFILE_START2;
31466
+ const elapsedMs = performance4.now() - BOOT_PROFILE_START2;
31120
31467
  const parts = [`[mixdog-boot] +${elapsedMs.toFixed(1)}ms`, `tui:${event}`];
31121
31468
  for (const [key, value] of Object.entries(fields || {})) {
31122
31469
  if (value === void 0 || value === null || value === "") continue;
@@ -31334,7 +31681,7 @@ function installTuiConsoleGuard() {
31334
31681
  };
31335
31682
  }
31336
31683
  async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {}) {
31337
- const startedAt = performance3.now();
31684
+ const startedAt = performance4.now();
31338
31685
  bootProfile2("run:start", { provider, model, toolMode, remote });
31339
31686
  if (!process.stdin.isTTY) {
31340
31687
  process.stderr.write(
@@ -31365,6 +31712,7 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
31365
31712
  process.stdout.write(`${TERMINAL_MODE_RESET_HIDDEN_CURSOR}\x1B[?1049h${TERMINAL_MODE_RESET_HIDDEN_CURSOR}\x1B[2J\x1B[H`);
31366
31713
  process.stdout.write("\x1B[5 q");
31367
31714
  process.on("exit", restoreTerminal);
31715
+ const storeOutcomePromise = createEngineSession({ provider, model, toolMode, remote }).then((store2) => ({ store: store2, error: null }), (error) => ({ store: null, error }));
31368
31716
  try {
31369
31717
  await loadThemeSettingFromConfig();
31370
31718
  } catch {
@@ -31374,8 +31722,10 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
31374
31722
  } };
31375
31723
  let store;
31376
31724
  try {
31377
- store = await createEngineSession({ provider, model, toolMode, remote });
31378
- 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) });
31379
31729
  } catch (error) {
31380
31730
  splash.stop();
31381
31731
  stopPerfProbe();
@@ -31440,8 +31790,8 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
31440
31790
  registerStdioDeath(process.stderr, "error", { requireCode: true });
31441
31791
  registerStdioDeath(process.stderr, "close");
31442
31792
  try {
31443
- const instance = render(/* @__PURE__ */ jsx21(App, { store, forceOnboarding: forceOnboarding === true }), { exitOnCtrlC: false, maxFps: 60, incrementalRendering: true, patchConsole: false, onRender: makeRenderProfiler() });
31444
- 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) });
31445
31795
  const { waitUntilExit } = instance;
31446
31796
  if (mouseTracking && typeof instance.setSelection === "function") {
31447
31797
  store.setRenderSelection = instance.setSelection;