mixdog 0.9.131 → 0.9.133

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 (159) hide show
  1. package/package.json +2 -2
  2. package/scripts/fixtures/patch-replay-corpus.json +9 -9
  3. package/scripts/reduction-trace-report.mjs +29 -0
  4. package/scripts/tool-failures.mjs +36 -0
  5. package/scripts/tool-stress.mjs +2 -2
  6. package/src/lib/rules-builder.cjs +0 -3
  7. package/src/rules/lead/01-general.md +1 -9
  8. package/src/rules/shared/01-tool.md +71 -39
  9. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +3 -0
  10. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -10
  11. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +72 -9
  12. package/src/runtime/agent/orchestrator/agent-trace.mjs +2 -0
  13. package/src/runtime/agent/orchestrator/config.mjs +9 -0
  14. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +212 -0
  15. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +2100 -0
  16. package/src/runtime/agent/orchestrator/providers/cursor.mjs +615 -0
  17. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +65 -35
  18. package/src/runtime/agent/orchestrator/providers/gemini.mjs +1 -1
  19. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +10 -0
  20. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +35 -6
  21. package/src/runtime/agent/orchestrator/providers/media-parity.test.mjs +142 -0
  22. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +14 -0
  23. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +4 -1
  24. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +2 -2
  25. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +6 -0
  26. package/src/runtime/agent/orchestrator/providers/registry.mjs +16 -5
  27. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +69 -53
  28. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +12 -22
  29. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +30 -5
  30. package/src/runtime/agent/orchestrator/session/eager-dispatch.test.mjs +55 -0
  31. package/src/runtime/agent/orchestrator/session/evidence-union.mjs +548 -0
  32. package/src/runtime/agent/orchestrator/session/evidence-union.test.mjs +211 -0
  33. package/src/runtime/agent/orchestrator/session/image-strip-recovery.mjs +68 -11
  34. package/src/runtime/agent/orchestrator/session/image-strip-recovery.test.mjs +122 -0
  35. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +1 -1
  36. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +8 -9
  37. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +31 -3
  38. package/src/runtime/agent/orchestrator/session/lossless-tool-output.test.mjs +124 -0
  39. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +1 -0
  40. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +17 -14
  41. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +2 -1
  42. package/src/runtime/agent/orchestrator/session/manager/provider-cache-key.mjs +2 -0
  43. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +3 -6
  44. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +47 -2
  45. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +1 -0
  46. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +18 -6
  47. package/src/runtime/agent/orchestrator/session/reduction-metrics.mjs +120 -0
  48. package/src/runtime/agent/orchestrator/session/reduction-metrics.test.mjs +87 -0
  49. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +13 -4
  50. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +55 -37
  51. package/src/runtime/agent/orchestrator/session/store-summary-reader.test.mjs +93 -2
  52. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +142 -23
  53. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +111 -71
  54. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +23 -3
  55. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +51 -46
  56. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +63 -24
  57. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +57 -23
  58. package/src/runtime/agent/orchestrator/tools/builtin/edit-sequential-occupation.test.mjs +56 -0
  59. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +134 -30
  60. package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +107 -0
  61. package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.test.mjs +27 -0
  62. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +600 -0
  63. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +140 -0
  64. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +96 -0
  65. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.test.mjs +83 -0
  66. package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +8 -5
  67. package/src/runtime/agent/orchestrator/tools/builtin/list-tool-integrity.test.mjs +132 -0
  68. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +90 -10
  69. package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +167 -25
  70. package/src/runtime/agent/orchestrator/tools/builtin/native-search-health.test.mjs +21 -0
  71. package/src/runtime/agent/orchestrator/tools/builtin/native-search-runner.mjs +3 -2
  72. package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +24 -0
  73. package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +33 -8
  74. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +17 -4
  75. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +9 -15
  76. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +40 -51
  77. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +115 -56
  78. package/src/runtime/agent/orchestrator/tools/builtin/shell-lossless-compact.mjs +18 -63
  79. package/src/runtime/agent/orchestrator/tools/builtin.mjs +6 -47
  80. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +5 -85
  81. package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +8 -15
  82. package/src/runtime/agent/orchestrator/tools/code-graph/search-references.test.mjs +20 -0
  83. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +4 -4
  84. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +1 -2
  85. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
  86. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +23 -18
  87. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +10 -0
  88. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +92 -116
  89. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +4 -0
  90. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +5 -45
  91. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +0 -17
  92. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
  93. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +15 -16
  94. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +4 -0
  95. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +11 -11
  96. package/src/runtime/agent/orchestrator/tools/tool-batch-trace.mjs +7 -0
  97. package/src/runtime/channels/lib/session-discovery.mjs +66 -1
  98. package/src/runtime/channels/lib/session-discovery.test.mjs +32 -0
  99. package/src/runtime/channels/lib/tool-dispatch.mjs +1 -1
  100. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -1
  101. package/src/runtime/shared/agent-route-config.mjs +3 -0
  102. package/src/runtime/shared/child-guardian.mjs +14 -0
  103. package/src/runtime/shared/edit-tool-dialect.mjs +30 -0
  104. package/src/runtime/shared/pristine-execution-contract.json +1 -0
  105. package/src/runtime/shared/schedule-model-ref.mjs +9 -5
  106. package/src/runtime/shared/schedule-session-run.mjs +2 -0
  107. package/src/runtime/shared/session-shard-health.mjs +89 -0
  108. package/src/runtime/shared/session-shard-health.test.mjs +30 -0
  109. package/src/runtime/shared/tool-execution-contract.mjs +1 -1
  110. package/src/runtime/shared/tool-surface.mjs +5 -0
  111. package/src/runtime/shared/webhook-session-run.mjs +8 -1
  112. package/src/session-runtime/config-helpers.mjs +18 -1
  113. package/src/session-runtime/lifecycle-api.mjs +13 -2
  114. package/src/session-runtime/model-capabilities.mjs +24 -2
  115. package/src/session-runtime/model-recency.mjs +11 -0
  116. package/src/session-runtime/model-route-api.mjs +37 -6
  117. package/src/session-runtime/model-settings-persist.test.mjs +6 -1
  118. package/src/session-runtime/native-search.mjs +1 -0
  119. package/src/session-runtime/provider-auth-api.mjs +13 -11
  120. package/src/session-runtime/provider-models.mjs +8 -4
  121. package/src/session-runtime/runtime-core.mjs +6 -31
  122. package/src/session-runtime/session-lifecycle.mjs +13 -1
  123. package/src/session-runtime/session-turn-api.mjs +1 -13
  124. package/src/session-runtime/tool-catalog-data.mjs +8 -5
  125. package/src/session-runtime/tool-catalog-schema.mjs +2 -3
  126. package/src/session-runtime/tool-catalog.mjs +6 -1
  127. package/src/session-runtime/tool-defs.mjs +0 -28
  128. package/src/session-runtime/tool-policy-surface.test.mjs +32 -0
  129. package/src/session-runtime/tool-surface.mjs +5 -1
  130. package/src/session-runtime/workflow-agents-api.mjs +9 -1
  131. package/src/session-runtime/workflow.mjs +5 -0
  132. package/src/standalone/agent-tool/lead-worker-index.mjs +144 -0
  133. package/src/standalone/agent-tool/notify.mjs +6 -2
  134. package/src/standalone/agent-tool/tag-registry.mjs +7 -1
  135. package/src/standalone/agent-tool/tool-def.mjs +1 -0
  136. package/src/standalone/agent-tool/worker-index.mjs +2 -12
  137. package/src/standalone/channel-client.mjs +18 -6
  138. package/src/standalone/channel-restart.test.mjs +163 -0
  139. package/src/standalone/channel-transport.mjs +54 -15
  140. package/src/standalone/channel-worker.mjs +6 -2
  141. package/src/standalone/provider-admin.mjs +14 -4
  142. package/src/standalone/session-runtime-pool-health.test.mjs +85 -0
  143. package/src/standalone/session-runtime-pool.mjs +22 -3
  144. package/src/standalone/session-runtime-worker.mjs +35 -0
  145. package/src/standalone/usage-dashboard.mjs +11 -1
  146. package/src/tui/app/model-options.mjs +2 -0
  147. package/src/tui/app/model-picker.mjs +64 -11
  148. package/src/tui/app/provider-setup-picker.mjs +32 -28
  149. package/src/tui/dist/index.mjs +111 -40
  150. package/src/tui/session/agent-envelope.mjs +1 -1
  151. package/src/tui/session/context-state.mjs +1 -0
  152. package/src/tui/session/session-api-ext.mjs +2 -0
  153. package/src/tui/session/session-api.mjs +1 -1
  154. package/src/tui/session/session-flow.mjs +32 -19
  155. package/src/workflows/default/WORKFLOW.md +3 -0
  156. package/src/workflows/solo/WORKFLOW.md +3 -1
  157. package/src/rules/lead/lead-tool.md +0 -3
  158. package/src/runtime/agent/orchestrator/session/loop/stop-hooks.mjs +0 -98
  159. package/src/runtime/agent/orchestrator/tools/result-compression.mjs +0 -279
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mixdog",
3
- "version": "0.9.131",
3
+ "version": "0.9.133",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Standalone mixdog coding-agent CLI/TUI workspace.",
@@ -59,7 +59,7 @@
59
59
  "test:tool-contracts": "npm run build:spawn:test && node scripts/tool-smoke.mjs",
