mixdog 1.3.1 → 1.3.3

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 (468) hide show
  1. package/README.md +8 -4
  2. package/package.json +8 -8
  3. package/scripts/native-release-status.mjs +238 -0
  4. package/scripts/verify-runtime-dependency-versions.mjs +158 -0
  5. package/src/agents/reviewer/AGENT.md +5 -7
  6. package/src/agents/reviewer/agent.json +1 -1
  7. package/src/defaults/shipped-history.json +9 -1
  8. package/src/defaults/skills/browser-use/SKILL.md +6 -3
  9. package/src/defaults/skills/local-provider/SKILL.md +33 -6
  10. package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
  11. package/src/defaults/skills/setup/SKILL.md +23 -1
  12. package/src/defaults/skills/setup/references/actions.md +50 -3
  13. package/src/defaults/skills/setup/references/surfaces.md +23 -3
  14. package/src/defaults/skills/setup-guide/SKILL.md +7 -3
  15. package/src/defaults/skills/visualize/SKILL.md +9 -6
  16. package/src/defaults/skills/visualize/references/craft.md +8 -1
  17. package/src/help.mjs +1 -1
  18. package/src/lib/keychain-cjs.cjs +4 -1
  19. package/src/lib/rules-builder.cjs +14 -7
  20. package/src/rules/agent-mode/AGENT_MODE.md +8 -5
  21. package/src/rules/agent-mode/MIXY.md +6 -8
  22. package/src/rules/lead/LEAD.md +7 -10
  23. package/src/rules/shared/10-tool-workflow.md +4 -0
  24. package/src/rules/shared/20-research.md +2 -0
  25. package/src/rules/shared/45-destructive.md +5 -1
  26. package/src/rules/shared/60-verification.md +6 -4
  27. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
  28. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
  29. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
  30. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
  31. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
  32. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
  33. package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
  34. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
  35. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  36. package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
  37. package/src/runtime/agent/orchestrator/config.mjs +11 -0
  38. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +9 -2
  39. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -5
  40. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
  41. package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
  42. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +84 -32
  43. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +38 -11
  44. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +15 -3
  45. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
  46. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  47. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +84 -42
  48. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
  49. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -1
  50. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
  51. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +27 -8
  52. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  53. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
  54. package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
  55. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +14 -1
  56. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
  57. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  58. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
  59. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +16 -0
  60. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
  61. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
  62. package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
  63. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
  64. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
  65. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
  66. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
  67. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +2 -1
  68. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
  69. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
  70. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
  71. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
  72. package/src/runtime/agent/orchestrator/providers/registry.mjs +19 -0
  73. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +13 -1
  74. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
  75. package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
  76. package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
  77. package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +5 -0
  78. package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
  79. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
  80. package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +10 -1
  81. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +4 -6
  82. package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
  83. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
  84. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +52 -6
  85. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +35 -13
  86. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +7 -6
  87. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
  88. package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
  89. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +8 -3
  90. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -1
  91. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
  92. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
  93. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
  94. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -2
  95. package/src/runtime/agent/orchestrator/session/agent-room.mjs +196 -45
  96. package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
  97. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  98. package/src/runtime/agent/orchestrator/session/compact/continuation.mjs +15 -6
  99. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +45 -19
  100. package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
  101. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +44 -0
  102. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +185 -37
  103. package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
  104. package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
  105. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
  106. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +6 -2
  107. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +4 -1
  108. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
  109. package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
  110. package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +240 -0
  111. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +102 -35
  112. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
  113. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +19 -9
  114. package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
  115. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
  116. package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
  117. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
  118. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +199 -109
  119. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +5 -0
  120. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +2 -0
  121. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/empty-nudge.mjs +1 -1
  122. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +62 -13
  123. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +22 -6
  124. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
  125. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +5 -5
  126. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -0
  127. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
  128. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +33 -8
  129. package/src/runtime/agent/orchestrator/session/loop/tool-exec/call-prep.mjs +5 -0
  130. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +27 -0
  131. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
  132. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
  133. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
  134. package/src/runtime/agent/orchestrator/session/manager/agent-listing-notice.mjs +102 -0
  135. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +2 -0
  136. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +13 -1
  137. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
  138. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +27 -14
  139. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +14 -0
  140. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  141. package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +1 -0
  142. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
  143. package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +32 -1
  144. package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +115 -0
  145. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
  146. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +6 -0
  147. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
  148. package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
  149. package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
  150. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +36 -0
  151. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
  152. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
  153. package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
  154. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +28 -9
  155. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +55 -27
  156. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +4 -9
  157. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +14 -1
  158. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +7 -3
  159. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
  160. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
  161. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
  162. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +52 -11
  163. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
  164. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +18 -3
  165. package/src/runtime/agent/orchestrator/session/routine-result.mjs +126 -12
  166. package/src/runtime/agent/orchestrator/session/runtime-user-context.mjs +3 -1
  167. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +16 -3
  168. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
  169. package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
  170. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
  171. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
  172. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
  173. package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +5 -4
  174. package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +140 -0
  175. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +5 -0
  176. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +15 -5
  177. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +49 -1
  178. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +80 -1
  179. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
  180. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
  181. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +33 -1
  182. package/src/runtime/agent/orchestrator/session/store-summary-visibility.d.mts +5 -0
  183. package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +37 -0
  184. package/src/runtime/agent/orchestrator/session/store.mjs +1 -0
  185. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
  186. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
  187. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
  188. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +35 -5
  189. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +42 -2
  190. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +47 -14
  191. package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
  192. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +6 -3
  193. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
  194. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
  195. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
  196. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
  197. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +127 -20
  198. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +557 -0
  199. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +10 -12
  200. package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
  201. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
  202. package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
  203. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
  204. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
  205. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +42 -23
  206. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
  207. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
  208. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
  209. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
  210. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
  211. package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
  212. package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
  213. package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
  214. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
  215. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
  216. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
  217. package/src/runtime/attachments/attachment-reference.mjs +155 -0
  218. package/src/runtime/attachments/legacy-office.mjs +110 -0
  219. package/src/runtime/attachments/limits.mjs +5 -1
  220. package/src/runtime/attachments/office-extract.mjs +3 -6
  221. package/src/runtime/attachments/pdf-extract.mjs +84 -10
  222. package/src/runtime/attachments/prompt-restore.mjs +70 -0
  223. package/src/runtime/attachments/store.mjs +109 -33
  224. package/src/runtime/browser-bridge/client.mjs +73 -20
  225. package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
  226. package/src/runtime/channels/lib/scheduler.mjs +14 -7
  227. package/src/runtime/channels/lib/state-file.mjs +15 -15
  228. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
  229. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
  230. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
  231. package/src/runtime/channels/lib/worker-main.mjs +3 -2
  232. package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
  233. package/src/runtime/effort-judge/judge-client.mjs +66 -16
  234. package/src/runtime/effort-judge/model-install.mjs +47 -7
  235. package/src/runtime/local-provider/asset-installer.mjs +10 -4
  236. package/src/runtime/local-provider/catalog.mjs +5 -2
  237. package/src/runtime/local-provider/registered-models.mjs +64 -27
  238. package/src/runtime/local-provider/server.mjs +2 -3
  239. package/src/runtime/media/adapters/image-api.mjs +89 -0
  240. package/src/runtime/media/auth.mjs +64 -1
  241. package/src/runtime/media/catalog.mjs +66 -4
  242. package/src/runtime/media/jobs.mjs +4 -0
  243. package/src/runtime/media/lanes.mjs +46 -5
  244. package/src/runtime/media/media-usage.mjs +17 -0
  245. package/src/runtime/media/renditions.mjs +15 -15
  246. package/src/runtime/media/store.mjs +36 -35
  247. package/src/runtime/media/tool.mjs +8 -1
  248. package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
  249. package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
  250. package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
  251. package/src/runtime/memory/lib/memory-chunk-quality.mjs +14 -2
  252. package/src/runtime/memory/lib/pg/process.mjs +8 -4
  253. package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
  254. package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
  255. package/src/runtime/office/core/journal.mjs +7 -14
  256. package/src/runtime/office/design/library/design-library-core.mjs +4 -16
  257. package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
  258. package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
  259. package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
  260. package/src/runtime/office/pdf/pdf-render.mjs +30 -18
  261. package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
  262. package/src/runtime/office/portable/portable-soffice.mjs +135 -29
  263. package/src/runtime/shared/agent-avatar-sprites.mjs +23 -8
  264. package/src/runtime/shared/agent-avatar.mjs +3 -2
  265. package/src/runtime/shared/agent-kind-migration.mjs +200 -0
  266. package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
  267. package/src/runtime/shared/agent-message-envelope.mjs +44 -0
  268. package/src/runtime/shared/agent-reap-state.d.mts +4 -0
  269. package/src/runtime/shared/agent-room-host.d.mts +31 -0
  270. package/src/runtime/shared/agent-room-host.mjs +58 -0
  271. package/src/runtime/shared/agent-store.mjs +24 -7
  272. package/src/runtime/shared/agent-task-starts.mjs +27 -0
  273. package/src/runtime/shared/atomic-file.d.mts +50 -0
  274. package/src/runtime/shared/atomic-file.mjs +394 -132
  275. package/src/runtime/shared/background-tasks.mjs +7 -4
  276. package/src/runtime/shared/bot-hops.mjs +35 -0
  277. package/src/runtime/shared/bot-split-migration.mjs +1 -1
  278. package/src/runtime/shared/bounded-download.mjs +4 -2
  279. package/src/runtime/shared/config.mjs +39 -9
  280. package/src/runtime/shared/defaults-separation.mjs +6 -2
  281. package/src/runtime/shared/delegated-approval-host.mjs +74 -0
  282. package/src/runtime/shared/err-text.mjs +4 -0
  283. package/src/runtime/shared/error-presentation.mjs +22 -4
  284. package/src/runtime/shared/file-has-data.mjs +28 -0
  285. package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
  286. package/src/runtime/shared/pending-agent-work.mjs +54 -0
  287. package/src/runtime/shared/process-lifecycle.mjs +8 -12
  288. package/src/runtime/shared/projects.mjs +50 -41
  289. package/src/runtime/shared/provider-accounts.mjs +17 -12
  290. package/src/runtime/shared/redact-directories.mjs +18 -0
  291. package/src/runtime/shared/remove-directory.mjs +28 -0
  292. package/src/runtime/shared/routine-trigger.d.mts +6 -1
  293. package/src/runtime/shared/routine-trigger.mjs +14 -6
  294. package/src/runtime/shared/run-child.d.mts +3 -0
  295. package/src/runtime/shared/run-child.mjs +24 -3
  296. package/src/runtime/shared/session-baseline-store.mjs +8 -2
  297. package/src/runtime/shared/session-sidebar-host.d.mts +11 -2
  298. package/src/runtime/shared/session-sidebar-host.mjs +16 -3
  299. package/src/runtime/shared/singleton-owner.mjs +7 -6
  300. package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
  301. package/src/runtime/shared/staged-update.mjs +4 -2
  302. package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
  303. package/src/runtime/shared/tool-execution-contract.mjs +10 -0
  304. package/src/runtime/shared/tool-work-units.mjs +12 -9
  305. package/src/runtime/shared/transcript-metadata.mjs +28 -3
  306. package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
  307. package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
  308. package/src/runtime/shared/turn-snapshot.mjs +277 -124
  309. package/src/runtime/shared/user-cwd.mjs +9 -4
  310. package/src/runtime/shared/user-data-guard.mjs +92 -61
  311. package/src/runtime/shared/utf8-cut.mjs +10 -0
  312. package/src/runtime/shared/worktrees/activity-gate.mjs +251 -61
  313. package/src/runtime/shared/worktrees/contract.mjs +11 -3
  314. package/src/runtime/shared/worktrees/maintenance.mjs +11 -2
  315. package/src/runtime/shared/worktrees/manager.mjs +419 -52
  316. package/src/runtime/shared/worktrees/registry.mjs +29 -27
  317. package/src/runtime/shared/worktrees/tool-host.mjs +39 -0
  318. package/src/runtime/tidy/core-install.mjs +26 -4
  319. package/src/runtime/tidy/install.mjs +91 -23
  320. package/src/runtime/tidy/process.mjs +41 -8
  321. package/src/runtime/tidy/resolve.mjs +10 -7
  322. package/src/runtime/web-search/lib/config.mjs +4 -16
  323. package/src/session-runtime/agent-home-delivery.mjs +542 -204
  324. package/src/session-runtime/agent-profile-changes.mjs +16 -0
  325. package/src/session-runtime/agent-room-delivery.mjs +81 -35
  326. package/src/session-runtime/agent-task-sessions.mjs +811 -179
  327. package/src/session-runtime/agent-tool-routing.mjs +1 -0
  328. package/src/session-runtime/boot/apis.mjs +48 -24
  329. package/src/session-runtime/boot/config.mjs +1 -0
  330. package/src/session-runtime/boot/facade.mjs +16 -7
  331. package/src/session-runtime/boot/infrastructure.mjs +14 -2
  332. package/src/session-runtime/boot/providers.mjs +2 -2
  333. package/src/session-runtime/boot/shared.mjs +12 -6
  334. package/src/session-runtime/boot/tools.mjs +23 -3
  335. package/src/session-runtime/bot-status.mjs +432 -0
  336. package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
  337. package/src/session-runtime/config-lifecycle.mjs +60 -55
  338. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
  339. package/src/session-runtime/held-requests.mjs +31 -33
  340. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
  341. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
  342. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +24 -26
  343. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
  344. package/src/session-runtime/lifecycle/teardown.mjs +11 -3
  345. package/src/session-runtime/mcp-glue/connect.mjs +100 -186
  346. package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
  347. package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
  348. package/src/session-runtime/media-api.mjs +5 -2
  349. package/src/session-runtime/model-recency.mjs +1 -0
  350. package/src/session-runtime/model-route/route-tuning.mjs +4 -1
  351. package/src/session-runtime/model-route/set-route.mjs +61 -1
  352. package/src/session-runtime/owned-automation-run.mjs +8 -1
  353. package/src/session-runtime/provider-models/row-hydration.mjs +8 -1
  354. package/src/session-runtime/quick-model-rows/model-meta.mjs +2 -0
  355. package/src/session-runtime/quick-model-rows/provider-rows.mjs +7 -1
  356. package/src/session-runtime/route-resolution.mjs +16 -8
  357. package/src/session-runtime/route-state.mjs +1 -59
  358. package/src/session-runtime/routine-home-turn.mjs +5 -3
  359. package/src/session-runtime/runtime-bootstrap.mjs +5 -3
  360. package/src/session-runtime/runtime-paths.mjs +11 -5
  361. package/src/session-runtime/runtime-review-api.mjs +4 -1
  362. package/src/session-runtime/runtime-tunables.mjs +6 -0
  363. package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
  364. package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
  365. package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
  366. package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
  367. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +34 -19
  368. package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
  369. package/src/session-runtime/services/agent-tool/send-flow.mjs +25 -21
  370. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +7 -0
  371. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
  372. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +8 -5
  373. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
  374. package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
  375. package/src/session-runtime/services/agent-tool.mjs +6 -0
  376. package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
  377. package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
  378. package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
  379. package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
  380. package/src/session-runtime/services/plugin-admin.mjs +26 -26
  381. package/src/session-runtime/services/provider-admin.mjs +26 -6
  382. package/src/session-runtime/services/schedule-access.mjs +33 -4
  383. package/src/session-runtime/services/seeds.mjs +4 -3
  384. package/src/session-runtime/services/usage-dashboard.mjs +1 -1
  385. package/src/session-runtime/services/usage-stats-model.mjs +2 -1
  386. package/src/session-runtime/services/worktree-access.mjs +27 -0
  387. package/src/session-runtime/session-create/materialize.mjs +2 -3
  388. package/src/session-runtime/session-create/session-options.mjs +15 -2
  389. package/src/session-runtime/session-hooks.mjs +9 -17
  390. package/src/session-runtime/settings-builtin-tools-api.mjs +65 -1
  391. package/src/session-runtime/settings-compaction-api.mjs +86 -39
  392. package/src/session-runtime/setup-tool/executor.mjs +12 -2
  393. package/src/session-runtime/setup-tool/settings-contract.mjs +34 -1
  394. package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
  395. package/src/session-runtime/skills-api.mjs +12 -5
  396. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +349 -0
  397. package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
  398. package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
  399. package/src/session-runtime/tool-policy/policy-stage.mjs +183 -0
  400. package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
  401. package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
  402. package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
  403. package/src/session-runtime/tool-policy-refresh.mjs +48 -369
  404. package/src/session-runtime/tool-surface.mjs +10 -4
  405. package/src/session-runtime/turn/session-ops.mjs +8 -0
  406. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +95 -88
  407. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  408. package/src/session-runtime/turn/turn-run.mjs +7 -2
  409. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
  410. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +527 -333
  411. package/src/session-runtime/workflow-agents-api/agent-route.mjs +85 -63
  412. package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
  413. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
  414. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
  415. package/src/session-runtime/workspace/transition-steps.mjs +1 -18
  416. package/src/session-runtime/workspace/transitions.mjs +279 -67
  417. package/src/session-runtime/workspace/workspace-state.mjs +7 -6
  418. package/src/session-runtime/workspace/worktree-tool.mjs +194 -0
  419. package/src/standalone/daemon-agent-control.mjs +4 -1
  420. package/src/standalone/daemon-lag-profiler.mjs +3 -1
  421. package/src/standalone/daemon-session-control.mjs +173 -15
  422. package/src/standalone/daemon-telemetry.mjs +9 -0
  423. package/src/standalone/daemon.mjs +33 -10
  424. package/src/standalone/local-session-runtime.mjs +33 -16
  425. package/src/standalone/session-client.mjs +7 -2
  426. package/src/standalone/session-protocol.mjs +8 -0
  427. package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +2 -0
  428. package/src/standalone/session-service/agent-route-follow.mjs +69 -0
  429. package/src/standalone/session-service/session-calls/session-turns.mjs +33 -4
  430. package/src/standalone/session-service.mjs +21 -0
  431. package/src/standalone/session-state-projection.mjs +19 -9
  432. package/src/standalone/session-tool-output.mjs +36 -9
  433. package/src/standalone/staged-import.mjs +47 -0
  434. package/src/tui/App.jsx +2 -0
  435. package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
  436. package/src/tui/app/message-selector.mjs +15 -2
  437. package/src/tui/app/model-options.mjs +16 -2
  438. package/src/tui/app/model-picker.mjs +2 -1
  439. package/src/tui/app/route-pickers.mjs +3 -1
  440. package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
  441. package/src/tui/app/slash-commands.mjs +1 -1
  442. package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
  443. package/src/tui/dist/index.mjs +617 -369
  444. package/src/tui/paste-attachments.mjs +0 -1
  445. package/src/tui/paste-text-policy.mjs +4 -5
  446. package/src/tui/prompt-history-store.mjs +16 -21
  447. package/src/tui/session/agent-job-feed/execution-delivery.mjs +4 -1
  448. package/src/tui/session/context-state.mjs +4 -0
  449. package/src/tui/session/notice-surface.mjs +3 -0
  450. package/src/tui/session/session-api/intake/abort.mjs +52 -18
  451. package/src/tui/session/session-api/intake/rewind.mjs +39 -4
  452. package/src/tui/session/session-api/media.mjs +2 -1
  453. package/src/tui/session/session-api/routes-agent-definitions.mjs +76 -0
  454. package/src/tui/session/session-api/routes-write.mjs +203 -0
  455. package/src/tui/session/session-api/routes.mjs +23 -247
  456. package/src/tui/session/session-api/settings.mjs +14 -1
  457. package/src/tui/session/session-flow/drain.mjs +28 -3
  458. package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
  459. package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
  460. package/src/tui/session/session-flow/steering.mjs +8 -3
  461. package/src/tui/session/tool-approval.mjs +142 -14
  462. package/src/tui/session/transcript-intake.mjs +2 -0
  463. package/src/tui/session/turn.mjs +23 -14
  464. package/src/tui/transcript-tool-failures.mjs +8 -2
  465. package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
  466. package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
  467. package/src/rules/agent-mode/ROUTINE.md +0 -6
  468. package/src/session-runtime/room-member-gate.mjs +0 -48
