@code-yeongyu/senpi-codemode 2026.8.7 → 2026.8.9-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 +33 -0
- package/package.json +4 -4
- package/src/extension/wake-source-state.ts +29 -0
- package/src/index.ts +10 -0
- package/src/tool/detached-cell-manager.ts +27 -1
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,39 @@
|
|
|
12
12
|
|
|
13
13
|
### Removed
|
|
14
14
|
|
|
15
|
+
## [2026.8.9-2] - 2026-08-09
|
|
16
|
+
|
|
17
|
+
### Breaking Changes
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Detached eval cells now emit the shared `wake_source_state` event under source `senpi-codemode` when they detach, complete, stop, or are disposed. The optional host event passthrough remains guarded, synchronous cells emit no lifecycle transition, and per-cell snapshot metadata is preserved.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
### Removed
|
|
28
|
+
|
|
29
|
+
## [2026.8.9] - 2026-08-09
|
|
30
|
+
|
|
31
|
+
### Breaking Changes
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- Detached eval cells now publish their liveness as a `resumption_channel_state` event (source `eval-detached`) on the
|
|
36
|
+
host event bus: a full per-source snapshot with `activeCount` and per-cell `id`/`description`/`startedAtMs` entries is
|
|
37
|
+
emitted whenever a cell detaches, settles, is stopped, or is disposed, and once on `session_start`. The goal builtin
|
|
38
|
+
consumes this to hold its hidden continuation while detached cells are still computing instead of nagging immediately
|
|
39
|
+
at turn end. Hosts without an event bus are unaffected (emission is a no-op), and the footer/status rendering is
|
|
40
|
+
unchanged.
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
### Removed
|
|
47
|
+
|
|
15
48
|
## [2026.8.7] - 2026-08-07
|
|
16
49
|
|
|
17
50
|
### 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.9-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.8.
|
|
33
|
+
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.8.9-2",
|
|
34
34
|
"typebox": "1.3.8"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@code-yeongyu/senpi": "2026.8.
|
|
37
|
+
"@code-yeongyu/senpi": "2026.8.9-2"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@code-yeongyu/senpi": "2026.8.
|
|
40
|
+
"@code-yeongyu/senpi": "2026.8.9-2"
|
|
41
41
|
},
|
|
42
42
|
"keywords": [
|
|
43
43
|
"senpi",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wake-source liveness contract shared with the goal builtin.
|
|
3
|
+
*
|
|
4
|
+
* The goal builtin delays its hidden continuation while any live wake source
|
|
5
|
+
* can still wake the session. Each emitter publishes a full per-source
|
|
6
|
+
* snapshot on every liveness transition and once on `session_start`.
|
|
7
|
+
*
|
|
8
|
+
* The event name is duplicated here on purpose: senpi-codemode is a separate
|
|
9
|
+
* package and must not import across the packages/coding-agent boundary, so
|
|
10
|
+
* the sentinel test pins this literal to the exact cross-package contract.
|
|
11
|
+
*/
|
|
12
|
+
export const WAKE_SOURCE_STATE_EVENT = "wake_source_state";
|
|
13
|
+
|
|
14
|
+
/** Source key identifying detached eval cells in the per-source snapshot. */
|
|
15
|
+
export const SENPI_CODEMODE_WAKE_SOURCE = "senpi-codemode";
|
|
16
|
+
|
|
17
|
+
/** One detached cell as broadcast on the wake-source state event. */
|
|
18
|
+
export interface WakeSourceStateItem {
|
|
19
|
+
readonly id: string;
|
|
20
|
+
readonly description: string;
|
|
21
|
+
/** Epoch milliseconds when the channel registered; lets consumers render their own elapsed labels. */
|
|
22
|
+
readonly startedAtMs: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface WakeSourceState {
|
|
26
|
+
readonly source: string;
|
|
27
|
+
readonly activeCount: number;
|
|
28
|
+
readonly items?: readonly WakeSourceStateItem[];
|
|
29
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from "./extension/runtime-factory.ts";
|
|
16
16
|
import type { CodemodeSessionManager, CreateCodemodeSessionManagerOptions } from "./extension/session-manager.ts";
|
|
17
17
|
import { SessionManagerProxy } from "./extension/session-manager-proxy.ts";
|
|
18
|
+
import { WAKE_SOURCE_STATE_EVENT, type WakeSourceState } from "./extension/wake-source-state.ts";
|
|
18
19
|
import { EvalDetachedCellManager, type EvalDetachedCellStatusEntry } from "./tool/detached-cell-manager.ts";
|
|
19
20
|
import { createEvalTool } from "./tool/eval-tool.ts";
|
|
20
21
|
import { renderEvalCall, renderEvalResult } from "./tool/render.ts";
|
|
@@ -38,6 +39,8 @@ export interface CodemodeExtensionAPI {
|
|
|
38
39
|
getActiveTools(): string[];
|
|
39
40
|
getAllTools(): readonly EvalSchemaToolInfo[];
|
|
40
41
|
sendUserMessage(content: string, options?: { deliverAs?: "steer" | "followUp" }): void;
|
|
42
|
+
/** Optional host event bus; a host without one turns wake-source emission into a harmless no-op. */
|
|
43
|
+
events?: { emit(name: string, data: unknown): void };
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
export interface SenpiCodemodeOptions {
|
|
@@ -79,6 +82,9 @@ export default function senpiCodemode(pi: CodemodeExtensionAPI, options: SenpiCo
|
|
|
79
82
|
const showDetachedCells = (entries: readonly EvalDetachedCellStatusEntry[]): void => {
|
|
80
83
|
statusTicker.sync(entries);
|
|
81
84
|
};
|
|
85
|
+
const emitWakeSourceState = (state: WakeSourceState): void => {
|
|
86
|
+
pi.events?.emit(WAKE_SOURCE_STATE_EVENT, state);
|
|
87
|
+
};
|
|
82
88
|
const registerEvalForRuntime = (
|
|
83
89
|
runtime: SessionRuntime,
|
|
84
90
|
modelId: string | undefined,
|
|
@@ -126,6 +132,7 @@ export default function senpiCodemode(pi: CodemodeExtensionAPI, options: SenpiCo
|
|
|
126
132
|
cellManager: new EvalDetachedCellManager({
|
|
127
133
|
notifier,
|
|
128
134
|
onStatusChange: showDetachedCells,
|
|
135
|
+
onWakeSourceState: emitWakeSourceState,
|
|
129
136
|
...(options.now === undefined ? {} : { now: options.now }),
|
|
130
137
|
}),
|
|
131
138
|
executionTracker: manager,
|
|
@@ -156,9 +163,12 @@ export default function senpiCodemode(pi: CodemodeExtensionAPI, options: SenpiCo
|
|
|
156
163
|
artifactsDir: runtime.artifactsDir,
|
|
157
164
|
notifier,
|
|
158
165
|
onStatusChange: showDetachedCells,
|
|
166
|
+
onWakeSourceState: emitWakeSourceState,
|
|
159
167
|
...(options.now === undefined ? {} : { now: options.now }),
|
|
160
168
|
});
|
|
161
169
|
activeCells = cellManager;
|
|
170
|
+
// The goal builtin clears its per-session counts at session_start; re-publish our snapshot.
|
|
171
|
+
cellManager.publishWakeSourceState();
|
|
162
172
|
activeRuntime = runtime;
|
|
163
173
|
activeModelId = ctx.model?.id;
|
|
164
174
|
registerEvalForRuntime(runtime, activeModelId, cellManager);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentToolResult } from "@code-yeongyu/senpi";
|
|
2
|
+
import { SENPI_CODEMODE_WAKE_SOURCE, type WakeSourceState } from "../extension/wake-source-state.ts";
|
|
2
3
|
import { detachedNotificationSpillPath } from "./detached-cell-notification.ts";
|
|
3
4
|
import { currentDetachedResult, detachedErrorResult, snapshotDetachedCell } from "./detached-cell-snapshot.ts";
|
|
4
5
|
import {
|
|
@@ -58,12 +59,15 @@ export interface EvalDetachedCellManagerOptions {
|
|
|
58
59
|
readonly artifactsDir?: string;
|
|
59
60
|
readonly notifier?: EvalDetachedCellNotifier;
|
|
60
61
|
readonly onStatusChange?: (entries: readonly EvalDetachedCellStatusEntry[]) => void;
|
|
62
|
+
/** Receives a full per-source liveness snapshot on every detached-cell transition; used by the goal builtin. */
|
|
63
|
+
readonly onWakeSourceState?: (state: WakeSourceState) => void;
|
|
61
64
|
readonly now?: () => number;
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
export class EvalDetachedCellManager {
|
|
65
68
|
readonly #artifactsDir: string | undefined;
|
|
66
69
|
readonly #onStatusChange: ((entries: readonly EvalDetachedCellStatusEntry[]) => void) | undefined;
|
|
70
|
+
readonly #onWakeSourceState: ((state: WakeSourceState) => void) | undefined;
|
|
67
71
|
readonly #cells = new Map<string, ManagedCell>();
|
|
68
72
|
readonly #detachedByLanguage = new Map<EvalLanguage, ManagedCell>();
|
|
69
73
|
readonly #notificationQueue: DetachedNotificationQueue;
|
|
@@ -72,6 +76,7 @@ export class EvalDetachedCellManager {
|
|
|
72
76
|
constructor(options: EvalDetachedCellManagerOptions = {}) {
|
|
73
77
|
this.#artifactsDir = options.artifactsDir;
|
|
74
78
|
this.#onStatusChange = options.onStatusChange;
|
|
79
|
+
this.#onWakeSourceState = options.onWakeSourceState;
|
|
75
80
|
this.#notificationQueue = new DetachedNotificationQueue(options.notifier, options.artifactsDir);
|
|
76
81
|
this.#now = options.now ?? Date.now;
|
|
77
82
|
}
|
|
@@ -155,6 +160,7 @@ export class EvalDetachedCellManager {
|
|
|
155
160
|
await Promise.allSettled(
|
|
156
161
|
detached.map(async (cell) => await this.stop(cell.cellId, "Session ended; detached eval cell cancelled")),
|
|
157
162
|
);
|
|
163
|
+
if (detached.length === 0) this.#emitWakeSourceState([]);
|
|
158
164
|
await this.#notificationQueue.flush();
|
|
159
165
|
}
|
|
160
166
|
|
|
@@ -162,6 +168,11 @@ export class EvalDetachedCellManager {
|
|
|
162
168
|
await this.#notificationQueue.flush();
|
|
163
169
|
}
|
|
164
170
|
|
|
171
|
+
/** Re-publish the current snapshot; consumers reset their per-source counts at session_start. */
|
|
172
|
+
publishWakeSourceState(): void {
|
|
173
|
+
this.#emitWakeSourceState([...this.#detachedByLanguage.values()]);
|
|
174
|
+
}
|
|
175
|
+
|
|
165
176
|
#settle(
|
|
166
177
|
cell: ManagedCell,
|
|
167
178
|
state: "completed" | "failed" | "cancelled",
|
|
@@ -188,14 +199,29 @@ export class EvalDetachedCellManager {
|
|
|
188
199
|
}
|
|
189
200
|
|
|
190
201
|
#emitStatus(): void {
|
|
202
|
+
const liveCells = [...this.#detachedByLanguage.values()];
|
|
191
203
|
this.#onStatusChange?.(
|
|
192
|
-
|
|
204
|
+
liveCells.map((cell) => ({
|
|
193
205
|
cellId: cell.cellId,
|
|
194
206
|
language: cell.input.language,
|
|
195
207
|
startedAtMs: cell.startedAtMs,
|
|
196
208
|
...(cell.input.summary === undefined ? {} : { summary: cell.input.summary }),
|
|
197
209
|
})),
|
|
198
210
|
);
|
|
211
|
+
this.#emitWakeSourceState(liveCells);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
#emitWakeSourceState(liveCells: readonly ManagedCell[]): void {
|
|
215
|
+
this.#onWakeSourceState?.({
|
|
216
|
+
source: SENPI_CODEMODE_WAKE_SOURCE,
|
|
217
|
+
activeCount: liveCells.length,
|
|
218
|
+
items: liveCells.map((cell) => ({
|
|
219
|
+
id: cell.cellId,
|
|
220
|
+
description:
|
|
221
|
+
cell.input.summary === undefined || cell.input.summary.length === 0 ? cell.cellId : cell.input.summary,
|
|
222
|
+
startedAtMs: cell.startedAtMs,
|
|
223
|
+
})),
|
|
224
|
+
});
|
|
199
225
|
}
|
|
200
226
|
|
|
201
227
|
#snapshot(cell: ManagedCell): EvalDetachedCellSnapshot {
|