mixdog 1.3.0 → 1.3.2

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 (423) hide show
  1. package/NOTICE.md +52 -9
  2. package/README.md +8 -4
  3. package/package.json +2 -2
  4. package/scripts/native-release-status.mjs +238 -0
  5. package/src/defaults/shipped-history.json +8 -1
  6. package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
  7. package/src/defaults/skills/setup/SKILL.md +23 -1
  8. package/src/defaults/skills/setup/references/actions.md +54 -4
  9. package/src/defaults/skills/setup/references/surfaces.md +23 -3
  10. package/src/defaults/skills/setup-guide/SKILL.md +17 -10
  11. package/src/defaults/skills/visualize/SKILL.md +9 -6
  12. package/src/defaults/skills/visualize/references/craft.md +8 -1
  13. package/src/help.mjs +1 -1
  14. package/src/lib/keychain-cjs.cjs +4 -1
  15. package/src/lib/rules-builder.cjs +14 -7
  16. package/src/output-styles/common.md +13 -8
  17. package/src/rules/agent-mode/AGENT_MODE.md +9 -6
  18. package/src/rules/agent-mode/MIXY.md +11 -6
  19. package/src/rules/lead/LEAD.md +2 -2
  20. package/src/rules/shared/10-tool-workflow.md +4 -0
  21. package/src/rules/shared/20-research.md +2 -0
  22. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
  23. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
  24. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
  25. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
  26. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
  27. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
  28. package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
  29. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
  30. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  31. package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
  32. package/src/runtime/agent/orchestrator/config.mjs +11 -0
  33. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +81 -16
  34. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -4
  35. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
  36. package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
  37. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +80 -31
  38. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
  39. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  40. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +67 -38
  41. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
  42. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +7 -2
  43. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
  44. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +34 -16
  45. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
  46. package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
  47. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
  48. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
  49. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +19 -1
  50. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
  51. package/src/runtime/agent/orchestrator/providers/lib/tool-call-wire-ids.mjs +65 -0
  52. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
  53. package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
  54. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
  55. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
  56. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
  57. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
  58. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +8 -2
  59. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +10 -2
  60. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
  61. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
  62. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +3 -1
  63. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
  64. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
  65. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +14 -2
  66. package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -0
  67. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +17 -0
  68. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
  69. package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
  70. package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
  71. package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
  72. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
  73. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +5 -5
  74. package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
  75. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
  76. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +12 -1
  77. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +17 -6
  78. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +41 -10
  79. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +6 -4
  80. package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
  81. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +2 -0
  82. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
  83. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
  84. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
  85. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +21 -2
  86. package/src/runtime/agent/orchestrator/session/agent-room.mjs +149 -45
  87. package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
  88. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  89. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +14 -11
  90. package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
  91. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +68 -18
  92. package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
  93. package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
  94. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
  95. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +5 -1
  96. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +1 -1
  97. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
  98. package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
  99. package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +230 -0
  100. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +156 -25
  101. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
  102. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +21 -9
  103. package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
  104. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
  105. package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
  106. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
  107. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +189 -105
  108. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +9 -0
  109. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
  110. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +111 -9
  111. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +11 -14
  112. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
  113. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +38 -5
  114. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +21 -0
  115. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
  116. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
  117. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
  118. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +46 -4
  119. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
  120. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +18 -8
  121. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +4 -0
  122. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  123. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  124. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
  125. package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +99 -0
  126. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
  127. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
  128. package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
  129. package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
  130. package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +50 -0
  131. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
  132. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
  133. package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
  134. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +62 -47
  135. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +6 -8
  136. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +12 -1
  137. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
  138. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +7 -1
  139. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
  140. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +1 -1
  141. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
  142. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +5 -2
  143. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +42 -10
  144. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
  145. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +35 -4
  146. package/src/runtime/agent/orchestrator/session/routine-result.mjs +179 -16
  147. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -1
  148. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
  149. package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
  150. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
  151. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
  152. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
  153. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +8 -3
  154. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +36 -1
  155. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -1
  156. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
  157. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
  158. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
  159. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
  160. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
  161. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +49 -14
  162. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +134 -17
  163. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +35 -13
  164. package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
  165. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +4 -3
  166. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
  167. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
  168. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
  169. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
  170. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +6 -14
  171. package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
  172. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
  173. package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
  174. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
  175. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
  176. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +34 -21
  177. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
  178. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
  179. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
  180. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
  181. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
  182. package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
  183. package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
  184. package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
  185. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
  186. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
  187. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
  188. package/src/runtime/attachments/attachment-reference.mjs +155 -0
  189. package/src/runtime/attachments/limits.mjs +5 -1
  190. package/src/runtime/attachments/office-extract.mjs +3 -6
  191. package/src/runtime/attachments/pdf-extract.mjs +84 -10
  192. package/src/runtime/attachments/store.mjs +55 -30
  193. package/src/runtime/browser-bridge/client.mjs +73 -20
  194. package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
  195. package/src/runtime/channels/lib/scheduler.mjs +14 -7
  196. package/src/runtime/channels/lib/state-file.mjs +15 -15
  197. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
  198. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
  199. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
  200. package/src/runtime/channels/lib/worker-main.mjs +3 -2
  201. package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
  202. package/src/runtime/effort-judge/judge-client.mjs +6 -3
  203. package/src/runtime/effort-judge/model-install.mjs +16 -8
  204. package/src/runtime/effort-judge/model-manifest.json +7 -7
  205. package/src/runtime/local-provider/asset-installer.mjs +10 -4
  206. package/src/runtime/local-provider/catalog.mjs +5 -2
  207. package/src/runtime/local-provider/registered-models.mjs +64 -27
  208. package/src/runtime/local-provider/server.mjs +2 -3
  209. package/src/runtime/media/adapters/image-api.mjs +89 -0
  210. package/src/runtime/media/auth.mjs +64 -1
  211. package/src/runtime/media/catalog.mjs +66 -4
  212. package/src/runtime/media/jobs.mjs +4 -0
  213. package/src/runtime/media/lanes.mjs +46 -5
  214. package/src/runtime/media/media-usage.mjs +17 -0
  215. package/src/runtime/media/renditions.mjs +15 -15
  216. package/src/runtime/media/store.mjs +36 -35
  217. package/src/runtime/media/tool.mjs +8 -1
  218. package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
  219. package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
  220. package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
  221. package/src/runtime/memory/lib/pg/process.mjs +8 -4
  222. package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
  223. package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
  224. package/src/runtime/office/core/journal.mjs +7 -14
  225. package/src/runtime/office/design/library/design-library-core.mjs +4 -16
  226. package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
  227. package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
  228. package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
  229. package/src/runtime/office/pdf/pdf-render.mjs +30 -18
  230. package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
  231. package/src/runtime/office/portable/portable-soffice.mjs +4 -0
  232. package/src/runtime/shared/agent-kind-migration.mjs +200 -0
  233. package/src/runtime/shared/agent-kind.mjs +152 -0
  234. package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
  235. package/src/runtime/shared/agent-message-envelope.mjs +44 -0
  236. package/src/runtime/shared/agent-reap-state.d.mts +4 -0
  237. package/src/runtime/shared/agent-route-config.d.mts +1 -0
  238. package/src/runtime/shared/agent-route-config.mjs +22 -8
  239. package/src/runtime/shared/agent-store.mjs +149 -0
  240. package/src/runtime/shared/agent-task-starts.mjs +27 -0
  241. package/src/runtime/shared/atomic-file.d.mts +50 -0
  242. package/src/runtime/shared/atomic-file.mjs +394 -132
  243. package/src/runtime/shared/background-tasks.mjs +7 -4
  244. package/src/runtime/shared/bot-hops.mjs +30 -0
  245. package/src/runtime/shared/bot-split-migration.mjs +457 -0
  246. package/src/runtime/shared/bounded-download.mjs +4 -2
  247. package/src/runtime/shared/config-patch.mjs +20 -1
  248. package/src/runtime/shared/config.mjs +39 -9
  249. package/src/runtime/shared/connector-catalog.d.mts +1 -2
  250. package/src/runtime/shared/connector-catalog.mjs +2 -4
  251. package/src/runtime/shared/connector-triggers.mjs +2 -0
  252. package/src/runtime/shared/defaults-separation.mjs +6 -2
  253. package/src/runtime/shared/delegated-approval-host.mjs +74 -0
  254. package/src/runtime/shared/err-text.mjs +7 -1
  255. package/src/runtime/shared/error-presentation.mjs +113 -10
  256. package/src/runtime/shared/file-has-data.mjs +28 -0
  257. package/src/runtime/shared/handoff-artifacts.d.mts +20 -0
  258. package/src/runtime/shared/handoff-artifacts.mjs +61 -0
  259. package/src/runtime/shared/llm/provider-model-identities.mjs +6 -1
  260. package/src/runtime/shared/llm/usage-accounting.mjs +7 -1
  261. package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
  262. package/src/runtime/shared/model-route-fallback.mjs +79 -7
  263. package/src/runtime/shared/pending-agent-work.mjs +54 -0
  264. package/src/runtime/shared/plain-preview.mjs +22 -17
  265. package/src/runtime/shared/process-lifecycle.mjs +8 -12
  266. package/src/runtime/shared/projects.mjs +50 -41
  267. package/src/runtime/shared/provider-accounts.mjs +17 -12
  268. package/src/runtime/shared/routine-trigger.d.mts +6 -1
  269. package/src/runtime/shared/routine-trigger.mjs +14 -6
  270. package/src/runtime/shared/run-child.d.mts +3 -0
  271. package/src/runtime/shared/run-child.mjs +24 -3
  272. package/src/runtime/shared/sensitive-path-rules.d.mts +15 -0
  273. package/src/runtime/shared/sensitive-path-rules.mjs +256 -0
  274. package/src/runtime/shared/session-baseline-store.mjs +8 -2
  275. package/src/runtime/shared/singleton-owner.mjs +7 -6
  276. package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
  277. package/src/runtime/shared/staged-update.mjs +4 -2
  278. package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
  279. package/src/runtime/shared/tool-execution-contract.mjs +6 -0
  280. package/src/runtime/shared/transcript-metadata.mjs +28 -3
  281. package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
  282. package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
  283. package/src/runtime/shared/turn-snapshot.mjs +44 -2
  284. package/src/runtime/shared/usage-readout-totals.mjs +15 -13
  285. package/src/runtime/shared/user-cwd.mjs +9 -4
  286. package/src/runtime/shared/user-data-guard.mjs +46 -44
  287. package/src/runtime/shared/utf8-cut.mjs +10 -0
  288. package/src/runtime/shared/worktrees/activity-gate.mjs +269 -61
  289. package/src/runtime/shared/worktrees/registry.mjs +26 -26
  290. package/src/runtime/tidy/core-install.mjs +26 -4
  291. package/src/runtime/tidy/install.mjs +91 -23
  292. package/src/runtime/web-search/lib/config.mjs +4 -16
  293. package/src/session-runtime/agent-home-delivery.mjs +859 -136
  294. package/src/session-runtime/agent-room-delivery.mjs +167 -19
  295. package/src/session-runtime/agent-task-sessions.mjs +831 -69
  296. package/src/session-runtime/agent-tool-routing.mjs +1 -0
  297. package/src/session-runtime/boot/apis.mjs +58 -24
  298. package/src/session-runtime/boot/config.mjs +1 -0
  299. package/src/session-runtime/boot/facade.mjs +11 -1
  300. package/src/session-runtime/boot/infrastructure.mjs +14 -2
  301. package/src/session-runtime/boot/providers.mjs +16 -6
  302. package/src/session-runtime/boot/shared.mjs +12 -6
  303. package/src/session-runtime/boot/tools.mjs +2 -2
  304. package/src/session-runtime/bot-status.mjs +432 -0
  305. package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
  306. package/src/session-runtime/config-lifecycle.mjs +78 -32
  307. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
  308. package/src/session-runtime/handoff-attachments.mjs +593 -0
  309. package/src/session-runtime/held-requests.mjs +36 -33
  310. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
  311. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
  312. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +45 -6
  313. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
  314. package/src/session-runtime/lifecycle/teardown.mjs +11 -3
  315. package/src/session-runtime/mcp-glue/connect.mjs +100 -186
  316. package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
  317. package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
  318. package/src/session-runtime/media-api.mjs +5 -2
  319. package/src/session-runtime/model-route/route-persist.mjs +2 -2
  320. package/src/session-runtime/model-route/route-tuning.mjs +11 -2
  321. package/src/session-runtime/model-route/set-route.mjs +82 -2
  322. package/src/session-runtime/native-web-search.mjs +2 -2
  323. package/src/session-runtime/new-session-config.mjs +6 -1
  324. package/src/session-runtime/owned-automation-run.mjs +8 -1
  325. package/src/session-runtime/room-member-gate.mjs +16 -3
  326. package/src/session-runtime/route-resolution.mjs +21 -19
  327. package/src/session-runtime/route-state.mjs +1 -59
  328. package/src/session-runtime/routine-home-turn.mjs +5 -3
  329. package/src/session-runtime/runtime-bootstrap.mjs +5 -3
  330. package/src/session-runtime/runtime-paths.mjs +11 -5
  331. package/src/session-runtime/runtime-tunables.mjs +6 -0
  332. package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
  333. package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
  334. package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
  335. package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
  336. package/src/session-runtime/services/agent-tool/helpers.mjs +24 -5
  337. package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
  338. package/src/session-runtime/services/agent-tool/send-flow.mjs +36 -24
  339. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -0
  340. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
  341. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +12 -6
  342. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +5 -8
  343. package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
  344. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
  345. package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
  346. package/src/session-runtime/services/agent-tool.mjs +6 -0
  347. package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
  348. package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
  349. package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
  350. package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
  351. package/src/session-runtime/services/plugin-admin.mjs +26 -26
  352. package/src/session-runtime/services/provider-admin.mjs +14 -4
  353. package/src/session-runtime/services/schedule-access.mjs +33 -4
  354. package/src/session-runtime/services/seeds.mjs +4 -3
  355. package/src/session-runtime/services/usage-dashboard.mjs +1 -1
  356. package/src/session-runtime/services/usage-stats-model.mjs +2 -1
  357. package/src/session-runtime/session-create/materialize.mjs +2 -3
  358. package/src/session-runtime/session-create/session-options.mjs +15 -2
  359. package/src/session-runtime/settings-builtin-tools-api.mjs +63 -1
  360. package/src/session-runtime/settings-compaction-api.mjs +86 -39
  361. package/src/session-runtime/setup-tool/executor.mjs +20 -3
  362. package/src/session-runtime/setup-tool/extended-actions.mjs +19 -7
  363. package/src/session-runtime/setup-tool/settings-contract.mjs +37 -1
  364. package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
  365. package/src/session-runtime/skills-api.mjs +12 -5
  366. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +265 -0
  367. package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
  368. package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
  369. package/src/session-runtime/tool-policy/policy-stage.mjs +165 -0
  370. package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
  371. package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
  372. package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
  373. package/src/session-runtime/tool-policy-refresh.mjs +47 -358
  374. package/src/session-runtime/tool-surface.mjs +5 -3
  375. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +99 -81
  376. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  377. package/src/session-runtime/turn/turn-run.mjs +2 -1
  378. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
  379. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +578 -301
  380. package/src/session-runtime/workflow-agents-api/agent-route.mjs +113 -53
  381. package/src/session-runtime/workflow-agents-api/onboarding.mjs +11 -4
  382. package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
  383. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
  384. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
  385. package/src/session-runtime/workspace/workspace-state.mjs +7 -6
  386. package/src/standalone/daemon-agent-control.mjs +4 -1
  387. package/src/standalone/daemon-session-control.mjs +60 -11
  388. package/src/standalone/daemon.mjs +34 -4
  389. package/src/standalone/local-session-runtime.mjs +22 -14
  390. package/src/standalone/session-client.mjs +7 -2
  391. package/src/standalone/session-protocol.mjs +7 -0
  392. package/src/standalone/session-service/agent-tree/agent-turns.mjs +46 -3
  393. package/src/standalone/session-service/agent-tree.mjs +4 -3
  394. package/src/standalone/session-service/session-calls/session-turns.mjs +19 -0
  395. package/src/standalone/session-service.mjs +11 -0
  396. package/src/standalone/session-state-projection.mjs +19 -9
  397. package/src/standalone/session-tool-output.mjs +35 -9
  398. package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
  399. package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
  400. package/src/tui/app/slash-commands.mjs +1 -1
  401. package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
  402. package/src/tui/dist/index.mjs +599 -342
  403. package/src/tui/prompt-history-store.mjs +16 -21
  404. package/src/tui/session/context-state.mjs +4 -0
  405. package/src/tui/session/notice-surface.mjs +3 -4
  406. package/src/tui/session/session-api/intake/abort.mjs +19 -1
  407. package/src/tui/session/session-api/media.mjs +2 -1
  408. package/src/tui/session/session-api/routes-agent-definitions.mjs +75 -0
  409. package/src/tui/session/session-api/routes-write.mjs +203 -0
  410. package/src/tui/session/session-api/routes.mjs +23 -236
  411. package/src/tui/session/session-api/settings.mjs +14 -1
  412. package/src/tui/session/session-flow/drain.mjs +28 -3
  413. package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
  414. package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
  415. package/src/tui/session/session-flow/steering.mjs +8 -3
  416. package/src/tui/session/tool-approval.mjs +185 -20
  417. package/src/tui/session/transcript-intake.mjs +4 -0
  418. package/src/tui/session/turn-live-call.mjs +11 -3
  419. package/src/tui/session/turn.mjs +36 -17
  420. package/src/tui/transcript-tool-failures.mjs +8 -2
  421. package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
  422. package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
  423. package/src/rules/agent-mode/ROUTINE.md +0 -6
