mixdog 1.2.2 → 1.2.3

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 (415) hide show
  1. package/README.md +18 -0
  2. package/package.json +2 -1
  3. package/scripts/provider-pingpong-live.mjs +509 -0
  4. package/src/agents/advisor/AGENT.md +1 -1
  5. package/src/agents/advisor/agent.json +1 -1
  6. package/src/defaults/shipped-history.json +5 -0
  7. package/src/defaults/skills/setup/SKILL.md +6 -1
  8. package/src/defaults/skills/setup/references/actions.md +44 -22
  9. package/src/defaults/skills/setup/references/surfaces.md +10 -8
  10. package/src/headless-exec.mjs +15 -8
  11. package/src/lib/keychain-cjs.cjs +7 -4
  12. package/src/output-styles/common.md +11 -10
  13. package/src/rules/agent/AGENT.md +5 -5
  14. package/src/rules/lead/LEAD.md +5 -5
  15. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +92 -61
  16. package/src/runtime/agent/orchestrator/cache-mtime.mjs +10 -1
  17. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +7 -0
  18. package/src/runtime/agent/orchestrator/config.mjs +5 -0
  19. package/src/runtime/agent/orchestrator/context/collect.mjs +1 -0
  20. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +26 -7
  21. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +112 -7
  22. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +48 -20
  23. package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +2 -0
  24. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +24 -17
  25. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +7 -6
  26. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  27. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +29 -10
  28. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-blocks.mjs +20 -1
  29. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-delta.mjs +15 -2
  30. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +11 -4
  31. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/message-events.mjs +3 -0
  32. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +12 -5
  33. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-outcome.mjs +1 -0
  34. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-state.mjs +14 -0
  35. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -0
  36. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +9 -0
  37. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +6 -0
  38. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +58 -7
  39. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +29 -10
  40. package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +16 -3
  41. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +10 -61
  42. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +12 -6
  43. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  44. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +114 -72
  45. package/src/runtime/agent/orchestrator/providers/compat-request-policy.mjs +78 -2
  46. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +37 -5
  47. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  48. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +1 -0
  49. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +43 -2
  50. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +3 -0
  51. package/src/runtime/agent/orchestrator/providers/cursor.mjs +9 -2
  52. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +64 -1
  53. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +70 -6
  54. package/src/runtime/agent/orchestrator/providers/custom-reasoning.mjs +34 -7
  55. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +32 -6
  56. package/src/runtime/agent/orchestrator/providers/deepseek.mjs +67 -0
  57. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +3 -1
  58. package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +11 -4
  59. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +25 -8
  60. package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +87 -5
  61. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +41 -5
  62. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +13 -13
  63. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +355 -37
  64. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -2
  65. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +93 -47
  66. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +55 -10
  67. package/src/runtime/agent/orchestrator/providers/gemini.mjs +26 -8
  68. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  69. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +1 -0
  70. package/src/runtime/agent/orchestrator/providers/lib/action-input-contract.mjs +60 -0
  71. package/src/runtime/agent/orchestrator/providers/lib/anthropic-native-blocks.mjs +1 -0
  72. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +246 -42
  73. package/src/runtime/agent/orchestrator/providers/lib/anthropic-server-tool-usage.mjs +18 -0
  74. package/src/runtime/agent/orchestrator/providers/lib/anthropic-system-blocks.mjs +27 -6
  75. package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +388 -0
  76. package/src/runtime/agent/orchestrator/providers/lib/inclusive-usage.mjs +54 -0
  77. package/src/runtime/agent/orchestrator/providers/lib/origin-bound-fetch.mjs +16 -0
  78. package/src/runtime/agent/orchestrator/providers/lib/responses-terminal-fields.mjs +11 -0
  79. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +8 -0
  80. package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +48 -22
  81. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +119 -18
  82. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +13 -15
  83. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +1 -1
  84. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  85. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +33 -1
  86. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -0
  87. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +2 -0
  88. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +121 -61
  89. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +35 -14
  90. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +5 -2
  91. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +37 -7
  92. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/tool-call-acc.mjs +2 -9
  93. package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +1 -1
  94. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +47 -2
  95. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +47 -25
  96. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +5 -1
  97. package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +151 -0
  98. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +42 -24
  99. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +63 -25
  100. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-head-parity.json +2901 -0
  101. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +172 -0
  102. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +53 -5
  103. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +7 -1
  104. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +2 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +103 -26
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +23 -4
  107. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +11 -6
  108. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +6 -1
  109. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +15 -23
  110. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +23 -10
  111. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +25 -4
  112. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +10 -0
  113. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-items.mjs +18 -7
  114. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +4 -0
  115. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +5 -0
  116. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/text-relay.mjs +9 -1
  117. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +80 -0
  118. package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +3 -1
  119. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +7 -0
  120. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +7 -1
  121. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +71 -24
  122. package/src/runtime/agent/orchestrator/providers/openai-responses-payload-parity.fixture.json +323 -0
  123. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +44 -16
  124. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +7 -16
  125. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/refusals.mjs +60 -0
  126. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +51 -35
  127. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state.mjs +30 -0
  128. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +12 -0
  129. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/terminal-frames.mjs +25 -6
  130. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +11 -3
  131. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +170 -0
  132. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +75 -70
  133. package/src/runtime/agent/orchestrator/providers/openrouter.mjs +75 -0
  134. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +147 -8
  135. package/src/runtime/agent/orchestrator/providers/registry.mjs +4 -1
  136. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +5 -0
  137. package/src/runtime/agent/orchestrator/providers/transport-scope.d.mts +6 -0
  138. package/src/runtime/agent/orchestrator/providers/transport-scope.mjs +28 -0
  139. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +157 -0
  140. package/src/runtime/agent/orchestrator/providers/wire-route.mjs +512 -0
  141. package/src/runtime/agent/orchestrator/providers/xai-tool-search-head-parity.json +1131 -0
  142. package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +183 -0
  143. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +100 -0
  144. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +21 -0
  145. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -0
  146. package/src/runtime/agent/orchestrator/runtime-core/provider-request-snapshot.mjs +7 -3
  147. package/src/runtime/agent/orchestrator/runtime-core/session-title.mjs +21 -1
  148. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +14 -3
  149. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +526 -0
  150. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  151. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +10 -7
  152. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +176 -71
  153. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +62 -10
  154. package/src/runtime/agent/orchestrator/runtime-core/tool-exposure.mjs +96 -0
  155. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +3 -1
  156. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +3 -2
  157. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +14 -93
  158. package/src/runtime/agent/orchestrator/session/compact/summary-anchor.mjs +5 -0
  159. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +120 -0
  160. package/src/runtime/agent/orchestrator/session/context-tool-schema.mjs +17 -2
  161. package/src/runtime/agent/orchestrator/session/context-utils.mjs +8 -4
  162. package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +2 -1
  163. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +6 -1
  164. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +91 -1
  165. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +1 -1
  166. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +56 -0
  167. package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +18 -8
  168. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -0
  169. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +21 -10
  170. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +132 -0
  171. package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +30 -0
  172. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +63 -20
  173. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +2 -1
  174. package/src/runtime/agent/orchestrator/session/loop/tool-call-resolution.mjs +95 -0
  175. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +30 -3
  176. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +15 -4
  177. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
  178. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +150 -0
  179. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +4 -1
  180. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -1
  181. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +34 -12
  182. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +16 -0
  183. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +57 -25
  184. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  185. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  186. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +8 -0
  187. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +208 -14
  188. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +15 -3
  189. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +25 -3
  190. package/src/runtime/agent/orchestrator/session/manager/session-fallback-title.mjs +82 -0
  191. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +19 -2
  192. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -0
  193. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -1
  194. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -1
  195. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +6 -0
  196. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +13 -0
  197. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +96 -0
  198. package/src/runtime/agent/orchestrator/session/side-question.mjs +192 -0
  199. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +17 -3
  200. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +23 -0
  201. package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +58 -2
  202. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +41 -28
  203. package/src/runtime/agent/orchestrator/session/store/session-observers.mjs +4 -2
  204. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +12 -1
  205. package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +3 -1
  206. package/src/runtime/agent/orchestrator/session/store-summary-archived-agent.mjs +34 -15
  207. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -0
  208. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +8 -0
  209. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +10 -1
  210. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +10 -2
  211. package/src/runtime/agent/orchestrator/session/store.mjs +19 -3
  212. package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +1 -0
  213. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +1 -1
  214. package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +1 -1
  215. package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +7 -1
  216. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -0
  217. package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +77 -0
  218. package/src/runtime/agent/orchestrator/tools/bash-policy-scan.mjs +8 -2
  219. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/command-preflight.mjs +3 -3
  220. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +4 -1
  221. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +14 -10
  222. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +49 -34
  223. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +23 -1
  224. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +1 -1
  225. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1 -1
  226. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +73 -39
  227. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +18 -1
  228. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -4
  229. package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +88 -12
  230. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +2 -0
  231. package/src/runtime/computer-bridge/action-schema.mjs +44 -21
  232. package/src/runtime/github/tool.mjs +92 -2
  233. package/src/runtime/memory/index.mjs +1 -0
  234. package/src/runtime/memory/lib/http-router/ingest-routes.mjs +22 -1
  235. package/src/runtime/memory/lib/ingest/transcript-offsets.mjs +2 -0
  236. package/src/runtime/memory/lib/pg/adapter.mjs +98 -1
  237. package/src/runtime/memory/lib/pg/process.mjs +31 -26
  238. package/src/runtime/memory/lib/pg/run-child.mjs +49 -0
  239. package/src/runtime/memory/lib/pg/supervisor.mjs +108 -3
  240. package/src/runtime/memory/lib/runtime-fetcher.mjs +12 -13
  241. package/src/runtime/memory/lib/transcript-ingest.mjs +14 -0
  242. package/src/runtime/office/README.md +1 -1
  243. package/src/runtime/office/authoring/html-document-author.mjs +2 -0
  244. package/src/runtime/office/authoring/pptx-contact-sheet.mjs +2 -0
  245. package/src/runtime/office/authoring/pptx-html-measure.mjs +2 -0
  246. package/src/runtime/office/authoring/pptx-html-runner.mjs +2 -0
  247. package/src/runtime/office/authoring/xlsx-html-runner.mjs +2 -0
  248. package/src/runtime/office/core/office-actions-read.mjs +3 -0
  249. package/src/runtime/office/core/office-render-preview.mjs +2 -0
  250. package/src/runtime/office/core/office-session-dispatch.mjs +7 -0
  251. package/src/runtime/office/core/office-sessionless-actions.mjs +8 -0
  252. package/src/runtime/office/core/office-sessions.mjs +5 -0
  253. package/src/runtime/office/core/office-transactions.mjs +3 -0
  254. package/src/runtime/office/core/office-write-ledger.mjs +98 -0
  255. package/src/runtime/office/design/library/templates/mixdog-executive.mixdog-edit.pptx +0 -0
  256. package/src/runtime/office/index.mjs +30 -0
  257. package/src/runtime/office/pdf/pdf-edit-document.mjs +2 -0
  258. package/src/runtime/office/pdf/pdf-render.mjs +23 -7
  259. package/src/runtime/office/pdf/pdf-security.mjs +2 -0
  260. package/src/runtime/office/portable/portable-opc.mjs +4 -1
  261. package/src/runtime/office/quality/visual-diff.mjs +2 -0
  262. package/src/runtime/shared/bounded-file-read.mjs +13 -0
  263. package/src/runtime/shared/bounded-line-diff.mjs +182 -0
  264. package/src/runtime/shared/buffered-appender.mjs +18 -0
  265. package/src/runtime/shared/config.mjs +78 -18
  266. package/src/runtime/shared/err-text.mjs +6 -0
  267. package/src/runtime/shared/file-permissions.d.mts +6 -1
  268. package/src/runtime/shared/file-permissions.mjs +54 -6
  269. package/src/runtime/shared/llm/anthropic-effort.mjs +6 -0
  270. package/src/runtime/shared/llm/usage-accounting.mjs +5 -2
  271. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +3 -0
  272. package/src/runtime/shared/loopback-listener.mjs +9 -0
  273. package/src/runtime/shared/provider-api-key.mjs +2 -0
  274. package/src/runtime/shared/review-diff.mjs +52 -0
  275. package/src/runtime/shared/session-sidebar-host.d.mts +4 -0
  276. package/src/runtime/shared/session-sidebar-host.mjs +38 -0
  277. package/src/runtime/shared/slow-span.mjs +114 -0
  278. package/src/runtime/shared/statusline/statusline-segments.mjs +28 -4
  279. package/src/runtime/shared/tool-card-model/card-header.mjs +6 -1
  280. package/src/runtime/shared/tool-card-model/generic-detail.mjs +39 -5
  281. package/src/runtime/shared/tool-card-model.mjs +5 -2
  282. package/src/runtime/shared/tool-output-limit.mjs +4 -0
  283. package/src/runtime/shared/tool-primitives.mjs +13 -0
  284. package/src/runtime/shared/tool-result-summary.mjs +8 -1
  285. package/src/runtime/shared/tool-surface.mjs +18 -3
  286. package/src/runtime/shared/tool-work-units.mjs +40 -4
  287. package/src/runtime/shared/transcript-writer/purge.mjs +74 -0
  288. package/src/runtime/shared/transcript-writer.mjs +10 -2
  289. package/src/runtime/shared/turn-changes.mjs +268 -100
  290. package/src/runtime/shared/turn-review-reply.mjs +8 -39
  291. package/src/runtime/shared/turn-snapshot-store.mjs +19 -108
  292. package/src/runtime/shared/turn-snapshot.mjs +857 -683
  293. package/src/runtime/shared/turn-worktree-snapshot.mjs +183 -255
  294. package/src/runtime/shared/user-data-guard.mjs +262 -49
  295. package/src/runtime/shared/worktrees/carry.mjs +531 -0
  296. package/src/runtime/shared/worktrees/contract.mjs +34 -0
  297. package/src/runtime/shared/worktrees/file-recovery.mjs +68 -0
  298. package/src/runtime/shared/worktrees/git-head.mjs +205 -0
  299. package/src/runtime/shared/worktrees/git.mjs +8 -3
  300. package/src/runtime/shared/worktrees/maintenance.mjs +1 -1
  301. package/src/runtime/shared/worktrees/manager.mjs +156 -8
  302. package/src/runtime/shared/worktrees/merge.mjs +31 -21
  303. package/src/runtime/shared/worktrees/paths.d.mts +6 -0
  304. package/src/runtime/terminal-bridge/client.mjs +44 -11
  305. package/src/runtime/terminal-bridge/tool-defs.mjs +13 -6
  306. package/src/runtime/tidy/apply.mjs +47 -2
  307. package/src/runtime/tidy/run-engines.mjs +11 -5
  308. package/src/session-runtime/agent-tool-routing.mjs +19 -13
  309. package/src/session-runtime/boot/apis.mjs +13 -5
  310. package/src/session-runtime/boot/facade.mjs +1 -0
  311. package/src/session-runtime/boot/infrastructure.mjs +6 -1
  312. package/src/session-runtime/boot/session.mjs +0 -1
  313. package/src/session-runtime/boot/tools.mjs +4 -1
  314. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +12 -5
  315. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +65 -0
  316. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -1
  317. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +11 -4
  318. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +115 -18
  319. package/src/session-runtime/internal-tool-executor.mjs +6 -2
  320. package/src/session-runtime/lifecycle/cold-heir.mjs +188 -0
  321. package/src/session-runtime/lifecycle/inheritance.mjs +71 -27
  322. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -3
  323. package/src/session-runtime/lifecycle/session-switch/resume.mjs +6 -0
  324. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +29 -6
  325. package/src/session-runtime/lifecycle/teardown.mjs +72 -7
  326. package/src/session-runtime/lifecycle-api.mjs +1 -0
  327. package/src/session-runtime/mcp-glue/connect.mjs +17 -5
  328. package/src/session-runtime/model-route/set-route.mjs +2 -2
  329. package/src/session-runtime/native-web-search-usage.mjs +51 -0
  330. package/src/session-runtime/native-web-search.mjs +10 -1
  331. package/src/session-runtime/new-session-config.mjs +4 -2
  332. package/src/session-runtime/provider-auth/login.mjs +4 -0
  333. package/src/session-runtime/resource-scope-decorate.mjs +10 -3
  334. package/src/session-runtime/runtime-review-api.mjs +8 -2
  335. package/src/session-runtime/services/agent-tool/index-write-queue.mjs +81 -20
  336. package/src/session-runtime/services/agent-tool/lead-worker-index/forgotten-leads.mjs +29 -0
  337. package/src/session-runtime/services/agent-tool/lead-worker-index/index-file.mjs +1 -1
  338. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +73 -7
  339. package/src/session-runtime/services/agent-tool/tag-registry.mjs +15 -1
  340. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +10 -1
  341. package/src/session-runtime/services/agent-tool/worker-index.mjs +46 -7
  342. package/src/session-runtime/services/agent-tool.mjs +3 -0
  343. package/src/session-runtime/services/channel-admin.mjs +19 -3
  344. package/src/session-runtime/services/memory-runtime-proxy/calls.mjs +4 -0
  345. package/src/session-runtime/services/memory-runtime-proxy.mjs +1 -0
  346. package/src/session-runtime/services/provider-admin.mjs +81 -15
  347. package/src/session-runtime/session-transcript-purge.mjs +32 -0
  348. package/src/session-runtime/session-turn-api.mjs +14 -5
  349. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -0
  350. package/src/session-runtime/setup-tool/executor.mjs +36 -6
  351. package/src/session-runtime/setup-tool/extended-actions.mjs +86 -6
  352. package/src/session-runtime/setup-tool/settings-contract.mjs +13 -3
  353. package/src/session-runtime/setup-tool/tool-defs.mjs +13 -4
  354. package/src/session-runtime/skills-api.mjs +3 -5
  355. package/src/session-runtime/tool-surface.mjs +5 -1
  356. package/src/session-runtime/turn/compaction-gate.mjs +25 -0
  357. package/src/session-runtime/turn/session-ops.mjs +7 -5
  358. package/src/session-runtime/turn/side-question-ops.mjs +22 -0
  359. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +5 -1
  360. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +8 -3
  361. package/src/session-runtime/turn/turn-run/turn-settle.mjs +10 -0
  362. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  363. package/src/session-runtime/turn/turn-run.mjs +25 -9
  364. package/src/session-runtime/usage-stats-api.mjs +43 -11
  365. package/src/session-runtime/workspace/project-branches.mjs +7 -5
  366. package/src/session-runtime/workspace/transition-steps.mjs +129 -31
  367. package/src/session-runtime/workspace/transitions.mjs +190 -15
  368. package/src/standalone/daemon-agent-control.mjs +9 -1
  369. package/src/standalone/daemon-local-session-bridge.mjs +14 -1
  370. package/src/standalone/daemon-owner.mjs +17 -4
  371. package/src/standalone/daemon-pg-stop.mjs +51 -0
  372. package/src/standalone/daemon-stop-budget.d.mts +2 -0
  373. package/src/standalone/daemon-stop-budget.mjs +18 -0
  374. package/src/standalone/daemon-stored-session-views.mjs +2 -0
  375. package/src/standalone/daemon-telemetry.mjs +19 -0
  376. package/src/standalone/daemon.mjs +61 -1
  377. package/src/standalone/desktop-service-registry.mjs +5 -0
  378. package/src/standalone/local-session-runtime.mjs +70 -19
  379. package/src/standalone/session-client.mjs +42 -0
  380. package/src/standalone/session-protocol.mjs +7 -0
  381. package/src/standalone/session-proxy/projection.mjs +41 -4
  382. package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +15 -1
  383. package/src/standalone/session-runtime-inline-host.mjs +1 -0
  384. package/src/standalone/session-service/session-calls/session-action.mjs +18 -3
  385. package/src/standalone/session-service-api.mjs +6 -1
  386. package/src/standalone/session-state-patch.d.mts +2 -1
  387. package/src/standalone/session-state-patch.mjs +41 -1
  388. package/src/tui/App.jsx +25 -0
  389. package/src/tui/app/app-view/floating-panel.jsx +26 -1
  390. package/src/tui/app/btw-controller.mjs +118 -0
  391. package/src/tui/app/create-app-pickers.mjs +2 -0
  392. package/src/tui/app/shell-layout/row-budgets.mjs +11 -1
  393. package/src/tui/app/shell-layout/surfaces.mjs +3 -1
  394. package/src/tui/app/slash-commands.mjs +13 -3
  395. package/src/tui/app/slash-dispatch/session-commands.mjs +19 -6
  396. package/src/tui/app/use-global-key-input/approval-shortcut.mjs +12 -0
  397. package/src/tui/app/use-global-key-input.mjs +5 -12
  398. package/src/tui/app/use-prompt-handlers.mjs +11 -0
  399. package/src/tui/components/BtwPanel.jsx +53 -0
  400. package/src/tui/components/QuestionPanel.jsx +275 -0
  401. package/src/tui/components/question-panel-model.mjs +156 -0
  402. package/src/tui/dist/index.mjs +2413 -1465
  403. package/src/tui/session/context-state.mjs +4 -2
  404. package/src/tui/session/session-api/commands.mjs +10 -0
  405. package/src/tui/session/session-api/controls.mjs +2 -2
  406. package/src/tui/session/session-api/integrations.mjs +9 -2
  407. package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
  408. package/src/tui/session/session-api/lifecycle.mjs +2 -1
  409. package/src/tui/session/session-api/settings.mjs +6 -0
  410. package/src/tui/session/session-api.mjs +1 -0
  411. package/src/tui/session/tool-approval.mjs +10 -7
  412. package/src/tui/session/transcript-spill.mjs +14 -7
  413. package/src/tui/session/turn-stream.mjs +174 -31
  414. package/src/tui/session/turn.mjs +12 -5
  415. package/src/workflows/default/WORKFLOW.md +3 -1
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { mkdir, writeFile } from 'node:fs/promises';
7
7
  import { dirname, resolve } from 'node:path';
