@code-yeongyu/senpi-codemode 2026.10.5 → 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 +22 -0
- package/README.md +16 -2
- package/package.json +4 -4
- package/src/bridge/kernel-tools-protocol.ts +1 -1
- 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-tools-probe.ts +24 -0
- package/src/extension/session-manager.ts +42 -55
- package/src/kernels/jl/kernel.ts +3 -1
- 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-global-sizes.js +84 -14
- package/src/kernels/py/prelude.py +6 -1
- package/src/kernels/rb/kernel.ts +3 -1
- package/src/kernels/rb/runner.rb +141 -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 +3 -0
- package/src/kernels/shared/subprocess-kernel.ts +33 -1
- package/src/kernels/shared/subprocess-startup.ts +90 -0
- package/src/tool/run-eval-cell.ts +5 -6
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import type { HostToKernelMessage } from "../bridge/protocol.ts";
|
|
2
|
+
import { kernelToolError } from "../kernels/js/kernel-tools-errors.ts";
|
|
3
|
+
import { restartNotice, unstartedResult } from "../kernels/shared/kernel-death.ts";
|
|
4
|
+
import type {
|
|
5
|
+
EvalKernel,
|
|
6
|
+
EvalKernelResult,
|
|
7
|
+
EvalKernelRunInput,
|
|
8
|
+
EvalLanguage,
|
|
9
|
+
KernelInterruptHandle,
|
|
10
|
+
} from "../tool/types.ts";
|
|
11
|
+
import { hasKernelTools, type KernelToolsMethods } from "./kernel-tools-probe.ts";
|
|
12
|
+
|
|
13
|
+
export type RestartParkedKernel = () => Promise<EvalKernel>;
|
|
14
|
+
|
|
15
|
+
class IdleParkingKernelClosedError extends Error {
|
|
16
|
+
readonly name = "IdleParkingKernelClosedError";
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
super("Kernel closed");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface WaitingCell {
|
|
24
|
+
readonly cancel: (reason: string) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Opt-in (`memory.idleParkMinutes` > 0): once no cell has been running or queued on the session's kernel for
|
|
29
|
+
* the configured time, closes it to give its memory back, and starts a fresh one when the next cell arrives.
|
|
30
|
+
* That cell's result says the kernel was restarted and every global is gone. A cell submitted while the
|
|
31
|
+
* kernel is being parked or restarted waits for the fresh kernel; a restart that fails settles that cell
|
|
32
|
+
* with the reason, and the next cell tries again.
|
|
33
|
+
*/
|
|
34
|
+
export class IdleParkingKernel implements EvalKernel {
|
|
35
|
+
readonly #language: EvalLanguage;
|
|
36
|
+
readonly #idleMs: number;
|
|
37
|
+
readonly #restart: RestartParkedKernel;
|
|
38
|
+
readonly #onParked: () => void;
|
|
39
|
+
#kernel: EvalKernel | null;
|
|
40
|
+
#inFlight = 0;
|
|
41
|
+
#timer: ReturnType<typeof setTimeout> | undefined;
|
|
42
|
+
#parking: Promise<void> | null = null;
|
|
43
|
+
#parkFailure: string | null = null;
|
|
44
|
+
#starting: Promise<EvalKernel> | null = null;
|
|
45
|
+
#announce: string | null = null;
|
|
46
|
+
readonly #waiting = new Map<string, WaitingCell>();
|
|
47
|
+
#closed = false;
|
|
48
|
+
// Present only when the wrapped kernel has kernel tools, so a probe for them sees what it saw before.
|
|
49
|
+
declare readonly describeKernelTools?: KernelToolsMethods["describeKernelTools"];
|
|
50
|
+
declare readonly invokeKernelTool?: KernelToolsMethods["invokeKernelTool"];
|
|
51
|
+
|
|
52
|
+
constructor(
|
|
53
|
+
language: EvalLanguage,
|
|
54
|
+
idleMinutes: number,
|
|
55
|
+
first: EvalKernel,
|
|
56
|
+
restart: RestartParkedKernel,
|
|
57
|
+
onParked: () => void = () => {},
|
|
58
|
+
) {
|
|
59
|
+
this.#language = language;
|
|
60
|
+
this.#idleMs = idleMinutes * 60_000;
|
|
61
|
+
this.#restart = restart;
|
|
62
|
+
this.#onParked = onParked;
|
|
63
|
+
this.#kernel = first;
|
|
64
|
+
if (hasKernelTools(first)) {
|
|
65
|
+
const tools: KernelToolsMethods = {
|
|
66
|
+
describeKernelTools: (names) => this.#withTools((kernel) => kernel.describeKernelTools(names)),
|
|
67
|
+
invokeKernelTool: (request, options) =>
|
|
68
|
+
this.#withTools((kernel) => kernel.invokeKernelTool(request, options)),
|
|
69
|
+
};
|
|
70
|
+
Object.assign(this, tools);
|
|
71
|
+
}
|
|
72
|
+
this.#arm();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
run(input: EvalKernelRunInput): Promise<EvalKernelResult> {
|
|
76
|
+
if (this.#closed) return Promise.reject(new IdleParkingKernelClosedError());
|
|
77
|
+
this.#disarm();
|
|
78
|
+
this.#inFlight++;
|
|
79
|
+
return this.#runOnCurrent(input).finally(() => {
|
|
80
|
+
this.#inFlight--;
|
|
81
|
+
this.#arm();
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
cancelQueued(cellId: string, reason: string): boolean {
|
|
86
|
+
if (this.#cancelWaiting(cellId, reason)) return true;
|
|
87
|
+
return this.#kernel?.cancelQueued(cellId, reason) ?? false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async interrupt(reason = "interrupted", cellId?: string): Promise<KernelInterruptHandle> {
|
|
91
|
+
if (cellId !== undefined && this.#cancelWaiting(cellId, reason)) return { stateRetained: Promise.resolve(true) };
|
|
92
|
+
if (cellId === undefined) for (const id of [...this.#waiting.keys()]) this.#cancelWaiting(id, reason);
|
|
93
|
+
const kernel = this.#kernel;
|
|
94
|
+
if (kernel === null) return { stateRetained: Promise.resolve(true) };
|
|
95
|
+
return await kernel.interrupt(reason, cellId);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
queueSnapshot(): { activeCellId: string | null; queuedCellIds: readonly string[] } {
|
|
99
|
+
const current = this.#kernel?.queueSnapshot() ?? { activeCellId: null, queuedCellIds: [] };
|
|
100
|
+
return { activeCellId: current.activeCellId, queuedCellIds: [...current.queuedCellIds, ...this.#waiting.keys()] };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
deliverToolReply(message: Extract<HostToKernelMessage, { type: "tool-reply" }>): void {
|
|
104
|
+
this.#kernel?.deliverToolReply(message);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async reset(): Promise<void> {
|
|
108
|
+
this.#announce = null;
|
|
109
|
+
const kernel = this.#kernel;
|
|
110
|
+
if (kernel !== null) await kernel.reset();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async close(): Promise<void> {
|
|
114
|
+
this.#closed = true;
|
|
115
|
+
this.#disarm();
|
|
116
|
+
for (const id of [...this.#waiting.keys()]) this.#cancelWaiting(id, new IdleParkingKernelClosedError().message);
|
|
117
|
+
if (this.#parking !== null) await this.#parking;
|
|
118
|
+
if (this.#starting !== null) await this.#starting.catch(() => undefined);
|
|
119
|
+
const kernel = this.#kernel;
|
|
120
|
+
this.#kernel = null;
|
|
121
|
+
if (kernel !== null) await kernel.close();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
listKernelToolNames(): readonly string[] {
|
|
125
|
+
return this.#kernel?.listKernelToolNames?.() ?? [];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** A tool call is work on the kernel: it keeps the kernel from being parked under it. */
|
|
129
|
+
async #withTools<T>(use: (kernel: KernelToolsMethods) => Promise<T>): Promise<T> {
|
|
130
|
+
const kernel = this.#parking === null ? this.#kernel : null;
|
|
131
|
+
if (this.#closed || kernel === null || !hasKernelTools(kernel)) {
|
|
132
|
+
throw kernelToolError(
|
|
133
|
+
"tools_unavailable",
|
|
134
|
+
`the ${this.#language} kernel was parked after idling, so the tools it defined are gone; run a cell to start a fresh kernel`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
this.#disarm();
|
|
138
|
+
this.#inFlight++;
|
|
139
|
+
try {
|
|
140
|
+
return await use(kernel);
|
|
141
|
+
} finally {
|
|
142
|
+
this.#inFlight--;
|
|
143
|
+
this.#arm();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#runOnCurrent(input: EvalKernelRunInput): Promise<EvalKernelResult> {
|
|
148
|
+
const kernel = this.#kernel;
|
|
149
|
+
if (kernel !== null && this.#parking === null) return kernel.run(input).then((result) => this.#announced(result));
|
|
150
|
+
return new Promise((settle, reject) => {
|
|
151
|
+
let cancelled = false;
|
|
152
|
+
this.#waiting.set(input.cellId, {
|
|
153
|
+
cancel: (reason) => {
|
|
154
|
+
cancelled = true;
|
|
155
|
+
settle(unstartedResult(input.cellId, reason));
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
this.#current().then(
|
|
159
|
+
(current) => {
|
|
160
|
+
this.#waiting.delete(input.cellId);
|
|
161
|
+
if (cancelled) return;
|
|
162
|
+
current.run(input).then((result) => settle(this.#announced(result)), reject);
|
|
163
|
+
},
|
|
164
|
+
(error: unknown) => {
|
|
165
|
+
this.#waiting.delete(input.cellId);
|
|
166
|
+
if (!cancelled) settle(unstartedResult(input.cellId, this.#restartFailure(error)));
|
|
167
|
+
},
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async #current(): Promise<EvalKernel> {
|
|
173
|
+
if (this.#parking !== null) await this.#parking;
|
|
174
|
+
const failure = this.#parkFailure;
|
|
175
|
+
if (failure !== null) {
|
|
176
|
+
this.#parkFailure = null;
|
|
177
|
+
throw new Error(failure);
|
|
178
|
+
}
|
|
179
|
+
if (this.#kernel !== null) return this.#kernel;
|
|
180
|
+
if (this.#closed) throw new IdleParkingKernelClosedError();
|
|
181
|
+
this.#starting ??= this.#restart()
|
|
182
|
+
.then((kernel) => {
|
|
183
|
+
this.#kernel = kernel;
|
|
184
|
+
this.#announce = restartNotice(this.#language, this.#parkReason());
|
|
185
|
+
return kernel;
|
|
186
|
+
})
|
|
187
|
+
.finally(() => {
|
|
188
|
+
this.#starting = null;
|
|
189
|
+
});
|
|
190
|
+
return await this.#starting;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
#announced(result: EvalKernelResult): EvalKernelResult {
|
|
194
|
+
const notice = this.#announce;
|
|
195
|
+
if (notice === null) return result;
|
|
196
|
+
this.#announce = null;
|
|
197
|
+
return {
|
|
198
|
+
...result,
|
|
199
|
+
notice: result.notice === undefined ? notice : `${notice}\n${result.notice}`,
|
|
200
|
+
kernelState: "restarted",
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
#arm(): void {
|
|
205
|
+
if (this.#closed || this.#inFlight > 0 || this.#kernel === null || this.#timer !== undefined) return;
|
|
206
|
+
this.#timer = setTimeout(() => {
|
|
207
|
+
this.#timer = undefined;
|
|
208
|
+
this.#park();
|
|
209
|
+
}, this.#idleMs);
|
|
210
|
+
this.#timer.unref?.();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
#disarm(): void {
|
|
214
|
+
if (this.#timer === undefined) return;
|
|
215
|
+
clearTimeout(this.#timer);
|
|
216
|
+
this.#timer = undefined;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
#park(): void {
|
|
220
|
+
const kernel = this.#kernel;
|
|
221
|
+
if (this.#closed || this.#inFlight > 0 || kernel === null) return;
|
|
222
|
+
const snapshot = kernel.queueSnapshot();
|
|
223
|
+
// Tools a cell defined stay callable by child tasks between cells, so their kernel is never parked.
|
|
224
|
+
const hasTools = (kernel.listKernelToolNames?.() ?? []).length > 0;
|
|
225
|
+
if (snapshot.activeCellId !== null || snapshot.queuedCellIds.length > 0 || hasTools) {
|
|
226
|
+
this.#arm();
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
this.#kernel = null;
|
|
230
|
+
this.#onParked();
|
|
231
|
+
const parking = kernel
|
|
232
|
+
.close()
|
|
233
|
+
.catch((error: unknown) => {
|
|
234
|
+
this.#parkFailure = `the idle ${this.#language} kernel could not be stopped: ${errorText(error)}; run the cell again to start a fresh kernel`;
|
|
235
|
+
})
|
|
236
|
+
.finally(() => {
|
|
237
|
+
if (this.#parking === parking) this.#parking = null;
|
|
238
|
+
});
|
|
239
|
+
this.#parking = parking;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
#cancelWaiting(cellId: string, reason: string): boolean {
|
|
243
|
+
const waiting = this.#waiting.get(cellId);
|
|
244
|
+
if (waiting === undefined) return false;
|
|
245
|
+
this.#waiting.delete(cellId);
|
|
246
|
+
waiting.cancel(reason);
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
#parkReason(): string {
|
|
251
|
+
const minutes = this.#idleMs / 60_000;
|
|
252
|
+
return `idling for ${minutes} minute${minutes === 1 ? "" : "s"} (memory.idleParkMinutes)`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
#restartFailure(error: unknown): string {
|
|
256
|
+
return `the ${this.#language} kernel parked after idling did not restart: ${errorText(error)}; run the cell again to retry`;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function parkWhenIdle(
|
|
261
|
+
language: EvalLanguage,
|
|
262
|
+
idleMinutes: number,
|
|
263
|
+
kernel: EvalKernel,
|
|
264
|
+
restart: RestartParkedKernel,
|
|
265
|
+
onParked?: () => void,
|
|
266
|
+
): EvalKernel {
|
|
267
|
+
return idleMinutes > 0 ? new IdleParkingKernel(language, idleMinutes, kernel, restart, onParked) : kernel;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function errorText(error: unknown): string {
|
|
271
|
+
return error instanceof Error ? error.message : String(error);
|
|
272
|
+
}
|
|
@@ -35,6 +35,31 @@ export function registerKernel(sessionId: string, language: EvalLanguage, memory
|
|
|
35
35
|
return id;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/** One registry entry per language for a session; replacing a language's kernel replaces its entry. */
|
|
39
|
+
export class SessionKernelRegistrations {
|
|
40
|
+
readonly #owner: string;
|
|
41
|
+
readonly #ids = new Map<EvalLanguage, string>();
|
|
42
|
+
|
|
43
|
+
constructor(owner: string) {
|
|
44
|
+
this.#owner = owner;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
register(language: EvalLanguage, memory: RegisteredKernelSource | undefined): void {
|
|
48
|
+
this.unregister(language);
|
|
49
|
+
if (memory !== undefined) this.#ids.set(language, registerKernel(this.#owner, language, memory));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
unregister(language: EvalLanguage): void {
|
|
53
|
+
const id = this.#ids.get(language);
|
|
54
|
+
if (id !== undefined) kernelRegistry.unregister(id);
|
|
55
|
+
this.#ids.delete(language);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
clear(): void {
|
|
59
|
+
for (const language of [...this.#ids.keys()]) this.unregister(language);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
38
63
|
export function javaScriptKernelMemory(kernel: JavaScriptKernel): RegisteredKernelSource {
|
|
39
64
|
return {
|
|
40
65
|
measure: "heap",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
KernelToolsDescribeResult,
|
|
3
|
+
KernelToolsInvokeOptions,
|
|
4
|
+
KernelToolsInvokeRequest,
|
|
5
|
+
} from "../kernels/js/kernel-tools-types.ts";
|
|
6
|
+
import type { EvalKernel } from "../tool/types.ts";
|
|
7
|
+
|
|
8
|
+
/** The kernel-tools surface a JavaScript kernel exposes; `run-eval-cell` finds it by probing for these methods. */
|
|
9
|
+
export interface KernelToolsMethods {
|
|
10
|
+
describeKernelTools(names: readonly string[]): Promise<KernelToolsDescribeResult>;
|
|
11
|
+
invokeKernelTool(
|
|
12
|
+
request: KernelToolsInvokeRequest,
|
|
13
|
+
options?: AbortSignal | KernelToolsInvokeOptions,
|
|
14
|
+
): Promise<unknown>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function hasKernelTools(kernel: EvalKernel): kernel is EvalKernel & KernelToolsMethods {
|
|
18
|
+
return (
|
|
19
|
+
"describeKernelTools" in kernel &&
|
|
20
|
+
typeof kernel.describeKernelTools === "function" &&
|
|
21
|
+
"invokeKernelTool" in kernel &&
|
|
22
|
+
typeof kernel.invokeKernelTool === "function"
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -2,22 +2,21 @@ import { join } from "node:path";
|
|
|
2
2
|
import type { ExtensionContext } from "@code-yeongyu/senpi";
|
|
3
3
|
import { type BridgeServerHandle, startBridgeServer } from "../bridge/http-server.ts";
|
|
4
4
|
import type { KernelToHostMessage } from "../bridge/protocol.ts";
|
|
5
|
-
import { isReservedToolName, runReservedTool } from "../bridges/reserved-dispatch.ts";
|
|
6
5
|
import type { CompletionRequest, CompletionResult } from "../completion/handler.ts";
|
|
7
6
|
import { resolveKernelMemoryThresholds } from "../config/memory-settings.ts";
|
|
8
|
-
import { defaultCodemodeSettings } from "../config/settings.ts";
|
|
9
7
|
import { collectOrphanedChildren } from "../host-sdk.ts";
|
|
10
8
|
import { JavaScriptKernel } from "../kernels/js/context-manager.ts";
|
|
11
9
|
import type { KernelLifecycle } from "../kernels/shared/kernel-death.ts";
|
|
12
|
-
import { marshalToolResult } from "../tool/image.ts";
|
|
13
10
|
import type { EvalKernel, EvalLanguage } from "../tool/types.ts";
|
|
11
|
+
import { type BridgeToolCallRequest, routeBridgeToolCall } from "./bridge-tool-call.ts";
|
|
12
|
+
import { parkWhenIdle } from "./idle-parking-kernel.ts";
|
|
14
13
|
import {
|
|
15
14
|
javaScriptKernelMemory,
|
|
16
|
-
|
|
15
|
+
SessionKernelRegistrations,
|
|
17
16
|
type StartedKernel,
|
|
18
17
|
startSubprocessKernel,
|
|
19
18
|
} from "./kernel-registration.ts";
|
|
20
|
-
import {
|
|
19
|
+
import type { RegisteredKernelSource } from "./kernel-registry.ts";
|
|
21
20
|
import { ReplaceableKernel } from "./kernel-replacement.ts";
|
|
22
21
|
import { assertSessionCwdAvailable } from "./session-cwd.ts";
|
|
23
22
|
import type {
|
|
@@ -61,7 +60,7 @@ class DefaultCodemodeSessionManager implements CodemodeSessionManager {
|
|
|
61
60
|
readonly #options: CreateCodemodeSessionManagerOptions;
|
|
62
61
|
#bridge: BridgeServerHandle | undefined;
|
|
63
62
|
#kernels = new Map<EvalLanguage, EvalKernel>();
|
|
64
|
-
readonly #registrations
|
|
63
|
+
readonly #registrations: SessionKernelRegistrations;
|
|
65
64
|
#kernelCreations = new Map<EvalLanguage, Promise<EvalKernel>>();
|
|
66
65
|
#onMessageRefs = new Map<EvalLanguage, (message: KernelToHostMessage) => void>();
|
|
67
66
|
#context: ExtensionContext | undefined;
|
|
@@ -70,6 +69,7 @@ class DefaultCodemodeSessionManager implements CodemodeSessionManager {
|
|
|
70
69
|
|
|
71
70
|
constructor(options: CreateCodemodeSessionManagerOptions) {
|
|
72
71
|
this.#options = options;
|
|
72
|
+
this.#registrations = new SessionKernelRegistrations(options.ownerSessionId ?? options.sessionId);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
async start(): Promise<void> {
|
|
@@ -81,29 +81,8 @@ class DefaultCodemodeSessionManager implements CodemodeSessionManager {
|
|
|
81
81
|
});
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// through runReservedTool (forwarding them made agent() fail with "Unknown tool __agent__"),
|
|
87
|
-
// and ordinary tool results are marshalled to { text, images, details, hasError } — the raw
|
|
88
|
-
// { content } shape left python cells unable to reach tool.read image blocks.
|
|
89
|
-
async #call(request: { toolName: string; args: unknown; callId: string; signal: AbortSignal }): Promise<unknown> {
|
|
90
|
-
if (!isReservedToolName(request.toolName)) {
|
|
91
|
-
return marshalToolResult(
|
|
92
|
-
await this.#options.executeTool(request.toolName, request.args, { signal: request.signal }),
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
const taskTools = this.#options.settings.taskTools ?? defaultCodemodeSettings.taskTools;
|
|
96
|
-
return await runReservedTool(request.toolName, {
|
|
97
|
-
callId: request.callId,
|
|
98
|
-
args: request.args,
|
|
99
|
-
executeTool: this.#options.executeTool,
|
|
100
|
-
taskToolName: taskTools.task,
|
|
101
|
-
taskOutputToolName: taskTools.output,
|
|
102
|
-
listTools: this.#options.listTools,
|
|
103
|
-
signal: request.signal,
|
|
104
|
-
emitStatus: () => {},
|
|
105
|
-
marshalToolResult,
|
|
106
|
-
});
|
|
84
|
+
async #call(request: BridgeToolCallRequest): Promise<unknown> {
|
|
85
|
+
return await routeBridgeToolCall(this.#options, request);
|
|
107
86
|
}
|
|
108
87
|
|
|
109
88
|
async getKernel(language: EvalLanguage, onMessage: (message: KernelToHostMessage) => void): Promise<EvalKernel> {
|
|
@@ -170,7 +149,6 @@ class DefaultCodemodeSessionManager implements CodemodeSessionManager {
|
|
|
170
149
|
const kernels = [...this.#kernels.values()];
|
|
171
150
|
const bridge = this.#bridge;
|
|
172
151
|
this.#kernels.clear();
|
|
173
|
-
for (const id of this.#registrations.values()) kernelRegistry.unregister(id);
|
|
174
152
|
this.#registrations.clear();
|
|
175
153
|
this.#onMessageRefs.clear();
|
|
176
154
|
this.#bridge = undefined;
|
|
@@ -194,36 +172,29 @@ class DefaultCodemodeSessionManager implements CodemodeSessionManager {
|
|
|
194
172
|
generation: number,
|
|
195
173
|
): Promise<EvalKernel> {
|
|
196
174
|
await assertSessionCwdAvailable(this.#options.cwd);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
175
|
+
const started = await this.#startLanguageKernel(language, onMessage);
|
|
176
|
+
const idleParkMinutes = this.#options.settings.memory?.idleParkMinutes ?? 0;
|
|
177
|
+
const resolvedKernel = parkWhenIdle(
|
|
178
|
+
language,
|
|
179
|
+
idleParkMinutes,
|
|
180
|
+
started.kernel,
|
|
181
|
+
async () => {
|
|
182
|
+
const restarted = await this.#startLanguageKernel(language, onMessage);
|
|
183
|
+
if (generation !== this.#generation) {
|
|
184
|
+
await restarted.kernel.close();
|
|
185
|
+
throw new CodemodeSessionDisposedError();
|
|
186
|
+
}
|
|
187
|
+
this.#registrations.register(language, restarted.memory);
|
|
188
|
+
return restarted.kernel;
|
|
189
|
+
},
|
|
190
|
+
() => this.#registrations.unregister(language),
|
|
191
|
+
);
|
|
214
192
|
if (generation !== this.#generation) {
|
|
215
193
|
await resolvedKernel.close();
|
|
216
194
|
throw new CodemodeSessionDisposedError();
|
|
217
195
|
}
|
|
218
196
|
this.#kernels.set(language, resolvedKernel);
|
|
219
|
-
this.#registrations.
|
|
220
|
-
language,
|
|
221
|
-
registerKernel(
|
|
222
|
-
this.#options.ownerSessionId ?? this.#options.sessionId,
|
|
223
|
-
language,
|
|
224
|
-
memory as RegisteredKernelSource,
|
|
225
|
-
),
|
|
226
|
-
);
|
|
197
|
+
this.#registrations.register(language, started.memory);
|
|
227
198
|
// The directory can vanish while the interpreter starts; every caller sharing this creation
|
|
228
199
|
// must see that, not only the next one. The kernel stays stored and dispose still closes it.
|
|
229
200
|
await assertSessionCwdAvailable(this.#options.cwd);
|
|
@@ -232,6 +203,22 @@ class DefaultCodemodeSessionManager implements CodemodeSessionManager {
|
|
|
232
203
|
return resolvedKernel;
|
|
233
204
|
}
|
|
234
205
|
|
|
206
|
+
// py/rb/jl instances can die; the session holds one replaceable kernel per language so every
|
|
207
|
+
// cell that kept a reference to it survives the death (JS heals its own worker).
|
|
208
|
+
async #startLanguageKernel(
|
|
209
|
+
language: EvalLanguage,
|
|
210
|
+
onMessage: (message: KernelToHostMessage) => void,
|
|
211
|
+
): Promise<{ readonly kernel: EvalKernel; readonly memory: RegisteredKernelSource | undefined }> {
|
|
212
|
+
if (language === "js") return await this.#createKernel(language, onMessage);
|
|
213
|
+
let memory: RegisteredKernelSource | undefined;
|
|
214
|
+
const kernel = await ReplaceableKernel.create(language, async (lifecycle) => {
|
|
215
|
+
const created = await this.#createKernel(language, onMessage, lifecycle);
|
|
216
|
+
memory ??= created.memory;
|
|
217
|
+
return created.kernel;
|
|
218
|
+
});
|
|
219
|
+
return { kernel, memory };
|
|
220
|
+
}
|
|
221
|
+
|
|
235
222
|
async #createKernel(
|
|
236
223
|
language: EvalLanguage,
|
|
237
224
|
onMessage: (message: KernelToHostMessage) => void,
|
package/src/kernels/jl/kernel.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { join } from "node:path";
|
|
|
2
2
|
import type { BridgeConnectionConfig, KernelToHostMessage } from "../../bridge/protocol.ts";
|
|
3
3
|
import type { SessionEnvironment } from "../session-env.ts";
|
|
4
4
|
import type { KernelLifecycle } from "../shared/kernel-death.ts";
|
|
5
|
+
import { readProcessGroupCpuTime } from "../shared/process-group-cpu.ts";
|
|
5
6
|
import { type CodemodeRuntimeAssetEnvironment, requireCodemodeRuntimeAsset } from "../shared/runtime-asset.ts";
|
|
6
7
|
import { SubprocessKernel, type SubprocessKernelMemory, type SubprocessSpawn } from "../shared/subprocess-kernel.ts";
|
|
7
8
|
|
|
@@ -14,7 +15,7 @@ export interface JuliaKernelStartOptions extends KernelLifecycle {
|
|
|
14
15
|
readonly command?: string;
|
|
15
16
|
readonly spawn?: SubprocessSpawn;
|
|
16
17
|
readonly onMessage?: (message: KernelToHostMessage) => void;
|
|
17
|
-
/**
|
|
18
|
+
/** Memory management: the runner reports its largest globals and the host reads the interpreter footprint for the ceiling. */
|
|
18
19
|
readonly memory?: SubprocessKernelMemory;
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -54,6 +55,7 @@ export class JuliaKernel extends SubprocessKernel {
|
|
|
54
55
|
onMessage: options.onMessage,
|
|
55
56
|
memory: options.memory && { language: "jl", ...options.memory },
|
|
56
57
|
onDeath: options.onDeath,
|
|
58
|
+
startup: { label: "Julia", readGroupCpuTime: readProcessGroupCpuTime },
|
|
57
59
|
});
|
|
58
60
|
}
|
|
59
61
|
}
|