@code-yeongyu/senpi-codemode 2026.7.31-2 → 2026.8.1

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.
@@ -0,0 +1,140 @@
1
+ import type { AgentToolResult } from "@code-yeongyu/senpi";
2
+ import type {
3
+ EvalDetachedCellManager,
4
+ EvalDetachedCellSnapshot,
5
+ EvalDetachedCellState,
6
+ } from "./detached-cell-manager.ts";
7
+ import { interruptionStateNote } from "./interrupt-note.ts";
8
+ import type { EvalCellResult, EvalControlInput, EvalToolDetails, EvalToolInput } from "./types.ts";
9
+
10
+ export async function executeEvalControl(
11
+ cellManager: EvalDetachedCellManager,
12
+ request: EvalControlInput,
13
+ ): Promise<AgentToolResult<EvalToolDetails>> {
14
+ const snapshot =
15
+ request.action === "stop" ? await cellManager.stop(request.cell_id) : cellManager.peek(request.cell_id);
16
+ return createDetachedControlResult(snapshot);
17
+ }
18
+
19
+ export function resultAfterDetach(
20
+ snapshot: EvalDetachedCellSnapshot,
21
+ input: EvalToolInput,
22
+ ): AgentToolResult<EvalToolDetails> {
23
+ if (snapshot.state !== "detached" && snapshot.state !== "running") return createDetachedControlResult(snapshot);
24
+ return {
25
+ content: [
26
+ {
27
+ type: "text",
28
+ text: `Eval cell ${snapshot.cellId} detached and is still running in the ${input.language} kernel. Completion will arrive as a notification. Use eval({ action: "peek", cell_id: "${snapshot.cellId}" }) or eval({ action: "stop", cell_id: "${snapshot.cellId}" }).`,
29
+ },
30
+ ],
31
+ details: snapshot.result.details,
32
+ };
33
+ }
34
+
35
+ export function createDetachedControlResult(snapshot: EvalDetachedCellSnapshot): AgentToolResult<EvalToolDetails> {
36
+ const terminationNote =
37
+ snapshot.state === "cancelled" ? interruptionStateNote(snapshot.language, snapshot.stateRetained) : undefined;
38
+ const output = textContent(snapshot.result);
39
+ const text = [
40
+ `Eval cell ${snapshot.cellId} (${snapshot.language}) is ${snapshot.state}.`,
41
+ output.length === 0 ? "(no buffered output)" : output,
42
+ ...(terminationNote === undefined ? [] : [terminationNote]),
43
+ ].join("\n");
44
+ return {
45
+ content: [{ type: "text", text }, ...snapshot.result.content.filter((part) => part.type === "image")],
46
+ details: {
47
+ ...snapshot.result.details,
48
+ ...(snapshot.state === "failed" ? { isError: true } : {}),
49
+ },
50
+ };
51
+ }
52
+
53
+ export function resultForDetachedState(
54
+ result: AgentToolResult<EvalToolDetails>,
55
+ state: EvalDetachedCellState,
56
+ durationMs: number,
57
+ ): AgentToolResult<EvalToolDetails> {
58
+ const details = result.details;
59
+ const cells = details.cells ?? [];
60
+ const nextCells =
61
+ cells.length === 0
62
+ ? []
63
+ : cells.map((cell, index) =>
64
+ index === 0
65
+ ? {
66
+ ...cell,
67
+ durationMs: terminalDuration(cell, state, durationMs),
68
+ status: cellStatus(state),
69
+ }
70
+ : { ...cell },
71
+ );
72
+ return {
73
+ content: result.content.map((part) => ({ ...part })),
74
+ details: {
75
+ ...details,
76
+ durationMs: terminalDuration(details, state, durationMs),
77
+ toolCalls: details.toolCalls.map((toolCall) => ({ ...toolCall })),
78
+ ...(details.statusEvents === undefined
79
+ ? {}
80
+ : {
81
+ statusEvents: details.statusEvents.map((event) => ({
82
+ ...event,
83
+ })),
84
+ }),
85
+ ...(nextCells.length === 0
86
+ ? {}
87
+ : {
88
+ cells: nextCells.map((cell) => ({
89
+ ...cell,
90
+ ...(cell.statusEvents === undefined
91
+ ? {}
92
+ : {
93
+ statusEvents: cell.statusEvents.map((event) => ({
94
+ ...event,
95
+ })),
96
+ }),
97
+ })),
98
+ }),
99
+ ...(details.jsonOutputs === undefined ? {} : { jsonOutputs: structuredClone(details.jsonOutputs) }),
100
+ },
101
+ };
102
+ }
103
+
104
+ export function detachedKernelBusyError(snapshot: EvalDetachedCellSnapshot): Error {
105
+ const tail = snapshot.outputTail.length === 0 ? "(no output yet)" : snapshot.outputTail;
106
+ return new Error(
107
+ `The ${snapshot.language} eval kernel is busy running detached cell ${snapshot.cellId}. Do not re-run it; use eval({ action: "peek", cell_id: "${snapshot.cellId}" }). Current output tail:\n${tail}`,
108
+ );
109
+ }
110
+
111
+ function textContent(result: AgentToolResult<EvalToolDetails>): string {
112
+ return result.content
113
+ .filter((part) => part.type === "text")
114
+ .map((part) => part.text)
115
+ .join("\n");
116
+ }
117
+
118
+ function terminalDuration(
119
+ value: { readonly durationMs?: number },
120
+ state: EvalDetachedCellState,
121
+ liveDurationMs: number,
122
+ ): number {
123
+ if (state === "completed" || state === "failed" || state === "cancelled") return value.durationMs ?? liveDurationMs;
124
+ return liveDurationMs;
125
+ }
126
+
127
+ function cellStatus(state: EvalDetachedCellState): EvalCellResult["status"] {
128
+ switch (state) {
129
+ case "running":
130
+ return "running";
131
+ case "detached":
132
+ return "detached";
133
+ case "completed":
134
+ return "complete";
135
+ case "failed":
136
+ return "error";
137
+ case "cancelled":
138
+ return "cancelled";
139
+ }
140
+ }
@@ -0,0 +1,53 @@
1
+ import type { EvalDetachedCellNotifier, EvalDetachedCellSnapshot } from "./detached-cell-manager.ts";
2
+ import { buildDetachedCellNotification } from "./detached-cell-notification.ts";
3
+
4
+ export interface PendingDetachedNotification {
5
+ readonly snapshot: () => EvalDetachedCellSnapshot;
6
+ readonly spillPath: string | undefined;
7
+ }
8
+
9
+ export class DetachedNotificationQueue {
10
+ readonly #artifactsDir: string | undefined;
11
+ readonly #notifier: EvalDetachedCellNotifier | undefined;
12
+ #pending: PendingDetachedNotification[] = [];
13
+ #flush: Promise<void> | undefined;
14
+
15
+ constructor(notifier: EvalDetachedCellNotifier | undefined, artifactsDir: string | undefined) {
16
+ this.#notifier = notifier;
17
+ this.#artifactsDir = artifactsDir;
18
+ }
19
+
20
+ enqueue(notification: PendingDetachedNotification): void {
21
+ this.#pending.push(notification);
22
+ this.#schedule();
23
+ }
24
+
25
+ async flush(): Promise<void> {
26
+ const flush = this.#flush;
27
+ if (flush !== undefined) await flush;
28
+ }
29
+
30
+ #schedule(): void {
31
+ if (this.#flush !== undefined) return;
32
+ const flush = Promise.resolve().then(async () => {
33
+ const pending = this.#pending.splice(0);
34
+ const notifications = await Promise.all(
35
+ pending.map(
36
+ async (item) => await buildDetachedCellNotification(item.snapshot(), item.spillPath, this.#artifactsDir),
37
+ ),
38
+ );
39
+ this.#notifier?.notify(notifications);
40
+ });
41
+ this.#flush = flush;
42
+ void flush.then(
43
+ () => this.#finish(flush),
44
+ () => this.#finish(flush),
45
+ );
46
+ }
47
+
48
+ #finish(flush: Promise<void>): void {
49
+ if (this.#flush !== flush) return;
50
+ this.#flush = undefined;
51
+ if (this.#pending.length > 0) this.#schedule();
52
+ }
53
+ }
@@ -0,0 +1,45 @@
1
+ import type { ExtensionContext } from "@code-yeongyu/senpi";
2
+ import type { EvalControlInput, EvalToolInput, EvalToolRequest } from "./types.ts";
3
+
4
+ const NON_INTERACTIVE_MODES = new Set(["print", "json"]);
5
+
6
+ export function parseEvalRequest(params: unknown): EvalToolRequest {
7
+ if (!isRecord(params)) throw new TypeError("eval parameters must be an object");
8
+ if (params.action === "peek" || params.action === "stop") {
9
+ if (typeof params.cell_id !== "string" || params.cell_id.length === 0)
10
+ throw new TypeError(`eval action "${params.action}" requires cell_id`);
11
+ return { action: params.action, cell_id: params.cell_id };
12
+ }
13
+ if (params.action !== undefined && params.action !== "run")
14
+ throw new TypeError(`Unknown eval action "${String(params.action)}"`);
15
+ if (!isEvalLanguage(params.language)) throw new TypeError("eval run requires language");
16
+ if (typeof params.code !== "string") throw new TypeError("eval run requires code");
17
+ if (params.on_timeout !== undefined && params.on_timeout !== "detach" && params.on_timeout !== "error")
18
+ throw new TypeError(`Unknown eval on_timeout value "${String(params.on_timeout)}"`);
19
+ return {
20
+ language: params.language,
21
+ code: params.code,
22
+ ...(params.action === "run" ? { action: "run" as const } : {}),
23
+ ...(typeof params.title === "string" ? { title: params.title } : {}),
24
+ ...(typeof params.timeout === "number" ? { timeout: params.timeout } : {}),
25
+ ...(params.on_timeout === "detach" || params.on_timeout === "error" ? { on_timeout: params.on_timeout } : {}),
26
+ ...(typeof params.reset === "boolean" ? { reset: params.reset } : {}),
27
+ };
28
+ }
29
+
30
+ export function isEvalControlRequest(request: EvalToolRequest): request is EvalControlInput {
31
+ return request.action === "peek" || request.action === "stop";
32
+ }
33
+
34
+ export function evalTimeoutBehavior(input: EvalToolInput, ctx: ExtensionContext): "detach" | "error" {
35
+ if (input.on_timeout !== undefined) return input.on_timeout;
36
+ return NON_INTERACTIVE_MODES.has(ctx.mode) ? "error" : "detach";
37
+ }
38
+
39
+ function isRecord(value: unknown): value is Record<string, unknown> {
40
+ return typeof value === "object" && value !== null;
41
+ }
42
+
43
+ function isEvalLanguage(value: unknown): value is EvalToolInput["language"] {
44
+ return value === "py" || value === "js" || value === "rb" || value === "jl";
45
+ }
@@ -0,0 +1,45 @@
1
+ import type { AgentToolResult, AgentToolUpdateCallback, ExtensionContext, ToolDefinition } from "@code-yeongyu/senpi";
2
+ import type { EvalSchemaToolInfo } from "../bridges/schema-bridge.ts";
3
+ import type { CompletionRequest, CompletionResult } from "../completion/handler.ts";
4
+ import type { ResolvedCodemodeSettings } from "../config/settings.ts";
5
+ import type { EvalExecutionTracker } from "../extension/session-manager.ts";
6
+ import type { EvalTimeoutFactory } from "./cell-execution.ts";
7
+ import type { EvalDetachedCellManager } from "./detached-cell-manager.ts";
8
+ import type { EvalImageResizer } from "./image.ts";
9
+ import type {
10
+ EnabledEvalLanguages,
11
+ EvalInputSchema,
12
+ EvalKernelManager,
13
+ EvalToolDetails,
14
+ EvalToolInput,
15
+ ExecuteTool,
16
+ } from "./types.ts";
17
+
18
+ export interface CreateEvalToolOptions {
19
+ readonly enabledLanguages: EnabledEvalLanguages;
20
+ readonly kernelManager: EvalKernelManager;
21
+ readonly cellTimeoutSeconds: number;
22
+ readonly executeTool: ExecuteTool;
23
+ readonly listTools?: () => readonly EvalSchemaToolInfo[];
24
+ readonly complete?: (request: CompletionRequest, ctx: ExtensionContext) => Promise<CompletionResult>;
25
+ readonly settings?: ResolvedCodemodeSettings;
26
+ readonly artifactsDir?: string;
27
+ readonly imageResizer?: EvalImageResizer;
28
+ readonly executionTracker?: EvalExecutionTracker;
29
+ readonly cellManager?: EvalDetachedCellManager;
30
+ readonly timeoutFactory?: EvalTimeoutFactory;
31
+ readonly proxyExecutor?: (params: EvalToolInput, signal?: AbortSignal) => Promise<AgentToolResult<EvalToolDetails>>;
32
+ readonly renderers?: Pick<ToolDefinition<EvalInputSchema, EvalToolDetails>, "renderCall" | "renderResult">;
33
+ readonly spawns?: boolean;
34
+ readonly spawnDefaultAgent?: string;
35
+ readonly modelId?: string;
36
+ readonly hostLine?: string;
37
+ }
38
+
39
+ export interface EvalCellInvocation {
40
+ readonly cellId: string;
41
+ readonly input: EvalToolInput;
42
+ readonly signal: AbortSignal;
43
+ readonly onUpdate: AgentToolUpdateCallback<EvalToolDetails> | undefined;
44
+ readonly ctx: ExtensionContext;
45
+ }