mixdog 0.9.162 → 0.9.164

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 (335) hide show
  1. package/package.json +1 -1
  2. package/scripts/code-graph-description-contract.mjs +9 -14
  3. package/scripts/import-usage-ledger.mjs +10 -0
  4. package/scripts/pg-memory-diet-apply.mjs +95 -0
  5. package/scripts/pg-memory-diet-verify.mjs +83 -0
  6. package/scripts/pg-memory-diet.mjs +164 -0
  7. package/scripts/search-rg-parity.mjs +132 -0
  8. package/src/defaults/skills/browser-use/SKILL.md +4 -5
  9. package/src/defaults/skills/docx/SKILL.md +8 -6
  10. package/src/defaults/skills/docx/references/native-authoring.md +78 -2
  11. package/src/defaults/skills/goal-management/SKILL.md +88 -19
  12. package/src/defaults/skills/pdf/SKILL.md +9 -7
  13. package/src/defaults/skills/pptx/SKILL.md +19 -16
  14. package/src/defaults/skills/pptx/references/charts.md +143 -31
  15. package/src/defaults/skills/pptx/references/composition.md +70 -27
  16. package/src/defaults/skills/pptx/references/direction.md +9 -4
  17. package/src/defaults/skills/pptx/references/kit.md +858 -65
  18. package/src/defaults/skills/pptx/references/pictures.md +213 -6
  19. package/src/defaults/skills/skill-creator/SKILL.md +5 -5
  20. package/src/defaults/skills/video/SKILL.md +3 -3
  21. package/src/defaults/skills/xlsx/SKILL.md +8 -6
  22. package/src/defaults/skills/xlsx/references/report-design.md +41 -1
  23. package/src/headless-exec.mjs +10 -0
  24. package/src/lib/rules-builder.cjs +23 -68
  25. package/src/output-styles/common.md +2 -0
  26. package/src/rules/lead/01-general.md +13 -14
  27. package/src/rules/lead/lead-brief.md +11 -13
  28. package/src/rules/shared/00-general.md +9 -14
  29. package/src/rules/shared/10-tool-workflow.md +21 -48
  30. package/src/rules/shared/15-skills.md +7 -0
  31. package/src/rules/shared/30-exploration.md +13 -34
  32. package/src/rules/shared/35-implementation.md +7 -8
  33. package/src/rules/shared/40-editing.md +5 -23
  34. package/src/rules/shared/50-execution.md +7 -11
  35. package/src/rules/shared/60-verification.md +11 -18
  36. package/src/rules/shared/70-delivery.md +5 -4
  37. package/src/rules/shared/75-goal.md +7 -4
  38. package/src/rules/shared/80-memory.md +5 -8
  39. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +0 -1
  40. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +2 -2
  41. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +0 -6
  42. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +20 -3
  43. package/src/runtime/agent/orchestrator/config-presets.mjs +100 -0
  44. package/src/runtime/agent/orchestrator/config-storage.mjs +246 -0
  45. package/src/runtime/agent/orchestrator/config.mjs +58 -552
  46. package/src/runtime/agent/orchestrator/context/collect.mjs +31 -5
  47. package/src/runtime/agent/orchestrator/context/skill-state.mjs +43 -0
  48. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +7 -1
  49. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +6 -2
  50. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +105 -1
  51. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +20 -5
  52. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +5 -2
  53. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +7 -5
  54. package/src/runtime/agent/orchestrator/providers/cursor.mjs +8 -3
  55. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +47 -1
  56. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +62 -101
  57. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +78 -8
  58. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +320 -0
  59. package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +136 -0
  60. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +21 -542
  61. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +7 -7
  62. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +2 -7
  63. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +26 -6
  64. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +8 -18
  65. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +2 -7
  66. package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -2
  67. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +7 -0
  68. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +41 -116
  69. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +2 -2
  70. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +13 -4
  71. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +132 -32
  72. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +10 -0
  73. package/src/runtime/agent/orchestrator/session/explicit-skills.mjs +57 -0
  74. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +23 -8
  75. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +0 -4
  76. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +104 -431
  77. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +0 -6
  78. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +18 -15
  79. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +2 -2
  80. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +11 -1
  81. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +65 -33
  82. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +5 -2
  83. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +14 -6
  84. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +2 -0
  85. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +1 -0
  86. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +0 -6
  87. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +0 -1
  88. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +33 -0
  89. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +142 -20
  90. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +75 -28
  91. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +9 -94
  92. package/src/runtime/agent/orchestrator/tools/builtin/binary-file.mjs +8 -8
  93. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +54 -26
  94. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +43 -54
  95. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +37 -25
  96. package/src/runtime/agent/orchestrator/tools/builtin/lib/glob-static-prefix.mjs +11 -18
  97. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +24 -36
  98. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-path-fanout.mjs +4 -4
  99. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-pattern-fanout.mjs +32 -4
  100. package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +2 -12
  101. package/src/runtime/agent/orchestrator/tools/builtin/lib/search-input-helpers.mjs +0 -53
  102. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +15 -37
  103. package/src/runtime/agent/orchestrator/tools/builtin/local-search-telemetry.mjs +12 -0
  104. package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +3 -4
  105. package/src/runtime/agent/orchestrator/tools/builtin/native-search-runner.mjs +2 -1
  106. package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +6 -84
  107. package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +2 -6
  108. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +4 -9
  109. package/src/runtime/agent/orchestrator/tools/builtin/read-office-files.mjs +453 -16
  110. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +40 -23
  111. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +25 -49
  112. package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +81 -0
  113. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +83 -67
  114. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +3 -6
  115. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +25 -1
  116. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +11 -2
  117. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +20 -61
  118. package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +16 -91
  119. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +3 -70
  120. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +0 -1
  121. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +0 -43
  122. package/src/runtime/agent/orchestrator/tools/builtin/shell-lossless-compact.mjs +78 -23
  123. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +1 -0
  124. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +16 -1
  125. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +3 -0
  126. package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +2 -0
  127. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
  128. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
  129. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +5 -2
  130. package/src/runtime/agent/orchestrator/tools/tool-batch-trace.mjs +34 -4
  131. package/src/runtime/attachments/pdf-extract.mjs +32 -2
  132. package/src/runtime/computer-bridge/action-schema.mjs +1 -3
  133. package/src/runtime/computer-bridge/client.mjs +20 -12
  134. package/src/runtime/computer-bridge/core-actions.mjs +7 -1
  135. package/src/runtime/computer-bridge/limits.d.mts +1 -0
  136. package/src/runtime/computer-bridge/limits.mjs +3 -0
  137. package/src/runtime/memory/index.mjs +73 -145
  138. package/src/runtime/memory/lib/http-router.mjs +14 -7
  139. package/src/runtime/memory/lib/http-wire.mjs +3 -47
  140. package/src/runtime/memory/lib/memory-recall-store.mjs +7 -5
  141. package/src/runtime/memory/lib/memory-service-lifecycle.mjs +105 -0
  142. package/src/runtime/memory/lib/pg/compact-indexes.mjs +93 -0
  143. package/src/runtime/memory/lib/recall-format.mjs +17 -5
  144. package/src/runtime/memory/lib/recall-substring-predicate.mjs +7 -0
  145. package/src/runtime/memory/lib/session-ingest.mjs +16 -0
  146. package/src/runtime/memory/lib/trace-store.mjs +4 -3
  147. package/src/runtime/office/authoring/pptx-author-action.mjs +1 -0
  148. package/src/runtime/office/authoring/pptx-brief.mjs +21 -5
  149. package/src/runtime/office/authoring/pptx-receipt.mjs +79 -4
  150. package/src/runtime/office/authoring/pptx-script-normalize.mjs +70 -1
  151. package/src/runtime/office/authoring/pptx-script-runner.mjs +1 -0
  152. package/src/runtime/office/capabilities.mjs +346 -46
  153. package/src/runtime/office/com/office-com-cleanup.ps1 +15 -2
  154. package/src/runtime/office/com/office-com-host.ps1 +519 -40
  155. package/src/runtime/office/com/office-com-session-host.ps1 +5 -0
  156. package/src/runtime/office/com/office-word-formatting.ps1 +6 -0
  157. package/src/runtime/office/core/office-actions-batch.mjs +39 -4
  158. package/src/runtime/office/core/office-actions-inspect.mjs +96 -1
  159. package/src/runtime/office/core/office-actions-lifecycle.mjs +1 -1
  160. package/src/runtime/office/core/office-actions-render.mjs +26 -12
  161. package/src/runtime/office/core/office-core.mjs +25 -5
  162. package/src/runtime/office/core/office-recalculation.mjs +17 -1
  163. package/src/runtime/office/core/office-sessions.mjs +185 -15
  164. package/src/runtime/office/core/office-transactions.mjs +9 -1
  165. package/src/runtime/office/core/pagination.mjs +27 -21
  166. package/src/runtime/office/core/pptx-page-cache.mjs +4 -1
  167. package/src/runtime/office/core/snapshot-contract.mjs +10 -0
  168. package/src/runtime/office/core/tabular.mjs +32 -9
  169. package/src/runtime/office/design/composition-system.mjs +5 -2
  170. package/src/runtime/office/design/content-model.mjs +69 -12
  171. package/src/runtime/office/design/design-discipline.mjs +18 -3
  172. package/src/runtime/office/design/design-table-input.mjs +33 -0
  173. package/src/runtime/office/design/design-tokens.mjs +3 -0
  174. package/src/runtime/office/design/docx/design-docx-components.mjs +41 -5
  175. package/src/runtime/office/design/docx/design-docx.mjs +20 -25
  176. package/src/runtime/office/design/xlsx/design-xlsx-components.mjs +3 -2
  177. package/src/runtime/office/design/xlsx/design-xlsx.mjs +154 -36
  178. package/src/runtime/office/index.mjs +62 -7
  179. package/src/runtime/office/pdf/pdf-adapter.mjs +76 -17
  180. package/src/runtime/office/pdf/pdf-analysis.mjs +154 -29
  181. package/src/runtime/office/pdf/pdf-draw.mjs +43 -3
  182. package/src/runtime/office/pdf/pdf-fonts.mjs +48 -2
  183. package/src/runtime/office/pdf/pdf-forms.mjs +131 -2
  184. package/src/runtime/office/pdf/pdf-search.mjs +99 -17
  185. package/src/runtime/office/pdf/pdf-writer.mjs +282 -20
  186. package/src/runtime/office/portable/docx-tracked-edits.mjs +14 -9
  187. package/src/runtime/office/portable/portable-cells.mjs +142 -7
  188. package/src/runtime/office/portable/portable-chart.mjs +50 -10
  189. package/src/runtime/office/portable/portable-docx-parts.mjs +115 -8
  190. package/src/runtime/office/portable/portable-docx-xml.mjs +213 -11
  191. package/src/runtime/office/portable/portable-docx.mjs +294 -36
  192. package/src/runtime/office/portable/portable-ooxml.mjs +4 -1
  193. package/src/runtime/office/portable/portable-opc.mjs +111 -7
  194. package/src/runtime/office/portable/portable-package.mjs +5 -1
  195. package/src/runtime/office/portable/portable-pivot.mjs +52 -2
  196. package/src/runtime/office/portable/portable-pptx-chart.mjs +50 -0
  197. package/src/runtime/office/portable/portable-pptx-charts.mjs +50 -11
  198. package/src/runtime/office/portable/portable-pptx-core.mjs +105 -11
  199. package/src/runtime/office/portable/portable-pptx-deck.mjs +66 -7
  200. package/src/runtime/office/portable/portable-pptx-shapes.mjs +36 -6
  201. package/src/runtime/office/portable/portable-pptx.mjs +2 -1
  202. package/src/runtime/office/portable/portable-sheet-page.mjs +94 -29
  203. package/src/runtime/office/portable/portable-sheet-styles.mjs +59 -2
  204. package/src/runtime/office/portable/portable-sheet-xml.mjs +267 -4
  205. package/src/runtime/office/portable/portable-slide-shapes.mjs +2 -1
  206. package/src/runtime/office/portable/portable-snapshot.mjs +344 -36
  207. package/src/runtime/office/portable/portable-validation.mjs +524 -75
  208. package/src/runtime/office/portable/portable-xlsx.mjs +455 -53
  209. package/src/runtime/office/portable/portable-xml.mjs +25 -0
  210. package/src/runtime/office/portable/text-metrics.mjs +140 -10
  211. package/src/runtime/office/portable/xlsx-assertions.mjs +56 -4
  212. package/src/runtime/office/portable/xlsx-audit-support.mjs +35 -0
  213. package/src/runtime/office/portable/xlsx-contract.mjs +118 -2
  214. package/src/runtime/office/portable/xlsx-formula-audit.mjs +52 -3
  215. package/src/runtime/office/portable/xlsx-sheet-hygiene.mjs +17 -3
  216. package/src/runtime/office/quality/assurance-checklist.mjs +16 -1
  217. package/src/runtime/office/quality/assurance-rendered.mjs +12 -0
  218. package/src/runtime/office/quality/assurance-structure.mjs +297 -7
  219. package/src/runtime/office/quality/design-aesthetics.mjs +29 -13
  220. package/src/runtime/office/quality/design-deck-diversity.mjs +93 -2
  221. package/src/runtime/office/quality/design-review-authored.mjs +23 -7
  222. package/src/runtime/office/quality/design-review-critique.mjs +17 -3
  223. package/src/runtime/office/quality/design-review.mjs +10 -4
  224. package/src/runtime/office/quality/document-acceptance.mjs +30 -5
  225. package/src/runtime/office/quality/pptx-deck-rubric.mjs +23 -0
  226. package/src/runtime/office/quality/quality-pipeline.mjs +22 -3
  227. package/src/runtime/office/quality/render-air.mjs +49 -4
  228. package/src/runtime/office/shared/labels.mjs +25 -0
  229. package/src/runtime/office/tool-defs.mjs +1 -1
  230. package/src/runtime/shared/abort-race.mjs +4 -1
  231. package/src/runtime/shared/atomic-file.d.mts +27 -0
  232. package/src/runtime/shared/atomic-file.mjs +31 -564
  233. package/src/runtime/shared/bounded-download.mjs +45 -29
  234. package/src/runtime/shared/debounced-writer.d.mts +13 -0
  235. package/src/runtime/shared/debounced-writer.mjs +73 -0
  236. package/src/runtime/shared/edit-tool-dialect.mjs +9 -2
  237. package/src/runtime/shared/file-lock.mjs +278 -0
  238. package/src/runtime/shared/file-permissions.d.mts +9 -0
  239. package/src/runtime/shared/file-permissions.mjs +141 -0
  240. package/src/runtime/shared/http-request-body.mjs +97 -0
  241. package/src/runtime/shared/json-metrics.mjs +38 -25
  242. package/src/runtime/shared/keyed-serial-queue.d.mts +4 -0
  243. package/src/runtime/shared/keyed-serial-queue.mjs +14 -0
  244. package/src/runtime/shared/llm/cost.mjs +42 -28
  245. package/src/runtime/shared/llm/usage-accounting.mjs +59 -0
  246. package/src/runtime/shared/llm/usage-ledger-import.mjs +159 -0
  247. package/src/runtime/shared/llm/usage-ledger.mjs +439 -0
  248. package/src/runtime/shared/llm/usage-measurement.mjs +40 -0
  249. package/src/runtime/shared/llm/usage-rollup.mjs +473 -0
  250. package/src/runtime/shared/llm/usage-session-history.mjs +491 -0
  251. package/src/runtime/shared/loopback-listener.mjs +84 -0
  252. package/src/runtime/shared/pristine-execution-contract.json +4 -0
  253. package/src/runtime/shared/pristine-execution.mjs +1 -0
  254. package/src/runtime/shared/profile-config.cjs +77 -0
  255. package/src/runtime/shared/skill-selection.d.mts +2 -0
  256. package/src/runtime/shared/skill-selection.mjs +20 -0
  257. package/src/runtime/shared/sleep.mjs +7 -0
  258. package/src/runtime/shared/tool-result-summary.mjs +10 -5
  259. package/src/session-runtime/builtin-features.d.mts +2 -1
  260. package/src/session-runtime/builtin-features.mjs +25 -4
  261. package/src/session-runtime/config-lifecycle.mjs +44 -182
  262. package/src/session-runtime/goal-deadlines.mjs +165 -0
  263. package/src/session-runtime/goal-runtime.mjs +170 -464
  264. package/src/session-runtime/goal-state.mjs +287 -0
  265. package/src/session-runtime/goal-storage.mjs +91 -11
  266. package/src/session-runtime/goal-tasks.mjs +9 -2
  267. package/src/session-runtime/goal-text.mjs +71 -8
  268. package/src/session-runtime/goal-tool-defs.mjs +12 -8
  269. package/src/session-runtime/inheritance-fit.mjs +132 -0
  270. package/src/session-runtime/internal-tool-executor.mjs +1 -1
  271. package/src/session-runtime/lifecycle-api.mjs +108 -61
  272. package/src/session-runtime/provider-auth-api.mjs +20 -0
  273. package/src/session-runtime/provider-catalog-cache.mjs +46 -0
  274. package/src/session-runtime/provider-models.mjs +40 -81
  275. package/src/session-runtime/provider-readiness.mjs +3 -4
  276. package/src/session-runtime/provider-usage.mjs +41 -22
  277. package/src/session-runtime/resource-api.mjs +2 -2
  278. package/src/session-runtime/runtime-core.mjs +9 -10
  279. package/src/session-runtime/session-hooks.mjs +2 -2
  280. package/src/session-runtime/session-lifecycle.mjs +0 -3
  281. package/src/session-runtime/session-turn-api.mjs +2 -2
  282. package/src/session-runtime/skill-tool-loading.mjs +17 -21
  283. package/src/session-runtime/skills-api.mjs +1 -1
  284. package/src/session-runtime/tool-catalog.mjs +12 -15
  285. package/src/session-runtime/tool-defs.mjs +2 -14
  286. package/src/session-runtime/tool-policy-refresh.mjs +1 -0
  287. package/src/session-runtime/usage-stats-api.mjs +72 -0
  288. package/src/session-runtime/warmup-schedulers.mjs +23 -15
  289. package/src/standalone/agent-dispatch-broker.mjs +6 -6
  290. package/src/standalone/agent-tool/job-views.mjs +0 -4
  291. package/src/standalone/agent-tool/render.mjs +7 -14
  292. package/src/standalone/agent-tool/spawn-flow.mjs +1 -3
  293. package/src/standalone/channel-binding.mjs +57 -0
  294. package/src/standalone/channel-transport.mjs +55 -80
  295. package/src/standalone/daemon-log.mjs +167 -0
  296. package/src/standalone/daemon.mjs +9 -145
  297. package/src/standalone/desktop-service-registry.mjs +37 -17
  298. package/src/standalone/fair-call-scheduler.mjs +14 -2
  299. package/src/standalone/hook-bus/command-handler.mjs +236 -0
  300. package/src/standalone/hook-bus/handler-timeout.mjs +17 -0
  301. package/src/standalone/hook-bus/handlers.mjs +28 -244
  302. package/src/standalone/hook-bus/rule-file.mjs +63 -0
  303. package/src/standalone/hook-bus.mjs +90 -98
  304. package/src/standalone/provider-admin.mjs +17 -3
  305. package/src/standalone/rpc-call-identity.mjs +23 -0
  306. package/src/standalone/session-call-cache.mjs +139 -0
  307. package/src/standalone/session-frame-stream.mjs +215 -0
  308. package/src/standalone/session-protocol.mjs +2 -0
  309. package/src/standalone/session-runtime-inline-host.mjs +55 -29
  310. package/src/standalone/session-service/agent-tree.mjs +0 -2
  311. package/src/standalone/session-service/projection.mjs +4 -2
  312. package/src/standalone/session-service/stored-reader.mjs +72 -0
  313. package/src/standalone/session-service.mjs +123 -148
  314. package/src/standalone/session-transport.mjs +67 -353
  315. package/src/standalone/test-fixtures/lifecycle-desktop-service.mjs +3 -0
  316. package/src/standalone/usage-dashboard-model.mjs +394 -0
  317. package/src/standalone/usage-dashboard.mjs +40 -389
  318. package/src/standalone/usage-stats-hours.mjs +50 -0
  319. package/src/standalone/usage-stats-model.mjs +634 -0
  320. package/src/standalone/usage-stats-period.mjs +84 -0
  321. package/src/tui/app/create-app-pickers.mjs +0 -1
  322. package/src/tui/app/model-picker.mjs +28 -29
  323. package/src/tui/app/slash-commands.mjs +1 -1
  324. package/src/tui/dist/index.mjs +582 -431
  325. package/src/tui/session/goal-continuation.mjs +96 -11
  326. package/src/tui/session/queue-helpers.mjs +6 -2
  327. package/src/tui/session/session-api-ext.mjs +84 -12
  328. package/src/tui/session/session-api.mjs +4 -0
  329. package/src/tui/session/session-flow.mjs +9 -5
  330. package/src/tui/session/turn.mjs +2 -1
  331. package/src/tui/session-local.mjs +5 -1
  332. package/src/vendor/statusline/src/gateway/route-meta.mjs +6 -2
  333. package/src/workflows/headless/WORKFLOW.md +3 -2
  334. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +0 -18
  335. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-fixed-fallback.mjs +0 -123
