pi-crew 0.9.44 → 0.9.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,82 @@
3
3
  > **Note:** `atomic-write-v2.ts` / `AtomicWriter` mentioned in historical entries below was consolidated into `atomic-write.ts` as of v0.9.42. This changelog is preserved as historical record — the migration was completed (the v2 class was never adopted; v1 won on simplicity + symlink-safety + link+unlink atomicity). See `docs/migration/atomic-write-v2-migration.md` for the decision rationale.
4
4
 
5
5
 
6
+ ## [0.9.46] — UI stability + notification coalescing (2026-07-20)
7
+
8
+ Three user-facing UI/notification fixes layered on top of the v0.9.45
9
+ remediation, plus CI hygiene. All verified live in a restarted Pi session.
10
+
11
+ ### UI flicker — stop render-path snapshot-cache deletes
12
+
13
+ The run-snapshot cache has a deliberate stale-while-revalidate design so the
14
+ widget always shows a populated snapshot. That was defeated by three hot-path
15
+ call sites in `lifecycle-handlers.ts` that hard-deleted cache entries; the worst
16
+ ran `invalidate(undefined)` on every ~160ms fallback tick, wiping ALL entries
17
+ → `activeWidgetRuns` dropped every run to "(loading…)" until the async preload
18
+ rebuilt the cache → continuous flicker across the widget, powerbar, and live
19
+ sidebar/dashboard. (`src/extension/registration/lifecycle-handlers.ts`)
20
+
21
+ **Fix:** never hard-delete from the render path. A no-runId tick is now a no-op;
22
+ a specific runId calls `refreshIfStale` (stale-while-revalidate); the two
23
+ `fs.watch` change handlers call `refresh` (rebuild-in-place). All three keep the
24
+ entry populated so `get()` never returns `undefined`.
25
+
26
+ ### TUI width-overflow crash — delegate `visibleWidth` to pi-tui
27
+
28
+ `Rendered line N exceeds terminal width (160 > 159)` killed the host Pi process
29
+ whenever an agent emitted an emoji that pi-crew's hand-maintained `WIDE_RANGES`
30
+ table counted as 1 column but pi-tui (the renderer) counted as 2 (e.g. `⏳`
31
+ U+23F3). pi-crew truncated/padded to its own measure, pi-tui re-measured and
32
+ hard-aborted. This was patched twice before by adding individual codepoints —
33
+ whack-a-mole, since ANY emoji in agent output could trigger it.
34
+ (`src/utils/visual.ts`)
35
+
36
+ **Definitive fix:** `visibleWidth` now delegates to pi-tui's own `visibleWidth`
37
+ (the function that drives the crash assert). truncate/pad/wrap therefore always
38
+ agree with the renderer; an emoji-width mismatch is structurally impossible. The
39
+ hand-maintained `WIDE_RANGES`/`isWideCodePoint` table is removed.
40
+
41
+ ### Notification coalescing — N drip → 1 consolidated wake-up
42
+
43
+ Launching N background subagents (no `batch_id`) produced N separate "changed
44
+ state" wake-ups, delivered one-per-turn at turn boundaries; after the leader
45
+ joined them all, redundant per-agent notices kept dripping in over later turns.
46
+ (`src/extension/registration/subagent-manager-setup.ts`)
47
+
48
+ **Fix (Rule 3):** a debounced coalescer for non-batch completions. Completions
49
+ within an 800ms window (reset on each new arrival) merge into ONE wake-up; each
50
+ is `resultConsumed`-re-checked before emit (Rule 2), so already-joined agents
51
+ are dropped and an all-consumed batch is suppressed entirely. Completions far
52
+ apart (>800ms) still flush separately. The explicit-`batch_id` path (Rule 1,
53
+ BatchBarrier) is unchanged.
54
+
55
+ ### CI hygiene
56
+
57
+ `biome check --write` applied across the v0.9.45 remediation files (lint +
58
+ format), and two unused test imports were removed — the local remediation
59
+ commits had never been CI-checked; this brings lint + format:check green.
60
+
61
+ ## [0.9.45] — Deep-review 2026-07-20 remediation (all 5 phases, 14 findings)
62
+
63
+ ### ⚠️ BREAKING: custom agent roles default to read-only (FIND-12)
64
+
65
+ `permissionForRole()` now returns `"read_only"` for unknown/custom agent
66
+ roles instead of the previous permissive `"workspace_write"`. This is a
67
+ security hardening (default-deny) to prevent privilege escalation via
68
+ typo'd or unrecognized agent names.
69
+
70
+ **Migration:** write-capable roles are now an EXPLICIT allowlist
71
+ (`WRITE_ROLES` in `src/runtime/role-permission.ts`). All shipped
72
+ write-capable roles are listed: `executor`, `writer`, `verifier`,
73
+ `test-engineer`, `agent` (direct-agent default), `cold-verifier`,
74
+ `chain-executor` (chain workflow), and `worker` (goal-loop + dynamic
75
+ workflow). A custom agent role that needs workspace write access
76
+ must be added to `WRITE_ROLES`. Built-in read-only roles (`explorer`,
77
+ `reviewer`, `security-reviewer`, `analyst`, `critic`, `planner`) are
78
+ unaffected. (An earlier draft proposed a `permissions.workspaceWrite`
79
+ agent-config opt-in; it was dropped — unreachable from the call sites.)
80
+
81
+
6
82
  ## [0.9.44] — 3 flaky CI test fixes (2026-07-19)
