pi-crew 0.11.4 → 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.
- package/CHANGELOG.md +109 -0
- package/README.md +2 -2
- package/dist/index.mjs +498 -492
- package/docs/actions-reference.md +22 -0
- package/docs/dynamic-workflows.md +37 -0
- package/index.ts +9 -5
- package/package.json +18 -14
- package/scripts/analyze-run.mjs +101 -14
- package/skills/model-routing-context/SKILL.md +4 -3
- package/skills/real-test-pi-crew/SKILL.md +10 -7
- package/src/config/env-vars.ts +6 -0
- package/src/extension/crew-vibes/index.ts +29 -10
- package/src/extension/pi-api.ts +1 -0
- package/src/extension/registration/lifecycle-handlers.ts +65 -3
- package/src/extension/team-tool/dispatch/index.ts +4 -3
- package/src/extension/team-tool/dispatch/status.ts +7 -3
- package/src/extension/team-tool/inspect.ts +90 -0
- package/src/extension/team-tool/respond.ts +54 -0
- package/src/extension/team-tool/status.ts +10 -0
- package/src/extension/team-tool.ts +393 -3
- package/src/extension/webhook-notify.ts +23 -1
- package/src/observability/exporters/otlp-exporter.ts +93 -11
- package/src/prompt/prompt-runtime.ts +13 -4
- package/src/runtime/agent-control.ts +1 -15
- package/src/runtime/background-runner.ts +3 -0
- package/src/runtime/broker/crew-broker.ts +12 -103
- package/src/runtime/broker/protocol/request-parsers.ts +9 -1
- package/src/runtime/broker/protocol/wait-resolve.ts +168 -0
- package/src/runtime/child-pi/child-pi-spawn.ts +5 -4
- package/src/runtime/dispatch-batch.ts +3 -1
- package/src/runtime/goal-workflow/dynamic-workflow-context.ts +111 -12
- package/src/runtime/goal-workflow/dynamic-workflow-runner.ts +19 -0
- package/src/runtime/live-session/live-agent-manager.ts +35 -0
- package/src/runtime/live-session/live-session-runtime.ts +34 -5
- package/src/runtime/mcp-proxy.ts +110 -7
- package/src/runtime/model/pi-args.ts +14 -11
- package/src/runtime/model/provider-extensions.ts +67 -1
- package/src/runtime/recovery/crash-recovery.ts +6 -1
- package/src/runtime/scheduling/run-coalesced-task-group.ts +89 -6
- package/src/runtime/stale-reconciler.ts +55 -0
- package/src/runtime/task-output-context.ts +11 -1
- package/src/runtime/task-runner/child-executor.ts +130 -4
- package/src/runtime/task-runner/output-splitter.ts +48 -5
- package/src/runtime/task-runner/post-execution.ts +2 -2
- package/src/runtime/task-runner/pre-execution.ts +37 -24
- package/src/runtime/task-runner/retrieval-orchestrator.ts +28 -2
- package/src/schema/team-tool-schema.ts +27 -1
- package/src/state/contracts.ts +16 -0
- package/src/state/crew-init.ts +114 -0
- package/src/state/stores/active-run-registry.ts +13 -6
- package/src/state/types.ts +0 -8
- package/src/ui/goal-flag.ts +44 -0
- package/src/ui/live-run-sidebar.ts +5 -1
- package/src/ui/pi-ui-compat.ts +20 -3
- package/src/ui/run-dashboard.ts +2 -1
- package/src/ui/widget/widget-model.ts +6 -2
- package/src/utils/ip-literal.ts +145 -0
- package/src/workflows/workflow-config.ts +6 -0
|
@@ -48,6 +48,7 @@ Reads the versioned `PlanRecord` list at `<stateRoot>/plans/plans.json` (ADR-4).
|
|
|
48
48
|
| `search` | BM25-ranked agent/team discovery | Smart search |
|
|
49
49
|
| `events` | Read the event log | Debug/audit |
|
|
50
50
|
| `artifacts` | List run artifacts | View outputs |
|
|
51
|
+
| `breakdown` | Read per-task prompt-token breakdown artifacts | Debug prompt bloat (requires `PI_CREW_PROMPT_BREAKDOWN=1`) |
|
|
51
52
|
| `worktrees` | List run worktree metadata | Inspect worktrees |
|
|
52
53
|
| `graph` | Load/save/list run graphs | Visualization |
|
|
53
54
|
| `explain` | Explain a run or task structure | Understand a run |
|
|
@@ -77,6 +78,7 @@ Reads the versioned `PlanRecord` list at `<stateRoot>/plans/plans.json` (ADR-4).
|
|
|
77
78
|
| **`workflow-list`** | **v0.9.0** List static + dynamic workflows | Discover workflows |
|
|
78
79
|
| **`workflow-save`** | **v0.9.0** Overwrite `.dwf.ts` source (requires `confirm:true`) | Update dynamic workflow |
|
|
79
80
|
| **`workflow-delete`** | **v0.9.0** Delete a `.dwf.ts` (requires `confirm:true`) | Remove dynamic workflow |
|
|
81
|
+
| `compare` | Compare two runs side by side (US-021) | Diff before/after a fix, or two attempts at one goal |
|
|
80
82
|
| `help` | Display help text | Help |
|
|
81
83
|
|
|
82
84
|
---
|
|
@@ -420,6 +422,26 @@ Append-only JSONL events: task.started, task.completed, run.blocked, etc.
|
|
|
420
422
|
|
|
421
423
|
---
|
|
422
424
|
|
|
425
|
+
### `breakdown` — Per-task prompt-token breakdown
|
|
426
|
+
|
|
427
|
+
G18 (opt-in measurement surface): reads the per-task
|
|
428
|
+
`metadata/<taskId>.prompt-breakdown.json` artifacts and aggregates per-task
|
|
429
|
+
`estTokens` totals plus the top-5 largest prompt sections. Artifacts are
|
|
430
|
+
recorded only when the host Pi session is started with
|
|
431
|
+
`PI_CREW_PROMPT_BREAKDOWN=1` (default off — zero cost when off).
|
|
432
|
+
|
|
433
|
+
```json
|
|
434
|
+
{
|
|
435
|
+
"action": "breakdown",
|
|
436
|
+
"runId": "team_...",
|
|
437
|
+
"taskId": "01_plan"
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
`taskId` is optional — omit it to aggregate every task with a breakdown.
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
423
445
|
### `worktrees` — Worktree metadata
|
|
424
446
|
|
|
425
447
|
```json
|
|
@@ -163,6 +163,13 @@ child worker, so no tokens are wasted past the limit.
|
|
|
163
163
|
- `spent()` accumulates each `ctx.agent()` run's reported `usage.input + usage.output`.
|
|
164
164
|
- Set it via the workflow's `maxTokenBudget` field, or the run `tokenBudget` param
|
|
165
165
|
(the param overrides the workflow value).
|
|
166
|
+
- **Content-based reserve (SDD-3 W-C G14):** before spawning, the call reserves
|
|
167
|
+
`max(512, estimateTokens(prompt + systemPrompt chars))` — the real in-tree
|
|
168
|
+
estimator (`chars/4`, same as `pre-execution.ts`) instead of the old flat
|
|
169
|
+
4096. Short calls no longer over-reserve (more headroom for concurrent
|
|
170
|
+
siblings); very long prompts reserve proportionally. On completion the
|
|
171
|
+
reserve is adjusted to the reported usage; on failure it is refunded in
|
|
172
|
+
full, so `spent()` never drifts from reality.
|
|
166
173
|
|
|
167
174
|
```ts
|
|
168
175
|
if (ctx.budget.total !== null && ctx.budget.remaining() < 500) {
|
|
@@ -170,6 +177,32 @@ if (ctx.budget.total !== null && ctx.budget.remaining() < 500) {
|
|
|
170
177
|
}
|
|
171
178
|
```
|
|
172
179
|
|
|
180
|
+
### Agent-call cap (SDD-3 W-C G13)
|
|
181
|
+
|
|
182
|
+
Every dynamic workflow is bounded by a **maxAgentCalls cap** — the maximum number
|
|
183
|
+
of `ctx.agent()` invocations per run (for DWF, steps ≡ agent calls: the loop
|
|
184
|
+
lives inside your script, so each spawned agent IS a step). This closes the
|
|
185
|
+
no-budget runaway gap where the only bounds were the semaphore (concurrency 4)
|
|
186
|
+
and the blind 30-minute script timeout.
|
|
187
|
+
|
|
188
|
+
- **Default: 200 calls** (`DEFAULT_MAX_AGENT_CALLS`) when nothing is configured —
|
|
189
|
+
a run is always bounded, even with no `tokenBudget`. 200 gives ≥5× headroom
|
|
190
|
+
over the largest real workflows (distill pipelines, adaptive plans use ~10-40).
|
|
191
|
+
- Override via the run `maxAgentCalls` param or `workflow.maxAgentCalls`
|
|
192
|
+
(frontmatter/`DynamicWorkflowConfig`); values < 1 or non-numeric fall back to
|
|
193
|
+
the default.
|
|
194
|
+
- The cap counts **every `ctx.agent()` invocation**, including cached replays
|
|
195
|
+
(PERS-1 hits) — a spin loop over the same prompt cannot bypass it.
|
|
196
|
+
- **Tripping the cap terminates the run with a structured reason**, not a blind
|
|
197
|
+
timeout: `ctx.agent()` throws `DwfAgentCallCapError`
|
|
198
|
+
(`"dynamic workflow agent-call cap reached (N/N calls; …)"`), the ctx signal is
|
|
199
|
+
aborted (in-flight children are killed; `ctx.pipeline()` rethrows instead of
|
|
200
|
+
swallowing the error into a `null` item), a `dwf.log` event records the trip,
|
|
201
|
+
and the runner fails the run with a `dwf.failed` event carrying the reason.
|
|
202
|
+
- Scripts that `catch` the cap error and keep looping will still make no further
|
|
203
|
+
spawns — every subsequent `ctx.agent()` re-trips the cap instantly (no cost).
|
|
204
|
+
Only the wall-clock script timeout bounds a deliberately-catching script.
|
|
205
|
+
|
|
173
206
|
### Typed args (round-14 P1-5)
|
|
174
207
|
|
|
175
208
|
`ctx.args<T>()` returns typed workflow arguments (sourced from `manifest.args`,
|
|
@@ -214,6 +247,10 @@ script can reach `process`/`require` directly or via constructor walking. The
|
|
|
214
247
|
- The path-allowlist (`resolveRealContainedPath`) limits **WHERE** scripts load from
|
|
215
248
|
(`.crew/workflows/`, `<proj>/.pi/teams/workflows/`, `~/.pi/agent/extensions/pi-crew/workflows/`),
|
|
216
249
|
not what they can do.
|
|
250
|
+
- **Resource bound (SDD-3 G13):** agent dispatches are capped at
|
|
251
|
+
`maxAgentCalls` (default 200) even without a token budget — a runaway script
|
|
252
|
+
terminates with a structured cap reason instead of burning unbounded worker
|
|
253
|
+
spawns until the script timeout.
|
|
217
254
|
- `isolated-vm` (real V8 isolate) is planned for **v1.5**.
|
|
218
255
|
- **Only place `.dwf.ts` files you have reviewed** in `.crew/workflows/`.
|
|
219
256
|
|
package/index.ts
CHANGED
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
* Resolution order:
|
|
5
5
|
* 1. dist/index.mjs (bundle) when present AND not explicitly disabled.
|
|
6
6
|
* Default since v0.9.17 close-out — benchmarks show bundle is
|
|
7
|
-
* ~19% faster
|
|
7
|
+
* ~19% faster cold-start on a cold Node cache; with a warm
|
|
8
|
+
* NODE_COMPILE_CACHE the gap shrinks to ~2%. The bundle's primary
|
|
9
|
+
* value is correctness (vendored typebox) and deterministic startup,
|
|
10
|
+
* not raw speed.
|
|
8
11
|
* See `scripts/bench-cold-start.mjs` for reproducible numbers.
|
|
9
12
|
* 2. Inline strip-types loading — fallback when bundle is missing
|
|
10
13
|
* (e.g. dev clone without `npm run build:bundle`) OR when
|
|
@@ -52,9 +55,9 @@ import { fileURLToPath } from "node:url";
|
|
|
52
55
|
import { dirname, resolve } from "node:path";
|
|
53
56
|
|
|
54
57
|
// IMPORTANT: src/ imports here MUST be dynamic (lazy `import()`), never
|
|
55
|
-
// top-level static imports.
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
+
// top-level static imports. The npm tarball ships dist/index.mjs as the
|
|
59
|
+
// entry and ALSO includes src/ (see "files" in package.json) for source
|
|
60
|
+
// maps of future tooling. dist/index.mjs is ~1.6MB. A top-level static
|
|
58
61
|
// `import { x } from "./src/..."` is resolved by the module loader BEFORE
|
|
59
62
|
// this file's code runs, so it would break every fresh `npm install`
|
|
60
63
|
// (Cannot find module './src/...'). Dynamic import() defers src/ resolution
|
|
@@ -105,7 +108,8 @@ if (!envForceOff) {
|
|
|
105
108
|
// Lazy src/ fallback — only resolved when the bundle is unavailable
|
|
106
109
|
// (dev clones without a built dist/). NEVER imported in published
|
|
107
110
|
// installs, where the bundle always ships + loads above. This dynamic
|
|
108
|
-
// import()
|
|
111
|
+
// import() keeps startup lazy so the bundle loads only when the extension
|
|
112
|
+
// actually activates.
|
|
109
113
|
let srcRegister:
|
|
110
114
|
| typeof import("./src/extension/register.ts").registerPiTeams
|
|
111
115
|
| undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-crew",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.6",
|
|
4
4
|
"description": "Pi extension for coordinated AI teams, workflows, worktrees, and async task orchestration",
|
|
5
5
|
"author": "baphuongna",
|
|
6
6
|
"license": "MIT",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"test": "npm run test:unit && npm run test:integration",
|
|
87
87
|
"test:full": "npm run test:unit && npm run test:integration && npm run test:integration:slow",
|
|
88
88
|
"test:unit": "node scripts/test-runner.mjs --test-concurrency=4 --test-timeout=180000 --test-force-exit 'test/unit/**/*.test.ts'",
|
|
89
|
-
"test:critical": "node scripts/test-runner.mjs --test-concurrency=4 --test-timeout=30000 --test-force-exit test/unit/runtime/broker/crew-broker-handshake.test.ts test/unit/runtime/broker/crew-broker-stale-socket.test.ts test/unit/runtime/broker/crew-broker-feature-flag.test.ts test/unit/runtime/broker/crew-broker-server-gate.test.ts test/unit/runtime/broker/crew-broker-client-fallback.test.ts test/unit/runtime/broker/crew-broker-mailbox-observer.test.ts test/unit/runtime/broker/crew-broker-close-during-reconnect.test.ts test/unit/runtime/broker/crew-broker-steer-dedup.test.ts test/unit/runtime/broker/crew-broker-symlink-steering.test.ts test/unit/ui/keybinding-map.parity.test.ts test/unit/ui/pi-tui-dispatch-probe.test.ts test/unit/utils/session-utils-extract.test.ts test/unit/config/config-schema-sync.test.ts test/unit/runtime/child-pi/child-pi-env-spread.test.ts",
|
|
89
|
+
"test:critical": "node scripts/test-runner.mjs --test-concurrency=4 --test-timeout=30000 --test-force-exit test/unit/runtime/broker/crew-broker-handshake.test.ts test/unit/runtime/broker/crew-broker-stale-socket.test.ts test/unit/runtime/broker/crew-broker-feature-flag.test.ts test/unit/runtime/broker/crew-broker-server-gate.test.ts test/unit/runtime/broker/crew-broker-client-fallback.test.ts test/unit/runtime/broker/crew-broker-mailbox-observer.test.ts test/unit/runtime/broker/crew-broker-close-during-reconnect.test.ts test/unit/runtime/broker/crew-broker-steer-dedup.test.ts test/unit/runtime/broker/crew-broker-symlink-steering.test.ts test/unit/ui/keybinding-map.parity.test.ts test/unit/ui/pi-tui-dispatch-probe.test.ts test/unit/utils/session-utils-extract.test.ts test/unit/config/config-schema-sync.test.ts test/unit/runtime/child-pi/child-pi-env-spread.test.ts test/unit/runtime/task-runner/child-executor-heartbeat-pulse.test.ts",
|
|
90
90
|
"test:watch": "node scripts/test-runner.mjs --watch --test-concurrency=4 --test-timeout=30000 'test/unit/**/*.test.ts'",
|
|
91
91
|
"test:integration": "node scripts/test-runner.mjs --test-concurrency=1 --test-timeout=300000 test/integration/*.test.ts",
|
|
92
92
|
"test:integration:slow": "node scripts/test-runner.mjs --test-concurrency=1 --test-timeout=600000 test/integration/slow/*.test.ts",
|
|
@@ -133,26 +133,27 @@
|
|
|
133
133
|
"@earendil-works/pi-agent-core": "*",
|
|
134
134
|
"@earendil-works/pi-ai": "*",
|
|
135
135
|
"@earendil-works/pi-coding-agent": "*",
|
|
136
|
-
"@earendil-works/pi-tui": "*"
|
|
136
|
+
"@earendil-works/pi-tui": "*",
|
|
137
|
+
"@sinclair/typebox": "*"
|
|
137
138
|
},
|
|
138
139
|
"dependencies": {
|
|
139
|
-
"
|
|
140
|
-
"acorn": "^8.17.0",
|
|
140
|
+
"acorn": "^8.18.0",
|
|
141
141
|
"ajv": "^8.20.0",
|
|
142
142
|
"cli-highlight": "^2.1.11",
|
|
143
143
|
"diff": "^5.2.0",
|
|
144
|
-
"esbuild": "^0.28.
|
|
144
|
+
"esbuild": "^0.28.2",
|
|
145
145
|
"jiti": "^2.7.0",
|
|
146
|
-
"yaml": "^2.9.
|
|
146
|
+
"yaml": "^2.9.1"
|
|
147
147
|
},
|
|
148
148
|
"devDependencies": {
|
|
149
|
-
"@biomejs/biome": "^2.5.
|
|
150
|
-
"@earendil-works/pi-agent-core": "^0.
|
|
151
|
-
"@earendil-works/pi-ai": "^0.
|
|
152
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
153
|
-
"@earendil-works/pi-tui": "^0.
|
|
154
|
-
"@
|
|
155
|
-
"
|
|
149
|
+
"@biomejs/biome": "^2.5.14",
|
|
150
|
+
"@earendil-works/pi-agent-core": "^1.0.0",
|
|
151
|
+
"@earendil-works/pi-ai": "^1.0.0",
|
|
152
|
+
"@earendil-works/pi-coding-agent": "^1.0.0",
|
|
153
|
+
"@earendil-works/pi-tui": "^1.0.0",
|
|
154
|
+
"@sinclair/typebox": "^0.34.52",
|
|
155
|
+
"@types/node": "^25.9.8",
|
|
156
|
+
"tsx": "^4.23.15",
|
|
156
157
|
"typescript": "^7.0.2"
|
|
157
158
|
},
|
|
158
159
|
"peerDependenciesMeta": {
|
|
@@ -167,6 +168,9 @@
|
|
|
167
168
|
},
|
|
168
169
|
"@earendil-works/pi-tui": {
|
|
169
170
|
"optional": true
|
|
171
|
+
},
|
|
172
|
+
"@sinclair/typebox": {
|
|
173
|
+
"optional": true
|
|
170
174
|
}
|
|
171
175
|
},
|
|
172
176
|
"readmeFilename": "README.md"
|
package/scripts/analyze-run.mjs
CHANGED
|
@@ -12,35 +12,40 @@
|
|
|
12
12
|
* pathological multi-GB runs, which pi-crew does not produce.
|
|
13
13
|
*
|
|
14
14
|
* Output:
|
|
15
|
-
* docs/perf-report-<runId>.md
|
|
15
|
+
* docs/perf/perf-report-<runId>.md — báo cáo tiếng Việt, bảng + 🔴 highlight
|
|
16
|
+
* (opt-in --ascii / PI_CREW_ASCII=1: glyph-free ASCII markers — GH-059)
|
|
16
17
|
* bench/results/<runId>.json — structured JSON
|
|
17
18
|
*/
|
|
18
19
|
|
|
19
20
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, createReadStream } from "node:fs";
|
|
20
21
|
import { join } from "node:path";
|
|
21
22
|
import { createInterface } from "node:readline";
|
|
23
|
+
import { pathToFileURL } from "node:url";
|
|
22
24
|
|
|
23
25
|
// ---------- CLI ----------
|
|
24
26
|
function parseArgs(argv) {
|
|
25
|
-
const args = { runId: null, crewRoot: null, resources: null, events: false, agents: false };
|
|
27
|
+
const args = { runId: null, crewRoot: null, resources: null, events: false, agents: false, ascii: false };
|
|
26
28
|
for (let i = 2; i < argv.length; i++) {
|
|
27
29
|
const a = argv[i];
|
|
28
30
|
if (a === "--crew-root") args.crewRoot = argv[++i];
|
|
29
31
|
else if (a === "--resources") args.resources = argv[++i];
|
|
30
32
|
else if (a === "--events") args.events = true;
|
|
31
33
|
else if (a === "--agents") args.agents = true;
|
|
34
|
+
else if (a === "--ascii") args.ascii = true;
|
|
32
35
|
else if (a === "-h" || a === "--help") {
|
|
33
|
-
process.stderr.write("Usage: analyze-run.mjs <runId> [--crew-root <path>] [--resources <path>] [--events]\n");
|
|
36
|
+
process.stderr.write("Usage: analyze-run.mjs <runId> [--crew-root <path>] [--resources <path>] [--events] [--agents] [--ascii]\n");
|
|
34
37
|
process.exit(0);
|
|
35
38
|
} else if (!a.startsWith("--")) {
|
|
36
39
|
args.runId = a;
|
|
37
40
|
}
|
|
38
41
|
}
|
|
39
42
|
if (!args.runId) {
|
|
40
|
-
process.stderr.write("Error: runId required.\nUsage: analyze-run.mjs <runId> [--crew-root <path>]\n");
|
|
43
|
+
process.stderr.write("Error: runId required.\nUsage: analyze-run.mjs <runId> [--crew-root <path>] [--events] [--agents] [--ascii]\n");
|
|
41
44
|
process.exit(1);
|
|
42
45
|
}
|
|
43
46
|
args.crewRoot = args.crewRoot || join(process.env.HOME || "/home/bom", ".crew");
|
|
47
|
+
// --ascii can also come from the environment (CI / vault workflows)
|
|
48
|
+
args.ascii = args.ascii || process.env.PI_CREW_ASCII === "1";
|
|
44
49
|
return args;
|
|
45
50
|
}
|
|
46
51
|
|
|
@@ -57,6 +62,47 @@ const fmtMs = (n) => {
|
|
|
57
62
|
const money = (n) => (n ? `$${n.toFixed(4)}` : "$0");
|
|
58
63
|
const esc = (s) => String(s ?? "").replace(/\|/g, "\\|").replace(/\n/g, " ").slice(0, 80);
|
|
59
64
|
|
|
65
|
+
// ---------- ASCII mode (--ascii / PI_CREW_ASCII=1) — issue #59, spec GH-059 ----------
|
|
66
|
+
// Post-process sweep over the FINAL markdown string, applied at the md write
|
|
67
|
+
// sites only (JSON/CSV outputs are already glyph-free). ORDER MATTERS:
|
|
68
|
+
// composite labels and duration-anchored slow markers first, VS16 sequences
|
|
69
|
+
// (⚠️/⏱️ = base + U+FE0F) before bare codepoints, variation-selector/ZWJ
|
|
70
|
+
// safety net LAST — a leftover U+FE0F is itself inside the consumer lint
|
|
71
|
+
// ranges (U+FE00-FE0F), so partial matches would still fail their gate.
|
|
72
|
+
const ASCII_RULES = [
|
|
73
|
+
// composite severity labels (sevLabel) — per-glyph would render "(!)(!)"
|
|
74
|
+
[/🔴🔴 Nghiêm trọng/g, "CRITICAL"],
|
|
75
|
+
[/🔴 Lỗi/g, "ERROR"],
|
|
76
|
+
[/🟡 Blocked/g, "BLOCKED"],
|
|
77
|
+
[/🟡 Retry/g, "RETRY"],
|
|
78
|
+
[/⚪ Warning/g, "WARN"],
|
|
79
|
+
// slow-phase markers: duration-formatted values followed by 🔴 (timeline
|
|
80
|
+
// cells "626.3s 🔴", bottlenecks "**626.3s** 🔴") + legend/table header
|
|
81
|
+
[/(\d+(?:\.\d+)?ms|\d+(?:\.\d+)?s|\d+m\d+s) 🔴/g, "$1 [SLOW]"],
|
|
82
|
+
[/🔴 = phase vượt/g, "[SLOW] = phase vượt"],
|
|
83
|
+
[/🔴>30s/g, "[SLOW]>30s"],
|
|
84
|
+
// VS16 sequences — MUST match the full sequence, never the bare codepoint
|
|
85
|
+
[/⚠️/g, "[WARN]"],
|
|
86
|
+
[/⏱️/g, ""],
|
|
87
|
+
// semantic markers
|
|
88
|
+
[/🔴/g, "(!)"],
|
|
89
|
+
[/🟡/g, "(~)"],
|
|
90
|
+
[/🔵/g, "(i)"],
|
|
91
|
+
[/⚪/g, "(~)"],
|
|
92
|
+
[/✅/g, "[OK]"],
|
|
93
|
+
[/❌/g, "[FAIL]"],
|
|
94
|
+
[/✓/g, "[ok]"],
|
|
95
|
+
// decorative heading emoji — dropped
|
|
96
|
+
[/[📋💰🐌🚨📊💡📈⚡🔍]/gu, ""],
|
|
97
|
+
// safety net: leftover variation selectors / ZWJ
|
|
98
|
+
[/[\uFE00-\uFE0F\u200D]/g, ""],
|
|
99
|
+
];
|
|
100
|
+
function toAscii(md) {
|
|
101
|
+
const out = ASCII_RULES.reduce((s, [re, rep]) => s.replace(re, rep), md);
|
|
102
|
+
// collapse the double space left by dropped heading emoji ("## 📋 X" → "## X")
|
|
103
|
+
return out.replace(/^(#+)[ \t]+/gm, "$1 ");
|
|
104
|
+
}
|
|
105
|
+
|
|
60
106
|
// ---------- line-by-line JSONL reader ----------
|
|
61
107
|
async function readJsonl(path) {
|
|
62
108
|
const out = [];
|
|
@@ -570,6 +616,37 @@ async function analyzeTranscripts(artifactsDir) {
|
|
|
570
616
|
}
|
|
571
617
|
|
|
572
618
|
// ---------- resources ----------
|
|
619
|
+
/**
|
|
620
|
+
* Max-decimation for per-agent trajectories: when samples exceed `cap`, split
|
|
621
|
+
* the index range into ≤cap buckets and keep each bucket's MAX-rssBytes
|
|
622
|
+
* point (first index wins ties — deterministic).
|
|
623
|
+
*
|
|
624
|
+
* The previous even-stride cap (keep every ceil(n/80)-th index) is
|
|
625
|
+
* aliasing-prone: a single-sample RSS spike (OOM, short-lived tool
|
|
626
|
+
* subprocess) landing between kept indices was dropped ENTIRELY — the
|
|
627
|
+
* report charted a flat line where the incident happened. Max-per-bucket
|
|
628
|
+
* guarantees the spike's bucket cannot select a smaller point, so peaks
|
|
629
|
+
* always survive downsampling.
|
|
630
|
+
*
|
|
631
|
+
* Output keeps input order (buckets are index-ordered) with ts strictly
|
|
632
|
+
* increasing (equal-ts samples across worker/tool PIDs collapse).
|
|
633
|
+
*/
|
|
634
|
+
export function decimateTrajectory(points, cap = 80) {
|
|
635
|
+
if (!Array.isArray(points) || points.length <= cap) return points;
|
|
636
|
+
const n = points.length;
|
|
637
|
+
const kept = new Array(cap).fill(null);
|
|
638
|
+
for (let i = 0; i < n; i++) {
|
|
639
|
+
const bucket = Math.min(Math.floor((i * cap) / n), cap - 1);
|
|
640
|
+
const cur = kept[bucket];
|
|
641
|
+
if (cur === null || (points[i].rssBytes || 0) > (cur.rssBytes || 0)) kept[bucket] = points[i];
|
|
642
|
+
}
|
|
643
|
+
const out = [];
|
|
644
|
+
for (const p of kept) {
|
|
645
|
+
if (p && (!out.length || p.ts > out[out.length - 1].ts)) out.push(p);
|
|
646
|
+
}
|
|
647
|
+
return out;
|
|
648
|
+
}
|
|
649
|
+
|
|
573
650
|
function analyzeResources(path, subagents) {
|
|
574
651
|
if (!path || !existsSync(path)) return null;
|
|
575
652
|
const samples = readJsonlSync(path);
|
|
@@ -657,8 +734,9 @@ function analyzeResources(path, subagents) {
|
|
|
657
734
|
avgCpuPct: sa.ownCpuSamples ? Math.round((sa.ownCpuSum / sa.ownCpuSamples) * 10) / 10 : 0,
|
|
658
735
|
attributed: sa.samples > 0,
|
|
659
736
|
descendantPids: [...sa.descendantPids],
|
|
660
|
-
// trajectory: cap to ~80 points
|
|
661
|
-
|
|
737
|
+
// trajectory: cap to ~80 points via max-decimation (per-bucket max RSS) so
|
|
738
|
+
// per-agent files stay readable WITHOUT dropping spikes to stride aliasing
|
|
739
|
+
trajectory: decimateTrajectory(sa.trajectory, 80),
|
|
662
740
|
}));
|
|
663
741
|
return {
|
|
664
742
|
sampleCount: samples.length,
|
|
@@ -706,7 +784,7 @@ async function main() {
|
|
|
706
784
|
|
|
707
785
|
// F1 (audit): validate runId to prevent path injection — runId flows into
|
|
708
786
|
// join(crewRoot, "state", "runs", runId) AND into output filenames
|
|
709
|
-
// (docs/perf-report-<runId>.md, bench/results/<runId>.json). Without this,
|
|
787
|
+
// (docs/perf/perf-report-<runId>.md, bench/results/<runId>.json). Without this,
|
|
710
788
|
// runId="../../tmp/x" would read/write outside the intended dirs.
|
|
711
789
|
if (!/^[A-Za-z0-9_.-]+$/.test(runId)) {
|
|
712
790
|
process.stderr.write(`Error: invalid runId (must be alphanumeric/_/./-): ${runId}\n`);
|
|
@@ -939,7 +1017,10 @@ async function main() {
|
|
|
939
1017
|
|
|
940
1018
|
const resultsDir = join(process.cwd(), "bench", "results");
|
|
941
1019
|
mkdirSync(resultsDir, { recursive: true });
|
|
942
|
-
|
|
1020
|
+
// Perf reports write to docs/perf/ (runtime output, gitignored via the
|
|
1021
|
+
// narrow `docs/perf/perf-report-team_*` pattern — curated perf docs in
|
|
1022
|
+
// docs/perf/ stay tracked). Legacy location was docs/ top level.
|
|
1023
|
+
const docsDir = join(process.cwd(), "docs", "perf");
|
|
943
1024
|
mkdirSync(docsDir, { recursive: true });
|
|
944
1025
|
const jsonPath = join(resultsDir, `${runId}.json`);
|
|
945
1026
|
writeFileSync(jsonPath, JSON.stringify(report, null, 2));
|
|
@@ -964,7 +1045,7 @@ async function main() {
|
|
|
964
1045
|
for (const sa of report.subagents) {
|
|
965
1046
|
const rows = eventTimeline ? eventTimeline.rows.filter((r) => r.taskId === sa.taskId) : [];
|
|
966
1047
|
const mdSa = renderSubagentFile(sa, report, rows);
|
|
967
|
-
writeFileSync(join(agentsDir, `${sa.taskId}.md`), mdSa + "\n");
|
|
1048
|
+
writeFileSync(join(agentsDir, `${sa.taskId}.md`), (args.ascii ? toAscii(mdSa) : mdSa) + "\n");
|
|
968
1049
|
}
|
|
969
1050
|
process.stderr.write(`[analyze-run] wrote ${report.subagents.length} per-agent files → ${agentsDir}/\n`);
|
|
970
1051
|
}
|
|
@@ -972,7 +1053,7 @@ async function main() {
|
|
|
972
1053
|
// ---------- emit markdown ----------
|
|
973
1054
|
const md = renderMarkdown(report, ea, subagents, args.agents);
|
|
974
1055
|
const mdPath = join(docsDir, `perf-report-${runId}.md`);
|
|
975
|
-
writeFileSync(mdPath, md);
|
|
1056
|
+
writeFileSync(mdPath, args.ascii ? toAscii(md) : md);
|
|
976
1057
|
|
|
977
1058
|
process.stderr.write(`[analyze-run] wrote ${mdPath}\n[analyze-run] wrote ${jsonPath}\n`);
|
|
978
1059
|
}
|
|
@@ -1327,7 +1408,13 @@ function renderMarkdown(report, ea, subagents, perAgent = false) {
|
|
|
1327
1408
|
return L.join("\n");
|
|
1328
1409
|
}
|
|
1329
1410
|
|
|
1330
|
-
main().
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1411
|
+
// CLI main-guard: run main() only when executed directly (node scripts/analyze-run.mjs),
|
|
1412
|
+
// never when imported (analyze-run-decimation.test.ts imports the pure
|
|
1413
|
+
// decimateTrajectory). import.meta.url vs argv[1] through pathToFileURL so
|
|
1414
|
+
// relative invocations resolve identically to absolute ones.
|
|
1415
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
1416
|
+
main().catch((e) => {
|
|
1417
|
+
process.stderr.write(`[analyze-run] fatal: ${e.stack || e}\n`);
|
|
1418
|
+
process.exit(1);
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: model-routing-context
|
|
3
3
|
description: >
|
|
4
|
-
Model routing, parent context, thinking level, and prompt construction workflow. Use when changing model fallback, child Pi args, inherited context, task prompts, or compact-
|
|
4
|
+
Model routing, parent context, thinking level, and prompt construction workflow. Use when changing model fallback, child Pi args, inherited context, task prompts, or compaction/compact-string behavior.
|
|
5
5
|
When NOT to use: project-level config (use resource-discovery-config); routing for non-pi-crew agents.
|
|
6
6
|
|
|
7
7
|
origin: pi-crew
|
|
@@ -10,7 +10,7 @@ triggers:
|
|
|
10
10
|
- "parent context"
|
|
11
11
|
- "thinking level"
|
|
12
12
|
- "task prompts"
|
|
13
|
-
- "
|
|
13
|
+
- "compaction"
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
# model-routing-context
|
|
@@ -27,7 +27,8 @@ Use this skill when working on model/context propagation.
|
|
|
27
27
|
- Preserve parent model inheritance unless an agent/task/user explicitly provides a non-empty model override.
|
|
28
28
|
- Treat empty strings and whitespace model values as absent.
|
|
29
29
|
- Carry relevant parent conversation context as reference-only; do not let it override explicit task instructions or safety constraints.
|
|
30
|
-
-
|
|
30
|
+
- Distinguish the two compaction layers: host-Pi session compaction (`source/pi/.../session-manager.ts`, `agent-session.ts`, compaction modules) is a reference-only concept — it is not a pi-crew API.
|
|
31
|
+
- Respect pi-crew's write-side compaction when building context: `compactString`/`compactValue` transcript truncation in `src/runtime/child-pi/child-pi-transcript.ts:132-164` (`compactString` exported at :134; cuts marked `[truncated]` / `[pi-crew truncated N entries]`). Avoid ballooning prompts with redundant transcript data.
|
|
31
32
|
- Avoid inline dynamic imports for model providers or prompt helpers.
|
|
32
33
|
- When changing model precedence, add tests for undefined, empty, whitespace, agent, task, parent, and explicit tool override cases.
|
|
33
34
|
- Redact secrets in context snippets and child prompts where logs/artifacts may persist them.
|
|
@@ -108,6 +108,7 @@ End-to-end verification discipline for pi-crew changes. Distilled from the broke
|
|
|
108
108
|
| `src/ui/` | T1 → (T5|T6) → T13 |
|
|
109
109
|
| `src/config/` (kể cả migration-validator) | T1 → T11c |
|
|
110
110
|
| `src/runtime/child-pi/` (spawn/kill/steer) | T7 → T9g |
|
|
111
|
+
| `src/runtime/mcp-proxy.ts` / `src/runtime/model/pi-args.ts` (worker argv build + MCP role-gate — G2/G3 surface, SDD-2) | T7 (`argv-flags.smoke.ts` với `PI_CREW_SMOKE=1`) → T9b-W loadout theo role (post-G2: read-only KHÔNG có MCP trên live-session path là ĐÚNG — xem mục full-loadout sanity 9b) + pinned suites `mcp-proxy.test.ts` / `phase6-runtime-hardening.test.ts` / `child-pi-timeout`+`child-pi-surface` (argv/spill) |
|
|
111
112
|
| `src/runtime/async-runner.ts` / `src/runtime/background-runner.ts` (spawn, allowlist, entry-guard, `executeBackgroundRun` core) | T1 → T9b async (event `async.spawned` KHÔNG được có `data.inline:true` — battery phải chứng minh runner DETACHED thật) → T7; đụng `BACKGROUND_RUNNER_ENV_ALLOWLIST` → thêm T10b/T10c (quirk F4-2026-08-30: mux env bị strip ⇒ async headless âm thầm) |
|
|
112
113
|
| `src/runtime/surface/` | T10a → T10b → T10c (dò herdr bằng `HERDR_ENV=1`, KHÔNG phải `$TMUX`) |
|
|
113
114
|
| `src/prompt/` (ask/message/delegate/surface-worker) | T9b-W → T9g |
|
|
@@ -124,7 +125,7 @@ End-to-end verification discipline for pi-crew changes. Distilled from the broke
|
|
|
124
125
|
| `dist/` (bundle) | T3 → T4 → T8 → T11j |
|
|
125
126
|
| bất kỳ đường dẫn trên, trước khi commit | gates đầy đủ: critical + unit + tsc + biome + md5 |
|
|
126
127
|
|
|
127
|
-
**When to use**: after any change to `src/runtime/broker/*.ts` (broker + tokens + issuer), `src/ui/`, `src/config/` (incl. `src/config/migration-validator.ts`), `src/extension/registration/lifecycle-handlers.ts`, `src/runtime/child-pi/*.ts` (worker spawn/kill/steering), `src/runtime/async-runner.ts` + `src/runtime/background-runner.ts` (detached spawn + allowlist + entry-guarded `executeBackgroundRun` core — async dispatch, xem quy tắc tier ở decision table), `src/runtime/heartbeat/*` + `src/ui/heartbeat-aggregator.ts` (ambient heartbeat — guest `gc-*` skip), `src/runtime/surface/*.ts` (MuxSurface providers, degrade, launch script), `src/prompt/*.ts` (worker-side tools: ask / message / delegate / surface-worker recorder), `src/runtime/goal-workflow/plan-templates.ts`, `src/runtime/team-runner.ts` or `src/runtime/task-runner/**` (scheduler / execution — Tier 7 smoke), `src/state/**` (durable state — Tier 7 + 9a events/status + **Tier 11a read-your-writes**), `src/utils/paths.ts` + `src/utils/project-markers.ts` + `src/runtime/stale-reconciler.ts` + `src/extension/team-tool/health-monitor.ts` (run-state layout + symlink defense — pinned suites listed in the 2026-09-20 update), `src/runtime/live-session/**` + `src/runtime/custom-tools/*` (live-session mode + worker custom tools), `src/schema/team-tool-schema.ts` (or any `Type.Unsafe({...})` schema definition), `src/extension/registration/team-tool.ts`, `workflows/*.workflow.md`, `.github/workflows/*.yml` (CI env — Tier 11e), `scripts/wc-gate.mjs` (Tier 11b), or before any commit touching these paths. Schema changes additionally require Tier 9 (feature battery) because the team tool's TypeBox schema is validated by pi-ai BEFORE the handler runs — a too-strict or malformed schema breaks every action silently. Surface changes additionally require Tier 10 (surface-mode battery) because surface is fail-closed: every failure degrades to headless and the run still goes green — only pane-level evidence proves the panes engaged. Resource `.md` changes (agent bodies/frontmatter, skill metadata, discovery, frontmatter parsing) additionally require **Tier 12** (resource-contract battery) because the agent/team/workflow frontmatter parser is line-based, not YAML — a folded scalar parses as `">"` for every consumer while all other tiers stay green.
|
|
128
|
+
**When to use**: after any change to `src/runtime/broker/*.ts` (broker + tokens + issuer), `src/ui/`, `src/config/` (incl. `src/config/migration-validator.ts`), `src/extension/registration/lifecycle-handlers.ts`, `src/runtime/child-pi/*.ts` (worker spawn/kill/steering), `src/runtime/mcp-proxy.ts` + `src/runtime/model/pi-args.ts` (worker argv build + MCP role-gate — G2/G3, SDD-2), `src/runtime/async-runner.ts` + `src/runtime/background-runner.ts` (detached spawn + allowlist + entry-guarded `executeBackgroundRun` core — async dispatch, xem quy tắc tier ở decision table), `src/runtime/heartbeat/*` + `src/ui/heartbeat-aggregator.ts` (ambient heartbeat — guest `gc-*` skip), `src/runtime/surface/*.ts` (MuxSurface providers, degrade, launch script), `src/prompt/*.ts` (worker-side tools: ask / message / delegate / surface-worker recorder), `src/runtime/goal-workflow/plan-templates.ts`, `src/runtime/team-runner.ts` or `src/runtime/task-runner/**` (scheduler / execution — Tier 7 smoke), `src/state/**` (durable state — Tier 7 + 9a events/status + **Tier 11a read-your-writes**), `src/utils/paths.ts` + `src/utils/project-markers.ts` + `src/runtime/stale-reconciler.ts` + `src/extension/team-tool/health-monitor.ts` (run-state layout + symlink defense — pinned suites listed in the 2026-09-20 update), `src/runtime/live-session/**` + `src/runtime/custom-tools/*` (live-session mode + worker custom tools), `src/schema/team-tool-schema.ts` (or any `Type.Unsafe({...})` schema definition), `src/extension/registration/team-tool.ts`, `workflows/*.workflow.md`, `.github/workflows/*.yml` (CI env — Tier 11e), `scripts/wc-gate.mjs` (Tier 11b), or before any commit touching these paths. Schema changes additionally require Tier 9 (feature battery) because the team tool's TypeBox schema is validated by pi-ai BEFORE the handler runs — a too-strict or malformed schema breaks every action silently. Surface changes additionally require Tier 10 (surface-mode battery) because surface is fail-closed: every failure degrades to headless and the run still goes green — only pane-level evidence proves the panes engaged. Resource `.md` changes (agent bodies/frontmatter, skill metadata, discovery, frontmatter parsing) additionally require **Tier 12** (resource-contract battery) because the agent/team/workflow frontmatter parser is line-based, not YAML — a folded scalar parses as `">"` for every consumer while all other tiers stay green.
|
|
128
129
|
|
|
129
130
|
> **Path map (2026-08-26 reorg + A1)**: `src/runtime/crew-broker*.ts` → `src/runtime/broker/`; `src/runtime/child-pi*.ts` → `src/runtime/child-pi/`; `src/runtime/plan-templates.ts` (flat) → `src/runtime/goal-workflow/plan-templates.ts`; NEW dirs `src/runtime/surface/` and `src/prompt/`. Test files moved with them (`test/unit/crew-broker-*.test.ts` → `test/unit/runtime/broker/`, `test/unit/keybinding-map.parity.test.ts` → `test/unit/ui/`, ...).
|
|
130
131
|
|
|
@@ -138,6 +139,7 @@ End-to-end verification discipline for pi-crew changes. Distilled from the broke
|
|
|
138
139
|
|
|
139
140
|
> **2026-09-29 update (RR-021 optimization wave + 4 review rounds, baseline `79a758d2`..`4393858c`, 37 commits; story `docs/stories/RR-021.md`; audit `pi-crew-optimization-audit.md`)**: (1) **Correctness/hang-risks** — `classifyReviewOutcome` word-boundary fix (the `\baccept\b` string-literal bug + negation guard), git/rg probe timeouts, `withRunLockSync`→`withRunLock` rerouting (lock family v0.9.26 intact), `reconcileAllStaleRuns` concurrent (`mapConcurrent` bound 4, per-run error isolation), sleeps→waitFor. (2) **Bundle hygiene** — esbuild/acorn/diff load via lazy sync-createRequire shims, ajv external, dead code −767 lines: bundle 3360 KB → **1575.2 KB**; NEW gates `check:lazy-imports` (ci.yml:142; src-side `// LAZY:` marker enforcement + dist-side no-hoisted-heavy-import assertion) and `check:bundle-size` (ci.yml:169). (3) **Test-gate integrity (review rounds 2–4)** — the runner now fails LOUDLY on `# cancelled N>0` (dual reporter: stdout UX unchanged, full TAP to a temp file, scraped in BOTH single-run and batch paths — `parseCancelledCount` + 21-test `test-runner-exit.test.ts`), on explicitly-listed files that do not exist (node:test v22 silently skipped them → exit 0; round-3 MAJOR B1), and `test:bundle` routes through the guarded runner (was the only CI gate bypassing it). Incremental tsc: typecheck ~20s → **~3.5s warm**. Suite anchors @ 2026-09-29: unit **8204 tests / 8201 pass / 0 fail / 3 skipped** (909 files), integration **136 / 132 pass / 4 skipped** (35 files incl. 3 slow), `test:critical` **116** (~18s), bundle **1575.2 KB** md5 `35ca335a90da01ac631493f220aaf121` (`--committed-hash` OK). Line refs moved: `PI_CREW_TEST_RUNNER_TIMEOUT_MS` now `scripts/test-runner.mjs:419`; crash-recovery design comment `:228`.
|
|
140
141
|
|
|
142
|
+
> **2026-10-01 update (SDD-2 W-B — G2 MCP role-gate + G3 spill-always + Phase D bump 0.99.1, commits `66f37875`→`f0b343ed`, execution record §13 `pi-crew-sdd-2026-09-30-buoi1.md`)**: (1) **G2 flips full-loadout sanity semantics** — MCP presence in workers is now ROLE- and PATH-dependent: the live-session SDK path strips MCP (`DefaultResourceLoader({ extensionsOverride: stripMcpExtensions })`) cho read-only roles (`mcpPermittedForRole` — explorer/reviewer/security-reviewer/analyst/critic/planner + unknown default-deny); a read-only worker WITHOUT MCP is CORRECT, a write-role worker without MCP is a regression; nếu strip không arm được (SDK hết export `DefaultResourceLoader`…) → fail-open + warning event `task.mcp_enforcement_degraded` (registry 168 → 169, `check:event-types --enforce` clean). CLI child-process path CHƯA cover (WI-4 defer: `~/.pi/agent/mcp.json` trống + `-ne` giết provider `oc-go` — read-only roles vẫn discover adapter MCP trên đường đó). (2) **G3 spill-always**: MỌI task text qua `task.md` 0600 — argv worker KHÔNG bao giờ chứa task text; probe đọc argv phải expect `@<temp>/task.md`, task ngắn mất prefix `Task: `; `TASK_ARG_LIMIT` đã xóa. (3) Phase D bump `@earendil-works/*` → 0.99.x: SDK bỏ option `enableMCP`, tool seam cần `ExtensionToolContext` (export mới `src/extension/pi-api.ts`). Decision table thêm row `mcp-proxy.ts`/`model/pi-args.ts`; D5 loadout line refs dời `:283-330` → `:280-336`.
|
|
141
143
|
> **2026-09-21 update (F-L2 — steer boot-window loss, found by the live feature battery)**: a steer written to `<artifactsRoot>/steering/<taskId>.jsonl` while the worker extension is still LOADING was silently lost forever — `registerPiTeamsPromptRuntime` starts the file poll at load time, pi's loader gates action methods until bind (`sendMessage: notInitialized` THROWS), and the old poll advanced `lastOffset` to `stat.size` BEFORE the line loop while its per-line catch swallowed the throw as "malformed line" (run `team_20260921035840`, root-caused by watcher-driven bisection probes R7-R9: +0.65s post-boot = lost, +2.5s+ = delivered). Fix (`src/prompt/prompt-runtime.ts`): walk the buffer by byte offset, advance only past terminal verdicts (delivered/non-steer/rejected/malformed), rewind to the failing line on a DELIVERY failure, `createSeenSteerIdSet.unmark()` releases the dedup id so the retry is not skipped. Regression suite `test/unit/prompt/prompt-runtime-steer-boot-window.test.ts` (4 tests) — mutation-checked both layers (offset-revert → 4 fail; unmark-removal → exactly the id-bearing test fails). Note: `dist/index.mjs` is UNCHANGED by this fix — child workers load prompt-runtime from the SOURCE path (`pi-args.ts PROMPT_RUNTIME_EXTENSION_PATH`), so the fix is live without a bundle bump.
|
|
142
144
|
>
|
|
143
145
|
> **F-L2 live re-verification (post-restart, 2026-09-21 afternoon)** — two independent proofs: (a) **lab**: pre-write the steer, then `PI_CREW_STEERING_FILE=<file> pi --extension <repo>/src/prompt/prompt-runtime.ts -p '<long task>'` — stderr shows `[pi-crew:prompt-runtime.steer-delivery-failed] Extension runtime not initialized…` ×2 (pre-bind ticks now LOGGED instead of swallowed) then the post-bind tick delivers and the model complies; (b) **real team run**: watcher writes at **+0.51s** after the steering dir appears (the previously-lost window) → worker session log gains the `custom_message` and the probe token lands in `results/<taskId>.txt`. Suite anchors @ 2026-09-21 (post-fix): unit **8006 tests / 8003 pass / 0 fail / 3 skipped** (875 files; 1088s under load), integration **134 / 130 pass / 4 skipped**, `test:critical` **116**, bundle **3360.1 KB** md5 `2f45df91ed0580ee5bb5b9b57a8a8c3f` (UNCHANGED — see the source-path note above).
|
|
@@ -204,7 +206,7 @@ The skill maps to existing CI gates as follows:
|
|
|
204
206
|
| `PI_CREW_SMOKE=1` env | Tier 11e | set ONLY in `weekly-smoke.yml` (auth-gated); nightly.yml deliberately does NOT (comment at `:24`) |
|
|
205
207
|
| `npm run check:decision-drift` | Tier 11 (doc↔code) | **build-blocking in `.github/workflows/ci.yml` since 2026-09-17** (review-remediation baseline): every `PI_CREW_*` token cited in `docs/decisions/*.md` must exist in `src/` |
|
|
206
208
|
| `npm run check:env-vars` | Tier 11 | **build-blocking in `ci.yml` since 2026-09-17**: every `PI_CREW_*` read must be routed via `getCrewEnv` + registered |
|
|
207
|
-
| `npm run check:event-types -- --enforce` | Tier 9a/11 | **build-blocking in `ci.yml` since 2026-09-17**: event registry (`src/state/contracts.ts`) must cover every emitted type — 168 registered / 0 drift @ 2026-09-17 (was 89, drift silent) |
|
|
209
|
+
| `npm run check:event-types -- --enforce` | Tier 9a/11 | **build-blocking in `ci.yml` since 2026-09-17**: event registry (`src/state/contracts.ts`) must cover every emitted type — 168 registered / 0 drift @ 2026-09-17 (was 89, drift silent); **169 @ 2026-10-01** sau SDD-2 (+`task.mcp_enforcement_degraded`) |
|
|
208
210
|
| `npm run check:lazy-imports` | Tier 3 companion (RR-021 WI-2.3 + review rounds 2-4) | `scripts/check-lazy-imports.mjs` — src-side: every runtime `import()` position needs a `// LAZY:` marker (known gaps: ternary-else / object-property / case-label / runtime member-access after same-line colon — documented in-script); dist-side: no module-scope hoisted heavy import (esbuild/acorn/diff/jiti/cli-highlight/yaml/ajv). Build-blocking at `ci.yml:142` |
|
|
209
211
|
| `npm run check:bundle-size` | Tier 3 companion | `scripts/check-bundle-size.mjs` — committed bundle ≤ budget (Sprint 6 gate, `ci.yml:169`; budget re-anchored when RR-021 shrank the bundle 3360→1575 KB) |
|
|
210
212
|
| `PI_CREW_TEST_RUNNER_TIMEOUT_MS` env | Tier 1 companion | overrides the test-runner spawn deadline (`scripts/test-runner.mjs:419` since RR-021; was :206); default **1_500_000 ms** since 2026-09-17 (was 900_000 — sized for a 5800-test suite; the full suite outgrew it and the ETIMEDOUT became a false green pre-F05). Since RR-021 rounds 2-3 the runner ALSO fail-closes on `# cancelled N>0` (TAP dual-reporter scrape) and on explicitly-listed missing files — see `test/unit/scripts/test-runner-exit.test.ts` (21 tests) |
|
|
@@ -593,7 +595,7 @@ If the two md5s match → session is on the latest code. If not → user must `/
|
|
|
593
595
|
- **message notify**: goal says "use the `message` tool to notify the parent when done". Proves `msg.send` (non-blocking) + the broker `from`-override (anti-spoof) + the wake pattern on the orchestrator session. Rate-limit 10 msg/60s per worker — a burst probe should hit the limit, not hang.
|
|
594
596
|
- **message DM/group**: goal says "DM task `<sibling taskId>` / send to group `x`" — proves `to:` routing + inbox pickup (delivered as fenced `<inbox-message>` DATA, not instructions).
|
|
595
597
|
- **delegate nesting**: goal says "use the `delegate` tool to spawn a child agent". Proves the role gate is open for every role (D8, default-on), the depth cap (`nesting.maxDepth: 4` — a depth-5 attempt must reject with the structured policy message + `delegate.rejected` event, never silently), and the nested-slot budget. Kill switch: `nesting.enabled: false` in **user** config only (sensitive — project config cannot flip it).
|
|
596
|
-
- **full loadout sanity** (D5): in any 9b run, have the worker report its loaded extensions/skills/tools. Workers are FULL pi sessions by default — no `--no-extensions`, no `--tools` allowlist, `--no-skills` only when the agent frontmatter says `inheritSkills: false`.
|
|
598
|
+
- **full loadout sanity** (D5): in any 9b run, have the worker report its loaded extensions/skills/tools. Workers are FULL pi sessions by default — no `--no-extensions`, no `--tools` allowlist, `--no-skills` only when the agent frontmatter says `inheritSkills: false`. **Post-G2 (SDD-2, `8dab9e49` 2026-10-01): MCP presence is ROLE- and PATH-dependent on the live-session SDK path** — read-only roles (explorer/reviewer/security-reviewer/analyst/critic/planner + unknown/undefined, per `mcpPermittedForRole`) are INTENTIONALLY stripped of MCP (`DefaultResourceLoader({ extensionsOverride: stripMcpExtensions })` drops `builtin:mcp` + `pi-mcp-adapter`) — a read-only worker missing MCP tools is CORRECT, not a regression; a WRITE-role worker (executor/verifier/writer/test-engineer…) missing MCP still means the loadout policy regressed. If the strip could not arm (SDK stopped exporting `DefaultResourceLoader` / agent dir unresolvable), a non-permitted role fails open with a `task.mcp_enforcement_degraded` warning event — grep it before calling a read-only worker's MCP absence "enforced". The CLI child-process path is NOT yet covered (WI-4 deferred — read-only roles there still discover adapter MCP); see Anti-patterns, armed-role row.
|
|
597
599
|
4. **Acceptance**: every action returns without `Unknown type` / `Validation failed for tool team` / empty error text; every spawn path completes with `consistency=1` and the expected probe token in the agent output.
|
|
598
600
|
|
|
599
601
|
**Real measured outcome** (this session, after the v0.9.57 schema fix): 9a (15 team actions) + 9b (4 subagent tools / 3 run paths) exercised; all green; the two silent-failure modes that motivated this tier (`Unknown type` from `Type.Unsafe` without Kind, and `Validation failed for tool team` from empty-string-strict schema) were caught ONLY by this battery — Tier 1-8 all passed while the team tool was broken live. The session also surfaced the unauthorized-agent-edit anti-pattern (a chain-run agent edited `chain-runner.ts` mid-smoke) — see Anti-patterns.
|
|
@@ -1093,7 +1095,7 @@ PY
|
|
|
1093
1095
|
| `makeFakeCtx({ flagOn: false })` without `brokerEnv: "0"` | `makeFakeCtx` deletes `PI_CREW_BROKER` env if `brokerEnv` is undefined | `612e18b` (test fix) | `test/unit/runtime/broker/crew-broker-server-gate.test.ts:78` — pass `brokerEnv: "0"` to preserve env |
|
|
1094
1096
|
| Trust green CI on one OS | macOS/Windows regressions slip through | n/a (permanent) | `.crew/knowledge.md` — "CI runs 3 OSes ... A flake on one OS IS a real bug" |
|
|
1095
1097
|
| Trusting a team-run agent not to edit the repo under test | Agents spawned by `team`/`Agent`/`crew_agent` inherit the session cwd and have `edit`/`write` tools — a proactive LLM (observed with deepseek) will make **unauthorized source edits** to pi-crew during a trivial smoke run (e.g. "improving" `chain-runner.ts` while parsing a chain string). The edit can be correct + green-tested yet still be unintended scope creep that silently lands in your commit. **Sharpened by D5 (2026-08-26)**: workers used to be tool-allowlisted (`read,grep,bash,...` by role); since full-loadout default EVERY worker has `edit`/`write` + extensions, so this risk now applies to ANY role, not just armed ones. | n/a (permanent) | After EVERY team/subagent run: `git status` and verify each changed file was authored by you. Diff + review any surprise change before staging. Consider `workspaceMode: 'worktree'` for parallel/risky runs to isolate mutations. |
|
|
1096
|
-
| **Armed-role tool-surface bug (found live 2026-08-11; INVERTED by D5 2026-08-26)**: originally an opt-in tool (e.g. `scratchpad`) armed via `ROLE_TOOL_CONFIGS[role]` + env never appeared in the worker surface — the builtin `agents/*.md` frontmatter `tools:` allowlist hard-filtered it via `--tools`. **Since D5 (`bcb9dd5d`, spec v0.7 §10) workers are FULL pi sessions by default: no `--no-extensions`, no `--tools`, no `--no-skills` unless the agent frontmatter declares them (`src/runtime/model/pi-args.ts:
|
|
1098
|
+
| **Armed-role tool-surface bug (found live 2026-08-11; INVERTED by D5 2026-08-26)**: originally an opt-in tool (e.g. `scratchpad`) armed via `ROLE_TOOL_CONFIGS[role]` + env never appeared in the worker surface — the builtin `agents/*.md` frontmatter `tools:` allowlist hard-filtered it via `--tools`. **Since D5 (`bcb9dd5d`, spec v0.7 §10) workers are FULL pi sessions by default: no `--no-extensions`, no `--tools`, no `--no-skills` unless the agent frontmatter declares them (`src/runtime/model/pi-args.ts:280-336`) — so the default failure mode flipped.** Now a tool missing from a worker means either (a) the agent's frontmatter declares a restrictive `tools:` list (opt-in) that doesn't include it, or (b) `inheritSkills: false` / SEC-1 declaration-strip on a dynamic/project agent source. Control tools (`ask`, `delegate`) are auto-added to any declared list. Reproduce: `pi -p --tools read,bash "list tools"` → restricted; plain `pi -p` → full set. | `f753be30` → `bcb9dd5d` | **Fix**: for agents that OPT IN to restrictions, keep `agents/*.md` frontmatter `tools:` in sync with `ROLE_TOOL_CONFIGS` (add new tools to BOTH for pinned roles). A worker claiming a tool is "not available" is a REAL signal — check the worker's actual argv (`--tools` present?) and frontmatter, not just env vars. |
|
|
1097
1099
|
| **Surface test that never engages surface**: `runtime.surface.visibleAgents` defaults to `[]` (nobody). A test setting only `mode:"auto"` (already default) passes green with ZERO panes — surface's fail-closed degrade makes the headless path indistinguishable from success in the run result. | n/a (process) | Always set `visibleAgents` when testing surface, and require pane-level evidence (events `worker.surface_spawned`/`worker.surface_closed`, `tmux list-panes` during the run, sentinel PID). See Tier 10. |
|
|
1098
1100
|
| **Reading `manifest.surface.panes == {}` at run END as "zero panes engaged"** (observed 2026-08-27, full-10tier report): `releaseSurfacePane` deletes the pane entry the moment the pane closes, so a FULLY SUCCESSFUL surface run also ends with `panes:{}` — the report flipped a live herdr engagement (pane `w6:pW`, `worker.surface_spawned` seq 99) into "by-design headless, gate short-circuited". Same trap, other direction: the executor worker re-derived the gate trace from ITS OWN env (`PI_CREW_DEPTH=1` — the CORRECT and EXPECTED depth for a tier-1 worker) instead of the HOST env the gate actually reads (`child-pi.ts` passes `depthEnv ?? process.env`), concluding "headless" while literally running inside a herdr pane (`PI_CREW_SURFACE_PANE=w6:pW` sat unread in its own env). | n/a (process) | Engage-evidence = `events.jsonl` (`worker.surface_spawned` + `worker.surface_closed`, no `surface.degraded`) + `manifest.surface.provider`/`workerPids` (only the surface branch writes `workerPids`). A worker's self-report of "which path taken" is a HYPOTHESIS — workers cannot see the host's gate inputs; trust events over worker prose. |
|
|
1099
1101
|
| **Reporting "session is loading the latest code" from FILE-md5 equality alone** (disk vs symlink): a live report (2026-08-27) did exactly this — md5 disk = md5 symlink → "Tier 4 PASS" — while the parent pi process had started BEFORE the bundle rebuild and was still running the PRE-A1 bundle in memory. Every downstream anomaly then got misread as a code bug (a false "config parser drops surface" finding + root-cause misread). File equality only proves the FILES match, not what the PROCESS loaded — extension code loads at cold-start only. | n/a (permanent) | Tier 4/8 needs PROCESS-level liveness: after any rebuild, the session must `/quit` + reopen, then prove the new code is live via a behavior probe (e.g. `team action='settings' config={args:'get runtime.surface.visibleAgents'}` must recognize the key; any new run's worker env shows `PI_CREW_MAX_DEPTH=4`). Corroborate with `ps -eo pid,lstart,args | grep pi` — a session started before the rebuild mtime is stale, full stop. |
|
|
@@ -1290,7 +1292,7 @@ Use this to answer "đủ full tính năng chưa?" without re-deriving. Every us
|
|
|
1290
1292
|
|
|
1291
1293
|
| Feature | Code entry | Verify via |
|
|
1292
1294
|
|---|---|---|
|
|
1293
|
-
| Team tool —
|
|
1295
|
+
| Team tool — 56 actions / 5 domains | `src/schema/team-tool-schema.ts:391-437`, dispatch in `src/extension/team-tool/` | 9a (read-only) + 9b/9c/9d/9e/9f theo domain |
|
|
1294
1296
|
| Runtime mode `child-process` (default) | `src/runtime/child-pi/` | 9b sync run + T7 |
|
|
1295
1297
|
| 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'` |
|
|
1296
1298
|
| 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`) |
|
|
@@ -1298,7 +1300,8 @@ Use this to answer "đủ full tính năng chưa?" without re-deriving. Every us
|
|
|
1298
1300
|
| Worker tool `ask` (blocking Q→parent) | `src/prompt/prompt-runtime.ts:639`, broker wait.* | 9b-W ask round-trip |
|
|
1299
1301
|
| Worker tool `message` (notify/DM/group) | `src/prompt/message-tool.ts` | 9b-W message probes |
|
|
1300
1302
|
| Worker tool `delegate` (nested spawning) | `src/prompt/prompt-runtime.ts:414` | 9b-W delegate + depth-cap reject |
|
|
1301
|
-
| Full loadout (D5) | `src/runtime/model/pi-args.ts:
|
|
1303
|
+
| Full loadout (D5) | `src/runtime/model/pi-args.ts:280-336` | 9b-W full-loadout sanity |
|
|
1304
|
+
| MCP role-gate + task spill (G2/G3, SDD-2 2026-10-01) | `src/runtime/mcp-proxy.ts`, `src/runtime/model/pi-args.ts`, `src/runtime/live-session/live-session-runtime.ts` | `mcp-proxy.test.ts` (15, đỏ-trước flip), `phase6-runtime-hardening.test.ts` (spill 3 case + hardened-default), `child-pi-timeout`/`child-pi-surface` argv assertions, T7 `argv-flags.smoke.ts` (`PI_CREW_SMOKE=1`); live-session strip fail-open → event `task.mcp_enforcement_degraded` |
|
|
1302
1305
|
| Surface panes tmux/herdr (A1) | `src/runtime/surface/` | T10 (10a E2E + 10b live + 10c herdr) |
|
|
1303
1306
|
| Broker (mailbox, steer, tokens) | `src/runtime/broker/` | T1/T2 + 9c steer/respond + T10a test #2 |
|
|
1304
1307
|
| Dashboard + keybindings + overlays | `src/ui/`, commands `src/extension/registration/commands/` | T5/T6 probe + parity golden test |
|
|
@@ -1491,7 +1494,7 @@ Source files (critical paths):
|
|
|
1491
1494
|
- `src/runtime/broker/crew-broker-child.ts` — child-side broker client wiring
|
|
1492
1495
|
- `src/ui/key-utils.ts:37-42` — `keyOf()` using pi-tui `matchesKey()`
|
|
1493
1496
|
- `src/ui/keybinding-map.ts` — dispatch using `matchesKey()` (commit `f05a10d`)
|
|
1494
|
-
- `src/runtime/model/pi-args.ts:
|
|
1497
|
+
- `src/runtime/model/pi-args.ts:280-336` — D5 loadout: `--tools`/`--no-skills` ONLY khi agent frontmatter khai báo; `DEFAULT_MAX_CREW_DEPTH = 4`
|
|
1495
1498
|
- `src/extension/registration/subagent-tools.ts` — `Agent` (:70), `get_subagent_result` (:359), `steer_subagent` (:475, alias `crew_agent*`)
|
|
1496
1499
|
- `src/runtime/live-session/live-session-runtime.ts` + `src/runtime/custom-tools/irc-tool.ts` — live-session mode + peer-to-peer irc (experimental)
|
|
1497
1500
|
|
package/src/config/env-vars.ts
CHANGED
|
@@ -290,6 +290,12 @@ export const CREW_ENV_VARS: Record<string, CrewEnvVarSpec> = {
|
|
|
290
290
|
name: "PI_CREW_DWF_SKIP_DETERMINISM_CHECK",
|
|
291
291
|
doc: "'1' disables the DWF determinism check (deterministic-ast.ts:60)",
|
|
292
292
|
},
|
|
293
|
+
PI_CREW_HEARTBEAT_PULSE_MS: {
|
|
294
|
+
name: "PI_CREW_HEARTBEAT_PULSE_MS",
|
|
295
|
+
parser: "int",
|
|
296
|
+
default: 15_000,
|
|
297
|
+
doc: "child-executor liveness-pulse interval (ms): touches the worker heartbeat while the attempt's child process is alive even with no stdout events (NEW-4/G25 silent-turn freeze); ≤0 disables (child-executor.ts)",
|
|
298
|
+
},
|
|
293
299
|
PI_CREW_INTERRUPT_GUARD_INTERVAL_MS: {
|
|
294
300
|
name: "PI_CREW_INTERRUPT_GUARD_INTERVAL_MS",
|
|
295
301
|
parser: "int",
|