mixdog 1.3.1 → 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 (377) hide show
  1. package/README.md +8 -4
  2. package/package.json +2 -2
  3. package/scripts/native-release-status.mjs +238 -0
  4. package/src/defaults/shipped-history.json +5 -1
  5. package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
  6. package/src/defaults/skills/setup/SKILL.md +23 -1
  7. package/src/defaults/skills/setup/references/actions.md +50 -3
  8. package/src/defaults/skills/setup/references/surfaces.md +23 -3
  9. package/src/defaults/skills/setup-guide/SKILL.md +7 -3
  10. package/src/defaults/skills/visualize/SKILL.md +9 -6
  11. package/src/defaults/skills/visualize/references/craft.md +8 -1
  12. package/src/help.mjs +1 -1
  13. package/src/lib/keychain-cjs.cjs +4 -1
  14. package/src/lib/rules-builder.cjs +14 -7
  15. package/src/rules/agent-mode/AGENT_MODE.md +8 -5
  16. package/src/rules/agent-mode/MIXY.md +6 -8
  17. package/src/rules/lead/LEAD.md +2 -2
  18. package/src/rules/shared/10-tool-workflow.md +4 -0
  19. package/src/rules/shared/20-research.md +2 -0
  20. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
  21. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
  22. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
  23. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
  24. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
  25. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
  26. package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
  27. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
  28. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  29. package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
  30. package/src/runtime/agent/orchestrator/config.mjs +11 -0
  31. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +9 -2
  32. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -5
  33. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
  34. package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
  35. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +80 -31
  36. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
  37. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  38. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +67 -38
  39. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
  40. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
  41. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +27 -8
  42. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
  43. package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
  44. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
  45. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
  46. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +16 -0
  47. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
  48. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
  49. package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
  50. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
  51. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
  52. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
  53. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
  54. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +2 -1
  55. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
  56. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
  57. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
  58. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
  59. package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -0
  60. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
  61. package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
  62. package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
  63. package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
  64. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
  65. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +4 -6
  66. package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
  67. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
  68. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +12 -1
  69. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +6 -5
  70. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +7 -6
  71. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
  72. package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
  73. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +2 -0
  74. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
  75. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
  76. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
  77. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +17 -2
  78. package/src/runtime/agent/orchestrator/session/agent-room.mjs +133 -38
  79. package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
  80. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  81. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +14 -11
  82. package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
  83. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +68 -18
  84. package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
  85. package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
  86. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
  87. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +5 -1
  88. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +1 -1
  89. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
  90. package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
  91. package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +230 -0
  92. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +136 -32
  93. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
  94. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +21 -9
  95. package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
  96. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
  97. package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
  98. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
  99. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +189 -105
  100. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
  101. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +5 -5
  102. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +3 -0
  103. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
  104. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +38 -5
  105. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +21 -0
  106. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
  107. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
  108. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
  109. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
  110. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +4 -0
  111. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  112. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  113. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
  114. package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +99 -0
  115. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
  116. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
  117. package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
  118. package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
  119. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
  120. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
  121. package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
  122. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +55 -27
  123. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +3 -8
  124. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +12 -1
  125. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
  126. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
  127. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
  128. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +42 -10
  129. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
  130. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +18 -3
  131. package/src/runtime/agent/orchestrator/session/routine-result.mjs +120 -9
  132. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -1
  133. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
  134. package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
  135. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
  136. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
  137. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
  138. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +8 -3
  139. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +36 -1
  140. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -1
  141. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
  142. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
  143. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
  144. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
  145. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
  146. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +35 -5
  147. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +42 -2
  148. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +35 -13
  149. package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
  150. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +4 -3
  151. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
  152. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
  154. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
  155. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +6 -14
  156. package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
  157. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
  158. package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
  159. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
  160. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
  161. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +34 -21
  162. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
  163. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
  164. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
  165. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
  166. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
  167. package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
  168. package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
  169. package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
  170. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
  171. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
  172. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
  173. package/src/runtime/attachments/attachment-reference.mjs +155 -0
  174. package/src/runtime/attachments/limits.mjs +5 -1
  175. package/src/runtime/attachments/office-extract.mjs +3 -6
  176. package/src/runtime/attachments/pdf-extract.mjs +84 -10
  177. package/src/runtime/attachments/store.mjs +49 -30
  178. package/src/runtime/browser-bridge/client.mjs +73 -20
  179. package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
  180. package/src/runtime/channels/lib/scheduler.mjs +14 -7
  181. package/src/runtime/channels/lib/state-file.mjs +15 -15
  182. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
  183. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
  184. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
  185. package/src/runtime/channels/lib/worker-main.mjs +3 -2
  186. package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
  187. package/src/runtime/effort-judge/judge-client.mjs +5 -2
  188. package/src/runtime/effort-judge/model-install.mjs +16 -8
  189. package/src/runtime/local-provider/asset-installer.mjs +10 -4
  190. package/src/runtime/local-provider/catalog.mjs +5 -2
  191. package/src/runtime/local-provider/registered-models.mjs +64 -27
  192. package/src/runtime/local-provider/server.mjs +2 -3
  193. package/src/runtime/media/adapters/image-api.mjs +89 -0
  194. package/src/runtime/media/auth.mjs +64 -1
  195. package/src/runtime/media/catalog.mjs +66 -4
  196. package/src/runtime/media/jobs.mjs +4 -0
  197. package/src/runtime/media/lanes.mjs +46 -5
  198. package/src/runtime/media/media-usage.mjs +17 -0
  199. package/src/runtime/media/renditions.mjs +15 -15
  200. package/src/runtime/media/store.mjs +36 -35
  201. package/src/runtime/media/tool.mjs +8 -1
  202. package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
  203. package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
  204. package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
  205. package/src/runtime/memory/lib/pg/process.mjs +8 -4
  206. package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
  207. package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
  208. package/src/runtime/office/core/journal.mjs +7 -14
  209. package/src/runtime/office/design/library/design-library-core.mjs +4 -16
  210. package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
  211. package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
  212. package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
  213. package/src/runtime/office/pdf/pdf-render.mjs +30 -18
  214. package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
  215. package/src/runtime/office/portable/portable-soffice.mjs +4 -0
  216. package/src/runtime/shared/agent-kind-migration.mjs +200 -0
  217. package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
  218. package/src/runtime/shared/agent-message-envelope.mjs +44 -0
  219. package/src/runtime/shared/agent-reap-state.d.mts +4 -0
  220. package/src/runtime/shared/agent-store.mjs +24 -7
  221. package/src/runtime/shared/agent-task-starts.mjs +27 -0
  222. package/src/runtime/shared/atomic-file.d.mts +50 -0
  223. package/src/runtime/shared/atomic-file.mjs +394 -132
  224. package/src/runtime/shared/background-tasks.mjs +7 -4
  225. package/src/runtime/shared/bot-hops.mjs +30 -0
  226. package/src/runtime/shared/bot-split-migration.mjs +1 -1
  227. package/src/runtime/shared/bounded-download.mjs +4 -2
  228. package/src/runtime/shared/config.mjs +39 -9
  229. package/src/runtime/shared/defaults-separation.mjs +6 -2
  230. package/src/runtime/shared/delegated-approval-host.mjs +74 -0
  231. package/src/runtime/shared/err-text.mjs +4 -0
  232. package/src/runtime/shared/error-presentation.mjs +22 -4
  233. package/src/runtime/shared/file-has-data.mjs +28 -0
  234. package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
  235. package/src/runtime/shared/pending-agent-work.mjs +54 -0
  236. package/src/runtime/shared/process-lifecycle.mjs +8 -12
  237. package/src/runtime/shared/projects.mjs +50 -41
  238. package/src/runtime/shared/provider-accounts.mjs +17 -12
  239. package/src/runtime/shared/routine-trigger.d.mts +6 -1
  240. package/src/runtime/shared/routine-trigger.mjs +14 -6
  241. package/src/runtime/shared/run-child.d.mts +3 -0
  242. package/src/runtime/shared/run-child.mjs +24 -3
  243. package/src/runtime/shared/session-baseline-store.mjs +8 -2
  244. package/src/runtime/shared/singleton-owner.mjs +7 -6
  245. package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
  246. package/src/runtime/shared/staged-update.mjs +4 -2
  247. package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
  248. package/src/runtime/shared/tool-execution-contract.mjs +6 -0
  249. package/src/runtime/shared/transcript-metadata.mjs +28 -3
  250. package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
  251. package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
  252. package/src/runtime/shared/turn-snapshot.mjs +44 -2
  253. package/src/runtime/shared/user-cwd.mjs +9 -4
  254. package/src/runtime/shared/user-data-guard.mjs +41 -42
  255. package/src/runtime/shared/utf8-cut.mjs +10 -0
  256. package/src/runtime/shared/worktrees/activity-gate.mjs +269 -61
  257. package/src/runtime/shared/worktrees/registry.mjs +26 -26
  258. package/src/runtime/tidy/core-install.mjs +26 -4
  259. package/src/runtime/tidy/install.mjs +91 -23
  260. package/src/runtime/web-search/lib/config.mjs +4 -16
  261. package/src/session-runtime/agent-home-delivery.mjs +555 -199
  262. package/src/session-runtime/agent-room-delivery.mjs +143 -36
  263. package/src/session-runtime/agent-task-sessions.mjs +667 -165
  264. package/src/session-runtime/agent-tool-routing.mjs +1 -0
  265. package/src/session-runtime/boot/apis.mjs +45 -24
  266. package/src/session-runtime/boot/config.mjs +1 -0
  267. package/src/session-runtime/boot/facade.mjs +11 -1
  268. package/src/session-runtime/boot/infrastructure.mjs +14 -2
  269. package/src/session-runtime/boot/providers.mjs +2 -2
  270. package/src/session-runtime/boot/shared.mjs +12 -6
  271. package/src/session-runtime/boot/tools.mjs +2 -2
  272. package/src/session-runtime/bot-status.mjs +432 -0
  273. package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
  274. package/src/session-runtime/config-lifecycle.mjs +60 -55
  275. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
  276. package/src/session-runtime/held-requests.mjs +36 -33
  277. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
  278. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
  279. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +19 -4
  280. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
  281. package/src/session-runtime/lifecycle/teardown.mjs +11 -3
  282. package/src/session-runtime/mcp-glue/connect.mjs +100 -186
  283. package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
  284. package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
  285. package/src/session-runtime/media-api.mjs +5 -2
  286. package/src/session-runtime/model-route/route-tuning.mjs +4 -1
  287. package/src/session-runtime/model-route/set-route.mjs +61 -1
  288. package/src/session-runtime/owned-automation-run.mjs +8 -1
  289. package/src/session-runtime/room-member-gate.mjs +16 -3
  290. package/src/session-runtime/route-resolution.mjs +16 -8
  291. package/src/session-runtime/route-state.mjs +1 -59
  292. package/src/session-runtime/routine-home-turn.mjs +5 -3
  293. package/src/session-runtime/runtime-bootstrap.mjs +5 -3
  294. package/src/session-runtime/runtime-paths.mjs +11 -5
  295. package/src/session-runtime/runtime-tunables.mjs +6 -0
  296. package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
  297. package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
  298. package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
  299. package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
  300. package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
  301. package/src/session-runtime/services/agent-tool/send-flow.mjs +25 -21
  302. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -0
  303. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
  304. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +8 -5
  305. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
  306. package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
  307. package/src/session-runtime/services/agent-tool.mjs +6 -0
  308. package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
  309. package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
  310. package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
  311. package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
  312. package/src/session-runtime/services/plugin-admin.mjs +26 -26
  313. package/src/session-runtime/services/provider-admin.mjs +14 -4
  314. package/src/session-runtime/services/schedule-access.mjs +33 -4
  315. package/src/session-runtime/services/seeds.mjs +4 -3
  316. package/src/session-runtime/services/usage-dashboard.mjs +1 -1
  317. package/src/session-runtime/services/usage-stats-model.mjs +2 -1
  318. package/src/session-runtime/session-create/materialize.mjs +2 -3
  319. package/src/session-runtime/session-create/session-options.mjs +15 -2
  320. package/src/session-runtime/settings-builtin-tools-api.mjs +63 -1
  321. package/src/session-runtime/settings-compaction-api.mjs +86 -39
  322. package/src/session-runtime/setup-tool/executor.mjs +8 -2
  323. package/src/session-runtime/setup-tool/settings-contract.mjs +31 -1
  324. package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
  325. package/src/session-runtime/skills-api.mjs +12 -5
  326. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +265 -0
  327. package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
  328. package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
  329. package/src/session-runtime/tool-policy/policy-stage.mjs +165 -0
  330. package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
  331. package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
  332. package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
  333. package/src/session-runtime/tool-policy-refresh.mjs +47 -369
  334. package/src/session-runtime/tool-surface.mjs +5 -3
  335. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +95 -88
  336. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  337. package/src/session-runtime/turn/turn-run.mjs +2 -1
  338. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
  339. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +519 -333
  340. package/src/session-runtime/workflow-agents-api/agent-route.mjs +46 -46
  341. package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
  342. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
  343. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
  344. package/src/session-runtime/workspace/workspace-state.mjs +7 -6
  345. package/src/standalone/daemon-agent-control.mjs +4 -1
  346. package/src/standalone/daemon-session-control.mjs +51 -10
  347. package/src/standalone/daemon.mjs +28 -10
  348. package/src/standalone/local-session-runtime.mjs +22 -14
  349. package/src/standalone/session-client.mjs +7 -2
  350. package/src/standalone/session-protocol.mjs +7 -0
  351. package/src/standalone/session-service.mjs +11 -0
  352. package/src/standalone/session-state-projection.mjs +19 -9
  353. package/src/standalone/session-tool-output.mjs +35 -9
  354. package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
  355. package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
  356. package/src/tui/app/slash-commands.mjs +1 -1
  357. package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
  358. package/src/tui/dist/index.mjs +529 -333
  359. package/src/tui/prompt-history-store.mjs +16 -21
  360. package/src/tui/session/context-state.mjs +4 -0
  361. package/src/tui/session/session-api/intake/abort.mjs +19 -1
  362. package/src/tui/session/session-api/media.mjs +2 -1
  363. package/src/tui/session/session-api/routes-agent-definitions.mjs +75 -0
  364. package/src/tui/session/session-api/routes-write.mjs +203 -0
  365. package/src/tui/session/session-api/routes.mjs +23 -247
  366. package/src/tui/session/session-api/settings.mjs +14 -1
  367. package/src/tui/session/session-flow/drain.mjs +28 -3
  368. package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
  369. package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
  370. package/src/tui/session/session-flow/steering.mjs +8 -3
  371. package/src/tui/session/tool-approval.mjs +185 -20
  372. package/src/tui/session/transcript-intake.mjs +2 -0
  373. package/src/tui/session/turn.mjs +23 -14
  374. package/src/tui/transcript-tool-failures.mjs +8 -2
  375. package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
  376. package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
  377. package/src/rules/agent-mode/ROUTINE.md +0 -6
