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
@@ -43,7 +43,8 @@ import { fileURLToPath } from 'node:url';
43
43
  import { pipeline } from 'node:stream/promises';
44
44
  import { spawnSync } from 'node:child_process';
45
45
  import { createGunzip } from 'node:zlib';
46
- import { renameWithRetrySync, writeFileAtomicSync } from '../../shared/atomic-file.mjs';
46
+ import { renameWithRetrySync, writeFileAtomicSync, writeJsonAtomicSync } from '../../shared/atomic-file.mjs';
47
+ import { fileHasData } from '../../shared/file-has-data.mjs';
47
48
  import { streamResponseToFile } from '../../shared/bounded-download.mjs';
48
49
  import { platformEntryKey, sha256File, verifySha256File } from '../../shared/native-asset.mjs';
49
50
  import { windowsProgramRoots, windowsSystemRoot } from '../../agent/orchestrator/tools/builtin/windows-roots.mjs';
@@ -264,7 +265,7 @@ async function loadManifest(dataDir) {
264
265
  if (!res.ok) throw new Error(`[voice-runtime] manifest fetch failed: ${res.status} ${res.statusText}`);
265
266
  const manifest = await res.json();
266
267
  mkdirSync(join(dataDir, 'voice-runtime'), { recursive: true });
267
- writeFileSync(cachedPath, JSON.stringify(manifest, null, 2));
268
+ writeJsonAtomicSync(cachedPath, manifest);
268
269
  return manifest;
269
270
  }
270
271
 
@@ -439,7 +440,7 @@ function gcPublishedRuntimeSiblings(rootDir, prefix) {
439
440
  }
440
441
 
441
442
  async function extractGz(gzPath, destPath) {
442
- await pipeline(createReadStream(gzPath), createGunzip(), createWriteStream(destPath));
443
+ await pipeline(createReadStream(gzPath), createGunzip(), createWriteStream(destPath, { flush: true }));
443
444
  }
444
445
 
445
446
  // Sweep .staging-* partials left by killed/crashed install attempts.
