pi-crew 0.9.14 → 0.9.16
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 +126 -0
- package/README.md +66 -0
- package/package.json +1 -1
- package/src/config/types.ts +5 -0
- package/src/extension/registration/team-tool.ts +145 -35
- package/src/extension/team-tool/run.ts +646 -150
- package/src/runtime/crew-agent-records.ts +55 -0
- package/src/runtime/subagent-manager.ts +42 -0
- package/src/runtime/team-runner.ts +1445 -318
- package/src/workflows/discover-workflows.ts +144 -29
- package/src/workflows/preflight-validator.ts +195 -0
- package/src/workflows/topology-analyzer.ts +219 -0
- package/src/workflows/workflow-config.ts +11 -0
- package/workflows/chain.workflow.md +6 -0
- package/workflows/default.workflow.md +1 -0
- package/workflows/fast-fix.workflow.md +1 -0
- package/workflows/implementation.workflow.md +1 -0
- package/workflows/parallel-research.workflow.md +1 -0
- package/workflows/pipeline.workflow.md +1 -0
- package/workflows/research.workflow.md +1 -0
- package/workflows/review.workflow.md +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,131 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [v0.9.16] — cancel wipes the trace (2026-06-29)
|
|
4
|
+
|
|
5
|
+
User-driven refinement of the cancellation policy. Previously, cancelled / stopped subagents and crew-agent records were preserved on disk (`agent_*.json` files in `.crew/state/subagents/`, plus `agents.json` + per-task `status.json` in the run state directory) and continued to appear in the UI dashboard / widget / transcript viewer. The user explicitly asked: *"một khi đã cancel thì phải xóa luôn dấu vết về subagent đó chứ để lại làm gì nữa"* — once cancelled, the trace must be wiped. This release implements that policy.
|
|
6
|
+
|
|
7
|
+
### Changed — terminal-status deletion
|
|
8
|
+
|
|
9
|
+
- **Subagent records** (`.crew/state/subagents/<id>.json`) — added `removePersistedSubagentRecord(cwd, id)` in `src/runtime/subagent-manager.ts`. On terminal status `cancelled` / `stopped` / `terminated:true`, the persisted file is **deleted immediately** (no linger). The file is saved first so any concurrent reader sees the final status, then `unlink`'d within microseconds — the next widget/dashboard tick sees no entry.
|
|
10
|
+
|
|
11
|
+
- **Crew-agent records** (per-run `agents.json` index + per-task `status.json`) — added `removeCrewAgent(manifest, taskId)` in `src/runtime/crew-agent-records.ts`. `upsertCrewAgent()` now checks `shouldDeleteCrewAgentOnTerminalStatus(record)` and dispatches to `removeCrewAgent` instead of `saveCrewAgents` for `cancelled` / `stopped` records. Both the index entry AND the per-task status.json are wiped.
|
|
12
|
+
|
|
13
|
+
### What is preserved vs wiped
|
|
14
|
+
|
|
15
|
+
| Status | Persisted? | Rationale |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| `completed` | ✅ yes | Successful work — audit value |
|
|
18
|
+
| `failed` | ✅ yes | Agent errored — debugging value |
|
|
19
|
+
| `error` | ✅ yes | Unexpected exception — debugging value |
|
|
20
|
+
| `cancelled` | ❌ **wiped** | User-initiated cancellation — no trace |
|
|
21
|
+
| `stopped` | ❌ **wiped** | External abort — no trace |
|
|
22
|
+
| `terminated: true` (any status) | ❌ **wiped** | Any termination event — no trace |
|
|
23
|
+
|
|
24
|
+
### In-memory handles
|
|
25
|
+
|
|
26
|
+
`terminateLiveAgent` in `src/runtime/live-agent-manager.ts:169` already deletes from the `liveAgents` Map immediately after abort — no change needed. The `evictStaleLiveAgentHandles` fallback (10-min linger for orphan terminal handles) remains as a safety net.
|
|
27
|
+
|
|
28
|
+
### New helper predicates
|
|
29
|
+
|
|
30
|
+
- `shouldDeleteOnTerminalStatus(record)` — for `.crew/state/subagents/` records
|
|
31
|
+
- `shouldDeleteCrewAgentOnTerminalStatus(record)` — for per-run `agents.json` records
|
|
32
|
+
|
|
33
|
+
Both exported for downstream callers and tests.
|
|
34
|
+
|
|
35
|
+
### Tests
|
|
36
|
+
|
|
37
|
+
- **NEW `test/unit/cancellation-trace-wipe.test.ts`** — 15 cases covering:
|
|
38
|
+
- `shouldDeleteOnTerminalStatus` / `shouldDeleteCrewAgentOnTerminalStatus` predicates for all 6 statuses
|
|
39
|
+
- `removePersistedSubagentRecord` happy path + ENOENT safe-fail
|
|
40
|
+
- `removeCrewAgent` removes from `agents.json` index AND per-task `status.json`
|
|
41
|
+
- `upsertCrewAgent` integration: cancelled → wiped, completed/failed → preserved
|
|
42
|
+
|
|
43
|
+
- **Existing tests still pass** — 118/118 across 9 test files (subagent-manager-cov, subagent-tools-integration, live-manager-cov, direct-agent-run, team-runner-merge, process-lifecycle, preflight-validator, topology-analyzer, cancellation-trace-wipe). 0 regressions.
|
|
44
|
+
|
|
45
|
+
### Verification
|
|
46
|
+
|
|
47
|
+
- `tsc --noEmit` → EXIT 0
|
|
48
|
+
- `node scripts/check-lazy-imports.mjs` → "All dynamic imports have `// LAZY:` marker"
|
|
49
|
+
- `npm test` (focused 9-file suite) → 118/118 pass
|
|
50
|
+
|
|
51
|
+
### Honesty discipline
|
|
52
|
+
|
|
53
|
+
- **Failed subagents are NOT wiped** — they may carry useful debugging info (error stack, partial output). Different from cancel. If the user later wants failed→wipe too, it's a 1-line predicate change.
|
|
54
|
+
- **No tombstone** — the cancellation event is in `events.jsonl` (the run's audit log) and `notifications/`. Disk-level agent files are intentionally gone after cancel.
|
|
55
|
+
- **Filesystem race window** is microseconds (save then unlink); under heavy concurrent access a reader might briefly see the final status before the file disappears. Acceptable trade-off for the "no trace" policy.
|
|
56
|
+
|
|
57
|
+
### Files touched
|
|
58
|
+
|
|
59
|
+
| File | Change |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `src/runtime/subagent-manager.ts` | +2 functions (`removePersistedSubagentRecord`, `shouldDeleteOnTerminalStatus`); modified `markStopped` + `runSubagent` finally block to wipe on cancel |
|
|
62
|
+
| `src/runtime/crew-agent-records.ts` | +2 functions (`removeCrewAgent`, `shouldDeleteCrewAgentOnTerminalStatus`); modified `upsertCrewAgent` to dispatch to remove on cancel |
|
|
63
|
+
| `test/unit/cancellation-trace-wipe.test.ts` | NEW (15 cases, 8.3 KB) |
|
|
64
|
+
| `CHANGELOG.md` | this entry |
|
|
65
|
+
| `package.json` | 0.9.15 → 0.9.16 |
|
|
66
|
+
|
|
67
|
+
## [v0.9.15] — workflow topology advisory (2026-06-29)
|
|
68
|
+
|
|
69
|
+
After a parallel-research assessment of v0.9.13 + v0.9.14 (`research-findings/pi-crew-performance-quality-assessment.md`, effectiveness evaluation at `.crew/research/pi-crew-effectiveness-evaluation.md`), a user-driven refinement showed that the original BLOCK-on-misuse design was too aggressive — **agents know their context better than the orchestrator**. This release ships an **advisory-only** topology classifier that prints measured-cost notes and proceeds either way. The agent (caller) decides.
|
|
70
|
+
|
|
71
|
+
Full rationale: `.crew/research/pi-crew-effectiveness-evaluation.md` §4.4 + the run-history evidence showing `fast-fix` (3-step sequential DAG) measured **5.7× slower and 1.9× costlier** than 3 raw `Agent` calls (`team_20260629092440_6b8538e31ba7616a`).
|
|
72
|
+
|
|
73
|
+
### Added — workflow topology advisory
|
|
74
|
+
|
|
75
|
+
- **New `src/workflows/topology-analyzer.ts`** — pure classifier that parses a `WorkflowConfig`, builds the DAG (Kahn-style longest-path depth), detects `parallelGroup:` declarations, and returns `{topology, stepCount, parallelGroupCount, fanOutDegree, dagDepth, recommendation, reason}`. Topologies: `single` (1 step, no concurrency), `sequential` (linear chain, no parallelGroup), `concurrent` (≥3 truly parallel agents via `parallelGroup`), `complex-dag` (4+ steps with branching — `≥2 deps` on ≥1 node), `dynamic` (`.dwf.ts` script). Honors explicit `topology:` frontmatter override when present.
|
|
76
|
+
|
|
77
|
+
- **New `src/workflows/preflight-validator.ts`** — applies the topology rule from `.crew/knowledge.md` "pi-crew USAGE THRESHOLD RULE" (CONVENTIONS section). Returns `{level: "info" | "note" | "warn", message, suggestion, topology, stepCount, recommendation}`. **Never blocks.** Three severity levels:
|
|
78
|
+
- `info` — context-only (dynamic workflow, `force:true` acknowledged)
|
|
79
|
+
- `note` — validated use case (concurrent / complex-dag); "✅ proceeding"
|
|
80
|
+
- `warn` — potential inefficiency (single / sequential 2-3 / 4+); measured cost evidence + "Proceeding anyway"
|
|
81
|
+
|
|
82
|
+
- **Top-level integration** (`src/extension/team-tool/run.ts`, called BEFORE `executeTeamRun`):
|
|
83
|
+
- Extension-layer guard logs the advisory via `console.warn` with icon (`⚠️ ` / `✅ ` / `ℹ️ `) and continues the run.
|
|
84
|
+
- Defense-in-depth in `src/runtime/team-runner.ts:447-468` also logs (catches direct API callers — CLI, tests, scheduler — that bypass the extension layer).
|
|
85
|
+
- Both layers **never throw / never short-circuit**. The agent is always in charge.
|
|
86
|
+
|
|
87
|
+
### Changed — agent awareness surfaces
|
|
88
|
+
|
|
89
|
+
- **`team` tool description** (`src/extension/registration/team-tool.ts`) now includes an explicit "ℹ️ ADVISORY NOTE (preflight, never blocks)" paragraph + 4 case-by-case notes. Agents reading the tool definition see the rule BEFORE deciding to call.
|
|
90
|
+
- **`team` prompt snippet** shortened and re-toned: "Use the team tool for multi-agent orchestration when you need ≥3 concurrent agents or a complex DAG. For single tasks or 2–3 sequential steps, the raw Agent tool is usually faster. **pi-crew notes the topology (informational only) but proceeds either way — you decide.**"
|
|
91
|
+
- **Workflow YAML frontmatter** — 8 builtin workflows now declare `topology:` (`single` / `sequential` / `concurrent` / `complex-dag` / `dynamic`) so the analyzer honors explicit classification when present and falls back to auto-detection when absent. New `WorkflowConfig.topology?` field in `src/workflows/workflow-config.ts`. `src/workflows/discover-workflows.ts:parseWorkflowFile` parses the field; invalid values are silently dropped (fall-through to auto).
|
|
92
|
+
|
|
93
|
+
### Tests
|
|
94
|
+
|
|
95
|
+
- **`test/unit/topology-analyzer.test.ts`** — 13 cases: each topology + `parallelGroupsFromSteps`, `fanOutDegreeFromSteps`, `dagDepthFromSteps` helpers + edge cases (empty deps, unknown deps, defensive cycle handling).
|
|
96
|
+
- **`test/unit/preflight-validator.test.ts`** — 11 cases: each topology × level combo, force-bypass acknowledgement, all-3-severities-reachable check, "validator never throws" contract.
|
|
97
|
+
- `test/unit/direct-agent-run.test.ts` + `test/unit/team-runner-merge.test.ts` — re-verified to ensure defense-in-depth guard does not break synthetic-1-step direct-agent runs (skip condition: `workflow.filePath === "<generated>"`).
|
|
98
|
+
|
|
99
|
+
### Verification
|
|
100
|
+
|
|
101
|
+
- `tsc --noEmit` → EXIT 0 (no type errors)
|
|
102
|
+
- `test:unit` → 5800+ tests, 0 fail attributable to this change (1 pre-existing env-related fail on `resolveNpmGlobalRoot` is unrelated and pre-dates this release)
|
|
103
|
+
- Live smoke test (`/tmp/preflight-smoke.ts`): all 6 topology cases emit the expected advisory level — `single` → WARN, `sequential 2/3/4` → WARN with measured-cost evidence, `concurrent` → NOTE, `force:true` → INFO
|
|
104
|
+
- Live integration test (`team_20260629152312_ca139c8540e938a1`): ran a 3-step sequential `fast-fix` workflow; advisory note appeared in console, workflow completed normally (no block), agent independently verified the rule via source + smoke test + unit tests
|
|
105
|
+
|
|
106
|
+
### Honesty discipline
|
|
107
|
+
|
|
108
|
+
- **No BLOCK** — the original v0.9.15 design had `level: "block"` that hard-rejected single-task runs; user feedback refined this to advisory-only after observing that the orchestrator doesn't know the agent's full context (audit needs, team coordination reasons, etc.). The agent is in charge.
|
|
109
|
+
- **`force:true`** parameter still works (acknowledged as `info` level) but is no longer required — runs always proceed.
|
|
110
|
+
|
|
111
|
+
### Files touched
|
|
112
|
+
|
|
113
|
+
| File | Change |
|
|
114
|
+
|---|---|
|
|
115
|
+
| `src/workflows/topology-analyzer.ts` | NEW (182 LOC) |
|
|
116
|
+
| `src/workflows/preflight-validator.ts` | NEW (177 LOC) |
|
|
117
|
+
| `src/workflows/workflow-config.ts` | +8 LOC (topology? field) |
|
|
118
|
+
| `src/workflows/discover-workflows.ts` | +12 LOC (parse frontmatter.topology + parseTopology helper) |
|
|
119
|
+
| `src/extension/team-tool/run.ts` | +18 LOC (extension-layer advisory log) |
|
|
120
|
+
| `src/runtime/team-runner.ts` | +18 LOC (defense-in-depth advisory log) |
|
|
121
|
+
| `src/extension/registration/team-tool.ts` | ~30 LOC (tool description + prompt snippet re-toned) |
|
|
122
|
+
| `src/config/types.ts` | +5 LOC (CrewReliabilityConfig.forcePreflight? field — reserved, currently unused) |
|
|
123
|
+
| `workflows/*.workflow.md` (8 files) | +1 line each (topology: frontmatter) |
|
|
124
|
+
| `test/unit/topology-analyzer.test.ts` | NEW (181 LOC, 13 cases) |
|
|
125
|
+
| `test/unit/preflight-validator.test.ts` | NEW (163 LOC, 11 cases) |
|
|
126
|
+
| `README.md` | +75 LOC ("Workflow topology advisory" section) |
|
|
127
|
+
| `CHANGELOG.md` | this entry |
|
|
128
|
+
|
|
3
129
|
## [v0.9.14] — reliability & UX fixes from the v0.9.13 performance/quality assessment (2026-06-29)
|
|
4
130
|
|
|
5
131
|
A parallel-research assessment of v0.9.13 measured three operational gaps and produced ten prioritized recommendations (effort × impact). This release ships **8 of 10 fixes + 2 UX bug fixes from bug reports**, with the remaining 2 honestly deferred (need product input). The unifying theme: **stop the silent lies** — retry that was built but never enabled, a "completed" status that hid zero work, and UI that read failed runs as still-running.
|
package/README.md
CHANGED
|
@@ -43,6 +43,7 @@ repo: https://github.com/baphuongna/pi-crew
|
|
|
43
43
|
|
|
44
44
|
## Features
|
|
45
45
|
|
|
46
|
+
- **Workflow topology advisory** (v0.9.15) — before each run, pi-crew classifies the workflow's shape (`single` / `sequential` / `concurrent` / `complex-dag`) and prints an **advisory note** with measured cost evidence (e.g. "3-step sequential: measured 5.7× slower than 3 raw Agent calls — proceeding anyway"). Never blocks — the agent decides. Tool description and prompt-snippet carry the same guidance up-front, so agents know the trade-off before calling. New files: `src/workflows/topology-analyzer.ts`, `src/workflows/preflight-validator.ts`. See [Workflow topology advisory](#workflow-topology-advisory) below.
|
|
46
47
|
- **One Pi tool** — `team` handles routing, planning, execution, review, and cleanup
|
|
47
48
|
- **Autonomous delegation** — policy injection decides when/how to delegate based on task complexity
|
|
48
49
|
- **needs_attention status** — tasks that complete without calling `submit_result` get `needs_attention` (terminal) instead of `completed`; allows retry/re-run without blocking downstream phases
|
|
@@ -213,6 +214,71 @@ When unsure which team/workflow fits:
|
|
|
213
214
|
| `research` | explore → analyze → write | Research and documentation |
|
|
214
215
|
| `parallel-research` | Parallel shards → synthesize → write | Multi-source research |
|
|
215
216
|
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Workflow topology advisory
|
|
220
|
+
|
|
221
|
+
Before every `team action='run'`, pi-crew classifies the workflow shape and prints an informational note. **It never blocks** — agents decide whether to proceed, refactor, or override.
|
|
222
|
+
|
|
223
|
+
### How it works
|
|
224
|
+
|
|
225
|
+
```text
|
|
226
|
+
team action='run', workflow='fast-fix', goal='...'
|
|
227
|
+
↓
|
|
228
|
+
pi-crew analyzes topology: 3-step sequential
|
|
229
|
+
↓
|
|
230
|
+
⚠️ [team-tool.preflight] WARN: 3-step sequential chain: measured 5.7× slower
|
|
231
|
+
and 1.9× costlier than 3 raw Agent calls (Run #3 in .crew/state/runs/).
|
|
232
|
+
Proceeding anyway.
|
|
233
|
+
↓
|
|
234
|
+
Workflow runs to completion. Agent sees the note, decides for next time.
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Topology → advisory level
|
|
238
|
+
|
|
239
|
+
| Topology | When | Level | What pi-crew prints |
|
|
240
|
+
|---|---|---|---|
|
|
241
|
+
| `single` | 1 step, no concurrency | `warn` | "raw Agent tool would be ~30× faster and ~5× cheaper. Proceeding anyway." |
|
|
242
|
+
| `sequential` (2-3 steps) | Linear chain, no fan-out | `warn` | "measured 5.7× slower than raw Agent calls. Proceeding anyway." |
|
|
243
|
+
| `sequential` (4+ steps) | Linear chain, longer | `warn` | "audit trail may justify pi-crew overhead. Proceeding anyway." |
|
|
244
|
+
| `concurrent` | ≥3 truly parallel agents (parallelGroup) | `note` | "✅ Validated use case: N-way parallel fan-out. pi-crew's parallelism wins." |
|
|
245
|
+
| `complex-dag` | 4+ steps with data dependencies | `note` | "✅ Validated use case: complex DAG with adaptive plan." |
|
|
246
|
+
| `dynamic` | `.dwf.ts` script | `info` | "Runtime decides topology." |
|
|
247
|
+
|
|
248
|
+
### When to prefer raw `Agent` over `team`
|
|
249
|
+
|
|
250
|
+
Use the raw `Agent` tool when:
|
|
251
|
+
- You have a single task or quick question (1-step)
|
|
252
|
+
- You have 2–3 sequential independent steps (no DAG branching, no concurrency)
|
|
253
|
+
|
|
254
|
+
Use `team` when:
|
|
255
|
+
- You have ≥3 agents running TRULY CONCURRENTLY (`parallelGroup`)
|
|
256
|
+
- You have a COMPLEX DAG (4+ steps with data dependencies, branching)
|
|
257
|
+
- You need an audit trail, team coordination, or worktree isolation that justifies pi-crew's overhead
|
|
258
|
+
|
|
259
|
+
### How agents learn the rule
|
|
260
|
+
|
|
261
|
+
The guidance is available in three places agents see:
|
|
262
|
+
|
|
263
|
+
1. **`team` tool description** — the LLM reads this when considering whether to call the tool. Includes an explicit "ℹ️ ADVISORY NOTE (preflight, never blocks)" section.
|
|
264
|
+
2. **`team` prompt snippet** — rendered in agent context when the tool is relevant. Single-line summary of the rule.
|
|
265
|
+
3. **`.crew/knowledge.md` CONVENTIONS section** — always injected into every worker session's context. Contains the full 4-question self-check.
|
|
266
|
+
|
|
267
|
+
### How to silence the advisory
|
|
268
|
+
|
|
269
|
+
The advisory is **informational only** — there is no `force:true` flag needed (the run proceeds regardless). If you want to silence the `console.warn` output for cleaner logs, set `PI_CREW_QUIET_PREFLIGHT=1` in your environment.
|
|
270
|
+
|
|
271
|
+
### Implementation
|
|
272
|
+
|
|
273
|
+
- `src/workflows/topology-analyzer.ts` — pure classifier (parses workflow YAML, builds DAG, detects parallelGroups)
|
|
274
|
+
- `src/workflows/preflight-validator.ts` — returns `{level: info|note|warn, message, suggestion}` (never throws)
|
|
275
|
+
- Integration: `src/extension/team-tool/run.ts` (extension layer, prints advisory) + `src/runtime/team-runner.ts` (defense-in-depth, also logs)
|
|
276
|
+
|
|
277
|
+
### Tests
|
|
278
|
+
|
|
279
|
+
- `test/unit/topology-analyzer.test.ts` — 13 cases (each topology + edge cases)
|
|
280
|
+
- `test/unit/preflight-validator.test.ts` — 11 cases (each level + advisory contract)
|
|
281
|
+
|
|
216
282
|
## Builtin Agents
|
|
217
283
|
|
|
218
284
|
```
|
package/package.json
CHANGED
package/src/config/types.ts
CHANGED
|
@@ -188,6 +188,11 @@ export interface CrewReliabilityConfig {
|
|
|
188
188
|
autoRepairIntervalMs?: number;
|
|
189
189
|
/** Remove /tmp/pi-crew-* directories after their orphaned runs are reconciled. Default: true. */
|
|
190
190
|
cleanupOrphanedTempDirs?: boolean;
|
|
191
|
+
/** Bypass the preflight topology validator (workflow threshold rule, .crew/knowledge.md
|
|
192
|
+
* 'pi-crew USAGE THRESHOLD RULE'). When true, runs that would be BLOCKED or WARNED
|
|
193
|
+
* proceed without intervention. Audit-trail the override via events.jsonl.
|
|
194
|
+
* Default: false (enforce). Use only for legitimate audit/debug sessions. */
|
|
195
|
+
forcePreflight?: boolean;
|
|
191
196
|
/** Inject a compact ambient crew-status note into the agent's context on every LLM call while crew runs are in-flight, so the agent stays continuously aware of active runs without calling the `team` tool. No-op when no runs are active. Default: true. */
|
|
192
197
|
ambientStatusInjection?: boolean;
|
|
193
198
|
/**
|
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
import { statSync } from "node:fs";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
ExtensionAPI,
|
|
4
|
+
ExtensionContext,
|
|
5
|
+
ToolDefinition,
|
|
6
|
+
} from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
3
8
|
import { loadConfig } from "../../config/config.ts";
|
|
4
|
-
import {
|
|
5
|
-
import type { CrewWidgetState } from "../../ui/widget/index.ts";
|
|
6
|
-
import { updateCrewWidget } from "../../ui/widget/index.ts";
|
|
7
|
-
import { updatePiCrewPowerbar } from "../../ui/powerbar-publisher.ts";
|
|
9
|
+
import type { MetricRegistry } from "../../observability/metric-registry.ts";
|
|
8
10
|
import type { createManifestCache } from "../../runtime/manifest-cache.ts";
|
|
11
|
+
import {
|
|
12
|
+
TeamToolParams,
|
|
13
|
+
type TeamToolParamsValue,
|
|
14
|
+
} from "../../schema/team-tool-schema.ts";
|
|
15
|
+
import { updatePiCrewPowerbar } from "../../ui/powerbar-publisher.ts";
|
|
9
16
|
import type { createRunSnapshotCache } from "../../ui/run-snapshot-cache.ts";
|
|
10
|
-
import
|
|
17
|
+
import { statusIcon, teamToolRenderer } from "../../ui/tool-renderers/index.ts";
|
|
18
|
+
import type { CrewWidgetState } from "../../ui/widget/index.ts";
|
|
19
|
+
import { updateCrewWidget } from "../../ui/widget/index.ts";
|
|
11
20
|
import { resolveRealContainedPath } from "../../utils/safe-paths.ts";
|
|
12
|
-
import { Text } from "@earendil-works/pi-tui";
|
|
13
|
-
import { teamToolRenderer, statusIcon } from "../../ui/tool-renderers/index.ts";
|
|
14
21
|
// Team tool handler — lazy-loaded because team-tool.ts imports many modules
|
|
15
22
|
import type { handleTeamTool as HandleTeamToolFn } from "../team-tool.ts";
|
|
23
|
+
|
|
16
24
|
let _cachedHandleTeamTool: typeof HandleTeamToolFn | undefined;
|
|
17
|
-
async function handleTeamTool(
|
|
25
|
+
async function handleTeamTool(
|
|
26
|
+
params: Parameters<typeof HandleTeamToolFn>[0],
|
|
27
|
+
ctx: Parameters<typeof HandleTeamToolFn>[1],
|
|
28
|
+
): Promise<ReturnType<typeof HandleTeamToolFn>> {
|
|
18
29
|
if (!_cachedHandleTeamTool) {
|
|
19
30
|
// LAZY: team-tool.ts imports many modules — defer until first use.
|
|
20
31
|
const mod = await import("../team-tool.ts");
|
|
@@ -22,12 +33,13 @@ async function handleTeamTool(params: Parameters<typeof HandleTeamToolFn>[0], ct
|
|
|
22
33
|
}
|
|
23
34
|
return _cachedHandleTeamTool(params, ctx);
|
|
24
35
|
}
|
|
25
|
-
|
|
26
|
-
import { toolResult } from "../tool-result.ts";
|
|
27
|
-
import { loadRunManifestById } from "../../state/state-store.ts";
|
|
36
|
+
|
|
28
37
|
import { readCrewAgents } from "../../runtime/crew-agent-records.ts";
|
|
38
|
+
import { loadRunManifestById } from "../../state/state-store.ts";
|
|
29
39
|
import { formatCompactToolProgress } from "../../ui/tool-progress-formatter.ts";
|
|
30
40
|
import { logInternalError } from "../../utils/internal-error.ts";
|
|
41
|
+
import { withSessionId } from "../team-tool/context.ts";
|
|
42
|
+
import { toolResult } from "../tool-result.ts";
|
|
31
43
|
|
|
32
44
|
const TEAM_TOOL_PROGRESS_TICK_MS = 1000;
|
|
33
45
|
|
|
@@ -35,22 +47,35 @@ type OnUpdate = (chunk: { content: { type: "text"; text: string }[] }) => void;
|
|
|
35
47
|
|
|
36
48
|
export interface RegisterTeamToolDeps {
|
|
37
49
|
foregroundControllers: Map<string | symbol, AbortController>;
|
|
38
|
-
startForegroundRun: (
|
|
50
|
+
startForegroundRun: (
|
|
51
|
+
ctx: ExtensionContext,
|
|
52
|
+
runner: (signal?: AbortSignal) => Promise<void>,
|
|
53
|
+
runId?: string,
|
|
54
|
+
) => void;
|
|
39
55
|
abortForegroundRun: (runId: string) => boolean;
|
|
40
56
|
openLiveSidebar: (ctx: ExtensionContext, runId: string) => void;
|
|
41
57
|
getManifestCache: (cwd: string) => ReturnType<typeof createManifestCache>;
|
|
42
|
-
getRunSnapshotCache?: (
|
|
58
|
+
getRunSnapshotCache?: (
|
|
59
|
+
cwd: string,
|
|
60
|
+
) => ReturnType<typeof createRunSnapshotCache>;
|
|
43
61
|
getMetricRegistry?: () => MetricRegistry | undefined;
|
|
44
62
|
widgetState: CrewWidgetState;
|
|
45
63
|
onJsonEvent?: (taskId: string, runId: string, event: unknown) => void;
|
|
46
64
|
}
|
|
47
65
|
|
|
48
|
-
export function resolveCwdOverride(
|
|
66
|
+
export function resolveCwdOverride(
|
|
67
|
+
baseCwd: string,
|
|
68
|
+
override: string | undefined,
|
|
69
|
+
): { ok: true; cwd: string } | { ok: false; error: string } {
|
|
49
70
|
if (!override) return { ok: true, cwd: baseCwd };
|
|
50
71
|
try {
|
|
51
72
|
const resolved = resolveRealContainedPath(baseCwd, override);
|
|
52
73
|
const stat = statSync(resolved);
|
|
53
|
-
if (!stat.isDirectory())
|
|
74
|
+
if (!stat.isDirectory())
|
|
75
|
+
return {
|
|
76
|
+
ok: false,
|
|
77
|
+
error: `cwd override is not a directory: ${resolved}`,
|
|
78
|
+
};
|
|
54
79
|
return { ok: true, cwd: resolved };
|
|
55
80
|
} catch (error) {
|
|
56
81
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -58,12 +83,28 @@ export function resolveCwdOverride(baseCwd: string, override: string | undefined
|
|
|
58
83
|
}
|
|
59
84
|
}
|
|
60
85
|
|
|
61
|
-
export function registerTeamTool(
|
|
86
|
+
export function registerTeamTool(
|
|
87
|
+
pi: ExtensionAPI,
|
|
88
|
+
deps: RegisterTeamToolDeps,
|
|
89
|
+
): void {
|
|
62
90
|
const tool: ToolDefinition = {
|
|
63
91
|
name: "team",
|
|
64
92
|
label: "Team",
|
|
65
|
-
description:
|
|
66
|
-
|
|
93
|
+
description: [
|
|
94
|
+
"Coordinate Pi teams. Use proactively for complex multi-file work, planning, implementation, tests, reviews, security audits, research, async/background runs, and worktree-isolated execution. Use action='recommend' when unsure which team/workflow to choose. Destructive actions require explicit user confirmation.",
|
|
95
|
+
"",
|
|
96
|
+
"ℹ️ ADVISORY NOTE (preflight, never blocks): pi-crew prints informational notes about workflow topology. Review the note, then decide. There is no BLOCK — agents always exercise judgment.",
|
|
97
|
+
"- Single-task run: raw `Agent` tool would be ~30× faster and ~5× cheaper. pi-crew notes this and proceeds anyway.",
|
|
98
|
+
"- 2–3 step sequential run: measured 5.7× slower than raw `Agent` calls in Run #3. pi-crew notes this and proceeds anyway.",
|
|
99
|
+
"- ≥3 concurrent or complex DAG: validated good use case, pi-crew notes 'validated use case'.",
|
|
100
|
+
"If unsure, call { action: 'recommend', goal } first to get a team/workflow suggestion.",
|
|
101
|
+
].join("\n"),
|
|
102
|
+
promptSnippet: [
|
|
103
|
+
"Use the team tool for multi-agent orchestration when you need ≥3 concurrent agents or a complex DAG.",
|
|
104
|
+
"For single tasks or 2–3 sequential steps, the raw Agent tool is usually faster (raw calls = ~5× faster for 3-step chains).",
|
|
105
|
+
"pi-crew notes the topology (informational only) but proceeds either way — you decide based on your context (audit trail, team coordination, etc.).",
|
|
106
|
+
"If unsure, call { action: 'recommend', goal } first.",
|
|
107
|
+
].join("\n"),
|
|
67
108
|
parameters: TeamToolParams as never,
|
|
68
109
|
async execute(_id, params, signal, onUpdate, ctx) {
|
|
69
110
|
const controller = new AbortController();
|
|
@@ -71,19 +112,50 @@ export function registerTeamTool(pi: ExtensionAPI, deps: RegisterTeamToolDeps):
|
|
|
71
112
|
deps.foregroundControllers.set(toolKey, controller);
|
|
72
113
|
const abort = (): void => controller.abort();
|
|
73
114
|
signal?.addEventListener("abort", abort, { once: true });
|
|
74
|
-
const stopProgress = startTeamToolProgressBinder(
|
|
115
|
+
const stopProgress = startTeamToolProgressBinder(
|
|
116
|
+
onUpdate as OnUpdate | undefined,
|
|
117
|
+
);
|
|
75
118
|
try {
|
|
76
119
|
const resolved = params as TeamToolParamsValue;
|
|
77
120
|
const cwdOverride = resolveCwdOverride(ctx.cwd, resolved.cwd);
|
|
78
|
-
if (!cwdOverride.ok)
|
|
121
|
+
if (!cwdOverride.ok)
|
|
122
|
+
return toolResult(
|
|
123
|
+
cwdOverride.error,
|
|
124
|
+
{ action: resolved.action ?? "list", status: "error" },
|
|
125
|
+
true,
|
|
126
|
+
);
|
|
79
127
|
const toolCtx = withSessionId({ ...ctx, cwd: cwdOverride.cwd });
|
|
80
128
|
// Phase 1.5: Auto-set session name from team run context
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
129
|
+
if (
|
|
130
|
+
resolved.action === "run" &&
|
|
131
|
+
resolved.goal &&
|
|
132
|
+
!pi.getSessionName()
|
|
133
|
+
) {
|
|
134
|
+
const runLabel =
|
|
135
|
+
resolved.team ?? resolved.agent ?? "direct";
|
|
136
|
+
pi.setSessionName(
|
|
137
|
+
`pi-crew: ${runLabel}/${resolved.workflow ?? "default"} — ${resolved.goal.slice(0, 60)}`,
|
|
138
|
+
);
|
|
84
139
|
}
|
|
85
|
-
const output = await handleTeamTool(resolved, {
|
|
86
|
-
|
|
140
|
+
const output = await handleTeamTool(resolved, {
|
|
141
|
+
...toolCtx,
|
|
142
|
+
signal: controller.signal,
|
|
143
|
+
metricRegistry: deps.getMetricRegistry?.(),
|
|
144
|
+
startForegroundRun: (runner, runId) =>
|
|
145
|
+
deps.startForegroundRun(toolCtx, runner, runId),
|
|
146
|
+
abortForegroundRun: deps.abortForegroundRun,
|
|
147
|
+
onRunStarted: (runId) => {
|
|
148
|
+
stopProgress.attach(toolCtx.cwd, runId);
|
|
149
|
+
deps.openLiveSidebar(toolCtx, runId);
|
|
150
|
+
},
|
|
151
|
+
onJsonEvent: deps.onJsonEvent,
|
|
152
|
+
getRunSnapshotCache: deps.getRunSnapshotCache,
|
|
153
|
+
});
|
|
154
|
+
if (
|
|
155
|
+
resolved.action === "run" &&
|
|
156
|
+
!output.isError &&
|
|
157
|
+
typeof output.details?.runId === "string"
|
|
158
|
+
) {
|
|
87
159
|
pi.appendEntry("crew:run-started", {
|
|
88
160
|
runId: output.details.runId,
|
|
89
161
|
team: resolved.team,
|
|
@@ -97,8 +169,21 @@ export function registerTeamTool(pi: ExtensionAPI, deps: RegisterTeamToolDeps):
|
|
|
97
169
|
const config = loadConfig(toolCtx.cwd).config.ui;
|
|
98
170
|
const cache = deps.getManifestCache(toolCtx.cwd);
|
|
99
171
|
const snapshotCache = deps.getRunSnapshotCache?.(toolCtx.cwd);
|
|
100
|
-
updateCrewWidget(
|
|
101
|
-
|
|
172
|
+
updateCrewWidget(
|
|
173
|
+
toolCtx,
|
|
174
|
+
deps.widgetState,
|
|
175
|
+
config,
|
|
176
|
+
cache,
|
|
177
|
+
snapshotCache,
|
|
178
|
+
);
|
|
179
|
+
updatePiCrewPowerbar(
|
|
180
|
+
pi.events,
|
|
181
|
+
toolCtx.cwd,
|
|
182
|
+
config,
|
|
183
|
+
cache,
|
|
184
|
+
snapshotCache,
|
|
185
|
+
toolCtx,
|
|
186
|
+
);
|
|
102
187
|
return output;
|
|
103
188
|
} finally {
|
|
104
189
|
signal?.removeEventListener("abort", abort);
|
|
@@ -113,7 +198,12 @@ export function registerTeamTool(pi: ExtensionAPI, deps: RegisterTeamToolDeps):
|
|
|
113
198
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
114
199
|
renderResult(result: any, options: any, theme: any, context: any): any {
|
|
115
200
|
try {
|
|
116
|
-
return teamToolRenderer.renderResult(
|
|
201
|
+
return teamToolRenderer.renderResult(
|
|
202
|
+
result,
|
|
203
|
+
options,
|
|
204
|
+
theme,
|
|
205
|
+
context,
|
|
206
|
+
);
|
|
117
207
|
} catch {
|
|
118
208
|
return new Text(statusIcon("completed", theme) + " done", 0, 0);
|
|
119
209
|
}
|
|
@@ -127,7 +217,9 @@ interface TeamToolProgressBinder {
|
|
|
127
217
|
stop: () => void;
|
|
128
218
|
}
|
|
129
219
|
|
|
130
|
-
function startTeamToolProgressBinder(
|
|
220
|
+
function startTeamToolProgressBinder(
|
|
221
|
+
onUpdate: OnUpdate | undefined,
|
|
222
|
+
): TeamToolProgressBinder {
|
|
131
223
|
if (!onUpdate) {
|
|
132
224
|
return { attach: () => {}, stop: () => {} };
|
|
133
225
|
}
|
|
@@ -137,20 +229,30 @@ function startTeamToolProgressBinder(onUpdate: OnUpdate | undefined): TeamToolPr
|
|
|
137
229
|
const tick = (): void => {
|
|
138
230
|
try {
|
|
139
231
|
if (!cwd || !runId) {
|
|
140
|
-
const elapsed = Math.max(
|
|
232
|
+
const elapsed = Math.max(
|
|
233
|
+
0,
|
|
234
|
+
Math.round((Date.now() - startedAt) / 1000),
|
|
235
|
+
);
|
|
141
236
|
const msg = `team status=starting elapsed=${elapsed}s`;
|
|
142
237
|
onUpdate({ content: [{ type: "text", text: msg }] });
|
|
143
238
|
return;
|
|
144
239
|
}
|
|
145
240
|
const loaded = loadRunManifestById(cwd, runId);
|
|
146
241
|
if (!loaded) {
|
|
147
|
-
const elapsed = Math.max(
|
|
242
|
+
const elapsed = Math.max(
|
|
243
|
+
0,
|
|
244
|
+
Math.round((Date.now() - startedAt) / 1000),
|
|
245
|
+
);
|
|
148
246
|
const msg = `team run=${runId} elapsed=${elapsed}s (manifest pending)`;
|
|
149
247
|
onUpdate({ content: [{ type: "text", text: msg }] });
|
|
150
248
|
return;
|
|
151
249
|
}
|
|
152
250
|
let agents;
|
|
153
|
-
try {
|
|
251
|
+
try {
|
|
252
|
+
agents = readCrewAgents(loaded.manifest);
|
|
253
|
+
} catch {
|
|
254
|
+
/* ignore */
|
|
255
|
+
}
|
|
154
256
|
const text = formatCompactToolProgress({
|
|
155
257
|
agentId: runId,
|
|
156
258
|
status: loaded.manifest.status,
|
|
@@ -162,14 +264,22 @@ function startTeamToolProgressBinder(onUpdate: OnUpdate | undefined): TeamToolPr
|
|
|
162
264
|
});
|
|
163
265
|
onUpdate({ content: [{ type: "text", text }] });
|
|
164
266
|
} catch (error) {
|
|
165
|
-
logInternalError(
|
|
267
|
+
logInternalError(
|
|
268
|
+
"team-tool.progress",
|
|
269
|
+
error,
|
|
270
|
+
`runId=${runId ?? ""}`,
|
|
271
|
+
);
|
|
166
272
|
}
|
|
167
273
|
};
|
|
168
274
|
tick();
|
|
169
275
|
const timer = setInterval(tick, TEAM_TOOL_PROGRESS_TICK_MS);
|
|
170
276
|
if (typeof timer.unref === "function") timer.unref();
|
|
171
277
|
return {
|
|
172
|
-
attach: (boundCwd: string, boundRunId: string) => {
|
|
278
|
+
attach: (boundCwd: string, boundRunId: string) => {
|
|
279
|
+
cwd = boundCwd;
|
|
280
|
+
runId = boundRunId;
|
|
281
|
+
tick();
|
|
282
|
+
},
|
|
173
283
|
stop: () => clearInterval(timer),
|
|
174
284
|
};
|
|
175
285
|
}
|