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,18 +1,25 @@
1
1
  import { readFile, stat } from 'node:fs/promises';
2
- import { MAX_PDF_BYTES, MAX_PDF_PAGES, isPdfBuffer } from '../../../../attachments/limits.mjs';
2
+ import { MAX_PDF_BYTES, isPdfBuffer } from '../../../../attachments/limits.mjs';
3
+ import { READ_PDF_MAX_PAGES_PER_CALL } from '../../../../attachments/attachment-reference.mjs';
3
4
  import { READ_MAX_SIZE_BYTES } from './read-constants.mjs';
4
5
  import { capMarker } from './tool-output-limit.mjs';
5
6
  import { imageBlocksFromBuffer } from './read-image-resize.mjs';
6
- import { inspectPdfBuffer } from '../../../../attachments/pdf-extract.mjs';
7
+ import { boundedUtf8, inspectPdfBuffer, pageHasTextLayer } from '../../../../attachments/pdf-extract.mjs';
7
8
 
8
9
  const DEFAULT_READ_MAX_OUTPUT_BYTES = 100 * 1024;
9
10
 
10
- // PDFs at or under this size are emitted as the runtime's canonical base64
11
- // document block (providers convert it to their native file shape): 20MB raw → ~27MB
12
- // base64, which stays under the 32MB request cap.
13
- // Larger PDFs fall back to bounded PDF.js text extraction.
14
- // (MAX_PDF_BYTES / MAX_PDF_PAGES come from the shared attachment limits.)
15
- const PDF_TEXT_FALLBACK_PAGES = 20;
11
+ // A whole-PDF read (no pages) returns a PDF of up to this many pages whole —
12
+ // as the canonical base64 document block for a model that reads PDFs natively
13
+ // (providers convert it to their native file shape; MAX_PDF_BYTES keeps it
14
+ // under the request cap). A longer PDF returns its outline and first pages:
15
+ // the model then reads the pages it needs.
16
+ const PDF_WHOLE_READ_MAX_PAGES = 10;
17
+ const PDF_PREVIEW_PAGES = 5;
18
+ // Scanned pages render at this width: past the point where text in a page
19
+ // image stays legible, below the vision ceiling that would downscale it again.
20
+ const PDF_PAGE_IMAGE_WIDTH = 1200;
21
+ // The renderer takes at most 12 explicit pages per call.
22
+ const PDF_RENDER_BATCH = 12;
16
23
 
17
24
  // Per-output text ceiling inside a notebook. A single cell output larger than
18
25
  // this is replaced with a jq hint (large-notebook guidance) so a
