mixdog 1.3.4 → 1.4.0

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 (472) hide show
  1. package/LICENSES/oh-my-pi-MIT.txt +23 -0
  2. package/NOTICE.md +17 -2
  3. package/README.md +11 -0
  4. package/package.json +3 -11
  5. package/scripts/daemon-stub.mjs +1 -1
  6. package/scripts/embed-google-oauth-client.mjs +49 -0
  7. package/scripts/gate-local.mjs +4 -2
  8. package/scripts/lib/test-spawn-ledger.mjs +46 -1
  9. package/scripts/native-release-status.mjs +2 -2
  10. package/scripts/recall-history-cases.json +1 -1
  11. package/scripts/suite-health.mjs +10 -1
  12. package/scripts/test.mjs +12 -11
  13. package/src/agents/reviewer/AGENT.md +7 -11
  14. package/src/agents/reviewer/agent.json +1 -1
  15. package/src/defaults/shipped-history.json +25 -1
  16. package/src/defaults/skills/browser-use/SKILL.md +77 -17
  17. package/src/defaults/skills/code-tidy/SKILL.md +142 -270
  18. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +4 -4
  19. package/src/defaults/skills/code-tidy/references/cleanup-report.md +31 -76
  20. package/src/defaults/skills/code-tidy/references/dead-code.md +6 -12
  21. package/src/defaults/skills/computer-use/SKILL.md +33 -9
  22. package/src/defaults/skills/setup/SKILL.md +29 -2
  23. package/src/defaults/skills/setup/references/actions.md +117 -28
  24. package/src/defaults/skills/setup/references/surfaces.md +77 -35
  25. package/src/defaults/skills/setup-guide/SKILL.md +3 -3
  26. package/src/defaults/skills/visualize/SKILL.md +2 -1
  27. package/src/lib/keychain-cjs.cjs +359 -29
  28. package/src/lib/rules-builder.cjs +10 -8
  29. package/src/rules/agent/AGENT.md +6 -6
  30. package/src/rules/agent-mode/AGENT_MODE.md +10 -2
  31. package/src/rules/agent-mode/MIXY.md +12 -0
  32. package/src/rules/lead/LEAD.md +18 -20
  33. package/src/rules/shared/05-tool-calls.md +5 -6
  34. package/src/rules/shared/10-tool-workflow.md +2 -0
  35. package/src/rules/shared/60-verification.md +7 -5
  36. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -3
  37. package/src/runtime/agent/orchestrator/agent-runtime/heartbeat-completion.mjs +193 -0
  38. package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +3 -1
  39. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +3 -1
  40. package/src/runtime/agent/orchestrator/config.mjs +34 -0
  41. package/src/runtime/agent/orchestrator/context/collect.mjs +3 -0
  42. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +72 -67
  43. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +54 -1
  44. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -3
  45. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +177 -73
  46. package/src/runtime/agent/orchestrator/mcp/client.mjs +25 -2
  47. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +31 -2
  48. package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +16 -3
  49. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +42 -7
  50. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +21 -2
  51. package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +23 -0
  52. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +11 -1
  53. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +230 -137
  54. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +64 -7
  55. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +18 -5
  56. package/src/runtime/agent/orchestrator/providers/anthropic-cache-warm.mjs +165 -0
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  58. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +4 -1
  59. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +14 -14
  60. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +160 -69
  61. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +25 -3
  62. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +64 -33
  63. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +135 -87
  64. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +52 -8
  65. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +10 -1
  66. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +22 -6
  67. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +15 -4
  68. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +17 -2
  69. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +142 -104
  70. package/src/runtime/agent/orchestrator/providers/cache-warm-admission.mjs +90 -0
  71. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  72. package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +10 -4
  73. package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +10 -217
  74. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +25 -9
  75. package/src/runtime/agent/orchestrator/providers/cursor.mjs +22 -6
  76. package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +409 -0
  77. package/src/runtime/agent/orchestrator/providers/devin-wire-families.mjs +187 -0
  78. package/src/runtime/agent/orchestrator/providers/devin-wire-identity.mjs +342 -0
  79. package/src/runtime/agent/orchestrator/providers/devin-wire-models.mjs +541 -0
  80. package/src/runtime/agent/orchestrator/providers/devin-wire-protobuf.mjs +305 -0
  81. package/src/runtime/agent/orchestrator/providers/devin-wire-request.mjs +558 -0
  82. package/src/runtime/agent/orchestrator/providers/devin-wire-schema.mjs +880 -0
  83. package/src/runtime/agent/orchestrator/providers/devin-wire-session.mjs +190 -0
  84. package/src/runtime/agent/orchestrator/providers/devin-wire-tool-args.mjs +351 -0
  85. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +560 -0
  86. package/src/runtime/agent/orchestrator/providers/devin.mjs +491 -0
  87. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +3 -0
  88. package/src/runtime/agent/orchestrator/providers/gemini-output-limit.mjs +46 -0
  89. package/src/runtime/agent/orchestrator/providers/gemini-reasoning-relay.mjs +30 -0
  90. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +7 -11
  91. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +61 -22
  92. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -0
  93. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -4
  94. package/src/runtime/agent/orchestrator/providers/gemini.mjs +25 -2
  95. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  96. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +96 -5
  97. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +17 -3
  98. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +23 -5
  99. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +52 -10
  100. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +8 -1
  101. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  102. package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +11 -3
  103. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +19 -1
  104. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +39 -3
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
  107. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +6 -1
  108. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -9
  109. package/src/runtime/agent/orchestrator/providers/openai-ws-send/send-context.mjs +22 -3
  110. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +43 -5
  111. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/handshake-failure.mjs +3 -1
  112. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure.mjs +6 -3
  113. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +1 -0
  114. package/src/runtime/agent/orchestrator/providers/provider-availability.mjs +19 -0
  115. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +65 -1
  116. package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +38 -12
  117. package/src/runtime/agent/orchestrator/providers/registry.mjs +43 -3
  118. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +39 -7
  119. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +6 -0
  120. package/src/runtime/agent/orchestrator/providers/wire-protobuf-codec.mjs +368 -0
  121. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +12 -7
  122. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
  123. package/src/runtime/agent/orchestrator/runtime-core/compaction-config-keys.mjs +62 -0
  124. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +37 -16
  125. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +100 -23
  126. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +1 -1
  127. package/src/runtime/agent/orchestrator/runtime-core/reaction-tool-defs.mjs +133 -0
  128. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -0
  129. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +19 -4
  130. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  131. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +125 -30
  132. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +23 -5
  133. package/src/runtime/agent/orchestrator/runtime-core/tool-profile.mjs +35 -10
  134. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +57 -10
  135. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +9 -6
  136. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -1
  137. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +18 -0
  138. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +0 -3
  139. package/src/runtime/agent/orchestrator/session/agent-room.mjs +10 -4
  140. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +792 -0
  141. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +148 -25
  142. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +475 -58
  143. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +42 -17
  144. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +8 -0
  145. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +47 -9
  146. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +6 -2
  147. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -4
  148. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +14 -2
  149. package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +8 -0
  150. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +18 -22
  151. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +32 -3
  152. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +11 -2
  153. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -1
  154. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +51 -3
  155. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +17 -3
  156. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -2
  157. package/src/runtime/agent/orchestrator/session/loop/tool-exec/after-hook.mjs +2 -1
  158. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +14 -4
  159. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
  160. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +16 -1
  161. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +10 -0
  162. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -10
  163. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +23 -5
  164. package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +60 -0
  165. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -2
  166. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +19 -1
  167. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +2 -0
  168. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +133 -90
  169. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +14 -0
  170. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -1
  171. package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +31 -4
  172. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -18
  173. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +23 -13
  174. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +11 -2
  175. package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +13 -0
  176. package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +129 -0
  177. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +20 -2
  178. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +2 -0
  179. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +8 -0
  180. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +1 -1
  181. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +13 -0
  182. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -2
  183. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +102 -15
  184. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +18 -5
  185. package/src/runtime/agent/orchestrator/session/message-ids.mjs +5 -3
  186. package/src/runtime/agent/orchestrator/session/message-reactions.mjs +89 -0
  187. package/src/runtime/agent/orchestrator/session/message-reactions.test-support.mjs +75 -0
  188. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +19 -8
  189. package/src/runtime/agent/orchestrator/session/prompt-cache-idle-gaps.mjs +113 -0
  190. package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +127 -0
  191. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +0 -2
  192. package/src/runtime/agent/orchestrator/session/routine-result.mjs +47 -11
  193. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +20 -2
  194. package/src/runtime/agent/orchestrator/session/sensitive-projection.mjs +100 -0
  195. package/src/runtime/agent/orchestrator/session/sensitive-values.mjs +257 -0
  196. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +20 -7
  197. package/src/runtime/agent/orchestrator/session/side-question.mjs +18 -0
  198. package/src/runtime/agent/orchestrator/session/skill-update-notice.mjs +60 -0
  199. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +6 -3
  200. package/src/runtime/agent/orchestrator/session/store/save-delta.mjs +9 -5
  201. package/src/runtime/agent/orchestrator/session/store/serialize.mjs +9 -8
  202. package/src/runtime/agent/orchestrator/session/store/session-persistence-events.mjs +29 -0
  203. package/src/runtime/agent/orchestrator/session/store-agent-worker-header-cache.mjs +85 -0
  204. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +133 -18
  205. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +12 -2
  206. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -14
  207. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +84 -3
  208. package/src/runtime/agent/orchestrator/session/stored-attachment-history.mjs +32 -0
  209. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +11 -5
  210. package/src/runtime/agent/orchestrator/session/thinking-binding.mjs +108 -0
  211. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +2 -2
  212. package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +2 -1
  213. package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +4 -0
  214. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +5 -0
  215. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +25 -2
  216. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +9 -7
  217. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-text.mjs +2 -2
  218. package/src/runtime/agent/orchestrator/session/turn-failure.mjs +5 -1
  219. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +5 -2
  220. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +12 -1
  221. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +240 -0
  222. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
  223. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/shell-cwd.mjs +38 -0
  224. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +25 -5
  225. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +65 -7
  226. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +7 -1
  227. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +36 -13
  228. package/src/runtime/browser-bridge/action-schema.mjs +13 -2
  229. package/src/runtime/browser-bridge/client.mjs +73 -8
  230. package/src/runtime/browser-bridge/input-fields.mjs +8 -9
  231. package/src/runtime/browser-bridge/mail-links.mjs +107 -0
  232. package/src/runtime/browser-bridge/tool-defs.mjs +1 -1
  233. package/src/runtime/channels/lib/config.mjs +6 -61
  234. package/src/runtime/channels/lib/crash-log.mjs +1 -1
  235. package/src/runtime/channels/lib/event-pipeline.mjs +4 -19
  236. package/src/runtime/channels/lib/event-queue.mjs +3 -18
  237. package/src/runtime/channels/lib/owned-runtime/automation-services.mjs +2 -2
  238. package/src/runtime/channels/lib/owned-runtime/automation-start.mjs +3 -4
  239. package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +4 -59
  240. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +33 -115
  241. package/src/runtime/channels/lib/owned-runtime/owned-stop.mjs +5 -22
  242. package/src/runtime/channels/lib/owned-runtime/state.mjs +2 -8
  243. package/src/runtime/channels/lib/owned-runtime.mjs +6 -23
  244. package/src/runtime/channels/lib/parent-bridge.mjs +2 -2
  245. package/src/runtime/channels/lib/runtime-paths.mjs +4 -43
  246. package/src/runtime/channels/lib/schedule-errors.mjs +51 -0
  247. package/src/runtime/channels/lib/scheduler.mjs +135 -154
  248. package/src/runtime/channels/lib/schedules-db.mjs +83 -11
  249. package/src/runtime/channels/lib/status-snapshot.mjs +1 -1
  250. package/src/runtime/channels/lib/tool-dispatch.mjs +14 -2
  251. package/src/runtime/channels/lib/webhooks-db.mjs +5 -9
  252. package/src/runtime/channels/lib/worker-bootstrap.mjs +0 -1
  253. package/src/runtime/channels/lib/worker-ipc.mjs +0 -1
  254. package/src/runtime/channels/lib/worker-main.mjs +19 -34
  255. package/src/runtime/computer-bridge/action-schema.mjs +42 -1
  256. package/src/runtime/computer-bridge/actions.mjs +9 -1
  257. package/src/runtime/computer-bridge/client.mjs +180 -8
  258. package/src/runtime/computer-bridge/core-actions.mjs +2 -2
  259. package/src/runtime/computer-bridge/error-recovery.mjs +27 -2
  260. package/src/runtime/computer-bridge/linux-readiness.mjs +65 -23
  261. package/src/runtime/computer-bridge/pending-continuation.mjs +1 -1
  262. package/src/runtime/connectors/connector-accounts-mcp.mjs +230 -0
  263. package/src/runtime/connectors/gmail-app-password.mjs +172 -39
  264. package/src/runtime/connectors/google-mcp.mjs +10 -12
  265. package/src/runtime/memory/lib/pg/adapter.mjs +77 -70
  266. package/src/runtime/memory/lib/pg/supervisor.mjs +24 -12
  267. package/src/runtime/memory/lib/runtime-fetcher.mjs +23 -5
  268. package/src/runtime/office/com/com-adapter.mjs +17 -1
  269. package/src/runtime/office/com/office-com-host.ps1 +51 -1
  270. package/src/runtime/office/pdf/cfb-fixture.mjs +328 -0
  271. package/src/runtime/office/pdf/cfb-protection.mjs +311 -0
  272. package/src/runtime/office/pdf/document-preview.mjs +326 -8
  273. package/src/runtime/office/pdf/ooxml-crypto.mjs +469 -0
  274. package/src/runtime/office/pdf/ooxml-key-worker.mjs +14 -0
  275. package/src/runtime/office/portable/portable-soffice.mjs +45 -5
  276. package/src/runtime/session-search/session-search-db.mjs +73 -9
  277. package/src/runtime/session-search/session-search-ingest.mjs +110 -20
  278. package/src/runtime/shared/abort-race.mjs +89 -0
  279. package/src/runtime/shared/agent-avatar.d.mts +0 -1
  280. package/src/runtime/shared/agent-kind.mjs +4 -3
  281. package/src/runtime/shared/bot-hops.mjs +62 -2
  282. package/src/runtime/shared/bounded-download.mjs +8 -3
  283. package/src/runtime/shared/config.mjs +51 -35
  284. package/src/runtime/shared/connector-accounts.mjs +20 -10
  285. package/src/runtime/shared/connector-catalog.d.mts +33 -2
  286. package/src/runtime/shared/connector-catalog.mjs +166 -8
  287. package/src/runtime/shared/connector-triggers.mjs +22 -19
  288. package/src/runtime/shared/defaults-separation.mjs +2 -14
  289. package/src/runtime/shared/delegated-approval-host.mjs +103 -11
  290. package/src/runtime/shared/developer-options.mjs +32 -1
  291. package/src/runtime/shared/err-text.mjs +24 -15
  292. package/src/runtime/shared/file-permissions.d.mts +0 -8
  293. package/src/runtime/shared/file-permissions.mjs +0 -11
  294. package/src/runtime/shared/git-direct.test-support.mjs +19 -0
  295. package/src/runtime/shared/google-oauth-client.generated.json +4 -0
  296. package/src/runtime/shared/google-oauth-client.mjs +34 -9
  297. package/src/runtime/shared/heartbeat-interval.d.mts +4 -0
  298. package/src/runtime/shared/heartbeat-interval.mjs +19 -0
  299. package/src/runtime/shared/idle-auto-clear-gate.mjs +104 -0
  300. package/src/runtime/shared/llm/anthropic-betas.mjs +7 -0
  301. package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +12 -0
  302. package/src/runtime/shared/llm/provider-catalog-registry.mjs +1 -0
  303. package/src/runtime/shared/llm/usage-rollup.mjs +1 -1
  304. package/src/runtime/shared/llm/well-formed.mjs +66 -0
  305. package/src/runtime/shared/mixy-presence.mjs +22 -0
  306. package/src/runtime/shared/orchestration.d.mts +2 -0
  307. package/src/runtime/shared/pg-schema-db.mjs +27 -16
  308. package/src/runtime/shared/profile-config.cjs +1 -1
  309. package/src/runtime/shared/prompt-origin.d.mts +4 -0
  310. package/src/runtime/shared/prompt-origin.mjs +21 -0
  311. package/src/runtime/shared/provider-accounts.mjs +231 -59
  312. package/src/runtime/shared/provider-api-key.mjs +19 -8
  313. package/src/runtime/shared/provider-display-name.mjs +29 -0
  314. package/src/runtime/shared/provider-timeout-phase.mjs +2 -0
  315. package/src/runtime/shared/routine-trigger.d.mts +2 -0
  316. package/src/runtime/shared/routine-trigger.mjs +52 -0
  317. package/src/runtime/shared/sensitive-path-rules.d.mts +2 -10
  318. package/src/runtime/shared/sensitive-path-rules.mjs +19 -41
  319. package/src/runtime/shared/session-activity-signal.mjs +34 -1
  320. package/src/runtime/shared/session-sidebar-host.d.mts +1 -1
  321. package/src/runtime/shared/session-sidebar-host.mjs +7 -3
  322. package/src/runtime/shared/session-turn-prelude.mjs +18 -0
  323. package/src/runtime/shared/tool-execution-contract.mjs +2 -0
  324. package/src/runtime/shared/tool-result-summary.mjs +1 -14
  325. package/src/runtime/shared/transcript-metadata.mjs +38 -0
  326. package/src/runtime/shared/user-data-guard.mjs +30 -2
  327. package/src/runtime/shared/worktrees/activity-gate.mjs +12 -5
  328. package/src/runtime/shared/worktrees/test-support.mjs +9 -4
  329. package/src/runtime/terminal-bridge/client.mjs +10 -1
  330. package/src/runtime/visualize-bridge/client.mjs +27 -8
  331. package/src/runtime/visualize-bridge/tool-defs.mjs +22 -8
  332. package/src/runtime/visualize-bridge/tool-meta.d.mts +5 -0
  333. package/src/runtime/visualize-bridge/tool-meta.mjs +22 -0
  334. package/src/runtime/web-search/lib/fetch-pipeline.mjs +2 -2
  335. package/src/session-runtime/agent-home-delivery.mjs +486 -179
  336. package/src/session-runtime/agent-room-delivery.mjs +28 -1
  337. package/src/session-runtime/agent-task-sessions.mjs +561 -199
  338. package/src/session-runtime/boot/apis.mjs +9 -0
  339. package/src/session-runtime/boot/begin.mjs +3 -2
  340. package/src/session-runtime/boot/infrastructure.mjs +4 -0
  341. package/src/session-runtime/boot/providers.mjs +3 -0
  342. package/src/session-runtime/boot/tools.mjs +8 -1
  343. package/src/session-runtime/bot-chain.mjs +32 -0
  344. package/src/session-runtime/bot-status.mjs +7 -4
  345. package/src/session-runtime/channel-config-api.mjs +63 -17
  346. package/src/session-runtime/config-lifecycle.mjs +6 -0
  347. package/src/session-runtime/connector-merge-migration.mjs +305 -0
  348. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +132 -28
  349. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +2 -0
  350. package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +45 -0
  351. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +72 -5
  352. package/src/session-runtime/internal-tool-executor.mjs +4 -0
  353. package/src/session-runtime/lifecycle/inheritance.mjs +16 -3
  354. package/src/session-runtime/lifecycle/session-catalog.mjs +3 -1
  355. package/src/session-runtime/lifecycle/teardown/runtime-stops.mjs +50 -4
  356. package/src/session-runtime/lifecycle/teardown.mjs +23 -5
  357. package/src/session-runtime/mcp-glue/server-catalog.mjs +61 -3
  358. package/src/session-runtime/mcp-oauth-login.mjs +5 -3
  359. package/src/session-runtime/mixy-heartbeat.mjs +927 -0
  360. package/src/session-runtime/model-route/route-tuning.mjs +10 -6
  361. package/src/session-runtime/notification-bus.mjs +31 -117
  362. package/src/session-runtime/notification-completion-queue.mjs +54 -0
  363. package/src/session-runtime/notification-listeners.mjs +76 -0
  364. package/src/session-runtime/owned-automation-run.mjs +35 -13
  365. package/src/session-runtime/prewarm/channel-start.mjs +3 -7
  366. package/src/session-runtime/provider-auth/login.mjs +7 -0
  367. package/src/session-runtime/provider-models/row-hydration.mjs +13 -2
  368. package/src/session-runtime/resource-mcp-api.mjs +143 -224
  369. package/src/session-runtime/resource-mcp-connector-accounts.mjs +163 -0
  370. package/src/session-runtime/resource-mcp-oauth-api.mjs +217 -0
  371. package/src/session-runtime/resource-mcp-secrets.mjs +82 -0
  372. package/src/session-runtime/resource-mcp-servers.mjs +78 -0
  373. package/src/session-runtime/resource-mcp-store.mjs +49 -0
  374. package/src/session-runtime/routine-home-turn.mjs +33 -6
  375. package/src/session-runtime/routine-owner.mjs +40 -9
  376. package/src/session-runtime/schedule-session-run.mjs +7 -3
  377. package/src/session-runtime/services/agent-tool/close-flow.mjs +3 -0
  378. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +8 -12
  379. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +7 -2
  380. package/src/session-runtime/services/agent-tool/tag-registry.mjs +1 -0
  381. package/src/session-runtime/services/agent-tool/worker-index/awaiting-user-sync.mjs +51 -0
  382. package/src/session-runtime/services/agent-tool/worker-index/disposal.mjs +55 -0
  383. package/src/session-runtime/services/agent-tool/worker-index/released-store.mjs +123 -0
  384. package/src/session-runtime/services/agent-tool/worker-index/restart-settlement.mjs +112 -0
  385. package/src/session-runtime/services/agent-tool/worker-index/row-reads.mjs +36 -0
  386. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -0
  387. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +17 -4
  388. package/src/session-runtime/services/agent-tool/worker-index/row-writes.mjs +58 -0
  389. package/src/session-runtime/services/agent-tool/worker-index.mjs +41 -241
  390. package/src/session-runtime/services/agent-tool/worker-rows.mjs +3 -0
  391. package/src/session-runtime/services/channel-admin.mjs +73 -31
  392. package/src/session-runtime/services/channel-session-router.mjs +4 -2
  393. package/src/session-runtime/services/channel-worker.mjs +1 -1
  394. package/src/session-runtime/services/provider-admin.mjs +128 -25
  395. package/src/session-runtime/services/schedule-access.mjs +14 -0
  396. package/src/session-runtime/services/seeds.mjs +0 -28
  397. package/src/session-runtime/services/usage-dashboard-model.mjs +1 -0
  398. package/src/session-runtime/session-compaction-sync.mjs +27 -0
  399. package/src/session-runtime/session-create/session-options.mjs +8 -4
  400. package/src/session-runtime/settings-builtin-tools-api.mjs +11 -0
  401. package/src/session-runtime/settings-compaction-api.mjs +2 -40
  402. package/src/session-runtime/settings-system-api.mjs +28 -1
  403. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +65 -0
  404. package/src/session-runtime/setup-tool/executor.mjs +187 -29
  405. package/src/session-runtime/setup-tool/extended-actions.mjs +33 -2
  406. package/src/session-runtime/setup-tool/session-route-actions.mjs +152 -0
  407. package/src/session-runtime/setup-tool/settings-contract.mjs +68 -6
  408. package/src/session-runtime/setup-tool/tool-defs.mjs +92 -6
  409. package/src/session-runtime/task-session-route.mjs +99 -0
  410. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +17 -13
  411. package/src/session-runtime/tool-policy/policy-stage.mjs +21 -4
  412. package/src/session-runtime/tool-policy/surface-helpers.mjs +2 -3
  413. package/src/session-runtime/tool-surface.mjs +4 -0
  414. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
  415. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +98 -21
  416. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +4 -0
  417. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -1
  418. package/src/session-runtime/workspace/transitions.mjs +10 -2
  419. package/src/standalone/channel-transport/client-registry/client-records.mjs +65 -0
  420. package/src/standalone/channel-transport/client-registry/client-registration.mjs +118 -0
  421. package/src/standalone/channel-transport/client-registry.mjs +24 -163
  422. package/src/standalone/channel-transport.mjs +1 -1
  423. package/src/standalone/daemon-channels-loader.mjs +10 -8
  424. package/src/standalone/daemon-session-control/list-page.mjs +72 -0
  425. package/src/standalone/daemon-session-control/request.mjs +51 -0
  426. package/src/standalone/daemon-session-control/room-host.mjs +57 -0
  427. package/src/standalone/daemon-session-control/route-control.mjs +48 -0
  428. package/src/standalone/daemon-session-control/send-turn.mjs +60 -0
  429. package/src/standalone/daemon-session-control/worktree-control.mjs +40 -0
  430. package/src/standalone/daemon-session-control.mjs +161 -157
  431. package/src/standalone/daemon.mjs +33 -2
  432. package/src/standalone/session-client.mjs +7 -3
  433. package/src/standalone/session-protocol.mjs +10 -0
  434. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +6 -2
  435. package/src/standalone/session-service/agent-tree/agent-child.mjs +1 -0
  436. package/src/standalone/session-service/quota-resume.mjs +3 -0
  437. package/src/standalone/session-service/retention/busy.mjs +65 -1
  438. package/src/standalone/session-service/session-calls/session-action.mjs +23 -2
  439. package/src/standalone/session-service/session-calls/session-turns.mjs +6 -0
  440. package/src/standalone/session-service/session-calls/session-views.mjs +8 -0
  441. package/src/standalone/session-service.mjs +13 -0
  442. package/src/standalone/session-tool-output.mjs +21 -10
  443. package/src/tui/app/model-options.mjs +4 -27
  444. package/src/tui/dist/index.mjs +81 -65
  445. package/src/tui/session/agent-job-feed/model-notification.mjs +3 -0
  446. package/src/tui/session/initial-state.mjs +3 -0
  447. package/src/tui/session/notice-surface.mjs +23 -1
  448. package/src/tui/session/queue-helpers.mjs +3 -42
  449. package/src/tui/session/session-api/integrations.mjs +1 -0
  450. package/src/tui/session/session-api/lifecycle.mjs +8 -0
  451. package/src/tui/session/session-api/routes-agent-definitions.mjs +14 -2
  452. package/src/tui/session/session-api/routes-write.mjs +5 -1
  453. package/src/tui/session/session-api/routes.mjs +1 -0
  454. package/src/tui/session/session-api/settings.mjs +30 -2
  455. package/src/tui/session/session-flow/auto-clear.mjs +4 -33
  456. package/src/tui/session/session-flow/drain-closed-queue.mjs +38 -0
  457. package/src/tui/session/session-flow/drain-enqueue.mjs +86 -0
  458. package/src/tui/session/session-flow/drain-retry.mjs +35 -0
  459. package/src/tui/session/session-flow/drain.mjs +118 -149
  460. package/src/tui/session/session-flow/queue/entry-shape.mjs +5 -0
  461. package/src/tui/session/session-flow/queue/take-entries.mjs +5 -2
  462. package/src/tui/session/tool-approval-delegation.mjs +64 -0
  463. package/src/tui/session/tool-approval-request.mjs +39 -0
  464. package/src/tui/session/tool-approval-settle.mjs +62 -0
  465. package/src/tui/session/tool-approval.mjs +69 -115
  466. package/src/tui/session/tui-steering-persist.mjs +8 -1
  467. package/src/tui/session/turn-stream.mjs +36 -3
  468. package/src/tui/session/turn.mjs +23 -2
  469. package/src/vendor/statusline/bin/statusline-route.mjs +1 -219
  470. package/src/vendor/statusline/src/gateway/route-meta.mjs +35 -13
  471. package/scripts/code-graph-description-contract.mjs +0 -188
  472. package/src/runtime/channels/lib/owned-runtime/ownership-refresh.mjs +0 -36
