Files
PaperClipAI/packages/paperclip-runner/scripts/qualify-cursor-mode.mjs

64 lines
5.0 KiB
JavaScript

import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { readFile, writeFile } from "node:fs/promises";
import { pathToFileURL } from "node:url";
import { runInNewContext } from "node:vm";
const PINNED_ARM_CHUNK = "c01657c111f65153d7a40a923f01a5a86d393d1cd6893eaa03f71b9604d2af0c";
const hash = source => createHash("sha256").update(source).digest("hex");
function segment(source, start, end) {
const first = source.indexOf(start); assert.ok(first >= 0, `Missing native method ${start}`);
const last = source.indexOf(end, first + start.length); assert.ok(last > first, `Missing method boundary ${end}`);
return source.slice(first, last);
}
const awaiter = (self, args, _promise, fn) => {
const iterator = fn.apply(self, args ?? []);
const next = value => { const result = iterator.next(value); return result.done ? Promise.resolve(result.value) : Promise.resolve(result.value).then(next); };
return next();
};
/** Executes the pinned native mode methods with dependency doubles. No network,
* model call, or claim that a selected mode exposes a particular tool. */
export async function qualifyCursorMode(source) {
assert.equal(hash(source), PINNED_ARM_CHUNK, "Native source identity changed");
const toAcp = mode => ({ default: "agent", plan: "plan", search: "ask" })[mode];
const scope = { S: awaiter, T: { ACP_MODE_CONFIG_ID: "mode" }, p: { mb: toAcp }, _: { mb: toAcp }, i: { GI: { invalidParams: value => new Error(value.message) } }, w: { debugLog() {} } };
const method = (start, end, name) => runInNewContext(`({${segment(source, start, end)}}).${name}`, scope);
const setConfig = method("setSessionConfigOption(e)", "unstable_setSessionModel(e)", "setSessionConfigOption");
const mapMode = method("mapAcpModeToCliMode(e)", "clientSupportsParameterizedModelPicker()", "mapAcpModeToCliMode");
const modes = method("buildModesStateFromCliMode(e)", "buildModesState(e)", "buildModesStateFromCliMode");
const configs = method("buildConfigOptions(e,t)", "buildModelsState(e)", "buildConfigOptions");
const setMode = method("setMode(e){", "getCliMode(){", "setMode");
const getCliMode = method("getCliMode(){", "sendCurrentModeUpdate(e)", "getCliMode");
const rows = [];
for (const selected of ["agent", "plan", "ask"]) {
const metadata = new Map([["mode", "default"]]); const updates = [];
const store = { getMetadata: key => metadata.get(key), setMetadata: (key, value) => metadata.set(key, value) };
const makeSession = () => ({ agentStore: store, setMode, getCliMode, sendCurrentModeUpdate: async value => updates.push(value) });
const session = makeSession();
const host = { sessions: new Map([["native", { session }]]), sharedServices: {}, mapAcpModeToCliMode: mapMode, buildModesStateFromCliMode: modes, buildConfigOptions: configs, getModelPickerMode: () => "variants", getAcpAvailableModels: async () => [], buildVariantModelConfigOptions: async () => [] };
const response = await setConfig.call(host, { sessionId: "native", configId: "mode", value: selected });
assert.equal(response.configOptions[0].currentValue, selected);
assert.equal(updates.at(-1), selected);
// A reconstructed native session reads the same admitted metadata. Actual
// database durability is deliberately outside this dependency-double proof.
const loaded = makeSession();
assert.equal((await configs.call(host, "native", loaded))[0].currentValue, selected);
class Data { constructor(value) { Object.assign(this, value); } }
const promptPart = segment(source, 'r=null!==(o=this.agentStore.getMetadata("mode"))', ',u=new a.h');
const action = runInNewContext(`(function(){let o;const ${promptPart};return d;})`, { _: { cT: value => ({ nativeModeInput: value }) }, c: { UserMessageAction: Data, UserMessage: Data }, N: "offline fixture", n: {}, crypto: { randomUUID: () => "fixture" } }).call(loaded);
assert.equal(action.userMessage.mode.nativeModeInput, metadata.get("mode"));
rows.push({ selected, nativeMetadata: metadata.get("mode"), configAcknowledgement: selected, currentModeNotification: selected, reconstructedSessionAcknowledgement: selected, userMessageModeInput: action.userMessage.mode.nativeModeInput });
}
const planHandler = segment(source, '"./src/acp/interaction-handlers/create-plan-handler.ts"(e,t,n)', '},"./src/acp/resource-link-security.ts"');
assert.doesNotMatch(planHandler, /setMode\(|setMetadata\("mode"/);
return { schema: "paperclip.cursor.native-mode-offline-proof.v1", sourceSha256: hash(source), rows, planHandlerHasModeMutation: false, boundary: "pinned native config/session/UserMessage methods; storage, mode enum converter and transport doubled; no inference" };
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
const [sourcePath, outputPath] = process.argv.slice(2);
if (!sourcePath || !outputPath) throw new Error("Usage: node qualify-cursor-mode.mjs PINNED_ARM_CHUNK OUTPUT_JSON");
const proof = await qualifyCursorMode(await readFile(sourcePath, "utf8"));
await writeFile(outputPath, `${JSON.stringify(proof, null, 2)}\n`);
}