7
83
 
8
84
  Three pre-existing flaky tests in the CI suite have been fixed, completing
package/README.md CHANGED
@@ -282,7 +282,28 @@ The advisory is **informational only** — there is no `force:true` flag needed
282
282
 
283
283
  - `test/functional/pi-crew-live.test.ts` + `test/functional/pi-crew-live-broad.test.ts` — 16 live integration tests run against the **real pi binary + real LLM provider** (verified after v0.9.42 audit, ~26 commits). Use these when you want to confirm end-to-end behavior, not just unit-level invariants.
284
284
 
285
- ## Recent changes (since v0.9.42)
285
+ ## Recent changes
286
+
287
+ ### v0.9.45 – v0.9.46 (2026-07-20): security + perf remediation, UI stability
288
+
289
+ - **Custom agent roles default to read-only (FIND-12, breaking):**
290
+ `permissionForRole()` returns `"read_only"` for unknown roles (was
291
+ permissive `"workspace_write"`). Write-capable roles are now an explicit
292
+ allowlist (`WRITE_ROLES`). See `CHANGELOG.md` for the migration.
293
+ - **Mailbox / snapshot-cache / scan perf:** delivery cache + async append
294
+ (FIND-01/02), `listActive` TTL + mtime-sort strict limit (FIND-03/04),
295
+ byte-bounded event-log tail-read (FIND-05).
296
+ - **Heartbeat race (FIND-06):** in-flight guard + drain + terminal-safety +
297
+ late-save repair in the coalesced task group.
298
+ - **UI flicker eliminated:** the render path no longer hard-deletes snapshot
299
+ entries, so the crew widget / powerbar / live sidebar stay stable (no more
300
+ "(loading…)" flashing every ~160ms).
301
+ - **Emoji width-overflow crash fixed for good:** `visibleWidth` delegates to
302
+ pi-tui's own measure, so agent-output emoji can never exceed terminal width.
303
+ - **Background-subagent notifications coalesce:** N near-simultaneous
304
+ completions now produce ONE consolidated wake-up instead of N drips.
305
+
306
+ ### v0.9.42 – v0.9.44: 4-wave audit + flaky-CI fixes
286
307
 
287
308
  A 4-wave audit + fix pass was completed (see `UPGRADE_REVIEW.md` for the full 647-line report and `CHANGELOG.md` for the diff):
288
309
 
@@ -126,7 +126,7 @@
126
126
  "format": "esm"
127
127
  },
