@zhuxixi/pi-agent-board 0.7.0 → 0.9.0
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 +46 -0
- package/README.md +16 -12
- package/VERIFY.md +2 -1
- package/docs/PTY_ATTACH_IMPLEMENTATION_PLAN.md +3 -1
- package/docs/RELEASE.md +148 -0
- package/docs/superpowers/plans/2026-09-10-reader-consistency.md +115 -0
- package/docs/superpowers/plans/2026-09-14-attach-cursor-dectcem-gate.md +469 -0
- package/docs/superpowers/plans/2026-09-14-attach-snapshot.md +92 -0
- package/docs/superpowers/plans/2026-09-14-host-meta-orphan-lock.md +771 -0
- package/docs/superpowers/plans/2026-09-14-issue-106-terminal-frame-cognition.md +299 -0
- package/docs/superpowers/plans/2026-09-14-issue-113-foreground-preview-race.md +609 -0
- package/docs/superpowers/plans/2026-09-14-terminal-model.md +145 -0
- package/docs/superpowers/plans/2026-09-15-coordinator-pipe-root-normalize.md +224 -0
- package/docs/superpowers/plans/2026-09-15-lease-publish-eprem-reclaim.md +341 -0
- package/docs/superpowers/plans/2026-09-18-detach-anchor-reporter-endpoint.md +875 -0
- package/docs/superpowers/plans/2026-09-20-control-lifecycle.md +116 -0
- package/docs/superpowers/plans/2026-09-20-issue-121-perf-gate-out-of-coverage.md +517 -0
- package/docs/superpowers/plans/2026-09-23-a5-mid-stream-assertion.md +25 -0
- package/docs/superpowers/plans/2026-09-23-cwd-browse-tab-drill.md +393 -0
- package/docs/superpowers/plans/2026-09-23-terminal-query-forwarding.md +51 -0
- package/docs/superpowers/plans/2026-09-23-unconditional-detach-key.md +58 -0
- package/docs/superpowers/plans/2026-09-24-control-reconcile-flake.md +698 -0
- package/docs/superpowers/plans/2026-09-24-probe-attached-clients.md +605 -0
- package/docs/superpowers/specs/2026-09-14-attach-cursor-dectcem-gate-design.md +114 -0
- package/docs/superpowers/specs/2026-09-14-host-meta-orphan-lock-design.md +120 -0
- package/docs/superpowers/specs/2026-09-14-issue-106-terminal-frame-cognition-design.md +146 -0
- package/docs/superpowers/specs/2026-09-14-issue-113-foreground-preview-race-design.md +116 -0
- package/docs/superpowers/specs/2026-09-15-coordinator-pipe-root-normalize-design.md +84 -0
- package/docs/superpowers/specs/2026-09-15-lease-publish-eprem-reclaim-design.md +92 -0
- package/docs/superpowers/specs/2026-09-18-detach-anchor-reporter-endpoint-design.md +123 -0
- package/docs/superpowers/specs/2026-09-20-issue-121-perf-gate-out-of-coverage-design.md +204 -0
- package/docs/superpowers/specs/2026-09-23-a5-mid-stream-assertion-design.md +43 -0
- package/docs/superpowers/specs/2026-09-23-cwd-browse-tab-drill-design.md +95 -0
- package/docs/superpowers/specs/2026-09-23-terminal-query-forwarding-design.md +63 -0
- package/docs/superpowers/specs/2026-09-23-unconditional-detach-key-design.md +57 -0
- package/docs/superpowers/specs/2026-09-24-control-reconcile-flake-design.md +98 -0
- package/docs/superpowers/specs/2026-09-24-probe-attached-clients-design.md +190 -0
- package/package.json +3 -2
- package/runner/job-runner.mjs +8 -7
- package/runner/pty-runner.mjs +633 -31
- package/runner/state-coordinator.mjs +43 -17
- package/runner/state-runner.mjs +6 -5
- package/scripts/release.mjs +446 -0
- package/scripts/run-perf-gate.mjs +40 -0
- package/src/commands/agent-board.ts +8 -8
- package/src/commands/attach-flow.ts +5 -5
- package/src/core/control-protocol.mjs +482 -0
- package/src/core/editor-state-reporter.mjs +11 -1
- package/src/core/foreground-preview-cache.mjs +117 -0
- package/src/core/host-protocol.mjs +62 -0
- package/src/core/launch-options.mjs +37 -0
- package/src/core/locks.mjs +68 -14
- package/src/core/paths.mjs +35 -3
- package/src/core/pid.mjs +32 -1
- package/src/core/pty-attach-jiggle-controller.mjs +27 -3
- package/src/core/pty-attach-reconnect.mjs +13 -6
- package/src/core/pty-attach-render.mjs +20 -0
- package/src/core/state-commands.mjs +88 -6
- package/src/core/status-consistency.mjs +98 -0
- package/src/core/store.mjs +59 -13
- package/src/core/terminal-attach-client.mjs +809 -0
- package/src/core/terminal-attach-protocol.mjs +252 -0
- package/src/core/terminal-model.mjs +222 -0
- package/src/core/terminal-query-sequences.mjs +132 -0
- package/src/core/terminal-snapshot.mjs +440 -0
- package/src/index.ts +12 -4
- package/src/runtime/service.mjs +284 -16
- package/src/ui/dashboard.ts +61 -41
- package/src/ui/pty-attach.ts +318 -72
- package/src/core/pty-input.mjs +0 -47
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,52 @@ conventional commits by `scripts/release_helper.mjs`. Entries are
|
|
|
5
5
|
forward-only: they begin with the first release after this file landed —
|
|
6
6
|
for earlier history, see the git log and the pull-request list.
|
|
7
7
|
|
|
8
|
+
## [0.9.0] - 2026-09-25
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
- **attach**: forward terminal queries to the real terminal — automatic theme works in attach sessions (issue #128) (#138)
|
|
13
|
+
- **attach**: unconditional Ctrl+\ detach key, terminal-independent (issue #126) (#137)
|
|
14
|
+
|
|
15
|
+
### Fixes
|
|
16
|
+
|
|
17
|
+
- **runner**: probes leave attachedClients at hello (issue #130) (#142)
|
|
18
|
+
- browse-mode Tab completes and drills down directories in cwd picker (issue #127) (#141)
|
|
19
|
+
|
|
20
|
+
### Changes
|
|
21
|
+
|
|
22
|
+
- **release**: orchestrate releases and publish from CI (#144)
|
|
23
|
+
- control-reconcile flaky assertions — windowed recorder + invariant realignment (issue #140) (#143)
|
|
24
|
+
- **attach**: scope A5 mid-stream seq assertion to post-snapshot window (issue #132) (#139)
|
|
25
|
+
|
|
26
|
+
[0.9.0]: https://github.com/zhuxixi/pi-agent-board/compare/v0.8.0...v0.9.0
|
|
27
|
+
|
|
28
|
+
## [0.8.0] - 2026-09-23
|
|
29
|
+
|
|
30
|
+
### Features
|
|
31
|
+
|
|
32
|
+
- **attach**: detach gate reads only editor_state — delete buffer heuristics (issue #91 phase 6, D1) (#135)
|
|
33
|
+
- control command lifecycle — staged acks, durable journal, reconcile, generation token (issue #91 phase 5, D4) (#134)
|
|
34
|
+
- switch attach to snapshot+subscribe with legacy fallback (issue #91 phase 4, D2 closure) (#122)
|
|
35
|
+
- canonical terminal model and capture-and-subscribe protocol (issue #91 phase 3, D2+D5) (#117)
|
|
36
|
+
- reader-side revision consistency, beat bootstrap, and paired-revision invariant (issue #91, D3 arc closure) (#111)
|
|
37
|
+
|
|
38
|
+
### Fixes
|
|
39
|
+
|
|
40
|
+
- **attach**: editor-shaped ← detach anchor + restore the editor-state reporter endpoint (issue #103) (#119)
|
|
41
|
+
- **paths**: normalize root before hashing the win32 coordinator pipe name (issue #124) (#125)
|
|
42
|
+
- **locks**: reclaim Windows lease orphans on publish-rename EPERM (issue #114) (#123)
|
|
43
|
+
- **attach**: stop painting the PTY cursor block when the child hides it (issue #102) (#120)
|
|
44
|
+
- **attach**: learn TUI frame cognition in terminal chain states (issue #106) (#118)
|
|
45
|
+
- **host**: reclaim orphaned host-meta locks and add contention diagnostics (issue #112) (#116)
|
|
46
|
+
- foreground preview read-your-writes cache for coordinator write races (issue #113) (#115)
|
|
47
|
+
|
|
48
|
+
### Changes
|
|
49
|
+
|
|
50
|
+
- move the A11 perf gate out of coverage instrumentation (issue #121) (#133)
|
|
51
|
+
|
|
52
|
+
[0.8.0]: https://github.com/zhuxixi/pi-agent-board/compare/v0.7.0...v0.8.0
|
|
53
|
+
|
|
8
54
|
## [0.7.0] - 2026-09-10
|
|
9
55
|
|
|
10
56
|
### Features
|
package/README.md
CHANGED
|
@@ -93,7 +93,7 @@ From the board:
|
|
|
93
93
|
- In Peek, press `r` to reply without attaching.
|
|
94
94
|
- Press `v` for a read-only transcript, or `e` for evidence and diagnostics.
|
|
95
95
|
- Press `Enter`, `Right`, or `>` to attach to the real Pi session.
|
|
96
|
-
- In PTY attach mode, press `Left`
|
|
96
|
+
- In PTY attach mode, press `Ctrl+Left` to return to the board at any time (even mid-draft), or `Ctrl+\` — the unconditional escape: it detaches regardless of editor state on any terminal that forwards the raw 0x1c byte (raw, kitty, and modifyOtherKeys keyboard modes all work). `Left` detaches only while the child Pi reports an empty editor; otherwise (draft, or unknown editor state) it is forwarded to the child. `Ctrl+]` is not a detach key — it is passed through to the child Pi editor. When the host is disconnected, `Left` always exits. `Ctrl+\` caveats: on keyboard layouts where `\` sits on AltGr (e.g. German) the chord may never produce the byte — use `Ctrl+Left` there; in legacy keyboard modes `Ctrl+4` emits the same byte and also detaches; and the key is captured from the child while attached, so the child's own `Ctrl+\` binding (e.g. the vim terminal-mode prefix) is unavailable. Automatic themes also resolve correctly in attach sessions: the child's background probe (OSC 11) and color-scheme change notifications are forwarded to your real terminal, replies flow back to the child when your terminal answers, and attach re-probes the background once when it settles so the theme is right from the first frame. Set `AGENT_BOARD_FORWARD_TERMINAL_QUERIES=0` to disable this (default on); keyboard-protocol negotiation (kitty/DA) is not forwarded — it happens once at child spawn, before attach exists.
|
|
97
97
|
|
|
98
98
|
## Dashboard Workflow
|
|
99
99
|
|
|
@@ -192,7 +192,9 @@ The `e` view shows durable session evidence, including changed files, commands a
|
|
|
192
192
|
|
|
193
193
|
### PTY attach
|
|
194
194
|
|
|
195
|
-
PTY attach opens the real interactive Pi session.
|
|
195
|
+
PTY attach opens the real interactive Pi session. `Ctrl+Left` detaches and returns to the board regardless of editor state, and `Ctrl+\` is the unconditional escape — a single raw ASCII byte (0x1c) that terminals forward unchanged in raw mode (kitty CSI-u and modifyOtherKeys encodings included), honored no matter what the child editor reports. `Left` detaches only while the child Pi reports an empty editor; while you are editing text — or when the child's editor state is unknown (e.g. the child extension is missing) — `Left` is forwarded to the Pi editor. A disconnected host can always be exited with `Left`. `Ctrl+]` is not a detach key — it is passed through to the child Pi editor. `Ctrl+\` caveats: on keyboard layouts where `\` sits on AltGr (e.g. German) the chord may never produce the byte — use `Ctrl+Left` there; in legacy keyboard modes `Ctrl+4` emits the same byte and also detaches; and the key is captured from the child while attached, so the child's own `Ctrl+\` binding (e.g. the vim terminal-mode prefix) is unavailable. While attached, `PageUp`, `PageDown`, `Home`, `End`, and the mouse wheel scroll local scrollback. Mouse drag or double-click selects and copies text, clicks open detected links, and middle-click paste is available on systems with the required X11 tooling.
|
|
196
|
+
|
|
197
|
+
Automatic light/dark themes work in attach sessions too. The child Pi's background-color probe (OSC 11) and its color-scheme change notification switch are forwarded to your real terminal; replies and notifications flow back to the child when your terminal answers — a terminal that never answers keeps the dark fallback. Attach also re-probes the background once when it settles, so the theme is correct from the first frame without re-selecting it in `/settings`. All of this is on by default; set `AGENT_BOARD_FORWARD_TERMINAL_QUERIES=0` to disable it. Not covered: keyboard-protocol negotiation (kitty flags / DA1) is not forwarded — the child negotiates once at spawn, before any attach client exists, and forwarding it would retune the shared real-terminal keyboard stack — so modifier-key encoding can still differ from a directly-run Pi (the modifyOtherKeys fallback applies).
|
|
196
198
|
|
|
197
199
|
The attach surface can forward terminal clipboard and image/file passthrough sequences. These behaviors can be disabled individually in [Configuration](#configuration). Cold hosts may briefly show a loading/reconnect surface while their PTY becomes ready.
|
|
198
200
|
|
|
@@ -314,6 +316,9 @@ Set these variables before starting Pi. Model-backed features fall back graceful
|
|
|
314
316
|
| `AGENT_BOARD_FORWARD_OSC52` | enabled; `0` disables | Disable OSC 52 clipboard sequence forwarding from an attached session. |
|
|
315
317
|
| `AGENT_BOARD_FORWARD_IMAGES` | enabled; `0` disables | Disable terminal image/file passthrough forwarding from an attached session. |
|
|
316
318
|
| `AGENT_BOARD_IME_FIX` | enabled; `0` disables | Disable the attach-view IME cursor coalescer if your terminal has compatibility problems. |
|
|
319
|
+
| `AGENT_BOARD_TERMINAL_SNAPSHOT` | enabled; `0` forces legacy | Force the pre-phase-4 attach path (screen.log replay + jiggle); escape hatch / rollback switch for the snapshot+subscribe attach protocol. |
|
|
320
|
+
|
|
321
|
+
`AGENT_BOARD_TERMINAL_SNAPSHOT=0` routes attach onto the legacy fallback family: the screen.log replay tail plus the shrink-and-hold jiggle redraw. These paths exist only for pre-protocol runners and as a rollback hatch — they are not part of the snapshot+subscribe protocol path (issue #91 phase 4). They will be retired once the installed runner fleet is on the snapshot protocol baseline (detectable on the wire via hello protocol fields).
|
|
317
322
|
|
|
318
323
|
Older `AGENT_VIEW_*` names are still read in selected compatibility paths. Prefer `AGENT_BOARD_*` for new setups. Internal child markers are managed by Agent Board and are not user settings.
|
|
319
324
|
|
|
@@ -376,23 +381,21 @@ npm install
|
|
|
376
381
|
npm run verify
|
|
377
382
|
```
|
|
378
383
|
|
|
379
|
-
`npm run verify` runs typecheck, tests, coverage, and a package dry-run. The same checks run in CI on Node 22 and Node 24. See [VERIFY.md](VERIFY.md) for the full verification checklist and known environment-dependent limitations.
|
|
384
|
+
`npm run verify` runs typecheck, the perf gate (`npm run test:perf`), tests, coverage, and a package dry-run. The A11 perf assertions are opt-in — they skip under `npm test` / `npm run test:coverage` and only measure via `npm run test:perf` (issue #121). The same checks run in CI on Node 22 and Node 24. See [VERIFY.md](VERIFY.md) for the full verification checklist and known environment-dependent limitations.
|
|
380
385
|
|
|
381
386
|
## Publishing
|
|
382
387
|
|
|
383
|
-
|
|
388
|
+
Releases run through `scripts/release.mjs` and are published by CI when a GitHub Release is created. The flow checks the parts that are easy to get wrong — the changelog range, tag ancestry against `main`, and drift from a late merge — instead of assuming them. See [docs/RELEASE.md](docs/RELEASE.md) for the full process, the one-time npm trusted-publisher setup, and failure recovery.
|
|
384
389
|
|
|
385
390
|
```bash
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
npm version patch # bumps package.json, commits, and tags vX.Y.Z
|
|
392
|
-
npm publish
|
|
391
|
+
node scripts/release.mjs precheck # gate: clean main, nothing in flight
|
|
392
|
+
node scripts/release.mjs prepare minor # changelog + verify + bump + tag + release branch
|
|
393
|
+
node scripts/release.mjs open-pr 0.9.0 # push the branch and tag, open the release PR
|
|
394
|
+
# merge the PR with a merge commit, then:
|
|
395
|
+
node scripts/release.mjs finish 0.9.0 # GitHub Release → CI publishes to npm
|
|
393
396
|
```
|
|
394
397
|
|
|
395
|
-
|
|
398
|
+
After publishing, users install the scoped package with:
|
|
396
399
|
|
|
397
400
|
```bash
|
|
398
401
|
pi install npm:@zhuxixi/pi-agent-board
|
|
@@ -403,6 +406,7 @@ The Pi package gallery uses the `pi.video` and `pi.image` URLs from `package.jso
|
|
|
403
406
|
## Further Reading
|
|
404
407
|
|
|
405
408
|
- [Manual verification](VERIFY.md) — static checks, Pi loading, provider authentication, persistence, and dashboard flows.
|
|
409
|
+
- [Release process](docs/RELEASE.md) — release commands, trusted publishing setup, and failure recovery.
|
|
406
410
|
- [Product requirements](PRD.md) — original product scope and design context.
|
|
407
411
|
- [Progress log](PROGRESS.md) — implementation checkpoints and known environment notes.
|
|
408
412
|
- [Exploration notes](docs/EXPLORATION.md) — Pi API and integration research.
|
package/VERIFY.md
CHANGED
|
@@ -10,7 +10,8 @@ Steps you can run yourself to check the extension. Grouped from "no auth needed"
|
|
|
10
10
|
```bash
|
|
11
11
|
npm install # dev + runtime deps
|
|
12
12
|
npm run typecheck # expect: 0 errors
|
|
13
|
-
npm test
|
|
13
|
+
npm run test:perf # expect: `burst:`/`paced:` value lines, pass 3 / skipped 0 — the ONLY path that measures perf assertions; they skip under npm test (issue #121)
|
|
14
|
+
npm test # expect: 0 failures (the 3 A11 perf tests skip here, reason points at `npm run test:perf`)
|
|
14
15
|
npm run pack:dry # expect: pi-agent-board-<version>.tgz contents only include deploy files
|
|
15
16
|
```
|
|
16
17
|
|
|
@@ -282,6 +282,8 @@ Runner → client:
|
|
|
282
282
|
|
|
283
283
|
For attach, the parent sends raw input bytes through `input`. The attach surface intercepts only `←` when the child input line appears empty; all other keys, including Pi's native `ctrl+]` editor shortcut, pass through to the child.
|
|
284
284
|
|
|
285
|
+
> **Historical:** superseded by the editor_state side channel (issue #91 Phase 6) — `←` now detaches only when the child pushes `editorEmpty=true`; otherwise (draft or unknown state) it is forwarded to the child. `Ctrl+←` always detaches.
|
|
286
|
+
|
|
285
287
|
The detach chord is `←` because it is already the board navigation key and preserves Pi editor keybindings.
|
|
286
288
|
|
|
287
289
|
## 6. Attach UI design
|
|
@@ -560,7 +562,7 @@ MVP live attach is accepted when:
|
|
|
560
562
|
| child Pi extension recursion | medium | `AGENT_BOARD_CHILD=1`; skip dashboard auto-open/footer in child |
|
|
561
563
|
| host liveness conflated with agent activity | high | add `host.json`; separate `hostAlive` from `row.alive` |
|
|
562
564
|
| worktree safety too conservative with idle hosts | low/medium | conservative MVP, later refine with activity state |
|
|
563
|
-
| `←` is also a child-editor cursor key | low | detach only when the child input line appears empty; keep all other editor shortcuts, including `ctrl+]`, pass-through |
|
|
565
|
+
| `←` is also a child-editor cursor key | low | detach only when the child input line appears empty; keep all other editor shortcuts, including `ctrl+]`, pass-through. **Superseded (issue #91 Phase 6):** detach now gates on the pushed `editorEmpty` state, not the rendered buffer; `Ctrl+←` always detaches |
|
|
564
566
|
| terminal images/OSC links not perfect in virtual renderer | medium | document limitation; raw takeover/core API if needed |
|
|
565
567
|
|
|
566
568
|
## 13. Confidence
|
package/docs/RELEASE.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Release process
|
|
2
|
+
|
|
3
|
+
Releases are driven by `scripts/release.mjs` and published by CI when a GitHub
|
|
4
|
+
Release is created. Four commands and one merge:
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
precheck → prepare <bump> → open-pr <version> → merge (merge commit!)
|
|
8
|
+
→ finish <version> → .github/workflows/publish.yml publishes to npm
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`main` is protected, so a release lands as a pull request — but the npm publish
|
|
12
|
+
is not tied to that PR: it is tied to the GitHub Release, which is created only
|
|
13
|
+
after the PR is merged and the tag is confirmed to be on `main`.
|
|
14
|
+
|
|
15
|
+
## One-time setup: the npm trusted publisher
|
|
16
|
+
|
|
17
|
+
`publish.yml` publishes with **trusted publishing** (OIDC), so no `NPM_TOKEN`
|
|
18
|
+
secret is stored in the repository. npm has to be told which workflow may
|
|
19
|
+
publish the package — this is a one-time, per-package web step:
|
|
20
|
+
|
|
21
|
+
1. Open <https://www.npmjs.com/package/@zhuxixi/pi-agent-board> and sign in as
|
|
22
|
+
the package owner.
|
|
23
|
+
2. Go to **Settings** → **Trusted Publisher** → **GitHub Actions**.
|
|
24
|
+
3. Fill in: organization `zhuxixi`, repository `pi-agent-board`, workflow
|
|
25
|
+
filename `publish.yml`, environment left empty.
|
|
26
|
+
4. Save.
|
|
27
|
+
|
|
28
|
+
Until this is configured, the CI publish step fails with an authentication
|
|
29
|
+
error. Publishing locally (the pre-2026-09 flow, see "Local publish fallback"
|
|
30
|
+
below) still works, because the npm account keeps its normal credentials.
|
|
31
|
+
|
|
32
|
+
## Commands
|
|
33
|
+
|
|
34
|
+
### `node scripts/release.mjs precheck`
|
|
35
|
+
|
|
36
|
+
Read-only gate. Run it before anything else; it fails closed when the tree is
|
|
37
|
+
dirty, `main` is not in sync with `origin/main`, a `release/*` PR is still open,
|
|
38
|
+
the last tag is not an ancestor of `origin/main` (an orphan tag from a past
|
|
39
|
+
squash merge), or there is nothing functional to release.
|
|
40
|
+
|
|
41
|
+
### `node scripts/release.mjs prepare <patch|minor|major|X.Y.Z> [--dry-run]`
|
|
42
|
+
|
|
43
|
+
Runs the gate, then:
|
|
44
|
+
|
|
45
|
+
1. renders the new CHANGELOG section from the conventional commits since the
|
|
46
|
+
last `v*` tag,
|
|
47
|
+
2. commits it as `docs(changelog): add <version> section`,
|
|
48
|
+
3. runs `npm version <bump>` — one commit plus the annotated `v<version>` tag,
|
|
49
|
+
4. switches to `release/v<version>`.
|
|
50
|
+
|
|
51
|
+
Nothing leaves the machine. `--dry-run` prints the section and the plan without
|
|
52
|
+
writing anything; review the preview before running it for real. Pick the bump
|
|
53
|
+
the same way as before: features are a `minor`, fixes alone are a `patch`.
|
|
54
|
+
|
|
55
|
+
### `node scripts/release.mjs open-pr <version> [--dry-run]`
|
|
56
|
+
|
|
57
|
+
Pushes the release branch and the tag, then opens the PR with the CHANGELOG
|
|
58
|
+
section as its body. The tag is pushed separately because tags are not covered
|
|
59
|
+
by the branch protection rule.
|
|
60
|
+
|
|
61
|
+
### Merge the PR with a merge commit
|
|
62
|
+
|
|
63
|
+
**Do not squash the release PR.** `npm version` created the tag on a commit that
|
|
64
|
+
only exists on the release branch; a merge commit brings that commit into
|
|
65
|
+
`main`, a squash does not, and the tag becomes unreachable from `main`
|
|
66
|
+
(`git describe` then misreads the changelog range of the next release). Use the
|
|
67
|
+
**Merge pull request** button, or `gh pr merge --merge`.
|
|
68
|
+
|
|
69
|
+
### `node scripts/release.mjs finish <version> [--dry-run] [--sync-main]`
|
|
70
|
+
|
|
71
|
+
Run it after the merge. It fetches, verifies that `v<version>` is an ancestor of
|
|
72
|
+
`origin/main`, re-checks the CHANGELOG against any functional PR merged after the
|
|
73
|
+
bump, and then creates the GitHub Release with the CHANGELOG section as its
|
|
74
|
+
notes. That release is the publish trigger. `--sync-main` also resets a clean
|
|
75
|
+
local `main` to `origin/main`, dropping the local copy of the two release
|
|
76
|
+
commits (they are content-equal to what the merge brought in).
|
|
77
|
+
|
|
78
|
+
To publish without the orchestration, the underlying commands are
|
|
79
|
+
`gh release create v<version> --title v<version> --notes-file <notes>` where the
|
|
80
|
+
notes are `node scripts/release.mjs notes <version>`.
|
|
81
|
+
|
|
82
|
+
## What CI does
|
|
83
|
+
|
|
84
|
+
`.github/workflows/publish.yml` runs on `release: published`, checks out the
|
|
85
|
+
tag, and:
|
|
86
|
+
|
|
87
|
+
1. asserts npm is at least 11.5.1 (trusted publishing needs it; Node 24 ships
|
|
88
|
+
a newer one),
|
|
89
|
+
2. asserts the tag equals `package.json`'s version,
|
|
90
|
+
3. runs `npm run typecheck` and `npm test`,
|
|
91
|
+
4. skips the publish when that version is already on npm, and otherwise
|
|
92
|
+
5. runs `npm publish --provenance --access public`.
|
|
93
|
+
|
|
94
|
+
Provenance means npm records which repository, workflow, and commit built the
|
|
95
|
+
tarball, and shows it on the package page.
|
|
96
|
+
|
|
97
|
+
## Recovering from a broken release
|
|
98
|
+
|
|
99
|
+
**Orphan tag** (`finish` refuses with "not an ancestor of origin/main"). The
|
|
100
|
+
release PR was squash-merged. Re-point the tag at the content-equivalent commit
|
|
101
|
+
on `main`, then retry:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
git fetch origin
|
|
105
|
+
git tag -f v0.9.0 <sha on origin/main with the same content>
|
|
106
|
+
git push origin v0.9.0 --force
|
|
107
|
+
node scripts/release.mjs finish 0.9.0 --sync-main
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**CHANGELOG drift** (`finish` refuses with missing PR numbers, or
|
|
111
|
+
`node scripts/release_helper.mjs verify` exits 1). A functional PR merged after
|
|
112
|
+
the changelog section was generated. Add the missing entries to the top section
|
|
113
|
+
in a `docs(changelog)` PR, merge it, then retry — the release notes are read
|
|
114
|
+
again at that point, so the fix is picked up.
|
|
115
|
+
|
|
116
|
+
**The publish failed** (npm outage, trusted publisher not configured yet).
|
|
117
|
+
Re-run the same tag without recreating the release: Actions → **Publish to
|
|
118
|
+
npm** → **Run workflow** → enter the tag.
|
|
119
|
+
|
|
120
|
+
**`npm view <package>` returns 404 right after a successful publish.** That is
|
|
121
|
+
the registry's view index lagging, not a failed publish. Compare endpoints: the
|
|
122
|
+
tarball, `/<package>/latest`, and `/-/package/<package>/dist-tags` answer
|
|
123
|
+
immediately while the root document 404s for a few minutes. Wait; re-publishing
|
|
124
|
+
the same version is rejected with a 403 and is never the fix.
|
|
125
|
+
|
|
126
|
+
**Local publish fallback.** When the trusted publisher is not configured and a
|
|
127
|
+
release cannot wait:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npm run verify
|
|
131
|
+
npm publish --registry=https://registry.npmjs.org --access public
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The registry must be passed explicitly: this machine defaults to
|
|
135
|
+
`registry.npmmirror.com`. This path skips provenance and the CI checks, so
|
|
136
|
+
prefer fixing the CI path.
|
|
137
|
+
|
|
138
|
+
## CHANGELOG conventions
|
|
139
|
+
|
|
140
|
+
`scripts/release_helper.mjs` renders the section from conventional commits in
|
|
141
|
+
the range `<last v* tag>..HEAD`. `feat`/`fix`/`perf` become Features / Fixes /
|
|
142
|
+
Performance, everything else (including `test` and `docs`) lands under Changes,
|
|
143
|
+
merges and `npm version` squash subjects are skipped, and `docs(changelog):`
|
|
144
|
+
subjects are ignored so changelog fixes do not feed back into the verify check.
|
|
145
|
+
|
|
146
|
+
Every entry carries the PR number parsed from the trailing `(#N)`. `verify`
|
|
147
|
+
fails when a functional commit's PR is missing from the top section — that is
|
|
148
|
+
what keeps a late merge from shipping without release notes.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Reader Consistency & Residual Convergence (PR #3, issue #91) Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** 关闭 spec 根治条件 5 的读侧(revision 一致性 enforcement)+ 收敛 PR #2 已知残留(coordinator 硬宕窗口的无 status 文件行)+ 合并后卫生波。完成后 **D3「状态所有权」弧线全部闭合**(写侧 PR #1/#2、读侧本 PR)。
|
|
6
|
+
|
|
7
|
+
**Architecture:** 三个小面:(1) 决策层让 `run_progress` 在「行存活 + runId 匹配 + status 文件缺失」时从 beat 的全量 patch 自举(部分反转 F2 守卫——F2 防的是稀疏 patch 物化 undefined,而 beat 的 patch 是全量的,且硬宕残留正是需要自愈的场景);(2) reconcile 作为唯一组合读者,读侧做 revision mismatch 检测——不一致则丢弃组合、踢 ensureCoordinator(boot replay 修复)、记 diagnostic;(3) 卫生波(.catch、双击窗口、JSDoc、warn 措辞、void+catch pass)。
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** 既有 coordinator/journal/client/决策层(PR #1/#2 已建并稳定)。
|
|
10
|
+
|
|
11
|
+
**Spec:** `docs/superpowers/specs/2026-09-09-harden-runner-architecture-design.md`(根治条件 5;§5 降级条款)。
|
|
12
|
+
|
|
13
|
+
## 设计决策(本 plan 锁定)
|
|
14
|
+
|
|
15
|
+
1. **run_progress bootstrap 是 F2 的部分反转,有边界**:F2 的 `!currentStatus → stale_run` 改为——行 `processState === "alive" && currentRunId === command.runId` 时从 patch 自举(决策层校验 patch 携带 `processState`/`semanticState`/`runId` 一致性后才自举;稀疏或身份不符仍 stale_run)。coordinator 的 `STATUS_BOOTSTRAP_KINDS` 增加 `run_progress`。理由:beat 是 runner 内存中权威状态的全量快照;硬宕残留(mark_queued applied 但 run_started 丢失)下,coordinator 恢复后第一条 beat 即自愈。
|
|
16
|
+
2. **读侧 mismatch 的语义**:reconcile 读 `s.currentRunId` 对应 status 时,若 `state.materializedRevision != null && status?.materializedRevision != null && 二者不等` → 视为不一致:跳过本行投影(不拼)、`appendDiagnostic(code: "state_status_revision_desync")`、异步 `ensureCoordinator(root)`(boot replay 从 journal 修复半物化对)、不计入 fixed。**legacy 行(任一侧无 revision)不检查**——与 spec 的 legacy 迁移条款一致。
|
|
17
|
+
3. **mismatch 只可能来自 coordinator 崩溃窗口**(写侧已全部在 view lock 内配对打戳);活着的 coordinator 不会产生 mismatch。所以修复路径 = ensureCoordinator spawn → boot replay,无需新命令 kind。
|
|
18
|
+
4. **双击窗口修复选「同步清理」**:`submitDispatch` 在发起异步 dispatch 前**同步**清 `this.launch`/input/mode(不等 .then),消除 coordinator 冷启动 round-trip 期间的重复提交窗口;.catch 兜底 fs 类异常。
|
|
19
|
+
5. **decided-rejection warn 措辞**:job-runner/state-runner 中对 `manual_fence`/`stale_run`/`no_change` 等**确定性拒绝**的分支不再使用「outcome unknown … replay will recover」句式——改为 info 级 `*_skipped` 或措辞明确的 warn(确定性拒绝已 journal,无恢复语义)。
|
|
20
|
+
|
|
21
|
+
## Global Constraints
|
|
22
|
+
|
|
23
|
+
- 所有 runner 进程 plain ESM `.mjs`。
|
|
24
|
+
- Commit message 英文 conventional commits;`git add <file>` 显式 stage,禁止 `-A`。
|
|
25
|
+
- 测试 `node --test`;隔离必须同时设 `AGENT_BOARD_ROOT` + `PI_CODING_AGENT_DIR`;coordinator 一律 tracked fixture(`test-support/ensure-coordinator-helper.mjs`,readiness wait + finally kill)。
|
|
26
|
+
- 决策层保持纯函数。
|
|
27
|
+
- 模糊结果(timeout/connection_reset)不回退直写;只有 `coordinator_disabled` 走 legacy 分支。
|
|
28
|
+
- **大 task 时间纪律**:分阶段提交工作增量,保持树常绿(吸取 PR #2 三次超时教训)。
|
|
29
|
+
- `$WT = /home/elling/git-repo/github/pi-agent-board/.pi/worktrees/issue-91-reader-consistency`;全部绝对路径 + `git -C $WT`。
|
|
30
|
+
|
|
31
|
+
## 验收映射
|
|
32
|
+
|
|
33
|
+
| Task | 验收目标 | 说明 |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| Task 1 | 根治条件 5 残留收敛(决策层) | run_progress bootstrap 守卫 + 纯函数测试 |
|
|
36
|
+
| Task 2 | 残留收敛(端到端) | 硬宕场景:beat 自举 → finalize 收敛 |
|
|
37
|
+
| Task 3 | **根治条件 5 读侧闭合** | reconcile mismatch 检测 + 修复踢 + diagnostic |
|
|
38
|
+
| Task 4 | 卫生波 | .catch / 双击窗口 / JSDoc / warn 措辞 / void+catch |
|
|
39
|
+
| Task 5 | 对账 | 全量回归 + 根治条件 1/2/5 全闭合声明 |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
### Task 1: 决策层——run_progress 缺失 status 时的自举分支
|
|
44
|
+
|
|
45
|
+
**Files:**
|
|
46
|
+
- Modify: `src/core/state-commands.mjs`
|
|
47
|
+
- Test: `test/state-commands.test.mjs`
|
|
48
|
+
|
|
49
|
+
**Interfaces:**
|
|
50
|
+
- Consumes: 现有 `run_progress` 分支(~L358:`if (!currentStatus) return reject("stale_run")`)、`applyStatusProjection`。
|
|
51
|
+
- Produces(Task 2 依赖):
|
|
52
|
+
- run_progress 分支新语义:
|
|
53
|
+
- `currentStatus == null` 且 `currentState.processState === "alive" && currentState.currentRunId === command.runId`:校验 `payload.statusPatch` 为自举合格(含 `processState`、`semanticState` 字段且 `statusPatch.runId === command.runId`)→ 以 patch 自身为基座走 applyStatusProjection(基座 = patch 本身,非 `{}`),mutate.status 为全量 patch;
|
|
54
|
+
- `currentStatus == null` 且行不存活或 runId 不匹配:维持 `stale_run` 拒绝(F2 原语义);
|
|
55
|
+
- `currentStatus != null`:原逻辑不变(liveness 守卫 + 投影)。
|
|
56
|
+
- 更新 F2 的既有测试(`run_progress` 缺 status → stale_run)为分场景:行不匹配仍拒;行匹配 → 自举 applied。
|
|
57
|
+
- [ ] **Step 1: 失败测试**——三个场景(自举 applied + 全量 status patch;行 exited → stale_run;patch 稀疏缺 processState → stale_run)
|
|
58
|
+
- [ ] **Step 2–5: 失败 → 实现 → PASS → Commit**(`feat(core): run_progress bootstraps a missing status from its full patch (issue #91)`)
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
### Task 2: Coordinator——bootstrap kinds 扩展 + 硬宕残留端到端
|
|
63
|
+
|
|
64
|
+
**Files:**
|
|
65
|
+
- Modify: `runner/state-coordinator.mjs`(`STATUS_BOOTSTRAP_KINDS` 增加 `"run_progress"`)
|
|
66
|
+
- Test: `test/state-coordinator.integration.test.mjs`
|
|
67
|
+
|
|
68
|
+
**Interfaces:**
|
|
69
|
+
- 端到端回归测试(残留关闭证明):`mark_queued` applied → **不发 run_started**(模拟硬宕窗口错过)→ 发一条 `run_progress` beat(全量 patch)→ 断言 status 文件自举创建(含 revision 戳)+ state 投影 applied → 再发 `run_finalized` → 断言 applied(此前会 stale_run 拒绝)。全程 journal 只有 mark_queued + run_finalized(beat 不落 journal)。
|
|
70
|
+
- 既有 F2 集成测试同步更新。
|
|
71
|
+
- [ ] **Step 1–5: TDD → Commit**(`feat(runner): beats bootstrap missing status, closing the hard-down window (issue #91)`)
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
### Task 3: 读侧 revision 一致性(reconcile enforcement)
|
|
76
|
+
|
|
77
|
+
**Files:**
|
|
78
|
+
- Create: `src/core/status-consistency.mjs`(纯函数 helper)
|
|
79
|
+
- Modify: `src/runtime/service.mjs`(reconcile 组合读处)
|
|
80
|
+
- Test: `test/status-consistency.test.mjs` + `test/service.test.mjs`
|
|
81
|
+
|
|
82
|
+
**Interfaces:**
|
|
83
|
+
- Produces:
|
|
84
|
+
- `statusRevisionDesynced(state, status)` → boolean:`state?.materializedRevision != null && status?.materializedRevision != null && state.materializedRevision !== status.materializedRevision`(legacy 任一侧缺失 → false,不检查)。
|
|
85
|
+
- reconcile(~L2022 读 status 处)集成:desynced → `appendDiagnostic(code: "state_status_revision_desync", level: "warn")` + `void ensureCoordinator(root).catch(() => {})`(异步踢修复;boot replay 修复半物化对)+ `continue`(跳过本行,不计 fixed,不拼状态)。
|
|
86
|
+
- `ensureCoordinator` 需从 `src/core/coordinator-client.mjs` 导出(检查是否已导出;PR #1 Task 5 建过,确认签名)。
|
|
87
|
+
- 测试:纯函数表驱动(desync/一致/legacy 侧缺失/双侧缺失);service 集成(手动构造半物化对——state 戳 N+1、status 戳 N——reconcile 跳过该行 + diagnostic 落盘 + 不计入 fixed;coordinator spawn 后可另行验证修复,不强求同测试内)。
|
|
88
|
+
- [ ] **Step 1–5: TDD → Commit**(`feat(core): reader-side revision consistency enforcement in reconcile (issue #91)`)
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
### Task 4: 卫生波
|
|
93
|
+
|
|
94
|
+
**Files:**
|
|
95
|
+
- Modify: `src/ui/dashboard.ts`、`src/runtime/service.mjs`、`runner/job-runner.mjs`、`runner/state-runner.mjs`(措辞)+ 相关调用点(void+catch pass)
|
|
96
|
+
|
|
97
|
+
**清单(每项独立可验证):**
|
|
98
|
+
1. `submitDispatch`(dashboard.ts ~L844+):发起异步 dispatch 前**同步**清 `this.launch`/输入/mode;`.then` 链尾加 `.catch`(notice "Dispatch failed")。
|
|
99
|
+
2. JSDoc:`launchForView` `@returns` → Promise 形状;`startHostUnderLease` `@returns` 修正。
|
|
100
|
+
3. decided-rejection 措辞:job-runner 的 run_started/finalize 处 `manual_fence`/`stale_run` 分支改 info 级 skipped diagnostic(或明确措辞 warn,不再用 "outcome unknown … replay will recover");state-runner 同类分支对齐。
|
|
101
|
+
4. void+catch pass:13 处 `markVisited?.()`、7 处 `service.reconcile()` 调用点加 `void …catch(() => {})`(仅吞 fs 类异常;调用点文件:dashboard.ts、attach-flow.ts、agent-board.ts、index.ts)。
|
|
102
|
+
- [ ] **验证**:受影响套件 + typecheck → Commit(`chore: post-merge hygiene wave — catch guards, dispatch double-submit window, honest diagnostics (issue #91)`)
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
### Task 5: 全量回归 + 验收对账
|
|
107
|
+
|
|
108
|
+
- [ ] `node --test test/*.test.mjs` 全绿 + `npm run typecheck` + 零测试进程泄漏(按 root 路径区分生产 coordinator)
|
|
109
|
+
- [ ] 对账写入 PR 描述:**根治条件 1/2/5 全闭合**(D3 弧线完成);3/4/6 属 Phase 3-6;已知残留清单更新(硬宕窗口已关,剩「coordinator 永不回来」= 既有 fail-closed 语义)
|
|
110
|
+
|
|
111
|
+
## Self-Review 记录
|
|
112
|
+
|
|
113
|
+
- Spec 覆盖:根治条件 5 读侧 + §5 降级;不越界到 Phase 3-6。
|
|
114
|
+
- 接口一致性:`statusRevisionDesynced`/bootstrap 分支语义/`STATUS_BOOTSTRAP_KINDS` 在 Task 1/2/3 间一致。
|
|
115
|
+
- 无占位符;每个 hygiene 项独立可验证。
|