pi-supernova 0.0.15 → 0.1.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/CHANGELOG.md +18 -0
- package/README.md +42 -11
- package/bottleneck.js +98 -97
- package/catalog.js +4 -29
- package/config.js +1 -2
- package/decode.js +61 -0
- package/evidence.js +14 -5
- package/format.js +16 -17
- package/guest-worker.js +29 -150
- package/host-bridge.js +95 -22
- package/index.js +69 -88
- package/ledger.js +73 -101
- package/omp-frame.js +0 -1
- package/package.json +6 -2
- package/parallel.js +42 -34
- package/patch.js +62 -72
- package/render-measure.js +48 -118
- package/render.js +12 -13
- package/repo-index.js +2 -1
- package/runtime.js +204 -210
- package/vfs.js +114 -70
- package/workspace.js +37 -33
package/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import { isString, isFunction
|
|
2
|
+
import { isString, isFunction } from "./decode.js";
|
|
3
3
|
import { buildCatalog, searchCatalog, describeTool, mergeNativeToolDefinitions } from "./catalog.js";
|
|
4
4
|
import { loadConfig } from "./config.js";
|
|
5
5
|
import { createHostBridge } from "./host-bridge.js";
|
|
@@ -82,7 +82,7 @@ function errorText(outcome, call) {
|
|
|
82
82
|
|
|
83
83
|
function successText(outcome, call) {
|
|
84
84
|
const truncated = outcome.returnTruncated ? " [return truncated]" : "";
|
|
85
|
-
const hint = outcome.undefinedReturn ? " (no return statement; add
|
|
85
|
+
const hint = outcome.undefinedReturn ? " (no return statement; add `return` to get a value)" : "";
|
|
86
86
|
return `ok #${call} ${outcome.wallMs}ms${truncated}${logsBlock(outcome, "\n--- result")}\n${outcome.resultText}${hint}`;
|
|
87
87
|
}
|
|
88
88
|
const TOOL_DESCRIPTION = `Run one JavaScript program that composes host tools. Async body or arrow; \`return\` a small shaped value (compact literal, capped; strings raw; console.log is captured).
|
|
@@ -111,50 +111,38 @@ export default function piSupernova(pi) {
|
|
|
111
111
|
getCwd: () => cwd,
|
|
112
112
|
});
|
|
113
113
|
|
|
114
|
-
function refreshCatalog() {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
114
|
+
function refreshCatalog(target = bridge) {
|
|
115
|
+
const tools = target.refreshTools();
|
|
116
|
+
const discoverable = mergeNativeToolDefinitions(tools, target.externalNames())
|
|
117
|
+
.filter(tool => target.isCallable(tool.name))
|
|
118
|
+
.map(tool => {
|
|
119
|
+
const schema = tool.parameters;
|
|
120
|
+
try {
|
|
121
|
+
if (isFunction(schema?.toJsonSchema)) return { ...tool, parameters: schema.toJsonSchema() };
|
|
122
|
+
if (schema && !schema.type && pi.zod?.toJSONSchema && (schema._zod || schema._def)) {
|
|
123
|
+
return { ...tool, parameters: pi.zod.toJSONSchema(schema, { io: "input" }) };
|
|
124
|
+
}
|
|
125
|
+
return tool;
|
|
126
|
+
} catch (error) {
|
|
127
|
+
return { ...tool, parameters: undefined, schemaError: error.message };
|
|
128
|
+
}
|
|
129
|
+
});
|
|
124
130
|
catalog = buildCatalog(discoverable, config.excludeTools || []);
|
|
125
131
|
return catalog;
|
|
126
132
|
}
|
|
127
133
|
|
|
128
|
-
function makeNovaApi() {
|
|
134
|
+
function makeNovaApi(runBridge, runCatalog, cancel) {
|
|
129
135
|
return {
|
|
130
|
-
async
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return bridge.call(name, args);
|
|
141
|
-
},
|
|
142
|
-
async callMany(calls) {
|
|
143
|
-
return bridge.callMany(calls);
|
|
144
|
-
},
|
|
145
|
-
speculateBegin() {
|
|
146
|
-
bridge.beginSpeculation();
|
|
147
|
-
},
|
|
148
|
-
async speculateCommit() {
|
|
149
|
-
await bridge.commitSpeculation();
|
|
150
|
-
},
|
|
151
|
-
speculateRollback() {
|
|
152
|
-
bridge.rollbackSpeculation();
|
|
153
|
-
},
|
|
154
|
-
names() {
|
|
155
|
-
const cat = catalog.length ? catalog : refreshCatalog();
|
|
156
|
-
return [...new Set([...cat.map((t) => t.name), ...bridge.executors.keys(), ...Object.keys(bridge.natives)])];
|
|
157
|
-
},
|
|
136
|
+
search: async (query, limit) => searchCatalog(runCatalog, query, Number.isInteger(limit) ? limit : config.maxSearchResults),
|
|
137
|
+
describe: async (name) => describeTool(runCatalog, name),
|
|
138
|
+
call: (name, args) => runBridge.call(name, args),
|
|
139
|
+
callMany: (calls) => runBridge.callMany(calls),
|
|
140
|
+
speculateBegin: () => runBridge.beginSpeculation(),
|
|
141
|
+
speculateCommit: () => runBridge.commitSpeculation(),
|
|
142
|
+
speculateRollback: () => runBridge.rollbackSpeculation(),
|
|
143
|
+
names: () => runCatalog.map(tool => tool.name),
|
|
144
|
+
batchRead: runBridge.supportsBatchRead(),
|
|
145
|
+
cancel,
|
|
158
146
|
};
|
|
159
147
|
}
|
|
160
148
|
|
|
@@ -177,70 +165,62 @@ export default function piSupernova(pi) {
|
|
|
177
165
|
renderCall: renderSupernovaCall,
|
|
178
166
|
renderResult: renderSupernovaResult,
|
|
179
167
|
async execute(_id, params, signal, onUpdate, ctx) {
|
|
180
|
-
|
|
168
|
+
const runCwd = ctx?.cwd || cwd;
|
|
181
169
|
const runController = new AbortController();
|
|
182
170
|
const abortRun = () => runController.abort(signal?.reason);
|
|
183
171
|
if (signal?.aborted) abortRun();
|
|
184
172
|
else signal?.addEventListener("abort", abortRun, { once: true });
|
|
173
|
+
const runBridge = bridge.fork({ getCwd: () => runCwd });
|
|
174
|
+
runBridge.bindCallContext(ctx, runController.signal);
|
|
175
|
+
runBridge.resetCallBudget();
|
|
185
176
|
|
|
186
|
-
bridge.bindCallContext(ctx, runController.signal);
|
|
187
|
-
bridge.resetCallBudget();
|
|
188
|
-
refreshCatalog();
|
|
189
|
-
bridge.beginSpeculation();
|
|
190
177
|
const call = ++programSeq;
|
|
191
|
-
|
|
178
|
+
runBridge.ledger.beginProgram(call);
|
|
192
179
|
const emitProgress = progressEmitter(onUpdate);
|
|
193
|
-
|
|
180
|
+
runBridge.setCallListener((_record, trace) => emitProgress(trace));
|
|
194
181
|
emitProgress([]);
|
|
195
|
-
|
|
182
|
+
const started = performance.now();
|
|
196
183
|
let outcome;
|
|
197
184
|
try {
|
|
198
|
-
|
|
185
|
+
const runCatalog = refreshCatalog(runBridge);
|
|
186
|
+
runBridge.beginSpeculation();
|
|
187
|
+
outcome = await runGuestProgram({
|
|
188
|
+
code: params?.code,
|
|
189
|
+
nova: makeNovaApi(runBridge, runCatalog, abortRun),
|
|
190
|
+
config: { ...config, timeoutMs: Number.isInteger(params?.timeoutMs) ? params.timeoutMs : config.timeoutMs },
|
|
191
|
+
signal: runController.signal,
|
|
192
|
+
onTimeout: abortRun,
|
|
193
|
+
});
|
|
194
|
+
runBridge.close();
|
|
195
|
+
if (outcome.ok) {
|
|
196
|
+
if (runBridge.getOverlayDepth() !== 1) throw new Error("program ended with an unfinished nova.speculate branch; await it before returning");
|
|
197
|
+
await runBridge.commitSpeculation();
|
|
198
|
+
}
|
|
199
|
+
else runBridge.rollbackSpeculation();
|
|
200
|
+
} catch (error) {
|
|
201
|
+
abortRun();
|
|
202
|
+
runBridge.close();
|
|
203
|
+
runBridge.rollbackSpeculation();
|
|
204
|
+
outcome = { ok: false, error: error instanceof Error ? error.message : String(error), logs: outcome?.logs ?? [], wallMs: Math.round(performance.now() - started) };
|
|
199
205
|
} finally {
|
|
200
|
-
|
|
206
|
+
runBridge.setCallListener(null);
|
|
201
207
|
emitProgress.flush();
|
|
202
208
|
signal?.removeEventListener("abort", abortRun);
|
|
203
209
|
}
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
await bridge.commitSpeculation();
|
|
211
|
-
return result(bridge.ledger.dedupe(successText(outcome, call), call), {
|
|
212
|
-
ok: true,
|
|
213
|
-
wallMs: outcome.wallMs,
|
|
214
|
-
returnTruncated: outcome.returnTruncated,
|
|
215
|
-
logTruncated: outcome.logTruncated,
|
|
216
|
-
logs: outcome.logs,
|
|
217
|
-
result: outcome.result,
|
|
218
|
-
trace,
|
|
210
|
+
const trace = runBridge.getTrace();
|
|
211
|
+
const text = outcome.ok ? successText(outcome, call) : errorText(outcome, call);
|
|
212
|
+
return result(runBridge.ledger.dedupe(text, call), {
|
|
213
|
+
ok: outcome.ok, error: outcome.error, wallMs: outcome.wallMs,
|
|
214
|
+
returnTruncated: outcome.returnTruncated, logTruncated: outcome.logTruncated,
|
|
215
|
+
logs: outcome.logs, result: outcome.result, trace,
|
|
219
216
|
});
|
|
220
217
|
},
|
|
221
218
|
});
|
|
222
219
|
|
|
223
|
-
async function runProgram(params, signal, onTimeout) {
|
|
224
|
-
const runStartedAt = performance.now();
|
|
225
|
-
const runConfig = {
|
|
226
|
-
...config,
|
|
227
|
-
timeoutMs: Number.isInteger(params?.timeoutMs) ? params.timeoutMs : config.timeoutMs,
|
|
228
|
-
};
|
|
229
|
-
try {
|
|
230
|
-
return await runGuestProgram({ code: String(params?.code || ""), nova: makeNovaApi(), config: runConfig, signal, onTimeout });
|
|
231
|
-
} catch (error) {
|
|
232
|
-
return {
|
|
233
|
-
ok: false,
|
|
234
|
-
error: error instanceof Error ? error.message : String(error),
|
|
235
|
-
logs: [],
|
|
236
|
-
wallMs: Math.round(performance.now() - runStartedAt),
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
220
|
pi.on("session_start", (_event, ctx) => {
|
|
242
221
|
if (ctx && isString(ctx.cwd) && ctx.cwd) cwd = ctx.cwd;
|
|
243
222
|
// A new session is a new model context: nothing has been seen yet.
|
|
223
|
+
bridge.bindCallContext(ctx);
|
|
244
224
|
bridge.ledger.reset();
|
|
245
225
|
programSeq = 0;
|
|
246
226
|
refreshCatalog();
|
|
@@ -248,14 +228,15 @@ export default function piSupernova(pi) {
|
|
|
248
228
|
});
|
|
249
229
|
|
|
250
230
|
pi.registerCommand("supernova", {
|
|
251
|
-
description: "Show pi-supernova status (
|
|
231
|
+
description: "Show pi-supernova status (callable tools and session statistics)",
|
|
252
232
|
handler: async (_args, ctx) => {
|
|
233
|
+
bridge.bindCallContext(ctx);
|
|
253
234
|
refreshCatalog();
|
|
254
|
-
const
|
|
255
|
-
const natives = Object.keys(bridge.natives).sort();
|
|
235
|
+
const external = bridge.externalNames().filter(bridge.isCallable).sort();
|
|
236
|
+
const natives = Object.keys(bridge.natives).filter(name => bridge.isCallable(name) && !external.includes(name)).sort();
|
|
256
237
|
const lines = [
|
|
257
238
|
`pi-supernova catalog: ${catalog.length} tools`,
|
|
258
|
-
`
|
|
239
|
+
`external tools: ${external.length ? external.join(", ") : "(none)"}`,
|
|
259
240
|
`native adapters: ${natives.join(", ")}`,
|
|
260
241
|
`timeoutMs=${config.timeoutMs} maxCallResultChars=${config.maxCallResultChars} maxReturnChars=${config.maxReturnChars} maxBridgeCalls=${config.maxBridgeCalls} maxHeapMb=${config.maxHeapMb}`,
|
|
261
242
|
sessionStats(bridge.ledger.stats),
|
package/ledger.js
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
// Seen-ledger: the model's context window is a memory. Nothing that already reached the
|
|
2
|
-
// model in this session is sent again verbatim. A run of identical lines (≥ MIN_RUN, with
|
|
3
|
-
// enough substantive lines) collapses to one marker that cites the earlier program and,
|
|
4
|
-
// when the lines came from a file, path:a–b, so one read(path, a, n) recovers them.
|
|
5
|
-
//
|
|
6
|
-
// This is not compression: every collapsed line already exists, verbatim, in the model's
|
|
7
|
-
// context. Changed lines are never collapsed, so a re-read after an edit shows exactly the
|
|
8
|
-
// delta. Lines the current program read with an explicit offset/limit are pinned and always
|
|
9
|
-
// shown: that is the model asking for a specific window on purpose.
|
|
10
|
-
|
|
11
1
|
const MIN_RUN = 6;
|
|
12
2
|
const MIN_SUBSTANTIVE = 4;
|
|
13
3
|
const MAX_CANDIDATES = 8;
|
|
@@ -15,136 +5,118 @@ const DEFAULT_WINDOW = 40;
|
|
|
15
5
|
const MAX_STORED_LINES = 200_000;
|
|
16
6
|
|
|
17
7
|
function hashLine(line) {
|
|
18
|
-
let
|
|
19
|
-
for (let i = 0; i < line.length; i++)
|
|
20
|
-
|
|
21
|
-
h = Math.imul(h, 0x01000193);
|
|
22
|
-
}
|
|
23
|
-
return h >>> 0;
|
|
8
|
+
let hash = 0x811c9dc5;
|
|
9
|
+
for (let i = 0; i < line.length; i++) hash = Math.imul(hash ^ line.charCodeAt(i), 0x01000193);
|
|
10
|
+
return hash >>> 0;
|
|
24
11
|
}
|
|
25
|
-
|
|
26
|
-
function
|
|
27
|
-
return
|
|
12
|
+
function substantive(line) { return line.trim().length >= 8; }
|
|
13
|
+
function newHistory() {
|
|
14
|
+
return { results: new Map(), occurrences: new Map(), storedLines: 0, latestCall: 0,
|
|
15
|
+
stats: { programs: 0, returnedChars: 0, collapsedChars: 0, collapsedRuns: 0 } };
|
|
28
16
|
}
|
|
29
17
|
|
|
30
18
|
export class SeenLedger {
|
|
31
|
-
constructor({ window = DEFAULT_WINDOW } = {}) {
|
|
19
|
+
constructor({ window = DEFAULT_WINDOW, history } = {}) {
|
|
32
20
|
this.window = window;
|
|
33
|
-
this.
|
|
34
|
-
this.
|
|
35
|
-
this.
|
|
36
|
-
this.pinned = new Set(); // "path:line" pinned by the current program
|
|
37
|
-
this.storedLines = 0;
|
|
38
|
-
this.stats = { programs: 0, returnedChars: 0, collapsedChars: 0, collapsedRuns: 0 };
|
|
21
|
+
this.history = history ?? newHistory();
|
|
22
|
+
this.origins = new Map();
|
|
23
|
+
this.pinned = new Set();
|
|
39
24
|
}
|
|
25
|
+
get results() { return this.history.results; }
|
|
26
|
+
get occurrences() { return this.history.occurrences; }
|
|
27
|
+
get storedLines() { return this.history.storedLines; }
|
|
28
|
+
get stats() { return this.history.stats; }
|
|
29
|
+
fork() { return new SeenLedger({ window: this.window, history: this.history }); }
|
|
40
30
|
|
|
41
31
|
reset() {
|
|
42
|
-
this.
|
|
43
|
-
this.occurrences.clear();
|
|
32
|
+
Object.assign(this.history, newHistory());
|
|
44
33
|
this.origins.clear();
|
|
45
34
|
this.pinned.clear();
|
|
46
|
-
this.storedLines = 0;
|
|
47
|
-
this.stats = { programs: 0, returnedChars: 0, collapsedChars: 0, collapsedRuns: 0 };
|
|
48
35
|
}
|
|
49
36
|
|
|
50
37
|
beginProgram(call) {
|
|
38
|
+
this.origins.clear();
|
|
51
39
|
this.pinned.clear();
|
|
52
40
|
this.stats.programs++;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
41
|
+
this.history.latestCall = Math.max(this.history.latestCall, call);
|
|
42
|
+
for (const old of this.results.keys()) if (old <= this.history.latestCall - this.window) this.forget(old);
|
|
56
43
|
}
|
|
57
44
|
|
|
58
45
|
forget(call) {
|
|
59
46
|
const entry = this.results.get(call);
|
|
60
47
|
if (!entry) return;
|
|
61
|
-
for (
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
if (kept.length) this.occurrences.set(entry.hashes[i], kept);
|
|
66
|
-
else this.occurrences.delete(entry.hashes[i]);
|
|
48
|
+
for (const hash of new Set(entry.hashes)) {
|
|
49
|
+
const kept = (this.occurrences.get(hash) ?? []).filter(item => item.call !== call);
|
|
50
|
+
if (kept.length) this.occurrences.set(hash, kept);
|
|
51
|
+
else this.occurrences.delete(hash);
|
|
67
52
|
}
|
|
68
|
-
this.storedLines -= entry.
|
|
53
|
+
this.history.storedLines -= entry.lines.length;
|
|
69
54
|
this.results.delete(call);
|
|
70
55
|
}
|
|
71
56
|
|
|
72
|
-
/** Provenance for lines the bridge is about to hand to the program: file text, outlines, evidence spans. */
|
|
73
57
|
recordOrigin(path, firstLine, lines, pin = false) {
|
|
58
|
+
if (this.window === 0) return;
|
|
74
59
|
for (let i = 0; i < lines.length; i++) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (
|
|
60
|
+
const text = lines[i];
|
|
61
|
+
if (!substantive(text)) continue;
|
|
62
|
+
if (this.origins.size >= MAX_STORED_LINES && !this.origins.has(text)) this.origins.delete(this.origins.keys().next().value);
|
|
63
|
+
this.origins.set(text, { path, line: firstLine + i });
|
|
64
|
+
if (pin) this.pinned.add(text);
|
|
78
65
|
}
|
|
79
66
|
}
|
|
80
67
|
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
return o !== undefined && this.pinned.has(o.path + ":" + o.line);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/** Length of the identical run between lines[i…] and earlier result `cand`, stopping at pinned lines. */
|
|
87
|
-
runLength(hashes, cand, i) {
|
|
88
|
-
const earlier = this.results.get(cand.call);
|
|
68
|
+
runLength(lines, hashes, candidate, index) {
|
|
69
|
+
const earlier = this.results.get(candidate.call);
|
|
89
70
|
if (!earlier) return 0;
|
|
90
|
-
let
|
|
91
|
-
while (
|
|
92
|
-
|
|
71
|
+
let count = 0;
|
|
72
|
+
while (index + count < lines.length && candidate.index + count < earlier.lines.length
|
|
73
|
+
&& hashes[index + count] === earlier.hashes[candidate.index + count]
|
|
74
|
+
&& lines[index + count] === earlier.lines[candidate.index + count]
|
|
75
|
+
&& !this.pinned.has(lines[index + count])) count++;
|
|
76
|
+
return count;
|
|
93
77
|
}
|
|
94
78
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const candidates = this.occurrences.get(hashes[i]);
|
|
79
|
+
longestRun(lines, hashes, index, call) {
|
|
80
|
+
const candidates = this.occurrences.get(hashes[index]);
|
|
98
81
|
if (!candidates) return null;
|
|
99
82
|
let best = null;
|
|
100
|
-
for (const
|
|
101
|
-
const length = this.runLength(hashes,
|
|
102
|
-
if (length >= MIN_RUN && (!best || length > best.length)) best = {
|
|
83
|
+
for (const candidate of candidates.filter(item => item.call < call).slice(-MAX_CANDIDATES)) {
|
|
84
|
+
const length = this.runLength(lines, hashes, candidate, index);
|
|
85
|
+
if (length >= MIN_RUN && (!best || length > best.length)) best = { ...candidate, length };
|
|
103
86
|
}
|
|
104
87
|
if (!best) return null;
|
|
105
|
-
|
|
106
|
-
|
|
88
|
+
let count = 0;
|
|
89
|
+
for (let i = index; i < index + best.length; i++) if (substantive(lines[i])) count++;
|
|
90
|
+
return count >= MIN_SUBSTANTIVE ? best : null;
|
|
107
91
|
}
|
|
108
92
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
93
|
+
citation(lines, index, run) {
|
|
94
|
+
const earlier = this.results.get(run.call);
|
|
95
|
+
const origin = offset => this.origins.get(lines[index + offset]) ?? earlier?.origins[run.index + offset];
|
|
96
|
+
const first = origin(0);
|
|
112
97
|
if (!first) return "";
|
|
113
|
-
let
|
|
114
|
-
for (let
|
|
115
|
-
const
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
expectLine = o.line + 1;
|
|
119
|
-
} else {
|
|
120
|
-
expectLine++;
|
|
121
|
-
}
|
|
98
|
+
let expected = first.line;
|
|
99
|
+
for (let i = 0; i < run.length; i++) {
|
|
100
|
+
const item = origin(i);
|
|
101
|
+
if (item && (item.path !== first.path || item.line !== expected)) return "";
|
|
102
|
+
expected++;
|
|
122
103
|
}
|
|
123
|
-
return first.path + ":" + first.line + "–" + (
|
|
104
|
+
return first.path + ":" + first.line + "–" + (expected - 1);
|
|
124
105
|
}
|
|
125
106
|
|
|
126
|
-
/**
|
|
127
|
-
* Collapse runs already shown; returns the text to send and remembers exactly that text as call N.
|
|
128
|
-
*/
|
|
129
107
|
dedupe(text, call) {
|
|
130
|
-
|
|
131
|
-
if (lines.length < MIN_RUN) {
|
|
108
|
+
if (this.window === 0) {
|
|
132
109
|
this.stats.returnedChars += text.length;
|
|
133
|
-
this.remember(call, lines);
|
|
134
110
|
return text;
|
|
135
111
|
}
|
|
136
|
-
const
|
|
137
|
-
|
|
112
|
+
const lines = text.split("\n");
|
|
113
|
+
const hashes = Uint32Array.from(lines, hashLine);
|
|
138
114
|
const out = [];
|
|
139
115
|
let collapsedChars = 0;
|
|
140
|
-
for (let i = 0; i < lines.length;
|
|
141
|
-
const run = substantive(lines[i]) ? this.longestRun(hashes,
|
|
142
|
-
if (!run) {
|
|
143
|
-
|
|
144
|
-
i++;
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
const cite = this.citation(hashes, i, run.length);
|
|
116
|
+
for (let i = 0; i < lines.length;) {
|
|
117
|
+
const run = substantive(lines[i]) ? this.longestRun(lines, hashes, i, call) : null;
|
|
118
|
+
if (!run) { out.push(lines[i++]); continue; }
|
|
119
|
+
const cite = this.citation(lines, i, run);
|
|
148
120
|
out.push("⋯ " + run.length + " lines same as #" + run.call + (cite ? " · " + cite : "") + " ⋯");
|
|
149
121
|
for (let k = 0; k < run.length; k++) collapsedChars += lines[i + k].length + 1;
|
|
150
122
|
this.stats.collapsedRuns++;
|
|
@@ -158,21 +130,21 @@ export class SeenLedger {
|
|
|
158
130
|
}
|
|
159
131
|
|
|
160
132
|
remember(call, lines) {
|
|
161
|
-
if (this.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
133
|
+
if (this.window === 0 || call <= this.history.latestCall - this.window || lines.length > MAX_STORED_LINES) return;
|
|
134
|
+
if (this.results.has(call)) this.forget(call);
|
|
135
|
+
for (const old of [...this.results.keys()].sort((a, b) => a - b)) {
|
|
136
|
+
if (this.storedLines + lines.length <= MAX_STORED_LINES) break;
|
|
137
|
+
this.forget(old);
|
|
166
138
|
}
|
|
167
|
-
const hashes =
|
|
139
|
+
const hashes = Uint32Array.from(lines, hashLine);
|
|
140
|
+
const origins = lines.map(line => this.origins.get(line));
|
|
168
141
|
for (let i = 0; i < lines.length; i++) {
|
|
169
|
-
hashes[i] = hashLine(lines[i]);
|
|
170
142
|
if (!substantive(lines[i])) continue;
|
|
171
143
|
let list = this.occurrences.get(hashes[i]);
|
|
172
144
|
if (!list) this.occurrences.set(hashes[i], (list = []));
|
|
173
145
|
list.push({ call, index: i });
|
|
174
146
|
}
|
|
175
|
-
this.results.set(call, { hashes });
|
|
176
|
-
this.storedLines += lines.length;
|
|
147
|
+
this.results.set(call, { hashes, lines, origins });
|
|
148
|
+
this.history.storedLines += lines.length;
|
|
177
149
|
}
|
|
178
150
|
}
|
package/omp-frame.js
CHANGED
|
@@ -127,7 +127,6 @@ function renderPortableFrame(theme, { header, sections = [], state = "pending",
|
|
|
127
127
|
const border = borderPaint(theme, state, borderColor);
|
|
128
128
|
const bgFn = bgFnForState(theme, state);
|
|
129
129
|
const h = box.horizontal;
|
|
130
|
-
const v = box.vertical;
|
|
131
130
|
const cap = h.repeat(3);
|
|
132
131
|
|
|
133
132
|
const paintBar = (leftChar, rightChar, label) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-supernova",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Dual-host CodeMode for Pi/OMP: progressive tool discovery, result bottleneck, and Amdahl Auto parallel.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "AdityaVG13",
|
|
@@ -90,5 +90,9 @@
|
|
|
90
90
|
"bugs": {
|
|
91
91
|
"url": "https://github.com/AdityaVG13/pi-stack/issues"
|
|
92
92
|
},
|
|
93
|
-
"homepage": "https://github.com/AdityaVG13/pi-stack/tree/main/packages/pi-supernova#readme"
|
|
93
|
+
"homepage": "https://github.com/AdityaVG13/pi-stack/tree/main/packages/pi-supernova#readme",
|
|
94
|
+
"dependencies": {
|
|
95
|
+
"acorn": "^8.18.0",
|
|
96
|
+
"string-width": "^8.2.2"
|
|
97
|
+
}
|
|
94
98
|
}
|
package/parallel.js
CHANGED
|
@@ -1,47 +1,55 @@
|
|
|
1
|
+
import { isFunction } from "./decode.js";
|
|
1
2
|
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
const READ_ONLY_TOOLS = new Set(["read", "grep", "glob", "find", "ls", "snap", "evidence", "surface", "asgrep_search", "asgrep_status", "ast_grep", "web_search"]);
|
|
4
|
+
const READ_ONLY_LSP = new Set(["definition", "references", "hover", "symbols", "diagnostics", "implementation", "type_definition", "incoming_calls", "outgoing_calls"]);
|
|
5
|
+
|
|
6
|
+
export function isMutatingTool(name, config = {}, args = {}, definition) {
|
|
7
|
+
if ((config.mutatingTools ?? []).includes(name)) return true;
|
|
8
|
+
if ((config.mutatingPrefixes ?? []).some(prefix => prefix && name.startsWith(prefix))) return true;
|
|
9
|
+
if (definition?.annotations?.readOnlyHint === true) return false;
|
|
10
|
+
if (name === "lsp") {
|
|
11
|
+
const action = args.action ?? args.operation;
|
|
12
|
+
if (READ_ONLY_LSP.has(action)) return false;
|
|
13
|
+
if (["rename", "rename_file"].includes(action)) return args.apply !== false;
|
|
14
|
+
if (action === "code_actions") return args.apply === true;
|
|
15
|
+
return true;
|
|
9
16
|
}
|
|
10
|
-
return
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const out = [];
|
|
14
|
-
for (const thunk of list) out.push(await thunk());
|
|
15
|
-
return out;
|
|
17
|
+
if (name === "todo") return args.op !== "view";
|
|
18
|
+
if (name === "hub") return !["list", "ps", "logs", "describe"].includes(args.op);
|
|
19
|
+
return !READ_ONLY_TOOLS.has(name);
|
|
16
20
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (
|
|
20
|
-
|
|
21
|
-
return count > 1;
|
|
21
|
+
|
|
22
|
+
function requireArray(value, name) {
|
|
23
|
+
if (!Array.isArray(value)) throw new TypeError(name + " requires an array");
|
|
24
|
+
return value;
|
|
22
25
|
}
|
|
26
|
+
|
|
23
27
|
export async function runParallelWave(thunks, meta, options = {}) {
|
|
24
|
-
const list =
|
|
25
|
-
if (list.
|
|
28
|
+
const list = requireArray(thunks, "parallel wave");
|
|
29
|
+
if (list.some(item => !isFunction(item))) throw new TypeError("parallel wave requires functions");
|
|
30
|
+
if (!list.length) return { results: [], mode: "serial", reason: "empty" };
|
|
26
31
|
const { mode = "auto", config = {} } = options;
|
|
27
|
-
const names =
|
|
28
|
-
const
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
const names = meta?.names ?? [];
|
|
33
|
+
const mutating = names.length !== list.length || names.some((name, i) => isMutatingTool(name, config, meta?.calls?.[i]?.args, meta?.definitions?.[i]));
|
|
34
|
+
if (!mutating && (mode === "parallel" || (mode === "auto" && list.length > 1))) {
|
|
35
|
+
// Do not finish a wave while its already-started host calls are still running.
|
|
36
|
+
const settled = await Promise.allSettled(list.map(thunk => Promise.resolve().then(thunk)));
|
|
37
|
+
const failure = settled.find(item => item.status === "rejected");
|
|
38
|
+
if (failure) throw failure.reason;
|
|
39
|
+
return { results: settled.map(item => item.value), mode: "parallel", reason: "independent-reads" };
|
|
32
40
|
}
|
|
33
|
-
const results =
|
|
34
|
-
|
|
35
|
-
return { results, mode: "serial", reason: "single-or-forced" };
|
|
41
|
+
const results = [];
|
|
42
|
+
for (const thunk of list) results.push(await thunk());
|
|
43
|
+
return { results, mode: "serial", reason: mutating ? "mutating" : "single-or-forced" };
|
|
36
44
|
}
|
|
45
|
+
|
|
37
46
|
export async function parallel(items) {
|
|
38
|
-
|
|
39
|
-
return Promise.all(list.map((item) => (item instanceof Function ? item() : item)));
|
|
47
|
+
return Promise.all(requireArray(items, "parallel").map(item => isFunction(item) ? item() : item));
|
|
40
48
|
}
|
|
49
|
+
|
|
41
50
|
export async function pipeline(items, ...stages) {
|
|
42
|
-
let current =
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
51
|
+
let current = requireArray(items, "pipeline");
|
|
52
|
+
if (stages.some(stage => !isFunction(stage))) throw new TypeError("pipeline stages must be functions");
|
|
53
|
+
for (const stage of stages) current = await Promise.all(current.map(item => stage(item)));
|
|
46
54
|
return current;
|
|
47
55
|
}
|