pi-crew 0.9.5 → 0.9.8
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 +556 -0
- package/README.md +10 -3
- package/docs/HARNESS_BACKLOG.md +51 -3
- package/docs/dynamic-workflows.md +315 -2
- package/docs/fix-plan-disabletools-exit-null.md +219 -0
- package/docs/troubleshooting.md +76 -0
- package/package.json +10 -3
- package/src/config/defaults.ts +8 -4
- package/src/extension/team-tool/doctor.ts +14 -0
- package/src/extension/team-tool/run.ts +2 -0
- package/src/runtime/background-runner.ts +1 -1
- package/src/runtime/capability-inventory.ts +20 -1
- package/src/runtime/child-pi.ts +109 -11
- package/src/runtime/deterministic-ast.ts +161 -0
- package/src/runtime/dwf-state-store.ts +97 -0
- package/src/runtime/dynamic-workflow-context.ts +381 -7
- package/src/runtime/dynamic-workflow-runner.ts +93 -2
- package/src/runtime/pi-args.ts +11 -0
- package/src/runtime/result-extractor.ts +72 -7
- package/src/runtime/task-output-context.ts +25 -9
- package/src/runtime/team-runner.ts +8 -3
- package/src/runtime/zombie-scanner.ts +297 -0
- package/src/schema/team-tool-schema.ts +28 -0
- package/src/skills/discover-skills.ts +61 -8
- package/src/skills/validate.ts +267 -0
- package/src/state/contracts.ts +1 -0
- package/src/state/state-store.ts +3 -0
- package/src/state/types.ts +9 -0
- package/src/ui/dashboard-panes/progress-pane.ts +5 -0
- package/src/ui/dwf-phase-display.ts +151 -0
- package/src/ui/keybinding-map.ts +128 -41
- package/src/ui/run-event-bus.ts +83 -0
- package/src/ui/run-snapshot-cache.ts +4 -0
- package/src/ui/snapshot-types.ts +3 -0
- package/src/workflows/workflow-config.ts +3 -0
- package/src/worktree/worktree-manager.ts +94 -0
- package/types/dwf.d.ts +187 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,561 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [v0.9.8] — deer-flow learning integration: L1/L2/L3/L4 (2026-06-24)
|
|
4
|
+
|
|
5
|
+
Four improvements distilled from researching [bytedance/deer-flow](https://github.com/bytedance/deer-flow) and the wider Pi-ecosystem (pi-boomerang, pi-subagents, pi-dynamic-workflows). Each was calibrated against real pi-crew code (the research over-reported gaps — several patterns pi-crew already does *better* than deer-flow) and sized from measured data, not guesses.
|
|
6
|
+
|
|
7
|
+
### L3 — Strict SKILL.md frontmatter validation (commit 5348c47)
|
|
8
|
+
|
|
9
|
+
Malformed skills now **fail-fast at discovery** instead of silently producing broken behavior at runtime. New `src/skills/validate.ts` validates frontmatter against the `ALLOWED_SKILL_PROPS` whitelist using a **HYBRID policy**:
|
|
10
|
+
|
|
11
|
+
- **HARD errors** (missing/malformed `name` or `description`, type mismatches) → skill excluded from `discoverSkills()`.
|
|
12
|
+
- **SOFT warnings** (unknown props like `origin`/`triggers`, missing `name` derived from directory) → skill kept, surfaced via `getLastDiscoveryDiagnostics()` / `buildSkillValidationDiagnostics()`.
|
|
13
|
+
|
|
14
|
+
Replaces the fragile line-prefix parser (broke on multi-line folded scalars `description: >`, quoted strings, nested YAML) with the `yaml` package (^2.9.0, already transitive, added as direct dep — zero install cost). Back-compat preserved: missing `name` derives from the directory; no-frontmatter skills still load with empty description.
|
|
15
|
+
|
|
16
|
+
**Bonus value**: pre-flight on the real environment surfaced 2 user skills that were silently broken (`agent-browser`: `allowed-tools` wrong type; `spike-wrap-up`: `<>` in description).
|
|
17
|
+
|
|
18
|
+
### L2 — Data-driven keybinding dispatch (commit 35fc3c6)
|
|
19
|
+
|
|
20
|
+
Replaced the 30-line imperative `if (includes(...))` chain in `dashboardActionForKey` with a single `for (const b of BINDINGS)` loop driven by a declarative `BINDINGS[]` table. Adding a key now means editing ONE place (the table) instead of two (table + dispatch) — removes the DRY violation that caused table-vs-dispatch drift. `KEY_RESERVED` is now exported and derived.
|
|
21
|
+
|
|
22
|
+
Behavior is **provably identical** to the old chain: a golden-snapshot parity test asserts every `(data, activePane)` pair returns the same action (~190 pairs). Pane-scoped bindings (`mailbox-detail`, `health-*`) precede their generic competitors so first-match-wins reproduces old precedence.
|
|
23
|
+
|
|
24
|
+
The `inTextInput` guard from the original plan was **intentionally skipped** — overlays are mutually exclusive and each handles its own input (`mailbox-compose-overlay.ts` captures every single-char key), so there is no leak path. Documented in the commit.
|
|
25
|
+
|
|
26
|
+
### L1 — RunEventBus.onWithReplay catch-up primitive (commit a2a478b)
|
|
27
|
+
|
|
28
|
+
Closes the transient-subscriber-absence gap: when an overlay/widget is disposed and recreated (toggle, reconnect), live events emitted in that window are lost as notification triggers. `onWithReplay(runId, eventsPath, lastSeenSeq, callback)` replays missed events from the durable JSONL log before attaching the live listener, then dedups via `metadata.seq` so each event fires exactly once.
|
|
29
|
+
|
|
30
|
+
Unlike deer-flow's 256-event RAM ring buffer (lost on crash), this reuses pi-crew's existing `readEventsCursor` — O(new bytes) via byte-offset incremental reads, monotonic seq, tail-capped. Strictly better: survives crashes, bounded memory. `RunEventPayload` gains optional `seq`; `emitFromTeamEvent` stamps it.
|
|
31
|
+
|
|
32
|
+
The **primitive is landed + fully tested** (7 cases: replay order, dedup race, transient live-only, cursor bound, sinceSeq filter, missing-log fallback, unsubscribe). Dashboard wiring (switching `onAny()` → `onWithReplay()` per-run) is deferred — the dashboard subscribes across multiple runs and needs a subscription-model refactor; state isn't lost during absence anyway (`run-snapshot-cache` rebuilds from disk, TTL 1500ms).
|
|
33
|
+
|
|
34
|
+
### L4 — Data-driven output thresholds + head/tail compaction (commit 463d08d)
|
|
35
|
+
|
|
36
|
+
Worker output was being truncated at 3 points with thresholds sized by guess, not data. Measured 27 real result artifacts: **max 9226 bytes, median 8272, 100% under 16KB**. The old thresholds cut **62% of real outputs** (head-only, no recovery path). This change sizes thresholds from that data and switches compaction from head-only to head+tail so closing markdown structure (code fences, headings) survives.
|
|
37
|
+
|
|
38
|
+
| Threshold | Before | After |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `maxAssistantTextChars` | 8192 | **16384** |
|
|
41
|
+
| `maxToolResultChars` | 1024 | **8192** |
|
|
42
|
+
| `maxCompactContentChars` | 4096 | **8192** |
|
|
43
|
+
| `maxToolInputChars` | 2048 | **4096** |
|
|
44
|
+
| `readIfSmall` (3 inconsistent values) | 24K/40K/80K | **single 32KB** |
|
|
45
|
+
| Compaction shape | head-only | **head(75%)+tail(25%)** |
|
|
46
|
+
|
|
47
|
+
**Why not caveman-shrink** (the alternative considered): tested it on the same 27 artifacts — only 3.9% compression (vs 42% on prose fixtures) because pi-crew output is code-citation-heavy with little prose to strip, AND it has a real data-loss bug (`funccall` protected-pattern eats sentinel placeholders for the `identifier (inline-code)` pattern, corrupting 24/27 files with null bytes, 127 inline codes lost). caveman's *concept* (detect/validate) is worth borrowing but its engine doesn't fit pi-crew's content type. Threshold-only wins on the data.
|
|
48
|
+
|
|
49
|
+
### Tests & verification
|
|
50
|
+
|
|
51
|
+
- 10 new L4 tests, 25 L3 validator tests, 7 L1 replay tests, 7 L2 parity tests.
|
|
52
|
+
- `npm run typecheck` + `check:lazy-imports` green.
|
|
53
|
+
- End-to-end team-run smoke tests confirm all 4 features load and run without crash.
|
|
54
|
+
- Real-world smoke scripts at `test/manual/l{1,2,3}-*-smoke.mjs`.
|
|
55
|
+
- Research artifacts at `source/deer-flow/.research/` + `.crew/research/worker-output-handling.md`.
|
|
56
|
+
|
|
57
|
+
### Backward compatibility
|
|
58
|
+
|
|
59
|
+
All four changes are additive or behavior-preserving:
|
|
60
|
+
- L3: valid skills unaffected; only malformed ones now excluded (was: silent breakage).
|
|
61
|
+
- L2: golden-snapshot parity test proves identical dispatch.
|
|
62
|
+
- L1: new method added; existing `on`/`onAny`/`emit` unchanged.
|
|
63
|
+
- L4: outputs that fit (100% of measured real outputs) are unchanged; only oversized ones now keep head+tail instead of head-only.
|
|
64
|
+
|
|
65
|
+
## [v0.9.7] — round-18 + process-safety fix (2026-06-23)
|
|
66
|
+
|
|
67
|
+
P2-3 feature: durable checkpoint + resume for dynamic-workflow runs. When a `.dwf.ts`
|
|
68
|
+
script crashes (timeout, OOM, agent error) between `ctx.agent()` calls, the runner now
|
|
69
|
+
persists a checkpoint after every agent call so `team action='resume' runId='X'` can
|
|
70
|
+
continue from the last checkpoint instead of re-running from scratch. **Backward
|
|
71
|
+
compatible** — fresh runs (no checkpoint) behave exactly as before.
|
|
72
|
+
|
|
73
|
+
### Implementation
|
|
74
|
+
|
|
75
|
+
**New file** `src/runtime/dwf-state-store.ts` (`DwfStore`):
|
|
76
|
+
- Atomic CRUD for a single run's DWF checkpoint, modeled on `GoalStore` /
|
|
77
|
+
`FileCheckpointStore`.
|
|
78
|
+
- Persists `DwfCheckpointState` (vars, phases, currentPhase, logs, spent, agentCount,
|
|
79
|
+
updatedAt) to `<stateRoot>/dwf-checkpoint.json` via `atomicWriteJson`.
|
|
80
|
+
- `load()` returns `undefined` for a missing/corrupt checkpoint (fresh run); `delete()`
|
|
81
|
+
is best-effort and never throws.
|
|
82
|
+
|
|
83
|
+
**`ctx.agent()` checkpoint hook** in `src/runtime/dynamic-workflow-context.ts`:
|
|
84
|
+
- New `MakeWorkflowCtxOptions.onCheckpoint?: (state) => void` — invoked after each
|
|
85
|
+
`ctx.agent()` call (success OR fail) so a crash between calls leaves durable state.
|
|
86
|
+
- New `MakeWorkflowCtxOptions.resumedState?` — hydrates `ctx.vars`, phase state, logs,
|
|
87
|
+
`budget.spent()`, and `agentCount` from the checkpoint on resume.
|
|
88
|
+
- New closure counter `agentCount` (incremented in `agent()`'s `finally`), exposed via a
|
|
89
|
+
non-enumerable `__agentCount` getter.
|
|
90
|
+
- New `getWorkflowCheckpoint(ctx)` helper (mirror of `getWorkflowPhaseState`).
|
|
91
|
+
|
|
92
|
+
**Runner wiring** in `src/runtime/dynamic-workflow-runner.ts`:
|
|
93
|
+
- On run start: `DwfStore.load()` → hydrate ctx (`resumedState`) + emit `dwf.resumed`.
|
|
94
|
+
- `onCheckpoint` → `DwfStore.save()` (best-effort, errors swallowed).
|
|
95
|
+
- On clean completion: `DwfStore.delete()` so a re-run starts fresh.
|
|
96
|
+
|
|
97
|
+
### Resume semantics
|
|
98
|
+
|
|
99
|
+
`team action='resume' runId='X'` re-dispatches with `runKind='dynamic-workflow'`. The
|
|
100
|
+
runner loads the checkpoint, hydrates `ctx.vars`/phases/logs, and re-executes the
|
|
101
|
+
script from the top. Scripts SHOULD be written defensively — check `ctx.vars.lastPhase`
|
|
102
|
+
to skip completed work (documented in `docs/dynamic-workflows.md`). No partial-resume of
|
|
103
|
+
a single agent call (it re-runs from scratch); checkpoints are written AFTER an agent
|
|
104
|
+
completes, never before.
|
|
105
|
+
|
|
106
|
+
### Tests (14 new)
|
|
107
|
+
- `test/unit/dwf-state-store.test.ts` (10): save/load round-trip, missing→undefined,
|
|
108
|
+
delete, corrupt-file resilience, path layout, dir creation, large-state preservation.
|
|
109
|
+
- `test/unit/dynamic-workflow-context.test.ts` (+8): onCheckpoint fires on success/fail,
|
|
110
|
+
agentCount accumulation, backward-compat (no callback), resumedState hydration,
|
|
111
|
+
shallow-copy isolation, getWorkflowCheckpoint snapshot.
|
|
112
|
+
- `test/integration/dwf-setresult.test.ts` (+4): fresh run (no resumed event),
|
|
113
|
+
completed run (checkpoint deleted), resume (hydration + dwf.resumed + delete),
|
|
114
|
+
corrupt checkpoint treated as fresh run.
|
|
115
|
+
|
|
116
|
+
### Docs
|
|
117
|
+
- `docs/dynamic-workflows.md` — new "Resume & Checkpoint (round-18 P2-3)" section +
|
|
118
|
+
defensive-script example.
|
|
119
|
+
- `types/dwf.d.ts` — resume pattern documented in header + `ctx.vars` JSDoc.
|
|
120
|
+
- `package.json` — bumped 1.0.1 → 1.1.0 (minor — new opt-in capability).
|
|
121
|
+
|
|
122
|
+
### Out of scope (future rounds)
|
|
123
|
+
- P2-2 VM sandbox — still waiting for `isolated-vm` v1.5 (vm.createContext is not a
|
|
124
|
+
real security boundary). This was the LAST P2 item.
|
|
125
|
+
|
|
126
|
+
### Process-safety follow-up fix (same release)
|
|
127
|
+
|
|
128
|
+
A heuristic-based zombie "cleanup" had killed a live interactive main `pi`
|
|
129
|
+
session by accident (uptime/RSS/orphan heuristics match a main session just
|
|
130
|
+
as readily as a real orphaned sub-agent). Fixed authoritatively:
|
|
131
|
+
|
|
132
|
+
- **`PI_CREW_KIND=subagent`** env marker, set by `buildPiWorkerArgs`
|
|
133
|
+
(`src/runtime/pi-args.ts`) on every child-pi spawn. A main session does NOT
|
|
134
|
+
carry it, so it can never be matched as a sub-agent. (An earlier draft also
|
|
135
|
+
added a `--crew-subagent` argv flag — removed because pi's strict option
|
|
136
|
+
parser rejects unknown flags and exits non-zero, which silently broke every
|
|
137
|
+
`ctx.agent()` call. The env var alone is the authoritative signal.)
|
|
138
|
+
- **`src/runtime/zombie-scanner.ts`** (new): read-only scanner that matches
|
|
139
|
+
ONLY processes with `PI_CREW_KIND=subagent` AND a dead `PI_CREW_PARENT_PID`.
|
|
140
|
+
Never matches a main session. Never kills.
|
|
141
|
+
- **`team action='doctor' focus='zombies'`**: renders the safe scan as a
|
|
142
|
+
human-readable report (zombies vs live sub-agents, with explicit do-not-kill
|
|
143
|
+
labelling for live parents).
|
|
144
|
+
- **`PI_CREW_KIND`** added to the env allowlist in `child-pi.ts`.
|
|
145
|
+
- **`docs/troubleshooting.md`** + **`.crew/knowledge.md`**: documented the
|
|
146
|
+
marker + the read-only rule so future agents never repeat the mistake.
|
|
147
|
+
- 8 new unit tests in `test/unit/zombie-scanner.test.ts`, including a
|
|
148
|
+
regression test asserting the current (main-session) process is never matched.
|
|
149
|
+
|
|
150
|
+
### Real-world smoke testing findings (2026-06-24)
|
|
151
|
+
|
|
152
|
+
Three bugs were caught by real `team action='run'` smoke tests that the unit
|
|
153
|
+
suite missed (units don't shell out to the real `pi` binary). **All three are
|
|
154
|
+
now fixed.**
|
|
155
|
+
|
|
156
|
+
- **Fixed: `--crew-subagent` argv flag broke every `ctx.agent()` call.** Pi's
|
|
157
|
+
strict option parser exits non-zero on unknown flags. The marker is now the
|
|
158
|
+
`PI_CREW_KIND=subagent` ENV var only.
|
|
159
|
+
- **Fixed: `ctx.agent({schema, systemPrompt})` silently dropped `systemPrompt`.**
|
|
160
|
+
The round-13 schema branch used the resolved role persona as the base for the
|
|
161
|
+
JSON-output instruction, ignoring the caller's explicit persona. Models then
|
|
162
|
+
returned prose and failed schema validation. Fix: `call.systemPrompt` is now
|
|
163
|
+
preferred as the base when both are set.
|
|
164
|
+
- **Fixed: `ctx.agent({disableTools: true, maxTurns: 1})` returned `exit null`.**
|
|
165
|
+
Root cause (found via Phase-0 diagnostic instrumentation) was NOT the
|
|
166
|
+
final-drain race originally hypothesised — it was an erroneous
|
|
167
|
+
`killProcessTree` call in the steer-injection path. When `maxTurns` was
|
|
168
|
+
reached on a `turn_end` event, the code wrote a "wrap up" steer to
|
|
169
|
+
`child.stdin`; Node's `writable.write()` returns `false` on normal
|
|
170
|
+
backpressure, which the code mis-treated as a fatal injection failure and
|
|
171
|
+
killed the worker mid-answer (answer was in stdout; exit came back `null`).
|
|
172
|
+
The `disableTools` correlation was a red herring — the real trigger was
|
|
173
|
+
`maxTurns:1` hitting on the first turn. Fix: steer injection is now
|
|
174
|
+
ADVISORY — a `write() === false` or non-writable stdin is logged, not fatal;
|
|
175
|
+
the hard-abort at `maxTurns + graceTurns` remains the safety net for genuine
|
|
176
|
+
runaways. Verified: maxTurns=1 × 10 real-binary runs now 10/10 exit=0 (was
|
|
177
|
+
~60% fail pre-fix). Regression guard: `test/unit/child-pi-steer-backpressure.test.ts`
|
|
178
|
+
(source-contract checks + opt-in real-binary smoke via `PI_CREW_SMOKE=1`).
|
|
179
|
+
|
|
180
|
+
## [v0.9.7] — round-17 (P2-4 worktree isolation per agent) (2026-06-23)
|
|
181
|
+
|
|
182
|
+
P2-4 feature: `ctx.agent({worktree: true})` spawns the agent in an isolated
|
|
183
|
+
git worktree so parallel file-modifying agents don't conflict. Fully backward
|
|
184
|
+
compatible — `worktree` defaults to false, so existing calls are unchanged.
|
|
185
|
+
|
|
186
|
+
### Implementation
|
|
187
|
+
|
|
188
|
+
**New helpers** in `src/worktree/worktree-manager.ts`:
|
|
189
|
+
- `prepareAgentWorktree(manifest, opts)` — creates a worktree from HEAD on a
|
|
190
|
+
unique branch; returns `{path, branch}` or `undefined` when the cwd is not a
|
|
191
|
+
git repo (graceful fallback).
|
|
192
|
+
- `cleanupAgentWorktree(manifest, worktreePath, branch?)` — removes the
|
|
193
|
+
worktree dir + branch, and captures a git diff as a side artifact when the
|
|
194
|
+
worktree has changes (for audit/merge).
|
|
195
|
+
|
|
196
|
+
**`ctx.agent()` integration** in `src/runtime/dynamic-workflow-context.ts`:
|
|
197
|
+
- New `worktree?: boolean` field on `AgentCallOpts`.
|
|
198
|
+
- When true, the agent runs with the worktree path as its cwd.
|
|
199
|
+
- Cleanup always runs (success, failure, or agent error) — no worktree leaks.
|
|
200
|
+
- Non-git cwd or creation failure → fall back to normal cwd + `ctx.log()`
|
|
201
|
+
warning; the agent still runs.
|
|
202
|
+
|
|
203
|
+
### Why worktrees for DWF
|
|
204
|
+
|
|
205
|
+
DWF scripts commonly fan out parallel agents that each modify files (e.g.
|
|
206
|
+
fixing different modules). Without isolation they race on the working tree.
|
|
207
|
+
`worktree: true` gives each agent its own checkout; the diff is captured as
|
|
208
|
+
an artifact for later merge.
|
|
209
|
+
|
|
210
|
+
### Tests (8 new, 60 total in dynamic-workflow-context.test.ts)
|
|
211
|
+
- prepareAgentWorktree creates an isolated worktree
|
|
212
|
+
- cleanupAgentWorktree removes dir + branch (no leak)
|
|
213
|
+
- cleanupAgentWorktree captures a diff artifact when there are changes
|
|
214
|
+
- prepareAgentWorktree returns undefined for non-git cwd (graceful fallback)
|
|
215
|
+
- ctx.agent({worktree:true}) isolates + cleans up (mock)
|
|
216
|
+
- ctx.agent({worktree:false}) uses normal cwd (backward compat)
|
|
217
|
+
- ctx.agent({worktree:true}) falls back gracefully + warns in non-git cwd
|
|
218
|
+
- ctx.agent({worktree:true}) cleans up even when the agent fails
|
|
219
|
+
|
|
220
|
+
### Docs
|
|
221
|
+
- `docs/dynamic-workflows.md` — worktree option in API table + example
|
|
222
|
+
- `types/dwf.d.ts` — `worktree?: boolean` on AgentCallOpts
|
|
223
|
+
- `package.json` — bumped 1.0.0 → 1.0.1 (patch, additive opt-in)
|
|
224
|
+
|
|
225
|
+
### Out of scope (future rounds)
|
|
226
|
+
- P2-2 VM sandbox — waiting for `isolated-vm` v1.5 (vm.createContext is not
|
|
227
|
+
a real security boundary)
|
|
228
|
+
- P2-3 Resume/checkpoint — round-18 candidate (large effort)
|
|
229
|
+
|
|
230
|
+
## [v0.9.7] — round-16 (P2-1 pipeline primitive) (2026-06-23)
|
|
231
|
+
|
|
232
|
+
Adds **`ctx.pipeline(items, ...stages)`** — a multi-stage transform primitive for
|
|
233
|
+
dynamic workflows. **Backward compatible** — existing DWF scripts are unaffected;
|
|
234
|
+
`pipeline` is a new opt-in capability.
|
|
235
|
+
|
|
236
|
+
### Feature — Pipeline primitive (P2-1)
|
|
237
|
+
|
|
238
|
+
**Files:** `src/runtime/dynamic-workflow-context.ts` + `types/dwf.d.ts` + `docs/dynamic-workflows.md`
|
|
239
|
+
|
|
240
|
+
Previously the DWF context only offered `ctx.fanOut()` (a single parallel map). The
|
|
241
|
+
new `ctx.pipeline()` chains stages: each item flows through **all stages in sequence**
|
|
242
|
+
(stage 1 → stage 2 → …), while **different items run concurrently**, bounded by the
|
|
243
|
+
workflow concurrency (`mapConcurrent`, the same primitive as `fanOut`).
|
|
244
|
+
|
|
245
|
+
Semantics (mirrors `pi-dynamic-workflows`' `pipeline()`):
|
|
246
|
+
|
|
247
|
+
- Each stage receives `(previous, original, index)` — `previous` is the prior stage's
|
|
248
|
+
output (the raw item for the first stage), `original` is the unchanged input item.
|
|
249
|
+
- A failed stage yields `null` for that item, logs `pipeline[i] failed: <msg>` via
|
|
250
|
+
`ctx.log()`, and the other items continue.
|
|
251
|
+
- On **abort**, the error propagates (it is not swallowed into `null`).
|
|
252
|
+
- Returns `(TResult | null)[]`, order-preserving.
|
|
253
|
+
|
|
254
|
+
Signature:
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
ctx.pipeline<TItem, TResult = unknown>(
|
|
258
|
+
items: TItem[],
|
|
259
|
+
...stages: Array<(previous: TResult, original: TItem, index: number) => Promise<TResult> | TResult>
|
|
260
|
+
): Promise<(TResult | null)[]>;
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Implementation notes:
|
|
264
|
+
|
|
265
|
+
- Uses `mapConcurrent(items, concurrency, …)` — NOT unbounded `Promise.all` — so
|
|
266
|
+
item-level parallelism respects the workflow's configured concurrency. Stages that
|
|
267
|
+
spawn agents additionally acquire `ctx.semaphore` for agent-level throttling.
|
|
268
|
+
- Validates inputs: non-array first arg → `TypeError`; non-function stage (or no
|
|
269
|
+
stages) → `TypeError`.
|
|
270
|
+
- Empty items array short-circuits to `[]`.
|
|
271
|
+
- Authoring types (`types/dwf.d.ts`) mirror the runtime signature for IDE IntelliSense.
|
|
272
|
+
|
|
273
|
+
Example use case: scan → analyze → review each shard, up to `concurrency` shards at a
|
|
274
|
+
time, with per-shard failure isolation.
|
|
275
|
+
|
|
276
|
+
Tests: `test/unit/dynamic-workflow-context.test.ts` — single/multi-stage transforms,
|
|
277
|
+
empty array, failed-stage isolation + logging, TypeError on bad inputs, stage-argument
|
|
278
|
+
contract, async stages, and concurrency-bounded execution.
|
|
279
|
+
|
|
280
|
+
## [v0.9.7] — round-15 (P1-4 phase UI) (2026-06-23)
|
|
281
|
+
|
|
282
|
+
The progress pane now **renders DWF phase markers** (▶/✓/⏸) by consuming the
|
|
283
|
+
`dwf.phase_started` / `dwf.phase_completed` events emitted by `ctx.phase()`
|
|
284
|
+
(round-12). **Backward compatible** — non-DWF runs are unaffected.
|
|
285
|
+
|
|
286
|
+
### Feature — Phase UI in progress-pane (P1-4)
|
|
287
|
+
|
|
288
|
+
**Files:** `src/ui/dwf-phase-display.ts` (new) + `progress-pane.ts` +
|
|
289
|
+
`run-snapshot-cache.ts` + `snapshot-types.ts`
|
|
290
|
+
|
|
291
|
+
Previously the phase events were produced but the UI did not consume them.
|
|
292
|
+
Now the progress pane shows a phase overview:
|
|
293
|
+
|
|
294
|
+
- `▶ Phase: <name>` — currently running phase.
|
|
295
|
+
- `✓ Phase: <name>` — completed phase.
|
|
296
|
+
- `⏸ Phase: <name>` — a phase whose completion scrolled out of the recent-event
|
|
297
|
+
window and is not the current one.
|
|
298
|
+
|
|
299
|
+
Implementation details:
|
|
300
|
+
|
|
301
|
+
- New pure-function module `src/ui/dwf-phase-display.ts`:
|
|
302
|
+
`extractDwfPhaseState(events)` derives phase state from the event window
|
|
303
|
+
(returns `null` for non-DWF runs); `renderDwfPhaseLines(state, { ascii })`
|
|
304
|
+
renders markers with Unicode glyphs and ASCII fallbacks (`[>]`/`[v]`/`[ ]`).
|
|
305
|
+
- `RunUiSnapshot` gains an optional `dwfPhaseState` field, computed from the
|
|
306
|
+
existing tailed `recentEvents` window (no extra I/O) in both sync and async
|
|
307
|
+
snapshot builders.
|
|
308
|
+
- `progress-pane.ts` renders the phase lines right after the summary line,
|
|
309
|
+
before the task-based phase grouping. Non-DWF runs produce zero lines.
|
|
310
|
+
- `signatureFor` includes `dwfPhaseState` so cache invalidation reflects phase
|
|
311
|
+
changes.
|
|
312
|
+
- Tests: `test/unit/dwf-phase-display.test.ts` — phase state tracking from an
|
|
313
|
+
event sequence (incl. scrolled-off recovery), correct markers (Unicode +
|
|
314
|
+
ASCII), header gating, and non-DWF snapshots unaffected.
|
|
315
|
+
|
|
316
|
+
## [v0.9.7] — round-14 (P1 DX + observability) (2026-06-23)
|
|
317
|
+
|
|
318
|
+
Four additive P1 features land in this round — **authoring types**, **per-workflow
|
|
319
|
+
token budget**, **log API**, and **typed args**. **Backward compatible** — existing
|
|
320
|
+
DWF scripts continue to work unchanged. New behavior is opt-in.
|
|
321
|
+
|
|
322
|
+
### Feature 1 — Authoring Types / IDE IntelliSense (P1-1)
|
|
323
|
+
|
|
324
|
+
**Files:** `types/dwf.d.ts` (new) + `package.json` (`./workflow` export)
|
|
325
|
+
|
|
326
|
+
A `.dwf.ts` script can now import the `WorkflowCtx` (and supporting) types from
|
|
327
|
+
the package's `./workflow` export for full TypeScript IntelliSense:
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
import type { WorkflowCtx } from "pi-crew/workflow";
|
|
331
|
+
export default async function run(ctx: WorkflowCtx): Promise<void> { /* ... */ }
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
- New file: `types/dwf.d.ts` — named exports mirroring the runtime types
|
|
335
|
+
(`WorkflowCtx`, `AgentCallOpts`, `AgentResult`, `WorkflowBudget`, ...).
|
|
336
|
+
- `package.json` gains `"./workflow": { "types": "./types/dwf.d.ts" }` and ships
|
|
337
|
+
the `types/` directory.
|
|
338
|
+
- New test: `test/unit/dwf-authoring-types.test.ts` — compiles a sample `.dwf.ts`
|
|
339
|
+
against the export (positive + negative `@ts-expect-error` check).
|
|
340
|
+
|
|
341
|
+
### Feature 2 — Per-Workflow Token Budget (P1-2)
|
|
342
|
+
|
|
343
|
+
**Files:** `src/runtime/dynamic-workflow-context.ts` + dispatch wiring
|
|
344
|
+
|
|
345
|
+
`ctx.budget` is a frozen `{total, spent(), remaining()}` surface. When a
|
|
346
|
+
per-workflow token budget is set, `ctx.agent()` auto-rejects with `ok:false`
|
|
347
|
+
(`"workflow token budget exhausted"`) **before** spawning a child worker. `spent()`
|
|
348
|
+
accumulates each run's reported `usage.input + usage.output`.
|
|
349
|
+
|
|
350
|
+
- `total` is `null` (unbounded) by default; `remaining()` is `Infinity` then.
|
|
351
|
+
- New `MakeWorkflowCtxOptions.tokenBudget`, `WorkflowConfig.maxTokenBudget`,
|
|
352
|
+
`RunDynamicWorkflowInput.tokenBudget`, and the `team run` `tokenBudget` param
|
|
353
|
+
(param overrides the workflow value).
|
|
354
|
+
- Budget check + accumulation wired into `ctx.agent()`; budget object passed
|
|
355
|
+
through `run.ts` and `background-runner.ts`.
|
|
356
|
+
- Tests: 7 unit cases (default null, set total, spent/remaining, exhaustion,
|
|
357
|
+
accumulation from the mock's `{input:10,output:5}`, frozen check).
|
|
358
|
+
|
|
359
|
+
### Feature 3 — Log API (P1-3)
|
|
360
|
+
|
|
361
|
+
**Files:** `src/state/contracts.ts` + `src/runtime/dynamic-workflow-context.ts`
|
|
362
|
+
|
|
363
|
+
`ctx.log(message)` appends a workflow-level log line: stringifies non-strings,
|
|
364
|
+
keeps a bounded in-memory copy (capped at **1000**), and always emits a durable
|
|
365
|
+
`dwf.log` event (`{message}`) to the run's `events.jsonl`.
|
|
366
|
+
|
|
367
|
+
- New event type `"dwf.log"` in `TEAM_EVENT_TYPES` (non-terminal).
|
|
368
|
+
- New runner-only `getWorkflowLogs(ctx)` accessor (mirrors `getWorkflowPhaseState`).
|
|
369
|
+
- Tests: 4 unit cases (append, stringify, event emission, 1000-cap) + 1
|
|
370
|
+
integration case (end-to-end through `runDynamicWorkflow`).
|
|
371
|
+
|
|
372
|
+
### Feature 4 — Typed Args (P1-5)
|
|
373
|
+
|
|
374
|
+
**Files:** `src/runtime/dynamic-workflow-context.ts` + `src/state/types.ts` +
|
|
375
|
+
`src/schema/team-tool-schema.ts` + `src/state/state-store.ts` + dispatch wiring
|
|
376
|
+
|
|
377
|
+
`ctx.args<T>()` returns typed workflow arguments (sourced from `manifest.args`,
|
|
378
|
+
passed via the run `args` param). Defaults to `{}` when unset.
|
|
379
|
+
|
|
380
|
+
- New `TeamRunManifest.args`, `MakeWorkflowCtxOptions.args`, `createRunManifest`
|
|
381
|
+
`args` param, and the `team run` `args` schema field (`Type.Unsafe`, any JSON
|
|
382
|
+
value — avoids `any`).
|
|
383
|
+
- The runner reads `manifest.args` and forwards it to `makeWorkflowCtx`.
|
|
384
|
+
- Tests: 3 unit cases (default `{}`, typed object, array) + 1 integration case
|
|
385
|
+
(reads `manifest.args` end-to-end).
|
|
386
|
+
|
|
387
|
+
### Other
|
|
388
|
+
|
|
389
|
+
- Version bumped `0.9.7` → `0.9.8`.
|
|
390
|
+
- `docs/dynamic-workflows.md`: API table rows + Log/Budget/Args/Authoring-types
|
|
391
|
+
sections.
|
|
392
|
+
|
|
393
|
+
## [v0.9.7] — round-13 (P0 AST determinism + structured output + abort cleanup) (2026-06-23)
|
|
394
|
+
|
|
395
|
+
Three P0 features land in this round. **Backward compatible** — existing DWF
|
|
396
|
+
scripts continue to work unchanged. New behavior is opt-in via the `schema`
|
|
397
|
+
field on `ctx.agent()` and an env-var escape hatch for the determinism check.
|
|
398
|
+
|
|
399
|
+
### Feature 1 — AST Determinism Check (P0-2)
|
|
400
|
+
|
|
401
|
+
**File:** `src/runtime/deterministic-ast.ts` (new) +
|
|
402
|
+
`src/runtime/dynamic-workflow-runner.ts` (integration)
|
|
403
|
+
|
|
404
|
+
Dynamic workflow scripts must now be **deterministic**. The runner parses each
|
|
405
|
+
`.dwf.ts` with `acorn` and walks the AST, rejecting `Date.now()`,
|
|
406
|
+
`Math.random()`, and `new Date()` calls before `jiti` executes the script.
|
|
407
|
+
Two runs of the same script against the same inputs now produce the same
|
|
408
|
+
outputs — critical for regression testing and workflow replay.
|
|
409
|
+
|
|
410
|
+
Why AST, not regex: regex matches `Date.now()` everywhere — including string
|
|
411
|
+
literals, comments, and prompt text. AST walking distinguishes **calls** from
|
|
412
|
+
strings, so prompts that say *"avoid `Date.now()` in your code"* still parse
|
|
413
|
+
cleanly. Other `Date.*` and `Math.*` methods (`Date.parse`, `Date.UTC`,
|
|
414
|
+
`Math.floor`, `Math.max`, etc.) are accepted — only `now` and `random` are
|
|
415
|
+
blocked.
|
|
416
|
+
|
|
417
|
+
- New dep: `acorn ^8.14.0` (small, well-maintained; verified Node ≥22 ESM/strip-types compatibility)
|
|
418
|
+
- New file: `src/runtime/deterministic-ast.ts` (determinism walker; MIT-licensed adaptation from pi-dynamic-workflows, attribution in `NOTICE.md`)
|
|
419
|
+
- New file: `test/unit/deterministic-ast.test.ts` (27 cases: accepts/rejects every form, comments, template literals, computed properties, parse-error delegation)
|
|
420
|
+
- New tests in `test/integration/dwf-setresult.test.ts` (5 end-to-end cases including env-var opt-out)
|
|
421
|
+
|
|
422
|
+
**Escape hatch:** `PI_CREW_DWF_SKIP_DETERMINISM_CHECK=1` bypasses the check for
|
|
423
|
+
power users who legitimately need time/random (e.g. randomized benchmark
|
|
424
|
+
scripts). Off by default.
|
|
425
|
+
|
|
426
|
+
### Feature 2 — Structured Output Helper (P0-3)
|
|
427
|
+
|
|
428
|
+
**Files:** `src/runtime/result-extractor.ts` + `src/runtime/dynamic-workflow-context.ts`
|
|
429
|
+
|
|
430
|
+
`AgentCallOpts` gains an optional `schema?: TSchema` field (TypeBox). When set,
|
|
431
|
+
`ctx.agent()` validates the extracted JSON against the schema via
|
|
432
|
+
`@sinclair/typebox`'s `Value.Check`. Mismatch yields
|
|
433
|
+
`{ok: false, error: "structured output does not match schema: ..."}` instead
|
|
434
|
+
of an untyped `structured: { ... }` blob.
|
|
435
|
+
|
|
436
|
+
How the runner helps the model comply:
|
|
437
|
+
|
|
438
|
+
- Appends a JSON-output directive to the prompt.
|
|
439
|
+
- Replaces the agent's system prompt suffix with a "structured-output
|
|
440
|
+
assistant" preamble that describes the schema's shape.
|
|
441
|
+
|
|
442
|
+
When `schema` is **omitted**, behavior is byte-identical to the previous
|
|
443
|
+
regex-based extractor — verified by the existing 30+ test cases plus 9 new
|
|
444
|
+
schema-specific cases in `test/unit/result-extractor.test.ts` and 4 new
|
|
445
|
+
end-to-end cases in `test/unit/dynamic-workflow-context.test.ts`.
|
|
446
|
+
|
|
447
|
+
Caveat: pi-crew DWF spawns `pi` as a subprocess (`runChildPi`), not an
|
|
448
|
+
in-memory `createAgentSession`. Subprocess structured output is captured via
|
|
449
|
+
the same event-stream → JSON-line → schema-check pipeline used for everything
|
|
450
|
+
else, so this round ships Option B (regex-extract + schema validation
|
|
451
|
+
post-hoc). Option A (in-process terminating tool) is planned for round-14.
|
|
452
|
+
|
|
453
|
+
### Feature 3 — Abort Listener Cleanup (P0-5) — NO-OP (already fixed in round 27)
|
|
454
|
+
|
|
455
|
+
Audited `src/runtime/child-pi.ts` for AbortSignal listener leaks. The fix was
|
|
456
|
+
landed in round 27 (BUG 4): both the `onParentAbort` flag handler and the
|
|
457
|
+
`abort` cancellation handler are now removed inside `settle()` regardless of
|
|
458
|
+
the exit path (normal completion, response timeout, hard kill, parent abort,
|
|
459
|
+
forced final drain). On runs with >10 tasks sharing one AbortSignal (the
|
|
460
|
+
common pattern under `background-runner`), this prevents the
|
|
461
|
+
`MaxListenersExceededWarning` and per-task closure capture that previously
|
|
462
|
+
pinned the worker stack frame in memory.
|
|
463
|
+
|
|
464
|
+
No code changes needed in round 13. Documented for the audit trail.
|
|
465
|
+
|
|
466
|
+
### Verification
|
|
467
|
+
|
|
468
|
+
- `npm run typecheck` — clean
|
|
469
|
+
- `npx tsc --noEmit` — clean
|
|
470
|
+
- 31 new unit tests across `deterministic-ast.test.ts` (27) and 9 schema-validation cases in `result-extractor.test.ts` and 4 new cases in `dynamic-workflow-context.test.ts` — all pass
|
|
471
|
+
- 5 new integration tests in `dwf-setresult.test.ts` — all pass
|
|
472
|
+
- 0 regressions in the existing 4 round-12 tests
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
## [v0.9.7] — round-12: DWF phases + structured-clone guard (2026-06-23)
|
|
476
|
+
|
|
477
|
+
Two additive P0 features for dynamic-workflow (DWF) scripts, both fully
|
|
478
|
+
backward-compatible (existing scripts continue to work unchanged). Researched
|
|
479
|
+
and adopted from the public `pi-dynamic-workflows` (Michaelliv/v1.0.1)
|
|
480
|
+
package — full comparison and adoption plan in
|
|
481
|
+
`.crew/artifacts/team_20260623095016_b693d3f967f88048/shared/06_synthesize.md`.
|
|
482
|
+
|
|
483
|
+
### Feature 1: `ctx.phase(title)` runtime phase API (P0-1)
|
|
484
|
+
|
|
485
|
+
`WorkflowCtx` gains a new `phase(title: string): void` method. The orphan
|
|
486
|
+
`dwf.phase_started` / `dwf.phase_completed` event types — declared in
|
|
487
|
+
`src/state/contracts.ts:89-93` since v0.9.0 but never produced by any
|
|
488
|
+
producer — finally have a producer. Use cases:
|
|
489
|
+
|
|
490
|
+
- Group `ctx.agent()` calls under logical phases (e.g. "Scan", "Audit",
|
|
491
|
+
"Review") so downstream UI and log readers can group by phase.
|
|
492
|
+
- Emit a clear phase boundary to the run's `events.jsonl` without writing
|
|
493
|
+
custom event-log code.
|
|
494
|
+
- Drive live progress reporting from the script itself.
|
|
495
|
+
|
|
496
|
+
Semantics:
|
|
497
|
+
|
|
498
|
+
- Validates `title` is a non-empty string (throws `TypeError` otherwise).
|
|
499
|
+
- Idempotent: calling `ctx.phase("Scan")` twice does not emit a duplicate
|
|
500
|
+
event or change state.
|
|
501
|
+
- When a previous phase is still open, emits `dwf.phase_completed` for it
|
|
502
|
+
**before** emitting `dwf.phase_started` for the new one (consumers never
|
|
503
|
+
see two open phases at once).
|
|
504
|
+
- The in-memory `phases[]` list (read-only via `getWorkflowPhaseState`,
|
|
505
|
+
mirrors the `__finalResult` non-enumerable getter pattern) is deduped and
|
|
506
|
+
capped at **100 distinct titles** to bound memory. Events still flow
|
|
507
|
+
past the cap — the events log is the durable source of truth.
|
|
508
|
+
- The runner **auto-closes the last open phase** before emitting
|
|
509
|
+
`dwf.completed`, so a script that ends mid-phase still produces a
|
|
510
|
+
well-formed event sequence.
|
|
511
|
+
|
|
512
|
+
**Files changed:**
|
|
513
|
+
- `src/runtime/dynamic-workflow-context.ts` — interface, implementation,
|
|
514
|
+
`__phaseState` getter, `getWorkflowPhaseState` helper
|
|
515
|
+
- `src/runtime/dynamic-workflow-runner.ts` — auto-close on completion
|
|
516
|
+
|
|
517
|
+
### Feature 2: structured-clone guard at the runner boundary (P0-4)
|
|
518
|
+
|
|
519
|
+
Defensive `assertStructuredCloneable(value, name)` helper applied to the
|
|
520
|
+
final artifact content and `manifest.summary` before they reach
|
|
521
|
+
`writeArtifact` and the run-event-bus emitter. Today this is mostly
|
|
522
|
+
future-proofing (the artifact file is read as a string, and strings are
|
|
523
|
+
always structured-cloneable), but the guard surfaces a clear, actionable
|
|
524
|
+
error pointing at the most common cause — forgetting `await` on
|
|
525
|
+
`ctx.agent()` / `ctx.review()` — instead of letting a cryptic
|
|
526
|
+
`DataCloneError` leak from deep inside the artifact store.
|
|
527
|
+
|
|
528
|
+
**Files changed:**
|
|
529
|
+
- `src/runtime/dynamic-workflow-runner.ts` — `assertStructuredCloneable`
|
|
530
|
+
helper, applied to `finalText` and `summaryText` (slice)
|
|
531
|
+
|
|
532
|
+
### Tests
|
|
533
|
+
|
|
534
|
+
- 7 new unit tests in `test/unit/dynamic-workflow-context.test.ts`
|
|
535
|
+
(emission, idempotency, validation, sequence, helper, dedup, 100-cap).
|
|
536
|
+
- 1 new integration test in `test/integration/dwf-setresult.test.ts`
|
|
537
|
+
(end-to-end phase event sequence, including runner auto-close).
|
|
538
|
+
- All 23 existing DWF unit tests still pass; both pre-existing integration
|
|
539
|
+
tests still pass.
|
|
540
|
+
|
|
541
|
+
### Docs
|
|
542
|
+
|
|
543
|
+
- `docs/dynamic-workflows.md` — updated WorkflowCtx example to use
|
|
544
|
+
`ctx.phase("Scan")` / `ctx.phase("Audit")`; added a `ctx.phase` row to
|
|
545
|
+
the API table; added a "Phases (round-12)" subsection explaining
|
|
546
|
+
semantics, idempotency, and the 100-cap.
|
|
547
|
+
|
|
548
|
+
### Out of scope (planned for future rounds)
|
|
549
|
+
|
|
550
|
+
- AST determinism check (P0-2)
|
|
551
|
+
- Structured output helper (P0-3)
|
|
552
|
+
- Abort listener cleanup pattern (P0-5)
|
|
553
|
+
- Authoring types / IDE IntelliSense (P1-1)
|
|
554
|
+
- Token budget (P1-2)
|
|
555
|
+
- Phase UI in `progress-pane` (P1-4)
|
|
556
|
+
- Pipeline primitive (P2-1)
|
|
557
|
+
- `isolated-vm` sandbox (P2-2, planned for v1.5)
|
|
558
|
+
|
|
3
559
|
## [v0.9.5] — fix "team run hangs forever at 25%" (2026-06-23)
|
|
4
560
|
|
|
5
561
|
Two coupled runtime bugs caused the recurring "run stuck at 25% (1/4)" failure
|
package/README.md
CHANGED
|
@@ -39,9 +39,9 @@ npm: pi-crew
|
|
|
39
39
|
repo: https://github.com/baphuongna/pi-crew
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
**v0.9.4 / v0.9.5**: See [CHANGELOG.md](CHANGELOG.md).
|
|
42
|
+
**v0.9.4 / v0.9.5 / v0.9.8**: See [CHANGELOG.md](CHANGELOG.md).
|
|
43
43
|
|
|
44
|
-
### Highlights (v0.6.4 → v0.9.
|
|
44
|
+
### Highlights (v0.6.4 → v0.9.8)
|
|
45
45
|
|
|
46
46
|
A long arc of **trust, cliff-resilience, and robustness** work. Principle: *build
|
|
47
47
|
trust and cliff-resilience, stay lean, delete before adding.*
|
|
@@ -197,7 +197,10 @@ background-dispatch discriminator.
|
|
|
197
197
|
- **Plugin system** — framework-aware context injection (Next.js, Vite, Vitest) via plugin registry
|
|
198
198
|
- **Health scoring** — penalty-based run health with time-series snapshots
|
|
199
199
|
- **Autonomous goal loops** (P0/P1) — `team action='goal'` runs an autonomous multi-turn loop: a worker does a turn, a separate LLM judge evaluates the transcript+evidence against the goal, and on "not-achieved" the reason is fed into the next turn's prompt. Stops on achieved / maxTurns / budget / blocked. Claude-Code-style `/goal`. See `docs/goals.md`.
|
|
200
|
-
- **Dynamic workflows** (P2/P3) — author orchestration as a `.dwf.ts` script (JS loops/branch/cross-review) instead of a static step list. The script runs in the background, calls subagents via `ctx.agent()`/`ctx.fanOut()`, holds intermediate results in JS variables, and only `ctx.setResult()` reaches the main context. `workflow-create`/`-delete`/`-save` require `confirm:true` at the tool-call layer (the only gate — a malicious agent that passes `confirm:true` programmatically bypasses it; this is postinstall-equivalent trust, not a human-in-the-loop dialog). See `docs/dynamic-workflows.md`.
|
|
200
|
+
- **Dynamic workflows** (P2/P3) — author orchestration as a `.dwf.ts` script (JS loops/branch/cross-review) instead of a static step list. The script runs in the background, calls subagents via `ctx.agent()`/`ctx.fanOut()`, holds intermediate results in JS variables, and only `ctx.setResult()` reaches the main context. `ctx.phase()` marks logical phases; **round-14** adds `ctx.log()` (durable `dwf.log` events), `ctx.budget` (per-workflow token budget that auto-rejects `ctx.agent()` when exhausted), and `ctx.args<T>()` (typed workflow arguments). TypeScript IntelliSense is available via `import type { WorkflowCtx } from "pi-crew/workflow"`. `workflow-create`/`-delete`/`-save` require `confirm:true` at the tool-call layer (the only gate — a malicious agent that passes `confirm:true` programmatically bypasses it; this is postinstall-equivalent trust, not a human-in-the-loop dialog). See `docs/dynamic-workflows.md`.
|
|
201
|
+
- **Strict SKILL.md validation** (L3, v0.9.8) — skills with malformed frontmatter (missing/malformed `name`/`description`, type mismatches) now **fail-fast at discovery** with visible diagnostics, instead of silently producing broken behavior at runtime. HYBRID policy: HARD on required fields, SOFT (warn) on unknown props for forward-compat. Surfaced via `buildSkillValidationDiagnostics()`.
|
|
202
|
+
- **Durable event replay** (L1, v0.9.8) — `RunEventBus.onWithReplay()` catches up a re-subscribing dashboard/overlay with events it missed during transient absence (toggle, reconnect), replaying from the durable JSONL log with seq-based dedup. No information loss even if the live subscriber was briefly gone.
|
|
203
|
+
- **Lossless-by-default output handling** (L4, v0.9.8) — worker output thresholds sized from measured data (100% of real outputs fit without compaction); when compaction is unavoidable it keeps head+tail (preserves closing code fences/headings) instead of head-only truncation. No more `[pi-crew compacted N chars]` markers eating the end of a worker's result.
|
|
201
204
|
|
|
202
205
|
---
|
|
203
206
|
|
|
@@ -468,6 +471,10 @@ pi-crew survives Pi's context compaction. When the context is compacted (auto or
|
|
|
468
471
|
Context compacted. 1 pi-crew run(s) still in-flight — use team status to continue.
|
|
469
472
|
```
|
|
470
473
|
|
|
474
|
+
**Durable event replay** (v0.9.8, L1): even if a dashboard/overlay is briefly gone during compaction or a reconnect, `RunEventBus.onWithReplay()` catches it up with the events it missed, replaying from the durable JSONL log with seq-based dedup — no information loss. (The dashboard wires this up per-run; the primitive is available for any subscriber.)
|
|
475
|
+
|
|
476
|
+
**Lossless-by-default worker output** (v0.9.8, L4): output-handling thresholds are sized from measured real data (100% of real worker outputs fit without any compaction). When compaction *is* unavoidable, it keeps head+tail instead of head-only truncation, so closing code fences and headings survive — no more `[pi-crew compacted N chars]` markers eating the end of a result.
|
|
477
|
+
|
|
471
478
|
### Plan-level human-in-the-loop (HITL)
|
|
472
479
|
|
|
473
480
|
Set `runtime.requirePlanApproval = true` to gate **any workflow** at the plan→execute boundary. After the read-only (planning) phases complete, the run pauses for explicit approval before mutating tasks run:
|