mixdog 1.3.1 → 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 (377) hide show
  1. package/README.md +8 -4
  2. package/package.json +2 -2
  3. package/scripts/native-release-status.mjs +238 -0
  4. package/src/defaults/shipped-history.json +5 -1
  5. package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
  6. package/src/defaults/skills/setup/SKILL.md +23 -1
  7. package/src/defaults/skills/setup/references/actions.md +50 -3
  8. package/src/defaults/skills/setup/references/surfaces.md +23 -3
  9. package/src/defaults/skills/setup-guide/SKILL.md +7 -3
  10. package/src/defaults/skills/visualize/SKILL.md +9 -6
  11. package/src/defaults/skills/visualize/references/craft.md +8 -1
  12. package/src/help.mjs +1 -1
  13. package/src/lib/keychain-cjs.cjs +4 -1
  14. package/src/lib/rules-builder.cjs +14 -7
  15. package/src/rules/agent-mode/AGENT_MODE.md +8 -5
  16. package/src/rules/agent-mode/MIXY.md +6 -8
  17. package/src/rules/lead/LEAD.md +2 -2
  18. package/src/rules/shared/10-tool-workflow.md +4 -0
  19. package/src/rules/shared/20-research.md +2 -0
  20. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
  21. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
  22. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
  23. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
  24. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
  25. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
  26. package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
  27. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
  28. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  29. package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
  30. package/src/runtime/agent/orchestrator/config.mjs +11 -0
  31. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +9 -2
  32. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -5
  33. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
  34. package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
  35. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +80 -31
  36. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
  37. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  38. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +67 -38
  39. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
  40. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
  41. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +27 -8
  42. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
  43. package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
  44. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
  45. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
  46. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +16 -0
  47. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
  48. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
  49. package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
  50. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
  51. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
  52. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
  53. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
  54. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +2 -1
  55. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
  56. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
  57. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
  58. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
  59. package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -0
  60. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
  61. package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
  62. package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
  63. package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
  64. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
  65. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +4 -6
  66. package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
  67. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
  68. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +12 -1
  69. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +6 -5
  70. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +7 -6
  71. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
  72. package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
  73. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +2 -0
  74. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
  75. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
  76. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
  77. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +17 -2
  78. package/src/runtime/agent/orchestrator/session/agent-room.mjs +133 -38
  79. package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
  80. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  81. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +14 -11
  82. package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
  83. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +68 -18
  84. package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
  85. package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
  86. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
  87. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +5 -1
  88. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +1 -1
  89. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
  90. package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
  91. package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +230 -0
  92. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +136 -32
  93. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
  94. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +21 -9
  95. package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
  96. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
  97. package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
  98. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
  99. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +189 -105
  100. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
  101. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +5 -5
  102. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +3 -0
  103. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
  104. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +38 -5
  105. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +21 -0
  106. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
  107. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
  108. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
  109. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
  110. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +4 -0
  111. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  112. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  113. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
  114. package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +99 -0
  115. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
  116. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
  117. package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
  118. package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
  119. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
  120. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
  121. package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
  122. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +55 -27
  123. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +3 -8
  124. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +12 -1
  125. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
  126. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
  127. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
  128. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +42 -10
  129. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
  130. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +18 -3
  131. package/src/runtime/agent/orchestrator/session/routine-result.mjs +120 -9
  132. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -1
  133. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
  134. package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
  135. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
  136. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
  137. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
  138. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +8 -3
  139. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +36 -1
  140. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -1
  141. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
  142. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
  143. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
  144. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
  145. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
  146. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +35 -5
  147. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +42 -2
  148. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +35 -13
  149. package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
  150. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +4 -3
  151. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
  152. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
  154. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
  155. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +6 -14
  156. package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
  157. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
  158. package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
  159. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
  160. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
  161. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +34 -21
  162. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
  163. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
  164. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
  165. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
  166. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
  167. package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
  168. package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
  169. package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
  170. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
  171. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
  172. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
  173. package/src/runtime/attachments/attachment-reference.mjs +155 -0
  174. package/src/runtime/attachments/limits.mjs +5 -1
  175. package/src/runtime/attachments/office-extract.mjs +3 -6
  176. package/src/runtime/attachments/pdf-extract.mjs +84 -10
  177. package/src/runtime/attachments/store.mjs +49 -30
  178. package/src/runtime/browser-bridge/client.mjs +73 -20
  179. package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
  180. package/src/runtime/channels/lib/scheduler.mjs +14 -7
  181. package/src/runtime/channels/lib/state-file.mjs +15 -15
  182. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
  183. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
  184. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
  185. package/src/runtime/channels/lib/worker-main.mjs +3 -2
  186. package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
  187. package/src/runtime/effort-judge/judge-client.mjs +5 -2
  188. package/src/runtime/effort-judge/model-install.mjs +16 -8
  189. package/src/runtime/local-provider/asset-installer.mjs +10 -4
  190. package/src/runtime/local-provider/catalog.mjs +5 -2
  191. package/src/runtime/local-provider/registered-models.mjs +64 -27
  192. package/src/runtime/local-provider/server.mjs +2 -3
  193. package/src/runtime/media/adapters/image-api.mjs +89 -0
  194. package/src/runtime/media/auth.mjs +64 -1
  195. package/src/runtime/media/catalog.mjs +66 -4
  196. package/src/runtime/media/jobs.mjs +4 -0
  197. package/src/runtime/media/lanes.mjs +46 -5
  198. package/src/runtime/media/media-usage.mjs +17 -0
  199. package/src/runtime/media/renditions.mjs +15 -15
  200. package/src/runtime/media/store.mjs +36 -35
  201. package/src/runtime/media/tool.mjs +8 -1
  202. package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
  203. package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
  204. package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
  205. package/src/runtime/memory/lib/pg/process.mjs +8 -4
  206. package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
  207. package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
  208. package/src/runtime/office/core/journal.mjs +7 -14
  209. package/src/runtime/office/design/library/design-library-core.mjs +4 -16
  210. package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
  211. package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
  212. package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
  213. package/src/runtime/office/pdf/pdf-render.mjs +30 -18
  214. package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
  215. package/src/runtime/office/portable/portable-soffice.mjs +4 -0
  216. package/src/runtime/shared/agent-kind-migration.mjs +200 -0
  217. package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
  218. package/src/runtime/shared/agent-message-envelope.mjs +44 -0
  219. package/src/runtime/shared/agent-reap-state.d.mts +4 -0
  220. package/src/runtime/shared/agent-store.mjs +24 -7
  221. package/src/runtime/shared/agent-task-starts.mjs +27 -0
  222. package/src/runtime/shared/atomic-file.d.mts +50 -0
  223. package/src/runtime/shared/atomic-file.mjs +394 -132
  224. package/src/runtime/shared/background-tasks.mjs +7 -4
  225. package/src/runtime/shared/bot-hops.mjs +30 -0
  226. package/src/runtime/shared/bot-split-migration.mjs +1 -1
  227. package/src/runtime/shared/bounded-download.mjs +4 -2
  228. package/src/runtime/shared/config.mjs +39 -9
  229. package/src/runtime/shared/defaults-separation.mjs +6 -2
  230. package/src/runtime/shared/delegated-approval-host.mjs +74 -0
  231. package/src/runtime/shared/err-text.mjs +4 -0
  232. package/src/runtime/shared/error-presentation.mjs +22 -4
  233. package/src/runtime/shared/file-has-data.mjs +28 -0
  234. package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
  235. package/src/runtime/shared/pending-agent-work.mjs +54 -0
  236. package/src/runtime/shared/process-lifecycle.mjs +8 -12
  237. package/src/runtime/shared/projects.mjs +50 -41
  238. package/src/runtime/shared/provider-accounts.mjs +17 -12
  239. package/src/runtime/shared/routine-trigger.d.mts +6 -1
  240. package/src/runtime/shared/routine-trigger.mjs +14 -6
  241. package/src/runtime/shared/run-child.d.mts +3 -0
  242. package/src/runtime/shared/run-child.mjs +24 -3
  243. package/src/runtime/shared/session-baseline-store.mjs +8 -2
  244. package/src/runtime/shared/singleton-owner.mjs +7 -6
  245. package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
  246. package/src/runtime/shared/staged-update.mjs +4 -2
  247. package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
  248. package/src/runtime/shared/tool-execution-contract.mjs +6 -0
  249. package/src/runtime/shared/transcript-metadata.mjs +28 -3
  250. package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
  251. package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
  252. package/src/runtime/shared/turn-snapshot.mjs +44 -2
  253. package/src/runtime/shared/user-cwd.mjs +9 -4
  254. package/src/runtime/shared/user-data-guard.mjs +41 -42
  255. package/src/runtime/shared/utf8-cut.mjs +10 -0
  256. package/src/runtime/shared/worktrees/activity-gate.mjs +269 -61
  257. package/src/runtime/shared/worktrees/registry.mjs +26 -26
  258. package/src/runtime/tidy/core-install.mjs +26 -4
  259. package/src/runtime/tidy/install.mjs +91 -23
  260. package/src/runtime/web-search/lib/config.mjs +4 -16
  261. package/src/session-runtime/agent-home-delivery.mjs +555 -199
  262. package/src/session-runtime/agent-room-delivery.mjs +143 -36
  263. package/src/session-runtime/agent-task-sessions.mjs +667 -165
  264. package/src/session-runtime/agent-tool-routing.mjs +1 -0
  265. package/src/session-runtime/boot/apis.mjs +45 -24
  266. package/src/session-runtime/boot/config.mjs +1 -0
  267. package/src/session-runtime/boot/facade.mjs +11 -1
  268. package/src/session-runtime/boot/infrastructure.mjs +14 -2
  269. package/src/session-runtime/boot/providers.mjs +2 -2
  270. package/src/session-runtime/boot/shared.mjs +12 -6
  271. package/src/session-runtime/boot/tools.mjs +2 -2
  272. package/src/session-runtime/bot-status.mjs +432 -0
  273. package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
  274. package/src/session-runtime/config-lifecycle.mjs +60 -55
  275. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
  276. package/src/session-runtime/held-requests.mjs +36 -33
  277. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
  278. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
  279. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +19 -4
  280. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
  281. package/src/session-runtime/lifecycle/teardown.mjs +11 -3
  282. package/src/session-runtime/mcp-glue/connect.mjs +100 -186
  283. package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
  284. package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
  285. package/src/session-runtime/media-api.mjs +5 -2
  286. package/src/session-runtime/model-route/route-tuning.mjs +4 -1
  287. package/src/session-runtime/model-route/set-route.mjs +61 -1
  288. package/src/session-runtime/owned-automation-run.mjs +8 -1
  289. package/src/session-runtime/room-member-gate.mjs +16 -3
  290. package/src/session-runtime/route-resolution.mjs +16 -8
  291. package/src/session-runtime/route-state.mjs +1 -59
  292. package/src/session-runtime/routine-home-turn.mjs +5 -3
  293. package/src/session-runtime/runtime-bootstrap.mjs +5 -3
  294. package/src/session-runtime/runtime-paths.mjs +11 -5
  295. package/src/session-runtime/runtime-tunables.mjs +6 -0
  296. package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
  297. package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
  298. package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
  299. package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
  300. package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
  301. package/src/session-runtime/services/agent-tool/send-flow.mjs +25 -21
  302. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -0
  303. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
  304. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +8 -5
  305. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
  306. package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
  307. package/src/session-runtime/services/agent-tool.mjs +6 -0
  308. package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
  309. package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
  310. package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
  311. package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
  312. package/src/session-runtime/services/plugin-admin.mjs +26 -26
  313. package/src/session-runtime/services/provider-admin.mjs +14 -4
  314. package/src/session-runtime/services/schedule-access.mjs +33 -4
  315. package/src/session-runtime/services/seeds.mjs +4 -3
  316. package/src/session-runtime/services/usage-dashboard.mjs +1 -1
  317. package/src/session-runtime/services/usage-stats-model.mjs +2 -1
  318. package/src/session-runtime/session-create/materialize.mjs +2 -3
  319. package/src/session-runtime/session-create/session-options.mjs +15 -2
  320. package/src/session-runtime/settings-builtin-tools-api.mjs +63 -1
  321. package/src/session-runtime/settings-compaction-api.mjs +86 -39
  322. package/src/session-runtime/setup-tool/executor.mjs +8 -2
  323. package/src/session-runtime/setup-tool/settings-contract.mjs +31 -1
  324. package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
  325. package/src/session-runtime/skills-api.mjs +12 -5
  326. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +265 -0
  327. package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
  328. package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
  329. package/src/session-runtime/tool-policy/policy-stage.mjs +165 -0
  330. package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
  331. package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
  332. package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
  333. package/src/session-runtime/tool-policy-refresh.mjs +47 -369
  334. package/src/session-runtime/tool-surface.mjs +5 -3
  335. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +95 -88
  336. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  337. package/src/session-runtime/turn/turn-run.mjs +2 -1
  338. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
  339. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +519 -333
  340. package/src/session-runtime/workflow-agents-api/agent-route.mjs +46 -46
  341. package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
  342. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
  343. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
  344. package/src/session-runtime/workspace/workspace-state.mjs +7 -6
  345. package/src/standalone/daemon-agent-control.mjs +4 -1
  346. package/src/standalone/daemon-session-control.mjs +51 -10
  347. package/src/standalone/daemon.mjs +28 -10
  348. package/src/standalone/local-session-runtime.mjs +22 -14
  349. package/src/standalone/session-client.mjs +7 -2
  350. package/src/standalone/session-protocol.mjs +7 -0
  351. package/src/standalone/session-service.mjs +11 -0
  352. package/src/standalone/session-state-projection.mjs +19 -9
  353. package/src/standalone/session-tool-output.mjs +35 -9
  354. package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
  355. package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
  356. package/src/tui/app/slash-commands.mjs +1 -1
  357. package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
  358. package/src/tui/dist/index.mjs +529 -333
  359. package/src/tui/prompt-history-store.mjs +16 -21
  360. package/src/tui/session/context-state.mjs +4 -0
  361. package/src/tui/session/session-api/intake/abort.mjs +19 -1
  362. package/src/tui/session/session-api/media.mjs +2 -1
  363. package/src/tui/session/session-api/routes-agent-definitions.mjs +75 -0
  364. package/src/tui/session/session-api/routes-write.mjs +203 -0
  365. package/src/tui/session/session-api/routes.mjs +23 -247
  366. package/src/tui/session/session-api/settings.mjs +14 -1
  367. package/src/tui/session/session-flow/drain.mjs +28 -3
  368. package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
  369. package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
  370. package/src/tui/session/session-flow/steering.mjs +8 -3
  371. package/src/tui/session/tool-approval.mjs +185 -20
  372. package/src/tui/session/transcript-intake.mjs +2 -0
  373. package/src/tui/session/turn.mjs +23 -14
  374. package/src/tui/transcript-tool-failures.mjs +8 -2
  375. package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
  376. package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
  377. package/src/rules/agent-mode/ROUTINE.md +0 -6