@@ -158,8 +158,9 @@ function runtimePaths(verDir) {
158
158
  // Download with retry
159
159
  // ---------------------------------------------------------------------------
160
160
 
161
- async function downloadWithRetry(url, destPath, expectedBytes) {
161
+ async function downloadWithRetry(url, destPath, expectedBytes, signal) {
162
162
  return downloadToFileWithRetry(url, destPath, {
163
+ signal,
163
164
  maxBytes: expectedBytes,
164
165
  expectedBytes,
165
166
  label: 'memory runtime download',
@@ -532,11 +533,12 @@ function publishRuntimeDir(runtimeBaseDir, stagingDir, key, sha256) {
532
533
  * staging lock, ending with the stale-version GC. Returns the installed
533
534
  * runtime-{version} directory.
534
535
  */
535
- async function installRuntimeVersion({ runtimeBaseDir, url, sha256, size, version, pkey }) {
536
+ async function installRuntimeVersion({ runtimeBaseDir, url, sha256, size, version, pkey, signal }) {
536
537
  // tar is only required for the download/extract path. Probe here (not at
537
538
  // ensureRuntime entry) so a machine without tar can still reuse an
538
539
  // already-extracted, sha-matching cached runtime via the fast path.
539
540
  await probeTar();
541
+ signal?.throwIfAborted();
540
542
 
541
543
  __mixdogMemoryLog(`[runtime-fetcher] downloading runtime ${version} for ${pkey} (~${size} bytes) …\n`);
542
544
 
@@ -566,7 +568,7 @@ async function installRuntimeVersion({ runtimeBaseDir, url, sha256, size, versio
566
568
  `[runtime-fetcher] retrying runtime download from fallback release repo — ${candidateUrl}\n`
567
569
  );
568
570
  }
569
- await downloadWithRetry(candidateUrl, tarPath, size);
571
+ await downloadWithRetry(candidateUrl, tarPath, size, signal);
570
572
  await verifySha256(tarPath, sha256);
571
573
  downloadOk = true;
572
574
  break;
@@ -575,9 +577,12 @@ async function installRuntimeVersion({ runtimeBaseDir, url, sha256, size, versio
575
577
  try {
576
578
  rmSync(tarPath, { force: true });
577
579
  } catch {}
580
+ // A cancelled install tries no fallback repository.
581
+ signal?.throwIfAborted();
578
582
  }
579
583
  }
580
584
  if (!downloadOk && lastDownloadErr) throw lastDownloadErr;
585
+ signal?.throwIfAborted();
581
586
  await _extractRuntimeTarGz(tarPath, stagingDir, stagingDir);
582
587
  } finally {
583
588
  try {
@@ -591,6 +596,11 @@ async function installRuntimeVersion({ runtimeBaseDir, url, sha256, size, versio
591
596
  } catch {}
592
597
  throw new Error(`[runtime-fetcher] download or verify failed for ${version}`);
593
598
  }
599
+ // Cancelled after the extract: drop the unpublished tree, publish nothing.
600
+ if (signal?.aborted) {
601
+ rmSync(stagingDir, { recursive: true, force: true });
602
+ signal.throwIfAborted();
603
+ }
594
604
 
595
605
  // Stamp sha256 inside staging dir.
596
606
  writeFileAtomicSync(join(stagingDir, '.version-sha256'), sha256);
@@ -615,6 +625,11 @@ async function installRuntimeVersion({ runtimeBaseDir, url, sha256, size, versio
615
625
  gcRuntimeDir(runtimeBaseDir, key);
616
626
  } finally {
617
627
  releaseStagingLock(stagingLock);
628
+ // Publishing is synchronous past the last cancellation check, so an
629
+ // aborted signal here means nothing was published.
630
+ if (signal?.aborted) {
631
+ __mixdogMemoryLog(`[runtime-fetcher] install of ${version} cancelled; its staging was removed\n`);
632
+ }
618
633
  }
619
634
 
620
635
  __mixdogMemoryLog(`[runtime-fetcher] runtime ready at ${verDir}\n`);
@@ -631,7 +646,10 @@ export const _runtimeLayoutInternals = {
631
646
  runtimeInstallKey,
632
647
  };
633
648
 
634
- export async function ensureRuntime(dataDir) {
649
+ /** `signal` cancels a download/install in progress: its retry waits end, its
650
+ * partial files and staging lock are removed and nothing is published, so the
651
+ * next call starts a fresh install. */
652
+ export async function ensureRuntime(dataDir, { signal = null } = {}) {
635
653
  const key = resolve(dataDir);
636
654
  if (runtimeCache.has(key)) return runtimeCache.get(key);
637
655
 
@@ -655,7 +673,7 @@ export async function ensureRuntime(dataDir) {
655
673
 
656
674
  const verDir =
657
675
  cachedRuntimeDir(runtimeBaseDir, version, sha256) ??
658
- (await installRuntimeVersion({ runtimeBaseDir, url, sha256, size, version, pkey }));
676
+ (await installRuntimeVersion({ runtimeBaseDir, url, sha256, size, version, pkey, signal }));
659
677
 
660
678
  const result = { runtimeDir: verDir, ...runtimePaths(verDir), version };
661
679
  runtimeCache.set(key, result);
@@ -39,6 +39,15 @@ function spawnPowerShell(script) {
39
39
  );
40
40
  }
41
41
 
42
+ // Resolves once the host process is gone. A signal-terminated child has exitCode null but signalCode set,
43
+ // and its 'close' may already have been emitted, so both are checked before waiting for the event.
44
+ export function hostExitSignal(child) {
45
+ return new Promise((done) => {
46
+ if (child.exitCode !== null || child.signalCode !== null) done();
47
+ else child.once('close', () => done());
48
+ });
49
+ }
50
+
42
51
  async function callMicrosoftOfficeOnce(payload, { timeoutMs = DEFAULT_TIMEOUT_MS, signal = null } = {}) {
43
52
  return await new Promise((resolve) => {
44
53
  const child = spawnPowerShell(HOST_SCRIPT);
@@ -65,13 +74,20 @@ async function callMicrosoftOfficeOnce(payload, { timeoutMs = DEFAULT_TIMEOUT_MS
65
74
  },
66
75
  };
67
76
  void drainSessionClient(client, error).then((cleanup) => {
68
- finish({
77
+ const result = {
69
78
  ok: false,
70
79
  cancelled,
71
80
  error: officeCleanupError(error, cleanup),
72
81
  operationMayHaveCompleted: true,
73
82
  cleanup,
83
+ };
84
+ // Settles when the host process is gone, which may be after this result when the
85
+ // grace period lapsed: callers that own files the host holds open wait on it.
86
+ // Non-enumerable so the result still serializes unchanged.
87
+ Object.defineProperty(result, 'hostExit', {
88
+ value: hostExitSignal(child),
74
89
  });
90
+ finish(result);
75
91
  });
76
92
  };
77
93
  const onAbort = () => beginCleanup('Microsoft Office operation was cancelled', true);
@@ -7545,6 +7545,52 @@ function Open-ValidationDocument($app, [string]$format, [string]$path) {
7545
7545
  }
7546
7546
  }
7547
7547
 
7548
+ $script:PreviewPasswordMessage = 'This file is password-protected. (OFFICE_PREVIEW_PASSWORD_PROTECTED)'
7549
+
7550
+ # An encrypted Office Open XML package is an OLE container (D0 CF 11 E0), not a ZIP. Office would stop to ask
7551
+ # for the password, and a hidden application cannot show the question, so the request is refused up front.
7552
+ function Assert-PreviewNotEncrypted([string]$path) {
7553
+ $extension = [System.IO.Path]::GetExtension($path).TrimStart('.').ToLowerInvariant()
7554
+ if (@('docx', 'docm', 'dotx', 'xlsx', 'xlsm', 'pptx', 'pptm') -notcontains $extension) { return }
7555
+ $header = New-Object byte[] 4
7556
+ $stream = [System.IO.File]::Open($path, 'Open', 'Read', 'ReadWrite')
7557
+ try { $read = $stream.Read($header, 0, 4) } finally { $stream.Dispose() }
7558
+ if ($read -eq 4 -and $header[0] -eq 0xD0 -and $header[1] -eq 0xCF -and $header[2] -eq 0x11 -and $header[3] -eq 0xE0) {
7559
+ throw $script:PreviewPasswordMessage
7560
+ }
7561
+ }
7562
+
7563
+ $script:PreviewWrongPasswordMessage = "That password didn't work. (OFFICE_PREVIEW_WRONG_PASSWORD)"
7564
+
7565
+ # The read-only open of the file preview. Word and Excel get the user's password when the request carries one
7566
+ # (Documents.Open PasswordDocument, Workbooks.Open Password), else a placeholder, so a protected legacy file fails
7567
+ # at once instead of waiting on a prompt nobody can see. Word's "password is incorrect" (0x800A1520) becomes the
7568
+ # plain message: the protected one without a password, the wrong-password one with it. Presentations.Open takes no
7569
+ # password, so PowerPoint never gets one. Links are not updated, macros stay off and DisplayAlerts is off
7570
+ # (New-HiddenApplication). Automation opens do not go through Protected View.
7571
+ function Open-PreviewDocument($app, [string]$format, [string]$path, [string]$password) {
7572
+ $placeholder = 'mixdog-preview-no-password'
7573
+ $openPassword = if ($password) { $password } else { $placeholder }
7574
+ try {
7575
+ switch ($format) {
7576
+ 'docx' { return $app.Documents.Open($path, $false, $true, $false, $openPassword, $placeholder, $false, $placeholder, $placeholder) }
7577
+ # No WriteResPassword: Excel rejects one for a workbook without write reservation (0x800A03EC), and a
7578
+ # read-only open never asks for it.
7579
+ 'xlsx' { return $app.Workbooks.Open($path, 0, $true, [Type]::Missing, $openPassword, [Type]::Missing, $true) }
7580
+ 'pptx' { return $app.Presentations.Open($path, $true, $true, $false) }
7581
+ }
7582
+ }
7583
+ catch {
7584
+ $inner = $_.Exception
7585
+ while ($null -ne $inner.InnerException) { $inner = $inner.InnerException }
7586
+ if ($format -eq 'docx' -and $inner.HResult -eq 0x800A1520) {
7587
+ if ($password) { throw $script:PreviewWrongPasswordMessage }
7588
+ throw $script:PreviewPasswordMessage
7589
+ }
7590
+ throw
7591
+ }
7592
+ }
7593
+
7548
7594
  function Validate-NativeDocument([string]$path, [string]$format) {
7549
7595
  $validationApp = $null
7550
7596
  $validationDocument = $null
@@ -7757,9 +7803,13 @@ try {
7757
7803
  $live = $true
7758
7804
  }
7759
7805
  else {
7806
+ if ($payload.readOnly -eq $true) { Assert-PreviewNotEncrypted $path }
7760
7807
  $app = New-HiddenApplication $format $progId
7761
7808
  $createdApp = $true
7762
- $document = Open-BackgroundDocument $app $format $path
7809
+ # A read-only request (the file preview) opens the document read-only, with no link updates and no
7810
+ # recalculation, and never waits on a password prompt. The close below discards, so nothing can be
7811
+ # written back.
7812
+ $document = if ($payload.readOnly -eq $true) { Open-PreviewDocument $app $format $path ([string]$payload.password) } else { Open-BackgroundDocument $app $format $path }
7763
7813
  }
7764
7814
 
7765
7815
  switch ([string]$payload.action) {
@@ -0,0 +1,328 @@
1
+ // A minimal Compound File Binary ([MS-CFB]) writer for tests: real container structure (header, FAT, DIFAT, mini
2
+ // FAT, mini stream, directory with sibling trees), laid out the way Office writes it.
3
+ import { createCipheriv, createHash, createHmac, randomBytes } from 'node:crypto';
4
+
5
+ const END_OF_CHAIN = 0xfffffffe;
6
+ const FAT_SECTOR = 0xfffffffd;
7
+ const DIFAT_SECTOR = 0xfffffffc;
8
+ const NO_STREAM = 0xffffffff;
9
+ const MINI_SECTOR_SIZE = 64;
10
+ const MINI_STREAM_CUTOFF = 4096;
11
+ const HEADER_DIFAT_ENTRIES = 109;
12
+
13
+ /**
14
+ * Entries under the root storage: `{ name, data }` is a stream, `{ name, children }` a storage of further entries.
15
+ * `version` is the CFB major version: 3 (512-byte sectors) or 4 (4096-byte sectors).
16
+ */
17
+ export function buildCfb(entries, { version = 3 } = {}) {
18
+ const sectorSize = version === 4 ? 4096 : 512;
19
+ const entriesPerSector = sectorSize / 4;
20
+ const root = { name: 'Root Entry', type: 5, children: entries };
21
+
22
+ // Directory ids in storage order, root first; the siblings under each storage form one balanced tree.
23
+ const nodes = [];
24
+ const register = (node) => {
25
+ node.id = nodes.length;
26
+ nodes.push(node);
27
+ for (const child of node.children ?? []) register(child);
28
+ };
29
+ register(root);
30
+ const link = (siblings) => {
31
+ const ordered = [...siblings].sort(
32
+ (a, b) => a.name.length - b.name.length || (a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1)
33
+ );
34
+ const build = (low, high) => {
35
+ if (low > high) return NO_STREAM;
36
+ const middle = (low + high) >> 1;
37
+ const node = ordered[middle];
38
+ node.left = build(low, middle - 1);
39
+ node.right = build(middle + 1, high);
40
+ return node.id;
41
+ };
42
+ return build(0, ordered.length - 1);
43
+ };
44
+ for (const node of nodes) {
45
+ node.left ??= NO_STREAM;
46
+ node.right ??= NO_STREAM;
47
+ node.type ??= node.children ? 1 : 2;
48
+ if (node.children) node.child = link(node.children);
49
+ }
50
+
51
+ const sectors = [];
52
+ const fat = [];
53
+ const allocate = (bytes) => {
54
+ const count = Math.ceil(bytes.length / sectorSize);
55
+ const start = sectors.length;
56
+ for (let i = 0; i < count; i++) {
57
+ const sector = Buffer.alloc(sectorSize);
58
+ bytes.copy(sector, 0, i * sectorSize, (i + 1) * sectorSize);
59
+ sectors.push(sector);
60
+ fat.push(i === count - 1 ? END_OF_CHAIN : sectors.length);
61
+ }
62
+ return count ? start : END_OF_CHAIN;
63
+ };
64
+
65
+ const miniStreams = [];
66
+ const miniFat = [];
67
+ for (const node of nodes) {
68
+ if (node.type !== 2) continue;
69
+ node.size = node.data.length;
70
+ if (node.size >= MINI_STREAM_CUTOFF) {
71
+ node.start = allocate(node.data);
72
+ } else if (node.size === 0) {
73
+ node.start = END_OF_CHAIN;
74
+ } else {
75
+ const count = Math.ceil(node.size / MINI_SECTOR_SIZE);
76
+ node.start = miniFat.length;
77
+ for (let i = 0; i < count; i++) miniFat.push(i === count - 1 ? END_OF_CHAIN : miniFat.length + 1);
78
+ miniStreams.push(Buffer.concat([node.data, Buffer.alloc(count * MINI_SECTOR_SIZE - node.size)]));
79
+ }
80
+ }
81
+ const miniStream = Buffer.concat(miniStreams);
82
+ root.size = miniStream.length;
83
+ root.start = allocate(miniStream);
84
+
85
+ const miniFatBytes = Buffer.alloc(Math.ceil(miniFat.length / entriesPerSector) * entriesPerSector * 4, 0xff);
86
+ miniFat.forEach((next, i) => {
87
+ miniFatBytes.writeUInt32LE(next, i * 4);
88
+ });
89
+ const miniFatStart = allocate(miniFatBytes);
90
+
91
+ const directoryBytes = Buffer.alloc(Math.ceil(nodes.length / (sectorSize / 128)) * sectorSize);
92
+ directoryBytes.fill(0xff, 0, directoryBytes.length);
93
+ nodes.forEach((node, i) => {
94
+ const at = i * 128;
95
+ directoryBytes.fill(0, at, at + 128);
96
+ directoryBytes.write(node.name, at, 'utf16le');
97
+ directoryBytes.writeUInt16LE((node.name.length + 1) * 2, at + 64);
98
+ directoryBytes[at + 66] = node.type;
99
+ directoryBytes[at + 67] = 1;
100
+ directoryBytes.writeUInt32LE(node.left, at + 68);
101
+ directoryBytes.writeUInt32LE(node.right, at + 72);
102
+ directoryBytes.writeUInt32LE(node.child ?? NO_STREAM, at + 76);
103
+ directoryBytes.writeUInt32LE(node.start ?? 0, at + 116);
104
+ directoryBytes.writeUInt32LE(node.size ?? 0, at + 120);
105
+ });
106
+ // Entries past the last one are unallocated: type 0, with every id unset.
107
+ for (let at = nodes.length * 128; at < directoryBytes.length; at += 128) directoryBytes.fill(0, at, at + 68);
108
+ const directoryStart = allocate(directoryBytes);
109
+
110
+ // The FAT describes every sector including its own and the DIFAT's, so their counts settle together.
111
+ let fatCount = 0;
112
+ let difatCount = 0;
113
+ while (fatCount * entriesPerSector < fat.length + fatCount + difatCount) {
114
+ fatCount++;
115
+ difatCount =
116
+ fatCount > HEADER_DIFAT_ENTRIES ? Math.ceil((fatCount - HEADER_DIFAT_ENTRIES) / (entriesPerSector - 1)) : 0;
117
+ }
118
+ const fatSectorIds = Array.from({ length: fatCount }, (_, i) => sectors.length + i);
119
+ const difatSectorIds = Array.from({ length: difatCount }, (_, i) => sectors.length + fatCount + i);
120
+ const fatEntries = [...fat, ...fatSectorIds.map(() => FAT_SECTOR), ...difatSectorIds.map(() => DIFAT_SECTOR)];
121
+ const fatBytes = Buffer.alloc(fatCount * sectorSize, 0xff);
122
+ fatEntries.forEach((next, i) => {
123
+ fatBytes.writeUInt32LE(next, i * 4);
124
+ });
125
+ for (let i = 0; i < fatCount; i++) sectors.push(fatBytes.subarray(i * sectorSize, (i + 1) * sectorSize));
126
+ const beyondHeader = fatSectorIds.slice(HEADER_DIFAT_ENTRIES);
127
+ for (let i = 0; i < difatCount; i++) {
128
+ const sector = Buffer.alloc(sectorSize, 0xff);
129
+ beyondHeader.slice(i * (entriesPerSector - 1), (i + 1) * (entriesPerSector - 1)).forEach((id, j) => {
130
+ sector.writeUInt32LE(id, j * 4);
131
+ });
132
+ sector.writeUInt32LE(i === difatCount - 1 ? END_OF_CHAIN : difatSectorIds[i + 1], sectorSize - 4);
133
+ sectors.push(sector);
134
+ }
135
+
136
+ const header = Buffer.alloc(sectorSize);
137
+ Buffer.from([0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1]).copy(header, 0);
138
+ header.writeUInt16LE(0x3e, 24);
139
+ header.writeUInt16LE(version, 26);
140
+ header.writeUInt16LE(0xfffe, 28);
141
+ header.writeUInt16LE(version === 4 ? 12 : 9, 30);
142
+ header.writeUInt16LE(6, 32);
143
+ header.writeUInt32LE(version === 4 ? directoryBytes.length / sectorSize : 0, 40);
144
+ header.writeUInt32LE(fatCount, 44);
145
+ header.writeUInt32LE(directoryStart, 48);
146
+ header.writeUInt32LE(MINI_STREAM_CUTOFF, 56);
147
+ header.writeUInt32LE(miniFatStart, 60);
148
+ header.writeUInt32LE(miniFatBytes.length / sectorSize, 64);
149
+ header.writeUInt32LE(difatCount ? difatSectorIds[0] : END_OF_CHAIN, 68);
150
+ header.writeUInt32LE(difatCount, 72);
151
+ header.fill(0xff, 76, 512);
152
+ fatSectorIds.slice(0, HEADER_DIFAT_ENTRIES).forEach((id, i) => {
153
+ header.writeUInt32LE(id, 76 + i * 4);
154
+ });
155
+ return Buffer.concat([header, ...sectors]);
156
+ }
157
+
158
+ /** The root-level streams of an encrypted OOXML document ([MS-OFFCRYPTO] 2.3.4), with the given ciphertext size. */
159
+ export function encryptedOoxmlEntries(packageBytes = 5000) {
160
+ return [
161
+ { name: 'EncryptionInfo', data: Buffer.from([4, 0, 4, 0, 0x40, 0, 0, 0, ...Buffer.from('<encryption/>')]) },
162
+ { name: 'EncryptedPackage', data: Buffer.concat([Buffer.alloc(8), Buffer.alloc(packageBytes, 0x5a)]) },
163
+ {
164
+ name: '\u0006DataSpaces',
165
+ children: [
166
+ { name: 'Version', data: Buffer.alloc(76) },
167
+ { name: 'DataSpaceMap', data: Buffer.alloc(112) },
168
+ ],
169
+ },
170
+ ];
171
+ }
172
+
173
+ const sha = (algorithm, ...parts) => {
174
+ const digest = createHash(algorithm);
175
+ for (const part of parts) digest.update(part);
176
+ return digest.digest();
177
+ };
178
+ const le32 = (value) => {
179
+ const bytes = Buffer.alloc(4);
180
+ bytes.writeUInt32LE(value, 0);
181
+ return bytes;
182
+ };
183
+ const padTo = (bytes, block) => Buffer.concat([bytes, Buffer.alloc((block - (bytes.length % block)) % block)]);
184
+ const aes = (mode, key, iv, data) => {
185
+ const cipher = createCipheriv(`aes-${key.length * 8}-${mode}`, key, iv);
186
+ cipher.setAutoPadding(false);
187
+ return Buffer.concat([cipher.update(data), cipher.final()]);
188
+ };
189
+ const spun = (algorithm, salt, password, count) => {
190
+ let value = sha(algorithm, salt, Buffer.from(password, 'utf16le'));
191
+ for (let i = 0; i < count; i++) value = sha(algorithm, le32(i), value);
192
+ return value;
193
+ };
194
+ const withSize = (plain, ciphertext) => {
195
+ const size = Buffer.alloc(8);
196
+ size.writeBigUInt64LE(BigInt(plain.length), 0);
197
+ return Buffer.concat([size, ciphertext]);
198
+ };
199
+
200
+ // 2.3.4.10 HashAlgorithm values the fixture writes, with their OpenSSL names.
201
+ const AGILE_HASHES = { 'SHA-1': 'sha1', SHA1: 'sha1', SHA256: 'sha256', SHA384: 'sha384', SHA512: 'sha512' };
202
+ // Truncated to, or padded with 0x36 up to, `length` bytes (2.3.4.11, 2.3.4.12).
203
+ const fitted = (bytes, length) =>
204
+ bytes.length >= length
205
+ ? bytes.subarray(0, length)
206
+ : Buffer.concat([bytes, Buffer.alloc(length - bytes.length, 0x36)]);
207
+
208
+ /**
209
+ * The root-level streams of `plain` encrypted with `password` the way Office writes it, following [MS-OFFCRYPTO]
210
+ * in the encrypting direction: agile encryption (2.3.4.10–2.3.4.15, AES-256-CBC, SHA-512 or `hashAlgorithm`) by
211
+ * default, or standard encryption (2.3.4.5–2.3.4.9, AES-128-ECB, SHA-1). `spinCount` is lowered for agile tests to
212
+ * keep them fast. The keyData salt is `keySaltSize` bytes. Agile encryption carries dataIntegrity (2.3.4.14) unless
213
+ * `integrity` is false, its HMAC key a salt of `hmacSaltSize` bytes: hashSize, as Excel writes it, unless given.
214
+ */
215
+ export function encryptOoxmlEntries(
216
+ plain,
217
+ password,
218
+ {
219
+ scheme = 'agile',
220
+ spinCount = 1000,
221
+ hashAlgorithm = 'SHA512',
222
+ keySaltSize = 16,
223
+ integrity = true,
224
+ hmacSaltSize,
225
+ } = {}
226
+ ) {
227
+ let encryptionInfo;
228
+ let encryptedPackage;
229
+ if (scheme === 'agile') {
230
+ const algorithm = AGILE_HASHES[hashAlgorithm];
231
+ const hashSize = sha(algorithm).length;
232
+ const secret = randomBytes(32);
233
+ const keySalt = randomBytes(keySaltSize);
234
+ const passwordSalt = randomBytes(16);
235
+ const hashed = spun(algorithm, passwordSalt, password, spinCount);
236
+ const keyFor = (block) => fitted(sha(algorithm, hashed, Buffer.from(block)), 32);
237
+ const verifierInput = randomBytes(16);
238
+ const encrypted = (block, data) => aes('cbc', keyFor(block), passwordSalt, padTo(data, 16)).toString('base64');
239
+ const segments = [];
240
+ for (let offset = 0, index = 0; offset < plain.length; offset += 4096, index++) {
241
+ const iv = fitted(sha(algorithm, keySalt, le32(index)), 16);
242
+ segments.push(aes('cbc', secret, iv, padTo(plain.subarray(offset, offset + 4096), 16)));
243
+ }
244
+ encryptedPackage = withSize(plain, Buffer.concat(segments));
245
+ // 2.3.4.14: an HMAC of the whole stream keyed by a random salt, both encrypted with the package key.
246
+ const sealed = (block, data) =>
247
+ aes('cbc', secret, fitted(sha(algorithm, keySalt, Buffer.from(block)), 16), padTo(data, 16)).toString('base64');
248
+ const hmacSalt = randomBytes(hmacSaltSize ?? hashSize);
249
+ const hmac = createHmac(algorithm, hmacSalt).update(encryptedPackage).digest();
250
+ const dataIntegrity = integrity
251
+ ? `<dataIntegrity encryptedHmacKey="${sealed([0x5f, 0xb2, 0xad, 0x01, 0x0c, 0xb9, 0xe1, 0xf6], hmacSalt)}"` +
252
+ ` encryptedHmacValue="${sealed([0xa0, 0x67, 0x7f, 0x02, 0xb2, 0x2c, 0x84, 0x33], hmac)}"/>`
253
+ : '';
254
+ const cipher = `blockSize="16" keyBits="256" hashSize="${hashSize}" cipherAlgorithm="AES" cipherChaining="ChainingModeCBC" hashAlgorithm="${hashAlgorithm}"`;
255
+ const xml =
256
+ `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r\n` +
257
+ `<encryption xmlns="http://schemas.microsoft.com/office/2006/encryption" xmlns:p="http://schemas.microsoft.com/office/2006/keyEncryptor/password">` +
258
+ `<keyData saltSize="${keySaltSize}" ${cipher} saltValue="${keySalt.toString('base64')}"/>` +
259
+ dataIntegrity +
260
+ `<keyEncryptors><keyEncryptor uri="http://schemas.microsoft.com/office/2006/keyEncryptor/password">` +
261
+ `<p:encryptedKey spinCount="${spinCount}" saltSize="16" ${cipher} saltValue="${passwordSalt.toString('base64')}"` +
262
+ ` encryptedVerifierHashInput="${encrypted([0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79], verifierInput)}"` +
263
+ ` encryptedVerifierHashValue="${encrypted([0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x34, 0x4e], sha(algorithm, verifierInput))}"` +
264
+ ` encryptedKeyValue="${encrypted([0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6], secret)}"/>` +
265
+ `</keyEncryptor></keyEncryptors></encryption>`;
266
+ encryptionInfo = Buffer.concat([Buffer.from([4, 0, 4, 0, 0x40, 0, 0, 0]), Buffer.from(xml, 'utf8')]);
267
+ } else {
268
+ const salt = randomBytes(16);
269
+ const final = sha('sha1', spun('sha1', salt, password, 50_000), le32(0));
270
+ const derive = (fill) => {
271
+ const buffer = Buffer.alloc(64, fill);
272
+ for (let i = 0; i < final.length; i++) buffer[i] ^= final[i];
273
+ return sha('sha1', buffer);
274
+ };
275
+ const key = Buffer.concat([derive(0x36), derive(0x5c)]).subarray(0, 16);
276
+ const verifier = randomBytes(16);
277
+ const csp = Buffer.from('Microsoft Enhanced RSA and AES Cryptographic Provider\0', 'utf16le');
278
+ const header = Buffer.concat([...[0x24, 0, 0x660e, 0x8004, 128, 0x18, 0, 0].map(le32), csp]);
279
+ encryptionInfo = Buffer.concat([
280
+ Buffer.from([3, 0, 2, 0]),
281
+ le32(0x24),
282
+ le32(header.length),
283
+ header,
284
+ le32(16),
285
+ salt,
286
+ aes('ecb', key, null, verifier),
287
+ le32(20),
288
+ aes('ecb', key, null, padTo(sha('sha1', verifier), 16)),
289
+ ]);
290
+ encryptedPackage = withSize(plain, aes('ecb', key, null, padTo(plain, 16)));
291
+ }
292
+ return [
293
+ { name: 'EncryptionInfo', data: encryptionInfo },
294
+ { name: 'EncryptedPackage', data: encryptedPackage },
295
+ {
296
+ name: '\u0006DataSpaces',
297
+ children: [
298
+ { name: 'Version', data: Buffer.alloc(76) },
299
+ { name: 'DataSpaceMap', data: Buffer.alloc(112) },
300
+ ],
301
+ },
302
+ ];
303
+ }
304
+
305
+ /** A binary Word document's "WordDocument" stream whose FibBase ([MS-DOC] 2.5.1) has fEncrypted set or clear. */
306
+ export function wordDocumentStream(encrypted) {
307
+ const data = Buffer.alloc(6000, 3);
308
+ data.writeUInt16LE(0xa5ec, 0);
309
+ data.writeUInt16LE(0x00c1, 2);
310
+ data.writeUInt16LE(encrypted ? 0x0100 | 0x0200 : 0x0200, 10);
311
+ return { name: 'WordDocument', data };
312
+ }
313
+
314
+ /** A "Current User" stream holding a CurrentUserAtom ([MS-PPT] 2.3.2) with this headerToken. */
315
+ export function currentUserStream(headerToken, userName = 'Mixdog') {
316
+ const name = Buffer.from(userName, 'latin1');
317
+ const atom = Buffer.alloc(28 + name.length + 4);
318
+ atom.writeUInt16LE(0x0ff6, 2);
319
+ atom.writeUInt32LE(atom.length - 8, 4);
320
+ atom.writeUInt32LE(0x14, 8);
321
+ atom.writeUInt32LE(headerToken, 12);
322
+ atom.writeUInt16LE(name.length, 20);
323
+ atom.writeUInt16LE(0x03f4, 22);
324
+ atom[24] = 3;
325
+ name.copy(atom, 28);
326
+ atom.writeUInt32LE(8, 28 + name.length);
327
+ return { name: 'Current User', data: atom };
328
+ }