@llblab/pi-kit 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/README.md +3 -3
- package/node_modules/@llblab/pi-grow-loop/AGENTS.md +2 -2
- package/node_modules/@llblab/pi-grow-loop/CHANGELOG.md +4 -0
- package/node_modules/@llblab/pi-grow-loop/README.md +6 -6
- package/node_modules/@llblab/pi-grow-loop/index.ts +6 -3
- package/node_modules/@llblab/pi-grow-loop/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +0 -1
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +9 -5
- package/node_modules/@llblab/pi-telegram/README.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/outbound.md +8 -2
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +1 -0
- package/node_modules/@llblab/pi-telegram/index.ts +22 -19
- package/node_modules/@llblab/pi-telegram/lib/activity.ts +19 -5
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +9 -1
- package/node_modules/@llblab/pi-telegram/lib/config.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/delivery.ts +18 -18
- package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +7 -1
- package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +2 -2
- package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +23 -30
- package/node_modules/@llblab/pi-telegram/lib/outbound-voice.ts +28 -42
- package/node_modules/@llblab/pi-telegram/lib/outbound.ts +18 -14
- package/node_modules/@llblab/pi-telegram/lib/preview.ts +115 -70
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +18 -8
- package/node_modules/@llblab/pi-telegram/lib/replies.ts +46 -38
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +36 -3
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/node_modules/@llblab/skills/abcd-context/AGENTS.md +1 -0
- package/node_modules/@llblab/skills/abcd-context/CHANGELOG.md +6 -2
- package/node_modules/@llblab/skills/abcd-context/SKILL.md +1 -1
- package/node_modules/@llblab/skills/abcd-context/docs/validation-design.md +11 -5
- package/node_modules/@llblab/skills/abcd-context/scripts/_self-test.mjs +61 -0
- package/node_modules/@llblab/skills/abcd-context/scripts/validate-context.mjs +67 -0
- package/node_modules/@llblab/skills/package.json +1 -1
- package/node_modules/@llblab/skills/release-flow/SKILL.md +2 -4
- package/package.json +4 -4
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
TELEGRAM_MESSAGE_PROMPT_SNIPPET,
|
|
22
22
|
} from "./prompts.ts";
|
|
23
23
|
import {
|
|
24
|
-
|
|
24
|
+
withTelegramReplyParameters,
|
|
25
25
|
normalizeTelegramNativeMarkdown,
|
|
26
26
|
} from "./replies.ts";
|
|
27
27
|
import {
|
|
@@ -189,18 +189,10 @@ export function planTelegramRichOutboundAttachment(options: {
|
|
|
189
189
|
],
|
|
190
190
|
skip_entity_detection: true,
|
|
191
191
|
};
|
|
192
|
-
const replyParameters =
|
|
193
|
-
options.turn.replyToMessageId > 0
|
|
194
|
-
? JSON.stringify({
|
|
195
|
-
message_id: options.turn.replyToMessageId,
|
|
196
|
-
allow_sending_without_reply: true,
|
|
197
|
-
})
|
|
198
|
-
: undefined;
|
|
199
192
|
return {
|
|
200
193
|
method: "sendRichMessage",
|
|
201
194
|
fields: {
|
|
202
195
|
chat_id: String(options.turn.chatId),
|
|
203
|
-
...(replyParameters ? { reply_parameters: replyParameters } : {}),
|
|
204
196
|
...getTelegramMultipartTargetFields(options.turn.target),
|
|
205
197
|
rich_message: JSON.stringify(richMessage),
|
|
206
198
|
...(options.replyMarkup
|
|
@@ -230,12 +222,15 @@ export function createTelegramRichOutboundAttachmentSender(
|
|
|
230
222
|
});
|
|
231
223
|
if (!plan) return false;
|
|
232
224
|
try {
|
|
233
|
-
const result = await
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
225
|
+
const result = await withTelegramReplyParameters(
|
|
226
|
+
turn.chatId, turn.replyToMessageId, turn.target,
|
|
227
|
+
(replyParameters) => deps.sendMultipart(
|
|
228
|
+
plan.method,
|
|
229
|
+
{ ...plan.fields, ...(replyParameters ? { reply_parameters: JSON.stringify(replyParameters) } : {}) },
|
|
230
|
+
plan.fileField,
|
|
231
|
+
plan.filePath,
|
|
232
|
+
plan.fileName,
|
|
233
|
+
),
|
|
239
234
|
);
|
|
240
235
|
const messageId =
|
|
241
236
|
result && typeof result === "object" &&
|
|
@@ -964,21 +959,19 @@ export async function sendQueuedTelegramOutboundAttachments(
|
|
|
964
959
|
const isPhoto = isTelegramOutboundPhotoAttachmentPath(attachment.path);
|
|
965
960
|
const method = isPhoto ? "sendPhoto" : "sendDocument";
|
|
966
961
|
const fieldName = isPhoto ? "photo" : "document";
|
|
967
|
-
|
|
968
|
-
turn.chatId,
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
attachment.path,
|
|
981
|
-
attachment.fileName,
|
|
962
|
+
await withTelegramReplyParameters(
|
|
963
|
+
turn.chatId, turn.replyToMessageId, turn.target,
|
|
964
|
+
(replyParameters) => deps.sendMultipart(
|
|
965
|
+
method,
|
|
966
|
+
{
|
|
967
|
+
chat_id: String(turn.chatId),
|
|
968
|
+
...(replyParameters ? { reply_parameters: JSON.stringify(replyParameters) } : {}),
|
|
969
|
+
...getTelegramMultipartTargetFields(turn.target),
|
|
970
|
+
},
|
|
971
|
+
fieldName,
|
|
972
|
+
attachment.path,
|
|
973
|
+
attachment.fileName,
|
|
974
|
+
),
|
|
982
975
|
);
|
|
983
976
|
} catch (error) {
|
|
984
977
|
if (deps.isDeliveryActive?.() === false) return;
|
|
@@ -8,7 +8,7 @@ import { unlink } from "node:fs/promises";
|
|
|
8
8
|
import { basename, extname } from "node:path";
|
|
9
9
|
|
|
10
10
|
import { assertTelegramInlineKeyboardCallbackData } from "./keyboard.ts";
|
|
11
|
-
import {
|
|
11
|
+
import { withTelegramReplyParameters } from "./replies.ts";
|
|
12
12
|
import {
|
|
13
13
|
getTelegramTargetThreadParams,
|
|
14
14
|
type TelegramTarget,
|
|
@@ -80,17 +80,6 @@ export interface TelegramVoiceReplySenderPorts<THandler = unknown> {
|
|
|
80
80
|
getProgrammaticVoiceHandlers?: () => TelegramOutboundProgrammaticVoiceHandler[];
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
function buildVoiceReplyParameters(
|
|
84
|
-
chatId: number,
|
|
85
|
-
replyToPrompt: boolean | undefined,
|
|
86
|
-
replyToMessageId: number | undefined,
|
|
87
|
-
target?: TelegramTarget,
|
|
88
|
-
): string | undefined {
|
|
89
|
-
if (replyToPrompt === false || replyToMessageId === undefined)
|
|
90
|
-
return undefined;
|
|
91
|
-
return buildTelegramMultipartReplyParameters(chatId, replyToMessageId, target);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
83
|
async function ensureTelegramVoiceFileFormat(
|
|
95
84
|
filePath: string,
|
|
96
85
|
): Promise<string> {
|
|
@@ -131,36 +120,33 @@ export function createTelegramVoiceReplySender<THandler = unknown>(
|
|
|
131
120
|
if (deps.isDeliveryActive?.() === false) return;
|
|
132
121
|
await sendVoiceChatAction(deps, turn.chatId);
|
|
133
122
|
if (deps.isDeliveryActive?.() === false) return;
|
|
134
|
-
|
|
135
|
-
turn.chatId,
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
"voice",
|
|
162
|
-
voiceFilePath,
|
|
163
|
-
basename(voiceFilePath),
|
|
123
|
+
await withTelegramReplyParameters(
|
|
124
|
+
turn.chatId, options?.replyToPrompt === false ? undefined : turn.replyToMessageId, turn.target,
|
|
125
|
+
(replyParameters) => deps.sendMultipart(
|
|
126
|
+
"sendVoice",
|
|
127
|
+
{
|
|
128
|
+
chat_id: String(turn.chatId),
|
|
129
|
+
...(replyParameters ? { reply_parameters: JSON.stringify(replyParameters) } : {}),
|
|
130
|
+
...(turn.target
|
|
131
|
+
? Object.fromEntries(
|
|
132
|
+
Object.entries(getTelegramTargetThreadParams(turn.target)).map(
|
|
133
|
+
([key, value]) => [key, String(value)],
|
|
134
|
+
),
|
|
135
|
+
)
|
|
136
|
+
: {}),
|
|
137
|
+
...(options?.replyMarkup !== undefined && options.replyMarkup !== null
|
|
138
|
+
? {
|
|
139
|
+
reply_markup:
|
|
140
|
+
typeof options.replyMarkup === "string"
|
|
141
|
+
? options.replyMarkup
|
|
142
|
+
: JSON.stringify(options.replyMarkup),
|
|
143
|
+
}
|
|
144
|
+
: {}),
|
|
145
|
+
},
|
|
146
|
+
"voice",
|
|
147
|
+
voiceFilePath,
|
|
148
|
+
basename(voiceFilePath),
|
|
149
|
+
),
|
|
164
150
|
);
|
|
165
151
|
};
|
|
166
152
|
|
|
@@ -11,6 +11,7 @@ import { join } from "node:path";
|
|
|
11
11
|
import type { TelegramAssistantSegmentEvent } from "./activity.ts";
|
|
12
12
|
import { resolveTelegramTempDir } from "./paths.ts";
|
|
13
13
|
import * as Replies from "./replies.ts";
|
|
14
|
+
import type { TelegramPreparedPreviewDelivery } from "./preview.ts";
|
|
14
15
|
import { isTelegramApiCommitUnknownError } from "./telegram-api.ts";
|
|
15
16
|
import type {
|
|
16
17
|
TelegramEditMessageTextBody,
|
|
@@ -249,12 +250,8 @@ export interface TelegramOutboundTextPreviewRuntimeDeps<
|
|
|
249
250
|
> {
|
|
250
251
|
execCommand: TelegramVoiceReplySenderDeps["execCommand"];
|
|
251
252
|
getHandlers?: () => TelegramOutboundHandlerConfig[] | undefined;
|
|
252
|
-
finalizeMarkdownPreview:
|
|
253
|
-
|
|
254
|
-
markdown: string,
|
|
255
|
-
replyToMessageId: number,
|
|
256
|
-
options?: { replyMarkup?: TReplyMarkup },
|
|
257
|
-
) => Promise<boolean>;
|
|
253
|
+
finalizeMarkdownPreview: TelegramPreparedPreviewDelivery<TReplyMarkup>["finalizeMarkdownPreview"];
|
|
254
|
+
preparePreviewDelivery?: (isDeliveryActive: () => boolean) => TelegramPreparedPreviewDelivery<TReplyMarkup>;
|
|
258
255
|
cwd?: string;
|
|
259
256
|
recordRuntimeEvent?: TelegramVoiceReplySenderDeps["recordRuntimeEvent"];
|
|
260
257
|
}
|
|
@@ -729,12 +726,13 @@ export function createTelegramOutboundTextPreviewRuntime<
|
|
|
729
726
|
TReplyMarkup = unknown,
|
|
730
727
|
>(
|
|
731
728
|
deps: TelegramOutboundTextPreviewRuntimeDeps<TReplyMarkup>,
|
|
732
|
-
):
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
729
|
+
): {
|
|
730
|
+
finalizeMarkdownPreview: TelegramPreparedPreviewDelivery<TReplyMarkup>["finalizeMarkdownPreview"];
|
|
731
|
+
preparePreviewDelivery: (isDeliveryActive: () => boolean) => TelegramPreparedPreviewDelivery<TReplyMarkup> | undefined;
|
|
732
|
+
} {
|
|
733
|
+
const wrap = (
|
|
734
|
+
finalize: TelegramPreparedPreviewDelivery<TReplyMarkup>["finalizeMarkdownPreview"],
|
|
735
|
+
): TelegramPreparedPreviewDelivery<TReplyMarkup>["finalizeMarkdownPreview"] => async (
|
|
738
736
|
chatId,
|
|
739
737
|
markdown,
|
|
740
738
|
replyToMessageId,
|
|
@@ -747,7 +745,7 @@ export function createTelegramOutboundTextPreviewRuntime<
|
|
|
747
745
|
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
748
746
|
replyMarkup: options?.replyMarkup,
|
|
749
747
|
});
|
|
750
|
-
return
|
|
748
|
+
return finalize(
|
|
751
749
|
chatId,
|
|
752
750
|
transformed.text,
|
|
753
751
|
replyToMessageId,
|
|
@@ -758,6 +756,12 @@ export function createTelegramOutboundTextPreviewRuntime<
|
|
|
758
756
|
: {}),
|
|
759
757
|
},
|
|
760
758
|
);
|
|
759
|
+
};
|
|
760
|
+
return {
|
|
761
|
+
finalizeMarkdownPreview: wrap(deps.finalizeMarkdownPreview),
|
|
762
|
+
preparePreviewDelivery(isDeliveryActive) {
|
|
763
|
+
const prepared = deps.preparePreviewDelivery?.(isDeliveryActive);
|
|
764
|
+
return prepared ? { ...prepared, finalizeMarkdownPreview: wrap(prepared.finalizeMarkdownPreview) } : undefined;
|
|
761
765
|
},
|
|
762
766
|
};
|
|
763
767
|
}
|
|
@@ -1018,7 +1022,7 @@ export function createTelegramAssistantOutputSender<
|
|
|
1018
1022
|
};
|
|
1019
1023
|
await outboundRuntime.sendMarkdownReply(
|
|
1020
1024
|
target.chatId,
|
|
1021
|
-
undefined,
|
|
1025
|
+
event.source === "telegram" ? event.replyToMessageId : undefined,
|
|
1022
1026
|
buttonReply.markdown,
|
|
1023
1027
|
{
|
|
1024
1028
|
target,
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { normalizeTelegramNativeMarkdown } from "./replies.ts";
|
|
8
|
-
import {
|
|
8
|
+
import type { TelegramAssistantOutputPreparation } from "./activity.ts";
|
|
9
|
+
import { stripTelegramCommentMarkupForPreview } from "./outbound-markup.ts";
|
|
9
10
|
import {
|
|
10
11
|
getTelegramTargetThreadParams,
|
|
11
12
|
type TelegramTarget,
|
|
@@ -14,6 +15,7 @@ import { shouldSuppressPreviewForVoice } from "./voice.ts";
|
|
|
14
15
|
|
|
15
16
|
const TELEGRAM_DRAFT_ID_MAX = 2_147_483_647;
|
|
16
17
|
const TELEGRAM_DRAFT_PREVIEW_MAX_CHARS = 4096;
|
|
18
|
+
const TELEGRAM_DRAFT_INTERVAL_MS = 2_000;
|
|
17
19
|
|
|
18
20
|
export type TelegramDraftSupport = "unknown" | "supported";
|
|
19
21
|
|
|
@@ -27,6 +29,11 @@ export interface TelegramPreviewState {
|
|
|
27
29
|
export interface TelegramPreviewRuntimeState extends TelegramPreviewState {
|
|
28
30
|
flushPromise?: Promise<void>;
|
|
29
31
|
flushRequested?: boolean;
|
|
32
|
+
precedingFlush?: Promise<void>;
|
|
33
|
+
publicationPromise?: Promise<void>;
|
|
34
|
+
sealed?: boolean;
|
|
35
|
+
nextDraftAt?: number;
|
|
36
|
+
flushTimer?: ReturnType<typeof setTimeout>;
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
export type TelegramPreviewReplyMarkup = unknown;
|
|
@@ -35,6 +42,7 @@ export interface TelegramPreviewRuntimeDeps {
|
|
|
35
42
|
getState: () => TelegramPreviewRuntimeState | undefined;
|
|
36
43
|
setState: (state: TelegramPreviewRuntimeState | undefined) => void;
|
|
37
44
|
maxMessageLength: number;
|
|
45
|
+
minDraftIntervalMs?: number;
|
|
38
46
|
getDraftSupport: () => TelegramDraftSupport;
|
|
39
47
|
setDraftSupport: (support: TelegramDraftSupport) => void;
|
|
40
48
|
allocateDraftId: () => number;
|
|
@@ -64,23 +72,13 @@ export interface TelegramPreviewActiveTurn {
|
|
|
64
72
|
voiceReplyRequired?: boolean;
|
|
65
73
|
}
|
|
66
74
|
|
|
67
|
-
export interface TelegramAssistantMessagePreviewStartDeps<
|
|
68
|
-
TMessage,
|
|
69
|
-
TReplyMarkup = TelegramPreviewReplyMarkup,
|
|
70
|
-
> {
|
|
75
|
+
export interface TelegramAssistantMessagePreviewStartDeps<TMessage> {
|
|
71
76
|
getActiveTurn: () => TelegramPreviewActiveTurn | undefined;
|
|
72
77
|
isAssistantMessage: (message: TMessage) => boolean;
|
|
73
78
|
getState: () => TelegramPreviewRuntimeState | undefined;
|
|
74
79
|
setState: (state: TelegramPreviewRuntimeState | undefined) => void;
|
|
75
80
|
createPreviewState: () => TelegramPreviewRuntimeState;
|
|
76
81
|
canSend?: () => boolean;
|
|
77
|
-
finalizePreview: (chatId: number) => Promise<boolean>;
|
|
78
|
-
finalizeMarkdownPreview: (
|
|
79
|
-
chatId: number,
|
|
80
|
-
markdown: string,
|
|
81
|
-
replyToMessageId?: number,
|
|
82
|
-
options?: { replyMarkup?: TReplyMarkup; target?: TelegramTarget },
|
|
83
|
-
) => Promise<boolean>;
|
|
84
82
|
}
|
|
85
83
|
|
|
86
84
|
export interface TelegramAssistantMessagePreviewUpdateDeps<TMessage> {
|
|
@@ -97,10 +95,7 @@ export interface TelegramAssistantMessagePreviewUpdateDeps<TMessage> {
|
|
|
97
95
|
) => void;
|
|
98
96
|
}
|
|
99
97
|
|
|
100
|
-
export type TelegramAssistantMessagePreviewHookDeps<
|
|
101
|
-
TMessage,
|
|
102
|
-
TReplyMarkup = TelegramPreviewReplyMarkup,
|
|
103
|
-
> = TelegramAssistantMessagePreviewStartDeps<TMessage, TReplyMarkup> &
|
|
98
|
+
export type TelegramAssistantMessagePreviewHookDeps<TMessage> = TelegramAssistantMessagePreviewStartDeps<TMessage> &
|
|
104
99
|
TelegramAssistantMessagePreviewUpdateDeps<TMessage>;
|
|
105
100
|
|
|
106
101
|
export interface TelegramAssistantMessagePreviewHookEvent<TMessage> {
|
|
@@ -140,6 +135,8 @@ export interface TelegramPreviewControllerDeps {
|
|
|
140
135
|
}
|
|
141
136
|
|
|
142
137
|
export interface TelegramPreviewController {
|
|
138
|
+
seal: () => void;
|
|
139
|
+
preparePublication: () => TelegramAssistantOutputPreparation | undefined;
|
|
143
140
|
prepareClear: (
|
|
144
141
|
chatId: number,
|
|
145
142
|
options?: { target?: TelegramTarget; isDeliveryActive?: () => boolean },
|
|
@@ -201,11 +198,18 @@ export interface TelegramAssistantPreviewRuntimeDeps<
|
|
|
201
198
|
) => Promise<number | undefined>;
|
|
202
199
|
}
|
|
203
200
|
|
|
201
|
+
export interface TelegramPreparedPreviewDelivery<TReplyMarkup = unknown> {
|
|
202
|
+
clearPreview: TelegramPreviewController["clear"];
|
|
203
|
+
setPreviewPendingText: TelegramPreviewController["setPendingText"];
|
|
204
|
+
finalizeMarkdownPreview: ReturnType<typeof createTelegramNativeMarkdownPreviewFinalizer<TReplyMarkup>>;
|
|
205
|
+
}
|
|
206
|
+
|
|
204
207
|
export type TelegramAssistantPreviewRuntime<
|
|
205
208
|
TMessage,
|
|
206
209
|
TReplyMarkup = TelegramPreviewReplyMarkup,
|
|
207
210
|
> = TelegramPreviewController &
|
|
208
211
|
TelegramAssistantMessagePreviewHooks<TMessage> & {
|
|
212
|
+
prepareDelivery: (isDeliveryActive: () => boolean) => TelegramPreparedPreviewDelivery<TReplyMarkup>;
|
|
209
213
|
finalizeMarkdown: (
|
|
210
214
|
chatId: number,
|
|
211
215
|
markdown: string,
|
|
@@ -214,6 +218,14 @@ export type TelegramAssistantPreviewRuntime<
|
|
|
214
218
|
) => Promise<boolean>;
|
|
215
219
|
};
|
|
216
220
|
|
|
221
|
+
function sealTelegramPreviewState(state: TelegramPreviewRuntimeState | undefined): void {
|
|
222
|
+
if (!state) return;
|
|
223
|
+
state.sealed = true;
|
|
224
|
+
state.flushRequested = false;
|
|
225
|
+
if (state.flushTimer) clearTimeout(state.flushTimer);
|
|
226
|
+
state.flushTimer = undefined;
|
|
227
|
+
}
|
|
228
|
+
|
|
217
229
|
export function createTelegramNativeMarkdownPreviewFinalizer<
|
|
218
230
|
TReplyMarkup,
|
|
219
231
|
>(deps: {
|
|
@@ -223,6 +235,7 @@ export function createTelegramNativeMarkdownPreviewFinalizer<
|
|
|
223
235
|
options?: { awaitFlush?: boolean; target?: TelegramTarget },
|
|
224
236
|
) => Promise<void>;
|
|
225
237
|
discard?: () => void;
|
|
238
|
+
isDeliveryActive?: () => boolean;
|
|
226
239
|
sendMarkdownReply: (
|
|
227
240
|
chatId: number,
|
|
228
241
|
replyToMessageId: number | undefined,
|
|
@@ -235,14 +248,21 @@ export function createTelegramNativeMarkdownPreviewFinalizer<
|
|
|
235
248
|
replyToMessageId?: number,
|
|
236
249
|
options?: { replyMarkup?: TReplyMarkup; target?: TelegramTarget },
|
|
237
250
|
) => Promise<boolean> {
|
|
251
|
+
return (...args) => prepareTelegramNativeMarkdownPreviewFinalizer(deps)(...args);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function prepareTelegramNativeMarkdownPreviewFinalizer<TReplyMarkup>(
|
|
255
|
+
deps: Parameters<typeof createTelegramNativeMarkdownPreviewFinalizer<TReplyMarkup>>[0],
|
|
256
|
+
): ReturnType<typeof createTelegramNativeMarkdownPreviewFinalizer<TReplyMarkup>> {
|
|
257
|
+
const state = deps.getState();
|
|
258
|
+
sealTelegramPreviewState(state);
|
|
259
|
+
const inFlight = state?.flushPromise ?? state?.precedingFlush;
|
|
238
260
|
return async (chatId, markdown, replyToMessageId, options) => {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if (deps.getState() !== state) return false;
|
|
243
|
-
}
|
|
261
|
+
if (deps.isDeliveryActive?.() === false) return false;
|
|
262
|
+
await inFlight?.catch(() => {});
|
|
263
|
+
if (deps.getState() !== state || deps.isDeliveryActive?.() === false) return false;
|
|
244
264
|
await deps.sendMarkdownReply(chatId, replyToMessageId, markdown, options);
|
|
245
|
-
if (deps.getState() === state) deps.discard?.();
|
|
265
|
+
if (deps.getState() === state && deps.isDeliveryActive?.() !== false) deps.discard?.();
|
|
246
266
|
return true;
|
|
247
267
|
};
|
|
248
268
|
}
|
|
@@ -254,15 +274,28 @@ export function createTelegramAssistantPreviewRuntime<
|
|
|
254
274
|
deps: TelegramAssistantPreviewRuntimeDeps<TMessage, TReplyMarkup>,
|
|
255
275
|
): TelegramAssistantPreviewRuntime<TMessage, TReplyMarkup> {
|
|
256
276
|
const controller = createTelegramPreviewControllerRuntime(deps);
|
|
257
|
-
const
|
|
277
|
+
const finalizerDeps = {
|
|
258
278
|
getState: controller.getState,
|
|
259
279
|
clear: controller.clear,
|
|
260
280
|
discard: () => controller.setState(undefined),
|
|
261
281
|
sendMarkdownReply: deps.sendMarkdownReply,
|
|
262
|
-
}
|
|
282
|
+
};
|
|
263
283
|
return {
|
|
264
284
|
...controller,
|
|
265
|
-
finalizeMarkdown:
|
|
285
|
+
finalizeMarkdown: createTelegramNativeMarkdownPreviewFinalizer(finalizerDeps),
|
|
286
|
+
prepareDelivery(isDeliveryActive) {
|
|
287
|
+
const state = controller.getState();
|
|
288
|
+
return {
|
|
289
|
+
setPreviewPendingText(text) {
|
|
290
|
+
if (controller.getState() === state && isDeliveryActive()) controller.setPendingText(text);
|
|
291
|
+
},
|
|
292
|
+
clearPreview: async (chatId, options) => {
|
|
293
|
+
if (controller.getState() !== state || !isDeliveryActive()) return;
|
|
294
|
+
await controller.prepareClear(chatId, { ...options, isDeliveryActive })();
|
|
295
|
+
},
|
|
296
|
+
finalizeMarkdownPreview: prepareTelegramNativeMarkdownPreviewFinalizer({ ...finalizerDeps, isDeliveryActive }),
|
|
297
|
+
};
|
|
298
|
+
},
|
|
266
299
|
...createTelegramAssistantMessagePreviewHooks({
|
|
267
300
|
getActiveTurn: deps.getActiveTurn,
|
|
268
301
|
isAssistantMessage: deps.isAssistantMessage,
|
|
@@ -270,8 +303,6 @@ export function createTelegramAssistantPreviewRuntime<
|
|
|
270
303
|
setState: controller.setState,
|
|
271
304
|
createPreviewState: controller.createState,
|
|
272
305
|
canSend: deps.canSend,
|
|
273
|
-
finalizePreview: controller.finalize,
|
|
274
|
-
finalizeMarkdownPreview,
|
|
275
306
|
getMessageText: deps.getMessageText,
|
|
276
307
|
schedulePreviewFlush: controller.scheduleFlush,
|
|
277
308
|
}),
|
|
@@ -288,14 +319,17 @@ export function createTelegramPreviewController(
|
|
|
288
319
|
deps.maxMessageLength ?? TELEGRAM_DRAFT_PREVIEW_MAX_CHARS;
|
|
289
320
|
let draftSupport = deps.initialDraftSupport ?? "unknown";
|
|
290
321
|
let nextDraftId = 0;
|
|
322
|
+
const setState = (nextState: TelegramPreviewRuntimeState | undefined): void => {
|
|
323
|
+
if (state !== nextState) sealTelegramPreviewState(state);
|
|
324
|
+
state = nextState;
|
|
325
|
+
};
|
|
291
326
|
const getRuntimeDeps = (
|
|
292
327
|
operationGeneration = generation,
|
|
293
328
|
): TelegramPreviewRuntimeDeps => ({
|
|
294
329
|
getState: () => state,
|
|
295
|
-
setState
|
|
296
|
-
state = nextState;
|
|
297
|
-
},
|
|
330
|
+
setState,
|
|
298
331
|
maxMessageLength,
|
|
332
|
+
minDraftIntervalMs: TELEGRAM_DRAFT_INTERVAL_MS,
|
|
299
333
|
getDraftSupport: () => draftSupport,
|
|
300
334
|
setDraftSupport: (support) => {
|
|
301
335
|
draftSupport = support;
|
|
@@ -311,20 +345,27 @@ export function createTelegramPreviewController(
|
|
|
311
345
|
});
|
|
312
346
|
return {
|
|
313
347
|
getState: () => state,
|
|
314
|
-
setState
|
|
315
|
-
state = nextState;
|
|
316
|
-
},
|
|
348
|
+
setState,
|
|
317
349
|
setPendingText: (text) => {
|
|
318
350
|
if (state) state.pendingText = text;
|
|
319
351
|
},
|
|
320
352
|
createState: () => createTelegramPreviewRuntimeState(),
|
|
321
353
|
resetState: () => {
|
|
322
354
|
generation += 1;
|
|
323
|
-
|
|
355
|
+
setState({ ...createTelegramPreviewRuntimeState(), nextDraftAt: state?.nextDraftAt });
|
|
324
356
|
},
|
|
325
357
|
invalidate: () => {
|
|
326
358
|
generation += 1;
|
|
327
|
-
|
|
359
|
+
setState(undefined);
|
|
360
|
+
},
|
|
361
|
+
seal: () => sealTelegramPreviewState(state),
|
|
362
|
+
preparePublication: () => {
|
|
363
|
+
if (!state) return undefined;
|
|
364
|
+
sealTelegramPreviewState(state);
|
|
365
|
+
const prior = state.publicationPromise ?? state.flushPromise ?? state.precedingFlush;
|
|
366
|
+
let settle!: () => void;
|
|
367
|
+
state.publicationPromise = new Promise<void>((resolve) => { settle = resolve; });
|
|
368
|
+
return { wait: async () => { await prior?.catch(() => {}); }, settle };
|
|
328
369
|
},
|
|
329
370
|
prepareClear: (chatId, options) => {
|
|
330
371
|
const admittedState = state;
|
|
@@ -350,11 +391,8 @@ export function createTelegramPreviewController(
|
|
|
350
391
|
};
|
|
351
392
|
}
|
|
352
393
|
|
|
353
|
-
export function createTelegramAssistantMessagePreviewHooks<
|
|
354
|
-
TMessage
|
|
355
|
-
TReplyMarkup = TelegramPreviewReplyMarkup,
|
|
356
|
-
>(
|
|
357
|
-
deps: TelegramAssistantMessagePreviewHookDeps<TMessage, TReplyMarkup>,
|
|
394
|
+
export function createTelegramAssistantMessagePreviewHooks<TMessage>(
|
|
395
|
+
deps: TelegramAssistantMessagePreviewHookDeps<TMessage>,
|
|
358
396
|
): TelegramAssistantMessagePreviewHooks<TMessage> {
|
|
359
397
|
return {
|
|
360
398
|
onMessageStart: async (
|
|
@@ -370,12 +408,9 @@ export function createTelegramAssistantMessagePreviewHooks<
|
|
|
370
408
|
};
|
|
371
409
|
}
|
|
372
410
|
|
|
373
|
-
export async function handleTelegramAssistantMessagePreviewStart<
|
|
374
|
-
TMessage,
|
|
375
|
-
TReplyMarkup = TelegramPreviewReplyMarkup,
|
|
376
|
-
>(
|
|
411
|
+
export async function handleTelegramAssistantMessagePreviewStart<TMessage>(
|
|
377
412
|
message: TMessage,
|
|
378
|
-
deps: TelegramAssistantMessagePreviewStartDeps<TMessage
|
|
413
|
+
deps: TelegramAssistantMessagePreviewStartDeps<TMessage>,
|
|
379
414
|
): Promise<void> {
|
|
380
415
|
const turn = deps.getActiveTurn();
|
|
381
416
|
if (!turn || !deps.isAssistantMessage(message)) return;
|
|
@@ -388,26 +423,13 @@ export async function handleTelegramAssistantMessagePreviewStart<
|
|
|
388
423
|
return;
|
|
389
424
|
}
|
|
390
425
|
const state = deps.getState();
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
await deps.finalizeMarkdownPreview(
|
|
399
|
-
turn.chatId,
|
|
400
|
-
previousText,
|
|
401
|
-
turn.replyToMessageId,
|
|
402
|
-
{
|
|
403
|
-
target: turn.target,
|
|
404
|
-
},
|
|
405
|
-
);
|
|
406
|
-
} else {
|
|
407
|
-
await deps.finalizePreview(turn.chatId);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
deps.setState(deps.createPreviewState());
|
|
426
|
+
sealTelegramPreviewState(state);
|
|
427
|
+
const next = deps.createPreviewState();
|
|
428
|
+
// Carry the previous delivery boundary; permanent text remains with its publication owner.
|
|
429
|
+
next.draftId = state?.draftId;
|
|
430
|
+
next.nextDraftAt = state?.nextDraftAt;
|
|
431
|
+
next.precedingFlush = state?.publicationPromise ?? state?.flushPromise ?? state?.precedingFlush;
|
|
432
|
+
deps.setState(next);
|
|
411
433
|
}
|
|
412
434
|
|
|
413
435
|
export async function handleTelegramAssistantMessagePreviewUpdate<TMessage>(
|
|
@@ -426,6 +448,7 @@ export async function handleTelegramAssistantMessagePreviewUpdate<TMessage>(
|
|
|
426
448
|
state = deps.createPreviewState();
|
|
427
449
|
deps.setState(state);
|
|
428
450
|
}
|
|
451
|
+
if (state.sealed) return;
|
|
429
452
|
state.pendingText = stripTelegramCommentMarkupForPreview(
|
|
430
453
|
deps.getMessageText(message),
|
|
431
454
|
);
|
|
@@ -473,9 +496,11 @@ export async function clearTelegramPreview(
|
|
|
473
496
|
): Promise<void> {
|
|
474
497
|
const state = deps.getState();
|
|
475
498
|
if (!state || options.isDeliveryActive?.() === false) return;
|
|
476
|
-
|
|
499
|
+
sealTelegramPreviewState(state);
|
|
500
|
+
const inFlight = state.flushPromise ?? state.precedingFlush;
|
|
501
|
+
if (inFlight && options.awaitFlush !== false) {
|
|
477
502
|
state.flushRequested = false;
|
|
478
|
-
await
|
|
503
|
+
await inFlight.catch(() => {});
|
|
479
504
|
if (deps.getState() !== state) return;
|
|
480
505
|
}
|
|
481
506
|
if (options.isDeliveryActive?.() === false) return;
|
|
@@ -782,13 +807,15 @@ async function performTelegramPreviewFlush(
|
|
|
782
807
|
) {
|
|
783
808
|
const draftId = state.draftId ?? deps.allocateDraftId();
|
|
784
809
|
state.draftId = draftId;
|
|
810
|
+
state.nextDraftAt = Date.now() + (deps.minDraftIntervalMs ?? 0);
|
|
785
811
|
try {
|
|
786
|
-
await deps.sendDraft(
|
|
812
|
+
const delivered = await deps.sendDraft(
|
|
787
813
|
chatId,
|
|
788
814
|
draftId,
|
|
789
815
|
normalizeTelegramNativeMarkdown(snapshot.text),
|
|
790
816
|
{ ...getTelegramTargetThreadParams(options.target ?? { chatId }) },
|
|
791
817
|
);
|
|
818
|
+
if (delivered === false || deps.getState() !== state || deps.canSend?.() === false) return;
|
|
792
819
|
deps.setDraftSupport("supported");
|
|
793
820
|
state.mode = "draft";
|
|
794
821
|
state.lastSentText = snapshot.text;
|
|
@@ -810,15 +837,33 @@ export async function flushTelegramPreview(
|
|
|
810
837
|
options: { target?: TelegramTarget } = {},
|
|
811
838
|
): Promise<void> {
|
|
812
839
|
const state = deps.getState();
|
|
813
|
-
if (!state) return;
|
|
840
|
+
if (!state || state.sealed) return;
|
|
814
841
|
if (state.flushPromise) {
|
|
815
842
|
state.flushRequested = true;
|
|
816
843
|
await state.flushPromise;
|
|
817
844
|
return;
|
|
818
845
|
}
|
|
819
846
|
state.flushPromise = (async () => {
|
|
847
|
+
if (state.precedingFlush) {
|
|
848
|
+
await state.precedingFlush.catch(() => {});
|
|
849
|
+
state.precedingFlush = undefined;
|
|
850
|
+
if (deps.getState() !== state || state.sealed) return;
|
|
851
|
+
}
|
|
820
852
|
do {
|
|
821
853
|
state.flushRequested = false;
|
|
854
|
+
const delay = (state.nextDraftAt ?? 0) - Date.now();
|
|
855
|
+
if (delay > 0) {
|
|
856
|
+
if (!state.flushTimer) {
|
|
857
|
+
state.flushTimer = setTimeout(() => {
|
|
858
|
+
state.flushTimer = undefined;
|
|
859
|
+
if (deps.getState() === state && !state.sealed) void flushTelegramPreview(chatId, deps, options);
|
|
860
|
+
}, delay);
|
|
861
|
+
state.flushTimer.unref?.();
|
|
862
|
+
}
|
|
863
|
+
break;
|
|
864
|
+
}
|
|
865
|
+
if (state.flushTimer) clearTimeout(state.flushTimer);
|
|
866
|
+
state.flushTimer = undefined;
|
|
822
867
|
try {
|
|
823
868
|
await performTelegramPreviewFlush(chatId, state, deps, options);
|
|
824
869
|
} catch (error) {
|
|
@@ -829,7 +874,7 @@ export async function flushTelegramPreview(
|
|
|
829
874
|
});
|
|
830
875
|
break;
|
|
831
876
|
}
|
|
832
|
-
} while (deps.getState() === state && state.flushRequested);
|
|
877
|
+
} while (deps.getState() === state && !state.sealed && state.flushRequested);
|
|
833
878
|
})();
|
|
834
879
|
try {
|
|
835
880
|
await state.flushPromise;
|