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
@@ -5,6 +5,7 @@ import http2 from 'node:http2';
5
5
  import { MAX_CONNECT_FRAME_BYTES, createCursorByteQueue } from './cursor-wire-guards.mjs';
6
6
  import { cursorClientVersion, warmCursorClientVersion } from './cursor-client-version.mjs';
7
7
  import { retryAfterMsFromError } from './retry-classifier.mjs';
8
+ import { providerTimeoutError } from '../stall-policy.mjs';
8
9
 
9
10
  export { warmCursorClientVersion };
10
11
 
@@ -153,7 +154,9 @@ export function openCursorStream({ accessToken, path = RUN_PATH, url = API_URL }
153
154
  if (closed || session.closed || session.destroyed) return;
154
155
  try {
155
156
  session.ping(() => {});
156
- } catch {}
157
+ } catch {
158
+ // the session may have closed since the check above
159
+ }
157
160
  }, H2_PING_INTERVAL_MS);
158
161
  ping.unref?.();
159
162
 
@@ -175,20 +178,28 @@ export function openCursorStream({ accessToken, path = RUN_PATH, url = API_URL }
175
178
  closeError = error || (status >= 400 ? httpStatusError(status, responseHeaders) : null);
176
179
  try {
177
180
  request.close();
178
- } catch {}
181
+ } catch {
182
+ // the request stream may already be closed
183
+ }
179
184
  try {
180
185
  session.close();
181
- } catch {}
186
+ } catch {
187
+ // the session may already be closed
188
+ }
182
189
  closeHandler?.(closeError);
183
190
  };
184
191
  const close = (error = null) => {
185
192
  if (closed) return;
186
193
  try {
187
194
  request.close(http2.constants.NGHTTP2_CANCEL);
188
- } catch {}
195
+ } catch {
196
+ // the request stream may already be closed
197
+ }
189
198
  try {
190
199
  session.destroy();
191
- } catch {}
200
+ } catch {
201
+ // the session may already be destroyed
202
+ }
192
203
  finish(error);
193
204
  };
194
205
 
