mixdog 1.2.6 → 1.3.1

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 (294) hide show
  1. package/LICENSES/simple-icons-CC0-1.0.txt +30 -0
  2. package/NOTICE.md +71 -9
  3. package/package.json +4 -1
  4. package/scripts/check-model-recommendations.mjs +89 -0
  5. package/scripts/gate-local.mjs +7 -0
  6. package/scripts/lib/isolated-test-env.mjs +16 -0
  7. package/src/agents/reviewer/AGENT.md +4 -2
  8. package/src/agents/reviewer/agent.json +1 -1
  9. package/src/defaults/shipped-history.json +10 -0
  10. package/src/defaults/skills/browser-use/SKILL.md +5 -0
  11. package/src/defaults/skills/memory-management/SKILL.md +4 -1
  12. package/src/defaults/skills/setup/references/actions.md +14 -4
  13. package/src/defaults/skills/setup-guide/SKILL.md +178 -0
  14. package/src/defaults/skills/setup-guide/model-recommendations.json +333 -0
  15. package/src/lib/rules-builder.cjs +55 -6
  16. package/src/output-styles/common.md +13 -8
  17. package/src/rules/agent/AGENT.md +4 -0
  18. package/src/rules/agent-mode/AGENT_MODE.md +24 -0
  19. package/src/rules/agent-mode/MIXY.md +28 -0
  20. package/src/rules/agent-mode/ROUTINE.md +6 -0
  21. package/src/rules/lead/LEAD.md +7 -3
  22. package/src/rules/shared/70-delivery.md +3 -0
  23. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +86 -0
  24. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +77 -14
  25. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +241 -14
  26. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +4 -2
  27. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +216 -0
  28. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -4
  29. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +10 -1
  30. package/src/runtime/agent/orchestrator/mcp/client.mjs +115 -11
  31. package/src/runtime/agent/orchestrator/mcp/oauth-account.mjs +97 -0
  32. package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +49 -0
  33. package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +59 -9
  34. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +32 -7
  35. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +45 -6
  36. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +195 -6
  37. package/src/runtime/agent/orchestrator/mcp/server-connectors.mjs +17 -0
  38. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  39. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +1 -0
  40. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +7 -2
  41. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +10 -3
  42. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +4 -0
  43. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +7 -8
  44. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  45. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +3 -1
  46. package/src/runtime/agent/orchestrator/providers/lib/tool-call-wire-ids.mjs +65 -0
  47. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +8 -2
  48. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +9 -2
  49. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +17 -12
  50. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -0
  51. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +3 -1
  52. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +14 -2
  53. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +17 -0
  54. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +96 -1
  55. package/src/runtime/agent/orchestrator/runtime-core/context-status-shape.mjs +4 -0
  56. package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +6 -4
  57. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +14 -0
  58. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -0
  59. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +12 -6
  60. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +32 -5
  61. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +21 -5
  62. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +40 -7
  63. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +52 -20
  64. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +2 -0
  65. package/src/runtime/agent/orchestrator/session/agent-home.d.mts +4 -0
  66. package/src/runtime/agent/orchestrator/session/agent-home.mjs +33 -0
  67. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +62 -0
  68. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +31 -0
  69. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +241 -0
  70. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +166 -0
  71. package/src/runtime/agent/orchestrator/session/agent-room.mjs +650 -0
  72. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +7 -1
  73. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +2 -0
  74. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +6 -2
  75. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +192 -0
  76. package/src/runtime/agent/orchestrator/session/human-gate/held-message.mjs +95 -0
  77. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +221 -0
  78. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +195 -0
  79. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +167 -0
  80. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +3 -1
  81. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +9 -0
  82. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +10 -0
  83. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +108 -6
  84. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -1
  85. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +74 -15
  86. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +9 -1
  87. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +5 -4
  88. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +13 -3
  89. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +49 -0
  90. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +18 -8
  91. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +1 -0
  92. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -1
  93. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  94. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -0
  95. package/src/runtime/agent/orchestrator/session/manager/pending-lifecycle-epoch.mjs +0 -8
  96. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +44 -16
  97. package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +50 -0
  98. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +22 -5
  99. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +39 -4
  100. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +33 -24
  101. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +58 -8
  102. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -0
  103. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +3 -2
  104. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +7 -1
  105. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +6 -0
  106. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +58 -0
  107. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +27 -0
  108. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +32 -5
  109. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +86 -0
  110. package/src/runtime/agent/orchestrator/session/routine-result.mjs +329 -0
  111. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +8 -0
  112. package/src/runtime/agent/orchestrator/session/store-record-strict-scan.mjs +28 -9
  113. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +16 -2
  114. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -12
  115. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +46 -0
  116. package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +0 -4
  117. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +58 -10
  118. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +92 -15
  119. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +12 -0
  120. package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +113 -5
  121. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +4 -0
  122. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +25 -1
  123. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +8 -0
  124. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +6 -0
  125. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +3 -0
  126. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +75 -57
  127. package/src/runtime/attachments/store.mjs +6 -0
  128. package/src/runtime/browser-bridge/client.mjs +24 -0
  129. package/src/runtime/channels/lib/scheduler.mjs +40 -12
  130. package/src/runtime/channels/lib/schedules-db.mjs +71 -12
  131. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +36 -16
  132. package/src/runtime/channels/lib/webhook.mjs +0 -1
  133. package/src/runtime/channels/lib/webhooks-db.mjs +100 -10
  134. package/src/runtime/channels/lib/worker-main.mjs +17 -20
  135. package/src/runtime/computer-bridge/client.mjs +28 -1
  136. package/src/runtime/connectors/gmail-app-password.mjs +690 -0
  137. package/src/runtime/connectors/google-mcp.mjs +446 -0
  138. package/src/runtime/effort-judge/judge-client.mjs +1 -1
  139. package/src/runtime/effort-judge/model-manifest.json +7 -7
  140. package/src/runtime/memory/lib/core-memory-access.mjs +69 -0
  141. package/src/runtime/memory/lib/core-memory-file.mjs +16 -8
  142. package/src/runtime/memory/lib/core-memory-index.mjs +15 -9
  143. package/src/runtime/memory/lib/core-memory-management.mjs +10 -4
  144. package/src/runtime/memory/lib/core-memory-store.mjs +53 -30
  145. package/src/runtime/memory/lib/core-memory-uniqueness.mjs +10 -6
  146. package/src/runtime/memory/lib/http-router/session-routes.mjs +3 -2
  147. package/src/runtime/memory/lib/memory-action-handlers/core-actions.mjs +44 -14
  148. package/src/runtime/memory/lib/memory-schema/core-and-meta.mjs +11 -0
  149. package/src/runtime/memory/lib/memory.mjs +7 -1
  150. package/src/runtime/memory/lib/query-core-recall.mjs +9 -1
  151. package/src/runtime/memory/lib/query-hybrid-search.mjs +2 -0
  152. package/src/runtime/memory/lib/tool-call-handler.mjs +2 -0
  153. package/src/runtime/memory/tool-defs.mjs +5 -0
  154. package/src/runtime/shared/agent-avatar-sprites.mjs +445 -0
  155. package/src/runtime/shared/agent-avatar.d.mts +64 -0
  156. package/src/runtime/shared/agent-avatar.mjs +320 -0
  157. package/src/runtime/shared/agent-kind.mjs +152 -0
  158. package/src/runtime/shared/agent-route-config.d.mts +4 -0
  159. package/src/runtime/shared/agent-route-config.mjs +30 -0
  160. package/src/runtime/shared/agent-store.mjs +132 -0
  161. package/src/runtime/shared/atomic-file.mjs +14 -1
  162. package/src/runtime/shared/bot-split-migration.mjs +457 -0
  163. package/src/runtime/shared/config-patch.mjs +20 -1
  164. package/src/runtime/shared/config.mjs +8 -1
  165. package/src/runtime/shared/connector-accounts.mjs +15 -0
  166. package/src/runtime/shared/connector-catalog.d.mts +51 -0
  167. package/src/runtime/shared/connector-catalog.mjs +228 -0
  168. package/src/runtime/shared/connector-triggers.d.mts +3 -0
  169. package/src/runtime/shared/connector-triggers.mjs +45 -0
  170. package/src/runtime/shared/err-text.mjs +3 -1
  171. package/src/runtime/shared/error-presentation.mjs +91 -6
  172. package/src/runtime/shared/google-oauth-client.mjs +24 -0
  173. package/src/runtime/shared/handoff-artifacts.d.mts +20 -0
  174. package/src/runtime/shared/handoff-artifacts.mjs +61 -0
  175. package/src/runtime/shared/held-request-host.d.mts +12 -0
  176. package/src/runtime/shared/held-request-host.mjs +71 -0
  177. package/src/runtime/shared/llm/provider-model-identities.mjs +6 -1
  178. package/src/runtime/shared/llm/usage-accounting.mjs +7 -1
  179. package/src/runtime/shared/model-route-fallback.mjs +79 -7
  180. package/src/runtime/shared/plain-preview.d.mts +1 -0
  181. package/src/runtime/shared/plain-preview.mjs +33 -0
  182. package/src/runtime/shared/routine-limits.d.mts +11 -0
  183. package/src/runtime/shared/routine-limits.mjs +21 -0
  184. package/src/runtime/shared/routine-trigger.d.mts +10 -0
  185. package/src/runtime/shared/routine-trigger.mjs +44 -0
  186. package/src/runtime/shared/schedule-model-ref.mjs +9 -10
  187. package/src/runtime/shared/sensitive-path-rules.d.mts +15 -0
  188. package/src/runtime/shared/sensitive-path-rules.mjs +256 -0
  189. package/src/runtime/shared/slow-span.mjs +4 -12
  190. package/src/runtime/shared/statusline/statusline-segments.mjs +13 -1
  191. package/src/runtime/shared/transcript-metadata.mjs +10 -0
  192. package/src/runtime/shared/turn-changes.mjs +0 -8
  193. package/src/runtime/shared/usage-readout-totals.mjs +58 -0
  194. package/src/runtime/shared/user-data-guard.mjs +119 -38
  195. package/src/session-runtime/agent-home-delivery.mjs +826 -0
  196. package/src/session-runtime/agent-profile-changes.mjs +21 -0
  197. package/src/session-runtime/agent-room-delivery.mjs +133 -0
  198. package/src/session-runtime/agent-task-sessions.mjs +460 -0
  199. package/src/session-runtime/boot/apis.mjs +63 -18
  200. package/src/session-runtime/boot/facade.mjs +5 -8
  201. package/src/session-runtime/boot/infrastructure.mjs +28 -17
  202. package/src/session-runtime/boot/providers.mjs +14 -4
  203. package/src/session-runtime/boot/tools.mjs +3 -0
  204. package/src/session-runtime/channel-config-api.mjs +53 -5
  205. package/src/session-runtime/config-lifecycle.mjs +43 -2
  206. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +36 -20
  207. package/src/session-runtime/cwd-plugins/plugin-status.mjs +6 -1
  208. package/src/session-runtime/handoff-attachments.mjs +593 -0
  209. package/src/session-runtime/held-requests.mjs +228 -0
  210. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +121 -0
  211. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +5 -1
  212. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +31 -2
  213. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +56 -3
  214. package/src/session-runtime/internal-tool-executor.mjs +2 -0
  215. package/src/session-runtime/lifecycle/session-catalog.mjs +9 -0
  216. package/src/session-runtime/lifecycle/session-switch/resume.mjs +4 -0
  217. package/src/session-runtime/lifecycle/shared.mjs +6 -0
  218. package/src/session-runtime/mcp-glue/server-catalog.mjs +8 -0
  219. package/src/session-runtime/mcp-glue/server-input.mjs +3 -0
  220. package/src/session-runtime/mcp-oauth-login.mjs +112 -15
  221. package/src/session-runtime/model-route/route-persist.mjs +2 -2
  222. package/src/session-runtime/model-route/route-tuning.mjs +7 -1
  223. package/src/session-runtime/model-route/set-route.mjs +35 -4
  224. package/src/session-runtime/native-web-search.mjs +2 -2
  225. package/src/session-runtime/new-session-config.mjs +6 -1
  226. package/src/session-runtime/owned-automation-run.mjs +229 -0
  227. package/src/session-runtime/resource-api.mjs +5 -0
  228. package/src/session-runtime/resource-mcp-api.mjs +107 -13
  229. package/src/session-runtime/resource-skills-api.mjs +12 -0
  230. package/src/session-runtime/room-member-gate.mjs +48 -0
  231. package/src/session-runtime/route-resolution.mjs +5 -11
  232. package/src/session-runtime/routine-home-turn.mjs +156 -0
  233. package/src/session-runtime/routine-owner.mjs +48 -0
  234. package/src/session-runtime/schedule-session-run.mjs +30 -64
  235. package/src/session-runtime/services/agent-tool/helpers.mjs +30 -1
  236. package/src/session-runtime/services/agent-tool/send-flow.mjs +11 -3
  237. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +19 -4
  238. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +2 -0
  239. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
  240. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +5 -8
  241. package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
  242. package/src/session-runtime/services/channel-admin.mjs +93 -24
  243. package/src/session-runtime/services/memory-access.mjs +37 -0
  244. package/src/session-runtime/services/provider-admin.mjs +74 -39
  245. package/src/session-runtime/services/schedule-access.mjs +91 -0
  246. package/src/session-runtime/session-create/session-options.mjs +34 -7
  247. package/src/session-runtime/session-hooks.mjs +16 -8
  248. package/src/session-runtime/setup-tool/executor.mjs +79 -6
  249. package/src/session-runtime/setup-tool/extended-actions.mjs +90 -20
  250. package/src/session-runtime/setup-tool/settings-contract.mjs +14 -6
  251. package/src/session-runtime/setup-tool/tool-defs.mjs +8 -0
  252. package/src/session-runtime/skills-api.mjs +16 -1
  253. package/src/session-runtime/tool-policy-refresh.mjs +249 -29
  254. package/src/session-runtime/turn/session-ops.mjs +8 -1
  255. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +10 -2
  256. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +14 -0
  257. package/src/session-runtime/webhook-session-run.mjs +49 -67
  258. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +239 -0
  259. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +650 -25
  260. package/src/session-runtime/workflow-agents-api/agent-route.mjs +67 -7
  261. package/src/session-runtime/workflow-agents-api/onboarding.mjs +11 -4
  262. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +11 -1
  263. package/src/session-runtime/workflow-agents-api.mjs +4 -1
  264. package/src/standalone/daemon-agent-control.mjs +1 -8
  265. package/src/standalone/daemon-session-control.mjs +21 -4
  266. package/src/standalone/daemon.mjs +31 -5
  267. package/src/standalone/session-protocol.mjs +27 -0
  268. package/src/standalone/session-service/agent-tree/agent-turns.mjs +46 -3
  269. package/src/standalone/session-service/agent-tree.mjs +4 -3
  270. package/src/standalone/session-service/session-calls/session-turns.mjs +28 -0
  271. package/src/standalone/session-service.mjs +19 -0
  272. package/src/tui/app/slash-commands.mjs +20 -0
  273. package/src/tui/dist/index.mjs +234 -95
  274. package/src/tui/session/context-state.mjs +6 -1
  275. package/src/tui/session/notice-surface.mjs +34 -4
  276. package/src/tui/session/queue-helpers.mjs +2 -1
  277. package/src/tui/session/session-api/extensions.mjs +4 -0
  278. package/src/tui/session/session-api/intake/submission.mjs +5 -0
  279. package/src/tui/session/session-api/integrations.mjs +8 -0
  280. package/src/tui/session/session-api/lifecycle/session-reset.mjs +13 -3
  281. package/src/tui/session/session-api/lifecycle.mjs +17 -1
  282. package/src/tui/session/session-api/routes.mjs +61 -5
  283. package/src/tui/session/session-flow/drain.mjs +12 -3
  284. package/src/tui/session/session-flow/queue/entry-shape.mjs +2 -1
  285. package/src/tui/session/session-flow/queue/take-entries.mjs +3 -2
  286. package/src/tui/session/session-flow/steering.mjs +58 -24
  287. package/src/tui/session/tool-approval.mjs +26 -5
  288. package/src/tui/session/transcript-intake.mjs +8 -2
  289. package/src/tui/session/transcript-spill.mjs +5 -1
  290. package/src/tui/session/tui-steering-persist.mjs +65 -42
  291. package/src/tui/session/turn-live-call.mjs +64 -0
  292. package/src/tui/session/turn.mjs +34 -8
  293. package/src/session-runtime/automation-agents.mjs +0 -94
  294. package/src/session-runtime/automation-workflow.mjs +0 -59
