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
@@ -42,6 +42,140 @@ function callerAttachmentParts(rt, args, callerCwd, surface) {
42
42
  }).parts;
43
43
  }
44
44
 
45
+ /** `session` actions that act on another session through session control
46
+ * (list, bots, read, send, cancel, delete, create, status of a target). */
47
+ async function runSessionControl(
48
+ { rt, executeSessionControl, listBots, getSessionManager },
49
+ args,
50
+ { action, target, callerSessionId, callerCwd }
51
+ ) {
52
+ // `callerAgentId`: the bot whose conversation gets results reported
53
+ // back; `callerBotId`: the bot the caller acts as, attended or not,
54
+ // for what only a bot's own sessions allow (an effort change).
55
+ const callerAgentId = attendedAgentOf(rt.session);
56
+ const acting = resolveSessionMode({ session: rt.session });
57
+ const callerBotId = acting.mode === AGENT_MODE ? acting.agentId : '';
58
+ if (action === 'create' && !callerAgentId) {
59
+ throw new Error(
60
+ 'session: create belongs to a bot conversation; delegated workers, agent task sessions and schedule/webhook runs cannot create agent task sessions'
61
+ );
62
+ }
63
+ // A bot id names that bot's home chat; every check below and in the
64
+ // control runs on the chat it resolves to.
65
+ const sessionTarget = BOT_TARGET_ACTIONS.has(action) && target ? await resolveBotTarget(target, listBots) : target;
66
+ // A task session reaches its agent only through its reported result.
67
+ const origin = rt.session?.taskOrigin;
68
+ if (
69
+ action === 'send' &&
70
+ origin &&
71
+ (sessionTarget === origin.sessionId || agentIdOfHomeSession(sessionTarget) === origin.agentId)
72
+ ) {
73
+ throw new Error('session: a task session reports to its agent only through its result');
74
+ }
75
+ if (typeof executeSessionControl !== 'function') {
76
+ throw new Error('session: cross-session control is unavailable in this runtime');
77
+ }
78
+ const { attachments, ...rest } = args || {};
79
+ if (attachments !== undefined && action !== 'send' && action !== 'create') {
80
+ throw new Error(`session: attachments apply to send and create, not action=${action}`);
81
+ }
82
+ const attachmentParts = callerAttachmentParts(rt, args, callerCwd, 'session');
83
+ return JSON.stringify(
84
+ await executeSessionControl(
85
+ { ...rest, action, ...(sessionTarget !== target ? { session_id: sessionTarget } : {}) },
86
+ {
87
+ callerSessionId,
88
+ callerAgentId,
89
+ callerBotId,
90
+ callerCwd,
91
+ ...(action === 'send' || action === 'create'
92
+ ? {
93
+ callerHop: turnHopOf(rt.session),
94
+ // The bot chain this turn acts in (bot-chain.mjs), checked
95
+ // against the target and carried to the turn it starts —
96
+ // only when the sender is a bot conversation (a bot's chat,
97
+ // room seat or run); any other session hands work to any
98
+ // bot as before, whatever started its turn (the hop limit
99
+ // still bounds a loop).
100
+ callerBotChain: callerBotId
101
+ ? delegationChainOf(callerSessionId, (id) =>
102
+ id === rt.session?.id ? rt.session : getSessionManager()?.getSession?.(id) || null
103
+ )
104
+ : [],
105
+ }
106
+ : {}),
107
+ // Whether a create may provision a worktree (worktree-access.mjs).
108
+ ...(action === 'create' ? { callerScope: worktreeCallerScope(rt.session) } : {}),
109
+ // Which bots and sessions it may see (bot-status.mjs).
110
+ ...(action === 'bots' || action === 'list' ? { callerScope: scheduleCallerScope(rt.session) } : {}),
111
+ ...(attachmentParts.length ? { attachmentParts } : {}),
112
+ }
113
+ )
114
+ );
115
+ }
116
+
117
+ /** `session action=rename`: the AI title of the Lead session, mirrored onto the live session. */
118
+ async function renameSession({ rt, getSessionManager }, action, sessionId, args) {
119
+ if (rt.config?.aiSessionTitles === false) throw new Error('session: AI session titles are disabled in settings');
120
+ const title = clean(args?.title);
121
+ if (!title) throw new Error('session: title is required for action=rename');
122
+ const result = await getSessionManager()?.updateSessionAiTitle(sessionId, title);
123
+ if (!result?.ok) {
124
+ throw new Error(
125
+ result?.reason === 'user-title' ? USER_TITLE_ERROR : `session: rename failed (${result?.reason || 'unavailable'})`
126
+ );
127
+ }
128
+ // Mirror the store's truth onto the live session, never the AI title
129
+ // itself: a manual rename that landed while the save was awaited wins.
130
+ const stored = getSessionManager()?.getSession?.(sessionId);
131
+ if (stored && rt.session?.id === sessionId && stored !== rt.session) {
132
+ rt.session.title = stored.title;
133
+ rt.session.titleLocked = stored.titleLocked;
134
+ rt.session.titleSource = stored.titleSource;
135
+ rt.session.titleUpdatedAt = stored.titleUpdatedAt;
136
+ }
137
+ if (stored && stored.titleSource === 'user') throw new Error(USER_TITLE_ERROR);
138
+ return JSON.stringify({ action, sessionId, title: result.title }, null, 2);
139
+ }
140
+
141
+ /** `session action=fork`: inherit the Lead session into a new one. */
142
+ async function forkSession(getLifecycleApi, action, sessionId, args) {
143
+ const lifecycle = getLifecycleApi();
144
+ if (typeof lifecycle?.inheritToNewSession !== 'function') {
145
+ throw new Error('session: fork is unavailable in this runtime');
146
+ }
147
+ const result = await lifecycle.inheritToNewSession(sessionId, {
148
+ compact: args?.compact === true,
149
+ titleSuffix: ' (Fork)',
150
+ });
151
+ return JSON.stringify(
152
+ { action, sourceSessionId: sessionId, sessionId: result.sessionId, compacted: result.compacted === true },
153
+ null,
154
+ 2
155
+ );
156
+ }
157
+
158
+ /** `session action=status`: the current session's identity, folder and route. */
159
+ function sessionStatusJson(rt, callerCtx, currentCwd) {
160
+ const current = rt.session;
161
+ return JSON.stringify(
162
+ {
163
+ sessionId: callerCtx?.callerSessionId || current?.id || null,
164
+ title: current?.title || null,
165
+ titleSource: current?.titleSource || null,
166
+ cwd: currentCwd,
167
+ project: current?.workspace?.projectPath || current?.desktopSession?.projectPath || currentCwd,
168
+ provider: current?.provider || null,
169
+ model: current?.model || null,
170
+ effort: current?.effort || null,
171
+ autoEffort: current?.autoEffort !== false,
172
+ fast: current?.fast === true,
173
+ },
174
+ null,
175
+ 2
176
+ );
177
+ }
178
+
45
179
  export function createWorkspaceToolHandlers({
46
180
  rt,
47
181
  goalRuntime,
@@ -101,70 +235,12 @@ export function createWorkspaceToolHandlers({
101
235
  (action === 'status' && target)
102
236
  ) {
103
237
  const callerSessionId = assertLead(action, callerCtx);
104
- // `callerAgentId`: the bot whose conversation gets results reported
105
- // back; `callerBotId`: the bot the caller acts as, attended or not,
106
- // for what only a bot's own sessions allow (an effort change).
107
- const callerAgentId = attendedAgentOf(rt.session);
108
- const acting = resolveSessionMode({ session: rt.session });
109
- const callerBotId = acting.mode === AGENT_MODE ? acting.agentId : '';
110
- if (action === 'create' && !callerAgentId) {
111
- throw new Error(
112
- 'session: create belongs to a bot conversation; delegated workers, agent task sessions and schedule/webhook runs cannot create agent task sessions'
113
- );
114
- }
115
- // A bot id names that bot's home chat; every check below and in the
116
- // control runs on the chat it resolves to.
117
- const sessionTarget =
118
- BOT_TARGET_ACTIONS.has(action) && target ? await resolveBotTarget(target, listBots) : target;
119
- // A task session reaches its agent only through its reported result.
120
- const origin = rt.session?.taskOrigin;
121
- if (
122
- action === 'send' &&
123
- origin &&
124
- (sessionTarget === origin.sessionId || agentIdOfHomeSession(sessionTarget) === origin.agentId)
125
- ) {
126
- throw new Error('session: a task session reports to its agent only through its result');
127
- }
128
- if (typeof executeSessionControl !== 'function') {
129
- throw new Error('session: cross-session control is unavailable in this runtime');
130
- }
131
- const { attachments, ...rest } = args || {};
132
- if (attachments !== undefined && action !== 'send' && action !== 'create') {
133
- throw new Error(`session: attachments apply to send and create, not action=${action}`);
134
- }
135
- const attachmentParts = callerAttachmentParts(rt, args, callerCwd, 'session');
136
- return JSON.stringify(
137
- await executeSessionControl(
138
- { ...rest, action, ...(sessionTarget !== target ? { session_id: sessionTarget } : {}) },
139
- {
140
- callerSessionId,
141
- callerAgentId,
142
- callerBotId,
143
- callerCwd,
144
- ...(action === 'send' || action === 'create'
145
- ? {
146
- callerHop: turnHopOf(rt.session),
147
- // The bot chain this turn acts in (bot-chain.mjs), checked
148
- // against the target and carried to the turn it starts —
149
- // only when the sender is a bot conversation (a bot's chat,
150
- // room seat or run); any other session hands work to any
151
- // bot as before, whatever started its turn (the hop limit
152
- // still bounds a loop).
153
- callerBotChain: callerBotId
154
- ? delegationChainOf(callerSessionId, (id) =>
155
- id === rt.session?.id ? rt.session : getSessionManager()?.getSession?.(id) || null
156
- )
157
- : [],
158
- }
159
- : {}),
160
- // Whether a create may provision a worktree (worktree-access.mjs).
161
- ...(action === 'create' ? { callerScope: worktreeCallerScope(rt.session) } : {}),
162
- // Which bots and sessions it may see (bot-status.mjs).
163
- ...(action === 'bots' || action === 'list' ? { callerScope: scheduleCallerScope(rt.session) } : {}),
164
- ...(attachmentParts.length ? { attachmentParts } : {}),
165
- }
166
- )
167
- );
238
+ return runSessionControl({ rt, executeSessionControl, listBots, getSessionManager }, args, {
239
+ action,
240
+ target,
241
+ callerSessionId,
242
+ callerCwd,
243
+ });
168
244
  }
169
245
  // Mixy's proposals to the user (mixy-heartbeat.mjs): kept so none repeats.
170
246
  if (action === 'suggest') {
@@ -184,28 +260,7 @@ export function createWorkspaceToolHandlers({
184
260
  if (action === 'status' || action === 'list-projects' || action === 'set-project') assertLead(action, callerCtx);
185
261
  if (action === 'rename') {
186
262
  const sessionId = assertLead(action, callerCtx);
187
- if (rt.config?.aiSessionTitles === false) throw new Error('session: AI session titles are disabled in settings');
188
- const title = clean(args?.title);
189
- if (!title) throw new Error('session: title is required for action=rename');
190
- const result = await getSessionManager()?.updateSessionAiTitle(sessionId, title);
191
- if (!result?.ok) {
192
- throw new Error(
193
- result?.reason === 'user-title'
194
- ? USER_TITLE_ERROR
195
- : `session: rename failed (${result?.reason || 'unavailable'})`
196
- );
197
- }
198
- // Mirror the store's truth onto the live session, never the AI title
199
- // itself: a manual rename that landed while the save was awaited wins.
200
- const stored = getSessionManager()?.getSession?.(sessionId);
201
- if (stored && rt.session?.id === sessionId && stored !== rt.session) {
202
- rt.session.title = stored.title;
203
- rt.session.titleLocked = stored.titleLocked;
204
- rt.session.titleSource = stored.titleSource;
205
- rt.session.titleUpdatedAt = stored.titleUpdatedAt;
206
- }
207
- if (stored && stored.titleSource === 'user') throw new Error(USER_TITLE_ERROR);
208
- return JSON.stringify({ action, sessionId, title: result.title }, null, 2);
263
+ return renameSession({ rt, getSessionManager }, action, sessionId, args);
209
264
  }
210
265
  if (action === 'compact') {
211
266
  const sessionId = assertLead(action, callerCtx);
@@ -219,38 +274,10 @@ export function createWorkspaceToolHandlers({
219
274
  }
220
275
  if (action === 'fork') {
221
276
  const sessionId = assertLead(action, callerCtx);
222
- const lifecycle = getLifecycleApi();
223
- if (typeof lifecycle?.inheritToNewSession !== 'function') {
224
- throw new Error('session: fork is unavailable in this runtime');
225
- }
226
- const result = await lifecycle.inheritToNewSession(sessionId, {
227
- compact: args?.compact === true,
228
- titleSuffix: ' (Fork)',
229
- });
230
- return JSON.stringify(
231
- { action, sourceSessionId: sessionId, sessionId: result.sessionId, compacted: result.compacted === true },
232
- null,
233
- 2
234
- );
277
+ return forkSession(getLifecycleApi, action, sessionId, args);
235
278
  }
236
279
  if (action === 'status') {
237
- const current = rt.session;
238
- return JSON.stringify(
239
- {
240
- sessionId: callerCtx?.callerSessionId || current?.id || null,
241
- title: current?.title || null,
242
- titleSource: current?.titleSource || null,
243
- cwd: currentCwd,
244
- project: current?.workspace?.projectPath || current?.desktopSession?.projectPath || currentCwd,
245
- provider: current?.provider || null,
246
- model: current?.model || null,
247
- effort: current?.effort || null,
248
- autoEffort: current?.autoEffort !== false,
249
- fast: current?.fast === true,
250
- },
251
- null,
252
- 2
253
- );
280
+ return sessionStatusJson(rt, callerCtx, currentCwd);
254
281
  }
255
282
  if (action === 'list-projects') {
256
283
  return JSON.stringify(
@@ -4,6 +4,7 @@
4
4
  * tombstone barrier and the turn abort.
5
5
  */
6
6
  import { SessionClosedError } from '../../../runtime/agent/orchestrator/session/manager/session-errors.mjs';
7
+ import { armTurnTimeout } from '../../../runtime/agent/orchestrator/session/manager/turn-reap.mjs';
7
8
  import { isScratchSession } from '../shared.mjs';
8
9
 
9
10
  // SessionEnd reason mapped to standard values ('clear'/'exit' where
@@ -88,11 +89,15 @@ export function closeCanonicalSession(deps, reason = 'canonical-session-close',
88
89
  return true;
89
90
  }
90
91
 
91
- export function abortRuntime(deps, reason = 'cli-abort') {
92
+ /** `options.timeout` is the progress watchdog's explicit "this abort is a
93
+ * timeout": the reap is armed before either abort runs, while the turn is
94
+ * still un-aborted (the outer abort cascades into the manager's controller). */
95
+ export function abortRuntime(deps, reason = 'cli-abort', { timeout = false } = {}) {
92
96
  const { getSession, getReservedSessionId, abortActiveTurns, mgr } = deps;
93
97
  const session = getSession();
94
98
  const sessionId = session?.id || getReservedSessionId?.() || 'pending';
95
99
  const abortError = new SessionClosedError(sessionId, `runtime abort (reason=${reason})`, reason);
100
+ if (timeout && session?.id) armTurnTimeout(session.id);
96
101
  let outerAborted = false;
97
102
  try {
98
103
  outerAborted = abortActiveTurns?.(abortError) === true;
@@ -281,6 +281,6 @@ export function createTeardown(deps, { ingestSessionIntoMemory, closeSurfaceSess
281
281
  return {
282
282
  closeCanonicalSession: (reason, options) => closeCanonicalSession(deps, reason, options),
283
283
  close,
284
- abort: (reason) => abortRuntime(deps, reason),
284
+ abort: (reason, options) => abortRuntime(deps, reason, options),
285
285
  };
286
286
  }
@@ -9,7 +9,7 @@ import {
9
9
  connectorOfServer,
10
10
  gmailAppPasswordAccount,
11
11
  } from '../../runtime/shared/connector-catalog.mjs';
12
- import { connectorAccountHealth } from '../../runtime/connectors/connector-accounts-mcp.mjs';
12
+ import { connectorAccountHealth, connectorAccountInUse } from '../../runtime/connectors/connector-accounts-mcp.mjs';
13
13
  import { googleOAuthClient } from '../../runtime/shared/google-oauth-client.mjs';
14
14
  import { envFlag } from '../../runtime/shared/env.mjs';
15
15
  import { resolveMcpServerCatalog } from './plugin-servers.mjs';
@@ -119,10 +119,12 @@ export function createMcpServerCatalog({
119
119
  };
120
120
  }
121
121
 
122
- /** One account of a multi-account connector server, as its row lists it:
123
- * `{ name, email, enabled?, appPassword?, oauth?, ownClient?, error?, authRequired? }`
124
- * (`enabled: false` only for an account that is switched off). */
125
- function accountRow(accountCfg, enabled) {
122
+ /** One account of a connector server, as its row lists it:
123
+ * `{ name, email, enabled?, inUse?, appPassword?, oauth?, ownClient?, error?, authRequired? }`
124
+ * (`enabled: false` only for an account that is switched off; `inUse:
125
+ * true` only for the connector's last-used account, the one a call
126
+ * without a conversation history uses). */
127
+ function accountRow(accountCfg, enabled, inUse) {
126
128
  const address = gmailAppPasswordAccount(accountCfg);
127
129
  const oauth = address ? null : mcpClient.getMcpServerOAuthState?.(accountCfg);
128
130
  const state = connectorAccountHealth(accountCfg.name);
@@ -130,6 +132,7 @@ export function createMcpServerCatalog({
130
132
  return {
131
133
  name: accountCfg.name,
132
134
  ...(enabled ? {} : { enabled: false }),
135
+ ...(inUse ? { inUse: true } : {}),
133
136
  email: address || oauth?.account || state?.email || '',
134
137
  ...(address ? { appPassword: { account: address } } : {}),
135
138
  ...(oauth ? { oauth } : {}),
@@ -152,11 +155,14 @@ export function createMcpServerCatalog({
152
155
  failures.get(name) || (dead ? { msg: live.error || live.lastError || 'MCP server is not alive' } : undefined);
153
156
  const entry = entries[name] || {};
154
157
  const loginPending = !(live && !dead) && mcpClient.isMcpLoginPending?.(name, getMcpScopeId()) === true;
155
- const accounts = entry.owner
156
- ? []
157
- : connectorAccountsOf(cfg).map((account) =>
158
- accountRow(connectorAccountServer(cfg, account), connectorAccountEnabled(account))
159
- );
158
+ const stored = entry.owner ? [] : connectorAccountsOf(cfg);
159
+ // The connector's last-used account (none when that one was removed),
160
+ // else its topmost switched-on one.
161
+ const recorded = stored.length ? connectorAccountInUse(connectorOfServer(cfg)) : '';
162
+ const inUse = recorded || stored.find(connectorAccountEnabled)?.name;
163
+ const accounts = stored.map((account) =>
164
+ accountRow(connectorAccountServer(cfg, account), connectorAccountEnabled(account), account.name === inUse)
165
+ );
160
166
  // A connector server none of whose accounts is signed in needs a sign-in, not a repair.
161
167
  const accountsSignIn = accounts.length > 0 && accounts.every((account) => account.authRequired === true);
162
168
  const status = mcpServerStatus(
@@ -79,8 +79,11 @@ export function createMcpServerInput({ mcpClient, getCurrentCwd }) {
79
79
  const type = clean(input.type).toLowerCase();
80
80
  if (url) {
81
81
  const kind = URL_TRANSPORT_TYPES.get(type) || (/^wss?:\/\//i.test(url) ? 'ws' : 'http');
82
- const secureUrl =
82
+ const normalizedUrl =
83
83
  typeof mcpClient.normalizeMcpTransportUrl === 'function' ? mcpClient.normalizeMcpTransportUrl(url, kind) : url;
84
+ // URL parsing adds a "/" to a bare origin (`https://mcp.stripe.com`):
85
+ // keep the address as it was given, as the catalog and legacy entries do.
86
+ const secureUrl = normalizedUrl === `${url}/` ? url : normalizedUrl;
84
87
  return { name, config: withOptionalHeaders({ type: kind, url: secureUrl }) };
85
88
  }
86
89
  const command = clean(input.command);
@@ -635,7 +635,7 @@ export function createMixyHeartbeat({
635
635
  now = Date.now,
636
636
  path = defaultStatePath(),
637
637
  isEnabled = async () =>
638
- (await import('../runtime/agent/orchestrator/config.mjs')).loadConfig({ secrets: false }).heartbeat !== false,
638
+ (await import('../runtime/agent/orchestrator/config.mjs')).loadConfig({ secrets: false }).heartbeat === true,
639
639
  // Whether the representative bot exists (mixy-presence.mjs, the rule the
640
640
  // desktop and her routines follow): without it no check runs at all.
641
641
  mixyExists = async () => (await import('./routine-owner.mjs')).readMixyExists(),
@@ -61,8 +61,8 @@ export function createLoginApi({ cfgMod, awaitKeychainPrewarm, reloadFullConfig,
61
61
  reloadFullConfig();
62
62
  return completeOAuthLogin(result);
63
63
  },
64
- async saveProviderApiKey(providerId, secret) {
65
- const result = await saveProviderApiKey(cfgMod, providerId, secret);
64
+ async saveProviderApiKey(providerId, secret, options) {
65
+ const result = await saveProviderApiKey(cfgMod, providerId, secret, options);
66
66
  refresh.afterCredentialChange();
67
67
  return result;
68
68
  },
@@ -14,9 +14,11 @@ import { migrateMergedConnectorServers } from './connector-merge-migration.mjs';
14
14
  import {
15
15
  connectorAccountServers,
16
16
  connectorAccountsOf,
17
+ connectorAppPasswordOnly,
17
18
  connectorById,
18
- connectorMultiAccount,
19
+ connectorServerInput,
19
20
  gmailAppPasswordAccount,
21
+ gmailAppPasswordServerInput,
20
22
  mergeConnectorAccountServers,
21
23
  } from '../runtime/shared/connector-catalog.mjs';
22
24
  import { saveMcpOAuthClientSecret } from '../runtime/agent/orchestrator/mcp/oauth-client.mjs';
@@ -75,14 +77,24 @@ export function createMcpResourceApi({ deps, sync, decorate }) {
75
77
  resolveMcpCatalog,
76
78
  pluginMcpServer,
77
79
  });
78
- const { storeOAuthClientSecret, storeGmailAppPassword, releaseOAuth, forgetUnusedGmailAppPassword, releaseAccount } =
79
- createMcpServerSecrets({
80
- gmailAppPassword,
81
- saveOAuthClientSecret,
82
- clearMcpServerOAuth,
83
- resolveConfiguredMcpCatalog,
84
- });
85
- const { addConnectorAccount, saveConnectorAccount, setConnectorAccountEnabled } = createConnectorAccountEdits({
80
+ const {
81
+ storeOAuthClientSecret,
82
+ storeGmailAppPassword,
83
+ releaseOAuth,
84
+ forgetUnusedGmailAppPassword,
85
+ forgetStoredSecrets,
86
+ releaseAccount,
87
+ } = createMcpServerSecrets({
88
+ gmailAppPassword,
89
+ saveOAuthClientSecret,
90
+ clearMcpServerOAuth,
91
+ resolveConfiguredMcpCatalog,
92
+ });
93
+ const {
94
+ addConnectorAccount: joinConnectorAccount,
95
+ saveConnectorAccount,
96
+ setConnectorAccountEnabled,
97
+ } = createConnectorAccountEdits({
86
98
  normalizeMcpServerInput,
87
99
  pluginMcpServer,
88
100
  sync,
@@ -91,16 +103,65 @@ export function createMcpResourceApi({ deps, sync, decorate }) {
91
103
  storeOAuthClientSecret,
92
104
  storeGmailAppPassword,
93
105
  forgetUnusedGmailAppPassword,
106
+ forgetStoredSecrets,
94
107
  releaseOAuth,
95
108
  });
96
109
  // Peers reload config from disk on receipt: land the write before publishing.
97
110
  const persisted = async () => {
98
111
  await flushSkillsSave?.();
99
112
  };
113
+ // One account addition of a connector at a time, each reading the servers
114
+ // the one before it saved: two at once never take the same name, nor does
115
+ // one's save drop the other's account.
116
+ const connectorAdds = new Map();
117
+ const oneAddAtATime = (connector, run) => {
118
+ const next = (connectorAdds.get(connector) || Promise.resolve()).then(run, run);
119
+ const settled = next.then(
120
+ () => {},
121
+ () => {}
122
+ );
123
+ connectorAdds.set(connector, settled);
124
+ void settled.then(() => {
125
+ if (connectorAdds.get(connector) === settled) connectorAdds.delete(connector);
126
+ });
127
+ return next;
128
+ };
129
+
130
+ async function addMcpServer(input = {}) {
131
+ // A catalog connector's account (as its own server config) joins that
132
+ // connector's one server.
133
+ const connector = clean(input.connector);
134
+ if (connector && !Array.isArray(input.accounts)) return oneAddAtATime(connector, () => addServerNow(input));
135
+ return addServerNow(input);
136
+ }
137
+
138
+ async function addServerNow(input) {
139
+ const { name, config: serverConfig } = normalizeMcpServerInput(input);
140
+ // A catalog connector's server keeps its catalog id (connector-catalog.mjs).
141
+ const connector = clean(input.connector);
142
+ if (connector) {
143
+ if (!connectorById(connector)) throw new Error(`Unknown connector: ${connector}`);
144
+ serverConfig.connector = connector;
145
+ }
146
+ const servers = userServers();
147
+ if (connector && !Array.isArray(input.accounts)) {
148
+ return joinConnectorAccount(servers, connector, name, serverConfig, input);
149
+ }
150
+ if (Object.hasOwn(servers, name) || accountTarget(servers, name)) {
151
+ throw nameTakenError(servers, name);
152
+ }
153
+ const owned = pluginMcpServer(name);
154
+ if (owned) throw new Error(`MCP server already exists: ${name} (provided by plugin "${owned.owner.id}")`);
155
+ await storeOAuthClientSecret(input, serverConfig);
156
+ await storeGmailAppPassword(input, serverConfig, true);
157
+ saveServers({ ...servers, [name]: serverConfig });
158
+ const status = await sync.reconnectMcpAndAnnounce({ only: name, enabled: serverConfig.enabled !== false });
159
+ return { name, status };
160
+ }
100
161
 
101
162
  return {
102
- /** One-time, idempotent: merge the one-account servers of a multi-account
103
- * connector (`gmail`, `gmail-2`…) into one server holding the accounts
163
+ /** One-time, idempotent: merge the one-account servers of a catalog
164
+ * connector (`notion`, `notion-2`…) into one server holding the accounts
104
165
  * (connector-catalog.mjs mergeConnectorAccountServers). Every account
105
166
  * keeps its address, so its stored sign-in and app password stay as
106
167
  * they are. Settings keyed by a merged-away server name go with it:
@@ -155,30 +216,32 @@ export function createMcpResourceApi({ deps, sync, decorate }) {
155
216
  },
156
217
  // Sign-in / sign-out (resource-mcp-oauth-api.mjs), in this key position.
157
218
  ...oauthApi,
158
- async addMcpServer(input = {}) {
159
- const { name, config: serverConfig } = normalizeMcpServerInput(input);
160
- // A catalog connector's server keeps its catalog id (connector-catalog.mjs).
161
- const connector = clean(input.connector);
162
- if (connector) {
163
- if (!connectorById(connector)) throw new Error(`Unknown connector: ${connector}`);
164
- serverConfig.connector = connector;
165
- }
166
- const servers = userServers();
167
- // A multi-account connector's account (as its own server config) joins
168
- // that connector's one server.
169
- if (connector && connectorMultiAccount(connectorById(connector)) && !Array.isArray(input.accounts)) {
170
- return addConnectorAccount(servers, connector, name, serverConfig, input);
171
- }
172
- if (Object.hasOwn(servers, name) || accountTarget(servers, name)) {
173
- throw nameTakenError(servers, name);
174
- }
175
- const owned = pluginMcpServer(name);
176
- if (owned) throw new Error(`MCP server already exists: ${name} (provided by plugin "${owned.owner.id}")`);
177
- await storeOAuthClientSecret(input, serverConfig);
178
- await storeGmailAppPassword(input, serverConfig, true);
179
- saveServers({ ...servers, [name]: serverConfig });
180
- const status = await sync.reconnectMcpAndAnnounce({ only: name, enabled: serverConfig.enabled !== false });
181
- return { name, status };
219
+ addMcpServer,
220
+ /** One more account of catalog connector `input.connector`, connected as
221
+ * its catalog entry says: `key` for an API-key service (Hugging Face,
222
+ * Stripe; Postgres takes its connection string), `email` and
223
+ * `appPassword` for Gmail, nothing more for a sign-in service (the
224
+ * account then signs in with beginMcpOAuthLogin(account)); the user's own
225
+ * OAuth client rides on `oauthClientId` / `oauthClientSecret`. The account
226
+ * gets a free name (`notion`, `notion-2`…) and joins the connector's one
227
+ * server, which is made when there is none. Resolves `{ name, account,
228
+ * status }`: the server's name and the new account's (for a Gmail
229
+ * address already connected, that account, which took the new password). */
230
+ async addConnectorAccount(input = {}) {
231
+ const entry = connectorById(clean(input.connector));
232
+ if (!entry) throw new Error(`Unknown connector: ${clean(input.connector)}`);
233
+ // Its name is picked from the servers the additions before it saved.
234
+ return oneAddAtATime(entry.id, () => {
235
+ const name = freeName(userServers(), entry.id);
236
+ const account = connectorAppPasswordOnly(entry)
237
+ ? gmailAppPasswordServerInput(name, input.email, input.appPassword)
238
+ : connectorServerInput(entry, name, input.key);
239
+ const ownClient = clean(input.oauthClientId);
240
+ return addServerNow({
241
+ ...account,
242
+ ...(ownClient ? { oauthClientId: ownClient, oauthClientSecret: input.oauthClientSecret } : {}),
243
+ });
244
+ });
182
245
  },
183
246
  async saveMcpServer(input = {}) {
184
247
  const servers = userServers();