mixdog 0.9.95 → 0.9.96

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 (178) hide show
  1. package/LICENSES/Apache-2.0.txt +201 -0
  2. package/LICENSES/MIT.txt +56 -0
  3. package/LICENSES/codex-NOTICE.txt +6 -0
  4. package/NOTICE.md +46 -9
  5. package/package.json +12 -9
  6. package/scripts/.tmp-coverage-report.mjs +96 -0
  7. package/scripts/build-tui.mjs +17 -1
  8. package/scripts/fixtures/patch-replay-corpus.json +98 -0
  9. package/src/defaults/mixdog-config.template.json +5 -8
  10. package/src/rules/agent/30-explorer.md +7 -4
  11. package/src/rules/shared/01-tool.md +20 -29
  12. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +18 -2
  13. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +31 -1
  14. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +6 -17
  15. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +12 -1
  16. package/src/runtime/agent/orchestrator/config.mjs +246 -67
  17. package/src/runtime/agent/orchestrator/mcp/client.mjs +26 -11
  18. package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +23 -0
  19. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +113 -14
  20. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +5 -1
  21. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +32 -11
  22. package/src/runtime/agent/orchestrator/providers/gemini.mjs +89 -2
  23. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +12 -1
  24. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +19 -1
  25. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +144 -39
  26. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +22 -8
  27. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +15 -1
  28. package/src/runtime/agent/orchestrator/providers/openai-transport-policy.mjs +1 -1
  29. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +3 -4
  30. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +67 -7
  31. package/src/runtime/agent/orchestrator/providers/stream-json-pool.mjs +291 -0
  32. package/src/runtime/agent/orchestrator/providers/stream-json-worker.mjs +21 -0
  33. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +28 -4
  34. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -33
  35. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +13 -0
  36. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +2 -2
  37. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +0 -24
  38. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +1 -27
  39. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +6 -10
  40. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +5 -0
  41. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +142 -84
  42. package/src/runtime/agent/orchestrator/session/manager/runtime-loaders.mjs +4 -0
  43. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +31 -1
  44. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +29 -9
  45. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -0
  46. package/src/runtime/agent/orchestrator/session/result-classification.mjs +65 -0
  47. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +86 -8
  48. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +12 -0
  49. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +3 -33
  50. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -5
  51. package/src/runtime/agent/orchestrator/stall-policy.mjs +1 -1
  52. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +2 -2
  53. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +35 -1
  54. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +118 -18
  55. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +8 -9
  56. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +2 -2
  57. package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +2 -2
  58. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +6 -15
  59. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +10 -26
  60. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +1 -1
  61. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +3 -3
  62. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -23
  63. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +23 -9
  64. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +7 -0
  65. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +17 -39
  66. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +73 -0
  67. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +1 -1
  68. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-spawn.mjs +0 -2
  69. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +3 -8
  70. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +30 -0
  71. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +5 -5
  72. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +87 -15
  73. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +6 -15
  74. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +2 -2
  75. package/src/runtime/agent/orchestrator/tools/lib/pwsh-standby-pool.mjs +83 -13
  76. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +39 -1
  77. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +29 -1
  78. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +16 -0
  79. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +108 -12
  80. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +10 -10
  81. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +56 -8
  82. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +2 -2
  83. package/src/runtime/channels/lib/config.mjs +2 -2
  84. package/src/runtime/channels/lib/inbound-handler.mjs +1 -1
  85. package/src/runtime/channels/lib/scheduler.mjs +2 -2
  86. package/src/runtime/memory/index.mjs +5 -38
  87. package/src/runtime/memory/lib/agent-ipc.mjs +145 -81
  88. package/src/runtime/memory/lib/compact-vector-cache.mjs +88 -0
  89. package/src/runtime/memory/lib/core-memory-store.mjs +18 -1
  90. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +12 -33
  91. package/src/runtime/memory/lib/embedding-provider.mjs +47 -26
  92. package/src/runtime/memory/lib/embedding-worker.mjs +30 -34
  93. package/src/runtime/memory/lib/http-router.mjs +8 -0
  94. package/src/runtime/memory/lib/ko-morph.mjs +3 -20
  95. package/src/runtime/memory/lib/memory-action-handlers.mjs +2 -1
  96. package/src/runtime/memory/lib/memory-cycle1.mjs +1 -0
  97. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +4 -2
  98. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +1 -0
  99. package/src/runtime/memory/lib/memory-cycle3.mjs +3 -1
  100. package/src/runtime/memory/lib/pg/process.mjs +32 -9
  101. package/src/runtime/memory/tool-defs.mjs +1 -5
  102. package/src/runtime/search/tool-defs.mjs +2 -2
  103. package/src/runtime/shared/agent-route-config.mjs +119 -0
  104. package/src/runtime/shared/atomic-file.mjs +80 -4
  105. package/src/runtime/shared/channel-notification-routing.mjs +2 -2
  106. package/src/runtime/shared/child-guardian.mjs +204 -108
  107. package/src/runtime/shared/child-spawn-gate.mjs +60 -31
  108. package/src/runtime/shared/config.mjs +130 -44
  109. package/src/runtime/shared/resource-admission.mjs +146 -97
  110. package/src/runtime/shared/stream-progress.mjs +6 -0
  111. package/src/runtime/shared/tool-surface.mjs +0 -5
  112. package/src/runtime/shared/turn-snapshot.mjs +31 -5
  113. package/src/runtime/shared/turn-worktree-snapshot.mjs +14 -2
  114. package/src/session-runtime/config-helpers.mjs +39 -37
  115. package/src/session-runtime/model-route-api.mjs +7 -5
  116. package/src/session-runtime/provider-auth-api.mjs +0 -7
  117. package/src/session-runtime/runtime-core.mjs +11 -11
  118. package/src/session-runtime/runtime-tunables.mjs +4 -4
  119. package/src/session-runtime/session-lifecycle.mjs +9 -2
  120. package/src/session-runtime/session-turn-api.mjs +41 -9
  121. package/src/session-runtime/settings-api.mjs +5 -17
  122. package/src/session-runtime/tool-defs.mjs +3 -3
  123. package/src/session-runtime/workflow-agents-api.mjs +55 -81
  124. package/src/session-runtime/workflow.mjs +14 -24
  125. package/src/standalone/agent-dispatch-broker.mjs +198 -0
  126. package/src/standalone/agent-tool/helpers.mjs +2 -2
  127. package/src/standalone/agent-tool/spawn-flow.mjs +2 -0
  128. package/src/standalone/agent-tool/spawn-preset.mjs +11 -21
  129. package/src/standalone/agent-tool/tool-def.mjs +0 -14
  130. package/src/standalone/backend-daemon.mjs +97 -36
  131. package/src/standalone/channel-admin.mjs +12 -12
  132. package/src/standalone/channel-daemon-client.mjs +17 -1
  133. package/src/standalone/channel-daemon-transport.mjs +90 -8
  134. package/src/standalone/engine-daemon-client.mjs +380 -205
  135. package/src/standalone/engine-daemon-protocol.mjs +3 -4
  136. package/src/standalone/engine-daemon-service.mjs +378 -134
  137. package/src/standalone/engine-daemon-transport.mjs +268 -77
  138. package/src/standalone/explore-tool.mjs +187 -70
  139. package/src/standalone/fair-call-scheduler.mjs +264 -0
  140. package/src/standalone/session-protocol.mjs +188 -0
  141. package/src/tui/App.jsx +30 -17
  142. package/src/tui/app/app-view.jsx +5 -2
  143. package/src/tui/app/core-memory-picker.mjs +19 -19
  144. package/src/tui/app/doctor.mjs +5 -11
  145. package/src/tui/app/message-selector.mjs +103 -0
  146. package/src/tui/app/onboarding-steps.mjs +1 -2
  147. package/src/tui/app/project-picker.mjs +80 -56
  148. package/src/tui/app/prompt-submit.mjs +46 -41
  149. package/src/tui/app/resume-picker.mjs +2 -2
  150. package/src/tui/app/route-pickers.mjs +3 -1
  151. package/src/tui/app/settings-picker.mjs +6 -8
  152. package/src/tui/app/transcript-window.mjs +38 -0
  153. package/src/tui/app/use-prompt-draft-flow.mjs +5 -7
  154. package/src/tui/app/use-prompt-handlers.mjs +86 -21
  155. package/src/tui/app/use-prompt-hint.mjs +3 -2
  156. package/src/tui/app/use-prompt-queue-history.mjs +10 -3
  157. package/src/tui/app/use-transcript-scroll.mjs +9 -2
  158. package/src/tui/app/use-transcript-window.mjs +50 -17
  159. package/src/tui/components/PromptInput.jsx +71 -38
  160. package/src/tui/components/Spinner.jsx +1 -1
  161. package/src/tui/components/prompt-input/edit-helpers.mjs +9 -11
  162. package/src/tui/components/prompt-input/escape-policy.mjs +42 -0
  163. package/src/tui/components/prompt-input/immediate-render.mjs +0 -10
  164. package/src/tui/components/prompt-input/interrupt-policy.mjs +10 -0
  165. package/src/tui/components/prompt-input/restore-policy.mjs +10 -0
  166. package/src/tui/dist/index.mjs +920 -1592
  167. package/src/tui/engine/live-share.mjs +34 -0
  168. package/src/tui/engine/session-api-ext.mjs +1 -4
  169. package/src/tui/engine/session-api.mjs +103 -33
  170. package/src/tui/engine/session-flow.mjs +7 -1
  171. package/src/tui/engine/tool-card-results.mjs +4 -2
  172. package/src/tui/engine/tool-result-text.mjs +10 -0
  173. package/src/tui/engine/turn.mjs +9 -69
  174. package/src/tui/engine-local-session.mjs +47 -31
  175. package/src/tui/index.jsx +1 -2
  176. package/src/tui/spinner-meta.mjs +4 -4
  177. package/src/tui/spinner-verbs.mjs +51 -3
  178. package/src/standalone/engine-daemon-local-bridge.mjs +0 -20
@@ -27,10 +27,6 @@ var __commonJS = (cb, mod) => function __require2() {
27
27
  throw mod = 0, e;
28
28
  }
29
29
  };
