pi-ast-sgrep 2.0.2 → 2.2.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 +16 -19
- package/dist/code-mode.d.ts +1 -1
- package/dist/code-mode.js +1 -1
- package/dist/codemode/connector.d.ts +18 -3
- package/dist/codemode/connector.js +85 -31
- package/dist/codemode/dispatch.d.ts +13 -1
- package/dist/codemode/dispatch.js +87 -24
- package/dist/codemode/guest-api.d.ts +16 -0
- package/dist/codemode/guest-api.js +194 -0
- package/dist/codemode/guest-worker.mjs +287 -0
- package/dist/codemode/index.d.ts +4 -3
- package/dist/codemode/index.js +4 -3
- package/dist/codemode/native.d.ts +1 -1
- package/dist/codemode/native.js +1 -1
- package/dist/codemode/runner.d.ts +13 -9
- package/dist/codemode/runner.js +411 -213
- package/dist/codemode/session-pool.d.ts +6 -1
- package/dist/codemode/session-pool.js +125 -32
- package/dist/codemode/types.d.ts +42 -2
- package/dist/codemode/types.js +40 -15
- package/dist/codemode/worker.d.ts +1 -1
- package/dist/codemode/worker.js +25 -2
- package/dist/host/commands.d.ts +6 -0
- package/dist/host/commands.js +49 -0
- package/dist/host/results.d.ts +123 -0
- package/dist/host/results.js +126 -0
- package/dist/host/tools.d.ts +28 -0
- package/dist/host/tools.js +802 -0
- package/dist/index.d.ts +7 -34
- package/dist/index.js +5 -543
- package/dist/runtime/config.d.ts +36 -0
- package/dist/runtime/config.js +98 -0
- package/dist/runtime/freshness.d.ts +43 -0
- package/dist/runtime/freshness.js +446 -0
- package/dist/runtime/index-health.d.ts +16 -0
- package/dist/runtime/index-health.js +111 -0
- package/dist/runtime/runtime.d.ts +48 -0
- package/dist/runtime/runtime.js +265 -0
- package/dist/runtime/sqlite.d.ts +15 -0
- package/dist/runtime/sqlite.js +63 -0
- package/dist/runtime/types.d.ts +55 -0
- package/dist/runtime/types.js +25 -0
- package/dist/ui/card.d.ts +66 -0
- package/dist/ui/card.js +375 -0
- package/dist/ui/present.d.ts +89 -0
- package/dist/ui/present.js +391 -0
- package/package.json +8 -7
- package/dist/codemode/sandbox-worker.d.ts +0 -1
- package/dist/codemode/sandbox-worker.js +0 -204
- package/dist/present.d.ts +0 -70
- package/dist/present.js +0 -260
- package/dist/runtime.d.ts +0 -137
- package/dist/runtime.js +0 -799
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/** Guest-call packing so the first shape a model tries actually works. */
|
|
2
|
+
import { CODEMODE_HOST_METHODS } from "./types.js";
|
|
3
|
+
const QUERY_METHODS = new Set([
|
|
4
|
+
"search",
|
|
5
|
+
"find",
|
|
6
|
+
"semantic",
|
|
7
|
+
"chain",
|
|
8
|
+
"catalogSearch",
|
|
9
|
+
]);
|
|
10
|
+
const SYMBOL_METHODS = new Set(["defs", "callers"]);
|
|
11
|
+
/** Catalog / common-misname aliases. Resolved before packing so defs vs search keys stay correct. */
|
|
12
|
+
const METHOD_ALIASES = {
|
|
13
|
+
index_status: "indexStatus",
|
|
14
|
+
index_repo: "indexRepo",
|
|
15
|
+
catalog_search: "catalogSearch",
|
|
16
|
+
catalog_describe: "catalogDescribe",
|
|
17
|
+
define: "defs",
|
|
18
|
+
definition: "defs",
|
|
19
|
+
definitions: "defs",
|
|
20
|
+
grep: "find",
|
|
21
|
+
keyword: "find",
|
|
22
|
+
keywordSearch: "find",
|
|
23
|
+
references: "callers",
|
|
24
|
+
};
|
|
25
|
+
function isPlainObject(value) {
|
|
26
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
27
|
+
}
|
|
28
|
+
function mergeRest(base, rest) {
|
|
29
|
+
if (!isPlainObject(rest))
|
|
30
|
+
return base;
|
|
31
|
+
return { ...rest, ...base };
|
|
32
|
+
}
|
|
33
|
+
function scopeToken(value) {
|
|
34
|
+
if (typeof value !== "string")
|
|
35
|
+
return undefined;
|
|
36
|
+
const path = value.trim();
|
|
37
|
+
if (!path || path.split(/[/\\]/u).includes(".."))
|
|
38
|
+
return undefined;
|
|
39
|
+
return path;
|
|
40
|
+
}
|
|
41
|
+
function editDistance(a, b) {
|
|
42
|
+
const rows = a.length + 1;
|
|
43
|
+
const cols = b.length + 1;
|
|
44
|
+
const prev = new Array(cols);
|
|
45
|
+
const cur = new Array(cols);
|
|
46
|
+
for (let j = 0; j < cols; j++)
|
|
47
|
+
prev[j] = j;
|
|
48
|
+
for (let i = 1; i < rows; i++) {
|
|
49
|
+
cur[0] = i;
|
|
50
|
+
for (let j = 1; j < cols; j++) {
|
|
51
|
+
const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
|
|
52
|
+
cur[j] = Math.min((prev[j] ?? 0) + 1, (cur[j - 1] ?? 0) + 1, (prev[j - 1] ?? 0) + cost);
|
|
53
|
+
}
|
|
54
|
+
for (let j = 0; j < cols; j++)
|
|
55
|
+
prev[j] = cur[j] ?? 0;
|
|
56
|
+
}
|
|
57
|
+
return prev[b.length] ?? a.length;
|
|
58
|
+
}
|
|
59
|
+
function suggestMethod(name) {
|
|
60
|
+
const needle = name.toLowerCase();
|
|
61
|
+
if (!needle)
|
|
62
|
+
return undefined;
|
|
63
|
+
const maxDistance = Math.max(1, Math.floor(needle.length / 3));
|
|
64
|
+
let best;
|
|
65
|
+
for (const candidate of CODEMODE_HOST_METHODS) {
|
|
66
|
+
const lower = candidate.toLowerCase();
|
|
67
|
+
const distance = lower.includes(needle) || needle.includes(lower)
|
|
68
|
+
? Math.min(1, editDistance(needle, lower))
|
|
69
|
+
: editDistance(needle, lower);
|
|
70
|
+
if (distance > maxDistance)
|
|
71
|
+
continue;
|
|
72
|
+
if (!best || distance < best.distance || (distance === best.distance && candidate.length < best.candidate.length)) {
|
|
73
|
+
best = { candidate, distance };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return best?.candidate;
|
|
77
|
+
}
|
|
78
|
+
export function resolveHostMethod(method) {
|
|
79
|
+
if (CODEMODE_HOST_METHODS.includes(method)) {
|
|
80
|
+
return method;
|
|
81
|
+
}
|
|
82
|
+
const aliased = METHOD_ALIASES[method] ?? METHOD_ALIASES[method.toLowerCase()];
|
|
83
|
+
if (aliased)
|
|
84
|
+
return aliased;
|
|
85
|
+
const camel = method.replace(/_([a-z])/gu, (_all, letter) => letter.toUpperCase());
|
|
86
|
+
if (camel !== method && CODEMODE_HOST_METHODS.includes(camel)) {
|
|
87
|
+
return camel;
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
/** Turn positional guest calls into the host object shape. */
|
|
92
|
+
export function packGuestCall(method, args) {
|
|
93
|
+
if (args.length === 0)
|
|
94
|
+
return {};
|
|
95
|
+
const first = args[0];
|
|
96
|
+
const rest = args[1];
|
|
97
|
+
if (typeof first === "string") {
|
|
98
|
+
if (SYMBOL_METHODS.has(method))
|
|
99
|
+
return mergeRest({ symbol: first }, rest);
|
|
100
|
+
if (method === "imports")
|
|
101
|
+
return mergeRest({ module: first }, rest);
|
|
102
|
+
if (method === "read") {
|
|
103
|
+
if (typeof args[1] === "number") {
|
|
104
|
+
const packed = { path: first, start: args[1] };
|
|
105
|
+
if (typeof args[2] === "number")
|
|
106
|
+
packed.end = args[2];
|
|
107
|
+
return packed;
|
|
108
|
+
}
|
|
109
|
+
return mergeRest({ path: first }, rest);
|
|
110
|
+
}
|
|
111
|
+
if (method === "edit" && typeof args[1] === "string") {
|
|
112
|
+
return { path: first, oldText: args[1], newText: typeof args[2] === "string" ? args[2] : "" };
|
|
113
|
+
}
|
|
114
|
+
if (method === "catalogDescribe")
|
|
115
|
+
return mergeRest({ name: first }, rest);
|
|
116
|
+
if (method === "indexRepo" || method === "indexStatus" || method === "doctor") {
|
|
117
|
+
return isPlainObject(rest) ? { ...rest } : {};
|
|
118
|
+
}
|
|
119
|
+
return mergeRest({ query: first }, rest);
|
|
120
|
+
}
|
|
121
|
+
if (isPlainObject(first))
|
|
122
|
+
return { ...first };
|
|
123
|
+
throw new Error(`asgrep.${method}: pass a string or object (got ${typeof first}). Example: asgrep.search("auth") or asgrep.search({ query: "auth" })`);
|
|
124
|
+
}
|
|
125
|
+
/** Accept query as a symbol alias and fold in:/fileFilter into the query string. */
|
|
126
|
+
export function coerceHostArgs(method, input) {
|
|
127
|
+
const args = { ...input };
|
|
128
|
+
if (SYMBOL_METHODS.has(method) && typeof args.symbol !== "string") {
|
|
129
|
+
const alias = args.query;
|
|
130
|
+
if (typeof alias === "string" && alias.trim())
|
|
131
|
+
args.symbol = alias.trim();
|
|
132
|
+
}
|
|
133
|
+
if (QUERY_METHODS.has(method)) {
|
|
134
|
+
const query = typeof args.query === "string" ? args.query : "";
|
|
135
|
+
const scoped = applyQueryScope(query, args);
|
|
136
|
+
if (scoped)
|
|
137
|
+
args.query = scoped;
|
|
138
|
+
}
|
|
139
|
+
return args;
|
|
140
|
+
}
|
|
141
|
+
export function applyQueryScope(query, args) {
|
|
142
|
+
const scope = scopeToken(args.in) ?? scopeToken(args.fileFilter) ?? scopeToken(args.file_filter);
|
|
143
|
+
if (!scope)
|
|
144
|
+
return query.trim() ? query : undefined;
|
|
145
|
+
if (query.split(/\s+/u).some((token) => token.startsWith("in:")))
|
|
146
|
+
return query;
|
|
147
|
+
const trimmed = query.trim();
|
|
148
|
+
return trimmed ? `in:${scope} ${trimmed}` : `in:${scope}`;
|
|
149
|
+
}
|
|
150
|
+
export function unknownMethodError(method) {
|
|
151
|
+
const close = suggestMethod(method);
|
|
152
|
+
const hint = close ? ` Did you mean ${close}?` : "";
|
|
153
|
+
return `unknown asgrep method '${method}'.${hint} Use search, find, defs, callers, read, edit, indexStatus. Example: asgrep.search("auth")`;
|
|
154
|
+
}
|
|
155
|
+
export function timeoutHint(message) {
|
|
156
|
+
if (!/timeout after \d+ms|timed out after \d+ms/i.test(message))
|
|
157
|
+
return message;
|
|
158
|
+
return `${message}; narrow with asgrep.search(query, { in: "src" }), lower limit, or split the program`;
|
|
159
|
+
}
|
|
160
|
+
function looksLikeBareExpression(code) {
|
|
161
|
+
const trimmed = code.trim().replace(/;\s*$/u, "");
|
|
162
|
+
if (!trimmed || /\breturn\b/.test(trimmed))
|
|
163
|
+
return false;
|
|
164
|
+
if (/^(?:const|let|var|function|class|if|for|while|switch|try|async|import|export|throw|do|break|continue|debugger|yield|with|else|case|default)\b/u.test(trimmed))
|
|
165
|
+
return false;
|
|
166
|
+
if (/;\s*\S/u.test(trimmed))
|
|
167
|
+
return false;
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Strip fences and invoke a function expression, including non-async arrows.
|
|
172
|
+
* A single expression with no `return` is returned automatically.
|
|
173
|
+
* Bare statements still wrap in an async IIFE.
|
|
174
|
+
*/
|
|
175
|
+
export function normalizeCode(raw) {
|
|
176
|
+
let code = raw.trim();
|
|
177
|
+
if (code.startsWith("```")) {
|
|
178
|
+
code = code.replace(/^```(?:javascript|js|typescript|ts)?\s*/i, "").replace(/\s*```$/u, "").trim();
|
|
179
|
+
}
|
|
180
|
+
const trimmed = code.replace(/;\s*$/u, "");
|
|
181
|
+
if (/^async\s*(?:function\b|\()/u.test(trimmed)) {
|
|
182
|
+
return `(${trimmed})()`;
|
|
183
|
+
}
|
|
184
|
+
if (/^function\b/u.test(trimmed)) {
|
|
185
|
+
return `(async ${trimmed})()`;
|
|
186
|
+
}
|
|
187
|
+
if (/^(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>/u.test(trimmed)) {
|
|
188
|
+
return `(async ${trimmed})()`;
|
|
189
|
+
}
|
|
190
|
+
if (looksLikeBareExpression(code)) {
|
|
191
|
+
return `(async () => {\nreturn ${trimmed}\n})()`;
|
|
192
|
+
}
|
|
193
|
+
return `(async () => {\n${code}\n})()`;
|
|
194
|
+
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
// Guest worker for asgrep Code Mode. A REAL FILE (not an embedded template
|
|
2
|
+
// string) so it gets syntax checking, real line numbers in stack traces, and
|
|
3
|
+
// normal review. Loaded by runner.ts via new URL("./guest-worker.mjs", ...) and
|
|
4
|
+
// spawned through an eval'd `import()` bootstrap so host execArgv flags never
|
|
5
|
+
// leak into the isolate.
|
|
6
|
+
//
|
|
7
|
+
// Protocol (all messages carry runId; stale-runId frames are dropped):
|
|
8
|
+
// in: { op: "run", runId, code, timeoutMs }
|
|
9
|
+
// { op: "call-result", runId, id, body }
|
|
10
|
+
// out: { op: "call-batch", runId, calls: [{ id, method, payload }] }
|
|
11
|
+
// { op: "log", runId, line }
|
|
12
|
+
// { op: "result", runId, serialized }
|
|
13
|
+
// { op: "error", runId, error }
|
|
14
|
+
import { parentPort } from "node:worker_threads";
|
|
15
|
+
import vm from "node:vm";
|
|
16
|
+
|
|
17
|
+
// In-guest caps are a second fence; the host enforces the same bounds on its
|
|
18
|
+
// side (runner.ts owns the authoritative limits).
|
|
19
|
+
const MAX_RESULT_JSON_CHARS = 1_000_000;
|
|
20
|
+
const MAX_BRIDGE_REQUEST_CHARS = 64_000;
|
|
21
|
+
const MAX_LOG_LINE_CHARS = 4_096;
|
|
22
|
+
const MAX_ERROR_CHARS = 8_192;
|
|
23
|
+
const RESULT_SERIALIZE_TIMEOUT_MS = 1_000;
|
|
24
|
+
|
|
25
|
+
const BLOCKED_GLOBALS = [
|
|
26
|
+
"ArrayBuffer", "SharedArrayBuffer", "DataView", "Atomics", "WebAssembly",
|
|
27
|
+
"eval", "Function", "AsyncFunction", "GeneratorFunction",
|
|
28
|
+
"Int8Array", "Uint8Array", "Uint8ClampedArray", "Int16Array", "Uint16Array",
|
|
29
|
+
"Int32Array", "Uint32Array", "Float32Array", "Float64Array",
|
|
30
|
+
"BigInt64Array", "BigUint64Array",
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
// Mirrors CODEMODE_HOST_METHODS (types.ts); duplicated so this file stays
|
|
34
|
+
// dependency-free plain JS.
|
|
35
|
+
const KNOWN_METHODS = [
|
|
36
|
+
"search", "find", "read", "edit", "semantic", "chain", "defs", "callers",
|
|
37
|
+
"imports", "indexStatus", "indexRepo", "doctor", "catalogSearch", "catalogDescribe",
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
let runId = -1;
|
|
41
|
+
let callSeq = 0;
|
|
42
|
+
const pendingCalls = new Map();
|
|
43
|
+
let outbox = [];
|
|
44
|
+
let outboxScheduled = false;
|
|
45
|
+
|
|
46
|
+
const post = (msg) => {
|
|
47
|
+
try {
|
|
48
|
+
parentPort.postMessage({ ...msg, runId });
|
|
49
|
+
return true;
|
|
50
|
+
} catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Calls issued in one guest microtask burst travel as ONE message so the host
|
|
56
|
+
// dispatcher sees them in a single tick and can coalesce the wave (sticky
|
|
57
|
+
// batch / runBatch). Per-message posts arrive on separate host turns and
|
|
58
|
+
// would silently defeat batching.
|
|
59
|
+
const bridge = (method, payload) => new Promise((resolve, reject) => {
|
|
60
|
+
const id = "c" + callSeq++;
|
|
61
|
+
pendingCalls.set(id, { resolve, reject });
|
|
62
|
+
outbox.push({ id, method, payload });
|
|
63
|
+
if (outboxScheduled) return;
|
|
64
|
+
outboxScheduled = true;
|
|
65
|
+
queueMicrotask(() => {
|
|
66
|
+
const calls = outbox;
|
|
67
|
+
outbox = [];
|
|
68
|
+
outboxScheduled = false;
|
|
69
|
+
if (!post({ op: "call-batch", calls })) {
|
|
70
|
+
for (const call of calls) {
|
|
71
|
+
const pending = pendingCalls.get(call.id);
|
|
72
|
+
if (pending) {
|
|
73
|
+
pendingCalls.delete(call.id);
|
|
74
|
+
pending.resolve(JSON.stringify({ ok: false, error: "codemode call failed" }));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const logSink = (line) => {
|
|
82
|
+
post({ op: "log", line: String(line) });
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// NAPI u64/i64 fields cross the bridge as BigInt; a guest re-passing them must
|
|
86
|
+
// not die on "Do not know how to serialize a BigInt".
|
|
87
|
+
const jsonSafe = (_key, item) =>
|
|
88
|
+
typeof item === "bigint"
|
|
89
|
+
? (item >= -9007199254740991n && item <= 9007199254740991n ? Number(item) : item.toString())
|
|
90
|
+
: item;
|
|
91
|
+
|
|
92
|
+
const stringify = JSON.stringify;
|
|
93
|
+
const stringifyBounded = (value, maxChars, label) => {
|
|
94
|
+
const serialized = stringify(value, jsonSafe);
|
|
95
|
+
if (serialized === undefined) return serialized;
|
|
96
|
+
if (serialized.length > maxChars) {
|
|
97
|
+
throw new Error("codemode " + label + " exceeds " + maxChars + " characters");
|
|
98
|
+
}
|
|
99
|
+
return serialized;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const sealCtor = (obj) => {
|
|
103
|
+
if (obj === null || obj === undefined) return;
|
|
104
|
+
try {
|
|
105
|
+
Object.defineProperty(obj, "constructor", { value: undefined, configurable: false, writable: false });
|
|
106
|
+
} catch {}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Everything below runs INSIDE the vm context once, installing the sandbox
|
|
110
|
+
// surface: blocked globals, sealed constructors, the asgrep proxy, console.
|
|
111
|
+
function installSandbox(contextObject) {
|
|
112
|
+
const context = vm.createContext(contextObject, { codeGeneration: { strings: false, wasm: false } });
|
|
113
|
+
const install = new vm.Script(INSTALL_SOURCE, { filename: "asgrep-codemode-bootstrap.js" });
|
|
114
|
+
return { context, install };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const INSTALL_SOURCE = `
|
|
118
|
+
{
|
|
119
|
+
const hostCall = globalThis.__asgrepBridge;
|
|
120
|
+
const hostLog = globalThis.__asgrepLog;
|
|
121
|
+
delete globalThis.__asgrepBridge;
|
|
122
|
+
delete globalThis.__asgrepLog;
|
|
123
|
+
|
|
124
|
+
for (const name of ${JSON.stringify(BLOCKED_GLOBALS)}) {
|
|
125
|
+
Object.defineProperty(globalThis, name, {
|
|
126
|
+
value: undefined, configurable: false, writable: false,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const sealCtor = (obj) => {
|
|
131
|
+
if (obj === null || obj === undefined) return;
|
|
132
|
+
try {
|
|
133
|
+
Object.defineProperty(obj, "constructor", {
|
|
134
|
+
value: undefined, configurable: false, writable: false,
|
|
135
|
+
});
|
|
136
|
+
} catch {}
|
|
137
|
+
};
|
|
138
|
+
sealCtor(globalThis);
|
|
139
|
+
sealCtor(Object); sealCtor(Object.prototype);
|
|
140
|
+
sealCtor(Array); sealCtor(Array.prototype);
|
|
141
|
+
sealCtor(Number); sealCtor(Number.prototype);
|
|
142
|
+
sealCtor(String); sealCtor(String.prototype);
|
|
143
|
+
sealCtor(Boolean); sealCtor(Boolean.prototype);
|
|
144
|
+
sealCtor(Error); sealCtor(Error.prototype);
|
|
145
|
+
sealCtor(RegExp); sealCtor(RegExp.prototype);
|
|
146
|
+
sealCtor(Date); sealCtor(Date.prototype);
|
|
147
|
+
sealCtor(Promise); sealCtor(Promise.prototype);
|
|
148
|
+
sealCtor(JSON); sealCtor(Math);
|
|
149
|
+
sealCtor(Reflect); sealCtor(Proxy); sealCtor(Symbol);
|
|
150
|
+
sealCtor(Map); sealCtor(Set); sealCtor(WeakMap); sealCtor(WeakSet);
|
|
151
|
+
sealCtor(hostCall); sealCtor(hostLog);
|
|
152
|
+
|
|
153
|
+
let resultValue;
|
|
154
|
+
const setResult = (value) => { resultValue = value; };
|
|
155
|
+
const stringify = JSON.stringify;
|
|
156
|
+
const jsonSafe = (_key, item) => typeof item === "bigint"
|
|
157
|
+
? (item >= -9007199254740991n && item <= 9007199254740991n ? Number(item) : item.toString())
|
|
158
|
+
: item;
|
|
159
|
+
const stringifyBounded = (value, maxChars, label) => {
|
|
160
|
+
const serialized = stringify(value, jsonSafe);
|
|
161
|
+
if (serialized === undefined) return serialized;
|
|
162
|
+
if (serialized.length > maxChars) {
|
|
163
|
+
throw new Error("codemode " + label + " exceeds " + maxChars + " characters");
|
|
164
|
+
}
|
|
165
|
+
return serialized;
|
|
166
|
+
};
|
|
167
|
+
const serializeResult = () => stringifyBounded(resultValue, ${MAX_RESULT_JSON_CHARS}, "result");
|
|
168
|
+
Object.freeze(setResult);
|
|
169
|
+
Object.freeze(serializeResult);
|
|
170
|
+
Object.defineProperty(globalThis, "__asgrepSetResult", {
|
|
171
|
+
value: setResult, configurable: false, writable: false,
|
|
172
|
+
});
|
|
173
|
+
Object.defineProperty(globalThis, "__asgrepSerializeResult", {
|
|
174
|
+
value: serializeResult, configurable: false, writable: false,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const invoke = async (method, args = {}) => {
|
|
178
|
+
const payload = stringifyBounded(args, ${MAX_BRIDGE_REQUEST_CHARS}, "call arguments");
|
|
179
|
+
const response = JSON.parse(await hostCall(method, payload));
|
|
180
|
+
if (!response.ok) throw new Error(response.error || ("asgrep." + method + " failed"));
|
|
181
|
+
return response.value;
|
|
182
|
+
};
|
|
183
|
+
const known = ${JSON.stringify(KNOWN_METHODS)};
|
|
184
|
+
const blocked = new Set(["then", "constructor", "prototype", "__proto__"]);
|
|
185
|
+
const call = (method) => (...guestArgs) => invoke(method, { __guestArgs: guestArgs });
|
|
186
|
+
const api = new Proxy(Object.create(null), {
|
|
187
|
+
get(_target, prop) {
|
|
188
|
+
if (typeof prop !== "string" || blocked.has(prop)) return undefined;
|
|
189
|
+
return call(prop);
|
|
190
|
+
},
|
|
191
|
+
has(_target, prop) {
|
|
192
|
+
return typeof prop === "string" && !blocked.has(prop);
|
|
193
|
+
},
|
|
194
|
+
ownKeys() { return known.slice(); },
|
|
195
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
196
|
+
if (typeof prop !== "string" || blocked.has(prop)) return undefined;
|
|
197
|
+
return { enumerable: known.includes(prop), configurable: true, value: call(prop) };
|
|
198
|
+
},
|
|
199
|
+
set() { return false; },
|
|
200
|
+
defineProperty() { return false; },
|
|
201
|
+
deleteProperty() { return false; },
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const formatLog = (value) => {
|
|
205
|
+
if (typeof value === "string") return value.slice(0, ${MAX_LOG_LINE_CHARS});
|
|
206
|
+
try { return stringifyBounded(value, ${MAX_LOG_LINE_CHARS}, "log line"); }
|
|
207
|
+
catch { return "[unserializable or oversized log value]"; }
|
|
208
|
+
};
|
|
209
|
+
const consoleApi = Object.create(null);
|
|
210
|
+
for (const level of ["log", "info", "warn", "error", "debug"]) {
|
|
211
|
+
Object.defineProperty(consoleApi, level, {
|
|
212
|
+
enumerable: true,
|
|
213
|
+
value: (...args) => {
|
|
214
|
+
let line = "";
|
|
215
|
+
for (const arg of args) {
|
|
216
|
+
const part = formatLog(arg);
|
|
217
|
+
const prefix = line.length === 0 ? "" : " ";
|
|
218
|
+
const remaining = ${MAX_LOG_LINE_CHARS} - line.length;
|
|
219
|
+
if (remaining <= 0) break;
|
|
220
|
+
line += (prefix + part).slice(0, remaining);
|
|
221
|
+
}
|
|
222
|
+
hostLog(line);
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
Object.freeze(consoleApi);
|
|
227
|
+
|
|
228
|
+
Object.defineProperty(globalThis, "asgrep", { value: api, configurable: false, writable: false });
|
|
229
|
+
Object.defineProperty(globalThis, "console", { value: consoleApi, configurable: false, writable: false });
|
|
230
|
+
sealCtor(api); sealCtor(consoleApi); sealCtor(setResult); sealCtor(serializeResult); sealCtor(invoke);
|
|
231
|
+
}
|
|
232
|
+
`;
|
|
233
|
+
|
|
234
|
+
async function runProgram(msg) {
|
|
235
|
+
const contextObject = Object.create(null);
|
|
236
|
+
Object.defineProperty(bridge, "constructor", { value: undefined });
|
|
237
|
+
Object.defineProperty(logSink, "constructor", { value: undefined });
|
|
238
|
+
contextObject.__asgrepBridge = bridge;
|
|
239
|
+
contextObject.__asgrepLog = logSink;
|
|
240
|
+
const { context, install } = installSandbox(contextObject);
|
|
241
|
+
try {
|
|
242
|
+
install.runInContext(context, { timeout: Math.min(msg.timeoutMs, 1000) });
|
|
243
|
+
const script = new vm.Script(msg.code, { filename: "asgrep-codemode.js" });
|
|
244
|
+
const value = await script.runInContext(context, {
|
|
245
|
+
displayErrors: true,
|
|
246
|
+
timeout: msg.timeoutMs,
|
|
247
|
+
});
|
|
248
|
+
const setResult = context.__asgrepSetResult;
|
|
249
|
+
if (typeof setResult !== "function") {
|
|
250
|
+
throw new Error("codemode result bridge is unavailable");
|
|
251
|
+
}
|
|
252
|
+
setResult(value);
|
|
253
|
+
const serializeScript = new vm.Script("globalThis.__asgrepSerializeResult()", {
|
|
254
|
+
filename: "asgrep-codemode-result.js",
|
|
255
|
+
});
|
|
256
|
+
const serialized = serializeScript.runInContext(context, {
|
|
257
|
+
displayErrors: true,
|
|
258
|
+
timeout: Math.min(msg.timeoutMs, RESULT_SERIALIZE_TIMEOUT_MS),
|
|
259
|
+
});
|
|
260
|
+
post({ op: "result", serialized });
|
|
261
|
+
} catch (cause) {
|
|
262
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
263
|
+
post({ op: "error", error: message.slice(0, MAX_ERROR_CHARS) });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
parentPort.on("message", (msg) => {
|
|
268
|
+
if (!msg || typeof msg !== "object") return;
|
|
269
|
+
// "run" claims the runId; every later frame must carry it or be dropped.
|
|
270
|
+
if (msg.op === "run") {
|
|
271
|
+
if (runId !== -1) return;
|
|
272
|
+
runId = msg.runId;
|
|
273
|
+
void runProgram(msg);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (typeof msg.runId === "number" && msg.runId !== runId) return;
|
|
277
|
+
if (msg.op === "call-result") {
|
|
278
|
+
const pending = pendingCalls.get(msg.id);
|
|
279
|
+
if (pending) {
|
|
280
|
+
pendingCalls.delete(msg.id);
|
|
281
|
+
pending.resolve(msg.body);
|
|
282
|
+
}
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
post({ op: "ready" });
|
package/dist/codemode/index.d.ts
CHANGED
|
@@ -10,9 +10,10 @@
|
|
|
10
10
|
* client. They never import each other. Do not install both for the same agent.
|
|
11
11
|
*/
|
|
12
12
|
export { createAsgrepConnector, type AsgrepConnector, type ConnectorHost, type DispatchSurface, type ConnectorBundle, } from "./connector.js";
|
|
13
|
-
export { runCodemode, normalizeCode, type CodemodeRunResult, type CodemodeRunSuccess, type CodemodeRunFailure } from "./runner.js";
|
|
14
|
-
export {
|
|
13
|
+
export { runCodemode, normalizeCode, warmCodemodeSandbox, resetCodemodeSandboxForTests, type CodemodeRunResult, type CodemodeRunSuccess, type CodemodeRunFailure } from "./runner.js";
|
|
14
|
+
export { applyQueryScope, packGuestCall, coerceHostArgs, resolveHostMethod } from "./guest-api.js";
|
|
15
|
+
export { CODEMODE_TYPES_FOR_MODEL, CODEMODE_HOST_METHODS, type SearchArgs, type FindArgs, type ReadArgs, type EditArgs, type ChainArgs, type CodemodeHostMethod } from "./types.js";
|
|
15
16
|
export { createCodemodeDispatcher, runNativeBatch, argvFor, asEnvelope, type DispatchStats, type BatchCapableHost, type StickyWorker, type BatchResult, } from "./dispatch.js";
|
|
16
17
|
export { startStickyWorker, runBatchViaStdin } from "./worker.js";
|
|
17
|
-
export { NativeSessionPool, sharedNativePool } from "./session-pool.js";
|
|
18
|
+
export { NativeSessionPool, sharedNativePool, isClosedWorkerError } from "./session-pool.js";
|
|
18
19
|
export { loadCodemodeNative, nativeAvailable, resetNativeCache, type CodemodeNativeBinding, type NativeSession, } from "./native.js";
|
package/dist/codemode/index.js
CHANGED
|
@@ -10,9 +10,10 @@
|
|
|
10
10
|
* client. They never import each other. Do not install both for the same agent.
|
|
11
11
|
*/
|
|
12
12
|
export { createAsgrepConnector, } from "./connector.js";
|
|
13
|
-
export { runCodemode, normalizeCode } from "./runner.js";
|
|
14
|
-
export {
|
|
13
|
+
export { runCodemode, normalizeCode, warmCodemodeSandbox, resetCodemodeSandboxForTests } from "./runner.js";
|
|
14
|
+
export { applyQueryScope, packGuestCall, coerceHostArgs, resolveHostMethod } from "./guest-api.js";
|
|
15
|
+
export { CODEMODE_TYPES_FOR_MODEL, CODEMODE_HOST_METHODS } from "./types.js";
|
|
15
16
|
export { createCodemodeDispatcher, runNativeBatch, argvFor, asEnvelope, } from "./dispatch.js";
|
|
16
17
|
export { startStickyWorker, runBatchViaStdin } from "./worker.js";
|
|
17
|
-
export { NativeSessionPool, sharedNativePool } from "./session-pool.js";
|
|
18
|
+
export { NativeSessionPool, sharedNativePool, isClosedWorkerError } from "./session-pool.js";
|
|
18
19
|
export { loadCodemodeNative, nativeAvailable, resetNativeCache, } from "./native.js";
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* 2. `@ast-sgrep/<platform>/ast-sgrep-codemode.node` via launcher (release install)
|
|
10
10
|
* 3. Local `extension/native/` / cargo `target/release` (dev builds)
|
|
11
11
|
*/
|
|
12
|
-
export declare const CODEMODE_BINDING_VERSION = "2.
|
|
12
|
+
export declare const CODEMODE_BINDING_VERSION = "2.1.0";
|
|
13
13
|
export type NativeSessionConfig = {
|
|
14
14
|
root?: string;
|
|
15
15
|
indexPath?: string;
|
package/dist/codemode/native.js
CHANGED
|
@@ -13,7 +13,7 @@ import { createRequire } from "node:module";
|
|
|
13
13
|
import { existsSync } from "node:fs";
|
|
14
14
|
import { dirname, join } from "node:path";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
|
-
export const CODEMODE_BINDING_VERSION = "2.
|
|
16
|
+
export const CODEMODE_BINDING_VERSION = "2.1.0";
|
|
17
17
|
let cached;
|
|
18
18
|
function platformTriple() {
|
|
19
19
|
const { platform, arch } = process;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { AsgrepConnector } from "./connector.js";
|
|
2
2
|
import type { DispatchStats } from "./dispatch.js";
|
|
3
|
-
|
|
3
|
+
import { normalizeCode } from "./guest-api.js";
|
|
4
|
+
export { normalizeCode };
|
|
5
|
+
/** Closed sum: success|failure — ok:true with error (or ok:false without) is unrepresentable. */
|
|
4
6
|
export type CodemodeRunSuccess = {
|
|
5
7
|
ok: true;
|
|
6
8
|
result: unknown;
|
|
@@ -19,16 +21,18 @@ export type CodemodeRunFailure = {
|
|
|
19
21
|
wallMs: number;
|
|
20
22
|
};
|
|
21
23
|
export type CodemodeRunResult = CodemodeRunSuccess | CodemodeRunFailure;
|
|
22
|
-
/**
|
|
23
|
-
export declare function
|
|
24
|
+
/** Spawn the warm standby isolate (session_start / pre-call). */
|
|
25
|
+
export declare function warmCodemodeSandbox(): Promise<void>;
|
|
26
|
+
/** Drop the standby isolate and any in-flight runs (tests / session shutdown). */
|
|
27
|
+
export declare function resetCodemodeSandboxForTests(): Promise<void>;
|
|
24
28
|
/**
|
|
25
|
-
* Run model-generated JavaScript against the typed
|
|
29
|
+
* Run model-generated JavaScript against the typed asgrep connector.
|
|
26
30
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
31
|
+
* Execution happens in a single-use worker_threads isolate: the guest gets a
|
|
32
|
+
* node:vm context inside the worker; asgrep/console are built there; the only
|
|
33
|
+
* host channel is a JSON postMessage bridge carrying runId envelopes. Timeout
|
|
34
|
+
* and abort call worker.terminate(), which is the only mechanism that actually
|
|
35
|
+
* stops a detached guest microtask or a runaway heap.
|
|
32
36
|
*/
|
|
33
37
|
export declare function runCodemode(rawCode: string, asgrep: AsgrepConnector, options?: {
|
|
34
38
|
timeoutMs?: number;
|