mixdog 1.0.8 → 1.0.9

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 (109) hide show
  1. package/package.json +2 -1
  2. package/scripts/lib/run-node-tests.mjs +13 -0
  3. package/scripts/session-efficiency-diag.mjs +283 -0
  4. package/src/defaults/skills/computer-use/SKILL.md +3 -3
  5. package/src/defaults/skills/local-provider/SKILL.md +2 -1
  6. package/src/defaults/skills/setup/SKILL.md +4 -4
  7. package/src/defaults/skills/setup/references/actions.md +1 -3
  8. package/src/defaults/skills/setup/references/surfaces.md +2 -3
  9. package/src/headless-exec.mjs +1 -0
  10. package/src/rules/shared/70-delivery.md +2 -2
  11. package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +10 -0
  12. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +1 -1
  13. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  14. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +8 -4
  15. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request.mjs +10 -1
  16. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +1 -1
  17. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +18 -3
  18. package/src/runtime/agent/orchestrator/providers/client-version-store.mjs +50 -0
  19. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +27 -4
  20. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +9 -5
  21. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +18 -4
  22. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +2 -2
  23. package/src/runtime/agent/orchestrator/providers/lib/anthropic-models.mjs +7 -1
  24. package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +25 -2
  25. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +7 -1
  26. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +4 -0
  27. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +8 -0
  28. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +17 -3
  29. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +7 -1
  30. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +16 -0
  31. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +0 -20
  32. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +12 -10
  33. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +3 -1
  34. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +1 -1
  35. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +15 -3
  36. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +11 -2
  37. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +5 -1
  38. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +23 -9
  39. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +15 -6
  40. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +4 -4
  41. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +7 -0
  42. package/src/runtime/channels/lib/voice-transcription.mjs +30 -4
  43. package/src/runtime/computer-bridge/action-schema.mjs +3 -3
  44. package/src/runtime/computer-bridge/actions.mjs +4 -0
  45. package/src/runtime/computer-bridge/bridge-env.test-support.mjs +22 -0
  46. package/src/runtime/computer-bridge/client.mjs +10 -183
  47. package/src/runtime/computer-bridge/core-actions.mjs +2 -1
  48. package/src/runtime/computer-bridge/error-recovery.mjs +46 -34
  49. package/src/runtime/computer-bridge/result-canonical.mjs +181 -0
  50. package/src/runtime/local-provider/asset-installer.mjs +20 -10
  51. package/src/runtime/local-provider/catalog.mjs +4 -7
  52. package/src/runtime/local-provider/data/manifest.json +14 -1
  53. package/src/runtime/local-provider/hardware.mjs +36 -5
  54. package/src/runtime/local-provider/resumable-installations.mjs +2 -2
  55. package/src/runtime/local-provider/server.mjs +1 -1
  56. package/src/runtime/memory/index.mjs +2 -4
  57. package/src/runtime/memory/lib/core-memory-file.mjs +3 -8
  58. package/src/runtime/memory/lib/core-memory-store.mjs +14 -61
  59. package/src/runtime/memory/lib/cycle-scheduler/backlog-probe.mjs +5 -9
  60. package/src/runtime/memory/lib/cycle1/cycle1-plan.mjs +7 -0
  61. package/src/runtime/memory/lib/cycle1/cycle1-rows.mjs +43 -16
  62. package/src/runtime/memory/lib/cycle1/cycle1-window.mjs +11 -2
  63. package/src/runtime/memory/lib/embedding-provider.mjs +2 -5
  64. package/src/runtime/memory/lib/embedding-reindex.mjs +1 -5
  65. package/src/runtime/memory/lib/http-router/lifecycle-routes.mjs +0 -1
  66. package/src/runtime/memory/lib/memory-action-handlers/maintenance-actions.mjs +1 -1
  67. package/src/runtime/memory/lib/memory-schema/core-and-meta.mjs +4 -22
  68. package/src/runtime/memory/lib/memory.mjs +79 -32
  69. package/src/runtime/memory/lib/query-maintenance-handlers.mjs +0 -2
  70. package/src/runtime/office/com/com-adapter.mjs +3 -0
  71. package/src/runtime/office/pdf/pdf-adapter.mjs +50 -14
  72. package/src/runtime/office/pdf/pdf-render.mjs +2 -6
  73. package/src/runtime/office/pdf/pdf-security.mjs +8 -2
  74. package/src/runtime/office/portable/font-provisioner.mjs +28 -0
  75. package/src/runtime/office/portable/portable-docx-edits.mjs +28 -1
  76. package/src/runtime/office/portable/portable-soffice.mjs +44 -7
  77. package/src/runtime/shared/llm/anthropic-betas.mjs +7 -1
  78. package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +1 -1
  79. package/src/runtime/shared/llm/default-context-window.mjs +42 -0
  80. package/src/runtime/shared/llm/model-catalog-projection.mjs +2 -0
  81. package/src/runtime/shared/llm/model-catalog.mjs +2 -0
  82. package/src/runtime/shared/plugin-paths.mjs +9 -2
  83. package/src/runtime/shared/worker-exec-argv.mjs +25 -0
  84. package/src/runtime/web-search/lib/formatter.mjs +43 -18
  85. package/src/session-runtime/automation-agents.mjs +88 -0
  86. package/src/session-runtime/automation-workflow.mjs +19 -6
  87. package/src/session-runtime/boot/apis.mjs +0 -1
  88. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +14 -12
  89. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +4 -25
  90. package/src/session-runtime/model-recency.mjs +10 -2
  91. package/src/session-runtime/model-route/set-route.mjs +7 -1
  92. package/src/session-runtime/resource-api.mjs +8 -2
  93. package/src/session-runtime/resource-plugins-api.mjs +14 -2
  94. package/src/session-runtime/route-state.mjs +6 -2
  95. package/src/session-runtime/schedule-session-run.mjs +7 -1
  96. package/src/session-runtime/services/agent-tool/render.mjs +0 -5
  97. package/src/session-runtime/settings-builtin-tools-api.mjs +0 -16
  98. package/src/session-runtime/setup-tool/executor.mjs +7 -13
  99. package/src/session-runtime/setup-tool/tool-defs.mjs +2 -3
  100. package/src/session-runtime/tool-policy-refresh.mjs +0 -2
  101. package/src/session-runtime/webhook-session-run.mjs +10 -11
  102. package/src/session-runtime/workflow-agents-api/agent-route.mjs +5 -0
  103. package/src/standalone/daemon-agent-control.mjs +8 -1
  104. package/src/standalone/daemon.mjs +4 -0
  105. package/src/tui/dist/index.mjs +3 -2
  106. package/src/tui/session/agent-job-feed/notification-router.mjs +7 -0
  107. package/src/tui/session/live-share.mjs +16 -1
  108. package/src/tui/session/notification-plan.mjs +4 -0
  109. package/src/session-runtime/bridge-first-use-gate.mjs +0 -72