@@ -0,0 +1,30 @@
1
+ // Bot-to-bot chain budget. Every send a bot makes carries `hop` (stored in the
2
+ // transcript meta of the message it delivers, and in the report that answers
3
+ // it). A turn knows the hop of the submission that started it (`turnHop`,
4
+ // stamped on the session when the turn is prepared); a send made in it goes
5
+ // out at turnHop + 1. A turn started by the user or by a routine starts at 0;
6
+ // a runtime notification keeps the hop of the work it belongs to.
7
+ export const MAX_BOT_HOPS = 3;
8
+ export const BOT_HOP_LIMIT_ERROR = 'session: Bot-to-bot chain limit reached; ask the user before continuing.';
9
+
10
+ const hopOf = (value) => (Number.isInteger(value) && value > 0 ? value : 0);
11
+
12
+ /** The hop a turn starts at: its submission's own, else (a runtime
13
+ * notification, `runtimeSource`) the hop the session was already at, else 0. */
14
+ export function startingTurnHop(transcriptMeta, runtimeSource, previous) {
15
+ return hopOf(transcriptMeta?.hop) || (runtimeSource ? hopOf(previous) : 0);
16
+ }
17
+
18
+ /** Stamp the starting hop on `session` for the turn about to run. */
19
+ export function stampTurnHop(session, transcriptMeta, runtimeSource) {
20
+ session.turnHop = startingTurnHop(transcriptMeta, runtimeSource, session.turnHop);
21
+ }
22
+
23
+ /** A bot's message steered into the running turn raises the turn's hop. */
24
+ export function raiseTurnHop(session, transcriptMeta) {
25
+ const hop = hopOf(transcriptMeta?.hop);
26
+ if (session && hop > hopOf(session.turnHop)) session.turnHop = hop;
27
+ }
28
+
29
+ /** The hop of the turn `session` is in. */
30
+ export const turnHopOf = (session) => hopOf(session?.turnHop);
@@ -0,0 +1,457 @@
1
+ // The one-time 1.3.1 split of bots out of agents/: every definition the
2
+ // read-only rule (agent-kind.mjs) classifies as a bot is COPIED from
3
+ // agents/<id>/ to bots/<id>/ with its own model route, and Mixy's identity
4
+ // from agents/lead/ to bots/mixy/. Nothing under agents/ and no session file
5
+ // is ever moved, rewritten or deleted: the originals stay for rollback and
6
+ // the manifest (bots/migration.json) tells the agent catalog which agents/
7
+ // ids are bots now. Home threads keep their ids — a bot's is still
8
+ // sess_home_<id>, Mixy's still sess_home_lead (MIXY_LEGACY_HOME_SESSION_ID) —
9
+ // so every transcript stays whole and continuable.
10
+ //
11
+ // Every later start re-syncs (migrateBotsToOwnStore): it adds nothing and
12
+ // rewrites nothing unless agents/ gained or changed a bot since (a downgrade
13
+ // to 1.3.0 and back). The re-sync never overwrites a bots/ edit: a copy is
14
+ // replaced only when its fingerprint still equals the one recorded when it
15
+ // was made (bots/ untouched since) AND its agents/ original's fingerprint
16
+ // differs and has newer files; when both sides changed, both are kept (the
17
+ // bots/ copy in use, the 1.3.0 edit left in agents/, logged). Every copy or
18
+ // replacement is preceded by a user-data backup that holds agents/ and bots/.
19
+ //
20
+ // One process migrates at a time (an exclusive lock file beside the profile,
21
+ // reclaimed when its owner died); another starting meanwhile waits for it,
22
+ // or after LOCK_WAIT_MS reads the profile as it is this start. Publishing
23
+ // the manifest is the commit point: a failure before it undoes only this
24
+ // run's own copies and leaves the profile as it was (the next start tries
25
+ // again); nothing after it is undone. A replaced bots/<id> is set aside as
26
+ // `.<id>.<run>.old` until then; the manifest names the committed ones
27
+ // (`retired`), which any later start removes, while one it does not name is
28
+ // an interrupted run's and is put back.
29
+ import { createHash } from 'node:crypto';
30
+ import {
31
+ cpSync,
32
+ existsSync,
33
+ mkdirSync,
34
+ readdirSync,
35
+ readFileSync,
36
+ renameSync,
37
+ rmSync,
38
+ statSync,
39
+ writeFileSync,
40
+ } from 'node:fs';
41
+ import { basename, dirname, join, relative } from 'node:path';
42
+ import { withFileLockSync, writeJsonAtomicSync } from './atomic-file.mjs';
43
+ import { evidenceKind, homeThreadEvidence } from './agent-kind.mjs';
44
+ import { BOT_AGENT_KIND } from './agent-route-config.mjs';
45
+ import { readMarkdownDocument } from './markdown-frontmatter.mjs';
46
+ import {
47
+ BOT_MIGRATION_MANIFEST,
48
+ BOT_MIGRATION_VERSION,
49
+ BOTS_DIR,
50
+ MIXY_BOT_ID,
51
+ migratedBotIds,
52
+ readBotMigration,
53
+ } from './agent-store.mjs';
54
+
55
+ export { BOT_MIGRATION_MANIFEST, BOT_MIGRATION_VERSION, BOTS_DIR, MIXY_BOT_ID, migratedBotIds, readBotMigration };
56
+ /** Mixy's home thread keeps the id it always had: its messages never move. */
57
+ export const MIXY_LEGACY_HOME_SESSION_ID = 'sess_home_lead';
58
+
59
+ /** The cross-process migration lock, beside (never inside) bots/ and agents/. */
60
+ export const BOT_MIGRATION_LOCK = '.bot-split.lock';
61
+ const LOCK_WAIT_MS = 10_000;
62
+ /** What the split's backup carries besides the routine user data: every
63
+ * definition it copies from and every copy a re-sync can replace. */
64
+ export const BOT_SPLIT_BACKUP_DIRS = Object.freeze(['agents', BOTS_DIR]);
65
+
66
+ const DELETED_MARKER = '.deleted';
67
+ const ARCHIVED_FILE = 'AGENT.md.archived';
68
+ // Mixy's own files under agents/lead/ (its identity, persona, 일하는 방식, face).
69
+ const MIXY_IDENTITY_KEYS = ['name', 'description', 'avatar', 'connectors', 'created'];
70
+
71
+ function readJson(file) {
72
+ try {
73
+ const value = JSON.parse(readFileSync(file, 'utf8'));
74
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
79
+
80
+ function writeJson(file, value) {
81
+ writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
82
+ }
83
+
84
+ // The definition file a bot's kind is read from: AGENT.md, or the archived
85
+ // copy of a deleted bot (its chat is kept, so it moves with the others).
86
+ export function definitionFile(dir) {
87
+ if (existsSync(join(dir, 'AGENT.md'))) return join(dir, 'AGENT.md');
88
+ if (existsSync(join(dir, DELETED_MARKER)) && existsSync(join(dir, ARCHIVED_FILE))) return join(dir, ARCHIVED_FILE);
89
+ return '';
90
+ }
91
+
92
+ /** The agents/ ids the rule classifies as bots (Mixy's `lead` never). */
93
+ export function classifyBotIds({ dataDir, shippedRoot }) {
94
+ const root = join(dataDir, 'agents');
95
+ let entries = [];
96
+ try {
97
+ entries = readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory());
98
+ } catch {
99
+ return [];
100
+ }
101
+ const bots = [];
102
+ for (const { name: id } of entries) {
103
+ if (id === 'lead') continue;
104
+ const dir = join(root, id);
105
+ const file = definitionFile(dir);
106
+ if (!file) continue;
107
+ const doc = readMarkdownDocument(readFileSync(file, 'utf8'));
108
+ // The evidence rule itself, whatever the store says (a re-sync runs it
109
+ // with the store active).
110
+ const kind = evidenceKind({
111
+ frontmatterKind: doc.frontmatter.kind,
112
+ dataDir,
113
+ shippedRoot,
114
+ id,
115
+ manifest: readJson(join(dir, 'agent.json')) || {},
116
+ body: doc.body,
117
+ });
118
+ if (kind === BOT_AGENT_KIND) bots.push(id);
119
+ }
120
+ return bots.sort();
121
+ }
122
+
123
+ /** Whether agents/lead holds a Mixy the user made or used: a name, face,
124
+ * connectors, persona or 일하는 방식, or any conversation in its thread —
125
+ * never v1.3.0's untouched automatic `{"created": true}`. */
126
+ export function mixyToMigrate(dataDir) {
127
+ const dir = join(dataDir, 'agents', 'lead');
128
+ const manifest = readJson(join(dir, 'agent.json')) || {};
129
+ if (manifest.created !== true) return false;
130
+ const identity =
131
+ Boolean(String(manifest.name ?? '').trim()) ||
132
+ manifest.avatar != null ||
133
+ manifest.connectors != null ||
134
+ existsSync(join(dir, 'PERSONA.md')) ||
135
+ existsSync(join(dir, 'AGENT.md'));
136
+ return identity || homeThreadEvidence(dataDir, 'lead').chatted;
137
+ }
138
+
139
+ // The provider/model Mixy's thread runs on, or null.
140
+ function threadRoute(dataDir, sessionId) {
141
+ const record = readJson(join(dataDir, 'sessions', `${sessionId}.json`));
142
+ const provider = String(record?.provider || '').trim();
143
+ const model = String(record?.model || '').trim();
144
+ return provider && model ? { provider, model } : null;
145
+ }
146
+
147
+ const completeRoute = (route) =>
148
+ route && typeof route === 'object' && String(route.provider || '').trim() && String(route.model || '').trim()
149
+ ? route
150
+ : null;
151
+
152
+ // --- Content fingerprints (for the re-sync rule) ---
153
+ // A folder's files as one hash; agent.json without the keys only the store
154
+ // writes (a bot's route, Mixy's thread id and created flag), so changing a
155
+ // bot's model on 1.3.1 never counts as editing its definition.
156
+ const STORE_ONLY_KEYS = ['route', 'homeSessionId', 'created'];
157
+
158
+ function folderFiles(dir) {
159
+ try {
160
+ return readdirSync(dir, { recursive: true, withFileTypes: true })
161
+ .filter((entry) => entry.isFile())
162
+ .map((entry) => join(entry.parentPath ?? entry.path, entry.name))
163
+ .filter((file) => !file.endsWith('.lock'))
164
+ .sort();
165
+ } catch {
166
+ return [];
167
+ }
168
+ }
169
+
170
+ // A JSON value with every object's keys sorted, nested ones included (a
171
+ // replacer array would drop every nested key not in the top-level list).
172
+ function canonical(value) {
173
+ if (Array.isArray(value)) return value.map(canonical);
174
+ if (!value || typeof value !== 'object') return value;
175
+ return Object.fromEntries(
176
+ Object.keys(value)
177
+ .sort()
178
+ .map((key) => [key, canonical(value[key])])
179
+ );
180
+ }
181
+
182
+ function folderHash(dir) {
183
+ const hash = createHash('sha256');
184
+ for (const file of folderFiles(dir)) {
185
+ let bytes = readFileSync(file);
186
+ const manifest = basename(file) === 'agent.json' && dirname(file) === dir ? readJson(file) : null;
187
+ if (manifest) {
188
+ for (const key of STORE_ONLY_KEYS) delete manifest[key];
189
+ bytes = Buffer.from(JSON.stringify(canonical(manifest)));
190
+ }
191
+ hash
192
+ .update(`${relative(dir, file)}\0`)
193
+ .update(bytes)
194
+ .update('\0');
195
+ }
196
+ return hash.digest('hex').slice(0, 24);
197
+ }
198
+
199
+ function newestChange(dir) {
200
+ let newest = 0;
201
+ for (const file of folderFiles(dir)) newest = Math.max(newest, statSync(file).mtimeMs);
202
+ return newest;
203
+ }
204
+
205
+ // A staging or set-aside folder an earlier run left (`.<id>.<pid>-<time>.<kind>`).
206
+ // Swept under the migration lock, so no run that made one is still going.
207
+ const LEFTOVER = /^\.(.+)\.(\d+)-\d+\.(staging|old)$/;
208
+ const removeBestEffort = (path) => {
209
+ try {
210
+ rmSync(path, { recursive: true, force: true });
211
+ } catch {
212
+ // a later start removes it
213
+ }
214
+ };
215
+ // A staging folder goes; a set-aside `.old` the manifest names as retired is
216
+ // a committed swap's rest and goes; any other `.old` is the bot as it was
217
+ // before a run that never committed: it is put back (a failure here fails
218
+ // this run, which then leaves the profile as it is).
219
+ function sweepLeftovers(botsRoot, retired) {
220
+ for (const name of existsSync(botsRoot) ? readdirSync(botsRoot) : []) {
221
+ const match = LEFTOVER.exec(name);
222
+ if (!match) continue;
223
+ const path = join(botsRoot, name);
224
+ if (match[3] === 'staging' || retired.has(name)) {
225
+ removeBestEffort(path);
226
+ continue;
227
+ }
228
+ const target = join(botsRoot, match[1]);
229
+ if (existsSync(target)) {
230
+ const aside = join(botsRoot, `.${match[1]}.${match[2]}-0.staging`);
231
+ renameSync(target, aside);
232
+ removeBestEffort(aside);
233
+ }
234
+ renameSync(path, target);
235
+ }
236
+ }
237
+
238
+ // The finished split's record as on disk now (never a cached read: another
239
+ // process may have just written it).
240
+ function readManifestNow(botsRoot) {
241
+ const manifest = readJson(join(botsRoot, BOT_MIGRATION_MANIFEST));
242
+ return manifest?.version === BOT_MIGRATION_VERSION ? manifest : null;
243
+ }
244
+
245
+ /**
246
+ * Runs the split, and on every later start the re-sync (`backup()` first
247
+ * whenever something is to be copied). `routeOf(id)` is the bot's route in
248
+ * the config (null when none); `leadRoute` the Lead's route at this time
249
+ * (Mixy's route when its thread names none).
250
+ *
251
+ * First run: every bot in agents/ is copied to bots/<id> (a bots/<id>
252
+ * already there — a concurrent run's, a hand-made or restored folder — is
253
+ * kept as it is and adopted), and a used Mixy to bots/mixy.
254
+ * Later runs (the manifest exists), so a downgrade to 1.3.0 and back loses
255
+ * nothing:
256
+ * - a bot 1.3.0 made in agents/ after the split is copied over as above;
257
+ * - a migrated bot whose agents/ original was edited after its copy (newer
258
+ * files, other content) while its bots/ copy was left as copied is
259
+ * copied again, keeping its bots/ route; when both sides changed the
260
+ * bots/ copy wins and the edit stays in agents/ (logged);
261
+ * - a migrated bot whose bots/<id> is gone (deleted for good) never comes
262
+ * back from its agents/ original;
263
+ * - Mixy, once made or used in agents/lead, fills what bots/mixy lacks.
264
+ * Every copy is staged and swapped in by rename; a failure before the
265
+ * manifest is published puts back what this run replaced, removes what it
266
+ * added, writes no manifest and leaves agents/ and every session untouched.
267
+ *
268
+ * Runs under the cross-process migration lock: a second process waits up to
269
+ * `lockWaitMs`, then reads the profile as it is (`{ failed, busy }`).
270
+ * `afterPublish` is an interleaving point for tests (a failure there is one
271
+ * after the commit point).
272
+ */
273
+ export function migrateBotsToOwnStore(options) {
274
+ const { dataDir, log, lockWaitMs = LOCK_WAIT_MS } = options;
275
+ try {
276
+ return withFileLockSync(join(dataDir, BOT_MIGRATION_LOCK), () => migrateLocked(options), {
277
+ timeoutMs: lockWaitMs,
278
+ });
279
+ } catch (error) {
280
+ // Only the lock fails here (migrateLocked reports its own failures).
281
+ const done = readBotMigration(dataDir);
282
+ log?.(`[bot-split] another start is migrating; this one reads the profile as it is: ${error?.message || error}`);
283
+ return { skipped: false, failed: true, busy: true, bots: done?.bots || [], mixy: done?.mixy === true };
284
+ }
285
+ }
286
+
287
+ function migrateLocked({ dataDir, shippedRoot, routeOf = () => null, leadRoute = null, backup, log, afterPublish }) {
288
+ const botsRoot = join(dataDir, BOTS_DIR);
289
+ const done = readManifestNow(botsRoot);
290
+ const runTag = `${process.pid}-${Date.now()}`;
291
+ // What this run changed, undone in reverse on a failure before the commit.
292
+ const changes = [];
293
+ let backedUp = false;
294
+ const beforeCopy = () => {
295
+ if (backedUp) return;
296
+ backedUp = true;
297
+ backup?.();
298
+ };
299
+ // Swaps a staged folder in for bots/<id> (atomic renames); the replaced
300
+ // folder is set aside as `.old` until the commit.
301
+ const swapIn = (id, staging, { replace }) => {
302
+ const target = join(botsRoot, id);
303
+ let old = '';
304
+ if (replace && existsSync(target)) {
305
+ old = join(botsRoot, `.${id}.${runTag}.old`);
306
+ renameSync(target, old);
307
+ }
308
+ try {
309
+ renameSync(staging, target);
310
+ } catch (error) {
311
+ removeBestEffort(staging);
312
+ // Should this fail too, the next start's sweep puts the `.old` back.
313
+ if (old) renameSync(old, target);
314
+ throw error;
315
+ }
316
+ changes.push({ target, old });
317
+ };
318
+ const stage = (id, source) => {
319
+ const staging = join(botsRoot, `.${id}.${runTag}.staging`);
320
+ rmSync(staging, { recursive: true, force: true });
321
+ if (source) cpSync(source, staging, { recursive: true });
322
+ else mkdirSync(staging, { recursive: true });
323
+ return staging;
324
+ };
325
+ let next;
326
+ try {
327
+ mkdirSync(botsRoot, { recursive: true });
328
+ sweepLeftovers(botsRoot, new Set(done?.retired || []));
329
+ // Committed set-asides the sweep could not remove stay named for a later start.
330
+ const retired = (done?.retired || []).filter((name) => existsSync(join(botsRoot, name)));
331
+ const copies = { ...(done?.copies || {}) };
332
+ const bots = new Set(done?.bots || []);
333
+ const record = (id, sourceDir) => {
334
+ copies[id] = { source: folderHash(sourceDir), copy: folderHash(join(botsRoot, id)), at: Date.now() };
335
+ };
336
+ // 1. Bots in agents/ not yet in the store.
337
+ for (const id of classifyBotIds({ dataDir, shippedRoot })) {
338
+ if (bots.has(id)) continue;
339
+ const source = join(dataDir, 'agents', id);
340
+ if (!existsSync(join(botsRoot, id))) {
341
+ beforeCopy();
342
+ const route = completeRoute(routeOf(id));
343
+ const staging = stage(id, source);
344
+ const manifest = readJson(join(staging, 'agent.json')) || {};
345
+ writeJson(join(staging, 'agent.json'), { ...manifest, ...(route ? { route } : {}) });
346
+ swapIn(id, staging, { replace: false });
347
+ }
348
+ bots.add(id);
349
+ record(id, source);
350
+ }
351
+ // 2. Migrated bots edited in agents/ after their copy (a 1.3.0 round trip).
352
+ for (const id of done ? [...bots] : []) {
353
+ const source = join(dataDir, 'agents', id);
354
+ const target = join(botsRoot, id);
355
+ const entry = copies[id];
356
+ if (!entry || !existsSync(target) || !existsSync(source)) continue;
357
+ const sourceChanged = folderHash(source) !== entry.source && newestChange(source) > entry.at;
358
+ if (!sourceChanged) continue;
359
+ if (folderHash(target) !== entry.copy) {
360
+ log?.(`[bot-split] ${id}: edited in both agents/ and bots/; the bots/ copy is kept`);
361
+ continue;
362
+ }
363
+ beforeCopy();
364
+ const route = readJson(join(target, 'agent.json'))?.route;
365
+ const staging = stage(id, source);
366
+ const manifest = readJson(join(staging, 'agent.json')) || {};
367
+ writeJson(join(staging, 'agent.json'), { ...manifest, ...(completeRoute(route) ? { route } : {}) });
368
+ swapIn(id, staging, { replace: true });
369
+ record(id, source);
370
+ }
371
+ // 3. Mixy: made or used in agents/lead; bots/mixy keeps what it has.
372
+ let mixy = done?.mixy === true;
373
+ const leadDir = join(dataDir, 'agents', 'lead');
374
+ const mixyDir = join(botsRoot, MIXY_BOT_ID);
375
+ const mixyEntry = copies[MIXY_BOT_ID];
376
+ const leadEdited =
377
+ mixyEntry &&
378
+ existsSync(mixyDir) &&
379
+ folderHash(leadDir) !== mixyEntry.source &&
380
+ newestChange(leadDir) > mixyEntry.at;
381
+ if (mixyToMigrate(dataDir) && (!mixyEntry || leadEdited)) {
382
+ const current = existsSync(mixyDir) ? readJson(join(mixyDir, 'agent.json')) || {} : {};
383
+ const untouched = mixyEntry && folderHash(mixyDir) === mixyEntry.copy;
384
+ if (mixyEntry && !untouched) {
385
+ log?.('[bot-split] Mixy: edited in both agents/lead and bots/mixy; bots/mixy is kept');
386
+ } else {
387
+ beforeCopy();
388
+ // A first copy fills what bots/mixy lacks (the host may have written
389
+ // its flag or route there); a re-sync takes agents/lead's identity.
390
+ const staging = stage(MIXY_BOT_ID, existsSync(mixyDir) && !mixyEntry ? mixyDir : null);
391
+ for (const name of folderFiles(leadDir).map((file) => relative(leadDir, file))) {
392
+ if (name === 'agent.json' || (existsSync(join(staging, name)) && !mixyEntry)) continue;
393
+ mkdirSync(dirname(join(staging, name)), { recursive: true });
394
+ cpSync(join(leadDir, name), join(staging, name));
395
+ }
396
+ const lead = readJson(join(leadDir, 'agent.json')) || {};
397
+ const identity = Object.fromEntries(
398
+ MIXY_IDENTITY_KEYS.filter((key) => key in lead).map((key) => [key, lead[key]])
399
+ );
400
+ const route =
401
+ completeRoute(current.route) || threadRoute(dataDir, MIXY_LEGACY_HOME_SESSION_ID) || completeRoute(leadRoute);
402
+ writeJson(join(staging, 'agent.json'), {
403
+ ...identity,
404
+ ...(mixyEntry ? {} : current),
405
+ ...(typeof current.created === 'boolean' ? { created: current.created } : {}),
406
+ ...(route ? { route } : {}),
407
+ homeSessionId: MIXY_LEGACY_HOME_SESSION_ID,
408
+ });
409
+ swapIn(MIXY_BOT_ID, staging, { replace: true });
410
+ record(MIXY_BOT_ID, leadDir);
411
+ mixy = true;
412
+ }
413
+ }
414
+ retired.push(...changes.filter(({ old }) => old).map(({ old }) => basename(old)));
415
+ next = {
416
+ version: BOT_MIGRATION_VERSION,
417
+ completedAt: done?.completedAt || new Date().toISOString(),
418
+ bots: [...bots].sort(),
419
+ mixy,
420
+ mixyHomeSessionId: mixy ? MIXY_LEGACY_HOME_SESSION_ID : null,
421
+ copies,
422
+ ...(retired.length ? { retired: retired.sort() } : {}),
423
+ };
424
+ // The commit point: once this rename lands, nothing of this run is undone.
425
+ if (!done || changes.length || JSON.stringify(next) !== JSON.stringify(done)) {
426
+ writeJsonAtomicSync(join(botsRoot, BOT_MIGRATION_MANIFEST), next, { mode: 0o644 });
427
+ }
428
+ } catch (error) {
429
+ // Undo this run: what it added goes, what it replaced comes back. A step
430
+ // that fails leaves an unretired `.old` the next start's sweep puts back.
431
+ for (const { target, old } of changes.reverse()) {
432
+ try {
433
+ const aside = join(botsRoot, `.${basename(target)}.${runTag}.staging`);
434
+ renameSync(target, aside);
435
+ if (old) renameSync(old, target);
436
+ removeBestEffort(aside);
437
+ } catch {
438
+ // the leftover sweep of a later start finishes it
439
+ }
440
+ }
441
+ for (const name of existsSync(botsRoot) ? readdirSync(botsRoot) : []) {
442
+ if (name.endsWith(`.${runTag}.staging`)) removeBestEffort(join(botsRoot, name));
443
+ }
444
+ log?.(`[bot-split] migration failed; the profile is unchanged: ${error?.message || error}`);
445
+ return { skipped: false, failed: true, bots: done?.bots || [], mixy: done?.mixy === true };
446
+ }
447
+ // Committed. Removing the set-aside copies is best-effort: the manifest
448
+ // names them as retired, so a later start removes what is left.
449
+ try {
450
+ afterPublish?.();
451
+ for (const { old } of changes) if (old) rmSync(old, { recursive: true, force: true });
452
+ } catch (error) {
453
+ log?.(`[bot-split] committed; leftover cleanup is retried next start: ${error?.message || error}`);
454
+ }
455
+ if (done && !changes.length) return { skipped: true, bots: next.bots, mixy: next.mixy };
456
+ return { skipped: false, bots: next.bots, mixy: next.mixy, changed: changes.map(({ target }) => basename(target)) };
457
+ }
@@ -55,7 +55,7 @@ export async function readResponseBuffer(response, { maxBytes, label = 'download
55
55
  export async function streamResponseToFile(
56
56
  response,
57
57
  destPath,
58
- { maxBytes, expectedBytes = 0, label = 'download', onProgress = null } = {}
58
+ { maxBytes, expectedBytes = 0, label = 'download', onProgress = null, signal = undefined } = {}
59
59
  ) {
60
60
  const { maximum, expected } = downloadBounds(maxBytes, expectedBytes, label);
61
61
  const advertised = responseByteLength(response, maximum, label);
@@ -94,7 +94,9 @@ export async function streamResponseToFile(
94
94
  });
95
95
 
96
96
  try {
97
- await pipeline(response.body, guard, createWriteStream(destPath));
97
+ // flush: the bytes reach disk before the caller installs the file, so a
98
+ // crash cannot leave an installed binary that is all NUL.
99
+ await pipeline(response.body, guard, createWriteStream(destPath, { flush: true }), { signal });
98
100
  return downloaded;
99
101
  } catch (error) {
100
102
  try {
@@ -43,6 +43,25 @@ function applyField(current, path, change) {
43
43
  return next;
44
44
  }
45
45
 
46
+ const isSet = (value) => value !== undefined && value !== null && value !== '';
47
+
48
+ function valueAt(source, path) {
49
+ let value = source;
50
+ for (const key of path) {
51
+ if (!isPlainObject(value) || !Object.hasOwn(value, key)) return undefined;
52
+ value = value[key];
53
+ }
54
+ return value;
55
+ }
56
+
57
+ /** A change with `unlessSet: [path…]` is skipped when the stored value at that
58
+ * path is already set. It is judged against `current`, the state before the
59
+ * whole patch, so the changes of one patch that share a guard stand or fall
60
+ * together whatever their order. */
46
61
  export function applyConfigPatch(current, changes) {
47
- return changes.reduce((next, change) => applyField(next, change.path, change), current);
62
+ return changes.reduce(
63
+ (next, change) =>
64
+ change.unlessSet && isSet(valueAt(current, change.unlessSet)) ? next : applyField(next, change.path, change),
65
+ current
66
+ );
48
67
  }
@@ -2,12 +2,14 @@
2
2
  * Unified config reader/writer.
3
3
  * Single file: mixdog-config.json with sections such as channels, agent, and memory.
4
4
  */
5
- import { readFileSync, statSync, mkdirSync, existsSync, watch as fsWatch } from 'node:fs';
6
- import { join, dirname } from 'node:path';
5
+ import { readFileSync, readdirSync, statSync, mkdirSync, existsSync, watch as fsWatch } from 'node:fs';
6
+ import { basename, join, dirname } from 'node:path';
7
7
  import { createRequire } from 'node:module';
8
8
  import { resolvePluginData } from './plugin-paths.mjs';
9
9
  import { hasOwn, isPlainObject } from './object.mjs';
10
10
  import {
11
+ parseJsonText,
12
+ quarantinePath,
11
13
  renameWithRetrySync,
12
14
  writeJsonAtomicSync,
13
15
  writeJsonAtomicAsync,
@@ -226,7 +228,7 @@ function readConfigFile() {
226
228
  const raw = readConfigRawCached();
227
229
  if (raw == null) return null;
228
230
  try {
229
- return JSON.parse(raw);
231
+ return parseJsonText(raw);
230
232
  } catch (err) {
231
233
  // Quarantine a malformed mixdog-config.json so the next boot starts fresh
232
234
  // instead of looping on a broken file. A cached raw string that fails to
@@ -346,21 +348,47 @@ function readAll() {
346
348
  }
347
349
 
348
350
  function quarantineMalformedConfig(parseErr) {
349
- const corrupt = `${configPath()}.corrupt-${Date.now()}`;
351
+ const corrupt = quarantinePath(configPath());
352
+ let keptAside = false;
350
353
  try {
351
354
  if (existsSync(configPath())) renameWithRetrySync(configPath(), corrupt);
355
+ keptAside = existsSync(corrupt);
352
356
  } catch {}
353
357
  process.stderr.write(
354
358
  `[config] mixdog-config.json is malformed (${parseErr.message}). Renamed to ${corrupt}. Restore it or delete to start fresh.\n`
355
359
  );
360
+ return keptAside;
356
361
  }
357
362
 
358
- function restoreAllForRmWOrThrow(reason) {
363
+ // A damaged config that was kept aside (its bytes preserved as
364
+ // mixdog-config.json.corrupt-<ts>) is not a lost config: those bytes are the
365
+ // safety net, so with no backup to restore a save may start from an empty
366
+ // config. Every save also snapshots a backup, so a stale copy from an earlier
367
+ // incident never reaches this point while a backup of a later config exists.
368
+ function hasQuarantinedCopy() {
369
+ const dir = dirname(configPath());
370
+ const prefix = `${basename(configPath())}.corrupt-`;
371
+ try {
372
+ return readdirSync(dir).some((name) => name.startsWith(prefix));
373
+ } catch {
374
+ return false;
375
+ }
376
+ }
377
+
378
+ // `keptAside`: the unusable config's bytes are preserved on disk, so with no
379
+ // backup to restore a save starts from an empty config instead of refusing.
380
+ function restoreAllForRmWOrThrow(reason, keptAside = false) {
359
381
  const restored = loadLatestMixdogConfigFromBackup(resolvePluginData(), { fresh: true });
360
382
  if (restored && isPlainObject(restored)) {
361
383
  process.stderr.write(`[config] RMW read: restored mixdog-config.json from latest user-data backup (${reason})\n`);
362
384
  return restored;
363
385
  }
386
+ if (keptAside) {
387
+ process.stderr.write(
388
+ `[config] RMW read: mixdog-config.json is unusable (${reason}); its bytes are kept aside and no backup exists; starting empty\n`
389
+ );
390
+ return {};
391
+ }
364
392
  const legacy = findLegacyUserDataEvidence(resolvePluginData(), { fresh: true });
365
393
  if (legacy) {
366
394
  const err = new Error(
@@ -381,6 +409,9 @@ function restoreAllForRmWOrThrow(reason) {
381
409
  * Read-modify-write baseline. ENOENT on a never-initialized data dir → {}.
382
410
  * Malformed on-disk config (or missing config after prior init) → restore
383
411
  * from backup or throw; never silently collapse to a one-section overwrite.
412
+ * The exception is a damaged config whose bytes were kept aside as
413
+ * .corrupt-<ts>: with no backup to restore, the save starts from an empty
414
+ * config. A config that cannot be READ (I/O error) still throws.
384
415
  */
385
416
  function readAllForRmW() {
386
417
  let raw;
@@ -389,7 +420,7 @@ function readAllForRmW() {
389
420
  } catch (err) {
390
421
  if (err.code === 'ENOENT') {
391
422
  if (isUserDataRecoveryExpected(resolvePluginData(), { fresh: true })) {
392
- return restoreAllForRmWOrThrow('config file missing after user-data was initialized');
423
+ return restoreAllForRmWOrThrow('config file missing after user-data was initialized', hasQuarantinedCopy());
393
424
  }
394
425
  return {};
395
426
  }
@@ -397,12 +428,11 @@ function readAllForRmW() {
397
428
  throw err;
398
429
  }
399
430
  try {
400
- const parsed = JSON.parse(raw);
431
+ const parsed = parseJsonText(raw);
401
432
  if (!isPlainObject(parsed)) throw new SyntaxError('config root must be a JSON object');
402
433
  return parsed;
403
434
  } catch (parseErr) {
404
- quarantineMalformedConfig(parseErr);
405
- return restoreAllForRmWOrThrow(parseErr.message);
435
+ return restoreAllForRmWOrThrow(parseErr.message, quarantineMalformedConfig(parseErr));
406
436
  }
407
437
  }
408
438
 
@@ -19,8 +19,7 @@ export type ConnectorBackend =
19
19
  placeholder?: string;
20
20
  /** Tools that ask before they run unless the agent set them. */
21
21
  askTools?: readonly string[];
22
- }
23
- | { kind: 'builtin'; feature: string };
22
+ };
24
23
 
25
24
  export interface ConnectorCatalogEntry {
26
25
  id: string;
@@ -4,9 +4,8 @@
4
4
  // explicitly:
5
5
  // oauth — a remote MCP server that signs in with MCP OAuth;
6
6
  // apiKey — an MCP server that takes one key (an HTTP header or a stdio
7
- // environment variable);
8
- // builtin — a Mixdog built-in feature (GitHub is the GitHub CLI of the
9
- // Git & GitHub built-in, never a GitHub MCP);
7
+ // environment variable).
8
+ // GitHub is not a connector: it is the signed-in GitHub CLI (github tool).
10
9
  // A connected catalog service is an ordinary user MCP server whose config
11
10
  // carries `connector: <id>`. An existing custom server is never adopted into
12
11
  // the catalog row.
@@ -50,7 +49,6 @@ export const CONNECTOR_CATALOG = Object.freeze(
50
49
  { id: 'airtable', name: 'Airtable', backend: oauth('https://mcp.airtable.com/mcp') },
51
50
  { id: 'confluence', name: 'Confluence', backend: oauth('https://mcp.atlassian.com/v1/mcp/authv2') },
52
51
  { id: 'dropbox', name: 'Dropbox', backend: oauth('https://mcp.dropbox.com/mcp') },
53
- { id: 'github', name: 'GitHub', backend: Object.freeze({ kind: 'builtin', feature: 'git' }) },
54
52
  {
55
53
  id: 'gmail',
56
54
  name: 'Gmail',