Add 5 pi extensions: pi-subagents, pi-crew, rpiv-pi, pi-interactive-shell, pi-intercom

This commit is contained in:
2026-05-08 15:59:25 +10:00
parent d0d1d9b045
commit 31b4110c87
457 changed files with 85157 additions and 0 deletions

View File

@@ -0,0 +1,324 @@
# Research: Pi Extension System Deep Dive
> Ngày: 2026-04-29 | Read-only research | Source: `source/pi-mono/packages/coding-agent/src/core/extensions/`
## 1. Extension System Architecture
Pi extension system là plugin framework cho coding agent. Extensions được viết bằng TypeScript,
load qua jiti (JIT compiler), và có thể hook vào mọi phase của agent lifecycle.
```
┌─────────────────────────────────────────────────────────────┐
│ ExtensionAPI ("pi.*") │
│ Event sub: pi.on(event, handler) │
│ Tools: pi.registerTool(def) │
│ Commands: pi.registerCommand(name, opts) │
│ Shortcuts: pi.registerShortcut(key, opts) │
│ Flags: pi.registerFlag(name, opts) │
│ Messages: pi.sendMessage() / pi.sendUserMessage() │
│ State: pi.appendEntry(customType, data) │
│ Provider: pi.registerProvider(name, config) │
│ Event bus: pi.events.emit/on() │
│ Model: pi.setModel() / getThinkingLevel() │
│ Tools mgmt: pi.getActiveTools() / setActiveTools() │
├─────────────────────────────────────────────────────────────┤
│ ExtensionFactory │
│ (pi: ExtensionAPI) => void | Promise<void> │
├─────────────────────────────────────────────────────────────┤
│ loader.ts ──► jiti → TypeScript module loading │
│ runner.ts ──► ExtensionRunner → lifecycle + event emit │
│ types.ts ───► 1545 dòng type definitions │
└─────────────────────────────────────────────────────────────┘
```
## 2. Extension Loading Flow
```
discoverAndLoadExtensions(cwd, agentDir, extensionPaths)
├── Scan directories:
│ ├── ~/.pi/agent/extensions/**/index.ts (user-global)
│ ├── .pi/extensions/**/index.ts (project-local)
│ └── CLI --extension paths (explicit)
├── Create ExtensionRuntime (shared state + action stubs)
├── For each extension file:
│ ├── jiti.import(path) # Load TS module
│ ├── Call default export: factory(pi) # Register handlers/tools/commands
│ └── Collect into Extension object
└── Return LoadExtensionsResult
ExtensionRunner.initialize(session, context, actions)
├── Bind real action implementations to runtime
├── Process queued provider registrations
└── Emit session_start event
```
### 2.1 Discovery priority
Project-local > user-global. Extensions cùng tên: project override user.
### 2.2 Runtime replacement (reload)
Khi `/reload` hoặc session switch:
1. `emitSessionShutdownEvent("reload")`
2. Invalidate old ExtensionRuntime (throws if stale extension tries to act)
3. Re-discover + re-load tất cả extensions
4. Re-initialize ExtensionRunner
## 3. Full Event Lifecycle
### 3.1 Event model (23 event types)
**Session events** — session-level lifecycle:
```
session_start ← Khi session được tạo/load/reload
resources_discover ← Extension có thể inject thêm paths
session_before_switch ← Trước khi switch session (có thể cancel)
session_before_fork ← Trước khi fork session (có thể cancel)
session_before_compact ← Trước khi compaction (có thể cancel hoặc custom)
session_compact ← Sau khi compaction hoàn tất
session_before_tree ← Trước khi navigate tree (có thể cancel)
session_tree ← Sau khi navigate tree
session_shutdown ← Khi session bị hủy (quit/reload/new/resume/fork)
```
**Agent events** — per-prompt:
```
input ← Khi user input received (có thể transform/block)
before_agent_start ← Trước khi agent loop chạy (inject custom message / swap system prompt)
context ← Transform messages trước khi gửi LLM
before_provider_request ← Thay đổi payload trước khi gửi provider
after_provider_response ← Quan sát response status/headers
agent_start ← Agent loop bắt đầu
agent_end ← Agent loop kết thúc
```
**Turn events** — per-turn:
```
turn_start ← Bắt đầu turn mới
turn_end ← Kết thúc turn (có message + tool results)
```
**Message events** — per-message:
```
message_start ← Message bắt đầu (user/assistant/toolResult)
message_update ← Streaming token-by-token update
message_end ← Message hoàn tất
```
**Tool events** — per-tool:
```
tool_call ← Trước khi tool execute (có thể block/mutate args)
tool_execution_start ← Tool bắt đầu chạy
tool_execution_update ← Partial/streaming result
tool_execution_end ← Tool hoàn tất
tool_result ← Sau khi tool execute (có thể modify result)
```
**Other:**
```
model_select ← Khi model được chọn/thay đổi
user_bash ← Khi user dùng ! prefix cho bash
```
### 3.2 Event result contracts
Mỗi event có thể return result để ảnh hưởng đến behavior:
| Event | Result type | Effect |
|---|---|---|
| `input` | `{ action: "continue" \| "transform" \| "handled" }` | Transform/block input |
| `before_agent_start` | `{ message?, systemPrompt? }` | Inject custom message, swap system prompt |
| `context` | `{ messages? }` | Replace context messages |
| `before_provider_request` | `any` | Replace payload |
| `tool_call` | `{ block?, reason? }` | Block tool execution |
| `tool_result` | `{ content?, details?, isError? }` | Modify result |
| `user_bash` | `{ operations?, result? }` | Custom bash execution |
| `session_before_*` | `{ cancel? }` | Cancel session operation |
| `session_before_compact` | `{ cancel?, compaction? }` | Cancel or custom compact |
| `session_before_tree` | `{ cancel?, summary?, customInstructions? }` | Cancel or custom summary |
| `resources_discover` | `{ skillPaths?, promptPaths?, themePaths? }` | Inject resource paths |
## 4. Context Objects Available to Extensions
### 4.1 ExtensionContext (`ctx.*`) — có sẵn trong mọi event handler
```typescript
interface ExtensionContext {
ui: ExtensionUIContext; // UI methods (select, confirm, notify, widgets...)
hasUI: boolean; // false in print/RPC mode
cwd: string; // Current working directory
sessionManager: ReadonlySessionManager; // Session access (read-only)
modelRegistry: ModelRegistry; // Auth + model discovery
model: Model<any> | undefined; // Current model
isIdle(): boolean; // Check if agent is streaming
signal: AbortSignal | undefined;// Current abort signal
abort(): void; // Abort current operation
hasPendingMessages(): boolean; // Check message queue
shutdown(): void; // Graceful shutdown
getContextUsage(): ContextUsage | undefined; // Token usage
compact(options?): void; // Trigger compaction
getSystemPrompt(): string; // Current system prompt
}
```
### 4.2 ExtensionCommandContext — extends Context, chỉ trong command handler
```typescript
interface ExtensionCommandContext extends ExtensionContext {
waitForIdle(): Promise<void>; // Wait for agent to finish
newSession(options?): Promise<{cancelled}>;
fork(entryId, options?): Promise<{cancelled}>;
navigateTree(targetId, options?): Promise<{cancelled}>;
switchSession(sessionPath, options?): Promise<{cancelled}>;
reload(): Promise<void>;
}
```
### 4.3 ReplacedSessionContext — sau khi switch/new session
```typescript
interface ReplacedSessionContext extends ExtensionCommandContext {
sendMessage(message, options?): Promise<void>;
sendUserMessage(content, options?): Promise<void>;
}
```
### 4.4 ExtensionUIContext (`ctx.ui.*`) — chỉ khi `hasUI=true`
```typescript
interface ExtensionUIContext {
select(title, options, opts?): Promise<string | undefined>;
confirm(title, message, opts?): Promise<boolean>;
input(title, placeholder?, opts?): Promise<string | undefined>;
notify(message, type?): void;
custom<T>(factory, options?): Promise<T>; // Custom overlay component
setWidget(key, content, options?): void; // Widget above/below editor
setFooter(factory): void; // Custom footer
setHeader(factory): void; // Custom header
setEditorComponent(factory): void; // Custom editor
setStatus(key, text): void; // Status bar
setTitle(title): void; // Terminal title
setWorkingMessage(message?): void; // Working loader text
setWorkingVisible(visible): void; // Show/hide loader
setWorkingIndicator(options?): void; // Custom loader animation
setHiddenThinkingLabel(label?): void; // Thinking block label
onTerminalInput(handler): () => void; // Raw terminal input
getToolsExpanded(): boolean;
setToolsExpanded(expanded): void;
theme: Theme;
getAllThemes(): {name, path}[];
getTheme(name): Theme | undefined;
setTheme(theme): {success, error?};
}
```
## 5. ToolDefinition Contract
```typescript
interface ToolDefinition<TParams extends TSchema, TDetails = unknown, TState = any> {
name: string; // Unique tool name
label: string; // Human-readable for UI
description: string; // For LLM
parameters: TParams; // TypeBox schema
promptSnippet?: string; // 1-line for system prompt "Available tools"
promptGuidelines?: string[]; // Bullets for system prompt "Guidelines"
renderShell?: "default" | "self"; // Who renders the outer frame
executionMode?: "sequential" | "parallel"; // Concurrency control
prepareArguments?: (args: unknown) => Static<TParams>;
// Core execution
execute(
toolCallId: string,
params: Static<TParams>,
signal: AbortSignal | undefined,
onUpdate: AgentToolUpdateCallback<TDetails> | undefined,
ctx: ExtensionContext,
): Promise<AgentToolResult<TDetails>>;
// Rendering (optional)
renderCall?(args, theme, context): Component; // Custom call display
renderResult?(result, options, theme, context): Component; // Custom result display
}
```
### 5.1 `terminate: true` pattern
Tool có thể set `terminate: true` trong result để kết thúc turn ngay sau tool call,
tiết kiệm 1 follow-up LLM turn:
```typescript
return {
content: [{ type: "text", text: "Done" }],
details: { ... },
terminate: true, // ← Kết thúc turn, không cần LLM follow-up
};
```
## 6. Provider Registration
Extension có thể đăng ký provider tùy chỉnh:
```typescript
pi.registerProvider("my-provider", {
baseUrl: "https://api.example.com",
apiKey: "PROVIDER_API_KEY",
api: "anthropic-messages",
models: [{
id: "my-model",
name: "My Model",
reasoning: false,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 200000,
maxTokens: 16384,
}],
// Optional OAuth:
oauth: {
name: "My Provider (SSO)",
async login(callbacks) { ... },
async refreshToken(credentials) { ... },
getApiKey(credentials) { return credentials.access; },
},
});
```
Hiệu lực ngay lập tức sau `session_start` (không cần `/reload`).
## 7. API Comparison: ExtensionAPI vs ExtensionContext
| Capability | `pi.*` (ExtensionAPI) | `ctx.*` (ExtensionContext) |
|---|---|---|
| Subscribe events | ✅ `pi.on(...)` | ❌ |
| Register tools | ✅ `pi.registerTool()` | ❌ |
| Register commands | ✅ `pi.registerCommand()` | ❌ |
| Register shortcuts | ✅ `pi.registerShortcut()` | ❌ |
| Register flags | ✅ `pi.registerFlag()` | ❌ |
| Register providers | ✅ `pi.registerProvider()` | ❌ |
| Send messages | ✅ `pi.sendMessage()` | ❌ |
| Send user messages | ✅ `pi.sendUserMessage()` | ❌ |
| Append entries | ✅ `pi.appendEntry()` | ❌ |
| Session name | ✅ `pi.setSessionName()` / `getSessionName()` | ❌ |
| Event bus | ✅ `pi.events` | ❌ |
| Get/set active tools | ✅ `pi.getActiveTools()` / `setActiveTools()` | ❌ |
| Get model | ❌ (register-time only) | ✅ `ctx.model` |
| Check idle | ❌ | ✅ `ctx.isIdle()` |
| Abort | ❌ | ✅ `ctx.abort()` |
| Trigger compaction | ❌ | ✅ `ctx.compact()` |
| Context usage | ❌ | ✅ `ctx.getContextUsage()` |
| System prompt | ❌ | ✅ `ctx.getSystemPrompt()` |
| Session manager | ❌ | ✅ `ctx.sessionManager` |
| UI interaction | ❌ | ✅ `ctx.ui` |
| Session control | ❌ | ✅ `ctx.newSession()` / `fork()` (command ctx) |
**Rule of thumb:**
- `pi.*`: Registration-time API (trong factory function, `session_start`)
- `ctx.*`: Runtime API (trong event handlers, command handlers)
## 8. Key Design Decisions
1. **No sandbox** — Extensions run in same Node.js process, full system access
2. **jiti loader** — TypeScript extensions compiled JIT, no build step
3. **Virtual modules** — For Bun compiled binary, built-in dependencies bundled
4. **Throwing stubs** — Runtime actions start as stubs, real implementations bound by runner
5. **Stale detection** — After reload, old extension instances throw on any API call
6. **Event bus** — Separate from extension events, for cross-extension communication