60
60
  "smoke:patch": "node scripts/apply-patch-edit-smoke.mjs",
61
61
  "test:shellhardening": "npm run build:spawn:test && node --test scripts/suite-shellhardening-test.mjs scripts/suite-shell-direct-exe-test.mjs",
62
- "test:providers": "node --test scripts/provider-toolcall-test.mjs scripts/provider-contract-test.mjs scripts/provider-stream-outcome-test.mjs",
62
+ "test:providers": "node --test scripts/provider-toolcall-test.mjs scripts/provider-contract-test.mjs scripts/provider-stream-outcome-test.mjs scripts/cursor-provider-test.mjs",
63
63
  "test:embedding-runtime:warmup": "node scripts/verify-embedding-runtime.mjs --warmup",
64
64
  "test:release-assets": "node --check scripts/verify-release-assets.mjs && node --check scripts/release-gate-test.mjs && node --check scripts/prepare-native-assets.mjs && node --test scripts/release-gate-test.mjs scripts/prepare-native-assets-test.mjs",
65
65
  "test:native-edit-wire": "node scripts/native-edit-wire-test.mjs",
@@ -169,9 +169,9 @@
169
169
  },
170
170
  {
171
171
  "id": "add-overwrite-crlf",
172
- "note": "Add File overwrite of an existing CRLF file must keep CRLF",
173
- "expect": "applied",
174
- "expect_content": { "a.txt": "NEW\r\n" },
172
+ "note": "Add File must reject an existing file without changing its CRLF content",
173
+ "expect": "rejected",
174
+ "expect_error": "Add File target already exists",
175
175
  "file_snapshots": { "a.txt": "old\r\n" },
176
176
  "args": { "patch": "*** Begin Patch\n*** Add File: a.txt\n+NEW\n*** End Patch\n" }
177
177
  },
