@code-yeongyu/senpi-codemode 2026.9.12-3 → 2026.9.13-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 +28 -0
- package/README.md +16 -4
- package/package.json +4 -4
- package/src/kernels/js/worker-core.js +2 -0
- package/src/kernels/session-env.ts +6 -0
- package/src/tool/detached-cell-manager.ts +6 -1
- package/src/tool/run-eval-cell.ts +31 -12
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,34 @@
|
|
|
12
12
|
|
|
13
13
|
### Removed
|
|
14
14
|
|
|
15
|
+
## [2026.9.13-2] - 2026-09-13
|
|
16
|
+
|
|
17
|
+
### Breaking Changes
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Added `PI_SESSION_CWD` and `PI_GOAL_STORE_FILE` session environment keys for kernels and shell children, including clearing of inherited values when absent (fixes #1663).
|
|
22
|
+
|
|
23
|
+
- Interactive foreground eval cells detach on queued steering without cancelling their computation or in-flight tools. An occupied detached slot keeps the call waiting ([#1637](https://github.com/code-yeongyu/senpi/issues/1637)).
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
### Removed
|
|
30
|
+
|
|
31
|
+
## [2026.9.13] - 2026-09-13
|
|
32
|
+
|
|
33
|
+
### Breaking Changes
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
### Removed
|
|
42
|
+
|
|
15
43
|
## [2026.9.12-3] - 2026-09-12
|
|
16
44
|
|
|
17
45
|
### Breaking Changes
|
package/README.md
CHANGED
|
@@ -57,8 +57,9 @@ schema; it is not an installation failure.
|
|
|
57
57
|
### Session environment
|
|
58
58
|
|
|
59
59
|
Every kernel starts with the active session's `PI_*` environment — `PI_SESSION_ID`,
|
|
60
|
-
`PI_SESSION_FILE` (when the session is persistent), `
|
|
61
|
-
`
|
|
60
|
+
`PI_SESSION_FILE` (when the session is persistent), `PI_SESSION_CWD` (the session's
|
|
61
|
+
working directory), `PI_GOAL_STORE_FILE` (the authoritative goal-store path, when the
|
|
62
|
+
host provides it), `PI_PROVIDER`, `PI_MODEL`, and `PI_REASONING_LEVEL` (when set) — resolved at session start, mirroring the bash tool's
|
|
62
63
|
session environment contract. The values are visible to `env()`/`process.env`/`os.environ`
|
|
63
64
|
inside cells and are inherited by every child process a cell spawns
|
|
64
65
|
(`Bun.$`, `Bun.spawn`, `child_process`, `subprocess`, ...). Inherited `PI_*` values from
|
|
@@ -67,6 +68,11 @@ what a child spawned from the bash tool sees. The values snapshot at kernel star
|
|
|
67
68
|
mid-session model switch updates the bash tool's next command but not already-running
|
|
68
69
|
kernels; a new session starts fresh kernels with fresh values.
|
|
69
70
|
|
|
71
|
+
`PI_GOAL_STORE_FILE` is supplied by the host's optional `ExtensionContext.goalStoreFile`
|
|
72
|
+
getter and may name a file that does not exist yet. It honors session-directory overrides
|
|
73
|
+
and in-memory sessions; it cannot be derived reliably from `PI_SESSION_FILE`. If the host
|
|
74
|
+
omits the getter, the variable is unset rather than inherited from the launching process.
|
|
75
|
+
|
|
70
76
|
## Settings
|
|
71
77
|
|
|
72
78
|
Configuration is loaded in this order:
|
|
@@ -177,6 +183,12 @@ cell keeps only its own language kernel busy. A new same-language call returns
|
|
|
177
183
|
a busy error with its cell id and output tail; calls in other languages continue
|
|
178
184
|
normally. Do not re-run the cell.
|
|
179
185
|
|
|
186
|
+
Queued steering also detaches an eligible interactive foreground call, including
|
|
187
|
+
one paused in a host tool bridge, without cancelling its computation. If the
|
|
188
|
+
language's detached slot is occupied, steering leaves the call waiting. Follow-up
|
|
189
|
+
messages, explicit `on_timeout: "error"`, and print/JSON calls do not trigger this
|
|
190
|
+
transition; caller abort and existing deadlines retain their cancellation behavior.
|
|
191
|
+
|
|
180
192
|
Every cell, detached or not, is bounded by two kill deadlines. The run budget
|
|
181
193
|
(`runBudgetSeconds`, or the call's `timeout`) charges only the cell's own
|
|
182
194
|
execution time and is paused while a host tool call is in flight, so a cell
|
|
@@ -185,8 +197,8 @@ The hard limit (`hardLimitSeconds`, raised by a larger `timeout`) is wall-clock
|
|
|
185
197
|
and bounds parked cells too. A cell killed by either deadline reports which one
|
|
186
198
|
in its result or completion notification, together with whether kernel state
|
|
187
199
|
survived; the tool schema states the configured numbers. The `timeout` value
|
|
188
|
-
never changes
|
|
189
|
-
|
|
200
|
+
never changes the idle detach deadline: that is `cellTimeoutSeconds` capped by
|
|
201
|
+
`foregroundWindowSeconds`; queued steering can detach the call earlier.
|
|
190
202
|
|
|
191
203
|
While any cell is detached, the interactive footer shows a highlighted
|
|
192
204
|
`↗ <language> · <summary>` status on the extension status line (the cell id
|
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.13-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.9.
|
|
33
|
+
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.9.13-2",
|
|
34
34
|
"typebox": "1.3.27"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@code-yeongyu/senpi": "2026.9.
|
|
37
|
+
"@code-yeongyu/senpi": "2026.9.13-2"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@code-yeongyu/senpi": "2026.9.
|
|
40
|
+
"@code-yeongyu/senpi": "2026.9.13-2"
|
|
41
41
|
},
|
|
42
42
|
"keywords": [
|
|
43
43
|
"senpi",
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
export const SESSION_ENVIRONMENT_KEYS = [
|
|
13
13
|
"PI_SESSION_ID",
|
|
14
14
|
"PI_SESSION_FILE",
|
|
15
|
+
"PI_SESSION_CWD",
|
|
16
|
+
"PI_GOAL_STORE_FILE",
|
|
15
17
|
"PI_PROVIDER",
|
|
16
18
|
"PI_MODEL",
|
|
17
19
|
"PI_REASONING_LEVEL",
|
|
@@ -22,6 +24,8 @@ export type SessionEnvironment = Readonly<Record<string, string>>;
|
|
|
22
24
|
|
|
23
25
|
/** Structural slice of `ExtensionContext` the session environment is resolved from. */
|
|
24
26
|
export interface SessionEnvironmentSource {
|
|
27
|
+
readonly cwd: string;
|
|
28
|
+
readonly goalStoreFile?: string;
|
|
25
29
|
readonly sessionManager: {
|
|
26
30
|
getSessionId(): string;
|
|
27
31
|
getSessionFile(): string | undefined;
|
|
@@ -33,6 +37,8 @@ export interface SessionEnvironmentSource {
|
|
|
33
37
|
export function sessionEnvironmentFrom(source: SessionEnvironmentSource): SessionEnvironment {
|
|
34
38
|
const env: Record<string, string> = {};
|
|
35
39
|
env.PI_SESSION_ID = source.sessionManager.getSessionId();
|
|
40
|
+
env.PI_SESSION_CWD = source.cwd;
|
|
41
|
+
if (source.goalStoreFile) env.PI_GOAL_STORE_FILE = source.goalStoreFile;
|
|
36
42
|
const sessionFile = source.sessionManager.getSessionFile();
|
|
37
43
|
if (sessionFile) env.PI_SESSION_FILE = sessionFile;
|
|
38
44
|
const model = source.model;
|
|
@@ -144,7 +144,12 @@ export class EvalDetachedCellManager {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
detach(cell: ManagedCell): boolean {
|
|
147
|
-
if (
|
|
147
|
+
if (
|
|
148
|
+
!cell.canDetach ||
|
|
149
|
+
!allowsDetachedCellTransition(cell.state, "detached") ||
|
|
150
|
+
this.#detachedByLanguage.has(cell.input.language)
|
|
151
|
+
)
|
|
152
|
+
return false;
|
|
148
153
|
cell.state = "detached";
|
|
149
154
|
cell.wasDetached = true;
|
|
150
155
|
this.#detachedByLanguage.set(cell.input.language, cell);
|
|
@@ -49,6 +49,12 @@ export async function runEvalCell(
|
|
|
49
49
|
let detached = false;
|
|
50
50
|
let execution: CellExecution;
|
|
51
51
|
const cell = cellManager.create(invocation.cellId, invocation.input, (error) => execution.cancel(error));
|
|
52
|
+
const detach = (): boolean => {
|
|
53
|
+
if (!cellManager.detach(cell)) return false;
|
|
54
|
+
detached = true;
|
|
55
|
+
execution.detach();
|
|
56
|
+
return true;
|
|
57
|
+
};
|
|
52
58
|
execution = new CellExecution({
|
|
53
59
|
callerSignal: invocation.signal,
|
|
54
60
|
cellId: invocation.cellId,
|
|
@@ -58,12 +64,7 @@ export async function runEvalCell(
|
|
|
58
64
|
timeoutMs: detachAfterMs,
|
|
59
65
|
maxPauseGraceMs: foregroundWindowMs,
|
|
60
66
|
onTimeout: (error: Error) => {
|
|
61
|
-
if (
|
|
62
|
-
detached = true;
|
|
63
|
-
execution.detach();
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
execution.cancel(error);
|
|
67
|
+
if (!detach()) execution.cancel(error);
|
|
67
68
|
},
|
|
68
69
|
},
|
|
69
70
|
}
|
|
@@ -74,6 +75,16 @@ export async function runEvalCell(
|
|
|
74
75
|
bridgeAbortController.abort(error);
|
|
75
76
|
},
|
|
76
77
|
});
|
|
78
|
+
const steeringSignal =
|
|
79
|
+
detaches && invocation.ctx.mode !== "print" && invocation.ctx.mode !== "json"
|
|
80
|
+
? invocation.ctx.steeringSignal
|
|
81
|
+
: undefined;
|
|
82
|
+
const onSteering = (): void => {
|
|
83
|
+
if (!steeringSignal?.aborted || detached || !state.active || invocation.signal.aborted) return;
|
|
84
|
+
// Losing the slot keeps this call foreground; only the existing deadlines/caller may cancel it.
|
|
85
|
+
detach();
|
|
86
|
+
};
|
|
87
|
+
steeringSignal?.addEventListener("abort", onSteering, { once: true });
|
|
77
88
|
const running = executeCell(
|
|
78
89
|
options,
|
|
79
90
|
invocation,
|
|
@@ -83,6 +94,7 @@ export async function runEvalCell(
|
|
|
83
94
|
execution,
|
|
84
95
|
bridgeContext,
|
|
85
96
|
bridgeAbortController,
|
|
97
|
+
onSteering,
|
|
86
98
|
);
|
|
87
99
|
let settleEventEmitted = false;
|
|
88
100
|
const emitSettled = (outcome: EvalExecutionSettleOutcome): void => {
|
|
@@ -110,12 +122,16 @@ export async function runEvalCell(
|
|
|
110
122
|
throw error;
|
|
111
123
|
},
|
|
112
124
|
);
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
125
|
+
try {
|
|
126
|
+
const outcome = await Promise.race([
|
|
127
|
+
finalized.then((result) => ({ kind: "result" as const, result })),
|
|
128
|
+
execution.detached.then(() => ({ kind: "detached" as const })),
|
|
129
|
+
]);
|
|
130
|
+
if (outcome.kind === "detached") return resultAfterDetach(cellManager.peek(invocation.cellId), invocation.input);
|
|
131
|
+
return outcome.result;
|
|
132
|
+
} finally {
|
|
133
|
+
steeringSignal?.removeEventListener("abort", onSteering);
|
|
134
|
+
}
|
|
119
135
|
}
|
|
120
136
|
|
|
121
137
|
async function executeCell(
|
|
@@ -127,6 +143,7 @@ async function executeCell(
|
|
|
127
143
|
execution: CellExecution,
|
|
128
144
|
bridgeContext: ExtensionContext,
|
|
129
145
|
bridgeAbortController: AbortController,
|
|
146
|
+
onReady: () => void,
|
|
130
147
|
): Promise<AgentToolResult<EvalToolDetails>> {
|
|
131
148
|
let handler: CellHandler | undefined;
|
|
132
149
|
try {
|
|
@@ -168,6 +185,8 @@ async function executeCell(
|
|
|
168
185
|
() => activeHandler.liveResult(),
|
|
169
186
|
(error) => execution.cancel(error),
|
|
170
187
|
);
|
|
188
|
+
// Includes a steer already queued at execute start or received while acquiring the kernel.
|
|
189
|
+
onReady();
|
|
171
190
|
if ("setContext" in options.kernelManager && typeof options.kernelManager.setContext === "function") {
|
|
172
191
|
options.kernelManager.setContext(bridgeContext);
|
|
173
192
|
}
|