@@ -28,6 +28,16 @@ export function restartNotificationId(row) {
28
28
  return `restart:${clean(row.task_id) || `${clean(row.sessionId)}:${clean(row.finishedAt)}`}`;
29
29
  }
30
30
 
31
+ /** The agent task id an execution id names: a task's own id, or the task a
32
+ * restart notification (restartNotificationId) reports; null for one keyed
33
+ * by session because its row recorded no task id. */
34
+ export function agentTaskIdOfExecution(executionId) {
35
+ const id = clean(executionId);
36
+ if (!id.startsWith('restart:')) return id || null;
37
+ const rest = id.slice('restart:'.length);
38
+ return rest && !rest.includes(':') ? rest : null;
39
+ }
40
+
31
41
  /** `pendingRestartFailures()` lists rows whose owner is not yet told;
32
42
  * `markRestartNotified(rows)` stamps them. `mgr` supplies the persisted
33
43
  * pending queue (enqueuePendingMessage / markCompletionEntry /
@@ -17,7 +17,13 @@ import {
17
17
  } from './job-task-reconcile.mjs';
18
18
  import { createTurnReviewCollector } from './turn-review.mjs';
19
19
  import { handoffContent } from '../../handoff-attachments.mjs';
20
+ import { rebindDelegation } from '../../../runtime/agent/orchestrator/session/human-gate/delegation.mjs';
20
21
  import { settledWorkerStatus } from './worker-index/row-liveness.mjs';
22
+ import { whileTurnDispatched } from '../../../runtime/agent/orchestrator/session/manager/runtime-state.mjs';
23
+
24
+ function workerResultIdentity({ session, tag, sessionId }) {
25
+ return { tag, sessionId, agent: session.agent || null, provider: session.provider, model: session.model };
26
+ }
21
27
 
22
28
  // `send` to an existing worker: resolve the live session, run one follow-up
23
29
  // turn under the progress watchdog, and reconcile the job/worker row. Busy
@@ -42,11 +48,7 @@ export function createSendFlow({ mgr, defaultCwd, sessionSurface, canUseSessionS
42
48
  return (result) => {
43
49
  const abnormalError = abnormalEmptyFinishError(result, session.agent || sendAgent);
44
50
  return {
45
- tag,
46
- sessionId,
47
- agent: session.agent || null,
48
- provider: session.provider,
49
- model: session.model,
51
+ ...workerResultIdentity({ session, tag, sessionId }),
50
52
  content: result?.content || '',
51
53
  ...(sessionSurface?.canonical === true ? { handoffOnly: true } : {}),
52
54
  ...(abnormalError ? { error: abnormalError } : {}),
@@ -74,10 +76,12 @@ export function createSendFlow({ mgr, defaultCwd, sessionSurface, canUseSessionS
74
76
  const cwd = session.cwd || defaultCwd;
75
77
  // The caller's forwarded files ride along as attachment parts.
76
78
  const prompt = handoffContent(text, args.attachmentParts);
77
- if (canUseSessionSurface(session)) {
78
- return sessionSurface.runTurn({ session, prompt, context: args.context || null, cwd, ...turnHooks });
79
- }
80
- return mgr.askSession(sessionId, prompt, args.context || null, null, cwd, null, turnHooks);
79
+ return whileTurnDispatched(sessionId, () => {
80
+ if (canUseSessionSurface(session)) {
81
+ return sessionSurface.runTurn({ session, prompt, context: args.context || null, cwd, ...turnHooks });
82
+ }
83
+ return mgr.askSession(sessionId, prompt, args.context || null, null, cwd, null, turnHooks);
84
+ });
81
85
  }
82
86
 
83
87
  async function runSend(prepared, notifyContext = null, job = null) {
@@ -110,11 +114,7 @@ export function createSendFlow({ mgr, defaultCwd, sessionSurface, canUseSessionS
110
114
  const settleCancelled = (error, result = null) => {
111
115
  finalStatus = 'cancelled';
112
116
  reconcileJobCancelled(job, {
113
- tag,
114
- sessionId,
115
- agent: session.agent || null,
116
- provider: session.provider,
117
- model: session.model,
117
+ ...workerResultIdentity({ session, tag, sessionId }),
118
118
  content: turnPartialHandoff(error, mgr.getSession(sessionId), handoffMsgStart) || result?.content || '',
119
119
  });
120
120
  };
@@ -145,11 +145,7 @@ export function createSendFlow({ mgr, defaultCwd, sessionSurface, canUseSessionS
145
145
  if (partial) {
146
146
  finalStatus = 'idle';
147
147
  const value = {
148
- tag,
149
- sessionId,
150
- agent: session.agent || null,
151
- provider: session.provider,
152
- model: session.model,
148
+ ...workerResultIdentity({ session, tag, sessionId }),
153
149
  content: watchdogStoppedHandoff(error, partial),
154
150
  stallAbort: true,
155
151
  };
@@ -164,7 +160,7 @@ export function createSendFlow({ mgr, defaultCwd, sessionSurface, canUseSessionS
164
160
  turnReview.complete();
165
161
  watchdog?.stop?.();
166
162
  registry.upsertWorkerSessionDeferred(session, tag, {
167
- ...settledWorkerStatus(finalStatus, stalled),
163
+ ...settledWorkerStatus(finalStatus, stalled, job),
168
164
  finishedAt: new Date().toISOString(),
169
165
  });
170
166
  reconcileJobFinally(job, finalStatus);
@@ -188,6 +184,14 @@ export function createSendFlow({ mgr, defaultCwd, sessionSurface, canUseSessionS
188
184
  return { ...prepared, session: mgr.getSession(sessionId) || session };
189
185
  }
190
186
 
187
+ // The calling session hands this worker new work (a `send`, or a spawn of its
188
+ // tag): from now on the worker acts as that caller does, so a run's worker
189
+ // asks when the attended chat reuses it and the other way round. Not for a
190
+ // completion wake, which is the worker's own continuation.
191
+ function adoptCaller({ sessionId }, notifyContext) {
192
+ return rebindDelegation(sessionId, notifyContext?.callerSessionId);
193
+ }
194
+
191
195
  // Shared send dispatch for an already-resolved live session. Used by the
192
196
  // `send` branch AND by the `spawn` branch when an explicit tag maps to a
193
197
  // live session (reuse path). Busy sessions queue the prompt; idle ones run a
@@ -255,5 +259,5 @@ export function createSendFlow({ mgr, defaultCwd, sessionSurface, canUseSessionS
255
259
  return true;
256
260
  }
257
261
 
258
- return { prepareSend, dispatchToExistingSession, wakeIdleWorker };
262
+ return { prepareSend, adoptCaller, dispatchToExistingSession, wakeIdleWorker };
259
263
  }
@@ -4,6 +4,7 @@
4
4
  * agent work, and status publication at start and after terminal settlement.
5
5
  */
