mixdog 0.9.95 → 0.9.97

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 (282) 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/README.md +0 -17
  6. package/package.json +16 -12
  7. package/scripts/.tmp-coverage-report.mjs +96 -0
  8. package/scripts/build-tui.mjs +17 -1
  9. package/scripts/{channel-daemon-stub.mjs → daemon-stub.mjs} +18 -12
  10. package/scripts/fixtures/patch-replay-corpus.json +98 -0
  11. package/src/defaults/mixdog-config.template.json +5 -8
  12. package/src/defaults/skills/setup/SKILL.md +1 -1
  13. package/src/lib/keychain-cjs.cjs +1 -1
  14. package/src/lib/mixdog-debug.cjs +2 -3
  15. package/src/rules/agent/30-explorer.md +7 -4
  16. package/src/rules/lead/01-general.md +1 -2
  17. package/src/rules/lead/lead-tool.md +0 -3
  18. package/src/rules/shared/01-tool.md +21 -29
  19. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +18 -2
  20. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +31 -1
  21. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +6 -17
  22. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +12 -1
  23. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +4 -13
  24. package/src/runtime/agent/orchestrator/config.mjs +246 -67
  25. package/src/runtime/agent/orchestrator/mcp/client.mjs +104 -41
  26. package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +23 -0
  27. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +113 -14
  28. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +5 -1
  29. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +32 -11
  30. package/src/runtime/agent/orchestrator/providers/gemini.mjs +89 -2
  31. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +12 -1
  32. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +19 -1
  33. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +144 -39
  34. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +22 -8
  35. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +15 -1
  36. package/src/runtime/agent/orchestrator/providers/openai-transport-policy.mjs +1 -1
  37. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +3 -4
  38. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +3 -6
  39. package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -2
  40. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +67 -7
  41. package/src/runtime/agent/orchestrator/providers/stream-json-pool.mjs +291 -0
  42. package/src/runtime/agent/orchestrator/providers/stream-json-worker.mjs +21 -0
  43. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +36 -10
  44. package/src/runtime/agent/orchestrator/session/compact.mjs +1 -1
  45. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +22 -33
  46. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +13 -0
  47. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +2 -2
  48. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +0 -24
  49. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +21 -29
  50. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +7 -10
  51. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +45 -25
  52. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +2 -3
  53. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +142 -84
  54. package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +12 -6
  55. package/src/runtime/agent/orchestrator/session/manager/runtime-loaders.mjs +4 -0
  56. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +31 -1
  57. package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +38 -2
  58. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +29 -9
  59. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -0
  60. package/src/runtime/agent/orchestrator/session/result-classification.mjs +65 -0
  61. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +86 -8
  62. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +10 -3
  63. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +4 -0
  64. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +71 -42
  65. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +4 -33
  66. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -5
  67. package/src/runtime/agent/orchestrator/stall-policy.mjs +1 -1
  68. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +2 -2
  69. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +35 -1
  70. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +116 -26
  71. package/src/runtime/agent/orchestrator/tools/builtin/binary-file.mjs +92 -19
  72. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +8 -9
  73. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +2 -2
  74. package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +2 -2
  75. package/src/runtime/agent/orchestrator/tools/builtin/fs-reachability.mjs +2 -2
  76. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +6 -15
  77. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +12 -26
  78. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +5 -5
  79. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +83 -33
  80. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +42 -45
  81. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +27 -9
  82. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +7 -0
  83. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +17 -39
  84. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +73 -0
  85. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +1 -1
  86. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-spawn.mjs +0 -2
  87. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +3 -8
  88. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +30 -0
  89. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +5 -5
  90. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +142 -26
  91. package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +12 -1
  92. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +47 -19
  93. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +3 -2
  94. package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +1 -2
  95. package/src/runtime/agent/orchestrator/tools/lib/pwsh-standby-pool.mjs +83 -13
  96. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +39 -1
  97. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +1 -1
  98. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +94 -20
  99. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +16 -0
  100. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +108 -12
  101. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +12 -11
  102. package/src/runtime/agent/orchestrator/tools/patch.mjs +1 -1
  103. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +63 -9
  104. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +2 -2
  105. package/src/runtime/channels/lib/config.mjs +34 -34
  106. package/src/runtime/channels/lib/inbound-handler.mjs +8 -8
  107. package/src/runtime/channels/lib/interaction-handlers.mjs +6 -6
  108. package/src/runtime/channels/lib/memory-client.mjs +31 -157
  109. package/src/runtime/channels/lib/output-forwarder.mjs +16 -16
  110. package/src/runtime/channels/lib/owned-runtime.mjs +60 -60
  111. package/src/runtime/channels/lib/owner-heartbeat.mjs +1 -1
  112. package/src/runtime/channels/lib/{backend-dispatch.mjs → provider-dispatch.mjs} +6 -6
  113. package/src/runtime/channels/lib/runtime-paths.mjs +4 -30
  114. package/src/runtime/channels/lib/scheduler.mjs +2 -2
  115. package/src/runtime/channels/lib/status-snapshot.mjs +3 -3
  116. package/src/runtime/channels/lib/tool-dispatch.mjs +4 -4
  117. package/src/runtime/channels/lib/worker-bootstrap.mjs +1 -1
  118. package/src/runtime/channels/lib/worker-ipc.mjs +7 -7
  119. package/src/runtime/channels/lib/worker-main.mjs +38 -38
  120. package/src/runtime/channels/{backends → providers}/discord-access.mjs +1 -1
  121. package/src/runtime/channels/{backends → providers}/discord-gateway.mjs +1 -1
  122. package/src/runtime/channels/{backends → providers}/discord.mjs +3 -3
  123. package/src/runtime/channels/{backends → providers}/telegram.mjs +12 -12
  124. package/src/runtime/channels/tool-defs.mjs +1 -1
  125. package/src/runtime/memory/index.mjs +76 -248
  126. package/src/runtime/memory/lib/agent-ipc.mjs +146 -81
  127. package/src/runtime/memory/lib/compact-vector-cache.mjs +88 -0
  128. package/src/runtime/memory/lib/core-memory-store.mjs +18 -1
  129. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +13 -34
  130. package/src/runtime/memory/lib/embedding-provider.mjs +47 -26
  131. package/src/runtime/memory/lib/embedding-worker.mjs +30 -34
  132. package/src/runtime/memory/lib/http-router.mjs +9 -6
  133. package/src/runtime/memory/lib/ko-morph.mjs +3 -20
  134. package/src/runtime/memory/lib/memory-action-handlers.mjs +2 -1
  135. package/src/runtime/memory/lib/memory-config-flags.mjs +1 -1
  136. package/src/runtime/memory/lib/memory-cycle1.mjs +1 -0
  137. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +4 -2
  138. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +1 -0
  139. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +4 -1
  140. package/src/runtime/memory/lib/memory-cycle3.mjs +3 -1
  141. package/src/runtime/memory/lib/memory-daemon-lifecycle.mjs +5 -0
  142. package/src/runtime/memory/lib/memory-log.mjs +1 -1
  143. package/src/runtime/memory/lib/memory-process-lock.mjs +5 -60
  144. package/src/runtime/memory/lib/memory.mjs +1 -1
  145. package/src/runtime/memory/lib/pg/process.mjs +32 -9
  146. package/src/runtime/memory/lib/session-ingest.mjs +1 -1
  147. package/src/runtime/memory/tool-defs.mjs +1 -5
  148. package/src/runtime/search/index.mjs +2 -2
  149. package/src/runtime/search/tool-defs.mjs +2 -2
  150. package/src/runtime/shared/abort-race.mjs +70 -0
  151. package/src/runtime/shared/agent-route-config.mjs +119 -0
  152. package/src/runtime/shared/atomic-file.mjs +80 -4
  153. package/src/runtime/shared/channel-notification-routing.mjs +2 -2
  154. package/src/runtime/shared/child-guardian.mjs +204 -108
  155. package/src/runtime/shared/child-spawn-gate.mjs +139 -72
  156. package/src/runtime/shared/config.mjs +120 -47
  157. package/src/runtime/shared/llm/index.mjs +1 -1
  158. package/src/runtime/shared/owner-fair-gate.mjs +134 -0
  159. package/src/runtime/shared/resource-admission.mjs +150 -100
  160. package/src/runtime/shared/stream-progress.mjs +6 -0
  161. package/src/runtime/shared/tool-execution-owner.mjs +12 -0
  162. package/src/runtime/shared/tool-result-summary.mjs +1 -1
  163. package/src/runtime/shared/tool-surface.mjs +91 -27
  164. package/src/runtime/shared/tool-workload-gates.mjs +29 -0
  165. package/src/runtime/shared/turn-snapshot.mjs +47 -6
  166. package/src/runtime/shared/turn-worktree-snapshot.mjs +14 -2
  167. package/src/session-runtime/channel-config-api.mjs +5 -5
  168. package/src/session-runtime/config-helpers.mjs +39 -37
  169. package/src/session-runtime/config-lifecycle.mjs +27 -27
  170. package/src/session-runtime/lifecycle-api.mjs +22 -3
  171. package/src/session-runtime/memory-runtime-prewarm.mjs +15 -0
  172. package/src/session-runtime/model-route-api.mjs +7 -5
  173. package/src/session-runtime/prewarm.mjs +1 -1
  174. package/src/session-runtime/provider-auth-api.mjs +0 -7
  175. package/src/session-runtime/provider-models.mjs +1 -1
  176. package/src/session-runtime/remote-control.mjs +6 -7
  177. package/src/session-runtime/runtime-core.mjs +63 -39
  178. package/src/session-runtime/runtime-tunables.mjs +4 -4
  179. package/src/session-runtime/self-update.mjs +1 -1
  180. package/src/session-runtime/session-lifecycle.mjs +45 -17
  181. package/src/session-runtime/session-title.mjs +1 -1
  182. package/src/session-runtime/session-turn-api.mjs +96 -24
  183. package/src/session-runtime/settings-api.mjs +12 -24
  184. package/src/session-runtime/tool-defs.mjs +3 -3
  185. package/src/session-runtime/workflow-agents-api.mjs +55 -81
  186. package/src/session-runtime/workflow.mjs +14 -24
  187. package/src/standalone/agent-dispatch-broker.mjs +198 -0
  188. package/src/standalone/agent-tool/helpers.mjs +2 -2
  189. package/src/standalone/agent-tool/spawn-flow.mjs +2 -0
  190. package/src/standalone/agent-tool/spawn-preset.mjs +11 -21
  191. package/src/standalone/agent-tool/tool-def.mjs +0 -14
  192. package/src/standalone/channel-admin.mjs +53 -64
  193. package/src/standalone/{channel-daemon-client.mjs → channel-client.mjs} +33 -19
  194. package/src/standalone/{channel-daemon-transport.mjs → channel-transport.mjs} +127 -46
  195. package/src/standalone/channel-worker.mjs +124 -647
  196. package/src/standalone/{backend-daemon.mjs → daemon.mjs} +270 -154
  197. package/src/standalone/explore-tool.mjs +187 -70
  198. package/src/standalone/fair-call-scheduler.mjs +264 -0
  199. package/src/standalone/memory-runtime-proxy.mjs +58 -1
  200. package/src/standalone/session-client.mjs +1134 -0
  201. package/src/standalone/session-protocol.mjs +192 -0
  202. package/src/standalone/session-runtime-pool.mjs +393 -0
  203. package/src/standalone/session-runtime-record.mjs +20 -0
  204. package/src/standalone/session-runtime-worker.mjs +203 -0
  205. package/src/standalone/{engine-daemon-service.mjs → session-service.mjs} +481 -224
  206. package/src/standalone/{engine-daemon-transport.mjs → session-transport.mjs} +390 -121
  207. package/src/standalone/session-wire.mjs +55 -0
  208. package/src/tui/App.jsx +34 -21
  209. package/src/tui/app/app-format.mjs +1 -1
  210. package/src/tui/app/app-view.jsx +5 -2
  211. package/src/tui/app/channel-pickers.mjs +41 -41
  212. package/src/tui/app/core-memory-picker.mjs +19 -19
  213. package/src/tui/app/doctor.mjs +5 -11
  214. package/src/tui/app/input-parsers.mjs +2 -2
  215. package/src/tui/app/message-selector.mjs +103 -0
  216. package/src/tui/app/onboarding-steps.mjs +3 -4
  217. package/src/tui/app/project-picker.mjs +80 -56
  218. package/src/tui/app/prompt-submit.mjs +48 -43
  219. package/src/tui/app/resume-picker.mjs +2 -2
  220. package/src/tui/app/route-pickers.mjs +3 -1
  221. package/src/tui/app/settings-picker.mjs +31 -33
  222. package/src/tui/app/transcript-window.mjs +38 -0
  223. package/src/tui/app/use-prompt-draft-flow.mjs +5 -7
  224. package/src/tui/app/use-prompt-handlers.mjs +86 -21
  225. package/src/tui/app/use-prompt-hint.mjs +3 -2
  226. package/src/tui/app/use-prompt-queue-history.mjs +10 -3
  227. package/src/tui/app/use-transcript-scroll.mjs +10 -3
  228. package/src/tui/app/use-transcript-window.mjs +50 -17
  229. package/src/tui/components/PromptInput.jsx +71 -38
  230. package/src/tui/components/Spinner.jsx +1 -1
  231. package/src/tui/components/ToolExecution.jsx +1 -1
  232. package/src/tui/components/prompt-input/edit-helpers.mjs +9 -11
  233. package/src/tui/components/prompt-input/escape-policy.mjs +42 -0
  234. package/src/tui/components/prompt-input/immediate-render.mjs +0 -10
  235. package/src/tui/components/prompt-input/interrupt-policy.mjs +10 -0
  236. package/src/tui/components/prompt-input/restore-policy.mjs +10 -0
  237. package/src/tui/components/tool-execution/surface-detail.mjs +1 -1
  238. package/src/tui/dist/index.mjs +1031 -1676
  239. package/src/tui/hooks/useSession.mjs +16 -0
  240. package/src/tui/index.jsx +12 -13
  241. package/src/tui/{engine → session}/agent-envelope.mjs +3 -3
  242. package/src/tui/{engine → session}/agent-job-feed.mjs +4 -4
  243. package/src/tui/{engine → session}/boot-profile.mjs +2 -2
  244. package/src/tui/{engine → session}/context-state.mjs +3 -3
  245. package/src/tui/{engine → session}/labels.mjs +2 -2
  246. package/src/tui/{engine → session}/live-share.mjs +38 -4
  247. package/src/tui/{engine → session}/notice-text.mjs +2 -2
  248. package/src/tui/{engine → session}/notification-plan.mjs +3 -3
  249. package/src/tui/session/oauth-flows.mjs +140 -0
  250. package/src/tui/{engine → session}/prompt-history.mjs +3 -3
  251. package/src/tui/{engine → session}/queue-helpers.mjs +2 -2
  252. package/src/tui/{engine → session}/render-timing.mjs +2 -2
  253. package/src/tui/{engine → session}/session-api-ext.mjs +30 -20
  254. package/src/tui/{engine → session}/session-api.mjs +110 -93
  255. package/src/tui/{engine → session}/session-flow.mjs +8 -2
  256. package/src/tui/{engine → session}/session-stats.mjs +3 -3
  257. package/src/tui/{engine → session}/tool-approval.mjs +3 -3
  258. package/src/tui/{engine → session}/tool-call-fields.mjs +2 -2
  259. package/src/tui/{engine → session}/tool-card-results.mjs +8 -6
  260. package/src/tui/{engine → session}/tool-result-status.mjs +5 -5
  261. package/src/tui/{engine → session}/tool-result-text.mjs +13 -3
  262. package/src/tui/{engine → session}/transcript-spill.mjs +34 -34
  263. package/src/tui/{engine → session}/turn.mjs +38 -176
  264. package/src/tui/{engine-local-session.mjs → session-local.mjs} +103 -103
  265. package/src/tui/session.mjs +22 -0
  266. package/src/tui/spinner-meta.mjs +4 -4
  267. package/src/tui/spinner-verbs.mjs +51 -3
  268. package/src/ui/statusline-segments.mjs +1 -1
  269. package/src/ui/statusline.mjs +1 -1
  270. package/vendor/ink/build/components/App.js +10 -1
  271. package/src/session-runtime/memory-daemon-probe.mjs +0 -61
  272. package/src/standalone/engine-daemon-client.mjs +0 -894
  273. package/src/standalone/engine-daemon-local-bridge.mjs +0 -20
  274. package/src/standalone/engine-daemon-protocol.mjs +0 -33
  275. package/src/tui/engine/turn-watchdog.mjs +0 -92
  276. package/src/tui/engine.mjs +0 -22
  277. package/src/tui/hooks/useEngine.mjs +0 -16
  278. /package/src/runtime/agent/orchestrator/session/compact/{engine.mjs → runner.mjs} +0 -0
  279. /package/src/runtime/channels/{backends → providers}/discord-attachments.mjs +0 -0
  280. /package/src/tui/{engine → session}/agent-response-tail.mjs +0 -0
  281. /package/src/tui/{engine → session}/frame-batched-store.mjs +0 -0
  282. /package/src/tui/{engine → session}/tui-steering-persist.mjs +0 -0
@@ -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,100 @@ 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
+ next.provider = next.provider === "telegram" || next.backend === "telegram" ? "telegram" : "discord";
1094
+ delete next.backend;
1095
+ delete channel.channelId;
1096
+ next.channel = channel;
1097
+ return next;
1098
+ }
1099
+ function canonicalizeUnifiedConfig(value = {}) {
1100
+ const next = isPlainObject2(value) ? { ...value } : {};
1101
+ const hadAgent = isPlainObject2(next.agent);
1102
+ const agent = hadAgent ? { ...next.agent } : {};
1103
+ if (!String(next.outputStyle || "").trim() && String(agent.outputStyle || "").trim()) {
1104
+ next.outputStyle = agent.outputStyle;
1105
+ }
1106
+ delete agent.outputStyle;
1107
+ for (const key of ["autoClear", "compaction", "shell"]) {
1108
+ if (!isPlainObject2(agent[key]) && isPlainObject2(next[key])) agent[key] = next[key];
1109
+ delete next[key];
1110
+ }
1111
+ if (isPlainObject2(next.mcpServers)) {
1112
+ agent.mcpServers = {
1113
+ ...next.mcpServers,
1114
+ ...isPlainObject2(agent.mcpServers) ? agent.mcpServers : {}
1115
+ };
1116
+ delete next.mcpServers;
1117
+ }
1118
+ if (!isPlainObject2(agent.searchRoute) && isPlainObject2(agent.search)) {
1119
+ agent.searchRoute = agent.search;
1120
+ }
1121
+ delete agent.search;
1122
+ delete agent.capabilities;
1123
+ const modules = isPlainObject2(agent.modules) ? { ...agent.modules } : {};
1124
+ if (hasOwn(modules, "memory")) {
1125
+ const legacyMemory = modules.memory;
1126
+ const explicitRecap = isPlainObject2(agent.recap) && hasOwn(agent.recap, "enabled");
1127
+ if (!explicitRecap) {
1128
+ const enabled = isPlainObject2(legacyMemory) ? legacyMemory.enabled !== false : legacyMemory !== false;
1129
+ agent.recap = { ...isPlainObject2(agent.recap) ? agent.recap : {}, enabled };
1130
+ }
1131
+ delete modules.memory;
1132
+ agent.modules = modules;
1133
+ }
1134
+ if (isPlainObject2(next.memory)) {
1135
+ const memory = { ...next.memory };
1136
+ const user = isPlainObject2(memory.user) ? { ...memory.user } : {};
1137
+ const legacyTitle = String(user.title || "").trim();
1138
+ if (legacyTitle && !String(agent.profile?.title || "").trim()) {
1139
+ agent.profile = { ...isPlainObject2(agent.profile) ? agent.profile : {}, title: legacyTitle };
1140
+ }
1141
+ delete user.title;
1142
+ if (Object.keys(user).length) memory.user = user;
1143
+ else delete memory.user;
1144
+ if (hasOwn(memory, "enabled")) {
1145
+ const explicitRecap = isPlainObject2(agent.recap) && hasOwn(agent.recap, "enabled");
1146
+ if (!explicitRecap) {
1147
+ agent.recap = { ...isPlainObject2(agent.recap) ? agent.recap : {}, enabled: memory.enabled !== false };
1148
+ }
1149
+ delete memory.enabled;
1150
+ }
1151
+ if (Object.keys(memory).length) next.memory = memory;
1152
+ else delete next.memory;
1153
+ }
1154
+ if (isPlainObject2(next.channels)) {
1155
+ next.channels = canonicalizeStoredChannelsConfig(next.channels);
1156
+ }
1157
+ delete next.search;
1158
+ delete next.capabilities;
1159
+ if (hadAgent || Object.keys(agent).length) next.agent = agent;
1160
+ return next;
1161
+ }
1034
1162
  function stripGeneratedMarker(data) {
1035
1163
  if (!isPlainObject2(data) || !Object.prototype.hasOwnProperty.call(data, GENERATED_KEY)) return data;
1036
1164
  const { [GENERATED_KEY]: _generated, ...rest } = data;
1037
1165
  return rest;
1038
1166
  }
