@oh-my-pi/pi-coding-agent 3.15.0 → 3.20.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 (193) hide show
  1. package/CHANGELOG.md +61 -1
  2. package/docs/extensions.md +1055 -0
  3. package/docs/rpc.md +69 -13
  4. package/docs/session-tree-plan.md +1 -1
  5. package/examples/extensions/README.md +141 -0
  6. package/examples/extensions/api-demo.ts +87 -0
  7. package/examples/extensions/chalk-logger.ts +26 -0
  8. package/examples/extensions/hello.ts +33 -0
  9. package/examples/extensions/pirate.ts +44 -0
  10. package/examples/extensions/plan-mode.ts +551 -0
  11. package/examples/extensions/subagent/agents/reviewer.md +35 -0
  12. package/examples/extensions/todo.ts +299 -0
  13. package/examples/extensions/tools.ts +145 -0
  14. package/examples/extensions/with-deps/index.ts +36 -0
  15. package/examples/extensions/with-deps/package-lock.json +31 -0
  16. package/examples/extensions/with-deps/package.json +16 -0
  17. package/examples/sdk/02-custom-model.ts +3 -3
  18. package/examples/sdk/05-tools.ts +7 -3
  19. package/examples/sdk/06-extensions.ts +81 -0
  20. package/examples/sdk/06-hooks.ts +14 -13
  21. package/examples/sdk/08-prompt-templates.ts +42 -0
  22. package/examples/sdk/08-slash-commands.ts +17 -12
  23. package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
  24. package/examples/sdk/12-full-control.ts +6 -6
  25. package/package.json +11 -7
  26. package/src/capability/extension-module.ts +34 -0
  27. package/src/cli/args.ts +22 -7
  28. package/src/cli/file-processor.ts +38 -67
  29. package/src/cli/list-models.ts +1 -1
  30. package/src/config.ts +25 -14
  31. package/src/core/agent-session.ts +505 -242
  32. package/src/core/auth-storage.ts +33 -21
  33. package/src/core/compaction/branch-summarization.ts +4 -4
  34. package/src/core/compaction/compaction.ts +3 -3
  35. package/src/core/custom-commands/bundled/wt/index.ts +430 -0
  36. package/src/core/custom-commands/loader.ts +9 -0
  37. package/src/core/custom-tools/wrapper.ts +5 -0
  38. package/src/core/event-bus.ts +59 -0
  39. package/src/core/export-html/vendor/highlight.min.js +1213 -0
  40. package/src/core/export-html/vendor/marked.min.js +6 -0
  41. package/src/core/extensions/index.ts +100 -0
  42. package/src/core/extensions/loader.ts +501 -0
  43. package/src/core/extensions/runner.ts +477 -0
  44. package/src/core/extensions/types.ts +712 -0
  45. package/src/core/extensions/wrapper.ts +147 -0
  46. package/src/core/hooks/types.ts +2 -2
  47. package/src/core/index.ts +10 -21
  48. package/src/core/keybindings.ts +199 -0
  49. package/src/core/messages.ts +26 -7
  50. package/src/core/model-registry.ts +123 -46
  51. package/src/core/model-resolver.ts +7 -5
  52. package/src/core/prompt-templates.ts +242 -0
  53. package/src/core/sdk.ts +378 -295
  54. package/src/core/session-manager.ts +72 -58
  55. package/src/core/settings-manager.ts +118 -22
  56. package/src/core/system-prompt.ts +24 -1
  57. package/src/core/terminal-notify.ts +37 -0
  58. package/src/core/tools/context.ts +4 -4
  59. package/src/core/tools/exa/mcp-client.ts +5 -4
  60. package/src/core/tools/exa/render.ts +176 -131
  61. package/src/core/tools/gemini-image.ts +361 -0
  62. package/src/core/tools/git.ts +216 -0
  63. package/src/core/tools/index.ts +28 -15
  64. package/src/core/tools/lsp/config.ts +5 -4
  65. package/src/core/tools/lsp/index.ts +17 -12
  66. package/src/core/tools/lsp/render.ts +39 -47
  67. package/src/core/tools/read.ts +66 -29
  68. package/src/core/tools/render-utils.ts +268 -0
  69. package/src/core/tools/renderers.ts +243 -225
  70. package/src/core/tools/task/discovery.ts +2 -2
  71. package/src/core/tools/task/executor.ts +66 -58
  72. package/src/core/tools/task/index.ts +29 -10
  73. package/src/core/tools/task/model-resolver.ts +8 -13
  74. package/src/core/tools/task/omp-command.ts +24 -0
  75. package/src/core/tools/task/render.ts +35 -60
  76. package/src/core/tools/task/types.ts +3 -0
  77. package/src/core/tools/web-fetch.ts +29 -28
  78. package/src/core/tools/web-search/index.ts +6 -5
  79. package/src/core/tools/web-search/providers/exa.ts +6 -5
  80. package/src/core/tools/web-search/render.ts +66 -111
  81. package/src/core/voice-controller.ts +135 -0
  82. package/src/core/voice-supervisor.ts +1003 -0
  83. package/src/core/voice.ts +308 -0
  84. package/src/discovery/builtin.ts +75 -1
  85. package/src/discovery/claude.ts +47 -1
  86. package/src/discovery/codex.ts +54 -2
  87. package/src/discovery/gemini.ts +55 -2
  88. package/src/discovery/helpers.ts +100 -1
  89. package/src/discovery/index.ts +2 -0
  90. package/src/index.ts +14 -9
  91. package/src/lib/worktree/collapse.ts +179 -0
  92. package/src/lib/worktree/constants.ts +14 -0
  93. package/src/lib/worktree/errors.ts +23 -0
  94. package/src/lib/worktree/git.ts +110 -0
  95. package/src/lib/worktree/index.ts +23 -0
  96. package/src/lib/worktree/operations.ts +216 -0
  97. package/src/lib/worktree/session.ts +114 -0
  98. package/src/lib/worktree/stats.ts +67 -0
  99. package/src/main.ts +61 -37
  100. package/src/migrations.ts +37 -7
  101. package/src/modes/interactive/components/bash-execution.ts +6 -4
  102. package/src/modes/interactive/components/custom-editor.ts +55 -0
  103. package/src/modes/interactive/components/custom-message.ts +95 -0
  104. package/src/modes/interactive/components/extensions/extension-list.ts +5 -0
  105. package/src/modes/interactive/components/extensions/inspector-panel.ts +18 -12
  106. package/src/modes/interactive/components/extensions/state-manager.ts +12 -0
  107. package/src/modes/interactive/components/extensions/types.ts +1 -0
  108. package/src/modes/interactive/components/footer.ts +324 -0
  109. package/src/modes/interactive/components/hook-editor.ts +1 -0
  110. package/src/modes/interactive/components/hook-selector.ts +3 -3
  111. package/src/modes/interactive/components/model-selector.ts +7 -6
  112. package/src/modes/interactive/components/oauth-selector.ts +3 -3
  113. package/src/modes/interactive/components/settings-defs.ts +55 -6
  114. package/src/modes/interactive/components/status-line/separators.ts +4 -4
  115. package/src/modes/interactive/components/status-line.ts +45 -35
  116. package/src/modes/interactive/components/tool-execution.ts +95 -23
  117. package/src/modes/interactive/interactive-mode.ts +644 -113
  118. package/src/modes/interactive/theme/defaults/alabaster.json +99 -0
  119. package/src/modes/interactive/theme/defaults/amethyst.json +103 -0
  120. package/src/modes/interactive/theme/defaults/anthracite.json +100 -0
  121. package/src/modes/interactive/theme/defaults/basalt.json +90 -0
  122. package/src/modes/interactive/theme/defaults/birch.json +101 -0
  123. package/src/modes/interactive/theme/defaults/dark-abyss.json +97 -0
  124. package/src/modes/interactive/theme/defaults/dark-aurora.json +94 -0
  125. package/src/modes/interactive/theme/defaults/dark-cavern.json +97 -0
  126. package/src/modes/interactive/theme/defaults/dark-copper.json +94 -0
  127. package/src/modes/interactive/theme/defaults/dark-cosmos.json +96 -0
  128. package/src/modes/interactive/theme/defaults/dark-eclipse.json +97 -0
  129. package/src/modes/interactive/theme/defaults/dark-ember.json +94 -0
  130. package/src/modes/interactive/theme/defaults/dark-equinox.json +96 -0
  131. package/src/modes/interactive/theme/defaults/dark-lavender.json +94 -0
  132. package/src/modes/interactive/theme/defaults/dark-lunar.json +95 -0
  133. package/src/modes/interactive/theme/defaults/dark-midnight.json +94 -0
  134. package/src/modes/interactive/theme/defaults/dark-nebula.json +96 -0
  135. package/src/modes/interactive/theme/defaults/dark-rainforest.json +97 -0
  136. package/src/modes/interactive/theme/defaults/dark-reef.json +97 -0
  137. package/src/modes/interactive/theme/defaults/dark-sakura.json +94 -0
  138. package/src/modes/interactive/theme/defaults/dark-slate.json +94 -0
  139. package/src/modes/interactive/theme/defaults/dark-solstice.json +96 -0
  140. package/src/modes/interactive/theme/defaults/dark-starfall.json +97 -0
  141. package/src/modes/interactive/theme/defaults/dark-swamp.json +96 -0
  142. package/src/modes/interactive/theme/defaults/dark-taiga.json +97 -0
  143. package/src/modes/interactive/theme/defaults/dark-terminal.json +94 -0
  144. package/src/modes/interactive/theme/defaults/dark-tundra.json +97 -0
  145. package/src/modes/interactive/theme/defaults/dark-twilight.json +97 -0
  146. package/src/modes/interactive/theme/defaults/dark-volcanic.json +97 -0
  147. package/src/modes/interactive/theme/defaults/graphite.json +99 -0
  148. package/src/modes/interactive/theme/defaults/index.ts +128 -0
  149. package/src/modes/interactive/theme/defaults/light-aurora-day.json +97 -0
  150. package/src/modes/interactive/theme/defaults/light-canyon.json +97 -0
  151. package/src/modes/interactive/theme/defaults/light-cirrus.json +96 -0
  152. package/src/modes/interactive/theme/defaults/light-coral.json +94 -0
  153. package/src/modes/interactive/theme/defaults/light-dawn.json +96 -0
  154. package/src/modes/interactive/theme/defaults/light-dunes.json +97 -0
  155. package/src/modes/interactive/theme/defaults/light-eucalyptus.json +94 -0
  156. package/src/modes/interactive/theme/defaults/light-frost.json +94 -0
  157. package/src/modes/interactive/theme/defaults/light-glacier.json +97 -0
  158. package/src/modes/interactive/theme/defaults/light-haze.json +96 -0
  159. package/src/modes/interactive/theme/defaults/light-honeycomb.json +94 -0
  160. package/src/modes/interactive/theme/defaults/light-lagoon.json +97 -0
  161. package/src/modes/interactive/theme/defaults/light-lavender.json +94 -0
  162. package/src/modes/interactive/theme/defaults/light-meadow.json +97 -0
  163. package/src/modes/interactive/theme/defaults/light-mint.json +94 -0
  164. package/src/modes/interactive/theme/defaults/light-opal.json +97 -0
  165. package/src/modes/interactive/theme/defaults/light-orchard.json +97 -0
  166. package/src/modes/interactive/theme/defaults/light-paper.json +94 -0
  167. package/src/modes/interactive/theme/defaults/light-prism.json +96 -0
  168. package/src/modes/interactive/theme/defaults/light-sand.json +94 -0
  169. package/src/modes/interactive/theme/defaults/light-savanna.json +97 -0
  170. package/src/modes/interactive/theme/defaults/light-soleil.json +96 -0
  171. package/src/modes/interactive/theme/defaults/light-wetland.json +97 -0
  172. package/src/modes/interactive/theme/defaults/light-zenith.json +95 -0
  173. package/src/modes/interactive/theme/defaults/limestone.json +100 -0
  174. package/src/modes/interactive/theme/defaults/mahogany.json +104 -0
  175. package/src/modes/interactive/theme/defaults/marble.json +99 -0
  176. package/src/modes/interactive/theme/defaults/obsidian.json +90 -0
  177. package/src/modes/interactive/theme/defaults/onyx.json +90 -0
  178. package/src/modes/interactive/theme/defaults/pearl.json +99 -0
  179. package/src/modes/interactive/theme/defaults/porcelain.json +90 -0
  180. package/src/modes/interactive/theme/defaults/quartz.json +102 -0
  181. package/src/modes/interactive/theme/defaults/sandstone.json +101 -0
  182. package/src/modes/interactive/theme/defaults/titanium.json +89 -0
  183. package/src/modes/print-mode.ts +14 -72
  184. package/src/modes/rpc/rpc-client.ts +23 -9
  185. package/src/modes/rpc/rpc-mode.ts +137 -125
  186. package/src/modes/rpc/rpc-types.ts +46 -24
  187. package/src/prompts/task.md +1 -0
  188. package/src/prompts/tools/gemini-image.md +4 -0
  189. package/src/prompts/tools/git.md +9 -0
  190. package/src/prompts/voice-summary.md +12 -0
  191. package/src/utils/image-convert.ts +26 -0
  192. package/src/utils/image-resize.ts +215 -0
  193. package/src/utils/shell-snapshot.ts +22 -20
