@astralyn/pi 0.81.1-1 → 0.82.0

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.
Files changed (185) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +690 -677
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +1 -0
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/cli/config-selector.d.ts.map +1 -1
  7. package/dist/cli/config-selector.js +1 -1
  8. package/dist/cli/config-selector.js.map +1 -1
  9. package/dist/cli/startup-ui.d.ts.map +1 -1
  10. package/dist/cli/startup-ui.js +1 -1
  11. package/dist/cli/startup-ui.js.map +1 -1
  12. package/dist/cli.js +0 -0
  13. package/dist/core/agent-session.d.ts +11 -0
  14. package/dist/core/agent-session.d.ts.map +1 -1
  15. package/dist/core/agent-session.js +7 -8
  16. package/dist/core/agent-session.js.map +1 -1
  17. package/dist/core/compaction/compaction.d.ts.map +1 -1
  18. package/dist/core/compaction/compaction.js +11 -3
  19. package/dist/core/compaction/compaction.js.map +1 -1
  20. package/dist/core/extensions/runner.d.ts.map +1 -1
  21. package/dist/core/extensions/runner.js +4 -0
  22. package/dist/core/extensions/runner.js.map +1 -1
  23. package/dist/core/extensions/types.d.ts +9 -1
  24. package/dist/core/extensions/types.d.ts.map +1 -1
  25. package/dist/core/extensions/types.js.map +1 -1
  26. package/dist/core/model-config.d.ts +30 -0
  27. package/dist/core/model-config.d.ts.map +1 -1
  28. package/dist/core/model-config.js +6 -0
  29. package/dist/core/model-config.js.map +1 -1
  30. package/dist/core/model-registry.d.ts.map +1 -1
  31. package/dist/core/model-registry.js +2 -2
  32. package/dist/core/model-registry.js.map +1 -1
  33. package/dist/core/model-resolver.d.ts.map +1 -1
  34. package/dist/core/model-resolver.js +7 -0
  35. package/dist/core/model-resolver.js.map +1 -1
  36. package/dist/core/model-runtime.d.ts +0 -1
  37. package/dist/core/model-runtime.d.ts.map +1 -1
  38. package/dist/core/model-runtime.js +3 -5
  39. package/dist/core/model-runtime.js.map +1 -1
  40. package/dist/core/remote-catalog-provider.d.ts +1 -1
  41. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  42. package/dist/core/remote-catalog-provider.js +5 -10
  43. package/dist/core/remote-catalog-provider.js.map +1 -1
  44. package/dist/core/settings-manager.d.ts +1 -1
  45. package/dist/core/settings-manager.d.ts.map +1 -1
  46. package/dist/core/settings-manager.js.map +1 -1
  47. package/dist/core/system-prompt.d.ts.map +1 -1
  48. package/dist/core/system-prompt.js +1 -1
  49. package/dist/core/system-prompt.js.map +1 -1
  50. package/dist/core/tools/bash.d.ts +2 -1
  51. package/dist/core/tools/bash.d.ts.map +1 -1
  52. package/dist/core/tools/bash.js +114 -24
  53. package/dist/core/tools/bash.js.map +1 -1
  54. package/dist/core/tools/find.d.ts.map +1 -1
  55. package/dist/core/tools/find.js +1 -0
  56. package/dist/core/tools/find.js.map +1 -1
  57. package/dist/core/tools/read.d.ts.map +1 -1
  58. package/dist/core/tools/read.js +4 -3
  59. package/dist/core/tools/read.js.map +1 -1
  60. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  61. package/dist/core/tools/tool-definition-wrapper.js +5 -1
  62. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  63. package/dist/extensions/index.d.ts.map +1 -1
  64. package/dist/extensions/index.js +2 -0
  65. package/dist/extensions/index.js.map +1 -1
  66. package/dist/extensions/llama/provider.d.ts.map +1 -1
  67. package/dist/extensions/llama/provider.js +1 -2
  68. package/dist/extensions/llama/provider.js.map +1 -1
  69. package/dist/extensions/subagent/activity.d.ts +16 -0
  70. package/dist/extensions/subagent/activity.d.ts.map +1 -0
  71. package/dist/extensions/subagent/activity.js +150 -0
  72. package/dist/extensions/subagent/activity.js.map +1 -0
  73. package/dist/extensions/subagent/agents.d.ts +7 -0
  74. package/dist/extensions/subagent/agents.d.ts.map +1 -0
  75. package/dist/extensions/subagent/agents.js +218 -0
  76. package/dist/extensions/subagent/agents.js.map +1 -0
  77. package/dist/extensions/subagent/constants.d.ts +22 -0
  78. package/dist/extensions/subagent/constants.d.ts.map +1 -0
  79. package/dist/extensions/subagent/constants.js +32 -0
  80. package/dist/extensions/subagent/constants.js.map +1 -0
  81. package/dist/extensions/subagent/index.d.ts +3 -0
  82. package/dist/extensions/subagent/index.d.ts.map +1 -0
  83. package/dist/extensions/subagent/index.js +249 -0
  84. package/dist/extensions/subagent/index.js.map +1 -0
  85. package/dist/extensions/subagent/picker.d.ts +31 -0
  86. package/dist/extensions/subagent/picker.d.ts.map +1 -0
  87. package/dist/extensions/subagent/picker.js +116 -0
  88. package/dist/extensions/subagent/picker.js.map +1 -0
  89. package/dist/extensions/subagent/render.d.ts +8 -0
  90. package/dist/extensions/subagent/render.d.ts.map +1 -0
  91. package/dist/extensions/subagent/render.js +322 -0
  92. package/dist/extensions/subagent/render.js.map +1 -0
  93. package/dist/extensions/subagent/resolve.d.ts +16 -0
  94. package/dist/extensions/subagent/resolve.d.ts.map +1 -0
  95. package/dist/extensions/subagent/resolve.js +103 -0
  96. package/dist/extensions/subagent/resolve.js.map +1 -0
  97. package/dist/extensions/subagent/runner.d.ts +30 -0
  98. package/dist/extensions/subagent/runner.d.ts.map +1 -0
  99. package/dist/extensions/subagent/runner.js +355 -0
  100. package/dist/extensions/subagent/runner.js.map +1 -0
  101. package/dist/extensions/subagent/schema.d.ts +28 -0
  102. package/dist/extensions/subagent/schema.d.ts.map +1 -0
  103. package/dist/extensions/subagent/schema.js +33 -0
  104. package/dist/extensions/subagent/schema.js.map +1 -0
  105. package/dist/extensions/subagent/sdk-runner.d.ts +14 -0
  106. package/dist/extensions/subagent/sdk-runner.d.ts.map +1 -0
  107. package/dist/extensions/subagent/sdk-runner.js +210 -0
  108. package/dist/extensions/subagent/sdk-runner.js.map +1 -0
  109. package/dist/extensions/subagent/settings.d.ts +9 -0
  110. package/dist/extensions/subagent/settings.d.ts.map +1 -0
  111. package/dist/extensions/subagent/settings.js +131 -0
  112. package/dist/extensions/subagent/settings.js.map +1 -0
  113. package/dist/extensions/subagent/types.d.ts +99 -0
  114. package/dist/extensions/subagent/types.d.ts.map +1 -0
  115. package/dist/extensions/subagent/types.js +2 -0
  116. package/dist/extensions/subagent/types.js.map +1 -0
  117. package/dist/modes/interactive/components/extension-editor.d.ts +1 -2
  118. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  119. package/dist/modes/interactive/components/extension-editor.js +16 -46
  120. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  121. package/dist/modes/interactive/components/index.d.ts +1 -0
  122. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  123. package/dist/modes/interactive/components/index.js +1 -0
  124. package/dist/modes/interactive/components/index.js.map +1 -1
  125. package/dist/modes/interactive/components/tool-execution.d.ts +2 -0
  126. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  127. package/dist/modes/interactive/components/tool-execution.js +25 -4
  128. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  129. package/dist/modes/interactive/components/tool-group.d.ts +16 -0
  130. package/dist/modes/interactive/components/tool-group.d.ts.map +1 -0
  131. package/dist/modes/interactive/components/tool-group.js +64 -0
  132. package/dist/modes/interactive/components/tool-group.js.map +1 -0
  133. package/dist/modes/interactive/external-editor.d.ts +12 -0
  134. package/dist/modes/interactive/external-editor.d.ts.map +1 -0
  135. package/dist/modes/interactive/external-editor.js +37 -0
  136. package/dist/modes/interactive/external-editor.js.map +1 -0
  137. package/dist/modes/interactive/interactive-mode.d.ts +3 -1
  138. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  139. package/dist/modes/interactive/interactive-mode.js +61 -62
  140. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  141. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  142. package/dist/modes/rpc/rpc-mode.js +1 -0
  143. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  144. package/dist/rpc-entry.js +0 -0
  145. package/dist/utils/clipboard.d.ts.map +1 -1
  146. package/dist/utils/clipboard.js +19 -8
  147. package/dist/utils/clipboard.js.map +1 -1
  148. package/dist/utils/version-check.d.ts.map +1 -1
  149. package/dist/utils/version-check.js +3 -1
  150. package/dist/utils/version-check.js.map +1 -1
  151. package/docs/compaction.md +401 -401
  152. package/docs/containerization.md +111 -111
  153. package/docs/custom-provider.md +766 -763
  154. package/docs/environment-variables.md +88 -0
  155. package/docs/extensions.md +2961 -2953
  156. package/docs/index.md +80 -77
  157. package/docs/models.md +6 -2
  158. package/docs/packages.md +228 -228
  159. package/docs/providers.md +8 -1
  160. package/docs/quickstart.md +165 -165
  161. package/docs/rpc.md +1576 -1559
  162. package/docs/sdk.md +1186 -1186
  163. package/docs/session-format.md +436 -436
  164. package/docs/settings.md +1 -1
  165. package/docs/termux.md +127 -127
  166. package/docs/tui.md +942 -942
  167. package/docs/usage.md +0 -13
  168. package/examples/extensions/README.md +213 -213
  169. package/examples/extensions/custom-compaction.ts +3 -0
  170. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  171. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  172. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  173. package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
  174. package/examples/extensions/doom-overlay/doom/build.sh +0 -0
  175. package/examples/extensions/gondolin/package-lock.json +2 -2
  176. package/examples/extensions/gondolin/package.json +1 -1
  177. package/examples/extensions/handoff.ts +9 -1
  178. package/examples/extensions/sandbox/package-lock.json +2 -2
  179. package/examples/extensions/sandbox/package.json +1 -1
  180. package/examples/extensions/summarize.ts +3 -0
  181. package/examples/extensions/with-deps/package-lock.json +2 -2
  182. package/examples/extensions/with-deps/package.json +1 -1
  183. package/examples/sdk/README.md +140 -140
  184. package/npm-shrinkwrap.json +16 -15
  185. package/package.json +10 -4