@@ -12,13 +12,16 @@
12
12
  * cache-node routing, so mixdog mirrors both.
13
13
  */
14
14
  import os from 'node:os';
15
+ import { readLastKnownVersion, rememberLastKnownVersion } from './client-version-store.mjs';
15
16
 
16
- // Offline fallback only; live value refreshes from npm (24h TTL, in-process).
17
+ // Offline fallback only; live value refreshes from npm (24h TTL) and the last
18
+ // live answer is kept on disk, so the floor is only a first-run default.
17
19
  // The backend gates model exposure AND per-request model access on the client
18
20
  // version (gpt-6-sol/luna require >= 0.155.0 per the published model catalog,
19
21
  // verified 2026-09-22), so keep this at the current release when bumping.
20
22
  const CODEX_CLIENT_VERSION_FLOOR = '0.155.1';
21
23
  const VERSION_TTL_MS = 24 * 60 * 60_000;
24
+ const LAST_KNOWN_KEY = 'codex-cli';
22
25
  let _cache = { value: null, fetchedAt: 0 };
23
26
  let _refreshInFlight = null;
24
27
 
@@ -32,14 +35,16 @@ async function _refresh() {
32
35
  const v = String(j?.version || '').trim();
33
36
  if (/^\d+\.\d+\.\d+/.test(v)) {
34
37
  _cache = { value: v, fetchedAt: Date.now() };
38
+ rememberLastKnownVersion(LAST_KNOWN_KEY, v);
35
39
  return v;
36
40
  }
37
41
  }
38
42
  } catch {
39
43
  /* offline — keep floor */
40
44
  }
41
- _cache = { value: CODEX_CLIENT_VERSION_FLOOR, fetchedAt: Date.now() };
42
- return CODEX_CLIENT_VERSION_FLOOR;
45
+ const fallback = _offlineVersion();
46
+ _cache = { value: fallback, fetchedAt: Date.now() };
47
+ return fallback;
43
48
  }
44
49
 