6
6
  import { sanitizeTaskMeta, startBackgroundTask } from '../../../../runtime/shared/background-tasks.mjs';
7
+ import { recordAgentTaskStart } from '../../../../runtime/shared/agent-task-starts.mjs';
7
8
  import { resourceAdmission } from '../../../../runtime/shared/resource-admission.mjs';
8
9
  import { clean, terminalPidForContext } from '../helpers.mjs';
9
10
  import { renderResult } from '../render.mjs';
@@ -82,6 +83,9 @@ export function createJobStarter({ mgr, notifyStatusChange, notifySessionComplet
82
83
  }
83
84
  },
84
85
  });
86
+ // The model's tool call that started this job owns it (its result message
87
+ // carries the id: agent-task-starts.mjs).
88
+ recordAgentTaskStart(notifyContext?.toolCallId, task.taskId);
85
89
  notifyStatusChange();
86
90
  return task;
87
91
  };
@@ -9,6 +9,7 @@ import { resolveAgentWatchdogPolicy } from '../../../../runtime/agent/orchestrat
9
9
  import { prepareAgentSession } from '../../../../runtime/agent/orchestrator/agent-runtime/session-builder.mjs';
10
10
  import { getProvider } from '../../../../runtime/agent/orchestrator/providers/registry.mjs';
