mixdog 1.3.4 → 1.3.5

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 (197) hide show
  1. package/LICENSES/oh-my-pi-MIT.txt +23 -0
  2. package/NOTICE.md +15 -0
  3. package/README.md +11 -0
  4. package/package.json +2 -1
  5. package/scripts/embed-google-oauth-client.mjs +49 -0
  6. package/scripts/lib/test-spawn-ledger.mjs +46 -1
  7. package/scripts/test.mjs +5 -0
  8. package/src/defaults/shipped-history.json +8 -0
  9. package/src/defaults/skills/browser-use/SKILL.md +21 -9
  10. package/src/defaults/skills/setup/SKILL.md +7 -2
  11. package/src/defaults/skills/setup/references/actions.md +82 -27
  12. package/src/defaults/skills/setup/references/surfaces.md +67 -34
  13. package/src/defaults/skills/setup-guide/SKILL.md +3 -3
  14. package/src/lib/keychain-cjs.cjs +359 -29
  15. package/src/rules/agent-mode/AGENT_MODE.md +10 -2
  16. package/src/rules/agent-mode/MIXY.md +12 -0
  17. package/src/runtime/agent/orchestrator/agent-runtime/heartbeat-completion.mjs +193 -0
  18. package/src/runtime/agent/orchestrator/config.mjs +18 -0
  19. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +2 -2
  20. package/src/runtime/agent/orchestrator/mcp/client.mjs +6 -2
  21. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +31 -2
  22. package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +16 -3
  23. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +42 -7
  24. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +21 -2
  25. package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +23 -0
  26. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +11 -1
  27. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +68 -23
  28. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +22 -1
  29. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +18 -5
  30. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  31. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -3
  32. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +52 -8
  33. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +10 -1
  34. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +22 -6
  35. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +15 -4
  36. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +17 -2
  37. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +19 -2
  38. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  39. package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +10 -4
  40. package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +10 -217
  41. package/src/runtime/agent/orchestrator/providers/cursor.mjs +7 -0
  42. package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +409 -0
  43. package/src/runtime/agent/orchestrator/providers/devin-wire-families.mjs +187 -0
  44. package/src/runtime/agent/orchestrator/providers/devin-wire-identity.mjs +342 -0
  45. package/src/runtime/agent/orchestrator/providers/devin-wire-models.mjs +541 -0
  46. package/src/runtime/agent/orchestrator/providers/devin-wire-protobuf.mjs +305 -0
  47. package/src/runtime/agent/orchestrator/providers/devin-wire-request.mjs +558 -0
  48. package/src/runtime/agent/orchestrator/providers/devin-wire-schema.mjs +880 -0
  49. package/src/runtime/agent/orchestrator/providers/devin-wire-session.mjs +190 -0
  50. package/src/runtime/agent/orchestrator/providers/devin-wire-tool-args.mjs +351 -0
  51. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +560 -0
  52. package/src/runtime/agent/orchestrator/providers/devin.mjs +491 -0
  53. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +3 -0
  54. package/src/runtime/agent/orchestrator/providers/gemini-output-limit.mjs +46 -0
  55. package/src/runtime/agent/orchestrator/providers/gemini-reasoning-relay.mjs +30 -0
  56. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +7 -11
  57. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +61 -22
  58. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -0
  59. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -4
  60. package/src/runtime/agent/orchestrator/providers/gemini.mjs +25 -2
  61. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  62. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +96 -5
  63. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +23 -5
  64. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +17 -0
  65. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +39 -3
  66. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
  67. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
  68. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +6 -1
  69. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +1 -0
  70. package/src/runtime/agent/orchestrator/providers/provider-availability.mjs +19 -0
  71. package/src/runtime/agent/orchestrator/providers/registry.mjs +17 -3
  72. package/src/runtime/agent/orchestrator/providers/wire-protobuf-codec.mjs +368 -0
  73. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
  74. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +1 -1
  75. package/src/runtime/agent/orchestrator/runtime-core/reaction-tool-defs.mjs +133 -0
  76. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  77. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +23 -5
  78. package/src/runtime/agent/orchestrator/runtime-core/tool-profile.mjs +35 -10
  79. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +57 -10
  80. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +9 -6
  81. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -1
  82. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +18 -0
  83. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +0 -3
  84. package/src/runtime/agent/orchestrator/session/agent-room.mjs +10 -4
  85. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +15 -3
  86. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +5 -0
  87. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +36 -4
  88. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +6 -0
  89. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +15 -3
  90. package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +8 -0
  91. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +8 -1
  92. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -1
  93. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +50 -2
  94. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +3 -0
  95. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +11 -2
  96. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +11 -2
  97. package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +60 -0
  98. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +2 -0
  99. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +14 -0
  100. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -1
  101. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +13 -0
  102. package/src/runtime/agent/orchestrator/session/message-ids.mjs +5 -3
  103. package/src/runtime/agent/orchestrator/session/message-reactions.mjs +89 -0
  104. package/src/runtime/agent/orchestrator/session/message-reactions.test-support.mjs +75 -0
  105. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +0 -2
  106. package/src/runtime/agent/orchestrator/session/routine-result.mjs +11 -2
  107. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +20 -2
  108. package/src/runtime/agent/orchestrator/session/store-agent-worker-header-cache.mjs +85 -0
  109. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +125 -12
  110. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +11 -5
  111. package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +4 -0
  112. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +5 -0
  113. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +7 -1
  114. package/src/runtime/channels/lib/schedule-errors.mjs +51 -0
  115. package/src/runtime/channels/lib/scheduler.mjs +109 -14
  116. package/src/runtime/channels/lib/schedules-db.mjs +49 -0
  117. package/src/runtime/channels/lib/tool-dispatch.mjs +13 -0
  118. package/src/runtime/channels/lib/worker-main.mjs +9 -0
  119. package/src/runtime/office/com/com-adapter.mjs +17 -1
  120. package/src/runtime/office/com/office-com-host.ps1 +51 -1
  121. package/src/runtime/office/pdf/cfb-fixture.mjs +328 -0
  122. package/src/runtime/office/pdf/cfb-protection.mjs +311 -0
  123. package/src/runtime/office/pdf/document-preview.mjs +326 -8
  124. package/src/runtime/office/pdf/ooxml-crypto.mjs +469 -0
  125. package/src/runtime/office/pdf/ooxml-key-worker.mjs +14 -0
  126. package/src/runtime/office/portable/portable-soffice.mjs +45 -5
  127. package/src/runtime/shared/bot-hops.mjs +62 -2
  128. package/src/runtime/shared/config.mjs +49 -22
  129. package/src/runtime/shared/connector-catalog.d.mts +4 -2
  130. package/src/runtime/shared/connector-catalog.mjs +16 -8
  131. package/src/runtime/shared/developer-options.mjs +32 -1
  132. package/src/runtime/shared/google-oauth-client.generated.json +4 -0
  133. package/src/runtime/shared/google-oauth-client.mjs +34 -9
  134. package/src/runtime/shared/llm/anthropic-betas.mjs +7 -0
  135. package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +12 -0
  136. package/src/runtime/shared/llm/provider-catalog-registry.mjs +1 -0
  137. package/src/runtime/shared/llm/well-formed.mjs +66 -0
  138. package/src/runtime/shared/provider-accounts.mjs +224 -58
  139. package/src/runtime/shared/provider-api-key.mjs +19 -8
  140. package/src/runtime/shared/routine-trigger.mjs +42 -0
  141. package/src/runtime/shared/tool-execution-contract.mjs +2 -0
  142. package/src/runtime/shared/transcript-metadata.mjs +38 -0
  143. package/src/runtime/shared/user-data-guard.mjs +30 -2
  144. package/src/runtime/shared/worktrees/activity-gate.mjs +12 -5
  145. package/src/session-runtime/agent-home-delivery.mjs +57 -13
  146. package/src/session-runtime/agent-room-delivery.mjs +28 -1
  147. package/src/session-runtime/agent-task-sessions.mjs +292 -79
  148. package/src/session-runtime/boot/begin.mjs +3 -2
  149. package/src/session-runtime/boot/tools.mjs +2 -0
  150. package/src/session-runtime/bot-chain.mjs +32 -0
  151. package/src/session-runtime/bot-status.mjs +7 -4
  152. package/src/session-runtime/channel-config-api.mjs +63 -17
  153. package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +45 -0
  154. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +72 -5
  155. package/src/session-runtime/internal-tool-executor.mjs +4 -0
  156. package/src/session-runtime/mcp-glue/server-catalog.mjs +7 -1
  157. package/src/session-runtime/mixy-heartbeat.mjs +807 -0
  158. package/src/session-runtime/model-route/route-tuning.mjs +10 -6
  159. package/src/session-runtime/owned-automation-run.mjs +33 -12
  160. package/src/session-runtime/provider-auth/login.mjs +7 -0
  161. package/src/session-runtime/provider-models/row-hydration.mjs +13 -2
  162. package/src/session-runtime/routine-home-turn.mjs +33 -6
  163. package/src/session-runtime/schedule-session-run.mjs +7 -3
  164. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +8 -12
  165. package/src/session-runtime/services/channel-admin.mjs +66 -18
  166. package/src/session-runtime/services/channel-worker.mjs +1 -1
  167. package/src/session-runtime/services/provider-admin.mjs +128 -25
  168. package/src/session-runtime/services/schedule-access.mjs +14 -0
  169. package/src/session-runtime/services/usage-dashboard-model.mjs +1 -0
  170. package/src/session-runtime/session-create/session-options.mjs +7 -4
  171. package/src/session-runtime/settings-system-api.mjs +16 -1
  172. package/src/session-runtime/setup-tool/executor.mjs +49 -14
  173. package/src/session-runtime/setup-tool/extended-actions.mjs +29 -3
  174. package/src/session-runtime/setup-tool/settings-contract.mjs +65 -4
  175. package/src/session-runtime/setup-tool/tool-defs.mjs +30 -3
  176. package/src/session-runtime/task-session-route.mjs +99 -0
  177. package/src/session-runtime/tool-policy/policy-stage.mjs +5 -2
  178. package/src/session-runtime/tool-policy/surface-helpers.mjs +2 -3
  179. package/src/session-runtime/tool-surface.mjs +4 -0
  180. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +98 -21
  181. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -1
  182. package/src/session-runtime/workspace/transitions.mjs +10 -2
  183. package/src/standalone/daemon-session-control.mjs +88 -20
  184. package/src/standalone/daemon.mjs +8 -0
  185. package/src/standalone/session-protocol.mjs +5 -0
  186. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +6 -2
  187. package/src/standalone/session-service/session-calls/session-turns.mjs +6 -0
  188. package/src/standalone/session-service.mjs +13 -0
  189. package/src/standalone/session-tool-output.mjs +8 -5
  190. package/src/tui/app/model-options.mjs +2 -0
  191. package/src/tui/dist/index.mjs +24 -2
  192. package/src/tui/session/notice-surface.mjs +17 -1
  193. package/src/tui/session/session-api/integrations.mjs +1 -0
  194. package/src/tui/session/session-api/lifecycle.mjs +8 -0
  195. package/src/tui/session/session-api/settings.mjs +14 -2
  196. package/src/tui/session/turn-stream.mjs +17 -1
  197. package/src/tui/session/turn.mjs +22 -2
