@llblab/pi-telegram 0.6.3 → 0.7.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/AGENTS.md +153 -0
- package/BACKLOG.md +5 -0
- package/CHANGELOG.md +148 -0
- package/README.md +40 -39
- package/docs/README.md +1 -0
- package/docs/architecture.md +36 -27
- package/docs/attachment-handlers.md +4 -6
- package/docs/callback-namespaces.md +36 -0
- package/docs/command-templates.md +53 -9
- package/docs/locks.md +4 -0
- package/docs/outbound-handlers.md +6 -5
- package/index.ts +60 -7
- package/lib/api.ts +1 -0
- package/lib/attachment-handlers.ts +21 -10
- package/lib/attachments.ts +1 -0
- package/lib/command-templates.ts +37 -3
- package/lib/commands.ts +363 -88
- package/lib/config.ts +6 -2
- package/lib/keyboard.ts +14 -0
- package/lib/lifecycle.ts +26 -0
- package/lib/locks.ts +3 -2
- package/lib/media.ts +1 -0
- package/lib/menu-model.ts +881 -0
- package/lib/menu-queue.ts +610 -0
- package/lib/menu-status.ts +226 -0
- package/lib/menu-thinking.ts +171 -0
- package/lib/menu.ts +143 -1019
- package/lib/model.ts +1 -0
- package/lib/outbound-handlers.ts +28 -19
- package/lib/pi.ts +8 -0
- package/lib/polling.ts +1 -0
- package/lib/preview.ts +97 -50
- package/lib/prompt-templates.ts +150 -0
- package/lib/prompts.ts +2 -0
- package/lib/queue.ts +60 -15
- package/lib/rendering.ts +1 -0
- package/lib/replies.ts +90 -2
- package/lib/routing.ts +106 -14
- package/lib/runtime.ts +2 -0
- package/lib/setup.ts +1 -0
- package/lib/status.ts +18 -6
- package/lib/turns.ts +1 -0
- package/lib/updates.ts +55 -6
- package/package.json +5 -2
package/lib/model.ts
CHANGED
package/lib/outbound-handlers.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Telegram outbound handler helpers
|
|
3
|
+
* Zones: telegram outbound, assistant markup, command templates, callback routing
|
|
3
4
|
* Owns assistant-authored outbound markup extraction, configured artifact generation, callback actions, and Telegram outbound delivery
|
|
4
5
|
*/
|
|
5
6
|
|
|
@@ -8,6 +9,7 @@ import { mkdir } from "node:fs/promises";
|
|
|
8
9
|
import { homedir } from "node:os";
|
|
9
10
|
import { basename, join, resolve } from "node:path";
|
|
10
11
|
|
|
12
|
+
import type { TelegramInlineKeyboardMarkup } from "./keyboard.ts";
|
|
11
13
|
import type { PendingTelegramTurn } from "./queue.ts";
|
|
12
14
|
import { buildTelegramMultipartReplyParameters } from "./replies.ts";
|
|
13
15
|
import { truncateTelegramQueueSummary } from "./turns.ts";
|
|
@@ -52,6 +54,7 @@ export interface TelegramVoiceExecOptions {
|
|
|
52
54
|
timeout?: number;
|
|
53
55
|
signal?: AbortSignal;
|
|
54
56
|
stdin?: string;
|
|
57
|
+
retry?: number;
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
export interface TelegramVoiceExecResult {
|
|
@@ -473,6 +476,9 @@ async function runVoiceReplyCommand(
|
|
|
473
476
|
{
|
|
474
477
|
cwd: options.cwd,
|
|
475
478
|
timeout: options.timeout,
|
|
479
|
+
...(typeof config === "object" && config.retry !== undefined
|
|
480
|
+
? { retry: config.retry }
|
|
481
|
+
: {}),
|
|
476
482
|
...(options.stdin !== undefined ? { stdin: options.stdin } : {}),
|
|
477
483
|
},
|
|
478
484
|
);
|
|
@@ -597,22 +603,27 @@ async function generateTelegramVoiceReplyFileWithHandler(
|
|
|
597
603
|
const startedAt = Date.now();
|
|
598
604
|
let stdout = "";
|
|
599
605
|
for (const [index, step] of steps.entries()) {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
606
|
+
try {
|
|
607
|
+
const result = await runVoiceReplyCommand(
|
|
608
|
+
`Outbound voice template step ${index + 1}`,
|
|
609
|
+
step,
|
|
610
|
+
values,
|
|
611
|
+
{
|
|
612
|
+
cwd: options.cwd,
|
|
613
|
+
timeout: getVoiceReplyCompositionStepTimeout(
|
|
614
|
+
options.timeout,
|
|
615
|
+
step,
|
|
616
|
+
startedAt,
|
|
617
|
+
),
|
|
618
|
+
execCommand: options.execCommand,
|
|
619
|
+
...(index === 0 ? {} : { stdin: stdout }),
|
|
620
|
+
},
|
|
621
|
+
);
|
|
622
|
+
stdout = result.stdout;
|
|
623
|
+
} catch (error) {
|
|
624
|
+
if (typeof step === "object" && step.critical) throw error;
|
|
625
|
+
stdout = "";
|
|
626
|
+
}
|
|
616
627
|
}
|
|
617
628
|
return getVoiceReplyOutputPath(options.handler, values, stdout);
|
|
618
629
|
}
|
|
@@ -722,9 +733,7 @@ export interface TelegramOutboundButtonStoredAction extends TelegramOutboundButt
|
|
|
722
733
|
createdAt: number;
|
|
723
734
|
}
|
|
724
735
|
|
|
725
|
-
export
|
|
726
|
-
inline_keyboard: Array<Array<{ text: string; callback_data: string }>>;
|
|
727
|
-
}
|
|
736
|
+
export type TelegramOutboundButtonMarkup = TelegramInlineKeyboardMarkup;
|
|
728
737
|
|
|
729
738
|
export interface TelegramButtonReplyPlan {
|
|
730
739
|
markdown: string;
|
package/lib/pi.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* pi SDK adapter boundary
|
|
3
|
+
* Zones: pi agent sdk boundary, shared adapters
|
|
3
4
|
* Owns direct pi SDK imports and exposes narrow bridge-facing helpers/types for the extension composition layer
|
|
4
5
|
*/
|
|
5
6
|
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
type ExtensionContext,
|
|
13
14
|
type SessionShutdownEvent,
|
|
14
15
|
type SessionStartEvent,
|
|
16
|
+
type SlashCommandInfo,
|
|
15
17
|
SettingsManager,
|
|
16
18
|
} from "@mariozechner/pi-coding-agent";
|
|
17
19
|
|
|
@@ -24,6 +26,7 @@ export type {
|
|
|
24
26
|
ExtensionContext,
|
|
25
27
|
SessionShutdownEvent,
|
|
26
28
|
SessionStartEvent,
|
|
29
|
+
SlashCommandInfo,
|
|
27
30
|
};
|
|
28
31
|
|
|
29
32
|
export interface PiSettingsManager {
|
|
@@ -31,9 +34,12 @@ export interface PiSettingsManager {
|
|
|
31
34
|
getEnabledModels: () => string[] | undefined;
|
|
32
35
|
}
|
|
33
36
|
|
|
37
|
+
export type PiSlashCommandInfo = SlashCommandInfo;
|
|
38
|
+
|
|
34
39
|
export interface PiExtensionApiRuntimePorts {
|
|
35
40
|
sendUserMessage: ExtensionAPI["sendUserMessage"];
|
|
36
41
|
exec: ExtensionAPI["exec"];
|
|
42
|
+
getCommands: ExtensionAPI["getCommands"];
|
|
37
43
|
getThinkingLevel: ExtensionAPI["getThinkingLevel"];
|
|
38
44
|
setThinkingLevel: ExtensionAPI["setThinkingLevel"];
|
|
39
45
|
setModel: ExtensionAPI["setModel"];
|
|
@@ -44,6 +50,7 @@ export function createExtensionApiRuntimePorts(
|
|
|
44
50
|
ExtensionAPI,
|
|
45
51
|
| "sendUserMessage"
|
|
46
52
|
| "exec"
|
|
53
|
+
| "getCommands"
|
|
47
54
|
| "getThinkingLevel"
|
|
48
55
|
| "setThinkingLevel"
|
|
49
56
|
| "setModel"
|
|
@@ -52,6 +59,7 @@ export function createExtensionApiRuntimePorts(
|
|
|
52
59
|
return {
|
|
53
60
|
sendUserMessage: (content) => api.sendUserMessage(content),
|
|
54
61
|
exec: (command, args, options) => api.exec(command, args, options),
|
|
62
|
+
getCommands: () => api.getCommands(),
|
|
55
63
|
getThinkingLevel: () => api.getThinkingLevel(),
|
|
56
64
|
setThinkingLevel: (level) => api.setThinkingLevel(level),
|
|
57
65
|
setModel: (model) => api.setModel(model),
|
package/lib/polling.ts
CHANGED
package/lib/preview.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Telegram preview streaming helpers
|
|
3
|
+
* Zones: telegram outbound, streaming preview, rendering
|
|
3
4
|
* Owns preview transport selection, runtime updates, and preview finalization
|
|
4
5
|
*/
|
|
5
6
|
|
|
@@ -44,9 +45,11 @@ export interface TelegramPreviewRuntimeState extends TelegramPreviewState {
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
export type TelegramSentPreviewMessage = TelegramSentMessage;
|
|
47
|
-
export type TelegramPreviewReplyMarkup =
|
|
48
|
+
export type TelegramPreviewReplyMarkup = unknown;
|
|
48
49
|
|
|
49
|
-
export interface TelegramPreviewRuntimeDeps
|
|
50
|
+
export interface TelegramPreviewRuntimeDeps<
|
|
51
|
+
TReplyMarkup = TelegramPreviewReplyMarkup,
|
|
52
|
+
> {
|
|
50
53
|
getState: () => TelegramPreviewRuntimeState | undefined;
|
|
51
54
|
setState: (state: TelegramPreviewRuntimeState | undefined) => void;
|
|
52
55
|
clearScheduledFlush: (state: TelegramPreviewRuntimeState) => void;
|
|
@@ -78,13 +81,13 @@ export interface TelegramPreviewRuntimeDeps {
|
|
|
78
81
|
sendRenderedChunks: (
|
|
79
82
|
chatId: number,
|
|
80
83
|
chunks: TelegramRenderedChunk[],
|
|
81
|
-
options?: { replyMarkup?:
|
|
84
|
+
options?: { replyMarkup?: TReplyMarkup },
|
|
82
85
|
) => Promise<number | undefined>;
|
|
83
86
|
editRenderedMessage: (
|
|
84
87
|
chatId: number,
|
|
85
88
|
messageId: number,
|
|
86
89
|
chunks: TelegramRenderedChunk[],
|
|
87
|
-
options?: { replyMarkup?:
|
|
90
|
+
options?: { replyMarkup?: TReplyMarkup },
|
|
88
91
|
) => Promise<number | undefined>;
|
|
89
92
|
}
|
|
90
93
|
|
|
@@ -92,7 +95,10 @@ export interface TelegramPreviewActiveTurn {
|
|
|
92
95
|
chatId: number;
|
|
93
96
|
}
|
|
94
97
|
|
|
95
|
-
export interface TelegramAssistantMessagePreviewStartDeps<
|
|
98
|
+
export interface TelegramAssistantMessagePreviewStartDeps<
|
|
99
|
+
TMessage,
|
|
100
|
+
TReplyMarkup = TelegramPreviewReplyMarkup,
|
|
101
|
+
> {
|
|
96
102
|
getActiveTurn: () => TelegramPreviewActiveTurn | undefined;
|
|
97
103
|
isAssistantMessage: (message: TMessage) => boolean;
|
|
98
104
|
getState: () => TelegramPreviewRuntimeState | undefined;
|
|
@@ -102,6 +108,8 @@ export interface TelegramAssistantMessagePreviewStartDeps<TMessage> {
|
|
|
102
108
|
finalizeMarkdownPreview: (
|
|
103
109
|
chatId: number,
|
|
104
110
|
markdown: string,
|
|
111
|
+
replyToMessageId?: number,
|
|
112
|
+
options?: { replyMarkup?: TReplyMarkup },
|
|
105
113
|
) => Promise<boolean>;
|
|
106
114
|
}
|
|
107
115
|
|
|
@@ -115,9 +123,11 @@ export interface TelegramAssistantMessagePreviewUpdateDeps<TMessage> {
|
|
|
115
123
|
schedulePreviewFlush: (chatId: number) => void;
|
|
116
124
|
}
|
|
117
125
|
|
|
118
|
-
export type TelegramAssistantMessagePreviewHookDeps<
|
|
119
|
-
|
|
120
|
-
|
|
126
|
+
export type TelegramAssistantMessagePreviewHookDeps<
|
|
127
|
+
TMessage,
|
|
128
|
+
TReplyMarkup = TelegramPreviewReplyMarkup,
|
|
129
|
+
> = TelegramAssistantMessagePreviewStartDeps<TMessage, TReplyMarkup> &
|
|
130
|
+
TelegramAssistantMessagePreviewUpdateDeps<TMessage>;
|
|
121
131
|
|
|
122
132
|
export interface TelegramAssistantMessagePreviewHookEvent<TMessage> {
|
|
123
133
|
message: TMessage;
|
|
@@ -132,7 +142,9 @@ export interface TelegramAssistantMessagePreviewHooks<TMessage> {
|
|
|
132
142
|
) => Promise<void>;
|
|
133
143
|
}
|
|
134
144
|
|
|
135
|
-
export interface TelegramPreviewControllerDeps
|
|
145
|
+
export interface TelegramPreviewControllerDeps<
|
|
146
|
+
TReplyMarkup = TelegramPreviewReplyMarkup,
|
|
147
|
+
> {
|
|
136
148
|
getDefaultReplyToMessageId?: () => number | undefined;
|
|
137
149
|
maxMessageLength?: number;
|
|
138
150
|
renderPreviewText?: (markdown: string) => string;
|
|
@@ -162,13 +174,13 @@ export interface TelegramPreviewControllerDeps {
|
|
|
162
174
|
chatId: number,
|
|
163
175
|
chunks: TelegramRenderedChunk[],
|
|
164
176
|
replyToMessageId: number | undefined,
|
|
165
|
-
options?: { replyMarkup?:
|
|
177
|
+
options?: { replyMarkup?: TReplyMarkup },
|
|
166
178
|
) => Promise<number | undefined>;
|
|
167
179
|
editRenderedMessage: (
|
|
168
180
|
chatId: number,
|
|
169
181
|
messageId: number,
|
|
170
182
|
chunks: TelegramRenderedChunk[],
|
|
171
|
-
options?: { replyMarkup?:
|
|
183
|
+
options?: { replyMarkup?: TReplyMarkup },
|
|
172
184
|
) => Promise<number | undefined>;
|
|
173
185
|
throttleMs?: number;
|
|
174
186
|
maxDraftId?: number;
|
|
@@ -179,7 +191,9 @@ export interface TelegramPreviewControllerDeps {
|
|
|
179
191
|
clearTimer?: (timer: ReturnType<typeof setTimeout>) => void;
|
|
180
192
|
}
|
|
181
193
|
|
|
182
|
-
export interface TelegramPreviewController
|
|
194
|
+
export interface TelegramPreviewController<
|
|
195
|
+
TReplyMarkup = TelegramPreviewReplyMarkup,
|
|
196
|
+
> {
|
|
183
197
|
getState: () => TelegramPreviewRuntimeState | undefined;
|
|
184
198
|
setState: (state: TelegramPreviewRuntimeState | undefined) => void;
|
|
185
199
|
setPendingText: (text: string) => void;
|
|
@@ -193,7 +207,7 @@ export interface TelegramPreviewController {
|
|
|
193
207
|
chatId: number,
|
|
194
208
|
markdown: string,
|
|
195
209
|
replyToMessageId?: number,
|
|
196
|
-
options?: { replyMarkup?:
|
|
210
|
+
options?: { replyMarkup?: TReplyMarkup },
|
|
197
211
|
) => Promise<boolean>;
|
|
198
212
|
}
|
|
199
213
|
|
|
@@ -230,27 +244,31 @@ export function createTelegramPreviewMessageTransport(
|
|
|
230
244
|
};
|
|
231
245
|
}
|
|
232
246
|
|
|
233
|
-
export interface TelegramPreviewRenderedChunkTransportDeps
|
|
247
|
+
export interface TelegramPreviewRenderedChunkTransportDeps<
|
|
248
|
+
TReplyMarkup = TelegramPreviewReplyMarkup,
|
|
249
|
+
> {
|
|
234
250
|
sendRenderedChunks: (
|
|
235
251
|
chatId: number,
|
|
236
252
|
chunks: TelegramRenderedChunk[],
|
|
237
253
|
options?: {
|
|
238
254
|
replyToMessageId?: number;
|
|
239
|
-
replyMarkup?:
|
|
255
|
+
replyMarkup?: TReplyMarkup;
|
|
240
256
|
},
|
|
241
257
|
) => Promise<number | undefined>;
|
|
242
258
|
editRenderedMessage: (
|
|
243
259
|
chatId: number,
|
|
244
260
|
messageId: number,
|
|
245
261
|
chunks: TelegramRenderedChunk[],
|
|
246
|
-
options?: { replyMarkup?:
|
|
262
|
+
options?: { replyMarkup?: TReplyMarkup },
|
|
247
263
|
) => Promise<number | undefined>;
|
|
248
264
|
}
|
|
249
265
|
|
|
250
|
-
export function createTelegramPreviewRenderedChunkTransport
|
|
251
|
-
|
|
266
|
+
export function createTelegramPreviewRenderedChunkTransport<
|
|
267
|
+
TReplyMarkup = TelegramPreviewReplyMarkup,
|
|
268
|
+
>(
|
|
269
|
+
deps: TelegramPreviewRenderedChunkTransportDeps<TReplyMarkup>,
|
|
252
270
|
): Pick<
|
|
253
|
-
TelegramPreviewControllerDeps
|
|
271
|
+
TelegramPreviewControllerDeps<TReplyMarkup>,
|
|
254
272
|
"sendRenderedChunks" | "editRenderedMessage"
|
|
255
273
|
> {
|
|
256
274
|
return {
|
|
@@ -264,19 +282,23 @@ export function createTelegramPreviewRenderedChunkTransport(
|
|
|
264
282
|
};
|
|
265
283
|
}
|
|
266
284
|
|
|
267
|
-
export type TelegramPreviewControllerRuntimeDeps
|
|
268
|
-
|
|
285
|
+
export type TelegramPreviewControllerRuntimeDeps<
|
|
286
|
+
TReplyMarkup = TelegramPreviewReplyMarkup,
|
|
287
|
+
> = Omit<
|
|
288
|
+
TelegramPreviewControllerDeps<TReplyMarkup>,
|
|
269
289
|
| "sendMessage"
|
|
270
290
|
| "editMessageText"
|
|
271
291
|
| "sendRenderedChunks"
|
|
272
292
|
| "editRenderedMessage"
|
|
273
293
|
> &
|
|
274
294
|
TelegramPreviewMessageTransportDeps &
|
|
275
|
-
TelegramPreviewRenderedChunkTransportDeps
|
|
295
|
+
TelegramPreviewRenderedChunkTransportDeps<TReplyMarkup>;
|
|
276
296
|
|
|
277
|
-
export function createTelegramPreviewControllerRuntime
|
|
278
|
-
|
|
279
|
-
|
|
297
|
+
export function createTelegramPreviewControllerRuntime<
|
|
298
|
+
TReplyMarkup = TelegramPreviewReplyMarkup,
|
|
299
|
+
>(
|
|
300
|
+
deps: TelegramPreviewControllerRuntimeDeps<TReplyMarkup>,
|
|
301
|
+
): TelegramPreviewController<TReplyMarkup> {
|
|
280
302
|
return createTelegramPreviewController({
|
|
281
303
|
getDefaultReplyToMessageId: deps.getDefaultReplyToMessageId,
|
|
282
304
|
maxMessageLength: deps.maxMessageLength,
|
|
@@ -302,18 +324,25 @@ export function createTelegramPreviewControllerRuntime(
|
|
|
302
324
|
|
|
303
325
|
export interface TelegramAssistantPreviewRuntimeDeps<
|
|
304
326
|
TMessage,
|
|
305
|
-
|
|
327
|
+
TReplyMarkup = TelegramPreviewReplyMarkup,
|
|
328
|
+
> extends TelegramPreviewControllerRuntimeDeps<TReplyMarkup> {
|
|
306
329
|
getActiveTurn: () => TelegramPreviewActiveTurn | undefined;
|
|
307
330
|
isAssistantMessage: (message: TMessage) => boolean;
|
|
308
331
|
getMessageText: (message: TMessage) => string;
|
|
309
332
|
}
|
|
310
333
|
|
|
311
|
-
export type TelegramAssistantPreviewRuntime<
|
|
312
|
-
|
|
334
|
+
export type TelegramAssistantPreviewRuntime<
|
|
335
|
+
TMessage,
|
|
336
|
+
TReplyMarkup = TelegramPreviewReplyMarkup,
|
|
337
|
+
> = TelegramPreviewController<TReplyMarkup> &
|
|
338
|
+
TelegramAssistantMessagePreviewHooks<TMessage>;
|
|
313
339
|
|
|
314
|
-
export function createTelegramAssistantPreviewRuntime<
|
|
315
|
-
|
|
316
|
-
|
|
340
|
+
export function createTelegramAssistantPreviewRuntime<
|
|
341
|
+
TMessage,
|
|
342
|
+
TReplyMarkup = TelegramPreviewReplyMarkup,
|
|
343
|
+
>(
|
|
344
|
+
deps: TelegramAssistantPreviewRuntimeDeps<TMessage, TReplyMarkup>,
|
|
345
|
+
): TelegramAssistantPreviewRuntime<TMessage, TReplyMarkup> {
|
|
317
346
|
const controller = createTelegramPreviewControllerRuntime(deps);
|
|
318
347
|
return {
|
|
319
348
|
...controller,
|
|
@@ -331,9 +360,11 @@ export function createTelegramAssistantPreviewRuntime<TMessage>(
|
|
|
331
360
|
};
|
|
332
361
|
}
|
|
333
362
|
|
|
334
|
-
export function createTelegramPreviewController
|
|
335
|
-
|
|
336
|
-
|
|
363
|
+
export function createTelegramPreviewController<
|
|
364
|
+
TReplyMarkup = TelegramPreviewReplyMarkup,
|
|
365
|
+
>(
|
|
366
|
+
deps: TelegramPreviewControllerDeps<TReplyMarkup>,
|
|
367
|
+
): TelegramPreviewController<TReplyMarkup> {
|
|
337
368
|
let state: TelegramPreviewRuntimeState | undefined;
|
|
338
369
|
const clearTimer = deps.clearTimer ?? clearTimeout;
|
|
339
370
|
const setTimer =
|
|
@@ -349,7 +380,7 @@ export function createTelegramPreviewController(
|
|
|
349
380
|
let nextDraftId = 0;
|
|
350
381
|
const getRuntimeDeps = (
|
|
351
382
|
replyToMessageId?: number,
|
|
352
|
-
): TelegramPreviewRuntimeDeps => ({
|
|
383
|
+
): TelegramPreviewRuntimeDeps<TReplyMarkup> => ({
|
|
353
384
|
getState: () => state,
|
|
354
385
|
setState: (nextState) => {
|
|
355
386
|
state = nextState;
|
|
@@ -420,8 +451,11 @@ export function createTelegramPreviewController(
|
|
|
420
451
|
};
|
|
421
452
|
}
|
|
422
453
|
|
|
423
|
-
export function createTelegramAssistantMessagePreviewHooks<
|
|
424
|
-
|
|
454
|
+
export function createTelegramAssistantMessagePreviewHooks<
|
|
455
|
+
TMessage,
|
|
456
|
+
TReplyMarkup = TelegramPreviewReplyMarkup,
|
|
457
|
+
>(
|
|
458
|
+
deps: TelegramAssistantMessagePreviewHookDeps<TMessage, TReplyMarkup>,
|
|
425
459
|
): TelegramAssistantMessagePreviewHooks<TMessage> {
|
|
426
460
|
return {
|
|
427
461
|
onMessageStart: async (
|
|
@@ -437,9 +471,12 @@ export function createTelegramAssistantMessagePreviewHooks<TMessage>(
|
|
|
437
471
|
};
|
|
438
472
|
}
|
|
439
473
|
|
|
440
|
-
export async function handleTelegramAssistantMessagePreviewStart<
|
|
474
|
+
export async function handleTelegramAssistantMessagePreviewStart<
|
|
475
|
+
TMessage,
|
|
476
|
+
TReplyMarkup = TelegramPreviewReplyMarkup,
|
|
477
|
+
>(
|
|
441
478
|
message: TMessage,
|
|
442
|
-
deps: TelegramAssistantMessagePreviewStartDeps<TMessage>,
|
|
479
|
+
deps: TelegramAssistantMessagePreviewStartDeps<TMessage, TReplyMarkup>,
|
|
443
480
|
): Promise<void> {
|
|
444
481
|
const turn = deps.getActiveTurn();
|
|
445
482
|
if (!turn || !deps.isAssistantMessage(message)) return;
|
|
@@ -517,9 +554,11 @@ export function shouldUseTelegramDraftPreview(options: {
|
|
|
517
554
|
);
|
|
518
555
|
}
|
|
519
556
|
|
|
520
|
-
export async function clearTelegramPreview
|
|
557
|
+
export async function clearTelegramPreview<
|
|
558
|
+
TReplyMarkup = TelegramPreviewReplyMarkup,
|
|
559
|
+
>(
|
|
521
560
|
chatId: number,
|
|
522
|
-
deps: TelegramPreviewRuntimeDeps
|
|
561
|
+
deps: TelegramPreviewRuntimeDeps<TReplyMarkup>,
|
|
523
562
|
): Promise<void> {
|
|
524
563
|
void chatId;
|
|
525
564
|
const state = deps.getState();
|
|
@@ -528,10 +567,12 @@ export async function clearTelegramPreview(
|
|
|
528
567
|
deps.setState(undefined);
|
|
529
568
|
}
|
|
530
569
|
|
|
531
|
-
async function performTelegramPreviewFlush
|
|
570
|
+
async function performTelegramPreviewFlush<
|
|
571
|
+
TReplyMarkup = TelegramPreviewReplyMarkup,
|
|
572
|
+
>(
|
|
532
573
|
chatId: number,
|
|
533
574
|
state: TelegramPreviewRuntimeState,
|
|
534
|
-
deps: TelegramPreviewRuntimeDeps
|
|
575
|
+
deps: TelegramPreviewRuntimeDeps<TReplyMarkup>,
|
|
535
576
|
): Promise<void> {
|
|
536
577
|
const snapshot = buildTelegramPreviewSnapshot({
|
|
537
578
|
state,
|
|
@@ -580,9 +621,11 @@ async function performTelegramPreviewFlush(
|
|
|
580
621
|
state.lastSentStrategy = snapshot.strategy;
|
|
581
622
|
}
|
|
582
623
|
|
|
583
|
-
export async function flushTelegramPreview
|
|
624
|
+
export async function flushTelegramPreview<
|
|
625
|
+
TReplyMarkup = TelegramPreviewReplyMarkup,
|
|
626
|
+
>(
|
|
584
627
|
chatId: number,
|
|
585
|
-
deps: TelegramPreviewRuntimeDeps
|
|
628
|
+
deps: TelegramPreviewRuntimeDeps<TReplyMarkup>,
|
|
586
629
|
): Promise<void> {
|
|
587
630
|
const state = deps.getState();
|
|
588
631
|
if (!state) return;
|
|
@@ -607,9 +650,11 @@ export async function flushTelegramPreview(
|
|
|
607
650
|
}
|
|
608
651
|
}
|
|
609
652
|
|
|
610
|
-
export async function finalizeTelegramPreview
|
|
653
|
+
export async function finalizeTelegramPreview<
|
|
654
|
+
TReplyMarkup = TelegramPreviewReplyMarkup,
|
|
655
|
+
>(
|
|
611
656
|
chatId: number,
|
|
612
|
-
deps: TelegramPreviewRuntimeDeps
|
|
657
|
+
deps: TelegramPreviewRuntimeDeps<TReplyMarkup>,
|
|
613
658
|
): Promise<boolean> {
|
|
614
659
|
const state = deps.getState();
|
|
615
660
|
if (!state) return false;
|
|
@@ -628,11 +673,13 @@ export async function finalizeTelegramPreview(
|
|
|
628
673
|
return state.messageId !== undefined;
|
|
629
674
|
}
|
|
630
675
|
|
|
631
|
-
export async function finalizeTelegramMarkdownPreview
|
|
676
|
+
export async function finalizeTelegramMarkdownPreview<
|
|
677
|
+
TReplyMarkup = TelegramPreviewReplyMarkup,
|
|
678
|
+
>(
|
|
632
679
|
chatId: number,
|
|
633
680
|
markdown: string,
|
|
634
|
-
deps: TelegramPreviewRuntimeDeps
|
|
635
|
-
options?: { replyMarkup?:
|
|
681
|
+
deps: TelegramPreviewRuntimeDeps<TReplyMarkup>,
|
|
682
|
+
options?: { replyMarkup?: TReplyMarkup },
|
|
636
683
|
): Promise<boolean> {
|
|
637
684
|
const state = deps.getState();
|
|
638
685
|
if (!state) return false;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* π prompt-template bridge helpers
|
|
3
|
+
* Zones: pi agent prompts, telegram controls, filesystem
|
|
4
|
+
* Discovers π prompt-template slash commands and expands them before Telegram queue dispatch
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
import type { PiSlashCommandInfo } from "./pi.ts";
|
|
9
|
+
|
|
10
|
+
export interface TelegramPromptTemplateCommand {
|
|
11
|
+
command: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
path: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type TelegramPromptTemplateReader = (path: string) => string;
|
|
17
|
+
|
|
18
|
+
const TELEGRAM_BOT_COMMAND_NAME_PATTERN = /^[a-z0-9_]{1,32}$/;
|
|
19
|
+
|
|
20
|
+
function stripPromptTemplateFrontmatter(content: string): string {
|
|
21
|
+
if (!content.startsWith("---")) return content;
|
|
22
|
+
const lines = content.split("\n");
|
|
23
|
+
if (lines[0]?.trim() !== "---") return content;
|
|
24
|
+
for (let index = 1; index < lines.length; index += 1) {
|
|
25
|
+
if (lines[index]?.trim() === "---")
|
|
26
|
+
return lines.slice(index + 1).join("\n");
|
|
27
|
+
}
|
|
28
|
+
return content;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function parsePromptTemplateArgs(argsString: string): string[] {
|
|
32
|
+
const args: string[] = [];
|
|
33
|
+
let current = "";
|
|
34
|
+
let quote: string | undefined;
|
|
35
|
+
for (const char of argsString) {
|
|
36
|
+
if (quote) {
|
|
37
|
+
if (char === quote) {
|
|
38
|
+
quote = undefined;
|
|
39
|
+
} else {
|
|
40
|
+
current += char;
|
|
41
|
+
}
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (char === '"' || char === "'") {
|
|
45
|
+
quote = char;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (char === " " || char === "\t") {
|
|
49
|
+
if (current) args.push(current);
|
|
50
|
+
current = "";
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
current += char;
|
|
54
|
+
}
|
|
55
|
+
if (current) args.push(current);
|
|
56
|
+
return args;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function substitutePromptTemplateArgs(
|
|
60
|
+
content: string,
|
|
61
|
+
args: readonly string[],
|
|
62
|
+
): string {
|
|
63
|
+
let result = content.replace(/\$(\d+)/g, (_, num: string) => {
|
|
64
|
+
const index = Number.parseInt(num, 10) - 1;
|
|
65
|
+
return args[index] ?? "";
|
|
66
|
+
});
|
|
67
|
+
result = result.replace(
|
|
68
|
+
/\$\{@:(\d+)(?::(\d+))?\}/g,
|
|
69
|
+
(_, startValue: string, lengthValue: string | undefined) => {
|
|
70
|
+
const start = Math.max(Number.parseInt(startValue, 10) - 1, 0);
|
|
71
|
+
if (lengthValue) {
|
|
72
|
+
const length = Number.parseInt(lengthValue, 10);
|
|
73
|
+
return args.slice(start, start + length).join(" ");
|
|
74
|
+
}
|
|
75
|
+
return args.slice(start).join(" ");
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
const allArgs = args.join(" ");
|
|
79
|
+
return result.replace(/\$ARGUMENTS/g, allArgs).replace(/\$@/g, allArgs);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function isTelegramPromptTemplateCommandName(name: string): boolean {
|
|
83
|
+
return TELEGRAM_BOT_COMMAND_NAME_PATTERN.test(name);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function mapPiPromptTemplateNameToTelegramCommandName(
|
|
87
|
+
name: string,
|
|
88
|
+
): string | undefined {
|
|
89
|
+
const command = name
|
|
90
|
+
.toLowerCase()
|
|
91
|
+
.replace(/[^a-z0-9_]+/g, "_")
|
|
92
|
+
.replace(/_+/g, "_")
|
|
93
|
+
.replace(/^_+|_+$/g, "")
|
|
94
|
+
.slice(0, 32)
|
|
95
|
+
.replace(/_+$/g, "");
|
|
96
|
+
return isTelegramPromptTemplateCommandName(command) ? command : undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface TelegramPromptTemplateCommandGetterDeps {
|
|
100
|
+
getCommands: () => readonly PiSlashCommandInfo[];
|
|
101
|
+
reservedCommandNames?: readonly string[];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function getTelegramPromptTemplateCommands(
|
|
105
|
+
commands: readonly PiSlashCommandInfo[],
|
|
106
|
+
reservedNames: ReadonlySet<string> = new Set(),
|
|
107
|
+
): TelegramPromptTemplateCommand[] {
|
|
108
|
+
const seen = new Set<string>();
|
|
109
|
+
const promptCommands: TelegramPromptTemplateCommand[] = [];
|
|
110
|
+
for (const command of commands) {
|
|
111
|
+
if (command.source !== "prompt") continue;
|
|
112
|
+
const telegramCommand = mapPiPromptTemplateNameToTelegramCommandName(
|
|
113
|
+
command.name,
|
|
114
|
+
);
|
|
115
|
+
if (!telegramCommand) continue;
|
|
116
|
+
if (reservedNames.has(telegramCommand)) continue;
|
|
117
|
+
if (seen.has(telegramCommand)) continue;
|
|
118
|
+
seen.add(telegramCommand);
|
|
119
|
+
promptCommands.push({
|
|
120
|
+
command: telegramCommand,
|
|
121
|
+
description: command.description,
|
|
122
|
+
path: command.sourceInfo.path,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return promptCommands.sort((a, b) => a.command.localeCompare(b.command));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function createTelegramPromptTemplateCommandGetter(
|
|
129
|
+
deps: TelegramPromptTemplateCommandGetterDeps,
|
|
130
|
+
): () => TelegramPromptTemplateCommand[] {
|
|
131
|
+
const reservedNames = new Set(deps.reservedCommandNames);
|
|
132
|
+
return function getPromptTemplateCommands() {
|
|
133
|
+
return getTelegramPromptTemplateCommands(deps.getCommands(), reservedNames);
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function expandTelegramPromptTemplateCommand(
|
|
138
|
+
commandName: string,
|
|
139
|
+
args: string,
|
|
140
|
+
commands: readonly TelegramPromptTemplateCommand[],
|
|
141
|
+
readTemplate: TelegramPromptTemplateReader = (path) =>
|
|
142
|
+
readFileSync(path, "utf-8"),
|
|
143
|
+
): string | undefined {
|
|
144
|
+
const command = commands.find(
|
|
145
|
+
(candidate) => candidate.command === commandName,
|
|
146
|
+
);
|
|
147
|
+
if (!command) return undefined;
|
|
148
|
+
const content = stripPromptTemplateFrontmatter(readTemplate(command.path));
|
|
149
|
+
return substitutePromptTemplateArgs(content, parsePromptTemplateArgs(args));
|
|
150
|
+
}
|
package/lib/prompts.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Telegram prompt injection helpers
|
|
3
|
+
* Zones: pi agent prompts, telegram guidance
|
|
3
4
|
* Owns Telegram-specific system prompt suffixes injected into pi agent turns
|
|
4
5
|
*/
|
|
5
6
|
|
|
@@ -14,6 +15,7 @@ Inbound context:
|
|
|
14
15
|
- \`[telegram]\` marks Telegram-originated messages.
|
|
15
16
|
- \`[reply]\` is quoted context from the replied-to message, not a new instruction by itself. Use it to resolve references like "this", "it", or "that message"; the actual instruction is before [reply] unless it explicitly asks to act on the quote.
|
|
16
17
|
- \`[attachments]\` gives a base directory plus relative local files; resolve and read them as needed. \`[outputs]\` contains attachment-handler stdout such as transcriptions or extracted text for those attachments.
|
|
18
|
+
- Unknown \`[callback] ...\` messages may be intended for another extension; if you see one, say the callback was not handled and the environment may be misconfigured.
|
|
17
19
|
|
|
18
20
|
Telegram-visible output:
|
|
19
21
|
- Telegram is often phone-width; prefer narrow table columns because wide monospace tables can become unreadable.
|