@llblab/pi-telegram 0.4.0 → 0.5.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.
package/lib/handlers.ts CHANGED
@@ -1,31 +1,20 @@
1
1
  /**
2
2
  * Telegram inbound attachment handler pipeline
3
- * Owns MIME/type matching plus command and auto-tool execution for downloaded inbound files before prompt enqueueing
3
+ * Owns MIME/type matching, command-template execution, fallback handling, and prompt injection before prompt enqueueing
4
4
  */
5
5
 
6
- import { readFile } from "node:fs/promises";
7
6
  import { homedir } from "node:os";
8
- import { basename, isAbsolute, join, resolve } from "node:path";
7
+ import { basename, isAbsolute, resolve } from "node:path";
9
8
 
10
9
  const DEFAULT_ATTACHMENT_HANDLER_TIMEOUT_MS = 120_000;
11
10
 
12
- function getDefaultAgentDir(): string {
13
- return process.env.PI_CODING_AGENT_DIR
14
- ? resolve(process.env.PI_CODING_AGENT_DIR)
15
- : join(homedir(), ".pi", "agent");
16
- }
17
-
18
- function getDefaultAutoToolsPath(): string {
19
- return join(getDefaultAgentDir(), "auto-tools.json");
20
- }
21
-
22
11
  export interface TelegramAttachmentHandlerConfig {
23
12
  match?: string | string[];
24
13
  mime?: string | string[];
25
14
  type?: string | string[];
26
- command?: string;
27
- tool?: string;
28
- args?: Record<string, unknown>;
15
+ template?: string;
16
+ args?: string | string[];
17
+ defaults?: Record<string, unknown>;
29
18
  timeoutMs?: number;
30
19
  }
31
20
 
@@ -77,8 +66,6 @@ export interface TelegramAttachmentHandlerRuntimeDeps<TContext> {
77
66
  options?: TelegramAttachmentHandlerExecOptions,
78
67
  ) => Promise<TelegramAttachmentHandlerExecResult>;
79
68
  getCwd: (ctx: TContext) => string;