@@ -35,6 +35,7 @@ const {
35
35
  setSecret: _setSecret,
36
36
  deleteSecret: _deleteSecret,
37
37
  hasSecret: _hasSecret,
38
+ hasFallbackSecret: _hasFallbackSecret,
38
39
  setSecretAsync: _setSecretAsyncImpl,
39
40
  deleteSecretAsync: _deleteSecretAsyncImpl,
40
41
  keychainStatus: _keychainStatus,
@@ -523,6 +524,18 @@ export function readSection(section) {
523
524
  return stripGeneratedMarker(readAll()[section] ?? null) ?? {};
524
525
  }
525
526
 
527
+ /**
528
+ * One section straight from the file, read-only: never served from the read
529
+ * cache, restored from a backup, quarantined or written. A missing, unreadable
530
+ * or malformed config throws. For checks that must fail closed, where a stale
531
+ * value restored from a backup must not count (developerOptionEnabledStrict).
532
+ */
533
+ export function readSectionStrict(section) {
534
+ const parsed = parseJsonText(readFileSync(configPath(), 'utf8'));
535
+ if (!isPlainObject(parsed)) throw new SyntaxError('config root must be a JSON object');
536
+ return stripGeneratedMarker(parsed[section] ?? null) ?? {};
537
+ }
538
+
526
539
  export function readConfig() {
527
540
  return stripGeneratedMarker(readAll()) ?? {};
528
541
  }
@@ -641,14 +654,17 @@ export function updateSectionAsync(section, updater) {
641
654
  }
642
655
 
643
656
  // ── Secret account names ─────────────────────────────────────────────────────
644
- // Canonical account strings stored in the OS keychain.
657
+ // Canonical account strings stored in the OS keychain (or, on a Linux host with
658
+ // no usable keychain provider, in the private secrets file behind the same
659
+ // get/set/delete calls: keychain-cjs.cjs "Linux fallback file").
645
660
  export const SECRET_ACCOUNTS = Object.freeze({
646
661
  agentApiKey: (provider) => `agent.${provider}.apiKey`,
647
662
  openaiUsageSessionKey: 'agent.openai.usageSessionKey',
648
663
  });
649
664
 
650
665
  // ── Secret-aware getters ─────────────────────────────────────────────────────
651
- // Read order: ENV MIXDOG_<UPPER_SNAKE> → OS keychain → null.
666
+ // Read order: ENV MIXDOG_<UPPER_SNAKE> → OS keychain → Linux fallback secrets
667
+ // file (read by _getSecret when the keychain has nothing) → null.
652
668
 
653
669
  function _envKey(account) {
654
670
  // 'agent.openai.apiKey' → 'MIXDOG_AGENT_OPENAI_APIKEY'
@@ -676,7 +692,9 @@ export function getOpenAIUsageSessionKey() {
676
692
  }
677
693
 
678
694
  /**
679
- * Persist a secret to the OS keychain. Throws on failure.
695
+ * Persist a secret to the OS keychain; on a Linux host with no keychain provider
696
+ * (secret-tool/libsecret missing, no Secret Service) to the owner-only secrets
697
+ * file in the data dir instead. Throws on failure.
680
698
  * Never writes to mixdog-config.json.
681
699
  */
682
700
  export function saveSecret(account, value) {
@@ -697,25 +715,25 @@ export async function saveSecretAsync(account, value) {
697
715
  }
698
716
 
699
717
  function saveSecretError(account, err) {
700
- {
701
- // On WSL/headless Linux (and any host missing a usable keychain provider,
702
- // e.g. secret-tool/libsecret not installed or no running Secret Service) the OS
703
- // write throws a cryptic provider error. Surface an actionable message that
704
- // points at the env-var read path the getters already honor, instead of
705
- // silently writing the plaintext secret to mixdog-config.json.
706
- const envKey = _envKey(account);
707
- const agentMatch = String(account || '').match(/^agent\.([^.]+)\.apiKey$/);
708
- const stdEnv = agentMatch ? AGENT_PROVIDER_ENV[agentMatch[1]] : null;
709
- const envHint = stdEnv ? `${stdEnv} (or ${envKey})` : envKey;
710
- const e = new Error(
711
- `[config] could not save secret to the OS keychain: ${err?.message ? err.message : err}\n` +
712
- ` Set the ${envHint} environment variable instead — the runtime reads it directly.`
713
- );
714
- e.cause = err;
715
- return e;
716
- }
718
+ // Reached for a locked keyring, a non-Linux keychain failure, or a Linux host
719
+ // with no keychain provider whose fallback secrets file could not be written
720
+ // (keychain-cjs.cjs). Surface an actionable message that points at the
721
+ // env-var read path the getters already honor, instead of silently writing
722
+ // the plaintext secret to mixdog-config.json. The fallback is a separate
723
+ // owner-only file, never the config.
724
+ const envKey = _envKey(account);
725
+ const agentMatch = String(account || '').match(/^agent\.([^.]+)\.apiKey$/);
726
+ const stdEnv = agentMatch ? AGENT_PROVIDER_ENV[agentMatch[1]] : null;
727
+ const envHint = stdEnv ? `${stdEnv} (or ${envKey})` : envKey;
728
+ const e = new Error(
729
+ `[config] could not save secret to the OS keychain: ${err?.message ? err.message : err}\n` +
730
+ ` Set the ${envHint} environment variable instead — the runtime reads it directly.`
731
+ );
732
+ e.cause = err;
733
+ return e;
717
734
  }
718
735
 
736
+ /** Remove a secret from the OS keychain (if present) and the Linux fallback file. */
719
737
  export function deleteSecret(account) {
720
738
  _deleteSecret(account);
721
739
  }
@@ -741,13 +759,16 @@ export function onKeychainRecovered(listener) {
741
759
  return typeof _onKeychainRecovered === 'function' ? _onKeychainRecovered(listener) : () => {};
742
760
  }
743
761
 
744
- /** Keyring health for the UI: { state: ok|locked|missing|error|unknown, message }. */
762
+ /** Keyring health for the UI: { state: ok|locked|missing|unreachable|error|unknown, message, fallback }.
763
+ * `fallback` is true when no keychain provider exists on this Linux host and
764
+ * secrets live in the private secrets file: working storage, not a failure. */
745
765
  export function getKeychainStatus() {
746
766
  return typeof _keychainStatus === 'function' ? _keychainStatus() : { state: 'unknown', message: '' };
747
767
  }
748
768
 
749
769
  /**
750
- * Whether a secret is stored in the OS keychain for `account` (ignores env).
770
+ * Whether a secret is stored in the OS keychain (or Linux fallback file) for
771
+ * `account` (ignores env).
751
772
  * Lets the setup UI show "Set" WITHOUT ever sending the secret value to the
752
773
  * browser.
753
774
  */
@@ -760,4 +781,10 @@ export function hasStoredSecret(account) {
760
781
  }
761
782
  }
762
783
 
784
+ /** Whether `account`'s stored secret currently lives in the Linux fallback file
785
+ * (never true on Windows/macOS). Lets the UI label where a key is kept. */
786
+ export function isFallbackSecret(account) {
787
+ return typeof _hasFallbackSecret === 'function' ? !!_hasFallbackSecret(account) : false;
788
+ }
789
+
763
790
  export { configPath };
@@ -25,10 +25,10 @@ export interface ConnectorCatalogEntry {
25
25
  id: string;
26
26
  name: string;
27
27
  backend: ConnectorBackend;
28
- /** Offered only after the user enters their own Google OAuth client. */
29
- ownKey?: boolean;
30
28
  /** Curated deferred-tool manifest line (connector-triggers.mjs). */
31
29
  trigger: string;
30
+ /** New connections go by app password only (Gmail). */
31
+ appPasswordOnly?: boolean;
32
32
  }
33
33
 
34
34
  export const CONNECTOR_CATALOG: readonly ConnectorCatalogEntry[];
@@ -43,6 +43,8 @@ export function connectorServerInput(
43
43
  key?: string,
44
44
  options?: { extraAccount?: boolean }
45
45
  ): Record<string, unknown>;
46
+ export function connectorAppPasswordOnly(entry: ConnectorCatalogEntry | null | undefined): boolean;
47
+ export function connectorUsesGoogleClient(entry: ConnectorCatalogEntry | null | undefined): boolean;
46
48
  export function connectorMultiAccount(entry: ConnectorCatalogEntry | null | undefined): boolean;
47
49
  export const GMAIL_APP_PASSWORD: Readonly<{ url: string; accountHeader: string; helpUrl: string }>;
48
50
  export function gmailAddress(value: unknown): string;
@@ -53,20 +53,18 @@ export const CONNECTOR_CATALOG = Object.freeze(
53
53
  id: 'gmail',
54
54
  name: 'Gmail',
55
55
  backend: google('gmail', GOOGLE_SCOPES.gmail, ['send_email']),
56
+ // New Gmail connections use an app password only (GMAIL_APP_PASSWORD);
57
+ // a Gmail server already on the Google sign-in keeps working.
58
+ appPasswordOnly: true,
56
59
  },
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
60
  {
61
61
  id: 'google-calendar',
62
62
  name: 'Google Calendar',
63
- ownKey: true,
64
63
  backend: google('calendar', GOOGLE_SCOPES.calendar, ['delete_event']),
65
64
  },
66
65
  {
67
66
  id: 'google-drive',
68
67
  name: 'Google Drive',
69
- ownKey: true,
70
68
  backend: google('drive', GOOGLE_SCOPES.drive, []),
71
69
  },
72
70
  {
@@ -113,9 +111,8 @@ export const CONNECTOR_CATALOG = Object.freeze(
113
111
  ].map((entry) => Object.freeze({ ...entry, trigger: connectorTrigger(entry.id) }))
114
112
  );
115
113
 
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));
114
+ /** The ids the setup tool's `connect_connector` offers: the whole catalog. */
115
+ export const CONNECTOR_IDS = Object.freeze(CONNECTOR_CATALOG.map((entry) => entry.id));
119
116
 