@@ -194,24 +194,24 @@
194
194
  {
195
195
  "id": "already-applied-insert",
196
196
  "note": "observed: re-sent insertion whose unique new-side is already on disk",
197
- "expect": "applied",
198
- "expect_content": { "a.js": "const STATUS = {\n pending: 1,\n archived: 2,\n}\n" },
197
+ "expect": "rejected",
198
+ "expect_error": "context not found",
199
199
  "file_snapshots": { "a.js": "const STATUS = {\n pending: 1,\n archived: 2,\n}\n" },
200
200
  "args": { "patch": "*** Begin Patch\n*** Update File: a.js\n@@\n const STATUS = {\n pending: 1,\n+ archived: 2,\n }\n*** End Patch\n" }
201
201
  },
202
202
  {
203
203
  "id": "already-applied-replace",
204
204
  "note": "observed: re-sent replacement whose unique new-side is already on disk",
205
- "expect": "applied",
206
- "expect_content": { "a.js": "head\nreturn ALL.has(v) || v === 'core' || v === 'lineage'\ntail\n" },
205
+ "expect": "rejected",
206
+ "expect_error": "context not found",
207
207
  "file_snapshots": { "a.js": "head\nreturn ALL.has(v) || v === 'core' || v === 'lineage'\ntail\n" },
208
208
  "args": { "patch": "*** Begin Patch\n*** Update File: a.js\n@@\n head\n-return ALL.has(v) || v === 'core'\n+return ALL.has(v) || v === 'core' || v === 'lineage'\n tail\n*** End Patch\n" }
209
209
  },