30
- var __export = (target, all) => {
31
- for (var name in all)
32
- __defProp(target, name, { get: all[name], enumerable: true });
33
- };
34
30
  var __copyProps = (to, from, except, desc) => {
35
31
  if (from && typeof from === "object" || typeof from === "function") {
36
32
  for (let key of __getOwnPropNames(from))
@@ -210,11 +206,11 @@ function cleanPathText(value) {
210
206
  }
211
207
  return text;
212
208
  }
213
- function mimeForPath(path3) {
214
- return IMAGE_MIME[extname(String(path3 || "")).toLowerCase()] || null;
209
+ function mimeForPath(path2) {
210
+ return IMAGE_MIME[extname(String(path2 || "")).toLowerCase()] || null;
215
211
  }
216
212
  function execFileBuffer(cmd, args, options = {}) {
217
- return new Promise((resolve5) => {
213
+ return new Promise((resolve4) => {
218
214
  execFile(cmd, args, {
219
215
  windowsHide: true,
220
216
  encoding: "buffer",
@@ -222,7 +218,7 @@ function execFileBuffer(cmd, args, options = {}) {
222
218
  timeout: 5e3,
223
219
  ...options
224
220
  }, (error, stdout2, stderr) => {
225
- resolve5({ ok: !error, code: error?.code ?? 0, stdout: stdout2, stderr, error });
221
+ resolve4({ ok: !error, code: error?.code ?? 0, stdout: stdout2, stderr, error });
226
222
  });
227
223
  });
228
224
  }
@@ -419,16 +415,16 @@ var init_paste_attachments = __esm({
419
415
  // src/runtime/shared/atomic-file.mjs
420
416
  import {
421
417
  closeSync,
422
- existsSync as existsSync4,
418
+ existsSync as existsSync3,
423
419
  fsyncSync,
424
420
  linkSync,
425
- mkdirSync as mkdirSync3,
421
+ mkdirSync as mkdirSync2,
426
422
  openSync,
427
- readFileSync as readFileSync3,
428
- renameSync as renameSync2,
429
- statSync as statSync2,
423
+ readFileSync as readFileSync2,
424
+ renameSync,
425
+ statSync,
430
426
  unlinkSync as unlinkSync2,
431
- writeFileSync as writeFileSync3
427
+ writeFileSync as writeFileSync2
432
428
  } from "fs";
433
429
  import {
434
430
  link as linkAsync,
@@ -439,11 +435,41 @@ import {
439
435
  unlink as unlinkAsync,
440
436
  writeFile as writeFileAsync
441
437
  } from "node:fs/promises";
442
- import { dirname as dirname4, basename as basename4, join as join4 } from "path";
438
+ import { dirname as dirname3, basename as basename3, join as join3 } from "path";
443
439
  import { randomBytes as randomBytes2 } from "crypto";
444
440
  import { execFile as execFile2, execFileSync } from "child_process";
445
441
  import { promisify } from "util";
446
442
  import { AsyncLocalStorage } from "node:async_hooks";
443
+ function _drainAdaptiveFileIo() {
444
+ while (_ioActive < _ioLimit && _ioQueue.length > 0) {
445
+ const item = _ioQueue.shift();
446
+ _ioActive += 1;
447
+ const startedAt = Date.now();
448
+ Promise.resolve().then(item.run).then(item.resolve, item.reject).finally(() => {
449
+ const elapsed = Date.now() - startedAt;
450
+ _ioActive = Math.max(0, _ioActive - 1);
451
+ if (elapsed >= 250) {
452
+ _ioLimit = Math.max(1, Math.ceil(_ioLimit / 2));
453
+ _ioFastStreak = 0;
454
+ } else if (elapsed <= 40 && _ioLimit < _ioCeiling) {
455
+ _ioFastStreak += 1;
456
+ if (_ioFastStreak >= _ioLimit * 4) {
457
+ _ioLimit += 1;
458
+ _ioFastStreak = 0;
459
+ }
460
+ } else {
461
+ _ioFastStreak = 0;
462
+ }
463
+ _drainAdaptiveFileIo();
464
+ });
465
+ }
466
+ }
467
+ function _runAdaptiveFileIo(run) {
468
+ return new Promise((resolve4, reject) => {
469
+ _ioQueue.push({ run, resolve: resolve4, reject });
470
+ _drainAdaptiveFileIo();
471
+ });
472
+ }
447
473
  function sleepSync(ms) {
448
474
  try {
449
475
  const buf = new SharedArrayBuffer(4);
@@ -456,7 +482,7 @@ function sleepSync(ms) {
456
482
  }
457
483
  function _describeLockHolder(lockPath) {
458
484
  try {
459
- const st = statSync2(lockPath);
485
+ const st = statSync(lockPath);
460
486
  const owner = _readLockOwner(lockPath);
461
487
  const ageMs = Math.max(0, Math.round(Date.now() - st.mtimeMs));
462
488
  const live = owner.pid === null ? "unknown" : _ownerIsLive(owner) ? "live" : "dead";
@@ -466,12 +492,28 @@ function _describeLockHolder(lockPath) {
466
492
  return "holder unknown (lock file unreadable/absent)";
467
493
  }
468
494
  }
495
+ function _reportLockWait(lockPath, waitedMs, mode) {
496
+ if (waitedMs < LOCK_WAIT_WARN_MS) return;
497
+ const now = Date.now();
498
+ if (now - (_lockWaitWarnedAt.get(lockPath) || 0) < LOCK_WAIT_WARN_INTERVAL_MS) return;
499
+ _lockWaitWarnedAt.set(lockPath, now);
500
+ if (_lockWaitWarnedAt.size > 64) {
501
+ for (const [path2, at] of _lockWaitWarnedAt) {
502
+ if (now - at >= LOCK_WAIT_WARN_INTERVAL_MS * 6) _lockWaitWarnedAt.delete(path2);
503
+ }
504
+ }
505
+ try {
506
+ process.stderr.write(`[atomic-file] ${mode} lock wait ${waitedMs}ms: ${lockPath}
507
+ `);
508
+ } catch {
509
+ }
510
+ }
469
511
  function renameWithRetrySync(src, dst, opts = {}) {
470
512
  const backoffs = Array.isArray(opts.backoffs) && opts.backoffs.length > 0 ? opts.backoffs : DEFAULT_BACKOFFS_MS;
471
513
  let lastErr = null;
472
514
  for (let attempt = 0; attempt <= backoffs.length; attempt++) {
473
515
  try {
474
- renameSync2(src, dst);
516
+ renameSync(src, dst);
475
517
  return true;
476
518
  } catch (err) {
477
519
  lastErr = err;
@@ -500,7 +542,7 @@ async function renameWithRetry(src, dst, opts = {}) {
500
542
  }
501
543
  function _readLockOwner(lockPath) {
502
544
  try {
503
- const raw = readFileSync3(lockPath, "utf8");
545
+ const raw = readFileSync2(lockPath, "utf8");
504
546
  const parts = String(raw).trim().split(/\s+/);
505
547
  const pid = Number.parseInt(parts[0], 10);
506
548
  return {
@@ -529,7 +571,7 @@ function _lockOwnedBySelf(lockPath) {
529
571
  function _tryReclaimStaleLock(lockPath, staleMs) {
530
572
  let initial;
531
573
  try {
532
- initial = statSync2(lockPath);
574
+ initial = statSync(lockPath);
533
575
  } catch {
534
576
  return false;
535
577
  }
@@ -542,7 +584,7 @@ function _tryReclaimStaleLock(lockPath, staleMs) {
542
584
  try {
543
585
  let cur;
544
586
  try {
545
- cur = statSync2(lockPath);
587
+ cur = statSync(lockPath);
546
588
  } catch {
547
589
  return false;
548
590
  }
@@ -569,7 +611,7 @@ function _tryAcquireReclaimGuard(lockPath, staleMs) {
569
611
  `;
570
612
  const stagedPath = `${guardPath}.${process.pid}.${randomBytes2(8).toString("hex")}.tmp`;
571
613
  try {
572
- writeFileSync3(stagedPath, token, { encoding: "utf8", mode: 384, flag: "wx" });
614
+ writeFileSync2(stagedPath, token, { encoding: "utf8", mode: 384, flag: "wx" });
573
615
  linkSync(stagedPath, guardPath);
574
616
  unlinkSync2(stagedPath);
575
617
  return { guardPath };
@@ -591,8 +633,8 @@ function _releaseReclaimGuard(reclaim) {
591
633
  }
592
634
  }
593
635
  function _asyncSleep(ms) {
594
- return new Promise((resolve5) => {
595
- setTimeout(resolve5, Math.max(1, Number(ms) || 1));
636
+ return new Promise((resolve4) => {
637
+ setTimeout(resolve4, Math.max(1, Number(ms) || 1));
596
638
  });
597
639
  }
598
640
  async function withFileLock(lockPath, fn, opts = {}) {
@@ -618,7 +660,8 @@ async function _withOsFileLock(lockPath, fn, opts = {}) {
618
660
  const timeoutMs = Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : DEFAULT_LOCK_TIMEOUT_MS;
619
661
  const staleMs = Number.isFinite(opts.staleMs) ? opts.staleMs : 3e4;
620
662
  const deadline = Date.now() + timeoutMs;
621
- mkdirSync3(dirname4(lockPath), { recursive: true });
663
+ mkdirSync2(dirname3(lockPath), { recursive: true });
664
+ const waitStartedAt = Date.now();
622
665
  let attempt = 0;
623
666
  let lastErr = null;
624
667
  while (true) {
@@ -646,10 +689,11 @@ async function _withOsFileLock(lockPath, fn, opts = {}) {
646
689
  continue;
647
690
  }
648
691
  try {
649
- writeFileSync3(fd, `${process.pid} ${Date.now()} ${_OWNER_TOKEN}
692
+ writeFileSync2(fd, `${process.pid} ${Date.now()} ${_OWNER_TOKEN}
650
693
  `, "utf8");
651
694
  } catch {
652
695
  }
696
+ _reportLockWait(lockPath, Date.now() - waitStartedAt, "async");
653
697
  _osHeldPaths.add(lockPath);
654
698
  try {
655
699
  if (opts.secret === true) await _enforceOwnerOnlyAclWin32Async(lockPath);
@@ -677,8 +721,8 @@ function _icaclsPrincipal(principal) {
677
721
  async function _resolveCurrentUserPrincipalAsync() {
678
722
  const systemRoot = process.env.SystemRoot || process.env.windir;
679
723
  if (systemRoot) {
680
- const whoami = join4(systemRoot, "System32", "whoami.exe");
681
- if (existsSync4(whoami)) {
724
+ const whoami = join3(systemRoot, "System32", "whoami.exe");
725
+ if (existsSync3(whoami)) {
682
726
  try {
683
727
  const { stdout: stdout2 } = await _execFileAsync(whoami, ["/user", "/fo", "csv", "/nh"], {
684
728
  encoding: "utf8",
@@ -702,8 +746,8 @@ async function _enforceOwnerOnlyAclWin32Async(targetPath) {
702
746
  err.code = "EACLNOROOT";
703
747
  throw err;
704
748
  }
705
- const icacls = join4(systemRoot, "System32", "icacls.exe");
706
- if (!existsSync4(icacls)) {
749
+ const icacls = join3(systemRoot, "System32", "icacls.exe");
750
+ if (!existsSync3(icacls)) {
707
751
  const err = new Error(`icacls.exe not found at ${icacls}; refusing to leave secret world-readable`);
708
752
  err.code = "EACLNOICACLS";
709
753
  throw err;
@@ -722,9 +766,9 @@ async function _enforceOwnerOnlyAclWin32Async(targetPath) {
722
766
  }
723
767
  async function writeFileAtomicAsync(filePath, data, opts = {}) {
724
768
  const run = async () => {
725
- const dir = dirname4(filePath);
769
+ const dir = dirname3(filePath);
726
770
  await mkdirAsync(dir, { recursive: true });
727
- const tmp = join4(dir, `.${basename4(filePath)}.${randomBytes2(12).toString("hex")}.tmp`);
771
+ const tmp = join3(dir, `.${basename3(filePath)}.${randomBytes2(12).toString("hex")}.tmp`);
728
772
  try {
729
773
  const writeOpts = { encoding: opts.encoding || "utf8", flag: "wx", mode: opts.mode !== void 0 ? opts.mode : 384 };
730
774
  await writeFileAsync(tmp, data, writeOpts);
@@ -785,18 +829,20 @@ async function writeFileAtomicAsync(filePath, data, opts = {}) {
785
829
  throw err;
786
830
  }
787
831
  };
788
- if (opts.lock === true) {
789
- return withFileLock(`${filePath}.lock`, run, opts);
790
- }
791
- return run();
832
+ return _runAdaptiveFileIo(() => opts.lock === true ? withFileLock(`${filePath}.lock`, run, opts) : run());
792
833
  }
793
834
  function writeJsonAtomicAsync(filePath, value, opts = {}) {
794
835
  return writeFileAtomicAsync(filePath, JSON.stringify(value, null, opts.compact ? 0 : 2) + "\n", opts);
795
836
  }
796
- var _execFileAsync, RETRY_CODES, LOCK_WAIT_CODES, DEFAULT_BACKOFFS_MS, _configuredLockTimeoutMs, DEFAULT_LOCK_TIMEOUT_MS, _OWNER_TOKEN, _osHeldPaths, _lockQueues, _heldLockPaths, _cachedUserSid;
837
+ var _execFileAsync, _ioCeiling, _ioLimit, _ioActive, _ioFastStreak, _ioQueue, RETRY_CODES, LOCK_WAIT_CODES, DEFAULT_BACKOFFS_MS, _configuredLockTimeoutMs, DEFAULT_LOCK_TIMEOUT_MS, _OWNER_TOKEN, _osHeldPaths, LOCK_WAIT_WARN_MS, LOCK_WAIT_WARN_INTERVAL_MS, _lockWaitWarnedAt, _lockQueues, _heldLockPaths, _cachedUserSid;
797
838
  var init_atomic_file = __esm({
798
839
  "src/runtime/shared/atomic-file.mjs"() {
799
840
  _execFileAsync = promisify(execFile2);
841
+ _ioCeiling = Math.max(1, Math.floor(Number(process.env.MIXDOG_FILE_IO_MAX_CONCURRENCY) || 16));
842
+ _ioLimit = Math.min(4, _ioCeiling);
843
+ _ioActive = 0;
844
+ _ioFastStreak = 0;
845
+ _ioQueue = [];
800
846
  RETRY_CODES = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY", "EEXIST"]);
801
847
  LOCK_WAIT_CODES = /* @__PURE__ */ new Set(["EEXIST", "EPERM", "EACCES", "EBUSY"]);
802
848
  DEFAULT_BACKOFFS_MS = Object.freeze([25, 50, 100, 200, 400, 800, 1200, 1600]);
@@ -804,6 +850,9 @@ var init_atomic_file = __esm({
804
850
  DEFAULT_LOCK_TIMEOUT_MS = Number.isFinite(_configuredLockTimeoutMs) && _configuredLockTimeoutMs >= 0 ? _configuredLockTimeoutMs : 2e3;
805
851
  _OWNER_TOKEN = randomBytes2(12).toString("hex");
806
852
  _osHeldPaths = /* @__PURE__ */ new Set();
853
+ LOCK_WAIT_WARN_MS = 500;
854
+ LOCK_WAIT_WARN_INTERVAL_MS = 1e4;
855
+ _lockWaitWarnedAt = /* @__PURE__ */ new Map();
807
856
  _lockQueues = /* @__PURE__ */ new Map();
808
857
  _heldLockPaths = new AsyncLocalStorage();
809
858
  _cachedUserSid = null;
@@ -813,13 +862,13 @@ var init_atomic_file = __esm({
813
862
  // src/runtime/shared/user-data-guard.mjs
814
863
  import {
815
864
  copyFileSync,
816
- existsSync as existsSync5,
817
- mkdirSync as mkdirSync4,
865
+ existsSync as existsSync4,
866
+ mkdirSync as mkdirSync3,
818
867
  readdirSync,
819
- readFileSync as readFileSync4,
868
+ readFileSync as readFileSync3,
820
869
  rmSync,
821
- statSync as statSync3,
822
- writeFileSync as writeFileSync4
870
+ statSync as statSync2,
871
+ writeFileSync as writeFileSync3
823
872
  } from "fs";
824
873
  import {
825
874
  copyFile as copyFileP,
@@ -828,14 +877,14 @@ import {
828
877
  rm as rmP,
829
878
  stat as statP
830
879
  } from "fs/promises";
831
- import { dirname as dirname5, join as join5, resolve as resolve3 } from "path";
832
- import { homedir as homedir3 } from "os";
880
+ import { dirname as dirname4, join as join4, resolve as resolve2 } from "path";
881
+ import { homedir as homedir2 } from "os";
833
882
  import { createHash } from "crypto";
834
883
  function mixdogConfigBaseDir() {
835
- return process.env.MIXDOG_CONFIG_DIR || join5(homedir3(), ".mixdog");
884
+ return process.env.MIXDOG_CONFIG_DIR || join4(homedir2(), ".mixdog");
836
885
  }
837
886
  function getBackupRoot() {
838
- return process.env.MIXDOG_USER_DATA_BACKUP_ROOT || join5(mixdogConfigBaseDir(), "backups", "user-data");
887
+ return process.env.MIXDOG_USER_DATA_BACKUP_ROOT || join4(mixdogConfigBaseDir(), "backups", "user-data");
839
888
  }
840
889
  function stamp() {
841
890
  return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -845,13 +894,13 @@ function safeReason(reason) {
845
894
  }
846
895
  function initMarkerPath(dataDir) {
847
896
  const id = createHash("sha256").update(String(dataDir || "unknown")).digest("hex").slice(0, 16);
848
- return join5(getBackupRoot(), `.initialized-${id}.json`);
897
+ return join4(getBackupRoot(), `.initialized-${id}.json`);
849
898
  }
850
899
  function isPlainObject(value) {
851
900
  return !!value && typeof value === "object" && !Array.isArray(value);
852
901
  }
853
902
  function hasUserDataInitMarker(dataDir) {
854
- return existsSync5(initMarkerPath(dataDir));
903
+ return existsSync4(initMarkerPath(dataDir));
855
904
  }
856
905
  function isStructurallyCompleteMixdogConfigBackup(parsed) {
857
906
  if (!isPlainObject(parsed)) return false;
@@ -868,10 +917,10 @@ function loadLatestMixdogConfigFromBackup(_dataDir) {
868
917
  return null;
869
918
  }
870
919
  for (const name of entries) {
871
- const cfgPath = join5(root, name, "mixdog-config.json");
872
- if (!existsSync5(cfgPath)) continue;
920
+ const cfgPath = join4(root, name, "mixdog-config.json");
921
+ if (!existsSync4(cfgPath)) continue;
873
922
  try {
874
- const parsed = JSON.parse(readFileSync4(cfgPath, "utf8"));
923
+ const parsed = JSON.parse(readFileSync3(cfgPath, "utf8"));
875
924
  if (isStructurallyCompleteMixdogConfigBackup(parsed)) return parsed;
876
925
  } catch {
877
926
  continue;
@@ -881,8 +930,8 @@ function loadLatestMixdogConfigFromBackup(_dataDir) {
881
930
  }
882
931
  function markUserDataInitialized(dataDir) {
883
932
  try {
884
- mkdirSync4(getBackupRoot(), { recursive: true });
885
- writeFileSync4(initMarkerPath(dataDir), JSON.stringify({
933
+ mkdirSync3(getBackupRoot(), { recursive: true });
934
+ writeFileSync3(initMarkerPath(dataDir), JSON.stringify({
886
935
  dataDir,
887
936
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
888
937
  }, null, 2) + "\n", "utf8");
@@ -893,12 +942,12 @@ async function copyTreeAsync(src, dst, copied) {
893
942
  const st = await statP(src);
894
943
  if (st.isDirectory()) {
895
944
  for (const name of await readdirP(src)) {
896
- await copyTreeAsync(join5(src, name), join5(dst, name), copied);
945
+ await copyTreeAsync(join4(src, name), join4(dst, name), copied);
897
946
  }
898
947
  return;
899
948
  }
900
949
  if (!st.isFile()) return;
901
- await mkdirP(dirname5(dst), { recursive: true });
950
+ await mkdirP(dirname4(dst), { recursive: true });
902
951
  await copyFileP(src, dst);
903
952
  copied.push(dst);
904
953
  }
@@ -911,7 +960,7 @@ async function pruneBackupsAsync(keep = 40) {
911
960
  }
912
961
  for (const name of entries.slice(keep)) {
913
962
  try {
914
- await rmP(join5(getBackupRoot(), name), { recursive: true, force: true });
963
+ await rmP(join4(getBackupRoot(), name), { recursive: true, force: true });
915
964
  } catch {
916
965
  }
917
966
  }
@@ -920,16 +969,16 @@ async function backupUserDataAsync(dataDir, reason = "snapshot") {
920
969
  if (process.env.MIXDOG_SKIP_USER_DATA_BACKUP === "1" || process.env.MIXDOG_SKIP_USER_DATA_BACKUP === "true") {
921
970
  return { dir: null, copied: [] };
922
971
  }
923
- if (!dataDir || !existsSync5(dataDir)) return { dir: null, copied: [] };
924
- const backupDir = join5(getBackupRoot(), `${stamp()}-${safeReason(reason)}`);
972
+ if (!dataDir || !existsSync4(dataDir)) return { dir: null, copied: [] };
973
+ const backupDir = join4(getBackupRoot(), `${stamp()}-${safeReason(reason)}`);
925
974
  const copied = [];
926
975
  for (const rel of USER_DATA_FILES) {
927
- const src = join5(dataDir, rel);
928
- if (existsSync5(src)) await copyTreeAsync(src, join5(backupDir, rel), copied);
976
+ const src = join4(dataDir, rel);
977
+ if (existsSync4(src)) await copyTreeAsync(src, join4(backupDir, rel), copied);
929
978
  }
930
979
  for (const rel of USER_DATA_DIRS) {
931
- const src = join5(dataDir, rel);
932
- if (existsSync5(src)) await copyTreeAsync(src, join5(backupDir, rel), copied);
980
+ const src = join4(dataDir, rel);
981
+ if (existsSync4(src)) await copyTreeAsync(src, join4(backupDir, rel), copied);
933
982
  }
934
983
  if (copied.length > 0) {
935
984
  markUserDataInitialized(dataDir);
@@ -980,8 +1029,8 @@ var init_provider_api_key = __esm({
980
1029
  });
981
1030
 
982
1031
  // src/runtime/shared/config.mjs
983
- import { readFileSync as readFileSync5, statSync as statSync4, mkdirSync as mkdirSync5, existsSync as existsSync6 } from "fs";
984
- import { join as join6, dirname as dirname6 } from "path";
1032
+ import { readFileSync as readFileSync4, statSync as statSync3, mkdirSync as mkdirSync4, existsSync as existsSync5 } from "fs";
1033
+ import { join as join5, dirname as dirname5 } from "path";
985
1034
  import { createRequire as createRequire3 } from "module";
986
1035
  function invalidateConfigReadCache() {
987
1036
  _configReadCache = null;
@@ -993,7 +1042,7 @@ function readConfigRawCached() {
993
1042
  return _configReadCache.raw;
994
1043
  }
995
1044
  try {
996
- const st2 = statSync4(CONFIG_PATH);
1045
+ const st2 = statSync3(CONFIG_PATH);
997
1046
  if (st2.mtimeMs === _configReadCache.mtimeMs && st2.size === _configReadCache.size) {
998
1047
  _configReadCache.atMs = now;
999
1048
  return _configReadCache.raw;
@@ -1003,7 +1052,7 @@ function readConfigRawCached() {
1003
1052
  }
1004
1053
  let st = null;
1005
1054
  try {
1006
- st = statSync4(CONFIG_PATH);
1055
+ st = statSync3(CONFIG_PATH);
1007
1056
  } catch (err) {
1008
1057
  if (err.code === "ENOENT") {
1009
1058
  _configReadCache = null;
@@ -1015,7 +1064,7 @@ function readConfigRawCached() {
1015
1064
  }
1016
1065
  let raw;
1017
1066
  try {
1018
- raw = readFileSync5(CONFIG_PATH, "utf8");
1067
+ raw = readFileSync4(CONFIG_PATH, "utf8");
1019
1068
  } catch (err) {
1020
1069
  if (err.code === "ENOENT") {
1021
1070
  _configReadCache = null;
@@ -1031,21 +1080,112 @@ function readConfigRawCached() {
1031
1080
  function isPlainObject2(value) {
1032
1081
  return !!value && typeof value === "object" && !Array.isArray(value);
1033
1082
  }
1083
+ function hasOwn(value, key) {
1084
+ return Object.prototype.hasOwnProperty.call(value || {}, key);
1085
+ }
1086
+ function canonicalizeStoredChannelsConfig(value = {}) {
1087
+ const next = isPlainObject2(value) ? { ...value } : {};
1088
+ delete next.promptInjection;
1089
+ delete next.schedules;
1090
+ delete next.nonInteractive;
1091
+ delete next.interactive;
1092
+ const channel = isPlainObject2(next.channel) ? { ...next.channel } : {};
1093
+ const legacyId = String(channel.channelId || "").trim();
1094
+ const discordId = String(channel.discordChannelId || "").trim();
1095
+ const telegramId = String(channel.telegramChatId || "").trim();
1096
+ const backend = next.backend === "telegram" ? "telegram" : "discord";
1097
+ if (legacyId) {
1098
+ if (!discordId && !telegramId) {
1099
+ if (backend === "telegram") channel.telegramChatId = legacyId;
1100
+ else channel.discordChannelId = legacyId;
1101
+ delete channel.channelId;
1102
+ } else if (backend === "telegram" && telegramId || backend === "discord" && discordId || discordId && telegramId) {
1103
+ delete channel.channelId;
1104
+ }
1105
+ } else {
1106
+ delete channel.channelId;
1107
+ }
1108
+ next.channel = channel;
1109
+ return next;
1110
+ }
1111
+ function canonicalizeUnifiedConfig(value = {}) {
1112
+ const next = isPlainObject2(value) ? { ...value } : {};
1113
+ const hadAgent = isPlainObject2(next.agent);
1114
+ const agent = hadAgent ? { ...next.agent } : {};
1115
+ if (!String(next.outputStyle || "").trim() && String(agent.outputStyle || "").trim()) {
1116
+ next.outputStyle = agent.outputStyle;
1117
+ }
1118
+ delete agent.outputStyle;
1119
+ for (const key of ["autoClear", "compaction", "shell"]) {
1120
+ if (!isPlainObject2(agent[key]) && isPlainObject2(next[key])) agent[key] = next[key];
1121
+ delete next[key];
1122
+ }
1123
+ if (isPlainObject2(next.mcpServers)) {
1124
+ agent.mcpServers = {
1125
+ ...next.mcpServers,
1126
+ ...isPlainObject2(agent.mcpServers) ? agent.mcpServers : {}
1127
+ };
1128
+ delete next.mcpServers;
1129
+ }
1130
+ if (!isPlainObject2(agent.searchRoute) && isPlainObject2(agent.search)) {
1131
+ agent.searchRoute = agent.search;
1132
+ }
1133
+ delete agent.search;
1134
+ delete agent.capabilities;
1135
+ const modules = isPlainObject2(agent.modules) ? { ...agent.modules } : {};
1136
+ if (hasOwn(modules, "memory")) {
1137
+ const legacyMemory = modules.memory;
1138
+ const explicitRecap = isPlainObject2(agent.recap) && hasOwn(agent.recap, "enabled");
1139
+ if (!explicitRecap) {
1140
+ const enabled = isPlainObject2(legacyMemory) ? legacyMemory.enabled !== false : legacyMemory !== false;
1141
+ agent.recap = { ...isPlainObject2(agent.recap) ? agent.recap : {}, enabled };
1142
+ }
1143
+ delete modules.memory;
1144
+ agent.modules = modules;
1145
+ }
1146
+ if (isPlainObject2(next.memory)) {
1147
+ const memory = { ...next.memory };
1148
+ const user = isPlainObject2(memory.user) ? { ...memory.user } : {};
1149
+ const legacyTitle = String(user.title || "").trim();
1150
+ if (legacyTitle && !String(agent.profile?.title || "").trim()) {
1151
+ agent.profile = { ...isPlainObject2(agent.profile) ? agent.profile : {}, title: legacyTitle };
1152
+ }
1153
+ delete user.title;
1154
+ if (Object.keys(user).length) memory.user = user;
1155
+ else delete memory.user;
1156
+ if (hasOwn(memory, "enabled")) {
1157
+ const explicitRecap = isPlainObject2(agent.recap) && hasOwn(agent.recap, "enabled");
1158
+ if (!explicitRecap) {
1159
+ agent.recap = { ...isPlainObject2(agent.recap) ? agent.recap : {}, enabled: memory.enabled !== false };
1160
+ }
1161
+ delete memory.enabled;
1162
+ }
1163
+ if (Object.keys(memory).length) next.memory = memory;
1164
+ else delete next.memory;
1165
+ }
1166
+ if (isPlainObject2(next.channels)) {
1167
+ next.channels = canonicalizeStoredChannelsConfig(next.channels);
1168
+ }
1169
+ delete next.search;
1170
+ delete next.capabilities;
1171
+ if (hadAgent || Object.keys(agent).length) next.agent = agent;
1172
+ return next;
1173
+ }
1034
1174
  function stripGeneratedMarker(data) {
1035
1175
  if (!isPlainObject2(data) || !Object.prototype.hasOwnProperty.call(data, GENERATED_KEY)) return data;
1036
1176
  const { [GENERATED_KEY]: _generated, ...rest } = data;
1037
1177
  return rest;
1038
1178
  }
1039
- function readJsonFile(path3) {
1179
+ function readJsonFile(path2) {
1040
1180
  let raw;
1041
- if (path3 === CONFIG_PATH) {
1181
+ if (path2 === CONFIG_PATH) {
1042
1182
  raw = readConfigRawCached();
1043
1183
  } else {
1044
1184
  try {
1045
- raw = readFileSync5(path3, "utf8");
1185
+ raw = readFileSync4(path2, "utf8");
1046
1186
  } catch (err) {
1047
1187
  if (err.code === "ENOENT") return null;
1048
- process.stderr.write(`[config] readJsonFile: unexpected read error for ${path3}: ${err.message}
1188
+ process.stderr.write(`[config] readJsonFile: unexpected read error for ${path2}: ${err.message}
1049
1189
  `);
1050
1190
  throw err;
1051
1191
  }
@@ -1054,11 +1194,11 @@ function readJsonFile(path3) {
1054
1194
  try {
1055
1195
  return JSON.parse(raw);
1056
1196
  } catch (err) {
1057
- if (path3 === CONFIG_PATH) {
1197
+ if (path2 === CONFIG_PATH) {
1058
1198
  invalidateConfigReadCache();
1059
- const corrupt = `${path3}.corrupt-${Date.now()}`;
1199
+ const corrupt = `${path2}.corrupt-${Date.now()}`;
1060
1200
  try {
1061
- renameWithRetrySync(path3, corrupt);
1201
+ renameWithRetrySync(path2, corrupt);
1062
1202
  } catch {
1063
1203
  }
1064
1204
  process.stderr.write(`[config] mixdog-config.json is malformed (${err.message}). Renamed to ${corrupt}. Restore it or delete to start fresh.
@@ -1069,12 +1209,12 @@ function readJsonFile(path3) {
1069
1209
  }
1070
1210
  function readAll() {
1071
1211
  const parsed = readJsonFile(CONFIG_PATH);
1072
- if (parsed != null) return parsed;
1212
+ if (parsed != null) return canonicalizeUnifiedConfig(parsed);
1073
1213
  if (hasUserDataInitMarker(DATA_DIR)) {
1074
1214
  const restored = loadLatestMixdogConfigFromBackup(DATA_DIR);
1075
1215
  if (restored && isPlainObject2(restored)) {
1076
1216
  process.stderr.write("[config] read: restored mixdog-config.json from latest user-data backup (missing after init)\n");
1077
- return restored;
1217
+ return canonicalizeUnifiedConfig(restored);
1078
1218
  }
1079
1219
  }
1080
1220
  return {};
@@ -1082,7 +1222,7 @@ function readAll() {
1082
1222
  function quarantineMalformedConfig(parseErr) {
1083
1223
  const corrupt = `${CONFIG_PATH}.corrupt-${Date.now()}`;
1084
1224
  try {
1085
- if (existsSync6(CONFIG_PATH)) renameWithRetrySync(CONFIG_PATH, corrupt);
1225
+ if (existsSync5(CONFIG_PATH)) renameWithRetrySync(CONFIG_PATH, corrupt);
1086
1226
  } catch {
1087
1227
  }
1088
1228
  process.stderr.write(
@@ -1104,7 +1244,7 @@ function restoreAllForRmWOrThrow(reason) {
1104
1244
  function readAllForRmW() {
1105
1245
  let raw;
1106
1246
  try {
1107
- raw = readFileSync5(CONFIG_PATH, "utf8");
1247
+ raw = readFileSync4(CONFIG_PATH, "utf8");
1108
1248
  } catch (err) {
1109
1249
  if (err.code === "ENOENT") {
1110
1250
  if (hasUserDataInitMarker(DATA_DIR)) {
@@ -1128,16 +1268,16 @@ function readAllForRmW() {
1128
1268
  function readSection(section) {
1129
1269
  return stripGeneratedMarker(readAll()[section] ?? null) ?? {};
1130
1270
  }
1131
- async function writeJsonFileAsync(path3, data) {
1132
- mkdirSync5(dirname6(path3), { recursive: true, mode: 448 });
1133
- if (path3 === CONFIG_PATH) {
1271
+ async function writeJsonFileAsync(path2, data) {
1272
+ mkdirSync4(dirname5(path2), { recursive: true, mode: 448 });
1273
+ if (path2 === CONFIG_PATH) {
1134
1274
  try {
1135
1275
  await backupUserDataAsync(DATA_DIR, "pre-config-write");
1136
1276
  } catch {
1137
1277
  }
1138
1278
  }
1139
- await writeJsonAtomicAsync(path3, data, { lock: false, fsyncDir: true, mode: 384, secret: true });
1140
- if (path3 === CONFIG_PATH) {
1279
+ await writeJsonAtomicAsync(path2, data, { lock: false, fsyncDir: true, mode: 384, secret: true });
1280
+ if (path2 === CONFIG_PATH) {
1141
1281
  invalidateConfigReadCache();
1142
1282
  try {
1143
1283
  markUserDataInitialized(DATA_DIR);
@@ -1150,10 +1290,10 @@ async function writeJsonFileAsync(path3, data) {
1150
1290
  }
1151
1291
  }
1152
1292
  async function writeAllAsync(data) {
1153
- await writeJsonFileAsync(CONFIG_PATH, data);
1293
+ await writeJsonFileAsync(CONFIG_PATH, canonicalizeUnifiedConfig(data));
1154
1294
  }
1155
1295
  function withConfigLockAsync(fn) {
1156
- return withFileLock(`${CONFIG_PATH}.lock`, fn, { secret: true });
1296
+ return withFileLock(`${CONFIG_PATH}.lock`, fn);
1157
1297
  }
1158
1298
  function trackConfigWrite(work) {
1159
1299
  let promise;
@@ -1177,981 +1317,37 @@ function updateSectionAsync(section, updater) {
1177
1317
  await writeAllAsync(all);
1178
1318
  }));
1179
1319
  }
1180
- var _require, _getSecret, _setSecret, _deleteSecret, _hasSecret, _invalidateSecretCache, DATA_DIR, CONFIG_PATH, GENERATED_KEY, _configReadCache, CONFIG_READ_TTL_MS, _pendingConfigWrites, CAPABILITY_DEFAULTS, SECRET_ACCOUNTS;
1320
+ var _require, _getSecret, _setSecret, _deleteSecret, _hasSecret, _invalidateSecretCache, DATA_DIR, CONFIG_PATH, GENERATED_KEY, _configReadCache, CONFIG_READ_TTL_MS, _pendingConfigWrites, SECRET_ACCOUNTS;
1181
1321
  var init_config = __esm({
1182
1322
  "src/runtime/shared/config.mjs"() {
1183
1323
  init_plugin_paths();
1184
1324
  init_atomic_file();
1185
1325
  init_user_data_guard();
1186
- init_provider_api_key();
1187
- _require = createRequire3(import.meta.url);
1188
- ({
1189
- getSecret: _getSecret,
1190
- setSecret: _setSecret,
1191
- deleteSecret: _deleteSecret,
1192
- hasSecret: _hasSecret,
1193
- invalidateSecretCache: _invalidateSecretCache
1194
- } = _require("../../lib/keychain-cjs.cjs"));
1195
- DATA_DIR = resolvePluginData();
1196
- CONFIG_PATH = join6(DATA_DIR, "mixdog-config.json");
1197
- GENERATED_KEY = "_generated";
1198
- _configReadCache = null;
1199
- CONFIG_READ_TTL_MS = (() => {
1200
- const n = Number(process.env.MIXDOG_CONFIG_READ_TTL_MS);
1201
- return Number.isFinite(n) && n >= 0 ? n : 0;
1202
- })();
1203
- _pendingConfigWrites = /* @__PURE__ */ new Set();
1204
- CAPABILITY_DEFAULTS = Object.freeze({ homeAccess: false });
1205
- SECRET_ACCOUNTS = Object.freeze({
1206
- discordToken: "discord.token",
1207
- telegramToken: "telegram.token",
1208
- agentApiKey: (provider) => `agent.${provider}.apiKey`,
1209
- openaiUsageSessionKey: "agent.openai.usageSessionKey",
1210
- opencodeGoAuthCookie: "agent.opencode-go.authCookie"
1211
- });
1212
- }
1213
- });
1214
-
1215
- // src/standalone/engine-daemon-protocol.mjs
1216
- var ENGINE_DAEMON_PROTOCOL;
1217
- var init_engine_daemon_protocol = __esm({
1218
- "src/standalone/engine-daemon-protocol.mjs"() {
1219
- ENGINE_DAEMON_PROTOCOL = 3;
1220
- }
1221
- });
1222
-
1223
- // src/standalone/engine-daemon-local-bridge.mjs
1224
- function engineDaemonLocalBridge() {
1225
- return globalThis[LOCAL_ENGINE_BRIDGE_KEY] ?? null;
1226
- }
1227
- var LOCAL_ENGINE_BRIDGE_KEY;
1228
- var init_engine_daemon_local_bridge = __esm({
1229
- "src/standalone/engine-daemon-local-bridge.mjs"() {
1230
- LOCAL_ENGINE_BRIDGE_KEY = /* @__PURE__ */ Symbol.for("mixdog.engine-daemon.local-bridge.v1");
1231
- }
1232
- });
1233
-
1234
- // src/standalone/engine-daemon-client.mjs
1235
- var engine_daemon_client_exports = {};
1236
- __export(engine_daemon_client_exports, {
1237
- attachEngineDaemon: () => attachEngineDaemon,
1238
- callDaemonSession: () => callDaemonSession,
1239
- createRemoteEngineSession: () => createRemoteEngineSession,
1240
- engineDaemonDiscoveryPath: () => engineDaemonDiscoveryPath,
1241
- ensureEngineDaemon: () => ensureEngineDaemon,
1242
- negotiateEngineDaemon: () => negotiateEngineDaemon,
1243
- probeEngineDaemonHealth: () => probeEngineDaemonHealth,
1244
- readEngineDaemonDiscovery: () => readEngineDaemonDiscovery,
1245
- shutdownEngineDaemon: () => shutdownEngineDaemon
1246
- });
1247
- import http from "node:http";
1248
- import os from "node:os";
1249
- import path2 from "node:path";
1250
- import { fork } from "node:child_process";
1251
- import { readFileSync as readFileSync7 } from "node:fs";
1252
- import { randomUUID as randomUUID2 } from "node:crypto";
1253
- import { fileURLToPath as fileURLToPath3 } from "node:url";
1254
- function runtimeRoot() {
1255
- return process.env.MIXDOG_RUNTIME_ROOT ? path2.resolve(process.env.MIXDOG_RUNTIME_ROOT) : path2.join(os.tmpdir(), "mixdog");
1256
- }
1257
- function engineDaemonDiscoveryPath() {
1258
- return path2.join(runtimeRoot(), "engine-daemon.json");
1259
- }
1260
- function daemonEntry() {
1261
- return fileURLToPath3(new URL("./backend-daemon.mjs", import.meta.url));
1262
- }
1263
- function request({
1264
- port,
1265
- token,
1266
- method = "GET",
1267
- path: urlPath,
1268
- body = null,
1269
- timeoutMs = 12e4,
1270
- control = false
1271
- }) {
1272
- return new Promise((resolve5, reject) => {
1273
- const payload = body ? JSON.stringify(body) : null;
1274
- const req = http.request({
1275
- hostname: "127.0.0.1",
1276
- port,
1277
- path: urlPath,
1278
- method,
1279
- agent: control ? daemonControlAgent : daemonCallAgent,
1280
- headers: {
1281
- "X-Mixdog-Daemon-Token": token,
1282
- ...payload ? { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(payload) } : {}
1283
- },
1284
- timeout: timeoutMs
1285
- }, (res) => {
1286
- let data = "";
1287
- res.setEncoding("utf8");
1288
- res.on("data", (chunk) => {
1289
- data += chunk;
1290
- });
1291
- res.on("end", () => {
1292
- let parsed = null;
1293
- try {
1294
- parsed = data ? JSON.parse(data) : {};
1295
- } catch {
1296
- parsed = null;
1297
- }
1298
- if (res.statusCode && res.statusCode >= 400) {
1299
- const err = new Error(parsed?.error || data || `HTTP ${res.statusCode}`);
1300
- err.statusCode = res.statusCode;
1301
- reject(err);
1302
- return;
1303
- }
1304
- resolve5(parsed ?? {});
1305
- });
1306
- });
1307
- req.on("timeout", () => {
1308
- req.destroy(new Error(`engine daemon timeout: ${method} ${urlPath}`));
1309
- });
1310
- req.on("error", reject);
1311
- if (payload) req.write(payload);
1312
- req.end();
1313
- });
1314
- }
1315
- async function probeEngineDaemonHealth({ port, token, timeoutMs = 800 } = {}) {
1316
- try {
1317
- const health = await request({ port, token, path: "/health", timeoutMs, control: true });
1318
- return health?.status === "ok" ? health : null;
1319
- } catch {
1320
- return null;
1321
- }
1322
- }
1323
- function readEngineDaemonDiscovery(discoveryPath = engineDaemonDiscoveryPath()) {
1324
- try {
1325
- const parsed = JSON.parse(readFileSync7(discoveryPath, "utf8"));
1326
- if (!parsed?.port || !parsed?.token || !parsed?.pid) return null;
1327
- return parsed;
1328
- } catch {
1329
- return null;
1330
- }
1331
- }
1332
- function delay(ms) {
1333
- return new Promise((resolve5) => setTimeout(resolve5, ms));
1334
- }
1335
- function negotiateEngineDaemon(health) {
1336
- const protocol = Number(health?.protocol) || 0;
1337
- if (protocol > ENGINE_DAEMON_PROTOCOL) return "defer";
1338
- if (protocol < ENGINE_DAEMON_PROTOCOL) return "restart";
1339
- return "ok";
1340
- }
1341
- async function waitForDaemonExit(discovery, timeoutMs = 5e3) {
1342
- const deadline = Date.now() + timeoutMs;
1343
- while (Date.now() < deadline) {
1344
- const health = await probeEngineDaemonHealth({ port: discovery.port, token: discovery.token, timeoutMs: 400 });
1345
- if (!health || Number(health.pid) !== Number(discovery.pid)) return true;
1346
- await delay(150);
1347
- }
1348
- return false;
1349
- }
1350
- function spawnDaemonCandidate({ cwd, log }) {
1351
- return new Promise((resolve5) => {
1352
- let settled = false;
1353
- const done = () => {
1354
- if (!settled) {
1355
- settled = true;
1356
- resolve5();
1357
- }
1358
- };
1359
- let child;
1360
- try {
1361
- child = fork(daemonEntry(), [], {
1362
- cwd,
1363
- stdio: ["ignore", "ignore", "pipe", "ipc"],
1364
- detached: process.platform !== "win32",
1365
- windowsHide: true,
1366
- env: {
1367
- ...process.env,
1368
- MIXDOG_ENGINE_DAEMON_HOST: "1",
1369
- // Engine-only spawn: the backend stays dormant on the channels side
1370
- // until a channels client registers.
1371
- MIXDOG_DAEMON_SPAWNED_FOR: "engine",
1372
- MIXDOG_SUPERVISOR_PID: process.env.MIXDOG_SUPERVISOR_PID || String(process.pid)
1373
- }
1374
- });
1375
- } catch (err) {
1376
- log(`daemon spawn failed: ${err?.message || err}`);
1377
- done();
1378
- return;
1379
- }
1380
- const mirror = (chunk) => {
1381
- const text = String(chunk || "").trimEnd();
1382
- if (text) log(text);
1383
- };
1384
- child.stderr?.on("data", mirror);
1385
- child.once("message", (msg) => {
1386
- if (msg?.type !== "ready") return;
1387
- try {
1388
- child.stderr?.off?.("data", mirror);
1389
- } catch {
1390
- }
1391
- try {
1392
- child.disconnect?.();
1393
- } catch {
1394
- }
1395
- try {
1396
- child.unref?.();
1397
- } catch {
1398
- }
1399
- try {
1400
- child.stderr?.unref?.();
1401
- } catch {
1402
- }
1403
- done();
1404
- });
1405
- child.once("exit", done);
1406
- child.once("error", (err) => {
1407
- log(`daemon spawn error: ${err?.message || err}`);
1408
- done();
1409
- });
1410
- const timer2 = setTimeout(done, 3e4);
1411
- timer2.unref?.();
1412
- });
1413
- }
1414
- async function ensureEngineDaemon({ cwd = process.cwd(), log = () => {
1415
- }, attempts = 5 } = {}) {
1416
- let upgradeBlocked = null;
1417
- for (let attempt = 0; attempt < attempts; attempt += 1) {
1418
- const discovery = readEngineDaemonDiscovery();
1419
- if (discovery) {
1420
- const health = await probeEngineDaemonHealth({ port: discovery.port, token: discovery.token });
1421
- if (health && Number(health.pid) === Number(discovery.pid)) {
1422
- const verdict = negotiateEngineDaemon(health);
1423
- if (verdict === "ok") return discovery;
1424
- if (verdict === "defer") {
1425
- const err = new Error(
1426
- `engine daemon ${health.version || "?"} (protocol ${health.protocol ?? 0}) is newer than this client`
1427
- );
1428
- err.daemonNewerThanClient = true;
1429
- throw err;
1430
- }
1431
- log(`stopping older engine daemon ${health.version || "?"} (protocol ${health.protocol ?? 0})`);
1432
- await shutdownEngineDaemon(discovery);
1433
- upgradeBlocked = await waitForDaemonExit(discovery) ? null : health;
1434
- }
1435
- }
1436
- await spawnDaemonCandidate({ cwd, log });
1437
- await delay(100);
1438
- }
1439
- if (upgradeBlocked) {
1440
- const err = new Error(
1441
- `an older Mixdog backend (protocol ${upgradeBlocked.protocol ?? 0}) is still serving live clients \u2014 close the running Mixdog windows and terminals, then start again`
1442
- );
1443
- err.daemonUpgradeBlocked = true;
1444
- throw err;
1445
- }
1446
- throw new Error("engine daemon is unavailable");
1447
- }
1448
- async function attachEngineDaemon({
1449
- discovery,
1450
- leadPid = process.pid,
1451
- cwd = process.cwd(),
1452
- lifecycle = true,
1453
- registrationId = randomUUID2(),
1454
- onFrame = () => {
1455
- },
1456
- onFatal = () => {
1457
- },
1458
- log = () => {
1459
- }
1460
- } = {}) {
1461
- if (!discovery?.port || !discovery?.token) throw new Error("daemon discovery {port, token} required");
1462
- const { port, token: serverToken } = discovery;
1463
- const stableRegistrationId = String(registrationId || randomUUID2());
1464
- let reg = null;
1465
- let registrationError = null;
1466
- for (let attempt = 0; attempt < 3; attempt += 1) {
1467
- try {
1468
- reg = await request({
1469
- port,
1470
- token: serverToken,
1471
- method: "POST",
1472
- path: "/client/register",
1473
- body: {
1474
- leadPid,
1475
- cwd,
1476
- lifecycle: lifecycle !== false,
1477
- registrationId: stableRegistrationId
1478
- },
1479
- timeoutMs: 3e3,
1480
- control: true
1481
- });
1482
- break;
1483
- } catch (error) {
1484
- registrationError = error;
1485
- if (Number(error?.statusCode) >= 400 && Number(error?.statusCode) < 500) throw error;
1486
- if (attempt < 2) await delay(100 * (attempt + 1));
1487
- }
1488
- }
1489
- if (!reg) throw registrationError || new Error("engine daemon registration failed");
1490
- const clientToken = reg?.token;
1491
- if (!clientToken) throw new Error("engine daemon register returned no client token");
1492
- let closed = false;
1493
- let streamRequest = null;
1494
- function openStream() {
1495
- if (closed) return;
1496
- const req = http.request({
1497
- hostname: "127.0.0.1",
1498
- port,
1499
- path: `/events?token=${encodeURIComponent(clientToken)}&server_token=${encodeURIComponent(serverToken)}`,
1500
- method: "GET",
1501
- headers: { Accept: "text/event-stream", "X-Mixdog-Daemon-Token": serverToken }
1502
- }, (res) => {
1503
- if (req !== streamRequest || closed) {
1504
- res.resume();
1505
- return;
1506
- }
1507
- if (res.statusCode !== 200) {
1508
- res.resume();
1509
- if (!closed) {
1510
- try {
1511
- onFatal(`bad sse status ${res.statusCode}`);
1512
- } catch {
1513
- }
1514
- }
1515
- return;
1516
- }
1517
- res.setEncoding("utf8");
1518
- let buffer = "";
1519
- res.on("data", (chunk) => {
1520
- if (req !== streamRequest || closed) return;
1521
- buffer += chunk;
1522
- let index;
1523
- while ((index = buffer.indexOf("\n\n")) >= 0) {
1524
- const raw = buffer.slice(0, index);
1525
- buffer = buffer.slice(index + 2);
1526
- for (const line of raw.split("\n")) {
1527
- if (!line.startsWith("data:")) continue;
1528
- const json = line.slice(5).trim();
1529
- if (!json) continue;
1530
- let frame = null;
1531
- try {
1532
- frame = JSON.parse(json);
1533
- } catch {
1534
- continue;
1535
- }
1536
- try {
1537
- onFrame(frame);
1538
- } catch (err) {
1539
- log(`onFrame threw: ${err?.message || err}`);
1540
- }
1541
- }
1542
- }
1543
- });
1544
- const lost = (reason) => {
1545
- if (req !== streamRequest || closed) return;
1546
- try {
1547
- onFatal(reason);
1548
- } catch {
1549
- }
1550
- };
1551
- res.on("end", () => lost("sse ended"));
1552
- res.on("error", () => lost("sse error"));
1553
- });
1554
- req.on("error", () => {
1555
- if (req === streamRequest && !closed) {
1556
- try {
1557
- onFatal("sse request error");
1558
- } catch {
1559
- }
1560
- }
1561
- });
1562
- streamRequest = req;
1563
- req.end();
1564
- }
1565
- openStream();
1566
- async function call(name, args = {}, { timeoutMs = 3e5, callId = null } = {}) {
1567
- let out;
1568
- try {
1569
- out = await request({
1570
- port,
1571
- token: serverToken,
1572
- method: "POST",
1573
- path: "/call",
1574
- body: { token: clientToken, name, args: args || {}, ...callId ? { callId } : {} },
1575
- timeoutMs
1576
- });
1577
- } catch (err) {
1578
- err.daemonTransportError = true;
1579
- throw err;
1580
- }
1581
- if (out && out.error) throw new Error(out.error);
1582
- return out?.result;
1583
- }
1584
- async function close(reason = "client close") {
1585
- if (closed) return;
1586
- closed = true;
1587
- try {
1588
- streamRequest?.destroy?.();
1589
- } catch {
1590
- }
1591
- try {
1592
- await request({
1593
- port,
1594
- token: serverToken,
1595
- method: "POST",
1596
- path: "/client/deregister",
1597
- body: { token: clientToken },
1598
- timeoutMs: 1500,
1599
- control: true
1600
- });
1601
- } catch {
1602
- }
1603
- log(`detached (${reason})`);
1604
- }
1605
- return { call, close, clientToken, port, pid: Number(discovery.pid) };
1606
- }
1607
- async function shutdownEngineDaemon(discovery = readEngineDaemonDiscovery()) {
1608
- if (!discovery?.port) return false;
1609
- try {
1610
- await request({
1611
- port: discovery.port,
1612
- token: discovery.token,
1613
- method: "POST",
1614
- path: "/shutdown",
1615
- body: {},
1616
- timeoutMs: 3e3,
1617
- control: true
1618
- });
1619
- return true;
1620
- } catch {
1621
- return false;
1622
- }
1623
- }
1624
- function addSessionView(views, sessionId, view) {
1625
- let bucket = views.get(sessionId);
1626
- if (!bucket) {
1627
- bucket = /* @__PURE__ */ new Set();
1628
- views.set(sessionId, bucket);
1629
- }
1630
- bucket.add(view);
1631
- }
1632
- function removeSessionView(views, sessionId, view) {
1633
- const bucket = views.get(sessionId);
1634
- if (!bucket) return;
1635
- bucket.delete(view);
1636
- if (bucket.size === 0) views.delete(sessionId);
1637
- }
1638
- function sessionViewCount(views, sessionId) {
1639
- return views.get(sessionId)?.size ?? 0;
1640
- }
1641
- function applyStatePatch(state, patch) {
1642
- const next = { ...state, ...patch.set || {} };
1643
- if (patch.itemsAppend) {
1644
- const base = Array.isArray(state.items) ? state.items : [];
1645
- next.items = base.slice(0, patch.itemsAppend.from).concat(patch.itemsAppend.values || []);
1646
- }
1647
- for (const key of patch.remove || []) delete next[key];
1648
- return next;
1649
- }
1650
- function scheduleReattach({ cwd, log }) {
1651
- if (reattachTimer || liveViews.size === 0) return;
1652
- reattachTimer = setTimeout(() => {
1653
- reattachTimer = null;
1654
- if (liveViews.size === 0) return;
1655
- void ensureSharedAttachment({ cwd, log }).catch((err) => {
1656
- log(`backend daemon re-attach failed: ${err?.message || err}`);
1657
- scheduleReattach({ cwd, log });
1658
- });
1659
- }, 500);
1660
- reattachTimer.unref?.();
1661
- }
1662
- async function ensureSharedAttachment({ cwd, log }) {
1663
- if (shared?.client) return shared;
1664
- if (shared?.pending) return shared.pending;
1665
- const pending = (async () => {
1666
- const localBridge = process.env.MIXDOG_ENGINE_DAEMON_HOST === "1" ? engineDaemonLocalBridge() : null;
1667
- const discovery = localBridge ? { pid: process.pid, local: true } : await ensureEngineDaemon({ cwd, log });
1668
- const views = /* @__PURE__ */ new Map();
1669
- const state = { client: null, views, discovery, refs: liveViews.size, log, cwd };
1670
- const attachmentOptions = {
1671
- cwd,
1672
- log,
1673
- onFrame: (frame) => {
1674
- const bucket = views.get(String(frame?.sessionId || ""));
1675
- if (!bucket) return;
1676
- for (const view of [...bucket]) {
1677
- if (frame.type === "session-state") view.applyFrame(frame);
1678
- else if (frame.type === "session-gone") view.applyGone(frame.reason || "session unloaded");
1679
- }
1680
- },
1681
- onFatal: (reason) => {
1682
- log(`backend daemon attachment lost (${reason})`);
1683
- if (shared === state) shared = null;
1684
- for (const bucket of views.values()) {
1685
- for (const view of [...bucket]) view.applyDetached(reason);
1686
- }
1687
- scheduleReattach({ cwd: state.cwd, log });
1688
- }
1689
- };
1690
- state.client = localBridge ? await localBridge.attach(attachmentOptions) : await attachEngineDaemon({ discovery, ...attachmentOptions });
1691
- shared = state;
1692
- for (const view of liveViews) {
1693
- addSessionView(views, view.sessionId(), view);
1694
- void view.recover(state).catch((err) => {
1695
- log(`session ${view.sessionId()} recovery failed: ${err?.message || err}`);
1696
- });
1697
- }
1698
- return state;
1699
- })();
1700
- shared = { pending };
1701
- try {
1702
- return await pending;
1703
- } catch (err) {
1704
- shared = null;
1705
- throw err;
1706
- }
1707
- }
1708
- async function createRemoteEngineSession(options = {}) {
1709
- const log = typeof options.log === "function" ? options.log : () => {
1710
- };
1711
- const cwd = options.cwd || process.cwd();
1712
- const openParams = {
1713
- cwd,
1714
- provider: options.provider,
1715
- model: options.model,
1716
- toolMode: options.toolMode || "full",
1717
- remote: options.remote === true,
1718
- desktopSession: options.desktopSession ?? null
1719
- };
1720
- let attachment = await ensureSharedAttachment({ cwd, log });
1721
- const created = await attachment.client.call("session.create", openParams, {
1722
- callId: `session-create:${process.pid}:${randomUUID2()}`
1723
- });
1724
- let sessionId = String(created?.sessionId || "");
1725
- if (!sessionId) throw new Error("session.create returned no sessionId");
1726
- let state = created?.full ?? {};
1727
- let revision = Number(created?.revision) || 0;
1728
- let sync = created?.sync ?? {};
1729
- let disposed = false;
1730
- const listeners = /* @__PURE__ */ new Set();
1731
- function emit() {
1732
- for (const listener of [...listeners]) {
1733
- try {
1734
- listener();
1735
- } catch (err) {
1736
- log(`session listener threw: ${err?.message || err}`);
1737
- }
1738
- }
1739
- }
1740
- function applyBody(body) {
1741
- if (disposed || !body) return false;
1742
- if (body.full !== void 0 && body.full !== null) {
1743
- state = body.full;
1744
- revision = Number(body.revision) || revision;
1745
- emit();
1746
- return true;
1747
- }
1748
- if (body.patch) {
1749
- if (Number(body.revision) === revision && Number(body.baseRevision) === revision - 1) return true;
1750
- if (Number(body.baseRevision) !== revision) return false;
1751
- state = applyStatePatch(state, body.patch);
1752
- revision = Number(body.revision) || revision;
1753
- emit();
1754
- return true;
1755
- }
1756
- if (Number.isFinite(Number(body.revision))) {
1757
- revision = Number(body.revision);
1758
- return true;
1759
- }
1760
- return false;
1761
- }
1762
- let resyncing = false;
1763
- function resync(reason) {
1764
- if (disposed || resyncing) return;
1765
- resyncing = true;
1766
- void attachment.client.call("session.read", {
1767
- sessionId,
1768
- open: openParams,
1769
- baseRevision: revision
1770
- }).then((result) => {
1771
- if (disposed) return;
1772
- if (result?.sync) sync = result.sync;
1773
- if (!applyBody(result) && result?.revision !== void 0) {
1774
- log(`session ${sessionId} resync returned an unusable body`);
1775
- }
1776
- }).catch((err) => {
1777
- log(`session ${sessionId} resync after ${reason} failed: ${err?.message || err}`);
1778
- }).finally(() => {
1779
- resyncing = false;
1780
- });
1781
- }
1782
- let recoveryPromise = null;
1783
- let view;
1784
- async function recover(nextAttachment) {
1785
- if (disposed) return;
1786
- if (recoveryPromise) return recoveryPromise;
1787
- recoveryPromise = (async () => {
1788
- const previousAttachment = attachment;
1789
- if (previousAttachment !== nextAttachment) {
1790
- removeSessionView(previousAttachment.views, sessionId, view);
1791
- attachment = nextAttachment;
1792
- }
1793
- let result;
1794
- try {
1795
- result = await attachment.client.call("session.subscribe", {
1796
- sessionId,
1797
- open: openParams,
1798
- baseRevision: revision
1799
- });
1800
- } catch {
1801
- result = await attachment.client.call("session.create", {
1802
- ...openParams,
1803
- sessionId
1804
- }, { callId: `session-recreate:${sessionId}` });
1805
- }
1806
- addSessionView(attachment.views, sessionId, view);
1807
- if (result?.sync) sync = result.sync;
1808
- if (!applyBody(result) && result?.revision !== void 0) resync("recovery body gap");
1809
- log(`session ${sessionId} projection recovered`);
1810
- })();
1811
- try {
1812
- return await recoveryPromise;
1813
- } finally {
1814
- recoveryPromise = null;
1815
- }
1816
- }
1817
- view = {
1818
- sessionId: () => sessionId,
1819
- applyFrame(frame) {
1820
- if (disposed) return;
1821
- if (!applyBody(frame)) resync("revision gap");
1822
- },
1823
- applyGone(reason) {
1824
- if (disposed) return;
1825
- void recover(attachment).catch((err) => {
1826
- log(`session ${sessionId} reload after ${reason} failed: ${err?.message || err}`);
1827
- });
1828
- },
1829
- applyDetached(reason) {
1830
- log(`session ${sessionId} projection detached (${reason})`);
1831
- },
1832
- recover
1833
- };
1834
- addSessionView(attachment.views, sessionId, view);
1835
- attachment.refs += 1;
1836
- liveViews.add(view);
1837
- async function sendCall(route, payload, callId) {
1838
- const send = () => attachment.client.call(route, payload, { callId });
1839
- try {
1840
- return await send();
1841
- } catch (err) {
1842
- const recoverable = err?.daemonTransportError || /unknown client token/i.test(String(err?.message || ""));
1843
- if (!recoverable || disposed) throw err;
1844
- if (shared === attachment) shared = null;
1845
- const next = await ensureSharedAttachment({ cwd, log });
1846
- await recover(next);
1847
- return await send();
1848
- }
1849
- }
1850
- async function applyResult(result, reason) {
1851
- const nextSessionId = String(result?.sessionId || sessionId);
1852
- if (nextSessionId && nextSessionId !== sessionId) {
1853
- const previousSessionId = sessionId;
1854
- addSessionView(attachment.views, nextSessionId, view);
1855
- removeSessionView(attachment.views, previousSessionId, view);
1856
- sessionId = nextSessionId;
1857
- }
1858
- if (result?.sync) sync = result.sync;
1859
- if (!applyBody(result) && result?.revision !== void 0) resync(`${reason} body gap`);
1860
- return result;
1861
- }
1862
- let callChain = Promise.resolve();
1863
- function serialize(task) {
1864
- const run = callChain.then(task);
1865
- callChain = run.then(() => {
1866
- }, () => {
1867
- });
1868
- return run;
1869
- }
1870
- function remoteCall(method, args, callOptions = {}) {
1871
- return serialize(async () => {
1872
- if (disposed) throw new Error("This session view is disposed.");
1873
- const stableCallId = typeof callOptions.callId === "string" && callOptions.callId.trim() ? callOptions.callId.trim() : randomUUID2();
1874
- const result = await sendCall("session.invoke", {
1875
- sessionId,
1876
- method,
1877
- args,
1878
- open: openParams,
1879
- baseRevision: revision
1880
- }, stableCallId);
1881
- await applyResult(result, method);
1882
- return result?.value ?? null;
1883
- });
1884
- }
1885
- function dispatch(method, args) {
1886
- void remoteCall(method, args).catch((err) => {
1887
- log(`session ${method} failed: ${err?.message || err}`);
1888
- });
1889
- }
1890
- async function rebindTo(result, previousSessionId) {
1891
- const nextSessionId = String(result?.sessionId || "");
1892
- if (!nextSessionId) throw new Error("session route returned no sessionId");
1893
- const lastLocalView = sessionViewCount(attachment.views, previousSessionId) <= 1;
1894
- addSessionView(attachment.views, nextSessionId, view);
1895
- removeSessionView(attachment.views, previousSessionId, view);
1896
- sessionId = nextSessionId;
1897
- if (result?.sync) sync = result.sync;
1898
- if (!applyBody(result) && result?.revision !== void 0) resync("session rebind body gap");
1899
- if (previousSessionId && previousSessionId !== nextSessionId && lastLocalView) {
1900
- try {
1901
- await attachment.client.call("session.unsubscribe", { sessionId: previousSessionId });
1902
- } catch (err) {
1903
- log(`session ${previousSessionId} unsubscribe failed: ${err?.message || err}`);
1904
- }
1905
- }
1906
- return nextSessionId;
1907
- }
1908
- async function createReservedSession() {
1909
- return serialize(async () => {
1910
- if (disposed) throw new Error("This session view is disposed.");
1911
- const previousSessionId = sessionId;
1912
- const result = await sendCall(
1913
- "session.create",
1914
- openParams,
1915
- `session-create:${process.pid}:${randomUUID2()}`
1916
- );
1917
- return await rebindTo(result, previousSessionId);
1918
- });
1919
- }
1920
- async function resumeSession(targetSessionId, resumeOptions) {
1921
- const target = String(targetSessionId || "");
1922
- if (!target) return false;
1923
- return serialize(async () => {
1924
- if (disposed) throw new Error("This session view is disposed.");
1925
- if (target === sessionId) {
1926
- const result2 = await sendCall("session.read", {
1927
- sessionId,
1928
- open: openParams,
1929
- baseRevision: revision
1930
- }, randomUUID2());
1931
- await applyResult(result2, "resume");
1932
- return true;
1933
- }
1934
- const previousSessionId = sessionId;
1935
- const result = await sendCall("session.subscribe", {
1936
- sessionId: target,
1937
- open: { ...openParams, resumeOptions: resumeOptions || void 0 },
1938
- baseRevision: null
1939
- }, randomUUID2());
1940
- await rebindTo(result, previousSessionId);
1941
- return true;
1942
- });
1943
- }
1944
- let submitSeq = 0;
1945
- async function submitAsync(prompt, options2 = {}) {
1946
- if (disposed) throw new Error("This session view is disposed.");
1947
- const submissionId = String(options2?.id || "").trim() || `session-submit-${process.pid}-${Date.now()}-${submitSeq += 1}`;
1948
- return serialize(async () => {
1949
- const result = await sendCall("session.submit", {
1950
- sessionId,
1951
- prompt,
1952
- options: { ...options2 || {}, id: submissionId },
1953
- open: openParams,
1954
- baseRevision: revision
1955
- }, `session-submit:${sessionId}:${submissionId}`);
1956
- await applyResult(result, "submit");
1957
- return result?.accepted === true;
1958
- });
1959
- }
1960
- const base = {
1961
- isRemoteEngine: true,
1962
- get disposedView() {
1963
- return disposed;
1964
- },
1965
- getState: () => state,
1966
- subscribe(listener) {
1967
- listeners.add(listener);
1968
- return () => listeners.delete(listener);
1969
- },
1970
- listSessions(options2) {
1971
- if (options2?.refreshFromStorage) dispatch("listSessions", [options2]);
1972
- return Array.isArray(sync.sessions) ? sync.sessions : [];
1973
- },
1974
- sessionStoreDir() {
1975
- return typeof sync.sessionStoreDir === "string" ? sync.sessionStoreDir : null;
1976
- },
1977
- async newSession() {
1978
- await createReservedSession();
1979
- return true;
1980
- },
1981
- resume: resumeSession,
1982
- async prefetchSession(targetSessionId) {
1983
- const target = String(targetSessionId || "");
1984
- if (!target) return false;
1985
- await callDaemonSession({
1986
- sessionId: target,
1987
- method: "read",
1988
- open: openParams,
1989
- cwd,
1990
- log,
1991
- attempts: 1
1992
- });
1993
- return true;
1994
- },
1995
- submitAsync,
1996
- submit(prompt, options2) {
1997
- if (disposed) return false;
1998
- void submitAsync(prompt, options2).catch((err) => {
1999
- log(`legacy submit failed: ${err?.message || err}`);
2000
- });
2001
- return true;
2002
- },
2003
- abort() {
2004
- void serialize(async () => {
2005
- const result = await sendCall("session.abort", {
2006
- sessionId,
2007
- open: openParams,
2008
- baseRevision: revision
2009
- }, randomUUID2());
2010
- await applyResult(result, "abort");
2011
- }).catch((err) => log(`session abort failed: ${err?.message || err}`));
2012
- return true;
2013
- },
2014
- resolveToolApproval(id, decision) {
2015
- void serialize(async () => {
2016
- const result = await sendCall("session.approve", {
2017
- sessionId,
2018
- approvalId: id,
2019
- decision,
2020
- open: openParams,
2021
- baseRevision: revision
2022
- }, randomUUID2());
2023
- await applyResult(result, "approval");
2024
- }).catch((err) => log(`session approval failed: ${err?.message || err}`));
2025
- return true;
2026
- },
2027
- async dispose(reason = "view dispose") {
2028
- if (disposed) return;
2029
- disposed = true;
2030
- liveViews.delete(view);
2031
- const releasedSessionId = sessionId;
2032
- const lastLocalView = sessionViewCount(attachment.views, releasedSessionId) <= 1;
2033
- if (lastLocalView) {
2034
- try {
2035
- await attachment.client.call("session.unsubscribe", { sessionId: releasedSessionId });
2036
- } catch (err) {
2037
- log(`session ${releasedSessionId} unsubscribe failed: ${err?.message || err}`);
2038
- }
2039
- }
2040
- removeSessionView(attachment.views, releasedSessionId, view);
2041
- listeners.clear();
2042
- attachment.refs = Math.max(0, attachment.refs - 1);
2043
- if (attachment.refs === 0 && liveViews.size === 0) {
2044
- try {
2045
- await attachment.client.close(reason);
2046
- } catch {
2047
- }
2048
- try {
2049
- daemonCallAgent.destroy();
2050
- } catch {
2051
- }
2052
- try {
2053
- daemonControlAgent.destroy();
2054
- } catch {
2055
- }
2056
- if (shared === attachment) shared = null;
2057
- }
2058
- }
2059
- };
2060
- return new Proxy(base, {
2061
- get(target, property, receiver) {
2062
- if (property in target || LOCAL_ENGINE_KEYS.has(property)) {
2063
- return Reflect.get(target, property, receiver);
2064
- }
2065
- if (typeof property === "symbol") return void 0;
2066
- return (...args) => remoteCall(property, args);
2067
- },
2068
- has(target, property) {
2069
- return typeof property === "string" ? true : Reflect.has(target, property);
2070
- }
2071
- });
2072
- }
2073
- async function callDaemonSession({
2074
- sessionId,
2075
- method,
2076
- args = [],
2077
- open: openHints = {},
2078
- cwd = process.cwd(),
2079
- log = () => {
2080
- },
2081
- attempts = 3
2082
- } = {}) {
2083
- const id = String(sessionId || "");
2084
- if (!id) throw new TypeError("sessionId is required");
2085
- const name = String(method || "");
2086
- if (!name) throw new TypeError("method is required");
2087
- let route = "session.invoke";
2088
- let payload = { sessionId: id, method: name, args, open: openHints || {} };
2089
- if (name === "submit") {
2090
- route = "session.submit";
2091
- payload = {
2092
- sessionId: id,
2093
- prompt: args?.[0],
2094
- options: args?.[1] || {},
2095
- open: openHints || {}
2096
- };
2097
- } else if (name === "abort") {
2098
- route = "session.abort";
2099
- payload = { sessionId: id, open: openHints || {} };
2100
- } else if (name === "resolveToolApproval") {
2101
- route = "session.approve";
2102
- payload = {
2103
- sessionId: id,
2104
- approvalId: args?.[0],
2105
- decision: args?.[1],
2106
- open: openHints || {}
2107
- };
2108
- } else if (name === "read") {
2109
- route = "session.read";
2110
- payload = { sessionId: id, open: openHints || {}, baseRevision: null };
2111
- }
2112
- const submissionId = name === "submit" ? String(args?.[1]?.id || "").trim() : "";
2113
- const callId = submissionId ? `session-submit:${id}:${submissionId}` : randomUUID2();
2114
- let lastError = null;
2115
- for (let attempt = 0; attempt < Math.max(1, attempts); attempt += 1) {
2116
- const attachment = await ensureSharedAttachment({ cwd, log });
2117
- try {
2118
- const result = await attachment.client.call(route, payload, { callId });
2119
- if (name === "submit") return { ...result, value: result?.accepted === true };
2120
- if (name === "abort") return { ...result, value: result?.aborted === true };
2121
- if (name === "resolveToolApproval") return { ...result, value: result?.approved === true };
2122
- return result;
2123
- } catch (err) {
2124
- if (!err?.daemonTransportError) throw err;
2125
- lastError = err;
2126
- if (shared === attachment) shared = null;
2127
- await delay(200 * (attempt + 1));
2128
- }
2129
- }
2130
- throw lastError || new Error("engine daemon session call failed");
2131
- }
2132
- var daemonCallAgent, daemonControlAgent, shared, liveViews, reattachTimer, LOCAL_ENGINE_KEYS;
2133
- var init_engine_daemon_client = __esm({
2134
- "src/standalone/engine-daemon-client.mjs"() {
2135
- init_engine_daemon_protocol();
2136
- init_engine_daemon_local_bridge();
2137
- daemonCallAgent = new http.Agent({ keepAlive: true, keepAliveMsecs: 5e3, maxSockets: 8 });
2138
- daemonControlAgent = new http.Agent({ keepAlive: true, keepAliveMsecs: 5e3, maxSockets: 4 });
2139
- shared = null;
2140
- liveViews = /* @__PURE__ */ new Set();
2141
- reattachTimer = null;
2142
- LOCAL_ENGINE_KEYS = /* @__PURE__ */ new Set([
2143
- "getState",
2144
- "subscribe",
2145
- "dispose",
2146
- "isRemoteEngine",
2147
- "disposedView",
2148
- "then",
2149
- "catch",
2150
- "finally",
2151
- "toJSON",
2152
- "inspect",
2153
- Symbol.toStringTag
2154
- ]);
1326
+ init_provider_api_key();
1327
+ _require = createRequire3(import.meta.url);
1328
+ ({
1329
+ getSecret: _getSecret,
1330
+ setSecret: _setSecret,
1331
+ deleteSecret: _deleteSecret,
1332
+ hasSecret: _hasSecret,
1333
+ invalidateSecretCache: _invalidateSecretCache
1334
+ } = _require("../../lib/keychain-cjs.cjs"));
1335
+ DATA_DIR = resolvePluginData();
1336
+ CONFIG_PATH = join5(DATA_DIR, "mixdog-config.json");
1337
+ GENERATED_KEY = "_generated";
1338
+ _configReadCache = null;
1339
+ CONFIG_READ_TTL_MS = (() => {
1340
+ const n = Number(process.env.MIXDOG_CONFIG_READ_TTL_MS);
1341
+ return Number.isFinite(n) && n >= 0 ? n : 0;
1342
+ })();
1343
+ _pendingConfigWrites = /* @__PURE__ */ new Set();
1344
+ SECRET_ACCOUNTS = Object.freeze({
1345
+ discordToken: "discord.token",
1346
+ telegramToken: "telegram.token",
1347
+ agentApiKey: (provider) => `agent.${provider}.apiKey`,
1348
+ openaiUsageSessionKey: "agent.openai.usageSessionKey",
1349
+ opencodeGoAuthCookie: "agent.opencode-go.authCookie"
1350
+ });
2155
1351
  }
2156
1352
  });
2157
1353
 
@@ -2160,7 +1356,7 @@ var require_mixdog_debug = __commonJS({
2160
1356
  "src/lib/mixdog-debug.cjs"(exports, module) {
2161
1357
  "use strict";
2162
1358
  var fs = __require("fs");
2163
- var path3 = __require("path");
1359
+ var path2 = __require("path");
2164
1360
  function isTruthyEnv(value) {
2165
1361
  if (value == null || value === "") return false;
2166
1362
  const s = String(value).trim().toLowerCase();
@@ -2173,12 +1369,12 @@ var require_mixdog_debug = __commonJS({
2173
1369
  function _detectFromSourceCheckout() {
2174
1370
  if (_cachedFromSource !== null) return _cachedFromSource;
2175
1371
  try {
2176
- const moduleDir = module && module.filename ? path3.dirname(module.filename) : process.cwd();
1372
+ const moduleDir = module && module.filename ? path2.dirname(module.filename) : process.cwd();
2177
1373
  const roots = [
2178
- path3.resolve(moduleDir, "..", ".."),
1374
+ path2.resolve(moduleDir, "..", ".."),
2179
1375
  process.cwd()
2180
1376
  ];
2181
- _cachedFromSource = roots.some((root) => fs.existsSync(path3.join(root, ".git")));
1377
+ _cachedFromSource = roots.some((root) => fs.existsSync(path2.join(root, ".git")));
2182
1378
  } catch {
2183
1379
  _cachedFromSource = false;
2184
1380
  }
@@ -2244,7 +1440,7 @@ var require_mixdog_debug = __commonJS({
2244
1440
  }
2245
1441
  const candidates = [];
2246
1442
  for (const e of entries) {
2247
- const p = path3.join(dataDir, e.name);
1443
+ const p = path2.join(dataDir, e.name);
2248
1444
  try {
2249
1445
  const st = fs.statSync(p);
2250
1446
  if (now - st.mtimeMs < minAgeMs) continue;
@@ -2285,7 +1481,7 @@ var require_mixdog_debug = __commonJS({
2285
1481
  function appendSessionStartCriticalLog(dataDir, line) {
2286
1482
  if (!dataDir || !line) return;
2287
1483
  try {
2288
- const p = path3.join(dataDir, SESSION_START_CRITICAL_LOG);
1484
+ const p = path2.join(dataDir, SESSION_START_CRITICAL_LOG);
2289
1485
  fs.mkdirSync(dataDir, { recursive: true });
2290
1486
  rotateBoundedLog2(p, SESSION_START_CRITICAL_MAX_BYTES, SESSION_START_CRITICAL_KEEP_BYTES);
2291
1487
  fs.appendFileSync(p, line.endsWith("\n") ? line : `${line}
@@ -2312,14 +1508,14 @@ var require_mixdog_debug = __commonJS({
2312
1508
  // src/tui/index.jsx
2313
1509
  import React22 from "react";
2314
1510
  import { render } from "../../../vendor/ink/build/index.js";
2315
- import { closeSync as closeSync2, constants as fsConstants, createWriteStream, mkdirSync as mkdirSync7, openSync as openSync2, readSync } from "node:fs";
1511
+ import { closeSync as closeSync2, constants as fsConstants, createWriteStream, mkdirSync as mkdirSync6, openSync as openSync2, readSync } from "node:fs";
2316
1512
  import { tmpdir as tmpdir2 } from "node:os";
2317
- import { dirname as dirname8, join as join9 } from "node:path";
1513
+ import { dirname as dirname7, join as join8 } from "node:path";
2318
1514
  import { performance as performance3 } from "node:perf_hooks";
2319
1515
  import { format } from "node:util";
2320
1516
 
2321
1517
  // src/tui/App.jsx
2322
- import React21, { useState as useState11, useCallback as useCallback12, useEffect as useEffect13, useLayoutEffect as useLayoutEffect5, useMemo as useMemo4, useRef as useRef13 } from "react";
1518
+ import React21, { useState as useState11, useCallback as useCallback13, useEffect as useEffect14, useLayoutEffect as useLayoutEffect5, useMemo as useMemo5, useRef as useRef14 } from "react";
2323
1519
  import { Box as Box19, Text as Text21, useApp, useInput as useInput6, useStdin as useStdin3, useStdout as useStdout2 } from "../../../vendor/ink/build/index.js";
2324
1520
 
2325
1521
  // src/tui/themes/base.mjs
@@ -3992,8 +3188,8 @@ function parseToolArgs(args) {
3992
3188
  }
3993
3189
  return { value: args };
3994
3190
  }
3995
- function displayToolPath(path3) {
3996
- const text = String(path3 ?? "");
3191
+ function displayToolPath(path2) {
3192
+ const text = String(path2 ?? "");
3997
3193
  return text.replace(/\\/g, "/").split("/").filter(Boolean).at(-1) || text;
3998
3194
  }
3999
3195
  function compactParts(parts) {
@@ -4187,16 +3383,16 @@ function looksLikeZeroResultText(text) {
4187
3383
  return /^\(?\s*(?:no|0)\s+(?:fuzzy\s+)?(?:match(?:es)?|results?|files?|entries|candidates?|hits?)\b/i.test(trimmed) || /^\(?\s*(?:empty|none)\s*\)?$/i.test(trimmed) || /^no\s+\S+\s+(?:found|matched)\b/i.test(trimmed);
4188
3384
  }
4189
3385
  function splitPathAndDelta(value, explicitDelta = "") {
4190
- let path3 = String(value ?? "").trim();
3386
+ let path2 = String(value ?? "").trim();
4191
3387
  let delta = String(explicitDelta ?? "").trim();
4192
3388
  if (!delta) {
4193
- const sep = path3.lastIndexOf(" \u2014 ");
3389
+ const sep = path2.lastIndexOf(" \u2014 ");
4194
3390
  if (sep !== -1) {
4195
- delta = path3.slice(sep + 3).trim();
4196
- path3 = path3.slice(0, sep).trim();
3391
+ delta = path2.slice(sep + 3).trim();
3392
+ path2 = path2.slice(0, sep).trim();
4197
3393
  }
4198
3394
  }
4199
- return { path: path3, delta };
3395
+ return { path: path2, delta };
4200
3396
  }
4201
3397
  function parseLineDelta(delta) {
4202
3398
  const totals = { added: 0, removed: 0, seen: false };
@@ -4230,14 +3426,14 @@ function summarizeUpdateResult(text, args) {
4230
3426
  for (const line of String(text ?? "").split("\n")) {
4231
3427
  const ok = /^\s*OK\s+(modify|add|delete|create)\s+(.+?)\s*$/i.exec(line);
4232
3428
  if (ok) {
4233
- const { path: path3, delta } = splitPathAndDelta(ok[2]);
4234
- changed.push({ action: ok[1].toLowerCase(), path: path3, delta });
3429
+ const { path: path2, delta } = splitPathAndDelta(ok[2]);
3430
+ changed.push({ action: ok[1].toLowerCase(), path: path2, delta });
4235
3431
  continue;
4236
3432
  }
4237
3433
  const edited = /^\s*(Edited|Created|Updated|Wrote):\s+(.+?)(?:\s+\(([^)]+)\))?\s*$/i.exec(line);
4238
3434
  if (edited) {
4239
- const { path: path3, delta } = splitPathAndDelta(edited[2], edited[3] || "");
4240
- changed.push({ action: edited[1].toLowerCase(), path: path3, delta });
3435
+ const { path: path2, delta } = splitPathAndDelta(edited[2], edited[3] || "");
3436
+ changed.push({ action: edited[1].toLowerCase(), path: path2, delta });
4241
3437
  }
4242
3438
  }
4243
3439
  if (changed.length === 1) {
@@ -4957,11 +4153,6 @@ function toolWorkUnit(name, args = {}, category = "") {
4957
4153
  }
4958
4154
  return unitDescriptor("Patch", {
4959
4155
  count: patchFileCount(a) || 1,
4960
- // post_shell is NOT named here: the turn engine already splits such a
4961
- // call into this edit card plus a trailing Shell card that carries the
4962
- // real command, its output and its failure banner (turn.mjs
4963
- // postShellTwins). Repeating "& verifying" in the header declared the
4964
- // verify phase twice and truncated the label on narrow panes.
4965
4156
  active: creating ? "Creating" : deleting ? "Deleting" : "Editing",
4966
4157
  done: creating ? "Created" : deleting ? "Deleted" : "Edited",
4967
4158
  noun: "file"
@@ -5179,9 +4370,9 @@ function reconcileTimer() {
5179
4370
  clearTimeout(timer);
5180
4371
  timer = null;
5181
4372
  }
5182
- const delay2 = nextDelayMs(Date.now());
5183
- if (delay2 == null) return;
5184
- timer = setTimeout(onTick, delay2);
4373
+ const delay = nextDelayMs(Date.now());
4374
+ if (delay == null) return;
4375
+ timer = setTimeout(onTick, delay);
5185
4376
  timer.unref?.();
5186
4377
  }
5187
4378
  function onTick() {
@@ -5411,6 +4602,16 @@ var SPINNER_VERBS = [
5411
4602
  "Zigzagging"
5412
4603
  ];
5413
4604
  var SPINNER_VERB_ROTATE_MS = 3e4;
4605
+ var SPINNER_VERB_CHANGE_ODDS = 0.5;
4606
+ function boundarySwaps(anchor, boundary) {
4607
+ let h = Math.floor(anchor / 1e3) + Math.imul(boundary, 2654435761) | 0;
4608
+ h = Math.imul(h ^ h >>> 16, 569420461);
4609
+ h = Math.imul(h ^ h >>> 15, 1935289751);
4610
+ h = (h ^ h >>> 15) >>> 0;
4611
+ return h / 4294967296 < SPINNER_VERB_CHANGE_ODDS;
4612
+ }
4613
+ var MAX_COUNTED_BOUNDARIES = 4096;
4614
+ var MAX_HELD_BOUNDARIES = 3;
5414
4615
  var SPINNER_MODE_OVERRIDE_VERBS = {
5415
4616
  compacting: "Compacting conversation",
5416
4617
  "auto-clear": "Auto-clearing conversation",
@@ -5420,14 +4621,24 @@ function spinnerVerbFor(startedAt = 0, now = 0, pool = SPINNER_VERBS) {
5420
4621
  const list = Array.isArray(pool) && pool.length ? pool : SPINNER_VERBS;
5421
4622
  const anchor = Math.max(0, Number(startedAt) || 0);
5422
4623
  const elapsed = anchor > 0 ? Math.max(0, (Number(now) || 0) - anchor) : 0;
5423
- const slot = Math.floor(elapsed / SPINNER_VERB_ROTATE_MS);
5424
- const seed = Math.floor(anchor / 1e3) + slot;
4624
+ const boundaries = Math.floor(elapsed / SPINNER_VERB_ROTATE_MS);
4625
+ const counted = Math.min(boundaries, MAX_COUNTED_BOUNDARIES);
4626
+ let swaps = boundaries - counted;
4627
+ let held = 0;
4628
+ for (let boundary = 1; boundary <= counted; boundary += 1) {
4629
+ if (held >= MAX_HELD_BOUNDARIES || boundarySwaps(anchor, boundary)) {
4630
+ swaps += 1;
4631
+ held = 0;
4632
+ } else {
4633
+ held += 1;
4634
+ }
4635
+ }
4636
+ const seed = Math.floor(anchor / 1e3) + swaps;
5425
4637
  return list[(seed * 7 + 3) % list.length];
5426
4638
  }
5427
4639
  var SPINNER_FRAMES = ["\u25C7", "\u25C6", "\u25C8", "\u25C6", "\u25C7"];
5428
4640
 
5429
4641
  // src/tui/spinner-meta.mjs
5430
- var SHOW_TOKENS_AFTER_MS = 3e4;
5431
4642
  var compactNumberFormatter = new Intl.NumberFormat("en-US", {
5432
4643
  notation: "compact",
5433
4644
  maximumFractionDigits: 1,
@@ -5462,7 +4673,10 @@ function buildSpinnerMeta({
5462
4673
  const thinkingText = spinnerThinkingLabel({ thinking, thinkingSince, thinkingMs, effort });
5463
4674
  return {
5464
4675
  tokensText: tokens > 0 ? `${formatSpinnerTokens(tokens)} tokens` : "",
5465
- showTokens: tokens > 0 && (Boolean(verbose) || Number(elapsedMs) > SHOW_TOKENS_AFTER_MS),
4676
+ // The count appears with the FIRST token (user: 30초가 아니라 바로).
4677
+ // Elapsed was never gated, so the byline used to grow a second field
4678
+ // mid-turn for no reason the reader could see; now both arrive together.
4679
+ showTokens: tokens > 0,
5466
4680
  thinkingText,
5467
4681
  thinkingActive: Boolean(thinking || Number(thinkingSince) > 0)
5468
4682
  };
@@ -6001,8 +5215,8 @@ function hasActiveStatuslineWork(line, agentWorkers = [], agentJobs = [], active
6001
5215
  function bootFullRenderEligible(mountAtMs, line, agentWorkers = [], agentJobs = [], activeTools = null) {
6002
5216
  const elapsed = Date.now() - mountAtMs;
6003
5217
  const active = hasActiveStatuslineWork(line, agentWorkers, agentJobs, activeTools);
6004
- const delay2 = active ? STATUSLINE_BOOT_FULL_DELAY_ACTIVE_MS : STATUSLINE_BOOT_FULL_DELAY_MS;
6005
- return elapsed >= delay2;
5218
+ const delay = active ? STATUSLINE_BOOT_FULL_DELAY_ACTIVE_MS : STATUSLINE_BOOT_FULL_DELAY_MS;
5219
+ return elapsed >= delay;
6006
5220
  }
6007
5221
  function canAttemptBootFullRender(nextAttemptAtMs = 0) {
6008
5222
  return Date.now() >= nextAttemptAtMs;
@@ -6775,15 +5989,15 @@ function singleTrailingLineBreakPrefix(text) {
6775
5989
  function draftStateEqual(a, b) {
6776
5990
  return a.value === b.value && a.cursor === b.cursor && a.selectionAnchor === b.selectionAnchor;
6777
5991
  }
6778
- var MODIFIED_ENTER_SHIFT_OR_CTRL = 1 | 4;
5992
+ var MODIFIED_ENTER_NEWLINE = 1 | 2 | 4;
6779
5993
  function isModifiedEnterSequence(input) {
6780
5994
  const text = String(input ?? "");
6781
5995
  const body = text.startsWith("\x1B[") ? text.slice(2) : text.startsWith("[") ? text.slice(1) : "";
6782
5996
  if (!body) return false;
6783
5997
  const kitty = /^13;(\d+)(?::\d+)?(?:;[\d:]+)?u$/.exec(body);
6784
- if (kitty) return (Number(kitty[1]) - 1 & MODIFIED_ENTER_SHIFT_OR_CTRL) !== 0;
5998
+ if (kitty) return (Number(kitty[1]) - 1 & MODIFIED_ENTER_NEWLINE) !== 0;
6785
5999
  const modifyOtherKeys = /^27;(\d+);13~$/.exec(body);
6786
- return Boolean(modifyOtherKeys && (Number(modifyOtherKeys[1]) - 1 & MODIFIED_ENTER_SHIFT_OR_CTRL) !== 0);
6000
+ return Boolean(modifyOtherKeys && (Number(modifyOtherKeys[1]) - 1 & MODIFIED_ENTER_NEWLINE) !== 0);
6787
6001
  }
6788
6002
  function isAnyModifiedEnterSequence(input) {
6789
6003
  const text = String(input ?? "");
@@ -6796,9 +6010,6 @@ function isAnyModifiedEnterSequence(input) {
6796
6010
  }
6797
6011
 
6798
6012
  // src/tui/components/prompt-input/immediate-render.mjs
6799
- function suppressed(value) {
6800
- return typeof value === "function" ? value() === true : value === true;
6801
- }
6802
6013
  function cancelPromptImmediateFlush(throttle, clearTimer = clearTimeout) {
6803
6014
  if (!throttle || throttle.timer === null) return false;
6804
6015
  clearTimer(throttle.timer);
@@ -6807,7 +6018,6 @@ function cancelPromptImmediateFlush(throttle, clearTimer = clearTimeout) {
6807
6018
  }
6808
6019
  function schedulePromptImmediateFlush({
6809
6020
  throttle,
6810
- isSuppressed = false,
6811
6021
  flush,
6812
6022
  intervalMs = 16,
6813
6023
  now = Date.now,
@@ -6818,10 +6028,6 @@ function schedulePromptImmediateFlush({
6818
6028
  if (!throttle || typeof flush !== "function") {
6819
6029
  throw new TypeError("schedulePromptImmediateFlush requires throttle state and a flush function");
6820
6030
  }
6821
- if (suppressed(isSuppressed)) {
6822
- cancelPromptImmediateFlush(throttle, clearTimer);
6823
- return false;
6824
- }
6825
6031
  const current = now();
6826
6032
  const elapsed = current - throttle.lastAt;
6827
6033
  if (elapsed >= intervalMs) {
@@ -6833,13 +6039,35 @@ function schedulePromptImmediateFlush({
6833
6039
  if (throttle.timer !== null) return false;
6834
6040
  throttle.timer = setTimer(() => {
6835
6041
  throttle.timer = null;
6836
- if (suppressed(isSuppressed)) return;
6837
6042
  throttle.lastAt = now();
6838
6043
  flush();
6839
6044
  }, intervalMs - elapsed);
6840
6045
  return true;
6841
6046
  }
6842
6047
 
6048
+ // src/tui/components/prompt-input/escape-policy.mjs
6049
+ var PROMPT_ESCAPE_CLEAR_WINDOW_MS = 800;
6050
+ function classifyPromptEscape({
6051
+ interruptActive = false,
6052
+ hasQueuedMessages = false,
6053
+ hasMessages = false,
6054
+ value = "",
6055
+ lastClearPressAt = 0,
6056
+ now = Date.now()
6057
+ } = {}) {
6058
+ if (interruptActive) return { action: "interrupt", nextClearPressAt: 0 };
6059
+ if (hasQueuedMessages) return { action: "restore-queue", nextClearPressAt: 0 };
6060
+ const current = Number(now);
6061
+ const previous = Number(lastClearPressAt);
6062
+ const repeated = Number.isFinite(current) && Number.isFinite(previous) && previous > 0 && current >= previous && current - previous <= PROMPT_ESCAPE_CLEAR_WINDOW_MS;
6063
+ const armed = Number.isFinite(current) && current > 0 ? current : 1;
6064
+ if (String(value ?? "") === "") {
6065
+ if (!hasMessages) return { action: "idle", nextClearPressAt: 0 };
6066
+ return repeated ? { action: "message-selector", nextClearPressAt: 0 } : { action: "arm-select", nextClearPressAt: armed };
6067
+ }
6068
+ return repeated ? { action: "clear", nextClearPressAt: 0 } : { action: "arm-clear", nextClearPressAt: armed };
6069
+ }
6070
+
6843
6071
  // src/tui/components/PromptInput.jsx
6844
6072
  import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
6845
6073
  var IME_LEFT_GUARD_COLUMNS = 0;
@@ -6862,6 +6090,7 @@ function PromptInput({
6862
6090
  interruptActive = false,
6863
6091
  onInterrupt,
6864
6092
  commandPaletteActive = false,
6093
+ commandPaletteOpen = commandPaletteActive,
6865
6094
  mask = false,
6866
6095
  hint = "",
6867
6096
  hintTone = "info",
@@ -6874,6 +6103,8 @@ function PromptInput({
6874
6103
  onCommandPaletteCancel,
6875
6104
  onCommandPaletteComplete,
6876
6105
  onRestoreQueued,
6106
+ hasQueuedMessages = false,
6107
+ hasMessages = false,
6877
6108
  onHistoryNavigate,
6878
6109
  onPasteText,
6879
6110
  selectionRef,
@@ -6888,10 +6119,10 @@ function PromptInput({
6888
6119
  });
6889
6120
  const [, bumpCursorAnchorEpoch] = useState3(0);
6890
6121
  const draftRef = useRef4(draft);
6891
- const interruptActiveRef = useRef4(interruptActive);
6892
6122
  const lastReportedValueRef = useRef4(draft.value);
6893
6123
  const pasteGenerationRef = useRef4(0);
6894
6124
  const submitGateRef = useRef4(false);
6125
+ const escapeClearAtRef = useRef4(0);
6895
6126
  if (valueRef) valueRef.current = draftRef.current.value;
6896
6127
  const { isRawModeSupported } = useStdin();
6897
6128
  const boxRef = useRef4(null);
@@ -6903,7 +6134,6 @@ function PromptInput({
6903
6134
  const undoRef = useRef4({ past: [], future: [], lastPushAt: 0, lastValue: null });
6904
6135
  const { value, cursor } = draft;
6905
6136
  draftRef.current = draft;
6906
- interruptActiveRef.current = interruptActive;
6907
6137
  if (selectionRef) {
6908
6138
  const range = selectionRange(draft);
6909
6139
  selectionRef.current = range ? { range, text: mask ? "" : draft.value.slice(range.start, range.end) } : null;
@@ -6928,7 +6158,6 @@ function PromptInput({
6928
6158
  const scheduleImmediateFlush = () => {
6929
6159
  schedulePromptImmediateFlush({
6930
6160
  throttle: flushThrottleRef.current,
6931
- isSuppressed: () => interruptActiveRef.current,
6932
6161
  flush: flushImmediate
6933
6162
  });
6934
6163
  };
@@ -6936,6 +6165,7 @@ function PromptInput({
6936
6165
  cancelPromptImmediateFlush(flushThrottleRef.current);
6937
6166
  }, []);
6938
6167
  const commitDraft = (next, options = {}) => {
6168
+ escapeClearAtRef.current = 0;
6939
6169
  const sameDraft = draftStateEqual(draftRef.current, next);
6940
6170
  if (!options.keepPreferredColumn) preferredColumnRef.current = null;
6941
6171
  if (sameDraft) {
@@ -7144,8 +6374,12 @@ function PromptInput({
7144
6374
  commitDraft(moveCursor(current, moved.cursor, { extend }), { keepPreferredColumn: true });
7145
6375
  return true;
7146
6376
  };
7147
- const restoreQueuedToDraft = () => {
7148
- return onRestoreQueued?.(draftRef.current.value) === true;
6377
+ const restoreQueuedToDraft = ({ showHint = false } = {}) => {
6378
+ return onRestoreQueued?.({
6379
+ restoreDraft: true,
6380
+ showHint,
6381
+ currentText: draftRef.current.value
6382
+ }) === true;
7149
6383
  };
7150
6384
  const applyHistoryNavigation = (direction, meta = {}) => {
7151
6385
  const nextValue = onHistoryNavigate?.(direction, draftRef.current.value, meta);
@@ -7237,6 +6471,7 @@ function PromptInput({
7237
6471
  if (disabled) return;
7238
6472
  const rawInput = String(input ?? "");
7239
6473
  const inputKey = rawInput.toLowerCase();
6474
+ if (!key.escape) escapeClearAtRef.current = 0;
7240
6475
  const rawShiftArrowForGrid = rawInput === "\x1B[1;2A" || rawInput === "\x1B[a" || rawInput === "[1;2A" || rawInput === "\x1B[1;2B" || rawInput === "\x1B[b" || rawInput === "[1;2B" || rawInput === "\x1B[1;2C" || rawInput === "\x1B[c" || rawInput === "[1;2C" || rawInput === "\x1B[1;2D" || rawInput === "\x1B[d" || rawInput === "[1;2D" || rawInput === "\x1B[1;6A" || rawInput === "[1;6A" || rawInput === "\x1B[1;6B" || rawInput === "[1;6B" || rawInput === "\x1B[1;6C" || rawInput === "[1;6C" || rawInput === "\x1B[1;6D" || rawInput === "[1;6D";
7241
6476
  const gridSelectionActive = typeof suppressShiftNavRef === "function" ? suppressShiftNavRef() : typeof suppressShiftNavRef?.current === "function" ? suppressShiftNavRef.current() : Boolean(suppressShiftNavRef?.current);
7242
6477
  if (gridSelectionActive) {
@@ -7264,13 +6499,13 @@ function PromptInput({
7264
6499
  const lineBreakIndex = rawInput.search(/[\r\n]/);
7265
6500
  const rawEnter = rawInput === "\r" || rawInput === "\n" || rawInput === "\r\n";
7266
6501
  const trailingEnterPrefix = singleTrailingLineBreakPrefix(rawInput);
7267
- const rawCtrlEnter = isModifiedEnterSequence(rawInput);
7268
- const modifiedLineBreak = key.shift || key.meta || key.ctrl || rawCtrlEnter;
6502
+ const rawModifiedEnter = isModifiedEnterSequence(rawInput);
6503
+ const modifiedLineBreak = key.shift || key.meta || key.ctrl || rawModifiedEnter;
7269
6504
  if (rawInput === "\n" && !key.return || key.ctrl && inputKey === "j") {
7270
6505
  updateDraft((d) => replaceSelection(d, "\n"));
7271
6506
  return;
7272
6507
  }
7273
- if (!rawCtrlEnter && isAnyModifiedEnterSequence(rawInput)) {
6508
+ if (!rawModifiedEnter && isAnyModifiedEnterSequence(rawInput)) {
7274
6509
  return;
7275
6510
  }
7276
6511
  const pasteFallback = lineBreakIndex !== -1 && trailingEnterPrefix === null && !rawEnter && (rawInput.length > 1 || !key.return);
@@ -7287,7 +6522,7 @@ function PromptInput({
7287
6522
  submitEnterChunk(trailingEnterPrefix);
7288
6523
  return;
7289
6524
  }
7290
- if (rawCtrlEnter) {
6525
+ if (rawModifiedEnter) {
7291
6526
  updateDraft((d) => replaceSelection(d, "\n"));
7292
6527
  return;
7293
6528
  }
@@ -7340,12 +6575,10 @@ ${d.value.slice(d.cursor)}`,
7340
6575
  if (!moveDraftVertically(-1, { extend: true })) {
7341
6576
  updateDraft((d) => moveCursor(d, 0, { extend: true }));
7342
6577
  }
7343
- } else {
7344
- const hasDraftText = String(draftRef.current.value || "").trim().length > 0;
7345
- if (!hasDraftText) {
7346
- if (!restoreQueuedToDraft()) applyHistoryNavigation("up", { emptyDraft: true });
7347
- } else if (!moveDraftVertically(-1, { extend: false })) {
7348
- applyHistoryNavigation("up", { emptyDraft: false });
6578
+ } else if (!moveDraftVertically(-1, { extend: false })) {
6579
+ const emptyDraft = String(draftRef.current.value || "").length === 0;
6580
+ if (!hasQueuedMessages || !restoreQueuedToDraft()) {
6581
+ applyHistoryNavigation("up", { emptyDraft });
7349
6582
  }
7350
6583
  }
7351
6584
  }
@@ -7360,7 +6593,7 @@ ${d.value.slice(d.cursor)}`,
7360
6593
  updateDraft((d) => moveCursor(d, d.value.length, { extend: true }));
7361
6594
  }
7362
6595
  } else if (!moveDraftVertically(1, { extend: false })) {
7363
- applyHistoryNavigation("down", { emptyDraft: String(draftRef.current.value || "").trim().length === 0 });
6596
+ applyHistoryNavigation("down", { emptyDraft: String(draftRef.current.value || "").length === 0 });
7364
6597
  }
7365
6598
  }
7366
6599
  return;
@@ -7392,9 +6625,8 @@ ${d.value.slice(d.cursor)}`,
7392
6625
  if (onTab?.(draftRef.current.value) === true) return;
7393
6626
  }
7394
6627
  if (key.escape) {
7395
- if (commandPaletteActive) {
6628
+ if (commandPaletteOpen) {
7396
6629
  onCommandPaletteCancel?.(draftRef.current.value);
7397
- commitDraft({ value: "", cursor: 0, selectionAnchor: null });
7398
6630
  return;
7399
6631
  }
7400
6632
  if (selectionRange(draftRef.current)) {
@@ -7405,19 +6637,48 @@ ${d.value.slice(d.cursor)}`,
7405
6637
  if (onEscape?.(currentValue, { phase: "before" }) === true) {
7406
6638
  return;
7407
6639
  }
7408
- if (currentValue) {
6640
+ let escape = classifyPromptEscape({
6641
+ interruptActive,
6642
+ hasQueuedMessages,
6643
+ hasMessages,
6644
+ value: currentValue,
6645
+ lastClearPressAt: escapeClearAtRef.current
6646
+ });
6647
+ if (escape.action === "restore-queue") {
6648
+ if (restoreQueuedToDraft()) {
6649
+ escapeClearAtRef.current = 0;
6650
+ return;
6651
+ }
6652
+ escape = classifyPromptEscape({
6653
+ interruptActive,
6654
+ hasMessages,
6655
+ value: currentValue,
6656
+ lastClearPressAt: escapeClearAtRef.current
6657
+ });
6658
+ }
6659
+ escapeClearAtRef.current = escape.nextClearPressAt;
6660
+ if (escape.action === "interrupt") {
6661
+ const restoredText = onInterrupt?.(currentValue);
6662
+ if (!currentValue && typeof restoredText === "string") {
6663
+ commitDraft({ value: restoredText, cursor: restoredText.length, selectionAnchor: null });
6664
+ }
6665
+ return;
6666
+ }
6667
+ if (escape.action === "arm-clear") {
6668
+ onEscape?.(currentValue, { phase: "clear-arm" });
6669
+ return;
6670
+ }
6671
+ if (escape.action === "clear") {
7409
6672
  onEscape?.(currentValue, { phase: "clear" });
7410
6673
  commitDraft({ value: "", cursor: 0, selectionAnchor: null });
7411
6674
  return;
7412
6675
  }
7413
- if (interruptActive) {
7414
- const restoredText = onInterrupt?.("");
7415
- if (typeof restoredText === "string") {
7416
- commitDraft({ value: restoredText, cursor: restoredText.length, selectionAnchor: null });
7417
- }
6676
+ if (escape.action === "arm-select") {
6677
+ onEscape?.("", { phase: "select-arm" });
7418
6678
  return;
7419
6679
  }
7420
- if (restoreQueuedToDraft()) {
6680
+ if (escape.action === "message-selector") {
6681
+ onEscape?.("", { phase: "select" });
7421
6682
  return;
7422
6683
  }
7423
6684
  onEscape?.("", { phase: "empty" });
@@ -8254,11 +7515,11 @@ function ContextUsageView({ detail, columns }) {
8254
7515
  const usage = detail?.usage || {};
8255
7516
  const compaction = detail?.compaction || {};
8256
7517
  const messages = detail?.messages || {};
8257
- const request2 = detail?.request || {};
7518
+ const request = detail?.request || {};
8258
7519
  const lastApi = detail?.lastApi || {};
8259
7520
  const cache = detail?.cache || {};
8260
7521
  const semantic = messages.semantic || {};
8261
- const schema = request2.toolSchemaBreakdown || {};
7522
+ const schema = request.toolSchemaBreakdown || {};
8262
7523
  const usedTokens = finiteNumber(usage.usedTokens);
8263
7524
  const windowTokens = finiteNumber(usage.windowTokens);
8264
7525
  const freeTokens = windowTokens ? Math.max(0, windowTokens - usedTokens) : finiteNumber(usage.freeTokens);
@@ -8839,15 +8100,15 @@ function singleTrailingLineBreakPrefix2(text) {
8839
8100
  const prefix = normalized.slice(0, -1);
8840
8101
  return prefix.includes("\n") ? null : prefix;
8841
8102
  }
8842
- var MODIFIED_ENTER_SHIFT_OR_CTRL2 = 1 | 4;
8103
+ var MODIFIED_ENTER_SHIFT_OR_CTRL = 1 | 4;
8843
8104
  function isModifiedEnterSequence2(input) {
8844
8105
  const text = String(input ?? "");
8845
8106
  const body = text.startsWith("\x1B[") ? text.slice(2) : text.startsWith("[") ? text.slice(1) : "";
8846
8107
  if (!body) return false;
8847
8108
  const kitty = /^13;(\d+)(?::\d+)?(?:;[\d:]+)?u$/.exec(body);
8848
- if (kitty) return (Number(kitty[1]) - 1 & MODIFIED_ENTER_SHIFT_OR_CTRL2) !== 0;
8109
+ if (kitty) return (Number(kitty[1]) - 1 & MODIFIED_ENTER_SHIFT_OR_CTRL) !== 0;
8849
8110
  const modifyOtherKeys = /^27;(\d+);13~$/.exec(body);
8850
- return Boolean(modifyOtherKeys && (Number(modifyOtherKeys[1]) - 1 & MODIFIED_ENTER_SHIFT_OR_CTRL2) !== 0);
8111
+ return Boolean(modifyOtherKeys && (Number(modifyOtherKeys[1]) - 1 & MODIFIED_ENTER_SHIFT_OR_CTRL) !== 0);
8851
8112
  }
8852
8113
  function isAnyModifiedEnterSequence2(input) {
8853
8114
  const text = String(input ?? "");
@@ -9211,169 +8472,17 @@ function TextEntryPanel({
9211
8472
  // src/tui/App.jsx
9212
8473
  init_paste_attachments();
9213
8474
 
9214
- // src/standalone/projects.mjs
9215
- import { homedir as homedir2 } from "node:os";
9216
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, statSync, writeFileSync as writeFileSync2 } from "node:fs";
9217
- import { basename as basename3, dirname as dirname3, isAbsolute as isAbsolute2, join as join3, resolve as resolve2 } from "node:path";
9218
- var MIXDOG_HOME = process.env.MIXDOG_HOME || join3(homedir2(), ".mixdog");
9219
- var PROJECTS_FILE = process.env.MIXDOG_PROJECTS_FILE ? resolve2(process.env.MIXDOG_PROJECTS_FILE) : join3(MIXDOG_HOME, "projects.json");
9220
- function toAbsolute(rawPath) {
9221
- const text = String(rawPath || "").trim();
9222
- if (!text) return "";
9223
- return isAbsolute2(text) ? resolve2(text) : resolve2(process.cwd(), text);
9224
- }
9225
- function normalizeKey(absPath) {
9226
- const text = String(absPath || "");
9227
- return process.platform === "win32" ? text.replace(/[\\/]+$/, "").toLowerCase() : text.replace(/\/+$/, "");
9228
- }
9229
- function readStore() {
9230
- try {
9231
- if (!existsSync3(PROJECTS_FILE)) return { projects: [] };
9232
- const raw = readFileSync2(PROJECTS_FILE, "utf8");
9233
- const parsed = JSON.parse(raw);
9234
- const projects = Array.isArray(parsed?.projects) ? parsed.projects : [];
9235
- return {
9236
- projects: projects.filter((entry) => entry && typeof entry.path === "string" && entry.path.trim()).map((entry) => ({
9237
- name: String(entry.name || basename3(entry.path) || entry.path),
9238
- path: String(entry.path),
9239
- addedAt: Number(entry.addedAt) || 0,
9240
- ...Number(entry.lastSelectedAt) > 0 ? { lastSelectedAt: Number(entry.lastSelectedAt) } : {}
9241
- }))
9242
- };
9243
- } catch {
9244
- return { projects: [] };
9245
- }
9246
- }
9247
- function writeStore(store) {
9248
- try {
9249
- mkdirSync2(dirname3(PROJECTS_FILE), { recursive: true });
9250
- const tmp = `${PROJECTS_FILE}.${process.pid}.tmp`;
9251
- writeFileSync2(tmp, `${JSON.stringify(store, null, 2)}
9252
- `, "utf8");
9253
- renameSync(tmp, PROJECTS_FILE);
9254
- } catch {
9255
- }
9256
- }
9257
- function ensureProjectIdMarker(absPath, name) {
9258
- try {
9259
- if (process.env.MIXDOG_DISABLE_PROJECT_MARKERS === "1") return;
9260
- if (!absPath) return;
9261
- try {
9262
- if (!statSync(absPath).isDirectory()) return;
9263
- } catch {
9264
- return;
9265
- }
9266
- const markerPath = join3(absPath, ".mixdog", "project.id");
9267
- if (existsSync3(markerPath)) return;
9268
- const value = String(name || basename3(absPath) || "").trim();
9269
- if (!value || value.toLowerCase() === "common") return;
9270
- const markerDir = join3(absPath, ".mixdog");
9271
- mkdirSync2(markerDir, { recursive: true });
9272
- const tmp = `${markerPath}.${process.pid}.tmp`;
9273
- writeFileSync2(tmp, `${value}
9274
- `, "utf8");
9275
- renameSync(tmp, markerPath);
9276
- } catch {
9277
- }
9278
- }
9279
- function projectRecency(entry) {
9280
- const last = Number(entry?.lastSelectedAt);
9281
- if (Number.isFinite(last) && last > 0) return last;
9282
- return Number(entry?.addedAt) || 0;
9283
- }
9284
- function compareProjects(a, b) {
9285
- const byRecency = projectRecency(b) - projectRecency(a);
9286
- if (byRecency !== 0) return byRecency;
9287
- const byName = String(a.name).localeCompare(String(b.name));
9288
- if (byName !== 0) return byName;
9289
- return String(a.path).localeCompare(String(b.path));
9290
- }
9291
- function listProjects() {
9292
- const { projects } = readStore();
9293
- return projects.slice().sort(compareProjects);
9294
- }
9295
- function addProject(rawPath) {
9296
- const absPath = toAbsolute(rawPath);
9297
- if (!absPath) return null;
9298
- const store = readStore();
9299
- const key = normalizeKey(absPath);
9300
- const existing = store.projects.find((entry2) => normalizeKey(entry2.path) === key);
9301
- if (existing) {
9302
- ensureProjectIdMarker(absPath, existing.name);
9303
- return existing;
9304
- }
9305
- const entry = {
9306
- name: basename3(absPath) || absPath,
9307
- path: absPath,
9308
- addedAt: Date.now()
9309
- };
9310
- store.projects.push(entry);
9311
- writeStore(store);
9312
- ensureProjectIdMarker(absPath, entry.name);
9313
- return entry;
9314
- }
9315
- function touchProjectSelected(rawPath) {
9316
- const absPath = toAbsolute(rawPath);
9317
- if (!absPath) return null;
9318
- const store = readStore();
9319
- const key = normalizeKey(absPath);
9320
- const entry = store.projects.find((item) => normalizeKey(item.path) === key);
9321
- if (!entry) return null;
9322
- entry.lastSelectedAt = Date.now();
9323
- writeStore(store);
9324
- ensureProjectIdMarker(absPath, entry.name);
9325
- return entry;
9326
- }
9327
- function renameProject(rawPath, nextName) {
9328
- const absPath = toAbsolute(rawPath);
9329
- if (!absPath) return null;
9330
- const store = readStore();
9331
- const key = normalizeKey(absPath);
9332
- const entry = store.projects.find((item) => normalizeKey(item.path) === key);
9333
- if (!entry) return null;
9334
- const trimmed = String(nextName || "").trim();
9335
- entry.name = trimmed || basename3(absPath) || absPath;
9336
- writeStore(store);
9337
- return entry;
9338
- }
9339
- function pathExists(rawPath) {
9340
- const absPath = toAbsolute(rawPath);
9341
- return !!absPath && existsSync3(absPath);
9342
- }
9343
- function isDirectory(rawPath) {
9344
- const absPath = toAbsolute(rawPath);
9345
- if (!absPath) return false;
9346
- try {
9347
- return statSync(absPath).isDirectory();
9348
- } catch {
9349
- return false;
9350
- }
9351
- }
9352
- function ensureDir(rawPath) {
9353
- const absPath = toAbsolute(rawPath);
9354
- if (!absPath) return "";
9355
- try {
9356
- mkdirSync2(absPath, { recursive: true });
9357
- return absPath;
9358
- } catch {
9359
- return "";
9360
- }
9361
- }
9362
- function resolveProjectPath(rawPath) {
9363
- return toAbsolute(rawPath);
9364
- }
9365
-
9366
8475
  // src/standalone/folder-dialog.mjs
9367
8476
  import { spawn } from "node:child_process";
9368
8477
  import path from "node:path";
9369
8478
  var DIALOG_TIMEOUT_MS = 5 * 60 * 1e3;
9370
8479
  function runCapture(cmd, args, { timeoutMs = DIALOG_TIMEOUT_MS } = {}) {
9371
- return new Promise((resolve5) => {
8480
+ return new Promise((resolve4) => {
9372
8481
  let child;
9373
8482
  try {
9374
8483
  child = spawn(cmd, args, { stdio: ["ignore", "pipe", "ignore"], windowsHide: true });
9375
8484
  } catch (error) {
9376
- resolve5({ ok: false, code: -1, stdout: "", error });
8485
+ resolve4({ ok: false, code: -1, stdout: "", error });
9377
8486
  return;
9378
8487
  }
9379
8488
  let stdout2 = "";
@@ -9385,7 +8494,7 @@ function runCapture(cmd, args, { timeoutMs = DIALOG_TIMEOUT_MS } = {}) {
9385
8494
  child.kill();
9386
8495
  } catch {
9387
8496
  }
9388
- resolve5({ ok: false, code: -1, stdout: "", error: new Error("dialog timed out") });
8497
+ resolve4({ ok: false, code: -1, stdout: "", error: new Error("dialog timed out") });
9389
8498
  }, timeoutMs);
9390
8499
  timer2.unref?.();
9391
8500
  child.stdout.on("data", (chunk) => {
@@ -9395,13 +8504,13 @@ function runCapture(cmd, args, { timeoutMs = DIALOG_TIMEOUT_MS } = {}) {
9395
8504
  if (settled) return;
9396
8505
  settled = true;
9397
8506
  clearTimeout(timer2);
9398
- resolve5({ ok: false, code: -1, stdout: "", error });
8507
+ resolve4({ ok: false, code: -1, stdout: "", error });
9399
8508
  });
9400
8509
  child.on("close", (code) => {
9401
8510
  if (settled) return;
9402
8511
  settled = true;
9403
8512
  clearTimeout(timer2);
9404
- resolve5({ ok: code === 0, code, stdout: stdout2 });
8513
+ resolve4({ ok: code === 0, code, stdout: stdout2 });
9405
8514
  });
9406
8515
  });
9407
8516
  }
@@ -9409,10 +8518,10 @@ function spawnFailed(result) {
9409
8518
  return !!result && result.code === -1;
9410
8519
  }
9411
8520
  function commandExists(cmd) {
9412
- return new Promise((resolve5) => {
8521
+ return new Promise((resolve4) => {
9413
8522
  const probe = process.platform === "win32" ? spawn("where", [cmd], { stdio: "ignore", windowsHide: true }) : spawn("which", [cmd], { stdio: "ignore" });
9414
- probe.on("error", () => resolve5(false));
9415
- probe.on("close", (code) => resolve5(code === 0));
8523
+ probe.on("error", () => resolve4(false));
8524
+ probe.on("close", (code) => resolve4(code === 0));
9416
8525
  });
9417
8526
  }
9418
8527
  function psSingleQuoted(value) {
@@ -9615,9 +8724,9 @@ async function pickFolder({ title = "Select a project folder", initialPath = ""
9615
8724
  powershellScript(title, resolvedInitial)
9616
8725
  ]);
9617
8726
  if (spawnFailed(result)) return { available: false, path: null };
9618
- const path3 = String(result.stdout || "").trim();
9619
- if (!result.ok && !path3) return { available: false, path: null };
9620
- return { available: true, path: path3 || null };
8727
+ const path2 = String(result.stdout || "").trim();
8728
+ if (!result.ok && !path2) return { available: false, path: null };
8729
+ return { available: true, path: path2 || null };
9621
8730
  }
9622
8731
  if (platform2 === "darwin") {
9623
8732
  const safeTitle = String(title).replace(/"/g, '\\"');
@@ -9625,8 +8734,8 @@ async function pickFolder({ title = "Select a project folder", initialPath = ""
9625
8734
  POSIX path of f`;
9626
8735
  const result = await runCapture("osascript", ["-e", script]);
9627
8736
  if (spawnFailed(result)) return { available: false, path: null };
9628
- const path3 = String(result.stdout || "").trim();
9629
- return { available: true, path: path3 || null };
8737
+ const path2 = String(result.stdout || "").trim();
8738
+ return { available: true, path: path2 || null };
9630
8739
  }
9631
8740
  if (await commandExists("zenity")) {
9632
8741
  const result = await runCapture("zenity", [
@@ -9635,16 +8744,16 @@ POSIX path of f`;
9635
8744
  `--title=${title}`
9636
8745
  ]);
9637
8746
  if (spawnFailed(result)) return { available: false, path: null };
9638
- const path3 = String(result.stdout || "").trim();
9639
- if (!result.ok && !path3) return { available: true, path: null };
9640
- return { available: true, path: path3 || null };
8747
+ const path2 = String(result.stdout || "").trim();
8748
+ if (!result.ok && !path2) return { available: true, path: null };
8749
+ return { available: true, path: path2 || null };
9641
8750
  }
9642
8751
  if (await commandExists("kdialog")) {
9643
8752
  const result = await runCapture("kdialog", ["--getexistingdirectory", "."]);
9644
8753
  if (spawnFailed(result)) return { available: false, path: null };
9645
- const path3 = String(result.stdout || "").trim();
9646
- if (!result.ok && !path3) return { available: true, path: null };
9647
- return { available: true, path: path3 || null };
8754
+ const path2 = String(result.stdout || "").trim();
8755
+ if (!result.ok && !path2) return { available: true, path: null };
8756
+ return { available: true, path: path2 || null };
9648
8757
  }
9649
8758
  return { available: false, path: null };
9650
8759
  }
@@ -9967,13 +9076,13 @@ function nativeClipboardCommand(text) {
9967
9076
  function copyToClipboard(text) {
9968
9077
  const value = String(text ?? "");
9969
9078
  const wroteOsc52 = writeOsc52Clipboard(value);
9970
- return new Promise((resolve5, reject) => {
9079
+ return new Promise((resolve4, reject) => {
9971
9080
  const { cmd, args, input } = nativeClipboardCommand(value);
9972
9081
  let child;
9973
9082
  try {
9974
9083
  child = spawn2(cmd, args, { stdio: ["pipe", "ignore", "ignore"], windowsHide: true });
9975
9084
  } catch (e) {
9976
- if (wroteOsc52) resolve5();
9085
+ if (wroteOsc52) resolve4();
9977
9086
  else reject(e);
9978
9087
  return;
9979
9088
  }
@@ -9982,7 +9091,7 @@ function copyToClipboard(text) {
9982
9091
  });
9983
9092
  if (!wroteOsc52) {
9984
9093
  child.on("close", (code) => {
9985
- if (code === 0) resolve5();
9094
+ if (code === 0) resolve4();
9986
9095
  else reject(new Error(`${cmd} exited with code ${code}`));
9987
9096
  });
9988
9097
  }
@@ -9991,7 +9100,7 @@ function copyToClipboard(text) {
9991
9100
  child.stdin.end(input);
9992
9101
  if (wroteOsc52) {
9993
9102
  child.unref?.();
9994
- resolve5();
9103
+ resolve4();
9995
9104
  }
9996
9105
  });
9997
9106
  }
@@ -10168,7 +9277,7 @@ var DIFF_STAT_SUMMARY_RE = /^\s*\d+\s+files?\s+changed\b/;
10168
9277
  function colorizeDiffStatLine(line, c) {
10169
9278
  const m = DIFF_STAT_FILE_RE.exec(line);
10170
9279
  if (m) {
10171
- const [, indent, path3, sep, tail] = m;
9280
+ const [, indent, path2, sep, tail] = m;
10172
9281
  const countMatch = /^(\d+)(\s*)([+\-]*)\s*$/.exec(tail);
10173
9282
  let coloredTail;
10174
9283
  if (countMatch) {
@@ -10179,7 +9288,7 @@ function colorizeDiffStatLine(line, c) {
10179
9288
  } else {
10180
9289
  coloredTail = c.diffContext(tail);
10181
9290
  }
10182
- return `${indent}${c.diffContext(path3)}${c.diffContext(sep)}${coloredTail}`;
9291
+ return `${indent}${c.diffContext(path2)}${c.diffContext(sep)}${coloredTail}`;
10183
9292
  }
10184
9293
  return c.diffContext(line);
10185
9294
  }
@@ -12447,6 +11556,16 @@ var TRANSCRIPT_WINDOW_TAIL_MAX_ITEMS = positiveIntEnv("MIXDOG_TUI_TRANSCRIPT_WIN
12447
11556
  var SELECTION_PAINT_INTERVAL_MS = positiveIntEnv("MIXDOG_TUI_SELECTION_PAINT_MS", 24);
12448
11557
  var SCROLL_COALESCE_MS = positiveIntEnv("MIXDOG_TUI_SCROLL_COALESCE_MS", 16);
12449
11558
  var PROMPT_HISTORY_LIMIT = 50;
11559
+ function accumulateDirectionalScrollDelta(state, deltaRows) {
11560
+ const delta = Number(deltaRows) || 0;
11561
+ if (!delta) return false;
11562
+ const direction = Math.sign(delta);
11563
+ const reversed = Boolean(state.direction && state.direction !== direction);
11564
+ if (reversed) state.pendingRows = 0;
11565
+ state.direction = direction;
11566
+ state.pendingRows += delta;
11567
+ return reversed;
11568
+ }
12450
11569
  function boolEnvDefaultTrue(name) {
12451
11570
  const raw = process.env[name];
12452
11571
  if (raw == null) return true;
@@ -12784,6 +11903,17 @@ function transcriptRowAt(values, index) {
12784
11903
  if (!values || index < 0 || index >= values.length) return 0;
12785
11904
  return typeof values.atIndex === "function" ? values.atIndex(index) : Number(values[index]) || 0;
12786
11905
  }
11906
+ function transcriptSwapReturnsToTail({
11907
+ count = 0,
11908
+ previousCount = 0,
11909
+ firstId = null,
11910
+ previousFirstId = null
11911
+ } = {}) {
11912
+ if (previousCount === 0) return true;
11913
+ if (count < previousCount) return true;
11914
+ if (count === 0) return false;
11915
+ return firstId != null && previousFirstId != null && firstId !== previousFirstId;
11916
+ }
12787
11917
  function appendTranscriptRow(prefix, tailValue) {
12788
11918
  return Object.freeze({
12789
11919
  length: prefix.length + 1,
@@ -13397,7 +12527,7 @@ var notifyRenderFrame = (seq = ++renderAckSeq) => {
13397
12527
  pendingRenderAcks = [];
13398
12528
  for (const ack of acks) ack(seq);
13399
12529
  };
13400
- var yieldToRenderer = ({ frames = 1 } = {}) => new Promise((resolve5) => {
12530
+ var yieldToRenderer = ({ frames = 1 } = {}) => new Promise((resolve4) => {
13401
12531
  const minSeq = renderAckSeq;
13402
12532
  let remainingFrames = Math.max(1, Math.floor(Number(frames) || 1));
13403
12533
  let settled = false;
@@ -13409,7 +12539,7 @@ var yieldToRenderer = ({ frames = 1 } = {}) => new Promise((resolve5) => {
13409
12539
  if (timer2) clearTimeout(timer2);
13410
12540
  const idx = pendingRenderAcks.indexOf(onFrame);
13411
12541
  if (idx !== -1) pendingRenderAcks.splice(idx, 1);
13412
- resolve5();
12542
+ resolve4();
13413
12543
  };
13414
12544
  const onTimeout = () => {
13415
12545
  if (settled) return;
@@ -13470,7 +12600,7 @@ function useTranscriptScroll({
13470
12600
  const scrollAnimationRef = useRef8(null);
13471
12601
  const pendingReadbackRowsRef = useRef8(0);
13472
12602
  const selectionPaintRef = useRef8({ t: 0, rect: null, pending: null, timer: null });
13473
- const scrollCoalesceRef = useRef8({ pendingRows: 0, timer: null });
12603
+ const scrollCoalesceRef = useRef8({ pendingRows: 0, direction: 0, timer: null });
13474
12604
  const selectionTextCaptureRef = useRef8(0);
13475
12605
  const stitchBufferRef = useRef8(/* @__PURE__ */ new Map());
13476
12606
  const stitchHarvestTimerRef = useRef8(null);
@@ -13761,6 +12891,7 @@ function useTranscriptScroll({
13761
12891
  if (coalesceState.timer) clearTimeout(coalesceState.timer);
13762
12892
  coalesceState.timer = null;
13763
12893
  coalesceState.pendingRows = 0;
12894
+ coalesceState.direction = 0;
13764
12895
  }, []);
13765
12896
  const scrollTranscriptRows = useCallback3((deltaRows, options = {}) => {
13766
12897
  const maxTarget = Math.max(0, Number(maxScrollRowsRef.current) || 0);
@@ -13893,13 +13024,18 @@ function useTranscriptScroll({
13893
13024
  });
13894
13025
  const queueScrollCoalesced = useCallback3((deltaRows) => {
13895
13026
  const state = scrollCoalesceRef.current;
13896
- state.pendingRows += deltaRows;
13027
+ const reversed = accumulateDirectionalScrollDelta(state, deltaRows);
13028
+ if (reversed && state.timer) {
13029
+ clearTimeout(state.timer);
13030
+ state.timer = null;
13031
+ }
13897
13032
  if (state.timer) return;
13898
13033
  const rows = state.pendingRows;
13899
13034
  state.pendingRows = 0;
13900
13035
  scrollTranscriptRows(rows);
13901
13036
  state.timer = setTimeout(() => {
13902
13037
  state.timer = null;
13038
+ state.direction = 0;
13903
13039
  if (state.pendingRows !== 0) {
13904
13040
  const remaining = state.pendingRows;
13905
13041
  state.pendingRows = 0;
@@ -14121,17 +13257,18 @@ function usePromptHint() {
14121
13257
  const [promptHintTone, setPromptHintTone] = useState8("info");
14122
13258
  const promptHintTimerRef = useRef10(null);
14123
13259
  const promptHintActiveRef = useRef10(false);
14124
- const showPromptHint = useCallback5((text, tone = "info") => {
13260
+ const showPromptHint = useCallback5((text, tone = "info", dismissMs = PROMPT_HINT_DISMISS_MS) => {
14125
13261
  if (promptHintTimerRef.current) clearTimeout(promptHintTimerRef.current);
14126
13262
  promptHintActiveRef.current = true;
14127
13263
  setPromptHint(String(text || ""));
14128
13264
  setPromptHintTone(tone);
13265
+ const timeoutMs = Number(dismissMs) > 0 ? Number(dismissMs) : PROMPT_HINT_DISMISS_MS;
14129
13266
  promptHintTimerRef.current = setTimeout(() => {
14130
13267
  promptHintTimerRef.current = null;
14131
13268
  promptHintActiveRef.current = false;
14132
13269
  setPromptHint("");
14133
13270
  setPromptHintTone("info");
14134
- }, PROMPT_HINT_DISMISS_MS);
13271
+ }, timeoutMs);
14135
13272
  }, []);
14136
13273
  const showSelectionCopyHint = useCallback5(
14137
13274
  (text, tone = "plain") => showPromptHint(text, tone),
@@ -14204,11 +13341,11 @@ function compactJson(value, max = 180) {
14204
13341
  if (!text) return "";
14205
13342
  return stringWidth8(text) > max ? `${text.slice(0, Math.max(1, max - 1))}\u2026` : text;
14206
13343
  }
14207
- function toolApprovalDescription(request2 = {}) {
14208
- const surface = formatToolSurface(request2.name, request2.args);
14209
- const summary = surface?.summary ? `${surface.label || request2.name} (${surface.summary})` : surface?.label || request2.name || "tool";
14210
- const reason = clean(request2.reason) || "Hook requested approval.";
14211
- const args = compactJson(request2.args);
13344
+ function toolApprovalDescription(request = {}) {
13345
+ const surface = formatToolSurface(request.name, request.args);
13346
+ const summary = surface?.summary ? `${surface.label || request.name} (${surface.summary})` : surface?.label || request.name || "tool";
13347
+ const reason = clean(request.reason) || "Hook requested approval.";
13348
+ const args = compactJson(request.args);
14212
13349
  return [
14213
13350
  summary ? `Tool: ${summary}` : "",
14214
13351
  reason ? `Reason: ${reason}` : "",
@@ -14515,8 +13652,8 @@ function createUsageContextPanels({
14515
13652
  const context = contextStatusValue || {};
14516
13653
  const usage = context.usage || {};
14517
13654
  const messages = context.messages || {};
14518
- const request2 = context.request || {};
14519
- const schemaBreakdown = request2.toolSchemaBreakdown || {};
13655
+ const request = context.request || {};
13656
+ const schemaBreakdown = request.toolSchemaBreakdown || {};
14520
13657
  const schemaTokensFor = (buckets) => buckets.reduce(
14521
13658
  (sum, bucket) => sum + Number(schemaBreakdown?.[bucket]?.tokens || 0),
14522
13659
  0
@@ -14602,7 +13739,7 @@ function createUsageContextPanels({
14602
13739
  {
14603
13740
  value: "request",
14604
13741
  label: "Request overhead",
14605
- description: `${fmt(request2.requestOverheadTokens)} framing \xB7 ${fmt(request2.reserveTokens)} reserve incl. tools`,
13742
+ description: `${fmt(request.requestOverheadTokens)} framing \xB7 ${fmt(request.reserveTokens)} reserve incl. tools`,
14606
13743
  _action: "request"
14607
13744
  },
14608
13745
  {
@@ -14678,9 +13815,9 @@ function createUsageContextPanels({
14678
13815
  results: messages.toolResultCount
14679
13816
  },
14680
13817
  request: {
14681
- toolSchemaBreakdown: request2.toolSchemaBreakdown,
14682
- overheadTokens: request2.requestOverheadTokens,
14683
- reserveTokens: request2.reserveTokens
13818
+ toolSchemaBreakdown: request.toolSchemaBreakdown,
13819
+ overheadTokens: request.requestOverheadTokens,
13820
+ reserveTokens: request.reserveTokens
14684
13821
  },
14685
13822
  lastApi: {
14686
13823
  contextTokens: usage.lastContextTokens,
@@ -14746,6 +13883,13 @@ function createPromptSubmit({
14746
13883
  pastedImagesRef,
14747
13884
  pastedTextsRef
14748
13885
  }) {
13886
+ const backendCall = (name, ...args) => {
13887
+ const target = store?.[name];
13888
+ if (typeof target !== "function") {
13889
+ return Promise.reject(new TypeError(`project backend method ${name} is unavailable`));
13890
+ }
13891
+ return Promise.resolve(target.apply(store, args));
13892
+ };
14749
13893
  const submitPrompt = (prompt, options) => {
14750
13894
  if (typeof store.submitAsync !== "function") return store.submit(prompt, options);
14751
13895
  void Promise.resolve(store.submitAsync(prompt, options)).then((accepted) => {
@@ -14919,9 +14063,14 @@ function createPromptSubmit({
14919
14063
  store.pushNotice("working directory path is required", "warn");
14920
14064
  return false;
14921
14065
  }
14922
- store.setCwd?.(commandText, { message: `Project set: ${projectNameFromPath(commandText)}` });
14923
- setSettingsPrompt(null);
14924
- void openSettingsPicker();
14066
+ void backendCall("setCwd", commandText, {
14067
+ message: `Project set: ${projectNameFromPath(commandText)}`
14068
+ }).then(() => {
14069
+ setSettingsPrompt(null);
14070
+ void openSettingsPicker();
14071
+ }).catch((error) => {
14072
+ store.pushNotice(`project switch failed: ${error?.message || error}`, "error");
14073
+ });
14925
14074
  return true;
14926
14075
  }
14927
14076
  if (settingsPrompt.kind === "project-new") {
@@ -14929,21 +14078,25 @@ function createPromptSubmit({
14929
14078
  store.pushNotice("project path is required", "warn");
14930
14079
  return false;
14931
14080
  }
14932
- const path3 = resolveProjectPath(commandText);
14933
- if (isDirectory(path3)) {
14934
- setSettingsPrompt(null);
14935
- registerProject(path3);
14936
- return true;
14937
- }
14938
- if (pathExists(path3)) {
14939
- store.pushNotice(`${path3} is not a directory`, "warn");
14940
- return false;
14941
- }
14942
- setSettingsPrompt({
14943
- kind: "project-create-confirm",
14944
- label: "New project \xB7 Create folder?",
14945
- hint: `${path3} does not exist. Type "y" to create it, or anything else to cancel.`,
14946
- pendingPath: path3
14081
+ void backendCall("inspectProjectPath", commandText).then((result) => {
14082
+ const path2 = String(result?.path || commandText);
14083
+ if (result?.directory === true) {
14084
+ setSettingsPrompt(null);
14085
+ void registerProject(path2);
14086
+ return;
14087
+ }
14088
+ if (result?.exists === true) {
14089
+ store.pushNotice(`${path2} is not a directory`, "warn");
14090
+ return;
14091
+ }
14092
+ setSettingsPrompt({
14093
+ kind: "project-create-confirm",
14094
+ label: "New project \xB7 Create folder?",
14095
+ hint: `${path2} does not exist. Type "y" to create it, or anything else to cancel.`,
14096
+ pendingPath: path2
14097
+ });
14098
+ }).catch((error) => {
14099
+ store.pushNotice(`project path check failed: ${error?.message || error}`, "error");
14947
14100
  });
14948
14101
  return true;
14949
14102
  }
@@ -14951,14 +14104,13 @@ function createPromptSubmit({
14951
14104
  const pendingPath = String(settingsPrompt.pendingPath || "");
14952
14105
  const answer = String(commandText || "").trim().toLowerCase();
14953
14106
  if (answer === "y" || answer === "yes") {
14954
- const created = ensureDir(pendingPath);
14955
- if (!created) {
14956
- store.pushNotice(`could not create folder: ${pendingPath}`, "error");
14107
+ void backendCall("ensureProjectDirectory", pendingPath).then((created) => {
14957
14108
  setSettingsPrompt(null);
14958
- return true;
14959
- }
14960
- setSettingsPrompt(null);
14961
- registerProject(pendingPath);
14109
+ void registerProject(created || pendingPath);
14110
+ }).catch((error) => {
14111
+ store.pushNotice(`could not create folder: ${error?.message || error}`, "error");
14112
+ setSettingsPrompt(null);
14113
+ });
14962
14114
  return true;
14963
14115
  }
14964
14116
  setSettingsPrompt(null);
@@ -14967,16 +14119,15 @@ function createPromptSubmit({
14967
14119
  }
14968
14120
  if (settingsPrompt.kind === "project-rename") {
14969
14121
  const targetPath = String(settingsPrompt.projectPath || "");
14970
- try {
14971
- const updated = renameProject(targetPath, commandText);
14122
+ void backendCall("renameProject", targetPath, commandText).then((updated) => {
14972
14123
  if (updated) {
14973
14124
  store.pushNotice(`project renamed to "${updated.name}"`, "info");
14974
14125
  }
14975
- } catch (e) {
14976
- store.pushNotice(`rename failed: ${e?.message || e}`, "error");
14977
- }
14978
- setSettingsPrompt(null);
14979
- openProjectPicker();
14126
+ setSettingsPrompt(null);
14127
+ void openProjectPicker();
14128
+ }).catch((error) => {
14129
+ store.pushNotice(`rename failed: ${error?.message || error}`, "error");
14130
+ });
14980
14131
  return true;
14981
14132
  }
14982
14133
  if (settingsPrompt.kind === "system-shell") {
@@ -16960,7 +16111,7 @@ var Item = React19.memo(function Item2({ item, prevKind, columns, toolOutputExpa
16960
16111
  // src/tui/app/app-view.jsx
16961
16112
  import { Fragment as Fragment6, jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
16962
16113
  function renderAppView(ctx) {
16963
- const { PANEL_MAX_VISIBLE, acceptSlashPalette, activeSlashQuery, activeTools, agentRevision, cancelChannelPrompt, cancelHookPrompt, cancelProviderPrompt, cancelSettingsPrompt, cancelSlashPalette, channelPrompt, clearPromptHint, completeSlashPalette, contextPanel, cycleWorkflowFromPrompt, exiting, expandedOptionPanel, floatingPanelRows, frameColumns, gridSelectionActiveRef, guardHintWidth, handlePromptEscape, handlePromptHistoryNavigate, handlePromptInterrupt, handlePromptPaste, hookPrompt, initialStatusLine, inputBoxHidden, inputHint, inputHintTone, liveSpinner, onPromptDraftChange, onSubmit, overlayHintAttachItemIndex, overlayHintBandRows, overlayHintFallbackRow, overlayHintOnLastItem, panelCloseMaskRows, panelInkMaskEpoch, panelTransitionClearRows, panelTransitionEpoch, picker, pickerOpenedFromEnterRef, pickerOpenedFromEnterTimerRef, pickerVisibleRows, promptBoxRectRef, promptBoxRows, promptDraft, promptDraftOverride, promptMetaVisible, promptMouseSelectionRef, promptSelectionRef, promptSpinnerColumns, promptValueRef, providerPrompt, queuedCompact, queuedVisible, renderedTranscriptItems, resizeEpoch, resizeState, restoreQueuedToPrompt, setPicker, setSlashIndex, setTextEntryLayoutRows, settingsPrompt, showWelcomeBanner, slashCommands, slashIndex, slashPaletteOpen, state, statuslineStats, store, toolApproval, toolOutputExpanded, transcriptContentHeight, transcriptGuardRows, transcriptMeasureRef, transcriptTailPinned, transcriptWindow, transientStatusWidth, tuiReady, usagePanel, viewportHeight, welcomePromptHintRows, welcomePromptHintText } = ctx;
16114
+ const { PANEL_MAX_VISIBLE, acceptSlashPalette, activeSlashQuery, activeTools, agentRevision, cancelChannelPrompt, cancelHookPrompt, cancelProviderPrompt, cancelSettingsPrompt, cancelSlashPalette, channelPrompt, clearPromptHint, completeSlashPalette, contextPanel, cycleWorkflowFromPrompt, exiting, expandedOptionPanel, floatingPanelRows, frameColumns, gridSelectionActiveRef, guardHintWidth, handlePromptEscape, handlePromptHistoryNavigate, handlePromptInterrupt, handlePromptPaste, hasUserMessages, hookPrompt, initialStatusLine, inputBoxHidden, inputHint, inputHintTone, liveSpinner, onPromptDraftChange, onSubmit, overlayHintAttachItemIndex, overlayHintBandRows, overlayHintFallbackRow, overlayHintOnLastItem, panelCloseMaskRows, panelInkMaskEpoch, panelTransitionClearRows, panelTransitionEpoch, picker, pickerOpenedFromEnterRef, pickerOpenedFromEnterTimerRef, pickerVisibleRows, promptBoxRectRef, promptBoxRows, promptDraft, promptDraftOverride, promptMetaVisible, promptMouseSelectionRef, promptSelectionRef, promptSpinnerColumns, promptValueRef, providerPrompt, queuedCompact, queuedVisible, renderedTranscriptItems, resizeEpoch, resizeState, restoreQueuedToPrompt, setPicker, setSlashIndex, setTextEntryLayoutRows, settingsPrompt, showWelcomeBanner, slashCommands, slashIndex, slashPaletteOpen, state, statuslineStats, store, toolApproval, toolOutputExpanded, transcriptContentHeight, transcriptGuardRows, transcriptMeasureRef, transcriptTailPinned, transcriptWindow, transientStatusWidth, tuiReady, usagePanel, viewportHeight, welcomePromptHintRows, welcomePromptHintText } = ctx;
16964
16115
  const promptInputControl = /* @__PURE__ */ jsx20(
16965
16116
  PromptInput,
16966
16117
  {
@@ -16984,6 +16135,7 @@ function renderAppView(ctx) {
16984
16135
  onPasteText: handlePromptPaste,
16985
16136
  onHistoryNavigate: handlePromptHistoryNavigate,
16986
16137
  commandPaletteActive: slashPaletteOpen && slashCommands.length > 0,
16138
+ commandPaletteOpen: slashPaletteOpen,
16987
16139
  onCommandPaletteNavigate: (direction) => {
16988
16140
  setSlashIndex((index) => {
16989
16141
  const total = slashCommands.length;
@@ -16998,7 +16150,9 @@ function renderAppView(ctx) {
16998
16150
  onCommandPaletteAccept: acceptSlashPalette,
16999
16151
  onCommandPaletteCancel: cancelSlashPalette,
17000
16152
  onCommandPaletteComplete: completeSlashPalette,
17001
- onRestoreQueued: (currentText) => restoreQueuedToPrompt({ restoreDraft: true, showHint: false, currentText })
16153
+ onRestoreQueued: restoreQueuedToPrompt,
16154
+ hasQueuedMessages: Array.isArray(state.queued) && state.queued.length > 0,
16155
+ hasMessages: hasUserMessages
17002
16156
  }
17003
16157
  );
17004
16158
  return (
@@ -17448,10 +16602,8 @@ function usePromptDraftFlow({
17448
16602
  return token ? `/${token} ` : void 0;
17449
16603
  }, [slashCommands, slashIndex]);
17450
16604
  const cancelSlashPalette = useCallback8((value = "") => {
17451
- setSlashDismissedFor("");
17452
- setPromptDraft("");
17453
- setPromptDraftOverride({ id: Date.now(), value: "" });
17454
- }, []);
16605
+ setSlashDismissedFor(String(value ?? ""));
16606
+ }, [setSlashDismissedFor]);
17455
16607
  return {
17456
16608
  onPromptDraftChange,
17457
16609
  cancelProviderPrompt,
@@ -17694,10 +16846,10 @@ function createResumePicker({
17694
16846
  formatSessionUpdatedAt: formatSessionUpdatedAt2,
17695
16847
  formatSessionMessageCount: formatSessionMessageCount2
17696
16848
  }) {
17697
- const openResumePicker = () => {
16849
+ const openResumePicker = async () => {
17698
16850
  let sessions;
17699
16851
  try {
17700
- sessions = store.listSessions({ refreshFromStorage: true });
16852
+ sessions = await store.listSessions({ refreshFromStorage: true });
17701
16853
  } catch (e) {
17702
16854
  store.pushNotice(`could not list saved chats: ${e?.message || e}`, "error");
17703
16855
  return;
@@ -17762,16 +16914,16 @@ function createCoreMemoryPicker({
17762
16914
  onCancel: closeMemoryCorePicker
17763
16915
  });
17764
16916
  void store.memoryControl?.({ action: "core", op: "list", project_id: "*" }, { silent: true }).then(async (result) => {
17765
- const memory = await store.getMemorySettings?.() || { enabled: true };
17766
- const memoryOn = memory.enabled !== false;
16917
+ const recap = await store.getRecapSettings?.() || { enabled: true };
16918
+ const recapOn = recap.enabled !== false;
17767
16919
  const coreRows = parseMemoryCoreRows2(result);
17768
16920
  const rows = [
17769
16921
  {
17770
- value: "memory-toggle",
17771
- label: "Memory",
17772
- meta: memoryOn ? "On" : "Off",
17773
- description: memoryOn ? "Background memory cycles on" : "Background memory cycles off",
17774
- _action: "toggle-memory"
16922
+ value: "recap-toggle",
16923
+ label: "Recap",
16924
+ meta: recapOn ? "On" : "Off",
16925
+ description: recapOn ? "Background session recaps on" : "Background recaps off; core memory remains available",
16926
+ _action: "toggle-recap"
17775
16927
  },
17776
16928
  { value: "core-add", label: "Add Memory", description: "store a new curated memory sentence", _action: "add-core" },
17777
16929
  {
@@ -17793,17 +16945,17 @@ function createCoreMemoryPicker({
17793
16945
  labelWidth: 12,
17794
16946
  footer: (item) => item && item._action === "core-entry" ? item._summary || item._element || "" : "",
17795
16947
  onSelect: (_value, item) => {
17796
- if (item?._action === "toggle-memory") toggleMemoryEnabled();
16948
+ if (item?._action === "toggle-recap") toggleRecapEnabled();
17797
16949
  else if (item?._action === "add-core") beginAddCoreMemory();
17798
16950
  else if (item?._action === "core-list") openCoreMemoryListPicker(item._rows);
17799
16951
  else if (item?._line) store.pushNotice(item._line, "info");
17800
16952
  },
17801
16953
  // Toggle rows also respond to ←/→ (matches settings/channels UX).
17802
16954
  onLeft: (item) => {
17803
- if (item?._action === "toggle-memory") toggleMemoryEnabled();
16955
+ if (item?._action === "toggle-recap") toggleRecapEnabled();
17804
16956
  },
17805
16957
  onRight: (item) => {
17806
- if (item?._action === "toggle-memory") toggleMemoryEnabled();
16958
+ if (item?._action === "toggle-recap") toggleRecapEnabled();
17807
16959
  },
17808
16960
  onCancel: closeMemoryCorePicker
17809
16961
  });
@@ -17844,12 +16996,12 @@ function createCoreMemoryPicker({
17844
16996
  store.pushNotice(`core memory failed: ${e?.message || e}`, "error");
17845
16997
  });
17846
16998
  };
17847
- const toggleMemoryEnabled = async () => {
17848
- const memory = await store.getMemorySettings?.() || { enabled: true };
17849
- void Promise.resolve(store.setMemoryEnabled?.(!(memory.enabled !== false))).then((next) => {
17850
- if (!next) store.pushNotice("memory setting is busy", "warn");
17851
- else store.pushNotice(`Memory ${next.enabled ? "on" : "off"}`, "info");
17852
- }).catch((e) => store.pushNotice(`memory toggle failed: ${e?.message || e}`, "error")).finally(() => openMemoryCorePicker());
16999
+ const toggleRecapEnabled = async () => {
17000
+ const recap = await store.getRecapSettings?.() || { enabled: true };
17001
+ void Promise.resolve(store.setRecapEnabled?.(!(recap.enabled !== false))).then((next) => {
17002
+ if (!next) store.pushNotice("recap setting is busy", "warn");
17003
+ else store.pushNotice(`Recap ${next.enabled ? "on" : "off"}`, "info");
17004
+ }).catch((e) => store.pushNotice(`recap toggle failed: ${e?.message || e}`, "error")).finally(() => openMemoryCorePicker());
17853
17005
  };
17854
17006
  const openCoreEntryActionsPicker = (entryItem) => {
17855
17007
  setPicker({
@@ -19107,7 +18259,6 @@ function createOnboardingSteps({
19107
18259
  if (defaultRoute) {
19108
18260
  void store.completeOnboarding?.({
19109
18261
  defaultRoute,
19110
- defaultProvider: defaultRoute.provider,
19111
18262
  ...hasOverrides ? { agentRoutes: { ...overrides } } : {},
19112
18263
  ...searchRoute ? { searchRoute } : {}
19113
18264
  }).then(done).catch(failed);
@@ -19788,7 +18939,7 @@ function createProviderSetupPicker({
19788
18939
  }
19789
18940
  });
19790
18941
  try {
19791
- await new Promise((resolve5) => setTimeout(resolve5, 0));
18942
+ await new Promise((resolve4) => setTimeout(resolve4, 0));
19792
18943
  setup = await store.getProviderSetup();
19793
18944
  } catch (e) {
19794
18945
  store.pushNotice(`providers failed: ${e?.message || e}`, "error");
@@ -20327,7 +19478,7 @@ function createModelPicker({
20327
19478
  items: [],
20328
19479
  onCancel: cancelModelPicker
20329
19480
  });
20330
- await new Promise((resolve5) => setTimeout(resolve5, 0));
19481
+ await new Promise((resolve4) => setTimeout(resolve4, 0));
20331
19482
  try {
20332
19483
  if (options.refreshModels !== true && options.cacheRef !== "search") {
20333
19484
  refreshModelsPromise = Promise.resolve(loadModels({ force: false }));
@@ -20682,7 +19833,7 @@ function createRoutePickers({
20682
19833
  const items = agents.map((agent) => ({
20683
19834
  value: agent.id,
20684
19835
  label: agent.label,
20685
- metaParts: agentModelParts2(routeOverrides[agent.id] || agent.route),
19836
+ metaParts: routeOverrides[agent.id] || agent.route ? agentModelParts2(routeOverrides[agent.id] || agent.route) : [{ text: "(follows main)", width: 17 }, { text: "", width: 6 }, { text: "", width: 4 }],
20686
19837
  description: agent.description || agent.definition?.description || "",
20687
19838
  _agent: agent
20688
19839
  }));
@@ -20909,7 +20060,7 @@ function createSettingsPicker({
20909
20060
  const snapshot = await store.getSettingsSnapshot?.({ heavy: !heavyCache }) || {};
20910
20061
  const autoClear = snapshot.autoClear || {};
20911
20062
  const compaction = snapshot.compaction || {};
20912
- const memory = snapshot.memory || { enabled: true };
20063
+ const recap = snapshot.recap || { enabled: true };
20913
20064
  const channels = snapshot.channels || { enabled: true };
20914
20065
  const systemShell = snapshot.systemShell || { source: "auto", command: "", effective: "" };
20915
20066
  const outputStyle = snapshot.outputStyle || {};
@@ -20929,7 +20080,7 @@ function createSettingsPicker({
20929
20080
  const outputStyleLabel = outputStyle?.current?.label || outputStyle?.current?.id || outputStyle?.configured || "Default";
20930
20081
  const workflowLabel = workflowDisplayName2(workflow);
20931
20082
  const boolLabel = (enabled) => enabled ? "On" : "Off";
20932
- const compactTypeDescription = memory.enabled === false ? "Injects raw transcript lines (memory off: no LLM chunking)." : "Uses Memory recall to rebuild context faster on large histories.";
20083
+ const compactTypeDescription = "Uses Memory recall to rebuild context faster on large histories.";
20933
20084
  const applyAutoClear = (patch = {}) => {
20934
20085
  void Promise.resolve(store.setAutoClear?.(patch)).then((next) => {
20935
20086
  if (!next) store.pushNotice("autoclear unavailable", "warn");
@@ -21130,8 +20281,8 @@ function createSettingsPicker({
21130
20281
  {
21131
20282
  value: "memory",
21132
20283
  label: "Memory",
21133
- meta: boolLabel(memory.enabled !== false),
21134
- description: memory.enabled === false ? "Memory off. Core memories stay editable." : "Session memory and core memories.",
20284
+ meta: `Recap ${boolLabel(recap.enabled !== false)}`,
20285
+ description: recap.enabled === false ? "Background recap off. Core memories and on-demand recall stay available." : "Background recap and core memories.",
21135
20286
  _action: "memory"
21136
20287
  },
21137
20288
  {
@@ -21898,6 +21049,13 @@ function createAppPickers({
21898
21049
 
21899
21050
  // src/tui/app/use-prompt-queue-history.mjs
21900
21051
  import { useCallback as useCallback9, useMemo as useMemo2 } from "react";
21052
+
21053
+ // src/tui/components/prompt-input/restore-policy.mjs
21054
+ function mergeQueuedRestoreText(queuedText = "", currentText = "") {
21055
+ return [String(queuedText ?? ""), String(currentText ?? "")].filter((text) => text.trim()).join("\n");
21056
+ }
21057
+
21058
+ // src/tui/app/use-prompt-queue-history.mjs
21901
21059
  function usePromptQueueHistory({
21902
21060
  store,
21903
21061
  state,
@@ -21930,8 +21088,8 @@ function usePromptQueueHistory({
21930
21088
  let timer2 = null;
21931
21089
  Promise.race([
21932
21090
  Promise.resolve(store.dispose?.("cli-react-exit", { detach: true })),
21933
- new Promise((resolve5) => {
21934
- timer2 = setTimeout(resolve5, 350);
21091
+ new Promise((resolve4) => {
21092
+ timer2 = setTimeout(resolve4, 350);
21935
21093
  })
21936
21094
  ]).finally(() => {
21937
21095
  if (timer2) clearTimeout(timer2);
@@ -21951,8 +21109,12 @@ function usePromptQueueHistory({
21951
21109
  if (restoreDraft) {
21952
21110
  if (restored2.pastedImages) installPastedImages(restored2.pastedImages, { merge: true });
21953
21111
  if (restored2.pastedTexts) installPastedTexts(restored2.pastedTexts, { merge: true });
21954
- syncPromptLayoutRows(restored2.text);
21955
- setPromptDraftOverride({ id: Date.now(), value: restored2.text });
21112
+ const restoredText = mergeQueuedRestoreText(
21113
+ restored2.text,
21114
+ promptValueRef.current ?? currentText
21115
+ );
21116
+ syncPromptLayoutRows(restoredText);
21117
+ setPromptDraftOverride({ id: Date.now(), value: restoredText });
21956
21118
  }
21957
21119
  if (showHint) {
21958
21120
  showPromptHint(`restored ${restored2.count} queued message${restored2.count === 1 ? "" : "s"}`, "info");
@@ -21961,7 +21123,7 @@ function usePromptQueueHistory({
21961
21123
  }
21962
21124
  return true;
21963
21125
  };
21964
- const restored = store.restoreQueued?.(currentText);
21126
+ const restored = store.restoreQueued?.("");
21965
21127
  if (restored && typeof restored.then === "function") {
21966
21128
  const queuedCount = Array.isArray(state?.queued) ? state.queued.length : 0;
21967
21129
  void Promise.resolve(restored).then(apply).catch(() => {
@@ -21993,6 +21155,88 @@ function usePromptQueueHistory({
21993
21155
  return { requestExit, restoreQueuedToPrompt, recentPromptHistory, resetPromptHistoryNav };
21994
21156
  }
21995
21157
 
21158
+ // src/tui/app/message-selector.mjs
21159
+ import { useCallback as useCallback10, useMemo as useMemo3 } from "react";
21160
+ var MAX_SELECTABLE_MESSAGES = 20;
21161
+ function selectableUserItems(items, limit = MAX_SELECTABLE_MESSAGES) {
21162
+ const source = Array.isArray(items) ? items : [];
21163
+ const rows = [];
21164
+ for (let index = source.length - 1; index >= 0 && rows.length < limit; index -= 1) {
21165
+ const item = source[index];
21166
+ if (item?.kind !== "user") continue;
21167
+ if (item?.id == null) continue;
21168
+ const text = String(item.text || "").trim();
21169
+ if (!text) continue;
21170
+ rows.push({ id: String(item.id), text });
21171
+ }
21172
+ return rows.reverse();
21173
+ }
21174
+ function messageSelectorLabel(text, width = 56) {
21175
+ const firstLine = String(text || "").split("\n").map((line) => line.trim()).find(Boolean) || "";
21176
+ if (firstLine.length <= width) return firstLine;
21177
+ return `${firstLine.slice(0, Math.max(1, width - 1))}\u2026`;
21178
+ }
21179
+ function useMessageSelector({
21180
+ store,
21181
+ state,
21182
+ setPicker,
21183
+ setPromptDraftOverride,
21184
+ syncPromptLayoutRows,
21185
+ showPromptHint,
21186
+ clearPromptHint
21187
+ }) {
21188
+ const hasUserMessages = useMemo3(
21189
+ () => (Array.isArray(state.items) ? state.items : []).some(
21190
+ (item) => item?.kind === "user" && String(item?.text || "").trim()
21191
+ ),
21192
+ [state.items]
21193
+ );
21194
+ const applyRewind = useCallback10((restored) => {
21195
+ const text = String(restored?.text || "");
21196
+ if (!text) {
21197
+ showPromptHint("Could not restore that message.", "error");
21198
+ return false;
21199
+ }
21200
+ clearPromptHint();
21201
+ syncPromptLayoutRows(text);
21202
+ setPromptDraftOverride({ id: Date.now(), value: text });
21203
+ return true;
21204
+ }, [clearPromptHint, syncPromptLayoutRows, setPromptDraftOverride, showPromptHint]);
21205
+ const openMessageSelector = useCallback10(() => {
21206
+ const items = store.getState?.().items ?? state.items;
21207
+ const rows = selectableUserItems(items);
21208
+ if (rows.length === 0) {
21209
+ showPromptHint("No message to jump back to.", "info");
21210
+ return false;
21211
+ }
21212
+ setPicker({
21213
+ _kind: "message-selector",
21214
+ title: "Jump back to a message",
21215
+ description: "Rewind the conversation to a previous prompt and edit it.",
21216
+ initialIndex: rows.length - 1,
21217
+ items: rows.map((row) => ({ value: row.id, label: messageSelectorLabel(row.text) })),
21218
+ onSelect: (value) => {
21219
+ setPicker(null);
21220
+ let restored;
21221
+ try {
21222
+ restored = store.rewindToItem?.(value);
21223
+ } catch (error) {
21224
+ showPromptHint(`rewind failed: ${error?.message || error}`, "error");
21225
+ return;
21226
+ }
21227
+ if (restored && typeof restored.then === "function") {
21228
+ void Promise.resolve(restored).then(applyRewind).catch((error) => showPromptHint(`rewind failed: ${error?.message || error}`, "error"));
21229
+ return;
21230
+ }
21231
+ applyRewind(restored);
21232
+ },
21233
+ onCancel: () => setPicker(null)
21234
+ });
21235
+ return true;
21236
+ }, [store, state.items, setPicker, applyRewind, showPromptHint]);
21237
+ return { hasUserMessages, openMessageSelector };
21238
+ }
21239
+
21996
21240
  // src/tui/app/use-terminal-chrome.mjs
21997
21241
  import { useEffect as useEffect11 } from "react";
21998
21242
  function useTerminalChrome({ stdout: stdout2, isRawModeSupported, setResizeState }) {
@@ -22042,7 +21286,7 @@ function useTerminalChrome({ stdout: stdout2, isRawModeSupported, setResizeState
22042
21286
  }
22043
21287
 
22044
21288
  // src/tui/app/use-transcript-window.mjs
22045
- import { useCallback as useCallback10, useEffect as useEffect12, useLayoutEffect as useLayoutEffect4, useMemo as useMemo3, useRef as useRef12, useState as useState10 } from "react";
21289
+ import { useCallback as useCallback11, useEffect as useEffect12, useLayoutEffect as useLayoutEffect4, useMemo as useMemo4, useRef as useRef12, useState as useState10 } from "react";
22046
21290
  var HARVEST_BUMP_STREAK_LIMIT = 10;
22047
21291
  var TUI_DEBUG = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_TUI_DEBUG || ""));
22048
21292
  var harvestBreakerLogged = false;
@@ -22133,7 +21377,7 @@ function useTranscriptWindow({
22133
21377
  useLayoutEffect4(() => {
22134
21378
  if (scrollOffset !== 0) setScrollOffset(0);
22135
21379
  }, [normalizedSessionKey, setScrollOffset]);
22136
- const transcriptMeasureRef = useCallback10((item) => {
21380
+ const transcriptMeasureRef = useCallback11((item) => {
22137
21381
  if (!TRANSCRIPT_MEASURED_ROWS || !item || item.id == null) return void 0;
22138
21382
  if (shouldSuppressFullyFailedToolItem(item)) {
22139
21383
  transcriptMeasuredRowsCache.delete(item);
@@ -22157,8 +21401,6 @@ function useTranscriptWindow({
22157
21401
  return fn;
22158
21402
  }, []);
22159
21403
  const streamingTailItem = streamingTail?.kind === "assistant" && streamingTail.streaming ? streamingTail : null;
22160
- const scrolledUpRowsForPin = Math.max(0, Number(scrollTargetRef.current) || 0);
22161
- const transcriptPinnedForStreaming = followingRef.current || scrolledUpRowsForPin === 0;
22162
21404
  const tailRows = streamingTailItem ? estimateTranscriptItemRowsCached(streamingTailItem, frameColumns, toolOutputExpanded) : 0;
22163
21405
  const tailSig = streamingTailItem ? `${streamingTailItem.id}:${tailRows}` : "_";
22164
21406
  const revision = Math.max(0, Number(structureRevision) || 0);
@@ -22174,13 +21416,8 @@ function useTranscriptWindow({
22174
21416
  revision,
22175
21417
  streamingTailItem
22176
21418
  );
22177
- const transcriptStreamingActive = !!streamingTailItem;
22178
- const scrolledUpForStreamingMeasure = scrolledUpRowsForPin > 0;
22179
- const prevEstimateGeometry = transcriptGeomRef.current?.suppressMeasuredRowHeights === true;
22180
- const hasStreamingReadingAnchor = !!transcriptAnchorRef.current || transcriptAnchorDirtyRef.current;
22181
- const bottomPinnedForMeasure = transcriptPinnedForStreaming;
22182
- const suppressMeasuredRowHeights = bottomPinnedForMeasure || transcriptStreamingActive && (scrolledUpForStreamingMeasure && hasStreamingReadingAnchor || scrolledUpForStreamingMeasure && prevEstimateGeometry && !transcriptPinnedForStreaming);
22183
- const transcriptRowIndex = useMemo3(() => buildTranscriptRowIndexIncremental(transcriptItems, {
21419
+ const suppressMeasuredRowHeights = false;
21420
+ const transcriptRowIndex = useMemo4(() => buildTranscriptRowIndexIncremental(transcriptItems, {
22184
21421
  columns: frameColumns,
22185
21422
  toolOutputExpanded,
22186
21423
  suppressMeasuredRowHeights,
@@ -22279,7 +21516,7 @@ function useTranscriptWindow({
22279
21516
  contentHeight: lockViewRows,
22280
21517
  floatingPanelRows: Number(floatingPanelRows) || 0
22281
21518
  };
22282
- const transcriptWindow = useMemo3(() => transcriptRenderWindow(transcriptItems, {
21519
+ const transcriptWindow = useMemo4(() => transcriptRenderWindow(transcriptItems, {
22283
21520
  scrollOffset: renderScrollOffset,
22284
21521
  viewportHeight: transcriptContentHeight,
22285
21522
  columns: frameColumns,
@@ -22461,7 +21698,7 @@ function useTranscriptWindow({
22461
21698
  const currentPosition = Math.max(0, Number(scrollPositionRef.current) || 0);
22462
21699
  const currentOffset = Math.max(0, Number(scrollOffset) || 0);
22463
21700
  const maxRows = Math.max(0, Number(transcriptWindow.maxScrollRows) || 0);
22464
- if (!followingRef.current && rowDelta > 0 && currentTarget === 0) {
21701
+ if (!followingRef.current && rowDelta >= 0 && currentTarget === 0) {
22465
21702
  followingRef.current = true;
22466
21703
  }
22467
21704
  const nearBottom = followingRef.current;
@@ -22514,6 +21751,16 @@ function useTranscriptWindow({
22514
21751
  maxRows
22515
21752
  });
22516
21753
  if (desired == null) {
21754
+ if (rowDelta < 0 || currentTarget > maxRows) {
21755
+ stopSmoothScroll();
21756
+ transcriptAnchorRef.current = null;
21757
+ transcriptAnchorDirtyRef.current = false;
21758
+ scrollTargetRef.current = 0;
21759
+ scrollPositionRef.current = 0;
21760
+ followingRef.current = true;
21761
+ if (currentOffset !== 0) setScrollOffset(0);
21762
+ return;
21763
+ }
22517
21764
  transcriptAnchorDirtyRef.current = true;
22518
21765
  return;
22519
21766
  }
@@ -22560,6 +21807,11 @@ function useTranscriptWindow({
22560
21807
  }, [themeEpoch, withSelectionClip, paintSelectionRect]);
22561
21808
  useEffect12(() => {
22562
21809
  const maxRows = Math.max(0, Number(transcriptWindow.maxScrollRows) || 0);
21810
+ if (maxRows === 0 && !followingRef.current) {
21811
+ transcriptAnchorRef.current = null;
21812
+ transcriptAnchorDirtyRef.current = false;
21813
+ followingRef.current = true;
21814
+ }
22563
21815
  if (scrollTargetRef.current <= maxRows && scrollPositionRef.current <= maxRows && scrollOffset <= maxRows) return;
22564
21816
  stopSmoothScroll();
22565
21817
  const next = Math.max(0, Math.min(maxRows, scrollTargetRef.current));
@@ -22589,42 +21841,49 @@ function createProjectPicker({
22589
21841
  setSettingsPrompt,
22590
21842
  setContextPanel,
22591
21843
  closeUsagePanel,
22592
- listProjects: listProjects2,
22593
- addProject: addProject2,
22594
- touchProjectSelected: touchProjectSelected2,
22595
- resolveProjectPath: resolveProjectPath2,
22596
21844
  projectNameFromPath: projectNameFromPath3,
22597
21845
  pickFolder: pickFolder2
22598
21846
  }) {
22599
- const buildProjectPickerState = ({ initialEntry = false } = {}) => {
22600
- let projects = [];
22601
- try {
22602
- projects = listProjects2() || [];
22603
- } catch {
22604
- projects = [];
21847
+ const backendCall = (name, ...args) => {
21848
+ const target = store?.[name];
21849
+ if (typeof target !== "function") {
21850
+ return Promise.reject(new TypeError(`project backend method ${name} is unavailable`));
22605
21851
  }
21852
+ return Promise.resolve(target.apply(store, args));
21853
+ };
21854
+ const buildProjectPickerState = ({
21855
+ initialEntry = false,
21856
+ projects = [],
21857
+ loading = false,
21858
+ requestId = null
21859
+ } = {}) => {
22606
21860
  const currentPath = String(state.cwd || process.cwd() || "");
22607
21861
  const items = [];
22608
- for (const project of projects) {
22609
- if (!project?.path) continue;
21862
+ if (!loading) {
21863
+ for (const project of projects) {
21864
+ if (!project?.path) continue;
21865
+ items.push({
21866
+ value: project.path,
21867
+ label: project.name || project.path,
21868
+ meta: project.path,
21869
+ _project: project
21870
+ });
21871
+ }
22610
21872
  items.push({
22611
- value: project.path,
22612
- label: project.name || project.path,
22613
- meta: project.path,
22614
- _project: project
21873
+ value: "__use_current__",
21874
+ label: "Current Path",
21875
+ meta: currentPath,
21876
+ _action: "current"
22615
21877
  });
22616
21878
  }
22617
- items.push({
22618
- value: "__use_current__",
22619
- label: "Current Path",
22620
- meta: currentPath,
22621
- _action: "current"
22622
- });
22623
21879
  return {
22624
21880
  kind: "project",
21881
+ _kind: "project",
21882
+ _projectRequestId: requestId,
21883
+ _projectInitialPending: loading && initialEntry,
22625
21884
  title: "Project",
22626
- description: "Choose a project.",
22627
- help: initialEntry ? "\u2191/\u2193 Select \xB7 Enter Open \xB7 c Create \xB7 r Rename" : "\u2191/\u2193 Select \xB7 Enter Open \xB7 c Create \xB7 r Rename \xB7 Esc Back",
21885
+ description: loading ? "Loading projects from the backend\u2026" : "Choose a project.",
21886
+ help: loading ? "Waiting for the project backend\u2026" : initialEntry ? "\u2191/\u2193 Select \xB7 Enter Open \xB7 c Create \xB7 r Rename" : "\u2191/\u2193 Select \xB7 Enter Open \xB7 c Create \xB7 r Rename \xB7 Esc Back",
22628
21887
  indexMode: "always",
22629
21888
  labelWidth: 18,
22630
21889
  metaWidth: 40,
@@ -22635,20 +21894,11 @@ function createProjectPicker({
22635
21894
  return;
22636
21895
  }
22637
21896
  if (item?._action === "current") {
22638
- setPicker(null);
22639
- try {
22640
- store.setCwd?.(currentPath, {
22641
- notice: !initialEntry,
22642
- message: `Project set: ${projectNameFromPath3(currentPath)}`
22643
- });
22644
- } catch (e) {
22645
- store.pushNotice(`project switch failed: ${e?.message || e}`, "error");
22646
- }
21897
+ void enterProject(currentPath, { notice: !initialEntry, register: false });
22647
21898
  return;
22648
21899
  }
22649
- setPicker(null);
22650
21900
  const project = item?._project;
22651
- if (project?.path) enterProject(project.path, { notice: !initialEntry });
21901
+ if (project?.path) void enterProject(project.path, { notice: !initialEntry });
22652
21902
  },
22653
21903
  onKey: (input, _key, item) => {
22654
21904
  if (input === "c" || input === "C") {
@@ -22707,40 +21957,44 @@ function createProjectPicker({
22707
21957
  openProjectPicker();
22708
21958
  return;
22709
21959
  }
22710
- registerProject(result.path);
21960
+ void registerProject(result.path);
22711
21961
  }).catch(() => {
22712
21962
  beginNewProjectManual();
22713
21963
  });
22714
21964
  };
22715
- const registerProject = (rawPath) => {
22716
- const path3 = resolveProjectPath2(rawPath);
22717
- if (!path3) {
21965
+ const registerProject = async (rawPath) => {
21966
+ const path2 = String(rawPath || "").trim();
21967
+ if (!path2) {
22718
21968
  store.pushNotice("project path is required", "warn");
22719
- return;
21969
+ return false;
22720
21970
  }
22721
21971
  try {
22722
- const project = addProject2(path3);
21972
+ const project = await backendCall("addProject", path2);
22723
21973
  if (project?.name) store.pushNotice(`project added: ${project.name}`, "info");
22724
- openProjectPicker();
21974
+ await openProjectPicker();
21975
+ return true;
22725
21976
  } catch (e) {
22726
21977
  store.pushNotice(`project add failed: ${e?.message || e}`, "error");
21978
+ return false;
22727
21979
  }
22728
21980
  };
22729
- const enterProject = (rawPath, options = {}) => {
22730
- const path3 = resolveProjectPath2(rawPath);
22731
- if (!path3) {
21981
+ const enterProject = async (rawPath, options = {}) => {
21982
+ const path2 = String(rawPath || "").trim();
21983
+ if (!path2) {
22732
21984
  store.pushNotice("project path is required", "warn");
22733
- return;
21985
+ return false;
22734
21986
  }
21987
+ setPicker(null);
22735
21988
  try {
22736
- store.setCwd?.(path3, {
21989
+ const resolved = await backendCall("setCwd", path2, {
22737
21990
  notice: options?.notice !== false,
22738
- message: `Project set: ${projectNameFromPath3(path3)}`
21991
+ message: `Project set: ${projectNameFromPath3(path2)}`
22739
21992
  });
22740
- addProject2(path3);
22741
- touchProjectSelected2(path3);
21993
+ if (options?.register !== false) await backendCall("addProject", resolved || path2);
21994
+ return true;
22742
21995
  } catch (e) {
22743
21996
  store.pushNotice(`project switch failed: ${e?.message || e}`, "error");
21997
+ return false;
22744
21998
  }
22745
21999
  };
22746
22000
  const beginRenameProject = (project) => {
@@ -22759,14 +22013,32 @@ function createProjectPicker({
22759
22013
  initialValue: project.name || ""
22760
22014
  });
22761
22015
  };
22762
- const openProjectPicker = () => {
22016
+ const openProjectPicker = async ({ initialEntry = false } = {}) => {
22763
22017
  setProviderPrompt(null);
22764
22018
  setChannelPrompt(null);
22765
22019
  setHookPrompt(null);
22766
22020
  setSettingsPrompt(null);
22767
22021
  setContextPanel(null);
22768
22022
  closeUsagePanel();
22769
- setPicker(buildProjectPickerState());
22023
+ const requestId = /* @__PURE__ */ Symbol("project-picker-request");
22024
+ setPicker(buildProjectPickerState({
22025
+ initialEntry,
22026
+ loading: true,
22027
+ requestId
22028
+ }));
22029
+ try {
22030
+ const projects = await backendCall("listProjects");
22031
+ setPicker((current) => current?._projectRequestId === requestId ? buildProjectPickerState({
22032
+ initialEntry,
22033
+ projects: Array.isArray(projects) ? projects : [],
22034
+ requestId
22035
+ }) : current);
22036
+ return projects;
22037
+ } catch (error) {
22038
+ setPicker((current) => current?._projectRequestId === requestId ? buildProjectPickerState({ initialEntry, projects: [], requestId }) : current);
22039
+ store.pushNotice(`project list failed: ${error?.message || error}`, "error");
22040
+ return [];
22041
+ }
22770
22042
  };
22771
22043
  return {
22772
22044
  buildProjectPickerState,
@@ -22782,20 +22054,20 @@ function createProjectPicker({
22782
22054
  // src/tui/app/use-prompt-handlers.mjs
22783
22055
  init_paste_attachments();
22784
22056
  init_paste_attachments();
22785
- import { useCallback as useCallback11 } from "react";
22057
+ import { useCallback as useCallback12, useEffect as useEffect13, useRef as useRef13 } from "react";
22786
22058
 
22787
22059
  // src/tui/prompt-history-store.mjs
22788
- import { chmodSync, existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync6, renameSync as renameSync3, writeFileSync as writeFileSync5 } from "node:fs";
22060
+ import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "node:fs";
22789
22061
  init_plugin_paths();
22790
- import { dirname as dirname7, join as join7, resolve as resolve4 } from "node:path";
22062
+ import { dirname as dirname6, join as join6, resolve as resolve3 } from "node:path";
22791
22063
  var PROMPT_HISTORY_LIMIT2 = 50;
22792
22064
  function promptHistoryKey2(value) {
22793
22065
  return String(value || "").trim().replace(/\s+/g, " ");
22794
22066
  }
22795
22067
  function readEntries(filePath) {
22796
- if (!filePath || !existsSync7(filePath)) return [];
22068
+ if (!filePath || !existsSync6(filePath)) return [];
22797
22069
  try {
22798
- const parsed = JSON.parse(readFileSync6(filePath, "utf8"));
22070
+ const parsed = JSON.parse(readFileSync5(filePath, "utf8"));
22799
22071
  const entries = Array.isArray(parsed?.entries) ? parsed.entries : [];
22800
22072
  return entries.map((entry) => String(entry || "").trim()).filter((entry) => promptHistoryKey2(entry));
22801
22073
  } catch {
@@ -22809,10 +22081,10 @@ function buildPayload(entries) {
22809
22081
  function writeEntriesSync(filePath, entries) {
22810
22082
  if (!filePath) return false;
22811
22083
  try {
22812
- mkdirSync6(dirname7(filePath), { recursive: true, mode: 448 });
22084
+ mkdirSync5(dirname6(filePath), { recursive: true, mode: 448 });
22813
22085
  const tmp = `${filePath}.${process.pid}.tmp`;
22814
- writeFileSync5(tmp, buildPayload(entries), { encoding: "utf8", mode: 384 });
22815
- renameSync3(tmp, filePath);
22086
+ writeFileSync4(tmp, buildPayload(entries), { encoding: "utf8", mode: 384 });
22087
+ renameSync2(tmp, filePath);
22816
22088
  try {
22817
22089
  chmodSync(filePath, 384);
22818
22090
  } catch {
@@ -22849,6 +22121,13 @@ function flushPromptHistory() {
22849
22121
  }
22850
22122
  process.once("exit", flushPromptHistory);
22851
22123
 
22124
+ // src/tui/components/prompt-input/interrupt-policy.mjs
22125
+ function promptInterruptRestoreText(result, currentText = "") {
22126
+ if (!result || result?.aborted === false) return "";
22127
+ if (String(currentText ?? "") !== "") return "";
22128
+ return String(result?.restoreText || "").trim();
22129
+ }
22130
+
22852
22131
  // src/tui/app/use-prompt-handlers.mjs
22853
22132
  function usePromptHandlers({
22854
22133
  store,
@@ -22873,6 +22152,7 @@ function usePromptHandlers({
22873
22152
  recentPromptHistory,
22874
22153
  resetPromptHistoryNav,
22875
22154
  restoreQueuedToPrompt,
22155
+ openMessageSelector,
22876
22156
  usagePanel,
22877
22157
  closeUsagePanel,
22878
22158
  contextPanel,
@@ -22884,7 +22164,9 @@ function usePromptHandlers({
22884
22164
  clearPastedTextsSnapshot,
22885
22165
  registerPastedText
22886
22166
  }) {
22887
- const handlePromptPaste = useCallback11((text, meta = {}) => {
22167
+ const interruptGenerationRef = useRef13(0);
22168
+ const pendingInterruptRestoreRef = useRef13(null);
22169
+ const handlePromptPaste = useCallback12((text, meta = {}) => {
22888
22170
  const source = String(meta?.source || "paste");
22889
22171
  const value = String(text ?? "");
22890
22172
  const processText = (raw, returnRaw = false) => {
@@ -22946,7 +22228,7 @@ function usePromptHandlers({
22946
22228
  }
22947
22229
  return processText(value);
22948
22230
  }, [registerPastedImage, registerPastedText, showPromptHint, state.cwd]);
22949
- const handlePromptHistoryNavigate = useCallback11((direction, currentText = "", meta = {}) => {
22231
+ const handlePromptHistoryNavigate = useCallback12((direction, currentText = "", meta = {}) => {
22950
22232
  const currentValue = String(currentText || "");
22951
22233
  const currentKey = promptHistoryKey2(currentValue);
22952
22234
  const nav = promptHistoryNavRef.current || { active: false, index: -1, seed: "", lastValue: "" };
@@ -22993,7 +22275,7 @@ function usePromptHandlers({
22993
22275
  promptHistoryDraftChangeRef.current = true;
22994
22276
  return nextValue;
22995
22277
  }, [recentPromptHistory, resetPromptHistoryNav, clearPromptHint]);
22996
- const handlePromptEscape = useCallback11((text = "", meta = {}) => {
22278
+ const handlePromptEscape = useCallback12((text = "", meta = {}) => {
22997
22279
  if (usagePanel) {
22998
22280
  closeUsagePanel();
22999
22281
  return true;
@@ -23002,8 +22284,28 @@ function usePromptHandlers({
23002
22284
  setContextPanel(null);
23003
22285
  return true;
23004
22286
  }
22287
+ if (meta.phase === "clear-arm") {
22288
+ showPromptHint("Esc again to clear", "plain", PROMPT_ESCAPE_CLEAR_WINDOW_MS);
22289
+ return true;
22290
+ }
22291
+ if (meta.phase === "select-arm") {
22292
+ showPromptHint("Esc again to pick a message", "plain", PROMPT_ESCAPE_CLEAR_WINDOW_MS);
22293
+ return true;
22294
+ }
22295
+ if (meta.phase === "select") {
22296
+ clearPromptHint();
22297
+ if (restoreQueuedToPrompt({ restoreDraft: true, showHint: false, currentText: text })) return true;
22298
+ return openMessageSelector?.() === true;
22299
+ }
23005
22300
  if (meta.phase === "clear") {
22301
+ try {
22302
+ const remembered = store.rememberPromptHistory?.(text);
22303
+ if (remembered?.catch) void remembered.catch(() => {
22304
+ });
22305
+ } catch {
22306
+ }
23006
22307
  clearPastedImagesSnapshot();
22308
+ clearPastedTextsSnapshot();
23007
22309
  clearPromptHint();
23008
22310
  return false;
23009
22311
  }
@@ -23011,21 +22313,57 @@ function usePromptHandlers({
23011
22313
  return restoreQueuedToPrompt({ restoreDraft: true, showHint: false, currentText: text });
23012
22314
  }
23013
22315
  return false;
23014
- }, [contextPanel, usagePanel, closeUsagePanel, restoreQueuedToPrompt, clearPromptHint, clearPastedImagesSnapshot, store]);
23015
- const handlePromptInterrupt = useCallback11((currentText = "") => {
23016
- const result = store.abort?.();
23017
- if (result?.aborted === false) return void 0;
23018
- if (result?.pastedImages) installPastedImages(result.pastedImages, { merge: true });
23019
- if (result?.discardPastedImages) clearPastedImagesSnapshot(result.discardPastedImages);
23020
- if (result?.pastedTexts) installPastedTexts(result.pastedTexts, { merge: true });
23021
- if (result?.discardPastedTexts) clearPastedTextsSnapshot(result.discardPastedTexts);
23022
- const restoreText = String(result?.restoreText || "").trim();
23023
- if (!restoreText) return void 0;
23024
- const existingText = String(currentText || "").trim();
23025
- const nextText = [restoreText, existingText].filter(Boolean).join("\n");
22316
+ }, [contextPanel, usagePanel, closeUsagePanel, restoreQueuedToPrompt, openMessageSelector, showPromptHint, clearPromptHint, clearPastedImagesSnapshot, clearPastedTextsSnapshot, store]);
22317
+ const commitAsyncInterruptRestore = useCallback12((pending) => {
22318
+ if (!pending || pending.generation !== interruptGenerationRef.current) return true;
22319
+ if (store.getState?.().busy) return false;
22320
+ const restoreText = promptInterruptRestoreText(pending.result, promptValueRef.current);
22321
+ if (!restoreText) return true;
22322
+ if (pending.result?.pastedImages) installPastedImages(pending.result.pastedImages, { merge: true });
22323
+ if (pending.result?.pastedTexts) installPastedTexts(pending.result.pastedTexts, { merge: true });
23026
22324
  clearPromptHint();
23027
- return nextText;
23028
- }, [store, clearPromptHint, installPastedImages, clearPastedImagesSnapshot]);
22325
+ syncPromptLayoutRows(restoreText);
22326
+ setPromptDraftOverride({ id: Date.now(), value: restoreText });
22327
+ return true;
22328
+ }, [store, promptValueRef, installPastedImages, installPastedTexts, clearPromptHint, syncPromptLayoutRows, setPromptDraftOverride]);
22329
+ useEffect13(() => {
22330
+ const pending = pendingInterruptRestoreRef.current;
22331
+ if (!pending) return;
22332
+ if (commitAsyncInterruptRestore(pending)) pendingInterruptRestoreRef.current = null;
22333
+ }, [state.busy, commitAsyncInterruptRestore]);
22334
+ const handlePromptInterrupt = useCallback12((currentText = "") => {
22335
+ const generation = ++interruptGenerationRef.current;
22336
+ pendingInterruptRestoreRef.current = null;
22337
+ const applyResult = (result2, draftText, asyncResult = false) => {
22338
+ if (generation !== interruptGenerationRef.current || result2?.aborted === false) return void 0;
22339
+ if (result2?.discardPastedImages) clearPastedImagesSnapshot(result2.discardPastedImages);
22340
+ if (result2?.discardPastedTexts) clearPastedTextsSnapshot(result2.discardPastedTexts);
22341
+ if (asyncResult) {
22342
+ const pending = { generation, result: result2 };
22343
+ if (!commitAsyncInterruptRestore(pending)) pendingInterruptRestoreRef.current = pending;
22344
+ return void 0;
22345
+ }
22346
+ const restoreText = promptInterruptRestoreText(result2, draftText);
22347
+ if (!restoreText) return void 0;
22348
+ if (result2?.pastedImages) installPastedImages(result2.pastedImages, { merge: true });
22349
+ if (result2?.pastedTexts) installPastedTexts(result2.pastedTexts, { merge: true });
22350
+ clearPromptHint();
22351
+ return restoreText;
22352
+ };
22353
+ let result;
22354
+ try {
22355
+ const options = { restorePrompt: String(currentText ?? "") === "" };
22356
+ result = typeof store.abortAsync === "function" ? store.abortAsync(options) : store.abort?.(options);
22357
+ } catch (error) {
22358
+ store.pushNotice?.(`interrupt failed: ${error?.message || error}`, "error");
22359
+ return void 0;
22360
+ }
22361
+ if (result && typeof result.then === "function") {
22362
+ void Promise.resolve(result).then((resolved) => applyResult(resolved, promptValueRef.current, true)).catch((error) => store.pushNotice?.(`interrupt failed: ${error?.message || error}`, "error"));
22363
+ return void 0;
22364
+ }
22365
+ return applyResult(result, currentText, false);
22366
+ }, [store, commitAsyncInterruptRestore, clearPromptHint, installPastedImages, installPastedTexts, clearPastedImagesSnapshot, clearPastedTextsSnapshot]);
23029
22367
  return {
23030
22368
  handlePromptPaste,
23031
22369
  handlePromptHistoryNavigate,
@@ -23092,30 +22430,31 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23092
22430
  const { stdout: stdout2 } = useStdout2();
23093
22431
  const [exiting, setExiting] = useState11(false);
23094
22432
  const [tuiReady, setTuiReady] = useState11(false);
23095
- const exitRequestedRef = useRef13(false);
22433
+ const exitRequestedRef = useRef14(false);
23096
22434
  const [resizeState, setResizeState] = useState11(() => ({ ...terminalSize(stdout2), epoch: 0 }));
23097
22435
  const [panelTransitionEpoch, setPanelTransitionEpoch] = useState11(0);
23098
22436
  const [panelInkMaskEpoch, setPanelInkMaskEpoch] = useState11(0);
23099
22437
  const rightSafetyColumns = 1;
23100
22438
  const frameColumns = Math.max(1, resizeState.columns - rightSafetyColumns);
23101
22439
  const [scrollOffset, setScrollOffset] = useState11(0);
23102
- const scrollPositionRef = useRef13(0);
23103
- const scrollTargetRef = useRef13(0);
23104
- const maxScrollRowsRef = useRef13(0);
23105
- const transcriptBottomSlackRowsRef = useRef13(0);
23106
- const transcriptAnchorRef = useRef13(null);
23107
- const transcriptAnchorDirtyRef = useRef13(false);
23108
- const transcriptGeomRef = useRef13({ prefixRows: null, totalRows: 0, viewRows: 1 });
22440
+ const scrollPositionRef = useRef14(0);
22441
+ const scrollTargetRef = useRef14(0);
22442
+ const maxScrollRowsRef = useRef14(0);
22443
+ const transcriptBottomSlackRowsRef = useRef14(0);
22444
+ const transcriptAnchorRef = useRef14(null);
22445
+ const transcriptAnchorDirtyRef = useRef14(false);
22446
+ const transcriptGeomRef = useRef14({ prefixRows: null, totalRows: 0, viewRows: 1 });
23109
22447
  const [measuredRowsVersion, setMeasuredRowsVersion] = useState11(0);
23110
- const followingRef = useRef13(false);
23111
- const lastItemsCountRef = useRef13(0);
23112
- const pickerOpenedFromEnterRef = useRef13(false);
23113
- const pickerOpenedFromEnterTimerRef = useRef13(null);
23114
- const projectPickerRef = useRef13(null);
22448
+ const followingRef = useRef14(false);
22449
+ const lastItemsCountRef = useRef14(0);
22450
+ const lastFirstItemIdRef = useRef14(null);
22451
+ const pickerOpenedFromEnterRef = useRef14(false);
22452
+ const pickerOpenedFromEnterTimerRef = useRef14(null);
22453
+ const projectPickerRef = useRef14(null);
23115
22454
  const buildProjectPickerState = (opts) => projectPickerRef.current.buildProjectPickerState(opts);
23116
22455
  const [picker, setPickerState] = useState11(null);
23117
- const livePickerRef = useRef13(null);
23118
- const setPicker = useCallback12((next) => {
22456
+ const livePickerRef = useRef14(null);
22457
+ const setPicker = useCallback13((next) => {
23119
22458
  livePickerRef.current = typeof next === "function" ? next(livePickerRef.current) : next;
23120
22459
  setPickerState((prev) => {
23121
22460
  const resolved = typeof next === "function" ? next(prev) : next;
@@ -23136,14 +22475,14 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23136
22475
  livePickerRef.current = picker;
23137
22476
  const [contextPanel, setContextPanel] = useState11(null);
23138
22477
  const [usagePanel, setUsagePanel] = useState11(null);
23139
- const usageRequestRef = useRef13(0);
23140
- const settingsHeavyCacheRef = useRef13(null);
23141
- const closeUsagePanel = useCallback12(() => {
22478
+ const usageRequestRef = useRef14(0);
22479
+ const settingsHeavyCacheRef = useRef14(null);
22480
+ const closeUsagePanel = useCallback13(() => {
23142
22481
  usageRequestRef.current += 1;
23143
22482
  setUsagePanel(null);
23144
22483
  }, []);
23145
22484
  const [providerPrompt, setProviderPrompt] = useState11(null);
23146
- const oauthSubmitRef = useRef13(false);
22485
+ const oauthSubmitRef = useRef14(false);
23147
22486
  const [channelPrompt, setChannelPrompt] = useState11(null);
23148
22487
  const [hookPrompt, setHookPrompt] = useState11(null);
23149
22488
  const [settingsPrompt, setSettingsPrompt] = useState11(null);
@@ -23157,22 +22496,22 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23157
22496
  setSettingsPrompt,
23158
22497
  setContextPanel,
23159
22498
  closeUsagePanel,
23160
- listProjects,
23161
- addProject,
23162
- touchProjectSelected,
23163
- resolveProjectPath,
23164
22499
  projectNameFromPath,
23165
22500
  pickFolder
23166
22501
  });
23167
22502
  projectPickerRef.current = projectPicker;
23168
- const initialPickerBuiltRef = useRef13(false);
22503
+ const initialPickerBuiltRef = useRef14(false);
23169
22504
  if (!initialPickerBuiltRef.current) {
23170
22505
  initialPickerBuiltRef.current = true;
23171
22506
  const onboardingOwnsScreen = !resolveOnboardingCompleted(store, onboardingCompleted) || forceOnboarding;
23172
22507
  if (!onboardingOwnsScreen && state.items.length === 0) {
23173
- setPicker(projectPicker.buildProjectPickerState({ initialEntry: true }));
22508
+ setPicker(projectPicker.buildProjectPickerState({ initialEntry: true, loading: true }));
23174
22509
  }
23175
22510
  }
22511
+ useEffect14(() => {
22512
+ if (livePickerRef.current?._projectInitialPending !== true) return;
22513
+ void projectPickerRef.current?.openProjectPicker({ initialEntry: true });
22514
+ }, [store]);
23176
22515
  const {
23177
22516
  beginNewProject,
23178
22517
  registerProject,
@@ -23181,7 +22520,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23181
22520
  openProjectPicker
23182
22521
  } = projectPicker;
23183
22522
  const [disabledSkills, setDisabledSkillsInner] = useState11(() => /* @__PURE__ */ new Set());
23184
- useEffect13(() => {
22523
+ useEffect14(() => {
23185
22524
  let alive = true;
23186
22525
  void Promise.resolve(store.getDisabledSkills?.()).then((result) => {
23187
22526
  if (!alive) return;
@@ -23193,7 +22532,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23193
22532
  alive = false;
23194
22533
  };
23195
22534
  }, [store]);
23196
- const setDisabledSkills = useCallback12((next) => {
22535
+ const setDisabledSkills = useCallback13((next) => {
23197
22536
  setDisabledSkillsInner((current) => {
23198
22537
  const base = current instanceof Set ? current : new Set(current);
23199
22538
  const set = typeof next === "function" ? next(base) : next instanceof Set ? next : new Set(next);
@@ -23208,7 +22547,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23208
22547
  const toolApproval = state.toolApproval || null;
23209
22548
  const [promptDraft, setPromptDraft] = useState11("");
23210
22549
  const [promptDraftOverride, setPromptDraftOverride] = useState11(null);
23211
- const promptLayoutValueRef = useRef13("");
22550
+ const promptLayoutValueRef = useRef14("");
23212
22551
  const [, setPromptLayoutRows] = useState11(1);
23213
22552
  const [textEntryLayoutRows, setTextEntryLayoutRows] = useState11(1);
23214
22553
  const {
@@ -23225,12 +22564,12 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23225
22564
  clearPastedTextsSnapshot,
23226
22565
  registerPastedText
23227
22566
  } = usePastedBuffers();
23228
- const promptValueRef = useRef13("");
23229
- const promptSelectionRef = useRef13(null);
23230
- const promptBoxRectRef = useRef13(null);
23231
- const promptMouseSelectionRef = useRef13(null);
23232
- const promptHistoryNavRef = useRef13({ active: false, index: -1, seed: "", lastValue: "" });
23233
- const promptHistoryDraftChangeRef = useRef13(false);
22567
+ const promptValueRef = useRef14("");
22568
+ const promptSelectionRef = useRef14(null);
22569
+ const promptBoxRectRef = useRef14(null);
22570
+ const promptMouseSelectionRef = useRef14(null);
22571
+ const promptHistoryNavRef = useRef14({ active: false, index: -1, seed: "", lastValue: "" });
22572
+ const promptHistoryDraftChangeRef = useRef14(false);
23234
22573
  const {
23235
22574
  promptHint,
23236
22575
  promptHintTone,
@@ -23240,7 +22579,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23240
22579
  showSelectionCopyHint,
23241
22580
  clearPromptHint
23242
22581
  } = usePromptHint();
23243
- const toastErrorSignature = useMemo4(() => (state.toasts || []).filter((toast) => toast?.tone === "error").map((toast) => `${toast.id || ""}:${toast.text || ""}`).join("|"), [state.toasts]);
22582
+ const toastErrorSignature = useMemo5(() => (state.toasts || []).filter((toast) => toast?.tone === "error").map((toast) => `${toast.id || ""}:${toast.text || ""}`).join("|"), [state.toasts]);
23244
22583
  const {
23245
22584
  welcomePromptHintDismissed,
23246
22585
  conditionalWelcomePromptHint,
@@ -23250,16 +22589,16 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23250
22589
  } = useWelcomePromptHint({ store, state, toastErrorSignature });
23251
22590
  const [slashIndex, setSlashIndex] = useState11(0);
23252
22591
  const [slashDismissedFor, setSlashDismissedFor] = useState11("");
23253
- const slashPaletteRef = useRef13({ open: false, count: 0 });
23254
- const workflowTabCycleRef = useRef13({ pending: false, lastAt: 0 });
23255
- const scrollFocusRef = useRef13({});
23256
- const onboardingStartedRef = useRef13(false);
23257
- const onboardingRef = useRef13({ defaultRoute: null, searchRoute: null, agentRoutes: {}, agents: [], providerModels: [] });
23258
- const providerModelsCacheRef = useRef13({ models: null, at: 0 });
23259
- const searchModelsCacheRef = useRef13({ models: null, at: 0 });
23260
- const modelPickerRequestRef = useRef13(0);
23261
- const onboardingPrefetchSeqRef = useRef13(0);
23262
- const clearModelCaches = useCallback12((scope = "all") => {
22592
+ const slashPaletteRef = useRef14({ open: false, count: 0 });
22593
+ const workflowTabCycleRef = useRef14({ pending: false, lastAt: 0 });
22594
+ const scrollFocusRef = useRef14({});
22595
+ const onboardingStartedRef = useRef14(false);
22596
+ const onboardingRef = useRef14({ defaultRoute: null, searchRoute: null, agentRoutes: {}, agents: [], providerModels: [] });
22597
+ const providerModelsCacheRef = useRef14({ models: null, at: 0 });
22598
+ const searchModelsCacheRef = useRef14({ models: null, at: 0 });
22599
+ const modelPickerRequestRef = useRef14(0);
22600
+ const onboardingPrefetchSeqRef = useRef14(0);
22601
+ const clearModelCaches = useCallback13((scope = "all") => {
23263
22602
  if (scope === "all" || scope === "provider") {
23264
22603
  providerModelsCacheRef.current = { models: null, at: 0 };
23265
22604
  onboardingRef.current.providerModels = [];
@@ -23269,7 +22608,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23269
22608
  searchModelsCacheRef.current = { models: null, at: 0 };
23270
22609
  }
23271
22610
  }, []);
23272
- useEffect13(() => {
22611
+ useEffect14(() => {
23273
22612
  let alive = true;
23274
22613
  const timer2 = setTimeout(async () => {
23275
22614
  const seq = onboardingPrefetchSeqRef.current;
@@ -23354,24 +22693,24 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23354
22693
  openUsagePanel: (...a) => openUsagePanel(...a),
23355
22694
  openContextPicker: (...a) => openContextPicker(...a)
23356
22695
  });
23357
- const dragRef = useRef13({ anchor: null, anchorScroll: 0, last: null, active: false, rect: null, region: null, anchorSpan: null });
23358
- const transcriptViewportRef = useRef13({ top: 0, bottom: 0 });
23359
- const panelTransitionRef = useRef13({ signature: "", reserve: 0, clearRows: 0, guardRows: 0, epoch: 0 });
23360
- const panelCloseInkMaskRowsRef = useRef13(0);
23361
- const projectBootInputLatchRef = useRef13(false);
23362
- const frameRowsRef = useRef13(24);
22696
+ const dragRef = useRef14({ anchor: null, anchorScroll: 0, last: null, active: false, rect: null, region: null, anchorSpan: null });
22697
+ const transcriptViewportRef = useRef14({ top: 0, bottom: 0 });
22698
+ const panelTransitionRef = useRef14({ signature: "", reserve: 0, clearRows: 0, guardRows: 0, epoch: 0 });
22699
+ const panelCloseInkMaskRowsRef = useRef14(0);
22700
+ const projectBootInputLatchRef = useRef14(false);
22701
+ const frameRowsRef = useRef14(24);
23363
22702
  const STATUSLINE_BAND_ROWS = 3;
23364
22703
  const promptContentColumns = Math.max(1, frameColumns - 4);
23365
- const syncPromptLayoutRows = useCallback12((value) => {
22704
+ const syncPromptLayoutRows = useCallback13((value) => {
23366
22705
  const text = String(value ?? "");
23367
22706
  promptLayoutValueRef.current = text;
23368
22707
  const nextRows = promptContentRows(text, promptContentColumns);
23369
22708
  setPromptLayoutRows((prev) => prev === nextRows ? prev : nextRows);
23370
22709
  }, [promptContentColumns]);
23371
- useEffect13(() => {
22710
+ useEffect14(() => {
23372
22711
  syncPromptLayoutRows(promptLayoutValueRef.current);
23373
22712
  }, [syncPromptLayoutRows]);
23374
- useEffect13(() => {
22713
+ useEffect14(() => {
23375
22714
  const kind = String(settingsPrompt?.kind || "");
23376
22715
  if (!CORE_MULTILINE_TEXT_ENTRY_KINDS.has(kind)) {
23377
22716
  setTextEntryLayoutRows(1);
@@ -23380,10 +22719,10 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23380
22719
  const cols = Math.max(1, frameColumns - 4 - stringWidth11("Sentence > "));
23381
22720
  setTextEntryLayoutRows(textEntryReservedRows(settingsPrompt?.initialValue, cols, 8));
23382
22721
  }, [settingsPrompt?.kind, settingsPrompt?.initialValue, frameColumns]);
23383
- const selectionLayoutRef = useRef13(null);
23384
- const selectionTextRef = useRef13("");
23385
- const lastClickRef = useRef13({ x: -1, y: -1, t: 0, count: 0 });
23386
- useEffect13(() => {
22722
+ const selectionLayoutRef = useRef14(null);
22723
+ const selectionTextRef = useRef14("");
22724
+ const lastClickRef = useRef14({ x: -1, y: -1, t: 0, count: 0 });
22725
+ useEffect14(() => {
23387
22726
  const timer2 = setTimeout(() => setTuiReady(true), 0);
23388
22727
  return () => clearTimeout(timer2);
23389
22728
  }, []);
@@ -23429,7 +22768,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23429
22768
  getStitchedSelectionText,
23430
22769
  showSelectionCopyHint
23431
22770
  });
23432
- useEffect13(() => () => {
22771
+ useEffect14(() => () => {
23433
22772
  stopSmoothScroll();
23434
22773
  }, [stopSmoothScroll]);
23435
22774
  useMouseInput({
@@ -23463,12 +22802,15 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23463
22802
  const count = state.items.length;
23464
22803
  const previousCount = lastItemsCountRef.current;
23465
22804
  lastItemsCountRef.current = count;
23466
- if (count < previousCount || previousCount === 0) {
22805
+ const firstId = count > 0 ? state.items[0]?.id ?? null : null;
22806
+ const previousFirstId = lastFirstItemIdRef.current;
22807
+ lastFirstItemIdRef.current = firstId;
22808
+ if (transcriptSwapReturnsToTail({ count, previousCount, firstId, previousFirstId })) {
23467
22809
  resetTranscriptScroll();
23468
22810
  return;
23469
22811
  }
23470
22812
  if (count === previousCount || dragRef.current.active) return;
23471
- }, [state.items.length, resetTranscriptScroll]);
22813
+ }, [state.items, resetTranscriptScroll]);
23472
22814
  const {
23473
22815
  requestExit,
23474
22816
  restoreQueuedToPrompt,
@@ -23490,6 +22832,15 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23490
22832
  setPromptDraftOverride,
23491
22833
  promptHistoryNavRef
23492
22834
  });
22835
+ const { hasUserMessages, openMessageSelector } = useMessageSelector({
22836
+ store,
22837
+ state,
22838
+ setPicker,
22839
+ setPromptDraftOverride,
22840
+ syncPromptLayoutRows,
22841
+ showPromptHint,
22842
+ clearPromptHint
22843
+ });
23493
22844
  const {
23494
22845
  handlePromptPaste,
23495
22846
  handlePromptHistoryNavigate,
@@ -23515,6 +22866,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23515
22866
  recentPromptHistory,
23516
22867
  resetPromptHistoryNav,
23517
22868
  restoreQueuedToPrompt,
22869
+ openMessageSelector,
23518
22870
  usagePanel,
23519
22871
  closeUsagePanel,
23520
22872
  contextPanel,
@@ -23525,7 +22877,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23525
22877
  clearPastedTextsSnapshot,
23526
22878
  registerPastedText
23527
22879
  });
23528
- const toggleExpand = useCallback12(() => {
22880
+ const toggleExpand = useCallback13(() => {
23529
22881
  setToolOutputExpanded((expanded) => !expanded);
23530
22882
  }, []);
23531
22883
  useGlobalKeyInput({
@@ -23565,7 +22917,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23565
22917
  setUsagePanel,
23566
22918
  closeUsagePanel
23567
22919
  });
23568
- useEffect13(() => {
22920
+ useEffect14(() => {
23569
22921
  if (contextPanel?.kind === "context") {
23570
22922
  openContextPicker();
23571
22923
  return;
@@ -23586,7 +22938,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23586
22938
  state.cwd,
23587
22939
  state.clientHostPid
23588
22940
  ]);
23589
- useEffect13(() => {
22941
+ useEffect14(() => {
23590
22942
  if (onboardingStartedRef.current) return void 0;
23591
22943
  if (resolveOnboardingCompleted(store, onboardingCompleted) && !forceOnboarding) return void 0;
23592
22944
  let canceled = false;
@@ -23647,7 +22999,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23647
22999
  hookPrompt: !!hookPrompt,
23648
23000
  settingsPrompt: !!settingsPrompt
23649
23001
  };
23650
- useEffect13(() => {
23002
+ useEffect14(() => {
23651
23003
  setSlashIndex((index) => Math.min(index, Math.max(0, slashCommands.length - 1)));
23652
23004
  }, [slashCommands.length, activeSlashQuery]);
23653
23005
  const {
@@ -23750,7 +23102,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23750
23102
  transcriptContentHeight,
23751
23103
  transcriptBottomSlackRows
23752
23104
  } = layout;
23753
- useEffect13(() => {
23105
+ useEffect14(() => {
23754
23106
  const transition = panelTransitionRef.current;
23755
23107
  const pendingInkMask = panelCloseInkMaskRowsRef.current;
23756
23108
  const hadTransitionClearance = panelTransitionClearRows > 0 || panelTransitionGuardRows > 0;
@@ -23767,7 +23119,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23767
23119
  const timer2 = setTimeout(() => setPanelTransitionEpoch((epoch) => epoch + 1), 0);
23768
23120
  return () => clearTimeout(timer2);
23769
23121
  }, [panelLayoutSignature, bottomClusterRows, panelTransitionClearRows, panelTransitionGuardRows]);
23770
- useEffect13(() => {
23122
+ useEffect14(() => {
23771
23123
  panelTransitionRef.current.tailId = latestTranscriptItem?.id ?? null;
23772
23124
  });
23773
23125
  const {
@@ -23811,7 +23163,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23811
23163
  measuredRowsVersion,
23812
23164
  setMeasuredRowsVersion
23813
23165
  });
23814
- const cycleWorkflowFromPrompt = useCallback12(() => {
23166
+ const cycleWorkflowFromPrompt = useCallback13(() => {
23815
23167
  if (slashPaletteOpen || toolApproval || picker || settingsPrompt || providerPrompt || channelPrompt || hookPrompt || contextPanel || usagePanel) return true;
23816
23168
  const repeatGuardMs = 300;
23817
23169
  const cycleGuard = workflowTabCycleRef.current;
@@ -23871,6 +23223,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23871
23223
  handlePromptHistoryNavigate,
23872
23224
  handlePromptInterrupt,
23873
23225
  handlePromptPaste,
23226
+ hasUserMessages,
23874
23227
  hookPrompt,
23875
23228
  initialStatusLine,
23876
23229
  onPromptDraftChange,
@@ -23942,44 +23295,19 @@ var SYNTHETIC_SESSION_TEXT_PATTERNS = Object.freeze([
23942
23295
  init_plugin_paths();
23943
23296
  init_config();
23944
23297
 
23945
- // src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs
23946
- var OPENAI_COMPAT_PRESETS = {
23947
- deepseek: {
23948
- baseURL: "https://api.deepseek.com",
23949
- defaultModel: "deepseek-v4-pro"
23950
- },
23951
- xai: {
23952
- baseURL: "https://api.x.ai/v1",
23953
- defaultModel: "grok-4.5"
23954
- },
23955
- // OpenCode Go - low-cost coding-model subscription gateway.
23956
- "opencode-go": {
23957
- baseURL: "https://opencode.ai/zen/go/v1",
23958
- defaultModel: "glm-5.2"
23959
- },
23960
- ollama: {
23961
- baseURL: "http://localhost:11434/v1",
23962
- defaultModel: "llama3.3:latest"
23963
- },
23964
- lmstudio: {
23965
- baseURL: "http://localhost:1234/v1",
23966
- defaultModel: "default"
23967
- }
23968
- };
23298
+ // src/runtime/shared/agent-route-config.mjs
23299
+ var LEGACY_AGENT_ROUTE_SLOTS = Object.freeze({
23300
+ explore: Object.freeze({ workflow: "explorer", maintenance: "explore" }),
23301
+ maintainer: Object.freeze({ alias: "maintenance", workflow: "memory", maintenance: "memory" }),
23302
+ worker: Object.freeze({ workflow: "agent" })
23303
+ });
23969
23304
 
23970
23305
  // src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs
23971
23306
  init_plugin_paths();
23972
- import { join as join8 } from "node:path";
23973
- var ANTHROPIC_DEFAULT_CREDENTIALS_PATH = join8(resolvePluginData(), "anthropic-oauth-credentials.json");
23307
+ import { join as join7 } from "node:path";
23308
+ var ANTHROPIC_DEFAULT_CREDENTIALS_PATH = join7(resolvePluginData(), "anthropic-oauth-credentials.json");
23974
23309
 
23975
23310
  // src/runtime/agent/orchestrator/config.mjs
23976
- var CONFIG_PROVIDER_IDS = /* @__PURE__ */ new Set([
23977
- ...Object.keys(AGENT_PROVIDER_ENV),
23978
- ...Object.keys(OPENAI_COMPAT_PRESETS),
23979
- "openai-oauth",
23980
- "anthropic-oauth",
23981
- "grok-oauth"
23982
- ]);
23983
23311
  var MAINTENANCE_SLOTS = Object.freeze(["explore", "memory"]);
23984
23312
  var PROFILE_LANGUAGES = Object.freeze([
23985
23313
  { id: "system", label: "System (locale)", prompt: null },
@@ -24074,8 +23402,8 @@ var TRANSCRIPT_PROCESS_NONCE = randomUUID();
24074
23402
 
24075
23403
  // src/tui/engine.mjs
24076
23404
  async function createEngineSession(options = {}) {
24077
- const { createRemoteEngineSession: createRemoteEngineSession2 } = await Promise.resolve().then(() => (init_engine_daemon_client(), engine_daemon_client_exports));
24078
- return await createRemoteEngineSession2(options);
23405
+ const { createRemoteEngineSession } = await import("../../standalone/engine-daemon-client.mjs");
23406
+ return await createRemoteEngineSession(options);
24079
23407
  }
24080
23408
 
24081
23409
  // src/tui/index.jsx
@@ -24281,8 +23609,8 @@ function waitWithTimeout(promise, ms) {
24281
23609
  let timer2 = null;
24282
23610
  return Promise.race([
24283
23611
  Promise.resolve(promise).then(() => true),
24284
- new Promise((resolve5) => {
24285
- timer2 = setTimeout(() => resolve5(false), ms);
23612
+ new Promise((resolve4) => {
23613
+ timer2 = setTimeout(() => resolve4(false), ms);
24286
23614
  })
24287
23615
  ]).finally(() => {
24288
23616
  if (timer2) clearTimeout(timer2);
@@ -24301,7 +23629,7 @@ function scheduleHardExit(code = 0) {
24301
23629
  timer2.unref?.();
24302
23630
  }
24303
23631
  function resolveTuiStderrLogPath() {
24304
- return process.env.MIXDOG_TUI_STDERR_LOG || join9(process.env.MIXDOG_RUNTIME_ROOT || join9(tmpdir2(), "mixdog"), "mixdog-tui.stderr.log");
23632
+ return process.env.MIXDOG_TUI_STDERR_LOG || join8(process.env.MIXDOG_RUNTIME_ROOT || join8(tmpdir2(), "mixdog"), "mixdog-tui.stderr.log");
24305
23633
  }
24306
23634
  function ansiFg(rgb) {
24307
23635
  const m = /rgb\((\d+),\s*(\d+),\s*(\d+)\)/.exec(String(rgb || ""));
@@ -24383,7 +23711,7 @@ function installTuiStderrGuard() {
24383
23711
  const originalWrite = process.stderr.write.bind(process.stderr);
24384
23712
  const logPath = resolveTuiStderrLogPath();
24385
23713
  try {
24386
- mkdirSync7(dirname8(logPath), { recursive: true });
23714
+ mkdirSync6(dirname7(logPath), { recursive: true });
24387
23715
  } catch {
24388
23716
  }
24389
23717
  (0, import_mixdog_debug.rotateBoundedLog)(logPath, import_mixdog_debug.PLUGIN_LOG_MAX_BYTES, import_mixdog_debug.PLUGIN_LOG_KEEP_BYTES);
@@ -24491,7 +23819,7 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
24491
23819
  if (outcome.error) throw outcome.error;
24492
23820
  store = outcome.store;
24493
23821
  try {
24494
- touchProjectSelected(store.getState?.()?.cwd || process.cwd());
23822
+ await store.touchProjectSelected(store.getState?.()?.cwd || process.cwd());
24495
23823
  } catch {
24496
23824
  }
24497
23825
  if (store.isRemoteEngine === true) {