@@ -1,6 +1,6 @@
1
1
  import { basename, extname, join, posix } from 'node:path';
2
2
  import { resolveImageLayout } from './image-layout.mjs';
3
- import { shrinkFontSizeToFit } from './text-metrics.mjs';
3
+ import { contrastRatio, shrinkFontSizeToFit } from './text-metrics.mjs';
4
4
  import { pictureXml, resolveGeometry, shapeXml, supportedShapeTypes, tableXml, textBodyXml, toEmu } from './portable-slide-shapes.mjs';
5
5
  import { readFile } from 'node:fs/promises';
6
6
  import { addPackageRelationship, ensureDefaultContentType, imagePixelSize, partRelationshipPath, removePackageRelationship, zipText } from './portable-opc.mjs';
@@ -176,6 +176,15 @@ export async function handleSetText(context, op) {
176
176
  }
177
177
 
178
178
 
179
+ // The ink a filled shape can carry: white on a dark card, near-black on a light
180
+ // one, and nothing at all when the fill is a theme reference or absent, so the
181
+ // inherited colour keeps deciding.
182
+ function inkOnFill(fill) {
183
+ const field = String(fill || '').replace(/^#/, '').toUpperCase();
184
+ if (!/^[0-9A-F]{6}$/.test(field)) return undefined;
185
+ return (contrastRatio('FFFFFF', field) ?? 0) >= (contrastRatio('1F2429', field) ?? 0) ? 'FFFFFF' : '1F2429';
186
+ }
187
+
179
188
  export async function handleAddTextboxOrAddShape(context, op) {
180
189
  const { zip } = context;
181
190
  const slides = context.slides;
@@ -209,7 +218,10 @@ export async function handleAddTextboxOrAddShape(context, op) {
209
218
  defaults: {
210
219
  fontName: op.fontName ?? properties.fontName,
211
220
  fontSize: op.fontSize ?? properties.fontSize ?? 18,
212
- color: op.color ?? properties.color,
221
+ // A caller who fills a shape and says nothing about its text gets ink
222
+ // the fill can carry. The default dark ink on a dark card was text the
223
+ // runtime's own contrast check then reported as unreadable.
224
+ color: op.color ?? properties.color ?? inkOnFill(op.fillColor ?? properties.fillColor),
213
225
  bold: properties.bold,
214
226
  italic: properties.italic,
215
227
  align: properties.align,
@@ -289,6 +301,7 @@ export async function handleAddImage(context, op) {
289
301
  width: placement.width,
290
302
  height: placement.height,
291
303
  crop: placement.crop,
304
+ altText: op.altText,
292
305
  });
293
306
  zip.file(path, appendSlideShape(current, picture));
294
307
  return {
@@ -351,6 +364,9 @@ export async function handleFitText(context, op) {
351
364
  slide: Number(op.slide),
352
365
  shape: Number(op.shape),
353
366
  scale: Number(fitted.scale.toFixed(2)),
367
+ // The size the copy now reads at: shrinking is the last repair, so the
368
+ // author sees what it cost and can rewrite the line instead.
369
+ fontSize: Math.max(...fitted.sizes.map((size) => Number(size) || 0), 0) || undefined,
354
370
  };
355
371
  }
356
372
 
@@ -383,6 +399,16 @@ export async function handleSetTableDataOrReplaceImage(context, op) {
383
399
  const media = await addSlideImage(zip, path, op.path);
384
400
  updated = shape.xml.replace(/(<a:blip\b[^>]*\br:embed=")[^"]*(")/, `$1${media.relationshipId}$2`);
385
401
  detail = { image: media.part, replaced: previous };
402
+ // The frame keeps the description of the picture that used to be in it, so
403
+ // a replaced photo is announced as the old one until the caller renames it.
404
+ const altText = String(op.altText ?? '').trim();
405
+ if (altText) {
406
+ const encoded = xmlEncode(altText);
407
+ updated = /<p:cNvPr\b[^>]*\bdescr="/.test(updated)
408
+ ? updated.replace(/(<p:cNvPr\b[^>]*\bdescr=")[^"]*(")/, `$1${encoded}$2`)
409
+ : updated.replace(/(<p:cNvPr\b[^>]*?)(\/?>)/, `$1 descr="${encoded}"$2`);
410
+ detail.altText = altText;
411
+ }
386
412
  }
387
413
  const nextInner = `${tree.inner.slice(0, shape.start)}${updated}${tree.inner.slice(shape.end)}`;
388
414
  const nextSlide = `${current.slice(0, tree.start)}${nextInner}${current.slice(tree.end)}`;
@@ -485,7 +511,7 @@ export async function handleAddMedia(context, op) {
485
511
  const poster = await addSlideImage(zip, path, op.poster);
486
512
  const id = nextShapeId(current);
487
513
  const shape = '<p:pic><p:nvPicPr>'
488
- + `<p:cNvPr id="${id}" name="${xmlEncode(posix.basename(mediaPart))}">`
514
+ + `<p:cNvPr id="${id}" name="${xmlEncode(posix.basename(mediaPart))}"${op.altText ? ` descr="${xmlEncode(String(op.altText))}"` : ''}>`
489
515
  + '<a:hlinkClick xmlns:a="' + DRAWING_MAIN_NS + '" r:id="" action="ppaction://media"/></p:cNvPr>'
490
516
  + '<p:cNvPicPr><a:picLocks noChangeAspect="1"/></p:cNvPicPr>'
491
517
  + `<p:nvPr><a:${kind}File xmlns:a="${DRAWING_MAIN_NS}" r:link="${linkId}"/>`
@@ -543,14 +569,18 @@ export async function handleAddAnimation(context, op) {
543
569
  fly: { presetID: 2, filter: 'slide(fromBottom)' },
544
570
  float: { presetID: 30, filter: 'slide(fromBottom)' },
545
571
  };
546
- const requested = String(op.effect || 'fade').toLowerCase().replace(/\s+/g, '');
572
+ // Every other multiword value in this runtime is written with a separator
573
+ // (outside_end, section_next), so on_click and after_previous name the same
574
+ // triggers as onclick and afterprevious.
575
+ const named = (value) => String(value || '').toLowerCase().replace(/[\s_-]+/g, '');
576
+ const requested = named(op.effect) || 'fade';
547
577
  if (!Object.hasOwn(effects, requested)) {
548
578
  throw new Error(`add_animation effect must be one of: ${Object.keys(effects).join(', ')}`);
549
579
  }
550
580
  const triggers = { onclick: 'clickEffect', withprevious: 'withEffect', afterprevious: 'afterEffect' };
551
- const trigger = String(op.trigger || 'onclick').toLowerCase().replace(/\s+/g, '');
581
+ const trigger = named(op.trigger) || 'onclick';
552
582
  if (!Object.hasOwn(triggers, trigger)) {
553
- throw new Error(`add_animation trigger must be one of: ${Object.keys(triggers).join(', ')}`);
583
+ throw new Error('add_animation trigger must be one of: on_click, with_previous, after_previous');
554
584
  }
555
585
  const effect = effects[requested];
556
586
  const duration = Math.max(1, Math.round((Number(op.duration) > 0 ? Number(op.duration) : 0.5) * 1000));
@@ -1,7 +1,7 @@
1
1
  import { presentationSlides, renumberPresentationSlides } from './portable-pptx-package.mjs';
2
2
  import { resolvePptxTargets } from './pptx-targets.mjs';
3
3
  import { handleAddChart, handleSetChartAxis, handleSetChartData, handleSetChartDataLabels, handleSetChartSeries, handleSetChartTrendlineOrSetChartErrorBars } from './portable-pptx-charts.mjs';
4
- import { handleAddCommentOrDeleteComment, handleAddProvenance, handleAddSlide, handleApplyTheme, handleDeleteSlide, handleDuplicateSlide, handleFillTemplate, handleImportSlides, handleKeepSlides, handleMoveSlide, handleReplaceText, handleSetFooterOrSetSlideNumber, handleSetLayout, handleSetNotes, handleSetSlideBackground, handleSetTransition } from './portable-pptx-deck.mjs';
4
+ import { handleAddCommentOrDeleteComment, handleAddProvenance, handleAddSlide, handleApplyTheme, handleDeleteSlide, handleDuplicateSlide, handleFillTemplate, handleImportSlides, handleKeepSlides, handleMoveSlide, handleReplaceText, handleSetFooterOrSetSlideNumber, handleSetLayout, handleSetNotes, handleSetSlideBackground, handleSetSlideVisibility, handleSetTransition } from './portable-pptx-deck.mjs';
5
5
  import { handleAddAnimation, handleAddImage, handleAddMedia, handleAddTable, handleAddTextboxOrAddShape, handleAlignShapesOrDistributeShapes, handleCropImage, handleDeleteShape, handleFitText, handleGroupShapesOrUngroupShape, handleSetHyperlink, handleSetShape, handleSetTableDataOrReplaceImage, handleSetText, handleZOrder } from './portable-pptx-shapes.mjs';
6
6
  export { inspectPptxTextBoxes } from './portable-pptx-core.mjs';
7
7
 
@@ -49,6 +49,7 @@ const PPTX_OPERATIONS = Object.freeze({
49
49
  crop_image: handleCropImage,
50
50
  add_animation: handleAddAnimation,
51
51
  set_transition: handleSetTransition,
52
+ set_slide_visibility: handleSetSlideVisibility,
52
53
  set_shape: handleSetShape,
53
54
  });
54
55
 
@@ -26,80 +26,139 @@ export function worksheetGeometry(xml) {
26
26
  return width === 0 ? 0 : Math.floor(width * 7 + 5) * 0.75;
27
27
  };
28
28
  const rowPoints = (index) => rows.get(index) ?? defaultRow;
29
- const edgeCell = (points, size, limit) => {
29
+ // Which cell a point falls in. A drawing that starts exactly on a boundary
30
+ // sits in the cell that begins there (an image placed at D2 is in D2, not in
31
+ // C1), while one that ends on a boundary still ends in the cell before it.
32
+ const edgeCell = (points, size, limit, trailing) => {
30
33
  let edge = 0;
31
34
  for (let index = 1; index <= limit; index += 1) {
32
35
  edge += size(index);
33
- if (edge >= points) return index;
36
+ if (trailing ? edge >= points : edge > points) return index;
34
37
  }
35
38
  throw new Error('Worksheet drawing exceeds the Excel page grid');
36
39
  };
37
40
  return {
38
41
  columnPoints,
39
42
  rowPoints,
40
- columnAt: (points) => edgeCell(points, columnPoints, 16_384),
41
- rowAt: (points) => edgeCell(points, rowPoints, 1_048_576),
43
+ columnAt: (points, { trailing = false } = {}) => edgeCell(points, columnPoints, 16_384, trailing),
44
+ rowAt: (points, { trailing = false } = {}) => edgeCell(points, rowPoints, 1_048_576, trailing),
42
45
  };
43
46
  }
44
47
 
45
- export async function contentPrintArea(zip, sheet, xml) {
46
- let lastColumn = 1;
47
- let lastRow = 1;
48
- for (const cell of cellRecords(xml, await sharedStrings(zip))) {
49
- if (!cell.formula && (cell.value == null || cell.value === '')) continue;
50
- const parsed = parseCellRef(cell.ref);
51
- lastColumn = Math.max(lastColumn, columnNumber(parsed.col));
52
- lastRow = Math.max(lastRow, parsed.row);
53
- }
54
- for (const reference of mergedRanges(xml)) {
55
- const area = parseAreaRange(reference);
56
- lastColumn = Math.max(lastColumn, area.endCol);
57
- lastRow = Math.max(lastRow, area.endRow);
58
- }
48
+ /** Every chart and picture anchored to a worksheet, resolved onto the cell grid
49
+ * (1-based, inclusive). Excel writes three anchor kinds and our own writer uses
50
+ * the absolute one, so the print area and the snapshot read the same resolver
51
+ * rather than each parsing anchors again. `body` is the anchor's own markup,
52
+ * which the caller inspects to tell a chart frame from a picture. */
53
+ export async function worksheetDrawings(zip, sheet, xml) {
59
54
  const relations = await zipText(zip, partRelationshipPath(sheet.path));
60
55
  const geometry = worksheetGeometry(xml);
61
- for (const match of relations.matchAll(/<Relationship\b([^>]*?)\/>/g)) {
56
+ const drawings = [];
57
+ for (const match of (relations || '').matchAll(/<Relationship\b([^>]*?)\/>/g)) {
62
58
  const attrs = match[1];
63
59
  if (!String(xmlAttribute(attrs, 'Type') || '').endsWith('/drawing') || xmlAttribute(attrs, 'TargetMode') === 'External') continue;
64
60
  const target = xmlAttribute(attrs, 'Target');
65
61
  const part = target.startsWith('/') ? target.slice(1) : posix.normalize(posix.join(posix.dirname(sheet.path), target));
66
62
  const drawing = await zipText(zip, part);
67
- for (const anchor of drawing.matchAll(/<xdr:(absoluteAnchor|oneCellAnchor|twoCellAnchor)\b[^>]*>([\s\S]*?)<\/xdr:\1>/g)) {
63
+ for (const anchor of (drawing || '').matchAll(/<xdr:(absoluteAnchor|oneCellAnchor|twoCellAnchor)\b[^>]*>([\s\S]*?)<\/xdr:\1>/g)) {
64
+ const kind = anchor[1];
68
65
  const body = anchor[2];
69
66
  const marker = (tag) => {
70
67
  const value = new RegExp(`<xdr:${tag}>([\\s\\S]*?)<\\/xdr:${tag}>`).exec(body)?.[1] || '';
71
68
  const number = (name) => Number(new RegExp(`<xdr:${name}>(\\d+)<\\/xdr:${name}>`).exec(value)?.[1]) || 0;
72
69
  return { column: number('col'), row: number('row'), x: number('colOff') / 12700, y: number('rowOff') / 12700 };
73
70
  };
74
- if (anchor[1] === 'twoCellAnchor') {
71
+ const start = kind === 'absoluteAnchor' ? null : marker('from');
72
+ if (start && (start.column >= 16_384 || start.row >= 1_048_576)) {
73
+ throw new Error('Worksheet drawing anchor exceeds the Excel grid');
74
+ }
75
+ if (kind === 'twoCellAnchor') {
75
76
  const end = marker('to');
76
77
  if (end.column >= 16_384 || end.row >= 1_048_576) throw new Error('Worksheet drawing anchor exceeds the Excel grid');
77
- lastColumn = Math.max(lastColumn, end.column + (end.x > 0 ? 1 : 0));
78
- lastRow = Math.max(lastRow, end.row + (end.y > 0 ? 1 : 0));
78
+ drawings.push({
79
+ part,
80
+ kind,
81
+ body,
82
+ startColumn: start.column + 1,
83
+ startRow: start.row + 1,
84
+ endColumn: end.column + (end.x > 0 ? 1 : 0),
85
+ endRow: end.row + (end.y > 0 ? 1 : 0),
86
+ });
79
87
  continue;
80
88
  }
81
89
  const extent = /<xdr:ext\b([^>]*?)\/>/.exec(body)?.[1] || '';
82
90
  let x = 0;
83
91
  let y = 0;
84
- if (anchor[1] === 'absoluteAnchor') {
92
+ if (kind === 'absoluteAnchor') {
85
93
  const position = /<xdr:pos\b([^>]*?)\/>/.exec(body)?.[1] || '';
86
94
  x = Number(xmlAttribute(position, 'x')) / 12700 || 0;
87
95
  y = Number(xmlAttribute(position, 'y')) / 12700 || 0;
88
96
  } else {
89
- const start = marker('from');
90
- if (start.column >= 16_384 || start.row >= 1_048_576) throw new Error('Worksheet drawing anchor exceeds the Excel grid');
91
97
  x = start.x;
92
98
  y = start.y;
93
99
  for (let index = 1; index <= start.column; index += 1) x += geometry.columnPoints(index);
94
100
  for (let index = 1; index <= start.row; index += 1) y += geometry.rowPoints(index);
95
101
  }
96
- lastColumn = Math.max(lastColumn, geometry.columnAt(x + (Number(xmlAttribute(extent, 'cx')) || 0) / 12700));
97
- lastRow = Math.max(lastRow, geometry.rowAt(y + (Number(xmlAttribute(extent, 'cy')) || 0) / 12700));
102
+ const width = (Number(xmlAttribute(extent, 'cx')) || 0) / 12700;
103
+ const height = (Number(xmlAttribute(extent, 'cy')) || 0) / 12700;
104
+ drawings.push({
105
+ part,
106
+ kind,
107
+ body,
108
+ startColumn: geometry.columnAt(x),
109
+ startRow: geometry.rowAt(y),
110
+ endColumn: geometry.columnAt(x + width, { trailing: true }),
111
+ endRow: geometry.rowAt(y + height, { trailing: true }),
112
+ left: x,
113
+ top: y,
114
+ width,
115
+ height,
116
+ });
98
117
  }
99
118
  }
119
+ return drawings;
120
+ }
121
+
122
+ export async function contentPrintArea(zip, sheet, xml) {
123
+ let lastColumn = 1;
124
+ let lastRow = 1;
125
+ for (const cell of cellRecords(xml, await sharedStrings(zip))) {
126
+ if (!cell.formula && (cell.value == null || cell.value === '')) continue;
127
+ const parsed = parseCellRef(cell.ref);
128
+ lastColumn = Math.max(lastColumn, columnNumber(parsed.col));
129
+ lastRow = Math.max(lastRow, parsed.row);
130
+ }
131
+ for (const reference of mergedRanges(xml)) {
132
+ const area = parseAreaRange(reference);
133
+ lastColumn = Math.max(lastColumn, area.endCol);
134
+ lastRow = Math.max(lastRow, area.endRow);
135
+ }
136
+ for (const drawing of await worksheetDrawings(zip, sheet, xml)) {
137
+ lastColumn = Math.max(lastColumn, drawing.endColumn);
138
+ lastRow = Math.max(lastRow, drawing.endRow);
139
+ }
100
140
  return `A1:${columnLabel(lastColumn)}${lastRow}`;
101
141
  }
102
142
 
143
+ // A chart or picture beside a table is wider than a portrait page, and a sheet
144
+ // with no page setup exports by column blocks — the page break runs through the
145
+ // chart. A sheet that declares nothing takes one page wide and keeps paging down
146
+ // (a fit only scales down, so a small sheet prints as before); a declared fit,
147
+ // print scale, or print area is the author's and stays.
148
+ export function fitDrawingSheetOnePageWide(xml) {
149
+ if (/<pageSetUpPr\b[^>]*\bfitToPage="1"/.test(xml)) return { xml, applied: false };
150
+ const setup = worksheetSection(xml, 'pageSetup');
151
+ if (setup && /\bscale="/.test(setup[0])) return { xml, applied: false };
152
+ const sheetPr = worksheetSection(xml, 'sheetPr');
153
+ const attrs = sheetPr ? /^<sheetPr\b([^>]*?)(?:\/>|>)/.exec(sheetPr[0])?.[1] || '' : '';
154
+ const body = sheetPr && !sheetPr[0].endsWith('/>')
155
+ ? sheetPr[0].slice(sheetPr[0].indexOf('>') + 1, sheetPr[0].lastIndexOf('</sheetPr>')) : '';
156
+ let next = upsertWorksheetSection(xml, 'sheetPr', `<sheetPr${attrs}>${body.replace(/<pageSetUpPr\b[^>]*?\/>/, '')}<pageSetUpPr fitToPage="1"/></sheetPr>`);
157
+ const existing = setup ? setup[0].replace(/\s+fitTo(?:Width|Height)="[^"]*"/g, '') : '<pageSetup/>';
158
+ next = upsertWorksheetSection(next, 'pageSetup', existing.replace(/\/?>$/, ' fitToWidth="1" fitToHeight="0"/>'));
159
+ return { xml: next, applied: true };
160
+ }
161
+
103
162
  export async function applyWorksheetPageSetup(zip, sheets, sheet, xml, op) {
104
163
  const orientation = String(op.orientation || '').toLowerCase();
105
164
  if (orientation && !['portrait', 'landscape'].includes(orientation)) {
@@ -107,6 +166,12 @@ export async function applyWorksheetPageSetup(zip, sheets, sheet, xml, op) {
107
166
  }
108
167
  const fitWide = Number(op.fitToPagesWide) || 0;
109
168
  const fitTall = op.fitToPagesTall == null ? null : Number(op.fitToPagesTall) || 0;
169
+ // A call that sets only the print area or margins keeps the fit the sheet
170
+ // already declares: with fitToPage on and the counts dropped, Excel would
171
+ // read the default 1 × 1 and shrink a long sheet onto one page.
172
+ const carriedFit = fitWide || fitTall != null
173
+ ? ''
174
+ : (worksheetSection(xml, 'pageSetup')?.[0].match(/\s+fitTo(?:Width|Height)="[^"]*"/g) || []).join('');
110
175
  if (fitWide || fitTall != null) {
111
176
  const existing = worksheetSection(xml, 'sheetPr');
112
177
  const attrs = existing ? /^<sheetPr\b([^>]*?)(?:\/>|>)/.exec(existing[0])?.[1] || '' : '';
@@ -118,7 +183,7 @@ export async function applyWorksheetPageSetup(zip, sheets, sheet, xml, op) {
118
183
  xml = upsertWorksheetSection(xml, 'printOptions', centered ? `<printOptions${centered}/>` : '');
119
184
  const margin = (value, fallback) => Number.isFinite(Number(value)) && Number(value) >= 0 ? Number(value) : fallback;
120
185
  xml = upsertWorksheetSection(xml, 'pageMargins', `<pageMargins left="${margin(op.leftMargin, 0.7)}" right="${margin(op.rightMargin, 0.7)}" top="${margin(op.topMargin, 0.75)}" bottom="${margin(op.bottomMargin, 0.75)}" header="0.3" footer="0.3"/>`);
121
- xml = upsertWorksheetSection(xml, 'pageSetup', `<pageSetup paperSize="9"${orientation ? ` orientation="${orientation}"` : ''}${fitWide ? ` fitToWidth="${fitWide}"` : ''}${fitTall == null ? '' : ` fitToHeight="${fitTall}"`}/>`);
186
+ xml = upsertWorksheetSection(xml, 'pageSetup', `<pageSetup paperSize="9"${orientation ? ` orientation="${orientation}"` : ''}${fitWide ? ` fitToWidth="${fitWide}"` : ''}${fitTall == null ? '' : ` fitToHeight="${fitTall}"`}${carriedFit}/>`);
122
187
  const printArea = op.fitToContent === true ? await contentPrintArea(zip, sheet, xml) : op.printArea;
123
188
  if (printArea) {
124
189
  const area = parseAreaRange(printArea);
@@ -109,9 +109,52 @@ function buildFill(color) {
109
109
  : '<fill><patternFill patternType="none"/></fill>';
110
110
  }
111
111
 
112
+ // A cell's four edges: the rule under a header, the rule over a total, the frame of an input block.
113
+ // Each side is { style, color }; a side the caller does not name keeps what the cell had.
114
+ const BORDER_SIDES = Object.freeze(['left', 'right', 'top', 'bottom']);
115
+ const BORDER_STYLES = Object.freeze(['none', 'hair', 'thin', 'medium', 'thick', 'dashed', 'dotted', 'double']);
116
+
117
+ function parseBorder(xml) {
118
+ const border = {};
119
+ for (const side of BORDER_SIDES) {
120
+ const element = new RegExp(`<${side}\\b([^>]*?)(?:\\/>|>([\\s\\S]*?)<\\/${side}>)`).exec(xml || '');
121
+ const style = element ? attribute(element[1], 'style') : '';
122
+ border[side] = style && style !== 'none'
123
+ ? { style, color: normalizeColor(attribute(/<color\b([^>]*?)\/>/.exec(element[2] || '')?.[1], 'rgb')) }
124
+ : null;
125
+ }
126
+ return border;
127
+ }
128
+
129
+ function buildBorder(border) {
130
+ const side = (name) => {
131
+ const edge = border[name];
132
+ if (!edge) return `<${name}/>`;
133
+ return `<${name} style="${edge.style}">${edge.color ? `<color rgb="${edge.color}"/>` : '<color auto="1"/>'}</${name}>`;
134
+ };
135
+ return `<border>${side('left')}${side('right')}${side('top')}${side('bottom')}<diagonal/></border>`;
136
+ }
137
+
138
+ function mergeBorders(base, requested) {
139
+ const next = { ...base };
140
+ const sides = requested && typeof requested === 'object' && !Array.isArray(requested) ? requested : {};
141
+ // One spec for every side (`borders: { style: 'thin', color }`) or a spec per side.
142
+ const uniform = BORDER_SIDES.every((name) => sides[name] === undefined) && (sides.style || sides.color);
143
+ for (const name of BORDER_SIDES) {
144
+ const spec = uniform ? sides : sides[name];
145
+ if (spec === undefined) continue;
146
+ if (spec === null || spec === false || spec === 'none' || spec?.style === 'none' || spec?.enabled === false) { next[name] = null; continue; }
147
+ const style = String(spec?.style || 'thin').toLowerCase();
148
+ if (!BORDER_STYLES.includes(style)) throw new Error(`set_style borders.${name}.style "${style}" is not one of ${BORDER_STYLES.join(', ')}`);
149
+ next[name] = { style, color: normalizeColor(spec?.color) };
150
+ }
151
+ return next;
152
+ }
153
+
112
154
  function parseXf(xml) {
113
155
  const attrs = /^<xf\b([^>]*?)(?:\/>|>)/.exec(xml)?.[1] || '';
114
156
  const alignment = /<alignment\b([^>]*?)\/?>/.exec(xml)?.[1] || '';
157
+ const protection = /<protection\b([^>]*?)\/?>/.exec(xml)?.[1] || '';
115
158
  return {
116
159
  numFmtId: Number(attribute(attrs, 'numFmtId')) || 0,
117
160
  fontId: Number(attribute(attrs, 'fontId')) || 0,
@@ -121,6 +164,9 @@ function parseXf(xml) {
121
164
  horizontal: attribute(alignment, 'horizontal'),
122
165
  vertical: attribute(alignment, 'vertical'),
123
166
  wrapText: attribute(alignment, 'wrapText') === '1',
167
+ // Every cell is locked until told otherwise; the flag only takes effect
168
+ // once the sheet itself is protected.
169
+ locked: attribute(protection, 'locked') !== '0',
124
170
  };
125
171
  }
126
172
 
@@ -131,14 +177,18 @@ function buildXf(xf) {
131
177
  + `${xf.vertical ? ` vertical="${xf.vertical}"` : ''}`
132
178
  + `${xf.wrapText ? ' wrapText="1"' : ''}/>`
133
179
  : '';
180
+ // An unlocked cell is how a protected sheet keeps its entry fields typable.
181
+ const protection = xf.locked === false ? '<protection locked="0"/>' : '';
134
182
  const head = `<xf numFmtId="${xf.numFmtId}" fontId="${xf.fontId}" fillId="${xf.fillId}"`
135
183
  + ` borderId="${xf.borderId}" xfId="${xf.xfId}"`
136
184
  + `${xf.numFmtId ? ' applyNumberFormat="1"' : ''}`
137
185
  + `${xf.fontId ? ' applyFont="1"' : ''}`
138
186
  + `${xf.fillId ? ' applyFill="1"' : ''}`
139
187
  + `${xf.borderId ? ' applyBorder="1"' : ''}`
140
- + `${aligned ? ' applyAlignment="1"' : ''}`;
141
- return aligned ? `${head}>${alignment}</xf>` : `${head}/>`;
188
+ + `${aligned ? ' applyAlignment="1"' : ''}`
189
+ + `${protection ? ' applyProtection="1"' : ''}`;
190
+ const body = `${alignment}${protection}`;
191
+ return body ? `${head}>${body}</xf>` : `${head}/>`;
142
192
  }
143
193
 
144
194
  function register(items, candidate) {
@@ -218,6 +268,11 @@ export function applyCellStyle(stylesXml, baseIndex, properties = {}) {
218
268
  next.fillId = register(sections.fills, buildFill(fill));
219
269
  }
220
270
 
271
+ if (Object.hasOwn(properties, 'borders')) {
272
+ const merged = mergeBorders(parseBorder(sections.borders[base.borderId] || ''), properties.borders);
273
+ next.borderId = BORDER_SIDES.some((name) => merged[name]) ? register(sections.borders, buildBorder(merged)) : 0;
274
+ }
275
+
221
276
  if (Object.hasOwn(properties, 'numberFormat')) {
222
277
  next.numFmtId = registerNumberFormat(sections.numFmts, properties.numberFormat);
223
278
  }
@@ -228,6 +283,7 @@ export function applyCellStyle(stylesXml, baseIndex, properties = {}) {
228
283
  next.vertical = VERTICAL[String(properties.verticalAlignment).toLowerCase()] || '';
229
284
  }
230
285
  if (Object.hasOwn(properties, 'wrapText')) next.wrapText = properties.wrapText === true;
286
+ if (Object.hasOwn(properties, 'locked')) next.locked = properties.locked !== false;
231
287
 
232
288
  const index = register(sections.cellXfs, buildXf(next));
233
289
  return { xml: serialize(stylesXml, sections), index };
@@ -299,6 +355,7 @@ export function resolveCellStyles(stylesXml) {
299
355
  ...(font.color ? { color: font.color.slice(-6) } : {}),
300
356
  ...(fill ? { fillColor: fill.slice(-6) } : {}),
301
357
  ...(xf.horizontal ? { horizontalAlignment: xf.horizontal } : {}),
358
+ ...(xf.locked === false ? { locked: false } : {}),
302
359
  };
303
360
  });
304
361
  }