@code-yeongyu/senpi-codemode 2026.10.9 → 2026.10.10-10
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 +158 -0
- package/README.md +161 -11
- package/package.json +6 -5
- package/src/bridge/protocol.ts +8 -3
- package/src/bridge/reserved.ts +2 -0
- package/src/bridges/eval-environment-schemas.ts +48 -0
- package/src/bridges/eval-virtual-schemas.ts +11 -4
- package/src/bridges/reserved-dispatch.ts +13 -1
- 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/packages-install.ts +98 -0
- package/src/environments/project-resolves.ts +54 -0
- package/src/environments/py-environment.ts +7 -3
- package/src/environments/py-installer.ts +5 -1
- package/src/environments/python-environments.ts +1 -1
- package/src/environments/revision-store.ts +20 -4
- package/src/extension/bridge-tool-call.ts +5 -1
- package/src/extension/eval-notifier.ts +13 -2
- package/src/extension/kernel-registration.ts +7 -2
- package/src/extension/runtime-factory.ts +51 -13
- package/src/extension/runtime-info.ts +6 -2
- package/src/extension/session-manager-contract.ts +3 -0
- package/src/extension/session-manager.ts +16 -2
- package/src/index.ts +3 -2
- 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/cell-ownership.js +268 -0
- package/src/kernels/js/cell-run-context.js +89 -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 +466 -0
- package/src/kernels/js/process-memory-host.ts +83 -0
- package/src/kernels/js/process-worker.ts +298 -0
- package/src/kernels/js/rejection-reports.js +61 -0
- package/src/kernels/js/run-queue.ts +7 -0
- package/src/kernels/js/worker-core.js +53 -8
- package/src/kernels/js/worker-cwd.js +5 -2
- package/src/kernels/js/worker-host.ts +9 -2
- package/src/kernels/js/worker-indirect-eval.js +37 -13
- package/src/kernels/js/worker-package-resolve.js +153 -0
- package/src/kernels/js/worker-require.d.ts +19 -0
- package/src/kernels/js/worker-require.js +52 -0
- package/src/kernels/js/worker-runtime.js +43 -9
- package/src/kernels/js/worker-shadow-guard.js +2 -6
- package/src/kernels/js/worker-shell-capture.js +5 -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 +15 -1
- package/src/kernels/py/host-cell.ts +8 -31
- package/src/kernels/py/prelude.py +33 -2
- package/src/kernels/py/process.ts +4 -0
- package/src/kernels/sandbox/sandbox-cell.ts +141 -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 +12 -11
- package/src/tool/cell-reserved-dispatch.ts +1 -0
- package/src/tool/cell-runtime.ts +4 -2
- package/src/tool/detached-cell-contract.ts +3 -0
- package/src/tool/detached-cell-manager.ts +4 -8
- package/src/tool/detached-cell-notification.ts +11 -63
- package/src/tool/detached-cell-snapshot.ts +17 -0
- package/src/tool/detached-eval-result.ts +13 -7
- package/src/tool/detached-notification-queue.ts +1 -2
- 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/interrupt-note.ts +4 -5
- package/src/tool/live-headline.ts +60 -0
- 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/managed-cell.ts +0 -4
- package/src/tool/render.ts +74 -15
- package/src/tool/run-eval-cell.ts +46 -4
- package/src/tool/runtime-label.ts +2 -1
- package/src/tool/types.ts +23 -1
package/src/tool/render.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type ToolDefinition,
|
|
8
8
|
type ToolRenderResultOptions,
|
|
9
9
|
truncateToVisualLines,
|
|
10
|
+
visibleWidth,
|
|
10
11
|
} from "@code-yeongyu/senpi";
|
|
11
12
|
import type { TruncationMeta } from "../output/output-meta.ts";
|
|
12
13
|
import { highlightedCode } from "./code-preview.ts";
|
|
@@ -21,6 +22,7 @@ import {
|
|
|
21
22
|
JSON_TREE_SCALAR_LEN_EXPANDED,
|
|
22
23
|
renderJsonTreeLines,
|
|
23
24
|
} from "./json-tree.ts";
|
|
25
|
+
import { knownLanguage, leadsWithHeadline, liveHeadline } from "./live-headline.ts";
|
|
24
26
|
import { formatRuntimeBadge } from "./runtime-label.ts";
|
|
25
27
|
import { codePointPrefix, formatDuration, renderToolCallWidget } from "./tool-widgets.ts";
|
|
26
28
|
import type {
|
|
@@ -341,18 +343,37 @@ function cellHeader(cell: EvalCellResult, environment: RenderEnvironment, badges
|
|
|
341
343
|
environment.spinnerFrame ?? Math.floor((cellElapsedMs(cell, environment) ?? 0) / LIVE_RENDER_TICK_MS),
|
|
342
344
|
);
|
|
343
345
|
const runtimeBadge = cell.runtime === undefined ? "" : ` (${formatRuntimeBadge(cell.language, cell.runtime)})`;
|
|
344
|
-
let header =
|
|
346
|
+
let header = leadsWithHeadline(cell.status)
|
|
347
|
+
? `eval ${cell.language}${runtimeBadge} ${presentation.label}`
|
|
348
|
+
: `eval ${cell.language}${runtimeBadge} ${presentation.label} ${presentation.icon}`;
|
|
345
349
|
if (cell.queuedBehind !== undefined && cell.queuedBehind.length > 0)
|
|
346
350
|
header += ` · queued behind ${cell.queuedBehind.map(sanitizeTerminalLabel).join(", ")}`;
|
|
351
|
+
else if (cell.queuedBehind !== undefined && cell.status === "queued")
|
|
352
|
+
header += ` · waiting for the ${cell.language} kernel to be ready`;
|
|
347
353
|
const throughputBadge = badges.throughput === undefined ? undefined : formatThroughputBadge(badges.throughput);
|
|
348
354
|
if (throughputBadge !== undefined) header += ` · ${throughputBadge}`;
|
|
349
355
|
const elapsedMs = badges.throughput?.wallDurationMs ?? cellElapsedMs(cell, environment);
|
|
350
356
|
if (elapsedMs !== undefined) header += ` · ${formatDuration(elapsedMs)}`;
|
|
351
357
|
if (badges.reset) header += " · reset";
|
|
352
358
|
if (badges.timeout !== undefined) header += ` · timeout ${badges.timeout}s`;
|
|
359
|
+
if (leadsWithHeadline(cell.status))
|
|
360
|
+
header = headlined(presentation.icon, cell.summary, cell.code, header, environment);
|
|
353
361
|
return style(environment.theme, presentation.color, header);
|
|
354
362
|
}
|
|
355
363
|
|
|
364
|
+
// An in-progress row leads with what the cell is doing (senpi#2802); collapsed, the headline is cut so the whole
|
|
365
|
+
// header stays on one line, and the code moves behind expand.
|
|
366
|
+
function headlined(
|
|
367
|
+
icon: string,
|
|
368
|
+
summary: string | undefined,
|
|
369
|
+
code: string | undefined,
|
|
370
|
+
rest: string,
|
|
371
|
+
environment: RenderEnvironment,
|
|
372
|
+
): string {
|
|
373
|
+
const budget = environment.expanded ? undefined : environment.width - 3 - visibleWidth(`${icon} · ${rest}`);
|
|
374
|
+
return `${icon} ${liveHeadline(summary, code, budget)} · ${rest}`;
|
|
375
|
+
}
|
|
376
|
+
|
|
356
377
|
function previewText(
|
|
357
378
|
text: string,
|
|
358
379
|
maxLines: number,
|
|
@@ -612,12 +633,23 @@ function renderAgentProgressEvents(events: readonly EvalStatusEvent[], environme
|
|
|
612
633
|
}
|
|
613
634
|
|
|
614
635
|
function renderCell(cell: EvalCellResult, environment: RenderEnvironment, badges: CellBadges): string[] {
|
|
636
|
+
if (leadsWithHeadline(cell.status) && !environment.expanded && cell.output.trim().length === 0) {
|
|
637
|
+
const statusEvents = (cell.statusEvents ?? []).filter((event) => event.op !== "agent");
|
|
638
|
+
if (statusEvents.length === 0) {
|
|
639
|
+
// Nothing to frame yet: the collapsed live row is its headline alone, one line, no empty box.
|
|
640
|
+
const lines = renderPrefixed(cellHeader(cell, environment, badges), environment, LIVE_LINE_PREFIX);
|
|
641
|
+
const agentEvents = (cell.statusEvents ?? []).filter((event) => event.op === "agent");
|
|
642
|
+
if (agentEvents.length > 0) appendLines(lines, renderAgentProgressEvents(agentEvents, environment));
|
|
643
|
+
return lines;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
615
646
|
const lines = renderPrefixed(cellHeader(cell, environment, badges), environment, {
|
|
616
647
|
prefix: "╭─ ",
|
|
617
648
|
continuation: "│ ",
|
|
618
649
|
color: "borderAccent",
|
|
619
650
|
});
|
|
620
|
-
|
|
651
|
+
const headlined = leadsWithHeadline(cell.status);
|
|
652
|
+
if (cell.summary !== undefined && !headlined) {
|
|
621
653
|
appendLines(
|
|
622
654
|
lines,
|
|
623
655
|
summaryVisualLines(cell.summary, Math.max(1, environment.width - 2), environment.expanded).map(
|
|
@@ -626,11 +658,14 @@ function renderCell(cell: EvalCellResult, environment: RenderEnvironment, badges
|
|
|
626
658
|
);
|
|
627
659
|
}
|
|
628
660
|
const innerWidth = Math.max(1, environment.width - 2);
|
|
629
|
-
const codePreview =
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
661
|
+
const codePreview =
|
|
662
|
+
headlined && !environment.expanded
|
|
663
|
+
? { lines: [], skipped: 0 }
|
|
664
|
+
: previewText(
|
|
665
|
+
highlightedCode(cell.code, cell.language, environment.theme, environment.repaint),
|
|
666
|
+
environment.expanded ? Number.POSITIVE_INFINITY : CODE_PREVIEW_LINES,
|
|
667
|
+
innerWidth,
|
|
668
|
+
);
|
|
634
669
|
if (codePreview.skipped > 0) {
|
|
635
670
|
appendLines(
|
|
636
671
|
lines,
|
|
@@ -879,6 +914,10 @@ function displaySummary(summary: string | undefined): string | undefined {
|
|
|
879
914
|
return normalizeEvalSummary(summary);
|
|
880
915
|
}
|
|
881
916
|
|
|
917
|
+
function cellIdSuffix(cellId: unknown): string {
|
|
918
|
+
return typeof cellId === "string" && cellId !== "" ? ` ${sanitizeTerminalLabel(cellId)}` : "";
|
|
919
|
+
}
|
|
920
|
+
|
|
882
921
|
export function renderEvalCall(
|
|
883
922
|
args: EvalToolRequest,
|
|
884
923
|
theme: Theme | undefined,
|
|
@@ -892,15 +931,23 @@ export function renderEvalCall(
|
|
|
892
931
|
return component;
|
|
893
932
|
}
|
|
894
933
|
if (!isEvalRunInput(args)) {
|
|
895
|
-
|
|
934
|
+
// While a peek/stop call streams in, `cell_id` can still be missing; the title is the action alone until it arrives.
|
|
935
|
+
const title = args.action === "list" ? "eval list" : `eval ${args.action}${cellIdSuffix(args.cell_id)}`;
|
|
896
936
|
component.setBlocks([{ kind: "text", text: style(theme, "toolTitle", title) }]);
|
|
897
937
|
return component;
|
|
898
938
|
}
|
|
939
|
+
// While the model streams the call, `code` usually arrives before `language` and `summary`; a partial call still
|
|
940
|
+
// renders (the host would otherwise fall back to a raw key=value row).
|
|
941
|
+
const code = typeof args.code === "string" ? args.code : "";
|
|
942
|
+
const language = knownLanguage(args.language);
|
|
899
943
|
if (theme === undefined && context.spinnerFrame === undefined) {
|
|
900
944
|
const reset = args.reset === true ? " reset" : "";
|
|
901
945
|
const timeout = args.timeout === undefined ? "" : ` timeout ${args.timeout}s`;
|
|
902
946
|
component.setBlocks([
|
|
903
|
-
{
|
|
947
|
+
{
|
|
948
|
+
kind: "text",
|
|
949
|
+
text: style(theme, "toolTitle", `eval${language === undefined ? "" : ` ${language}`}${reset}${timeout}`),
|
|
950
|
+
},
|
|
904
951
|
...(displaySummary(args.summary) === undefined
|
|
905
952
|
? []
|
|
906
953
|
: [summaryBlock(displaySummary(args.summary) ?? "", theme, context.expanded)]),
|
|
@@ -909,9 +956,11 @@ export function renderEvalCall(
|
|
|
909
956
|
text: style(
|
|
910
957
|
theme,
|
|
911
958
|
"mdCodeBlock",
|
|
912
|
-
|
|
913
|
-
?
|
|
914
|
-
:
|
|
959
|
+
code.trim().length === 0
|
|
960
|
+
? "..."
|
|
961
|
+
: language === undefined
|
|
962
|
+
? code
|
|
963
|
+
: displayCode(code, language, context.argsComplete ? context.invalidate : undefined),
|
|
915
964
|
),
|
|
916
965
|
maxVisualLines: context.expanded ? undefined : CODE_PREVIEW_LINES,
|
|
917
966
|
collapseKind: "code",
|
|
@@ -933,11 +982,13 @@ export function renderEvalCall(
|
|
|
933
982
|
now: renderNow(context),
|
|
934
983
|
...(context.argsComplete ? { repaint: context.invalidate } : {}),
|
|
935
984
|
};
|
|
985
|
+
const summary = displaySummary(args.summary);
|
|
986
|
+
if (language === undefined) return streamingCallLines(summary, code, environment);
|
|
936
987
|
const cell: EvalCellResult = {
|
|
937
988
|
index: 0,
|
|
938
|
-
...(
|
|
939
|
-
code
|
|
940
|
-
language
|
|
989
|
+
...(summary === undefined ? {} : { summary }),
|
|
990
|
+
code,
|
|
991
|
+
language,
|
|
941
992
|
output: "",
|
|
942
993
|
status: context.spinnerFrame === undefined ? "pending" : "running",
|
|
943
994
|
};
|
|
@@ -952,6 +1003,14 @@ export function renderEvalCall(
|
|
|
952
1003
|
return component;
|
|
953
1004
|
}
|
|
954
1005
|
|
|
1006
|
+
/** A call whose language has not streamed in yet: the headline row alone, until the full frame can render. */
|
|
1007
|
+
function streamingCallLines(summary: string | undefined, code: string, environment: RenderEnvironment): string[] {
|
|
1008
|
+
const icon = environment.spinnerFrame === undefined ? "○" : spinner(environment.spinnerFrame);
|
|
1009
|
+
return renderPrefixed(headlined(icon, summary, code, "eval", environment), environment, LIVE_LINE_PREFIX);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
const LIVE_LINE_PREFIX: PrefixStyle = { prefix: "╶─ ", continuation: " ", color: "borderAccent" };
|
|
1013
|
+
|
|
955
1014
|
export function renderEvalResult(
|
|
956
1015
|
result: AgentToolResult<EvalResultDetails>,
|
|
957
1016
|
options: ToolRenderResultOptions,
|
|
@@ -7,12 +7,16 @@ 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";
|
|
12
|
+
import type { JsEnvironments } from "../environments/js-environments.ts";
|
|
13
|
+
import type { PackagesInstallEnvironments } from "../environments/packages-install.ts";
|
|
11
14
|
import {
|
|
12
15
|
KERNEL_TOOLS_CAPABILITIES,
|
|
13
16
|
type KernelToolsCapability,
|
|
14
17
|
type KernelToolsDescribeResult,
|
|
15
18
|
} from "../kernels/js/kernel-tools-types.ts";
|
|
19
|
+
import { sandboxCellExecutor, sandboxRuntimeInfo } from "../kernels/sandbox/sandbox-cell.ts";
|
|
16
20
|
import { TIMEOUT_PAUSE_OP, TIMEOUT_RESUME_OP } from "../timeouts/bridge-timeout.ts";
|
|
17
21
|
import { abortError, CellExecution, defaultTimeoutFactory } from "./cell-execution.ts";
|
|
18
22
|
import { CellHandler, type CellState } from "./cell-handler.ts";
|
|
@@ -42,7 +46,8 @@ export async function runEvalCell(
|
|
|
42
46
|
const bridgeAbortController = new AbortController();
|
|
43
47
|
const cellSignal = AbortSignal.any([invocation.signal, bridgeAbortController.signal]);
|
|
44
48
|
const bridgeContext: ExtensionContext = { ...invocation.ctx, signal: cellSignal };
|
|
45
|
-
const runtime =
|
|
49
|
+
const runtime =
|
|
50
|
+
invocation.input.isolate === true ? sandboxRuntimeInfo() : options.runtimes?.[invocation.input.language];
|
|
46
51
|
const state: CellState = {
|
|
47
52
|
input: invocation.input,
|
|
48
53
|
...(runtime === undefined ? {} : { runtime }),
|
|
@@ -238,6 +243,7 @@ async function executeCell(
|
|
|
238
243
|
...(kernelTools === undefined ? {} : { kernelTools }),
|
|
239
244
|
...(options.handles === undefined ? {} : { handles: options.handles }),
|
|
240
245
|
hardDeadlineMs: cell.startedAtMs + cell.hardLimitSeconds * 1_000,
|
|
246
|
+
...cellEnvironments(invocation.input.language, options),
|
|
241
247
|
});
|
|
242
248
|
handler = activeHandler;
|
|
243
249
|
cellManager.bindKernel(
|
|
@@ -261,25 +267,50 @@ async function executeCell(
|
|
|
261
267
|
}
|
|
262
268
|
if (invocation.input.reset) await execution.wait(kernel.reset());
|
|
263
269
|
execution.setKernel(kernel);
|
|
264
|
-
const magic = planMagicCell(
|
|
270
|
+
const magic = planMagicCell(
|
|
271
|
+
invocation.input.language,
|
|
272
|
+
invocation.input.code,
|
|
273
|
+
options.pythonEnvironments,
|
|
274
|
+
options.jsEnvironments,
|
|
275
|
+
);
|
|
265
276
|
// Resolved when the cell's turn comes in the kernel's queue: a %load reads the file the cells ahead of it
|
|
266
277
|
// wrote, and a refusal settles in queue order like any cell.
|
|
267
278
|
const loadOptions = { cwd: invocation.ctx.cwd, artifactsDir: options.artifactsDir };
|
|
268
|
-
|
|
269
|
-
|
|
279
|
+
// An isolated cell never runs a file outside its sandbox: %load there is refused.
|
|
280
|
+
const isolatedLoad = invocation.input.isolate === true && magic.kind === "load";
|
|
281
|
+
const resolveAtStart = isolatedLoad
|
|
282
|
+
? () => ({
|
|
283
|
+
ok: false as const,
|
|
284
|
+
message:
|
|
285
|
+
"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.",
|
|
286
|
+
})
|
|
287
|
+
: magic.kind === "load"
|
|
270
288
|
? () => loadCell(magic.target, loadOptions)
|
|
271
289
|
: magic.kind === "refused"
|
|
272
290
|
? () => ({ ok: false as const, message: magic.message })
|
|
273
291
|
: undefined;
|
|
292
|
+
const isolated =
|
|
293
|
+
invocation.input.isolate === true && resolveAtStart === undefined
|
|
294
|
+
? sandboxCellExecutor(invocation.input.code, {
|
|
295
|
+
sandbox: resolveSandbox(options.settings ?? {}),
|
|
296
|
+
executeTool: options.executeTool,
|
|
297
|
+
toolNames: () => (options.listTools?.() ?? []).map((tool) => tool.name),
|
|
298
|
+
describeTool: (name) => options.listTools?.().find((tool) => tool.name === name)?.description,
|
|
299
|
+
})
|
|
300
|
+
: undefined;
|
|
274
301
|
const environments = invocation.input.language === "py" ? options.pythonEnvironments : undefined;
|
|
275
302
|
const envRoot = environments === undefined ? undefined : () => environments.activeRoot ?? "";
|
|
303
|
+
const jsEnvironments = invocation.input.language === "js" ? options.jsEnvironments : undefined;
|
|
304
|
+
const packageRoot = jsEnvironments === undefined ? undefined : () => jsEnvironments.packageRoot;
|
|
276
305
|
const result = await execution.wait(
|
|
277
306
|
kernel.run({
|
|
278
307
|
cellId: invocation.cellId,
|
|
279
308
|
code: invocation.input.code,
|
|
280
309
|
...(resolveAtStart === undefined ? {} : { resolveAtStart }),
|
|
281
310
|
...(magic.kind === "host" ? { host: magic.executor } : {}),
|
|
311
|
+
...(isolated === undefined ? {} : { host: isolated }),
|
|
282
312
|
...(envRoot === undefined ? {} : { envRoot }),
|
|
313
|
+
...(packageRoot === undefined ? {} : { packageRoot }),
|
|
283
314
|
...(bridgeCellToken === undefined ? {} : { bridgeCellToken }),
|
|
284
315
|
kernelPreludes: options.kernelPreludes?.(),
|
|
285
316
|
onMessage,
|
|
@@ -336,3 +367,14 @@ function kernelToolsFor(kernel: EvalKernel): KernelToolsCapability | undefined {
|
|
|
336
367
|
invoke: (request, options) => withTools.invokeKernelTool(request, options),
|
|
337
368
|
} satisfies ExtensionKernelTools;
|
|
338
369
|
}
|
|
370
|
+
|
|
371
|
+
// Only JS cells call tools in-process; a Python cell reaches packages.install() over the bridge, whose session manager
|
|
372
|
+
// holds the same Python environment its magics use.
|
|
373
|
+
function cellEnvironments(
|
|
374
|
+
language: string,
|
|
375
|
+
options: { readonly jsEnvironments?: JsEnvironments },
|
|
376
|
+
): { readonly environments?: PackagesInstallEnvironments } {
|
|
377
|
+
if (language === "js" && options.jsEnvironments !== undefined)
|
|
378
|
+
return { environments: { js: options.jsEnvironments } };
|
|
379
|
+
return {};
|
|
380
|
+
}
|
|
@@ -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 === undefined ? base : `${base}, ${runtime.isolation}`;
|
|
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 (`process`), or the cell ran in an isolated QuickJS VM (`sandbox`). */
|
|
283
|
+
readonly isolation?: "process" | "sandbox";
|
|
262
284
|
}
|
|
263
285
|
|
|
264
286
|
export type EvalRuntimes = Readonly<Partial<Record<EvalLanguage, EvalRuntimeInfo>>>;
|