pi-crew 0.11.24 → 0.11.26
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 +72 -0
- package/README.md +2 -2
- package/agents/designer.md +23 -0
- package/agents/orchestrator.md +17 -0
- package/agents/verifier.md +1 -1
- package/dist/index.mjs +568 -559
- package/{workflows/chain.workflow.md → docs/chain-workflow.md} +8 -0
- package/docs/dynamic-workflows.md +23 -0
- package/docs/publishing.md +34 -5
- package/docs/troubleshooting.md +50 -2
- package/docs/usage.md +7 -2
- package/install.mjs +2 -1
- package/package.json +4 -2
- package/skills/read-only-explorer/SKILL.md +15 -5
- package/skills/real-test-pi-crew/SKILL.md +7 -7
- package/src/config/config-validation.ts +58 -0
- package/src/config/config.ts +90 -5
- package/src/config/env-vars.ts +14 -7
- package/src/config/role-tools.ts +7 -5
- package/src/config/types.ts +4 -1
- package/src/extension/crew-cleanup.ts +6 -0
- package/src/extension/project-init.ts +19 -1
- package/src/extension/register.ts +68 -30
- package/src/extension/registration/artifact-cleanup.ts +52 -11
- package/src/extension/registration/commands/manage.ts +9 -2
- package/src/extension/registration/hook-registration.ts +3 -0
- package/src/extension/registration/lazy-configurers.ts +2 -0
- package/src/extension/registration/lifecycle-handlers.ts +79 -18
- package/src/extension/registration/render-loop.ts +27 -2
- package/src/extension/registration/tool-loop-guard.ts +5 -3
- package/src/extension/registration/verify-gate-tool.ts +7 -3
- package/src/extension/team-tool/api/read.ts +12 -3
- package/src/extension/team-tool/cancel.ts +11 -0
- package/src/extension/team-tool/config-patch.ts +56 -11
- package/src/extension/team-tool/destructive-gate.ts +72 -12
- package/src/extension/team-tool/dispatch/manage.ts +9 -1
- package/src/extension/team-tool/handle-schedule.ts +38 -4
- package/src/extension/team-tool/handle-settings.ts +76 -6
- package/src/extension/team-tool/health-monitor.ts +14 -2
- package/src/extension/team-tool/lifecycle-actions.ts +28 -10
- package/src/extension/team-tool/respond.ts +12 -5
- package/src/extension/team-tool/run-deadline.ts +29 -2
- package/src/extension/team-tool/run.ts +11 -0
- package/src/extension/team-tool/status.ts +64 -8
- package/src/extension/team-tool/workflow-manage.ts +5 -1
- package/src/extension/team-tool.ts +3 -0
- package/src/i18n.ts +18 -14
- package/src/prompt/inbox-poll.ts +14 -2
- package/src/prompt/message-tool.ts +1 -1
- package/src/prompt/prompt-runtime.ts +25 -3
- package/src/prompt/scratchpad-lifecycle.ts +14 -13
- package/src/prompt/surface-worker.ts +35 -9
- package/src/runtime/broker/crew-broker.ts +113 -111
- package/src/runtime/broker/delegate/delegate-abort.ts +118 -0
- package/src/runtime/broker/delegate/delegate-event.ts +3 -1
- package/src/runtime/broker/delegate/delegate-finalize.ts +179 -0
- package/src/runtime/broker/protocol/wait-resolve.ts +71 -5
- package/src/runtime/budget-enforcement.ts +26 -6
- package/src/runtime/chain-parser.ts +5 -3
- package/src/runtime/chain-runner.ts +41 -22
- package/src/runtime/child-pi/mock-fixtures.ts +12 -5
- package/src/runtime/compaction/tool-output-pruner.ts +8 -3
- package/src/runtime/delegate-spawn.ts +61 -8
- package/src/runtime/dispatch-batch.ts +10 -4
- package/src/runtime/effectiveness.ts +31 -3
- package/src/runtime/goal-workflow/plan-templates.ts +2 -2
- package/src/runtime/live-session/live-agent-control.ts +89 -6
- package/src/runtime/live-session/live-agent-manager.ts +94 -10
- package/src/runtime/live-session/live-session-runtime.ts +9 -2
- package/src/runtime/model/model-fallback.ts +6 -9
- package/src/runtime/model/pi-args.ts +3 -3
- package/src/runtime/model/runtime-policy.ts +4 -2
- package/src/runtime/model/runtime-resolver.ts +6 -2
- package/src/runtime/process/orphan-reaper.ts +143 -0
- package/src/runtime/process-status.ts +38 -16
- package/src/runtime/resilient-edit.ts +170 -93
- package/src/runtime/role-permission.ts +3 -1
- package/src/runtime/run-tracker.ts +9 -1
- package/src/runtime/sanitize/worker-text.ts +98 -0
- package/src/runtime/scheduling/run-coalesced-task-group.ts +82 -28
- package/src/runtime/spawn-policy.ts +190 -8
- package/src/runtime/stale-reconciler.ts +36 -1
- package/src/runtime/subagent-manager.ts +29 -0
- package/src/runtime/surface/herdr-provider.ts +31 -1
- package/src/runtime/task-output-context.ts +58 -11
- package/src/runtime/task-runner/post-execution.ts +150 -0
- package/src/runtime/task-runner/prompt-builder.ts +41 -8
- package/src/runtime/task-runner.ts +16 -3
- package/src/runtime/verification/classifier-pre-gate.ts +27 -6
- package/src/runtime/verification/verify-gate.ts +153 -30
- package/src/state/atomic-write.ts +73 -4
- package/src/state/contracts.ts +7 -0
- package/src/state/coordination/mailbox.ts +40 -11
- package/src/state/event-log/event-log-rotation.ts +28 -6
- package/src/state/event-log/poison-marker.ts +16 -1
- package/src/state/stores/artifact-store.ts +181 -6
- package/src/state/stores/manifest-io.ts +126 -8
- package/src/state/stores/state-store.ts +30 -31
- package/src/state/types.ts +13 -0
- package/src/state/usage.ts +41 -0
- package/src/ui/run-dashboard.ts +3 -3
- package/src/ui/settings-overlay.ts +19 -1
- package/src/utils/internal-error.ts +5 -1
- package/src/utils/timer-limits.ts +30 -0
- package/src/utils/token-counter.ts +47 -0
- package/src/workflows/discover-workflows.ts +9 -1
- package/src/worktree/worktree-manager.ts +128 -18
- 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/workflows/strict-fast-fix.workflow.md +1 -1
|
@@ -6,6 +6,14 @@ topology: dynamic
|
|
|
6
6
|
|
|
7
7
|
# Chain Workflow - Sequential execution with context passing
|
|
8
8
|
|
|
9
|
+
> **This file is DOCUMENTATION, not a runnable workflow (R28-M1 / wave-6 M3-24).**
|
|
10
|
+
> It lives in `docs/` because the framework discovers every
|
|
11
|
+
> `workflows/*.workflow.md` as a workflow resource; as a doc it parsed to 10
|
|
12
|
+
> phantom steps (its prose headings) and the advertised `chain` workflow could
|
|
13
|
+
> never run. Chain execution is implemented by the chain runner
|
|
14
|
+
> (`src/runtime/chain-runner.ts` + `src/extension/team-tool/chain-dispatch.ts`);
|
|
15
|
+
> there is no `chain` workflow resource.
|
|
16
|
+
|
|
9
17
|
**Source:** pi-boomerang integration plan
|
|
10
18
|
**Syntax:** `step1 -> step2 -> step3`
|
|
11
19
|
**Version:** 1.0.0
|
|
@@ -275,6 +275,29 @@ script can reach `process`/`require` directly or via constructor walking. The
|
|
|
275
275
|
(e.g. `require('child'+'_process')`, `globalThis.process.mainModule.require`).
|
|
276
276
|
The real boundary is commit-review + the path-allowlist, not the content check.
|
|
277
277
|
|
|
278
|
+
### Project-sourced workflows are DEFAULT-DENY (F-01)
|
|
279
|
+
|
|
280
|
+
`.dwf.ts` files that resolve to a **project** source (`.crew/workflows/`,
|
|
281
|
+
`<proj>/.pi/teams/workflows/`) are refused at run time unless you explicitly opt
|
|
282
|
+
in with the env var `PI_CREW_TRUST_PROJECT_DWF=1`
|
|
283
|
+
(`src/runtime/goal-workflow/dynamic-workflow-runner.ts:189`). The rejected run
|
|
284
|
+
emits a `dwf.trust_denied` event and throws before the script is loaded:
|
|
285
|
+
|
|
286
|
+
```text
|
|
287
|
+
Project dynamic workflow requires explicit trust. Set PI_CREW_TRUST_PROJECT_DWF=1
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
The gate compares against the literal string `"1"` — `true`, `yes`, or `on` all
|
|
291
|
+
FAIL closed. Cloning a repo and running its workflow is otherwise arbitrary code
|
|
292
|
+
execution, hence the opt-in.
|
|
293
|
+
|
|
294
|
+
**Scope of the gate (honest):** only `source === "project"` is gated. **Builtin**
|
|
295
|
+
workflows and **user-scope** workflows (`~/.pi/agent/extensions/pi-crew/workflows/`)
|
|
296
|
+
proceed with **no** trust prompt — they run as ordinary trusted local code. The
|
|
297
|
+
`PI_CREW_TRUST_PROJECT_DWF` gate is therefore not a blanket guarantee that every
|
|
298
|
+
`.dwf.ts` you execute was reviewed: it protects against *repo-supplied* authoring,
|
|
299
|
+
not against user-scope authoring.
|
|
300
|
+
|
|
278
301
|
## Determinism (round-13 P0-2)
|
|
279
302
|
|
|
280
303
|
Dynamic workflow scripts must be **deterministic** — the runner rejects
|
package/docs/publishing.md
CHANGED
|
@@ -24,8 +24,8 @@ Before publishing to npm:
|
|
|
24
24
|
npm run check
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
For a fast pre-publish smoke (
|
|
28
|
-
of the full
|
|
27
|
+
For a fast pre-publish smoke (124 broker/UI/config tests in ~15s, instead
|
|
28
|
+
of the full `npm run check` unit+integration suite, which takes minutes):
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
31
|
npm run test:critical && npm run typecheck && npm run build:bundle
|
|
@@ -94,7 +94,7 @@ lockfile drift):
|
|
|
94
94
|
- **push to `refs/heads/main`** — `check:conflict-markers` + `typecheck`,
|
|
95
95
|
then the **FULL unit suite** (`npm run test:unit`, the same gate CI
|
|
96
96
|
runs). Any failure blocks the push. This closes the 2026-10 gap where
|
|
97
|
-
local pushes ran only `test:critical` (
|
|
97
|
+
local pushes ran only `test:critical` (124 tests) while CI ran the full
|
|
98
98
|
suite — three waves shipped red that way.
|
|
99
99
|
- **push to any other ref** — fast gates only (`check:conflict-markers` +
|
|
100
100
|
`typecheck`).
|
|
@@ -128,8 +128,37 @@ The package exports:
|
|
|
128
128
|
./schema.json
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
-
Use this for editor validation of
|
|
131
|
+
Use this for editor validation of the **current** config path
|
|
132
|
+
(`src/config/config.ts:208`; the `~/.pi/agent/extensions/pi-crew/config.json`
|
|
133
|
+
path is legacy/read-only-for-migration — see `docs/usage.md`):
|
|
132
134
|
|
|
133
135
|
```text
|
|
134
|
-
~/.pi/agent/
|
|
136
|
+
~/.pi/agent/pi-crew.json
|
|
135
137
|
```
|
|
138
|
+
|
|
139
|
+
## Known tarball / consumer-side notes (R26/R29 census, 2026-10-19)
|
|
140
|
+
|
|
141
|
+
Verified with `npm pack --dry-run`. These are **declared defects**, not hidden:
|
|
142
|
+
|
|
143
|
+
- **`skills/research/scripts/__pycache__/safe_io.cpython-312.pyc` ships** in the
|
|
144
|
+
tarball (11.5 kB): the `files` entry `"skills/**"` plus the missing
|
|
145
|
+
`.npmignore` rule means compiled Python bytecode is published and it embeds
|
|
146
|
+
build-machine paths. Fix = add `__pycache__` to a `.npmignore` / tighten
|
|
147
|
+
`files`.
|
|
148
|
+
- **Dev tooling ships to consumers:** `scripts/analyze-run.mjs` (64.5 kB),
|
|
149
|
+
`scripts/resource-sampler.mjs`, `scripts/watch-agent-transcript.mjs`. Harmless
|
|
150
|
+
bulk; consider moving them behind a dev-only entry.
|
|
151
|
+
- **Consumer-side git hooks reference scripts that are NOT published:**
|
|
152
|
+
`scripts/install-hooks.mjs` (published) sets `core.hooksPath=.githooks`, and
|
|
153
|
+
`.githooks/pre-commit` runs `node scripts/check-lockfile-sync.mjs` — but
|
|
154
|
+
`files[]` ships neither `scripts/check-lockfile-sync.mjs` nor
|
|
155
|
+
`scripts/test-runner.mjs`, so an installed-from-npm consumer's hook fails.
|
|
156
|
+
- **Peer range on `pi-coding-agent` is unbounded** (`>=1.0.4`, no ceiling) while
|
|
157
|
+
the `pi-tui` peer IS bounded (`<2.0.0`); pi-crew consumes pi-tui symbols
|
|
158
|
+
re-exported through `pi-coding-agent`, so a future 2.x installs clean and fails
|
|
159
|
+
at load (R49-F2 — bounding it is prospective work).
|
|
160
|
+
- **Committed `dist/` must match the tag:** the `--committed-hash` gate reads the
|
|
161
|
+
committed blob (`dist/index.mjs` — the ONLY tracked dist artifact) from the git
|
|
162
|
+
object DB, so every commit that touches `src/` needs
|
|
163
|
+
`npm run build:bundle && git add -f dist/index.mjs` rerun and committed
|
|
164
|
+
(`dist/` is `.gitignore`d, hence `-f`).
|
package/docs/troubleshooting.md
CHANGED
|
@@ -18,8 +18,11 @@ run/process status. Start here.
|
|
|
18
18
|
**Symptom:** `team action='run'` returns a `blocked` status with a message like
|
|
19
19
|
"Child worker execution is disabled".
|
|
20
20
|
|
|
21
|
-
**Cause:** Worker execution is off.
|
|
22
|
-
|
|
21
|
+
**Cause:** Worker execution is off. When `executeWorkers` is **explicitly**
|
|
22
|
+
`false` (or `PI_CREW_EXECUTE_WORKERS=0`), pi-crew refuses to create no-op
|
|
23
|
+
scaffold subagents and returns `blocked`. Note the default is the **opposite**:
|
|
24
|
+
child-worker execution is enabled by default
|
|
25
|
+
(`src/extension/team-tool/run.ts:205`).
|
|
23
26
|
|
|
24
27
|
**Fix — pick one:**
|
|
25
28
|
- Set in your config (`~/.pi/agent/pi-crew.json`): `"executeWorkers": true`
|
|
@@ -149,6 +152,51 @@ code + a help hint inline. Common ones:
|
|
|
149
152
|
| E012 | RunStale | run reconciled as stale | see "Stale async process" above |
|
|
150
153
|
| E013 | ModelOutOfScope | requested model not in pi's `enabledModels` allowlist | pick a model listed in `enabledModels` (settings.json) or disable `reliability.scopeModels` |
|
|
151
154
|
|
|
155
|
+
## Doctor says OK but the run is dead (doctor blind spots, R48)
|
|
156
|
+
|
|
157
|
+
`team action='doctor'` is a **liveness sanity check, not a watchdog**. The
|
|
158
|
+
deep-review health/status/doctor census (round 48) found these classes are
|
|
159
|
+
**NOT** detected — status/health can print `running` for a dead host:
|
|
160
|
+
|
|
161
|
+
| Class | Why it is missed | What to do |
|
|
162
|
+
|---|---|---|
|
|
163
|
+
| Host process dead, manifest frozen `running` | orphan detection keys on updatedAt/magic-string heuristics (`src/runtime/process-status.ts`) | check the host pid yourself; `team action='cancel'` then re-run |
|
|
164
|
+
| Disk full (`ENOSPC`) | no free-space probe in doctor | `df -h` the state root / TMPDIR |
|
|
165
|
+
| Bad state-file permissions (`EACCES`) | reads may classify as unreadable, not as a doctor failure | `ls -l <crewRoot>/state/runs/*` |
|
|
166
|
+
| Orphaned worker processes (reparented to PID 1) | only reaped when a run is reconciled | `ps -ef \| grep ' pi '`, then `team action='cleanup'` |
|
|
167
|
+
| Broken `node_modules` / missing `pi` resolve | spawn succeeds, worker dies instantly | run `pi --version` in the run cwd |
|
|
168
|
+
|
|
169
|
+
Only two classes are reliably caught: stale broker socket and orphan mux pane.
|
|
170
|
+
Treat a green `doctor` as "no *known* problem", not as "the run is alive".
|
|
171
|
+
|
|
172
|
+
## Debug logging: `PI_CREW_DEBUG` / `PI_TEAMS_DEBUG` are TRUTHY, not "=1" (R45-F4)
|
|
173
|
+
|
|
174
|
+
Internal debug logging (`logInternalError(..., "debug")`, `src/utils/internal-error.ts:10`)
|
|
175
|
+
is gated on a raw truthiness check, so **any non-empty value enables it** —
|
|
176
|
+
including `PI_CREW_DEBUG=0`, `PI_TEAMS_DEBUG=false`, and `PI_TEAMS_DEBUG=off`.
|
|
177
|
+
To actually silence debug output, **unset** the variable (do not set it to `0`).
|
|
178
|
+
|
|
179
|
+
## Mux surfaces (tmux / herdr) — known sharp edges (R42)
|
|
180
|
+
|
|
181
|
+
`runtime.surface.mode: 'tmux' | 'herdr'` (default `auto`) runs tier-1 workers in
|
|
182
|
+
real mux panes. Live-probed known limits:
|
|
183
|
+
|
|
184
|
+
- **Panic characters in commands:** tmux `send-keys -l` swallows a trailing `\n`,
|
|
185
|
+
while herdr `pane.send_text` treats `\n` as Enter — a boot line containing a
|
|
186
|
+
newline would be split/executed early. pi-crew's own callers pass
|
|
187
|
+
shell-escaped single-line paths, so this is latent, not live.
|
|
188
|
+
- **Pane/tab ids are not regex-validated before `-t`:** a malformed id in a
|
|
189
|
+
manifest (`kill-pane -t "-x"`) can act on the *wrong* pane (today every path is
|
|
190
|
+
liveness-gated, so it is defence-in-depth, not an open hole).
|
|
191
|
+
- **`capture-pane -p` trims trailing spaces** (cosmetic; only feeds doctor
|
|
192
|
+
liveness).
|
|
193
|
+
- **No tmux minimum-version gate:** old tmux silently falls back to a two-strike
|
|
194
|
+
headless degrade.
|
|
195
|
+
- **herdr is an external socket daemon** — pi-crew never spawns it, and the
|
|
196
|
+
server-wide subscription (no pane filter) amplifies traffic.
|
|
197
|
+
- Pane ids are unique only within one mux server lifetime; a stale manifest can
|
|
198
|
+
name an id now owned by an unrelated pane.
|
|
199
|
+
|
|
152
200
|
## Still stuck
|
|
153
201
|
|
|
154
202
|
- `team action='explain' runId=…` — structured per-task analysis (why, files,
|
package/docs/usage.md
CHANGED
|
@@ -42,7 +42,7 @@ Supported fields:
|
|
|
42
42
|
"requirePlanApproval": false
|
|
43
43
|
},
|
|
44
44
|
"ui": {
|
|
45
|
-
"widgetPlacement": "
|
|
45
|
+
"widgetPlacement": "bottom",
|
|
46
46
|
"widgetMaxLines": 8,
|
|
47
47
|
"powerbar": true,
|
|
48
48
|
"dashboardPlacement": "right",
|
|
@@ -239,6 +239,12 @@ Dirty worktrees are preserved unless `force: true` is provided.
|
|
|
239
239
|
|
|
240
240
|
## Slash commands
|
|
241
241
|
|
|
242
|
+
> **`/team-cleanup <runId>` does not exist** (R27-D1). The registered commands
|
|
243
|
+
> are `/team-manager` (and its alias `/team-cleanup-menu`) — runId-targeted
|
|
244
|
+
> cleanup is the `cleanup` action on the `team` tool (`team action='cleanup'
|
|
245
|
+
> runId=…`). Only `team-cleanup-menu` is registered
|
|
246
|
+
> (`src/extension/registration/commands/manage.ts:99-107`).
|
|
247
|
+
|
|
242
248
|
```text
|
|
243
249
|
/teams
|
|
244
250
|
/team-run default "Implement login with tests"
|
|
@@ -251,7 +257,6 @@ Dirty worktrees are preserved unless `force: true` is provided.
|
|
|
251
257
|
/team-events team_...
|
|
252
258
|
/team-artifacts team_...
|
|
253
259
|
/team-worktrees team_...
|
|
254
|
-
/team-cleanup team_...
|
|
255
260
|
/team-forget team_... --confirm
|
|
256
261
|
/team-export team_...
|
|
257
262
|
/team-import .crew/artifacts/team_.../export/run-export.json # or .pi/teams/artifacts/... on legacy layout
|
package/install.mjs
CHANGED
|
@@ -13,7 +13,8 @@ const legacyConfigPath = path.join(agentDir, "extensions", "pi-crew", "config.js
|
|
|
13
13
|
* Default config written to `~/.pi/agent/pi-crew.json` on fresh installs.
|
|
14
14
|
*
|
|
15
15
|
* WI-1b.2 (G17): this object is intentionally an embedded literal — install.mjs
|
|
16
|
-
* runs under bare `node` as the `pi-crew` bin (engines floor is >=22.
|
|
16
|
+
* runs under bare `node` as the `pi-crew` bin (engines floor is >=22.19.0 — the
|
|
17
|
+
* pre-R29-F3 comment said >=22.0.0; .ts
|
|
17
18
|
* imports would require >=22.18 or `--experimental-strip-types`, which the npm
|
|
18
19
|
* bin shim cannot pass). Runtime jiti/esbuild loading was rejected as a
|
|
19
20
|
* postinstall failure surface; build-time codegen would need package.json /
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-crew",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.26",
|
|
4
4
|
"description": "Pi extension for coordinated AI teams, workflows, worktrees, and async task orchestration",
|
|
5
5
|
"author": "baphuongna",
|
|
6
6
|
"license": "MIT",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"docs/troubleshooting.md",
|
|
58
58
|
"docs/usage.md",
|
|
59
59
|
"docs/architecture.md",
|
|
60
|
+
"docs/chain-workflow.md",
|
|
60
61
|
"docs/dynamic-workflows.md",
|
|
61
62
|
"docs/resource-formats.md",
|
|
62
63
|
"docs/publishing.md",
|
|
@@ -116,6 +117,7 @@
|
|
|
116
117
|
"smoke:pi": "pi install .",
|
|
117
118
|
"smoke:release": "node scripts/release-smoke.mjs",
|
|
118
119
|
"prepack": "node scripts/clean-strip-types.mjs",
|
|
120
|
+
"prepublishOnly": "npm run ci",
|
|
119
121
|
"test:fast": "npm run test:critical && npm run test:bundle",
|
|
120
122
|
"check:wc-gate": "node scripts/wc-gate.mjs"
|
|
121
123
|
},
|
|
@@ -136,7 +138,7 @@
|
|
|
136
138
|
"peerDependencies": {
|
|
137
139
|
"@earendil-works/pi-agent-core": ">=1.0.4",
|
|
138
140
|
"@earendil-works/pi-ai": ">=1.0.4",
|
|
139
|
-
"@earendil-works/pi-coding-agent": ">=1.0.4",
|
|
141
|
+
"@earendil-works/pi-coding-agent": ">=1.0.4 <2.0.0",
|
|
140
142
|
"@earendil-works/pi-tui": ">=1.0.4 <2.0.0",
|
|
141
143
|
"@sinclair/typebox": "*"
|
|
142
144
|
},
|
|
@@ -17,11 +17,21 @@ triggers:
|
|
|
17
17
|
|
|
18
18
|
Use this skill for explorer, analyst, reviewer, and source-audit roles. These roles must inspect code without modifying it.
|
|
19
19
|
|
|
20
|
+
> **Tool availability (enforced — R27-D6).** `explorer` (and `librarian`) ship
|
|
21
|
+
> **without a `bash` tool**: `src/config/role-tools.ts:25-29` sets
|
|
22
|
+
> `tools: ["read", "grep", "find", "ls", "glob", "ask"]` +
|
|
23
|
+
> `excludeTools: ["edit", "write", "bash", "web"]`. The shell syntax below
|
|
24
|
+
> (`rg …`, `find …`, `git status`) is therefore **reference syntax for the
|
|
25
|
+
> intent**, not runnable in an explorer session. Map each example to the tool you
|
|
26
|
+
> actually have: `rg` → `grep`, `find`/shell globbing → `find`/`glob`,
|
|
27
|
+
> `ls` → `ls`, `cat`/`sed -n` → `read` (with offset/limit). `analyst`/`reviewer`
|
|
28
|
+
> roles keep `bash` (`role-tools.ts:33-35`, `:64-68`) — the restriction is per-role.
|
|
29
|
+
|
|
20
30
|
## Core Contract
|
|
21
31
|
|
|
22
32
|
1. **Do not edit files** — no write, no edit, no delete
|
|
23
33
|
2. **Do not write generated artifacts** outside the run artifact directory
|
|
24
|
-
3. **Prefer read-only
|
|
34
|
+
3. **Prefer read-only tools**: `read`, `grep`, `find`, `ls`, `glob` (explorer has no shell — see the note above)
|
|
25
35
|
4. **Record exact files inspected** — include path and relevant line numbers
|
|
26
36
|
5. **Distinguish direct evidence from inference** — don't guess
|
|
27
37
|
6. **If implementation is needed, recommend** — don't modify code
|
|
@@ -242,10 +252,10 @@ If ANY answer is NO → Stop. Adhere to read-only contract.
|
|
|
242
252
|
|
|
243
253
|
## Source patterns
|
|
244
254
|
|
|
245
|
-
- `src/runtime/task-runner.ts` — task execution pipeline
|
|
246
|
-
- `src/runtime/child-pi
|
|
247
|
-
- `src/runtime/live-agent-manager.ts` — live agent lifecycle
|
|
248
|
-
- `src/state/event-log
|
|
255
|
+
- `src/runtime/task-runner.ts` + `src/runtime/task-runner/` — task execution pipeline
|
|
256
|
+
- `src/runtime/child-pi/` — worker spawning
|
|
257
|
+
- `src/runtime/live-session/live-agent-manager.ts` — live agent lifecycle
|
|
258
|
+
- `src/state/event-log/` — event logging system
|
|
249
259
|
- `src/extension/team-tool/` — API and tool handling
|
|
250
260
|
- `src/ui/` — widget and TUI rendering
|
|
251
261
|
|
|
@@ -275,7 +275,7 @@ To add Tier 1 to CI as a fast-feedback gate (under 30s):
|
|
|
275
275
|
|
|
276
276
|
---
|
|
277
277
|
|
|
278
|
-
## Tier 1 — Critical unit tests (~
|
|
278
|
+
## Tier 1 — Critical unit tests (~13s, 124 tests, the only suite you need for broker/UI changes)
|
|
279
279
|
|
|
280
280
|
**What**: run the curated 15-file fast subset.
|
|
281
281
|
|
|
@@ -287,7 +287,7 @@ To add Tier 1 to CI as a fast-feedback gate (under 30s):
|
|
|
287
287
|
time npm run test:critical
|
|
288
288
|
```
|
|
289
289
|
|
|
290
|
-
Expected output: `# tests
|
|
290
|
+
Expected output: `# tests 124 # pass 124 # fail 0 # cancelled 0 # duration_ms ~12800`. (Count was 97 at v0.9.46, 101 at v0.9.66, 102 at the waitMethodsEnabled flip, 116 since 2026-09-20, 120 @ 2026-10-07, **124 @ 2026-10-18** (+`child-executor-heartbeat-pulse.test.ts` → 15 files) — verify with the actual run; the skill's hard-coded numbers drift between releases.) **Since RR-021 round-2 the runner itself fails loudly on `# cancelled N>0`** (dual-reporter TAP scrape — the round-1 unref incident shipped green with 2 cancelled tests), and **round-3 added the missing-file pre-check**: an explicitly-listed file that does not exist now exits 1 naming the path instead of being silently skipped by node:test.
|
|
291
291
|
|
|
292
292
|
**References**:
|
|
293
293
|
|
|
@@ -327,7 +327,7 @@ PI_CREW_BROKER=0 npm run test:critical
|
|
|
327
327
|
PI_CREW_BROKER=1 npm run test:critical
|
|
328
328
|
```
|
|
329
329
|
|
|
330
|
-
All three must show `# pass
|
|
330
|
+
All three must show `# pass 124 # fail 0`. Measured times in this session (2026-08-11): ~26s for default, ~26s for `PI_CREW_BROKER=0`, ~26s for `PI_CREW_BROKER=1` (varies ±1-2s run-to-run).
|
|
331
331
|
|
|
332
332
|
**References**:
|
|
333
333
|
|
|
@@ -609,7 +609,7 @@ If the two md5s match → session is on the latest code. If not → user must `/
|
|
|
609
609
|
|
|
610
610
|
**What**: drive the team tool + subagent tools through a spread of actions from the parent Pi session to prove the full surface works end-to-end, not just one smoke run.
|
|
611
611
|
|
|
612
|
-
**Why this exists**: Tier 7 proves one team run completes. But pi-crew has **
|
|
612
|
+
**Why this exists**: Tier 7 proves one team run completes. But pi-crew has **57 `team` actions across 5 domain dispatchers** (`RUN` 10: run/parallel/plan/plans/orchestrate/resume/retry/wait/steer/goal · `STATUS` 17: status/list/get/events/artifacts/breakdown/summary/graph/search/health/worktrees/checkpoint/cache/explain/onboard/recommend/help · `CONTROL` 7 · `MANAGE` 17 · `AUTOMATE` 6 — `src/schema/team-tool-schema.ts:437-489`) plus the subagent tools (`Agent`, `crew_agent`, `get_subagent_result`, `steer_subagent` — with `crew_agent_result`/`crew_agent_steer` aliases), the worker-side tools (`ask`, `delegate`, `message`), and the `team-settings` config surface, dispatched through several code paths (sync run, async run, chain, parallel, direct subagent). A schema or registration regression can break *some* paths while others still pass. The battery catches path-specific breakage.
|
|
613
613
|
|
|
614
614
|
**When required**: any change to `src/schema/team-tool-schema.ts`, `src/extension/registration/team-tool.ts`, `src/extension/team-tool/*.ts` (handler dispatch), or the subagent-tool registration. Optional but cheap for any change — the read-only actions are free.
|
|
615
615
|
|
|
@@ -1383,7 +1383,7 @@ Use this to answer "đủ full tính năng chưa?" without re-deriving. Every us
|
|
|
1383
1383
|
|
|
1384
1384
|
| Feature | Code entry | Verify via |
|
|
1385
1385
|
|---|---|---|
|
|
1386
|
-
| Team tool —
|
|
1386
|
+
| Team tool — 57 actions / 5 domains | `src/schema/team-tool-schema.ts:437-489`, dispatch in `src/extension/team-tool/` | 9a (read-only) + 9b/9c/9d/9e/9f theo domain |
|
|
1387
1387
|
| Runtime mode `child-process` (default) | `src/runtime/child-pi/` | 9b sync run + T7 |
|
|
1388
1388
|
| Runtime mode `scaffold` (dry-run) | `src/runtime/task-runner/pre-execution.ts:176` | 9b `action='plan'`/`'plans'` (preview không spawn) hoặc run với `runtime.mode='scaffold'` |
|
|
1389
1389
|
| Runtime mode `live-session` (experimental) | `src/runtime/live-session/` | Run với `runtime.mode='live-session'` + irc tool xuất hiện trong worker (`src/runtime/custom-tools/irc-tool.ts`) |
|
|
@@ -1464,7 +1464,7 @@ The skill does NOT need to be updated for every commit — only when the cited l
|
|
|
1464
1464
|
## Quick reference — exact commands
|
|
1465
1465
|
|
|
1466
1466
|
```bash
|
|
1467
|
-
# Tier 1 (critical unit, ~
|
|
1467
|
+
# Tier 1 (critical unit, ~13s, 124 tests)
|
|
1468
1468
|
npm run test:critical
|
|
1469
1469
|
# Tier 2 (3-path proof, broker changes only)
|
|
1470
1470
|
PI_CREW_BROKER=0 npm run test:critical
|
|
@@ -1548,7 +1548,7 @@ PY
|
|
|
1548
1548
|
|
|
1549
1549
|
Before claiming "tested":
|
|
1550
1550
|
|
|
1551
|
-
- [ ] Tier 1: `test:critical` fresh-run, all pass (<25s). Count varies by release — was 97 at v0.9.46, 101 at v0.9.66, 102 at the waitMethodsEnabled flip, 116 @ 2026-09-20,
|
|
1551
|
+
- [ ] Tier 1: `test:critical` fresh-run, all pass (<25s). Count varies by release — was 97 at v0.9.46, 101 at v0.9.66, 102 at the waitMethodsEnabled flip, 116 @ 2026-09-20, 120 @ 2026-10-07, **124 @ 2026-10-18**; record the actual count in the report.
|
|
1552
1552
|
- [ ] Tier 2: 3-path proof all pass — **required if you touched `src/config/defaults.ts` or `src/extension/registration/lifecycle-handlers.ts`**
|
|
1553
1553
|
- [ ] Tier 3: `npm run typecheck` exit 0, `npm run build:bundle` exit 0
|
|
1554
1554
|
- [ ] Tier 4: bundle md5 matches what the session loaded (or user has `/quit`-ed + reopened)
|
|
@@ -110,6 +110,64 @@ export const DANGEROUS_OBJECT_KEYS = new Set([
|
|
|
110
110
|
"__lookupSetter__",
|
|
111
111
|
]);
|
|
112
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Case-normalized view of DANGEROUS_OBJECT_KEYS. The canonical set keeps the
|
|
115
|
+
* real member spellings (toString, valueOf, …), but key matching must be
|
|
116
|
+
* lowercase on BOTH sides — `"valueOf".toLowerCase()` is `"valueof"`, which
|
|
117
|
+
* would never match a camelCase entry.
|
|
118
|
+
*/
|
|
119
|
+
const DANGEROUS_KEYS_LOWER = new Set([...DANGEROUS_OBJECT_KEYS].map((key) => key.toLowerCase()));
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* True when `key` collides with a reserved Object.prototype member.
|
|
123
|
+
* Case-insensitive to catch `__Proto__`, `CONSTRUCTOR`, `TOSTRING`, etc.
|
|
124
|
+
*/
|
|
125
|
+
export function isDangerousKey(key: string): boolean {
|
|
126
|
+
return DANGEROUS_KEYS_LOWER.has(key.toLowerCase());
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* True when ANY dot-separated segment of a dotted config path collides with a
|
|
131
|
+
* reserved Object.prototype member. This is the guard for dotted-path walkers
|
|
132
|
+
* (setNested/unsetPath): it catches the NESTED pollution vectors
|
|
133
|
+
* `__proto__.polluted` and `constructor.prototype.x` that single-key checks
|
|
134
|
+
* miss, because the walk hits `obj["__proto__"]` (the [[Prototype]] getter)
|
|
135
|
+
* long before the final segment is written.
|
|
136
|
+
*/
|
|
137
|
+
export function isDangerousPath(dottedPath: string): boolean {
|
|
138
|
+
return dottedPath.split(".").some((segment) => segment.length > 0 && isDangerousKey(segment));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Deeply strip dangerous prototype-pollution keys from an arbitrary value
|
|
143
|
+
* (objects, arrays, primitives pass through). Returns a fresh plain-object
|
|
144
|
+
* tree that is safe to merge/assign at a parse boundary.
|
|
145
|
+
*
|
|
146
|
+
* M1-1 shared boundary contract: every boundary that accepts a user-supplied
|
|
147
|
+
* config patch, dotted key, or translation registration (team-settings
|
|
148
|
+
* set/unset, updateConfig unsetPaths, config/autonomy patch parsing, i18n
|
|
149
|
+
* addTranslations) routes through isDangerousPath/isDangerousKey/
|
|
150
|
+
* stripDangerousKeysDeep here — ONE canonical drop rule. This is
|
|
151
|
+
* BOUNDARY-LEVEL defense only; it must not grow handler-specific predicates:
|
|
152
|
+
* carrier keys of tool payloads (`args`, `scope`, `unset`, …) are
|
|
153
|
+
* intentionally NOT dangerous keys — never add them (RT24-F1 lesson: mirrored
|
|
154
|
+
* handler predicates drifted and over-blocked reads).
|
|
155
|
+
*
|
|
156
|
+
* Case-insensitive superset of the retired per-module strippers (which covered
|
|
157
|
+
* only `__proto__`/`constructor`/`prototype` case-sensitively): this drops the
|
|
158
|
+
* full DANGEROUS_OBJECT_KEYS set at every level.
|
|
159
|
+
*/
|
|
160
|
+
export function stripDangerousKeysDeep<T>(value: T): T {
|
|
161
|
+
if (value === null || typeof value !== "object") return value;
|
|
162
|
+
if (Array.isArray(value)) return value.map((item) => stripDangerousKeysDeep(item)) as T;
|
|
163
|
+
const safe: Record<string, unknown> = {};
|
|
164
|
+
for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
|
|
165
|
+
if (isDangerousKey(key)) continue;
|
|
166
|
+
safe[key] = stripDangerousKeysDeep(child);
|
|
167
|
+
}
|
|
168
|
+
return safe as T;
|
|
169
|
+
}
|
|
170
|
+
|
|
113
171
|
/**
|
|
114
172
|
* Strips dangerous Object.prototype keys from an object.
|
|
115
173
|
* Returns a new object built with Object.create(null) to prevent
|
package/src/config/config.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { withFileLockSync } from "../state/coordination/locks.ts";
|
|
|
6
6
|
import { logInternalError } from "../utils/internal-error.ts";
|
|
7
7
|
import { isJunkPathLiteral, projectCrewRoot, projectPiRoot } from "../utils/paths.ts";
|
|
8
8
|
import { mergeConfig } from "./config-merge.ts";
|
|
9
|
-
import { parseConfig, parseConfigWithWarnings } from "./config-validation.ts";
|
|
9
|
+
import { isDangerousKey, isDangerousPath, parseConfig, parseConfigWithWarnings } from "./config-validation.ts";
|
|
10
10
|
import { DEFAULT_BROKER, DEFAULT_NESTING, DEFAULT_PERSISTENCE, resolveBrokerEnvOverride } from "./defaults.ts";
|
|
11
11
|
import { getCrewEnv, getCrewEnvBool } from "./env-vars.ts";
|
|
12
12
|
import { sanitizeProjectConfig } from "./sanitize-project-config.ts";
|
|
@@ -258,6 +258,13 @@ function resolvePersistenceEnvOverrideAndDefaults(parsed: PersistenceConfig | un
|
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
function unsetPath(record: Record<string, unknown>, dottedPath: string): void {
|
|
261
|
+
// R24-H2 (M1-1): walking a raw dotted key through `target["__proto__"]`
|
|
262
|
+
// resolves to Object.prototype via the [[Prototype]] getter, so a key like
|
|
263
|
+
// `__proto__.toString` DELETES Object.prototype members. Refuse dangerous
|
|
264
|
+
// segments at the walker itself (shared proto-safe helpers in
|
|
265
|
+
// config-validation.ts) — this covers
|
|
266
|
+
// every unsetPaths consumer (team-settings unset, team `config` action).
|
|
267
|
+
if (isDangerousPath(dottedPath)) return;
|
|
261
268
|
const parts = dottedPath.split(".").filter(Boolean);
|
|
262
269
|
if (parts.length === 0) return;
|
|
263
270
|
let target: Record<string, unknown> = record;
|
|
@@ -305,6 +312,66 @@ function measureJsonDepth(root: unknown): number {
|
|
|
305
312
|
return maxDepth;
|
|
306
313
|
}
|
|
307
314
|
|
|
315
|
+
/**
|
|
316
|
+
* M3-13 (R45-F1): `parseConfig` is a strict WHITELIST builder (see
|
|
317
|
+
* config-validation.ts) — it returns an object literal of known keys only, so
|
|
318
|
+
* merging a patch into `parseConfig(current)` and writing the result back
|
|
319
|
+
* silently DELETES every unknown on-disk key (typos, forward-compat keys
|
|
320
|
+
* written by a newer version, keys owned by another tool). updateConfig used
|
|
321
|
+
* to do exactly that on every write.
|
|
322
|
+
*
|
|
323
|
+
* This helper re-attaches the raw on-disk shape underneath the normalized
|
|
324
|
+
* merge result: `parsed` wins for every key/value it defines (so mergeConfig's
|
|
325
|
+
* per-key semantics and its sanitizers keep applying to known keys), while raw
|
|
326
|
+
* keys that the whitelist dropped are carried through untouched.
|
|
327
|
+
*
|
|
328
|
+
* Dangerous prototype keys are NEVER carried through (M1-1/R24-H1: the parse
|
|
329
|
+
* boundary drops `__proto__`/`constructor`/`prototype` at every depth).
|
|
330
|
+
*
|
|
331
|
+
* @param parsed normalized merge result (what the whitelist understands)
|
|
332
|
+
* @param raw raw on-disk record, already with `unsetPaths` applied
|
|
333
|
+
* @returns a new record shaped like the on-disk file, preserving key order
|
|
334
|
+
*/
|
|
335
|
+
function preserveUnknownKeys(parsed: Record<string, unknown>, raw: Record<string, unknown>): Record<string, unknown> {
|
|
336
|
+
const target = cloneRawRecord(raw, 0);
|
|
337
|
+
return overlayKnownKeys(target, parsed, 0);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const MAX_PRESERVED_DEPTH = 100;
|
|
341
|
+
|
|
342
|
+
function isPlainRecordObject(value: unknown): value is Record<string, unknown> {
|
|
343
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Depth-bounded clone that keeps raw key order and skips dangerous keys. */
|
|
347
|
+
function cloneRawRecord(source: Record<string, unknown>, depth: number): Record<string, unknown> {
|
|
348
|
+
const out: Record<string, unknown> = {};
|
|
349
|
+
if (depth > MAX_PRESERVED_DEPTH) return out;
|
|
350
|
+
for (const [key, value] of Object.entries(source)) {
|
|
351
|
+
if (isDangerousKey(key)) continue;
|
|
352
|
+
out[key] = isPlainRecordObject(value) ? cloneRawRecord(value, depth + 1) : value;
|
|
353
|
+
}
|
|
354
|
+
return out;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** Overlay `source` (normalized) onto `target` (raw clone), recursing into shared objects. */
|
|
358
|
+
function overlayKnownKeys(target: Record<string, unknown>, source: Record<string, unknown>, depth: number): Record<string, unknown> {
|
|
359
|
+
if (depth > MAX_PRESERVED_DEPTH) return target;
|
|
360
|
+
for (const [key, value] of Object.entries(source)) {
|
|
361
|
+
// `undefined` means "not set in this layer" (mergeConfig's withoutUndefined
|
|
362
|
+
// contract) — never write it over a preserved raw value.
|
|
363
|
+
if (value === undefined) continue;
|
|
364
|
+
if (isDangerousKey(key)) continue;
|
|
365
|
+
const existing = target[key];
|
|
366
|
+
if (isPlainRecordObject(value) && isPlainRecordObject(existing)) {
|
|
367
|
+
overlayKnownKeys(existing, value, depth + 1);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
target[key] = value;
|
|
371
|
+
}
|
|
372
|
+
return target;
|
|
373
|
+
}
|
|
374
|
+
|
|
308
375
|
function readConfigRecord(filePath: string): Record<string, unknown> {
|
|
309
376
|
if (!fs.existsSync(filePath)) return {};
|
|
310
377
|
// Defense-in-depth: reject config files larger than 10 MB before parsing.
|
|
@@ -462,7 +529,11 @@ export function updateConfig(patch: PiTeamsConfig, options: UpdateConfigOptions
|
|
|
462
529
|
const message = error instanceof Error ? error.message : String(error);
|
|
463
530
|
throw new Error(`Could not update pi-crew config: ${message}`);
|
|
464
531
|
}
|
|
465
|
-
|
|
532
|
+
const normalizedCurrent = parseConfig(current);
|
|
533
|
+
// Known-key semantics (per-key merge + otlp header sanitization) stay in
|
|
534
|
+
// mergeConfig — see M3-13 below for why the WRITE target is rebuilt from
|
|
535
|
+
// the raw record instead of from this whitelist-normalized result.
|
|
536
|
+
let merged = mergeConfig(normalizedCurrent, patch);
|
|
466
537
|
if (options.unsetPaths?.length) {
|
|
467
538
|
const raw = JSON.parse(JSON.stringify(merged)) as Record<string, unknown>;
|
|
468
539
|
for (const unset of options.unsetPaths) unsetPath(raw, unset);
|
|
@@ -472,12 +543,26 @@ export function updateConfig(patch: PiTeamsConfig, options: UpdateConfigOptions
|
|
|
472
543
|
// (e.g. `team action='config'` with an empty patch — read-only path).
|
|
473
544
|
// Both sides are parseConfig-normalized, so JSON.stringify key order is
|
|
474
545
|
// deterministic (same construction path); no key sorting needed.
|
|
475
|
-
const
|
|
476
|
-
|
|
546
|
+
const normalizedUnchanged = JSON.stringify(merged) === JSON.stringify(normalizedCurrent);
|
|
547
|
+
// M3-13 (R45-F1): parseConfig is a whitelist, so writing `merged` erased
|
|
548
|
+
// every unknown on-disk key (typos, forward-compat keys). Rebuild the
|
|
549
|
+
// written shape from the RAW record: known keys take the merged value,
|
|
550
|
+
// unknown keys survive untouched. `unsetPaths` are applied to the raw
|
|
551
|
+
// base too, so an explicit unset of an unknown key still removes it.
|
|
552
|
+
const rawBase = cloneRawRecord(current, 0);
|
|
553
|
+
for (const unset of options.unsetPaths ?? []) unsetPath(rawBase, unset);
|
|
554
|
+
const toWrite = preserveUnknownKeys(merged as unknown as Record<string, unknown>, rawBase);
|
|
555
|
+
// An unsetPaths entry may name a key the whitelist cannot see (an unknown
|
|
556
|
+
// key), in which case the normalized comparison above reports "unchanged"
|
|
557
|
+
// even though the raw record must change. Only that case overrides the
|
|
558
|
+
// skip: if applying the unsets left the raw base byte-identical, nothing
|
|
559
|
+
// outside the whitelist moved and the legacy skip stands.
|
|
560
|
+
const unsetsChangedRaw = JSON.stringify(rawBase) !== JSON.stringify(current);
|
|
561
|
+
if (normalizedUnchanged && !unsetsChangedRaw) {
|
|
477
562
|
return { path: filePath, config: merged, written: false }; // unchanged — skip write
|
|
478
563
|
}
|
|
479
564
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
480
|
-
atomicWriteFile(filePath, `${JSON.stringify(
|
|
565
|
+
atomicWriteFile(filePath, `${JSON.stringify(toWrite, null, 2)}\n`);
|
|
481
566
|
// (F16) Invalidate the loadConfig cache after a write — the next
|
|
482
567
|
// caller must see the new value, not a 0-2s stale snapshot.
|
|
483
568
|
invalidateConfigCache();
|
package/src/config/env-vars.ts
CHANGED
|
@@ -277,7 +277,7 @@ export const CREW_ENV_VARS: Record<string, CrewEnvVarSpec> = {
|
|
|
277
277
|
},
|
|
278
278
|
PI_CREW_DEBUG: {
|
|
279
279
|
name: "PI_CREW_DEBUG",
|
|
280
|
-
doc: "truthiness enables background debug logging (background-runner.ts:63)",
|
|
280
|
+
doc: "truthiness enables background debug logging (background-runner.ts:63); RAW truthiness — '0'/'false' still ENABLE, unset to disable (R45-F4)",
|
|
281
281
|
},
|
|
282
282
|
PI_CREW_DEBUG_BUDGET: {
|
|
283
283
|
name: "PI_CREW_DEBUG_BUDGET",
|
|
@@ -371,7 +371,7 @@ export const CREW_ENV_VARS: Record<string, CrewEnvVarSpec> = {
|
|
|
371
371
|
},
|
|
372
372
|
PI_CREW_TRUST_PROJECT_DWF: {
|
|
373
373
|
name: "PI_CREW_TRUST_PROJECT_DWF",
|
|
374
|
-
doc: "'1' allows project-sourced .dwf.ts workflows (dynamic-workflow-runner.ts:
|
|
374
|
+
doc: "'1' allows project-sourced .dwf.ts workflows (dynamic-workflow-runner.ts:189)",
|
|
375
375
|
},
|
|
376
376
|
PI_TEAMS_CHILD_RESPONSE_TIMEOUT_MS: {
|
|
377
377
|
name: "PI_TEAMS_CHILD_RESPONSE_TIMEOUT_MS",
|
|
@@ -380,7 +380,7 @@ export const CREW_ENV_VARS: Record<string, CrewEnvVarSpec> = {
|
|
|
380
380
|
},
|
|
381
381
|
PI_TEAMS_DEBUG: {
|
|
382
382
|
name: "PI_TEAMS_DEBUG",
|
|
383
|
-
doc: "truthiness gates debug-severity internal-error logging (internal-error.ts:
|
|
383
|
+
doc: "truthiness gates debug-severity internal-error logging (internal-error.ts:10); RAW truthiness — '0'/'false' still ENABLE, unset to disable (R45-F4)",
|
|
384
384
|
},
|
|
385
385
|
PI_TEAMS_MOCK_CHILD_PI: {
|
|
386
386
|
name: "PI_TEAMS_MOCK_CHILD_PI",
|
|
@@ -766,18 +766,25 @@ export const CREW_ENV_VARS: Record<string, CrewEnvVarSpec> = {
|
|
|
766
766
|
* Raw crew-env lookup. Returns the raw string (undefined when unset) with
|
|
767
767
|
* mirror-alias fallback applied per the entry's `mirrorPrecedence`. This is
|
|
768
768
|
* the behavior-preserving replacement for `process.env.PI_CREW_X` reads.
|
|
769
|
+
*
|
|
770
|
+
* R26-F1 (deep-review round 26): `env` is an OPTIONAL source override. Reads
|
|
771
|
+
* that receive an env object as a parameter (`isRootSession(env)`,
|
|
772
|
+
* `currentCrewDepth(env)`, …) must still route through the registry — passing
|
|
773
|
+
* the object here keeps the registry invariant intact for those sites instead
|
|
774
|
+
* of leaving them as gate-blind `env.PI_CREW_X` dot reads. Defaults to
|
|
775
|
+
* `process.env`, so existing call sites are unchanged.
|
|
769
776
|
*/
|
|
770
|
-
export function getCrewEnv(name: string): string | undefined {
|
|
777
|
+
export function getCrewEnv(name: string, env: NodeJS.ProcessEnv = process.env): string | undefined {
|
|
771
778
|
const spec = CREW_ENV_VARS[name];
|
|
772
779
|
if (!spec?.mirror || spec.mirrorPrecedence === "or") {
|
|
773
|
-
return
|
|
780
|
+
return env[name];
|
|
774
781
|
}
|
|
775
782
|
const crewName = name.startsWith("PI_TEAMS_") ? spec.mirror : name;
|
|
776
783
|
const teamsName = name.startsWith("PI_TEAMS_") ? name : spec.mirror;
|
|
777
784
|
if (spec.mirrorPrecedence === "teams") {
|
|
778
|
-
return
|
|
785
|
+
return env[teamsName] ?? env[crewName];
|
|
779
786
|
}
|
|
780
|
-
return
|
|
787
|
+
return env[crewName] ?? env[teamsName];
|
|
781
788
|
}
|
|
782
789
|
|
|
783
790
|
/**
|
package/src/config/role-tools.ts
CHANGED
|
@@ -27,9 +27,11 @@ export const ROLE_TOOL_CONFIGS: Record<string, RoleToolConfig> = {
|
|
|
27
27
|
excludeTools: ["edit", "write", "bash", "web"],
|
|
28
28
|
},
|
|
29
29
|
|
|
30
|
-
// Analyst - Read and analyze, limited execution
|
|
30
|
+
// Analyst - Read and analyze, limited execution. (ask_question removed
|
|
31
|
+
// 2026-10-18: the host tool is `ask` — excluding a nonexistent name was a
|
|
32
|
+
// dead entry; the live `ask` tool stays allowed for analyst.)
|
|
31
33
|
analyst: {
|
|
32
|
-
excludeTools: ["edit", "write"
|
|
34
|
+
excludeTools: ["edit", "write"],
|
|
33
35
|
},
|
|
34
36
|
|
|
35
37
|
// Planner - Read-only planning; emits plans as TEXT (runner persists result).
|
|
@@ -41,7 +43,7 @@ export const ROLE_TOOL_CONFIGS: Record<string, RoleToolConfig> = {
|
|
|
41
43
|
// default/implementation workflows.
|
|
42
44
|
planner: {
|
|
43
45
|
tools: ["read", "grep", "find", "ls", "glob", "ask"],
|
|
44
|
-
excludeTools: ["edit", "write", "bash", "web"
|
|
46
|
+
excludeTools: ["edit", "write", "bash", "web"],
|
|
45
47
|
},
|
|
46
48
|
|
|
47
49
|
// Critic - Read-only plan/design critique (F2: was missing from the map,
|
|
@@ -67,7 +69,7 @@ export const ROLE_TOOL_CONFIGS: Record<string, RoleToolConfig> = {
|
|
|
67
69
|
// Writer - Documentation focused
|
|
68
70
|
writer: {
|
|
69
71
|
tools: ["read", "edit", "write", "ls", "ask"],
|
|
70
|
-
excludeTools: ["bash", "web"
|
|
72
|
+
excludeTools: ["bash", "web"],
|
|
71
73
|
},
|
|
72
74
|
|
|
73
75
|
// Security Reviewer - Strict restrictions
|
|
@@ -76,7 +78,7 @@ export const ROLE_TOOL_CONFIGS: Record<string, RoleToolConfig> = {
|
|
|
76
78
|
// resolved at runtime (returned {}), silently dropping enforcement.
|
|
77
79
|
"security-reviewer": {
|
|
78
80
|
tools: ["read", "grep", "find", "ask"],
|
|
79
|
-
excludeTools: ["edit", "write", "bash", "web"
|
|
81
|
+
excludeTools: ["edit", "write", "bash", "web"],
|
|
80
82
|
},
|
|
81
83
|
|
|
82
84
|
// Verifier - Runs tests (needs bash) but must NOT edit source (F4: moved
|