pi-crew 0.1.31 → 0.1.32
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/README.md +80 -27
- package/docs/architecture.md +18 -9
- package/docs/refactor-tasks-phase3.md +1 -1
- package/docs/research-extension-examples.md +297 -0
- package/docs/research-extension-system.md +324 -0
- package/docs/research-optimization-plan.md +548 -0
- package/docs/research-pi-coding-agent.md +357 -0
- package/docs/resource-formats.md +4 -3
- package/docs/usage.md +3 -3
- package/package.json +1 -1
- package/schema.json +53 -1
- package/src/agents/discover-agents.ts +2 -2
- package/src/config/config.ts +14 -1
- package/src/config/defaults.ts +3 -2
- package/src/extension/import-index.ts +4 -3
- package/src/extension/management.ts +2 -2
- package/src/extension/project-init.ts +9 -7
- package/src/extension/register.ts +63 -0
- package/src/extension/registration/artifact-cleanup.ts +4 -3
- package/src/extension/registration/compaction-guard.ts +125 -0
- package/src/extension/registration/subagent-tools.ts +27 -8
- package/src/extension/registration/team-tool.ts +18 -1
- package/src/extension/run-import.ts +4 -4
- package/src/extension/run-index.ts +14 -14
- package/src/extension/team-tool/api.ts +3 -0
- package/src/extension/team-tool/doctor.ts +6 -5
- package/src/runtime/manifest-cache.ts +3 -5
- package/src/runtime/subagent-manager.ts +29 -2
- package/src/schema/config-schema.ts +12 -0
- package/src/state/state-store.ts +8 -9
- package/src/teams/discover-teams.ts +2 -2
- package/src/utils/paths.ts +34 -7
- package/src/workflows/discover-workflows.ts +2 -2
- package/src/worktree/cleanup.ts +3 -1
- package/src/worktree/worktree-manager.ts +3 -1
package/README.md
CHANGED
|
@@ -132,9 +132,12 @@ User config path:
|
|
|
132
132
|
Project config path:
|
|
133
133
|
|
|
134
134
|
```text
|
|
135
|
-
.
|
|
135
|
+
.crew/config.json # default (new projects)
|
|
136
|
+
.pi/teams/config.json # legacy (when the repo already has .pi/)
|
|
136
137
|
```
|
|
137
138
|
|
|
139
|
+
The project root is auto-detected by walking up from the current directory and stopping at any of: `.git`, `.pi`, `.crew`, `.hg`, `.svn`, `.factory`, `.omc`, or any common manifest file (`package.json`, `pyproject.toml`, `Cargo.toml`, `go.mod`, `pom.xml`, `composer.json`, `build.gradle[.kts]`). If the project already has a `.pi/` directory, pi-crew reuses it under `.pi/teams/` to avoid creating a parallel layout; otherwise it uses `.crew/`.
|
|
140
|
+
|
|
138
141
|
Config merge priority:
|
|
139
142
|
|
|
140
143
|
```text
|
|
@@ -179,6 +182,11 @@ Supported config:
|
|
|
179
182
|
"showModel": true,
|
|
180
183
|
"showTokens": true,
|
|
181
184
|
"showTools": true
|
|
185
|
+
},
|
|
186
|
+
"tools": {
|
|
187
|
+
"enableClaudeStyleAliases": true,
|
|
188
|
+
"enableSteer": true,
|
|
189
|
+
"terminateOnForeground": false
|
|
182
190
|
}
|
|
183
191
|
}
|
|
184
192
|
```
|
|
@@ -186,6 +194,7 @@ Supported config:
|
|
|
186
194
|
Safety notes:
|
|
187
195
|
|
|
188
196
|
- Foreground child-process runs continue in the Pi extension process and return control to chat immediately, so large workflows do not block the interactive session. They are interrupted on session shutdown. Use `async: true` only for intentionally detached runs that may survive the current session.
|
|
197
|
+
- `tools.terminateOnForeground` is an opt-in power-user setting. When true, foreground `Agent`/`crew_agent` calls return with `terminate: true` after the child result is available, saving one follow-up LLM turn. Default is false so the assistant can still summarize raw worker output.
|
|
189
198
|
|
|
190
199
|
UI notes:
|
|
191
200
|
|
|
@@ -454,12 +463,20 @@ User resources:
|
|
|
454
463
|
~/.pi/agent/workflows/*.workflow.md
|
|
455
464
|
```
|
|
456
465
|
|
|
457
|
-
Project resources:
|
|
466
|
+
Project resources (new default layout):
|
|
467
|
+
|
|
468
|
+
```text
|
|
469
|
+
.crew/agents/*.md
|
|
470
|
+
.crew/teams/*.team.md
|
|
471
|
+
.crew/workflows/*.workflow.md
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
Legacy layout (when `.pi/` already exists in the repo):
|
|
458
475
|
|
|
459
476
|
```text
|
|
460
|
-
.pi/agents/*.md
|
|
461
|
-
.pi/teams/*.team.md
|
|
462
|
-
.pi/workflows/*.workflow.md
|
|
477
|
+
.pi/teams/agents/*.md
|
|
478
|
+
.pi/teams/teams/*.team.md
|
|
479
|
+
.pi/teams/workflows/*.workflow.md
|
|
463
480
|
```
|
|
464
481
|
|
|
465
482
|
Discovery priority:
|
|
@@ -525,33 +542,41 @@ review
|
|
|
525
542
|
|
|
526
543
|
## State layout
|
|
527
544
|
|
|
528
|
-
Project-local state is preferred when the cwd
|
|
545
|
+
Project-local state is preferred when the cwd is inside a recognised project (any of the markers listed in the Config section above). Otherwise pi-crew falls back to user-global state.
|
|
529
546
|
|
|
530
|
-
|
|
547
|
+
The project state root (`<crewRoot>` below) resolves to:
|
|
531
548
|
|
|
532
549
|
```text
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
550
|
+
<repoRoot>/.crew/ # default, used for new projects
|
|
551
|
+
<repoRoot>/.pi/teams/ # legacy reuse when .pi/ already exists
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
Typical project-local state (`<crewRoot>` is one of the two paths above):
|
|
555
|
+
|
|
556
|
+
```text
|
|
557
|
+
<crewRoot>/state/runs/{runId}/manifest.json
|
|
558
|
+
<crewRoot>/state/runs/{runId}/tasks.json
|
|
559
|
+
<crewRoot>/state/runs/{runId}/events.jsonl
|
|
560
|
+
<crewRoot>/artifacts/{runId}/...
|
|
561
|
+
<crewRoot>/worktrees/{runId}/{taskId}
|
|
562
|
+
<crewRoot>/imports/{runId}/run-export.json
|
|
539
563
|
```
|
|
540
564
|
|
|
541
565
|
Mailbox state:
|
|
542
566
|
|
|
543
567
|
```text
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
568
|
+
<crewRoot>/state/runs/{runId}/mailbox/inbox.jsonl
|
|
569
|
+
<crewRoot>/state/runs/{runId}/mailbox/outbox.jsonl
|
|
570
|
+
<crewRoot>/state/runs/{runId}/mailbox/delivery.json
|
|
571
|
+
<crewRoot>/state/runs/{runId}/mailbox/tasks/{taskId}/inbox.jsonl
|
|
572
|
+
<crewRoot>/state/runs/{runId}/mailbox/tasks/{taskId}/outbox.jsonl
|
|
549
573
|
```
|
|
550
574
|
|
|
551
|
-
User-global fallback:
|
|
575
|
+
User-global fallback (shared with other Pi tools):
|
|
552
576
|
|
|
553
577
|
```text
|
|
554
|
-
~/.pi/agent/extensions/pi-crew/runs/...
|
|
578
|
+
~/.pi/agent/extensions/pi-crew/state/runs/...
|
|
579
|
+
~/.pi/agent/extensions/pi-crew/artifacts/...
|
|
555
580
|
~/.pi/agent/extensions/pi-crew/imports/...
|
|
556
581
|
```
|
|
557
582
|
|
|
@@ -570,18 +595,34 @@ Optionally copy builtin resources:
|
|
|
570
595
|
/team-init --copy-builtins --overwrite
|
|
571
596
|
```
|
|
572
597
|
|
|
573
|
-
Created directories:
|
|
598
|
+
Created directories (new projects):
|
|
574
599
|
|
|
575
600
|
```text
|
|
576
|
-
.
|
|
577
|
-
.
|
|
578
|
-
.
|
|
601
|
+
.crew/agents/
|
|
602
|
+
.crew/teams/
|
|
603
|
+
.crew/workflows/
|
|
604
|
+
.crew/imports/
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
If the project already has `.pi/`, the legacy layout is initialised instead:
|
|
608
|
+
|
|
609
|
+
```text
|
|
610
|
+
.pi/teams/agents/
|
|
611
|
+
.pi/teams/teams/
|
|
612
|
+
.pi/teams/workflows/
|
|
579
613
|
.pi/teams/imports/
|
|
580
614
|
```
|
|
581
615
|
|
|
582
|
-
`.gitignore` entries
|
|
616
|
+
`.gitignore` entries are written for whichever layout is active, e.g.:
|
|
583
617
|
|
|
584
618
|
```text
|
|
619
|
+
# new layout
|
|
620
|
+
.crew/state/
|
|
621
|
+
.crew/artifacts/
|
|
622
|
+
.crew/worktrees/
|
|
623
|
+
.crew/imports/
|
|
624
|
+
|
|
625
|
+
# legacy layout
|
|
585
626
|
.pi/teams/state/
|
|
586
627
|
.pi/teams/artifacts/
|
|
587
628
|
.pi/teams/worktrees/
|
|
@@ -597,14 +638,26 @@ Export writes:
|
|
|
597
638
|
{artifactsRoot}/export/run-export.md
|
|
598
639
|
```
|
|
599
640
|
|
|
600
|
-
Import stores bundles under:
|
|
641
|
+
Import stores bundles under (new layout):
|
|
642
|
+
|
|
643
|
+
```text
|
|
644
|
+
.crew/imports/{runId}/run-export.json
|
|
645
|
+
.crew/imports/{runId}/README.md
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
or under the legacy layout when `.pi/` already exists:
|
|
601
649
|
|
|
602
650
|
```text
|
|
603
651
|
.pi/teams/imports/{runId}/run-export.json
|
|
604
652
|
.pi/teams/imports/{runId}/README.md
|
|
605
653
|
```
|
|
606
654
|
|
|
607
|
-
or user-global imports with `--user
|
|
655
|
+
or user-global imports with `--user`:
|
|
656
|
+
|
|
657
|
+
```text
|
|
658
|
+
~/.pi/agent/extensions/pi-crew/imports/{runId}/run-export.json
|
|
659
|
+
~/.pi/agent/extensions/pi-crew/imports/{runId}/README.md
|
|
660
|
+
```
|
|
608
661
|
|
|
609
662
|
## Doctor and validation
|
|
610
663
|
|
package/docs/architecture.md
CHANGED
|
@@ -12,12 +12,14 @@ Runtime layer
|
|
|
12
12
|
team runner, task graph scheduler, child Pi process runner, async runner,
|
|
13
13
|
model fallback, policy engine, worktree manager, live-session experimental path
|
|
14
14
|
|
|
15
|
-
State layer
|
|
16
|
-
.pi/
|
|
17
|
-
.pi/teams/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
State layer (project root resolves to <crewRoot>:
|
|
16
|
+
- .crew/ when no .pi/ exists in the repo (default)
|
|
17
|
+
- .pi/teams/ when the repo already has .pi/ (legacy reuse))
|
|
18
|
+
<crewRoot>/state/runs/{runId}/manifest.json
|
|
19
|
+
<crewRoot>/state/runs/{runId}/tasks.json
|
|
20
|
+
<crewRoot>/state/runs/{runId}/events.jsonl
|
|
21
|
+
<crewRoot>/state/runs/{runId}/agents/{taskId}/status.json
|
|
22
|
+
<crewRoot>/artifacts/{runId}/...
|
|
21
23
|
```
|
|
22
24
|
|
|
23
25
|
## Run flow
|
|
@@ -104,10 +106,10 @@ Model choice is based on Pi's current configuration/model registry, not hardcode
|
|
|
104
106
|
|
|
105
107
|
## State layer
|
|
106
108
|
|
|
107
|
-
Run state is under:
|
|
109
|
+
Run state is under `<crewRoot>` (`.crew/` for new projects, or `.pi/teams/` when the repo already has `.pi/`):
|
|
108
110
|
|
|
109
111
|
```text
|
|
110
|
-
|
|
112
|
+
<crewRoot>/state/runs/{runId}/
|
|
111
113
|
manifest.json run metadata/status/artifacts/async pid
|
|
112
114
|
tasks.json task graph and per-task status
|
|
113
115
|
events.jsonl append-only run events
|
|
@@ -125,7 +127,7 @@ Run state is under:
|
|
|
125
127
|
Artifacts are under:
|
|
126
128
|
|
|
127
129
|
```text
|
|
128
|
-
|
|
130
|
+
<crewRoot>/artifacts/{runId}/
|
|
129
131
|
goal.md
|
|
130
132
|
prompts/{taskId}.md
|
|
131
133
|
results/{taskId}.txt
|
|
@@ -136,6 +138,13 @@ Artifacts are under:
|
|
|
136
138
|
summary.md
|
|
137
139
|
```
|
|
138
140
|
|
|
141
|
+
`<crewRoot>` resolution is centralised in `src/utils/paths.ts#projectCrewRoot()`:
|
|
142
|
+
|
|
143
|
+
- if `<repoRoot>/.pi/` already exists, return `<repoRoot>/.pi/teams/` (legacy reuse, no parallel `.crew/`)
|
|
144
|
+
- otherwise return `<repoRoot>/.crew/` (default for fresh projects)
|
|
145
|
+
|
|
146
|
+
User-global fallback (when no project root is detected) lives under `~/.pi/agent/extensions/pi-crew/`.
|
|
147
|
+
|
|
139
148
|
Atomic writes use temp-file replace with retry for transient Windows `EPERM`/`EBUSY`/`EACCES`. JSONL append paths are best-effort where used for observers/progress; write failures must not crash child output parsing.
|
|
140
149
|
|
|
141
150
|
## UI and observability
|
|
@@ -278,7 +278,7 @@ export const MAX_PARALLEL_CONCURRENCY: number;
|
|
|
278
278
|
**Source**: `source/pi-subagents/artifacts.ts` (hàm `cleanupOldArtifacts`)
|
|
279
279
|
**Đích**: bổ sung vào `pi-crew/src/state/artifact-store.ts`
|
|
280
280
|
|
|
281
|
-
**Lý do**: Pi-crew `.pi/teams
|
|
281
|
+
**Lý do**: Pi-crew `<crewRoot>/state/artifacts/` (`<crewRoot>` = `.crew/` mới hoặc `.pi/teams/` legacy) không có TTL → run cũ tích lũy mãi. Pattern subagents:
|
|
282
282
|
- File `.last-cleanup` chứa timestamp.
|
|
283
283
|
- Nếu marker mới hơn 24h → skip (không scan dir lớn mỗi extension load).
|
|
284
284
|
- Nếu cần scan: xoá file mtime > `maxAgeDays * 24h`.
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# Research: Extension Examples & Patterns
|
|
2
|
+
|
|
3
|
+
> Ngày: 2026-04-29 | Read-only research | Source: `source/pi-mono/packages/coding-agent/examples/extensions/`
|
|
4
|
+
|
|
5
|
+
## 1. Example Catalog (86 files, 60+ extensions)
|
|
6
|
+
|
|
7
|
+
### 1.1 Sorted by relevance to pi-crew
|
|
8
|
+
|
|
9
|
+
| Priority | Example | Relevance |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| ⭐⭐⭐ | `subagent/` | Most similar to pi-crew: child Pi spawning, parallel, chain |
|
|
12
|
+
| ⭐⭐⭐ | `custom-compaction.ts` | Hook compaction — useful for preserving run state |
|
|
13
|
+
| ⭐⭐⭐ | `event-bus.ts` | Cross-extension communication pattern |
|
|
14
|
+
| ⭐⭐⭐ | `plan-mode/` | State persistence, dynamic tools, widget management |
|
|
15
|
+
| ⭐⭐⭐ | `structured-output.ts` | `terminate: true` — save LLM turns |
|
|
16
|
+
| ⭐⭐ | `handoff.ts` | Context transfer to new session |
|
|
17
|
+
| ⭐⭐ | `dynamic-tools.ts` | Register tools at runtime |
|
|
18
|
+
| ⭐⭐ | `permission-gate.ts` | Gate dangerous operations |
|
|
19
|
+
| ⭐⭐ | `trigger-compact.ts` | Proactive compaction monitoring |
|
|
20
|
+
| ⭐⭐ | `send-user-message.ts` | sendUserMessage pattern |
|
|
21
|
+
| ⭐ | `dirty-repo-guard.ts` | Guard against uncommitted changes |
|
|
22
|
+
| ⭐ | `model-status.ts` | Model status in footer |
|
|
23
|
+
| ⭐ | `confirm-destructive.ts` | Confirm destructive operations |
|
|
24
|
+
|
|
25
|
+
## 2. Deep Analysis of Key Examples
|
|
26
|
+
|
|
27
|
+
### 2.1 subagent/ — The Reference Implementation
|
|
28
|
+
|
|
29
|
+
**Files:**
|
|
30
|
+
- `index.ts` (~530 dòng): Main tool with execute + render
|
|
31
|
+
- `agents.ts` (~130 dòng): Agent discovery (user/project scope)
|
|
32
|
+
|
|
33
|
+
**Architecture:**
|
|
34
|
+
```
|
|
35
|
+
subagent tool
|
|
36
|
+
├── Single: runSingleAgent() → spawn pi --mode json -p
|
|
37
|
+
├── Parallel: mapWithConcurrencyLimit(tasks, 4, runSingleAgent)
|
|
38
|
+
└── Chain: sequential loop with {previous} placeholder
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Key patterns:**
|
|
42
|
+
- Agent discovery: `discoverAgents(cwd, scope)` — scans `.md` files with YAML frontmatter
|
|
43
|
+
- Child process: `getPiInvocation()` detects current runtime (node/bun/pi binary)
|
|
44
|
+
- Streaming: `onUpdate` callback for partial results during execution
|
|
45
|
+
- Render: `renderCall()` + `renderResult()` with collapsed/expanded views
|
|
46
|
+
- Abort: AbortSignal propagated to child process
|
|
47
|
+
|
|
48
|
+
**What pi-crew does better:**
|
|
49
|
+
- Durable state (manifest, tasks, events) instead of in-memory only
|
|
50
|
+
- Team/workflow abstraction instead of flat agent list
|
|
51
|
+
- Task graph with DAG dependencies instead of linear chain
|
|
52
|
+
- Async background runner with PID tracking
|
|
53
|
+
- Policy engine for limits/retry/escalation
|
|
54
|
+
- Mailbox for inter-task communication
|
|
55
|
+
- Worktree isolation per task
|
|
56
|
+
|
|
57
|
+
**What pi-crew could adopt from this:**
|
|
58
|
+
- `terminate: true` on final results (not used in example either, but available)
|
|
59
|
+
- `renderCall/Result` custom rendering patterns
|
|
60
|
+
- `mapWithConcurrencyLimit` pattern (pi-crew already has similar)
|
|
61
|
+
|
|
62
|
+
### 2.2 custom-compaction.ts — Custom Compaction
|
|
63
|
+
|
|
64
|
+
**Pattern:**
|
|
65
|
+
```typescript
|
|
66
|
+
pi.on("session_before_compact", async (event, ctx) => {
|
|
67
|
+
// 1. Get preparation data
|
|
68
|
+
const { messagesToSummarize, turnPrefixMessages, tokensBefore, firstKeptEntryId } = event.preparation;
|
|
69
|
+
|
|
70
|
+
// 2. Use different model for summarization (cheaper)
|
|
71
|
+
const model = ctx.modelRegistry.find("google", "gemini-2.5-flash");
|
|
72
|
+
|
|
73
|
+
// 3. Custom prompt
|
|
74
|
+
const summary = await complete(model, { messages: [...] }, { apiKey, signal });
|
|
75
|
+
|
|
76
|
+
// 4. Return custom compaction result
|
|
77
|
+
return {
|
|
78
|
+
compaction: { summary, firstKeptEntryId, tokensBefore }
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Relevance to pi-crew:**
|
|
84
|
+
- Can use cheap model to summarize completed tasks
|
|
85
|
+
- Can protect foreground runs from being compacted mid-execution
|
|
86
|
+
- Can store structured artifact index in compaction `details`
|
|
87
|
+
|
|
88
|
+
### 2.3 event-bus.ts — Cross-Extension Communication
|
|
89
|
+
|
|
90
|
+
**Pattern:**
|
|
91
|
+
```typescript
|
|
92
|
+
// Extension A: emit events
|
|
93
|
+
pi.events.emit("my:notification", { message: "hello", from: "ext-a" });
|
|
94
|
+
|
|
95
|
+
// Extension B: listen
|
|
96
|
+
pi.events.on("my:notification", (data) => {
|
|
97
|
+
currentCtx?.ui.notify(`Event from ${data.from}: ${data.message}`);
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Relevance to pi-crew:**
|
|
102
|
+
- Already used for internal events (`subagent.stuck-blocked`)
|
|
103
|
+
- Could publish structured events for other extensions to consume:
|
|
104
|
+
- `pi-crew:run:completed`
|
|
105
|
+
- `pi-crew:subagent:completed`
|
|
106
|
+
- `pi-crew:run:failed`
|
|
107
|
+
|
|
108
|
+
### 2.4 plan-mode/ — State Persistence + Dynamic Tools
|
|
109
|
+
|
|
110
|
+
**Key patterns:**
|
|
111
|
+
|
|
112
|
+
State persistence:
|
|
113
|
+
```typescript
|
|
114
|
+
// Save
|
|
115
|
+
pi.appendEntry("plan-mode", { enabled, todos, executing });
|
|
116
|
+
|
|
117
|
+
// Restore on session_start
|
|
118
|
+
const entries = ctx.sessionManager.getEntries();
|
|
119
|
+
const state = entries
|
|
120
|
+
.filter(e => e.type === "custom" && e.customType === "plan-mode")
|
|
121
|
+
.pop()?.data;
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Dynamic tools:
|
|
125
|
+
```typescript
|
|
126
|
+
// Switch between tool sets
|
|
127
|
+
if (planModeEnabled) {
|
|
128
|
+
pi.setActiveTools(["read", "bash", "grep", "find", "ls"]);
|
|
129
|
+
} else {
|
|
130
|
+
pi.setActiveTools(["read", "bash", "edit", "write"]);
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Tool call gate:
|
|
135
|
+
```typescript
|
|
136
|
+
pi.on("tool_call", async (event) => {
|
|
137
|
+
if (planModeEnabled && event.toolName === "bash") {
|
|
138
|
+
if (!isSafeCommand(event.input.command)) {
|
|
139
|
+
return { block: true, reason: "..." };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Relevance to pi-crew:**
|
|
146
|
+
- `pi.appendEntry` pattern for cross-session run awareness
|
|
147
|
+
- `pi.setActiveTools` could be used to restrict tools during team runs
|
|
148
|
+
- `tool_call` gate for destructive team actions
|
|
149
|
+
|
|
150
|
+
### 2.5 structured-output.ts — terminate: true
|
|
151
|
+
|
|
152
|
+
**Pattern:**
|
|
153
|
+
```typescript
|
|
154
|
+
async execute(_toolCallId, params) {
|
|
155
|
+
return {
|
|
156
|
+
content: [{ type: "text", text: "Done" }],
|
|
157
|
+
details: { headline, summary, actionItems },
|
|
158
|
+
terminate: true, // ← No follow-up LLM turn needed
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Relevance to pi-crew:**
|
|
164
|
+
- `Agent` tool results could use `terminate: true` when background run queued
|
|
165
|
+
- `get_subagent_result` could terminate when result is final
|
|
166
|
+
- `team` tool status/list/recommend actions could terminate
|
|
167
|
+
|
|
168
|
+
### 2.6 handoff.ts — Context Transfer to New Session
|
|
169
|
+
|
|
170
|
+
**Pattern:**
|
|
171
|
+
```typescript
|
|
172
|
+
// 1. Extract conversation context
|
|
173
|
+
const messages = ctx.sessionManager.getBranch()
|
|
174
|
+
.filter(e => e.type === "message")
|
|
175
|
+
.map(e => e.message);
|
|
176
|
+
|
|
177
|
+
// 2. Generate focused prompt
|
|
178
|
+
const prompt = await complete(model, { systemPrompt, messages }, { apiKey });
|
|
179
|
+
|
|
180
|
+
// 3. Create new session with pre-filled editor
|
|
181
|
+
await ctx.newSession({
|
|
182
|
+
parentSession: currentSessionFile,
|
|
183
|
+
withSession: async (replacementCtx) => {
|
|
184
|
+
replacementCtx.ui.setEditorText(prompt);
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Relevance to pi-crew:**
|
|
190
|
+
- When a task in a team run needs isolated context, could handoff to new session
|
|
191
|
+
- Parent session tracking via `parentSession`
|
|
192
|
+
|
|
193
|
+
### 2.7 permission-gate.ts — Dangerous Operation Gate
|
|
194
|
+
|
|
195
|
+
**Pattern:**
|
|
196
|
+
```typescript
|
|
197
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
198
|
+
if (event.toolName !== "bash") return;
|
|
199
|
+
if (isDangerousPattern(event.input.command)) {
|
|
200
|
+
const choice = await ctx.ui.select("Allow?", ["Yes", "No"]);
|
|
201
|
+
if (choice !== "Yes") {
|
|
202
|
+
return { block: true, reason: "Blocked by user" };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Relevance to pi-crew:**
|
|
209
|
+
- Gate destructive team actions (delete, forget, prune)
|
|
210
|
+
- Only allow with explicit `confirm: true` parameter
|
|
211
|
+
|
|
212
|
+
### 2.8 trigger-compact.ts — Proactive Compaction
|
|
213
|
+
|
|
214
|
+
**Pattern:**
|
|
215
|
+
```typescript
|
|
216
|
+
pi.on("turn_end", (_event, ctx) => {
|
|
217
|
+
const usage = ctx.getContextUsage();
|
|
218
|
+
if (usage?.tokens && usage.tokens > THRESHOLD) {
|
|
219
|
+
ctx.compact({ customInstructions: "..." });
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Relevance to pi-crew:**
|
|
225
|
+
- Monitor context during long team runs
|
|
226
|
+
- Auto-compact before hitting overflow errors
|
|
227
|
+
- Use compact's callback to track state
|
|
228
|
+
|
|
229
|
+
## 3. Pattern Summary
|
|
230
|
+
|
|
231
|
+
### 3.1 Patterns pi-crew already implements well
|
|
232
|
+
|
|
233
|
+
| Pattern | pi-crew implementation |
|
|
234
|
+
|---|---|
|
|
235
|
+
| Child Pi spawning | `SubagentManager` + `spawn.ts` with full process management |
|
|
236
|
+
| Parallel execution | `mapConcurrent` in team runner |
|
|
237
|
+
| State persistence | Durable file-based (manifest, tasks, events, artifacts) |
|
|
238
|
+
| Widget rendering | `CrewWidget`, `LiveRunSidebar`, `Powerbar` |
|
|
239
|
+
| Lifecycle hooks | `session_start`, `session_before_switch`, `session_shutdown` |
|
|
240
|
+
| Config merge | `loadConfig` with user/project priority |
|
|
241
|
+
| Abort propagation | `AbortController` trees in foreground runs |
|
|
242
|
+
|
|
243
|
+
### 3.2 Patterns pi-crew could adopt
|
|
244
|
+
|
|
245
|
+
| Pattern | Current status | Recommendation |
|
|
246
|
+
|---|---|---|
|
|
247
|
+
| `terminate: true` | ❌ Not used | Add to Agent/get_subagent_result |
|
|
248
|
+
| `session_before_compact` hook | ❌ Not hooked | Cancel compact during foreground runs |
|
|
249
|
+
| Custom compaction model | ❌ Not used | Use Haiku/Gemini Flash for task summaries |
|
|
250
|
+
| `pi.events` publish | ⚠️ Internal only | Add public structured events |
|
|
251
|
+
| `pi.appendEntry` | ❌ Not used | Cross-session run references |
|
|
252
|
+
| `tool_call` permission gate | ❌ Not gated | Gate destructive team actions |
|
|
253
|
+
| Config-driven tool registration | ❌ Always all | Register tools per config |
|
|
254
|
+
| Working indicator | ❌ Widget only | Use `ctx.ui.setWorkingIndicator` |
|
|
255
|
+
| Session name auto-set | ❌ Manual only | Auto-name from team run context |
|
|
256
|
+
| `ctx.compact()` proactive | ❌ No monitoring | Monitor + auto-compact at threshold |
|
|
257
|
+
|
|
258
|
+
## 4. Example: Complete Tool with terminate + render
|
|
259
|
+
|
|
260
|
+
This shows a hypothetical optimized pi-crew Agent tool:
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
// OPTIMIZED Agent tool pattern
|
|
264
|
+
const AgentTool = defineTool({
|
|
265
|
+
name: "Agent",
|
|
266
|
+
label: "Agent",
|
|
267
|
+
description: "Launch a real pi-crew subagent...",
|
|
268
|
+
parameters: Type.Object({
|
|
269
|
+
prompt: Type.String(),
|
|
270
|
+
description: Type.String(),
|
|
271
|
+
subagent_type: Type.String(),
|
|
272
|
+
run_in_background: Type.Optional(Type.Boolean()),
|
|
273
|
+
}),
|
|
274
|
+
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
275
|
+
// ... spawn subagent ...
|
|
276
|
+
if (params.run_in_background) {
|
|
277
|
+
return {
|
|
278
|
+
content: [{ type: "text", text: `Agent queued. ID: ${record.id}` }],
|
|
279
|
+
details: { agentId: record.id, status: "queued" },
|
|
280
|
+
terminate: true, // ← No need for LLM follow-up
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
await record.promise;
|
|
284
|
+
const output = readResult(record);
|
|
285
|
+
return {
|
|
286
|
+
content: [{ type: "text", text: output }],
|
|
287
|
+
details: { agentId: record.id, status: record.status },
|
|
288
|
+
terminate: true, // ← Final result, save LLM turn
|
|
289
|
+
};
|
|
290
|
+
},
|
|
291
|
+
renderResult(result, { expanded }, theme) {
|
|
292
|
+
// Custom rendering with colored status icons
|
|
293
|
+
// Collapsed/expanded views
|
|
294
|
+
// Usage stats display
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
```
|