package/docs/usage.md CHANGED
@@ -289,19 +289,6 @@ pi --tools read,grep,find,ls -p "Review the code"
289
289
  pi --exclude-tools ask_question
290
290
  ```
291
291
 
292
- ### Environment Variables
293
-
294
- | Variable | Description |
295
- |----------|-------------|
296
- | `PI_CODING_AGENT_DIR` | Override config directory; default is `~/.pi/agent` |
297
- | `PI_CODING_AGENT_SESSION_DIR` | Override session storage directory; overridden by `--session-dir` |
298
- | `PI_PACKAGE_DIR` | Override package directory, useful for Nix/Guix store paths |
299
- | `PI_OFFLINE` | Disable startup network operations, including npm version checks, package update checks, and model refresh |
300
- | `PI_SKIP_VERSION_CHECK` | Skip the Pi version update check at startup. This prevents the npm registry latest-version request |
301
- | `PI_TELEMETRY` | Override optional provider attribution headers: `1`/`true`/`yes` or `0`/`false`/`no`. This does not disable version checks |
302
- | `PI_CACHE_RETENTION` | Set to `long` for extended prompt cache where supported |
303
- | `VISUAL`, `EDITOR` | Fallback external editor for Ctrl+G when `externalEditor` is unset; defaults to Notepad on Windows and `nano` elsewhere |
304
-
305
292
  ## Design Principles
306
293
 
307
294
  Pi keeps the core small and pushes workflow-specific behavior into extensions, skills, prompt templates, and packages.
@@ -1,213 +1,213 @@
1
- # Extension Examples
2
-
3
- Example extensions for pi-coding-agent.
4
-
5
- ## Usage
6
-
7
- ```bash
8
- # Load an extension with --extension flag
9
- pi --extension examples/extensions/permission-gate.ts
10
-
11
- # Or copy to extensions directory for auto-discovery
12
- cp permission-gate.ts ~/.pi/agent/extensions/
13
- ```
14
-
15
- ## Examples
16
-
17
- ### Lifecycle & Safety
18
-
19
- | Extension | Description |
20
- |-----------|-------------|
21
- | `permission-gate.ts` | Prompts for confirmation before dangerous bash commands (rm -rf, sudo, etc.) |
22
- | `project-trust.ts` | Demonstrates the `project_trust` event for user/global and CLI extensions |
23
- | `protected-paths.ts` | Blocks writes to protected paths (.env, .git/, node_modules/) |
24
- | `confirm-destructive.ts` | Confirms before destructive session actions (clear, switch, fork) |
25
- | `dirty-repo-guard.ts` | Prevents session changes with uncommitted git changes |
26
- | `sandbox/` | OS-level sandboxing using `@anthropic-ai/sandbox-runtime` with per-project config |
27
- | `gondolin/` | Route built-in tools and `!` commands into a Gondolin micro-VM |
28
-
29
- ### Custom Tools
30
-
31
- | Extension | Description |
32
- |-----------|-------------|
33
- | `todo.ts` | Todo list tool + `/todos` command with custom rendering and state persistence |
34
- | `hello.ts` | Minimal custom tool example |
35
- | `question.ts` | Demonstrates `ctx.ui.select()` for asking the user questions with custom UI |
36
- | `questionnaire.ts` | Multi-question input with tab bar navigation between questions |
37
- | `tool-override.ts` | Override built-in tools (e.g., add logging/access control to `read`) |
38
- | `dynamic-tools.ts` | Register tools after startup (`session_start`) and at runtime via command, with prompt snippets and tool-specific prompt guidelines |
39
- | `kimi-deferred-tools.ts` | Search for and progressively activate tools for Kimi's deferred-tool loading protocol |
40
- | `structured-output.ts` | Final structured-output tool that returns `terminate: true` so the agent can end on the tool call |
41
- | `built-in-tool-renderer.ts` | Custom compact rendering for built-in tools (read, bash, edit, write) while keeping original behavior |
42
- | `minimal-mode.ts` | Override built-in tool rendering for minimal display (only tool calls, no output in collapsed mode) |
43
- | `truncated-tool.ts` | Wraps ripgrep with proper output truncation (50KB/2000 lines) |
44
- | `ssh.ts` | Delegate all tools to a remote machine via SSH using pluggable operations |
45
- | `subagent/` | Delegate tasks to specialized subagents with isolated context windows |
46
-
47
- ### Commands & UI
48
-
49
- | Extension | Description |
50
- |-----------|-------------|
51
- | `preset.ts` | Named presets for model, thinking level, tools, and instructions via `--preset` flag and `/preset` command |
52
- | `plan-mode/` | Claude Code-style plan mode for read-only exploration with `/plan` command and step tracking |
53
- | `tools.ts` | Interactive `/tools` command to enable/disable tools with session persistence |
54
- | `handoff.ts` | Transfer context to a new focused session via `/handoff <goal>` |
55
- | `qna.ts` | Extracts questions from last response into editor via `ctx.ui.setEditorText()` |
56
- | `status-line.ts` | Shows turn progress in footer via `ctx.ui.setStatus()` with themed colors |
57
- | `github-issue-autocomplete.ts` | Adds `#1234` issue completions by stacking a custom autocomplete provider that preloads open issues from `gh issue list` |
58
- | `widget-placement.ts` | Shows widgets above and below the editor via `ctx.ui.setWidget()` placement |
59
- | `hidden-thinking-label.ts` | Customizes the collapsed thinking label via `ctx.ui.setHiddenThinkingLabel()` |
60
- | `working-indicator.ts` | Customizes the streaming working indicator via `ctx.ui.setWorkingIndicator()` |
61
- | `model-status.ts` | Shows model changes in status bar via `model_select` hook |
62
- | `snake.ts` | Snake game with custom UI, keyboard handling, and session persistence |
63
- | `tic-tac-toe.ts` | Tic-tac-toe vs the agent with `executionMode: "sequential"` tools to prevent race conditions on shared cursor state |
64
- | `send-user-message.ts` | Demonstrates `pi.sendUserMessage()` for sending user messages from extensions |
65
- | `timed-confirm.ts` | Demonstrates AbortSignal for auto-dismissing `ctx.ui.confirm()` and `ctx.ui.select()` dialogs |
66
- | `rpc-demo.ts` | Exercises all RPC-supported extension UI methods; pair with [`examples/rpc-extension-ui.ts`](../rpc-extension-ui.ts) |
67
- | `modal-editor.ts` | Custom vim-like modal editor via `ctx.ui.setEditorComponent()` |
68
- | `rainbow-editor.ts` | Animated rainbow text effect via custom editor |
69
- | `notify.ts` | Desktop notifications via OSC 777 when agent finishes (Ghostty, iTerm2, WezTerm) |
70
- | `titlebar-spinner.ts` | Braille spinner animation in terminal title while the agent is working |
71
- | `summarize.ts` | Summarize conversation with GPT-5.2 and show in transient UI |
72
- | `custom-footer.ts` | Custom footer with git branch and token stats via `ctx.ui.setFooter()` |
73
- | `custom-header.ts` | Custom header via `ctx.ui.setHeader()` |
74
- | `overlay-test.ts` | Test overlay compositing with inline text inputs and edge cases |
75
- | `overlay-qa-tests.ts` | Comprehensive overlay QA tests: anchors, margins, stacking, overflow, animation |
76
- | `doom-overlay/` | DOOM game running as an overlay at 35 FPS (demonstrates real-time game rendering) |
77
- | `shutdown-command.ts` | Adds `/quit` command demonstrating `ctx.shutdown()` |
78
- | `reload-runtime.ts` | Adds `/reload-runtime` and `reload_runtime` tool showing safe reload flow |
79
- | `interactive-shell.ts` | Run interactive commands (vim, htop) with full terminal via `user_bash` hook |
80
- | `inline-bash.ts` | Expands `!{command}` patterns in prompts via `input` event transformation |
81
- | `input-transform-streaming.ts` | Skips expensive input preprocessing for mid-stream steering via `streamingBehavior` |
82
-
83
- ### Git Integration
84
-
85
- | Extension | Description |
86
- |-----------|-------------|
87
- | `git-checkpoint.ts` | Creates git stash checkpoints at each turn for code restoration on fork |
88
- | `auto-commit-on-exit.ts` | Auto-commits on exit using last assistant message for commit message |
89
-
90
- ### System Prompt & Compaction
91
-
92
- | Extension | Description |
93
- |-----------|-------------|
94
- | `pirate.ts` | Demonstrates `systemPromptAppend` to dynamically modify system prompt |
95
- | `claude-rules.ts` | Scans `.claude/rules/` folder and lists rules in system prompt |
96
- | `custom-compaction.ts` | Custom compaction that summarizes entire conversation |
97
- | `trigger-compact.ts` | Triggers compaction when context usage exceeds 100k tokens and adds `/trigger-compact` command |
98
-
99
- ### System Integration
100
-
101
- | Extension | Description |
102
- |-----------|-------------|
103
- | `mac-system-theme.ts` | Syncs pi theme with macOS dark/light mode |
104
-
105
- ### Resources
106
-
107
- | Extension | Description |
108
- |-----------|-------------|
109
- | `dynamic-resources/` | Loads skills, prompts, and themes using `resources_discover` |
110
-
111
- ### Messages & Communication
112
-
113
- | Extension | Description |
114
- |-----------|-------------|
115
- | `message-renderer.ts` | Custom message rendering with colors and expandable details via `registerMessageRenderer` |
116
- | `entry-renderer.ts` | TUI-only session entry rendering via `appendEntry` and `registerEntryRenderer` |
117
- | `event-bus.ts` | Inter-extension communication via `pi.events` |
118
-
119
- ### Session Metadata
120
-
121
- | Extension | Description |
122
- |-----------|-------------|
123
- | `session-name.ts` | Name sessions for the session selector via `setSessionName` |
124
- | `bookmark.ts` | Bookmark entries with labels for `/tree` navigation via `setLabel` |
125
-
126
- ### Custom Providers
127
-
128
- | Extension | Description |
129
- |-----------|-------------|
130
- | `custom-provider-anthropic/` | Custom Anthropic provider with OAuth support and custom streaming implementation |
131
- | `custom-provider-gitlab-duo/` | GitLab Duo provider using pi-ai's built-in Anthropic/OpenAI streaming via proxy |
132
-
133
- ### External Dependencies
134
-
135
- | Extension | Description |
136
- |-----------|-------------|
137
- | `with-deps/` | Extension with its own package.json and dependencies (demonstrates jiti module resolution) |
138
- | `file-trigger.ts` | Watches a trigger file and injects contents into conversation |
139
-
140
- ## Writing Extensions
141
-
142
- See [docs/extensions.md](../../docs/extensions.md) for full documentation.
143
-
144
- ```typescript
145
- import type { ExtensionAPI } from "@astralyn/pi";
146
- import { Type } from "typebox";
147
-
148
- export default function (pi: ExtensionAPI) {
149
- // Subscribe to lifecycle events
150
- pi.on("tool_call", async (event, ctx) => {
151
- if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
152
- const ok = await ctx.ui.confirm("Dangerous!", "Allow rm -rf?");
153
- if (!ok) return { block: true, reason: "Blocked by user" };
154
- }
155
- });
156
-
157
- // Register custom tools
158
- pi.registerTool({
159
- name: "greet",
160
- label: "Greeting",
161
- description: "Generate a greeting",
162
- parameters: Type.Object({
163
- name: Type.String({ description: "Name to greet" }),
164
- }),
165
- async execute(toolCallId, params, signal, onUpdate, ctx) {
166
- return {
167
- content: [{ type: "text", text: `Hello, ${params.name}!` }],
168
- details: {},
169
- };
170
- },
171
- });
172
-
173
- // Register commands
174
- pi.registerCommand("hello", {
175
- description: "Say hello",
176
- handler: async (args, ctx) => {
177
- ctx.ui.notify("Hello!", "info");
178
- },
179
- });
180
- }
181
- ```
182
-
183
- ## Key Patterns
184
-
185
- **Use StringEnum for string parameters** (required for Google API compatibility):
186
- ```typescript
187
- import { StringEnum } from "@earendil-works/pi-ai";
188
-
189
- // Good
190
- action: StringEnum(["list", "add"] as const)
191
-
192
- // Bad - doesn't work with Google
193
- action: Type.Union([Type.Literal("list"), Type.Literal("add")])
194
- ```
195
-
196
- **State persistence via details:**
197
- ```typescript
198
- // Store state in tool result details for proper forking support
199
- return {
200
- content: [{ type: "text", text: "Done" }],
201
- details: { todos: [...todos], nextId }, // Persisted in session
202
- };
203
-
204
- // Reconstruct on session events
205
- pi.on("session_start", async (_event, ctx) => {
206
- for (const entry of ctx.sessionManager.getBranch()) {
207
- if (entry.type === "message" && entry.message.toolName === "my_tool") {
208
- const details = entry.message.details;
209
- // Reconstruct state from details
210
- }
211
- }
212
- });
213
- ```
1
+ # Extension Examples
2
+
3
+ Example extensions for pi-coding-agent.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ # Load an extension with --extension flag
9
+ pi --extension examples/extensions/permission-gate.ts
10
+
11
+ # Or copy to extensions directory for auto-discovery
12
+ cp permission-gate.ts ~/.pi/agent/extensions/
13
+ ```
14
+
15
+ ## Examples
16
+
17
+ ### Lifecycle & Safety
18
+
19
+ | Extension | Description |
20
+ |-----------|-------------|
21
+ | `permission-gate.ts` | Prompts for confirmation before dangerous bash commands (rm -rf, sudo, etc.) |
22
+ | `project-trust.ts` | Demonstrates the `project_trust` event for user/global and CLI extensions |
23
+ | `protected-paths.ts` | Blocks writes to protected paths (.env, .git/, node_modules/) |
24
+ | `confirm-destructive.ts` | Confirms before destructive session actions (clear, switch, fork) |
25
+ | `dirty-repo-guard.ts` | Prevents session changes with uncommitted git changes |
26
+ | `sandbox/` | OS-level sandboxing using `@anthropic-ai/sandbox-runtime` with per-project config |
27
+ | `gondolin/` | Route built-in tools and `!` commands into a Gondolin micro-VM |
28
+
29
+ ### Custom Tools
30
+
31
+ | Extension | Description |
32
+ |-----------|-------------|
33
+ | `todo.ts` | Todo list tool + `/todos` command with custom rendering and state persistence |
34
+ | `hello.ts` | Minimal custom tool example |
35
+ | `question.ts` | Demonstrates `ctx.ui.select()` for asking the user questions with custom UI |
36
+ | `questionnaire.ts` | Multi-question input with tab bar navigation between questions |
37
+ | `tool-override.ts` | Override built-in tools (e.g., add logging/access control to `read`) |
38
+ | `dynamic-tools.ts` | Register tools after startup (`session_start`) and at runtime via command, with prompt snippets and tool-specific prompt guidelines |
39
+ | `kimi-deferred-tools.ts` | Search for and progressively activate tools for Kimi's deferred-tool loading protocol |
40
+ | `structured-output.ts` | Final structured-output tool that returns `terminate: true` so the agent can end on the tool call |
41
+ | `built-in-tool-renderer.ts` | Custom compact rendering for built-in tools (read, bash, edit, write) while keeping original behavior |
42
+ | `minimal-mode.ts` | Override built-in tool rendering for minimal display (only tool calls, no output in collapsed mode) |
43
+ | `truncated-tool.ts` | Wraps ripgrep with proper output truncation (50KB/2000 lines) |
44
+ | `ssh.ts` | Delegate all tools to a remote machine via SSH using pluggable operations |
45
+ | `subagent/` | Delegate tasks to specialized subagents with isolated context windows |
46
+
47
+ ### Commands & UI
48
+
49
+ | Extension | Description |
50
+ |-----------|-------------|
51
+ | `preset.ts` | Named presets for model, thinking level, tools, and instructions via `--preset` flag and `/preset` command |
52
+ | `plan-mode/` | Claude Code-style plan mode for read-only exploration with `/plan` command and step tracking |
53
+ | `tools.ts` | Interactive `/tools` command to enable/disable tools with session persistence |
54
+ | `handoff.ts` | Transfer context to a new focused session via `/handoff <goal>` |
55
+ | `qna.ts` | Extracts questions from last response into editor via `ctx.ui.setEditorText()` |
56
+ | `status-line.ts` | Shows turn progress in footer via `ctx.ui.setStatus()` with themed colors |
57
+ | `github-issue-autocomplete.ts` | Adds `#1234` issue completions by stacking a custom autocomplete provider that preloads open issues from `gh issue list` |
58
+ | `widget-placement.ts` | Shows widgets above and below the editor via `ctx.ui.setWidget()` placement |
59
+ | `hidden-thinking-label.ts` | Customizes the collapsed thinking label via `ctx.ui.setHiddenThinkingLabel()` |
60
+ | `working-indicator.ts` | Customizes the streaming working indicator via `ctx.ui.setWorkingIndicator()` |
61
+ | `model-status.ts` | Shows model changes in status bar via `model_select` hook |
62
+ | `snake.ts` | Snake game with custom UI, keyboard handling, and session persistence |
63
+ | `tic-tac-toe.ts` | Tic-tac-toe vs the agent with `executionMode: "sequential"` tools to prevent race conditions on shared cursor state |
64
+ | `send-user-message.ts` | Demonstrates `pi.sendUserMessage()` for sending user messages from extensions |
65
+ | `timed-confirm.ts` | Demonstrates AbortSignal for auto-dismissing `ctx.ui.confirm()` and `ctx.ui.select()` dialogs |
66
+ | `rpc-demo.ts` | Exercises all RPC-supported extension UI methods; pair with [`examples/rpc-extension-ui.ts`](../rpc-extension-ui.ts) |
67
+ | `modal-editor.ts` | Custom vim-like modal editor via `ctx.ui.setEditorComponent()` |
68
+ | `rainbow-editor.ts` | Animated rainbow text effect via custom editor |
69
+ | `notify.ts` | Desktop notifications via OSC 777 when agent finishes (Ghostty, iTerm2, WezTerm) |
70
+ | `titlebar-spinner.ts` | Braille spinner animation in terminal title while the agent is working |
71
+ | `summarize.ts` | Summarize conversation with GPT-5.2 and show in transient UI |
72
+ | `custom-footer.ts` | Custom footer with git branch and token stats via `ctx.ui.setFooter()` |
73
+ | `custom-header.ts` | Custom header via `ctx.ui.setHeader()` |
74
+ | `overlay-test.ts` | Test overlay compositing with inline text inputs and edge cases |
75
+ | `overlay-qa-tests.ts` | Comprehensive overlay QA tests: anchors, margins, stacking, overflow, animation |
76
+ | `doom-overlay/` | DOOM game running as an overlay at 35 FPS (demonstrates real-time game rendering) |
77
+ | `shutdown-command.ts` | Adds `/quit` command demonstrating `ctx.shutdown()` |
78
+ | `reload-runtime.ts` | Adds `/reload-runtime` and `reload_runtime` tool showing safe reload flow |
79
+ | `interactive-shell.ts` | Run interactive commands (vim, htop) with full terminal via `user_bash` hook |
80
+ | `inline-bash.ts` | Expands `!{command}` patterns in prompts via `input` event transformation |
81
+ | `input-transform-streaming.ts` | Skips expensive input preprocessing for mid-stream steering via `streamingBehavior` |
82
+
83
+ ### Git Integration
84
+
85
+ | Extension | Description |
86
+ |-----------|-------------|
87
+ | `git-checkpoint.ts` | Creates git stash checkpoints at each turn for code restoration on fork |
88
+ | `auto-commit-on-exit.ts` | Auto-commits on exit using last assistant message for commit message |
89
+
90
+ ### System Prompt & Compaction
91
+
92
+ | Extension | Description |
93
+ |-----------|-------------|
94
+ | `pirate.ts` | Demonstrates `systemPromptAppend` to dynamically modify system prompt |
95
+ | `claude-rules.ts` | Scans `.claude/rules/` folder and lists rules in system prompt |
96
+ | `custom-compaction.ts` | Custom compaction that summarizes entire conversation |
97
+ | `trigger-compact.ts` | Triggers compaction when context usage exceeds 100k tokens and adds `/trigger-compact` command |
98
+
99
+ ### System Integration
100
+
101
+ | Extension | Description |
102
+ |-----------|-------------|
103
+ | `mac-system-theme.ts` | Syncs pi theme with macOS dark/light mode |
104
+
105
+ ### Resources
106
+
107
+ | Extension | Description |
108
+ |-----------|-------------|
109
+ | `dynamic-resources/` | Loads skills, prompts, and themes using `resources_discover` |
110
+
111
+ ### Messages & Communication
112
+
113
+ | Extension | Description |
114
+ |-----------|-------------|
115
+ | `message-renderer.ts` | Custom message rendering with colors and expandable details via `registerMessageRenderer` |
116
+ | `entry-renderer.ts` | TUI-only session entry rendering via `appendEntry` and `registerEntryRenderer` |
117
+ | `event-bus.ts` | Inter-extension communication via `pi.events` |
118
+
119
+ ### Session Metadata
120
+
121
+ | Extension | Description |
122
+ |-----------|-------------|
123
+ | `session-name.ts` | Name sessions for the session selector via `setSessionName` |
124
+ | `bookmark.ts` | Bookmark entries with labels for `/tree` navigation via `setLabel` |
125
+
126
+ ### Custom Providers
127
+
128
+ | Extension | Description |
129
+ |-----------|-------------|
130
+ | `custom-provider-anthropic/` | Custom Anthropic provider with OAuth support and custom streaming implementation |
131
+ | `custom-provider-gitlab-duo/` | GitLab Duo provider using pi-ai's built-in Anthropic/OpenAI streaming via proxy |
132
+
133
+ ### External Dependencies
134
+
135
+ | Extension | Description |
136
+ |-----------|-------------|
137
+ | `with-deps/` | Extension with its own package.json and dependencies (demonstrates jiti module resolution) |
138
+ | `file-trigger.ts` | Watches a trigger file and injects contents into conversation |
139
+
140
+ ## Writing Extensions
141
+
142
+ See [docs/extensions.md](../../docs/extensions.md) for full documentation.
143
+
144
+ ```typescript
145
+ import type { ExtensionAPI } from "@astralyn/pi";
146
+ import { Type } from "typebox";
147
+
148
+ export default function (pi: ExtensionAPI) {
149
+ // Subscribe to lifecycle events
150
+ pi.on("tool_call", async (event, ctx) => {
151
+ if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
152
+ const ok = await ctx.ui.confirm("Dangerous!", "Allow rm -rf?");
153
+ if (!ok) return { block: true, reason: "Blocked by user" };
154
+ }
155
+ });
156
+
157
+ // Register custom tools
158
+ pi.registerTool({
159
+ name: "greet",
160
+ label: "Greeting",
161
+ description: "Generate a greeting",
162
+ parameters: Type.Object({
163
+ name: Type.String({ description: "Name to greet" }),
164
+ }),
165
+ async execute(toolCallId, params, signal, onUpdate, ctx) {
166
+ return {
167
+ content: [{ type: "text", text: `Hello, ${params.name}!` }],
168
+ details: {},
169
+ };
170
+ },
171
+ });
172
+
173
+ // Register commands
174
+ pi.registerCommand("hello", {
175
+ description: "Say hello",
176
+ handler: async (args, ctx) => {
177
+ ctx.ui.notify("Hello!", "info");
178
+ },
179
+ });
180
+ }
181
+ ```
182
+
183
+ ## Key Patterns
184
+
185
+ **Use StringEnum for string parameters** (required for Google API compatibility):
186
+ ```typescript
187
+ import { StringEnum } from "@earendil-works/pi-ai";
188
+
189
+ // Good
190
+ action: StringEnum(["list", "add"] as const)
191
+
192
+ // Bad - doesn't work with Google
193
+ action: Type.Union([Type.Literal("list"), Type.Literal("add")])
194
+ ```
195
+
196
+ **State persistence via details:**
197
+ ```typescript
198
+ // Store state in tool result details for proper forking support
199
+ return {
200
+ content: [{ type: "text", text: "Done" }],
201
+ details: { todos: [...todos], nextId }, // Persisted in session
202
+ };
203
+
204
+ // Reconstruct on session events
205
+ pi.on("session_start", async (_event, ctx) => {
206
+ for (const entry of ctx.sessionManager.getBranch()) {
207
+ if (entry.type === "message" && entry.message.toolName === "my_tool") {
208
+ const details = entry.message.details;
209
+ // Reconstruct state from details
210
+ }
211
+ }
212
+ });
213
+ ```
@@ -15,6 +15,7 @@
15
15
 
