@oh-my-pi/pi-coding-agent 3.15.1 → 3.20.1

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 (129) hide show
  1. package/CHANGELOG.md +60 -0
  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/find.ts +7 -1
  62. package/src/core/tools/gemini-image.ts +361 -0
  63. package/src/core/tools/git.ts +216 -0
  64. package/src/core/tools/index.ts +28 -15
  65. package/src/core/tools/ls.ts +9 -2
  66. package/src/core/tools/lsp/config.ts +5 -4
  67. package/src/core/tools/lsp/index.ts +17 -12
  68. package/src/core/tools/lsp/render.ts +39 -47
  69. package/src/core/tools/read.ts +66 -29
  70. package/src/core/tools/render-utils.ts +268 -0
  71. package/src/core/tools/renderers.ts +243 -225
  72. package/src/core/tools/task/discovery.ts +2 -2
  73. package/src/core/tools/task/executor.ts +66 -58
  74. package/src/core/tools/task/index.ts +29 -10
  75. package/src/core/tools/task/model-resolver.ts +8 -13
  76. package/src/core/tools/task/omp-command.ts +24 -0
  77. package/src/core/tools/task/render.ts +37 -62
  78. package/src/core/tools/task/types.ts +3 -0
  79. package/src/core/tools/web-fetch.ts +29 -28
  80. package/src/core/tools/web-search/index.ts +6 -5
  81. package/src/core/tools/web-search/providers/exa.ts +6 -5
  82. package/src/core/tools/web-search/render.ts +66 -111
  83. package/src/core/voice-controller.ts +135 -0
  84. package/src/core/voice-supervisor.ts +1003 -0
  85. package/src/core/voice.ts +308 -0
  86. package/src/discovery/builtin.ts +75 -1
  87. package/src/discovery/claude.ts +47 -1
  88. package/src/discovery/codex.ts +54 -2
  89. package/src/discovery/gemini.ts +55 -2
  90. package/src/discovery/helpers.ts +100 -1
  91. package/src/discovery/index.ts +2 -0
  92. package/src/index.ts +14 -9
  93. package/src/lib/worktree/collapse.ts +179 -0
  94. package/src/lib/worktree/constants.ts +14 -0
  95. package/src/lib/worktree/errors.ts +23 -0
  96. package/src/lib/worktree/git.ts +110 -0
  97. package/src/lib/worktree/index.ts +23 -0
  98. package/src/lib/worktree/operations.ts +216 -0
  99. package/src/lib/worktree/session.ts +114 -0
  100. package/src/lib/worktree/stats.ts +67 -0
  101. package/src/main.ts +61 -37
  102. package/src/migrations.ts +37 -7
  103. package/src/modes/interactive/components/bash-execution.ts +6 -4
  104. package/src/modes/interactive/components/custom-editor.ts +55 -0
  105. package/src/modes/interactive/components/custom-message.ts +95 -0
  106. package/src/modes/interactive/components/extensions/extension-list.ts +5 -0
  107. package/src/modes/interactive/components/extensions/inspector-panel.ts +18 -12
  108. package/src/modes/interactive/components/extensions/state-manager.ts +12 -0
  109. package/src/modes/interactive/components/extensions/types.ts +1 -0
  110. package/src/modes/interactive/components/footer.ts +324 -0
  111. package/src/modes/interactive/components/hook-selector.ts +3 -3
  112. package/src/modes/interactive/components/model-selector.ts +7 -6
  113. package/src/modes/interactive/components/oauth-selector.ts +3 -3
  114. package/src/modes/interactive/components/settings-defs.ts +55 -6
  115. package/src/modes/interactive/components/status-line.ts +45 -37
  116. package/src/modes/interactive/components/tool-execution.ts +95 -23
  117. package/src/modes/interactive/interactive-mode.ts +643 -113
  118. package/src/modes/interactive/theme/defaults/index.ts +16 -16
  119. package/src/modes/print-mode.ts +14 -72
  120. package/src/modes/rpc/rpc-client.ts +23 -9
  121. package/src/modes/rpc/rpc-mode.ts +137 -125
  122. package/src/modes/rpc/rpc-types.ts +46 -24
  123. package/src/prompts/task.md +1 -0
  124. package/src/prompts/tools/gemini-image.md +4 -0
  125. package/src/prompts/tools/git.md +9 -0
  126. package/src/prompts/voice-summary.md +12 -0
  127. package/src/utils/image-convert.ts +26 -0
  128. package/src/utils/image-resize.ts +215 -0
  129. package/src/utils/shell-snapshot.ts +22 -20
