pi-crew 0.1.37 → 0.1.39
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/AGENTS.md +1 -1
- package/CHANGELOG.md +27 -0
- package/README.md +5 -0
- package/agents/analyst.md +11 -11
- package/agents/critic.md +11 -11
- package/agents/executor.md +11 -11
- package/agents/explorer.md +11 -11
- package/agents/planner.md +11 -11
- package/agents/reviewer.md +11 -11
- package/agents/security-reviewer.md +11 -11
- package/agents/test-engineer.md +11 -11
- package/agents/verifier.md +11 -11
- package/agents/writer.md +11 -11
- package/docs/refactor-tasks-phase3.md +394 -394
- package/docs/refactor-tasks-phase4.md +564 -564
- package/docs/refactor-tasks-phase5.md +402 -402
- package/docs/refactor-tasks-phase6.md +662 -662
- package/docs/research-extension-examples.md +297 -297
- package/docs/research-extension-system.md +324 -324
- package/docs/research-optimization-plan.md +548 -548
- package/docs/research-pi-coding-agent.md +357 -357
- package/docs/research-source-pi-crew-reference.md +174 -174
- package/docs/resource-formats.md +10 -8
- package/docs/runtime-flow.md +148 -148
- package/docs/source-runtime-refactor-map.md +83 -83
- package/docs/usage.md +6 -0
- package/index.ts +6 -6
- package/package.json +3 -3
- package/schema.json +2 -2
- package/src/agents/agent-serializer.ts +34 -34
- package/src/config/config.ts +8 -4
- package/src/extension/cross-extension-rpc.ts +82 -82
- package/src/extension/import-index.ts +18 -2
- package/src/extension/register.ts +11 -1
- package/src/extension/registration/compaction-guard.ts +125 -125
- package/src/extension/registration/subagent-helpers.ts +30 -6
- package/src/extension/registration/subagent-tools.ts +8 -3
- package/src/extension/result-watcher.ts +98 -98
- package/src/extension/run-import.ts +12 -2
- package/src/extension/run-index.ts +12 -2
- package/src/extension/run-maintenance.ts +24 -24
- package/src/extension/team-tool/api.ts +54 -14
- package/src/extension/team-tool/cancel.ts +31 -31
- package/src/extension/team-tool/doctor.ts +179 -179
- package/src/extension/team-tool/inspect.ts +41 -41
- package/src/extension/team-tool/lifecycle-actions.ts +79 -79
- package/src/extension/team-tool/plan.ts +19 -19
- package/src/extension/team-tool/status.ts +73 -73
- package/src/observability/correlation.ts +35 -35
- package/src/observability/event-to-metric.ts +54 -54
- package/src/observability/exporters/adapter.ts +24 -24
- package/src/observability/exporters/otlp-exporter.ts +65 -65
- package/src/observability/exporters/prometheus-exporter.ts +47 -47
- package/src/observability/metric-registry.ts +72 -72
- package/src/observability/metric-retention.ts +46 -46
- package/src/observability/metric-sink.ts +51 -51
- package/src/observability/metrics-primitives.ts +166 -166
- package/src/prompt/prompt-runtime.ts +68 -68
- package/src/runtime/agent-control.ts +64 -64
- package/src/runtime/agent-memory.ts +72 -72
- package/src/runtime/agent-observability.ts +114 -113
- package/src/runtime/async-marker.ts +26 -26
- package/src/runtime/background-runner.ts +53 -53
- package/src/runtime/crash-recovery.ts +56 -56
- package/src/runtime/crew-agent-records.ts +54 -9
- package/src/runtime/crew-agent-runtime.ts +58 -58
- package/src/runtime/deadletter.ts +36 -36
- package/src/runtime/direct-run.ts +35 -35
- package/src/runtime/foreground-control.ts +82 -82
- package/src/runtime/green-contract.ts +46 -46
- package/src/runtime/group-join.ts +88 -88
- package/src/runtime/heartbeat-gradient.ts +28 -28
- package/src/runtime/heartbeat-watcher.ts +80 -80
- package/src/runtime/live-agent-control.ts +87 -78
- package/src/runtime/live-agent-manager.ts +85 -85
- package/src/runtime/live-control-realtime.ts +36 -36
- package/src/runtime/live-session-runtime.ts +299 -299
- package/src/runtime/manifest-cache.ts +248 -212
- package/src/runtime/model-fallback.ts +261 -261
- package/src/runtime/parallel-research.ts +44 -44
- package/src/runtime/parallel-utils.ts +99 -99
- package/src/runtime/pi-json-output.ts +111 -111
- package/src/runtime/policy-engine.ts +78 -78
- package/src/runtime/post-exit-stdio-guard.ts +86 -86
- package/src/runtime/process-status.ts +56 -56
- package/src/runtime/progress-event-coalescer.ts +43 -43
- package/src/runtime/recovery-recipes.ts +74 -74
- package/src/runtime/retry-executor.ts +59 -59
- package/src/runtime/role-permission.ts +39 -39
- package/src/runtime/session-usage.ts +79 -79
- package/src/runtime/sidechain-output.ts +28 -28
- package/src/runtime/subagent-manager.ts +80 -12
- package/src/runtime/task-display.ts +38 -38
- package/src/runtime/task-output-context.ts +127 -106
- package/src/runtime/task-runner/live-executor.ts +98 -98
- package/src/runtime/task-runner/progress.ts +111 -111
- package/src/runtime/task-runner/result-utils.ts +14 -14
- package/src/runtime/task-runner/state-helpers.ts +22 -22
- package/src/runtime/team-runner.ts +1 -1
- package/src/runtime/worker-heartbeat.ts +21 -21
- package/src/runtime/worker-startup.ts +57 -57
- package/src/schema/config-schema.ts +21 -21
- package/src/schema/team-tool-schema.ts +100 -100
- package/src/state/artifact-store.ts +122 -108
- package/src/state/contracts.ts +105 -105
- package/src/state/jsonl-writer.ts +77 -77
- package/src/state/mailbox.ts +67 -22
- package/src/state/state-store.ts +36 -5
- package/src/state/task-claims.ts +42 -42
- package/src/state/usage.ts +29 -29
- package/src/subagents/async-entry.ts +1 -1
- package/src/subagents/index.ts +3 -3
- package/src/subagents/live/control.ts +1 -1
- package/src/subagents/live/manager.ts +1 -1
- package/src/subagents/live/realtime.ts +1 -1
- package/src/subagents/live/session-runtime.ts +1 -1
- package/src/subagents/manager.ts +1 -1
- package/src/subagents/spawn.ts +1 -1
- package/src/teams/discover-teams.ts +27 -5
- package/src/teams/team-serializer.ts +38 -36
- package/src/types/diff.d.ts +18 -18
- package/src/ui/crew-footer.ts +101 -101
- package/src/ui/crew-select-list.ts +111 -111
- package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
- package/src/ui/dynamic-border.ts +25 -25
- package/src/ui/layout-primitives.ts +106 -106
- package/src/ui/loaders.ts +158 -158
- package/src/ui/mascot.ts +441 -441
- package/src/ui/render-diff.ts +119 -119
- package/src/ui/run-dashboard.ts +5 -2
- package/src/ui/run-snapshot-cache.ts +19 -8
- package/src/ui/spinner.ts +17 -17
- package/src/ui/status-colors.ts +54 -54
- package/src/ui/syntax-highlight.ts +116 -116
- package/src/ui/transcript-viewer.ts +15 -1
- package/src/utils/completion-dedupe.ts +63 -63
- package/src/utils/file-coalescer.ts +84 -84
- package/src/utils/frontmatter.ts +36 -36
- package/src/utils/fs-watch.ts +31 -31
- package/src/utils/git.ts +262 -262
- package/src/utils/ids.ts +12 -12
- package/src/utils/names.ts +26 -26
- package/src/utils/paths.ts +3 -2
- package/src/utils/safe-paths.ts +34 -0
- package/src/utils/sleep.ts +32 -32
- package/src/utils/timings.ts +31 -31
- package/src/utils/visual.ts +159 -159
- package/src/workflows/discover-workflows.ts +30 -3
- package/src/workflows/validate-workflow.ts +40 -40
- package/src/worktree/branch-freshness.ts +45 -45
- package/teams/default.team.md +12 -12
- package/teams/fast-fix.team.md +11 -11
- package/teams/implementation.team.md +18 -18
- package/teams/parallel-research.team.md +14 -14
- package/teams/research.team.md +11 -11
- package/teams/review.team.md +12 -12
- package/workflows/default.workflow.md +29 -29
- package/workflows/fast-fix.workflow.md +22 -22
- package/workflows/implementation.workflow.md +38 -38
- package/workflows/parallel-research.workflow.md +46 -46
- package/workflows/research.workflow.md +22 -22
- package/workflows/review.workflow.md +30 -30
|
@@ -1,297 +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
|
-
```
|
|
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
|
+
```
|