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,9 +8,9 @@
8
8
  * Data shape: { projects: [{ name, path, addedAt, lastSelectedAt? }] }
9
9
  */
10
10
  import { homedir } from 'node:os';
11
- import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
11
+ import { existsSync, mkdirSync, readFileSync, statSync } from 'node:fs';
12
12
  import { basename, isAbsolute, join, resolve } from 'node:path';
13
- import { withFileLockSync, writeJsonAtomicSync } from './atomic-file.mjs';
13
+ import { holdsNoData, readJsonStateSync, updateJsonAtomicSync, writeFileAtomicSync } from './atomic-file.mjs';
14
14
 
15
15
  const MIXDOG_HOME = process.env.MIXDOG_HOME || join(homedir(), '.mixdog');
16
16
  const PROJECTS_FILE = process.env.MIXDOG_PROJECTS_FILE
@@ -30,47 +30,57 @@ function normalizeKey(absPath) {
30
30
  return process.platform === 'win32' ? text.replace(/[\\/]+$/, '').toLowerCase() : text.replace(/\/+$/, '');
31
31
  }
32
32
 
33
+ // The registry document as a store. A strict read (permission checks and
34
+ // mutations) drops entries without an absolute path: they can never grant
35
+ // anything, and the replaced file survives as projects.json.bak.
36
+ function normalizeStore(parsed, strict) {
37
+ const projects = Array.isArray(parsed?.projects) ? parsed.projects : [];
38
+ return {
39
+ projects: projects
40
+ .filter(
41
+ (entry) => entry && typeof entry.path === 'string' && entry.path.trim() && (!strict || isAbsolute(entry.path))
42
+ )
43
+ .map((entry) => ({
44
+ name: String(entry.name || basename(entry.path) || entry.path),
45
+ path: String(entry.path),
46
+ addedAt: Number(entry.addedAt) || 0,
47
+ ...(Number(entry.lastSelectedAt) > 0 ? { lastSelectedAt: Number(entry.lastSelectedAt) } : {}),
48
+ })),
49
+ };
50
+ }
51
+
52
+ // A damaged registry file (NUL, truncated, malformed) is kept aside as
53
+ // projects.json.corrupt-<ts> and the last good projects.json.bak is restored
54
+ // when there is one, else the registry starts empty: a damaged file never
55
+ // blocks adding, removing or selecting a project. Strict callers still see a
56
+ // real I/O failure (EACCES, EIO) as an error — an unreadable registry is not
57
+ // an empty one; the tolerant listing reads it as empty.
33
58
  function readStore(strict = false) {
34
59
  try {
35
- if (!strict && !existsSync(PROJECTS_FILE)) return { projects: [] };
36
- const raw = readFileSync(PROJECTS_FILE, 'utf8');
37
- const parsed = JSON.parse(raw);
38
- if (
39
- strict &&
40
- (!Array.isArray(parsed?.projects) ||
41
- parsed.projects.some(
42
- (entry) => !entry || typeof entry.path !== 'string' || !entry.path.trim() || !isAbsolute(entry.path)
43
- ))
44
- )
45
- throw new TypeError('Project registry is invalid.');
46
- const projects = Array.isArray(parsed?.projects) ? parsed.projects : [];
47
- return {
48
- projects: projects
49
- .filter((entry) => entry && typeof entry.path === 'string' && entry.path.trim())
50
- .map((entry) => ({
51
- name: String(entry.name || basename(entry.path) || entry.path),
52
- path: String(entry.path),
53
- addedAt: Number(entry.addedAt) || 0,
54
- ...(Number(entry.lastSelectedAt) > 0 ? { lastSelectedAt: Number(entry.lastSelectedAt) } : {}),
55
- })),
56
- };
60
+ return normalizeStore(readJsonStateSync(PROJECTS_FILE), strict);
57
61
  } catch (error) {
58
- if (strict && error?.code !== 'ENOENT') throw error;
62
+ if (strict) throw error;
59
63
  return { projects: [] };
60
64
  }
61
65
  }
62
66
 
63
67
  // Serialize the entire read/modify/write across cooperating processes, not
