@yansigit/opencodex 2.33.1 → 2.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/README.md +3 -3
  2. package/gui/dist/assets/index-BjCaHxdz.js +112 -0
  3. package/gui/dist/assets/index-DLkXOXLC.css +1 -0
  4. package/gui/dist/index.html +2 -2
  5. package/package.json +1 -3
  6. package/src/adapters/anthropic.ts +79 -2
  7. package/src/adapters/command-code.ts +40 -3
  8. package/src/adapters/cursor/call-id.ts +44 -0
  9. package/src/adapters/cursor/checkpoint-store.ts +15 -10
  10. package/src/adapters/cursor/discovery.ts +60 -2
  11. package/src/adapters/cursor/effort-map.ts +79 -1
  12. package/src/adapters/cursor/envelope-echo.ts +9 -9
  13. package/src/adapters/cursor/live-models.ts +7 -2
  14. package/src/adapters/cursor/live-transport.ts +14 -0
  15. package/src/adapters/cursor/message-mapper.ts +4 -1
  16. package/src/adapters/cursor/native-exec-shell.ts +103 -6
  17. package/src/adapters/cursor/native-exec.ts +20 -0
  18. package/src/adapters/cursor/protobuf-request.ts +74 -39
  19. package/src/adapters/cursor/request-builder.ts +32 -10
  20. package/src/adapters/cursor/tool-definitions.ts +13 -10
  21. package/src/adapters/cursor/tool-result-normalize.ts +35 -3
  22. package/src/adapters/cursor/types.ts +16 -4
  23. package/src/adapters/cursor.ts +97 -70
  24. package/src/adapters/google-antigravity-replay.ts +105 -25
  25. package/src/adapters/google-antigravity-wire.ts +5 -0
  26. package/src/adapters/google-errors.ts +41 -12
  27. package/src/adapters/google-http.ts +12 -11
  28. package/src/adapters/google.ts +156 -65
  29. package/src/adapters/image.ts +1 -1
  30. package/src/adapters/kiro-constants.ts +15 -0
  31. package/src/adapters/kiro-tools.ts +43 -15
  32. package/src/adapters/kiro.ts +54 -9
  33. package/src/adapters/openai-chat.ts +286 -242
  34. package/src/adapters/openai-responses.ts +334 -24
  35. package/src/adapters/run-turn-queue.ts +36 -1
  36. package/src/adapters/tool-catalog-nudge.ts +2 -2
  37. package/src/adapters/xai-tool-schema.ts +436 -0
  38. package/src/bridge.ts +67 -26
  39. package/src/chat/inbound.ts +14 -1
  40. package/src/chat/outbound.ts +15 -7
  41. package/src/claude/agents-inject.ts +8 -1
  42. package/src/claude/outbound.ts +10 -8
  43. package/src/cli/account-api.ts +27 -7
  44. package/src/cli/account-extended.ts +10 -3
  45. package/src/cli/account.ts +29 -5
  46. package/src/cli/alias.ts +66 -0
  47. package/src/cli/claude.ts +26 -1
  48. package/src/cli/dispatch.ts +13 -1
  49. package/src/cli/help.ts +1 -0
  50. package/src/cli/index.ts +5 -0
  51. package/src/cli/init.ts +1 -0
  52. package/src/cli/models-runtime.ts +95 -0
  53. package/src/cli/models.ts +13 -7
  54. package/src/cli/provider-runtime.ts +16 -2
  55. package/src/cli/registry.ts +6 -1
  56. package/src/cli/telemetry-commands.ts +25 -0
  57. package/src/cli/v2.ts +34 -10
  58. package/src/codex/account-pause.ts +2 -1
  59. package/src/codex/account-priority.ts +3 -2
  60. package/src/codex/app-server-processes.ts +80 -6
  61. package/src/codex/auth-api.ts +48 -8
  62. package/src/codex/auth-context.ts +21 -18
  63. package/src/codex/catalog/aggregation.ts +6 -0
  64. package/src/codex/catalog/model-metadata.ts +13 -1
  65. package/src/codex/catalog/native-models.ts +5 -2
  66. package/src/codex/catalog/parsing.ts +16 -0
  67. package/src/codex/catalog/provider-fetch.ts +20 -37
  68. package/src/codex/catalog/sync.ts +127 -2
  69. package/src/codex/catalog.ts +1 -1
  70. package/src/codex/codex-write-lock.ts +3 -1
  71. package/src/codex/convergence-types.ts +1 -1
  72. package/src/codex/convergence.ts +22 -2
  73. package/src/codex/desired-state.ts +2 -2
  74. package/src/codex/desktop-app-restart.ts +18 -5
  75. package/src/codex/inject-coordination.ts +83 -0
  76. package/src/codex/inject.ts +14 -1
  77. package/src/codex/log-guard/inspect.ts +22 -4
  78. package/src/codex/model-entitlements.ts +9 -2
  79. package/src/codex/prompt-layers.ts +371 -25
  80. package/src/codex/prompt-text-probe.ts +238 -0
  81. package/src/codex/quota.ts +123 -18
  82. package/src/codex/routing.ts +9 -0
  83. package/src/codex/subagent-model-fallback.ts +198 -27
  84. package/src/codex/transition-state.ts +107 -8
  85. package/src/combos/types.ts +10 -0
  86. package/src/compatibility/openai-responses.ts +33 -1
  87. package/src/config/autonomous-remediation.ts +21 -0
  88. package/src/config/provider-validation.ts +14 -0
  89. package/src/config/rebase-provenance.ts +68 -0
  90. package/src/config.ts +191 -17
  91. package/src/generated/compatibility-version.json +229 -153
  92. package/src/images/loop.ts +5 -4
  93. package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
  94. package/src/lab/fabric/producer-child.ts +1 -1
  95. package/src/lib/config-ownership.ts +20 -0
  96. package/src/lib/errors.ts +11 -2
  97. package/src/lib/package-tree-integrity.ts +101 -0
  98. package/src/oauth/aistudio-credentials.ts +65 -0
  99. package/src/oauth/aistudio-native-daemon.ts +54 -0
  100. package/src/oauth/generic-account-failover.ts +231 -0
  101. package/src/oauth/index.ts +57 -5
  102. package/src/oauth/key-providers.ts +10 -1
  103. package/src/oauth/kiro.ts +45 -0
  104. package/src/oauth/login-cli.ts +19 -20
  105. package/src/oauth/types.ts +15 -0
  106. package/src/providers/codex-capacity.ts +5 -2
  107. package/src/providers/command-code-efforts.ts +38 -6
  108. package/src/providers/context-cap.ts +4 -3
  109. package/src/providers/default-aliases.ts +65 -0
  110. package/src/providers/derive.ts +28 -0
  111. package/src/providers/fastwire.ts +7 -1
  112. package/src/providers/model-presets.ts +119 -0
  113. package/src/providers/new-model-policy.ts +146 -0
  114. package/src/providers/provider-id-rewrite.ts +2 -1
  115. package/src/providers/quota.ts +73 -14
  116. package/src/providers/registry.ts +160 -68
  117. package/src/providers/slug-codec.ts +52 -0
  118. package/src/responses/code-mode-helper-compat.ts +50 -0
  119. package/src/responses/custom-tool-compat.ts +34 -10
  120. package/src/responses/parser.ts +4 -0
  121. package/src/responses/schema.ts +5 -1
  122. package/src/responses/thought-signature-replay.ts +17 -0
  123. package/src/router.ts +40 -2
  124. package/src/routing/analytics.ts +1 -0
  125. package/src/routing/quota.ts +10 -0
  126. package/src/server/auth-cors.ts +23 -0
  127. package/src/server/chat-completions.ts +24 -16
  128. package/src/server/chat-native-sse.ts +3 -3
  129. package/src/server/chat-native.ts +30 -11
  130. package/src/server/claude-messages.ts +1 -1
  131. package/src/server/effort-policy.ts +16 -0
  132. package/src/server/index.ts +152 -80
  133. package/src/server/lifecycle.ts +52 -1
  134. package/src/server/management/agent-settings-routes.ts +31 -15
  135. package/src/server/management/codex-prompt-routes.ts +570 -0
  136. package/src/server/management/combo-routes.ts +2 -1
  137. package/src/server/management/config-routes.ts +27 -9
  138. package/src/server/management/context.ts +9 -0
  139. package/src/server/management/logs-usage-routes.ts +1 -1
  140. package/src/server/management/model-routes.ts +266 -0
  141. package/src/server/management/provider-routes.ts +137 -3
  142. package/src/server/management/routing-profile-routes.ts +2 -2
  143. package/src/server/management-api.ts +2 -0
  144. package/src/server/relay-eager.ts +147 -20
  145. package/src/server/relay.ts +251 -19
  146. package/src/server/request-log-conversation.ts +21 -0
  147. package/src/server/request-log.ts +46 -20
  148. package/src/server/responses/collaboration.ts +42 -5
  149. package/src/server/responses/combo-stream-preflight.ts +10 -3
  150. package/src/server/responses/core.ts +571 -137
  151. package/src/server/responses/empty-completion-guard.ts +35 -0
  152. package/src/server/responses/fetch-helpers.ts +14 -6
  153. package/src/server/responses/input-admission.ts +3 -1
  154. package/src/server/responses/passthrough-error.ts +33 -9
  155. package/src/server/responses/responses-field-backfill.ts +105 -13
  156. package/src/server/responses/ws-upstream.ts +35 -5
  157. package/src/server/responses-custom-tool-repair.ts +52 -7
  158. package/src/server/responses-terminal-repair.ts +25 -4
  159. package/src/server/sse-frame-buffer.ts +31 -4
  160. package/src/server/ws-bridge.ts +15 -4
  161. package/src/telemetry/dispatcher.ts +44 -0
  162. package/src/telemetry/fingerprint.ts +24 -0
  163. package/src/telemetry/hook.ts +43 -0
  164. package/src/telemetry/ledger.ts +54 -0
  165. package/src/telemetry/types.ts +23 -0
  166. package/src/types/config.ts +66 -14
  167. package/src/types/provider.ts +77 -0
  168. package/src/types/request.ts +16 -10
  169. package/src/types/tools.ts +20 -25
  170. package/src/types.ts +1 -0
  171. package/src/usage/expected-prices.ts +43 -24
  172. package/src/usage/log.ts +17 -7
  173. package/src/web-search/exa-executor.ts +40 -9
  174. package/src/web-search/loop.ts +5 -4
  175. package/gui/dist/assets/index-CIDo4y4k.js +0 -102
  176. package/gui/dist/assets/index-DrSQdTRd.css +0 -1
  177. package/src/server/aistudio-ws-hub.ts +0 -295
