mixdog 1.1.2 → 1.2.1

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 (424) hide show
  1. package/README.md +8 -5
  2. package/package.json +2 -1
  3. package/scripts/lib/run-node-tests.mjs +9 -0
  4. package/scripts/prune-desktop-runtime.mjs +3 -0
  5. package/scripts/test-direct.mjs +4 -2
  6. package/src/defaults/shipped-history.json +10 -0
  7. package/src/defaults/skills/pptx/SKILL.md +6 -6
  8. package/src/defaults/skills/pptx/references/review.md +6 -4
  9. package/src/defaults/skills/pptx/scripts/source-extract.mjs +1 -1
  10. package/src/defaults/skills/setup/references/actions.md +18 -5
  11. package/src/defaults/skills/visualize/SKILL.md +160 -0
  12. package/src/defaults/skills/visualize/references/craft.md +88 -0
  13. package/src/headless-command.mjs +56 -3
  14. package/src/headless-exec.mjs +17 -1
  15. package/src/help.mjs +2 -0
  16. package/src/rules/shared/60-verification.md +3 -1
  17. package/src/rules/shared/70-delivery.md +4 -2
  18. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
  19. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +1 -0
  20. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +2 -0
  21. package/src/runtime/agent/orchestrator/cache-mtime.mjs +26 -5
  22. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  23. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +17 -11
  24. package/src/runtime/agent/orchestrator/config-storage.mjs +1 -0
  25. package/src/runtime/agent/orchestrator/config.mjs +19 -5
  26. package/src/runtime/agent/orchestrator/context/collect.mjs +2 -1
  27. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +11 -5
  28. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +74 -21
  29. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +133 -15
  30. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +55 -8
  31. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +86 -8
  32. package/src/runtime/agent/orchestrator/mcp/client.mjs +474 -164
  33. package/src/runtime/agent/orchestrator/mcp/lazy-output-validator.mjs +25 -0
  34. package/src/runtime/agent/orchestrator/mcp/plugin-placeholders.mjs +54 -0
  35. package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +4 -0
  36. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +108 -17
  37. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  38. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +3 -0
  39. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -1
  40. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -12
  41. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -0
  42. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +9 -0
  43. package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +337 -0
  44. package/src/runtime/agent/orchestrator/providers/command-code-oauth-tokens.mjs +159 -0
  45. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +444 -0
  46. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +2 -0
  47. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  48. package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +2 -1
  49. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -4
  50. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +21 -6
  51. package/src/runtime/agent/orchestrator/providers/effort-rejection.mjs +244 -0
  52. package/src/runtime/agent/orchestrator/providers/gemini.mjs +41 -28
  53. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  54. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
  55. package/src/runtime/agent/orchestrator/providers/lib/gemini-model-catalog.mjs +7 -1
  56. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +8 -40
  57. package/src/runtime/agent/orchestrator/providers/mimo-oauth-tokens.mjs +2 -0
  58. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +51 -12
  59. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +37 -2
  60. package/src/runtime/agent/orchestrator/providers/model-proxy-bridge.mjs +1080 -0
  61. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +82 -0
  62. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +556 -0
  63. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +251 -0
  64. package/src/runtime/agent/orchestrator/providers/muse-code-oauth-tokens.mjs +2 -0
  65. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +69 -102
  66. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +15 -0
  67. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +6 -5
  68. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -1
  69. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +6 -2
  70. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +11 -0
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +2 -1
  72. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +15 -4
  73. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs +1 -1
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +5 -2
  75. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +3 -0
  76. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +4 -4
  77. package/src/runtime/agent/orchestrator/providers/registry.mjs +47 -7
  78. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +10 -0
  79. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +36 -3
  80. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -11
  81. package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +15 -55
  82. package/src/runtime/agent/orchestrator/runtime-core/goal-control.mjs +15 -2
  83. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +16 -1
  84. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +278 -84
  85. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +4 -1
  86. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +21 -0
  87. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +54 -3
  88. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +10 -3
  89. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -0
  90. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +9 -5
  91. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +18 -8
  92. package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +284 -0
  93. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +268 -0
  94. package/src/runtime/agent/orchestrator/session/catalog-db/sqlite-gate.mjs +27 -0
  95. package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +628 -0
  96. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +10 -3
  97. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +24 -13
  98. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +13 -5
  99. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +362 -0
  100. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +253 -0
  101. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +184 -0
  102. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +136 -0
  103. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +128 -0
  104. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +127 -0
  105. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +101 -0
  106. package/src/runtime/agent/orchestrator/session/explicit-skills.mjs +83 -5
  107. package/src/runtime/agent/orchestrator/session/imported-message.mjs +11 -0
  108. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +2 -1
  109. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -1
  110. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +1 -1
  111. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/diagnostic.mjs +1 -7
  112. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/refusal.mjs +1 -1
  113. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +3 -2
  114. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +5 -3
  115. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -4
  116. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +21 -10
  117. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -0
  118. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +3 -0
  119. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -3
  120. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +15 -3
  121. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +22 -12
  122. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +9 -2
  123. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +9 -3
  124. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +5 -2
  125. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +37 -16
  126. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +4 -0
  127. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +17 -4
  128. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +90 -0
  129. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +22 -12
  130. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +32 -3
  131. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +27 -0
  132. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -1
  133. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +1 -0
  134. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +2 -0
  135. package/src/runtime/agent/orchestrator/session/manager.mjs +0 -1
  136. package/src/runtime/agent/orchestrator/session/message-ids.mjs +123 -0
  137. package/src/runtime/agent/orchestrator/session/message-ids.test-support.mjs +29 -0
  138. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +33 -22
  139. package/src/runtime/agent/orchestrator/session/store/listing.mjs +6 -2
  140. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +3 -0
  141. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +6 -0
  142. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +10 -3
  143. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +11 -0
  144. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +817 -0
  145. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +36 -0
  146. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +23 -12
  147. package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +158 -0
  148. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +202 -374
  149. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +20 -3
  150. package/src/runtime/agent/orchestrator/session/synthetic-user-envelope.mjs +3 -1
  151. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +1 -1
  152. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +48 -9
  153. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +161 -0
  154. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +17 -1
  155. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -0
  156. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +52 -2
  157. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +32 -22
  158. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +10 -3
  159. package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +2 -2
  160. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +6 -6
  161. package/src/runtime/agent/orchestrator/tools/builtin/read-cache-policy.mjs +8 -0
  162. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +203 -33
  163. package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +32 -35
  164. package/src/runtime/agent/orchestrator/tools/builtin/read-single-guards.mjs +8 -4
  165. package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +11 -12
  166. package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +9 -6
  167. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +83 -68
  168. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +8 -3
  169. package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +20 -3
  170. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +16 -7
  171. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +2 -1
  172. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +137 -0
  173. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +11 -10
  174. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +21 -11
  175. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +12 -1
  176. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +13 -10
  177. package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +1 -1
  178. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +17 -12
  179. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +56 -13
  180. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +58 -0
  181. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +2 -2
  182. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +1 -1
  183. package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +25 -1
  184. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +4 -4
  185. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/route.mjs +2 -2
  186. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +1 -1
  187. package/src/runtime/agent/orchestrator/tools/patch/read-redirects.mjs +34 -30
  188. package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +7 -7
  189. package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +1 -1
  190. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +78 -5
  191. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +2 -0
  192. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -0
  193. package/src/runtime/attachments/store.d.mts +3 -0
  194. package/src/runtime/attachments/store.mjs +38 -0
  195. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +1 -1
  196. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +16 -4
  197. package/src/runtime/channels/lib/worker-main.mjs +17 -14
  198. package/src/runtime/computer-bridge/linux-readiness.mjs +0 -2
  199. package/src/runtime/effort-judge/judge-client.mjs +56 -18
  200. package/src/runtime/memory/lib/memory.mjs +1 -1
  201. package/src/runtime/memory/lib/session-ingest.mjs +2 -2
  202. package/src/runtime/shared/atomic-file.mjs +33 -6
  203. package/src/runtime/shared/best-effort-stderr.mjs +8 -0
  204. package/src/runtime/shared/code-graph-state.mjs +12 -0
  205. package/src/runtime/shared/defaults-separation.mjs +2 -0
  206. package/src/runtime/shared/extension-scopes.mjs +5 -3
  207. package/src/runtime/shared/file-lock.mjs +37 -11
  208. package/src/runtime/shared/git-runner-worker.mjs +3 -2
  209. package/src/runtime/shared/git-runner.mjs +4 -3
  210. package/src/runtime/shared/llm/anthropic-effort.mjs +14 -0
  211. package/src/runtime/shared/llm/default-context-window.mjs +6 -0
  212. package/src/runtime/shared/llm/model-catalog-projection.mjs +5 -4
  213. package/src/runtime/shared/llm/model-catalog.mjs +240 -130
  214. package/src/runtime/shared/llm/provider-catalog-cache.mjs +36 -28
  215. package/src/runtime/shared/llm/provider-catalog-registry.mjs +162 -0
  216. package/src/runtime/shared/llm/provider-seed-rosters.mjs +130 -0
  217. package/src/runtime/shared/model-proxy-mode.d.mts +3 -0
  218. package/src/runtime/shared/model-proxy-mode.mjs +19 -0
  219. package/src/runtime/shared/plugin-manifest.mjs +18 -9
  220. package/src/runtime/shared/pristine-execution-contract.json +9 -2
  221. package/src/runtime/shared/pristine-execution.mjs +28 -4
  222. package/src/runtime/shared/provider-accounts.mjs +125 -21
  223. package/src/runtime/shared/provider-api-key.mjs +33 -1
  224. package/src/runtime/shared/schedule-model-ref.mjs +11 -2
  225. package/src/runtime/shared/session-usage-totals.mjs +19 -0
  226. package/src/runtime/shared/sharp-electron-linux.mjs +25 -0
  227. package/src/runtime/shared/shell-display.mjs +9 -3
  228. package/src/runtime/shared/singleton-owner.mjs +36 -6
  229. package/src/runtime/shared/skill-mentions.d.mts +10 -0
  230. package/src/runtime/shared/skill-mentions.mjs +133 -0
  231. package/src/runtime/shared/skill-script-runtime.mjs +264 -0
  232. package/src/runtime/shared/statusline/statusline-segments.mjs +68 -14
  233. package/src/runtime/shared/tool-primitives.mjs +35 -2
  234. package/src/runtime/shared/tool-surface.mjs +7 -0
  235. package/src/runtime/shared/tool-work-units.mjs +14 -0
  236. package/src/runtime/shared/transcript-metadata.mjs +17 -1
  237. package/src/runtime/shared/turn-changes.mjs +791 -0
  238. package/src/runtime/shared/turn-snapshot-store.mjs +346 -12
  239. package/src/runtime/shared/turn-snapshot.mjs +690 -81
  240. package/src/runtime/shared/turn-worktree-snapshot.mjs +185 -15
  241. package/src/runtime/shared/worker-requests.mjs +15 -2
  242. package/src/runtime/shared/worktrees/cache-cleanups.mjs +52 -0
  243. package/src/runtime/shared/worktrees/contract.mjs +551 -0
  244. package/src/runtime/shared/worktrees/disk.mjs +57 -0
  245. package/src/runtime/shared/worktrees/git.mjs +247 -0
  246. package/src/runtime/shared/worktrees/include.mjs +480 -0
  247. package/src/runtime/shared/worktrees/lock.mjs +46 -0
  248. package/src/runtime/shared/worktrees/maintenance.mjs +96 -0
  249. package/src/runtime/shared/worktrees/manager.mjs +1215 -0
  250. package/src/runtime/shared/worktrees/merge.mjs +182 -0
  251. package/src/runtime/shared/worktrees/owner.mjs +46 -0
  252. package/src/runtime/shared/worktrees/paths.mjs +62 -0
  253. package/src/runtime/shared/worktrees/registry.mjs +110 -0
  254. package/src/runtime/shared/worktrees/settings.mjs +84 -0
  255. package/src/runtime/shared/worktrees/slug.mjs +62 -0
  256. package/src/runtime/shared/worktrees/test-support.mjs +96 -0
  257. package/src/runtime/visualize-bridge/client.mjs +181 -0
  258. package/src/runtime/visualize-bridge/tool-defs.mjs +97 -0
  259. package/src/session-runtime/automation-agents.mjs +8 -2
  260. package/src/session-runtime/boot/apis.mjs +90 -0
  261. package/src/session-runtime/boot/config.mjs +7 -2
  262. package/src/session-runtime/boot/facade.mjs +21 -0
  263. package/src/session-runtime/boot/infrastructure.mjs +30 -18
  264. package/src/session-runtime/boot/session.mjs +1 -0
  265. package/src/session-runtime/boot/tools.mjs +4 -0
  266. package/src/session-runtime/config-lifecycle.mjs +10 -1
  267. package/src/session-runtime/conversation-import/discovery.mjs +698 -0
  268. package/src/session-runtime/conversation-import/service.mjs +369 -0
  269. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +21 -6
  270. package/src/session-runtime/cwd-plugins/plugin-status.mjs +47 -42
  271. package/src/session-runtime/global-extensions.mjs +2 -2
  272. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +27 -2
  273. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +32 -1
  274. package/src/session-runtime/internal-tool-executor.mjs +9 -1
  275. package/src/session-runtime/lifecycle/session-catalog.mjs +8 -1
  276. package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +5 -0
  277. package/src/session-runtime/lifecycle/session-switch/resume.mjs +21 -3
  278. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +16 -0
  279. package/src/session-runtime/mcp-glue/connect.mjs +68 -16
  280. package/src/session-runtime/mcp-glue/plugin-servers.mjs +499 -0
  281. package/src/session-runtime/mcp-glue/server-catalog.mjs +122 -33
  282. package/src/session-runtime/mcp-glue/server-input.mjs +29 -0
  283. package/src/session-runtime/mcp-glue.mjs +22 -2
  284. package/src/session-runtime/model-route/route-persist.mjs +3 -0
  285. package/src/session-runtime/model-route/set-route.mjs +15 -2
  286. package/src/session-runtime/new-session-config.mjs +3 -2
  287. package/src/session-runtime/notification-bus.mjs +18 -3
  288. package/src/session-runtime/provider-auth/accounts.mjs +33 -15
  289. package/src/session-runtime/provider-models/row-hydration.mjs +2 -0
  290. package/src/session-runtime/provider-models/web-search-catalog.mjs +5 -1
  291. package/src/session-runtime/provider-readiness.mjs +16 -10
  292. package/src/session-runtime/resource-extension-sync.mjs +45 -7
  293. package/src/session-runtime/resource-mcp-api.mjs +121 -84
  294. package/src/session-runtime/resource-plugins-api.mjs +29 -41
  295. package/src/session-runtime/resource-scope-decorate.mjs +6 -8
  296. package/src/session-runtime/resource-skills-api.mjs +23 -2
  297. package/src/session-runtime/route-resolution.mjs +10 -1
  298. package/src/session-runtime/route-state.mjs +16 -2
  299. package/src/session-runtime/runtime-core.mjs +5 -0
  300. package/src/session-runtime/runtime-facade.mjs +8 -0
  301. package/src/session-runtime/runtime-review-api.mjs +9 -0
  302. package/src/session-runtime/schedule-session-run.mjs +1 -1
  303. package/src/session-runtime/services/agent-tool/helpers.mjs +5 -0
  304. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +3 -0
  305. package/src/session-runtime/services/agent-tool/job-views/job-lookup.mjs +1 -0
  306. package/src/session-runtime/services/agent-tool/job-views/spawn-meta.mjs +1 -0
  307. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +15 -0
  308. package/src/session-runtime/services/agent-tool/send-flow.mjs +1 -0
  309. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +10 -2
  310. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -7
  311. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
  312. package/src/session-runtime/services/agent-tool/tag-registry.mjs +2 -1
  313. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -0
  314. package/src/session-runtime/services/agent-tool/worker-rows.mjs +1 -0
  315. package/src/session-runtime/services/agent-tool.mjs +1 -0
  316. package/src/session-runtime/services/channel-session-router.mjs +4 -1
  317. package/src/session-runtime/services/daemon-candidate.mjs +3 -2
  318. package/src/session-runtime/services/daemon-crash-capture/spawn-capture.mjs +55 -0
  319. package/src/session-runtime/services/hook-bus/command-handler.mjs +4 -1
  320. package/src/session-runtime/services/hook-bus/handlers.mjs +7 -3
  321. package/src/session-runtime/services/provider-admin.mjs +64 -18
  322. package/src/session-runtime/services/usage-dashboard-model.mjs +3 -0
  323. package/src/session-runtime/session-create/session-options.mjs +10 -1
  324. package/src/session-runtime/session-turn-api.mjs +1 -0
  325. package/src/session-runtime/settings-api.mjs +4 -2
  326. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -2
  327. package/src/session-runtime/settings-worktree-api.mjs +17 -0
  328. package/src/session-runtime/setup-tool/executor.mjs +23 -5
  329. package/src/session-runtime/setup-tool/extended-actions.mjs +1 -0
  330. package/src/session-runtime/setup-tool/settings-contract.mjs +4 -1
  331. package/src/session-runtime/setup-tool/tool-defs.mjs +38 -4
  332. package/src/session-runtime/skills-api.mjs +7 -3
  333. package/src/session-runtime/turn/agent-ops.mjs +12 -1
  334. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +14 -3
  335. package/src/session-runtime/turn/turn-run/snapshot-tracker.mjs +20 -15
  336. package/src/session-runtime/turn/turn-run/turn-open.mjs +4 -5
  337. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +29 -1
  338. package/src/session-runtime/turn/turn-run/turn-settle.mjs +6 -1
  339. package/src/session-runtime/turn/turn-run.mjs +6 -7
  340. package/src/session-runtime/webhook-session-run.mjs +16 -4
  341. package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -3
  342. package/src/session-runtime/workspace/admission-gate.mjs +197 -0
  343. package/src/session-runtime/workspace/project-branches.mjs +136 -0
  344. package/src/session-runtime/workspace/transition-steps.mjs +364 -0
  345. package/src/session-runtime/workspace/transitions.mjs +651 -0
  346. package/src/session-runtime/workspace/workspace-state.mjs +112 -0
  347. package/src/skill-script-entry.mjs +36 -0
  348. package/src/standalone/channel-transport/http-routes.mjs +17 -0
  349. package/src/standalone/channel-transport.mjs +13 -1
  350. package/src/standalone/daemon-desktop-runtime.mjs +1 -0
  351. package/src/standalone/daemon-owner.mjs +372 -0
  352. package/src/standalone/daemon-stored-session-views.mjs +15 -4
  353. package/src/standalone/daemon.mjs +179 -8
  354. package/src/standalone/desktop-service-registry.mjs +20 -0
  355. package/src/standalone/local-session-runtime.mjs +11 -0
  356. package/src/standalone/session-client/attachment-calls.mjs +3 -1
  357. package/src/standalone/session-client/daemon-unavailable.mjs +62 -0
  358. package/src/standalone/session-client/lease-heartbeat.mjs +47 -0
  359. package/src/standalone/session-client.mjs +104 -13
  360. package/src/standalone/session-protocol.mjs +21 -0
  361. package/src/standalone/session-proxy.mjs +3 -0
  362. package/src/standalone/session-service/agent-tree/agent-child.mjs +4 -0
  363. package/src/standalone/session-service/agent-tree/agent-rehydrate.mjs +1 -0
  364. package/src/standalone/session-service/entries/lifecycle.mjs +6 -0
  365. package/src/standalone/session-service/projection/revisions.mjs +2 -0
  366. package/src/standalone/session-service/projection/transcript-window.mjs +149 -19
  367. package/src/standalone/session-service/projection/workspace.mjs +14 -0
  368. package/src/standalone/session-service/session-calls/session-action.mjs +17 -4
  369. package/src/standalone/session-transport/call-route.mjs +13 -0
  370. package/src/standalone/session-transport/client-registry/client-grace.mjs +45 -3
  371. package/src/standalone/session-transport/client-registry/registration-replays.mjs +1 -0
  372. package/src/standalone/session-transport/client-registry.mjs +20 -3
  373. package/src/standalone/session-transport/routes.mjs +79 -2
  374. package/src/standalone/session-transport/stack.mjs +2 -0
  375. package/src/standalone/session-transport.mjs +24 -1
  376. package/src/tui/App.jsx +9 -1
  377. package/src/tui/app/app-view/floating-panel.jsx +9 -1
  378. package/src/tui/app/app-view.jsx +6 -0
  379. package/src/tui/app/extension-pickers/plugins-pickers.mjs +35 -16
  380. package/src/tui/app/message-selector.mjs +2 -1
  381. package/src/tui/app/model-options.mjs +37 -5
  382. package/src/tui/app/model-picker/provider-models-list.mjs +2 -2
  383. package/src/tui/app/model-picker.mjs +4 -2
  384. package/src/tui/app/skill-mention-palette.mjs +88 -0
  385. package/src/tui/app/slash-palette.mjs +23 -7
  386. package/src/tui/app/transcript-row-estimate.mjs +11 -3
  387. package/src/tui/app/use-prompt-draft-flow.mjs +7 -2
  388. package/src/tui/app/use-prompt-state.mjs +5 -0
  389. package/src/tui/components/Message.jsx +22 -1
  390. package/src/tui/components/PromptInput.jsx +21 -3
  391. package/src/tui/components/SlashCommandPalette.jsx +12 -3
  392. package/src/tui/components/TranscriptItem.jsx +9 -1
  393. package/src/tui/components/prompt-input/key-handler.mjs +21 -8
  394. package/src/tui/components/user-message-skills.mjs +38 -0
  395. package/src/tui/dist/index.mjs +839 -207
  396. package/src/tui/index.jsx +10 -2
  397. package/src/tui/session/compaction-view.mjs +147 -0
  398. package/src/tui/session/context-state.mjs +6 -0
  399. package/src/tui/session/initial-state.mjs +3 -0
  400. package/src/tui/session/queue-helpers.mjs +7 -2
  401. package/src/tui/session/quota-resume.mjs +2 -1
  402. package/src/tui/session/session-api/commands.mjs +10 -2
  403. package/src/tui/session/session-api/controls.mjs +4 -0
  404. package/src/tui/session/session-api/extensions.mjs +4 -2
  405. package/src/tui/session/session-api/intake/rewind.mjs +2 -1
  406. package/src/tui/session/session-api/integrations.mjs +17 -0
  407. package/src/tui/session/session-api/lifecycle/dispose.mjs +6 -1
  408. package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -0
  409. package/src/tui/session/session-api/lifecycle/resume.mjs +3 -0
  410. package/src/tui/session/session-api/routes.mjs +10 -1
  411. package/src/tui/session/session-api/settings.mjs +9 -0
  412. package/src/tui/session/session-api/worktree.mjs +60 -0
  413. package/src/tui/session/session-api.mjs +2 -0
  414. package/src/tui/session/session-flow/auto-clear.mjs +31 -12
  415. package/src/tui/session/session-flow/drain.mjs +27 -3
  416. package/src/tui/session/session-flow/session-reset.mjs +4 -3
  417. package/src/tui/session/session-flow.mjs +2 -0
  418. package/src/tui/session/transcript-intake.mjs +14 -5
  419. package/src/tui/session/tui-steering-persist.mjs +25 -9
  420. package/src/tui/session/turn-stream.mjs +32 -4
  421. package/src/tui/session/turn-tool-cards.mjs +10 -2
  422. package/src/tui/session/turn.mjs +56 -10
  423. package/src/runtime/effort-judge/previous-human-request.mjs +0 -162
  424. package/src/runtime/effort-judge/verified-context-input.mjs +0 -10