@@ -0,0 +1,228 @@
1
+ // The built-in connector catalog: services a user connects with one click
2
+ // from Desktop (Extensions → 커넥터) or from a chat card the setup tool's
3
+ // `connect_connector` action surfaces. Each entry declares its backend
4
+ // explicitly:
5
+ // oauth — a remote MCP server that signs in with MCP OAuth;
6
+ // apiKey — an MCP server that takes one key (an HTTP header or a stdio
7
+ // environment variable).
8
+ // GitHub is not a connector: it is the signed-in GitHub CLI (github tool).
9
+ // A connected catalog service is an ordinary user MCP server whose config
10
+ // carries `connector: <id>`. An existing custom server is never adopted into
11
+ // the catalog row.
12
+
13
+ import { connectorTrigger } from './connector-triggers.mjs';
14
+
15
+ // `oauthClient` names a pre-registered client the runtime supplies
16
+ // (mcp/oauth-client.mjs: 'google') in place of dynamic client registration;
17
+ // `scopes` are then the ones requested.
18
+ const oauth = (url, client = {}) => Object.freeze({ kind: 'oauth', url, ...client });
19
+
20
+ const GOOGLE_SCOPE = 'https://www.googleapis.com/auth';
21
+ // The Google access each built-in connector asks for: the one place to widen
22
+ // it (e.g. gmail.readonly). openid + email name the signed-in account.
23
+ export const GOOGLE_SCOPES = Object.freeze({
24
+ gmail: Object.freeze(['openid', 'email', `${GOOGLE_SCOPE}/gmail.send`]),
25
+ calendar: Object.freeze(['openid', 'email', `${GOOGLE_SCOPE}/calendar.events`]),
26
+ drive: Object.freeze(['openid', 'email', `${GOOGLE_SCOPE}/drive.file`]),
27
+ });
28
+ // Gmail / Calendar / Drive are Mixdog's built-in Google connectors
29
+ // (runtime/connectors/google-mcp.mjs), answered in-process at a reserved
30
+ // address and signed in with Mixdog's Google client. `askTools` ask before
31
+ // they run unless the agent set them.
32
+ const google = (service, scopes, askTools) =>
33
+ oauth(`https://${service}.mixdog.invalid/mcp`, { oauthClient: 'google', scopes, askTools });
34
+ // Gmail can also connect with a Google app password instead of the sign-in:
35
+ // IMAP and SMTP over TLS (runtime/connectors/gmail-app-password.mjs), answered
36
+ // in-process at its own reserved address. Its server is still the Gmail
37
+ // connector (`connector: 'gmail'`, so Gmail's askTools apply to its tools of
38
+ // the same names); the account rides in a header, the app password lives only
39
+ // in the keychain.
40
+ export const GMAIL_APP_PASSWORD = Object.freeze({
41
+ url: 'https://gmail-imap.mixdog.invalid/mcp',
42
+ accountHeader: 'X-Mixdog-Gmail-Account',
43
+ helpUrl: 'https://myaccount.google.com/apppasswords',
44
+ });
45
+ const apiKey = (spec) => Object.freeze({ kind: 'apiKey', ...spec });
46
+
47
+ export const CONNECTOR_CATALOG = Object.freeze(
48
+ [
49
+ { id: 'airtable', name: 'Airtable', backend: oauth('https://mcp.airtable.com/mcp') },
50
+ { id: 'confluence', name: 'Confluence', backend: oauth('https://mcp.atlassian.com/v1/mcp/authv2') },
51
+ { id: 'dropbox', name: 'Dropbox', backend: oauth('https://mcp.dropbox.com/mcp') },
52
+ {
53
+ id: 'gmail',
54
+ name: 'Gmail',
55
+ backend: google('gmail', GOOGLE_SCOPES.gmail, ['send_email']),
56
+ },
57
+ // Calendar and Drive are offered only to a user who entered their own
58
+ // Google OAuth client (Connectors → add custom → 내 Google 키): `ownKey`.
59
+ // Existing connections keep working; Mixy and the chat cards never offer them.
60
+ {
61
+ id: 'google-calendar',
62
+ name: 'Google Calendar',
63
+ ownKey: true,
64
+ backend: google('calendar', GOOGLE_SCOPES.calendar, ['delete_event']),
65
+ },
66
+ {
67
+ id: 'google-drive',
68
+ name: 'Google Drive',
69
+ ownKey: true,
70
+ backend: google('drive', GOOGLE_SCOPES.drive, []),
71
+ },
72
+ {
73
+ id: 'hugging-face',
74
+ name: 'Hugging Face',
75
+ backend: apiKey({
76
+ url: 'https://huggingface.co/mcp',
77
+ header: 'Authorization',
78
+ prefix: 'Bearer ',
79
+ keyUrl: 'https://huggingface.co/settings/tokens',
80
+ }),
81
+ },
82
+ { id: 'jira', name: 'Jira', backend: oauth('https://mcp.atlassian.com/v1/mcp/authv2') },
83
+ { id: 'linear', name: 'Linear', backend: oauth('https://mcp.linear.app/mcp') },
84
+ { id: 'notion', name: 'Notion', backend: oauth('https://mcp.notion.com/mcp') },
85
+ {
86
+ id: 'postgres',
87
+ name: 'Postgres',
88
+ backend: apiKey({
89
+ command: 'npx',
90
+ args: ['-y', '@microsoft/postgres-mcp@0.2.0', 'run'],
91
+ env: 'POSTGRES_MCP_CONNECTION_STRING',
92
+ keyUrl: 'https://github.com/microsoft/postgres-mcp/blob/main/USAGE.md',
93
+ placeholder: 'postgresql://user:password@host:5432/db',
94
+ }),
95
+ },
96
+ { id: 'sentry', name: 'Sentry', backend: oauth('https://mcp.sentry.dev/mcp') },
97
+ {
98
+ id: 'stripe',
99
+ name: 'Stripe',
100
+ backend: apiKey({
101
+ url: 'https://mcp.stripe.com',
102
+ header: 'Authorization',
103
+ prefix: 'Bearer ',
104
+ keyUrl: 'https://dashboard.stripe.com/apikeys',
105
+ placeholder: 'rk_live_…',
106
+ }),
107
+ },
108
+ { id: 'supabase', name: 'Supabase', backend: oauth('https://mcp.supabase.com/mcp') },
109
+ { id: 'trello', name: 'Trello', backend: oauth('https://mcp.trello.com/v1') },
110
+ { id: 'vercel', name: 'Vercel', backend: oauth('https://mcp.vercel.com') },
111
+ // The ids are the deferred-tool manifest's curated keys: a server named by
112
+ // its id gets that one-line trigger (connector-triggers.mjs).
113
+ ].map((entry) => Object.freeze({ ...entry, trigger: connectorTrigger(entry.id) }))
114
+ );
115
+
116
+ /** The ids offered to everyone (the setup tool's `connect_connector`): the
117
+ * catalog minus the own-key-only Google services. */
118
+ export const CONNECTOR_IDS = Object.freeze(CONNECTOR_CATALOG.filter((entry) => !entry.ownKey).map((entry) => entry.id));
119
+
120
+ /** The catalog entry for `id`, or null. */
121
+ export function connectorById(id) {
122
+ return CONNECTOR_CATALOG.find((entry) => entry.id === id) || null;
123
+ }
124
+
125
+ const urlKey = (url) =>
126
+ String(url || '')
127
+ .trim()
128
+ .replace(/\/+$/, '')
129
+ .toLowerCase();
130
+
131
+ /** The catalog id a configured MCP server connects: its `connector` marker,
132
+ * '' for none (or a marker the catalog no longer knows). */
133
+ export function connectorOfServer(cfg) {
134
+ const marker = typeof cfg?.connector === 'string' ? cfg.connector : '';
135
+ return connectorById(marker) ? marker : '';
136
+ }
137
+
138
+ /** The permission a catalog connector's tool has when nothing set its own:
139
+ * `needs_approval` for the high-risk tools it names, else `auto`. */
140
+ export function connectorToolDefault(connectorId, tool) {
141
+ const backend = connectorById(connectorId)?.backend;
142
+ return backend?.askTools?.includes(tool) ? 'needs_approval' : 'auto';
143
+ }
144
+
145
+ const EMAIL = /^[^\s@<>(),;:"]+@[^\s@<>(),;:"]+\.[^\s@<>(),;:".]+$/;
146
+
147
+ /** A Gmail address as typed (trimmed, lowercased), or '' when it is none. */
148
+ export function gmailAddress(value) {
149
+ const address = String(value ?? '')
150
+ .trim()
151
+ .toLowerCase();
152
+ return EMAIL.test(address) ? address : '';
153
+ }
154
+
155
+ /** A Google app password as typed, its spaces removed; '' unless it then
156
+ * has the 16 characters Google issues. */
157
+ export function gmailAppPasswordValue(value) {
158
+ const password = String(value ?? '').replace(/\s+/g, '');
159
+ return password.length === 16 ? password : '';
160
+ }
161
+
162
+ /** The account of a Gmail app-password server config, '' for any other. */
163
+ export function gmailAppPasswordAccount(cfg) {
164
+ if (urlKey(cfg?.url) !== urlKey(GMAIL_APP_PASSWORD.url)) return '';
165
+ const headers = cfg?.headers && typeof cfg.headers === 'object' ? cfg.headers : {};
166
+ const header = GMAIL_APP_PASSWORD.accountHeader.toLowerCase();
167
+ const key = Object.keys(headers).find((name) => name.toLowerCase() === header);
168
+ return key ? gmailAddress(headers[key]) : '';
169
+ }
170
+
171
+ /** The MCP server input that connects Gmail under `name` as `email` with an
172
+ * app password. `appPassword` only travels to the host, which checks it
173
+ * with Gmail and keeps it in the keychain; it never reaches the config. */
174
+ export function gmailAppPasswordServerInput(name, email, password) {
175
+ const address = gmailAddress(email);
176
+ if (!address) throw new Error('GMAIL_APP_PASSWORD_ADDRESS: Enter a Gmail address.');
177
+ const appPassword = gmailAppPasswordValue(password);
178
+ if (!appPassword) throw new Error('GMAIL_APP_PASSWORD_FORMAT: An app password has 16 characters.');
179
+ return {
180
+ name,
181
+ type: 'http',
182
+ url: GMAIL_APP_PASSWORD.url,
183
+ headers: { [GMAIL_APP_PASSWORD.accountHeader]: address },
184
+ connector: 'gmail',
185
+ appPassword,
186
+ };
187
+ }
188
+
189
+ /** Whether several accounts of catalog connector `entry` can be connected at
190
+ * once: Gmail (also by app password), Calendar and Drive with the Google
191
+ * sign-in. Other remote servers keep one (their tokens and URL are shared). */
192
+ export function connectorMultiAccount(entry) {
193
+ return entry?.backend?.kind === 'oauth' && entry.backend.oauthClient === 'google';
194
+ }
195
+
196
+ /** The MCP server input that connects catalog entry `entry` under `name`
197
+ * (oauth / apiKey); `key` fills an apiKey backend; `extraAccount`
198
+ * makes it one more account of a multi-account connector. */
199
+ export function connectorServerInput(entry, name, key = '', { extraAccount = false } = {}) {
200
+ const { backend } = entry;
201
+ if (backend.kind === 'oauth') {
202
+ // Tokens are stored per exact server URL, so another account of the same
203
+ // connector needs its own URL; the in-process Google servers ignore the query.
204
+ const url =
205
+ extraAccount && connectorMultiAccount(entry) ? `${backend.url}?account=${encodeURIComponent(name)}` : backend.url;
206
+ return { name, type: 'http', url, connector: entry.id };
207
+ }
208
+ if (backend.kind !== 'apiKey') throw new Error(`${entry.name} is not an MCP connector`);
209
+ const value = String(key || '').trim();
210
+ if (!value) throw new Error(`${entry.name} needs a key`);
211
+ if (backend.url) {
212
+ return {
213
+ name,
214
+ type: 'http',
215
+ url: backend.url,
216
+ headers: { [backend.header]: `${backend.prefix || ''}${value}` },
217
+ connector: entry.id,
218
+ };
219
+ }
220
+ return {
221
+ name,
222
+ type: 'stdio',
223
+ command: backend.command,
224
+ args: [...backend.args],
225
+ env: { [backend.env]: value },
226
+ connector: entry.id,
227
+ };
228
+ }
@@ -0,0 +1,3 @@
1
+ export declare const CONNECTOR_TRIGGER_MAX_CHARS: number;
2
+ export declare const CONNECTOR_TRIGGERS: Readonly<Record<string, string>>;
3
+ export declare function connectorTrigger(serverId: string): string;
@@ -0,0 +1,45 @@
1
+ // Curated selection triggers for catalog connectors (MCP servers). A catalog
2
+ // entry carries its line as `trigger`; the deferred-tool manifest lists each
3
+ // connected server once as `- mcp__<server id>: <trigger>`, so one line must
4
+ // say what the connector is for (plus a close negative where useful), in
5
+ // English, at most CONNECTOR_TRIGGER_MAX_CHARS characters.
6
+ export const CONNECTOR_TRIGGER_MAX_CHARS = 100;
7
+
8
+ export const CONNECTOR_TRIGGERS = Object.freeze({
9
+ // Not a catalog connector (Mixdog uses the signed-in GitHub CLI), but a
10
+ // user-added `github` MCP server still gets this line.
11
+ github: 'GitHub repos, issues, pull requests, Actions and code search; not local Git history or diffs.',
12
+ notion: 'Notion pages, databases and workspace search; create or update team docs and notes.',
13
+ linear: 'Linear issues, projects, cycles and comments; create, triage or update tickets.',
14
+ sentry: 'Sentry issues, events, stack traces and releases; triage production errors.',
15
+ gmail: 'Gmail: send email; with an app password also search, read, archive, label, star, trash.',
16
+ 'google-calendar': 'Google Calendar events on the primary calendar; list, search, create, update or delete.',
17
+ 'google-drive': 'Google Drive files Mixdog created or was given; search, read or create; not local files.',
18
+ jira: 'Jira issues, projects, sprints and comments; JQL search, create or transition tickets.',
19
+ confluence: 'Confluence spaces and pages; search, read or edit the team wiki; not Jira tickets.',
20
+ dropbox: 'Dropbox files and folders; search, read, upload or share cloud files; not local files.',
21
+ trello: 'Trello boards, lists and cards; create, move or update cards.',
22
+ airtable: 'Airtable bases, tables and records; query, create or update rows.',
23
+ stripe: 'Stripe customers, payments, invoices, subscriptions and refunds; billing data and actions.',
24
+ vercel: 'Vercel projects, deployments, domains and logs; deploy status and build errors.',
25
+ supabase: 'Supabase projects: Postgres tables and SQL, auth, storage and edge functions.',
26
+ postgres: 'PostgreSQL schemas, tables and SQL queries on a connected database; not local files.',
27
+ 'hugging-face': 'Hugging Face Hub models, datasets, Spaces and papers; not local GGUF model setup.',
28
+ });
29
+
30
+ // Server ids differ only in separators and case (google_calendar, huggingface).
31
+ const triggerKey = (id) =>
32
+ String(id || '')
33
+ .toLowerCase()
34
+ .replace(/[^a-z0-9]/g, '');
35
+ const TRIGGERS_BY_KEY = new Map(Object.entries(CONNECTOR_TRIGGERS).map(([id, line]) => [triggerKey(id), line]));
36
+
37
+ /** The curated trigger of the catalog connector `serverId`, or ''. */
38
+ export function connectorTrigger(serverId) {
39
+ // Another account of a connector is named `<id>-2`, `<id>-3`…
40
+ return (
41
+ TRIGGERS_BY_KEY.get(triggerKey(serverId)) ||
42
+ TRIGGERS_BY_KEY.get(triggerKey(String(serverId || '').replace(/[-_]\d+$/, ''))) ||
43
+ ''
44
+ );
45
+ }
@@ -237,7 +237,9 @@ function typedStreamFailureText(error, text) {
237
237
 
238
238
  export function presentErrorText(error, options = {}) {
239
239
  const presentation = describeError(error);
240
- if (['image-size', 'payload-size', 'request'].includes(presentation.kind)) return presentation.summary;
240
+ if (['image-size', 'payload-size', 'request', 'model-unavailable', 'tool-schema'].includes(presentation.kind)) {
241
+ return presentation.summary;
242
+ }
241
243
  const kindLine = hasTypedEvidence(error) ? KIND_LINES[presentation.providerKind] : null;
242
244
  if (kindLine && presentation.kind !== 'unknown') {
243
245
  return typeof kindLine === 'function' ? kindLine(presentation) : kindLine;
@@ -138,6 +138,77 @@ function resetLabel(resetAt) {
138
138
  return reset.toDateString() === new Date().toDateString() ? time : `${reset.getMonth() + 1}/${reset.getDate()} ${time}`;
139
139
  }
140
140
 
141
+ // A refused request that names its model as unknown, retired or not offered
142
+ // to this account. Read only for request-refusal kinds, so a typed overload,
143
+ // quota or auth failure never reads as a missing model.
144
+ const MODEL_REFUSAL_KINDS = new Set(['invalid_request', 'permission', 'unknown']);
145
+ const MODEL_UNAVAILABLE_CODES = new Set(['model_not_found', 'model_not_supported']);
146
+ // The model must be the subject of the refusal, at the start of a line or
147
+ // sentence ("The model `x` does not exist", "models/x is not found", Ollama's
148
+ // `model "x" not found`), or the refusal names it ("Unknown model", "… is not
149
+ // a valid model ID", Anthropic's bare "model: x"). A model merely mentioned
150
+ // beside some other missing thing ("For model x, tool lookup: route not
151
+ // found") does not count.
152
+ const MODEL_REFUSAL_PREDICATE =
153
+ "(?:(?:is|was|has been)\\s+)?(?:not found|does not exist|doesn'?t exist|not (?:supported|available)|(?:currently\\s+)?unavailable|no longer (?:available|supported)|deprecated|retired|removed)\\b";
154
+ const SENTENCE_START = '(?:^|[.:;!]\\s+)';
155
+ const MODEL_UNAVAILABLE_RE = new RegExp(
156
+ [
157
+ // "The model `x` does not exist", "models/x is not found", Vertex
158
+ // "Publisher Model `…` was not found", Ollama `model "x" not found`.
159
+ `${SENTENCE_START}(?:(?:the|this|that|requested|selected|publisher)\\s+)*models?(?:\\/\\S+|\\s+[\`'"]?[\\w./:@-]+[\`'"]?)?\\s+${MODEL_REFUSAL_PREDICATE}`,
160
+ // OpenAI "The 'gpt-x' model is not supported when using Codex …".
161
+ `${SENTENCE_START}(?:the\\s+)?[\`'"][^\`'"\\n]+[\`'"]\\s+model\\s+${MODEL_REFUSAL_PREDICATE}`,
162
+ `${SENTENCE_START}(?:unknown|invalid|unsupported) model\\b`,
163
+ // OpenRouter: no provider serves this model id.
164
+ `${SENTENCE_START}no endpoints found for [\\w.-]+\\/[\\w.:-]+`,
165
+ '\\bis not a valid model\\b',
166
+ '^model: \\S+$',
167
+ ].join('|'),
168
+ 'im'
169
+ );
170
+
171
+ /**
172
+ * True when `error` (or its text) says the requested model is unknown,
173
+ * retired or not offered to this account: a typed model code, or provider
174
+ * wording with the model as the subject of the refusal.
175
+ */
176
+ export function namesUnavailableModel(error) {
177
+ if (!error) return false;
178
+ if (typeof error === 'string') return MODEL_UNAVAILABLE_RE.test(error);
179
+ const codes = [error.code, error.providerErrorCode, error.error?.code, error.providerError?.code];
180
+ if (codes.some((code) => MODEL_UNAVAILABLE_CODES.has(String(code || '').toLowerCase()))) return true;
181
+ return [error.message, error.error?.message, error.providerError?.message, error.detail]
182
+ .filter((text) => typeof text === 'string' && text)
183
+ .some((text) => MODEL_UNAVAILABLE_RE.test(text));
184
+ }
185
+ const MODEL_UNAVAILABLE_COPY = [
186
+ 'model-unavailable',
187
+ "This model isn't available for this account.",
188
+ 'Choose another model, or check the model and endpoint in Providers.',
189
+ ];
190
+ const NOT_FOUND_SUMMARY = "The provider couldn't find the requested model or endpoint.";
191
+ // A tool definition the provider's schema validator refused (an MCP or custom
192
+ // tool schema), named by the field path or wording each API uses.
193
+ const TOOL_SCHEMA_RE =
194
+ /invalid schema for (?:function|tool)\b|\btools\.\d+\.(?:custom\.)?input_schema\b|\bfunction_declarations\[\d+\]\.parameters\b|\btools\[\d+\]\.function\.parameters\b|\binvalid (?:tool|function) (?:schema|parameters|definition)\b/i;
195
+ const TOOL_SCHEMA_COPY = [
196
+ 'tool-schema',
197
+ 'The provider rejected a tool definition.',
198
+ 'Disable the tool or MCP server named in the details, then try again.',
199
+ ];
200
+
201
+ // The first line of a provider message worth showing as-is; '' for a stack
202
+ // frame, a markup page or a raw JSON body.
203
+ function firstReadableLine(clean) {
204
+ const lines = clean
205
+ .split('\n')
206
+ .map((line) => line.trim())
207
+ .filter(Boolean);
208
+ const first = lines.find((line) => !/^at\s|^\{|^<!doctype|^<html/i.test(line)) || '';
209
+ return first && !/^[{[]|^<|(?:API|HTTP)\s+[45]\d\d.*[<{]/i.test(first) ? first : '';
210
+ }
211
+
141
212
  // Only legacy items (no persisted descriptor) and untyped strings reach this.
142
213
  const LEGACY_TEXT_KINDS = [
143
214
  [/The provider usage limit was reached|quota.*(?:hit|exceed)|usage limit/i, 'quota_exhausted'],
@@ -241,6 +312,25 @@ export function describeError(value) {
241
312
  kind = 'image-size';
242
313
  summary = 'An attached image exceeds the provider limit.';
243
314
  recovery = 'Reduce the image dimensions or number of images, then try again.';
315
+ } else if (
316
+ clean === MODEL_UNAVAILABLE_COPY[1] ||
317
+ (MODEL_REFUSAL_KINDS.has(descriptor.kind) &&
318
+ (MODEL_UNAVAILABLE_CODES.has(code) || MODEL_UNAVAILABLE_RE.test(clean)))
319
+ ) {
320
+ [kind, summary, recovery] = MODEL_UNAVAILABLE_COPY;
321
+ } else if (
322
+ clean === TOOL_SCHEMA_COPY[1] ||
323
+ (['invalid_request', 'unknown'].includes(descriptor.kind) && TOOL_SCHEMA_RE.test(clean))
324
+ ) {
325
+ [kind, summary, recovery] = TOOL_SCHEMA_COPY;
326
+ } else if (
327
+ clean === NOT_FOUND_SUMMARY ||
328
+ (MODEL_REFUSAL_KINDS.has(descriptor.kind) && (status === 404 || status === 410))
329
+ ) {
330
+ // The provider's own words name what is missing; the next step is the same.
331
+ kind = 'not-found';
332
+ summary = firstReadableLine(clean) || NOT_FOUND_SUMMARY;
333
+ recovery = MODEL_UNAVAILABLE_COPY[2];
244
334
  } else if (
245
335
  descriptor.kind === 'unknown' &&
246
336
  /payload too large|request (?:body )?too large|The request exceeds the size limit/i.test(clean)
@@ -272,12 +362,7 @@ export function describeError(value) {
272
362
  summary = 'The provider rejected this request.';
273
363
  recovery = 'Review the request details before trying again.';
274
364
  } else {
275
- const lines = clean
276
- .split('\n')
277
- .map((line) => line.trim())
278
- .filter(Boolean);
279
- const first = lines.find((line) => !/^at\s|^\{|^<!doctype|^<html/i.test(line)) || '';
280
- summary = first && !/^[{[]|^<|(?:API|HTTP)\s+[45]\d\d.*[<{]/i.test(first) ? first : 'Something went wrong.';
365
+ summary = firstReadableLine(clean) || 'Something went wrong.';
281
366
  }
282
367
  summary = summary.replace(/\s+/g, ' ').trim() || 'Something went wrong.';
283
368
  if (summary.length > SUMMARY_LIMIT) summary = `${summary.slice(0, SUMMARY_LIMIT - 1).trimEnd()}…`;
@@ -0,0 +1,24 @@
1
+ // Mixdog's own Google OAuth client (a "Desktop app" client, marked "Use this
2
+ // client for an AI-powered agent"), used by the Gmail, Google Calendar and
3
+ // Google Drive catalog connectors instead of dynamic client registration,
4
+ // which Google does not offer. Google treats desktop clients as public: the
5
+ // id and its non-confidential secret ship with the app, and every sign-in
6
+ // uses PKCE (S256), state and a loopback redirect
7
+ // (developers.google.com/identity/protocols/oauth2/native-app).
8
+ //
9
+ // The values come only from the environment; the source ships none, so a
10
+ // build without them offers no built-in Google sign-in (the user's own
11
+ // Google client still works).
12
+
13
+ const value = (name) => String(process.env[name] || '').trim();
14
+
15
+ /** `{ clientId, clientSecret }` of Mixdog's Google client, or null when this
16
+ * build has none. */
17
+ export function googleOAuthClient() {
18
+ const clientId = value('MIXDOG_GOOGLE_OAUTH_CLIENT_ID');
19
+ if (!clientId) return null;
20
+ return { clientId, clientSecret: value('MIXDOG_GOOGLE_OAUTH_CLIENT_SECRET') };
21
+ }
22
+
23
+ /** Google issues a refresh token only for `access_type=offline`. */
24
+ export const GOOGLE_AUTH_PARAMS = Object.freeze({ access_type: 'offline' });
@@ -0,0 +1,20 @@
1
+ export interface HandoffFile {
2
+ name: string;
3
+ path: string;
4
+ kind: 'image' | 'file' | 'text';
5
+ mimeType: string;
6
+ size: number;
7
+ /** Attachment-store reference when the file was stored. */
8
+ id?: string;
9
+ }
10
+ export function normalizeHandoffFiles(files: unknown): HandoffFile[];
11
+ export function artifactLines(files: unknown, omitted?: number, options?: { ids?: boolean }): string[];
12
+ export interface HandoffRecord {
13
+ id: string;
14
+ attachmentRef: string;
15
+ kind: 'image' | 'file' | 'text';
16
+ name: string;
17
+ mimeType: string;
18
+ size: number;
19
+ }
20
+ export function artifactRecords(files: unknown): HandoffRecord[];
@@ -0,0 +1,61 @@
1
+ // The files a bot's or agent's turn handed back (session-runtime/
2
+ // handoff-attachments.mjs collectTurnArtifacts) as the rows that carry them
3
+ // show them: one model-facing line per file — the attachment id that forwards
4
+ // it, or its path when it is too large or of a type no prompt carries — and
5
+ // the message attachment records of the stored ones. Only those records
6
+ // authorize forwarding by id; the lines are information for the model.
7
+ import { escapeHarnessTags } from './shell-output-summary.mjs';
8
+ import { normalizeMessageAttachments } from './transcript-metadata.mjs';
9
+
10
+ const sizeLabel = (size) => {
11
+ const bytes = Number(size) || 0;
12
+ if (bytes >= 1_048_576) return `${(bytes / 1_048_576).toFixed(1)} MB`;
13
+ return bytes >= 1024 ? `${Math.round(bytes / 1024)} KB` : `${bytes} B`;
14
+ };
15
+
16
+ const oneLine = (value) => escapeHarnessTags(String(value ?? '').replace(/[\r\n]+/g, ' '));
17
+
18
+ /** `files` ({ name, mimeType, size, path, id? }) normalized: bounded strings,
19
+ * ids only of the attachment-store shape. */
20
+ export function normalizeHandoffFiles(files) {
21
+ return (Array.isArray(files) ? files : [])
22
+ .filter((file) => file && typeof file === 'object' && typeof file.path === 'string' && file.path)
23
+ .slice(0, 8)
24
+ .map((file) => ({
25
+ name: String(file.name || 'file').slice(0, 200),
26
+ path: file.path.slice(0, 1024),
27
+ kind: ['image', 'file', 'text'].includes(file.kind) ? file.kind : 'file',
28
+ mimeType: String(file.mimeType || 'application/octet-stream').slice(0, 100),
29
+ size: Number(file.size) >= 0 ? Number(file.size) : 0,
30
+ ...(/^[a-f0-9]{64}$/.test(String(file.id ?? '')) ? { id: file.id } : {}),
31
+ }));
32
+ }
33
+
34
+ /** The model-facing list of `files`, empty when there are none. `ids`: the
35
+ * row also records the stored files as its attachments (a task report), so
36
+ * their ids can be forwarded; a plain-text handoff (an agent completion)
37
+ * lists paths only — an id named in text is never trusted. */
38
+ export function artifactLines(files, omitted = 0, { ids = true } = {}) {
39
+ const list = normalizeHandoffFiles(files);
40
+ if (!list.length) return [];
41
+ const attachable = (file) => (ids ? ` [attachment:${file.id}]` : ' (attachable: forward it by path)');
42
+ return [
43
+ `Files from this turn (forward one with attachments: [${ids ? 'id or path' : 'path'}]; link a path to show it):`,
44
+ ...list.map(
45
+ (file) =>
46
+ `- ${oneLine(file.name)} (${oneLine(file.mimeType)}, ${sizeLabel(file.size)}) — ${oneLine(file.path)}${
47
+ file.id ? attachable(file) : ' (too large or unsupported to attach; link the path)'
48
+ }`
49
+ ),
50
+ ...(omitted > 0 ? [`- …and ${omitted} more file(s)`] : []),
51
+ ];
52
+ }
53
+
54
+ /** The message attachment records of `files` (the stored ones). */
55
+ export function artifactRecords(files) {
56
+ return normalizeMessageAttachments(
57
+ normalizeHandoffFiles(files)
58
+ .filter((file) => file.id)
59
+ .map((file) => ({ ...file, attachmentRef: file.id }))
60
+ );
61
+ }
@@ -0,0 +1,12 @@
1
+ export interface HeldRequestPushEvent {
2
+ sessionId: string;
3
+ agentId: string;
4
+ agentName: string;
5
+ requestId: string;
6
+ }
7
+ export function registerHeldRequestResolver(resolver: (id: string, decision: unknown) => boolean): () => void;
8
+ export function resolveHeldRequest(id: string, decision: unknown): boolean;
9
+ export function registerHeldRequestNotifier(notifier: (event: HeldRequestPushEvent) => void): () => void;
10
+ export function notifyHeldRequest(event: Partial<HeldRequestPushEvent>): void;
11
+ export function recordRunOwner(sessionId: string, agentId: string): void;
12
+ export function runOwnerOf(sessionId: string): string;
@@ -0,0 +1,71 @@
1
+ // Runtime ↔ desktop-service channel for held requests of unattended runs
2
+ // (session-runtime/held-requests.mjs). The daemon's held-request registry
3
+ // registers the resolver; the desktop service hosted in this daemon registers
4
+ // a notifier that sends the sanitized phone push. The session API answers a
5
+ // held request through resolveHeldRequest, whatever session it was sent from.
6
+ //
7
+ // Deliberately dependency-free: the desktop service bundle imports this file
8
+ // too, and both copies share one registry through a global symbol.
9
+
10
+ const KEY = Symbol.for('mixdog.heldRequestHost.v1');
11
+
12
+ function registry() {
13
+ globalThis[KEY] ??= { resolver: null, notifiers: [], runOwners: new Map() };
14
+ return globalThis[KEY];
15
+ }
16
+
17
+ const RUN_OWNER_LIMIT = 500;
18
+ /** Remember which agent an unattended run session belongs to, so its pushes
19
+ * follow that agent's notification mute after the run ends (bounded, newest kept). */
20
+ export function recordRunOwner(sessionId, agentId) {
21
+ const owners = registry().runOwners;
22
+ owners.delete(sessionId);
23
+ owners.set(String(sessionId), String(agentId));
24
+ while (owners.size > RUN_OWNER_LIMIT) owners.delete(owners.keys().next().value);
25
+ }
26
+
27
+ export function runOwnerOf(sessionId) {
28
+ return registry().runOwners.get(String(sessionId || '')) || '';
29
+ }
30
+
31
+ /** `resolver(id, decision) → boolean` (true when `id` was a pending held request). */
32
+ export function registerHeldRequestResolver(resolver) {
33
+ registry().resolver = resolver;
34
+ return () => {
35
+ if (registry().resolver === resolver) registry().resolver = null;
36
+ };
37
+ }
38
+
39
+ export function resolveHeldRequest(id, decision) {
40
+ const { resolver } = registry();
41
+ return typeof resolver === 'function' && String(id || '').startsWith('held_')
42
+ ? resolver(id, decision) === true
43
+ : false;
44
+ }
45
+
46
+ /** `notifier({ sessionId, agentId, agentName, requestId })`: one per desktop service. */
47
+ export function registerHeldRequestNotifier(notifier) {
48
+ const entry = { notifier };
49
+ registry().notifiers.push(entry);
50
+ return () => {
51
+ const list = registry().notifiers;
52
+ const index = list.indexOf(entry);
53
+ if (index >= 0) list.splice(index, 1);
54
+ };
55
+ }
56
+
57
+ /** Hand one held request to every registered notifier. Carries ids and the
58
+ * agent's name only: never the tool, its arguments or any text. */
59
+ export function notifyHeldRequest({ sessionId, agentId, agentName, requestId }) {
60
+ const event = {
61
+ sessionId: String(sessionId || ''),
62
+ agentId: String(agentId || ''),
63
+ agentName: String(agentName || ''),
64
+ requestId: String(requestId || ''),
65
+ };
66
+ for (const { notifier } of registry().notifiers) {
67
+ try {
68
+ notifier(event);
69
+ } catch {}
70
+ }
71
+ }
@@ -28,7 +28,12 @@ export const ANTIGRAVITY_MODELS = Object.freeze([
28
28
  low: 'gemini-3.1-pro-low',
29
29
  high: 'gemini-3.1-pro-high',
30
30
  }),
31
- fallbackModel('gemini-4-argon', 'Gemini 4 Argon', 'gemini-pro', 1048576, 'gemini-4-argon', ['low', 'medium', 'high']),
31
+ // The gateway serves Argon only through per-tier ids; bare `gemini-4-argon` is a 404.
32
+ fallbackModel('gemini-4-argon', 'Gemini 4 Argon', 'gemini-pro', 1048576, {
33
+ low: 'gemini-4-argon-low',
34
+ medium: 'gemini-4-argon-medium',
35
+ high: 'gemini-4-argon-high',
36
+ }),
32
37
  fallbackModel('claude-opus-4-6-thinking', 'Claude Opus 4.6 (Thinking)', null, 250000, 'claude-opus-4-6-thinking', [
33
38
  'low',
34
39
  'medium',
@@ -63,6 +63,12 @@ export async function accountProviderSend(provider, instance, send, model, opts
63
63
  if (!ledger) return;
64
64
  recorded.add(owner);
65
65
  const usage = result.usage || UNMEASURED_USAGE;
66
+ const durationMs = timed ? Date.now() - startedAt : 0;
67
+ // The one measured request time: the loop's speed figures read it, so
68
+ // the composer and the ledger show the same span.
69
+ if (timed && result && typeof result === 'object') {
70
+ Object.defineProperty(result, 'usageDurationMs', { value: durationMs, configurable: true, writable: true });
71
+ }
66
72
  const row = makeUsageRecord({
67
73
  id: result.responseId ? undefined : id,
68
74
  ts: Date.now(),
@@ -88,7 +94,7 @@ export async function accountProviderSend(provider, instance, send, model, opts
88
94
  // The account this send is bound to, so quota history can tell one
89
95
  // connected subscription's records from another's.
90
96
  account: ACCOUNT_PROVIDERS.includes(provider) ? currentProviderAccountId(provider) : '',
91
- durationMs: timed ? Date.now() - startedAt : 0,
97
+ durationMs,
92
98
  });
93
99
  // Committed by the ledger worker (batched with concurrent sends); the
94
100
  // send still settles only once its row is durable, as before, but the