@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.
@@ -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 jsonLiteral(constant) ?? "unknown";
138
+ return JSON.stringify(constant) ?? "unknown";
143
139
  }
144
- const enumValues = Array.isArray(record.enum) ? record.enum.map(jsonLiteral) : undefined;
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 all three public CodeMode tools. */
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 every public CodeMode tool. */
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 every public CodeMode tool. */
294
+ /** Schema-derived details retained by one session-scoped CodeMode operation. */
244
295
  export type CodeModeResultDetails = Static<typeof CodeModeResultDetailsSchema>;
245
296
 
246
- /** A successful result with optional JSON data. */
247
- export function createCodeModeSuccess(sessionId: string, data?: CodeModeJsonValue): CodeModeResult {
248
- return data === undefined
249
- ? { result: "success", sessionId }
250
- : { result: "success", sessionId, data };
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 result. */
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
- return { result: "failed", sessionId, error: { code, message } };
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 three Pi tools. */
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
- /** Creates the three stable Pi definitions while leaving admission and session policy to the coordinator. */
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 session and retain its terminal result.",
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
- return [executeTool, resultTool, cancelTool];
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 three CodeMode tools with semantic Transcript rendering. */
41
- export type CodeModeRenderedToolName = "codemode_execute" | "codemode_result" | "codemode_cancel";
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 three CodeMode Transcript renderers. */
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 CODEMODE_SCRIPT_MAX_LINES = 200;
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
- return Value.Check(CodeModeCancelParametersSchema, parameters) ? parameters : undefined;
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 = CODEMODE_STATUS_PRESENTATION[state];
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 as a semantic operation with bounded source detail. */
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
- : "Cancel";
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 = parsedParameters?.sessionId;
293
- const preview =
294
- executeParameters === undefined
295
- ? undefined
296
- : firstMeaningfulScriptLine(executeParameters.script);
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
- preview === undefined ? undefined : theme.fg("dim", preview),
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) return container;
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
- const script = boundedCodeModeText(executeParameters.script, CODEMODE_SCRIPT_MAX_LINES);
323
- appendCodeModeBlock(container, "ts", script);
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 three CodeMode tools with semantic call and result Transcript renderers. */
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.args,
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
  ),