mixdog 0.9.50 → 0.9.52

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 (134) hide show
  1. package/package.json +5 -3
  2. package/scripts/abort-recovery-test.mjs +17 -1
  3. package/scripts/agent-model-liveness-test.mjs +79 -2
  4. package/scripts/anthropic-admission-retry-integration-test.mjs +119 -0
  5. package/scripts/anthropic-transport-policy-test.mjs +466 -0
  6. package/scripts/atomic-lock-tryonce-test.mjs +60 -1
  7. package/scripts/build-tui.mjs +13 -1
  8. package/scripts/channel-daemon-smoke.mjs +630 -10
  9. package/scripts/code-graph-aggregate-cwd-test.mjs +41 -37
  10. package/scripts/code-graph-disk-hit-test.mjs +11 -0
  11. package/scripts/code-graph-root-federation-test.mjs +273 -0
  12. package/scripts/compact-pressure-test.mjs +55 -1
  13. package/scripts/compact-smoke.mjs +80 -0
  14. package/scripts/context-mcp-metering-test.mjs +1350 -19
  15. package/scripts/deferred-tool-loading-test.mjs +17 -0
  16. package/scripts/gemini-provider-test.mjs +1053 -0
  17. package/scripts/hook-bus-test.mjs +23 -0
  18. package/scripts/internal-tools-normalization-test.mjs +10 -0
  19. package/scripts/interrupted-turn-history-test.mjs +371 -0
  20. package/scripts/lifecycle-api-test.mjs +76 -0
  21. package/scripts/max-output-recovery-test.mjs +55 -0
  22. package/scripts/mcp-grace-deferred-test.mjs +89 -13
  23. package/scripts/memory-pg-recovery-test.mjs +59 -0
  24. package/scripts/openai-oauth-ws-1006-retry-test.mjs +391 -4
  25. package/scripts/process-lifecycle-test.mjs +389 -0
  26. package/scripts/provider-admission-scheduler-test.mjs +582 -0
  27. package/scripts/provider-contract-test.mjs +268 -0
  28. package/scripts/provider-toolcall-test.mjs +520 -3
  29. package/scripts/reactive-compact-persist-smoke.mjs +59 -0
  30. package/scripts/resource-admission-test.mjs +789 -0
  31. package/scripts/session-bench-cache-break-test.mjs +102 -0
  32. package/scripts/session-bench.mjs +101 -42
  33. package/scripts/shell-failure-diagnostics-test.mjs +73 -4
  34. package/scripts/shell-jobs-windows-hide-test.mjs +1 -1
  35. package/scripts/smoke-loop-failure-summary-test.mjs +38 -0
  36. package/scripts/smoke-loop-failure-summary.mjs +16 -0
  37. package/scripts/smoke-loop.mjs +2 -7
  38. package/scripts/steering-drain-buckets-test.mjs +18 -0
  39. package/scripts/tool-failures.mjs +15 -1
  40. package/scripts/toolcall-args-test.mjs +14 -6
  41. package/scripts/tui-transcript-perf-test.mjs +43 -7
  42. package/scripts/web-fetch-routing-test.mjs +158 -0
  43. package/src/cli.mjs +15 -2
  44. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +26 -0
  45. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +4 -1
  46. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +12 -0
  47. package/src/runtime/agent/orchestrator/internal-tools.mjs +2 -0
  48. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +331 -0
  49. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +118 -26
  50. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +29 -17
  51. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +136 -38
  52. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +24 -4
  53. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +554 -42
  54. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +67 -18
  55. package/src/runtime/agent/orchestrator/providers/gemini.mjs +95 -46
  56. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +12 -4
  57. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +54 -14
  58. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +1 -1
  59. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +14 -3
  60. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +10 -80
  61. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +89 -17
  62. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +93 -26
  63. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +144 -51
  64. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +22 -8
  65. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +74 -1
  66. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +111 -6
  67. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +13 -17
  68. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +78 -12
  69. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +44 -5
  70. package/src/runtime/agent/orchestrator/providers/registry.mjs +49 -8
  71. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +224 -104
  72. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +127 -55
  73. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +99 -32
  74. package/src/runtime/agent/orchestrator/session/context-utils.mjs +17 -1
  75. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +38 -0
  76. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -0
  77. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +69 -37
  78. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +10 -1
  79. package/src/runtime/agent/orchestrator/session/manager/message-sanitize.mjs +8 -28
  80. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +6 -7
  81. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +2 -0
  82. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +10 -1
  83. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +42 -1
  84. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +5 -1
  85. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +220 -0
  86. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +24 -4
  87. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -0
  88. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +17 -0
  89. package/src/runtime/agent/orchestrator/session/store.mjs +89 -22
  90. package/src/runtime/agent/orchestrator/stall-policy.mjs +2 -12
  91. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +42 -4
  92. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +74 -37
  93. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +223 -2
  94. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +380 -37
  95. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +176 -21
  96. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +14 -0
  97. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +108 -2
  98. package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +93 -0
  99. package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +12 -3
  100. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +124 -14
  101. package/src/runtime/memory/index.mjs +22 -4
  102. package/src/runtime/memory/lib/pg/adapter.mjs +84 -10
  103. package/src/runtime/memory/lib/pg/process.mjs +91 -47
  104. package/src/runtime/memory/lib/pg/supervisor.mjs +50 -13
  105. package/src/runtime/search/index.mjs +41 -0
  106. package/src/runtime/search/lib/http-fetch.mjs +154 -0
  107. package/src/runtime/search/tool-defs.mjs +23 -0
  108. package/src/runtime/shared/atomic-file.mjs +28 -150
  109. package/src/runtime/shared/process-lifecycle.mjs +363 -0
  110. package/src/runtime/shared/process-shutdown.mjs +27 -4
  111. package/src/runtime/shared/resource-admission.mjs +359 -0
  112. package/src/runtime/shared/staged-child-result.mjs +19 -0
  113. package/src/session-runtime/context-status.mjs +38 -27
  114. package/src/session-runtime/lifecycle-api.mjs +26 -6
  115. package/src/session-runtime/provider-request-tools.mjs +72 -0
  116. package/src/session-runtime/runtime-core.mjs +43 -18
  117. package/src/session-runtime/session-turn-api.mjs +5 -4
  118. package/src/session-runtime/tool-catalog.mjs +375 -15
  119. package/src/standalone/agent-tool.mjs +17 -38
  120. package/src/standalone/channel-daemon-client.mjs +200 -38
  121. package/src/standalone/channel-daemon-transport.mjs +136 -9
  122. package/src/standalone/channel-worker.mjs +79 -12
  123. package/src/standalone/hook-bus/handlers.mjs +4 -0
  124. package/src/standalone/hook-bus/rules.mjs +7 -1
  125. package/src/standalone/hook-bus.mjs +10 -2
  126. package/src/tui/App.jsx +17 -11
  127. package/src/tui/app/live-spinner-visibility.mjs +20 -0
  128. package/src/tui/dist/index.mjs +101 -281
  129. package/src/tui/engine/session-api-ext.mjs +13 -2
  130. package/src/tui/engine/session-api.mjs +1 -1
  131. package/src/tui/engine/turn.mjs +10 -6
  132. package/src/tui/engine.mjs +13 -4
  133. package/src/tui/index.jsx +4 -1
  134. package/src/tui/lib/voice-setup.mjs +16 -0