@@ -1,7 +1,7 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
2
  import { readFileSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
- import { updateJsonAtomicSync } from './atomic-file.mjs';
4
+ import { parseJsonText, updateJsonAtomic } from './atomic-file.mjs';
5
5
  import { resolvePluginData } from './plugin-paths.mjs';
6
6
 
7
7
  export const ACCOUNT_PROVIDERS = Object.freeze([
@@ -9,6 +9,7 @@ export const ACCOUNT_PROVIDERS = Object.freeze([
9
9
  'anthropic-oauth',
10
10
  'grok-oauth',
11
11
  'muse-code-oauth',
12
+ 'command-code-oauth',
12
13
  'mimo-oauth',
13
14
  'cursor-oauth',
14
15
  'antigravity-oauth',
@@ -48,7 +49,7 @@ function validate(raw) {
48
49
 
49
50
  function read(dataDir) {
50
51
  try {
51
- return validate(JSON.parse(readFileSync(file(dataDir), 'utf8')));
52
+ return validate(parseJsonText(readFileSync(file(dataDir), 'utf8')));
52
53
  } catch (error) {
53
54
  if (error.code === 'ENOENT') return validate(null);
54
55
  throw error;
@@ -74,16 +75,39 @@ function normalizeLabels(pool) {
74
75
  return { ...pool, accounts };
75
76
  }
76
77
 
78
+ // How the roster picks an account. `manual` serves from the selected account
79
+ // only; `limit` keeps the selected account until it reaches a limit and then
80
+ // moves to the account with the most headroom; `headroom` starts every new
81
+ // session on the account with the most headroom and keeps it for that session.
82
+ export const ACCOUNT_POLICIES = Object.freeze(['manual', 'limit', 'headroom']);
83
+
84
+ // `auto` predates `policy` and is still written beside it, so a build that
85
+ // knows only `auto` reads the same switching on/off. Whenever the two
86
+ // disagree (an older build toggled `auto`), `auto` decides whether switching
87
+ // is on at all.
88
+ export function providerAccountPolicy(pool) {
89
+ if (pool?.auto === false) return 'manual';
90
+ return pool?.policy === 'headroom' ? 'headroom' : 'limit';
91
+ }
92
+
93
+ function normalizePool(pool) {
94
+ if (!pool) return pool;
95
+ const policy = providerAccountPolicy(pool);
96
+ return { ...normalizeLabels(pool), policy, auto: policy !== 'manual' };
97
+ }
98
+
77
99
  export function readProviderAccountPool(provider, dataDir = resolvePluginData()) {
78
100
  requireAccountProvider(provider);
79
- return normalizeLabels(read(dataDir).providers[provider]) || { accounts: [], selectedId: null, auto: true };
101
+ return (
102
+ normalizePool(read(dataDir).providers[provider]) || { accounts: [], selectedId: null, auto: true, policy: 'limit' }
103
+ );
80
104
  }
81
105
 
82
- function update(provider, mutate) {
106
+ async function update(provider, mutate) {
83
107
  requireAccountProvider(provider);
84
108
  // Read failures must not be interpreted as an empty account roster.
85
109
  read();
86
- const result = updateJsonAtomicSync(
110
+ const result = await updateJsonAtomic(
87
111
  file(),
88
112
  (raw) => {
89
113
  const state = validate(raw);
@@ -94,7 +118,7 @@ function update(provider, mutate) {
94
118
  },
95
119
  { mode: 0o600, secret: true, fsyncDir: true }
96
120
  );
97
- return normalizeLabels(result.providers[provider]);
121
+ return normalizePool(result.providers[provider]);
98
122
  }
99
123
 
100
124
  /**
@@ -117,7 +141,7 @@ export function newProviderAccountId() {
117
141
  return randomUUID();
118
142
  }
119
143
 
120
- export function registerProviderAccount(provider, id, { label = '', includeDefault = false } = {}) {
144
+ export async function registerProviderAccount(provider, id, { label = '', includeDefault = false } = {}) {
121
145
  requireAccountId(id);
122
146
  return update(provider, (pool) => {
123
147
  if (includeDefault && !pool.accounts.some((row) => row.id === 'default')) {
@@ -131,12 +155,12 @@ export function registerProviderAccount(provider, id, { label = '', includeDefau
131
155
  });
132
156
  }
133
157
 
134
- export function changeProviderAccounts(provider, change) {
158
+ export async function changeProviderAccounts(provider, change) {
135
159
  if (
136
160
  !change ||
137
161
  typeof change !== 'object' ||
138
162
  Array.isArray(change) ||
139
- Object.keys(change).some((key) => !['selectedId', 'order', 'auto', 'useCredits', 'rename'].includes(key))
163
+ Object.keys(change).some((key) => !['selectedId', 'order', 'auto', 'policy', 'useCredits', 'rename'].includes(key))
140
164
  ) {
141
165
  throw new TypeError('Invalid provider account change.');
142
166
  }
@@ -175,8 +199,19 @@ export function changeProviderAccounts(provider, change) {
175
199
  }
176
200
  if (change.auto !== undefined) {
177
201
  if (typeof change.auto !== 'boolean') throw new TypeError('Automatic account switching must be boolean.');
202
+ // Turning switching on keeps a non-manual policy; on from manual is the
203
+ // classic switch-at-limit behaviour.
204
+ const current = providerAccountPolicy(pool);
205
+ if (!change.auto) pool.policy = 'manual';
206
+ else if (current === 'manual') pool.policy = 'limit';
207
+ else pool.policy = current;
178
208
  pool.auto = change.auto;
179
209
  }
210
+ if (change.policy !== undefined) {
211
+ if (!ACCOUNT_POLICIES.includes(change.policy)) throw new TypeError('Unknown account switching policy.');
212
+ pool.policy = change.policy;
213
+ pool.auto = change.policy !== 'manual';
214
+ }
180
215
  if (change.useCredits !== undefined) {
181
216
  if (typeof change.useCredits !== 'boolean') throw new TypeError('Credit use must be boolean.');
182
217
  pool.useCredits = change.useCredits;
@@ -184,7 +219,24 @@ export function changeProviderAccounts(provider, change) {
184
219
  });
185
220
  }
186
221
 
187
- export function removeProviderAccount(provider, id) {
222
+ // Selects `id` only while the selection is still `expectedSelectedId`, checked
223
+ // under the write's own lock, so a newer selection (a manual switch) wins. A
224
+ // given `expectedPolicy` must also still hold: a policy change while the
225
+ // request ran (e.g. to Manual) cancels the commit.
226
+ // Returns the pool when the selection moved, otherwise null.
227
+ export async function selectProviderAccountIf(provider, expectedSelectedId, id, expectedPolicy) {
228
+ requireAccountId(id);
229
+ let moved = false;
230
+ const pool = await update(provider, (pool) => {
231
+ if (pool.selectedId !== expectedSelectedId || !pool.accounts.some((row) => row.id === id)) return;
232
+ if (expectedPolicy && providerAccountPolicy(pool) !== expectedPolicy) return;
233
+ pool.selectedId = id;
234
+ moved = true;
235
+ });
236
+ return moved ? pool : null;
237
+ }
238
+
239
+ export async function removeProviderAccount(provider, id) {
188
240
  requireAccountId(id);
189
241
  return update(provider, (pool) => {
190
242
  pool.accounts = pool.accounts.filter((row) => row.id !== id);
@@ -192,7 +244,7 @@ export function removeProviderAccount(provider, id) {
192
244
  });
193
245
  }
194
246
 
195
- export function recordProviderAccountUsage(provider, id, snapshot) {
247
+ export async function recordProviderAccountUsage(provider, id, snapshot) {
196
248
  if (!snapshot || !Array.isArray(snapshot.quotaWindows)) return;
197
249
  const windows = snapshot.quotaWindows.slice(0, 12).map((row) => ({
198
250
  label: String(row.label || '').slice(0, 40),
@@ -200,7 +252,7 @@ export function recordProviderAccountUsage(provider, id, snapshot) {
200
252
  resetAt: typeof row.resetAt === 'number' && Number.isFinite(row.resetAt) ? row.resetAt : null,
201
253
  }));
202
254
  if (!readProviderAccountPool(provider).accounts.some((row) => row.id === id)) return;
203
- update(provider, (pool) => {
255
+ await update(provider, (pool) => {
204
256
  const row = pool.accounts.find((entry) => entry.id === id);
205
257
  if (!row) return;
206
258
  row.usage = { windows, checkedAt: Date.now() };
@@ -225,7 +277,7 @@ const MAX_BLOCK_MS = 6 * 60 * 60_000;
225
277
  // early can tell us anything.
226
278
  // `options.quota` records that the block came from a typed quota refusal or an
227
279
  // exhausted usage window, so later sends still count it as quota evidence.
228
- export function blockProviderAccount(provider, id, until, model = '', source = 'inferred', options = {}) {
280
+ export async function blockProviderAccount(provider, id, until, model = '', source = 'inferred', options = {}) {
229
281
  return update(provider, (pool) => {
230
282
  const row = pool.accounts.find((entry) => entry.id === id);
231
283
  if (!row) return;
@@ -245,7 +297,7 @@ export function blockProviderAccount(provider, id, until, model = '', source = '
245
297
  // the refusal window and the meter recorded against the OLD subscription would
246
298
  // otherwise outlive it and refuse a request the provider would have served.
247
299
  // Both are dropped; the next usage reading refills them.
248
- export function clearProviderAccountQuotaState(provider, id) {
300
+ export async function clearProviderAccountQuotaState(provider, id) {
249
301
  return update(provider, (pool) => {
250
302
  const row = pool.accounts.find((entry) => entry.id === id);
251
303
  if (!row) return;
@@ -318,14 +370,53 @@ export function providerAccountsResetAt(rows, now = Date.now(), model = '', opti
318
370
  return known.length ? Math.min(...known) : null;
319
371
  }
320
372
 
373
+ function windowFull(row, window, now) {
374
+ return window.usedPct >= 100 && (window.resetAt ? window.resetAt > now : now - row.usage.checkedAt < 60_000);
375
+ }
376
+
321
377
  export function providerAccountExhausted(row, now = Date.now(), model = '', options = {}) {
322
378
  if (row?.blockedUntil > now && blockAppliesToModel(row, model)) return true;
323
379
  if (options.useCredits === true) return false;
324
380
  return (row?.usage?.windows || []).some(
325
- (window) =>
326
- window.usedPct >= 100 &&
327
- quotaWindowAppliesToModel(window, model) &&
328
- (window.resetAt ? window.resetAt > now : now - row.usage.checkedAt < 60_000)
381
+ (window) => quotaWindowAppliesToModel(window, model) && windowFull(row, window, now)
382
+ );
383
+ }
384
+
385
+ function quotaWindowFamily(window) {
386
+ return FAMILY_QUOTA_WINDOW.exec(String(window?.label || '').trim())?.[1] || '';
387
+ }
388
+
389
+ // Which limit currently holds the account, independent of any one model:
390
+ // `{ scope: 'account' }` when every request is refused (an unscoped window is
391
+ // full, or a block with no narrower meter to attribute it to), `{ scope:
392
+ // 'model', families }` when only model-family meters ("7D Fable") or a block
393
+ // attributed to one are spent, so other models still run — else null.
394
+ export function providerAccountLimit(row, now = Date.now(), options = {}) {
395
+ if (!row) return null;
396
+ const windows = row.usage?.windows || [];
397
+ const blocked = row.blockedUntil > now;
398
+ const scope = String(row.blockedModel || '').trim();
399
+ const scopedWindows = windows.filter((window) => quotaWindowFamily(window));
400
+ // A block counts as family-scoped only when a family meter covers the model
401
+ // that earned it; any other refusal is treated as the account's.
402
+ const covering = scope ? scopedWindows.filter((window) => quotaWindowAppliesToModel(window, scope)) : [];
403
+ if (blocked && !covering.length) return { scope: 'account' };
404
+ const credits = options.useCredits === true;
405
+ if (!credits && windows.some((window) => !quotaWindowFamily(window) && windowFull(row, window, now))) {
406
+ return { scope: 'account' };
407
+ }
408
+ const families = new Set(blocked ? covering.map(quotaWindowFamily) : []);
409
+ if (!credits) {
410
+ for (const window of scopedWindows) if (windowFull(row, window, now)) families.add(quotaWindowFamily(window));
411
+ }
412
+ return families.size ? { scope: 'model', families: [...families] } : null;
413
+ }
414
+
415
+ // The account refuses `model` only through a model-scoped limit: it still
416
+ // serves other models, so it remains the provider's account of record.
417
+ export function providerAccountExhaustedForModelOnly(row, now = Date.now(), model = '', options = {}) {
418
+ return (
419
+ providerAccountExhausted(row, now, model, options) && providerAccountLimit(row, now, options)?.scope === 'model'
329
420
  );
330
421
  }
331
422
 
@@ -347,15 +438,28 @@ function accountHeadroom(row, now, model) {
347
438
  return used.length ? 100 - Math.max(...used) : null;
348
439
  }
349
440
 
350
- export function chooseProviderAccount(pool, excluded = new Set(), now = Date.now(), model = '') {
441
+ // `sessionAccountId` is the account the requesting session already runs on
442
+ // (headroom policy only): kept while usable, so a session switches only on a
443
+ // limit. Without one, the session's first request takes the account with the
444
+ // most headroom.
445
+ export function chooseProviderAccount(
446
+ pool,
447
+ excluded = new Set(),
448
+ now = Date.now(),
449
+ model = '',
450
+ { sessionAccountId } = {}
451
+ ) {
351
452
  const options = { useCredits: pool.useCredits === true };
352
453
  const usable = (row) => row && !excluded.has(row.id) && !providerAccountExhausted(row, now, model, options);
353
454
  const selected = pool.accounts.find((row) => row.id === pool.selectedId);
354
- if (pool.auto === false) {
455
+ const policy = providerAccountPolicy(pool);
456
+ if (policy === 'manual') {
355
457
  if (excluded.has(selected?.id)) return null;
356
458
  return options.useCredits || usable(selected) ? selected || null : null;
357
459
  }
358
- if (usable(selected)) return selected;
460
+ const preferred =
461
+ policy === 'headroom' ? pool.accounts.find((row) => sessionAccountId && row.id === sessionAccountId) : selected;
462
+ if (usable(preferred)) return preferred;
359
463
  // Falling back in roster order is blind: the account after the one that just
360
464
  // refused may itself be nearly spent, so the request pays a second switch to
361
465
  // learn what a recorded meter already knew. Ranking needs evidence for EVERY
@@ -1,7 +1,8 @@
1
1
  import { createRequire } from 'node:module';
2
+ import { modelProxyClientMode } from './model-proxy-mode.mjs';
2
3
 
3
4
  const require = createRequire(import.meta.url);
4
- const { getSecret, hasSecret } = require('../../lib/keychain-cjs.cjs');
5
+ const { getSecret, getSecretAsync, hasSecret } = require('../../lib/keychain-cjs.cjs');
5
6
 
6
7
  // Lightweight API-key lookup shared by pristine startup and the general config
7
8
  // loader. This module deliberately has no mixdog-config.json dependency: asking
@@ -41,6 +42,8 @@ function environmentApiKey(provider) {
41
42
  /** Presence-only credential probe. On Windows this is a cheap DPAPI-file
42
43
  * check; decrypting through PowerShell is reserved for an actual API call. */
43
44
  export function hasAgentApiKey(provider) {
45
+ // Remote mode: this server holds no model credentials (model-proxy-mode.mjs).
46
+ if (modelProxyClientMode()) return false;
44
47
  const id = String(provider || '').trim();
45
48
  if (!id) return false;
46
49
  if (environmentApiKey(id)) return true;
@@ -52,6 +55,7 @@ export function hasAgentApiKey(provider) {
52
55
  }
53
56
 
54
57
  export function getAgentApiKey(provider) {
58
+ if (modelProxyClientMode()) return null;
55
59
  const id = String(provider || '').trim();
56
60
  const environment = environmentApiKey(id);
57
61
  if (environment) return environment;
@@ -64,3 +68,31 @@ export function getAgentApiKey(provider) {
64
68
  }
65
69
  return null;
66
70
  }
71
+
72
+ /** The one API-key resolver for key-based providers: an explicit config key
73
+ * wins, otherwise the environment/keychain (getAgentApiKey). */
74
+ export function resolveProviderApiKey(provider, config) {
75
+ return config?.apiKey || getAgentApiKey(provider) || '';
76
+ }
77
+
78
+ /** Auth-error reload: the stored/environment key is re-read first so a
79
+ * rotated key beats the stale one captured in config. */
80
+ export function reloadProviderApiKey(provider, config) {
81
+ return getAgentApiKey(provider) || config?.apiKey || '';
82
+ }
83
+
84
+ /** One provider's key read on demand, warming no other account: the
85
+ * environment first (getAgentApiKey's precedence), then only this provider's
86
+ * keychain entry through the async path (no whole-keyring snapshot). */
87
+ export async function readAgentApiKeyAsync(provider) {
88
+ if (modelProxyClientMode()) return null;
89
+ const id = String(provider || '').trim();
90
+ if (!id) return null;
91
+ const environment = environmentApiKey(id);
92
+ if (environment) return environment;
93
+ try {
94
+ return (await getSecretAsync(`agent.${id}.apiKey`)) || null;
95
+ } catch {
96
+ return null;
97
+ }
98
+ }
@@ -1,7 +1,8 @@
1
1
  // schedule.model wire format shared by the channels-worker scheduler and the
2
2
  // engine-side run-now dispatch: either a config.presets id/name (legacy) or a
3
- // direct "provider/model[@effort][+fast][?parameter=value]" route string written
4
- // by the desktop schedule editor. Slash-form values become direct route
3
+ // direct "provider/model[@effort][+fast][+noauto][?parameter=value]" route string
4
+ // written by the desktop schedule editor (`+noauto`: Auto effort off for the
5
+ // route; absent means on). Slash-form values become direct route
5
6
  // objects, which agent-dispatch consumes without a presets lookup.
6
7
  // The optional route fields an automation run carries, from a preset, a
7
8
  // maintenance slot or a parsed route ref. One shape so schedule and webhook
@@ -13,6 +14,7 @@ export function modelRouteFields(slot = {}) {
13
14
  model: slot.model,
14
15
  ...(slot.effort ? { effort: slot.effort } : {}),
15
16
  ...(slot.fast === true ? { fast: true } : {}),
17
+ ...(slot.autoEffort === false ? { autoEffort: false } : {}),
16
18
  ...(slot.modelParameters ? { modelParameters: { ...slot.modelParameters } } : {}),
17
19
  };
18
20
  }
@@ -24,6 +26,7 @@ export function formatScheduleModelRef(route) {
24
26
  `${route.provider}/${route.model}` +
25
27
  (route.effort ? `@${route.effort}` : '') +
26
28
  (route.fast === true ? '+fast' : '') +
29
+ (route.autoEffort === false ? '+noauto' : '') +
27
30
  (parameters.length ? `?${new URLSearchParams(parameters).toString()}` : '')
28
31
  );
29
32
  }
@@ -37,6 +40,11 @@ export function parseScheduleModelRef(ref) {
37
40
  if (slash <= 0) return raw;
38
41
  let rest = routeRef.slice(slash + 1);
39
42
  let fast = false;
43
+ let autoEffort = true;
44
+ if (rest.endsWith('+noauto')) {
45
+ autoEffort = false;
46
+ rest = rest.slice(0, -7);
47
+ }
40
48
  if (rest.endsWith('+fast')) {
41
49
  fast = true;
42
50
  rest = rest.slice(0, -5);
@@ -52,6 +60,7 @@ export function parseScheduleModelRef(ref) {
52
60
  model: rest,
53
61
  ...(effort ? { effort } : {}),
54
62
  ...(fast ? { fast: true } : {}),
63
+ ...(autoEffort ? {} : { autoEffort: false }),
55
64
  ...(query ? { modelParameters: Object.fromEntries(new URLSearchParams(query)) } : {}),
56
65
  };
57
66
  }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Cumulative per-session accounting as agent pool rows publish it, read off
3
+ * the counters the session already persists (totalInputTokens /
4
+ * totalOutputTokens / totalToolCalls, advanced once per provider response).
5
+ * A counter the session never recorded stays unknown (null), not zero.
6
+ */
7
+ const counter = (value) => (Number.isFinite(value) && value >= 0 ? value : null);
8
+
9
+ export function sessionUsageTotals(session) {
10
+ const input = counter(session?.totalInputTokens);
11
+ const output = counter(session?.totalOutputTokens);
12
+ return {
13
+ usage:
14
+ input === null && output === null
15
+ ? null
16
+ : { inputTokens: input ?? 0, outputTokens: output ?? 0, totalTokens: (input ?? 0) + (output ?? 0) },
17
+ toolCalls: counter(session?.totalToolCalls),
18
+ };
19
+ }
@@ -0,0 +1,25 @@
1
+ // Route sharp to its WebAssembly build when this process runs on Electron's
2
+ // Linux binary (the desktop daemon runs as ELECTRON_RUN_AS_NODE).
3
+ //
4
+ // The Electron executable links the system libglib/libgobject for GTK, so they
5
+ // sit in the global symbol scope before sharp loads. sharp's native addon then
6
+ // binds g_object_* to the system GObject instead of the copy bundled in its
7
+ // libvips, and the first pipeline dies with SIGSEGV inside g_object_unref —
8
+ // one pasted image restarted the whole daemon. sharp itself warns about this
9
+ // (SharpElectronLinux). The wasm build links nothing from the host, so it is
10
+ // immune; plain Node keeps the faster native addon.
11
+ import { registerHooks } from 'node:module';
12
+
13
+ const NATIVE_SHARP = /^@img\/sharp-linux(?:musl)?-[^/]+\/sharp\.node$/;
14
+
15
+ export function sharpElectronLinuxRedirect(specifier) {
16
+ return NATIVE_SHARP.test(specifier) ? '@img/sharp-wasm32/sharp.node' : null;
17
+ }
18
+
19
+ if (process.versions.electron && process.platform === 'linux') {
20
+ registerHooks({
21
+ resolve(specifier, context, nextResolve) {
22
+ return nextResolve(sharpElectronLinuxRedirect(specifier) || specifier, context);
23
+ },
24
+ });
25
+ }
@@ -11,9 +11,15 @@
11
11
  export const POWERSHELL_UTF8_PREFIX =
12
12
  '[Console]::OutputEncoding=[System.Text.Encoding]::UTF8; $OutputEncoding=New-Object System.Text.UTF8Encoding $false;';
13
13
 
14
+ // The `mixdog-skill-node` definition PowerShell commands carry next
15
+ // (renderPowerShellSkillFunction) is the same kind of execution detail.
16
+ const SKILL_FUNCTION_LINE = /^[ \t]*function mixdog-skill-node \{[^\r\n]*\r?\n/;
17
+
14
18
  export function displayShellCommand(value) {
15
19
  const text = String(value ?? '');
16
- const trimmed = text.trimStart();
17
- if (!trimmed.startsWith(POWERSHELL_UTF8_PREFIX)) return text;
18
- return trimmed.slice(POWERSHELL_UTF8_PREFIX.length).replace(/^[ \t]*\r?\n/, '');
20
+ let rest = text.trimStart();
21
+ const prefixed = rest.startsWith(POWERSHELL_UTF8_PREFIX);
22
+ if (prefixed) rest = rest.slice(POWERSHELL_UTF8_PREFIX.length).replace(/^[ \t]*\r?\n/, '');
23
+ const withoutFunction = rest.replace(SKILL_FUNCTION_LINE, '');
24
+ return prefixed || withoutFunction !== rest ? withoutFunction : text;
19
25
  }
@@ -13,6 +13,9 @@ function writeJsonAtomic(path, value) {
13
13
 
14
14
  function acquireClaimLock(path) {
15
15
  const lockPath = `${path}.claim.lock`;
16
+ // A first boot (e.g. a daemon an ssh bootstrap starts on a fresh server)
17
+ // has no data directory yet: the lock's parent must exist.
18
+ mkdirSync(dirname(path), { recursive: true });
16
19
  for (let i = 0; i < 20; i++) {
17
20
  try {
18
21
  mkdirSync(lockPath, { recursive: false });
@@ -32,22 +35,32 @@ function acquireClaimLock(path) {
32
35
  return null;
33
36
  }
34
37
 
35
- export function readSingletonOwner(path) {
38
+ // A live pid alone does not prove the record's process still owns it: the OS
39
+ // reuses pids. `isStale(owner)` lets a caller with a stronger proof (the
40
+ // daemon's published discovery) reject a live-pid record; `stale` reports it.
41
+ export function readSingletonOwner(path, { isStale = null } = {}) {
36
42
  const owner = readJsonSafe(path);
37
- if (!owner || typeof owner !== 'object') return { owner: null, alive: false };
38
- return { owner, alive: isPidAlive(owner.pid) };
43
+ if (!owner || typeof owner !== 'object') return { owner: null, alive: false, stale: false };
44
+ const pidAlive = isPidAlive(owner.pid);
45
+ const stale = pidAlive && typeof isStale === 'function' && isStale(owner) === true;
46
+ return { owner, alive: pidAlive && !stale, stale };
39
47
  }
40
48
 
41
- export function claimSingletonOwner(path, { kind = 'runtime', pid = process.pid, meta = {} } = {}) {
49
+ export function claimSingletonOwner(path, { kind = 'runtime', pid = process.pid, meta = {}, isStale = null } = {}) {
42
50
  const owner = {
43
51
  kind,
44
52
  pid,
45
53
  claimedAt: new Date().toISOString(),
46
54
  ...meta,
47
55
  };
56
+ const livePeer = (current) =>
57
+ Boolean(current?.pid) &&
58
+ Number(current.pid) !== Number(pid) &&
59
+ isPidAlive(current.pid) &&
60
+ !(typeof isStale === 'function' && isStale(current) === true);
48
61
 
49
62
  const current = readJsonSafe(path);
50
- if (current?.pid && Number(current.pid) !== Number(pid) && isPidAlive(current.pid)) {
63
+ if (livePeer(current)) {
51
64
  return { owned: false, owner: current };
52
65
  }
53
66
 
@@ -55,7 +68,7 @@ export function claimSingletonOwner(path, { kind = 'runtime', pid = process.pid,
55
68
  if (!lockPath) return { owned: false, owner: readJsonSafe(path) };
56
69
  try {
57
70
  const lockedCurrent = readJsonSafe(path);
58
- if (lockedCurrent?.pid && Number(lockedCurrent.pid) !== Number(pid) && isPidAlive(lockedCurrent.pid)) {
71
+ if (livePeer(lockedCurrent)) {
59
72
  return { owned: false, owner: lockedCurrent };
60
73
  }
61
74
  writeJsonAtomic(path, owner);
@@ -76,6 +89,23 @@ export function releaseSingletonOwner(path, pid = process.pid) {
76
89
  }
77
90
  }
78
91
 
92
+ // Rewrite the caller's own record (`owner.pid`) under the claim lock: when it
93
+ // still names that pid or is gone, it becomes `owner` ('refreshed'); a record
94
+ // naming another pid is reported, never overwritten ('superseded'); a claim
95
+ // lock held past its wait leaves the record alone ('busy').
96
+ export function refreshSingletonOwner(path, owner) {
97
+ const lockPath = acquireClaimLock(path);
98
+ if (!lockPath) return { status: 'busy', owner: null };
99
+ try {
100
+ const current = readJsonSafe(path);
101
+ if (current?.pid && Number(current.pid) !== Number(owner.pid)) return { status: 'superseded', owner: current };
102
+ writeJsonAtomic(path, owner);
103
+ return { status: 'refreshed', owner };
104
+ } finally {
105
+ rmSync(lockPath, { recursive: true, force: true });
106
+ }
107
+ }
108
+
79
109
  // Atomic ownership handoff: replace the owner record from fromPid to toOwner
80
110
  // under the SAME claim lock, so a concurrent claimSingletonOwner() cannot slip
81
111
  // between a release() and a re-claim() and fork a competing daemon. Verifies
@@ -0,0 +1,10 @@
1
+ export interface SkillMention {
2
+ name: string;
3
+ start: number;
4
+ end: number;
5
+ }
6
+ export function isReservedMentionName(name: unknown): boolean;
7
+ export function isInMarkdownCode(text: unknown, offset: number): boolean;
8
+ export function skillMentionCandidates(text: unknown): SkillMention[];
9
+ export function skillMentions(text: unknown, isSkill: (name: string) => boolean): SkillMention[];
10
+ export function mentionedSkillNames(text: unknown, isSkill: (name: string) => boolean): string[];
@@ -0,0 +1,133 @@
1
+ // Inline `$name` skill mentions: a skill can be named anywhere in a prompt.
2
+ // A candidate is `$` at the start of the text or after whitespace or an
3
+ // opening bracket/quote, followed by a skill-name token (the same
4
+ // lowercase-letters, digits and hyphens rule, 64 characters at most, that
5
+ // skill-document.mjs enforces). It selects a skill only when the token is
6
+ // exactly a known skill name, so `$HOME`, `$300` and unknown names stay prose.
7
+ // The token ends where no ASCII identifier character follows (`$pptx_env`,
8
+ // `$pptxUPPER` are not `pptx`); any other character, Korean particles
9
+ // included (`$pptx로`), ends it.
10
+ const MENTION = /(^|[\s([{"'`])\$([a-z0-9][a-z0-9-]{0,63})(?![A-Za-z0-9_-])/g;
11
+
12
+ // Common environment-variable names (compared case-insensitively) and
13
+ // numeric-only tokens are never inline mentions, even when a skill has that
14
+ // name: `$path` or `$300` in prose means the variable or the amount. Choosing
15
+ // such a skill explicitly (the `Skill:` header chip) still works.
16
+ const RESERVED_NAMES = new Set([
17
+ 'path',
18
+ 'home',
19
+ 'user',
20
+ 'shell',
21
+ 'pwd',
22
+ 'tmpdir',
23
+ 'temp',
24
+ 'tmp',
25
+ 'lang',
26
+ 'term',
27
+ 'xdg_config_home',
28
+ 'userprofile',
29
+ 'appdata',
30
+ 'localappdata',
31
+ 'programfiles',
32
+ 'systemroot',
33
+ 'windir',
34
+ 'comspec',
35
+ ]);
36
+
37
+ /** True when `name` can never be an inline mention (env var or number). */
38
+ export function isReservedMentionName(name) {
39
+ const value = String(name ?? '');
40
+ return /^[0-9]+$/.test(value) || RESERVED_NAMES.has(value.toLowerCase());
41
+ }
42
+
43
+ // Markdown code is never a mention: fenced blocks (``` or ~~~, 3+ chars,
44
+ // optional info string, closed by a fence of the same char at least as long,
45
+ // else running to the end) and inline code spans (a backtick run closed by
46
+ // the next run of exactly the same length; an unmatched run is literal).
47
+ // Deliberate Mixdog difference: Codex (codex-rs/skills/src/mentions.rs) scans
48
+ // raw bytes with no code handling. Offsets stay those of the original text,
49
+ // so desktop highlighting lines up (and never lands inside code).
50
+ const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})(.*)$/;
51
+ const FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})[ \t]*$/;
52
+
53
+ function inlineCodeRanges(source, from, to, ranges) {
54
+ const runs = [];
55
+ const re = /`+/g;
56
+ re.lastIndex = from;
57
+ for (let match = re.exec(source); match && match.index < to; match = re.exec(source)) {
58
+ runs.push({ start: match.index, end: Math.min(to, match.index + match[0].length) });
59
+ }
60
+ for (let index = 0; index < runs.length; index += 1) {
61
+ const length = runs[index].end - runs[index].start;
62
+ const close = runs.findIndex((run, at) => at > index && run.end - run.start === length);
63
+ if (close < 0) continue;
64
+ ranges.push([runs[index].start, runs[close].end]);
65
+ index = close;
66
+ }
67
+ }
68
+
69
+ /** [start, end) offsets of Markdown code (fenced blocks and inline spans). */
70
+ function codeRanges(source) {
71
+ const ranges = [];
72
+ let open = null;
73
+ let proseStart = 0;
74
+ for (let at = 0; at <= source.length; ) {
75
+ const newline = source.indexOf('\n', at);
76
+ const end = newline < 0 ? source.length : newline;
77
+ const next = newline < 0 ? source.length + 1 : newline + 1;
78
+ const line = source.slice(at, end).replace(/\r$/, '');
79
+ if (!open) {
80
+ const fence = FENCE_OPEN.exec(line);
81
+ if (fence && !(fence[1][0] === '`' && fence[2].includes('`'))) {
82
+ inlineCodeRanges(source, proseStart, at, ranges);
83
+ open = { char: fence[1][0], length: fence[1].length, start: at };
84
+ }
85
+ } else {
86
+ const fence = FENCE_CLOSE.exec(line);
87
+ if (fence && fence[1][0] === open.char && fence[1].length >= open.length) {
88
+ ranges.push([open.start, Math.min(next, source.length)]);
89
+ proseStart = Math.min(next, source.length);
90
+ open = null;
91
+ }
92
+ }
93
+ at = next;
94
+ }
95
+ if (open) ranges.push([open.start, source.length]);
96
+ else inlineCodeRanges(source, proseStart, source.length, ranges);
97
+ return ranges;
98
+ }
99
+
100
+ /** True when `offset` in `text` lies inside Markdown code (a fenced block or
101
+ * an inline code span) by the same rules mention recognition uses, so a
102
+ * composer can tell whether a `$` being typed can become a mention. */
103
+ export function isInMarkdownCode(text, offset) {
104
+ const source = typeof text === 'string' ? text : '';
105
+ if (!Number.isInteger(offset) || (!source.includes('`') && !source.includes('~~~'))) return false;
106
+ return codeRanges(source).some(([from, to]) => offset >= from && offset < to);
107
+ }
108
+
109
+ /** Every syntactically valid `$name` in `text` outside Markdown code, in
110
+ * order: `start` is the `$`. */
111
+ export function skillMentionCandidates(text) {
112
+ const source = typeof text === 'string' ? text : '';
113
+ const found = [];
114
+ let code = null;
115
+ for (const match of source.matchAll(MENTION)) {
116
+ if (isReservedMentionName(match[2])) continue;
117
+ const start = match.index + match[1].length;
118
+ code ??= source.includes('`') || source.includes('~~~') ? codeRanges(source) : [];
119
+ if (code.some(([from, to]) => start >= from && start < to)) continue;
120
+ found.push({ name: match[2], start, end: start + 1 + match[2].length });
121
+ }
122
+ return found;
123
+ }
124
+
125
+ /** Candidates whose name `isSkill(name)` accepts (all occurrences, in order). */
126
+ export function skillMentions(text, isSkill) {
127
+ return skillMentionCandidates(text).filter((mention) => isSkill(mention.name));
128
+ }
129
+
130
+ /** Distinct mentioned skill names in first-mention order. */
131
+ export function mentionedSkillNames(text, isSkill) {
132
+ return [...new Set(skillMentions(text, isSkill).map((mention) => mention.name))];
133
+ }