pi-supernova 0.7.1 → 0.8.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 +127 -44
- package/docs/CHANGELOG.md +87 -0
- package/docs/TOKEN_COSTS.md +151 -3
- package/index.js +5 -3
- package/package.json +1 -1
- package/src/adapters/bash.js +8 -1
- package/src/adapters/edit.js +2 -1
- package/src/adapters/errors.js +1 -1
- package/src/adapters/read.js +28 -7
- package/src/adapters/write.js +14 -5
- package/src/bridge/host-bridge.js +3 -1
- package/src/context/evidence.js +19 -2
- package/src/contract/bash.js +15 -1
- package/src/contract/edit.js +21 -3
- package/src/contract/read.js +15 -0
- package/src/fs/check.js +14 -5
- package/src/fs/text-ops.js +28 -3
- package/src/fs/vfs.js +21 -7
- package/src/fs/workspace.js +8 -3
- package/src/output/bottleneck.js +11 -12
- package/src/output/format.js +24 -16
- package/src/runtime/guest-worker.js +1 -1
- package/src/runtime/program-batch.js +43 -24
- package/src/runtime/program-file.js +4 -1
- package/src/runtime/reference.js +14 -18
- package/src/runtime/runtime.js +17 -4
- package/src/shared/decode.js +30 -0
- package/src/shared/syntax-context.js +31 -0
- package/src/shared/utf8.js +17 -0
- package/src/ui/render.js +37 -13
package/README.md
CHANGED
|
@@ -12,34 +12,56 @@ 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
|
-
##
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
the
|
|
20
|
-
- **
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
- **
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
- **
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
-
|
|
36
|
-
|
|
37
|
-
- **
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
15
|
+
## What is new in 0.8.0
|
|
16
|
+
|
|
17
|
+
- **Shared program source:** top-level `code` or `file` supplies a batch default;
|
|
18
|
+
entries may override it. A shared program is sent once instead of in every entry,
|
|
19
|
+
and defaults count once against the 48,000-character admission cap.
|
|
20
|
+
- **Explicit object defaults:** `mergeData:true` shallowly overlays per-entry data
|
|
21
|
+
onto common data (entry keys win; nested objects are replaced). Whole-input
|
|
22
|
+
replacement remains the default.
|
|
23
|
+
- **Checkpoint failures throw:** a failed `edit(async () => {...})` rolls back and
|
|
24
|
+
rethrows its original cause; catch explicitly when rejecting a candidate is
|
|
25
|
+
intentional. Ignored failures no longer report success.
|
|
26
|
+
- **Accurate failure cards:** the nova card reads the host's error flag, shows the
|
|
27
|
+
original cause and `committed`/`rolledBack` totals, marks writes whose
|
|
28
|
+
persistence cannot be attributed as attempted, and labels pure JavaScript runs
|
|
29
|
+
instead of "complete".
|
|
30
|
+
- **Bounded, explicit reads:** errors state both limits (`160 lines / 8192
|
|
31
|
+
characters`) with copyable recovery (`offset`, `about`, `complete:true`, and
|
|
32
|
+
`Promise.allSettled` for optional siblings). Markdown edits skip code-reference
|
|
33
|
+
searches; exact-symbol evidence excludes generic matches.
|
|
34
|
+
- **Fail-closed images:** unsupported formats (for example BMP) fail before model
|
|
35
|
+
delivery with PNG-conversion guidance, and sets over 16 images / 20 MiB report
|
|
36
|
+
aggregate sizes instead of silently omitting attachments. Pending changes roll back.
|
|
37
|
+
- **Shell follows the program clock:** `bash()` inherits the program's `timeoutMs`;
|
|
38
|
+
explicit per-command limits still win.
|
|
39
|
+
|
|
40
|
+
### Tokens: 0.7.1 to 0.8.0 (`js-tiktoken`, `o200k_base` / `cl100k_base`)
|
|
41
|
+
|
|
42
|
+
| Metric | 0.7.1 | 0.8.0 | Change |
|
|
43
|
+
|---|---:|---:|---:|
|
|
44
|
+
| Standing definition per request | 596 / 588 | 631 / 626 | +35 / +38 |
|
|
45
|
+
| Frozen 6-call mixed workload, total traffic | 9,596 / 9,458 | 9,841 / 9,724 | +2.6% / +2.8% |
|
|
46
|
+
| 16-program job with shared source + data (32 files) | 17,771 / 17,595 | 3,587 / 3,533 | -79.8% / -79.9% |
|
|
47
|
+
| 8 programs sharing a 48-path input | 16,309 / 14,649 | 5,499 / 5,197 | -66.3% / -64.5% |
|
|
48
|
+
|
|
49
|
+
Rows 3-4 deliver identical complete outputs and files; only argument placement
|
|
50
|
+
changes. Row 2 repeats no inputs, so it pays the +35-token guidance and nothing
|
|
51
|
+
else. Method, gates and limits: [TOKEN_COSTS.md](https://github.com/AdityaVG13/pi-stack/blob/main/packages/pi-supernova/docs/TOKEN_COSTS.md).
|
|
52
|
+
|
|
53
|
+
### Speed: engine micro-benchmarks (Apple M5 Max, Node 26.7)
|
|
54
|
+
|
|
55
|
+
| Benchmark | Before | After | Change |
|
|
56
|
+
|---|---:|---:|---:|
|
|
57
|
+
| Package 200-row report (median, 10k iterations) | 0.1665 ms | 0.1314 ms | -21% |
|
|
58
|
+
| Package nested source object (median) | 0.0366 ms | 0.0240 ms | -35% |
|
|
59
|
+
| Idle worker exit | 267 ms | 17 ms | -94% |
|
|
60
|
+
| 8-file read wave p50 / p95 (300 samples) | 1.90 / 2.74 ms | 1.77 / 2.53 ms | -7% / -8% |
|
|
61
|
+
| Cold unbatched p95 vs coalesced warm p95 (8 reads) | 13.85 ms | 2.39 ms | -83% |
|
|
62
|
+
|
|
63
|
+
Identical output hashes before and after. Local engine benchmarks, not end-to-end
|
|
64
|
+
agent latency or provider time.
|
|
43
65
|
|
|
44
66
|
See the [changelog](https://github.com/AdityaVG13/pi-stack/blob/main/packages/pi-supernova/docs/CHANGELOG.md)
|
|
45
67
|
and [token measurements](https://github.com/AdityaVG13/pi-stack/blob/main/packages/pi-supernova/docs/TOKEN_COSTS.md).
|
|
@@ -162,6 +184,13 @@ lines. Structural warnings and source windows are not substitutes for tests.
|
|
|
162
184
|
|
|
163
185
|
### Safe read-modify-write
|
|
164
186
|
|
|
187
|
+
Path-only `read(path)` requires at most **160 lines and 8192 UTF-16 characters**.
|
|
188
|
+
A short document can exceed the character limit. For larger files use
|
|
189
|
+
`read(path,{offset:1,limit:80})` (one-based lines), `read(path,{about:"keywords"})`,
|
|
190
|
+
or `read(path,{complete:true})`. The default raw-text read budget is **31,744
|
|
191
|
+
characters**, derived from the configured call/return budgets, not an unlimited
|
|
192
|
+
full-file buffer. Large JSONL needs a bounded parser through `bash({command,args})`.
|
|
193
|
+
|
|
165
194
|
Plain reads are bounded views, not guaranteed full-file buffers. Use
|
|
166
195
|
`read({path:"file.txt",complete:true})` when code needs the complete file; it
|
|
167
196
|
throws rather than handing back partial text. Prefer `edit` for large-file
|
|
@@ -252,12 +281,21 @@ for short one-off operations. See [token measurements](https://github.com/Aditya
|
|
|
252
281
|
}
|
|
253
282
|
~~~
|
|
254
283
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
284
|
+
Supply 1--32 `programs` entries. Top-level `code` OR `file` supplies an optional
|
|
285
|
+
source default; an entry's own `code` OR `file` replaces it. Without a shared
|
|
286
|
+
source, every entry still requires its own. A shared file is reread when each
|
|
287
|
+
entry runs, so earlier commits can create or update it.
|
|
288
|
+
|
|
289
|
+
Top-level `data` supplies an optional input default. By default, explicit entry
|
|
290
|
+
`data` replaces it entirely, including null, false, 0 and empty strings. With
|
|
291
|
+
**`mergeData:true`**, both the default and every explicit entry input must be
|
|
292
|
+
objects: own entry fields override default fields in a **shallow** merge. Nested
|
|
293
|
+
objects are replaced, not recursively merged. Each guest receives its own copy;
|
|
294
|
+
mutations cannot leak between guests or back into caller-owned inputs.
|
|
295
|
+
|
|
296
|
+
The JSON-encoded array plus any shared code/file/data must fit `maxCodeChars`.
|
|
297
|
+
Common source/input counts once, before expansion. Every entry is validated
|
|
298
|
+
before any program runs. Result representations and output limits are unchanged.
|
|
261
299
|
Entries run sequentially in fresh guests and commit separately. A successful
|
|
262
300
|
entry can create the file executed by a later entry. No implicit retries,
|
|
263
301
|
reordering, shared heap or nested batches are introduced.
|
|
@@ -278,6 +316,26 @@ This avoids repeating literal arguments, without a compression codec or result e
|
|
|
278
316
|
Mutating `data` in one guest cannot affect the next. An entry with `data:null`
|
|
279
317
|
receives null, not the shared object; there is no implicit object merge.
|
|
280
318
|
|
|
319
|
+
For the same program with varying inputs, no temporary script or input file is
|
|
320
|
+
needed. For example, audit both files with a common term:
|
|
321
|
+
|
|
322
|
+
```json
|
|
323
|
+
{
|
|
324
|
+
"code": "const text=await read({path:data.path,complete:true}); return {path:data.path,found:text.includes(data.term),text};",
|
|
325
|
+
"data": {"term":"TODO"},
|
|
326
|
+
"mergeData": true,
|
|
327
|
+
"programs": [
|
|
328
|
+
{"data":{"path":"src/a.js"}},
|
|
329
|
+
{"data":{"path":"src/b.js"}}
|
|
330
|
+
]
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
The complete source is still returned twice, once for each requested file; shared
|
|
335
|
+
arguments do not authorize result deduplication or context rewriting. Existing
|
|
336
|
+
programs that depend on whole-input replacement keep that behavior unless the
|
|
337
|
+
caller explicitly requests `mergeData:true`.
|
|
338
|
+
|
|
281
339
|
The batch stops on the first failed entry, cancellation/deadline, or exhausted
|
|
282
340
|
output/log/image budget. Earlier successful commits remain; only the active
|
|
283
341
|
program's uncommitted writes roll back. Admission errors throw before any program.
|
|
@@ -290,7 +348,7 @@ Set `parallel: true` with `programs` to run independent entries concurrently
|
|
|
290
348
|
(up to 8 at once). Each still gets a fresh guest and its own commit; results stay
|
|
291
349
|
in submission order. A failed entry does not stop siblings. Two entries writing
|
|
292
350
|
the same file race: the losing commit reports a conflict. Sequential remains the
|
|
293
|
-
default. `parallel`
|
|
351
|
+
default. `parallel` and `mergeData` are invalid on a lone `code` or `file` call.
|
|
294
352
|
|
|
295
353
|
The outer deadline, host-call budget, log allowance, text budget and image limits
|
|
296
354
|
are shared across the batch. Individual read budgets are not reduced. Every
|
|
@@ -306,7 +364,10 @@ settings, hide observations, or infer a plan on the agent's behalf.
|
|
|
306
364
|
### Large inputs and report outputs
|
|
307
365
|
|
|
308
366
|
The default program limit is 48,000 UTF-16 code units (configurable via
|
|
309
|
-
`maxCodeChars` and exposed in the tool schema).
|
|
367
|
+
`maxCodeChars` and exposed in the tool schema). The same cap applies separately
|
|
368
|
+
to serialized JSON `data`, including quote/newline escaping and object keys.
|
|
369
|
+
Oversized input fails before commands run and reports its actual serialized size.
|
|
370
|
+
Split larger documents into
|
|
310
371
|
separate invocations: first `write(path, firstChunk)`, then
|
|
311
372
|
`write({path,content:nextChunk,append:true})`. Append uses the complete internal
|
|
312
373
|
file buffer, never a bounded model-facing read; it retains conflict checks and
|
|
@@ -316,7 +377,9 @@ file and publish it only when complete. External write overrides reject append.
|
|
|
316
377
|
|
|
317
378
|
Supernova is a bounded foreground executor, not a durable background-job manager.
|
|
318
379
|
For long archive scans, use resumable chunks or a host background-job tool and write
|
|
319
|
-
progress records under `.work`.
|
|
380
|
+
progress records under `.work`. Shell commands inherit the current program
|
|
381
|
+
`timeoutMs` unless they specify their own; increasing the outer deadline no longer
|
|
382
|
+
leaves a hidden 60-second shell cap. Set the inner `bash` timeout shorter than the
|
|
320
383
|
outer program timeout (for example 10 seconds inside a 20-second program) to retain
|
|
321
384
|
bounded shell diagnostics. A hard guest deadline cannot guarantee pending shell
|
|
322
385
|
output delivery; progress files survive shell execution but staged VFS writes may
|
|
@@ -344,12 +407,21 @@ Inputs are capped at 16 MiB, including staged files. JSON reads require regular
|
|
|
344
407
|
files and reject named pipes without waiting for a writer. The entire input must
|
|
345
408
|
be valid JSON before any selection. Each selector is budgeted before allocating
|
|
346
409
|
the next slice; sparse selector/path/edit arrays are rejected. Selected JSON must
|
|
347
|
-
fit the ordinary read budget
|
|
348
|
-
|
|
410
|
+
fit the ordinary read budget. Oversized selections return a routing object
|
|
411
|
+
`{status:"too_large",path,keys}` (or `length` for an array), not the requested
|
|
412
|
+
array/object: check `status` before calling `.map` or `.filter`, then select
|
|
413
|
+
narrower fields or slices. The input-size cap still throws before projection;
|
|
414
|
+
JSON is never returned malformed or silently truncated. Oversized unwindowed
|
|
349
415
|
plain .json reads also fail with a projection hint. Explicit offset/limit or
|
|
350
416
|
resolve:true still allow raw inspection, but line windows are not JSON documents.
|
|
351
417
|
Do not combine json with complete, line windows, or source views. External read
|
|
352
418
|
overrides reject JSON projection rather than silently ignoring the option.
|
|
419
|
+
Uncaught read errors abort the program, including `return {a:await read(...),
|
|
420
|
+
b:await read(...)}`; earlier successful values are not an implicit partial return.
|
|
421
|
+
For optional sources, explicitly return `await Promise.allSettled(paths.map(path =>
|
|
422
|
+
read(path)))`. This keeps successful text and per-path errors without weakening
|
|
423
|
+
rollback for uncaught failures.
|
|
424
|
+
|
|
353
425
|
Other read options, even false-valued flags, do not bypass a captured external
|
|
354
426
|
read executor; its policy, transforms and failures remain authoritative.
|
|
355
427
|
|
|
@@ -386,9 +458,11 @@ recovery backups before retrying. Import-based mutations and shell side effects
|
|
|
386
458
|
are outside the VFS counters; this is not a filesystem audit.
|
|
387
459
|
|
|
388
460
|
`edit(async () => {...})` creates a nested filesystem checkpoint. It returns
|
|
389
|
-
`{ok:true,committed:true,value}` on success
|
|
390
|
-
|
|
391
|
-
|
|
461
|
+
`{ok:true,committed:true,value}` on success. On failure it rolls back and rethrows
|
|
462
|
+
the cause, so an ignored failed checkpoint cannot report program success. Use
|
|
463
|
+
`try { await edit(async () => {...}); } catch (error) {...}` for deliberate recovery.
|
|
464
|
+
Shell commands, overlapping/nested checkpoints, and concurrent commands outside
|
|
465
|
+
the active callback are rejected. Await the checkpoint before proceeding.
|
|
392
466
|
|
|
393
467
|
## Context, caching and failure fidelity
|
|
394
468
|
|
|
@@ -396,9 +470,15 @@ outside the active callback are rejected. Await the checkpoint before proceeding
|
|
|
396
470
|
is not proof the model still retains an earlier result after compaction.
|
|
397
471
|
- Oversized text reads provide an exact next-line offset. A single line too large
|
|
398
472
|
for the budget fails explicitly instead of pretending it was read completely.
|
|
473
|
+
- Model attachments support PNG, JPEG, GIF and WebP. BMP and other unsupported
|
|
474
|
+
MIME types fail before attachment or commit, rather than causing a provider
|
|
475
|
+
HTTP 400 on the next request. Convert those sources to PNG first; Supernova
|
|
476
|
+
does not silently convert, resize or modify the original image.
|
|
399
477
|
- Returned images remain image content blocks, including in arrays/objects. Images
|
|
400
478
|
not returned by the program stay out of model output. Returned images are limited
|
|
401
|
-
to 16 attachments / 20 MiB
|
|
479
|
+
to 16 attachments / 20 MiB. Overflow fails the program with the aggregate count
|
|
480
|
+
and byte size, returns no images, and rolls back pending writes. Resize or return
|
|
481
|
+
fewer images when necessary.
|
|
402
482
|
- Dense multiline string arrays can render as verbatim source blocks instead of
|
|
403
483
|
escaped string literals. Each block gives its array index and exact UTF-16 length;
|
|
404
484
|
strings and result types are unchanged. This is output framing, not source
|
|
@@ -455,7 +535,10 @@ There is no dependency on or automatic routing to any consumer package.
|
|
|
455
535
|
|
|
456
536
|
CodeMode executes trusted JavaScript in a terminable worker, **not a security
|
|
457
537
|
sandbox**. The four adapters constrain writes/edits to the workspace and allow
|
|
458
|
-
explicit external reads.
|
|
538
|
+
explicit external reads. Absolute temporary-directory paths outside the workspace
|
|
539
|
+
are not write destinations: use a workspace path such as `.work/verification.log`,
|
|
540
|
+
or a separately authorized external command. Errors identify the rejected path
|
|
541
|
+
and workspace, including symlink escapes. JavaScript imports and shell commands still have process
|
|
459
542
|
privileges. Do not run untrusted programs as though these adapters isolate them.
|
|
460
543
|
|
|
461
544
|
Pi preflights the outer `supernova` call. Internal primitives do not emit ordinary
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,93 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## [0.8.1] - 2026-09-19
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Reads, windows, edits and appends reject **non-UTF-8** files with the path and
|
|
10
|
+
a conversion hint instead of returning U+FFFD, which an edit or append could
|
|
11
|
+
have written back as corruption. Prefix windows still drop one partial
|
|
12
|
+
character at the byte cut; diff/receipt snapshots stay tolerant so an explicit
|
|
13
|
+
`replace:true` still overwrites any file.
|
|
14
|
+
- Unknown options now fail loudly on every command: `read` (for example a
|
|
15
|
+
foreign `{start,end}` window, which used to return the whole file) and
|
|
16
|
+
`bash`/`write`/`edit` (`env`, `maxOutputChars`, `mode`, `all` were dropped
|
|
17
|
+
silently). Each error names the option and the supported set.
|
|
18
|
+
- Filesystem failures name the path or command: write and edit no longer say
|
|
19
|
+
"read path is a directory"; `ENOTDIR`/`EACCES`/`EPERM`/`EROFS`/`ENOSPC` no
|
|
20
|
+
longer leak raw codes or the `.supernova-<uuid>.new` temporary; `bash cwd`
|
|
21
|
+
must be a directory (`spawn ENOTDIR` is gone).
|
|
22
|
+
- `readWindow` no longer leaks an unhandled rejection while `finally` awaits
|
|
23
|
+
`file.close()`; failing window reads settle cleanly.
|
|
24
|
+
- Edit target misses report the closest matching line with its exact bytes
|
|
25
|
+
instead of only the file head; multi-edit failures name the entry
|
|
26
|
+
(`edit 2 of 3`); `edit(path,{oldText,newText}|{edits}|{patch})` dispatches.
|
|
27
|
+
- Syntax errors quote the offending source line and column with a caret;
|
|
28
|
+
`file:` programs name the file, and invalid UTF-8 names the file too.
|
|
29
|
+
- JSON projection reports the parse position with the offending line when V8
|
|
30
|
+
provides one, and ignores one leading BOM (also in write checks).
|
|
31
|
+
- Timeout and batch-deadline messages report elapsed time against the limit, and
|
|
32
|
+
a deadline-killed program is no longer reported as a plain program failure.
|
|
33
|
+
|
|
34
|
+
### Internals
|
|
35
|
+
|
|
36
|
+
- 259 package tests, actual Pi + OMP host smoke, isolated Spark run, a
|
|
37
|
+
536-program stress pass, and both frozen token gates.
|
|
38
|
+
|
|
39
|
+
## [0.8.0] - 2026-09-19
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- Program batches accept a top-level `code` OR `file` as a source default, so a
|
|
44
|
+
shared program is sent once instead of in every entry. Entries may still
|
|
45
|
+
override it, defaults count once against the 48,000-character admission cap,
|
|
46
|
+
and every entry keeps its own fresh guest and its own commit.
|
|
47
|
+
- `mergeData: true` opts into a shallow object overlay of the top-level `data`
|
|
48
|
+
object and each entry's own object data (entry keys win; nested objects are
|
|
49
|
+
replaced, not merged). Whole-input replacement stays the default.
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
|
|
53
|
+
- Standing reference now states the read limits and their recovery forms
|
|
54
|
+
(path-only `160 lines / 8192 characters`, `offset`/`about`/`complete:true`,
|
|
55
|
+
one-based windows, the complete-read budget) and the optional-read contract
|
|
56
|
+
(`Promise.allSettled` keeps successful siblings): +35/+38 definition tokens per
|
|
57
|
+
request versus 0.7.1, paid back many times over on repeated-source batches.
|
|
58
|
+
- `bash()` inherits the program's `timeoutMs` instead of an independent 60s
|
|
59
|
+
default; explicit per-command limits still win.
|
|
60
|
+
- Missing-file errors point at directory/source-question recovery and
|
|
61
|
+
`Promise.allSettled`.
|
|
62
|
+
- Result packaging reuses decoded result branches and skips escaped rendering
|
|
63
|
+
when the complete raw framing is provably shorter; settled programs no longer
|
|
64
|
+
arm a 250 ms drain timer.
|
|
65
|
+
|
|
66
|
+
### Fixed
|
|
67
|
+
|
|
68
|
+
- Failed `edit(async () => {...})` checkpoints roll back and rethrow the original
|
|
69
|
+
cause instead of reporting success; an explicit `catch` still recovers.
|
|
70
|
+
- Pi failure cards render as failures: the renderer reads the host's error flag
|
|
71
|
+
from render context, shows the original cause, prints `committed`/`rolledBack`
|
|
72
|
+
totals, marks writes whose persistence cannot be attributed as attempted, and
|
|
73
|
+
labels pure JavaScript execution instead of "complete".
|
|
74
|
+
- Unsupported image formats (for example BMP) fail before model delivery or
|
|
75
|
+
commit with PNG-conversion guidance instead of attaching unusable data.
|
|
76
|
+
- Oversized image sets report aggregate sizes instead of silently dropping
|
|
77
|
+
attachments; pending writes roll back.
|
|
78
|
+
- Rust lifetimes and loop labels no longer produce false edit/write warnings;
|
|
79
|
+
genuinely broken strings, brackets and character literals still warn.
|
|
80
|
+
- Markdown/MDX/RST/TXT edits skip declaration-reference searches for fenced code
|
|
81
|
+
and capital labels; usage evidence ignores declarations, generic calls and
|
|
82
|
+
prefix-only matches.
|
|
83
|
+
- Missing argv data identifies the offending argument index; oversized `data`
|
|
84
|
+
reports its serialized size and a lossless chunked-write recovery.
|
|
85
|
+
|
|
86
|
+
### Internals
|
|
87
|
+
|
|
88
|
+
- 243 package tests, actual Pi and OMP host smoke, isolated Spark run, a
|
|
89
|
+
536-program stress pass, and both frozen token gates. `docs/TOKEN_COSTS.md`
|
|
90
|
+
records the traffic baselines, their limits and the measured 0.7.1 comparisons.
|
|
91
|
+
|
|
5
92
|
## [0.7.1] - 2026-09-17
|
|
6
93
|
|
|
7
94
|
### Fixed
|
package/docs/TOKEN_COSTS.md
CHANGED
|
@@ -21,6 +21,154 @@ The benchmark uses js-tiktoken 1.0.21, pinned as a development dependency. It ru
|
|
|
21
21
|
real Supernova programs in temporary workspaces without provider calls or downloads.
|
|
22
22
|
The token regression gate also runs in the normal test suite.
|
|
23
23
|
|
|
24
|
+
## Unreleased explicit batch reuse (2026-09-19)
|
|
25
|
+
|
|
26
|
+
**79.82% / 79.92% less traffic on the shared-source/object-input workload, not a
|
|
27
|
+
claim of 80% savings on every task.** Source and object defaults remove repeated
|
|
28
|
+
arguments without compression, citations, result elision, context rewriting,
|
|
29
|
+
reduced limits or changes to reasoning settings. Every program still executes in
|
|
30
|
+
a fresh guest with its own commit. No model decision point is removed.
|
|
31
|
+
|
|
32
|
+
The missing capability was combining common executable/input values with distinct
|
|
33
|
+
per-entry inputs without repeating them or first saving helper files. Batches now
|
|
34
|
+
accept top-level `code` OR `file` as a source default. `mergeData:true` explicitly
|
|
35
|
+
opts into a shallow object overlay; the legacy whole-input replacement remains
|
|
36
|
+
the default. Entries can override the source, and nested data objects are replaced,
|
|
37
|
+
not recursively merged. Nothing is inferred or automatically deduplicated.
|
|
38
|
+
|
|
39
|
+
### Frozen eligible workload
|
|
40
|
+
|
|
41
|
+
The pre-feature working tree, including the earlier unreleased papercut fixes,
|
|
42
|
+
ran 16 independent package-doc scaffolds. Each program wrote a LICENSE and README,
|
|
43
|
+
reread both exact staged contents, and returned its complete receipts. The control
|
|
44
|
+
repeated the same executable and literal license/introduction with a distinct
|
|
45
|
+
directory per entry. Its encoded program array was **32,353 characters**, within
|
|
46
|
+
the existing 48,000-character admission cap. This is not an inadmissible baseline.
|
|
47
|
+
|
|
48
|
+
The candidate supplies the same complete code and common data once, plus those
|
|
49
|
+
same per-entry directories. Both arms use **one tool invocation plus the final
|
|
50
|
+
model handoff**, the same 16-program schedule, and all 32 final files. The baseline
|
|
51
|
+
was captured before the implementation; neither program source nor data was padded
|
|
52
|
+
or changed to meet the gate.
|
|
53
|
+
|
|
54
|
+
| Tokenizer | Before | After | Reduction | Argument tokens before / after | Unchanged result tokens |
|
|
55
|
+
| --- | ---: | ---: | ---: | ---: | ---: |
|
|
56
|
+
| o200k_base | 17,771 | 3,587 | **79.82%** | 7,685 / 643 | 1,039 |
|
|
57
|
+
| cl100k_base | 17,595 | 3,533 | **79.92%** | 7,619 / 637 | 1,007 |
|
|
58
|
+
|
|
59
|
+
Accounting is `2*definition + 2*arguments + complete result`. It includes both
|
|
60
|
+
requests' standing definitions, generated arguments and their replay. The complete
|
|
61
|
+
normalized output equals the pre-feature snapshot byte-for-byte, and the contents
|
|
62
|
+
and complete inventory of all 32 files are checked. Only run counters and elapsed
|
|
63
|
+
times are normalized, as in the existing benchmark. No hidden helper files appear.
|
|
64
|
+
|
|
65
|
+
The new gate requires **at least 78% on both tokenizers**, not an argument-only
|
|
66
|
+
percentage. Its fixture is
|
|
67
|
+
[`batch-reuse-baseline.json`](https://github.com/AdityaVG13/pi-stack/blob/main/packages/pi-supernova/tests/efficiency/batch-reuse-baseline.json),
|
|
68
|
+
with SHA-256 `589960c417006630872c16fae9ae5be4f5cdbdddbf86dda9fd9377651352055a`.
|
|
69
|
+
|
|
70
|
+
This workload benefits from substantial repeated source and input. Unique-input,
|
|
71
|
+
large-result, or already-file-backed jobs need not see that gain. Saving a reusable
|
|
72
|
+
program/input file was already an alternative when extra workspace artifacts and
|
|
73
|
+
setup were acceptable; this is not a claim of 80% over that different workflow.
|
|
74
|
+
|
|
75
|
+
### Existing workload stays separate
|
|
76
|
+
|
|
77
|
+
The original six-call schedule, its argument hash, all 13 logical operations,
|
|
78
|
+
failures, decision boundaries and complete outputs remain unchanged. It already
|
|
79
|
+
uses saved programs and does not exercise the new source defaults. Clearer shorter
|
|
80
|
+
standing guidance offsets the new option's schema cost:
|
|
81
|
+
|
|
82
|
+
| Tokenizer | At start of this pass | Now | Additional reduction | Versus original non-batched baseline |
|
|
83
|
+
| --- | ---: | ---: | ---: | ---: |
|
|
84
|
+
| o200k_base | 10,191 | 9,841 | **3.43%** | 65.02% |
|
|
85
|
+
| cl100k_base | 10,067 | 9,724 | **3.41%** | 65.07% |
|
|
86
|
+
|
|
87
|
+
The definition measures 631/626 tokens, versus 681/675 at the start. The original
|
|
88
|
+
traffic gate was strengthened from 40% to 65%, without changing its baseline. An
|
|
89
|
+
initial shorter guidance draft failed the existing recovery/surface checks; the
|
|
90
|
+
copyable offset recovery and explicit selector/view guidance were restored rather
|
|
91
|
+
than removing those requirements. There is no claim of 70--80% on this workload.
|
|
92
|
+
|
|
93
|
+
### Verification and boundaries
|
|
94
|
+
|
|
95
|
+
New source/overlay regressions and the reuse gate failed before implementation.
|
|
96
|
+
The strengthened existing traffic gate also failed first. Current verification:
|
|
97
|
+
239 package tests, both tokenizers' gates, lint, actual Pi argument validation and
|
|
98
|
+
TUI smoke, actual network-denied OMP execution, and 71 targeted tests on an isolated
|
|
99
|
+
Spark copy under Node v24.16.0. The actual-host probes exercise shared source,
|
|
100
|
+
object overlays and independent guest copies.
|
|
101
|
+
|
|
102
|
+
The existing stress runner passed 33,024 independent reads, 20 commits/140 conflicts,
|
|
103
|
+
cancellation, complete-source fidelity and program batches. On Apple M5 Max,
|
|
104
|
+
Node v26.7.0, the 300-sample, eight-file check measured pristine-warm median/p95
|
|
105
|
+
1.645/2.390 ms versus unbatched-cold p95 13.848 ms; its existing acceptance check
|
|
106
|
+
passed. This is a runtime smoke measurement, **not a new end-to-end speedup claim**.
|
|
107
|
+
|
|
108
|
+
Complete-output and file equality prove the tested contracts, not unchanged
|
|
109
|
+
live-model quality. There was no provider/model A/B evaluation, no reasoning-budget
|
|
110
|
+
change, and no compression feature enabled. Deployment, session restart and
|
|
111
|
+
publication are separate; installed OMP packages were not changed by this pass.
|
|
112
|
+
|
|
113
|
+
## Unreleased optimization pass (2026-09-19)
|
|
114
|
+
|
|
115
|
+
Measured against the working tree immediately before this pass, **including the
|
|
116
|
+
unreleased papercut fixes**, not against a published release or the older baselines
|
|
117
|
+
below. Machine: Apple M5 Max, 18 CPUs, 48 GiB RAM, macOS arm64, Node v26.7.0.
|
|
118
|
+
|
|
119
|
+
| Measurement | Before | After | Reduction |
|
|
120
|
+
| --- | ---: | ---: | ---: |
|
|
121
|
+
| Definition tokens, o200k_base | 699 | 658 | 5.87% |
|
|
122
|
+
| Definition tokens, cl100k_base | 691 | 651 | 5.79% |
|
|
123
|
+
| Fixed six-call traffic, o200k_base | 10,317 | 10,030 | 2.78% |
|
|
124
|
+
| Fixed six-call traffic, cl100k_base | 10,179 | 9,899 | 2.75% |
|
|
125
|
+
| 200-row report packaging, median | 0.1665 ms | 0.1314 ms | 21.09% |
|
|
126
|
+
| Nested source packaging, median | 0.0366 ms | 0.0240 ms | 34.53% |
|
|
127
|
+
|
|
128
|
+
A subsequent Cortex papercut follow-up adds explicit data-limit, workspace-write,
|
|
129
|
+
and inherited-timeout guidance. That later definition measures 674/667 tokens
|
|
130
|
+
(o200k_base/cl100k_base), with fixed-workload totals of 10,142/10,011. The table
|
|
131
|
+
above records the optimization checkpoint, before that additional guidance.
|
|
132
|
+
|
|
133
|
+
The Mac/Spark recheck adds optional-read recovery and removes the unsupported BMP
|
|
134
|
+
attachment advertisement. The resulting definition is 681/675 tokens, with fixed
|
|
135
|
+
traffic totals of 10,191/10,067. Both frozen-workload gates still pass. Verification
|
|
136
|
+
now includes 235 Mac package tests, 51 focused tests on an isolated Spark source
|
|
137
|
+
copy (Node v24.16.0), and actual Pi/OMP smoke on Mac. Installed OMP packages were
|
|
138
|
+
not updated by either follow-up.
|
|
139
|
+
|
|
140
|
+
Token savings come only from shorter standing guidance. Arguments, decision
|
|
141
|
+
boundaries and complete logical result text remain unchanged. No compression,
|
|
142
|
+
elision, lower output limits, or history rewriting was introduced or enabled.
|
|
143
|
+
|
|
144
|
+
Packaging now copies only changed branches and skips an escaped-source rendering
|
|
145
|
+
when the existing complete raw rendering is provably shorter. The benchmark runs
|
|
146
|
+
10,000 measured iterations per payload after warmup. Complete packaged-output
|
|
147
|
+
SHA-256 hashes match before/after, including typed values, emitted text, images,
|
|
148
|
+
logs and truncation flags. Local acceptance: identical hashes and at least 10%
|
|
149
|
+
lower median packaging time for both fixtures; this is not a timing-sensitive CI
|
|
150
|
+
gate or an end-to-end agent speed claim.
|
|
151
|
+
|
|
152
|
+
Successful runs no longer leave a 250 ms drain timer alive; early completion of
|
|
153
|
+
pending calls clears its fallback timer. A cold child-process probe exited at
|
|
154
|
+
267 ms before versus 17 ms after, while result delivery itself remained about
|
|
155
|
+
17 ms in both. Cancellation and the 250 ms bound on stuck calls remain tested.
|
|
156
|
+
The 300-sample, eight-file benchmark measured prewarmed median/p95 of
|
|
157
|
+
1.904/2.743 ms before and 1.765/2.530 ms after. These filesystem timings are noisy;
|
|
158
|
+
no generalized I/O speedup is claimed, and prewarming/model latency is excluded.
|
|
159
|
+
|
|
160
|
+
Reproduce the local latency and stress checks:
|
|
161
|
+
|
|
162
|
+
~~~sh
|
|
163
|
+
SUPERNOVA_MEASURE_SAMPLES=300 npm run measure --prefix packages/pi-supernova
|
|
164
|
+
node packages/pi-supernova/tests/efficiency/stress.mjs
|
|
165
|
+
~~~
|
|
166
|
+
|
|
167
|
+
Verification: 229 package tests, both tokenizers' existing frozen-workload gates,
|
|
168
|
+
lint, actual Pi loader/TUI and OMP execution smoke tests, plus the stress runner
|
|
169
|
+
(33,024 independent reads, write contention, cancellation and source fidelity).
|
|
170
|
+
No version bump, installation update, commit or publication is part of this pass.
|
|
171
|
+
|
|
24
172
|
## Workload
|
|
25
173
|
|
|
26
174
|
The baseline is a frozen, non-batched implementation snapshot with program-file
|
|
@@ -72,12 +220,12 @@ text contributes to totals through later history, not as a second charge.
|
|
|
72
220
|
|
|
73
221
|
Observed for 0.6.0 on 2026-09-15, on an Apple M5 Max running macOS and Node v26.7.0.
|
|
74
222
|
|
|
75
|
-
| Tokenizer | Non-batched baseline | Batched baseline (d444eb7) |
|
|
223
|
+
| Tokenizer | Non-batched baseline | Batched baseline (d444eb7) | 0.6.0 | Further reduction | Total reduction |
|
|
76
224
|
| --- | ---: | ---: | ---: | ---: | ---: |
|
|
77
225
|
| o200k_base | 28,130 | 18,535 | 9,843 | **46.90%** | **65.01%** |
|
|
78
226
|
| cl100k_base | 27,841 | 18,310 | 9,726 | **46.88%** | **65.07%** |
|
|
79
227
|
|
|
80
|
-
The gate requires at least
|
|
228
|
+
The gate now requires at least 65% reduction on **each tokenizer for the complete
|
|
81
229
|
workload**, not for every scenario individually. Token counts and reductions are
|
|
82
230
|
computed from the recorded baseline and fresh execution results, not constants
|
|
83
231
|
returned by the runtime. A second gate requires another 19% against the measured
|
|
@@ -86,7 +234,7 @@ as the original workload: removing a decision boundary cannot satisfy this gate.
|
|
|
86
234
|
|
|
87
235
|
### Definition and result accounting
|
|
88
236
|
|
|
89
|
-
The
|
|
237
|
+
The 0.6.0 serialized definition was 602 tokens with o200k_base and 595 with
|
|
90
238
|
cl100k_base, versus 908 and 901 in the frozen non-batched baseline. It retains
|
|
91
239
|
command signatures, complete-read and JSON limits, array-read failure rules,
|
|
92
240
|
transaction boundaries, batch defaults and edit/view guidance on every request.
|
package/index.js
CHANGED
|
@@ -212,6 +212,7 @@ export function registerCodeMode(pi) {
|
|
|
212
212
|
|
|
213
213
|
function rejectLoneParallel(params) {
|
|
214
214
|
if (params?.parallel !== undefined) throw new Error("parallel applies to the programs array; no commands ran");
|
|
215
|
+
if (params?.mergeData !== undefined) throw new Error("mergeData applies to the programs array; no commands ran");
|
|
215
216
|
}
|
|
216
217
|
|
|
217
218
|
function bindRunSignal(signal) {
|
|
@@ -224,8 +225,8 @@ export function registerCodeMode(pi) {
|
|
|
224
225
|
return { runController, abortRun };
|
|
225
226
|
}
|
|
226
227
|
|
|
227
|
-
function openRunBridge(ctx, runCwd, budget, runController) {
|
|
228
|
-
const runBridge = bridge.fork({ getCwd: () => runCwd, budget });
|
|
228
|
+
function openRunBridge(ctx, runCwd, budget, runController, timeoutMs) {
|
|
229
|
+
const runBridge = bridge.fork({ getCwd: () => runCwd, budget, timeoutMs });
|
|
229
230
|
runBridge.bindCallContext(ctx, runController.signal);
|
|
230
231
|
runBridge.resetCallBudget();
|
|
231
232
|
|
|
@@ -332,6 +333,7 @@ export function registerCodeMode(pi) {
|
|
|
332
333
|
data: Type.Optional(Type.Unknown()),
|
|
333
334
|
}, {additionalProperties:false}), {minItems:1,maxItems:32})),
|
|
334
335
|
parallel: Type.Optional(Type.Boolean()),
|
|
336
|
+
mergeData: Type.Optional(Type.Boolean()),
|
|
335
337
|
}),
|
|
336
338
|
// One self-owned result frame is shared by Pi and OMP; renderCall stays empty
|
|
337
339
|
// so separate call/result slots cannot duplicate the lifecycle card.
|
|
@@ -345,7 +347,7 @@ export function registerCodeMode(pi) {
|
|
|
345
347
|
cancelWarmTimer();
|
|
346
348
|
const runCwd = isString(ctx?.cwd) && ctx.cwd ? ctx.cwd : cwd;
|
|
347
349
|
const { runController, abortRun } = bindRunSignal(signal);
|
|
348
|
-
const runBridge = openRunBridge(ctx, runCwd, budget, runController);
|
|
350
|
+
const runBridge = openRunBridge(ctx, runCwd, budget, runController, params?.timeoutMs);
|
|
349
351
|
const call = ++programSeq;
|
|
350
352
|
runBridge.ledger.beginProgram(call);
|
|
351
353
|
const emitProgress = progressEmitter(onUpdate);
|
package/package.json
CHANGED
package/src/adapters/bash.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
1
2
|
import { isString } from "../shared/decode.js";
|
|
2
3
|
import { unwrapIfFullyQuoted } from "../fs/text-ops.js";
|
|
3
4
|
import { sourceForReferences } from "../fs/source-window.js";
|
|
@@ -35,6 +36,12 @@ export function createBash(ctx) {
|
|
|
35
36
|
const { literal, command, argv } = parseBash(params);
|
|
36
37
|
const targetCwd = params?.cwd ? await resolveWorkspacePath(cwd, params.cwd, "bash cwd", true) : cwd;
|
|
37
38
|
|
|
39
|
+
if (params?.cwd !== undefined) {
|
|
40
|
+
const st = await fs.stat(targetCwd).catch(() => null);
|
|
41
|
+
|
|
42
|
+
if (!st?.isDirectory()) throw new Error("bash cwd is not a directory: " + params.cwd);
|
|
43
|
+
}
|
|
44
|
+
|
|
38
45
|
const transactionBarrier = await vfs.prepareExternalMutation("bash");
|
|
39
46
|
let res;
|
|
40
47
|
|
|
@@ -43,7 +50,7 @@ export function createBash(ctx) {
|
|
|
43
50
|
cwd: targetCwd,
|
|
44
51
|
env: hooks.commandEnv(),
|
|
45
52
|
commandLabel: literal ? command : undefined,
|
|
46
|
-
timeoutMs: params?.timeoutMs,
|
|
53
|
+
timeoutMs: params?.timeoutMs === undefined ? config.timeoutMs : params.timeoutMs,
|
|
47
54
|
signal,
|
|
48
55
|
maxOutputChars: config.maxCallResultChars,
|
|
49
56
|
});
|
package/src/adapters/edit.js
CHANGED
|
@@ -131,6 +131,7 @@ export function createEdit(ctx) {
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
async function changedDeclarationRefs(cwd, target, original, updated, diff, signal) {
|
|
134
|
+
if (/\.(md|mdx|rst|txt)$/i.test(target)) return "";
|
|
134
135
|
const names = collectChangedNames(target, original, updated, diff);
|
|
135
136
|
|
|
136
137
|
if (names.size === 0) return "";
|
|
@@ -143,7 +144,7 @@ export function createEdit(ctx) {
|
|
|
143
144
|
} catch (error) {
|
|
144
145
|
signal?.throwIfAborted();
|
|
145
146
|
|
|
146
|
-
return "references unavailable: " + error.message;
|
|
147
|
+
return "references unavailable: " + String(error.message).slice(0, 512);
|
|
147
148
|
}
|
|
148
149
|
}
|
|
149
150
|
|
package/src/adapters/errors.js
CHANGED
|
@@ -25,7 +25,7 @@ export function imageTooLarge(rel, size) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export function missingFile(targetPath) {
|
|
28
|
-
const error = new Error("no such file: " + targetPath + " (locate it with read using a directory path or source question)");
|
|
28
|
+
const error = new Error("no such file: " + targetPath + " (locate it with read using a directory path or source question; use Promise.allSettled for optional reads to retain successful siblings)");
|
|
29
29
|
error.code = "ENOENT";
|
|
30
30
|
return error;
|
|
31
31
|
}
|