@code-yeongyu/senpi-codemode 2026.9.29 → 2026.10.1-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 +86 -0
- package/README.md +3 -2
- package/package.json +4 -4
- package/src/extension/runtime-factory.ts +13 -2
- package/src/output/output-meta.ts +13 -3
- package/src/output/streaming-output.ts +19 -2
- package/src/tool/cell-runtime.ts +4 -2
- package/src/tool/image.ts +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,92 @@
|
|
|
12
12
|
|
|
13
13
|
### Removed
|
|
14
14
|
|
|
15
|
+
## [2026.10.1-2] - 2026-10-01
|
|
16
|
+
|
|
17
|
+
### Breaking Changes
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
### Removed
|
|
26
|
+
|
|
27
|
+
## [2026.10.1] - 2026-10-01
|
|
28
|
+
|
|
29
|
+
### Breaking Changes
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
### Removed
|
|
38
|
+
|
|
39
|
+
## [2026.9.30] - 2026-09-30
|
|
40
|
+
|
|
41
|
+
### Breaking Changes
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- An `eval` cell's return value reaches the model whole up to the normal tool-output budget: a long single-line value is no longer cut after 768 bytes with a bare `…`. Any output that is still cut (a printed line past the column cap, or output past the byte or line budget) now tells the model so, with the kept and original sizes and a `[Full output: <path>]` pointer to the saved full output. Reported by @haamsuk-collab. ([#2402](https://github.com/code-yeongyu/senpi/issues/2402))
|
|
50
|
+
|
|
51
|
+
### Removed
|
|
52
|
+
|
|
53
|
+
## [2026.9.29-5] - 2026-09-29
|
|
54
|
+
|
|
55
|
+
### Breaking Changes
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
|
|
59
|
+
### Changed
|
|
60
|
+
|
|
61
|
+
### Fixed
|
|
62
|
+
|
|
63
|
+
### Removed
|
|
64
|
+
|
|
65
|
+
## [2026.9.29-4] - 2026-09-29
|
|
66
|
+
|
|
67
|
+
### Breaking Changes
|
|
68
|
+
|
|
69
|
+
### Added
|
|
70
|
+
|
|
71
|
+
### Changed
|
|
72
|
+
|
|
73
|
+
### Fixed
|
|
74
|
+
|
|
75
|
+
### Removed
|
|
76
|
+
|
|
77
|
+
## [2026.9.29-3] - 2026-09-29
|
|
78
|
+
|
|
79
|
+
### Breaking Changes
|
|
80
|
+
|
|
81
|
+
### Added
|
|
82
|
+
|
|
83
|
+
### Changed
|
|
84
|
+
|
|
85
|
+
### Fixed
|
|
86
|
+
|
|
87
|
+
### Removed
|
|
88
|
+
|
|
89
|
+
## [2026.9.29-2] - 2026-09-29
|
|
90
|
+
|
|
91
|
+
### Breaking Changes
|
|
92
|
+
|
|
93
|
+
### Added
|
|
94
|
+
|
|
95
|
+
### Changed
|
|
96
|
+
|
|
97
|
+
### Fixed
|
|
98
|
+
|
|
99
|
+
### Removed
|
|
100
|
+
|
|
15
101
|
## [2026.9.29] - 2026-09-29
|
|
16
102
|
|
|
17
103
|
### Breaking Changes
|
package/README.md
CHANGED
|
@@ -128,7 +128,7 @@ Configuration is loaded in this order:
|
|
|
128
128
|
| `taskTools.task` | `"task"` | Registered tool name used by `agent()`. |
|
|
129
129
|
| `taskTools.output` | `"task_output"` | Registered tool name used by `output()`. |
|
|
130
130
|
| `outputSink.headBytes` | `20480` | Bytes retained from the beginning of a middle-truncated preview; `0` disables it. |
|
|
131
|
-
| `outputSink.maxColumns` | `768` | Maximum
|
|
131
|
+
| `outputSink.maxColumns` | `768` | Maximum columns per printed output line; `0` disables column clamping. A cell's return value is never column-clamped (the byte and line budgets still apply). |
|
|
132
132
|
| `statusEvents` | `true` | Enables kernel status-event forwarding and rendering. Each cell retains at most 100 status rows; after overflow, one omitted-count row precedes the latest 99 events. |
|
|
133
133
|
| `memory.gcWatermarkMb` | `256` | JavaScript kernel: a finished cell whose heap reached this size and grew runs a full collection before its result; whenever at least this much stays live, an idle full collection runs about a second after the cell, so dropped globals return their memory without a reset. Python kernel: a finished cell whose process footprint reached this size and grew runs `gc.collect()` (plus glibc `malloc_trim(0)` on Linux) before its result, and while at least this much stays live the next cells collect too (at most 1/20 of the time), so `del rows` returns its memory. `0` disables. Env override: `SENPI_CODEMODE_MEMORY_GC_WATERMARK_MB`. |
|
|
134
134
|
| `memory.noticeMb` | `1024` | JavaScript and Python kernels: when live memory after a collection (JS heap, Python process footprint) reaches this size (first time, or 25% more than at the last notice), the result gets one bracketed notice naming the largest globals and how to drop them (`rows = undefined`, `del rows`), plus `details.memory`. Ruby and Julia get no notice. `0` disables. Env override: `SENPI_CODEMODE_MEMORY_NOTICE_MB`. |
|
|
@@ -320,7 +320,8 @@ Cell output is streamed while the cell runs. Large streams spill to an absolute
|
|
|
320
320
|
file after the default 50 KiB threshold or when the output column cap drops
|
|
321
321
|
bytes. With a session file such as `/path/session.jsonl`, artifacts live in
|
|
322
322
|
`/path/session-artifacts/`; sessions without a file use a unique temporary
|
|
323
|
-
directory.
|
|
323
|
+
directory. A truncated result tells the model so in its text: the kept and
|
|
324
|
+
original sizes, then a plain-path notice such as
|
|
324
325
|
`[Full output: /absolute/path/eval-….log]`.
|
|
325
326
|
|
|
326
327
|
## Deliberate differences from oh-my-pi
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/senpi-codemode",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.10.1-2",
|
|
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.
|
|
33
|
+
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.1-2",
|
|
34
34
|
"typebox": "1.3.34"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@code-yeongyu/senpi": "2026.
|
|
37
|
+
"@code-yeongyu/senpi": "2026.10.1-2"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@code-yeongyu/senpi": "2026.
|
|
40
|
+
"@code-yeongyu/senpi": "2026.10.1-2"
|
|
41
41
|
},
|
|
42
42
|
"keywords": [
|
|
43
43
|
"senpi",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ExtensionContext, withBundledBunCommands } from "@code-yeongyu/senpi";
|
|
2
2
|
import type { AgentExecuteTool } from "../bridges/agent-bridge.ts";
|
|
3
3
|
import type { EvalSchemaToolInfo } from "../bridges/schema-bridge.ts";
|
|
4
4
|
import type { CompletionRequest, CompletionResult } from "../completion/handler.ts";
|
|
@@ -67,7 +67,7 @@ export async function createRuntime(
|
|
|
67
67
|
const executeTool = createExecuteTool(pi, activeTools);
|
|
68
68
|
const create = options.createSessionManager ?? createCodemodeSessionManager;
|
|
69
69
|
const sessionId = sessionIdFrom(event);
|
|
70
|
-
const sessionEnv = sessionEnvironmentFrom(ctx);
|
|
70
|
+
const sessionEnv = { ...sessionEnvironmentFrom(ctx), ...bundledBunPathEntry() };
|
|
71
71
|
const configuredPoolWidth = settings.parallelPoolWidth;
|
|
72
72
|
const parallelPoolWidth = Number.isFinite(configuredPoolWidth) ? Math.max(1, Math.trunc(configuredPoolWidth)) : 1;
|
|
73
73
|
const manager = await create({
|
|
@@ -105,6 +105,17 @@ export function createExecuteTool(pi: CodemodeRuntimeAPI, activeTools?: Readonly
|
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
// In a compiled executable, Bun Shell runs `bun` as this executable unless PATH has one, which booted
|
|
109
|
+
// a second agent from `bun test` in a cell (omo#9362). Kernel children get the same bundled `bun`
|
|
110
|
+
// directory the bash tool's PATH carries.
|
|
111
|
+
function bundledBunPathEntry(): Record<string, string> {
|
|
112
|
+
const env = withBundledBunCommands(process.env);
|
|
113
|
+
if (env === process.env) return {};
|
|
114
|
+
const pathKey = Object.keys(env).find((key) => key.toLowerCase() === "path") ?? "PATH";
|
|
115
|
+
const value = env[pathKey];
|
|
116
|
+
return value === undefined ? {} : { [pathKey]: value };
|
|
117
|
+
}
|
|
118
|
+
|
|
108
119
|
function sessionIdFrom(event: unknown): string {
|
|
109
120
|
if (typeof event === "object" && event !== null && "sessionId" in event && typeof event.sessionId === "string") {
|
|
110
121
|
return event.sessionId;
|
|
@@ -63,9 +63,11 @@ export function formatTruncationWarning(meta: TruncationMeta | undefined): strin
|
|
|
63
63
|
const elidedLines = meta.elidedLines ?? Math.max(0, meta.totalLines - meta.outputLines);
|
|
64
64
|
const elidedBytes = meta.elidedBytes ?? Math.max(0, meta.totalBytes - meta.outputBytes);
|
|
65
65
|
message =
|
|
66
|
-
|
|
67
|
-
?
|
|
68
|
-
:
|
|
66
|
+
elidedLines === 0
|
|
67
|
+
? `${formatBytes(elidedBytes)} elided from the middle of a line`
|
|
68
|
+
: meta.headRange !== undefined && meta.tailRange !== undefined
|
|
69
|
+
? `Showing lines ${meta.headRange.start}-${meta.headRange.end} and ${meta.tailRange.start}-${meta.tailRange.end} of ${meta.totalLines}; ${elidedLines} middle line${elidedLines === 1 ? "" : "s"} (${formatBytes(elidedBytes)}) elided`
|
|
70
|
+
: `Showing ${meta.outputLines} of ${meta.totalLines} lines; middle elided`;
|
|
69
71
|
break;
|
|
70
72
|
}
|
|
71
73
|
case "head":
|
|
@@ -83,6 +85,14 @@ export function formatTruncationWarning(meta: TruncationMeta | undefined): strin
|
|
|
83
85
|
return `[${message}]`;
|
|
84
86
|
}
|
|
85
87
|
|
|
88
|
+
/** Model-facing notice for a cut result: kept and original sizes, then the full-output pointer. */
|
|
89
|
+
export function formatModelTruncationNotice(meta: TruncationMeta): string {
|
|
90
|
+
const { artifactId, ...shown } = meta;
|
|
91
|
+
const detail = formatTruncationWarning(shown)?.slice(1, -1);
|
|
92
|
+
const notice = `[Output truncated: kept ${meta.outputBytes} of ${meta.totalBytes} bytes.${detail === undefined ? "" : ` ${detail}`}]`;
|
|
93
|
+
return artifactId === undefined ? notice : `${notice}\n${artifactNotice(artifactId)}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
86
96
|
function formatByteLoss(meta: TruncationMeta): string {
|
|
87
97
|
const dropped = formatBytes(Math.max(0, meta.totalBytes - meta.outputBytes));
|
|
88
98
|
switch (meta.truncatedBy) {
|
|
@@ -47,6 +47,11 @@ function lineCount(text: string): number {
|
|
|
47
47
|
return text.length === 0 ? 0 : countNewlines(text) + 1;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
export interface PushOptions {
|
|
51
|
+
/** `false` exempts the chunk from the per-line column clamp; the byte and line budgets still apply. */
|
|
52
|
+
readonly clampColumns?: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
50
55
|
export class OutputSink {
|
|
51
56
|
readonly #artifactPath: string | undefined;
|
|
52
57
|
readonly #spillThreshold: number;
|
|
@@ -82,7 +87,7 @@ export class OutputSink {
|
|
|
82
87
|
this.#tail = new TailBuffer(this.#spillThreshold);
|
|
83
88
|
}
|
|
84
89
|
|
|
85
|
-
push(chunk: string): void {
|
|
90
|
+
push(chunk: string, options: PushOptions = {}): void {
|
|
86
91
|
if (chunk.length === 0) return;
|
|
87
92
|
this.#emitPreview(chunk);
|
|
88
93
|
const rawBytes = Buffer.byteLength(chunk, "utf8");
|
|
@@ -90,7 +95,9 @@ export class OutputSink {
|
|
|
90
95
|
this.#totalNewlines += countNewlines(chunk);
|
|
91
96
|
this.#sawData = true;
|
|
92
97
|
const droppedBefore = this.#columnDroppedBytes;
|
|
93
|
-
const
|
|
98
|
+
const clamp = this.#maxColumns > 0 && options.clampColumns !== false;
|
|
99
|
+
if (!clamp) this.#trackLineBytes(chunk);
|
|
100
|
+
const retained = clamp ? this.#clampColumns(chunk) : chunk;
|
|
94
101
|
this.#mirrorRaw(chunk, this.#columnDroppedBytes > droppedBefore);
|
|
95
102
|
this.#retain(retained);
|
|
96
103
|
}
|
|
@@ -146,6 +153,16 @@ export class OutputSink {
|
|
|
146
153
|
if (effectiveBytes > this.#headBytes + this.#tail.bytes()) this.#truncated = true;
|
|
147
154
|
}
|
|
148
155
|
|
|
156
|
+
#trackLineBytes(chunk: string): void {
|
|
157
|
+
const newline = chunk.lastIndexOf("\n");
|
|
158
|
+
if (newline === -1) {
|
|
159
|
+
this.#currentLineBytes += Buffer.byteLength(chunk, "utf8");
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
this.#currentLineBytes = Buffer.byteLength(chunk.substring(newline + 1), "utf8");
|
|
163
|
+
this.#columnCapped = false;
|
|
164
|
+
}
|
|
165
|
+
|
|
149
166
|
#clampColumns(chunk: string): string {
|
|
150
167
|
const output: string[] = [];
|
|
151
168
|
let cursor = 0;
|
package/src/tool/cell-runtime.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AgentToolResult, AgentToolUpdateCallback, ExtensionContext } from "@code-yeongyu/senpi";
|
|
2
2
|
import type { KernelMemoryReport } from "../bridge/memory-protocol.ts";
|
|
3
3
|
import type { KernelToHostMessage } from "../bridge/protocol.ts";
|
|
4
|
+
import { formatModelTruncationNotice } from "../output/output-meta.ts";
|
|
4
5
|
import { DEFAULT_MAX_BYTES, TailLineRing } from "../output/streaming-output.ts";
|
|
5
6
|
import type { EvalToolCallMetric } from "./call-capture.ts";
|
|
6
7
|
import { type EvalImageResizer, EvalOutputCollector, type EvalOutputResult } from "./image.ts";
|
|
@@ -85,7 +86,7 @@ export class CellResultBuilder {
|
|
|
85
86
|
this.#state.durationMs = result.durationMs;
|
|
86
87
|
this.#memory = result.memory;
|
|
87
88
|
if (result.ok) {
|
|
88
|
-
if (result.valueRepr) this.#output.
|
|
89
|
+
if (result.valueRepr) this.#output.pushValue(`${result.valueRepr}\n`);
|
|
89
90
|
this.#state.status = "complete";
|
|
90
91
|
} else {
|
|
91
92
|
this.#state.error = result.error.message;
|
|
@@ -153,11 +154,12 @@ export class CellResultBuilder {
|
|
|
153
154
|
this.#state.output = output.output;
|
|
154
155
|
const details = this.#details(output, isError);
|
|
155
156
|
this.emitUpdate(isError);
|
|
156
|
-
const
|
|
157
|
+
const shown =
|
|
157
158
|
output.output ||
|
|
158
159
|
(output.images.length > 0
|
|
159
160
|
? `(displayed ${output.images.length} image${output.images.length === 1 ? "" : "s"}; no text output)`
|
|
160
161
|
: "(no output)");
|
|
162
|
+
const text = output.meta === undefined ? shown : `${shown}\n${formatModelTruncationNotice(output.meta)}`;
|
|
161
163
|
const notice = this.#memory?.notice;
|
|
162
164
|
const noticePart = notice === undefined ? [] : [{ type: "text" as const, text: notice }];
|
|
163
165
|
return { content: [{ type: "text", text }, ...noticePart, ...output.images], details };
|
package/src/tool/image.ts
CHANGED
|
@@ -91,6 +91,11 @@ export class EvalOutputCollector {
|
|
|
91
91
|
this.#sink.push(text);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/** The cell's return value: exempt from the column clamp, still bound by the byte and line budgets. */
|
|
95
|
+
pushValue(text: string): void {
|
|
96
|
+
this.#sink.push(text, { clampColumns: false });
|
|
97
|
+
}
|
|
98
|
+
|
|
94
99
|
display(message: DisplayMessage): void {
|
|
95
100
|
if (message.mimeType.startsWith("image/")) {
|
|
96
101
|
if (
|