mixdog 1.3.4 → 1.4.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 (472) hide show
  1. package/LICENSES/oh-my-pi-MIT.txt +23 -0
  2. package/NOTICE.md +17 -2
  3. package/README.md +11 -0
  4. package/package.json +3 -11
  5. package/scripts/daemon-stub.mjs +1 -1
  6. package/scripts/embed-google-oauth-client.mjs +49 -0
  7. package/scripts/gate-local.mjs +4 -2
  8. package/scripts/lib/test-spawn-ledger.mjs +46 -1
  9. package/scripts/native-release-status.mjs +2 -2
  10. package/scripts/recall-history-cases.json +1 -1
  11. package/scripts/suite-health.mjs +10 -1
  12. package/scripts/test.mjs +12 -11
  13. package/src/agents/reviewer/AGENT.md +7 -11
  14. package/src/agents/reviewer/agent.json +1 -1
  15. package/src/defaults/shipped-history.json +25 -1
  16. package/src/defaults/skills/browser-use/SKILL.md +77 -17
  17. package/src/defaults/skills/code-tidy/SKILL.md +142 -270
  18. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +4 -4
  19. package/src/defaults/skills/code-tidy/references/cleanup-report.md +31 -76
  20. package/src/defaults/skills/code-tidy/references/dead-code.md +6 -12
  21. package/src/defaults/skills/computer-use/SKILL.md +33 -9
  22. package/src/defaults/skills/setup/SKILL.md +29 -2
  23. package/src/defaults/skills/setup/references/actions.md +117 -28
  24. package/src/defaults/skills/setup/references/surfaces.md +77 -35
  25. package/src/defaults/skills/setup-guide/SKILL.md +3 -3
  26. package/src/defaults/skills/visualize/SKILL.md +2 -1
  27. package/src/lib/keychain-cjs.cjs +359 -29
  28. package/src/lib/rules-builder.cjs +10 -8
  29. package/src/rules/agent/AGENT.md +6 -6
  30. package/src/rules/agent-mode/AGENT_MODE.md +10 -2
  31. package/src/rules/agent-mode/MIXY.md +12 -0
  32. package/src/rules/lead/LEAD.md +18 -20
  33. package/src/rules/shared/05-tool-calls.md +5 -6
  34. package/src/rules/shared/10-tool-workflow.md +2 -0
  35. package/src/rules/shared/60-verification.md +7 -5
  36. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -3
  37. package/src/runtime/agent/orchestrator/agent-runtime/heartbeat-completion.mjs +193 -0
  38. package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +3 -1
  39. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +3 -1
  40. package/src/runtime/agent/orchestrator/config.mjs +34 -0
  41. package/src/runtime/agent/orchestrator/context/collect.mjs +3 -0
  42. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +72 -67
  43. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +54 -1
  44. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -3
  45. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +177 -73
  46. package/src/runtime/agent/orchestrator/mcp/client.mjs +25 -2
  47. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +31 -2
  48. package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +16 -3
  49. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +42 -7
  50. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +21 -2
  51. package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +23 -0
  52. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +11 -1
  53. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +230 -137
  54. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +64 -7
  55. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +18 -5
  56. package/src/runtime/agent/orchestrator/providers/anthropic-cache-warm.mjs +165 -0
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  58. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +4 -1
  59. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +14 -14
  60. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +160 -69
  61. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +25 -3
  62. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +64 -33
  63. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +135 -87
  64. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +52 -8
  65. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +10 -1
  66. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +22 -6
  67. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +15 -4
  68. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +17 -2
  69. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +142 -104
  70. package/src/runtime/agent/orchestrator/providers/cache-warm-admission.mjs +90 -0
  71. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  72. package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +10 -4
  73. package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +10 -217
  74. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +25 -9
  75. package/src/runtime/agent/orchestrator/providers/cursor.mjs +22 -6
  76. package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +409 -0
  77. package/src/runtime/agent/orchestrator/providers/devin-wire-families.mjs +187 -0
  78. package/src/runtime/agent/orchestrator/providers/devin-wire-identity.mjs +342 -0
  79. package/src/runtime/agent/orchestrator/providers/devin-wire-models.mjs +541 -0
  80. package/src/runtime/agent/orchestrator/providers/devin-wire-protobuf.mjs +305 -0
  81. package/src/runtime/agent/orchestrator/providers/devin-wire-request.mjs +558 -0
  82. package/src/runtime/agent/orchestrator/providers/devin-wire-schema.mjs +880 -0
  83. package/src/runtime/agent/orchestrator/providers/devin-wire-session.mjs +190 -0
  84. package/src/runtime/agent/orchestrator/providers/devin-wire-tool-args.mjs +351 -0
  85. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +560 -0
  86. package/src/runtime/agent/orchestrator/providers/devin.mjs +491 -0
  87. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +3 -0
  88. package/src/runtime/agent/orchestrator/providers/gemini-output-limit.mjs +46 -0
  89. package/src/runtime/agent/orchestrator/providers/gemini-reasoning-relay.mjs +30 -0
  90. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +7 -11
  91. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +61 -22
  92. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -0
  93. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -4
  94. package/src/runtime/agent/orchestrator/providers/gemini.mjs +25 -2
  95. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  96. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +96 -5
  97. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +17 -3
  98. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +23 -5
  99. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +52 -10
  100. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +8 -1
  101. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  102. package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +11 -3
  103. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +19 -1
  104. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +39 -3
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
  107. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +6 -1
  108. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -9
  109. package/src/runtime/agent/orchestrator/providers/openai-ws-send/send-context.mjs +22 -3
  110. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +43 -5
  111. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/handshake-failure.mjs +3 -1
  112. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure.mjs +6 -3
  113. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +1 -0
  114. package/src/runtime/agent/orchestrator/providers/provider-availability.mjs +19 -0
  115. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +65 -1
  116. package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +38 -12
  117. package/src/runtime/agent/orchestrator/providers/registry.mjs +43 -3
  118. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +39 -7
  119. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +6 -0
  120. package/src/runtime/agent/orchestrator/providers/wire-protobuf-codec.mjs +368 -0
  121. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +12 -7
  122. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
  123. package/src/runtime/agent/orchestrator/runtime-core/compaction-config-keys.mjs +62 -0
  124. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +37 -16
  125. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +100 -23
  126. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +1 -1
  127. package/src/runtime/agent/orchestrator/runtime-core/reaction-tool-defs.mjs +133 -0
  128. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -0
  129. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +19 -4
  130. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  131. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +125 -30
  132. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +23 -5
  133. package/src/runtime/agent/orchestrator/runtime-core/tool-profile.mjs +35 -10
  134. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +57 -10
  135. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +9 -6
  136. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -1
  137. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +18 -0
  138. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +0 -3
  139. package/src/runtime/agent/orchestrator/session/agent-room.mjs +10 -4
  140. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +792 -0
  141. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +148 -25
  142. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +475 -58
  143. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +42 -17
  144. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +8 -0
  145. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +47 -9
  146. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +6 -2
  147. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -4
  148. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +14 -2
  149. package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +8 -0
  150. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +18 -22
  151. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +32 -3
  152. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +11 -2
  153. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -1
  154. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +51 -3
  155. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +17 -3
  156. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -2
  157. package/src/runtime/agent/orchestrator/session/loop/tool-exec/after-hook.mjs +2 -1
  158. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +14 -4
  159. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
  160. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +16 -1
  161. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +10 -0
  162. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -10
  163. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +23 -5
  164. package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +60 -0
  165. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -2
  166. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +19 -1
  167. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +2 -0
  168. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +133 -90
  169. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +14 -0
  170. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -1
  171. package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +31 -4
  172. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -18
  173. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +23 -13
  174. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +11 -2
  175. package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +13 -0
  176. package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +129 -0
  177. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +20 -2
  178. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +2 -0
  179. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +8 -0
  180. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +1 -1
  181. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +13 -0
  182. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -2
  183. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +102 -15
  184. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +18 -5
  185. package/src/runtime/agent/orchestrator/session/message-ids.mjs +5 -3
  186. package/src/runtime/agent/orchestrator/session/message-reactions.mjs +89 -0
  187. package/src/runtime/agent/orchestrator/session/message-reactions.test-support.mjs +75 -0
  188. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +19 -8
  189. package/src/runtime/agent/orchestrator/session/prompt-cache-idle-gaps.mjs +113 -0
  190. package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +127 -0
  191. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +0 -2
  192. package/src/runtime/agent/orchestrator/session/routine-result.mjs +47 -11
  193. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +20 -2
  194. package/src/runtime/agent/orchestrator/session/sensitive-projection.mjs +100 -0
  195. package/src/runtime/agent/orchestrator/session/sensitive-values.mjs +257 -0
  196. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +20 -7
  197. package/src/runtime/agent/orchestrator/session/side-question.mjs +18 -0
  198. package/src/runtime/agent/orchestrator/session/skill-update-notice.mjs +60 -0
  199. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +6 -3
  200. package/src/runtime/agent/orchestrator/session/store/save-delta.mjs +9 -5
  201. package/src/runtime/agent/orchestrator/session/store/serialize.mjs +9 -8
  202. package/src/runtime/agent/orchestrator/session/store/session-persistence-events.mjs +29 -0
  203. package/src/runtime/agent/orchestrator/session/store-agent-worker-header-cache.mjs +85 -0
  204. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +133 -18
  205. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +12 -2
  206. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -14
  207. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +84 -3
  208. package/src/runtime/agent/orchestrator/session/stored-attachment-history.mjs +32 -0
  209. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +11 -5
  210. package/src/runtime/agent/orchestrator/session/thinking-binding.mjs +108 -0
  211. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +2 -2
  212. package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +2 -1
  213. package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +4 -0
  214. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +5 -0
  215. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +25 -2
  216. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +9 -7
  217. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-text.mjs +2 -2
  218. package/src/runtime/agent/orchestrator/session/turn-failure.mjs +5 -1
  219. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +5 -2
  220. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +12 -1
  221. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +240 -0
  222. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
  223. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/shell-cwd.mjs +38 -0
  224. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +25 -5
  225. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +65 -7
  226. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +7 -1
  227. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +36 -13
  228. package/src/runtime/browser-bridge/action-schema.mjs +13 -2
  229. package/src/runtime/browser-bridge/client.mjs +73 -8
  230. package/src/runtime/browser-bridge/input-fields.mjs +8 -9
  231. package/src/runtime/browser-bridge/mail-links.mjs +107 -0
  232. package/src/runtime/browser-bridge/tool-defs.mjs +1 -1
  233. package/src/runtime/channels/lib/config.mjs +6 -61
  234. package/src/runtime/channels/lib/crash-log.mjs +1 -1
  235. package/src/runtime/channels/lib/event-pipeline.mjs +4 -19
  236. package/src/runtime/channels/lib/event-queue.mjs +3 -18
  237. package/src/runtime/channels/lib/owned-runtime/automation-services.mjs +2 -2
  238. package/src/runtime/channels/lib/owned-runtime/automation-start.mjs +3 -4
  239. package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +4 -59
  240. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +33 -115
  241. package/src/runtime/channels/lib/owned-runtime/owned-stop.mjs +5 -22
  242. package/src/runtime/channels/lib/owned-runtime/state.mjs +2 -8
  243. package/src/runtime/channels/lib/owned-runtime.mjs +6 -23
  244. package/src/runtime/channels/lib/parent-bridge.mjs +2 -2
  245. package/src/runtime/channels/lib/runtime-paths.mjs +4 -43
  246. package/src/runtime/channels/lib/schedule-errors.mjs +51 -0
  247. package/src/runtime/channels/lib/scheduler.mjs +135 -154
  248. package/src/runtime/channels/lib/schedules-db.mjs +83 -11
  249. package/src/runtime/channels/lib/status-snapshot.mjs +1 -1
  250. package/src/runtime/channels/lib/tool-dispatch.mjs +14 -2
  251. package/src/runtime/channels/lib/webhooks-db.mjs +5 -9
  252. package/src/runtime/channels/lib/worker-bootstrap.mjs +0 -1
  253. package/src/runtime/channels/lib/worker-ipc.mjs +0 -1
  254. package/src/runtime/channels/lib/worker-main.mjs +19 -34
  255. package/src/runtime/computer-bridge/action-schema.mjs +42 -1
  256. package/src/runtime/computer-bridge/actions.mjs +9 -1
  257. package/src/runtime/computer-bridge/client.mjs +180 -8
  258. package/src/runtime/computer-bridge/core-actions.mjs +2 -2
  259. package/src/runtime/computer-bridge/error-recovery.mjs +27 -2
  260. package/src/runtime/computer-bridge/linux-readiness.mjs +65 -23
  261. package/src/runtime/computer-bridge/pending-continuation.mjs +1 -1
  262. package/src/runtime/connectors/connector-accounts-mcp.mjs +230 -0
  263. package/src/runtime/connectors/gmail-app-password.mjs +172 -39
  264. package/src/runtime/connectors/google-mcp.mjs +10 -12
  265. package/src/runtime/memory/lib/pg/adapter.mjs +77 -70
  266. package/src/runtime/memory/lib/pg/supervisor.mjs +24 -12
  267. package/src/runtime/memory/lib/runtime-fetcher.mjs +23 -5
  268. package/src/runtime/office/com/com-adapter.mjs +17 -1
  269. package/src/runtime/office/com/office-com-host.ps1 +51 -1
  270. package/src/runtime/office/pdf/cfb-fixture.mjs +328 -0
  271. package/src/runtime/office/pdf/cfb-protection.mjs +311 -0
  272. package/src/runtime/office/pdf/document-preview.mjs +326 -8
  273. package/src/runtime/office/pdf/ooxml-crypto.mjs +469 -0
  274. package/src/runtime/office/pdf/ooxml-key-worker.mjs +14 -0
  275. package/src/runtime/office/portable/portable-soffice.mjs +45 -5
  276. package/src/runtime/session-search/session-search-db.mjs +73 -9
  277. package/src/runtime/session-search/session-search-ingest.mjs +110 -20
  278. package/src/runtime/shared/abort-race.mjs +89 -0
  279. package/src/runtime/shared/agent-avatar.d.mts +0 -1
  280. package/src/runtime/shared/agent-kind.mjs +4 -3
  281. package/src/runtime/shared/bot-hops.mjs +62 -2
  282. package/src/runtime/shared/bounded-download.mjs +8 -3
  283. package/src/runtime/shared/config.mjs +51 -35
  284. package/src/runtime/shared/connector-accounts.mjs +20 -10
  285. package/src/runtime/shared/connector-catalog.d.mts +33 -2
  286. package/src/runtime/shared/connector-catalog.mjs +166 -8
  287. package/src/runtime/shared/connector-triggers.mjs +22 -19
  288. package/src/runtime/shared/defaults-separation.mjs +2 -14
  289. package/src/runtime/shared/delegated-approval-host.mjs +103 -11
  290. package/src/runtime/shared/developer-options.mjs +32 -1
  291. package/src/runtime/shared/err-text.mjs +24 -15
  292. package/src/runtime/shared/file-permissions.d.mts +0 -8
  293. package/src/runtime/shared/file-permissions.mjs +0 -11
  294. package/src/runtime/shared/git-direct.test-support.mjs +19 -0
  295. package/src/runtime/shared/google-oauth-client.generated.json +4 -0
  296. package/src/runtime/shared/google-oauth-client.mjs +34 -9
  297. package/src/runtime/shared/heartbeat-interval.d.mts +4 -0
  298. package/src/runtime/shared/heartbeat-interval.mjs +19 -0
  299. package/src/runtime/shared/idle-auto-clear-gate.mjs +104 -0
  300. package/src/runtime/shared/llm/anthropic-betas.mjs +7 -0
  301. package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +12 -0
  302. package/src/runtime/shared/llm/provider-catalog-registry.mjs +1 -0
  303. package/src/runtime/shared/llm/usage-rollup.mjs +1 -1
  304. package/src/runtime/shared/llm/well-formed.mjs +66 -0
  305. package/src/runtime/shared/mixy-presence.mjs +22 -0
  306. package/src/runtime/shared/orchestration.d.mts +2 -0
  307. package/src/runtime/shared/pg-schema-db.mjs +27 -16
  308. package/src/runtime/shared/profile-config.cjs +1 -1
  309. package/src/runtime/shared/prompt-origin.d.mts +4 -0
  310. package/src/runtime/shared/prompt-origin.mjs +21 -0
  311. package/src/runtime/shared/provider-accounts.mjs +231 -59
  312. package/src/runtime/shared/provider-api-key.mjs +19 -8
  313. package/src/runtime/shared/provider-display-name.mjs +29 -0
  314. package/src/runtime/shared/provider-timeout-phase.mjs +2 -0
  315. package/src/runtime/shared/routine-trigger.d.mts +2 -0
  316. package/src/runtime/shared/routine-trigger.mjs +52 -0
  317. package/src/runtime/shared/sensitive-path-rules.d.mts +2 -10
  318. package/src/runtime/shared/sensitive-path-rules.mjs +19 -41
  319. package/src/runtime/shared/session-activity-signal.mjs +34 -1
  320. package/src/runtime/shared/session-sidebar-host.d.mts +1 -1
  321. package/src/runtime/shared/session-sidebar-host.mjs +7 -3
  322. package/src/runtime/shared/session-turn-prelude.mjs +18 -0
  323. package/src/runtime/shared/tool-execution-contract.mjs +2 -0
  324. package/src/runtime/shared/tool-result-summary.mjs +1 -14
  325. package/src/runtime/shared/transcript-metadata.mjs +38 -0
  326. package/src/runtime/shared/user-data-guard.mjs +30 -2
  327. package/src/runtime/shared/worktrees/activity-gate.mjs +12 -5
  328. package/src/runtime/shared/worktrees/test-support.mjs +9 -4
  329. package/src/runtime/terminal-bridge/client.mjs +10 -1
  330. package/src/runtime/visualize-bridge/client.mjs +27 -8
  331. package/src/runtime/visualize-bridge/tool-defs.mjs +22 -8
  332. package/src/runtime/visualize-bridge/tool-meta.d.mts +5 -0
  333. package/src/runtime/visualize-bridge/tool-meta.mjs +22 -0
  334. package/src/runtime/web-search/lib/fetch-pipeline.mjs +2 -2
  335. package/src/session-runtime/agent-home-delivery.mjs +486 -179
  336. package/src/session-runtime/agent-room-delivery.mjs +28 -1
  337. package/src/session-runtime/agent-task-sessions.mjs +561 -199
  338. package/src/session-runtime/boot/apis.mjs +9 -0
  339. package/src/session-runtime/boot/begin.mjs +3 -2
  340. package/src/session-runtime/boot/infrastructure.mjs +4 -0
  341. package/src/session-runtime/boot/providers.mjs +3 -0
  342. package/src/session-runtime/boot/tools.mjs +8 -1
  343. package/src/session-runtime/bot-chain.mjs +32 -0
  344. package/src/session-runtime/bot-status.mjs +7 -4
  345. package/src/session-runtime/channel-config-api.mjs +63 -17
  346. package/src/session-runtime/config-lifecycle.mjs +6 -0
  347. package/src/session-runtime/connector-merge-migration.mjs +305 -0
  348. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +132 -28
  349. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +2 -0
  350. package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +45 -0
  351. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +72 -5
  352. package/src/session-runtime/internal-tool-executor.mjs +4 -0
  353. package/src/session-runtime/lifecycle/inheritance.mjs +16 -3
  354. package/src/session-runtime/lifecycle/session-catalog.mjs +3 -1
  355. package/src/session-runtime/lifecycle/teardown/runtime-stops.mjs +50 -4
  356. package/src/session-runtime/lifecycle/teardown.mjs +23 -5
  357. package/src/session-runtime/mcp-glue/server-catalog.mjs +61 -3
  358. package/src/session-runtime/mcp-oauth-login.mjs +5 -3
  359. package/src/session-runtime/mixy-heartbeat.mjs +927 -0
  360. package/src/session-runtime/model-route/route-tuning.mjs +10 -6
  361. package/src/session-runtime/notification-bus.mjs +31 -117
  362. package/src/session-runtime/notification-completion-queue.mjs +54 -0
  363. package/src/session-runtime/notification-listeners.mjs +76 -0
  364. package/src/session-runtime/owned-automation-run.mjs +35 -13
  365. package/src/session-runtime/prewarm/channel-start.mjs +3 -7
  366. package/src/session-runtime/provider-auth/login.mjs +7 -0
  367. package/src/session-runtime/provider-models/row-hydration.mjs +13 -2
  368. package/src/session-runtime/resource-mcp-api.mjs +143 -224
  369. package/src/session-runtime/resource-mcp-connector-accounts.mjs +163 -0
  370. package/src/session-runtime/resource-mcp-oauth-api.mjs +217 -0
  371. package/src/session-runtime/resource-mcp-secrets.mjs +82 -0
  372. package/src/session-runtime/resource-mcp-servers.mjs +78 -0
  373. package/src/session-runtime/resource-mcp-store.mjs +49 -0
  374. package/src/session-runtime/routine-home-turn.mjs +33 -6
  375. package/src/session-runtime/routine-owner.mjs +40 -9
  376. package/src/session-runtime/schedule-session-run.mjs +7 -3
  377. package/src/session-runtime/services/agent-tool/close-flow.mjs +3 -0
  378. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +8 -12
  379. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +7 -2
  380. package/src/session-runtime/services/agent-tool/tag-registry.mjs +1 -0
  381. package/src/session-runtime/services/agent-tool/worker-index/awaiting-user-sync.mjs +51 -0
  382. package/src/session-runtime/services/agent-tool/worker-index/disposal.mjs +55 -0
  383. package/src/session-runtime/services/agent-tool/worker-index/released-store.mjs +123 -0
  384. package/src/session-runtime/services/agent-tool/worker-index/restart-settlement.mjs +112 -0
  385. package/src/session-runtime/services/agent-tool/worker-index/row-reads.mjs +36 -0
  386. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -0
  387. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +17 -4
  388. package/src/session-runtime/services/agent-tool/worker-index/row-writes.mjs +58 -0
  389. package/src/session-runtime/services/agent-tool/worker-index.mjs +41 -241
  390. package/src/session-runtime/services/agent-tool/worker-rows.mjs +3 -0
  391. package/src/session-runtime/services/channel-admin.mjs +73 -31
  392. package/src/session-runtime/services/channel-session-router.mjs +4 -2
  393. package/src/session-runtime/services/channel-worker.mjs +1 -1
  394. package/src/session-runtime/services/provider-admin.mjs +128 -25
  395. package/src/session-runtime/services/schedule-access.mjs +14 -0
  396. package/src/session-runtime/services/seeds.mjs +0 -28
  397. package/src/session-runtime/services/usage-dashboard-model.mjs +1 -0
  398. package/src/session-runtime/session-compaction-sync.mjs +27 -0
  399. package/src/session-runtime/session-create/session-options.mjs +8 -4
  400. package/src/session-runtime/settings-builtin-tools-api.mjs +11 -0
  401. package/src/session-runtime/settings-compaction-api.mjs +2 -40
  402. package/src/session-runtime/settings-system-api.mjs +28 -1
  403. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +65 -0
  404. package/src/session-runtime/setup-tool/executor.mjs +187 -29
  405. package/src/session-runtime/setup-tool/extended-actions.mjs +33 -2
  406. package/src/session-runtime/setup-tool/session-route-actions.mjs +152 -0
  407. package/src/session-runtime/setup-tool/settings-contract.mjs +68 -6
  408. package/src/session-runtime/setup-tool/tool-defs.mjs +92 -6
  409. package/src/session-runtime/task-session-route.mjs +99 -0
  410. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +17 -13
  411. package/src/session-runtime/tool-policy/policy-stage.mjs +21 -4
  412. package/src/session-runtime/tool-policy/surface-helpers.mjs +2 -3
  413. package/src/session-runtime/tool-surface.mjs +4 -0
  414. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
  415. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +98 -21
  416. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +4 -0
  417. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -1
  418. package/src/session-runtime/workspace/transitions.mjs +10 -2
  419. package/src/standalone/channel-transport/client-registry/client-records.mjs +65 -0
  420. package/src/standalone/channel-transport/client-registry/client-registration.mjs +118 -0
  421. package/src/standalone/channel-transport/client-registry.mjs +24 -163
  422. package/src/standalone/channel-transport.mjs +1 -1
  423. package/src/standalone/daemon-channels-loader.mjs +10 -8
  424. package/src/standalone/daemon-session-control/list-page.mjs +72 -0
  425. package/src/standalone/daemon-session-control/request.mjs +51 -0
  426. package/src/standalone/daemon-session-control/room-host.mjs +57 -0
  427. package/src/standalone/daemon-session-control/route-control.mjs +48 -0
  428. package/src/standalone/daemon-session-control/send-turn.mjs +60 -0
  429. package/src/standalone/daemon-session-control/worktree-control.mjs +40 -0
  430. package/src/standalone/daemon-session-control.mjs +161 -157
  431. package/src/standalone/daemon.mjs +33 -2
  432. package/src/standalone/session-client.mjs +7 -3
  433. package/src/standalone/session-protocol.mjs +10 -0
  434. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +6 -2
  435. package/src/standalone/session-service/agent-tree/agent-child.mjs +1 -0
  436. package/src/standalone/session-service/quota-resume.mjs +3 -0
  437. package/src/standalone/session-service/retention/busy.mjs +65 -1
  438. package/src/standalone/session-service/session-calls/session-action.mjs +23 -2
  439. package/src/standalone/session-service/session-calls/session-turns.mjs +6 -0
  440. package/src/standalone/session-service/session-calls/session-views.mjs +8 -0
  441. package/src/standalone/session-service.mjs +13 -0
  442. package/src/standalone/session-tool-output.mjs +21 -10
  443. package/src/tui/app/model-options.mjs +4 -27
  444. package/src/tui/dist/index.mjs +81 -65
  445. package/src/tui/session/agent-job-feed/model-notification.mjs +3 -0
  446. package/src/tui/session/initial-state.mjs +3 -0
  447. package/src/tui/session/notice-surface.mjs +23 -1
  448. package/src/tui/session/queue-helpers.mjs +3 -42
  449. package/src/tui/session/session-api/integrations.mjs +1 -0
  450. package/src/tui/session/session-api/lifecycle.mjs +8 -0
  451. package/src/tui/session/session-api/routes-agent-definitions.mjs +14 -2
  452. package/src/tui/session/session-api/routes-write.mjs +5 -1
  453. package/src/tui/session/session-api/routes.mjs +1 -0
  454. package/src/tui/session/session-api/settings.mjs +30 -2
  455. package/src/tui/session/session-flow/auto-clear.mjs +4 -33
  456. package/src/tui/session/session-flow/drain-closed-queue.mjs +38 -0
  457. package/src/tui/session/session-flow/drain-enqueue.mjs +86 -0
  458. package/src/tui/session/session-flow/drain-retry.mjs +35 -0
  459. package/src/tui/session/session-flow/drain.mjs +118 -149
  460. package/src/tui/session/session-flow/queue/entry-shape.mjs +5 -0
  461. package/src/tui/session/session-flow/queue/take-entries.mjs +5 -2
  462. package/src/tui/session/tool-approval-delegation.mjs +64 -0
  463. package/src/tui/session/tool-approval-request.mjs +39 -0
  464. package/src/tui/session/tool-approval-settle.mjs +62 -0
  465. package/src/tui/session/tool-approval.mjs +69 -115
  466. package/src/tui/session/tui-steering-persist.mjs +8 -1
  467. package/src/tui/session/turn-stream.mjs +36 -3
  468. package/src/tui/session/turn.mjs +23 -2
  469. package/src/vendor/statusline/bin/statusline-route.mjs +1 -219
  470. package/src/vendor/statusline/src/gateway/route-meta.mjs +35 -13
  471. package/scripts/code-graph-description-contract.mjs +0 -188
  472. package/src/runtime/channels/lib/owned-runtime/ownership-refresh.mjs +0 -36