1039
- function readJsonFile(path3) {
1167
+ function readJsonFile(path2) {
1040
1168
  let raw;
1041
- if (path3 === CONFIG_PATH) {
1169
+ if (path2 === CONFIG_PATH) {
1042
1170
  raw = readConfigRawCached();
1043
1171
  } else {
1044
1172
  try {
1045
- raw = readFileSync5(path3, "utf8");
1173
+ raw = readFileSync4(path2, "utf8");
1046
1174
  } catch (err) {
1047
1175
  if (err.code === "ENOENT") return null;
1048
- process.stderr.write(`[config] readJsonFile: unexpected read error for ${path3}: ${err.message}
1176
+ process.stderr.write(`[config] readJsonFile: unexpected read error for ${path2}: ${err.message}
1049
1177
  `);
1050
1178
  throw err;
1051
1179
  }
@@ -1054,11 +1182,11 @@ function readJsonFile(path3) {
1054
1182
  try {
1055
1183
  return JSON.parse(raw);
1056
1184
  } catch (err) {
1057
- if (path3 === CONFIG_PATH) {
1185
+ if (path2 === CONFIG_PATH) {
1058
1186
  invalidateConfigReadCache();
1059
- const corrupt = `${path3}.corrupt-${Date.now()}`;
1187
+ const corrupt = `${path2}.corrupt-${Date.now()}`;
1060
1188
  try {
1061
- renameWithRetrySync(path3, corrupt);
1189
+ renameWithRetrySync(path2, corrupt);
1062
1190
  } catch {
1063
1191
  }
1064
1192
  process.stderr.write(`[config] mixdog-config.json is malformed (${err.message}). Renamed to ${corrupt}. Restore it or delete to start fresh.
@@ -1069,12 +1197,12 @@ function readJsonFile(path3) {
1069
1197
  }
1070
1198
  function readAll() {
1071
1199
  const parsed = readJsonFile(CONFIG_PATH);
1072
- if (parsed != null) return parsed;
1200
+ if (parsed != null) return canonicalizeUnifiedConfig(parsed);
1073
1201
  if (hasUserDataInitMarker(DATA_DIR)) {
1074
1202
  const restored = loadLatestMixdogConfigFromBackup(DATA_DIR);
1075
1203
  if (restored && isPlainObject2(restored)) {
1076
1204
  process.stderr.write("[config] read: restored mixdog-config.json from latest user-data backup (missing after init)\n");
1077
- return restored;
1205
+ return canonicalizeUnifiedConfig(restored);
1078
1206
  }
1079
1207
  }
1080
1208
  return {};
@@ -1082,7 +1210,7 @@ function readAll() {
1082
1210
  function quarantineMalformedConfig(parseErr) {
1083
1211
  const corrupt = `${CONFIG_PATH}.corrupt-${Date.now()}`;
1084
1212
  try {
1085
- if (existsSync6(CONFIG_PATH)) renameWithRetrySync(CONFIG_PATH, corrupt);
1213
+ if (existsSync5(CONFIG_PATH)) renameWithRetrySync(CONFIG_PATH, corrupt);
1086
1214
  } catch {
1087
1215
  }
1088
1216
  process.stderr.write(
@@ -1104,7 +1232,7 @@ function restoreAllForRmWOrThrow(reason) {
1104
1232
  function readAllForRmW() {
1105
1233
  let raw;
1106
1234
  try {
1107
- raw = readFileSync5(CONFIG_PATH, "utf8");
1235
+ raw = readFileSync4(CONFIG_PATH, "utf8");
1108
1236
  } catch (err) {
1109
1237
  if (err.code === "ENOENT") {
1110
1238
  if (hasUserDataInitMarker(DATA_DIR)) {
@@ -1128,16 +1256,16 @@ function readAllForRmW() {
1128
1256
  function readSection(section) {
1129
1257
  return stripGeneratedMarker(readAll()[section] ?? null) ?? {};
1130
1258
  }
1131
- async function writeJsonFileAsync(path3, data) {
1132
- mkdirSync5(dirname6(path3), { recursive: true, mode: 448 });
1133
- if (path3 === CONFIG_PATH) {
1259
+ async function writeJsonFileAsync(path2, data) {
1260
+ mkdirSync4(dirname5(path2), { recursive: true, mode: 448 });
1261
+ if (path2 === CONFIG_PATH) {
1134
1262
  try {
1135
1263
  await backupUserDataAsync(DATA_DIR, "pre-config-write");
1136
1264
  } catch {
1137
1265
  }
1138
1266
  }
1139
- await writeJsonAtomicAsync(path3, data, { lock: false, fsyncDir: true, mode: 384, secret: true });
1140
- if (path3 === CONFIG_PATH) {
1267
+ await writeJsonAtomicAsync(path2, data, { lock: false, fsyncDir: true, mode: 384, secret: true });
1268
+ if (path2 === CONFIG_PATH) {
1141
1269
  invalidateConfigReadCache();
1142
1270
  try {
1143
1271
  markUserDataInitialized(DATA_DIR);
@@ -1150,10 +1278,10 @@ async function writeJsonFileAsync(path3, data) {
1150
1278
  }
1151
1279
  }
1152
1280
  async function writeAllAsync(data) {
1153
- await writeJsonFileAsync(CONFIG_PATH, data);
1281
+ await writeJsonFileAsync(CONFIG_PATH, canonicalizeUnifiedConfig(data));
1154
1282
  }
1155
1283
  function withConfigLockAsync(fn) {
1156
- return withFileLock(`${CONFIG_PATH}.lock`, fn, { secret: true });
1284
+ return withFileLock(`${CONFIG_PATH}.lock`, fn);
1157
1285
  }
1158
1286
  function trackConfigWrite(work) {
1159
1287
  let promise;
@@ -1177,7 +1305,7 @@ function updateSectionAsync(section, updater) {
1177
1305
  await writeAllAsync(all);
1178
1306
  }));
1179
1307
  }
1180
- var _require, _getSecret, _setSecret, _deleteSecret, _hasSecret, _invalidateSecretCache, DATA_DIR, CONFIG_PATH, GENERATED_KEY, _configReadCache, CONFIG_READ_TTL_MS, _pendingConfigWrites, CAPABILITY_DEFAULTS, SECRET_ACCOUNTS;
1308
+ var _require, _getSecret, _setSecret, _deleteSecret, _hasSecret, _invalidateSecretCache, DATA_DIR, CONFIG_PATH, GENERATED_KEY, _configReadCache, CONFIG_READ_TTL_MS, _pendingConfigWrites, SECRET_ACCOUNTS;
1181
1309
  var init_config = __esm({
1182
1310
  "src/runtime/shared/config.mjs"() {
1183
1311
  init_plugin_paths();
@@ -1193,965 +1321,21 @@ var init_config = __esm({
1193
1321
  invalidateSecretCache: _invalidateSecretCache
1194
1322
  } = _require("../../lib/keychain-cjs.cjs"));
1195
1323
  DATA_DIR = resolvePluginData();
1196
- CONFIG_PATH = join6(DATA_DIR, "mixdog-config.json");
1324
+ CONFIG_PATH = join5(DATA_DIR, "mixdog-config.json");
1197
1325
  GENERATED_KEY = "_generated";
1198
1326
  _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
- ]);
1327
+ CONFIG_READ_TTL_MS = (() => {
1328
+ const n = Number(process.env.MIXDOG_CONFIG_READ_TTL_MS);
1329
+ return Number.isFinite(n) && n >= 0 ? n : 0;
1330
+ })();
1331
+ _pendingConfigWrites = /* @__PURE__ */ new Set();
1332
+ SECRET_ACCOUNTS = Object.freeze({
1333
+ discordToken: "discord.token",
1334
+ telegramToken: "telegram.token",
1335
+ agentApiKey: (provider) => `agent.${provider}.apiKey`,
1336
+ openaiUsageSessionKey: "agent.openai.usageSessionKey",
1337
+ opencodeGoAuthCookie: "agent.opencode-go.authCookie"
1338
+ });
2155
1339
  }
2156
1340
  });
2157
1341
 
@@ -2160,7 +1344,7 @@ var require_mixdog_debug = __commonJS({
2160
1344
  "src/lib/mixdog-debug.cjs"(exports, module) {
2161
1345
  "use strict";
2162
1346
  var fs = __require("fs");
2163
- var path3 = __require("path");
1347
+ var path2 = __require("path");
2164
1348
  function isTruthyEnv(value) {
2165
1349
  if (value == null || value === "") return false;
2166
1350
  const s = String(value).trim().toLowerCase();
@@ -2173,12 +1357,12 @@ var require_mixdog_debug = __commonJS({
2173
1357
  function _detectFromSourceCheckout() {
2174
1358
  if (_cachedFromSource !== null) return _cachedFromSource;
2175
1359
  try {
2176
- const moduleDir = module && module.filename ? path3.dirname(module.filename) : process.cwd();
1360
+ const moduleDir = module && module.filename ? path2.dirname(module.filename) : process.cwd();
2177
1361
  const roots = [
2178
- path3.resolve(moduleDir, "..", ".."),
1362
+ path2.resolve(moduleDir, "..", ".."),
2179
1363
  process.cwd()
2180
1364
  ];
2181
- _cachedFromSource = roots.some((root) => fs.existsSync(path3.join(root, ".git")));
1365
+ _cachedFromSource = roots.some((root) => fs.existsSync(path2.join(root, ".git")));
2182
1366
  } catch {
2183
1367
  _cachedFromSource = false;
2184
1368
  }
@@ -2217,8 +1401,7 @@ var require_mixdog_debug = __commonJS({
2217
1401
  "webhook.log",
2218
1402
  "perf.log",
2219
1403
  "tool-events.log",
2220
- "memory-runtime-proxy.log",
2221
- "channels-worker-standalone.log",
1404
+ "daemon.log",
2222
1405
  "mixdog-tui.stderr.log"
2223
1406
  ]);
2224
1407
  var STALE_PLUGIN_LOG_SIBLING_RE = [
@@ -2244,7 +1427,7 @@ var require_mixdog_debug = __commonJS({
2244
1427
  }
2245
1428
  const candidates = [];
2246
1429
  for (const e of entries) {
2247
- const p = path3.join(dataDir, e.name);
1430
+ const p = path2.join(dataDir, e.name);
2248
1431
  try {
2249
1432
  const st = fs.statSync(p);
2250
1433
  if (now - st.mtimeMs < minAgeMs) continue;
@@ -2285,7 +1468,7 @@ var require_mixdog_debug = __commonJS({
2285
1468
  function appendSessionStartCriticalLog(dataDir, line) {
2286
1469
  if (!dataDir || !line) return;
2287
1470
  try {
2288
- const p = path3.join(dataDir, SESSION_START_CRITICAL_LOG);
1471
+ const p = path2.join(dataDir, SESSION_START_CRITICAL_LOG);
2289
1472
  fs.mkdirSync(dataDir, { recursive: true });
2290
1473
  rotateBoundedLog2(p, SESSION_START_CRITICAL_MAX_BYTES, SESSION_START_CRITICAL_KEEP_BYTES);
2291
1474
  fs.appendFileSync(p, line.endsWith("\n") ? line : `${line}
@@ -2312,14 +1495,14 @@ var require_mixdog_debug = __commonJS({
2312
1495
  // src/tui/index.jsx
2313
1496
  import React22 from "react";
2314
1497
  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";
1498
+ import { closeSync as closeSync2, constants as fsConstants, createWriteStream, mkdirSync as mkdirSync6, openSync as openSync2, readSync } from "node:fs";
2316
1499
  import { tmpdir as tmpdir2 } from "node:os";
2317
- import { dirname as dirname8, join as join9 } from "node:path";
1500
+ import { dirname as dirname7, join as join8 } from "node:path";
2318
1501
  import { performance as performance3 } from "node:perf_hooks";
2319
1502
  import { format } from "node:util";
2320
1503
 
2321
1504
  // 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";
1505
+ import React21, { useState as useState11, useCallback as useCallback13, useEffect as useEffect14, useLayoutEffect as useLayoutEffect5, useMemo as useMemo5, useRef as useRef14 } from "react";
2323
1506
  import { Box as Box19, Text as Text21, useApp, useInput as useInput6, useStdin as useStdin3, useStdout as useStdout2 } from "../../../vendor/ink/build/index.js";
2324
1507
 
2325
1508
  // src/tui/themes/base.mjs
@@ -3828,9 +3011,9 @@ var AGENT_RESPONSE_MARKER = RIGHT_ARROW;
3828
3011
  var RESULT_GUTTER = ` ${RESULT_GUTTER_GLYPH} `;
3829
3012
  var RESULT_GUTTER_CONT = ` ${RESULT_GUTTER_CONT_GLYPH} `;
3830
3013
 
3831
- // src/tui/hooks/useEngine.mjs
3014
+ // src/tui/hooks/useSession.mjs
3832
3015
  import { useSyncExternalStore } from "react";
3833
- function useEngine(store) {
3016
+ function useSession(store) {
3834
3017
  return useSyncExternalStore(store.subscribe, store.getState, store.getState);
3835
3018
  }
3836
3019
 
@@ -3992,8 +3175,8 @@ function parseToolArgs(args) {
3992
3175
  }
3993
3176
  return { value: args };
3994
3177
  }
3995
- function displayToolPath(path3) {
3996
- const text = String(path3 ?? "");
3178
+ function displayToolPath(path2) {
3179
+ const text = String(path2 ?? "");
3997
3180
  return text.replace(/\\/g, "/").split("/").filter(Boolean).at(-1) || text;
3998
3181
  }
3999
3182
  function compactParts(parts) {
@@ -4187,16 +3370,16 @@ function looksLikeZeroResultText(text) {
4187
3370
  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
3371
  }
4189
3372
  function splitPathAndDelta(value, explicitDelta = "") {
4190
- let path3 = String(value ?? "").trim();
3373
+ let path2 = String(value ?? "").trim();
4191
3374
  let delta = String(explicitDelta ?? "").trim();
4192
3375
  if (!delta) {
4193
- const sep = path3.lastIndexOf(" \u2014 ");
3376
+ const sep = path2.lastIndexOf(" \u2014 ");
4194
3377
  if (sep !== -1) {
4195
- delta = path3.slice(sep + 3).trim();
4196
- path3 = path3.slice(0, sep).trim();
3378
+ delta = path2.slice(sep + 3).trim();
3379
+ path2 = path2.slice(0, sep).trim();
4197
3380
  }
4198
3381
  }
4199
- return { path: path3, delta };
3382
+ return { path: path2, delta };
4200
3383
  }
4201
3384
  function parseLineDelta(delta) {
4202
3385
  const totals = { added: 0, removed: 0, seen: false };
@@ -4230,14 +3413,14 @@ function summarizeUpdateResult(text, args) {
4230
3413
  for (const line of String(text ?? "").split("\n")) {
4231
3414
  const ok = /^\s*OK\s+(modify|add|delete|create)\s+(.+?)\s*$/i.exec(line);
4232
3415
  if (ok) {
4233
- const { path: path3, delta } = splitPathAndDelta(ok[2]);
4234
- changed.push({ action: ok[1].toLowerCase(), path: path3, delta });
3416
+ const { path: path2, delta } = splitPathAndDelta(ok[2]);
3417
+ changed.push({ action: ok[1].toLowerCase(), path: path2, delta });
4235
3418
  continue;
4236
3419
  }
4237
3420
  const edited = /^\s*(Edited|Created|Updated|Wrote):\s+(.+?)(?:\s+\(([^)]+)\))?\s*$/i.exec(line);
4238
3421
  if (edited) {
4239
- const { path: path3, delta } = splitPathAndDelta(edited[2], edited[3] || "");
4240
- changed.push({ action: edited[1].toLowerCase(), path: path3, delta });
3422
+ const { path: path2, delta } = splitPathAndDelta(edited[2], edited[3] || "");
3423
+ changed.push({ action: edited[1].toLowerCase(), path: path2, delta });
4241
3424
  }
4242
3425
  }
4243
3426
  if (changed.length === 1) {
@@ -4580,6 +3763,32 @@ function summarizeAgentSurfaceBrief(name, args, resultText, { isError = false, i
4580
3763
  }
4581
3764
 
4582
3765
  // src/runtime/shared/tool-surface.mjs
3766
+ function patchOperationProfile(args = {}) {
3767
+ const a = parseToolArgs(args);
3768
+ const patchText = String(a.patch ?? "");
3769
+ const counts = /* @__PURE__ */ new Map();
3770
+ const add = (kind, count = 1) => {
3771
+ counts.set(kind, Number(counts.get(kind) || 0) + Math.max(1, Number(count || 1)));
3772
+ };
3773
+ for (const line of patchText.split("\n")) {
3774
+ const match = /^\*\*\*\s+(Update|Add|Delete) File:\s+.+\s*$/i.exec(line);
3775
+ if (!match) continue;
3776
+ add(match[1].toLowerCase());
3777
+ }
3778
+ if (counts.size > 0) return counts;
3779
+ const gitSections = patchText.split(/(?=^diff --git )/m).filter((section) => /^diff --git /m.test(section));
3780
+ const unifiedSections = gitSections.length > 0 ? gitSections : /^---\s+.+\n\+\+\+\s+.+$/m.test(patchText) ? [patchText] : [];
3781
+ for (const section of unifiedSections) {
3782
+ if (/^new file mode /m.test(section) || /^---\s+\/dev\/null(?:\s|$)/m.test(section)) add("add");
3783
+ else if (/^deleted file mode /m.test(section) || /^\+\+\+\s+\/dev\/null(?:\s|$)/m.test(section)) add("delete");
3784
+ else add("update");
3785
+ }
3786
+ if (counts.size > 0) return counts;
3787
+ if (a.old_string === "") add("add");
3788
+ else if (a.new_string === "" && a.old_string != null) add("delete");
3789
+ else add("update", patchFileCount(a) || 1);
3790
+ return counts;
3791
+ }
4583
3792
  function displayToolName(name, args = {}) {
4584
3793
  if (isExternalMcpToolName(name)) {
4585
3794
  const mcp = parseMcpToolName(name);
@@ -4594,7 +3803,11 @@ function displayToolName(name, args = {}) {
4594
3803
  case "apply_patch": {
4595
3804
  const parsed = parseToolArgs(args);
4596
3805
  if (parsed && parsed.dry_run === true) return "Check";
4597
- return parsed && parsed.old_string === "" ? "Create" : "Update";
3806
+ const operations = patchOperationProfile(parsed);
3807
+ if (operations.size > 1) return "Change";
3808
+ if (operations.has("add")) return "Create";
3809
+ if (operations.has("delete")) return "Delete";
3810
+ return "Update";
4598
3811
  }
4599
3812
  case "shell":
4600
3813
  case "bash":
@@ -4925,6 +4138,28 @@ function unitDescriptor(category, overrides = {}) {
4925
4138
  function queryCount(args, ...keys) {
4926
4139
  return collectionCount(...keys.map((key) => args?.[key]));
4927
4140
  }
4141
+ function patchMutationUnits(args = {}) {
4142
+ const a = parseToolArgs(args);
4143
+ if (a.dry_run === true) {
4144
+ return [unitDescriptor("Patch", {
4145
+ count: patchFileCount(a) || 1,
4146
+ active: "Checking",
4147
+ done: "Checked",
4148
+ noun: "file"
4149
+ })];
4150
+ }
4151
+ const copy = {
4152
+ add: { active: "Creating", done: "Created" },
4153
+ delete: { active: "Deleting", done: "Deleted" },
4154
+ update: { active: "Editing", done: "Edited" }
4155
+ };
4156
+ return [...patchOperationProfile(a)].map(([kind, count]) => unitDescriptor("Patch", {
4157
+ count,
4158
+ active: copy[kind]?.active || "Editing",
4159
+ done: copy[kind]?.done || "Edited",
4160
+ noun: "file"
4161
+ }));
4162
+ }
4928
4163
  function toolWorkUnit(name, args = {}, category = "") {
4929
4164
  const a = parseToolArgs(args);
4930
4165
  const normalized = normalizeToolName(name);
@@ -4944,26 +4179,12 @@ function toolWorkUnit(name, args = {}, category = "") {
4944
4179
  case "read_mcp_resource":
4945
4180
  return unitDescriptor("Read", { count: queryCount(a, "uri", "uris") || 1, noun: "resource" });
4946
4181
  case "apply_patch": {
4947
- const patchText = String(a.patch ?? "");
4948
- const creating = a.old_string === "" || /^\*\*\*\s+Add File:/mi.test(patchText);
4949
- const deleting = !creating && a.new_string === "" && a.old_string != null || /^\*\*\*\s+Delete File:/mi.test(patchText);
4950
- if (a.dry_run === true) {
4951
- return unitDescriptor("Patch", {
4952
- count: patchFileCount(a) || 1,
4953
- active: "Checking",
4954
- done: "Checked",
4955
- noun: "file"
4956
- });
4957
- }
4182
+ const units = patchMutationUnits(a);
4183
+ if (units.length === 1) return units[0];
4958
4184
  return unitDescriptor("Patch", {
4959
- 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
- active: creating ? "Creating" : deleting ? "Deleting" : "Editing",
4966
- done: creating ? "Created" : deleting ? "Deleted" : "Edited",
4185
+ count: units.reduce((total, unit) => total + unit.count, 0),
4186
+ active: "Changing",
4187
+ done: "Changed",
4967
4188
  noun: "file"
4968
4189
  });
4969
4190
  }
@@ -5179,9 +4400,9 @@ function reconcileTimer() {
5179
4400
  clearTimeout(timer);
5180
4401
  timer = null;
5181
4402
  }
5182
- const delay2 = nextDelayMs(Date.now());
5183
- if (delay2 == null) return;
5184
- timer = setTimeout(onTick, delay2);
4403
+ const delay = nextDelayMs(Date.now());
4404
+ if (delay == null) return;
4405
+ timer = setTimeout(onTick, delay);
5185
4406
  timer.unref?.();
5186
4407
  }
5187
4408
  function onTick() {
@@ -5411,6 +4632,16 @@ var SPINNER_VERBS = [
5411
4632
  "Zigzagging"
5412
4633
  ];
5413
4634
  var SPINNER_VERB_ROTATE_MS = 3e4;
4635
+ var SPINNER_VERB_CHANGE_ODDS = 0.5;
4636
+ function boundarySwaps(anchor, boundary) {
4637
+ let h = Math.floor(anchor / 1e3) + Math.imul(boundary, 2654435761) | 0;
4638
+ h = Math.imul(h ^ h >>> 16, 569420461);
4639
+ h = Math.imul(h ^ h >>> 15, 1935289751);
4640
+ h = (h ^ h >>> 15) >>> 0;
4641
+ return h / 4294967296 < SPINNER_VERB_CHANGE_ODDS;
4642
+ }
4643
+ var MAX_COUNTED_BOUNDARIES = 4096;
4644
+ var MAX_HELD_BOUNDARIES = 3;
5414
4645
  var SPINNER_MODE_OVERRIDE_VERBS = {
5415
4646
  compacting: "Compacting conversation",
5416
4647
  "auto-clear": "Auto-clearing conversation",
@@ -5420,14 +4651,24 @@ function spinnerVerbFor(startedAt = 0, now = 0, pool = SPINNER_VERBS) {
5420
4651
  const list = Array.isArray(pool) && pool.length ? pool : SPINNER_VERBS;
5421
4652
  const anchor = Math.max(0, Number(startedAt) || 0);
5422
4653
  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;
4654
+ const boundaries = Math.floor(elapsed / SPINNER_VERB_ROTATE_MS);
4655
+ const counted = Math.min(boundaries, MAX_COUNTED_BOUNDARIES);
4656
+ let swaps = boundaries - counted;
4657
+ let held = 0;
4658
+ for (let boundary = 1; boundary <= counted; boundary += 1) {
4659
+ if (held >= MAX_HELD_BOUNDARIES || boundarySwaps(anchor, boundary)) {
4660
+ swaps += 1;
4661
+ held = 0;
4662
+ } else {
4663
+ held += 1;
4664
+ }
4665
+ }
4666
+ const seed = Math.floor(anchor / 1e3) + swaps;
5425
4667
  return list[(seed * 7 + 3) % list.length];
5426
4668
  }
5427
4669
  var SPINNER_FRAMES = ["\u25C7", "\u25C6", "\u25C8", "\u25C6", "\u25C7"];
5428
4670
 
5429
4671
  // src/tui/spinner-meta.mjs
5430
- var SHOW_TOKENS_AFTER_MS = 3e4;
5431
4672
  var compactNumberFormatter = new Intl.NumberFormat("en-US", {
5432
4673
  notation: "compact",
5433
4674
  maximumFractionDigits: 1,
@@ -5462,7 +4703,10 @@ function buildSpinnerMeta({
5462
4703
  const thinkingText = spinnerThinkingLabel({ thinking, thinkingSince, thinkingMs, effort });
5463
4704
  return {
5464
4705
  tokensText: tokens > 0 ? `${formatSpinnerTokens(tokens)} tokens` : "",
5465
- showTokens: tokens > 0 && (Boolean(verbose) || Number(elapsedMs) > SHOW_TOKENS_AFTER_MS),
4706
+ // The count appears with the FIRST token (user: 30초가 아니라 바로).
4707
+ // Elapsed was never gated, so the byline used to grow a second field
4708
+ // mid-turn for no reason the reader could see; now both arrive together.
4709
+ showTokens: tokens > 0,
5466
4710
  thinkingText,
5467
4711
  thinkingActive: Boolean(thinking || Number(thinkingSince) > 0)
5468
4712
  };
@@ -6001,8 +5245,8 @@ function hasActiveStatuslineWork(line, agentWorkers = [], agentJobs = [], active
6001
5245
  function bootFullRenderEligible(mountAtMs, line, agentWorkers = [], agentJobs = [], activeTools = null) {
6002
5246
  const elapsed = Date.now() - mountAtMs;
6003
5247
  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;
5248
+ const delay = active ? STATUSLINE_BOOT_FULL_DELAY_ACTIVE_MS : STATUSLINE_BOOT_FULL_DELAY_MS;
5249
+ return elapsed >= delay;
6006
5250
  }
6007
5251
  function canAttemptBootFullRender(nextAttemptAtMs = 0) {
6008
5252
  return Date.now() >= nextAttemptAtMs;
@@ -6775,15 +6019,15 @@ function singleTrailingLineBreakPrefix(text) {
6775
6019
  function draftStateEqual(a, b) {
6776
6020
  return a.value === b.value && a.cursor === b.cursor && a.selectionAnchor === b.selectionAnchor;
6777
6021
  }
6778
- var MODIFIED_ENTER_SHIFT_OR_CTRL = 1 | 4;
6022
+ var MODIFIED_ENTER_NEWLINE = 1 | 2 | 4;
6779
6023
  function isModifiedEnterSequence(input) {
6780
6024
  const text = String(input ?? "");
6781
6025
  const body = text.startsWith("\x1B[") ? text.slice(2) : text.startsWith("[") ? text.slice(1) : "";
6782
6026
  if (!body) return false;
6783
6027
  const kitty = /^13;(\d+)(?::\d+)?(?:;[\d:]+)?u$/.exec(body);
6784
- if (kitty) return (Number(kitty[1]) - 1 & MODIFIED_ENTER_SHIFT_OR_CTRL) !== 0;
6028
+ if (kitty) return (Number(kitty[1]) - 1 & MODIFIED_ENTER_NEWLINE) !== 0;
6785
6029
  const modifyOtherKeys = /^27;(\d+);13~$/.exec(body);
6786
- return Boolean(modifyOtherKeys && (Number(modifyOtherKeys[1]) - 1 & MODIFIED_ENTER_SHIFT_OR_CTRL) !== 0);
6030
+ return Boolean(modifyOtherKeys && (Number(modifyOtherKeys[1]) - 1 & MODIFIED_ENTER_NEWLINE) !== 0);
6787
6031
  }
6788
6032
  function isAnyModifiedEnterSequence(input) {
6789
6033
  const text = String(input ?? "");
@@ -6796,9 +6040,6 @@ function isAnyModifiedEnterSequence(input) {
6796
6040
  }
6797
6041
 
6798
6042
  // src/tui/components/prompt-input/immediate-render.mjs
6799
- function suppressed(value) {
6800
- return typeof value === "function" ? value() === true : value === true;
6801
- }
6802
6043
  function cancelPromptImmediateFlush(throttle, clearTimer = clearTimeout) {
6803
6044
  if (!throttle || throttle.timer === null) return false;
6804
6045
  clearTimer(throttle.timer);
@@ -6807,7 +6048,6 @@ function cancelPromptImmediateFlush(throttle, clearTimer = clearTimeout) {
6807
6048
  }
6808
6049
  function schedulePromptImmediateFlush({
6809
6050
  throttle,
6810
- isSuppressed = false,
6811
6051
  flush,
6812
6052
  intervalMs = 16,
6813
6053
  now = Date.now,
@@ -6818,10 +6058,6 @@ function schedulePromptImmediateFlush({
6818
6058
  if (!throttle || typeof flush !== "function") {
6819
6059
  throw new TypeError("schedulePromptImmediateFlush requires throttle state and a flush function");
6820
6060
  }
6821
- if (suppressed(isSuppressed)) {
6822
- cancelPromptImmediateFlush(throttle, clearTimer);
6823
- return false;
6824
- }
6825
6061
  const current = now();
6826
6062
  const elapsed = current - throttle.lastAt;
6827
6063
  if (elapsed >= intervalMs) {
@@ -6833,13 +6069,35 @@ function schedulePromptImmediateFlush({
6833
6069
  if (throttle.timer !== null) return false;
6834
6070
  throttle.timer = setTimer(() => {
6835
6071
  throttle.timer = null;
6836
- if (suppressed(isSuppressed)) return;
6837
6072
  throttle.lastAt = now();
6838
6073
  flush();
6839
6074
  }, intervalMs - elapsed);
6840
6075
  return true;
6841
6076
  }
6842
6077
 
6078
+ // src/tui/components/prompt-input/escape-policy.mjs
6079
+ var PROMPT_ESCAPE_CLEAR_WINDOW_MS = 800;
6080
+ function classifyPromptEscape({
6081
+ interruptActive = false,
6082
+ hasQueuedMessages = false,
6083
+ hasMessages = false,
6084
+ value = "",
6085
+ lastClearPressAt = 0,
6086
+ now = Date.now()
6087
+ } = {}) {
6088
+ if (interruptActive) return { action: "interrupt", nextClearPressAt: 0 };
6089
+ if (hasQueuedMessages) return { action: "restore-queue", nextClearPressAt: 0 };
6090
+ const current = Number(now);
6091
+ const previous = Number(lastClearPressAt);
6092
+ const repeated = Number.isFinite(current) && Number.isFinite(previous) && previous > 0 && current >= previous && current - previous <= PROMPT_ESCAPE_CLEAR_WINDOW_MS;
6093
+ const armed = Number.isFinite(current) && current > 0 ? current : 1;
6094
+ if (String(value ?? "") === "") {
6095
+ if (!hasMessages) return { action: "idle", nextClearPressAt: 0 };
6096
+ return repeated ? { action: "message-selector", nextClearPressAt: 0 } : { action: "arm-select", nextClearPressAt: armed };
6097
+ }
6098
+ return repeated ? { action: "clear", nextClearPressAt: 0 } : { action: "arm-clear", nextClearPressAt: armed };
6099
+ }
6100
+
6843
6101
  // src/tui/components/PromptInput.jsx
6844
6102
  import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
6845
6103
  var IME_LEFT_GUARD_COLUMNS = 0;
@@ -6862,6 +6120,7 @@ function PromptInput({
6862
6120
  interruptActive = false,
6863
6121
  onInterrupt,
6864
6122
  commandPaletteActive = false,
6123
+ commandPaletteOpen = commandPaletteActive,
6865
6124
  mask = false,
6866
6125
  hint = "",
6867
6126
  hintTone = "info",
@@ -6874,6 +6133,8 @@ function PromptInput({
6874
6133
  onCommandPaletteCancel,
6875
6134
  onCommandPaletteComplete,
6876
6135
  onRestoreQueued,
6136
+ hasQueuedMessages = false,
6137
+ hasMessages = false,
6877
6138
  onHistoryNavigate,
6878
6139
  onPasteText,
6879
6140
  selectionRef,
@@ -6888,10 +6149,10 @@ function PromptInput({
6888
6149
  });
6889
6150
  const [, bumpCursorAnchorEpoch] = useState3(0);
6890
6151
  const draftRef = useRef4(draft);
6891
- const interruptActiveRef = useRef4(interruptActive);
6892
6152
  const lastReportedValueRef = useRef4(draft.value);
6893
6153
  const pasteGenerationRef = useRef4(0);
6894
6154
  const submitGateRef = useRef4(false);
6155
+ const escapeClearAtRef = useRef4(0);
6895
6156
  if (valueRef) valueRef.current = draftRef.current.value;
6896
6157
  const { isRawModeSupported } = useStdin();
6897
6158
  const boxRef = useRef4(null);
@@ -6903,7 +6164,6 @@ function PromptInput({
6903
6164
  const undoRef = useRef4({ past: [], future: [], lastPushAt: 0, lastValue: null });
6904
6165
  const { value, cursor } = draft;
6905
6166
  draftRef.current = draft;
6906
- interruptActiveRef.current = interruptActive;
6907
6167
  if (selectionRef) {
6908
6168
  const range = selectionRange(draft);
6909
6169
  selectionRef.current = range ? { range, text: mask ? "" : draft.value.slice(range.start, range.end) } : null;
@@ -6928,7 +6188,6 @@ function PromptInput({
6928
6188
  const scheduleImmediateFlush = () => {
6929
6189
  schedulePromptImmediateFlush({
6930
6190
  throttle: flushThrottleRef.current,
6931
- isSuppressed: () => interruptActiveRef.current,
6932
6191
  flush: flushImmediate
6933
6192
  });
6934
6193
  };
@@ -6936,6 +6195,7 @@ function PromptInput({
6936
6195
  cancelPromptImmediateFlush(flushThrottleRef.current);
6937
6196
  }, []);
6938
6197
  const commitDraft = (next, options = {}) => {
6198
+ escapeClearAtRef.current = 0;
6939
6199
  const sameDraft = draftStateEqual(draftRef.current, next);
6940
6200
  if (!options.keepPreferredColumn) preferredColumnRef.current = null;
6941
6201
  if (sameDraft) {
@@ -7144,8 +6404,12 @@ function PromptInput({
7144
6404
  commitDraft(moveCursor(current, moved.cursor, { extend }), { keepPreferredColumn: true });
7145
6405
  return true;
7146
6406
  };
7147
- const restoreQueuedToDraft = () => {
7148
- return onRestoreQueued?.(draftRef.current.value) === true;
6407
+ const restoreQueuedToDraft = ({ showHint = false } = {}) => {
6408
+ return onRestoreQueued?.({
6409
+ restoreDraft: true,
6410
+ showHint,
6411
+ currentText: draftRef.current.value
6412
+ }) === true;
7149
6413
  };
7150
6414
  const applyHistoryNavigation = (direction, meta = {}) => {
7151
6415
  const nextValue = onHistoryNavigate?.(direction, draftRef.current.value, meta);
@@ -7237,6 +6501,7 @@ function PromptInput({
7237
6501
  if (disabled) return;
7238
6502
  const rawInput = String(input ?? "");
7239
6503
  const inputKey = rawInput.toLowerCase();
6504
+ if (!key.escape) escapeClearAtRef.current = 0;
7240
6505
  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
6506
  const gridSelectionActive = typeof suppressShiftNavRef === "function" ? suppressShiftNavRef() : typeof suppressShiftNavRef?.current === "function" ? suppressShiftNavRef.current() : Boolean(suppressShiftNavRef?.current);
7242
6507
  if (gridSelectionActive) {
@@ -7264,13 +6529,13 @@ function PromptInput({
7264
6529
  const lineBreakIndex = rawInput.search(/[\r\n]/);
7265
6530
  const rawEnter = rawInput === "\r" || rawInput === "\n" || rawInput === "\r\n";
7266
6531
  const trailingEnterPrefix = singleTrailingLineBreakPrefix(rawInput);
7267
- const rawCtrlEnter = isModifiedEnterSequence(rawInput);
7268
- const modifiedLineBreak = key.shift || key.meta || key.ctrl || rawCtrlEnter;
6532
+ const rawModifiedEnter = isModifiedEnterSequence(rawInput);
6533
+ const modifiedLineBreak = key.shift || key.meta || key.ctrl || rawModifiedEnter;
7269
6534
  if (rawInput === "\n" && !key.return || key.ctrl && inputKey === "j") {
7270
6535
  updateDraft((d) => replaceSelection(d, "\n"));
7271
6536
  return;
7272
6537
  }
7273
- if (!rawCtrlEnter && isAnyModifiedEnterSequence(rawInput)) {
6538
+ if (!rawModifiedEnter && isAnyModifiedEnterSequence(rawInput)) {
7274
6539
  return;
7275
6540
  }
7276
6541
  const pasteFallback = lineBreakIndex !== -1 && trailingEnterPrefix === null && !rawEnter && (rawInput.length > 1 || !key.return);
@@ -7287,7 +6552,7 @@ function PromptInput({
7287
6552
  submitEnterChunk(trailingEnterPrefix);
7288
6553
  return;
7289
6554
  }
7290
- if (rawCtrlEnter) {
6555
+ if (rawModifiedEnter) {
7291
6556
  updateDraft((d) => replaceSelection(d, "\n"));
7292
6557
  return;
7293
6558
  }
@@ -7340,12 +6605,10 @@ ${d.value.slice(d.cursor)}`,
7340
6605
  if (!moveDraftVertically(-1, { extend: true })) {
7341
6606
  updateDraft((d) => moveCursor(d, 0, { extend: true }));
7342
6607
  }
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 });
6608
+ } else if (!moveDraftVertically(-1, { extend: false })) {
6609
+ const emptyDraft = String(draftRef.current.value || "").length === 0;
6610
+ if (!hasQueuedMessages || !restoreQueuedToDraft()) {
6611
+ applyHistoryNavigation("up", { emptyDraft });
7349
6612
  }
7350
6613
  }
7351
6614
  }
@@ -7360,7 +6623,7 @@ ${d.value.slice(d.cursor)}`,
7360
6623
  updateDraft((d) => moveCursor(d, d.value.length, { extend: true }));
7361
6624
  }
7362
6625
  } else if (!moveDraftVertically(1, { extend: false })) {
7363
- applyHistoryNavigation("down", { emptyDraft: String(draftRef.current.value || "").trim().length === 0 });
6626
+ applyHistoryNavigation("down", { emptyDraft: String(draftRef.current.value || "").length === 0 });
7364
6627
  }
7365
6628
  }
7366
6629
  return;
@@ -7392,9 +6655,8 @@ ${d.value.slice(d.cursor)}`,
7392
6655
  if (onTab?.(draftRef.current.value) === true) return;
7393
6656
  }
7394
6657
  if (key.escape) {
7395
- if (commandPaletteActive) {
6658
+ if (commandPaletteOpen) {
7396
6659
  onCommandPaletteCancel?.(draftRef.current.value);
7397
- commitDraft({ value: "", cursor: 0, selectionAnchor: null });
7398
6660
  return;
7399
6661
  }
7400
6662
  if (selectionRange(draftRef.current)) {
@@ -7405,19 +6667,48 @@ ${d.value.slice(d.cursor)}`,
7405
6667
  if (onEscape?.(currentValue, { phase: "before" }) === true) {
7406
6668
  return;
7407
6669
  }
7408
- if (currentValue) {
6670
+ let escape = classifyPromptEscape({
6671
+ interruptActive,
6672
+ hasQueuedMessages,
6673
+ hasMessages,
6674
+ value: currentValue,
6675
+ lastClearPressAt: escapeClearAtRef.current
6676
+ });
6677
+ if (escape.action === "restore-queue") {
6678
+ if (restoreQueuedToDraft()) {
6679
+ escapeClearAtRef.current = 0;
6680
+ return;
6681
+ }
6682
+ escape = classifyPromptEscape({
6683
+ interruptActive,
6684
+ hasMessages,
6685
+ value: currentValue,
6686
+ lastClearPressAt: escapeClearAtRef.current
6687
+ });
6688
+ }
6689
+ escapeClearAtRef.current = escape.nextClearPressAt;
6690
+ if (escape.action === "interrupt") {
6691
+ const restoredText = onInterrupt?.(currentValue);
6692
+ if (!currentValue && typeof restoredText === "string") {
6693
+ commitDraft({ value: restoredText, cursor: restoredText.length, selectionAnchor: null });
6694
+ }
6695
+ return;
6696
+ }
6697
+ if (escape.action === "arm-clear") {
6698
+ onEscape?.(currentValue, { phase: "clear-arm" });
6699
+ return;
6700
+ }
6701
+ if (escape.action === "clear") {
7409
6702
  onEscape?.(currentValue, { phase: "clear" });
7410
6703
  commitDraft({ value: "", cursor: 0, selectionAnchor: null });
7411
6704
  return;
7412
6705
  }
7413
- if (interruptActive) {
7414
- const restoredText = onInterrupt?.("");
7415
- if (typeof restoredText === "string") {
7416
- commitDraft({ value: restoredText, cursor: restoredText.length, selectionAnchor: null });
7417
- }
6706
+ if (escape.action === "arm-select") {
6707
+ onEscape?.("", { phase: "select-arm" });
7418
6708
  return;
7419
6709
  }
7420
- if (restoreQueuedToDraft()) {
6710
+ if (escape.action === "message-selector") {
6711
+ onEscape?.("", { phase: "select" });
7421
6712
  return;
7422
6713
  }
7423
6714
  onEscape?.("", { phase: "empty" });
@@ -8254,11 +7545,11 @@ function ContextUsageView({ detail, columns }) {
8254
7545
  const usage = detail?.usage || {};
8255
7546
  const compaction = detail?.compaction || {};
8256
7547
  const messages = detail?.messages || {};
8257
- const request2 = detail?.request || {};
7548
+ const request = detail?.request || {};
8258
7549
  const lastApi = detail?.lastApi || {};
8259
7550
  const cache = detail?.cache || {};
8260
7551
  const semantic = messages.semantic || {};
8261
- const schema = request2.toolSchemaBreakdown || {};
7552
+ const schema = request.toolSchemaBreakdown || {};
8262
7553
  const usedTokens = finiteNumber(usage.usedTokens);
8263
7554
  const windowTokens = finiteNumber(usage.windowTokens);
8264
7555
  const freeTokens = windowTokens ? Math.max(0, windowTokens - usedTokens) : finiteNumber(usage.freeTokens);
@@ -8839,15 +8130,15 @@ function singleTrailingLineBreakPrefix2(text) {
8839
8130
  const prefix = normalized.slice(0, -1);
8840
8131
  return prefix.includes("\n") ? null : prefix;
8841
8132
  }
8842
- var MODIFIED_ENTER_SHIFT_OR_CTRL2 = 1 | 4;
8133
+ var MODIFIED_ENTER_SHIFT_OR_CTRL = 1 | 4;
8843
8134
  function isModifiedEnterSequence2(input) {
8844
8135
  const text = String(input ?? "");
8845
8136
  const body = text.startsWith("\x1B[") ? text.slice(2) : text.startsWith("[") ? text.slice(1) : "";
8846
8137
  if (!body) return false;
8847
8138
  const kitty = /^13;(\d+)(?::\d+)?(?:;[\d:]+)?u$/.exec(body);
8848
- if (kitty) return (Number(kitty[1]) - 1 & MODIFIED_ENTER_SHIFT_OR_CTRL2) !== 0;
8139
+ if (kitty) return (Number(kitty[1]) - 1 & MODIFIED_ENTER_SHIFT_OR_CTRL) !== 0;
8849
8140
  const modifyOtherKeys = /^27;(\d+);13~$/.exec(body);
8850
- return Boolean(modifyOtherKeys && (Number(modifyOtherKeys[1]) - 1 & MODIFIED_ENTER_SHIFT_OR_CTRL2) !== 0);
8141
+ return Boolean(modifyOtherKeys && (Number(modifyOtherKeys[1]) - 1 & MODIFIED_ENTER_SHIFT_OR_CTRL) !== 0);
8851
8142
  }
8852
8143
  function isAnyModifiedEnterSequence2(input) {
8853
8144
  const text = String(input ?? "");
@@ -9211,169 +8502,17 @@ function TextEntryPanel({
9211
8502
  // src/tui/App.jsx
9212
8503
  init_paste_attachments();
9213
8504
 
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
8505
  // src/standalone/folder-dialog.mjs
9367
8506
  import { spawn } from "node:child_process";
9368
8507
  import path from "node:path";
9369
8508
  var DIALOG_TIMEOUT_MS = 5 * 60 * 1e3;
9370
8509
  function runCapture(cmd, args, { timeoutMs = DIALOG_TIMEOUT_MS } = {}) {
9371
- return new Promise((resolve5) => {
8510
+ return new Promise((resolve4) => {
9372
8511
  let child;
9373
8512
  try {
9374
8513
  child = spawn(cmd, args, { stdio: ["ignore", "pipe", "ignore"], windowsHide: true });
9375
8514
  } catch (error) {
9376
- resolve5({ ok: false, code: -1, stdout: "", error });
8515
+ resolve4({ ok: false, code: -1, stdout: "", error });
9377
8516
  return;
9378
8517
  }
9379
8518
  let stdout2 = "";
@@ -9385,7 +8524,7 @@ function runCapture(cmd, args, { timeoutMs = DIALOG_TIMEOUT_MS } = {}) {
9385
8524
  child.kill();
9386
8525
  } catch {
9387
8526
  }
9388
- resolve5({ ok: false, code: -1, stdout: "", error: new Error("dialog timed out") });
8527
+ resolve4({ ok: false, code: -1, stdout: "", error: new Error("dialog timed out") });
9389
8528
  }, timeoutMs);
9390
8529
  timer2.unref?.();
9391
8530
  child.stdout.on("data", (chunk) => {
@@ -9395,13 +8534,13 @@ function runCapture(cmd, args, { timeoutMs = DIALOG_TIMEOUT_MS } = {}) {
9395
8534
  if (settled) return;
9396
8535
  settled = true;
9397
8536
  clearTimeout(timer2);
9398
- resolve5({ ok: false, code: -1, stdout: "", error });
8537
+ resolve4({ ok: false, code: -1, stdout: "", error });
9399
8538
  });
9400
8539
  child.on("close", (code) => {
9401
8540
  if (settled) return;
9402
8541
  settled = true;
9403
8542
  clearTimeout(timer2);
9404
- resolve5({ ok: code === 0, code, stdout: stdout2 });
8543
+ resolve4({ ok: code === 0, code, stdout: stdout2 });
9405
8544
  });
9406
8545
  });
9407
8546
  }
@@ -9409,10 +8548,10 @@ function spawnFailed(result) {
9409
8548
  return !!result && result.code === -1;
9410
8549
  }
9411
8550
  function commandExists(cmd) {
9412
- return new Promise((resolve5) => {
8551
+ return new Promise((resolve4) => {
9413
8552
  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));
8553
+ probe.on("error", () => resolve4(false));
8554
+ probe.on("close", (code) => resolve4(code === 0));
9416
8555
  });
9417
8556
  }
9418
8557
  function psSingleQuoted(value) {
@@ -9615,9 +8754,9 @@ async function pickFolder({ title = "Select a project folder", initialPath = ""
9615
8754
  powershellScript(title, resolvedInitial)
9616
8755
  ]);
9617
8756
  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 };
8757
+ const path2 = String(result.stdout || "").trim();
8758
+ if (!result.ok && !path2) return { available: false, path: null };
8759
+ return { available: true, path: path2 || null };
9621
8760
  }
9622
8761
  if (platform2 === "darwin") {
9623
8762
  const safeTitle = String(title).replace(/"/g, '\\"');
@@ -9625,8 +8764,8 @@ async function pickFolder({ title = "Select a project folder", initialPath = ""
9625
8764
  POSIX path of f`;
9626
8765
  const result = await runCapture("osascript", ["-e", script]);
9627
8766
  if (spawnFailed(result)) return { available: false, path: null };
9628
- const path3 = String(result.stdout || "").trim();
9629
- return { available: true, path: path3 || null };
8767
+ const path2 = String(result.stdout || "").trim();
8768
+ return { available: true, path: path2 || null };
9630
8769
  }
9631
8770
  if (await commandExists("zenity")) {
9632
8771
  const result = await runCapture("zenity", [
@@ -9635,16 +8774,16 @@ POSIX path of f`;
9635
8774
  `--title=${title}`
9636
8775
  ]);
9637
8776
  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 };
8777
+ const path2 = String(result.stdout || "").trim();
8778
+ if (!result.ok && !path2) return { available: true, path: null };
8779
+ return { available: true, path: path2 || null };
9641
8780
  }
9642
8781
  if (await commandExists("kdialog")) {
9643
8782
  const result = await runCapture("kdialog", ["--getexistingdirectory", "."]);
9644
8783
  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 };
8784
+ const path2 = String(result.stdout || "").trim();
8785
+ if (!result.ok && !path2) return { available: true, path: null };
8786
+ return { available: true, path: path2 || null };
9648
8787
  }
9649
8788
  return { available: false, path: null };
9650
8789
  }
@@ -9967,13 +9106,13 @@ function nativeClipboardCommand(text) {
9967
9106
  function copyToClipboard(text) {
9968
9107
  const value = String(text ?? "");
9969
9108
  const wroteOsc52 = writeOsc52Clipboard(value);
9970
- return new Promise((resolve5, reject) => {
9109
+ return new Promise((resolve4, reject) => {
9971
9110
  const { cmd, args, input } = nativeClipboardCommand(value);
9972
9111
  let child;
9973
9112
  try {
9974
9113
  child = spawn2(cmd, args, { stdio: ["pipe", "ignore", "ignore"], windowsHide: true });
9975
9114
  } catch (e) {
9976
- if (wroteOsc52) resolve5();
9115
+ if (wroteOsc52) resolve4();
9977
9116
  else reject(e);
9978
9117
  return;
9979
9118
  }
@@ -9982,7 +9121,7 @@ function copyToClipboard(text) {
9982
9121
  });
9983
9122
  if (!wroteOsc52) {
9984
9123
  child.on("close", (code) => {
9985
- if (code === 0) resolve5();
9124
+ if (code === 0) resolve4();
9986
9125
  else reject(new Error(`${cmd} exited with code ${code}`));
9987
9126
  });
9988
9127
  }
@@ -9991,7 +9130,7 @@ function copyToClipboard(text) {
9991
9130
  child.stdin.end(input);
9992
9131
  if (wroteOsc52) {
9993
9132
  child.unref?.();
9994
- resolve5();
9133
+ resolve4();
9995
9134
  }
9996
9135
  });
9997
9136
  }
@@ -10168,7 +9307,7 @@ var DIFF_STAT_SUMMARY_RE = /^\s*\d+\s+files?\s+changed\b/;
10168
9307
  function colorizeDiffStatLine(line, c) {
10169
9308
  const m = DIFF_STAT_FILE_RE.exec(line);
10170
9309
  if (m) {
10171
- const [, indent, path3, sep, tail] = m;
9310
+ const [, indent, path2, sep, tail] = m;
10172
9311
  const countMatch = /^(\d+)(\s*)([+\-]*)\s*$/.exec(tail);
10173
9312
  let coloredTail;
10174
9313
  if (countMatch) {
@@ -10179,7 +9318,7 @@ function colorizeDiffStatLine(line, c) {
10179
9318
  } else {
10180
9319
  coloredTail = c.diffContext(tail);
10181
9320
  }
10182
- return `${indent}${c.diffContext(path3)}${c.diffContext(sep)}${coloredTail}`;
9321
+ return `${indent}${c.diffContext(path2)}${c.diffContext(sep)}${coloredTail}`;
10183
9322
  }
10184
9323
  return c.diffContext(line);
10185
9324
  }
@@ -12447,6 +11586,16 @@ var TRANSCRIPT_WINDOW_TAIL_MAX_ITEMS = positiveIntEnv("MIXDOG_TUI_TRANSCRIPT_WIN
12447
11586
  var SELECTION_PAINT_INTERVAL_MS = positiveIntEnv("MIXDOG_TUI_SELECTION_PAINT_MS", 24);
12448
11587
  var SCROLL_COALESCE_MS = positiveIntEnv("MIXDOG_TUI_SCROLL_COALESCE_MS", 16);
12449
11588
  var PROMPT_HISTORY_LIMIT = 50;
11589
+ function accumulateDirectionalScrollDelta(state, deltaRows) {
11590
+ const delta = Number(deltaRows) || 0;
11591
+ if (!delta) return false;
11592
+ const direction = Math.sign(delta);
11593
+ const reversed = Boolean(state.direction && state.direction !== direction);
11594
+ if (reversed) state.pendingRows = 0;
11595
+ state.direction = direction;
11596
+ state.pendingRows += delta;
11597
+ return reversed;
11598
+ }
12450
11599
  function boolEnvDefaultTrue(name) {
12451
11600
  const raw = process.env[name];
12452
11601
  if (raw == null) return true;
@@ -12784,6 +11933,17 @@ function transcriptRowAt(values, index) {
12784
11933
  if (!values || index < 0 || index >= values.length) return 0;
12785
11934
  return typeof values.atIndex === "function" ? values.atIndex(index) : Number(values[index]) || 0;
12786
11935
  }
11936
+ function transcriptSwapReturnsToTail({
11937
+ count = 0,
11938
+ previousCount = 0,
11939
+ firstId = null,
11940
+ previousFirstId = null
11941
+ } = {}) {
11942
+ if (previousCount === 0) return true;
11943
+ if (count < previousCount) return true;
11944
+ if (count === 0) return false;
11945
+ return firstId != null && previousFirstId != null && firstId !== previousFirstId;
11946
+ }
12787
11947
  function appendTranscriptRow(prefix, tailValue) {
12788
11948
  return Object.freeze({
12789
11949
  length: prefix.length + 1,
@@ -13378,7 +12538,7 @@ function useMouseInput({
13378
12538
  // src/tui/app/use-transcript-scroll.mjs
13379
12539
  import { useCallback as useCallback3, useEffect as useEffect8, useLayoutEffect as useLayoutEffect3, useRef as useRef8 } from "react";
13380
12540
 
13381
- // src/tui/engine/render-timing.mjs
12541
+ // src/tui/session/render-timing.mjs
13382
12542
  var RENDER_ACK_HANG_GUARD_MS = 250;
13383
12543
  var RENDER_SETTLE_IDLE_MS = 64;
13384
12544
  var TUI_RENDER_FPS = 120;
@@ -13397,7 +12557,7 @@ var notifyRenderFrame = (seq = ++renderAckSeq) => {
13397
12557
  pendingRenderAcks = [];
13398
12558
  for (const ack of acks) ack(seq);
13399
12559
  };
13400
- var yieldToRenderer = ({ frames = 1 } = {}) => new Promise((resolve5) => {
12560
+ var yieldToRenderer = ({ frames = 1 } = {}) => new Promise((resolve4) => {
13401
12561
  const minSeq = renderAckSeq;
13402
12562
  let remainingFrames = Math.max(1, Math.floor(Number(frames) || 1));
13403
12563
  let settled = false;
@@ -13409,7 +12569,7 @@ var yieldToRenderer = ({ frames = 1 } = {}) => new Promise((resolve5) => {
13409
12569
  if (timer2) clearTimeout(timer2);
13410
12570
  const idx = pendingRenderAcks.indexOf(onFrame);
13411
12571
  if (idx !== -1) pendingRenderAcks.splice(idx, 1);
13412
- resolve5();
12572
+ resolve4();
13413
12573
  };
13414
12574
  const onTimeout = () => {
13415
12575
  if (settled) return;
@@ -13470,7 +12630,7 @@ function useTranscriptScroll({
13470
12630
  const scrollAnimationRef = useRef8(null);
13471
12631
  const pendingReadbackRowsRef = useRef8(0);
13472
12632
  const selectionPaintRef = useRef8({ t: 0, rect: null, pending: null, timer: null });
13473
- const scrollCoalesceRef = useRef8({ pendingRows: 0, timer: null });
12633
+ const scrollCoalesceRef = useRef8({ pendingRows: 0, direction: 0, timer: null });
13474
12634
  const selectionTextCaptureRef = useRef8(0);
13475
12635
  const stitchBufferRef = useRef8(/* @__PURE__ */ new Map());
13476
12636
  const stitchHarvestTimerRef = useRef8(null);
@@ -13761,6 +12921,7 @@ function useTranscriptScroll({
13761
12921
  if (coalesceState.timer) clearTimeout(coalesceState.timer);
13762
12922
  coalesceState.timer = null;
13763
12923
  coalesceState.pendingRows = 0;
12924
+ coalesceState.direction = 0;
13764
12925
  }, []);
13765
12926
  const scrollTranscriptRows = useCallback3((deltaRows, options = {}) => {
13766
12927
  const maxTarget = Math.max(0, Number(maxScrollRowsRef.current) || 0);
@@ -13893,13 +13054,18 @@ function useTranscriptScroll({
13893
13054
  });
13894
13055
  const queueScrollCoalesced = useCallback3((deltaRows) => {
13895
13056
  const state = scrollCoalesceRef.current;
13896
- state.pendingRows += deltaRows;
13057
+ const reversed = accumulateDirectionalScrollDelta(state, deltaRows);
13058
+ if (reversed && state.timer) {
13059
+ clearTimeout(state.timer);
13060
+ state.timer = null;
13061
+ }
13897
13062
  if (state.timer) return;
13898
13063
  const rows = state.pendingRows;
13899
13064
  state.pendingRows = 0;
13900
13065
  scrollTranscriptRows(rows);
13901
13066
  state.timer = setTimeout(() => {
13902
13067
  state.timer = null;
13068
+ state.direction = 0;
13903
13069
  if (state.pendingRows !== 0) {
13904
13070
  const remaining = state.pendingRows;
13905
13071
  state.pendingRows = 0;
@@ -14121,17 +13287,18 @@ function usePromptHint() {
14121
13287
  const [promptHintTone, setPromptHintTone] = useState8("info");
14122
13288
  const promptHintTimerRef = useRef10(null);
14123
13289
  const promptHintActiveRef = useRef10(false);
14124
- const showPromptHint = useCallback5((text, tone = "info") => {
13290
+ const showPromptHint = useCallback5((text, tone = "info", dismissMs = PROMPT_HINT_DISMISS_MS) => {
14125
13291
  if (promptHintTimerRef.current) clearTimeout(promptHintTimerRef.current);
14126
13292
  promptHintActiveRef.current = true;
14127
13293
  setPromptHint(String(text || ""));
14128
13294
  setPromptHintTone(tone);
13295
+ const timeoutMs = Number(dismissMs) > 0 ? Number(dismissMs) : PROMPT_HINT_DISMISS_MS;
14129
13296
  promptHintTimerRef.current = setTimeout(() => {
14130
13297
  promptHintTimerRef.current = null;
14131
13298
  promptHintActiveRef.current = false;
14132
13299
  setPromptHint("");
14133
13300
  setPromptHintTone("info");
14134
- }, PROMPT_HINT_DISMISS_MS);
13301
+ }, timeoutMs);
14135
13302
  }, []);
14136
13303
  const showSelectionCopyHint = useCallback5(
14137
13304
  (text, tone = "plain") => showPromptHint(text, tone),
@@ -14204,11 +13371,11 @@ function compactJson(value, max = 180) {
14204
13371
  if (!text) return "";
14205
13372
  return stringWidth8(text) > max ? `${text.slice(0, Math.max(1, max - 1))}\u2026` : text;
14206
13373
  }
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);
13374
+ function toolApprovalDescription(request = {}) {
13375
+ const surface = formatToolSurface(request.name, request.args);
13376
+ const summary = surface?.summary ? `${surface.label || request.name} (${surface.summary})` : surface?.label || request.name || "tool";
13377
+ const reason = clean(request.reason) || "Hook requested approval.";
13378
+ const args = compactJson(request.args);
14212
13379
  return [
14213
13380
  summary ? `Tool: ${summary}` : "",
14214
13381
  reason ? `Reason: ${reason}` : "",
@@ -14515,8 +13682,8 @@ function createUsageContextPanels({
14515
13682
  const context = contextStatusValue || {};
14516
13683
  const usage = context.usage || {};
14517
13684
  const messages = context.messages || {};
14518
- const request2 = context.request || {};
14519
- const schemaBreakdown = request2.toolSchemaBreakdown || {};
13685
+ const request = context.request || {};
13686
+ const schemaBreakdown = request.toolSchemaBreakdown || {};
14520
13687
  const schemaTokensFor = (buckets) => buckets.reduce(
14521
13688
  (sum, bucket) => sum + Number(schemaBreakdown?.[bucket]?.tokens || 0),
14522
13689
  0
@@ -14602,7 +13769,7 @@ function createUsageContextPanels({
14602
13769
  {
14603
13770
  value: "request",
14604
13771
  label: "Request overhead",
14605
- description: `${fmt(request2.requestOverheadTokens)} framing \xB7 ${fmt(request2.reserveTokens)} reserve incl. tools`,
13772
+ description: `${fmt(request.requestOverheadTokens)} framing \xB7 ${fmt(request.reserveTokens)} reserve incl. tools`,
14606
13773
  _action: "request"
14607
13774
  },
14608
13775
  {
@@ -14678,9 +13845,9 @@ function createUsageContextPanels({
14678
13845
  results: messages.toolResultCount
14679
13846
  },
14680
13847
  request: {
14681
- toolSchemaBreakdown: request2.toolSchemaBreakdown,
14682
- overheadTokens: request2.requestOverheadTokens,
14683
- reserveTokens: request2.reserveTokens
13848
+ toolSchemaBreakdown: request.toolSchemaBreakdown,
13849
+ overheadTokens: request.requestOverheadTokens,
13850
+ reserveTokens: request.reserveTokens
14684
13851
  },
14685
13852
  lastApi: {
14686
13853
  contextTokens: usage.lastContextTokens,
@@ -14746,10 +13913,17 @@ function createPromptSubmit({
14746
13913
  pastedImagesRef,
14747
13914
  pastedTextsRef
14748
13915
  }) {
13916
+ const serviceCall = (name, ...args) => {
13917
+ const target = store?.[name];
13918
+ if (typeof target !== "function") {
13919
+ return Promise.reject(new TypeError(`project service method ${name} is unavailable`));
13920
+ }
13921
+ return Promise.resolve(target.apply(store, args));
13922
+ };
14749
13923
  const submitPrompt = (prompt, options) => {
14750
13924
  if (typeof store.submitAsync !== "function") return store.submit(prompt, options);
14751
13925
  void Promise.resolve(store.submitAsync(prompt, options)).then((accepted) => {
14752
- if (accepted === false) store.pushNotice("prompt was not accepted by the session backend", "error");
13926
+ if (accepted === false) store.pushNotice("prompt was not accepted by the session service", "error");
14753
13927
  }).catch((error) => {
14754
13928
  store.pushNotice(`prompt submit failed: ${error?.message || error}`, "error");
14755
13929
  });
@@ -14875,7 +14049,7 @@ function createPromptSubmit({
14875
14049
  const channelId = isPipe ? parts[1] : parts[0];
14876
14050
  Promise.resolve(store.setChannel({
14877
14051
  channelId,
14878
- backend: channelPrompt.backend
14052
+ provider: channelPrompt.provider
14879
14053
  })).then(() => resumeAfterChannelPrompt(channelPrompt)).catch((e) => {
14880
14054
  store.pushNotice(`channel save failed: ${e?.message || e}`, "error");
14881
14055
  });
@@ -14919,9 +14093,14 @@ function createPromptSubmit({
14919
14093
  store.pushNotice("working directory path is required", "warn");
14920
14094
  return false;
14921
14095
  }
14922
- store.setCwd?.(commandText, { message: `Project set: ${projectNameFromPath(commandText)}` });
14923
- setSettingsPrompt(null);
14924
- void openSettingsPicker();
14096
+ void serviceCall("setCwd", commandText, {
14097
+ message: `Project set: ${projectNameFromPath(commandText)}`
14098
+ }).then(() => {
14099
+ setSettingsPrompt(null);
14100
+ void openSettingsPicker();
14101
+ }).catch((error) => {
14102
+ store.pushNotice(`project switch failed: ${error?.message || error}`, "error");
14103
+ });
14925
14104
  return true;
14926
14105
  }
14927
14106
  if (settingsPrompt.kind === "project-new") {
@@ -14929,21 +14108,25 @@ function createPromptSubmit({
14929
14108
  store.pushNotice("project path is required", "warn");
14930
14109
  return false;
14931
14110
  }
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
14111
+ void serviceCall("inspectProjectPath", commandText).then((result) => {
14112
+ const path2 = String(result?.path || commandText);
14113
+ if (result?.directory === true) {
14114
+ setSettingsPrompt(null);
14115
+ void registerProject(path2);
14116
+ return;
14117
+ }
14118
+ if (result?.exists === true) {
14119
+ store.pushNotice(`${path2} is not a directory`, "warn");
14120
+ return;
14121
+ }
14122
+ setSettingsPrompt({
14123
+ kind: "project-create-confirm",
14124
+ label: "New project \xB7 Create folder?",
14125
+ hint: `${path2} does not exist. Type "y" to create it, or anything else to cancel.`,
14126
+ pendingPath: path2
14127
+ });
14128
+ }).catch((error) => {
14129
+ store.pushNotice(`project path check failed: ${error?.message || error}`, "error");
14947
14130
  });
14948
14131
  return true;
14949
14132
  }
@@ -14951,14 +14134,13 @@ function createPromptSubmit({
14951
14134
  const pendingPath = String(settingsPrompt.pendingPath || "");
14952
14135
  const answer = String(commandText || "").trim().toLowerCase();
14953
14136
  if (answer === "y" || answer === "yes") {
14954
- const created = ensureDir(pendingPath);
14955
- if (!created) {
14956
- store.pushNotice(`could not create folder: ${pendingPath}`, "error");
14137
+ void serviceCall("ensureProjectDirectory", pendingPath).then((created) => {
14957
14138
  setSettingsPrompt(null);
14958
- return true;
14959
- }
14960
- setSettingsPrompt(null);
14961
- registerProject(pendingPath);
14139
+ void registerProject(created || pendingPath);
14140
+ }).catch((error) => {
14141
+ store.pushNotice(`could not create folder: ${error?.message || error}`, "error");
14142
+ setSettingsPrompt(null);
14143
+ });
14962
14144
  return true;
14963
14145
  }
14964
14146
  setSettingsPrompt(null);
@@ -14967,16 +14149,15 @@ function createPromptSubmit({
14967
14149
  }
14968
14150
  if (settingsPrompt.kind === "project-rename") {
14969
14151
  const targetPath = String(settingsPrompt.projectPath || "");
14970
- try {
14971
- const updated = renameProject(targetPath, commandText);
14152
+ void serviceCall("renameProject", targetPath, commandText).then((updated) => {
14972
14153
  if (updated) {
14973
14154
  store.pushNotice(`project renamed to "${updated.name}"`, "info");
14974
14155
  }
14975
- } catch (e) {
14976
- store.pushNotice(`rename failed: ${e?.message || e}`, "error");
14977
- }
14978
- setSettingsPrompt(null);
14979
- openProjectPicker();
14156
+ setSettingsPrompt(null);
14157
+ void openProjectPicker();
14158
+ }).catch((error) => {
14159
+ store.pushNotice(`rename failed: ${error?.message || error}`, "error");
14160
+ });
14980
14161
  return true;
14981
14162
  }
14982
14163
  if (settingsPrompt.kind === "system-shell") {
@@ -16960,7 +16141,7 @@ var Item = React19.memo(function Item2({ item, prevKind, columns, toolOutputExpa
16960
16141
  // src/tui/app/app-view.jsx
16961
16142
  import { Fragment as Fragment6, jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
16962
16143
  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;
16144
+ 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
16145
  const promptInputControl = /* @__PURE__ */ jsx20(
16965
16146
  PromptInput,
16966
16147
  {
@@ -16984,6 +16165,7 @@ function renderAppView(ctx) {
16984
16165
  onPasteText: handlePromptPaste,
16985
16166
  onHistoryNavigate: handlePromptHistoryNavigate,
16986
16167
  commandPaletteActive: slashPaletteOpen && slashCommands.length > 0,
16168
+ commandPaletteOpen: slashPaletteOpen,
16987
16169
  onCommandPaletteNavigate: (direction) => {
16988
16170
  setSlashIndex((index) => {
16989
16171
  const total = slashCommands.length;
@@ -16998,7 +16180,9 @@ function renderAppView(ctx) {
16998
16180
  onCommandPaletteAccept: acceptSlashPalette,
16999
16181
  onCommandPaletteCancel: cancelSlashPalette,
17000
16182
  onCommandPaletteComplete: completeSlashPalette,
17001
- onRestoreQueued: (currentText) => restoreQueuedToPrompt({ restoreDraft: true, showHint: false, currentText })
16183
+ onRestoreQueued: restoreQueuedToPrompt,
16184
+ hasQueuedMessages: Array.isArray(state.queued) && state.queued.length > 0,
16185
+ hasMessages: hasUserMessages
17002
16186
  }
17003
16187
  );
17004
16188
  return (
@@ -17448,10 +16632,8 @@ function usePromptDraftFlow({
17448
16632
  return token ? `/${token} ` : void 0;
17449
16633
  }, [slashCommands, slashIndex]);
17450
16634
  const cancelSlashPalette = useCallback8((value = "") => {
17451
- setSlashDismissedFor("");
17452
- setPromptDraft("");
17453
- setPromptDraftOverride({ id: Date.now(), value: "" });
17454
- }, []);
16635
+ setSlashDismissedFor(String(value ?? ""));
16636
+ }, [setSlashDismissedFor]);
17455
16637
  return {
17456
16638
  onPromptDraftChange,
17457
16639
  cancelProviderPrompt,
@@ -17694,10 +16876,10 @@ function createResumePicker({
17694
16876
  formatSessionUpdatedAt: formatSessionUpdatedAt2,
17695
16877
  formatSessionMessageCount: formatSessionMessageCount2
17696
16878
  }) {
17697
- const openResumePicker = () => {
16879
+ const openResumePicker = async () => {
17698
16880
  let sessions;
17699
16881
  try {
17700
- sessions = store.listSessions({ refreshFromStorage: true });
16882
+ sessions = await store.listSessions({ refreshFromStorage: true });
17701
16883
  } catch (e) {
17702
16884
  store.pushNotice(`could not list saved chats: ${e?.message || e}`, "error");
17703
16885
  return;
@@ -17762,16 +16944,16 @@ function createCoreMemoryPicker({
17762
16944
  onCancel: closeMemoryCorePicker
17763
16945
  });
17764
16946
  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;
16947
+ const recap = await store.getRecapSettings?.() || { enabled: true };
16948
+ const recapOn = recap.enabled !== false;
17767
16949
  const coreRows = parseMemoryCoreRows2(result);
17768
16950
  const rows = [
17769
16951
  {
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"
16952
+ value: "recap-toggle",
16953
+ label: "Recap",
16954
+ meta: recapOn ? "On" : "Off",
16955
+ description: recapOn ? "Background session recaps on" : "Background recaps off; core memory remains available",
16956
+ _action: "toggle-recap"
17775
16957
  },
17776
16958
  { value: "core-add", label: "Add Memory", description: "store a new curated memory sentence", _action: "add-core" },
17777
16959
  {
@@ -17793,17 +16975,17 @@ function createCoreMemoryPicker({
17793
16975
  labelWidth: 12,
17794
16976
  footer: (item) => item && item._action === "core-entry" ? item._summary || item._element || "" : "",
17795
16977
  onSelect: (_value, item) => {
17796
- if (item?._action === "toggle-memory") toggleMemoryEnabled();
16978
+ if (item?._action === "toggle-recap") toggleRecapEnabled();
17797
16979
  else if (item?._action === "add-core") beginAddCoreMemory();
17798
16980
  else if (item?._action === "core-list") openCoreMemoryListPicker(item._rows);
17799
16981
  else if (item?._line) store.pushNotice(item._line, "info");
17800
16982
  },
17801
16983
  // Toggle rows also respond to ←/→ (matches settings/channels UX).
17802
16984
  onLeft: (item) => {
17803
- if (item?._action === "toggle-memory") toggleMemoryEnabled();
16985
+ if (item?._action === "toggle-recap") toggleRecapEnabled();
17804
16986
  },
17805
16987
  onRight: (item) => {
17806
- if (item?._action === "toggle-memory") toggleMemoryEnabled();
16988
+ if (item?._action === "toggle-recap") toggleRecapEnabled();
17807
16989
  },
17808
16990
  onCancel: closeMemoryCorePicker
17809
16991
  });
@@ -17844,12 +17026,12 @@ function createCoreMemoryPicker({
17844
17026
  store.pushNotice(`core memory failed: ${e?.message || e}`, "error");
17845
17027
  });
17846
17028
  };
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());
17029
+ const toggleRecapEnabled = async () => {
17030
+ const recap = await store.getRecapSettings?.() || { enabled: true };
17031
+ void Promise.resolve(store.setRecapEnabled?.(!(recap.enabled !== false))).then((next) => {
17032
+ if (!next) store.pushNotice("recap setting is busy", "warn");
17033
+ else store.pushNotice(`Recap ${next.enabled ? "on" : "off"}`, "info");
17034
+ }).catch((e) => store.pushNotice(`recap toggle failed: ${e?.message || e}`, "error")).finally(() => openMemoryCorePicker());
17853
17035
  };
17854
17036
  const openCoreEntryActionsPicker = (entryItem) => {
17855
17037
  setPicker({
@@ -19107,7 +18289,6 @@ function createOnboardingSteps({
19107
18289
  if (defaultRoute) {
19108
18290
  void store.completeOnboarding?.({
19109
18291
  defaultRoute,
19110
- defaultProvider: defaultRoute.provider,
19111
18292
  ...hasOverrides ? { agentRoutes: { ...overrides } } : {},
19112
18293
  ...searchRoute ? { searchRoute } : {}
19113
18294
  }).then(done).catch(failed);
@@ -19433,7 +18614,7 @@ function createChannelPickers({
19433
18614
  onboardingWarnReopen
19434
18615
  }) {
19435
18616
  let pickerGen = 0;
19436
- const openChannelTypeActionsPicker = async (backend, options = {}) => {
18617
+ const openChannelTypeActionsPicker = async (provider, options = {}) => {
19437
18618
  pickerGen += 1;
19438
18619
  const parentReturn = typeof options.returnTo === "function" ? options.returnTo : () => openChannelSettingTypePicker();
19439
18620
  setProviderPrompt(null);
@@ -19447,23 +18628,23 @@ function createChannelPickers({
19447
18628
  store.pushNotice(`channels failed: ${e?.message || e}`, "error");
19448
18629
  return;
19449
18630
  }
19450
- const isTelegram = backend === "telegram";
19451
- const activeBackend = setup.backend || "discord";
18631
+ const isTelegram = provider === "telegram";
18632
+ const activeProvider = setup.provider || "discord";
19452
18633
  const tokenDescription = isTelegram ? `${setup.telegram?.status ?? "Off"}${setup.telegram?.problem ? " \xB7 Invalid" : ""}` : `${setup.discord.status}${setup.discord.problem ? " \xB7 Invalid" : ""}`;
19453
18634
  const mainEntry = setup.channel || {};
19454
- const mainTarget = isTelegram ? mainEntry?.telegramChatId || (activeBackend === "telegram" ? mainEntry?.channelId : "") : mainEntry?.discordChannelId || (activeBackend === "discord" ? mainEntry?.channelId : "");
18635
+ const mainTarget = isTelegram ? mainEntry?.telegramChatId || (activeProvider === "telegram" ? mainEntry?.channelId : "") : mainEntry?.discordChannelId || (activeProvider === "discord" ? mainEntry?.channelId : "");
19455
18636
  const mainDescription = isTelegram ? mainTarget ? `Chat ID ${mainTarget}` : "Not set \xB7 Enter Telegram chat ID" : mainTarget ? `Channel ID ${mainTarget}` : "Not set \xB7 Enter Discord channel ID";
19456
18637
  const openChannelPrompt = (prompt) => {
19457
18638
  setPicker(null);
19458
18639
  setContextPanel(null);
19459
18640
  setChannelPrompt({
19460
18641
  ...prompt,
19461
- afterSave: () => openChannelTypeActionsPicker(backend, options)
18642
+ afterSave: () => openChannelTypeActionsPicker(provider, options)
19462
18643
  });
19463
18644
  };
19464
18645
  setPicker({
19465
18646
  title: isTelegram ? "Telegram" : "Discord",
19466
- description: activeBackend === backend ? "Active channel type \xB7 token and main target" : "Token and main target settings",
18647
+ description: activeProvider === provider ? "Active channel type \xB7 token and main target" : "Token and main target settings",
19467
18648
  help: "\u2191/\u2193 Select \xB7 Enter Edit \xB7 Esc Back",
19468
18649
  indexMode: "always",
19469
18650
  labelWidth: 18,
@@ -19502,7 +18683,7 @@ function createChannelPickers({
19502
18683
  if (item._action === "main-target") {
19503
18684
  openChannelPrompt({
19504
18685
  kind: "channel-add",
19505
- backend,
18686
+ provider,
19506
18687
  label: isTelegram ? "Main chat" : "Main channel",
19507
18688
  hint: isTelegram ? "Paste the Telegram chat ID." : "Paste the Discord channel ID."
19508
18689
  });
@@ -19532,15 +18713,15 @@ function createChannelPickers({
19532
18713
  store.pushNotice(`channels failed: ${e?.message || e}`, "error");
19533
18714
  return;
19534
18715
  }
19535
- const activeBackend = setup.backend || "discord";
18716
+ const activeProvider = setup.provider || "discord";
19536
18717
  const mainEntry = setup.channel || {};
19537
- const typeDescription = (backend) => {
19538
- const selected = activeBackend === backend;
19539
- const hasToken = backend === "telegram" ? setup.telegram?.authenticated === true : setup.discord?.authenticated === true;
19540
- const hasTarget = backend === "telegram" ? Boolean(mainEntry?.telegramChatId || activeBackend === "telegram" && mainEntry?.channelId) : Boolean(mainEntry?.discordChannelId || activeBackend === "discord" && mainEntry?.channelId);
18718
+ const typeDescription = (provider) => {
18719
+ const selected = activeProvider === provider;
18720
+ const hasToken = provider === "telegram" ? setup.telegram?.authenticated === true : setup.discord?.authenticated === true;
18721
+ const hasTarget = provider === "telegram" ? Boolean(mainEntry?.telegramChatId || activeProvider === "telegram" && mainEntry?.channelId) : Boolean(mainEntry?.discordChannelId || activeProvider === "discord" && mainEntry?.channelId);
19541
18722
  const needs = [
19542
18723
  ...hasToken ? [] : ["token"],
19543
- ...hasTarget ? [] : [backend === "telegram" ? "chat ID" : "channel ID"]
18724
+ ...hasTarget ? [] : [provider === "telegram" ? "chat ID" : "channel ID"]
19544
18725
  ];
19545
18726
  return [
19546
18727
  ...selected ? ["Selected"] : [],
@@ -19549,7 +18730,7 @@ function createChannelPickers({
19549
18730
  };
19550
18731
  setPicker({
19551
18732
  title: "Channel Type Settings",
19552
- description: "Choose a type. Selected is the active backend; Ready means token and main target are set.",
18733
+ description: "Choose a type. Selected is the active provider; Ready means token and main target are set.",
19553
18734
  help: "\u2191/\u2193 Select \xB7 Enter Open \xB7 Esc Back",
19554
18735
  indexMode: "always",
19555
18736
  labelWidth: 18,
@@ -19558,20 +18739,20 @@ function createChannelPickers({
19558
18739
  value: "discord",
19559
18740
  label: "Discord",
19560
18741
  description: typeDescription("discord"),
19561
- _backend: "discord"
18742
+ _provider: "discord"
19562
18743
  },
19563
18744
  {
19564
18745
  value: "telegram",
19565
18746
  label: "Telegram",
19566
18747
  description: typeDescription("telegram"),
19567
- _backend: "telegram"
18748
+ _provider: "telegram"
19568
18749
  }
19569
18750
  ],
19570
18751
  onSelect: (value, item) => {
19571
- const backend = item?._backend || (value === "telegram" ? "telegram" : value === "discord" ? "discord" : null);
19572
- if (!backend) return;
18752
+ const provider = item?._provider || (value === "telegram" ? "telegram" : value === "discord" ? "discord" : null);
18753
+ if (!provider) return;
19573
18754
  setPicker(null);
19574
- openChannelTypeActionsPicker(backend, {
18755
+ openChannelTypeActionsPicker(provider, {
19575
18756
  returnTo: () => openChannelSettingTypePicker(options)
19576
18757
  });
19577
18758
  },
@@ -19601,8 +18782,8 @@ function createChannelPickers({
19601
18782
  setChannelPrompt(prompt);
19602
18783
  };
19603
18784
  const worker = await store.getChannelWorkerStatus?.();
19604
- const activeBackend = setup.backend === "telegram" ? "telegram" : "discord";
19605
- const backendLabel = activeBackend === "telegram" ? "Telegram" : "Discord";
18785
+ const activeProvider = setup.provider === "telegram" ? "telegram" : "discord";
18786
+ const providerLabel = activeProvider === "telegram" ? "Telegram" : "Discord";
19606
18787
  const remoteEnabled = await store.isRemoteEnabled?.() === true;
19607
18788
  const boolLabel = (enabled) => enabled ? "On" : "Off";
19608
18789
  const reopenRoot = (extra = {}) => {
@@ -19612,23 +18793,23 @@ function createChannelPickers({
19612
18793
  const applyRemoteRuntime = (highlightValue = "remote-runtime") => {
19613
18794
  void Promise.resolve(store.toggleRemote?.()).then((enabled) => store.pushNotice(enabled === true ? "Remote mode ON" : "Remote mode OFF", "info")).catch((e) => store.pushNotice(`Remote toggle failed: ${e?.message || e}`, "error")).finally(() => reopenRoot({ highlightValue }));
19614
18795
  };
19615
- const cycleChannelBackend = (direction = 1, highlightValue = "channel-backend") => {
19616
- const backends = ["discord", "telegram"];
19617
- const currentIndex = Math.max(0, backends.indexOf(activeBackend));
19618
- const chosen = backends[(currentIndex + direction + backends.length) % backends.length];
19619
- if (chosen === activeBackend) {
19620
- reopenRoot({ highlightValue, backendOverride: activeBackend });
18796
+ const cycleChannelProvider = (direction = 1, highlightValue = "channel-provider") => {
18797
+ const providers = ["discord", "telegram"];
18798
+ const currentIndex = Math.max(0, providers.indexOf(activeProvider));
18799
+ const chosen = providers[(currentIndex + direction + providers.length) % providers.length];
18800
+ if (chosen === activeProvider) {
18801
+ reopenRoot({ highlightValue, providerOverride: activeProvider });
19621
18802
  return;
19622
18803
  }
19623
18804
  try {
19624
- store.setBackend(chosen);
18805
+ store.setChannelProvider(chosen);
19625
18806
  const label = chosen === "telegram" ? "Telegram" : "Discord";
19626
18807
  const restartHint = remoteEnabled || worker?.running ? `Channel set to ${label}. Restart remote to apply.` : `Channel set to ${label}.`;
19627
18808
  store.pushNotice(restartHint, "info");
19628
18809
  } catch (e) {
19629
- store.pushNotice(`channel backend failed: ${e?.message || e}`, "error");
18810
+ store.pushNotice(`channel provider failed: ${e?.message || e}`, "error");
19630
18811
  }
19631
- reopenRoot({ highlightValue, backendOverride: chosen });
18812
+ reopenRoot({ highlightValue, providerOverride: chosen });
19632
18813
  };
19633
18814
  const applyVoice = (highlightValue = "voice") => {
19634
18815
  if (isVoiceInstallBusy()) {
@@ -19649,11 +18830,11 @@ function createChannelPickers({
19649
18830
  _action: "remote-runtime"
19650
18831
  },
19651
18832
  {
19652
- value: "channel-backend",
18833
+ value: "channel-provider",
19653
18834
  label: "Channel",
19654
- meta: backendLabel,
18835
+ meta: providerLabel,
19655
18836
  description: "Select Discord or Telegram",
19656
- _action: "channel-backend"
18837
+ _action: "channel-provider"
19657
18838
  },
19658
18839
  {
19659
18840
  value: "channel-setting",
@@ -19688,18 +18869,18 @@ function createChannelPickers({
19688
18869
  indexMode: "always",
19689
18870
  labelWidth: 18,
19690
18871
  metaWidth: 12,
19691
- pickerKey: `channels:${activeBackend}:${remoteEnabled ? "on" : "off"}:${options.highlightValue || "root"}`,
18872
+ pickerKey: `channels:${activeProvider}:${remoteEnabled ? "on" : "off"}:${options.highlightValue || "root"}`,
19692
18873
  initialIndex: Math.max(0, items.findIndex((entry) => entry.value === options.highlightValue)),
19693
18874
  items,
19694
18875
  confirmBar: options.confirmBar || null,
19695
18876
  onLeft: options.confirmBar ? void 0 : (item) => {
19696
18877
  if (item?._action === "remote-runtime") applyRemoteRuntime(item.value);
19697
- else if (item?._action === "channel-backend") cycleChannelBackend(-1, item.value);
18878
+ else if (item?._action === "channel-provider") cycleChannelProvider(-1, item.value);
19698
18879
  else if (item?._action === "voice") applyVoice(item.value);
19699
18880
  },
19700
18881
  onRight: options.confirmBar ? void 0 : (item) => {
19701
18882
  if (item?._action === "remote-runtime") applyRemoteRuntime(item.value);
19702
- else if (item?._action === "channel-backend") cycleChannelBackend(1, item.value);
18883
+ else if (item?._action === "channel-provider") cycleChannelProvider(1, item.value);
19703
18884
  else if (item?._action === "voice") applyVoice(item.value);
19704
18885
  },
19705
18886
  onSelect: (_value, item) => {
@@ -19708,8 +18889,8 @@ function createChannelPickers({
19708
18889
  applyRemoteRuntime(item.value);
19709
18890
  return;
19710
18891
  }
19711
- if (item._action === "channel-backend") {
19712
- cycleChannelBackend(1, item.value);
18892
+ if (item._action === "channel-provider") {
18893
+ cycleChannelProvider(1, item.value);
19713
18894
  return;
19714
18895
  }
19715
18896
  if (item._action === "voice") {
@@ -19788,7 +18969,7 @@ function createProviderSetupPicker({
19788
18969
  }
19789
18970
  });
19790
18971
  try {
19791
- await new Promise((resolve5) => setTimeout(resolve5, 0));
18972
+ await new Promise((resolve4) => setTimeout(resolve4, 0));
19792
18973
  setup = await store.getProviderSetup();
19793
18974
  } catch (e) {
19794
18975
  store.pushNotice(`providers failed: ${e?.message || e}`, "error");
@@ -20327,7 +19508,7 @@ function createModelPicker({
20327
19508
  items: [],
20328
19509
  onCancel: cancelModelPicker
20329
19510
  });
20330
- await new Promise((resolve5) => setTimeout(resolve5, 0));
19511
+ await new Promise((resolve4) => setTimeout(resolve4, 0));
20331
19512
  try {
20332
19513
  if (options.refreshModels !== true && options.cacheRef !== "search") {
20333
19514
  refreshModelsPromise = Promise.resolve(loadModels({ force: false }));
@@ -20682,7 +19863,7 @@ function createRoutePickers({
20682
19863
  const items = agents.map((agent) => ({
20683
19864
  value: agent.id,
20684
19865
  label: agent.label,
20685
- metaParts: agentModelParts2(routeOverrides[agent.id] || agent.route),
19866
+ metaParts: routeOverrides[agent.id] || agent.route ? agentModelParts2(routeOverrides[agent.id] || agent.route) : [{ text: "(follows main)", width: 17 }, { text: "", width: 6 }, { text: "", width: 4 }],
20686
19867
  description: agent.description || agent.definition?.description || "",
20687
19868
  _agent: agent
20688
19869
  }));
@@ -20909,7 +20090,7 @@ function createSettingsPicker({
20909
20090
  const snapshot = await store.getSettingsSnapshot?.({ heavy: !heavyCache }) || {};
20910
20091
  const autoClear = snapshot.autoClear || {};
20911
20092
  const compaction = snapshot.compaction || {};
20912
- const memory = snapshot.memory || { enabled: true };
20093
+ const recap = snapshot.recap || { enabled: true };
20913
20094
  const channels = snapshot.channels || { enabled: true };
20914
20095
  const systemShell = snapshot.systemShell || { source: "auto", command: "", effective: "" };
20915
20096
  const outputStyle = snapshot.outputStyle || {};
@@ -20919,17 +20100,17 @@ function createSettingsPicker({
20919
20100
  const plugins = heavyCache ? heavyCache.plugins : snapshot.plugins || { count: 0 };
20920
20101
  const skills = heavyCache ? heavyCache.skills : snapshot.skills || { count: 0 };
20921
20102
  const channelWorker = snapshot.channelWorker || null;
20922
- const channelBackend = heavyCache ? heavyCache.channelBackend || "discord" : snapshot.channelBackend || "discord";
20923
- const effectiveChannelBackend = overrides && Object.prototype.hasOwnProperty.call(overrides, "channelBackend") ? overrides.channelBackend : channelBackend;
20924
- settingsHeavyCacheRef.current = { mcp, hooks, plugins, skills, channelBackend: effectiveChannelBackend };
20925
- const channelBackendLabel = effectiveChannelBackend === "telegram" ? "Telegram" : "Discord";
20103
+ const channelProvider = heavyCache ? heavyCache.channelProvider || "discord" : snapshot.channelProvider || "discord";
20104
+ const effectiveChannelProvider = overrides && Object.prototype.hasOwnProperty.call(overrides, "channelProvider") ? overrides.channelProvider : channelProvider;
20105
+ settingsHeavyCacheRef.current = { mcp, hooks, plugins, skills, channelProvider: effectiveChannelProvider };
20106
+ const channelProviderLabel = effectiveChannelProvider === "telegram" ? "Telegram" : "Discord";
20926
20107
  const remoteEnabled = snapshot.remoteEnabled === true;
20927
20108
  const remoteRuntimeDescription = channelWorker?.running ? `runtime running \xB7 pid ${channelWorker.pid}` : "Stopped. Manual ON claims remote from any other session.";
20928
20109
  const compactTypeLabel = "Fast-track (fixed)";
20929
20110
  const outputStyleLabel = outputStyle?.current?.label || outputStyle?.current?.id || outputStyle?.configured || "Default";
20930
20111
  const workflowLabel = workflowDisplayName2(workflow);
20931
20112
  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.";
20113
+ const compactTypeDescription = "Uses Memory recall to rebuild context faster on large histories.";
20933
20114
  const applyAutoClear = (patch = {}) => {
20934
20115
  void Promise.resolve(store.setAutoClear?.(patch)).then((next) => {
20935
20116
  if (!next) store.pushNotice("autoclear unavailable", "warn");
@@ -21033,23 +20214,23 @@ function createSettingsPicker({
21033
20214
  void openSettingsPicker({ light: true });
21034
20215
  });
21035
20216
  };
21036
- const cycleChannelBackend = (direction = 1) => {
21037
- const backends = ["discord", "telegram"];
21038
- const currentIndex = Math.max(0, backends.indexOf(effectiveChannelBackend));
21039
- const chosen = backends[(currentIndex + direction + backends.length) % backends.length];
21040
- if (chosen === effectiveChannelBackend) {
21041
- openSettingsPicker({ light: true, overrides: { channelBackend: effectiveChannelBackend } });
20217
+ const cycleChannelProvider = (direction = 1) => {
20218
+ const providers = ["discord", "telegram"];
20219
+ const currentIndex = Math.max(0, providers.indexOf(effectiveChannelProvider));
20220
+ const chosen = providers[(currentIndex + direction + providers.length) % providers.length];
20221
+ if (chosen === effectiveChannelProvider) {
20222
+ openSettingsPicker({ light: true, overrides: { channelProvider: effectiveChannelProvider } });
21042
20223
  return;
21043
20224
  }
21044
20225
  try {
21045
- store.setBackend(chosen);
20226
+ store.setChannelProvider(chosen);
21046
20227
  const label = chosen === "telegram" ? "Telegram" : "Discord";
21047
20228
  const restartHint = remoteEnabled || channelWorker?.running ? `Channel set to ${label}. Restart remote to apply.` : `Channel set to ${label}.`;
21048
20229
  store.pushNotice(restartHint, "info");
21049
20230
  } catch (e) {
21050
- store.pushNotice(`channel backend failed: ${e?.message || e}`, "error");
20231
+ store.pushNotice(`channel provider failed: ${e?.message || e}`, "error");
21051
20232
  }
21052
- openSettingsPicker({ light: true, overrides: { channelBackend: chosen } });
20233
+ openSettingsPicker({ light: true, overrides: { channelProvider: chosen } });
21053
20234
  };
21054
20235
  const items = [
21055
20236
  {
@@ -21130,8 +20311,8 @@ function createSettingsPicker({
21130
20311
  {
21131
20312
  value: "memory",
21132
20313
  label: "Memory",
21133
- meta: boolLabel(memory.enabled !== false),
21134
- description: memory.enabled === false ? "Memory off. Core memories stay editable." : "Session memory and core memories.",
20314
+ meta: `Recap ${boolLabel(recap.enabled !== false)}`,
20315
+ description: recap.enabled === false ? "Background recap off. Core memories and on-demand recall stay available." : "Background recap and core memories.",
21135
20316
  _action: "memory"
21136
20317
  },
21137
20318
  {
@@ -21174,11 +20355,11 @@ function createSettingsPicker({
21174
20355
  _action: "channels"
21175
20356
  },
21176
20357
  {
21177
- value: "channel-backend",
20358
+ value: "channel-provider",
21178
20359
  label: "Channel",
21179
- meta: channelBackendLabel,
20360
+ meta: channelProviderLabel,
21180
20361
  description: "Left/Right or Enter changes channel type (Discord or Telegram).",
21181
- _action: "channel-backend"
20362
+ _action: "channel-provider"
21182
20363
  },
21183
20364
  {
21184
20365
  value: "channel-setting",
@@ -21232,7 +20413,7 @@ function createSettingsPicker({
21232
20413
  else if (item?._action === "autocompact") applyCompaction({ auto: !(compaction.auto !== false) });
21233
20414
  else if (item?._action === "channels") applyChannels(!(channels.enabled !== false));
21234
20415
  else if (item?._action === "remote-runtime") applyRemoteRuntime();
21235
- else if (item?._action === "channel-backend") cycleChannelBackend(-1);
20416
+ else if (item?._action === "channel-provider") cycleChannelProvider(-1);
21236
20417
  else if (item?._action === "output-style") cycleOutputStyle(-1);
21237
20418
  else if (item?._action === "theme") cycleTheme(-1);
21238
20419
  else if (item?._action === "workflow") cycleWorkflow(-1);
@@ -21242,7 +20423,7 @@ function createSettingsPicker({
21242
20423
  else if (item?._action === "autocompact") applyCompaction({ auto: !(compaction.auto !== false) });
21243
20424
  else if (item?._action === "channels") applyChannels(!(channels.enabled !== false));
21244
20425
  else if (item?._action === "remote-runtime") applyRemoteRuntime();
21245
- else if (item?._action === "channel-backend") cycleChannelBackend(1);
20426
+ else if (item?._action === "channel-provider") cycleChannelProvider(1);
21246
20427
  else if (item?._action === "output-style") cycleOutputStyle(1);
21247
20428
  else if (item?._action === "theme") cycleTheme(1);
21248
20429
  else if (item?._action === "workflow") cycleWorkflow(1);
@@ -21253,7 +20434,7 @@ function createSettingsPicker({
21253
20434
  else if (item._action === "autocompact") applyCompaction({ auto: !(compaction.auto !== false) });
21254
20435
  else if (item._action === "channels") applyChannels(!(channels.enabled !== false));
21255
20436
  else if (item._action === "remote-runtime") applyRemoteRuntime();
21256
- else if (item._action === "channel-backend") cycleChannelBackend(1);
20437
+ else if (item._action === "channel-provider") cycleChannelProvider(1);
21257
20438
  else if (item._action === "channel-setting") openChannelSettingTypePicker({ returnTo: openSettingsPicker });
21258
20439
  else if (item._action === "output-style") openOutputStylePicker({ returnTo: openSettingsPicker });
21259
20440
  else if (item._action === "theme") openThemePicker({ returnTo: openSettingsPicker });
@@ -21898,6 +21079,13 @@ function createAppPickers({
21898
21079
 
21899
21080
  // src/tui/app/use-prompt-queue-history.mjs
21900
21081
  import { useCallback as useCallback9, useMemo as useMemo2 } from "react";
21082
+
21083
+ // src/tui/components/prompt-input/restore-policy.mjs
21084
+ function mergeQueuedRestoreText(queuedText = "", currentText = "") {
21085
+ return [String(queuedText ?? ""), String(currentText ?? "")].filter((text) => text.trim()).join("\n");
21086
+ }
21087
+
21088
+ // src/tui/app/use-prompt-queue-history.mjs
21901
21089
  function usePromptQueueHistory({
21902
21090
  store,
21903
21091
  state,
@@ -21930,8 +21118,8 @@ function usePromptQueueHistory({
21930
21118
  let timer2 = null;
21931
21119
  Promise.race([
21932
21120
  Promise.resolve(store.dispose?.("cli-react-exit", { detach: true })),
21933
- new Promise((resolve5) => {
21934
- timer2 = setTimeout(resolve5, 350);
21121
+ new Promise((resolve4) => {
21122
+ timer2 = setTimeout(resolve4, 350);
21935
21123
  })
21936
21124
  ]).finally(() => {
21937
21125
  if (timer2) clearTimeout(timer2);
@@ -21951,8 +21139,12 @@ function usePromptQueueHistory({
21951
21139
  if (restoreDraft) {
21952
21140
  if (restored2.pastedImages) installPastedImages(restored2.pastedImages, { merge: true });
21953
21141
  if (restored2.pastedTexts) installPastedTexts(restored2.pastedTexts, { merge: true });
21954
- syncPromptLayoutRows(restored2.text);
21955
- setPromptDraftOverride({ id: Date.now(), value: restored2.text });
21142
+ const restoredText = mergeQueuedRestoreText(
21143
+ restored2.text,
21144
+ promptValueRef.current ?? currentText
21145
+ );
21146
+ syncPromptLayoutRows(restoredText);
21147
+ setPromptDraftOverride({ id: Date.now(), value: restoredText });
21956
21148
  }
21957
21149
  if (showHint) {
21958
21150
  showPromptHint(`restored ${restored2.count} queued message${restored2.count === 1 ? "" : "s"}`, "info");
@@ -21961,7 +21153,7 @@ function usePromptQueueHistory({
21961
21153
  }
21962
21154
  return true;
21963
21155
  };
21964
- const restored = store.restoreQueued?.(currentText);
21156
+ const restored = store.restoreQueued?.("");
21965
21157
  if (restored && typeof restored.then === "function") {
21966
21158
  const queuedCount = Array.isArray(state?.queued) ? state.queued.length : 0;
21967
21159
  void Promise.resolve(restored).then(apply).catch(() => {
@@ -21993,6 +21185,88 @@ function usePromptQueueHistory({
21993
21185
  return { requestExit, restoreQueuedToPrompt, recentPromptHistory, resetPromptHistoryNav };
21994
21186
  }
21995
21187
 
21188
+ // src/tui/app/message-selector.mjs
21189
+ import { useCallback as useCallback10, useMemo as useMemo3 } from "react";
21190
+ var MAX_SELECTABLE_MESSAGES = 20;
21191
+ function selectableUserItems(items, limit = MAX_SELECTABLE_MESSAGES) {
21192
+ const source = Array.isArray(items) ? items : [];
21193
+ const rows = [];
21194
+ for (let index = source.length - 1; index >= 0 && rows.length < limit; index -= 1) {
21195
+ const item = source[index];
21196
+ if (item?.kind !== "user") continue;
21197
+ if (item?.id == null) continue;
21198
+ const text = String(item.text || "").trim();
21199
+ if (!text) continue;
21200
+ rows.push({ id: String(item.id), text });
21201
+ }
21202
+ return rows.reverse();
21203
+ }
21204
+ function messageSelectorLabel(text, width = 56) {
21205
+ const firstLine = String(text || "").split("\n").map((line) => line.trim()).find(Boolean) || "";
21206
+ if (firstLine.length <= width) return firstLine;
21207
+ return `${firstLine.slice(0, Math.max(1, width - 1))}\u2026`;
21208
+ }
21209
+ function useMessageSelector({
21210
+ store,
21211
+ state,
21212
+ setPicker,
21213
+ setPromptDraftOverride,
21214
+ syncPromptLayoutRows,
21215
+ showPromptHint,
21216
+ clearPromptHint
21217
+ }) {
21218
+ const hasUserMessages = useMemo3(
21219
+ () => (Array.isArray(state.items) ? state.items : []).some(
21220
+ (item) => item?.kind === "user" && String(item?.text || "").trim()
21221
+ ),
21222
+ [state.items]
21223
+ );
21224
+ const applyRewind = useCallback10((restored) => {
21225
+ const text = String(restored?.text || "");
21226
+ if (!text) {
21227
+ showPromptHint("Could not restore that message.", "error");
21228
+ return false;
21229
+ }
21230
+ clearPromptHint();
21231
+ syncPromptLayoutRows(text);
21232
+ setPromptDraftOverride({ id: Date.now(), value: text });
21233
+ return true;
21234
+ }, [clearPromptHint, syncPromptLayoutRows, setPromptDraftOverride, showPromptHint]);
21235
+ const openMessageSelector = useCallback10(() => {
21236
+ const items = store.getState?.().items ?? state.items;
21237
+ const rows = selectableUserItems(items);
21238
+ if (rows.length === 0) {
21239
+ showPromptHint("No message to jump back to.", "info");
21240
+ return false;
21241
+ }
21242
+ setPicker({
21243
+ _kind: "message-selector",
21244
+ title: "Jump back to a message",
21245
+ description: "Rewind the conversation to a previous prompt and edit it.",
21246
+ initialIndex: rows.length - 1,
21247
+ items: rows.map((row) => ({ value: row.id, label: messageSelectorLabel(row.text) })),
21248
+ onSelect: (value) => {
21249
+ setPicker(null);
21250
+ let restored;
21251
+ try {
21252
+ restored = store.rewindToItem?.(value);
21253
+ } catch (error) {
21254
+ showPromptHint(`rewind failed: ${error?.message || error}`, "error");
21255
+ return;
21256
+ }
21257
+ if (restored && typeof restored.then === "function") {
21258
+ void Promise.resolve(restored).then(applyRewind).catch((error) => showPromptHint(`rewind failed: ${error?.message || error}`, "error"));
21259
+ return;
21260
+ }
21261
+ applyRewind(restored);
21262
+ },
21263
+ onCancel: () => setPicker(null)
21264
+ });
21265
+ return true;
21266
+ }, [store, state.items, setPicker, applyRewind, showPromptHint]);
21267
+ return { hasUserMessages, openMessageSelector };
21268
+ }
21269
+
21996
21270
  // src/tui/app/use-terminal-chrome.mjs
21997
21271
  import { useEffect as useEffect11 } from "react";
21998
21272
  function useTerminalChrome({ stdout: stdout2, isRawModeSupported, setResizeState }) {
@@ -22042,7 +21316,7 @@ function useTerminalChrome({ stdout: stdout2, isRawModeSupported, setResizeState
22042
21316
  }
22043
21317
 
22044
21318
  // 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";
21319
+ import { useCallback as useCallback11, useEffect as useEffect12, useLayoutEffect as useLayoutEffect4, useMemo as useMemo4, useRef as useRef12, useState as useState10 } from "react";
22046
21320
  var HARVEST_BUMP_STREAK_LIMIT = 10;
22047
21321
  var TUI_DEBUG = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_TUI_DEBUG || ""));
22048
21322
  var harvestBreakerLogged = false;
@@ -22133,7 +21407,7 @@ function useTranscriptWindow({
22133
21407
  useLayoutEffect4(() => {
22134
21408
  if (scrollOffset !== 0) setScrollOffset(0);
22135
21409
  }, [normalizedSessionKey, setScrollOffset]);
22136
- const transcriptMeasureRef = useCallback10((item) => {
21410
+ const transcriptMeasureRef = useCallback11((item) => {
22137
21411
  if (!TRANSCRIPT_MEASURED_ROWS || !item || item.id == null) return void 0;
22138
21412
  if (shouldSuppressFullyFailedToolItem(item)) {
22139
21413
  transcriptMeasuredRowsCache.delete(item);
@@ -22157,8 +21431,6 @@ function useTranscriptWindow({
22157
21431
  return fn;
22158
21432
  }, []);
22159
21433
  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
21434
  const tailRows = streamingTailItem ? estimateTranscriptItemRowsCached(streamingTailItem, frameColumns, toolOutputExpanded) : 0;
22163
21435
  const tailSig = streamingTailItem ? `${streamingTailItem.id}:${tailRows}` : "_";
22164
21436
  const revision = Math.max(0, Number(structureRevision) || 0);
@@ -22174,13 +21446,8 @@ function useTranscriptWindow({
22174
21446
  revision,
22175
21447
  streamingTailItem
22176
21448
  );
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, {
21449
+ const suppressMeasuredRowHeights = false;
21450
+ const transcriptRowIndex = useMemo4(() => buildTranscriptRowIndexIncremental(transcriptItems, {
22184
21451
  columns: frameColumns,
22185
21452
  toolOutputExpanded,
22186
21453
  suppressMeasuredRowHeights,
@@ -22279,7 +21546,7 @@ function useTranscriptWindow({
22279
21546
  contentHeight: lockViewRows,
22280
21547
  floatingPanelRows: Number(floatingPanelRows) || 0
22281
21548
  };
22282
- const transcriptWindow = useMemo3(() => transcriptRenderWindow(transcriptItems, {
21549
+ const transcriptWindow = useMemo4(() => transcriptRenderWindow(transcriptItems, {
22283
21550
  scrollOffset: renderScrollOffset,
22284
21551
  viewportHeight: transcriptContentHeight,
22285
21552
  columns: frameColumns,
@@ -22461,7 +21728,7 @@ function useTranscriptWindow({
22461
21728
  const currentPosition = Math.max(0, Number(scrollPositionRef.current) || 0);
22462
21729
  const currentOffset = Math.max(0, Number(scrollOffset) || 0);
22463
21730
  const maxRows = Math.max(0, Number(transcriptWindow.maxScrollRows) || 0);
22464
- if (!followingRef.current && rowDelta > 0 && currentTarget === 0) {
21731
+ if (!followingRef.current && rowDelta >= 0 && currentTarget === 0) {
22465
21732
  followingRef.current = true;
22466
21733
  }
22467
21734
  const nearBottom = followingRef.current;
@@ -22514,6 +21781,16 @@ function useTranscriptWindow({
22514
21781
  maxRows
22515
21782
  });
22516
21783
  if (desired == null) {
21784
+ if (rowDelta < 0 || currentTarget > maxRows) {
21785
+ stopSmoothScroll();
21786
+ transcriptAnchorRef.current = null;
21787
+ transcriptAnchorDirtyRef.current = false;
21788
+ scrollTargetRef.current = 0;
21789
+ scrollPositionRef.current = 0;
21790
+ followingRef.current = true;
21791
+ if (currentOffset !== 0) setScrollOffset(0);
21792
+ return;
21793
+ }
22517
21794
  transcriptAnchorDirtyRef.current = true;
22518
21795
  return;
22519
21796
  }
@@ -22560,6 +21837,11 @@ function useTranscriptWindow({
22560
21837
  }, [themeEpoch, withSelectionClip, paintSelectionRect]);
22561
21838
  useEffect12(() => {
22562
21839
  const maxRows = Math.max(0, Number(transcriptWindow.maxScrollRows) || 0);
21840
+ if (maxRows === 0 && !followingRef.current) {
21841
+ transcriptAnchorRef.current = null;
21842
+ transcriptAnchorDirtyRef.current = false;
21843
+ followingRef.current = true;
21844
+ }
22563
21845
  if (scrollTargetRef.current <= maxRows && scrollPositionRef.current <= maxRows && scrollOffset <= maxRows) return;
22564
21846
  stopSmoothScroll();
22565
21847
  const next = Math.max(0, Math.min(maxRows, scrollTargetRef.current));
@@ -22589,42 +21871,49 @@ function createProjectPicker({
22589
21871
  setSettingsPrompt,
22590
21872
  setContextPanel,
22591
21873
  closeUsagePanel,
22592
- listProjects: listProjects2,
22593
- addProject: addProject2,
22594
- touchProjectSelected: touchProjectSelected2,
22595
- resolveProjectPath: resolveProjectPath2,
22596
21874
  projectNameFromPath: projectNameFromPath3,
22597
21875
  pickFolder: pickFolder2
22598
21876
  }) {
22599
- const buildProjectPickerState = ({ initialEntry = false } = {}) => {
22600
- let projects = [];
22601
- try {
22602
- projects = listProjects2() || [];
22603
- } catch {
22604
- projects = [];
21877
+ const serviceCall = (name, ...args) => {
21878
+ const target = store?.[name];
21879
+ if (typeof target !== "function") {
21880
+ return Promise.reject(new TypeError(`project service method ${name} is unavailable`));
22605
21881
  }
21882
+ return Promise.resolve(target.apply(store, args));
21883
+ };
21884
+ const buildProjectPickerState = ({
21885
+ initialEntry = false,
21886
+ projects = [],
21887
+ loading = false,
21888
+ requestId = null
21889
+ } = {}) => {
22606
21890
  const currentPath = String(state.cwd || process.cwd() || "");
22607
21891
  const items = [];
22608
- for (const project of projects) {
22609
- if (!project?.path) continue;
21892
+ if (!loading) {
21893
+ for (const project of projects) {
21894
+ if (!project?.path) continue;
21895
+ items.push({
21896
+ value: project.path,
21897
+ label: project.name || project.path,
21898
+ meta: project.path,
21899
+ _project: project
21900
+ });
21901
+ }
22610
21902
  items.push({
22611
- value: project.path,
22612
- label: project.name || project.path,
22613
- meta: project.path,
22614
- _project: project
21903
+ value: "__use_current__",
21904
+ label: "Current Path",
21905
+ meta: currentPath,
21906
+ _action: "current"
22615
21907
  });
22616
21908
  }
22617
- items.push({
22618
- value: "__use_current__",
22619
- label: "Current Path",
22620
- meta: currentPath,
22621
- _action: "current"
22622
- });
22623
21909
  return {
22624
21910
  kind: "project",
21911
+ _kind: "project",
21912
+ _projectRequestId: requestId,
21913
+ _projectInitialPending: loading && initialEntry,
22625
21914
  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",
21915
+ description: loading ? "Loading projects from the session\u2026" : "Choose a project.",
21916
+ help: loading ? "Waiting for the project service\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
21917
  indexMode: "always",
22629
21918
  labelWidth: 18,
22630
21919
  metaWidth: 40,
@@ -22635,20 +21924,11 @@ function createProjectPicker({
22635
21924
  return;
22636
21925
  }
22637
21926
  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
- }
21927
+ void enterProject(currentPath, { notice: !initialEntry, register: false });
22647
21928
  return;
22648
21929
  }
22649
- setPicker(null);
22650
21930
  const project = item?._project;
22651
- if (project?.path) enterProject(project.path, { notice: !initialEntry });
21931
+ if (project?.path) void enterProject(project.path, { notice: !initialEntry });
22652
21932
  },
22653
21933
  onKey: (input, _key, item) => {
22654
21934
  if (input === "c" || input === "C") {
@@ -22707,40 +21987,44 @@ function createProjectPicker({
22707
21987
  openProjectPicker();
22708
21988
  return;
22709
21989
  }
22710
- registerProject(result.path);
21990
+ void registerProject(result.path);
22711
21991
  }).catch(() => {
22712
21992
  beginNewProjectManual();
22713
21993
  });
22714
21994
  };
22715
- const registerProject = (rawPath) => {
22716
- const path3 = resolveProjectPath2(rawPath);
22717
- if (!path3) {
21995
+ const registerProject = async (rawPath) => {
21996
+ const path2 = String(rawPath || "").trim();
21997
+ if (!path2) {
22718
21998
  store.pushNotice("project path is required", "warn");
22719
- return;
21999
+ return false;
22720
22000
  }
22721
22001
  try {
22722
- const project = addProject2(path3);
22002
+ const project = await serviceCall("addProject", path2);
22723
22003
  if (project?.name) store.pushNotice(`project added: ${project.name}`, "info");
22724
- openProjectPicker();
22004
+ await openProjectPicker();
22005
+ return true;
22725
22006
  } catch (e) {
22726
22007
  store.pushNotice(`project add failed: ${e?.message || e}`, "error");
22008
+ return false;
22727
22009
  }
22728
22010
  };
22729
- const enterProject = (rawPath, options = {}) => {
22730
- const path3 = resolveProjectPath2(rawPath);
22731
- if (!path3) {
22011
+ const enterProject = async (rawPath, options = {}) => {
22012
+ const path2 = String(rawPath || "").trim();
22013
+ if (!path2) {
22732
22014
  store.pushNotice("project path is required", "warn");
22733
- return;
22015
+ return false;
22734
22016
  }
22017
+ setPicker(null);
22735
22018
  try {
22736
- store.setCwd?.(path3, {
22019
+ const resolved = await serviceCall("setCwd", path2, {
22737
22020
  notice: options?.notice !== false,
22738
- message: `Project set: ${projectNameFromPath3(path3)}`
22021
+ message: `Project set: ${projectNameFromPath3(path2)}`
22739
22022
  });
22740
- addProject2(path3);
22741
- touchProjectSelected2(path3);
22023
+ if (options?.register !== false) await serviceCall("addProject", resolved || path2);
22024
+ return true;
22742
22025
  } catch (e) {
22743
22026
  store.pushNotice(`project switch failed: ${e?.message || e}`, "error");
22027
+ return false;
22744
22028
  }
22745
22029
  };
22746
22030
  const beginRenameProject = (project) => {
@@ -22759,14 +22043,32 @@ function createProjectPicker({
22759
22043
  initialValue: project.name || ""
22760
22044
  });
22761
22045
  };
22762
- const openProjectPicker = () => {
22046
+ const openProjectPicker = async ({ initialEntry = false } = {}) => {
22763
22047
  setProviderPrompt(null);
22764
22048
  setChannelPrompt(null);
22765
22049
  setHookPrompt(null);
22766
22050
  setSettingsPrompt(null);
22767
22051
  setContextPanel(null);
22768
22052
  closeUsagePanel();
22769
- setPicker(buildProjectPickerState());
22053
+ const requestId = /* @__PURE__ */ Symbol("project-picker-request");
22054
+ setPicker(buildProjectPickerState({
22055
+ initialEntry,
22056
+ loading: true,
22057
+ requestId
22058
+ }));
22059
+ try {
22060
+ const projects = await serviceCall("listProjects");
22061
+ setPicker((current) => current?._projectRequestId === requestId ? buildProjectPickerState({
22062
+ initialEntry,
22063
+ projects: Array.isArray(projects) ? projects : [],
22064
+ requestId
22065
+ }) : current);
22066
+ return projects;
22067
+ } catch (error) {
22068
+ setPicker((current) => current?._projectRequestId === requestId ? buildProjectPickerState({ initialEntry, projects: [], requestId }) : current);
22069
+ store.pushNotice(`project list failed: ${error?.message || error}`, "error");
22070
+ return [];
22071
+ }
22770
22072
  };
22771
22073
  return {
22772
22074
  buildProjectPickerState,
@@ -22782,20 +22084,20 @@ function createProjectPicker({
22782
22084
  // src/tui/app/use-prompt-handlers.mjs
22783
22085
  init_paste_attachments();
22784
22086
  init_paste_attachments();
22785
- import { useCallback as useCallback11 } from "react";
22087
+ import { useCallback as useCallback12, useEffect as useEffect13, useRef as useRef13 } from "react";
22786
22088
 
22787
22089
  // 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";
22090
+ import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "node:fs";
22789
22091
  init_plugin_paths();
22790
- import { dirname as dirname7, join as join7, resolve as resolve4 } from "node:path";
22092
+ import { dirname as dirname6, join as join6, resolve as resolve3 } from "node:path";
22791
22093
  var PROMPT_HISTORY_LIMIT2 = 50;
22792
22094
  function promptHistoryKey2(value) {
22793
22095
  return String(value || "").trim().replace(/\s+/g, " ");
22794
22096
  }
22795
22097
  function readEntries(filePath) {
22796
- if (!filePath || !existsSync7(filePath)) return [];
22098
+ if (!filePath || !existsSync6(filePath)) return [];
22797
22099
  try {
22798
- const parsed = JSON.parse(readFileSync6(filePath, "utf8"));
22100
+ const parsed = JSON.parse(readFileSync5(filePath, "utf8"));
22799
22101
  const entries = Array.isArray(parsed?.entries) ? parsed.entries : [];
22800
22102
  return entries.map((entry) => String(entry || "").trim()).filter((entry) => promptHistoryKey2(entry));
22801
22103
  } catch {
@@ -22809,10 +22111,10 @@ function buildPayload(entries) {
22809
22111
  function writeEntriesSync(filePath, entries) {
22810
22112
  if (!filePath) return false;
22811
22113
  try {
22812
- mkdirSync6(dirname7(filePath), { recursive: true, mode: 448 });
22114
+ mkdirSync5(dirname6(filePath), { recursive: true, mode: 448 });
22813
22115
  const tmp = `${filePath}.${process.pid}.tmp`;
22814
- writeFileSync5(tmp, buildPayload(entries), { encoding: "utf8", mode: 384 });
22815
- renameSync3(tmp, filePath);
22116
+ writeFileSync4(tmp, buildPayload(entries), { encoding: "utf8", mode: 384 });
22117
+ renameSync2(tmp, filePath);
22816
22118
  try {
22817
22119
  chmodSync(filePath, 384);
22818
22120
  } catch {
@@ -22849,6 +22151,13 @@ function flushPromptHistory() {
22849
22151
  }
22850
22152
  process.once("exit", flushPromptHistory);
22851
22153
 
22154
+ // src/tui/components/prompt-input/interrupt-policy.mjs
22155
+ function promptInterruptRestoreText(result, currentText = "") {
22156
+ if (!result || result?.aborted === false) return "";
22157
+ if (String(currentText ?? "") !== "") return "";
22158
+ return String(result?.restoreText || "").trim();
22159
+ }
22160
+
22852
22161
  // src/tui/app/use-prompt-handlers.mjs
22853
22162
  function usePromptHandlers({
22854
22163
  store,
@@ -22873,6 +22182,7 @@ function usePromptHandlers({
22873
22182
  recentPromptHistory,
22874
22183
  resetPromptHistoryNav,
22875
22184
  restoreQueuedToPrompt,
22185
+ openMessageSelector,
22876
22186
  usagePanel,
22877
22187
  closeUsagePanel,
22878
22188
  contextPanel,
@@ -22884,7 +22194,9 @@ function usePromptHandlers({
22884
22194
  clearPastedTextsSnapshot,
22885
22195
  registerPastedText
22886
22196
  }) {
22887
- const handlePromptPaste = useCallback11((text, meta = {}) => {
22197
+ const interruptGenerationRef = useRef13(0);
22198
+ const pendingInterruptRestoreRef = useRef13(null);
22199
+ const handlePromptPaste = useCallback12((text, meta = {}) => {
22888
22200
  const source = String(meta?.source || "paste");
22889
22201
  const value = String(text ?? "");
22890
22202
  const processText = (raw, returnRaw = false) => {
@@ -22946,7 +22258,7 @@ function usePromptHandlers({
22946
22258
  }
22947
22259
  return processText(value);
22948
22260
  }, [registerPastedImage, registerPastedText, showPromptHint, state.cwd]);
22949
- const handlePromptHistoryNavigate = useCallback11((direction, currentText = "", meta = {}) => {
22261
+ const handlePromptHistoryNavigate = useCallback12((direction, currentText = "", meta = {}) => {
22950
22262
  const currentValue = String(currentText || "");
22951
22263
  const currentKey = promptHistoryKey2(currentValue);
22952
22264
  const nav = promptHistoryNavRef.current || { active: false, index: -1, seed: "", lastValue: "" };
@@ -22993,7 +22305,7 @@ function usePromptHandlers({
22993
22305
  promptHistoryDraftChangeRef.current = true;
22994
22306
  return nextValue;
22995
22307
  }, [recentPromptHistory, resetPromptHistoryNav, clearPromptHint]);
22996
- const handlePromptEscape = useCallback11((text = "", meta = {}) => {
22308
+ const handlePromptEscape = useCallback12((text = "", meta = {}) => {
22997
22309
  if (usagePanel) {
22998
22310
  closeUsagePanel();
22999
22311
  return true;
@@ -23002,8 +22314,28 @@ function usePromptHandlers({
23002
22314
  setContextPanel(null);
23003
22315
  return true;
23004
22316
  }
22317
+ if (meta.phase === "clear-arm") {
22318
+ showPromptHint("Esc again to clear", "plain", PROMPT_ESCAPE_CLEAR_WINDOW_MS);
22319
+ return true;
22320
+ }
22321
+ if (meta.phase === "select-arm") {
22322
+ showPromptHint("Esc again to pick a message", "plain", PROMPT_ESCAPE_CLEAR_WINDOW_MS);
22323
+ return true;
22324
+ }
22325
+ if (meta.phase === "select") {
22326
+ clearPromptHint();
22327
+ if (restoreQueuedToPrompt({ restoreDraft: true, showHint: false, currentText: text })) return true;
22328
+ return openMessageSelector?.() === true;
22329
+ }
23005
22330
  if (meta.phase === "clear") {
22331
+ try {
22332
+ const remembered = store.rememberPromptHistory?.(text);
22333
+ if (remembered?.catch) void remembered.catch(() => {
22334
+ });
22335
+ } catch {
22336
+ }
23006
22337
  clearPastedImagesSnapshot();
22338
+ clearPastedTextsSnapshot();
23007
22339
  clearPromptHint();
23008
22340
  return false;
23009
22341
  }
@@ -23011,21 +22343,57 @@ function usePromptHandlers({
23011
22343
  return restoreQueuedToPrompt({ restoreDraft: true, showHint: false, currentText: text });
23012
22344
  }
23013
22345
  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");
22346
+ }, [contextPanel, usagePanel, closeUsagePanel, restoreQueuedToPrompt, openMessageSelector, showPromptHint, clearPromptHint, clearPastedImagesSnapshot, clearPastedTextsSnapshot, store]);
22347
+ const commitAsyncInterruptRestore = useCallback12((pending) => {
22348
+ if (!pending || pending.generation !== interruptGenerationRef.current) return true;
22349
+ if (store.getState?.().busy) return false;
22350
+ const restoreText = promptInterruptRestoreText(pending.result, promptValueRef.current);
22351
+ if (!restoreText) return true;
22352
+ if (pending.result?.pastedImages) installPastedImages(pending.result.pastedImages, { merge: true });
22353
+ if (pending.result?.pastedTexts) installPastedTexts(pending.result.pastedTexts, { merge: true });
23026
22354
  clearPromptHint();
23027
- return nextText;
23028
- }, [store, clearPromptHint, installPastedImages, clearPastedImagesSnapshot]);
22355
+ syncPromptLayoutRows(restoreText);
22356
+ setPromptDraftOverride({ id: Date.now(), value: restoreText });
22357
+ return true;
22358
+ }, [store, promptValueRef, installPastedImages, installPastedTexts, clearPromptHint, syncPromptLayoutRows, setPromptDraftOverride]);
22359
+ useEffect13(() => {
22360
+ const pending = pendingInterruptRestoreRef.current;
22361
+ if (!pending) return;
22362
+ if (commitAsyncInterruptRestore(pending)) pendingInterruptRestoreRef.current = null;
22363
+ }, [state.busy, commitAsyncInterruptRestore]);
22364
+ const handlePromptInterrupt = useCallback12((currentText = "") => {
22365
+ const generation = ++interruptGenerationRef.current;
22366
+ pendingInterruptRestoreRef.current = null;
22367
+ const applyResult = (result2, draftText, asyncResult = false) => {
22368
+ if (generation !== interruptGenerationRef.current || result2?.aborted === false) return void 0;
22369
+ if (result2?.discardPastedImages) clearPastedImagesSnapshot(result2.discardPastedImages);
22370
+ if (result2?.discardPastedTexts) clearPastedTextsSnapshot(result2.discardPastedTexts);
22371
+ if (asyncResult) {
22372
+ const pending = { generation, result: result2 };
22373
+ if (!commitAsyncInterruptRestore(pending)) pendingInterruptRestoreRef.current = pending;
22374
+ return void 0;
22375
+ }
22376
+ const restoreText = promptInterruptRestoreText(result2, draftText);
22377
+ if (!restoreText) return void 0;
22378
+ if (result2?.pastedImages) installPastedImages(result2.pastedImages, { merge: true });
22379
+ if (result2?.pastedTexts) installPastedTexts(result2.pastedTexts, { merge: true });
22380
+ clearPromptHint();
22381
+ return restoreText;
22382
+ };
22383
+ let result;
22384
+ try {
22385
+ const options = { restorePrompt: String(currentText ?? "") === "" };
22386
+ result = typeof store.abortAsync === "function" ? store.abortAsync(options) : store.abort?.(options);
22387
+ } catch (error) {
22388
+ store.pushNotice?.(`interrupt failed: ${error?.message || error}`, "error");
22389
+ return void 0;
22390
+ }
22391
+ if (result && typeof result.then === "function") {
22392
+ void Promise.resolve(result).then((resolved) => applyResult(resolved, promptValueRef.current, true)).catch((error) => store.pushNotice?.(`interrupt failed: ${error?.message || error}`, "error"));
22393
+ return void 0;
22394
+ }
22395
+ return applyResult(result, currentText, false);
22396
+ }, [store, commitAsyncInterruptRestore, clearPromptHint, installPastedImages, installPastedTexts, clearPastedImagesSnapshot, clearPastedTextsSnapshot]);
23029
22397
  return {
23030
22398
  handlePromptPaste,
23031
22399
  handlePromptHistoryNavigate,
@@ -23084,7 +22452,7 @@ function resolveOnboardingCompleted(store, onboardingCompleted) {
23084
22452
  }
23085
22453
  }
23086
22454
  function App({ store, initialStatusLine = "", forceOnboarding = false, onboardingCompleted = void 0 }) {
23087
- const state = useEngine(store);
22455
+ const state = useSession(store);
23088
22456
  const [toolOutputExpanded, setToolOutputExpanded] = useState11(false);
23089
22457
  const [onboardingActive, setOnboardingActive] = useState11(false);
23090
22458
  const { exit } = useApp();
@@ -23092,30 +22460,31 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23092
22460
  const { stdout: stdout2 } = useStdout2();
23093
22461
  const [exiting, setExiting] = useState11(false);
23094
22462
  const [tuiReady, setTuiReady] = useState11(false);
23095
- const exitRequestedRef = useRef13(false);
22463
+ const exitRequestedRef = useRef14(false);
23096
22464
  const [resizeState, setResizeState] = useState11(() => ({ ...terminalSize(stdout2), epoch: 0 }));
23097
22465
  const [panelTransitionEpoch, setPanelTransitionEpoch] = useState11(0);
23098
22466
  const [panelInkMaskEpoch, setPanelInkMaskEpoch] = useState11(0);
23099
22467
  const rightSafetyColumns = 1;
23100
22468
  const frameColumns = Math.max(1, resizeState.columns - rightSafetyColumns);
23101
22469
  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 });
22470
+ const scrollPositionRef = useRef14(0);
22471
+ const scrollTargetRef = useRef14(0);
22472
+ const maxScrollRowsRef = useRef14(0);
22473
+ const transcriptBottomSlackRowsRef = useRef14(0);
22474
+ const transcriptAnchorRef = useRef14(null);
22475
+ const transcriptAnchorDirtyRef = useRef14(false);
22476
+ const transcriptGeomRef = useRef14({ prefixRows: null, totalRows: 0, viewRows: 1 });
23109
22477
  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);
22478
+ const followingRef = useRef14(false);
22479
+ const lastItemsCountRef = useRef14(0);
22480
+ const lastFirstItemIdRef = useRef14(null);
22481
+ const pickerOpenedFromEnterRef = useRef14(false);
22482
+ const pickerOpenedFromEnterTimerRef = useRef14(null);
22483
+ const projectPickerRef = useRef14(null);
23115
22484
  const buildProjectPickerState = (opts) => projectPickerRef.current.buildProjectPickerState(opts);
23116
22485
  const [picker, setPickerState] = useState11(null);
23117
- const livePickerRef = useRef13(null);
23118
- const setPicker = useCallback12((next) => {
22486
+ const livePickerRef = useRef14(null);
22487
+ const setPicker = useCallback13((next) => {
23119
22488
  livePickerRef.current = typeof next === "function" ? next(livePickerRef.current) : next;
23120
22489
  setPickerState((prev) => {
23121
22490
  const resolved = typeof next === "function" ? next(prev) : next;
@@ -23136,14 +22505,14 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23136
22505
  livePickerRef.current = picker;
23137
22506
  const [contextPanel, setContextPanel] = useState11(null);
23138
22507
  const [usagePanel, setUsagePanel] = useState11(null);
23139
- const usageRequestRef = useRef13(0);
23140
- const settingsHeavyCacheRef = useRef13(null);
23141
- const closeUsagePanel = useCallback12(() => {
22508
+ const usageRequestRef = useRef14(0);
22509
+ const settingsHeavyCacheRef = useRef14(null);
22510
+ const closeUsagePanel = useCallback13(() => {
23142
22511
  usageRequestRef.current += 1;
23143
22512
  setUsagePanel(null);
23144
22513
  }, []);
23145
22514
  const [providerPrompt, setProviderPrompt] = useState11(null);
23146
- const oauthSubmitRef = useRef13(false);
22515
+ const oauthSubmitRef = useRef14(false);
23147
22516
  const [channelPrompt, setChannelPrompt] = useState11(null);
23148
22517
  const [hookPrompt, setHookPrompt] = useState11(null);
23149
22518
  const [settingsPrompt, setSettingsPrompt] = useState11(null);
@@ -23157,22 +22526,22 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23157
22526
  setSettingsPrompt,
23158
22527
  setContextPanel,
23159
22528
  closeUsagePanel,
23160
- listProjects,
23161
- addProject,
23162
- touchProjectSelected,
23163
- resolveProjectPath,
23164
22529
  projectNameFromPath,
23165
22530
  pickFolder
23166
22531
  });
23167
22532
  projectPickerRef.current = projectPicker;
23168
- const initialPickerBuiltRef = useRef13(false);
22533
+ const initialPickerBuiltRef = useRef14(false);
23169
22534
  if (!initialPickerBuiltRef.current) {
23170
22535
  initialPickerBuiltRef.current = true;
23171
22536
  const onboardingOwnsScreen = !resolveOnboardingCompleted(store, onboardingCompleted) || forceOnboarding;
23172
22537
  if (!onboardingOwnsScreen && state.items.length === 0) {
23173
- setPicker(projectPicker.buildProjectPickerState({ initialEntry: true }));
22538
+ setPicker(projectPicker.buildProjectPickerState({ initialEntry: true, loading: true }));
23174
22539
  }
23175
22540
  }
22541
+ useEffect14(() => {
22542
+ if (livePickerRef.current?._projectInitialPending !== true) return;
22543
+ void projectPickerRef.current?.openProjectPicker({ initialEntry: true });
22544
+ }, [store]);
23176
22545
  const {
23177
22546
  beginNewProject,
23178
22547
  registerProject,
@@ -23181,7 +22550,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23181
22550
  openProjectPicker
23182
22551
  } = projectPicker;
23183
22552
  const [disabledSkills, setDisabledSkillsInner] = useState11(() => /* @__PURE__ */ new Set());
23184
- useEffect13(() => {
22553
+ useEffect14(() => {
23185
22554
  let alive = true;
23186
22555
  void Promise.resolve(store.getDisabledSkills?.()).then((result) => {
23187
22556
  if (!alive) return;
@@ -23193,7 +22562,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23193
22562
  alive = false;
23194
22563
  };
23195
22564
  }, [store]);
23196
- const setDisabledSkills = useCallback12((next) => {
22565
+ const setDisabledSkills = useCallback13((next) => {
23197
22566
  setDisabledSkillsInner((current) => {
23198
22567
  const base = current instanceof Set ? current : new Set(current);
23199
22568
  const set = typeof next === "function" ? next(base) : next instanceof Set ? next : new Set(next);
@@ -23208,7 +22577,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23208
22577
  const toolApproval = state.toolApproval || null;
23209
22578
  const [promptDraft, setPromptDraft] = useState11("");
23210
22579
  const [promptDraftOverride, setPromptDraftOverride] = useState11(null);
23211
- const promptLayoutValueRef = useRef13("");
22580
+ const promptLayoutValueRef = useRef14("");
23212
22581
  const [, setPromptLayoutRows] = useState11(1);
23213
22582
  const [textEntryLayoutRows, setTextEntryLayoutRows] = useState11(1);
23214
22583
  const {
@@ -23225,12 +22594,12 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23225
22594
  clearPastedTextsSnapshot,
23226
22595
  registerPastedText
23227
22596
  } = 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);
22597
+ const promptValueRef = useRef14("");
22598
+ const promptSelectionRef = useRef14(null);
22599
+ const promptBoxRectRef = useRef14(null);
22600
+ const promptMouseSelectionRef = useRef14(null);
22601
+ const promptHistoryNavRef = useRef14({ active: false, index: -1, seed: "", lastValue: "" });
22602
+ const promptHistoryDraftChangeRef = useRef14(false);
23234
22603
  const {
23235
22604
  promptHint,
23236
22605
  promptHintTone,
@@ -23240,7 +22609,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23240
22609
  showSelectionCopyHint,
23241
22610
  clearPromptHint
23242
22611
  } = usePromptHint();
23243
- const toastErrorSignature = useMemo4(() => (state.toasts || []).filter((toast) => toast?.tone === "error").map((toast) => `${toast.id || ""}:${toast.text || ""}`).join("|"), [state.toasts]);
22612
+ const toastErrorSignature = useMemo5(() => (state.toasts || []).filter((toast) => toast?.tone === "error").map((toast) => `${toast.id || ""}:${toast.text || ""}`).join("|"), [state.toasts]);
23244
22613
  const {
23245
22614
  welcomePromptHintDismissed,
23246
22615
  conditionalWelcomePromptHint,
@@ -23250,16 +22619,16 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23250
22619
  } = useWelcomePromptHint({ store, state, toastErrorSignature });
23251
22620
  const [slashIndex, setSlashIndex] = useState11(0);
23252
22621
  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") => {
22622
+ const slashPaletteRef = useRef14({ open: false, count: 0 });
22623
+ const workflowTabCycleRef = useRef14({ pending: false, lastAt: 0 });
22624
+ const scrollFocusRef = useRef14({});
22625
+ const onboardingStartedRef = useRef14(false);
22626
+ const onboardingRef = useRef14({ defaultRoute: null, searchRoute: null, agentRoutes: {}, agents: [], providerModels: [] });
22627
+ const providerModelsCacheRef = useRef14({ models: null, at: 0 });
22628
+ const searchModelsCacheRef = useRef14({ models: null, at: 0 });
22629
+ const modelPickerRequestRef = useRef14(0);
22630
+ const onboardingPrefetchSeqRef = useRef14(0);
22631
+ const clearModelCaches = useCallback13((scope = "all") => {
23263
22632
  if (scope === "all" || scope === "provider") {
23264
22633
  providerModelsCacheRef.current = { models: null, at: 0 };
23265
22634
  onboardingRef.current.providerModels = [];
@@ -23269,7 +22638,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23269
22638
  searchModelsCacheRef.current = { models: null, at: 0 };
23270
22639
  }
23271
22640
  }, []);
23272
- useEffect13(() => {
22641
+ useEffect14(() => {
23273
22642
  let alive = true;
23274
22643
  const timer2 = setTimeout(async () => {
23275
22644
  const seq = onboardingPrefetchSeqRef.current;
@@ -23354,24 +22723,24 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23354
22723
  openUsagePanel: (...a) => openUsagePanel(...a),
23355
22724
  openContextPicker: (...a) => openContextPicker(...a)
23356
22725
  });
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);
22726
+ const dragRef = useRef14({ anchor: null, anchorScroll: 0, last: null, active: false, rect: null, region: null, anchorSpan: null });
22727
+ const transcriptViewportRef = useRef14({ top: 0, bottom: 0 });
22728
+ const panelTransitionRef = useRef14({ signature: "", reserve: 0, clearRows: 0, guardRows: 0, epoch: 0 });
22729
+ const panelCloseInkMaskRowsRef = useRef14(0);
22730
+ const projectBootInputLatchRef = useRef14(false);
22731
+ const frameRowsRef = useRef14(24);
23363
22732
  const STATUSLINE_BAND_ROWS = 3;
23364
22733
  const promptContentColumns = Math.max(1, frameColumns - 4);
23365
- const syncPromptLayoutRows = useCallback12((value) => {
22734
+ const syncPromptLayoutRows = useCallback13((value) => {
23366
22735
  const text = String(value ?? "");
23367
22736
  promptLayoutValueRef.current = text;
23368
22737
  const nextRows = promptContentRows(text, promptContentColumns);
23369
22738
  setPromptLayoutRows((prev) => prev === nextRows ? prev : nextRows);
23370
22739
  }, [promptContentColumns]);
23371
- useEffect13(() => {
22740
+ useEffect14(() => {
23372
22741
  syncPromptLayoutRows(promptLayoutValueRef.current);
23373
22742
  }, [syncPromptLayoutRows]);
23374
- useEffect13(() => {
22743
+ useEffect14(() => {
23375
22744
  const kind = String(settingsPrompt?.kind || "");
23376
22745
  if (!CORE_MULTILINE_TEXT_ENTRY_KINDS.has(kind)) {
23377
22746
  setTextEntryLayoutRows(1);
@@ -23380,10 +22749,10 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23380
22749
  const cols = Math.max(1, frameColumns - 4 - stringWidth11("Sentence > "));
23381
22750
  setTextEntryLayoutRows(textEntryReservedRows(settingsPrompt?.initialValue, cols, 8));
23382
22751
  }, [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(() => {
22752
+ const selectionLayoutRef = useRef14(null);
22753
+ const selectionTextRef = useRef14("");
22754
+ const lastClickRef = useRef14({ x: -1, y: -1, t: 0, count: 0 });
22755
+ useEffect14(() => {
23387
22756
  const timer2 = setTimeout(() => setTuiReady(true), 0);
23388
22757
  return () => clearTimeout(timer2);
23389
22758
  }, []);
@@ -23429,7 +22798,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23429
22798
  getStitchedSelectionText,
23430
22799
  showSelectionCopyHint
23431
22800
  });
23432
- useEffect13(() => () => {
22801
+ useEffect14(() => () => {
23433
22802
  stopSmoothScroll();
23434
22803
  }, [stopSmoothScroll]);
23435
22804
  useMouseInput({
@@ -23463,12 +22832,15 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23463
22832
  const count = state.items.length;
23464
22833
  const previousCount = lastItemsCountRef.current;
23465
22834
  lastItemsCountRef.current = count;
23466
- if (count < previousCount || previousCount === 0) {
22835
+ const firstId = count > 0 ? state.items[0]?.id ?? null : null;
22836
+ const previousFirstId = lastFirstItemIdRef.current;
22837
+ lastFirstItemIdRef.current = firstId;
22838
+ if (transcriptSwapReturnsToTail({ count, previousCount, firstId, previousFirstId })) {
23467
22839
  resetTranscriptScroll();
23468
22840
  return;
23469
22841
  }
23470
22842
  if (count === previousCount || dragRef.current.active) return;
23471
- }, [state.items.length, resetTranscriptScroll]);
22843
+ }, [state.items, resetTranscriptScroll]);
23472
22844
  const {
23473
22845
  requestExit,
23474
22846
  restoreQueuedToPrompt,
@@ -23490,6 +22862,15 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23490
22862
  setPromptDraftOverride,
23491
22863
  promptHistoryNavRef
23492
22864
  });
22865
+ const { hasUserMessages, openMessageSelector } = useMessageSelector({
22866
+ store,
22867
+ state,
22868
+ setPicker,
22869
+ setPromptDraftOverride,
22870
+ syncPromptLayoutRows,
22871
+ showPromptHint,
22872
+ clearPromptHint
22873
+ });
23493
22874
  const {
23494
22875
  handlePromptPaste,
23495
22876
  handlePromptHistoryNavigate,
@@ -23515,6 +22896,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23515
22896
  recentPromptHistory,
23516
22897
  resetPromptHistoryNav,
23517
22898
  restoreQueuedToPrompt,
22899
+ openMessageSelector,
23518
22900
  usagePanel,
23519
22901
  closeUsagePanel,
23520
22902
  contextPanel,
@@ -23525,7 +22907,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23525
22907
  clearPastedTextsSnapshot,
23526
22908
  registerPastedText
23527
22909
  });
23528
- const toggleExpand = useCallback12(() => {
22910
+ const toggleExpand = useCallback13(() => {
23529
22911
  setToolOutputExpanded((expanded) => !expanded);
23530
22912
  }, []);
23531
22913
  useGlobalKeyInput({
@@ -23565,7 +22947,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23565
22947
  setUsagePanel,
23566
22948
  closeUsagePanel
23567
22949
  });
23568
- useEffect13(() => {
22950
+ useEffect14(() => {
23569
22951
  if (contextPanel?.kind === "context") {
23570
22952
  openContextPicker();
23571
22953
  return;
@@ -23586,7 +22968,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23586
22968
  state.cwd,
23587
22969
  state.clientHostPid
23588
22970
  ]);
23589
- useEffect13(() => {
22971
+ useEffect14(() => {
23590
22972
  if (onboardingStartedRef.current) return void 0;
23591
22973
  if (resolveOnboardingCompleted(store, onboardingCompleted) && !forceOnboarding) return void 0;
23592
22974
  let canceled = false;
@@ -23647,7 +23029,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23647
23029
  hookPrompt: !!hookPrompt,
23648
23030
  settingsPrompt: !!settingsPrompt
23649
23031
  };
23650
- useEffect13(() => {
23032
+ useEffect14(() => {
23651
23033
  setSlashIndex((index) => Math.min(index, Math.max(0, slashCommands.length - 1)));
23652
23034
  }, [slashCommands.length, activeSlashQuery]);
23653
23035
  const {
@@ -23750,7 +23132,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23750
23132
  transcriptContentHeight,
23751
23133
  transcriptBottomSlackRows
23752
23134
  } = layout;
23753
- useEffect13(() => {
23135
+ useEffect14(() => {
23754
23136
  const transition = panelTransitionRef.current;
23755
23137
  const pendingInkMask = panelCloseInkMaskRowsRef.current;
23756
23138
  const hadTransitionClearance = panelTransitionClearRows > 0 || panelTransitionGuardRows > 0;
@@ -23767,7 +23149,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23767
23149
  const timer2 = setTimeout(() => setPanelTransitionEpoch((epoch) => epoch + 1), 0);
23768
23150
  return () => clearTimeout(timer2);
23769
23151
  }, [panelLayoutSignature, bottomClusterRows, panelTransitionClearRows, panelTransitionGuardRows]);
23770
- useEffect13(() => {
23152
+ useEffect14(() => {
23771
23153
  panelTransitionRef.current.tailId = latestTranscriptItem?.id ?? null;
23772
23154
  });
23773
23155
  const {
@@ -23811,7 +23193,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23811
23193
  measuredRowsVersion,
23812
23194
  setMeasuredRowsVersion
23813
23195
  });
23814
- const cycleWorkflowFromPrompt = useCallback12(() => {
23196
+ const cycleWorkflowFromPrompt = useCallback13(() => {
23815
23197
  if (slashPaletteOpen || toolApproval || picker || settingsPrompt || providerPrompt || channelPrompt || hookPrompt || contextPanel || usagePanel) return true;
23816
23198
  const repeatGuardMs = 300;
23817
23199
  const cycleGuard = workflowTabCycleRef.current;
@@ -23871,6 +23253,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23871
23253
  handlePromptHistoryNavigate,
23872
23254
  handlePromptInterrupt,
23873
23255
  handlePromptPaste,
23256
+ hasUserMessages,
23874
23257
  hookPrompt,
23875
23258
  initialStatusLine,
23876
23259
  onPromptDraftChange,
@@ -23914,7 +23297,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
23914
23297
  });
23915
23298
  }
23916
23299
 
23917
- // src/tui/engine/transcript-spill.mjs
23300
+ // src/tui/session/transcript-spill.mjs
23918
23301
  import { randomUUID } from "node:crypto";
23919
23302
 
23920
23303
  // src/session-runtime/session-text.mjs
@@ -23942,44 +23325,19 @@ var SYNTHETIC_SESSION_TEXT_PATTERNS = Object.freeze([
23942
23325
  init_plugin_paths();
23943
23326
  init_config();
23944
23327
 
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
- };
23328
+ // src/runtime/shared/agent-route-config.mjs
23329
+ var LEGACY_AGENT_ROUTE_SLOTS = Object.freeze({
23330
+ explore: Object.freeze({ workflow: "explorer", maintenance: "explore" }),
23331
+ maintainer: Object.freeze({ alias: "maintenance", workflow: "memory", maintenance: "memory" }),
23332
+ worker: Object.freeze({ workflow: "agent" })
23333
+ });
23969
23334
 
23970
23335
  // src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs
23971
23336
  init_plugin_paths();
23972
- import { join as join8 } from "node:path";
23973
- var ANTHROPIC_DEFAULT_CREDENTIALS_PATH = join8(resolvePluginData(), "anthropic-oauth-credentials.json");
23337
+ import { join as join7 } from "node:path";
23338
+ var ANTHROPIC_DEFAULT_CREDENTIALS_PATH = join7(resolvePluginData(), "anthropic-oauth-credentials.json");
23974
23339
 
23975
23340
  // 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
23341
  var MAINTENANCE_SLOTS = Object.freeze(["explore", "memory"]);
23984
23342
  var PROFILE_LANGUAGES = Object.freeze([
23985
23343
  { id: "system", label: "System (locale)", prompt: null },
@@ -24038,44 +23396,41 @@ var AGENT_PROVIDER_ALIASES = Object.freeze({
24038
23396
  "xai-api": "xai"
24039
23397
  });
24040
23398
 
24041
- // src/tui/engine/boot-profile.mjs
23399
+ // src/tui/session/boot-profile.mjs
24042
23400
  import { performance as performance2 } from "node:perf_hooks";
24043
23401
  var BOOT_PROFILE_ENABLED = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_BOOT_PROFILE || ""));
24044
23402
  var BOOT_PROFILE_START = globalThis.__mixdogBootProfileStart || (globalThis.__mixdogBootProfileStart = performance2.now());
24045
23403
 
24046
- // src/tui/engine/agent-job-feed.mjs
23404
+ // src/tui/session/agent-job-feed.mjs
24047
23405
  init_paste_attachments();
24048
23406
 
24049
23407
  // src/runtime/agent/orchestrator/session/manager/delivered-completions.mjs
24050
23408
  var DELIVERED_TTL_MS = 6 * 60 * 60 * 1e3;
24051
23409
 
24052
- // src/tui/engine/tui-steering-persist.mjs
23410
+ // src/tui/session/tui-steering-persist.mjs
24053
23411
  init_plugin_paths();
24054
23412
  init_atomic_file();
24055
23413
  var STALE_STEERING_RESTORE_TTL_MS = 30 * 60 * 1e3;
24056
23414
  var _persistChain = Promise.resolve();
24057
23415
 
23416
+ // src/tui/session/oauth-flows.mjs
23417
+ var DEFAULT_FLOW_TTL_MS = 10 * 60 * 1e3;
23418
+
24058
23419
  // src/tui/app/doctor.mjs
24059
23420
  init_plugin_paths();
24060
23421
 
24061
- // src/tui/engine/session-api.mjs
24062
- var MANUAL_ABORT_RECOVERY_MS = (() => {
24063
- const v = Number(process.env.MIXDOG_MANUAL_ABORT_RECOVERY_MS);
24064
- return Number.isFinite(v) && v > 0 ? v : 4e3;
24065
- })();
24066
-
24067
- // src/tui/engine/live-share.mjs
23422
+ // src/tui/session/live-share.mjs
24068
23423
  var MAX_BUFFER_BYTES = 8 * 1024 * 1024;
24069
23424
 
24070
- // src/tui/engine/transcript-spill.mjs
23425
+ // src/tui/session/transcript-spill.mjs
24071
23426
  var TUI_DEBUG2 = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_TUI_DEBUG || ""));
24072
23427
  var TRANSCRIPT_SPILL_STALE_MS = 24 * 60 * 60 * 1e3;
24073
23428
  var TRANSCRIPT_PROCESS_NONCE = randomUUID();
24074
23429
 
24075
- // src/tui/engine.mjs
24076
- 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);
23430
+ // src/tui/session.mjs
23431
+ async function createSessionRuntime(options = {}) {
23432
+ const { createSession } = await import("../../standalone/session-client.mjs");
23433
+ return await createSession(options);
24079
23434
  }
24080
23435
 
24081
23436
  // src/tui/index.jsx
@@ -24281,8 +23636,8 @@ function waitWithTimeout(promise, ms) {
24281
23636
  let timer2 = null;
24282
23637
  return Promise.race([
24283
23638
  Promise.resolve(promise).then(() => true),
24284
- new Promise((resolve5) => {
24285
- timer2 = setTimeout(() => resolve5(false), ms);
23639
+ new Promise((resolve4) => {
23640
+ timer2 = setTimeout(() => resolve4(false), ms);
24286
23641
  })
24287
23642
  ]).finally(() => {
24288
23643
  if (timer2) clearTimeout(timer2);
@@ -24301,7 +23656,7 @@ function scheduleHardExit(code = 0) {
24301
23656
  timer2.unref?.();
24302
23657
  }
24303
23658
  function resolveTuiStderrLogPath() {
24304
- return process.env.MIXDOG_TUI_STDERR_LOG || join9(process.env.MIXDOG_RUNTIME_ROOT || join9(tmpdir2(), "mixdog"), "mixdog-tui.stderr.log");
23659
+ return process.env.MIXDOG_TUI_STDERR_LOG || join8(process.env.MIXDOG_RUNTIME_ROOT || join8(tmpdir2(), "mixdog"), "mixdog-tui.stderr.log");
24305
23660
  }
24306
23661
  function ansiFg(rgb) {
24307
23662
  const m = /rgb\((\d+),\s*(\d+),\s*(\d+)\)/.exec(String(rgb || ""));
@@ -24383,7 +23738,7 @@ function installTuiStderrGuard() {
24383
23738
  const originalWrite = process.stderr.write.bind(process.stderr);
24384
23739
  const logPath = resolveTuiStderrLogPath();
24385
23740
  try {
24386
- mkdirSync7(dirname8(logPath), { recursive: true });
23741
+ mkdirSync6(dirname7(logPath), { recursive: true });
24387
23742
  } catch {
24388
23743
  }
24389
23744
  (0, import_mixdog_debug.rotateBoundedLog)(logPath, import_mixdog_debug.PLUGIN_LOG_MAX_BYTES, import_mixdog_debug.PLUGIN_LOG_KEEP_BYTES);
@@ -24477,7 +23832,7 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
24477
23832
  process.stdout.write(`${TERMINAL_MODE_RESET_HIDDEN_CURSOR}\x1B[?1049h${TERMINAL_MODE_RESET_HIDDEN_CURSOR}\x1B[2J\x1B[H`);
24478
23833
  process.stdout.write("\x1B[5 q");
24479
23834
  process.on("exit", restoreTerminal);
24480
- const storeOutcomePromise = createEngineSession({ provider, model, toolMode, remote }).then((store2) => ({ store: store2, error: null }), (error) => ({ store: null, error }));
23835
+ const storeOutcomePromise = createSessionRuntime({ provider, model, toolMode, remote }).then((store2) => ({ store: store2, error: null }), (error) => ({ store: null, error }));
24481
23836
  try {
24482
23837
  await loadThemeSettingFromConfig();
24483
23838
  } catch {
@@ -24491,10 +23846,10 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
24491
23846
  if (outcome.error) throw outcome.error;
24492
23847
  store = outcome.store;
24493
23848
  try {
24494
- touchProjectSelected(store.getState?.()?.cwd || process.cwd());
23849
+ await store.touchProjectSelected(store.getState?.()?.cwd || process.cwd());
24495
23850
  } catch {
24496
23851
  }
24497
- if (store.isRemoteEngine === true) {
23852
+ if (store.isRemoteSession === true) {
24498
23853
  const remoteSetTheme = store.setTheme;
24499
23854
  store.listThemes = () => listThemes();
24500
23855
  store.getTheme = () => getThemeSetting();