pi-supernova 0.10.0 → 0.10.2
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 +47 -158
- package/docs/CHANGELOG.md +28 -0
- package/package.json +1 -1
- package/src/contract/edit.js +1 -1
- package/src/contract/read.js +5 -3
- package/src/fs/json-read.js +42 -0
- package/src/fs/text-ops.js +1 -1
- package/src/output/outcome.js +11 -7
- package/src/runtime/guest-worker.js +1 -1
- package/src/runtime/reference.js +1 -1
- package/src/runtime/runtime.js +19 -1
package/README.md
CHANGED
|
@@ -12,161 +12,38 @@ Ordinary JavaScript control flow remains available; the guest command bindings
|
|
|
12
12
|
are only `read`, `edit`, `write`, and `bash`. Supernova supplies retrieval,
|
|
13
13
|
transactional file operations, batching, bounded results and the grouped nova UI.
|
|
14
14
|
|
|
15
|
-
## What is new in 0.10.
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
-
|
|
21
|
-
read
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
-
|
|
47
|
-
delivered with acknowledgements within eight I/O slots, not retained as one
|
|
48
|
-
giant host-side batch. Explicit arrays and coalesced reads use the same path.
|
|
49
|
-
- **Output work stays in the worker:** bounded formatting avoids expanding large
|
|
50
|
-
values before clipping. Model-visible source previews retain exact ranges and
|
|
51
|
-
continuation; a clipped preview cannot be passed back as a complete edit view.
|
|
52
|
-
|
|
53
|
-
- **Cancellation no longer crashes the host:** bounded reads and CAS signing use
|
|
54
|
-
abort-checked file handles instead of aborting streams. A failed read can cancel
|
|
55
|
-
sibling reads without an uncaught `AbortError` terminating OMP.
|
|
56
|
-
- **Memory is charged to the guest:** worker-local heap and external buffers
|
|
57
|
-
replace process-wide RSS accounting. Bun enforcement remains best-effort.
|
|
58
|
-
- **Focused internals:** read adapters, file I/O, transactions, worker lifecycle,
|
|
59
|
-
tool ownership, source ranking and rendering have separate modules. All read
|
|
60
|
-
modes, batching, checkpoints and rollback behavior remain supported.
|
|
61
|
-
|
|
62
|
-
**0.9.0 release-candidate baseline:** 315/315 package tests passed on Node and
|
|
63
|
-
Bun, plus actual Pi/OMP and clean tarball installation checks. See the newer
|
|
64
|
-
[cross-platform verification results and coverage limits](#verification) below.
|
|
65
|
-
|
|
66
|
-
### Concurrent file operations
|
|
67
|
-
|
|
68
|
-
Batching and explicit parallel programs remain supported. Within one program,
|
|
69
|
-
`Promise.all` now overlaps native edits and writes to different files, up to eight
|
|
70
|
-
operations at once:
|
|
71
|
-
|
|
72
|
-
```js
|
|
73
|
-
await Promise.all([
|
|
74
|
-
edit("src/a.js", "oldA", "newA"),
|
|
75
|
-
edit("src/b.js", "oldB", "newB"),
|
|
76
|
-
]);
|
|
77
|
-
return await bash("npm test");
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Same-file operations retain submission order. Reads before/after mutations,
|
|
81
|
-
`bash`, edit checkpoints, and overridden mutating tools remain ordering barriers.
|
|
82
|
-
Shell calls inside one program stay sequential; use `programs` with
|
|
83
|
-
`parallel:true` for explicitly independent shell workflows or separate JS workers.
|
|
84
|
-
Edits still stage until program success (or a shell boundary); transactional disk
|
|
85
|
-
commits retain their conflict checks. `await edit(...)` one after another is still
|
|
86
|
-
sequential, and multiple replacements in one file remain one edit operation.
|
|
87
|
-
|
|
88
|
-
## What is new in 0.8.2
|
|
89
|
-
|
|
90
|
-
This patch release fixes shell failure handling, cancellation and parallel-batch
|
|
91
|
-
limits, and makes one-call batching guidance explicit.
|
|
92
|
-
|
|
93
|
-
- **Shell quoting stays intact:** quoted executable paths are no longer unwrapped.
|
|
94
|
-
Shell syntax errors suggest literal `bash({command,args})` with `data` for
|
|
95
|
-
embedded scripts, or a quoted heredoc. Commands are not rewritten or retried.
|
|
96
|
-
- **Validation before commit:** invalid timeouts and null-byte
|
|
97
|
-
arguments are rejected before the shell boundary flushes staged files.
|
|
98
|
-
- **Useful failure output:** long command labels are bounded so the original
|
|
99
|
-
stderr is not crowded out by a repeated script.
|
|
100
|
-
- **Timeouts retain diagnostics:** the outer program deadline stops the worker
|
|
101
|
-
and gives pending host calls a bounded drain to retain shell output. Explicit
|
|
102
|
-
cancellation is reported separately from timeout. The outer `timeoutMs` covers
|
|
103
|
-
every wait and command, including `sleep`.
|
|
104
|
-
- **Parallel budgets fail honestly:** exceeding the shared log or image
|
|
105
|
-
allowance marks the batch failed and stops queued entries. Already-running
|
|
106
|
-
entries settle; their results and completed commits remain. Aggregate logs stay
|
|
107
|
-
capped rather than multiplying the allowance per guest. In 0.8.2 this also
|
|
108
|
-
applied to output text; 0.9.0 makes display-text clipping nonfatal.
|
|
109
|
-
- **Batch known work in one call:** combine independent reads/checks with
|
|
110
|
-
`Promise.all`, then sequence edits and verification in the same program. Use
|
|
111
|
-
another invocation when returned evidence is needed for the next decision.
|
|
112
|
-
|
|
113
|
-
Verified on **macOS / Node 26.7**: 267 package tests (384 repository tests),
|
|
114
|
-
2,328 stress invocations, actual Pi/OMP host checks, lint and both token-budget
|
|
115
|
-
checks. This is not a claim of exhaustive platform or formal mutation testing.
|
|
116
|
-
|
|
117
|
-
## 0.8.0 features and measurements
|
|
118
|
-
|
|
119
|
-
- **Shared program source:** top-level `code` or `file` supplies a batch default;
|
|
120
|
-
entries may override it. A shared program is sent once instead of in every entry,
|
|
121
|
-
and defaults count once against the 48,000-character admission cap.
|
|
122
|
-
- **Explicit object defaults:** `mergeData:true` shallowly overlays per-entry data
|
|
123
|
-
onto common data (entry keys win; nested objects are replaced). Whole-input
|
|
124
|
-
replacement remains the default.
|
|
125
|
-
- **Checkpoint failures throw:** a failed `edit(async () => {...})` rolls back and
|
|
126
|
-
rethrows its original cause; catch explicitly when rejecting a candidate is
|
|
127
|
-
intentional. Ignored failures no longer report success.
|
|
128
|
-
- **Accurate failure cards:** the nova card reads the host's error flag, shows the
|
|
129
|
-
original cause and `committed`/`rolledBack` totals, marks writes whose
|
|
130
|
-
persistence cannot be attributed as attempted, and labels pure JavaScript runs
|
|
131
|
-
instead of "complete".
|
|
132
|
-
- **Historical read limits (superseded in 0.9.0):** errors stated both limits (`160 lines / 8192
|
|
133
|
-
characters`) with copyable recovery (`offset`, `about`, `complete:true`, and
|
|
134
|
-
`Promise.allSettled` for optional siblings). Markdown edits skip code-reference
|
|
135
|
-
searches; exact-symbol evidence excludes generic matches.
|
|
136
|
-
- **Fail-closed images:** unsupported formats (for example BMP) fail before model
|
|
137
|
-
delivery with PNG-conversion guidance, and sets over 16 images / 20 MiB report
|
|
138
|
-
aggregate sizes instead of silently omitting attachments. Pending changes roll back.
|
|
139
|
-
- **Shell follows the program clock:** `bash()` inherits the program's `timeoutMs`;
|
|
140
|
-
explicit per-command limits still win.
|
|
141
|
-
|
|
142
|
-
### Tokens: 0.7.1 to 0.8.0 (`js-tiktoken`, `o200k_base` / `cl100k_base`)
|
|
143
|
-
|
|
144
|
-
| Metric | 0.7.1 | 0.8.0 | Change |
|
|
145
|
-
|---|---:|---:|---:|
|
|
146
|
-
| Standing definition per request | 596 / 588 | 631 / 626 | +35 / +38 |
|
|
147
|
-
| Frozen 6-call mixed workload, total traffic | 9,596 / 9,458 | 9,841 / 9,724 | +2.6% / +2.8% |
|
|
148
|
-
| 16-program job with shared source + data (32 files) | 17,771 / 17,595 | 3,587 / 3,533 | -79.8% / -79.9% |
|
|
149
|
-
| 8 programs sharing a 48-path input | 16,309 / 14,649 | 5,499 / 5,197 | -66.3% / -64.5% |
|
|
150
|
-
|
|
151
|
-
Rows 3-4 deliver identical complete outputs and files; only argument placement
|
|
152
|
-
changes. Row 2 repeats no inputs, so it pays the +35-token guidance and nothing
|
|
153
|
-
else. Method, gates and limits: [TOKEN_COSTS.md](https://github.com/AdityaVG13/pi-stack/blob/main/packages/pi-supernova/docs/TOKEN_COSTS.md).
|
|
154
|
-
|
|
155
|
-
### Speed: engine micro-benchmarks (Apple M5 Max, Node 26.7)
|
|
156
|
-
|
|
157
|
-
| Benchmark | Before | After | Change |
|
|
158
|
-
|---|---:|---:|---:|
|
|
159
|
-
| Package 200-row report (median, 10k iterations) | 0.1665 ms | 0.1314 ms | -21% |
|
|
160
|
-
| Package nested source object (median) | 0.0366 ms | 0.0240 ms | -35% |
|
|
161
|
-
| Idle worker exit | 267 ms | 17 ms | -94% |
|
|
162
|
-
| 8-file read wave p50 / p95 (300 samples) | 1.90 / 2.74 ms | 1.77 / 2.53 ms | -7% / -8% |
|
|
163
|
-
| Cold unbatched p95 vs coalesced warm p95 (8 reads) | 13.85 ms | 2.39 ms | -83% |
|
|
164
|
-
|
|
165
|
-
Identical output hashes before and after. Local engine benchmarks, not end-to-end
|
|
166
|
-
agent latency or provider time.
|
|
167
|
-
|
|
168
|
-
See the [changelog](https://github.com/AdityaVG13/pi-stack/blob/main/packages/pi-supernova/docs/CHANGELOG.md)
|
|
169
|
-
and [token measurements](https://github.com/AdityaVG13/pi-stack/blob/main/packages/pi-supernova/docs/TOKEN_COSTS.md).
|
|
15
|
+
## What is new in 0.10.2
|
|
16
|
+
|
|
17
|
+
- Results print the `mutations:` line only when it informs: files committed or
|
|
18
|
+
rolled back, an uncertain filesystem outcome, or shell side effects left behind
|
|
19
|
+
by a failed program. It had appeared on most results, usually all zeros.
|
|
20
|
+
- Clearer guest errors: naming a variable after a command (`const read = await
|
|
21
|
+
read(...)`) says so, `supernova(...)` inside a program says to call the
|
|
22
|
+
commands directly, and an empty `oldText` explains how to insert.
|
|
23
|
+
|
|
24
|
+
Earlier releases: [changelog](https://github.com/AdityaVG13/pi-stack/blob/main/packages/pi-supernova/docs/CHANGELOG.md).
|
|
25
|
+
|
|
26
|
+
## Tokens: native tool calls vs Supernova
|
|
27
|
+
|
|
28
|
+
Fixed 13-program workload (inspect, reproduce, repair, verify; JSON report update;
|
|
29
|
+
five-term audit), full tool-history replay, `js-tiktoken`. Regenerate with
|
|
30
|
+
`npm run test:tokens --prefix packages/pi-supernova`.
|
|
31
|
+
|
|
32
|
+
| Workload | Tokenizer | Without Supernova batching | Through Supernova 0.10.2 | Reduction |
|
|
33
|
+
|---|---|---:|---:|---:|
|
|
34
|
+
| Mixed 13-program workload | o200k_base | 28,130 | 9,631 | **65.8%** |
|
|
35
|
+
| | cl100k_base | 27,841 | 9,476 | **66.0%** |
|
|
36
|
+
| 16 programs, shared source + data (32 files) | o200k_base | 17,771 | 3,559 | **80.0%** |
|
|
37
|
+
| | cl100k_base | 17,595 | 3,493 | **80.1%** |
|
|
38
|
+
| 8 programs sharing a 48-path input | o200k_base | 16,309 | 5,471 | **66.5%** |
|
|
39
|
+
| | cl100k_base | 14,649 | 5,157 | **64.8%** |
|
|
40
|
+
| Standing tool definition per request | o200k_base / cl100k_base | 908 / 901 | 617 / 606 | 32.0% / 32.7% |
|
|
41
|
+
|
|
42
|
+
All rows deliver identical complete outputs and files; only call and argument
|
|
43
|
+
placement change. The baseline is a frozen non-batched snapshot, not a separate
|
|
44
|
+
product. Excludes provider envelopes, unrelated conversation, reasoning tokens and
|
|
45
|
+
caching; no model-quality claim. Method and speed measurements:
|
|
46
|
+
[TOKEN_COSTS.md](https://github.com/AdityaVG13/pi-stack/blob/main/packages/pi-supernova/docs/TOKEN_COSTS.md).
|
|
170
47
|
|
|
171
48
|
## Install and update
|
|
172
49
|
|
|
@@ -183,8 +60,10 @@ Local checkout installs are for development, not distribution:
|
|
|
183
60
|
pi install /path/to/pi-stack/packages/pi-supernova
|
|
184
61
|
```
|
|
185
62
|
|
|
186
|
-
|
|
187
|
-
|
|
63
|
+
Image reads and returned images are validated with Sharp: at most 32 million
|
|
64
|
+
decoded pixels across all frames, one image at a time, in a subprocess with a
|
|
65
|
+
5-second kill deadline; text-only work never loads it. Keep Sharp's
|
|
66
|
+
platform-specific optional dependencies enabled. For a local checkout, refresh dependencies before starting
|
|
188
67
|
the host:
|
|
189
68
|
|
|
190
69
|
```bash
|
|
@@ -640,7 +519,9 @@ return {verdict, values};
|
|
|
640
519
|
```
|
|
641
520
|
|
|
642
521
|
Selectors support "." (root), .field, .nested[0], .items[0:10], and .["quoted.key"].
|
|
643
|
-
Use json:true for the complete parsed value.
|
|
522
|
+
Use json:true for the complete parsed value. Put the selector in `json`
|
|
523
|
+
(`json:".field"`), not a second `selector` key; a leftover `selector` folds
|
|
524
|
+
when `json` is absent, `true`, or `"."`. Selectors are not full jq: pipes,
|
|
644
525
|
filters, wildcards and negative indices fail explicitly. Missing keys and indices
|
|
645
526
|
fail; false, zero and null remain values. Slices use an exclusive end and clamp to
|
|
646
527
|
array length. Only own JSON properties are traversed; nothing is evaluated.
|
|
@@ -683,6 +564,14 @@ continuation handles.
|
|
|
683
564
|
|
|
684
565
|
## Execution and automatic batching
|
|
685
566
|
|
|
567
|
+
```js
|
|
568
|
+
await Promise.all([
|
|
569
|
+
edit("src/a.js", "oldA", "newA"),
|
|
570
|
+
edit("src/b.js", "oldB", "newB"),
|
|
571
|
+
]);
|
|
572
|
+
return await bash("npm test");
|
|
573
|
+
```
|
|
574
|
+
|
|
686
575
|
Put already-known independent reads and checks in **one** Supernova program using
|
|
687
576
|
`Promise.all` (or `Promise.allSettled` when failures should remain independent).
|
|
688
577
|
Sequence edits and their known verification in that same program. Start another
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.10.2] - 2026-09-24
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Results print the `mutations:` line only when it informs: files committed or
|
|
8
|
+
rolled back, an uncertain filesystem outcome, or (failures only) shell side
|
|
9
|
+
effects that survive the rollback. Five days of sessions carried it on 2,207
|
|
10
|
+
of 2,467 results, 1,928 of them all-zero (~240k characters).
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Redeclaring or shadowing `read`/`edit`/`write`/`bash` (e.g.
|
|
15
|
+
`const read = await read(...)`) now says the name is a supernova command,
|
|
16
|
+
instead of a misleading `data` hint or a bare TDZ error. The `data` hint
|
|
17
|
+
appears only when `data` itself was redeclared.
|
|
18
|
+
- `supernova(...)` called inside a program says to call the commands directly.
|
|
19
|
+
- Empty `oldText` says how to insert: anchor on adjacent existing text.
|
|
20
|
+
|
|
21
|
+
## [0.10.1] - 2026-09-23
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- JSON `read` folds a leftover `selector` key into `json`. The standing schema
|
|
26
|
+
wrote `json:true|selector` as a union on `json`'s value; models sent a second
|
|
27
|
+
option and hit `read does not accept option "selector"`. `json:true` plus
|
|
28
|
+
`selector:".field"` or `selector:"field"` now projects `.field`. Two real
|
|
29
|
+
projections still fail.
|
|
30
|
+
|
|
3
31
|
## [0.9.0] - 2026-09-20
|
|
4
32
|
|
|
5
33
|
### Changed
|
package/package.json
CHANGED
package/src/contract/edit.js
CHANGED
|
@@ -82,7 +82,7 @@ function classifyReplacements(args) {
|
|
|
82
82
|
|
|
83
83
|
if (!Array.isArray(edits) || !edits.length) throw new Error(EDIT_USAGE);
|
|
84
84
|
|
|
85
|
-
for (const e of edits) if (!isString(e?.oldText) || !e.oldText.length || !isString(e?.newText)) throw new Error(EDIT_USAGE + "; replacements require non-empty oldText and string newText");
|
|
85
|
+
for (const e of edits) if (!isString(e?.oldText) || !e.oldText.length || !isString(e?.newText)) throw new Error(EDIT_USAGE + "; replacements require non-empty oldText and string newText" + (isString(e?.oldText) && !e.oldText.length ? "; to insert, include adjacent existing text in oldText and repeat it in newText" : ""));
|
|
86
86
|
|
|
87
87
|
return { kind: "edits", command: "edit", args };
|
|
88
88
|
}
|
package/src/contract/read.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { errorMessage, isString, isObject, isNumber, looksLikePath } from "../shared/decode.js";
|
|
2
|
-
import { sessionJsonArgs, validateJsonRead } from "../fs/json-read.js";
|
|
2
|
+
import { foldJsonSelectorAlias, sessionJsonArgs, validateJsonRead } from "../fs/json-read.js";
|
|
3
3
|
|
|
4
4
|
export const SESSION_URI = /^(?:agent|artifact):\/\//i;
|
|
5
5
|
|
|
@@ -79,11 +79,13 @@ function autoResolve(args) {
|
|
|
79
79
|
export function normalizeRead(params) {
|
|
80
80
|
if (!isObject(params)) throw new Error("read requires an options object");
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
const folded = foldJsonSelectorAlias(params);
|
|
83
|
+
|
|
84
|
+
if (folded.path !== undefined && folded.target !== undefined && folded.path !== folded.target) {
|
|
83
85
|
throw new Error("read accepts either path or target, not both");
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
const args = sessionJsonArgs({ ...
|
|
88
|
+
const args = sessionJsonArgs({ ...folded, path: folded.path ?? folded.target });
|
|
87
89
|
validateJsonRead(args);
|
|
88
90
|
assertReadOptions(args);
|
|
89
91
|
assertReadFlags(args);
|
package/src/fs/json-read.js
CHANGED
|
@@ -96,6 +96,48 @@ export function jsonProjector(json) {
|
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
function isWholeJson(json) {
|
|
100
|
+
return json === true || json === ".";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function coerceOneSelector(value) {
|
|
104
|
+
if (!isString(value) || value.length === 0 || value.length > 2048) throw new Error(SELECTOR_HELP);
|
|
105
|
+
const selector = value.startsWith(".") ? value : "." + value;
|
|
106
|
+
|
|
107
|
+
parseSelector(selector);
|
|
108
|
+
|
|
109
|
+
return selector;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function coerceSelector(value) {
|
|
113
|
+
if (Array.isArray(value)) {
|
|
114
|
+
if (!value.length || value.length > 64) throw new Error("JSON selector list requires 1 to 64 selectors");
|
|
115
|
+
|
|
116
|
+
return value.map(coerceOneSelector);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return coerceOneSelector(value);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Standing copy writes json:true|selector as a union on json's value.
|
|
124
|
+
* Models still send a leftover selector key (often with json:true).
|
|
125
|
+
* Fold it when json is absent or the whole document; reject two real projections.
|
|
126
|
+
*/
|
|
127
|
+
export function foldJsonSelectorAlias(args) {
|
|
128
|
+
if (!isObject(args) || args.selector === undefined) return args;
|
|
129
|
+
|
|
130
|
+
if (args.json !== undefined && !isWholeJson(args.json)) {
|
|
131
|
+
throw new Error("read accepts json or selector, not both; put the selector in json (json:\".field\")");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const next = { ...args, json: coerceSelector(args.selector) };
|
|
135
|
+
|
|
136
|
+
delete next.selector;
|
|
137
|
+
|
|
138
|
+
return next;
|
|
139
|
+
}
|
|
140
|
+
|
|
99
141
|
export function sessionJsonArgs(args) {
|
|
100
142
|
if (!isString(args.path) || !/^(agent|artifact):\/\//i.test(args.path) || !args.path.includes("?")) return args;
|
|
101
143
|
const [uri, query] = args.path.split("?");
|
package/src/fs/text-ops.js
CHANGED
|
@@ -92,7 +92,7 @@ function nearMissPreview(content, oldText) {
|
|
|
92
92
|
}
|
|
93
93
|
function matchReplacement(target, content, replacement) {
|
|
94
94
|
if (!isString(replacement?.oldText) || replacement.oldText.length === 0) {
|
|
95
|
-
throw new Error("edit requires non-empty oldText");
|
|
95
|
+
throw new Error("edit requires non-empty oldText; to insert, include adjacent existing text in oldText and repeat it in newText");
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
if (!isString(replacement?.newText)) throw new Error("edit requires newText");
|
package/src/output/outcome.js
CHANGED
|
@@ -11,14 +11,20 @@ function logsBlock(outcome, tail = "") {
|
|
|
11
11
|
return `\n--- logs${outcome.logTruncated ? " [logs truncated]" : ""}\n${outcome.logs?.join("\n") ?? ""}${tail}`;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
// Printed only when it informs: files committed or rolled back, an uncertain
|
|
15
|
+
// filesystem outcome, or (failures only) shell side effects that survive the
|
|
16
|
+
// rollback. A successful program rolls nothing back, so its shell calls need
|
|
17
|
+
// no warning.
|
|
18
|
+
function mutationText(outcome, failed) {
|
|
15
19
|
const m = outcome.mutations;
|
|
16
20
|
|
|
17
21
|
if (!m) return "";
|
|
18
|
-
const external = m.external ? "; external calls attempted=" + m.external + ", their side effects cannot be rolled back" : "";
|
|
22
|
+
const external = failed && m.external ? "; external calls attempted=" + m.external + ", their side effects cannot be rolled back" : "";
|
|
19
23
|
const uncertain = m.pendingCommits || m.recoveryFailed ? "; filesystem outcome uncertain: inspect disk and any recovery backups before retrying" : "";
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
if (!m.committed && !m.rolledBack && !external && !uncertain) return "";
|
|
26
|
+
|
|
27
|
+
return "\nmutations: committed=" + (m.committed || 0) + " rolledBack=" + (m.rolledBack || 0) + " (file versions)" + external + uncertain;
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
function mutationReceipts(trace) {
|
|
@@ -39,17 +45,15 @@ function splitTurnHint(outcome) {
|
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
function errorText(outcome, call) {
|
|
42
|
-
return `error #${call} ${outcome.wallMs}ms${outcome.returnTruncated ? " [output truncated]" : ""}${mutationText(outcome)}${splitTurnHint(outcome)}
|
|
48
|
+
return `error #${call} ${outcome.wallMs}ms${outcome.returnTruncated ? " [output truncated]" : ""}${mutationText(outcome, true)}${splitTurnHint(outcome)}
|
|
43
49
|
error: ${outcome.error}${logsBlock(outcome)}`;
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
function successText(outcome, call) {
|
|
47
53
|
const truncated = outcome.returnTruncated ? " [return truncated]" : "";
|
|
48
54
|
const hint = outcome.undefinedReturn ? " (no return statement; add `return` to get a value)" : "";
|
|
49
|
-
const m = outcome.mutations;
|
|
50
|
-
const showMutations = m && (m.committed || m.rolledBack || m.external || m.pendingCommits || m.recoveryFailed) ? mutationText(outcome) : "";
|
|
51
55
|
|
|
52
|
-
return `ok #${call} ${outcome.wallMs}ms${truncated}${
|
|
56
|
+
return `ok #${call} ${outcome.wallMs}ms${truncated}${mutationText(outcome, false)}${splitTurnHint(outcome)}${logsBlock(outcome, "\n--- result")}\n${outcome.resultText}${hint}`;
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
function fitOutput(outcome, call, limit, format) {
|
|
@@ -227,7 +227,7 @@ async function handleRun(msg) {
|
|
|
227
227
|
// Existing programs may declare their own data variable; bind it only when supplied.
|
|
228
228
|
compiled = compileGuest(prepared, msg.data);
|
|
229
229
|
} catch (err) {
|
|
230
|
-
postFailure(runId, new Error("JavaScript syntax error: " + err.message + "; no commands ran.
|
|
230
|
+
postFailure(runId, new Error("JavaScript syntax error: " + err.message + "; no commands ran."));
|
|
231
231
|
|
|
232
232
|
return;
|
|
233
233
|
}
|
package/src/runtime/reference.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
export const REFERENCE = `JS body/async arrow; read/write/edit/bash, no fs/import/require. file: workspace JS; data: literals ≤48000 JSON chars.
|
|
3
3
|
read(path|paths,offset=1,limit?) → text/text[]; dirs→entries; PNG/JPEG/GIF/WebP ≤16/20MiB.
|
|
4
4
|
Text ≤64 MiB internally; complete:true requires whole file. Display capped: summarize or read(path,{offset:1,limit:80}); larger files via bash.
|
|
5
|
-
read({path,json:true
|
|
5
|
+
read({path,json:true}) or json:".field" or json:[".a"] → JSON, no jq (16MiB input/64MiB selection). Values retain their types. selector is json's value; a leftover selector key folds.
|
|
6
6
|
read("symbol or question") = read({query,resolve:true}) → view; check status; view.text is a span. read(path,{about}) windows; read({query,evidence:true}) evidence; read({path,outline:true}) declarations.
|
|
7
7
|
write/edit workspace-only; external changes need separately authorized command. write(path,text) or {path,content,append:true}; after read edit or replace:true.
|
|
8
8
|
edit(path,oldText,newText) or {path,edits:[{oldText,newText}]}; unique exact match; numbered windows. edit(view,text) or edit(view,old,new). edit(async()=>{...}) checkpoint: no bash; merge/rollback+rethrow.
|
package/src/runtime/runtime.js
CHANGED
|
@@ -12,6 +12,24 @@ import { errorMessage, isFunction, isObject, isString } from "../shared/decode.j
|
|
|
12
12
|
import { errorContext } from "../shared/syntax-context.js";
|
|
13
13
|
import {validateReturnedImages} from "../shared/image.js";
|
|
14
14
|
|
|
15
|
+
const GUEST_COMMANDS = new Set(["read", "edit", "write", "bash"]);
|
|
16
|
+
|
|
17
|
+
// Hints for mistakes seen in real sessions. Bindings named after a command
|
|
18
|
+
// shadow it (redeclaration or TDZ); `supernova` is the tool, not a guest
|
|
19
|
+
// function; `data` can only collide when the call supplied it. Anything
|
|
20
|
+
// else gets no hint.
|
|
21
|
+
function guestMistakeHint(message) {
|
|
22
|
+
const name = /Identifier '([^']+)' has already been declared/.exec(message)?.[1] ?? /Cannot access '([^']+)' before initialization/.exec(message)?.[1];
|
|
23
|
+
|
|
24
|
+
if (GUEST_COMMANDS.has(name)) return "; `" + name + "` is a supernova command: give your variable another name (e.g. const text = await " + name + "(...))";
|
|
25
|
+
|
|
26
|
+
if (name === "data") return "; the data parameter is already bound as `data`: do not redeclare its binding";
|
|
27
|
+
|
|
28
|
+
if (/\bsupernova is not defined\b/.test(message)) return "; inside a program, call read, edit, write or bash directly (supernova is the tool you are already in)";
|
|
29
|
+
|
|
30
|
+
return "";
|
|
31
|
+
}
|
|
32
|
+
|
|
15
33
|
const ABORT_MESSAGE = "supernova aborted";
|
|
16
34
|
const TIMEOUT_MESSAGE = "supernova timed out: increase the outer timeoutMs (and any shorter bash timeoutMs), or split the program; sleeps count toward the deadline";
|
|
17
35
|
|
|
@@ -241,7 +259,7 @@ class GuestRun {
|
|
|
241
259
|
guestError(msg) {
|
|
242
260
|
const location = normalizeGuestLocation(this.prepared.body, msg.location);
|
|
243
261
|
const where = location ? " (line " + location.line + ":" + location.col + ")" : "";
|
|
244
|
-
void this.complete(this.fail(msg.message + where));
|
|
262
|
+
void this.complete(this.fail(msg.message + where + guestMistakeHint(msg.message)));
|
|
245
263
|
}
|
|
246
264
|
|
|
247
265
|
onMemory(msg) {
|