@@ -8083,6 +8083,23 @@ function slashArgumentHint(value) {
8083
8083
  return command?.params ? `${command.usage} ${command.params}` : "";
8084
8084
  }
8085
8085
 
8086
+ // src/tui/app/live-spinner-visibility.mjs
8087
+ function isCompletedTranscriptTail(latestTranscriptItem) {
8088
+ return latestTranscriptItem?.kind === "turndone" || latestTranscriptItem?.kind === "statusdone";
8089
+ }
8090
+ function isCompletedTranscriptTailAppendedThisCommit(latestTranscriptItem, previousTailId) {
8091
+ return isCompletedTranscriptTail(latestTranscriptItem) && (latestTranscriptItem?.id ?? null) !== previousTailId;
8092
+ }
8093
+ function isLiveSpinnerMetaVisible({
8094
+ inputBoxHidden,
8095
+ slashPaletteOpen,
8096
+ liveSpinner,
8097
+ liveSpinnerIsCommand,
8098
+ latestTranscriptItem
8099
+ }) {
8100
+ return !inputBoxHidden && !slashPaletteOpen && !!liveSpinner && (liveSpinnerIsCommand || latestTranscriptItem?.kind !== "turndone");
8101
+ }
8102
+
8086
8103
  // src/tui/app/input-parsers.mjs
