@yansigit/opencodex 2.33.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/gui/dist/assets/index-BjCaHxdz.js +112 -0
- package/gui/dist/assets/index-DLkXOXLC.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +79 -2
- package/src/adapters/command-code.ts +141 -23
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/checkpoint-store.ts +15 -10
- package/src/adapters/cursor/discovery.ts +60 -2
- package/src/adapters/cursor/effort-map.ts +79 -1
- package/src/adapters/cursor/envelope-echo.ts +162 -0
- package/src/adapters/cursor/live-models.ts +7 -2
- package/src/adapters/cursor/live-transport.ts +17 -1
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-fs.ts +13 -12
- package/src/adapters/cursor/native-exec-network.ts +3 -5
- package/src/adapters/cursor/native-exec-policy.ts +47 -0
- package/src/adapters/cursor/native-exec-shell.ts +116 -31
- package/src/adapters/cursor/native-exec.ts +38 -10
- package/src/adapters/cursor/protobuf-events.ts +28 -2
- package/src/adapters/cursor/protobuf-request.ts +93 -41
- package/src/adapters/cursor/request-builder.ts +39 -10
- package/src/adapters/cursor/tool-definitions.ts +27 -3
- package/src/adapters/cursor/tool-result-normalize.ts +51 -6
- package/src/adapters/cursor/types.ts +23 -4
- package/src/adapters/cursor.ts +170 -29
- package/src/adapters/google-aistudio-parser.ts +49 -0
- package/src/adapters/google-antigravity-replay.ts +105 -25
- package/src/adapters/google-antigravity-wire.ts +5 -0
- package/src/adapters/google-errors.ts +41 -12
- package/src/adapters/google-http.ts +12 -11
- package/src/adapters/google.ts +219 -36
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +15 -0
- package/src/adapters/kiro-tools.ts +43 -15
- package/src/adapters/kiro.ts +54 -9
- package/src/adapters/openai-chat.ts +286 -242
- package/src/adapters/openai-responses.ts +335 -24
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-catalog-nudge.ts +2 -2
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/bridge.ts +67 -26
- package/src/chat/inbound.ts +29 -1
- package/src/chat/outbound.ts +15 -7
- package/src/claude/agents-inject.ts +8 -1
- package/src/claude/outbound.ts +10 -8
- package/src/cli/account-api.ts +27 -7
- package/src/cli/account-extended.ts +10 -3
- package/src/cli/account.ts +29 -5
- package/src/cli/alias.ts +66 -0
- package/src/cli/claude.ts +26 -1
- package/src/cli/dispatch.ts +13 -1
- package/src/cli/help.ts +1 -0
- package/src/cli/index.ts +6 -1
- package/src/cli/init.ts +1 -0
- package/src/cli/models-runtime.ts +95 -0
- package/src/cli/models.ts +13 -7
- package/src/cli/provider-runtime.ts +16 -2
- package/src/cli/registry.ts +6 -1
- package/src/cli/telemetry-commands.ts +25 -0
- package/src/cli/v2.ts +34 -10
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +3 -2
- package/src/codex/app-server-processes.ts +80 -6
- package/src/codex/auth-api.ts +48 -8
- package/src/codex/auth-context.ts +21 -18
- package/src/codex/catalog/aggregation.ts +6 -0
- package/src/codex/catalog/model-metadata.ts +13 -1
- package/src/codex/catalog/native-models.ts +5 -2
- package/src/codex/catalog/parsing.ts +16 -0
- package/src/codex/catalog/provider-fetch.ts +20 -3
- package/src/codex/catalog/sync.ts +127 -2
- 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 +22 -2
- package/src/codex/desired-state.ts +2 -2
- package/src/codex/desktop-app-restart.ts +18 -5
- package/src/codex/inject-coordination.ts +83 -0
- package/src/codex/inject.ts +14 -1
- package/src/codex/log-guard/inspect.ts +22 -4
- package/src/codex/model-entitlements.ts +9 -2
- package/src/codex/prompt-layers.ts +371 -25
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota.ts +123 -18
- package/src/codex/routing.ts +9 -0
- package/src/codex/subagent-model-fallback.ts +198 -27
- package/src/codex/transition-state.ts +107 -8
- package/src/combos/types.ts +10 -0
- package/src/compatibility/openai-responses.ts +33 -1
- package/src/config/autonomous-remediation.ts +21 -0
- package/src/config/provider-validation.ts +14 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +191 -17
- package/src/generated/compatibility-version.json +279 -159
- package/src/generated/model-metadata.ts +3 -0
- package/src/images/loop.ts +5 -4
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
- package/src/lab/fabric/producer-child.ts +1 -1
- package/src/lib/config-ownership.ts +20 -0
- package/src/lib/errors.ts +11 -2
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/oauth/aistudio-credentials.ts +65 -0
- package/src/oauth/aistudio-native-daemon.ts +116 -0
- package/src/oauth/aistudio-session-sync.ts +95 -0
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/google-aistudio-auth.ts +98 -0
- package/src/oauth/index.ts +57 -5
- package/src/oauth/key-providers.ts +18 -1
- package/src/oauth/kiro.ts +45 -0
- package/src/oauth/login-cli.ts +65 -1
- package/src/oauth/types.ts +15 -0
- package/src/providers/codex-capacity.ts +5 -2
- 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 +29 -1
- package/src/providers/fastwire.ts +7 -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 +157 -46
- package/src/providers/registry.ts +184 -71
- package/src/providers/slug-codec.ts +52 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/custom-tool-compat.ts +34 -10
- package/src/responses/parser.ts +4 -0
- package/src/responses/schema.ts +5 -1
- package/src/responses/thought-signature-replay.ts +17 -0
- package/src/router.ts +43 -2
- package/src/routing/account-pool/cooldown.ts +8 -0
- package/src/routing/account-pool/index.ts +1 -0
- package/src/routing/analytics.ts +1 -0
- package/src/routing/quota.ts +10 -0
- package/src/server/auth-cors.ts +24 -0
- package/src/server/chat-completions.ts +26 -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/effort-policy.ts +16 -0
- package/src/server/index.ts +180 -14
- package/src/server/lifecycle.ts +52 -1
- package/src/server/management/agent-settings-routes.ts +31 -15
- 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 +27 -9
- package/src/server/management/context.ts +9 -0
- package/src/server/management/logs-usage-routes.ts +11 -5
- package/src/server/management/model-routes.ts +266 -0
- package/src/server/management/oauth-account-routes.ts +13 -3
- package/src/server/management/provider-routes.ts +137 -3
- package/src/server/management/routing-profile-routes.ts +2 -2
- package/src/server/management-api.ts +2 -0
- package/src/server/port-reclaim.ts +19 -1
- package/src/server/relay-eager.ts +147 -20
- package/src/server/relay.ts +251 -19
- package/src/server/request-log-conversation.ts +33 -0
- package/src/server/request-log.ts +48 -21
- package/src/server/responses/collaboration.ts +42 -5
- package/src/server/responses/combo-stream-preflight.ts +10 -3
- package/src/server/responses/core.ts +575 -140
- package/src/server/responses/empty-completion-guard.ts +35 -0
- package/src/server/responses/fetch-helpers.ts +14 -6
- package/src/server/responses/input-admission.ts +3 -1
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/policy-fallback.ts +1 -1
- package/src/server/responses/responses-field-backfill.ts +105 -13
- package/src/server/responses/ws-upstream.ts +35 -5
- package/src/server/responses-custom-tool-repair.ts +52 -7
- package/src/server/responses-terminal-repair.ts +25 -4
- package/src/server/sse-frame-buffer.ts +31 -4
- package/src/server/ws-bridge.ts +14 -2
- package/src/smoke/fingerprint-cache.ts +133 -0
- package/src/smoke/live-scenarios.ts +33 -0
- package/src/smoke/runner.ts +119 -0
- package/src/telemetry/dispatcher.ts +44 -0
- package/src/telemetry/fingerprint.ts +24 -0
- package/src/telemetry/hook.ts +43 -0
- package/src/telemetry/ledger.ts +54 -0
- package/src/telemetry/types.ts +23 -0
- package/src/types/config.ts +66 -14
- package/src/types/provider.ts +79 -1
- package/src/types/request.ts +18 -10
- package/src/types/tools.ts +30 -11
- package/src/types.ts +1 -0
- package/src/usage/command-code-manifest.ts +116 -0
- package/src/usage/cost.ts +2 -2
- package/src/usage/expected-prices.ts +126 -24
- package/src/usage/log.ts +18 -8
- package/src/usage/summary.ts +34 -12
- package/src/web-search/exa-executor.ts +40 -9
- package/src/web-search/index.ts +16 -8
- package/src/web-search/loop.ts +5 -4
- package/gui/dist/assets/index-DKLr4LTE.js +0 -102
- package/gui/dist/assets/index-DrSQdTRd.css +0 -1
|
@@ -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,9 @@ 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";
|
|
23
|
+
export { nativeShellDisabledMessage, type CursorNativeExecPolicyContext } from "./native-exec-policy";
|
|
24
|
+
import { nativeShellDisabledMessage, type CursorNativeExecPolicyContext } from "./native-exec-policy";
|
|
22
25
|
import {
|
|
23
26
|
createAdmissionGate,
|
|
24
27
|
type AdmissionLease,
|
|
@@ -32,6 +35,7 @@ export const CURSOR_BACKGROUND_SHELL_TERM_GRACE_MS = 2_000;
|
|
|
32
35
|
|
|
33
36
|
type BackgroundShellTerminationReason = "session_close" | "idle" | "absolute" | "shutdown";
|
|
34
37
|
type BackgroundShellTimer = ReturnType<typeof setTimeout>;
|
|
38
|
+
type ProcessGroupLiveness = "alive" | "gone" | "unknown";
|
|
35
39
|
|
|
36
40
|
export interface BackgroundShellTerminationReport {
|
|
37
41
|
attempted: number;
|
|
@@ -42,10 +46,14 @@ export interface BackgroundShellTerminationReport {
|
|
|
42
46
|
|
|
43
47
|
export interface BackgroundShellRuntime {
|
|
44
48
|
spawn: typeof spawn;
|
|
49
|
+
platform: NodeJS.Platform;
|
|
45
50
|
now(): number;
|
|
46
51
|
setTimer(callback: () => void, delayMs: number): BackgroundShellTimer;
|
|
47
52
|
clearTimer(timer: BackgroundShellTimer): void;
|
|
48
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;
|
|
49
57
|
}
|
|
50
58
|
|
|
51
59
|
interface BackgroundShellEntry {
|
|
@@ -65,10 +73,30 @@ interface BackgroundShellEntry {
|
|
|
65
73
|
|
|
66
74
|
const defaultBackgroundShellRuntime: BackgroundShellRuntime = {
|
|
67
75
|
spawn,
|
|
76
|
+
platform: process.platform,
|
|
68
77
|
now: () => Date.now(),
|
|
69
78
|
setTimer: (callback, delayMs) => setTimeout(callback, delayMs),
|
|
70
79
|
clearTimer: timer => clearTimeout(timer),
|
|
71
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
|
+
},
|
|
72
100
|
};
|
|
73
101
|
|
|
74
102
|
let backgroundShellRuntime = defaultBackgroundShellRuntime;
|
|
@@ -81,21 +109,7 @@ let absoluteTerminations = 0;
|
|
|
81
109
|
let unresolvedKills = 0;
|
|
82
110
|
let killFailures = 0;
|
|
83
111
|
|
|
84
|
-
|
|
85
|
-
export function nativeShellDisabledMessage(): string {
|
|
86
|
-
// Do not insist on "the same command" — that steers models into replaying bash/CMD
|
|
87
|
-
// idioms through the Codex bridge on Windows PowerShell 5.1 and looping (#604).
|
|
88
|
-
// Keep this host-shell-neutral: OpenCodex may run on a different OS than the Codex
|
|
89
|
-
// client that executes the bridge (LAN/SSH remote-proxy).
|
|
90
|
-
return (
|
|
91
|
-
"Route this through the Codex bridge shell tool from the current catalog (`shell_command` or `exec_command`, including the long `mcp_opencodex-responses_*` display name if listed). "
|
|
92
|
-
+ "Adapt the command for the Codex client host shell before calling the bridge "
|
|
93
|
-
+ "(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."
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function rejectedShellResult(command: string, cwd: string, started: number) {
|
|
112
|
+
function rejectedShellResult(command: string, cwd: string, started: number, opts: CursorNativeExecPolicyContext = {}) {
|
|
99
113
|
return create(ShellResultSchema, {
|
|
100
114
|
result: {
|
|
101
115
|
case: "failure",
|
|
@@ -105,7 +119,7 @@ function rejectedShellResult(command: string, cwd: string, started: number) {
|
|
|
105
119
|
exitCode: 1,
|
|
106
120
|
signal: "",
|
|
107
121
|
stdout: "",
|
|
108
|
-
stderr: nativeShellDisabledMessage(),
|
|
122
|
+
stderr: nativeShellDisabledMessage(opts),
|
|
109
123
|
executionTime: Date.now() - started,
|
|
110
124
|
aborted: true,
|
|
111
125
|
}),
|
|
@@ -113,10 +127,10 @@ function rejectedShellResult(command: string, cwd: string, started: number) {
|
|
|
113
127
|
});
|
|
114
128
|
}
|
|
115
129
|
|
|
116
|
-
export function rejectShellExecForPolicy(execMsg: ExecServerMessage): Uint8Array {
|
|
130
|
+
export function rejectShellExecForPolicy(execMsg: ExecServerMessage, opts: CursorNativeExecPolicyContext = {}): Uint8Array {
|
|
117
131
|
if (execMsg.message.case !== "shellArgs") throw new Error("invalid shell exec");
|
|
118
132
|
const args = execMsg.message.value;
|
|
119
|
-
return execBytes(execMsg, "shellResult", rejectedShellResult(args.command, resolve(args.workingDirectory || process.cwd()), Date.now()));
|
|
133
|
+
return execBytes(execMsg, "shellResult", rejectedShellResult(args.command, resolve(args.workingDirectory || process.cwd()), Date.now(), opts));
|
|
120
134
|
}
|
|
121
135
|
|
|
122
136
|
export function shellExec(execMsg: ExecServerMessage): Uint8Array {
|
|
@@ -154,7 +168,7 @@ export function shellExec(execMsg: ExecServerMessage): Uint8Array {
|
|
|
154
168
|
}));
|
|
155
169
|
}
|
|
156
170
|
|
|
157
|
-
export function rejectShellStreamExecForPolicy(execMsg: ExecServerMessage): Uint8Array[] {
|
|
171
|
+
export function rejectShellStreamExecForPolicy(execMsg: ExecServerMessage, opts: CursorNativeExecPolicyContext = {}): Uint8Array[] {
|
|
158
172
|
if (execMsg.message.case !== "shellStreamArgs") throw new Error("invalid shell stream exec");
|
|
159
173
|
const args = execMsg.message.value;
|
|
160
174
|
const cwd = resolve(args.workingDirectory || process.cwd());
|
|
@@ -164,12 +178,12 @@ export function rejectShellStreamExecForPolicy(execMsg: ExecServerMessage): Uint
|
|
|
164
178
|
event: { case: "start", value: create(ShellStreamStartSchema, { sandboxPolicy: args.requestedSandboxPolicy }) },
|
|
165
179
|
})),
|
|
166
180
|
execBytes(execMsg, "shellStream", create(ShellStreamSchema, {
|
|
167
|
-
event: { case: "stderr", value: create(ShellStreamStderrSchema, { data: nativeShellDisabledMessage() }) },
|
|
181
|
+
event: { case: "stderr", value: create(ShellStreamStderrSchema, { data: nativeShellDisabledMessage(opts) }) },
|
|
168
182
|
})),
|
|
169
183
|
execBytes(execMsg, "shellStream", create(ShellStreamSchema, {
|
|
170
184
|
event: { case: "exit", value: create(ShellStreamExitSchema, { code: 1, cwd, aborted: true }) },
|
|
171
185
|
})),
|
|
172
|
-
execBytes(execMsg, "shellResult", rejectedShellResult(args.command, cwd, started)),
|
|
186
|
+
execBytes(execMsg, "shellResult", rejectedShellResult(args.command, cwd, started, opts)),
|
|
173
187
|
execStreamCloseBytes(execMsg),
|
|
174
188
|
];
|
|
175
189
|
}
|
|
@@ -260,12 +274,12 @@ export async function shellStreamExec(execMsg: ExecServerMessage): Promise<Uint8
|
|
|
260
274
|
return replies;
|
|
261
275
|
}
|
|
262
276
|
|
|
263
|
-
export function rejectBackgroundShellSpawnExecForPolicy(execMsg: ExecServerMessage): Uint8Array {
|
|
277
|
+
export function rejectBackgroundShellSpawnExecForPolicy(execMsg: ExecServerMessage, opts: CursorNativeExecPolicyContext = {}): Uint8Array {
|
|
264
278
|
if (execMsg.message.case !== "backgroundShellSpawnArgs") throw new Error("invalid background shell exec");
|
|
265
279
|
const args = execMsg.message.value;
|
|
266
280
|
const cwd = resolve(args.workingDirectory || process.cwd());
|
|
267
281
|
return execBytes(execMsg, "backgroundShellSpawnResult", create(BackgroundShellSpawnResultSchema, {
|
|
268
|
-
result: { case: "error", value: create(BackgroundShellSpawnErrorSchema, { command: args.command, workingDirectory: cwd, error: nativeShellDisabledMessage() }) },
|
|
282
|
+
result: { case: "error", value: create(BackgroundShellSpawnErrorSchema, { command: args.command, workingDirectory: cwd, error: nativeShellDisabledMessage(opts) }) },
|
|
269
283
|
}));
|
|
270
284
|
}
|
|
271
285
|
|
|
@@ -355,6 +369,13 @@ function waitForBackgroundShellClose(entry: BackgroundShellEntry): Promise<boole
|
|
|
355
369
|
});
|
|
356
370
|
}
|
|
357
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
|
+
|
|
358
379
|
function tryKillBackgroundShell(entry: BackgroundShellEntry, signal?: NodeJS.Signals): boolean {
|
|
359
380
|
try {
|
|
360
381
|
return backgroundShellRuntime.kill(entry.child, signal);
|
|
@@ -363,6 +384,34 @@ function tryKillBackgroundShell(entry: BackgroundShellEntry, signal?: NodeJS.Sig
|
|
|
363
384
|
}
|
|
364
385
|
}
|
|
365
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
|
+
|
|
366
415
|
async function terminateBackgroundShell(
|
|
367
416
|
entry: BackgroundShellEntry,
|
|
368
417
|
reason: BackgroundShellTerminationReason,
|
|
@@ -380,11 +429,43 @@ async function terminateBackgroundShell(
|
|
|
380
429
|
try { entry.child.stderr.resume(); } catch { /* keep draining when supported */ }
|
|
381
430
|
|
|
382
431
|
let attemptKillFailures = 0;
|
|
383
|
-
|
|
384
|
-
let
|
|
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);
|
|
385
453
|
if (!closed && backgroundShells.get(entry.shellId) !== entry) closed = true;
|
|
386
|
-
if (
|
|
387
|
-
|
|
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
|
+
}
|
|
388
469
|
closed = await waitForBackgroundShellClose(entry);
|
|
389
470
|
if (!closed && backgroundShells.get(entry.shellId) !== entry) closed = true;
|
|
390
471
|
}
|
|
@@ -471,7 +552,11 @@ export function backgroundShellSpawnExec(execMsg: ExecServerMessage, sessionId:
|
|
|
471
552
|
if (!admissionLease) return backgroundShellSpawnError(execMsg, args.command, cwd, "background shell limit reached");
|
|
472
553
|
let child: ChildProcessWithoutNullStreams;
|
|
473
554
|
try {
|
|
474
|
-
child = backgroundShellRuntime.spawn(args.command, {
|
|
555
|
+
child = backgroundShellRuntime.spawn(args.command, {
|
|
556
|
+
cwd,
|
|
557
|
+
shell: true,
|
|
558
|
+
...(backgroundShellRuntime.platform === "win32" ? {} : { detached: true }),
|
|
559
|
+
});
|
|
475
560
|
} catch (err) {
|
|
476
561
|
admissionLease.release();
|
|
477
562
|
return backgroundShellSpawnError(execMsg, args.command, cwd, errorText(err));
|
|
@@ -517,10 +602,10 @@ export function backgroundShellSpawnExec(execMsg: ExecServerMessage, sessionId:
|
|
|
517
602
|
}
|
|
518
603
|
}
|
|
519
604
|
|
|
520
|
-
export function rejectWriteShellStdinExecForPolicy(execMsg: ExecServerMessage): Uint8Array {
|
|
605
|
+
export function rejectWriteShellStdinExecForPolicy(execMsg: ExecServerMessage, opts: CursorNativeExecPolicyContext = {}): Uint8Array {
|
|
521
606
|
if (execMsg.message.case !== "writeShellStdinArgs") throw new Error("invalid shell stdin exec");
|
|
522
607
|
return execBytes(execMsg, "writeShellStdinResult", create(WriteShellStdinResultSchema, {
|
|
523
|
-
result: { case: "error", value: create(WriteShellStdinErrorSchema, { error: nativeShellDisabledMessage() }) },
|
|
608
|
+
result: { case: "error", value: create(WriteShellStdinErrorSchema, { error: nativeShellDisabledMessage(opts) }) },
|
|
524
609
|
}));
|
|
525
610
|
}
|
|
526
611
|
|
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
type CursorNativeToolDeps,
|
|
52
52
|
} from "./native-exec-tools";
|
|
53
53
|
import { clientBytes, execBytes, execStreamCloseBytes, execThrowBytes } from "./native-exec-common";
|
|
54
|
+
import { type CursorNativeExecPolicyContext } from "./native-exec-policy";
|
|
54
55
|
import type { McpToolDefinition } from "./gen/agent_pb";
|
|
55
56
|
import { OCX_RESPONSES_TOOL_PROVIDER } from "./tool-definitions";
|
|
56
57
|
|
|
@@ -72,12 +73,18 @@ export interface CursorNativeExecContext extends CursorNativeExecDeps {
|
|
|
72
73
|
rejectNativeFileMutations?: boolean;
|
|
73
74
|
/** The synthetic exact-match edit tools (edit_file / multi_edit) are advertised this request. */
|
|
74
75
|
structuredEditAvailable?: boolean;
|
|
76
|
+
/** Codex code mode advertises one freeform exec tool; native rejections must steer to nested helpers. */
|
|
77
|
+
codeMode?: boolean;
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
export function cursorUnsafeNativeLocalExecEnabled(input: Pick<CursorNativeExecContext, "unsafeAllowNativeLocalExec"> = {}): boolean {
|
|
78
81
|
return input.unsafeAllowNativeLocalExec === true;
|
|
79
82
|
}
|
|
80
83
|
|
|
84
|
+
function nativeExecPolicyContext(deps: CursorNativeExecContext): CursorNativeExecPolicyContext {
|
|
85
|
+
return deps.codeMode === true ? { codeMode: true } : {};
|
|
86
|
+
}
|
|
87
|
+
|
|
81
88
|
/**
|
|
82
89
|
* Content-addressed blob store shared across streams. Bounded: without eviction a long-running
|
|
83
90
|
* proxy accumulates every conversation's prompt blobs forever (unbounded memory) and any stale
|
|
@@ -403,6 +410,19 @@ export function storeCursorBlob(data: Uint8Array, requestScope?: CursorBlobReque
|
|
|
403
410
|
return blobId;
|
|
404
411
|
}
|
|
405
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
|
+
|
|
406
426
|
/**
|
|
407
427
|
* Long-lived pin for blobs referenced by an active Cursor conversation checkpoint.
|
|
408
428
|
* Unlike a request scope, this lease is not sealed and is not released by getBlob hydration.
|
|
@@ -556,16 +576,17 @@ export async function handleCursorNativeExec(execMsg: ExecServerMessage, deps: C
|
|
|
556
576
|
}))];
|
|
557
577
|
}
|
|
558
578
|
if (!cursorUnsafeNativeLocalExecEnabled(deps)) {
|
|
559
|
-
|
|
560
|
-
if (execCase === "
|
|
561
|
-
if (execCase === "
|
|
562
|
-
if (execCase === "
|
|
563
|
-
if (execCase === "
|
|
564
|
-
if (execCase === "
|
|
565
|
-
if (execCase === "
|
|
566
|
-
if (execCase === "
|
|
567
|
-
if (execCase === "
|
|
568
|
-
if (execCase === "
|
|
579
|
+
const policy = nativeExecPolicyContext(deps);
|
|
580
|
+
if (execCase === "readArgs") return [rejectReadExecForPolicy(execMsg, policy)];
|
|
581
|
+
if (execCase === "writeArgs") return [rejectWriteExecForPolicy(execMsg, policy)];
|
|
582
|
+
if (execCase === "deleteArgs") return [rejectDeleteExecForPolicy(execMsg, policy)];
|
|
583
|
+
if (execCase === "lsArgs") return [rejectLsExecForPolicy(execMsg, policy)];
|
|
584
|
+
if (execCase === "grepArgs") return [rejectGrepExecForPolicy(execMsg, policy)];
|
|
585
|
+
if (execCase === "shellArgs") return [rejectShellExecForPolicy(execMsg, policy)];
|
|
586
|
+
if (execCase === "shellStreamArgs") return rejectShellStreamExecForPolicy(execMsg, policy);
|
|
587
|
+
if (execCase === "backgroundShellSpawnArgs") return [rejectBackgroundShellSpawnExecForPolicy(execMsg, policy)];
|
|
588
|
+
if (execCase === "writeShellStdinArgs") return [rejectWriteShellStdinExecForPolicy(execMsg, policy)];
|
|
589
|
+
if (execCase === "fetchArgs") return [rejectFetchExecForPolicy(execMsg, policy)];
|
|
569
590
|
}
|
|
570
591
|
if (execCase === "readArgs") return [readExec(execMsg)];
|
|
571
592
|
if (execCase === "writeArgs") return [deps.rejectNativeFileMutations ? rejectWriteExecForApplyPatch(execMsg, deps.structuredEditAvailable === true) : writeExec(execMsg)];
|
|
@@ -622,6 +643,13 @@ export function handleCursorNativeKv(
|
|
|
622
643
|
if (kvMsg.message.case === "getBlobArgs") {
|
|
623
644
|
const blobKey = key(kvMsg.message.value.blobId);
|
|
624
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
|
+
}
|
|
625
653
|
if (blobData && requestScope && blobRequestScopes.get(requestScope)?.kind === "request") {
|
|
626
654
|
releaseHydratedBlob(blobKey, requestScope);
|
|
627
655
|
}
|
|
@@ -1136,6 +1136,31 @@ function cursorFreeformWrapperValid(args: string): boolean {
|
|
|
1136
1136
|
}
|
|
1137
1137
|
}
|
|
1138
1138
|
|
|
1139
|
+
/**
|
|
1140
|
+
* Cursor sometimes sends a freeform body as a JSON string or as a one-key object instead of the
|
|
1141
|
+
* advertised `{input: string}` wrapper. Preserve incomplete JSON for the late native-args path,
|
|
1142
|
+
* but wrap complete/raw bodies before the Responses bridge validates them.
|
|
1143
|
+
*/
|
|
1144
|
+
function normalizeFreeformArgs(args: string): string {
|
|
1145
|
+
if (args.length === 0) return args;
|
|
1146
|
+
try {
|
|
1147
|
+
const parsed = JSON.parse(args) as unknown;
|
|
1148
|
+
if (typeof parsed === "string") return JSON.stringify({ input: parsed });
|
|
1149
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1150
|
+
const object = parsed as Record<string, unknown>;
|
|
1151
|
+
if ("input" in object || Object.keys(object).length === 0) return args;
|
|
1152
|
+
const values = Object.values(object);
|
|
1153
|
+
const input = values.length === 1 && typeof values[0] === "string" ? values[0] : JSON.stringify(object);
|
|
1154
|
+
return JSON.stringify({ input });
|
|
1155
|
+
}
|
|
1156
|
+
return args;
|
|
1157
|
+
} catch {
|
|
1158
|
+
return args.trimStart().startsWith("{") || args.trimStart().startsWith("[")
|
|
1159
|
+
? args
|
|
1160
|
+
: JSON.stringify({ input: args });
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1139
1164
|
function dropInvalidFreeformCall(state: CursorProtobufEventState, callId: string, toolName: string): CursorServerMessage[] {
|
|
1140
1165
|
state.openToolCalls.delete(callId);
|
|
1141
1166
|
state.translatorBudget?.closeCall(callId);
|
|
@@ -1162,6 +1187,7 @@ function dropStructuredEditCall(state: CursorProtobufEventState, callId: string,
|
|
|
1162
1187
|
function commitToolCall(state: CursorProtobufEventState, callId: string, finalArgs: string): CursorServerMessage[] {
|
|
1163
1188
|
const open = state.openToolCalls.get(callId);
|
|
1164
1189
|
if (!open) return [];
|
|
1190
|
+
if (state.freeformToolNames?.has(open.name)) finalArgs = normalizeFreeformArgs(finalArgs);
|
|
1165
1191
|
if (state.freeformToolNames?.has(open.name) && !cursorFreeformWrapperValid(finalArgs)) {
|
|
1166
1192
|
return dropInvalidFreeformCall(state, callId, open.name);
|
|
1167
1193
|
}
|
|
@@ -1436,7 +1462,7 @@ function parseCursorTextToolCalls(text: string, state: CursorProtobufEventState)
|
|
|
1436
1462
|
&& (
|
|
1437
1463
|
(name !== undefined && openBeforeStart.args.length === 0)
|
|
1438
1464
|
|| (state.freeformToolNames?.has(openBeforeStart.name) === true
|
|
1439
|
-
&& !cursorFreeformWrapperValid(openBeforeStart.args)
|
|
1465
|
+
&& !cursorFreeformWrapperValid(normalizeFreeformArgs(openBeforeStart.args))
|
|
1440
1466
|
)
|
|
1441
1467
|
)
|
|
1442
1468
|
) {
|
|
@@ -1460,7 +1486,7 @@ function parseCursorTextToolCalls(text: string, state: CursorProtobufEventState)
|
|
|
1460
1486
|
if (
|
|
1461
1487
|
!openBeforeStart && open && !hasMcpArgBytes(args)
|
|
1462
1488
|
&& state.freeformToolNames?.has(open.name) === true
|
|
1463
|
-
&& !cursorFreeformWrapperValid(open.args)
|
|
1489
|
+
&& !cursorFreeformWrapperValid(normalizeFreeformArgs(open.args))
|
|
1464
1490
|
) {
|
|
1465
1491
|
open.awaitingNativeArgs = true;
|
|
1466
1492
|
return [];
|