mixdog 1.4.0 → 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 (493) hide show
  1. package/package.json +3 -2
  2. package/scripts/build-shipped-history.mjs +115 -33
  3. package/scripts/check-voice-runtime-inputs.mjs +25 -0
  4. package/scripts/gate-local.mjs +183 -63
  5. package/scripts/latest-guard.mjs +21 -9
  6. package/scripts/lib/gate-scheduler.mjs +138 -32
  7. package/scripts/lib/gate-timing-reporter.mjs +16 -0
  8. package/scripts/lib/isolated-test-env.mjs +10 -5
  9. package/scripts/lib/run-node-tests.mjs +91 -66
  10. package/scripts/lib/scoped-temp.d.mts +16 -0
  11. package/scripts/lib/scoped-temp.mjs +124 -0
  12. package/scripts/lib/stale-temp.mjs +44 -0
  13. package/scripts/lib/test-failure-records.mjs +14 -1
  14. package/scripts/lib/test-failure-summary.mjs +16 -4
  15. package/scripts/lib/test-spawn-ledger.mjs +12 -3
  16. package/scripts/post-release-check.mjs +1 -6
  17. package/scripts/pptx-kit-index.mjs +17 -5
  18. package/scripts/suite-health.mjs +115 -24
  19. package/scripts/test-data-dir.mjs +10 -7
  20. package/scripts/test.mjs +9 -14
  21. package/src/agents/advisor/AGENT.md +4 -4
  22. package/src/agents/advisor/agent.json +1 -1
  23. package/src/agents/explore/AGENT.md +22 -0
  24. package/src/agents/explore/agent.json +6 -0
  25. package/src/app.mjs +4 -0
  26. package/src/cli.mjs +7 -0
  27. package/src/defaults/agents.json +7 -0
  28. package/src/defaults/shipped-history.json +23 -2
  29. package/src/defaults/skills/browser-use/SKILL.md +10 -5
  30. package/src/defaults/skills/code-tidy/SKILL.md +71 -102
  31. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +66 -114
  32. package/src/defaults/skills/code-tidy/references/cleanup-report.md +19 -35
  33. package/src/defaults/skills/code-tidy/references/dead-code.md +3 -9
  34. package/src/defaults/skills/code-tidy/references/test-suite-slop.md +16 -27
  35. package/src/defaults/skills/computer-use/SKILL.md +16 -4
  36. package/src/defaults/skills/goal-management/SKILL.md +1 -1
  37. package/src/defaults/skills/setup/references/actions.md +12 -6
  38. package/src/defaults/skills/setup/references/surfaces.md +2 -2
  39. package/src/defaults/skills/setup-guide/SKILL.md +5 -2
  40. package/src/defaults/skills/setup-guide/model-recommendations.json +13 -0
  41. package/src/defaults/skills/skill-creator/SKILL.md +85 -190
  42. package/src/defaults/skills/skill-creator/references/design.md +70 -162
  43. package/src/defaults/skills/skill-creator/references/evaluation.md +33 -92
  44. package/src/defaults/skills/test-suite/SKILL.md +194 -0
  45. package/src/defaults/skills/test-suite/references/case-management.md +51 -0
  46. package/src/defaults/skills/test-suite/references/flaky-policy.md +39 -0
  47. package/src/defaults/skills/test-suite/references/reliability.md +49 -0
  48. package/src/defaults/skills/test-suite/references/suite-design.md +78 -0
  49. package/src/defaults/skills/test-suite/scripts/inventory.mjs +335 -0
  50. package/src/defaults/skills/visualize/SKILL.md +6 -2
  51. package/src/lib/rules-builder.cjs +35 -42
  52. package/src/output-styles/common.md +2 -2
  53. package/src/output-styles/simple.md +1 -1
  54. package/src/rules/agent/AGENT.md +5 -4
  55. package/src/rules/lead/LEAD.md +12 -7
  56. package/src/rules/shared/10-tool-workflow.md +3 -1
  57. package/src/rules/shared/20-research.md +3 -6
  58. package/src/rules/shared/40-editing.md +1 -0
  59. package/src/rules/shared/45-destructive.md +0 -2
  60. package/src/rules/shared/50-execution.md +2 -0
  61. package/src/rules/shared/60-verification.md +10 -9
  62. package/src/rules/shared/70-delivery.md +8 -9
  63. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/abort-link.mjs +5 -1
  64. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +10 -1
  65. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +156 -24
  66. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +13 -0
  67. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +4 -1
  68. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +2 -0
  69. package/src/runtime/agent/orchestrator/config.mjs +12 -7
  70. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +8 -8
  71. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +5 -26
  72. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +3 -1
  73. package/src/runtime/agent/orchestrator/internal-agents.mjs +3 -14
  74. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +122 -12
  75. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -3
  76. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +13 -2
  77. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +152 -58
  78. package/src/runtime/agent/orchestrator/mcp/client.mjs +51 -18
  79. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +88 -54
  80. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +42 -26
  81. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +54 -8
  82. package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +1 -1
  83. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +37 -31
  84. package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +8 -2
  85. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +6 -2
  86. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -2
  87. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  88. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +31 -21
  89. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +3 -1
  90. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +14 -3
  91. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +6 -0
  92. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -2
  93. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +3 -1
  94. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -2
  95. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  96. package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +1 -10
  97. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +1 -1
  98. package/src/runtime/agent/orchestrator/providers/cursor.mjs +3 -0
  99. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +40 -0
  100. package/src/runtime/agent/orchestrator/providers/custom-provider-keys.mjs +34 -0
  101. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +3 -0
  102. package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +14 -7
  103. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +3 -2
  104. package/src/runtime/agent/orchestrator/providers/devin.mjs +3 -0
  105. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +2 -2
  106. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -1
  107. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +248 -41
  108. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -11
  109. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +45 -6
  110. package/src/runtime/agent/orchestrator/providers/lib/first-output-window.mjs +58 -0
  111. package/src/runtime/agent/orchestrator/providers/lib/listen-on.mjs +14 -0
  112. package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +0 -18
  113. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +138 -6
  114. package/src/runtime/agent/orchestrator/providers/mimo-oauth-login.mjs +1 -10
  115. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +1 -1
  116. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +100 -33
  117. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +1 -1
  118. package/src/runtime/agent/orchestrator/providers/oauth-secret-fields.fixtures.mjs +45 -0
  119. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
  120. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +9 -2
  121. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +7 -0
  122. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +14 -3
  123. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +4 -2
  124. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
  125. package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +5 -3
  126. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +16 -3
  127. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
  128. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +5 -2
  129. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +13 -3
  130. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +13 -2
  131. package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +81 -42
  132. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +8 -0
  133. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +160 -31
  134. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +22 -12
  135. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +7 -1
  136. package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +49 -22
  137. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +1 -1
  138. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +3 -1
  139. package/src/runtime/agent/orchestrator/providers/registry.mjs +45 -2
  140. package/src/runtime/agent/orchestrator/providers/vertex-ai-auth.mjs +622 -0
  141. package/src/runtime/agent/orchestrator/providers/vertex-ai-maas.mjs +185 -0
  142. package/src/runtime/agent/orchestrator/providers/vertex-ai-models.mjs +445 -0
  143. package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +567 -0
  144. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +41 -13
  145. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
  146. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +8 -6
  147. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +7 -3
  148. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  149. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +6 -1
  150. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +14 -5
  151. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -0
  152. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +5 -2
  153. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -5
  154. package/src/runtime/agent/orchestrator/session/agent-room.mjs +171 -37
  155. package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +1 -3
  156. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +102 -12
  157. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  158. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -8
  159. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +23 -0
  160. package/src/runtime/agent/orchestrator/session/compact/observation-supersession.mjs +103 -23
  161. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +160 -36
  162. package/src/runtime/agent/orchestrator/session/context-utils.mjs +13 -1
  163. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +164 -23
  164. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +139 -19
  165. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +38 -7
  166. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +3 -0
  167. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +100 -22
  168. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -3
  169. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -6
  170. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +33 -3
  171. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +9 -2
  172. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -1
  173. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +13 -5
  174. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +14 -0
  175. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +13 -1
  176. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +23 -4
  177. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +2 -0
  178. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +23 -1
  179. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +39 -21
  180. package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +13 -8
  181. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +50 -28
  182. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -3
  183. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +3 -1
  184. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +2 -2
  185. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +1 -0
  186. package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +2 -1
  187. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +30 -13
  188. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +63 -6
  189. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +7 -0
  190. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +15 -0
  191. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +35 -13
  192. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +24 -14
  193. package/src/runtime/agent/orchestrator/session/manager/session-scratch-environment.mjs +70 -0
  194. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +0 -3
  195. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +10 -6
  196. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +4 -0
  197. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +3 -0
  198. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +56 -29
  199. package/src/runtime/agent/orchestrator/session/manager/turn-reap.mjs +125 -0
  200. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +11 -5
  201. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +5 -1
  202. package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +30 -7
  203. package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +154 -0
  204. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +7 -0
  205. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -0
  206. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +15 -0
  207. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +37 -8
  208. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +252 -39
  209. package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +163 -64
  210. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +164 -4
  211. package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +142 -18
  212. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +61 -2
  213. package/src/runtime/agent/orchestrator/stall-policy.mjs +23 -0
  214. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +6 -2
  215. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +2 -2
  216. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -2
  217. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -1
  218. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +46 -20
  219. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +7 -1
  220. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +67 -31
  221. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +25 -13
  222. package/src/runtime/agent/orchestrator/tools/builtin/lib/scoped-search-flight.mjs +9 -5
  223. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +58 -20
  224. package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +7 -1
  225. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +6 -3
  226. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +1 -1
  227. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +23 -12
  228. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +6 -1
  229. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -8
  230. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +20 -15
  231. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +96 -20
  232. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +59 -10
  233. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/federation.mjs +3 -1
  234. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
  235. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  236. package/src/runtime/agent/orchestrator/tools/lib/isolated-test-env.mjs +4 -3
  237. package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +99 -2
  238. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +2 -0
  239. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +103 -13
  240. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +3 -1
  241. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +49 -15
  242. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +45 -27
  243. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +34 -10
  244. package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +28 -4
  245. package/src/runtime/agent/orchestrator/tools/lib/wait-until-fixture.mjs +26 -0
  246. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -2
  247. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
  248. package/src/runtime/channels/lib/webhook/signature.mjs +4 -1
  249. package/src/runtime/channels/lib/whisper-port.mjs +29 -11
  250. package/src/runtime/channels/lib/whisper-server.mjs +10 -1
  251. package/src/runtime/computer-bridge/action-schema.mjs +2 -2
  252. package/src/runtime/computer-bridge/core-actions.mjs +2 -2
  253. package/src/runtime/computer-bridge/error-recovery.mjs +5 -4
  254. package/src/runtime/computer-bridge/linux-action-capabilities.mjs +24 -5
  255. package/src/runtime/computer-bridge/linux-readiness.d.mts +7 -2
  256. package/src/runtime/connectors/connector-accounts-mcp.mjs +236 -103
  257. package/src/runtime/connectors/gmail-app-password.mjs +3 -3
  258. package/src/runtime/explore/explorer.mjs +93 -0
  259. package/src/runtime/explore/tool-defs.mjs +35 -0
  260. package/src/runtime/explore/tool.mjs +123 -0
  261. package/src/runtime/local-provider/server-process.mjs +2 -1
  262. package/src/runtime/media/adapters/gemini-video.mjs +4 -1
  263. package/src/runtime/media/adapters/xai-media.mjs +2 -1
  264. package/src/runtime/memory/index.mjs +4 -0
  265. package/src/runtime/memory/lib/embedding-provider.mjs +4 -1
  266. package/src/runtime/memory/lib/embedding-worker.mjs +10 -3
  267. package/src/runtime/memory/lib/memory-action-handlers/cycle-backfill-action.mjs +1 -7
  268. package/src/runtime/memory/lib/memory-chunk-quality.mjs +1 -6
  269. package/src/runtime/office/authoring/pdf-author-action.mjs +1 -6
  270. package/src/runtime/office/design/design-art-direction.mjs +29 -27
  271. package/src/runtime/office/pdf/pdf-writer.mjs +6 -1
  272. package/src/runtime/office/portable/portable-snapshot-docx.mjs +7 -1
  273. package/src/runtime/office/portable/portable-snapshot-shared.mjs +6 -1
  274. package/src/runtime/office/quality/assurance-rendered.mjs +9 -2
  275. package/src/runtime/office/quality/assurance-structure-docx.mjs +4 -2
  276. package/src/runtime/office/quality/design-review.mjs +1 -5
  277. package/src/runtime/office/quality/document-brief.mjs +1 -5
  278. package/src/runtime/office/quality/forced-break-pages.mjs +9 -6
  279. package/src/runtime/office/quality/quality-pipeline.mjs +1 -1
  280. package/src/runtime/shared/agent-route-config.mjs +17 -2
  281. package/src/runtime/shared/atomic-file.mjs +103 -2
  282. package/src/runtime/shared/background-tasks.mjs +15 -6
  283. package/src/runtime/shared/bot-owned-work.mjs +56 -0
  284. package/src/runtime/shared/connector-accounts.mjs +5 -5
  285. package/src/runtime/shared/connector-catalog.d.mts +11 -4
  286. package/src/runtime/shared/connector-catalog.mjs +60 -52
  287. package/src/runtime/shared/custom-provider-keys.mjs +20 -0
  288. package/src/runtime/shared/defaults-separation.mjs +1 -4
  289. package/src/runtime/shared/fetch-safe-listen.d.mts +16 -0
  290. package/src/runtime/shared/fetch-safe-listen.mjs +50 -0
  291. package/src/runtime/shared/file-has-data.mjs +26 -2
  292. package/src/runtime/shared/file-lock.mjs +13 -15
  293. package/src/runtime/shared/git-launch.d.mts +9 -0
  294. package/src/runtime/shared/git-launch.mjs +213 -0
  295. package/src/runtime/shared/git-runner-worker.mjs +2 -1
  296. package/src/runtime/shared/handoff-artifacts.d.mts +2 -1
  297. package/src/runtime/shared/handoff-artifacts.mjs +18 -8
  298. package/src/runtime/shared/idle-auto-clear-gate.mjs +29 -7
  299. package/src/runtime/shared/llm/cost.mjs +11 -2
  300. package/src/runtime/shared/llm/http-agent.mjs +313 -29
  301. package/src/runtime/shared/llm/model-catalog-projection.mjs +3 -2
  302. package/src/runtime/shared/llm/model-catalog.mjs +46 -6
  303. package/src/runtime/shared/llm/provider-catalog-registry.mjs +3 -0
  304. package/src/runtime/shared/llm/system-proxy.mjs +452 -0
  305. package/src/runtime/shared/loopback-listener.mjs +25 -2
  306. package/src/runtime/shared/model-route-chain.d.mts +7 -2
  307. package/src/runtime/shared/model-route-chain.mjs +22 -4
  308. package/src/runtime/shared/open-url.mjs +10 -0
  309. package/src/runtime/shared/provider-accounts.mjs +52 -2
  310. package/src/runtime/shared/session-activity-signal.mjs +14 -0
  311. package/src/runtime/shared/session-scratch.mjs +37 -0
  312. package/src/runtime/shared/staged-install-worker.mjs +5 -0
  313. package/src/runtime/shared/staged-update.mjs +47 -2
  314. package/src/runtime/shared/tool-card-model/agent-surface.mjs +1 -3
  315. package/src/runtime/shared/tool-card-model/card-status.mjs +15 -2
  316. package/src/runtime/shared/tool-result-summary.mjs +1 -1
  317. package/src/runtime/shared/tool-surface.mjs +113 -36
  318. package/src/runtime/shared/tool-work-units.mjs +6 -0
  319. package/src/runtime/shared/turn-snapshot-store.mjs +13 -6
  320. package/src/runtime/shared/turn-worktree-snapshot.mjs +414 -54
  321. package/src/runtime/shared/worktrees/activity-gate.mjs +215 -318
  322. package/src/runtime/shared/worktrees/activity-ledger-worker.mjs +56 -0
  323. package/src/runtime/shared/worktrees/activity-ledger.mjs +391 -0
  324. package/src/runtime/shared/worktrees/branch-management.mjs +9 -3
  325. package/src/runtime/shared/worktrees/contract.mjs +4 -1
  326. package/src/runtime/shared/worktrees/git.mjs +72 -0
  327. package/src/runtime/shared/worktrees/manager.mjs +285 -168
  328. package/src/runtime/shared/worktrees/test-support.mjs +6 -10
  329. package/src/runtime/tidy/install.mjs +95 -15
  330. package/src/runtime/tidy/languages.mjs +17 -15
  331. package/src/runtime/tidy/process.mjs +7 -4
  332. package/src/runtime/tidy/resolve.mjs +19 -16
  333. package/src/runtime/tidy/runners/biome.mjs +3 -3
  334. package/src/runtime/tidy/tool.mjs +1 -1
  335. package/src/runtime/tidy/worktree.mjs +10 -6
  336. package/src/runtime/visualize-bridge/client.mjs +6 -1
  337. package/src/runtime/visualize-bridge/tool-defs.mjs +7 -2
  338. package/src/runtime/web-search/index.mjs +1 -1
  339. package/src/session-runtime/agent-home-delivery.mjs +41 -0
  340. package/src/session-runtime/agent-task-sessions.mjs +458 -312
  341. package/src/session-runtime/agent-tool-routing.mjs +8 -41
  342. package/src/session-runtime/boot/tools.mjs +7 -1
  343. package/src/session-runtime/bot-status.mjs +107 -101
  344. package/src/session-runtime/bot-target.mjs +38 -0
  345. package/src/session-runtime/channel-config-api.mjs +6 -1
  346. package/src/session-runtime/connector-merge-migration.mjs +18 -15
  347. package/src/session-runtime/conversation-import/codex-titles.mjs +88 -0
  348. package/src/session-runtime/conversation-import/discovery.mjs +401 -36
  349. package/src/session-runtime/conversation-import/service.mjs +35 -8
  350. package/src/session-runtime/handoff-attachments.mjs +82 -31
  351. package/src/session-runtime/held-requests.mjs +3 -1
  352. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +12 -13
  353. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +1 -0
  354. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -0
  355. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +8 -0
  356. package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +4 -1
  357. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +145 -112
  358. package/src/session-runtime/lifecycle/cold-heir.mjs +11 -6
  359. package/src/session-runtime/lifecycle/session-catalog.mjs +9 -1
  360. package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -1
  361. package/src/session-runtime/lifecycle/teardown.mjs +1 -1
  362. package/src/session-runtime/mcp-glue/server-catalog.mjs +16 -10
  363. package/src/session-runtime/mcp-glue/server-input.mjs +4 -1
  364. package/src/session-runtime/mixy-heartbeat.mjs +1 -1
  365. package/src/session-runtime/model-route/set-route.mjs +11 -2
  366. package/src/session-runtime/provider-auth/login.mjs +2 -2
  367. package/src/session-runtime/provider-catalog-cache.mjs +82 -36
  368. package/src/session-runtime/provider-models/catalog-load.mjs +23 -6
  369. package/src/session-runtime/provider-models.mjs +4 -2
  370. package/src/session-runtime/resource-mcp-api.mjs +98 -35
  371. package/src/session-runtime/resource-mcp-connector-accounts.mjs +44 -31
  372. package/src/session-runtime/resource-mcp-secrets.mjs +28 -11
  373. package/src/session-runtime/resource-mcp-servers.mjs +5 -4
  374. package/src/session-runtime/resource-recall-api.mjs +6 -1
  375. package/src/session-runtime/runtime-feature-gates.mjs +5 -1
  376. package/src/session-runtime/services/agent-tool/execute.mjs +14 -9
  377. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +20 -4
  378. package/src/session-runtime/services/agent-tool/model-select.mjs +48 -7
  379. package/src/session-runtime/services/agent-tool/render.mjs +71 -17
  380. package/src/session-runtime/services/agent-tool/send-flow.mjs +10 -2
  381. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +8 -3
  382. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
  383. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +6 -2
  384. package/src/session-runtime/services/agent-tool/tool-def.mjs +9 -9
  385. package/src/session-runtime/services/agent-watchdog-registry.mjs +32 -4
  386. package/src/session-runtime/services/channel-worker/daemon-spawn.mjs +2 -0
  387. package/src/session-runtime/services/daemon-candidate.mjs +4 -1
  388. package/src/session-runtime/services/memory-runtime-proxy/daemon-spawn.mjs +4 -0
  389. package/src/session-runtime/services/plugin-admin.mjs +10 -6
  390. package/src/session-runtime/services/provider-admin.mjs +283 -11
  391. package/src/session-runtime/session-turn-api.mjs +2 -0
  392. package/src/session-runtime/settings-builtin-tools-api.mjs +4 -1
  393. package/src/session-runtime/settings-system-api.mjs +1 -1
  394. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +13 -3
  395. package/src/session-runtime/setup-tool/connector-widen-approval.mjs +96 -0
  396. package/src/session-runtime/setup-tool/executor.mjs +6 -2
  397. package/src/session-runtime/setup-tool/tool-defs.mjs +2 -1
  398. package/src/session-runtime/tool-policy/policy-stage.mjs +1 -0
  399. package/src/session-runtime/turn/agent-ops.mjs +23 -0
  400. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +49 -11
  401. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +5 -1
  402. package/src/session-runtime/workflow-agents-api/agent-route.mjs +25 -5
  403. package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -3
  404. package/src/session-runtime/workspace/activity-guards.mjs +176 -0
  405. package/src/session-runtime/workspace/session-view.mjs +149 -0
  406. package/src/session-runtime/workspace/side-checkouts.mjs +203 -0
  407. package/src/session-runtime/workspace/transition-runner.mjs +188 -0
  408. package/src/session-runtime/workspace/transitions.mjs +108 -1041
  409. package/src/session-runtime/workspace/workspace-merge.mjs +132 -0
  410. package/src/session-runtime/workspace/worktree-intents.mjs +242 -0
  411. package/src/session-runtime/workspace/worktree-maintenance.mjs +80 -0
  412. package/src/session-runtime/workspace/worktree-setup.mjs +85 -0
  413. package/src/session-runtime/workspace/worktree-tool.mjs +73 -56
  414. package/src/standalone/daemon-desktop-runtime.mjs +2 -0
  415. package/src/standalone/daemon-session-control.mjs +169 -138
  416. package/src/standalone/daemon.mjs +7 -0
  417. package/src/standalone/desktop-service-registry.mjs +9 -0
  418. package/src/standalone/local-session-runtime.mjs +5 -1
  419. package/src/standalone/session-client/daemon-unavailable.mjs +3 -0
  420. package/src/standalone/session-client.mjs +5 -1
  421. package/src/standalone/session-frame-stream.mjs +83 -11
  422. package/src/standalone/session-protocol.mjs +4 -0
  423. package/src/standalone/session-service/agent-tree/agent-turns.mjs +16 -12
  424. package/src/standalone/session-service/agent-tree.mjs +45 -9
  425. package/src/standalone/session-service/projection/transcript-window.mjs +700 -64
  426. package/src/standalone/session-service/session-calls/session-action.mjs +10 -3
  427. package/src/standalone/session-service/session-calls/session-views.mjs +160 -104
  428. package/src/standalone/session-service/stored-reader.mjs +127 -40
  429. package/src/standalone/session-transport/client-registry/client-grace.mjs +12 -9
  430. package/src/tui/App.jsx +154 -164
  431. package/src/tui/app/app-view/floating-panel.jsx +10 -4
  432. package/src/tui/app/app-view/text-entry-labels.mjs +1 -0
  433. package/src/tui/app/clipboard.mjs +21 -10
  434. package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +1 -3
  435. package/src/tui/app/model-picker/route-selection.mjs +5 -6
  436. package/src/tui/app/prompt-handlers/use-prompt-history-nav.mjs +1 -1
  437. package/src/tui/app/prompt-handlers/use-prompt-interrupt.mjs +3 -0
  438. package/src/tui/app/prompt-submit/provider-prompts.mjs +3 -1
  439. package/src/tui/app/provider-setup/api-key-actions.mjs +31 -1
  440. package/src/tui/app/provider-setup/oauth-actions.mjs +58 -21
  441. package/src/tui/app/provider-setup/provider-items.mjs +8 -2
  442. package/src/tui/app/provider-setup-picker.mjs +29 -18
  443. package/src/tui/app/shell-layout/row-budgets.mjs +12 -10
  444. package/src/tui/app/slash-commands.mjs +1 -0
  445. package/src/tui/app/slash-dispatch/session-commands.mjs +46 -0
  446. package/src/tui/app/slash-dispatch.mjs +1 -1
  447. package/src/tui/app/use-btw-panel.mjs +29 -0
  448. package/src/tui/app/use-context-panel-refresh.mjs +31 -0
  449. package/src/tui/app/use-model-catalog-cache.mjs +13 -10
  450. package/src/tui/app/use-mouse-input.mjs +11 -0
  451. package/src/tui/app/use-onboarding-start.mjs +8 -3
  452. package/src/tui/app/use-panel-transition.mjs +10 -1
  453. package/src/tui/app/use-pasted-buffers.mjs +28 -20
  454. package/src/tui/app/use-prompt-draft-flow.mjs +7 -0
  455. package/src/tui/app/use-prompt-layout-rows.mjs +3 -3
  456. package/src/tui/app/use-prompt-queue-history.mjs +3 -2
  457. package/src/tui/app/use-selection-focus.mjs +11 -2
  458. package/src/tui/app/use-selection-geometry.mjs +18 -9
  459. package/src/tui/app/use-selection-paint.mjs +4 -4
  460. package/src/tui/app/use-selection-stitch.mjs +3 -3
  461. package/src/tui/app/use-slash-palette.mjs +62 -0
  462. package/src/tui/app/use-terminal-chrome.mjs +2 -2
  463. package/src/tui/app/use-transcript-activity.mjs +47 -32
  464. package/src/tui/app/use-transcript-follow.mjs +12 -4
  465. package/src/tui/app/use-transcript-scroll.mjs +46 -17
  466. package/src/tui/app/use-transcript-swap-reset.mjs +27 -0
  467. package/src/tui/app/use-transcript-window/geometry.mjs +19 -5
  468. package/src/tui/app/use-transcript-window/instance-state.mjs +4 -0
  469. package/src/tui/app/use-transcript-window/selection-shift.mjs +5 -1
  470. package/src/tui/app/use-transcript-window.mjs +21 -11
  471. package/src/tui/app/use-tui-ready.mjs +18 -0
  472. package/src/tui/app/use-welcome-prompt-hint.mjs +5 -0
  473. package/src/tui/app/use-workflow-tab-cycle.mjs +5 -3
  474. package/src/tui/components/Picker.jsx +27 -3
  475. package/src/tui/components/QuestionPanel.jsx +13 -4
  476. package/src/tui/components/SignInUrl.jsx +68 -0
  477. package/src/tui/components/TextEntryPanel.jsx +76 -46
  478. package/src/tui/components/sign-in-url-layout.mjs +24 -0
  479. package/src/tui/dist/index.mjs +1963 -1361
  480. package/src/tui/session/goal-turn-state.mjs +6 -2
  481. package/src/tui/session/session-api/controls.mjs +4 -0
  482. package/src/tui/session/session-api/extensions.mjs +8 -0
  483. package/src/tui/session/session-api/intake/abort.mjs +2 -1
  484. package/src/tui/session/session-api/intake.mjs +2 -0
  485. package/src/tui/session/session-api/integrations.mjs +2 -2
  486. package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
  487. package/src/tui/session/session-api/settings.mjs +2 -2
  488. package/src/tui/session/session-flow/queue/take-entries.mjs +11 -0
  489. package/src/tui/session/transcript-spill/spill-writer.mjs +15 -0
  490. package/src/tui/session/transcript-spill.mjs +92 -4
  491. package/src/tui/session/transcript-store.mjs +2 -0
  492. package/src/ui/statusline-agents.mjs +1 -3
  493. package/src/defaults/skills/skill-creator/references/source-review.md +0 -94