45
50
  /**
@@ -50,7 +55,25 @@ async function _refresh() {
50
55
  function codexClientVersionSync() {
51
56
  if (_cacheFresh()) return _cache.value;
52
57
  _ensureRefresh();
53
- return _cache.value || CODEX_CLIENT_VERSION_FLOOR;
58
+ return _cache.value || _offlineVersion();
59
+ }
60
+
61
+ // The newest version this machine has seen, never below the shipped floor.
62
+ function _offlineVersion() {
63
+ const lastKnown = readLastKnownVersion(LAST_KNOWN_KEY);
64
+ const newer = lastKnown && _compareVersion(lastKnown, CODEX_CLIENT_VERSION_FLOOR) > 0;
65
+ return newer ? lastKnown : CODEX_CLIENT_VERSION_FLOOR;
66
+ }
67
+
68
+ // Numeric x.y.z order; a pre-release suffix on the patch part is ignored.
69
+ function _compareVersion(a, b) {
70
+ const parts = (version) => String(version).split('.').map((part) => Number.parseInt(part, 10) || 0);
71
+ const [pa, pb] = [parts(a), parts(b)];
72
+ for (let i = 0; i < 3; i += 1) {
73
+ const delta = (pa[i] || 0) - (pb[i] || 0);
74
+ if (delta) return delta;
75
+ }
76
+ return 0;
54
77
  }
55
78
 
56
79
  function _cacheFresh() {
@@ -4,13 +4,17 @@ import { createRemoteVersionSource } from './npm-cli-version.mjs';
4
4
  // documented version endpoint; the official installer script embeds the
5
5
  // current build (…/lab/<build>/…), parsed best-effort. Any format change or
6
6
  // failure falls back to the floor.
7
- export const CURSOR_CLIENT_VERSION_FLOOR = 'cli-2026.08.11-e8db854';
7
+ export const CURSOR_CLIENT_VERSION_FLOOR = 'cli-2026.10.01-e373342';
8
8
  const BUILD_PATTERN = /downloads\.cursor\.com\/lab\/(\d{4}\.\d{2}\.\d{2}-[0-9a-f]{6,40})\//;
9
9
 
10
- const live = createRemoteVersionSource('https://cursor.com/install', async (res) => {
11
- const match = (await res.text()).match(BUILD_PATTERN);
12
- return match ? `cli-${match[1]}` : null;
13
- });
10
+ const live = createRemoteVersionSource(
11
+ 'https://cursor.com/install',
12
+ async (res) => {
13
+ const match = (await res.text()).match(BUILD_PATTERN);
14
+ return match ? `cli-${match[1]}` : null;
15
+ },
16
+ { persistKey: 'cursor-cli' }
17
+ );
14
18
  let envVersion;
15
19
 
16
20
  function envOverride() {
@@ -1,10 +1,19 @@
1
1
  import { normalizeAnthropicEffortInput } from './anthropic-effort.mjs';
2
+ import { codexModelSupportsEffortUpdates } from './openai-oauth-catalog.mjs';
2
3
 
3
4
  export const EFFORT_CONFIGURATION_BETA = 'mid-conversation-output-config-2026-07-01';
4
- const ANTHROPIC_MODELS = new Set(['claude-fable-5-1', 'claude-mythos-5-1', 'claude-opus-5', 'claude-opus-5-5']);
5
+ const ANTHROPIC_MODELS = new Set([
6
+ 'claude-fable-5-1',
7
+ 'claude-mythos-5-1',
8
+ 'claude-opus-5',
9
+ 'claude-opus-5-5',
10
+ 'claude-sonnet-5-5',
11
+ ]);
5
12
  // https://developers.openai.com/api/docs/guides/reasoning#change-reasoning-mid-conversation
6
- // — the GPT-6 model family, standard single-agent mode.
7
- const OPENAI_MODELS = new Set(['gpt-6-astra', 'gpt-6-sol', 'gpt-6-luna']);
13
+ // — the GPT-6 model family, standard single-agent mode. The public API
14
+ // publishes no per-model flag, so this list is its whole answer; the OAuth
15
+ // backend's catalog carries one and only falls back here without it.
16
+ const OPENAI_MODELS = new Set(['gpt-6-astra', 'gpt-6-sol', 'gpt-6-luna', 'gpt-6-1-sol']);
8
17
  const OPENAI_PROVIDERS = new Set(['openai', 'openai-oauth']);
9
18
  const ANTHROPIC_PROVIDERS = new Set(['anthropic', 'anthropic-oauth']);
10
19
  const EFFORTS = new Set(['minimal', 'low', 'medium', 'high', 'xhigh', 'max']);
@@ -35,11 +44,16 @@ function modelKey(model) {
35
44
  .replace(/\./g, '-');
36
45
  }
37
46
 
47
+ function openAiSupportsEffortUpdates(provider, model, id) {
48
+ const declared = provider === 'openai-oauth' ? codexModelSupportsEffortUpdates(String(model || '').trim()) : null;
49
+ return declared ?? OPENAI_MODELS.has(id);
50
+ }
51
+
38
52
  // Explicit protocol capabilities, not a prediction about future model families.
39
53
  export function effortConfigurationMode(provider, model, opts = {}) {
40
54
  const id = modelKey(model);
41
55
  if (opts.effortConfigurationEnabled === false || Number(opts.thinkingBudgetTokens) > 0) return null;
42
- if (OPENAI_PROVIDERS.has(provider) && OPENAI_MODELS.has(id)) {
56
+ if (OPENAI_PROVIDERS.has(provider) && openAiSupportsEffortUpdates(provider, model, id)) {
43
57
  const parameters = opts.modelParameters || {};
44
58
  const mode = opts.reasoning?.mode ?? parameters.reasoning_mode ?? parameters.mode ?? 'standard';
45
59
  if (mode !== 'standard' || opts.multiAgent === true || parameters.multi_agent === true) return null;
@@ -3,9 +3,9 @@ import { createNpmVersionSource, maxSemver } from './npm-cli-version.mjs';
3
3
  // Grok CLI client version for the cli-chat-proxy version gate (HTTP 426).
4
4
  // Effective = env override, else max(floor, learned-from-426, live npm latest
5
5
  // of the official @xai-official/grok CLI).
6
- export const GROK_CLI_VERSION_FLOOR = '0.2.87';
6
+ export const GROK_CLI_VERSION_FLOOR = '1.0.46';
7
7
 
8
- const live = createNpmVersionSource('@xai-official/grok');
8
+ const live = createNpmVersionSource('@xai-official/grok', { persistKey: 'grok-cli' });
9
9
  let envVersion;
10
10
  let learned = null;
11
11
 
@@ -3,6 +3,12 @@
3
3
  export const MODELS = [
4
4
  { id: 'claude-opus-5-5', name: 'Claude Opus 5.5', provider: 'anthropic', family: 'opus', contextWindow: 1000000 },
5
5
  { id: 'claude-fable-5-1', name: 'Claude Fable 5.1', provider: 'anthropic', family: 'fable', contextWindow: 1000000 },
6
- { id: 'claude-sonnet-5', name: 'Claude Sonnet 5', provider: 'anthropic', family: 'sonnet', contextWindow: 1000000 },
6
+ {
7
+ id: 'claude-sonnet-5-5',
8
+ name: 'Claude Sonnet 5.5',
9
+ provider: 'anthropic',
10
+ family: 'sonnet',
11
+ contextWindow: 1000000,
12
+ },
7
13
  ];
8
14
  export const ANTHROPIC_VERSION = '2023-06-01';
@@ -3,6 +3,12 @@
3
3
  // Sync accessor never blocks (kicks a background refresh); warm() is the
4
4
  // awaitable cold-start path. Failures resolve to null so callers fall back to
5
5
  // their floor. MIXDOG_DISABLE_LIVE_CLI_VERSIONS=1 turns the lookup off.
6
+ //
7
+ // A source given a `persistKey` also remembers its last live answer on disk,
8
+ // so a new process (or an offline one) starts from the newest version this
9
+ // machine has seen rather than from a floor that only moves with a release.
10
+
11
+ import { readLastKnownVersion, rememberLastKnownVersion } from './client-version-store.mjs';
6
12
 
7
13
  const SEMVER = /^\d+\.\d+\.\d+$/;
8
14
  const DEFAULT_TTL_MS = 24 * 60 * 60_000;
@@ -43,10 +49,22 @@ export function createNpmVersionSource(pkg, options) {
43
49
  * Generic source: GET `url`, `parse(res)` returns the version string or null
44
50
  * (unrecognised format). Same TTL/in-flight/timeout/disable rules as npm.
45
51
  */
