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 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
- ## Unreleased
16
-
17
- - **`parallel: true` on `programs`:** independent entries run at once (up to 8),
18
- keep result order, and do not stop siblings on failure. Sequential is still
19
- the default.
20
- - **JSON `.length`:** `read({path, json:".items.length"})` returns the array
21
- length without dumping the array.
22
- - Prefer `edit` for a file you already read; `write` still replaces the file.
23
-
24
- ## What is new in 0.7.0
25
-
26
- - **Faster batches:** guest workers pipeline their successor, so sequential
27
- programs run about twice as fast with no token cost.
28
- - **Hardened guest:** `process.kill` is sealed out; patch hunks report
29
- relocation and ambiguous hunks fail instead of misapplying.
30
- - **Leaner receipts:** multi-edit output caps at 32 matches with exact totals,
31
- write receipts go workspace-relative, evidence/outline reads go compact.
32
-
33
- ## What is new in 0.6.0
34
-
35
- - **Shared batch input:** supply top-level `data` once; each program gets an
36
- independent copy unless it supplies its own replacement data.
37
- - **Conflict protection:** byte snapshots survive partial reads and body-cache
38
- eviction; receipt generation cannot silently rebase a pending write.
39
- - **Read fidelity:** staged declarations remain discoverable in large/new files,
40
- line windows preserve source endings, and `complete` always means the whole file.
41
- - **Explicit failures:** incompatible read modes, budget-limited matches, captured
42
- overrides and conflicting new-file aliases no longer silently change outcomes.
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
- Use programs instead of top-level code/file. Supply 1--32 entries, each with
256
- code OR file and optional data. Top-level data supplies an optional default for
257
- each entry; explicit entry data replaces it entirely, including null, false, 0
258
- and empty strings. Every guest receives its own copy, not a shared mutable heap.
259
- The JSON-encoded array (or `{programs,data}` when defaults are supplied) must fit
260
- maxCodeChars. Common input counts once; result representations and output limits are unchanged.
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` is invalid on a lone `code` or `file` call.
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). Split larger documents into
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`. Set the inner `bash` timeout shorter than the
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 or the
348
- read throws; it is never returned as malformed/truncated JSON. Oversized unwindowed
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 or `{ok:false,committed:false,error}` on
390
- failure. Shell commands, overlapping/nested checkpoints, and concurrent commands
391
- outside the active callback are rejected. Await the checkpoint before proceeding.
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; resize or return fewer when necessary.
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. JavaScript imports and shell commands still have process
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
@@ -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) | Current | Further reduction | Total reduction |
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 40% reduction on **each tokenizer for the complete
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 current serialized definition is 602 tokens with o200k_base and 595 with
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-supernova",
3
- "version": "0.7.1",
3
+ "version": "0.8.1",
4
4
  "description": "CodeMode for Pi and OMP: read, edit, write and bash, with transactional files, source views and shared-input program batches.",
5
5
  "type": "module",
6
6
  "author": "AdityaVG13",
@@ -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
  });
@@ -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
 
@@ -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
  }