@@ -0,0 +1,712 @@
1
+ /**
2
+ * Extension system types.
3
+ *
4
+ * Extensions are TypeScript modules that can:
5
+ * - Subscribe to agent lifecycle events
6
+ * - Register LLM-callable tools
7
+ * - Register commands, keyboard shortcuts, and CLI flags
8
+ * - Interact with the user via UI primitives
9
+ */
10
+
11
+ import type { AgentMessage, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
12
+ import type { ImageContent, Model, TextContent, ToolResultMessage } from "@oh-my-pi/pi-ai";
13
+ import type { Component, KeyId, TUI } from "@oh-my-pi/pi-tui";
14
+ import type { Static, TSchema } from "@sinclair/typebox";
15
+ import type { Theme } from "../../modes/interactive/theme/theme";
16
+ import type { CompactionPreparation, CompactionResult } from "../compaction";
17
+ import type { EventBus } from "../event-bus";
18
+ import type { ExecOptions, ExecResult } from "../exec";
19
+ import type { CustomMessage } from "../messages";
20
+ import type { ModelRegistry } from "../model-registry";
21
+ import type {
22
+ BranchSummaryEntry,
23
+ CompactionEntry,
24
+ ReadonlySessionManager,
25
+ SessionEntry,
26
+ SessionManager,
27
+ } from "../session-manager";
28
+ import type { BashToolDetails, FindToolDetails, GrepToolDetails, LsToolDetails, ReadToolDetails } from "../tools";
29
+ import type { EditToolDetails } from "../tools/edit";
30
+
31
+ export type { ExecOptions, ExecResult } from "../exec";
32
+ export type { AgentToolResult, AgentToolUpdateCallback };
33
+
34
+ // ============================================================================
35
+ // UI Context
36
+ // ============================================================================
37
+
38
+ /**
39
+ * UI context for extensions to request interactive UI.
40
+ * Each mode (interactive, RPC, print) provides its own implementation.
41
+ */
42
+ export interface ExtensionUIContext {
43
+ /** Show a selector and return the user's choice. */
44
+ select(title: string, options: string[]): Promise<string | undefined>;
45
+
46
+ /** Show a confirmation dialog. */
47
+ confirm(title: string, message: string): Promise<boolean>;
48
+
49
+ /** Show a text input dialog. */
50
+ input(title: string, placeholder?: string): Promise<string | undefined>;
51
+
52
+ /** Show a notification to the user. */
53
+ notify(message: string, type?: "info" | "warning" | "error"): void;
54
+
55
+ /** Set status text in the footer/status bar. Pass undefined to clear. */
56
+ setStatus(key: string, text: string | undefined): void;
57
+
58
+ /** Set a widget to display above the editor. Accepts string array or component factory. */
59
+ setWidget(key: string, content: string[] | undefined): void;
60
+ setWidget(key: string, content: ((tui: TUI, theme: Theme) => Component & { dispose?(): void }) | undefined): void;
61
+
62
+ /** Set the terminal window/tab title. */
63
+ setTitle(title: string): void;
64
+
65
+ /** Show a custom component with keyboard focus. */
66
+ custom<T>(
67
+ factory: (
68
+ tui: TUI,
69
+ theme: Theme,
70
+ done: (result: T) => void,
71
+ ) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,
72
+ ): Promise<T>;
73
+
74
+ /** Set the text in the core input editor. */
75
+ setEditorText(text: string): void;
76
+
77
+ /** Get the current text from the core input editor. */
78
+ getEditorText(): string;
79
+
80
+ /** Show a multi-line editor for text editing. */
81
+ editor(title: string, prefill?: string): Promise<string | undefined>;
82
+
83
+ /** Get the current theme for styling. */
84
+ readonly theme: Theme;
85
+ }
86
+
87
+ // ============================================================================
88
+ // Extension Context
89
+ // ============================================================================
90
+
91
+ /**
92
+ * Context passed to extension event handlers.
93
+ */
94
+ export interface ExtensionContext {
95
+ /** UI methods for user interaction */
96
+ ui: ExtensionUIContext;
97
+ /** Whether UI is available (false in print/RPC mode) */
98
+ hasUI: boolean;
99
+ /** Current working directory */
100
+ cwd: string;
101
+ /** Session manager (read-only) */
102
+ sessionManager: ReadonlySessionManager;
103
+ /** Model registry for API key resolution */
104
+ modelRegistry: ModelRegistry;
105
+ /** Current model (may be undefined) */
106
+ model: Model<any> | undefined;
107
+ /** Whether the agent is idle (not streaming) */
108
+ isIdle(): boolean;
109
+ /** Abort the current agent operation */
110
+ abort(): void;
111
+ /** Whether there are queued messages waiting */
112
+ hasPendingMessages(): boolean;
113
+ /** @deprecated Use hasPendingMessages() instead */
114
+ hasQueuedMessages(): boolean;
115
+ }
116
+
117
+ /**
118
+ * Extended context for command handlers.
119
+ * Includes session control methods only safe in user-initiated commands.
120
+ */
121
+ export interface ExtensionCommandContext extends ExtensionContext {
122
+ /** Wait for the agent to finish streaming */
123
+ waitForIdle(): Promise<void>;
124
+
125
+ /** Start a new session, optionally with initialization. */
126
+ newSession(options?: {
127
+ parentSession?: string;
128
+ setup?: (sessionManager: SessionManager) => Promise<void>;
129
+ }): Promise<{ cancelled: boolean }>;
130
+
131
+ /** Branch from a specific entry, creating a new session file. */
132
+ branch(entryId: string): Promise<{ cancelled: boolean }>;
133
+
134
+ /** Navigate to a different point in the session tree. */
135
+ navigateTree(targetId: string, options?: { summarize?: boolean }): Promise<{ cancelled: boolean }>;
136
+ }
137
+
138
+ // ============================================================================
139
+ // Tool Types
140
+ // ============================================================================
141
+
142
+ /** Rendering options for tool results */
143
+ export interface ToolRenderResultOptions {
144
+ /** Whether the result view is expanded */
145
+ expanded: boolean;
146
+ /** Whether this is a partial/streaming result */
147
+ isPartial: boolean;
148
+ /** Current spinner frame index for animated elements (optional) */
149
+ spinnerFrame?: number;
150
+ }
151
+
152
+ /** Session event for tool onSession lifecycle */
153
+ export interface ToolSessionEvent {
154
+ /** Reason for the session event */
155
+ reason: "start" | "switch" | "branch" | "tree" | "shutdown";
156
+ /** Previous session file path, or undefined for "start" and "shutdown" */
157
+ previousSessionFile: string | undefined;
158
+ }
159
+
160
+ /**
161
+ * Tool definition for registerTool().
162
+ */
163
+ export interface ToolDefinition<TParams extends TSchema = TSchema, TDetails = unknown> {
164
+ /** Tool name (used in LLM tool calls) */
165
+ name: string;
166
+ /** Human-readable label for UI */
167
+ label: string;
168
+ /** Description for LLM */
169
+ description: string;
170
+ /** Parameter schema (TypeBox) */
171
+ parameters: TParams;
172
+ /** If true, tool is excluded unless explicitly listed in --tools or agent's tools field */
173
+ hidden?: boolean;
174
+
175
+ /** Execute the tool. */
176
+ execute(
177
+ toolCallId: string,
178
+ params: Static<TParams>,
179
+ onUpdate: AgentToolUpdateCallback<TDetails> | undefined,
180
+ ctx: ExtensionContext,
181
+ signal?: AbortSignal,
182
+ ): Promise<AgentToolResult<TDetails>>;
183
+
184
+ /** Called on session lifecycle events - use to reconstruct state or cleanup resources */
185
+ onSession?: (event: ToolSessionEvent, ctx: ExtensionContext) => void | Promise<void>;
186
+
187
+ /** Custom rendering for tool call display */
188
+ renderCall?: (args: Static<TParams>, theme: Theme) => Component;
189
+
190
+ /** Custom rendering for tool result display */
191
+ renderResult?: (result: AgentToolResult<TDetails>, options: ToolRenderResultOptions, theme: Theme) => Component;
192
+ }
193
+
194
+ // ============================================================================
195
+ // Session Events
196
+ // ============================================================================
197
+
198
+ /** Fired on initial session load */
199
+ export interface SessionStartEvent {
200
+ type: "session_start";
201
+ }
202
+
203
+ /** Fired before switching to another session (can be cancelled) */
204
+ export interface SessionBeforeSwitchEvent {
205
+ type: "session_before_switch";
206
+ reason: "new" | "resume";
207
+ targetSessionFile?: string;
208
+ }
209
+
210
+ /** Fired after switching to another session */
211
+ export interface SessionSwitchEvent {
212
+ type: "session_switch";
213
+ reason: "new" | "resume";
214
+ previousSessionFile: string | undefined;
215
+ }
216
+
217
+ /** Fired before branching a session (can be cancelled) */
218
+ export interface SessionBeforeBranchEvent {
219
+ type: "session_before_branch";
220
+ entryId: string;
221
+ }
222
+
223
+ /** Fired after branching a session */
224
+ export interface SessionBranchEvent {
225
+ type: "session_branch";
226
+ previousSessionFile: string | undefined;
227
+ }
228
+
229
+ /** Fired before context compaction (can be cancelled or customized) */
230
+ export interface SessionBeforeCompactEvent {
231
+ type: "session_before_compact";
232
+ preparation: CompactionPreparation;
233
+ branchEntries: SessionEntry[];
234
+ customInstructions?: string;
235
+ signal: AbortSignal;
236
+ }
237
+
238
+ /** Fired after context compaction */
239
+ export interface SessionCompactEvent {
240
+ type: "session_compact";
241
+ compactionEntry: CompactionEntry;
242
+ fromExtension: boolean;
243
+ }
244
+
245
+ /** Fired on process exit */
246
+ export interface SessionShutdownEvent {
247
+ type: "session_shutdown";
248
+ }
249
+
250
+ /** Preparation data for tree navigation */
251
+ export interface TreePreparation {
252
+ targetId: string;
253
+ oldLeafId: string | null;
254
+ commonAncestorId: string | null;
255
+ entriesToSummarize: SessionEntry[];
256
+ userWantsSummary: boolean;
257
+ }
258
+
259
+ /** Fired before navigating in the session tree (can be cancelled) */
260
+ export interface SessionBeforeTreeEvent {
261
+ type: "session_before_tree";
262
+ preparation: TreePreparation;
263
+ signal: AbortSignal;
264
+ }
265
+
266
+ /** Fired after navigating in the session tree */
267
+ export interface SessionTreeEvent {
268
+ type: "session_tree";
269
+ newLeafId: string | null;
270
+ oldLeafId: string | null;
271
+ summaryEntry?: BranchSummaryEntry;
272
+ fromExtension?: boolean;
273
+ }
274
+
275
+ export type SessionEvent =
276
+ | SessionStartEvent
277
+ | SessionBeforeSwitchEvent
278
+ | SessionSwitchEvent
279
+ | SessionBeforeBranchEvent
280
+ | SessionBranchEvent
281
+ | SessionBeforeCompactEvent
282
+ | SessionCompactEvent
283
+ | SessionShutdownEvent
284
+ | SessionBeforeTreeEvent
285
+ | SessionTreeEvent;
286
+
287
+ // ============================================================================
288
+ // Agent Events
289
+ // ============================================================================
290
+
291
+ /** Fired before each LLM call. Can modify messages. */
292
+ export interface ContextEvent {
293
+ type: "context";
294
+ messages: AgentMessage[];
295
+ }
296
+
297
+ /** Fired after user submits prompt but before agent loop. */
298
+ export interface BeforeAgentStartEvent {
299
+ type: "before_agent_start";
300
+ prompt: string;
301
+ images?: ImageContent[];
302
+ }
303
+
304
+ /** Fired when an agent loop starts */
305
+ export interface AgentStartEvent {
306
+ type: "agent_start";
307
+ }
308
+
309
+ /** Fired when an agent loop ends */
310
+ export interface AgentEndEvent {
311
+ type: "agent_end";
312
+ messages: AgentMessage[];
313
+ }
314
+
315
+ /** Fired at the start of each turn */
316
+ export interface TurnStartEvent {
317
+ type: "turn_start";
318
+ turnIndex: number;
319
+ timestamp: number;
320
+ }
321
+
322
+ /** Fired at the end of each turn */
323
+ export interface TurnEndEvent {
324
+ type: "turn_end";
325
+ turnIndex: number;
326
+ message: AgentMessage;
327
+ toolResults: ToolResultMessage[];
328
+ }
329
+
330
+ // ============================================================================
331
+ // Tool Events
332
+ // ============================================================================
333
+
334
+ /** Fired before a tool executes. Can block. */
335
+ export interface ToolCallEvent {
336
+ type: "tool_call";
337
+ toolName: string;
338
+ toolCallId: string;
339
+ input: Record<string, unknown>;
340
+ }
341
+
342
+ interface ToolResultEventBase {
343
+ type: "tool_result";
344
+ toolCallId: string;
345
+ input: Record<string, unknown>;
346
+ content: (TextContent | ImageContent)[];
347
+ isError: boolean;
348
+ }
349
+
350
+ export interface BashToolResultEvent extends ToolResultEventBase {
351
+ toolName: "bash";
352
+ details: BashToolDetails | undefined;
353
+ }
354
+
355
+ export interface ReadToolResultEvent extends ToolResultEventBase {
356
+ toolName: "read";
357
+ details: ReadToolDetails | undefined;
358
+ }
359
+
360
+ export interface EditToolResultEvent extends ToolResultEventBase {
361
+ toolName: "edit";
362
+ details: EditToolDetails | undefined;
363
+ }
364
+
365
+ export interface WriteToolResultEvent extends ToolResultEventBase {
366
+ toolName: "write";
367
+ details: undefined;
368
+ }
369
+
370
+ export interface GrepToolResultEvent extends ToolResultEventBase {
371
+ toolName: "grep";
372
+ details: GrepToolDetails | undefined;
373
+ }
374
+
375
+ export interface FindToolResultEvent extends ToolResultEventBase {
376
+ toolName: "find";
377
+ details: FindToolDetails | undefined;
378
+ }
379
+
380
+ export interface LsToolResultEvent extends ToolResultEventBase {
381
+ toolName: "ls";
382
+ details: LsToolDetails | undefined;
383
+ }
384
+
385
+ export interface CustomToolResultEvent extends ToolResultEventBase {
386
+ toolName: string;
387
+ details: unknown;
388
+ }
389
+
390
+ /** Fired after a tool executes. Can modify result. */
391
+ export type ToolResultEvent =
392
+ | BashToolResultEvent
393
+ | ReadToolResultEvent
394
+ | EditToolResultEvent
395
+ | WriteToolResultEvent
396
+ | GrepToolResultEvent
397
+ | FindToolResultEvent
398
+ | LsToolResultEvent
399
+ | CustomToolResultEvent;
400
+
401
+ // Type guards
402
+ export function isBashToolResult(e: ToolResultEvent): e is BashToolResultEvent {
403
+ return e.toolName === "bash";
404
+ }
405
+ export function isReadToolResult(e: ToolResultEvent): e is ReadToolResultEvent {
406
+ return e.toolName === "read";
407
+ }
408
+ export function isEditToolResult(e: ToolResultEvent): e is EditToolResultEvent {
409
+ return e.toolName === "edit";
410
+ }
411
+ export function isWriteToolResult(e: ToolResultEvent): e is WriteToolResultEvent {
412
+ return e.toolName === "write";
413
+ }
414
+ export function isGrepToolResult(e: ToolResultEvent): e is GrepToolResultEvent {
415
+ return e.toolName === "grep";
416
+ }
417
+ export function isFindToolResult(e: ToolResultEvent): e is FindToolResultEvent {
418
+ return e.toolName === "find";
419
+ }
420
+ export function isLsToolResult(e: ToolResultEvent): e is LsToolResultEvent {
421
+ return e.toolName === "ls";
422
+ }
423
+
424
+ /** Union of all event types */
425
+ export type ExtensionEvent =
426
+ | SessionEvent
427
+ | ContextEvent
428
+ | BeforeAgentStartEvent
429
+ | AgentStartEvent
430
+ | AgentEndEvent
431
+ | TurnStartEvent
432
+ | TurnEndEvent
433
+ | ToolCallEvent
434
+ | ToolResultEvent;
435
+
436
+ // ============================================================================
437
+ // Event Results
438
+ // ============================================================================
439
+
440
+ export interface ContextEventResult {
441
+ messages?: AgentMessage[];
442
+ }
443
+
444
+ export interface ToolCallEventResult {
445
+ block?: boolean;
446
+ reason?: string;
447
+ }
448
+
449
+ export interface ToolResultEventResult {
450
+ content?: (TextContent | ImageContent)[];
451
+ details?: unknown;
452
+ isError?: boolean;
453
+ }
454
+
455
+ export interface BeforeAgentStartEventResult {
456
+ message?: Pick<CustomMessage, "customType" | "content" | "display" | "details">;
457
+ systemPromptAppend?: string;
458
+ }
459
+
460
+ export interface SessionBeforeSwitchResult {
461
+ cancel?: boolean;
462
+ }
463
+
464
+ export interface SessionBeforeBranchResult {
465
+ cancel?: boolean;
466
+ skipConversationRestore?: boolean;
467
+ }
468
+
469
+ export interface SessionBeforeCompactResult {
470
+ cancel?: boolean;
471
+ compaction?: CompactionResult;
472
+ }
473
+
474
+ export interface SessionBeforeTreeResult {
475
+ cancel?: boolean;
476
+ summary?: {
477
+ summary: string;
478
+ details?: unknown;
479
+ };
480
+ }
481
+
482
+ // ============================================================================
483
+ // Message Rendering
484
+ // ============================================================================
485
+
486
+ export interface MessageRenderOptions {
487
+ expanded: boolean;
488
+ }
489
+
490
+ export type MessageRenderer<T = unknown> = (
491
+ message: CustomMessage<T>,
492
+ options: MessageRenderOptions,
493
+ theme: Theme,
494
+ ) => Component | undefined;
495
+
496
+ // ============================================================================
497
+ // Command Registration
498
+ // ============================================================================
499
+
500
+ export interface RegisteredCommand {
501
+ name: string;
502
+ description?: string;
503
+ handler: (args: string, ctx: ExtensionCommandContext) => Promise<void>;
504
+ }
505
+
506
+ // ============================================================================
507
+ // Extension API
508
+ // ============================================================================
509
+
510
+ /** Handler function type for events */
511
+ // biome-ignore lint/suspicious/noConfusingVoidType: void allows bare return statements
512
+ export type ExtensionHandler<E, R = undefined> = (event: E, ctx: ExtensionContext) => Promise<R | void> | R | void;
513
+
514
+ /**
515
+ * ExtensionAPI passed to extension factory functions.
516
+ */
517
+ export interface ExtensionAPI {
518
+ // =========================================================================
519
+ // Module Access
520
+ // =========================================================================
521
+
522
+ /** File logger for error/warning/debug messages */
523
+ logger: typeof import("../logger").logger;
524
+
525
+ /** Injected @sinclair/typebox module for defining tool parameters */
526
+ typebox: typeof import("@sinclair/typebox");
527
+
528
+ /** Injected pi-coding-agent exports for accessing SDK utilities */
529
+ pi: typeof import("../../index.js");
530
+
531
+ // =========================================================================
532
+ // Event Subscription
533
+ // =========================================================================
534
+
535
+ on(event: "session_start", handler: ExtensionHandler<SessionStartEvent>): void;
536
+ on(
537
+ event: "session_before_switch",
538
+ handler: ExtensionHandler<SessionBeforeSwitchEvent, SessionBeforeSwitchResult>,
539
+ ): void;
540
+ on(event: "session_switch", handler: ExtensionHandler<SessionSwitchEvent>): void;
541
+ on(
542
+ event: "session_before_branch",
543
+ handler: ExtensionHandler<SessionBeforeBranchEvent, SessionBeforeBranchResult>,
544
+ ): void;
545
+ on(event: "session_branch", handler: ExtensionHandler<SessionBranchEvent>): void;
546
+ on(
547
+ event: "session_before_compact",
548
+ handler: ExtensionHandler<SessionBeforeCompactEvent, SessionBeforeCompactResult>,
549
+ ): void;
550
+ on(event: "session_compact", handler: ExtensionHandler<SessionCompactEvent>): void;
551
+ on(event: "session_shutdown", handler: ExtensionHandler<SessionShutdownEvent>): void;
552
+ on(event: "session_before_tree", handler: ExtensionHandler<SessionBeforeTreeEvent, SessionBeforeTreeResult>): void;
553
+ on(event: "session_tree", handler: ExtensionHandler<SessionTreeEvent>): void;
554
+ on(event: "context", handler: ExtensionHandler<ContextEvent, ContextEventResult>): void;
555
+ on(event: "before_agent_start", handler: ExtensionHandler<BeforeAgentStartEvent, BeforeAgentStartEventResult>): void;
556
+ on(event: "agent_start", handler: ExtensionHandler<AgentStartEvent>): void;
557
+ on(event: "agent_end", handler: ExtensionHandler<AgentEndEvent>): void;
558
+ on(event: "turn_start", handler: ExtensionHandler<TurnStartEvent>): void;
559
+ on(event: "turn_end", handler: ExtensionHandler<TurnEndEvent>): void;
560
+ on(event: "tool_call", handler: ExtensionHandler<ToolCallEvent, ToolCallEventResult>): void;
561
+ on(event: "tool_result", handler: ExtensionHandler<ToolResultEvent, ToolResultEventResult>): void;
562
+
563
+ // =========================================================================
564
+ // Tool Registration
565
+ // =========================================================================
566
+
567
+ /** Register a tool that the LLM can call. */
568
+ registerTool<TParams extends TSchema = TSchema, TDetails = unknown>(tool: ToolDefinition<TParams, TDetails>): void;
569
+
570
+ // =========================================================================
571
+ // Command, Shortcut, Flag Registration
572
+ // =========================================================================
573
+
574
+ /** Register a custom command. */
575
+ registerCommand(name: string, options: { description?: string; handler: RegisteredCommand["handler"] }): void;
576
+
577
+ /** Register a keyboard shortcut. */
578
+ registerShortcut(
579
+ shortcut: KeyId,
580
+ options: {
581
+ description?: string;
582
+ handler: (ctx: ExtensionContext) => Promise<void> | void;
583
+ },
584
+ ): void;
585
+
586
+ /** Register a CLI flag. */
587
+ registerFlag(
588
+ name: string,
589
+ options: {
590
+ description?: string;
591
+ type: "boolean" | "string";
592
+ default?: boolean | string;
593
+ },
594
+ ): void;
595
+
596
+ /** Get the value of a registered CLI flag. */
597
+ getFlag(name: string): boolean | string | undefined;
598
+
599
+ // =========================================================================
600
+ // Message Rendering
601
+ // =========================================================================
602
+
603
+ /** Register a custom renderer for CustomMessageEntry. */
604
+ registerMessageRenderer<T = unknown>(customType: string, renderer: MessageRenderer<T>): void;
605
+
606
+ // =========================================================================
607
+ // Actions
608
+ // =========================================================================
609
+
610
+ /** Send a custom message to the session. */
611
+ sendMessage<T = unknown>(
612
+ message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details">,
613
+ options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },
614
+ ): void;
615
+
616
+ /** Append a custom entry to the session for state persistence (not sent to LLM). */
617
+ appendEntry<T = unknown>(customType: string, data?: T): void;
618
+
619
+ /** Execute a shell command. */
620
+ exec(command: string, args: string[], options?: ExecOptions): Promise<ExecResult>;
621
+
622
+ /** Get the list of currently active tool names. */
623
+ getActiveTools(): string[];
624
+
625
+ /** Get all configured tools (built-in + extension tools). */
626
+ getAllTools(): string[];
627
+
628
+ /** Set the active tools by name. */
629
+ setActiveTools(toolNames: string[]): void;
630
+
631
+ /** Shared event bus for extension communication. */
632
+ events: EventBus;
633
+ }
634
+
635
+ /** Extension factory function type. */
636
+ export type ExtensionFactory = (pi: ExtensionAPI) => void;
637
+
638
+ // ============================================================================
639
+ // Loaded Extension Types
640
+ // ============================================================================
641
+
642
+ export interface RegisteredTool {
643
+ definition: ToolDefinition;
644
+ extensionPath: string;
645
+ }
646
+
647
+ export interface ExtensionFlag {
648
+ name: string;
649
+ description?: string;
650
+ type: "boolean" | "string";
651
+ default?: boolean | string;
652
+ extensionPath: string;
653
+ }
654
+
655
+ export interface ExtensionShortcut {
656
+ shortcut: KeyId;
657
+ description?: string;
658
+ handler: (ctx: ExtensionContext) => Promise<void> | void;
659
+ extensionPath: string;
660
+ }
661
+
662
+ type HandlerFn = (...args: unknown[]) => Promise<unknown>;
663
+
664
+ export type SendMessageHandler = <T = unknown>(
665
+ message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details">,
666
+ options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },
667
+ ) => void;
668
+
669
+ export type AppendEntryHandler = <T = unknown>(customType: string, data?: T) => void;
670
+
671
+ export type GetActiveToolsHandler = () => string[];
672
+
673
+ export type GetAllToolsHandler = () => string[];
674
+
675
+ export type SetActiveToolsHandler = (toolNames: string[]) => void;
676
+
677
+ /** Loaded extension with all registered items. */
678
+ export interface LoadedExtension {
679
+ path: string;
680
+ resolvedPath: string;
681
+ handlers: Map<string, HandlerFn[]>;
682
+ tools: Map<string, RegisteredTool>;
683
+ messageRenderers: Map<string, MessageRenderer>;
684
+ commands: Map<string, RegisteredCommand>;
685
+ flags: Map<string, ExtensionFlag>;
686
+ flagValues: Map<string, boolean | string>;
687
+ shortcuts: Map<KeyId, ExtensionShortcut>;
688
+ setSendMessageHandler: (handler: SendMessageHandler) => void;
689
+ setAppendEntryHandler: (handler: AppendEntryHandler) => void;
690
+ setGetActiveToolsHandler: (handler: GetActiveToolsHandler) => void;
691
+ setGetAllToolsHandler: (handler: GetAllToolsHandler) => void;
692
+ setSetActiveToolsHandler: (handler: SetActiveToolsHandler) => void;
693
+ setFlagValue: (name: string, value: boolean | string) => void;
694
+ }
695
+
696
+ /** Result of loading extensions. */
697
+ export interface LoadExtensionsResult {
698
+ extensions: LoadedExtension[];
699
+ errors: Array<{ path: string; error: string }>;
700
+ setUIContext(uiContext: ExtensionUIContext, hasUI: boolean): void;
701
+ }
702
+
703
+ // ============================================================================
704
+ // Extension Error
705
+ // ============================================================================
706
+
707
+ export interface ExtensionError {
708
+ extensionPath: string;
709
+ event: string;
710
+ error: string;
711
+ stack?: string;
712
+ }