@code-yeongyu/senpi-codemode 2026.9.2 → 2026.9.3-3
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 +83 -0
- package/README.md +4 -2
- package/package.json +4 -4
- package/src/extension/skill-contribution.ts +43 -9
- package/src/index.ts +21 -1
- package/src/kernels/js/worker-runtime.js +6 -0
- package/src/kernels/js/worker-shell-capture.d.ts +10 -0
- package/src/kernels/js/worker-shell-capture.js +126 -0
- package/src/prompt/eval-prompt.ts +24 -7
- package/src/skill/bun-1-4/SKILL.md +1 -1
- package/src/skill/bun-1-4/references/runtime-apis.md +1 -1
- package/src/tool/eval-tool-options.ts +5 -1
- package/src/tool/eval-tool.ts +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,89 @@
|
|
|
12
12
|
|
|
13
13
|
### Removed
|
|
14
14
|
|
|
15
|
+
## [2026.9.3-3] - 2026-09-03
|
|
16
|
+
|
|
17
|
+
### Breaking Changes
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
### Removed
|
|
26
|
+
|
|
27
|
+
## [2026.9.3-2] - 2026-09-03
|
|
28
|
+
|
|
29
|
+
### Breaking Changes
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- JavaScript eval cells no longer leak child-process output onto the host terminal under Bun: `Bun.$` commands awaited without `.quiet()`/`.text()` and `Bun.spawn` children with the default stderr now route their output into the cell's stdout/stderr streams instead of the inherited fd 1/2 that the interactive TUI owns.
|
|
38
|
+
|
|
39
|
+
### Removed
|
|
40
|
+
|
|
41
|
+
## [2026.9.3] - 2026-09-03
|
|
42
|
+
|
|
43
|
+
### Breaking Changes
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
### Removed
|
|
52
|
+
|
|
53
|
+
## [2026.9.2-4] - 2026-09-02
|
|
54
|
+
|
|
55
|
+
### Breaking Changes
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
|
|
59
|
+
### Changed
|
|
60
|
+
|
|
61
|
+
### Fixed
|
|
62
|
+
|
|
63
|
+
### Removed
|
|
64
|
+
|
|
65
|
+
## [2026.9.2-3] - 2026-09-02
|
|
66
|
+
|
|
67
|
+
### Breaking Changes
|
|
68
|
+
|
|
69
|
+
### Added
|
|
70
|
+
|
|
71
|
+
### Changed
|
|
72
|
+
|
|
73
|
+
- The eval prompt's JS runtime line is now runtime-aware: on a bun kernel it names `Bun <version>` and
|
|
74
|
+
`Bun.*` builtins, and only while the bundled `bun-1-4` skill is active it adds a MUST READ pointer to
|
|
75
|
+
that skill's absolute path before the first js cell; node kernels keep the Node.js worker wording.
|
|
76
|
+
`activeBunSkillPath()` exposes the same gate the `resources_discover` contribution uses.
|
|
77
|
+
- The bundled `bun-1-4` skill description is rewritten as a fact-framed MUST READ notice with
|
|
78
|
+
English-only copy (Korean trigger words removed; the `Bun.stringWidth` example no longer uses Hangul).
|
|
79
|
+
|
|
80
|
+
### Fixed
|
|
81
|
+
|
|
82
|
+
- Compiled binaries now contribute the bundled `bun-1-4` skill by resolving the codemode sidecar shipped next to the executable, and a missing skill is reported on stderr so it can no longer corrupt the RPC protocol stream on stdout.
|
|
83
|
+
|
|
84
|
+
### Removed
|
|
85
|
+
|
|
86
|
+
## [2026.9.2-2] - 2026-09-02
|
|
87
|
+
|
|
88
|
+
### Breaking Changes
|
|
89
|
+
|
|
90
|
+
### Added
|
|
91
|
+
|
|
92
|
+
### Changed
|
|
93
|
+
|
|
94
|
+
### Fixed
|
|
95
|
+
|
|
96
|
+
### Removed
|
|
97
|
+
|
|
15
98
|
## [2026.9.2] - 2026-09-02
|
|
16
99
|
|
|
17
100
|
### Breaking Changes
|
package/README.md
CHANGED
|
@@ -36,7 +36,9 @@ task-tool names are known.
|
|
|
36
36
|
resolves absolute executable paths, and the eval prompt host line names the
|
|
37
37
|
JS runtime (`node`/`bun`).
|
|
38
38
|
- JavaScript import rewriting for supported local modules and package imports
|
|
39
|
-
in the persistent Node.js
|
|
39
|
+
in the persistent JS worker (Bun when senpi runs on bun, Node.js otherwise).
|
|
40
|
+
- On a Bun >= 1.4 kernel the eval prompt names the bundled `bun-1-4` skill as
|
|
41
|
+
MUST READ before the first js cell; node kernels keep the Node.js wording.
|
|
40
42
|
- GPT models receive a terse `eval` prompt dialect that prioritizes composing
|
|
41
43
|
active tools through `tool.<name>(args)` and documents detach-on-timeout.
|
|
42
44
|
|
|
@@ -44,7 +46,7 @@ task-tool names are known.
|
|
|
44
46
|
|
|
45
47
|
| Language | Default | Runtime | Notes |
|
|
46
48
|
| --- | --- | --- | --- |
|
|
47
|
-
| `js` | enabled |
|
|
49
|
+
| `js` | enabled | In-process worker on senpi's own runtime (Bun or Node.js 24+) | Supports top-level `await` and `return`; the eval prompt's runtime line follows the kernel. |
|
|
48
50
|
| `py` | enabled | `python3` or `python` | Optional interpreter detected at session start. |
|
|
49
51
|
| `rb` | disabled | `ruby` | Optional interpreter detected at session start. |
|
|
50
52
|
| `jl` | disabled | `julia` | Optional interpreter detected at session start. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/senpi-codemode",
|
|
3
|
-
"version": "2026.9.
|
|
3
|
+
"version": "2026.9.3-3",
|
|
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.9.
|
|
33
|
+
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.9.3-3",
|
|
34
34
|
"typebox": "1.3.18"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@code-yeongyu/senpi": "2026.9.
|
|
37
|
+
"@code-yeongyu/senpi": "2026.9.3-3"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@code-yeongyu/senpi": "2026.9.
|
|
40
|
+
"@code-yeongyu/senpi": "2026.9.3-3"
|
|
41
41
|
},
|
|
42
42
|
"keywords": [
|
|
43
43
|
"senpi",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { type CodemodeRuntimeAssetEnvironment, resolveCodemodeRuntimeAsset } from "../kernels/shared/runtime-asset.ts";
|
|
4
5
|
|
|
5
6
|
const BUN_SKILL_BASE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
6
7
|
|
|
@@ -27,29 +28,62 @@ export function bunVersionSupportsSkill(version: string | undefined): boolean {
|
|
|
27
28
|
return major > 1 || (major === 1 && minor >= 4);
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
const BUN_SKILL_PACKAGE_RELATIVE_PATH = join("skill", "bun-1-4", "SKILL.md");
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Absolute path of the bundled bun-1-4 SKILL.md, or undefined (logged once) when it is not shipped.
|
|
35
|
+
*
|
|
36
|
+
* A compiled Bun binary has no readable module-relative asset, so resolution falls back to the
|
|
37
|
+
* codemode sidecar shipped next to the executable, exactly as the Ruby and Julia kernel runners do.
|
|
38
|
+
* The miss is reported on stderr because stdout carries the RPC protocol stream.
|
|
39
|
+
*/
|
|
40
|
+
export function bundledBunSkillPath(
|
|
41
|
+
baseDir: string = BUN_SKILL_BASE_DIR,
|
|
42
|
+
environment: CodemodeRuntimeAssetEnvironment = {},
|
|
43
|
+
): string | undefined {
|
|
44
|
+
const localPath = join(baseDir, "..", "skill", "bun-1-4", "SKILL.md");
|
|
45
|
+
const candidate = resolveCodemodeRuntimeAsset(localPath, BUN_SKILL_PACKAGE_RELATIVE_PATH, environment);
|
|
33
46
|
if (existsSync(candidate)) return candidate;
|
|
34
47
|
if (!loggedMissingBunSkill) {
|
|
35
48
|
loggedMissingBunSkill = true;
|
|
36
|
-
|
|
49
|
+
// A compiled binary never has the module-relative asset, so naming only that path
|
|
50
|
+
// would hide the sidecar location an operator actually has to populate.
|
|
51
|
+
const executableDir = dirname(environment.executablePath ?? process.execPath);
|
|
52
|
+
const sidecarPath = join(
|
|
53
|
+
executableDir,
|
|
54
|
+
"node_modules",
|
|
55
|
+
"@code-yeongyu",
|
|
56
|
+
"senpi-codemode",
|
|
57
|
+
"src",
|
|
58
|
+
BUN_SKILL_PACKAGE_RELATIVE_PATH,
|
|
59
|
+
);
|
|
60
|
+
console.error(
|
|
61
|
+
`[senpi-codemode] bundled bun-1-4 skill not found at ${localPath} or ${sidecarPath}; skipping contribution`,
|
|
62
|
+
);
|
|
37
63
|
}
|
|
38
64
|
return undefined;
|
|
39
65
|
}
|
|
40
66
|
|
|
41
67
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* A node kernel never
|
|
68
|
+
* Absolute path of the bundled bun-1-4 SKILL.md when it is active for this process:
|
|
69
|
+
* the in-process js eval kernel itself runs bun >= 1.4 (`process.versions.bun`) and the
|
|
70
|
+
* asset is shipped. A node kernel never activates it, regardless of any bun binary on PATH.
|
|
45
71
|
*/
|
|
72
|
+
export function activeBunSkillPath(
|
|
73
|
+
getKernelBunVersion: BunKernelVersionSource = kernelBunVersion,
|
|
74
|
+
baseDir?: string,
|
|
75
|
+
): string | undefined {
|
|
76
|
+
if (!bunVersionSupportsSkill(getKernelBunVersion())) return undefined;
|
|
77
|
+
return bundledBunSkillPath(baseDir);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Builds the `resources_discover` handler that contributes the active bun-1-4 skill, if any. */
|
|
46
81
|
export function createBunSkillDiscoverHandler(
|
|
47
82
|
getKernelBunVersion: BunKernelVersionSource = kernelBunVersion,
|
|
48
83
|
baseDir?: string,
|
|
49
84
|
): () => { skillPaths: string[] } | undefined {
|
|
50
85
|
return () => {
|
|
51
|
-
|
|
52
|
-
const skillPath = bundledBunSkillPath(baseDir);
|
|
86
|
+
const skillPath = activeBunSkillPath(getKernelBunVersion, baseDir);
|
|
53
87
|
return skillPath === undefined ? undefined : { skillPaths: [skillPath] };
|
|
54
88
|
};
|
|
55
89
|
}
|
package/src/index.ts
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
import { jsRuntimeInfo, jsRuntimeLabel } from "./extension/runtime-info.ts";
|
|
17
17
|
import type { CodemodeSessionManager, CreateCodemodeSessionManagerOptions } from "./extension/session-manager.ts";
|
|
18
18
|
import { SessionManagerProxy } from "./extension/session-manager-proxy.ts";
|
|
19
|
-
import { registerBunSkillContribution } from "./extension/skill-contribution.ts";
|
|
19
|
+
import { activeBunSkillPath, registerBunSkillContribution } from "./extension/skill-contribution.ts";
|
|
20
20
|
import { WAKE_SOURCE_STATE_EVENT, type WakeSourceState } from "./extension/wake-source-state.ts";
|
|
21
21
|
import { EvalDetachedCellManager, type EvalDetachedCellStatusEntry } from "./tool/detached-cell-manager.ts";
|
|
22
22
|
import {
|
|
@@ -64,10 +64,20 @@ export interface SenpiCodemodeOptions {
|
|
|
64
64
|
readonly now?: () => number;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/** Whether the session registry holds `monitor`; false when the runtime cannot be read yet. */
|
|
68
|
+
function monitorIsRegistered(pi: CodemodeExtensionAPI): boolean {
|
|
69
|
+
try {
|
|
70
|
+
return pi.getAllTools().some((tool) => tool.name === "monitor");
|
|
71
|
+
} catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
67
76
|
export default function senpiCodemode(pi: CodemodeExtensionAPI, options: SenpiCodemodeOptions = {}): void {
|
|
68
77
|
const manager = new SessionManagerProxy();
|
|
69
78
|
const complete = options.complete ?? ((request, ctx) => createCompletionHandler()(ctx)(request));
|
|
70
79
|
const renderers = { renderCall: renderEvalCall, renderResult: renderEvalResult };
|
|
80
|
+
const bunSkillPath = activeBunSkillPath();
|
|
71
81
|
let activeRuntime: SessionRuntime | undefined;
|
|
72
82
|
let activeModelId: string | undefined;
|
|
73
83
|
let activeContext: ExtensionContext | undefined;
|
|
@@ -107,6 +117,11 @@ export default function senpiCodemode(pi: CodemodeExtensionAPI, options: SenpiCo
|
|
|
107
117
|
pi.rpc?.emit(EVAL_EXECUTION_EVENT, toEvalExecutionRpcPayload(payload));
|
|
108
118
|
pi.events?.emit(EVAL_EXECUTION_EVENT, payload);
|
|
109
119
|
};
|
|
120
|
+
// `listTools` below survives because it is lazy; this read is eager, and the loader's
|
|
121
|
+
// action methods throw while extensions are still loading (the bundled codemode path
|
|
122
|
+
// reaches this before the runtime is bound). An unreadable registry means "do not teach
|
|
123
|
+
// a tool we cannot confirm"; session_start / model_select re-register once it is live.
|
|
124
|
+
const monitor = monitorIsRegistered(pi);
|
|
110
125
|
pi.registerTool(
|
|
111
126
|
createEvalTool({
|
|
112
127
|
enabledLanguages: runtime.enabledLanguages,
|
|
@@ -121,10 +136,12 @@ export default function senpiCodemode(pi: CodemodeExtensionAPI, options: SenpiCo
|
|
|
121
136
|
executionTracker: manager,
|
|
122
137
|
onCellSettled,
|
|
123
138
|
renderers,
|
|
139
|
+
monitor,
|
|
124
140
|
spawns: runtime.spawns,
|
|
125
141
|
spawnDefaultAgent: runtime.settings.taskTools.task,
|
|
126
142
|
hostLine: hostLine(),
|
|
127
143
|
runtimes: runtime.runtimes,
|
|
144
|
+
...(bunSkillPath === undefined ? {} : { bunSkillPath }),
|
|
128
145
|
...(modelId === undefined ? {} : { modelId }),
|
|
129
146
|
}),
|
|
130
147
|
);
|
|
@@ -157,8 +174,11 @@ export default function senpiCodemode(pi: CodemodeExtensionAPI, options: SenpiCo
|
|
|
157
174
|
}),
|
|
158
175
|
executionTracker: manager,
|
|
159
176
|
renderers,
|
|
177
|
+
// The baseline tool is registered before extensions such as monitor load.
|
|
178
|
+
monitor: false,
|
|
160
179
|
hostLine: hostLine(),
|
|
161
180
|
runtimes: { js: jsRuntimeInfo() },
|
|
181
|
+
...(bunSkillPath === undefined ? {} : { bunSkillPath }),
|
|
162
182
|
}),
|
|
163
183
|
);
|
|
164
184
|
pi.registerRemovedToolHint(
|
|
@@ -3,6 +3,7 @@ import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { dirname, isAbsolute, join, normalize, resolve, sep } from "node:path";
|
|
4
4
|
import { inspect } from "node:util";
|
|
5
5
|
import { awaitMaybePromise, indirectEval, wrapUserCode } from "./worker-indirect-eval.js";
|
|
6
|
+
import { installShellCapture } from "./worker-shell-capture.js";
|
|
6
7
|
|
|
7
8
|
const PREPARED_CELL_PREFIX = "/*senpi:prepared-cell*/";
|
|
8
9
|
const INTERNAL_URL = /^([a-z][a-z0-9+.-]*):\/\/(.*)$/iu;
|
|
@@ -84,11 +85,16 @@ export class JsWorkerRuntime {
|
|
|
84
85
|
process.stderr.write = routeWrite(process.stderr, originalStderrWrite, "stderr");
|
|
85
86
|
console.log = (...values) => this.#emitText("stdout", `${values.map(formatValue).join(" ")}\n`);
|
|
86
87
|
console.error = (...values) => this.#emitText("stderr", `${values.map(formatValue).join(" ")}\n`);
|
|
88
|
+
const restoreShellCapture = installShellCapture({
|
|
89
|
+
isActive: () => this.#hooks !== null,
|
|
90
|
+
emitText: (stream, data) => this.#emitText(stream, data),
|
|
91
|
+
});
|
|
87
92
|
globalThis.__senpi_restore_console__ = () => {
|
|
88
93
|
console.log = originalLog;
|
|
89
94
|
console.error = originalError;
|
|
90
95
|
process.stdout.write = originalStdoutWrite;
|
|
91
96
|
process.stderr.write = originalStderrWrite;
|
|
97
|
+
restoreShellCapture();
|
|
92
98
|
};
|
|
93
99
|
}
|
|
94
100
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type ShellCaptureStream = "stdout" | "stderr";
|
|
2
|
+
|
|
3
|
+
export type ShellCaptureRestore = () => void;
|
|
4
|
+
|
|
5
|
+
export interface ShellCaptureOptions {
|
|
6
|
+
readonly isActive: () => boolean;
|
|
7
|
+
readonly emitText: (stream: ShellCaptureStream, data: string) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function installShellCapture(options: ShellCaptureOptions): ShellCaptureRestore;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
const SHELL_CONFIG_METHODS = ["env", "cwd", "nothrow", "throws"];
|
|
2
|
+
const SHELL_READ_METHODS = ["text", "json", "lines", "arrayBuffer", "bytes", "blob"];
|
|
3
|
+
|
|
4
|
+
export function installShellCapture(options) {
|
|
5
|
+
const bun = globalThis.Bun;
|
|
6
|
+
if (!isBunRuntime(bun)) return () => {};
|
|
7
|
+
const originalShell = bun.$;
|
|
8
|
+
const originalSpawn = bun.spawn;
|
|
9
|
+
bun.$ = capturedShell(originalShell, options);
|
|
10
|
+
bun.spawn = capturedSpawn(originalSpawn, options);
|
|
11
|
+
return () => {
|
|
12
|
+
bun.$ = originalShell;
|
|
13
|
+
bun.spawn = originalSpawn;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isBunRuntime(bun) {
|
|
18
|
+
return bun !== null && typeof bun === "object" && typeof bun.$ === "function" && typeof bun.spawn === "function";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function capturedShell(originalShell, options) {
|
|
22
|
+
const shell = (strings, ...expressions) => {
|
|
23
|
+
const promise = originalShell(strings, ...expressions);
|
|
24
|
+
return options.isActive() ? captureShellPromise(promise, options.emitText) : promise;
|
|
25
|
+
};
|
|
26
|
+
for (const key of Object.keys(originalShell)) shell[key] = originalShell[key];
|
|
27
|
+
for (const method of SHELL_CONFIG_METHODS) {
|
|
28
|
+
shell[method] = (...args) => {
|
|
29
|
+
originalShell[method](...args);
|
|
30
|
+
return shell;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return shell;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function captureShellPromise(promise, emitText) {
|
|
37
|
+
const prototype = Object.getPrototypeOf(promise);
|
|
38
|
+
let echo = true;
|
|
39
|
+
const echoOnce = (output) => {
|
|
40
|
+
if (!echo) return;
|
|
41
|
+
echo = false;
|
|
42
|
+
emitShellOutput(output, emitText);
|
|
43
|
+
};
|
|
44
|
+
prototype.quiet.call(promise);
|
|
45
|
+
promise.quiet = function quiet() {
|
|
46
|
+
echo = false;
|
|
47
|
+
return prototype.quiet.call(this);
|
|
48
|
+
};
|
|
49
|
+
for (const method of SHELL_READ_METHODS) {
|
|
50
|
+
if (typeof prototype[method] !== "function") continue;
|
|
51
|
+
promise[method] = function read(...args) {
|
|
52
|
+
echo = false;
|
|
53
|
+
return prototype[method].apply(this, args);
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
promise.then = function then(onFulfilled, onRejected) {
|
|
57
|
+
return prototype.then.call(
|
|
58
|
+
this,
|
|
59
|
+
(output) => {
|
|
60
|
+
echoOnce(output);
|
|
61
|
+
return onFulfilled ? onFulfilled(output) : output;
|
|
62
|
+
},
|
|
63
|
+
(error) => {
|
|
64
|
+
echoOnce(error);
|
|
65
|
+
if (onRejected) return onRejected(error);
|
|
66
|
+
throw error;
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
return promise;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function emitShellOutput(output, emitText) {
|
|
74
|
+
if (output === null || typeof output !== "object") return;
|
|
75
|
+
const stdout = outputText(output.stdout);
|
|
76
|
+
if (stdout) emitText("stdout", stdout);
|
|
77
|
+
const stderr = outputText(output.stderr);
|
|
78
|
+
if (stderr) emitText("stderr", stderr);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function outputText(value) {
|
|
82
|
+
if (value instanceof Uint8Array) return new TextDecoder().decode(value);
|
|
83
|
+
return typeof value === "string" ? value : "";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function capturedSpawn(originalSpawn, options) {
|
|
87
|
+
return (...args) => {
|
|
88
|
+
if (!options.isActive()) return originalSpawn(...args);
|
|
89
|
+
const [first, second] = args;
|
|
90
|
+
if (Array.isArray(first)) {
|
|
91
|
+
const spawnOptions = second === undefined ? {} : second;
|
|
92
|
+
if (!needsStderrCapture(spawnOptions)) return originalSpawn(...args);
|
|
93
|
+
return drainStderr(originalSpawn(first, { ...spawnOptions, stderr: "pipe" }), options.emitText);
|
|
94
|
+
}
|
|
95
|
+
if (!needsStderrCapture(first)) return originalSpawn(...args);
|
|
96
|
+
return drainStderr(originalSpawn({ ...first, stderr: "pipe" }), options.emitText);
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function needsStderrCapture(spawnOptions) {
|
|
101
|
+
return (
|
|
102
|
+
spawnOptions !== null &&
|
|
103
|
+
typeof spawnOptions === "object" &&
|
|
104
|
+
spawnOptions.stdio === undefined &&
|
|
105
|
+
spawnOptions.stderr === undefined
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function drainStderr(child, emitText) {
|
|
110
|
+
const stream = child?.stderr;
|
|
111
|
+
if (!(stream instanceof ReadableStream)) return child;
|
|
112
|
+
void readStream(stream, emitText).catch((error) => {
|
|
113
|
+
emitText("stderr", `[spawn stderr capture failed: ${String(error)}]\n`);
|
|
114
|
+
});
|
|
115
|
+
return child;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function readStream(stream, emitText) {
|
|
119
|
+
const decoder = new TextDecoder();
|
|
120
|
+
for await (const chunk of stream) {
|
|
121
|
+
const text = decoder.decode(chunk, { stream: true });
|
|
122
|
+
if (text) emitText("stderr", text);
|
|
123
|
+
}
|
|
124
|
+
const tail = decoder.decode();
|
|
125
|
+
if (tail) emitText("stderr", tail);
|
|
126
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { EvalRuntimeInfo } from "../tool/types.ts";
|
|
2
|
+
|
|
1
3
|
export interface EnabledLanguages {
|
|
2
4
|
readonly py: boolean;
|
|
3
5
|
readonly js: boolean;
|
|
@@ -13,11 +15,17 @@ export interface EvalPromptParts {
|
|
|
13
15
|
|
|
14
16
|
export interface EvalPromptOptions {
|
|
15
17
|
readonly spawns: boolean;
|
|
18
|
+
/** Whether the session registry exposes the monitor tool through eval. */
|
|
19
|
+
readonly monitor?: boolean;
|
|
16
20
|
readonly spawnDefaultAgent?: string;
|
|
17
21
|
/** Active model id; selects the emphasis dialect of the batching guidance. */
|
|
18
22
|
readonly modelId?: string;
|
|
19
23
|
/** Preformatted host line (e.g. "darwin arm64 · Apple M5 Max · 18 cores"); enables the host-sizing note. */
|
|
20
24
|
readonly hostLine?: string;
|
|
25
|
+
/** Identity of the in-process js kernel; a bun runtime swaps the Node.js worker line for the Bun one. */
|
|
26
|
+
readonly jsRuntime?: EvalRuntimeInfo;
|
|
27
|
+
/** Absolute path of the active bun-1-4 skill; rendered as a MUST READ pointer only on a bun kernel. */
|
|
28
|
+
readonly bunSkillPath?: string;
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
/** Prompt dialect for the eval-first batching emphasis. */
|
|
@@ -99,21 +107,26 @@ Work incrementally: imports in one call, define in the next, test, then use —
|
|
|
99
107
|
\`eval\` is your default execution surface: if a step needs more than one tool call, write ONE cell that performs the whole step — never issue the calls one at a time.
|
|
100
108
|
- Enumerate every lookup the step needs, then run all independent ones simultaneously with \`parallel(thunks)\` inside the cell; keep calls sequential only when one result feeds the next.
|
|
101
109
|
- Write real code around the calls: loop or comprehend over file sets with \`read()\`/stdlib, branch per case, and wrap risky calls in try/except so one failure degrades only its item — recover or retry inside the cell, keep the batch alive.
|
|
102
|
-
- Post-process \`tool.<name>()\` results programmatically and return distilled facts, not raw dumps.
|
|
110
|
+
- Post-process \`tool.<name>()\` results programmatically — filter, join, aggregate — and return distilled facts, not raw dumps.
|
|
111
|
+
{{#if monitor}}- Start long-running work (build, test run, deploy, or watch) through \`tool.monitor({ command, filter })\`, putting the decisive-line filter inside the same cell, then keep working until its event wakes the turn; sleeping, timed retries, and repeated status reads are not waiting.{{/if}}
|
|
103
112
|
</eval_first_batching>{{/if}}{{#if styleGpt}}<gpt_eval_dialect>
|
|
104
113
|
GPT eval: compose multi-tool work inside one cell with \`tool.<name>(args)\` and \`parallel(thunks)\`; do not split a planned step into serial tool calls.
|
|
105
114
|
- Long pure-compute cells detach on timeout and notify on completion. Do not poll or re-run them; use \`eval({ action: "peek"|"stop", cell_id })\` only to inspect or stop a detached cell.
|
|
106
|
-
-
|
|
115
|
+
- Filter, join, and aggregate tool results in the cell; return only decision-relevant facts.
|
|
116
|
+
{{#if monitor}}- For long-running build, test run, deploy, or watch work, start \`tool.monitor({ command, filter })\` with the decisive-line filter in the same cell; keep working while its event wakes the turn; sleeping, timed retries, and repeated status reads are not waiting.{{/if}}
|
|
107
117
|
</gpt_eval_dialect>{{/if}}{{#if styleCodex}}Route multi-call steps through eval: one cell per step, independent lookups dispatched together via \`parallel(thunks)\`; keep work sequential only when one result determines the next action.
|
|
108
|
-
- Loop or comprehend over file sets with \`read()\`/stdlib instead of reading files one call at a time; post-process \`tool.<name>()\` results programmatically.
|
|
118
|
+
- Loop or comprehend over file sets with \`read()\`/stdlib instead of reading files one call at a time; post-process \`tool.<name>()\` results programmatically — filter, join, aggregate.
|
|
109
119
|
- Wrap failable calls in try/except inside the cell; a failed item degrades only itself. After two distinct failed strategies for the same fact, fall back to direct tool calls.
|
|
110
|
-
- Reduce large results in-kernel to the facts the task needs before returning.
|
|
120
|
+
- Reduce large results in-kernel to the facts the task needs before returning.
|
|
121
|
+
{{#if monitor}}- Long-running build/test/deploy/watch work: start \`tool.monitor({ command, filter })\` with the decisive-line filter inside the same cell, then continue working; its event wakes the turn — sleeping, timed retries, and repeated status reads are not waiting.{{/if}}{{/if}}{{#if styleKimi}}**EVAL IS YOUR SUPERPOWER — MAKE IT YOUR DEFAULT WAY TO ACT.** Before any step, think: "how do I execute this WHOLE step in ONE parallelized cell?" — then write that ONE cell.
|
|
111
122
|
- **BATCH EVERYTHING AT ONCE:** enumerate EVERY independent lookup the step needs and dispatch them ALL simultaneously with \`parallel(thunks)\` in that cell; keep calls sequential only when one result feeds the next.
|
|
112
123
|
- **WRITE REAL CODE, NOT CALL CHAINS:** loop or comprehend over file sets with \`read()\`/stdlib, post-process \`tool.<name>()\` results programmatically, and put try/except around each risky call so the rest of the batch completes.
|
|
113
|
-
- **DISTILL IN-KERNEL:** filter and aggregate results in code, then return ONLY the distilled facts.
|
|
124
|
+
- **DISTILL IN-KERNEL:** filter, join, and aggregate \`tool.<name>()\` results in code, then return ONLY the distilled facts.
|
|
125
|
+
{{#if monitor}}- **DO start long-running build, test run, deploy, or watch work with \`tool.monitor({ command, filter })\`, put the decisive-line filter INSIDE THE SAME CELL, and KEEP WORKING until its event wakes the turn; sleeping, timed retries, and repeated status reads are not waiting.**{{/if}}{{/if}}{{#if styleDefault}}**EVAL IS YOUR PRIMARY EXECUTION SURFACE.** Any step that needs MORE THAN ONE tool call MUST be written as ONE cell — NEVER as a chain of single tool calls.
|
|
114
126
|
- **PLAN THE WHOLE STEP, THEN BATCH IT.** Enumerate every read/search/lookup the step needs and dispatch ALL independent ones through \`parallel(thunks)\` in one cell.
|
|
115
127
|
- **WRITE REAL CODE, NOT CALL LISTS.** Loop or comprehend over file sets with \`read()\`/stdlib, branch \`if\`/\`else\` per case, post-process \`tool.<name>()\` results programmatically, and wrap EVERY risky call in try/except so ONE failure NEVER kills the batch.
|
|
116
|
-
- **DISTILL IN-KERNEL.** Filter, diff, and aggregate in code before returning; return facts, NOT dumps.
|
|
128
|
+
- **DISTILL IN-KERNEL.** Filter, join, diff, and aggregate in code before returning; return facts, NOT dumps.
|
|
129
|
+
{{#if monitor}}- **LONG-RUNNING build, test run, deploy, or watch work MUST start with \`tool.monitor({ command, filter })\`, with the decisive-line filter INSIDE THE SAME CELL; KEEP WORKING until its event wakes the turn — SLEEPING, TIMED RETRIES, AND REPEATED STATUS READS ARE NOT WAITING.**{{/if}}{{/if}}
|
|
117
130
|
{{#if hostLine}}
|
|
118
131
|
Host: {{hostLine}} — cells execute here. Size \`parallel(thunks)\` pools to its cores; \`tool.<name>()\` shell commands must fit this platform, even when the code you are writing targets another machine.
|
|
119
132
|
{{/if}}
|
|
@@ -132,7 +145,7 @@ Fields:
|
|
|
132
145
|
A detached cell keeps its language kernel busy while it finishes; another language can continue. Do not re-run a detached cell: the same-language busy error names its cell id and output tail. Completion arrives as one notification with the final value/error and buffered output. Stopping a cell interrupts its kernel; the stop result states whether kernel state survived or the kernel was restarted and its variables lost.
|
|
133
146
|
|
|
134
147
|
{{#if py}}Live event loop: use top-level \`await\` directly; \`asyncio.run(…)\` raises "cannot be called from a running event loop".{{/if}}
|
|
135
|
-
{{#if js}}JS runs under Node.js worker: top-level \`await\`/\`return\` work; \`fetch\`/\`Buffer\` available.{{/if}}
|
|
148
|
+
{{#if js}}{{#if jsBun}}JS runs in-process on Bun {{jsVersion}}: top-level \`await\`/\`return\` work; \`Bun.*\` builtins available.{{#if bunSkillPath}} MUST READ the bun-1-4 skill at {{bunSkillPath}} before your first js cell — its builtins replace the npm packages you would otherwise install.{{/if}}{{else}}JS runs under Node.js worker: top-level \`await\`/\`return\` work; \`fetch\`/\`Buffer\` available.{{/if}}{{/if}}
|
|
136
149
|
{{#if rb}}Ruby: synchronous; helper options are keyword args{{#if spawns}} (e.g. \`output("id", limit: 2)\`){{/if}}; the last expression auto-displays unless it is \`nil\`, an assignment, or a definition (like IRB).{{/if}}
|
|
137
150
|
{{#if jl}}Julia: synchronous; helper options are standard keyword args{{#if spawns}} (e.g. \`output("id", limit=2)\`){{/if}}; the last expression auto-displays unless it is an assignment or a definition (like the Julia REPL).{{/if}}
|
|
138
151
|
On error, fix and re-run only the failing step. State usually survives a normal error, but a timeout or stop may have restarted the kernel — its message says which. Before rebuilding state, check a sentinel (a variable you defined earlier); only re-establish what is actually gone, since blind re-runs duplicate side effects.
|
|
@@ -205,6 +218,7 @@ export function buildEvalPrompt(
|
|
|
205
218
|
rb: enabled.rb,
|
|
206
219
|
jl: enabled.jl,
|
|
207
220
|
spawns: options.spawns,
|
|
221
|
+
monitor: options.monitor === true,
|
|
208
222
|
spawnDefaultAgent,
|
|
209
223
|
styleClaude: style === "claude",
|
|
210
224
|
styleCodex: style === "codex",
|
|
@@ -212,6 +226,9 @@ export function buildEvalPrompt(
|
|
|
212
226
|
styleKimi: style === "kimi",
|
|
213
227
|
styleDefault: style === "default",
|
|
214
228
|
hostLine: options.hostLine ?? "",
|
|
229
|
+
jsBun: options.jsRuntime?.name === "bun",
|
|
230
|
+
jsVersion: options.jsRuntime?.version ?? "",
|
|
231
|
+
bunSkillPath: options.bunSkillPath ?? "",
|
|
215
232
|
};
|
|
216
233
|
const examples = REUSE_CHAIN_EXAMPLES.filter((example) => enabled[example.language])
|
|
217
234
|
.map((example) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: bun-1-4
|
|
3
|
-
description: "MUST
|
|
3
|
+
description: "MUST READ before your first js eval cell: this session's eval js kernel runs Bun 1.4+ (this skill is present only when it does). Also read before any bun -e, script, server, CLI, test, bundle, or package-management work. Bun 1.4 ships builtins that replace 15+ npm deps — check here BEFORE installing sharp, puppeteer/playwright (scraping), marked, node-cron, node-pty, concurrently, serve-static, tar, json5, fast-xml-parser, string-width. Triggers: eval js, bun, Bun.serve, bun test, bun build, bun install, bun run, image resize, headless browser, markdown render, cron, PTY."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Bun 1.4 — Use the Builtins First
|
|
@@ -139,7 +139,7 @@ Blog: [#also-built-in](https://bun.com/blog/bun-v1.4#also-built-in)
|
|
|
139
139
|
Docs: <https://bun.com/docs/runtime/utils>
|
|
140
140
|
|
|
141
141
|
```ts
|
|
142
|
-
Bun.stringWidth("
|
|
142
|
+
Bun.stringWidth("\x1b[32mgreen\x1b[0m e\u0301"); // terminal columns, ANSI + grapheme aware
|
|
143
143
|
Bun.sliceAnsi(str, 0, 20); // slice by columns, preserving ANSI codes
|
|
144
144
|
Bun.wrapAnsi(str, 80); // wrap by columns
|
|
145
145
|
```
|
|
@@ -36,11 +36,15 @@ export interface CreateEvalToolOptions {
|
|
|
36
36
|
readonly proxyExecutor?: (params: EvalToolInput, signal?: AbortSignal) => Promise<AgentToolResult<EvalToolDetails>>;
|
|
37
37
|
readonly renderers?: Pick<ToolDefinition<EvalInputSchema, EvalToolDetails>, "renderCall" | "renderResult">;
|
|
38
38
|
readonly spawns?: boolean;
|
|
39
|
+
/** Whether the session registry exposes the monitor tool through eval. */
|
|
40
|
+
readonly monitor?: boolean;
|
|
39
41
|
readonly spawnDefaultAgent?: string;
|
|
40
42
|
readonly modelId?: string;
|
|
41
43
|
readonly hostLine?: string;
|
|
42
|
-
/** Display identity of each language's runtime, shown in headers and details. */
|
|
44
|
+
/** Display identity of each language's runtime, shown in headers and details; `js` also selects the prompt's runtime line. */
|
|
43
45
|
readonly runtimes?: EvalRuntimes;
|
|
46
|
+
/** Absolute path of the active bun-1-4 skill; the prompt names it as MUST READ on a bun kernel. */
|
|
47
|
+
readonly bunSkillPath?: string;
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
export interface EvalCellInvocation {
|
package/src/tool/eval-tool.ts
CHANGED
|
@@ -28,9 +28,12 @@ export function createEvalTool(options: CreateEvalToolOptions): ToolDefinition<E
|
|
|
28
28
|
const parameters = createEvalInputSchema(options.enabledLanguages);
|
|
29
29
|
const prompt = buildEvalPrompt(options.enabledLanguages, {
|
|
30
30
|
spawns: options.spawns ?? false,
|
|
31
|
+
monitor: options.monitor,
|
|
31
32
|
...(options.spawnDefaultAgent === undefined ? {} : { spawnDefaultAgent: options.spawnDefaultAgent }),
|
|
32
33
|
...(options.modelId === undefined ? {} : { modelId: options.modelId }),
|
|
33
34
|
...(options.hostLine === undefined ? {} : { hostLine: options.hostLine }),
|
|
35
|
+
...(options.runtimes?.js === undefined ? {} : { jsRuntime: options.runtimes.js }),
|
|
36
|
+
...(options.bunSkillPath === undefined ? {} : { bunSkillPath: options.bunSkillPath }),
|
|
34
37
|
});
|
|
35
38
|
const languages = enabledLanguageList(options.enabledLanguages);
|
|
36
39
|
const cellManager =
|