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
@@ -11,6 +11,7 @@
11
11
  * moves a long block into the attachment store; it is read back from there.
12
12
  */
13
13
  import { isAttachmentReference, readAttachmentTextWithin } from '../../../../attachments/store.mjs';
14
+ import { isMediaReferencePart } from '../../../../attachments/attachment-reference.mjs';
14
15
  import { toolResultText } from './tool-result-text.mjs';
15
16
 
16
17
  const IMAGE_METADATA_LINE = /\[Image(?::| source:) [^\]\r\n]+\]\s*$/;
@@ -116,14 +117,85 @@ function referencedFeedbackText(part) {
116
117
  }
117
118
  }
118
119
 
120
+ const PASTED_LINES = /· (\d+) lines?$/;
121
+ const PASTED_TEXT_MAX_BYTES = 1024 * 1024;
122
+ /** Blob bytes one transcript load may read to count the lines of pastes
123
+ * whose stored name does not carry the count (pastes stored before it did). */
124
+ export const PASTED_LINE_READ_BUDGET_BYTES = 4 * 1024 * 1024;
125
+
126
+ /** A stored plain-text prompt part's text within the load's byte budget, else ''. */
127
+ function offloadedPromptText(part, budget) {
128
+ const allowance = Math.min(PASTED_TEXT_MAX_BYTES, budget.remaining);
129
+ if (allowance <= 0) return '';
130
+ try {
131
+ const text = readAttachmentTextWithin(part, allowance);
132
+ if (text == null) return '';
133
+ budget.remaining -= Buffer.byteLength(text, 'utf8');
134
+ return text;
135
+ } catch (error) {
136
+ if (error?.code !== 'ENOENT') budget.remaining -= allowance;
137
+ return '';
138
+ }
139
+ }
140
+
141
+ /** Visible lines of a stored paste (as `pastedTextLineCount` counts them), or
142
+ * null when the count would take a read past the load's budget. */
143
+ function storedPasteLines(part, budget) {
144
+ const named = PASTED_LINES.exec(String(part.filename || ''));
145
+ if (named) return Number(named[1]);
146
+ const allowance = Math.min(PASTED_TEXT_MAX_BYTES, budget.remaining);
147
+ if (allowance <= 0) return null;
148
+ try {
149
+ const read = readAttachmentTextWithin(part, allowance);
150
+ if (read == null) return null;
151
+ budget.remaining -= Buffer.byteLength(read, 'utf8');
152
+ const value = read.replace(/\r\n?/g, '\n').replace(/\n+$/, '');
153
+ return value ? value.split('\n').length : 0;
154
+ } catch (error) {
155
+ // A blob that failed its integrity check was read before it failed: the
156
+ // read counts against the cap (at most the allowance). A missing blob
157
+ // read nothing.
158
+ if (error?.code !== 'ENOENT') budget.remaining -= allowance;
159
+ return null;
160
+ }
161
+ }
162
+
163
+ /** The chip token a stored pasted / attached text stood for in the live
164
+ * bubble (intake moved its body into the store and dropped the token). A
165
+ * paste whose line count is unknown shows as the plain `[Pasted text #n]`. */
166
+ function storedTextToken(part, number, budget) {
167
+ if (part?.type !== 'text') return '';
168
+ if (part.textKind === 'paste') {
169
+ const lines = storedPasteLines(part, budget);
170
+ return lines == null ? `[Pasted text #${number}]` : `[Pasted text #${number} +${lines} lines]`;
171
+ }
172
+ if (part.textKind === 'file') return `[File #${number}: ${part.filename || 'attachment.txt'}]`;
173
+ return '';
174
+ }
175
+
119
176
  /** The display lines of a stored prompt's parts, and for each image part the
120
177
  * line that draws it: the composer's metadata line right before it, else a
121
- * bare line of its own (a bare line never stands for the next image). */
122
- function restoredUserParts(content) {
178
+ * bare line of its own (a bare line never stands for the next image). A
179
+ * stored pasted or attached text shows as its chip token. */
180
+ function restoredUserParts(content, budget) {
123
181
  const texts = [];
124
182
  const images = [];
125
183
  let pendingMetadata = '';
184
+ let storedTexts = 0;
185
+ // Plain text the intake moved into the store (a long prompt, a bot's message).
186
+ const offloaded = [];
187
+ let companionText = false;
126
188
  for (const part of content) {
189
+ // An attachment sent by reference is model-facing text; the bubble shows
190
+ // the attachment as the chip its message records.
191
+ if (isMediaReferencePart(part)) continue;
192
+ const token = isAttachmentReference(part) ? storedTextToken(part, storedTexts + 1, budget) : '';
193
+ if (token) {
194
+ storedTexts += 1;
195
+ texts.push(token);
196
+ pendingMetadata = '';
197
+ continue;
198
+ }
127
199
  if (part?.type === 'image' || part?.type === 'input_image') {
128
200
  let line = pendingMetadata;
129
201
  if (!line) {
@@ -135,27 +207,72 @@ function restoredUserParts(content) {
135
207
  continue;
136
208
  }
137
209
  const text = isAttachmentReference(part) ? referencedFeedbackText(part) : toolResultText([part]);
138
- if (text) texts.push(text);
210
+ if (text) {
211
+ texts.push(text);
212
+ if (!IMAGE_METADATA_LINE.test(text)) companionText = true;
213
+ } else if (isAttachmentReference(part) && part?.type === 'text' && !part.textKind) {
214
+ // Keeps its place among the parts until it is known whether to read it.
215
+ texts.push(part);
216
+ offloaded.push(part);
217
+ }
139
218
  pendingMetadata = IMAGE_METADATA_LINE.test(text) ? text.trimEnd().split(/\r?\n/).pop().trim() : '';
140
219
  }
141
- return { texts, images };
220
+ // A prompt whose only real text was moved out restores it in place (bounded,
221
+ // within the load's read budget); image labels and chips are not companion
222
+ // text. Beside real text such parts stay out, as before.
223
+ const resolved = [];
224
+ for (const entry of texts) {
225
+ if (typeof entry === 'string') resolved.push(entry);
226
+ else if (!companionText) {
227
+ const text = offloadedPromptText(entry, budget);
228
+ if (text) resolved.push(text);
229
+ }
230
+ }
231
+ return { texts: resolved, images };
232
+ }
233
+
234
+ /**
235
+ * The user-bubble projection of one transcript load: each stored prompt's
236
+ * parts are worked out once (its text and its image refs share them, and a
237
+ * load that widens its range sees the same messages again), and the blob
238
+ * reads that count paste lines share one byte budget.
239
+ */
240
+ export function createUserTextRestore({ readBudgetBytes = PASTED_LINE_READ_BUDGET_BYTES } = {}) {
241
+ const budget = { remaining: readBudgetBytes };
242
+ const parts = new WeakMap();
243
+ const partsOf = (content) => {
244
+ let found = parts.get(content);
245
+ if (!found) {
246
+ found = restoredUserParts(content, budget);
247
+ parts.set(content, found);
248
+ }
249
+ return found;
250
+ };
251
+ return {
252
+ text(content) {
253
+ if (!Array.isArray(content)) {
254
+ return withVisualizeFeedbackDisplay(typeof content === 'string' ? content : toolResultText(content));
255
+ }
256
+ return withVisualizeFeedbackDisplay(partsOf(content).texts.join('\n'));
257
+ },
258
+ /** The stored original of each image the restored bubble draws, by the
259
+ * `[Image: …]` line that draws it. Every image keeps its slot (one
260
+ * without a stored original has no `ref`) so later images do not shift
261
+ * onto it; empty when no image has a stored original. */
262
+ imageRefs(content) {
263
+ if (!Array.isArray(content)) return [];
264
+ const slots = partsOf(content).images.map(({ part, line }) =>
265
+ isAttachmentReference(part) ? { line, ref: part.attachmentRef } : { line }
266
+ );
267
+ return slots.some((slot) => slot.ref) ? slots : [];
268
+ },
269
+ };
142
270
  }
143
271
 
144
272
  export function restoredUserText(content) {
145
- if (!Array.isArray(content)) {
146
- return withVisualizeFeedbackDisplay(typeof content === 'string' ? content : toolResultText(content));
147
- }
148
- return withVisualizeFeedbackDisplay(restoredUserParts(content).texts.join('\n'));
273
+ return createUserTextRestore().text(content);
149
274
  }
150
275
 
151
- /** The stored original of each image the restored bubble draws, by the
152
- * `[Image: …]` line that draws it. Every image keeps its slot (one without a
153
- * stored original has no `ref`) so later images do not shift onto it; empty
154
- * when no image has a stored original. */
155
276
  export function restoredUserImageRefs(content) {
156
- if (!Array.isArray(content)) return [];
157
- const slots = restoredUserParts(content).images.map(({ part, line }) =>
158
- isAttachmentReference(part) ? { line, ref: part.attachmentRef } : { line }
159
- );
160
- return slots.some((slot) => slot.ref) ? slots : [];
277
+ return createUserTextRestore().imageRefs(content);
161
278
  }
@@ -1,16 +1,41 @@
1
- // Cross-process advisory lock for patch/write operations. Uses a sibling
2
- // lockfile written exclusively (`wx`) and a process.kill(pid, 0) liveness
3
- // check to clean up after crashed holders. Pair with the in-process
4
- // withPathLock for the same target: in-process serialises async callers
5
- // in this Node, advisory lock serialises across Node processes.
6
- import { openSync, closeSync, writeSync, readFileSync, unlinkSync, mkdirSync } from 'node:fs';
1
+ // Cross-process advisory lock for patch/write operations. Uses a lockfile
2
+ // written exclusively (`wx`) and a process.kill(pid, 0) liveness check to
3
+ // clean up after crashed holders. Pair with the in-process withPathLock for
4
+ // the same target: in-process serialises async callers in this Node, advisory
5
+ // lock serialises across Node processes.
6
+ import { openSync, closeSync, writeSync, readFileSync, unlinkSync, mkdirSync, realpathSync } from 'node:fs';
7
7
  import { compareCodePoints } from '../../../../shared/code-point-order.mjs';
8
+ import { resolvePluginData } from '../../../../shared/plugin-paths.mjs';
8
9
  import { sleep } from '../../../../shared/sleep.mjs';
9
- import { dirname, basename, join } from 'node:path';
10
- import { randomBytes } from 'node:crypto';
10
+ import { dirname, basename, join, resolve } from 'node:path';
11
+ import { createHash, randomBytes } from 'node:crypto';
11
12
 
13
+ // The deepest existing component (the target itself when present, so a leaf
14
+ // symlink shares its referent's lock like atomicWrite writes through it) is
15
+ // resolved; components not created yet are appended as given.
16
+ function canonicalLockTarget(targetPath) {
17
+ const resolved = resolve(targetPath);
18
+ const tail = [];
19
+ let dir = resolved;
20
+ for (;;) {
21
+ try {
22
+ return join(realpathSync.native(dir), ...tail.reverse());
23
+ } catch {
24
+ if (dirname(dir) === dir) return resolved;
25
+ tail.push(basename(dir));
26
+ dir = dirname(dir);
27
+ }
28
+ }
29
+ }
30
+
31
+ // Lockfiles live in the local data dir, keyed by the target's canonical path,
32
+ // never beside the target: sync-drive folders (e.g. Naver MYBOX) refuse
33
+ // dot-prefixed sibling files, and the pid liveness check only spans this host.
12
34
  function lockFileFor(targetPath) {
13
- return join(dirname(targetPath), `.${basename(targetPath)}.mixdog-lock`);
35
+ const canonical = canonicalLockTarget(targetPath);
36
+ const key = process.platform === 'win32' ? canonical.toLowerCase() : canonical;
37
+ const hash = createHash('sha256').update(key).digest('hex');
38
+ return join(resolvePluginData(), 'locks', `${hash}.mixdog-lock`);
14
39
  }
15
40
 
16
41
  function isPidAlive(pid) {
@@ -87,15 +112,12 @@ function tryCreateLock(lockFile, token) {
87
112
 
88
113
  async function acquireAdvisoryLock(targetPath, { timeoutMs = 5000, pollMs = 25 } = {}) {
89
114
  const lockFile = lockFileFor(targetPath);
90
- // Ensure parent directory exists before openSync('wx'); the lockfile lives
91
- // next to the target, so writes to new files in missing parent dirs would
92
- // otherwise fail with ENOENT here.
93
115
  mkdirSync(dirname(lockFile), { recursive: true });
94
116
  const token = `${process.pid}.${randomBytes(8).toString('hex')}`;
95
117
  const deadline = Date.now() + timeoutMs;
96
118
  let stalePostDeadlineRetryUsed = false;
97
119
  const timeoutError = (holderPid) => {
98
- const err = new Error(`advisory lock timeout: ${lockFile} held by pid ${holderPid}`);
120
+ const err = new Error(`advisory lock timeout: ${targetPath} (${lockFile}) held by pid ${holderPid}`);
99
121
  err.code = 'EAGAIN';
100
122
  return err;
101
123
  };
@@ -24,9 +24,24 @@ function expectedTargetSnapshotChanged(currentStat, expected) {
24
24
  return !sameFileVersion(currentStat, expected);
25
25
  }
26
26
 
27
+ // Files the agent writes into a user's project are durable by default: without
28
+ // the flush a crash can publish the renamed file with every byte NUL.
29
+ // MIXDOG_ATOMIC_FSYNC=0|false|no|off opts out.
27
30
  function atomicWriteShouldFsync(value) {
28
31
  if (value === true || value === false) return value;
29
- return /^(1|true|yes|on|sync)$/i.test(String(process.env.MIXDOG_ATOMIC_FSYNC || ''));
32
+ return !/^(0|false|no|off)$/i.test(String(process.env.MIXDOG_ATOMIC_FSYNC || ''));
33
+ }
34
+
35
+ // Filesystems that refuse fsync answer with one of these; durability is
36
+ // best-effort there and must not fail the write.
37
+ const FSYNC_TOLERATED = new Set(['EPERM', 'ENOTSUP', 'EINVAL']);
38
+
39
+ async function syncHandle(fh) {
40
+ try {
41
+ await fh.sync();
42
+ } catch (error) {
43
+ if (!FSYNC_TOLERATED.has(error?.code)) throw error;
44
+ }
30
45
  }
31
46
 
32
47
  // 'wx' preflight creates an empty placeholder at targetPath; if the write is
@@ -104,7 +119,10 @@ async function writeTempFile(tmp, content, { mode, fsync }) {
104
119
  if (payloadByteLength(content) > STREAMING_THRESHOLD_BYTES) {
105
120
  // Streaming path: avoid buffering the entire payload through
106
121
  // fh.writeFile (which copies into a single Buffer).
107
- const ws = createWriteStream(tmp, { flags: 'wx', mode });
122
+ // Owner-writable while we flush: the fsync reopens the file for writing,
123
+ // which a read-only mode would refuse; the final mode is applied after.
124
+ const streamMode = fsync ? mode | 0o200 : mode;
125
+ const ws = createWriteStream(tmp, { flags: 'wx', mode: streamMode });
108
126
  // Stream construction precedes open; only the open event proves ownership.
109
127
  ws.once('open', () => {
110
128
  ownsTemp = true;
@@ -115,15 +133,16 @@ async function writeTempFile(tmp, content, { mode, fsync }) {
115
133
  await pipeline(source, ws);
116
134
  if (fsync) {
117
135
  fh = await fsPromises.open(tmp, 'r+');
118
- await fh.sync();
136
+ await syncHandle(fh);
119
137
  await fh.close();
120
138
  fh = null;
139
+ if (streamMode !== mode) await fsPromises.chmod(tmp, mode);
121
140
  }
122
141
  } else {
123
142
  fh = await fsPromises.open(tmp, 'wx', mode);
124
143
  ownsTemp = true;
125
144
  await fh.writeFile(content);
126
- if (fsync) await fh.sync();
145
+ if (fsync) await syncHandle(fh);
127
146
  await fh.close();
128
147
  fh = null;
129
148
  }
@@ -260,7 +279,8 @@ export async function atomicWrite(
260
279
  // beside the real target, so the rename stays on one filesystem.
261
280
  const writeTarget = symlinkWriteTarget(targetPath) ?? targetPath;
262
281
  const dir = dirname(writeTarget);
263
- const tmp = join(dir, `.${basename(writeTarget)}.mixdog-tmp-${randomBytes(4).toString('hex')}`);
282
+ // No leading dot: sync-drive folders (e.g. Naver MYBOX) refuse dot-prefixed names.
283
+ const tmp = join(dir, `${basename(writeTarget)}.mixdog-tmp-${randomBytes(4).toString('hex')}`);
264
284
  const existingStat = statOrNull(writeTarget);
265
285
  let effectiveMode = mode;
266
286
  if (effectiveMode === undefined && existingStat) effectiveMode = existingStat.mode & 0o777;
@@ -3,7 +3,7 @@ import { killShellJob, watchBackgroundShellJob } from '../shell-jobs.mjs';
3
3
  import { renderBackgroundPartialOutput } from '../shell-output.mjs';
4
4
  import { readShellTaskOutput } from '../lib/shell-task-output.mjs';
5
5
  import { normalizeOutputPath } from '../path-utils.mjs';
6
- import { cleanupArtifactOnTaskSettled, consumeTeeArtifact } from './transport-artifacts.mjs';
6
+ import { cleanupArtifactAfter, cleanupArtifactOnTaskSettled, consumeTeeArtifact } from './transport-artifacts.mjs';
7
7
  import { _backgroundResultLines, _prependDestructiveWarning } from './result-format.mjs';
8
8
 
9
9
  const DEFAULT_BACKGROUND_MESSAGE =
@@ -68,8 +68,9 @@ export function renderBackgroundedResult({ result, command, cwd, options, starte
68
68
  }
69
69
  // The promoted producer is still writing into the tee file, so it cannot be
70
70
  // consumed here — but it must not survive the command either. Consume (and
71
- // delete) it when the task settles.
72
- if (teePlan) cleanupArtifactOnTaskSettled(teePlan.teePath, result.jobId, consumeTeeArtifact);
71
+ // delete) it when the task settles — for a queued command, its run.
72
+ if (teePlan && result.jobId) cleanupArtifactOnTaskSettled(teePlan.teePath, result.jobId, consumeTeeArtifact);
73
+ else if (teePlan) cleanupArtifactAfter(teePlan.teePath, result.settled, consumeTeeArtifact);
73
74
  let taskBlock = null;
74
75
  if (task) taskBlock = renderBackgroundTask(task, { ownerTool: true });
75
76
  else if (result.jobId) taskBlock = `[task_id: ${result.jobId}]`;
@@ -1,9 +1,9 @@
1
1
  import { renderBackgroundTask, startBackgroundTask } from '../../../../../shared/background-tasks.mjs';
2
2
  import { killShellDescendants, waitForShellDescendants } from '../../lib/shell-descendants.mjs';
3
3
  import { releaseShellJobWorkspace } from '../lib/shell-job-records.mjs';
4
- import { consumeFilterTeeCapture } from '../shell-analysis.mjs';
4
+ import { readFilterTeeCapture } from '../shell-analysis.mjs';
5
5
  import { compactShellOutputLosslessly, renderShellOutputBody } from '../shell-lossless-compact.mjs';
6
- import { removeTransportFileNow } from './transport-artifacts.mjs';
6
+ import { cleanupArtifactAfter, removeTransportFileNow } from './transport-artifacts.mjs';
7
7
  import { DELIVERY_FULL_CHARS, EMPTY_PIECE, fitPieces, shellTextPiece } from '../lib/shell-delivery.mjs';
8
8
  import { shellLogPiece } from '../lib/shell-task-output.mjs';
9
9
  import { shellTaskIdentity } from './backgrounded-result.mjs';
@@ -74,17 +74,16 @@ function survivingDescendantsWarning(result, taskContext) {
74
74
  return [heading, renderBackgroundTask(task, { ownerTool: true })].join('\n');
75
75
  }
76
76
 
77
- // Filter-swallow rescue: the tee file is ALWAYS consumed (deleted) here; its
78
- // tail is attached only when the run failed with an empty visible capture —
79
- // the exact `(no output)` shape that previously cost the model extra
80
- // diagnostic turns.
81
- function filterRescueNote(teePlan, { failed, stdout, stderr }) {
82
- const rescueTail = consumeFilterTeeCapture(teePlan.teePath);
83
- // consumeFilterTeeCapture unlinks best-effort and swallows the failure; on
84
- // Windows a process that survived an unconfirmed kill still holds the file.
85
- // Route it through the same retry + exit-hook path the settlement branch
86
- // uses instead of abandoning it.
87
- removeTransportFileNow(teePlan.teePath);
77
+ // Filter-swallow rescue: the tee file is ALWAYS consumed (deleted) — once the
78
+ // run's tree has settled when its result says it may still run; its tail is
79
+ // attached only when the run failed with an empty visible capture — the exact
80
+ // `(no output)` shape that previously cost the model extra diagnostic turns.
81
+ function filterRescueNote(teePlan, { failed, stdout, stderr }, settled) {
82
+ const rescueTail = readFilterTeeCapture(teePlan.teePath);
83
+ // Removal goes through the retry + exit-hook path: on Windows a process
84
+ // that survived an unconfirmed kill still holds the file.
85
+ if (settled) cleanupArtifactAfter(teePlan.teePath, settled);
86
+ else removeTransportFileNow(teePlan.teePath);
88
87
  if (!failed || !rescueTail || stdout.trim() || stderr.trim()) return '';
89
88
  return `\n\n[filter-swallowed output rescue] the command failed but its trailing filter(s) matched nothing, so the visible output was empty. Unfiltered pipeline output (tail):\n${rescueTail}`;
90
89
  }
@@ -148,7 +147,7 @@ export function renderCompletedResult({
148
147
  const { signal, exitCode, completedExit, benignExit, isReallyErrored } = outcome;
149
148
  const commandExitedNonzero = completedExit && exitCode !== 0 && !benignExit;
150
149
  const rescueNote = teePlan
151
- ? filterRescueNote(teePlan, { failed: isReallyErrored || commandExitedNonzero, stdout, stderr })
150
+ ? filterRescueNote(teePlan, { failed: isReallyErrored || commandExitedNonzero, stdout, stderr }, result.settled)
152
151
  : '';
153
152
  const descendantWarning = survivingDescendantsWarning(result, { command, cwd, options, startedAtMs });
154
153
  // rewriteNote states that the text which RAN is not the text the caller
@@ -79,6 +79,15 @@ export function removeTransportFileNow(file) {
79
79
  finishTransportArtifact(file, removeTransportFile);
80
80
  }
81
81
 
82
+ /** Remove `file` once a run's own lifetime promise settles: a queued command
83
+ * has no job to subscribe to until it runs. */
84
+ export function cleanupArtifactAfter(file, settled, remove = removeTransportFile) {
85
+ if (!file) return;
86
+ registerTransportArtifact(file);
87
+ const done = () => finishTransportArtifact(file, remove);
88
+ Promise.resolve(settled).then(done, done);
89
+ }
90
+
82
91
  export function cleanupArtifactOnTaskSettled(file, jobId, remove = removeTransportFile) {
83
92
  if (!file) return;
84
93
  registerTransportArtifact(file);
@@ -12,7 +12,11 @@ import { recordShellCaptureTelemetry } from './shell-output.mjs';
12
12
  import { normalizeErrorMessage } from './path-diagnostics.mjs';
13
13
  import { invalidateBuiltinResultCache } from './cache-layers.mjs';
14
14
  import { formatShellToolFailure } from './bash-tool/result-format.mjs';
15
- import { cleanupArtifactOnTaskSettled, removeTransportFileNow } from './bash-tool/transport-artifacts.mjs';
15
+ import {
16
+ cleanupArtifactAfter,
17
+ cleanupArtifactOnTaskSettled,
18
+ removeTransportFileNow,
19
+ } from './bash-tool/transport-artifacts.mjs';
16
20
  import { combineAbortSignals, sessionAbortSignal } from './bash-tool/abort-signals.mjs';
17
21
  import { beginInterruptibleTaskWait } from '../../session/task-wait-control.mjs';
18
22
  import { prepareShellCommand } from './bash-tool/command-preflight.mjs';
@@ -39,9 +43,10 @@ export { buildShellSpawnEnv } from './bash-tool/spawn-env.mjs';
39
43
  export { executeTaskTool } from './task-tool.mjs';
40
44
 
41
45
  // Reads cached after a mutating command must not serve pre-command state. A
42
- // promoted command has barely started when the tool returns and a queued one
43
- // has not started at all, so the invalidation repeats when it settles.
44
- function settleMutationCaches(shellEffects, backgroundJobId, queuedSettled = null) {
46
+ // promoted command has barely started when the tool returns, a queued one has
47
+ // not started at all and a killed tree may still run, so the invalidation
48
+ // repeats when it settles.
49
+ function settleMutationCaches(shellEffects, backgroundJobId, runSettled = null) {
45
50
  const invalidate = () => {
46
51
  if (shellEffects.mutationMode === 'paths') {
47
52
  invalidateBuiltinResultCache(shellEffects.paths);
@@ -55,7 +60,7 @@ function settleMutationCaches(shellEffects, backgroundJobId, queuedSettled = nul
55
60
  if (backgroundJobId && shellEffects.mutationMode !== 'none') {
56
61
  subscribeShellJobSettled(backgroundJobId, invalidate);
57
62
  }
58
- if (queuedSettled && shellEffects.mutationMode !== 'none') queuedSettled.then(invalidate).catch(() => {});
63
+ if (runSettled && shellEffects.mutationMode !== 'none') runSettled.then(invalidate).catch(() => {});
59
64
  }
60
65
 
61
66
  export async function executeBashTool(args, workDir, options = {}) {
@@ -89,7 +94,8 @@ export async function executeBashTool(args, workDir, options = {}) {
89
94
  const inputWake = backgroundOnTimeout ? beginInterruptibleTaskWait(options?.sessionId) : null;
90
95
  let backgrounded = false;
91
96
  let backgroundJobId = null;
92
- let queuedSettled = null;
97
+ // A queued run, or a foreground tree that may outlive its result, settles later.
98
+ let runSettled = null;
93
99
  try {
94
100
  const invocation = await planShellInvocation({ command, resolvedSpec, cwd: bashWorkDir });
95
101
  if (invocation.failure) return invocation.failure;
@@ -127,7 +133,7 @@ export async function executeBashTool(args, workDir, options = {}) {
127
133
  });
128
134
  backgrounded = result.backgrounded === true;
129
135
  backgroundJobId = backgrounded ? result.jobId || null : null;
130
- queuedSettled = backgrounded ? result.settled || null : null;
136
+ runSettled = result.settled || null;
131
137
  const stdout = stripAnsi(result.stdout || '');
132
138
  const stderr = stripAnsi(result.stderr || '');
133
139
  recordShellCaptureTelemetry(options?.resultTelemetry, result, stdout, stderr);
@@ -152,14 +158,13 @@ export async function executeBashTool(args, workDir, options = {}) {
152
158
  inputWake?.dispose();
153
159
  combinedAbort?.cleanup?.();
154
160
  if (hoisted.hoistPath) {
155
- // A promoted or queued command still runs from the hoisted file; a
156
- // foreground one is done with it now (an unconfirmed kill may keep it
157
- // locked — retried).
158
- if (queuedSettled) {
159
- queuedSettled.then(() => removeTransportFileNow(hoisted.hoistPath)).catch(() => {});
160
- } else if (backgrounded) cleanupArtifactOnTaskSettled(hoisted.hoistPath, backgroundJobId);
161
+ // A promoted or queued command still runs from the hoisted file, and so
162
+ // may a killed tree until it settles; a finished foreground one is done
163
+ // with it now (an unconfirmed kill may keep it locked — retried).
164
+ if (runSettled) cleanupArtifactAfter(hoisted.hoistPath, runSettled);
165
+ else if (backgrounded) cleanupArtifactOnTaskSettled(hoisted.hoistPath, backgroundJobId);
161
166
  else removeTransportFileNow(hoisted.hoistPath);
162
167
  }
163
- settleMutationCaches(shellEffects, backgroundJobId, queuedSettled);
168
+ settleMutationCaches(shellEffects, backgroundJobId, runSettled);
164
169
  }
165
170
  }
@@ -87,7 +87,13 @@ export const BUILTIN_TOOLS = [
87
87
  type: 'integer',
88
88
  minimum: 1,
89
89
  maximum: PUBLIC_READ_WINDOW_MAX,
90
- description: 'Maximum line count; default 500 per exact file, 25 per glob-expanded file; batch default.',
90
+ description:
91
+ 'Maximum line count; default 500 per exact file, 25 per glob-expanded file; batch default. Lines only: PDFs use pages.',
92
+ },
93
+ pages: {
94
+ type: 'string',
95
+ description:
96
+ 'PDF page range "N" or "N-M", 1-based, ≤20 pages per call. Text pages return text; scanned pages (no text layer) return page images, or OCR text on a text-only model. Without pages, a PDF over 10 pages returns its first 5 pages and page count.',
91
97
  },
92
98
  },
93
99
  required: ['file_path'],
@@ -24,7 +24,7 @@ import { access, mkdir, readFile, readdir, rm, stat, writeFile } from 'node:fs/p
24
24
  import { unlinkSync } from 'node:fs';
25
25
  import { homedir } from 'node:os';
26
26
  import { join } from 'node:path';
27
- import { writeJsonAtomicAsync } from '../../../../../shared/atomic-file.mjs';
27
+ import { holdsNoData, writeJsonAtomicAsync } from '../../../../../shared/atomic-file.mjs';
28
28
  import { isPidAlive } from '../../../../../shared/pid-liveness.mjs';
29
29
  import { displayShellCommand } from '../../../../../shared/shell-display.mjs';
30
30
  import { tryAdmitWorkspaceActivity } from '../../../../../shared/worktrees/activity-gate.mjs';
@@ -339,23 +339,11 @@ export function releaseShellJobWorkspace(jobId) {
339
339
  if (workspaceLeases.get(key) === leave) workspaceLeases.delete(key);
340
340
  }
341
341
 
342
- /** True once `sessionId` closed in this runtime. */
343
- export function isShellOwnerSessionClosed(sessionId) {
344
- return closedOwnerSessions.has(String(sessionId || '').trim());
345
- }
346
-
347
342
  /** Flag a still-open job record as "termination unconfirmed" for the readouts. */
348
343
  export function markShellJobTerminationUnconfirmed(jobId) {
349
344
  return patchRunningShellJobRecord(jobId, { terminationUnconfirmed: true });
350
345
  }
351
346
 
352
- /** Record the pids a held job's termination watch last found alive, so a
353
- * reader in another process can tell a live hold from a stale one. */
354
- export function noteShellJobSurvivors(jobId, pids) {
355
- const survivorPids = (pids || []).map(Number).filter((pid) => Number.isInteger(pid) && pid > 0);
356
- return patchRunningShellJobRecord(jobId, { survivorPids });
357
- }
358
-
359
347
  function patchRunningShellJobRecord(jobId, patch) {
360
348
  const key = validJobId(jobId);
361
349
  if (!key) return Promise.resolve(false);
@@ -431,7 +419,11 @@ export async function readShellJobRecord(jobId, { strict = false } = {}) {
431
419
  const dir = jobsDir();
432
420
  const jsonPath = join(dir, `${key}.json`);
433
421
  try {
434
- const record = JSON.parse(await readFile(jsonPath, 'utf8'));
422
+ const text = await readFile(jsonPath, 'utf8');
423
+ // A crash left no data (fsync is off for these records): nothing to inspect,
424
+ // the same as no record, even for a strict reader.
425
+ if (holdsNoData(text)) return null;
426
+ const record = JSON.parse(text);
435
427
  if (
436
428
  strict &&
437
429
  (!record ||
@@ -1,11 +1,14 @@
1
1
  // Deterministic PDF fixtures shared by the read and MCP media tests.
2
- export function makePdf(pageCount) {
2
+ // `pageText(index)` is the text of page index+1; null draws a filled box and
3
+ // no text, like a scanned page without a text layer.
4
+ export function makePdf(pageCount, { pageText = (i) => `Page ${i + 1}` } = {}) {
3
5
  const objs = ['<< /Type /Catalog /Pages 2 0 R >>'];
4
6
  const kids = Array.from({ length: pageCount }, (_, i) => `${4 + i * 2} 0 R`);
5
7
  objs.push(`<< /Type /Pages /Kids [${kids.join(' ')}] /Count ${pageCount} >>`);
6
8
  objs.push('<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>');
7
9
  for (let i = 0; i < pageCount; i += 1) {
8
- const stream = `BT /F1 12 Tf 10 10 Td (Page ${i + 1}) Tj ET`;
10
+ const text = pageText(i);
11
+ const stream = text === null ? '0.2 g 20 20 160 160 re f' : `BT /F1 12 Tf 10 10 Td (${text}) Tj ET`;
9
12
  objs.push(
10
13
  `<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] /Contents ${5 + i * 2} 0 R /Resources << /Font << /F1 3 0 R >> >> >>`
11
14
  );
@@ -44,4 +47,3 @@ export function makeEncryptedPdf() {
44
47
  out += `trailer\n<< /Size 5 /Root 1 0 R /Encrypt 4 0 R /ID [<${'cd'.repeat(16)}> <${'cd'.repeat(16)}>] >>\nstartxref\n${xref}\n%%EOF\n`;
45
48
  return Buffer.from(out, 'latin1');
46
49
  }
47
-
@@ -37,8 +37,10 @@ export function imageMimeForPath(p) {
37
37
  // behaviour (refuse over-cap originals with an isError text result, else emit
38
38
  // the raw base64 image block). Decoder rejection is an explicit tool error;
39
39
  // corrupt bytes must never enter model history.
40
- export async function readImageAsContent(fullPath, displayPath, preflightStat = null) {
41
- const mimeType = imageMimeForPath(fullPath);
40
+ // `knownMimeType` names the image type of a path whose extension does not
41
+ // (a stored attachment, recognized by its signature).
42
+ export async function readImageAsContent(fullPath, displayPath, preflightStat = null, knownMimeType = null) {
43
+ const mimeType = knownMimeType || imageMimeForPath(fullPath);
42
44
  if (!mimeType) return null;
43
45
  let st;
44
46
  try {