@code-yeongyu/senpi-codemode 2026.9.5 → 2026.9.7-2
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 +74 -0
- package/README.md +34 -5
- package/package.json +4 -4
- package/src/bridge/protocol.ts +1 -0
- package/src/bridge/reserved.ts +2 -0
- package/src/extension/runtime-factory.ts +3 -0
- package/src/extension/session-manager.ts +7 -0
- package/src/kernels/AGENTS.md +7 -0
- package/src/kernels/jl/kernel.ts +6 -2
- package/src/kernels/js/context-manager.ts +73 -129
- package/src/kernels/js/inline-worker.ts +2 -2
- package/src/kernels/js/interrupt-bounds.ts +66 -0
- package/src/kernels/js/kernel-contract.ts +3 -0
- package/src/kernels/js/run-queue.ts +18 -3
- package/src/kernels/js/worker-core.js +59 -0
- package/src/kernels/js/worker-indirect-eval.js +10 -6
- package/src/kernels/js/worker-runtime.js +16 -0
- package/src/kernels/js/worker-shell-capture.d.ts +25 -0
- package/src/kernels/js/worker-shell-capture.js +70 -8
- package/src/kernels/js/worker-slot.ts +106 -0
- package/src/kernels/js/worker-startup.ts +70 -0
- package/src/kernels/py/kernel-contract.ts +3 -0
- package/src/kernels/py/transport.ts +11 -3
- package/src/kernels/rb/kernel.ts +6 -2
- package/src/kernels/session-env.ts +56 -0
- package/src/kernels/shared/runtime-asset.ts +40 -14
- package/src/kernels/shared/subprocess-contract.ts +3 -0
- package/src/kernels/shared/subprocess-kernel.ts +9 -1
- package/src/output/output-meta.ts +26 -2
- package/src/prompt/eval-prompt.ts +10 -5
- package/src/tool/cell-execution.ts +9 -11
- package/src/tool/detached-cell-contract.ts +45 -0
- package/src/tool/detached-cell-manager.ts +43 -71
- package/src/tool/detached-cell-notification.ts +4 -5
- package/src/tool/detached-cell-snapshot.ts +2 -0
- package/src/tool/detached-cell-status.ts +30 -0
- package/src/tool/detached-eval-result.ts +1 -0
- package/src/tool/detached-notification-queue.ts +2 -2
- package/src/tool/image.ts +12 -3
- package/src/tool/interrupt-note.ts +29 -15
- package/src/tool/types.ts +2 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { SENPI_CODEMODE_WAKE_SOURCE, type WakeSourceState } from "../extension/wake-source-state.ts";
|
|
2
|
+
import type { EvalDetachedCellStatusEntry } from "./detached-cell-manager.ts";
|
|
3
|
+
|
|
4
|
+
export interface LiveDetachedCell {
|
|
5
|
+
readonly cellId: string;
|
|
6
|
+
readonly startedAtMs: number;
|
|
7
|
+
readonly input: { readonly language: EvalDetachedCellStatusEntry["language"]; readonly summary?: string };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function detachedStatusEntries(liveCells: readonly LiveDetachedCell[]): EvalDetachedCellStatusEntry[] {
|
|
11
|
+
return liveCells.map((cell) => ({
|
|
12
|
+
cellId: cell.cellId,
|
|
13
|
+
language: cell.input.language,
|
|
14
|
+
startedAtMs: cell.startedAtMs,
|
|
15
|
+
...(cell.input.summary === undefined ? {} : { summary: cell.input.summary }),
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function detachedWakeSourceState(liveCells: readonly LiveDetachedCell[]): WakeSourceState {
|
|
20
|
+
return {
|
|
21
|
+
source: SENPI_CODEMODE_WAKE_SOURCE,
|
|
22
|
+
activeCount: liveCells.length,
|
|
23
|
+
items: liveCells.map((cell) => ({
|
|
24
|
+
id: cell.cellId,
|
|
25
|
+
description:
|
|
26
|
+
cell.input.summary === undefined || cell.input.summary.length === 0 ? cell.cellId : cell.input.summary,
|
|
27
|
+
startedAtMs: cell.startedAtMs,
|
|
28
|
+
})),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -40,6 +40,7 @@ export function createDetachedControlResult(snapshot: EvalDetachedCellSnapshot):
|
|
|
40
40
|
`Eval cell ${snapshot.cellId} (${snapshot.language}) is ${snapshot.state}.`,
|
|
41
41
|
output.length === 0 ? "(no buffered output)" : output,
|
|
42
42
|
...(terminationNote === undefined ? [] : [terminationNote]),
|
|
43
|
+
...(snapshot.interruptNote === undefined ? [] : [snapshot.interruptNote.trim()]),
|
|
43
44
|
].join("\n");
|
|
44
45
|
return {
|
|
45
46
|
content: [{ type: "text", text }, ...snapshot.result.content.filter((part) => part.type === "image")],
|
|
@@ -2,7 +2,7 @@ import type { EvalDetachedCellNotifier, EvalDetachedCellSnapshot } from "./detac
|
|
|
2
2
|
import { buildDetachedCellNotification } from "./detached-cell-notification.ts";
|
|
3
3
|
|
|
4
4
|
export interface PendingDetachedNotification {
|
|
5
|
-
readonly snapshot: () => EvalDetachedCellSnapshot
|
|
5
|
+
readonly snapshot: () => EvalDetachedCellSnapshot | Promise<EvalDetachedCellSnapshot>;
|
|
6
6
|
readonly spillPath: string | undefined;
|
|
7
7
|
}
|
|
8
8
|
|
|
@@ -30,7 +30,7 @@ export class DetachedNotificationQueue {
|
|
|
30
30
|
const flush = Promise.resolve().then(async () => {
|
|
31
31
|
const pending = this.#pending.splice(0);
|
|
32
32
|
const notifications = await Promise.all(
|
|
33
|
-
pending.map(async (item) => await buildDetachedCellNotification(item.snapshot(), item.spillPath)),
|
|
33
|
+
pending.map(async (item) => await buildDetachedCellNotification(await item.snapshot(), item.spillPath)),
|
|
34
34
|
);
|
|
35
35
|
this.#notifier?.notify(notifications);
|
|
36
36
|
});
|
package/src/tool/image.ts
CHANGED
|
@@ -119,7 +119,7 @@ export class EvalOutputCollector {
|
|
|
119
119
|
async finish(): Promise<EvalOutputResult> {
|
|
120
120
|
await this.#processImages();
|
|
121
121
|
const summary = await this.#finalSummary();
|
|
122
|
-
const meta = truncationMetaFromSummary(summary);
|
|
122
|
+
const meta = truncationMetaFromSummary(summary, this.#options.maxColumns);
|
|
123
123
|
const notice = summary.artifactId === undefined ? undefined : artifactNotice(summary.artifactId);
|
|
124
124
|
return {
|
|
125
125
|
output: summary.output.trimEnd(),
|
|
@@ -215,7 +215,7 @@ function formatDisplayJson(value: unknown): string {
|
|
|
215
215
|
return `${text.slice(0, MAX_DISPLAY_TEXT_BYTES)}\n[…${text.length - MAX_DISPLAY_TEXT_BYTES}ch elided…]`;
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
function truncationMetaFromSummary(summary: OutputSummary): TruncationMeta | undefined {
|
|
218
|
+
function truncationMetaFromSummary(summary: OutputSummary, maxColumns: number): TruncationMeta | undefined {
|
|
219
219
|
if (!summary.truncated) return undefined;
|
|
220
220
|
const artifact = summary.artifactId === undefined ? {} : { artifactId: summary.artifactId };
|
|
221
221
|
if (summary.elidedBytes !== undefined && summary.elidedBytes > 0) {
|
|
@@ -239,9 +239,18 @@ function truncationMetaFromSummary(summary: OutputSummary): TruncationMeta | und
|
|
|
239
239
|
...artifact,
|
|
240
240
|
};
|
|
241
241
|
}
|
|
242
|
+
const droppedBytes = Math.max(0, summary.totalBytes - summary.outputBytes);
|
|
243
|
+
const clampedLines = summary.columnTruncatedLines ?? 0;
|
|
244
|
+
const columnOnly = clampedLines > 0 && (summary.columnDroppedBytes ?? 0) >= droppedBytes;
|
|
245
|
+
const byteCapped = summary.totalBytes - (summary.columnDroppedBytes ?? 0) > DEFAULT_MAX_BYTES;
|
|
242
246
|
return {
|
|
243
247
|
direction: "tail",
|
|
244
|
-
truncatedBy:
|
|
248
|
+
truncatedBy: columnOnly ? "columns" : byteCapped ? "bytes" : "lines",
|
|
249
|
+
...(columnOnly
|
|
250
|
+
? { maxColumns, columnTruncatedLines: clampedLines }
|
|
251
|
+
: byteCapped
|
|
252
|
+
? { maxBytes: DEFAULT_MAX_BYTES }
|
|
253
|
+
: {}),
|
|
245
254
|
totalLines: summary.totalLines,
|
|
246
255
|
totalBytes: summary.totalBytes,
|
|
247
256
|
outputLines: summary.outputLines,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EvalLanguage } from "./types.ts";
|
|
1
|
+
import type { EvalLanguage, KernelInterruptHandle } from "./types.ts";
|
|
2
2
|
|
|
3
3
|
const TIMEOUT_STATE_GRACE_MS = 5_500;
|
|
4
4
|
|
|
@@ -13,30 +13,40 @@ function fallbackTimeoutMessage(base: string): string {
|
|
|
13
13
|
*/
|
|
14
14
|
export async function describeTimeoutState(
|
|
15
15
|
error: Error,
|
|
16
|
-
execution: { readonly
|
|
16
|
+
execution: { readonly interruptHandle: Promise<KernelInterruptHandle> | undefined },
|
|
17
17
|
): Promise<Error> {
|
|
18
|
-
const
|
|
19
|
-
if (
|
|
18
|
+
const pending = execution.interruptHandle;
|
|
19
|
+
if (pending === undefined) {
|
|
20
20
|
error.message = fallbackTimeoutMessage(error.message);
|
|
21
21
|
return error;
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
]).finally(() => {
|
|
30
|
-
if (timer !== undefined) clearTimeout(timer);
|
|
31
|
-
});
|
|
32
|
-
if (retained === undefined) error.message = fallbackTimeoutMessage(error.message);
|
|
33
|
-
else if (retained)
|
|
23
|
+
const outcome = await withinGrace(
|
|
24
|
+
pending.then(async (handle) => ({ retained: await handle.stateRetained, note: handle.note })),
|
|
25
|
+
TIMEOUT_STATE_GRACE_MS,
|
|
26
|
+
);
|
|
27
|
+
if (outcome === undefined) error.message = fallbackTimeoutMessage(error.message);
|
|
28
|
+
else if (outcome.retained)
|
|
34
29
|
error.message = `${error.message} The kernel remains running; its existing variables are preserved.`;
|
|
35
30
|
else
|
|
36
31
|
error.message = `${error.message} The kernel was unresponsive and restarted; variables from earlier cells are lost.`;
|
|
32
|
+
if (outcome?.note !== undefined) error.message = `${error.message} ${outcome.note.trim()}`;
|
|
37
33
|
return error;
|
|
38
34
|
}
|
|
39
35
|
|
|
36
|
+
async function withinGrace<T>(operation: Promise<T>, graceMs: number): Promise<T | undefined> {
|
|
37
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
38
|
+
try {
|
|
39
|
+
return await Promise.race([
|
|
40
|
+
operation,
|
|
41
|
+
new Promise<undefined>((resolve) => {
|
|
42
|
+
timer = setTimeout(() => resolve(undefined), graceMs);
|
|
43
|
+
}),
|
|
44
|
+
]);
|
|
45
|
+
} finally {
|
|
46
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
40
50
|
const LANGUAGE_LABEL: Record<EvalLanguage, string> = {
|
|
41
51
|
py: "Python kernel",
|
|
42
52
|
js: "JavaScript worker",
|
|
@@ -56,3 +66,7 @@ export function interruptionStateNote(language: EvalLanguage, stateRetained: boo
|
|
|
56
66
|
if (stateRetained) return `${label} was interrupted and remains running; its existing variables are preserved.`;
|
|
57
67
|
return `${label} was unresponsive to interrupt and was restarted; variables from earlier cells are lost.`;
|
|
58
68
|
}
|
|
69
|
+
|
|
70
|
+
export function unknownInterruptionStateNote(language: EvalLanguage): string {
|
|
71
|
+
return `${LANGUAGE_LABEL[language]} interrupt outcome is unknown; re-establish any variables the next cell needs.`;
|
|
72
|
+
}
|
package/src/tool/types.ts
CHANGED
|
@@ -112,6 +112,8 @@ export interface EvalKernelRunInput {
|
|
|
112
112
|
export interface KernelInterruptHandle {
|
|
113
113
|
/** Resolves once the kernel knows whether user state survived the interrupt. */
|
|
114
114
|
readonly stateRetained: Promise<boolean>;
|
|
115
|
+
/** Extra outcome detail worth showing the model, e.g. that a blocked worker was abandoned. */
|
|
116
|
+
readonly note?: string;
|
|
115
117
|
}
|
|
116
118
|
|
|
117
119
|
export interface EvalKernel {
|