mixdog 1.4.1 → 1.4.2

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 (326) hide show
  1. package/package.json +3 -2
  2. package/scripts/build-shipped-history.mjs +115 -33
  3. package/scripts/gate-local.mjs +172 -61
  4. package/scripts/lib/gate-scheduler.mjs +138 -32
  5. package/scripts/lib/gate-timing-reporter.mjs +16 -0
  6. package/scripts/lib/run-node-tests.mjs +54 -29
  7. package/scripts/lib/scoped-temp.mjs +1 -0
  8. package/scripts/lib/test-failure-records.mjs +14 -1
  9. package/scripts/lib/test-failure-summary.mjs +16 -4
  10. package/scripts/lib/test-spawn-ledger.mjs +12 -3
  11. package/scripts/pptx-kit-index.mjs +5 -6
  12. package/scripts/suite-health.mjs +110 -28
  13. package/scripts/test.mjs +9 -14
  14. package/src/agents/advisor/AGENT.md +4 -4
  15. package/src/agents/advisor/agent.json +1 -1
  16. package/src/agents/explore/AGENT.md +22 -0
  17. package/src/agents/explore/agent.json +6 -0
  18. package/src/cli.mjs +7 -0
  19. package/src/defaults/agents.json +7 -0
  20. package/src/defaults/shipped-history.json +18 -2
  21. package/src/defaults/skills/browser-use/SKILL.md +4 -1
  22. package/src/defaults/skills/code-tidy/SKILL.md +71 -102
  23. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +66 -114
  24. package/src/defaults/skills/code-tidy/references/cleanup-report.md +19 -35
  25. package/src/defaults/skills/code-tidy/references/dead-code.md +3 -9
  26. package/src/defaults/skills/code-tidy/references/test-suite-slop.md +16 -27
  27. package/src/defaults/skills/computer-use/SKILL.md +12 -2
  28. package/src/defaults/skills/goal-management/SKILL.md +1 -1
  29. package/src/defaults/skills/setup/references/actions.md +12 -6
  30. package/src/defaults/skills/setup/references/surfaces.md +2 -2
  31. package/src/defaults/skills/setup-guide/SKILL.md +5 -2
  32. package/src/defaults/skills/setup-guide/model-recommendations.json +13 -0
  33. package/src/defaults/skills/skill-creator/SKILL.md +85 -190
  34. package/src/defaults/skills/skill-creator/references/design.md +70 -162
  35. package/src/defaults/skills/skill-creator/references/evaluation.md +33 -92
  36. package/src/defaults/skills/test-suite/SKILL.md +194 -0
  37. package/src/defaults/skills/test-suite/references/case-management.md +51 -0
  38. package/src/defaults/skills/test-suite/references/flaky-policy.md +39 -0
  39. package/src/defaults/skills/test-suite/references/reliability.md +49 -0
  40. package/src/defaults/skills/test-suite/references/suite-design.md +78 -0
  41. package/src/defaults/skills/test-suite/scripts/inventory.mjs +335 -0
  42. package/src/defaults/skills/visualize/SKILL.md +6 -2
  43. package/src/lib/rules-builder.cjs +6 -30
  44. package/src/rules/lead/LEAD.md +3 -3
  45. package/src/rules/shared/20-research.md +3 -6
  46. package/src/rules/shared/40-editing.md +1 -0
  47. package/src/rules/shared/60-verification.md +10 -9
  48. package/src/rules/shared/70-delivery.md +4 -3
  49. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/abort-link.mjs +5 -1
  50. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +10 -1
  51. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +130 -21
  52. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +13 -0
  53. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +4 -1
  54. package/src/runtime/agent/orchestrator/config.mjs +12 -7
  55. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +7 -7
  56. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +5 -26
  57. package/src/runtime/agent/orchestrator/internal-agents.mjs +3 -14
  58. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +122 -12
  59. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -3
  60. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +13 -2
  61. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +152 -58
  62. package/src/runtime/agent/orchestrator/mcp/client.mjs +51 -18
  63. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +83 -52
  64. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +42 -26
  65. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +43 -8
  66. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +30 -28
  67. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  68. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +31 -21
  69. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +3 -1
  70. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -2
  71. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  72. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +1 -1
  73. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +40 -0
  74. package/src/runtime/agent/orchestrator/providers/custom-provider-keys.mjs +34 -0
  75. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +3 -0
  76. package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +14 -7
  77. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +3 -2
  78. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +2 -2
  79. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -1
  80. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +48 -31
  81. package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +0 -18
  82. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +138 -6
  83. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +1 -1
  84. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +100 -33
  85. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +1 -1
  86. package/src/runtime/agent/orchestrator/providers/oauth-secret-fields.fixtures.mjs +45 -0
  87. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +26 -28
  88. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +3 -1
  89. package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +1 -0
  90. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +1 -1
  91. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +3 -1
  92. package/src/runtime/agent/orchestrator/providers/registry.mjs +33 -2
  93. package/src/runtime/agent/orchestrator/providers/vertex-ai-auth.mjs +622 -0
  94. package/src/runtime/agent/orchestrator/providers/vertex-ai-maas.mjs +185 -0
  95. package/src/runtime/agent/orchestrator/providers/vertex-ai-models.mjs +445 -0
  96. package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +567 -0
  97. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
  98. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +2 -1
  99. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  100. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +3 -0
  101. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +14 -5
  102. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -0
  103. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -5
  104. package/src/runtime/agent/orchestrator/session/agent-room.mjs +171 -37
  105. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +94 -10
  106. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +3 -3
  107. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +10 -5
  108. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +5 -5
  109. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +11 -1
  110. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -3
  111. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -6
  112. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +5 -3
  113. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +21 -2
  114. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +2 -0
  115. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +23 -1
  116. package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +13 -8
  117. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +50 -28
  118. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +1 -0
  119. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +3 -4
  120. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +2 -0
  121. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +7 -0
  122. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +15 -0
  123. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +25 -14
  124. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +17 -14
  125. package/src/runtime/agent/orchestrator/session/manager/session-scratch-environment.mjs +70 -0
  126. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +0 -3
  127. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -3
  128. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +47 -29
  129. package/src/runtime/agent/orchestrator/session/manager/turn-reap.mjs +125 -0
  130. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +11 -5
  131. package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +1 -1
  132. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +243 -38
  133. package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +163 -64
  134. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +164 -4
  135. package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +142 -18
  136. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +6 -2
  137. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +36 -26
  138. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +3 -5
  139. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +58 -20
  140. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +20 -15
  141. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +96 -20
  142. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +59 -10
  143. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  144. package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +99 -2
  145. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +2 -0
  146. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +103 -13
  147. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +3 -1
  148. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +49 -15
  149. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +45 -27
  150. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +34 -10
  151. package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +2 -0
  152. package/src/runtime/agent/orchestrator/tools/lib/wait-until-fixture.mjs +26 -0
  153. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -2
  154. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
  155. package/src/runtime/channels/lib/whisper-port.mjs +19 -10
  156. package/src/runtime/channels/lib/whisper-server.mjs +10 -1
  157. package/src/runtime/connectors/connector-accounts-mcp.mjs +236 -103
  158. package/src/runtime/connectors/gmail-app-password.mjs +3 -3
  159. package/src/runtime/explore/explorer.mjs +93 -0
  160. package/src/runtime/explore/tool-defs.mjs +35 -0
  161. package/src/runtime/explore/tool.mjs +123 -0
  162. package/src/runtime/memory/index.mjs +4 -0
  163. package/src/runtime/memory/lib/embedding-provider.mjs +4 -1
  164. package/src/runtime/memory/lib/embedding-worker.mjs +4 -0
  165. package/src/runtime/office/quality/assurance-structure-docx.mjs +1 -1
  166. package/src/runtime/shared/agent-route-config.mjs +17 -2
  167. package/src/runtime/shared/atomic-file.mjs +103 -2
  168. package/src/runtime/shared/background-tasks.mjs +15 -6
  169. package/src/runtime/shared/connector-accounts.mjs +5 -5
  170. package/src/runtime/shared/connector-catalog.d.mts +11 -4
  171. package/src/runtime/shared/connector-catalog.mjs +60 -52
  172. package/src/runtime/shared/custom-provider-keys.mjs +20 -0
  173. package/src/runtime/shared/file-has-data.mjs +26 -2
  174. package/src/runtime/shared/file-lock.mjs +13 -15
  175. package/src/runtime/shared/git-launch.d.mts +9 -0
  176. package/src/runtime/shared/git-launch.mjs +213 -0
  177. package/src/runtime/shared/git-runner-worker.mjs +2 -1
  178. package/src/runtime/shared/llm/cost.mjs +6 -1
  179. package/src/runtime/shared/llm/http-agent.mjs +313 -29
  180. package/src/runtime/shared/llm/model-catalog-projection.mjs +3 -2
  181. package/src/runtime/shared/llm/model-catalog.mjs +46 -6
  182. package/src/runtime/shared/llm/provider-catalog-registry.mjs +3 -0
  183. package/src/runtime/shared/llm/system-proxy.mjs +452 -0
  184. package/src/runtime/shared/provider-accounts.mjs +52 -2
  185. package/src/runtime/shared/staged-install-worker.mjs +5 -0
  186. package/src/runtime/shared/staged-update.mjs +47 -2
  187. package/src/runtime/shared/tool-surface.mjs +110 -36
  188. package/src/runtime/shared/tool-work-units.mjs +4 -0
  189. package/src/runtime/shared/turn-worktree-snapshot.mjs +414 -54
  190. package/src/runtime/shared/worktrees/activity-gate.mjs +215 -318
  191. package/src/runtime/shared/worktrees/activity-ledger-worker.mjs +56 -0
  192. package/src/runtime/shared/worktrees/activity-ledger.mjs +391 -0
  193. package/src/runtime/shared/worktrees/branch-management.mjs +9 -3
  194. package/src/runtime/shared/worktrees/git.mjs +57 -0
  195. package/src/runtime/shared/worktrees/manager.mjs +205 -137
  196. package/src/runtime/tidy/install.mjs +95 -15
  197. package/src/runtime/tidy/languages.mjs +17 -15
  198. package/src/runtime/tidy/process.mjs +7 -4
  199. package/src/runtime/tidy/resolve.mjs +19 -16
  200. package/src/runtime/tidy/runners/biome.mjs +3 -3
  201. package/src/runtime/tidy/tool.mjs +1 -1
  202. package/src/runtime/tidy/worktree.mjs +10 -6
  203. package/src/runtime/visualize-bridge/client.mjs +4 -1
  204. package/src/runtime/visualize-bridge/tool-defs.mjs +7 -2
  205. package/src/runtime/web-search/index.mjs +1 -1
  206. package/src/session-runtime/agent-task-sessions.mjs +410 -353
  207. package/src/session-runtime/boot/tools.mjs +2 -0
  208. package/src/session-runtime/bot-status.mjs +103 -103
  209. package/src/session-runtime/connector-merge-migration.mjs +18 -15
  210. package/src/session-runtime/conversation-import/discovery.mjs +159 -40
  211. package/src/session-runtime/conversation-import/service.mjs +9 -3
  212. package/src/session-runtime/handoff-attachments.mjs +1 -1
  213. package/src/session-runtime/held-requests.mjs +3 -1
  214. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -0
  215. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +8 -0
  216. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +143 -116
  217. package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -1
  218. package/src/session-runtime/lifecycle/teardown.mjs +1 -1
  219. package/src/session-runtime/mcp-glue/server-catalog.mjs +16 -10
  220. package/src/session-runtime/mcp-glue/server-input.mjs +4 -1
  221. package/src/session-runtime/mixy-heartbeat.mjs +1 -1
  222. package/src/session-runtime/provider-auth/login.mjs +2 -2
  223. package/src/session-runtime/resource-mcp-api.mjs +98 -35
  224. package/src/session-runtime/resource-mcp-connector-accounts.mjs +44 -31
  225. package/src/session-runtime/resource-mcp-secrets.mjs +28 -11
  226. package/src/session-runtime/resource-mcp-servers.mjs +5 -4
  227. package/src/session-runtime/services/agent-tool/send-flow.mjs +10 -2
  228. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +8 -3
  229. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
  230. package/src/session-runtime/services/agent-watchdog-registry.mjs +32 -4
  231. package/src/session-runtime/services/channel-worker/daemon-spawn.mjs +2 -0
  232. package/src/session-runtime/services/daemon-candidate.mjs +4 -1
  233. package/src/session-runtime/services/memory-runtime-proxy/daemon-spawn.mjs +4 -0
  234. package/src/session-runtime/services/plugin-admin.mjs +10 -6
  235. package/src/session-runtime/services/provider-admin.mjs +281 -10
  236. package/src/session-runtime/session-turn-api.mjs +2 -0
  237. package/src/session-runtime/settings-builtin-tools-api.mjs +4 -1
  238. package/src/session-runtime/settings-system-api.mjs +1 -1
  239. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +13 -3
  240. package/src/session-runtime/setup-tool/connector-widen-approval.mjs +96 -0
  241. package/src/session-runtime/setup-tool/executor.mjs +6 -2
  242. package/src/session-runtime/setup-tool/tool-defs.mjs +2 -1
  243. package/src/session-runtime/turn/agent-ops.mjs +23 -0
  244. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +49 -11
  245. package/src/session-runtime/workflow-agents-api/agent-route.mjs +10 -1
  246. package/src/session-runtime/workspace/activity-guards.mjs +176 -0
  247. package/src/session-runtime/workspace/session-view.mjs +149 -0
  248. package/src/session-runtime/workspace/side-checkouts.mjs +203 -0
  249. package/src/session-runtime/workspace/transition-runner.mjs +188 -0
  250. package/src/session-runtime/workspace/transitions.mjs +108 -1066
  251. package/src/session-runtime/workspace/workspace-merge.mjs +132 -0
  252. package/src/session-runtime/workspace/worktree-intents.mjs +242 -0
  253. package/src/session-runtime/workspace/worktree-maintenance.mjs +80 -0
  254. package/src/session-runtime/workspace/worktree-setup.mjs +85 -0
  255. package/src/session-runtime/workspace/worktree-tool.mjs +54 -54
  256. package/src/standalone/daemon-desktop-runtime.mjs +2 -0
  257. package/src/standalone/daemon-session-control.mjs +151 -136
  258. package/src/standalone/daemon.mjs +7 -0
  259. package/src/standalone/desktop-service-registry.mjs +9 -0
  260. package/src/standalone/local-session-runtime.mjs +5 -1
  261. package/src/standalone/session-client/daemon-unavailable.mjs +3 -0
  262. package/src/standalone/session-client.mjs +5 -1
  263. package/src/standalone/session-frame-stream.mjs +83 -11
  264. package/src/standalone/session-protocol.mjs +4 -0
  265. package/src/standalone/session-service/agent-tree.mjs +45 -9
  266. package/src/standalone/session-service/projection/transcript-window.mjs +700 -64
  267. package/src/standalone/session-service/session-calls/session-views.mjs +160 -104
  268. package/src/standalone/session-service/stored-reader.mjs +127 -40
  269. package/src/tui/App.jsx +154 -164
  270. package/src/tui/app/app-view/floating-panel.jsx +3 -0
  271. package/src/tui/app/app-view/text-entry-labels.mjs +1 -0
  272. package/src/tui/app/clipboard.mjs +21 -10
  273. package/src/tui/app/prompt-handlers/use-prompt-history-nav.mjs +1 -1
  274. package/src/tui/app/prompt-handlers/use-prompt-interrupt.mjs +3 -0
  275. package/src/tui/app/prompt-submit/provider-prompts.mjs +3 -1
  276. package/src/tui/app/provider-setup/api-key-actions.mjs +31 -1
  277. package/src/tui/app/provider-setup/oauth-actions.mjs +25 -6
  278. package/src/tui/app/provider-setup-picker.mjs +20 -13
  279. package/src/tui/app/shell-layout/row-budgets.mjs +12 -10
  280. package/src/tui/app/slash-commands.mjs +1 -0
  281. package/src/tui/app/slash-dispatch/session-commands.mjs +46 -0
  282. package/src/tui/app/slash-dispatch.mjs +1 -1
  283. package/src/tui/app/use-btw-panel.mjs +29 -0
  284. package/src/tui/app/use-context-panel-refresh.mjs +31 -0
  285. package/src/tui/app/use-model-catalog-cache.mjs +13 -10
  286. package/src/tui/app/use-mouse-input.mjs +11 -0
  287. package/src/tui/app/use-onboarding-start.mjs +8 -3
  288. package/src/tui/app/use-panel-transition.mjs +10 -1
  289. package/src/tui/app/use-pasted-buffers.mjs +28 -20
  290. package/src/tui/app/use-prompt-draft-flow.mjs +7 -0
  291. package/src/tui/app/use-prompt-layout-rows.mjs +3 -3
  292. package/src/tui/app/use-prompt-queue-history.mjs +3 -2
  293. package/src/tui/app/use-selection-focus.mjs +11 -2
  294. package/src/tui/app/use-selection-geometry.mjs +18 -9
  295. package/src/tui/app/use-selection-paint.mjs +4 -4
  296. package/src/tui/app/use-selection-stitch.mjs +3 -3
  297. package/src/tui/app/use-slash-palette.mjs +62 -0
  298. package/src/tui/app/use-terminal-chrome.mjs +2 -2
  299. package/src/tui/app/use-transcript-activity.mjs +47 -32
  300. package/src/tui/app/use-transcript-follow.mjs +12 -4
  301. package/src/tui/app/use-transcript-scroll.mjs +46 -17
  302. package/src/tui/app/use-transcript-swap-reset.mjs +27 -0
  303. package/src/tui/app/use-transcript-window/geometry.mjs +19 -5
  304. package/src/tui/app/use-transcript-window/instance-state.mjs +4 -0
  305. package/src/tui/app/use-transcript-window/selection-shift.mjs +5 -1
  306. package/src/tui/app/use-transcript-window.mjs +21 -11
  307. package/src/tui/app/use-tui-ready.mjs +18 -0
  308. package/src/tui/app/use-welcome-prompt-hint.mjs +5 -0
  309. package/src/tui/app/use-workflow-tab-cycle.mjs +5 -3
  310. package/src/tui/components/Picker.jsx +27 -3
  311. package/src/tui/components/SignInUrl.jsx +68 -0
  312. package/src/tui/components/TextEntryPanel.jsx +76 -46
  313. package/src/tui/components/sign-in-url-layout.mjs +24 -0
  314. package/src/tui/dist/index.mjs +1896 -1341
  315. package/src/tui/session/goal-turn-state.mjs +6 -2
  316. package/src/tui/session/session-api/controls.mjs +4 -0
  317. package/src/tui/session/session-api/extensions.mjs +8 -0
  318. package/src/tui/session/session-api/intake/abort.mjs +2 -1
  319. package/src/tui/session/session-api/intake.mjs +2 -0
  320. package/src/tui/session/session-api/integrations.mjs +2 -2
  321. package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
  322. package/src/tui/session/session-api/settings.mjs +2 -2
  323. package/src/tui/session/transcript-spill/spill-writer.mjs +15 -0
  324. package/src/tui/session/transcript-spill.mjs +92 -4
  325. package/src/tui/session/transcript-store.mjs +2 -0
  326. package/src/defaults/skills/skill-creator/references/source-review.md +0 -94
