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
@@ -6,12 +6,15 @@
6
6
  * transaction ledger that makes creation rollback and deletion resumable:
7
7
  * which resources this transaction created ('pending' is written before a
8
8
  * resource is made, 'created' after), the deletion decision and any recovery
9
- * diagnostics. Writes are atomic (temp file + rename) under a file lock; an
10
- * unreadable registry is an error, never silently replaced.
9
+ * diagnostics. Writes are atomic (temp file + rename) under a file lock and keep
10
+ * the replaced version as registry.json.bak. A damaged registry (NUL, truncated,
11
+ * malformed) never blocks worktree operations: its bytes are kept aside as
12
+ * registry.json.corrupt-<ts>, the last good copy is restored when there is one,
13
+ * and otherwise the registry starts empty (worktree folders on disk are never
14
+ * touched by that).
11
15
  */
12
- import { readFile } from 'node:fs/promises';
13
16
  import { join } from 'node:path';
14
- import { parseJsonText, withFileLock, writeJsonAtomicAsync } from '../atomic-file.mjs';
17
+ import { readJsonState, readJsonStateSync, updateJsonAtomic } from '../atomic-file.mjs';
15
18
  import { isPlainObject } from '../object.mjs';
16
19
  import { resolvePluginData } from '../plugin-paths.mjs';
17
20
  import { normalizeWorktreeEntry, WORKTREE_STORE_DIR } from './contract.mjs';
@@ -30,24 +33,21 @@ function emptyDoc() {
30
33
  return { version: REGISTRY_VERSION, entries: {} };
31
34
  }
32
35
 
