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
@@ -0,0 +1,55 @@
1
+ // Wire identity shared by the daemon's session transport and its views. Kept dependency-free
2
+ // so both the host process and the client can read it without pulling in the
3
+ // other side's module graph.
4
+ import { createHash } from 'node:crypto';
5
+ import { readFileSync } from 'node:fs';
6
+ import {
7
+ SESSION_CONFIGURE_ACTIONS,
8
+ SESSION_READ_ACTIONS,
9
+ } from './session-protocol.mjs';
10
+
11
+ /** Compatibility generation, not an application or capability version.
12
+ * Increment only for a rare, fundamentally incompatible wire rewrite. */
13
+ export const SESSION_PROTOCOL = 1;
14
+
15
+ /** Monotonic API index inside protocol 1. Increment when actions or payload
16
+ * shapes change. Newer clients replace older daemons; older clients attach to
17
+ * newer daemons and are normalized at the session boundary. */
18
+ export const SESSION_REVISION = 1;
19
+
20
+ /** Diagnostic-only action-surface identity. It never advances the protocol. */
21
+ export const SESSION_CAPABILITY_FINGERPRINT = createHash('sha256')
22
+ .update(JSON.stringify({
23
+ read: SESSION_READ_ACTIONS,
24
+ configure: SESSION_CONFIGURE_ACTIONS,
25
+ }))
26
+ .digest('hex')
27
+ .slice(0, 16);
28
+
29
+ let cachedVersion = null;
30
+
31
+ function numericVersion(value) {
32
+ const match = String(value || '').match(/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
33
+ return match ? match.slice(1).map(Number) : [0, 0, 0];
34
+ }
35
+
36
+ export function compareRuntimeVersions(left, right) {
37
+ const a = numericVersion(left);
38
+ const b = numericVersion(right);
39
+ for (let index = 0; index < 3; index += 1) {
40
+ if (a[index] !== b[index]) return a[index] - b[index];
41
+ }
42
+ return 0;
43
+ }
44
+
45
+ /** Build rank within one protocol generation. */
46
+ export function runtimeVersion() {
47
+ if (cachedVersion) return cachedVersion;
48
+ try {
49
+ const manifest = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8'));
50
+ cachedVersion = String(manifest?.version || '0.0.0');
51
+ } catch {
52
+ cachedVersion = '0.0.0';
53
+ }
54
+ return cachedVersion;
55
+ }
package/src/tui/App.jsx CHANGED
@@ -10,7 +10,7 @@
10
10
  * queued steering prompts + rounded prompt input (one cluster)
11
11
  * statusline (vendored L1/L2)
12
12
  *
13
- * State comes from the engine store via useEngine; submitting a line calls
13
+ * State comes from the session store via useSession; submitting a line calls
14
14
  * store.submit() (or handles a slash command locally). The whole tree is live
15
15
  * (no <Static>): full-width bands and the native hardware caret both need real
16
16
  * layout, which <Static> collapses. The terminal handles scrollback itself as
@@ -19,7 +19,7 @@
19
19
  import React, { useState, useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react';
20
20
  import { Box, Text, useApp, useInput, useStdin, useStdout } from 'ink';
21
21
  import { theme, surfaceBackground } from './theme.mjs';
22
- import { useEngine } from './hooks/useEngine.mjs';
22
+ import { useSession } from './hooks/useSession.mjs';
23
23
  import { classifyToolCategory } from '../runtime/shared/tool-surface.mjs';
24
24
  import { localPackageVersion } from '../runtime/shared/update-checker.mjs';
25
25
  import { Spinner } from './components/Spinner.jsx';
@@ -42,16 +42,6 @@ import {
42
42
  splitPastedImagePathCandidates,
43
43
  } from './paste-attachments.mjs';
44
44
  import { formatDuration } from './time-format.mjs';
45
- import {
46
- listProjects,
47
- addProject,
48
- touchProjectSelected,
49
- renameProject,
50
- isDirectory,
51
- pathExists,
52
- ensureDir,
53
- resolveProjectPath,
54
- } from '../standalone/projects.mjs';
55
45
  import { pickFolder } from '../standalone/folder-dialog.mjs';
56
46
  import {
57
47
  formatHookDenialDetail,
@@ -100,6 +90,7 @@ import { usePromptDraftFlow } from './app/use-prompt-draft-flow.mjs';
100
90
  import { useTranscriptActivity } from './app/use-transcript-activity.mjs';
101
91
  import { createAppPickers } from './app/create-app-pickers.mjs';
102
92
  import { usePromptQueueHistory } from './app/use-prompt-queue-history.mjs';
93
+ import { useMessageSelector } from './app/message-selector.mjs';
103
94
  import { useTerminalChrome } from './app/use-terminal-chrome.mjs';
104
95
  import { useTranscriptWindow } from './app/use-transcript-window.mjs';
105
96
  import {
@@ -120,6 +111,7 @@ import {
120
111
  transcriptMeasuredRowsCache,
121
112
  buildTranscriptRowIndex,
122
113
  transcriptRenderWindow,
114
+ transcriptSwapReturnsToTail,
123
115
  } from './app/transcript-window.mjs';
124
116
  import {
125
117
  SEARCH_DEFAULT_ROUTE,
@@ -265,7 +257,7 @@ function resolveOnboardingCompleted(store, onboardingCompleted) {
265
257
  }
266
258
 
267
259
  export function App({ store, initialStatusLine = '', forceOnboarding = false, onboardingCompleted = undefined }) {
268
- const state = useEngine(store);
260
+ const state = useSession(store);
269
261
  const [toolOutputExpanded, setToolOutputExpanded] = useState(false);
270
262
  // True for the entire first-run onboarding wizard (every step + nested depth)
271
263
  // so the welcome banner stays reserved and the layout doesn't jump when the
@@ -326,6 +318,9 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
326
318
  // same glide back to the bottom.
327
319
  const followingRef = useRef(false);
328
320
  const lastItemsCountRef = useRef(0);
321
+ // Head item of the last committed transcript: a bulk swap (session load /
322
+ // clear / compaction trim) changes it, a live append never does.
323
+ const lastFirstItemIdRef = useRef(null);
329
324
  // picker = null | { type, title, items, onSelect }
330
325
  // Rendered as an option panel attached directly above the bottom prompt.
331
326
  const pickerOpenedFromEnterRef = useRef(false);
@@ -385,7 +380,7 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
385
380
  const [usagePanel, setUsagePanel] = useState(null);
386
381
  const usageRequestRef = useRef(0);
387
382
  // Cache of the last computed heavy settings-picker status objects (MCP,
388
- // hooks, plugins, skills, channel backend). ←/→ cycle/toggle handlers in
383
+ // hooks, plugins, skills, channel provider). ←/→ cycle/toggle handlers in
389
384
  // openSettingsPicker() pass { light: true } to reuse this cache instead of
390
385
  // re-querying these heavy getters on every keystroke; only a full open
391
386
  // (initial /config or Esc-return) recomputes them.
@@ -413,10 +408,6 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
413
408
  setSettingsPrompt,
414
409
  setContextPanel,
415
410
  closeUsagePanel,
416
- listProjects,
417
- addProject,
418
- touchProjectSelected,
419
- resolveProjectPath,
420
411
  projectNameFromPath,
421
412
  pickFolder,
422
413
  });
@@ -429,9 +420,13 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
429
420
  initialPickerBuiltRef.current = true;
430
421
  const onboardingOwnsScreen = !resolveOnboardingCompleted(store, onboardingCompleted) || forceOnboarding;
431
422
  if (!onboardingOwnsScreen && state.items.length === 0) {
432
- setPicker(projectPicker.buildProjectPickerState({ initialEntry: true }));
423
+ setPicker(projectPicker.buildProjectPickerState({ initialEntry: true, loading: true }));
433
424
  }
434
425
  }
426
+ useEffect(() => {
427
+ if (livePickerRef.current?._projectInitialPending !== true) return;
428
+ void projectPickerRef.current?.openProjectPicker({ initialEntry: true });
429
+ }, [store]);
435
430
  const {
436
431
  beginNewProject,
437
432
  registerProject,
@@ -793,12 +788,17 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
793
788
  const count = state.items.length;
794
789
  const previousCount = lastItemsCountRef.current;
795
790
  lastItemsCountRef.current = count;
796
- if (count < previousCount || previousCount === 0) {
791
+ const firstId = count > 0 ? (state.items[0]?.id ?? null) : null;
792
+ const previousFirstId = lastFirstItemIdRef.current;
793
+ lastFirstItemIdRef.current = firstId;
794
+ // A bulk swap (compaction included) is an explicit return to the tail: the
795
+ // rows the reader was anchored to no longer exist.
796
+ if (transcriptSwapReturnsToTail({ count, previousCount, firstId, previousFirstId })) {
797
797
  resetTranscriptScroll();
798
798
  return;
799
799
  }
800
800
  if (count === previousCount || dragRef.current.active) return;
801
- }, [state.items.length, resetTranscriptScroll]);
801
+ }, [state.items, resetTranscriptScroll]);
802
802
 
803
803
  // Exit + queued-restore + prompt history: app/use-prompt-queue-history.mjs.
804
804
  const {
@@ -823,6 +823,17 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
823
823
  promptHistoryNavRef,
824
824
  });