@@ -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 {
@@ -11,11 +11,40 @@
11
11
  * stringify it to "[object Object]"; scalar reads leave it unset and get the
12
12
  * rich block shapes.
13
13
  */
14
+ import { open } from 'node:fs/promises';
14
15
  import { extname } from 'node:path';
15
16
  import { extractOoxmlText } from '../../../../attachments/office-extract.mjs';
17
+ import { readImageAsContent } from './read-image.mjs';
16
18
 
17
19
  const OOXML_EXTENSIONS = new Set(['.docx', '.pptx', '.xlsx', '.xlsm']);
18
20
 
21
+ // File signatures of the media an extension-less path may hold: the stored
22
+ // attachments a reference points at keep no extension.
23
+ const SNIFFED_IMAGE_TYPES = [
24
+ { mimeType: 'image/png', matches: (head) => head.subarray(0, 8).equals(Buffer.from('89504e470d0a1a0a', 'hex')) },
25
+ { mimeType: 'image/jpeg', matches: (head) => head[0] === 0xff && head[1] === 0xd8 && head[2] === 0xff },
26
+ { mimeType: 'image/gif', matches: (head) => head.subarray(0, 4).toString('latin1') === 'GIF8' },
27
+ {
28
+ mimeType: 'image/webp',
29
+ matches: (head) =>
30
+ head.subarray(0, 4).toString('latin1') === 'RIFF' && head.subarray(8, 12).toString('latin1') === 'WEBP',
31
+ },
32
+ ];
33
+
34
+ async function sniffedMediaType(fullPath, st) {
35
+ if (!st?.isFile?.() || st.size < 5) return null;
36
+ const handle = await open(fullPath, 'r');
37
+ try {
38
+ const head = Buffer.alloc(12);
39
+ const { bytesRead } = await handle.read(head, 0, head.length, 0);
40
+ const bytes = head.subarray(0, bytesRead);
41
+ if (bytes.subarray(0, 5).toString('latin1') === '%PDF-') return 'application/pdf';
42
+ return SNIFFED_IMAGE_TYPES.find((type) => type.matches(bytes))?.mimeType || null;
43
+ } finally {
44
+ await handle.close();
45
+ }
46
+ }
47
+
19
48
  /**
20
49
  * @returns {Promise<{ result: unknown } | null>} null when the path is not a
21
50
  * media file.
@@ -24,8 +53,24 @@ export async function readMediaFile({ fullPath, st, args, readStateScope, option
24
53
  const { extractPdfText, extractIpynbText, READ_MAX_OUTPUT_BYTES, _recordReadSnapshot } = helpers;
25
54
  const textOnly = options?.mediaTextOnly === true;
26
55
  const ext = extname(fullPath).toLowerCase();
27
- if (ext === '.pdf') {
28
- return { result: await extractPdfText(fullPath, args.pages, { maxOutputBytes: READ_MAX_OUTPUT_BYTES, textOnly }) };
56
+ const sniffed = ext === '' ? await sniffedMediaType(fullPath, st) : null;
57
+ if (ext === '.pdf' || sniffed === 'application/pdf') {
58
+ return {
59
+ result: await extractPdfText(fullPath, args.pages, {
60
+ maxOutputBytes: READ_MAX_OUTPUT_BYTES,
61
+ textOnly,
62
+ // The caller's model decides how a scanned page arrives: as a page
63
+ // image for one that reads images, as OCR text for a text-only one;
64
+ // a whole document block only for one that reads PDFs natively.
65
+ pageMode: options?.pdfPageMode === 'ocr' ? 'ocr' : 'image',
66
+ nativeDocument: options?.pdfNativeDocument !== false,
67
+ signal: options?.signal || null,
68
+ }),
69
+ };
70
+ }
71
+ if (sniffed && !textOnly) {
72
+ const image = await readImageAsContent(fullPath, args.path || fullPath, st, sniffed);
73
+ if (image) return { result: image };
29
74
  }
30
75
  if (OOXML_EXTENSIONS.has(ext)) {
31
76
  // OOXML text extraction — always a flat string, so it is batch-safe