pi-supernova 0.0.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/index.js ADDED
@@ -0,0 +1,227 @@
1
+
2
+ let Type;
3
+ try {
4
+ Type = (await import("typebox")).Type;
5
+ } catch {
6
+ Type = {
7
+ Object: (props, opts) => ({ type: "object", properties: props || {}, additionalProperties: false, ...opts }),
8
+ String: (opts) => ({ type: "string", ...opts }),
9
+ Integer: (opts) => ({ type: "integer", ...opts }),
10
+ Optional: (s) => ({ ...s }),
11
+ };
12
+ }
13
+
14
+ import { isString, isFunction } from "./decode.js";
15
+ import { buildCatalog, searchCatalog, describeTool } from "./catalog.js";
16
+ import { loadConfig } from "./config.js";
17
+ import { createHostBridge } from "./host-bridge.js";
18
+ import { runGuestProgram } from "./runtime.js";
19
+ import {
20
+ extractOperationsFromCode,
21
+ renderSupernovaCall,
22
+ renderSupernovaResult,
23
+ SafeText,
24
+ } from "./render.js";
25
+
26
+ export { extractOperationsFromCode, renderSupernovaCall, renderSupernovaResult, SafeText };
27
+
28
+ function result(text, details) {
29
+ return { content: [{ type: "text", text }], details };
30
+ }
31
+
32
+ const TOOL_DESCRIPTION = `Execute JavaScript that orchestrates host tools in one shot (Code Mode).
33
+
34
+ Inside the program you get:
35
+ nova.search(query) — thin catalog hits (name + one-liner)
36
+ nova.describe(name) — full parameter summary on demand
37
+ nova.call(name, args) — invoke a host tool (or native adapter)
38
+ nova.callMany([{name,args}]) — Auto parallel wave (serial if any mutating)
39
+ parallel(thunks) / pipeline(items, ...stages)
40
+
41
+ Prefer search→describe→call. Keep intermediates in the program; return a shaped value.
42
+ Schemas are NOT dumped into the system prompt — discover them inside the runtime.`;
43
+
44
+ export default function piSupernova(pi) {
45
+ const config = loadConfig();
46
+ let cwd = process.cwd();
47
+ let catalog = [];
48
+
49
+ const bridge = createHostBridge({
50
+ pi,
51
+ config,
52
+ getCwd: () => cwd,
53
+ });
54
+
55
+ function refreshCatalog() {
56
+ let tools = [];
57
+ try {
58
+ if (isFunction(pi.getAllTools)) {
59
+ tools = pi.getAllTools() || [];
60
+ }
61
+ } catch {
62
+ tools = [];
63
+ }
64
+ catalog = buildCatalog(tools, config.excludeTools || []);
65
+ return catalog;
66
+ }
67
+
68
+ function makeNovaApi() {
69
+ return {
70
+ search(query, limit) {
71
+ const cat = catalog.length ? catalog : refreshCatalog();
72
+ const lim = Number.isInteger(limit) ? limit : config.maxSearchResults;
73
+ return searchCatalog(cat, query, lim);
74
+ },
75
+ describe(name) {
76
+ const cat = catalog.length ? catalog : refreshCatalog();
77
+ return describeTool(cat, name);
78
+ },
79
+ async call(name, args) {
80
+ return bridge.call(name, args);
81
+ },
82
+ async callMany(calls) {
83
+ return bridge.callMany(calls);
84
+ },
85
+ async speculate(fn) {
86
+ bridge.beginSpeculation();
87
+ try {
88
+ const val = await fn();
89
+ await bridge.commitSpeculation();
90
+ return { ok: true, committed: true, value: val };
91
+ } catch (err) {
92
+ bridge.rollbackSpeculation();
93
+ return { ok: false, committed: false, error: err instanceof Error ? err.message : String(err) };
94
+ }
95
+ },
96
+ async surface(filePath) {
97
+ return bridge.call("surface", { path: filePath });
98
+ },
99
+ async snap(query, targetPath) {
100
+ return bridge.call("snap", { query, path: targetPath });
101
+ },
102
+ has(name) {
103
+ return bridge.hasExecutor(name) || catalog.some((t) => t.name === name);
104
+ },
105
+ };
106
+ }
107
+
108
+ pi.registerTool({
109
+ name: "supernova",
110
+ label: "Supernova",
111
+ description: TOOL_DESCRIPTION,
112
+ promptSnippet: "Compose multiple host tools in one JavaScript program via supernova",
113
+ promptGuidelines: [
114
+ "Use supernova when a task needs multi-step tool composition, loops, filtering, or parallel reads.",
115
+ "Discover tools with nova.search / nova.describe inside the program — do not guess full schemas.",
116
+ "Return a compact shaped value; intermediates stay in the runtime.",
117
+ ],
118
+ parameters: Type.Object({
119
+ code: Type.String({
120
+ description:
121
+ "JavaScript async body or arrow. Globals: nova/tools, parallel, pipeline, console.",
122
+ }),
123
+ timeoutMs: Type.Optional(
124
+ Type.Integer({
125
+ minimum: 1000,
126
+ description: "Hard timeout in ms (default from supernova.json / package default)",
127
+ }),
128
+ ),
129
+ }),
130
+ // "self" = we paint a muted violet/grey-blue card (see SafeText framing).
131
+ // "default" uses the host's loud green tool panels and can fall back to raw JSON args.
132
+ renderShell: "self",
133
+ renderCall: renderSupernovaCall,
134
+ renderResult: renderSupernovaResult,
135
+ async execute(_id, params, signal, onUpdate, ctx) {
136
+ if (ctx && isString(ctx.cwd) && ctx.cwd) cwd = ctx.cwd;
137
+ const runController = new AbortController();
138
+ const abortRun = () => runController.abort(signal?.reason);
139
+ if (signal?.aborted) abortRun();
140
+ else signal?.addEventListener("abort", abortRun, { once: true });
141
+
142
+ bridge.bindCallContext(ctx, runController.signal);
143
+ bridge.resetCallBudget();
144
+ refreshCatalog();
145
+ bridge.beginSpeculation();
146
+
147
+ bridge.setCallListener((_record, allTrace) => {
148
+ if (isFunction(onUpdate)) {
149
+ try {
150
+ onUpdate({
151
+ content: [{ type: "text", text: "" }],
152
+ details: { trace: allTrace, running: true },
153
+ });
154
+ } catch {}
155
+ }
156
+ });
157
+
158
+ const runConfig = {
159
+ ...config,
160
+ timeoutMs: Number.isInteger(params?.timeoutMs) ? params.timeoutMs : config.timeoutMs,
161
+ };
162
+
163
+ let outcome;
164
+ try {
165
+ outcome = await runGuestProgram({
166
+ code: String(params?.code || ""),
167
+ nova: makeNovaApi(),
168
+ config: runConfig,
169
+ signal: runController.signal,
170
+ onTimeout: abortRun,
171
+ });
172
+ } finally {
173
+ signal?.removeEventListener("abort", abortRun);
174
+ }
175
+
176
+ const trace = bridge.getTrace();
177
+ if (!outcome.ok) {
178
+ bridge.rollbackSpeculation();
179
+ let text = `Supernova error (${outcome.wallMs}ms):\n${outcome.error}`;
180
+ if (outcome.logs?.length) text += `\n\nLogs:\n${outcome.logs.join("\n")}`;
181
+ return result(text, {
182
+ ok: false,
183
+ error: outcome.error,
184
+ wallMs: outcome.wallMs,
185
+ logs: outcome.logs,
186
+ trace,
187
+ });
188
+ }
189
+
190
+ await bridge.commitSpeculation();
191
+ let text = `Supernova ok (${outcome.wallMs}ms)`;
192
+ if (outcome.returnTruncated) text += " [return truncated]";
193
+ if (outcome.logs?.length) text += `\n\nLogs:\n${outcome.logs.join("\n")}`;
194
+ text += `\n\nResult:\n${outcome.resultText}`;
195
+ return result(text, {
196
+ ok: true,
197
+ wallMs: outcome.wallMs,
198
+ returnTruncated: outcome.returnTruncated,
199
+ logTruncated: outcome.logTruncated,
200
+ logs: outcome.logs,
201
+ result: outcome.result,
202
+ trace,
203
+ });
204
+ },
205
+ });
206
+
207
+ pi.on("session_start", (_event, ctx) => {
208
+ if (ctx && isString(ctx.cwd) && ctx.cwd) cwd = ctx.cwd;
209
+ refreshCatalog();
210
+ });
211
+
212
+ pi.registerCommand("supernova", {
213
+ description: "Show pi-supernova status (catalog size, captured executors)",
214
+ handler: async (_args, ctx) => {
215
+ refreshCatalog();
216
+ const captured = [...bridge.executors.keys()].sort();
217
+ const natives = Object.keys(bridge.natives).sort();
218
+ const lines = [
219
+ `pi-supernova catalog: ${catalog.length} tools`,
220
+ `captured executors: ${captured.length ? captured.join(", ") : "(none yet — load this package early)"}`,
221
+ `native adapters: ${natives.join(", ")}`,
222
+ `timeoutMs=${config.timeoutMs} maxCallResultChars=${config.maxCallResultChars} maxBridgeCalls=${config.maxBridgeCalls}`,
223
+ ];
224
+ ctx.ui.notify(lines.join("\n"), "info");
225
+ },
226
+ });
227
+ }
package/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "pi-supernova",
3
+ "version": "0.0.1",
4
+ "description": "Dual-host CodeMode for Pi/OMP: progressive tool discovery, result bottleneck, and Amdahl Auto parallel.",
5
+ "type": "module",
6
+ "author": "AdityaVG13",
7
+ "license": "MIT",
8
+ "keywords": [
9
+ "pi-package",
10
+ "pi",
11
+ "omp",
12
+ "oh-my-pi",
13
+ "pi-coding-agent",
14
+ "pi-extension",
15
+ "extension",
16
+ "codemode",
17
+ "supernova",
18
+ "progressive-discovery"
19
+ ],
20
+ "engines": {
21
+ "node": ">=22.0.0"
22
+ },
23
+ "main": "./index.js",
24
+ "exports": "./index.js",
25
+ "files": [
26
+ "index.js",
27
+ "catalog.js",
28
+ "host-bridge.js",
29
+ "bottleneck.js",
30
+ "parallel.js",
31
+ "runtime.js",
32
+ "config.js",
33
+ "config.default.json",
34
+ "README.md",
35
+ "LICENSE",
36
+ "CHANGELOG.md",
37
+ "snap.js",
38
+ "diff.js",
39
+ "render.js",
40
+ "surface.js",
41
+ "decode.js"
42
+ ],
43
+ "scripts": {
44
+ "test": "node --test test/*.test.mjs",
45
+ "prepublishOnly": "npm test && node ../../scripts/preflight.mjs"
46
+ },
47
+ "pi": {
48
+ "extensions": [
49
+ "./index.js"
50
+ ]
51
+ },
52
+ "omp": {
53
+ "extensions": [
54
+ "./index.js"
55
+ ]
56
+ },
57
+ "peerDependencies": {
58
+ "@earendil-works/pi-coding-agent": "*",
59
+ "@earendil-works/pi-tui": "*",
60
+ "typebox": "*"
61
+ },
62
+ "peerDependenciesMeta": {
63
+ "@earendil-works/pi-coding-agent": {
64
+ "optional": true
65
+ },
66
+ "@earendil-works/pi-tui": {
67
+ "optional": true
68
+ },
69
+ "typebox": {
70
+ "optional": true
71
+ }
72
+ },
73
+ "devDependencies": {
74
+ "typebox": "^1.0.0",
75
+ "@earendil-works/pi-tui": "^0.84.0"
76
+ },
77
+ "publishConfig": {
78
+ "access": "public"
79
+ },
80
+ "repository": {
81
+ "type": "git",
82
+ "url": "git+https://github.com/AdityaVG13/pi-stack.git",
83
+ "directory": "packages/pi-supernova"
84
+ },
85
+ "bugs": {
86
+ "url": "https://github.com/AdityaVG13/pi-stack/issues"
87
+ },
88
+ "homepage": "https://github.com/AdityaVG13/pi-stack/tree/main/packages/pi-supernova#readme"
89
+ }
package/parallel.js ADDED
@@ -0,0 +1,48 @@
1
+
2
+ import { isString } from "./decode.js";
3
+
4
+ export function isMutatingTool(name, config) {
5
+ const exact = new Set(config.mutatingTools || []);
6
+ if (exact.has(name)) return true;
7
+ const prefixes = config.mutatingPrefixes || [];
8
+ for (const prefix of prefixes) {
9
+ if (isString(prefix) && prefix.length > 0 && name.startsWith(prefix)) return true;
10
+ }
11
+ return false;
12
+ }
13
+
14
+ export async function runParallelWave(thunks, meta, options = {}) {
15
+ const list = Array.isArray(thunks) ? thunks : [];
16
+ if (list.length === 0) {
17
+ return { results: [], mode: "serial", reason: "empty" };
18
+ }
19
+ const mode = options.mode || "auto";
20
+ const names = Array.isArray(meta?.names) ? meta.names : [];
21
+ const config = options.config || {};
22
+ const anyMutating = names.some((n) => isString(n) && isMutatingTool(n, config));
23
+ const useParallel = mode === "parallel" || (mode === "auto" && !anyMutating && list.length > 1);
24
+
25
+ if (!useParallel) {
26
+ const out = [];
27
+ for (const thunk of list) {
28
+ out.push(await thunk());
29
+ }
30
+ return { results: out, mode: "serial", reason: anyMutating ? "mutating" : "single-or-forced" };
31
+ }
32
+
33
+ const results = await Promise.all(list.map((thunk) => thunk()));
34
+ return { results, mode: "parallel", reason: "independent-reads" };
35
+ }
36
+
37
+ export async function parallel(items) {
38
+ const list = Array.isArray(items) ? items : [];
39
+ return Promise.all(list.map((item) => (item instanceof Function ? item() : item)));
40
+ }
41
+
42
+ export async function pipeline(items, ...stages) {
43
+ let current = Array.isArray(items) ? items.slice() : [];
44
+ for (const stage of stages) {
45
+ current = await Promise.all(current.map((item) => stage(item)));
46
+ }
47
+ return current;
48
+ }