@@ -111,13 +111,25 @@ import {
111
111
  providerAccountLimit,
112
112
  providerAccountBlockedUntil,
113
113
  ACCOUNT_PROVIDERS,
114
+ MAX_ACCOUNTS,
115
+ customProviderKeyIds,
116
+ setCustomProviderKeyIds,
114
117
  } from '../../runtime/shared/provider-accounts.mjs';
118
+ import { customProviderKeyAccount } from '../../runtime/shared/custom-provider-keys.mjs';
115
119
  import { currentProviderAccountId, withProviderAccount } from '../../runtime/shared/provider-auth-binding.mjs';
116
120
  import {
117
121
  CUSTOM_API_KEY_ENV_RE,
118
122
  effectiveCustomModelRows,
119
123
  normalizeCustomProviderConfig as normalizeStoredCustomConfig,
120
124
  } from '../../runtime/agent/orchestrator/providers/custom-provider-config.mjs';
125
+ import {
126
+ VERTEX_ADC_MODE,
127
+ VERTEX_API_KEY_MODE,
128
+ describeVertexAdc,
129
+ parseVertexServiceAccount,
130
+ probeVertexMetadata,
131
+ } from '../../runtime/agent/orchestrator/providers/vertex-ai-auth.mjs';
132
+ import { vertexEnvLocation } from '../../runtime/agent/orchestrator/providers/vertex-ai-models.mjs';
121
133
 
