pi-cursor-sdk 0.1.55 → 0.1.57
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/CHANGELOG.md +60 -2
- package/README.md +100 -28
- package/docs/cursor-live-smoke-checklist.md +7 -7
- package/docs/cursor-model-ux-spec.md +48 -41
- package/docs/cursor-native-tool-replay.md +4 -4
- package/docs/cursor-native-tool-visual-audit.md +1 -1
- package/docs/cursor-testing-lessons.md +20 -7
- package/docs/cursor-tool-surfaces.md +13 -2
- package/docs/platform-smoke-implementation.md +220 -0
- package/docs/platform-smoke.md +183 -247
- package/package.json +39 -7
- package/platform-smoke.config.mjs +5 -1
- package/scripts/cloud-runtime-smoke.d.mts +3 -0
- package/scripts/cloud-runtime-smoke.mjs +502 -0
- package/scripts/debug-provider-events.mjs +7 -2
- package/scripts/isolated-cursor-smoke.sh +4 -6
- package/scripts/lib/cursor-child-process.d.mts +1 -0
- package/scripts/lib/cursor-child-process.mjs +137 -7
- package/scripts/lib/local-resume-smoke-harness.mjs +543 -0
- package/scripts/local-resume-cleanup-smoke.mjs +108 -0
- package/scripts/local-resume-smoke.d.mts +1 -0
- package/scripts/local-resume-smoke.mjs +642 -0
- package/scripts/platform-smoke/artifact-anchored-extract.d.mts +10 -0
- package/scripts/platform-smoke/artifact-anchored-extract.mjs +111 -0
- package/scripts/platform-smoke/artifact-bundle-chunk.mjs +57 -0
- package/scripts/platform-smoke/artifact-bundle-contract.mjs +34 -0
- package/scripts/platform-smoke/artifact-fs-safety.mjs +311 -0
- package/scripts/platform-smoke/artifact-openat-extract.c +335 -0
- package/scripts/platform-smoke/artifact-secrets.mjs +155 -0
- package/scripts/platform-smoke/artifacts.mjs +293 -65
- package/scripts/platform-smoke/card-detect.mjs +16 -4
- package/scripts/platform-smoke/crabbox-runner.mjs +45 -3
- package/scripts/platform-smoke/doctor.mjs +20 -10
- package/scripts/platform-smoke/live-suite-runner.mjs +18 -57
- package/scripts/platform-smoke/local-resume-runner.mjs +252 -0
- package/scripts/platform-smoke/local-resume-suites.d.mts +15 -0
- package/scripts/platform-smoke/local-resume-suites.mjs +104 -0
- package/scripts/platform-smoke/scenarios.mjs +16 -2
- package/scripts/platform-smoke/target-runtime.mjs +206 -0
- package/scripts/platform-smoke/targets.mjs +33 -141
- package/scripts/platform-smoke/visual-evidence.mjs +6 -7
- package/scripts/platform-smoke/wrapped-line-match.mjs +9 -0
- package/scripts/platform-smoke.mjs +40 -27
- package/scripts/refresh-cursor-model-snapshots.mjs +18 -6
- package/scripts/steering-rpc-smoke.mjs +12 -2
- package/scripts/tmux-live-smoke.sh +3 -5
- package/shared/cursor-cloud-lifecycle-constants.d.mts +3 -0
- package/shared/cursor-cloud-lifecycle-constants.mjs +7 -0
- package/shared/cursor-sensitive-text.mjs +7 -1
- package/src/context.ts +5 -2
- package/src/cursor-agents-context-registration.ts +7 -0
- package/src/cursor-agents-context.ts +3 -1
- package/src/cursor-api-key.ts +15 -1
- package/src/cursor-bridge-contract.ts +3 -0
- package/src/cursor-cloud-lifecycle.ts +733 -0
- package/src/cursor-cloud-options.ts +206 -0
- package/src/cursor-cloud-reporting.ts +246 -0
- package/src/cursor-config.ts +659 -0
- package/src/cursor-display-only-trace.ts +14 -0
- package/src/cursor-display-text.ts +8 -2
- package/src/cursor-durable-fs.ts +49 -0
- package/src/cursor-fallback-models.generated.ts +2045 -485
- package/src/cursor-live-run-accounting.ts +7 -1
- package/src/cursor-live-run-coordinator.ts +1 -0
- package/src/cursor-pi-tool-bridge-run.ts +14 -4
- package/src/cursor-provider-errors.ts +31 -5
- package/src/cursor-provider-live-run-drain.ts +5 -0
- package/src/cursor-provider-run-finalizer.ts +37 -28
- package/src/cursor-provider-run-outcome.ts +7 -2
- package/src/cursor-provider-turn-finalize.ts +77 -7
- package/src/cursor-provider-turn-prepare.ts +228 -12
- package/src/cursor-provider-turn-runner.ts +42 -13
- package/src/cursor-provider-turn-send.ts +59 -16
- package/src/cursor-provider-turn-types.ts +44 -10
- package/src/cursor-runtime-state.ts +478 -0
- package/src/cursor-sdk-event-debug.ts +46 -6
- package/src/cursor-sdk-process-error-guard.ts +101 -30
- package/src/cursor-session-agent-cleanup.ts +328 -0
- package/src/cursor-session-agent-resume.ts +439 -0
- package/src/cursor-session-agent.ts +109 -13
- package/src/cursor-session-scope.ts +35 -2
- package/src/cursor-session-send-policy.ts +1 -1
- package/src/cursor-skill-tool.ts +30 -11
- package/src/cursor-state.ts +112 -69
- package/src/cursor-tool-manifest.ts +1 -1
- package/src/cursor-usage-accounting.ts +14 -4
- package/src/index.ts +11 -2
- package/src/model-discovery.ts +10 -56
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import {
|
|
6
|
+
assertNotResumedFrom,
|
|
7
|
+
assertTurnMetadata,
|
|
8
|
+
assistantEntryContaining,
|
|
9
|
+
buildLocalResumeSmokeEnv,
|
|
10
|
+
cleanupArtifactRoot,
|
|
11
|
+
compactionEntryCount,
|
|
12
|
+
createRunContext,
|
|
13
|
+
fail,
|
|
14
|
+
getEntries,
|
|
15
|
+
getState,
|
|
16
|
+
latestResumeEntry,
|
|
17
|
+
parseTimeout,
|
|
18
|
+
promptAbortAndRead,
|
|
19
|
+
promptAndRead,
|
|
20
|
+
reportFailure,
|
|
21
|
+
resumeEntries,
|
|
22
|
+
scrubSmokeText,
|
|
23
|
+
resumeEntryCount,
|
|
24
|
+
rpcData,
|
|
25
|
+
userEntryContaining,
|
|
26
|
+
withRpc,
|
|
27
|
+
writeTreeCommandExtension,
|
|
28
|
+
} from "./lib/local-resume-smoke-harness.mjs";
|
|
29
|
+
import { runCleanupSmoke } from "./local-resume-cleanup-smoke.mjs";
|
|
30
|
+
import { writePlatformArtifactBundle } from "./platform-smoke/artifacts.mjs";
|
|
31
|
+
import { LOCAL_RESUME_SUITES } from "./platform-smoke/local-resume-suites.mjs";
|
|
32
|
+
|
|
33
|
+
export { buildLocalResumeSmokeEnv };
|
|
34
|
+
|
|
35
|
+
const argv = process.argv.slice(2);
|
|
36
|
+
const args = new Set(argv);
|
|
37
|
+
|
|
38
|
+
function printHelp() {
|
|
39
|
+
const npmUsage = LOCAL_RESUME_SUITES.map((lane) => ` npm run ${lane.script}`).join("\n");
|
|
40
|
+
const nodeUsage = LOCAL_RESUME_SUITES.map((lane) => ` node scripts/local-resume-smoke.mjs${lane.flag ? ` ${lane.flag}` : ""}`).join("\n");
|
|
41
|
+
console.log(`Live local Cursor resume smoke for pi-cursor-sdk.
|
|
42
|
+
|
|
43
|
+
Usage:
|
|
44
|
+
${npmUsage}
|
|
45
|
+
${nodeUsage}
|
|
46
|
+
|
|
47
|
+
Environment:
|
|
48
|
+
CURSOR_LOCAL_RESUME_SMOKE_MODEL Cursor model id (default: cursor/composer-2-5:slow).
|
|
49
|
+
CURSOR_LOCAL_RESUME_SMOKE_TIMEOUT_MS Timeout in ms per model turn (default: 300000).
|
|
50
|
+
CURSOR_LOCAL_RESUME_SMOKE_KEEP_ARTIFACTS Keep temp artifacts when set to 1.
|
|
51
|
+
CURSOR_LOCAL_RESUME_SMOKE_EXTENSION_PATH Packed extension path override (platform runner only).
|
|
52
|
+
CURSOR_LOCAL_RESUME_SMOKE_ARTIFACT_DIR Fixed artifact root (platform runner only).
|
|
53
|
+
|
|
54
|
+
Exit codes:
|
|
55
|
+
0 local resume proof passed
|
|
56
|
+
1 auth/run/assertion failure
|
|
57
|
+
2 invalid command-line usage`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (args.has("-h") || args.has("--help")) {
|
|
61
|
+
printHelp();
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const laneFlags = new Set(LOCAL_RESUME_SUITES.flatMap((lane) => lane.flag ? [lane.flag] : []));
|
|
66
|
+
const unknownArgs = argv.filter((arg) => !laneFlags.has(arg));
|
|
67
|
+
const selectedLaneArgs = argv.filter((arg) => laneFlags.has(arg));
|
|
68
|
+
if (unknownArgs.length > 0 || selectedLaneArgs.length > 1) {
|
|
69
|
+
const message = unknownArgs.length > 0
|
|
70
|
+
? `unknown argument(s): ${unknownArgs.join(", ")}`
|
|
71
|
+
: "only one smoke lane may be selected";
|
|
72
|
+
console.error(scrubSmokeText(`[local-resume-smoke] usage error: ${message}\nRun with --help for usage.`));
|
|
73
|
+
process.exit(2);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function rewriteResumeAgentIds(sessionFile, agentId) {
|
|
77
|
+
const lines = readFileSync(sessionFile, "utf8")
|
|
78
|
+
.split(/\n/)
|
|
79
|
+
.filter(Boolean)
|
|
80
|
+
.map((line) => {
|
|
81
|
+
const entry = JSON.parse(line);
|
|
82
|
+
if (entry?.type === "custom" && entry.customType === "cursor-sdk-agent-resume" && entry.data?.agentId) entry.data.agentId = agentId;
|
|
83
|
+
return JSON.stringify(entry);
|
|
84
|
+
});
|
|
85
|
+
writeFileSync(sessionFile, `${lines.join("\n")}\n`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function runSmoke() {
|
|
89
|
+
const timeoutMs = parseTimeout();
|
|
90
|
+
const { artifactRoot, sessionDir, sessionId, seenMetadata } = createRunContext("pi-cursor-local-resume-smoke-");
|
|
91
|
+
const marker = `LOCAL_RESUME_${Date.now()}`;
|
|
92
|
+
let first;
|
|
93
|
+
let second;
|
|
94
|
+
console.error(scrubSmokeText(`[local-resume-smoke] artifacts: ${artifactRoot}`));
|
|
95
|
+
try {
|
|
96
|
+
first = await withRpc({ artifactDir: artifactRoot, sessionDir, sessionId }, (rpc) => promptAndRead({
|
|
97
|
+
rpc,
|
|
98
|
+
artifactDir: artifactRoot,
|
|
99
|
+
message: `Remember exact marker ${marker}. You must repeat it from conversation memory on my next turn. Reply exactly FIRST_OK.`,
|
|
100
|
+
timeoutMs,
|
|
101
|
+
seenMetadata,
|
|
102
|
+
}));
|
|
103
|
+
assertTurnMetadata("first turn", first, { resumedAgent: false });
|
|
104
|
+
|
|
105
|
+
second = await withRpc({ artifactDir: artifactRoot, sessionDir, sessionId }, (rpc) => promptAndRead({
|
|
106
|
+
rpc,
|
|
107
|
+
artifactDir: artifactRoot,
|
|
108
|
+
message: "Repeat the exact LOCAL_RESUME marker from my immediately previous message. Do not use tools or inspect files. Reply with only MARKER=<marker>.",
|
|
109
|
+
timeoutMs,
|
|
110
|
+
seenMetadata,
|
|
111
|
+
}));
|
|
112
|
+
if (!second.text.includes(`MARKER=${marker}`)) fail("second turn did not recall local resume marker", JSON.stringify({ expected: `MARKER=${marker}`, actual: second.text }, null, 2));
|
|
113
|
+
assertTurnMetadata("second turn", second, { resumedAgent: true });
|
|
114
|
+
if (first.metadata.run.agentId !== second.metadata.run.agentId) {
|
|
115
|
+
fail("second turn did not reuse the first local SDK agent", JSON.stringify({ first: first.metadata.run.agentId, second: second.metadata.run.agentId }, null, 2));
|
|
116
|
+
}
|
|
117
|
+
console.log("local-resume-smoke-ok");
|
|
118
|
+
console.error(scrubSmokeText(`[local-resume-smoke] agent ${second.metadata.run.agentId} resumed across restart`));
|
|
119
|
+
} finally {
|
|
120
|
+
cleanupArtifactRoot(artifactRoot);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function runSafetySmoke() {
|
|
125
|
+
const timeoutMs = parseTimeout();
|
|
126
|
+
const { artifactRoot, sessionDir, sessionId, seenMetadata } = createRunContext("pi-cursor-local-resume-safety-smoke-");
|
|
127
|
+
const baseMarker = `LOCAL_BASE_${Date.now()}`;
|
|
128
|
+
const futureMarker = `LOCAL_FUTURE_${Date.now()}`;
|
|
129
|
+
let originalSessionFile;
|
|
130
|
+
let originalAgentId;
|
|
131
|
+
let futureEntryId;
|
|
132
|
+
console.error(scrubSmokeText(`[local-resume-smoke] artifacts: ${artifactRoot}`));
|
|
133
|
+
try {
|
|
134
|
+
await withRpc({ artifactDir: artifactRoot, sessionDir, sessionId }, async (rpc) => {
|
|
135
|
+
const first = await promptAndRead({
|
|
136
|
+
rpc,
|
|
137
|
+
artifactDir: artifactRoot,
|
|
138
|
+
message: `Remember exact base marker ${baseMarker}. Reply exactly BASE_OK.`,
|
|
139
|
+
timeoutMs,
|
|
140
|
+
seenMetadata,
|
|
141
|
+
});
|
|
142
|
+
assertTurnMetadata("base turn", first, { resumedAgent: false });
|
|
143
|
+
const future = await promptAndRead({
|
|
144
|
+
rpc,
|
|
145
|
+
artifactDir: artifactRoot,
|
|
146
|
+
message: `Remember exact future-only marker ${futureMarker}. Reply exactly FUTURE_OK.`,
|
|
147
|
+
timeoutMs,
|
|
148
|
+
seenMetadata,
|
|
149
|
+
});
|
|
150
|
+
assertTurnMetadata("future turn", future, { resumedAgent: false });
|
|
151
|
+
if (future.metadata.run.agentId !== first.metadata.run.agentId) fail("same process did not keep one local SDK agent", JSON.stringify({ first: first.metadata.run.agentId, future: future.metadata.run.agentId }, null, 2));
|
|
152
|
+
originalAgentId = future.metadata.run.agentId;
|
|
153
|
+
const state = await getState(rpc);
|
|
154
|
+
originalSessionFile = state.sessionFile;
|
|
155
|
+
const entries = await getEntries(rpc);
|
|
156
|
+
if (resumeEntryCount(entries) < 2) fail("original branch did not persist resume entries", JSON.stringify({ resumeEntries: resumeEntryCount(entries), sessionFile: originalSessionFile }, null, 2));
|
|
157
|
+
futureEntryId = userEntryContaining(entries, futureMarker)?.id;
|
|
158
|
+
if (!futureEntryId) fail("could not find future-marker user entry", originalSessionFile ?? "");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
await withRpc({ artifactDir: artifactRoot, sessionDir, sessionId }, async (rpc) => {
|
|
162
|
+
const same = await promptAndRead({
|
|
163
|
+
rpc,
|
|
164
|
+
artifactDir: artifactRoot,
|
|
165
|
+
message: "From this conversation memory only, what exact LOCAL_FUTURE marker did I ask you to remember? Reply exactly MARKER=<marker> if known, otherwise NO_MARKER. Do not inspect environment variables or files.",
|
|
166
|
+
timeoutMs,
|
|
167
|
+
seenMetadata,
|
|
168
|
+
});
|
|
169
|
+
assertTurnMetadata("same-session restart", same, { resumedAgent: true });
|
|
170
|
+
if (same.metadata.run.agentId !== originalAgentId) fail("same-session restart did not resume original agent", JSON.stringify({ original: originalAgentId, actual: same.metadata.run.agentId }, null, 2));
|
|
171
|
+
|
|
172
|
+
const clone = await rpcData(rpc, "clone", {}, 120000);
|
|
173
|
+
if (clone.cancelled === true) fail("clone was cancelled");
|
|
174
|
+
const cloneState = await getState(rpc);
|
|
175
|
+
if (cloneState.sessionFile === originalSessionFile) fail("clone did not switch session file", String(originalSessionFile));
|
|
176
|
+
const cloneEntries = await getEntries(rpc);
|
|
177
|
+
if (resumeEntryCount(cloneEntries) < 1) fail("clone did not carry any resume entries to reject", JSON.stringify({ sessionFile: cloneState.sessionFile }, null, 2));
|
|
178
|
+
const cloneTurn = await promptAndRead({
|
|
179
|
+
rpc,
|
|
180
|
+
artifactDir: artifactRoot,
|
|
181
|
+
message: "What exact LOCAL_FUTURE marker is visible in this cloned pi transcript? Reply exactly MARKER=<marker> if visible, otherwise NO_MARKER.",
|
|
182
|
+
timeoutMs,
|
|
183
|
+
seenMetadata,
|
|
184
|
+
});
|
|
185
|
+
assertNotResumedFrom("clone session", cloneTurn, originalAgentId);
|
|
186
|
+
|
|
187
|
+
await rpcData(rpc, "switch_session", { sessionPath: originalSessionFile }, 120000);
|
|
188
|
+
const fork = await rpcData(rpc, "fork", { entryId: futureEntryId }, 120000);
|
|
189
|
+
if (fork.cancelled === true) fail("fork was cancelled");
|
|
190
|
+
const forkEntries = await getEntries(rpc);
|
|
191
|
+
if (JSON.stringify(forkEntries).includes(futureMarker)) fail("fork branch already contained future marker before prompt", String(futureEntryId));
|
|
192
|
+
const forkTurn = await promptAndRead({
|
|
193
|
+
rpc,
|
|
194
|
+
artifactDir: artifactRoot,
|
|
195
|
+
message: "On this current forked earlier branch only, what exact LOCAL_FUTURE marker is visible? Reply exactly MARKER=<marker> only if the full marker is present on this active branch; otherwise reply exactly NO_MARKER. Do not use memory from other branches, environment variables, or files.",
|
|
196
|
+
timeoutMs,
|
|
197
|
+
seenMetadata,
|
|
198
|
+
});
|
|
199
|
+
assertNotResumedFrom("fork before future", forkTurn, originalAgentId);
|
|
200
|
+
if (forkTurn.text.includes(futureMarker)) fail("forked earlier branch leaked future marker", forkTurn.text);
|
|
201
|
+
});
|
|
202
|
+
console.log("local-resume-safety-smoke-ok");
|
|
203
|
+
console.error(scrubSmokeText(`[local-resume-smoke] original ${originalAgentId} rejected for clone and fork-before-future`));
|
|
204
|
+
} finally {
|
|
205
|
+
cleanupArtifactRoot(artifactRoot);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function longRunningAbortPrompt(markerDir) {
|
|
210
|
+
return `Call pi__bash with command:
|
|
211
|
+
node -e "const fs=require('fs');fs.mkdirSync('${markerDir}',{recursive:true});fs.writeFileSync('${markerDir}/started.txt',String(process.pid));setTimeout(()=>console.log('LOCAL_RESUME_ABORT_SHOULD_NOT_PRINT'),30000)"
|
|
212
|
+
|
|
213
|
+
Do not answer until the tool completes.`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function runToolSurfaceSmoke() {
|
|
217
|
+
const timeoutMs = parseTimeout();
|
|
218
|
+
const { artifactRoot, sessionDir, sessionId, seenMetadata } = createRunContext("pi-cursor-local-resume-tool-surface-smoke-");
|
|
219
|
+
const marker = `LOCAL_TOOL_SURFACE_${Date.now()}`;
|
|
220
|
+
let originalAgentId;
|
|
221
|
+
let originalPoolKey;
|
|
222
|
+
console.error(scrubSmokeText(`[local-resume-smoke] artifacts: ${artifactRoot}`));
|
|
223
|
+
try {
|
|
224
|
+
await withRpc({ artifactDir: artifactRoot, sessionDir, sessionId }, async (rpc) => {
|
|
225
|
+
const first = await promptAndRead({
|
|
226
|
+
rpc,
|
|
227
|
+
artifactDir: artifactRoot,
|
|
228
|
+
message: `Remember exact tool-surface marker ${marker}. Reply exactly TOOL_SURFACE_OK.`,
|
|
229
|
+
timeoutMs,
|
|
230
|
+
seenMetadata,
|
|
231
|
+
});
|
|
232
|
+
assertTurnMetadata("baseline tool surface", first, { resumedAgent: false });
|
|
233
|
+
originalAgentId = first.metadata.run.agentId;
|
|
234
|
+
originalPoolKey = latestResumeEntry(await getEntries(rpc))?.poolKey;
|
|
235
|
+
if (!originalPoolKey) fail("baseline turn did not persist a resume pool key");
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
await withRpc({ artifactDir: artifactRoot, sessionDir, sessionId }, async (rpc) => {
|
|
239
|
+
const sameSurface = await promptAndRead({
|
|
240
|
+
rpc,
|
|
241
|
+
artifactDir: artifactRoot,
|
|
242
|
+
message: "From this conversation memory only, what exact LOCAL_TOOL_SURFACE marker did I ask you to remember? Reply exactly MARKER=<marker> if known, otherwise NO_MARKER. Do not inspect environment variables or files.",
|
|
243
|
+
timeoutMs,
|
|
244
|
+
seenMetadata,
|
|
245
|
+
});
|
|
246
|
+
assertTurnMetadata("same tool surface restart", sameSurface, { resumedAgent: true });
|
|
247
|
+
if (sameSurface.metadata.run.agentId !== originalAgentId) fail("same tool surface restart did not resume original agent", JSON.stringify({ original: originalAgentId, actual: sameSurface.metadata.run.agentId }, null, 2));
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
await withRpc({ artifactDir: artifactRoot, sessionDir, sessionId, bridge: true, exposeBuiltinTools: true }, async (rpc) => {
|
|
251
|
+
const changedSurface = await promptAndRead({
|
|
252
|
+
rpc,
|
|
253
|
+
artifactDir: artifactRoot,
|
|
254
|
+
message: "What exact LOCAL_TOOL_SURFACE marker is visible in this pi transcript? Reply exactly MARKER=<marker> if visible, otherwise NO_MARKER.",
|
|
255
|
+
timeoutMs,
|
|
256
|
+
seenMetadata,
|
|
257
|
+
});
|
|
258
|
+
if (!changedSurface.text.includes(`MARKER=${marker}`)) fail("changed tool surface did not bootstrap transcript marker", JSON.stringify({ expected: `MARKER=${marker}`, actual: changedSurface.text }, null, 2));
|
|
259
|
+
assertNotResumedFrom("changed tool surface", changedSurface, originalAgentId);
|
|
260
|
+
if (!changedSurface.metadata.providerMeta?.bridgeRunId) fail("changed tool surface did not start a bridge run", changedSurface.metadataPath);
|
|
261
|
+
const changedHandle = latestResumeEntry(await getEntries(rpc));
|
|
262
|
+
if (!changedHandle?.poolKey) fail("changed tool surface did not persist a resume pool key");
|
|
263
|
+
if (changedHandle.agentId !== changedSurface.metadata.run.agentId) fail("changed tool surface persisted handle for a different agent", JSON.stringify({ handle: changedHandle.agentId, run: changedSurface.metadata.run.agentId }, null, 2));
|
|
264
|
+
if (changedHandle.poolKey === originalPoolKey) fail("changed tool surface reused the original pool key");
|
|
265
|
+
});
|
|
266
|
+
console.log("local-resume-tool-surface-smoke-ok");
|
|
267
|
+
console.error(scrubSmokeText(`[local-resume-smoke] original ${originalAgentId} rejected after bridge builtin tool surface change`));
|
|
268
|
+
} finally {
|
|
269
|
+
cleanupArtifactRoot(artifactRoot);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function runAbortSmoke() {
|
|
274
|
+
const timeoutMs = parseTimeout();
|
|
275
|
+
const { artifactRoot, sessionDir, sessionId, seenMetadata } = createRunContext("pi-cursor-local-resume-abort-smoke-");
|
|
276
|
+
const marker = `LOCAL_ABORT_${Date.now()}`;
|
|
277
|
+
const markerDir = ".debug/local-resume-abort";
|
|
278
|
+
const markerPath = join(artifactRoot, "workspace", markerDir, "started.txt");
|
|
279
|
+
let originalAgentId;
|
|
280
|
+
let resumeCountBeforeAbort;
|
|
281
|
+
console.error(scrubSmokeText(`[local-resume-smoke] artifacts: ${artifactRoot}`));
|
|
282
|
+
try {
|
|
283
|
+
await withRpc({ artifactDir: artifactRoot, sessionDir, sessionId, bridge: true, exposeBuiltinTools: true }, async (rpc) => {
|
|
284
|
+
const baseline = await promptAndRead({
|
|
285
|
+
rpc,
|
|
286
|
+
artifactDir: artifactRoot,
|
|
287
|
+
message: `Remember exact abort marker ${marker}. Reply exactly ABORT_BASELINE_OK.`,
|
|
288
|
+
timeoutMs,
|
|
289
|
+
seenMetadata,
|
|
290
|
+
});
|
|
291
|
+
assertTurnMetadata("abort baseline", baseline, { resumedAgent: false });
|
|
292
|
+
if (!baseline.metadata.providerMeta?.bridgeRunId) fail("abort baseline did not start a bridge run", baseline.metadataPath);
|
|
293
|
+
originalAgentId = baseline.metadata.run.agentId;
|
|
294
|
+
resumeCountBeforeAbort = resumeEntryCount(await getEntries(rpc));
|
|
295
|
+
if (resumeCountBeforeAbort < 1) fail("abort baseline did not persist a resume handle");
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
await withRpc({ artifactDir: artifactRoot, sessionDir, sessionId, bridge: true, exposeBuiltinTools: true }, async (rpc) => {
|
|
299
|
+
const aborted = await promptAbortAndRead({
|
|
300
|
+
rpc,
|
|
301
|
+
artifactDir: artifactRoot,
|
|
302
|
+
message: longRunningAbortPrompt(markerDir),
|
|
303
|
+
markerPath,
|
|
304
|
+
timeoutMs,
|
|
305
|
+
seenMetadata,
|
|
306
|
+
});
|
|
307
|
+
assertTurnMetadata("aborted turn", aborted, { resumedAgent: true });
|
|
308
|
+
if (aborted.metadata.run.agentId !== originalAgentId) fail("aborted turn did not start from the original resumed agent", JSON.stringify({ original: originalAgentId, actual: aborted.metadata.run.agentId }, null, 2));
|
|
309
|
+
const entriesAfterAbort = await getEntries(rpc);
|
|
310
|
+
const resumeCountAfterAbort = resumeEntryCount(entriesAfterAbort);
|
|
311
|
+
if (resumeCountAfterAbort !== resumeCountBeforeAbort) fail("aborted turn persisted a new resume handle", JSON.stringify({ before: resumeCountBeforeAbort, after: resumeCountAfterAbort }, null, 2));
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
await withRpc({ artifactDir: artifactRoot, sessionDir, sessionId, bridge: true, exposeBuiltinTools: true }, async (rpc) => {
|
|
315
|
+
const afterAbort = await promptAndRead({
|
|
316
|
+
rpc,
|
|
317
|
+
artifactDir: artifactRoot,
|
|
318
|
+
message: "Reply exactly AFTER_ABORT_OK.",
|
|
319
|
+
timeoutMs,
|
|
320
|
+
seenMetadata,
|
|
321
|
+
});
|
|
322
|
+
assertNotResumedFrom("after aborted turn restart", afterAbort, originalAgentId);
|
|
323
|
+
if (!afterAbort.metadata.providerMeta?.bridgeRunId) fail("after aborted turn restart did not start a bridge run", afterAbort.metadataPath);
|
|
324
|
+
const handle = latestResumeEntry(await getEntries(rpc));
|
|
325
|
+
if (handle?.agentId !== afterAbort.metadata.run.agentId) fail("after aborted turn did not persist the new agent handle", JSON.stringify({ handle: handle?.agentId, run: afterAbort.metadata.run.agentId }, null, 2));
|
|
326
|
+
});
|
|
327
|
+
console.log("local-resume-abort-smoke-ok");
|
|
328
|
+
console.error(scrubSmokeText(`[local-resume-smoke] original ${originalAgentId} not reused after aborted bridge turn`));
|
|
329
|
+
} finally {
|
|
330
|
+
cleanupArtifactRoot(artifactRoot);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function runTreeSmoke() {
|
|
335
|
+
const timeoutMs = parseTimeout();
|
|
336
|
+
const { artifactRoot, sessionDir, sessionId, seenMetadata } = createRunContext("pi-cursor-local-resume-tree-smoke-");
|
|
337
|
+
const baseMarker = `LOCAL_TREE_BASE_${Date.now()}`;
|
|
338
|
+
const futureMarker = `LOCAL_TREE_FUTURE_${Date.now()}`;
|
|
339
|
+
const extensionPath = writeTreeCommandExtension(artifactRoot);
|
|
340
|
+
let originalAgentId;
|
|
341
|
+
let baseAssistantId;
|
|
342
|
+
let baseResumeEntryId;
|
|
343
|
+
console.error(scrubSmokeText(`[local-resume-smoke] artifacts: ${artifactRoot}`));
|
|
344
|
+
try {
|
|
345
|
+
await withRpc({ artifactDir: artifactRoot, sessionDir, sessionId, extraExtensions: [extensionPath] }, async (rpc) => {
|
|
346
|
+
const base = await promptAndRead({
|
|
347
|
+
rpc,
|
|
348
|
+
artifactDir: artifactRoot,
|
|
349
|
+
message: `Remember exact tree base marker ${baseMarker}. Reply exactly TREE_BASE_OK.`,
|
|
350
|
+
timeoutMs,
|
|
351
|
+
seenMetadata,
|
|
352
|
+
});
|
|
353
|
+
assertTurnMetadata("tree base", base, { resumedAgent: false });
|
|
354
|
+
const baseEntries = await getEntries(rpc);
|
|
355
|
+
baseAssistantId = assistantEntryContaining(baseEntries, "TREE_BASE_OK")?.id;
|
|
356
|
+
baseResumeEntryId = resumeEntries(baseEntries).at(-1)?.id;
|
|
357
|
+
if (!baseAssistantId) fail("tree smoke could not find base assistant entry");
|
|
358
|
+
if (!baseResumeEntryId) fail("tree smoke could not find base resume entry");
|
|
359
|
+
|
|
360
|
+
const future = await promptAndRead({
|
|
361
|
+
rpc,
|
|
362
|
+
artifactDir: artifactRoot,
|
|
363
|
+
message: `Remember exact tree future-only marker ${futureMarker}. Reply exactly TREE_FUTURE_OK.`,
|
|
364
|
+
timeoutMs,
|
|
365
|
+
seenMetadata,
|
|
366
|
+
});
|
|
367
|
+
assertTurnMetadata("tree future", future, { resumedAgent: false });
|
|
368
|
+
if (future.metadata.run.agentId !== base.metadata.run.agentId) fail("tree setup did not keep one original local SDK agent", JSON.stringify({ base: base.metadata.run.agentId, future: future.metadata.run.agentId }, null, 2));
|
|
369
|
+
originalAgentId = future.metadata.run.agentId;
|
|
370
|
+
|
|
371
|
+
const question = "On this current earlier tree branch only, what exact LOCAL_TREE_FUTURE marker is visible? Reply exactly MARKER=<marker> only if the full marker is present on this active branch; otherwise reply exactly NO_MARKER. Do not use memory from other branches, environment variables, or files.";
|
|
372
|
+
const assistantTarget = await promptAndRead({
|
|
373
|
+
rpc,
|
|
374
|
+
artifactDir: artifactRoot,
|
|
375
|
+
message: `/local_resume_tree_go ${baseAssistantId} ${question}`,
|
|
376
|
+
timeoutMs,
|
|
377
|
+
seenMetadata,
|
|
378
|
+
});
|
|
379
|
+
assertNotResumedFrom("tree assistant target", assistantTarget, originalAgentId);
|
|
380
|
+
if (assistantTarget.text.includes(futureMarker)) fail("tree assistant target leaked future marker", assistantTarget.text);
|
|
381
|
+
|
|
382
|
+
const resumeEntryTarget = await promptAndRead({
|
|
383
|
+
rpc,
|
|
384
|
+
artifactDir: artifactRoot,
|
|
385
|
+
message: `/local_resume_tree_go ${baseResumeEntryId} ${question}`,
|
|
386
|
+
timeoutMs,
|
|
387
|
+
seenMetadata,
|
|
388
|
+
});
|
|
389
|
+
assertNotResumedFrom("tree resume-entry target", resumeEntryTarget, originalAgentId);
|
|
390
|
+
if (resumeEntryTarget.text.includes(futureMarker)) fail("tree resume-entry target leaked future marker", resumeEntryTarget.text);
|
|
391
|
+
});
|
|
392
|
+
console.log("local-resume-tree-smoke-ok");
|
|
393
|
+
console.error(scrubSmokeText(`[local-resume-smoke] original ${originalAgentId} rejected for tree assistant and resume-entry targets`));
|
|
394
|
+
} finally {
|
|
395
|
+
cleanupArtifactRoot(artifactRoot);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async function runCopySwitchSmoke() {
|
|
400
|
+
const timeoutMs = parseTimeout();
|
|
401
|
+
const { artifactRoot, sessionDir, sessionId, seenMetadata } = createRunContext("pi-cursor-local-resume-copy-switch-smoke-");
|
|
402
|
+
const marker = `LOCAL_COPY_SWITCH_${Date.now()}`;
|
|
403
|
+
let originalAgentId;
|
|
404
|
+
let originalSessionFile;
|
|
405
|
+
console.error(scrubSmokeText(`[local-resume-smoke] artifacts: ${artifactRoot}`));
|
|
406
|
+
try {
|
|
407
|
+
await withRpc({ artifactDir: artifactRoot, sessionDir, sessionId }, async (rpc) => {
|
|
408
|
+
const baseline = await promptAndRead({
|
|
409
|
+
rpc,
|
|
410
|
+
artifactDir: artifactRoot,
|
|
411
|
+
message: `Remember exact copy-switch marker ${marker}. Reply exactly COPY_SWITCH_OK.`,
|
|
412
|
+
timeoutMs,
|
|
413
|
+
seenMetadata,
|
|
414
|
+
});
|
|
415
|
+
assertTurnMetadata("copy-switch baseline", baseline, { resumedAgent: false });
|
|
416
|
+
originalAgentId = baseline.metadata.run.agentId;
|
|
417
|
+
originalSessionFile = (await getState(rpc)).sessionFile;
|
|
418
|
+
if (!originalSessionFile) fail("copy-switch baseline did not persist a session file");
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
const copiedSessionFile = join(dirname(originalSessionFile), `copied-${Date.now()}.jsonl`);
|
|
422
|
+
copyFileSync(originalSessionFile, copiedSessionFile);
|
|
423
|
+
|
|
424
|
+
await withRpc({ artifactDir: artifactRoot, sessionDir, sessionId }, async (rpc) => {
|
|
425
|
+
await rpcData(rpc, "switch_session", { sessionPath: copiedSessionFile }, 120000);
|
|
426
|
+
const entries = await getEntries(rpc);
|
|
427
|
+
if (resumeEntryCount(entries) < 1) fail("copied session did not carry a resume entry to reject", copiedSessionFile);
|
|
428
|
+
const switched = await promptAndRead({
|
|
429
|
+
rpc,
|
|
430
|
+
artifactDir: artifactRoot,
|
|
431
|
+
message: "What exact LOCAL_COPY_SWITCH marker is visible in this copied session? Reply exactly MARKER=<marker> if visible, otherwise NO_MARKER.",
|
|
432
|
+
timeoutMs,
|
|
433
|
+
seenMetadata,
|
|
434
|
+
});
|
|
435
|
+
assertNotResumedFrom("copied session switch", switched, originalAgentId);
|
|
436
|
+
if (!switched.text.includes(`MARKER=${marker}`)) fail("copied session did not bootstrap marker from transcript", JSON.stringify({ expected: `MARKER=${marker}`, actual: switched.text }, null, 2));
|
|
437
|
+
});
|
|
438
|
+
console.log("local-resume-copy-switch-smoke-ok");
|
|
439
|
+
console.error(scrubSmokeText(`[local-resume-smoke] original ${originalAgentId} rejected for copied session switch`));
|
|
440
|
+
} finally {
|
|
441
|
+
cleanupArtifactRoot(artifactRoot);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
async function runFallbackSmoke() {
|
|
446
|
+
const timeoutMs = parseTimeout();
|
|
447
|
+
const { artifactRoot, sessionDir, sessionId, seenMetadata } = createRunContext("pi-cursor-local-resume-fallback-smoke-");
|
|
448
|
+
const marker = `LOCAL_FALLBACK_${Date.now()}`;
|
|
449
|
+
const bogusAgentId = `agent-missing-${Date.now()}`;
|
|
450
|
+
let originalAgentId;
|
|
451
|
+
let sessionFile;
|
|
452
|
+
console.error(scrubSmokeText(`[local-resume-smoke] artifacts: ${artifactRoot}`));
|
|
453
|
+
try {
|
|
454
|
+
await withRpc({ artifactDir: artifactRoot, sessionDir, sessionId }, async (rpc) => {
|
|
455
|
+
const baseline = await promptAndRead({
|
|
456
|
+
rpc,
|
|
457
|
+
artifactDir: artifactRoot,
|
|
458
|
+
message: `Remember exact fallback marker ${marker}. Reply exactly FALLBACK_OK.`,
|
|
459
|
+
timeoutMs,
|
|
460
|
+
seenMetadata,
|
|
461
|
+
});
|
|
462
|
+
assertTurnMetadata("fallback baseline", baseline, { resumedAgent: false });
|
|
463
|
+
originalAgentId = baseline.metadata.run.agentId;
|
|
464
|
+
sessionFile = (await getState(rpc)).sessionFile;
|
|
465
|
+
if (!sessionFile) fail("fallback baseline did not persist a session file");
|
|
466
|
+
if (resumeEntryCount(await getEntries(rpc)) < 1) fail("fallback baseline did not persist a resume handle");
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
rewriteResumeAgentIds(sessionFile, bogusAgentId);
|
|
470
|
+
|
|
471
|
+
await withRpc({ artifactDir: artifactRoot, sessionDir, sessionId }, async (rpc) => {
|
|
472
|
+
const fallback = await promptAndRead({
|
|
473
|
+
rpc,
|
|
474
|
+
artifactDir: artifactRoot,
|
|
475
|
+
message: "What exact LOCAL_FALLBACK marker is visible after the missing local agent fallback? Reply exactly MARKER=<marker> if visible, otherwise NO_MARKER.",
|
|
476
|
+
timeoutMs,
|
|
477
|
+
seenMetadata,
|
|
478
|
+
});
|
|
479
|
+
assertNotResumedFrom("missing-agent fallback", fallback, originalAgentId);
|
|
480
|
+
if (!fallback.text.includes(`MARKER=${marker}`)) fail("missing-agent fallback did not bootstrap marker from transcript", JSON.stringify({ expected: `MARKER=${marker}`, actual: fallback.text }, null, 2));
|
|
481
|
+
const continuityNotice = fallback.events.some((event) =>
|
|
482
|
+
event?.type === "message_update" &&
|
|
483
|
+
event.assistantMessageEvent?.type === "thinking_delta" &&
|
|
484
|
+
typeof event.assistantMessageEvent.delta === "string" &&
|
|
485
|
+
event.assistantMessageEvent.delta.includes("Could not resume prior Cursor agent")
|
|
486
|
+
);
|
|
487
|
+
if (!continuityNotice) fail("missing-agent fallback did not emit resume continuity notice", fallback.metadataPath);
|
|
488
|
+
});
|
|
489
|
+
console.log("local-resume-fallback-smoke-ok");
|
|
490
|
+
console.error(scrubSmokeText(`[local-resume-smoke] missing ${bogusAgentId} fell back from original ${originalAgentId}`));
|
|
491
|
+
} finally {
|
|
492
|
+
cleanupArtifactRoot(artifactRoot);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
async function runCompactionSmoke() {
|
|
497
|
+
const timeoutMs = parseTimeout();
|
|
498
|
+
const { artifactRoot, sessionDir, sessionId, seenMetadata } = createRunContext("pi-cursor-local-resume-compaction-smoke-");
|
|
499
|
+
const marker = `LOCAL_COMPACTION_${Date.now()}`;
|
|
500
|
+
let preCompactionAgentId;
|
|
501
|
+
let postCompactionAgentId;
|
|
502
|
+
console.error(scrubSmokeText(`[local-resume-smoke] artifacts: ${artifactRoot}`));
|
|
503
|
+
mkdirSync(join(artifactRoot, "agent"), { recursive: true });
|
|
504
|
+
writeFileSync(join(artifactRoot, "agent", "settings.json"), JSON.stringify({ compaction: { keepRecentTokens: 1, reserveTokens: 16384 } }, null, 2));
|
|
505
|
+
try {
|
|
506
|
+
await withRpc({ artifactDir: artifactRoot, sessionDir, sessionId }, async (rpc) => {
|
|
507
|
+
const baseline = await promptAndRead({
|
|
508
|
+
rpc,
|
|
509
|
+
artifactDir: artifactRoot,
|
|
510
|
+
message: `Remember exact compaction marker ${marker}. Reply exactly COMPACTION_BASELINE_OK.`,
|
|
511
|
+
timeoutMs,
|
|
512
|
+
seenMetadata,
|
|
513
|
+
});
|
|
514
|
+
assertTurnMetadata("compaction baseline", baseline, { resumedAgent: false });
|
|
515
|
+
preCompactionAgentId = baseline.metadata.run.agentId;
|
|
516
|
+
const result = await rpcData(rpc, "compact", { customInstructions: `Preserve the exact marker ${marker}.` }, timeoutMs);
|
|
517
|
+
if (!result.summary || typeof result.tokensBefore !== "number") fail("manual compaction did not return a summary result", JSON.stringify(result, null, 2));
|
|
518
|
+
const compactedEntries = await getEntries(rpc);
|
|
519
|
+
if (compactionEntryCount(compactedEntries) < 1) fail("manual compaction did not append a compaction entry");
|
|
520
|
+
|
|
521
|
+
const postCompaction = await promptAndRead({
|
|
522
|
+
rpc,
|
|
523
|
+
artifactDir: artifactRoot,
|
|
524
|
+
message: "What exact LOCAL_COMPACTION marker is visible after compaction? Reply exactly MARKER=<marker> if visible, otherwise NO_MARKER.",
|
|
525
|
+
timeoutMs,
|
|
526
|
+
seenMetadata,
|
|
527
|
+
});
|
|
528
|
+
assertNotResumedFrom("post-compaction turn", postCompaction, preCompactionAgentId);
|
|
529
|
+
if (!postCompaction.text.includes(`MARKER=${marker}`)) fail("post-compaction turn did not recall marker", JSON.stringify({ expected: `MARKER=${marker}`, actual: postCompaction.text }, null, 2));
|
|
530
|
+
postCompactionAgentId = postCompaction.metadata.run.agentId;
|
|
531
|
+
const postHandle = latestResumeEntry(await getEntries(rpc));
|
|
532
|
+
if (postHandle?.agentId !== postCompactionAgentId) fail("post-compaction turn did not persist the new agent handle", JSON.stringify({ handle: postHandle?.agentId, run: postCompactionAgentId }, null, 2));
|
|
533
|
+
if (postHandle.compactionGeneration !== 1) fail("post-compaction handle did not record compactionGeneration=1", JSON.stringify(postHandle, null, 2));
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
await withRpc({ artifactDir: artifactRoot, sessionDir, sessionId }, async (rpc) => {
|
|
537
|
+
const restart = await promptAndRead({
|
|
538
|
+
rpc,
|
|
539
|
+
artifactDir: artifactRoot,
|
|
540
|
+
message: "What exact LOCAL_COMPACTION marker is visible after post-compaction restart? Reply exactly MARKER=<marker> if visible, otherwise NO_MARKER.",
|
|
541
|
+
timeoutMs,
|
|
542
|
+
seenMetadata,
|
|
543
|
+
});
|
|
544
|
+
assertTurnMetadata("post-compaction restart", restart, { resumedAgent: true });
|
|
545
|
+
if (restart.metadata.run.agentId !== postCompactionAgentId) fail("post-compaction restart did not resume the post-compaction agent", JSON.stringify({ expected: postCompactionAgentId, actual: restart.metadata.run.agentId }, null, 2));
|
|
546
|
+
});
|
|
547
|
+
console.log("local-resume-compaction-smoke-ok");
|
|
548
|
+
console.error(scrubSmokeText(`[local-resume-smoke] pre-compaction ${preCompactionAgentId} replaced by and resumed post-compaction ${postCompactionAgentId}`));
|
|
549
|
+
} finally {
|
|
550
|
+
cleanupArtifactRoot(artifactRoot);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
async function runDefaultDryRunSmoke() {
|
|
555
|
+
const timeoutMs = parseTimeout();
|
|
556
|
+
const { artifactRoot, sessionDir, sessionId, seenMetadata } = createRunContext("pi-cursor-local-resume-default-dry-run-smoke-");
|
|
557
|
+
const marker = `LOCAL_DEFAULT_DRY_RUN_${Date.now()}`;
|
|
558
|
+
let defaultAgentId;
|
|
559
|
+
console.error(scrubSmokeText(`[local-resume-smoke] artifacts: ${artifactRoot}`));
|
|
560
|
+
try {
|
|
561
|
+
await withRpc({ artifactDir: artifactRoot, sessionDir, sessionId, localResumeEnv: "unset" }, async (rpc) => {
|
|
562
|
+
const baseline = await promptAndRead({
|
|
563
|
+
rpc,
|
|
564
|
+
artifactDir: artifactRoot,
|
|
565
|
+
message: `Remember exact default-dry-run marker ${marker}. Reply exactly DEFAULT_DRY_RUN_OK.`,
|
|
566
|
+
timeoutMs,
|
|
567
|
+
seenMetadata,
|
|
568
|
+
});
|
|
569
|
+
assertTurnMetadata("builtin default baseline", baseline, { resumedAgent: false });
|
|
570
|
+
if (baseline.metadata.providerMeta?.localResume !== true) fail("builtin default baseline did not record localResume=true", baseline.metadataPath);
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
await withRpc({ artifactDir: artifactRoot, sessionDir, sessionId, localResumeEnv: "unset" }, async (rpc) => {
|
|
574
|
+
const defaultRestart = await promptAndRead({
|
|
575
|
+
rpc,
|
|
576
|
+
artifactDir: artifactRoot,
|
|
577
|
+
message: "What exact LOCAL_DEFAULT_DRY_RUN marker did I ask you to remember? Reply exactly MARKER=<marker> if visible, otherwise NO_MARKER.",
|
|
578
|
+
timeoutMs,
|
|
579
|
+
seenMetadata,
|
|
580
|
+
});
|
|
581
|
+
if (!defaultRestart.text.includes(`MARKER=${marker}`)) fail("builtin default restart did not recall marker", JSON.stringify({ expected: `MARKER=${marker}`, actual: defaultRestart.text }, null, 2));
|
|
582
|
+
assertTurnMetadata("builtin default restart", defaultRestart, { resumedAgent: true });
|
|
583
|
+
defaultAgentId = defaultRestart.metadata.run.agentId;
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
await withRpc({
|
|
587
|
+
artifactDir: artifactRoot,
|
|
588
|
+
sessionDir,
|
|
589
|
+
sessionId,
|
|
590
|
+
localResumeEnv: "off",
|
|
591
|
+
}, async (rpc) => {
|
|
592
|
+
const optedOut = await promptAndRead({
|
|
593
|
+
rpc,
|
|
594
|
+
artifactDir: artifactRoot,
|
|
595
|
+
message: "What exact LOCAL_DEFAULT_DRY_RUN marker did I ask you to remember? Reply exactly MARKER=<marker> if visible, otherwise NO_MARKER.",
|
|
596
|
+
timeoutMs,
|
|
597
|
+
seenMetadata,
|
|
598
|
+
});
|
|
599
|
+
if (!optedOut.text.includes(`MARKER=${marker}`)) fail("env opt-out run did not bootstrap marker from transcript", JSON.stringify({ expected: `MARKER=${marker}`, actual: optedOut.text }, null, 2));
|
|
600
|
+
if (optedOut.metadata.providerMeta?.localResume !== false) fail("env opt-out run did not record localResume=false", optedOut.metadataPath);
|
|
601
|
+
if (optedOut.metadata.providerMeta?.resumedAgent !== false) fail("env opt-out run unexpectedly resumed an agent", optedOut.metadataPath);
|
|
602
|
+
if (optedOut.metadata.run?.agentId === defaultAgentId) fail("env opt-out run reused the default-resume agent", JSON.stringify({ configured: defaultAgentId, actual: optedOut.metadata.run?.agentId }, null, 2));
|
|
603
|
+
});
|
|
604
|
+
console.log("local-resume-default-dry-run-smoke-ok");
|
|
605
|
+
console.error(scrubSmokeText(`[local-resume-smoke] built-in default resumed ${defaultAgentId}; env opt-out rejected it`));
|
|
606
|
+
} finally {
|
|
607
|
+
cleanupArtifactRoot(artifactRoot);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const SMOKE_RUNNERS = {
|
|
612
|
+
restart: runSmoke,
|
|
613
|
+
safety: runSafetySmoke,
|
|
614
|
+
toolSurface: runToolSurfaceSmoke,
|
|
615
|
+
abort: runAbortSmoke,
|
|
616
|
+
tree: runTreeSmoke,
|
|
617
|
+
copySwitch: runCopySwitchSmoke,
|
|
618
|
+
fallback: runFallbackSmoke,
|
|
619
|
+
compaction: runCompactionSmoke,
|
|
620
|
+
defaultDryRun: runDefaultDryRunSmoke,
|
|
621
|
+
cleanup: runCleanupSmoke,
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
function selectedRun() {
|
|
625
|
+
const lane = LOCAL_RESUME_SUITES.find((candidate) => candidate.flag && args.has(candidate.flag));
|
|
626
|
+
return SMOKE_RUNNERS[lane?.key ?? "restart"];
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
630
|
+
const run = selectedRun();
|
|
631
|
+
run()
|
|
632
|
+
.catch((error) => {
|
|
633
|
+
reportFailure(error);
|
|
634
|
+
process.exitCode = 1;
|
|
635
|
+
})
|
|
636
|
+
.finally(() => {
|
|
637
|
+
const artifactRoot = process.env.CURSOR_LOCAL_RESUME_SMOKE_ARTIFACT_DIR;
|
|
638
|
+
if (artifactRoot && process.env.CURSOR_LOCAL_RESUME_SMOKE_EMIT_BUNDLE === "1") {
|
|
639
|
+
writePlatformArtifactBundle(artifactRoot, "local-resume-evidence");
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type AnchoredArtifactFile = {
|
|
2
|
+
path: string;
|
|
3
|
+
content: Uint8Array;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export function writeExtractedFilesAnchored(
|
|
7
|
+
outputDir: string,
|
|
8
|
+
files: readonly AnchoredArtifactFile[],
|
|
9
|
+
expectedRoot: { dev: number | bigint; ino: number | bigint },
|
|
10
|
+
): boolean;
|