pi-crew 0.11.5 → 0.11.6

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.md +1 -1
  3. package/dist/index.mjs +493 -488
  4. package/docs/dynamic-workflows.md +37 -0
  5. package/package.json +12 -8
  6. package/scripts/analyze-run.mjs +95 -11
  7. package/skills/model-routing-context/SKILL.md +4 -3
  8. package/skills/real-test-pi-crew/SKILL.md +9 -6
  9. package/src/config/env-vars.ts +6 -0
  10. package/src/extension/crew-vibes/index.ts +29 -10
  11. package/src/extension/pi-api.ts +1 -0
  12. package/src/extension/registration/lifecycle-handlers.ts +65 -3
  13. package/src/extension/team-tool/respond.ts +54 -0
  14. package/src/extension/team-tool/status.ts +10 -0
  15. package/src/extension/team-tool.ts +393 -3
  16. package/src/extension/webhook-notify.ts +23 -1
  17. package/src/observability/exporters/otlp-exporter.ts +93 -11
  18. package/src/prompt/prompt-runtime.ts +13 -4
  19. package/src/runtime/agent-control.ts +1 -15
  20. package/src/runtime/background-runner.ts +3 -0
  21. package/src/runtime/broker/crew-broker.ts +12 -103
  22. package/src/runtime/broker/protocol/request-parsers.ts +9 -1
  23. package/src/runtime/broker/protocol/wait-resolve.ts +168 -0
  24. package/src/runtime/child-pi/child-pi-spawn.ts +5 -4
  25. package/src/runtime/dispatch-batch.ts +3 -1
  26. package/src/runtime/goal-workflow/dynamic-workflow-context.ts +111 -12
  27. package/src/runtime/goal-workflow/dynamic-workflow-runner.ts +19 -0
  28. package/src/runtime/live-session/live-agent-manager.ts +35 -0
  29. package/src/runtime/live-session/live-session-runtime.ts +34 -5
  30. package/src/runtime/mcp-proxy.ts +110 -7
  31. package/src/runtime/model/pi-args.ts +14 -11
  32. package/src/runtime/model/provider-extensions.ts +67 -1
  33. package/src/runtime/scheduling/run-coalesced-task-group.ts +89 -6
  34. package/src/runtime/stale-reconciler.ts +47 -0
  35. package/src/runtime/task-runner/child-executor.ts +130 -4
  36. package/src/runtime/task-runner/output-splitter.ts +48 -5
  37. package/src/runtime/task-runner/post-execution.ts +0 -2
  38. package/src/runtime/task-runner/pre-execution.ts +8 -3
  39. package/src/runtime/task-runner/retrieval-orchestrator.ts +28 -2
  40. package/src/schema/team-tool-schema.ts +25 -0
  41. package/src/state/contracts.ts +16 -0
  42. package/src/state/crew-init.ts +114 -0
  43. package/src/state/stores/active-run-registry.ts +13 -6
  44. package/src/state/types.ts +0 -8
  45. package/src/ui/goal-flag.ts +44 -0
  46. package/src/ui/live-run-sidebar.ts +5 -1
  47. package/src/ui/pi-ui-compat.ts +20 -3
  48. package/src/ui/run-dashboard.ts +2 -1
  49. package/src/ui/widget/widget-model.ts +6 -2
  50. package/src/utils/ip-literal.ts +145 -0
  51. package/src/workflows/workflow-config.ts +6 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,75 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.11.6] — SDD-3/4 upgrade waves, W-E/W-I, small-wins, Pi 1.0.0 compat, GH #61 fix (2026-10-03)
