@code-yeongyu/senpi-codemode 2026.10.10-3 → 2026.10.10-4
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 +18 -0
- package/package.json +4 -4
- package/src/kernels/sandbox/sandbox-cell.ts +26 -1
- package/src/tool/cell-runtime.ts +4 -2
- package/src/tool/detached-cell-manager.ts +4 -4
- package/src/tool/detached-cell-snapshot.ts +17 -0
- package/src/tool/detached-eval-result.ts +13 -7
- package/src/tool/interrupt-note.ts +4 -5
- package/src/tool/render.ts +2 -0
- package/src/tool/run-eval-cell.ts +3 -2
- package/src/tool/runtime-label.ts +1 -1
- package/src/tool/types.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,24 @@
|
|
|
12
12
|
|
|
13
13
|
### Removed
|
|
14
14
|
|
|
15
|
+
## [2026.10.10-4] - 2026-10-06
|
|
16
|
+
|
|
17
|
+
### Breaking Changes
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- A detached eval cell that waits for its kernel to start now says so (`waiting for the js kernel to be ready`) instead of `queued behind in the js kernel` with an empty predecessor ([#2790](https://github.com/code-yeongyu/senpi/issues/2790)).
|
|
26
|
+
|
|
27
|
+
- A stopped (or failed) detached cell's result and notification show its buffered output instead of the live `1/1 cells running` frame, so a cancelled cell no longer reads as still running. The kernel-state note now says plainly whether the kernel was restarted: `The JavaScript worker was not restarted; variables from earlier cells are kept.` instead of `... remains running; its existing variables are preserved.`, or `The JavaScript worker was restarted; variables from earlier cells are lost.` instead of `... was unresponsive to interrupt and was restarted ...` (the worker may have answered the interrupt and still needed a restart) ([#2791](https://github.com/code-yeongyu/senpi/issues/2791)).
|
|
28
|
+
|
|
29
|
+
- An isolated (`isolate: true`) eval cell's result names QuickJS as its runtime (`quickjs <version>, sandbox`) instead of the persistent kernel's Bun or Node runtime ([#2811](https://github.com/code-yeongyu/senpi/issues/2811)).
|
|
30
|
+
|
|
31
|
+
### Removed
|
|
32
|
+
|
|
15
33
|
## [2026.10.10-3] - 2026-10-06
|
|
16
34
|
|
|
17
35
|
### Breaking Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/senpi-codemode",
|
|
3
|
-
"version": "2026.10.10-
|
|
3
|
+
"version": "2026.10.10-4",
|
|
4
4
|
"description": "Source-only senpi extension package for codemode evaluation tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -33,15 +33,15 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@babel/parser": "8.0.4",
|
|
36
|
-
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.10-
|
|
36
|
+
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.10-4",
|
|
37
37
|
"quickjs-wasi": "3.6.2",
|
|
38
38
|
"typebox": "1.3.34"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@code-yeongyu/senpi": "2026.10.10-
|
|
41
|
+
"@code-yeongyu/senpi": "2026.10.10-4"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@code-yeongyu/senpi": "2026.10.10-
|
|
44
|
+
"@code-yeongyu/senpi": "2026.10.10-4"
|
|
45
45
|
},
|
|
46
46
|
"keywords": [
|
|
47
47
|
"senpi",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ResolvedSandbox } from "../../config/feature-settings.ts";
|
|
2
2
|
import { marshalToolResult } from "../../tool/image.ts";
|
|
3
|
-
import type { ExecuteTool, HostCellExecutor } from "../../tool/types.ts";
|
|
3
|
+
import type { EvalRuntimeInfo, ExecuteTool, HostCellExecutor } from "../../tool/types.ts";
|
|
4
4
|
import type { CodemodeSandbox } from "./vendor/pi-codemode/runtime/host.ts";
|
|
5
5
|
import type { CodemodeError, CodemodeOutputFrame, CodemodeTool } from "./vendor/pi-codemode/types.ts";
|
|
6
6
|
|
|
@@ -13,6 +13,31 @@ export interface SandboxCellOptions {
|
|
|
13
13
|
readonly wasmPath?: string;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* The runtime an isolated cell's result reports: the QuickJS build it runs on, never the persistent kernel's (senpi#2811).
|
|
18
|
+
* Read from the installed quickjs-wasi package the wasm loads from, only when an isolated cell runs; when that package
|
|
19
|
+
* cannot be resolved the cell itself fails with eval_isolate_unavailable, and the version says so.
|
|
20
|
+
*/
|
|
21
|
+
export function sandboxRuntimeInfo(): EvalRuntimeInfo {
|
|
22
|
+
let version = "unavailable";
|
|
23
|
+
try {
|
|
24
|
+
// getBuiltinModule, not a static import: node:module would otherwise load with the extension.
|
|
25
|
+
const manifest: unknown = process.getBuiltinModule("node:module").createRequire(import.meta.url)(
|
|
26
|
+
"quickjs-wasi/package.json",
|
|
27
|
+
);
|
|
28
|
+
if (
|
|
29
|
+
typeof manifest === "object" &&
|
|
30
|
+
manifest !== null &&
|
|
31
|
+
"version" in manifest &&
|
|
32
|
+
typeof manifest.version === "string"
|
|
33
|
+
)
|
|
34
|
+
version = manifest.version;
|
|
35
|
+
} catch {
|
|
36
|
+
// Reported as "unavailable": the same missing package makes the cell fail with eval_isolate_unavailable.
|
|
37
|
+
}
|
|
38
|
+
return { name: "quickjs", version, isolation: "sandbox" };
|
|
39
|
+
}
|
|
40
|
+
|
|
16
41
|
// On the script's first line so reported line numbers still match the cell; globals, so a cell's own
|
|
17
42
|
// `const print` shadows them instead of failing as a redeclaration.
|
|
18
43
|
const OUTPUT_ALIASES =
|
package/src/tool/cell-runtime.ts
CHANGED
|
@@ -219,8 +219,10 @@ export class CellResultBuilder {
|
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
#liveUpdateText(): string {
|
|
222
|
-
if (this.#state.status === "queued" && this.#state.queuedBehind
|
|
223
|
-
return
|
|
222
|
+
if (this.#state.status === "queued" && this.#state.queuedBehind !== undefined) {
|
|
223
|
+
return this.#state.queuedBehind.length === 0
|
|
224
|
+
? `waiting for the ${this.#state.input.language} kernel to be ready`
|
|
225
|
+
: `queued behind ${this.#state.queuedBehind.join(", ")} in the ${this.#state.input.language} kernel`;
|
|
224
226
|
}
|
|
225
227
|
const summary = this.#state.input.summary === undefined ? "" : ` ${this.#state.input.summary}`;
|
|
226
228
|
const output = this.#liveLines.text();
|
|
@@ -13,7 +13,7 @@ import type {
|
|
|
13
13
|
EvalDetachedCellSnapshot,
|
|
14
14
|
EvalDetachedCellStatusEntry,
|
|
15
15
|
} from "./detached-cell-contract.ts";
|
|
16
|
-
import {
|
|
16
|
+
import { cancelledDetachedResult, detachedErrorResult, snapshotDetachedCell } from "./detached-cell-snapshot.ts";
|
|
17
17
|
import {
|
|
18
18
|
activeDetachedCellReuseError,
|
|
19
19
|
allowsDetachedCellTransition,
|
|
@@ -260,7 +260,7 @@ export class EvalDetachedCellManager {
|
|
|
260
260
|
return;
|
|
261
261
|
}
|
|
262
262
|
if (foreground) {
|
|
263
|
-
if (this.#settle(cell, "cancelled",
|
|
263
|
+
if (this.#settle(cell, "cancelled", cancelledDetachedResult(cell))) cell.onKill?.(expiry.error);
|
|
264
264
|
return;
|
|
265
265
|
}
|
|
266
266
|
await this.#cancel(cell, expiry.error.message);
|
|
@@ -269,7 +269,7 @@ export class EvalDetachedCellManager {
|
|
|
269
269
|
#cancelQueued(cell: ManagedCell, reason: string, error = new Error(reason)): void {
|
|
270
270
|
const dequeued = cell.kernel?.cancelQueued(cell.cellId, reason) ?? false;
|
|
271
271
|
cell.stateRetained = true;
|
|
272
|
-
this.#settle(cell, "cancelled",
|
|
272
|
+
this.#settle(cell, "cancelled", cancelledDetachedResult(cell));
|
|
273
273
|
// Acquisition/reset has no queue promise yet; its execution has no interrupt target either.
|
|
274
274
|
if (!dequeued) cell.onKill?.(error);
|
|
275
275
|
}
|
|
@@ -278,7 +278,7 @@ export class EvalDetachedCellManager {
|
|
|
278
278
|
const outcome = Promise.withResolvers<void>();
|
|
279
279
|
cell.interruptOutcome = outcome;
|
|
280
280
|
try {
|
|
281
|
-
if (!this.#settle(cell, "cancelled",
|
|
281
|
+
if (!this.#settle(cell, "cancelled", cancelledDetachedResult(cell)) || cell.kernel === undefined) return;
|
|
282
282
|
const handle = await cell.kernel.interrupt(reason, cell.cellId);
|
|
283
283
|
cell.interruptNote = handle.note;
|
|
284
284
|
cell.stateRetained = await handle.stateRetained;
|
|
@@ -63,6 +63,23 @@ export function currentDetachedResult(cell: DetachedCellResultSource): AgentTool
|
|
|
63
63
|
return cell.terminalResult ?? cell.liveResult?.() ?? fallbackResult(cell.input);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* The result a cell cancelled before its handler finished settles with: its buffered output, not the live progress
|
|
68
|
+
* frame ("1/1 cells running"), so a stopped cell never reads as still running.
|
|
69
|
+
*/
|
|
70
|
+
export function cancelledDetachedResult(cell: DetachedCellResultSource): AgentToolResult<EvalToolDetails> {
|
|
71
|
+
if (cell.terminalResult !== undefined) return cell.terminalResult;
|
|
72
|
+
const current = currentDetachedResult(cell);
|
|
73
|
+
const output = detachedOutputTail(current);
|
|
74
|
+
return {
|
|
75
|
+
content: [
|
|
76
|
+
{ type: "text", text: output.length > 0 ? output : "(no output)" },
|
|
77
|
+
...current.content.filter((part) => part.type === "image"),
|
|
78
|
+
],
|
|
79
|
+
details: current.details,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
66
83
|
export function detachedErrorResult(cell: DetachedCellResultSource, error: Error): AgentToolResult<EvalToolDetails> {
|
|
67
84
|
const current = currentDetachedResult(cell);
|
|
68
85
|
const output = detachedOutputTail(current);
|
|
@@ -46,9 +46,7 @@ function createEvalListResult(cellManager: EvalDetachedCellManager): AgentToolRe
|
|
|
46
46
|
language: snapshot.language,
|
|
47
47
|
state: snapshot.state,
|
|
48
48
|
startedAtMs: snapshot.startedAtMs,
|
|
49
|
-
...(snapshot.queuedBehind === undefined
|
|
50
|
-
? {}
|
|
51
|
-
: { queuedBehind: [...snapshot.queuedBehind] }),
|
|
49
|
+
...(snapshot.queuedBehind === undefined ? {} : { queuedBehind: [...snapshot.queuedBehind] }),
|
|
52
50
|
...(summary ? { summary } : {}),
|
|
53
51
|
};
|
|
54
52
|
});
|
|
@@ -60,7 +58,7 @@ function createEvalListResult(cellManager: EvalDetachedCellManager): AgentToolRe
|
|
|
60
58
|
" ",
|
|
61
59
|
);
|
|
62
60
|
const elapsed = Math.floor(snapshot.result.details.durationMs / 1000);
|
|
63
|
-
const queued = cell.queuedBehind === undefined ? "" : `
|
|
61
|
+
const queued = cell.queuedBehind === undefined ? "" : ` ${queuedPhrase(cell.queuedBehind, cell.language)}`;
|
|
64
62
|
return `${cell.cellId} ${cell.language} ${cell.state} ${elapsed}s${queued} - ${preview}`;
|
|
65
63
|
})
|
|
66
64
|
.join("\n");
|
|
@@ -76,11 +74,13 @@ export function resultAfterDetach(
|
|
|
76
74
|
otherLiveCells: number,
|
|
77
75
|
): AgentToolResult<EvalToolDetails> {
|
|
78
76
|
if (snapshot.state !== "detached" && snapshot.state !== "running") return createDetachedControlResult(snapshot);
|
|
79
|
-
const
|
|
77
|
+
const queuedBehind = snapshot.queuedBehind;
|
|
80
78
|
const text =
|
|
81
|
-
|
|
79
|
+
queuedBehind === undefined
|
|
82
80
|
? `Eval cell ${snapshot.cellId} detached and is running in the ${input.language} kernel (${otherLiveCells} other live cells). Completion arrives as a notification; do not re-run it. eval({ action: "peek" | "stop", cell_id }) or eval({ action: "list" }).`
|
|
83
|
-
:
|
|
81
|
+
: queuedBehind.length === 0
|
|
82
|
+
? `Eval cell ${snapshot.cellId} is detached and waiting for the ${input.language} kernel to be ready; it runs first once the kernel is ready and completes as one notification. peek/stop/list with eval({ action, cell_id })`
|
|
83
|
+
: `Eval cell ${snapshot.cellId} is queued behind ${queuedBehind.join(", ")} in the ${input.language} kernel and detached; it runs after ${queuedBehind.join(", ")} and completes as one notification. peek/stop/list with eval({ action, cell_id })`;
|
|
84
84
|
return {
|
|
85
85
|
content: [
|
|
86
86
|
{
|
|
@@ -164,6 +164,12 @@ export function resultForDetachedState(
|
|
|
164
164
|
};
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
function queuedPhrase(queuedBehind: readonly string[], language: string): string {
|
|
168
|
+
return queuedBehind.length === 0
|
|
169
|
+
? `waiting for the ${language} kernel to be ready`
|
|
170
|
+
: `queued behind ${queuedBehind.join(", ")}`;
|
|
171
|
+
}
|
|
172
|
+
|
|
167
173
|
function textContent(result: AgentToolResult<EvalToolDetails>): string {
|
|
168
174
|
return result.content
|
|
169
175
|
.filter((part) => part.type === "text")
|
|
@@ -26,9 +26,8 @@ export async function describeTimeoutState(
|
|
|
26
26
|
);
|
|
27
27
|
if (outcome === undefined) error.message = fallbackTimeoutMessage(error.message);
|
|
28
28
|
else if (outcome.retained)
|
|
29
|
-
error.message = `${error.message} The kernel
|
|
30
|
-
else
|
|
31
|
-
error.message = `${error.message} The kernel was unresponsive and restarted; variables from earlier cells are lost.`;
|
|
29
|
+
error.message = `${error.message} The kernel was not restarted; variables from earlier cells are kept.`;
|
|
30
|
+
else error.message = `${error.message} The kernel was restarted; variables from earlier cells are lost.`;
|
|
32
31
|
if (outcome?.note !== undefined) error.message = `${error.message} ${outcome.note.trim()}`;
|
|
33
32
|
return error;
|
|
34
33
|
}
|
|
@@ -63,8 +62,8 @@ const LANGUAGE_LABEL: Record<EvalLanguage, string> = {
|
|
|
63
62
|
export function interruptionStateNote(language: EvalLanguage, stateRetained: boolean | undefined): string | undefined {
|
|
64
63
|
if (stateRetained === undefined) return undefined;
|
|
65
64
|
const label = LANGUAGE_LABEL[language];
|
|
66
|
-
if (stateRetained) return
|
|
67
|
-
return
|
|
65
|
+
if (stateRetained) return `The ${label} was not restarted; variables from earlier cells are kept.`;
|
|
66
|
+
return `The ${label} was restarted; variables from earlier cells are lost.`;
|
|
68
67
|
}
|
|
69
68
|
|
|
70
69
|
export function unknownInterruptionStateNote(language: EvalLanguage): string {
|
package/src/tool/render.ts
CHANGED
|
@@ -344,6 +344,8 @@ function cellHeader(cell: EvalCellResult, environment: RenderEnvironment, badges
|
|
|
344
344
|
let header = `eval ${cell.language}${runtimeBadge} ${presentation.label} ${presentation.icon}`;
|
|
345
345
|
if (cell.queuedBehind !== undefined && cell.queuedBehind.length > 0)
|
|
346
346
|
header += ` · queued behind ${cell.queuedBehind.map(sanitizeTerminalLabel).join(", ")}`;
|
|
347
|
+
else if (cell.queuedBehind !== undefined && cell.status === "queued")
|
|
348
|
+
header += ` · waiting for the ${cell.language} kernel to be ready`;
|
|
347
349
|
const throughputBadge = badges.throughput === undefined ? undefined : formatThroughputBadge(badges.throughput);
|
|
348
350
|
if (throughputBadge !== undefined) header += ` · ${throughputBadge}`;
|
|
349
351
|
const elapsedMs = badges.throughput?.wallDurationMs ?? cellElapsedMs(cell, environment);
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
type KernelToolsCapability,
|
|
15
15
|
type KernelToolsDescribeResult,
|
|
16
16
|
} from "../kernels/js/kernel-tools-types.ts";
|
|
17
|
-
import { sandboxCellExecutor } from "../kernels/sandbox/sandbox-cell.ts";
|
|
17
|
+
import { sandboxCellExecutor, sandboxRuntimeInfo } from "../kernels/sandbox/sandbox-cell.ts";
|
|
18
18
|
import { TIMEOUT_PAUSE_OP, TIMEOUT_RESUME_OP } from "../timeouts/bridge-timeout.ts";
|
|
19
19
|
import { abortError, CellExecution, defaultTimeoutFactory } from "./cell-execution.ts";
|
|
20
20
|
import { CellHandler, type CellState } from "./cell-handler.ts";
|
|
@@ -44,7 +44,8 @@ export async function runEvalCell(
|
|
|
44
44
|
const bridgeAbortController = new AbortController();
|
|
45
45
|
const cellSignal = AbortSignal.any([invocation.signal, bridgeAbortController.signal]);
|
|
46
46
|
const bridgeContext: ExtensionContext = { ...invocation.ctx, signal: cellSignal };
|
|
47
|
-
const runtime =
|
|
47
|
+
const runtime =
|
|
48
|
+
invocation.input.isolate === true ? sandboxRuntimeInfo() : options.runtimes?.[invocation.input.language];
|
|
48
49
|
const state: CellState = {
|
|
49
50
|
input: invocation.input,
|
|
50
51
|
...(runtime === undefined ? {} : { runtime }),
|
|
@@ -11,7 +11,7 @@ const ELLIPSIS = "\u2026";
|
|
|
11
11
|
*/
|
|
12
12
|
export function formatRuntimeBadge(language: EvalLanguage, runtime: EvalRuntimeInfo, home: string = homedir()): string {
|
|
13
13
|
const base = language === "js" ? `${runtime.name} ${runtime.version}` : runtime.version;
|
|
14
|
-
const label = runtime.isolation ===
|
|
14
|
+
const label = runtime.isolation === undefined ? base : `${base}, ${runtime.isolation}`;
|
|
15
15
|
if (runtime.path === undefined || runtime.path.length === 0) return label;
|
|
16
16
|
return `${label}, ${minifyPath(runtime.path, home)}`;
|
|
17
17
|
}
|
package/src/tool/types.ts
CHANGED
|
@@ -279,8 +279,8 @@ export interface EvalRuntimeInfo {
|
|
|
279
279
|
readonly name: string;
|
|
280
280
|
readonly version: string;
|
|
281
281
|
readonly path?: string;
|
|
282
|
-
/** JS only: the kernel runs in a child process
|
|
283
|
-
readonly isolation?: "process";
|
|
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";
|
|
284
284
|
}
|
|
285
285
|
|
|
286
286
|
export type EvalRuntimes = Readonly<Partial<Record<EvalLanguage, EvalRuntimeInfo>>>;
|