@code-yeongyu/senpi-codemode 2026.10.9 → 2026.10.10-3
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 +54 -0
- package/README.md +131 -5
- package/package.json +6 -5
- package/src/bridge/protocol.ts +8 -3
- package/src/config/executable-settings.json +5 -0
- package/src/config/project-trust.ts +51 -0
- package/src/config/settings-overrides.ts +2 -0
- package/src/environments/js-environments.ts +208 -0
- package/src/environments/js-installer.ts +268 -0
- package/src/environments/js-revision-files.ts +91 -0
- package/src/environments/no-links.ts +132 -0
- package/src/environments/project-resolves.ts +54 -0
- package/src/environments/revision-store.ts +20 -4
- package/src/extension/kernel-registration.ts +7 -2
- package/src/extension/runtime-factory.ts +40 -3
- package/src/extension/runtime-info.ts +6 -2
- package/src/extension/session-manager.ts +16 -2
- package/src/index.ts +1 -0
- package/src/interpreters/detect.ts +34 -1
- package/src/kernels/AGENTS.md +2 -0
- package/src/kernels/js/cell-dispatch.ts +44 -0
- package/src/kernels/js/context-manager.ts +46 -51
- package/src/kernels/js/control-frames.ts +24 -0
- package/src/kernels/js/host-entries.ts +62 -0
- package/src/kernels/js/kernel-contract.ts +16 -1
- package/src/kernels/js/kernel-memory-bridge.ts +1 -1
- package/src/kernels/js/kernel-memory.ts +79 -0
- package/src/kernels/js/kernel-tools-registry.js +1 -0
- package/src/kernels/js/local-module-loader.ts +17 -6
- package/src/kernels/js/process-drain-scan.d.ts +8 -0
- package/src/kernels/js/process-drain-scan.js +28 -0
- package/src/kernels/js/process-entry.js +462 -0
- package/src/kernels/js/process-memory-host.ts +83 -0
- package/src/kernels/js/process-worker.ts +298 -0
- package/src/kernels/js/run-queue.ts +7 -0
- package/src/kernels/js/worker-core.js +7 -2
- package/src/kernels/js/worker-cwd.js +5 -2
- package/src/kernels/js/worker-host.ts +9 -2
- package/src/kernels/js/worker-package-resolve.js +153 -0
- package/src/kernels/js/worker-runtime.js +1 -0
- package/src/kernels/js/worker-slot.ts +9 -0
- package/src/kernels/js/worker-startup.ts +49 -1
- package/src/kernels/js/worker-webview.js +8 -0
- package/src/kernels/py/host-cell.ts +8 -31
- package/src/kernels/py/prelude.py +10 -2
- package/src/kernels/py/process.ts +4 -0
- package/src/kernels/sandbox/sandbox-cell.ts +116 -0
- package/src/kernels/sandbox/vendor/pi-codemode/VENDORED.md +1 -0
- package/src/kernels/sandbox/vendor/pi-codemode/runtime/prelude-source.ts +14 -1
- package/src/kernels/sandbox/vendor/pi-codemode/runtime/worker.ts +4 -1
- package/src/kernels/sandbox/vendor/pi-codemode/types.ts +2 -0
- package/src/kernels/shared/host-cell.ts +57 -0
- package/src/kernels/shared/subprocess-process.ts +14 -0
- package/src/prompt/eval-prompt.ts +8 -1
- package/src/tool/AGENTS.md +1 -0
- package/src/tool/cell-handler.ts +9 -11
- package/src/tool/eval-request.ts +23 -1
- package/src/tool/eval-tool-options.ts +3 -0
- package/src/tool/eval-tool.ts +17 -4
- package/src/tool/load-cell.ts +18 -3
- package/src/tool/magic-cell-host.ts +60 -2
- package/src/tool/magic-cells.ts +12 -2
- package/src/tool/run-eval-cell.ts +30 -3
- package/src/tool/runtime-label.ts +2 -1
- package/src/tool/types.ts +23 -1
package/src/tool/eval-tool.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AgentToolResult, ToolDefinition } from "@code-yeongyu/senpi";
|
|
2
2
|
import type { TUnsafe } from "typebox";
|
|
3
|
+
import { resolveAdvertiseHelpers, resolveSandbox } from "../config/feature-settings.ts";
|
|
3
4
|
import {
|
|
4
5
|
DEFAULT_FOREGROUND_WINDOW_SECONDS,
|
|
5
6
|
defaultCodemodeSettings,
|
|
@@ -8,7 +9,12 @@ import {
|
|
|
8
9
|
import { buildEvalPrompt } from "../prompt/eval-prompt.ts";
|
|
9
10
|
import { EvalDetachedCellManager } from "./detached-cell-manager.ts";
|
|
10
11
|
import { executeEvalControl } from "./detached-eval-result.ts";
|
|
11
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
EvalIsolateInvalidError,
|
|
14
|
+
isEvalControlRequest,
|
|
15
|
+
normalizeEvalSummary,
|
|
16
|
+
parseEvalRequest,
|
|
17
|
+
} from "./eval-request.ts";
|
|
12
18
|
import type { CreateEvalToolOptions } from "./eval-tool-options.ts";
|
|
13
19
|
import { runEvalCell } from "./run-eval-cell.ts";
|
|
14
20
|
import {
|
|
@@ -43,9 +49,11 @@ export function createEvalTool(options: CreateEvalToolOptions) {
|
|
|
43
49
|
foregroundWindowSeconds,
|
|
44
50
|
hardLimitSeconds: options.hardLimitSeconds ?? defaultEvalDeadlineSeconds.hardLimitSeconds,
|
|
45
51
|
};
|
|
46
|
-
const
|
|
52
|
+
const sandbox = resolveSandbox(options.settings ?? defaultCodemodeSettings).enabled;
|
|
53
|
+
const parameters = createEvalInputSchema(options.enabledLanguages, deadlines, { sandbox });
|
|
47
54
|
const prompt = buildEvalPrompt(options.enabledLanguages, {
|
|
48
55
|
spawns: options.spawns ?? false,
|
|
56
|
+
...(resolveAdvertiseHelpers(options.settings ?? defaultCodemodeSettings) ? { advertiseHelpers: true } : {}),
|
|
49
57
|
monitor: options.monitor,
|
|
50
58
|
maxDetachedCells,
|
|
51
59
|
runBudgetSeconds: deadlines.runBudgetSeconds,
|
|
@@ -74,9 +82,14 @@ export function createEvalTool(options: CreateEvalToolOptions) {
|
|
|
74
82
|
async function execute(
|
|
75
83
|
...[toolCallId, params, signal, onUpdate, ctx]: EvalExecuteArgs<EvalToolRequest>
|
|
76
84
|
): Promise<AgentToolResult<EvalResultDetails>> {
|
|
77
|
-
const request = parseEvalRequest(params, languages);
|
|
85
|
+
const request = parseEvalRequest(params, languages, { sandbox });
|
|
78
86
|
if (isEvalControlRequest(request)) return await executeEvalControl(cellManager, request);
|
|
79
|
-
if (options.proxyExecutor)
|
|
87
|
+
if (options.proxyExecutor) {
|
|
88
|
+
// A proxy runs cells elsewhere and knows nothing of sandbox cells: an isolated cell must never run unisolated.
|
|
89
|
+
if ("isolate" in request && request.isolate === true)
|
|
90
|
+
throw new EvalIsolateInvalidError("isolate: true is not available through this eval proxy");
|
|
91
|
+
return await options.proxyExecutor(request, signal);
|
|
92
|
+
}
|
|
80
93
|
if (!languages.includes(request.language))
|
|
81
94
|
throw new RangeError(
|
|
82
95
|
`Unsupported eval language "${request.language}". Enabled languages: ${languages.join(", ")}`,
|
package/src/tool/load-cell.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { closeSync, constants, fstatSync, openSync,
|
|
1
|
+
import { closeSync, constants, fstatSync, openSync, readSync } from "node:fs";
|
|
2
2
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
|
|
@@ -50,6 +50,18 @@ function resolveTarget(target: string, options: LoadCellOptions): { path: string
|
|
|
50
50
|
* Reads a `%load` target when its cell's turn comes in the kernel's queue, so a file the previous cell wrote is
|
|
51
51
|
* there. Every failure is a refusal that names the target as the user wrote it, never an absolute path.
|
|
52
52
|
*/
|
|
53
|
+
/** Reads from `fd` until EOF or `limit` bytes, whichever comes first; a file still growing cannot exceed it. */
|
|
54
|
+
export function readAtMost(fd: number, limit: number): Buffer {
|
|
55
|
+
const buffer = Buffer.alloc(limit);
|
|
56
|
+
let filled = 0;
|
|
57
|
+
while (filled < limit) {
|
|
58
|
+
const read = readSync(fd, buffer, filled, limit - filled, null);
|
|
59
|
+
if (read === 0) break;
|
|
60
|
+
filled += read;
|
|
61
|
+
}
|
|
62
|
+
return buffer.subarray(0, filled);
|
|
63
|
+
}
|
|
64
|
+
|
|
53
65
|
export function loadCell(target: string, options: LoadCellOptions): LoadedCell {
|
|
54
66
|
const resolved = resolveTarget(target, options);
|
|
55
67
|
if ("message" in resolved) return { ok: false, message: resolved.message };
|
|
@@ -60,8 +72,11 @@ export function loadCell(target: string, options: LoadCellOptions): LoadedCell {
|
|
|
60
72
|
fd = openSync(resolved.path, constants.O_RDONLY | constants.O_NONBLOCK);
|
|
61
73
|
const info = fstatSync(fd);
|
|
62
74
|
if (!info.isFile()) return { ok: false, message: `not a file: ${target}` };
|
|
63
|
-
|
|
64
|
-
|
|
75
|
+
const tooLarge = { ok: false as const, message: `%load reads files up to 8 MiB: ${target}` };
|
|
76
|
+
if (info.size > MAX_LOAD_BYTES) return tooLarge;
|
|
77
|
+
const bytes = readAtMost(fd, MAX_LOAD_BYTES + 1);
|
|
78
|
+
if (bytes.length > MAX_LOAD_BYTES) return tooLarge;
|
|
79
|
+
return { ok: true, code: bytes.toString("utf8"), sourceFile: resolved.path };
|
|
65
80
|
} catch (error) {
|
|
66
81
|
const code = errorCode(error);
|
|
67
82
|
if (code === "ENOENT" || code === "ENOTDIR") return { ok: false, message: `file not found: ${target}` };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { JsEnvironments } from "../environments/js-environments.ts";
|
|
1
2
|
import type { PythonEnvironments } from "../environments/python-environments.ts";
|
|
2
3
|
import { TIMEOUT_PAUSE_OP, TIMEOUT_RESUME_OP } from "../timeouts/bridge-timeout.ts";
|
|
3
4
|
import { MagicCellError, parseMagicCell } from "./magic-cells.ts";
|
|
@@ -13,6 +14,7 @@ export function planMagicCell(
|
|
|
13
14
|
language: EvalLanguage,
|
|
14
15
|
code: string,
|
|
15
16
|
environments: PythonEnvironments | undefined,
|
|
17
|
+
jsEnvironments?: JsEnvironments,
|
|
16
18
|
): MagicCellPlan {
|
|
17
19
|
let magic: ReturnType<typeof parseMagicCell>;
|
|
18
20
|
try {
|
|
@@ -23,6 +25,7 @@ export function planMagicCell(
|
|
|
23
25
|
}
|
|
24
26
|
if (magic === undefined) return { kind: "ordinary" };
|
|
25
27
|
if (magic.kind === "load") return { kind: "load", target: magic.target };
|
|
28
|
+
if (language === "js") return planJsMagic(magic, jsEnvironments);
|
|
26
29
|
if (environments === undefined) {
|
|
27
30
|
return {
|
|
28
31
|
kind: "refused",
|
|
@@ -34,8 +37,9 @@ export function planMagicCell(
|
|
|
34
37
|
return {
|
|
35
38
|
kind: "host",
|
|
36
39
|
executor: async () => {
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
await environments.setMode(mode);
|
|
41
|
+
const where = mode === "project" ? "the session directory" : "this session's managed revisions";
|
|
42
|
+
return { ok: true, valueRepr: `environment: ${mode} (${where})` };
|
|
39
43
|
},
|
|
40
44
|
};
|
|
41
45
|
}
|
|
@@ -63,3 +67,57 @@ export function planMagicCell(
|
|
|
63
67
|
},
|
|
64
68
|
};
|
|
65
69
|
}
|
|
70
|
+
|
|
71
|
+
function planJsMagic(
|
|
72
|
+
magic: Exclude<NonNullable<ReturnType<typeof parseMagicCell>>, { kind: "load" }>,
|
|
73
|
+
environments: JsEnvironments | undefined,
|
|
74
|
+
): MagicCellPlan {
|
|
75
|
+
if (environments === undefined) {
|
|
76
|
+
return {
|
|
77
|
+
kind: "refused",
|
|
78
|
+
message: "environment_installer_unavailable: this session has no JavaScript package environment",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (magic.kind === "environment") {
|
|
82
|
+
const mode = magic.mode;
|
|
83
|
+
return {
|
|
84
|
+
kind: "host",
|
|
85
|
+
executor: async () => {
|
|
86
|
+
await environments.setMode(mode);
|
|
87
|
+
const where = mode === "project" ? "the session directory" : "this session's managed revisions";
|
|
88
|
+
return { ok: true, valueRepr: `environment: ${mode} (${where})` };
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (magic.kind !== "js-add") return { kind: "refused", message: "this magic is not available in JavaScript" };
|
|
93
|
+
const requested = magic.args;
|
|
94
|
+
return {
|
|
95
|
+
kind: "host",
|
|
96
|
+
executor: async ({ signal, emit }) => {
|
|
97
|
+
emit({ type: "status", event: { op: TIMEOUT_PAUSE_OP } });
|
|
98
|
+
try {
|
|
99
|
+
const receipt = await environments.install(
|
|
100
|
+
requested,
|
|
101
|
+
signal,
|
|
102
|
+
(stream, data) => emit({ type: "text", stream, data }),
|
|
103
|
+
magic.installer,
|
|
104
|
+
);
|
|
105
|
+
const added = receipt.added.length > 0 ? receipt.added.join(", ") : "nothing new";
|
|
106
|
+
const where =
|
|
107
|
+
receipt.revision === undefined ? receipt.mode : `${receipt.mode} (revision ${receipt.revision})`;
|
|
108
|
+
const shadow =
|
|
109
|
+
receipt.shadowed.length > 0
|
|
110
|
+
? `; environment_resolution_conflict: ${receipt.shadowed.join(", ")} still ${receipt.shadowed.length === 1 ? "resolves" : "resolve"} from the project's node_modules first`
|
|
111
|
+
: "";
|
|
112
|
+
return {
|
|
113
|
+
ok: true,
|
|
114
|
+
valueRepr: `added ${added} with ${receipt.installer} into ${where}${shadow}; already-imported modules stay cached until reset`,
|
|
115
|
+
};
|
|
116
|
+
} catch (error) {
|
|
117
|
+
return { ok: false, error: { message: error instanceof Error ? error.message : String(error) } };
|
|
118
|
+
} finally {
|
|
119
|
+
emit({ type: "status", event: { op: TIMEOUT_RESUME_OP } });
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
package/src/tool/magic-cells.ts
CHANGED
|
@@ -3,9 +3,10 @@ import type { EvalLanguage } from "./types.ts";
|
|
|
3
3
|
export type MagicCell =
|
|
4
4
|
| { readonly kind: "pip"; readonly args: string }
|
|
5
5
|
| { readonly kind: "environment"; readonly mode: "managed" | "project" }
|
|
6
|
-
| { readonly kind: "load"; readonly target: string }
|
|
6
|
+
| { readonly kind: "load"; readonly target: string }
|
|
7
|
+
| { readonly kind: "js-add"; readonly installer: "bun" | "npm"; readonly args: string };
|
|
7
8
|
|
|
8
|
-
const HOST_MAGICS = ["pip", "environment", "load"] as const;
|
|
9
|
+
const HOST_MAGICS = ["pip", "environment", "load", "bun", "npm"] as const;
|
|
9
10
|
const LOAD_LANGUAGES: ReadonlySet<EvalLanguage> = new Set(["py", "js"]);
|
|
10
11
|
const COMMENT_PREFIX: Partial<Record<EvalLanguage, string>> = { py: "#", js: "//" };
|
|
11
12
|
type HostMagic = (typeof HOST_MAGICS)[number];
|
|
@@ -34,6 +35,8 @@ function hostMagicOf(language: EvalLanguage, line: string): HostMagic | undefine
|
|
|
34
35
|
const name = match?.[1];
|
|
35
36
|
const magic = HOST_MAGICS.find((candidate) => candidate === name);
|
|
36
37
|
if (magic === "load") return LOAD_LANGUAGES.has(language) ? magic : undefined;
|
|
38
|
+
if (magic === "bun" || magic === "npm") return language === "js" ? magic : undefined;
|
|
39
|
+
if (magic === "environment") return language === "py" || language === "js" ? magic : undefined;
|
|
37
40
|
return language === "py" ? magic : undefined;
|
|
38
41
|
}
|
|
39
42
|
|
|
@@ -56,6 +59,13 @@ export function parseMagicCell(language: EvalLanguage, code: string): MagicCell
|
|
|
56
59
|
throw new MagicCellError(`put %${magic} on its own cell, then run the code that uses it in the next cell`);
|
|
57
60
|
const args = first.trim().slice(`%${magic}`.length).trim();
|
|
58
61
|
if (magic === "pip") return { kind: "pip", args };
|
|
62
|
+
if (magic === "bun" || magic === "npm") {
|
|
63
|
+
const [verb, ...rest] = args.split(/\s+/);
|
|
64
|
+
if (verb !== "add" && verb !== "install") {
|
|
65
|
+
throw new MagicCellError(`%${magic} supports only add: %${magic} add <package ...>`);
|
|
66
|
+
}
|
|
67
|
+
return { kind: "js-add", installer: magic, args: rest.join(" ") };
|
|
68
|
+
}
|
|
59
69
|
if (magic === "load") {
|
|
60
70
|
if (args === "") throw new MagicCellError("%load takes one argument: the path of the file to run");
|
|
61
71
|
return { kind: "load", target: args };
|
|
@@ -7,12 +7,14 @@ import {
|
|
|
7
7
|
kernelToolsStorage,
|
|
8
8
|
} from "@code-yeongyu/senpi";
|
|
9
9
|
import type { KernelToHostMessage } from "../bridge/protocol.ts";
|
|
10
|
+
import { resolveSandbox } from "../config/feature-settings.ts";
|
|
10
11
|
import { DEFAULT_FOREGROUND_WINDOW_SECONDS, defaultCodemodeSettings } from "../config/settings.ts";
|
|
11
12
|
import {
|
|
12
13
|
KERNEL_TOOLS_CAPABILITIES,
|
|
13
14
|
type KernelToolsCapability,
|
|
14
15
|
type KernelToolsDescribeResult,
|
|
15
16
|
} from "../kernels/js/kernel-tools-types.ts";
|
|
17
|
+
import { sandboxCellExecutor } from "../kernels/sandbox/sandbox-cell.ts";
|
|
16
18
|
import { TIMEOUT_PAUSE_OP, TIMEOUT_RESUME_OP } from "../timeouts/bridge-timeout.ts";
|
|
17
19
|
import { abortError, CellExecution, defaultTimeoutFactory } from "./cell-execution.ts";
|
|
18
20
|
import { CellHandler, type CellState } from "./cell-handler.ts";
|
|
@@ -261,25 +263,50 @@ async function executeCell(
|
|
|
261
263
|
}
|
|
262
264
|
if (invocation.input.reset) await execution.wait(kernel.reset());
|
|
263
265
|
execution.setKernel(kernel);
|
|
264
|
-
const magic = planMagicCell(
|
|
266
|
+
const magic = planMagicCell(
|
|
267
|
+
invocation.input.language,
|
|
268
|
+
invocation.input.code,
|
|
269
|
+
options.pythonEnvironments,
|
|
270
|
+
options.jsEnvironments,
|
|
271
|
+
);
|
|
265
272
|
// Resolved when the cell's turn comes in the kernel's queue: a %load reads the file the cells ahead of it
|
|
266
273
|
// wrote, and a refusal settles in queue order like any cell.
|
|
267
274
|
const loadOptions = { cwd: invocation.ctx.cwd, artifactsDir: options.artifactsDir };
|
|
268
|
-
|
|
269
|
-
|
|
275
|
+
// An isolated cell never runs a file outside its sandbox: %load there is refused.
|
|
276
|
+
const isolatedLoad = invocation.input.isolate === true && magic.kind === "load";
|
|
277
|
+
const resolveAtStart = isolatedLoad
|
|
278
|
+
? () => ({
|
|
279
|
+
ok: false as const,
|
|
280
|
+
message:
|
|
281
|
+
"isolate: true cannot run a %load cell: an isolated cell sees no host files, and loading one would break that isolation. Run %load in a normal cell, or paste the code into the isolated cell.",
|
|
282
|
+
})
|
|
283
|
+
: magic.kind === "load"
|
|
270
284
|
? () => loadCell(magic.target, loadOptions)
|
|
271
285
|
: magic.kind === "refused"
|
|
272
286
|
? () => ({ ok: false as const, message: magic.message })
|
|
273
287
|
: undefined;
|
|
288
|
+
const isolated =
|
|
289
|
+
invocation.input.isolate === true && resolveAtStart === undefined
|
|
290
|
+
? sandboxCellExecutor(invocation.input.code, {
|
|
291
|
+
sandbox: resolveSandbox(options.settings ?? {}),
|
|
292
|
+
executeTool: options.executeTool,
|
|
293
|
+
toolNames: () => (options.listTools?.() ?? []).map((tool) => tool.name),
|
|
294
|
+
describeTool: (name) => options.listTools?.().find((tool) => tool.name === name)?.description,
|
|
295
|
+
})
|
|
296
|
+
: undefined;
|
|
274
297
|
const environments = invocation.input.language === "py" ? options.pythonEnvironments : undefined;
|
|
275
298
|
const envRoot = environments === undefined ? undefined : () => environments.activeRoot ?? "";
|
|
299
|
+
const jsEnvironments = invocation.input.language === "js" ? options.jsEnvironments : undefined;
|
|
300
|
+
const packageRoot = jsEnvironments === undefined ? undefined : () => jsEnvironments.packageRoot;
|
|
276
301
|
const result = await execution.wait(
|
|
277
302
|
kernel.run({
|
|
278
303
|
cellId: invocation.cellId,
|
|
279
304
|
code: invocation.input.code,
|
|
280
305
|
...(resolveAtStart === undefined ? {} : { resolveAtStart }),
|
|
281
306
|
...(magic.kind === "host" ? { host: magic.executor } : {}),
|
|
307
|
+
...(isolated === undefined ? {} : { host: isolated }),
|
|
282
308
|
...(envRoot === undefined ? {} : { envRoot }),
|
|
309
|
+
...(packageRoot === undefined ? {} : { packageRoot }),
|
|
283
310
|
...(bridgeCellToken === undefined ? {} : { bridgeCellToken }),
|
|
284
311
|
kernelPreludes: options.kernelPreludes?.(),
|
|
285
312
|
onMessage,
|
|
@@ -10,7 +10,8 @@ const ELLIPSIS = "\u2026";
|
|
|
10
10
|
* runtime name because node and bun are otherwise indistinguishable.
|
|
11
11
|
*/
|
|
12
12
|
export function formatRuntimeBadge(language: EvalLanguage, runtime: EvalRuntimeInfo, home: string = homedir()): string {
|
|
13
|
-
const
|
|
13
|
+
const base = language === "js" ? `${runtime.name} ${runtime.version}` : runtime.version;
|
|
14
|
+
const label = runtime.isolation === "process" ? `${base}, process` : base;
|
|
14
15
|
if (runtime.path === undefined || runtime.path.length === 0) return label;
|
|
15
16
|
return `${label}, ${minifyPath(runtime.path, home)}`;
|
|
16
17
|
}
|
package/src/tool/types.ts
CHANGED
|
@@ -52,6 +52,8 @@ export interface EvalToolInput {
|
|
|
52
52
|
readonly timeout?: number;
|
|
53
53
|
readonly on_timeout?: "detach" | "error";
|
|
54
54
|
readonly reset?: boolean;
|
|
55
|
+
/** Run in a fresh sandbox VM instead of the persistent kernel; accepted only while `sandbox.enabled` is on. */
|
|
56
|
+
readonly isolate?: boolean;
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
export interface EvalListInput {
|
|
@@ -117,14 +119,26 @@ const fullEvalInputSchema = Type.Object(
|
|
|
117
119
|
/** Runtime accepts a discriminated run/control union. */
|
|
118
120
|
export type EvalInputSchema = TUnsafe<EvalToolRequest> & Pick<typeof fullEvalInputSchema, "properties">;
|
|
119
121
|
|
|
122
|
+
const ISOLATE_FIELD_DESCRIPTION =
|
|
123
|
+
"js only. Fresh sandbox per call: no persistence, no fs/net/process/timers; only tool.* plus print/display.";
|
|
124
|
+
|
|
120
125
|
export function createEvalInputSchema(
|
|
121
126
|
enabled: EnabledEvalLanguages,
|
|
122
127
|
deadlines: EvalDeadlineSeconds = defaultEvalDeadlineSeconds,
|
|
128
|
+
options: { readonly sandbox?: boolean } = {},
|
|
123
129
|
): EvalInputSchema {
|
|
124
130
|
const languages = enabledLanguageList(enabled);
|
|
125
131
|
if (languages.length === 0) throw new Error("eval requires at least one enabled language");
|
|
126
132
|
const languageSchema = evalLanguageUnion(languages);
|
|
127
|
-
|
|
133
|
+
// The sandbox field exists in the schema only while sandbox cells are turned on, so the default schema
|
|
134
|
+
// (and the prompt built from it) stays exactly what it was.
|
|
135
|
+
const properties =
|
|
136
|
+
options.sandbox === true && languages.includes("js")
|
|
137
|
+
? {
|
|
138
|
+
...evalInputProperties(languageSchema, deadlines),
|
|
139
|
+
isolate: Type.Optional(Type.Boolean({ description: ISOLATE_FIELD_DESCRIPTION })),
|
|
140
|
+
}
|
|
141
|
+
: evalInputProperties(languageSchema, deadlines);
|
|
128
142
|
return Type.Unsafe<EvalToolRequest>(
|
|
129
143
|
Type.Object(properties, {
|
|
130
144
|
// Keep branches self-contained for Mistral-hosted GLM (#2240).
|
|
@@ -175,6 +189,12 @@ export interface EvalKernelRunInput {
|
|
|
175
189
|
readonly envRoot?: () => string;
|
|
176
190
|
/** The file a `%load` cell runs: tracebacks name it and its relative imports resolve from its directory. */
|
|
177
191
|
readonly sourceFile?: string;
|
|
192
|
+
/**
|
|
193
|
+
* JavaScript only: the session's managed package revision; bare imports that do not resolve from cwd fall back to
|
|
194
|
+
* it. Read when the cell starts running (not when it was queued), so a cell queued behind `%bun add` resolves the
|
|
195
|
+
* revision that install published.
|
|
196
|
+
*/
|
|
197
|
+
readonly packageRoot?: () => string | undefined;
|
|
178
198
|
/**
|
|
179
199
|
* Called when the cell's turn comes in the kernel's queue, never earlier: what to run instead of `code` (a `%load`
|
|
180
200
|
* cell's file), or a refusal that settles the cell as failed in queue order.
|
|
@@ -259,6 +279,8 @@ export interface EvalRuntimeInfo {
|
|
|
259
279
|
readonly name: string;
|
|
260
280
|
readonly version: string;
|
|
261
281
|
readonly path?: string;
|
|
282
|
+
/** JS only: the kernel runs in a child process instead of a worker thread. */
|
|
283
|
+
readonly isolation?: "process";
|
|
262
284
|
}
|
|
263
285
|
|
|
264
286
|
export type EvalRuntimes = Readonly<Partial<Record<EvalLanguage, EvalRuntimeInfo>>>;
|