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/output/format.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { isString, isObject } from "../shared/decode.js";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
function normalizeText(text) {
|
|
4
|
+
return isString(text) ? text : String(text ?? "");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function charLimit(maxChars, length) {
|
|
5
8
|
const numericLimit = Number(maxChars);
|
|
6
|
-
const limit = Number.isFinite(numericLimit) ? Math.max(0, Math.floor(numericLimit)) : numericLimit === Infinity ? normalized.length : 0;
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
return Number.isFinite(numericLimit) ? Math.max(0, Math.floor(numericLimit)) : numericLimit === Infinity ? length : 0;
|
|
11
|
+
}
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
truncated: true,
|
|
14
|
-
originalChars: normalized.length,
|
|
15
|
-
};
|
|
16
|
-
}
|
|
13
|
+
function truncatedSlice(text, end) {
|
|
14
|
+
return { text: text.slice(0, headEnd(text, end)), truncated: true, originalChars: text.length };
|
|
15
|
+
}
|
|
17
16
|
|
|
17
|
+
function truncateHeadTail(normalized, limit, label) {
|
|
18
18
|
let head = headEnd(normalized, Math.floor(limit * 0.7));
|
|
19
19
|
let tail = 0;
|
|
20
20
|
let marker = "";
|
|
@@ -23,7 +23,7 @@ export function truncateChars(text, maxChars, label = "value") {
|
|
|
23
23
|
const omitted = normalized.length - head - tail;
|
|
24
24
|
marker = "\n…[" + label + " truncated " + omitted + " chars]…\n";
|
|
25
25
|
|
|
26
|
-
if (marker.length > limit) return
|
|
26
|
+
if (marker.length > limit) return truncatedSlice(normalized, limit);
|
|
27
27
|
const budget = limit - marker.length;
|
|
28
28
|
const nextHead = headEnd(normalized, Math.min(head, budget));
|
|
29
29
|
const nextTail = normalized.length - tailStartIndex(normalized, Math.max(0, budget - nextHead));
|
|
@@ -36,6 +36,17 @@ export function truncateChars(text, maxChars, label = "value") {
|
|
|
36
36
|
return { text: normalized.slice(0, head) + marker + normalized.slice(normalized.length - tail), truncated: true, originalChars: normalized.length };
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
export function truncateChars(text, maxChars, label = "value") {
|
|
40
|
+
const normalized = normalizeText(text);
|
|
41
|
+
const limit = charLimit(maxChars, normalized.length);
|
|
42
|
+
|
|
43
|
+
if (normalized.length <= limit) return { text: normalized, truncated: false };
|
|
44
|
+
|
|
45
|
+
if (limit <= 100) return truncatedSlice(normalized, limit);
|
|
46
|
+
|
|
47
|
+
return truncateHeadTail(normalized, limit, label);
|
|
48
|
+
}
|
|
49
|
+
|
|
39
50
|
// Lone surrogates in a tool result make the message invalid UTF-8 at the API
|
|
40
51
|
// boundary, so a cut must never split a surrogate pair.
|
|
41
52
|
function headEnd(text, end) {
|
|
@@ -90,35 +101,35 @@ export function formatBoundedStringArray(values, budget) {
|
|
|
90
101
|
return out;
|
|
91
102
|
}
|
|
92
103
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (Array.isArray(value) && value.length && hasWellFormedStrings(value) && value.some(text => text.includes("\n"))) {
|
|
98
|
-
const raw = "strings[" + value.length + "]\n" + value.map((text, i) => "[" + i + "] " + text.length + " UTF-16 units\n" + text + "\n").join("");
|
|
99
|
-
const escapedSize = value.reduce((sum, text) => sum + JSON.stringify(text).length, value.length + 1);
|
|
104
|
+
function formatRawStringArray(value) {
|
|
105
|
+
if (!(Array.isArray(value) && value.length && hasWellFormedStrings(value) && value.some(text => text.includes("\n")))) return null;
|
|
106
|
+
const raw = "strings[" + value.length + "]\n" + value.map((text, i) => "[" + i + "] " + text.length + " UTF-16 units\n" + text + "\n").join("");
|
|
107
|
+
const escapedSize = value.reduce((sum, text) => sum + JSON.stringify(text).length, value.length + 1);
|
|
100
108
|
|
|
101
|
-
|
|
102
|
-
|
|
109
|
+
return raw.length < escapedSize ? raw : null;
|
|
110
|
+
}
|
|
103
111
|
|
|
104
|
-
|
|
105
|
-
|
|
112
|
+
function shouldFrameRaw(input) {
|
|
113
|
+
return isString(input) && input.includes("\n") && !hasUnpairedSurrogate(input) && JSON.stringify(input).length - input.length > 64;
|
|
114
|
+
}
|
|
106
115
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
116
|
+
function visitRawStrings(input, strings) {
|
|
117
|
+
if (shouldFrameRaw(input)) {
|
|
118
|
+
const index = strings.push(input) - 1;
|
|
110
119
|
|
|
111
|
-
|
|
112
|
-
|
|
120
|
+
return { [RAW_TEXT]: "raw[" + index + "]" };
|
|
121
|
+
}
|
|
113
122
|
|
|
114
|
-
|
|
123
|
+
if (Array.isArray(input)) return input.map(child => visitRawStrings(child, strings));
|
|
115
124
|
|
|
116
|
-
|
|
125
|
+
if (isObject(input)) return Object.fromEntries(Object.entries(input).map(([key, child]) => [key, visitRawStrings(child, strings)]));
|
|
117
126
|
|
|
118
|
-
|
|
119
|
-
|
|
127
|
+
return input;
|
|
128
|
+
}
|
|
120
129
|
|
|
121
|
-
|
|
130
|
+
function framedReturn(value, escaped) {
|
|
131
|
+
const strings = [];
|
|
132
|
+
const referencedValue = visitRawStrings(value, strings);
|
|
122
133
|
|
|
123
134
|
if (!strings.length) return escaped;
|
|
124
135
|
// Keep every key, value, duplicate string and byte. References are unquoted
|
|
@@ -128,6 +139,16 @@ export function formatReturn(value) {
|
|
|
128
139
|
return framed.length < escaped.length ? framed : escaped;
|
|
129
140
|
}
|
|
130
141
|
|
|
142
|
+
/** Lossless framing for source arrays, not string escaping or source compression. */
|
|
143
|
+
export function formatReturn(value) {
|
|
144
|
+
if (isString(value)) return value;
|
|
145
|
+
const rawArray = formatRawStringArray(value);
|
|
146
|
+
|
|
147
|
+
if (rawArray !== null) return rawArray;
|
|
148
|
+
|
|
149
|
+
return framedReturn(value, formatValue(value));
|
|
150
|
+
}
|
|
151
|
+
|
|
131
152
|
const RAW_TEXT = Symbol("raw text reference");
|
|
132
153
|
|
|
133
154
|
const IDENT_KEY = /^[A-Za-z_$][\w$]*$/;
|
|
@@ -172,45 +193,69 @@ function formatFlatWithin(value, limit) {
|
|
|
172
193
|
return walkFlat(value, push, new Set()) ? parts.join("") : null;
|
|
173
194
|
}
|
|
174
195
|
|
|
175
|
-
function
|
|
176
|
-
if (value
|
|
196
|
+
function walkFlatArray(value, push, seen) {
|
|
197
|
+
if (value.length === 0) return push("[]");
|
|
177
198
|
|
|
178
|
-
if (!
|
|
199
|
+
if (!push("[")) return false;
|
|
179
200
|
|
|
180
|
-
|
|
181
|
-
|
|
201
|
+
for (let i = 0; i < value.length; i++) {
|
|
202
|
+
if (i && !push(",")) return false;
|
|
182
203
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (value.length === 0) return push("[]");
|
|
204
|
+
if (!walkFlat(value[i] === undefined ? null : value[i], push, seen)) return false;
|
|
205
|
+
}
|
|
186
206
|
|
|
187
|
-
|
|
207
|
+
return push("]");
|
|
208
|
+
}
|
|
188
209
|
|
|
189
|
-
|
|
190
|
-
|
|
210
|
+
function walkFlatObject(value, push, seen) {
|
|
211
|
+
const keys = Object.keys(value).filter((key) => value[key] !== undefined);
|
|
191
212
|
|
|
192
|
-
|
|
193
|
-
}
|
|
213
|
+
if (keys.length === 0) return push("{}");
|
|
194
214
|
|
|
195
|
-
|
|
196
|
-
|
|
215
|
+
for (let i = 0; i < keys.length; i++) {
|
|
216
|
+
if (!push((i ? "," : "{") + formatKey(keys[i]) + ":")) return false;
|
|
197
217
|
|
|
198
|
-
|
|
218
|
+
if (!walkFlat(value[keys[i]], push, seen)) return false;
|
|
219
|
+
}
|
|
199
220
|
|
|
200
|
-
|
|
221
|
+
return push("}");
|
|
222
|
+
}
|
|
201
223
|
|
|
202
|
-
|
|
203
|
-
|
|
224
|
+
function walkFlat(value, push, seen) {
|
|
225
|
+
if (value?.[RAW_TEXT] !== undefined) return push(value[RAW_TEXT]);
|
|
204
226
|
|
|
205
|
-
|
|
206
|
-
}
|
|
227
|
+
if (!isObject(value) && !Array.isArray(value)) return push(formatPrimitive(value));
|
|
207
228
|
|
|
208
|
-
|
|
229
|
+
if (seen.has(value)) return push("[Circular]");
|
|
230
|
+
seen.add(value);
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
return Array.isArray(value) ? walkFlatArray(value, push, seen) : walkFlatObject(value, push, seen);
|
|
209
234
|
} finally {
|
|
210
235
|
seen.delete(value);
|
|
211
236
|
}
|
|
212
237
|
}
|
|
213
238
|
|
|
239
|
+
function definedKeys(value) {
|
|
240
|
+
return Object.keys(value).filter((key) => value[key] !== undefined);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function formatArray(value, indent, width, seen) {
|
|
244
|
+
if (value.length === 0) return "[]";
|
|
245
|
+
const pad = indent + " ";
|
|
246
|
+
|
|
247
|
+
return "[\n" + value.map((item) => pad + formatValue(item === undefined ? null : item, pad, width, seen)).join(",\n") + "\n" + indent + "]";
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function formatObject(value, indent, width, seen) {
|
|
251
|
+
const keys = definedKeys(value);
|
|
252
|
+
|
|
253
|
+
if (keys.length === 0) return "{}";
|
|
254
|
+
const pad = indent + " ";
|
|
255
|
+
|
|
256
|
+
return "{\n" + keys.map((key) => pad + formatKey(key) + ":" + formatValue(value[key], pad, width, seen)).join(",\n") + "\n" + indent + "}";
|
|
257
|
+
}
|
|
258
|
+
|
|
214
259
|
/**
|
|
215
260
|
* Compact JS-literal rendering for the model: containers that fit in FORMAT_WIDTH
|
|
216
261
|
* stay on one line with no separator whitespace, identifier keys are unquoted,
|
|
@@ -228,19 +273,8 @@ export function formatValue(value, indent = "", width = FORMAT_WIDTH, seen = new
|
|
|
228
273
|
const flat = formatFlatWithin(value, width - indent.length);
|
|
229
274
|
|
|
230
275
|
if (flat !== null) return flat;
|
|
231
|
-
const pad = indent + " ";
|
|
232
|
-
|
|
233
|
-
if (Array.isArray(value)) {
|
|
234
|
-
if (value.length === 0) return "[]";
|
|
235
|
-
|
|
236
|
-
return "[\n" + value.map((item) => pad + formatValue(item === undefined ? null : item, pad, width, seen)).join(",\n") + "\n" + indent + "]";
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const keys = Object.keys(value).filter((key) => value[key] !== undefined);
|
|
240
|
-
|
|
241
|
-
if (keys.length === 0) return "{}";
|
|
242
276
|
|
|
243
|
-
return
|
|
277
|
+
return Array.isArray(value) ? formatArray(value, indent, width, seen) : formatObject(value, indent, width, seen);
|
|
244
278
|
} finally {
|
|
245
279
|
seen.delete(value);
|
|
246
280
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Custom loader: guest programs cannot import host I/O modules. */
|
|
2
|
+
import { isString } from "../shared/decode.js";
|
|
3
|
+
|
|
4
|
+
const DENY = new Set([
|
|
5
|
+
"fs", "node:fs", "fs/promises", "node:fs/promises",
|
|
6
|
+
"child_process", "node:child_process",
|
|
7
|
+
"module", "node:module",
|
|
8
|
+
"worker_threads", "node:worker_threads",
|
|
9
|
+
"net", "node:net", "http", "node:http", "https", "node:https",
|
|
10
|
+
"os", "node:os", "vm", "node:vm",
|
|
11
|
+
"dgram", "node:dgram", "cluster", "node:cluster",
|
|
12
|
+
"inspector", "node:inspector", "sqlite", "node:sqlite",
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
export function guestImportMessage(specifier) {
|
|
16
|
+
return "guest cannot import " + specifier + "; use read, edit, write, or bash";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function isDeniedGuestImport(specifier) {
|
|
20
|
+
if (!isString(specifier)) return false;
|
|
21
|
+
const key = specifier.replace(/^node:/, "");
|
|
22
|
+
|
|
23
|
+
return DENY.has(specifier) || DENY.has("node:" + key) || DENY.has(key);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
27
|
+
if (isDeniedGuestImport(specifier)) {
|
|
28
|
+
const error = new Error(guestImportMessage(specifier));
|
|
29
|
+
error.code = "ERR_GUEST_IMPORT";
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return nextResolve(specifier, context);
|
|
34
|
+
}
|