@@ -8,12 +8,18 @@
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 { effortJudgeInstallCurrent, effortJudgeInstallStamped, installEffortJudgeModel } from './model-install.mjs';
16
+ import { fileHasData } from '../shared/file-has-data.mjs';
17
+ import {
18
+ effortJudgeInstallCurrent,
19
+ effortJudgeInstallStamped,
20
+ installEffortJudgeModel,
21
+ invalidateDamagedEffortJudgeInstall,
22
+ } from './model-install.mjs';
17
23
 
18
24
  export const EFFORT_JUDGE_UNSUPPORTED = 'Auto effort is unsupported on darwin-x64 (no onnxruntime-node binding)';
19
25
  const WORKER_PATH = fileURLToPath(new URL('./judge-worker.mjs', import.meta.url));
@@ -86,7 +92,9 @@ export function effortJudgeModelDir() {
86
92
  // it is. Unstamped leftovers (hand-copied packs) never load.
87
93
  export function effortJudgeAvailable(dir = effortJudgeModelDir()) {
88
94
  if (!onnxRuntimeSupported()) return false;
89
- if (!MODEL_FILES.every((file) => existsSync(join(dir, file)))) return false;
95
+ // A model file a crash left empty or NUL-filled is not installed: the refresh
96
+ // then re-verifies the folder against the manifest hashes and fetches it again.
97
+ if (!MODEL_FILES.every((file) => fileHasData(join(dir, file)))) return false;
90
98
  return Boolean(process.env.MIXDOG_EFFORT_JUDGE_DIR) || effortJudgeInstallStamped(dir);
91
99
  }
92
100
 
@@ -136,6 +144,7 @@ function ensureWorker(dir) {
136
144
  lastLoadError = msg.message;
137
145
  lastLoadErrorAt = Date.now();
138
146
  notifyReady(false);
147
+ void repairAfterLoadError(dir);
139
148
  } else if (msg.type === 'result') {
140
149
  settle(msg.id, (entry) => entry.resolve(msg.probs));
141
150
  } else if (msg.type === 'error') {
@@ -201,6 +210,49 @@ function request(target, { request: text, prev, prevRequest, step }) {
201
210
  // One install at a time (the settings install and the boot refresh can overlap).
202
211
  let installing = null;
203
212
  let refreshChecked = false;
213
+ let verifying = false;
214
+
215
+ /** Why the judge last failed to load; '' when it loaded or never failed. */
216
+ export function effortJudgeLastError() {
217
+ return lastLoadError;
218
+ }
219
+
220
+ // Runs `task` as the one install: the slot stays taken until it settles, so
221
+ // installs, refreshes and repairs never overlap.
222
+ function occupyInstall(task) {
223
+ const run = task().finally(() => {
224
+ if (installing === run) installing = null;
225
+ });
226
+ installing = run;
227
+ return run;
228
+ }
229
+
230
+ // A model that fails to load may be damaged on disk (a crash can leave a
231
+ // stamped file with a zeroed tail). Re-verify the hashes once per load error,
232
+ // off the turn's path: a mismatch drops the stamp and re-downloads only the bad
233
+ // files; matching hashes keep the plain LOAD_RETRY_MS back-off. Verifying and
234
+ // invalidating hold the install slot, after any install in flight has settled,
235
+ // so the stamp they remove is never one an installer just wrote.
236
+ async function repairAfterLoadError(dir) {
237
+ if (verifying || process.env.MIXDOG_EFFORT_JUDGE_DIR || !onnxRuntimeSupported()) return;
238
+ verifying = true;
239
+ try {
240
+ while (installing) await installing.catch(() => {});
241
+ await occupyInstall(async () => {
242
+ const bad = await invalidateDamagedEffortJudgeInstall(dir);
243
+ if (!bad.length) return;
244
+ process.stderr.write(`[effort-judge] reinstalling damaged model files: ${bad.join(', ')}\n`);
245
+ await runInstall();
246
+ // The files are replaced: the next turn loads them without the back-off.
247
+ lastLoadErrorAt = 0;
248
+ lastLoadError = '';
249
+ });
250
+ } catch (error) {
251
+ process.stderr.write(`[effort-judge] model repair failed: ${error?.message || error}\n`);
252
+ } finally {
253
+ verifying = false;
254
+ }
255
+ }
204
256
 
205
257
  /**
206
258
  * Installs or updates the judge model from the release the bundled manifest
@@ -210,19 +262,17 @@ let refreshChecked = false;
210
262
  */
211
263
  export function installEffortJudge() {
212
264
  if (!onnxRuntimeSupported()) return Promise.reject(new Error(EFFORT_JUDGE_UNSUPPORTED));
213
- installing ??= (async () => {
214
- const dir = effortJudgeModelDir();
215
- if (process.env.MIXDOG_EFFORT_JUDGE_DIR || (effortJudgeAvailable(dir) && effortJudgeInstallCurrent(dir))) {
216
- if (!effortJudgeAvailable(dir))
217
- throw new Error(`The Auto reasoning model is not installed (expected in ${dir}).`);
218
- return;
219
- }
220
- await installEffortJudgeModel(dir);
221
- if (worker) await shutdownEffortJudge();
222
- })().finally(() => {
223
- installing = null;
224
- });
225
- return installing;
265
+ return installing ?? occupyInstall(runInstall);
266
+ }
267
+
268
+ async function runInstall() {
269
+ const dir = effortJudgeModelDir();
270
+ if (process.env.MIXDOG_EFFORT_JUDGE_DIR || (effortJudgeAvailable(dir) && effortJudgeInstallCurrent(dir))) {
271
+ if (!effortJudgeAvailable(dir)) throw new Error(`The Auto reasoning model is not installed (expected in ${dir}).`);
272
+ return;
273
+ }
274
+ await installEffortJudgeModel(dir);
275
+ if (worker) await shutdownEffortJudge();
226
276
  }
227
277
 
228
278
  /**
@@ -4,9 +4,11 @@
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
+ import { open as openAsync } from 'node:fs/promises';
8
9
  import { join } from 'node:path';
9
10
  import { fileURLToPath } from 'node:url';
11
+ import { isFileFsyncUnsupported, parseJsonText, writeJsonAtomicSync } from '../shared/atomic-file.mjs';
10
12
  import { downloadToFileWithRetry } from '../shared/bounded-download.mjs';
11
13
  import { sha256File, validSha256 } from '../shared/native-asset.mjs';
12
14
 
@@ -30,24 +32,61 @@ export function effortJudgeManifest() {
30
32
  return manifest;
31
33
  }
32
34
 
35
+ // Release named by the stamp; '' when it is missing or damaged (empty,
36
+ // NUL-filled), so a damaged stamp reads as not installed and the next refresh
37
+ // re-verifies every file against its sha256 and stamps again.
38
+ function stampedRelease(dir) {
39
+ try {
40
+ return String(parseJsonText(readFileSync(join(dir, STAMP), 'utf8')).release || '');
41
+ } catch {
42
+ return '';
43
+ }
44
+ }
45
+
33
46
  /** True when `dir` holds a complete install of some release (stamped). */
34
47
  export function effortJudgeInstallStamped(dir) {
35
- return existsSync(join(dir, STAMP));
48
+ return stampedRelease(dir) !== '';
36
49
  }
37
50
 
38
51
  /** True when `dir` holds the release the manifest names. */
39
52
  export function effortJudgeInstallCurrent(dir, manifest = effortJudgeManifest()) {
53
+ return stampedRelease(dir) === manifest.release;
54
+ }
55
+
56
+ /**
57
+ * Files in `dir` that are missing or differ from the manifest sha256. When any
58
+ * does, the stamp is removed so the folder reads as not installed and the next
59
+ * install re-downloads only those files (a crash can leave a stamped model with
60
+ * its tail zeroed: same size, wrong hash).
61
+ */
62
+ export async function invalidateDamagedEffortJudgeInstall(dir, manifest = effortJudgeManifest()) {
63
+ const bad = [];
64
+ for (const [name, file] of Object.entries(manifest.files)) {
65
+ const target = join(dir, name);
66
+ if (!existsSync(target) || (await sha256File(target)) !== file.sha256) bad.push(name);
67
+ }
68
+ if (bad.length) rmSync(join(dir, STAMP), { force: true });
69
+ return bad;
70
+ }
71
+
72
+ // Flushes the downloaded file through a write handle (Windows refuses fsync on
73
+ // a read-only one) so a crash right after the rename cannot publish a file
74
+ // whose size is committed but whose tail never reached the disk.
75
+ export async function fsyncFile(path, open = openAsync) {
76
+ const handle = await open(path, 'r+');
40
77
  try {
41
- return JSON.parse(readFileSync(join(dir, STAMP), 'utf8')).release === manifest.release;
42
- } catch {
43
- return false;
78
+ await handle.sync();
79
+ } catch (err) {
80
+ if (!isFileFsyncUnsupported(err)) throw err;
81
+ } finally {
82
+ await handle.close();
44
83
  }
45
84
  }
46
85
 
47
86
  /** Download what is missing or different, then stamp the release. */
48
87
  export async function installEffortJudgeModel(
49
88
  dir,
50
- { fetchFn = globalThis.fetch, manifest = effortJudgeManifest() } = {}
89
+ { fetchFn = globalThis.fetch, manifest = effortJudgeManifest(), syncFile = fsyncFile } = {}
51
90
  ) {
52
91
  mkdirSync(dir, { recursive: true });
53
92
  let replaced = false;
@@ -65,6 +104,7 @@ export async function installEffortJudgeModel(
65
104
  const actual = await sha256File(partial);
66
105
  if (actual !== file.sha256)
67
106
  throw new Error(`effort judge ${name}: sha256 mismatch (expected ${file.sha256}, got ${actual})`);
107
+ await syncFile(partial);
68
108
  // From the first replaced file until the new stamp, the folder is a mix of
69
109
  // releases: without a stamp it reads as not installed, so an interrupted
70
110
  // install is never loaded.
@@ -76,5 +116,5 @@ export async function installEffortJudgeModel(
76
116
  }
77
117
  }
78
118
  if (replaced) for (const name of DERIVED) rmSync(join(dir, name), { force: true });
79
- writeFileSync(join(dir, STAMP), `${JSON.stringify({ release: manifest.release })}\n`);
119
+ writeJsonAtomicSync(join(dir, STAMP), { release: manifest.release }, { compact: true });
80
120
  }
@@ -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
  }
@@ -1,5 +1,4 @@
1
- import { existsSync } from 'node:fs';
2
-
1
+ import { fileHasData } from '../shared/file-has-data.mjs';
3
2
  import { resolvePluginData } from '../shared/plugin-paths.mjs';
4
3
  import {
5
4
  exactLocalProviderFile,
@@ -65,7 +64,7 @@ export async function ensureLocalProviderServer(modelId, { dataDir = resolvePlug
65
64
  const contextWindow = localContextSettings(entry, dataDir).contextWindow;
66
65
  const executable = localProviderRuntimeExecutable(dataDir);
67
66
  const weights = localProviderModelPath(entry, dataDir);
68
- if (!executable || !existsSync(executable)) {
67
+ if (!executable || !fileHasData(executable)) {
69
68
  throw new Error('[local-provider] runtime is not installed');
70
69
  }
71
70
  if (!exactLocalProviderFile(weights, entry.size)) {
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Normalized Image API adapter for the `openrouter` and `nano-gpt` lanes.
3
+ *
4
+ * Both gateways document the same contract: POST {base}/images with `model`,
5
+ * `prompt`, the model's advertised enum parameters and `input_references`
6
+ * (image_url parts; data URLs accepted), answering `data[].b64_json` (NanoGPT
7
+ * may answer a signed `url` instead) plus `usage`.
8
+ * https://openrouter.ai/docs/guides/overview/multimodal/image-generation
9
+ * https://docs.nano-gpt.com/api-reference/image-generation
10
+ */
11
+ import { resolveImageApiAuth } from '../auth.mjs';
12
+ import { timeoutSignal } from '../bounded-signal.mjs';
13
+ import { decodeBase64Media, downloadPublicMedia } from '../download.mjs';
14
+ import { mediaError } from '../lanes.mjs';
15
+ import { imageApiUsage, withReportedUsage } from '../media-usage.mjs';
16
+ import { upstreamError } from '../upstream-error.mjs';
17
+
18
+ const IMAGE_TIMEOUT_MS = 180_000;
19
+ const LABELS = Object.freeze({ openrouter: 'OpenRouter image', 'nano-gpt': 'NanoGPT image' });
20
+ // Request field per Studio option; values come from the model's own enums.
21
+ const PARAMS = Object.freeze({
22
+ aspectRatio: 'aspect_ratio',
23
+ resolution: 'resolution',
24
+ quality: 'quality',
25
+ background: 'background',
26
+ });
27
+
28
+ function referenceUrl(reference) {
29
+ const data = String(reference?.base64 || '');
30
+ return data.startsWith('data:') ? data : `data:${reference?.mime || 'image/png'};base64,${data}`;
31
+ }
32
+
33
+ /** The stored format comes from the bytes, not a label: only rasters the store keeps. */
34
+ function rasterMime(bytes) {
35
+ if (bytes.length >= 8 && bytes.readUInt32BE(0) === 0x89504e47) return 'image/png';
36
+ if (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) return 'image/jpeg';
37
+ if (bytes.length >= 12 && bytes.toString('ascii', 0, 4) === 'RIFF' && bytes.toString('ascii', 8, 12) === 'WEBP')
38
+ return 'image/webp';
39
+ return '';
40
+ }
41
+
42
+ export async function generateImage({ lane, model, prompt, options = {}, references = [], signal }) {
43
+ const label = LABELS[lane] || 'Image API';
44
+ const { baseURL, token } = resolveImageApiAuth(lane);
45
+ const body = { model, prompt, n: 1 };
46
+ for (const [option, param] of Object.entries(PARAMS)) {
47
+ const value = String(options?.[option] || '').trim();
48
+ if (value) body[param] = value;
49
+ }
50
+ if (references.length) {
51
+ body.input_references = references.map((reference) => ({
52
+ type: 'image_url',
53
+ image_url: { url: referenceUrl(reference) },
54
+ }));
55
+ }
56
+ let res;
57
+ try {
58
+ res = await fetch(`${baseURL}/images`, {
59
+ method: 'POST',
60
+ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
61
+ body: JSON.stringify(body),
62
+ redirect: 'error',
63
+ signal: timeoutSignal(signal, IMAGE_TIMEOUT_MS),
64
+ });
65
+ } catch (error) {
66
+ if (error?.name === 'TimeoutError') throw mediaError(`${label} timed out`, 'MEDIA_TIMEOUT', 504);
67
+ throw error;
68
+ }
69
+ if (!res.ok) throw upstreamError(label, res.status, (await res.text().catch(() => '')).slice(0, 400));
70
+ const data = await res.json().catch(() => null);
71
+ const usage = imageApiUsage(data);
72
+ const entry = Array.isArray(data?.data) ? data.data[0] : null;
73
+ let bytes;
74
+ try {
75
+ if (entry?.b64_json) bytes = decodeBase64Media(entry.b64_json, label);
76
+ else if (entry?.url) bytes = await downloadPublicMedia(entry.url, { signal, label });
77
+ else throw mediaError(`${label} returned no image data`, 'MEDIA_EMPTY_RESULT', 502);
78
+ } catch (error) {
79
+ throw withReportedUsage(error, usage);
80
+ }
81
+ const mime = rasterMime(bytes);
82
+ if (!mime) {
83
+ throw withReportedUsage(
84
+ mediaError(`${label} returned an unsupported image format`, 'MEDIA_UPSTREAM_FAILED', 502),
85
+ usage
86
+ );
87
+ }
88
+ return { bytes, mime, revisedPrompt: entry.revised_prompt || null, usage };
89
+ }
@@ -5,11 +5,74 @@
5
5
  * paths) so the Studio never holds a second copy of a session. API-key lanes
6
6
  * read the same keychain-backed store the agent providers use.
7
7
  */
8
- import { getAgentApiKey } from '../shared/provider-api-key.mjs';
8
+ import { readSection } from '../shared/config.mjs';
9
+ import {
10
+ getAgentApiKey,
11
+ hasAgentApiKey,
12
+ hasProviderApiKey,
13
+ resolveProviderApiKey,
14
+ } from '../shared/provider-api-key.mjs';
9
15
 
10
16
  export { hasGrokOAuthCredentials } from '../agent/orchestrator/providers/oauth-credential-probes.mjs';
11
17
 
12
18
  const XAI_BASE_URL = 'https://api.x.ai/v1';
19
+ // Both gateways serve the same normalized Image API (GET /images/models,
20
+ // POST /images) under these documented bases.
21
+ const IMAGE_API_BASE_URL = Object.freeze({
22
+ openrouter: 'https://openrouter.ai/api/v1',
23
+ 'nano-gpt': 'https://api.nano-gpt.com/api/v1',
24
+ });
25
+
26
+ /** Lanes served by the normalized Image API adapter. */
27
+ export function imageApiLane(laneId) {
28
+ return Object.hasOwn(IMAGE_API_BASE_URL, laneId);
29
+ }
30
+
31
+ function nanoGptHost(baseURL) {
32
+ try {
33
+ const host = new URL(String(baseURL || '')).hostname.toLowerCase();
34
+ return host === 'nano-gpt.com' || host.endsWith('.nano-gpt.com');
35
+ } catch {
36
+ return false;
37
+ }
38
+ }
39
+
40
+ // NanoGPT is not a built-in provider: it is connected as an OpenAI-compatible
41
+ // custom provider, whose key lives under that provider's own id (keychain or
42
+ // its configured environment variable). The first enabled, keyed endpoint on
43
+ // a NanoGPT host is the lane's credential.
44
+ function nanoGptProviders() {
45
+ const providers = readSection('agent')?.providers || {};
46
+ return Object.entries(providers).filter(
47
+ ([id, entry]) =>
48
+ id.startsWith('custom-') &&
49
+ entry?.type === 'custom' &&
50
+ entry.enabled !== false &&
51
+ entry.auth !== 'none' &&
52
+ nanoGptHost(entry.baseURL)
53
+ );
54
+ }
55
+
56
+ /** Presence-only probe for an Image API lane's key; no secret is read. */
57
+ export function hasImageApiCredentials(laneId) {
58
+ if (laneId === 'openrouter') return hasAgentApiKey('openrouter');
59
+ if (laneId === 'nano-gpt') return nanoGptProviders().some(([id, entry]) => hasProviderApiKey(id, entry));
60
+ return false;
61
+ }
62
+
63
+ /** Base URL + bearer for an Image API lane (OpenRouter key, or the connected NanoGPT custom provider's key). */
64
+ export function resolveImageApiAuth(laneId) {
65
+ let token = '';
66
+ if (laneId === 'openrouter') token = getAgentApiKey('openrouter') || '';
67
+ else if (laneId === 'nano-gpt') {
68
+ for (const [id, entry] of nanoGptProviders()) {
69
+ token = resolveProviderApiKey(id, entry);
70
+ if (token) break;
71
+ }
72
+ }
73
+ if (!imageApiLane(laneId) || !token) throw unauthenticated(`${laneId} API key is not configured`);
74
+ return { baseURL: IMAGE_API_BASE_URL[laneId], token };
75
+ }
13
76
 
14
77
  // Built here rather than with lanes.mjs's mediaError: importing it would close
15
78
  // an auth -> lanes -> catalog -> auth module cycle.