@@ -40,15 +47,16 @@ function parsePagesArg(pagesArg) {
40
47
  if (to < from) {
41
48
  return { error: `Error: pages "${trimmed}" inverted — end (${to}) precedes start (${from})` };
42
49
  }
43
- if (to - from + 1 > 20) {
44
- return { error: `Error: pages "${trimmed}" spans ${to - from + 1} pages; max 20 per request — narrow the range` };
50
+ if (to - from + 1 > READ_PDF_MAX_PAGES_PER_CALL) {
51
+ return {
52
+ error: `Error: pages "${trimmed}" spans ${to - from + 1} pages; max ${READ_PDF_MAX_PAGES_PER_CALL} per request — narrow the range`,
53
+ };
45
54
  }
46
55
  return { filter: { from, to } };
47
56
  }
48
57
 
49
- // PDF.js text extraction. Fallback path for PDFs over the document-block
50
- // size cap, and the always-path when a page range is requested (a base64
51
- // document block can't be page-filtered, so a narrowed read keeps using text).
58
+ // PDF.js text extraction for a file without the %PDF- signature: pdf.js
59
+ // surfaces the parse error as text instead of a document block.
52
60
  async function extractPdfTextBody(fullPath, pageFilter, maxOutputBytes) {
53
61
  const buf = await readFile(fullPath);
54
62
  const result = await inspectPdfBuffer(buf, {
@@ -60,79 +68,240 @@ async function extractPdfTextBody(fullPath, pageFilter, maxOutputBytes) {
60
68
  return result.text || '(no text content extracted from PDF)';
61
69
  }
62
70
 
71
+ // Bounded UTF-8 cut for the text a paged read returns.
72
+ function capPdfText(text, maxOutputBytes) {
73
+ const bounded = boundedUtf8(text, maxOutputBytes);
74
+ if (!bounded.truncated) return text;
75
+ return `${bounded.text}\n\n${capMarker({ limit: maxOutputBytes, shown: 'PDF text cut', narrow: 'read a narrower pages range' })}`;
76
+ }
77
+
78
+ async function renderPdfPageImages(fullPath, pages, signal) {
79
+ const { renderPdfPages } = await import('../../../../office/pdf/pdf-render.mjs');
80
+ const out = [];
81
+ for (let offset = 0; offset < pages.length; offset += PDF_RENDER_BATCH) {
82
+ const rendered = await renderPdfPages(fullPath, {
83
+ pages: pages.slice(offset, offset + PDF_RENDER_BATCH),
84
+ maxWidth: PDF_PAGE_IMAGE_WIDTH,
85
+ signal,
86
+ writeImages: false,
87
+ });
88
+ out.push(...rendered.images);
89
+ }
90
+ return out;
91
+ }
92
+
93
+ async function recognizePdfPages(fullPath, pages, signal) {
94
+ const [{ recognizePdfPageTexts }, { defaultOfficeDataDir }] = await Promise.all([
95
+ import('../../../../office/pdf/pdf-ocr.mjs'),
96
+ import('../../../../office/core/journal.mjs'),
97
+ ]);
98
+ return recognizePdfPageTexts(fullPath, pages, { dataDir: defaultOfficeDataDir(), signal });
99
+ }
100
+
101
+ function pageList(pages) {
102
+ return pages.length > 6 ? `${pages.slice(0, 6).join(', ')}, …` : pages.join(', ');
103
+ }
104
+
105
+ function errorText(error) {
106
+ return error instanceof Error ? error.message : String(error);
107
+ }
108
+
109
+ function pdfFileLabel(fullPath) {
110
+ return fullPath.split(/[\\/]/).pop();
111
+ }
112
+
113
+ // The scanned pages of one paged read, as page images or OCR text. A page the
114
+ // renderer or the OCR engine cannot produce says so instead of failing the
115
+ // whole read: the text pages are still returned.
116
+ async function scannedPageBodies(fullPath, scanned, { pageMode, signal, renderPages, recognizePages }) {
117
+ const bodies = new Map();
118
+ if (!scanned.length) return bodies;
119
+ if (pageMode === 'text') {
120
+ for (const page of scanned) {
121
+ bodies.set(page, {
122
+ text: `(no text layer: a scanned page. Read this PDF on its own with pages="${page}" to see it as an image or OCR text.)`,
123
+ });
124
+ }
125
+ return bodies;
126
+ }
127
+ if (pageMode === 'image') {
128
+ try {
129
+ for (const image of await renderPages(fullPath, scanned, signal)) {
130
+ bodies.set(image.page, { image });
131
+ }
132
+ } catch (error) {
133
+ for (const page of scanned) {
134
+ bodies.set(page, { text: `(no text layer, and the page image could not be rendered: ${errorText(error)})` });
135
+ }
136
+ }
137
+ return bodies;
138
+ }
139
+ try {
140
+ for (const { page, text } of await recognizePages(fullPath, scanned, signal)) {
141
+ bodies.set(page, { ocr: text || '(OCR found no text on this page)' });
142
+ }
143
+ } catch (error) {
144
+ const note =
145
+ `(no text layer, and OCR is unavailable here: ${errorText(error)}. OCR language data downloads on first ` +
146
+ 'use, so this needs a network connection once; after that it works offline. A model that accepts images ' +
147
+ 'can read these pages as page images instead.)';
148
+ for (const page of scanned) bodies.set(page, { text: note });
149
+ }
150
+ return bodies;
151
+ }
152
+
153
+ // One page range: text pages as their text, scanned pages (no text layer) as
154
+ // page images for a model that reads images, OCR text for a text-only model,
155
+ // or a note in a flat-text context. Order follows the pages.
156
+ async function readPdfPageRange(fullPath, buf, pageCount, range, options) {
157
+ const { maxOutputBytes, note = '' } = options;
158
+ const extracted = await inspectPdfBuffer(buf, {
159
+ extractText: true,
160
+ maxPages: Infinity,
161
+ maxOutputBytes,
162
+ pageRange: range,
163
+ });
164
+ const scanned = extracted.pages.filter((entry) => !pageHasTextLayer(entry.text)).map((entry) => entry.page);
165
+ const bodies = await scannedPageBodies(fullPath, scanned, options);
166
+ const label = pdfFileLabel(fullPath);
167
+ const textPages = extracted.pages.length - scanned.length;
168
+ const header =
169
+ `[PDF ${label}: pages ${range.from}-${range.to} of ${pageCount}; ` +
170
+ `${textPages === extracted.pages.length ? 'every page has a text layer' : `scanned (no text layer): ${pageList(scanned)}`}` +
171
+ `${note ? `. ${note}` : ''}]`;
172
+ const blocks = [];
173
+ let text = header;
174
+ let textBytes = 0;
175
+ const flush = () => {
176
+ if (!text) return;
177
+ blocks.push({ type: 'text', text: capPdfText(text, Math.max(1, maxOutputBytes - textBytes)) });
178
+ textBytes += Buffer.byteLength(text, 'utf8');
179
+ text = '';
180
+ };
181
+ for (const entry of extracted.pages) {
182
+ const body = bodies.get(entry.page);
183
+ if (body?.image) {
184
+ text += `${text ? '\n\n' : ''}--- Page ${entry.page} (scanned; page image follows) ---`;
185
+ flush();
186
+ const { data, mimeType, width, height } = body.image;
187
+ blocks.push({ type: 'image', data, mimeType, width, height });
188
+ continue;
189
+ }
190
+ let pageText = entry.text;
191
+ let title = `--- Page ${entry.page} ---`;
192
+ if (body?.ocr) {
193
+ title = `--- Page ${entry.page} (OCR of a scanned page) ---`;
194
+ pageText = body.ocr;
195
+ } else if (body?.text) {
196
+ pageText = body.text;
197
+ }
198
+ text += `${text ? '\n\n' : ''}${title}\n${pageText}`;
199
+ }
200
+ flush();
201
+ if (blocks.every((block) => block.type === 'text')) return blocks.map((block) => block.text).join('\n\n');
202
+ return { content: blocks };
203
+ }
204
+
205
+ function pdfInspectError(fullPath, error) {
206
+ const label = pdfFileLabel(fullPath);
207
+ const protectedPdf = error?.name === 'PasswordException' || /password/i.test(String(error?.message || ''));
208
+ return protectedPdf
209
+ ? `Error: this PDF is password-protected and cannot be read (${label}).`
210
+ : `Error: ${label} is not a valid PDF (${errorText(error)}).`;
211
+ }
212
+
213
+ /**
214
+ * Reads a PDF: whole for a short one, by page range otherwise.
215
+ *
216
+ * pages "N" / "N-M" (≤20) reads that range: text first; pages without a text
217
+ * layer come back as page images (`pageMode: 'image'`, a model that sees
218
+ * images), OCR text (`pageMode: 'ocr'`, a text-only model) or a note
219
+ * (`textOnly`, a batch that must stay flat text). Without pages a PDF of up to
220
+ * 10 pages is returned whole (a document block when `nativeDocument`, the
221
+ * model reading PDFs natively, else every page through the range path), a
222
+ * longer one as the outline and its first 5 pages. offset/limit do not apply.
223
+ */
63
224
  export async function extractPdfText(
64
225
  fullPath,
65
226
  pagesArg,
66
- { maxOutputBytes = DEFAULT_READ_MAX_OUTPUT_BYTES, textOnly = false } = {}
227
+ {
228
+ maxOutputBytes = DEFAULT_READ_MAX_OUTPUT_BYTES,
229
+ textOnly = false,
230
+ pageMode = 'image',
231
+ nativeDocument = true,
232
+ signal = null,
233
+ renderPages = renderPdfPageImages,
234
+ recognizePages = recognizePdfPages,
235
+ } = {}
67
236
  ) {
68
237
  try {
69
238
  let pdfStat;
70
239
  try {
71
240
  pdfStat = await stat(fullPath);
72
241
  } catch (e) {
73
- return `Error: pdf stat failed — ${e instanceof Error ? e.message : String(e)}`;
242
+ return `Error: pdf stat failed — ${errorText(e)}`;
74
243
  }
75
244
 
76
245
  const pages = parsePagesArg(pagesArg);
77
246
  if (pages.error) return pages.error;
78
247
 
79
- // Document-block path: whole-PDF read, no page range, within the size
80
- // cap, and confirmed %PDF- magic. Emits the canonical document block
81
- // that provider adapters convert to native media (figures, layout,
82
- // tables) instead of lossy pdf-parse text. The magic-byte guard prevents
83
- // a non-PDF (mislabelled extension) from poisoning history.
84
- // textOnly (batch context) skips the block and always emits text.
85
- if (!textOnly && !pages.filter && pdfStat.size <= MAX_PDF_BYTES) {
86
- const buf = await readFile(fullPath);
87
- if (isPdfBuffer(buf)) {
88
- // A password-protected or invalid PDF is rejected by every provider
89
- // and would wedge the session if it entered history as a block.
90
- let pageCount;
91
- try {
92
- ({ pageCount } = await inspectPdfBuffer(buf, { maxPages: Infinity }));
93
- } catch (inspectErr) {
94
- const label = fullPath.split(/[\\/]/).pop();
95
- const protectedPdf =
96
- inspectErr?.name === 'PasswordException' || /password/i.test(String(inspectErr?.message || ''));
97
- return protectedPdf
98
- ? `Error: this PDF is password-protected and cannot be read (${label}).`
99
- : `Error: ${label} is not a valid PDF (${inspectErr instanceof Error ? inspectErr.message : String(inspectErr)}).`;
100
- }
101
- if (pageCount > MAX_PDF_PAGES) {
102
- const head = await inspectPdfBuffer(buf, {
103
- extractText: true,
104
- maxPages: Infinity,
105
- maxOutputBytes,
106
- pageRange: { from: 1, to: PDF_TEXT_FALLBACK_PAGES },
107
- });
108
- return (
109
- `[PDF has ${pageCount} pages, too many to attach natively (max ${MAX_PDF_PAGES});` +
110
- `showing extracted text of pages 1-${PDF_TEXT_FALLBACK_PAGES}. Pass \`pages\` (e.g. "21-40", max 20 per request) to read others.]\n` +
111
- (head.text || '(no text content extracted from PDF)')
112
- );
248
+ const buf = await readFile(fullPath);
249
+ // Not a real PDF (mislabelled extension) — pdf.js surfaces a clean parse
250
+ // error rather than a bad document block entering history.
251
+ if (!isPdfBuffer(buf)) return await extractPdfTextBody(fullPath, pages.filter, maxOutputBytes);
252
+ // A password-protected or invalid PDF is rejected by every provider and
253
+ // would wedge the session if it entered history as a block.
254
+ let pageCount;
255
+ try {
256
+ ({ pageCount } = await inspectPdfBuffer(buf, { maxPages: Infinity }));
257
+ } catch (inspectErr) {
258
+ return pdfInspectError(fullPath, inspectErr);
259
+ }
260
+ const mode = textOnly ? 'text' : pageMode;
261
+ const rangeOptions = { maxOutputBytes, pageMode: mode, signal, renderPages, recognizePages };
262
+ if (pages.filter) {
263
+ if (pages.filter.from > pageCount) {
264
+ return `Error: pages "${pagesArg}" out of range — this PDF has ${pageCount} page${pageCount === 1 ? '' : 's'}`;
265
+ }
266
+ const range = { from: pages.filter.from, to: Math.min(pages.filter.to, pageCount) };
267
+ return await readPdfPageRange(fullPath, buf, pageCount, range, rangeOptions);
268
+ }
269
+ if (pageCount > PDF_WHOLE_READ_MAX_PAGES) {
270
+ return await readPdfPageRange(
271
+ fullPath,
272
+ buf,
273
+ pageCount,
274
+ { from: 1, to: PDF_PREVIEW_PAGES },
275
+ {
276
+ ...rangeOptions,
277
+ note: `Showing the first ${PDF_PREVIEW_PAGES} pages; pass pages (e.g. "${PDF_PREVIEW_PAGES + 1}-${Math.min(pageCount, PDF_PREVIEW_PAGES + 10)}", up to ${READ_PDF_MAX_PAGES_PER_CALL} per call) to read the pages the request needs`,
113
278
  }
114
- return {
115
- content: [
116
- {
117
- type: 'document',
118
- source: {
119
- type: 'base64',
120
- media_type: 'application/pdf',
121
- data: buf.toString('base64'),
122
- },
123
- pageCount,
279
+ );
280
+ }
281
+ // A short PDF whole: a model that reads PDFs natively gets the canonical
282
+ // document block (figures, layout, tables) with its page and text-layer
283
+ // counts for the estimate; others get every page through the range path.
284
+ if (mode === 'image' && nativeDocument && pdfStat.size <= MAX_PDF_BYTES) {
285
+ const { textPages, textChars } = await inspectPdfBuffer(buf, { maxPages: Infinity, measureText: true });
286
+ return {
287
+ content: [
288
+ {
289
+ type: 'document',
290
+ source: {
291
+ type: 'base64',
292
+ media_type: 'application/pdf',
293
+ data: buf.toString('base64'),
124
294
  },
125
- ],
126
- };
127
- }
128
- // Not a real PDF — fall through to pdf-parse, which surfaces a
129
- // clean parse error rather than emitting a bad document block.
295
+ pageCount,
296
+ textPages,
297
+ textChars,
298
+ },
299
+ ],
300
+ };
130
301
  }
131
-
132
- // TEXT fallback: >20MB PDFs, page-filtered reads, or non-magic files.
133
- return await extractPdfTextBody(fullPath, pages.filter, maxOutputBytes);
302
+ return await readPdfPageRange(fullPath, buf, pageCount, { from: 1, to: Math.max(1, pageCount) }, rangeOptions);
134
303
  } catch (err) {
135
- return `Error: PDF extraction failed — ${err instanceof Error ? err.message : String(err)}`;
304
+ return `Error: PDF extraction failed — ${errorText(err)}`;
136
305
  }
137
306
  }
138
307
 
@@ -197,6 +366,30 @@ function capNotebookText(blocks, maxOutputBytes) {
197
366
  return capped;
198
367
  }
199
368
 
369
+ // The image blocks (each after its resize note, when it has one) for one
370
+ // cell's image outputs. An image the resize helper cannot take (sharp
371
+ // unavailable) stays the text placeholder already in the cell's text.
372
+ async function notebookImageBlocks(pendingImages, fullPath, cellIndex) {
373
+ const blocks = [];
374
+ for (const img of pendingImages) {
375
+ let buf;
376
+ try {
377
+ buf = Buffer.from(img.b64, 'base64');
378
+ } catch {
379
+ buf = null;
380
+ }
381
+ if (!buf || buf.length === 0) continue;
382
+ const built = await imageBlocksFromBuffer(buf, img.mimeType, {
383
+ sourcePath: `${fullPath} [cell ${cellIndex}]`,
384
+ });
385
+ if (built) {
386
+ if (built.textBlock) blocks.push(built.textBlock);
387
+ blocks.push(built.imageBlock);
388
+ }
389
+ }
390
+ return blocks;
391
+ }
392
+
200
393
  export async function extractIpynbText(
201
394
  fullPath,
202
395
  { maxOutputBytes = DEFAULT_READ_MAX_OUTPUT_BYTES, hasRangeArgs = false, textOnly = false } = {}
@@ -214,7 +407,7 @@ export async function extractIpynbText(
214
407
  try {
215
408
  nbStat = await stat(fullPath);
216
409
  } catch (e) {
217
- return `Error: ipynb stat failed — ${e instanceof Error ? e.message : String(e)}`;
410
+ return `Error: ipynb stat failed — ${errorText(e)}`;
218
411
  }
219
412
  if (nbStat.size > READ_MAX_SIZE_BYTES) {
220
413
  return `Error: notebook too large (size: ${nbStat.size}B, cap: ${READ_MAX_SIZE_BYTES}B) — use a cell range to narrow`;
@@ -253,23 +446,7 @@ export async function extractIpynbText(
253
446
  // placeholder in `block` is the only representation.
254
447
  if (!textOnly && pendingImages.length > 0) {
255
448
  flushText();
256
- for (const img of pendingImages) {
257
- let buf;
258
- try {
259
- buf = Buffer.from(img.b64, 'base64');
260
- } catch {
261
- buf = null;
262
- }
263
- if (!buf || buf.length === 0) continue;
264
- const built = await imageBlocksFromBuffer(buf, img.mimeType, {
265
- sourcePath: `${fullPath} [cell ${cellIndex}]`,
266
- });
267
- if (built) {
268
- if (built.textBlock) blocks.push(built.textBlock);
269
- blocks.push(built.imageBlock);
270
- }
271
- // built === null → sharp unavailable; placeholder text already emitted.
272
- }
449
+ blocks.push(...(await notebookImageBlocks(pendingImages, fullPath, cellIndex)));
273
450
  }
274
451
  }
275
452
  }
@@ -293,6 +470,6 @@ export async function extractIpynbText(
293
470
  }
294
471
  return { content: blocks };
295
472
  } catch (err) {
296
- return `Error: ipynb parse failed — ${err instanceof Error ? err.message : String(err)}`;
473
+ return `Error: ipynb parse failed — ${errorText(err)}`;
297
474
  }
298
475
  }
@@ -1255,9 +1255,9 @@ export function buildPowerShellFilterTeePlan(command) {
1255
1255
  }
1256
1256
 
1257
1257
  // Read the tail of a tee spill file (BOM-sniffed: Windows PowerShell 5.1
1258
- // writes UTF-16LE, pwsh 7 writes UTF-8) and ALWAYS delete the file. Returns
1259
- // null when the file is missing/empty/unreadable.
1260
- export function consumeFilterTeeCapture(teePath, { maxBytes = 16384 } = {}) {
1258
+ // writes UTF-16LE, pwsh 7 writes UTF-8). Returns null when the file is
1259
+ // missing/empty/unreadable.
1260
+ export function readFilterTeeCapture(teePath, { maxBytes = 16384 } = {}) {
1261
1261
  let out = null;
1262
1262
  try {
1263
1263
  const st = statSync(teePath);
@@ -1281,10 +1281,16 @@ export function consumeFilterTeeCapture(teePath, { maxBytes = 16384 } = {}) {
1281
1281
  } catch {
1282
1282
  out = null;
1283
1283
  }
1284
+ return out?.trim() ? out : null;
1285
+ }
1286
+
1287
+ // The tail of a tee spill file, which is then ALWAYS deleted.
1288
+ export function consumeFilterTeeCapture(teePath, options) {
1289
+ const out = readFilterTeeCapture(teePath, options);
1284
1290
  try {
1285
1291
  unlinkSync(teePath);
1286
1292
  } catch {
1287
1293
  /* best-effort cleanup */
1288
1294
  }
1289
- return out?.trim() ? out : null;
1295
+ return out;
1290
1296
  }
@@ -28,10 +28,8 @@ import {
28
28
  adoptShellWorkspaceAdmission,
29
29
  clearTerminationHold,
30
30
  completeShellJobRecord,
31
- isShellOwnerSessionClosed,
32
31
  listShellJobRecords,
33
32
  markShellJobTerminationUnconfirmed,
34
- noteShellJobSurvivors,
35
33
  publishShellJobRecord,
36
34
  registerTerminationHold,
37
35
  retireShellJobRecord,
@@ -196,8 +194,6 @@ function trackShellJobRecord(task, options) {
196
194
  sweep: killed,
197
195
  onTerminated: finish,
198
196
  releaseRequested: () => takeReleaseRequest(jobId),
199
- ownerClosed: () => isShellOwnerSessionClosed(options?.ownerSessionId),
200
- onSurvivors: (pids) => void noteShellJobSurvivors(jobId, pids),
201
197
  onUnconfirmed: () => {
202
198
  void markShellJobTerminationUnconfirmed(jobId);
203
199
  registerTerminationHold(jobId, {
@@ -343,41 +339,58 @@ export function peekShellJob(jobId) {
343
339
  return attachJobInsights(normalizeShellJobDetail(getNativeTask(jobId)));
344
340
  }
345
341
 
346
- /** True while the job's process is still alive in this daemon. Owners of
342
+ /** Run `onTreeSettled` once a job whose native task is terminal is over as a
343
+ * whole: at once for an untracked job, else once every process of its tree is
344
+ * confirmed gone (or the user released it) — a root's exit is not the end of
345
+ * what it started. */
346
+ function afterTreeSettled(jobId, onTreeSettled) {
347
+ const entry = backgroundTrackers.get(jobId);
348
+ if (entry) void entry.terminated.then(onTreeSettled);
349
+ else onTreeSettled();
350
+ }
351
+
352
+ /** True while a process of the job is still alive in this daemon. Owners of
347
353
  * in-use artifacts consult it before a fallback cleanup fires. */
348
354
  export function isShellJobRunning(jobId) {
349
- return getNativeTask(String(jobId || ''))?.status === 'running';
355
+ const key = String(jobId || '');
356
+ return getNativeTask(key)?.status === 'running' || backgroundTrackers.has(key);
350
357
  }
351
358
 
352
- /** Run `onSettled` once the job leaves 'running' (immediately when it already
353
- * has). Owners of per-command artifacts (hoisted script files, tee captures,
354
- * cache invalidation) use this instead of a fixed timer, which either fired
355
- * under a still-running command or never at all. Returns an unsubscribe, or
356
- * null when the job is unknown here — the caller then keeps its own fallback. */
359
+ /** Run `onSettled` once the job leaves 'running' and its tree has settled
360
+ * (immediately when both already have). Owners of per-command artifacts
361
+ * (hoisted script files, tee captures, cache invalidation) use this instead of
362
+ * a fixed timer, which either fired under a still-running command or never at
363
+ * all. Returns an unsubscribe, or null when the job is unknown here — the
364
+ * caller then keeps its own fallback. */
357
365
  export function subscribeShellJobSettled(jobId, onSettled) {
358
366
  const key = String(jobId || '').trim();
359
367
  if (!key || typeof onSettled !== 'function') return null;
360
368
  const current = getNativeTask(key);
361
369
  if (!current) return null;
362
- if (current.status !== 'running') {
363
- queueMicrotask(() => {
370
+ let done = false;
371
+ const deliver = (task) =>
372
+ afterTreeSettled(key, () => {
373
+ if (done) return;
374
+ done = true;
364
375
  try {
365
- onSettled(current);
376
+ onSettled(task);
366
377
  } catch {}
367
378
  });
368
- return () => {};
379
+ if (current.status !== 'running') {
380
+ queueMicrotask(() => deliver(current));
381
+ return () => {
382
+ done = true;
383
+ };
369
384
  }
370
- let done = false;
385
+ let seen = false;
371
386
  let unsubscribe = () => {};
372
387
  const fire = (task) => {
373
- if (done || !task || task.status === 'running') return;
374
- done = true;
388
+ if (seen || done || !task || task.status === 'running') return;
389
+ seen = true;
375
390
  try {
376
391
  unsubscribe();
377
392
  } catch {}
378
- try {
379
- onSettled(task);
380
- } catch {}
393
+ deliver(task);
381
394
  };
382
395
  unsubscribe = subscribeNativeTask(key, fire);
383
396
  return () => {
@@ -38,12 +38,16 @@ export const CODE_GRAPH_DISK_MEMORY_MAX_BYTES = Math.max(
38
38
  // Younger .tmp files may belong to an in-flight persist still holding the sibling .lock;
39
39
  // DEFAULT_LOCK_TIMEOUT_MS is 8s — 120s is a safe margin for large graph JSON writes.
40
40
  export const ORPHAN_TMP_MIN_AGE_MS = 120_000;
41
- export const RE_CACHE_TMP = /^\.[0-9a-f]{16}\.json\.[0-9a-f]{24}\.tmp$/i;
42
- export const RE_MANIFEST_TMP = /^\.manifest\.json\.[0-9a-f]{24}\.tmp$/i;
41
+ // Atomic writer temp name: `<basename>.mixdog-tmp-<16hex>`; the dot-prefixed
42
+ // `.<basename>.<24hex>.tmp` is what older builds left behind.
43
+ export const RE_CACHE_TMP =
44
+ /^(?:\.[0-9a-f]{16}\.json\.[0-9a-f]{24}\.tmp|[0-9a-f]{16}\.json\.mixdog-tmp-[0-9a-f]{16,24})$/i;
45
+ export const RE_MANIFEST_TMP = /^(?:\.manifest\.json\.[0-9a-f]{24}\.tmp|manifest\.json\.mixdog-tmp-[0-9a-f]{16,24})$/i;
43
46
  export const RE_CACHE_LOCK = /^[0-9a-f]{16}\.json\.lock$/i;
44
47
  // Same debris, for the `<hash>.calls.json` call-site sidecar written beside
45
- // each entry (atomic writer temp name: `.<basename>.<24hex>.tmp`).
46
- export const RE_CALLS_CACHE_TMP = /^\.[0-9a-f]{16}\.calls\.json\.[0-9a-f]{24}\.tmp$/i;
48
+ // each entry.
49
+ export const RE_CALLS_CACHE_TMP =
50
+ /^(?:\.[0-9a-f]{16}\.calls\.json\.[0-9a-f]{24}\.tmp|[0-9a-f]{16}\.calls\.json\.mixdog-tmp-[0-9a-f]{16,24})$/i;
47
51
  export const RE_CALLS_CACHE_LOCK = /^[0-9a-f]{16}\.calls\.json\.lock$/i;
48
52
  export const CODE_GRAPH_MEMORY_MAX_ENTRIES = Math.max(
49
53
  1,