46
- export function createRemoteVersionSource(url, parse, { ttlMs = DEFAULT_TTL_MS, timeoutMs = TIMEOUT_MS } = {}) {
52
+ export function createRemoteVersionSource(
53
+ url,
54
+ parse,
55
+ { ttlMs = DEFAULT_TTL_MS, timeoutMs = TIMEOUT_MS, persistKey = null } = {}
56
+ ) {
47
57
  let value = null;
48
58
  let expiresAt = 0;
49
59
  let inFlight = null;
60
+ let lastKnownLoaded = false;
61
+
62
+ // Stale on purpose: it answers until the refresh lands, never instead of it.
63
+ function loadLastKnown() {
64
+ if (lastKnownLoaded) return;
65
+ lastKnownLoaded = true;
66
+ if (!value) value = readLastKnownVersion(persistKey);
67
+ }
50
68
 
51
69
  async function refresh() {
52
70
  let next = null;
@@ -56,7 +74,10 @@ export function createRemoteVersionSource(url, parse, { ttlMs = DEFAULT_TTL_MS,
56
74
  } catch {
57
75
  /* offline or slow — keep previous/floor */
58
76
  }
59
- if (next) value = next;
77
+ if (next) {
78
+ value = next;
79
+ rememberLastKnownVersion(persistKey, next);
80
+ }
60
81
  expiresAt = Date.now() + (next ? ttlMs : FAILURE_TTL_MS);
61
82
  return value;
62
83
  }
@@ -76,11 +97,13 @@ export function createRemoteVersionSource(url, parse, { ttlMs = DEFAULT_TTL_MS,
76
97
  return {
77
98
  /** Cached live version or null; refreshes in the background when stale. */
78
99
  sync() {
100
+ loadLastKnown();
79
101
  if (!fresh()) ensureRefresh();
80
102
  return value;
81
103
  },
82
104
  /** Awaitable, never rejects; bounded by the fetch timeout. */
83
105
  warm() {
106
+ loadLastKnown();
84
107
  return fresh() ? Promise.resolve(value) : ensureRefresh();
85
108
  },
86
109
  };
@@ -154,7 +154,13 @@ function openAIOAuthState() {
154
154
  }
155
155
 
156
156
  function grokOAuthState() {
157
- const paths = [process.env.GROK_OAUTH_CREDENTIALS_PATH, join(resolvePluginData(), 'grok-oauth.json')];
157
+ // Same account/path precedence as the token store (getOwnTokenPath): a login
158
+ // lands in the selected account's pool file, never the legacy root file.
159
+ const paths = [
160
+ boundProviderAuthPath('grok-oauth') ||
161
+ process.env.GROK_OAUTH_CREDENTIALS_PATH ||
162
+ join(resolvePluginData(), 'grok-oauth.json'),
163
+ ];
158
164
  return memoProbe('grok-oauth', paths, () =>
159
165
  resolveProbeState(paths, (docs) => docs.some((own) => !!(own?.access_token && own?.refresh_token)))
160
166
  );
@@ -86,6 +86,10 @@ export function _normalizeCodexModel(m) {
86
86
  supportVerbosity: m?.support_verbosity === true,
87
87
  defaultVerbosity: m?.default_verbosity || null,
88
88
  supportsReasoningSummaries: m?.supports_reasoning_summaries === true,
89
+ // Absent on an older catalog: the caller then falls back to its own list.
90
+ ...(typeof m?.supports_reasoning_effort_updates === 'boolean'
91
+ ? { supportsReasoningEffortUpdates: m.supports_reasoning_effort_updates }
92
+ : {}),
89
93
  ...(typeof m?.supports_image_generation === 'boolean'
90
94
  ? { supportsImageGeneration: m.supports_image_generation }
91
95
  : {}),
@@ -14,6 +14,7 @@ import { appendAgentTrace } from '../agent-trace.mjs';
14
14
  import { resolveProviderCacheKey, resolveProviderPromptCacheLane } from '../agent-runtime/cache-strategy.mjs';
15
15
  import { shouldFallbackTransport } from './retry-classifier.mjs';
16
16
  import { envFlag as _envFlag } from '../../../shared/env.mjs';
17
+ import { getModelsDevRowSync } from '../../../shared/llm/model-catalog.mjs';
17
18
  import {
18
19
  traceHash,
19
20
  stableTraceStringify,
@@ -161,6 +162,13 @@ export function xaiModelSupportsReasoningEffort(model) {
161
162
  .toLowerCase();
162
163
  if (!id) return true;
163
164
  if (!id.startsWith('grok')) return true;
165
+ // The catalog answers first: a listed model takes the parameter exactly
166
+ // when it publishes effort values (grok-4.3 does, grok-4.20-*-reasoning
167
+ // does not), so a new release needs no edit here.
168
+ const listed = getModelsDevRowSync(id, 'xai');
169
+ if (listed) {
170
+ return (listed.reasoning_options || []).some((option) => option?.type === 'effort' && option.values?.length > 0);
171
+ }
164
172
  // grok-4.5 / grok-4.6 and anything newer in that line.
165
173
  return /grok[-_]?4\.(?:[5-9]|\d{2,})/.test(id);
166
174
  }
@@ -1,13 +1,27 @@
1
+ import { getModelMetadataSync } from '../../../shared/llm/model-catalog.mjs';
2
+
1
3
  // Public Responses contracts are independent of the OAuth backend.
2
4
  // Match documented model IDs and dated snapshots, not unknown future families.
3
5
  // GPT-5.6 and later: prompt_cache_options caching and Fast mode.
4
- const GPT_56_PLUS_MODELS = /^(?:gpt-6-(?:astra|sol|luna)|gpt-5\.6-(?:sol|terra|luna))(?:-\d{4}-\d{2}-\d{2})?$/;
6
+ const GPT_56_PLUS_MODELS =
7
+ /^(?:gpt-6-(?:astra|sol|luna)|gpt-6\.1-sol|gpt-5\.6-(?:sol|terra|luna))(?:-\d{4}-\d{2}-\d{2})?$/;
8
+
9
+ // OpenAI bills prompt-cache writes from GPT-5.6 on, and exactly those models
10
+ // take prompt_cache_options and Fast mode. The id pattern is the offline
11
+ // floor; a published cache-write price admits a later model without an edit
12
+ // here, and a family no catalog lists yet stays out.
13
+ // https://developers.openai.com/api/docs/guides/prompt-caching
14
+ function isGpt56PlusModel(model) {
15
+ const id = String(model || '').trim();
16
+ if (!id) return false;
17
+ return GPT_56_PLUS_MODELS.test(id) || getModelMetadataSync(id, 'openai')?.cacheWriteCostPerM > 0;
18
+ }
5
19
  // Earlier models documented for Priority processing (now Fast mode).
6
20
  const EARLIER_FAST_MODELS = /^gpt-5\.(?:5|4|4-mini)(?:-\d{4}|$)/;
7
21
 
8
22
  export function openAiDirectSupportsFast(model) {
9
23
  const id = String(model?.id || model || '').trim();
10
- return GPT_56_PLUS_MODELS.test(id) || EARLIER_FAST_MODELS.test(id);
24
+ return isGpt56PlusModel(id) || EARLIER_FAST_MODELS.test(id);
11
25
  }
12
26
 
13
27
  export function applyOpenAIDirectCachePolicy(body, model, storeResponses) {
@@ -17,7 +31,7 @@ export function applyOpenAIDirectCachePolicy(body, model, storeResponses) {
17
31
  // Preserve the existing opt-out: no explicit cache-retention hint when
18
32
  // response storage is disabled. The provider's default cache still applies.
19
33
  if (!storeResponses) return body;
20
- if (GPT_56_PLUS_MODELS.test(String(model || '').trim())) {
34
+ if (isGpt56PlusModel(model)) {
21
35
  body.prompt_cache_options = { ttl: '30m' };
22
36
  } else {
23
37
  body.prompt_cache_retention = '24h';
@@ -21,7 +21,7 @@ import { CODEX_OAUTH_ORIGINATOR, codexModelsUrl } from './openai-codex-endpoints
21
21
  import { _normalizeCodexModel, _markLatestCodex } from './openai-codex-model.mjs';
22
22
 
23
23
  const CODEX_MODEL_CACHE_TTL_MS = 24 * 60 * 60_000;
24
- const CODEX_MODEL_CACHE_SCHEMA_VERSION = 5;
24
+ const CODEX_MODEL_CACHE_SCHEMA_VERSION = 6;
25
25
  const CATALOG_FETCH_TIMEOUT_MS = 10_000;
26
26
 
27
27
  // In-memory mirror of the on-disk catalog, same pattern as anthropic-oauth.
@@ -59,6 +59,12 @@ export function codexCatalogHas(id) {
59
59
  return _mirror.some((m) => m.id === id);
60
60
  }
61
61
 
62
+ /** The catalog's own answer, or null when it has none for this model. */
63
+ export function codexModelSupportsEffortUpdates(id) {
64
+ const supported = findCachedCodexModel(id)?.supportsReasoningEffortUpdates;
65
+ return typeof supported === 'boolean' ? supported : null;
66
+ }
67
+
62
68
  export function codexModelSupportsServiceTier(id, serviceTier) {
63
69
  return modelSupportsServiceTier(findCachedCodexModel(id), serviceTier);
64
70
  }
@@ -469,6 +469,22 @@ const CONTEXT_OVERFLOW_PATTERNS = [
469
469
  /context[_ ]length[_ ]exceeded/i,
470
470
  /prompt is too long/i,
471
471
  /reduce the length of (?:the )?(?:messages|input|prompt)/i,
472
+ // Wordings other backends use for the same refusal, each tied to a context
473
+ // or input-length noun so a tokens-per-minute rate limit never matches.
474
+ /prompt too long/i, // z.ai, Ollama
475
+ /prompt exceeds max length/i, // z.ai CN endpoint
476
+ /input is too long for requested model/i, // Amazon Bedrock
477
+ /input token count.*exceeds the maximum/i, // Google
478
+ /maximum prompt length is \d+/i, // xAI
479
+ /exceeds (?:the )?maximum allowed input length/i, // OpenRouter relays
480
+ /is longer than the model'?s context length/i, // Together AI
481
+ /exceeds the available context size/i, // llama.cpp server
482
+ /greater than the context length/i, // LM Studio
483
+ /context window exceeds limit/i, // MiniMax
484
+ /exceeded model token limit/i, // Kimi
485
+ /too large for model with \d+ maximum context length/i, // Mistral
486
+ /range of input length should be/i, // DashScope / Qwen
487
+ /model_context_window_exceeded/i, // z.ai finish reason surfaced as text
472
488
  ];
473
489
 
474
490
  /**
@@ -148,26 +148,6 @@ export function localGitToolsActive(configLike, { gitAvailable } = {}) {
148
148
  );
149
149
  }
150
150
 
151
- /** Capabilities that ask the user once per session before their first live
152
- * call: the desktop and the browser are the user's, and one approval at the
153
- * moment of first use is how the user learns the model reached for them. */
154
- export const BRIDGE_FIRST_USE_IDS = Object.freeze(['browser', 'computer']);
155
-
156
- /** On unless the profile turns it off for that capability;
157
- * MIXDOG_BRIDGE_FIRST_USE_APPROVAL overrides per process (headless, bench). */
158
- export function builtinFirstUseApproval(configLike, id) {
159
- return (
160
- featureEnvOverride('MIXDOG_BRIDGE_FIRST_USE_APPROVAL') ?? configLike?.builtins?.[id]?.firstUseApproval !== false
161
- );
162
- }
163
-
164
- export function setBuiltinFirstUseApprovalInConfig(configLike, id, enabled) {
165
- const next = { ...(configLike || {}) };
166
- next.builtins = { ...(next.builtins || {}) };
167
- next.builtins[id] = { ...(next.builtins[id] || {}), firstUseApproval: enabled !== false };
168
- return next;
169
- }
170
-
171
151
  export function setBuiltinInstalledInConfig(configLike, id, installed = true) {
172
152
  const next = { ...(configLike || {}) };
173
153
  next.builtins = { ...(next.builtins || {}) };
@@ -5,6 +5,7 @@
5
5
  import { clean, hasOwn } from './session-text.mjs';
6
6
  import { modelSupportsServiceTier } from '../providers/model-service-tiers.mjs';
7
7
  import { openAiDirectSupportsFast } from '../providers/openai-direct-request.mjs';
8
+ import { supportsAnthropicFastMode } from '../../../shared/llm/anthropic-betas.mjs';
8
9
 
9
10
  const FAST_CAPABLE_PROVIDERS = new Set([
10
11
  'anthropic',
@@ -52,16 +53,11 @@ function geminiModelSupportsHostedWebSearch(model) {
52
53
  function anthropicModelSupportsHostedWebSearch(model) {
53
54
  const id = clean(model?.id || model).toLowerCase();
54
55
  if (!id) return false;
55
- const match = id.match(/^claude-(opus|sonnet|haiku)-(\d+)(?:[-.](\d+))?/);
56
+ const match = id.match(/^claude-(opus|sonnet|haiku|fable|mythos)-(\d+)(?:[-.](\d+))?/);
56
57
  if (!match) return false;
57
58
  return (Number(match[2]) || 0) >= 4;
58
59
  }
59
60
 
60
- function anthropicModelMetaSupportsFast(model) {
61
- const id = clean(model?.id || model).toLowerCase();
62
- return /^claude-(opus|sonnet)/.test(id);
63
- }
64
-
65
61
  export function fastCapableFor(provider, model, effort = null, modelParameters = {}) {
66
62
  const p = clean(provider);
67
63
  if (!FAST_CAPABLE_PROVIDERS.has(p)) return false;
@@ -82,7 +78,7 @@ export function fastCapableFor(provider, model, effort = null, modelParameters =
82
78
  }
83
79
  if (p === 'openai') return openAiDirectSupportsFast(model);
84
80
  if (p === 'openai-oauth') return modelSupportsServiceTier(model, 'priority');
85
- if (p === 'anthropic' || p === 'anthropic-oauth') return anthropicModelMetaSupportsFast(model);
81
+ if (p === 'anthropic' || p === 'anthropic-oauth') return supportsAnthropicFastMode(clean(model?.id || model));
86
82
  return false;
87
83
  }
88
84
 
@@ -127,9 +123,15 @@ export function saveModelSettings(cfgMod, route, { fastCapable = true, baseConfi
127
123
  } else {
128
124
  delete nextSetting.modelParameters;
129
125
  }
130
- const contextPercent = Number(route.contextPercent);
131
- if (Number.isFinite(contextPercent) && contextPercent >= 10 && contextPercent <= 100) {
132
- nextSetting.contextPercent = Math.round(contextPercent / 10) * 10;
126
+ // Only a window the user moved off the model's default is a setting. The
127
+ // pickers send the default stop with every selection; storing it froze each
128
+ // model at whatever its default was that day, so a later default never
129
+ // reached anyone who had already selected the model.
130
+ const requestedPercent = Number(route.contextPercent);
131
+ const contextPercent = Math.round(requestedPercent / 10) * 10;
132
+ const isDefault = contextPercent === Number(route.contextDefaultPercent);
133
+ if (requestedPercent >= 10 && requestedPercent <= 100 && !isDefault) {
134
+ nextSetting.contextPercent = contextPercent;
133
135
  } else {
134
136
  delete nextSetting.contextPercent;
135
137
  }
@@ -119,7 +119,9 @@ function summarySendOpts(opts) {
119
119
  effort: 'low',
120
120
  effortConfiguration: undefined,
121
121
  effortConfigurationEnabled: false,
122
- fast: opts.fast ?? opts.sendOpts?.fast ?? true,
122
+ // Fast mode is opt-in (it needs separate account credits); the summary
123
+ // follows the route/session choice and never enables it on its own.
124
+ fast: opts.fast ?? opts.sendOpts?.fast ?? false,
123
125
  maxOutputTokens: opts.maxOutputTokens || SUMMARY_OUTPUT_TOKENS,
124
126
  providerState: undefined,
125
127
  onToolCall: undefined,
@@ -54,7 +54,7 @@ export async function resolveCompactionRoute({
54
54
  const contextWindow =
55
55
  sameModel && positiveInt(sessionRef?.contextWindow)
56
56
  ? positiveInt(sessionRef.contextWindow)
57
- : resolveSessionContextMeta(selectedProvider, selectedModel).contextWindow;
57
+ : resolveSessionContextMeta(selectedProvider, selectedModel, {}, { wholeWindow: true }).contextWindow;
58
58
  return {
59
59
  provider: selectedProvider,
60
60
  providerName,
@@ -2,6 +2,7 @@
2
2
  // resolution.
3
3
 
4
4
  import { getModelMetadataSync } from '../../providers/model-catalog.mjs';
5
+ import { contextWindowRange } from '../../../../shared/llm/default-context-window.mjs';
5
6
  import { positiveInt } from '../../../../shared/numbers.mjs';
6
7
 
7
8
  // Family-pattern fallback used only when the provider and external catalogs
@@ -96,11 +97,12 @@ function providerRawContextWindow(info, catalogInfo) {
96
97
  if (catalogWindow && fromCache !== catalogWindow) return catalogWindow;
97
98
  return fromCache || null;
98
99
  }
99
- export function resolveSessionContextMeta(provider, model, seed = {}) {
100
+ // `wholeWindow` asks for everything the model can take in (the summary route
101
+ // budgets its own input that way) instead of the window a session starts with.
102
+ export function resolveSessionContextMeta(provider, model, seed = {}, { wholeWindow = false } = {}) {
100
103
  const info = typeof provider?.getCachedModelInfo === 'function' ? provider.getCachedModelInfo(model) : null;
101
104
  const catalogInfo = getModelMetadataSync(model, providerNameOf(provider));
102
- const requestedContextWindow =
103
- positiveInt(seed.selectedContextWindow) ||
105
+ const servedContextWindow =
104
106
  providerRawContextWindow(info, catalogInfo) ||
105
107
  positiveInt(catalogInfo?.contextWindow) ||
106
108
  positiveInt(catalogInfo?.maxContextWindow) ||
@@ -110,6 +112,16 @@ export function resolveSessionContextMeta(provider, model, seed = {}) {
110
112
  positiveInt(seed.raw_context_window) ||
111
113
  positiveInt(seed.contextWindow) ||
112
114
  guessContextWindow(model, providerNameOf(provider));
115
+ // A session that records no selection starts from the model's default
116
+ // window, the one the picker shows. A recorded percentage without its window
117
+ // (a session older than selectedContextWindow, a route saved before the
118
+ // catalog warmed) is still the user's choice and keeps the served window.
119
+ const recordsSelection = wholeWindow || boundedPercent(seed.contextPercent) !== null;
120
+ const requestedContextWindow =
121
+ positiveInt(seed.selectedContextWindow) ||
122
+ (recordsSelection
123
+ ? servedContextWindow
124
+ : contextWindowRange({ provider: providerNameOf(provider), contextWindow: servedContextWindow }).defaultWindow);
113
125
  // A managed runtime's allocated capacity also bounds restored selections
114
126
  // and catalog metadata; raising a slider cannot allocate server memory.
115
127
  const runtimeContextWindow = positiveInt(info?.runtimeContextWindow);
@@ -13,7 +13,12 @@ import { buildProviderCacheOpts, cacheCapabilityForProvider } from '../../agent-
13
13
  import { normalizeAutoClearConfig, resolveAutoClearIdleMs } from '../../runtime-core/config-helpers.mjs';
14
14
  import { _buildBaseRules } from './rules-cache.mjs';
15
15
  import { composeSessionSystem, seedSessionMessages } from './session-prompt-composition.mjs';
16
- import { _prepareResumeTools, delegationDisabled, resolveSessionToolSurface } from './session-tool-surface.mjs';
16
+ import {
17
+ _prepareResumeTools,
18
+ delegationDisabled,
19
+ honoredSchemaAllowlist,
20
+ resolveSessionToolSurface,
21
+ } from './session-tool-surface.mjs';
17
22
  import { _forgetPreparedResume, _preparedResumeMatches, _readPreparedResume } from './prepared-resume-cache.mjs';
18
23
  import {
19
24
  filterModelEditToolNames,
@@ -175,6 +180,7 @@ export function createSession(opts) {
175
180
  logSessionSurface(surface);
176
181
  const contextMeta = resolveSessionContextMeta(provider, modelName, {
177
182
  selectedContextWindow: opts.selectedContextWindow,
183
+ contextPercent: route.contextPercent,
178
184
  });
179
185
  const session = buildSessionRecord({
180
186
  opts,
@@ -215,7 +221,10 @@ export function _refreshSessionRuleVariantsForModel(session, previousModel, prev
215
221
  ...(getHiddenAgent(session?.agent || null) ? ['Skill'] : []),
216
222
  ...(delegationDisabled(session, isAgentOwner(session)) ? ['agent'] : []),
217
223
  ];
218
- const allowTools = isAgentOwner(session) ? null : session?.schemaAllowedTools;
224
+ const allowTools = honoredSchemaAllowlist(session?.schemaAllowedTools, {
225
+ ownerIsAgent: isAgentOwner(session),
226
+ agent: session?.agent || null,
227
+ });
219
228
  const previousRules = _buildBaseRules({
220
229
  omitTools: [...deny, unusedModelEditToolName(previousModel)],
221
230
  allowTools,
@@ -63,7 +63,11 @@ function buildShellEnvironmentContext(opts, ownerIsAgent, toolsForRouting) {
63
63
  // four system blocks; environmentTailContext is the persisted env tail.
64
64
  export function composeSessionSystem(opts, { profile, providerName, modelName, surface }) {
65
65
  const { ownerIsAgent, resolvedAgent, skills } = surface;
66
- const skipAgentRules = opts.skipAgentRules === true;
66
+ // A tool-free hidden role (a one-shot classifier or title writer) carries
67
+ // only its own role rules: the shared tool policy and the worker conduct
68
+ // rules describe work it is offered no tool for.
69
+ const toolFreeRole = ownerIsAgent && surface.schemaAllowedTools?.length === 0;
70
+ const skipAgentRules = opts.skipAgentRules === true || toolFreeRole;
67
71
  const injectedRules = skipAgentRules
68
72
  ? ''
69
73
  : _buildBaseRules({
@@ -29,6 +29,19 @@ function resolveToolSpec(toolPreset, profile, ownerIsAgent) {
29
29
  return Array.isArray(profile?.tools) ? profile.tools : toolPreset;
30
30
  }
31
31
 
32
+ // The schema allowlist a session honours. Agent-owned sessions share one
33
+ // schema, so an allowlist handed down by a caller never narrows them — except
34
+ // the profile the session's own hidden role declares (agents.json
35
+ // toolSchemaProfile). Without that exception a tool-free maintenance role was
36
+ // sent the whole Agent schema on every one-shot call while the dispatch gate
37
+ // refused each of those tools (usage ledger 2026-09-30..10-05: 4104 cycle1
38
+ // calls at ~8.5K prompt tokens, about two thirds of it the 12-tool schema).
39
+ export function honoredSchemaAllowlist(schemaAllowedTools, { ownerIsAgent = false, agent = null } = {}) {
40
+ if (!Array.isArray(schemaAllowedTools)) return null;
41
+ if (ownerIsAgent && !getHiddenAgent(agent)) return null;
42
+ return schemaAllowedTools;
43
+ }
44
+
32
45
  // Every tool omitted by a caller schema allowlist is omitted from BP1 too.
33
46
  // The model never receives guidance for a tool it cannot call, and a new
34
47
  // process-wide built-in cannot leak into a narrow profile's prompt.
@@ -48,13 +61,17 @@ export function resolveSessionToolSurface(opts, { profile, toolPreset, modelName
48
61
  const resolvedAgent = opts.agent || opts.role || profile?.taskType || null;
49
62
  const hiddenAgent = getHiddenAgent(resolvedAgent);
50
63
  const isRetrievalAgent = hiddenAgent?.kind === 'retrieval';
51
- // Lead and Agent share the same cwd-scoped Skill inventory.
52
- const skills = opts.skipSkills ? [] : collectPromptSkillsCached(opts.cwd);
53
- // BP1 shared tool policy ships to EVERY role (Lead, workers, retrieval,
54
- // maintenance): its anti-spiral clauses (one anchor is enough, never
64
+ const hasCallerAllow = Array.isArray(opts.schemaAllowedTools);
65
+ const schemaAllowedTools = honoredSchemaAllowlist(opts.schemaAllowedTools, { ownerIsAgent, agent: resolvedAgent });
66
+ // Lead and Agent share the same cwd-scoped Skill inventory. A session that
67
+ // is offered no tool cannot load a skill, so it is not sent the manifest.
68
+ const skills = opts.skipSkills || schemaAllowedTools?.length === 0 ? [] : collectPromptSkillsCached(opts.cwd);
69
+ // BP1 shared tool policy ships to every role that is offered tools (Lead,
70
+ // workers, retrieval): its anti-spiral clauses (one anchor is enough, never
55
71
  // repeat equivalent patterns/scopes, plausible hit → stop) are exactly
56
72
  // what narrow retrieval roles need. Role docs
57
- // override role-inapplicable entries.
73
+ // override role-inapplicable entries. A tool-free role is sent none of it
74
+ // (session-prompt-composition.mjs).
58
75
  const sessionDeny = [
59
76
  ...(Array.isArray(opts.disallowedTools) ? opts.disallowedTools : []),
60
77
  ...(delegationDisabled(opts, ownerIsAgent) ? ['agent'] : []),
@@ -70,8 +87,6 @@ export function resolveSessionToolSurface(opts, { profile, toolPreset, modelName
70
87
  cwd: opts.cwd || null,
71
88
  });
72
89
 
73
- const hasCallerAllow = Array.isArray(opts.schemaAllowedTools);
74
- const schemaAllowedTools = ownerIsAgent || !hasCallerAllow ? null : opts.schemaAllowedTools;
75
90
  const tools = finalizeSessionToolList(toolsForRouting, {
76
91
  schemaAllowedTools,
77
92
  disallowedTools: sessionDeny,
@@ -121,8 +136,7 @@ export function _prepareResumeTools(session, preset) {
121
136
  toolSpec,
122
137
  ownerIsAgent,
123
138
  tools: finalizeSessionToolList(toolsForRouting, {
124
- schemaAllowedTools:
125
- !ownerIsAgent && Array.isArray(session.schemaAllowedTools) ? session.schemaAllowedTools : null,
139
+ schemaAllowedTools: honoredSchemaAllowlist(session.schemaAllowedTools, { ownerIsAgent, agent: session.agent }),
126
140
  disallowedTools: [
127
141
  ...(Array.isArray(session.disallowedTools) ? session.disallowedTools : []),
128
142
  ...(delegationDisabled(session, ownerIsAgent) ? ['agent'] : []),