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/shared/decode.js
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
const toStr = Object.prototype.toString;
|
|
2
2
|
|
|
3
3
|
export const isString = (v) => toStr.call(v) === "[object String]";
|
|
4
|
+
|
|
4
5
|
export const isObject = (v) => toStr.call(v) === "[object Object]";
|
|
6
|
+
|
|
5
7
|
export const isFunction = (v) => toStr.call(v) === "[object Function]" || v instanceof Function;
|
|
8
|
+
|
|
6
9
|
export const isNumber = (v) => toStr.call(v) === "[object Number]" && Number.isFinite(v);
|
|
7
10
|
|
|
8
11
|
const MAX_DEPTH = 64;
|
|
12
|
+
|
|
9
13
|
const MAX_TYPED_ARRAY = 4096;
|
|
10
14
|
|
|
11
15
|
function plainFromBinary(value) {
|
|
12
16
|
const bytes = value.byteLength;
|
|
17
|
+
|
|
13
18
|
if (value instanceof ArrayBuffer) value = new Uint8Array(value);
|
|
14
19
|
else if (value instanceof DataView) value = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
20
|
+
|
|
15
21
|
if (value.length > MAX_TYPED_ARRAY) return "[" + value.constructor.name + " " + bytes + " bytes]";
|
|
22
|
+
|
|
16
23
|
return value instanceof BigInt64Array || value instanceof BigUint64Array
|
|
17
24
|
? Array.from(value, x => x.toString() + "n")
|
|
18
25
|
: Array.from(value);
|
|
@@ -20,18 +27,25 @@ function plainFromBinary(value) {
|
|
|
20
27
|
|
|
21
28
|
function plainFromMap(value, seen, depth) {
|
|
22
29
|
const allStringKeys = [...value.keys()].every(isString);
|
|
30
|
+
|
|
23
31
|
if (!allStringKeys) return [...value].map(([k, v]) => [toPlain(k, seen, depth + 1), toPlain(v, seen, depth + 1)]);
|
|
24
32
|
const out = Object.create(null);
|
|
33
|
+
|
|
25
34
|
for (const [k, v] of value) out[k] = toPlain(v, seen, depth + 1);
|
|
35
|
+
|
|
26
36
|
return out;
|
|
27
37
|
}
|
|
28
38
|
|
|
29
39
|
function plainFromCollection(value, seen, depth) {
|
|
30
40
|
if (Array.isArray(value)) return value.map((x) => toPlain(x, seen, depth + 1));
|
|
41
|
+
|
|
31
42
|
if (value instanceof Set) return [...value].map((x) => toPlain(x, seen, depth + 1));
|
|
43
|
+
|
|
32
44
|
if (value instanceof Map) return plainFromMap(value, seen, depth);
|
|
33
45
|
const out = Object.create(null);
|
|
46
|
+
|
|
34
47
|
for (const k of Object.keys(value)) out[k] = toPlain(value[k], seen, depth + 1);
|
|
48
|
+
|
|
35
49
|
return out;
|
|
36
50
|
}
|
|
37
51
|
|
|
@@ -39,23 +53,38 @@ function plainFromCollection(value, seen, depth) {
|
|
|
39
53
|
export function toPlain(value, seen = new Set(), depth = 0) {
|
|
40
54
|
if (value === null || value === undefined) return value;
|
|
41
55
|
const tag = toStr.call(value);
|
|
56
|
+
|
|
42
57
|
if (tag === "[object String]" || tag === "[object Number]" || tag === "[object Boolean]") return value.valueOf();
|
|
58
|
+
|
|
43
59
|
if (tag === "[object BigInt]") return value.toString() + "n";
|
|
60
|
+
|
|
44
61
|
if (isFunction(value)) return "[Function" + (value.name ? " " + value.name : "") + "]";
|
|
62
|
+
|
|
45
63
|
if (tag === "[object Symbol]") return value.toString();
|
|
64
|
+
|
|
46
65
|
if (depth > MAX_DEPTH) return "[Depth]";
|
|
66
|
+
|
|
47
67
|
if (seen.has(value)) return "[Circular]";
|
|
68
|
+
|
|
48
69
|
if (value instanceof Date) return Number.isNaN(value.getTime()) ? "Invalid Date" : value.toISOString();
|
|
70
|
+
|
|
49
71
|
if (value instanceof RegExp) return value.toString();
|
|
72
|
+
|
|
50
73
|
if (value instanceof Error) {
|
|
51
74
|
const out = { name: value.name, message: value.message };
|
|
75
|
+
|
|
52
76
|
if (value.cause !== undefined) out.cause = toPlain(value.cause, seen, depth + 1);
|
|
77
|
+
|
|
53
78
|
return out;
|
|
54
79
|
}
|
|
80
|
+
|
|
55
81
|
if (value instanceof Promise) return "[Promise]";
|
|
82
|
+
|
|
56
83
|
if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) return plainFromBinary(value);
|
|
84
|
+
|
|
57
85
|
if (isFunction(value.toJSON)) return toPlain(value.toJSON(), seen, depth + 1);
|
|
58
86
|
seen.add(value);
|
|
87
|
+
|
|
59
88
|
try {
|
|
60
89
|
return plainFromCollection(value, seen, depth);
|
|
61
90
|
} finally {
|
package/src/ui/omp-frame.js
CHANGED
|
@@ -21,7 +21,9 @@ const DEFAULT_BOX = {
|
|
|
21
21
|
|
|
22
22
|
function boxOf(theme) {
|
|
23
23
|
const b = theme?.boxRound;
|
|
24
|
+
|
|
24
25
|
if (b && b.topLeft && b.horizontal && b.vertical) return b;
|
|
26
|
+
|
|
25
27
|
return DEFAULT_BOX;
|
|
26
28
|
}
|
|
27
29
|
|
|
@@ -33,6 +35,7 @@ function borderKeyFor(state) {
|
|
|
33
35
|
|
|
34
36
|
function borderPaint(theme, state, borderColor) {
|
|
35
37
|
const key = borderColor || borderKeyFor(state);
|
|
38
|
+
|
|
36
39
|
if (theme && isFunction(theme.fg)) {
|
|
37
40
|
try {
|
|
38
41
|
return (text) => theme.fg(key, text);
|
|
@@ -40,6 +43,7 @@ function borderPaint(theme, state, borderColor) {
|
|
|
40
43
|
/* fall through */
|
|
41
44
|
}
|
|
42
45
|
}
|
|
46
|
+
|
|
43
47
|
return (text) => text;
|
|
44
48
|
}
|
|
45
49
|
|
|
@@ -51,6 +55,7 @@ function statusHeader(theme, { title, description, state, icon }) {
|
|
|
51
55
|
const prefix = prefixSpec ? (theme?.fg ? theme.fg(prefixSpec[0], prefixSpec[1]) : prefixSpec[1]) : "";
|
|
52
56
|
const titleText = theme?.fg ? theme.fg("accent", title) : title;
|
|
53
57
|
const descText = description ? (theme?.fg ? theme.fg("muted", description) : description) : "";
|
|
58
|
+
|
|
54
59
|
return descText ? `${prefix}${titleText}: ${descText}` : `${prefix}${titleText}`;
|
|
55
60
|
}
|
|
56
61
|
|
|
@@ -59,6 +64,7 @@ function padLine(line, width, bgFn) {
|
|
|
59
64
|
const vis = measureWidth(line);
|
|
60
65
|
const pad = Math.max(0, w - vis);
|
|
61
66
|
const padded = line + " ".repeat(pad);
|
|
67
|
+
|
|
62
68
|
return bgFn ? bgFn(padded) : padded;
|
|
63
69
|
}
|
|
64
70
|
|
|
@@ -71,6 +77,7 @@ function bgKeyFor(state) {
|
|
|
71
77
|
function wrapBg(paint) {
|
|
72
78
|
return (text) => {
|
|
73
79
|
const out = paint(text);
|
|
80
|
+
|
|
74
81
|
return isString(out) ? out : text;
|
|
75
82
|
};
|
|
76
83
|
}
|
|
@@ -78,18 +85,24 @@ function wrapBg(paint) {
|
|
|
78
85
|
function bgFnForState(theme, state) {
|
|
79
86
|
if (!state || !theme) return undefined;
|
|
80
87
|
const key = bgKeyFor(state);
|
|
88
|
+
|
|
81
89
|
if (isFunction(theme.bg)) {
|
|
82
90
|
try {
|
|
83
91
|
if (!isString(theme.bg(key, "x"))) return undefined;
|
|
84
92
|
} catch {
|
|
85
93
|
return undefined;
|
|
86
94
|
}
|
|
95
|
+
|
|
87
96
|
return wrapBg((text) => theme.bg(key, text));
|
|
88
97
|
}
|
|
98
|
+
|
|
89
99
|
if (!isFunction(theme.getBgAnsi)) return undefined;
|
|
100
|
+
|
|
90
101
|
try {
|
|
91
102
|
const ansi = theme.getBgAnsi(key);
|
|
103
|
+
|
|
92
104
|
if (!ansi) return undefined;
|
|
105
|
+
|
|
93
106
|
return (text) => `${ansi}${text}\x1b[49m`;
|
|
94
107
|
} catch {
|
|
95
108
|
return undefined;
|
|
@@ -100,8 +113,10 @@ function frameBodyLines(sections, contentWidth, box, border, bgFn, w, paintBar)
|
|
|
100
113
|
const lines = [];
|
|
101
114
|
const normalized = sections.length > 0 ? sections : [{ lines: [] }];
|
|
102
115
|
const v = box.vertical;
|
|
116
|
+
|
|
103
117
|
for (const section of normalized) {
|
|
104
118
|
if (section.label) lines.push(paintBar(box.teeRight || "├", box.teeLeft || "┤", section.label));
|
|
119
|
+
|
|
105
120
|
for (const raw of section.lines || []) {
|
|
106
121
|
for (const piece of String(raw).split("\n")) {
|
|
107
122
|
const body = clampLine(piece, contentWidth);
|
|
@@ -110,19 +125,24 @@ function frameBodyLines(sections, contentWidth, box, border, bgFn, w, paintBar)
|
|
|
110
125
|
}
|
|
111
126
|
}
|
|
112
127
|
}
|
|
128
|
+
|
|
113
129
|
return lines;
|
|
114
130
|
}
|
|
115
131
|
|
|
116
132
|
function renderPortableFrame(theme, { header, sections = [], state = "pending", borderColor, width }) {
|
|
117
133
|
const w = Math.max(1, width | 0);
|
|
134
|
+
|
|
118
135
|
if (w < 8) {
|
|
119
136
|
const rawLines = [header];
|
|
137
|
+
|
|
120
138
|
for (const section of sections) {
|
|
121
139
|
if (section.label) rawLines.push(section.label);
|
|
122
140
|
rawLines.push(...(section.lines || []));
|
|
123
141
|
}
|
|
124
|
-
|
|
142
|
+
|
|
143
|
+
return rawLines.flatMap((line) => line ? [clampLine(line, w)] : []);
|
|
125
144
|
}
|
|
145
|
+
|
|
126
146
|
const box = boxOf(theme);
|
|
127
147
|
const border = borderPaint(theme, state, borderColor);
|
|
128
148
|
const bgFn = bgFnForState(theme, state);
|
|
@@ -132,14 +152,18 @@ function renderPortableFrame(theme, { header, sections = [], state = "pending",
|
|
|
132
152
|
const paintBar = (leftChar, rightChar, label) => {
|
|
133
153
|
const left = `${leftChar}${cap}`;
|
|
134
154
|
const right = rightChar;
|
|
155
|
+
|
|
135
156
|
if (!label) {
|
|
136
157
|
const fill = Math.max(0, w - measureWidth(left) - measureWidth(right));
|
|
158
|
+
|
|
137
159
|
return padLine(`${border(left)}${border(h.repeat(fill))}${border(right)}`, w, bgFn);
|
|
138
160
|
}
|
|
161
|
+
|
|
139
162
|
const rawLabel = ` ${label} `;
|
|
140
163
|
const maxLabel = Math.max(0, w - measureWidth(left) - measureWidth(right));
|
|
141
164
|
const trimmed = clampLine(rawLabel, maxLabel);
|
|
142
165
|
const fill = Math.max(0, w - measureWidth(left) - measureWidth(trimmed) - measureWidth(right));
|
|
166
|
+
|
|
143
167
|
return padLine(`${border(left)}${trimmed}${border(h.repeat(fill))}${border(right)}`, w, bgFn);
|
|
144
168
|
};
|
|
145
169
|
|
|
@@ -148,6 +172,7 @@ function renderPortableFrame(theme, { header, sections = [], state = "pending",
|
|
|
148
172
|
lines.push(paintBar(box.topLeft, box.topRight, header));
|
|
149
173
|
lines.push(...frameBodyLines(sections, contentWidth, box, border, bgFn, w, paintBar));
|
|
150
174
|
lines.push(paintBar(box.bottomLeft, box.bottomRight, null));
|
|
175
|
+
|
|
151
176
|
return lines;
|
|
152
177
|
}
|
|
153
178
|
|
|
@@ -155,16 +180,20 @@ function createPortableFramedComponent(theme, build) {
|
|
|
155
180
|
let cacheWidth;
|
|
156
181
|
let cacheKey;
|
|
157
182
|
let cacheLines;
|
|
183
|
+
|
|
158
184
|
return {
|
|
159
185
|
render(width) {
|
|
160
186
|
const opts = build(width);
|
|
187
|
+
|
|
161
188
|
const key = `${opts.state}|${opts.borderColor}|${opts.header}|${(opts.sections || [])
|
|
162
189
|
.map((s) => (s.lines || []).join("\n"))
|
|
163
190
|
.join("||")}`;
|
|
191
|
+
|
|
164
192
|
if (cacheLines && cacheWidth === width && cacheKey === key) return cacheLines;
|
|
165
193
|
cacheLines = renderPortableFrame(theme, opts);
|
|
166
194
|
cacheWidth = width;
|
|
167
195
|
cacheKey = key;
|
|
196
|
+
|
|
168
197
|
return cacheLines;
|
|
169
198
|
},
|
|
170
199
|
invalidate() {
|
package/src/ui/render-measure.js
CHANGED
|
@@ -2,24 +2,30 @@ import { stripVTControlCharacters } from "node:util";
|
|
|
2
2
|
import stringWidth from "string-width";
|
|
3
3
|
|
|
4
4
|
const ELLIPSIS = "…";
|
|
5
|
+
|
|
5
6
|
const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
6
7
|
|
|
7
8
|
const widthCache = new Map();
|
|
9
|
+
|
|
8
10
|
let cachedWidthChars = 0;
|
|
11
|
+
|
|
9
12
|
const MAX_WIDTH_CACHE_CHARS = 512_000;
|
|
10
13
|
|
|
11
14
|
export function measureWidth(text) {
|
|
12
15
|
const raw = String(text ?? "");
|
|
13
16
|
const cached = widthCache.get(raw);
|
|
17
|
+
|
|
14
18
|
if (cached !== undefined) return cached;
|
|
15
19
|
const normalized = raw.replace(/\t/g, " ");
|
|
16
20
|
// eslint-disable-next-line no-control-regex -- intentional ANSI SGR recognition
|
|
17
21
|
const plain = normalized.replace(/\x1b\[(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?m/g, "");
|
|
22
|
+
|
|
18
23
|
// ASCII and these single-column chrome glyphs need no Unicode segmentation.
|
|
19
24
|
// Any other character/control/escape sequence uses the full oracle.
|
|
20
25
|
const width = /^[\x20-\x7e\u2500-\u257f\u00b7\u00d7\u2026\u2713\u2717]*$/.test(plain)
|
|
21
26
|
? plain.length
|
|
22
27
|
: stringWidth(normalized);
|
|
28
|
+
|
|
23
29
|
// Cache immutable text only, never host/theme/result objects. Bound both
|
|
24
30
|
// bookkeeping and retained text; unusually long lines bypass retention.
|
|
25
31
|
if (raw.length <= 4096) {
|
|
@@ -28,32 +34,41 @@ export function measureWidth(text) {
|
|
|
28
34
|
widthCache.delete(oldest);
|
|
29
35
|
cachedWidthChars -= oldest.length;
|
|
30
36
|
}
|
|
37
|
+
|
|
31
38
|
widthCache.set(raw, width);
|
|
32
39
|
cachedWidthChars += raw.length;
|
|
33
40
|
}
|
|
41
|
+
|
|
34
42
|
return width;
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
function takePrefix(text, width) {
|
|
38
46
|
let end = 0;
|
|
39
47
|
let columns = 0;
|
|
48
|
+
|
|
40
49
|
for (const { segment, index } of segmenter.segment(text)) {
|
|
41
50
|
const next = measureWidth(segment);
|
|
51
|
+
|
|
42
52
|
if (columns + next > width) break;
|
|
43
53
|
columns += next;
|
|
44
54
|
end = index + segment.length;
|
|
45
55
|
}
|
|
56
|
+
|
|
46
57
|
return text.slice(0, end);
|
|
47
58
|
}
|
|
48
59
|
|
|
49
60
|
export function hardTruncate(text, maxWidth, ellipsis = ELLIPSIS) {
|
|
50
61
|
const width = Math.max(0, Math.floor(maxWidth));
|
|
62
|
+
|
|
51
63
|
if (!width) return "";
|
|
52
64
|
const raw = String(text ?? "").replace(/\t/g, " ");
|
|
65
|
+
|
|
53
66
|
if (measureWidth(raw) <= width) return raw;
|
|
54
67
|
const suffix = stripVTControlCharacters(String(ellipsis));
|
|
55
68
|
const suffixWidth = measureWidth(suffix);
|
|
69
|
+
|
|
56
70
|
if (suffixWidth >= width) return takePrefix(suffix, width);
|
|
71
|
+
|
|
57
72
|
return takePrefix(stripVTControlCharacters(raw), width - suffixWidth) + suffix;
|
|
58
73
|
}
|
|
59
74
|
|
|
@@ -65,27 +80,36 @@ export function clampLine(line, width) {
|
|
|
65
80
|
export function wrapLine(line, width) {
|
|
66
81
|
if (width <= 0) return [];
|
|
67
82
|
const text = String(line).replace(/\t/g, " ");
|
|
83
|
+
|
|
68
84
|
if (measureWidth(text) <= width) return [text];
|
|
69
85
|
const out = [];
|
|
70
86
|
let current = "";
|
|
71
87
|
let columns = 0;
|
|
88
|
+
|
|
72
89
|
for (const { segment } of segmenter.segment(text)) {
|
|
73
90
|
const size = measureWidth(segment);
|
|
91
|
+
|
|
74
92
|
if (columns + size > width && current) { out.push(current); current = ""; columns = 0; }
|
|
93
|
+
|
|
75
94
|
if (size > width) { out.push(ELLIPSIS); continue; }
|
|
95
|
+
|
|
76
96
|
current += segment;
|
|
77
97
|
columns += size;
|
|
78
98
|
}
|
|
99
|
+
|
|
79
100
|
if (current) out.push(current);
|
|
101
|
+
|
|
80
102
|
return out;
|
|
81
103
|
}
|
|
82
104
|
|
|
83
105
|
export function fitPath(pathText, budget) {
|
|
84
106
|
const width = Math.max(0, Math.floor(budget));
|
|
85
107
|
const text = String(pathText ?? "").replace(/\\/g, "/");
|
|
108
|
+
|
|
86
109
|
if (measureWidth(text) <= width) return text;
|
|
87
110
|
const parts = text.split("/").filter(Boolean);
|
|
88
111
|
const base = parts.at(-1) ?? text;
|
|
89
112
|
const suffix = parts.length > 1 ? "…/" + base : base;
|
|
113
|
+
|
|
90
114
|
return measureWidth(suffix) <= width ? suffix : hardTruncate(base, width);
|
|
91
115
|
}
|