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
@@ -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 });
@@ -25,6 +25,8 @@ import { settledWorkerStatus } from '../worker-index/row-liveness.mjs';
25
25
  import { saveSessionAsync } from '../../../../runtime/agent/orchestrator/session/store.mjs';
26
26
  import { presetDescriptor } from './spawn-prep.mjs';
27
27
  import { workerStageForStatus } from '../worker-rows.mjs';
28
+ import { handoffContent } from '../../../handoff-attachments.mjs';
29
+ import { whileTurnDispatched } from '../../../../runtime/agent/orchestrator/session/manager/runtime-state.mjs';
28
30
 
29
31
  // Mid-turn worker persistence cadence. The watchdog sweep runs every second;
30
32
  // a transcript save is far more expensive than a bookkeeping stamp, so the
@@ -117,11 +119,15 @@ export function createSpawnRunner({
117
119
  };
118
120
 
119
121
  async function runTurn(prepared, turnHooks) {
120
- const { args, session, prompt, workerCwd } = prepared;
121
- if (typeof sessionSurface?.runTurn === 'function' && sessionSurface.canRun?.(session) !== false) {
122
- return sessionSurface.runTurn({ session, prompt, context: args.context || null, cwd: workerCwd, ...turnHooks });
123
- }
124
- return mgr.askSession(session.id, prompt, args.context || null, null, workerCwd, null, turnHooks);
122
+ const { args, session, workerCwd } = prepared;
123
+ // The caller's forwarded files ride along as attachment parts.
124
+ const prompt = handoffContent(prepared.prompt, args.attachmentParts);
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
+ });
125
131
  }
126
132
 