@@ -1,4 +1,4 @@
1
- import { spawn, spawnSync, type ChildProcessWithoutNullStreams } from "node:child_process";
1
+ import { execFileSync, spawn, spawnSync, type ChildProcessWithoutNullStreams } from "node:child_process";
2
2
  import { resolve } from "node:path";
3
3
  import { create } from "@bufbuild/protobuf";
4
4
  import {
@@ -19,6 +19,7 @@ import {
19
19
  type ExecServerMessage,
20
20
  } from "./gen/agent_pb";
21
21
  import { errorText, execBytes, execStreamCloseBytes } from "./native-exec-common";
22
+ import { resolveTrustedWindowsTaskkillExe } from "../../lib/windows-elevation";
22
23
  export { nativeShellDisabledMessage, type CursorNativeExecPolicyContext } from "./native-exec-policy";
23
24
  import { nativeShellDisabledMessage, type CursorNativeExecPolicyContext } from "./native-exec-policy";
24
25
  import {
@@ -34,6 +35,7 @@ export const CURSOR_BACKGROUND_SHELL_TERM_GRACE_MS = 2_000;
34
35
 
35
36
  type BackgroundShellTerminationReason = "session_close" | "idle" | "absolute" | "shutdown";
36
37
  type BackgroundShellTimer = ReturnType<typeof setTimeout>;
38
+ type ProcessGroupLiveness = "alive" | "gone" | "unknown";
37
39
 
38
40
  export interface BackgroundShellTerminationReport {
39
41
  attempted: number;
@@ -44,10 +46,14 @@ export interface BackgroundShellTerminationReport {
44
46
 
45
47
  export interface BackgroundShellRuntime {
46
48
  spawn: typeof spawn;
49
+ platform: NodeJS.Platform;
47
50
  now(): number;
48
51
  setTimer(callback: () => void, delayMs: number): BackgroundShellTimer;
49
52
  clearTimer(timer: BackgroundShellTimer): void;
50
53
  kill(child: ChildProcessWithoutNullStreams, signal?: NodeJS.Signals): boolean;
54
+ killProcessGroup(pid: number, signal?: NodeJS.Signals): boolean;
55
+ isProcessGroupAlive(pid: number): ProcessGroupLiveness;
56
+ killTree(child: ChildProcessWithoutNullStreams): boolean;
51
57
  }
52
58
 
53
59
  interface BackgroundShellEntry {
@@ -67,10 +73,30 @@ interface BackgroundShellEntry {
67
73
 
68
74
  const defaultBackgroundShellRuntime: BackgroundShellRuntime = {
69
75
  spawn,
76
+ platform: process.platform,
70
77
  now: () => Date.now(),
71
78
  setTimer: (callback, delayMs) => setTimeout(callback, delayMs),
72
79
  clearTimer: timer => clearTimeout(timer),
73
80
  kill: (child, signal) => child.kill(signal),
81
+ killProcessGroup: (pid, signal) => {
82
+ process.kill(-pid, signal);
83
+ return true;
84
+ },
85
+ isProcessGroupAlive: pid => {
86
+ try {
87
+ process.kill(-pid, 0);
88
+ return "alive";
89
+ } catch (error) {
90
+ return (error as NodeJS.ErrnoException).code === "ESRCH" ? "gone" : "unknown";
91
+ }
92
+ },
93
+ killTree: child => {
94
+ execFileSync(resolveTrustedWindowsTaskkillExe(), ["/PID", String(child.pid), "/T", "/F"], {
95
+ stdio: "pipe",
96
+ windowsHide: true,
97
+ });
98
+ return true;
99
+ },
74
100
  };
75
101
 
76
102
  let backgroundShellRuntime = defaultBackgroundShellRuntime;
@@ -343,6 +369,13 @@ function waitForBackgroundShellClose(entry: BackgroundShellEntry): Promise<boole
343
369
  });
344
370
  }
345
371
 
372
+ function waitForBackgroundShellGrace(): Promise<void> {
373
+ return new Promise(resolveWait => {
374
+ const timer = backgroundShellRuntime.setTimer(resolveWait, CURSOR_BACKGROUND_SHELL_TERM_GRACE_MS);
375
+ void timer;
376
+ });
377
+ }
378
+
346
379
  function tryKillBackgroundShell(entry: BackgroundShellEntry, signal?: NodeJS.Signals): boolean {
347
380
  try {
348
381
  return backgroundShellRuntime.kill(entry.child, signal);
@@ -351,6 +384,34 @@ function tryKillBackgroundShell(entry: BackgroundShellEntry, signal?: NodeJS.Sig
351
384
  }
352
385
  }
353
386
 
387
+ function tryKillBackgroundShellProcessGroup(entry: BackgroundShellEntry, signal?: NodeJS.Signals): boolean {
388
+ const pid = entry.child.pid;
389
+ if (typeof pid !== "number" || !Number.isSafeInteger(pid) || pid <= 0) return false;
390
+ try {
391
+ return backgroundShellRuntime.killProcessGroup(pid, signal);
392
+ } catch {
393
+ return false;
394
+ }
395
+ }
396
+
397
+ function tryKillBackgroundShellTree(entry: BackgroundShellEntry): boolean {
398
+ try {
399
+ return backgroundShellRuntime.killTree(entry.child);
400
+ } catch {
401
+ return false;
402
+ }
403
+ }
404
+
405
+ function isBackgroundShellProcessGroupAlive(entry: BackgroundShellEntry): ProcessGroupLiveness {
406
+ const pid = entry.child.pid;
407
+ if (typeof pid !== "number" || !Number.isSafeInteger(pid) || pid <= 0) return "unknown";
408
+ try {
409
+ return backgroundShellRuntime.isProcessGroupAlive(pid);
410
+ } catch {
411
+ return "unknown";
412
+ }
413
+ }
414
+
354
415
  async function terminateBackgroundShell(
355
416
  entry: BackgroundShellEntry,
356
417
  reason: BackgroundShellTerminationReason,
@@ -368,11 +429,43 @@ async function terminateBackgroundShell(
368
429
  try { entry.child.stderr.resume(); } catch { /* keep draining when supported */ }
369
430
 
370
431
  let attemptKillFailures = 0;
371
- if (!tryKillBackgroundShell(entry)) attemptKillFailures += 1;
372
- let closed = await waitForBackgroundShellClose(entry);
432
+ let treeTerminationStarted = false;
433
+ let processGroupTerminationStarted = false;
434
+ let processGroupTerminationAttempted = false;
435
+ let treeTerminationFailed = false;
436
+ if (backgroundShellRuntime.platform === "win32") {
437
+ treeTerminationStarted = tryKillBackgroundShellTree(entry);
438
+ if (!treeTerminationStarted) {
439
+ treeTerminationFailed = true;
440
+ attemptKillFailures += 1;
441
+ if (!tryKillBackgroundShell(entry)) attemptKillFailures += 1;
442
+ }
443
+ } else {
444
+ processGroupTerminationAttempted = true;
445
+ if (tryKillBackgroundShellProcessGroup(entry, "SIGTERM")) {
446
+ processGroupTerminationStarted = true;
447
+ } else {
448
+ attemptKillFailures += 1;
449
+ if (!tryKillBackgroundShell(entry)) attemptKillFailures += 1;
450
+ }
451
+ }
452
+ let closed = processGroupTerminationStarted ? false : await waitForBackgroundShellClose(entry);
373
453
  if (!closed && backgroundShells.get(entry.shellId) !== entry) closed = true;
374
- if (!closed) {
375
- if (!tryKillBackgroundShell(entry, "SIGKILL")) attemptKillFailures += 1;
454
+ if (processGroupTerminationStarted) {
455
+ await waitForBackgroundShellGrace();
456
+ if (isBackgroundShellProcessGroupAlive(entry) !== "gone" && !tryKillBackgroundShellProcessGroup(entry, "SIGKILL")) {
457
+ attemptKillFailures += 1;
458
+ }
459
+ closed = backgroundShells.get(entry.shellId) !== entry;
460
+ }
461
+ if ((!closed || treeTerminationFailed || processGroupTerminationAttempted && !processGroupTerminationStarted)
462
+ && !treeTerminationStarted && !processGroupTerminationStarted) {
463
+ if (backgroundShellRuntime.platform === "win32") {
464
+ if (!tryKillBackgroundShell(entry, "SIGKILL")) attemptKillFailures += 1;
465
+ } else if (!tryKillBackgroundShellProcessGroup(entry, "SIGKILL")) {
466
+ attemptKillFailures += 1;
467
+ if (!closed && !tryKillBackgroundShell(entry, "SIGKILL")) attemptKillFailures += 1;
468
+ }
376
469
  closed = await waitForBackgroundShellClose(entry);
377
470
  if (!closed && backgroundShells.get(entry.shellId) !== entry) closed = true;
378
471
  }
@@ -459,7 +552,11 @@ export function backgroundShellSpawnExec(execMsg: ExecServerMessage, sessionId:
459
552
  if (!admissionLease) return backgroundShellSpawnError(execMsg, args.command, cwd, "background shell limit reached");
460
553
  let child: ChildProcessWithoutNullStreams;
461
554
  try {
462
- child = backgroundShellRuntime.spawn(args.command, { cwd, shell: true });
555
+ child = backgroundShellRuntime.spawn(args.command, {
556
+ cwd,
557
+ shell: true,
558
+ ...(backgroundShellRuntime.platform === "win32" ? {} : { detached: true }),
559
+ });
463
560
  } catch (err) {
464
561
  admissionLease.release();
465
562
  return backgroundShellSpawnError(execMsg, args.command, cwd, errorText(err));
@@ -410,6 +410,19 @@ export function storeCursorBlob(data: Uint8Array, requestScope?: CursorBlobReque
410
410
  return blobId;
411
411
  }
412
412
 
413
+ /**
414
+ * Serve-time integrity for content-addressed blobs (devlog 260826_cursor_responses_gap 080):
415
+ * a raw 32-byte blob id IS the SHA-256 of its bytes, so served data whose digest mismatches
416
+ * the id means in-store corruption — the splice signature behind garbled replayed tool
417
+ * results. Ids longer than 32 bytes (digested-key namespace) and server-minted ids are not
418
+ * content-addressed and always pass.
419
+ */
420
+ export function cursorBlobServeIntegrityOk(blobId: Uint8Array, served: Uint8Array): boolean {
421
+ if (blobId.byteLength !== 32) return true;
422
+ const digest = createHash("sha256").update(served).digest();
423
+ return digest.equals(Buffer.from(blobId));
424
+ }
425
+
413
426
  /**
414
427
  * Long-lived pin for blobs referenced by an active Cursor conversation checkpoint.
415
428
  * Unlike a request scope, this lease is not sealed and is not released by getBlob hydration.
@@ -630,6 +643,13 @@ export function handleCursorNativeKv(
630
643
  if (kvMsg.message.case === "getBlobArgs") {
631
644
  const blobKey = key(kvMsg.message.value.blobId);
632
645
  const blobData = getBlob(blobKey);
646
+ // Splice-class corruption guard (devlog 260826 080): diagnostic only, never blocks serving.
647
+ if (blobData && !cursorBlobServeIntegrityOk(kvMsg.message.value.blobId, blobData)) {
648
+ debugProviderDiagnostic("cursor", "blob-integrity-mismatch", {
649
+ blobKey: blobKey.slice(0, 18),
650
+ servedBytes: blobData.byteLength,
651
+ });
652
+ }
633
653
  if (blobData && requestScope && blobRequestScopes.get(requestScope)?.kind === "request") {
634
654
  releaseHydratedBlob(blobKey, requestScope);
635
655
  }
@@ -1,9 +1,10 @@
1
1
  import { create, fromBinary, toBinary, toJson } from "@bufbuild/protobuf";
2
2
  import { fromJson, type JsonValue } from "@bufbuild/protobuf";
3
3
  import { ValueSchema } from "@bufbuild/protobuf/wkt";
4
- import type { OcxAssistantContentPart, OcxMessage, OcxRequestOptions, OcxToolResultMessage } from "../../types";
4
+ import type { OcxAssistantContentPart, OcxMessage, OcxToolResultMessage } from "../../types";
5
5
  import { namespacedToolName } from "../../types";
6
6
  import type { CursorRunRequest } from "./types";
7
+ import { decodeCursorCallId } from "./call-id";
7
8
  import { cursorNeedsExternalToolContinuation, isCursorExternalWireModel } from "./discovery";
8
9
  import { normalizeCursorToolResultText } from "./tool-result-normalize";
9
10
  import { debugProviderDiagnostic } from "../../lib/debug";
@@ -180,26 +181,9 @@ function truncateToolResultBlob(entry: RootBlobCandidate, maxBytes: number): Roo
180
181
  return markerOnly.byteLength <= maxBytes ? markerOnly : null;
181
182
  }
182
183
 
183
- function structuredOutputPrompt(textFormat: OcxRequestOptions["textFormat"]): string | undefined {
184
- if (!textFormat) return undefined;
185
- if (textFormat.type === "json_schema" && textFormat.schema) {
186
- return [
187
- "Your response must be a single valid JSON object strictly conforming to this JSON schema:",
188
- JSON.stringify(textFormat.schema),
189
- "Do not include any surrounding markdown fences, preamble, or commentary; return raw JSON only.",
190
- ].join("\n");
191
- }
192
- if (textFormat.type === "json_object") {
193
- return "Your response must be a single valid JSON object. Do not include any markdown fences or commentary; return raw JSON only.";
194
- }
195
- return undefined;
196
- }
197
-
198
184
  function systemPromptBlobs(request: CursorRunRequest): RootBlobCandidate[] {
199
185
  const prompts = request.system.length > 0 ? [...request.system] : ["You are a helpful assistant."];
200
186
  if (cursorRequestHasShellAlias(request.tools)) prompts.push(CURSOR_SHELL_ALIAS_SYSTEM_NOTE);
201
- const structuredPrompt = structuredOutputPrompt(request.textFormat);
202
- if (structuredPrompt) prompts.push(structuredPrompt);
203
187
  const cursorToolGuidance = buildCursorToolGuidanceSystemNote(
204
188
  cursorToolsForActivePrompt(request.tools, activePromptText(request), request.toolChoice),
205
189
  request.toolChoice,
@@ -222,11 +206,10 @@ function assistantRootText(
222
206
  // Cursor builds the actual model prompt from rootPromptMessagesJson (turns[] is UI/display metadata),
223
207
  // so prior history must be replayed here or a ResumeAction has nothing model-visible to continue from.
224
208
  // The active user message is excluded because it travels in the action. When the continuation cannot
225
- // rely on native MCP turn state, tool results stay assistant-role text so Cursor does not wrap them
226
- // as `<user_query>` (#1992). External replay uses a neutral "Tool output" label; protocol markers
227
- // such as [Tool Result] are reserved for native wire encoding because external models echo them.
228
- // Native resume models already carry the paired MCP result on turns[], so it is omitted from root
229
- // replay. Each entry is a SHA-256 blob ID.
209
+ // rely on native MCP turn state, tool results stay assistant-role text with a [Tool Result] /
210
+ // [Tool Error] marker so Cursor does not wrap them as `<user_query>` (#1992). Native resume models
211
+ // already carry the paired MCP result on turns[], so that marker is omitted from root replay Auto
212
+ // few-shot-mimics it as chat text otherwise. Each entry is a SHA-256 blob ID.
230
213
  function rootPromptMessages(request: CursorRunRequest, requestScope: CursorBlobRequestScopeToken): {
231
214
  ids: Uint8Array[];
232
215
  byteLength: number;
@@ -250,6 +233,42 @@ function rootPromptMessages(request: CursorRunRequest, requestScope: CursorBlobR
250
233
  const echoToolResultInRoot = cursorNeedsExternalToolContinuation(request.modelId);
251
234
  const lastRawIsToolResult = messages.at(-1)?.role === "toolResult";
252
235
  const activeUserIndex = lastRawIsToolResult ? -1 : lastActionIndex(messages);
236
+ // Repetition breaker (devlog 260826 gap-9): external full-replay flattens history to text,
237
+ // so N identical assistant/tool-result rounds replay as N identical lines and PRIME the model
238
+ // to emit the same line again (self-reinforcing loop: S2a 180x, identical-probe repetition).
239
+ // Collapse consecutive duplicates into one entry + a count marker, and count collapses so a
240
+ // strategy-change note can be appended when the pattern is severe.
241
+ let lastReplayText: string | undefined;
242
+ let lastReplayEntry: RootBlobCandidate | undefined;
243
+ let collapsedRepeats = 0;
244
+ let maxRunLength = 1;
245
+ let currentRun = 1;
246
+ const pushDeduped = (
247
+ payload: { role: string; content: [{ type: "text"; text: string }] },
248
+ role: RootBlobCandidate["role"],
249
+ opts: { messageIndex: number; text?: string },
250
+ normalized: string,
251
+ ): void => {
252
+ if (externalModel && lastReplayText !== undefined && normalized === lastReplayText && lastReplayEntry) {
253
+ collapsedRepeats++;
254
+ currentRun++;
255
+ if (currentRun > maxRunLength) maxRunLength = currentRun;
256
+ const marked = `${normalized}\n[note: this exact output was produced ${currentRun} times in a row]`;
257
+ const replacement = rootBlobCandidate(
258
+ { role: payload.role, content: [{ type: "text", text: marked }] },
259
+ role,
260
+ opts,
261
+ );
262
+ entries[entries.indexOf(lastReplayEntry)] = replacement;
263
+ lastReplayEntry = replacement;
264
+ return;
265
+ }
266
+ currentRun = 1;
267
+ const entry = rootBlobCandidate(payload, role, opts);
268
+ entries.push(entry);
269
+ lastReplayText = normalized;
270
+ lastReplayEntry = entry;
271
+ };
253
272
 
254
273
  for (let i = 0; i < messages.length; i++) {
255
274
  if (i === activeUserIndex) break;
@@ -261,6 +280,9 @@ function rootPromptMessages(request: CursorRunRequest, requestScope: CursorBlobR
261
280
  // A bare string survives blob hydration but external workers reject the completed replay
262
281
  // before tokenization (`usedTokens: 0`, then invalid_argument).
263
282
  if (text.length > 0) {
283
+ lastReplayText = undefined;
284
+ lastReplayEntry = undefined;
285
+ currentRun = 1;
264
286
  entries.push(rootBlobCandidate({
265
287
  role: "user",
266
288
  content: [{ type: "text", text }],
@@ -271,25 +293,30 @@ function rootPromptMessages(request: CursorRunRequest, requestScope: CursorBlobR
271
293
  // Native Composer state can preserve it through ThinkingMessage/history structures.
272
294
  const text = assistantRootText(message, !externalModel).trim();
273
295
  if (text.length > 0) {
274
- entries.push(rootBlobCandidate(
296
+ pushDeduped(
275
297
  { role: "assistant", content: [{ type: "text", text }] },
276
298
  "assistant",
277
299
  { messageIndex: i },
278
- ));
300
+ text,
301
+ );
279
302
  }
280
303
  // Assistant tool CALLS are intentionally NOT replayed as visible "[Tool Call]" text here.
281
304
  } else if (message.role === "toolResult") {
282
- // Native resume models already receive the paired MCP result through turns[]. External
283
- // replay uses neutral text here so models do not echo protocol envelopes as chat.
305
+ // Native resume models already receive the paired MCP result through turns[]. Replaying
306
+ // the same payload as assistant-role "[Tool Result]" / "[tool_result]" text teaches Auto
307
+ // to echo that envelope as chat instead of continuing from the structured result.
284
308
  if (!echoToolResultInRoot) continue;
285
309
  const text = externalToolResultToText(message);
286
- entries.push(rootBlobCandidate(
287
- toolResultRootPayload(text),
288
- "toolResult",
289
- { messageIndex: i, text },
290
- ));
310
+ pushDeduped(toolResultRootPayload(text), "toolResult", { messageIndex: i, text }, text);
291
311
  }
292
312
  }
313
+ // Severe repetition: tell the model ONCE, imperatively, to change strategy.
314
+ if (externalModel && maxRunLength >= 3) {
315
+ entries.push(rootBlobCandidate({
316
+ role: "user",
317
+ content: [{ type: "text", text: `[context note] The transcript above contains the same output repeated ${maxRunLength} times in a row. Repeating it again is a failure. Take a DIFFERENT action now, or state plainly what is blocking progress.` }],
318
+ }, "user", {}));
319
+ }
293
320
 
294
321
  let selected = entries;
295
322
  let historyMessageStart = 0;
@@ -465,6 +492,7 @@ function decodeResultParts(message: OcxToolResultMessage): DecodedResultPart[] |
465
492
  if (typeof content === "string") return undefined;
466
493
  return content.map((part): DecodedResultPart => {
467
494
  if (part.type === "text") return { kind: "text", text: part.text };
495
+ if (part.type === "video") return { kind: "text", text: "[video]" };
468
496
  const decoded = decodeInlineImage(part.imageUrl);
469
497
  return decoded ? { kind: "image", ...decoded } : { kind: "undecodable" };
470
498
  });
@@ -559,7 +587,7 @@ function toolResultToText(message: OcxToolResultMessage): string {
559
587
  const normalized = normalizedToolResult(message, contentToText(message.content));
560
588
  return [
561
589
  "[tool_result]",
562
- `call_id: ${message.toolCallId}`,
590
+ `call_id: ${decodeCursorCallId(message.toolCallId)}`,
563
591
  `name: ${namespacedToolName(message.toolNamespace, message.toolName)}`,
564
592
  `is_error: ${normalized.isError}`,
565
593
  "output:",
@@ -570,7 +598,7 @@ function toolResultToText(message: OcxToolResultMessage): string {
570
598
  function externalToolResultToText(message: OcxToolResultMessage): string {
571
599
  const normalized = normalizedToolResult(message, contentToText(message.content));
572
600
  const label = normalized.isError ? "Tool error" : "Tool output";
573
- return `${label} for ${namespacedToolName(message.toolNamespace, message.toolName)} (call_id: ${message.toolCallId}, is_error: ${normalized.isError}):\n${normalized.text}`;
601
+ return `${label} for ${namespacedToolName(message.toolNamespace, message.toolName)} (call_id: ${decodeCursorCallId(message.toolCallId)}, is_error: ${normalized.isError}):\n${normalized.text}`;
574
602
  }
575
603
 
576
604
  /**
@@ -626,7 +654,7 @@ function toolCallStep(
626
654
  args: create(McpArgsSchema, {
627
655
  name: toolName,
628
656
  toolName,
629
- toolCallId: part.id,
657
+ toolCallId: decodeCursorCallId(part.id),
630
658
  providerIdentifier: OCX_RESPONSES_TOOL_PROVIDER,
631
659
  args,
632
660
  }),
@@ -870,7 +898,7 @@ function buildPreparedCursorRunRequest(
870
898
  ? "userMessageAction"
871
899
  : "resumeAction";
872
900
  const actionText = externalToolContinuation
873
- ? (request.echoRetryContinuationText ?? externalToolContinuationText(request.rawMessages))
901
+ ? (request.echoRetryContinuationText ?? CURSOR_EXTERNAL_TOOL_CONTINUATION_TEXT)
874
902
  : request.echoRetryContinuationText
875
903
  ? `${text}\n\n[correction] ${request.echoRetryContinuationText}`
876
904
  : text;
@@ -1000,12 +1028,15 @@ function buildPreparedCursorRunRequest(
1000
1028
  displayName: request.modelId,
1001
1029
  displayNameShort: request.modelId,
1002
1030
  aliases: [],
1031
+ ...(request.maxMode === true ? { maxMode: true } : {}),
1003
1032
  }),
1004
1033
  } : {}),
1005
- ...(requestedModelParameters.length > 0 ? {
1034
+ ...(requestedModelParameters.length > 0 || request.maxMode === true ? {
1006
1035
  requestedModel: create(RequestedModelSchema, {
1007
1036
  modelId: request.modelId,
1008
- maxMode: false,
1037
+ // Max Mode must be raised on BOTH RequestedModel and ModelDetails; missing either
1038
+ // can invalid_argument upstream (devlog 260826 070).
1039
+ maxMode: request.maxMode === true,
1009
1040
  parameters: requestedModelParameters.map(parameter =>
1010
1041
  create(RequestedModel_ModelParameterbytesSchema, parameter)),
1011
1042
  }),
@@ -1023,7 +1054,11 @@ function buildPreparedCursorRunRequest(
1023
1054
  // the event-state `clientToolNames` use (live-transport.ts). Advertising the raw `request.tools`
1024
1055
  // here would let mcp_tools expose a tool that the event state does not recognize for a generic
1025
1056
  // tool-count prompt, so a call to it would be rejected as an unknown Responses tool.
1026
- ...(mcpToolDefs.length > 0 ? { mcpTools: create(McpToolsSchema, { mcpTools: mcpToolDefs }) } : {}),
1057
+ // An explicitly empty McpTools wrapper (bare API callers) suppresses Cursor's default
1058
+ // native catalog; an absent field lets identified Codex sessions keep it (devlog 260826 040).
1059
+ ...(mcpToolDefs.length > 0 || request.suppressDefaultCursorToolCatalog === true
1060
+ ? { mcpTools: create(McpToolsSchema, { mcpTools: mcpToolDefs }) }
1061
+ : {}),
1027
1062
  });
1028
1063
 
1029
1064
  const message = create(AgentClientMessageSchema, {
@@ -10,6 +10,8 @@ import type {
10
10
  import { isAllowedToolChoice, namespacedToolName, toolChoiceAliases, type OcxTool, type OcxToolChoice } from "../../types";
11
11
  import type { CursorRequestMessage, CursorRequestedModelParameter, CursorRunRequest } from "./types";
12
12
  import { cursorCheckpointModelAffinityId, cursorWireModelSelection, type CursorRoutingLevel } from "./discovery";
13
+ import { cursorUltraBaseModelId } from "./discovery";
14
+ import { decodeCursorCallId } from "./call-id";
13
15
  import { cursorEffortSuffix, cursorRequestWireModelIdWithEffort } from "./effort-map";
14
16
  import {
15
17
  cursorMcpToolEncodedSize,
@@ -188,13 +190,19 @@ function normalizeCursorModelId(modelId: string, reasoning?: string): {
188
190
  modelId: string;
189
191
  requestedModelParameters?: readonly CursorRequestedModelParameter[];
190
192
  routingLevel?: CursorRoutingLevel;
193
+ maxMode?: boolean;
191
194
  } {
192
- const selection = cursorWireModelSelection(modelId);
195
+ // Synthetic ultra (-1m) picker rows resolve to their wire base with Max Mode on
196
+ // (devlog 260826 070); the marker never reaches the wire.
197
+ const ultraBase = cursorUltraBaseModelId(modelId);
198
+ const selection = cursorWireModelSelection(ultraBase ?? modelId);
199
+ const maxMode = ultraBase !== undefined ? { maxMode: true } : {};
193
200
  const id = selection.modelId;
194
201
  const suffix = cursorEffortSuffix(id, reasoning);
195
202
  if ((id === "grok-4.5-fast" || id === "grok-4.6-fast") && suffix) {
196
203
  return {
197
204
  ...selection,
205
+ ...maxMode,
198
206
  modelId: id.slice(0, -"-fast".length),
199
207
  requestedModelParameters: [
200
208
  { id: "effort", value: suffix },
@@ -209,7 +217,7 @@ function normalizeCursorModelId(modelId: string, reasoning?: string): {
209
217
  requestedModelParameters: [{ id: "fast", value: "false" }],
210
218
  };
211
219
  }
212
- return { ...selection, modelId: suffix ? cursorRequestWireModelIdWithEffort(id, suffix) : id };
220
+ return { ...selection, ...maxMode, modelId: suffix ? cursorRequestWireModelIdWithEffort(id, suffix) : id };
213
221
  }
214
222
 
215
223
  function contentPartToText(part: OcxContentPart | OcxAssistantContentPart): string | undefined {
@@ -233,7 +241,7 @@ function contentPartToText(part: OcxContentPart | OcxAssistantContentPart): stri
233
241
  function toolResultToText(message: OcxToolResultMessage): string {
234
242
  return [
235
243
  "[tool_result]",
236
- `call_id: ${message.toolCallId}`,
244
+ `call_id: ${decodeCursorCallId(message.toolCallId)}`,
237
245
  `name: ${namespacedToolName(message.toolNamespace, message.toolName)}`,
238
246
  `is_error: ${message.isError}`,
239
247
  "output:",
@@ -305,7 +313,7 @@ export function cursorConversationIdFromClientThread(threadId: string, identityS
305
313
 
306
314
  /**
307
315
  * Resolve the Cursor conversation id for this turn.
308
- * Priority: force-fresh → isolate helper → remembered → thread override → client thread → random.
316
+ * Priority: force-fresh → isolate helper → remembered → client thread owner → random.
309
317
  * Never use OpenAI Responses `previous_response_id` (resp_*) or shared `prompt_cache_key`
310
318
  * (cache-cohort fingerprint, not conversation ownership).
311
319
  */
@@ -317,7 +325,7 @@ export function resolveCursorConversationId(
317
325
  if (options.forceFreshConversation === true) return generatedCursorConversationId();
318
326
  if (parsed._cursorIsolateConversation === true) return generatedCursorConversationId();
319
327
  if (parsed._cursorConversationId) return parsed._cursorConversationId;
320
- const threadId = parsed._clientThreadId?.trim();
328
+ const threadId = cursorClientThreadOwner(parsed);
321
329
  if (threadId) {
322
330
  const recovered = lookupCursorThreadConversation(threadId, parsed._cursorIdentityScope);
323
331
  if (recovered) return recovered;
@@ -326,6 +334,10 @@ export function resolveCursorConversationId(
326
334
  return generatedCursorConversationId();
327
335
  }
328
336
 
337
+ export function cursorClientThreadOwner(parsed: OcxParsedRequest): string | undefined {
338
+ return parsed._clientThreadId?.trim() || parsed._cursorClientThreadId?.trim() || undefined;
339
+ }
340
+
329
341
  function updateFramed(hash: ReturnType<typeof createHash>, value: string): void {
330
342
  const bytes = Buffer.from(value, "utf8");
331
343
  const length = Buffer.allocUnsafe(4);
@@ -368,6 +380,7 @@ function lookupPrefixSnapshot(
368
380
  const modelId = cursorCheckpointModelAffinityId(request.modelId);
369
381
  for (let covered = parsed.context.messages.length; covered >= 1; covered--) {
370
382
  const snapshot = getCursorCheckpointForPrefix({
383
+ conversationId: request.conversationId,
371
384
  prefixDigest: cursorCoveredPrefixDigest(parsed, covered),
372
385
  systemDigest,
373
386
  coveredMessageCount: covered,
@@ -411,10 +424,14 @@ function resolveCursorCheckpoint(
411
424
  snapshot = getCursorCheckpoint(ref);
412
425
  if (!snapshot) return { reason: "expired" };
413
426
  } else {
427
+ if (
428
+ isolated
429
+ || (!parsed._cursorConversationId && !cursorClientThreadOwner(parsed))
430
+ ) return { reason: "missing_ref" };
414
431
  snapshot = lookupPrefixSnapshot(parsed, request, identityScope);
415
432
  if (!snapshot) return { reason: "missing_ref" };
416
433
  }
417
- if (!isolated && snapshot.conversationId !== request.conversationId && ref) {
434
+ if (snapshot.conversationId !== request.conversationId) {
418
435
  return { reason: "conversation_changed" };
419
436
  }
420
437
  if (snapshot.identityScope !== identityScope) return { reason: "identity_changed" };
@@ -443,16 +460,21 @@ export function createCursorRequest(
443
460
  modelId: model.modelId,
444
461
  ...(model.requestedModelParameters ? { requestedModelParameters: model.requestedModelParameters } : {}),
445
462
  ...(model.routingLevel ? { routingLevel: model.routingLevel } : {}),
463
+ ...(model.maxMode ? { maxMode: true } : {}),
446
464
  conversationId: resolveCursorConversationId(parsed, model.modelId, options),
447
465
  system: [...(parsed.context.systemPrompt ?? []), ...(limitNote ? [limitNote] : [])],
448
466
  messages,
449
467
  rawMessages: parsed.context.messages,
450
468
  ...(parsed._compactionRequest === true || parsed._contextCompactionBoundary === true ? { contextUsageReset: true } : {}),
451
469
  ...(parsed._compactionRequest === true ? { contextUsageStoreCheckpoints: false } : {}),
452
- ...(budget.tools.length ? { tools: budget.tools } : {}),
453
- ...(parsed.options.toolChoice ? { toolChoice: parsed.options.toolChoice } : {}),
454
- ...(parsed.options.textFormat ? { textFormat: parsed.options.textFormat } : {}),
455
- ...(parsed.options.parallelToolCalls !== undefined ? { parallelToolCalls: parsed.options.parallelToolCalls } : {}),
470
+ ...(budget.tools.length ? { tools: budget.tools } : {}),
471
+ // Bare API caller (no tools, no Codex thread identity): suppress Cursor's default
472
+ // native tool catalog instead of paying its ~10-15K token preamble (devlog 260826 040).
473
+ ...(budget.tools.length === 0 && !cursorClientThreadOwner(parsed)
474
+ ? { suppressDefaultCursorToolCatalog: true }
475
+ : {}),
476
+ ...(parsed.options.toolChoice ? { toolChoice: parsed.options.toolChoice } : {}),
477
+ ...(parsed.options.parallelToolCalls !== undefined ? { parallelToolCalls: parsed.options.parallelToolCalls } : {}),
456
478
  };
457
479
  const resolved = resolveCursorCheckpoint(parsed, request, options);
458
480
  if ("reason" in resolved) {
@@ -71,6 +71,13 @@ export const CURSOR_EXEC_COMMAND_INPUT_SCHEMA = {
71
71
  additionalProperties: false,
72
72
  } as const;
73
73
 
74
+ /** Cursor requires freeform custom tools to advertise their body as one string input. */
75
+ export const CURSOR_FREEFORM_INPUT_SCHEMA = {
76
+ type: "object",
77
+ properties: { input: { type: "string" } },
78
+ required: ["input"],
79
+ } as const;
80
+
74
81
  /**
75
82
  * Structured single-replacement schema advertised to Cursor models in addition to the freeform
76
83
  * `apply_patch` tool. Cursor-trained models reliably emit exact-match replacements (the native
@@ -112,13 +119,6 @@ export const CURSOR_MULTI_EDIT_INPUT_SCHEMA = {
112
119
  additionalProperties: false,
113
120
  } as const;
114
121
 
115
- /** Cursor requires freeform custom tools to advertise their body as one string input. */
116
- export const CURSOR_FREEFORM_INPUT_SCHEMA = {
117
- type: "object",
118
- properties: { input: { type: "string" } },
119
- required: ["input"],
120
- } as const;
121
-
122
122
  /**
123
123
  * Responses/Codex-side schema used ONLY for arg-key normalization after Cursor returns a call.
124
124
  * Cursor models are trained to emit `cmd`; Codex `shell_command` / `exec_command` validate
@@ -682,13 +682,16 @@ export function buildCursorToolGuidanceSystemNote(
682
682
  // Code mode: shell/edit/MCP live inside freeform `exec` as nested helpers. Without this the
683
683
  // model probes for a top-level shell tool that is not there.
684
684
  codeMode
685
- ? `\`${CODEX_UNIFIED_EXEC_TOOL}\` is Codex code mode: its body is JavaScript evaluated in a V8 isolate, not a shell command and not Node. Shell, file edits, and MCP are nested helpers called INSIDE that body as \`await tools.<name>(...)\`, for example \`await tools.exec_command({cmd: \"ls\"})\`. When commands require network access, file writes outside workspace, or fail due to sandbox/permission restrictions, pass \`sandbox_permissions: "require_escalated"\` and a clear \`justification: "..."\` to \`tools.exec_command\`. Read the tool description and the isolate global \`ALL_TOOLS\` (not \`tools.ALL_TOOLS\`) for helpers this turn provides; absence from the top-level catalog or from \`exec\`'s description is not absence. Those nested helpers are not themselves top-level tools, so do not call \`exec_command\` or \`shell_command\` at the top level here${codeModeOtherTopLevelNames.length > 0 ? `; every other tool this turn lists, including ${quotedNames(codeModeOtherTopLevelNames)}, remains callable at the top level as usual` : ""}. Your tool list may display tools under a longer \`mcp_opencodex-responses_*\` name; call whichever your list shows. Nested \`tools.apply_patch(input)\` is host-executed: the string must begin exactly with \`*** Begin Patch\` and end with \`*** End Patch\` (no trailing \`***\` on those lines). OpenCodex does not rewrite JavaScript inside exec, so a decorated \`*** Begin Patch ***\` envelope is rejected by Codex before the file is touched.`
685
+ ? `\`${CODEX_UNIFIED_EXEC_TOOL}\` is Codex code mode: its body is JavaScript evaluated in a V8 isolate, not a shell command and not Node. Shell, file edits, and MCP are nested helpers called INSIDE that body as \`await tools.<name>(...)\`, for example \`await tools.exec_command({cmd: \"ls\"})\`. Read the tool description and the isolate global \`ALL_TOOLS\` (not \`tools.ALL_TOOLS\`) for helpers this turn provides; absence from the top-level catalog or from \`exec\`'s description is not absence. Those nested helpers are not themselves top-level tools, so do not call \`exec_command\` or \`shell_command\` at the top level here${codeModeOtherTopLevelNames.length > 0 ? `; every other tool this turn lists, including ${quotedNames(codeModeOtherTopLevelNames)}, remains callable at the top level as usual` : ""}. Nested \`tools.apply_patch(input)\` is host-executed: the string must begin exactly with \`*** Begin Patch\` and end with \`*** End Patch\` (no trailing \`***\` on those lines). OpenCodex does not rewrite JavaScript inside exec, so a decorated \`*** Begin Patch ***\` envelope is rejected by Codex before the file is touched.`
686
686
  : undefined,
687
687
  codeMode
688
688
  ? "In code mode the isolate returns nothing on its own: call `text(...)` (or `notify(...)`) on any value you need to see, or the call completes with empty output. There is no `require`, no `module`, and no filesystem or network globals; reach the host only through the nested helpers."
689
689
  : undefined,
690
690
  codeMode
691
- ? "To read, search, or inspect files or run CLI commands, call `await tools.exec_command({ cmd: '...' })` inside `exec`. Do not write Node.js scripts or `require('fs')`."
691
+ ? 'When commands require network access, file writes outside workspace, or fail due to sandbox/permission restrictions, pass `sandbox_permissions: "require_escalated"` and a clear `justification: "..."` to `tools.exec_command`.'
692
+ : undefined,
693
+ codeMode
694
+ ? "Nested helpers may appear under longer `mcp_opencodex-responses_*` display names; use the exact helper names exposed by this turn."
692
695
  : undefined,
693
696
  codeMode
694
697
  ? "NEVER attempt Cursor-native Shell, Read, Grep, List, or any tool absent from the catalog — they are not executed in this environment and every probe wastes a turn. The exec code cell (with its nested helpers) is the ONLY execution surface; go to it directly on the FIRST attempt and do not narrate switching surfaces."
@@ -734,7 +737,7 @@ export function buildCursorToolGuidanceSystemNote(
734
737
  "Do not count or report a tool call unless a tool result was actually returned.",
735
738
  "When pursuing a multi-step task, check, or verification, do not stop or narrate intended future actions in plain text; immediately call the tool to execute the next step until the task is complete.",
736
739
  hasBareExec
737
- ? `If a Cursor-native file read, directory listing, grep, or shell operation is rejected by the runtime, use ${shellBridgeLabel} with an equivalent host-shell-safe command (POSIX: \`cat\`/\`ls\`/\`rg\`; Windows PowerShell: \`Get-Content\`/\`Get-ChildItem\`/\`Select-String\`). For file edits, use ${structuredEditNames.length > 0 ? `the structured edit tools (${quotedNames(structuredEditNames)}) or ` : ""}\`apply_patch\` when available.`
740
+ ? `For every file read, directory listing, grep, or shell operation use ${shellBridgeLabel} directly with host-shell-safe commands (POSIX: \`cat\`/\`ls\`/\`rg\`; Windows PowerShell: \`Get-Content\`/\`Get-ChildItem\`/\`Select-String\`). For file edits, use ${structuredEditNames.length > 0 ? `the structured edit tools (${quotedNames(structuredEditNames)}) or ` : ""}\`apply_patch\` when available.`
738
741
  : undefined,
739
742
  ].filter((note): note is string => typeof note === "string");
740
743
  return notes.join(" ");