33
- async function readDoc(path) {
34
- let text;
35
- try {
36
- text = await readFile(path, 'utf8');
37
- } catch (error) {
38
- if (error?.code === 'ENOENT') return emptyDoc();
39
- throw error;
40
- }
41
- const value = parseJsonText(text);
42
- if (!isPlainObject(value) || !isPlainObject(value.entries)) {
43
- throw new Error(`Worktree registry ${path} is malformed.`);
44
- }
36
+ const isRegistryDoc = (value) => isPlainObject(value) && isPlainObject(value.entries);
37
+
38
+ function validateDoc(value) {
39
+ if (!isRegistryDoc(value)) throw new Error('Worktree registry is malformed.');
45
40
  return value;
46
41
  }
47
42
 
48
43
  /** Snapshot of every record (plain clones). */
49
44
  export async function readRegistry() {
50
- return readDoc(registryPath());
45
+ return (await readJsonState(registryPath(), { validate: validateDoc })) ?? emptyDoc();
46
+ }
47
+
48
+ /** readRegistry for synchronous callers (the workspace admission gate). */
49
+ export function readRegistrySync() {
50
+ return readJsonStateSync(registryPath(), { validate: validateDoc }) ?? emptyDoc();
51
51
  }
52
52
 
53
53
  /**
@@ -58,17 +58,17 @@ export async function readRegistry() {
58
58
  * @returns {Promise<T>}
59
59
  */
60
60
  export async function mutateRegistry(mutate) {
61
- const path = registryPath();
62
- return withFileLock(
63
- `${path}.lock`,
64
- async () => {
65
- const doc = await readDoc(path);
66
- const result = mutate(doc);
67
- await writeJsonAtomicAsync(path, doc, { lock: false });
68
- return result;
61
+ let result;
62
+ await updateJsonAtomic(
63
+ registryPath(),
64
+ (current) => {
65
+ const doc = isRegistryDoc(current) ? current : emptyDoc();
66
+ result = mutate(doc);
67
+ return doc;
69
68
  },
70
- { timeoutMs: 15_000 }
69
+ { timeoutMs: 15_000, backup: true }
71
70
  );
71
+ return result;
72
72
  }
73
73
 
74
74
  export async function getRecord(id) {
@@ -76,6 +76,11 @@ function powerShellHost(env) {
76
76
  return false;
77
77
  }
78
78
 
79
+ function coreInstallable(id, manifest, env) {
80
+ if (!TIDY_CORE_ENGINE_IDS.includes(id) || !manifestAsset(manifest, id)) return false;
81
+ return id !== 'psscriptanalyzer' || powerShellHost(env);
82
+ }
83
+
79
84
  let installJob = null;
80
85
  let installInFlight = null;
81
86
  // Bumped on every job mutation and every refreshed host-module probe, so the
@@ -118,6 +123,14 @@ async function cachedHostModuleProbe(engine) {
118
123
  return entry?.version || '';
119
124
  }
120
125
 
126
+ /** A host-module engine (PSScriptAnalyzer) whose host exists but whose first
127
+ * probe has not answered yet. Until then "module missing" only means "not
128
+ * looked at yet", so the status reports it as pending instead. */
129
+ function hostModuleProbing(catalog, engine) {
130
+ if (!catalog.managedModule || engine.source !== 'missing' || !engine.command) return false;
131
+ return (hostModuleCache.get(String(engine.command))?.at || 0) === 0;
132
+ }
133
+
121
134
  function clampPercent(value) {
122
135
  return Math.max(0, Math.min(100, Math.round(value)));
123
136
  }
@@ -222,14 +235,17 @@ async function runCoreInstall({
222
235
  signal = null,
223
236
  installEngines = installEnginesImpl,
224
237
  resolveEngines = resolveEnginesImpl,
238
+ engines: only = null,
225
239
  } = {}) {
226
240
  const loaded = manifest || readEnginesManifest();
227
241
  const toolsDir = pluginData ? managedToolsDir(pluginData) : '';
228
- const job = startJob(TIDY_CORE_ENGINE_IDS);
242
+ // A retry for named engines installs just those; the default is the set.
243
+ const wanted = Array.isArray(only) ? TIDY_CORE_ENGINE_IDS.filter((id) => only.includes(id)) : TIDY_CORE_ENGINE_IDS;
244
+ const job = startJob(wanted);
229
245
  try {
230
246
  const resolution = await resolveEngines({
231
247
  cwd: resolutionScope(toolsDir),
232
- engineIds: [...TIDY_CORE_ENGINE_IDS],
248
+ engineIds: [...wanted],
233
249
  pluginData,
234
250
  manifest: loaded,
235
251
  env,
@@ -238,7 +254,7 @@ async function runCoreInstall({
238
254
  });
239
255
  const resolved = new Map((resolution.engines || []).map((engine) => [engine.id, engine]));
240
256
  const pending = [];
241
- for (const id of TIDY_CORE_ENGINE_IDS) {
257
+ for (const id of wanted) {
242
258
  const catalog = ENGINE_CATALOG[id];
243
259
  const engine = resolved.get(id);
244
260
  if (id === 'psscriptanalyzer' && !powerShellHost(env)) {
@@ -400,6 +416,7 @@ function withInstallStatus(inventory) {
400
416
  toolsDir: inventory.toolsDir,
401
417
  core: [...inventory.core],
402
418
  engines: inventory.engines.map((engine) => ({ ...engine })),
419
+ probing: inventory.probing,
403
420
  installing: tidyInstallStatus(),
404
421
  };
405
422
  }
@@ -424,6 +441,7 @@ async function buildEngineInventory({ pluginData, manifest, env, probeVersions,
424
441
  const source = statusSource(engine.source);
425
442
  const version = engine.version || '';
426
443
  const installHint = engine.installHint || catalog.installHint || '';
444
+ const probing = hostModuleProbing(catalog, engine);
427
445
  return {
428
446
  id,
429
447
  title: catalog.title || id,
@@ -437,7 +455,11 @@ async function buildEngineInventory({ pluginData, manifest, env, probeVersions,
437
455
  // Managed engines carry their on-disk cost; a host binary is the user's.
438
456
  ...(source === 'managed' ? { bytes: managedEngineBytes(pluginData, id, version) } : {}),
439
457
  ...(source === 'missing' && installHint ? { installHint } : {}),
458
+ // The host probe has not answered: pending, neither installed nor missing.
459
+ ...(probing ? { probing: true } : {}),
460
+ // A missing core engine the Install/Retry control can fetch right now.
461
+ ...(source === 'missing' && !probing && coreInstallable(id, loaded, env) ? { installable: true } : {}),
440
462
  };
441
463
  });
442
- return { toolsDir, core: [...TIDY_CORE_ENGINE_IDS], engines };
464
+ return { toolsDir, core: [...TIDY_CORE_ENGINE_IDS], engines, probing: engines.some((engine) => engine.probing) };
443
465
  }
@@ -4,11 +4,12 @@
4
4
  // Policy gating happens BEFORE any network call: 'never' refuses, 'ask' returns
5
5
  // needsApproval unless the call carried approveDownloads:true, 'auto' proceeds.
6
6
  // Toolchain engines (rustfmt, gofmt, zig fmt, ...) are never downloadable.
7
- import { chmodSync, existsSync, mkdirSync, renameSync, rmSync, statSync } from 'node:fs';
7
+ import { chmodSync, existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSync } from 'node:fs';
8
8
  import { fileURLToPath } from 'node:url';
9
9
  import { join } from 'node:path';
10
10
  import { MAX_NATIVE_BINARY_DOWNLOAD_BYTES, streamResponseToFile } from '../shared/bounded-download.mjs';
11
11
  import { platformEntryKey, platformKey, sha256File } from '../shared/native-asset.mjs';
12
+ import { fileHasData } from '../shared/file-has-data.mjs';
12
13
  import { readJsonSafe } from '../shared/json-file.mjs';
13
14
  import { ENGINE_CATALOG } from './engines.mjs';
14
15
  import { extractArchive } from './extract.mjs';
@@ -51,7 +52,7 @@ export function managedEngineBinary({ id, manifest, pluginData }) {
51
52
  const binPath = String(found.asset.binPath || '');
52
53
  if (!binPath) return null;
53
54
  const full = join(managedEngineDir(pluginData, id, found.version), binPath);
54
- return existsSync(full) ? { path: full, version: found.version } : null;
55
+ return fileHasData(full) ? { path: full, version: found.version } : null;
55
56
  }
56
57
 
57
58
  /** Throws unless `filePath` hashes to `expected`. */
@@ -148,44 +149,109 @@ export function planInstall({ ids = [], manifest, pluginData = '', policy = 'aut
148
149
  return { targets, present, errors, needsApproval: null };
149
150
  }
150
151
 
151
- async function downloadAsset(target, tmpDir, fetchFn, signal, onProgress) {
152
- const archiveFile = join(tmpDir, `asset-${target.id}`);
153
- const response = await fetchFn(target.url, { signal, redirect: 'follow' });
154
- if (!response?.ok) {
155
- throw new Error(`${LABEL} ${target.id}: HTTP ${response?.status || 0} for ${target.url}`);
152
+ // A network switch can leave a socket open that never delivers another byte;
153
+ // without a deadline the install (and the single in-flight job behind it)
154
+ // would wait forever.
155
+ const DEFAULT_STALL_TIMEOUT_MS = 60_000;
156
+ // A `.tmp-<pid>-<ms>-…` staging dir whose owner crashed is never cleaned by
157
+ // that owner; an hour is far beyond any live download.
158
+ const STALE_TMP_AGE_MS = 60 * 60 * 1000;
159
+
160
+ function pidAlive(pid) {
161
+ try {
162
+ process.kill(pid, 0);
163
+ return true;
164
+ } catch (error) {
165
+ return error?.code === 'EPERM';
166
+ }
167
+ }
168
+
169
+ /** Remove staging dirs a killed install left behind (dead owner, or ancient). */
170
+ function reclaimStaleStaging(engineRoot) {
171
+ let names;
172
+ try {
173
+ names = readdirSync(engineRoot);
174
+ } catch {
175
+ return;
176
+ }
177
+ for (const name of names) {
178
+ const match = /^\.tmp-(\d+)-(\d+)-/.exec(name);
179
+ if (!match) continue;
180
+ const pid = Number(match[1]);
181
+ const ownerGone = pid !== process.pid && !pidAlive(pid);
182
+ if (!ownerGone && Date.now() - Number(match[2]) < STALE_TMP_AGE_MS) continue;
183
+ try {
184
+ rmSync(join(engineRoot, name), { recursive: true, force: true });
185
+ } catch {
186
+ /* best-effort */
187
+ }
156
188
  }
157
- await streamResponseToFile(response, archiveFile, {
158
- maxBytes: MAX_NATIVE_BINARY_DOWNLOAD_BYTES,
159
- label: `${LABEL} ${target.id}`,
160
- // Byte progress per engine: the desktop card renders it live, so the
161
- // callback carries the engine id the bytes belong to.
162
- ...(typeof onProgress === 'function'
163
- ? {
164
- onProgress: ({ downloaded, total }) => {
165
- onProgress({ id: target.id, receivedBytes: downloaded, totalBytes: total });
166
- },
189
+ }
190
+
191
+ async function downloadAsset(target, tmpDir, fetchFn, signal, onProgress, stallTimeoutMs) {
192
+ const archiveFile = join(tmpDir, `asset-${target.id}`);
193
+ const stalled = new AbortController();
194
+ const merged = signal ? AbortSignal.any([signal, stalled.signal]) : stalled.signal;
195
+ let timer;
196
+ const arm = () => {
197
+ clearTimeout(timer);
198
+ timer = setTimeout(() => stalled.abort(), stallTimeoutMs);
199
+ };
200
+ arm();
201
+ try {
202
+ const response = await fetchFn(target.url, { signal: merged, redirect: 'follow' });
203
+ if (!response?.ok) {
204
+ throw new Error(`${LABEL} ${target.id}: HTTP ${response?.status || 0} for ${target.url}`);
205
+ }
206
+ await streamResponseToFile(response, archiveFile, {
207
+ maxBytes: MAX_NATIVE_BINARY_DOWNLOAD_BYTES,
208
+ label: `${LABEL} ${target.id}`,
209
+ signal: merged,
210
+ // Byte progress per engine: the desktop card renders it live, so the
211
+ // callback carries the engine id the bytes belong to. Any bytes also
212
+ // prove the connection is alive.
213
+ onProgress: ({ downloaded, total }) => {
214
+ arm();
215
+ if (typeof onProgress === 'function') {
216
+ onProgress({ id: target.id, receivedBytes: downloaded, totalBytes: total });
167
217
  }
168
- : {}),
169
- });
218
+ },
219
+ });
220
+ } catch (error) {
221
+ if (stalled.signal.aborted && !signal?.aborted) {
222
+ throw new Error(`${LABEL} ${target.id}: download stalled (no data for ${stallTimeoutMs / 1000}s)`);
223
+ }
224
+ throw error;
225
+ } finally {
226
+ clearTimeout(timer);
227
+ }
170
228
  return archiveFile;
171
229
  }
172
230
 
173
231
  /** Download + verify + extract one engine into its versioned managed dir. */
174
232
  export async function installEngine(
175
233
  target,
176
- { pluginData, fetchFn = globalThis.fetch, signal = null, onProgress = null } = {}
234
+ {
235
+ pluginData,
236
+ fetchFn = globalThis.fetch,
237
+ signal = null,
238
+ onProgress = null,
239
+ stallTimeoutMs = DEFAULT_STALL_TIMEOUT_MS,
240
+ } = {}
177
241
  ) {
178
242
  const engineRoot = join(managedToolsDir(pluginData), target.id);
179
243
  const finalDir = managedEngineDir(pluginData, target.id, target.version);
180
244
  const binFull = join(finalDir, target.binPath);
181
- if (existsSync(binFull)) {
245
+ // An engine binary a crash left NUL-filled is not present: it is installed again.
246
+ if (fileHasData(binFull)) {
182
247
  return { id: target.id, version: target.version, path: binFull, bytes: 0, status: 'present' };
183
248
  }
184
249
  mkdirSync(engineRoot, { recursive: true });
250
+ reclaimStaleStaging(engineRoot);
185
251
  const tmpDir = join(engineRoot, `.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
186
252
  mkdirSync(tmpDir, { recursive: true });
187
253
  try {
188
- const archiveFile = await downloadAsset(target, tmpDir, fetchFn, signal, onProgress);
254
+ const archiveFile = await downloadAsset(target, tmpDir, fetchFn, signal, onProgress, stallTimeoutMs);
189
255
  await verifyDownloadDigest(archiveFile, target.sha256);
190
256
  const bytes = statSync(archiveFile).size;
191
257
  const stageDir = join(tmpDir, 'stage');
@@ -207,6 +273,7 @@ export async function installEngine(
207
273
  }
208
274
  }
209
275
  try {
276
+ if (existsSync(finalDir) && !fileHasData(binFull)) rmSync(finalDir, { recursive: true, force: true });
210
277
  renameSync(stageDir, finalDir);
211
278
  } catch (error) {
212
279
  // A concurrent install won the race; its verified copy is equivalent.
@@ -235,6 +302,7 @@ export async function installEngines({
235
302
  fetchFn = globalThis.fetch,
236
303
  signal = null,
237
304
  onProgress = null,
305
+ stallTimeoutMs = undefined,
238
306
  } = {}) {
239
307
  const loaded = manifest || readEnginesManifest();
240
308
  const plan = planInstall({ ids, manifest: loaded, pluginData, policy, approveDownloads });
@@ -249,7 +317,7 @@ export async function installEngines({
249
317
  }
250
318
  for (const target of plan.targets) {
251
319
  try {
252
- installed.push(await installEngine(target, { pluginData, fetchFn, signal, onProgress }));
320
+ installed.push(await installEngine(target, { pluginData, fetchFn, signal, onProgress, stallTimeoutMs }));
253
321
  } catch (error) {
254
322
  errors.push({ id: target.id, error: error?.message || String(error) });
255
323
  }
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { resolvePluginData } from '../../shared/plugin-paths.mjs';
4
- import { renameWithRetrySync, writeJsonAtomicSync } from '../../shared/atomic-file.mjs';
4
+ import { readJsonStateSync, writeJsonAtomicSync } from '../../shared/atomic-file.mjs';
5
5
 
6
6
  // Unified mode: web search shares the plugin data dir with the rest of mixdog.
7
7
  const SHARED_DATA_DIR = resolvePluginData();
@@ -19,22 +19,10 @@ export function ensureDataDir() {
19
19
  fs.mkdirSync(DATA_DIR, { recursive: true });
20
20
  }
21
21
 
22
+ // usage/cache state: a damaged file (empty, NUL-filled, truncated) is kept as
23
+ // <file>.corrupt-<ts> and reads as `fallback`, so search/fetch keep working.
22
24
  export function readJson(filePath, fallback) {
23
- try {
24
- const raw = fs.readFileSync(filePath, 'utf8');
25
- try {
26
- return JSON.parse(raw);
27
- } catch (parseErr) {
28
- try {
29
- renameWithRetrySync(filePath, `${filePath}.corrupt.${Date.now()}`);
30
- } catch {}
31
- process.stderr.write(`[web-search-config] corrupt JSON backed up: ${filePath}\n`);
32
- throw parseErr;
33
- }
34
- } catch (err) {
35
- if (err.code === 'ENOENT') return fallback;
36
- throw err;
37
- }
25
+ return readJsonStateSync(filePath, { fallback });
38
26
  }
39
27
 
40
28
  export function writeJson(filePath, value) {