mixdog 0.8.0 → 0.9.0

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 (285) hide show
  1. package/README.md +47 -23
  2. package/package.json +33 -27
  3. package/scripts/_test-folder-dialog.mjs +30 -0
  4. package/scripts/agent-parallel-smoke.mjs +388 -0
  5. package/scripts/agent-tag-reuse-smoke.mjs +183 -0
  6. package/scripts/background-task-meta-smoke.mjs +38 -0
  7. package/scripts/boot-smoke.mjs +52 -9
  8. package/scripts/build-runtime-linux.sh +348 -0
  9. package/scripts/build-runtime-macos.sh +217 -0
  10. package/scripts/build-runtime-windows.ps1 +242 -0
  11. package/scripts/compact-active-turn-test.mjs +68 -0
  12. package/scripts/compact-smoke.mjs +859 -129
  13. package/scripts/compact-trigger-migration-smoke.mjs +187 -0
  14. package/scripts/fix-brief-fn.mjs +35 -0
  15. package/scripts/fix-format-tool-surface.mjs +24 -0
  16. package/scripts/fix-tool-exec-visible.mjs +42 -0
  17. package/scripts/generate-runtime-manifest.mjs +166 -0
  18. package/scripts/hook-bus-test.mjs +330 -0
  19. package/scripts/lead-workflow-smoke.mjs +33 -39
  20. package/scripts/live-worker-smoke.mjs +43 -37
  21. package/scripts/llm-trace-summary.mjs +315 -0
  22. package/scripts/memory-meta-concurrency-test.mjs +20 -0
  23. package/scripts/output-style-smoke.mjs +56 -15
  24. package/scripts/parent-abort-link-test.mjs +44 -0
  25. package/scripts/patch-agent-brief.mjs +48 -0
  26. package/scripts/patch-app.mjs +21 -0
  27. package/scripts/patch-app2.mjs +18 -0
  28. package/scripts/patch-dist-brief.mjs +96 -0
  29. package/scripts/patch-tool-exec.mjs +70 -0
  30. package/scripts/pretool-ask-runtime-test.mjs +54 -0
  31. package/scripts/provider-toolcall-test.mjs +376 -0
  32. package/scripts/reactive-compact-persist-smoke.mjs +124 -0
  33. package/scripts/sanitize-tool-pairs-test.mjs +260 -0
  34. package/scripts/session-context-bench.mjs +344 -0
  35. package/scripts/session-ingest-smoke.mjs +177 -0
  36. package/scripts/set-effort-config-test.mjs +41 -0
  37. package/scripts/smoke-runtime-negative.ps1 +106 -0
  38. package/scripts/smoke-runtime-negative.sh +97 -0
  39. package/scripts/smoke.mjs +25 -0
  40. package/scripts/tool-result-hook-test.mjs +48 -0
  41. package/scripts/tool-smoke.mjs +1223 -95
  42. package/scripts/toolcall-args-test.mjs +150 -0
  43. package/scripts/tui-background-failure-smoke.mjs +73 -0
  44. package/scripts/usage-metrics-epoch-smoke.mjs +114 -0
  45. package/src/agents/debugger/AGENT.md +8 -0
  46. package/src/agents/explore/AGENT.md +4 -0
  47. package/src/agents/heavy-worker/AGENT.md +9 -3
  48. package/src/agents/maintainer/AGENT.md +4 -0
  49. package/src/agents/reviewer/AGENT.md +8 -0
  50. package/src/agents/scheduler-task/AGENT.md +12 -0
  51. package/src/agents/scheduler-task/agent.json +6 -0
  52. package/src/agents/webhook-handler/AGENT.md +12 -0
  53. package/src/agents/webhook-handler/agent.json +6 -0
  54. package/src/agents/worker/AGENT.md +9 -3
  55. package/src/app.mjs +77 -3
  56. package/src/defaults/hidden-roles.json +17 -12
  57. package/src/headless-role.mjs +117 -0
  58. package/src/help.mjs +30 -0
  59. package/src/hooks/lib/permission-evaluator.cjs +11 -475
  60. package/src/lib/keychain-cjs.cjs +9 -1
  61. package/src/lib/mixdog-debug.cjs +0 -7
  62. package/src/lib/rules-builder.cjs +240 -96
  63. package/src/lib/text-utils.cjs +1 -1
  64. package/src/mixdog-session-runtime.mjs +2325 -450
  65. package/src/output-styles/default.md +12 -28
  66. package/src/output-styles/extreme-simple.md +9 -6
  67. package/src/output-styles/simple.md +22 -9
  68. package/src/repl.mjs +118 -59
  69. package/src/rules/agent/00-common.md +15 -0
  70. package/src/rules/{bridge → agent}/20-skip-protocol.md +1 -2
  71. package/src/rules/agent/30-explorer.md +22 -0
  72. package/src/rules/{bridge → agent}/40-cycle1-agent.md +7 -0
  73. package/src/rules/{bridge → agent}/41-cycle2-agent.md +7 -0
  74. package/src/rules/{bridge → agent}/42-cycle3-agent.md +7 -0
  75. package/src/rules/lead/01-general.md +9 -5
  76. package/src/rules/lead/04-workflow.md +51 -12
  77. package/src/rules/lead/lead-tool.md +6 -0
  78. package/src/rules/shared/01-tool.md +12 -1
  79. package/src/runtime/agent/orchestrator/activity-bus.mjs +7 -18
  80. package/src/runtime/agent/orchestrator/agent-owner.mjs +11 -0
  81. package/src/runtime/agent/orchestrator/{smart-bridge/bridge-llm.mjs → agent-runtime/agent-dispatch.mjs} +138 -111
  82. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +94 -0
  83. package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/cache-strategy.mjs +32 -23
  84. package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/session-builder.mjs +33 -27
  85. package/src/runtime/agent/orchestrator/{bridge-trace.mjs → agent-trace.mjs} +132 -81
  86. package/src/runtime/agent/orchestrator/cache-mtime.mjs +0 -21
  87. package/src/runtime/agent/orchestrator/config.mjs +174 -55
  88. package/src/runtime/agent/orchestrator/context/collect.mjs +195 -487
  89. package/src/runtime/agent/orchestrator/dispatch-persist.mjs +1 -1
  90. package/src/runtime/agent/orchestrator/internal-roles.mjs +77 -29
  91. package/src/runtime/agent/orchestrator/internal-tools.mjs +5 -6
  92. package/src/runtime/agent/orchestrator/mcp/client.mjs +15 -9
  93. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -1
  94. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +377 -243
  95. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +146 -93
  96. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +236 -4
  97. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +49 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini.mjs +58 -13
  99. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +67 -149
  100. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +132 -2
  101. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +4 -1
  102. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +45 -0
  103. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +61 -116
  104. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +25 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +79 -255
  106. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +221 -70
  107. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +477 -147
  108. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +343 -496
  109. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -6
  110. package/src/runtime/agent/orchestrator/providers/registry.mjs +88 -51
  111. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +31 -11
  112. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +41 -8
  113. package/src/runtime/agent/orchestrator/session/cache/post-edit-marks.mjs +4 -4
  114. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +1 -1
  115. package/src/runtime/agent/orchestrator/session/compact.mjs +1173 -267
  116. package/src/runtime/agent/orchestrator/session/context-utils.mjs +199 -36
  117. package/src/runtime/agent/orchestrator/session/loop.mjs +851 -674
  118. package/src/runtime/agent/orchestrator/session/manager.mjs +1593 -466
  119. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +107 -0
  120. package/src/runtime/agent/orchestrator/session/store.mjs +291 -46
  121. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
  122. package/src/runtime/agent/orchestrator/stall-policy.mjs +31 -16
  123. package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +3 -219
  124. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +34 -7
  125. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +1 -1
  126. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +19 -0
  127. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +9 -9
  128. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +60 -37
  129. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +21 -2
  130. package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +1 -1
  131. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -7
  132. package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -3
  133. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +36 -12
  134. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -0
  135. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -2
  136. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +5 -12
  137. package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +1 -1
  138. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -36
  139. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +2 -40
  140. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +148 -27
  141. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +2 -2
  142. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +43 -75
  143. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +90 -20
  144. package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +1 -1
  145. package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -5
  146. package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +86 -0
  147. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +11 -11
  148. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4106 -4019
  149. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +33 -4
  150. package/src/runtime/agent/orchestrator/tools/patch.mjs +90 -6
  151. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +6 -4
  152. package/src/runtime/agent/orchestrator/tools/result-compression.mjs +4 -4
  153. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +8 -1
  154. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -4
  155. package/src/runtime/channels/index.mjs +152 -24
  156. package/src/runtime/channels/lib/scheduler.mjs +18 -14
  157. package/src/runtime/channels/lib/session-discovery.mjs +3 -2
  158. package/src/runtime/channels/lib/tool-format.mjs +0 -2
  159. package/src/runtime/channels/lib/transcript-discovery.mjs +3 -2
  160. package/src/runtime/channels/lib/webhook.mjs +1 -1
  161. package/src/runtime/channels/tool-defs.mjs +29 -29
  162. package/src/runtime/memory/index.mjs +635 -107
  163. package/src/runtime/memory/lib/agent-ipc.mjs +29 -12
  164. package/src/runtime/memory/lib/core-memory-store.mjs +2 -2
  165. package/src/runtime/memory/lib/embedding-model-config.mjs +55 -0
  166. package/src/runtime/memory/lib/embedding-provider.mjs +31 -4
  167. package/src/runtime/memory/lib/embedding-worker.mjs +19 -10
  168. package/src/runtime/memory/lib/memory-cycle1.mjs +38 -17
  169. package/src/runtime/memory/lib/memory-cycle2.mjs +6 -7
  170. package/src/runtime/memory/lib/memory-cycle3.mjs +4 -4
  171. package/src/runtime/memory/lib/memory-ops-policy.mjs +2 -1
  172. package/src/runtime/memory/lib/memory-session-merge.mjs +38 -0
  173. package/src/runtime/memory/lib/memory.mjs +88 -9
  174. package/src/runtime/memory/lib/model-profile.mjs +1 -1
  175. package/src/runtime/memory/lib/pg/adapter.mjs +15 -1
  176. package/src/runtime/memory/lib/pg/supervisor.mjs +12 -0
  177. package/src/runtime/memory/lib/runtime-fetcher.mjs +37 -3
  178. package/src/runtime/memory/lib/session-ingest.mjs +194 -0
  179. package/src/runtime/memory/lib/trace-store.mjs +96 -51
  180. package/src/runtime/memory/tool-defs.mjs +46 -37
  181. package/src/runtime/search/index.mjs +102 -466
  182. package/src/runtime/search/lib/web-tools.mjs +45 -25
  183. package/src/runtime/search/tool-defs.mjs +16 -23
  184. package/src/runtime/shared/abort-controller.mjs +1 -1
  185. package/src/runtime/shared/atomic-file.mjs +4 -3
  186. package/src/runtime/shared/background-tasks.mjs +122 -11
  187. package/src/runtime/shared/child-spawn-gate.mjs +145 -0
  188. package/src/runtime/shared/config.mjs +7 -4
  189. package/src/runtime/shared/err-text.mjs +131 -4
  190. package/src/runtime/shared/llm/cost.mjs +2 -2
  191. package/src/runtime/shared/llm/http-agent.mjs +23 -7
  192. package/src/runtime/shared/llm/index.mjs +34 -11
  193. package/src/runtime/shared/llm/usage-log.mjs +4 -4
  194. package/src/runtime/shared/markdown-frontmatter.mjs +56 -0
  195. package/src/runtime/shared/singleton-owner.mjs +104 -0
  196. package/src/runtime/shared/tool-execution-contract.mjs +199 -20
  197. package/src/runtime/shared/tool-execution-contract.test.mjs +183 -0
  198. package/src/runtime/shared/tool-surface.mjs +624 -98
  199. package/src/runtime/shared/user-data-guard.mjs +0 -2
  200. package/src/standalone/agent-task-status.mjs +203 -0
  201. package/src/standalone/agent-task-status.test.mjs +76 -0
  202. package/src/standalone/agent-tool.mjs +1913 -0
  203. package/src/standalone/channel-worker.mjs +370 -14
  204. package/src/standalone/explore-tool.mjs +165 -70
  205. package/src/standalone/folder-dialog.mjs +314 -0
  206. package/src/standalone/hook-bus.mjs +898 -22
  207. package/src/standalone/memory-runtime-proxy.mjs +320 -0
  208. package/src/standalone/projects.mjs +226 -0
  209. package/src/standalone/provider-admin.mjs +41 -24
  210. package/src/standalone/seeds.mjs +2 -69
  211. package/src/standalone/usage-dashboard.mjs +96 -8
  212. package/src/tui/App.jsx +4798 -2153
  213. package/src/tui/components/AnsiText.jsx +39 -28
  214. package/src/tui/components/ContextPanel.jsx +87 -29
  215. package/src/tui/components/Markdown.jsx +43 -77
  216. package/src/tui/components/MarkdownTable.jsx +9 -184
  217. package/src/tui/components/Message.jsx +28 -11
  218. package/src/tui/components/Picker.jsx +95 -56
  219. package/src/tui/components/PromptInput.jsx +367 -239
  220. package/src/tui/components/QueuedCommands.jsx +1 -1
  221. package/src/tui/components/SlashCommandPalette.jsx +27 -21
  222. package/src/tui/components/Spinner.jsx +67 -38
  223. package/src/tui/components/StatusLine.jsx +606 -38
  224. package/src/tui/components/TextEntryPanel.jsx +128 -9
  225. package/src/tui/components/ToolExecution.jsx +617 -368
  226. package/src/tui/components/TurnDone.jsx +3 -3
  227. package/src/tui/components/UsagePanel.jsx +3 -5
  228. package/src/tui/components/tool-output-format.mjs +365 -0
  229. package/src/tui/components/tool-output-format.test.mjs +220 -0
  230. package/src/tui/dist/index.mjs +8915 -2418
  231. package/src/tui/engine-runtime-notification.test.mjs +115 -0
  232. package/src/tui/engine-tool-result-text.test.mjs +75 -0
  233. package/src/tui/engine.mjs +1455 -279
  234. package/src/tui/figures.mjs +21 -40
  235. package/src/tui/index.jsx +75 -31
  236. package/src/tui/input-editing.mjs +25 -0
  237. package/src/tui/markdown/format-token.mjs +511 -68
  238. package/src/tui/markdown/format-token.test.mjs +216 -0
  239. package/src/tui/markdown/render-ansi.mjs +94 -0
  240. package/src/tui/markdown/render-ansi.test.mjs +108 -0
  241. package/src/tui/markdown/stream-fence.mjs +34 -0
  242. package/src/tui/markdown/stream-fence.test.mjs +26 -0
  243. package/src/tui/markdown/table-layout.mjs +250 -0
  244. package/src/tui/paste-attachments.mjs +0 -7
  245. package/src/tui/spinner-verbs.mjs +1 -2
  246. package/src/tui/statusline-ansi-bridge.mjs +172 -0
  247. package/src/tui/statusline-ansi-bridge.test.mjs +159 -0
  248. package/src/tui/theme.mjs +746 -24
  249. package/src/tui/time-format.mjs +1 -1
  250. package/src/tui/transcript-tool-failures.mjs +67 -0
  251. package/src/tui/transcript-tool-failures.test.mjs +111 -0
  252. package/src/ui/ansi.mjs +1 -2
  253. package/src/ui/markdown.mjs +85 -26
  254. package/src/ui/markdown.test.mjs +70 -0
  255. package/src/ui/model-display.mjs +121 -0
  256. package/src/ui/session-stats.mjs +44 -0
  257. package/src/ui/statusline-context-label.test.mjs +15 -0
  258. package/src/ui/statusline.mjs +386 -178
  259. package/src/ui/tool-card.mjs +3 -16
  260. package/src/vendor/statusline/bin/statusline-lib.mjs +8 -4
  261. package/src/vendor/statusline/bin/statusline-route.mjs +169 -37
  262. package/src/vendor/statusline/bin/statusline-route.test.mjs +80 -0
  263. package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +3 -3
  264. package/src/vendor/statusline/src/gateway/claude-current.mjs +1 -1
  265. package/src/vendor/statusline/src/gateway/route-meta.mjs +44 -6
  266. package/src/vendor/statusline/src/gateway/session-routes.mjs +1 -1
  267. package/src/workflows/default/WORKFLOW.md +12 -5
  268. package/src/workflows/default/workflow.json +0 -1
  269. package/src/workflows/solo/WORKFLOW.md +15 -0
  270. package/src/workflows/solo/workflow.json +7 -0
  271. package/vendor/ink/build/output.js +6 -1
  272. package/src/agents/scheduler-task.md +0 -3
  273. package/src/agents/web-researcher/AGENT.md +0 -3
  274. package/src/agents/web-researcher/agent.json +0 -6
  275. package/src/agents/webhook-handler.md +0 -3
  276. package/src/rules/bridge/00-common.md +0 -5
  277. package/src/rules/bridge/30-explorer.md +0 -4
  278. package/src/rules/lead/00-tool-lead.md +0 -5
  279. package/src/rules/shared/00-language.md +0 -3
  280. package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
  281. package/src/runtime/agent/orchestrator/tools/mutation-content-cache.mjs +0 -67
  282. package/src/runtime/memory/lib/bridge-trace-queries.mjs +0 -120
  283. package/src/runtime/shared/llm/pid-cleanup.mjs +0 -27
  284. package/src/standalone/bridge-tool.mjs +0 -1414
  285. package/src/tui/runtime/shared/process-shutdown.mjs +0 -1