127
133
  async function runSpawn(prepared, notifyContext = null, job = null) {
@@ -218,7 +224,7 @@ export function createSpawnRunner({
218
224
  watchdog?.stop?.();
219
225
  upsertWorkerSessionDeferred(session, tag, {
220
226
  ...presetDescriptor(agent, preset, presetName),
221
- ...settledWorkerStatus(finalStatus, stalled),
227
+ ...settledWorkerStatus(finalStatus, stalled, job),
222
228
  finishedAt: new Date().toISOString(),
223
229
  });
224
230
  setImmediate(notifyStatusChange);
@@ -27,11 +27,10 @@ export function resolveAgentSpawnPreset(config, args = {}) {
27
27
  }
28
28
 
29
29
  const agentName = normalizeAgentName(args.agent);
30
- const agentRoute = !clean(args.preset)
31
- ? configuredAgentRouteCandidates(config, agentName)
32
- .map((candidate) => normalizeAgentRoute(candidate))
33
- .find(Boolean) || null
34
- : null;
30
+ // The agent's stored route: config.agents, or a store bot's own folder.
31
+ const candidates = !clean(args.preset) ? configuredAgentRouteCandidates(config, agentName) : [];
32
+ const agentRoute = candidates.map((candidate) => normalizeAgentRoute(candidate)).find(Boolean) || null;
33
+ const storedFallbacks = candidates.find((candidate) => normalizeAgentRoute(candidate))?.fallbacks;
35
34
  if (agentRoute) {
36
35
  return {
37
36
  presetName: agentPresetName(agentName),
@@ -41,9 +40,7 @@ export function resolveAgentSpawnPreset(config, args = {}) {
41
40
  type: 'agent',
42
41
  provider: agentRoute.provider,
43
42
  model: agentRoute.model,
44
- ...(config.agents?.[agentName]?.fallbacks?.length
45
- ? { fallbacks: config.agents[agentName].fallbacks.map(normalizeAgentRoute) }
46
- : {}),
43
+ ...(storedFallbacks?.length ? { fallbacks: storedFallbacks.map(normalizeAgentRoute) } : {}),
47
44
  effort: agentRoute.effort,
48
45
  fast: agentRoute.fast === true,
49
46
  ...(agentRoute.autoEffort === false ? { autoEffort: false } : {}),
@@ -53,6 +53,13 @@ export const AGENT_TOOL = {
53
53
  file: { type: 'string', description: 'Path to task text; alternative to prompt/message for spawn/send.' },
54
54
  cwd: { type: 'string', description: 'Project directory for a new or respawned agent.' },
55
55
  context: { type: 'string', description: 'Extra context for the spawn/send turn.' },
56
+ attachments: {
57
+ type: 'array',
58
+ items: { type: 'string' },
59
+ maxItems: 8,
60
+ description:
61
+ "spawn/send: files to attach (images, PDF/Office, text): a conversation attachment's file name or [attachment:id], or a path inside your folders. Produced files come back as paths; forward them by path.",
62
+ },
56
63
  model: {
57
64
  type: 'string',
58
65
  description:
@@ -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
  }
@@ -92,7 +92,7 @@ function refreshScope(options) {
92
92
  * then resolve whatever usage the provider — or a local budget fallback — can
93
93
  * supply. The row is appended to `rows` before the first emit so every paint
94
94
  * from here on includes it. */
95
- async function apiProviderRow(item, { config, providers, preview, refreshFor, snapshotOptions, rows, emit }) {
95
+ async function apiProviderRow(item, { providers, preview, snapshotOptions, rows, emit }) {
96
96
  const providerCfg = providers[item.id] || {};
97
97
  const row = baseRow(item, 'api', providerCfg);
98
98
  row.status = row.authenticated ? 'checking' : 'missing';
@@ -535,7 +535,8 @@ function exportUnpricedModels(state) {
535
535
  const rows = [];
536
536
  for (const provider of state.providers.values()) {
537
537
  for (const model of provider.models.values()) {
538
- if (model.turns > 0 && model.costKnownTurns < model.turns) rows.push({ provider: provider.provider, model: model.model });
538
+ if (model.turns > 0 && model.costKnownTurns < model.turns)
539
+ rows.push({ provider: provider.provider, model: model.model });
539
540
  }
540
541
  }
541
542
  return rows.sort((a, b) => a.provider.localeCompare(b.provider) || a.model.localeCompare(b.model));
@@ -3,7 +3,6 @@
3
3
  // event, and the optional WS transport prewarm.
4
4
  import { deferredSurfaceModeForLead } from '../../runtime/agent/orchestrator/runtime-core/effort.mjs';
5
5
  import { filterMcpToolsForSession } from '../../runtime/agent/orchestrator/runtime-core/extension-scopes.mjs';
6
- import { attachSessionHooks } from '../session-hooks.mjs';
7
6
  import { applyDeferredToolSurface } from '../../runtime/agent/orchestrator/runtime-core/tool-catalog.mjs';
8
7
  import { writeStatuslineRoute } from '../statusline-route.mjs';
9
8
  import { sessionOptions } from './session-options.mjs';
@@ -42,10 +41,10 @@ function seedToolSurface(deps, reason) {
42
41
 
43
42
  /** Adopt the provider session and publish it to hooks and statusline. */
44
43
  export function materializeSession(deps, reason, coreMemoryContext) {
45
- const { rt, adoptSession, mgr, hooks, hookCommonPayload, statusRoutes } = deps;
44
+ const { rt, adoptSession, mgr, hooks, statusRoutes } = deps;
45
+ // adoptSession attaches the hook bridge (boot/infrastructure.mjs sessionAdopter).
46
46
  adoptSession(mgr.createSession(sessionOptions(deps, coreMemoryContext)));
47
47
  rt.reservedSessionId = null;
48
- attachSessionHooks(rt.session, { hooks, hookCommonPayload, getCwd: () => rt.currentCwd });
49
48
  seedToolSurface(deps, reason);
50
49
  writeStatuslineRoute(statusRoutes, rt.session, rt.route);
51
50
  hooks.emit('session:create', {