@ian-pascoe/pi-codemode 0.2.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 +48 -12
- package/package.json +1 -1
- package/src/codemode-console-output.ts +11 -0
- package/src/codemode-observer-ui.ts +15 -36
- package/src/codemode-session-coordinator.ts +127 -90
- package/src/codemode-tool-catalog.ts +4 -6
- package/src/codemode-tool-contract.ts +37 -7
- package/src/codemode-tool-rendering.ts +13 -7
- package/src/codemode-worker-protocol.ts +141 -33
- package/src/codemode-worker.ts +283 -18
- package/src/pi-codemode-extension.ts +17 -21
- 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,6 +8,11 @@ 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",
|
|
@@ -170,6 +175,15 @@ const CodeModeErrorCodeSchema = Type.Unsafe<CodeModeErrorCode>({
|
|
|
170
175
|
type: "string",
|
|
171
176
|
enum: [...CODEMODE_ERROR_CODES],
|
|
172
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 });
|
|
173
187
|
|
|
174
188
|
/** Stable error retained by a failed CodeMode result. */
|
|
175
189
|
export const CodeModeErrorSchema = Type.Object(
|
|
@@ -186,6 +200,7 @@ const CodeModeSuccessSchema = Type.Object(
|
|
|
186
200
|
sessionId: SessionIdSchema,
|
|
187
201
|
data: Type.Optional(CodeModeJsonValueSchema),
|
|
188
202
|
reclaimedSessionId: Type.Optional(SessionIdSchema),
|
|
203
|
+
console: Type.Optional(CodeModeConsoleOutputSchema),
|
|
189
204
|
},
|
|
190
205
|
{ additionalProperties: false },
|
|
191
206
|
);
|
|
@@ -225,6 +240,7 @@ const CodeModeFailedSchema = Type.Object(
|
|
|
225
240
|
result: Type.Literal("failed"),
|
|
226
241
|
sessionId: SessionIdSchema,
|
|
227
242
|
error: CodeModeErrorSchema,
|
|
243
|
+
console: Type.Optional(CodeModeConsoleOutputSchema),
|
|
228
244
|
},
|
|
229
245
|
{ additionalProperties: false },
|
|
230
246
|
);
|
|
@@ -245,6 +261,7 @@ const CodeModeSuccessDetailsSchema = Type.Object(
|
|
|
245
261
|
sessionId: SessionIdSchema,
|
|
246
262
|
data: Type.Optional(CodeModeJsonValueSchema),
|
|
247
263
|
reclaimedSessionId: Type.Optional(SessionIdSchema),
|
|
264
|
+
console: Type.Optional(CodeModeConsoleOutputSchema),
|
|
248
265
|
presentation: Type.Optional(CodeModePresentationSnapshotSchema),
|
|
249
266
|
},
|
|
250
267
|
{ additionalProperties: false },
|
|
@@ -262,6 +279,7 @@ const CodeModeFailedDetailsSchema = Type.Object(
|
|
|
262
279
|
result: Type.Literal("failed"),
|
|
263
280
|
sessionId: SessionIdSchema,
|
|
264
281
|
error: CodeModeErrorSchema,
|
|
282
|
+
console: Type.Optional(CodeModeConsoleOutputSchema),
|
|
265
283
|
presentation: Type.Optional(CodeModePresentationSnapshotSchema),
|
|
266
284
|
},
|
|
267
285
|
{ additionalProperties: false },
|
|
@@ -276,11 +294,19 @@ export const CodeModeResultDetailsSchema = Type.Union([
|
|
|
276
294
|
/** Schema-derived details retained by one session-scoped CodeMode operation. */
|
|
277
295
|
export type CodeModeResultDetails = Static<typeof CodeModeResultDetailsSchema>;
|
|
278
296
|
|
|
279
|
-
/** A
|
|
280
|
-
export function createCodeModeSuccess(
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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] };
|
|
284
310
|
}
|
|
285
311
|
|
|
286
312
|
/** A polling result for a live Cell. */
|
|
@@ -288,13 +314,17 @@ export function createCodeModePending(sessionId: string): CodeModeResult {
|
|
|
288
314
|
return { result: "pending", sessionId };
|
|
289
315
|
}
|
|
290
316
|
|
|
291
|
-
/** A stable expected failure
|
|
317
|
+
/** A stable expected failure with non-empty Cell Console output; empty Console lists are omitted. */
|
|
292
318
|
export function createCodeModeFailure(
|
|
293
319
|
sessionId: string,
|
|
294
320
|
code: CodeModeErrorCode,
|
|
295
321
|
message: string,
|
|
322
|
+
consoleEntries?: readonly CodeModeConsoleEntry[],
|
|
296
323
|
): CodeModeResult {
|
|
297
|
-
|
|
324
|
+
const result = { result: "failed" as const, sessionId, error: { code, message } };
|
|
325
|
+
return consoleEntries === undefined || consoleEntries.length === 0
|
|
326
|
+
? result
|
|
327
|
+
: { ...result, console: [...consoleEntries] };
|
|
298
328
|
}
|
|
299
329
|
|
|
300
330
|
/** A bounded JSON compatibility parse that never invokes getters or `toJSON`. */
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
import { Type } from "typebox";
|
|
22
22
|
import { Value } from "typebox/value";
|
|
23
23
|
import { formatCodeModePresentationData } from "./codemode-presentation-output.js";
|
|
24
|
+
import { formatCodeModeDuration } from "./codemode-session-coordinator.js";
|
|
24
25
|
import {
|
|
25
26
|
CodeModeCancelParametersSchema,
|
|
26
27
|
CodeModeExecuteParametersSchema,
|
|
@@ -116,13 +117,6 @@ function shortCodeModeSessionId(sessionId: string): string {
|
|
|
116
117
|
/** Resolves one Session ID to the shortest unambiguous CodeMode Transcript label. */
|
|
117
118
|
export type CodeModeSessionPrefixFormatter = (sessionId: string) => string;
|
|
118
119
|
|
|
119
|
-
function formatCodeModeDuration(elapsedMs: number): string {
|
|
120
|
-
if (elapsedMs < 1_000) return `${elapsedMs}ms`;
|
|
121
|
-
if (elapsedMs < 60_000) return `${(elapsedMs / 1_000).toFixed(1)}s`;
|
|
122
|
-
const seconds = Math.floor(elapsedMs / 1_000);
|
|
123
|
-
return `${Math.floor(seconds / 60)}m ${String(seconds % 60).padStart(2, "0")}s`;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
120
|
function pluralizedCodeModeCount(count: number, noun: string): string {
|
|
127
121
|
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
128
122
|
}
|
|
@@ -252,6 +246,7 @@ function renderCodeModeSummary(
|
|
|
252
246
|
formatSessionPrefix: CodeModeSessionPrefixFormatter,
|
|
253
247
|
): string {
|
|
254
248
|
const presentation = details.presentation;
|
|
249
|
+
const consoleEntries = details.result === "pending" ? undefined : details.console;
|
|
255
250
|
const state = codeModeCellState(toolName, details);
|
|
256
251
|
const status =
|
|
257
252
|
details.result === "failed" && details.error.code === "eviction"
|
|
@@ -279,6 +274,9 @@ function renderCodeModeSummary(
|
|
|
279
274
|
: undefined,
|
|
280
275
|
details.result === "failed" ? theme.fg("muted", details.error.code) : undefined,
|
|
281
276
|
details.result === "failed" ? boundedCodeModePreview(details.error.message, 64) : undefined,
|
|
277
|
+
consoleEntries === undefined
|
|
278
|
+
? undefined
|
|
279
|
+
: theme.fg("muted", pluralizedCodeModeCount(consoleEntries.length, "console call")),
|
|
282
280
|
state !== "running" && presentation !== undefined && presentation.nested_tool_count > 0
|
|
283
281
|
? theme.fg("muted", pluralizedCodeModeCount(presentation.nested_tool_count, "tool"))
|
|
284
282
|
: undefined,
|
|
@@ -414,6 +412,7 @@ export function renderCodeModeToolResult(
|
|
|
414
412
|
return renderCodeModeFallback(result, options, theme, isError);
|
|
415
413
|
}
|
|
416
414
|
const details = result.details;
|
|
415
|
+
const consoleEntries = details.result === "pending" ? undefined : details.console;
|
|
417
416
|
const summary = renderCodeModeSummary(details, toolName, theme, formatSessionPrefix);
|
|
418
417
|
if (!options.expanded) {
|
|
419
418
|
const hint = options.isPartial ? "" : ` · ${keyText("app.tools.expand")} to expand`;
|
|
@@ -462,6 +461,13 @@ export function renderCodeModeToolResult(
|
|
|
462
461
|
}
|
|
463
462
|
}
|
|
464
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
|
+
|
|
465
471
|
if (details.result === "success") {
|
|
466
472
|
container.addChild(new Spacer(1));
|
|
467
473
|
container.addChild(new Text(theme.fg("muted", theme.bold("Result")), 0, 0));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
|
+
import { CODEMODE_CONSOLE_METHODS, type CodeModeConsoleEntry } from "./codemode-console-output.ts";
|
|
2
3
|
|
|
3
4
|
/** Maximum UTF-8 bytes in one CodeMode worker protocol line, excluding its newline. */
|
|
4
5
|
export const CODEMODE_WORKER_MESSAGE_LIMIT_BYTES = 8 * 1024 * 1024;
|
|
@@ -76,6 +77,7 @@ export type CodeModeWorkerResponse =
|
|
|
76
77
|
readonly sessionId: string;
|
|
77
78
|
readonly cellId: string;
|
|
78
79
|
readonly resultJson?: string;
|
|
80
|
+
readonly console?: readonly CodeModeConsoleEntry[];
|
|
79
81
|
}
|
|
80
82
|
| {
|
|
81
83
|
readonly version: 1;
|
|
@@ -83,6 +85,7 @@ export type CodeModeWorkerResponse =
|
|
|
83
85
|
readonly sessionId: string;
|
|
84
86
|
readonly cellId: string;
|
|
85
87
|
readonly error: { readonly code: CodeModeWorkerCellErrorCode; readonly message: string };
|
|
88
|
+
readonly console?: readonly CodeModeConsoleEntry[];
|
|
86
89
|
}
|
|
87
90
|
| {
|
|
88
91
|
readonly version: 1;
|
|
@@ -132,19 +135,61 @@ function hasString(values: readonly string[], candidate: string): boolean {
|
|
|
132
135
|
function hasExactKeys(value: CodeModeProtocolObject, expected: readonly string[]): boolean {
|
|
133
136
|
const keys = objectKeys(value);
|
|
134
137
|
if (keys.length !== expected.length) return false;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
138
|
+
return expected.every((expectedKey) => expectedKey !== undefined && keys.includes(expectedKey));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function hasDuplicateJsonObjectKeys(message: string): boolean {
|
|
142
|
+
const objectKeysByDepth: (string[] | undefined)[] = [];
|
|
143
|
+
for (let index = 0; index < message.length; index += 1) {
|
|
144
|
+
const character = message[index];
|
|
145
|
+
if (character === "{") {
|
|
146
|
+
objectKeysByDepth.push([]);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (character === "[") {
|
|
150
|
+
objectKeysByDepth.push(undefined);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (character === "}" || character === "]") {
|
|
154
|
+
objectKeysByDepth.pop();
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (character !== '"') continue;
|
|
158
|
+
|
|
159
|
+
let endIndex = index + 1;
|
|
160
|
+
for (; endIndex < message.length; endIndex += 1) {
|
|
161
|
+
if (message[endIndex] === "\\") {
|
|
162
|
+
endIndex += 1;
|
|
163
|
+
continue;
|
|
143
164
|
}
|
|
165
|
+
if (message[endIndex] === '"') break;
|
|
144
166
|
}
|
|
145
|
-
if (
|
|
167
|
+
if (endIndex >= message.length) return false;
|
|
168
|
+
let nextIndex = endIndex + 1;
|
|
169
|
+
while (
|
|
170
|
+
message[nextIndex] === " " ||
|
|
171
|
+
message[nextIndex] === "\n" ||
|
|
172
|
+
message[nextIndex] === "\r" ||
|
|
173
|
+
message[nextIndex] === "\t"
|
|
174
|
+
) {
|
|
175
|
+
nextIndex += 1;
|
|
176
|
+
}
|
|
177
|
+
const keys = objectKeysByDepth[objectKeysByDepth.length - 1];
|
|
178
|
+
if (message[nextIndex] === ":" && keys !== undefined) {
|
|
179
|
+
try {
|
|
180
|
+
// SAFETY: This slice is one syntactically bounded JSON string token; the string refinement below rejects every other JSON value.
|
|
181
|
+
const key = jsonParse(message.slice(index, endIndex + 1)) as CodeModeProtocolValue;
|
|
182
|
+
if (isString(key)) {
|
|
183
|
+
if (hasString(keys, key)) return true;
|
|
184
|
+
keys.push(key);
|
|
185
|
+
}
|
|
186
|
+
} catch {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
index = endIndex;
|
|
146
191
|
}
|
|
147
|
-
return
|
|
192
|
+
return false;
|
|
148
193
|
}
|
|
149
194
|
|
|
150
195
|
function parseProtocolJson(
|
|
@@ -158,6 +203,9 @@ function parseProtocolJson(
|
|
|
158
203
|
if (Buffer.byteLength(message, "utf8") > CODEMODE_WORKER_MESSAGE_LIMIT_BYTES) {
|
|
159
204
|
return { ok: false, message: `CodeMode worker ${subject} exceeds 8 MiB` };
|
|
160
205
|
}
|
|
206
|
+
if (hasDuplicateJsonObjectKeys(message)) {
|
|
207
|
+
return { ok: false, message: `CodeMode worker ${subject} contains duplicate object keys` };
|
|
208
|
+
}
|
|
161
209
|
try {
|
|
162
210
|
// SAFETY: Successful JSON.parse output is exactly the recursive JSON representation modeled by CodeModeProtocolValue.
|
|
163
211
|
return { ok: true, value: jsonParse(message) as CodeModeProtocolValue };
|
|
@@ -308,6 +356,30 @@ function parseWorkerError(
|
|
|
308
356
|
return { code: value.code, message: value.message };
|
|
309
357
|
}
|
|
310
358
|
|
|
359
|
+
function parseConsoleOutput(
|
|
360
|
+
value: CodeModeProtocolSlot,
|
|
361
|
+
): readonly CodeModeConsoleEntry[] | undefined {
|
|
362
|
+
if (!arrayIsArray(value) || value.length === 0) return undefined;
|
|
363
|
+
const entries: CodeModeConsoleEntry[] = [];
|
|
364
|
+
for (const candidate of value) {
|
|
365
|
+
if (
|
|
366
|
+
!isRecord(candidate) ||
|
|
367
|
+
!hasExactKeys(candidate, ["method", "text"]) ||
|
|
368
|
+
!isString(candidate.method) ||
|
|
369
|
+
!hasString(CODEMODE_CONSOLE_METHODS, candidate.method) ||
|
|
370
|
+
!isString(candidate.text)
|
|
371
|
+
) {
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
374
|
+
entries.push({
|
|
375
|
+
// SAFETY: The literal-membership check above refines the protocol string to a supported Console method.
|
|
376
|
+
method: candidate.method as CodeModeConsoleEntry["method"],
|
|
377
|
+
text: candidate.text,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
return entries;
|
|
381
|
+
}
|
|
382
|
+
|
|
311
383
|
function parseToolBatchResponse(
|
|
312
384
|
decoded: CodeModeProtocolObject,
|
|
313
385
|
): CodeModeWorkerResponse | undefined {
|
|
@@ -401,43 +473,58 @@ export function parseCodeModeWorkerResponse(
|
|
|
401
473
|
}
|
|
402
474
|
if (
|
|
403
475
|
decoded.type === "cell-result" &&
|
|
404
|
-
hasExactKeys(
|
|
405
|
-
decoded,
|
|
406
|
-
decoded.resultJson === undefined
|
|
407
|
-
? ["cellId", "sessionId", "type", "version"]
|
|
408
|
-
: ["cellId", "resultJson", "sessionId", "type", "version"],
|
|
409
|
-
) &&
|
|
410
476
|
isNonEmptyString(decoded.cellId) &&
|
|
411
477
|
(decoded.resultJson === undefined || isString(decoded.resultJson))
|
|
412
478
|
) {
|
|
479
|
+
const expectedKeys = ["cellId", "sessionId", "type", "version"];
|
|
480
|
+
if (decoded.resultJson !== undefined) expectedKeys.push("resultJson");
|
|
481
|
+
if (decoded.console !== undefined) expectedKeys.push("console");
|
|
482
|
+
if (!hasExactKeys(decoded, expectedKeys)) {
|
|
483
|
+
return { ok: false, message: "CodeMode worker response has an invalid protocol shape" };
|
|
484
|
+
}
|
|
485
|
+
const consoleEntries =
|
|
486
|
+
decoded.console === undefined ? undefined : parseConsoleOutput(decoded.console);
|
|
487
|
+
if (decoded.console !== undefined && consoleEntries === undefined) {
|
|
488
|
+
return { ok: false, message: "CodeMode worker response has an invalid protocol shape" };
|
|
489
|
+
}
|
|
413
490
|
const value = {
|
|
414
491
|
version: CODEMODE_WORKER_PROTOCOL_VERSION,
|
|
415
492
|
type: "cell-result",
|
|
416
493
|
sessionId: decoded.sessionId,
|
|
417
494
|
cellId: decoded.cellId,
|
|
418
495
|
} as const;
|
|
419
|
-
|
|
420
|
-
?
|
|
421
|
-
|
|
496
|
+
const resultValue =
|
|
497
|
+
decoded.resultJson === undefined ? value : { ...value, resultJson: decoded.resultJson };
|
|
498
|
+
return consoleEntries === undefined
|
|
499
|
+
? { ok: true, value: resultValue }
|
|
500
|
+
: { ok: true, value: { ...resultValue, console: consoleEntries } };
|
|
422
501
|
}
|
|
423
|
-
if (
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
502
|
+
if (decoded.type === "cell-error" && isNonEmptyString(decoded.cellId)) {
|
|
503
|
+
const expectedKeys = ["cellId", "error", "sessionId", "type", "version"];
|
|
504
|
+
if (decoded.console !== undefined) expectedKeys.push("console");
|
|
505
|
+
if (!hasExactKeys(decoded, expectedKeys)) {
|
|
506
|
+
return { ok: false, message: "CodeMode worker response has an invalid protocol shape" };
|
|
507
|
+
}
|
|
428
508
|
const error = parseWorkerError(decoded.error);
|
|
429
|
-
|
|
509
|
+
const consoleEntries =
|
|
510
|
+
decoded.console === undefined ? undefined : parseConsoleOutput(decoded.console);
|
|
511
|
+
if (
|
|
512
|
+
error !== undefined &&
|
|
513
|
+
["script", "serialization", "runtime"].includes(error.code) &&
|
|
514
|
+
(decoded.console === undefined || consoleEntries !== undefined)
|
|
515
|
+
) {
|
|
430
516
|
// SAFETY: The literal-membership check above refines the protocol string to the closed worker error code union.
|
|
431
517
|
const code = error.code as CodeModeWorkerCellErrorCode;
|
|
518
|
+
const value = {
|
|
519
|
+
version: CODEMODE_WORKER_PROTOCOL_VERSION,
|
|
520
|
+
type: "cell-error",
|
|
521
|
+
sessionId: decoded.sessionId,
|
|
522
|
+
cellId: decoded.cellId,
|
|
523
|
+
error: { code, message: error.message },
|
|
524
|
+
} as const;
|
|
432
525
|
return {
|
|
433
526
|
ok: true,
|
|
434
|
-
value: {
|
|
435
|
-
version: CODEMODE_WORKER_PROTOCOL_VERSION,
|
|
436
|
-
type: "cell-error",
|
|
437
|
-
sessionId: decoded.sessionId,
|
|
438
|
-
cellId: decoded.cellId,
|
|
439
|
-
error: { code, message: error.message },
|
|
440
|
-
},
|
|
527
|
+
value: consoleEntries === undefined ? value : { ...value, console: consoleEntries },
|
|
441
528
|
};
|
|
442
529
|
}
|
|
443
530
|
}
|
|
@@ -456,7 +543,28 @@ export function serializeCodeModeWorkerRequest(
|
|
|
456
543
|
|
|
457
544
|
/** Serializes one worker response, replacing oversized Cell output with a bounded error. */
|
|
458
545
|
export function serializeCodeModeWorkerResponse(response: CodeModeWorkerResponse): string {
|
|
459
|
-
|
|
546
|
+
let message: string;
|
|
547
|
+
if (response.type === "cell-result" && response.console?.length === 0) {
|
|
548
|
+
const responseBase = {
|
|
549
|
+
version: response.version,
|
|
550
|
+
type: response.type,
|
|
551
|
+
sessionId: response.sessionId,
|
|
552
|
+
cellId: response.cellId,
|
|
553
|
+
} as const;
|
|
554
|
+
message = jsonStringify(
|
|
555
|
+
response.resultJson === undefined
|
|
556
|
+
? responseBase
|
|
557
|
+
: { ...responseBase, resultJson: response.resultJson },
|
|
558
|
+
);
|
|
559
|
+
} else if (response.type === "cell-error" && response.console?.length === 0) {
|
|
560
|
+
message = jsonStringify({
|
|
561
|
+
version: response.version,
|
|
562
|
+
type: response.type,
|
|
563
|
+
sessionId: response.sessionId,
|
|
564
|
+
cellId: response.cellId,
|
|
565
|
+
error: response.error,
|
|
566
|
+
});
|
|
567
|
+
} else message = jsonStringify(response);
|
|
460
568
|
if (Buffer.byteLength(message, "utf8") <= CODEMODE_WORKER_MESSAGE_LIMIT_BYTES) return message;
|
|
461
569
|
if (
|
|
462
570
|
response.type === "cell-result" ||
|