@@ -1,9 +1,11 @@
1
1
  import { resolvePluginData } from '../../shared/plugin-paths.mjs';
2
2
  import { readSection, updateSection, getAgentApiKey, AGENT_PROVIDER_ENV } from '../../shared/config.mjs';
3
- import { OPENAI_COMPAT_PRESETS } from './providers/openai-compat.mjs';
4
- import { hasAnthropicOAuthCredentials } from './providers/anthropic-oauth.mjs';
5
- import { hasOpenAIOAuthCredentials } from './providers/openai-oauth.mjs';
6
- import { hasGrokOAuthCredentials } from './providers/grok-oauth.mjs';
3
+ import { OPENAI_COMPAT_PRESETS } from './providers/openai-compat-presets.mjs';
4
+ import {
5
+ hasAnthropicOAuthCredentials,
6
+ hasOpenAIOAuthCredentials,
7
+ hasGrokOAuthCredentials,
8
+ } from './providers/oauth-credential-probes.mjs';
7
9
 
8
10
  // Thin wrapper around resolvePluginData so callers in this orchestrator tree
9
11
  // can import a single helper without reaching into shared/.
@@ -17,7 +19,7 @@ export function getPluginData() {
17
19
  // llm/index.mjs and setup-server.mjs so UI/runtime cannot drift from config.
18
20
  //
19
21
  // Every hidden maintenance slot carries a CONCRETE preset here, so
20
- // resolvePresetName() (bridge-llm) always resolves a model directly from
22
+ // resolvePresetName() (agent-dispatch) always resolves a model directly from
21
23
  // `maint[slot]` — no shared `defaultPreset` fallback is needed or used.
22
24
  // Memory cycles + Lead helper fan-out (explore/cycle1/cycle2/cycle3) and
23
25
  // entry-driven dispatch (scheduler/webhook) all default to `haiku`. The three
@@ -28,13 +30,6 @@ export function getPluginData() {
28
30
  // scheduler/webhook still let a per-entry config.json model win first (the
29
31
  // caller passes it explicitly via opts.preset); the haiku default below only
30
32
  // applies when an entry omits its own model.
31
- export const DEFAULT_MAINTENANCE = Object.freeze({
32
- explore: 'haiku',
33
- memory: 'haiku',
34
- scheduler: 'haiku',
35
- webhook: 'haiku',
36
- });
37
-
38
33
  // Slots surfaced as tunable rows in the Maintenance Setup panel. This is the
39
34
  // UI / allow-list view (GET cleanup + POST validation) and is intentionally a
40
35
  // SUBSET of DEFAULT_MAINTENANCE: scheduler/webhook carry a per-entry model and
@@ -42,6 +37,56 @@ export const DEFAULT_MAINTENANCE = Object.freeze({
42
37
  // an entry omits its own model.
43
38
  export const MAINTENANCE_SLOTS = Object.freeze(['explore', 'memory']);
44
39
 
40
+ // --- User profile (statusline /profile) -------------------------------------
41
+ // Supported response languages for the /profile picker. `system` is the default
42
+ // sentinel: it leaves the language unset so the model follows the user's locale
43
+ // / written language (no forced "Always respond in X" injection). Each entry is
44
+ // { id, label, prompt } — `prompt` is the human language name the prompt-side
45
+ // wiring uses for "Always respond in <prompt>." Keep `system` first.
46
+ export const PROFILE_LANGUAGES = Object.freeze([
47
+ { id: 'system', label: 'System (locale)', prompt: null },
48
+ { id: 'en', label: 'English', prompt: 'English' },
49
+ { id: 'ko', label: '한국어', prompt: 'Korean (한국어)' },
50
+ { id: 'ja', label: '日本語', prompt: 'Japanese (日本語)' },
51
+ { id: 'zh-Hans', label: '中文(简体)', prompt: 'Simplified Chinese (简体中文)' },
52
+ { id: 'zh-Hant', label: '中文(繁體)', prompt: 'Traditional Chinese (繁體中文)' },
53
+ { id: 'es', label: 'Español', prompt: 'Spanish (Español)' },
54
+ { id: 'fr', label: 'Français', prompt: 'French (Français)' },
55
+ { id: 'de', label: 'Deutsch', prompt: 'German (Deutsch)' },
56
+ { id: 'pt', label: 'Português', prompt: 'Portuguese (Português)' },
57
+ { id: 'ru', label: 'Русский', prompt: 'Russian (Русский)' },
58
+ { id: 'it', label: 'Italiano', prompt: 'Italian (Italiano)' },
59
+ { id: 'vi', label: 'Tiếng Việt', prompt: 'Vietnamese (Tiếng Việt)' },
60
+ { id: 'th', label: 'ภาษาไทย', prompt: 'Thai (ภาษาไทย)' },
61
+ { id: 'id', label: 'Bahasa Indonesia', prompt: 'Indonesian (Bahasa Indonesia)' },
62
+ { id: 'hi', label: 'हिन्दी', prompt: 'Hindi (हिन्दी)' },
63
+ { id: 'ar', label: 'العربية', prompt: 'Arabic (العربية)' },
64
+ { id: 'tr', label: 'Türkçe', prompt: 'Turkish (Türkçe)' },
65
+ { id: 'pl', label: 'Polski', prompt: 'Polish (Polski)' },
66
+ { id: 'nl', label: 'Nederlands', prompt: 'Dutch (Nederlands)' },
67
+ { id: 'uk', label: 'Українська', prompt: 'Ukrainian (Українська)' },
68
+ ]);
69
+
70
+ const PROFILE_LANGUAGE_IDS = new Set(PROFILE_LANGUAGES.map((lang) => lang.id));
71
+ const PROFILE_TITLE_MAX = 64;
72
+
73
+ // Resolve a stored profile (or raw config fragment) into a stable shape:
74
+ // { title: string, language: <valid id> }
75
+ // Unknown language ids fall back to 'system'; titles are trimmed/capped.
76
+ export function normalizeProfileConfig(value = {}) {
77
+ const raw = value && typeof value === 'object' ? value : {};
78
+ const title = String(raw.title ?? raw.name ?? '').trim().slice(0, PROFILE_TITLE_MAX);
79
+ const requested = String(raw.language ?? raw.lang ?? 'system').trim();
80
+ const language = PROFILE_LANGUAGE_IDS.has(requested) ? requested : 'system';
81
+ return { title, language };
82
+ }
83
+
84
+ // Look up the catalog entry for a stored language id (defaults to 'system').
85
+ export function profileLanguageEntry(languageId) {
86
+ const id = String(languageId || 'system');
87
+ return PROFILE_LANGUAGES.find((lang) => lang.id === id) || PROFILE_LANGUAGES[0];
88
+ }
89
+
45
90
  // Map short Anthropic family labels to the full model ids used by the API.
46
91
  // Honors ANTHROPIC_DEFAULT_{OPUS|SONNET|HAIKU}_MODEL env overrides.
47
92
  const ANTHROPIC_FAMILY_MODEL = Object.freeze({
@@ -57,14 +102,37 @@ function resolveAnthropicFamilyModel(family) {
57
102
  return ANTHROPIC_FAMILY_MODEL[key] || null;
58
103
  }
59
104
 
105
+ // Canonical maintenance defaults. Single source of truth — imported by
106
+ // llm/index.mjs and setup-server.mjs so UI/runtime cannot drift from config.
107
+ //
108
+ // Each maintenance slot stores its model route DIRECTLY ({provider, model}) —
109
+ // parity with `agents.<role>`. The old shape stored a preset NAME string (e.g.
110
+ // "haiku") that had to be looked up in the config.presets array; that
111
+ // indirection is the legacy path. agent-dispatch.resolveMaintenanceRoute still
112
+ // accepts a legacy name string for backward compatibility, but new configs and
113
+ // these defaults use the direct route. loadConfig() migrates any stored string
114
+ // slot to a route on read (see migrateMaintenanceRoutes). The cycle1/2/3 memory
115
+ // agents share ONE `memory` route via the `maintKey: 'memory'` override on
116
+ // their hidden-role entries.
117
+ const _HAIKU_ROUTE = Object.freeze({
118
+ provider: 'anthropic-oauth',
119
+ model: resolveAnthropicFamilyModel('haiku'),
120
+ });
121
+ export const DEFAULT_MAINTENANCE = Object.freeze({
122
+ explore: { ..._HAIKU_ROUTE },
123
+ memory: { ..._HAIKU_ROUTE },
124
+ scheduler: { ..._HAIKU_ROUTE },
125
+ webhook: { ..._HAIKU_ROUTE },
126
+ });
127
+
60
128
  // Seed presets keyed by preset.name so workflow/maintenance references stay
61
129
  // consistent with the resolve-by-name lookup in presetKey().
62
130
  export const DEFAULT_PRESETS = Object.freeze([
63
- Object.freeze({ id: 'haiku', name: 'HAIKU', type: 'bridge', provider: 'anthropic-oauth', model: resolveAnthropicFamilyModel('haiku'), tools: 'full' }),
64
- Object.freeze({ id: 'sonnet-mid', name: 'SONNET MID', type: 'bridge', provider: 'anthropic-oauth', model: resolveAnthropicFamilyModel('sonnet'), effort: 'medium', tools: 'full' }),
65
- Object.freeze({ id: 'sonnet-high', name: 'SONNET HIGH', type: 'bridge', provider: 'anthropic-oauth', model: resolveAnthropicFamilyModel('sonnet'), effort: 'high', tools: 'full' }),
66
- Object.freeze({ id: 'opus-mid', name: 'OPUS MID', type: 'bridge', provider: 'anthropic-oauth', model: resolveAnthropicFamilyModel('opus'), effort: 'medium', tools: 'full' }),
67
- Object.freeze({ id: 'opus-high', name: 'OPUS HIGH', type: 'bridge', provider: 'anthropic-oauth', model: resolveAnthropicFamilyModel('opus'), effort: 'high', tools: 'full' }),
131
+ Object.freeze({ id: 'haiku', name: 'HAIKU', type: 'agent', provider: 'anthropic-oauth', model: resolveAnthropicFamilyModel('haiku'), tools: 'full' }),
132
+ Object.freeze({ id: 'sonnet-mid', name: 'SONNET MID', type: 'agent', provider: 'anthropic-oauth', model: resolveAnthropicFamilyModel('sonnet'), effort: 'medium', tools: 'full' }),
133
+ Object.freeze({ id: 'sonnet-high', name: 'SONNET HIGH', type: 'agent', provider: 'anthropic-oauth', model: resolveAnthropicFamilyModel('sonnet'), effort: 'high', tools: 'full' }),
134
+ Object.freeze({ id: 'opus-mid', name: 'OPUS MID', type: 'agent', provider: 'anthropic-oauth', model: resolveAnthropicFamilyModel('opus'), effort: 'medium', tools: 'full' }),
135
+ Object.freeze({ id: 'opus-high', name: 'OPUS HIGH', type: 'agent', provider: 'anthropic-oauth', model: resolveAnthropicFamilyModel('opus'), effort: 'high', tools: 'full' }),
68
136
  ]);
69
137
  function buildDefaultConfig(options = {}) {
70
138
  const detectCredentials = options.detectCredentials !== false;
@@ -77,18 +145,16 @@ function buildDefaultConfig(options = {}) {
77
145
  apiKey: apiKey || undefined,
78
146
  };
79
147
  }
80
- // OAuth provider detection delegates to each provider module so the
81
- // canonical credential loader (loadTokens / loadCredentials) is the
82
- // single source of truth. WebSocket transport is on by default —
148
+ // OAuth provider detection uses lightweight credential probes so config
149
+ // load does not import provider runtimes or SDKs. WebSocket transport is on by default —
83
150
  // measured ~96% cross-session cache hit with delta payloads. Users who
84
151
  // need to force SSE (e.g. a corporate proxy blocking WSS) can set
85
152
  // `websocket: false` in mixdog-config.json (agent.providers.openai-oauth).
86
153
  providers['openai-oauth'] = { enabled: detectCredentials ? hasOpenAIOAuthCredentials() : false, websocket: true };
87
154
  providers['anthropic-oauth'] = { enabled: detectCredentials ? hasAnthropicOAuthCredentials() : false };
88
- // Grok CLI OAuth ("Grok Build"). Like the other OAuth entries it is not
155
+ // Grok OAuth ("Grok Build"). Like the other OAuth entries it is not
89
156
  // stored in mixdog-config.json — enabled at runtime from the presence of
90
- // either token source (own store or ~/.grok/auth.json) via
91
- // hasGrokOAuthCredentials().
157
+ // Mixdog-owned credentials.
92
158
  providers['grok-oauth'] = { enabled: detectCredentials ? hasGrokOAuthCredentials() : false };
93
159
  // Local providers — opt-in via setup UI after HTTP ping confirms server is running
94
160
  providers.ollama = { enabled: false, baseURL: 'http://localhost:11434/v1' };
@@ -100,10 +166,65 @@ function hasKeys(value) {
100
166
  return !!value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length > 0;
101
167
  }
102
168
 
169
+ function normalizeSearchRoute(route) {
170
+ if (!route || typeof route !== 'object' || Array.isArray(route))
171
+ return null;
172
+ const provider = normalizeAgentProviderId(route.provider);
173
+ const model = String(route.model || '').trim();
174
+ if (!provider || !model)
175
+ return null;
176
+ const out = { provider, model };
177
+ const effort = String(route.effort || '').trim();
178
+ if (effort)
179
+ out.effort = effort;
180
+ if (route.fast === true)
181
+ out.fast = true;
182
+ const toolType = String(route.toolType || '').trim();
183
+ if (toolType)
184
+ out.toolType = toolType;
185
+ return out;
186
+ }
187
+
188
+ // Migrate stored maintenance slots from the legacy preset-NAME string shape to
189
+ // the direct {provider, model} route shape. A slot value that is already a
190
+ // route object is normalized (provider/model/effort/fast only); a string value
191
+ // is resolved against the config.presets array (the legacy lookup) and rewritten
192
+ // to a route. Unresolvable strings are dropped so the DEFAULT_MAINTENANCE route
193
+ // fills the slot. `presets` is the normalized preset array for legacy lookup.
194
+ function migrateMaintenanceRoutes(rawMaint, presets) {
195
+ const out = {};
196
+ const list = Array.isArray(presets) ? presets : [];
197
+ for (const [slot, value] of Object.entries(rawMaint || {})) {
198
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
199
+ const provider = normalizeAgentProviderId(value.provider);
200
+ const model = String(value.model || '').trim();
201
+ if (provider && model) {
202
+ const route = { provider, model };
203
+ const effort = String(value.effort || '').trim();
204
+ if (effort) route.effort = effort;
205
+ if (value.fast === true) route.fast = true;
206
+ out[slot] = route;
207
+ }
208
+ continue;
209
+ }
210
+ const name = String(value || '').trim();
211
+ if (!name) continue;
212
+ const preset = list.find(p => p && (p.id === name || p.name === name));
213
+ if (preset && preset.provider && preset.model) {
214
+ const route = { provider: preset.provider, model: preset.model };
215
+ if (preset.effort) route.effort = preset.effort;
216
+ if (preset.fast === true) route.fast = true;
217
+ out[slot] = route;
218
+ }
219
+ // Unresolvable legacy name → dropped; DEFAULT_MAINTENANCE route fills it.
220
+ }
221
+ return out;
222
+ }
223
+
103
224
  // Persist the agent section. `build` receives the section value read INSIDE
104
225
  // the file lock (current on-disk state) and returns the full replacement.
105
226
  // Building from `current` rather than a snapshot taken before the lock keeps
106
- // the whole-section save linearizable: a concurrent writer (each Claude Code
227
+ // the whole-section save linearizable: a concurrent writer (each host-agent
107
228
  // session runs its own server under MIXDOG_MULTI_INSTANCE) that lands between
108
229
  // our read and write is rebased onto, not silently clobbered (lost-update).
109
230
  function persistAgentConfig(build) {
@@ -169,7 +290,7 @@ export function loadConfig(options = {}) {
169
290
  migratedMaintenance = true;
170
291
  }
171
292
  // Self-ref guard: mcpServers.mixdog / mcpServers["trib-plugin"]
172
- // would self-spawn through the in-process tool bridge. Strip on
293
+ // would self-spawn through the in-process tool adapter. Strip on
173
294
  // ingress so user-edited configs cannot brick the agent boot.
174
295
  const mcpServers = (raw.mcpServers && typeof raw.mcpServers === 'object') ? { ...raw.mcpServers } : {};
175
296
  if (mcpServers['mixdog'] || mcpServers['trib-plugin']) {
@@ -239,22 +360,28 @@ export function loadConfig(options = {}) {
239
360
  .filter(p => p.id !== 'workflow-search');
240
361
  const workflowRoutes = raw.workflowRoutes && typeof raw.workflowRoutes === 'object' ? { ...raw.workflowRoutes } : {};
241
362
  delete workflowRoutes.search;
363
+ // Migrate legacy preset-name maintenance slots to direct routes,
364
+ // then overlay onto the route-shaped defaults.
365
+ const migratedMaint = migrateMaintenanceRoutes(rawMaint, normalizedPresets);
242
366
  return {
243
367
  providers: mergedProviders,
244
368
  mcpServers,
245
369
  presets: normalizedPresets,
246
370
  default: raw.default || null,
247
- maintenance: { ...DEFAULT_MAINTENANCE, ...rawMaint },
371
+ maintenance: { ...DEFAULT_MAINTENANCE, ...migratedMaint },
248
372
  workflowRoutes,
373
+ searchRoute: normalizeSearchRoute(raw.searchRoute),
249
374
  fastModels: raw.fastModels && typeof raw.fastModels === 'object' ? raw.fastModels : {},
250
375
  modelSettings: raw.modelSettings && typeof raw.modelSettings === 'object' ? raw.modelSettings : {},
251
376
  onboarding: raw.onboarding && typeof raw.onboarding === 'object' ? raw.onboarding : {},
252
377
  agents: raw.agents && typeof raw.agents === 'object' ? raw.agents : {},
253
378
  workflow: raw.workflow && typeof raw.workflow === 'object' ? { active: String(raw.workflow.active || 'default') } : { active: 'default' },
254
379
  agentMaintenance: { enabled: true, interval: '1h', ...raw.agentMaintenance },
380
+ profile: normalizeProfileConfig(raw.profile),
255
381
  autoClear: { enabled: true, idleMs: 60 * 60 * 1000, ...raw.autoClear },
382
+ compaction: raw.compaction && typeof raw.compaction === 'object' ? { ...raw.compaction } : {},
256
383
  trajectory: { enabled: true, ...raw.trajectory },
257
- bridge: raw.bridge && typeof raw.bridge === 'object' ? raw.bridge : {},
384
+ runtime: raw.runtime && typeof raw.runtime === 'object' ? raw.runtime : {},
258
385
  shell: raw.shell && typeof raw.shell === 'object' ? raw.shell : {},
259
386
  };
260
387
  }
@@ -268,15 +395,18 @@ export function loadConfig(options = {}) {
268
395
  default: null,
269
396
  maintenance: { ...DEFAULT_MAINTENANCE },
270
397
  workflowRoutes: {},
398
+ searchRoute: null,
271
399
  fastModels: {},
272
400
  modelSettings: {},
273
401
  onboarding: {},
274
402
  agents: {},
275
403
  workflow: { active: 'default' },
276
404
  agentMaintenance: { enabled: true, interval: '1h' },
405
+ profile: normalizeProfileConfig(null),
277
406
  autoClear: { enabled: true, idleMs: 60 * 60 * 1000 },
407
+ compaction: {},
278
408
  trajectory: { enabled: true },
279
- bridge: {},
409
+ runtime: {},
280
410
  shell: {},
281
411
  };
282
412
  }
@@ -293,8 +423,8 @@ export function loadConfig(options = {}) {
293
423
  * maintenance/...) are replaced from the passed snapshot (last-writer-wins);
294
424
  * only unmanaged keys are rebased on the in-lock current. Safe only for a
295
425
  * caller holding a fresh full config. For a single-field change, patch in-lock
296
- * via persistAgentConfig((current) => ({ ...current, <field> })) see
297
- * setDefaultPreset — so a concurrent instance's edits are not reverted.
426
+ * via persistAgentConfig((current) => ({ ...current, <field> })) so a
427
+ * concurrent instance's edits are not reverted.
298
428
  */
299
429
  export function saveConfig(config) {
300
430
  // Strip ephemeral defaults from providers but preserve any unknown
@@ -339,20 +469,23 @@ export function saveConfig(config) {
339
469
  default: config.default || null,
340
470
  maintenance: config.maintenance || {},
341
471
  workflowRoutes,
472
+ searchRoute: normalizeSearchRoute(config.searchRoute),
342
473
  fastModels: config.fastModels || {},
343
474
  modelSettings: config.modelSettings || {},
344
475
  onboarding: config.onboarding || {},
345
476
  agents: config.agents || {},
346
477
  workflow: config.workflow || { active: 'default' },
347
478
  agentMaintenance: config.agentMaintenance || {},
479
+ profile: normalizeProfileConfig(config.profile),
348
480
  autoClear: config.autoClear || {},
481
+ compaction: config.compaction || {},
349
482
  trajectory: config.trajectory || {},
350
- bridge: config.bridge || {},
483
+ runtime: config.runtime || {},
351
484
  shell: config.shell || {},
352
485
  }));
353
486
  }
354
487
  // --- Preset helpers ---
355
- // preset shape: { id, name, type: 'bridge', provider, model, effort?, fast?, tools? }
488
+ // preset shape: { id, name, type: 'agent', provider, model, effort?, fast?, tools? }
356
489
  const AGENT_PROVIDER_ALIASES = Object.freeze({
357
490
  'openai-api': 'openai',
358
491
  'gemini-api': 'gemini',
@@ -377,7 +510,7 @@ function normalizePreset(preset) {
377
510
  const model = String(preset.model || '').trim();
378
511
  const provider = normalizeAgentProviderId(preset.provider);
379
512
  if (!name || !model || !provider) return null;
380
- const out = { id, name, type: 'bridge', provider, model };
513
+ const out = { id, name, type: 'agent', provider, model };
381
514
  if (preset.effort)
382
515
  out.effort = String(preset.effort).trim();
383
516
  if (preset.fast === true && FAST_CAPABLE_PRESET_PROVIDERS.has(provider))
@@ -413,34 +546,20 @@ export function listPresets(config) {
413
546
  // (both cache shards are identical there), while swapping the model itself
414
547
  // legitimately needs a fresh session (cache shard is model-specific). Two
415
548
  // presets mapping to the same (provider, model) therefore collapse into
416
- // one Bridge session, so opus-mid / opus-max no longer fragment the pool.
549
+ // one Agent session, so opus-mid / opus-max no longer fragment the pool.
417
550
  //
418
- // bridge lane: "bridge:<agentId>:<provider>:<model>" — per Sub role
419
- // other lane: "bridge:<provider>:<model>" — shared utility
551
+ // agent lane: "agent:<agentId>:<provider>:<model>" — per Sub role
552
+ // other lane: "agent:<provider>:<model>" — shared utility
420
553
  export function resolveRuntimeSpec(preset, ctx) {
421
- const lane = ctx.lane || 'bridge';
554
+ const lane = ctx.lane || 'agent';
422
555
  const provider = String(preset?.provider || '').trim() || 'unknown';
423
556
  const model = String(preset?.model || '').trim() || '_';
424
557
  let scopeKey;
425
- if (lane === 'bridge') {
426
- if (!ctx.agentId) throw new Error('bridge lane requires agentId');
427
- scopeKey = `bridge:${ctx.agentId}:${provider}:${model}`;
558
+ if (lane === 'agent') {
559
+ if (!ctx.agentId) throw new Error('agent lane requires agentId');
560
+ scopeKey = `agent:${ctx.agentId}:${provider}:${model}`;
428
561
  } else {
429
- scopeKey = `bridge:${provider}:${model}`;
562
+ scopeKey = `agent:${provider}:${model}`;
430
563
  }
431
564
  return { lane, scopeKey, reuse: true, preset };
432
565
  }
433
-
434
- export function setDefaultPreset(config, key) {
435
- const preset = getPreset(config, key);
436
- if (!preset)
437
- throw new Error(`preset "${key}" not found`);
438
- const nextDefault = presetKey(preset);
439
- // Patch only `default` under the file lock. saveConfig(config) would
440
- // rewrite the whole agent section from this possibly-stale snapshot and
441
- // could revert a concurrent instance's preset edits; an in-lock single
442
- // field patch cannot.
443
- persistAgentConfig((current) => ({ ...current, default: nextDefault }));
444
- config.default = nextDefault;
445
- return preset;
446
- }