8
+ import { noteOfficeWrite } from '../core/office-write-ledger.mjs';
8
9
  import {
9
10
  PDFDocument,
10
11
  PDFArray,
@@ -130,6 +131,7 @@ export async function writeSibling(path, target, bytes) {
130
131
  if (output.toLowerCase() === resolve(path).toLowerCase())
131
132
  throw new Error('output must differ from the document being edited');
132
133
  await mkdir(dirname(output), { recursive: true });
134
+ noteOfficeWrite(output);
133
135
  await writeFile(output, bytes);
134
136
  return output;
135
137
  }
@@ -5,6 +5,7 @@ import { createCanvas, loadImage } from '@napi-rs/canvas';
5
5
  import { pdfjsStandardFontDataUrl, resolvedPdfJs } from '../../attachments/pdfjs-runtime.mjs';
6
6
  import { installPdfGlobals } from './pdf-document.mjs';
7
7
  import { workerExecArgv } from '../../shared/worker-exec-argv.mjs';
8
+ import { noteOfficeWrite } from '../core/office-write-ledger.mjs';
8
9
 
9
10
  const PAGE_NUMBER_PATTERN = /^\s*(?:(?:page\s*)?\d+\s*(?:of|\/)\s*\d+|페이지\s*\d+|\d+\s*페이지)\s*$/i;
10
11
 
@@ -144,8 +145,8 @@ async function runPdfRenderWorker(data, signal = null) {
144
145
  }
145
146
 
146
147
  // Many pages are reviewed as contact sheets: one labelled grid image per group
147
- // of pages, written next to the PDF.
148
- async function writeContactSheet(group, width, imagePath, renderPage) {
148
+ // of pages, for a file next to the PDF.
149
+ async function contactSheet(group, width, imagePath, renderPage) {
149
150
  const columns = group.length <= 2 ? 1 : Math.max(2, Math.ceil(Math.sqrt(group.length * 0.75)));
150
151
  const rows = Math.ceil(group.length / columns);
151
152
  const gap = 12;
@@ -182,7 +183,6 @@ async function writeContactSheet(group, width, imagePath, renderPage) {
182
183
  context.strokeRect(x, y + labelHeight, image.width, image.height);
183
184
  }
184
185
  const data = sheet.toBuffer('image/png');
185
- await writeFile(imagePath, data);
186
186
  return {
187
187
  page: group[0],
188
188
  pages: group,
@@ -204,7 +204,18 @@ async function writeContactSheet(group, width, imagePath, renderPage) {
204
204
  };
205
205
  }
206
206
 
207
- async function renderPdfPagesDirect(path, { pages = null, maxWidth = 1400, maximumScale = 2, signal = null } = {}) {
207
+ // The page images land beside the PDF, each reported as an Office write first.
208
+ async function writePageImages(images) {
209
+ for (const image of images) {
210
+ noteOfficeWrite(image.path);
211
+ await writeFile(image.path, Buffer.from(image.data, 'base64'));
212
+ }
213
+ }
214
+
215
+ async function renderPdfPagesDirect(
216
+ path,
217
+ { pages = null, maxWidth = 1400, maximumScale = 2, signal = null, writeImages = true } = {}
218
+ ) {
208
219
  if (signal?.aborted) throw new Error('PDF rendering was cancelled');
209
220
  installPdfGlobals();
210
221
  const { getDocument, VerbosityLevel } = await resolvedPdfJs();
@@ -248,7 +259,6 @@ async function renderPdfPagesDirect(path, { pages = null, maxWidth = 1400, maxim
248
259
  for (const pageNumber of selected) {
249
260
  const { canvas, data } = await renderPage(pageNumber, maxWidth);
250
261
  const imagePath = join(dirname(path), `${stem}-page-${pageNumber}.png`);
251
- await writeFile(imagePath, data);
252
262
  output.push({
253
263
  page: pageNumber,
254
264
  path: imagePath,
@@ -264,9 +274,10 @@ async function renderPdfPagesDirect(path, { pages = null, maxWidth = 1400, maxim
264
274
  for (let offset = 0; offset < selected.length; offset += pagesPerSheet) {
265
275
  const group = selected.slice(offset, offset + pagesPerSheet);
266
276
  const imagePath = join(dirname(path), `${stem}-pages-${group[0]}-${group.at(-1)}.png`);
267
- output.push(await writeContactSheet(group, width, imagePath, renderPage));
277
+ output.push(await contactSheet(group, width, imagePath, renderPage));
268
278
  }
269
279
  }
280
+ if (writeImages) await writePageImages(output);
270
281
  const reviewedPages = output.flatMap((image) => image.pages || [image.page]);
271
282
  const reviewedPageSet = new Set(reviewedPages);
272
283
  return {
@@ -293,7 +304,9 @@ export async function renderPdfPages(path, options = {}) {
293
304
  const signal = options.signal || null;
294
305
  if (signal?.aborted) throw new Error('PDF rendering was cancelled');
295
306
  if (!isMainThread) return await renderPdfPagesDirect(path, options);
296
- return await runPdfRenderWorker(
307
+ // The worker rasterizes; this thread writes the images, where the call's
308
+ // write ledger can see each one before it lands.
309
+ const rendered = await runPdfRenderWorker(
297
310
  {
298
311
  kind: PDF_RENDER_WORKER_KIND,
299
312
  path,
@@ -301,10 +314,13 @@ export async function renderPdfPages(path, options = {}) {
301
314
  pages: options.pages ?? null,
302
315
  maxWidth: options.maxWidth ?? 1400,
303
316
  maximumScale: options.maximumScale ?? 2,
317
+ writeImages: false,
304
318
  },
305
319
  },
306
320
  signal
307
321
  );
322
+ await writePageImages(rendered.images);
323
+ return rendered;
308
324
  }
309
325
 
310
326
  if (!isMainThread && [PDF_RENDER_WORKER_KIND, PDF_RENDER_PAGE_WORKER_KIND].includes(workerData?.kind)) {
@@ -2,6 +2,7 @@ import { spawn } from 'node:child_process';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { writeFile, rm, rename } from 'node:fs/promises';
4
4
  import { dirname, join } from 'node:path';
5
+ import { noteOfficeWrite } from '../core/office-write-ledger.mjs';
5
6
 
6
7
  // qpdf on PATH, or wherever MIXDOG_QPDF_PATH points (the Windows installer
7
8
  // does not add itself to PATH).
@@ -89,6 +90,7 @@ export async function securePdf({ input, output, mode, password = '', ownerPassw
89
90
  ? ['--encrypt', password, ownerPassword || password, '256', '--', input, target]
90
91
  : [`--password=${password}`, '--decrypt', input, target];
91
92
  await writeFile(responsePath, `${args.join('\n')}\n`, { encoding: 'utf8', mode: 0o600 });
93
+ noteOfficeWrite(output);
92
94
  try {
93
95
  await run(QPDF, [`@${responsePath}`]);
94
96
  if (samePath) await rename(target, output);
@@ -1,6 +1,7 @@
1
1
  import { posix } from 'node:path';
2
2
  import { createHash } from 'node:crypto';
3
3
  import { readFile, writeFile } from 'node:fs/promises';
4
+ import { noteOfficeWrite } from '../core/office-write-ledger.mjs';
4
5
  import { koreanParticleReplacements } from '../shared/korean-particles.mjs';
5
6
  import { presetLabels } from '../shared/labels.mjs';
6
7
  import {
@@ -137,7 +138,9 @@ export async function packageBuffer(zip) {
137
138
  }
138
139
 
139
140
  export async function savePackage(zip, path) {
140
- await writeFile(path, await packageBuffer(zip));
141
+ const bytes = await packageBuffer(zip);
142
+ noteOfficeWrite(path);
143
+ await writeFile(path, bytes);
141
144
  }
142
145
 
143
146
  export function relationshipMap(xml) {
@@ -1,6 +1,7 @@
1
1
  import { writeFile } from 'node:fs/promises';
2
2
  import { dirname, extname, basename, join } from 'node:path';
3
3
  import { createCanvas, loadImage } from '@napi-rs/canvas';
4
+ import { noteOfficeWrite } from '../core/office-write-ledger.mjs';
4
5
 
5
6
  function imageByPage(images) {
6
7
  return new Map((images || []).map((image) => [Number(image.page), image]));
@@ -61,6 +62,7 @@ export async function compareRenderedPages(beforeImages, afterImages, outputPdf)
61
62
  diffContext.putImageData(diff, 0, 0);
62
63
  const data = diffCanvas.toBuffer('image/png');
63
64
  const path = join(dirname(outputPdf), `${stem}-visual-diff-page-${page}.png`);
65
+ noteOfficeWrite(path);
64
66
  await writeFile(path, data);
65
67
  const total = width * height;
66
68
  metrics.push({
@@ -156,6 +156,19 @@ export function readBoundedFile(
156
156
  }
157
157
  }
158
158
 
159
+ /**
160
+ * A file's state for the turn review of a tool write: null when absent, its
161
+ * bytes (binary included) up to PATCH_EDIT_MAX_BYTES and within `budget`,
162
+ * otherwise an omitted marker — counted as changed, never diffed or restored.
163
+ * Throws ENOTREGULAR for anything but a regular file.
164
+ */
165
+ export function reviewFileContent(path, { budget = null } = {}) {
166
+ const read = readBoundedFile(path, { maxBytes: PATCH_EDIT_MAX_BYTES, budget, detectBinary: false });
167
+ if (read.state === 'absent') return null;
168
+ if (read.state === 'omitted') return { omitted: true, size: read.size, reason: read.reason };
169
+ return read.content;
170
+ }
171
+
159
172
  /** Human phrase for an omitted read: "binary (N bytes)", "larger than 8 MiB (N bytes)". */
160
173
  export function describeOmittedFile({ reason, size, limit } = {}) {
161
174
  const bytes = `${Number(size) || 0} bytes`;
@@ -0,0 +1,182 @@
1
+ // In-process line diffs for the turn-change paths, bounded the way codex
2
+ // bounds its own: every file gets a short timeout, every operation (one tool
3
+ // call, one review or seal pass) a total budget. A file over the size limits,
4
+ // past its timeout or past the spent budget gets one replacement hunk between
5
+ // its common line prefix and suffix instead — still an exact patch that
6
+ // applies, only not a minimal one. Results are cached by content, so the same
7
+ // pair is not diffed again across calls and reviews.
8
+ import { createHash } from 'node:crypto';
9
+ import { performance } from 'node:perf_hooks';
10
+ import { formatPatch, structuredPatch } from 'diff';
11
+
12
+ /** Longest a single file's Myers diff may run (codex's per-file value). */
13
+ export const LINE_DIFF_FILE_TIMEOUT_MS = 100;
14
+ /** Total Myers diff time one operation may spend across its files. */
15
+ // Kept well under the lag target: an operation also renders, hashes and
16
+ // bounds its patches outside the diff itself.
17
+ export const LINE_DIFF_OPERATION_BUDGET_MS = 150;
18
+ // A side over either limit is never Myers-diffed.
19
+ const LINE_DIFF_MAX_CHARS = 1024 * 1024;
20
+ const LINE_DIFF_MAX_LINES = 20_000;
21
+ const CONTEXT_LINES = 3;
22
+ const CACHE_MAX_ENTRIES = 512;
23
+ // Cached hunks reference their texts: bound the cache by their length.
24
+ const CACHE_MAX_CHARS = 32 * 1024 * 1024;
25
+
26
+ /** One operation's diff budget; pass it to every lineDiffHunks call of it. */
27
+ export function createLineDiffBudget(totalMs = LINE_DIFF_OPERATION_BUDGET_MS) {
28
+ return { remainingMs: totalMs };
29
+ }
30
+
31
+ const _cache = new Map();
32
+ let _cachedChars = 0;
33
+ const _hashByBytes = new WeakMap();
34
+
35
+ const sha1 = (value) => createHash('sha1').update(value).digest('hex');
36
+
37
+ /** Content hash of one side: a byte array is hashed once per object. A text
38
+ * hashes to the same id as its UTF-8 bytes; one that has no exact UTF-8 form
39
+ * (a lone surrogate) gets no id. */
40
+ function sideHash(source, text) {
41
+ if (source && typeof source === 'object') {
42
+ let hash = _hashByBytes.get(source);
43
+ if (!hash) {
44
+ hash = sha1(source);
45
+ _hashByBytes.set(source, hash);
46
+ }
47
+ return hash;
48
+ }
49
+ return text.isWellFormed() ? sha1(text) : null;
50
+ }
51
+
52
+ function cacheKey(sources, oldText, newText) {
53
+ const before = sideHash(sources[0], oldText);
54
+ const after = before && sideHash(sources[1], newText);
55
+ return after ? `${before}:${after}` : null;
56
+ }
57
+
58
+ function cacheGet(key) {
59
+ const entry = _cache.get(key);
60
+ if (!entry) return null;
61
+ _cache.delete(key);
62
+ _cache.set(key, entry);
63
+ return entry.result;
64
+ }
65
+
66
+ function cacheSet(key, result, chars) {
67
+ if (chars > CACHE_MAX_CHARS) return;
68
+ _cachedChars -= _cache.get(key)?.chars || 0;
69
+ _cache.delete(key);
70
+ _cache.set(key, { result, chars });
71
+ _cachedChars += chars;
72
+ while (_cache.size > CACHE_MAX_ENTRIES || _cachedChars > CACHE_MAX_CHARS) {
73
+ const [oldest, entry] = _cache.entries().next().value;
74
+ _cache.delete(oldest);
75
+ _cachedChars -= entry.chars;
76
+ }
77
+ }
78
+
79
+ function overLimits(text) {
80
+ if (text.length > LINE_DIFF_MAX_CHARS) return true;
81
+ let lines = 0;
82
+ for (let index = text.indexOf('\n'); index >= 0; index = text.indexOf('\n', index + 1)) {
83
+ lines += 1;
84
+ if (lines > LINE_DIFF_MAX_LINES) return true;
85
+ }
86
+ return false;
87
+ }
88
+
89
+ /** Lines with their `\n` (the last one may lack it), as jsdiff tokenizes. */
90
+ function lineTokens(text) {
91
+ if (!text) return [];
92
+ const lines = text.split('\n');
93
+ const tokens = lines.map((line, index) => (index < lines.length - 1 ? `${line}\n` : line));
94
+ if (tokens.at(-1) === '') tokens.pop();
95
+ return tokens;
96
+ }
97
+
98
+ /** The one hunk replacing everything between the texts' common line prefix
99
+ * and suffix, with up to three context lines each side, in jsdiff's hunk
100
+ * shape (`` included). */
101
+ function replacementHunks(oldText, newText) {
102
+ const before = lineTokens(oldText);
103
+ const after = lineTokens(newText);
104
+ const shortest = Math.min(before.length, after.length);
105
+ let start = 0;
106
+ while (start < shortest && before[start] === after[start]) start += 1;
107
+ let end = 0;
108
+ while (end < shortest - start && before[before.length - 1 - end] === after[after.length - 1 - end]) end += 1;
109
+ const lead = Math.min(CONTEXT_LINES, start);
110
+ const trail = Math.min(CONTEXT_LINES, end);
111
+ const lines = [];
112
+ const push = (mark, tokens) => {
113
+ for (const token of tokens) {
114
+ if (token.endsWith('\n')) lines.push(mark + token.slice(0, -1));
115
+ else lines.push(mark + token, '\');
116
+ }
117
+ };
118
+ push(' ', before.slice(start - lead, start));
119
+ push('-', before.slice(start, before.length - end));
120
+ push('+', after.slice(start, after.length - end));
121
+ push(' ', before.slice(before.length - end, before.length - end + trail));
122
+ const first = start - lead;
123
+ return [
124
+ {
125
+ oldStart: first + 1,
126
+ oldLines: before.length - end + trail - first,
127
+ newStart: first + 1,
128
+ newLines: after.length - end + trail - first,
129
+ lines,
130
+ },
131
+ ];
132
+ }
133
+
134
+ /**
135
+ * Hunks (context 3, jsdiff's structuredPatch shape) turning `oldText` into
136
+ * `newText`: jsdiff's own when its diff finishes within the file timeout and
137
+ * what is left of `budget` (`exact`), else the replacement hunk. `sources`
138
+ * are the raw sides the texts came from (bytes or the texts), used only as
139
+ * the cache key. The returned value is shared: never mutate it.
140
+ */
141
+ export function lineDiffHunks(oldText, newText, budget, sources = [oldText, newText]) {
142
+ if (oldText === newText) return { hunks: [], exact: true };
143
+ const key = cacheKey(sources, oldText, newText);
144
+ const cached = key ? cacheGet(key) || budget.fallbacks?.get(key) : null;
145
+ if (cached) return cached;
146
+ let result = null;
147
+ // A fallback forced by the operation's spent budget is not this pair's own
148
+ // outcome: it is not cached, so a later operation can still diff it.
149
+ let repeatable = true;
150
+ if (!overLimits(oldText) && !overLimits(newText)) {
151
+ if (budget.remainingMs > 0) {
152
+ const timeout = Math.min(LINE_DIFF_FILE_TIMEOUT_MS, budget.remainingMs);
153
+ const startedAt = performance.now();
154
+ const patch = structuredPatch('', '', oldText, newText, '', '', { context: CONTEXT_LINES, timeout });
155
+ budget.remainingMs -= performance.now() - startedAt;
156
+ if (patch) result = { hunks: patch.hunks, exact: true };
157
+ else repeatable = timeout >= LINE_DIFF_FILE_TIMEOUT_MS;
158
+ } else {
159
+ repeatable = false;
160
+ }
161
+ }
162
+ result ||= { hunks: replacementHunks(oldText, newText), exact: false };
163
+ if (key && repeatable) cacheSet(key, result, oldText.length + newText.length);
164
+ // Within its own operation it is final: the budget never grows back.
165
+ else if (key) (budget.fallbacks ||= new Map()).set(key, result);
166
+ return result;
167
+ }
168
+
169
+ /** The `--- `/`+++ ` headers and hunks of `hunks`, as jsdiff formats them,
170
+ * ending in exactly one newline. */
171
+ export function unifiedHunksBody(oldHeader, newHeader, hunks) {
172
+ const generated = formatPatch({ oldFileName: oldHeader, newFileName: newHeader, oldHeader: '', newHeader: '', hunks });
173
+ let end = generated.length;
174
+ // Linear trim: a /\n*$/ regex rescans from every position of a large patch.
175
+ while (end > 0 && generated.charCodeAt(end - 1) === 10) end -= 1;
176
+ return `${generated.slice(generated.search(/^--- /m), end)}\n`;
177
+ }
178
+
179
+ export function _resetLineDiffCacheForTest() {
180
+ _cache.clear();
181
+ _cachedChars = 0;
182
+ }
@@ -175,6 +175,24 @@ export function appendBuffered(path, text) {
175
175
  }
176
176
  }
177
177
 
178
+ /**
179
+ * Drop every not-yet-written chunk for `path` (its file is being deleted, so a
180
+ * later flush must not recreate it). A write already handed to the OS cannot
181
+ * be recalled; callers unlink after this and it lands before or is removed.
182
+ */
183
+ export function discardBuffered(path) {
184
+ const q = queues.get(path);
185
+ if (!q) return;
186
+ if (q.timer) {
187
+ clearTimeout(q.timer);
188
+ q.timer = null;
189
+ }
190
+ q.chunks = [];
191
+ q.bytes = 0;
192
+ q.failCount = 0;
193
+ deleteIfIdle(path, q);
194
+ }
195
+
178
196
  /** Observable overflow counters for buffered append queues. */
179
197
  export function getBufferedAppenderStats(path) {
180
198
  const q = queues.get(path);
@@ -18,8 +18,10 @@ import {
18
18
  backupUserData,
19
19
  backupUserDataAsync,
20
20
  markUserDataInitialized,
21
+ ensureUserDataInitialized,
21
22
  loadLatestMixdogConfigFromBackup,
22
- hasUserDataInitMarker,
23
+ isUserDataRecoveryExpected,
24
+ findLegacyUserDataEvidence,
23
25
  } from './user-data-guard.mjs';
24
26
  import { AGENT_PROVIDER_ENV, AGENT_PROVIDER_ENV_ALIASES, getAgentApiKey } from './provider-api-key.mjs';
25
27
 
@@ -236,7 +238,7 @@ function readConfigFile() {
236
238
  }
237
239
  }
238
240
 
239
- function writeConfigFile(data) {
241
+ function writeConfigFile(data, postBackupReason = 'post-config-write') {
240
242
  // R4 data-at-rest: mixdog-config.json holds provider apiKeys; clamp to
241
243
  // owner-only on POSIX via 0o600/0o700 mode bits, AND fail-closed on
242
244
  // Windows where those bits are advisory — `secret: true` makes the
@@ -261,29 +263,76 @@ function writeConfigFile(data) {
261
263
  markUserDataInitialized(resolvePluginData());
262
264
  } catch {}
263
265
  try {
264
- backupUserData(resolvePluginData(), 'post-config-write');
266
+ backupUserData(resolvePluginData(), postBackupReason);
265
267
  } catch {}
266
268
  }
267
269
 
270
+ // Config paths whose restore could not be persisted: the in-memory restore is
271
+ // still served, but the failure is logged (and the write retried) only once
272
+ // per process instead of on every read.
273
+ const _restorePersistFailed = new Set();
274
+
275
+ /**
276
+ * Write a config restored on a plain read back to disk through the normal save
277
+ * path (atomic write + backups), so later reads hit the file instead of
278
+ * restoring again. Try-once lock: a contended lock means a writer (possibly
279
+ * this process's own outer RMW) is about to persist, so the next read retries.
280
+ */
281
+ function persistRestoredConfig(restored) {
282
+ const path = configPath();
283
+ if (_restorePersistFailed.has(path)) return;
284
+ try {
285
+ const wrote = withFileLockSync(
286
+ `${path}.lock`,
287
+ () => {
288
+ if (existsSync(path)) return false;
289
+ writeAll(restored, 'post-restore');
290
+ return true;
291
+ },
292
+ { timeoutMs: 0 }
293
+ );
294
+ if (wrote) {
295
+ process.stderr.write(
296
+ '[config] read: restored mixdog-config.json from latest user-data backup (missing after init) and saved it\n'
297
+ );
298
+ }
299
+ } catch (err) {
300
+ if (err?.code === 'ELOCKCONTENDED') return;
301
+ _restorePersistFailed.add(path);
302
+ process.stderr.write(
303
+ `[config] read: restored mixdog-config.json from latest user-data backup (missing after init); saving it failed, serving it from memory: ${err?.message || err}\n`
304
+ );
305
+ }
306
+ }
307
+
268
308
  function readAll() {
269
309
  const parsed = readConfigFile();
270
- if (parsed != null) return canonicalizeUnifiedConfig(parsed);
310
+ if (parsed != null) {
311
+ // A data dir from before the in-dir init marker gets one (plus a first
312
+ // scoped snapshot) once per process, so its config stays recoverable.
313
+ try {
314
+ ensureUserDataInitialized(resolvePluginData());
315
+ } catch {}
316
+ return canonicalizeUnifiedConfig(parsed);
317
+ }
271
318
  // Symmetric with readAllForRmW(): a missing/unreadable config on a data
272
319
  // dir that was previously initialized means the file was LOST (deleted,
273
- // failed write), not a fresh install. Self-heal from the newest
320
+ // failed write), not a fresh install. A recreated data dir has no marker
321
+ // and starts fresh. Self-heal from this data dir's newest
274
322
  // structurally-complete backup instead of silently collapsing to {} —
275
323
  // which callers (readSection/getCapabilities) merge with in-memory
276
- // defaults, dropping the user's presets/agent routes/sections. In-memory only:
277
- // readAll() runs OUTSIDE the config lock (unlike the RMW path), so we do
278
- // not write here to avoid an unlocked-write race; the next updateSection
279
- // re-persists the file under the lock.
280
- if (hasUserDataInitMarker(resolvePluginData())) {
324
+ // defaults, dropping the user's presets/agent routes/sections. The restore
325
+ // is persisted under the config lock (only if the file is still missing) so
326
+ // later reads hit the file instead of restoring and logging again.
327
+ // An upgraded data dir (legacy marker / matching legacy backup, no in-dir
328
+ // marker yet) is a recovery case too; without a verifiable backup reads stay
329
+ // empty and RMW writes refuse (see readAllForRmW).
330
+ if (isUserDataRecoveryExpected(resolvePluginData())) {
281
331
  const restored = loadLatestMixdogConfigFromBackup(resolvePluginData());
282
332
  if (restored && isPlainObject(restored)) {
283
- process.stderr.write(
284
- '[config] read: restored mixdog-config.json from latest user-data backup (missing after init)\n'
285
- );
286
- return canonicalizeUnifiedConfig(restored);
333
+ const config = canonicalizeUnifiedConfig(restored);
334
+ persistRestoredConfig(config);
335
+ return config;
287
336
  }
288
337
  }
289
338
  return {};
@@ -300,11 +349,22 @@ function quarantineMalformedConfig(parseErr) {
300
349
  }
301
350
 
302
351
  function restoreAllForRmWOrThrow(reason) {
303
- const restored = loadLatestMixdogConfigFromBackup(resolvePluginData());
352
+ const restored = loadLatestMixdogConfigFromBackup(resolvePluginData(), { fresh: true });
304
353
  if (restored && isPlainObject(restored)) {
305
354
  process.stderr.write(`[config] RMW read: restored mixdog-config.json from latest user-data backup (${reason})\n`);
306
355
  return restored;
307
356
  }
357
+ const legacy = findLegacyUserDataEvidence(resolvePluginData(), { fresh: true });
358
+ if (legacy) {
359
+ const err = new Error(
360
+ `[config] mixdog-config.json is missing but this data dir (${resolvePluginData()}) was initialized by an older ` +
361
+ `Mixdog build and no backup verified to belong to it was found; refusing to write a fresh config over a ` +
362
+ `recoverable state. Restore mixdog-config.json from a backup under ${legacy.roots.join(' or ')}, or delete ` +
363
+ `the legacy marker(s) to start fresh: ${legacy.markers.join(', ') || '(none; matching legacy backups are incomplete)'}`
364
+ );
365
+ err.code = 'EUSERDATARECOVERY';
366
+ throw err;
367
+ }
308
368
  throw new Error(
309
369
  `[config] mixdog-config.json is unreadable and no valid backup was found (${reason}); refusing section write that would wipe other sections`
310
370
  );
@@ -321,7 +381,7 @@ function readAllForRmW() {
321
381
  raw = readFileSync(configPath(), 'utf8');
322
382
  } catch (err) {
323
383
  if (err.code === 'ENOENT') {
324
- if (hasUserDataInitMarker(resolvePluginData())) {
384
+ if (isUserDataRecoveryExpected(resolvePluginData(), { fresh: true })) {
325
385
  return restoreAllForRmWOrThrow('config file missing after user-data was initialized');
326
386
  }
327
387
  return {};
@@ -402,8 +462,8 @@ export function subscribeConfigChange(listener) {
402
462
  };
403
463
  }
404
464
 
405
- function writeAll(data) {
406
- writeConfigFile(canonicalizeUnifiedConfig(data));
465
+ function writeAll(data, postBackupReason) {
466
+ writeConfigFile(canonicalizeUnifiedConfig(data), postBackupReason);
407
467
  notifyConfigChanged();
408
468
  }
409
469
 
@@ -215,6 +215,12 @@ export function presentErrorText(error, options = {}) {
215
215
  const name = error && typeof error === 'object' ? String(error.name || '') : '';
216
216
  const code = error && typeof error === 'object' ? String(error.code || '') : '';
217
217
  const status = errorHttpStatus(error);
218
+ // The fixed request overhead error is actionable as written (sizes,
219
+ // contributors, remedy): keep its lines instead of a generic "Context too
220
+ // large." that would suggest compacting.
221
+ if (code === 'TOOL_SURFACE_EXCEEDS_CONTEXT' || /\(TOOL_SURFACE_EXCEEDS_CONTEXT\)/.test(text)) {
222
+ return stripErrorPrefix(text);
223
+ }
218
224
  const jsonError = extractJsonError(text);
219
225
  if (jsonError) text = jsonError;
220
226
  const embeddedError = extractEmbeddedError(text);
@@ -1,3 +1,8 @@
1
1
  export function enforceOwnerOnlyAclWin32(targetPath: string, options?: { fresh?: boolean }): void;
2
2
 
3
- export function enforceOwnerOnlyAclWin32Async(targetPath: string, options?: { fresh?: boolean }): Promise<void>;
3
+ export function enforceOwnerOnlyAclWin32Async(
4
+ targetPath: string,
5
+ options?: { fresh?: boolean }
6
+ ): Promise<void>;
7
+
8
+ export function enforceOwnerOnlyDirectoryWin32Async(targetPath: string): Promise<void>;
@@ -73,6 +73,16 @@ function icaclsPath() {
73
73
  return path;
74
74
  }
75
75
 
76
+ const quoteForPowerShell = (value) => `'${String(value).replaceAll("'", "''")}'`;
77
+
78
+ function powerShellCommand(icacls, script) {
79
+ return {
80
+ executable: join(dirname(icacls), 'WindowsPowerShell', 'v1.0', 'powershell.exe'),
81
+ label: 'Windows ACL update',
82
+ args: ['-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(script, 'utf16le').toString('base64')],
83
+ };
84
+ }
85
+
76
86
  function ownerOnlyAclCommand(targetPath, sid, fresh, icacls) {
77
87
  if (fresh) {
78
88
  return {
@@ -84,7 +94,7 @@ function ownerOnlyAclCommand(targetPath, sid, fresh, icacls) {
84
94
  // Never /reset a published secret: that temporarily restores broad parent
85
95
  // grants, and a later failure leaves them in place. Build the protected DACL
86
96
  // in memory and publish it in one native ACL update instead.
87
- const quote = (value) => `'${String(value).replaceAll("'", "''")}'`;
97
+ const quote = quoteForPowerShell;
88
98
  const script = [
89
99
  "$ErrorActionPreference = 'Stop';",
90
100
  `$path = ${quote(targetPath)};`,
@@ -100,11 +110,37 @@ function ownerOnlyAclCommand(targetPath, sid, fresh, icacls) {
100
110
  'if ($directory) { [System.IO.Directory]::SetAccessControl($path, $acl) }',
101
111
  'else { [System.IO.File]::SetAccessControl($path, $acl) };',
102
112
  ].join(' ');
103
- return {
104
- executable: join(dirname(icacls), 'WindowsPowerShell', 'v1.0', 'powershell.exe'),
105
- label: 'Windows ACL update',
106
- args: ['-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(script, 'utf16le').toString('base64')],
107
- };
113
+ return powerShellCommand(icacls, script);
114
+ }
115
+
116
+ // Replaces a directory's whole DACL (explicit grants to anyone else included)
117
+ // with one protected, inheritable owner-only grant, in one native update, so
118
+ // files created inside need no ACL call of their own. Files already inside
119
+ // that carry an explicit grant or any other principal's grant are reset to
120
+ // inherit only that owner grant.
121
+ function ownerOnlyDirectoryCommand(targetPath, sid, icacls) {
122
+ const script = [
123
+ "$ErrorActionPreference = 'Stop';",
124
+ `$path = ${quoteForPowerShell(targetPath)};`,
125
+ `$sid = [System.Security.Principal.SecurityIdentifier]::new(${quoteForPowerShell(sid)});`,
126
+ '$acl = [System.Security.AccessControl.DirectorySecurity]::new();',
127
+ '$acl.SetAccessRuleProtection($true, $false);',
128
+ '$acl.AddAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new(',
129
+ '$sid, [System.Security.AccessControl.FileSystemRights]::FullControl,',
130
+ "[System.Security.AccessControl.InheritanceFlags]'ContainerInherit, ObjectInherit',",
131
+ '[System.Security.AccessControl.PropagationFlags]::None,',
132
+ '[System.Security.AccessControl.AccessControlType]::Allow));',
133
+ '[System.IO.Directory]::SetAccessControl($path, $acl);',
134
+ 'foreach ($file in [System.IO.Directory]::GetFiles($path)) {',
135
+ '$current = [System.IO.File]::GetAccessControl($file);',
136
+ '$rules = @($current.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier]));',
137
+ '$foreign = @($rules | Where-Object { -not $_.IsInherited -or $_.IdentityReference -ne $sid });',
138
+ 'if ($current.AreAccessRulesProtected -or $foreign.Count -gt 0) {',
139
+ '$reset = [System.Security.AccessControl.FileSecurity]::new();',
140
+ '$reset.SetAccessRuleProtection($false, $false);',
141
+ '[System.IO.File]::SetAccessControl($file, $reset) } };',
142
+ ].join(' ');
143
+ return powerShellCommand(icacls, script);
108
144
  }
109
145
 
110
146
  function aclFailure(targetPath, cause, label = 'icacls') {
@@ -143,3 +179,15 @@ export async function enforceOwnerOnlyAclWin32Async(targetPath, { fresh = false
143
179
  throw aclFailure(targetPath, error, command.label);
144
180
  }
145
181
  }
182
+
183
+ export async function enforceOwnerOnlyDirectoryWin32Async(targetPath) {
184
+ if (process.platform !== 'win32') return;
185
+ const icacls = icaclsPath();
186
+ if (cachedUserSid === null) cachedUserSid = await resolveCurrentUserPrincipalAsync();
187
+ const command = ownerOnlyDirectoryCommand(targetPath, cachedUserSid, icacls);
188
+ try {
189
+ await execFileAsync(command.executable, command.args, { windowsHide: true, timeout: 15_000 });
190
+ } catch (error) {
191
+ throw aclFailure(targetPath, error, command.label);
192
+ }
193
+ }
@@ -291,6 +291,12 @@ export function applyAnthropicEffortToBody(
291
291
  ) {
292
292
  if (!body || typeof body !== 'object') return;
293
293
 
294
+ // A gateway that declares its own effort contract (DeepSeek ignores
295
+ // budget_tokens) projects effort itself; returning true skips the Claude rules.
296
+ if (typeof opts.messagesEffortProjection === 'function') {
297
+ if (opts.messagesEffortProjection(body, { model, effort: opts.effort }) === true) return;
298
+ }
299
+
294
300
  const thinkingBudgetTokens = Number(opts.thinkingBudgetTokens);
295
301
  if (Number.isFinite(thinkingBudgetTokens) && thinkingBudgetTokens > 0) {
296
302
  assertAnthropicManualBudgetSupported(model);