@llblab/pi-telegram 0.24.3 → 0.24.5
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 +6 -5
- package/CHANGELOG.md +11 -0
- package/README.md +8 -4
- package/docs/architecture.md +8 -7
- package/docs/multi-instance-bus.md +7 -5
- package/docs/public-api.md +7 -3
- package/index.ts +63 -22
- package/lib/bindings.ts +32 -11
- package/lib/bus-follower.ts +31 -1
- package/lib/commands.ts +44 -2
- package/lib/config.ts +51 -0
- package/lib/locks.ts +50 -45
- package/lib/menu-settings.ts +94 -6
- package/lib/outbound-attachments.ts +10 -15
- package/lib/pi.ts +6 -0
- package/lib/prompts.ts +126 -8
- package/lib/recovery.ts +464 -0
- package/lib/routing.ts +301 -256
- package/package.json +1 -1
package/lib/menu-settings.ts
CHANGED
|
@@ -26,6 +26,7 @@ export interface TelegramSettingsStateDeps {
|
|
|
26
26
|
getTimeInjectionMode: () => TelegramTimeMode;
|
|
27
27
|
getVoiceReplyMode: () => TelegramVoiceReplyMode;
|
|
28
28
|
isVoiceReplyModeConfigured: () => boolean;
|
|
29
|
+
isAutomaticThreadCleanupEnabled: () => boolean;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export interface TelegramSettingsMutationDeps extends TelegramSettingsStateDeps {
|
|
@@ -38,6 +39,7 @@ export interface TelegramSettingsMutationDeps extends TelegramSettingsStateDeps
|
|
|
38
39
|
mode: TelegramVoiceReplyMode | undefined,
|
|
39
40
|
) => Promise<void>;
|
|
40
41
|
setTimeInjectionMode: (mode: TelegramTimeMode) => Promise<void>;
|
|
42
|
+
setAutomaticThreadCleanupEnabled: (enabled: boolean) => Promise<void>;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
export interface TelegramSettingsMenuOpenDeps<
|
|
@@ -99,6 +101,7 @@ export interface TelegramSettingsMenuRuntimeDeps<
|
|
|
99
101
|
TContext,
|
|
100
102
|
TModel extends MenuModel = MenuModel,
|
|
101
103
|
> extends TelegramSettingsMutationDeps {
|
|
104
|
+
reloadConfig?: () => Promise<void>;
|
|
102
105
|
getModelMenuState: (
|
|
103
106
|
chatId: number,
|
|
104
107
|
ctx: TContext,
|
|
@@ -129,6 +132,8 @@ export interface TelegramSettingsMenuRuntimeDeps<
|
|
|
129
132
|
}
|
|
130
133
|
|
|
131
134
|
export const SETTINGS_MENU_TITLE = "<b>⚙️ Settings:</b>";
|
|
135
|
+
export const AUTOMATIC_THREAD_CLEANUP_SETTINGS_TITLE =
|
|
136
|
+
"<b>🧹 Automatic thread cleanup:</b>";
|
|
132
137
|
export const PROACTIVE_PUSH_SETTINGS_TITLE = "<b>📌 Proactive push:</b>";
|
|
133
138
|
export const DRAFT_PREVIEWS_SETTINGS_TITLE = "<b>📝 Draft previews:</b>";
|
|
134
139
|
export const ASSISTANT_RENDERING_SETTINGS_TITLE =
|
|
@@ -158,6 +163,19 @@ export function buildTelegramSettingsMenuText(): string {
|
|
|
158
163
|
return SETTINGS_MENU_TITLE;
|
|
159
164
|
}
|
|
160
165
|
|
|
166
|
+
export function buildAutomaticThreadCleanupSettingsText(
|
|
167
|
+
enabled: boolean,
|
|
168
|
+
): string {
|
|
169
|
+
return [
|
|
170
|
+
`${AUTOMATIC_THREAD_CLEANUP_SETTINGS_TITLE} <code>${enabled ? "on" : "off"}</code>`,
|
|
171
|
+
"",
|
|
172
|
+
"Delete this Pi instance's Telegram tab when Pi quits normally.",
|
|
173
|
+
"",
|
|
174
|
+
"<code>-</code> <code>on</code> (default): delete the bound thread and release Telegram authority on graceful quit.",
|
|
175
|
+
"<code>-</code> <code>off</code>: preserve the tab as a restart hint; manual /telegram-disconnect still confirms and deletes it.",
|
|
176
|
+
].join("\n");
|
|
177
|
+
}
|
|
178
|
+
|
|
161
179
|
export function buildProactivePushSettingsText(
|
|
162
180
|
proactivePushEnabled: boolean,
|
|
163
181
|
): string {
|
|
@@ -236,6 +254,7 @@ export function buildTelegramSettingsMenuReplyMarkup(
|
|
|
236
254
|
TelegramTimeMode | TelegramSectionRegistry,
|
|
237
255
|
sectionRegistryOrVoiceReplyModeConfigured?: TelegramSectionRegistry | boolean,
|
|
238
256
|
voiceReplyModeConfigured = true,
|
|
257
|
+
automaticThreadCleanupEnabled = true,
|
|
239
258
|
): TelegramSettingsMenuReplyMarkup {
|
|
240
259
|
const hasRenderingMode =
|
|
241
260
|
assistantRenderingModeOrVoiceReplyMode === "rich" ||
|
|
@@ -269,6 +288,12 @@ export function buildTelegramSettingsMenuReplyMarkup(
|
|
|
269
288
|
}
|
|
270
289
|
}
|
|
271
290
|
rows.push(
|
|
291
|
+
[
|
|
292
|
+
{
|
|
293
|
+
text: `🧹 Auto thread cleanup: ${automaticThreadCleanupEnabled ? "on" : "off"}`,
|
|
294
|
+
callback_data: "settings:open:automatic-thread-cleanup",
|
|
295
|
+
},
|
|
296
|
+
],
|
|
272
297
|
[
|
|
273
298
|
{
|
|
274
299
|
text: `👄 Voice reply: ${getTelegramSettingsStateValueLabel(
|
|
@@ -328,6 +353,7 @@ export async function openTelegramSettingsMenu<
|
|
|
328
353
|
deps.getTimeInjectionMode(),
|
|
329
354
|
sectionRegistry,
|
|
330
355
|
deps.isVoiceReplyModeConfigured(),
|
|
356
|
+
deps.isAutomaticThreadCleanupEnabled(),
|
|
331
357
|
),
|
|
332
358
|
);
|
|
333
359
|
if (messageId === undefined) return;
|
|
@@ -336,6 +362,26 @@ export async function openTelegramSettingsMenu<
|
|
|
336
362
|
deps.storeModelMenuState(state);
|
|
337
363
|
}
|
|
338
364
|
|
|
365
|
+
export function buildAutomaticThreadCleanupSettingsReplyMarkup(
|
|
366
|
+
enabled: boolean,
|
|
367
|
+
): TelegramSettingsMenuReplyMarkup {
|
|
368
|
+
return {
|
|
369
|
+
inline_keyboard: [
|
|
370
|
+
[{ text: "⬆️ Back", callback_data: "settings:list" }],
|
|
371
|
+
[
|
|
372
|
+
{
|
|
373
|
+
text: enabled ? "🟢 On" : "⚫️ On",
|
|
374
|
+
callback_data: "settings:set:automatic-thread-cleanup:on",
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
text: enabled ? "⚫️ Off" : "🟡 Off",
|
|
378
|
+
callback_data: "settings:set:automatic-thread-cleanup:off",
|
|
379
|
+
},
|
|
380
|
+
],
|
|
381
|
+
],
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
339
385
|
export function buildProactivePushSettingsReplyMarkup(
|
|
340
386
|
proactivePushEnabled: boolean,
|
|
341
387
|
): TelegramSettingsMenuReplyMarkup {
|
|
@@ -443,10 +489,21 @@ export async function updateTelegramSettingsMenuMessage(
|
|
|
443
489
|
deps.getTimeInjectionMode(),
|
|
444
490
|
sectionRegistry,
|
|
445
491
|
deps.isVoiceReplyModeConfigured(),
|
|
492
|
+
deps.isAutomaticThreadCleanupEnabled(),
|
|
446
493
|
),
|
|
447
494
|
);
|
|
448
495
|
}
|
|
449
496
|
|
|
497
|
+
export async function updateAutomaticThreadCleanupSettingsMessage(
|
|
498
|
+
deps: TelegramSettingsMenuCallbackDeps,
|
|
499
|
+
): Promise<void> {
|
|
500
|
+
const enabled = deps.isAutomaticThreadCleanupEnabled();
|
|
501
|
+
await deps.updateSettingsMessage(
|
|
502
|
+
buildAutomaticThreadCleanupSettingsText(enabled),
|
|
503
|
+
buildAutomaticThreadCleanupSettingsReplyMarkup(enabled),
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
|
|
450
507
|
export async function updateProactivePushSettingsMessage(
|
|
451
508
|
deps: TelegramSettingsMenuCallbackDeps,
|
|
452
509
|
): Promise<void> {
|
|
@@ -509,6 +566,11 @@ export async function handleTelegramSettingsMenuCallbackAction(
|
|
|
509
566
|
await deps.answerCallbackQuery(callbackQueryId);
|
|
510
567
|
return true;
|
|
511
568
|
}
|
|
569
|
+
if (data === "settings:open:automatic-thread-cleanup") {
|
|
570
|
+
await updateAutomaticThreadCleanupSettingsMessage(deps);
|
|
571
|
+
await deps.answerCallbackQuery(callbackQueryId);
|
|
572
|
+
return true;
|
|
573
|
+
}
|
|
512
574
|
if (data === "settings:open:proactive") {
|
|
513
575
|
await updateProactivePushSettingsMessage(deps);
|
|
514
576
|
await deps.answerCallbackQuery(callbackQueryId);
|
|
@@ -598,6 +660,19 @@ export async function handleTelegramSettingsMenuCallbackAction(
|
|
|
598
660
|
return true;
|
|
599
661
|
}
|
|
600
662
|
}
|
|
663
|
+
if (
|
|
664
|
+
data === "settings:set:automatic-thread-cleanup:on" ||
|
|
665
|
+
data === "settings:set:automatic-thread-cleanup:off"
|
|
666
|
+
) {
|
|
667
|
+
const enabled = data.endsWith(":on");
|
|
668
|
+
await deps.setAutomaticThreadCleanupEnabled(enabled);
|
|
669
|
+
await updateAutomaticThreadCleanupSettingsMessage(deps);
|
|
670
|
+
await deps.answerCallbackQuery(
|
|
671
|
+
callbackQueryId,
|
|
672
|
+
`Automatic thread cleanup ${enabled ? "enabled" : "disabled"}`,
|
|
673
|
+
);
|
|
674
|
+
return true;
|
|
675
|
+
}
|
|
601
676
|
if (
|
|
602
677
|
data === "settings:set:proactive:on" ||
|
|
603
678
|
data === "settings:set:proactive:off"
|
|
@@ -623,8 +698,9 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
623
698
|
sectionRegistry?: TelegramSectionRegistry,
|
|
624
699
|
): TelegramSettingsMenuRuntime<TContext> {
|
|
625
700
|
return {
|
|
626
|
-
openSettingsMenu: (chatId, _replyToMessageId, ctx) =>
|
|
627
|
-
|
|
701
|
+
openSettingsMenu: async (chatId, _replyToMessageId, ctx) => {
|
|
702
|
+
await deps.reloadConfig?.();
|
|
703
|
+
return openTelegramSettingsMenu(
|
|
628
704
|
{
|
|
629
705
|
getModelMenuState: () => deps.getModelMenuState(chatId, ctx),
|
|
630
706
|
isProactivePushEnabled: deps.isProactivePushEnabled,
|
|
@@ -633,6 +709,8 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
633
709
|
getVoiceReplyMode: deps.getVoiceReplyMode,
|
|
634
710
|
isVoiceReplyModeConfigured: deps.isVoiceReplyModeConfigured,
|
|
635
711
|
getTimeInjectionMode: deps.getTimeInjectionMode,
|
|
712
|
+
isAutomaticThreadCleanupEnabled:
|
|
713
|
+
deps.isAutomaticThreadCleanupEnabled,
|
|
636
714
|
sendSettingsMenu: (state, text, replyMarkup) =>
|
|
637
715
|
deps.sendInteractiveMessage(
|
|
638
716
|
state.chatId,
|
|
@@ -643,9 +721,11 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
643
721
|
storeModelMenuState: deps.storeModelMenuState,
|
|
644
722
|
},
|
|
645
723
|
sectionRegistry,
|
|
646
|
-
)
|
|
647
|
-
|
|
648
|
-
|
|
724
|
+
);
|
|
725
|
+
},
|
|
726
|
+
updateSettingsMenuMessage: async (state) => {
|
|
727
|
+
await deps.reloadConfig?.();
|
|
728
|
+
return updateTelegramSettingsMenuMessage(
|
|
649
729
|
{
|
|
650
730
|
isProactivePushEnabled: deps.isProactivePushEnabled,
|
|
651
731
|
areDraftPreviewsEnabled: deps.areDraftPreviewsEnabled,
|
|
@@ -653,6 +733,8 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
653
733
|
getVoiceReplyMode: deps.getVoiceReplyMode,
|
|
654
734
|
isVoiceReplyModeConfigured: deps.isVoiceReplyModeConfigured,
|
|
655
735
|
getTimeInjectionMode: deps.getTimeInjectionMode,
|
|
736
|
+
isAutomaticThreadCleanupEnabled:
|
|
737
|
+
deps.isAutomaticThreadCleanupEnabled,
|
|
656
738
|
updateSettingsMessage: (text, replyMarkup) =>
|
|
657
739
|
deps.editInteractiveMessage(
|
|
658
740
|
state.chatId,
|
|
@@ -663,9 +745,11 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
663
745
|
),
|
|
664
746
|
},
|
|
665
747
|
sectionRegistry,
|
|
666
|
-
)
|
|
748
|
+
);
|
|
749
|
+
},
|
|
667
750
|
handleCallbackQuery: async (query, ctx) => {
|
|
668
751
|
if (!query.data?.startsWith("settings:")) return false;
|
|
752
|
+
await deps.reloadConfig?.();
|
|
669
753
|
const messageId = query.message?.message_id;
|
|
670
754
|
const chatId = query.message?.chat?.id;
|
|
671
755
|
let state = deps.getStoredModelMenuState(messageId, chatId);
|
|
@@ -693,11 +777,15 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
693
777
|
getVoiceReplyMode: deps.getVoiceReplyMode,
|
|
694
778
|
isVoiceReplyModeConfigured: deps.isVoiceReplyModeConfigured,
|
|
695
779
|
getTimeInjectionMode: deps.getTimeInjectionMode,
|
|
780
|
+
isAutomaticThreadCleanupEnabled:
|
|
781
|
+
deps.isAutomaticThreadCleanupEnabled,
|
|
696
782
|
setProactivePushEnabled: deps.setProactivePushEnabled,
|
|
697
783
|
setDraftPreviewsEnabled: deps.setDraftPreviewsEnabled,
|
|
698
784
|
setAssistantRenderingMode: deps.setAssistantRenderingMode,
|
|
699
785
|
setVoiceReplyMode: deps.setVoiceReplyMode,
|
|
700
786
|
setTimeInjectionMode: deps.setTimeInjectionMode,
|
|
787
|
+
setAutomaticThreadCleanupEnabled:
|
|
788
|
+
deps.setAutomaticThreadCleanupEnabled,
|
|
701
789
|
updateSettingsMessage: (text, replyMarkup) =>
|
|
702
790
|
deps.editInteractiveMessage(
|
|
703
791
|
state.chatId,
|
|
@@ -10,6 +10,12 @@ import { basename } from "node:path";
|
|
|
10
10
|
import { Type } from "@sinclair/typebox";
|
|
11
11
|
|
|
12
12
|
import type { ExtensionAPI } from "./pi.ts";
|
|
13
|
+
import {
|
|
14
|
+
TELEGRAM_ATTACH_PROMPT_GUIDELINES,
|
|
15
|
+
TELEGRAM_ATTACH_PROMPT_SNIPPET,
|
|
16
|
+
TELEGRAM_MESSAGE_PROMPT_GUIDELINES,
|
|
17
|
+
TELEGRAM_MESSAGE_PROMPT_SNIPPET,
|
|
18
|
+
} from "./prompts.ts";
|
|
13
19
|
import {
|
|
14
20
|
buildTelegramMultipartReplyParameters,
|
|
15
21
|
normalizeTelegramNativeMarkdown,
|
|
@@ -432,13 +438,8 @@ export function registerTelegramOutboundAttachmentTool(
|
|
|
432
438
|
label: "Telegram Attach",
|
|
433
439
|
description:
|
|
434
440
|
"Queue one or more local files for the active Telegram reply, or send them immediately to Telegram when no Telegram turn is active.",
|
|
435
|
-
promptSnippet:
|
|
436
|
-
|
|
437
|
-
promptGuidelines: [
|
|
438
|
-
"When handling a [telegram] message and the user asked for a file or generated artifact, call telegram_attach with the local path instead of only mentioning the path in text.",
|
|
439
|
-
"When a local/TUI user explicitly asks to send a generated file to Telegram, telegram_attach can deliver it to the paired/default Telegram chat even without an active Telegram turn.",
|
|
440
|
-
"For an explicit thread target, provide chat_id plus thread_id; registered multi-instance followers default to their assigned thread target.",
|
|
441
|
-
],
|
|
441
|
+
promptSnippet: TELEGRAM_ATTACH_PROMPT_SNIPPET,
|
|
442
|
+
promptGuidelines: [...TELEGRAM_ATTACH_PROMPT_GUIDELINES],
|
|
442
443
|
parameters: Type.Object({
|
|
443
444
|
paths: Type.Array(
|
|
444
445
|
Type.String({ description: "Local file path to attach" }),
|
|
@@ -499,14 +500,8 @@ export function registerTelegramOutboundMessageTool(
|
|
|
499
500
|
label: "Telegram Message",
|
|
500
501
|
description:
|
|
501
502
|
"Send a Markdown text message directly to the paired/default Telegram chat or an explicit chat_id. Hidden telegram_button comments in the text become attached inline prompt buttons.",
|
|
502
|
-
promptSnippet:
|
|
503
|
-
|
|
504
|
-
promptGuidelines: [
|
|
505
|
-
"Use telegram_message only when the user explicitly asks to send a message to Telegram from the local/TUI side, or names a concrete Telegram delivery target.",
|
|
506
|
-
"For an explicit thread target, provide chat_id plus thread_id; registered multi-instance followers default to their assigned thread target.",
|
|
507
|
-
"Add buttons by embedding the same top-level telegram_button HTML comments used in normal Telegram replies; Telegram does not support standalone buttons.",
|
|
508
|
-
"Do not use this tool for ordinary Telegram-originated replies; answer normally so the bridge can deliver the active turn reply.",
|
|
509
|
-
],
|
|
503
|
+
promptSnippet: TELEGRAM_MESSAGE_PROMPT_SNIPPET,
|
|
504
|
+
promptGuidelines: [...TELEGRAM_MESSAGE_PROMPT_GUIDELINES],
|
|
510
505
|
parameters: Type.Object({
|
|
511
506
|
text: Type.String({ description: "Message text to send" }),
|
|
512
507
|
chat_id: Type.Optional(
|
package/lib/pi.ts
CHANGED
|
@@ -120,6 +120,8 @@ export interface PiExtensionApiRuntimePorts {
|
|
|
120
120
|
getCommands: ExtensionAPI["getCommands"];
|
|
121
121
|
getThinkingLevel: ExtensionAPI["getThinkingLevel"];
|
|
122
122
|
setThinkingLevel: ExtensionAPI["setThinkingLevel"];
|
|
123
|
+
getActiveTools: ExtensionAPI["getActiveTools"];
|
|
124
|
+
setActiveTools: ExtensionAPI["setActiveTools"];
|
|
123
125
|
setModel: ExtensionAPI["setModel"];
|
|
124
126
|
}
|
|
125
127
|
|
|
@@ -131,6 +133,8 @@ export function createExtensionApiRuntimePorts(
|
|
|
131
133
|
| "getCommands"
|
|
132
134
|
| "getThinkingLevel"
|
|
133
135
|
| "setThinkingLevel"
|
|
136
|
+
| "getActiveTools"
|
|
137
|
+
| "setActiveTools"
|
|
134
138
|
| "setModel"
|
|
135
139
|
>,
|
|
136
140
|
): PiExtensionApiRuntimePorts {
|
|
@@ -141,6 +145,8 @@ export function createExtensionApiRuntimePorts(
|
|
|
141
145
|
getCommands: () => api.getCommands(),
|
|
142
146
|
getThinkingLevel: () => api.getThinkingLevel(),
|
|
143
147
|
setThinkingLevel: (level) => api.setThinkingLevel(level),
|
|
148
|
+
getActiveTools: () => api.getActiveTools(),
|
|
149
|
+
setActiveTools: (names) => api.setActiveTools(names),
|
|
144
150
|
setModel: (model) => api.setModel(model),
|
|
145
151
|
};
|
|
146
152
|
}
|
package/lib/prompts.ts
CHANGED
|
@@ -18,6 +18,106 @@ const TELEGRAM_TURN_SYSTEM_PROMPT_SUFFIX = `
|
|
|
18
18
|
|
|
19
19
|
Telegram turn note: Call \`telegram_help\` if you need the pi-telegram bridge action contract.`;
|
|
20
20
|
|
|
21
|
+
export const TELEGRAM_ATTACH_PROMPT_SNIPPET =
|
|
22
|
+
"Queue files for the active Telegram reply; outside Telegram turns, send files directly to Telegram.";
|
|
23
|
+
export const TELEGRAM_ATTACH_PROMPT_GUIDELINES = [
|
|
24
|
+
"When handling a [telegram] message and the user asked for a file or generated artifact, call telegram_attach with the local path instead of only mentioning the path in text.",
|
|
25
|
+
"When a local/TUI user explicitly asks to send a generated file to Telegram, telegram_attach can deliver it to the paired/default Telegram chat even without an active Telegram turn.",
|
|
26
|
+
"For an explicit thread target, provide chat_id plus thread_id; registered multi-instance followers default to their assigned thread target.",
|
|
27
|
+
] as const;
|
|
28
|
+
export const TELEGRAM_MESSAGE_PROMPT_SNIPPET =
|
|
29
|
+
"Send direct Telegram Markdown text when the user explicitly asks for Telegram delivery outside the normal reply flow.";
|
|
30
|
+
export const TELEGRAM_MESSAGE_PROMPT_GUIDELINES = [
|
|
31
|
+
"Use telegram_message only when the user explicitly asks to send a message to Telegram from the local/TUI side, or names a concrete Telegram delivery target.",
|
|
32
|
+
"For an explicit thread target, provide chat_id plus thread_id; registered multi-instance followers default to their assigned thread target.",
|
|
33
|
+
"Add buttons by embedding the same top-level telegram_button HTML comments used in normal Telegram replies; Telegram does not support standalone buttons.",
|
|
34
|
+
"Do not use this tool for ordinary Telegram-originated replies; answer normally so the bridge can deliver the active turn reply.",
|
|
35
|
+
] as const;
|
|
36
|
+
|
|
37
|
+
const TELEGRAM_MODEL_CONTEXT_TOOL_NAMES = new Set([
|
|
38
|
+
"telegram_attach",
|
|
39
|
+
"telegram_message",
|
|
40
|
+
"telegram_help",
|
|
41
|
+
]);
|
|
42
|
+
const TELEGRAM_MODEL_CONTEXT_MEMORY_KEY = Symbol.for(
|
|
43
|
+
"@llblab/pi-telegram:model-context-suspended-tools",
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
export interface TelegramModelContextAvailabilityMemory {
|
|
47
|
+
suspended: boolean;
|
|
48
|
+
toolNames: Set<string>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getTelegramModelContextAvailabilityMemory(): TelegramModelContextAvailabilityMemory {
|
|
52
|
+
const globals = globalThis as unknown as Record<symbol, unknown>;
|
|
53
|
+
const existing = globals[TELEGRAM_MODEL_CONTEXT_MEMORY_KEY];
|
|
54
|
+
if (
|
|
55
|
+
existing &&
|
|
56
|
+
typeof existing === "object" &&
|
|
57
|
+
"toolNames" in existing &&
|
|
58
|
+
(existing as { toolNames?: unknown }).toolNames instanceof Set
|
|
59
|
+
) {
|
|
60
|
+
return existing as TelegramModelContextAvailabilityMemory;
|
|
61
|
+
}
|
|
62
|
+
const memory: TelegramModelContextAvailabilityMemory = {
|
|
63
|
+
suspended: false,
|
|
64
|
+
toolNames: new Set<string>(),
|
|
65
|
+
};
|
|
66
|
+
globals[TELEGRAM_MODEL_CONTEXT_MEMORY_KEY] = memory;
|
|
67
|
+
return memory;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface TelegramModelContextAvailabilityRuntime {
|
|
71
|
+
reconcile: () => void;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function createTelegramModelContextAvailabilityRuntime(deps: {
|
|
75
|
+
getActiveTools: () => string[];
|
|
76
|
+
setActiveTools: (names: string[]) => void;
|
|
77
|
+
isAvailable: () => boolean;
|
|
78
|
+
canReconcile?: () => boolean;
|
|
79
|
+
memory?: TelegramModelContextAvailabilityMemory;
|
|
80
|
+
}): TelegramModelContextAvailabilityRuntime {
|
|
81
|
+
const memory =
|
|
82
|
+
deps.memory ?? getTelegramModelContextAvailabilityMemory();
|
|
83
|
+
return {
|
|
84
|
+
reconcile() {
|
|
85
|
+
if (deps.canReconcile && !deps.canReconcile()) return;
|
|
86
|
+
const activeTools = deps.getActiveTools();
|
|
87
|
+
if (!deps.isAvailable()) {
|
|
88
|
+
if (!memory.suspended) {
|
|
89
|
+
memory.toolNames.clear();
|
|
90
|
+
for (const name of activeTools) {
|
|
91
|
+
if (TELEGRAM_MODEL_CONTEXT_TOOL_NAMES.has(name)) {
|
|
92
|
+
memory.toolNames.add(name);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
memory.suspended = true;
|
|
96
|
+
}
|
|
97
|
+
const nextTools = activeTools.filter(
|
|
98
|
+
(name) => !TELEGRAM_MODEL_CONTEXT_TOOL_NAMES.has(name),
|
|
99
|
+
);
|
|
100
|
+
if (nextTools.length !== activeTools.length) {
|
|
101
|
+
deps.setActiveTools(nextTools);
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (!memory.suspended) return;
|
|
106
|
+
const nextTools = [...activeTools];
|
|
107
|
+
for (const name of TELEGRAM_MODEL_CONTEXT_TOOL_NAMES) {
|
|
108
|
+
if (memory.toolNames.has(name) && !nextTools.includes(name)) {
|
|
109
|
+
nextTools.push(name);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
memory.toolNames.clear();
|
|
113
|
+
memory.suspended = false;
|
|
114
|
+
if (nextTools.length !== activeTools.length) {
|
|
115
|
+
deps.setActiveTools(nextTools);
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
21
121
|
function buildTelegramHelpText(profileName?: string): string {
|
|
22
122
|
const diagnosticsPaths = getTelegramDiagnosticsDisplayPaths(profileName);
|
|
23
123
|
return `--- TELEGRAM BRIDGE HELP ---
|
|
@@ -143,11 +243,25 @@ export function createTelegramBeforeAgentStartHook(
|
|
|
143
243
|
});
|
|
144
244
|
}
|
|
145
245
|
|
|
246
|
+
function stripTelegramToolMetadataFromSystemPrompt(
|
|
247
|
+
systemPrompt: string,
|
|
248
|
+
): string {
|
|
249
|
+
const telegramLines = new Set([
|
|
250
|
+
`- telegram_attach: ${TELEGRAM_ATTACH_PROMPT_SNIPPET}`,
|
|
251
|
+
`- telegram_message: ${TELEGRAM_MESSAGE_PROMPT_SNIPPET}`,
|
|
252
|
+
...TELEGRAM_ATTACH_PROMPT_GUIDELINES.map((line) => `- ${line}`),
|
|
253
|
+
...TELEGRAM_MESSAGE_PROMPT_GUIDELINES.map((line) => `- ${line}`),
|
|
254
|
+
]);
|
|
255
|
+
return systemPrompt
|
|
256
|
+
.split("\n")
|
|
257
|
+
.filter((line) => !telegramLines.has(line))
|
|
258
|
+
.join("\n");
|
|
259
|
+
}
|
|
260
|
+
|
|
146
261
|
export interface TelegramProactivePromptHookDeps<TContext> {
|
|
147
262
|
baseHook?: (event: BeforeAgentStartEvent) => { systemPrompt: string };
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
isCurrentOwner: (ctx: TContext) => boolean;
|
|
263
|
+
reconcileAvailability?: () => void;
|
|
264
|
+
isAvailable: (ctx: TContext) => boolean;
|
|
151
265
|
}
|
|
152
266
|
|
|
153
267
|
export function createTelegramProactiveBeforeAgentStartHook<TContext>(
|
|
@@ -158,10 +272,14 @@ export function createTelegramProactiveBeforeAgentStartHook<TContext>(
|
|
|
158
272
|
) => Promise<{ systemPrompt: string }> {
|
|
159
273
|
const baseHook = deps.baseHook ?? createTelegramBeforeAgentStartHook();
|
|
160
274
|
return async (event, ctx) => {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
275
|
+
deps.reconcileAvailability?.();
|
|
276
|
+
if (!deps.isAvailable(ctx)) {
|
|
277
|
+
return {
|
|
278
|
+
systemPrompt: stripTelegramToolMetadataFromSystemPrompt(
|
|
279
|
+
event.systemPrompt,
|
|
280
|
+
),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
return baseHook(event);
|
|
166
284
|
};
|
|
167
285
|
}
|