@@ -243,17 +254,21 @@ export async function callCursorUnary({ accessToken, path, body, url = API_URL,
243
254
  let status = 0;
244
255
  let responseHeaders = null;
245
256
  let settled = false;
246
- const timeout = setTimeout(() => finish(new Error('Cursor request timed out')), timeoutMs);
257
+ const timeout = setTimeout(() => finish(providerTimeoutError('Cursor request', timeoutMs)), timeoutMs);
247
258
  const finish = (error = null) => {
248
259
  if (settled) return;
249
260
  settled = true;
250
261
  clearTimeout(timeout);
251
262
  try {
252
263
  request.close();
253
- } catch {}
264
+ } catch {
265
+ // the request stream may already be closed
266
+ }
254
267
  try {
255
268
  session.close();
256
- } catch {}
269
+ } catch {
270
+ // the session may already be closed
271
+ }
257
272
  if (error) reject(error);
258
273
  else if (status >= 400) reject(httpStatusError(status, responseHeaders));
259
274
  else resolve(Buffer.concat(chunks));
@@ -322,7 +337,8 @@ export function parseEndStream(bytes) {
322
337
  if (!payload?.error) return null;
323
338
  const connectCode = String(payload.error.code || 'error').toLowerCase();
324
339
  const detail = String(payload.error.message || 'request failed');
325
- const tooLarge = connectCode === 'resource_exhausted' && !THROTTLE_CUE_RE.test(detail) && REQUEST_TOO_LARGE_RE.test(detail);
340
+ const tooLarge =
341
+ connectCode === 'resource_exhausted' && !THROTTLE_CUE_RE.test(detail) && REQUEST_TOO_LARGE_RE.test(detail);
326
342
  const retryAfter =
327
343
  payload.error.retryAfter ??
328
344
  payload.error.retry_after ??
@@ -132,7 +132,9 @@ async function* responseSseEvents(response, signal) {
132
132
  if (signal) {
133
133
  try {
134
134
  signal.removeEventListener('abort', onAbort);
135
- } catch {}
135
+ } catch {
136
+ // the abort listener may already be gone
137
+ }
136
138
  }
137
139
  if (aborted) await reader.cancel().catch(() => {});
138
140
  }
@@ -480,6 +482,7 @@ function selectParameterizedCursorVariant(group, requested, sendOpts = {}) {
480
482
  availableVariants[0] ||
481
483
  null;
482
484
  const parameters = { ...(defaultVariant?.parameters || {}), ...aliasParameters };
485
+ const defaultContext = parameters.context;
483
486
  const routeParameters =
484
487
  sendOpts.modelParameters && typeof sendOpts.modelParameters === 'object' ? sendOpts.modelParameters : {};
485
488
  for (const definition of group.definitions) {
@@ -501,9 +504,18 @@ function selectParameterizedCursorVariant(group, requested, sendOpts = {}) {
501
504
  ) {
502
505
  parameters.fast = sendOpts.fast === true ? 'true' : 'false';
503
506
  }
504
- const selected = availableVariants.find((variant) =>
505
- Object.entries(parameters).every(([id, value]) => String(variant.parameters?.[id] ?? '') === String(value))
506
- );
507
+ const matches = (variant) =>
508
+ Object.entries(parameters).every(([id, value]) => String(variant.parameters?.[id] ?? '') === String(value));
509
+ let selected = availableVariants.find(matches);
510
+ // Keep a supported explicit context. A persisted context can exist in this
511
+ // mode but conflict with its requested effort/fast/other options. In that
512
+ // case try only the mode's default context, preserving every other choice
513
+ // and still rejecting the request unless the full combination is offered.
514
+ if (!selected && Object.hasOwn(routeParameters, 'context')) {
515
+ if (defaultContext === undefined) delete parameters.context;
516
+ else parameters.context = defaultContext;
517
+ selected = availableVariants.find(matches);
518
+ }
507
519
  if (!selected && availableVariants.length) {
508
520
  throw new Error(`Cursor model ${group.id} does not offer the selected parameter combination`);
509
521
  }
@@ -642,7 +654,9 @@ class CursorProviderBase {
642
654
  let emittedToolCall = false;
643
655
  try {
644
656
  sendOpts.onStageChange?.('requesting');
645
- } catch {}
657
+ } catch {
658
+ // the stage observer is advisory
659
+ }
646
660
  try {
647
661
  const response = await runtime.handleChatCompletion(body, accessToken, {
648
662
  onStageChange: sendOpts.onStageChange,
@@ -657,7 +671,9 @@ class CursorProviderBase {
657
671
  });
658
672
  try {
659
673
  sendOpts.onStageChange?.('streaming');
660
- } catch {}
674
+ } catch {
675
+ // the stage observer is advisory
676
+ }
661
677
  return await consumeCompatChatCompletionStream(responseSseEvents(response, signal), {
662
678
  signal,
663
679
  label: this.name,
@@ -0,0 +1,409 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { existsSync, mkdirSync, readFileSync, unlinkSync } from 'node:fs';
3
+ import { createServer } from 'node:http';
4
+ import { join, resolve } from 'node:path';
5
+ import { resolvePluginData } from '../../../shared/plugin-paths.mjs';
6
+ import { writeJsonAtomicSync } from '../../../shared/atomic-file.mjs';
7
+ import { boundProviderAuthPath } from '../../../shared/provider-auth-binding.mjs';
8
+ import { modelProxyClientMode } from '../../../shared/model-proxy-mode.mjs';
9
+ import { openLoginUrl } from './lib/open-login-url.mjs';
10
+ import { OAUTH_PAGE_CONTENT_TYPE, oauthErrorHtml, oauthSuccessHtml } from './lib/oauth-page.mjs';
11
+ import { createOAuthPkce, parseOAuthCodeInput } from './lib/oauth-pkce.mjs';
12
+ import {
13
+ accountIdentityFields,
14
+ decodeJwtPayload,
15
+ expiryFromAccessToken,
16
+ normalizeAccountIdentity,
17
+ } from './lib/oauth-token-utils.mjs';
18
+
19
+ const AUTHORIZE_URL = 'https://app.devin.ai/auth/cli/continue';
20
+ const TOKEN_URL = 'https://api.devin.ai/auth/cli/token';
21
+ const CALLBACK_HOST = '127.0.0.1';
22
+ const CALLBACK_PORT = 59653;
23
+ const CALLBACK_PATH = '/callback';
24
+ const REDIRECT_URI = `http://${CALLBACK_HOST}:${CALLBACK_PORT}${CALLBACK_PATH}`;
25
+ const LOGIN_TIMEOUT_MS = 5 * 60_000;
26
+ const EXCHANGE_TIMEOUT_MS = 30_000;
27
+ // The login token is the only credential and cannot be refreshed. A token
28
+ // without a readable `exp` claim is assumed to live for a year.
29
+ const DEFAULT_TOKEN_LIFETIME_MS = 365 * 24 * 60 * 60_000;
30
+ export const REAUTH_GUIDANCE = 'Open /providers in Mixdog to sign in again.';
31
+
32
+ function reauthError(message) {
33
+ return Object.assign(new Error(message), { reauthRequired: true });
34
+ }
35
+
36
+ // Sign-in failures the desktop words in the user's language: the message leads
37
+ // with `DEVIN_OAUTH_<CODE>:` and apps/desktop/src/renderer/oauth-failure-copy.ts
38
+ // maps each code to its cause and remedy (like GMAIL_APP_PASSWORD_<CODE>).
39
+ export const DEVIN_OAUTH_FAILURE_CODES = Object.freeze([
40
+ 'DEVIN_OAUTH_CALLBACK_PORT_IN_USE',
41
+ 'DEVIN_OAUTH_CALLBACK_SERVER_FAILED',
42
+ 'DEVIN_OAUTH_LOGIN_TIMED_OUT',
43
+ 'DEVIN_OAUTH_AUTHORIZATION_DENIED',
44
+ 'DEVIN_OAUTH_STATE_MISMATCH',
45
+ 'DEVIN_OAUTH_CODE_REQUIRED',
46
+ 'DEVIN_OAUTH_TOKEN_EXCHANGE_FAILED',
47
+ ]);
48
+
49
+ function loginFailure(code, text, props = {}) {
50
+ const failureCode = `DEVIN_OAUTH_${code}`;
51
+ return Object.assign(new Error(`${failureCode}: ${text}`), { failureCode }, props);
52
+ }
53
+
54
+ function credentialsPath() {
55
+ const bound = boundProviderAuthPath('devin-oauth');
56
+ if (bound) return resolve(bound);
57
+ const explicit = process.env.DEVIN_OAUTH_CREDENTIALS_PATH;
58
+ if (explicit) return resolve(explicit);
59
+ const dir = resolvePluginData();
60
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
61
+ return join(dir, 'devin-oauth.json');
62
+ }
63
+
64
+ export function devinTokenExpiry(token) {
65
+ return expiryFromAccessToken(token);
66
+ }
67
+
68
+ // The login token is a JWT whose `sub` is the Devin user; no request needed.
69
+ export function devinTokenIdentity(token) {
70
+ const payload = decodeJwtPayload(token);
71
+ return normalizeAccountIdentity({ id: payload?.sub, email: payload?.email });
72
+ }
73
+
74
+ function loadStoredCredentials() {
75
+ if (modelProxyClientMode()) return null;
76
+ const path = credentialsPath();
77
+ if (!existsSync(path)) return null;
78
+ try {
79
+ const raw = JSON.parse(readFileSync(path, 'utf8'));
80
+ if (!raw?.access_token) return null;
81
+ return {
82
+ ...raw,
83
+ expires_at: Number(raw.expires_at) || devinTokenExpiry(raw.access_token),
84
+ identity: normalizeAccountIdentity(raw.identity) || devinTokenIdentity(raw.access_token),
85
+ source: 'Mixdog token store',
86
+ };
87
+ } catch {
88
+ return null;
89
+ }
90
+ }
91
+
92
+ function saveCredentials(tokens) {
93
+ writeJsonAtomicSync(credentialsPath(), tokens, {
94
+ lock: true,
95
+ fsyncDir: true,
96
+ mode: 0o600,
97
+ secret: true,
98
+ });
99
+ }
100
+
101
+ function tokenRecord(accessToken) {
102
+ const identity = devinTokenIdentity(accessToken);
103
+ return {
104
+ access_token: accessToken,
105
+ refresh_token: null,
106
+ expires_at: devinTokenExpiry(accessToken) || Date.now() + DEFAULT_TOKEN_LIFETIME_MS,
107
+ ...(identity ? { identity } : {}),
108
+ };
109
+ }
110
+
111
+ export function hasDevinOAuthCredentials() {
112
+ const tokens = loadStoredCredentials();
113
+ if (!tokens?.access_token) return false;
114
+ const expiresAt = Number(tokens.expires_at) || 0;
115
+ return expiresAt === 0 || expiresAt > Date.now();
116
+ }
117
+
118
+ export function describeDevinOAuthCredentials() {
119
+ const tokens = loadStoredCredentials();
120
+ if (!tokens?.access_token) {
121
+ return {
122
+ authenticated: false,
123
+ usable: false,
124
+ refreshable: false,
125
+ reauthRequired: false,
126
+ status: 'Not Set',
127
+ detail: 'Mixdog token store',
128
+ };
129
+ }
130
+ const expiresAt = Number(tokens.expires_at) || 0;
131
+ const expired = expiresAt > 0 && expiresAt <= Date.now();
132
+ return {
133
+ authenticated: !expired,
134
+ usable: !expired,
135
+ refreshable: false,
136
+ reauthRequired: expired,
137
+ status: expired ? 'Reauth Required' : 'Access Only',
138
+ detail: tokens.source || 'Devin OAuth',
139
+ expiresAt: expiresAt || null,
140
+ ...accountIdentityFields(tokens.identity),
141
+ };
142
+ }
143
+
144
+ export function forgetDevinOAuthCredentials() {
145
+ const path = credentialsPath();
146
+ if (!existsSync(path)) return { removed: false };
147
+ unlinkSync(path);
148
+ return { removed: true };
149
+ }
150
+
151
+ export function generateDevinOAuthParams() {
152
+ const { verifier, challenge } = createOAuthPkce();
153
+ const state = randomUUID();
154
+ const params = new URLSearchParams({
155
+ response_type: 'code',
156
+ redirect_uri: REDIRECT_URI,
157
+ code_challenge: challenge,
158
+ code_challenge_method: 'S256',
159
+ state,
160
+ prompt: 'select_account',
161
+ });
162
+ return { verifier, challenge, state, loginUrl: `${AUTHORIZE_URL}?${params}` };
163
+ }
164
+
165
+ export async function exchangeDevinCode(code, verifier, { fetchFn = fetch, signal } = {}) {
166
+ const timeout = AbortSignal.timeout(EXCHANGE_TIMEOUT_MS);
167
+ let response;
168
+ try {
169
+ response = await fetchFn(TOKEN_URL, {
170
+ method: 'POST',
171
+ headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
172
+ body: JSON.stringify({ code, code_verifier: verifier }),
173
+ signal: signal ? AbortSignal.any([signal, timeout]) : timeout,
174
+ redirect: 'error',
175
+ });
176
+ } catch (error) {
177
+ // A cancelled login keeps its own abort; any other failure to get an
178
+ // answer (network, timeout) is a coded exchange failure. The cause is not
179
+ // quoted: only its kind is.
180
+ if (signal?.aborted) throw error;
181
+ throw loginFailure(
182
+ 'TOKEN_EXCHANGE_FAILED',
183
+ error?.name === 'TimeoutError' ? 'Devin token exchange timed out' : 'Devin token exchange request failed'
184
+ );
185
+ }
186
+ // Response bodies may carry the login token itself, so none is ever quoted
187
+ // in an error: only the status is reported.
188
+ if (!response.ok) {
189
+ throw loginFailure('TOKEN_EXCHANGE_FAILED', `Devin token exchange failed (${response.status})`, {
190
+ status: response.status,
191
+ httpStatus: response.status,
192
+ });
193
+ }
194
+ const data = await response.json().catch(() => null);
195
+ if (typeof data?.token !== 'string' || !data.token) {
196
+ throw loginFailure('TOKEN_EXCHANGE_FAILED', 'Devin token exchange returned no token');
197
+ }
198
+ return tokenRecord(data.token);
199
+ }
200
+
201
+ // The callback port is fixed, so at most one login can own it. A newer login
202
+ // in this process takes the port over from the one it supersedes.
203
+ let activeLogin = null;
204
+
205
+ function listenOnCallbackPort(server) {
206
+ return new Promise((resolveListen, rejectListen) => {
207
+ server.once('error', rejectListen);
208
+ server.listen(CALLBACK_PORT, CALLBACK_HOST, () => {
209
+ server.off('error', rejectListen);
210
+ resolveListen();
211
+ });
212
+ });
213
+ }
214
+
215
+ // Logins start one at a time, in call order: ownership of the port is decided
216
+ // only once the previous start has fully settled, so overlapping calls
217
+ // supersede each other instead of racing for the bind.
218
+ let startQueue = Promise.resolve();
219
+
220
+ export function beginDevinOAuthLogin(options) {
221
+ const started = startQueue.then(() => startDevinOAuthLogin(options));
222
+ startQueue = started.catch(() => {});
223
+ return started;
224
+ }
225
+
226
+ async function startDevinOAuthLogin({ openBrowser = true, timeoutMs = LOGIN_TIMEOUT_MS, fetchFn = fetch } = {}) {
227
+ activeLogin?.cancel();
228
+ const params = generateDevinOAuthParams();
229
+ const controller = new AbortController();
230
+ const server = createServer();
231
+ let timer = null;
232
+ let exchangePromise = null;
233
+ let settled = false;
234
+ let resolveLogin;
235
+ let rejectLogin;
236
+ const waitForCallback = new Promise((resolvePromise, rejectPromise) => {
237
+ resolveLogin = resolvePromise;
238
+ rejectLogin = rejectPromise;
239
+ });
240
+ // A caller may only ever cancel; that rejection must not go unhandled.
241
+ waitForCallback.catch(() => {});
242
+
243
+ const login = {
244
+ provider: 'devin-oauth',
245
+ url: params.loginUrl,
246
+ manualUrl: null,
247
+ waitForCallback,
248
+ // A remote client signs in on its own machine, where the redirect to this
249
+ // host's loopback listener cannot land: it pastes the final address (or
250
+ // code) back, and the state it carries must be this login's own.
251
+ completeCode: async (input) => {
252
+ const text = String(input || '').trim();
253
+ // An address carries its code and state as query parameters only: one
254
+ // without a code is not a bare code (the shared parser would take the
255
+ // whole address for one and spend it on the exchange).
256
+ const address = URL.canParse(text) ? new URL(text) : null;
257
+ const pasted = address
258
+ ? { code: address.searchParams.get('code') || '', state: address.searchParams.get('state') || '' }
259
+ : parseOAuthCodeInput(text, { allowHashState: true });
260
+ if (pasted.state && pasted.state !== params.state) throw loginFailure('STATE_MISMATCH', 'OAuth state mismatch');
261
+ if (!pasted.code) throw loginFailure('CODE_REQUIRED', 'Authorization code is required');
262
+ return await acceptCode(pasted.code);
263
+ },
264
+ cancel: () => finish(null, new Error('Devin OAuth cancelled')),
265
+ };
266
+
267
+ function finish(value, error = null) {
268
+ if (settled) return;
269
+ settled = true;
270
+ if (activeLogin === login) activeLogin = null;
271
+ // Work arriving after the login settled (or was cancelled) is rejected.
272
+ controller.abort();
273
+ clearTimeout(timer);
274
+ try {
275
+ server.close();
276
+ server.closeIdleConnections?.();
277
+ } catch {
278
+ /* already closed */
279
+ }
280
+ if (error) rejectLogin(error);
281
+ else resolveLogin(value);
282
+ }
283
+
284
+ // The authorization code is spent once: a duplicate callback joins the first
285
+ // exchange instead of failing the login.
286
+ const acceptCode = (code) => {
287
+ if (exchangePromise) return exchangePromise;
288
+ controller.signal.throwIfAborted();
289
+ // The browser deadline ends once a code arrives; the exchange has its own.
290
+ clearTimeout(timer);
291
+ exchangePromise = exchangeDevinCode(code, params.verifier, { fetchFn, signal: controller.signal }).then(
292
+ (tokens) => {
293
+ // A cancelled login never stores the account it was exchanging.
294
+ controller.signal.throwIfAborted();
295
+ saveCredentials(tokens);
296
+ return tokens;
297
+ }
298
+ );
299
+ exchangePromise.then(
300
+ (tokens) => finish(tokens),
301
+ (error) => finish(null, error instanceof Error ? error : new Error(String(error)))
302
+ );
303
+ return exchangePromise;
304
+ };
305
+
306
+ const reply = (res, status, html) => {
307
+ res.writeHead(status, { 'Content-Type': OAUTH_PAGE_CONTENT_TYPE, Connection: 'close' });
308
+ res.end(html);
309
+ };
310
+
311
+ const handleRequest = async (req, res) => {
312
+ const target = new URL(req.url || '/', REDIRECT_URI);
313
+ if (req.method !== 'GET' || target.pathname !== CALLBACK_PATH) {
314
+ res.writeHead(404);
315
+ res.end();
316
+ return;
317
+ }
318
+ // A request that is not this login's own is refused; the real callback
319
+ // may still arrive.
320
+ if (target.searchParams.get('state') !== params.state) {
321
+ reply(res, 400, oauthErrorHtml('The sign-in callback did not match this login.'));
322
+ return;
323
+ }
324
+ const authorizationError = target.searchParams.get('error');
325
+ if (authorizationError) {
326
+ // Only a plain OAuth error code is named; free text (error_description)
327
+ // may carry anything, secrets included, so it never reaches the message.
328
+ const code = /^[A-Za-z0-9_.-]{1,64}$/.test(authorizationError) ? ` (${authorizationError})` : '';
329
+ reply(res, 400, oauthErrorHtml('Devin did not authorize this sign-in.'));
330
+ // A late denial must not cancel an already accepted code.
331
+ if (!exchangePromise) {
332
+ finish(null, loginFailure('AUTHORIZATION_DENIED', `Authorization failed${code}`));
333
+ }
334
+ return;
335
+ }
336
+ const code = target.searchParams.get('code')?.trim();
337
+ if (!code) {
338
+ reply(res, 400, oauthErrorHtml('The sign-in callback carried no authorization code.'));
339
+ return;
340
+ }
341
+ try {
342
+ await acceptCode(code);
343
+ reply(res, 200, oauthSuccessHtml('Devin'));
344
+ } catch {
345
+ // The shared exchange reports its error through waitForCallback.
346
+ reply(res, 500, oauthErrorHtml('Devin could not complete the token exchange.'));
347
+ }
348
+ };
349
+
350
+ // Whatever a stray local client sends (a malformed target included) is
351
+ // answered here and never escapes the listener.
352
+ server.on('request', async (req, res) => {
353
+ try {
354
+ await handleRequest(req, res);
355
+ } catch {
356
+ if (res.headersSent) res.destroy();
357
+ else reply(res, 400, oauthErrorHtml('The sign-in callback was malformed.'));
358
+ }
359
+ });
360
+
361
+ try {
362
+ await listenOnCallbackPort(server);
363
+ } catch (error) {
364
+ const failure =
365
+ error?.code === 'EADDRINUSE'
366
+ ? loginFailure(
367
+ 'CALLBACK_PORT_IN_USE',
368
+ `Callback port ${CALLBACK_HOST}:${CALLBACK_PORT} is already in use. Close the program using it (or another Devin sign-in) and try again.`,
369
+ { code: 'EADDRINUSE' }
370
+ )
371
+ : loginFailure(
372
+ 'CALLBACK_SERVER_FAILED',
373
+ `Callback server failed on ${CALLBACK_HOST}:${CALLBACK_PORT}: ${error?.message || error}`
374
+ );
375
+ finish(null, failure);
376
+ throw failure;
377
+ }
378
+ // Once bound, a runtime fault on the listener ends the login.
379
+ server.on('error', (error) =>
380
+ finish(null, loginFailure('CALLBACK_SERVER_FAILED', `Callback server failed: ${error?.message || error}`))
381
+ );
382
+ timer = setTimeout(
383
+ () => finish(null, loginFailure('LOGIN_TIMED_OUT', `Browser authentication timed out after ${timeoutMs}ms`)),
384
+ timeoutMs
385
+ );
386
+ timer.unref?.();
387
+ activeLogin = login;
388
+
389
+ process.stderr.write(`\n[devin-oauth] Open the Devin sign-in page:\n${params.loginUrl}\n\n`);
390
+ if (openBrowser) await openLoginUrl('devin-oauth', params.loginUrl);
391
+ return login;
392
+ }
393
+
394
+ export async function loginDevinOAuth() {
395
+ const login = await beginDevinOAuthLogin();
396
+ return await login.waitForCallback;
397
+ }
398
+
399
+ // The login token cannot be refreshed: once it expires the user signs in again.
400
+ export async function resolveDevinOAuthAccessToken() {
401
+ const tokens = loadStoredCredentials();
402
+ if (!tokens?.access_token) {
403
+ throw reauthError(`Devin OAuth is not connected. ${REAUTH_GUIDANCE}`);
404
+ }
405
+ if (tokens.expires_at > 0 && tokens.expires_at <= Date.now()) {
406
+ throw reauthError(`Devin login token expired and cannot be refreshed. ${REAUTH_GUIDANCE}`);
407
+ }
408
+ return tokens.access_token;
409
+ }