120
117
  /** The catalog entry for `id`, or null. */
121
118
  export function connectorById(id) {
@@ -193,6 +190,17 @@ export function connectorMultiAccount(entry) {
193
190
  return entry?.backend?.kind === 'oauth' && entry.backend.oauthClient === 'google';
194
191
  }
195
192
 
193
+ /** Whether a new connection of `entry` goes by app password only (Gmail). */
194
+ export function connectorAppPasswordOnly(entry) {
195
+ return entry?.appPasswordOnly === true;
196
+ }
197
+
198
+ /** Whether `entry` signs in with Mixdog's built-in Google client (or the
199
+ * user's own Google client in its place). */
200
+ export function connectorUsesGoogleClient(entry) {
201
+ return entry?.backend?.kind === 'oauth' && entry.backend.oauthClient === 'google';
202
+ }
203
+
196
204
  /** The MCP server input that connects catalog entry `entry` under `name`
197
205
  * (oauth / apiKey); `key` fills an apiKey backend; `extraAccount`
198
206
  * makes it one more account of a multi-account connector. */
@@ -5,12 +5,25 @@
5
5
  // `developer.<optionId>` is true. Default off. An option with a `warning`
6
6
  // turns on only after the user confirms that warning; an option with a
7
7
  // `provider` gates that dev-only OAuth provider.
8
- import { readSection } from './config.mjs';
8
+ import { readSection, readSectionStrict } from './config.mjs';
9
9
 
10
10
  const OAUTH_RISK_WARNING =
11
11
  'Using this provider through OAuth carries a high risk of penalties such as account restrictions.';
12
12
 
13
13
  export const DEVELOPER_SECTIONS = Object.freeze([
14
+ Object.freeze({
15
+ id: 'approvals',
16
+ label: 'Approvals',
17
+ options: Object.freeze([
18
+ Object.freeze({
19
+ id: 'allowAllActions',
20
+ label: 'Allow all actions',
21
+ description: 'Skips the approval cards for every action except payments and permanent deletes.',
22
+ warning:
23
+ 'Turning this on lets agents act without asking you first, except for payments and permanent deletes. Use it only if you accept that risk.',
24
+ }),
25
+ ]),
26
+ }),
14
27
  Object.freeze({
15
28
  id: 'providers',
16
29
  label: 'Providers',
@@ -30,6 +43,13 @@ export const DEVELOPER_SECTIONS = Object.freeze([
30
43
  warning: OAUTH_RISK_WARNING,
31
44
  provider: 'cursor-oauth',
32
45
  }),
46
+ Object.freeze({
47
+ id: 'devinOAuth',
48
+ label: 'Devin',
49
+ description: 'Show Devin OAuth in Providers and the model picker.',
50
+ warning: OAUTH_RISK_WARNING,
51
+ provider: 'devin-oauth',
52
+ }),
33
53
  Object.freeze({
34
54
  id: 'mimoOAuth',
35
55
  label: 'MiMo',
@@ -74,6 +94,17 @@ export function developerOptionEnabled(id, stored = storedDeveloperConfig()) {
74
94
  return developerOption(id) !== null && normalizeDeveloperConfig(stored)[id] === true;
75
95
  }
76
96
 
97
+ /** `developerOptionEnabled` for a safety bypass: fail-closed. Read straight
98
+ * from the config file, so a missing or malformed config is never restored
99
+ * from a backup (which may hold a stale `true`); any error means off. */
100
+ export function developerOptionEnabledStrict(id) {
101
+ try {
102
+ return developerOption(id) !== null && normalizeDeveloperConfig(readSectionStrict('agent').developer)[id] === true;
103
+ } catch {
104
+ return false;
105
+ }
106
+ }
107
+
77
108
  /** Settings view of every section; `stored` defaults to the on-disk values. */
78
109
  export function developerSettingsView(stored = storedDeveloperConfig()) {
79
110
  const values = normalizeDeveloperConfig(stored);
@@ -0,0 +1,4 @@
1
+ {
2
+ "clientId": "187841207053-o7jhqbnjjh99kfio1fgs7422m540q75m.apps.googleusercontent.com",
3
+ "clientSecret": "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"
4
+ }
@@ -6,18 +6,43 @@
6
6
  // uses PKCE (S256), state and a loopback redirect
7
7
  // (developers.google.com/identity/protocols/oauth2/native-app).
8
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).
9
+ // The source ships no client. The values come from the environment first
10
+ // (MIXDOG_GOOGLE_OAUTH_CLIENT_ID / _SECRET: a developer's override, and what
11
+ // `npm run dev:isolated` passes through), then from the generated, gitignored
12
+ // google-oauth-client.generated.json beside this file. Release packaging
13
+ // writes that file from the repository secrets
14
+ // (scripts/embed-google-oauth-client.mjs) before the npm package and every
15
+ // desktop runtime.asar are built, so each shipped build carries it. A build
16
+ // with neither offers no built-in Google sign-in (the user's own Google
17
+ // client still works).
12
18
 
13
- const value = (name) => String(process.env[name] || '').trim();
19
+ import { readFileSync } from 'node:fs';
20
+ import { fileURLToPath } from 'node:url';
21
+
22
+ /** The generated file release packaging writes (see the header). */
23
+ export const GOOGLE_OAUTH_CLIENT_FILE = fileURLToPath(new URL('./google-oauth-client.generated.json', import.meta.url));
24
+
25
+ const text = (value) => String(value || '').trim();
26
+
27
+ function embeddedClient(file) {
28
+ let source;
29
+ try {
30
+ source = readFileSync(file, 'utf8');
31
+ } catch (error) {
32
+ if (error?.code === 'ENOENT') return null;
33
+ throw error;
34
+ }
35
+ const { clientId, clientSecret } = JSON.parse(source);
36
+ return text(clientId) ? { clientId: text(clientId), clientSecret: text(clientSecret) } : null;
37
+ }
14
38
 
15
39
  /** `{ 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') };
40
+ * build has none. The environment wins over the generated file as a whole:
41
+ * the two are never mixed. */
42
+ export function googleOAuthClient({ env = process.env, file = GOOGLE_OAUTH_CLIENT_FILE } = {}) {
43
+ const clientId = text(env.MIXDOG_GOOGLE_OAUTH_CLIENT_ID);
44
+ if (clientId) return { clientId, clientSecret: text(env.MIXDOG_GOOGLE_OAUTH_CLIENT_SECRET) };
45
+ return embeddedClient(file);
21
46
  }
22
47
 
23
48
  /** Google issues a refresh token only for `access_type=offline`. */
@@ -7,6 +7,9 @@ const FAST_MODE_BETA_HEADER = 'fast-mode-2026-02-01';
7
7
  const TOOL_SEARCH_BETA_HEADER = 'advanced-tool-use-2025-11-20';
8
8
  const THINKING_DISPLAY_UPDATES_BETA_HEADER = 'thinking-display-updates-2026-08-18';
9
9
  export const SERVER_SIDE_FALLBACK_BETA_HEADER = 'server-side-fallback-2026-07-01';
10
+ // Turn-scoped mid-conversation system messages (clear_at), sent only when the
11
+ // request body carries one.
12
+ export const TURN_SCOPED_SYSTEM_BETA_HEADER = 'mid-conversation-system-clear-at-2026-08-21';
10
13
 
11
14
  // Fast mode ships on Opus 5.5, Opus 5 and Opus 4.8 only. Opus 4.7 rejects
12
15
  // `speed: "fast"` and Opus 4.6 silently runs at standard speed.
@@ -28,6 +31,7 @@ export function buildAnthropicBetaHeaders({
28
31
  effort = false,
29
32
  serverFallback = false,
30
33
  thinkingDisplayUpdates = false,
34
+ turnScopedSystem = false,
31
35
  } = {}) {
32
36
  const headers = String(base || '')
33
37
  .split(',')
@@ -48,5 +52,8 @@ export function buildAnthropicBetaHeaders({
48
52
  if (thinkingDisplayUpdates && !headers.includes(THINKING_DISPLAY_UPDATES_BETA_HEADER)) {
49
53
  headers.push(THINKING_DISPLAY_UPDATES_BETA_HEADER);
50
54
  }
55
+ if (turnScopedSystem && !headers.includes(TURN_SCOPED_SYSTEM_BETA_HEADER)) {
56
+ headers.push(TURN_SCOPED_SYSTEM_BETA_HEADER);
57
+ }
51
58
  return headers.join(',');
52
59
  }
@@ -63,3 +63,15 @@ export function emitsAnthropicProgressUpdates(model) {
63
63
  const id = normalizeClaudeModelId(model);
64
64
  return /^claude-(?:opus-5-5|sonnet-5-5|fable-5-1|mythos-5-1|fable-5)(?:-\d{8})?$/.test(id);
65
65
  }
66
+
67
+ // Models that accept turn-scoped mid-conversation system messages
68
+ // ({role:'system', clear_at:'next_user_message'}, beta
69
+ // mid-conversation-system-clear-at-2026-08-21). An id list for the same
70
+ // reason as above: a wrong "yes" is a 400 on every request, a wrong "no" only
71
+ // keeps the legacy tail-reminder layout — so unknown ids stay off.
72
+ export function supportsAnthropicTurnScopedSystemMessages(model) {
73
+ const id = normalizeClaudeModelId(model);
74
+ return /^claude-(?:fable-5-1|mythos-5-1|fable-5|mythos-5|opus-5-5|opus-5|opus-4-8|sonnet-5-5|haiku-5-5)(?:-\d{8})?$/.test(
75
+ id
76
+ );
77
+ }
@@ -92,6 +92,7 @@ const PROVIDER_CATALOGS = Object.freeze({
92
92
  rosterLimitsAuthoritative: true,
93
93
  },
94
94
  'cursor-oauth': { relay: true },
95
+ 'devin-oauth': { relay: true },
95
96
  'cursor-api': { relay: true },
96
97
  // The models.dev listing id of the Go gateway: a relay catalog, not a
97
98
  // selectable provider (the model lookup needs it flagged as a relay).
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Outgoing-request surrogate sanitizer.
3
+ *
4
+ * A lone UTF-16 surrogate (half of an emoji left by a char-count cut, or
5
+ * carried by tool output / file content) is serialized by JSON.stringify as a
6
+ * `\ud83d` escape that provider APIs reject ("not valid JSON"). This rewrites
7
+ * every string value and every object key to well-formed text (lone surrogate
8
+ * -> U+FFFD) in the request that is about to be sent.
9
+ *
10
+ * Copy-on-write: a request that is already well-formed is returned as the very
11
+ * same object (no allocation, identity kept for caches keyed on messages or
12
+ * tool lists). Only the containers on the path to a bad string are copied; the
13
+ * stored transcript is never mutated. Only arrays and plain objects are
14
+ * walked: Buffer / Uint8Array / ArrayBuffer and any other class instance are
15
+ * binary or opaque payloads and are returned untouched. Own symbol properties
16
+ * (e.g. the provider-native tool prefix count on a tool list) survive a copy.
17
+ */
18
+ function copySymbols(target, source) {
19
+ for (const symbol of Object.getOwnPropertySymbols(source)) {
20
+ Object.defineProperty(target, symbol, Object.getOwnPropertyDescriptor(source, symbol));
21
+ }
22
+ return target;
23
+ }
24
+
25
+ function setOwn(target, key, value) {
26
+ Object.defineProperty(target, key, { value, enumerable: true, writable: true, configurable: true });
27
+ }
28
+
29
+ function walk(value, active) {
30
+ if (typeof value === 'string') return value.isWellFormed() ? value : value.toWellFormed();
31
+ if (value === null || typeof value !== 'object' || active.has(value)) return value;
32
+ if (Array.isArray(value)) {
33
+ active.add(value);
34
+ let out = null;
35
+ for (let i = 0; i < value.length; i += 1) {
36
+ const next = walk(value[i], active);
37
+ if (!out && next !== value[i]) out = value.slice();
38
+ if (out) out[i] = next;
39
+ }
40
+ active.delete(value);
41
+ return out ? copySymbols(out, value) : value;
42
+ }
43
+ const proto = Object.getPrototypeOf(value);
44
+ if (proto !== Object.prototype && proto !== null) return value;
45
+ active.add(value);
46
+ const keys = Object.keys(value);
47
+ let out = null;
48
+ for (let i = 0; i < keys.length; i += 1) {
49
+ const key = keys[i];
50
+ const entry = value[key];
51
+ const nextKey = key.isWellFormed() ? key : key.toWellFormed();
52
+ const nextEntry = walk(entry, active);
53
+ if (!out && (nextKey !== key || nextEntry !== entry)) {
54
+ out = Object.create(proto);
55
+ for (let j = 0; j < i; j += 1) setOwn(out, keys[j], value[keys[j]]);
56
+ }
57
+ if (out) setOwn(out, nextKey, nextEntry);
58
+ }
59
+ active.delete(value);
60
+ return out ? copySymbols(out, value) : value;
61
+ }
62
+
63
+ /** `value` with every string and object key made well-formed; `value` itself when it already is. */
64
+ export function sanitizeSurrogates(value) {
65
+ return walk(value, new WeakSet());
66
+ }