mixdog 1.2.5 → 1.3.0

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/LICENSES/simple-icons-CC0-1.0.txt +30 -0
  2. package/NOTICE.md +19 -0
  3. package/package.json +4 -1
  4. package/scripts/check-model-recommendations.mjs +89 -0
  5. package/scripts/gate-local.mjs +7 -0
  6. package/scripts/lib/gate-scheduler.mjs +2 -2
  7. package/scripts/lib/isolated-test-env.mjs +16 -0
  8. package/scripts/lib/run-node-tests.mjs +5 -14
  9. package/scripts/provider-pingpong-live.mjs +9 -9
  10. package/src/agents/advisor/AGENT.md +4 -4
  11. package/src/agents/advisor/agent.json +1 -1
  12. package/src/agents/reviewer/AGENT.md +5 -4
  13. package/src/agents/reviewer/agent.json +1 -1
  14. package/src/defaults/shipped-history.json +17 -0
  15. package/src/defaults/skills/browser-use/SKILL.md +7 -1
  16. package/src/defaults/skills/computer-use/SKILL.md +3 -1
  17. package/src/defaults/skills/memory-management/SKILL.md +4 -1
  18. package/src/defaults/skills/setup/references/actions.md +11 -4
  19. package/src/defaults/skills/setup-guide/SKILL.md +175 -0
  20. package/src/defaults/skills/setup-guide/model-recommendations.json +333 -0
  21. package/src/defaults/skills/visualize/SKILL.md +3 -2
  22. package/src/lib/rules-builder.cjs +56 -7
  23. package/src/output-styles/common.md +22 -6
  24. package/src/output-styles/detailed.md +7 -5
  25. package/src/output-styles/minimal.md +7 -3
  26. package/src/output-styles/simple.md +2 -3
  27. package/src/rules/agent/AGENT.md +7 -1
  28. package/src/rules/agent-mode/AGENT_MODE.md +24 -0
  29. package/src/rules/agent-mode/MIXY.md +21 -0
  30. package/src/rules/agent-mode/ROUTINE.md +6 -0
  31. package/src/rules/lead/LEAD.md +9 -4
  32. package/src/rules/shared/10-tool-workflow.md +2 -1
  33. package/src/rules/shared/60-verification.md +4 -5
  34. package/src/rules/shared/70-delivery.md +3 -0
  35. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -1
  36. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +86 -0
  37. package/src/runtime/agent/orchestrator/config.mjs +6 -0
  38. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +77 -14
  39. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +182 -13
  40. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +5 -3
  41. package/src/runtime/agent/orchestrator/internal-agents.mjs +10 -0
  42. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +215 -0
  43. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +58 -64
  44. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +10 -1
  45. package/src/runtime/agent/orchestrator/mcp/client.mjs +122 -15
  46. package/src/runtime/agent/orchestrator/mcp/oauth-account.mjs +97 -0
  47. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +2 -3
  48. package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +49 -0
  49. package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +59 -9
  50. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +32 -7
  51. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +45 -6
  52. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +195 -6
  53. package/src/runtime/agent/orchestrator/mcp/server-connectors.mjs +17 -0
  54. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +9 -2
  55. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  56. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +41 -19
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +3 -2
  58. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +4 -1
  59. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +2 -0
  60. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn.mjs +3 -0
  61. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -1
  62. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +3 -1
  63. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +11 -5
  64. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +4 -0
  65. package/src/runtime/agent/orchestrator/providers/catalog-deadline.mjs +163 -0
  66. package/src/runtime/agent/orchestrator/providers/catalog-failures.mjs +34 -0
  67. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  68. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +38 -14
  69. package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +100 -26
  70. package/src/runtime/agent/orchestrator/providers/lib/send-callbacks.mjs +24 -0
  71. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +13 -0
  72. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +2 -1
  73. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
  74. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +4 -0
  75. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
  76. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
  77. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +49 -4
  78. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +0 -7
  79. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +7 -1
  80. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
  81. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +2 -0
  82. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
  83. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +17 -12
  84. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +5 -1
  85. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +11 -2
  86. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +1 -0
  87. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +45 -16
  88. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -0
  89. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +1 -0
  90. package/src/runtime/agent/orchestrator/providers/openai-ws-connect.mjs +196 -0
  91. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +74 -9
  92. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +14 -1
  93. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +2 -0
  94. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth-login.mjs +194 -0
  95. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth-tokens.mjs +353 -0
  96. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +293 -0
  97. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +33 -100
  98. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +23 -14
  99. package/src/runtime/agent/orchestrator/providers/registry.mjs +41 -27
  100. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +98 -4
  101. package/src/runtime/agent/orchestrator/runtime-core/context-status-shape.mjs +4 -0
  102. package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +6 -4
  103. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +15 -8
  104. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -0
  105. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +12 -6
  106. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +32 -5
  107. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +10 -4
  108. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +4 -1
  109. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +50 -20
  110. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +2 -0
  111. package/src/runtime/agent/orchestrator/session/agent-home.d.mts +4 -0
  112. package/src/runtime/agent/orchestrator/session/agent-home.mjs +33 -0
  113. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +62 -0
  114. package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +15 -3
  115. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +31 -0
  116. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +241 -0
  117. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +162 -0
  118. package/src/runtime/agent/orchestrator/session/agent-room.mjs +641 -0
  119. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +7 -1
  120. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +74 -13
  121. package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +102 -0
  122. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +8 -2
  123. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +16 -0
  124. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +2 -0
  125. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +6 -2
  126. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +165 -0
  127. package/src/runtime/agent/orchestrator/session/human-gate/held-message.mjs +95 -0
  128. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +221 -0
  129. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +195 -0
  130. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +167 -0
  131. package/src/runtime/agent/orchestrator/session/lifecycle-scan.mjs +9 -0
  132. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +3 -1
  133. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +39 -2
  134. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +10 -0
  135. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +16 -1
  136. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +82 -33
  137. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +11 -4
  138. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +5 -4
  139. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +13 -3
  140. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +7 -0
  141. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +1 -0
  142. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +6 -1
  143. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  144. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -0
  145. package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +13 -5
  146. package/src/runtime/agent/orchestrator/session/manager/pending-lifecycle-epoch.mjs +0 -8
  147. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +10 -7
  148. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +62 -22
  149. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +44 -16
  150. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +22 -5
  151. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +23 -1
  152. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +81 -34
  153. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +39 -4
  154. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +27 -5
  155. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +55 -8
  156. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -0
  157. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +3 -2
  158. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +6 -0
  159. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +58 -0
  160. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -2
  161. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +24 -0
  162. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +32 -5
  163. package/src/runtime/agent/orchestrator/session/request-fork.mjs +47 -0
  164. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +70 -0
  165. package/src/runtime/agent/orchestrator/session/routine-result.mjs +277 -0
  166. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +16 -1
  167. package/src/runtime/agent/orchestrator/session/side-question.mjs +5 -43
  168. package/src/runtime/agent/orchestrator/session/store/fs-probe.mjs +10 -0
  169. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +73 -6
  170. package/src/runtime/agent/orchestrator/session/store/load-session.mjs +40 -0
  171. package/src/runtime/agent/orchestrator/session/store/save-fault.mjs +15 -0
  172. package/src/runtime/agent/orchestrator/session/store/session-paths.mjs +12 -0
  173. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +8 -0
  174. package/src/runtime/agent/orchestrator/session/store-agent-worker-header.mjs +36 -0
  175. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +258 -43
  176. package/src/runtime/agent/orchestrator/session/store-record-strict-scan.mjs +325 -0
  177. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +84 -21
  178. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -12
  179. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +46 -0
  180. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +46 -30
  181. package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +0 -4
  182. package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +144 -45
  183. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +32 -13
  184. package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +364 -38
  185. package/src/runtime/agent/orchestrator/session/store.mjs +9 -2
  186. package/src/runtime/agent/orchestrator/session/task-wait-control.mjs +43 -12
  187. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +23 -6
  188. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +44 -1
  189. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +12 -6
  190. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +13 -7
  191. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/bashism-rewrite.mjs +1 -1
  192. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/benign-exit.mjs +7 -1
  193. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +40 -7
  194. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +37 -34
  195. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/inline-hoist.mjs +1 -1
  196. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +0 -4
  197. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -0
  198. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +29 -27
  199. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +1 -1
  200. package/src/runtime/agent/orchestrator/tools/builtin/find-files-tool.mjs +28 -11
  201. package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +12 -3
  202. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +203 -101
  203. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +90 -41
  204. package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +17 -1
  205. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-delivery.mjs +199 -0
  206. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +12 -0
  207. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +165 -39
  208. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +25 -13
  209. package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +113 -5
  210. package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +7 -6
  211. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +19 -4
  212. package/src/runtime/agent/orchestrator/tools/builtin/read-line-collector.mjs +5 -3
  213. package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +25 -7
  214. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +2 -1
  215. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-render.mjs +3 -1
  216. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +12 -5
  217. package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +61 -22
  218. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +32 -8
  219. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +193 -19
  220. package/src/runtime/agent/orchestrator/tools/builtin/shell-direct-exe.mjs +57 -7
  221. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +123 -32
  222. package/src/runtime/agent/orchestrator/tools/builtin/shell-lossless-compact.mjs +26 -1
  223. package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +13 -28
  224. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +90 -11
  225. package/src/runtime/agent/orchestrator/tools/builtin/wait-budgets.mjs +23 -0
  226. package/src/runtime/agent/orchestrator/tools/builtin.mjs +54 -10
  227. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +22 -28
  228. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +11 -7
  229. package/src/runtime/agent/orchestrator/tools/destructive-warning.mjs +126 -9
  230. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  231. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +15 -6
  232. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +64 -2
  233. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +10 -2
  234. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promotion-results.mjs +13 -3
  235. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +8 -0
  236. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -1
  237. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +1 -1
  238. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +104 -55
  239. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +7 -2
  240. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +39 -23
  241. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +6 -2
  242. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
  243. package/src/runtime/attachments/office-extract.mjs +11 -9
  244. package/src/runtime/browser-bridge/client.mjs +24 -0
  245. package/src/runtime/channels/lib/scheduler.mjs +40 -12
  246. package/src/runtime/channels/lib/schedules-db.mjs +71 -12
  247. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +36 -16
  248. package/src/runtime/channels/lib/webhook.mjs +0 -1
  249. package/src/runtime/channels/lib/webhooks-db.mjs +100 -10
  250. package/src/runtime/channels/lib/worker-main.mjs +17 -20
  251. package/src/runtime/computer-bridge/client.mjs +28 -1
  252. package/src/runtime/connectors/gmail-app-password.mjs +690 -0
  253. package/src/runtime/connectors/google-mcp.mjs +446 -0
  254. package/src/runtime/memory/lib/core-memory-access.mjs +69 -0
  255. package/src/runtime/memory/lib/core-memory-file.mjs +16 -8
  256. package/src/runtime/memory/lib/core-memory-index.mjs +15 -9
  257. package/src/runtime/memory/lib/core-memory-management.mjs +10 -4
  258. package/src/runtime/memory/lib/core-memory-store.mjs +53 -30
  259. package/src/runtime/memory/lib/core-memory-uniqueness.mjs +10 -6
  260. package/src/runtime/memory/lib/embedding-tokenizer.mjs +32 -18
  261. package/src/runtime/memory/lib/http-router/session-routes.mjs +3 -2
  262. package/src/runtime/memory/lib/memory-action-handlers/core-actions.mjs +44 -14
  263. package/src/runtime/memory/lib/memory-schema/core-and-meta.mjs +11 -0
  264. package/src/runtime/memory/lib/memory.mjs +7 -1
  265. package/src/runtime/memory/lib/query-core-recall.mjs +9 -1
  266. package/src/runtime/memory/lib/query-hybrid-search.mjs +2 -0
  267. package/src/runtime/memory/lib/runtime-fetcher.mjs +1 -1
  268. package/src/runtime/memory/lib/tool-call-handler.mjs +9 -5
  269. package/src/runtime/memory/tool-defs.mjs +5 -0
  270. package/src/runtime/office/authoring/pptx-script-normalize.mjs +11 -8
  271. package/src/runtime/office/core/tabular.mjs +1 -1
  272. package/src/runtime/office/design/library/design-template-inspect.mjs +7 -4
  273. package/src/runtime/office/portable/docx-formatting.mjs +1 -1
  274. package/src/runtime/office/portable/docx-runs.mjs +1 -1
  275. package/src/runtime/office/portable/docx-tracked-edits.mjs +2 -2
  276. package/src/runtime/office/portable/portable-docx-edits.mjs +11 -10
  277. package/src/runtime/office/portable/portable-docx-operations.mjs +21 -17
  278. package/src/runtime/office/portable/portable-docx-parts.mjs +15 -9
  279. package/src/runtime/office/portable/portable-docx-styles.mjs +4 -1
  280. package/src/runtime/office/portable/portable-docx-xml.mjs +9 -9
  281. package/src/runtime/office/portable/portable-opc.mjs +27 -13
  282. package/src/runtime/office/portable/portable-pivot.mjs +3 -3
  283. package/src/runtime/office/portable/portable-pptx-chart.mjs +3 -3
  284. package/src/runtime/office/portable/portable-pptx-charts.mjs +24 -18
  285. package/src/runtime/office/portable/portable-pptx-core.mjs +5 -5
  286. package/src/runtime/office/portable/portable-pptx-deck.mjs +8 -4
  287. package/src/runtime/office/portable/portable-pptx-package.mjs +7 -4
  288. package/src/runtime/office/portable/portable-pptx-shapes.mjs +18 -9
  289. package/src/runtime/office/portable/portable-sheet-parts.mjs +9 -3
  290. package/src/runtime/office/portable/portable-sheet-xml.mjs +5 -5
  291. package/src/runtime/office/portable/portable-xlsx-charts.mjs +4 -1
  292. package/src/runtime/office/portable/portable-xlsx-drawings.mjs +10 -6
  293. package/src/runtime/office/portable/portable-xlsx-operations.mjs +8 -5
  294. package/src/runtime/office/portable/portable-xlsx-pivot-ops.mjs +1 -1
  295. package/src/runtime/office/portable/portable-xlsx-reference-shift.mjs +11 -7
  296. package/src/runtime/office/portable/portable-xlsx-sheet-edits.mjs +6 -3
  297. package/src/runtime/office/portable/portable-xml.mjs +1 -1
  298. package/src/runtime/shared/agent-avatar-sprites.mjs +445 -0
  299. package/src/runtime/shared/agent-avatar.d.mts +64 -0
  300. package/src/runtime/shared/agent-avatar.mjs +320 -0
  301. package/src/runtime/shared/agent-route-config.d.mts +3 -0
  302. package/src/runtime/shared/agent-route-config.mjs +16 -0
  303. package/src/runtime/shared/atomic-file.mjs +36 -14
  304. package/src/runtime/shared/background-tasks.mjs +60 -20
  305. package/src/runtime/shared/child-spawn-gate.mjs +26 -16
  306. package/src/runtime/shared/config.mjs +8 -11
  307. package/src/runtime/shared/connector-accounts.mjs +15 -0
  308. package/src/runtime/shared/connector-catalog.d.mts +52 -0
  309. package/src/runtime/shared/connector-catalog.mjs +230 -0
  310. package/src/runtime/shared/connector-triggers.d.mts +3 -0
  311. package/src/runtime/shared/connector-triggers.mjs +43 -0
  312. package/src/runtime/shared/file-lock.mjs +7 -1
  313. package/src/runtime/shared/file-permissions-worker.mjs +38 -0
  314. package/src/runtime/shared/file-permissions.d.mts +19 -4
  315. package/src/runtime/shared/file-permissions.mjs +310 -4
  316. package/src/runtime/shared/git-runner-worker.mjs +6 -78
  317. package/src/runtime/shared/google-oauth-client.mjs +24 -0
  318. package/src/runtime/shared/held-request-host.d.mts +12 -0
  319. package/src/runtime/shared/held-request-host.mjs +71 -0
  320. package/src/runtime/shared/llm/http-agent.mjs +7 -3
  321. package/src/runtime/shared/llm/provider-catalog-registry.mjs +10 -0
  322. package/src/runtime/shared/llm/usage-accounting.mjs +2 -0
  323. package/src/runtime/shared/llm/usage-ledger-import.mjs +32 -3
  324. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +12 -5
  325. package/src/runtime/shared/llm/usage-ledger-worker.mjs +1 -1
  326. package/src/runtime/shared/llm/usage-ledger.mjs +20 -5
  327. package/src/runtime/shared/memory-snapshot.mjs +5 -1
  328. package/src/runtime/shared/notify-trace.mjs +8 -1
  329. package/src/runtime/shared/plain-preview.d.mts +1 -0
  330. package/src/runtime/shared/plain-preview.mjs +28 -0
  331. package/src/runtime/shared/pristine-execution-contract.json +5 -1
  332. package/src/runtime/shared/process-runner-worker.mjs +7 -0
  333. package/src/runtime/shared/process-runner.mjs +38 -0
  334. package/src/runtime/shared/provider-accounts.mjs +2 -1
  335. package/src/runtime/shared/provider-api-key.mjs +0 -1
  336. package/src/runtime/shared/read-row-numbers.mjs +1 -1
  337. package/src/runtime/shared/resource-admission.mjs +60 -18
  338. package/src/runtime/shared/routine-limits.d.mts +11 -0
  339. package/src/runtime/shared/routine-limits.mjs +21 -0
  340. package/src/runtime/shared/routine-trigger.d.mts +10 -0
  341. package/src/runtime/shared/routine-trigger.mjs +44 -0
  342. package/src/runtime/shared/run-child.mjs +84 -0
  343. package/src/runtime/shared/schedule-model-ref.mjs +9 -10
  344. package/src/runtime/shared/session-activity-signal.mjs +23 -0
  345. package/src/runtime/shared/shell-output-summary.mjs +23 -0
  346. package/src/runtime/shared/singleton-owner.mjs +126 -42
  347. package/src/runtime/shared/slow-span.mjs +4 -12
  348. package/src/runtime/shared/statusline/statusline-segments.mjs +13 -1
  349. package/src/runtime/shared/tool-output-limit.mjs +4 -2
  350. package/src/runtime/shared/tool-output-marker.mjs +58 -0
  351. package/src/runtime/shared/transcript-metadata.mjs +10 -0
  352. package/src/runtime/shared/turn-changes.mjs +0 -8
  353. package/src/runtime/shared/usage-readout-totals.mjs +56 -0
  354. package/src/runtime/shared/user-data-guard.mjs +131 -34
  355. package/src/runtime/shared/worktrees/activity-gate.mjs +33 -8
  356. package/src/runtime/shared/worktrees/git-head.mjs +20 -2
  357. package/src/runtime/shared/worktrees/lock.mjs +3 -2
  358. package/src/runtime/terminal-bridge/tool-defs.mjs +1 -1
  359. package/src/runtime/tidy/runners/biome.mjs +3 -1
  360. package/src/runtime/tidy/shadow.mjs +2 -1
  361. package/src/runtime/web-search/index.mjs +5 -7
  362. package/src/runtime/web-search/lib/formatter.mjs +42 -6
  363. package/src/runtime/web-search/tool-defs.mjs +6 -1
  364. package/src/session-runtime/agent-home-delivery.mjs +459 -0
  365. package/src/session-runtime/agent-profile-changes.mjs +21 -0
  366. package/src/session-runtime/agent-room-delivery.mjs +92 -0
  367. package/src/session-runtime/agent-task-sessions.mjs +200 -0
  368. package/src/session-runtime/boot/apis.mjs +50 -18
  369. package/src/session-runtime/boot/facade.mjs +5 -8
  370. package/src/session-runtime/boot/infrastructure.mjs +28 -17
  371. package/src/session-runtime/boot/tools.mjs +3 -0
  372. package/src/session-runtime/channel-config-api.mjs +53 -5
  373. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +36 -20
  374. package/src/session-runtime/cwd-plugins/plugin-status.mjs +6 -1
  375. package/src/session-runtime/held-requests.mjs +228 -0
  376. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +121 -0
  377. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +5 -1
  378. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +31 -2
  379. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +31 -2
  380. package/src/session-runtime/internal-tool-executor.mjs +2 -0
  381. package/src/session-runtime/lifecycle/session-catalog.mjs +9 -0
  382. package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +4 -3
  383. package/src/session-runtime/lifecycle/session-switch/resume.mjs +11 -4
  384. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +3 -3
  385. package/src/session-runtime/lifecycle/shared.mjs +25 -2
  386. package/src/session-runtime/lifecycle/teardown/session-close.mjs +2 -0
  387. package/src/session-runtime/lifecycle/teardown.mjs +5 -2
  388. package/src/session-runtime/lifecycle-api.mjs +3 -1
  389. package/src/session-runtime/mcp-glue/server-catalog.mjs +8 -0
  390. package/src/session-runtime/mcp-glue/server-input.mjs +3 -0
  391. package/src/session-runtime/mcp-oauth-login.mjs +112 -15
  392. package/src/session-runtime/model-route/set-route.mjs +14 -3
  393. package/src/session-runtime/owned-automation-run.mjs +229 -0
  394. package/src/session-runtime/provider-auth/login.mjs +10 -6
  395. package/src/session-runtime/provider-auth/setup.mjs +19 -2
  396. package/src/session-runtime/provider-catalog-cache.mjs +101 -25
  397. package/src/session-runtime/provider-models/catalog-load.mjs +14 -3
  398. package/src/session-runtime/provider-models/row-hydration.mjs +4 -0
  399. package/src/session-runtime/provider-models.mjs +76 -10
  400. package/src/session-runtime/provider-readiness.mjs +4 -0
  401. package/src/session-runtime/resource-api.mjs +5 -0
  402. package/src/session-runtime/resource-mcp-api.mjs +107 -13
  403. package/src/session-runtime/resource-skills-api.mjs +12 -0
  404. package/src/session-runtime/room-member-gate.mjs +48 -0
  405. package/src/session-runtime/routine-home-turn.mjs +156 -0
  406. package/src/session-runtime/routine-owner.mjs +48 -0
  407. package/src/session-runtime/schedule-session-run.mjs +30 -64
  408. package/src/session-runtime/services/agent-tool/close-flow.mjs +2 -1
  409. package/src/session-runtime/services/agent-tool/helpers.mjs +10 -0
  410. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +10 -2
  411. package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +13 -4
  412. package/src/session-runtime/services/agent-tool/notify.mjs +15 -4
  413. package/src/session-runtime/services/agent-tool/restart-notify.mjs +134 -0
  414. package/src/session-runtime/services/agent-tool/send-flow.mjs +24 -1
  415. package/src/session-runtime/services/agent-tool/spawn-flow/deferred-spawn.mjs +2 -1
  416. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -3
  417. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +19 -4
  418. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +2 -0
  419. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +5 -1
  420. package/src/session-runtime/services/agent-tool/tag-registry/tag-lookup.mjs +8 -3
  421. package/src/session-runtime/services/agent-tool/tag-registry.mjs +3 -0
  422. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +4 -0
  423. package/src/session-runtime/services/agent-tool/worker-index.mjs +44 -10
  424. package/src/session-runtime/services/agent-tool.mjs +20 -0
  425. package/src/session-runtime/services/channel-admin.mjs +93 -24
  426. package/src/session-runtime/services/memory-access.mjs +37 -0
  427. package/src/session-runtime/services/provider-admin.mjs +166 -59
  428. package/src/session-runtime/services/schedule-access.mjs +91 -0
  429. package/src/session-runtime/services/usage-dashboard-model.mjs +2 -2
  430. package/src/session-runtime/services/usage-dashboard.mjs +27 -69
  431. package/src/session-runtime/session-create/session-options.mjs +34 -7
  432. package/src/session-runtime/session-hooks.mjs +16 -8
  433. package/src/session-runtime/setup-tool/executor.mjs +68 -6
  434. package/src/session-runtime/setup-tool/extended-actions.mjs +72 -14
  435. package/src/session-runtime/setup-tool/settings-contract.mjs +8 -6
  436. package/src/session-runtime/setup-tool/tool-defs.mjs +8 -0
  437. package/src/session-runtime/skills-api.mjs +16 -1
  438. package/src/session-runtime/tool-policy-refresh.mjs +238 -29
  439. package/src/session-runtime/turn/session-ops.mjs +10 -3
  440. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +11 -2
  441. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +3 -0
  442. package/src/session-runtime/usage-stats-api.mjs +6 -1
  443. package/src/session-runtime/webhook-session-run.mjs +49 -67
  444. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +239 -0
  445. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +556 -22
  446. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +11 -1
  447. package/src/session-runtime/workflow-agents-api.mjs +4 -1
  448. package/src/standalone/channel-transport/http-routes.mjs +10 -9
  449. package/src/standalone/daemon-agent-control.mjs +1 -8
  450. package/src/standalone/daemon-desktop-runtime.mjs +20 -7
  451. package/src/standalone/daemon-owner.mjs +117 -25
  452. package/src/standalone/daemon-session-control.mjs +11 -2
  453. package/src/standalone/daemon.mjs +61 -17
  454. package/src/standalone/session-call-cache.mjs +6 -0
  455. package/src/standalone/session-protocol.mjs +28 -1
  456. package/src/standalone/session-service/retention/busy.mjs +7 -2
  457. package/src/standalone/session-service/session-calls/session-turns.mjs +9 -0
  458. package/src/standalone/session-service.mjs +19 -0
  459. package/src/standalone/session-transport/routes.mjs +7 -6
  460. package/src/tui/app/model-options.mjs +39 -2
  461. package/src/tui/app/model-picker.mjs +16 -2
  462. package/src/tui/app/provider-setup-picker.mjs +1 -2
  463. package/src/tui/app/slash-commands.mjs +20 -0
  464. package/src/tui/dist/index.mjs +487 -128
  465. package/src/tui/session/context-state.mjs +6 -1
  466. package/src/tui/session/notice-surface.mjs +35 -4
  467. package/src/tui/session/notice-text.mjs +0 -1
  468. package/src/tui/session/queue-helpers.mjs +2 -1
  469. package/src/tui/session/session-api/extensions.mjs +4 -0
  470. package/src/tui/session/session-api/intake/submission.mjs +12 -13
  471. package/src/tui/session/session-api/integrations.mjs +9 -5
  472. package/src/tui/session/session-api/lifecycle/session-reset.mjs +13 -3
  473. package/src/tui/session/session-api/lifecycle.mjs +17 -1
  474. package/src/tui/session/session-api/routes.mjs +48 -3
  475. package/src/tui/session/session-flow/drain.mjs +20 -3
  476. package/src/tui/session/session-flow/queue/entry-shape.mjs +2 -1
  477. package/src/tui/session/session-flow/queue/take-entries.mjs +3 -2
  478. package/src/tui/session/session-flow/steering.mjs +104 -24
  479. package/src/tui/session/session-flow.mjs +2 -0
  480. package/src/tui/session/tool-approval.mjs +26 -5
  481. package/src/tui/session/transcript-intake.mjs +6 -2
  482. package/src/tui/session/transcript-spill.mjs +5 -1
  483. package/src/tui/session/tui-steering-persist.mjs +65 -42
  484. package/src/tui/session/turn-live-call.mjs +56 -0
  485. package/src/tui/session/turn-stream.mjs +33 -0
  486. package/src/tui/session/turn.mjs +50 -7
  487. package/src/ui/statusline.mjs +1 -6
  488. package/src/ui/streaming-markdown-heal.mjs +24 -7
  489. package/src/vendor/statusline/bin/statusline-route.mjs +0 -1
  490. package/src/vendor/statusline/src/gateway/route-meta.mjs +0 -1
  491. package/src/workflows/default/WORKFLOW.md +9 -13
  492. package/src/session-runtime/automation-agents.mjs +0 -94
  493. package/src/session-runtime/automation-workflow.mjs +0 -59
