@ian-pascoe/pi-codemode 0.1.0 → 0.3.0
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/README.md +96 -24
- package/package.json +6 -6
- package/src/codemode-console-output.ts +11 -0
- package/src/codemode-observer-ui.ts +19 -38
- package/src/codemode-session-coordinator.ts +313 -158
- package/src/codemode-tool-catalog.ts +4 -6
- package/src/codemode-tool-contract.ts +108 -14
- package/src/codemode-tool-rendering.ts +138 -39
- package/src/codemode-worker-protocol.ts +141 -33
- package/src/codemode-worker.ts +283 -18
- package/src/pi-codemode-extension.ts +31 -28
- package/src/pi-tool-bridge.ts +5 -38
|
@@ -95,10 +95,6 @@ function schemaRecord(value: CodeModeJsonValue | undefined): CodeModeJsonObject
|
|
|
95
95
|
return value !== undefined && isCodeModeJsonObject(value) ? value : undefined;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
function jsonLiteral(value: CodeModeJsonValue): string | undefined {
|
|
99
|
-
return JSON.stringify(value);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
98
|
function quotedName(name: string): string {
|
|
103
99
|
return JSON.stringify(name);
|
|
104
100
|
}
|
|
@@ -139,9 +135,11 @@ function schemaType(
|
|
|
139
135
|
|
|
140
136
|
const constant = record.const;
|
|
141
137
|
if (Object.hasOwn(record, "const") && constant !== undefined) {
|
|
142
|
-
return
|
|
138
|
+
return JSON.stringify(constant) ?? "unknown";
|
|
143
139
|
}
|
|
144
|
-
const enumValues = Array.isArray(record.enum)
|
|
140
|
+
const enumValues = Array.isArray(record.enum)
|
|
141
|
+
? record.enum.map((value) => JSON.stringify(value))
|
|
142
|
+
: undefined;
|
|
145
143
|
if (
|
|
146
144
|
enumValues !== undefined &&
|
|
147
145
|
enumValues.length > 0 &&
|
|
@@ -8,11 +8,17 @@ import type {
|
|
|
8
8
|
import type { Usage } from "@earendil-works/pi-ai";
|
|
9
9
|
import { type Static, Type } from "typebox";
|
|
10
10
|
import { Value } from "typebox/value";
|
|
11
|
+
import {
|
|
12
|
+
CODEMODE_CONSOLE_METHODS,
|
|
13
|
+
type CodeModeConsoleEntry,
|
|
14
|
+
type CodeModeConsoleMethod,
|
|
15
|
+
} from "./codemode-console-output.js";
|
|
11
16
|
|
|
12
17
|
const CODEMODE_TOOL_NAMES = {
|
|
13
18
|
execute: "codemode_execute",
|
|
14
19
|
result: "codemode_result",
|
|
15
20
|
cancel: "codemode_cancel",
|
|
21
|
+
sessions: "codemode_sessions",
|
|
16
22
|
} as const;
|
|
17
23
|
const RESERVED_CODEMODE_TOOL_NAMES = new Set<string>(Object.values(CODEMODE_TOOL_NAMES));
|
|
18
24
|
|
|
@@ -26,6 +32,7 @@ export const CODEMODE_ERROR_CODES = [
|
|
|
26
32
|
"unknown",
|
|
27
33
|
"busy",
|
|
28
34
|
"capacity",
|
|
35
|
+
"eviction",
|
|
29
36
|
"script",
|
|
30
37
|
"serialization",
|
|
31
38
|
"timeout",
|
|
@@ -116,12 +123,17 @@ export const CodeModeCancelParametersSchema = Type.Object(
|
|
|
116
123
|
{ additionalProperties: false },
|
|
117
124
|
);
|
|
118
125
|
|
|
126
|
+
/** Strict empty arguments accepted by the read-only `codemode_sessions` tool. */
|
|
127
|
+
export const CodeModeSessionsParametersSchema = Type.Object({}, { additionalProperties: false });
|
|
128
|
+
|
|
119
129
|
/** Parsed arguments for `codemode_execute`. */
|
|
120
130
|
export type CodeModeExecuteParameters = Static<typeof CodeModeExecuteParametersSchema>;
|
|
121
131
|
/** Parsed arguments for `codemode_result`. */
|
|
122
132
|
export type CodeModeResultParameters = Static<typeof CodeModeResultParametersSchema>;
|
|
123
133
|
/** Parsed arguments for `codemode_cancel`. */
|
|
124
134
|
export type CodeModeCancelParameters = Static<typeof CodeModeCancelParametersSchema>;
|
|
135
|
+
/** Parsed arguments for the read-only `codemode_sessions` tool. */
|
|
136
|
+
export type CodeModeSessionsParameters = Static<typeof CodeModeSessionsParametersSchema>;
|
|
125
137
|
|
|
126
138
|
/** A JSON object accepted in a successful CodeMode result. */
|
|
127
139
|
export type CodeModeJsonObject = { readonly [key: string]: CodeModeJsonValue };
|
|
@@ -163,6 +175,15 @@ const CodeModeErrorCodeSchema = Type.Unsafe<CodeModeErrorCode>({
|
|
|
163
175
|
type: "string",
|
|
164
176
|
enum: [...CODEMODE_ERROR_CODES],
|
|
165
177
|
});
|
|
178
|
+
const CodeModeConsoleMethodSchema = Type.Unsafe<CodeModeConsoleMethod>({
|
|
179
|
+
type: "string",
|
|
180
|
+
enum: [...CODEMODE_CONSOLE_METHODS],
|
|
181
|
+
});
|
|
182
|
+
const CodeModeConsoleEntrySchema = Type.Object(
|
|
183
|
+
{ method: CodeModeConsoleMethodSchema, text: Type.String() },
|
|
184
|
+
{ additionalProperties: false },
|
|
185
|
+
);
|
|
186
|
+
const CodeModeConsoleOutputSchema = Type.Array(CodeModeConsoleEntrySchema, { minItems: 1 });
|
|
166
187
|
|
|
167
188
|
/** Stable error retained by a failed CodeMode result. */
|
|
168
189
|
export const CodeModeErrorSchema = Type.Object(
|
|
@@ -178,9 +199,35 @@ const CodeModeSuccessSchema = Type.Object(
|
|
|
178
199
|
result: Type.Literal("success"),
|
|
179
200
|
sessionId: SessionIdSchema,
|
|
180
201
|
data: Type.Optional(CodeModeJsonValueSchema),
|
|
202
|
+
reclaimedSessionId: Type.Optional(SessionIdSchema),
|
|
203
|
+
console: Type.Optional(CodeModeConsoleOutputSchema),
|
|
204
|
+
},
|
|
205
|
+
{ additionalProperties: false },
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
/** One live CodeMode Session with its Unix-epoch last-activity time. */
|
|
209
|
+
export const CodeModeSessionListEntrySchema = Type.Object(
|
|
210
|
+
{
|
|
211
|
+
sessionId: SessionIdSchema,
|
|
212
|
+
state: Type.Union([Type.Literal("idle"), Type.Literal("running")]),
|
|
213
|
+
cellCount: NonNegativeSafeIntegerSchema,
|
|
214
|
+
lastActivityAtMs: NonNegativeSafeIntegerSchema,
|
|
181
215
|
},
|
|
182
216
|
{ additionalProperties: false },
|
|
183
217
|
);
|
|
218
|
+
|
|
219
|
+
/** Idle-LRU-first, then running-LRU aggregate returned by `codemode_sessions`. */
|
|
220
|
+
export const CodeModeSessionsResultSchema = Type.Object(
|
|
221
|
+
{
|
|
222
|
+
result: Type.Literal("success"),
|
|
223
|
+
sessions: Type.Array(CodeModeSessionListEntrySchema),
|
|
224
|
+
},
|
|
225
|
+
{ additionalProperties: false },
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
/** Schema-derived live Session list ordered by reclamation priority. */
|
|
229
|
+
export type CodeModeSessionsResult = Static<typeof CodeModeSessionsResultSchema>;
|
|
230
|
+
|
|
184
231
|
const CodeModePendingSchema = Type.Object(
|
|
185
232
|
{
|
|
186
233
|
result: Type.Literal("pending"),
|
|
@@ -193,18 +240,19 @@ const CodeModeFailedSchema = Type.Object(
|
|
|
193
240
|
result: Type.Literal("failed"),
|
|
194
241
|
sessionId: SessionIdSchema,
|
|
195
242
|
error: CodeModeErrorSchema,
|
|
243
|
+
console: Type.Optional(CodeModeConsoleOutputSchema),
|
|
196
244
|
},
|
|
197
245
|
{ additionalProperties: false },
|
|
198
246
|
);
|
|
199
247
|
|
|
200
|
-
/** Schema-derived result union shared by
|
|
248
|
+
/** Schema-derived result union shared by the execute, result, and cancel tools. */
|
|
201
249
|
export const CodeModeResultSchema = Type.Union([
|
|
202
250
|
CodeModeSuccessSchema,
|
|
203
251
|
CodeModePendingSchema,
|
|
204
252
|
CodeModeFailedSchema,
|
|
205
253
|
]);
|
|
206
254
|
|
|
207
|
-
/** Schema-derived result returned by
|
|
255
|
+
/** Schema-derived result returned by one session-scoped CodeMode operation. */
|
|
208
256
|
export type CodeModeResult = Static<typeof CodeModeResultSchema>;
|
|
209
257
|
|
|
210
258
|
const CodeModeSuccessDetailsSchema = Type.Object(
|
|
@@ -212,6 +260,8 @@ const CodeModeSuccessDetailsSchema = Type.Object(
|
|
|
212
260
|
result: Type.Literal("success"),
|
|
213
261
|
sessionId: SessionIdSchema,
|
|
214
262
|
data: Type.Optional(CodeModeJsonValueSchema),
|
|
263
|
+
reclaimedSessionId: Type.Optional(SessionIdSchema),
|
|
264
|
+
console: Type.Optional(CodeModeConsoleOutputSchema),
|
|
215
265
|
presentation: Type.Optional(CodeModePresentationSnapshotSchema),
|
|
216
266
|
},
|
|
217
267
|
{ additionalProperties: false },
|
|
@@ -229,6 +279,7 @@ const CodeModeFailedDetailsSchema = Type.Object(
|
|
|
229
279
|
result: Type.Literal("failed"),
|
|
230
280
|
sessionId: SessionIdSchema,
|
|
231
281
|
error: CodeModeErrorSchema,
|
|
282
|
+
console: Type.Optional(CodeModeConsoleOutputSchema),
|
|
232
283
|
presentation: Type.Optional(CodeModePresentationSnapshotSchema),
|
|
233
284
|
},
|
|
234
285
|
{ additionalProperties: false },
|
|
@@ -240,14 +291,22 @@ export const CodeModeResultDetailsSchema = Type.Union([
|
|
|
240
291
|
CodeModePendingDetailsSchema,
|
|
241
292
|
CodeModeFailedDetailsSchema,
|
|
242
293
|
]);
|
|
243
|
-
/** Schema-derived details retained by
|
|
294
|
+
/** Schema-derived details retained by one session-scoped CodeMode operation. */
|
|
244
295
|
export type CodeModeResultDetails = Static<typeof CodeModeResultDetailsSchema>;
|
|
245
296
|
|
|
246
|
-
/** A
|
|
247
|
-
export function createCodeModeSuccess(
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
297
|
+
/** A success with optional data and non-empty Cell Console output; empty Console lists are omitted. */
|
|
298
|
+
export function createCodeModeSuccess(
|
|
299
|
+
sessionId: string,
|
|
300
|
+
data?: CodeModeJsonValue,
|
|
301
|
+
consoleEntries?: readonly CodeModeConsoleEntry[],
|
|
302
|
+
): CodeModeResult {
|
|
303
|
+
const result =
|
|
304
|
+
data === undefined
|
|
305
|
+
? { result: "success" as const, sessionId }
|
|
306
|
+
: { result: "success" as const, sessionId, data };
|
|
307
|
+
return consoleEntries === undefined || consoleEntries.length === 0
|
|
308
|
+
? result
|
|
309
|
+
: { ...result, console: [...consoleEntries] };
|
|
251
310
|
}
|
|
252
311
|
|
|
253
312
|
/** A polling result for a live Cell. */
|
|
@@ -255,13 +314,17 @@ export function createCodeModePending(sessionId: string): CodeModeResult {
|
|
|
255
314
|
return { result: "pending", sessionId };
|
|
256
315
|
}
|
|
257
316
|
|
|
258
|
-
/** A stable expected failure
|
|
317
|
+
/** A stable expected failure with non-empty Cell Console output; empty Console lists are omitted. */
|
|
259
318
|
export function createCodeModeFailure(
|
|
260
319
|
sessionId: string,
|
|
261
320
|
code: CodeModeErrorCode,
|
|
262
321
|
message: string,
|
|
322
|
+
consoleEntries?: readonly CodeModeConsoleEntry[],
|
|
263
323
|
): CodeModeResult {
|
|
264
|
-
|
|
324
|
+
const result = { result: "failed" as const, sessionId, error: { code, message } };
|
|
325
|
+
return consoleEntries === undefined || consoleEntries.length === 0
|
|
326
|
+
? result
|
|
327
|
+
: { ...result, console: [...consoleEntries] };
|
|
265
328
|
}
|
|
266
329
|
|
|
267
330
|
/** A bounded JSON compatibility parse that never invokes getters or `toJSON`. */
|
|
@@ -402,7 +465,7 @@ export type CodeModeToolOperationResult = {
|
|
|
402
465
|
readonly presentation?: CodeModePresentationSnapshot;
|
|
403
466
|
};
|
|
404
467
|
|
|
405
|
-
/** Operations supplied by the session coordinator to build the
|
|
468
|
+
/** Operations supplied by the session coordinator to build the four Pi tools. */
|
|
406
469
|
export interface CodeModeToolOperations {
|
|
407
470
|
execute(
|
|
408
471
|
input: CodeModeExecuteParameters,
|
|
@@ -412,6 +475,7 @@ export interface CodeModeToolOperations {
|
|
|
412
475
|
): Promise<CodeModeToolOperationResult>;
|
|
413
476
|
result(input: CodeModeResultParameters): Promise<CodeModeToolOperationResult>;
|
|
414
477
|
cancel(input: CodeModeCancelParameters): Promise<CodeModeToolOperationResult>;
|
|
478
|
+
sessions(): Promise<CodeModeSessionsResult>;
|
|
415
479
|
}
|
|
416
480
|
|
|
417
481
|
function structuredCodeModeResult(
|
|
@@ -438,9 +502,19 @@ type CodeModeToolDefinitions = readonly [
|
|
|
438
502
|
ToolDefinition<typeof CodeModeExecuteParametersSchema, CodeModeResultDetails>,
|
|
439
503
|
ToolDefinition<typeof CodeModeResultParametersSchema, CodeModeResultDetails>,
|
|
440
504
|
ToolDefinition<typeof CodeModeCancelParametersSchema, CodeModeResultDetails>,
|
|
505
|
+
ToolDefinition<typeof CodeModeSessionsParametersSchema, CodeModeSessionsResult>,
|
|
441
506
|
];
|
|
442
507
|
|
|
443
|
-
|
|
508
|
+
function structuredCodeModeSessionsResult(
|
|
509
|
+
result: CodeModeSessionsResult,
|
|
510
|
+
): AgentToolResult<CodeModeSessionsResult> {
|
|
511
|
+
return {
|
|
512
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
513
|
+
details: result,
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/** Creates the four stable Pi definitions while leaving admission and session policy to the coordinator. */
|
|
444
518
|
export function createCodeModeToolDefinitions(
|
|
445
519
|
operations: CodeModeToolOperations,
|
|
446
520
|
executeDescription = "Execute TypeScript in a persistent isolated Deno CodeMode Session.",
|
|
@@ -450,6 +524,13 @@ export function createCodeModeToolDefinitions(
|
|
|
450
524
|
name: CODEMODE_TOOL_NAMES.execute,
|
|
451
525
|
label: "CodeMode Execute",
|
|
452
526
|
description: executeDescription,
|
|
527
|
+
promptSnippet:
|
|
528
|
+
"Batch, filter, and aggregate Pi tool calls in TypeScript with less latency and context usage.",
|
|
529
|
+
promptGuidelines: [
|
|
530
|
+
"Prefer codemode_execute when multiple Pi tool calls can be filtered, joined, aggregated, paginated, or used to drive later calls, or when one large result can be reduced before returning. Use direct parallel calls for a few small results needed verbatim.",
|
|
531
|
+
"Return only decision-relevant CodeMode data while preserving paths, line numbers, IDs, URLs, source names, and concise evidence needed for verification.",
|
|
532
|
+
"Reuse a CodeMode Session for related work. Prefer direct tools for simple one-off calls, full raw output, and confirmation-sensitive or destructive actions; use CodeMode mutations only when conditional sequencing is the point, and fall back to direct tools when the CodeMode boundary does not fit.",
|
|
533
|
+
],
|
|
453
534
|
parameters: CodeModeExecuteParametersSchema,
|
|
454
535
|
executionMode: "sequential",
|
|
455
536
|
async execute(_toolCallId, input, signal, onUpdate, context) {
|
|
@@ -469,12 +550,25 @@ export function createCodeModeToolDefinitions(
|
|
|
469
550
|
const cancelTool: ToolDefinition<typeof CodeModeCancelParametersSchema, CodeModeResultDetails> = {
|
|
470
551
|
name: CODEMODE_TOOL_NAMES.cancel,
|
|
471
552
|
label: "CodeMode Cancel",
|
|
472
|
-
description: "Cancel a live CodeMode
|
|
553
|
+
description: "Cancel a live CodeMode Session, free its capacity, and retain its result.",
|
|
473
554
|
parameters: CodeModeCancelParametersSchema,
|
|
474
555
|
executionMode: "sequential",
|
|
475
556
|
async execute(_toolCallId, input) {
|
|
476
557
|
return structuredCodeModeResult(await operations.cancel(input));
|
|
477
558
|
},
|
|
478
559
|
};
|
|
479
|
-
|
|
560
|
+
const sessionsTool: ToolDefinition<
|
|
561
|
+
typeof CodeModeSessionsParametersSchema,
|
|
562
|
+
CodeModeSessionsResult
|
|
563
|
+
> = {
|
|
564
|
+
name: CODEMODE_TOOL_NAMES.sessions,
|
|
565
|
+
label: "List Sessions",
|
|
566
|
+
description: "List live CodeMode Sessions without changing their recency or state.",
|
|
567
|
+
parameters: CodeModeSessionsParametersSchema,
|
|
568
|
+
executionMode: "sequential",
|
|
569
|
+
async execute() {
|
|
570
|
+
return structuredCodeModeSessionsResult(await operations.sessions());
|
|
571
|
+
},
|
|
572
|
+
};
|
|
573
|
+
return [executeTool, resultTool, cancelTool, sessionsTool];
|
|
480
574
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getMarkdownTheme,
|
|
3
|
+
highlightCode,
|
|
3
4
|
keyText,
|
|
4
5
|
truncateHead,
|
|
5
6
|
type AgentToolResult,
|
|
@@ -20,11 +21,14 @@ import {
|
|
|
20
21
|
import { Type } from "typebox";
|
|
21
22
|
import { Value } from "typebox/value";
|
|
22
23
|
import { formatCodeModePresentationData } from "./codemode-presentation-output.js";
|
|
24
|
+
import { formatCodeModeDuration } from "./codemode-session-coordinator.js";
|
|
23
25
|
import {
|
|
24
26
|
CodeModeCancelParametersSchema,
|
|
25
27
|
CodeModeExecuteParametersSchema,
|
|
26
28
|
CodeModeResultDetailsSchema,
|
|
27
29
|
CodeModeResultParametersSchema,
|
|
30
|
+
CodeModeSessionsParametersSchema,
|
|
31
|
+
CodeModeSessionsResultSchema,
|
|
28
32
|
createCodeModeToolDefinitions,
|
|
29
33
|
type CodeModeCancelParameters,
|
|
30
34
|
type CodeModeErrorCode,
|
|
@@ -34,17 +38,24 @@ import {
|
|
|
34
38
|
type CodeModePresentationSnapshot,
|
|
35
39
|
type CodeModeResultDetails,
|
|
36
40
|
type CodeModeResultParameters,
|
|
41
|
+
type CodeModeSessionsParameters,
|
|
42
|
+
type CodeModeSessionsResult,
|
|
37
43
|
type CodeModeToolOperations,
|
|
38
44
|
} from "./codemode-tool-contract.js";
|
|
39
45
|
|
|
40
|
-
/** Names of the
|
|
41
|
-
export type CodeModeRenderedToolName =
|
|
46
|
+
/** Names of the four CodeMode tools with semantic Transcript rendering. */
|
|
47
|
+
export type CodeModeRenderedToolName =
|
|
48
|
+
| "codemode_execute"
|
|
49
|
+
| "codemode_result"
|
|
50
|
+
| "codemode_cancel"
|
|
51
|
+
| "codemode_sessions";
|
|
42
52
|
|
|
43
|
-
/** Parsed arguments accepted by one of the
|
|
53
|
+
/** Parsed arguments accepted by one of the four CodeMode Transcript renderers. */
|
|
44
54
|
export type CodeModeRenderedToolParameters =
|
|
45
55
|
| CodeModeExecuteParameters
|
|
46
56
|
| CodeModeResultParameters
|
|
47
|
-
| CodeModeCancelParameters
|
|
57
|
+
| CodeModeCancelParameters
|
|
58
|
+
| CodeModeSessionsParameters;
|
|
48
59
|
|
|
49
60
|
/** Theme operations used by CodeMode Transcript renderers. */
|
|
50
61
|
export type CodeModeRenderTheme = Pick<Theme, "bold" | "fg">;
|
|
@@ -62,7 +73,7 @@ const CODEMODE_STATUS_PRESENTATION = {
|
|
|
62
73
|
cancelled: { color: "warning", label: "■ cancelled" },
|
|
63
74
|
timed_out: { color: "error", label: "! timed out" },
|
|
64
75
|
} satisfies Record<CodeModeCellState, CodeModeStatusPresentation>;
|
|
65
|
-
const
|
|
76
|
+
const CODEMODE_COLLAPSED_SCRIPT_LINES = 8;
|
|
66
77
|
const CODEMODE_PRESENTATION_MAX_BYTES = 50 * 1024;
|
|
67
78
|
const CodeModeJsonStringSchema = Type.String();
|
|
68
79
|
|
|
@@ -82,14 +93,6 @@ function boundedCodeModePreview(text: string, width = 72): string {
|
|
|
82
93
|
return `${sliceByColumn(singleLine, 0, width - 1, true).trimEnd()}…`;
|
|
83
94
|
}
|
|
84
95
|
|
|
85
|
-
function firstMeaningfulScriptLine(script: string): string | undefined {
|
|
86
|
-
const line = sanitizeCodeModeText(script)
|
|
87
|
-
.split("\n")
|
|
88
|
-
.map((candidate) => candidate.trim())
|
|
89
|
-
.find(Boolean);
|
|
90
|
-
return line === undefined ? undefined : boundedCodeModePreview(line);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
96
|
function parseCodeModeRenderedToolParameters(
|
|
94
97
|
toolName: CodeModeRenderedToolName,
|
|
95
98
|
parameters: CodeModeJsonValue,
|
|
@@ -100,7 +103,10 @@ function parseCodeModeRenderedToolParameters(
|
|
|
100
103
|
if (toolName === "codemode_result") {
|
|
101
104
|
return Value.Check(CodeModeResultParametersSchema, parameters) ? parameters : undefined;
|
|
102
105
|
}
|
|
103
|
-
|
|
106
|
+
if (toolName === "codemode_cancel") {
|
|
107
|
+
return Value.Check(CodeModeCancelParametersSchema, parameters) ? parameters : undefined;
|
|
108
|
+
}
|
|
109
|
+
return Value.Check(CodeModeSessionsParametersSchema, parameters) ? parameters : undefined;
|
|
104
110
|
}
|
|
105
111
|
|
|
106
112
|
function shortCodeModeSessionId(sessionId: string): string {
|
|
@@ -111,13 +117,6 @@ function shortCodeModeSessionId(sessionId: string): string {
|
|
|
111
117
|
/** Resolves one Session ID to the shortest unambiguous CodeMode Transcript label. */
|
|
112
118
|
export type CodeModeSessionPrefixFormatter = (sessionId: string) => string;
|
|
113
119
|
|
|
114
|
-
function formatCodeModeDuration(elapsedMs: number): string {
|
|
115
|
-
if (elapsedMs < 1_000) return `${elapsedMs}ms`;
|
|
116
|
-
if (elapsedMs < 60_000) return `${(elapsedMs / 1_000).toFixed(1)}s`;
|
|
117
|
-
const seconds = Math.floor(elapsedMs / 1_000);
|
|
118
|
-
return `${Math.floor(seconds / 60)}m ${String(seconds % 60).padStart(2, "0")}s`;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
120
|
function pluralizedCodeModeCount(count: number, noun: string): string {
|
|
122
121
|
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
123
122
|
}
|
|
@@ -139,6 +138,9 @@ function codeModeSessionLifecycle(
|
|
|
139
138
|
toolName: CodeModeRenderedToolName,
|
|
140
139
|
details: CodeModeResultDetails,
|
|
141
140
|
): "Session reusable" | "Session closed" | "No reusable Session" {
|
|
141
|
+
if (details.result === "failed" && details.error.code === "eviction") {
|
|
142
|
+
return "Session closed";
|
|
143
|
+
}
|
|
142
144
|
if (details.presentation !== undefined) {
|
|
143
145
|
return details.presentation.session_state === "live" ? "Session reusable" : "Session closed";
|
|
144
146
|
}
|
|
@@ -192,6 +194,16 @@ function appendCodeModeBlock(container: Container, language: "ts" | "json", cont
|
|
|
192
194
|
);
|
|
193
195
|
}
|
|
194
196
|
|
|
197
|
+
function highlightedCodeModeSource(source: string): string {
|
|
198
|
+
return highlightCode(source, "typescript")
|
|
199
|
+
.map((line) => ` ${line}`)
|
|
200
|
+
.join("\n");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function appendHighlightedCodeModeSource(container: Container, source: string): void {
|
|
204
|
+
container.addChild(new Text(highlightedCodeModeSource(source), 0, 0));
|
|
205
|
+
}
|
|
206
|
+
|
|
195
207
|
function boundedCodeModeText(text: string, maxLines: number): string {
|
|
196
208
|
const safe = sanitizeCodeModeText(text);
|
|
197
209
|
const truncated = truncateHead(safe, {
|
|
@@ -234,8 +246,12 @@ function renderCodeModeSummary(
|
|
|
234
246
|
formatSessionPrefix: CodeModeSessionPrefixFormatter,
|
|
235
247
|
): string {
|
|
236
248
|
const presentation = details.presentation;
|
|
249
|
+
const consoleEntries = details.result === "pending" ? undefined : details.console;
|
|
237
250
|
const state = codeModeCellState(toolName, details);
|
|
238
|
-
const status =
|
|
251
|
+
const status =
|
|
252
|
+
details.result === "failed" && details.error.code === "eviction"
|
|
253
|
+
? { color: "warning" as const, label: "■ reclaimed" }
|
|
254
|
+
: CODEMODE_STATUS_PRESENTATION[state];
|
|
239
255
|
const activeToolNames = presentation?.active_tool_names.slice(0, 3) ?? [];
|
|
240
256
|
const omittedActiveToolCount = Math.max(
|
|
241
257
|
0,
|
|
@@ -258,6 +274,9 @@ function renderCodeModeSummary(
|
|
|
258
274
|
: undefined,
|
|
259
275
|
details.result === "failed" ? theme.fg("muted", details.error.code) : undefined,
|
|
260
276
|
details.result === "failed" ? boundedCodeModePreview(details.error.message, 64) : undefined,
|
|
277
|
+
consoleEntries === undefined
|
|
278
|
+
? undefined
|
|
279
|
+
: theme.fg("muted", pluralizedCodeModeCount(consoleEntries.length, "console call")),
|
|
261
280
|
state !== "running" && presentation !== undefined && presentation.nested_tool_count > 0
|
|
262
281
|
? theme.fg("muted", pluralizedCodeModeCount(presentation.nested_tool_count, "tool"))
|
|
263
282
|
: undefined,
|
|
@@ -268,7 +287,7 @@ function renderCodeModeSummary(
|
|
|
268
287
|
return parts.join(" ");
|
|
269
288
|
}
|
|
270
289
|
|
|
271
|
-
/** Render one CodeMode tool call
|
|
290
|
+
/** Render one CodeMode tool call with a bounded collapsed preview or complete expanded source. */
|
|
272
291
|
export function renderCodeModeToolCall(
|
|
273
292
|
toolName: CodeModeRenderedToolName,
|
|
274
293
|
parameters: CodeModeJsonValue,
|
|
@@ -281,7 +300,9 @@ export function renderCodeModeToolCall(
|
|
|
281
300
|
? "Run Cell"
|
|
282
301
|
: toolName === "codemode_result"
|
|
283
302
|
? "Poll"
|
|
284
|
-
: "
|
|
303
|
+
: toolName === "codemode_cancel"
|
|
304
|
+
? "Cancel"
|
|
305
|
+
: "List Sessions";
|
|
285
306
|
const parsedParameters = parseCodeModeRenderedToolParameters(toolName, parameters);
|
|
286
307
|
const executeParameters =
|
|
287
308
|
toolName === "codemode_execute" &&
|
|
@@ -289,11 +310,14 @@ export function renderCodeModeToolCall(
|
|
|
289
310
|
"script" in parsedParameters
|
|
290
311
|
? parsedParameters
|
|
291
312
|
: undefined;
|
|
292
|
-
const sessionId =
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
313
|
+
const sessionId =
|
|
314
|
+
parsedParameters !== undefined && "sessionId" in parsedParameters
|
|
315
|
+
? parsedParameters.sessionId
|
|
316
|
+
: undefined;
|
|
317
|
+
const source =
|
|
318
|
+
executeParameters === undefined ? undefined : sanitizeCodeModeText(executeParameters.script);
|
|
319
|
+
const oneLineSource = source !== undefined && !source.includes("\n") ? source : undefined;
|
|
320
|
+
const expansionHint = `${keyText("app.tools.expand")} to expand`;
|
|
297
321
|
const container = new Container();
|
|
298
322
|
container.addChild(
|
|
299
323
|
new Text(
|
|
@@ -301,7 +325,12 @@ export function renderCodeModeToolCall(
|
|
|
301
325
|
theme.fg("toolTitle", theme.bold("CodeMode")),
|
|
302
326
|
theme.fg("accent", operation),
|
|
303
327
|
theme.fg("muted", sessionId === undefined ? "new" : formatSessionPrefix(sessionId)),
|
|
304
|
-
|
|
328
|
+
!expanded && oneLineSource !== undefined
|
|
329
|
+
? highlightCode(oneLineSource, "typescript")[0]
|
|
330
|
+
: undefined,
|
|
331
|
+
!expanded && oneLineSource !== undefined
|
|
332
|
+
? theme.fg("dim", `· ${expansionHint}`)
|
|
333
|
+
: undefined,
|
|
305
334
|
]
|
|
306
335
|
.filter((part): part is string => part !== undefined)
|
|
307
336
|
.join(" "),
|
|
@@ -309,7 +338,17 @@ export function renderCodeModeToolCall(
|
|
|
309
338
|
0,
|
|
310
339
|
),
|
|
311
340
|
);
|
|
312
|
-
if (!expanded)
|
|
341
|
+
if (!expanded) {
|
|
342
|
+
if (source === undefined || oneLineSource !== undefined) return container;
|
|
343
|
+
const sourceLines = source.split("\n");
|
|
344
|
+
const visibleSource = sourceLines.slice(0, CODEMODE_COLLAPSED_SCRIPT_LINES).join("\n");
|
|
345
|
+
appendHighlightedCodeModeSource(container, visibleSource);
|
|
346
|
+
const omittedLines = Math.max(0, sourceLines.length - CODEMODE_COLLAPSED_SCRIPT_LINES);
|
|
347
|
+
const omitted =
|
|
348
|
+
omittedLines === 0 ? "" : `… ${pluralizedCodeModeCount(omittedLines, "line")} omitted · `;
|
|
349
|
+
container.addChild(new Text(theme.fg("dim", ` ${omitted}${expansionHint}`), 0, 0));
|
|
350
|
+
return container;
|
|
351
|
+
}
|
|
313
352
|
container.addChild(new Spacer(1));
|
|
314
353
|
if (sessionId !== undefined) appendCodeModeField(container, theme, "Session", sessionId);
|
|
315
354
|
if (executeParameters === undefined) return container;
|
|
@@ -319,8 +358,41 @@ export function renderCodeModeToolCall(
|
|
|
319
358
|
appendCodeModeField(container, theme, "Timeout", `${executeParameters.timeoutMs}ms`);
|
|
320
359
|
container.addChild(new Spacer(1));
|
|
321
360
|
container.addChild(new Text(theme.fg("muted", theme.bold("TypeScript")), 0, 0));
|
|
322
|
-
|
|
323
|
-
|
|
361
|
+
appendHighlightedCodeModeSource(container, source ?? "");
|
|
362
|
+
return container;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function renderCodeModeSessionsResult(
|
|
366
|
+
result: AgentToolResult<unknown>,
|
|
367
|
+
options: ToolRenderResultOptions,
|
|
368
|
+
theme: CodeModeRenderTheme,
|
|
369
|
+
): Component {
|
|
370
|
+
if (!Value.Check(CodeModeSessionsResultSchema, result.details)) {
|
|
371
|
+
return renderCodeModeFallback(result, options, theme, false);
|
|
372
|
+
}
|
|
373
|
+
const sessions: CodeModeSessionsResult = result.details;
|
|
374
|
+
const summary = `${theme.fg("success", "✓")} ${pluralizedCodeModeCount(sessions.sessions.length, "session")}`;
|
|
375
|
+
if (!options.expanded) {
|
|
376
|
+
const hint = options.isPartial ? "" : ` · ${keyText("app.tools.expand")} to expand`;
|
|
377
|
+
return new Text(`${summary}${hint}`, 0, 0);
|
|
378
|
+
}
|
|
379
|
+
const container = new Container();
|
|
380
|
+
container.addChild(new Text(summary, 0, 0));
|
|
381
|
+
if (sessions.sessions.length === 0) {
|
|
382
|
+
container.addChild(new Spacer(1));
|
|
383
|
+
container.addChild(new Text(theme.fg("dim", "No live Sessions"), 0, 0));
|
|
384
|
+
return container;
|
|
385
|
+
}
|
|
386
|
+
container.addChild(new Spacer(1));
|
|
387
|
+
for (const session of sessions.sessions) {
|
|
388
|
+
container.addChild(
|
|
389
|
+
new Text(
|
|
390
|
+
`${theme.fg(session.state === "running" ? "accent" : "muted", session.state)} ${sanitizeCodeModeText(session.sessionId)} ${pluralizedCodeModeCount(session.cellCount, "cell")} ${session.lastActivityAtMs}`,
|
|
391
|
+
0,
|
|
392
|
+
0,
|
|
393
|
+
),
|
|
394
|
+
);
|
|
395
|
+
}
|
|
324
396
|
return container;
|
|
325
397
|
}
|
|
326
398
|
|
|
@@ -330,14 +402,17 @@ export function renderCodeModeToolResult(
|
|
|
330
402
|
result: AgentToolResult<unknown>,
|
|
331
403
|
options: ToolRenderResultOptions,
|
|
332
404
|
theme: CodeModeRenderTheme,
|
|
333
|
-
parameters: CodeModeRenderedToolParameters,
|
|
334
405
|
isError: boolean,
|
|
335
406
|
formatSessionPrefix: CodeModeSessionPrefixFormatter = shortCodeModeSessionId,
|
|
336
407
|
): Component {
|
|
408
|
+
if (toolName === "codemode_sessions") {
|
|
409
|
+
return renderCodeModeSessionsResult(result, options, theme);
|
|
410
|
+
}
|
|
337
411
|
if (!Value.Check(CodeModeResultDetailsSchema, result.details)) {
|
|
338
412
|
return renderCodeModeFallback(result, options, theme, isError);
|
|
339
413
|
}
|
|
340
414
|
const details = result.details;
|
|
415
|
+
const consoleEntries = details.result === "pending" ? undefined : details.console;
|
|
341
416
|
const summary = renderCodeModeSummary(details, toolName, theme, formatSessionPrefix);
|
|
342
417
|
if (!options.expanded) {
|
|
343
418
|
const hint = options.isPartial ? "" : ` · ${keyText("app.tools.expand")} to expand`;
|
|
@@ -386,6 +461,13 @@ export function renderCodeModeToolResult(
|
|
|
386
461
|
}
|
|
387
462
|
}
|
|
388
463
|
|
|
464
|
+
if (consoleEntries !== undefined) {
|
|
465
|
+
container.addChild(new Spacer(1));
|
|
466
|
+
container.addChild(new Text(theme.fg("muted", theme.bold("Console")), 0, 0));
|
|
467
|
+
const output = consoleEntries.map((entry) => `${entry.method}: ${entry.text}`).join("\n");
|
|
468
|
+
container.addChild(new Text(theme.fg("toolOutput", boundedCodeModeText(output, 2_000)), 0, 0));
|
|
469
|
+
}
|
|
470
|
+
|
|
389
471
|
if (details.result === "success") {
|
|
390
472
|
container.addChild(new Spacer(1));
|
|
391
473
|
container.addChild(new Text(theme.fg("muted", theme.bold("Result")), 0, 0));
|
|
@@ -409,13 +491,13 @@ export function renderCodeModeToolResult(
|
|
|
409
491
|
return container;
|
|
410
492
|
}
|
|
411
493
|
|
|
412
|
-
/** Create the
|
|
494
|
+
/** Create the four CodeMode tools with semantic call and result Transcript renderers. */
|
|
413
495
|
export function createRenderedCodeModeToolDefinitions(
|
|
414
496
|
operations: CodeModeToolOperations,
|
|
415
497
|
executeDescription?: string,
|
|
416
498
|
formatSessionPrefix: CodeModeSessionPrefixFormatter = shortCodeModeSessionId,
|
|
417
499
|
): ReturnType<typeof createCodeModeToolDefinitions> {
|
|
418
|
-
const [executeTool, resultTool, cancelTool] = createCodeModeToolDefinitions(
|
|
500
|
+
const [executeTool, resultTool, cancelTool, sessionsTool] = createCodeModeToolDefinitions(
|
|
419
501
|
operations,
|
|
420
502
|
executeDescription,
|
|
421
503
|
);
|
|
@@ -436,7 +518,6 @@ export function createRenderedCodeModeToolDefinitions(
|
|
|
436
518
|
result,
|
|
437
519
|
options,
|
|
438
520
|
theme,
|
|
439
|
-
context.args,
|
|
440
521
|
context.isError,
|
|
441
522
|
formatSessionPrefix,
|
|
442
523
|
),
|
|
@@ -457,7 +538,6 @@ export function createRenderedCodeModeToolDefinitions(
|
|
|
457
538
|
result,
|
|
458
539
|
options,
|
|
459
540
|
theme,
|
|
460
|
-
context.args,
|
|
461
541
|
context.isError,
|
|
462
542
|
formatSessionPrefix,
|
|
463
543
|
),
|
|
@@ -478,7 +558,26 @@ export function createRenderedCodeModeToolDefinitions(
|
|
|
478
558
|
result,
|
|
479
559
|
options,
|
|
480
560
|
theme,
|
|
481
|
-
context.
|
|
561
|
+
context.isError,
|
|
562
|
+
formatSessionPrefix,
|
|
563
|
+
),
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
...sessionsTool,
|
|
567
|
+
renderCall: (_args, theme, context) =>
|
|
568
|
+
renderCodeModeToolCall(
|
|
569
|
+
"codemode_sessions",
|
|
570
|
+
{},
|
|
571
|
+
theme,
|
|
572
|
+
context.expanded,
|
|
573
|
+
formatSessionPrefix,
|
|
574
|
+
),
|
|
575
|
+
renderResult: (result, options, theme, context) =>
|
|
576
|
+
renderCodeModeToolResult(
|
|
577
|
+
"codemode_sessions",
|
|
578
|
+
result,
|
|
579
|
+
options,
|
|
580
|
+
theme,
|
|
482
581
|
context.isError,
|
|
483
582
|
formatSessionPrefix,
|
|
484
583
|
),
|