@@ -0,0 +1,567 @@
1
+ import { resolveProviderApiKey } from '../../../shared/provider-api-key.mjs';
2
+ import { getLlmDispatcher } from '../../../shared/llm/http-agent.mjs';
3
+ import { sanitizeToolPairs } from '../session/context-utils.mjs';
4
+ import { noteRequestServiceTier } from '../../../shared/llm/usage-context.mjs';
5
+ import { buildGeminiRequest } from './gemini-request-body.mjs';
6
+ import { geminiEndsWithFunctionResponse, runGeminiStreamAttempts } from './gemini-retry-policy.mjs';
7
+ import { consumeGeminiRestStreamResponse } from './gemini-stream.mjs';
8
+ import { geminiTextLeakGuardFor } from './gemini-sdk-request.mjs';
9
+ import { createGeminiReasoningRelay } from './gemini-reasoning-relay.mjs';
10
+ import {
11
+ geminiFailureUsage,
12
+ geminiIncompleteError,
13
+ geminiSendResult,
14
+ parseGeminiCandidate,
15
+ resolveGeminiUsage,
16
+ } from './gemini-response.mjs';
17
+ import { emitGeminiToolCalls } from './gemini-schema.mjs';
18
+ import { geminiRestError } from './gemini.mjs';
19
+ import {
20
+ ANTHROPIC_RETRY_BACKOFF_MS,
21
+ ANTHROPIC_RETRY_JITTER_RATIO,
22
+ anthropicMaxAttempts,
23
+ canFallbackNonStreaming,
24
+ emitProviderRetryStage,
25
+ withRetry,
26
+ } from './retry-classifier.mjs';
27
+ import { buildAnthropicApiRequest } from './anthropic-api-request.mjs';
28
+ import { applyAnthropicErrorContract } from './anthropic-error-contract.mjs';
29
+ import { createAnthropicMidState, assertAnthropicStreamNotEmpty } from './anthropic-midstream-recovery.mjs';
30
+ import { parseSSEStream } from './anthropic-sse.mjs';
31
+ import { buildAnthropicTurnResult } from './anthropic-turn-result.mjs';
32
+ import { streamCallbacks } from './lib/send-callbacks.mjs';
33
+ import { preparePdfTextForProvider } from './media-normalization.mjs';
34
+ import { VERTEX_API_KEY_MODE, VertexAccessTokenManager } from './vertex-ai-auth.mjs';
35
+ import { VertexMaasRoute, toVertexExclusiveUsage, vertexErrorBody } from './vertex-ai-maas.mjs';
36
+ import {
37
+ VERTEX_FLEX_TIER,
38
+ claudeNeedsInterleavedBeta,
39
+ VERTEX_MODELS,
40
+ VERTEX_MULTI_REGIONS,
41
+ VERTEX_PRIORITY_TIER,
42
+ assertVertexServing,
43
+ vertexEnvLocation,
44
+ vertexModelAccessError,
45
+ vertexPricingModel,
46
+ vertexRequestedTier,
47
+ vertexRoute,
48
+ vertexServingProblem,
49
+ vertexTierOption,
50
+ } from './vertex-ai-models.mjs';
51
+
52
+ export {
53
+ VERTEX_FLEX_TIER,
54
+ VERTEX_MODELS,
55
+ VERTEX_PRIORITY_TIER,
56
+ VERTEX_REGIONAL_PRICING_SUFFIX,
57
+ VERTEX_SERVICE_TIER_PARAMETER,
58
+ } from './vertex-ai-models.mjs';
59
+
60
+ const VERTEX_ANTHROPIC_VERSION = 'vertex-2023-10-16';
61
+
62
+ // Vertex takes Anthropic betas in the body (`anthropic_beta`), never as a
63
+ // header, and only these (effort, fast mode, server fallback and the other
64
+ // first-party betas are not offered); tool search has its own name there.
65
+ const INTERLEAVED_THINKING_BETA = 'interleaved-thinking-2025-05-14';
66
+ const VERTEX_CLAUDE_BETAS = new Map([
67
+ [INTERLEAVED_THINKING_BETA, INTERLEAVED_THINKING_BETA],
68
+ ['advanced-tool-use-2025-11-20', 'tool-search-tool-2025-10-19'],
69
+ ['context-1m-2025-08-07', 'context-1m-2025-08-07'],
70
+ ['context-management-2025-06-27', 'context-management-2025-06-27'],
71
+ ['compact-2026-01-12', 'compact-2026-01-12'],
72
+ ]);
73
+
74
+ // Interleaved thinking needs the beta only for a manual thinking budget with
75
+ // tools on the models that document it (adaptive thinking interleaves on its own).
76
+ function needsInterleavedThinking(body, model) {
77
+ return (
78
+ body.thinking?.type === 'enabled' &&
79
+ Array.isArray(body.tools) &&
80
+ body.tools.length > 0 &&
81
+ claudeNeedsInterleavedBeta(model)
82
+ );
83
+ }
84
+
85
+ /** The Vertex names of the selected `betas` this request body needs. */
86
+ function vertexClaudeBetas(betas, body, model) {
87
+ const out = new Set();
88
+ for (const beta of betas || []) {
89
+ const mapped = VERTEX_CLAUDE_BETAS.get(beta);
90
+ if (!mapped || (mapped === INTERLEAVED_THINKING_BETA && !needsInterleavedThinking(body, model))) continue;
91
+ out.add(mapped);
92
+ }
93
+ return [...out];
94
+ }
95
+
96
+ // Flex and Priority PayGo are selected per request with
97
+ // X-Vertex-AI-LLM-Shared-Request-Type (Provisioned Throughput is still used
98
+ // first when the project has it). The served tier is usageMetadata.trafficType:
99
+ // ON_DEMAND_FLEX, ON_DEMAND_PRIORITY, or ON_DEMAND for a Priority request
100
+ // downgraded to Standard PayGo (billed at Standard rates).
101
+ const SERVED_TIER = Object.freeze({
102
+ ON_DEMAND_FLEX: VERTEX_FLEX_TIER,
103
+ FLEX: VERTEX_FLEX_TIER,
104
+ BATCH: VERTEX_FLEX_TIER,
105
+ ON_DEMAND_PRIORITY: VERTEX_PRIORITY_TIER,
106
+ ON_DEMAND: 'default',
107
+ });
108
+
109
+ // A Flex request queues server-side and may sit silent for minutes before its
110
+ // first chunk (the docs allow request timeouts up to 30 minutes), so the shared
111
+ // 60s first-chunk window would kill and replay a healthy one. Kept under the
112
+ // 600s socket body timeout (MIXDOG_LLM_BODY_TIMEOUT_MS).
113
+ const VERTEX_FLEX_FIRST_CHUNK_TIMEOUT_MS = 570_000;
114
+
115
+ // Multi-region locations are served by representative endpoints, not by
116
+ // <location>-aiplatform.googleapis.com.
117
+ function vertexHost(location) {
118
+ if (location === 'global') return 'aiplatform.googleapis.com';
119
+ if (VERTEX_MULTI_REGIONS.has(location)) return `aiplatform.${location}.rep.googleapis.com`;
120
+ return `${location}-aiplatform.googleapis.com`;
121
+ }
122
+
123
+ // Express mode: an API key on the global endpoint, with no project or location in the path.
124
+ const EXPRESS_BASE = 'https://aiplatform.googleapis.com/v1';
125
+
126
+ /**
127
+ * The request's location, and whether the user configured it (`pinned`):
128
+ * without one, the environment's region (for a Claude model, its own first),
129
+ * else global.
130
+ */
131
+ function vertexLocation(config, claudeId = '') {
132
+ const configured = String(config?.location || '').trim();
133
+ const location = (configured || vertexEnvLocation(process.env, claudeId) || 'global').trim().toLowerCase();
134
+ // The location names the host the bearer token is sent to: only a region
135
+ // label (or global / a multi-region) may reach the host template.
136
+ if (!/^[a-z][a-z0-9-]{0,62}$/.test(location)) throw new Error('[vertex-ai] location is invalid');
137
+ return { location, pinned: Boolean(configured) };
138
+ }
139
+
140
+ const servesAt = (route, location, tier) => !vertexServingProblem(route, location, tier);
141
+
142
+ const isNotFound = (error) => [error?.status, error?.httpStatus, error?.response?.status].map(Number).includes(404);
143
+
144
+ function vertexProjectOverride(config) {
145
+ const projectId = String(config?.projectId || '').trim();
146
+ if (projectId && (!/^[a-zA-Z0-9._:-]{1,128}$/.test(projectId) || projectId.includes('/'))) {
147
+ throw new Error('[vertex-ai] project ID override is invalid');
148
+ }
149
+ return projectId;
150
+ }
151
+
152
+ function geminiCallbacks(opts) {
153
+ const reasoning = createGeminiReasoningRelay(opts);
154
+ return {
155
+ onStreamDelta: typeof opts.onStreamDelta === 'function' ? opts.onStreamDelta : null,
156
+ onToolCall: typeof opts.onToolCall === 'function' ? opts.onToolCall : null,
157
+ onTextDelta: typeof opts.onTextDelta === 'function' ? opts.onTextDelta : null,
158
+ onReasoningDelta: reasoning.onReasoningDelta,
159
+ retractReasoning: reasoning.retract,
160
+ };
161
+ }
162
+
163
+ // Vertex rejects (400) functionCall.id / functionResponse.id, which the
164
+ // Gemini API request builder carries; calls pair by name and order here.
165
+ function withoutFunctionIds(contents) {
166
+ const strip = (key, part) => {
167
+ const { id: _id, ...rest } = part[key];
168
+ return { ...part, [key]: rest };
169
+ };
170
+ return (contents || []).map((content) =>
171
+ Array.isArray(content?.parts)
172
+ ? {
173
+ ...content,
174
+ parts: content.parts.map((part) => {
175
+ if (part?.functionCall && 'id' in part.functionCall) return strip('functionCall', part);
176
+ if (part?.functionResponse && 'id' in part.functionResponse) return strip('functionResponse', part);
177
+ return part;
178
+ }),
179
+ }
180
+ : content
181
+ );
182
+ }
183
+
184
+ function geminiVertexBody(request) {
185
+ return {
186
+ contents: withoutFunctionIds(request.contents),
187
+ // Vertex accepts only a Content object here, never a bare string.
188
+ ...(request.systemInstruction ? { systemInstruction: { parts: [{ text: request.systemInstruction }] } } : {}),
189
+ ...(request.generationConfig ? { generationConfig: request.generationConfig } : {}),
190
+ ...(request.geminiTools ? { tools: request.geminiTools } : {}),
191
+ ...(request.toolConfig ? { toolConfig: request.toolConfig } : {}),
192
+ ...(request.safetySettings ? { safetySettings: request.safetySettings } : {}),
193
+ };
194
+ }
195
+
196
+ export class VertexAIProvider {
197
+ static inputExcludesCache = true;
198
+ nativePdf = true;
199
+ name = 'vertex-ai';
200
+
201
+ constructor(config = {}) {
202
+ this.config = config;
203
+ const customFetch = typeof config.fetchFn === 'function' ? config.fetchFn : null;
204
+ this._fetch = customFetch ?? fetch;
205
+ this._auth = new VertexAccessTokenManager({
206
+ credentialResolver: () => resolveProviderApiKey('vertex-ai', this.config),
207
+ authMode: () => this.config.authMode,
208
+ fetchFn: this._fetch,
209
+ now: typeof config.now === 'function' ? config.now : () => Date.now(),
210
+ });
211
+ this._maas = new VertexMaasRoute({
212
+ name: this.name,
213
+ config,
214
+ auth: this._auth,
215
+ fetchFn: customFetch,
216
+ });
217
+ }
218
+
219
+ _projectId() {
220
+ return vertexProjectOverride(this.config) || this._auth.projectId();
221
+ }
222
+
223
+ // Only a Flex route has a budget; the shared window covers the rest. Vertex
224
+ // never marks transport progress at the response headers, so the transport
225
+ // deadline must already cover the headers wait plus the first-chunk wait.
226
+ firstResponseBudget(_model, { modelParameters } = {}) {
227
+ if (vertexRequestedTier({ modelParameters }) !== VERTEX_FLEX_TIER) return null;
228
+ const ms = 2 * VERTEX_FLEX_FIRST_CHUNK_TIMEOUT_MS;
229
+ return { transportMs: ms, semanticMs: ms };
230
+ }
231
+
232
+ _express() {
233
+ return this.config.authMode === VERTEX_API_KEY_MODE;
234
+ }
235
+
236
+ _expressKey() {
237
+ const key = String(resolveProviderApiKey('vertex-ai', this.config) || '').trim();
238
+ if (!key) throw new Error('[vertex-ai] the Vertex AI API key is not configured');
239
+ return key;
240
+ }
241
+
242
+ /** `target`: the send's { identity, project } and the location it goes to. */
243
+ _locationBase({ location, project }) {
244
+ if (this._express()) return EXPRESS_BASE;
245
+ return `https://${vertexHost(location)}/v1/projects/${encodeURIComponent(project)}/locations/${location}`;
246
+ }
247
+
248
+ _url(publisher, model, method, target) {
249
+ // A version suffix (claude-…@default, @YYYYMMDD) stays a literal `@` in the path.
250
+ const id = encodeURIComponent(model).replace(/%40/g, '@');
251
+ return `${this._locationBase(target)}/publishers/${publisher}/models/${id}:${method}`;
252
+ }
253
+
254
+ /**
255
+ * `identity` is the account `url` was built for, read in the same tick as
256
+ * the URL before the first attempt; every attempt (retries included) must
257
+ * still run as that account, so a switch in between aborts the request.
258
+ * Express mode sends its API key instead (its URL names no project).
259
+ */
260
+ async _authorizedFetch(url, init, identity) {
261
+ if (this._express()) {
262
+ const headers = new Headers(init?.headers || {});
263
+ headers.set('x-goog-api-key', this._expressKey());
264
+ return await this._fetch(url, { ...init, headers, dispatcher: getLlmDispatcher() });
265
+ }
266
+ let token = '';
267
+ const run = async () => {
268
+ token = await this._auth.getAccessToken({ identity });
269
+ this._auth.assertIdentity(identity);
270
+ const headers = new Headers(init?.headers || {});
271
+ headers.set('Authorization', `Bearer ${token}`);
272
+ return await this._fetch(url, { ...init, headers, dispatcher: getLlmDispatcher() });
273
+ };
274
+ let response = await run();
275
+ if (response.status === 401) {
276
+ this._auth.invalidate(token);
277
+ response = await run();
278
+ }
279
+ return response;
280
+ }
281
+
282
+ async send(messages, model, tools, sendOpts = {}) {
283
+ const route = vertexRoute(model);
284
+ const tier = vertexRequestedTier(sendOpts);
285
+ const express = this._express();
286
+ if (express && route.wire !== 'gemini') {
287
+ throw new Error(
288
+ `[vertex-ai] ${route.publisher}/${route.id} needs a service-account key or the gcloud login: ` +
289
+ 'a Vertex AI express mode API key serves Gemini models only'
290
+ );
291
+ }
292
+ const requested = vertexLocation(this.config, route.wire === 'claude' ? route.id : '');
293
+ const { pinned } = requested;
294
+ if (express && pinned && requested.location !== 'global') {
295
+ throw new Error(
296
+ '[vertex-ai] a Vertex AI express mode API key serves only the global location; set the location to global'
297
+ );
298
+ }
299
+ // A configured location is never moved. A region from the environment
300
+ // gives way to global when it cannot serve the request (or for express mode).
301
+ let location = express ? 'global' : requested.location;
302
+ if (!pinned && location !== 'global' && !servesAt(route, location, tier) && servesAt(route, 'global', tier)) {
303
+ location = 'global';
304
+ }
305
+ assertVertexServing(route, location, tier);
306
+ await this._auth.ready();
307
+ let result;
308
+ try {
309
+ // One account for the whole send, the region fallback included: its
310
+ // identity and project are read in one tick, and every attempt (on any
311
+ // location) must still run as it.
312
+ const account = express
313
+ ? { identity: null, project: '' }
314
+ : { identity: this._auth.identity(), project: this._projectId() };
315
+ const sendAt = (at) => {
316
+ const target = { ...account, location: at };
317
+ if (route.wire === 'gemini') return this._sendGemini(messages, route.id, tools, sendOpts, tier, target);
318
+ if (route.wire === 'claude') return this._sendClaude(messages, route.id, tools, sendOpts, target);
319
+ return this._sendMaas(messages, route, tools, sendOpts, target);
320
+ };
321
+ try {
322
+ result = await sendAt(location);
323
+ } catch (error) {
324
+ // A region from the environment that does not have the model (404) is retried once on global.
325
+ if (pinned || location === 'global' || !isNotFound(error) || !servesAt(route, 'global', tier)) throw error;
326
+ location = 'global';
327
+ result = await sendAt(location);
328
+ }
329
+ } catch (error) {
330
+ let project = '(unknown project)';
331
+ try {
332
+ project = this._projectId();
333
+ } catch {
334
+ // No resolvable project: keep the placeholder; the original error is rethrown below.
335
+ }
336
+ throw this._auth.accessError(vertexModelAccessError(error, route, { project, location }), { project });
337
+ }
338
+ const pricingModel = vertexPricingModel(route, location);
339
+ if (pricingModel && result && typeof result === 'object') result.pricingModel = pricingModel;
340
+ return result;
341
+ }
342
+
343
+ async _sendMaas(messages, route, tools, opts, target) {
344
+ // Built for the account it is sent with.
345
+ const baseURL = `${this._locationBase(target)}/endpoints/openapi`;
346
+ return await this._maas.send(messages, `${route.publisher}/${route.id}`, tools, opts, {
347
+ baseURL,
348
+ identity: target.identity,
349
+ });
350
+ }
351
+
352
+ async _sendGemini(messages, model, tools, opts, tier, target) {
353
+ const signal = opts.signal || null;
354
+ const headers = {
355
+ 'Content-Type': 'application/json',
356
+ ...(tier ? { 'X-Vertex-AI-LLM-Shared-Request-Type': tier } : {}),
357
+ };
358
+ const failureUsage = geminiFailureUsage(opts, null, model);
359
+ const vertexFailureUsage = {
360
+ ...failureUsage,
361
+ normalize: (um) => toVertexExclusiveUsage(failureUsage.normalize(um)),
362
+ };
363
+ const callbacks = geminiCallbacks(opts);
364
+ const request = buildGeminiRequest(messages, model, tools, opts, null);
365
+ const body = geminiVertexBody(request);
366
+ // One account for every attempt and the unary fallback: the send's.
367
+ const { identity } = target;
368
+ const streamUrl = `${this._url('google', model, 'streamGenerateContent', target)}?alt=sse`;
369
+ const unaryUrl = this._url('google', model, 'generateContent', target);
370
+ const stream = { ...request, opts, signal, useModel: model, tools, callbacks, cachedContent: null };
371
+ try {
372
+ opts.onStageChange?.('requesting');
373
+ } catch {
374
+ // A throwing progress callback must not fail the request.
375
+ }
376
+ // Every attempt sends the same tier; the served trafficType settles the price.
377
+ noteRequestServiceTier(tier === VERTEX_PRIORITY_TIER ? tier : '');
378
+
379
+ let outcome;
380
+ try {
381
+ outcome = await runGeminiStreamAttempts({
382
+ signal,
383
+ sleepFn: this.config.sleepFn,
384
+ afterToolResult: geminiEndsWithFunctionResponse(request.contents),
385
+ failureUsage: vertexFailureUsage,
386
+ run: async ({ signal: attemptSignal, connected }) => {
387
+ callbacks.retractReasoning();
388
+ const res = await this._authorizedFetch(
389
+ streamUrl,
390
+ { method: 'POST', headers, body: JSON.stringify(body), signal: attemptSignal },
391
+ identity
392
+ );
393
+ if (!res.ok) {
394
+ const text = vertexErrorBody(await res.text().catch(() => ''));
395
+ throw geminiRestError(res, text, 'Vertex AI Gemini streamGenerateContent');
396
+ }
397
+ connected();
398
+ try {
399
+ opts.onStageChange?.('streaming');
400
+ } catch {
401
+ // A throwing progress callback must not fail the request.
402
+ }
403
+ const textLeakGuard = geminiTextLeakGuardFor(stream);
404
+ const response = await consumeGeminiRestStreamResponse(res, {
405
+ signal: attemptSignal,
406
+ onStreamDelta: callbacks.onStreamDelta,
407
+ onTextDelta: callbacks.onTextDelta,
408
+ onReasoningDelta: callbacks.onReasoningDelta,
409
+ textLeakGuard,
410
+ label: 'Vertex AI Gemini streamGenerateContent',
411
+ failureUsage: vertexFailureUsage,
412
+ ...(tier === VERTEX_FLEX_TIER
413
+ ? { firstByteTimeoutMs: VERTEX_FLEX_FIRST_CHUNK_TIMEOUT_MS, idleAfterFirstChunk: true }
414
+ : {}),
415
+ });
416
+ return { response, textLeakGuard };
417
+ },
418
+ });
419
+ } catch (streamError) {
420
+ if (!canFallbackNonStreaming(streamError, { signal })) throw streamError;
421
+ callbacks.retractReasoning();
422
+ const res = await this._authorizedFetch(
423
+ unaryUrl,
424
+ { method: 'POST', headers, body: JSON.stringify(body), signal },
425
+ identity
426
+ );
427
+ if (!res.ok) {
428
+ const text = vertexErrorBody(await res.text().catch(() => ''));
429
+ throw geminiRestError(res, text, 'Vertex AI Gemini generateContent');
430
+ }
431
+ outcome = { response: await res.json(), textLeakGuard: null };
432
+ }
433
+
434
+ const parsed = parseGeminiCandidate(outcome.response, outcome.textLeakGuard);
435
+ const usage = resolveGeminiUsage(outcome.response, opts, null, model, this.name);
436
+ const incomplete = geminiIncompleteError(outcome.response, parsed, model, usage);
437
+ if (incomplete) throw incomplete;
438
+ outcome.textLeakGuard?.releaseDeferredToolCalls?.();
439
+ emitGeminiToolCalls(parsed.nativeToolCalls, callbacks.onToolCall);
440
+ const result = geminiSendResult(parsed, model, opts, toVertexExclusiveUsage(usage));
441
+ // The served tier decides the price: a Flex or Priority request can be
442
+ // served by Provisioned Throughput first, and a Priority one downgraded.
443
+ const served = SERVED_TIER[usage?.raw?.trafficType];
444
+ if (served) result.serviceTier = served;
445
+ return result;
446
+ }
447
+
448
+ async _sendClaude(messages, model, tools, opts, target) {
449
+ messages = sanitizeToolPairs(messages);
450
+ messages = await preparePdfTextForProvider(messages, this);
451
+ const requestOpts = { ...opts, fast: false, serverFallback: false };
452
+ const { params, betas, knownToolNames } = await buildAnthropicApiRequest({
453
+ name: this.name,
454
+ config: { ...this.config, disableBetaHeaders: true },
455
+ messages,
456
+ useModel: model,
457
+ tools,
458
+ opts: requestOpts,
459
+ fastModeLatched: false,
460
+ capabilityModel: model,
461
+ });
462
+ const { model: _model, ...body } = params;
463
+ body.anthropic_version = VERTEX_ANTHROPIC_VERSION;
464
+ const vertexBetas = vertexClaudeBetas(betas, body, model);
465
+ if (vertexBetas.length) body.anthropic_beta = vertexBetas;
466
+ const callbacks = streamCallbacks(opts);
467
+ try {
468
+ callbacks.onStageChange?.('requesting');
469
+ } catch {
470
+ // A throwing progress callback must not fail the request.
471
+ }
472
+ // One account for every attempt: the send's.
473
+ const { identity } = target;
474
+ const url = this._url('anthropic', model, 'streamRawPredict', target);
475
+ const payload = JSON.stringify(body);
476
+ // The initial response retries like the first-party Anthropic route: 429
477
+ // and 529 (and 5xx) on its backoff, Retry-After honored.
478
+ const response = await withRetry(
479
+ async ({ signal: attemptSignal }) => {
480
+ const res = await this._authorizedFetch(
481
+ url,
482
+ { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: payload, signal: attemptSignal },
483
+ identity
484
+ );
485
+ if (res.ok) return res;
486
+ const text = await res.text().catch(() => '');
487
+ let data = null;
488
+ try {
489
+ data = JSON.parse(vertexErrorBody(text));
490
+ } catch {
491
+ // A non-JSON body carries no google.rpc.Status; the status code stands alone.
492
+ }
493
+ const error = Object.assign(
494
+ new Error(`Vertex AI Claude streamRawPredict ${res.status}: ${text.slice(0, 300)}`),
495
+ {
496
+ ...(data ? { data } : {}),
497
+ status: res.status,
498
+ httpStatus: res.status,
499
+ headers: res.headers,
500
+ response: { status: res.status, headers: res.headers },
501
+ initialResponseError: true,
502
+ }
503
+ );
504
+ throw applyAnthropicErrorContract(error, { status: res.status, headers: res.headers, bodyText: text });
505
+ },
506
+ {
507
+ signal: opts.signal || null,
508
+ maxAttempts: anthropicMaxAttempts(),
509
+ backoffMs: ANTHROPIC_RETRY_BACKOFF_MS,
510
+ retryJitterRatio: ANTHROPIC_RETRY_JITTER_RATIO,
511
+ retryJitterMode: 'positive',
512
+ recoveryOwner: 'vertex-ai-claude-initial-response',
513
+ retry529: opts.retry529,
514
+ sleepFn: this.config.sleepFn,
515
+ onRetry: ({ attempt, maxAttempts, lastErr, delayMs }) =>
516
+ emitProviderRetryStage(callbacks.onStageChange, { attempt: attempt + 1, maxAttempts, lastErr, delayMs }),
517
+ }
518
+ );
519
+ try {
520
+ callbacks.onStageChange?.('streaming');
521
+ } catch {
522
+ // A throwing progress callback must not fail the request.
523
+ }
524
+ const controller = new AbortController();
525
+ const externalSignal = opts.signal || null;
526
+ const abort = () => controller.abort(externalSignal?.reason);
527
+ if (externalSignal) {
528
+ if (externalSignal.aborted) abort();
529
+ else externalSignal.addEventListener('abort', abort, { once: true });
530
+ }
531
+ const midState = createAnthropicMidState(0);
532
+ try {
533
+ const parsed = await parseSSEStream(
534
+ response,
535
+ controller.signal,
536
+ (reason) => controller.abort(reason),
537
+ callbacks.onStreamDelta,
538
+ callbacks.onToolCall,
539
+ midState,
540
+ callbacks.onTextDelta,
541
+ knownToolNames,
542
+ { onStreamItemStart: callbacks.onStreamItemStart }
543
+ );
544
+ assertAnthropicStreamNotEmpty(midState, parsed, 'Vertex AI Claude');
545
+ return buildAnthropicTurnResult(parsed, { provider: this.name, useModel: model, opts });
546
+ } finally {
547
+ externalSignal?.removeEventListener?.('abort', abort);
548
+ }
549
+ }
550
+
551
+ async listModels() {
552
+ return VERTEX_MODELS.map((model) => {
553
+ const tierOption = vertexTierOption(model.id);
554
+ return { ...model, provider: this.name, ...(tierOption ? { modelParameterOptions: [tierOption] } : {}) };
555
+ });
556
+ }
557
+
558
+ async isAvailable() {
559
+ try {
560
+ if (this._express()) return Boolean(this._expressKey());
561
+ await this._auth.ready();
562
+ return Boolean(this._projectId());
563
+ } catch {
564
+ return false;
565
+ }
566
+ }
567
+ }
@@ -22,6 +22,7 @@ import { HEADLESS_MODEL_TOOL_NAMES, HEADLESS_TOOL_PROFILE, normalizeToolProfile
22
22
  import { DEFERRED_DEFAULT_LEAD_TOOLS } from './tool-catalog-data.mjs';
23
23
  import { gitExecutablePresent } from '../../../shared/path-executable.mjs';
24
24
  import { onnxRuntimeSupported } from '../../../shared/onnx-runtime-support.mjs';
25
+ import { exploreRouteConfigured } from '../../../shared/agent-route-config.mjs';
25
26
  import { setBuiltinInstalledInConfig, withGrandfatheredBuiltins } from '../../../shared/builtin-grandfathering.mjs';
26
27
 
27
28
  // Browser Use / Computer Use have no install marker: the desktop app publishes
@@ -158,6 +159,7 @@ export function featureDisallowedToolsFor(
158
159
  ...(builtinFeatureActive(configLike, 'media') ? [] : ['media']),
159
160
  ...(builtinFeatureActive(configLike, 'tidy') ? [] : ['tidy']),
160
161
  ...(builtinFeatureActive(configLike, 'visualize') ? [] : ['visualize']),
162
+ ...(exploreRouteConfigured(configLike) ? [] : ['explore']),
161
163
  // Bots' emoji reactions (Settings → General → Reactions); on unless turned off.
162
164
  ...(configLike?.reactions === false ? ['react'] : []),
163
165
  ];
@@ -51,7 +51,8 @@ already holds — Lead makes them directly in every mode; the number of files or
51
51
  scopes does not change this.
52
52
 
53
53
  Dispatch all ready independent scopes in one turn. Only a scope that requires
54
- another's output waits.
54
+ another's output waits. At most two building or testing scopes run at
55
+ once.
55
56
  A new independent need goes to a new agent. When scopes touch the same file, name one owner for
56
57
  that file in each brief.
57
58
  Use only available agents; when none fits a role, Lead does that work itself
@@ -1,6 +1,7 @@
1
1
  // Canonical route order — the single surface order wherever tools serialize:
2
2
  // locator → path → content → symbol → read → edit → execute.
3
3
  export const ROUTE_TOOL_ORDER = Object.freeze([
4
+ 'explore',
4
5
  'find',
5
6
  'glob',
6
7
  'list',
@@ -58,6 +59,8 @@ export const DEFERRED_DEFAULT_READONLY_TOOLS = Object.freeze([
58
59
  'load_tool',
59
60
  ]);
60
61
  export const DEFERRED_DEFAULT_LEAD_TOOLS = Object.freeze([
62
+ // Present only once Explore has a model (builtin-features.mjs).
63
+ 'explore',
61
64
  'find',
62
65
  'glob',
63
66
  'list',
@@ -240,6 +240,7 @@ const OFFICE_RUNTIME_TOOLS = ['office'];
240
240
  const MEDIA_RUNTIME_TOOLS = ['media'];
241
241
  const TIDY_RUNTIME_TOOLS = ['tidy'];
242
242
  const VISUALIZE_RUNTIME_TOOLS = ['visualize'];
243
+ const EXPLORE_RUNTIME_TOOLS = ['explore'];
243
244
 
244
245
  const admitted = (tools, names) => (tools || []).filter((tool) => names.includes(tool?.name));
245
246
 
@@ -262,6 +263,7 @@ export function collectStandaloneToolDefs({
262
263
  mediaToolDefs = [],
263
264
  tidyToolDefs = [],
264
265
  visualizeToolDefs = [],
266
+ exploreToolDefs = [],
265
267
  setupToolDefs = [],
266
268
  askUserToolDefs = [],
267
269
  handoffUserToolDefs = [],
@@ -285,6 +287,7 @@ export function collectStandaloneToolDefs({
285
287
  ...admitted(mediaToolDefs, MEDIA_RUNTIME_TOOLS),
286
288
  ...admitted(tidyToolDefs, TIDY_RUNTIME_TOOLS),
287
289
  ...admitted(visualizeToolDefs, VISUALIZE_RUNTIME_TOOLS),
290
+ ...admitted(exploreToolDefs, EXPLORE_RUNTIME_TOOLS),
288
291
  ...setupToolDefs,
289
292
  ...askUserToolDefs,
290
293
  ...handoffUserToolDefs,
@@ -4,6 +4,8 @@ import { clean } from '../session-text.mjs';
4
4
  import { configuredOrchestrationMode } from '../../../../shared/orchestration.mjs';
5
5
  import { AGENT_MODE_DELEGATION, MIXY_CONTACTS_INTRO, orchestrationInstructions } from '../orchestration.mjs';
6
6
  import { DEFAULT_WORKFLOW_ID, isContactAgentId, normalizeWorkflowId } from '../workflow-ids.mjs';
7
+ import { MIXY_AGENT_ID } from '../../session/agent-home.mjs';
8
+ import { mixyDisplayName } from '../../session/mixy-name.mjs';
7
9
 
8
10
  export function createWorkflowContext({ packs, agents }) {
9
11
  function workflowSummary(pack, { hasAgents = true } = {}) {
@@ -59,7 +61,9 @@ export function createWorkflowContext({ packs, agents }) {
59
61
  // - coding: the orchestration mode block and the full agent catalog;
60
62
  // - agent mode: the fixed delegation block (no per-agent setting) and the
61
63
  // sub-agent catalog — identical for every agent, so their prompts share
62
- // it — plus the contact-agent catalog: every bot but `agentId` itself.
64
+ // it — plus the contact-agent catalog: every bot but `agentId` itself,
65
+ // and for any bot but Mixy, Mixy first under her display name, so a bot
66
+ // asked to hand work to her by that name finds her (session send lead).
63
67
  function leadPromptParts(config, dir, { agentMode = false, agentId = '' } = {}) {
64
68
  const mode = configuredOrchestrationMode(config);
65
69
  const ids = agents.delegatableAgentIds(config, dir);
@@ -79,10 +83,15 @@ export function createWorkflowContext({ packs, agents }) {
79
83
  dir,
80
84
  ids.filter((id) => !isContact(id))
81
85
  );
82
- const contacts = agentCatalog(
83
- dir,
84
- ids.filter((id) => isContact(id) && id !== agentId)
85
- );
86
+ const contacts = [
87
+ agentId === MIXY_AGENT_ID ? '' : `- ${mixyDisplayName()} (${MIXY_AGENT_ID}): the user's representative bot`,
88
+ agentCatalog(
89
+ dir,
90
+ ids.filter((id) => isContact(id) && id !== agentId)
91
+ ),
92
+ ]
93
+ .filter(Boolean)
94
+ .join('\n');
86
95
  return {
87
96
  // An agent-mode Lead without agents has no one to delegate to at all.
88
97
  orchestrationContext: subAgents || contacts ? AGENT_MODE_DELEGATION : '',
@@ -7,6 +7,7 @@ export const AGENT_DELETED_MARKER = '.deleted';
7
7
  export const FIXED_AGENT_SLOTS = Object.freeze([
8
8
  // Short one-liners on purpose: these render inside the 260px sidebar rail.
9
9
  { id: 'maintainer', label: 'Maintainer', description: 'Memory and upkeep', workflowSlot: 'memory' },
10
+ { id: 'explore', label: 'Explore', description: 'Codebase exploration', workflowSlot: 'explore' },
10
11
  ]);
11
12
  export const AGENT_ROLE_IDS = new Set(FIXED_AGENT_SLOTS.map((agent) => agent.id));
12
13
  // Slot-backed built-ins run through dedicated maintenance channels, so they are never