128
128
  "src/state/locks.ts": {
129
- "bytes": 21665,
129
+ "bytes": 24376,
130
130
  "imports": [
131
131
  {
132
132
  "path": "node:async_hooks",
@@ -378,7 +378,7 @@
378
378
  "format": "esm"
379
379
  },
380
380
  "src/agents/discover-agents.ts": {
381
- "bytes": 25646,
381
+ "bytes": 25658,
382
382
  "imports": [
383
383
  {
384
384
  "path": "node:fs",
@@ -1194,7 +1194,7 @@
1194
1194
  "format": "esm"
1195
1195
  },
1196
1196
  "src/utils/incremental-reader.ts": {
1197
- "bytes": 2588,
1197
+ "bytes": 6221,
1198
1198
  "imports": [
1199
1199
  {
1200
1200
  "path": "node:fs",
@@ -1231,7 +1231,7 @@
1231
1231
  "format": "esm"
1232
1232
  },
1233
1233
  "src/state/event-log.ts": {
1234
- "bytes": 52073,
1234
+ "bytes": 54432,
1235
1235
  "imports": [
1236
1236
  {
1237
1237
  "path": "node:crypto",
@@ -1537,7 +1537,7 @@
1537
1537
  "format": "esm"
1538
1538
  },
1539
1539
  "src/state/mailbox.ts": {
1540
- "bytes": 28080,
1540
+ "bytes": 34179,
1541
1541
  "imports": [
1542
1542
  {
1543
1543
  "path": "node:fs",
@@ -3645,7 +3645,7 @@
3645
3645
  "format": "esm"
3646
3646
  },
3647
3647
  "src/runtime/role-permission.ts": {
3648
- "bytes": 1396,
3648
+ "bytes": 2938,
3649
3649
  "imports": [],
3650
3650
  "format": "esm"
3651
3651
  },
@@ -5389,7 +5389,7 @@
5389
5389
  "format": "esm"
5390
5390
  },
5391
5391
  "src/extension/team-tool/api.ts": {
5392
- "bytes": 40596,
5392
+ "bytes": 40676,
5393
5393
  "imports": [
5394
5394
  {
5395
5395
  "path": "node:fs",
@@ -6229,7 +6229,7 @@
6229
6229
  "format": "esm"
6230
6230
  },
6231
6231
  "src/runtime/async-runner.ts": {
6232
- "bytes": 14825,
6232
+ "bytes": 15391,
6233
6233
  "imports": [
6234
6234
  {
6235
6235
  "path": "node:child_process",
@@ -6281,6 +6281,11 @@
6281
6281
  "kind": "import-statement",
6282
6282
  "original": "../utils/paths.ts"
6283
6283
  },
6284
+ {
6285
+ "path": "src/utils/redaction.ts",
6286
+ "kind": "import-statement",
6287
+ "original": "../utils/redaction.ts"
6288
+ },
6284
6289
  {
6285
6290
  "path": "src/runtime/orphan-worker-registry.ts",
6286
6291
  "kind": "import-statement",
@@ -6666,7 +6671,7 @@
6666
6671
  "format": "esm"
6667
6672
  },
6668
6673
  "src/extension/run-import.ts": {
6669
- "bytes": 5982,
6674
+ "bytes": 7115,
6670
6675
  "imports": [
6671
6676
  {
6672
6677
  "path": "node:crypto",
@@ -6698,6 +6703,11 @@
6698
6703
  "kind": "import-statement",
6699
6704
  "original": "../state/atomic-write.ts"
6700
6705
  },
6706
+ {
6707
+ "path": "src/utils/internal-error.ts",
6708
+ "kind": "import-statement",
6709
+ "original": "../utils/internal-error.ts"
6710
+ },
6701
6711
  {
6702
6712
  "path": "src/utils/paths.ts",
6703
6713
  "kind": "import-statement",
@@ -6922,8 +6932,14 @@
6922
6932
  "format": "esm"
6923
6933
  },
6924
6934
  "src/utils/visual.ts": {
6925
- "bytes": 8407,
6926
- "imports": [],
6935
+ "bytes": 7078,
6936
+ "imports": [
6937
+ {
6938
+ "path": "@earendil-works/pi-tui",
6939
+ "kind": "import-statement",
6940
+ "external": true
6941
+ }
6942
+ ],
6927
6943
  "format": "esm"
6928
6944
  },
6929
6945
  "src/ui/live-duration.ts": {
@@ -8250,7 +8266,7 @@
8250
8266
  "format": "esm"
8251
8267
  },
8252
8268
  "src/runtime/task-packet.ts": {
8253
- "bytes": 10509,
8269
+ "bytes": 10512,
8254
8270
  "imports": [
8255
8271
  {
8256
8272
  "path": "node:path",
@@ -8292,7 +8308,7 @@
8292
8308
  "format": "esm"
8293
8309
  },
8294
8310
  "src/runtime/run-coalesced-task-group.ts": {
8295
- "bytes": 8999,
8311
+ "bytes": 11679,
8296
8312
  "imports": [
8297
8313
  {
8298
8314
  "path": "src/state/artifact-store.ts",
@@ -9343,7 +9359,7 @@
9343
9359
  "format": "esm"
9344
9360
  },
9345
9361
  "src/state/run-metrics.ts": {
9346
- "bytes": 4530,
9362
+ "bytes": 5832,
9347
9363
  "imports": [
9348
9364
  {
9349
9365
  "path": "node:fs",
@@ -10722,7 +10738,7 @@
10722
10738
  "format": "esm"
10723
10739
  },
10724
10740
  "src/ui/run-dashboard.ts": {
10725
- "bytes": 38956,
10741
+ "bytes": 38776,
10726
10742
  "imports": [
10727
10743
  {
10728
10744
  "path": "node:fs",
@@ -10814,11 +10830,6 @@
10814
10830
  "kind": "import-statement",
10815
10831
  "original": "./keybinding-map.ts"
10816
10832
  },
10817
- {
10818
- "path": "src/ui/layout-primitives.ts",
10819
- "kind": "import-statement",
10820
- "original": "./layout-primitives.ts"
10821
- },
10822
10833
  {
10823
10834
  "path": "src/ui/overlays/help-overlay.ts",
10824
10835
  "kind": "import-statement",
@@ -10848,6 +10859,11 @@
10848
10859
  "path": "src/ui/theme-adapter.ts",
10849
10860
  "kind": "import-statement",
10850
10861
  "original": "./theme-adapter.ts"
10862
+ },
10863
+ {
10864
+ "path": "src/ui/widget/widget-renderer.ts",
10865
+ "kind": "import-statement",
10866
+ "original": "./widget/widget-renderer.ts"
10851
10867
  }
10852
10868
  ],
10853
10869
  "format": "esm"
@@ -11438,7 +11454,7 @@
11438
11454
  "format": "esm"
11439
11455
  },
11440
11456
  "src/runtime/manifest-cache.ts": {
11441
- "bytes": 11387,
11457
+ "bytes": 12865,
11442
11458
  "imports": [
11443
11459
  {
11444
11460
  "path": "node:fs",
@@ -11665,7 +11681,7 @@
11665
11681
  "format": "esm"
11666
11682
  },
11667
11683
  "src/ui/live-run-sidebar.ts": {
11668
- "bytes": 11936,
11684
+ "bytes": 11756,
11669
11685
  "imports": [
11670
11686
  {
11671
11687
  "path": "node:fs",
@@ -11707,11 +11723,6 @@
11707
11723
  "kind": "import-statement",
11708
11724
  "original": "../utils/visual.ts"
11709
11725
  },
11710
- {
11711
- "path": "src/ui/layout-primitives.ts",
11712
- "kind": "import-statement",
11713
- "original": "./layout-primitives.ts"
11714
- },
11715
11726
  {
11716
11727
  "path": "src/ui/render-scheduler.ts",
11717
11728
  "kind": "import-statement",
@@ -11736,6 +11747,11 @@
11736
11747
  "path": "src/ui/theme-adapter.ts",
11737
11748
  "kind": "import-statement",
11738
11749
  "original": "./theme-adapter.ts"
11750
+ },
11751
+ {
11752
+ "path": "src/ui/widget/widget-renderer.ts",
11753
+ "kind": "import-statement",
11754
+ "original": "./widget/widget-renderer.ts"
11739
11755
  }
11740
11756
  ],
11741
11757
  "format": "esm"
@@ -12399,7 +12415,7 @@
12399
12415
  "format": "esm"
12400
12416
  },
12401
12417
  "src/extension/registration/lifecycle-handlers.ts": {
12402
- "bytes": 28646,
12418
+ "bytes": 30286,
12403
12419
  "imports": [
12404
12420
  {
12405
12421
  "path": "node:fs",
@@ -12631,7 +12647,7 @@
12631
12647
  "format": "esm"
12632
12648
  },
12633
12649
  "src/extension/registration/subagent-manager-setup.ts": {
12634
- "bytes": 8033,
12650
+ "bytes": 12885,
12635
12651
  "imports": [
12636
12652
  {
12637
12653
  "path": "src/config/config.ts",
@@ -12802,7 +12818,7 @@
12802
12818
  "format": "esm"
12803
12819
  },
12804
12820
  "src/runtime/resilient-edit.ts": {
12805
- "bytes": 5549,
12821
+ "bytes": 5531,
12806
12822
  "imports": [
12807
12823
  {
12808
12824
  "path": "src/runtime/replace.ts",
@@ -12980,7 +12996,7 @@
12980
12996
  "imports": [],
12981
12997
  "exports": [],
12982
12998
  "inputs": {},
12983
- "bytes": 5999575
12999
+ "bytes": 6035945
12984
13000
  },
12985
13001
  "dist/index.mjs": {
12986
13002
  "imports": [
@@ -13929,6 +13945,11 @@
13929
13945
  "kind": "import-statement",
13930
13946
  "external": true
13931
13947
  },
13948
+ {
13949
+ "path": "@earendil-works/pi-tui",
13950
+ "kind": "import-statement",
13951
+ "external": true
13952
+ },
13932
13953
  {
13933
13954
  "path": "node:fs",
13934
13955
  "kind": "import-statement",
@@ -14624,7 +14645,7 @@
14624
14645
  "bytesInOutput": 4426
14625
14646
  },
14626
14647
  "src/state/locks.ts": {
14627
- "bytesInOutput": 8382
14648
+ "bytesInOutput": 9166
14628
14649
  },
14629
14650
  "src/utils/paths.ts": {
14630
14651
  "bytesInOutput": 5250
@@ -14765,13 +14786,13 @@
14765
14786
  "bytesInOutput": 10871
14766
14787
  },
14767
14788
  "src/utils/incremental-reader.ts": {
14768
- "bytesInOutput": 2166
14789
+ "bytesInOutput": 3934
14769
14790
  },
14770
14791
  "src/state/event-log-rotation.ts": {
14771
14792
  "bytesInOutput": 4480
14772
14793
  },
14773
14794
  "src/state/event-log.ts": {
14774
- "bytesInOutput": 28022
14795
+ "bytesInOutput": 28864
14775
14796
  },
14776
14797
  "src/utils/task-name-generator.ts": {
14777
14798
  "bytesInOutput": 106
@@ -14810,7 +14831,7 @@
14810
14831
  "bytesInOutput": 4465
14811
14832
  },
14812
14833
  "src/state/mailbox.ts": {
14813
- "bytesInOutput": 18349
14834
+ "bytesInOutput": 21862
14814
14835
  },
14815
14836
  "src/extension/help.ts": {
14816
14837
  "bytesInOutput": 2863
@@ -15104,7 +15125,7 @@
15104
15125
  "bytesInOutput": 15395
15105
15126
  },
15106
15127
  "src/runtime/role-permission.ts": {
15107
- "bytesInOutput": 1002
15128
+ "bytesInOutput": 1050
15108
15129
  },
15109
15130
  "src/runtime/worker-heartbeat.ts": {
15110
15131
  "bytesInOutput": 580
@@ -15383,7 +15404,7 @@
15383
15404
  "bytesInOutput": 241
15384
15405
  },
15385
15406
  "src/extension/team-tool/api.ts": {
15386
- "bytesInOutput": 41740
15407
+ "bytesInOutput": 41762
15387
15408
  },
15388
15409
  "src/runtime/checkpoint.ts": {
15389
15410
  "bytesInOutput": 2566
@@ -15464,7 +15485,7 @@
15464
15485
  "bytesInOutput": 7769
15465
15486
  },
15466
15487
  "src/runtime/async-runner.ts": {
15467
- "bytesInOutput": 9083
15488
+ "bytesInOutput": 9162
15468
15489
  },
15469
15490
  "src/subagents/async-entry.ts": {
15470
15491
  "bytesInOutput": 119
@@ -15512,7 +15533,7 @@
15512
15533
  "bytesInOutput": 4221
15513
15534
  },
15514
15535
  "src/extension/run-import.ts": {
15515
- "bytesInOutput": 4272
15536
+ "bytesInOutput": 4467
15516
15537
  },
15517
15538
  "src/extension/run-maintenance.ts": {
15518
15539
  "bytesInOutput": 4797
@@ -15530,7 +15551,7 @@
15530
15551
  "bytesInOutput": 1979
15531
15552
  },
15532
15553
  "src/utils/visual.ts": {
15533
- "bytesInOutput": 6973
15554
+ "bytesInOutput": 4539
15534
15555
  },
15535
15556
  "src/ui/live-duration.ts": {
15536
15557
  "bytesInOutput": 793
@@ -15728,7 +15749,7 @@
15728
15749
  "bytesInOutput": 7286
15729
15750
  },
15730
15751
  "src/runtime/run-coalesced-task-group.ts": {
15731
- "bytesInOutput": 6596
15752
+ "bytesInOutput": 7581
15732
15753
  },
15733
15754
  "src/runtime/runtime-policy.ts": {
15734
15755
  "bytesInOutput": 575
@@ -15944,7 +15965,7 @@
15944
15965
  "bytesInOutput": 4154
15945
15966
  },
15946
15967
  "src/ui/run-dashboard.ts": {
15947
- "bytesInOutput": 29994
15968
+ "bytesInOutput": 29822
15948
15969
  },
15949
15970
  "src/ui/overlays/confirm-overlay.ts": {
15950
15971
  "bytesInOutput": 2237
@@ -15980,7 +16001,7 @@
15980
16001
  "bytesInOutput": 4563
15981
16002
  },
15982
16003
  "src/ui/live-run-sidebar.ts": {
15983
- "bytesInOutput": 9966
16004
+ "bytesInOutput": 9794
15984
16005
  },
15985
16006
  "src/extension/registration/ui.ts": {
15986
16007
  "bytesInOutput": 4277
@@ -16091,7 +16112,7 @@
16091
16112
  "bytesInOutput": 3062
16092
16113
  },
16093
16114
  "src/runtime/manifest-cache.ts": {
16094
- "bytesInOutput": 8432
16115
+ "bytesInOutput": 8928
16095
16116
  },
16096
16117
  "src/utils/fs-watch.ts": {
16097
16118
  "bytesInOutput": 382
@@ -16118,7 +16139,7 @@
16118
16139
  "bytesInOutput": 2451
16119
16140
  },
16120
16141
  "src/extension/registration/lifecycle-handlers.ts": {
16121
- "bytesInOutput": 20920
16142
+ "bytesInOutput": 21304
16122
16143
  },
16123
16144
  "src/runtime/session-resources.ts": {
16124
16145
  "bytesInOutput": 463
@@ -16145,7 +16166,7 @@
16145
16166
  "bytesInOutput": 5262
16146
16167
  },
16147
16168
  "src/extension/registration/subagent-manager-setup.ts": {
16148
- "bytesInOutput": 4912
16169
+ "bytesInOutput": 7568
16149
16170
  },
16150
16171
  "src/extension/registration/subagent-tools.ts": {
16151
16172
  "bytesInOutput": 15572
@@ -16166,7 +16187,7 @@
16166
16187
  "bytesInOutput": 81
16167
16188
  }
16168
16189
  },
16169
- "bytes": 2791928
16190
+ "bytes": 2800922
16170
16191
  }
16171
16192
  }
16172
16193
  }