@code-yeongyu/senpi-codemode 2026.10.4 → 2026.10.6
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 +37 -0
- package/README.md +27 -2
- package/package.json +4 -4
- package/src/bridge/kernel-tools-protocol.ts +1 -1
- package/src/bridge/protocol.ts +11 -0
- package/src/config/feature-settings.ts +110 -0
- package/src/config/memory-settings.ts +8 -0
- package/src/config/settings-constants.ts +7 -0
- package/src/config/settings-overrides.ts +72 -0
- package/src/config/settings.ts +35 -78
- package/src/config/unknown-settings.ts +19 -0
- package/src/extension/bridge-tool-call.ts +37 -0
- package/src/extension/idle-parking-kernel.ts +272 -0
- package/src/extension/kernel-registration.ts +25 -0
- package/src/extension/kernel-replacement.ts +230 -0
- package/src/extension/kernel-tools-probe.ts +24 -0
- package/src/extension/session-manager.ts +49 -38
- package/src/kernels/jl/kernel.ts +6 -2
- package/src/kernels/jl/runner.jl +154 -0
- package/src/kernels/js/context-manager.ts +1 -1
- package/src/kernels/js/kernel-tools-types.ts +2 -1
- package/src/kernels/js/worker-core.js +1 -0
- package/src/kernels/js/worker-global-sizes.js +84 -14
- package/src/kernels/py/kernel-contract.ts +2 -1
- package/src/kernels/py/kernel.ts +48 -8
- package/src/kernels/py/prelude.py +6 -1
- package/src/kernels/py/transport.ts +25 -4
- package/src/kernels/rb/kernel.ts +6 -2
- package/src/kernels/rb/runner.rb +141 -0
- package/src/kernels/session-env.ts +3 -0
- package/src/kernels/shared/kernel-death.ts +39 -0
- package/src/kernels/shared/kernel-memory-host.ts +14 -6
- package/src/kernels/{js/kernel-tools-host.ts → shared/kernel-tools-pump.ts} +2 -2
- package/src/kernels/shared/process-group-cpu.ts +139 -0
- package/src/kernels/shared/subprocess-contract.ts +5 -1
- package/src/kernels/shared/subprocess-kernel.ts +66 -3
- package/src/kernels/shared/subprocess-queue.ts +5 -0
- package/src/kernels/shared/subprocess-startup.ts +90 -0
- package/src/tool/cell-runtime.ts +17 -4
- package/src/tool/detached-cell-notification.ts +13 -2
- package/src/tool/run-eval-cell.ts +5 -6
- package/src/tool/types.ts +14 -0
|
@@ -2,8 +2,10 @@ import type { KernelMemoryThresholds } from "../../bridge/memory-protocol.ts";
|
|
|
2
2
|
import type { BridgeConnectionConfig, KernelToHostMessage } from "../../bridge/protocol.ts";
|
|
3
3
|
import type { EvalKernelRunInput, EvalLanguage } from "../../tool/types.ts";
|
|
4
4
|
import type { SessionEnvironment } from "../session-env.ts";
|
|
5
|
+
import type { KernelLifecycle } from "./kernel-death.ts";
|
|
5
6
|
import type { FootprintReader } from "./kernel-memory-host.ts";
|
|
6
7
|
import type { SubprocessSpawn } from "./subprocess-process.ts";
|
|
8
|
+
import type { SubprocessStartupOptions } from "./subprocess-startup.ts";
|
|
7
9
|
|
|
8
10
|
export type KernelRunInput = EvalKernelRunInput;
|
|
9
11
|
|
|
@@ -16,7 +18,7 @@ export interface SubprocessKernelMemory {
|
|
|
16
18
|
readonly readFootprint: FootprintReader;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
export interface SubprocessKernelOptions {
|
|
21
|
+
export interface SubprocessKernelOptions extends KernelLifecycle {
|
|
20
22
|
readonly command: string;
|
|
21
23
|
readonly args: readonly string[];
|
|
22
24
|
readonly cwd?: string;
|
|
@@ -28,4 +30,6 @@ export interface SubprocessKernelOptions {
|
|
|
28
30
|
readonly spawn?: SubprocessSpawn;
|
|
29
31
|
readonly onMessage?: (message: KernelToHostMessage) => void;
|
|
30
32
|
readonly memory?: SubprocessKernelMemory & { readonly language: EvalLanguage };
|
|
33
|
+
/** Fails a start that stops making progress before `ready`; absent, the kernel waits for `ready` indefinitely. */
|
|
34
|
+
readonly startup?: SubprocessStartupOptions;
|
|
31
35
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { HostToKernelMessage, KernelToHostMessage } from "../../bridge/protocol.ts";
|
|
2
2
|
import { decodeBridgeFrame, encodeBridgeFrame, isKernelToHostMessage } from "../../bridge/protocol.ts";
|
|
3
|
-
import type { KernelInterruptHandle } from "../../tool/types.ts";
|
|
3
|
+
import type { KernelInterruptHandle, PendingCell } from "../../tool/types.ts";
|
|
4
4
|
import type { KernelToolsInvokeOptions } from "../js/kernel-tools-types.ts";
|
|
5
5
|
import { rejectKernelToolsUnavailable } from "../kernel-tools-unavailable.ts";
|
|
6
6
|
import { applySessionEnvironment } from "../session-env.ts";
|
|
7
|
+
import { describeExit } from "./kernel-death.ts";
|
|
7
8
|
import { KernelMemoryHost } from "./kernel-memory-host.ts";
|
|
8
9
|
import type { KernelResult, KernelRunInput, SubprocessKernelOptions, ToolCallMessage } from "./subprocess-contract.ts";
|
|
9
10
|
import { type SubprocessLike, SubprocessProcess, type SubprocessSpawn, spawnSubprocess } from "./subprocess-process.ts";
|
|
@@ -19,8 +20,10 @@ import {
|
|
|
19
20
|
KernelRetirementError,
|
|
20
21
|
KernelStartupError,
|
|
21
22
|
type PendingRun,
|
|
23
|
+
settlePendingRun,
|
|
22
24
|
timeoutResult,
|
|
23
25
|
} from "./subprocess-run.ts";
|
|
26
|
+
import { SubprocessStartupWatchdog } from "./subprocess-startup.ts";
|
|
24
27
|
|
|
25
28
|
export type {
|
|
26
29
|
KernelResult,
|
|
@@ -38,11 +41,13 @@ export class SubprocessKernel {
|
|
|
38
41
|
private readonly memory: KernelMemoryHost | null;
|
|
39
42
|
private process: SubprocessProcess | null = null;
|
|
40
43
|
private processReady = false;
|
|
44
|
+
private startup: SubprocessStartupWatchdog | null = null;
|
|
41
45
|
private retirementPromise: Promise<void> | null = null;
|
|
42
46
|
private retirementProcess: SubprocessProcess | null = null;
|
|
43
47
|
private retirementFailure: Error | null = null;
|
|
44
48
|
private failure: Error | null = null;
|
|
45
49
|
private closed = false;
|
|
50
|
+
private dead = false;
|
|
46
51
|
|
|
47
52
|
constructor(options: SubprocessKernelOptions) {
|
|
48
53
|
this.options = options;
|
|
@@ -52,6 +57,14 @@ export class SubprocessKernel {
|
|
|
52
57
|
this.spawnProcess();
|
|
53
58
|
}
|
|
54
59
|
|
|
60
|
+
isAlive(): boolean {
|
|
61
|
+
return !this.dead;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
drainPending(): readonly PendingCell[] {
|
|
65
|
+
return this.runs.drain().map((run) => ({ input: run.input, settle: (result) => settlePendingRun(run, result) }));
|
|
66
|
+
}
|
|
67
|
+
|
|
55
68
|
run(input: KernelRunInput): Promise<KernelResult> {
|
|
56
69
|
if (this.closed) throw new KernelClosedError();
|
|
57
70
|
const run = this.runs.enqueue(input);
|
|
@@ -122,6 +135,7 @@ export class SubprocessKernel {
|
|
|
122
135
|
async close(): Promise<void> {
|
|
123
136
|
const wasClosed = this.closed;
|
|
124
137
|
this.closed = true;
|
|
138
|
+
this.startup?.stop();
|
|
125
139
|
if (!wasClosed) {
|
|
126
140
|
this.settleAll(new KernelClosingError());
|
|
127
141
|
this.runs.clearToolCalls();
|
|
@@ -186,10 +200,21 @@ export class SubprocessKernel {
|
|
|
186
200
|
});
|
|
187
201
|
this.process = process;
|
|
188
202
|
this.processReady = false;
|
|
203
|
+
this.startup?.stop();
|
|
204
|
+
const startup = this.options.startup;
|
|
205
|
+
this.startup = startup
|
|
206
|
+
? new SubprocessStartupWatchdog(startup, child.pid, (message) => this.stalled(process, message))
|
|
207
|
+
: null;
|
|
189
208
|
this.memory?.processReplaced();
|
|
190
209
|
try {
|
|
210
|
+
const thresholds = this.options.memory?.thresholds;
|
|
191
211
|
process.send(
|
|
192
|
-
encodeBridgeFrame({
|
|
212
|
+
encodeBridgeFrame({
|
|
213
|
+
type: "init",
|
|
214
|
+
sessionId: this.options.sessionId,
|
|
215
|
+
connection: this.options.connection,
|
|
216
|
+
...(thresholds === undefined ? {} : { memory: thresholds }),
|
|
217
|
+
}),
|
|
193
218
|
);
|
|
194
219
|
} catch (error) {
|
|
195
220
|
const failure = new KernelStartupError(error instanceof Error ? error.message : String(error));
|
|
@@ -210,7 +235,9 @@ export class SubprocessKernel {
|
|
|
210
235
|
|
|
211
236
|
private handleMessage(process: SubprocessProcess, message: KernelToHostMessage): void {
|
|
212
237
|
if (!this.accepts(process)) return;
|
|
238
|
+
if (!this.processReady) this.startup?.observe(message);
|
|
213
239
|
if (message.type === "ready") {
|
|
240
|
+
this.startup?.stop();
|
|
214
241
|
this.processReady = true;
|
|
215
242
|
this.runs.handleMessage(message, this.onMessage);
|
|
216
243
|
this.pumpRuns();
|
|
@@ -236,6 +263,7 @@ export class SubprocessKernel {
|
|
|
236
263
|
|
|
237
264
|
private handleExit(process: SubprocessProcess, code: number | null, signal: NodeJS.Signals | null): void {
|
|
238
265
|
if (this.process !== process) return;
|
|
266
|
+
this.startup?.stop();
|
|
239
267
|
if (process.isRetiring) {
|
|
240
268
|
this.process = null;
|
|
241
269
|
this.retirementFailure = null;
|
|
@@ -243,7 +271,41 @@ export class SubprocessKernel {
|
|
|
243
271
|
}
|
|
244
272
|
this.process = null;
|
|
245
273
|
this.processReady = false;
|
|
246
|
-
|
|
274
|
+
const error = new KernelExitedError(signal ?? code ?? "unknown");
|
|
275
|
+
if (this.options.onDeath) this.die(error, describeExit(code, signal), this.options.onDeath);
|
|
276
|
+
else this.failClosed(error);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* The interpreter exited on its own and is gone: the running cell fails once (its side effects may
|
|
281
|
+
* already have happened), the cells that never started stay queued for `drainPending`, and the owner
|
|
282
|
+
* replaces this instance. `run()` is never reached on it again.
|
|
283
|
+
*/
|
|
284
|
+
private die(error: Error, reason: string, onDeath: (reason: string) => void): void {
|
|
285
|
+
this.failure = error;
|
|
286
|
+
this.closed = true;
|
|
287
|
+
this.dead = true;
|
|
288
|
+
this.runs.clearToolCalls();
|
|
289
|
+
const active = this.runs.active;
|
|
290
|
+
if (active) {
|
|
291
|
+
this.runs.releaseActive(active);
|
|
292
|
+
this.runs.settle(active, failureResult(active, new Error(`${error.message}; every global is lost`)));
|
|
293
|
+
}
|
|
294
|
+
onDeath(reason);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** A stalled start is a death: the owner replaces the kernel and the cells that never started run there. */
|
|
298
|
+
private stalled(process: SubprocessProcess, message: string): void {
|
|
299
|
+
if (!this.accepts(process)) return;
|
|
300
|
+
const error = new KernelStartupError(message);
|
|
301
|
+
const onDeath = this.options.onDeath;
|
|
302
|
+
if (!onDeath) {
|
|
303
|
+
this.failClosed(error);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
process.retire();
|
|
307
|
+
this.trackRetirement(process, this.terminateOwnedProcess(process));
|
|
308
|
+
this.die(error, message, onDeath);
|
|
247
309
|
}
|
|
248
310
|
|
|
249
311
|
private accepts(process: SubprocessProcess): boolean {
|
|
@@ -299,6 +361,7 @@ export class SubprocessKernel {
|
|
|
299
361
|
}
|
|
300
362
|
|
|
301
363
|
private failClosed(error: Error): void {
|
|
364
|
+
this.startup?.stop();
|
|
302
365
|
const process = this.process;
|
|
303
366
|
this.failure = error;
|
|
304
367
|
this.closed = true;
|
|
@@ -44,6 +44,11 @@ export class SubprocessRunQueue {
|
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/** Removes every queued run that has not started, oldest first. */
|
|
48
|
+
drain(): PendingRun[] {
|
|
49
|
+
return this.#queue.splice(0);
|
|
50
|
+
}
|
|
51
|
+
|
|
47
52
|
releaseActive(run: PendingRun): boolean {
|
|
48
53
|
if (this.#active !== run) return false;
|
|
49
54
|
this.#active = null;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { KernelToHostMessage } from "../../bridge/protocol.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* How long a starting interpreter may stay completely still - no output line, no new startup stage, and
|
|
5
|
+
* no CPU used by its process group - before startup fails. A cold Julia compiling its prelude prints
|
|
6
|
+
* nothing for seconds but keeps the CPU busy, so it never trips; only a runner that is silent AND idle
|
|
7
|
+
* (blocked on a lock, a pipe or a stopped process) does. It restarts on every sign of progress, so it is
|
|
8
|
+
* not a total startup budget. Where the group's CPU cannot be read it never fires.
|
|
9
|
+
*/
|
|
10
|
+
export const subprocessStartupNoProgressMs = 30_000;
|
|
11
|
+
|
|
12
|
+
const STDERR_TAIL_CHARS = 2_000;
|
|
13
|
+
const stages = ["interpreter-launch", "stdlib-imports", "runtime-init", "host-init"] as const;
|
|
14
|
+
export type SubprocessStartupStage = (typeof stages)[number];
|
|
15
|
+
|
|
16
|
+
export interface SubprocessStartupOptions {
|
|
17
|
+
readonly label: string;
|
|
18
|
+
readonly noProgressMs?: number;
|
|
19
|
+
/** CPU time used so far by a process group (any unit that only grows); `undefined` when unreadable. */
|
|
20
|
+
readonly readGroupCpuTime?: (pgid: number) => bigint | undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class SubprocessStartupWatchdog {
|
|
24
|
+
readonly #options: SubprocessStartupOptions;
|
|
25
|
+
readonly #pid: number | undefined;
|
|
26
|
+
readonly #onStall: (message: string) => void;
|
|
27
|
+
readonly #noProgressMs: number;
|
|
28
|
+
#stageIndex = 0;
|
|
29
|
+
#stderrTail = "";
|
|
30
|
+
#lastCpu: bigint | undefined;
|
|
31
|
+
#timer: ReturnType<typeof setTimeout> | undefined;
|
|
32
|
+
#stopped = false;
|
|
33
|
+
|
|
34
|
+
constructor(options: SubprocessStartupOptions, pid: number | undefined, onStall: (message: string) => void) {
|
|
35
|
+
this.#options = options;
|
|
36
|
+
this.#pid = pid;
|
|
37
|
+
this.#onStall = onStall;
|
|
38
|
+
this.#noProgressMs = options.noProgressMs ?? subprocessStartupNoProgressMs;
|
|
39
|
+
this.#lastCpu = this.#readCpu();
|
|
40
|
+
this.#arm();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Any message the runner sent before `ready` is progress; a later startup stage also moves the stage. */
|
|
44
|
+
observe(message: KernelToHostMessage): void {
|
|
45
|
+
if (this.#stopped) return;
|
|
46
|
+
if (message.type === "status" && message.event.op === "kernel-startup") {
|
|
47
|
+
const stage = stages.find((candidate) => candidate === message.event.stage);
|
|
48
|
+
const next = stage === undefined ? -1 : stages.indexOf(stage);
|
|
49
|
+
if (next > this.#stageIndex) this.#stageIndex = next;
|
|
50
|
+
}
|
|
51
|
+
if (message.type === "text" && message.stream === "stderr") {
|
|
52
|
+
this.#stderrTail = (this.#stderrTail + message.data).slice(-STDERR_TAIL_CHARS);
|
|
53
|
+
}
|
|
54
|
+
this.#arm();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
stop(): void {
|
|
58
|
+
this.#stopped = true;
|
|
59
|
+
clearTimeout(this.#timer);
|
|
60
|
+
this.#timer = undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#arm(): void {
|
|
64
|
+
clearTimeout(this.#timer);
|
|
65
|
+
this.#timer = setTimeout(() => this.#expire(), this.#noProgressMs);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
#expire(): void {
|
|
69
|
+
if (this.#stopped) return;
|
|
70
|
+
const cpu = this.#readCpu();
|
|
71
|
+
// Unreadable CPU means busy and idle look the same: never cut a start off on silence alone.
|
|
72
|
+
// Any change counts, a drop included: a member of the group exited, which is activity.
|
|
73
|
+
if (cpu === undefined || cpu !== this.#lastCpu) {
|
|
74
|
+
this.#lastCpu = cpu;
|
|
75
|
+
this.#arm();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
this.stop();
|
|
79
|
+
const stage = stages[this.#stageIndex] ?? "interpreter-launch";
|
|
80
|
+
const seconds = Math.round(this.#noProgressMs / 1000);
|
|
81
|
+
const tail = this.#stderrTail.trim();
|
|
82
|
+
this.#onStall(
|
|
83
|
+
`${this.#options.label} kernel stalled at ${stage}: no output, stage change or CPU use for ${seconds} s${tail ? `; stderr: ${tail}` : ""}`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
#readCpu(): bigint | undefined {
|
|
88
|
+
return this.#pid === undefined ? undefined : this.#options.readGroupCpuTime?.(this.#pid);
|
|
89
|
+
}
|
|
90
|
+
}
|
package/src/tool/cell-runtime.ts
CHANGED
|
@@ -5,7 +5,14 @@ import { formatModelTruncationNotice } from "../output/output-meta.ts";
|
|
|
5
5
|
import { DEFAULT_MAX_BYTES, TailLineRing } from "../output/streaming-output.ts";
|
|
6
6
|
import type { EvalToolCallMetric } from "./call-capture.ts";
|
|
7
7
|
import { type EvalImageResizer, EvalOutputCollector, type EvalOutputResult } from "./image.ts";
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
EvalKernelState,
|
|
10
|
+
EvalMemoryDetails,
|
|
11
|
+
EvalRuntimeInfo,
|
|
12
|
+
EvalStatusEvent,
|
|
13
|
+
EvalToolDetails,
|
|
14
|
+
EvalToolInput,
|
|
15
|
+
} from "./types.ts";
|
|
9
16
|
|
|
10
17
|
const LIVE_UPDATE_LINES = 8;
|
|
11
18
|
/** Same cadence as the core bash tool's streaming updates (BASH_UPDATE_THROTTLE_MS). */
|
|
@@ -48,6 +55,8 @@ export class CellResultBuilder {
|
|
|
48
55
|
readonly #output: EvalOutputCollector;
|
|
49
56
|
readonly #state: CellState;
|
|
50
57
|
#memory: KernelMemoryReport | undefined;
|
|
58
|
+
#restartNotice: string | undefined;
|
|
59
|
+
#kernelState: EvalKernelState | undefined;
|
|
51
60
|
readonly #liveLines = new TailLineRing({ maxBytes: DEFAULT_MAX_BYTES * 2, maxLines: LIVE_UPDATE_LINES });
|
|
52
61
|
#lastOutputUpdateAt = 0;
|
|
53
62
|
#outputUpdateTimer: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -85,6 +94,8 @@ export class CellResultBuilder {
|
|
|
85
94
|
async finalize(result: KernelResult): Promise<AgentToolResult<EvalToolDetails>> {
|
|
86
95
|
this.#state.durationMs = result.durationMs;
|
|
87
96
|
this.#memory = result.memory;
|
|
97
|
+
this.#restartNotice = result.notice;
|
|
98
|
+
this.#kernelState = result.kernelState;
|
|
88
99
|
if (result.ok) {
|
|
89
100
|
if (result.valueRepr) this.#output.pushValue(`${result.valueRepr}\n`);
|
|
90
101
|
this.#state.status = "complete";
|
|
@@ -160,9 +171,10 @@ export class CellResultBuilder {
|
|
|
160
171
|
? `(displayed ${output.images.length} image${output.images.length === 1 ? "" : "s"}; no text output)`
|
|
161
172
|
: "(no output)");
|
|
162
173
|
const text = output.meta === undefined ? shown : `${shown}\n${formatModelTruncationNotice(output.meta)}`;
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
174
|
+
const noticeParts = [this.#restartNotice, this.#memory?.notice].flatMap((notice) =>
|
|
175
|
+
notice === undefined ? [] : [{ type: "text" as const, text: notice }],
|
|
176
|
+
);
|
|
177
|
+
return { content: [{ type: "text", text }, ...noticeParts, ...output.images], details };
|
|
166
178
|
}
|
|
167
179
|
|
|
168
180
|
#details(output: EvalOutputResult | undefined, isError: boolean): EvalToolDetails {
|
|
@@ -202,6 +214,7 @@ export class CellResultBuilder {
|
|
|
202
214
|
...(output?.notice === undefined ? {} : { notice: output.notice }),
|
|
203
215
|
...(output?.meta === undefined ? {} : { meta: output.meta }),
|
|
204
216
|
...(output === undefined || this.#memory === undefined ? {} : { memory: memoryDetails(this.#memory) }),
|
|
217
|
+
...(this.#kernelState === undefined ? {} : { kernelState: this.#kernelState }),
|
|
205
218
|
};
|
|
206
219
|
}
|
|
207
220
|
|
|
@@ -2,7 +2,7 @@ import { mkdir, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import type { EvalDetachedCellNotification, EvalDetachedCellSnapshot } from "./detached-cell-manager.ts";
|
|
4
4
|
import { interruptionStateNote, unknownInterruptionStateNote } from "./interrupt-note.ts";
|
|
5
|
-
import type { EvalMemoryDetails } from "./types.ts";
|
|
5
|
+
import type { EvalKernelState, EvalMemoryDetails } from "./types.ts";
|
|
6
6
|
|
|
7
7
|
const NOTIFICATION_TAIL_BYTES = 512;
|
|
8
8
|
|
|
@@ -78,11 +78,22 @@ function outcomeOf(cell: EvalDetachedCellSnapshot): string {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
function stateNoteOf(cell: EvalDetachedCellSnapshot): string {
|
|
81
|
-
if (cell.state !== "cancelled")
|
|
81
|
+
if (cell.state !== "cancelled") {
|
|
82
|
+
const kernelState = cell.result.details?.kernelState;
|
|
83
|
+
return kernelState === undefined ? memoryStateNote(cell.result.details?.memory) : KERNEL_STATE_NOTES[kernelState];
|
|
84
|
+
}
|
|
82
85
|
const note = interruptionStateNote(cell.language, cell.stateRetained) ?? unknownInterruptionStateNote(cell.language);
|
|
83
86
|
return cell.interruptNote === undefined ? note : `${note} ${cell.interruptNote.trim()}`;
|
|
84
87
|
}
|
|
85
88
|
|
|
89
|
+
/** A kernel death decides what survived, whatever the memory report says. */
|
|
90
|
+
const KERNEL_STATE_NOTES: Readonly<Record<EvalKernelState, string>> = {
|
|
91
|
+
lost: "The kernel died while this cell ran - every global is lost; the next eval cell runs on a fresh kernel.",
|
|
92
|
+
restarted:
|
|
93
|
+
"The kernel was restarted before this cell ran - globals from earlier cells are gone; this cell's variables are available to the next eval cell.",
|
|
94
|
+
"not-run": "This cell never ran and changed no kernel state.",
|
|
95
|
+
};
|
|
96
|
+
|
|
86
97
|
export function memoryStateNote(memory: EvalMemoryDetails | undefined): string {
|
|
87
98
|
if (memory?.overCeiling === true)
|
|
88
99
|
return "Kernel memory is over its ceiling - the kernel restarts before the next eval cell and every global is lost.";
|
|
@@ -209,7 +209,7 @@ async function executeCell(
|
|
|
209
209
|
const kernel = await execution.wait(options.kernelManager.getKernel(invocation.input.language, onMessage));
|
|
210
210
|
// Computed before the handler so its construction-time snapshot captures this cell's capability.
|
|
211
211
|
// Worker messages later restore that snapshot before calling host tools (#1754, #2512).
|
|
212
|
-
const kernelTools =
|
|
212
|
+
const kernelTools = kernelToolsFor(kernel);
|
|
213
213
|
const runBound = async (): Promise<AgentToolResult<EvalToolDetails>> => {
|
|
214
214
|
const queue = kernel.queueSnapshot();
|
|
215
215
|
state.queuedBehind = [...(queue.activeCellId === null ? [] : [queue.activeCellId]), ...queue.queuedCellIds];
|
|
@@ -292,16 +292,15 @@ async function executeCell(
|
|
|
292
292
|
}
|
|
293
293
|
}
|
|
294
294
|
|
|
295
|
-
function
|
|
296
|
-
if (language !== "js") return undefined;
|
|
295
|
+
function kernelToolsFor(kernel: EvalKernel): KernelToolsCapability | undefined {
|
|
297
296
|
if (!("describeKernelTools" in kernel) || typeof kernel.describeKernelTools !== "function") return undefined;
|
|
298
|
-
const
|
|
297
|
+
const withTools = kernel as EvalKernel & {
|
|
299
298
|
describeKernelTools: (names: readonly string[]) => Promise<KernelToolsDescribeResult>;
|
|
300
299
|
invokeKernelTool: ExtensionKernelTools["invoke"];
|
|
301
300
|
};
|
|
302
301
|
return {
|
|
303
302
|
capabilities: KERNEL_TOOLS_CAPABILITIES,
|
|
304
|
-
describe: (names) =>
|
|
305
|
-
invoke: (request, options) =>
|
|
303
|
+
describe: (names) => withTools.describeKernelTools(names),
|
|
304
|
+
invoke: (request, options) => withTools.invokeKernelTool(request, options),
|
|
306
305
|
} satisfies ExtensionKernelTools;
|
|
307
306
|
}
|
package/src/tool/types.ts
CHANGED
|
@@ -161,6 +161,12 @@ export interface KernelInterruptHandle {
|
|
|
161
161
|
readonly note?: string;
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
/** An unstarted cell a dead kernel hands back, so its replacement runs it with the same input and callbacks. */
|
|
165
|
+
export interface PendingCell {
|
|
166
|
+
readonly input: EvalKernelRunInput;
|
|
167
|
+
readonly settle: (result: EvalKernelResult) => void;
|
|
168
|
+
}
|
|
169
|
+
|
|
164
170
|
export interface EvalKernel {
|
|
165
171
|
run(input: EvalKernelRunInput): Promise<EvalKernelResult>;
|
|
166
172
|
cancelQueued(cellId: string, reason: string): boolean;
|
|
@@ -171,6 +177,10 @@ export interface EvalKernel {
|
|
|
171
177
|
close(): Promise<void>;
|
|
172
178
|
/** Names this kernel has registered; JS collides with other languages in the same session. */
|
|
173
179
|
listKernelToolNames?(): readonly string[];
|
|
180
|
+
/** False once the interpreter died on its own; the session manager then replaces the instance. */
|
|
181
|
+
isAlive?(): boolean;
|
|
182
|
+
/** Hands over, and forgets, every queued cell of a dead kernel that never started. */
|
|
183
|
+
drainPending?(): readonly PendingCell[];
|
|
174
184
|
}
|
|
175
185
|
|
|
176
186
|
export interface EvalKernelManager {
|
|
@@ -273,6 +283,10 @@ export interface EvalToolDetails {
|
|
|
273
283
|
readonly meta?: TruncationMeta;
|
|
274
284
|
/** Kernel memory after the cell; its notice text is delivered as its own content part. */
|
|
275
285
|
readonly memory?: EvalMemoryDetails;
|
|
286
|
+
/** What a kernel death did to this cell's state, when one did. */
|
|
287
|
+
readonly kernelState?: EvalKernelState;
|
|
276
288
|
}
|
|
277
289
|
|
|
290
|
+
export type EvalKernelState = NonNullable<EvalKernelResult["kernelState"]>;
|
|
291
|
+
|
|
278
292
|
export type EvalMemoryDetails = Omit<KernelMemoryReport, "notice">;
|