pi-supernova 0.5.0 → 0.7.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 +97 -11
- package/docs/CHANGELOG.md +150 -0
- package/docs/TOKEN_COSTS.md +71 -29
- package/index.js +126 -82
- package/package.json +2 -2
- package/src/adapters/bash.js +73 -0
- package/src/adapters/edit.js +249 -0
- package/src/adapters/errors.js +31 -0
- package/src/adapters/index.js +31 -0
- package/src/adapters/list.js +102 -0
- package/src/adapters/read.js +805 -0
- package/src/adapters/refs.js +41 -0
- package/src/adapters/write.js +96 -0
- package/src/bridge/catalog.js +30 -220
- package/src/bridge/host-bridge.js +142 -1032
- package/src/bridge/invoke.js +35 -0
- package/src/bridge/native-tools.js +1 -188
- package/src/context/evidence.js +142 -70
- package/src/context/fuzzy.js +61 -22
- package/src/context/ledger.js +43 -24
- package/src/context/outline.js +26 -12
- package/src/context/repo-index.js +242 -71
- package/src/context/search.js +189 -56
- package/src/context/snap.js +306 -150
- package/src/context/spans.js +2 -1
- package/src/context/surface.js +29 -14
- package/src/contract/bash.js +31 -0
- package/src/contract/edit.js +95 -0
- package/src/contract/read.js +220 -0
- package/src/fs/check.js +19 -7
- package/src/fs/diff.js +18 -7
- package/src/fs/json-read.js +66 -35
- package/src/fs/patch.js +97 -51
- package/src/fs/source-window.js +82 -0
- package/src/fs/text-ops.js +512 -0
- package/src/fs/vfs.js +289 -162
- package/src/fs/workspace.js +122 -105
- package/src/output/bottleneck.js +211 -107
- package/src/output/format.js +112 -63
- package/src/runtime/guest-deny-imports.js +34 -0
- package/src/runtime/guest-worker.js +306 -213
- package/src/runtime/parallel.js +99 -63
- package/src/runtime/program-batch.js +189 -69
- package/src/runtime/program-file.js +6 -3
- package/src/runtime/reference.js +13 -12
- package/src/runtime/runtime.js +327 -176
- package/src/shared/decode.js +61 -27
- package/src/ui/omp-frame.js +70 -46
- package/src/ui/render-measure.js +51 -29
- package/src/ui/render.js +242 -146
package/src/ui/render.js
CHANGED
|
@@ -18,25 +18,31 @@ import { formatValue } from "../output/format.js";
|
|
|
18
18
|
|
|
19
19
|
export { measureWidth, hardTruncate, clampLine };
|
|
20
20
|
|
|
21
|
+
function diffGut(theme, item) {
|
|
22
|
+
const num = item.lineNum || 0;
|
|
23
|
+
|
|
24
|
+
if (item.type === "remove") {
|
|
25
|
+
const gut = theme.fg("toolDiffRemoved", `-${num}`.padStart(5));
|
|
26
|
+
|
|
27
|
+
return `${gut}${theme.fg("borderMuted", " │ ")}${theme.fg("toolDiffRemoved", `- ${cleanInlineText(item.text)}`)}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (item.type === "add") {
|
|
31
|
+
const gut = theme.fg("toolDiffAdded", `+${num}`.padStart(5));
|
|
32
|
+
|
|
33
|
+
return `${gut}${theme.fg("borderMuted", " │ ")}${theme.fg("toolDiffAdded", `+ ${cleanInlineText(item.text)}`)}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const gut = theme.fg("dim", ` ${num}`.padStart(5));
|
|
37
|
+
|
|
38
|
+
return `${gut}${theme.fg("borderMuted", " │ ")}${theme.fg("toolDiffContext", ` ${cleanInlineText(item.text)}`)}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
21
41
|
function formatDiffRows(diff, theme, maxShown = 6) {
|
|
22
42
|
if (!diff || !Array.isArray(diff.lines) || diff.lines.length === 0) return [];
|
|
23
43
|
const body = [];
|
|
24
44
|
|
|
25
|
-
for (const item of diff.lines.slice(0, maxShown))
|
|
26
|
-
const num = item.lineNum || 0;
|
|
27
|
-
|
|
28
|
-
if (item.type === "remove") {
|
|
29
|
-
const gut = theme.fg("toolDiffRemoved", `-${num}`.padStart(5));
|
|
30
|
-
body.push(`${gut}${theme.fg("borderMuted", " │ ")}${theme.fg("toolDiffRemoved", `- ${cleanInlineText(item.text)}`)}`);
|
|
31
|
-
} else if (item.type === "add") {
|
|
32
|
-
const gut = theme.fg("toolDiffAdded", `+${num}`.padStart(5));
|
|
33
|
-
body.push(`${gut}${theme.fg("borderMuted", " │ ")}${theme.fg("toolDiffAdded", `+ ${cleanInlineText(item.text)}`)}`);
|
|
34
|
-
} else {
|
|
35
|
-
const gut = theme.fg("dim", ` ${num}`.padStart(5));
|
|
36
|
-
body.push(`${gut}${theme.fg("borderMuted", " │ ")}${theme.fg("toolDiffContext", ` ${cleanInlineText(item.text)}`)}`);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
45
|
+
for (const item of diff.lines.slice(0, maxShown)) body.push(diffGut(theme, item));
|
|
40
46
|
const displayLineCount = Number.isInteger(diff.displayLineCount) ? diff.displayLineCount : diff.lines.length;
|
|
41
47
|
|
|
42
48
|
if (displayLineCount > maxShown) {
|
|
@@ -128,54 +134,71 @@ function contextFrom(opts, ctxOrArgs) {
|
|
|
128
134
|
return { state: opts.state, lastComponent: opts.lastComponent };
|
|
129
135
|
}
|
|
130
136
|
|
|
137
|
+
function isRenderContext(value) {
|
|
138
|
+
return "lastComponent" in value || "invalidate" in value;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function isToolArgs(value) {
|
|
142
|
+
return "code" in value || "file" in value || "programs" in value || "timeoutMs" in value;
|
|
143
|
+
}
|
|
144
|
+
|
|
131
145
|
function detectResultHost(options, ctxOrArgs) {
|
|
132
146
|
if (isTheme(options)) return "pi";
|
|
133
147
|
|
|
134
148
|
if (!isObject(ctxOrArgs)) return "pi";
|
|
135
149
|
|
|
136
|
-
if (
|
|
150
|
+
if (isRenderContext(ctxOrArgs)) return "pi";
|
|
137
151
|
|
|
138
|
-
if (
|
|
152
|
+
if (isToolArgs(ctxOrArgs)) return "omp";
|
|
139
153
|
|
|
140
154
|
return "pi";
|
|
141
155
|
}
|
|
142
156
|
|
|
143
|
-
function
|
|
144
|
-
if (
|
|
145
|
-
const opts = isObject(options) ? options : {};
|
|
146
|
-
const context = contextFrom(opts, ctxOrArgs);
|
|
157
|
+
function ensureState(context) {
|
|
158
|
+
if (!isObject(context.state)) context.state = {};
|
|
147
159
|
|
|
148
|
-
|
|
160
|
+
return context;
|
|
161
|
+
}
|
|
149
162
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
isPartial: !!opts.isPartial,
|
|
154
|
-
theme: themeOrCtx,
|
|
155
|
-
context,
|
|
156
|
-
args: ctxOrArgs?.code || ctxOrArgs?.file || ctxOrArgs?.programs ? ctxOrArgs : context.args,
|
|
157
|
-
host: detectResultHost(options, ctxOrArgs),
|
|
158
|
-
options: opts,
|
|
159
|
-
};
|
|
160
|
-
}
|
|
163
|
+
function resultArgs(ctxOrArgs, context) {
|
|
164
|
+
return ctxOrArgs?.code || ctxOrArgs?.file || ctxOrArgs?.programs ? ctxOrArgs : context.args;
|
|
165
|
+
}
|
|
161
166
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
167
|
+
function piResultArgs(result, options, theme, ctxOrArgs) {
|
|
168
|
+
const opts = isObject(options) ? options : {};
|
|
169
|
+
const context = ensureState(contextFrom(opts, ctxOrArgs));
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
result,
|
|
173
|
+
expanded: !!opts.expanded,
|
|
174
|
+
isPartial: !!opts.isPartial,
|
|
175
|
+
theme,
|
|
176
|
+
context,
|
|
177
|
+
args: resultArgs(ctxOrArgs, context),
|
|
178
|
+
host: detectResultHost(options, ctxOrArgs),
|
|
179
|
+
options: opts,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
165
182
|
|
|
166
|
-
|
|
183
|
+
function oddballResultArgs(result, theme, themeOrCtx) {
|
|
184
|
+
const context = ensureState(isObject(themeOrCtx) ? themeOrCtx : {});
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
result,
|
|
188
|
+
expanded: !!context.expanded,
|
|
189
|
+
isPartial: !!context.isPartial,
|
|
190
|
+
theme,
|
|
191
|
+
context,
|
|
192
|
+
args: context.args,
|
|
193
|
+
host: "pi",
|
|
194
|
+
options: {},
|
|
195
|
+
};
|
|
196
|
+
}
|
|
167
197
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
theme: options,
|
|
173
|
-
context,
|
|
174
|
-
args: context.args,
|
|
175
|
-
host: "pi",
|
|
176
|
-
options: {},
|
|
177
|
-
};
|
|
178
|
-
}
|
|
198
|
+
function normalizeResultRenderArgs(result, options, themeOrCtx, ctxOrArgs) {
|
|
199
|
+
if (isTheme(themeOrCtx)) return piResultArgs(result, options, themeOrCtx, ctxOrArgs);
|
|
200
|
+
|
|
201
|
+
if (isTheme(options)) return oddballResultArgs(result, options, themeOrCtx);
|
|
179
202
|
|
|
180
203
|
throw new Error("supernova renderResult: theme missing (expected Pi or OMP signature)");
|
|
181
204
|
}
|
|
@@ -233,47 +256,57 @@ let cachedDiffChars = 0;
|
|
|
233
256
|
|
|
234
257
|
const MAX_CACHED_DIFF_CHARS = 1_000_000;
|
|
235
258
|
|
|
236
|
-
function
|
|
237
|
-
|
|
259
|
+
function rememberDiff(diff, parsed) {
|
|
260
|
+
if (diff.length > MAX_CACHED_DIFF_CHARS) return parsed;
|
|
238
261
|
|
|
239
|
-
|
|
262
|
+
while (textDiffCache.size >= 24 || cachedDiffChars + diff.length > MAX_CACHED_DIFF_CHARS) {
|
|
263
|
+
const oldest = textDiffCache.keys().next().value;
|
|
264
|
+
textDiffCache.delete(oldest);
|
|
265
|
+
cachedDiffChars -= oldest.length;
|
|
266
|
+
}
|
|
240
267
|
|
|
241
|
-
|
|
242
|
-
|
|
268
|
+
textDiffCache.set(diff, parsed);
|
|
269
|
+
cachedDiffChars += diff.length;
|
|
243
270
|
|
|
244
|
-
|
|
271
|
+
return parsed;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function tallyDiffLine(parsed, counts) {
|
|
275
|
+
if (parsed.type === "add") counts.added += 1;
|
|
276
|
+
else if (parsed.type === "remove") counts.removed += 1;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function parseTraceDiffText(diff) {
|
|
245
280
|
const lines = [];
|
|
246
|
-
|
|
247
|
-
let added = 0;
|
|
248
|
-
let removed = 0;
|
|
281
|
+
const counts = { added: 0, removed: 0, displayLineCount: 0 };
|
|
249
282
|
|
|
250
283
|
for (const rawLine of cleanBlockText(diff).split("\n")) {
|
|
251
284
|
const parsed = parseDiffLine(rawLine);
|
|
252
285
|
|
|
253
286
|
if (!parsed) continue;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
else if (parsed.type === "remove") removed += 1;
|
|
257
|
-
displayLineCount++;
|
|
287
|
+
tallyDiffLine(parsed, counts);
|
|
288
|
+
counts.displayLineCount++;
|
|
258
289
|
|
|
259
290
|
if (lines.length < 24) lines.push(parsed);
|
|
260
291
|
}
|
|
261
292
|
|
|
262
293
|
if (lines.length === 0) return undefined;
|
|
263
|
-
const parsed = { added, removed, lines, displayLineCount };
|
|
264
294
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const oldest = textDiffCache.keys().next().value;
|
|
268
|
-
textDiffCache.delete(oldest);
|
|
269
|
-
cachedDiffChars -= oldest.length;
|
|
270
|
-
}
|
|
295
|
+
return { added: counts.added, removed: counts.removed, lines, displayLineCount: counts.displayLineCount };
|
|
296
|
+
}
|
|
271
297
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
298
|
+
function normalizeTraceDiff(item) {
|
|
299
|
+
const diff = item?.diff;
|
|
275
300
|
|
|
276
|
-
return
|
|
301
|
+
if (isObject(diff)) return diff;
|
|
302
|
+
|
|
303
|
+
if (!isString(diff) || !diff.trim()) return undefined;
|
|
304
|
+
const cached = textDiffCache.get(diff);
|
|
305
|
+
|
|
306
|
+
if (cached) return cached;
|
|
307
|
+
const parsed = parseTraceDiffText(diff);
|
|
308
|
+
|
|
309
|
+
return parsed ? rememberDiff(diff, parsed) : undefined;
|
|
277
310
|
}
|
|
278
311
|
|
|
279
312
|
function operationsFromTrace(trace) {
|
|
@@ -357,33 +390,25 @@ function opDuration(op, isPartial) {
|
|
|
357
390
|
return "";
|
|
358
391
|
}
|
|
359
392
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
*/
|
|
364
|
-
function formatOpRow(theme, op, width, isPartial, isError) {
|
|
365
|
-
const marker = opMarker(theme, op, isPartial, isError);
|
|
366
|
-
const toolText = op.tool.padEnd(TOOL_COL);
|
|
367
|
-
const tool = theme.fg("syntaxFunction", toolText);
|
|
368
|
-
const durationText = opDuration(op, isPartial);
|
|
369
|
-
const duration = theme.fg("dim", durationText.padStart(DURATION_COL));
|
|
370
|
-
let prefix = `${marker} ${tool} ${duration} `;
|
|
371
|
-
let used = 2 + toolText.length + 1 + DURATION_COL + 2;
|
|
393
|
+
function appendExit(theme, op) {
|
|
394
|
+
if (!Number.isInteger(op.exitCode)) return { text: "", width: 0 };
|
|
395
|
+
const exit = `exit ${op.exitCode}`;
|
|
372
396
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
prefix += theme.fg("error", exit) + " ";
|
|
376
|
-
used += exit.length + 2;
|
|
377
|
-
}
|
|
397
|
+
return { text: theme.fg("error", exit) + " ", width: exit.length + 2 };
|
|
398
|
+
}
|
|
378
399
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
used += added.length + 1 + removed.length + 1;
|
|
384
|
-
}
|
|
400
|
+
function appendDiffCounts(theme, op) {
|
|
401
|
+
if (!(op.diff && isObject(op.diff))) return { text: "", width: 0 };
|
|
402
|
+
const added = `+${op.diff.added || 0}`;
|
|
403
|
+
const removed = `-${op.diff.removed || 0}`;
|
|
385
404
|
|
|
386
|
-
|
|
405
|
+
return {
|
|
406
|
+
text: theme.fg("toolDiffAdded", added) + theme.fg("dim", "/") + theme.fg("toolDiffRemoved", removed) + " ",
|
|
407
|
+
width: added.length + 1 + removed.length + 1,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function opRowSuffix(theme, op, prefix, budget) {
|
|
387
412
|
const target = formatTarget(op, budget);
|
|
388
413
|
|
|
389
414
|
if (target) return prefix + theme.fg("muted", target);
|
|
@@ -393,6 +418,24 @@ function formatOpRow(theme, op, width, isPartial, isError) {
|
|
|
393
418
|
return prefix.trimEnd();
|
|
394
419
|
}
|
|
395
420
|
|
|
421
|
+
/**
|
|
422
|
+
* One aligned row: marker · tool · duration · [exit N] · [+a/-r] · target.
|
|
423
|
+
* Fixed columns keep a ledger of mixed calls scannable at a glance.
|
|
424
|
+
*/
|
|
425
|
+
function formatOpRow(theme, op, width, isPartial, isError) {
|
|
426
|
+
const marker = opMarker(theme, op, isPartial, isError);
|
|
427
|
+
const toolText = op.tool.slice(0, TOOL_COL).padEnd(TOOL_COL);
|
|
428
|
+
const tool = theme.fg("syntaxFunction", toolText);
|
|
429
|
+
const durationText = opDuration(op, isPartial).slice(-DURATION_COL);
|
|
430
|
+
const duration = theme.fg("dim", durationText.padStart(DURATION_COL));
|
|
431
|
+
const exit = appendExit(theme, op);
|
|
432
|
+
const counts = appendDiffCounts(theme, op);
|
|
433
|
+
const prefix = `${marker} ${tool} ${duration} ` + exit.text + counts.text;
|
|
434
|
+
const used = 2 + toolText.length + 1 + DURATION_COL + 2 + exit.width + counts.width;
|
|
435
|
+
|
|
436
|
+
return opRowSuffix(theme, op, prefix, Math.max(1, width - used));
|
|
437
|
+
}
|
|
438
|
+
|
|
396
439
|
function traceFor(payload, context) {
|
|
397
440
|
const trace = payload?.trace || context?.state?.trace;
|
|
398
441
|
|
|
@@ -417,38 +460,60 @@ function appendOps(lines, theme, ops, maxOps, maxDiffLines, width, isPartial, is
|
|
|
417
460
|
if (ops.length > maxOps) lines.push(theme.fg("dim", ` … ${ops.length - maxOps} more calls`));
|
|
418
461
|
}
|
|
419
462
|
|
|
420
|
-
function
|
|
421
|
-
|
|
422
|
-
const error = "✗ " + (payload?.error ? cleanBlockText(payload.error) : "error");
|
|
463
|
+
function appendError(lines, theme, payload, expanded, width) {
|
|
464
|
+
const error = "✗ " + (payload?.error ? cleanBlockText(payload.error) : "error");
|
|
423
465
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
else if (expanded && payload?.result !== undefined) {
|
|
427
|
-
lines.push(theme.fg("dim", "── result ──"));
|
|
466
|
+
for (const line of expanded ? resultLines(error, width) : error.split("\n")) lines.push(theme.fg("error", line));
|
|
467
|
+
}
|
|
428
468
|
|
|
429
|
-
|
|
430
|
-
|
|
469
|
+
function appendResult(lines, theme, payload, expanded, width) {
|
|
470
|
+
if (expanded) lines.push(theme.fg("dim", "── result ──"));
|
|
471
|
+
const wrapped = resultLines(payload.result, width);
|
|
472
|
+
const shown = expanded ? wrapped : wrapped.slice(0, 8);
|
|
431
473
|
|
|
432
|
-
|
|
433
|
-
lines.push(theme.fg("dim", "── logs ──"));
|
|
474
|
+
for (const line of shown) lines.push(theme.fg("toolOutput", line));
|
|
434
475
|
|
|
435
|
-
|
|
436
|
-
|
|
476
|
+
if (!expanded && wrapped.length > shown.length) lines.push(theme.fg("dim", ` … ${wrapped.length - shown.length} more result lines`));
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function appendLogs(lines, theme, payload, width) {
|
|
480
|
+
lines.push(theme.fg("dim", "── logs ──"));
|
|
481
|
+
|
|
482
|
+
for (const log of payload.logs) for (const line of resultLines(log, width)) lines.push(theme.fg("dim", line));
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function appendTail(lines, theme, payload, expanded, isError, width, previewResult) {
|
|
486
|
+
if (isError) appendError(lines, theme, payload, expanded, width);
|
|
487
|
+
else if (payload?.result !== undefined && (expanded || previewResult)) appendResult(lines, theme, payload, expanded, width);
|
|
488
|
+
|
|
489
|
+
if (expanded && payload?.logs?.length) appendLogs(lines, theme, payload, width);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function bodyLimits(expanded, isPartial) {
|
|
493
|
+
return { maxOps: expanded ? 24 : 8, maxDiffLines: expanded ? 24 : isPartial ? 0 : 8 };
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function visibleTrace(trace, maxOps, isPartial) {
|
|
497
|
+
return isPartial ? trace.slice(-maxOps) : trace.slice(0, maxOps);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function appendOverflow(lines, theme, trace, maxOps, isPartial) {
|
|
501
|
+
if (trace.length > maxOps) lines.push(theme.fg("dim", ` … ${trace.length - maxOps} ${isPartial ? "earlier" : "more"} calls`));
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function appendEmptyOps(lines, theme, ops, isError, isPartial) {
|
|
505
|
+
if (ops.length === 0 && !isError && !isPartial) lines.push(theme.fg("dim", "no adapter calls"));
|
|
437
506
|
}
|
|
438
507
|
|
|
439
508
|
function buildBodyLines(theme, width, { payload, context, expanded, isPartial, isError }) {
|
|
440
509
|
const trace = traceFor(payload, context);
|
|
441
|
-
const maxOps = expanded
|
|
442
|
-
const
|
|
443
|
-
// Select before parsing diffs: invisible history must not consume a frame.
|
|
444
|
-
// While running, show current activity rather than the first completed calls.
|
|
445
|
-
const visible = isPartial ? trace.slice(-maxOps) : trace.slice(0, maxOps);
|
|
446
|
-
const ops = operationsFromTrace(visible);
|
|
510
|
+
const { maxOps, maxDiffLines } = bodyLimits(expanded, isPartial);
|
|
511
|
+
const ops = operationsFromTrace(visibleTrace(trace, maxOps, isPartial));
|
|
447
512
|
const lines = [];
|
|
448
513
|
appendOps(lines, theme, ops, maxOps, maxDiffLines, width, isPartial, isError);
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
appendTail(lines, theme, payload, expanded, isError, width);
|
|
514
|
+
appendOverflow(lines, theme, trace, maxOps, isPartial);
|
|
515
|
+
appendEmptyOps(lines, theme, ops, isError, isPartial);
|
|
516
|
+
appendTail(lines, theme, payload, expanded, isError, width, ops.length === 0 && !isPartial);
|
|
452
517
|
|
|
453
518
|
return { lines, opCount: trace.length };
|
|
454
519
|
}
|
|
@@ -461,6 +526,43 @@ function describeCard(model, opCount) {
|
|
|
461
526
|
return [calls, status, wall].filter(Boolean).join(" · ");
|
|
462
527
|
}
|
|
463
528
|
|
|
529
|
+
function cardIcon(model) {
|
|
530
|
+
if (model.isError) return "error";
|
|
531
|
+
|
|
532
|
+
if (model.isPartial) return "running";
|
|
533
|
+
|
|
534
|
+
return undefined;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function cardChrome(model) {
|
|
538
|
+
return {
|
|
539
|
+
state: model.isError ? "error" : model.isPartial ? "pending" : "success",
|
|
540
|
+
borderColor: model.isError ? "error" : "dim",
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function renderCardLines(theme, model, width, view) {
|
|
545
|
+
const header = novaStatusLine(theme, {
|
|
546
|
+
icon: cardIcon(model),
|
|
547
|
+
title: "nova",
|
|
548
|
+
description: describeCard(model, view.opCount),
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
// Empty body: one status line, no framed box.
|
|
552
|
+
if (view.lines.length === 0) return [clampLine(header, width)];
|
|
553
|
+
const chrome = cardChrome(model);
|
|
554
|
+
|
|
555
|
+
return novaFramedBlock(theme, () => ({
|
|
556
|
+
header,
|
|
557
|
+
sections: [{ lines: view.lines }],
|
|
558
|
+
state: chrome.state,
|
|
559
|
+
// borderMuted is invisible on OMP's card background; dim matches the duration column.
|
|
560
|
+
borderColor: chrome.borderColor,
|
|
561
|
+
width,
|
|
562
|
+
paintBg: model.host !== "omp",
|
|
563
|
+
})).render(width);
|
|
564
|
+
}
|
|
565
|
+
|
|
464
566
|
class UnifiedResultCard {
|
|
465
567
|
set(theme, model) {
|
|
466
568
|
this.theme = theme;
|
|
@@ -477,25 +579,7 @@ class UnifiedResultCard {
|
|
|
477
579
|
|
|
478
580
|
if (this.cache?.width === width) return this.cache.lines;
|
|
479
581
|
const view = buildBodyLines(theme, Math.max(1, width - 4), model);
|
|
480
|
-
|
|
481
|
-
const header = novaStatusLine(theme, {
|
|
482
|
-
icon: model.isError ? "error" : model.isPartial ? "running" : undefined,
|
|
483
|
-
title: "nova",
|
|
484
|
-
description: describeCard(model, view.opCount),
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
// A program with no host calls has nothing to frame: one status line, no empty box.
|
|
488
|
-
const lines = view.lines.length === 0
|
|
489
|
-
? [clampLine(header, width)]
|
|
490
|
-
: novaFramedBlock(theme, () => ({
|
|
491
|
-
header,
|
|
492
|
-
sections: [{ lines: view.lines }],
|
|
493
|
-
state: model.isError ? "error" : model.isPartial ? "pending" : "success",
|
|
494
|
-
// borderMuted is invisible on OMP's card background; dim matches the duration column.
|
|
495
|
-
borderColor: model.isError ? "error" : "dim",
|
|
496
|
-
width,
|
|
497
|
-
})).render(width);
|
|
498
|
-
|
|
582
|
+
const lines = renderCardLines(theme, model, width, view);
|
|
499
583
|
this.cache = { width, lines };
|
|
500
584
|
|
|
501
585
|
return lines;
|
|
@@ -510,6 +594,24 @@ function syncState(context, payload) {
|
|
|
510
594
|
if (payload.wallMs != null && context.state.wallMs !== payload.wallMs) context.state.wallMs = payload.wallMs;
|
|
511
595
|
}
|
|
512
596
|
|
|
597
|
+
function errorPayload(result) {
|
|
598
|
+
return result?.isError ? { ok: false, error: result.content?.flatMap(block => block.type === "text" ? [block.text] : []).join("\n") } : undefined;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function payloadFromResult(result) {
|
|
602
|
+
return result?.details ?? errorPayload(result);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function bindResultCard(host, options, context) {
|
|
606
|
+
const previous = host === "omp" ? options?.lastComponent : context?.lastComponent;
|
|
607
|
+
const comp = previous instanceof UnifiedResultCard ? previous : new UnifiedResultCard();
|
|
608
|
+
|
|
609
|
+
if (host === "omp" && options) options.lastComponent = comp;
|
|
610
|
+
else if (context) context.lastComponent = comp;
|
|
611
|
+
|
|
612
|
+
return comp;
|
|
613
|
+
}
|
|
614
|
+
|
|
513
615
|
export function renderSupernovaResult(resultArg, optionsArg, themeArg, contextArg) {
|
|
514
616
|
const { result, expanded, isPartial, theme, context, args, options, host } = normalizeResultRenderArgs(
|
|
515
617
|
resultArg,
|
|
@@ -517,17 +619,11 @@ export function renderSupernovaResult(resultArg, optionsArg, themeArg, contextAr
|
|
|
517
619
|
themeArg,
|
|
518
620
|
contextArg,
|
|
519
621
|
);
|
|
520
|
-
|
|
521
|
-
const payload = result?.details ?? (result?.isError ? { ok: false, error: result.content?.filter(block => block.type === "text").map(block => block.text).join("\n") } : undefined);
|
|
622
|
+
const payload = payloadFromResult(result);
|
|
522
623
|
syncState(context, payload);
|
|
523
|
-
|
|
524
624
|
const isError = result?.isError || payload?.ok === false;
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
if (host === "omp" && options) options.lastComponent = comp;
|
|
529
|
-
else if (context) context.lastComponent = comp;
|
|
530
|
-
comp.set(theme, { payload, context, args, expanded, isPartial, isError });
|
|
625
|
+
const comp = bindResultCard(host, options, context);
|
|
626
|
+
comp.set(theme, { payload, context, args, expanded, isPartial, isError, host });
|
|
531
627
|
|
|
532
628
|
return comp;
|
|
533
629
|
}
|