@@ -304,6 +304,31 @@ export function skillOutsideScopeError(name) {
304
304
  return `Error: skill "${name}" is not available in this project (its project scope excludes the current folder)`;
305
305
  }
306
306
 
307
+ /** Why the Skill tool refuses `name` at `cwd` now (missing feature, switched
308
+ * off, outside the project scope), or null when nothing refuses it. `config`
309
+ * and `skills` let a batch of checks share one config read and one scan. */
310
+ export function skillLoadRefusal(name, cwd, config = null, skills = null) {
311
+ const missing = skillMissingFeature(name, config, skills);
312
+ if (missing) {
313
+ return `Error: skill "${name}" needs the ${missing} built-in feature, which is not installed or is switched off in Extensions → Plugin → Built-in`;
314
+ }
315
+ if (isSkillDisabled(name, config, skills)) return `Error: skill "${name}" is disabled`;
316
+ if (skillOutsideScope(name, cwd, config, skills)) return skillOutsideScopeError(name);
317
+ return null;
318
+ }
319
+
320
+ /**
321
+ * What the Skill tool would deliver for `name` at `cwd` now: the injected
322
+ * `<skill>` body and the resource it came from, or null when the skill no
323
+ * longer loads there (refused by skillLoadRefusal, or removed).
324
+ */
325
+ export function currentSkillDelivery(name, cwd, config = null, skills = null) {
326
+ if (skillLoadRefusal(name, cwd, config, skills)) return null;
327
+ const resource = loadSkillResource(name, cwd);
328
+ if (!resource) return null;
329
+ return { body: buildSkillResultEnvelope(name, resource.content, resource.dir), resource };
330
+ }
331
+
307
332
  /**
308
333
  * Skills a session may see: not switched off, feature available, and — when
309
334
  * `cwd` is given — inside the skill's (or its plugin's) project scope. Without
@@ -348,11 +373,16 @@ const _skillsCache = new Map();
348
373
  // every plain-file gate path are stat'ed on every call as before.
349
374
  const _ROOT_WATCH_LIMIT = 64;
350
375
  const _rootWatches = new Map(); // root -> { watcher, dirty, text, settled }
376
+ // Bumped by every closed root watch; part of each cached SKILL.md text stamp.
377
+ let _skillTextGeneration = 0;
351
378
 
352
379
  function _closeRootWatch(root) {
353
380
  const watch = _rootWatches.get(root);
354
381
  if (!watch) return;
355
382
  _rootWatches.delete(root);
383
+ // Any change under a skill root (or a lost watcher, or an invalidation)
384
+ // makes the next load re-read SKILL.md text, whatever the file stamps say.
385
+ _skillTextGeneration += 1;
356
386
  try {
357
387
  watch.watcher.close();
358
388
  } catch {
@@ -452,6 +482,26 @@ export function skillExecutionDir(dir, source) {
452
482
  return String(dir).replace(/([\\/])runtime\.asar(?=[\\/]|$)/, '$1runtime.asar.unpacked');
453
483
  }
454
484
 
485
+ // SKILL.md text per catalog entry, with the stamp it was read at: the
486
+ // file's own mtime and size (any depth) plus the skills watcher generation
487
+ // (an edit that keeps both, e.g. inside one timestamp tick).
488
+ const _skillFileText = new WeakMap();
489
+
490
+ function skillFileText(skill) {
491
+ let stamp;
492
+ try {
493
+ const stat = statSync(skill.filePath);
494
+ stamp = `${_skillTextGeneration}:${stat.mtimeMs}:${stat.size}`;
495
+ } catch {
496
+ return null;
497
+ }
498
+ const cached = _skillFileText.get(skill);
499
+ if (cached?.stamp === stamp) return cached.text;
500
+ const text = readSafe(skill.filePath);
501
+ _skillFileText.set(skill, { stamp, text });
502
+ return text;
503
+ }
504
+
455
505
  /**
456
506
  * Load full skill content plus its on-disk directory (for base-dir + ${MIXDOG_SKILL_DIR};
457
507
  * ${MIXDOG_SKILL_NODE} is resolved at render time in buildSkillResultEnvelope).
@@ -462,7 +512,10 @@ export function loadSkillResource(name, cwd) {
462
512
  const skills = collectSkillsCached(cwd);
463
513
  const skill = skills.find((s) => s.name === skillName);
464
514
  if (!skill) return null;
465
- const content = readSafe(skill.filePath);
515
+ // The catalog's freshness signature does not see every nested SKILL.md, so
516
+ // the text follows the file itself: one stat per load, re-read only when
517
+ // that file's own mtime or size moved.
518
+ const content = skillFileText(skill);
466
519
  if (content == null) return null;
467
520
  try {
468
521
  return {
@@ -203,7 +203,7 @@ export function readProjectConnectorsFile(path) {
203
203
  });
204
204
  }
205
205
 
206
- export function projectConnectorPolicies(dataDir = resolvePluginData()) {
206
+ function projectConnectorPolicies(dataDir = resolvePluginData()) {
207
207
  return cachedRead(join(dataDir, PROJECT_CONNECTORS_FILE), readProjectConnectorsFile);
208
208
  }
209
209
 
@@ -219,7 +219,7 @@ export function connectorToolPermission(policy, toolOrName) {
219
219
  return policy?.tools?.[mcp.server]?.[mcp.tool] || connectorToolDefault(serverConnector(mcp.server), mcp.tool);
220
220
  }
221
221
 
222
- const PERMISSION_RANK = Object.freeze({ disabled: 0, needs_approval: 1, auto: 2 });
222
+ export const PERMISSION_RANK = Object.freeze({ disabled: 0, needs_approval: 1, auto: 2 });
223
223
  export const NO_WIDEN_MESSAGE = 'This change would widen what the bot may do without asking; make it on the desktop.';
224
224
 
225
225
  /** Whether replacing the stored policy `before` (a stored value, a parsed
@@ -229,7 +229,7 @@ export const NO_WIDEN_MESSAGE = 'This change would widen what the bot may do wit
229
229
  * default, which is `needs_approval` for a catalog connector's high-risk
230
230
  * tools and `auto` otherwise). A legacy allow list or an unreadable policy is
231
231
  * not compared: it counts as widening. */
