@iislee/opencodex 2.31.0 → 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.
- package/README.md +3 -3
- package/bin/ocx.mjs +99 -70
- package/gui/dist/assets/index-Be1sDpWw.css +1 -0
- package/gui/dist/assets/index-D4OKgUV4.js +114 -0
- package/gui/dist/index.html +2 -2
- package/package.json +3 -1
- package/src/adapters/anthropic.ts +104 -15
- package/src/adapters/base.ts +29 -1
- package/src/adapters/command-code.ts +42 -6
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/catalog.ts +541 -0
- package/src/adapters/cursor/checkpoint-store.ts +15 -10
- package/src/adapters/cursor/cursor-errors.ts +15 -0
- package/src/adapters/cursor/discovery.ts +69 -18
- package/src/adapters/cursor/effort-map.ts +79 -1
- package/src/adapters/cursor/envelope-echo.ts +290 -0
- package/src/adapters/cursor/live-models.ts +7 -2
- package/src/adapters/cursor/live-transport.ts +14 -0
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- package/src/adapters/cursor/native-exec-fs.ts +2 -2
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +5 -2
- package/src/adapters/cursor/native-exec.ts +20 -0
- package/src/adapters/cursor/protobuf-request.ts +68 -14
- package/src/adapters/cursor/request-builder.ts +40 -10
- package/src/adapters/cursor/tool-definitions.ts +14 -4
- package/src/adapters/cursor/tool-result-normalize.ts +28 -5
- package/src/adapters/cursor/types.ts +20 -0
- package/src/adapters/cursor.ts +209 -38
- package/src/adapters/exec-tool-result-normalize.ts +99 -0
- package/src/adapters/google-antigravity-replay.ts +173 -25
- package/src/adapters/google-antigravity-wire.ts +5 -0
- package/src/adapters/google-errors.ts +44 -12
- package/src/adapters/google.ts +136 -24
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +27 -0
- package/src/adapters/kiro-tools.ts +55 -16
- package/src/adapters/kiro.ts +182 -23
- package/src/adapters/openai-chat.ts +300 -242
- package/src/adapters/openai-responses.ts +353 -27
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-catalog-nudge.ts +4 -3
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/adapters/xai-web-search.ts +17 -16
- package/src/bridge.ts +90 -37
- package/src/chat/inbound.ts +14 -1
- package/src/chat/outbound.ts +15 -7
- package/src/claude/agents-inject.ts +8 -1
- package/src/claude/context-windows.ts +16 -9
- package/src/claude/outbound.ts +24 -11
- package/src/cli/access.ts +46 -3
- package/src/cli/account-api.ts +116 -19
- package/src/cli/account-extended.ts +262 -29
- package/src/cli/account-main.ts +12 -12
- package/src/cli/account.ts +69 -15
- package/src/cli/agent.ts +8 -1
- package/src/cli/alias.ts +66 -0
- package/src/cli/capabilities-command.ts +94 -0
- package/src/cli/capabilities.ts +496 -0
- package/src/cli/claude-desktop.ts +31 -11
- package/src/cli/claude.ts +26 -1
- package/src/cli/dispatch.ts +256 -28
- package/src/cli/doctor.ts +126 -14
- package/src/cli/help.ts +17 -6
- package/src/cli/index.ts +39 -9
- package/src/cli/init.ts +1 -0
- package/src/cli/inspect.ts +230 -0
- package/src/cli/models-runtime.ts +95 -0
- package/src/cli/models.ts +26 -10
- package/src/cli/observe.ts +31 -8
- package/src/cli/provider-runtime.ts +16 -2
- package/src/cli/provider.ts +2 -1
- package/src/cli/registry.ts +49 -10
- package/src/cli/runtime-api.ts +51 -7
- package/src/cli/status.ts +41 -1
- package/src/cli/storage.ts +234 -0
- package/src/cli/system-command.ts +16 -0
- package/src/cli/system-restart-client.ts +1 -1
- package/src/cli/usage-report.ts +184 -0
- package/src/cli/v2.ts +34 -10
- package/src/cli/version-skew.ts +46 -0
- package/src/clients/config-export.ts +85 -3
- package/src/clients/effective-status.ts +30 -2
- package/src/clients/probes/cline.ts +81 -0
- package/src/codex/account-label.ts +21 -0
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +3 -2
- package/src/codex/app-server-processes.ts +83 -7
- package/src/codex/auth-api.ts +50 -10
- package/src/codex/auth-context.ts +21 -18
- package/src/codex/autostart-health.ts +16 -0
- package/src/codex/catalog/aggregation.ts +18 -0
- package/src/codex/catalog/effort.ts +18 -3
- package/src/codex/catalog/metadata.ts +27 -1
- package/src/codex/catalog/native-models.ts +5 -2
- package/src/codex/catalog/parsing.ts +72 -27
- package/src/codex/catalog/provider-fetch.ts +187 -31
- package/src/codex/catalog/sync.ts +131 -5
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-write-lock.ts +3 -1
- package/src/codex/convergence-types.ts +1 -1
- package/src/codex/convergence.ts +27 -2
- package/src/codex/desired-state.ts +2 -2
- package/src/codex/desktop-app-restart.ts +355 -0
- package/src/codex/history-job.ts +32 -3
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +5 -5
- package/src/codex/history-provider.ts +825 -247
- package/src/codex/history-worker.ts +8 -5
- package/src/codex/inject-coordination.ts +83 -0
- package/src/codex/inject.ts +63 -22
- package/src/codex/injected-marker.ts +1 -1
- package/src/codex/internal/history-writer.ts +4 -3
- package/src/codex/log-guard/inspect.ts +22 -4
- package/src/codex/model-entitlements.ts +9 -2
- package/src/codex/native-profile-startup.ts +157 -27
- package/src/codex/native-residue.ts +26 -33
- package/src/codex/prompt-layers.ts +371 -25
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota.ts +91 -10
- package/src/codex/routing.ts +9 -0
- package/src/codex/shim.ts +56 -3
- package/src/codex/subagent-model-fallback.ts +104 -25
- package/src/codex/transition-state.ts +116 -8
- package/src/combos/failover.ts +27 -0
- package/src/combos/types.ts +10 -0
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +121 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +309 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +206 -824
- package/src/generated/compatibility-version.json +373 -213
- package/src/images/loop.ts +5 -4
- package/src/images/plan.ts +5 -4
- package/src/integrations/ownership-policy.ts +160 -0
- package/src/integrations/ownership.ts +46 -2
- package/src/integrations/registry.ts +7 -0
- package/src/integrations/state.ts +79 -7
- package/src/integrations/writer.ts +26 -2
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
- package/src/lab/fabric/producer-child.ts +1 -1
- package/src/lib/admin-secrets.ts +24 -0
- package/src/lib/bounded-body.ts +14 -2
- package/src/lib/config-ownership.ts +20 -0
- package/src/lib/errors.ts +36 -3
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/lib/process-control.ts +2 -1
- package/src/lib/service-secrets.ts +15 -0
- package/src/lib/tool-argument-integers.ts +56 -5
- package/src/oauth/callback-server.ts +22 -2
- package/src/oauth/cline.ts +203 -0
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/health.ts +1 -1
- package/src/oauth/index.ts +65 -6
- package/src/oauth/key-providers.ts +10 -1
- package/src/oauth/kimi.ts +9 -1
- package/src/oauth/kiro.ts +45 -0
- package/src/oauth/local-token-detect.ts +2 -0
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +14 -5
- package/src/oauth/types.ts +15 -0
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/command-code-efforts.ts +38 -6
- package/src/providers/context-cap.ts +4 -3
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +28 -0
- package/src/providers/fastwire.ts +7 -1
- package/src/providers/label.ts +34 -1
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/new-model-policy.ts +146 -0
- package/src/providers/provider-id-rewrite.ts +2 -1
- package/src/providers/quota.ts +65 -7
- package/src/providers/registry.ts +184 -69
- package/src/providers/slug-codec.ts +52 -0
- package/src/providers/xai-transport.ts +21 -0
- package/src/reasoning-effort.ts +19 -2
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/custom-tool-compat.ts +157 -39
- package/src/responses/namespace-tool-compat.ts +84 -4
- package/src/responses/parser.ts +7 -2
- package/src/responses/reasoning-replay-cache.ts +81 -3
- package/src/responses/schema.ts +5 -1
- package/src/responses/state.ts +94 -4
- package/src/responses/thought-signature-replay.ts +17 -0
- package/src/responses/turn-termination.ts +107 -0
- package/src/router.ts +40 -2
- package/src/routing/analytics.ts +1 -0
- package/src/routing/quota.ts +10 -0
- package/src/server/auth-cors.ts +27 -7
- package/src/server/chat-completions.ts +24 -16
- package/src/server/chat-native-sse.ts +3 -3
- package/src/server/chat-native.ts +30 -11
- package/src/server/claude-messages.ts +1 -1
- package/src/server/index.ts +179 -32
- package/src/server/lifecycle.ts +52 -1
- package/src/server/local-management-read-client.ts +1 -1
- package/src/server/local-provider-reload-client.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +45 -23
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +2 -1
- package/src/server/management/config-routes.ts +59 -12
- package/src/server/management/context.ts +10 -1
- package/src/server/management/logs-usage-routes.ts +30 -24
- package/src/server/management/model-routes.ts +274 -4
- package/src/server/management/model-rows.ts +4 -0
- package/src/server/management/native-integration-routes.ts +2 -1
- package/src/server/management/oauth-account-routes.ts +10 -4
- package/src/server/management/provider-capability-config.ts +1 -1
- package/src/server/management/provider-routes.ts +81 -11
- package/src/server/management/route-registry.ts +332 -0
- package/src/server/management/routing-profile-routes.ts +2 -2
- package/src/server/management/system-restart.ts +1 -1
- package/src/server/management-api.ts +18 -16
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/proxy-liveness.ts +29 -5
- package/src/server/relay-eager.ts +147 -20
- package/src/server/relay.ts +251 -19
- package/src/server/request-log-conversation.ts +51 -0
- package/src/server/request-log.ts +70 -16
- package/src/server/responses/codex-auth-error.ts +55 -0
- package/src/server/responses/collaboration.ts +37 -3
- package/src/server/responses/combo-stream-preflight.ts +178 -0
- package/src/server/responses/compact.ts +6 -21
- package/src/server/responses/core.ts +810 -208
- package/src/server/responses/empty-completion-guard.ts +63 -6
- package/src/server/responses/fetch-helpers.ts +17 -102
- package/src/server/responses/input-admission.ts +3 -1
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/responses-field-backfill.ts +105 -13
- package/src/server/responses/ws-upstream.ts +75 -2
- package/src/server/responses-custom-tool-repair.ts +87 -6
- package/src/server/responses-terminal-repair.ts +25 -4
- package/src/server/responses-undeclared-tool-guard.ts +241 -18
- package/src/server/sse-frame-buffer.ts +31 -4
- package/src/server/ws-bridge.ts +14 -2
- package/src/service.ts +42 -4
- package/src/storage/policy-job.ts +14 -4
- package/src/storage/policy.ts +88 -23
- package/src/types/config.ts +66 -0
- package/src/types/provider.ts +69 -0
- package/src/types/request.ts +17 -4
- package/src/types/tools.ts +116 -11
- package/src/types.ts +3 -1
- package/src/update/index.ts +5 -4
- package/src/update/job.ts +3 -1
- package/src/update/transactional-install.mjs +8 -1
- package/src/usage/expected-prices.ts +43 -24
- package/src/usage/log.ts +63 -12
- package/src/usage/summary.ts +211 -8
- package/src/vision/describe.ts +18 -13
- package/src/web-search/exa-executor.ts +40 -9
- package/src/web-search/executor.ts +10 -3
- package/src/web-search/loop.ts +5 -4
- package/gui/dist/assets/index-BF38heuV.js +0 -104
- package/gui/dist/assets/index-DMiI18Kv.css +0 -1
|
@@ -43,11 +43,11 @@ function codexNativeMutationRefusal(operation: "write" | "delete", structuredEdi
|
|
|
43
43
|
const structuredHint = structuredEditAvailable
|
|
44
44
|
? " Use the structured edit tools (`edit_file` / `multi_edit`) or the `apply_patch` tool for file edits so Codex can approve the change, enforce sandbox policy, show diffs, and record rollout."
|
|
45
45
|
: " Use the `apply_patch` tool for file edits so Codex can approve the change, enforce sandbox policy, show diffs, and record rollout.";
|
|
46
|
-
return `
|
|
46
|
+
return `Make this ${operation} through the Codex edit path instead.${structuredHint} No file was changed. Do NOT narrate this redirect or comment on tool availability — just make the edit call.`;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
const NATIVE_LOCAL_EXEC_DISABLED =
|
|
50
|
-
"
|
|
50
|
+
"Re-issue this operation NOW through the catalog shell tool (`shell_command` / `exec_command`, or the listed `mcp_opencodex-responses_*` display alias) with the host-shell-safe equivalent: POSIX (`cat`, `head`, `ls`, `rg`, `grep`) or Windows PowerShell (`Get-Content`, `Get-ChildItem`, `Select-String`); use `apply_patch` for file edits. Do NOT narrate this redirect, do NOT comment on tool availability, and do NOT re-announce the task — just make the bridge call.";
|
|
51
51
|
|
|
52
52
|
export function rejectReadExecForPolicy(execMsg: ExecServerMessage): Uint8Array {
|
|
53
53
|
if (execMsg.message.case !== "readArgs") throw new Error("invalid read exec");
|
|
@@ -7,7 +7,7 @@ export interface CursorNativeNetworkDeps {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
const NATIVE_FETCH_DISABLED =
|
|
10
|
-
"
|
|
10
|
+
"Re-issue this fetch NOW through the catalog shell tool `shell_command` (aliases: `exec_command`, `mcp_opencodex-responses_shell_command`, `mcp_opencodex-responses_exec_command`) with curl or wget. Do NOT narrate this redirect or comment on tool availability — just make the bridge call.";
|
|
11
11
|
|
|
12
12
|
export function rejectFetchExecForPolicy(execMsg: ExecServerMessage): Uint8Array {
|
|
13
13
|
if (execMsg.message.case !== "fetchArgs") throw new Error("invalid fetch exec");
|
|
@@ -87,11 +87,14 @@ export function nativeShellDisabledMessage(): string {
|
|
|
87
87
|
// idioms through the Codex bridge on Windows PowerShell 5.1 and looping (#604).
|
|
88
88
|
// Keep this host-shell-neutral: OpenCodex may run on a different OS than the Codex
|
|
89
89
|
// client that executes the bridge (LAN/SSH remote-proxy).
|
|
90
|
+
// Silent-redirect framing (devlog 260826 gap-8): no "blocked/denied/disabled" words —
|
|
91
|
+
// models narrate those as a surface switch ("Shell이 차단되어 전환합니다") and burn turns.
|
|
90
92
|
return (
|
|
91
|
-
"
|
|
93
|
+
"Re-issue this command NOW through the catalog shell tool (`shell_command` or `exec_command`; the long `mcp_opencodex-responses_*` display name is the same tool). "
|
|
92
94
|
+ "Adapt the command for the Codex client host shell before calling the bridge "
|
|
93
95
|
+ "(Windows PowerShell 5.1: no CMD `cd /d`, no bash heredocs; `&&`/`||` are unsupported parser errors — prefer the bridge working-directory argument for directory changes, and use `if ($?) { ... }` for success-gated follow-up steps; do not treat `;` as a substitute for `&&`). "
|
|
94
|
-
+ "Make at most one corrected bridge attempt after a failure, then report the error and stop — do not repeat equivalent failing commands."
|
|
96
|
+
+ "Make at most one corrected bridge attempt after a failure, then report the error and stop — do not repeat equivalent failing commands. "
|
|
97
|
+
+ "Do NOT narrate this redirect, do NOT comment on tool availability, and do NOT re-announce the task — just make the bridge call."
|
|
95
98
|
);
|
|
96
99
|
}
|
|
97
100
|
|
|
@@ -403,6 +403,19 @@ export function storeCursorBlob(data: Uint8Array, requestScope?: CursorBlobReque
|
|
|
403
403
|
return blobId;
|
|
404
404
|
}
|
|
405
405
|
|
|
406
|
+
/**
|
|
407
|
+
* Serve-time integrity for content-addressed blobs (devlog 260826_cursor_responses_gap 080):
|
|
408
|
+
* a raw 32-byte blob id IS the SHA-256 of its bytes, so served data whose digest mismatches
|
|
409
|
+
* the id means in-store corruption — the splice signature behind garbled replayed tool
|
|
410
|
+
* results. Ids longer than 32 bytes (digested-key namespace) and server-minted ids are not
|
|
411
|
+
* content-addressed and always pass.
|
|
412
|
+
*/
|
|
413
|
+
export function cursorBlobServeIntegrityOk(blobId: Uint8Array, served: Uint8Array): boolean {
|
|
414
|
+
if (blobId.byteLength !== 32) return true;
|
|
415
|
+
const digest = createHash("sha256").update(served).digest();
|
|
416
|
+
return digest.equals(Buffer.from(blobId));
|
|
417
|
+
}
|
|
418
|
+
|
|
406
419
|
/**
|
|
407
420
|
* Long-lived pin for blobs referenced by an active Cursor conversation checkpoint.
|
|
408
421
|
* Unlike a request scope, this lease is not sealed and is not released by getBlob hydration.
|
|
@@ -622,6 +635,13 @@ export function handleCursorNativeKv(
|
|
|
622
635
|
if (kvMsg.message.case === "getBlobArgs") {
|
|
623
636
|
const blobKey = key(kvMsg.message.value.blobId);
|
|
624
637
|
const blobData = getBlob(blobKey);
|
|
638
|
+
// Splice-class corruption guard (devlog 260826 080): diagnostic only, never blocks serving.
|
|
639
|
+
if (blobData && !cursorBlobServeIntegrityOk(kvMsg.message.value.blobId, blobData)) {
|
|
640
|
+
debugProviderDiagnostic("cursor", "blob-integrity-mismatch", {
|
|
641
|
+
blobKey: blobKey.slice(0, 18),
|
|
642
|
+
servedBytes: blobData.byteLength,
|
|
643
|
+
});
|
|
644
|
+
}
|
|
625
645
|
if (blobData && requestScope && blobRequestScopes.get(requestScope)?.kind === "request") {
|
|
626
646
|
releaseHydratedBlob(blobKey, requestScope);
|
|
627
647
|
}
|
|
@@ -4,6 +4,7 @@ import { ValueSchema } from "@bufbuild/protobuf/wkt";
|
|
|
4
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";
|
|
@@ -217,6 +218,42 @@ function rootPromptMessages(request: CursorRunRequest, requestScope: CursorBlobR
|
|
|
217
218
|
const echoToolResultInRoot = cursorNeedsExternalToolContinuation(request.modelId);
|
|
218
219
|
const lastRawIsToolResult = messages.at(-1)?.role === "toolResult";
|
|
219
220
|
const activeUserIndex = lastRawIsToolResult ? -1 : lastActionIndex(messages);
|
|
221
|
+
// Repetition breaker (devlog 260826 gap-9): external full-replay flattens history to text,
|
|
222
|
+
// so N identical assistant/tool-result rounds replay as N identical lines and PRIME the model
|
|
223
|
+
// to emit the same line again (self-reinforcing loop: S2a 180x, identical-probe repetition).
|
|
224
|
+
// Collapse consecutive duplicates into one entry + a count marker, and count collapses so a
|
|
225
|
+
// strategy-change note can be appended when the pattern is severe.
|
|
226
|
+
let lastReplayText: string | undefined;
|
|
227
|
+
let lastReplayEntry: RootBlobCandidate | undefined;
|
|
228
|
+
let collapsedRepeats = 0;
|
|
229
|
+
let maxRunLength = 1;
|
|
230
|
+
let currentRun = 1;
|
|
231
|
+
const pushDeduped = (
|
|
232
|
+
payload: { role: string; content: [{ type: "text"; text: string }] },
|
|
233
|
+
role: RootBlobCandidate["role"],
|
|
234
|
+
opts: { messageIndex: number; text?: string },
|
|
235
|
+
normalized: string,
|
|
236
|
+
): void => {
|
|
237
|
+
if (externalModel && lastReplayText !== undefined && normalized === lastReplayText && lastReplayEntry) {
|
|
238
|
+
collapsedRepeats++;
|
|
239
|
+
currentRun++;
|
|
240
|
+
if (currentRun > maxRunLength) maxRunLength = currentRun;
|
|
241
|
+
const marked = `${normalized}\n[note: this exact output was produced ${currentRun} times in a row]`;
|
|
242
|
+
const replacement = rootBlobCandidate(
|
|
243
|
+
{ role: payload.role, content: [{ type: "text", text: marked }] },
|
|
244
|
+
role,
|
|
245
|
+
opts,
|
|
246
|
+
);
|
|
247
|
+
entries[entries.indexOf(lastReplayEntry)] = replacement;
|
|
248
|
+
lastReplayEntry = replacement;
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
currentRun = 1;
|
|
252
|
+
const entry = rootBlobCandidate(payload, role, opts);
|
|
253
|
+
entries.push(entry);
|
|
254
|
+
lastReplayText = normalized;
|
|
255
|
+
lastReplayEntry = entry;
|
|
256
|
+
};
|
|
220
257
|
|
|
221
258
|
for (let i = 0; i < messages.length; i++) {
|
|
222
259
|
if (i === activeUserIndex) break;
|
|
@@ -228,6 +265,9 @@ function rootPromptMessages(request: CursorRunRequest, requestScope: CursorBlobR
|
|
|
228
265
|
// A bare string survives blob hydration but external workers reject the completed replay
|
|
229
266
|
// before tokenization (`usedTokens: 0`, then invalid_argument).
|
|
230
267
|
if (text.length > 0) {
|
|
268
|
+
lastReplayText = undefined;
|
|
269
|
+
lastReplayEntry = undefined;
|
|
270
|
+
currentRun = 1;
|
|
231
271
|
entries.push(rootBlobCandidate({
|
|
232
272
|
role: "user",
|
|
233
273
|
content: [{ type: "text", text }],
|
|
@@ -238,11 +278,12 @@ function rootPromptMessages(request: CursorRunRequest, requestScope: CursorBlobR
|
|
|
238
278
|
// Native Composer state can preserve it through ThinkingMessage/history structures.
|
|
239
279
|
const text = assistantRootText(message, !externalModel).trim();
|
|
240
280
|
if (text.length > 0) {
|
|
241
|
-
|
|
281
|
+
pushDeduped(
|
|
242
282
|
{ role: "assistant", content: [{ type: "text", text }] },
|
|
243
283
|
"assistant",
|
|
244
284
|
{ messageIndex: i },
|
|
245
|
-
|
|
285
|
+
text,
|
|
286
|
+
);
|
|
246
287
|
}
|
|
247
288
|
// Assistant tool CALLS are intentionally NOT replayed as visible "[Tool Call]" text here.
|
|
248
289
|
} else if (message.role === "toolResult") {
|
|
@@ -254,13 +295,16 @@ function rootPromptMessages(request: CursorRunRequest, requestScope: CursorBlobR
|
|
|
254
295
|
// node_repl result is an error even when the runtime said isError=false).
|
|
255
296
|
const prefix = normalizedToolResult(message, contentToText(message.content)).isError ? "[Tool Error]" : "[Tool Result]";
|
|
256
297
|
const text = `${prefix}\n${toolResultToText(message)}`;
|
|
257
|
-
|
|
258
|
-
toolResultRootPayload(text),
|
|
259
|
-
"toolResult",
|
|
260
|
-
{ messageIndex: i, text },
|
|
261
|
-
));
|
|
298
|
+
pushDeduped(toolResultRootPayload(text), "toolResult", { messageIndex: i, text }, text);
|
|
262
299
|
}
|
|
263
300
|
}
|
|
301
|
+
// Severe repetition: tell the model ONCE, imperatively, to change strategy.
|
|
302
|
+
if (externalModel && maxRunLength >= 3) {
|
|
303
|
+
entries.push(rootBlobCandidate({
|
|
304
|
+
role: "user",
|
|
305
|
+
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.` }],
|
|
306
|
+
}, "user", {}));
|
|
307
|
+
}
|
|
264
308
|
|
|
265
309
|
let selected = entries;
|
|
266
310
|
let historyMessageStart = 0;
|
|
@@ -436,6 +480,7 @@ function decodeResultParts(message: OcxToolResultMessage): DecodedResultPart[] |
|
|
|
436
480
|
if (typeof content === "string") return undefined;
|
|
437
481
|
return content.map((part): DecodedResultPart => {
|
|
438
482
|
if (part.type === "text") return { kind: "text", text: part.text };
|
|
483
|
+
if (part.type === "video") return { kind: "text", text: "[video]" };
|
|
439
484
|
const decoded = decodeInlineImage(part.imageUrl);
|
|
440
485
|
return decoded ? { kind: "image", ...decoded } : { kind: "undecodable" };
|
|
441
486
|
});
|
|
@@ -530,7 +575,7 @@ function toolResultToText(message: OcxToolResultMessage): string {
|
|
|
530
575
|
const normalized = normalizedToolResult(message, contentToText(message.content));
|
|
531
576
|
return [
|
|
532
577
|
"[tool_result]",
|
|
533
|
-
`call_id: ${message.toolCallId}`,
|
|
578
|
+
`call_id: ${decodeCursorCallId(message.toolCallId)}`,
|
|
534
579
|
`name: ${namespacedToolName(message.toolNamespace, message.toolName)}`,
|
|
535
580
|
`is_error: ${normalized.isError}`,
|
|
536
581
|
"output:",
|
|
@@ -591,7 +636,7 @@ function toolCallStep(
|
|
|
591
636
|
args: create(McpArgsSchema, {
|
|
592
637
|
name: toolName,
|
|
593
638
|
toolName,
|
|
594
|
-
toolCallId: part.id,
|
|
639
|
+
toolCallId: decodeCursorCallId(part.id),
|
|
595
640
|
providerIdentifier: OCX_RESPONSES_TOOL_PROVIDER,
|
|
596
641
|
args,
|
|
597
642
|
}),
|
|
@@ -837,8 +882,10 @@ function buildPreparedCursorRunRequest(
|
|
|
837
882
|
? "userMessageAction"
|
|
838
883
|
: "resumeAction";
|
|
839
884
|
const actionText = externalToolContinuation
|
|
840
|
-
? CURSOR_EXTERNAL_TOOL_CONTINUATION_TEXT
|
|
841
|
-
:
|
|
885
|
+
? (request.echoRetryContinuationText ?? CURSOR_EXTERNAL_TOOL_CONTINUATION_TEXT)
|
|
886
|
+
: request.echoRetryContinuationText
|
|
887
|
+
? `${text}\n\n[correction] ${request.echoRetryContinuationText}`
|
|
888
|
+
: text;
|
|
842
889
|
const action = create(ConversationActionSchema, {
|
|
843
890
|
action: actionCase === "userMessageAction"
|
|
844
891
|
? {
|
|
@@ -965,12 +1012,15 @@ function buildPreparedCursorRunRequest(
|
|
|
965
1012
|
displayName: request.modelId,
|
|
966
1013
|
displayNameShort: request.modelId,
|
|
967
1014
|
aliases: [],
|
|
1015
|
+
...(request.maxMode === true ? { maxMode: true } : {}),
|
|
968
1016
|
}),
|
|
969
1017
|
} : {}),
|
|
970
|
-
...(requestedModelParameters.length > 0 ? {
|
|
1018
|
+
...(requestedModelParameters.length > 0 || request.maxMode === true ? {
|
|
971
1019
|
requestedModel: create(RequestedModelSchema, {
|
|
972
1020
|
modelId: request.modelId,
|
|
973
|
-
|
|
1021
|
+
// Max Mode must be raised on BOTH RequestedModel and ModelDetails; missing either
|
|
1022
|
+
// can invalid_argument upstream (devlog 260826 070).
|
|
1023
|
+
maxMode: request.maxMode === true,
|
|
974
1024
|
parameters: requestedModelParameters.map(parameter =>
|
|
975
1025
|
create(RequestedModel_ModelParameterbytesSchema, parameter)),
|
|
976
1026
|
}),
|
|
@@ -988,7 +1038,11 @@ function buildPreparedCursorRunRequest(
|
|
|
988
1038
|
// the event-state `clientToolNames` use (live-transport.ts). Advertising the raw `request.tools`
|
|
989
1039
|
// here would let mcp_tools expose a tool that the event state does not recognize for a generic
|
|
990
1040
|
// tool-count prompt, so a call to it would be rejected as an unknown Responses tool.
|
|
991
|
-
|
|
1041
|
+
// An explicitly empty McpTools wrapper (bare API callers) suppresses Cursor's default
|
|
1042
|
+
// native catalog; an absent field lets identified Codex sessions keep it (devlog 260826 040).
|
|
1043
|
+
...(mcpToolDefs.length > 0 || request.suppressDefaultCursorToolCatalog === true
|
|
1044
|
+
? { mcpTools: create(McpToolsSchema, { mcpTools: mcpToolDefs }) }
|
|
1045
|
+
: {}),
|
|
992
1046
|
});
|
|
993
1047
|
|
|
994
1048
|
const message = create(AgentClientMessageSchema, {
|
|
@@ -10,7 +10,9 @@ 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 {
|
|
13
|
+
import { cursorUltraBaseModelId } from "./discovery";
|
|
14
|
+
import { decodeCursorCallId } from "./call-id";
|
|
15
|
+
import { cursorGrokFastSelection, resolveCursorSelection } from "./catalog";
|
|
14
16
|
import {
|
|
15
17
|
cursorMcpToolEncodedSize,
|
|
16
18
|
cursorMcpToolsEncodedSize,
|
|
@@ -188,21 +190,34 @@ function normalizeCursorModelId(modelId: string, reasoning?: string): {
|
|
|
188
190
|
modelId: string;
|
|
189
191
|
requestedModelParameters?: readonly CursorRequestedModelParameter[];
|
|
190
192
|
routingLevel?: CursorRoutingLevel;
|
|
193
|
+
maxMode?: boolean;
|
|
191
194
|
} {
|
|
195
|
+
// Router ids (auto / auto-<level>) keep their dedicated wire selection.
|
|
192
196
|
const selection = cursorWireModelSelection(modelId);
|
|
197
|
+
if (selection.routingLevel !== undefined || selection.modelId === "default") return selection;
|
|
198
|
+
// Umbrella catalog resolution (devlog 260828_cursor_umbrella_catalog): one
|
|
199
|
+
// resolver owns effort composition, variant dimensions, the synthetic -1m
|
|
200
|
+
// marker (ultra -> Max Mode, evidence-gated), and the cursor- wire prefix.
|
|
193
201
|
const id = selection.modelId;
|
|
194
|
-
|
|
195
|
-
|
|
202
|
+
// Grok Fast stays parameterized: current Cursor clients send the base id
|
|
203
|
+
// plus effort/fast parameters instead of the flattened -fast id.
|
|
204
|
+
const grokFast = cursorGrokFastSelection(id, reasoning);
|
|
205
|
+
if (grokFast) {
|
|
196
206
|
return {
|
|
197
207
|
...selection,
|
|
198
|
-
modelId:
|
|
208
|
+
modelId: grokFast.wireBaseId,
|
|
199
209
|
requestedModelParameters: [
|
|
200
|
-
{ id: "effort", value:
|
|
210
|
+
{ id: "effort", value: grokFast.effort },
|
|
201
211
|
{ id: "fast", value: "true" },
|
|
202
212
|
],
|
|
203
213
|
};
|
|
204
214
|
}
|
|
205
|
-
|
|
215
|
+
const resolved = resolveCursorSelection(id, reasoning);
|
|
216
|
+
return {
|
|
217
|
+
...selection,
|
|
218
|
+
...(resolved.maxMode ? { maxMode: true } : {}),
|
|
219
|
+
modelId: resolved.wireId,
|
|
220
|
+
};
|
|
206
221
|
}
|
|
207
222
|
|
|
208
223
|
function contentPartToText(part: OcxContentPart | OcxAssistantContentPart): string | undefined {
|
|
@@ -226,7 +241,7 @@ function contentPartToText(part: OcxContentPart | OcxAssistantContentPart): stri
|
|
|
226
241
|
function toolResultToText(message: OcxToolResultMessage): string {
|
|
227
242
|
return [
|
|
228
243
|
"[tool_result]",
|
|
229
|
-
`call_id: ${message.toolCallId}`,
|
|
244
|
+
`call_id: ${decodeCursorCallId(message.toolCallId)}`,
|
|
230
245
|
`name: ${namespacedToolName(message.toolNamespace, message.toolName)}`,
|
|
231
246
|
`is_error: ${message.isError}`,
|
|
232
247
|
"output:",
|
|
@@ -298,7 +313,7 @@ export function cursorConversationIdFromClientThread(threadId: string, identityS
|
|
|
298
313
|
|
|
299
314
|
/**
|
|
300
315
|
* Resolve the Cursor conversation id for this turn.
|
|
301
|
-
* Priority: force-fresh → isolate helper → remembered →
|
|
316
|
+
* Priority: force-fresh → isolate helper → remembered → client thread owner → random.
|
|
302
317
|
* Never use OpenAI Responses `previous_response_id` (resp_*) or shared `prompt_cache_key`
|
|
303
318
|
* (cache-cohort fingerprint, not conversation ownership).
|
|
304
319
|
*/
|
|
@@ -310,7 +325,7 @@ export function resolveCursorConversationId(
|
|
|
310
325
|
if (options.forceFreshConversation === true) return generatedCursorConversationId();
|
|
311
326
|
if (parsed._cursorIsolateConversation === true) return generatedCursorConversationId();
|
|
312
327
|
if (parsed._cursorConversationId) return parsed._cursorConversationId;
|
|
313
|
-
const threadId = parsed
|
|
328
|
+
const threadId = cursorClientThreadOwner(parsed);
|
|
314
329
|
if (threadId) {
|
|
315
330
|
const recovered = lookupCursorThreadConversation(threadId, parsed._cursorIdentityScope);
|
|
316
331
|
if (recovered) return recovered;
|
|
@@ -319,6 +334,10 @@ export function resolveCursorConversationId(
|
|
|
319
334
|
return generatedCursorConversationId();
|
|
320
335
|
}
|
|
321
336
|
|
|
337
|
+
export function cursorClientThreadOwner(parsed: OcxParsedRequest): string | undefined {
|
|
338
|
+
return parsed._clientThreadId?.trim() || parsed._cursorClientThreadId?.trim() || undefined;
|
|
339
|
+
}
|
|
340
|
+
|
|
322
341
|
function updateFramed(hash: ReturnType<typeof createHash>, value: string): void {
|
|
323
342
|
const bytes = Buffer.from(value, "utf8");
|
|
324
343
|
const length = Buffer.allocUnsafe(4);
|
|
@@ -361,6 +380,7 @@ function lookupPrefixSnapshot(
|
|
|
361
380
|
const modelId = cursorCheckpointModelAffinityId(request.modelId);
|
|
362
381
|
for (let covered = parsed.context.messages.length; covered >= 1; covered--) {
|
|
363
382
|
const snapshot = getCursorCheckpointForPrefix({
|
|
383
|
+
conversationId: request.conversationId,
|
|
364
384
|
prefixDigest: cursorCoveredPrefixDigest(parsed, covered),
|
|
365
385
|
systemDigest,
|
|
366
386
|
coveredMessageCount: covered,
|
|
@@ -404,10 +424,14 @@ function resolveCursorCheckpoint(
|
|
|
404
424
|
snapshot = getCursorCheckpoint(ref);
|
|
405
425
|
if (!snapshot) return { reason: "expired" };
|
|
406
426
|
} else {
|
|
427
|
+
if (
|
|
428
|
+
isolated
|
|
429
|
+
|| (!parsed._cursorConversationId && !cursorClientThreadOwner(parsed))
|
|
430
|
+
) return { reason: "missing_ref" };
|
|
407
431
|
snapshot = lookupPrefixSnapshot(parsed, request, identityScope);
|
|
408
432
|
if (!snapshot) return { reason: "missing_ref" };
|
|
409
433
|
}
|
|
410
|
-
if (
|
|
434
|
+
if (snapshot.conversationId !== request.conversationId) {
|
|
411
435
|
return { reason: "conversation_changed" };
|
|
412
436
|
}
|
|
413
437
|
if (snapshot.identityScope !== identityScope) return { reason: "identity_changed" };
|
|
@@ -436,6 +460,7 @@ export function createCursorRequest(
|
|
|
436
460
|
modelId: model.modelId,
|
|
437
461
|
...(model.requestedModelParameters ? { requestedModelParameters: model.requestedModelParameters } : {}),
|
|
438
462
|
...(model.routingLevel ? { routingLevel: model.routingLevel } : {}),
|
|
463
|
+
...(model.maxMode ? { maxMode: true } : {}),
|
|
439
464
|
conversationId: resolveCursorConversationId(parsed, model.modelId, options),
|
|
440
465
|
system: [...(parsed.context.systemPrompt ?? []), ...(limitNote ? [limitNote] : [])],
|
|
441
466
|
messages,
|
|
@@ -443,6 +468,11 @@ export function createCursorRequest(
|
|
|
443
468
|
...(parsed._compactionRequest === true || parsed._contextCompactionBoundary === true ? { contextUsageReset: true } : {}),
|
|
444
469
|
...(parsed._compactionRequest === true ? { contextUsageStoreCheckpoints: false } : {}),
|
|
445
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
|
+
: {}),
|
|
446
476
|
...(parsed.options.toolChoice ? { toolChoice: parsed.options.toolChoice } : {}),
|
|
447
477
|
...(parsed.options.parallelToolCalls !== undefined ? { parallelToolCalls: parsed.options.parallelToolCalls } : {}),
|
|
448
478
|
};
|
|
@@ -3,6 +3,7 @@ import { ValueSchema } from "@bufbuild/protobuf/wkt";
|
|
|
3
3
|
import type { OcxRequestOptions, OcxTool } from "../../types";
|
|
4
4
|
import { namespacedToolName, toolChoiceAliases } from "../../types";
|
|
5
5
|
import { McpToolDefinitionSchema, McpToolsSchema, type McpToolDefinition } from "./gen/agent_pb";
|
|
6
|
+
import { CODE_MODE_RESULT_ECHO_SENTENCE } from "../exec-tool-result-normalize";
|
|
6
7
|
|
|
7
8
|
export const OCX_RESPONSES_TOOL_PROVIDER = "opencodex-responses";
|
|
8
9
|
export const CODEX_EXEC_COMMAND_TOOL = "exec_command";
|
|
@@ -651,10 +652,13 @@ export function buildCursorToolGuidanceSystemNote(
|
|
|
651
652
|
// Code mode: shell/edit/MCP live inside freeform `exec` as nested helpers. Without this the
|
|
652
653
|
// model probes for a top-level shell tool that is not there.
|
|
653
654
|
codeMode
|
|
654
|
-
? `\`${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` : ""}.`
|
|
655
|
+
? `\`${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.`
|
|
655
656
|
: undefined,
|
|
656
657
|
codeMode
|
|
657
|
-
?
|
|
658
|
+
? CODE_MODE_RESULT_ECHO_SENTENCE + " There is no `require`, no `module`, and no filesystem or network globals; reach the host only through the nested helpers."
|
|
659
|
+
: undefined,
|
|
660
|
+
codeMode
|
|
661
|
+
? "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."
|
|
658
662
|
: undefined,
|
|
659
663
|
hasBareExec
|
|
660
664
|
? `${shellBridgeLabel} is the Codex Responses shell bridge for this turn, exposed through Cursor's tool protocol; it is not an external MCP server tool. \`shell_command\` and \`exec_command\` are aliases of the same bridge.`
|
|
@@ -663,7 +667,10 @@ export function buildCursorToolGuidanceSystemNote(
|
|
|
663
667
|
? "Your tool list may display it under a longer `mcp_opencodex-responses_shell_command` / `mcp_opencodex-responses_exec_command` name; those are the SAME tool — call whichever your list shows, and do not comment on the naming difference to the user."
|
|
664
668
|
: undefined,
|
|
665
669
|
hasBareExec
|
|
666
|
-
? `
|
|
670
|
+
? `NEVER attempt Cursor-native Shell, Read, Grep, List, or any tool not in the catalog above — they are not executed locally in this environment and every attempt wastes a turn and can stall the session. ${shellBridgeLabel} is the ONLY shell surface; go to it directly on the FIRST attempt, never as a fallback after probing a native tool. Do not narrate switching surfaces ("native is blocked, using the bridge instead") — there is exactly one surface.`
|
|
671
|
+
: undefined,
|
|
672
|
+
hasBareExec
|
|
673
|
+
? "Tool-selection commentary is forbidden: for any shell, read, grep, list, or file operation, your FIRST visible action is the bridge call itself — never a sentence about which tool you will use, which tool was redirected, or switching surfaces. Words like 차단/전환/blocked/switching must not appear in your output for tool-routing reasons."
|
|
667
674
|
: undefined,
|
|
668
675
|
hostShellNote,
|
|
669
676
|
"Cursor product features (Chronicle, screen recording, Notes, Plans, background agents) are available only if this turn's catalog lists a matching tool; do not offer or promise them otherwise.",
|
|
@@ -675,6 +682,9 @@ export function buildCursorToolGuidanceSystemNote(
|
|
|
675
682
|
? `For file edits, prefer the structured edit tools ${quotedNames(structuredEditNames)} — they take replacements that OpenCodex converts into Codex \`apply_patch\` changes. Include exact leading whitespace in old_string/new_string. Use \`apply_patch\` directly only with a \`*** Begin Patch\` envelope and bare \`@@\` hunks (never git-style \`@@ -n,m +n,m @@\`); never emit patch-like plain text as tool arguments.`
|
|
676
683
|
: "For file edits, use the `apply_patch` tool, not built-in file write/delete tools."
|
|
677
684
|
: undefined,
|
|
685
|
+
hasApplyPatch
|
|
686
|
+
? "Creating or modifying file CONTENT via shell redirection (`>`, `>>`, `printf`/`echo` into a file, `cat <<EOF`, `sed -i`) is forbidden while apply_patch or the structured edit tools are advertised — use those edit tools so the change is reviewable. Shell output redirection is fine for logs/scratch pipes that are not the deliverable file."
|
|
687
|
+
: undefined,
|
|
678
688
|
hasBareExec
|
|
679
689
|
? "For tool-count demos, each counted tool must be a separate Codex shell-bridge invocation/result; do not collapse several requested tools into one chained shell command."
|
|
680
690
|
: undefined,
|
|
@@ -687,7 +697,7 @@ export function buildCursorToolGuidanceSystemNote(
|
|
|
687
697
|
: undefined,
|
|
688
698
|
"Do not count or report a tool call unless a tool result was actually returned.",
|
|
689
699
|
hasBareExec
|
|
690
|
-
? `
|
|
700
|
+
? `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.`
|
|
691
701
|
: undefined,
|
|
692
702
|
].filter((note): note is string => typeof note === "string");
|
|
693
703
|
return notes.join(" ");
|
|
@@ -9,6 +9,23 @@
|
|
|
9
9
|
* dropping images oldest-first — see toolCallStep in protobuf-request.ts).
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import {
|
|
13
|
+
EMPTY_EXEC_OUTPUT_MESSAGE,
|
|
14
|
+
EMPTY_EXEC_OUTPUT_REGEX,
|
|
15
|
+
FAILED_EXEC_OUTPUT_MESSAGE,
|
|
16
|
+
FAILED_EXEC_OUTPUT_REGEX,
|
|
17
|
+
isCodexExecBridgeTool,
|
|
18
|
+
} from "../exec-tool-result-normalize";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Cursor treats a failed-but-empty wrapper as an empty result too (its Computer Use branch marks
|
|
22
|
+
* such results `isError` separately). The shared success regex deliberately excludes
|
|
23
|
+
* `Script failed`, so restore that arm here rather than widening the shared one.
|
|
24
|
+
*/
|
|
25
|
+
function isEmptyOrFailedExecWrapper(text: string): boolean {
|
|
26
|
+
return EMPTY_EXEC_OUTPUT_REGEX.test(text) || FAILED_EXEC_OUTPUT_REGEX.test(text);
|
|
27
|
+
}
|
|
28
|
+
|
|
12
29
|
const COMPUTER_USE_TOOL_NAMES = new Set([
|
|
13
30
|
"node_repl",
|
|
14
31
|
"node_repl__js",
|
|
@@ -49,9 +66,6 @@ const RUNTIME_FAILURE_GUIDANCE: ReadonlyArray<{ marker: string; guidance: string
|
|
|
49
66
|
},
|
|
50
67
|
];
|
|
51
68
|
|
|
52
|
-
/** Matches exec wrappers whose only payload is an empty-output marker. */
|
|
53
|
-
const EMPTY_EXEC_OUTPUT_REGEX = /^(?:(?:Script completed|Script failed|Command finished|Execution finished)[^\n]*\n+)?(?:Wall time[^\n]*\n+)?(?:Output:\s*)?(?:<empty>)?\s*$/;
|
|
54
|
-
|
|
55
69
|
export interface NormalizedToolResultText {
|
|
56
70
|
text: string;
|
|
57
71
|
isError: boolean;
|
|
@@ -73,13 +87,23 @@ export function normalizeCursorToolResultText(
|
|
|
73
87
|
): NormalizedToolResultText {
|
|
74
88
|
const isError = options.isError === true;
|
|
75
89
|
const computerUse = isNodeReplOrComputerUseTool(options.toolName, options.toolNamespace);
|
|
76
|
-
if (computerUse &&
|
|
90
|
+
if (computerUse && isEmptyOrFailedExecWrapper(text.trim())) {
|
|
77
91
|
return {
|
|
78
92
|
text: "[empty output: the tool ran but produced no stdout or return value. Verify application state with get_app_state, or make the script emit output.]",
|
|
79
93
|
isError: true,
|
|
80
94
|
changed: true,
|
|
81
95
|
};
|
|
82
96
|
}
|
|
97
|
+
if (isCodexExecBridgeTool(options.toolName, options.toolNamespace) && isEmptyOrFailedExecWrapper(text.trim())) {
|
|
98
|
+
return {
|
|
99
|
+
// A `Script failed` wrapper is empty but NOT a success: reporting it as an empty success
|
|
100
|
+
// would erase the only failure signal. Text classification stays separate from Cursor's
|
|
101
|
+
// isError policy, which the Computer Use branch above owns.
|
|
102
|
+
text: FAILED_EXEC_OUTPUT_REGEX.test(text.trim()) ? FAILED_EXEC_OUTPUT_MESSAGE : EMPTY_EXEC_OUTPUT_MESSAGE,
|
|
103
|
+
isError: false,
|
|
104
|
+
changed: true,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
83
107
|
if (!isError) {
|
|
84
108
|
for (const { marker, guidance } of RUNTIME_FAILURE_GUIDANCE) {
|
|
85
109
|
if (text.includes(marker)) {
|
|
@@ -89,4 +113,3 @@ export function normalizeCursorToolResultText(
|
|
|
89
113
|
}
|
|
90
114
|
return { text, isError, changed: false };
|
|
91
115
|
}
|
|
92
|
-
|
|
@@ -15,6 +15,26 @@ export interface CursorRunRequest {
|
|
|
15
15
|
requestedModelParameters?: readonly CursorRequestedModelParameter[];
|
|
16
16
|
/** Cursor Router optimization parameter; valid only while modelId is the `default` wire model. */
|
|
17
17
|
routingLevel?: CursorRoutingLevel;
|
|
18
|
+
/**
|
|
19
|
+
* Cursor Max Mode (ultra/big-context). Set from a synthetic `-1m` picker variant; the wire
|
|
20
|
+
* keeps the original model id and raises RequestedModel.maxMode + ModelDetails.maxMode
|
|
21
|
+
* (both fields — missing either can invalid_argument upstream). Devlog 260826 070.
|
|
22
|
+
*/
|
|
23
|
+
maxMode?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Bare API callers (no caller tools, no Codex thread identity) pay a ~10-15K input-token
|
|
26
|
+
* preamble because an absent AgentRunRequest.mcp_tools field makes Cursor inject its default
|
|
27
|
+
* native tool catalog. When true, an explicitly empty McpTools wrapper is serialized instead,
|
|
28
|
+
* suppressing that default. Codex-identified sessions keep the absent-field behavior.
|
|
29
|
+
*/
|
|
30
|
+
suppressDefaultCursorToolCatalog?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Corrective active-turn text for the single envelope-echo retry (devlog 260826 gap-10).
|
|
33
|
+
* When set on an external tool-result continuation, buildPreparedCursorRunRequest uses it as
|
|
34
|
+
* the userMessageAction text instead of the standard continuation text; rawMessages stay
|
|
35
|
+
* untouched so history replay is unchanged.
|
|
36
|
+
*/
|
|
37
|
+
echoRetryContinuationText?: string;
|
|
18
38
|
conversationId: string;
|
|
19
39
|
system: string[];
|
|
20
40
|
messages: CursorRequestMessage[];
|