80
- readTextFile?: (path: string) => Promise<string>;
81
- autoToolsPath?: string;
82
69
  recordRuntimeEvent?: (
83
70
  category: string,
84
71
  error: unknown,
@@ -99,15 +86,12 @@ interface AttachmentHandlerInvocation {
99
86
  args: string[];
100
87
  }
101
88
 
102
- interface AutoToolConfig {
103
- name: string;
104
- script: string;
105
- args: string[];
106
- }
107
-
108
89
  function normalizeStringList(value: string | string[] | undefined): string[] {
109
90
  if (Array.isArray(value)) {
110
- return value.map(String).map((item) => item.trim()).filter(Boolean);
91
+ return value
92
+ .map(String)
93
+ .map((item) => item.trim())
94
+ .filter(Boolean);
111
95
  }
112
96
  if (typeof value === "string" && value.trim()) return [value.trim()];
113
97
  return [];
@@ -124,7 +108,9 @@ function matchesWildcard(pattern: string, value: string | undefined): boolean {
124
108
  return new RegExp(`^${escaped}$`).test(normalizedValue);
125
109
  }
126
110
 
127
- function handlerHasSelectors(handler: TelegramAttachmentHandlerConfig): boolean {
111
+ function handlerHasSelectors(
112
+ handler: TelegramAttachmentHandlerConfig,
113
+ ): boolean {
128
114
  return (
129
115
  normalizeStringList(handler.match).length > 0 ||
130
116
  normalizeStringList(handler.mime).length > 0 ||
@@ -132,7 +118,10 @@ function handlerHasSelectors(handler: TelegramAttachmentHandlerConfig): boolean
132
118
  );
133
119
  }
134
120
 
135
- function matchesAnyPattern(patterns: string[], value: string | undefined): boolean {
121
+ function matchesAnyPattern(
122
+ patterns: string[],
123
+ value: string | undefined,
124
+ ): boolean {
136
125
  return patterns.some((pattern) => matchesWildcard(pattern, value));
137
126
  }
138
127
 
@@ -150,12 +139,12 @@ export function telegramAttachmentHandlerMatchesFile(
150
139
  return matchesAnyPattern(matchPatterns, file.kind);
151
140
  }
152
141
 
153
- export function findTelegramAttachmentHandler(
142
+ export function findTelegramAttachmentHandlers(
154
143
  handlers: TelegramAttachmentHandlerConfig[] | undefined,
155
144
  file: TelegramAttachmentHandlerFile,
156
- ): TelegramAttachmentHandlerConfig | undefined {
157
- if (!Array.isArray(handlers)) return undefined;
158
- return handlers.find(
145
+ ): TelegramAttachmentHandlerConfig[] {
146
+ if (!Array.isArray(handlers)) return [];
147
+ return handlers.filter(
159
148
  (handler) =>
160
149
  !!handler &&
161
150
  typeof handler === "object" &&
@@ -163,29 +152,64 @@ export function findTelegramAttachmentHandler(
163
152
  );
164
153
  }
165
154
 
166
- function hasAttachmentPlaceholder(value: string): boolean {
167
- return /\{(?:filename|path|basename|mime|type)\}/.test(value);
155
+ export function findTelegramAttachmentHandler(
156
+ handlers: TelegramAttachmentHandlerConfig[] | undefined,
157
+ file: TelegramAttachmentHandlerFile,
158
+ ): TelegramAttachmentHandlerConfig | undefined {
159
+ return findTelegramAttachmentHandlers(handlers, file)[0];
168
160
  }
169
161
 
170
- export function substituteTelegramAttachmentHandlerToken(
171
- token: string,
162
+ function hasAttachmentFilePlaceholder(value: string): boolean {
163
+ return /\{file\}/.test(value);
164
+ }
165
+
166
+ function normalizeTelegramAttachmentHandlerArgs(
167
+ value: string | string[] | undefined,
168
+ ): string[] {
169
+ if (Array.isArray(value)) return value.map(String).map((item) => item.trim());
170
+ if (typeof value !== "string") return [];
171
+ return value.split(",").map((item) => item.trim());
172
+ }
173
+
174
+ function getTelegramAttachmentHandlerArgDefaults(
175
+ handler: TelegramAttachmentHandlerConfig,
176
+ ): Record<string, string> {
177
+ const defaults: Record<string, string> = {};
178
+ for (const item of normalizeTelegramAttachmentHandlerArgs(handler.args)) {
179
+ if (!item) continue;
180
+ const [name, ...defaultParts] = item.split("=");
181
+ if (!name || defaultParts.length === 0) continue;
182
+ defaults[name.trim()] = defaultParts.join("=").trim();
183
+ }
184
+ for (const [key, value] of Object.entries(handler.defaults ?? {})) {
185
+ defaults[key] = value === undefined || value === null ? "" : String(value);
186
+ }
187
+ return defaults;
188
+ }
189
+
190
+ function getTelegramAttachmentHandlerTemplateValues(
191
+ handler: TelegramAttachmentHandlerConfig,
172
192
  file: TelegramAttachmentHandlerFile,
173
- ): string {
174
- const replacements: Record<string, string> = {
175
- "{filename}": file.path,
176
- "{path}": file.path,
177
- "{basename}": file.fileName || basename(file.path),
178
- "{mime}": file.mimeType ?? "",
179
- "{type}": file.kind ?? "",
193
+ ): Record<string, string> {
194
+ return {
195
+ ...getTelegramAttachmentHandlerArgDefaults(handler),
196
+ file: file.path,
197
+ mime: file.mimeType ?? "",
198
+ type: file.kind ?? "",
180
199
  };
181
- let result = token;
182
- for (const [key, value] of Object.entries(replacements)) {
183
- result = result.split(key).join(value);
184
- }
185
- return result;
186
200
  }
187
201
 
188
- export function splitTelegramAttachmentHandlerCommand(input: string): string[] {
202
+ function substituteTelegramAttachmentHandlerTemplateToken(
203
+ token: string,
204
+ values: Record<string, string>,
205
+ ): string {
206
+ return token.replace(/\{([A-Za-z_][A-Za-z0-9_-]*)\}/g, (_match, name) => {
207
+ if (Object.hasOwn(values, name)) return values[name] ?? "";
208
+ throw new Error(`Missing attachment handler template value: ${name}`);
209
+ });
210
+ }
211
+
212
+ export function splitTelegramAttachmentHandlerTemplate(input: string): string[] {
189
213
  const words: string[] = [];
190
214
  let current = "";
191
215
  let quote: "'" | '"' | undefined;
@@ -237,135 +261,38 @@ function expandExecutablePath(command: string, cwd: string): string {
237
261
  return command;
238
262
  }
239
263
 
240
- export function buildTelegramAttachmentCommandInvocation(
241
- commandTemplate: string,
264
+ function buildTelegramAttachmentTemplateInvocation(
265
+ template: string,
266
+ handler: TelegramAttachmentHandlerConfig,
242
267
  file: TelegramAttachmentHandlerFile,
243
268
  cwd: string,
244
269
  ): AttachmentHandlerInvocation {
245
- const parts = splitTelegramAttachmentHandlerCommand(commandTemplate);
270
+ const parts = splitTelegramAttachmentHandlerTemplate(template);
246
271
  const commandPart = parts[0];
247
- if (!commandPart) throw new Error("Attachment handler command is empty");
248
- const hadPlaceholder = parts.some(hasAttachmentPlaceholder);
272
+ if (!commandPart) throw new Error("Attachment handler template is empty");
273
+ const values = getTelegramAttachmentHandlerTemplateValues(handler, file);
274
+ const hadFilePlaceholder = parts.some(hasAttachmentFilePlaceholder);
249
275
  const command = expandExecutablePath(
250
- substituteTelegramAttachmentHandlerToken(commandPart, file),
276
+ substituteTelegramAttachmentHandlerTemplateToken(commandPart, values),
251
277
  cwd,
252
278
  );
253
279
  const args = parts
254
280
  .slice(1)
255
- .map((part) => substituteTelegramAttachmentHandlerToken(part, file));
256
- if (!hadPlaceholder) args.push(file.path);
257
- return { command, args };
258
- }
259
-
260
- function normalizeAutoToolName(name: string): string {
261
- return name
262
- .trim()
263
- .toLowerCase()
264
- .replace(/[^a-z0-9_]/g, "_")
265
- .replace(/_+/g, "_")
266
- .replace(/^_+|_+$/g, "");
267
- }
268
-
269
- function normalizeAutoToolArgs(value: unknown): string[] {
270
- const source = Array.isArray(value)
271
- ? value
272
- : typeof value === "string"
273
- ? value.split(",")
274
- : [];
275
- const args: string[] = [];
276
- const seen = new Set<string>();
277
- for (const item of source) {
278
- const arg = normalizeAutoToolName(String(item));
279
- if (!arg || seen.has(arg)) continue;
280
- seen.add(arg);
281
- args.push(arg);
282
- }
283
- return args;
284
- }
285
-
286
- export function parseTelegramAutoToolsRegistry(
287
- content: string,
288
- ): Map<string, AutoToolConfig> {
289
- const raw = JSON.parse(content) as unknown;
290
- const entries = Array.isArray(raw)
291
- ? raw.map((value) => [undefined, value] as const)
292
- : raw && typeof raw === "object"
293
- ? Object.entries(raw as Record<string, unknown>)
294
- : [];
295
- const tools = new Map<string, AutoToolConfig>();
296
- for (const [key, value] of entries) {
297
- if (!value || typeof value !== "object") continue;
298
- const record = value as Record<string, unknown>;
299
- const name = normalizeAutoToolName(
300
- typeof record.name === "string" ? record.name : (key ?? ""),
281
+ .map((part) =>
282
+ substituteTelegramAttachmentHandlerTemplateToken(part, values),
301
283
  );
302
- const script = typeof record.script === "string" ? record.script.trim() : "";
303
- if (!name || !script) continue;
304
- tools.set(name, { name, script, args: normalizeAutoToolArgs(record.args) });
305
- }
306
- return tools;
307
- }
308
-
309
- async function readTelegramAutoToolsRegistry(
310
- path: string,
311
- readTextFile: (path: string) => Promise<string>,
312
- ): Promise<Map<string, AutoToolConfig>> {
313
- try {
314
- return parseTelegramAutoToolsRegistry(await readTextFile(path));
315
- } catch {
316
- return new Map();
317
- }
318
- }
319
-
320
- function getConfiguredToolArgValue(
321
- value: unknown,
322
- file: TelegramAttachmentHandlerFile,
323
- ): string | undefined {
324
- if (value === undefined || value === null) return undefined;
325
- return substituteTelegramAttachmentHandlerToken(String(value), file);
326
- }
327
-
328
- function getDefaultToolArgValue(
329
- arg: string,
330
- file: TelegramAttachmentHandlerFile,
331
- ): string {
332
- if (["file", "filename", "path"].includes(arg)) return file.path;
333
- if (["basename", "name"].includes(arg)) {
334
- return file.fileName || basename(file.path);
335
- }
336
- if (["mime", "mime_type", "mimetype"].includes(arg)) return file.mimeType ?? "";
337
- if (["type", "kind"].includes(arg)) return file.kind ?? "";
338
- return "";
284
+ if (!hadFilePlaceholder) args.push(file.path);
285
+ return { command, args };
339
286
  }
340
287
 
341
- async function buildTelegramAttachmentToolInvocation(
288
+ export function buildTelegramAttachmentHandlerInvocation(
342
289
  handler: TelegramAttachmentHandlerConfig,
343
290
  file: TelegramAttachmentHandlerFile,
344
291
  cwd: string,
345
- deps: Pick<
346
- TelegramAttachmentHandlerRuntimeDeps<unknown>,
347
- "readTextFile" | "autoToolsPath"
348
- >,
349
- ): Promise<AttachmentHandlerInvocation> {
350
- const toolName = normalizeAutoToolName(handler.tool ?? "");
351
- if (!toolName) throw new Error("Attachment handler tool is empty");
352
- const readRegistryFile =
353
- deps.readTextFile ?? ((path: string) => readFile(path, "utf8"));
354
- const registry = await readTelegramAutoToolsRegistry(
355
- deps.autoToolsPath ?? getDefaultAutoToolsPath(),
356
- readRegistryFile,
357
- );
358
- const tool = registry.get(toolName);
359
- if (!tool) {
360
- throw new Error(`Attachment handler tool not found in auto-tools: ${toolName}`);
361
- }
362
- const script = expandExecutablePath(tool.script, cwd);
363
- const args = tool.args.map(
364
- (arg) =>
365
- getConfiguredToolArgValue(handler.args?.[arg], file) ??
366
- getDefaultToolArgValue(arg, file),
367
- );
368
- return { command: script, args };
292
+ ): AttachmentHandlerInvocation {
293
+ const { template } = handler;
294
+ if (!template) throw new Error("Attachment handler template is required");
295
+ return buildTelegramAttachmentTemplateInvocation(template, handler, file, cwd);
369
296
  }
370
297
 
371
298
  function getTelegramAttachmentHandlerTimeout(
@@ -378,9 +305,10 @@ function getTelegramAttachmentHandlerTimeout(
378
305
  : DEFAULT_ATTACHMENT_HANDLER_TIMEOUT_MS;
379
306
  }
380
307
 
381
- function getTelegramAttachmentHandlerKind(handler: TelegramAttachmentHandlerConfig): string {
382
- if (handler.command) return "command";
383
- if (handler.tool) return "tool";
308
+ function getTelegramAttachmentHandlerKind(
309
+ handler: TelegramAttachmentHandlerConfig,
310
+ ): string {
311
+ if (handler.template) return "template";
384
312
  return "unknown";
385
313
  }
386
314
 
@@ -399,19 +327,19 @@ async function executeTelegramAttachmentHandler(
399
327
  handler: TelegramAttachmentHandlerConfig,
400
328
  file: TelegramAttachmentHandlerFile,
401
329
  cwd: string,
402
- deps: Pick<
403
- TelegramAttachmentHandlerRuntimeDeps<unknown>,
404
- "execCommand" | "readTextFile" | "autoToolsPath"
405
- >,
330
+ deps: Pick<TelegramAttachmentHandlerRuntimeDeps<unknown>, "execCommand">,
406
331
  ): Promise<string> {
407
- const invocation = handler.command
408
- ? buildTelegramAttachmentCommandInvocation(handler.command, file, cwd)
409
- : await buildTelegramAttachmentToolInvocation(handler, file, cwd, deps);
332
+ const invocation = buildTelegramAttachmentHandlerInvocation(
333
+ handler,
334
+ file,
335
+ cwd,
336
+ );
410
337
  const result = await deps.execCommand(invocation.command, invocation.args, {
411
338
  cwd,
412
339
  timeout: getTelegramAttachmentHandlerTimeout(handler),
413
340
  });
414
- if (result.code !== 0) throw new Error(formatTelegramAttachmentHandlerFailure(result));
341
+ if (result.code !== 0)
342
+ throw new Error(formatTelegramAttachmentHandlerFailure(result));
415
343
  return result.stdout.trim();
416
344
  }
417
345
 
@@ -423,28 +351,28 @@ export async function processTelegramAttachmentHandlers<
423
351
  handlers?: TelegramAttachmentHandlerConfig[];
424
352
  cwd: string;
425
353
  execCommand: TelegramAttachmentHandlerRuntimeDeps<unknown>["execCommand"];
426
- readTextFile?: (path: string) => Promise<string>;
427
- autoToolsPath?: string;
428
354
  recordRuntimeEvent?: TelegramAttachmentHandlerRuntimeDeps<unknown>["recordRuntimeEvent"];
429
355
  }): Promise<TelegramAttachmentHandlerProcessResult<TFile>> {
430
356
  const promptFiles: TFile[] = [...options.files];
431
357
  const outputs: TelegramAttachmentHandlerOutput[] = [];
432
358
  for (const file of options.files) {
433
- const handler = findTelegramAttachmentHandler(options.handlers, file);
434
- if (!handler) continue;
435
- try {
436
- const output = await executeTelegramAttachmentHandler(
437
- handler,
438
- file,
439
- options.cwd,
440
- options,
441
- );
442
- if (output) outputs.push({ file, output, handler });
443
- } catch (error) {
444
- options.recordRuntimeEvent?.("attachment-handler", error, {
445
- fileName: file.fileName || basename(file.path),
446
- handler: getTelegramAttachmentHandlerKind(handler),
447
- });
359
+ const handlers = findTelegramAttachmentHandlers(options.handlers, file);
360
+ for (const handler of handlers) {
361
+ try {
362
+ const output = await executeTelegramAttachmentHandler(
363
+ handler,
364
+ file,
365
+ options.cwd,
366
+ options,
367
+ );
368
+ if (output) outputs.push({ file, output, handler });
369
+ break;
370
+ } catch (error) {
371
+ options.recordRuntimeEvent?.("attachment-handler", error, {
372
+ fileName: file.fileName || basename(file.path),
373
+ handler: getTelegramAttachmentHandlerKind(handler),
374
+ });
375
+ }
448
376
  }
449
377
  }
450
378
  return {
@@ -466,8 +394,6 @@ export function createTelegramAttachmentHandlerRuntime<TContext>(
466
394
  handlers: deps.getHandlers(),
467
395
  cwd: deps.getCwd(ctx),
468
396
  execCommand: deps.execCommand,
469
- readTextFile: deps.readTextFile,
470
- autoToolsPath: deps.autoToolsPath,
471
397
  recordRuntimeEvent: deps.recordRuntimeEvent,
472
398
  }),
473
399
  };
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Telegram lifecycle hook registration helpers
3
+ * Owns binding prepared Telegram lifecycle runtimes to pi extension lifecycle events
4
+ */
5
+
6
+ import type {
7
+ AgentEndEvent,
8
+ AgentStartEvent,
9
+ BeforeAgentStartEvent,
10
+ ExtensionAPI,
11
+ ExtensionContext,
12
+ SessionShutdownEvent,
13
+ SessionStartEvent,
14
+ } from "./pi.ts";
15
+
16
+ export interface TelegramBeforeAgentStartResult {
17
+ systemPrompt?: string;
18
+ }
19
+
20
+ type TelegramBeforeAgentStartReturn =
21
+ | Promise<TelegramBeforeAgentStartResult | undefined>
22
+ | TelegramBeforeAgentStartResult
23
+ | undefined;
24
+
25
+ type TelegramLifecycleModel = ExtensionContext["model"];
26
+ type TelegramLifecycleMessage = AgentEndEvent["messages"][number];
27
+
28
+ export interface TelegramLifecycleRegistrationDeps {
29
+ onSessionStart: (
30
+ event: SessionStartEvent,
31
+ ctx: ExtensionContext,
32
+ ) => Promise<void>;
33
+ onSessionShutdown: (
34
+ event: SessionShutdownEvent,
35
+ ctx: ExtensionContext,
36
+ ) => Promise<void>;
37
+ onBeforeAgentStart: (
38
+ event: BeforeAgentStartEvent,
39
+ ctx: ExtensionContext,
40
+ ) => TelegramBeforeAgentStartReturn;
41
+ onModelSelect: (
42
+ event: { model: TelegramLifecycleModel },
43
+ ctx: ExtensionContext,
44
+ ) => Promise<void> | void;
45
+ onAgentStart: (
46
+ event: AgentStartEvent,
47
+ ctx: ExtensionContext,
48
+ ) => Promise<void>;
49
+ onToolExecutionStart: (
50
+ event: unknown,
51
+ ctx: ExtensionContext,
52
+ ) => Promise<void> | void;
53
+ onToolExecutionEnd: (
54
+ event: unknown,
55
+ ctx: ExtensionContext,
56
+ ) => Promise<void> | void;
57
+ onMessageStart: (
58
+ event: { message: TelegramLifecycleMessage },
59
+ ctx: ExtensionContext,
60
+ ) => Promise<void>;
61
+ onMessageUpdate: (
62
+ event: { message: TelegramLifecycleMessage },
63
+ ctx: ExtensionContext,
64
+ ) => Promise<void>;
65
+ onAgentEnd: (event: AgentEndEvent, ctx: ExtensionContext) => Promise<void>;
66
+ }
67
+
68
+ export interface TelegramSessionLifecycleHooks {
69
+ onSessionStart: (
70
+ event: SessionStartEvent,
71
+ ctx: ExtensionContext,
72
+ ) => Promise<void>;
73
+ onSessionShutdown: (
74
+ event: SessionShutdownEvent,
75
+ ctx: ExtensionContext,
76
+ ) => Promise<void>;
77
+ }
78
+
79
+ export interface TelegramExtraLifecycleHooks {
80
+ onSessionStart?: (
81
+ event: SessionStartEvent,
82
+ ctx: ExtensionContext,
83
+ ) => Promise<void>;
84
+ onSessionShutdown?: (
85
+ event: SessionShutdownEvent,
86
+ ctx: ExtensionContext,
87
+ ) => Promise<void>;
88
+ }
89
+
90
+ export function appendTelegramLifecycleHooks(
91
+ base: TelegramSessionLifecycleHooks,
92
+ extra: TelegramExtraLifecycleHooks,
93
+ ): TelegramSessionLifecycleHooks {
94
+ return {
95
+ onSessionStart: async (event, ctx) => {
96
+ await base.onSessionStart(event, ctx);
97
+ await extra.onSessionStart?.(event, ctx);
98
+ },
99
+ onSessionShutdown: async (event, ctx) => {
100
+ await base.onSessionShutdown(event, ctx);
101
+ await extra.onSessionShutdown?.(event, ctx);
102
+ },
103
+ };
104
+ }
105
+
106
+ export function registerTelegramLifecycleHooks(
107
+ pi: ExtensionAPI,
108
+ deps: TelegramLifecycleRegistrationDeps,
109
+ ): void {
110
+ pi.on("session_start", async (event, ctx) => {
111
+ await deps.onSessionStart(event, ctx);
112
+ });
113
+ pi.on("session_shutdown", async (event, ctx) => {
114
+ await deps.onSessionShutdown(event, ctx);
115
+ });
116
+ pi.on("before_agent_start", async (event, ctx) => {
117
+ return deps.onBeforeAgentStart(event, ctx);
118
+ });
119
+ pi.on("model_select", async (event, ctx) => {
120
+ await deps.onModelSelect(event, ctx);
121
+ });
122
+ pi.on("agent_start", async (event, ctx) => {
123
+ await deps.onAgentStart(event, ctx);
124
+ });
125
+ pi.on("tool_execution_start", async (event, ctx) => {
126
+ await deps.onToolExecutionStart(event, ctx);
127
+ });
128
+ pi.on("tool_execution_end", async (event, ctx) => {
129
+ await deps.onToolExecutionEnd(event, ctx);
130
+ });
131
+ pi.on("message_start", async (event, ctx) => {
132
+ await deps.onMessageStart(event, ctx);
133
+ });
134
+ pi.on("message_update", async (event, ctx) => {
135
+ await deps.onMessageUpdate(event, ctx);
136
+ });
137
+ pi.on("agent_end", async (event, ctx) => {
138
+ await deps.onAgentEnd(event, ctx);
139
+ });
140
+ }