11
11
  import { resolveRouteContextState } from '../../../route-state.mjs';
12
+ import { contextSelectionFromState } from '../../../../runtime/agent/orchestrator/runtime-core/route-context-state.mjs';
12
13
  import { createSpawnPlanner } from './spawn-plan.mjs';
13
14
  import { enterWorkerSpawn } from '../../../workspace/admission-gate.mjs';
14
15
 
@@ -97,11 +98,13 @@ export function createSpawnPreparer({
97
98
 
98
99
  async function createSpawnedSession(plan, spec, prepState) {
99
100
  // A sub-100 context share needs the window it selects; a percent alone
100
- // keeps the full served window.
101
+ // keeps the full served window. A model whose window is not known yet
102
+ // resolves to nothing: the agent's own choice then travels as stored (the
103
+ // session sizes it from the provider's window) instead of being dropped
104
+ // for the model's default.
101
105
  const sessionSpec = { ...spec };
102
106
  if (plan.preset.contextPercent || plan.preset.contextTokens) {
103
- const { contextPercent, selectedContextWindow, contextTokens } = resolveRouteContextState(plan.preset, null);
104
- Object.assign(sessionSpec, { contextPercent, selectedContextWindow }, contextTokens ? { contextTokens } : {});
107
+ Object.assign(sessionSpec, contextSelectionFromState(resolveRouteContextState(plan.preset, null), plan.preset));
105
108
  }
106
109
  if (sessionSurface?.canonical === true && typeof sessionSurface.createChild === 'function') {
107
110
  return sessionSurface.createChild({ spec: sessionSpec, prompt: plan.prompt, tag: plan.tag });
@@ -26,6 +26,7 @@ import { saveSessionAsync } from '../../../../runtime/agent/orchestrator/session
26
26
  import { presetDescriptor } from './spawn-prep.mjs';
27
27
  import { workerStageForStatus } from '../worker-rows.mjs';
28
28
  import { handoffContent } from '../../../handoff-attachments.mjs';
29
+ import { whileTurnDispatched } from '../../../../runtime/agent/orchestrator/session/manager/runtime-state.mjs';
29
30
 
30
31
  // Mid-turn worker persistence cadence. The watchdog sweep runs every second;
31
32
  // a transcript save is far more expensive than a bookkeeping stamp, so the
@@ -121,10 +122,12 @@ export function createSpawnRunner({
121
122
  const { args, session, workerCwd } = prepared;
122
123
  // The caller's forwarded files ride along as attachment parts.
123
124
  const prompt = handoffContent(prepared.prompt, args.attachmentParts);
124
- if (typeof sessionSurface?.runTurn === 'function' && sessionSurface.canRun?.(session) !== false) {
125
- return sessionSurface.runTurn({ session, prompt, context: args.context || null, cwd: workerCwd, ...turnHooks });
126
- }
127
- return mgr.askSession(session.id, prompt, args.context || null, null, workerCwd, null, turnHooks);
125
+ return whileTurnDispatched(session.id, () => {
126
+ if (typeof sessionSurface?.runTurn === 'function' && sessionSurface.canRun?.(session) !== false) {
127
+ return sessionSurface.runTurn({ session, prompt, context: args.context || null, cwd: workerCwd, ...turnHooks });
128
+ }
129
+ return mgr.askSession(session.id, prompt, args.context || null, null, workerCwd, null, turnHooks);
130
+ });
128
131
  }
129
132
 
130
133
  async function runSpawn(prepared, notifyContext = null, job = null) {
@@ -221,7 +224,7 @@ export function createSpawnRunner({
221
224
  watchdog?.stop?.();
222
225
  upsertWorkerSessionDeferred(session, tag, {
223
226
  ...presetDescriptor(agent, preset, presetName),
224
- ...settledWorkerStatus(finalStatus, stalled),
227
+ ...settledWorkerStatus(finalStatus, stalled, job),
225
228
  finishedAt: new Date().toISOString(),
226
229
  });
227
230
  setImmediate(notifyStatusChange);
@@ -31,10 +31,30 @@ export const PROCESS_RESTART_FAILURE = 'process-restart';
31
31
  /** The row's turn was stopped by the progress (stall) watchdog. */
32
32
  export const WATCHDOG_STALL_FAILURE = 'stalled';
33
33
 
34
+ /** A failed row whose turn a process restart ended and whose owner has not
35
+ * been told yet (restart-notify). It is the only record of that owed result,
36
+ * so closing the worker never drops it. */
37
+ export const isUnnotifiedRestartFailure = (row) =>
38
+ row?.status === 'failed' &&
39
+ row.failureReason === PROCESS_RESTART_FAILURE &&
40
+ Boolean(clean(row.ownerSessionId)) &&
41
+ !row.restartNotified;
42
+
34
43
  /** A turn's settled row status and failure reason: a watchdog stop is a
35
44
  * failure whatever partial output it handed back (`finalStatus` may still
36
- * read `idle` for the job's sake); everything else keeps `finalStatus`. */
37
- export function settledWorkerStatus(finalStatus, stalled) {
45
+ * read `idle` for the job's sake); a turn the process exit cut off (its `job`
46
+ * cancelled with terminalReason PROCESS_RESTART_FAILURE, lifecycle/teardown)
47
+ * fails as a restart failure under its task id, which the next process tells
48
+ * its owner; everything else keeps `finalStatus`. */
49
+ export function settledWorkerStatus(finalStatus, stalled, job = null) {
50
+ if (job?.status === 'cancelled' && job.terminalReason === PROCESS_RESTART_FAILURE) {
51
+ return {
52
+ status: 'failed',
53
+ stage: 'failed',
54
+ failureReason: PROCESS_RESTART_FAILURE,
55
+ ...(job.taskId ? { task_id: job.taskId } : {}),
56
+ };
57
+ }
38
58
  return stalled
39
59
  ? { status: 'failed', stage: 'failed', failureReason: WATCHDOG_STALL_FAILURE }
40
60
  : { status: finalStatus, stage: finalStatus };
@@ -22,6 +22,7 @@ import {
22
22
  createRowLiveness,
23
23
  isActiveWorkerRow,
24
24
  isStaleApprovalWait,
25
+ isUnnotifiedRestartFailure,
25
26
  PROCESS_RESTART_FAILURE,
26
27
  runtimeDied,
27
28
  } from './worker-index/row-liveness.mjs';
@@ -101,12 +102,15 @@ export function createWorkerIndex({ dataDir, cfgMod, mgr, tags, tagAgents, tagCw
101
102
  const rowFromSession = (session, fallbackTag = '', extra = {}) =>
102
103
  workerRowFromSession(session, fallbackTag, extra, (sessionId) => mgr.getSessionRuntime?.(sessionId));
103
104
 
105
+ // A row whose owner is still owed its restart failure stays: closing the
106
+ // worker (a process exit closes every one) must not lose that result.
104
107
  function removeWorkerRow({ tag = '', sessionId = '' } = {}) {
105
108
  const targetTag = clean(tag);
106
109
  const targetSessionId = clean(sessionId);
107
110
  batch.flush();
108
111
  store.write((byKey) => {
109
112
  for (const [key, row] of [...byKey.entries()]) {
113
+ if (isUnnotifiedRestartFailure(row)) continue;
110
114
  if ((targetSessionId && row.sessionId === targetSessionId) || (targetTag && row.tag === targetTag)) {
111
115
  byKey.delete(key);
112
116
  }
@@ -194,12 +198,6 @@ export function createWorkerIndex({ dataDir, cfgMod, mgr, tags, tagAgents, tagCw
194
198
  return true;
195
199
  }
196
200
 
197
- const isUnnotifiedRestartFailure = (row) =>
198
- row.status === 'failed' &&
199
- row.failureReason === PROCESS_RESTART_FAILURE &&
200
- Boolean(clean(row.ownerSessionId)) &&
201
- !row.restartNotified;
202
-
203
201
  /** Rows whose turn a process restart ended and whose owner has not been told
204
202
  * yet — whoever settled the row (this process at startup, or the
205
203
  * predecessor's exit flush). Read-only: the caller enqueues the owner's
@@ -169,6 +169,12 @@ export function createStandaloneAgent({
169
169
  },
170
170
  getStatus,
171
171
  recoverWorkers,
172
+ // Daemon start: settle the rows the previous process left running and
173
+ // queue each owner's failure result; resolves once they are queued.
174
+ settleRestartFailures: () => {
175
+ registry.recoverStaleWorkerRows({ deadOnly: true });
176
+ return notifyRestartFailures();
177
+ },
172
178
  upsertLeadSession: registry.upsertLeadSession,
173
179
  updateLeadWorkerCwd: registry.updateLeadWorkerCwd,
174
180
  forgetLeadSession: registry.forgetLeadSession,
@@ -165,7 +165,7 @@ export function reconcileLostDaemonCaptures({
165
165
  for (const name of names) {
166
166
  const file = path.join(dir, name);
167
167
  const record = readRecord(file);
168
- if (!record || record.kind !== 'mixdog-daemon-crash-capture') continue;
168
+ if (record?.kind !== 'mixdog-daemon-crash-capture') continue;
169
169
  if (!record.pid || record.pid === selfPid || record.exitedAt || record.daemonExit || record.exitReason) continue;
170
170
  if (pidAlive(record.pid)) continue;
171
171
  try {
@@ -21,9 +21,10 @@ import { daemonCrashCaptureDir } from './paths.mjs';
21
21
  // Prune only files this module creates: `daemon-<yyyymmdd>-<hhmmss>-<pid>-<rand>`.
22
22
  const CAPTURE_STEM_RE = /^daemon-\d{8}-\d{6}-\d+-[0-9a-f]{4}$/;
23
23
  const CAPTURE_SUFFIXES = ['.err.log', '.json'];
24
- // Debris from this module's own atomic sidecar writes (writeFileAtomicSync
25
- // names its temp `.<basename>.<hex>.tmp`), never anyone else's file.
26
- const CAPTURE_TEMP_RE = /^\.daemon-\d{8}-\d{6}-(\d+)-[0-9a-f]{4}\.json\.[0-9a-f]+\.tmp$/;
24
+ // Debris from this module's own atomic sidecar writes (the atomic writer names
25
+ // its temp `<basename>.mixdog-tmp-<hex>`; older builds `.<basename>.<hex>.tmp`),
26
+ // never anyone else's file.
27
+ const CAPTURE_TEMP_RE = /^\.?daemon-\d{8}-\d{6}-(\d+)-[0-9a-f]{4}\.json\.(?:[0-9a-f]+\.tmp|mixdog-tmp-[0-9a-f]+)$/;
27
28
  export const CRASH_CAPTURE_KEEP_BOOTS = 8;
28
29
  const CRASH_CAPTURE_KEEP_BYTES = 2 * 1024 * 1024;
29
30
  const CRASH_CAPTURE_TAIL_BYTES = 256 * 1024;
@@ -4,8 +4,7 @@
4
4
  * file-stamp set, standard events merged, legacy rules collected, and the
5
5
  * parse errors both reported and recorded on the result.
6
6
  */
7
- import { readFileSync } from 'node:fs';
8
- import { applyHookRulePatches, hookFileStamp } from './rule-file.mjs';
7
+ import { applyHookRulePatches, hookFileStamp, readHookDocument } from './rule-file.mjs';
9
8
  import {
10
9
  hookConfigEntries,
11
10
  mergeEvents,
@@ -61,14 +60,24 @@ export function createConfigLoader({ dataDir, rulesPath, emit, pendingPatches })
61
60
  for (const entry of entries) {
62
61
  const filePath = entry.path;
63
62
  let parsed = null;
63
+ let damaged = false;
64
64
  try {
65
- parsed = JSON.parse(readFileSync(filePath, 'utf8'));
65
+ // The rules file is this module's own (written with a `.bak`): a
66
+ // damaged copy is restored from it. Other hooks files are only read.
67
+ parsed = readHookDocument(filePath, {
68
+ restore: filePath === rulesPath,
69
+ onDamaged: (error) => {
70
+ damaged = true;
71
+ errors.push({ file: filePath, error: error?.message || String(error) });
72
+ },
73
+ });
66
74
  } catch (error) {
67
- if (error?.code === 'ENOENT' || error?.code === 'ENOTDIR') continue;
68
- if (!(error instanceof SyntaxError)) cacheable = false;
75
+ cacheable = false;
69
76
  errors.push({ file: filePath, error: error?.message || String(error) });
70
77
  continue;
71
78
  }
79
+ // Missing, or damaged without a good copy (reported above): no hooks.
80
+ if (damaged || parsed === null) continue;
72
81
  sources.push(filePath);
73
82
  sourceEntries.push({
74
83
  path: filePath,
@@ -1,7 +1,11 @@
1
1
  import { readFileSync, statSync } from 'node:fs';
2
2
  import { isDeepStrictEqual } from 'node:util';
3
- import { writeJsonAtomicSync } from '../../../runtime/shared/atomic-file.mjs';
4
- import { withFileLockSync } from '../../../runtime/shared/file-lock.mjs';
3
+ import {
4
+ holdsNoData,
5
+ parseJsonText,
6
+ readJsonStateSync,
7
+ updateJsonAtomicSync,
8
+ } from '../../../runtime/shared/atomic-file.mjs';
5
9
  import { normalizeRules } from './config.mjs';
6
10
 
7
11
  export function hookFileStamp(path) {
@@ -14,13 +18,41 @@ export function hookFileStamp(path) {
14
18
  }
15
19
  }
16
20
 
17
- export function readHookDocument(path) {
21
+ // The hooks document at `path`, or null when it is missing or damaged (empty,
22
+ // NUL-filled after a crash, or not JSON). A damaged file must not turn every
23
+ // tool call into a policy-check failure, and must not silently drop the deny
24
+ // rules of a good copy: with `restore` (the rules file this module writes with
25
+ // a `.bak`), a valid `<path>.bak` is restored and read — the damaged bytes are
26
+ // kept aside as `<path>.corrupt-<ts>`. Without a good copy the file stays in
27
+ // place (a hand-edit can still fix it) and `onDamaged(error)` reports it; the
28
+ // caller then has no rules from it. Hooks files the user or a plugin owns are
29
+ // read with `restore: false`: never moved, locked or replaced. Real I/O errors
30
+ // still throw.
31
+ export function readHookDocument(path, { restore = true, onDamaged } = {}) {
32
+ let text;
18
33
  try {
19
- return JSON.parse(readFileSync(path, 'utf8'));
34
+ text = readFileSync(path, 'utf8');
20
35
  } catch (error) {
21
36
  if (error?.code === 'ENOENT' || error?.code === 'ENOTDIR') return null;
22
37
  throw error;
23
38
  }
39
+ let damage;
40
+ if (holdsNoData(text)) {
41
+ damage = new SyntaxError('the file holds no data (empty or NUL-filled)');
42
+ } else {
43
+ try {
44
+ return parseJsonText(text);
45
+ } catch (error) {
46
+ if (!(error instanceof SyntaxError)) throw error;
47
+ damage = error;
48
+ }
49
+ }
50
+ if (restore) {
51
+ const restored = readJsonStateSync(path, { fallback: null, keepWithoutBackup: true });
52
+ if (restored !== null) return restored;
53
+ }
54
+ onDamaged?.(damage);
55
+ return null;
24
56
  }
25
57
 
26
58
  function replaceRules(document, rules) {
@@ -36,13 +68,19 @@ function replaceRules(document, rules) {
36
68
  }
37
69
 
38
70
  export function updateHookRules(path, update) {
39
- return withFileLockSync(`${path}.lock`, () => {
40
- const document = readHookDocument(path);
41
- const rules = normalizeRules(document).filter((rule) => rule && typeof rule === 'object');
42
- const next = update(rules);
43
- writeJsonAtomicSync(path, replaceRules(document, next), { lock: false });
44
- return { stamp: hookFileStamp(path), rules: next };
45
- });
71
+ let next;
72
+ // A damaged file is kept aside as <path>.corrupt-<ts> (its last good copy
73
+ // <path>.bak restored) before the update, never overwritten.
74
+ updateJsonAtomicSync(
75
+ path,
76
+ (document) => {
77
+ const rules = normalizeRules(document).filter((rule) => rule && typeof rule === 'object');
78
+ next = update(rules);
79
+ return replaceRules(document, next);
80
+ },
81
+ { backup: true }
82
+ );
83
+ return { stamp: hookFileStamp(path), rules: next };
46
84
  }
47
85
 
48
86
  export function applyHookRulePatches(rules, patches, { strict = true } = {}) {
@@ -3,12 +3,11 @@ import { existsSync, mkdirSync, readdirSync, renameSync, rmSync } from 'node:fs'
3
3
  import { basename, dirname, join, resolve } from 'node:path';
4
4
  import { homedir } from 'node:os';
5
5
  import { createHash, randomUUID } from 'node:crypto';
6
- import { withFileLockSync, writeJsonAtomicSync } from '../../runtime/shared/atomic-file.mjs';
6
+ import { readJsonStateSync, updateJsonAtomicSync, withFileLockSync } from '../../runtime/shared/atomic-file.mjs';
7
7
  import { resolvePluginData } from '../../runtime/shared/plugin-paths.mjs';
8
8
  import { pluginManifest } from '../../runtime/shared/plugin-manifest.mjs';
9
9
  import { pluginMetadata } from '../../runtime/shared/plugin-metadata.mjs';
10
10
  import { clean } from '../../runtime/shared/clean.mjs';
11
- import { readJsonSafe } from '../../runtime/shared/json-file.mjs';
12
11
 
13
12
  const REGISTRY_VERSION = 1;
14
13
 
@@ -28,38 +27,39 @@ function installRoot(dataDir = resolvePluginData()) {
28
27
  return join(pluginBaseDir(dataDir), 'installed');
29
28
  }
30
29
 
30
+ const emptyRegistry = () => ({ version: REGISTRY_VERSION, plugins: [] });
31
+ const hasPluginList = (raw) => Boolean(raw && Array.isArray(raw.plugins));
32
+
33
+ // A damaged registry (empty, NUL-filled after a crash, malformed, or not a
34
+ // plugin list) never blocks the plugin features: its bytes are kept aside as
35
+ // registry.json.corrupt-<ts>, the last good copy registry.json.bak is
36
+ // restored when there is one, else the registry starts empty. I/O errors
37
+ // still throw.
31
38
  function loadRegistry(dataDir = resolvePluginData()) {
32
- const path = registryPath(dataDir);
33
- if (!existsSync(path)) return { version: REGISTRY_VERSION, plugins: [] };
34
- const raw = readJsonSafe(path);
35
- if (raw && Array.isArray(raw.plugins)) return raw;
36
- throw new Error(`plugin registry is corrupt: ${path}`);
39
+ return readJsonStateSync(registryPath(dataDir), {
40
+ validate(raw) {
41
+ if (!hasPluginList(raw)) throw new Error('plugin registry has no plugin list');
42
+ return raw;
43
+ },
44
+ fallback: emptyRegistry(),
45
+ secret: true,
46
+ });
37
47
  }
38
48
 
39
49
  function mutateRegistry(dataDir, mutator) {
40
50
  const path = registryPath(dataDir);
41
51
  mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
42
- return withFileLockSync(
43
- `${path}.lock`,
44
- () => {
45
- const registry = loadRegistry(dataDir);
46
- const result = mutator(registry);
47
- writeJsonAtomicSync(
48
- path,
49
- {
50
- version: REGISTRY_VERSION,
51
- plugins: Array.isArray(registry.plugins) ? registry.plugins : [],
52
- },
53
- {
54
- lock: false,
55
- secret: true,
56
- fsyncDir: true,
57
- }
58
- );
59
- return result;
52
+ let result;
53
+ updateJsonAtomicSync(
54
+ path,
55
+ (raw) => {
56
+ const registry = hasPluginList(raw) ? raw : emptyRegistry();
57
+ result = mutator(registry);
58
+ return { version: REGISTRY_VERSION, plugins: registry.plugins };
60
59
  },
61
- { secret: true }
60
+ { secret: true, fsyncDir: true, backup: true }
62
61
  );
62
+ return result;
63
63
  }
64
64
 
65
65
  function displayNameFromUrl(url) {
@@ -394,7 +394,10 @@ function oauthProviderSetup(p, providers, checkSecrets) {
394
394
 
395
395
  const CUSTOM_ID_RE = /^custom-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
396
396
  const CUSTOM_TEST_TIMEOUT_MS = 20_000;
397
- const CUSTOM_DISCOVERY_TIMEOUT_MS = 15_000;
397
+ // A catalog host can sit behind a slow gateway: api.nano-gpt.com's /models
398
+ // answered 503 only after ~44 s, so the wait must outlast that to surface the
399
+ // service's own answer instead of a local timeout.
400
+ const CUSTOM_DISCOVERY_TIMEOUT_MS = 60_000;
398
401
 
399
402
  export function isCustomProviderConfig(entry) {
400
403
  return Boolean(entry) && typeof entry === 'object' && entry.type === 'custom';
@@ -949,7 +952,9 @@ async function withTimeout(run, ms, label) {
949
952
  const timeout = new Promise((_, reject) => {
950
953
  timer = setTimeout(() => {
951
954
  controller.abort();
952
- reject(new Error(`${label} timed out after ${Math.round(ms / 1000)}s`));
955
+ reject(
956
+ Object.assign(new Error(`${label} timed out after ${Math.round(ms / 1000)}s`), { code: 'EPROVIDERTIMEOUT' })
957
+ );
953
958
  }, ms);
954
959
  });
955
960
  try {
@@ -1330,7 +1335,11 @@ export async function discoverCustomProviderModels(cfgMod, rawInput) {
1330
1335
  try {
1331
1336
  const { createCustomProvider } = await loadCustomProviderModule();
1332
1337
  const provider = await createCustomProvider(id || 'custom-discovery', { ...config, apiKey });
1333
- const listed = await withTimeout(() => provider.listModels(), CUSTOM_DISCOVERY_TIMEOUT_MS, 'model discovery');
1338
+ const listed = await withTimeout(
1339
+ (signal) => provider.listModels({ signal, timeoutMs: CUSTOM_DISCOVERY_TIMEOUT_MS }),
1340
+ CUSTOM_DISCOVERY_TIMEOUT_MS,
1341
+ 'model discovery'
1342
+ );
1334
1343
  const models = publicCustomModelRows(listed);
1335
1344
  // A failed discovery that still left manual rows reports its error too.
1336
1345
  const failure = provider.customDiscoveryError;
@@ -1343,8 +1352,9 @@ export async function discoverCustomProviderModels(cfgMod, rawInput) {
1343
1352
  function discoveryErrorView(error, secrets) {
1344
1353
  const status = Number.isInteger(error?.status) ? error.status : undefined;
1345
1354
  const authKind = status === 401 || status === 403 ? 'authentication' : 'request';
1355
+ const kind = status === 404 || status === 405 ? 'unavailable' : authKind;
1346
1356
  return {
1347
- kind: status === 404 || status === 405 ? 'unavailable' : authKind,
1357
+ kind: error?.code === 'EPROVIDERTIMEOUT' ? 'timeout' : kind,
1348
1358
  ...(status === undefined ? {} : { status }),
1349
1359
  message: sanitizeCustomError(error, secrets).message,
1350
1360
  };
@@ -11,17 +11,31 @@
11
11
  // - a group-room member session is no background run, but it speaks for the
12
12
  // room, not for the user: it creates or changes no agent, automation or
13
13
  // other setting either.
14
+ // - an unattended run — a routine turn in a bot's home chat (Mixy's too), or
15
+ // work it or a room started (held-requests.mjs unattendedRunOf) — is checked
16
+ // first and only ever narrows: at most its own bot's agent scope
17
+ // (`unattended: true`), which manages that bot's own automations and route
18
+ // and changes no other setting.
14
19
  // The app's own panels call channel-admin directly and act for the human.
15
20
  import { MIXY_AGENT_ID, agentIdOfHomeSession } from '../../runtime/agent/orchestrator/session/agent-home.mjs';
16
21
  import { roomMemberOfSession } from '../../runtime/agent/orchestrator/session/agent-room.mjs';
17
22
  import { mixyDisplayName } from '../../runtime/agent/orchestrator/session/mixy-name.mjs';
18
23
  import { isAgentOwner } from '../../runtime/agent/orchestrator/agent-owner.mjs';
24
+ import { unattendedRunOf } from '../held-requests.mjs';
19
25
 
20
26
  const INTERACTIVE_SOURCE_TYPES = new Set(['', 'lead', 'cli']);
27
+ // What an unattended run may still change: its own bot's automations (each
28
+ // checked by assertAutomationAccess) and its own route (assertOwnAgentSettings).
29
+ const UNATTENDED_OWN_ACTIONS = new Set([
30
+ 'save_automation',
31
+ 'set_automation_enabled',
32
+ 'delete_automation',
33
+ 'run_automation',
34
+ 'set_agent_route',
35
+ ]);
21
36
 
22
- /** `{ kind: 'human' | 'agent' | 'room-member' | 'headless', agentId? }` of a session record. */
23
- export function scheduleCallerScope(session) {
24
- const id = String(session?.id || '');
37
+ /** The scope by the session's id and record alone. */
38
+ function provenanceScope(session, id) {
25
39
  const member = roomMemberOfSession(id);
26
40
  if (member) return { kind: 'room-member', agentId: member.agentId };
27
41
  const homeAgent = agentIdOfHomeSession(id);
@@ -35,6 +49,17 @@ export function scheduleCallerScope(session) {
35
49
  return headless ? { kind: 'headless' } : { kind: 'human' };
36
50
  }
37
51
 
52
+ /** `{ kind: 'human' | 'agent' | 'room-member' | 'headless', agentId?, unattended? }` of a session record. */
53
+ export function scheduleCallerScope(session) {
54
+ const id = String(session?.id || '');
55
+ const scope = provenanceScope(session, id);
56
+ const run = id ? unattendedRunOf(id) : null;
57
+ if (!run || scope.kind === 'headless' || scope.kind === 'room-member') return scope;
58
+ if (run.kind === 'room') return { kind: 'room-member', agentId: run.agentId };
59
+ if (!run.agentId || (scope.kind === 'agent' && scope.agentId !== run.agentId)) return { kind: 'headless' };
60
+ return { kind: 'agent', agentId: run.agentId, unattended: true };
61
+ }
62
+
38
63
  /** Throws unless `scope` may `action` the `kind` ('schedule' | 'webhook')
39
64
  * automation `current` (null when creating) with `owner` the requested owner
40
65
  * ('' keeps the saved one). `agentIds` are the agents a transfer may name;
@@ -83,8 +108,12 @@ export function assertOwnAgentSettings(scope, agentId, action) {
83
108
  }
84
109
  }
85
110
 
86
- /** Throws when `scope` is a headless run: it reads settings, never changes them. */
111
+ /** Throws when `scope` is a headless run: it reads settings, never changes
112
+ * them; an unattended run changes only its own bot's automations and route. */
87
113
  export function assertInteractiveCaller(scope, action) {
114
+ if (scope?.unattended && !UNATTENDED_OWN_ACTIONS.has(action)) {
115
+ throw new Error(`An unattended run cannot change settings (setup ${action})`);
116
+ }
88
117
  if (scope?.kind === 'human' || scope?.kind === 'agent') return;
89
118
  if (scope?.kind === 'room-member') throw new Error(`A group-room member cannot change settings (setup ${action})`);
90
119
  throw new Error(`A background run cannot change settings (setup ${action})`);
@@ -1,5 +1,6 @@
1
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
+ import { writeFileAtomicSync } from '../../runtime/shared/atomic-file.mjs';
3
4
  import { pluginSkillsRoots } from '../../runtime/shared/plugin-manifest.mjs';
4
5
  import {
5
6
  EMPTY_HISTORY,
@@ -43,7 +44,7 @@ export function ensureStandaloneEnvironment({ rootDir, dataDir }) {
43
44
  retireSeededPackCopies({ rootDir, dataDir, backup, includeHistory });
44
45
  if (includeHistory && !backup.failures) {
45
46
  try {
46
- writeFileSync(markerFile, `${stamp}\n`);
47
+ writeFileAtomicSync(markerFile, `${stamp}\n`);
47
48
  } catch {
48
49
  /* marker only; the sweep simply repeats */
49
50
  }
@@ -68,7 +69,7 @@ function cleanupRetiredChannelSecrets(dataDir) {
68
69
  }
69
70
  }
70
71
  try {
71
- writeFileSync(marker, `${new Date().toISOString()}\n`);
72
+ writeFileAtomicSync(marker, `${new Date().toISOString()}\n`);
72
73
  } catch {
73
74
  /* marker only */
74
75
  }