mixdog 0.9.151 → 0.9.153

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 (307) hide show
  1. package/LICENSES/browser-import-NOTICE.txt +11 -0
  2. package/NOTICE.md +11 -0
  3. package/README.md +108 -68
  4. package/package.json +17 -4
  5. package/scripts/computer-task-cost-report.mjs +144 -0
  6. package/scripts/run-office-live-tests.mjs +32 -0
  7. package/scripts/tool-stress.mjs +0 -2
  8. package/src/agents/front-worker/AGENT.md +13 -0
  9. package/src/agents/front-worker/agent.json +6 -0
  10. package/src/agents/heavy-worker/AGENT.md +10 -11
  11. package/src/agents/heavy-worker/agent.json +1 -1
  12. package/src/agents/maintainer/AGENT.md +0 -2
  13. package/src/agents/reviewer/AGENT.md +9 -13
  14. package/src/agents/reviewer/agent.json +1 -1
  15. package/src/agents/security/AGENT.md +14 -0
  16. package/src/agents/security/agent.json +6 -0
  17. package/src/agents/worker/AGENT.md +8 -8
  18. package/src/agents/worker/agent.json +1 -1
  19. package/src/agents/writer/AGENT.md +14 -0
  20. package/src/agents/writer/agent.json +6 -0
  21. package/src/defaults/skills/setup/SKILL.md +18 -27
  22. package/src/headless-exec.mjs +3 -10
  23. package/src/headless-exec.test.mjs +2 -7
  24. package/src/rules/agent/00-common.md +4 -7
  25. package/src/rules/lead/01-general.md +0 -3
  26. package/src/rules/shared/10-tool-workflow.md +10 -2
  27. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +183 -26
  28. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.test.mjs +378 -0
  29. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +4 -0
  30. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +109 -0
  31. package/src/runtime/agent/orchestrator/cache-break-trace.test.mjs +115 -0
  32. package/src/runtime/agent/orchestrator/config.mjs +13 -68
  33. package/src/runtime/agent/orchestrator/context/collect-skills.test.mjs +153 -0
  34. package/src/runtime/agent/orchestrator/context/collect.mjs +49 -45
  35. package/src/runtime/agent/orchestrator/mcp/client.mjs +242 -15
  36. package/src/runtime/agent/orchestrator/mcp/features.test.mjs +66 -0
  37. package/src/runtime/agent/orchestrator/mcp/security.test.mjs +36 -20
  38. package/src/runtime/agent/orchestrator/providers/admission-scheduler-metrics.test.mjs +47 -0
  39. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +16 -3
  40. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +54 -33
  41. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +1 -0
  42. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
  43. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +7 -0
  44. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +3 -8
  45. package/src/runtime/agent/orchestrator/providers/gemini.mjs +7 -0
  46. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +19 -3
  47. package/src/runtime/agent/orchestrator/providers/lib/provider-replay.mjs +50 -0
  48. package/src/runtime/agent/orchestrator/providers/lib/sse-framing.mjs +93 -0
  49. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +30 -9
  50. package/src/runtime/agent/orchestrator/providers/media-parity.test.mjs +59 -0
  51. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +3 -3
  52. package/src/runtime/agent/orchestrator/providers/openai-codex-identity.test.mjs +71 -0
  53. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +18 -26
  54. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +8 -0
  55. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +7 -0
  56. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +25 -2
  57. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +35 -4
  58. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +47 -2
  59. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +103 -12
  60. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +13 -0
  61. package/src/runtime/agent/orchestrator/providers/openai-turn-state.mjs +68 -0
  62. package/src/runtime/agent/orchestrator/providers/openai-ws-delta-parity.test.mjs +122 -6
  63. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +36 -11
  64. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +144 -93
  65. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +18 -13
  66. package/src/runtime/agent/orchestrator/providers/provider-replay.test.mjs +288 -0
  67. package/src/runtime/agent/orchestrator/providers/sse-framing.test.mjs +719 -0
  68. package/src/runtime/agent/orchestrator/providers/stream-json-pool.mjs +399 -39
  69. package/src/runtime/agent/orchestrator/providers/stream-json-worker.mjs +26 -0
  70. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +86 -7
  71. package/src/runtime/agent/orchestrator/session/agent-loop.test.mjs +222 -0
  72. package/src/runtime/agent/orchestrator/session/browser-snapshot-supersession.mjs +61 -0
  73. package/src/runtime/agent/orchestrator/session/browser-snapshot-supersession.test.mjs +83 -0
  74. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +13 -0
  75. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +26 -2
  76. package/src/runtime/agent/orchestrator/session/compact-policy.test.mjs +112 -0
  77. package/src/runtime/agent/orchestrator/session/compaction-read-reset.test.mjs +192 -0
  78. package/src/runtime/agent/orchestrator/session/context-usage-restart.test.mjs +72 -0
  79. package/src/runtime/agent/orchestrator/session/context-utils.mjs +100 -15
  80. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +17 -1
  81. package/src/runtime/agent/orchestrator/session/eager-dispatch.test.mjs +74 -0
  82. package/src/runtime/agent/orchestrator/session/image-strip-recovery.mjs +12 -8
  83. package/src/runtime/agent/orchestrator/session/image-strip-recovery.test.mjs +20 -1
  84. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +110 -2
  85. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +3 -16
  86. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.test.mjs +28 -0
  87. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +2 -2
  88. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +6 -0
  89. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
  90. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +6 -0
  91. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +111 -39
  92. package/src/runtime/agent/orchestrator/session/manager/ask-session.test.mjs +46 -0
  93. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -6
  94. package/src/runtime/agent/orchestrator/session/manager/message-sanitize.mjs +3 -0
  95. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +27 -3
  96. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +26 -54
  97. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.test.mjs +63 -1
  98. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +3 -0
  99. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -110
  100. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +580 -0
  101. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.test.mjs +593 -0
  102. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +119 -190
  103. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +115 -0
  104. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +43 -8
  105. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +129 -12
  106. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +9 -2
  107. package/src/runtime/agent/orchestrator/session/store/listing.mjs +8 -1
  108. package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +15 -1
  109. package/src/runtime/agent/orchestrator/session/store-live-publication.test.mjs +83 -0
  110. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +36 -2
  111. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +157 -30
  112. package/src/runtime/agent/orchestrator/session/store-summary-reader.test.mjs +64 -0
  113. package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +57 -0
  114. package/src/runtime/agent/orchestrator/session/store-summary-visibility.test.mjs +219 -0
  115. package/src/runtime/agent/orchestrator/session/store.mjs +26 -2
  116. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +22 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool-cwd-env.test.mjs +14 -0
  118. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -10
  119. package/src/runtime/agent/orchestrator/tools/builtin/find-search-budget.test.mjs +30 -14
  120. package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +15 -4
  121. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +73 -15
  122. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +76 -2
  123. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +24 -683
  124. package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +64 -72
  125. package/src/runtime/agent/orchestrator/tools/builtin/native-search-runner.mjs +2 -5
  126. package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.mjs +75 -0
  127. package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.test.mjs +24 -0
  128. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +6 -6
  129. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +12 -0
  130. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +4 -1
  131. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +24 -5
  132. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.test.mjs +44 -0
  133. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +12 -2
  134. package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +35 -21
  135. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +28 -13
  136. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.test.mjs +54 -0
  137. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
  138. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
  139. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +12 -0
  140. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +11 -11
  141. package/src/runtime/attachments/pdf-extract.mjs +3 -0
  142. package/src/runtime/attachments/pdfjs-runtime.mjs +13 -0
  143. package/src/runtime/bridge-clients.test.mjs +827 -0
  144. package/src/runtime/browser-bridge/action-schema.mjs +251 -0
  145. package/src/runtime/browser-bridge/client.mjs +152 -0
  146. package/src/runtime/browser-bridge/tool-defs.mjs +159 -0
  147. package/src/runtime/computer-bridge/action-schema.mjs +653 -0
  148. package/src/runtime/computer-bridge/client.mjs +231 -0
  149. package/src/runtime/computer-bridge/tool-defs.mjs +33 -0
  150. package/src/runtime/media/store.mjs +18 -8
  151. package/src/runtime/media/store.test.mjs +14 -0
  152. package/src/runtime/memory/index.mjs +1 -1
  153. package/src/runtime/memory/lib/transcript-ingest.mjs +8 -6
  154. package/src/runtime/memory/lib/transcript-ingest.test.mjs +52 -0
  155. package/src/runtime/office/assurance-benchmark.mjs +358 -0
  156. package/src/runtime/office/assurance.mjs +833 -0
  157. package/src/runtime/office/benchmark.mjs +496 -0
  158. package/src/runtime/office/capabilities.mjs +551 -0
  159. package/src/runtime/office/com-adapter.mjs +283 -0
  160. package/src/runtime/office/content-model.mjs +183 -0
  161. package/src/runtime/office/contract-benchmark.mjs +105 -0
  162. package/src/runtime/office/design-library.mjs +1562 -0
  163. package/src/runtime/office/design-system.mjs +1966 -0
  164. package/src/runtime/office/index.mjs +2413 -0
  165. package/src/runtime/office/journal.mjs +82 -0
  166. package/src/runtime/office/office-assurance.test.mjs +371 -0
  167. package/src/runtime/office/office-com-host.ps1 +3781 -0
  168. package/src/runtime/office/office-com-session-host.ps1 +855 -0
  169. package/src/runtime/office/office-live-runtime.test.mjs +819 -0
  170. package/src/runtime/office/office-runtime.test.mjs +2070 -0
  171. package/src/runtime/office/ooxml-validator.mjs +271 -0
  172. package/src/runtime/office/pagination.mjs +132 -0
  173. package/src/runtime/office/pdf-adapter.mjs +591 -0
  174. package/src/runtime/office/pdf-analysis.mjs +372 -0
  175. package/src/runtime/office/pdf-render.mjs +167 -0
  176. package/src/runtime/office/pdf-security.mjs +63 -0
  177. package/src/runtime/office/polish-benchmark.mjs +190 -0
  178. package/src/runtime/office/portable-ooxml.mjs +1867 -0
  179. package/src/runtime/office/quality-live-benchmark.mjs +486 -0
  180. package/src/runtime/office/quality-pipeline.mjs +136 -0
  181. package/src/runtime/office/tabular.mjs +302 -0
  182. package/src/runtime/office/templates/mixdog-executive.pptx +0 -0
  183. package/src/runtime/office/templates/mixdog-executive.pptx.mixdog.json +190 -0
  184. package/src/runtime/office/tool-defs.mjs +113 -0
  185. package/src/runtime/office/visual-diff.mjs +89 -0
  186. package/src/runtime/office/xlsx-assertions.mjs +172 -0
  187. package/src/runtime/office/xlsx-contract.mjs +147 -0
  188. package/src/runtime/shared/agent-route-config.mjs +9 -0
  189. package/src/runtime/shared/child-spawn-gate.mjs +17 -0
  190. package/src/runtime/shared/child-spawn-remote.mjs +69 -7
  191. package/src/runtime/shared/child-spawn-remote.test.mjs +124 -0
  192. package/src/runtime/shared/pristine-execution-contract.json +2 -1
  193. package/src/runtime/shared/provider-api-key.mjs +1 -0
  194. package/src/runtime/shared/session-runtime-health.mjs +91 -0
  195. package/src/runtime/shared/session-runtime-health.test.mjs +53 -0
  196. package/src/runtime/shared/skill-document.mjs +86 -0
  197. package/src/runtime/shared/skill-document.test.mjs +81 -0
  198. package/src/runtime/shared/tool-execution-contract.mjs +16 -0
  199. package/src/runtime/shared/tool-surface.mjs +27 -0
  200. package/src/runtime/shared/turn-snapshot-store.mjs +1 -0
  201. package/src/runtime/shared/turn-snapshot.mjs +32 -9
  202. package/src/runtime/shared/user-cwd.mjs +33 -14
  203. package/src/runtime/shared/user-cwd.test.mjs +43 -0
  204. package/src/session-runtime/agent-disable.test.mjs +20 -1
  205. package/src/session-runtime/context-status.mjs +9 -3
  206. package/src/session-runtime/cwd-plugins.mjs +26 -26
  207. package/src/session-runtime/cwd-plugins.test.mjs +75 -0
  208. package/src/session-runtime/cwd-tool-routing.test.mjs +5 -0
  209. package/src/session-runtime/deferred-tool-delta.mjs +104 -0
  210. package/src/session-runtime/deferred-tool-delta.test.mjs +40 -0
  211. package/src/session-runtime/global-extensions.mjs +26 -0
  212. package/src/session-runtime/global-extensions.test.mjs +28 -0
  213. package/src/session-runtime/global-settings-session-identity.test.mjs +76 -0
  214. package/src/session-runtime/goal-runtime.mjs +1141 -0
  215. package/src/session-runtime/goal-runtime.test.mjs +645 -0
  216. package/src/session-runtime/lifecycle-api.mjs +58 -11
  217. package/src/session-runtime/lifecycle-api.test.mjs +183 -0
  218. package/src/session-runtime/mcp-glue.mjs +49 -47
  219. package/src/session-runtime/plugin-mcp-standard.test.mjs +177 -0
  220. package/src/session-runtime/plugin-mcp.mjs +96 -22
  221. package/src/session-runtime/prewarm.mjs +1 -10
  222. package/src/session-runtime/provider-request-snapshot.mjs +33 -9
  223. package/src/session-runtime/provider-request-snapshot.test.mjs +27 -5
  224. package/src/session-runtime/resource-api-global.test.mjs +109 -0
  225. package/src/session-runtime/resource-api.mjs +168 -116
  226. package/src/session-runtime/runtime-core.mjs +208 -29
  227. package/src/session-runtime/runtime-tunables.mjs +6 -8
  228. package/src/session-runtime/session-lifecycle.mjs +34 -12
  229. package/src/session-runtime/session-title.mjs +2 -2
  230. package/src/session-runtime/session-turn-api.mjs +37 -25
  231. package/src/session-runtime/skills-api.mjs +79 -25
  232. package/src/session-runtime/skills-api.test.mjs +99 -0
  233. package/src/session-runtime/tool-catalog-data.mjs +1 -1
  234. package/src/session-runtime/tool-catalog-schema.mjs +0 -5
  235. package/src/session-runtime/tool-catalog.mjs +42 -112
  236. package/src/session-runtime/tool-defs.mjs +6 -1
  237. package/src/session-runtime/tool-policy-surface.test.mjs +93 -17
  238. package/src/session-runtime/tool-surface.mjs +30 -8
  239. package/src/session-runtime/workflow-agents-api.mjs +8 -25
  240. package/src/session-runtime/workflow.mjs +8 -6
  241. package/src/standalone/agent-dispatch-broker.mjs +5 -9
  242. package/src/standalone/agent-tool/helpers.mjs +2 -2
  243. package/src/standalone/agent-tool/job-views.mjs +3 -1
  244. package/src/standalone/agent-tool/notify.mjs +11 -9
  245. package/src/standalone/agent-tool/notify.test.mjs +37 -0
  246. package/src/standalone/agent-tool/render.mjs +3 -0
  247. package/src/standalone/agent-tool/spawn-flow.mjs +61 -16
  248. package/src/standalone/agent-tool/spawn-flow.test.mjs +41 -0
  249. package/src/standalone/agent-tool/tool-def.mjs +1 -1
  250. package/src/standalone/agent-tool/worker-index.mjs +123 -0
  251. package/src/standalone/agent-tool/worker-index.test.mjs +132 -0
  252. package/src/standalone/agent-tool/worker-rows.mjs +2 -1
  253. package/src/standalone/agent-tool.mjs +57 -10
  254. package/src/standalone/daemon.mjs +113 -15
  255. package/src/standalone/hook-bus/config.mjs +1 -0
  256. package/src/standalone/plugin-admin.mjs +17 -0
  257. package/src/standalone/plugin-admin.test.mjs +7 -0
  258. package/src/standalone/provider-admin.mjs +1 -0
  259. package/src/standalone/session-protocol.mjs +5 -0
  260. package/src/standalone/session-runtime-agent-control-client.mjs +133 -0
  261. package/src/standalone/session-runtime-agent-control-client.test.mjs +71 -0
  262. package/src/standalone/session-runtime-dispatch-cancel.test.mjs +139 -0
  263. package/src/standalone/session-runtime-host-factory.mjs +17 -0
  264. package/src/standalone/session-runtime-host-factory.test.mjs +31 -0
  265. package/src/standalone/session-runtime-host-health.test.mjs +5 -0
  266. package/src/standalone/session-runtime-host.mjs +709 -58
  267. package/src/standalone/session-runtime-inline-host.mjs +258 -0
  268. package/src/standalone/session-runtime-inline-host.test.mjs +385 -0
  269. package/src/standalone/session-runtime-provider-cooldown.mjs +78 -0
  270. package/src/standalone/session-runtime-provider-cooldown.test.mjs +80 -0
  271. package/src/standalone/session-runtime-shard-host.test.mjs +642 -0
  272. package/src/standalone/session-runtime-shard-router.mjs +149 -0
  273. package/src/standalone/session-runtime-shard-router.test.mjs +101 -0
  274. package/src/standalone/session-runtime-worker.mjs +302 -26
  275. package/src/standalone/session-service-agent-tree.test.mjs +984 -0
  276. package/src/standalone/session-service-goal-recovery.test.mjs +177 -0
  277. package/src/standalone/session-service.mjs +711 -11
  278. package/src/tui/app/app-view.jsx +1 -0
  279. package/src/tui/app/core-memory-picker.mjs +2 -0
  280. package/src/tui/app/extension-pickers.mjs +1 -1
  281. package/src/tui/app/maintenance-pickers.mjs +3 -3
  282. package/src/tui/app/model-options.mjs +2 -0
  283. package/src/tui/app/model-picker.mjs +14 -4
  284. package/src/tui/app/panel-handoff.test.mjs +311 -0
  285. package/src/tui/app/project-picker.mjs +1 -0
  286. package/src/tui/app/route-pickers.mjs +21 -6
  287. package/src/tui/app/settings-picker.mjs +26 -3
  288. package/src/tui/app/slash-commands.mjs +1 -0
  289. package/src/tui/app/slash-dispatch.mjs +73 -22
  290. package/src/tui/app/theme-effort-pickers.mjs +7 -2
  291. package/src/tui/components/Picker.jsx +10 -1
  292. package/src/tui/dist/index.mjs +187 -45
  293. package/src/tui/lib/voice-setup.mjs +4 -1
  294. package/src/tui/session/agent-message-source.test.mjs +37 -0
  295. package/src/tui/session/completion-card-restore.test.mjs +50 -0
  296. package/src/tui/session/context-state.mjs +3 -13
  297. package/src/tui/session/goal-continuation.mjs +141 -0
  298. package/src/tui/session/goal-continuation.test.mjs +203 -0
  299. package/src/tui/session/queue-helpers.mjs +5 -2
  300. package/src/tui/session/session-action-surface.test.mjs +190 -0
  301. package/src/tui/session/session-api-ext.mjs +48 -8
  302. package/src/tui/session/session-api.mjs +73 -1
  303. package/src/tui/session/session-flow.mjs +31 -1
  304. package/src/tui/session/turn.mjs +88 -15
  305. package/src/tui/session-local.mjs +39 -3
  306. package/src/vendor/statusline/src/gateway/route-meta.mjs +31 -0
  307. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +0 -301