4
+
5
+ ### fix(extensions): GH-061 — normalize directory `pi.extensions` entries to entry-file identity (child workers double-load)
6
+
7
+ - Packages that declare a DIRECTORY extension entry (e.g. pi-web-access 0.35.0 `"pi.extensions": ["./dist"]`) killed every child worker at startup: pi's package auto-load resolves the directory to its entry file (`dist/index.js`) while pi-crew's provider-extension discovery injected the raw directory via `--extension …/dist` — jiti loads the directory fine, so the same extension registered twice under two different path identities and pi's `detectExtensionConflicts` (which only tolerates duplicate tools when `ext.path` matches exactly) failed the session with `Tool "…" conflicts with …/dist`, exit 1 before the task ran (issue #61, pi-crew 0.11.5 + Pi 0.99.2). `resolvePackageEntry` now normalizes directory entries through new `resolveExtensionDirEntry()`, mirroring pi's own `collectAutoExtensionEntries` semantics EXACTLY: nested pi-manifest first, then `index.ts` → `index.js` in pi's order (verified identical in SDK 0.99.2 and 1.0.0), with `index.mjs` and nested `package.json` `main` as additional fallbacks pi's auto-load would not register (single injected instance there cannot conflict; `main` preserves the legacy jiti dir-loading behaviour). An unresolvable directory is SKIPPED, matching pi's own view of the package. Order matters: `mjs` before `js` would re-arm the conflict for dirs shipping both (unit-pinned). Verified three ways: 5 new unit tests pin file-identity/nested-manifest/unresolvable-skip/order/main-fallback (15/15); a scratch-agent-dir A/B E2E reproduces the exact issue-#61 failure with the old directory identity (`Tool "gh61_probe" conflicts with …/dist`, exit 1) and loads exactly once with the normalized file identity (exit 0); full suite green. Also learned (and recorded): child workers receive env through an allowlist (`child-pi-spawn.ts`), so `PI_CODING_AGENT_DIR`-style env redirects never reach the child — the repro simulates the child side directly.
8
+
9
+ ### fix(security): W-I G6 — bracket IPv6 literals + block IPv4-compatible loopback in the OTLP SSRF guard
10
+
11
+ - The bracketed IPv4-compatible IPv6 form `[::a.b.c.d]` slipped through BOTH SSRF guard layers in `otlp-exporter.ts`: the URL-literal check did not bracket-normalize IPv6 hosts before comparing, and the resolved-address check treated `::127.0.0.1`-style IPv4-compatible loopbacks as non-loopback. Both layers now normalize IPv6 literals to bracketed form (RFC 3986 host ABNF) and reject IPv4-compatible loopback ranges the same way plain loopback is rejected; pi-crew never builds `--provider`-only URLs (the new pi 1.0.0 hard-fail for that combination is a no-op here). Pinned by `test/unit/ip-literal.test.ts` (96 lines) + expanded `otlp-ssrf.test.ts`.
12
+
13
+ ### fix(runtime): W-I G20 — output-splitter partial delimiter-hit is now fail-closed (no cross-task broadcast)
14
+
15
+ - When a worker's raw output contained a PARTIAL set of the coordination delimiters (some but not all sections parseable), the section parser was skipped ENTIRELY (`output-splitter.ts:74-81` — parse only ran when `delimiterHits.size===0`) and the FULL raw output — including other tasks' sections — was broadcast to EVERY task. Partial hits now route to the deadletter path (fail-closed): no regular task receives another task's content; the deliberate firstTask deadletter fallback (US-003) is preserved as documented at `:202-207`.
16
+
17
+ ### feat(runtime): W-I G5 — `respond_delivered`/`respond_missed` delivery events with liveness discrimination
18
+
19
+ - A leader `respond` to a parked waiting task had no delivery receipt: if the worker died between park and ack, the answer silently fell into the void and the leader had to inspect run state to notice. The broker's `wait.resolve` handler now emits `task.respond_delivered` (with `questionId` + the worker pid, heartbeat-first/checkpoint-fallback — the same liveness source the stale-reconciler uses) ONLY on the worker-claimed `answered` outcome — every other terminal path (timed-out/aborted/legacy no-outcome) fails closed with no event; the root-side `task.respond_missed` twin fires at write time when the target worker is not live (extension/team-tool/respond.ts). Both types registered in the event-types registry (enforce clean).
20
+
21
+ ### feat(status): W-E G19 — drop dead state fields, surface `goalAchieved` in status/UI
22
+
23
+ - `controlReservation` and `workerExitStatus` were write-only state (1/2 writes, 0 readers — verified including bracket-access; `releaseControlChannel` had 0 hits repo-wide): dropped from `src/state/types.ts` and every writer. `goalAchieved` (written by goal-achievement assessment, previously 0 code readers) is now wired through `ui/goal-flag.ts` into `/team-status`, the live-run sidebar, run dashboard and dock widget, with three new suites pinning the label logic, the dead-fields removal, and status rendering (`undefined` renders nothing for runs predating the assessment). Ships with `docs/state-durability-policy.md` (199 lines): all 39 sync `appendEvent` call-sites classified keep-sync (13 mandatory crash-recovery/terminal + 26 documented read-back contracts — the M2a/M2b conversion attempt of 2026-09-10 was already tried and reverted, and those revert comments are the per-site do-not-convert evidence), exit-hook flush (`event-log.ts` SIGTERM→`setImmediate` flush) documented, SIGKILL ≤20ms/SIGHUP/second-signal residual risks accepted explicitly. The plan's B1 sweep is therefore a documented no-op, not skipped work.
24
+
25
+ ### fix(runtime): SDD-4 W-D — heartbeat liveness: hb.pid, foreign-LIVE skip, honest completion notify, liveness pulse
26
+
27
+ - (1) NEW-3: heartbeat state now carries `pid`, and `terminateLiveAgentsForRun` kills by pid (previously name-matched `pgrep` could miss or over-match). (2) G24: stale-reconciler skips LIVE-marked agents owned by a DIFFERENT session (foreign-LIVE) instead of reaping them. (3) NEW-2: completion notify is honest and deduped — a task whose runner died after finishing its work but before the final ack reports per-attempt truth instead of a blanket failure. (4) G25/NEW-4: child-executor heartbeat gets a liveness PULSE — alive-but-silent turns no longer freeze the heartbeat clock (the `PI_CREW_HEARTBEAT_PULSE_MS` env this release also registers in the snapshot test that SDD-4 initially missed). Live-proven in the SDD-4 battery (tmux probes, honest-notify scenario) plus the async-resume stale-pointer fix (W-C2) and crew-vibes stale-context fix.
28
+
29
+ ### fix(runtime): SDD-3 W-C — resume-force semantics, runKind preservation, agent-call caps
30
+
31
+ - (1) G12: `resume` with `force:true` on a LIVE run is refused with the exact semantics "force bypasses OWNERSHIP checks only — it can never bypass this liveness check" (live-proven). (2) G11: `resume` preserves the original `runKind` (async runs stay async across resume — sync proven in battery). (3) G13: `maxAgentCalls` is enforced per dynamic-workflow run (structured abort reason when tripped). (4) G14: `estimateTokens` accounting for budget gates. All pinned by the SDD-3 battery.
32
+
33
+ ### fix(retrieval): G23b — suggested-files min-score floor (keyword-grazer junk out of worker prompts)
34
+
35
+ - The byPath retrieval pass only floored `score>0`, so generic path-keyword grazers sailed through and live evidence showed ~10 junk files suggested to workers daily. New `MIN_SUGGEST_SCORE = 0.5` in `retrieval-orchestrator.ts` (path-only matches keep the legacy floor — genuinely relevant path-only scores can be low); 115 lines of new table-driven tests. Plus G22 (typeof-guards for `setWidget`/`showCustom`/`setStatusFallback` on older host runtimes), G21 (slash-command parity enumeration completed; README honest command count), G23a (model-routing-context SKILL.md aligned with the actual compaction code), G23c (empty legacy `.pi/teams` skeleton swept on crew-init).
36
+
37
+ ### fix(perf): analyze-run max-decimation (spike aliasing)
38
+
39
+ - `scripts/analyze-run.mjs` downsampled long series by striding indices evenly (`i += step`), which can drop a spike PEAK entirely when it lands between kept indices — the perf report would show a flat line where a latency spike lived (found by adversarial cold-verify of the 2026-10-01 deep review). Each bucket now keeps its MAX point (10 new tests pin the shape, including the odd/even-index spike case).
40
+
41
+ ### test: G8 — broker/protocol table-driven invariant suites
42
+
43
+ - Table-driven coverage for the broker/protocol parsers previously pinned only implicitly: protocol parsers, wait/mailbox paths, events replay, manifest loader, merge-gate field/batch guards, policy-engine table, delegate-usage token helpers (commits `d4c74861` + `6a8bf1fe`). Test-only — zero source changes.
44
+
45
+ ### feat(deps): bump `@earendil-works/*` devDeps `^0.99.1` → `^1.0.0` (Pi 1.0.0 compatibility)
46
+
47
+ - Pi 1.0.0 (2026-10-01) compatibility audited before bumping (`docs/pi-1.0.0-compat-research-2026-10-02.md`, 233 lines): the extension API surface pi-crew couples to is byte-identical between 0.99.2 and 1.0.0 (extension/resource-loader/sdk d.ts zero-diff; only `QuietStartup` added), the feared breakages are no-ops for pi-crew (no `typeof tools` probing, no `--provider`-only URL building, exports map identical, G2 `DefaultResourceLoader` path intact, all 19 `pi.on` events unchanged), and pi-tui 1.0.0 d.ts is 0-diff against 0.99.2 for all 12 imported symbols (the HIGH open gap, closed by direct tarball diff). Bumped all four devDeps in lockstep (pi-coding-agent 1.0.0 depends on `^1.0.0` siblings — a split bump would dual-version), peerDependencies stay `"*"`. Gates green on SDK 1.0.0 (typecheck doubles as the member-level interface check), bundle bytes UNCHANGED (`a0edaed9…` — the SDK is bundle-external, the bump only changes the typecheck/test environment), smoke run clean on a pi-1.0.0 host, and the T5/T10/T13 battery re-run green in BOTH tuiModes (fullscreen default + `--tui-mode regular`) with the UI catalog regenerated on pi-tui 1.0.0 (glyph self-check 18/18; the only capture diffs were the new `X cancel` keybinding catching up, not rendering regressions).
48
+
49
+ ### refactor(broker): extract `wait.resolve` handler to `protocol/wait-resolve.ts` (wc-gate M4 §5)
50
+
51
+ - The G5 delivery-ack commit pushed `crew-broker.ts` to 2032 lines, over the 2000-line `src/runtime/` module gate (1999 before). The whole `wait.resolve` handler moved out as a pure extraction (3-member deps surface, same pattern as `msg-inbox.ts`): crew-broker.ts is 1909 lines, behavior unchanged (23/23 wait-request suite green; the standalone-run exit-hang of that suite is a pre-existing teardown quirk, verified identical on pre-refactor code).
52
+
53
+ ### fix(security): review remediation — hardened mcp-proxy defaults + G2 fail-open visibility
54
+
55
+ - Remediation round for the SDD-2 W-B review pair (reviewer MAJOR-1 + security-reviewer MINORs). (1) The two `mcp-proxy` config helpers had no production call sites but kept the pre-hardening permissive default: an omitted `shareMcp` fell through to the enable paths (`enableMcp:true`), re-arming the exact G2 gap for any future caller that forgets the flag (and the old test asserted that permissive default, hard-coding it). `buildMcpProxyConfig`/`buildMcpProxyFromSession` now require EXPLICIT `shareMcp:true` — `false` OR `undefined` denies (test-first: hardened-default assertions red on HEAD → 15/15 green). (2) The live-session G2 strip silently degraded to fail-open when the host SDK stopped exporting `DefaultResourceLoader` or the agent dir was unresolvable (version drift) — a non-permitted role would quietly re-inherit every parent MCP server with no diagnostic. New pure helper `g2EnforcementDegradationReason()` + `task.mcp_enforcement_degraded` warning event (registered in `TEAM_EVENT_TYPES`) emitted by the live-session runtime whenever the strip could not be armed for a non-permitted role. (3) `isMcpExtensionPath`'s name-based-denylist limitation (a differently-named third-party MCP adapter escapes the strip) is now documented on the function; allowlist-shaped filtering stays a follow-up work item. Also fixed the biome indentation slip in `phase6-runtime-hardening.test.ts` that made `format:check` fail on HEAD (reviewer MAJOR-1). npm audit recorded in the SDD-2 execution record: 2 high transitive advisories (`brace-expansion` under the pi SDK, `fast-uri` toolchain) — pre-existing, not introduced by W-B, left for a separate decision.
56
+
57
+ ### fix(security): G3 — always spill task text to a 0600 temp file (argv never carries it)
58
+
59
+ - `buildPiWorkerArgs` spilled task text to a `task.md` (mode 0600, `@<path>` inclusion arg) ONLY when it exceeded 8000 chars; shorter tasks rode argv as a `Task: ...` positional — and argv is world-readable via `/proc/<pid>/cmdline`, leaking full task prompts (goals, file paths, anything embedded) to every local user/process scanner (upgrade-plan G3, severity MED). The short-argv branch and the `TASK_ARG_LIMIT` constant are removed: EVERY task, regardless of length, now goes through the same 0600 `task.md` spill (existing temp-dir/cleanup machinery unchanged — `cleanupTempDir`/surface-settle/orphan sweeps all apply). Behavior note: short-task child prompts lose the cosmetic `Task: ` prefix, matching the raw-text contract the long path always had. Test-first: 4 red assertions on HEAD (flipped `child-pi-timeout` argv test, flipped `child-pi-surface` script-content assertion, 3 new spill cases in `phase6-runtime-hardening` covering tiny/empty/just-under-limit tasks asserting argv has no task text + file content + 0600 mode) → 33/33 green; wider pi-args/child-pi/surface suites 175/175.
60
+
61
+ ### fix(security): G2 — invert mcp-proxy `shareMcp` semantics and gate live-session MCP by role (least-privilege)
62
+
63
+ - `buildMcpProxyConfig({ shareMcp:false })` returned `enableMcp:true` — a flag inversion enshrined by the old tests — and the only production call site hardcoded `shareMcp:true`, so EVERY live-session worker self-discovered ALL of the parent's MCP servers (with credentials), read-only roles included: an active least-privilege gap (upgrade-plan G2, severity MED). Now `shareMcp:false` returns `enableMcp:false` (flipped test-first: 2 red assertions on HEAD → 13/13 green). The call site is rewired to a role-permission signal: new `mcpPermittedForRole(role)` denies MCP for read-only roles (explorer/reviewer/security-reviewer/analyst/critic/planner) and — matching FIND-12 default-deny — for unknown/undefined roles; write-capable roles keep sharing. Because pi ≥0.99 ships MCP as a built-in EXTENSION (`builtin:mcp`) and the pre-0.99 `enableMCP:false` `createAgentSession` option no longer exists (verified: zero `enableMCP` matches in the 0.99.2 SDK), the dead `enableMCP:false` spread and the no-op `buildMcpProxyFromSession([], { shareMcp:true })` scaffolding in `live-session-runtime.ts` were REMOVED and enforcement moved to the supported hook: the live-session `DefaultResourceLoader` now passes `extensionsOverride: stripMcpExtensions` for non-permitted roles, dropping `builtin:mcp` and any `pi-mcp-adapter` replacer from the child's extension list (no MCP code load, no server connections). Known limitation: enforcement requires `DefaultResourceLoader` to be exported by the host SDK (true on 0.99.x); without a loader the child falls back to pi defaults.
64
+
65
+ ### fix(package): GH-060 — move `@sinclair/typebox` to peerDependencies (host warning)
66
+
67
+ - pi ≥0.99's extension host treats `@sinclair/typebox`/`typebox` as host-provided packages and warns when an extension declares them in `dependencies` ("Installed copies can bypass the extension loader and create duplicate runtime modules", issue #60). Mirror the existing `@earendil-works/pi-*` peer pattern: removed from `dependencies`, added as `"@sinclair/typebox": "*"` in `peerDependencies` + `peerDependenciesMeta.optional: true`, kept `^0.34.52` in `devDependencies` for typecheck/dev strip-types. Runtime is unaffected by construction: the shipped entry `dist/index.mjs` has typebox VENDORED since v0.9.58 (`30a82b33`) — zero runtime specifier resolution in the default path. Known edge (pre-existing, unchanged): the strip-types fallback (`PI_CREW_USE_BUNDLE=0`) relies on specifier resolution, where the host alias map covers `@sinclair/typebox`, `/compile`, `/value` but NOT `/compiler` (used by `src/extension/registration/team-tool.ts` for `TypeCompiler`), and the host's unscoped `typebox` 1.3.27 API lacks `TypeCompiler`/`TypeRegistry` — strip-types against current host typebox was already unstable regardless of this change; dev checkouts keep working via devDependencies.
68
+
69
+ ### feat(perf): GH-059 — `--ascii` mode for analyze-run perf reports (glyph-free)
70
+
71
+ - `scripts/analyze-run.mjs` gains an opt-in `--ascii` flag (or `PI_CREW_ASCII=1` env) that renders perf reports free of emoji/dingbats for vault-lint-strict Markdown consumers (issue #59). Post-process sweep at the two md write sites covers BOTH outputs — `docs/perf/perf-report-<runId>.md` and the per-agent drill-downs `bench/results/<runId>.agents/<taskId>.md` (the latter missed by the issue's own repro). Substitutions: composite sevLabels → `CRITICAL/ERROR/BLOCKED/RETRY/WARN`, duration-anchored slow markers → `[SLOW]` (timeline cells, bottleneck table, legend, header), `⚠️` → `[WARN]`, severity circles → `(!)/(~)/(i)`, `✅/❌/✓` → `[OK]/[FAIL]/[ok]`, decorative heading emoji dropped. VS16-sequence-aware (`⚠️`/`⏱️` matched as base+U+FE0F, never bare codepoint) plus a variation-selector/ZWJ safety net — leftover U+FE0F bytes are themselves inside the consumer lint ranges and would still fail their gate. Default output unchanged (regression-locked by test). JSON/CSV outputs were already glyph-free and are untouched. Spec: `docs/specs/GH-059-ascii-flag.md`; pinned by 4 new cases in `test/unit/scripts/analyze-run-audit.test.ts` (ascii-clean both outputs, per-agent model-attempt markers, default-keeps-emoji, env-gate).
72
+
3
73
  ## [0.11.5] — Phase A + Buổi 1 quick wins: G1 fence, NEW-1 data-loss, G18 breakdown, Node-24 matrix (2026-09-30)
4
74
 
5
75
  ### fix(security): G1 — sanitize the dependency-output fence body (mirror ADR-5 seams)
package/README.md CHANGED
@@ -89,7 +89,7 @@ Or via tool calls (all examples verified against the action schema):
89
89
  ```
90
90
 
91
91
  `action: "recommend"` picks a team/workflow when you're unsure which fits.
92
- Slash commands (`/team-status`, `/team-dashboard`, `/team-config`, …) cover ops
92
+ 42 slash commands (`/team-status`, `/team-dashboard`, `/team-config`, …) cover ops
93
93
  and debugging — [full list](docs/commands-reference.md).
94
94
 
95
95
  ## Built-in teams