@code-yeongyu/senpi-codemode 2026.8.23 → 2026.8.24
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
CHANGED
|
@@ -12,6 +12,20 @@
|
|
|
12
12
|
|
|
13
13
|
### Removed
|
|
14
14
|
|
|
15
|
+
## [2026.8.24] - 2026-08-24
|
|
16
|
+
|
|
17
|
+
### Breaking Changes
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Detached eval cell overflow notices now point at the absolute spill file path (`…/local/detached-eval-<id>.log`) instead of a `local://detached-eval-<id>.log` URI. `local://` is resolved only by the in-cell kernel helpers, not by the agent `read` tool, so following the old notice failed with `ENOENT …/local:/detached-eval-<id>.log`. This restores the documented contract that spill notices carry plain absolute paths.
|
|
26
|
+
|
|
27
|
+
### Removed
|
|
28
|
+
|
|
15
29
|
## [2026.8.23] - 2026-08-23
|
|
16
30
|
|
|
17
31
|
### Breaking Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/senpi-codemode",
|
|
3
|
-
"version": "2026.8.
|
|
3
|
+
"version": "2026.8.24",
|
|
4
4
|
"description": "Source-only senpi extension package for codemode evaluation tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -30,14 +30,14 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@babel/parser": "8.0.4",
|
|
33
|
-
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.8.
|
|
34
|
-
"typebox": "1.3.
|
|
33
|
+
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.8.24",
|
|
34
|
+
"typebox": "1.3.18"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@code-yeongyu/senpi": "2026.8.
|
|
37
|
+
"@code-yeongyu/senpi": "2026.8.24"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@code-yeongyu/senpi": "2026.8.
|
|
40
|
+
"@code-yeongyu/senpi": "2026.8.24"
|
|
41
41
|
},
|
|
42
42
|
"keywords": [
|
|
43
43
|
"senpi",
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# src/kernels
|
|
2
|
+
|
|
3
|
+
Persistent kernels for four runtimes plus shared subprocess lifecycle. Earned
|
|
4
|
+
by score 13 — distinct multi-runtime domain (31 files, TS hosts plus embedded
|
|
5
|
+
runner/prelude assets).
|
|
6
|
+
|
|
7
|
+
## WHERE TO LOOK
|
|
8
|
+
|
|
9
|
+
| Task | Path |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| JavaScript host API | `js/context-manager.ts` (`JavaScriptKernel`), `js/worker-host.ts` |
|
|
12
|
+
| JS worker runtime, entries | `js/worker-runtime.js`, `js/worker-entry.js`, `js/inline-worker-entry.js`, `js/inline-worker.ts`, `js/worker-core.js` (+ `worker-core.d.ts`) |
|
|
13
|
+
| JS import rewriting, queueing | `js/rewrite-imports.ts`, `js/run-queue.ts`, `js/prelude.ts`, `js/local-module-loader.ts` |
|
|
14
|
+
| Python kernel | `py/kernel.ts`, `py/transport.ts`, `py/process.ts`, `py/prelude.py` |
|
|
15
|
+
| Ruby kernel | `rb/kernel.ts` + `rb/prelude.rb`, `rb/runner.rb` |
|
|
16
|
+
| Julia kernel | `jl/kernel.ts` + `jl/prelude.jl`, `jl/runner.jl` |
|
|
17
|
+
| Shared subprocess layer | `shared/subprocess-kernel.ts`, `subprocess-{contract,process,queue,run}.ts`, `runtime-asset.ts` |
|
|
18
|
+
|
|
19
|
+
## CONVENTIONS
|
|
20
|
+
|
|
21
|
+
- Each language dir pairs a typed TS host/controller with an embedded runner or
|
|
22
|
+
prelude asset; `shared/runtime-asset.ts` ships them.
|
|
23
|
+
- Transport messages are discriminated by string `type` (`ready`, `result`,
|
|
24
|
+
`tool-call`, `closed`, `init-failed`, ...) exchanged as framed bridge
|
|
25
|
+
messages, one JSON line per frame.
|
|
26
|
+
- JS persistent cell bindings are rewritten onto `globalThis`; imports are
|
|
27
|
+
AST-parsed (Babel) and rewritten to bridge-compatible dynamic imports.
|
|
28
|
+
- JS runs on worker threads with an inline-worker fallback; py/rb/jl run as
|
|
29
|
+
framed subprocesses through `shared/`.
|
|
30
|
+
- Subprocess retirement/restart, worker recovery, timeout, and interrupt
|
|
31
|
+
semantics live here, never in the tool layer.
|
|
32
|
+
|
|
33
|
+
## ANTI-PATTERNS
|
|
34
|
+
|
|
35
|
+
- Never treat arbitrary objects as bridge messages without discriminant
|
|
36
|
+
validation.
|
|
37
|
+
- Never rewrite imports by filename/regex — `rewrite-imports.ts` applies
|
|
38
|
+
source-position edits from the parsed program.
|
|
39
|
+
- An optional interpreter being absent (py/rb/jl not installed) is a capability
|
|
40
|
+
gap, not an installation failure or error path.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# src/tool
|
|
2
|
+
|
|
3
|
+
Eval tool core: input schema, cell execution lifecycle, detached-cell state
|
|
4
|
+
machine, status events, and all call/result rendering. Earned by score 12 —
|
|
5
|
+
highest reference density in the package (`createEvalTool` and `EvalToolDetails`
|
|
6
|
+
anchor most suites).
|
|
7
|
+
|
|
8
|
+
## WHERE TO LOOK
|
|
9
|
+
|
|
10
|
+
| Task | Path |
|
|
11
|
+
| --- | --- |
|
|
12
|
+
| Tool registration, options | `eval-tool.ts`, `eval-tool-options.ts`, `eval-request.ts` |
|
|
13
|
+
| Wire contract, TypeBox schemas | `types.ts` (`createEvalInputSchema`, `fullEvalInputSchema`) |
|
|
14
|
+
| Cell execution, settlement | `cell-handler.ts`, `cell-execution.ts`, `cell-runtime.ts` |
|
|
15
|
+
| Detached cells | `detached-cell-manager.ts` + `detached-cell-{state,snapshot,notification}.ts`, `detached-notification-queue.ts`, `detached-eval-result.ts` |
|
|
16
|
+
| Call/result rendering | `render.ts`, `runtime-label.ts`, `json-tree.ts`, `image.ts`, `tool-widgets.ts` |
|
|
17
|
+
| Status events, execution events | `status-events.ts`, `eval-execution-event.ts` |
|
|
18
|
+
| Interrupt, capture | `interrupt-note.ts`, `call-capture.ts` |
|
|
19
|
+
|
|
20
|
+
## CONVENTIONS
|
|
21
|
+
|
|
22
|
+
- Wire/schema fields are snake_case (`cell_id`, `on_timeout`); internal TS
|
|
23
|
+
fields are camelCase. Schemas and shared eval types live only in `types.ts`.
|
|
24
|
+
- Rendering is bounded by explicit line/code-point budgets with an injectable
|
|
25
|
+
render clock; nothing here depends on wall-clock luck.
|
|
26
|
+
- Detached execution is a first-class state machine — snapshot, notification
|
|
27
|
+
queue, spill-file notice, result conversion — never folded into ordinary
|
|
28
|
+
cell execution.
|
|
29
|
+
- Unicode tree glyphs and status icons are intentional UI conventions.
|
|
30
|
+
|
|
31
|
+
## ANTI-PATTERNS
|
|
32
|
+
|
|
33
|
+
- Never add unbounded output: previews, JSON tree depth/lines/scalar length,
|
|
34
|
+
widget lines, and collapsed errors all cap.
|
|
35
|
+
- Never bypass the kernel bridge message contract with ad-hoc return values;
|
|
36
|
+
kernels import `KernelInterruptHandle` from `types.ts`, so discriminants and
|
|
37
|
+
lifecycle state are cross-runtime contracts — change them only with all four
|
|
38
|
+
runtimes and the detached path in mind.
|
|
39
|
+
- `render.ts` (1,030 LOC) is the package's largest file and the highest-risk
|
|
40
|
+
hotspot for regressions; changes there need render contracts first.
|
|
@@ -93,7 +93,7 @@ export class EvalDetachedCellManager {
|
|
|
93
93
|
this.#artifactsDir = options.artifactsDir;
|
|
94
94
|
this.#onStatusChange = options.onStatusChange;
|
|
95
95
|
this.#onWakeSourceState = options.onWakeSourceState;
|
|
96
|
-
this.#notificationQueue = new DetachedNotificationQueue(options.notifier
|
|
96
|
+
this.#notificationQueue = new DetachedNotificationQueue(options.notifier);
|
|
97
97
|
this.#now = options.now ?? Date.now;
|
|
98
98
|
this.#hardLimitSeconds = options.hardLimitSeconds ?? DEFAULT_HARD_LIMIT_SECONDS;
|
|
99
99
|
}
|
|
@@ -12,7 +12,6 @@ export function detachedNotificationSpillPath(artifactsDir: string | undefined,
|
|
|
12
12
|
export async function buildDetachedCellNotification(
|
|
13
13
|
snapshot: EvalDetachedCellSnapshot,
|
|
14
14
|
spillPath: string | undefined,
|
|
15
|
-
artifactsDir: string | undefined,
|
|
16
15
|
): Promise<EvalDetachedCellNotification> {
|
|
17
16
|
const body = notificationBody(snapshot);
|
|
18
17
|
const overflow = Buffer.byteLength(body, "utf8") > NOTIFICATION_TAIL_BYTES;
|
|
@@ -21,7 +20,9 @@ export async function buildDetachedCellNotification(
|
|
|
21
20
|
try {
|
|
22
21
|
await mkdir(dirname(spillPath), { recursive: true });
|
|
23
22
|
await writeFile(spillPath, body, "utf8");
|
|
24
|
-
|
|
23
|
+
// The agent read tool resolves plain paths only, so the notice must carry
|
|
24
|
+
// the absolute spill path, never the kernel-helper local:// scheme.
|
|
25
|
+
spillNotice = `\nBuffered output overflowed; full output: ${spillPath}`;
|
|
25
26
|
} catch (error) {
|
|
26
27
|
const message = error instanceof Error ? error.message : String(error);
|
|
27
28
|
spillNotice = `\nBuffered output overflow could not be spilled: ${message}`;
|
|
@@ -84,12 +85,6 @@ function safeCellId(cellId: string): string {
|
|
|
84
85
|
return cellId.replace(/[^a-zA-Z0-9_-]/gu, "_");
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
function localUri(path: string, artifactsDir: string | undefined): string {
|
|
88
|
-
if (artifactsDir === undefined) return `local://${path}`;
|
|
89
|
-
const root = join(artifactsDir, "local");
|
|
90
|
-
return path.startsWith(`${root}/`) ? `local://${path.slice(root.length + 1)}` : `local://${path}`;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
88
|
function truncateTailUtf8(text: string, maxBytes: number): string {
|
|
94
89
|
if (Buffer.byteLength(text, "utf8") <= maxBytes) return text;
|
|
95
90
|
const bytes = Buffer.from(text, "utf8");
|
|
@@ -7,14 +7,12 @@ export interface PendingDetachedNotification {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export class DetachedNotificationQueue {
|
|
10
|
-
readonly #artifactsDir: string | undefined;
|
|
11
10
|
readonly #notifier: EvalDetachedCellNotifier | undefined;
|
|
12
11
|
#pending: PendingDetachedNotification[] = [];
|
|
13
12
|
#flush: Promise<void> | undefined;
|
|
14
13
|
|
|
15
|
-
constructor(notifier: EvalDetachedCellNotifier | undefined
|
|
14
|
+
constructor(notifier: EvalDetachedCellNotifier | undefined) {
|
|
16
15
|
this.#notifier = notifier;
|
|
17
|
-
this.#artifactsDir = artifactsDir;
|
|
18
16
|
}
|
|
19
17
|
|
|
20
18
|
enqueue(notification: PendingDetachedNotification): void {
|
|
@@ -32,9 +30,7 @@ export class DetachedNotificationQueue {
|
|
|
32
30
|
const flush = Promise.resolve().then(async () => {
|
|
33
31
|
const pending = this.#pending.splice(0);
|
|
34
32
|
const notifications = await Promise.all(
|
|
35
|
-
pending.map(
|
|
36
|
-
async (item) => await buildDetachedCellNotification(item.snapshot(), item.spillPath, this.#artifactsDir),
|
|
37
|
-
),
|
|
33
|
+
pending.map(async (item) => await buildDetachedCellNotification(item.snapshot(), item.spillPath)),
|
|
38
34
|
);
|
|
39
35
|
this.#notifier?.notify(notifications);
|
|
40
36
|
});
|