8087
8104
  function parseHookRuleInput(text) {
8088
8105
  const parts = String(text || "").split("|").map((part) => part.trim());
@@ -14531,26 +14548,6 @@ function withFileLockSync(lockPath, fn, opts = {}) {
14531
14548
  timeoutErr.cause = lastErr;
14532
14549
  throw timeoutErr;
14533
14550
  }
14534
- function _readLockOwnerPid(lockPath) {
14535
- try {
14536
- const raw = readFileSync3(lockPath, "utf8");
14537
- const tok = String(raw).trim().split(/\s+/)[0];
14538
- const pid = Number.parseInt(tok, 10);
14539
- return Number.isFinite(pid) && pid > 0 ? pid : null;
14540
- } catch {
14541
- return null;
14542
- }
14543
- }
14544
- function _pidIsDead(pid) {
14545
- if (pid === null) return false;
14546
- if (pid === process.pid) return false;
14547
- try {
14548
- process.kill(pid, 0);
14549
- return false;
14550
- } catch (err) {
14551
- return err?.code === "ESRCH";
14552
- }
14553
- }
14554
14551
  function _readLockOwner(lockPath) {
14555
14552
  try {
14556
14553
  const raw = readFileSync3(lockPath, "utf8");
@@ -14574,27 +14571,20 @@ function _ownerIsLive(owner) {
14574
14571
  return err?.code !== "ESRCH";
14575
14572
  }
14576
14573
  }
14577
- function _ownerIsSelfForeign(owner) {
14578
- return owner.pid === process.pid && owner.token !== null && owner.token !== _OWNER_TOKEN;
14579
- }
14580
14574
  function _lockOwnedBySelf(lockPath) {
14581
14575
  const owner = _readLockOwner(lockPath);
14582
14576
  if (owner.pid !== process.pid) return false;
14583
14577
  return owner.token === null ? true : owner.token === _OWNER_TOKEN;
14584
14578
  }
14585
14579
  function _tryReclaimStaleLock(lockPath, staleMs) {
14586
- let st;
14587
14580
  try {
14588
- st = statSync2(lockPath);
14581
+ statSync2(lockPath);
14589
14582
  } catch {
14590
14583
  return false;
14591
14584
  }
14592
14585
  const owner = _readLockOwner(lockPath);
14593
- const stale = Date.now() - st.mtimeMs > staleMs;
14594
14586
  const dead = owner.pid !== null && !_ownerIsLive(owner);
14595
- const pidless = owner.pid === null;
14596
- const selfForeign = _ownerIsSelfForeign(owner);
14597
- if (!dead && !((pidless || selfForeign) && stale)) return false;
14587
+ if (!dead) return false;
14598
14588
  const reclaim = _tryAcquireReclaimGuard(lockPath, staleMs);
14599
14589
  if (reclaim === null) return false;
14600
14590
  try {
@@ -14606,11 +14596,8 @@ function _tryReclaimStaleLock(lockPath, staleMs) {
14606
14596
  }
14607
14597
  const curOwner = _readLockOwner(lockPath);
14608
14598
  if (curOwner.pid !== owner.pid || curOwner.token !== owner.token) return false;
14609
- const curStale = Date.now() - cur.mtimeMs > staleMs;
14610
14599
  const curDead = curOwner.pid !== null && !_ownerIsLive(curOwner);
14611
- const curPidless = curOwner.pid === null;
14612
- const curSelfForeign = _ownerIsSelfForeign(curOwner);
14613
- if (curDead || (curPidless || curSelfForeign) && curStale) {
14600
+ if (curDead) {
14614
14601
  try {
14615
14602
  unlinkSync2(lockPath);
14616
14603
  return true;
@@ -14623,83 +14610,22 @@ function _tryReclaimStaleLock(lockPath, staleMs) {
14623
14610
  _releaseReclaimGuard(reclaim);
14624
14611
  }
14625
14612
  }
14626
- function _reclaimGuardIsUnreadableAndStale(guardPath, staleMs) {
14627
- if (_readLockOwnerPid(guardPath) !== null) return false;
14628
- try {
14629
- const st = statSync2(guardPath);
14630
- return Date.now() - st.mtimeMs > staleMs;
14631
- } catch {
14632
- return false;
14633
- }
14634
- }
14635
- function _reclaimGuardMatchesToken(guardPath, token) {
14636
- try {
14637
- return readFileSync3(guardPath, "utf8") === token;
14638
- } catch {
14639
- return false;
14640
- }
14641
- }
14642
- function _readGuardContent(guardPath) {
14643
- try {
14644
- return readFileSync3(guardPath, "utf8");
14645
- } catch {
14646
- return null;
14647
- }
14648
- }
14649
- function _parseStampPid(raw) {
14650
- if (raw === null) return null;
14651
- const tok = String(raw).trim().split(/\s+/)[0];
14652
- const pid = Number.parseInt(tok, 10);
14653
- return Number.isFinite(pid) && pid > 0 ? pid : null;
14654
- }
14655
- function _unlinkReclaimGuardIfContentMatches(guardPath, content) {
14656
- if (_readGuardContent(guardPath) !== content) return false;
14657
- try {
14658
- unlinkSync2(guardPath);
14659
- return true;
14660
- } catch {
14661
- return false;
14662
- }
14663
- }
14664
- function _unlinkUnreadableStaleReclaimGuard(guardPath, staleMs) {
14665
- if (!_reclaimGuardIsUnreadableAndStale(guardPath, staleMs)) return false;
14666
- try {
14667
- unlinkSync2(guardPath);
14668
- return true;
14669
- } catch {
14670
- return false;
14671
- }
14672
- }
14673
- function _tryClearStaleReclaimGuard(guardPath, staleMs) {
14674
- const guardContent = _readGuardContent(guardPath);
14675
- const guardPid = _parseStampPid(guardContent);
14676
- if (guardPid !== null) {
14677
- let guardStale = false;
14678
- try {
14679
- guardStale = Date.now() - statSync2(guardPath).mtimeMs > staleMs;
14680
- } catch {
14681
- }
14682
- if (_pidIsDead(guardPid) || guardStale) {
14683
- _unlinkReclaimGuardIfContentMatches(guardPath, guardContent);
14684
- }
14685
- return;
14686
- }
14687
- _unlinkUnreadableStaleReclaimGuard(guardPath, staleMs);
14688
- }
14689
14613
  function _tryAcquireReclaimGuard(lockPath, staleMs) {
14690
14614
  const guardPath = `${lockPath}.reclaim`;
14691
- try {
14692
- const fd = openSync(guardPath, "wx", 384);
14693
- const token = `${process.pid} ${Date.now()} ${randomBytes2(8).toString("hex")}
14615
+ const token = `${process.pid} ${Date.now()} ${randomBytes2(8).toString("hex")}
14694
14616
  `;
14617
+ const stagedPath = `${guardPath}.${process.pid}.${randomBytes2(8).toString("hex")}.tmp`;
14618
+ try {
14619
+ writeFileSync3(stagedPath, token, { encoding: "utf8", mode: 384, flag: "wx" });
14620
+ linkSync(stagedPath, guardPath);
14621
+ unlinkSync2(stagedPath);
14622
+ return { guardPath };
14623
+ } catch (err) {
14695
14624
  try {
14696
- writeFileSync3(fd, token, "utf8");
14625
+ unlinkSync2(stagedPath);
14697
14626
  } catch {
14698
14627
  }
14699
- return { fd, guardPath, token };
14700
- } catch (err) {
14701
14628
  if (LOCK_WAIT_CODES.has(err?.code)) {
14702
- _tryClearStaleReclaimGuard(guardPath, staleMs);
14703
14629
  return null;
14704
14630
  }
14705
14631
  throw err;
@@ -14707,15 +14633,9 @@ function _tryAcquireReclaimGuard(lockPath, staleMs) {
14707
14633
  }
14708
14634
  function _releaseReclaimGuard(reclaim) {
14709
14635
  try {
14710
- closeSync(reclaim.fd);
14636
+ unlinkSync2(reclaim.guardPath);
14711
14637
  } catch {
14712
14638
  }
14713
- if (_reclaimGuardMatchesToken(reclaim.guardPath, reclaim.token)) {
14714
- try {
14715
- unlinkSync2(reclaim.guardPath);
14716
- } catch {
14717
- }
14718
- }
14719
14639
  }
14720
14640
  function _asyncSleep(ms) {
14721
14641
  return new Promise((resolve5) => {
@@ -15357,6 +15277,20 @@ function isVoiceEnabled() {
15357
15277
  return false;
15358
15278
  }
15359
15279
  }
15280
+ async function getVoiceStatus({ dataDir = resolvePluginData() } = {}) {
15281
+ const fetcher = await loadVoiceRuntimeFetcher();
15282
+ const runtime = fetcher.resolveVoiceRuntime(dataDir);
15283
+ return {
15284
+ enabled: isVoiceEnabled(),
15285
+ busy: isVoiceInstallBusy(),
15286
+ installed: runtime.installed === true,
15287
+ components: {
15288
+ whisper: Boolean(runtime.binary && runtime.serverCmd),
15289
+ model: Boolean(runtime.model),
15290
+ ffmpeg: Boolean(runtime.ffmpeg)
15291
+ }
15292
+ };
15293
+ }
15360
15294
  var PROGRESS_BAR_CELLS = 6;
15361
15295
  function renderProgressBarText(phase, downloaded, total) {
15362
15296
  const label = phaseLabel(phase);
@@ -21453,7 +21387,6 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
21453
21387
  const inputHint = promptHint || toastHint || (progressHint?.text || "");
21454
21388
  const inputHintTone = promptHint ? promptHintTone : latestToast?.tone || progressHint?.tone || "info";
21455
21389
  const latestTranscriptItem = state.items[state.items.length - 1] || null;
21456
- const latestDoneAtTail = latestTranscriptItem?.kind === "turndone" || latestTranscriptItem?.kind === "statusdone";
21457
21390
  const latestDoneItem = null;
21458
21391
  const SCROLL_HINT_ROWS = 0;
21459
21392
  const LIVE_STATUS_ROWS = 0;
@@ -21473,7 +21406,13 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
21473
21406
  const TEXT_ENTRY_ROWS = PANEL_CHROME_ROWS + textEntryContentRows + textEntryDetailRows;
21474
21407
  const OPTION_PANEL_EXTRA_ROWS = expandedOptionPanel ? 3 : 0;
21475
21408
  const queuedVisible = !hasFloatingPanel && !inputBoxHidden && state.queued?.length > 0;
21476
- const promptMetaVisible = !inputBoxHidden && !slashPaletteOpen && !!liveSpinner && (liveSpinnerIsCommand || !latestDoneAtTail);
21409
+ const promptMetaVisible = isLiveSpinnerMetaVisible({
21410
+ inputBoxHidden,
21411
+ slashPaletteOpen,
21412
+ liveSpinner,
21413
+ liveSpinnerIsCommand,
21414
+ latestTranscriptItem
21415
+ });
21477
21416
  const promptMetaRows = promptMetaVisible ? 2 : 0;
21478
21417
  const overlayHintRequested = !inputBoxHidden && !hasFloatingPanel && !liveSpinner && !!inputHint && !queuedVisible;
21479
21418
  const overlayHintRows = 0;
@@ -21516,7 +21455,10 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
21516
21455
  const promptRowsOnlyChange = panelShrinkRows > 0 && panelKindSignature(panelTransition.signature) === panelKindSignature(panelLayoutSignature);
21517
21456
  const prevMetaRows = Number(String(panelTransition.signature).split("|")[PANEL_LAYOUT_SIG.PROMPT_META]) || 0;
21518
21457
  const nextMetaRows = Number(String(panelLayoutSignature).split("|")[PANEL_LAYOUT_SIG.PROMPT_META]) || 0;
21519
- const doneTailAppendedThisCommit = latestDoneAtTail && (latestTranscriptItem?.id ?? null) !== panelTransition.tailId;
21458
+ const doneTailAppendedThisCommit = isCompletedTranscriptTailAppendedThisCommit(
21459
+ latestTranscriptItem,
21460
+ panelTransition.tailId
21461
+ );
21520
21462
  const spinnerMetaCollapseRows = doneTailAppendedThisCommit ? Math.max(0, prevMetaRows - nextMetaRows) : 0;
21521
21463
  const prevQueuedSigRows = Number(String(panelTransition.signature).split("|")[PANEL_LAYOUT_SIG.QUEUED]) || 0;
21522
21464
  const nextQueuedSigRows = Number(String(panelLayoutSignature).split("|")[PANEL_LAYOUT_SIG.QUEUED]) || 0;
@@ -22626,7 +22568,7 @@ function parseBackgroundTaskEnvelope(text) {
22626
22568
  const name = surface === "explore" || surface === "search" || surface === "shell" || surface === "agent" ? surface : "task";
22627
22569
  const status = String(fields.status || "").toLowerCase();
22628
22570
  const taskId = fields.task_id || fields.taskid || "";
22629
- const errorText2 = fields.error || "";
22571
+ const errorText = fields.error || "";
22630
22572
  const agentResult = parseAgentResultEnvelope(body, {
22631
22573
  status,
22632
22574
  taskId,
@@ -22639,7 +22581,7 @@ function parseBackgroundTaskEnvelope(text) {
22639
22581
  fast: fields.fast
22640
22582
  });
22641
22583
  if (agentResult) return { ...agentResult, rawResult: value };
22642
- const errorOnlyBody = isBackgroundErrorOnlyBody(body, errorText2);
22584
+ const errorOnlyBody = isBackgroundErrorOnlyBody(body, errorText);
22643
22585
  const resultBody = body && !errorOnlyBody ? body : "";
22644
22586
  return {
22645
22587
  name,
@@ -22658,13 +22600,13 @@ function parseBackgroundTaskEnvelope(text) {
22658
22600
  preset: fields.preset || void 0,
22659
22601
  effort: fields.effort || void 0,
22660
22602
  fast: fields.fast || void 0,
22661
- error: errorText2 || void 0,
22603
+ error: errorText || void 0,
22662
22604
  startedAt: fields.started || fields.startedat || void 0,
22663
22605
  finishedAt: fields.finished || fields.finishedat || void 0
22664
22606
  },
22665
- result: resultBody || (!errorText2 ? [status ? `status: ${status}` : "", taskId ? `task_id: ${taskId}` : ""].filter(Boolean).join(" \xB7 ") : ""),
22607
+ result: resultBody || (!errorText ? [status ? `status: ${status}` : "", taskId ? `task_id: ${taskId}` : ""].filter(Boolean).join(" \xB7 ") : ""),
22666
22608
  rawResult: value,
22667
- isError: /^(failed|error|timeout|cancelled|canceled|killed)$/i.test(status) || /^error:/i.test(body) || Boolean(errorText2)
22609
+ isError: /^(failed|error|timeout|cancelled|canceled|killed)$/i.test(status) || /^error:/i.test(body) || Boolean(errorText)
22668
22610
  };
22669
22611
  }
22670
22612
  function isStatusOnlyAgentCompletionNotification(text) {
@@ -24906,7 +24848,7 @@ function createRunTurn(bag) {
24906
24848
  tuiDebug2(`runTurn WATCHDOG TRIP turn=${turnIndex} elapsedMs=${elapsed} idleMs=${idleMs} lastProgress=${lastProgressLabel} \u2014 aborting stuck turn`);
24907
24849
  pushNotice(`Turn timed out after ${Math.round(idleMs / 1e3)}s idle (last progress: ${lastProgressLabel}) \u2014 aborting stuck request. Input will be released shortly if abort does not unwind.`, "warn", { transcript: true });
24908
24850
  try {
24909
- runtime.abort("cli-react-abort-watchdog");
24851
+ runtime.abort("watchdog");
24910
24852
  } catch {
24911
24853
  }
24912
24854
  watchdogGraceTimer = setTimeout(() => {
@@ -24951,6 +24893,7 @@ function createRunTurn(bag) {
24951
24893
  let thinkingSegmentStartedAt = 0;
24952
24894
  let accumulatedThinkingMs = 0;
24953
24895
  let cancelled = false;
24896
+ let failed = false;
24954
24897
  let askResult = null;
24955
24898
  let turnFinishedNormally = false;
24956
24899
  let transcriptCompactedThisTurn = false;
@@ -25675,12 +25618,14 @@ function createRunTurn(bag) {
25675
25618
  flushStreamBatch();
25676
25619
  if (error?.name === "SessionClosedError") {
25677
25620
  cancelled = true;
25678
- if (assistantText.trim() && currentAssistantId) {
25679
- settleStreamingTail(currentAssistantId, { text: currentAssistantText || assistantText });
25621
+ if (assistantText.trim()) {
25622
+ const id = currentAssistantId || ensureAssistant(assistantText);
25623
+ settleStreamingTail(id, { text: currentAssistantText || assistantText });
25680
25624
  }
25681
25625
  flushToolResults([], toolCards, cardByCallId, toolGroups, resultsDone, { finalize: true, cancelled: true });
25682
25626
  finalizeToolHeaders();
25683
25627
  } else {
25628
+ failed = true;
25684
25629
  finalizeToolHeaders();
25685
25630
  pushNotice(toolErrorDisplay(error, "turn"), "error");
25686
25631
  }
@@ -25717,7 +25662,7 @@ function createRunTurn(bag) {
25717
25662
  const finalAssistantLen = Math.max(assistantText.length, currentAssistantText.length);
25718
25663
  const finalResponseLength = finalAssistantLen + thinkingText.length;
25719
25664
  const finalOutputTokens = Math.max(0, Number(getState().spinner?.outputTokens || 0), Math.round(finalResponseLength / 4));
25720
- const turnStatus = cancelled ? "cancelled" : "done";
25665
+ const turnStatus = cancelled ? "cancelled" : failed ? "failed" : "done";
25721
25666
  const resultContent = askResult?.content != null ? String(askResult.content).trim() : "";
25722
25667
  const assistantOutput = (currentAssistantText || assistantText || "").trim();
25723
25668
  const isNoOpTurn = turnFinishedNormally && !cancelled && toolCards.length === 0 && !resultContent && !assistantOutput && !producedTranscriptItem;
@@ -25742,8 +25687,9 @@ function createRunTurn(bag) {
25742
25687
  }
25743
25688
  if (flags.leadTurnEpoch === turnEpoch) clearActiveToolSummary();
25744
25689
  _publishedThinkingActive = false;
25745
- tuiDebug2(`runTurn end turn=${turnIndex} status=${cancelled ? "cancelled" : "done"} elapsedMs=${Date.now() - startedAt}${watchdogTripped ? " watchdogTripped=1" : ""} pending=${pending.length}`);
25746
- return cancelled ? "cancelled" : "done";
25690
+ const finalStatus = cancelled ? "cancelled" : failed ? "failed" : "done";
25691
+ tuiDebug2(`runTurn end turn=${turnIndex} status=${finalStatus} elapsedMs=${Date.now() - startedAt}${watchdogTripped ? " watchdogTripped=1" : ""} pending=${pending.length}`);
25692
+ return finalStatus;
25747
25693
  }
25748
25694
  return runTurn;
25749
25695
  }
@@ -25878,6 +25824,14 @@ function createEngineApiB(bag) {
25878
25824
  return next;
25879
25825
  },
25880
25826
  isRemoteEnabled: () => runtime.isRemoteEnabled?.() === true,
25827
+ getVoiceStatus: () => getVoiceStatus(),
25828
+ toggleVoice: async () => {
25829
+ const result = await toggleVoice({ pushNotice, setProgressHint });
25830
+ return {
25831
+ ...await getVoiceStatus(),
25832
+ result: typeof result === "boolean" ? { ok: true, enabled: result } : result && typeof result === "object" ? result : { ok: false }
25833
+ };
25834
+ },
25881
25835
  // Theme is a TUI-local concern (no runtime round-trip). listThemes returns
25882
25836
  // picker metadata; getTheme reports the active id; setTheme applies the
25883
25837
  // palette in-place + persists ui.theme and bumps a themeEpoch so the React
@@ -26119,8 +26073,8 @@ function createEngineApiB(bag) {
26119
26073
  set({ commandBusy: false });
26120
26074
  }
26121
26075
  },
26122
- listSessions: () => {
26123
- return runtime.listSessions();
26076
+ listSessions: (options) => {
26077
+ return runtime.listSessions(options);
26124
26078
  },
26125
26079
  newSession: async () => {
26126
26080
  if (getState().commandBusy) return false;
@@ -26961,7 +26915,7 @@ function createEngineApiA(bag) {
26961
26915
  const requeueEntries = restoreState && !restoreState.committed && Array.isArray(restoreState.requeueEntries) ? restoreState.requeueEntries.filter(
26962
26916
  (entry) => entry?.abortDiscardOnAbort !== true && entry?.mode !== "pending-resume"
26963
26917
  ) : [];
26964
- const aborted = runtime.abort("cli-react-abort");
26918
+ const aborted = runtime.abort(hasPendingSteering ? "interrupt" : "user-cancel");
26965
26919
  if (restoreState) {
26966
26920
  if (aborted !== false && Array.isArray(restoreState.discardExecutionPendingResumeKeys)) {
26967
26921
  discardExecutionPendingResume?.(restoreState.discardExecutionPendingResumeKeys);
@@ -27108,7 +27062,9 @@ async function createEngineSession({
27108
27062
  provider: providerName,
27109
27063
  model,
27110
27064
  toolMode = "full",
27111
- remote = false
27065
+ remote = false,
27066
+ cwd,
27067
+ desktopSession
27112
27068
  } = {}) {
27113
27069
  const startedAt = performance2.now();
27114
27070
  bootProfile("engine:create:start", { provider: providerName, model, toolMode, remote });
@@ -27120,9 +27076,16 @@ async function createEngineSession({
27120
27076
  const importStartedAt = performance2.now();
27121
27077
  const { createMixdogSessionRuntime } = await import(SESSION_RUNTIME_MODULE);
27122
27078
  bootProfile("session-runtime:imported", { ms: (performance2.now() - importStartedAt).toFixed(1) });
27123
- const runtime = await createMixdogSessionRuntime({ provider: providerName, model, toolMode, remote });
27079
+ const runtime = await createMixdogSessionRuntime({
27080
+ provider: providerName,
27081
+ model,
27082
+ toolMode,
27083
+ remote,
27084
+ ...cwd ? { cwd } : {},
27085
+ ...desktopSession ? { desktopSession } : {}
27086
+ });
27124
27087
  bootProfile("engine:create:runtime-ready", { ms: (performance2.now() - startedAt).toFixed(1) });
27125
- const cwd = runtime.cwd || process.cwd();
27088
+ const runtimeCwd = runtime.cwd || process.cwd();
27126
27089
  const stateStartedAt = performance2.now();
27127
27090
  const flags = {
27128
27091
  disposed: false,
@@ -27180,14 +27143,14 @@ async function createEngineSession({
27180
27143
  activeToolSummary: null,
27181
27144
  // Seed from the persisted cwd-scoped store so up-arrow history is available
27182
27145
  // on a fresh start, before any bulk swap / first submit republishes it.
27183
- promptHistoryList: buildMergedPromptHistory([], loadPromptHistory(cwd)),
27146
+ promptHistoryList: buildMergedPromptHistory([], loadPromptHistory(runtimeCwd)),
27184
27147
  ...baseRouteState(),
27185
27148
  displayContextWindow: 0,
27186
27149
  compactBoundaryTokens: 0,
27187
27150
  autoCompactTokenLimit: 0,
27188
27151
  ...initialAgentState,
27189
27152
  toolMode: runtime.toolMode,
27190
- cwd,
27153
+ cwd: runtimeCwd,
27191
27154
  themeEpoch: 0
27192
27155
  };
27193
27156
  bootProfile("engine:route-state-ready", { ms: (performance2.now() - stateStartedAt).toFixed(1) });
@@ -27569,154 +27532,9 @@ async function createEngineSession({
27569
27532
  return createEngineApi(bag);
27570
27533
  }
27571
27534
 
27572
- // src/runtime/shared/process-shutdown.mjs
27573
- var SIGNAL_EXIT_CODES = {
27574
- SIGINT: 130,
27575
- SIGTERM: 143,
27576
- SIGHUP: 129
27577
- };
27578
- function errorText(error) {
27579
- return error?.stack || error?.message || String(error);
27580
- }
27581
- function writeStderr(line) {
27582
- try {
27583
- process.stderr.write(`${line}
27584
- `);
27585
- } catch {
27586
- }
27587
- }
27588
- function signalExitCode(signal, fallback = 1) {
27589
- return SIGNAL_EXIT_CODES[signal] || fallback;
27590
- }
27591
- function waitWithTimeout(promise, timeoutMs, label = "cleanup") {
27592
- const ms = Math.max(1, Math.floor(Number(timeoutMs) || 1));
27593
- return new Promise((resolve5, reject) => {
27594
- let settled = false;
27595
- const timer2 = setTimeout(() => {
27596
- if (settled) return;
27597
- settled = true;
27598
- reject(new Error(`${label} timed out after ${ms}ms`));
27599
- }, ms);
27600
- if (typeof timer2.unref === "function") timer2.unref();
27601
- Promise.resolve(promise).then((value) => {
27602
- if (settled) return;
27603
- settled = true;
27604
- clearTimeout(timer2);
27605
- resolve5(value);
27606
- }).catch((error) => {
27607
- if (settled) return;
27608
- settled = true;
27609
- clearTimeout(timer2);
27610
- reject(error);
27611
- });
27612
- });
27613
- }
27614
- function installProcessSignalCleanup({
27615
- name = "mixdog",
27616
- signals = ["SIGINT", "SIGTERM", "SIGHUP"],
27617
- timeoutMs = 6500,
27618
- exit = true,
27619
- fatal = true,
27620
- beforeCleanup,
27621
- cleanup,
27622
- afterCleanup,
27623
- log = writeStderr
27624
- } = {}) {
27625
- let installed = true;
27626
- let running = false;
27627
- let hardExitTimer = null;
27628
- const handlers = [];
27629
- const removeHandlers = () => {
27630
- for (const [event, handler] of handlers.splice(0)) {
27631
- try {
27632
- process.removeListener(event, handler);
27633
- } catch {
27634
- }
27635
- }
27636
- };
27637
- const hardExit = (code) => {
27638
- try {
27639
- process.exit(code);
27640
- } catch {
27641
- }
27642
- };
27643
- const run = async (reason = "process-exit", {
27644
- code = 0,
27645
- shouldExit = false,
27646
- error = null
27647
- } = {}) => {
27648
- if (running) {
27649
- if (shouldExit) hardExit(code);
27650
- return false;
27651
- }
27652
- running = true;
27653
- removeHandlers();
27654
- if (shouldExit) {
27655
- hardExitTimer = setTimeout(() => hardExit(code), Math.max(1e3, Number(timeoutMs) + 1e3));
27656
- if (typeof hardExitTimer.unref === "function") hardExitTimer.unref();
27657
- }
27658
- if (error && typeof log === "function") {
27659
- log(`[${name}] ${reason}: ${errorText(error)}`);
27660
- }
27661
- try {
27662
- beforeCleanup?.(reason, { code, error });
27663
- } catch {
27664
- }
27665
- try {
27666
- if (typeof cleanup === "function") {
27667
- await waitWithTimeout(cleanup(reason, { code, error }), timeoutMs, `${name} shutdown`);
27668
- }
27669
- } catch (cleanupError) {
27670
- if (typeof log === "function") log(`[${name}] cleanup failed: ${errorText(cleanupError)}`);
27671
- }
27672
- try {
27673
- afterCleanup?.(reason, { code, error });
27674
- } catch {
27675
- }
27676
- if (hardExitTimer) {
27677
- clearTimeout(hardExitTimer);
27678
- hardExitTimer = null;
27679
- }
27680
- running = false;
27681
- if (shouldExit) hardExit(code);
27682
- return true;
27683
- };
27684
- const add = (event, handler) => {
27685
- try {
27686
- process.once(event, handler);
27687
- handlers.push([event, handler]);
27688
- } catch {
27689
- }
27690
- };
27691
- for (const signal of signals) {
27692
- if (!signal) continue;
27693
- add(signal, () => {
27694
- void run(signal, { code: signalExitCode(signal), shouldExit: exit });
27695
- });
27696
- }
27697
- if (fatal) {
27698
- add("uncaughtException", (error) => {
27699
- void run("uncaughtException", { code: 1, shouldExit: exit, error });
27700
- });
27701
- add("unhandledRejection", (error) => {
27702
- void run("unhandledRejection", { code: 1, shouldExit: exit, error });
27703
- });
27704
- }
27705
- return {
27706
- run,
27707
- uninstall() {
27708
- if (!installed) return;
27709
- installed = false;
27710
- removeHandlers();
27711
- if (hardExitTimer) {
27712
- clearTimeout(hardExitTimer);
27713
- hardExitTimer = null;
27714
- }
27715
- }
27716
- };
27717
- }
27718
-
27719
27535
  // src/tui/index.jsx
27536
+ import { installProcessSignalCleanup } from "../../runtime/shared/process-shutdown.mjs";
27537
+ import { finishProcessLifecycle } from "../../runtime/shared/process-lifecycle.mjs";
27720
27538
  var import_mixdog_debug = __toESM(require_mixdog_debug(), 1);
27721
27539
  import { jsx as jsx21 } from "react/jsx-runtime";
27722
27540
  var TERMINAL_MODE_RESET = "\x1B[?1006l\x1B[?1005l\x1B[?1015l\x1B[?1003l\x1B[?1002l\x1B[?1000l\x1B[?2004l\x1B[>0s\x1B[?25h";
@@ -27913,7 +27731,7 @@ function dumpActiveHandles(label) {
27913
27731
  tuiExitDebug(`${label}:failed`, { error: error?.message || String(error) });
27914
27732
  }
27915
27733
  }
27916
- function waitWithTimeout2(promise, ms) {
27734
+ function waitWithTimeout(promise, ms) {
27917
27735
  let timer2 = null;
27918
27736
  return Promise.race([
27919
27737
  Promise.resolve(promise).then(() => true),
@@ -28147,7 +27965,8 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
28147
27965
  const disposeStoreOnce = async (reason = "cli-react-exit") => {
28148
27966
  if (storeDisposed) return;
28149
27967
  storeDisposed = true;
28150
- await waitWithTimeout2(store.dispose?.(reason), EXIT_WAIT_TIMEOUT_MS);
27968
+ const completed = await waitWithTimeout(store.dispose?.(reason), EXIT_WAIT_TIMEOUT_MS);
27969
+ if (!completed) throw new Error(`mixdog-tui dispose timed out after ${EXIT_WAIT_TIMEOUT_MS}ms`);
28151
27970
  };
28152
27971
  const signalCleanup = installProcessSignalCleanup({
28153
27972
  name: "mixdog-tui",
@@ -28221,6 +28040,7 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
28221
28040
  await disposeStoreOnce("cli-react-exit");
28222
28041
  } catch (error) {
28223
28042
  tuiExitDebug("store:dispose:failed", { error: error?.message || String(error) });
28043
+ finishProcessLifecycle("forced-cleanup", 0);
28224
28044
  }
28225
28045
  restoreTerminal();
28226
28046
  dumpActiveHandles("after-restore");
@@ -6,6 +6,7 @@ import { resetAllStreamingMarkdownStablePrefixes } from '../markdown/streaming-m
6
6
  import { toolResultText } from './tool-result-text.mjs';
7
7
  import { parseSyntheticAgentMessage } from './agent-envelope.mjs';
8
8
  import { flushTuiSteeringPersist } from './tui-steering-persist.mjs';
9
+ import { getVoiceStatus, toggleVoice } from '../lib/voice-setup.mjs';
9
10
 
10
11
  export function createEngineApiB(bag) {
11
12
  const {
@@ -118,6 +119,16 @@ export function createEngineApiB(bag) {
118
119
  return next;
119
120
  },
120
121
  isRemoteEnabled: () => runtime.isRemoteEnabled?.() === true,
122
+ getVoiceStatus: () => getVoiceStatus(),
123
+ toggleVoice: async () => {
124
+ const result = await toggleVoice({ pushNotice, setProgressHint });
125
+ return {
126
+ ...(await getVoiceStatus()),
127
+ result: typeof result === 'boolean'
128
+ ? { ok: true, enabled: result }
129
+ : (result && typeof result === 'object' ? result : { ok: false }),
130
+ };
131
+ },
121
132
  // Theme is a TUI-local concern (no runtime round-trip). listThemes returns
122
133
  // picker metadata; getTheme reports the active id; setTheme applies the
123
134
  // palette in-place + persists ui.theme and bumps a themeEpoch so the React
@@ -362,8 +373,8 @@ export function createEngineApiB(bag) {
362
373
  set({ commandBusy: false });
363
374
  }
364
375
  },
365
- listSessions: () => {
366
- return runtime.listSessions();
376
+ listSessions: (options) => {
377
+ return runtime.listSessions(options);
367
378
  },
368
379
  newSession: async () => {
369
380
  if (getState().commandBusy) return false;
@@ -580,7 +580,7 @@ export function createEngineApiA(bag) {
580
580
  (entry) => entry?.abortDiscardOnAbort !== true && entry?.mode !== 'pending-resume',
581
581
  )
582
582
  : [];
583
- const aborted = runtime.abort('cli-react-abort');
583
+ const aborted = runtime.abort(hasPendingSteering ? 'interrupt' : 'user-cancel');
584
584
  if (restoreState) {
585
585
  if (aborted !== false && Array.isArray(restoreState.discardExecutionPendingResumeKeys)) {
586
586
  discardExecutionPendingResume?.(restoreState.discardExecutionPendingResumeKeys);