@@ -0,0 +1,477 @@
1
+ /**
2
+ * Extension runner - executes extensions and manages their lifecycle.
3
+ */
4
+
5
+ import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
6
+ import type { ImageContent, Model } from "@oh-my-pi/pi-ai";
7
+ import type { KeyId } from "@oh-my-pi/pi-tui";
8
+ import { theme } from "../../modes/interactive/theme/theme";
9
+ import type { ModelRegistry } from "../model-registry";
10
+ import type { SessionManager } from "../session-manager";
11
+ import type {
12
+ AppendEntryHandler,
13
+ BeforeAgentStartEvent,
14
+ BeforeAgentStartEventResult,
15
+ ContextEvent,
16
+ ContextEventResult,
17
+ ExtensionCommandContext,
18
+ ExtensionContext,
19
+ ExtensionError,
20
+ ExtensionEvent,
21
+ ExtensionFlag,
22
+ ExtensionShortcut,
23
+ ExtensionUIContext,
24
+ GetActiveToolsHandler,
25
+ GetAllToolsHandler,
26
+ LoadedExtension,
27
+ MessageRenderer,
28
+ RegisteredCommand,
29
+ RegisteredTool,
30
+ SendMessageHandler,
31
+ SessionBeforeCompactResult,
32
+ SessionBeforeTreeResult,
33
+ SetActiveToolsHandler,
34
+ ToolCallEvent,
35
+ ToolCallEventResult,
36
+ ToolResultEventResult,
37
+ } from "./types";
38
+
39
+ /** Combined result from all before_agent_start handlers */
40
+ interface BeforeAgentStartCombinedResult {
41
+ messages?: NonNullable<BeforeAgentStartEventResult["message"]>[];
42
+ systemPromptAppend?: string;
43
+ }
44
+
45
+ export type ExtensionErrorListener = (error: ExtensionError) => void;
46
+
47
+ export type NewSessionHandler = (options?: {
48
+ parentSession?: string;
49
+ setup?: (sessionManager: SessionManager) => Promise<void>;
50
+ }) => Promise<{ cancelled: boolean }>;
51
+
52
+ export type BranchHandler = (entryId: string) => Promise<{ cancelled: boolean }>;
53
+
54
+ export type NavigateTreeHandler = (
55
+ targetId: string,
56
+ options?: { summarize?: boolean },
57
+ ) => Promise<{ cancelled: boolean }>;
58
+
59
+ const noOpUIContext: ExtensionUIContext = {
60
+ select: async () => undefined,
61
+ confirm: async () => false,
62
+ input: async () => undefined,
63
+ notify: () => {},
64
+ setStatus: () => {},
65
+ setWidget: () => {},
66
+ setTitle: () => {},
67
+ custom: async () => undefined as never,
68
+ setEditorText: () => {},
69
+ getEditorText: () => "",
70
+ editor: async () => undefined,
71
+ get theme() {
72
+ return theme;
73
+ },
74
+ };
75
+
76
+ export class ExtensionRunner {
77
+ private extensions: LoadedExtension[];
78
+ private uiContext: ExtensionUIContext;
79
+ private hasUI: boolean;
80
+ private cwd: string;
81
+ private sessionManager: SessionManager;
82
+ private modelRegistry: ModelRegistry;
83
+ private errorListeners: Set<ExtensionErrorListener> = new Set();
84
+ private getModel: () => Model<any> | undefined = () => undefined;
85
+ private isIdleFn: () => boolean = () => true;
86
+ private waitForIdleFn: () => Promise<void> = async () => {};
87
+ private abortFn: () => void = () => {};
88
+ private hasPendingMessagesFn: () => boolean = () => false;
89
+ private newSessionHandler: NewSessionHandler = async () => ({ cancelled: false });
90
+ private branchHandler: BranchHandler = async () => ({ cancelled: false });
91
+ private navigateTreeHandler: NavigateTreeHandler = async () => ({ cancelled: false });
92
+
93
+ constructor(
94
+ extensions: LoadedExtension[],
95
+ cwd: string,
96
+ sessionManager: SessionManager,
97
+ modelRegistry: ModelRegistry,
98
+ ) {
99
+ this.extensions = extensions;
100
+ this.uiContext = noOpUIContext;
101
+ this.hasUI = false;
102
+ this.cwd = cwd;
103
+ this.sessionManager = sessionManager;
104
+ this.modelRegistry = modelRegistry;
105
+ }
106
+
107
+ initialize(options: {
108
+ getModel: () => Model<any> | undefined;
109
+ sendMessageHandler: SendMessageHandler;
110
+ appendEntryHandler: AppendEntryHandler;
111
+ getActiveToolsHandler: GetActiveToolsHandler;
112
+ getAllToolsHandler: GetAllToolsHandler;
113
+ setActiveToolsHandler: SetActiveToolsHandler;
114
+ newSessionHandler?: NewSessionHandler;
115
+ branchHandler?: BranchHandler;
116
+ navigateTreeHandler?: NavigateTreeHandler;
117
+ isIdle?: () => boolean;
118
+ waitForIdle?: () => Promise<void>;
119
+ abort?: () => void;
120
+ hasPendingMessages?: () => boolean;
121
+ uiContext?: ExtensionUIContext;
122
+ hasUI?: boolean;
123
+ }): void {
124
+ this.getModel = options.getModel;
125
+ this.isIdleFn = options.isIdle ?? (() => true);
126
+ this.waitForIdleFn = options.waitForIdle ?? (async () => {});
127
+ this.abortFn = options.abort ?? (() => {});
128
+ this.hasPendingMessagesFn = options.hasPendingMessages ?? (() => false);
129
+
130
+ if (options.newSessionHandler) {
131
+ this.newSessionHandler = options.newSessionHandler;
132
+ }
133
+ if (options.branchHandler) {
134
+ this.branchHandler = options.branchHandler;
135
+ }
136
+ if (options.navigateTreeHandler) {
137
+ this.navigateTreeHandler = options.navigateTreeHandler;
138
+ }
139
+
140
+ for (const ext of this.extensions) {
141
+ ext.setSendMessageHandler(options.sendMessageHandler);
142
+ ext.setAppendEntryHandler(options.appendEntryHandler);
143
+ ext.setGetActiveToolsHandler(options.getActiveToolsHandler);
144
+ ext.setGetAllToolsHandler(options.getAllToolsHandler);
145
+ ext.setSetActiveToolsHandler(options.setActiveToolsHandler);
146
+ }
147
+
148
+ this.uiContext = options.uiContext ?? noOpUIContext;
149
+ this.hasUI = options.hasUI ?? false;
150
+ }
151
+
152
+ getUIContext(): ExtensionUIContext | null {
153
+ return this.uiContext;
154
+ }
155
+
156
+ getHasUI(): boolean {
157
+ return this.hasUI;
158
+ }
159
+
160
+ getExtensionPaths(): string[] {
161
+ return this.extensions.map((e) => e.path);
162
+ }
163
+
164
+ /** Get all registered tools from all extensions. */
165
+ getAllRegisteredTools(): RegisteredTool[] {
166
+ const tools: RegisteredTool[] = [];
167
+ for (const ext of this.extensions) {
168
+ for (const tool of ext.tools.values()) {
169
+ tools.push(tool);
170
+ }
171
+ }
172
+ return tools;
173
+ }
174
+
175
+ getFlags(): Map<string, ExtensionFlag> {
176
+ const allFlags = new Map<string, ExtensionFlag>();
177
+ for (const ext of this.extensions) {
178
+ for (const [name, flag] of ext.flags) {
179
+ allFlags.set(name, flag);
180
+ }
181
+ }
182
+ return allFlags;
183
+ }
184
+
185
+ setFlagValue(name: string, value: boolean | string): void {
186
+ for (const ext of this.extensions) {
187
+ if (ext.flags.has(name)) {
188
+ ext.setFlagValue(name, value);
189
+ }
190
+ }
191
+ }
192
+
193
+ private static readonly RESERVED_SHORTCUTS = new Set([
194
+ "ctrl+c",
195
+ "ctrl+d",
196
+ "ctrl+z",
197
+ "ctrl+k",
198
+ "ctrl+p",
199
+ "ctrl+l",
200
+ "ctrl+o",
201
+ "ctrl+t",
202
+ "ctrl+g",
203
+ "shift+tab",
204
+ "shift+ctrl+p",
205
+ "alt+enter",
206
+ "escape",
207
+ "enter",
208
+ ]);
209
+
210
+ getShortcuts(): Map<KeyId, ExtensionShortcut> {
211
+ const allShortcuts = new Map<KeyId, ExtensionShortcut>();
212
+ for (const ext of this.extensions) {
213
+ for (const [key, shortcut] of ext.shortcuts) {
214
+ const normalizedKey = key.toLowerCase() as KeyId;
215
+
216
+ if (ExtensionRunner.RESERVED_SHORTCUTS.has(normalizedKey)) {
217
+ console.warn(
218
+ `Extension shortcut '${key}' from ${shortcut.extensionPath} conflicts with built-in shortcut. Skipping.`,
219
+ );
220
+ continue;
221
+ }
222
+
223
+ const existing = allShortcuts.get(normalizedKey);
224
+ if (existing) {
225
+ console.warn(
226
+ `Extension shortcut conflict: '${key}' registered by both ${existing.extensionPath} and ${shortcut.extensionPath}. Using ${shortcut.extensionPath}.`,
227
+ );
228
+ }
229
+ allShortcuts.set(normalizedKey, shortcut);
230
+ }
231
+ }
232
+ return allShortcuts;
233
+ }
234
+
235
+ onError(listener: ExtensionErrorListener): () => void {
236
+ this.errorListeners.add(listener);
237
+ return () => this.errorListeners.delete(listener);
238
+ }
239
+
240
+ emitError(error: ExtensionError): void {
241
+ for (const listener of this.errorListeners) {
242
+ listener(error);
243
+ }
244
+ }
245
+
246
+ hasHandlers(eventType: string): boolean {
247
+ for (const ext of this.extensions) {
248
+ const handlers = ext.handlers.get(eventType);
249
+ if (handlers && handlers.length > 0) {
250
+ return true;
251
+ }
252
+ }
253
+ return false;
254
+ }
255
+
256
+ getMessageRenderer(customType: string): MessageRenderer | undefined {
257
+ for (const ext of this.extensions) {
258
+ const renderer = ext.messageRenderers.get(customType);
259
+ if (renderer) {
260
+ return renderer;
261
+ }
262
+ }
263
+ return undefined;
264
+ }
265
+
266
+ getRegisteredCommands(): RegisteredCommand[] {
267
+ const commands: RegisteredCommand[] = [];
268
+ for (const ext of this.extensions) {
269
+ for (const command of ext.commands.values()) {
270
+ commands.push(command);
271
+ }
272
+ }
273
+ return commands;
274
+ }
275
+
276
+ getCommand(name: string): RegisteredCommand | undefined {
277
+ for (const ext of this.extensions) {
278
+ const command = ext.commands.get(name);
279
+ if (command) {
280
+ return command;
281
+ }
282
+ }
283
+ return undefined;
284
+ }
285
+
286
+ private createContext(): ExtensionContext {
287
+ return {
288
+ ui: this.uiContext,
289
+ hasUI: this.hasUI,
290
+ cwd: this.cwd,
291
+ sessionManager: this.sessionManager,
292
+ modelRegistry: this.modelRegistry,
293
+ model: this.getModel(),
294
+ isIdle: () => this.isIdleFn(),
295
+ abort: () => this.abortFn(),
296
+ hasPendingMessages: () => this.hasPendingMessagesFn(),
297
+ hasQueuedMessages: () => this.hasPendingMessagesFn(),
298
+ };
299
+ }
300
+
301
+ createCommandContext(): ExtensionCommandContext {
302
+ return {
303
+ ...this.createContext(),
304
+ waitForIdle: () => this.waitForIdleFn(),
305
+ newSession: (options) => this.newSessionHandler(options),
306
+ branch: (entryId) => this.branchHandler(entryId),
307
+ navigateTree: (targetId, options) => this.navigateTreeHandler(targetId, options),
308
+ };
309
+ }
310
+
311
+ private isSessionBeforeEvent(
312
+ type: string,
313
+ ): type is "session_before_switch" | "session_before_branch" | "session_before_compact" | "session_before_tree" {
314
+ return (
315
+ type === "session_before_switch" ||
316
+ type === "session_before_branch" ||
317
+ type === "session_before_compact" ||
318
+ type === "session_before_tree"
319
+ );
320
+ }
321
+
322
+ async emit(
323
+ event: ExtensionEvent,
324
+ ): Promise<SessionBeforeCompactResult | SessionBeforeTreeResult | ToolResultEventResult | undefined> {
325
+ const ctx = this.createContext();
326
+ let result: SessionBeforeCompactResult | SessionBeforeTreeResult | ToolResultEventResult | undefined;
327
+
328
+ for (const ext of this.extensions) {
329
+ const handlers = ext.handlers.get(event.type);
330
+ if (!handlers || handlers.length === 0) continue;
331
+
332
+ for (const handler of handlers) {
333
+ try {
334
+ const handlerResult = await handler(event, ctx);
335
+
336
+ if (this.isSessionBeforeEvent(event.type) && handlerResult) {
337
+ result = handlerResult as SessionBeforeCompactResult | SessionBeforeTreeResult;
338
+ if (result.cancel) {
339
+ return result;
340
+ }
341
+ }
342
+
343
+ if (event.type === "tool_result" && handlerResult) {
344
+ result = handlerResult as ToolResultEventResult;
345
+ }
346
+ } catch (err) {
347
+ const message = err instanceof Error ? err.message : String(err);
348
+ const stack = err instanceof Error ? err.stack : undefined;
349
+ this.emitError({
350
+ extensionPath: ext.path,
351
+ event: event.type,
352
+ error: message,
353
+ stack,
354
+ });
355
+ }
356
+ }
357
+ }
358
+
359
+ return result;
360
+ }
361
+
362
+ async emitToolCall(event: ToolCallEvent): Promise<ToolCallEventResult | undefined> {
363
+ const ctx = this.createContext();
364
+ let result: ToolCallEventResult | undefined;
365
+
366
+ for (const ext of this.extensions) {
367
+ const handlers = ext.handlers.get("tool_call");
368
+ if (!handlers || handlers.length === 0) continue;
369
+
370
+ for (const handler of handlers) {
371
+ try {
372
+ const handlerResult = await handler(event, ctx);
373
+
374
+ if (handlerResult) {
375
+ result = handlerResult as ToolCallEventResult;
376
+ if (result.block) {
377
+ return result;
378
+ }
379
+ }
380
+ } catch (err) {
381
+ const message = err instanceof Error ? err.message : String(err);
382
+ const stack = err instanceof Error ? err.stack : undefined;
383
+ this.emitError({
384
+ extensionPath: ext.path,
385
+ event: "tool_call",
386
+ error: message,
387
+ stack,
388
+ });
389
+ return { block: true, reason: `Extension ${ext.path} failed: ${message}` };
390
+ }
391
+ }
392
+ }
393
+
394
+ return result;
395
+ }
396
+
397
+ async emitContext(messages: AgentMessage[]): Promise<AgentMessage[]> {
398
+ const ctx = this.createContext();
399
+ let currentMessages = structuredClone(messages);
400
+
401
+ for (const ext of this.extensions) {
402
+ const handlers = ext.handlers.get("context");
403
+ if (!handlers || handlers.length === 0) continue;
404
+
405
+ for (const handler of handlers) {
406
+ try {
407
+ const event: ContextEvent = { type: "context", messages: currentMessages };
408
+ const handlerResult = await handler(event, ctx);
409
+
410
+ if (handlerResult && (handlerResult as ContextEventResult).messages) {
411
+ currentMessages = (handlerResult as ContextEventResult).messages!;
412
+ }
413
+ } catch (err) {
414
+ const message = err instanceof Error ? err.message : String(err);
415
+ const stack = err instanceof Error ? err.stack : undefined;
416
+ this.emitError({
417
+ extensionPath: ext.path,
418
+ event: "context",
419
+ error: message,
420
+ stack,
421
+ });
422
+ }
423
+ }
424
+ }
425
+
426
+ return currentMessages;
427
+ }
428
+
429
+ async emitBeforeAgentStart(
430
+ prompt: string,
431
+ images?: ImageContent[],
432
+ ): Promise<BeforeAgentStartCombinedResult | undefined> {
433
+ const ctx = this.createContext();
434
+ const messages: NonNullable<BeforeAgentStartEventResult["message"]>[] = [];
435
+ const systemPromptAppends: string[] = [];
436
+
437
+ for (const ext of this.extensions) {
438
+ const handlers = ext.handlers.get("before_agent_start");
439
+ if (!handlers || handlers.length === 0) continue;
440
+
441
+ for (const handler of handlers) {
442
+ try {
443
+ const event: BeforeAgentStartEvent = { type: "before_agent_start", prompt, images };
444
+ const handlerResult = await handler(event, ctx);
445
+
446
+ if (handlerResult) {
447
+ const result = handlerResult as BeforeAgentStartEventResult;
448
+ if (result.message) {
449
+ messages.push(result.message);
450
+ }
451
+ if (result.systemPromptAppend) {
452
+ systemPromptAppends.push(result.systemPromptAppend);
453
+ }
454
+ }
455
+ } catch (err) {
456
+ const message = err instanceof Error ? err.message : String(err);
457
+ const stack = err instanceof Error ? err.stack : undefined;
458
+ this.emitError({
459
+ extensionPath: ext.path,
460
+ event: "before_agent_start",
461
+ error: message,
462
+ stack,
463
+ });
464
+ }
465
+ }
466
+ }
467
+
468
+ if (messages.length > 0 || systemPromptAppends.length > 0) {
469
+ return {
470
+ messages: messages.length > 0 ? messages : undefined,
471
+ systemPromptAppend: systemPromptAppends.length > 0 ? systemPromptAppends.join("\n\n") : undefined,
472
+ };
473
+ }
474
+
475
+ return undefined;
476
+ }
477
+ }