@llblab/pi-telegram 0.22.0 → 0.23.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/AGENTS.md +12 -7
- package/BACKLOG.md +1 -23
- package/CHANGELOG.md +53 -1
- package/README.md +3 -1
- package/docs/README.md +1 -1
- package/docs/activity.md +8 -0
- package/docs/architecture.md +17 -15
- package/docs/locks.md +22 -12
- package/docs/multi-instance-bus.md +17 -16
- package/docs/outbound.md +16 -0
- package/docs/public-api.md +9 -4
- package/index.ts +74 -59
- package/lib/activity.ts +100 -3
- package/lib/bindings.ts +81 -6
- package/lib/bus-follower.ts +205 -17
- package/lib/bus-leader.ts +339 -133
- package/lib/bus.ts +82 -19
- package/lib/commands.ts +28 -4
- package/lib/config.ts +40 -28
- package/lib/locks.ts +318 -34
- package/lib/logs.ts +7 -3
- package/lib/media.ts +102 -1
- package/lib/menu-settings.ts +4 -1
- package/lib/outbound-attachments.ts +150 -1
- package/lib/outbound.ts +106 -1
- package/lib/polling.ts +5 -0
- package/lib/queue.ts +41 -48
- package/lib/routing.ts +88 -1
- package/lib/sync.ts +40 -3
- package/lib/telegram-api.ts +77 -13
- package/lib/text-groups.ts +183 -16
- package/lib/thread-reconciler.ts +66 -22
- package/lib/threads.ts +131 -23
- package/lib/turns.ts +102 -13
- package/lib/updates.ts +2 -0
- package/package.json +1 -1
package/lib/threads.ts
CHANGED
|
@@ -44,7 +44,13 @@ export interface TelegramThreadNameInput {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export type TelegramTopicTargetStatus =
|
|
47
|
-
|
|
47
|
+
| "active"
|
|
48
|
+
| "offline"
|
|
49
|
+
| "stale"
|
|
50
|
+
| "pending"
|
|
51
|
+
| "starting"
|
|
52
|
+
| "probe-required"
|
|
53
|
+
| "failed";
|
|
48
54
|
|
|
49
55
|
export type TelegramTopicSyncStatus = "open" | "closed" | "deleted" | "unknown";
|
|
50
56
|
|
|
@@ -438,6 +444,60 @@ export function getTelegramTopicTargetsPath(
|
|
|
438
444
|
return getTelegramStatePath(agentDir, profileName);
|
|
439
445
|
}
|
|
440
446
|
|
|
447
|
+
const TELEGRAM_LEADER_SESSION_HANDOFF_KEY =
|
|
448
|
+
"__piTelegramLeaderSessionHandoff";
|
|
449
|
+
export const TELEGRAM_LEADER_SESSION_HANDOFF_TTL_MS = 30_000;
|
|
450
|
+
|
|
451
|
+
export interface TelegramLeaderSessionHandoff {
|
|
452
|
+
pid: number;
|
|
453
|
+
instanceId: string;
|
|
454
|
+
createdAtMs: number;
|
|
455
|
+
profileKey: string;
|
|
456
|
+
target: TelegramTarget & { threadId: number };
|
|
457
|
+
slot?: string;
|
|
458
|
+
threadName?: string;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export function getTelegramLeaderSessionHandoff():
|
|
462
|
+
| TelegramLeaderSessionHandoff
|
|
463
|
+
| undefined {
|
|
464
|
+
const value = (globalThis as Record<string, unknown>)[
|
|
465
|
+
TELEGRAM_LEADER_SESSION_HANDOFF_KEY
|
|
466
|
+
];
|
|
467
|
+
if (!value || typeof value !== "object") return undefined;
|
|
468
|
+
const handoff = value as Partial<TelegramLeaderSessionHandoff>;
|
|
469
|
+
if (
|
|
470
|
+
typeof handoff.pid !== "number" ||
|
|
471
|
+
typeof handoff.instanceId !== "string" ||
|
|
472
|
+
typeof handoff.createdAtMs !== "number" ||
|
|
473
|
+
typeof handoff.profileKey !== "string" ||
|
|
474
|
+
typeof handoff.target?.chatId !== "number" ||
|
|
475
|
+
typeof handoff.target.threadId !== "number"
|
|
476
|
+
) {
|
|
477
|
+
return undefined;
|
|
478
|
+
}
|
|
479
|
+
return handoff as TelegramLeaderSessionHandoff;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
export function setTelegramLeaderSessionHandoff(
|
|
483
|
+
handoff: TelegramLeaderSessionHandoff | undefined,
|
|
484
|
+
): void {
|
|
485
|
+
const store = globalThis as Record<string, unknown>;
|
|
486
|
+
if (!handoff) delete store[TELEGRAM_LEADER_SESSION_HANDOFF_KEY];
|
|
487
|
+
else store[TELEGRAM_LEADER_SESSION_HANDOFF_KEY] = handoff;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
export function isTelegramLeaderSessionHandoffFresh(
|
|
491
|
+
handoff: TelegramLeaderSessionHandoff | undefined,
|
|
492
|
+
options: { pid?: number; nowMs?: number; ttlMs?: number } = {},
|
|
493
|
+
): handoff is TelegramLeaderSessionHandoff {
|
|
494
|
+
if (!handoff) return false;
|
|
495
|
+
const pid = options.pid ?? process.pid;
|
|
496
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
497
|
+
const ttlMs = options.ttlMs ?? TELEGRAM_LEADER_SESSION_HANDOFF_TTL_MS;
|
|
498
|
+
return handoff.pid === pid && nowMs - handoff.createdAtMs <= ttlMs;
|
|
499
|
+
}
|
|
500
|
+
|
|
441
501
|
export function getTelegramThreadOwnerKey(owner: TelegramThreadOwner): string {
|
|
442
502
|
switch (owner.kind) {
|
|
443
503
|
case "leader": {
|
|
@@ -593,6 +653,7 @@ function normalizeRecord(
|
|
|
593
653
|
status !== "stale" &&
|
|
594
654
|
status !== "pending" &&
|
|
595
655
|
status !== "starting" &&
|
|
656
|
+
status !== "probe-required" &&
|
|
596
657
|
status !== "failed"
|
|
597
658
|
)
|
|
598
659
|
return undefined;
|
|
@@ -655,6 +716,10 @@ function isCurrentThreadRecord(record: TelegramTopicTargetRecord): boolean {
|
|
|
655
716
|
);
|
|
656
717
|
}
|
|
657
718
|
|
|
719
|
+
function isPersistedThreadRecord(record: TelegramTopicTargetRecord): boolean {
|
|
720
|
+
return isCurrentThreadRecord(record) || record.status === "probe-required";
|
|
721
|
+
}
|
|
722
|
+
|
|
658
723
|
function normalizeIdentityRecord(
|
|
659
724
|
value: unknown,
|
|
660
725
|
): TelegramThreadIdentityRecord | undefined {
|
|
@@ -882,7 +947,7 @@ function parseTopicTargetFile(value: unknown): TelegramTopicTargetFile {
|
|
|
882
947
|
.map((record) => normalizeRecord(record))
|
|
883
948
|
.filter(
|
|
884
949
|
(record): record is TelegramTopicTargetRecord =>
|
|
885
|
-
!!record &&
|
|
950
|
+
!!record && isPersistedThreadRecord(record),
|
|
886
951
|
);
|
|
887
952
|
return {
|
|
888
953
|
version: 1,
|
|
@@ -1172,7 +1237,7 @@ export function createTelegramTopicTargetStore(
|
|
|
1172
1237
|
isPendingProvisionLiveOrTargeted(provision, nowMs),
|
|
1173
1238
|
);
|
|
1174
1239
|
const currentRecords = Array.from(records.values())
|
|
1175
|
-
.filter(
|
|
1240
|
+
.filter(isPersistedThreadRecord)
|
|
1176
1241
|
.map(cloneRecord);
|
|
1177
1242
|
records = new Map(
|
|
1178
1243
|
currentRecords.map((record) => [
|
|
@@ -1411,7 +1476,7 @@ export function createTelegramTopicTargetStore(
|
|
|
1411
1476
|
records.delete(getRecordOwnerKey(existing));
|
|
1412
1477
|
}
|
|
1413
1478
|
}
|
|
1414
|
-
if (!
|
|
1479
|
+
if (!isPersistedThreadRecord(next)) {
|
|
1415
1480
|
rememberIdentity(next);
|
|
1416
1481
|
records.delete(nextOwnerKey);
|
|
1417
1482
|
markDirty();
|
|
@@ -1996,6 +2061,51 @@ export async function provisionOwnBusTopic(
|
|
|
1996
2061
|
instanceId: deps.instanceId,
|
|
1997
2062
|
...(deps.telegramProfile ? { telegramProfile: deps.telegramProfile } : {}),
|
|
1998
2063
|
};
|
|
2064
|
+
const leaderSessionHandoff = getTelegramLeaderSessionHandoff();
|
|
2065
|
+
if (
|
|
2066
|
+
isTelegramLeaderSessionHandoffFresh(leaderSessionHandoff) &&
|
|
2067
|
+
leaderSessionHandoff.profileKey === profileKey
|
|
2068
|
+
) {
|
|
2069
|
+
const existingHandoffRecord = deps.store
|
|
2070
|
+
.list()
|
|
2071
|
+
.find((record) => targetMatches(record.target, leaderSessionHandoff.target));
|
|
2072
|
+
deps.store.upsert({
|
|
2073
|
+
profileKey,
|
|
2074
|
+
owner: currentLeaderOwner,
|
|
2075
|
+
target: { ...leaderSessionHandoff.target },
|
|
2076
|
+
status: "active",
|
|
2077
|
+
createdAtMs: existingHandoffRecord?.createdAtMs ?? leaderSessionHandoff.createdAtMs,
|
|
2078
|
+
updatedAtMs: nowMs,
|
|
2079
|
+
threadName:
|
|
2080
|
+
existingHandoffRecord?.threadName ?? leaderSessionHandoff.threadName,
|
|
2081
|
+
instanceId: deps.instanceId,
|
|
2082
|
+
slot: existingHandoffRecord?.slot ?? leaderSessionHandoff.slot,
|
|
2083
|
+
...(existingHandoffRecord?.syncStatus
|
|
2084
|
+
? { syncStatus: existingHandoffRecord.syncStatus }
|
|
2085
|
+
: {}),
|
|
2086
|
+
...(existingHandoffRecord?.lastSyncObservedAtMs !== undefined
|
|
2087
|
+
? {
|
|
2088
|
+
lastSyncObservedAtMs:
|
|
2089
|
+
existingHandoffRecord.lastSyncObservedAtMs,
|
|
2090
|
+
}
|
|
2091
|
+
: {}),
|
|
2092
|
+
lastReconcileAction: "leader-session-handoff-restored",
|
|
2093
|
+
});
|
|
2094
|
+
await deps.store.persist();
|
|
2095
|
+
setTelegramLeaderSessionHandoff(undefined);
|
|
2096
|
+
deps.recordEvent("bus", "Bus leader session handoff restored", {
|
|
2097
|
+
phase: "leader-session-handoff-restore",
|
|
2098
|
+
chatId: leaderSessionHandoff.target.chatId,
|
|
2099
|
+
threadId: leaderSessionHandoff.target.threadId,
|
|
2100
|
+
slot: existingHandoffRecord?.slot ?? leaderSessionHandoff.slot,
|
|
2101
|
+
threadName:
|
|
2102
|
+
existingHandoffRecord?.threadName ?? leaderSessionHandoff.threadName,
|
|
2103
|
+
previousInstanceId: leaderSessionHandoff.instanceId,
|
|
2104
|
+
instanceId: deps.instanceId,
|
|
2105
|
+
});
|
|
2106
|
+
} else if (leaderSessionHandoff) {
|
|
2107
|
+
setTelegramLeaderSessionHandoff(undefined);
|
|
2108
|
+
}
|
|
1999
2109
|
const recordsBeforePreviousLeaderCleanup = deps.store.list();
|
|
2000
2110
|
const previousLeaderCleanupPlan = ThreadReconciler.planThreadReconciliation({
|
|
2001
2111
|
nowMs,
|
|
@@ -2051,7 +2161,7 @@ export async function provisionOwnBusTopic(
|
|
|
2051
2161
|
continue;
|
|
2052
2162
|
}
|
|
2053
2163
|
const previousLeaderCleanupStartedAtMs = Date.now();
|
|
2054
|
-
await ThreadReconciler.applyThreadReconciliationPlan(
|
|
2164
|
+
const cleanup = await ThreadReconciler.applyThreadReconciliationPlan(
|
|
2055
2165
|
{ actions: [action] },
|
|
2056
2166
|
{
|
|
2057
2167
|
callApi: deps.callApi,
|
|
@@ -2097,6 +2207,11 @@ export async function provisionOwnBusTopic(
|
|
|
2097
2207
|
"Telegram leader ownership changed during topic reconciliation.",
|
|
2098
2208
|
);
|
|
2099
2209
|
}
|
|
2210
|
+
if (cleanup.incompleteActions?.length) {
|
|
2211
|
+
throw new Error(
|
|
2212
|
+
"Previous Telegram leader topic deletion was not confirmed.",
|
|
2213
|
+
);
|
|
2214
|
+
}
|
|
2100
2215
|
deps.store.markStaleByTarget(record.target);
|
|
2101
2216
|
deps.store.reserveThread({
|
|
2102
2217
|
target: record.target,
|
|
@@ -2256,6 +2371,7 @@ export interface TelegramCurrentInstanceThreadRuntime {
|
|
|
2256
2371
|
findRecord(): TelegramTopicTargetRecord | undefined;
|
|
2257
2372
|
getRecord(): TelegramTopicTargetRecord | undefined;
|
|
2258
2373
|
getIdentity(target?: TelegramTarget): TelegramInstanceThreadIdentityCandidate;
|
|
2374
|
+
getRestorationIdentity(): TelegramInstanceThreadIdentityCandidate;
|
|
2259
2375
|
}
|
|
2260
2376
|
|
|
2261
2377
|
export function createTelegramCurrentInstanceThreadRuntime(deps: {
|
|
@@ -2301,6 +2417,14 @@ export function createTelegramCurrentInstanceThreadRuntime(deps: {
|
|
|
2301
2417
|
record,
|
|
2302
2418
|
});
|
|
2303
2419
|
},
|
|
2420
|
+
getRestorationIdentity() {
|
|
2421
|
+
const follower = deps.getFollower();
|
|
2422
|
+
return resolveTelegramInstanceThreadIdentity({
|
|
2423
|
+
follower: follower?.registered ? follower : undefined,
|
|
2424
|
+
leader: deps.getLeader(),
|
|
2425
|
+
record: findRecord(),
|
|
2426
|
+
});
|
|
2427
|
+
},
|
|
2304
2428
|
};
|
|
2305
2429
|
}
|
|
2306
2430
|
|
|
@@ -2657,25 +2781,9 @@ export function createTelegramTopicTargetProvisioner(
|
|
|
2657
2781
|
};
|
|
2658
2782
|
assertLeaderEpoch("start");
|
|
2659
2783
|
normalizeCurrentThreadNameSlots(deps.store);
|
|
2660
|
-
|
|
2784
|
+
const existing = deps.store.getByProfileKey(request.profileKey);
|
|
2661
2785
|
const isManualFollowerRequest = request.owner?.kind === "manual-follower";
|
|
2662
|
-
|
|
2663
|
-
isManualFollowerRequest &&
|
|
2664
|
-
existing &&
|
|
2665
|
-
isCurrentThreadRecord(existing)
|
|
2666
|
-
) {
|
|
2667
|
-
deps.store.markStaleByTarget(
|
|
2668
|
-
existing.target,
|
|
2669
|
-
"unknown",
|
|
2670
|
-
"Manual follower runtime was replaced before reconnect.",
|
|
2671
|
-
);
|
|
2672
|
-
deps.store.forgetIdentityByProfileKey(request.profileKey);
|
|
2673
|
-
existing = undefined;
|
|
2674
|
-
}
|
|
2675
|
-
const identity =
|
|
2676
|
-
isManualFollowerRequest && !existing
|
|
2677
|
-
? undefined
|
|
2678
|
-
: deps.store.getIdentityByProfileKey(request.profileKey);
|
|
2786
|
+
const identity = deps.store.getIdentityByProfileKey(request.profileKey);
|
|
2679
2787
|
const nowMs = getNowMs();
|
|
2680
2788
|
if (existing && isCurrentThreadRecord(existing)) {
|
|
2681
2789
|
const slot = existing.slot ?? deps.store.allocateSlot(request.profileKey);
|
package/lib/turns.ts
CHANGED
|
@@ -9,9 +9,11 @@ import { basename, dirname, join } from "node:path";
|
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
11
|
buildTelegramReplyContextBlock,
|
|
12
|
+
collectTelegramFileInfos,
|
|
12
13
|
collectTelegramMessageIds,
|
|
13
14
|
downloadTelegramMessageFiles,
|
|
14
15
|
extractTelegramForwardContextText,
|
|
16
|
+
extractTelegramMessageText,
|
|
15
17
|
extractTelegramMessagesPromptText,
|
|
16
18
|
extractTelegramMessagesText,
|
|
17
19
|
formatTelegramHistoryText,
|
|
@@ -132,11 +134,36 @@ function appendTelegramAttachmentSection(
|
|
|
132
134
|
return `${prefix}${header}\n${items.map((item) => `- ${item}`).join("\n")}`;
|
|
133
135
|
}
|
|
134
136
|
|
|
135
|
-
function appendTelegramSourceContext(
|
|
137
|
+
function appendTelegramSourceContext(
|
|
138
|
+
text: string,
|
|
139
|
+
sourceContext: string | undefined,
|
|
140
|
+
): string {
|
|
136
141
|
if (!sourceContext) return text;
|
|
137
142
|
return text ? `${text}\n\n${sourceContext}` : sourceContext;
|
|
138
143
|
}
|
|
139
144
|
|
|
145
|
+
|
|
146
|
+
function buildTelegramForwardContextBlock(options: {
|
|
147
|
+
context: string;
|
|
148
|
+
text: string;
|
|
149
|
+
files: DownloadedTelegramTurnFile[];
|
|
150
|
+
}): string {
|
|
151
|
+
const metadata = options.context.replace(/:\s+/g, ":");
|
|
152
|
+
const from = metadata.match(/^from:(.+)$/)?.[1];
|
|
153
|
+
let block = `[forward|${metadata}]${options.text ? ` ${options.text}` : ""}`;
|
|
154
|
+
if (options.files.length === 0) return block;
|
|
155
|
+
const dirs = [...new Set(options.files.map((file) => dirname(file.path)))];
|
|
156
|
+
const sameDir = dirs.length === 1;
|
|
157
|
+
const header = `[attachments${from ? `|from:${from}` : ""}]${
|
|
158
|
+
sameDir ? ` ${dirs[0]}` : ""
|
|
159
|
+
}`;
|
|
160
|
+
const items = sameDir
|
|
161
|
+
? options.files.map((file) => `/${basename(file.path)}`)
|
|
162
|
+
: options.files.map((file) => file.path);
|
|
163
|
+
block += `\n\n${header}\n${items.map((item) => `- ${item}`).join("\n")}`;
|
|
164
|
+
return block;
|
|
165
|
+
}
|
|
166
|
+
|
|
140
167
|
function appendTelegramPromptText(prompt: string, rawText: string): string {
|
|
141
168
|
if (!rawText) return prompt;
|
|
142
169
|
if (rawText.startsWith("\n")) return `${prompt}${rawText}`;
|
|
@@ -165,6 +192,7 @@ export function buildTelegramTurnPrompt(options: {
|
|
|
165
192
|
rawText: string;
|
|
166
193
|
files: DownloadedTelegramTurnFile[];
|
|
167
194
|
promptFiles?: DownloadedTelegramTurnFile[];
|
|
195
|
+
displayFiles?: DownloadedTelegramTurnFile[];
|
|
168
196
|
handlerOutputs?: string[];
|
|
169
197
|
sourceContext?: string;
|
|
170
198
|
historyTurns?: Pick<PendingTelegramTurn, "historyText">[];
|
|
@@ -186,14 +214,15 @@ export function buildTelegramTurnPrompt(options: {
|
|
|
186
214
|
? `${prompt}\n${options.rawText}`
|
|
187
215
|
: appendTelegramPromptText(prompt, options.rawText);
|
|
188
216
|
}
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
prompt =
|
|
217
|
+
const displayFiles =
|
|
218
|
+
options.displayFiles ?? options.promptFiles ?? options.files;
|
|
219
|
+
prompt = appendTelegramAttachmentSection(prompt, displayFiles);
|
|
192
220
|
prompt = appendTelegramListSection(
|
|
193
221
|
prompt,
|
|
194
222
|
"outputs",
|
|
195
223
|
options.handlerOutputs ?? [],
|
|
196
224
|
);
|
|
225
|
+
prompt = appendTelegramSourceContext(prompt, options.sourceContext);
|
|
197
226
|
if (options.voiceContext) {
|
|
198
227
|
prompt = appendTelegramVoiceContext(prompt, options.voiceContext);
|
|
199
228
|
}
|
|
@@ -375,6 +404,7 @@ export interface BuildTelegramPromptTurnOptions {
|
|
|
375
404
|
statusText?: string;
|
|
376
405
|
files: DownloadedTelegramTurnFile[];
|
|
377
406
|
promptFiles?: DownloadedTelegramTurnFile[];
|
|
407
|
+
displayFiles?: DownloadedTelegramTurnFile[];
|
|
378
408
|
handlerOutputs?: string[];
|
|
379
409
|
sourceContext?: string;
|
|
380
410
|
timeLine?: string | null;
|
|
@@ -433,9 +463,26 @@ export function createTelegramPromptTurnRuntimeBuilder<
|
|
|
433
463
|
const replyContext = firstMessage
|
|
434
464
|
? buildTelegramReplyContextBlock(firstMessage, replyFiles)
|
|
435
465
|
: "";
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
|
|
466
|
+
const forwardEntries = messages.flatMap((message) => {
|
|
467
|
+
const context = extractTelegramForwardContextText(
|
|
468
|
+
message,
|
|
469
|
+
deps.getAllowedUserId?.(),
|
|
470
|
+
);
|
|
471
|
+
return context
|
|
472
|
+
? [
|
|
473
|
+
{
|
|
474
|
+
context,
|
|
475
|
+
text: extractTelegramMessageText(message),
|
|
476
|
+
message,
|
|
477
|
+
fileNames: new Set(
|
|
478
|
+
collectTelegramFileInfos(message.rich_message ? [message] : []).map(
|
|
479
|
+
(file) => file.fileName,
|
|
480
|
+
),
|
|
481
|
+
),
|
|
482
|
+
},
|
|
483
|
+
]
|
|
484
|
+
: [];
|
|
485
|
+
});
|
|
439
486
|
const files = await downloadTelegramMessageFiles(messages, {
|
|
440
487
|
downloadFile: deps.downloadFile,
|
|
441
488
|
});
|
|
@@ -444,13 +491,51 @@ export function createTelegramPromptTurnRuntimeBuilder<
|
|
|
444
491
|
: { rawText, promptFiles: files };
|
|
445
492
|
const sourceBlocks: string[] = [];
|
|
446
493
|
let promptRawText = processed.rawText;
|
|
447
|
-
|
|
494
|
+
const forwardedFilePaths = new Set<string>();
|
|
495
|
+
const getForwardFiles = (entry: (typeof forwardEntries)[number]) =>
|
|
496
|
+
(processed.promptFiles ?? files).filter((file) => {
|
|
497
|
+
if (!entry.fileNames.has(file.fileName)) return false;
|
|
498
|
+
forwardedFilePaths.add(file.path);
|
|
499
|
+
return true;
|
|
500
|
+
});
|
|
501
|
+
if (forwardEntries.length === 1 && messages.length === 1) {
|
|
502
|
+
const [forward] = forwardEntries;
|
|
448
503
|
sourceBlocks.push(
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
504
|
+
buildTelegramForwardContextBlock({
|
|
505
|
+
context: forward!.context,
|
|
506
|
+
text: processed.rawText,
|
|
507
|
+
files: getForwardFiles(forward!),
|
|
508
|
+
}),
|
|
452
509
|
);
|
|
453
510
|
promptRawText = "";
|
|
511
|
+
} else if (forwardEntries.length > 0 && processed.rawText === rawText) {
|
|
512
|
+
const forwardedMessages = new Set(
|
|
513
|
+
forwardEntries.map((entry) => entry.message),
|
|
514
|
+
);
|
|
515
|
+
promptRawText = messages
|
|
516
|
+
.filter((message) => !forwardedMessages.has(message))
|
|
517
|
+
.map(extractTelegramMessageText)
|
|
518
|
+
.filter(Boolean)
|
|
519
|
+
.join("\n\n");
|
|
520
|
+
sourceBlocks.push(
|
|
521
|
+
...forwardEntries.map((entry) =>
|
|
522
|
+
buildTelegramForwardContextBlock({
|
|
523
|
+
context: entry.context,
|
|
524
|
+
text: entry.text,
|
|
525
|
+
files: getForwardFiles(entry),
|
|
526
|
+
}),
|
|
527
|
+
),
|
|
528
|
+
);
|
|
529
|
+
} else if (forwardEntries.length > 0) {
|
|
530
|
+
sourceBlocks.push(
|
|
531
|
+
...forwardEntries.map((entry) =>
|
|
532
|
+
buildTelegramForwardContextBlock({
|
|
533
|
+
context: entry.context,
|
|
534
|
+
text: "",
|
|
535
|
+
files: getForwardFiles(entry),
|
|
536
|
+
}),
|
|
537
|
+
),
|
|
538
|
+
);
|
|
454
539
|
}
|
|
455
540
|
if (replyContext) sourceBlocks.push(replyContext);
|
|
456
541
|
const sourceContext = sourceBlocks.join("\n\n");
|
|
@@ -475,6 +560,9 @@ export function createTelegramPromptTurnRuntimeBuilder<
|
|
|
475
560
|
statusText: processed.rawText,
|
|
476
561
|
files,
|
|
477
562
|
promptFiles: processed.promptFiles,
|
|
563
|
+
displayFiles: (processed.promptFiles ?? files).filter(
|
|
564
|
+
(file) => !forwardedFilePaths.has(file.path),
|
|
565
|
+
),
|
|
478
566
|
handlerOutputs: processed.handlerOutputs,
|
|
479
567
|
timeLine,
|
|
480
568
|
inferImageMimeType: guessMediaType,
|
|
@@ -522,6 +610,7 @@ export async function buildTelegramPromptTurn(
|
|
|
522
610
|
rawText: options.rawText,
|
|
523
611
|
files: options.files,
|
|
524
612
|
promptFiles: options.promptFiles,
|
|
613
|
+
displayFiles: options.displayFiles,
|
|
525
614
|
handlerOutputs: options.handlerOutputs,
|
|
526
615
|
sourceContext: options.sourceContext,
|
|
527
616
|
historyTurns: options.historyTurns,
|
|
@@ -566,14 +655,14 @@ export async function buildTelegramPromptTurn(
|
|
|
566
655
|
historyText: appendTelegramSourceContext(
|
|
567
656
|
formatTelegramHistoryText(
|
|
568
657
|
options.rawText,
|
|
569
|
-
options.promptFiles ?? options.files,
|
|
658
|
+
options.displayFiles ?? options.promptFiles ?? options.files,
|
|
570
659
|
options.handlerOutputs,
|
|
571
660
|
),
|
|
572
661
|
options.sourceContext,
|
|
573
662
|
),
|
|
574
663
|
statusSummary: formatTelegramTurnStatusSummary(
|
|
575
664
|
options.statusText ?? options.rawText,
|
|
576
|
-
options.promptFiles ?? options.files,
|
|
665
|
+
options.displayFiles ?? options.promptFiles ?? options.files,
|
|
577
666
|
options.handlerOutputs,
|
|
578
667
|
),
|
|
579
668
|
// Voice tagging (used for preview suppression and prompt guidance)
|
package/lib/updates.ts
CHANGED
|
@@ -249,6 +249,7 @@ export function getAuthorizedTelegramGuestMessage(
|
|
|
249
249
|
|
|
250
250
|
export interface TelegramMessageOwnershipView {
|
|
251
251
|
instanceId: string;
|
|
252
|
+
ownerGeneration?: string;
|
|
252
253
|
}
|
|
253
254
|
|
|
254
255
|
export type TelegramMessageOwnershipLookup = (
|
|
@@ -258,6 +259,7 @@ export type TelegramMessageOwnershipLookup = (
|
|
|
258
259
|
|
|
259
260
|
export interface TelegramTargetOwnershipView {
|
|
260
261
|
instanceId: string;
|
|
262
|
+
ownerGeneration?: string;
|
|
261
263
|
}
|
|
262
264
|
|
|
263
265
|
export type TelegramTargetOwnershipLookup = (
|