@@ -9,15 +9,17 @@
9
9
  // tree (taskkill /T /F on Windows, SIGTERM->SIGKILL of every descendant on
10
10
  // POSIX). No external dependencies.
11
11
  import { spawn } from 'node:child_process';
12
+ import { runProcessOffThread } from '../../../shared/process-runner.mjs';
12
13
  import { detachedSpawnOpts } from '../../../shared/spawn-flags.mjs';
13
14
 
14
15
  const isWin = process.platform === 'win32';
15
16
 
17
+ // The SIGTERM → SIGKILL escalation wait. Kept ref'd: it runs right after the
18
+ // tree was signalled, when the dying children may be the only handles left,
19
+ // and an unref'd timer would then never fire, leaving the awaited kill (and
20
+ // every close awaiting it) pending forever. It is short and bounded.
16
21
  function delay(ms) {
17
- return new Promise((resolve) => {
18
- const t = setTimeout(resolve, ms);
19
- if (t.unref) t.unref();
20
- });
22
+ return new Promise((resolve) => setTimeout(resolve, ms));
21
23
  }
22
24
 
23
25
  // Best-effort signal: a pid that already exited (or cannot be signalled) is skipped.
@@ -44,54 +46,26 @@ function transportPid(transport) {
44
46
  * settled, so enumerators never pile up whatever this value is. */
45
47
  export const ENUMERATION_DEADLINE_MS = 20_000;
46
48
 
47
- // Spawn a helper command and resolve its stdout (empty string on any error).
48
- // `timeoutMs` > 0: past the deadline the helper is killed (Node reaps it on
49
- // exit) and the call settles at once instead of waiting for it.
50
- function run(cmd, args, { strict = false, timeoutMs = 0 } = {}) {
51
- return new Promise((resolve, reject) => {
52
- let out = '';
53
- let cp;
54
- let timer = null;
55
- let settled = false;
56
- const settle = (finish) => {
57
- if (settled) return;
58
- settled = true;
59
- if (timer) clearTimeout(timer);
60
- finish();
61
- };
62
- try {
63
- cp = spawn(cmd, args, { windowsHide: true });
64
- } catch (error) {
65
- if (strict) {
66
- reject(error);
67
- return;
68
- }
69
- resolve('');
70
- return;
71
- }
72
- cp.stdout?.on('data', (d) => {
73
- out += String(d);
74
- });
75
- cp.on('error', (error) => settle(() => (strict ? reject(error) : resolve(out))));
76
- cp.on('close', (code) =>
77
- settle(() => (strict && code !== 0 ? reject(new Error(`Process enumeration failed (${code}).`)) : resolve(out)))
78
- );
79
- if (timeoutMs > 0) {
80
- timer = setTimeout(() => {
81
- try {
82
- cp.kill('SIGKILL');
83
- } catch {
84
- /* already gone */
85
- }
86
- cp.stdout?.destroy?.();
87
- cp.stderr?.destroy?.();
88
- settle(() =>
89
- strict ? reject(new Error(`Process enumeration timed out after ${timeoutMs} ms.`)) : resolve('')
90
- );
91
- }, timeoutMs);
92
- timer.unref?.();
49
+ // Output bound of one helper command: a process table is tens of KB.
50
+ const HELPER_MAX_BYTES = 64 * 1024 * 1024;
51
+
52
+ // Run a helper command off the event loop (process-runner.mjs: under load one
53
+ // PowerShell start blocked the daemon's loop for seconds) and resolve its
54
+ // stdout. Past `timeoutMs` (default ENUMERATION_DEADLINE_MS) the helper is
55
+ // killed and the call settles. Strict: a failed, non-zero or timed-out run
56
+ // rejects; otherwise it resolves '' (non-zero exits keep their stdout).
57
+ function run(cmd, args, { strict = false, timeoutMs = ENUMERATION_DEADLINE_MS } = {}) {
58
+ return runProcessOffThread(cmd, args, { timeoutMs, maxBytes: HELPER_MAX_BYTES }).then(
59
+ ({ code, stdout }) => {
60
+ if (strict && code !== 0) throw new Error(`Process enumeration failed (${code}).`);
61
+ return stdout;
62
+ },
63
+ (error) => {
64
+ if (!strict) return '';
65
+ if (error?.code === 'ETIMEDOUT') throw new Error(`Process enumeration timed out after ${timeoutMs} ms.`);
66
+ throw error;
93
67
  }
94
- });
68
+ );
95
69
  }
96
70
 
97
71
  // Snapshot the whole process table once: parent map + a per-pid identity
@@ -114,21 +88,25 @@ async function enumerate({ strict = false } = {}) {
114
88
  ],
115
89
  { strict, timeoutMs: ENUMERATION_DEADLINE_MS }
116
90
  )
117
- : await run('ps', ['-A', '-o', 'pid=,ppid=,pgid=,comm=,lstart='], {
91
+ : await run('ps', ['-A', '-o', 'pid=,ppid=,pgid=,stat=,comm=,lstart='], {
118
92
  strict,
119
93
  timeoutMs: ENUMERATION_DEADLINE_MS,
120
94
  });
121
- const row = isWin ? /^(\d+)\s+(\d+)\s+()(\S+)(?:\s+(.+))?$/ : /^(\d+)\s+(\d+)\s+(\d+)\s+(\S+)(?:\s+(.+))?$/;
95
+ const row = isWin ? /^(\d+)\s+(\d+)\s+()()(\S+)(?:\s+(.+))?$/ : /^(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)(?:\s+(.+))?$/;
122
96
  for (const line of out.split(/\r?\n/)) {
123
97
  const m = line.trim().match(row);
124
98
  if (!m) continue;
99
+ // A zombie runs nothing and holds no port or file; it only waits to be
100
+ // reaped (forever under an init that does not reap, e.g. a container
101
+ // without one). Counting it alive would hold a dead tree as running.
102
+ if (m[4].startsWith('Z')) continue;
125
103
  const pid = Number(m[1]);
126
104
  const ppid = Number(m[2]);
127
- const token = (m[5] || '').trim() || null;
105
+ const token = (m[6] || '').trim() || null;
128
106
  if (!childrenOf.has(ppid)) childrenOf.set(ppid, []);
129
107
  childrenOf.get(ppid).push(pid);
130
108
  tokenOf.set(pid, token);
131
- nameOf.set(pid, m[4]);
109
+ nameOf.set(pid, m[5]);
132
110
  if (m[3]) pgidOf.set(pid, Number(m[3]));
133
111
  }
134
112
  if (strict && !nameOf.has(process.pid)) throw new Error('The process snapshot could not be verified.');
@@ -142,17 +120,33 @@ async function enumerate({ strict = false } = {}) {
142
120
  * `shared: true` (pre-kill captures) joins a read already in flight, so many
143
121
  * jobs promoted together do not each spawn an enumerator; a capture taken
144
122
  * while its root runs is valid whenever that read began. An observation after
145
- * a kill never shares: a read that began before the kill would report the
146
- * killed tree alive and defer the confirmation by a whole re-check. */
123
+ * a kill or exit must see a table taken AFTER it asked: a read that began
124
+ * before the kill would report the killed tree alive and defer the
125
+ * confirmation by a whole re-check. Such a caller starts a read when none
126
+ * runs, else joins the one queued to begin when the running read settles —
127
+ * every observer asking meanwhile shares it. At most one enumerator runs at
128
+ * a time, however many shells settle or watch at once (each one is a
129
+ * PowerShell process on Windows; one per settle and watch pass compounded
130
+ * machine load into multi-second daemon stalls). */
147
131
  let tableInFlight = null;
132
+ let nextTable = null;
133
+ function startTableRead() {
134
+ const read = enumerate({ strict: true }).finally(() => {
135
+ if (tableInFlight === read) tableInFlight = null;
136
+ });
137
+ tableInFlight = read;
138
+ return read;
139
+ }
148
140
  export function readProcessTable({ shared = false } = {}) {
149
- if (!shared) return enumerate({ strict: true });
150
- if (!tableInFlight) {
151
- tableInFlight = enumerate({ strict: true }).finally(() => {
152
- tableInFlight = null;
153
- });
154
- }
155
- return tableInFlight;
141
+ if (shared && tableInFlight) return tableInFlight;
142
+ if (nextTable) return nextTable;
143
+ if (!tableInFlight) return startTableRead();
144
+ const settled = () => {};
145
+ nextTable = tableInFlight.then(settled, settled).then(() => {
146
+ nextTable = null;
147
+ return startTableRead();
148
+ });
149
+ return nextTable;
156
150
  }
157
151
 
158
152
  // Walk every descendant pid of rootPid (excludes rootPid).
@@ -5,6 +5,9 @@
5
5
  */
6
6
  import { readServicePort } from '../../../shared/service-discovery.mjs';
7
7
  import { createMcpOAuth } from './oauth.mjs';
8
+ import { mcpOAuthClientFor } from './oauth-client.mjs';
9
+ import { googleMcpService, withGoogleMcp } from '../../../connectors/google-mcp.mjs';
10
+ import { gmailAppPasswordService, withGmailAppPasswordMcp } from '../../../connectors/gmail-app-password.mjs';
8
11
  import {
9
12
  AUTO_DETECT_PORTS,
10
13
  expandEnvVars,
@@ -138,12 +141,18 @@ export function createMcpTransport({ name, cfg, sdk, log, strictEnv, oauthOption
138
141
  : createMcpOAuth({
139
142
  url,
140
143
  serverName: name,
144
+ oauthClient: mcpOAuthClientFor(cfg, url),
141
145
  ...oauthOptions,
146
+ // A built-in Google connector is answered in-process.
147
+ ...(googleMcpService(url) ? { fetchImpl: withGoogleMcp(oauthOptions?.fetchImpl) } : {}),
142
148
  // SSE advertises its POST endpoint on the authenticated event stream.
143
149
  // No other path on the origin is allowed to receive this server's token.
144
150
  isTransportEndpoint: (input) => kind === 'sse' && transport?._endpoint?.href === new URL(input).href,
145
151
  });
146
- transport = new Transport(new URL(url), oauth ? { fetch: oauth.fetch } : opts);
152
+ // Gmail with an app password is answered in-process too (its account
153
+ // header is the configured authentication; the password is in the keychain).
154
+ const local = !oauth && gmailAppPasswordService(url) ? { ...opts, fetch: withGmailAppPasswordMcp() } : opts;
155
+ transport = new Transport(new URL(url), oauth ? { fetch: oauth.fetch } : local);
147
156
  if (oauth) {
148
157
  transport.oauth = oauth;
149
158
  const close = transport.close.bind(transport);
@@ -14,7 +14,7 @@ import { basename, join, resolve } from 'node:path';
14
14
  import { pathToFileURL } from 'node:url';
15
15
  import { tmpdir } from 'node:os';
16
16
  import { createHash, randomUUID } from 'node:crypto';
17
- import { buildSmartReadTruncationMarker, smartReadTruncate } from '../tools/builtin/read-formatting.mjs';
17
+ import { smartReadTruncate } from '../tools/builtin/read-formatting.mjs';
18
18
  import { shutdownStdioChild, killStdioChildTreeFast } from './child-tree.mjs';
19
19
  import { makeToolEnvelope, normalizeToolEnvelope } from '../session/tool-envelope.mjs';
20
20
  import { classifyResultKind } from '../session/result-classification.mjs';
@@ -33,11 +33,17 @@ import {
33
33
  import { createLazyOutputValidator } from './lazy-output-validator.mjs';
34
34
  import { appendMcpStderrTail, createMcpTransport, hasConfiguredMcpAuth } from './client-transport.mjs';
35
35
  import { listAllMcpTools, MCP_CONNECT_CANCELLED, runBoundedHandshake } from './client-handshake.mjs';
36
+ import { rememberServerConnector } from './server-connectors.mjs';
37
+ import { mcpOAuthClientFor } from './oauth-client.mjs';
38
+ import { googleMcpService, withGoogleMcp } from '../../../connectors/google-mcp.mjs';
36
39
  import {
37
40
  clearMcpOAuth,
41
+ commitMcpOAuthSwitch,
38
42
  hasStoredMcpOAuthTokens,
39
43
  isMcpOAuthReauthenticationRequired,
40
44
  MCP_OAUTH_CALLBACK_NOT_PENDING,
45
+ startMcpOAuthSwitch,
46
+ storedMcpOAuthIdentity,
41
47
  } from './oauth.mjs';
42
48
  export { probeMcpServerAuth } from './oauth-probe.mjs';
43
49
 
@@ -67,6 +73,14 @@ const _connectAbortGenerations = new Map();
67
73
  const _pendingConnects = new Set();
68
74
  // scopeId -> root path advertised by that scope's scope-following servers.
69
75
  const _scopeRoots = new Map();
76
+ // One stdio process per server config, shared by every scope that connects
77
+ // the same server with the same resolved config: share key -> connection
78
+ // { key, client, transport, liveness, stderrTail, warnings, tools,
79
+ // capabilities, instructions, users: Set<registryKey> }. Every session
80
+ // runtime has its own scope, so per-scope processes multiplied with the
81
+ // number of retained sessions. A scope's close releases only its own use; the
82
+ // process tree is shut down when its last user releases it.
83
+ const _sharedStdio = new Map();
70
84
  let mcpSdkPromise = null;
71
85
  // Memo for mcpToolHasField(name, field) — keyed by `${scopeId}|${toolName}|${field}`.
72
86
  // The lookup (regex parse + servers Map get + tools.find + schema property
@@ -99,6 +113,24 @@ function scopedServerEntries(scopeId) {
99
113
  const normalized = normalizeMcpScopeId(scopeId);
100
114
  return [...servers.entries()].filter(([, server]) => server?.scopeId === normalized);
101
115
  }
116
+ // Share key of a stdio server, or null when its connection must stay private
117
+ // to one scope: remote transports, and servers whose advertised roots follow
118
+ // their scope's cwd.
119
+ function sharedStdioKey(name, cfg) {
120
+ if (cfg?._mixdogRootsFollowScope === true) return null;
121
+ try {
122
+ if (resolveMcpTransportKind(cfg) !== 'stdio') return null;
123
+ } catch {
124
+ return null;
125
+ }
126
+ return `${String(name || '')}\u0000${JSON.stringify(cfg)}`;
127
+ }
128
+ // A connection that failed a call is never handed to a later connect; scopes
129
+ // still holding it keep it until they release it themselves.
130
+ function retireSharedConnection(server) {
131
+ const shared = server?.shared;
132
+ if (shared && _sharedStdio.get(shared.key) === shared) _sharedStdio.delete(shared.key);
133
+ }
102
134
  function currentConnectAbortGeneration(scopeId) {
103
135
  return _connectAbortGenerations.get(normalizeMcpScopeId(scopeId)) || 0;
104
136
  }
@@ -308,7 +340,11 @@ function callAdmissionFor(server) {
308
340
  const cfg = server?.cfg || {};
309
341
  gate = createOwnerFairGate({
310
342
  name: `MCP ${server.name}`,
311
- activeMax: positiveInt(cfg.maxConcurrency ?? cfg.max_concurrency ?? process.env.MIXDOG_MCP_MAX_INFLIGHT, 8),
343
+ // JSON-RPC multiplexes requests by id over one transport, so the client
344
+ // imposes no in-flight cap of its own; a server that needs one declares
345
+ // it (maxConcurrency) or the operator sets MIXDOG_MCP_MAX_INFLIGHT.
346
+ activeMax:
347
+ positiveInt(cfg.maxConcurrency ?? cfg.max_concurrency ?? process.env.MIXDOG_MCP_MAX_INFLIGHT, null) ?? Infinity,
312
348
  queueMax: positiveInt(cfg.maxQueue ?? cfg.max_queue ?? process.env.MIXDOG_MCP_MAX_QUEUE, 256),
313
349
  minOwnerQueue: 8,
314
350
  waitTimeoutMs: positiveInt(
@@ -407,6 +443,7 @@ async function reconnectMcpServer(scopeId, serverName, failedServer) {
407
443
  if (!current || genAtStart !== currentConnectAbortGeneration(scopeId) || current.liveness?.closing) {
408
444
  throw new Error(`MCP server "${serverName}" was disconnected`);
409
445
  }
446
+ retireSharedConnection(current);
410
447
  await replaceServerConnection(scopeId, serverName, current, failedServer.cfg, genAtStart);
411
448
  });
412
449
  const replacement = servers.get(registryKey);
@@ -1037,7 +1074,7 @@ function capMcpOutputParts(content) {
1037
1074
  const s = typeof content === 'string' ? content : String(content ?? '');
1038
1075
  const bodyBytes = Buffer.byteLength(s, 'utf8');
1039
1076
  const bodyLines = countTextLines(s);
1040
- const { text, truncated } = smartReadTruncate(s, bodyLines, bodyBytes);
1077
+ const { text, truncated, marker } = smartReadTruncate(s, bodyLines, bodyBytes);
1041
1078
  if (!truncated) return { text, spillNote: '', truncated: false };
1042
1079
  // Spill the full body to a tmp file so the caller can recover content
1043
1080
  // elided by the head/tail cap.
@@ -1056,7 +1093,6 @@ function capMcpOutputParts(content) {
1056
1093
  /* spill best-effort */
1057
1094
  }
1058
1095
  const spillNote = spillPath ? `\n\n... [full output spilled to ${spillPath}] ...` : '';
1059
- const marker = buildSmartReadTruncationMarker(bodyLines, bodyBytes);
1060
1096
  const split = text.indexOf(`\n${marker}\n`);
1061
1097
  return {
1062
1098
  text,
@@ -1220,22 +1256,52 @@ export function getMcpServerOAuthState(cfg) {
1220
1256
  return null;
1221
1257
  }
1222
1258
  if (!url || hasConfiguredMcpAuth(cfg)) return null;
1223
- return { signedIn: hasStoredMcpOAuthTokens(url) };
1259
+ const signedIn = hasStoredMcpOAuthTokens(url);
1260
+ if (!signedIn) return { signedIn };
1261
+ // Who it is signed in as (never guessed: empty when the server named nobody)
1262
+ // and whether 계정 바꾸기 can make the provider show its account choice.
1263
+ const who = storedMcpOAuthIdentity(url);
1264
+ return {
1265
+ signedIn,
1266
+ account: typeof who?.account === 'string' ? who.account : '',
1267
+ workspace: typeof who?.workspace === 'string' ? who.workspace : '',
1268
+ forcedAccountChoice: who?.forcedChoice === true,
1269
+ };
1270
+ }
1271
+ /** 계정 바꾸기 of an HTTP/SSE server: a browser sign-in beside the stored one
1272
+ * (oauth.mjs startMcpOAuthSwitch); `launch` opens the authorization URL. */
1273
+ export function startMcpServerOAuthSwitch(cfg, launch, serverName) {
1274
+ const url = resolveMcpOAuthUrl(cfg);
1275
+ if (!url) throw new Error(`MCP server "${serverName}" does not use OAuth sign-in`);
1276
+ return startMcpOAuthSwitch({
1277
+ url,
1278
+ launch,
1279
+ serverName,
1280
+ oauthClient: mcpOAuthClientFor(cfg, url),
1281
+ ...(googleMcpService(url) ? { fetchImpl: withGoogleMcp() } : {}),
1282
+ });
1283
+ }
1284
+ /** Store a switch's sign-in for the server's URL; resolves its rollback. */
1285
+ export function commitMcpServerOAuthSwitch(cfg, signIn) {
1286
+ return commitMcpOAuthSwitch({ url: resolveMcpOAuthUrl(cfg), signIn });
1224
1287
  }
1225
1288
  /**
1226
1289
  * Log out of an HTTP/SSE server URL: delete the OAuth tokens and client
1227
1290
  * registration stored for it, shared by every server configured with that
1228
- * exact URL, and drop them from live connections.
1291
+ * exact URL, and drop them from live connections. `options.revoke` also
1292
+ * revokes the grant where the authorization server documents how; resolves
1293
+ * `{ revoked }`.
1229
1294
  */
1230
- export async function clearMcpServerOAuth(cfg) {
1295
+ export async function clearMcpServerOAuth(cfg, options = {}) {
1231
1296
  let url;
1232
1297
  try {
1233
1298
  url = resolveMcpOAuthUrl(cfg);
1234
1299
  } catch {
1235
1300
  // A config without a valid remote URL never connected, so never logged in.
1236
- return;
1301
+ return { revoked: false };
1237
1302
  }
1238
- if (url) await clearMcpOAuth({ url });
1303
+ if (url) return clearMcpOAuth({ url, revoke: options.revoke === true });
1304
+ return { revoked: false };
1239
1305
  }
1240
1306
  /**
1241
1307
  * Close a single server: for stdio transports first shut down the full child
@@ -1245,6 +1311,13 @@ export async function clearMcpServerOAuth(cfg) {
1245
1311
  * only kills the direct child and discards the pid we need to walk the tree.
1246
1312
  */
1247
1313
  async function _closeServer(server) {
1314
+ // A shared stdio connection closes only when its last scope releases it.
1315
+ const shared = server?.shared;
1316
+ if (shared) {
1317
+ shared.users.delete(server.registryKey);
1318
+ if (shared.users.size > 0) return;
1319
+ retireSharedConnection(server);
1320
+ }
1248
1321
  // A deliberate close is not a crash: suppress the unexpected-close report.
1249
1322
  if (server?.liveness) server.liveness.closing = true;
1250
1323
  const transport = server?.transport;
@@ -1263,6 +1336,19 @@ async function _closeServer(server) {
1263
1336
  /* ignore */
1264
1337
  }
1265
1338
  }
1339
+ // Register `name` in `scopeId` on the live shared connection for `shareKey`,
1340
+ // when there is one. Returns whether it did.
1341
+ function adoptSharedConnection(shareKey, name, cfg, scopeId, genAtStart) {
1342
+ const shared = shareKey ? _sharedStdio.get(shareKey) : null;
1343
+ if (!shared || shared.liveness.alive === false || shared.liveness.closing) return false;
1344
+ const registryKey = mcpServerRegistryKey(scopeId, name);
1345
+ const { key: _key, users, ...connection } = shared;
1346
+ users.add(registryKey);
1347
+ rememberServerConnector(name, cfg);
1348
+ servers.set(registryKey, { scopeId, registryKey, name, ...connection, cfg, shared, generation: genAtStart });
1349
+ _invalidateMcpToolFieldMemo();
1350
+ return true;
1351
+ }
1266
1352
  // Connects whose handshake has not finished yet: disconnectAll() must be able
1267
1353
  // to see (and tear down) their transports, because `servers` only lists fully
1268
1354
  // handshaken entries. Generation token aborts a connect that outlives a
@@ -1282,6 +1368,8 @@ async function connectServer(
1282
1368
  // abort before creating a transport/child at all.
1283
1369
  throw new Error(`MCP server "${name}" connect aborted by shutdown`);
1284
1370
  }
1371
+ const shareKey = sharedStdioKey(name, cfg);
1372
+ if (adoptSharedConnection(shareKey, name, cfg, scopeId, genAtStart)) return;
1285
1373
  const client = createMcpClient(sdk, name, cfg, scopeId);
1286
1374
  const { transport, autoDetectAdvert, stderrTail, warnings } = createMcpTransport({
1287
1375
  name,
@@ -1326,26 +1414,42 @@ async function connectServer(
1326
1414
  }
1327
1415
  throw new Error(`MCP server "${name}" connect aborted by shutdown`);
1328
1416
  }
1417
+ // Another scope finished connecting the same server meanwhile: use its
1418
+ // process and release this one.
1419
+ if (adoptSharedConnection(shareKey, name, cfg, scopeId, genAtStart)) {
1420
+ await _closeServer({ client, transport, liveness });
1421
+ return;
1422
+ }
1329
1423
  const tools = mcpServerTools(name, capabilities, toolsResult.tools, cfg);
1330
1424
  const registryKey = mcpServerRegistryKey(scopeId, name);
1331
- servers.set(registryKey, {
1332
- scopeId,
1333
- registryKey,
1334
- name,
1425
+ const connection = {
1335
1426
  client,
1336
1427
  transport,
1337
1428
  tools,
1338
- cfg,
1339
1429
  instructions,
1340
1430
  capabilities,
1341
1431
  liveness,
1342
1432
  stderrTail,
1343
1433
  warnings: warnings || [],
1434
+ };
1435
+ const shared = shareKey ? { ...connection, key: shareKey, users: new Set([registryKey]) } : null;
1436
+ if (shared) _sharedStdio.set(shareKey, shared);
1437
+ rememberServerConnector(name, cfg);
1438
+ servers.set(registryKey, {
1439
+ scopeId,
1440
+ registryKey,
1441
+ name,
1442
+ ...connection,
1443
+ cfg,
1444
+ shared,
1344
1445
  generation: genAtStart,
1345
1446
  });
1346
1447
  if (capabilities?.tools?.listChanged && sdk.ToolListChangedNotificationSchema) {
1448
+ // A shared connection refreshes the tools of every scope using it.
1347
1449
  client.setNotificationHandler(sdk.ToolListChangedNotificationSchema, () =>
1348
- refreshServerTools(registryKey, client, name, cfg, capabilities)
1450
+ Promise.all(
1451
+ [...(shared?.users ?? [registryKey])].map((key) => refreshServerTools(key, client, name, cfg, capabilities))
1452
+ )
1349
1453
  );
1350
1454
  }
1351
1455
  _invalidateMcpToolFieldMemo();
@@ -1514,6 +1618,8 @@ async function runToolRefresh(registryKey, live, name, cfg, capabilities) {
1514
1618
  }
1515
1619
  if (servers.get(registryKey) !== live) return;
1516
1620
  live.tools = mcpServerTools(name, capabilities, refreshed.tools, cfg);
1621
+ // A scope adopting the connection later starts from the current listing.
1622
+ if (live.shared) live.shared.tools = live.tools;
1517
1623
  _invalidateMcpToolFieldMemo();
1518
1624
  } catch (error) {
1519
1625
  mcpLog(
@@ -1567,6 +1673,7 @@ export function _registerMcpServerForTest(scopeId, name, rawTools = [], options
1567
1673
  generation: currentConnectAbortGeneration(normalizedScopeId),
1568
1674
  };
1569
1675
  _knownMcpScopes.add(normalizedScopeId);
1676
+ rememberServerConnector(name, server.cfg);
1570
1677
  servers.set(registryKey, server);
1571
1678
  _invalidateMcpToolFieldMemo();
1572
1679
  return server;
@@ -0,0 +1,97 @@
1
+ // The account side of an MCP OAuth sign-in: which documented authorization
2
+ // parameter makes a provider show its account choice, and who a sign-in
3
+ // belongs to, read only from what the authorization server returned.
4
+ //
5
+ // Parameters are sent only where the provider documents them (keyed by the
6
+ // authorization endpoint's host); everywhere else nothing is added:
7
+ // Google prompt=select_account developers.google.com/identity/protocols/oauth2/web-server#creatingclient
8
+ // GitHub prompt=select_account docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps
9
+ // Atlassian (Jira, Confluence, Trello) prompt=consent developer.atlassian.com/cloud/jira/platform/oauth-2-3lo-apps/
10
+ // Linear prompt=consent linear.app/developers/oauth-2-0-authentication
11
+ // Dropbox force_reapprove=true www.dropbox.com/developers/documentation/http/documentation#oauth2-authorize
12
+
13
+ const SELECT_ACCOUNT = Object.freeze({ prompt: 'select_account' });
14
+ // A normal connect shows the chooser only where it is the documented choice.
15
+ const CONNECT_PARAMS = Object.freeze({
16
+ 'accounts.google.com': SELECT_ACCOUNT,
17
+ 'github.com': SELECT_ACCOUNT,
18
+ });
19
+ // 계정 바꾸기: the provider's documented way to force the choice (or the grant).
20
+ const SWITCH_PARAMS = Object.freeze({
21
+ ...CONNECT_PARAMS,
22
+ 'auth.atlassian.com': Object.freeze({ prompt: 'consent' }),
23
+ 'linear.app': Object.freeze({ prompt: 'consent' }),
24
+ 'mcp.linear.app': Object.freeze({ prompt: 'consent' }),
25
+ 'www.dropbox.com': Object.freeze({ force_reapprove: 'true' }),
26
+ });
27
+
28
+ const hostOf = (url) => {
29
+ try {
30
+ return new URL(url).host.toLowerCase();
31
+ } catch {
32
+ return '';
33
+ }
34
+ };
35
+
36
+ /** `authorizationUrl` with the account parameters of `mode` (`connect`,
37
+ * `reauth`, `switch`); a repair (`reauth`) adds nothing. A `prompt` the
38
+ * request already carries (the SDK's `consent` for offline access) is kept. */
39
+ export function withAccountParams(authorizationUrl, mode = 'connect') {
40
+ const target = new URL(authorizationUrl);
41
+ const table = mode === 'switch' ? SWITCH_PARAMS : mode === 'connect' ? CONNECT_PARAMS : {};
42
+ const params = table[target.host.toLowerCase()];
43
+ if (!params) return target;
44
+ for (const [key, value] of Object.entries(params)) {
45
+ if (key !== 'prompt') {
46
+ target.searchParams.set(key, value);
47
+ continue;
48
+ }
49
+ const values = new Set((target.searchParams.get('prompt') || '').split(' ').filter(Boolean));
50
+ values.add(value);
51
+ target.searchParams.set('prompt', [...values].join(' '));
52
+ }
53
+ return target;
54
+ }
55
+
56
+ /** Whether 계정 바꾸기 can make the provider of this authorization endpoint
57
+ * show its choice (otherwise the user switches at the provider first). */
58
+ export function canForceAccountChoice(authorizationEndpoint) {
59
+ return Object.hasOwn(SWITCH_PARAMS, hostOf(authorizationEndpoint));
60
+ }
61
+
62
+ const text = (value) => (typeof value === 'string' && value.trim() ? value.trim() : '');
63
+
64
+ function idTokenClaims(idToken) {
65
+ const payload = typeof idToken === 'string' ? idToken.split('.')[1] : '';
66
+ if (!payload) return {};
67
+ try {
68
+ const claims = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
69
+ return claims && typeof claims === 'object' ? claims : {};
70
+ } catch {
71
+ return {};
72
+ }
73
+ }
74
+
75
+ /** `{ account, workspace }` from a token endpoint response: the OIDC id_token
76
+ * claims and the identity fields providers return beside the tokens (Notion's
77
+ * workspace and owner, Slack's team, a `user` / `organization` object). Null
78
+ * when the response names nobody: an identity is never guessed. */
79
+ export function identityFromTokenResponse(body) {
80
+ if (!body || typeof body !== 'object') return null;
81
+ const claims = idTokenClaims(body.id_token);
82
+ const user = body.user && typeof body.user === 'object' ? body.user : {};
83
+ const person = body.owner?.user?.person;
84
+ const account =
85
+ text(claims.email) ||
86
+ text(claims.preferred_username) ||
87
+ text(body.email) ||
88
+ text(user.email) ||
89
+ text(user.login) ||
90
+ text(user.username) ||
91
+ text(person?.email) ||
92
+ text(body.owner?.user?.name);
93
+ const workspace =
94
+ text(body.workspace_name) || text(body.team?.name) || text(body.organization?.name) || text(body.org?.name);
95
+ if (!account && !workspace) return null;
96
+ return { account, workspace };
97
+ }
@@ -70,8 +70,7 @@ export function answerOAuthCallback(req, res, { redirectUrl, state, finish, serv
70
70
  });
71
71
  res.end(html);
72
72
  };
73
- const invalid = () =>
74
- page(400, oauthErrorHtml('This sign-in link is invalid or has expired.', 'Start the sign-in again from Mixdog.'));
73
+ const invalid = () => page(400, oauthErrorHtml('This sign-in link is invalid or has expired.'));
75
74
  let target;
76
75
  try {
77
76
  target = new URL(req.url || '/', redirectUrl);
@@ -94,7 +93,7 @@ export function answerOAuthCallback(req, res, { redirectUrl, state, finish, serv
94
93
  const server = serverName || 'the MCP server';
95
94
  if (target.searchParams.has('error')) {
96
95
  // The authorization server's error text is never reflected.
97
- page(400, oauthErrorHtml(`Sign-in to ${server} was not completed.`, 'Start the sign-in again from Mixdog.'));
96
+ page(400, oauthErrorHtml(`Sign-in to ${server} was not completed.`));
98
97
  finish(undefined);
99
98
  return;
100
99
  }
@@ -0,0 +1,49 @@
1
+ // A pre-registered OAuth client for an MCP server, used as static client
2
+ // information in place of dynamic client registration:
3
+ // - the user's own (custom connector → 고급 설정 → 앱 번호/비밀값): the id
4
+ // in the server config (`oauthClientId`, not a secret), the secret only in
5
+ // the keychain under mcpOAuthClientSecretAccount(url);
6
+ // - else the catalog connector's (connector-catalog.mjs `oauthClient`), with
7
+ // its scopes and the provider's offline-access parameter.
8
+ import { createHash } from 'node:crypto';
9
+ import keychain from '../../../../lib/keychain-cjs.cjs';
10
+ import { connectorById, connectorOfServer } from '../../../shared/connector-catalog.mjs';
11
+ import { GOOGLE_AUTH_PARAMS, googleOAuthClient } from '../../../shared/google-oauth-client.mjs';
12
+ import { normalizeMcpTransportUrl } from './client-config.mjs';
13
+
14
+ const BUILT_IN_CLIENTS = {
15
+ google: () => {
16
+ const client = googleOAuthClient();
17
+ return client && { ...client, authParams: GOOGLE_AUTH_PARAMS };
18
+ },
19
+ };
20
+
21
+ /** The keychain account of the user's client secret for one server URL. */
22
+ export function mcpOAuthClientSecretAccount(url) {
23
+ const key = normalizeMcpTransportUrl(String(url));
24
+ return `mcp-oauth-client.${createHash('sha256').update(key).digest('hex')}`;
25
+ }
26
+
27
+ /** Store (or, with '', delete) the user's client secret for server `url`. */
28
+ export async function saveMcpOAuthClientSecret(url, secret, credentialStore = keychain) {
29
+ const account = mcpOAuthClientSecretAccount(url);
30
+ if (secret) await credentialStore.setSecretAsync(account, secret);
31
+ else await credentialStore.deleteSecretAsync(account);
32
+ }
33
+
34
+ /**
35
+ * `{ clientId, clientSecret?, secretAccount?, scopes?, authParams? }` for
36
+ * server config `cfg` at `url`, or null to register a client dynamically.
37
+ */
38
+ export function mcpOAuthClientFor(cfg, url) {
39
+ const backend = connectorById(connectorOfServer(cfg))?.backend;
40
+ const catalog = backend?.kind === 'oauth' ? backend : null;
41
+ const scopes = catalog?.scopes ? [...catalog.scopes] : undefined;
42
+ const own = typeof cfg?.oauthClientId === 'string' ? cfg.oauthClientId.trim() : '';
43
+ if (own) {
44
+ const authParams = catalog?.oauthClient === 'google' ? GOOGLE_AUTH_PARAMS : undefined;
45
+ return { clientId: own, secretAccount: mcpOAuthClientSecretAccount(url), scopes, authParams };
46
+ }
47
+ const builtIn = catalog?.oauthClient ? BUILT_IN_CLIENTS[catalog.oauthClient]?.() : null;
48
+ return builtIn ? { ...builtIn, scopes } : null;
49
+ }