64
- // just publication. Unlike the generic JSON updater, a corrupt registry must
65
- // never be repaired into an empty document. Preserve synchronous return values
66
- // and no-op behavior; failed reads, lock acquisition and writes propagate.
68
+ // just publication: the file is read under the registry lock. Preserve
69
+ // synchronous return values and no-op behavior (an unchanged store is not
70
+ // written); failed reads, lock acquisition and writes propagate. Each write
71
+ // keeps the replaced version as projects.json.bak.
67
72
  function mutateStore(mutator) {
68
- return withFileLockSync(`${PROJECTS_FILE}.lock`, () => {
69
- const store = readStore(true);
70
- const { value, changed } = mutator(store);
71
- if (changed) writeJsonAtomicSync(PROJECTS_FILE, store, { lock: false });
72
- return value;
73
- });
73
+ let outcome;
74
+ updateJsonAtomicSync(
75
+ PROJECTS_FILE,
76
+ (current) => {
77
+ const store = normalizeStore(current, true);
78
+ outcome = mutator(store);
79
+ return outcome.changed ? store : undefined;
80
+ },
81
+ { backup: true }
82
+ );
83
+ return outcome.value;
74
84
  }
75
85
 
76
86
  /**
@@ -78,7 +88,8 @@ function mutateStore(mutator) {
78
88
  * selection store and memory project_id classification (resolveProjectId)
79
89
  * agree. Rules:
80
90
  * - Never overwrite an existing marker (user may have set 'common' or a
81
- * custom id — respect it).
91
+ * custom id — respect it). A marker holding no data (empty or NUL, what a
92
+ * crash leaves) is no id: it is rewritten.
82
93
  * - Only act when absPath is a real directory on disk; never create the
83
94
  * project folder itself (addProject does NOT create the directory).
84
95
  * - Marker value = provided name, else basename(absPath). Skip when the
@@ -97,14 +108,12 @@ function ensureProjectIdMarker(absPath, name) {
97
108
  return; // missing / unstattable → silently skip
98
109
  }
99
110
  const markerPath = join(absPath, '.mixdog', 'project.id');
100
- if (existsSync(markerPath)) return; // preserve existing value
111
+ const present = existsSync(markerPath);
112
+ if (present && !holdsNoData(readFileSync(markerPath))) return; // preserve existing value
101
113
  const value = String(name || basename(absPath) || '').trim();
102
114
  if (!value || value.toLowerCase() === 'common') return;
103
- const markerDir = join(absPath, '.mixdog');
104
- mkdirSync(markerDir, { recursive: true });
105
- const tmp = `${markerPath}.${process.pid}.tmp`;
106
- writeFileSync(tmp, `${value}\n`, 'utf8');
107
- renameSync(tmp, markerPath);
115
+ // A missing marker is created only if still missing (a concurrent creator's wins).
116
+ writeFileAtomicSync(markerPath, `${value}\n`, { createOnly: !present });
108
117
  } catch {
109
118
  /* best-effort marker; never throw */
110
119
  }
@@ -1,7 +1,6 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
- import { readFileSync } from 'node:fs';
3
2
  import { join } from 'node:path';
4
- import { parseJsonText, updateJsonAtomic } from './atomic-file.mjs';
3
+ import { readJsonStateSync, unsupportedState, updateJsonAtomic } from './atomic-file.mjs';
5
4
  import { resolvePluginData } from './plugin-paths.mjs';
6
5
 