@@ -541,13 +542,13 @@ export async function ensureWhisperRuntime(dataDir, onProgress = null) {
541
542
  const activeDir = join(rootDir, activeName);
542
543
  const whisperCmd = join(activeDir, variant.executable);
543
544
 
544
- if (existsSync(whisperCmd)) {
545
+ if (fileHasData(whisperCmd)) {
545
546
  gcStaleVersions(rootDir, activeName, 'whisper-');
546
547
  return { whisperCmd, version: ver, variantId: variant.id };
547
548
  }
548
549
 
549
550
  return _withInstallLock(rootDir, 'install', async () => {
550
- if (existsSync(whisperCmd)) {
551
+ if (fileHasData(whisperCmd)) {
551
552
  gcStaleVersions(rootDir, activeName, 'whisper-');
552
553
  return { whisperCmd, version: ver, variantId: variant.id };
553
554
  }
@@ -641,14 +642,14 @@ function resolveManagedWhisperCmd(dataDir) {
641
642
  const variant = variants.find((v) => v.id === variantId);
642
643
  if (variant?.executable) {
643
644
  const p = join(activeDir, variant.executable);
644
- if (existsSync(p)) return p;
645
+ if (fileHasData(p)) return p;
645
646
  }
646
647
  }
647
648
  } catch {}
648
649
  }
649
650
  for (const c of ['Release/whisper-cli.exe', 'Release/whisper-cli']) {
650
651
  const p = join(activeDir, c);
651
- if (existsSync(p)) return p;
652
+ if (fileHasData(p)) return p;
652
653
  }
653
654
  return null;
654
655
  }
@@ -659,7 +660,7 @@ function resolveManagedWhisperModelById(dataDir, modelId = 'standard') {
659
660
  if (!entry?.filename) return null;
660
661
  const modelDir = join(dataDir, 'voice', 'models');
661
662
  const p = join(modelDir, entry.filename);
662
- return existsSync(p) ? p : null;
663
+ return fileHasData(p) ? p : null;
663
664
  }
664
665
 
665
666
  // Clean-install policy: the models dir holds ONLY filenames declared by the
@@ -712,13 +713,13 @@ export async function ensureFfmpegRuntime(dataDir, onProgress = null) {
712
713
  const activeDir = join(rootDir, activeName);
713
714
  const ffmpegPath = join(activeDir, platformEntry.executable);
714
715
 
715
- if (existsSync(ffmpegPath)) {
716
+ if (fileHasData(ffmpegPath)) {
716
717
  gcStaleVersions(rootDir, activeName, 'ffmpeg-');
717
718
  return { ffmpegPath, version: ver };
718
719
  }
719
720
 
720
721
  return _withInstallLock(rootDir, 'install', async () => {
721
- if (existsSync(ffmpegPath)) {
722
+ if (fileHasData(ffmpegPath)) {
722
723
  gcStaleVersions(rootDir, activeName, 'ffmpeg-');
723
724
  return { ffmpegPath, version: ver };
724
725
  }
@@ -783,13 +784,13 @@ function resolveManagedFfmpegPath(dataDir) {
783
784
  const platformEntry = manifest.ffmpeg?.platforms?.[key];
784
785
  if (platformEntry?.executable) {
785
786
  const p = join(activeDir, platformEntry.executable);
786
- if (existsSync(p)) return p;
787
+ if (fileHasData(p)) return p;
787
788
  }
788
789
  } catch {}
789
790
  }
790
791
  for (const c of ['ffmpeg.exe', 'ffmpeg']) {
791
792
  const p = join(activeDir, c);
792
- if (existsSync(p)) return p;
793
+ if (fileHasData(p)) return p;
793
794
  }
794
795
  return null;
795
796
  }
@@ -835,7 +836,7 @@ export function resolveVoiceRuntime(dataDir, { modelId = 'standard', cleanup = t
835
836
  const managedFfmpegPath = resolveManagedFfmpegPath(dataDir);
836
837
  const ext = process.platform === 'win32' ? '.exe' : '';
837
838
  const managedServerCmd = managedWhisperCmd ? join(dirname(managedWhisperCmd), `whisper-server${ext}`) : null;
838
- const serverCmd = managedServerCmd && existsSync(managedServerCmd) ? managedServerCmd : null;
839
+ const serverCmd = managedServerCmd && fileHasData(managedServerCmd) ? managedServerCmd : null;
839
840
  return {
840
841
  kind: 'managed-whisper.cpp',
841
842
  label: 'whisper.cpp',
@@ -8,8 +8,9 @@
8
8
  // verification stays local — the relay never inspects payloads. Works out
9
9
  // of the box: no binary, no authtoken, no reserved domain.
10
10
  import { randomBytes, randomUUID } from 'node:crypto';
11
- import { readFileSync, writeFileSync, mkdirSync, chmodSync } from 'node:fs';
11
+ import { readFileSync, chmodSync } from 'node:fs';
12
12
  import { join } from 'node:path';
13
+ import { readJsonStateSync, writeJsonAtomicSync } from '../../../shared/atomic-file.mjs';
13
14
  import { DATA_DIR } from '../config.mjs';
14
15
  import { logWebhook } from './log.mjs';
15
16
  import { MAX_HOOK_FRAME_BYTES, MAX_HOOK_HEADER_BYTES, MAX_TUNNEL_BODY_BYTES } from './relay-tunnel/limits.mjs';
@@ -55,19 +56,28 @@ function hookIdentityPath() {
55
56
  return join(DATA_DIR, 'relay-hook-device.json');
56
57
  }
57
58
 
59
+ function validHookIdentity(parsed) {
60
+ if (
61
+ typeof parsed?.deviceId !== 'string' ||
62
+ !/^[0-9a-f-]{8,64}$/.test(parsed.deviceId) ||
63
+ typeof parsed.deviceSecret !== 'string' ||
64
+ parsed.deviceSecret.length < 16
65
+ ) {
66
+ throw new Error('invalid hook tunnel identity');
67
+ }
68
+ return { deviceId: parsed.deviceId, deviceSecret: parsed.deviceSecret };
69
+ }
70
+
58
71
  // Stable per-install identity (trust-on-first-use at the relay, mirroring
59
72
  // the desktop leg). The secret never leaves this machine except toward the
60
73
  // relay; the deviceId doubles as the public URL path segment.
61
74
  function loadOrCreateHookIdentity() {
62
75
  const path = hookIdentityPath();
63
76
  try {
64
- const parsed = JSON.parse(readFileSync(path, 'utf8'));
65
- if (
66
- typeof parsed.deviceId === 'string' &&
67
- /^[0-9a-f-]{8,64}$/.test(parsed.deviceId) &&
68
- typeof parsed.deviceSecret === 'string' &&
69
- parsed.deviceSecret.length >= 16
70
- ) {
77
+ // A damaged identity (empty, NUL-filled, malformed) is kept aside and the
78
+ // last good copy restored; only when there is none is a new identity made.
79
+ const stored = readJsonStateSync(path, { validate: validHookIdentity, secret: true, fallback: null });
80
+ if (stored) {
71
81
  // The secret authenticates the webhook tunnel leg (whoever holds it can
72
82
  // take over inbound deliveries), so clamp legacy world-readable files.
73
83
  try {
@@ -75,20 +85,14 @@ function loadOrCreateHookIdentity() {
75
85
  } catch {
76
86
  /* windows/fs quirk */
77
87
  }
78
- return { deviceId: parsed.deviceId, deviceSecret: parsed.deviceSecret };
88
+ return stored;
79
89
  }
80
- } catch {
81
- /* first run */
90
+ } catch (err) {
91
+ logWebhook(`hook tunnel: identity read failed — ${err?.message || err}`);
82
92
  }
83
93
  const identity = { deviceId: randomUUID(), deviceSecret: randomBytes(24).toString('hex') };
84
94
  try {
85
- mkdirSync(DATA_DIR, { recursive: true });
86
- writeFileSync(path, JSON.stringify(identity, null, 2), { encoding: 'utf8', mode: 0o600 });
87
- try {
88
- chmodSync(path, 0o600);
89
- } catch {
90
- /* windows/fs quirk */
91
- }
95
+ writeJsonAtomicSync(path, identity, { secret: true, backup: true });
92
96
  } catch (err) {
93
97
  logWebhook(`hook tunnel: identity persist failed — ${err?.message || err}`);
94
98
  }
@@ -2,8 +2,6 @@ import { logWebhook } from './log.mjs';
2
2
  import { recordEndpointEvent, updateDeliveryStatus } from '../webhooks-db.mjs';
3
3
  import { UNTRUSTED_MARKER, scrubFenceMarker } from './untrusted-fence.mjs';
4
4
 
5
- const DISPATCH_TIMEOUT_MS = 10 * 60 * 1000;
6
-
7
5
  /** The headers a run's prompt shows (and a stored event keeps). */
8
6
  function promptHeaders(headers) {
9
7
  return Object.fromEntries(Object.entries(headers || {}).filter(([k]) => k.startsWith('x-') || k === 'content-type'));
@@ -47,33 +45,16 @@ function webhookEventSummary(headers, body) {
47
45
  return [type, field(body?.action)].filter(Boolean).join(' · ').slice(0, 120);
48
46
  }
49
47
 
50
- function trackDispatch({ name, deliveryId, dispatchP, controller }) {
48
+ function trackDispatch({ name, deliveryId, dispatchP, updateDeliveryStatus }) {
51
49
  let settled = false;
52
- const timeoutHandle = setTimeout(
53
- () => controller.abort(new Error(`bridge dispatch timed out after ${DISPATCH_TIMEOUT_MS}ms`)),
54
- DISPATCH_TIMEOUT_MS
55
- );
56
50
  const finish = (status, fields, message) => {
57
51
  if (settled) return;
58
52
  settled = true;
59
- clearTimeout(timeoutHandle);
60
53
  void updateDeliveryStatus(name, deliveryId, status, fields).catch((e) =>
61
54
  logWebhook(`${name}: delivery status update failed: ${e?.message || e}`)
62
55
  );
63
56
  logWebhook(message);
64
57
  };
65
- controller.signal.addEventListener(
66
- 'abort',
67
- () => {
68
- const reason = controller.signal.reason;
69
- finish(
70
- 'failed',
71
- { error: String(reason?.message || reason) },
72
- `${name}: webhook session run aborted: ${reason?.message || reason}`
73
- );
74
- },
75
- { once: true }
76
- );
77
58
  dispatchP.then(
78
59
  (run) =>
79
60
  run?.skipped
@@ -88,23 +69,20 @@ function trackDispatch({ name, deliveryId, dispatchP, controller }) {
88
69
  );
89
70
  }
90
71
 
91
- function createWebhookSessionDispatcher({ getBridgeDispatch }) {
72
+ // `store` is the delivery record (injectable for tests).
73
+ function createWebhookSessionDispatcher({ getBridgeDispatch, store = { recordEndpointEvent, updateDeliveryStatus } }) {
74
+ const { recordEndpointEvent: recordEvent, updateDeliveryStatus: setStatus } = store;
92
75
  async function dispatchSessionRun(name, fullPrompt, deliveryId, res, extra = {}) {
93
- await updateDeliveryStatus(name, deliveryId, 'processing');
94
- // Session dispatch must not be allowed to hang forever — without a
95
- // ceiling a stuck LLM call leaves the delivery in `processing`
96
- // for the lifetime of the process and dedup keeps re-running
97
- // forever. 10 minutes covers the slowest handler run we ship.
98
- // Racing a bare timer against the dispatch promise reported failure while
99
- // the run itself kept going: the delivery row was already `failed` when the
100
- // late result arrived and flipped it again. The timeout now aborts the
101
- // dispatch through a signal the dispatcher receives, and the first outcome
102
- // to land is the only one that writes a terminal status.
103
- const controller = new AbortController();
76
+ await setStatus(name, deliveryId, 'processing');
77
+ // The HTTP caller is answered 202 at once; the run is a turn in the bot's
78
+ // chat and has no deadline (a run may wait on the user's approval for as
79
+ // long as it takes), so nothing here cancels it. The delivery stays
80
+ // `processing` until the run ends, then `done` / `skipped` / `failed`; a
81
+ // second delivery meanwhile is skipped by the run's own overlap rule.
104
82
  const dispatchP = Promise.resolve(
105
83
  getBridgeDispatch()({
106
84
  prompt: fullPrompt,
107
- signal: controller.signal,
85
+ signal: null,
108
86
  // The endpoint's project folder, named to the bot in the turn.
109
87
  cwd: extra.cwd || null,
110
88
  attachments: extra.attachments || null,
@@ -123,7 +101,7 @@ function createWebhookSessionDispatcher({ getBridgeDispatch }) {
123
101
  },
124
102
  })
125
103
  );
126
- trackDispatch({ name, deliveryId, dispatchP, controller });
104
+ trackDispatch({ name, deliveryId, dispatchP, updateDeliveryStatus: setStatus });
127
105
  res.writeHead(202, { 'Content-Type': 'application/json' });
128
106
  res.end(JSON.stringify({ status: 'accepted', handler: 'session', id: deliveryId }));
129
107
  }
@@ -135,7 +113,7 @@ function createWebhookSessionDispatcher({ getBridgeDispatch }) {
135
113
  if (!getBridgeDispatch()) throw new Error(`[webhook] session dispatch requires bridgeDispatch`);
136
114
  // Kept for the app's "Run now", which re-runs the latest event. Losing
137
115
  // the record never blocks the delivery itself.
138
- await recordEndpointEvent(name, { body, headers: promptHeaders(headers) }).catch((e) =>
116
+ await recordEvent(name, { body, headers: promptHeaders(headers) }).catch((e) =>
139
117
  logWebhook(`${name}: event record failed: ${e?.message || e}`)
140
118
  );
141
119
  await dispatchSessionRun(name, run.prompt, deliveryId, res, {
@@ -148,7 +126,7 @@ function createWebhookSessionDispatcher({ getBridgeDispatch }) {
148
126
  });
149
127
  return true;
150
128
  } catch (err) {
151
- await updateDeliveryStatus(name, deliveryId, 'failed', { error: String(err?.message || err) });
129
+ await setStatus(name, deliveryId, 'failed', { error: String(err?.message || err) });
152
130
  logWebhook(`${name}: folder handler error: ${err}`);
153
131
  return false;
154
132
  }
@@ -10,6 +10,7 @@ import { Scheduler } from './scheduler.mjs';
10
10
  import { hasPending as dispatchHasPending } from '../../agent/orchestrator/dispatch-persist.mjs';
11
11
  import { stripSoftWarns } from '../../agent/orchestrator/tool-loop-guard.mjs';
12
12
  import { JsonStateFile } from './state-file.mjs';
13
+ import { writeJsonAtomicSync } from '../../shared/atomic-file.mjs';
13
14
  import {
14
15
  makeInstanceId,
15
16
  getStatusPath,
@@ -115,7 +116,7 @@ function writeBridgeState(active) {
115
116
  try {
116
117
  const stateFile = path.join(ensurePrivateRuntimeRoot(resolveRuntimeRoot()), 'bridge-state.json');
117
118
  fs.mkdirSync(path.dirname(stateFile), { recursive: true });
118
- fs.writeFileSync(stateFile, JSON.stringify({ active, ts: Date.now() }));
119
+ writeJsonAtomicSync(stateFile, { active, ts: Date.now() }, { compact: true });
119
120
  } catch {}
120
121
  }
121
122
  function isChannelBridgeActive() {
@@ -225,7 +226,7 @@ function wireWebhookHandlers() {
225
226
  cwd: cwd || null,
226
227
  attachments: attachments || null,
227
228
  event: context?.event || '',
228
- // Dispatch-timeout cancellation only works if the signal reaches the run.
229
+ // The dispatch has no deadline; a caller-supplied signal still reaches the run.
229
230
  signal: signal || null,
230
231
  prompt,
231
232
  skillSelection,
@@ -7,8 +7,9 @@
7
7
  // arrays (~30 MB). The compact form is built once from tokenizer.json and
8
8
  // cached next to it as tokenizer.bin, keyed by the source file's size and
9
9
  // modification time.
10
- import { readFileSync, statSync, writeFileSync } from 'node:fs';
10
+ import { readFileSync, statSync } from 'node:fs';
11
11
  import { join } from 'node:path';
12
+ import { writeFileAtomicSync } from '../shared/atomic-file.mjs';
12
13
 
13
14
  const MAGIC = 0x4d585442; // "MXTB"
14
15
  const VERSION = 3;
@@ -93,6 +94,9 @@ function readCached(dir) {
93
94
  const h = new Int32Array(buf.buffer, buf.byteOffset, 7);
94
95
  const [size, mtime] = sourceStamp(dir);
95
96
  if (h[0] !== MAGIC || h[1] !== VERSION || h[2] !== size || h[3] !== mtime % 2 ** 31) return null;
97
+ // header (28) + byte table (1024) + char pairs + merges + meta; a shorter
98
+ // file is torn and is rebuilt.
99
+ if (buf.length < 1052 + h[4] * 8 + h[5] * 12 + h[6]) return null;
96
100
  return buf;
97
101
  } catch {
98
102
  return null;
@@ -104,7 +108,9 @@ export function loadCompactTokenizer(dir) {
104
108
  let buf = readCached(dir);
105
109
  if (!buf) {
106
110
  buf = build(dir);
107
- writeFileSync(join(dir, 'tokenizer.bin'), buf);
111
+ // Atomic: a torn cache (valid header, short body) would pass readCached's
112
+ // header check and then fail on every load.
113
+ writeFileAtomicSync(join(dir, 'tokenizer.bin'), buf);
108
114
  }
109
115
  // Copy into an aligned buffer so typed-array views are valid.
110
116
  const ab = new ArrayBuffer(buf.length);
@@ -8,11 +8,12 @@
8
8
  // effort. Decisions are logged locally (lengths and probabilities only, never
9
9
  // the request text).
10
10
  import { Worker } from 'node:worker_threads';
11
- import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync } from 'node:fs';
11
+ import { appendFileSync, mkdirSync, readFileSync, statSync } from 'node:fs';
12
12
  import { dirname, join } from 'node:path';
13
13
  import { fileURLToPath } from 'node:url';
14
14
  import { resolvePluginData } from '../shared/plugin-paths.mjs';
15
15
  import { onnxRuntimeSupported } from '../shared/onnx-runtime-support.mjs';
16
+ import { fileHasData } from '../shared/file-has-data.mjs';
16
17
  import { effortJudgeInstallCurrent, effortJudgeInstallStamped, installEffortJudgeModel } from './model-install.mjs';
17
18
 
18
19
  export const EFFORT_JUDGE_UNSUPPORTED = 'Auto effort is unsupported on darwin-x64 (no onnxruntime-node binding)';
@@ -86,7 +87,9 @@ export function effortJudgeModelDir() {
86
87
  // it is. Unstamped leftovers (hand-copied packs) never load.
87
88
  export function effortJudgeAvailable(dir = effortJudgeModelDir()) {
88
89
  if (!onnxRuntimeSupported()) return false;
89
- if (!MODEL_FILES.every((file) => existsSync(join(dir, file)))) return false;
90
+ // A model file a crash left empty or NUL-filled is not installed: the refresh
91
+ // then re-verifies the folder against the manifest hashes and fetches it again.
92
+ if (!MODEL_FILES.every((file) => fileHasData(join(dir, file)))) return false;
90
93
  return Boolean(process.env.MIXDOG_EFFORT_JUDGE_DIR) || effortJudgeInstallStamped(dir);
91
94
  }
92
95
 
@@ -267,7 +270,7 @@ export function effortJudgeInfo(dir = effortJudgeModelDir()) {
267
270
  /* not installed */
268
271
  }
269
272
  // The installed model names its backbone in calibration.json; older packs predate the field.
270
- let backbone = 'mmBERT-small';
273
+ let backbone = 'EmbeddingGemma 2 (18 layers)';
271
274
  try {
272
275
  backbone = JSON.parse(readFileSync(join(dir, 'calibration.json'), 'utf8')).backbone || backbone;
273
276
  } catch {
@@ -4,9 +4,10 @@
4
4
  // download never replaces a working file. Files that already match are kept.
5
5
  // install.json records the installed release; a newer bundled manifest makes
6
6
  // the install stale and the next refresh downloads only what changed.
7
- import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
7
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync } from 'node:fs';
8
8
  import { join } from 'node:path';
9
9
  import { fileURLToPath } from 'node:url';
10
+ import { parseJsonText, writeJsonAtomicSync } from '../shared/atomic-file.mjs';
10
11
  import { downloadToFileWithRetry } from '../shared/bounded-download.mjs';
11
12
  import { sha256File, validSha256 } from '../shared/native-asset.mjs';
12
13
 
@@ -30,18 +31,25 @@ export function effortJudgeManifest() {
30
31
  return manifest;
31
32
  }
32
33
 
34
+ // Release named by the stamp; '' when it is missing or damaged (empty,
35
+ // NUL-filled), so a damaged stamp reads as not installed and the next refresh
36
+ // re-verifies every file against its sha256 and stamps again.
37
+ function stampedRelease(dir) {
38
+ try {
39
+ return String(parseJsonText(readFileSync(join(dir, STAMP), 'utf8')).release || '');
40
+ } catch {
41
+ return '';
42
+ }
43
+ }
44
+
33
45
  /** True when `dir` holds a complete install of some release (stamped). */
34
46
  export function effortJudgeInstallStamped(dir) {
35
- return existsSync(join(dir, STAMP));
47
+ return stampedRelease(dir) !== '';
36
48
  }
37
49
 
38
50
  /** True when `dir` holds the release the manifest names. */
39
51
  export function effortJudgeInstallCurrent(dir, manifest = effortJudgeManifest()) {
40
- try {
41
- return JSON.parse(readFileSync(join(dir, STAMP), 'utf8')).release === manifest.release;
42
- } catch {
43
- return false;
44
- }
52
+ return stampedRelease(dir) === manifest.release;
45
53
  }
46
54
 
47
55
  /** Download what is missing or different, then stamp the release. */
@@ -76,5 +84,5 @@ export async function installEffortJudgeModel(
76
84
  }
77
85
  }
78
86
  if (replaced) for (const name of DERIVED) rmSync(join(dir, name), { force: true });
79
- writeFileSync(join(dir, STAMP), `${JSON.stringify({ release: manifest.release })}\n`);
87
+ writeJsonAtomicSync(join(dir, STAMP), { release: manifest.release }, { compact: true });
80
88
  }
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "schema_version": 1,
3
- "release": "effort-judge-v6",
3
+ "release": "effort-judge-v7",
4
4
  "files": {
5
5
  "model.onnx": {
6
- "url": "https://github.com/tribgames/mixdog/releases/download/effort-judge-v6/model.onnx",
7
- "sha256": "5abed811aaa44690f8d6292472aa8d930abae49f73a99265bb7fe874044df395",
6
+ "url": "https://github.com/tribgames/mixdog/releases/download/effort-judge-v7/model.onnx",
7
+ "sha256": "d01ceb1685e123335d2b70809e7b53fcc07ffb110b9412b408bc6b09a5394288",
8
8
  "size": 153062716
9
9
  },
10
10
  "tokenizer.json": {
11
- "url": "https://github.com/tribgames/mixdog/releases/download/effort-judge-v6/tokenizer.json",
11
+ "url": "https://github.com/tribgames/mixdog/releases/download/effort-judge-v7/tokenizer.json",
12
12
  "sha256": "22c064aaa468855d6211fe5c2ec8fdec595c97c55eb2ffd3b9fdf71a93b16292",
13
13
  "size": 32170665
14
14
  },
15
15
  "calibration.json": {
16
- "url": "https://github.com/tribgames/mixdog/releases/download/effort-judge-v6/calibration.json",
17
- "sha256": "cc52c28b19fa891cca8913c69871235d5f863bd224d9336ada7d0f8cfb0765f6",
18
- "size": 157
16
+ "url": "https://github.com/tribgames/mixdog/releases/download/effort-judge-v7/calibration.json",
17
+ "sha256": "01fe4cc99972bcca15e6ce8384cabf83c5705117e33da9a87541dc0f3d60a035",
18
+ "size": 223
19
19
  }
20
20
  }
21
21
  }
@@ -4,6 +4,7 @@ import { dirname, join } from 'node:path';
4
4
  import { Readable, Transform } from 'node:stream';
5
5
  import { pipeline } from 'node:stream/promises';
6
6
 
7
+ import { fileHasData } from '../shared/file-has-data.mjs';
7
8
  import { sha256File } from '../shared/native-asset.mjs';
8
9
  import { resolvePluginData } from '../shared/plugin-paths.mjs';
9
10
  import {
@@ -125,9 +126,14 @@ export async function downloadVerifiedLocalAsset(
125
126
  callback(null, chunk);
126
127
  },
127
128
  });
128
- await pipeline(Readable.fromWeb(response.body), meter, createWriteStream(partial, { flags: resumed ? 'a' : 'w' }), {
129
- signal: operationSignal,
130
- });
129
+ await pipeline(
130
+ Readable.fromWeb(response.body),
131
+ meter,
132
+ createWriteStream(partial, { flags: resumed ? 'a' : 'w', flush: true }),
133
+ {
134
+ signal: operationSignal,
135
+ }
136
+ );
131
137
  if (total !== asset.size) {
132
138
  throw new Error(`[local-provider] incomplete download for ${asset.name || destination}: ${total}/${asset.size}`);
133
139
  }
@@ -198,7 +204,7 @@ async function installRuntimeInternal({ dataDir, onProgress, fetchFn, signal })
198
204
  }
199
205
  const target = localProviderRuntimeDirectory(dataDir);
200
206
  const executable = join(target, entry.executable);
201
- if (existsSync(executable)) return localProviderCatalogStatus({ dataDir, hardware });
207
+ if (fileHasData(executable)) return localProviderCatalogStatus({ dataDir, hardware });
202
208
  ensureDiskSpace(localProviderRuntimeRoot(dataDir), entry.downloadBytes);
203
209
  const staging = join(localProviderRuntimeRoot(dataDir), `.staging-${process.pid}`);
204
210
  const downloads = join(localProviderRuntimeRoot(dataDir), '.downloads');
@@ -2,6 +2,7 @@ import { existsSync, readFileSync, statSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
 
5
+ import { fileHasData } from '../shared/file-has-data.mjs';
5
6
  import { resolvePluginData } from '../shared/plugin-paths.mjs';
6
7
  import { localProviderDiskStatus, partialAssetBytes } from './asset-storage.mjs';
7
8
  import { localProviderHardwareStatus, localProviderPlatformKey } from './hardware.mjs';
@@ -54,7 +55,9 @@ export function localProviderModelPath(entry, dataDir = resolvePluginData()) {
54
55
  export function exactLocalProviderFile(path, size) {
55
56
  try {
56
57
  const stat = statSync(path);
57
- return stat.isFile() && stat.size === size;
58
+ // A crash can commit the size of a renamed download without its bytes: a
59
+ // file whose head is all NUL is not installed (it is fetched again).
60
+ return stat.isFile() && stat.size === size && fileHasData(path);
58
61
  } catch {
59
62
  return false;
60
63
  }
@@ -108,7 +111,7 @@ export function localProviderCatalogStatus({
108
111
  platformSupported: Boolean(platformEntry),
109
112
  available: Boolean(platformEntry && hardware?.supported && models.some((entry) => entry.compatible)),
110
113
  runtime: {
111
- installed: Boolean(executable && existsSync(executable)),
114
+ installed: Boolean(executable && fileHasData(executable)),
112
115
  version: LOCAL_PROVIDER_MANIFEST.runtime.version,
113
116
  downloadBytes: platformEntry?.downloadBytes || 0,
114
117
  source: LOCAL_PROVIDER_MANIFEST.runtime.source,
@@ -1,7 +1,7 @@
1
- import { existsSync, readFileSync, statSync } from 'node:fs';
1
+ import { existsSync, statSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { resolvePluginData } from '../shared/plugin-paths.mjs';
4
- import { writeJsonAtomicSync } from '../shared/atomic-file.mjs';
4
+ import { readJsonStateSync, unsupportedState, updateJsonAtomicSync } from '../shared/atomic-file.mjs';
5
5
 
6
6
  const MAX_REGISTRY_BYTES = 1024 * 1024;
7
7
  const cache = new Map();
@@ -42,44 +42,81 @@ function validateModel(entry) {
42
42
  return entry;
43
43
  }
44
44
 
45
- export function registeredLocalModels(dataDir = resolvePluginData()) {
46
- const path = registryPath(dataDir);
47
- if (!existsSync(path)) return [];
48
- const stat = statSync(path);
49
- if (stat.size > MAX_REGISTRY_BYTES) throw new Error('[local-provider] registered model index is too large');
50
- const stamp = `${stat.mtimeMs}:${stat.size}`;
51
- if (cache.get(path)?.stamp === stamp) return cache.get(path).models.map((entry) => ({ ...entry }));
52
- const value = JSON.parse(readFileSync(path, 'utf8'));
45
+ // Each candidate (the index or its backup) is bounded by its own bytes.
46
+ function registryText(content) {
47
+ if (content.length > MAX_REGISTRY_BYTES) throw new Error('[local-provider] registered model index is too large');
48
+ return content.toString('utf8');
49
+ }
50
+
51
+ function parseRegistry(value) {
52
+ // A newer build's index is left in place (reads as no models), not discarded.
53
+ if (Number.isSafeInteger(value?.schema) && value.schema > 1) {
54
+ throw unsupportedState(`[local-provider] registered model index schema ${value.schema} is newer than this build`);
55
+ }
53
56
  if (value?.schema !== 1 || !Array.isArray(value.models) || value.models.length > 256) {
54
57
  throw new Error('[local-provider] invalid registered model index');
55
58
  }
56
59
  const models = value.models.map(validateModel);
57
60
  if (new Set(models.map((entry) => entry.id)).size !== models.length)
58
61
  throw new Error('[local-provider] duplicate registered model ids');
62
+ return models;
63
+ }
64
+
65
+ export function registeredLocalModels(dataDir = resolvePluginData()) {
66
+ const path = registryPath(dataDir);
67
+ if (!existsSync(path)) return [];
68
+ const stat = statSync(path);
69
+ const stamp = `${stat.mtimeMs}:${stat.size}`;
70
+ if (cache.get(path)?.stamp === stamp) return cache.get(path).models.map((entry) => ({ ...entry }));
71
+ // A damaged index (empty, NUL-filled, truncated, invalid) never blocks the
72
+ // provider: its bytes are kept as <file>.corrupt-<ts>, the last good copy
73
+ // (<file>.bak) is restored when there is one, else no model is registered.
74
+ // The downloaded weights are untouched.
75
+ const models = readJsonStateSync(path, { validate: parseRegistry, decode: registryText, fallback: [] });
59
76
  cache.set(path, { stamp, models });
60
77
  return models.map((entry) => ({ ...entry }));
61
78
  }
62
79
 
63
- export function registerLocalModel(entry, dataDir = resolvePluginData()) {
64
- validateModel(entry);
65
- const models = registeredLocalModels(dataDir);
66
- const existing = models.find((model) => model.id === entry.id);
67
- if (existing) {
68
- if (existing.contextWindow !== entry.contextWindow)
69
- throw new Error('[local-provider] this model is already registered with another context size');
70
- return existing;
71
- }
72
- if (models.length >= 256) throw new Error('[local-provider] registered model limit reached');
80
+ // Read-modify-write under the index lock. A damaged index was recovered by the
81
+ // read first; a newer build's index makes the write refuse instead of
82
+ // replacing it (and its backup) with this build's format.
83
+ function mutateRegistry(dataDir, change) {
84
+ registeredLocalModels(dataDir);
73
85
  const path = registryPath(dataDir);
74
- writeJsonAtomicSync(path, { schema: 1, models: [...models, entry] });
86
+ let result;
87
+ updateJsonAtomicSync(
88
+ path,
89
+ (raw) => {
90
+ const next = change(raw == null ? [] : parseRegistry(raw));
91
+ if (!next) return undefined;
92
+ result = next.result;
93
+ return { schema: 1, models: next.models };
94
+ },
95
+ { backup: true }
96
+ );
75
97
  cache.delete(path);
76
- return entry;
98
+ return result;
99
+ }
100
+
101
+ export function registerLocalModel(entry, dataDir = resolvePluginData()) {
102
+ validateModel(entry);
103
+ let existing;
104
+ const registered = mutateRegistry(dataDir, (models) => {
105
+ existing = models.find((model) => model.id === entry.id);
106
+ if (existing) return null;
107
+ if (models.length >= 256) throw new Error('[local-provider] registered model limit reached');
108
+ return { models: [...models, entry], result: entry };
109
+ });
110
+ if (!existing) return registered;
111
+ if (existing.contextWindow !== entry.contextWindow)
112
+ throw new Error('[local-provider] this model is already registered with another context size');
113
+ return existing;
77
114
  }
78
115
 
79
116
  export function forgetRegisteredLocalModel(modelId, dataDir = resolvePluginData()) {
80
- const models = registeredLocalModels(dataDir);
81
- if (!models.some((entry) => entry.id === modelId)) return;
82
- const path = registryPath(dataDir);
83
- writeJsonAtomicSync(path, { schema: 1, models: models.filter((entry) => entry.id !== modelId) });
84
- cache.delete(path);
117
+ mutateRegistry(dataDir, (models) =>
118
+ models.some((entry) => entry.id === modelId)
119
+ ? { models: models.filter((entry) => entry.id !== modelId), result: undefined }
120
+ : null
121
+ );
85
122
  }