pi-supernova 0.6.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 +27 -3
- package/docs/CHANGELOG.md +104 -0
- package/docs/TOKEN_COSTS.md +13 -5
- package/index.js +120 -79
- package/package.json +1 -1
- 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 +28 -222
- package/src/bridge/host-bridge.js +113 -1668
- package/src/bridge/invoke.js +35 -0
- package/src/bridge/native-tools.js +1 -198
- package/src/context/evidence.js +140 -76
- package/src/context/fuzzy.js +42 -24
- package/src/context/ledger.js +43 -24
- package/src/context/outline.js +23 -18
- package/src/context/repo-index.js +206 -170
- package/src/context/search.js +157 -77
- package/src/context/snap.js +240 -136
- package/src/context/spans.js +2 -1
- package/src/context/surface.js +14 -5
- 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 +12 -8
- package/src/fs/diff.js +18 -7
- package/src/fs/json-read.js +66 -35
- package/src/fs/patch.js +94 -50
- package/src/fs/source-window.js +82 -0
- package/src/fs/text-ops.js +512 -0
- package/src/fs/vfs.js +205 -175
- package/src/fs/workspace.js +119 -108
- package/src/output/bottleneck.js +195 -116
- package/src/output/format.js +101 -67
- package/src/runtime/guest-deny-imports.js +34 -0
- package/src/runtime/guest-worker.js +289 -292
- package/src/runtime/parallel.js +97 -64
- package/src/runtime/program-batch.js +178 -69
- package/src/runtime/reference.js +15 -14
- package/src/runtime/runtime.js +327 -187
- package/src/shared/decode.js +58 -36
- package/src/ui/omp-frame.js +59 -42
- package/src/ui/render-measure.js +51 -29
- package/src/ui/render.js +241 -145
package/src/shared/decode.js
CHANGED
|
@@ -60,60 +60,82 @@ function plainFromCollection(value, seen, depth) {
|
|
|
60
60
|
return out;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (value === null || value === undefined) return value;
|
|
66
|
-
const tag = toStr.call(value);
|
|
63
|
+
function withSeen(value, seen, fn) {
|
|
64
|
+
seen.add(value);
|
|
67
65
|
|
|
68
|
-
|
|
66
|
+
try { return fn(); }
|
|
67
|
+
finally { seen.delete(value); }
|
|
68
|
+
}
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
function functionLabel(value) {
|
|
71
|
+
return "[Function" + (value.name ? " " + value.name : "") + "]";
|
|
72
|
+
}
|
|
71
73
|
|
|
72
|
-
|
|
74
|
+
function plainByTag(value, tag) {
|
|
75
|
+
if (tag === "[object String]") return { hit: true, out: value.valueOf() };
|
|
73
76
|
|
|
74
|
-
if (
|
|
77
|
+
if (tag === "[object Number]" || tag === "[object Boolean]") return { hit: true, out: value.valueOf() };
|
|
75
78
|
|
|
76
|
-
if (tag === "[object
|
|
79
|
+
if (tag === "[object BigInt]") return { hit: true, out: value.toString() + "n" };
|
|
77
80
|
|
|
78
|
-
if (
|
|
81
|
+
if (tag === "[object Symbol]") return { hit: true, out: value.toString() };
|
|
79
82
|
|
|
80
|
-
|
|
83
|
+
return { hit: false };
|
|
84
|
+
}
|
|
81
85
|
|
|
82
|
-
|
|
86
|
+
function plainAtom(value) {
|
|
87
|
+
if (value === null || value === undefined) return { hit: true, out: value };
|
|
88
|
+
const tagged = plainByTag(value, toStr.call(value));
|
|
83
89
|
|
|
84
|
-
if (
|
|
90
|
+
if (tagged.hit) return tagged;
|
|
85
91
|
|
|
86
|
-
if (value
|
|
87
|
-
const out = { name: value.name, message: value.message };
|
|
92
|
+
if (isFunction(value)) return { hit: true, out: functionLabel(value) };
|
|
88
93
|
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
return { hit: false };
|
|
95
|
+
}
|
|
91
96
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
97
|
+
function plainDate(value) {
|
|
98
|
+
return Number.isNaN(value.getTime()) ? "Invalid Date" : value.toISOString();
|
|
99
|
+
}
|
|
95
100
|
|
|
96
|
-
|
|
97
|
-
}
|
|
101
|
+
function plainHosted(value) {
|
|
102
|
+
if (value instanceof Date) return { hit: true, out: plainDate(value) };
|
|
98
103
|
|
|
99
|
-
if (value instanceof
|
|
104
|
+
if (value instanceof RegExp) return { hit: true, out: value.toString() };
|
|
100
105
|
|
|
101
|
-
if (
|
|
106
|
+
if (value instanceof Promise) return { hit: true, out: "[Promise]" };
|
|
102
107
|
|
|
103
|
-
if (
|
|
104
|
-
seen.add(value);
|
|
108
|
+
if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) return { hit: true, out: plainFromBinary(value) };
|
|
105
109
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
seen.add(value);
|
|
110
|
+
return { hit: false };
|
|
111
|
+
}
|
|
110
112
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
function plainError(value, seen, depth) {
|
|
114
|
+
const out = { name: value.name, message: value.message };
|
|
115
|
+
|
|
116
|
+
if (value.cause !== undefined) out.cause = withSeen(value, seen, () => toPlain(value.cause, seen, depth + 1));
|
|
117
|
+
|
|
118
|
+
return out;
|
|
116
119
|
}
|
|
117
120
|
|
|
118
|
-
|
|
121
|
+
/** Convert any guest value to structured-clone-safe, JSON-shaped data. */
|
|
122
|
+
export function toPlain(value, seen = new Set(), depth = 0) {
|
|
123
|
+
const atom = plainAtom(value);
|
|
124
|
+
|
|
125
|
+
if (atom.hit) return atom.out;
|
|
126
|
+
|
|
127
|
+
if (depth > MAX_DEPTH) return "[Depth]";
|
|
128
|
+
|
|
129
|
+
if (seen.has(value)) return "[Circular]";
|
|
130
|
+
|
|
131
|
+
if (value instanceof Error) return plainError(value, seen, depth);
|
|
132
|
+
const hosted = plainHosted(value);
|
|
133
|
+
|
|
134
|
+
if (hosted.hit) return hosted.out;
|
|
135
|
+
|
|
136
|
+
if (isFunction(value.toJSON)) return withSeen(value, seen, () => toPlain(value.toJSON(), seen, depth + 1));
|
|
119
137
|
|
|
138
|
+
return withSeen(value, seen, () => plainFromCollection(value, seen, depth));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ---- RPC to the host thread ----
|
package/src/ui/omp-frame.js
CHANGED
|
@@ -89,20 +89,17 @@ function wrapBg(paint) {
|
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
function
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
if (!isString(theme.bg(key, "x"))) return undefined;
|
|
99
|
-
} catch {
|
|
100
|
-
return undefined;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return wrapBg((text) => theme.bg(key, text));
|
|
92
|
+
function probeThemeBg(theme, key) {
|
|
93
|
+
try {
|
|
94
|
+
if (!isString(theme.bg(key, "x"))) return undefined;
|
|
95
|
+
} catch {
|
|
96
|
+
return undefined;
|
|
104
97
|
}
|
|
105
98
|
|
|
99
|
+
return wrapBg((text) => theme.bg(key, text));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function themeAnsiBgFn(theme, key) {
|
|
106
103
|
if (!isFunction(theme.getBgAnsi)) return undefined;
|
|
107
104
|
|
|
108
105
|
try {
|
|
@@ -116,47 +113,56 @@ function bgFnForState(theme, state) {
|
|
|
116
113
|
}
|
|
117
114
|
}
|
|
118
115
|
|
|
119
|
-
function
|
|
116
|
+
function bgFnForState(theme, state) {
|
|
117
|
+
if (!state || !theme) return undefined;
|
|
118
|
+
const key = bgKeyFor(state);
|
|
119
|
+
|
|
120
|
+
if (isFunction(theme.bg)) return probeThemeBg(theme, key);
|
|
121
|
+
|
|
122
|
+
return themeAnsiBgFn(theme, key);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function frameSectionLines(section, contentWidth, box, border, bgFn, w, paintBar) {
|
|
120
126
|
const lines = [];
|
|
121
|
-
const normalized = sections.length > 0 ? sections : [{ lines: [] }];
|
|
122
|
-
const v = box.vertical;
|
|
123
127
|
|
|
124
|
-
|
|
125
|
-
if (section.label) lines.push(paintBar(box.teeRight || "├", box.teeLeft || "┤", section.label));
|
|
128
|
+
if (section.label) lines.push(paintBar(box.teeRight || "├", box.teeLeft || "┤", section.label));
|
|
126
129
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
130
|
+
for (const raw of section.lines || []) {
|
|
131
|
+
for (const piece of String(raw).split("\n")) {
|
|
132
|
+
const body = clampLine(piece, contentWidth);
|
|
133
|
+
const pad = Math.max(0, contentWidth - measureWidth(body));
|
|
134
|
+
lines.push(padLine(`${border(box.vertical)} ${body}${" ".repeat(pad)} ${border(box.vertical)}`, w, bgFn));
|
|
133
135
|
}
|
|
134
136
|
}
|
|
135
137
|
|
|
136
138
|
return lines;
|
|
137
139
|
}
|
|
138
140
|
|
|
139
|
-
function
|
|
140
|
-
const
|
|
141
|
+
function frameBodyLines(sections, contentWidth, box, border, bgFn, w, paintBar) {
|
|
142
|
+
const normalized = sections.length > 0 ? sections : [{ lines: [] }];
|
|
143
|
+
const lines = [];
|
|
141
144
|
|
|
142
|
-
|
|
143
|
-
const rawLines = [header];
|
|
145
|
+
for (const section of normalized) lines.push(...frameSectionLines(section, contentWidth, box, border, bgFn, w, paintBar));
|
|
144
146
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
return lines;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function collapseNarrowFrame(header, sections, w) {
|
|
151
|
+
const rawLines = [header];
|
|
149
152
|
|
|
150
|
-
|
|
153
|
+
for (const section of sections) {
|
|
154
|
+
if (section.label) rawLines.push(section.label);
|
|
155
|
+
rawLines.push(...(section.lines || []));
|
|
151
156
|
}
|
|
152
157
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
158
|
+
return rawLines.flatMap((line) => line ? [clampLine(line, w)] : []);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function paintBarFor(box, border, bgFn, w) {
|
|
156
162
|
const h = box.horizontal;
|
|
157
163
|
const cap = h.repeat(3);
|
|
158
164
|
|
|
159
|
-
|
|
165
|
+
return (leftChar, rightChar, label) => {
|
|
160
166
|
const left = `${leftChar}${cap}`;
|
|
161
167
|
const right = rightChar;
|
|
162
168
|
|
|
@@ -173,14 +179,25 @@ function renderPortableFrame(theme, { header, sections = [], state = "pending",
|
|
|
173
179
|
|
|
174
180
|
return padLine(`${border(left)}${trimmed}${border(h.repeat(fill))}${border(right)}`, w, bgFn);
|
|
175
181
|
};
|
|
182
|
+
}
|
|
176
183
|
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
lines.push(paintBar(box.topLeft, box.topRight, header));
|
|
180
|
-
lines.push(...frameBodyLines(sections, contentWidth, box, border, bgFn, w, paintBar));
|
|
181
|
-
lines.push(paintBar(box.bottomLeft, box.bottomRight, null));
|
|
184
|
+
function renderPortableFrame(theme, { header, sections = [], state = "pending", borderColor, width, paintBg = true }) {
|
|
185
|
+
const w = Math.max(1, width | 0);
|
|
182
186
|
|
|
183
|
-
return
|
|
187
|
+
if (w < 8) return collapseNarrowFrame(header, sections, w);
|
|
188
|
+
const box = boxOf(theme);
|
|
189
|
+
const border = borderPaint(theme, state, borderColor);
|
|
190
|
+
// Hosts that tint the whole tool block themselves (OMP's contentBox) must
|
|
191
|
+
// get unpainted rows: a second bg wrap here would clear the outer bg with
|
|
192
|
+
// \x1b[49m and strand the host's right pad as an unpainted bar.
|
|
193
|
+
const bgFn = paintBg ? bgFnForState(theme, state) : undefined;
|
|
194
|
+
const paintBar = paintBarFor(box, border, bgFn, w);
|
|
195
|
+
|
|
196
|
+
return [
|
|
197
|
+
paintBar(box.topLeft, box.topRight, header),
|
|
198
|
+
...frameBodyLines(sections, Math.max(1, w - 2 - 2), box, border, bgFn, w, paintBar),
|
|
199
|
+
paintBar(box.bottomLeft, box.bottomRight, null),
|
|
200
|
+
];
|
|
184
201
|
}
|
|
185
202
|
|
|
186
203
|
function createPortableFramedComponent(theme, build) {
|
package/src/ui/render-measure.js
CHANGED
|
@@ -11,6 +11,27 @@ let cachedWidthChars = 0;
|
|
|
11
11
|
|
|
12
12
|
const MAX_WIDTH_CACHE_CHARS = 512_000;
|
|
13
13
|
|
|
14
|
+
function asciiWidth(plain, normalized) {
|
|
15
|
+
return /^[\x20-\x7e\u2500-\u257f\u00b7\u00d7\u2026\u2713\u2717]*$/.test(plain)
|
|
16
|
+
? plain.length
|
|
17
|
+
: stringWidth(normalized);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function rememberWidth(raw, width) {
|
|
21
|
+
if (raw.length > 4096) return width;
|
|
22
|
+
|
|
23
|
+
while (widthCache.size >= 4096 || cachedWidthChars + raw.length > MAX_WIDTH_CACHE_CHARS) {
|
|
24
|
+
const oldest = widthCache.keys().next().value;
|
|
25
|
+
widthCache.delete(oldest);
|
|
26
|
+
cachedWidthChars -= oldest.length;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
widthCache.set(raw, width);
|
|
30
|
+
cachedWidthChars += raw.length;
|
|
31
|
+
|
|
32
|
+
return width;
|
|
33
|
+
}
|
|
34
|
+
|
|
14
35
|
export function measureWidth(text) {
|
|
15
36
|
const raw = String(text ?? "");
|
|
16
37
|
const cached = widthCache.get(raw);
|
|
@@ -22,24 +43,9 @@ export function measureWidth(text) {
|
|
|
22
43
|
|
|
23
44
|
// ASCII and these single-column chrome glyphs need no Unicode segmentation.
|
|
24
45
|
// Any other character/control/escape sequence uses the full oracle.
|
|
25
|
-
const width = /^[\x20-\x7e\u2500-\u257f\u00b7\u00d7\u2026\u2713\u2717]*$/.test(plain)
|
|
26
|
-
? plain.length
|
|
27
|
-
: stringWidth(normalized);
|
|
28
|
-
|
|
29
46
|
// Cache immutable text only, never host/theme/result objects. Bound both
|
|
30
47
|
// bookkeeping and retained text; unusually long lines bypass retention.
|
|
31
|
-
|
|
32
|
-
while (widthCache.size >= 4096 || cachedWidthChars + raw.length > MAX_WIDTH_CACHE_CHARS) {
|
|
33
|
-
const oldest = widthCache.keys().next().value;
|
|
34
|
-
widthCache.delete(oldest);
|
|
35
|
-
cachedWidthChars -= oldest.length;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
widthCache.set(raw, width);
|
|
39
|
-
cachedWidthChars += raw.length;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return width;
|
|
48
|
+
return rememberWidth(raw, asciiWidth(plain, normalized));
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
function takePrefix(text, width) {
|
|
@@ -76,6 +82,24 @@ export function clampLine(line, width) {
|
|
|
76
82
|
return hardTruncate(line, width);
|
|
77
83
|
}
|
|
78
84
|
|
|
85
|
+
function pushWrapSegment(out, current, columns, segment, size, width) {
|
|
86
|
+
if (columns + size > width && current.text) {
|
|
87
|
+
out.push(current.text);
|
|
88
|
+
current.text = "";
|
|
89
|
+
columns = 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (size > width) {
|
|
93
|
+
out.push(ELLIPSIS);
|
|
94
|
+
|
|
95
|
+
return columns;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
current.text += segment;
|
|
99
|
+
|
|
100
|
+
return columns + size;
|
|
101
|
+
}
|
|
102
|
+
|
|
79
103
|
/** Wrap complete, already-sanitized result text without splitting graphemes. */
|
|
80
104
|
export function wrapLine(line, width) {
|
|
81
105
|
if (width <= 0) return [];
|
|
@@ -83,33 +107,31 @@ export function wrapLine(line, width) {
|
|
|
83
107
|
|
|
84
108
|
if (measureWidth(text) <= width) return [text];
|
|
85
109
|
const out = [];
|
|
86
|
-
|
|
110
|
+
const current = { text: "" };
|
|
87
111
|
let columns = 0;
|
|
88
112
|
|
|
89
113
|
for (const { segment } of segmenter.segment(text)) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (columns + size > width && current) { out.push(current); current = ""; columns = 0; }
|
|
93
|
-
|
|
94
|
-
if (size > width) { out.push(ELLIPSIS); continue; }
|
|
95
|
-
|
|
96
|
-
current += segment;
|
|
97
|
-
columns += size;
|
|
114
|
+
columns = pushWrapSegment(out, current, columns, segment, measureWidth(segment), width);
|
|
98
115
|
}
|
|
99
116
|
|
|
100
|
-
if (current) out.push(current);
|
|
117
|
+
if (current.text) out.push(current.text);
|
|
101
118
|
|
|
102
119
|
return out;
|
|
103
120
|
}
|
|
104
121
|
|
|
122
|
+
function shortenedPath(text) {
|
|
123
|
+
const parts = text.split("/").filter(Boolean);
|
|
124
|
+
const base = parts.at(-1) ?? text;
|
|
125
|
+
|
|
126
|
+
return { base, suffix: parts.length > 1 ? "…/" + base : base };
|
|
127
|
+
}
|
|
128
|
+
|
|
105
129
|
export function fitPath(pathText, budget) {
|
|
106
130
|
const width = Math.max(0, Math.floor(budget));
|
|
107
131
|
const text = String(pathText ?? "").replace(/\\/g, "/");
|
|
108
132
|
|
|
109
133
|
if (measureWidth(text) <= width) return text;
|
|
110
|
-
const
|
|
111
|
-
const base = parts.at(-1) ?? text;
|
|
112
|
-
const suffix = parts.length > 1 ? "…/" + base : base;
|
|
134
|
+
const { base, suffix } = shortenedPath(text);
|
|
113
135
|
|
|
114
136
|
return measureWidth(suffix) <= width ? suffix : hardTruncate(base, width);
|
|
115
137
|
}
|