825
825
 
826
+ // Double-Esc message selector (Claude Code parity): app/message-selector.mjs.
827
+ const { hasUserMessages, openMessageSelector } = useMessageSelector({
828
+ store,
829
+ state,
830
+ setPicker,
831
+ setPromptDraftOverride,
832
+ syncPromptLayoutRows,
833
+ showPromptHint,
834
+ clearPromptHint,
835
+ });
836
+
826
837
  // PROMPT HANDLER cluster extracted to app/use-prompt-handlers.mjs.
827
838
  const {
828
839
  handlePromptPaste,
@@ -849,6 +860,7 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
849
860
  recentPromptHistory,
850
861
  resetPromptHistoryNav,
851
862
  restoreQueuedToPrompt,
863
+ openMessageSelector,
852
864
  usagePanel,
853
865
  closeUsagePanel,
854
866
  contextPanel,
@@ -1257,6 +1269,7 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
1257
1269
  handlePromptHistoryNavigate,
1258
1270
  handlePromptInterrupt,
1259
1271
  handlePromptPaste,
1272
+ hasUserMessages,
1260
1273
  hookPrompt,
1261
1274
  initialStatusLine,
1262
1275
  onPromptDraftChange,
@@ -8,7 +8,7 @@ import stringWidth from 'string-width';
8
8
  import { theme } from '../theme.mjs';
9
9
  import { formatToolSurface } from '../../runtime/shared/tool-surface.mjs';
10
10
 
11
- // SEARCH_DEFAULT marker — mirrors backend SEARCH_DEFAULT_PROVIDER/MODEL
11
+ // SEARCH_DEFAULT marker — mirrors search defaults SEARCH_DEFAULT_PROVIDER/MODEL
12
12
  // (mixdog-session-runtime.mjs 1167-1168). A search route of {provider:'default',
13
13
  // model:'default'} means "follow the Main Model" at runtime (nativeSearchRoutes).
14
14
  export const SEARCH_DEFAULT_ROUTE = Object.freeze({ provider: 'default', model: 'default' });
@@ -20,7 +20,7 @@ import { UsagePanel } from '../components/UsagePanel.jsx';
20
20
  import { TextEntryPanel } from '../components/TextEntryPanel.jsx';
21
21
  import { Item } from '../components/TranscriptItem.jsx';
22
22
  export function renderAppView(ctx) {
23
- 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; /* DESTRUCTURE */
23
+ 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; /* DESTRUCTURE */
24
24
  const promptInputControl = (
25
25
  <PromptInput
26
26
  onSubmit={onSubmit}
@@ -47,6 +47,7 @@ export function renderAppView(ctx) {
47
47
  // actually be accepted — otherwise Enter must submit the raw text as
48
48
  // before instead of dead-ending in the palette accept path.
49
49
  commandPaletteActive={slashPaletteOpen && slashCommands.length > 0}
50
+ commandPaletteOpen={slashPaletteOpen}
50
51
  onCommandPaletteNavigate={(direction) => {
51
52
  setSlashIndex((index) => {
52
53
  const total = slashCommands.length;
@@ -65,7 +66,9 @@ export function renderAppView(ctx) {
65
66
  onCommandPaletteAccept={acceptSlashPalette}
66
67
  onCommandPaletteCancel={cancelSlashPalette}
67
68
  onCommandPaletteComplete={completeSlashPalette}
68
- onRestoreQueued={(currentText) => restoreQueuedToPrompt({ restoreDraft: true, showHint: false, currentText })}
69
+ onRestoreQueued={restoreQueuedToPrompt}
70
+ hasQueuedMessages={Array.isArray(state.queued) && state.queued.length > 0}
71
+ hasMessages={hasUserMessages}
69
72
  />
70
73
  );
71
74
 
@@ -27,7 +27,7 @@ export function createChannelPickers({
27
27
  // a long first-time install finishing later must not hijack whatever
28
28
  // screen/picker the user has since navigated to.
29
29
  let pickerGen = 0;
30
- const openChannelTypeActionsPicker = async (backend, options = {}) => {
30
+ const openChannelTypeActionsPicker = async (provider, options = {}) => {
31
31
  pickerGen += 1;
32
32
  const parentReturn = typeof options.returnTo === 'function'
33
33
  ? options.returnTo
@@ -43,15 +43,15 @@ export function createChannelPickers({
43
43
  store.pushNotice(`channels failed: ${e?.message || e}`, 'error');
44
44
  return;
45
45
  }
46
- const isTelegram = backend === 'telegram';
47
- const activeBackend = setup.backend || 'discord';
46
+ const isTelegram = provider === 'telegram';
47
+ const activeProvider = setup.provider || 'discord';
48
48
  const tokenDescription = isTelegram
49
49
  ? `${setup.telegram?.status ?? 'Off'}${setup.telegram?.problem ? ' · Invalid' : ''}`
50
50
  : `${setup.discord.status}${setup.discord.problem ? ' · Invalid' : ''}`;
51
51
  const mainEntry = setup.channel || {};
52
52
  const mainTarget = isTelegram
53
- ? (mainEntry?.telegramChatId || (activeBackend === 'telegram' ? mainEntry?.channelId : ''))
54
- : (mainEntry?.discordChannelId || (activeBackend === 'discord' ? mainEntry?.channelId : ''));
53
+ ? (mainEntry?.telegramChatId || (activeProvider === 'telegram' ? mainEntry?.channelId : ''))
54
+ : (mainEntry?.discordChannelId || (activeProvider === 'discord' ? mainEntry?.channelId : ''));
55
55
  const mainDescription = isTelegram
56
56
  ? (mainTarget ? `Chat ID ${mainTarget}` : 'Not set · Enter Telegram chat ID')
57
57
  : (mainTarget ? `Channel ID ${mainTarget}` : 'Not set · Enter Discord channel ID');
@@ -61,13 +61,13 @@ export function createChannelPickers({
61
61
  setContextPanel(null);
62
62
  setChannelPrompt({
63
63
  ...prompt,
64
- afterSave: () => openChannelTypeActionsPicker(backend, options),
64
+ afterSave: () => openChannelTypeActionsPicker(provider, options),
65
65
  });
66
66
  };
67
67
 
68
68
  setPicker({
69
69
  title: isTelegram ? 'Telegram' : 'Discord',
70
- description: activeBackend === backend
70
+ description: activeProvider === provider
71
71
  ? 'Active channel type · token and main target'
72
72
  : 'Token and main target settings',
73
73
  help: '↑/↓ Select · Enter Edit · Esc Back',
@@ -108,7 +108,7 @@ export function createChannelPickers({
108
108
  if (item._action === 'main-target') {
109
109
  openChannelPrompt({
110
110
  kind: 'channel-add',
111
- backend,
111
+ provider,
112
112
  label: isTelegram ? 'Main chat' : 'Main channel',
113
113
  hint: isTelegram
114
114
  ? 'Paste the Telegram chat ID.'
@@ -140,19 +140,19 @@ export function createChannelPickers({
140
140
  store.pushNotice(`channels failed: ${e?.message || e}`, 'error');
141
141
  return;
142
142
  }
143
- const activeBackend = setup.backend || 'discord';
143
+ const activeProvider = setup.provider || 'discord';
144
144
  const mainEntry = setup.channel || {};
145
- const typeDescription = (backend) => {
146
- const selected = activeBackend === backend;
147
- const hasToken = backend === 'telegram'
145
+ const typeDescription = (provider) => {
146
+ const selected = activeProvider === provider;
147
+ const hasToken = provider === 'telegram'
148
148
  ? setup.telegram?.authenticated === true
149
149
  : setup.discord?.authenticated === true;
150
- const hasTarget = backend === 'telegram'
151
- ? Boolean(mainEntry?.telegramChatId || (activeBackend === 'telegram' && mainEntry?.channelId))
152
- : Boolean(mainEntry?.discordChannelId || (activeBackend === 'discord' && mainEntry?.channelId));
150
+ const hasTarget = provider === 'telegram'
151
+ ? Boolean(mainEntry?.telegramChatId || (activeProvider === 'telegram' && mainEntry?.channelId))
152
+ : Boolean(mainEntry?.discordChannelId || (activeProvider === 'discord' && mainEntry?.channelId));
153
153
  const needs = [
154
154
  ...(hasToken ? [] : ['token']),
155
- ...(hasTarget ? [] : [backend === 'telegram' ? 'chat ID' : 'channel ID']),
155
+ ...(hasTarget ? [] : [provider === 'telegram' ? 'chat ID' : 'channel ID']),
156
156
  ];
157
157
  return [
158
158
  ...(selected ? ['Selected'] : []),
@@ -161,7 +161,7 @@ export function createChannelPickers({
161
161
  };
162
162
  setPicker({
163
163
  title: 'Channel Type Settings',
164
- description: 'Choose a type. Selected is the active backend; Ready means token and main target are set.',
164
+ description: 'Choose a type. Selected is the active provider; Ready means token and main target are set.',
165
165
  help: '↑/↓ Select · Enter Open · Esc Back',
166
166
  indexMode: 'always',
167
167
  labelWidth: 18,
@@ -170,20 +170,20 @@ export function createChannelPickers({
170
170
  value: 'discord',
171
171
  label: 'Discord',
172
172
  description: typeDescription('discord'),
173
- _backend: 'discord',
173
+ _provider: 'discord',
174
174
  },
175
175
  {
176
176
  value: 'telegram',
177
177
  label: 'Telegram',
178
178
  description: typeDescription('telegram'),
179
- _backend: 'telegram',
179
+ _provider: 'telegram',
180
180
  },
181
181
  ],
182
182
  onSelect: (value, item) => {
183
- const backend = item?._backend || (value === 'telegram' ? 'telegram' : value === 'discord' ? 'discord' : null);
184
- if (!backend) return;
183
+ const provider = item?._provider || (value === 'telegram' ? 'telegram' : value === 'discord' ? 'discord' : null);
184
+ if (!provider) return;
185
185
  setPicker(null);
186
- openChannelTypeActionsPicker(backend, {
186
+ openChannelTypeActionsPicker(provider, {
187
187
  returnTo: () => openChannelSettingTypePicker(options),
188
188
  });
189
189
  },
@@ -218,8 +218,8 @@ export function createChannelPickers({
218
218
  // Schedules/webhooks management is desktop-only (user decision): the TUI
219
219
  // no longer carries their pickers or the webhook-endpoint info page.
220
220
  const worker = await store.getChannelWorkerStatus?.();
221
- const activeBackend = setup.backend === 'telegram' ? 'telegram' : 'discord';
222
- const backendLabel = activeBackend === 'telegram' ? 'Telegram' : 'Discord';
221
+ const activeProvider = setup.provider === 'telegram' ? 'telegram' : 'discord';
222
+ const providerLabel = activeProvider === 'telegram' ? 'Telegram' : 'Discord';
223
223
  const remoteEnabled = (await store.isRemoteEnabled?.()) === true;
224
224
  const boolLabel = (enabled) => enabled ? 'On' : 'Off';
225
225
  // Onboarding Step 5 reuses this root picker with a ConfirmBar. Reopens after
@@ -237,25 +237,25 @@ export function createChannelPickers({
237
237
  .catch((e) => store.pushNotice(`Remote toggle failed: ${e?.message || e}`, 'error'))
238
238
  .finally(() => reopenRoot({ highlightValue }));
239
239
  };
240
- const cycleChannelBackend = (direction = 1, highlightValue = 'channel-backend') => {
241
- const backends = ['discord', 'telegram'];
242
- const currentIndex = Math.max(0, backends.indexOf(activeBackend));
243
- const chosen = backends[(currentIndex + direction + backends.length) % backends.length];
244
- if (chosen === activeBackend) {
245
- reopenRoot({ highlightValue, backendOverride: activeBackend });
240
+ const cycleChannelProvider = (direction = 1, highlightValue = 'channel-provider') => {
241
+ const providers = ['discord', 'telegram'];
242
+ const currentIndex = Math.max(0, providers.indexOf(activeProvider));
243
+ const chosen = providers[(currentIndex + direction + providers.length) % providers.length];
244
+ if (chosen === activeProvider) {
245
+ reopenRoot({ highlightValue, providerOverride: activeProvider });
246
246
  return;
247
247
  }
248
248
  try {
249
- store.setBackend(chosen);
249
+ store.setChannelProvider(chosen);
250
250
  const label = chosen === 'telegram' ? 'Telegram' : 'Discord';
251
251
  const restartHint = (remoteEnabled || worker?.running)
252
252
  ? `Channel set to ${label}. Restart remote to apply.`
253
253
  : `Channel set to ${label}.`;
254
254
  store.pushNotice(restartHint, 'info');
255
255
  } catch (e) {
256
- store.pushNotice(`channel backend failed: ${e?.message || e}`, 'error');
256
+ store.pushNotice(`channel provider failed: ${e?.message || e}`, 'error');
257
257
  }
258
- reopenRoot({ highlightValue, backendOverride: chosen });
258
+ reopenRoot({ highlightValue, providerOverride: chosen });
259
259
  };
260
260
  // Voice toggle: enabling installs the managed whisper/ffmpeg runtime (first
261
261
  // time only) then flips voice.enabled so the channels pipeline transcribes
@@ -285,11 +285,11 @@ export function createChannelPickers({
285
285
  _action: 'remote-runtime',
286
286
  },
287
287
  {
288
- value: 'channel-backend',
288
+ value: 'channel-provider',
289
289
  label: 'Channel',
290
- meta: backendLabel,
290
+ meta: providerLabel,
291
291
  description: 'Select Discord or Telegram',
292
- _action: 'channel-backend',
292
+ _action: 'channel-provider',
293
293
  },
294
294
  {
295
295
  value: 'channel-setting',
@@ -329,18 +329,18 @@ export function createChannelPickers({
329
329
  indexMode: 'always',
330
330
  labelWidth: 18,
331
331
  metaWidth: 12,
332
- pickerKey: `channels:${activeBackend}:${remoteEnabled ? 'on' : 'off'}:${options.highlightValue || 'root'}`,
332
+ pickerKey: `channels:${activeProvider}:${remoteEnabled ? 'on' : 'off'}:${options.highlightValue || 'root'}`,
333
333
  initialIndex: Math.max(0, items.findIndex((entry) => entry.value === options.highlightValue)),
334
334
  items,
335
335
  confirmBar: options.confirmBar || null,
336
336
  onLeft: options.confirmBar ? undefined : (item) => {
337
337
  if (item?._action === 'remote-runtime') applyRemoteRuntime(item.value);
338
- else if (item?._action === 'channel-backend') cycleChannelBackend(-1, item.value);
338
+ else if (item?._action === 'channel-provider') cycleChannelProvider(-1, item.value);
339
339
  else if (item?._action === 'voice') applyVoice(item.value);
340
340
  },
341
341
  onRight: options.confirmBar ? undefined : (item) => {
342
342
  if (item?._action === 'remote-runtime') applyRemoteRuntime(item.value);
343
- else if (item?._action === 'channel-backend') cycleChannelBackend(1, item.value);
343
+ else if (item?._action === 'channel-provider') cycleChannelProvider(1, item.value);
344
344
  else if (item?._action === 'voice') applyVoice(item.value);
345
345
  },
346
346
  onSelect: (_value, item) => {
@@ -349,8 +349,8 @@ export function createChannelPickers({
349
349
  applyRemoteRuntime(item.value);
350
350
  return;
351
351
  }
352
- if (item._action === 'channel-backend') {
353
- cycleChannelBackend(1, item.value);
352
+ if (item._action === 'channel-provider') {
353
+ cycleChannelProvider(1, item.value);
354
354
  return;
355
355
  }
356
356
  if (item._action === 'voice') {
@@ -39,20 +39,20 @@ export function createCoreMemoryPicker({
39
39
  });
40
40
  void store.memoryControl?.({ action: 'core', op: 'list', project_id: '*' }, { silent: true })
41
41
  .then(async (result) => {
42
- // Memory on/off lives here too so /memory is a one-stop surface:
42
+ // The recap toggle lives here too so /memory is a one-stop surface:
43
43
  // toggle row + add row + curated entries.
44
- const memory = (await store.getMemorySettings?.()) || { enabled: true };
45
- const memoryOn = memory.enabled !== false;
44
+ const recap = (await store.getRecapSettings?.()) || { enabled: true };
45
+ const recapOn = recap.enabled !== false;
46
46
  const coreRows = parseMemoryCoreRows(result);
47
47
  const rows = [
48
48
  {
49
- value: 'memory-toggle',
50
- label: 'Memory',
51
- meta: memoryOn ? 'On' : 'Off',
52
- description: memoryOn
53
- ? 'Background memory cycles on'
54
- : 'Background memory cycles off',
55
- _action: 'toggle-memory',
49
+ value: 'recap-toggle',
50
+ label: 'Recap',
51
+ meta: recapOn ? 'On' : 'Off',
52
+ description: recapOn
53
+ ? 'Background session recaps on'
54
+ : 'Background recaps off; core memory remains available',
55
+ _action: 'toggle-recap',
56
56
  },
57
57
  { value: 'core-add', label: 'Add Memory', description: 'store a new curated memory sentence', _action: 'add-core' },
58
58
  {
@@ -76,17 +76,17 @@ export function createCoreMemoryPicker({
76
76
  labelWidth: 12,
77
77
  footer: (item) => (item && item._action === 'core-entry' ? (item._summary || item._element || '') : ''),
78
78
  onSelect: (_value, item) => {
79
- if (item?._action === 'toggle-memory') toggleMemoryEnabled();
79
+ if (item?._action === 'toggle-recap') toggleRecapEnabled();
80
80
  else if (item?._action === 'add-core') beginAddCoreMemory();
81
81
  else if (item?._action === 'core-list') openCoreMemoryListPicker(item._rows);
82
82
  else if (item?._line) store.pushNotice(item._line, 'info');
83
83
  },
84
84
  // Toggle rows also respond to ←/→ (matches settings/channels UX).
85
85
  onLeft: (item) => {
86
- if (item?._action === 'toggle-memory') toggleMemoryEnabled();
86
+ if (item?._action === 'toggle-recap') toggleRecapEnabled();
87
87
  },
88
88
  onRight: (item) => {
89
- if (item?._action === 'toggle-memory') toggleMemoryEnabled();
89
+ if (item?._action === 'toggle-recap') toggleRecapEnabled();
90
90
  },
91
91
  onCancel: closeMemoryCorePicker,
92
92
  });
@@ -137,14 +137,14 @@ export function createCoreMemoryPicker({
137
137
  });
138
138
  };
139
139
 
140
- const toggleMemoryEnabled = async () => {
141
- const memory = (await store.getMemorySettings?.()) || { enabled: true };
142
- void Promise.resolve(store.setMemoryEnabled?.(!(memory.enabled !== false)))
140
+ const toggleRecapEnabled = async () => {
141
+ const recap = (await store.getRecapSettings?.()) || { enabled: true };
142
+ void Promise.resolve(store.setRecapEnabled?.(!(recap.enabled !== false)))
143
143
  .then((next) => {
144
- if (!next) store.pushNotice('memory setting is busy', 'warn');
145
- else store.pushNotice(`Memory ${next.enabled ? 'on' : 'off'}`, 'info');
144
+ if (!next) store.pushNotice('recap setting is busy', 'warn');
145
+ else store.pushNotice(`Recap ${next.enabled ? 'on' : 'off'}`, 'info');
146
146
  })
147
- .catch((e) => store.pushNotice(`memory toggle failed: ${e?.message || e}`, 'error'))
147
+ .catch((e) => store.pushNotice(`recap toggle failed: ${e?.message || e}`, 'error'))
148
148
  .finally(() => openMemoryCorePicker());
149
149
  };
150
150
 
@@ -138,18 +138,12 @@ export async function buildDoctorReport(runtime = {}, getState = () => ({})) {
138
138
  row(failed.length || conn < conf ? 'warn' : 'ok', detail);
139
139
  });
140
140
 
141
- // 5. memory: enabled/disabled (+ backend health only if the accessor
142
- // already exposes it; no new probing).
141
+ // 5. memory recap: background-cycle state. Memory ingest, on-demand recall,
142
+ // and curated core memory are always available.
143
143
  await check('memory', async (row) => {
144
- const mem = runtime.getMemorySettings?.() || {};
145
- const enabled = mem.enabled !== false;
146
- let detail = enabled ? 'enabled' : 'disabled';
147
- if (mem.backend) detail += ` · backend ${mem.backend}`;
148
- const health = mem.backendHealth || mem.health;
149
- if (health != null) {
150
- detail += ` · ${typeof health === 'string' ? health : (health.ok ? 'healthy' : 'unhealthy')}`;
151
- }
152
- row(enabled ? 'ok' : 'warn', detail);
144
+ const recap = runtime.getRecapSettings?.() || {};
145
+ const enabled = recap.enabled !== false;
146
+ row('ok', `recap ${enabled ? 'enabled' : 'disabled'} · core memory available`);
153
147
  });
154
148
 
155
149
  // 6. channels: enabled + worker status + configured tokens (names only).
@@ -139,7 +139,7 @@ export function parseMemoryCandidateRows(text) {
139
139
  // op:'candidates' returns this exact sentinel (index.mjs ~3158) when the
140
140
  // resolved scope has none — treat as an empty list, not an inert text row.
141
141
  if (!trimmed || /^core candidates:\s*none$/i.test(trimmed)) return [];
142
- // Backend row shape (index.mjs ~3164), one candidate per line, no group
142
+ // Service row shape (index.mjs ~3164), one candidate per line, no group
143
143
  // headers:
144
144
  // id=<n> project=<COMMON|slug> score=<x.xx|-> <element> — <summary> (<reason>)
145
145
  const rowPattern = /^id=(\d+)\s+project=(\S+)\s+score=(\S+)\s+(.+?)\s+—\s+(.+?)\s+\(([^)]*)\)$/;
@@ -171,7 +171,7 @@ export function parseMemoryCandidateRows(text) {
171
171
  });
172
172
  }
173
173
 
174
- // Backend "core" op errors are flattened to plain text by store.memoryControl
174
+ // Service "core" operation errors are flattened to plain text by store.memoryControl
175
175
  // (isError is dropped -- see engine.mjs memoryControl / toolResponseText in
176
176
  // mixdog-session-runtime.mjs). Success text always uses a past-tense verb
177
177
  // ("core added/edited/deleted/promoted...", "core candidate dismissed...");