pi-supernova 0.3.1 → 0.4.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 +220 -3
- package/docs/CHANGELOG.md +40 -0
- package/docs/TOKEN_COSTS.md +171 -0
- package/index.js +116 -37
- package/package.json +3 -1
- package/src/bridge/catalog.js +37 -2
- package/src/bridge/host-bridge.js +340 -13
- package/src/bridge/native-tools.js +35 -1
- package/src/config/config.default.json +1 -1
- package/src/config/config.js +19 -0
- package/src/context/evidence.js +119 -5
- package/src/context/fuzzy.js +37 -0
- package/src/context/ledger.js +128 -6
- package/src/context/outline.js +45 -1
- package/src/context/repo-index.js +63 -1
- package/src/context/search.js +45 -0
- package/src/context/snap.js +80 -4
- package/src/context/surface.js +25 -0
- package/src/fs/check.js +50 -0
- package/src/fs/diff.js +16 -0
- package/src/fs/json-read.js +87 -0
- package/src/fs/patch.js +26 -0
- package/src/fs/vfs.js +101 -6
- package/src/fs/workspace.js +36 -0
- package/src/output/bottleneck.js +46 -0
- package/src/output/format.js +117 -17
- package/src/runtime/guest-worker.js +151 -18
- package/src/runtime/parallel.js +33 -0
- package/src/runtime/program-batch.js +112 -0
- package/src/runtime/program-file.js +40 -0
- package/src/runtime/reference.js +25 -0
- package/src/runtime/runtime.js +90 -6
- package/src/shared/decode.js +29 -0
- package/src/ui/omp-frame.js +30 -1
- package/src/ui/render-measure.js +24 -0
- package/src/ui/render.js +96 -2
package/src/ui/render.js
CHANGED
|
@@ -21,8 +21,10 @@ export { measureWidth, hardTruncate, clampLine };
|
|
|
21
21
|
function formatDiffRows(diff, theme, maxShown = 6) {
|
|
22
22
|
if (!diff || !Array.isArray(diff.lines) || diff.lines.length === 0) return [];
|
|
23
23
|
const body = [];
|
|
24
|
+
|
|
24
25
|
for (const item of diff.lines.slice(0, maxShown)) {
|
|
25
26
|
const num = item.lineNum || 0;
|
|
27
|
+
|
|
26
28
|
if (item.type === "remove") {
|
|
27
29
|
const gut = theme.fg("toolDiffRemoved", `-${num}`.padStart(5));
|
|
28
30
|
body.push(`${gut}${theme.fg("borderMuted", " │ ")}${theme.fg("toolDiffRemoved", `- ${cleanInlineText(item.text)}`)}`);
|
|
@@ -34,10 +36,13 @@ function formatDiffRows(diff, theme, maxShown = 6) {
|
|
|
34
36
|
body.push(`${gut}${theme.fg("borderMuted", " │ ")}${theme.fg("toolDiffContext", ` ${cleanInlineText(item.text)}`)}`);
|
|
35
37
|
}
|
|
36
38
|
}
|
|
39
|
+
|
|
37
40
|
const displayLineCount = Number.isInteger(diff.displayLineCount) ? diff.displayLineCount : diff.lines.length;
|
|
41
|
+
|
|
38
42
|
if (displayLineCount > maxShown) {
|
|
39
43
|
body.push(theme.fg("dim", ` │ … ${displayLineCount - maxShown} more lines`));
|
|
40
44
|
}
|
|
45
|
+
|
|
41
46
|
return body;
|
|
42
47
|
}
|
|
43
48
|
|
|
@@ -50,6 +55,7 @@ function stripUnsafeControls(value) {
|
|
|
50
55
|
|
|
51
56
|
function cleanBlockText(value) {
|
|
52
57
|
const normalized = stripVTControlCharacters(String(value ?? "")).replace(/\r\n?/g, "\n");
|
|
58
|
+
|
|
53
59
|
return stripUnsafeControls(normalized).replace(/[\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/g, "");
|
|
54
60
|
}
|
|
55
61
|
|
|
@@ -59,8 +65,10 @@ function cleanInlineText(value) {
|
|
|
59
65
|
|
|
60
66
|
function displayOperation(tool, target, diff, ok, item) {
|
|
61
67
|
const rawName = cleanInlineText(tool);
|
|
68
|
+
|
|
62
69
|
if (!rawName) return null;
|
|
63
70
|
const normalized = rawName === "apply_patch" ? "patch" : rawName;
|
|
71
|
+
|
|
64
72
|
return { tool: normalized, target, diff, ok, ms: item?.ms, exitCode: item?.exitCode, time: item?.time, error: item?.error };
|
|
65
73
|
}
|
|
66
74
|
|
|
@@ -76,12 +84,17 @@ function isTheme(value) {
|
|
|
76
84
|
export function normalizeCallRenderArgs(a, b, c) {
|
|
77
85
|
if (isTheme(b)) {
|
|
78
86
|
const context = isObject(c) ? c : {};
|
|
87
|
+
|
|
79
88
|
if (!isObject(context.state)) context.state = {};
|
|
89
|
+
|
|
80
90
|
return { args: a, theme: b, context, host: "pi" };
|
|
81
91
|
}
|
|
92
|
+
|
|
82
93
|
if (isTheme(c)) {
|
|
83
94
|
const options = isObject(b) ? b : {};
|
|
95
|
+
|
|
84
96
|
if (!isObject(options.state)) options.state = {};
|
|
97
|
+
|
|
85
98
|
const context = {
|
|
86
99
|
...options,
|
|
87
100
|
state: options.state,
|
|
@@ -92,8 +105,10 @@ export function normalizeCallRenderArgs(a, b, c) {
|
|
|
92
105
|
lastComponent: options.lastComponent,
|
|
93
106
|
invalidate: options.invalidate,
|
|
94
107
|
};
|
|
108
|
+
|
|
95
109
|
return { args: a, theme: c, context, host: "omp", options };
|
|
96
110
|
}
|
|
111
|
+
|
|
97
112
|
throw new Error("supernova renderCall: theme missing (expected Pi or OMP signature)");
|
|
98
113
|
}
|
|
99
114
|
|
|
@@ -109,14 +124,19 @@ function contextFrom(opts, ctxOrArgs) {
|
|
|
109
124
|
if (isObject(ctxOrArgs) && !isTheme(ctxOrArgs)) {
|
|
110
125
|
if ("lastComponent" in ctxOrArgs || "state" in ctxOrArgs || "invalidate" in ctxOrArgs) return ctxOrArgs;
|
|
111
126
|
}
|
|
127
|
+
|
|
112
128
|
return { state: opts.state, lastComponent: opts.lastComponent };
|
|
113
129
|
}
|
|
114
130
|
|
|
115
131
|
function detectResultHost(options, ctxOrArgs) {
|
|
116
132
|
if (isTheme(options)) return "pi";
|
|
133
|
+
|
|
117
134
|
if (!isObject(ctxOrArgs)) return "pi";
|
|
135
|
+
|
|
118
136
|
if ("lastComponent" in ctxOrArgs || "invalidate" in ctxOrArgs) return "pi";
|
|
119
|
-
|
|
137
|
+
|
|
138
|
+
if ("code" in ctxOrArgs || "file" in ctxOrArgs || "programs" in ctxOrArgs || "timeoutMs" in ctxOrArgs) return "omp";
|
|
139
|
+
|
|
120
140
|
return "pi";
|
|
121
141
|
}
|
|
122
142
|
|
|
@@ -124,22 +144,27 @@ function normalizeResultRenderArgs(result, options, themeOrCtx, ctxOrArgs) {
|
|
|
124
144
|
if (isTheme(themeOrCtx)) {
|
|
125
145
|
const opts = isObject(options) ? options : {};
|
|
126
146
|
const context = contextFrom(opts, ctxOrArgs);
|
|
147
|
+
|
|
127
148
|
if (!isObject(context.state)) context.state = {};
|
|
149
|
+
|
|
128
150
|
return {
|
|
129
151
|
result,
|
|
130
152
|
expanded: !!opts.expanded,
|
|
131
153
|
isPartial: !!opts.isPartial,
|
|
132
154
|
theme: themeOrCtx,
|
|
133
155
|
context,
|
|
134
|
-
args: ctxOrArgs?.code ? ctxOrArgs : context.args,
|
|
156
|
+
args: ctxOrArgs?.code || ctxOrArgs?.file || ctxOrArgs?.programs ? ctxOrArgs : context.args,
|
|
135
157
|
host: detectResultHost(options, ctxOrArgs),
|
|
136
158
|
options: opts,
|
|
137
159
|
};
|
|
138
160
|
}
|
|
161
|
+
|
|
139
162
|
// Extremely defensive: (result, theme, context) oddball
|
|
140
163
|
if (isTheme(options)) {
|
|
141
164
|
const context = isObject(themeOrCtx) ? themeOrCtx : {};
|
|
165
|
+
|
|
142
166
|
if (!isObject(context.state)) context.state = {};
|
|
167
|
+
|
|
143
168
|
return {
|
|
144
169
|
result,
|
|
145
170
|
expanded: !!context.expanded,
|
|
@@ -151,18 +176,22 @@ function normalizeResultRenderArgs(result, options, themeOrCtx, ctxOrArgs) {
|
|
|
151
176
|
options: {},
|
|
152
177
|
};
|
|
153
178
|
}
|
|
179
|
+
|
|
154
180
|
throw new Error("supernova renderResult: theme missing (expected Pi or OMP signature)");
|
|
155
181
|
}
|
|
156
182
|
|
|
157
183
|
function batchTarget(paths) {
|
|
158
184
|
const names = paths.map((p) => String(p).replace(/\\/g, "/").split("/").pop());
|
|
185
|
+
|
|
159
186
|
return `${paths.length} files: ${names.join(", ")}`;
|
|
160
187
|
}
|
|
161
188
|
|
|
162
189
|
const OPERATION_TARGETS = [
|
|
163
190
|
[(item) => item?.name === "snap", (item, args) => {
|
|
164
191
|
const query = args.query ? `"${args.query}"` : "";
|
|
192
|
+
|
|
165
193
|
if (!args.path) return query;
|
|
194
|
+
|
|
166
195
|
return `${query} → ${args.path}`;
|
|
167
196
|
}],
|
|
168
197
|
[(item) => item?.name === "search", (item, args) => (args.query ? `"${args.query}"` : "")],
|
|
@@ -177,60 +206,79 @@ const OPERATION_TARGETS = [
|
|
|
177
206
|
|
|
178
207
|
function operationTarget(item) {
|
|
179
208
|
const args = item?.args || {};
|
|
209
|
+
|
|
180
210
|
for (const [predicate, formatter] of OPERATION_TARGETS) {
|
|
181
211
|
if (predicate(item, args)) return formatter(item, args);
|
|
182
212
|
}
|
|
213
|
+
|
|
183
214
|
return "";
|
|
184
215
|
}
|
|
185
216
|
|
|
186
217
|
function parseDiffLine(rawLine) {
|
|
187
218
|
const signed = /^([+-])\s*(\d+)\s?(.*)$/.exec(rawLine);
|
|
219
|
+
|
|
188
220
|
if (signed) return { type: signed[1] === "+" ? "add" : "remove", lineNum: Number(signed[2]), text: signed[3] };
|
|
189
221
|
const contextual = /^\s+(\d+)\s?(.*)$/.exec(rawLine);
|
|
222
|
+
|
|
190
223
|
if (contextual) return { type: "context", lineNum: Number(contextual[1]), text: contextual[2] };
|
|
224
|
+
|
|
191
225
|
return null;
|
|
192
226
|
}
|
|
193
227
|
|
|
194
228
|
// Text diffs are immutable, even when hosts replace trace snapshots on each frame.
|
|
195
229
|
// Cache only parsed data, not theme, paths or mutable result objects.
|
|
196
230
|
const textDiffCache = new Map();
|
|
231
|
+
|
|
197
232
|
let cachedDiffChars = 0;
|
|
233
|
+
|
|
198
234
|
const MAX_CACHED_DIFF_CHARS = 1_000_000;
|
|
199
235
|
|
|
200
236
|
function normalizeTraceDiff(item) {
|
|
201
237
|
const diff = item?.diff;
|
|
238
|
+
|
|
202
239
|
if (isObject(diff)) return diff;
|
|
240
|
+
|
|
203
241
|
if (!isString(diff) || !diff.trim()) return undefined;
|
|
204
242
|
const cached = textDiffCache.get(diff);
|
|
243
|
+
|
|
205
244
|
if (cached) return cached;
|
|
206
245
|
const lines = [];
|
|
207
246
|
let displayLineCount = 0;
|
|
208
247
|
let added = 0;
|
|
209
248
|
let removed = 0;
|
|
249
|
+
|
|
210
250
|
for (const rawLine of cleanBlockText(diff).split("\n")) {
|
|
211
251
|
const parsed = parseDiffLine(rawLine);
|
|
252
|
+
|
|
212
253
|
if (!parsed) continue;
|
|
254
|
+
|
|
213
255
|
if (parsed.type === "add") added += 1;
|
|
214
256
|
else if (parsed.type === "remove") removed += 1;
|
|
215
257
|
displayLineCount++;
|
|
258
|
+
|
|
216
259
|
if (lines.length < 24) lines.push(parsed);
|
|
217
260
|
}
|
|
261
|
+
|
|
218
262
|
if (lines.length === 0) return undefined;
|
|
219
263
|
const parsed = { added, removed, lines, displayLineCount };
|
|
264
|
+
|
|
220
265
|
if (diff.length <= MAX_CACHED_DIFF_CHARS) {
|
|
221
266
|
while (textDiffCache.size >= 24 || cachedDiffChars + diff.length > MAX_CACHED_DIFF_CHARS) {
|
|
222
267
|
const oldest = textDiffCache.keys().next().value;
|
|
223
268
|
textDiffCache.delete(oldest);
|
|
224
269
|
cachedDiffChars -= oldest.length;
|
|
225
270
|
}
|
|
271
|
+
|
|
226
272
|
textDiffCache.set(diff, parsed);
|
|
227
273
|
cachedDiffChars += diff.length;
|
|
228
274
|
}
|
|
275
|
+
|
|
229
276
|
return parsed;
|
|
230
277
|
}
|
|
231
278
|
|
|
232
279
|
function operationsFromTrace(trace) {
|
|
233
280
|
if (!Array.isArray(trace)) return [];
|
|
281
|
+
|
|
234
282
|
return trace
|
|
235
283
|
.map((item) => displayOperation(item?.name || "tool", operationTarget(item), normalizeTraceDiff(item), item?.ok, item))
|
|
236
284
|
.filter(Boolean);
|
|
@@ -241,53 +289,71 @@ const EMPTY_CALL = { render: () => [], invalidate() {} };
|
|
|
241
289
|
|
|
242
290
|
export function renderSupernovaCall(a, b, c) {
|
|
243
291
|
const { context, options } = normalizeCallRenderArgs(a, b, c);
|
|
292
|
+
|
|
244
293
|
if (options) options.lastComponent = EMPTY_CALL;
|
|
245
294
|
else if (context) context.lastComponent = EMPTY_CALL;
|
|
295
|
+
|
|
246
296
|
return EMPTY_CALL;
|
|
247
297
|
}
|
|
248
298
|
|
|
249
299
|
const TOOL_COL = 7;
|
|
300
|
+
|
|
250
301
|
const DURATION_COL = 6;
|
|
251
302
|
|
|
252
303
|
function formatDuration(ms) {
|
|
253
304
|
if (!Number.isFinite(ms) || ms < 0) return "";
|
|
305
|
+
|
|
254
306
|
if (ms < 1000) return `${Math.round(ms)}ms`;
|
|
307
|
+
|
|
255
308
|
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
256
309
|
const minutes = Math.floor(ms / 60000);
|
|
257
310
|
const seconds = Math.round((ms % 60000) / 1000);
|
|
311
|
+
|
|
258
312
|
return `${minutes}m${String(seconds).padStart(2, "0")}s`;
|
|
259
313
|
}
|
|
260
314
|
|
|
261
315
|
/** First meaningful line of a shell command plus a count of the hidden remainder. */
|
|
262
316
|
function summarizeCommand(raw) {
|
|
263
317
|
const lines = cleanBlockText(raw).split("\n").map((line) => line.trim()).filter(Boolean);
|
|
318
|
+
|
|
264
319
|
if (lines.length === 0) return "";
|
|
265
320
|
const first = lines[0].replace(/\s+/g, " ");
|
|
321
|
+
|
|
266
322
|
return lines.length > 1 ? `${first} …+${lines.length - 1} lines` : first;
|
|
267
323
|
}
|
|
268
324
|
|
|
269
325
|
const SHELL_TOOLS = ["bash", "exec"];
|
|
326
|
+
|
|
270
327
|
const SEARCH_TOOLS = ["snap", "search"];
|
|
271
328
|
|
|
272
329
|
function formatTarget(op, budget) {
|
|
273
330
|
if (SHELL_TOOLS.includes(op.tool)) return clampLine(summarizeCommand(op.target), budget);
|
|
274
331
|
const text = cleanInlineText(op.target);
|
|
332
|
+
|
|
275
333
|
if (!text) return "";
|
|
334
|
+
|
|
276
335
|
if (SEARCH_TOOLS.includes(op.tool)) return clampLine(text, budget);
|
|
336
|
+
|
|
277
337
|
return fitPath(text, budget);
|
|
278
338
|
}
|
|
279
339
|
|
|
280
340
|
function opMarker(theme, op, isPartial, isError) {
|
|
281
341
|
if (op.ok === false) return theme.fg("error", "×");
|
|
342
|
+
|
|
282
343
|
if (op.ok === true) return theme.fg("success", "✓");
|
|
344
|
+
|
|
283
345
|
if (isPartial) return theme.fg("dim", "·");
|
|
346
|
+
|
|
284
347
|
if (isError) return theme.fg("error", "×");
|
|
348
|
+
|
|
285
349
|
return theme.fg("success", "✓");
|
|
286
350
|
}
|
|
287
351
|
|
|
288
352
|
function opDuration(op, isPartial) {
|
|
289
353
|
if (Number.isFinite(op.ms)) return formatDuration(op.ms);
|
|
354
|
+
|
|
290
355
|
if (isPartial && op.ok === undefined && Number.isFinite(op.time)) return formatDuration(Date.now() - op.time) + "…";
|
|
356
|
+
|
|
291
357
|
return "";
|
|
292
358
|
}
|
|
293
359
|
|
|
@@ -303,54 +369,69 @@ function formatOpRow(theme, op, width, isPartial, isError) {
|
|
|
303
369
|
const duration = theme.fg("dim", durationText.padStart(DURATION_COL));
|
|
304
370
|
let prefix = `${marker} ${tool} ${duration} `;
|
|
305
371
|
let used = 2 + toolText.length + 1 + DURATION_COL + 2;
|
|
372
|
+
|
|
306
373
|
if (Number.isInteger(op.exitCode)) {
|
|
307
374
|
const exit = `exit ${op.exitCode}`;
|
|
308
375
|
prefix += theme.fg("error", exit) + " ";
|
|
309
376
|
used += exit.length + 2;
|
|
310
377
|
}
|
|
378
|
+
|
|
311
379
|
if (op.diff && isObject(op.diff)) {
|
|
312
380
|
const added = `+${op.diff.added || 0}`;
|
|
313
381
|
const removed = `-${op.diff.removed || 0}`;
|
|
314
382
|
prefix += theme.fg("toolDiffAdded", added) + theme.fg("dim", "/") + theme.fg("toolDiffRemoved", removed) + " ";
|
|
315
383
|
used += added.length + 1 + removed.length + 1;
|
|
316
384
|
}
|
|
385
|
+
|
|
317
386
|
const budget = Math.max(1, width - used);
|
|
318
387
|
const target = formatTarget(op, budget);
|
|
388
|
+
|
|
319
389
|
if (target) return prefix + theme.fg("muted", target);
|
|
390
|
+
|
|
320
391
|
if (op.ok === false && op.error) return prefix + theme.fg("error", clampLine(cleanInlineText(op.error), budget));
|
|
392
|
+
|
|
321
393
|
return prefix.trimEnd();
|
|
322
394
|
}
|
|
323
395
|
|
|
324
396
|
function traceFor(payload, context) {
|
|
325
397
|
const trace = payload?.trace || context?.state?.trace;
|
|
398
|
+
|
|
326
399
|
return Array.isArray(trace) ? trace : [];
|
|
327
400
|
}
|
|
328
401
|
|
|
329
402
|
function resultLines(value, width) {
|
|
330
403
|
const text = isString(value) ? value : formatValue(value);
|
|
404
|
+
|
|
331
405
|
return cleanBlockText(text).split("\n").flatMap(line => wrapLine(line, width));
|
|
332
406
|
}
|
|
333
407
|
|
|
334
408
|
function appendOps(lines, theme, ops, maxOps, maxDiffLines, width, isPartial, isError) {
|
|
335
409
|
for (const op of ops.slice(0, maxOps)) {
|
|
336
410
|
lines.push(formatOpRow(theme, op, width, isPartial, isError));
|
|
411
|
+
|
|
337
412
|
if (maxDiffLines === 0 || !op.diff || !isObject(op.diff)) continue;
|
|
413
|
+
|
|
338
414
|
for (const row of formatDiffRows(op.diff, theme, maxDiffLines)) lines.push(" " + row);
|
|
339
415
|
}
|
|
416
|
+
|
|
340
417
|
if (ops.length > maxOps) lines.push(theme.fg("dim", ` … ${ops.length - maxOps} more calls`));
|
|
341
418
|
}
|
|
342
419
|
|
|
343
420
|
function appendTail(lines, theme, payload, expanded, isError, width) {
|
|
344
421
|
if (isError) {
|
|
345
422
|
const error = "✗ " + (payload?.error ? cleanBlockText(payload.error) : "error");
|
|
423
|
+
|
|
346
424
|
for (const line of expanded ? resultLines(error, width) : error.split("\n")) lines.push(theme.fg("error", line));
|
|
347
425
|
}
|
|
348
426
|
else if (expanded && payload?.result !== undefined) {
|
|
349
427
|
lines.push(theme.fg("dim", "── result ──"));
|
|
428
|
+
|
|
350
429
|
for (const line of resultLines(payload.result, width)) lines.push(theme.fg("toolOutput", line));
|
|
351
430
|
}
|
|
431
|
+
|
|
352
432
|
if (expanded && payload?.logs?.length) {
|
|
353
433
|
lines.push(theme.fg("dim", "── logs ──"));
|
|
434
|
+
|
|
354
435
|
for (const log of payload.logs) for (const line of resultLines(log, width)) lines.push(theme.fg("dim", line));
|
|
355
436
|
}
|
|
356
437
|
}
|
|
@@ -365,8 +446,10 @@ function buildBodyLines(theme, width, { payload, context, expanded, isPartial, i
|
|
|
365
446
|
const ops = operationsFromTrace(visible);
|
|
366
447
|
const lines = [];
|
|
367
448
|
appendOps(lines, theme, ops, maxOps, maxDiffLines, width, isPartial, isError);
|
|
449
|
+
|
|
368
450
|
if (trace.length > maxOps) lines.push(theme.fg("dim", ` … ${trace.length - maxOps} ${isPartial ? "earlier" : "more"} calls`));
|
|
369
451
|
appendTail(lines, theme, payload, expanded, isError, width);
|
|
452
|
+
|
|
370
453
|
return { lines, opCount: trace.length };
|
|
371
454
|
}
|
|
372
455
|
|
|
@@ -374,6 +457,7 @@ function describeCard(model, opCount) {
|
|
|
374
457
|
const wall = model.payload?.wallMs != null ? formatDuration(model.payload.wallMs) : "";
|
|
375
458
|
const calls = opCount > 0 ? `${opCount} call${opCount === 1 ? "" : "s"}` : "";
|
|
376
459
|
const status = model.isError ? "failed" : model.isPartial ? "running" : calls ? "" : "complete";
|
|
460
|
+
|
|
377
461
|
return [calls, status, wall].filter(Boolean).join(" · ");
|
|
378
462
|
}
|
|
379
463
|
|
|
@@ -388,14 +472,18 @@ class UnifiedResultCard {
|
|
|
388
472
|
}
|
|
389
473
|
render(width = 80) {
|
|
390
474
|
const { theme, model } = this;
|
|
475
|
+
|
|
391
476
|
if (!theme || !model || width <= 0) return [];
|
|
477
|
+
|
|
392
478
|
if (this.cache?.width === width) return this.cache.lines;
|
|
393
479
|
const view = buildBodyLines(theme, Math.max(1, width - 4), model);
|
|
480
|
+
|
|
394
481
|
const header = novaStatusLine(theme, {
|
|
395
482
|
icon: model.isError ? "error" : model.isPartial ? "running" : undefined,
|
|
396
483
|
title: "nova",
|
|
397
484
|
description: describeCard(model, view.opCount),
|
|
398
485
|
});
|
|
486
|
+
|
|
399
487
|
// A program with no host calls has nothing to frame: one status line, no empty box.
|
|
400
488
|
const lines = view.lines.length === 0
|
|
401
489
|
? [clampLine(header, width)]
|
|
@@ -407,14 +495,18 @@ class UnifiedResultCard {
|
|
|
407
495
|
borderColor: model.isError ? "error" : "dim",
|
|
408
496
|
width,
|
|
409
497
|
})).render(width);
|
|
498
|
+
|
|
410
499
|
this.cache = { width, lines };
|
|
500
|
+
|
|
411
501
|
return lines;
|
|
412
502
|
}
|
|
413
503
|
}
|
|
414
504
|
|
|
415
505
|
function syncState(context, payload) {
|
|
416
506
|
if (!context?.state || !payload) return;
|
|
507
|
+
|
|
417
508
|
if (Array.isArray(payload.trace) && context.state.trace !== payload.trace) context.state.trace = payload.trace;
|
|
509
|
+
|
|
418
510
|
if (payload.wallMs != null && context.state.wallMs !== payload.wallMs) context.state.wallMs = payload.wallMs;
|
|
419
511
|
}
|
|
420
512
|
|
|
@@ -432,8 +524,10 @@ export function renderSupernovaResult(resultArg, optionsArg, themeArg, contextAr
|
|
|
432
524
|
const isError = result?.isError || payload?.ok === false;
|
|
433
525
|
const previous = host === "omp" ? options?.lastComponent : context?.lastComponent;
|
|
434
526
|
const comp = previous instanceof UnifiedResultCard ? previous : new UnifiedResultCard();
|
|
527
|
+
|
|
435
528
|
if (host === "omp" && options) options.lastComponent = comp;
|
|
436
529
|
else if (context) context.lastComponent = comp;
|
|
437
530
|
comp.set(theme, { payload, context, args, expanded, isPartial, isError });
|
|
531
|
+
|
|
438
532
|
return comp;
|
|
439
533
|
}
|