@llblab/pi-telegram 0.20.5 → 0.20.6
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/BACKLOG.md +8 -28
- package/CHANGELOG.md +8 -0
- package/docs/architecture.md +1 -1
- package/lib/outbound-markup.ts +39 -4
- package/lib/prompts.ts +3 -2
- package/lib/routing.ts +91 -13
- package/package.json +1 -1
package/BACKLOG.md
CHANGED
|
@@ -2,38 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
_This backlog tracks only open release-relevant work: live promoted-follower verification, evidence-gated Telegram client/runtime follow-ups, and upstream Pi API blockers. Completed validation evidence belongs in `CHANGELOG.md`, not in this queue._
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## P1 — Guest Media Live Follow-Ups
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Context: 0.20.5 shipped deterministic Guest Mode file/audio delivery coverage. Post-release private-DM smoke confirmed that one local document reaches the remote conversation through `answerGuestQuery`; remaining checks validate Telegram client behavior rather than gate the implemented transport.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- [ ] Capture or minimize the raw private `guest_message` shape for owner-authored turns without reply context and identify the stable remote-peer fields supplied by Telegram (`chat` identity, username/name/id, or another explicit peer field) before choosing a resolver.
|
|
12
|
-
- [ ] Centralize Guest Mode peer attribution: group turns use the group title; private non-owner turns use the sender; private owner turns use the replied guest when present and otherwise the remote private-chat peer.
|
|
13
|
-
- [ ] Compare ownership by Telegram user id (`allowedUserId`), not display name or username. Never emit the configured owner as `guest`; if Telegram omits a username, fall back to the remote peer's stable name/id rather than the owner.
|
|
14
|
-
- [ ] Keep `[reply|from:...]` and `[attachments|from:...]` source attribution aligned with the same resolved peer without changing the current-turn/source-context distinction.
|
|
15
|
-
- [ ] Add regressions for incoming private guests, owner replies, owner-authored no-reply turns, missing usernames, username changes, and named-profile pairing identities.
|
|
16
|
-
- [ ] Update the prompt-context contract/docs only after the minimized Telegram fixture establishes the actual private Guest Mode field semantics.
|
|
17
|
-
|
|
18
|
-
Done when: `[telegram|guest:...]` always identifies the remote peer or group for private/group Guest Mode, never the paired owner, and reply/attachment provenance remains source-correct.
|
|
19
|
-
|
|
20
|
-
## P0 — Guest Reply File And Audio Delivery
|
|
21
|
-
|
|
22
|
-
Evidence: live Guest Mode accepted `telegram_attach` during an active guest turn and reported the file as queued, but delivered nothing. Code inspection confirms that guest turns use sentinel `chatId: 0`; the tool appends files to `queuedAttachments`, then the agent-end guest branch sends only `answerGuestQuery` text and returns before queued attachments or voice artifacts run. Telegram's `answerGuestQuery` accepts one `InlineQueryResult`, not ordinary `sendDocument`/`sendVoice` multipart delivery, so local artifacts require a guest-specific result plan rather than reuse of chat/thread attachment transport.
|
|
9
|
+
Open work:
|
|
23
10
|
|
|
24
|
-
|
|
11
|
+
- [x] Confirm one local document in a private Guest Mode DM after extension reload.
|
|
12
|
+
- [x] Confirm one synthesized paired-comment voice result in a private Guest Mode DM; Telegram delivered it to the correctly attributed remote peer without visible fallback text.
|
|
13
|
+
- [ ] Confirm one local document and one synthesized voice/audio result in group Guest Mode.
|
|
14
|
+
- [ ] Record a focused client/API caveat only if live behavior contradicts the one-result and staging contracts.
|
|
25
15
|
|
|
26
|
-
|
|
27
|
-
- [x] Map the current Bot API `InlineQueryResult` capabilities for document, photo, audio, and voice replies, including URL versus cached `file_id`, caption limits, supported formats, and the one-result-per-guest-query constraint. `answerGuestQuery` accepts exactly one result; local multipart uploads are not accepted there. URL results require public HTTP content (documents only PDF/ZIP, audio MP3, voice OGG/OPUS, photos JPEG up to 5 MB), while cached result variants accept Telegram `document_file_id`, `photo_file_id`, `audio_file_id`, or `voice_file_id`; media captions remain limited to 0–1024 characters after entity parsing.
|
|
28
|
-
- [x] Design one guest reply planner that chooses exactly one result: text article, one cached local file/media with answer text reduced to a caption, or one cached synthesized voice/audio result. Guest tool admission rejects a second attachment before mutation. A failure before the one-shot answer may degrade to one text article; an ambiguous/failing `answerGuestQuery` call must not issue a second answer that could duplicate delivery.
|
|
29
|
-
- [x] Determine an evidence-backed local-file staging path. Local media must upload through the existing leader-owned multipart transport to the paired owner's bot chat, extract the returned Telegram `file_id`, answer the guest query with the matching cached result, and delete the staging message in `finally`. The staging message can briefly appear or notify the owner; this unavoidable Bot API limitation must be documented, no external hosting is introduced, and cleanup failure must be diagnosed rather than hidden.
|
|
30
|
-
- [x] Extend `answerGuestQuery` and bus forwarding from hard-coded article input to the minimal typed result union required by confirmed file/audio/voice cases.
|
|
31
|
-
- [x] Route `telegram_attach`, queued outbound artifacts, and `telegram_voice` through the guest planner before the guest branch returns; never call ordinary multipart methods with sentinel `chatId: 0`.
|
|
32
|
-
- [x] Preserve follower operation by routing staging and `answerGuestQuery` through the transport leader without duplicate answers or leaked staging messages.
|
|
33
|
-
- [x] Add regressions for unsupported fail-closed behavior, document/image/audio/voice result construction, caption fallback, staging cleanup/failure, multiple-file rejection, guest query one-shot semantics, and text fallback after media failure.
|
|
34
|
-
- [ ] Capture live private and group Guest Mode evidence for one local document and one synthesized voice/audio reply before claiming support.
|
|
35
|
-
|
|
36
|
-
Done when: guest turns never silently lose queued artifacts, one supported local file or audio/voice result can be delivered through `answerGuestQuery` with clear constraints, and unsupported/multi-file cases fail visibly without sending to an unrelated thread.
|
|
16
|
+
Done when: private and group Guest Mode each have direct document and voice/audio delivery evidence, or a confirmed Telegram limitation is documented.
|
|
37
17
|
|
|
38
18
|
## P1 — Compaction Status Ownership And Native Activity
|
|
39
19
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.20.6: Guest Attribution And Voice Action Hotfix
|
|
4
|
+
|
|
5
|
+
- `[Voice Action Syntax]` Hidden `telegram_voice` actions now accept the intuitive paired form `<!-- telegram_voice ... -->...<!-- /telegram_voice -->` alongside inline, attribute-text, and single-comment multiline forms. The parser captures a non-empty multiline body and preserves language/rate attributes while leaving surrounding prose visible. Impact: agents can use an explicit closing tag without leaking the intended TTS payload as ordinary text.
|
|
6
|
+
- `[Voice Investigation]` Removed the premature Guest Mode generate-before-answer experiment and unrelated voice-pipeline changes after live evidence identified unsupported paired markup as the confounder. After reload, voice-only paired actions delivered exactly one playable voice response with no visible fallback text in both the Aster thread and a correctly attributed private Guest Mode conversation; `telegram_attach` independently delivered the same format. Impact: standard Threaded Mode and private Guest Mode now have direct live proof on the established delivery architecture, while group Guest Mode confirmation remains explicit in `BACKLOG.md`.
|
|
7
|
+
- `[Guest Attribution]` Private Guest Mode now resolves `guest` as the remote conversation peer instead of the paired owner: a non-owner sender identifies itself, while an owner-authored turn prefers the replied peer, then the private-chat identity, then non-owner caller metadata. Username falls back to the remote display name and numeric id, and an unresolved payload records redacted field-presence diagnostics without leaking the owner's identity into `[telegram|guest:...]`. Group Guest Mode continues to use the group title. Impact: the agent sees the third party it is assisting in a direct conversation, not the bot owner who invoked it.
|
|
8
|
+
- `[Validation]` Added route and resolver regressions for owner-authored no-reply DMs, explicit replies, non-owner peers, username changes, missing usernames, numeric fallback, alternate paired profile ids, and owner-only unresolved input. Impact: private attribution and source-context provenance remain distinct while the paired owner can never become the guest label.
|
|
9
|
+
- `[Live Evidence]` After reloading the hotfix workspace, a real owner-authored private Guest Mode invocation arrived as `[telegram|guest:mariannaelunina]` instead of the paired owner `llblab`. Impact: the repaired remote-peer attribution now has direct Telegram evidence, so the completed P0 moved out of `BACKLOG.md` and the tree is ready for the next hotfix scope.
|
|
10
|
+
|
|
3
11
|
## 0.20.5: Guest Media And Runtime Recovery Hotfix
|
|
4
12
|
|
|
5
13
|
- `[Setup Persistence]` The setup prompt now applies the validated bot identity to its config store before invoking persistence, so adapters that serialize current store state cannot write an empty or stale `telegram.json` on first run. Persistence failure rolls the in-memory candidate back and remains ahead of success notifications and polling startup. Impact: setup from a missing file or `{}` durably writes `botToken`, `botId`, and `botUsername` on the first successful command for default and named profiles without reporting an unsaved connection.
|
package/docs/architecture.md
CHANGED
|
@@ -233,7 +233,7 @@ Assistant delivery guarantees:
|
|
|
233
233
|
|
|
234
234
|
- Model-authored Markdown is the source of truth; the bridge does not pre-render assistant Markdown to HTML unless the operator selects `assistant.rendering: "html"` for compatibility.
|
|
235
235
|
- Before native Rich Markdown delivery, the bridge normalizes known Bot-API-fragile source forms without changing visible meaning, including space-after-marker blockquotes and dollar-prefixed ticker atoms that Telegram may otherwise treat as unterminated math.
|
|
236
|
-
- Prompt context blocks use compact metadata (`[tag|key:value]`) as the stable inbound contract. `[telegram...]` names the current surface only: owner/current turns use `[telegram]` or `[telegram|thread:<name>]`; guest-mode turns use `[telegram|guest:<group-title-or-peer-username-or-id>]`. Source authors for quoted/forwarded material and their files are carried by `[reply|from:<username-or-id>]`, `[forward|from:<username-or-id>]`, and `[attachments|from:<username-or-id>]`, while plain `[attachments]` remains current-turn attachments and is ordered before reply/forward/source context.
|
|
236
|
+
- Prompt context blocks use compact metadata (`[tag|key:value]`) as the stable inbound contract. `[telegram...]` names the current surface only: owner/current turns use `[telegram]` or `[telegram|thread:<name>]`; guest-mode turns use `[telegram|guest:<group-title-or-peer-username-or-id>]`. In a private Guest Mode turn the paired owner's `from` identity is never the guest: an explicit replied peer wins, then the remote private-chat identity, then non-owner caller metadata; username falls back to the remote display name and numeric id. Source authors for quoted/forwarded material and their files are carried by `[reply|from:<username-or-id>]`, `[forward|from:<username-or-id>]`, and `[attachments|from:<username-or-id>]`, while plain `[attachments]` remains current-turn attachments and is ordered before reply/forward/source context.
|
|
237
237
|
- Quoted rich replies use Telegram `rich_message` blocks as the prompt-context source when available, so `[reply]` context receives rendered plain text instead of raw `InputRichMessage.markdown` fallback text.
|
|
238
238
|
- Long native Markdown replies are split only at Telegram Rich Message transport limits; oversized fenced code, display-math, and fully wrapped inline-formatting blocks are rewrapped per chunk so persisted Rich Markdown chunks remain structurally valid.
|
|
239
239
|
- When Draft previews are enabled, streaming previews pass structurally closed assistant Markdown prefixes through to `sendRichMessageDraft` with ownership checks, voice suppression, and serialized flushes. Unclosed inline spans, links, fenced code, comments, and display-math blocks are held back until a safe boundary exists. Draft failures are recorded and the failing frame is skipped instead of degrading to raw plain-message previews, because partial Markdown can be invalid while the final message remains valid.
|
package/lib/outbound-markup.ts
CHANGED
|
@@ -64,6 +64,36 @@ function isTopLevelClosingFence(
|
|
|
64
64
|
);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
function collectPairedTelegramVoiceActionBody(
|
|
68
|
+
markdown: string,
|
|
69
|
+
bodyStart: number,
|
|
70
|
+
commentContent: string,
|
|
71
|
+
): { content: string; end: number } | undefined {
|
|
72
|
+
const normalizedContent = commentContent.trim();
|
|
73
|
+
if (
|
|
74
|
+
!normalizedContent.startsWith("telegram_voice") ||
|
|
75
|
+
!isTelegramActionCommentContent(commentContent)
|
|
76
|
+
) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
let offset = bodyStart;
|
|
80
|
+
while (offset < markdown.length) {
|
|
81
|
+
const lineEnd = getMarkdownLineEnd(markdown, offset);
|
|
82
|
+
const line = getMarkdownLineText(markdown, offset, lineEnd);
|
|
83
|
+
if (line === "<!-- /telegram_voice -->") {
|
|
84
|
+
const body = markdown.slice(bodyStart, offset).trim();
|
|
85
|
+
if (!body) return undefined;
|
|
86
|
+
return {
|
|
87
|
+
content: `${commentContent.trimEnd()}\n${body}`,
|
|
88
|
+
end: lineEnd,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (line.startsWith("<!--")) return undefined;
|
|
92
|
+
offset = lineEnd;
|
|
93
|
+
}
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
|
|
67
97
|
function collectInlineClosedTelegramActionBody(
|
|
68
98
|
markdown: string,
|
|
69
99
|
bodyStart: number,
|
|
@@ -117,14 +147,19 @@ export function collectTopLevelHtmlComments(markdown: string): {
|
|
|
117
147
|
const closesOnOpeningLine = closeIndex < lineEnd;
|
|
118
148
|
const hasOnlyWhitespaceAfterClose =
|
|
119
149
|
line.slice(closeColumn + 3).trim() === "";
|
|
120
|
-
const
|
|
150
|
+
const pairedVoiceBody =
|
|
121
151
|
closesOnOpeningLine && hasOnlyWhitespaceAfterClose
|
|
152
|
+
? collectPairedTelegramVoiceActionBody(markdown, lineEnd, content)
|
|
153
|
+
: undefined;
|
|
154
|
+
const inlineBody =
|
|
155
|
+
!pairedVoiceBody && closesOnOpeningLine && hasOnlyWhitespaceAfterClose
|
|
122
156
|
? collectInlineClosedTelegramActionBody(markdown, lineEnd, content)
|
|
123
157
|
: undefined;
|
|
124
|
-
|
|
125
|
-
|
|
158
|
+
const recoveredBody = pairedVoiceBody ?? inlineBody;
|
|
159
|
+
if (recoveredBody) {
|
|
160
|
+
end = recoveredBody.end;
|
|
126
161
|
raw = markdown.slice(offset, end);
|
|
127
|
-
content =
|
|
162
|
+
content = recoveredBody.content;
|
|
128
163
|
}
|
|
129
164
|
comments.push({ raw, content, start: offset, end });
|
|
130
165
|
offset = getMarkdownLineEnd(markdown, end);
|
package/lib/prompts.ts
CHANGED
|
@@ -16,7 +16,7 @@ Telegram bridge available. Do not use it from local/TUI prompts unless explicitl
|
|
|
16
16
|
|
|
17
17
|
const TELEGRAM_TURN_SYSTEM_PROMPT_SUFFIX = `
|
|
18
18
|
|
|
19
|
-
Telegram turn note: If context was compacted or you need the pi-telegram bridge contract, call tool \`telegram_help\`; hidden comments are valid only for explicit \`telegram_voice\` or \`telegram_button\` actions with payload
|
|
19
|
+
Telegram turn note: If context was compacted or you need the pi-telegram bridge contract, call tool \`telegram_help\`; hidden comments are valid only for explicit \`telegram_voice\` or \`telegram_button\` actions with payload. For voice use a top-level HTML action: \`<!-- telegram_voice: Speak this. -->\`, multiline \`<!-- telegram_voice lang=ru\nSpeak this.\n-->\`, or paired \`<!-- telegram_voice lang=ru -->\nSpeak this.\n<!-- /telegram_voice -->\`.`;
|
|
20
20
|
|
|
21
21
|
function buildTelegramHelpText(profileName?: string): string {
|
|
22
22
|
const diagnosticsPaths = getTelegramDiagnosticsDisplayPaths(profileName);
|
|
@@ -37,7 +37,8 @@ How to answer Telegram turns:
|
|
|
37
37
|
Assistant-authored Telegram actions:
|
|
38
38
|
- \`telegram_voice\` and \`telegram_button\` are hidden top-level HTML comments, not Pi tools.
|
|
39
39
|
- Put action comments at column zero, outside code, quotes, lists, and indented examples.
|
|
40
|
-
- Voice forms: \`<!-- telegram_voice text="Short summary"
|
|
40
|
+
- Voice forms: \`<!-- telegram_voice text="Short summary" -->\`, \`<!-- telegram_voice: Short summary -->\`, multiline \`<!-- telegram_voice lang=ru\nShort summary.\n-->\`, or paired \`<!-- telegram_voice lang=ru -->\nShort summary.\n<!-- /telegram_voice -->\`.
|
|
41
|
+
- Keep the complete action at top level and include a non-empty voice payload.
|
|
41
42
|
- Keep voice text TTS-friendly; avoid raw Markdown, code, and tables in voice text.
|
|
42
43
|
- Voice delivery generates and attaches OGG automatically; do not also call \`telegram_attach\` for the same audio.
|
|
43
44
|
- Button forms: \`<!-- telegram_button: OK -->\`, \`<!-- telegram_button label=Continue prompt="Continue with the current plan." -->\`, or multiline \`<!-- telegram_button label="Show risks"\nList the main risks first.\n-->\`.
|
package/lib/routing.ts
CHANGED
|
@@ -21,10 +21,67 @@ import * as TextGroups from "./text-groups.ts";
|
|
|
21
21
|
import * as ThreadReconciler from "./thread-reconciler.ts";
|
|
22
22
|
import * as Turns from "./turns.ts";
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
interface TelegramPromptPeerView {
|
|
25
|
+
id?: unknown;
|
|
26
|
+
username?: unknown;
|
|
27
|
+
first_name?: unknown;
|
|
28
|
+
last_name?: unknown;
|
|
29
|
+
title?: unknown;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function formatTelegramPromptPeer(
|
|
33
|
+
peer: TelegramPromptPeerView | undefined,
|
|
34
|
+
): string | undefined {
|
|
35
|
+
if (!peer) return undefined;
|
|
36
|
+
if (typeof peer.username === "string" && peer.username.length > 0) {
|
|
37
|
+
return peer.username;
|
|
38
|
+
}
|
|
39
|
+
const displayName = [peer.first_name, peer.last_name]
|
|
40
|
+
.filter(
|
|
41
|
+
(part): part is string =>
|
|
42
|
+
typeof part === "string" && part.length > 0,
|
|
43
|
+
)
|
|
44
|
+
.join(" ");
|
|
45
|
+
if (displayName) return displayName;
|
|
46
|
+
if (typeof peer.title === "string" && peer.title.length > 0) {
|
|
47
|
+
return peer.title;
|
|
48
|
+
}
|
|
49
|
+
return typeof peer.id === "number" ? String(peer.id) : undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function isTelegramPromptOwnerPeer(
|
|
53
|
+
peer: TelegramPromptPeerView | undefined,
|
|
54
|
+
ownerUserId: number | undefined,
|
|
55
|
+
): boolean {
|
|
56
|
+
return ownerUserId !== undefined && peer?.id === ownerUserId;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function resolveTelegramGuestPromptPeer(input: {
|
|
60
|
+
chatType?: string;
|
|
61
|
+
chat?: TelegramPromptPeerView;
|
|
62
|
+
from?: TelegramPromptPeerView;
|
|
63
|
+
replyFrom?: TelegramPromptPeerView;
|
|
64
|
+
guestBotCallerUser?: TelegramPromptPeerView;
|
|
65
|
+
guestBotCallerChat?: TelegramPromptPeerView;
|
|
66
|
+
ownerUserId?: number;
|
|
67
|
+
}): string | undefined {
|
|
68
|
+
if (input.chatType !== "private") {
|
|
69
|
+
return formatTelegramPromptPeer(input.chat);
|
|
70
|
+
}
|
|
71
|
+
if (!isTelegramPromptOwnerPeer(input.from, input.ownerUserId)) {
|
|
72
|
+
return formatTelegramPromptPeer(input.from);
|
|
73
|
+
}
|
|
74
|
+
for (const candidate of [
|
|
75
|
+
input.replyFrom,
|
|
76
|
+
input.chat,
|
|
77
|
+
input.guestBotCallerUser,
|
|
78
|
+
input.guestBotCallerChat,
|
|
79
|
+
]) {
|
|
80
|
+
if (isTelegramPromptOwnerPeer(candidate, input.ownerUserId)) continue;
|
|
81
|
+
const peer = formatTelegramPromptPeer(candidate);
|
|
82
|
+
if (peer) return peer;
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
28
85
|
}
|
|
29
86
|
|
|
30
87
|
function appendTelegramSourceAttachmentSection(
|
|
@@ -1812,22 +1869,43 @@ export function createTelegramInboundRouteRuntime<
|
|
|
1812
1869
|
const gm = guestMessage as unknown as Record<string, unknown>;
|
|
1813
1870
|
// Build telegram prefix with guest context
|
|
1814
1871
|
const chatRaw = gm.chat as Record<string, unknown>;
|
|
1815
|
-
const chatTitle = chatRaw?.title as string | undefined;
|
|
1816
1872
|
const chatType = chatRaw?.type as string;
|
|
1817
1873
|
const fromRaw = gm.from as Record<string, unknown> | undefined;
|
|
1818
1874
|
const replyMsg = gm.reply_to_message as Record<string, unknown> | undefined;
|
|
1819
1875
|
const replyFromRaw = replyMsg?.from as Record<string, unknown> | undefined;
|
|
1820
|
-
const
|
|
1876
|
+
const guestBotCallerUser = gm.guest_bot_caller_user as
|
|
1877
|
+
| Record<string, unknown>
|
|
1878
|
+
| undefined;
|
|
1879
|
+
const guestBotCallerChat = gm.guest_bot_caller_chat as
|
|
1880
|
+
| Record<string, unknown>
|
|
1881
|
+
| undefined;
|
|
1882
|
+
const ownerUserId = deps.configStore.getAllowedUserId();
|
|
1821
1883
|
const replyPeer = formatTelegramPromptPeer(replyFromRaw);
|
|
1822
|
-
const
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
:
|
|
1884
|
+
const guestPeer = resolveTelegramGuestPromptPeer({
|
|
1885
|
+
chatType,
|
|
1886
|
+
chat: chatRaw,
|
|
1887
|
+
from: fromRaw,
|
|
1888
|
+
replyFrom: replyFromRaw,
|
|
1889
|
+
guestBotCallerUser,
|
|
1890
|
+
guestBotCallerChat,
|
|
1891
|
+
ownerUserId,
|
|
1892
|
+
});
|
|
1826
1893
|
const prefixParts = ["telegram"];
|
|
1827
|
-
if (
|
|
1828
|
-
prefixParts.push(`guest:${chatTitle}`);
|
|
1829
|
-
} else if (chatType === "private" && guestPeer) {
|
|
1894
|
+
if (guestPeer) {
|
|
1830
1895
|
prefixParts.push(`guest:${guestPeer}`);
|
|
1896
|
+
} else if (chatType === "private") {
|
|
1897
|
+
deps.recordRuntimeEvent?.(
|
|
1898
|
+
"guest",
|
|
1899
|
+
new Error("Private Guest Mode remote peer could not be resolved"),
|
|
1900
|
+
{
|
|
1901
|
+
phase: "peer-attribution",
|
|
1902
|
+
chatId: typeof chatRaw?.id === "number" ? chatRaw.id : undefined,
|
|
1903
|
+
fromId: typeof fromRaw?.id === "number" ? fromRaw.id : undefined,
|
|
1904
|
+
hasReplyFrom: !!replyFromRaw,
|
|
1905
|
+
hasCallerUser: !!guestBotCallerUser,
|
|
1906
|
+
hasCallerChat: !!guestBotCallerChat,
|
|
1907
|
+
},
|
|
1908
|
+
);
|
|
1831
1909
|
}
|
|
1832
1910
|
const telegramPrefix = `[${prefixParts.join("|")}]`;
|
|
1833
1911
|
// Extract reply context
|