122
134
  const API_PROVIDERS = Object.freeze([
123
135
  Object.freeze({ id: 'openrouter', name: 'OpenRouter', env: 'OPENROUTER_API_KEY', url: 'https://openrouter.ai/keys' }),
@@ -134,6 +146,7 @@ const API_PROVIDERS = Object.freeze([
134
146
  url: 'https://console.anthropic.com/settings/keys',
135
147
  }),
136
148
  Object.freeze({ id: 'gemini', name: 'Gemini API', env: 'GEMINI_API_KEY', url: 'https://aistudio.google.com/apikey' }),
149
+ Object.freeze({ id: 'vertex-ai', name: 'Vertex AI', env: null, url: null }),
137
150
  Object.freeze({
138
151
  id: 'deepseek',
139
152
  name: 'DeepSeek API',
@@ -374,6 +387,23 @@ function storedSecretDetail(account) {
374
387
  return isFallbackSecret(account) ? 'stored in private file' : 'stored in keychain';
375
388
  }
376
389
 
390
+ // The Vertex AI gcloud-login row's detail: who is signed in and where, or what to fix.
391
+ function vertexAdcDetail(adc, location) {
392
+ if (!adc) return `gcloud login · ${location}`;
393
+ if (adc.state !== 'ready') return adc.message;
394
+ const account = adc.account || (adc.type === 'metadata' ? 'metadata server' : 'gcloud user login');
395
+ return [account, adc.projectId || 'no project', location].join(' · ');
396
+ }
397
+
398
+ // The Vertex AI location a row shows and where it comes from: the saved one
399
+ // ('config'), else what the runtime would use unset ('environment' or
400
+ // 'default'), which the connect form offers as a hint without saving it.
401
+ function vertexRowLocation(configured) {
402
+ if (configured.location) return { location: String(configured.location), locationSource: 'config' };
403
+ const env = vertexEnvLocation();
404
+ return env ? { location: env, locationSource: 'environment' } : { location: 'global', locationSource: 'default' };
405
+ }
406
+
377
407
  // `keychainIssue`: the setup response's one keyring check (never asked per row).
378
408
  function apiProviderSetup(p, providers, checkSecrets, keychainIssue) {
379
409
  const configured = providers[p.id] || {};
@@ -402,6 +432,74 @@ function apiProviderSetup(p, providers, checkSecrets, keychainIssue) {
402
432
  }
403
433
  const rowIssue = authenticated ? null : keychainIssue;
404
434
  if (rowIssue) detail = rowIssue.message;
435
+ if (p.id === 'vertex-ai' && configured.authMode === VERTEX_ADC_MODE) {
436
+ // gcloud login: nothing is stored; connected while enabled and the login is readable.
437
+ const { location, locationSource } = vertexRowLocation(configured);
438
+ const adc = checkSecrets ? describeVertexAdc({ projectId: configured.projectId }) : null;
439
+ // Without a login file a metadata server may stand in: probed once, in the
440
+ // background, for the next refresh to report.
441
+ if (adc?.state === 'missing') void probeVertexMetadata();
442
+ const ready = configuredEnabled && (!adc || adc.state === 'ready');
443
+ let adcStatus = configuredEnabled ? 'Not connected' : 'Off';
444
+ if (ready) adcStatus = 'Set';
445
+ return {
446
+ ...p,
447
+ group: 'api',
448
+ type: 'api-key',
449
+ authKind: 'adc',
450
+ authMode: VERTEX_ADC_MODE,
451
+ enabled: configuredEnabled,
452
+ authenticated: ready,
453
+ stored: false,
454
+ env: false,
455
+ envName: null,
456
+ status: adcStatus,
457
+ detail: vertexAdcDetail(adc, location),
458
+ location,
459
+ locationSource,
460
+ projectId: adc?.projectId || String(configured.projectId || '') || null,
461
+ ...(adc ? { adc } : {}),
462
+ };
463
+ }
464
+ if (p.id === 'vertex-ai') {
465
+ const { location, locationSource } = vertexRowLocation(configured);
466
+ // An express-mode API key, or the service-account key.
467
+ const express = configured.authMode === VERTEX_API_KEY_MODE;
468
+ // The connect form offers the gcloud login too; its status line reads this.
469
+ const adc = checkSecrets && !authenticated ? describeVertexAdc({ projectId: configured.projectId }) : null;
470
+ let projectId = String(configured.projectId || '');
471
+ if (!projectId && authenticated && checkSecrets && !express) {
472
+ try {
473
+ projectId = parseVertexServiceAccount(getAgentApiKey(p.id)).projectId;
474
+ } catch {
475
+ // An unreadable key leaves the project id empty; the status line then shows the location only.
476
+ }
477
+ }
478
+ if (!rowIssue) {
479
+ if (express) detail = `API key (express mode) · ${location}`;
480
+ else detail = projectId ? `${projectId} · ${location}` : `Location: ${location}`;
481
+ }
482
+ return {
483
+ ...p,
484
+ ...(rowIssue ? { keychainIssue: rowIssue } : {}),
485
+ group: 'api',
486
+ type: 'api-key',
487
+ authKind: express ? 'api-key' : 'service-account',
488
+ authMode: express ? VERTEX_API_KEY_MODE : 'service-account',
489
+ ...(adc ? { adc } : {}),
490
+ enabled: configuredEnabled || authenticated,
491
+ authenticated,
492
+ stored,
493
+ env: false,
494
+ envName: null,
495
+ status,
496
+ detail,
497
+ location,
498
+ locationSource,
499
+ projectId: projectId || null,
500
+ ...(credentialSource ? { credentialSource } : {}),
501
+ };
502
+ }
405
503
  return {
406
504
  ...p,
407
505
  ...(rowIssue ? { keychainIssue: rowIssue } : {}),
@@ -561,8 +659,12 @@ function customProviderRow(id, entry, checkSecrets = true) {
561
659
  row.auth = keyless ? 'none' : 'key';
562
660
  row.discovery = structuredClone(config?.discovery ?? entry.discovery ?? { newModelsHidden: false });
563
661
  row.modelOverrides = structuredClone(config?.modelOverrides ?? entry.modelOverrides ?? {});
662
+ if (config?.headers) row.headers = { ...config.headers };
564
663
  if (config?.wires) row.wires = [...config.wires];
565
664
  if (config?.modelWires) row.modelWires = { ...config.modelWires };
665
+ // Additional API keys by id only; their values never leave the keychain.
666
+ const extraKeyIds = keyless ? [] : customProviderKeyIds(id);
667
+ if (extraKeyIds.length) row.extraApiKeys = extraKeyIds.map((keyId) => ({ id: keyId }));
566
668
  // A stored config that does not normalize cannot run (the registry skips
567
669
  // it): say so explicitly instead of listing it as connected.
568
670
  // Every readiness flag consumers read says "not ready" (`stored` stays the
@@ -660,6 +762,20 @@ async function modelProxyProviderSetup(options) {
660
762
 
661
763
  function apiProviderStatusRow(p, config) {
662
764
  const configured = config.providers?.[p.id] || {};
765
+ if (p.id === 'vertex-ai' && configured.authMode === VERTEX_ADC_MODE) {
766
+ const adc = describeVertexAdc({ projectId: configured.projectId });
767
+ return {
768
+ id: p.id,
769
+ type: 'api-key',
770
+ enabled: configured.enabled === true,
771
+ authenticated: configured.enabled === true && adc.state === 'ready',
772
+ stored: false,
773
+ env: false,
774
+ envName: null,
775
+ label: p.name,
776
+ detail: vertexAdcDetail(adc, vertexRowLocation(configured).location),
777
+ };
778
+ }
663
779
  const envName = AGENT_PROVIDER_ENV[p.id] || p.env;
664
780
  const env = Boolean(envName && process.env[envName]);
665
781
  const stored = hasStoredSecret(SECRET_ACCOUNTS.agentApiKey(p.id));
@@ -720,6 +836,7 @@ export function renderProviderStatus(config = {}) {
720
836
  if (row.type === 'oauth') source = row.detail || 'oauth';
721
837
  else if (row.env) source = `env:${row.envName}`;
722
838
  else if (row.stored) source = 'keychain';
839
+ else if (row.detail) source = row.detail;
723
840
  const enabled = row.enabled ? 'enabled' : 'disabled';
724
841
  return `${row.id.padEnd(width)} ${row.type.padEnd(7)} ${auth.padEnd(8)} ${enabled.padEnd(8)} ${source}`;
725
842
  })
@@ -984,15 +1101,68 @@ export async function updateProviderAccounts(provider, change) {
984
1101
  return listProviderAccounts(provider);
985
1102
  }
986
1103
 
987
- export async function saveProviderApiKey(cfgMod, provider, secret) {
1104
+ export async function saveProviderApiKey(cfgMod, provider, secret, options) {
988
1105
  requireLocalProviderManagement();
989
1106
  const id = String(provider || '').trim();
990
1107
  if (!API_PROVIDER_IDS.has(id)) throw new Error(`unknown API-key provider "${id}"`);
991
1108
  const value = String(secret || '').trim();
992
- if (!value) throw new Error(`API key is required for ${id}`);
1109
+ const adc = id === 'vertex-ai' && options?.authMode === VERTEX_ADC_MODE;
1110
+ const express = id === 'vertex-ai' && options?.authMode === VERTEX_API_KEY_MODE;
1111
+ if (!value && !adc) throw new Error(`API key is required for ${id}`);
1112
+ let patch = { enabled: true };
1113
+ if (id === 'vertex-ai') {
1114
+ if (adc && value) throw new Error('Vertex AI gcloud login takes no key');
1115
+ if (express && /[\s{]/.test(value)) throw new Error('Vertex AI express mode takes an API key, not a JSON key file');
1116
+ if (!adc && !express) parseVertexServiceAccount(value);
1117
+ // Only a location the user chose is saved: an empty one is unset (the
1118
+ // runtime then follows the environment's region, then global). None given
1119
+ // at all (the terminal setup) keeps a saved one; express mode never keeps
1120
+ // one, as it serves global only.
1121
+ const location = String(options?.location ?? '')
1122
+ .trim()
1123
+ .toLowerCase();
1124
+ const projectId = String(options?.projectId || '').trim();
1125
+ if (location && !/^[a-z0-9-]{1,63}$/.test(location)) throw new Error('Vertex AI location is invalid');
1126
+ if (express && location && location !== 'global') {
1127
+ throw new Error('Vertex AI express mode serves only the global location');
1128
+ }
1129
+ const locationPatch =
1130
+ location || express || options?.location !== undefined ? { location: location || undefined } : {};
1131
+ if (projectId && (!/^[a-zA-Z0-9._:-]{1,128}$/.test(projectId) || projectId.includes('/'))) {
1132
+ throw new Error('Vertex AI project ID override is invalid');
1133
+ }
1134
+ if (adc) {
1135
+ // gcloud login: nothing secret is stored, and the login must already be usable.
1136
+ let login = describeVertexAdc({ projectId });
1137
+ // No login file: a GCE / Cloud Run / GKE host's metadata server.
1138
+ if (login.state === 'missing' && (await probeVertexMetadata())) login = describeVertexAdc({ projectId });
1139
+ if (login.state !== 'ready') throw new Error(login.message);
1140
+ if (!login.projectId) {
1141
+ throw new Error(
1142
+ 'No Google Cloud project found for the gcloud login. Enter a project ID override, or run `gcloud config set project PROJECT_ID`.'
1143
+ );
1144
+ }
1145
+ // The login replaces a key saved earlier rather than sitting beside it.
1146
+ const account = SECRET_ACCOUNTS.agentApiKey(id);
1147
+ if (hasStoredSecret(account)) await deleteSecretAsync(account);
1148
+ updateConfigProvider(cfgMod, id, {
1149
+ enabled: true,
1150
+ ...locationPatch,
1151
+ projectId: projectId || undefined,
1152
+ authMode: VERTEX_ADC_MODE,
1153
+ });
1154
+ return { provider: id, type: 'adc', authenticated: true };
1155
+ }
1156
+ patch = {
1157
+ enabled: true,
1158
+ ...locationPatch,
1159
+ projectId: projectId || undefined,
1160
+ authMode: express ? VERTEX_API_KEY_MODE : 'service-account',
1161
+ };
1162
+ }
993
1163
  await saveSecretAsync(SECRET_ACCOUNTS.agentApiKey(id), value);
994
- updateConfigProvider(cfgMod, id, { enabled: true });
995
- return { provider: id, type: 'api-key', authenticated: true };
1164
+ updateConfigProvider(cfgMod, id, patch);
1165
+ return { provider: id, type: id === 'vertex-ai' && !express ? 'service-account' : 'api-key', authenticated: true };
996
1166
  }
997
1167
 
998
1168
  function sanitizeCustomError(error, secrets = []) {
@@ -1049,6 +1219,63 @@ function validateCustomTransportFields(input) {
1049
1219
  if (typeof input.apiKey === 'string' && input.apiKey !== '')
1050
1220
  throw new TypeError('custom provider apiKey and apiKeyEnv are mutually exclusive');
1051
1221
  }
1222
+ if (input.extraApiKeys !== undefined) validateExtraApiKeys(input.extraApiKeys);
1223
+ }
1224
+
1225
+ const CUSTOM_KEY_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
1226
+
1227
+ // Additional API keys in use order, beside the primary key (MAX_ACCOUNTS in
1228
+ // all): `{ id }` keeps a saved key, `{ apiKey }` adds one and `{ id, apiKey }`
1229
+ // replaces a saved key's value; a blank `apiKey` keeps the saved key.
1230
+ function validateExtraApiKeys(rows) {
1231
+ const invalid = () => new TypeError('custom provider extraApiKeys are invalid');
1232
+ if (!Array.isArray(rows) || rows.length >= MAX_ACCOUNTS) throw invalid();
1233
+ const ids = new Set();
1234
+ for (const row of rows) {
1235
+ if (!row || typeof row !== 'object' || Array.isArray(row)) throw invalid();
1236
+ if (Object.keys(row).some((key) => key !== 'id' && key !== 'apiKey')) throw invalid();
1237
+ if (row.id !== undefined && (typeof row.id !== 'string' || !CUSTOM_KEY_ID_RE.test(row.id) || ids.has(row.id)))
1238
+ throw new TypeError('custom provider extraApiKeys id is invalid');
1239
+ if (row.apiKey !== undefined && (typeof row.apiKey !== 'string' || row.apiKey.length > CUSTOM_API_KEY_MAX))
1240
+ throw new TypeError('custom provider extraApiKeys apiKey is invalid');
1241
+ if (row.id === undefined && !row.apiKey?.trim()) throw new TypeError('custom provider additional API key is empty');
1242
+ if (row.id !== undefined) ids.add(row.id);
1243
+ }
1244
+ }
1245
+
1246
+ // The additional-key change a save makes over the `saved` key ids: `rows`
1247
+ // absent keeps them; a typed key is stored under a new id, so a replaced key
1248
+ // starts without the old one's cooldown. A keyless provider keeps none.
1249
+ function extraKeyPlan(saved, rows, keyless) {
1250
+ if (keyless) return { saved, ids: [], added: [], removed: saved };
1251
+ if (rows === undefined) return { saved, ids: saved, added: [], removed: [] };
1252
+ const ids = [];
1253
+ const added = [];
1254
+ for (const row of rows) {
1255
+ if (row.id !== undefined && !saved.includes(row.id)) throw new Error('An additional API key is no longer saved.');
1256
+ const typed = typeof row.apiKey === 'string' ? row.apiKey.trim() : '';
1257
+ if (!typed) {
1258
+ ids.push(row.id);
1259
+ continue;
1260
+ }
1261
+ const keyId = randomUUID();
1262
+ ids.push(keyId);
1263
+ added.push({ keyId, key: typed });
1264
+ }
1265
+ return { saved, ids, added, removed: saved.filter((keyId) => !ids.includes(keyId)) };
1266
+ }
1267
+
1268
+ const extraKeysChanged = (plan) => plan.ids.join() !== plan.saved.join();
1269
+
1270
+ async function applyExtraKeys(id, plan) {
1271
+ for (const { keyId, key } of plan.added) await saveSecretAsync(customProviderKeyAccount(id, keyId), key);
1272
+ if (extraKeysChanged(plan)) await setCustomProviderKeyIds(id, plan.ids);
1273
+ }
1274
+
1275
+ // A failed save puts the saved roster back and drops the keys it added.
1276
+ async function revertExtraKeys(id, plan) {
1277
+ if (extraKeysChanged(plan)) await setCustomProviderKeyIds(id, plan.saved);
1278
+ for (const { keyId } of plan.added) await deleteSecretAsync(customProviderKeyAccount(id, keyId));
1052
1279
  }
1053
1280
 
1054
1281
  /** Whether the named environment variable is set to a non-empty value. */
@@ -1064,7 +1291,7 @@ function inputObject(input) {
1064
1291
  }
1065
1292
 
1066
1293
  // An edit that omits a stored setting keeps it: `auth` for every input shape,
1067
- // and `discovery` / `modelOverrides` for a v2 input (a legacy `models` input
1294
+ // and `discovery` / `modelOverrides` / `headers` for a v2 input (a legacy `models` input
1068
1295
  // states the whole manual model list itself).
1069
1296
  function withStoredSettings(input, entry) {
1070
1297
  const stored = storedCustomConfig(entry);
@@ -1079,6 +1306,8 @@ function withStoredSettings(input, entry) {
1079
1306
  if (out.schemaVersion === stored.schemaVersion) {
1080
1307
  if (out.discovery === undefined) out.discovery = stored.discovery;
1081
1308
  if (out.modelOverrides === undefined) out.modelOverrides = stored.modelOverrides;
1309
+ // `headers: {}` clears them; an omitted field keeps the stored ones.
1310
+ if (out.headers === undefined && stored.headers) out.headers = stored.headers;
1082
1311
  // Served wires and per-model wires have no setup field yet: an edit on
1083
1312
  // the same protocol keeps them (another protocol serves other wires).
1084
1313
  if ((out.protocol ?? stored.protocol) === stored.protocol) {
@@ -1095,7 +1324,9 @@ function withStoredSettings(input, entry) {
1095
1324
  // the config. Synchronous, so a caller can merge and commit one snapshot.
1096
1325
  function customInputAt(snapshot, input, normalizeCustomProviderConfig, options) {
1097
1326
  const id = input.id === undefined ? null : input.id;
1098
- const merged = id ? withStoredSettings(input, existingCustomEntry(snapshot, id)) : input;
1327
+ // Additional keys are secrets, applied by the save itself (extraKeyPlan).
1328
+ const { extraApiKeys: _extraKeys, ...settings } = input;
1329
+ const merged = id ? withStoredSettings(settings, existingCustomEntry(snapshot, id)) : settings;
1099
1330
  const config = normalizeCustomProviderConfig(merged, options);
1100
1331
  const typed = typeof merged.apiKey === 'string' ? merged.apiKey.trim() : '';
1101
1332
  const keyless = config.auth === 'none';
@@ -1147,9 +1378,16 @@ export async function saveCustomProvider(cfgMod, rawInput) {
1147
1378
  const commit = async () => {
1148
1379
  const account = SECRET_ACCOUNTS.agentApiKey(id);
1149
1380
  const previousKey = typed && existingId ? getAgentApiKey(id) : null;
1381
+ // Auth as of this save's turn in the key queue: a no-auth save queued
1382
+ // earlier may have landed since the input was resolved.
1383
+ const keyless = existingId
1384
+ ? customInputAt(cfgMod.loadConfig(), rawInput, normalizeCustomProviderConfig, { requireModels: false }).keyless
1385
+ : resolved.keyless;
1386
+ const extra = extraKeyPlan(existingId ? customProviderKeyIds(id) : [], rawInput.extraApiKeys, keyless);
1150
1387
  if (typed) await saveSecretAsync(account, typed);
1151
1388
  let entry;
1152
1389
  try {
1390
+ await applyExtraKeys(id, extra);
1153
1391
  // Synchronous from here: omitted settings come from the config this
1154
1392
  // save commits over, so an edit that landed during the awaits above (a
1155
1393
  // model's visibility, a peer save, a removal) is never reverted.
@@ -1162,15 +1400,29 @@ export async function saveCustomProvider(cfgMod, rawInput) {
1162
1400
  { baseConfig: current }
1163
1401
  );
1164
1402
  } catch (error) {
1403
+ await revertExtraKeys(id, extra);
1165
1404
  if (typed) {
1166
1405
  if (previousKey) await saveSecretAsync(account, previousKey);
1167
1406
  else await deleteSecretAsync(account);
1168
1407
  }
1169
1408
  throw error;
1170
1409
  }
1410
+ // A removed key is dropped once the save holds; a keychain refusal then
1411
+ // leaves an unused secret behind (reported), never a failed save that did commit.
1412
+ for (const keyId of extra.removed) {
1413
+ await deleteSecretAsync(customProviderKeyAccount(id, keyId)).catch((error) => {
1414
+ process.stderr.write(
1415
+ `[provider-admin] could not delete additional API key ${keyId} of ${id}: ${error?.message || error}\n`
1416
+ );
1417
+ });
1418
+ }
1171
1419
  return customProviderRow(id, entry);
1172
1420
  };
1173
- return typed ? serializeCustomKeySave(id, commit) : commit();
1421
+ // Every save that may change the keys runs in the provider's key queue: a
1422
+ // typed key, an additional-key list, or a no-auth save (which drops them),
1423
+ // decided from the input alone, never from a roster read outside the queue.
1424
+ const keysChange = typed || rawInput.extraApiKeys !== undefined || (existingId && resolved.keyless);
1425
+ return keysChange ? serializeCustomKeySave(id, commit) : commit();
1174
1426
  }
1175
1427
 
1176
1428
  // Replaces only the stored key of an existing custom provider. The provider's
@@ -1179,7 +1431,8 @@ export async function saveCustomProvider(cfgMod, rawInput) {
1179
1431
  export async function saveCustomProviderKey(cfgMod, rawId, rawKey) {
1180
1432
  requireLocalProviderManagement();
1181
1433
  const id = String(rawId || '').trim();
1182
- if (typeof rawKey !== 'string' || rawKey.length > CUSTOM_API_KEY_MAX) throw new TypeError('custom provider apiKey is invalid');
1434
+ if (typeof rawKey !== 'string' || rawKey.length > CUSTOM_API_KEY_MAX)
1435
+ throw new TypeError('custom provider apiKey is invalid');
1183
1436
  const key = rawKey.trim();
1184
1437
  if (!key) throw new Error('API key is required');
1185
1438
  const takesStoredKey = () => {
@@ -1398,6 +1651,9 @@ export async function removeCustomProvider(cfgMod, rawId) {
1398
1651
  // provider registered so the removal can be retried. A missing secret
1399
1652
  // (keyless provider) is not an error.
1400
1653
  await deleteSecretAsync(SECRET_ACCOUNTS.agentApiKey(id));
1654
+ const extraKeyIds = customProviderKeyIds(id);
1655
+ for (const keyId of extraKeyIds) await deleteSecretAsync(customProviderKeyAccount(id, keyId));
1656
+ if (extraKeyIds.length) await setCustomProviderKeyIds(id, []);
1401
1657
  const current = cfgMod.loadConfig();
1402
1658
  const { [id]: _removed, ...providers } = current.providers || {};
1403
1659
  cfgMod.saveConfig(withoutProviderReferences({ ...current, providers }, id), { baseConfig: current });
@@ -1424,13 +1680,29 @@ export async function testCustomProvider(cfgMod, rawInput) {
1424
1680
  error: { kind: 'empty', message: 'No models were found for this provider' },
1425
1681
  };
1426
1682
  const { createCustomProvider } = await loadCustomProviderModule();
1427
- const provider = await createCustomProvider(id || 'custom-test', { ...config, apiKey });
1428
- await withTimeout(
1683
+ const primary = await createCustomProvider(id || 'custom-test', { ...config, apiKey });
1684
+ // A provider with additional keys tests through the same key pool as a
1685
+ // real request: an explicit quota refusal moves on to the next key.
1686
+ const keyIds = id && !config.apiKeyEnv && config.auth !== 'none' ? customProviderKeyIds(id) : [];
1687
+ let provider = primary;
1688
+ let routingKey;
1689
+ if (keyIds.length) {
1690
+ const [pool, keys] = await Promise.all([
1691
+ import('../../runtime/agent/orchestrator/providers/account-pool.mjs'),
1692
+ import('../../runtime/agent/orchestrator/providers/custom-provider-keys.mjs'),
1693
+ ]);
1694
+ routingKey = pool.ROUTING_METADATA_KEY;
1695
+ provider = pool.createCustomKeyPoolProvider(id, primary, (keyId) => keys.customKeyEngine(primary, id, keyId));
1696
+ }
1697
+ const answer = await withTimeout(
1429
1698
  (signal) => provider.send([{ role: 'user', content: 'Reply with OK.' }], model, [], { signal, maxTokens: 16 }),
1430
1699
  CUSTOM_TEST_TIMEOUT_MS,
1431
1700
  'connection test'
1432
1701
  );
1433
- return { ok: true };
1702
+ // Key numbers count the primary key as 1.
1703
+ const served = routingKey ? answer?.providerMetadata?.[routingKey]?.accountId : undefined;
1704
+ const index = keyIds.indexOf(served);
1705
+ return index < 0 ? { ok: true } : { ok: true, key: { id: served, number: index + 2 } };
1434
1706
  } catch (error) {
1435
1707
  throw sanitizeCustomError(error, customSecrets(apiKey, config));
1436
1708
  }
@@ -36,6 +36,8 @@ export function createSessionTurnApi(deps) {
36
36
  agentControl: agent.agentControl,
37
37
  taskControl: agent.taskControl,
38
38
  stopShellJob: agent.stopShellJob,
39
+ listStoppableShellJobs: agent.listStoppableShellJobs,
40
+ stopShellJobs: agent.stopShellJobs,
39
41
  releaseShellJob: agent.releaseShellJob,
40
42
  listShellJobs: agent.listShellJobs,
41
43
  onNotification: agent.onNotification,
@@ -8,6 +8,7 @@ import {
8
8
  setBuiltinInstalledInConfig,
9
9
  } from '../runtime/agent/orchestrator/runtime-core/builtin-features.mjs';
10
10
  import { featureEnvOverride, moduleEnabled } from '../runtime/agent/orchestrator/runtime-core/config-helpers.mjs';
11
+ import { noteCacheWarmingEnabled } from '../runtime/agent/orchestrator/session/cache-warm.mjs';
11
12
  import { normalizeAgentAvatar } from '../runtime/shared/agent-avatar.mjs';
12
13
  import { LOCAL_PROVIDER_ID } from '../runtime/local-provider/managed-runtime.mjs';
13
14
  import { INSTRUCTION_FILES_MODES, normalizeInstructionFilesMode } from '../runtime/shared/instruction-files-mode.mjs';
@@ -100,11 +101,13 @@ export function createBuiltinToolSettings(
100
101
  // default on. The warmer reads the saved value at each
101
102
  // warm, so a change applies without a restart.
102
103
  getCacheWarmingSettings() {
103
- return { enabled: getConfig()?.promptCacheWarming !== false };
104
+ return { enabled: getConfig()?.promptCacheWarming === true };
104
105
  },
105
106
  setCacheWarmingEnabled(enabled) {
106
107
  if (typeof enabled !== 'boolean') throw new TypeError('cache warming must be on or off (a boolean)');
107
108
  saveConfigAndAdopt({ ...getConfig(), promptCacheWarming: enabled });
109
+ // The warmer's synchronous state follows at once (off cancels chains).
110
+ noteCacheWarmingEnabled(enabled);
108
111
  return this.getCacheWarmingSettings();
109
112
  },
110
113
  // The one pixel face every Lead row on the bot page wears, shared by every
@@ -57,7 +57,7 @@ export function createSystemSettings({
57
57
  },
58
58
  getHeartbeatSettings() {
59
59
  return {
60
- heartbeat: getConfig()?.heartbeat !== false,
60
+ heartbeat: getConfig()?.heartbeat === true,
61
61
  intervalMinutes: normalizeHeartbeatIntervalMinutes(getConfig()?.heartbeatIntervalMinutes),
62
62
  };
63
63
  },
@@ -3,8 +3,12 @@
3
3
  * prompt-cache warming. Each goes through the runtime method its settings
4
4
  * surface uses. Permission and connector writes are made as a remote client's
5
5
  * are (`noWiden`): a chat may take authority away, never give an agent more
6
- * without asking — that stays with the person in the desktop app. */
6
+ * without asking — that stays with the person: in the desktop app, or on
7
+ * the approval card a connector tool's widening raises
8
+ * (connector-widen-approval.mjs). */
9
+ import { CONNECTOR_POLICY_WIDENS } from '../../runtime/agent/orchestrator/mcp/agent-connectors.mjs';
7
10
  import { agentPermissions, setAgentPermission } from '../../runtime/agent/orchestrator/session/human-gate/store.mjs';
11
+ import { askToWidenConnectorTool } from './connector-widen-approval.mjs';
8
12
  import { clean } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
9
13
 
10
14
  const NO_WIDEN = Object.freeze({ noWiden: true });
@@ -38,11 +42,17 @@ export const AGENT_SETTINGS_HANDLERS = {
38
42
  const name = requireText(args.name, 'name');
39
43
  return { agent, connectors: await rt.setAgentConnectorEnabled(agent, name, args.enabled, NO_WIDEN) };
40
44
  },
41
- async set_agent_connector_tool(rt, args) {
45
+ // A narrowing applies at once; a widening waits on the person's card.
46
+ async set_agent_connector_tool(rt, args, context) {
42
47
  const agent = requireText(args.agent, 'agent');
43
48
  const name = requireText(args.name, 'name');
44
49
  const tool = requireText(args.tool, 'tool');
45
- await rt.setAgentConnectorToolPermission(agent, name, tool, args.permission, NO_WIDEN);
50
+ try {
51
+ await rt.setAgentConnectorToolPermission(agent, name, tool, args.permission, NO_WIDEN);
52
+ } catch (error) {
53
+ if (error?.code !== CONNECTOR_POLICY_WIDENS) throw error;
54
+ return askToWidenConnectorTool(rt, { agent, server: name, tool, requested: args.permission }, context);
55
+ }
46
56
  return { agent, connector: name, tool, permission: args.permission };
47
57
  },
48
58
  async set_project_connector(rt, args) {
@@ -0,0 +1,96 @@
1
+ /** A bot's request for more authority on one connector tool (setup
2
+ * set_agent_connector_tool toward auto or needs_approval): the model cannot
3
+ * tell the user's words from text it read, so the change waits on an
4
+ * approval card (human-gate/requests.mjs kind `connector`) that only a
5
+ * person answers. The card offers each level above the tool's current one
6
+ * (needs_approval, auto) and Deny; nothing is remembered, so every widening
7
+ * asks again. The picked level is applied to that one tool, re-judged on the
8
+ * stored policy inside its write (mcp/agent-connectors.mjs
9
+ * withApprovedConnectorToolChange): a restriction that landed meanwhile stays. */
10
+ import {
11
+ CONNECTOR_TOOL_ALREADY,
12
+ CONNECTOR_TOOL_NARROWED,
13
+ NO_WIDEN_MESSAGE,
14
+ PERMISSION_RANK,
15
+ agentConnectors,
16
+ connectorGroupId,
17
+ connectorToolPermission,
18
+ withApprovedConnectorToolChange,
19
+ } from '../../runtime/agent/orchestrator/mcp/agent-connectors.mjs';
20
+ import { recordHumanDecision } from '../../runtime/agent/orchestrator/session/human-gate/gate.mjs';
21
+ import { CONNECTOR_WIDEN_LEVELS } from '../../runtime/agent/orchestrator/session/human-gate/requests.mjs';
22
+ import { runAbortable } from '../../runtime/shared/abort-race.mjs';
23
+
24
+ const NO_WIDEN = Object.freeze({ noWiden: true });
25
+
26
+ /** The tool's effective level for `agent` now, as dispatch reads it. */
27
+ function currentLevel(agent, server, tool) {
28
+ return connectorToolPermission(agentConnectors(agent), {
29
+ name: `mcp__${server}__${tool}`,
30
+ mcpServerName: server,
31
+ mcpToolName: tool,
32
+ });
33
+ }
34
+
35
+ /** Ask the person on a card, then apply the level they picked.
36
+ * `requestApproval` is the calling turn's approval hook. */
37
+ export async function askToWidenConnectorTool(
38
+ rt,
39
+ { agent, server, tool, requested },
40
+ { requestApproval, signal, sessionId, toolCallId } = {}
41
+ ) {
42
+ const from = currentLevel(agent, server, tool);
43
+ const receipt = { agent, connector: server, tool, requested };
44
+ if (PERMISSION_RANK[from] >= PERMISSION_RANK[requested]) {
45
+ return { ...receipt, saved: false, permission: from, note: `The tool is already ${from}; nothing changed.` };
46
+ }
47
+ // Nobody can be shown a card here: the person makes it in the desktop app.
48
+ if (typeof requestApproval !== 'function') throw new Error(NO_WIDEN_MESSAGE);
49
+ const choices = CONNECTOR_WIDEN_LEVELS.filter((level) => PERMISSION_RANK[level] > PERMISSION_RANK[from]);
50
+ const answer = await runAbortable(signal, () =>
51
+ requestApproval({
52
+ name: 'setup',
53
+ kind: 'connector',
54
+ agentId: agent,
55
+ // The card names the connector, not one of its accounts.
56
+ connector: { server: connectorGroupId(server), tool, from, requested, choices },
57
+ args: { action: 'set_agent_connector_tool', agent, name: server, tool, permission: requested },
58
+ reason: `Bot "${agent}" asks to change the permission of ${tool} (${connectorGroupId(server)}) from ${from} to ${requested}.`,
59
+ remember: ['once'],
60
+ sessionId,
61
+ toolCallId,
62
+ })
63
+ );
64
+ const level = answer?.approved === true && choices.includes(answer.scope) ? answer.scope : '';
65
+ // Audited with the answering device; no grant is ever stored (no action kind).
66
+ await recordHumanDecision(
67
+ { agentId: agent, kind: 'connector', tool: `${server}/${tool}`, target: `${from}>${level || requested}` },
68
+ answer
69
+ );
70
+ // A decision that settles after its turn was cancelled authorizes nothing.
71
+ signal?.throwIfAborted();
72
+ if (!level) {
73
+ const why = answer?.timedOut ? 'The approval card timed out' : 'The user did not approve this change';
74
+ return { ...receipt, saved: false, approved: false, permission: from, note: `${why}; nothing changed.` };
75
+ }
76
+ try {
77
+ await withApprovedConnectorToolChange({ server, tool, from, to: level }, () =>
78
+ rt.setAgentConnectorToolPermission(agent, server, tool, level, NO_WIDEN)
79
+ );
80
+ } catch (error) {
81
+ if (error?.code !== CONNECTOR_TOOL_NARROWED && error?.code !== CONNECTOR_TOOL_ALREADY) throw error;
82
+ return {
83
+ ...receipt,
84
+ saved: false,
85
+ approved: true,
86
+ permission: currentLevel(agent, server, tool),
87
+ note: `The user approved ${level}, but it was not applied: ${error.message}`,
88
+ };
89
+ }
90
+ return {
91
+ ...receipt,
92
+ approved: true,
93
+ permission: level,
94
+ note: `The user approved on the card; ${tool} is now ${level}.`,
95
+ };
96
+ }
@@ -364,7 +364,7 @@ async function featureStatus(rt, getConfig) {
364
364
  computer: { active: builtinFeatureActive(config, 'computer') },
365
365
  terminal: { active: builtinFeatureActive(config, 'terminal') },
366
366
  heartbeat: {
367
- enabled: config?.heartbeat !== false,
367
+ enabled: config?.heartbeat === true,
368
368
  intervalMinutes: normalizeHeartbeatIntervalMinutes(config?.heartbeatIntervalMinutes),
369
369
  },
370
370
  reactions: { enabled: config?.reactions !== false },
@@ -909,7 +909,7 @@ export function createSetupToolExecutor({
909
909
  };
910
910
  }
911
911
 
912
- async function execute(args, { signal, callerSessionId }) {
912
+ async function execute(args, { signal, callerSessionId, requestApproval = null, toolCallId = null }) {
913
913
  signal?.throwIfAborted();
914
914
  const requestDesktop = (request) => desktop.request(request, { signal });
915
915
  const action = validateSetupInput(args);
@@ -948,6 +948,10 @@ export function createSetupToolExecutor({
948
948
  getSession: getCallerSession,
949
949
  getConfig: () => getConfig?.() || {},
950
950
  executeSessionControl,
951
+ // The calling turn's approval card (a connector tool's widening asks the person).
952
+ requestApproval,
953
+ signal,
954
+ toolCallId,
951
955
  });
952
956
  }
953
957
 
@@ -477,7 +477,8 @@ export const SETUP_TOOL_DEFS = Object.freeze([
477
477
  permission: {
478
478
  type: 'string',
479
479
  enum: [...CONNECTOR_PERMISSIONS],
480
- description: 'set_agent_connector_tool: needs_approval or disabled; auto is made in the desktop app.',
480
+ description:
481
+ 'set_agent_connector_tool: a narrowing (toward disabled) applies at once; a widening (disabled → needs_approval/auto, needs_approval → auto) waits on an approval card the user answers (needs_approval, auto or deny, whatever was asked) and the result says what was applied.',
481
482
  },
482
483
  botOrder: {
483
484
  type: 'array',
@@ -135,6 +135,7 @@ export function createPolicyStage({
135
135
  : _buildBaseRules({
136
136
  omitTools: [...denied, unusedModelEditToolName(getRoute()?.model)],
137
137
  allowTools: schemaAllowedTools,
138
+ ownerIsAgent,
138
139
  provider: getRoute()?.provider,
139
140
  model: getRoute()?.model,
140
141
  });