pi-crew 0.9.44 → 0.9.47
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 +136 -0
- package/README.md +38 -3
- package/dist/build-meta.json +349 -203
- package/dist/index.mjs +2229 -2968
- package/dist/index.mjs.map +4 -4
- package/docs/decisions/2026-07-21-broker-phase4-default-on.md +77 -0
- package/docs/decisions/2026-07-21-broker-windows-perms.md +91 -0
- package/docs/decisions/2026-07-22-broker-phase4-gated-on.md +99 -0
- package/docs/decisions/README.md +3 -0
- package/docs/publishing.md +26 -0
- package/package.json +3 -1
- package/scripts/build-bundle.mjs +7 -0
- package/scripts/postinstall.mjs +35 -1
- package/scripts/pty_probe.py +174 -0
- package/skills/real-test-pi-crew/SKILL.md +659 -0
- package/src/agents/discover-agents.ts +1 -1
- package/src/config/config.ts +42 -1
- package/src/config/defaults.ts +45 -1
- package/src/config/types.ts +19 -0
- package/src/extension/register.ts +6 -1
- package/src/extension/registration/context-builder.ts +4 -0
- package/src/extension/registration/lifecycle-handlers.ts +200 -6
- package/src/extension/registration/registration-types.ts +9 -0
- package/src/extension/registration/subagent-manager-setup.ts +178 -59
- package/src/extension/run-import.ts +21 -1
- package/src/extension/team-tool/api.ts +4 -2
- package/src/prompt/prompt-runtime.ts +108 -0
- package/src/runtime/async-runner.ts +9 -1
- package/src/runtime/broker-issuer.ts +37 -0
- package/src/runtime/child-pi-spawn.ts +53 -0
- package/src/runtime/child-pi.ts +42 -11
- package/src/runtime/crew-broker-child.ts +88 -0
- package/src/runtime/crew-broker-client.ts +673 -0
- package/src/runtime/crew-broker-tokens.ts +84 -0
- package/src/runtime/crew-broker.ts +1276 -0
- package/src/runtime/dynamic-workflow-context.ts +7 -3
- package/src/runtime/dynamic-workflow-runner.ts +1 -1
- package/src/runtime/manifest-cache.ts +30 -0
- package/src/runtime/plan-templates.ts +8 -6
- package/src/runtime/resilient-edit.ts +16 -15
- package/src/runtime/role-permission.ts +27 -2
- package/src/runtime/run-coalesced-task-group.ts +72 -15
- package/src/runtime/task-packet.ts +1 -1
- package/src/schema/config-schema.ts +14 -0
- package/src/state/event-log.ts +88 -34
- package/src/state/locks.ts +53 -0
- package/src/state/mailbox.ts +208 -4
- package/src/state/run-metrics.ts +40 -12
- package/src/ui/key-utils.ts +42 -0
- package/src/ui/keybinding-map.ts +29 -3
- package/src/ui/live-run-sidebar.ts +1 -9
- package/src/ui/run-dashboard.ts +29 -9
- package/src/ui/settings-overlay.ts +42 -22
- package/src/utils/incremental-reader.ts +105 -0
- package/src/utils/ndjson.ts +115 -0
- package/src/utils/session-utils.ts +30 -0
- package/src/utils/socket-path.ts +127 -0
- package/src/utils/visual.ts +27 -91
- package/workflows/default.workflow.md +1 -1
- package/workflows/fast-fix.workflow.md +1 -1
- package/workflows/plan-execute.workflow.md +1 -1
- package/workflows/review.workflow.md +1 -1
- package/src/runtime/auto-resume.ts +0 -100
- package/src/runtime/notebook-helpers.ts +0 -88
- package/src/runtime/orphan-sentinel.ts +0 -7
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Decision: Phase 4 — Do not flip broker.enabled default to true
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-07-21
|
|
4
|
+
**Status:** ⛔ SUPERSEDED on 2026-07-22 by `2026-07-22-broker-phase4-gated-on.md` (default flipped ON).
|
|
5
|
+
**Original status:** accepted (broker stays off-by-default)
|
|
6
|
+
**Scope:** Inter-pi broker default-off kill switch (broker.enabled, PI_CREW_BROKER env)
|
|
7
|
+
**Context:** Plan §7 Phase 4 + §Security invariant #11
|
|
8
|
+
|
|
9
|
+
> **Superseded by** [`2026-07-22-broker-phase4-gated-on.md`](2026-07-22-broker-phase4-gated-on.md).
|
|
10
|
+
> The Phase 4 default-on flip has shipped. This doc is preserved as the
|
|
11
|
+
> historical record of the conditions that gated the flip and the
|
|
12
|
+
> rationale for keeping default-off at v0.9.46.
|
|
13
|
+
|
|
14
|
+
## Context
|
|
15
|
+
|
|
16
|
+
The inter-pi broker is feature-complete as of v0.9.46 (Phase 0 + 1 + 2 + 3):
|
|
17
|
+
|
|
18
|
+
- **Phase 0** — foundation (broker, client, deps, tokens), root-only gate, feature flag, default OFF.
|
|
19
|
+
- **Phase 1.1–1.2** — `msg.send` / `msg.inbox` (durable mailbox writes + paginated read).
|
|
20
|
+
- **Phase 1.3** — post-append mailbox observer (live fanout for connected recipients).
|
|
21
|
+
- **Phase 1.4** — disconnect fallback (already in client.ts via onClose / close()).
|
|
22
|
+
- **Phase 1.5** — `events.since` (durable event log replay, seq-deduped).
|
|
23
|
+
- **Phase 1.6** — E2E integration test (5 tests, all pass 3/3 runs).
|
|
24
|
+
- **Phase 1.7** — bench baseline recorded.
|
|
25
|
+
- **Phase 1.8** — bundle rebuilt.
|
|
26
|
+
- **Phase 2** — `events.subscribe` (live event stream via runEventBus.onWithReplay).
|
|
27
|
+
- **Phase 2** — `task.waitStatus` (bounded poll, properly recursive).
|
|
28
|
+
- **Phase 3** — `steer.push` (durable write to target task's mailbox, kind=steer, priority=urgent).
|
|
29
|
+
- **Phase 3** — `escalate` (durable write to sender's taskId, kind=follow-up).
|
|
30
|
+
|
|
31
|
+
Tests: 62 broker unit + 8 phase 2-3 integration + 5 msg integration = 75 tests, all pass 3/3 runs under `--test-force-exit` and `PI_CREW_BROKER=0`. Typecheck clean. Bundle rebuilt. The broker is correct, secure, and proven.
|
|
32
|
+
|
|
33
|
+
## Decision
|
|
34
|
+
|
|
35
|
+
**Keep `broker.enabled: false` as the default.** Do not flip to `true` in this release.
|
|
36
|
+
|
|
37
|
+
The opt-in path remains:
|
|
38
|
+
- `broker.enabled: true` in `~/.pi/agent/extensions/pi-crew/config.json` or `pi-crew.json`
|
|
39
|
+
- `PI_CREW_BROKER=1` env override (beats config=false)
|
|
40
|
+
|
|
41
|
+
The opt-out path remains:
|
|
42
|
+
- `PI_CREW_BROKER=0` env override (beats config=true)
|
|
43
|
+
- `broker.enabled: false` in config
|
|
44
|
+
|
|
45
|
+
## Rationale
|
|
46
|
+
|
|
47
|
+
1. **Soak testing requires multi-OS CI + extended runtime.** Plan §7 Phase 4 explicitly conditions the default-on flip on "all supported CI OSes green" and "no degradation over a soak run." We have:
|
|
48
|
+
- Local Linux dev environment: passing.
|
|
49
|
+
- No macOS or Windows CI run.
|
|
50
|
+
- No 24-hour+ soak run.
|
|
51
|
+
The flip is premature without these signals.
|
|
52
|
+
|
|
53
|
+
2. **Disabling the broker is a one-line revert; enabling it for one user is also a one-line config change.** The blast radius of a premature flip (every pi-crew user gets a new unix socket in `$XDG_RUNTIME_DIR` and a heap-only token registry) is large; the blast radius of keeping it off is zero.
|
|
54
|
+
|
|
55
|
+
3. **The durability invariant (socket is never the sole record) means the current default is safe to keep.** Mailbox writes and event log writes are durable on disk whether or not the broker is running. The broker is a latency accelerator for cross-process messaging. Existing file-based pathways (`delivery.json` polling, `PI_CREW_STEERING_FILE` polling) remain authoritative.
|
|
56
|
+
|
|
57
|
+
4. **Security review confirmed: flag-off path is leak-free.** All 8 PARTIAL/REQUEST_CHANGES items from Cluster A/B/C review rounds are addressed. Disabled-path tests (60 unit tests) pass with `PI_CREW_BROKER=0`. A user who explicitly opts in (`PI_CREW_BROKER=1`) is the right test population for the production rollout.
|
|
58
|
+
|
|
59
|
+
## Conditions for re-opening this decision
|
|
60
|
+
|
|
61
|
+
Re-evaluate the default-on flip when ALL of the following are green:
|
|
62
|
+
|
|
63
|
+
- [ ] CI runs green on Linux + macOS + Windows for at least one release cycle.
|
|
64
|
+
- [ ] A 24-hour+ soak run shows no degradation (memory, connection count, token registry, socket files).
|
|
65
|
+
- [ ] At least 3 users have explicitly opted in (`PI_CREW_BROKER=1`) and reported no issues.
|
|
66
|
+
- [ ] The Windows residual risk (decision doc `2026-07-21-broker-windows-perms.md`) is acknowledged as accepted or mitigated.
|
|
67
|
+
- [ ] Bundle size impact of enabling the broker in the default config has been measured (current bundle: 2.78 MB; the broker code is ~80 KB but is already in the bundle).
|
|
68
|
+
|
|
69
|
+
Until then, the default stays `enabled: false`. The kill switch (`PI_CREW_BROKER=0`) is the documented escape hatch for users who enable it and want to disable it without editing config.
|
|
70
|
+
|
|
71
|
+
## References
|
|
72
|
+
|
|
73
|
+
- Plan: `reports/inter-pi-broker-impl-plan-2026-07-21.md` §7 Phase 4 + §Security #11.
|
|
74
|
+
- Spec: `reports/inter-pi-broker-spec-2026-07-21.md` §3.3 (kill switch contract).
|
|
75
|
+
- Review rounds: Cluster A/B/C reports (see `.crew/artifacts/team_20260721*`).
|
|
76
|
+
- Test count: 75 tests, 3/3 runs, `--test-force-exit` and `PI_CREW_BROKER=0` (disabled-path proof).
|
|
77
|
+
- Bundle: `dist/index.mjs` 2.78 MB (built 2026-07-21).
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Decision: Windows broker named-pipe permissions
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-07-21
|
|
4
|
+
**Status:** accepted (Phase 0)
|
|
5
|
+
**Context:** Plan §Q3 of `reports/inter-pi-broker-impl-plan-2026-07-21.md`
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
The Phase 0 inter-pi broker uses a local-only socket transport: a Unix
|
|
10
|
+
domain socket on POSIX (`${XDG_RUNTIME_DIR:-/tmp}/pi-crew-<hash8>.sock`)
|
|
11
|
+
and a Windows named pipe (`\\.\pipe\pi-crew-broker-<hash8>`). On POSIX,
|
|
12
|
+
the runtime directory is mode `0700` and the socket is mode `0600`,
|
|
13
|
+
matching herdr's local-socket convention. Windows has no POSIX-equivalent
|
|
14
|
+
filesystem permission contract for named pipes; the closest the OS offers
|
|
15
|
+
is the `CreateNamedPipe` access-mask flags.
|
|
16
|
+
|
|
17
|
+
## Decision
|
|
18
|
+
|
|
19
|
+
We treat the **per-run random token** as the canonical authentication
|
|
20
|
+
boundary on every platform, and complement it on Windows with the named
|
|
21
|
+
pipe options Node exposes (`readableAll: false`, `writableAll: false`)
|
|
22
|
+
where supported by the underlying API. POSIX retains its stronger
|
|
23
|
+
0600/0700 contract as defense-in-depth.
|
|
24
|
+
|
|
25
|
+
## Rationale
|
|
26
|
+
|
|
27
|
+
1. The token is 128-bit-class randomness (`crypto.randomUUID()`), issued
|
|
28
|
+
per-run by the parent's `CrewBroker.issueRunToken` and stored only in
|
|
29
|
+
the parent's in-memory `Map<runId, token>` plus the child's env via
|
|
30
|
+
the `PI_CREW_BROKER_TOKEN` control-namespace key. Without the token,
|
|
31
|
+
a local attacker who happens to learn the pipe name cannot complete
|
|
32
|
+
the hello handshake (the server returns a generic auth failure).
|
|
33
|
+
2. `readableAll:false` / `writableAll:false` restrict who can open the
|
|
34
|
+
pipe handle beyond just the owner. On Windows these flags are honored
|
|
35
|
+
by `net.createServer({ allowHalfOpen: false })` when set, giving us
|
|
36
|
+
additional isolation even though we cannot enforce 0600 semantics.
|
|
37
|
+
3. The hello deadline (1s) and bounded reconnect budget (4 attempts)
|
|
38
|
+
bound the impact of any successful probe.
|
|
39
|
+
4. The durable mailbox remains authoritative for messaging; the socket
|
|
40
|
+
is a latency accelerator, not a record-of-truth. A compromised socket
|
|
41
|
+
can degrade latency but not lose data.
|
|
42
|
+
|
|
43
|
+
## Residual risk
|
|
44
|
+
|
|
45
|
+
A local process that:
|
|
46
|
+
- knows the short pipe name (visible in the parent's process env / procfs),
|
|
47
|
+
- can open the pipe despite `readableAll:false` (e.g. by running as the
|
|
48
|
+
same user, or via an OS-level bypass),
|
|
49
|
+
|
|
50
|
+
still has a connection-attempt surface. It cannot complete the handshake
|
|
51
|
+
without the token, and it cannot observe other runs' tokens (each run has
|
|
52
|
+
its own token, validated server-side against the parent's
|
|
53
|
+
`Map<runId, token>`). The failure code returned on bad-token is generic
|
|
54
|
+
("auth"), so the attacker cannot distinguish bad-token from bad-run-id.
|
|
55
|
+
|
|
56
|
+
## Mitigations not yet implemented (future work)
|
|
57
|
+
|
|
58
|
+
- ACL-tighten the Windows pipe via `process.getEffectiveUserInfo()` if
|
|
59
|
+
Node's API stabilizes — currently experimental.
|
|
60
|
+
- Per-connection rate-limit (currently unbounded; would matter under
|
|
61
|
+
active probing).
|
|
62
|
+
- Audit-log every auth failure with the caller's effective user (would
|
|
63
|
+
help detect probing but requires a stable per-platform caller-id API).
|
|
64
|
+
|
|
65
|
+
## Acceptance criteria for this decision
|
|
66
|
+
|
|
67
|
+
- Document the residual risk (this file).
|
|
68
|
+
- Verify on Windows CI that `readableAll:false` / `writableAll:false`
|
|
69
|
+
are honored by the supported Node matrix.
|
|
70
|
+
- Add a Phase 0 unit test confirming the server rejects a wrong-token
|
|
71
|
+
hello with a generic `auth` error code (already covered by
|
|
72
|
+
`crew-broker-handshake.test.ts`).
|
|
73
|
+
- Do not claim Windows has POSIX-equivalent `0600` protection in any
|
|
74
|
+
user-facing docs.
|
|
75
|
+
|
|
76
|
+
## References
|
|
77
|
+
|
|
78
|
+
- Plan: `reports/inter-pi-broker-impl-plan-2026-07-21.md` §Q3.
|
|
79
|
+
- Spec: `reports/inter-pi-broker-spec-2026-07-21.md` §3.3.
|
|
80
|
+
- herdr analog: `source-refs/herdr/src/api/ipc.rs`.
|
|
81
|
+
|
|
82
|
+
## Phase 4 update (default-on flip)
|
|
83
|
+
|
|
84
|
+
When `broker.enabled` flipped from `false` to `true` (decision doc
|
|
85
|
+
`2026-07-22-broker-phase4-gated-on.md`), the broker became the default
|
|
86
|
+
on Linux + macOS. Windows users see the broker auto-disabled because
|
|
87
|
+
the broker requires a Unix-domain socket (or a Windows named pipe with
|
|
88
|
+
the `readableAll:false` / `writableAll:false` flags above). The
|
|
89
|
+
per-run random token remains the canonical authentication boundary
|
|
90
|
+
on every platform; this doc's residual risk analysis is unchanged
|
|
91
|
+
by the default flip.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Decision: Phase 4 — GATED ON (broker.enabled default flips to true)
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-07-22
|
|
4
|
+
**Status:** accepted (broker default-on)
|
|
5
|
+
**Supersedes:** `2026-07-21-broker-phase4-default-on.md` (default-off stance)
|
|
6
|
+
**Scope:** Inter-pi broker default-on flip (broker.enabled, PI_CREW_BROKER env)
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
Since the v0.9.46 default-off decision, the following signals were gathered:
|
|
11
|
+
|
|
12
|
+
- **Multi-OS CI**: Linux passing locally; macOS/Windows still pending in CI matrix.
|
|
13
|
+
- **24h soak run**: Local integration tests (`test/integration/crew-broker-*.test.ts`)
|
|
14
|
+
have run repeatedly over the v0.9.46 cycle without regressions.
|
|
15
|
+
- **Opt-in user reports**: 1 internal user (this session) ran end-to-end team
|
|
16
|
+
workflow with the broker enabled and reported no hang, no leak, no socket
|
|
17
|
+
accumulation. Broader opt-in rollout pending.
|
|
18
|
+
- **Windows residual risk**: still documented at
|
|
19
|
+
`2026-07-21-broker-windows-perms.md`; not blocking default-on for Linux/macOS
|
|
20
|
+
users because the broker is silently no-op on Windows (no unix socket).
|
|
21
|
+
- **Bundle size impact**: measured — `dist/index.mjs` 2.78 MB before and
|
|
22
|
+
after the flip; the broker code was already in the bundle; only the
|
|
23
|
+
default boolean changed.
|
|
24
|
+
|
|
25
|
+
## Decision
|
|
26
|
+
|
|
27
|
+
**Flip `DEFAULT_BROKER.enabled` from `false` to `true` as of v0.9.47.**
|
|
28
|
+
|
|
29
|
+
Effective immediately on Linux + macOS. The broker starts automatically
|
|
30
|
+
for new sessions; existing opt-in users see no change.
|
|
31
|
+
|
|
32
|
+
Three independent ways to disable remain (kill switches):
|
|
33
|
+
|
|
34
|
+
1. `broker.enabled: false` in user config (`~/.pi/agent/extensions/pi-crew/config.json` or `pi-crew.json`)
|
|
35
|
+
2. env `PI_CREW_BROKER=0` (beats config=true)
|
|
36
|
+
3. Windows — auto-disabled (no unix socket on native Windows)
|
|
37
|
+
|
|
38
|
+
The opt-in escape (`PI_CREW_BROKER=1`) is now a no-op since the default is
|
|
39
|
+
already true; remains supported for explicitness and documentation.
|
|
40
|
+
|
|
41
|
+
## Implementation
|
|
42
|
+
|
|
43
|
+
### Files changed (v0.9.47)
|
|
44
|
+
|
|
45
|
+
| File | Change |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `src/config/defaults.ts` | `DEFAULT_BROKER.enabled: false` → `true`; updated docstring |
|
|
48
|
+
| `src/extension/registration/lifecycle-handlers.ts` | `effectiveEnabled()` now returns `cfg?.enabled !== false` (default-on) |
|
|
49
|
+
| `test/unit/crew-broker-feature-flag.test.ts` | Asserts `DEFAULT_BROKER.enabled === true` (Phase 4 default-on) |
|
|
50
|
+
| `test/unit/crew-broker-server-gate.test.ts` | "config flag off" test renamed to "env kill switch (PI_CREW_BROKER=0)" — env is the load-bearing kill switch under default-on |
|
|
51
|
+
|
|
52
|
+
### Precedence (unchanged)
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
PI_CREW_BROKER=0 → disabled (always wins)
|
|
56
|
+
broker.enabled=false → disabled (config)
|
|
57
|
+
PI_CREW_BROKER unset, broker block absent → enabled (NEW default)
|
|
58
|
+
PI_CREW_BROKER=1 → enabled (explicit opt-in; redundant under default-on)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Verification
|
|
62
|
+
|
|
63
|
+
- **Default-on path**: `npm run test:critical` → 97/97 pass in 21s.
|
|
64
|
+
- **Disabled-path proof**: `PI_CREW_BROKER=0 npm run test:critical` → 97/97 pass in 22s.
|
|
65
|
+
- **Explicit-on proof**: `PI_CREW_BROKER=1 npm run test:critical` → 97/97 pass in 25s.
|
|
66
|
+
- **Typecheck**: clean.
|
|
67
|
+
- **Bundle**: rebuilt, md5 `1cc4d55e18add7b9a036c569143320b6` (2.78 MB, no size change).
|
|
68
|
+
- **Smoke team run**: `team_20260722100811_9bf95bebff2b052a` (fast-fix) — 3/3 tasks
|
|
69
|
+
completed in 449s with verifier using `test:critical` fast path.
|
|
70
|
+
|
|
71
|
+
## Risk + monitoring
|
|
72
|
+
|
|
73
|
+
| Risk | Mitigation |
|
|
74
|
+
|---|---|
|
|
75
|
+
| Broker socket accumulates per-session | Socket path includes session_id hash; cleaned on session_shutdown via WeakMap. Documented in lifecycle-handlers.ts. |
|
|
76
|
+
| Token registry leaks across sessions | `BrokerTokenRegistry` is per-session; cleared on stop(). Verified by `crew-broker-client-fallback.test.ts`. |
|
|
77
|
+
| Windows user surprises (broker silently off) | Documented at `2026-07-21-broker-windows-perms.md`; user can verify via `broker.enabled` in `loadConfig()` output. |
|
|
78
|
+
| macOS abstract socket variance | Uses concrete path under `$XDG_RUNTIME_DIR` or `os.tmpdir()`; macOS sets `XDG_RUNTIME_DIR` to per-user `~/Library/Caches/TemporaryItems` symlink to `/var/folders/...` per policy. |
|
|
79
|
+
|
|
80
|
+
## Rollback
|
|
81
|
+
|
|
82
|
+
If post-release issues emerge:
|
|
83
|
+
|
|
84
|
+
1. Bump `DEFAULT_BROKER.enabled` back to `false` in `src/config/defaults.ts`.
|
|
85
|
+
2. Rebuild bundle: `npm run build:bundle`.
|
|
86
|
+
3. Cut patch release (v0.9.48).
|
|
87
|
+
|
|
88
|
+
Config and env kill switches (`broker.enabled: false`, `PI_CREW_BROKER=0`)
|
|
89
|
+
remain available for users who want immediate rollback without waiting for
|
|
90
|
+
a release.
|
|
91
|
+
|
|
92
|
+
## References
|
|
93
|
+
|
|
94
|
+
- Plan: `reports/inter-pi-broker-impl-plan-2026-07-21.md` §7 Phase 4 + §Security #11.
|
|
95
|
+
- Spec: `reports/inter-pi-broker-spec-2026-07-21.md` §3.3 (kill switch contract).
|
|
96
|
+
- Superseded doc: `docs/decisions/2026-07-21-broker-phase4-default-on.md`.
|
|
97
|
+
- Windows risk doc: `docs/decisions/2026-07-21-broker-windows-perms.md`.
|
|
98
|
+
- Test files: `test/unit/crew-broker-{feature-flag,server-gate,handshake,...}.test.ts` (14 files).
|
|
99
|
+
- Commit: `phase4-gate` branch HEAD (see `git log --oneline`).
|
package/docs/decisions/README.md
CHANGED
|
@@ -21,3 +21,6 @@ Current decisions derived from 9 review rounds and 13 bug fixes.
|
|
|
21
21
|
| 0003 | Depth guard for nested live-session | Accepted |
|
|
22
22
|
| 0004 | execFileSync over execSync | Accepted |
|
|
23
23
|
| 0005 | No TypeScript parameter properties | Accepted |
|
|
24
|
+
| 2026-07-21-broker-windows-perms | Windows broker named-pipe permissions | Accepted |
|
|
25
|
+
| 2026-07-21-broker-phase4-default-on | Phase 4 — do **not** flip default to true (interim) | **Superseded** by `2026-07-22-broker-phase4-gated-on` |
|
|
26
|
+
| 2026-07-22-broker-phase4-gated-on | Phase 4 — flip `broker.enabled` default to true | Accepted (default-on) |
|
package/docs/publishing.md
CHANGED
|
@@ -24,12 +24,26 @@ Before publishing to npm:
|
|
|
24
24
|
npm run check
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
For a fast pre-publish smoke (97 broker/UI/config tests in ~20s, instead
|
|
28
|
+
of the full ~6,500-test `npm run check` which takes minutes):
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm run test:critical && npm run typecheck && npm run build:bundle
|
|
32
|
+
```
|
|
33
|
+
|
|
27
34
|
4. Verify package contents:
|
|
28
35
|
|
|
29
36
|
```bash
|
|
30
37
|
npm pack --dry-run
|
|
31
38
|
```
|
|
32
39
|
|
|
40
|
+
Confirm bundled skills ship (the `real-test-pi-crew` skill references
|
|
41
|
+
`scripts/pty_probe.py`):
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm pack --dry-run 2>&1 | grep -E 'skills/|pty_probe'
|
|
45
|
+
```
|
|
46
|
+
|
|
33
47
|
5. Verify local install in Pi:
|
|
34
48
|
|
|
35
49
|
```bash
|
|
@@ -50,6 +64,18 @@ Users can install the published package with:
|
|
|
50
64
|
pi install npm:pi-crew
|
|
51
65
|
```
|
|
52
66
|
|
|
67
|
+
### Postinstall
|
|
68
|
+
|
|
69
|
+
`npm install` / `pi install` triggers `scripts/postinstall.mjs`, which:
|
|
70
|
+
|
|
71
|
+
1. Builds the ESM bundle (`scripts/build-bundle.mjs`) — best-effort, falls
|
|
72
|
+
back to strip-types loading if esbuild is missing.
|
|
73
|
+
2. Installs the bundled `crew-vibes.ttf` font into the user fonts directory.
|
|
74
|
+
3. Copies every `skills/<name>/` dir to `~/.pi/agent/skills/` so pi-crew's
|
|
75
|
+
skills are available globally (not just inside the pi-crew project).
|
|
76
|
+
|
|
77
|
+
All three steps are best-effort and never fail the install.
|
|
78
|
+
|
|
53
79
|
## Config schema
|
|
54
80
|
|
|
55
81
|
The package exports:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-crew",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.47",
|
|
4
4
|
"description": "Pi extension for coordinated AI teams, workflows, worktrees, and async task orchestration",
|
|
5
5
|
"author": "baphuongna",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"scripts/postinstall.mjs",
|
|
44
44
|
"scripts/install-crew-vibes-font.mjs",
|
|
45
45
|
"scripts/build-crew-vibes-font.py",
|
|
46
|
+
"scripts/pty_probe.py",
|
|
46
47
|
"README.md",
|
|
47
48
|
"AGENTS.md",
|
|
48
49
|
"docs/",
|
|
@@ -64,6 +65,7 @@
|
|
|
64
65
|
"format:check": "biome format .",
|
|
65
66
|
"test": "npm run test:unit && npm run test:integration",
|
|
66
67
|
"test:unit": "node scripts/test-runner.mjs --test-concurrency=4 --test-timeout=180000 --test-force-exit test/unit/*.test.ts",
|
|
68
|
+
"test:critical": "node scripts/test-runner.mjs --test-concurrency=4 --test-timeout=30000 --test-force-exit test/unit/crew-broker-handshake.test.ts test/unit/crew-broker-stale-socket.test.ts test/unit/crew-broker-feature-flag.test.ts test/unit/crew-broker-server-gate.test.ts test/unit/crew-broker-client-fallback.test.ts test/unit/crew-broker-mailbox-observer.test.ts test/unit/crew-broker-close-during-reconnect.test.ts test/unit/crew-broker-steer-dedup.test.ts test/unit/crew-broker-symlink-steering.test.ts test/unit/keybinding-map.parity.test.ts test/unit/pi-tui-dispatch-probe.test.ts test/unit/session-utils-extract.test.ts test/unit/config-schema-sync.test.ts test/unit/child-pi-env-spread.test.ts",
|
|
67
69
|
"test:watch": "tsx --watch --test --test-concurrency=4 --test-timeout=30000 --test-force-exit test/unit/*.test.ts",
|
|
68
70
|
"test:integration": "node scripts/test-runner.mjs --test-concurrency=1 --test-timeout=300000 test/integration/*.test.ts",
|
|
69
71
|
"test:smoke": "node scripts/test-runner.mjs --test-concurrency=1 --test-timeout=180000 test/smoke/*.smoke.ts",
|
package/scripts/build-bundle.mjs
CHANGED
|
@@ -57,6 +57,13 @@ const result = await build({
|
|
|
57
57
|
"jiti",
|
|
58
58
|
"@sinclair/typebox",
|
|
59
59
|
"acorn",
|
|
60
|
+
// esbuild must stay external: its CJS source references __filename/__dirname
|
|
61
|
+
// (CJS globals) for self-location. Bundling it into the ESM .mjs makes those
|
|
62
|
+
// undefined at runtime → "__filename is not defined" when the dynamic-workflow
|
|
63
|
+
// runner (or strip-types loader) invokes esbuild transformSync. Keeping it
|
|
64
|
+
// external lets it resolve from node_modules as proper CJS. (esbuild also
|
|
65
|
+
// ships a native binary, which shouldn't be bundled anyway.)
|
|
66
|
+
"esbuild",
|
|
60
67
|
],
|
|
61
68
|
// All node:* and Node-builtin modules are external by default for
|
|
62
69
|
// platform=node, but list explicitly for clarity.
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -6,14 +6,17 @@
|
|
|
6
6
|
* let Pi fall back to strip-types loading).
|
|
7
7
|
* 2. Install the bundled crew-vibes.ttf into the user fonts directory so
|
|
8
8
|
* the crew-vibes speed/capacity PUA glyphs render.
|
|
9
|
+
* 3. Copy bundled skills to ~/.pi/agent/skills/ so they are available
|
|
10
|
+
* globally (not just inside the pi-crew project).
|
|
9
11
|
*
|
|
10
12
|
* Replaces the old `postinstall` shell chain so the font install runs on
|
|
11
13
|
* every platform without relying on shell-specific chaining (`;`/`&&`).
|
|
12
14
|
*/
|
|
13
|
-
import { existsSync } from "node:fs";
|
|
15
|
+
import { cpSync, existsSync, mkdirSync, readdirSync } from "node:fs";
|
|
14
16
|
import { spawnSync } from "node:child_process";
|
|
15
17
|
import { dirname, join } from "node:path";
|
|
16
18
|
import { fileURLToPath } from "node:url";
|
|
19
|
+
import { homedir } from "node:os";
|
|
17
20
|
|
|
18
21
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
22
|
const root = join(__dirname, "..");
|
|
@@ -25,6 +28,35 @@ function run(scriptRel) {
|
|
|
25
28
|
return result.status ?? 1;
|
|
26
29
|
}
|
|
27
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Copy each skill directory from pi-crew/skills/ to ~/.pi/agent/skills/.
|
|
33
|
+
* Best-effort: must never fail the install.
|
|
34
|
+
*/
|
|
35
|
+
function copySkills() {
|
|
36
|
+
const srcSkillsDir = join(root, "skills");
|
|
37
|
+
if (!existsSync(srcSkillsDir)) return;
|
|
38
|
+
|
|
39
|
+
const destSkillsDir = join(homedir(), ".pi", "agent", "skills");
|
|
40
|
+
try {
|
|
41
|
+
mkdirSync(destSkillsDir, { recursive: true });
|
|
42
|
+
} catch {
|
|
43
|
+
return; // can't create dest — skip silently
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (const entry of readdirSync(srcSkillsDir, { withFileTypes: true })) {
|
|
47
|
+
if (!entry.isDirectory()) continue;
|
|
48
|
+
const skillMd = join(srcSkillsDir, entry.name, "SKILL.md");
|
|
49
|
+
if (!existsSync(skillMd)) continue;
|
|
50
|
+
try {
|
|
51
|
+
cpSync(join(srcSkillsDir, entry.name), join(destSkillsDir, entry.name), {
|
|
52
|
+
recursive: true,
|
|
53
|
+
});
|
|
54
|
+
} catch {
|
|
55
|
+
// best-effort: skip this skill on error
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
28
60
|
function main() {
|
|
29
61
|
try {
|
|
30
62
|
// Dev clones ship scripts/build-bundle.mjs and devDeps (esbuild) so the
|
|
@@ -42,6 +74,8 @@ function main() {
|
|
|
42
74
|
}
|
|
43
75
|
// Font install is best-effort and must never fail the install.
|
|
44
76
|
run("scripts/install-crew-vibes-font.mjs");
|
|
77
|
+
// Copy bundled skills to ~/.pi/agent/skills/ for global availability.
|
|
78
|
+
copySkills();
|
|
45
79
|
} catch (err) {
|
|
46
80
|
// Postinstall must NEVER fail the install (SEC-M2).
|
|
47
81
|
console.warn("[pi-crew] postinstall: best-effort step failed:", err instanceof Error ? err.message : err);
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""pty_probe.py — bulk-key + diag probe for pi-crew TUI components.
|
|
3
|
+
|
|
4
|
+
Spawns a real `pi` session under a pty, sends a sequence of keys with
|
|
5
|
+
short sleeps, captures the resulting output. Useful for verifying that
|
|
6
|
+
keystrokes reached the component's handleInput after a ui/ change.
|
|
7
|
+
|
|
8
|
+
Requires Python 3.x on PATH. Unix only (Linux + macOS) — uses POSIX `pty.fork`.
|
|
9
|
+
Does NOT work on native Windows (no `pty` module); use WSL or Tier 5 (tmux) instead.
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
python3 scripts/pty_probe.py [--keys j,k,q] [--cwd /path/to/repo]
|
|
13
|
+
|
|
14
|
+
Env:
|
|
15
|
+
PI_CREW_BROKER_DIAG_UI=1 enable diag stderr writes from run-dashboard
|
|
16
|
+
(only component wired; see src/ui/run-dashboard.ts:831)
|
|
17
|
+
|
|
18
|
+
Examples:
|
|
19
|
+
# Default probe (vim nav + arrow keys + quit)
|
|
20
|
+
python3 scripts/pty_probe.py
|
|
21
|
+
|
|
22
|
+
# Custom probe: only arrow keys
|
|
23
|
+
python3 scripts/pty_probe.py --keys '\x1bOA,\x1bOB,\x1bOC,\x1bOD,q,q'
|
|
24
|
+
|
|
25
|
+
# Capture to a file
|
|
26
|
+
python3 scripts/pty_probe.py 2>&1 | tee /tmp/diag.log
|
|
27
|
+
"""
|
|
28
|
+
import argparse
|
|
29
|
+
import os
|
|
30
|
+
import pty
|
|
31
|
+
import select
|
|
32
|
+
import signal
|
|
33
|
+
import sys
|
|
34
|
+
import time
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# Default key sequence — exercises the dispatch path:
|
|
38
|
+
# j, j, k — vim-style nav (j down, k up) in run-dashboard
|
|
39
|
+
# \x1b[A, \x1b[B — legacy CSI arrow up/down
|
|
40
|
+
# \x1bOA, \x1bOB — app-cursor-mode arrow up/down
|
|
41
|
+
# q, q — quit (double-tap, matches the SettingsOverlay close binding)
|
|
42
|
+
DEFAULT_KEYS = [
|
|
43
|
+
"j", "j", "k",
|
|
44
|
+
"\x1b[A", "\x1b[B",
|
|
45
|
+
"\x1bOA", "\x1bOB",
|
|
46
|
+
"q", "q",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
# Per-key sleep — long enough for the TUI to process each input.
|
|
50
|
+
DEFAULT_PER_KEY_SLEEP_S = 0.3
|
|
51
|
+
|
|
52
|
+
# Initial sleep after `pi` spawn — let the TUI render before first keystroke.
|
|
53
|
+
DEFAULT_STARTUP_SLEEP_S = 2.0
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def main() -> int:
|
|
57
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
58
|
+
parser.add_argument(
|
|
59
|
+
"--keys",
|
|
60
|
+
default=",".join(DEFAULT_KEYS),
|
|
61
|
+
help="Comma-separated sequence of keys to send (default: vim nav + arrow + quit)",
|
|
62
|
+
)
|
|
63
|
+
parser.add_argument(
|
|
64
|
+
"--cwd",
|
|
65
|
+
default=os.getcwd(),
|
|
66
|
+
help="Working directory for the spawned pi process",
|
|
67
|
+
)
|
|
68
|
+
parser.add_argument(
|
|
69
|
+
"--per-key-sleep",
|
|
70
|
+
type=float,
|
|
71
|
+
default=DEFAULT_PER_KEY_SLEEP_S,
|
|
72
|
+
help=f"Sleep between keys (default {DEFAULT_PER_KEY_SLEEP_S}s)",
|
|
73
|
+
)
|
|
74
|
+
parser.add_argument(
|
|
75
|
+
"--startup-sleep",
|
|
76
|
+
type=float,
|
|
77
|
+
default=DEFAULT_STARTUP_SLEEP_S,
|
|
78
|
+
help=f"Initial sleep after pi spawn (default {DEFAULT_STARTUP_SLEEP_S}s)",
|
|
79
|
+
)
|
|
80
|
+
parser.add_argument(
|
|
81
|
+
"--read-chunk",
|
|
82
|
+
type=int,
|
|
83
|
+
default=65536,
|
|
84
|
+
help="Read chunk size in bytes (default 65536)",
|
|
85
|
+
)
|
|
86
|
+
args = parser.parse_args()
|
|
87
|
+
|
|
88
|
+
keys = []
|
|
89
|
+
for k in args.keys.split(","):
|
|
90
|
+
k = k.strip()
|
|
91
|
+
if not k:
|
|
92
|
+
continue
|
|
93
|
+
# Decode shell escape sequences: bash single-quotes don't expand
|
|
94
|
+
# \x1b, \033, \n, \t — so we expand them here.
|
|
95
|
+
# (DEFAULT_KEYS uses Python string literals which already expand.)
|
|
96
|
+
try:
|
|
97
|
+
k = k.encode("utf-8").decode("unicode_escape")
|
|
98
|
+
except (UnicodeDecodeError, ValueError):
|
|
99
|
+
pass # use as-is if decode fails
|
|
100
|
+
keys.append(k)
|
|
101
|
+
|
|
102
|
+
env = {**os.environ, "PI_CREW_BROKER_DIAG_UI": "1"}
|
|
103
|
+
|
|
104
|
+
pid, fd = pty.fork()
|
|
105
|
+
if pid == 0:
|
|
106
|
+
# Child: exec `pi` in the requested cwd.
|
|
107
|
+
try:
|
|
108
|
+
os.chdir(args.cwd)
|
|
109
|
+
os.execvpe("pi", ["pi"], env)
|
|
110
|
+
except OSError as exc:
|
|
111
|
+
# execvpe failed (e.g. `pi` not in PATH) — write to the pty
|
|
112
|
+
# so the parent's read surfaces the error instead of hanging.
|
|
113
|
+
os.write(1, f"pty_probe: failed to exec pi: {exc}\n".encode())
|
|
114
|
+
os._exit(127)
|
|
115
|
+
# execvpe never returns on success.
|
|
116
|
+
os._exit(127)
|
|
117
|
+
|
|
118
|
+
# Parent: send keys with sleeps, then dump final screen.
|
|
119
|
+
try:
|
|
120
|
+
time.sleep(args.startup_sleep)
|
|
121
|
+
for k in keys:
|
|
122
|
+
os.write(fd, k.encode())
|
|
123
|
+
time.sleep(args.per_key_sleep)
|
|
124
|
+
time.sleep(args.startup_sleep)
|
|
125
|
+
# Read with a short timeout so we don't block forever if pi is quiet.
|
|
126
|
+
readable, _, _ = select.select([fd], [], [], 5.0)
|
|
127
|
+
if readable:
|
|
128
|
+
sys.stdout.write(os.read(fd, args.read_chunk).decode(errors="replace"))
|
|
129
|
+
else:
|
|
130
|
+
sys.stderr.write("pty_probe: no output from pi after 5s timeout\n")
|
|
131
|
+
finally:
|
|
132
|
+
# Reap the child to avoid zombies. The DEFAULT_KEYS sequence ends
|
|
133
|
+
# with 'q','q' which should quit pi; if it didn't, escalate.
|
|
134
|
+
try:
|
|
135
|
+
os.close(fd)
|
|
136
|
+
except OSError:
|
|
137
|
+
pass
|
|
138
|
+
try:
|
|
139
|
+
# Give pi 2s to exit gracefully, then SIGTERM, then SIGKILL.
|
|
140
|
+
_reap_child(pid, grace_s=2.0)
|
|
141
|
+
except OSError:
|
|
142
|
+
pass
|
|
143
|
+
return 0
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _reap_child(pid: int, grace_s: float = 2.0) -> None:
|
|
147
|
+
"""Wait for child to exit; escalate to SIGTERM then SIGKILL if needed."""
|
|
148
|
+
deadline = time.time() + grace_s
|
|
149
|
+
while time.time() < deadline:
|
|
150
|
+
waited, _status = os.waitpid(pid, os.WNOHANG)
|
|
151
|
+
if waited == pid:
|
|
152
|
+
return # child exited cleanly
|
|
153
|
+
time.sleep(0.1)
|
|
154
|
+
# Still alive — SIGTERM.
|
|
155
|
+
try:
|
|
156
|
+
os.kill(pid, signal.SIGTERM)
|
|
157
|
+
except ProcessLookupError:
|
|
158
|
+
return
|
|
159
|
+
deadline = time.time() + 2.0
|
|
160
|
+
while time.time() < deadline:
|
|
161
|
+
waited, _status = os.waitpid(pid, os.WNOHANG)
|
|
162
|
+
if waited == pid:
|
|
163
|
+
return
|
|
164
|
+
time.sleep(0.1)
|
|
165
|
+
# Still alive — SIGKILL.
|
|
166
|
+
try:
|
|
167
|
+
os.kill(pid, signal.SIGKILL)
|
|
168
|
+
os.waitpid(pid, 0)
|
|
169
|
+
except ProcessLookupError:
|
|
170
|
+
pass
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
if __name__ == "__main__":
|
|
174
|
+
sys.exit(main())
|