7
6
  export const ACCOUNT_PROVIDERS = Object.freeze([
@@ -34,12 +33,19 @@ function requireAccountId(id) {
34
33
 
35
34
  function validate(raw) {
36
35
  if (raw == null) return { version: 1, providers: {} };
36
+ // A newer build's roster is left in place: reads see no accounts and writes
37
+ // refuse, so a downgrade never discards it.
38
+ if (Number.isSafeInteger(raw.version) && raw.version > 1) {
39
+ throw unsupportedState(`Provider account settings version ${raw.version} is newer than this build.`);
40
+ }
37
41
  if (raw.version !== 1 || !raw.providers || typeof raw.providers !== 'object') {
38
42
  throw new Error('Provider account settings are unreadable.');
39
43
  }
40
44
  for (const [provider, pool] of Object.entries(raw.providers)) {
41
- requireAccountProvider(provider);
42
- if (!Array.isArray(pool.accounts) || pool.accounts.length > MAX_ACCOUNTS) {
45
+ // A provider a newer build added is carried as is: this build never reads
46
+ // or writes its pool, and treating it as damage would discard it.
47
+ if (!ACCOUNT_PROVIDERS.includes(provider)) continue;
48
+ if (!Array.isArray(pool?.accounts) || pool.accounts.length > MAX_ACCOUNTS) {
43
49
  throw new Error('Invalid provider account list.');
44
50
  }
45
51
  const ids = pool.accounts.map((row) => requireAccountId(row.id));
@@ -48,13 +54,11 @@ function validate(raw) {
48
54
  return raw;
49
55
  }
50
56
 
57
+ // Every OAuth provider's init reads the roster, so a damaged one (crash
58
+ // leftovers, truncation, a shape this build rejects) must never throw: it is
59
+ // kept aside and the last good copy, which every write keeps, is restored.
51
60
  function read(dataDir) {
52
- try {
53
- return validate(parseJsonText(readFileSync(file(dataDir), 'utf8')));
54
- } catch (error) {
55
- if (error.code === 'ENOENT') return validate(null);
56
- throw error;
57
- }
61
+ return readJsonStateSync(file(dataDir), { validate, fallback: validate(null), secret: true });
58
62
  }
59
63
 
60
64
  // Labels an earlier build assigned automatically. They read as a different
@@ -106,7 +110,8 @@ export function readProviderAccountPool(provider, dataDir = resolvePluginData())
106
110
 
107
111
  async function update(provider, mutate) {
108
112
  requireAccountProvider(provider);
109
- // Read failures must not be interpreted as an empty account roster.
113
+ // A roster that parses yet fails validation is recovered here first; the
114
+ // mutator's validate would otherwise refuse every write. I/O failures throw.
110
115
  read();
111
116
  const result = await updateJsonAtomic(
112
117
  file(),
@@ -117,7 +122,7 @@ async function update(provider, mutate) {
117
122
  state.providers[provider] = pool;
118
123
  return validate(state);
119
124
  },
120
- { mode: 0o600, secret: true, fsyncDir: true }
125
+ { mode: 0o600, secret: true, fsyncDir: true, backup: true }
121
126
  );
122
127
  return normalizePool(result.providers[provider]);
123
128
  }
@@ -0,0 +1,18 @@
1
+ import { pathToFileURL } from 'node:url';
2
+
3
+ /**
4
+ * `text` with every mention of the given directories taken out (and the
5
+ * separator after it), whether written as a native path, with forward slashes
6
+ * or as a file URL, so an error never discloses a private scratch location.
7
+ */
8
+ export function redactDirectories(text, directories) {
9
+ let out = String(text ?? '');
10
+ for (const directory of directories) {
11
+ if (!directory) continue;
12
+ const forms = new Set([directory, directory.replaceAll('\\', '/'), pathToFileURL(directory).href]);
13
+ for (const form of [...forms].sort((a, b) => b.length - a.length)) {
14
+ out = out.split(`${form}\\`).join('').split(`${form}/`).join('').split(form).join('');
15
+ }
16
+ }
17
+ return out;
18
+ }
@@ -0,0 +1,28 @@
1
+ import { rm } from 'node:fs/promises';
2
+
3
+ export const REMOVE_RETRY_MS = 100;
4
+ const REMOVE_ATTEMPTS = 6;
5
+ const RETRYABLE_REMOVE_CODES = new Set(['EBUSY', 'EPERM', 'ENOTEMPTY', 'EACCES']);
6
+
7
+ /**
8
+ * Removes a private scratch folder. A process that just exited can still hold
9
+ * its files for a moment on Windows, so a busy folder is retried briefly; a
10
+ * failure that persists is logged (never swallowed) and does not throw.
11
+ */
12
+ export async function removeDirectoryWithRetry(
13
+ dir,
14
+ { remove = rm, log = (line) => process.stderr.write(line), retryMs = REMOVE_RETRY_MS } = {}
15
+ ) {
16
+ for (let attempt = 1; ; attempt += 1) {
17
+ try {
18
+ await remove(dir, { recursive: true, force: true });
19
+ return;
20
+ } catch (error) {
21
+ if (attempt >= REMOVE_ATTEMPTS || !RETRYABLE_REMOVE_CODES.has(error?.code)) {
22
+ log(`scratch folder ${dir} could not be removed: ${error?.message || error}\n`);
23
+ return;
24
+ }
25
+ await new Promise((resolve) => setTimeout(resolve, retryMs * attempt));
26
+ }
27
+ }
28
+ }
@@ -4,7 +4,12 @@ export interface RoutineTrigger {
4
4
  event?: string;
5
5
  }
6
6
 
7
- export declare function routineTriggerHeader(input: { kind: string; name: string; project?: string }): string;
7
+ export declare function routineTriggerHeader(input: {
8
+ kind: string;
9
+ name: string;
10
+ project?: string;
11
+ now?: Date | number;
12
+ }): string;
8
13
  export declare function stripRoutineTriggerHeader(text: unknown): string;
9
14
  export declare function normalizeRoutineTrigger(value: unknown): RoutineTrigger | null;
10
15
  export declare function routineTriggerField(value: unknown): { routineTrigger?: RoutineTrigger };
@@ -4,18 +4,26 @@
4
4
  // carries (live, persisted in meta.transcript, restored) so a chat paints it
5
5
  // as a routine marker instead of a user bubble.
6
6
 
7
+ import { localTimestampParts } from './time-format.mjs';
8
+
7
9
  const HEADER_PREFIX = '[Routine trigger]';
8
10
  const KINDS = new Set(['schedule', 'webhook']);
9
11
 
10
- /** The first line of a routine turn's prompt: which routine fired and how to
11
- * treat the turn. Only the turn's content carries it, never the system
12
- * prompt, so the thread's prompt cache is untouched. */
13
- export function routineTriggerHeader({ kind, name, project = '' }) {
12
+ /** The first line of a routine turn's prompt: which routine fired, when (local
13
+ * time and zone: the turn gets no other date), and how to treat the turn.
14
+ * It is the routine's only conduct text. Only the turn's content carries it,
15
+ * never the system prompt, so the thread's prompt cache is untouched. */
16
+ export function routineTriggerHeader({ kind, name, project = '', now = new Date() }) {
14
17
  const what = kind === 'webhook' ? 'webhook' : 'schedule';
18
+ const at = localTimestampParts(now);
15
19
  return [
16
20
  `${HEADER_PREFIX} Your ${what} "${String(name)}" fired.`,
17
- 'Carry out its instructions below now, yourself or by delegating; nobody is watching this turn, so approvals wait for the user.',
18
- 'Reply in this chat with the result.',
21
+ ...(at ? [`Local time: ${at.date} ${at.time.slice(0, 5)} ${at.timeZone} (${at.offset}).`] : []),
22
+ 'Carry out its instructions below now, yourself or by delegating.',
23
+ 'Nobody is watching this turn and nothing asks for approval: act within your permissions.',
24
+ 'Payments and permanent deletes are refused here: report that and suggest doing it from the chat.',
25
+ 'If a sign-in, code or CAPTCHA blocks you, stop and say which site needs the user.',
26
+ 'Reply in this chat in plain words, without tool names; if you handed work off, say it started — its result arrives here later.',
19
27
  ...(project ? [`Project folder: ${project}`] : []),
20
28
  ].join(' ');
21
29
  }
@@ -0,0 +1,3 @@
1
+ import type { ChildProcess } from 'node:child_process';
2
+
3
+ export function killChildTree(child: ChildProcess): void;
@@ -8,6 +8,29 @@
8
8
  // maxBytes, or the spawn error itself.
9
9
  import { spawn } from 'node:child_process';
10
10
 
11
+ /** Kills `child` with everything it started. On Windows a plain kill ends only
12
+ * the child itself: `git` there is a launcher whose git.exe (and the alias,
13
+ * hook or filter it runs) would live on, still holding the output pipes. */
14
+ export function killChildTree(child) {
15
+ if (process.platform === 'win32' && child.pid && child.exitCode === null) {
16
+ const taskkill = `${process.env.SystemRoot || 'C:\\Windows'}\\System32\\taskkill.exe`;
17
+ const fallback = () => {
18
+ try {
19
+ child.kill();
20
+ } catch {}
21
+ };
22
+ spawn(taskkill, ['/PID', String(child.pid), '/T', '/F'], { stdio: 'ignore', windowsHide: true })
23
+ .once('error', fallback)
24
+ .once('exit', (code) => {
25
+ if (code !== 0) fallback();
26
+ });
27
+ return;
28
+ }
29
+ try {
30
+ child.kill();
31
+ } catch {}
32
+ }
33
+
11
34
  export function runChild({ command, args, cwd, input, env, timeoutMs, maxBytes, stdoutEncoding = 'utf8' }) {
12
35
  return new Promise((resolvePromise, rejectPromise) => {
13
36
  const hasInput = (typeof input === 'string' || ArrayBuffer.isView(input)) && input.length > 0;
@@ -35,9 +58,7 @@ export function runChild({ command, args, cwd, input, env, timeoutMs, maxBytes,
35
58
  ? setTimeout(() => {
36
59
  const error = new Error(`${command} ${args.join(' ')} timed out after ${timeoutMs}ms`);
37
60
  error.code = 'ETIMEDOUT';
38
- try {
39
- child.kill();
40
- } catch {}
61
+ killChildTree(child);
41
62
  finish(error);
42
63
  }, timeoutMs)
43
64
  : null;
@@ -809,8 +809,14 @@ export function _unsavedBytesForTest(sessionId) {
809
809
  async function readBlob(sessionId, entry) {
810
810
  if (!entry?.blob) return undefined;
811
811
  try {
812
- const bytes = await readFile(join(blobDir(sessionId), entry.blob));
813
- return digest(bytes) === entry.blob ? bytes : undefined;
812
+ const path = join(blobDir(sessionId), entry.blob);
813
+ const bytes = await readFile(path);
814
+ if (digest(bytes) === entry.blob) return bytes;
815
+ // A torn blob (unflushed write cut by a crash). Blobs are published whole
816
+ // and never replaced, so a mismatch is damage: drop it, or the create-only
817
+ // write of a later capture of the same content would skip over it.
818
+ await rm(path, { force: true });
819
+ return undefined;
814
820
  } catch {
815
821
  return undefined;
816
822
  }
@@ -1,4 +1,13 @@
1
- export type SessionSidebarChange = { favorite: boolean } | { archived: boolean };
1
+ export type SessionSidebarChange =
2
+ | { favorite: boolean }
3
+ | { archived: boolean }
4
+ | { archived: true; cascade: false }
5
+ | { forgotten: true };
2
6
  export type SessionSidebarHost = (sessionId: unknown, change: unknown) => unknown;
3
- export function registerSessionSidebarHost(host: SessionSidebarHost): () => void;
7
+ export type SessionSidebarState = { archived: Record<string, number> };
8
+ export function registerSessionSidebarHost(
9
+ host: SessionSidebarHost,
10
+ options?: { state?: (() => SessionSidebarState | Promise<SessionSidebarState>) | null }
11
+ ): () => void;
4
12
  export function applySessionSidebarChange(sessionId: string, change: SessionSidebarChange): Promise<void>;
13
+ export function readSessionSidebarState(): Promise<SessionSidebarState | null>;
@@ -15,11 +15,15 @@ function hosts() {
15
15
 
16
16
  /**
17
17
  * Register a desktop host. `host(sessionId, change)` applies one change:
18
- * `{ favorite: boolean }` or `{ archived: boolean }`.
18
+ * `{ favorite: boolean }`, `{ archived: boolean }` (an archive takes the
19
+ * session's sub-agent sessions with it, a restore brings back the ones it took),
20
+ * `{ archived: true, cascade: false }` (that session alone)
21
+ * or `{ forgotten: true }` (a session the store deleted: its records go).
22
+ * `state()` answers the archive flags: `{ archived }`, session id → stamp.
19
23
  * @returns {() => void} Unregister.
20
24
  */
21
- export function registerSessionSidebarHost(host) {
22
- const entry = { host };
25
+ export function registerSessionSidebarHost(host, { state = null } = {}) {
26
+ const entry = { host, state: typeof state === 'function' ? state : null };
23
27
  hosts().push(entry);
24
28
  return () => {
25
29
  const list = hosts();
@@ -36,3 +40,12 @@ export async function applySessionSidebarChange(sessionId, change) {
36
40
  if (!active) throw new Error('no desktop app is attached to this runtime');
37
41
  await active.host(sessionId, change);
38
42
  }
43
+
44
+ /** The active desktop's flags, `{ archived }` (session id → epoch-ms stamp);
45
+ * null when no desktop (or one without a state reader) is attached. */
46
+ export async function readSessionSidebarState() {
47
+ const active = hosts().at(-1);
48
+ if (!active?.state) return null;
49
+ const state = await active.state();
50
+ return { archived: { ...state?.archived } };
51
+ }
@@ -1,6 +1,6 @@
1
- import { mkdirSync, renameSync, rmdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
1
+ import { rmdirSync, rmSync, statSync } from 'node:fs';
2
2
  import { readFile, rename, rm, rmdir, stat, writeFile } from 'node:fs/promises';
3
- import { dirname } from 'node:path';
3
+ import { writeJsonAtomicSync } from './atomic-file.mjs';
4
4
  import { withFileLock, withFileLockSync } from './file-lock.mjs';
5
5
  import { readJsonSafe } from './json-file.mjs';
6
6
  import { isPidAlive } from './pid-liveness.mjs';
@@ -19,11 +19,12 @@ const CLAIM_LOCK_STALE_MS = 5000;
19
19
  const CLAIM_LOCK_OPTS = Object.freeze({ timeoutMs: CLAIM_LOCK_TIMEOUT_MS, staleMs: CLAIM_LOCK_STALE_MS });
20
20
  const BUSY = Symbol('claim lock busy');
21
21
 
22
+ // A claim or handoff is flushed before it is published. The heartbeat's async
23
+ // refresh below is not: it rewrites the record every few seconds, and a record
24
+ // a crash leaves NUL reads as "no owner" (readJsonSafe), so the next claim
25
+ // simply takes over.
22
26
  function writeJsonAtomic(path, value) {
23
- mkdirSync(dirname(path), { recursive: true });
24
- const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
25
- writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`);
26
- renameSync(tmp, path);
27
+ writeJsonAtomicSync(path, value);
27
28
  }
28
29
 
29
30
  // Held by another live process or this one's async holder ('busy'). Any other
@@ -1,8 +1,9 @@
1
1
  // Explicit skill dependencies. Imported manifests are read, never rewritten.
2
- import { createHash, randomUUID } from 'node:crypto';
3
- import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
2
+ import { createHash } from 'node:crypto';
3
+ import { existsSync, readFileSync, rmSync, statSync } from 'node:fs';
4
4
  import { dirname, join, resolve } from 'node:path';
5
5
  import { parseDocument } from 'yaml';
6
+ import { writeFileAtomicSync } from './atomic-file.mjs';
6
7
  import { resolvePluginData } from './plugin-paths.mjs';
7
8
 
8
9
  const MAX_METADATA_BYTES = 64 * 1024;
@@ -119,12 +120,5 @@ export function saveSkillToolDependencies(filePath, dependencies) {
119
120
  const tools = normalizeSkillToolDependencies(dependencies, { strict: true });
120
121
  const content = `${JSON.stringify({ dependencies: { tools } }, null, 2)}\n`;
121
122
  if (Buffer.byteLength(content) > MAX_METADATA_BYTES) throw new Error('Skill dependency metadata is too large.');
122
- mkdirSync(dirname(path), { recursive: true });
123
- const temporary = `${path}.${randomUUID()}.tmp`;
124
- try {
125
- writeFileSync(temporary, content, { encoding: 'utf8', flag: 'wx' });
126
- renameSync(temporary, path);
127
- } finally {
128
- rmSync(temporary, { force: true });
129
- }
123
+ writeFileAtomicSync(path, content);
130
124
  }
@@ -50,7 +50,7 @@ import { resolvePluginData } from './plugin-paths.mjs';
50
50
  import { isPidAlive as pidAlive } from './pid-liveness.mjs';
51
51
  import { sleepSync } from './sleep.mjs';
52
52
  import { detachedSpawnOpts, hiddenSpawnOpts } from './spawn-flags.mjs';
53
- import { renameWithRetrySync } from './atomic-file.mjs';
53
+ import { renameWithRetrySync, writeJsonAtomicSync } from './atomic-file.mjs';
54
54
  import {
55
55
  isDevInstall,
56
56
  localPackageVersion,
@@ -295,7 +295,9 @@ export async function runStagedInstall(version) {
295
295
 
296
296
  if (!existsSync(join(pkgDir, 'src', 'cli.mjs'))) return { ok: false, error: 'staged cli.mjs missing' };
297
297
  // Marker written LAST — its presence is the completion signal for the swap.
298
- writeFileSync(markerPath(verDir), JSON.stringify({ version: v, pkgDir, stagedAt: Date.now() }), 'utf8');
298
+ // Published whole and flushed: a NUL marker would read as "not staged" and
299
+ // force a full re-install.
300
+ writeJsonAtomicSync(markerPath(verDir), { version: v, pkgDir, stagedAt: Date.now() }, { compact: true });
299
301
  return { ok: true, version: v, dir: pkgDir };
300
302
  } catch (err) {
301
303
  return { ok: false, error: err?.message || String(err) };
@@ -197,11 +197,11 @@ async function refreshShellJobsStatus(ownerPid) {
197
197
  }
198
198
  // A killed command whose processes are not confirmed gone has already
199
199
  // reported its result, so its tool card is closed: it is listed until
200
- // its owner retires the record. Its root pid may be gone while a
201
- // descendant lives, so the owner's liveness decides, not the root's.
200
+ // its owner retires the record. An incomplete observation can leave no
201
+ // known survivors even after the owner session closes; only the owning
202
+ // runtime can confirm termination, so its liveness decides, not the root's.
202
203
  const unconfirmed = detail?.terminationUnconfirmed === true;
203
- if (unconfirmed ? !isHeldShellJobAlive(detail, ownerByJob.get(id)) : !(await isShellJobAlive(detail, p, dir, id)))
204
- continue;
204
+ if (unconfirmed ? !isPidAlive(ownerByJob.get(id) || 0) : !(await isShellJobAlive(detail, p, dir, id))) continue;
205
205
  // A foreground command is awaited in its own tool card; listing it here
206
206
  // made every short command flash in and out of the shell list. Only
207
207
  // backgrounded jobs (including promoted ones) are background work.
@@ -327,17 +327,6 @@ function summarizeShellJobs({ count, oldestMs, jobs, bySession }) {
327
327
  return { count, elapsedLabel, jobs, sessions };
328
328
  }
329
329
 
330
- // A held record lives while its owner process does. Once its owner session
331
- // has closed, it also needs a process left to hold: the root or a survivor
332
- // its termination watch last reported. Without one it is an orphan whose
333
- // processes are gone, and it resolves instead of staying unconfirmed.
334
- function isHeldShellJobAlive(detail, ownerPid) {
335
- if (!isPidAlive(ownerPid || 0)) return false;
336
- if (detail?.ownerSessionClosed !== true) return true;
337
- const survivors = Array.isArray(detail?.survivorPids) ? detail.survivorPids : [];
338
- return [detail?.pid, ...survivors].some((pid) => isPidAlive(positiveInt(pid)));
339
- }
340
-
341
330
  async function isShellJobAlive(detail, detailPath, dir, id) {
342
331
  const pid = positiveInt(detail?.pid);
343
332
  if (!pid) return false;
@@ -1,4 +1,5 @@
1
1
  import { clean } from './clean.mjs';
2
+ import { hopOf } from './bot-hops.mjs';
2
3
  import {
3
4
  parseTaskNotification,
4
5
  renderTaskCompletionEnvelope,
@@ -113,6 +114,8 @@ export function normalizeToolNotifyContext(context = {}) {
113
114
  callerSessionId: callerSessionId || null,
114
115
  routingSessionId: routingSessionId || null,
115
116
  clientHostPid,
117
+ // The bot-chain hop of the turn that launched the work (bot-hops.mjs).
118
+ ...(hopOf(context.hop) ? { hop: context.hop } : {}),
116
119
  };
117
120
  }
118
121
 
@@ -133,6 +136,7 @@ function toolCompletionMeta({ surface = 'tool', id, status, resultType, instruct
133
136
  ? { routing_session_id: ctx.routingSessionId }
134
137
  : {}),
135
138
  ...(ctx.clientHostPid ? { client_host_pid: String(ctx.clientHostPid) } : {}),
139
+ ...(ctx.hop ? { hop: ctx.hop } : {}),
136
140
  };
137
141
  }
138
142
 
@@ -236,6 +240,12 @@ export function isTranscriptHiddenToolItem(item) {
236
240
  // stopped, and the restored transcript must keep that row
237
241
  // (scripts/turn-checkpoint-crash-test.mjs).
238
242
  const INTERNAL_TRANSCRIPT_INTERRUPT_RE = /^\[request interrupted by user(?: for tool use)?\]$/i;
243
+ // Every runtime control row written as a user turn when a turn is cut short
244
+ // (including the bare "[Request interrupted]" a kept Stop prompt gets). One
245
+ // pattern for "not real conversation": realTurnCount and the preview/title
246
+ // noise filter both use it.
247
+ export const CONTROL_MARKER_RE =
248
+ /^\[(?:truncated|request interrupted(?: by user(?: for tool use)?| by process restart)?)\]$/i;
239
249
  const TRANSCRIPT_CANCELLED_STATUS_RE = /^\[request interrupted(?: by process restart)?\]$/i;
240
250
 
241
251
  // Crash/implicit interruption markers remain in model-visible history for
@@ -573,6 +573,15 @@ function gitStageUnit(a) {
573
573
  });
574
574
  }
575
575
 
576
+ function gitWorktreeUnit(a) {
577
+ return unitDescriptor('Git', {
578
+ active: 'Managing',
579
+ done: 'Managed',
580
+ noun: 'worktree',
581
+ effect: String(a.worktree_action || '').toLowerCase() !== 'list',
582
+ });
583
+ }
584
+
576
585
  function cwdUnit(a) {
577
586
  const action = String(a.action || a.type || '').toLowerCase();
578
587
  // A bare `path` selects that Project even when action is omitted.
@@ -604,14 +613,6 @@ function cwdUnit(a) {
604
613
  const [active, done] = words;
605
614
  return unitDescriptor('Setup', { active, done, noun: 'session', effect: true });
606
615
  }
607
- if (action.endsWith('-worktree') || action.endsWith('-worktrees')) {
608
- return unitDescriptor('Setup', {
609
- active: 'Managing',
610
- done: 'Managed',
611
- noun: 'worktree',
612
- effect: !action.startsWith('list'),
613
- });
614
- }
615
616
  if (action === 'status') {
616
617
  return unitDescriptor('Setup', { active: 'Checking', done: 'Checked', noun: 'session' });
617
618
  }
@@ -715,7 +716,9 @@ const TOOL_UNITS = new Map([
715
716
  (a) =>
716
717
  a.action === 'stage'
717
718
  ? gitStageUnit(a)
718
- : unitDescriptor('Git', { count: queryCount(a, 'command', 'commands') || 1, noun: 'Git command' }),
719
+ : a.action === 'worktree'
720
+ ? gitWorktreeUnit(a)
721
+ : unitDescriptor('Git', { count: queryCount(a, 'command', 'commands') || 1, noun: 'Git command' }),
719
722
  ],
720
723
  ['github', githubUnit],
721
724
  // Preserve the staging work unit when rendering historical transcripts.