pi-supernova 0.8.2 → 0.9.1
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 +188 -51
- package/docs/CHANGELOG.md +86 -1
- package/docs/TOKEN_COSTS.md +38 -0
- package/index.js +10 -175
- package/package.json +2 -1
- package/src/adapters/bash.js +14 -30
- package/src/adapters/errors.js +1 -9
- package/src/adapters/read-focus.js +98 -0
- package/src/adapters/read-image.js +51 -0
- package/src/adapters/read-json.js +42 -0
- package/src/adapters/read-text.js +71 -0
- package/src/adapters/read.js +66 -635
- package/src/bridge/catalog.js +3 -2
- package/src/bridge/host-bridge.js +35 -167
- package/src/bridge/tool-registry.js +104 -0
- package/src/bridge/trace.js +41 -0
- package/src/context/evidence-graph.js +249 -0
- package/src/context/evidence-rank.js +153 -0
- package/src/context/evidence.js +10 -424
- package/src/context/fuzzy.js +116 -43
- package/src/context/query.js +80 -0
- package/src/context/repo-index.js +23 -166
- package/src/context/search-files.js +19 -0
- package/src/context/search.js +2 -24
- package/src/context/snap-search.js +203 -0
- package/src/context/snap.js +5 -266
- package/src/context/source-entry.js +112 -0
- package/src/contract/bash.js +6 -1
- package/src/contract/program.js +36 -0
- package/src/contract/read.js +8 -53
- package/src/fs/check.js +1 -1
- package/src/fs/commit.js +161 -0
- package/src/fs/diff.js +11 -15
- package/src/fs/directory.js +79 -0
- package/src/fs/file-io.js +100 -0
- package/src/fs/glob.js +54 -0
- package/src/fs/json-size.js +54 -0
- package/src/fs/lines.js +117 -0
- package/src/fs/read-window.js +74 -0
- package/src/fs/session-resource.js +50 -0
- package/src/fs/text-ops.js +7 -227
- package/src/fs/vfs.js +5 -239
- package/src/fs/workspace.js +2 -1
- package/src/output/bottleneck.js +13 -67
- package/src/output/final.js +114 -0
- package/src/output/format.js +94 -5
- package/src/output/outcome.js +91 -0
- package/src/runtime/batch-input.js +68 -0
- package/src/runtime/guest-api.js +281 -0
- package/src/runtime/guest-worker.js +62 -333
- package/src/runtime/parallel.js +41 -39
- package/src/runtime/program-batch.js +21 -75
- package/src/runtime/program-file.js +3 -11
- package/src/runtime/program.js +141 -0
- package/src/runtime/reference.js +6 -5
- package/src/runtime/runtime.js +77 -253
- package/src/runtime/worker-pool.js +91 -0
- package/src/shared/decode.js +22 -8
- package/src/shared/image-worker.js +30 -0
- package/src/shared/image.js +78 -0
- package/src/shared/png.js +57 -0
- package/src/shared/result.js +77 -0
- package/src/shared/syntax-context.js +61 -3
- package/src/ui/host-render.js +104 -0
- package/src/ui/progress.js +51 -0
- package/src/ui/render.js +21 -421
- package/src/ui/trace.js +277 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {parseBatchPayload,batchTimeoutMs} from "./batch-input.js";
|
|
2
|
+
import { isString } from "../shared/decode.js";
|
|
2
3
|
import { truncateChars } from "../output/format.js";
|
|
3
4
|
|
|
4
5
|
const textOf = result => (Array.isArray(result?.content) ? result.content : []).filter(block => block?.type === "text").map(block => block.text).join("\n");
|
|
@@ -28,66 +29,6 @@ export function programBatchText(results, total, stopped = "", failed = 0) {
|
|
|
28
29
|
}).join("");
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
function assertProgramEntry(p, defaults = {}) {
|
|
32
|
-
const source = p?.code === undefined && p?.file === undefined ? defaults : p;
|
|
33
|
-
|
|
34
|
-
if (!isObject(p) || Array.isArray(p) || Object.keys(p).some(key => !["code","file","data"].includes(key)) ||
|
|
35
|
-
((source.code === undefined) === (source.file === undefined)) || !isString(source.code ?? source.file) || !(source.code ?? source.file).trim()) {
|
|
36
|
-
throw new Error("each program requires code OR file (own or shared), with optional data; no nested batches or per-entry timeouts; no programs ran");
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const objectData = value => isObject(value) && !Array.isArray(value);
|
|
41
|
-
|
|
42
|
-
function applyBatchDefaults(parsed, mergeData) {
|
|
43
|
-
if (mergeData && !objectData(parsed.data)) throw new Error("mergeData requires top-level object data; no programs ran");
|
|
44
|
-
const source = parsed.code !== undefined ? {code:parsed.code} : parsed.file !== undefined ? {file:parsed.file} : {};
|
|
45
|
-
|
|
46
|
-
return parsed.programs.map(program => {
|
|
47
|
-
assertProgramEntry(program, source);
|
|
48
|
-
const entry = program.code === undefined && program.file === undefined ? {...source,...program} : program;
|
|
49
|
-
|
|
50
|
-
if (mergeData) {
|
|
51
|
-
if (program.data !== undefined && !objectData(program.data)) throw new Error("mergeData requires object data in every explicit entry; no programs ran");
|
|
52
|
-
// Shallow own-property overlay, including literal __proto__ keys. The
|
|
53
|
-
// runtime snapshots data again per guest; no mutable heap is shared.
|
|
54
|
-
entry.data = {...parsed.data,...program.data};
|
|
55
|
-
} else if (program.data === undefined && Object.hasOwn(parsed,"data")) entry.data = parsed.data;
|
|
56
|
-
|
|
57
|
-
return entry;
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function parseBatchPayload(params, config) {
|
|
62
|
-
if (!Array.isArray(params.programs) || !params.programs.length || params.programs.length > 32) throw new Error("programs requires 1..32 entries; no programs ran");
|
|
63
|
-
if (params.mergeData !== undefined && params.mergeData !== true && params.mergeData !== false) throw new Error("mergeData must be boolean; no programs ran");
|
|
64
|
-
const defaults = Object.fromEntries(["code","file","data"].filter(key => params[key] !== undefined).map(key => [key,params[key]]));
|
|
65
|
-
|
|
66
|
-
if (defaults.code !== undefined || defaults.file !== undefined) assertProgramEntry(defaults);
|
|
67
|
-
for (const p of params.programs) assertProgramEntry(p, defaults);
|
|
68
|
-
const hasDefaults = Object.keys(defaults).length > 0;
|
|
69
|
-
let encoded;
|
|
70
|
-
|
|
71
|
-
try { encoded = JSON.stringify(hasDefaults ? {programs:params.programs,...defaults} : params.programs); } catch { throw new Error("programs and defaults must be JSON-serializable; no programs ran"); }
|
|
72
|
-
|
|
73
|
-
if (encoded.length > (config.maxCodeChars ?? 48000)) throw new Error("programs JSON exceeds the code character budget (including shared code/file/data); no programs ran");
|
|
74
|
-
const parsed = hasDefaults ? JSON.parse(encoded) : {programs:JSON.parse(encoded)};
|
|
75
|
-
|
|
76
|
-
if (Object.hasOwn(defaults,"data") && !Object.hasOwn(parsed,"data")) throw new Error("data must be JSON-serializable; no programs ran");
|
|
77
|
-
|
|
78
|
-
// Validate and expand every entry before executing any. Defaults count once
|
|
79
|
-
// against admission, not once for each independent guest receiving a copy.
|
|
80
|
-
return applyBatchDefaults(parsed, params.mergeData === true);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function batchTimeoutMs(params, config) {
|
|
84
|
-
const requestedTimeout = params.timeoutMs === undefined ? config.timeoutMs : Number(params.timeoutMs);
|
|
85
|
-
|
|
86
|
-
if (!Number.isFinite(requestedTimeout) || requestedTimeout <= 0) throw new Error("program batch timeoutMs must be a positive finite number");
|
|
87
|
-
|
|
88
|
-
return requestedTimeout;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
32
|
const MAX_PARALLEL_PROGRAMS = 8;
|
|
92
33
|
|
|
93
34
|
class ProgramBatch {
|
|
@@ -136,7 +77,7 @@ class ProgramBatch {
|
|
|
136
77
|
}
|
|
137
78
|
|
|
138
79
|
collectImages(result, i) {
|
|
139
|
-
for (const block of
|
|
80
|
+
for (const block of imageBlocks(result)) {
|
|
140
81
|
this.imageBytes += Buffer.byteLength(block.data,"base64");
|
|
141
82
|
|
|
142
83
|
if (this.images.length >= 16 || this.imageBytes > 20*1024*1024) { this.imageDropped = true; continue; }
|
|
@@ -158,18 +99,9 @@ class ProgramBatch {
|
|
|
158
99
|
|
|
159
100
|
parallelBudgetStop(settled) {
|
|
160
101
|
const results = settled.filter(Boolean);
|
|
161
|
-
|
|
162
|
-
for (const [i, result] of settled.entries()) {
|
|
163
|
-
let imageSeq = 0;
|
|
164
|
-
for (const block of result?.content ?? []) if (block.type === "image" && isString(block.data)) {
|
|
165
|
-
images++;
|
|
166
|
-
bytes += Buffer.byteLength(block.data, "base64");
|
|
167
|
-
labelChars += ("program " + (i + 1) + " image " + (++imageSeq)).length + 1;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
102
|
+
const {images, bytes} = imageTotals(settled);
|
|
170
103
|
let kind;
|
|
171
104
|
if (images > 16 || bytes > 20 * 1024 * 1024) kind = "image";
|
|
172
|
-
else if (results.some(result => result.details?.returnTruncated) || programBatchText(results, this.programs.length).length + labelChars > this.config.maxReturnChars) kind = "output";
|
|
173
105
|
else if (results.some(result => result.details?.logTruncated) || results.reduce((n, result) => n + (result.details?.logs?.length ?? 0), 0) > (this.config.maxLogLines ?? 100)) kind = "log";
|
|
174
106
|
return kind ? "batch " + kind + " budget exceeded; completed commits remain" : "";
|
|
175
107
|
}
|
|
@@ -202,9 +134,8 @@ class ProgramBatch {
|
|
|
202
134
|
if (this.imageDropped) stopped = "batch image budget exceeded; remaining programs did not run";
|
|
203
135
|
|
|
204
136
|
if (result.details?.ok === false) stopped = "program " + (i+1) + " failed; remaining programs did not run; earlier commits remain";
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (text.length + this.imageTextChars() > this.config.maxReturnChars || result.details?.returnTruncated) stopped ||= "batch output budget exceeded; remaining programs did not run; earlier commits remain";
|
|
137
|
+
// Display clipping is not an execution failure. Finish every requested entry
|
|
138
|
+
// unless a real execution/resource limit stops it; boundedText caps delivery.
|
|
208
139
|
|
|
209
140
|
if (result.details?.logTruncated) stopped ||= "batch log budget exceeded; remaining programs did not run; earlier commits remain";
|
|
210
141
|
|
|
@@ -270,3 +201,18 @@ export async function runProgramBatch(id, params, signal, onUpdate, ctx, config,
|
|
|
270
201
|
|
|
271
202
|
return new ProgramBatch(id, params, signal, onUpdate, ctx, config, execute, programs, batchTimeoutMs(params, config)).run();
|
|
272
203
|
}
|
|
204
|
+
|
|
205
|
+
function imageBlocks(result) {
|
|
206
|
+
return (Array.isArray(result?.content) ? result.content : []).filter(block => block?.type === "image" && isString(block.data));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function imageTotals(settled) {
|
|
210
|
+
let images = 0, bytes = 0;
|
|
211
|
+
for (const result of settled) {
|
|
212
|
+
for (const block of imageBlocks(result)) {
|
|
213
|
+
images++;
|
|
214
|
+
bytes += Buffer.byteLength(block.data, "base64");
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return {images, bytes};
|
|
218
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readLimitedBytes } from "../fs/file-io.js";
|
|
1
2
|
import * as fs from "node:fs/promises";
|
|
2
3
|
import { resolveWorkspacePath } from "../fs/workspace.js";
|
|
3
4
|
|
|
@@ -21,22 +22,13 @@ export async function readProgramFile(file, cwd, maxChars, signal) {
|
|
|
21
22
|
const maxBytes = chars * 3;
|
|
22
23
|
const tooLarge = () => new Error("code exceeds " + chars + " characters; split the program");
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
const chunks = [];
|
|
26
|
-
let bytes = 0;
|
|
27
|
-
|
|
28
|
-
for await (const chunk of handle.createReadStream({ end: maxBytes, autoClose: false, signal })) {
|
|
29
|
-
bytes += chunk.length;
|
|
30
|
-
|
|
31
|
-
if (bytes > maxBytes) throw tooLarge();
|
|
32
|
-
chunks.push(chunk);
|
|
33
|
-
}
|
|
25
|
+
const bytes = await readLimitedBytes(handle, stat, maxBytes, file, signal, tooLarge);
|
|
34
26
|
|
|
35
27
|
signal?.throwIfAborted();
|
|
36
28
|
// Do not silently replace invalid bytes in executable source. Preserve BOMs.
|
|
37
29
|
let code;
|
|
38
30
|
|
|
39
|
-
try { code = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(
|
|
31
|
+
try { code = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes); }
|
|
40
32
|
catch { throw new Error("program file " + file + " is not valid UTF-8 (encoded data could not be decoded); save it as UTF-8 text"); }
|
|
41
33
|
|
|
42
34
|
if (code.length > chars) throw tooLarge();
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import {parse} from 'acorn';
|
|
2
|
+
import {isObject,isString} from '../shared/decode.js';
|
|
3
|
+
import {guestImportMessage,isDeniedGuestImport} from './guest-deny-imports.js';
|
|
4
|
+
|
|
5
|
+
const PARSE_OPTIONS = { ecmaVersion: "latest", sourceType: "module", allowReturnOutsideFunction: true, allowAwaitOutsideFunction: true };
|
|
6
|
+
|
|
7
|
+
const FUNCTION_TYPES = new Set(["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"]);
|
|
8
|
+
|
|
9
|
+
function hasReturn(node) {
|
|
10
|
+
if (!isObject(node)) return false;
|
|
11
|
+
|
|
12
|
+
if (node.type === "ReturnStatement") return true;
|
|
13
|
+
|
|
14
|
+
if (FUNCTION_TYPES.has(node.type)) return false;
|
|
15
|
+
|
|
16
|
+
return Object.values(node).some(value => Array.isArray(value) ? value.some(hasReturn) : hasReturn(value));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function parseExpressionFunction(code) {
|
|
20
|
+
try {
|
|
21
|
+
const program = parse(code, PARSE_OPTIONS);
|
|
22
|
+
const statements = program.body.filter(node => node.type !== "EmptyStatement");
|
|
23
|
+
const statement = statements.length === 1 ? statements[0] : undefined;
|
|
24
|
+
const candidate = statement?.type === "ExpressionStatement" ? statement.expression : statement;
|
|
25
|
+
|
|
26
|
+
if (candidate && FUNCTION_TYPES.has(candidate.type)) {
|
|
27
|
+
return { program, expression: candidate, expressionSource: code.slice(statement.start, statement.end).replace(/;\s*$/, "") };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { program };
|
|
31
|
+
} catch (bodyError) {
|
|
32
|
+
const expressionSource = code.trimEnd().replace(/;+\s*$/, "");
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const wrapped = parse("(" + expressionSource + "\n)", PARSE_OPTIONS);
|
|
36
|
+
const expression = wrapped.body[0]?.expression;
|
|
37
|
+
|
|
38
|
+
if (!expression || !FUNCTION_TYPES.has(expression.type)) throw bodyError;
|
|
39
|
+
|
|
40
|
+
return { expression, expressionSource };
|
|
41
|
+
} catch { throw bodyError; }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function deniedSpecifier(node) {
|
|
46
|
+
if (node?.type === "Literal" && isString(node.value)) return node.value;
|
|
47
|
+
if (node?.type === "TemplateLiteral" && node.expressions.length === 0) return node.quasis[0]?.value?.cooked;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function assertGuestImports(node) {
|
|
51
|
+
if (Array.isArray(node)) { node.forEach(assertGuestImports); return; }
|
|
52
|
+
if (!isObject(node)) return;
|
|
53
|
+
if (["ImportDeclaration", "ImportExpression"].includes(node.type)) rejectGuestImport(node.source);
|
|
54
|
+
if (node.type === "CallExpression" && node.callee?.type === "Identifier" && node.callee.name === "require") rejectGuestImport(node.arguments?.[0]);
|
|
55
|
+
for (const key of Object.keys(node)) {
|
|
56
|
+
if (!["start", "end", "loc", "range"].includes(key)) assertGuestImports(node[key]);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function prepareProgram(code) {
|
|
61
|
+
const parsed = parseExpressionFunction(code);
|
|
62
|
+
assertGuestImports(parsed.program ?? parsed.expression);
|
|
63
|
+
const body = parsed.expression ? "return await (" + parsed.expressionSource + "\n)();" : code;
|
|
64
|
+
const returns = parsed.expression
|
|
65
|
+
? parsed.expression.type === "ArrowFunctionExpression" && parsed.expression.body.type !== "BlockStatement" || hasReturn(parsed.expression.body)
|
|
66
|
+
: hasReturn(parsed.program);
|
|
67
|
+
|
|
68
|
+
return { body, hasReturn: returns };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function admitData(data, cap) {
|
|
72
|
+
if (data === undefined) return { data };
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const encoded = JSON.stringify(data);
|
|
76
|
+
|
|
77
|
+
if (encoded === undefined) return { error: "data must be JSON-serializable" };
|
|
78
|
+
if (encoded.length > cap) return { error: "data exceeds " + cap + " characters (serialized JSON: " + encoded.length + " UTF-16 characters); no commands ran. Split literal inputs across invocations; large text can use write({path,content,append:true}) chunks without omitting content" };
|
|
79
|
+
|
|
80
|
+
return { data: JSON.parse(encoded) };
|
|
81
|
+
} catch { return { error: "data must be JSON-serializable" }; }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function admitCode({ code, file, cap }) {
|
|
85
|
+
if ((code === undefined) === (file === undefined)) return { error: "supply exactly one of code or file; no commands ran" };
|
|
86
|
+
if (file === undefined && (!isString(code) || !code.trim())) return { error: "code must be a non-empty string" };
|
|
87
|
+
if (file === undefined && code.length > cap) return { error: "code exceeds " + cap + " characters; split large writes into write({path,content,append:true}) chunks" };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function admitTimeout(config) {
|
|
91
|
+
const requestedTimeout = Number(config.timeoutMs === undefined ? 60000 : config.timeoutMs);
|
|
92
|
+
|
|
93
|
+
if (!Number.isFinite(requestedTimeout) || requestedTimeout <= 0) return { error: "timeoutMs must be a positive finite number" };
|
|
94
|
+
|
|
95
|
+
return { timeoutMs: Math.max(1, Math.min(2_147_483_647, Math.floor(requestedTimeout))) };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function admitGuest({ code, file, data, config }) {
|
|
99
|
+
const cap = config.maxCodeChars ?? 48000;
|
|
100
|
+
const codeError = admitCode({ code, file, cap });
|
|
101
|
+
|
|
102
|
+
if (codeError) return codeError;
|
|
103
|
+
const admitted = admitData(data, cap);
|
|
104
|
+
|
|
105
|
+
if (admitted.error) return admitted;
|
|
106
|
+
const timeout = admitTimeout(config);
|
|
107
|
+
|
|
108
|
+
if (timeout.error) return timeout;
|
|
109
|
+
|
|
110
|
+
return { data: admitted.data, timeoutMs: timeout.timeoutMs };
|
|
111
|
+
}
|
|
112
|
+
export { admitGuest, prepareProgram };
|
|
113
|
+
|
|
114
|
+
function rejectGuestImport(source) {
|
|
115
|
+
const spec = deniedSpecifier(source);
|
|
116
|
+
const reason = spec && isDeniedGuestImport(spec) ? guestImportMessage(spec) : "guest cannot import modules; use read, edit, write, or bash";
|
|
117
|
+
throw new Error(reason + "; no commands ran");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function containingAwait(node, offset) {
|
|
121
|
+
if (Array.isArray(node)) return node.map(child => containingAwait(child, offset)).find(Boolean);
|
|
122
|
+
if (!isObject(node) || offset < node.start || offset >= node.end) return null;
|
|
123
|
+
for (const child of Object.values(node)) {
|
|
124
|
+
const found = containingAwait(child, offset);
|
|
125
|
+
if (found) return found;
|
|
126
|
+
}
|
|
127
|
+
return node.type === "AwaitExpression" ? node : null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// V8 points at 'await'; JSC points at the called function's parenthesis. Report
|
|
131
|
+
// the enclosing await expression consistently, using source syntax, not offsets.
|
|
132
|
+
export function normalizeGuestLocation(source, location) {
|
|
133
|
+
if (!location?.awaited) return location;
|
|
134
|
+
const {line, col} = location;
|
|
135
|
+
const prefix = source.split("\n").slice(0, line - 1).join("\n");
|
|
136
|
+
const offset = prefix.length + Number(line > 1) + col - 1;
|
|
137
|
+
try {
|
|
138
|
+
const node = containingAwait(parse(source, {...PARSE_OPTIONS, locations:true}), offset);
|
|
139
|
+
return node ? {line:node.loc.start.line, col:node.loc.start.column + 1} : location;
|
|
140
|
+
} catch { return location; }
|
|
141
|
+
}
|
package/src/runtime/reference.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
// Standing tool description: sent on every request. No result or history compression.
|
|
2
2
|
export const REFERENCE = `JS body/async arrow: read/write/edit/bash; no fs/import/require. file: workspace scripts; data: literals (≤48000 JSON chars).
|
|
3
3
|
read(path|paths,offset=1,limit?) → raw text/text[]; directories → entries; images: PNG/JPEG/GIF/WebP (≤16 images/20 MiB).
|
|
4
|
-
|
|
5
|
-
read({path,json:selector}) → parsed JSON: ".field", ".a[0:3]", ".a.length", quoted keys, true; ≤16 MiB, no jq.
|
|
4
|
+
Text ≤64 MiB internally; complete:true requires the whole file. Display alone is capped; return a summary or read(path,{offset:1,limit:80}). Larger files/JSONL: bounded bash parser.
|
|
5
|
+
read({path,json:selector}) → parsed JSON: ".field", ".a[0:3]", ".a.length", quoted keys, true; input ≤16 MiB, selections ≤64 MiB storage, no jq. Values retain their types.
|
|
6
6
|
read("symbol or question") = read({query,resolve:true}) → view; check status; view.text is a span, not the file.
|
|
7
7
|
read(path,{about}) → windows; read({query,evidence:true}) → ranked evidence; read({path,outline:true}) → declarations.
|
|
8
8
|
write(path,text) replaces unread workspace files; write({path,content,append:true}) appends without reading. After read: edit or replace:true.
|
|
9
9
|
edit(path,oldText,newText) | edit({path,edits:[{oldText,newText}]}) unique exact read text; returns numbered windows/checks/references.
|
|
10
10
|
edit(view,text) replaces span; edit(view,old,new) uniquely matches within it. edit(async()=>{...}) checkpoint: merge on success, rollback/rethrow on failure.
|
|
11
11
|
bash(command,{cwd?,timeoutMs?}) | bash({command,args}) literal argv for scripts; bounded output, nonzero throws. Outer timeoutMs caps ALL waits/commands; bash inherits unless overridden.
|
|
12
|
-
Edits
|
|
13
|
-
programs:[{code?,file?,data?}] inherits
|
|
14
|
-
|
|
12
|
+
Edits commit on success/before bash. Promise.allSettled keeps partial results.
|
|
13
|
+
programs:[{code?,file?,data?}] inherits source/data; entries override. mergeData:true shallow object merge (entry keys win).
|
|
14
|
+
Promise.all: ≤8 reads or disjoint-file mutations; same-file serial; bash/checkpoint barriers.
|
|
15
|
+
Fresh guests/separate commits. Sequential failure stops; prior commits stay. parallel:true for disjoint entries. ONE call for known work; split for new decisions.
|
|
15
16
|
`;
|