@@ -0,0 +1,372 @@
1
+ import { createRequire } from 'node:module';
2
+ import {
3
+ access,
4
+ mkdir,
5
+ readFile,
6
+ rm,
7
+ writeFile,
8
+ } from 'node:fs/promises';
9
+ import { basename, dirname, extname, join } from 'node:path';
10
+ import {
11
+ DOMMatrix,
12
+ ImageData,
13
+ Path2D,
14
+ } from '@napi-rs/canvas';
15
+ import fontkit from '@pdf-lib/fontkit';
16
+ import {
17
+ PDFDocument,
18
+ StandardFonts,
19
+ rgb,
20
+ } from 'pdf-lib';
21
+ import sharp from 'sharp';
22
+ import { resolvedPdfJs } from '../attachments/pdfjs-runtime.mjs';
23
+ import { renderPdfPages } from './pdf-render.mjs';
24
+
25
+ const require = createRequire(import.meta.url);
26
+ const MAX_QUERY_PAGES = 100;
27
+
28
+ function installPdfGlobals() {
29
+ globalThis.DOMMatrix ??= DOMMatrix;
30
+ globalThis.ImageData ??= ImageData;
31
+ globalThis.Path2D ??= Path2D;
32
+ }
33
+
34
+ function selectedPages(total, pages) {
35
+ const values = Array.isArray(pages) && pages.length
36
+ ? [...new Set(pages.map(Number))]
37
+ : Array.from({ length: total }, (_, index) => index + 1);
38
+ if (values.length > MAX_QUERY_PAGES) throw new Error(`PDF analysis accepts at most ${MAX_QUERY_PAGES} pages per call`);
39
+ for (const page of values) {
40
+ if (!Number.isInteger(page) || page < 1 || page > total) throw new Error(`PDF page out of range: ${page}`);
41
+ }
42
+ return values;
43
+ }
44
+
45
+ async function openPdfJs(path) {
46
+ installPdfGlobals();
47
+ const pdfjs = await resolvedPdfJs();
48
+ const loading = pdfjs.getDocument({
49
+ data: new Uint8Array(await readFile(path)),
50
+ disableWorker: true,
51
+ useSystemFonts: true,
52
+ isEvalSupported: false,
53
+ verbosity: pdfjs.VerbosityLevel.ERRORS,
54
+ });
55
+ return { pdfjs, document: await loading.promise };
56
+ }
57
+
58
+ export async function extractPdfTextLayout(path, {
59
+ pages = null,
60
+ maxItems = 20_000,
61
+ signal = null,
62
+ } = {}) {
63
+ const { pdfjs, document } = await openPdfJs(path);
64
+ try {
65
+ const output = [];
66
+ let truncated = false;
67
+ for (const pageNumber of selectedPages(document.numPages, pages)) {
68
+ if (signal?.aborted) throw new Error('PDF layout extraction was cancelled');
69
+ const page = await document.getPage(pageNumber);
70
+ try {
71
+ const viewport = page.getViewport({ scale: 1 });
72
+ const content = await page.getTextContent();
73
+ const items = [];
74
+ for (const item of content.items || []) {
75
+ if (!item?.str) continue;
76
+ if (output.reduce((sum, entry) => sum + entry.items.length, 0) + items.length >= maxItems) {
77
+ truncated = true;
78
+ break;
79
+ }
80
+ const transform = pdfjs.Util.transform(viewport.transform, item.transform);
81
+ const height = Math.max(1, Math.hypot(transform[2], transform[3]));
82
+ items.push({
83
+ text: item.str,
84
+ x: Number(transform[4].toFixed(2)),
85
+ top: Number((transform[5] - height).toFixed(2)),
86
+ width: Number(Number(item.width || 0).toFixed(2)),
87
+ height: Number(height.toFixed(2)),
88
+ direction: item.dir || '',
89
+ font: item.fontName || '',
90
+ });
91
+ }
92
+ output.push({
93
+ page: pageNumber,
94
+ width: Number(viewport.width.toFixed(2)),
95
+ height: Number(viewport.height.toFixed(2)),
96
+ items,
97
+ });
98
+ if (truncated) break;
99
+ } finally {
100
+ try { page.cleanup?.(); } catch {}
101
+ }
102
+ }
103
+ return { pageCount: document.numPages, pages: output, truncated };
104
+ } finally {
105
+ try { await document.destroy?.(); } catch {}
106
+ }
107
+ }
108
+
109
+ function clusterRows(items, tolerance = 3) {
110
+ const rows = [];
111
+ for (const item of [...items].sort((left, right) => left.top - right.top || left.x - right.x)) {
112
+ if (!String(item.text || '').trim()) continue;
113
+ let row = rows.find((entry) => Math.abs(entry.top - item.top) <= tolerance);
114
+ if (!row) {
115
+ row = { top: item.top, cells: [] };
116
+ rows.push(row);
117
+ }
118
+ row.cells.push(item);
119
+ row.top = (row.top * (row.cells.length - 1) + item.top) / row.cells.length;
120
+ }
121
+ return rows.map((row) => ({
122
+ top: Number(row.top.toFixed(2)),
123
+ cells: row.cells.sort((left, right) => left.x - right.x).map((item) => ({
124
+ text: item.text,
125
+ x: item.x,
126
+ width: item.width,
127
+ })),
128
+ }));
129
+ }
130
+
131
+ export function inferPdfTables(layout) {
132
+ const tables = [];
133
+ for (const page of layout.pages || []) {
134
+ const rows = clusterRows(page.items || []).filter((row) => row.cells.length >= 2);
135
+ if (rows.length < 2) continue;
136
+ const counts = new Map();
137
+ for (const row of rows) counts.set(row.cells.length, (counts.get(row.cells.length) || 0) + 1);
138
+ const [columns, repeated] = [...counts.entries()].sort((left, right) => right[1] - left[1])[0] || [0, 0];
139
+ const selected = rows.filter((row) => Math.abs(row.cells.length - columns) <= 1);
140
+ if (columns < 2 || selected.length < 2) continue;
141
+ tables.push({
142
+ page: page.page,
143
+ columns,
144
+ confidence: Number((repeated / rows.length).toFixed(3)),
145
+ rows: selected.map((row) => row.cells.map((cell) => cell.text)),
146
+ geometry: selected,
147
+ });
148
+ }
149
+ return {
150
+ pageCount: layout.pageCount,
151
+ tableCount: tables.length,
152
+ tables,
153
+ truncated: layout.truncated === true,
154
+ };
155
+ }
156
+
157
+ async function pdfImageBuffer(image) {
158
+ if (!image?.data || !image.width || !image.height) return null;
159
+ const channels = Math.round(image.data.length / (image.width * image.height));
160
+ if (![1, 2, 3, 4].includes(channels)) return null;
161
+ return await sharp(Buffer.from(image.data), {
162
+ raw: { width: image.width, height: image.height, channels },
163
+ }).png().toBuffer();
164
+ }
165
+
166
+ function resolvedPdfObject(objects, name) {
167
+ return new Promise((resolve, reject) => {
168
+ try {
169
+ objects.get(name, (value) => resolve(value));
170
+ } catch (error) {
171
+ reject(error);
172
+ }
173
+ });
174
+ }
175
+
176
+ export async function extractPdfImages(path, {
177
+ pages = null,
178
+ signal = null,
179
+ } = {}) {
180
+ const { pdfjs, document } = await openPdfJs(path);
181
+ const images = [];
182
+ try {
183
+ for (const pageNumber of selectedPages(document.numPages, pages)) {
184
+ if (signal?.aborted) throw new Error('PDF image extraction was cancelled');
185
+ const page = await document.getPage(pageNumber);
186
+ try {
187
+ const operators = await page.getOperatorList();
188
+ const seen = new Set();
189
+ for (let index = 0; index < operators.fnArray.length; index += 1) {
190
+ const fn = operators.fnArray[index];
191
+ if (![pdfjs.OPS.paintImageXObject, pdfjs.OPS.paintInlineImageXObject].includes(fn)) continue;
192
+ const args = operators.argsArray[index] || [];
193
+ const key = fn === pdfjs.OPS.paintInlineImageXObject ? `inline-${index}` : String(args[0]);
194
+ if (seen.has(key)) continue;
195
+ seen.add(key);
196
+ const image = fn === pdfjs.OPS.paintInlineImageXObject
197
+ ? args[0]
198
+ : await resolvedPdfObject(page.objs, args[0]);
199
+ const data = await pdfImageBuffer(image);
200
+ if (!data) continue;
201
+ images.push({
202
+ page: pageNumber,
203
+ index: images.length + 1,
204
+ width: image.width,
205
+ height: image.height,
206
+ mimeType: 'image/png',
207
+ data: data.toString('base64'),
208
+ });
209
+ }
210
+ } finally {
211
+ try { page.cleanup?.(); } catch {}
212
+ }
213
+ }
214
+ return {
215
+ pageCount: document.numPages,
216
+ imageCount: images.length,
217
+ images: images.map(({ data, ...image }) => image),
218
+ _images: images,
219
+ };
220
+ } finally {
221
+ try { await document.destroy?.(); } catch {}
222
+ }
223
+ }
224
+
225
+ export function parseOcrTsv(value) {
226
+ const rows = String(value || '').split(/\r?\n/);
227
+ if (!rows.length) return [];
228
+ const header = rows.shift().split('\t');
229
+ const position = Object.fromEntries(header.map((name, index) => [name, index]));
230
+ return rows.map((line) => line.split('\t')).filter((columns) => columns.length >= header.length).map((columns) => ({
231
+ text: columns[position.text] || '',
232
+ confidence: Number(columns[position.conf] || -1),
233
+ left: Number(columns[position.left] || 0),
234
+ top: Number(columns[position.top] || 0),
235
+ width: Number(columns[position.width] || 0),
236
+ height: Number(columns[position.height] || 0),
237
+ })).filter((word) => word.text.trim() && word.width > 0 && word.height > 0);
238
+ }
239
+
240
+ export function parseOcrBlocks(blocks) {
241
+ const words = [];
242
+ for (const block of blocks || []) {
243
+ for (const paragraph of block?.paragraphs || []) {
244
+ for (const line of paragraph?.lines || []) {
245
+ for (const word of line?.words || []) {
246
+ const bbox = word?.bbox || {};
247
+ const left = Number(bbox.x0 || 0);
248
+ const top = Number(bbox.y0 || 0);
249
+ const width = Number(bbox.x1 || 0) - left;
250
+ const height = Number(bbox.y1 || 0) - top;
251
+ if (String(word?.text || '').trim() && width > 0 && height > 0) {
252
+ words.push({
253
+ text: String(word.text),
254
+ confidence: Number(word.confidence || 0),
255
+ left,
256
+ top,
257
+ width,
258
+ height,
259
+ });
260
+ }
261
+ }
262
+ }
263
+ }
264
+ }
265
+ return words;
266
+ }
267
+
268
+ async function existingPath(candidates) {
269
+ for (const candidate of candidates.filter(Boolean)) {
270
+ try {
271
+ await access(candidate);
272
+ return candidate;
273
+ } catch {}
274
+ }
275
+ return '';
276
+ }
277
+
278
+ async function ocrFontPath(explicit = '') {
279
+ return await existingPath([
280
+ explicit,
281
+ process.env.MIXDOG_OCR_FONT,
282
+ process.platform === 'win32' ? 'C:\\Windows\\Fonts\\malgun.ttf' : '',
283
+ process.platform === 'darwin' ? '/System/Library/Fonts/AppleSDGothicNeo.ttc' : '',
284
+ '/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc',
285
+ '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',
286
+ ]);
287
+ }
288
+
289
+ export async function ocrPdf(path, operation, {
290
+ dataDir,
291
+ signal = null,
292
+ } = {}) {
293
+ const source = await readFile(path);
294
+ const document = await PDFDocument.load(source, {
295
+ ignoreEncryption: false,
296
+ updateMetadata: false,
297
+ });
298
+ const pages = selectedPages(document.getPageCount(), operation.pages || (operation.page ? [operation.page] : null));
299
+ const languages = Array.isArray(operation.languages)
300
+ ? operation.languages.map(String).join('+')
301
+ : String(operation.languages || 'eng+kor');
302
+ const cachePath = join(dataDir, 'office', 'ocr', 'languages');
303
+ await mkdir(cachePath, { recursive: true });
304
+ const tesseract = require('tesseract.js');
305
+ const worker = await tesseract.createWorker(languages, tesseract.OEM.LSTM_ONLY, {
306
+ cachePath,
307
+ gzip: true,
308
+ });
309
+ document.registerFontkit(fontkit);
310
+ const fontPath = await ocrFontPath(operation.fontPath);
311
+ const font = fontPath
312
+ ? await document.embedFont(await readFile(fontPath), { subset: true })
313
+ : await document.embedFont(StandardFonts.Helvetica);
314
+ let wordCount = 0;
315
+ let totalConfidence = 0;
316
+ let text = '';
317
+ const temporaryImages = [];
318
+ try {
319
+ for (const pageNumber of pages) {
320
+ if (signal?.aborted) throw new Error('PDF OCR was cancelled');
321
+ const rendered = await renderPdfPages(path, {
322
+ pages: [pageNumber],
323
+ maxWidth: Math.max(1200, Math.min(3200, Number(operation.maxWidth) || 2400)),
324
+ signal,
325
+ });
326
+ const image = rendered.images[0];
327
+ temporaryImages.push(image.path);
328
+ const recognized = await worker.recognize(image.path, {}, { text: true, tsv: true, blocks: true });
329
+ const positionedWords = parseOcrTsv(recognized.data.tsv);
330
+ const words = (positionedWords.length ? positionedWords : parseOcrBlocks(recognized.data.blocks))
331
+ .filter((word) => word.confidence >= Number(operation.minConfidence ?? 40));
332
+ const page = document.getPage(pageNumber - 1);
333
+ const scaleX = page.getWidth() / image.width;
334
+ const scaleY = page.getHeight() / image.height;
335
+ for (const word of words) {
336
+ try {
337
+ page.drawText(word.text, {
338
+ x: word.left * scaleX,
339
+ y: page.getHeight() - (word.top + word.height) * scaleY,
340
+ size: Math.max(4, word.height * scaleY * 0.8),
341
+ font,
342
+ color: rgb(0, 0, 0),
343
+ opacity: 0,
344
+ });
345
+ wordCount += 1;
346
+ totalConfidence += word.confidence;
347
+ } catch (error) {
348
+ if (!fontPath && /encode|WinAnsi/i.test(String(error?.message || ''))) {
349
+ throw new Error('OCR detected non-Latin text but no Unicode OCR font is available; add fontPath or MIXDOG_OCR_FONT');
350
+ }
351
+ throw error;
352
+ }
353
+ }
354
+ text += `${text ? '\n\n' : ''}--- Page ${pageNumber} ---\n${recognized.data.text || ''}`;
355
+ }
356
+ if (wordCount > 0) await writeFile(path, await document.save({ useObjectStreams: true, addDefaultPage: false }));
357
+ return {
358
+ op: operation.op,
359
+ changed: wordCount > 0,
360
+ pages,
361
+ languages,
362
+ wordCount,
363
+ averageConfidence: wordCount ? Number((totalConfidence / wordCount).toFixed(2)) : 0,
364
+ text,
365
+ searchableTextLayer: wordCount > 0,
366
+ fontEmbedded: Boolean(fontPath),
367
+ };
368
+ } finally {
369
+ await worker.terminate().catch(() => {});
370
+ for (const image of temporaryImages) await rm(image, { force: true }).catch(() => {});
371
+ }
372
+ }
@@ -0,0 +1,167 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import { basename, dirname, extname, join } from 'node:path';
3
+ import {
4
+ DOMMatrix,
5
+ ImageData,
6
+ Path2D,
7
+ createCanvas,
8
+ } from '@napi-rs/canvas';
9
+ import { resolvedPdfJs } from '../attachments/pdfjs-runtime.mjs';
10
+
11
+ function installPdfJsCanvasGlobals() {
12
+ globalThis.DOMMatrix ??= DOMMatrix;
13
+ globalThis.ImageData ??= ImageData;
14
+ globalThis.Path2D ??= Path2D;
15
+ }
16
+
17
+ function requestedPages(pageCount, pages) {
18
+ const selected = Array.isArray(pages) && pages.length
19
+ ? pages
20
+ : Array.from({ length: pageCount }, (_, index) => index + 1);
21
+ const unique = [...new Set(selected.map(Number))];
22
+ if (Array.isArray(pages) && pages.length && unique.length > 12) {
23
+ throw new Error('render accepts at most 12 explicit pages per call');
24
+ }
25
+ for (const page of unique) {
26
+ if (!Number.isInteger(page) || page < 1 || page > pageCount) {
27
+ throw new Error(`render page out of range: ${page} (document has ${pageCount})`);
28
+ }
29
+ }
30
+ return unique;
31
+ }
32
+
33
+ export async function renderPdfPages(path, {
34
+ pages = null,
35
+ maxWidth = 1400,
36
+ signal = null,
37
+ } = {}) {
38
+ if (signal?.aborted) throw new Error('PDF rendering was cancelled');
39
+ installPdfJsCanvasGlobals();
40
+ const { getDocument, VerbosityLevel } = await resolvedPdfJs();
41
+ const loading = getDocument({
42
+ data: new Uint8Array(await readFile(path)),
43
+ disableWorker: true,
44
+ useSystemFonts: true,
45
+ isEvalSupported: false,
46
+ verbosity: VerbosityLevel.ERRORS,
47
+ });
48
+ const document = await loading.promise;
49
+ try {
50
+ const selected = requestedPages(document.numPages, pages);
51
+ const stem = basename(path, extname(path));
52
+ const output = [];
53
+ const renderPage = async (pageNumber, targetWidth, minimumScale = 0.25) => {
54
+ if (signal?.aborted) throw new Error('PDF rendering was cancelled');
55
+ const page = await document.getPage(pageNumber);
56
+ try {
57
+ const base = page.getViewport({ scale: 1 });
58
+ const scale = Math.min(2, Math.max(minimumScale, Number(targetWidth || 1400) / base.width));
59
+ const viewport = page.getViewport({ scale });
60
+ const canvas = createCanvas(Math.ceil(viewport.width), Math.ceil(viewport.height));
61
+ const context = canvas.getContext('2d');
62
+ await page.render({
63
+ canvas,
64
+ canvasContext: context,
65
+ viewport,
66
+ background: 'rgb(255,255,255)',
67
+ }).promise;
68
+ if (signal?.aborted) throw new Error('PDF rendering was cancelled');
69
+ return canvas;
70
+ } finally {
71
+ try { page.cleanup?.(); } catch {}
72
+ }
73
+ };
74
+ if (selected.length <= 12) {
75
+ for (const pageNumber of selected) {
76
+ const canvas = await renderPage(pageNumber, maxWidth);
77
+ const data = canvas.toBuffer('image/png');
78
+ const imagePath = join(dirname(path), `${stem}-page-${pageNumber}.png`);
79
+ await writeFile(imagePath, data);
80
+ output.push({
81
+ page: pageNumber,
82
+ path: imagePath,
83
+ width: canvas.width,
84
+ height: canvas.height,
85
+ mimeType: 'image/png',
86
+ data: data.toString('base64'),
87
+ });
88
+ }
89
+ } else {
90
+ const width = Math.max(256, Number(maxWidth) || 1400);
91
+ const pagesPerSheet = Math.ceil(selected.length / 12);
92
+ for (let offset = 0; offset < selected.length; offset += pagesPerSheet) {
93
+ const group = selected.slice(offset, offset + pagesPerSheet);
94
+ const columns = group.length <= 2
95
+ ? 1
96
+ : Math.max(2, Math.ceil(Math.sqrt(group.length * 0.75)));
97
+ const rows = Math.ceil(group.length / columns);
98
+ const gap = 12;
99
+ const labelHeight = 24;
100
+ const cellWidth = Math.max(64, Math.floor((width - (columns + 1) * gap) / columns));
101
+ const thumbnails = [];
102
+ for (const pageNumber of group) {
103
+ thumbnails.push({
104
+ page: pageNumber,
105
+ canvas: await renderPage(pageNumber, cellWidth, 0.05),
106
+ });
107
+ }
108
+ const cellHeight = Math.max(...thumbnails.map(({ canvas }) => canvas.height));
109
+ const sheet = createCanvas(
110
+ width,
111
+ gap + rows * (labelHeight + cellHeight + gap),
112
+ );
113
+ const context = sheet.getContext('2d');
114
+ context.fillStyle = 'rgb(238,240,244)';
115
+ context.fillRect(0, 0, sheet.width, sheet.height);
116
+ context.font = '16px sans-serif';
117
+ context.textBaseline = 'middle';
118
+ for (let index = 0; index < thumbnails.length; index += 1) {
119
+ const { page: pageNumber, canvas } = thumbnails[index];
120
+ const column = index % columns;
121
+ const row = Math.floor(index / columns);
122
+ const x = gap + column * (cellWidth + gap);
123
+ const y = gap + row * (labelHeight + cellHeight + gap);
124
+ context.fillStyle = 'rgb(45,50,60)';
125
+ context.fillText(`Page ${pageNumber}`, x, y + labelHeight / 2);
126
+ context.fillStyle = 'rgb(255,255,255)';
127
+ context.fillRect(x, y + labelHeight, cellWidth, cellHeight);
128
+ context.drawImage(canvas, x, y + labelHeight);
129
+ context.strokeStyle = 'rgb(180,185,195)';
130
+ context.strokeRect(x, y + labelHeight, canvas.width, canvas.height);
131
+ }
132
+ const data = sheet.toBuffer('image/png');
133
+ const first = group[0];
134
+ const last = group.at(-1);
135
+ const imagePath = join(dirname(path), `${stem}-pages-${first}-${last}.png`);
136
+ await writeFile(imagePath, data);
137
+ output.push({
138
+ page: first,
139
+ pages: group,
140
+ path: imagePath,
141
+ width: sheet.width,
142
+ height: sheet.height,
143
+ mimeType: 'image/png',
144
+ data: data.toString('base64'),
145
+ });
146
+ }
147
+ }
148
+ const reviewedPages = output.flatMap((image) => image.pages || [image.page]);
149
+ const reviewedPageSet = new Set(reviewedPages);
150
+ return {
151
+ pageCount: document.numPages,
152
+ images: output,
153
+ visualCoverage: {
154
+ reviewedPages,
155
+ reviewed: reviewedPages.length,
156
+ total: document.numPages,
157
+ complete: reviewedPages.length === document.numPages,
158
+ remainingPages: Array.from(
159
+ { length: document.numPages },
160
+ (_, index) => index + 1,
161
+ ).filter((page) => !reviewedPageSet.has(page)),
162
+ },
163
+ };
164
+ } finally {
165
+ try { await document.destroy?.(); } catch {}
166
+ }
167
+ }
@@ -0,0 +1,63 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { writeFile, rm, rename } from 'node:fs/promises';
4
+ import { dirname, join } from 'node:path';
5
+
6
+ function run(command, args) {
7
+ return new Promise((resolve, reject) => {
8
+ const child = spawn(command, args, {
9
+ windowsHide: true,
10
+ stdio: ['ignore', 'pipe', 'pipe'],
11
+ });
12
+ let stdout = '';
13
+ let stderr = '';
14
+ child.stdout.setEncoding('utf8');
15
+ child.stderr.setEncoding('utf8');
16
+ child.stdout.on('data', (chunk) => { stdout += chunk; });
17
+ child.stderr.on('data', (chunk) => { stderr += chunk; });
18
+ child.once('error', reject);
19
+ child.once('close', (code) => {
20
+ if (code === 0) resolve({ stdout, stderr });
21
+ else reject(new Error(stderr.trim() || stdout.trim() || `qpdf exited with code ${code}`));
22
+ });
23
+ });
24
+ }
25
+
26
+ export async function qpdfAvailable() {
27
+ try {
28
+ await run('qpdf', ['--version']);
29
+ return true;
30
+ } catch {
31
+ return false;
32
+ }
33
+ }
34
+
35
+ export async function securePdf({
36
+ input,
37
+ output,
38
+ mode,
39
+ password = '',
40
+ ownerPassword = '',
41
+ }) {
42
+ if (!await qpdfAvailable()) {
43
+ throw new Error('PDF encryption/decryption requires qpdf; it is not installed in this environment');
44
+ }
45
+ const samePath = input.toLowerCase() === output.toLowerCase();
46
+ const target = samePath
47
+ ? join(dirname(output), `.mixdog-qpdf-${randomUUID()}.pdf`)
48
+ : output;
49
+ const responsePath = join(dirname(target), `.mixdog-qpdf-${randomUUID()}.args`);
50
+ const quote = (value) => `"${String(value).replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`;
51
+ const args = mode === 'encrypt'
52
+ ? ['--encrypt', password, ownerPassword || password, '256', '--', input, target]
53
+ : [`--password=${password}`, '--decrypt', input, target];
54
+ await writeFile(responsePath, `${args.map(quote).join('\n')}\n`, { encoding: 'utf8', mode: 0o600 });
55
+ try {
56
+ await run('qpdf', [`@${responsePath}`]);
57
+ if (samePath) await rename(target, output);
58
+ return { ok: true, mode, input, output };
59
+ } finally {
60
+ await rm(responsePath, { force: true }).catch(() => {});
61
+ if (samePath) await rm(target, { force: true }).catch(() => {});
62
+ }
63
+ }