232
- export function connectorPolicyWidens(before, after) {
232
+ function connectorPolicyWidens(before, after) {
233
233
  let old;
234
234
  try {
235
235
  old = before == null ? { off: [], tools: {} } : parseConnectorPolicy(before);
@@ -5,9 +5,16 @@
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';
8
+ import { mcpOAuthClientFor, mcpOAuthDisplayName } from './oauth-client.mjs';
9
9
  import { googleMcpService, withGoogleMcp } from '../../../connectors/google-mcp.mjs';
10
10
  import { gmailAppPasswordService, withGmailAppPasswordMcp } from '../../../connectors/gmail-app-password.mjs';
11
+ import { connectorAccountRequest, withConnectorAccountsMcp } from '../../../connectors/connector-accounts-mcp.mjs';
12
+ import {
13
+ connectorAccountsOf,
14
+ connectorLiveAccountServers,
15
+ gmailAppPasswordAccount,
16
+ } from '../../../shared/connector-catalog.mjs';
17
+ import { isMcpOAuthReauthenticationRequired } from './oauth.mjs';
11
18
  import {
12
19
  AUTO_DETECT_PORTS,
13
20
  expandEnvVars,
@@ -63,10 +70,65 @@ export function hasConfiguredMcpAuth(cfg) {
63
70
  return (
64
71
  Boolean(String(cfg.bearer_token_env_var || '').trim()) ||
65
72
  configured(cfg.headers) ||
66
- configured(cfg.env_http_headers)
73
+ configured(cfg.env_http_headers) ||
74
+ // A multi-account connector's server: each account authenticates itself.
75
+ connectorAccountsOf(cfg).length > 0
67
76
  );
68
77
  }
69
78
 
79
+ /** One account of a multi-account connector server (connector-catalog.mjs
80
+ * connectorAccountServer) as connector-accounts-mcp.mjs calls it: through
81
+ * the transport the account had as a server of its own — its app-password
82
+ * header, or its own non-interactive OAuth sign-in (refreshed as needed; a
83
+ * new sign-in goes through 계정 바꾸기, never through a tool call). */
84
+ function connectorAccountClient(serverName, accountCfg, oauthOptions) {
85
+ const base = { name: accountCfg.name, email: '', appPassword: false, close() {} };
86
+ let url;
87
+ try {
88
+ url = resolveMcpOAuthUrl(accountCfg);
89
+ if (hasConfiguredMcpAuth(accountCfg)) {
90
+ const email = gmailAppPasswordAccount(accountCfg);
91
+ const fetchImpl = gmailAppPasswordService(url) ? withGmailAppPasswordMcp() : fetch;
92
+ return {
93
+ ...base,
94
+ email,
95
+ appPassword: Boolean(email),
96
+ request: connectorAccountRequest(url, fetchImpl, resolveMcpHttpHeaders(accountCfg)),
97
+ };
98
+ }
99
+ const oauth = createMcpOAuth({
100
+ url,
101
+ serverName: mcpOAuthDisplayName(accountCfg, serverName),
102
+ oauthClient: mcpOAuthClientFor(accountCfg, url),
103
+ ...(oauthOptions?.fetchImpl ? { fetchImpl: oauthOptions.fetchImpl } : {}),
104
+ ...(googleMcpService(url) ? { fetchImpl: withGoogleMcp(oauthOptions?.fetchImpl) } : {}),
105
+ });
106
+ const send = connectorAccountRequest(url, (input, init) => oauth.fetch(input, init));
107
+ return {
108
+ ...base,
109
+ async request(message, options) {
110
+ try {
111
+ return await send(message, options);
112
+ } catch (error) {
113
+ const reason = scrubMcpConnectionMessage(error?.message || String(error), accountCfg);
114
+ const scrubbed = new Error(
115
+ isMcpOAuthReauthenticationRequired(error)
116
+ ? 'Sign-in required; sign this account in again in Connectors.'
117
+ : reason
118
+ );
119
+ if (isMcpOAuthReauthenticationRequired(error)) scrubbed.authRequired = true;
120
+ throw scrubbed;
121
+ }
122
+ },
123
+ close: () => oauth.close(),
124
+ };
125
+ } catch (error) {
126
+ // A broken account (no sign-in client, an invalid address) stays listed with why.
127
+ const failed = new Error(error?.message || String(error));
128
+ return { ...base, request: async () => Promise.reject(failed) };
129
+ }
130
+ }
131
+
70
132
  // The SDK's WebSocket transport opens its socket without a way to set request
71
133
  // headers. This subclass changes only socket creation: Node's WebSocket takes
72
134
  // `{ protocols, headers }`; the guard and event wiring match the stock start().
@@ -101,24 +163,115 @@ function createHeaderWebSocketTransport(Base, messageSchema, url, headers) {
101
163
  }
102
164
  })(url);
103
165
  }
166
+ // Unset variables a config references: a failure under `strictEnv`,
167
+ // otherwise warnings (names only), each logged.
168
+ function missingEnvWarnings(name, cfg, strictEnv, log) {
169
+ const warnings = [];
170
+ const missing = findMissingMcpEnvVars(cfg);
171
+ if (missing.length) {
172
+ const message = `missing environment variable(s): ${missing.join(', ')}`;
173
+ if (strictEnv === true) throw new Error(message);
174
+ warnings.push(message);
175
+ log(`[mcp-client] "${name}": ${message}\n`);
176
+ }
177
+ return warnings;
178
+ }
179
+
180
+ // A multi-account connector: one server, every account behind it; closing the
181
+ // transport closes every account client.
182
+ function connectorAccountsTransport({ name, cfg, Transport, url, accountServers, oauthOptions, log }) {
183
+ const accounts = accountServers.map((accountCfg) => connectorAccountClient(name, accountCfg, oauthOptions));
184
+ const transport = new Transport(new URL(url), {
185
+ fetch: withConnectorAccountsMcp({ connector: cfg.connector, accounts }),
186
+ });
187
+ const close = transport.close.bind(transport);
188
+ transport.close = async () => {
189
+ for (const account of accounts) account.close();
190
+ await close();
191
+ };
192
+ log(`[mcp-client] Connecting "${name}" with ${accounts.length} account(s)\n`);
193
+ return transport;
194
+ }
195
+
196
+ // A streamable HTTP or SSE server: its configured authentication, or else an
197
+ // OAuth sign-in the transport owns (and closes with it).
198
+ function remoteHttpTransport({ name, cfg, kind, Transport, url, oauthOptions, log }) {
199
+ const opts = requestInitFor(cfg);
200
+ // Explicit authentication remains authoritative (including custom API-key
201
+ // headers). Never forward those credentials to OAuth discovery endpoints.
202
+ let transport;
203
+ let oauth = null;
204
+ if (!hasConfiguredMcpAuth(cfg)) {
205
+ oauth = createMcpOAuth({
206
+ url,
207
+ serverName: mcpOAuthDisplayName(cfg, name),
208
+ oauthClient: mcpOAuthClientFor(cfg, url),
209
+ ...oauthOptions,
210
+ // A built-in Google connector is answered in-process.
211
+ ...(googleMcpService(url) ? { fetchImpl: withGoogleMcp(oauthOptions?.fetchImpl) } : {}),
212
+ // SSE advertises its POST endpoint on the authenticated event stream.
213
+ // No other path on the origin is allowed to receive this server's token.
214
+ isTransportEndpoint: (input) => kind === 'sse' && transport?._endpoint?.href === new URL(input).href,
215
+ });
216
+ }
217
+ // Gmail with an app password is answered in-process too (its account
218
+ // header is the configured authentication; the password is in the keychain).
219
+ const local = !oauth && gmailAppPasswordService(url) ? { ...opts, fetch: withGmailAppPasswordMcp() } : opts;
220
+ transport = new Transport(new URL(url), oauth ? { fetch: oauth.fetch } : local);
221
+ if (oauth) {
222
+ transport.oauth = oauth;
223
+ const close = transport.close.bind(transport);
224
+ transport.close = async () => {
225
+ oauth.close();
226
+ await close();
227
+ };
228
+ }
229
+ log(`[mcp-client] Connecting "${name}" via ${kind.toUpperCase()}: ${mcpUrlForLog(url)}\n`);
230
+ return transport;
231
+ }
232
+
233
+ function webSocketTransport(name, cfg, sdk, log) {
234
+ const { WebSocketClientTransport } = sdk;
235
+ const url = normalizeMcpTransportUrl(expandEnvVars(String(cfg.url ?? '')), 'ws');
236
+ const opts = requestInitFor(cfg);
237
+ log(`[mcp-client] Connecting "${name}" via WebSocket: ${mcpUrlForLog(url)}\n`);
238
+ return opts
239
+ ? createHeaderWebSocketTransport(
240
+ WebSocketClientTransport,
241
+ sdk.JSONRPCMessageSchema,
242
+ new URL(url),
243
+ opts.requestInit.headers
244
+ )
245
+ : new WebSocketClientTransport(new URL(url));
246
+ }
247
+
248
+ // A stdio child, its stderr logged and its bounded (scrubbed) tail kept.
249
+ function stdioTransport(name, cfg, StdioClientTransport, log) {
250
+ const transport = new StdioClientTransport({
251
+ command: expandEnvVars(String(cfg.command ?? '')),
252
+ args: Array.isArray(cfg.args) ? expandEnvVars(cfg.args) : cfg.args,
253
+ cwd: cfg.cwd == null ? cfg.cwd : expandEnvVars(String(cfg.cwd)),
254
+ env: resolveMcpStdioEnvironment(cfg),
255
+ stderr: cfg.stderr ?? 'pipe',
256
+ });
257
+ let tail = '';
258
+ transport.stderr?.on?.('data', (chunk) => {
259
+ const text = scrubMcpConnectionMessage(chunk, cfg);
260
+ tail = (tail + text).slice(-MCP_STDERR_TAIL_CHARS);
261
+ log(`[mcp:${name}:stderr] ${text}`);
262
+ });
263
+ return { transport, stderrTail: () => tail.trim() };
264
+ }
265
+
104
266
  /** Returns { transport, autoDetectAdvert, stderrTail, warnings } — stderrTail()
105
267
  * is the last ~4 KB of a stdio server's (scrubbed) stderr, '' for remote
106
268
  * transports. `strictEnv` (the caller passes it for plugin-declared servers)
107
269
  * makes unset variables without a default fail the server; otherwise they
108
270
  * expand to empty and are reported in `warnings` (names only). */
109
271
  export function createMcpTransport({ name, cfg, sdk, log, strictEnv, oauthOptions }) {
110
- const { StdioClientTransport, StreamableHTTPClientTransport, SSEClientTransport, WebSocketClientTransport } = sdk;
272
+ const { StdioClientTransport, StreamableHTTPClientTransport, SSEClientTransport } = sdk;
111
273
  const kind = resolveMcpTransportKind(cfg);
112
- const warnings = [];
113
- if (kind !== 'autoDetect') {
114
- const missing = findMissingMcpEnvVars(cfg);
115
- if (missing.length) {
116
- const message = `missing environment variable(s): ${missing.join(', ')}`;
117
- if (strictEnv === true) throw new Error(message);
118
- warnings.push(message);
119
- log(`[mcp-client] "${name}": ${message}\n`);
120
- }
121
- }
274
+ const warnings = kind !== 'autoDetect' ? missingEnvWarnings(name, cfg, strictEnv, log) : [];
122
275
  if (kind === 'autoDetect') {
123
276
  const { url, advert } = resolveAutoDetectUrl(name, cfg);
124
277
  log(`[mcp-client] Connecting "${name}" via autoDetect HTTP: ${url}\n`);
@@ -132,72 +285,23 @@ export function createMcpTransport({ name, cfg, sdk, log, strictEnv, oauthOption
132
285
  if (kind === 'http' || kind === 'sse') {
133
286
  const Transport = kind === 'http' ? StreamableHTTPClientTransport : SSEClientTransport;
134
287
  const url = resolveMcpOAuthUrl(cfg);
135
- const opts = requestInitFor(cfg);
136
- // Explicit authentication remains authoritative (including custom API-key
137
- // headers). Never forward those credentials to OAuth discovery endpoints.
138
- let transport;
139
- const oauth = hasConfiguredMcpAuth(cfg)
140
- ? null
141
- : createMcpOAuth({
142
- url,
143
- serverName: name,
144
- oauthClient: mcpOAuthClientFor(cfg, url),
145
- ...oauthOptions,
146
- // A built-in Google connector is answered in-process.
147
- ...(googleMcpService(url) ? { fetchImpl: withGoogleMcp(oauthOptions?.fetchImpl) } : {}),
148
- // SSE advertises its POST endpoint on the authenticated event stream.
149
- // No other path on the origin is allowed to receive this server's token.
150
- isTransportEndpoint: (input) => kind === 'sse' && transport?._endpoint?.href === new URL(input).href,
151
- });
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);
156
- if (oauth) {
157
- transport.oauth = oauth;
158
- const close = transport.close.bind(transport);
159
- transport.close = async () => {
160
- oauth.close();
161
- await close();
162
- };
288
+ // A disabled account is neither connected nor routed to.
289
+ const accountServers = connectorLiveAccountServers(cfg);
290
+ if (!accountServers.length && connectorAccountsOf(cfg).length) {
291
+ throw new Error(`Every account of "${name}" is disabled`);
163
292
  }
164
- log(`[mcp-client] Connecting "${name}" via ${kind.toUpperCase()}: ${mcpUrlForLog(url)}\n`);
165
- return {
166
- transport,
167
- autoDetectAdvert: null,
168
- stderrTail: noStderrTail,
169
- warnings,
170
- };
293
+ const transport = accountServers.length
294
+ ? connectorAccountsTransport({ name, cfg, Transport, url, accountServers, oauthOptions, log })
295
+ : remoteHttpTransport({ name, cfg, kind, Transport, url, oauthOptions, log });
296
+ return { transport, autoDetectAdvert: null, stderrTail: noStderrTail, warnings };
171
297
  }
172
298
  if (kind === 'ws') {
173
- const url = normalizeMcpTransportUrl(expandEnvVars(String(cfg.url ?? '')), 'ws');
174
- const opts = requestInitFor(cfg);
175
- log(`[mcp-client] Connecting "${name}" via WebSocket: ${mcpUrlForLog(url)}\n`);
176
- const transport = opts
177
- ? createHeaderWebSocketTransport(
178
- WebSocketClientTransport,
179
- sdk.JSONRPCMessageSchema,
180
- new URL(url),
181
- opts.requestInit.headers
182
- )
183
- : new WebSocketClientTransport(new URL(url));
299
+ const transport = webSocketTransport(name, cfg, sdk, log);
184
300
  return { transport, autoDetectAdvert: null, stderrTail: noStderrTail, warnings };
185
301
  }
186
302
  if (kind === 'stdio') {
187
- const transport = new StdioClientTransport({
188
- command: expandEnvVars(String(cfg.command ?? '')),
189
- args: Array.isArray(cfg.args) ? expandEnvVars(cfg.args) : cfg.args,
190
- cwd: cfg.cwd == null ? cfg.cwd : expandEnvVars(String(cfg.cwd)),
191
- env: resolveMcpStdioEnvironment(cfg),
192
- stderr: cfg.stderr ?? 'pipe',
193
- });
194
- let tail = '';
195
- transport.stderr?.on?.('data', (chunk) => {
196
- const text = scrubMcpConnectionMessage(chunk, cfg);
197
- tail = (tail + text).slice(-MCP_STDERR_TAIL_CHARS);
198
- log(`[mcp:${name}:stderr] ${text}`);
199
- });
200
- return { transport, autoDetectAdvert: null, stderrTail: () => tail.trim(), warnings };
303
+ const { transport, stderrTail } = stdioTransport(name, cfg, StdioClientTransport, log);
304
+ return { transport, autoDetectAdvert: null, stderrTail, warnings };
201
305
  }
202
306
  throw new Error(
203
307
  `Invalid config for "${name}": need autoDetect, type (stdio/http/sse/ws), url (http), or command (stdio)`
@@ -35,13 +35,16 @@ import { createLazyOutputValidator } from './lazy-output-validator.mjs';
35
35
  import { appendMcpStderrTail, createMcpTransport, hasConfiguredMcpAuth } from './client-transport.mjs';
36
36
  import { listAllMcpTools, MCP_CONNECT_CANCELLED, runBoundedHandshake } from './client-handshake.mjs';
37
37
  import { rememberServerConnector } from './server-connectors.mjs';
38
- import { mcpOAuthClientFor } from './oauth-client.mjs';
38
+ import { mcpOAuthClientFor, mcpOAuthDisplayName } from './oauth-client.mjs';
39
39
  import { googleMcpService, withGoogleMcp } from '../../../connectors/google-mcp.mjs';
40
+ import { gmailAppPasswordService } from '../../../connectors/gmail-app-password.mjs';
41
+ import { connectorLiveAccountServers } from '../../../shared/connector-catalog.mjs';
40
42
  import {
41
43
  clearMcpOAuth,
42
44
  commitMcpOAuthSwitch,
43
45
  hasStoredMcpOAuthTokens,
44
46
  isMcpOAuthReauthenticationRequired,
47
+ isMcpOAuthScopeNotGranted,
45
48
  MCP_OAUTH_CALLBACK_NOT_PENDING,
46
49
  startMcpOAuthSwitch,
47
50
  storedMcpOAuthIdentity,
@@ -1116,6 +1119,23 @@ export function isMcpTool(name) {
1116
1119
  export function isRegisteredMcpTool(name, scopeId = DEFAULT_MCP_SCOPE_ID) {
1117
1120
  return Boolean(resolveMcpToolTarget(scopeId, name)?.definition);
1118
1121
  }
1122
+ /** True when the prefixed name is `read_email` of a server whose every
1123
+ * endpoint Mixdog answers in-process (the Gmail app-password connector,
1124
+ * alone or behind the multi-account server): its result is a mail as the
1125
+ * mailbox holds it, never another server's text (browser-bridge/mail-links.mjs). */
1126
+ export function isBuiltinMailRead(name, scopeId = DEFAULT_MCP_SCOPE_ID) {
1127
+ const target = resolveMcpToolTarget(normalizeMcpScopeId(scopeId), name);
1128
+ if (!target || (target.definition?.mcpToolName || MCP_TOOL_NAME_RE.exec(name)?.[2]) !== 'read_email') return false;
1129
+ const accounts = connectorLiveAccountServers(target.server.cfg);
1130
+ return (accounts.length ? accounts : [target.server.cfg]).every((cfg) => {
1131
+ try {
1132
+ const url = resolveMcpOAuthUrl(cfg);
1133
+ return Boolean(gmailAppPasswordService(url) || googleMcpService(url));
1134
+ } catch {
1135
+ return false;
1136
+ }
1137
+ });
1138
+ }
1119
1139
  /**
1120
1140
  * Check whether the inputSchema for an MCP tool declares the given top-level
1121
1141
  * property. Used to decide if the orchestrator should auto-inject context
@@ -1279,7 +1299,7 @@ export function startMcpServerOAuthSwitch(cfg, launch, serverName) {
1279
1299
  return startMcpOAuthSwitch({
1280
1300
  url,
1281
1301
  launch,
1282
- serverName,
1302
+ serverName: mcpOAuthDisplayName(cfg, serverName),
1283
1303
  oauthClient: mcpOAuthClientFor(cfg, url),
1284
1304
  ...(googleMcpService(url) ? { fetchImpl: withGoogleMcp() } : {}),
1285
1305
  });
@@ -1464,8 +1484,11 @@ async function connectServer(
1464
1484
  // sign-in was required, or an explicit one did not complete (cancelled,
1465
1485
  // denied, timed out or a failed exchange after it started, or cancelled
1466
1486
  // before it could). A failure after the sign-in obtained tokens is real.
1487
+ // A sign-in Google (or another provider) answered without a requested
1488
+ // scope is reported as its own error: the status row carries the message.
1467
1489
  const signInIncomplete =
1468
1490
  transport.oauth &&
1491
+ !isMcpOAuthScopeNotGranted(error) &&
1469
1492
  (isMcpOAuthReauthenticationRequired(error) ||
1470
1493
  (transport.oauth.loginStarted && !transport.oauth.loginCompleted) ||
1471
1494
  (transport.oauth.interactive && error?.code === MCP_CONNECT_CANCELLED));
@@ -97,6 +97,35 @@ export function answerOAuthCallback(req, res, { redirectUrl, state, finish, serv
97
97
  finish(undefined);
98
98
  return;
99
99
  }
100
- page(200, oauthSuccessHtml(server));
101
- finish(target.searchParams.get('code'));
100
+ // The code is only the start: the page stays open until the login reports
101
+ // its outcome through `respond` (after the code exchange and scope check).
102
+ finish(target.searchParams.get('code'), (failure) => {
103
+ // Resolves once the page is flushed (bounded), so the caller may close the listener.
104
+ const flushed = new Promise((resolve) => {
105
+ const timer = setTimeout(resolve, 1000);
106
+ timer.unref?.();
107
+ const done = () => {
108
+ clearTimeout(timer);
109
+ resolve();
110
+ };
111
+ res.once('finish', done);
112
+ res.once('close', done);
113
+ });
114
+ if (!failure) page(200, oauthSuccessHtml(server));
115
+ else if (failure.stale) invalid();
116
+ else page(400, oauthErrorHtml(failurePageReason(server, failure.code)));
117
+ return flushed;
118
+ });
119
+ }
120
+
121
+ // Fixed bilingual texts by error code: SDK/server error text and tokens are
122
+ // never put on the page.
123
+ function failurePageReason(server, code) {
124
+ if (code === 'scopeNotGranted') {
125
+ return (
126
+ `${server} 접근 권한이 허용되지 않았습니다. Mixdog로 돌아가 다시 연결하고, 권한 확인란을 체크해 주세요. (Google은 확인란이 처음에 해제되어 있습니다.)\n` +
127
+ `Access to ${server} was not granted. Return to Mixdog and connect again with the access box checked (Google starts it unchecked).`
128
+ );
129
+ }
130
+ return `${server} 로그인을 완료하지 못했습니다.\nSign-in to ${server} could not be completed.`;
102
131
  }
@@ -7,7 +7,7 @@
7
7
  // its scopes and the provider's offline-access parameter.
8
8
  import { createHash } from 'node:crypto';
9
9
  import keychain from '../../../../lib/keychain-cjs.cjs';
10
- import { connectorById, connectorOfServer } from '../../../shared/connector-catalog.mjs';
10
+ import { connectorById, connectorOfServer, gmailAppPasswordAccount } from '../../../shared/connector-catalog.mjs';
11
11
  import { GOOGLE_AUTH_PARAMS, googleOAuthClient } from '../../../shared/google-oauth-client.mjs';
12
12
  import { normalizeMcpTransportUrl } from './client-config.mjs';
13
13
 
@@ -31,6 +31,12 @@ export async function saveMcpOAuthClientSecret(url, secret, credentialStore = ke
31
31
  else await credentialStore.deleteSecretAsync(account);
32
32
  }
33
33
 
34
+ /** What the sign-in page calls server `name` configured as `cfg`: a catalog
35
+ * connector's display name ("Google Drive"), else the server name. */
36
+ export function mcpOAuthDisplayName(cfg, name) {
37
+ return connectorById(connectorOfServer(cfg))?.name || name;
38
+ }
39
+
34
40
  /**
35
41
  * `{ clientId, clientSecret?, secretAccount?, scopes?, authParams? }` for
36
42
  * server config `cfg` at `url`, or null to register a client dynamically.
@@ -44,6 +50,13 @@ export function mcpOAuthClientFor(cfg, url) {
44
50
  const authParams = catalog?.oauthClient === 'google' ? GOOGLE_AUTH_PARAMS : undefined;
45
51
  return { clientId: own, secretAccount: mcpOAuthClientSecretAccount(url), scopes, authParams };
46
52
  }
47
- const builtIn = catalog?.oauthClient ? BUILT_IN_CLIENTS[catalog.oauthClient]?.() : null;
48
- return builtIn ? { ...builtIn, scopes } : null;
53
+ if (!catalog?.oauthClient || gmailAppPasswordAccount(cfg)) return null;
54
+ const builtIn = BUILT_IN_CLIENTS[catalog.oauthClient]?.();
55
+ // Google offers no dynamic client registration: never fall back to it.
56
+ if (!builtIn) {
57
+ throw new Error(
58
+ `MCP OAuth clientUnavailable: this build has no built-in ${catalog.oauthClient} sign-in client; use your own ${catalog.oauthClient} client.`
59
+ );
60
+ }
61
+ return { ...builtIn, scopes };
49
62
  }
@@ -3,7 +3,7 @@ import { randomBytes } from 'node:crypto';
3
3
  import { normalizeMcpTransportUrl } from './client-config.mjs';
4
4
  import { identityFromTokenResponse } from './oauth-account.mjs';
5
5
  import { answerOAuthCallback } from './oauth-callback.mjs';
6
- import { anySignal, loginFailed, reauthenticationRequired } from './oauth-shared.mjs';
6
+ import { anySignal, loginFailed, reauthenticationRequired, scopeNotGranted } from './oauth-shared.mjs';
7
7
 
8
8
  /**
9
9
  * The browser login of one connection over its credential `cache` and login
@@ -12,10 +12,15 @@ import { anySignal, loginFailed, reauthenticationRequired } from './oauth-shared
12
12
  */
13
13
  export function createLoginFlow({ url, cache, flow, provider, lifetimeSignal, openBrowser, fetchImpl, timeoutMs }) {
14
14
  const { creds } = cache;
15
+ // The flush of the browser page being answered (see `settle`): connections
16
+ // are dropped only after it, so the page is never cut off by a reset.
17
+ let flushing;
15
18
  function stopListening() {
16
- flow.listener?.close();
17
- flow.listener?.closeAllConnections();
19
+ const listener = flow.listener;
18
20
  flow.listener = undefined;
21
+ listener?.close();
22
+ if (flushing) flushing.then(() => listener?.closeAllConnections());
23
+ else listener?.closeAllConnections();
19
24
  }
20
25
  function closeListener() {
21
26
  stopListening();
@@ -23,10 +28,13 @@ export function createLoginFlow({ url, cache, flow, provider, lifetimeSignal, op
23
28
  flow.state = undefined;
24
29
  }
25
30
  // The login's callback is answered once, by the loopback or a paste.
26
- function answerCallback(code) {
31
+ // `respond` (loopback only) answers the browser page once the outcome is
32
+ // known; a callback nobody is waiting for gets the invalid-link page.
33
+ function answerCallback(code, respond) {
27
34
  const finish = flow.finishCallback;
28
35
  flow.finishCallback = undefined;
29
- finish?.(code);
36
+ if (finish) finish(code, respond);
37
+ else respond?.({ stale: true });
30
38
  }
31
39
  async function listenForCallback(serverName) {
32
40
  flow.listener = createServer((req, res) =>
@@ -53,6 +61,7 @@ export function createLoginFlow({ url, cache, flow, provider, lifetimeSignal, op
53
61
  // Who this login's tokens belong to (oauth-account.mjs): the SDK keeps only
54
62
  // the token fields, so the token endpoint's answer is read here.
55
63
  flow.identity = undefined;
64
+ flow.signal = signal;
56
65
  const safeFetch = async (input, init = {}) => {
57
66
  const response = await fetchImpl(normalizeMcpTransportUrl(input), {
58
67
  ...init,
@@ -79,10 +88,31 @@ export function createLoginFlow({ url, cache, flow, provider, lifetimeSignal, op
79
88
  }
80
89
  };
81
90
  let abort;
91
+ // The browser page held open for the loopback callback until the code
92
+ // exchange and scope check end: `settle()` is success, `settle({ code })`
93
+ // a failure. Bounded by `signal`, like the exchange itself.
94
+ let respond;
95
+ const settle = (failure) => {
96
+ const answer = respond;
97
+ respond = undefined;
98
+ if (!answer) return undefined;
99
+ flushing = answer(failure);
100
+ return flushing;
101
+ };
82
102
  const callback = new Promise((resolve, reject) => {
83
- const finish = (code) => (code ? resolve(code) : reject(loginFailed()));
103
+ const finish = (code, page) => {
104
+ if (page) respond = page;
105
+ return code ? resolve(code) : reject(loginFailed());
106
+ };
84
107
  flow.finishCallback = finish;
85
- abort = () => finish();
108
+ // Timeout, cancel or abort: the held page gets the generic failure now
109
+ // (the exchange or a credential write may be stalled past the deadline),
110
+ // and the listener closes once it is flushed.
111
+ abort = () => {
112
+ finish();
113
+ settle({});
114
+ stopListening();
115
+ };
86
116
  signal.addEventListener('abort', abort, { once: true });
87
117
  });
88
118
  // A discovery/registration error can precede waiting for the callback.
@@ -121,12 +151,17 @@ export function createLoginFlow({ url, cache, flow, provider, lifetimeSignal, op
121
151
  }
122
152
  progress('onAuthorized');
123
153
  signal.throwIfAborted();
154
+ await settle();
124
155
  } catch (error) {
156
+ await settle({ code: error?.code });
125
157
  // SDK errors may contain authorization server response bodies/secrets.
126
158
  if (error?.code === 'reauthenticationRequired' || !interactive) throw reauthenticationRequired();
159
+ // Our own error names scopes only (no token data): the one to pass on.
160
+ if (error?.code === 'scopeNotGranted') throw scopeNotGranted(error.missingScopes);
127
161
  throw loginFailed();
128
162
  } finally {
129
163
  flow.allowBrowser = false;
164
+ flow.signal = undefined;
130
165
  signal.removeEventListener('abort', abort);
131
166
  closeListener();
132
167
  flow.finishCallback = undefined;