210
210
  {
211
211
  "id": "already-applied-mixed-remaining",
212
212
  "note": "already-applied hunks skip; a later unique old-side hunk still applies",
213
- "expect": "applied",
214
- "expect_content": { "a.js": "const STATUS = {\n pending: 1,\n archived: 2,\n}\n// lineage backfill\n" },
213
+ "expect": "rejected",
214
+ "expect_error": "context not found",
215
215
  "file_snapshots": { "a.js": "const STATUS = {\n pending: 1,\n archived: 2,\n}\n// pending/active only\n" },
216
216
  "args": { "patch": "*** Begin Patch\n*** Update File: a.js\n@@\n const STATUS = {\n pending: 1,\n+ archived: 2,\n }\n@@\n-// pending/active only\n+// lineage backfill\n*** End Patch\n" }
217
217
  },
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs';
3
+ import { resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { summarizeReductionTraceRows } from '../src/runtime/agent/orchestrator/session/reduction-metrics.mjs';
6
+
7
+ export function parseTraceJsonl(text) {
8
+ const rows = [];
9
+ for (const line of String(text || '').split(/\r?\n/)) {
10
+ if (!line.trim()) continue;
11
+ try { rows.push(JSON.parse(line)); } catch { /* retain valid rows */ }
12
+ }
13
+ return rows;
14
+ }
15
+
16
+ function main(argv) {
17
+ const tracePath = argv[2];
18
+ if (!tracePath) {
19
+ process.stderr.write('Usage: node scripts/reduction-trace-report.mjs <agent-trace.jsonl>\n');
20
+ return 2;
21
+ }
22
+ const rows = parseTraceJsonl(readFileSync(resolve(tracePath), 'utf8'));
23
+ process.stdout.write(`${JSON.stringify(summarizeReductionTraceRows(rows), null, 2)}\n`);
24
+ return 0;
25
+ }
26
+
27
+ if (resolve(process.argv[1] || '') === fileURLToPath(import.meta.url)) {
28
+ process.exitCode = main(process.argv);
29
+ }
@@ -2,6 +2,7 @@
2
2
  import { existsSync, readFileSync } from 'node:fs';
3
3
  import { homedir } from 'node:os';
4
4
  import { resolve } from 'node:path';
5
+ import { classifyToolFailure } from '../src/runtime/agent/orchestrator/agent-trace-format.mjs';
5
6
 
6
7
  function argValue(name, fallback = null) {
7
8
  const idx = process.argv.indexOf(name);
@@ -87,9 +88,40 @@ function rowCategory(row) {
87
88
  return row.category || row.result_kind || row.resultKind || '(uncategorized)';
88
89
  }
89
90
 
91
+ function rowErrorText(row) {
92
+ return row.error_preview || row.result || row.error || row.message || row.error_first_line || '';
93
+ }
94
+
95
+ function isKnownTestFixture(row) {
96
+ if (row.session_id !== 'no-session' || row.agent != null || row.model != null) return false;
97
+ const tool = rowTool(row);
98
+ if (tool === 'unknown_test_tool') return true;
99
+ return tool === 'apply_patch' && /^Error:\s*patch failed\s*$/i.test(String(rowErrorText(row)).trim());
100
+ }
101
+
102
+ function normalizeRowCategory(row) {
103
+ const storedCategory = rowCategory(row);
104
+ let category = storedCategory;
105
+ if (isKnownTestFixture(row)) {
106
+ category = 'expected-test';
107
+ } else if (rowTool(row) === 'apply_patch') {
108
+ const derived = classifyToolFailure(rowErrorText(row), 'apply_patch');
109
+ // Failure previews are bounded and may end before the nested cause. Never
110
+ // downgrade a stored specific category to the generic fallback merely
111
+ // because the historical preview lacks that tail.
112
+ if (derived !== 'runtime/failure' || storedCategory === 'runtime/failure') {
113
+ category = derived;
114
+ }
115
+ }
116
+ return category === storedCategory
117
+ ? row
118
+ : { ...row, stored_category: storedCategory, category };
119
+ }
120
+
90
121
  const sinceTs = parseSince(sinceArg);
91
122
  const onlyArg = String(argValue('--only', 'all') || 'all').toLowerCase();
92
123
  const rows = files.flatMap(readRows)
124
+ .map(normalizeRowCategory)
93
125
  .filter((row) => sinceTs == null || Number(row.ts || 0) >= sinceTs)
94
126
  .filter((row) => !toolFilter || rowTool(row) === toolFilter)
95
127
  .filter((row) => !agentFilter || String(row.agent || '-') === agentFilter)
@@ -150,6 +182,8 @@ const actionableByFamily = tally(actionableRows, categoryFamily);
150
182
  const commandExitByTool = tally(commandExitRows, rowTool);
151
183
  const expectedByCategory = tally(expectedRows, rowCategory);
152
184
  const patchByCategory = tally(patchRows, rowCategory);
185
+ const reclassifiedRows = rows.filter((row) => row.stored_category && row.stored_category !== rowCategory(row));
186
+ const reclassifiedByCategory = tally(reclassifiedRows, (row) => `${row.stored_category} -> ${rowCategory(row)}`);
153
187
 
154
188
  if (jsonMode) {
155
189
  console.log(JSON.stringify({
@@ -160,6 +194,7 @@ if (jsonMode) {
160
194
  expected_absorbed: { shown: expectedRecent.length, matched: expectedRows.length },
161
195
  session_cancellations: { shown: 0, matched: cancellationRows.length },
162
196
  patch_failures: { matched: patchRows.length, categories: asObject(patchByCategory) },
197
+ reclassified: { matched: reclassifiedRows.length, categories: asObject(reclassifiedByCategory) },
163
198
  since: sinceTs ? new Date(sinceTs).toISOString() : null,
164
199
  filters: {
165
200
  tool: toolFilter,
@@ -200,6 +235,7 @@ console.log(`actionable families (matched): ${asText(actionableByFamily)}`);
200
235
  console.log(`patch failures (matched): ${patchRows.length} — ${asText(patchByCategory)}`);
201
236
  console.log(`command-exit tools (matched): ${asText(commandExitByTool)}`);
202
237
  console.log(`expected/absorbed categories (matched): ${asText(expectedByCategory)}`);
238
+ console.log(`reclassified rows (matched): ${reclassifiedRows.length} — ${asText(reclassifiedByCategory)}`);
203
239
  console.log(`shown categories: ${asText(byCategory)}`);
204
240
  for (const row of recent) {
205
241
  const tool = rowTool(row);
@@ -129,13 +129,13 @@ try {
129
129
 
130
130
  // ── Phase D: cancellation under load ─────────────────────────────────────
131
131
  const bg = await timed('shell-async', /task_id/, () => executeBuiltinTool('shell', {
132
- command: 'node -e "setTimeout(()=>{}, 30000)"', run_in_background: true, timeout_ms: 60_000,
132
+ command: 'node -e "setTimeout(()=>{}, 30000)"',
133
133
  }, root, { sessionId: 'stress-cancel' }));
134
134
  const bgId = (/task_id:\s*(\S+)/.exec(String(bg)) || [])[1];
135
135
  if (!bgId) failures.push('async shell did not return task_id');
136
136
  else {
137
137
  await timed('task-cancel', /cancelled/, () => executeBuiltinTool('task', { action: 'cancel', task_id: bgId }, root, { sessionId: 'stress-cancel' }));
138
- const st = await timed('task-status', /cancelled|failed/, () => executeBuiltinTool('task', { action: 'status', task_id: bgId }, root, { sessionId: 'stress-cancel' }));
138
+ const st = await timed('task-status', /cancelled|failed/, () => executeBuiltinTool('task', { action: 'read', task_id: bgId }, root, { sessionId: 'stress-cancel' }));
139
139
  if (!/cancelled/.test(String(st))) failures.push(`cancelled task not reported cancelled: ${String(st).slice(0, 120)}`);
140
140
  }
141
141
  } finally {
@@ -211,9 +211,6 @@ function buildLeadRoleContent({ PLUGIN_ROOT, DATA_DIR, includeLeadBrief = true }
211
211
  const general = readOptional(path.join(LEAD_DIR, '01-general.md'));
212
212
  const parts = [];
213
213
 
214
- const toolLead = readOptional(path.join(LEAD_DIR, 'lead-tool.md'));
215
- if (toolLead) parts.push(toolLead);
216
-
217
214
  if (includeLeadBrief) {
218
215
  const briefLead = readOptional(path.join(LEAD_DIR, 'lead-brief.md'));
219
216
  if (briefLead) parts.push(briefLead);
@@ -7,17 +7,9 @@
7
7
  - Confirm destructive/hard-to-reverse actions against explicit validated paths;
8
8
  never `~`, a root, or unresolved variables/globs; report material deletion
9
9
  recoverability.
10
- - Ask only for decisions.
11
- - Investigate, build, and verify only what the requested outcome requires;
12
- trust internal and framework guarantees.
13
- - Blocking tests cover only essential integrity, security, compatibility, and
14
- buildability invariants. Treat mutable behavior, UX, exact text, snapshots,
15
- and implementation shape as advisory specifications; update them when the
16
- requested behavior changes instead of preserving obsolete behavior.
17
10
  - After required work, run final verification only when the outcome needs
18
11
  evidence the successful tool result does not already give, and run only
19
- affected blocking invariants. Verification is that extra check, not
20
- reopening already obtained content. Combine commands when dependency or
12
+ affected blocking invariants. Combine commands when dependency or
21
13
  atomicity requires it.
22
14
  - Mid-task: replacement supersedes; addition folds in; status gets a brief
23
15
  answer while work continues. After compaction, resume the summary.
@@ -5,15 +5,18 @@
5
5
  path/name only→`find`; wildcard/recursive paths→`glob` (including known-root
6
6
  unknown descendants); exact directory entries→`list`;
7
7
  file-content search→`grep`; known-file content→`read`;
8
- exact symbol, body, or relation→`code_graph`;
8
+ exact symbol, body, or relation→`code_graph`
9
+ (identifier declarations/usages→`code_graph`; literal values/strings→`grep`);
10
+ local Git repository inspection and mutation→`git`;
9
11
  program execution, calculations, data transformation, file generation, or
10
12
  unsupported formats→`shell`;
11
13
  web/current→`search`; returned URL body→`web_fetch`;
12
14
  prior work→`recall` (history only, never current local state);
13
15
  durable compact English memory→`memory`;
14
16
  explicit Project change→`cwd`
15
- (a shell-local `cd` never changes the Project);
16
- explicit user-requested conversation reset→`session_manage`.
17
+ (a shell-local `cd` never changes the Project).
18
+ Paths reachable by expanding an environment variable or the home directory
19
+ are resolved locations, not unknowns.
17
20
  Use only named tools present in the current tool surface.
18
21
  - Requirements define what must be true; evidence establishes what is true.
19
22
  Never use one as the other. Treat supplied target locations as resolved;
@@ -22,30 +25,56 @@
22
25
  inspect the original content itself. Within the current project, pass project-relative
23
26
  paths and omit optional scopes equal to its root; explicit paths may be
24
27
  outside cwd only for targets outside the project.
25
- - After identifying all result-critical evidence needs, plan the fewest
26
- evidence-complete dependent rounds, then the fewest calls. Known state
27
- system/framework guarantees, supplied facts, exact lines or values already
28
- visible here, tool returns, applied patches, and proved checks — is never
29
- re-found, re-derived, or re-verified.
28
+ For a required new file, Add File itself is the atomic absence check: call
29
+ it directly, and inspect only if it reports that the target already exists.
30
+ - Evidence economy: investigate, build, and verify only what the requested
31
+ outcome requires; trust internal and framework guarantees. After
32
+ identifying all result-critical evidence needs, plan the fewest
33
+ evidence-complete dependent rounds, then the fewest calls.
34
+ Known state — system/framework guarantees, supplied facts, exact lines or
35
+ values already visible here, tool returns, applied patches, and proved
36
+ checks — is never re-found, re-derived, or re-verified at any granularity:
37
+ no re-query call, no confirmation subcommand inside a shell command, no
38
+ availability probe for what the operation itself would report, no reopening
39
+ a file to rebuild context or confirm an edit, no rerun of a passed check.
30
40
  A hole (needed content absent and not reconstructable) is fetched once;
31
- a change re-opens only that hole. Batch only calls whose need and inputs
32
- cannot be changed or eliminated by another result; otherwise run the cheapest
33
- decisive call that satisfies the remaining evidence needs. Before each batch, deduplicate
34
- the remaining necessary facets, route each once to the cheapest sufficient
35
- tool, and launch independent facets together never split or duplicate a
36
- facet across tools, mutate merely to widen retrieval, reserve known work, or
37
- cap fanout.
38
- Cover every independent facet of the round in one batch — one best-routed
39
- call per facet; extra tools on the same facet add cost, not progress.
40
- Returned output is fully mined
41
- before the next round. `code_graph references` supplies the declaration and
42
- scoped usages and ends that facet; values/locations end at the context grep returns; `read`
43
- covers only what returned spans cannot, as an anchored offset/limit
44
- window. Already obtained hunk text is any visible span `grep`,
45
- `code_graph`, `shell`, system/reminder text, or `read`not only `read`.
46
- Each follow-up may address only facts left unresolved or changed by
47
- prior results; never re-query or re-verify established facts. Evidence that
48
- determines the answer, edit, or deliverable ends retrieval — patch if needed.
41
+ a change re-opens only that hole. Returned output is fully mined before the
42
+ next round. `code_graph references` supplies the declaration and scoped
43
+ usages and ends that facet; values/locations end at the context grep
44
+ returns; `read` covers only what returned spans cannot, as an anchored
45
+ offset/limit window. Already obtained hunk text is any visible span
46
+ `grep`, `code_graph`, `shell`, system/reminder text, or `read` not only
47
+ `read`. Each follow-up may address only facts left unresolved or changed by
48
+ prior results. Evidence that determines the answer, edit, or deliverable
49
+ ends retrieval patch if needed.
50
+ - Parallel batching: independent calls share one batch by default — one
51
+ best-routed call per facet. Cost is counted in rounds, not calls: a batch
52
+ of N calls in one message is one round, so a call-count saving never
53
+ justifies folding work into a single worse-routed call.
54
+ The opening round is a one-shot sweep: every facet knowable from the
55
+ request alone enumeration, content probes, file samples launches in
56
+ that single first batch. Each later round exists only for facets the
57
+ previous round's results created; a facet no result produced belonged in
58
+ the round before it.
59
+ Opening-round batching never licenses a guessed path. `glob.path` must be
60
+ an established existing directory; omit it for the current Project. When
61
+ the location itself is unknown, use `find` first and call `glob` only if
62
+ wildcard descendants are still needed.
63
+ Serialize two calls only when the later call's
64
+ inputs are actually produced by the earlier result; the mere possibility
65
+ that a result could reshape later work never defers an independent call.
66
+ Before each batch, deduplicate the remaining necessary facets and route
67
+ each once to the cheapest sufficient tool — never split or duplicate a
68
+ facet across tools, mutate merely to widen retrieval, reserve known work,
69
+ or cap fanout. Applying one analysis to many targets is a single
70
+ parameterized call over all targets, not one call per target.
71
+ Enumerating sibling directories or same-kind files is one wildcard call
72
+ (`glob`, or `read` with a glob for content sampling), never a
73
+ directory-by-directory `list` walk or one `read` per file.
74
+ - Blocking checks cover only essential integrity, security, compatibility,
75
+ and buildability invariants. Treat mutable behavior, UX, exact text,
76
+ snapshots, and implementation shape as advisory specifications; update them
77
+ when the requested behavior changes instead of preserving obsolete behavior.
49
78
  - A successful verification closes the task unless later changes affect it.
50
79
  Rerun a failed action only after its inputs or subject changes; otherwise
51
80
  report it unresolved.
@@ -53,17 +82,19 @@
53
82
  mutate only when the deliverable requires it, first preserving evidence
54
83
  at risk. Never mutate merely to clear an obstacle or unexpected state;
55
84
  unrecoverably lost evidence ends its search — report best effort.
56
- - Before `apply_patch`, use only already obtained hunk text. Never infer
57
- patch context from another file, a sample, or expected text. Never
58
- reopen a path to refresh patch context or to confirm a successful
59
- apply_patch. Apply all determined edits in one cohesive `apply_patch`
60
- call.
61
- Hand-authored text is edited only with `apply_patch`. Use `shell` for program
62
- execution, runtime/state operations, calculations, data transformation, file
63
- generation, or formats unsupported by file tools. Do not use `shell` instead
64
- of an available file tool for ordinary file-content inspection.
65
- - Shell commands start in the foreground. If still running after 10 seconds,
66
- the call returns a tracked `task_id` and completion arrives by notification.
85
+ - Before the exposed file-editing tool, use only already obtained exact source
86
+ text. Never infer edit context from another file, a sample, or expected text.
87
+ For context patches, include exact unchanged lines around each change and use
88
+ a class/function locator when that context is not unique.
89
+ - Apply all determined changes in the fewest safe calls the active tool
90
+ supports. Hand-authored text is edited only with the exposed file-editing
91
+ tool.
92
+ - Avoid Shell for file operations covered by dedicated tools unless explicitly
93
+ instructed or after verifying that a dedicated tool cannot do the job.
94
+ Shell otherwise joins investigation only for facts requiring execution or
95
+ unsupported decoding; an already-open shell is never a routing reason.
96
+ - Shell commands start in the foreground. If still running after 15 seconds,
97
+ the command continues as a tracked `task_id`; completion arrives by notification.
67
98
  Only when the request explicitly requires
68
99
  a service to survive after the run exits, detach it at shell level (for
69
100
  example, `nohup ... &`); never detach ordinary jobs merely to avoid tracking.
@@ -73,5 +104,6 @@
73
104
  to return the current status and output snapshot. If it is still running,
74
105
  await the completion notification; do not
75
106
  call `task` again unless the user explicitly asks for another snapshot.
76
- Omit timeout by default, including for long jobs; set it only for a real total
77
- deadline, since it kills even async jobs.
107
+ Omit `timeout_ms` by default, including for long jobs. A positive value is a
108
+ hard total deadline that kills the command even after task promotion; `0`
109
+ means no deadline.
@@ -173,6 +173,9 @@ function maintenanceRouteToPreset(routeOrName, agent) {
173
173
  const effort = String(routeOrName.effort || '').trim();
174
174
  if (effort) out.effort = effort;
175
175
  if (routeOrName.fast === true) out.fast = true;
176
+ if (routeOrName.modelParameters && typeof routeOrName.modelParameters === 'object') {
177
+ out.modelParameters = { ...routeOrName.modelParameters };
178
+ }
176
179
  return out;
177
180
  }
178
181
 
@@ -49,8 +49,9 @@ function isAgentProgressWatchdogAbortError(err) {
49
49
  return typeof msg === 'string' && WATCHDOG_ABORT_RE.test(msg);
50
50
  }
51
51
 
52
- // Tools that enforce their own execution deadline: 'shell' kills the process
53
- // at its configured timeout. These are NOT blanket-exempted from the
52
+ // Tools that enforce their own execution deadline: shell kills the process
53
+ // only when the caller supplied a positive timeout_ms. These are NOT
54
+ // blanket-exempted from the
54
55
  // tool-running watchdog — if their own
55
56
  // deadline timer dies the session would otherwise hang forever. Instead the
56
57
  // watchdog raises the tool-running ceiling to their self-deadline + a grace
@@ -60,10 +61,6 @@ const SELF_DEADLINE_TOOLS = new Set(['shell']);
60
61
  // Grace added on top of a tool's own deadline before the watchdog steps in, so
61
62
  // the tool's in-process kill always fires first under normal operation.
62
63
  const TOOL_SELF_DEADLINE_GRACE_MS = 60_000;
63
- // Fallback deadline matching the shell implementation's 120s foreground
64
- // timeout.
65
- const SHELL_DEFAULT_TIMEOUT_MS = 120_000;
66
-
67
64
  function bareToolName(toolName) {
68
65
  if (typeof toolName !== 'string' || !toolName) return '';
69
66
  // Strip any MCP/server prefix (e.g. 'server__shell' or 'server.shell').
@@ -80,17 +77,16 @@ function isSelfDeadlineTool(toolName) {
80
77
  * recorded into the progress snapshot at dispatch time. Returns a positive
81
78
  * number when the tool enforces its own deadline, or null when unknown/missing
82
79
  * (caller then falls back to the plain toolRunningMs ceiling).
83
- * - shell: explicit `timeout` (ms) if positive, else the 120s default.
80
+ * - shell: explicit positive `timeout_ms`; omitted/0 has no self-deadline.
84
81
  */
85
82
  export function resolveToolSelfDeadlineMs(toolName, args) {
86
83
  if (!isSelfDeadlineTool(toolName)) return null;
87
84
  const bare = bareToolName(toolName);
88
85
  const a = (args && typeof args === 'object') ? args : {};
89
86
  if (bare === 'shell') {
90
- const t = Number(a.timeout);
87
+ const t = Number(a.timeout_ms);
91
88
  if (Number.isFinite(t) && t > 0) return t;
92
- const envDefault = parseInt(process.env.BASH_DEFAULT_TIMEOUT_MS ?? '', 10);
93
- return envDefault > 0 ? envDefault : SHELL_DEFAULT_TIMEOUT_MS;
89
+ return null;
94
90
  }
95
91
  return null;
96
92
  }
@@ -130,7 +130,7 @@ function traceAgentCompact({
130
130
  }
131
131
 
132
132
  const TOOL_ARG_KEYS = {
133
- read: ['path', 'offset', 'limit', 'line', 'context', 'symbol'],
133
+ read: ['path', 'offset', 'limit', 'line', 'context'],
134
134
  grep: ['pattern', 'path', 'glob', 'output_mode', 'head_limit', 'offset'],
135
135
  glob: ['pattern', 'path', 'head_limit', 'offset', 'sort'],
136
136
  find: ['query', 'path', 'head_limit'],
@@ -140,7 +140,7 @@ const TOOL_ARG_KEYS = {
140
140
  code_graph: ['mode', 'file', 'files', 'symbol', 'symbols', 'body', 'language', 'limit', 'depth', 'page', 'cwd'],
141
141
  shell: ['command', 'timeout_ms'],
142
142
  task: ['action', 'task_id'],
143
- edit: ['path', 'replace_all', 'edits'],
143
+ edit: ['file_path', 'replace_all'],
144
144
  edit_many: ['edits'],
145
145
  write: ['path'],
146
146
  apply_patch: ['base_path', 'dry_run'],
@@ -305,11 +305,10 @@ function classifyPatchFailure(text) {
305
305
  if (PATCH_COMMITTED_WRITES_RE.test(text)) return 'patch/partial-apply';
306
306
  // Resource guards (byte cap) are deliberate rejections, not tool defects.
307
307
  if (/patch too large|byte cap/.test(text)) return 'patch/limit';
308
- if (/parse failed|invalid patch|malformed patch|missing \*\*\* (?:begin|end) patch|patch body is empty|unsupported patch format/.test(text)
309
- // `patch contained no file sections` / `contains an empty file path`:
310
- // the envelope parsed but produced nothing applicable — a patch-text
311
- // defect, not a context miss.
312
- || /contained no file sections|contains an empty file path/.test(text)) return 'patch/parse';
308
+ if (/(?:multiple|conflicting) operations target/.test(text)) return 'patch/duplicate-target';
309
+ // Inspect the nested cause before the outer `V4A parse failed` wrapper.
310
+ // Conversion-time context misses are edit-evidence failures, not malformed
311
+ // patch envelopes.
313
312
  if (/hunk rejected|context not found|context mismatch|anchor not found|expected first old(?:\/context| line)/.test(text)) {
314
313
  // Stale context requires REAL evidence that the region exists but has
315
314
  // moved/changed (nearest line, divergent line, rejected hunk). The
@@ -319,10 +318,16 @@ function classifyPatchFailure(text) {
319
318
  ? 'patch/stale-context'
320
319
  : 'patch/context';
321
320
  }
321
+ if (/parse failed|invalid patch|malformed patch|missing \*\*\* (?:begin|end) patch|patch body is empty|unsupported patch format/.test(text)
322
+ // `patch contained no file sections` / `contains an empty file path`:
323
+ // the envelope parsed but produced nothing applicable — a patch-text
324
+ // defect, not a context miss.
325
+ || /contained no file sections|contains an empty file path/.test(text)) return 'patch/parse';
326
+ if (/target\(s\) fall outside the write root/.test(text)) return 'path/outside-root';
322
327
  // Missing/unreadable targets are path problems even when a preflight
323
328
  // wrapper reports them; fall through to the generic path rules.
324
329
  if (/enoent|no such file|target unreadable|source (?:missing or )?unreadable|destination unreadable|cannot find/.test(text)) return null;
325
- if (/preflight rejected|does not support|cannot be combined|only one v4a rename|missing parsed entry|internal js dispatch|rollback snapshot target/.test(text)) return 'patch/verification';
330
+ if (/preflight rejected|does not support|cannot be combined|only one v4a rename|missing parsed entry|internal js dispatch|rollback snapshot target|refusing hunkless delete/.test(text)) return 'patch/verification';
326
331
  return null;
327
332
  }
328
333
 
@@ -368,6 +373,7 @@ function classifyToolFailure(resultText, toolName) {
368
373
  || /must be|schema|required|old_string is .*>?=/.test(text)) return 'schema/args';
369
374
  if (/not in allow-list|not allowed/.test(text)) return 'permission';
370
375
  if (String(toolName || '') === 'shell' || /^\s*\[exit code:\s*\d+\]/i.test(raw)) return 'command-exit';
376
+ if (String(toolName || '') === 'glob' && /enoent|path does not exist|directory does not exist/.test(text)) return 'navigation/miss';
371
377
  if (/enoent|cannot find|not found at this path|path does not exist|no such file|file not found in graph|unreadable/.test(text)) return 'path/enoent';
372
378
  if (/timed out|timeout|interrupted|aborted/.test(text)) return 'timeout/abort';
373
379
  if (/unknown tool|tool.*not.*available|missing.*tool/.test(text)) return 'tool-surface';
@@ -414,7 +420,7 @@ function traceAgentToolFailure({ sessionId, iteration, toolName, toolKind, toolM
414
420
  }
415
421
  }
416
422
 
417
- function traceAgentTool({ sessionId, iteration, toolName, toolKind, toolMs, toolArgs, agent, resultKind, model, resultText, localSearchTelemetry = null, cwd }) {
423
+ function traceAgentTool({ sessionId, iteration, toolName, toolKind, toolMs, toolArgs, agent, resultKind, model, resultText, localSearchTelemetry = null, resultTelemetry = null, cwd }) {
418
424
  const nextCallCount = countJsonNextCalls(resultText);
419
425
  const resultBytesEst = typeof resultText === 'string' ? Buffer.byteLength(resultText, 'utf8') : 0;
420
426
  const resultLinesEst = typeof resultText === 'string' && resultText.length > 0 ? resultText.split('\n').length : 0;
@@ -465,6 +471,9 @@ function traceAgentTool({ sessionId, iteration, toolName, toolKind, toolMs, tool
465
471
  local_search: localSearchTelemetry && Object.keys(localSearchTelemetry).length > 0
466
472
  ? { ...localSearchTelemetry }
467
473
  : null,
474
+ payload: resultTelemetry?.integrity
475
+ ? { integrity: { ...resultTelemetry.integrity } }
476
+ : {},
468
477
  cwd: cwd || null,
469
478
  });
470
479
  if (
@@ -586,6 +595,60 @@ export function traceAgentShellOutput({
586
595
  });
587
596
  }
588
597
 
598
+ export function buildToolOutputTelemetryPayload({
599
+ toolCallId,
600
+ preOffloadBytes,
601
+ postOffloadBytes,
602
+ modelVisibleBytes,
603
+ offloaded,
604
+ resultKind,
605
+ }) {
606
+ const before = Number(preOffloadBytes);
607
+ const after = Number(postOffloadBytes);
608
+ if (!Number.isFinite(before) || before < 0 || !Number.isFinite(after) || after < 0) return null;
609
+ const visible = Math.max(0, Math.trunc(Number(modelVisibleBytes) || 0));
610
+ const saved = Math.max(0, Math.trunc(before) - visible);
611
+ return {
612
+ tool_call_id: toolCallId || null,
613
+ result_kind: resultKind || null,
614
+ pre_offload_bytes: Math.trunc(before),
615
+ post_offload_bytes: Math.trunc(after),
616
+ model_visible_bytes: visible,
617
+ saved_bytes: saved,
618
+ reduction_pct: before > 0
619
+ ? Math.round((1 - visible / before) * 100)
620
+ : null,
621
+ offloaded: offloaded === true,
622
+ };
623
+ }
624
+
625
+ export function traceAgentToolOutput({
626
+ sessionId,
627
+ toolName,
628
+ toolCallId,
629
+ preOffloadBytes,
630
+ postOffloadBytes,
631
+ modelVisibleBytes,
632
+ offloaded,
633
+ resultKind,
634
+ }) {
635
+ const payload = buildToolOutputTelemetryPayload({
636
+ toolCallId,
637
+ preOffloadBytes,
638
+ postOffloadBytes,
639
+ modelVisibleBytes,
640
+ offloaded,
641
+ resultKind,
642
+ });
643
+ if (!sessionId || !payload || payload.offloaded !== true) return;
644
+ appendAgentTrace({
645
+ sessionId,
646
+ kind: 'tool_output',
647
+ tool_name: toolName || null,
648
+ payload,
649
+ });
650
+ }
651
+
589
652
  // Per-turn batch shape — one row per assistant turn with the number of
590
653
  // tool calls observed. Lets a consumer compute Lead-side multi-tool
591
654
  // adoption ratio (calls > 1 / total turns) directly from trace rows
@@ -13,6 +13,7 @@ import {
13
13
  traceAgentToolFailure,
14
14
  traceAgentCompress,
15
15
  traceAgentShellOutput,
16
+ traceAgentToolOutput,
16
17
  traceAgentBatch,
17
18
  } from './agent-trace-format.mjs';
18
19
 
@@ -342,6 +343,7 @@ export {
342
343
  grokCacheChainTraceFields,
343
344
  traceAgentCompress,
344
345
  traceAgentShellOutput,
346
+ traceAgentToolOutput,
345
347
  traceAgentBatch,
346
348
  traceStreamAborted,
347
349
  traceStreamStalled,
@@ -9,6 +9,7 @@ import {
9
9
  hasAnthropicOAuthCredentials,
10
10
  hasOpenAIOAuthCredentials,
11
11
  hasGrokOAuthCredentials,
12
+ hasCursorOAuthCredentials,
12
13
  } from './providers/oauth-credential-probes.mjs';
13
14
 
14
15
  // Thin wrapper around resolvePluginData so callers in this orchestrator tree
@@ -155,6 +156,9 @@ function buildDefaultConfig(options = {}) {
155
156
  // stored in mixdog-config.json — enabled at runtime from the presence of
156
157
  // Mixdog-owned credentials.
157
158
  providers['grok-oauth'] = { enabled: detectCredentials ? hasGrokOAuthCredentials() : false };
159
+ // Experimental direct Cursor wire provider. It remains disabled unless a
160
+ // Mixdog-owned login or CURSOR_ACCESS_TOKEN is present.
161
+ providers['cursor-oauth'] = { enabled: detectCredentials ? hasCursorOAuthCredentials() : false };
158
162
  // Local providers — opt-in via setup UI after HTTP ping confirms server is running
159
163
  providers.ollama = { enabled: false, baseURL: 'http://localhost:11434/v1' };
160
164
  providers.lmstudio = { enabled: false, baseURL: 'http://localhost:1234/v1' };
@@ -479,6 +483,9 @@ export function loadConfig(options = {}) {
479
483
  if (kc) mergedProviders[name] = { ...(mergedProviders[name] || {}), apiKey: kc, enabled: true };
480
484
  }
481
485
  }
486
+ // Cursor account access is OAuth-only. The dashboard's "API"
487
+ // meter is a quota bucket on that account, not a separate provider.
488
+ delete mergedProviders['cursor-api'];
482
489
  // Drop unknown maintenance keys (e.g. truly legacy slot names from
483
490
  // pre-removal installs). Every valid fallback slot lives in
484
491
  // DEFAULT_MAINTENANCE, so the allow-list below is the single
@@ -799,6 +806,8 @@ const FAST_CAPABLE_PRESET_PROVIDERS = new Set([
799
806
  'anthropic-oauth',
800
807
  'openai',
801
808
  'openai-oauth',
809
+ 'cursor-oauth',
810
+ 'cursor-api',
802
811
  ]);
803
812
  function normalizeAgentProviderId(provider) {
804
813
  const id = String(provider || '').trim();