16
16
  import type { ExtensionAPI } from "@astralyn/pi";
17
17
  import { convertToLlm, serializeConversation } from "@astralyn/pi";
18
+ import { uuidv7 } from "@earendil-works/pi-ai";
18
19
  import { complete } from "@earendil-works/pi-ai/compat";
19
20
 
20
21
  export default function (pi: ExtensionAPI) {
@@ -96,6 +97,8 @@ ${conversationText}
96
97
  env: auth.env,
97
98
  maxTokens: 8192,
98
99
  signal,
100
+ cacheRetention: "none",
101
+ sessionId: uuidv7(),
99
102
  },
100
103
  );
101
104
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider",
3
- "version": "0.81.1",
3
+ "version": "0.82.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-custom-provider",
9
- "version": "0.81.1",
9
+ "version": "0.82.0",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sdk": "^0.52.0"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-anthropic",
3
3
  "private": true,
4
- "version": "0.81.1",
4
+ "version": "0.82.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-gitlab-duo",
3
3
  "private": true,
4
- "version": "0.81.1",
4
+ "version": "0.82.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
File without changes
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-gondolin",
3
- "version": "0.81.1",
3
+ "version": "0.82.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-gondolin",
9
- "version": "0.81.1",
9
+ "version": "0.82.0",
10
10
  "dependencies": {
11
11
  "@earendil-works/gondolin": "0.12.0"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-gondolin",
3
3
  "private": true,
4
- "version": "0.81.1",
4
+ "version": "0.82.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -15,6 +15,7 @@
15
15
  import type { ExtensionAPI, SessionEntry } from "@astralyn/pi";
16
16
  import { BorderedLoader, convertToLlm, serializeConversation } from "@astralyn/pi";
17
17
  import type { AgentMessage } from "@earendil-works/pi-agent-core";
18
+ import { uuidv7 } from "@earendil-works/pi-ai";
18
19
  import { complete, type Message } from "@earendil-works/pi-ai/compat";
19
20
 
20
21
  const SYSTEM_PROMPT = `You are a context transfer assistant. Given a conversation history and the user's goal for a new thread, generate a focused prompt that:
@@ -136,7 +137,14 @@ export default function (pi: ExtensionAPI) {
136
137
  const response = await complete(
137
138
  ctx.model!,
138
139
  { systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
139
- { apiKey: auth.apiKey, headers: auth.headers, env: auth.env, signal: loader.signal },
140
+ {
141
+ apiKey: auth.apiKey,
142
+ headers: auth.headers,
143
+ env: auth.env,
144
+ signal: loader.signal,
145
+ cacheRetention: "none",
146
+ sessionId: uuidv7(),
147
+ },
140
148
  );
141
149
 
142
150
  if (response.stopReason === "aborted") {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-sandbox",
3
- "version": "1.11.1",
3
+ "version": "1.12.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-sandbox",
9
- "version": "1.11.1",
9
+ "version": "1.12.0",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sandbox-runtime": "^0.0.26"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-sandbox",
3
3
  "private": true,
4
- "version": "1.11.1",
4
+ "version": "1.12.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,5 +1,6 @@
1
1
  import type { ExtensionAPI, ExtensionCommandContext } from "@astralyn/pi";
2
2
  import { DynamicBorder, getMarkdownTheme } from "@astralyn/pi";
3
+ import { uuidv7 } from "@earendil-works/pi-ai";
3
4
  import { complete, getModel } from "@earendil-works/pi-ai/compat";
4
5
  import { Container, Markdown, matchesKey, Text } from "@earendil-works/pi-tui";
5
6
 
@@ -193,6 +194,8 @@ export default function (pi: ExtensionAPI) {
193
194
  headers: auth.headers,
194
195
  env: auth.env,
195
196
  reasoningEffort: "high",
197
+ cacheRetention: "none",
198
+ sessionId: uuidv7(),
196
199
  },
197
200
  );
198
201
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
- "version": "0.81.1",
3
+ "version": "0.82.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-with-deps",
9
- "version": "0.81.1",
9
+ "version": "0.82.0",
10
10
  "dependencies": {
11
11
  "ms": "^2.1.3"
12
12
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
3
  "private": true,
4
- "version": "0.81.1",
4
+ "version": "0.82.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",