@segmentstream/agent 0.6.8 → 0.6.11
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/README.md +18 -4
- package/dist/_chunks/conversation-runtime-ovs8IhXJ.mjs +212 -0
- package/dist/_chunks/inbound-request-DRNPIIpb.mjs +325 -0
- package/dist/extension/hooks/conversations.mjs +1 -1
- package/dist/extension/instructions/conversations.mjs +1 -1
- package/dist/extension/instructions.md +4 -0
- package/dist/extension/lib/conversation-runtime.mjs +1 -109
- package/dist/extension/lib/conversations.d.ts +13 -2
- package/dist/extension/lib/conversations.mjs +5 -5
- package/dist/extension/lib/telegram/adapter.mjs +20 -6
- package/dist/extension/lib/telegram/attachments-send.mjs +1 -1
- package/dist/extension/lib/telegram/audience.d.ts +5 -1
- package/dist/extension/lib/telegram/audience.mjs +15 -2
- package/dist/extension/lib/telegram/confirmations.mjs +1 -2
- package/dist/extension/lib/telegram/conversations.d.ts +2 -2
- package/dist/extension/lib/telegram/conversations.mjs +110 -26
- package/dist/extension/lib/telegram/customer-text.d.ts +1 -1
- package/dist/extension/lib/telegram/customer-text.mjs +1 -217
- package/dist/extension/lib/telegram/files.mjs +1 -1
- package/dist/extension/lib/telegram/images.mjs +1 -1
- package/dist/extension/lib/telegram/inbound-request.d.ts +7 -0
- package/dist/extension/lib/telegram/inbound-request.mjs +1 -74
- package/dist/extension/lib/telegram/inbound.mjs +15 -1
- package/dist/extension/lib/telegram/media-policy.mjs +1 -1
- package/dist/extension/lib/telegram/router.d.ts +2 -0
- package/dist/extension/lib/telegram/router.mjs +4 -3
- package/dist/extension/lib/telegram/silent.mjs +1 -1
- package/dist/extension/lib/telegram/turn-errors.d.ts +1 -1
- package/dist/extension/lib/telegram/turn-errors.mjs +9 -1
- package/dist/extension/lib/telegram/voice.mjs +1 -1
- package/package.json +1 -1
|
@@ -4,113 +4,5 @@ import { createRequire as __eveCreateRequire } from "node:module";
|
|
|
4
4
|
const __filename = __eveFileURLToPath(import.meta.url);
|
|
5
5
|
__eveDirname(__filename);
|
|
6
6
|
__eveCreateRequire(import.meta.url);
|
|
7
|
-
import {
|
|
8
|
-
import { conversationEvent, conversations, summarizeArguments } from "./conversations.mjs";
|
|
9
|
-
import { parseAddress } from "./telegram/sessions.mjs";
|
|
10
|
-
import { completionKind } from "./telegram/silent.mjs";
|
|
11
|
-
function conversationCoordinates(context) {
|
|
12
|
-
const principal = context.session.auth.current;
|
|
13
|
-
const telegram = principal?.authenticator === "segmentstream-telegram" || context.channel.kind?.includes("telegram");
|
|
14
|
-
const address = context.channel.continuationToken;
|
|
15
|
-
const attributes = principal?.attributes;
|
|
16
|
-
const fields = address?.split(":");
|
|
17
|
-
const localAddress = telegram && fields?.length === 4 ? fields.slice(1).join(":") : address;
|
|
18
|
-
const parsed = telegram && localAddress !== void 0 && /^-?\d+:\d*:\d*$/u.test(localAddress) ? parseAddress(localAddress) : void 0;
|
|
19
|
-
const attributeChatId = typeof attributes?.chatId === "string" && (!telegram || /^-?\d+$/u.test(attributes.chatId)) ? attributes.chatId : void 0;
|
|
20
|
-
const chatId = telegram ? parsed?.chatId ?? attributeChatId : attributeChatId !== void 0 ? attributeChatId : address;
|
|
21
|
-
if (!chatId) return;
|
|
22
|
-
const memberId = memberIdOf(principal);
|
|
23
|
-
const chatType = attributes?.chatType;
|
|
24
|
-
return {
|
|
25
|
-
provider: telegram ? "telegram" : context.channel.kind ?? "eve",
|
|
26
|
-
chatId,
|
|
27
|
-
chatType: chatType === "group" || chatType === "supergroup" || chatType === void 0 && telegram && chatId.startsWith("-") ? "group" : chatType === "channel" ? "channel" : "private",
|
|
28
|
-
threadId: !telegram ? null : parsed !== void 0 ? parsed.messageThreadId || null : typeof attributes?.messageThreadId === "string" ? attributes.messageThreadId : null,
|
|
29
|
-
participantIds: memberId === void 0 ? [] : [memberId],
|
|
30
|
-
senderId: principal?.subject ?? null
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
function conversationRecorder(archive = conversations()) {
|
|
34
|
-
return async (event, context) => {
|
|
35
|
-
const coordinates = conversationCoordinates(context);
|
|
36
|
-
if (coordinates === void 0) return;
|
|
37
|
-
const base = {
|
|
38
|
-
...coordinates,
|
|
39
|
-
sessionId: context.session.id,
|
|
40
|
-
turnId: context.session.turn.id,
|
|
41
|
-
at: event.meta.at
|
|
42
|
-
};
|
|
43
|
-
const eventId = `eve:${context.session.id}:${event.meta.id}`;
|
|
44
|
-
if (event.type === "actions.requested") for (const action of event.data.actions) {
|
|
45
|
-
const name = "toolName" in action ? action.toolName : action.name;
|
|
46
|
-
await archive.append(conversationEvent({
|
|
47
|
-
...base,
|
|
48
|
-
eventId: `${eventId}:${action.callId}`,
|
|
49
|
-
kind: "tool_call",
|
|
50
|
-
toolCallId: action.callId,
|
|
51
|
-
toolName: name,
|
|
52
|
-
argsSummary: summarizeArguments(action.input),
|
|
53
|
-
status: "requested"
|
|
54
|
-
}));
|
|
55
|
-
}
|
|
56
|
-
else if (event.type === "action.result") {
|
|
57
|
-
const result = event.data.result;
|
|
58
|
-
const name = "toolName" in result ? result.toolName : "name" in result ? result.name : result.kind;
|
|
59
|
-
await archive.append(conversationEvent({
|
|
60
|
-
...base,
|
|
61
|
-
eventId,
|
|
62
|
-
kind: "tool_result",
|
|
63
|
-
toolCallId: result.callId,
|
|
64
|
-
toolName: name,
|
|
65
|
-
status: event.data.status
|
|
66
|
-
}));
|
|
67
|
-
} else if (event.type === "task.started" || event.type === "task.settled") await archive.append(conversationEvent({
|
|
68
|
-
...base,
|
|
69
|
-
eventId,
|
|
70
|
-
kind: event.type === "task.started" ? "tool_call" : "tool_result",
|
|
71
|
-
toolCallId: event.data.callId,
|
|
72
|
-
toolName: event.data.name ?? "task",
|
|
73
|
-
argsSummary: "{}",
|
|
74
|
-
status: event.type === "task.started" ? "requested" : event.data.status === "cancelled" ? "cancelled" : event.data.status === "failed" ? "failed" : "completed"
|
|
75
|
-
}));
|
|
76
|
-
else if (event.type === "message.completed" && event.data.finishReason !== "tool-calls") {
|
|
77
|
-
const kind = completionKind(event.data.message, event.data.finishReason);
|
|
78
|
-
const silent = kind !== "reply";
|
|
79
|
-
const note = kind === "silent_marker" ? "Model returned an explicit silence marker; no message delivery." : kind === "empty" ? "Model completed without final text; no message delivery." : kind === "whitespace" ? "Model returned only whitespace; no message delivery." : "Model produced a reply; transport delivery is recorded separately.";
|
|
80
|
-
await archive.append(conversationEvent({
|
|
81
|
-
...base,
|
|
82
|
-
eventId,
|
|
83
|
-
kind: "decision",
|
|
84
|
-
decision: silent ? "silent" : "reply",
|
|
85
|
-
reason: kind === "reply" ? "model_reply" : `model_${kind}_output`,
|
|
86
|
-
text: note,
|
|
87
|
-
argsSummary: JSON.stringify({
|
|
88
|
-
outputKind: kind,
|
|
89
|
-
outputCharacters: event.data.message?.length ?? 0,
|
|
90
|
-
finishReason: event.data.finishReason
|
|
91
|
-
})
|
|
92
|
-
}));
|
|
93
|
-
if (coordinates.provider !== "telegram" && !silent) await archive.append(conversationEvent({
|
|
94
|
-
...base,
|
|
95
|
-
eventId: `${eventId}:outbound`,
|
|
96
|
-
kind: "outbound",
|
|
97
|
-
text: event.data.message,
|
|
98
|
-
senderId: "agent",
|
|
99
|
-
senderIsBot: true,
|
|
100
|
-
status: "completed"
|
|
101
|
-
}));
|
|
102
|
-
} else if (event.type === "message.received" && coordinates.provider !== "telegram") await archive.append(conversationEvent({
|
|
103
|
-
...base,
|
|
104
|
-
eventId,
|
|
105
|
-
kind: "inbound",
|
|
106
|
-
text: event.data.message,
|
|
107
|
-
attachments: (event.data.parts ?? []).filter((part) => part.type === "file").map((part, index) => ({
|
|
108
|
-
id: `${eventId}:file:${index}`,
|
|
109
|
-
kind: "file",
|
|
110
|
-
...part.filename ? { filename: part.filename } : {},
|
|
111
|
-
contentType: part.mediaType
|
|
112
|
-
}))
|
|
113
|
-
}));
|
|
114
|
-
};
|
|
115
|
-
}
|
|
7
|
+
import { n as conversationRecorder, t as conversationCoordinates } from "../../_chunks/conversation-runtime-ovs8IhXJ.mjs";
|
|
116
8
|
export { conversationCoordinates, conversationRecorder };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Environment } from "./environment.js";
|
|
2
2
|
import { type RuntimeToken } from "./runtime-token.js";
|
|
3
|
-
/** Provider-neutral platform archive.
|
|
3
|
+
/** Provider-neutral platform archive. Content uses the shared redaction and byte-limit policy. */
|
|
4
4
|
export interface ConversationEvent {
|
|
5
5
|
readonly eventId: string;
|
|
6
6
|
readonly provider: string;
|
|
@@ -22,6 +22,9 @@ export interface ConversationEvent {
|
|
|
22
22
|
readonly toolCallId: string | null;
|
|
23
23
|
readonly toolName: string | null;
|
|
24
24
|
readonly argsSummary: string | null;
|
|
25
|
+
readonly argsText?: string | null;
|
|
26
|
+
readonly resultText?: string | null;
|
|
27
|
+
readonly errorText?: string | null;
|
|
25
28
|
readonly status: "requested" | "completed" | "failed" | "rejected" | "cancelled" | "unknown" | "sent" | "uncertain" | null;
|
|
26
29
|
readonly operationId?: string | null;
|
|
27
30
|
readonly requestedBy?: {
|
|
@@ -52,12 +55,20 @@ export interface ConversationPage {
|
|
|
52
55
|
readonly nextCursor: string | null;
|
|
53
56
|
}
|
|
54
57
|
export interface Conversations {
|
|
55
|
-
|
|
58
|
+
/** True only for the first insert of this event ID. Legacy custom archives may omit the result, but Telegram dispatch requires explicit true. */
|
|
59
|
+
append(event: ConversationEvent): Promise<boolean | void>;
|
|
56
60
|
context(input: {
|
|
57
61
|
readonly provider: string;
|
|
58
62
|
readonly chatId: string;
|
|
59
63
|
readonly threadId?: string;
|
|
60
64
|
readonly limit?: number;
|
|
65
|
+
/** Read routing audit records as this chat's reader, rather than model transcript only. */
|
|
66
|
+
readonly includeDecisions?: boolean;
|
|
67
|
+
readonly participantId?: string;
|
|
68
|
+
readonly before?: string;
|
|
69
|
+
readonly from?: string;
|
|
70
|
+
readonly senderId?: string;
|
|
71
|
+
readonly messageId?: string;
|
|
61
72
|
}): Promise<ConversationPage>;
|
|
62
73
|
}
|
|
63
74
|
/** Collapse delivery snapshots from older archives before building a user/model transcript. */
|
|
@@ -66,14 +66,14 @@ function conversations(input = {}) {
|
|
|
66
66
|
}
|
|
67
67
|
return {
|
|
68
68
|
append: async (event) => {
|
|
69
|
-
await call("events", event);
|
|
69
|
+
return (await call("events", event)).stored;
|
|
70
70
|
},
|
|
71
|
-
context: async (search) => await call("search", {
|
|
71
|
+
context: async ({ includeDecisions, participantId, ...search }) => await call("search", {
|
|
72
72
|
search,
|
|
73
|
-
context:
|
|
73
|
+
context: !includeDecisions,
|
|
74
74
|
reader: {
|
|
75
|
-
participantId: null,
|
|
76
|
-
groups:
|
|
75
|
+
participantId: participantId ?? null,
|
|
76
|
+
groups: includeDecisions === true,
|
|
77
77
|
admin: false
|
|
78
78
|
}
|
|
79
79
|
})
|
|
@@ -13,10 +13,9 @@ import { agentAttachments, parseAttachmentUrl } from "../attachments.mjs";
|
|
|
13
13
|
import { agentIdentities } from "../identities.mjs";
|
|
14
14
|
import { conversations } from "../conversations.mjs";
|
|
15
15
|
import { observedMessages } from "./observed.mjs";
|
|
16
|
-
import { InboundRequest, currentInboundRequest, runInboundRequest, withReplacedText } from "./inbound-request.mjs";
|
|
17
16
|
import { telegramSendScope } from "./send-queue.mjs";
|
|
18
|
-
import { failureText,
|
|
19
|
-
import { captureRouteDecider, captureTelegramFetch, conversationIdentities, telegramConversationCapture } from "./conversations.mjs";
|
|
17
|
+
import { d as failureText, g as withDeliveryLocale, h as safeTelegramFetch, i as withReplacedText, m as reportTelegramError, n as currentInboundRequest, r as runInboundRequest, t as InboundRequest } from "../../../_chunks/inbound-request-DRNPIIpb.mjs";
|
|
18
|
+
import { captureRouteDecider, captureTelegramFetch, conversationIdentities, recordGateDecision, telegramConversationCapture } from "./conversations.mjs";
|
|
20
19
|
import { runtimeTokenVerifier } from "../runtime-identity.mjs";
|
|
21
20
|
import { botIdentityCache } from "./bot.mjs";
|
|
22
21
|
import { confirmationLocale, confirmationRenderer } from "./confirmation-text.mjs";
|
|
@@ -125,6 +124,7 @@ function segmentstreamTelegram(options) {
|
|
|
125
124
|
return store;
|
|
126
125
|
}
|
|
127
126
|
const channel = telegramChannel({
|
|
127
|
+
turnPolicy: "queue",
|
|
128
128
|
api: {
|
|
129
129
|
apiBaseUrl: TELEGRAM_PROXY_ORIGIN,
|
|
130
130
|
fileBaseUrl: TELEGRAM_PROXY_ORIGIN,
|
|
@@ -201,7 +201,21 @@ function segmentstreamTelegram(options) {
|
|
|
201
201
|
connectUrl: (code) => `${appOrigin(environment)}/agent/connect?code=${encodeURIComponent(code)}`
|
|
202
202
|
}
|
|
203
203
|
});
|
|
204
|
-
if (inbound !== void 0 && result !== null)
|
|
204
|
+
if (inbound !== void 0 && result !== null) {
|
|
205
|
+
inbound.locale = confirmationLocale(result.auth);
|
|
206
|
+
inbound.deliveryFailed = async (error) => {
|
|
207
|
+
recordGateDecision("inbound_delivery_failed");
|
|
208
|
+
const errorId = reportTelegramError("inbound_delivery_failed", error, {
|
|
209
|
+
chatId: message.chat.id,
|
|
210
|
+
messageId: message.messageId
|
|
211
|
+
});
|
|
212
|
+
try {
|
|
213
|
+
await context.telegram.post(failureText(errorId));
|
|
214
|
+
} catch (noticeError) {
|
|
215
|
+
reportTelegramError("inbound_failure_notice_failed", noticeError, { errorId });
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
205
219
|
return result;
|
|
206
220
|
} catch (error) {
|
|
207
221
|
const errorId = reportTelegramError("inbound_message_failed", error, { chatId: message.chat.id });
|
|
@@ -305,8 +319,8 @@ function gatedRoute(input) {
|
|
|
305
319
|
args.waitUntil(task);
|
|
306
320
|
};
|
|
307
321
|
inbound.waitUntil = waitUntil;
|
|
308
|
-
return runInboundRequest(inbound, () => input.capture(request, async () => {
|
|
309
|
-
const response = await route.handler(
|
|
322
|
+
return runInboundRequest(inbound, () => input.capture(request, async (routedRequest = request) => {
|
|
323
|
+
const response = await route.handler(routedRequest, {
|
|
310
324
|
...args,
|
|
311
325
|
waitUntil,
|
|
312
326
|
from: (address) => withReplacedText(args.from(inbound.address ?? scoped(address)), inbound)
|
|
@@ -12,7 +12,7 @@ import { runtimeToken } from "../runtime-token.mjs";
|
|
|
12
12
|
import { ACT_AS_HEADER } from "../ontology-connection.mjs";
|
|
13
13
|
import { AttachmentError, CONVERSATION_HEADER, SESSION_HEADER } from "../attachments.mjs";
|
|
14
14
|
import { inTelegramSendQueue, telegramSendScope } from "./send-queue.mjs";
|
|
15
|
-
import { customerCopy,
|
|
15
|
+
import { c as customerCopy, f as formattingRefused, l as customerMessage, m as reportTelegramError } from "../../../_chunks/inbound-request-DRNPIIpb.mjs";
|
|
16
16
|
import { confirmationLocale } from "./confirmation-text.mjs";
|
|
17
17
|
import { TELEGRAM_PLACEHOLDER_TOKEN, TELEGRAM_PROXY_ORIGIN, telegramProxyFetch } from "./proxy-fetch.mjs";
|
|
18
18
|
import { attachmentOwner } from "../attachment-tools.mjs";
|
|
@@ -17,10 +17,14 @@ export interface MessageAudience {
|
|
|
17
17
|
readonly replyTo: (Recipient & {
|
|
18
18
|
readonly messageId: string;
|
|
19
19
|
readonly text: string;
|
|
20
|
+
/** A reply can quote evidence instead of addressing its uploader. */
|
|
21
|
+
readonly hasAttachments?: boolean;
|
|
22
|
+
readonly filename?: string;
|
|
23
|
+
readonly self?: boolean;
|
|
20
24
|
}) | null;
|
|
21
25
|
}
|
|
22
26
|
/** Current-message entities only; mentions inside a quoted/code range are not addressees. */
|
|
23
27
|
export declare function messageAudience(message: TelegramMessage, bot: BotIdentity | undefined): MessageAudience;
|
|
24
|
-
/**
|
|
28
|
+
/** Explicit recipients win; quoting an upload or one's own message goes to smart inference. */
|
|
25
29
|
export declare function otherRecipient(audience: MessageAudience | undefined): string | undefined;
|
|
26
30
|
export {};
|
|
@@ -53,6 +53,7 @@ function messageAudience(message, bot) {
|
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
55
|
const rawReply = telegramRecord(message.raw.reply_to_message);
|
|
56
|
+
const documentName = telegramRecord(rawReply.document).file_name;
|
|
56
57
|
const author = message.replyToMessage?.from;
|
|
57
58
|
const replyId = message.replyToMessage?.messageId ?? rawReply.message_id;
|
|
58
59
|
const replyTo = replyId === void 0 ? null : {
|
|
@@ -63,7 +64,19 @@ function messageAudience(message, bot) {
|
|
|
63
64
|
first_name: author?.firstName
|
|
64
65
|
}, bot),
|
|
65
66
|
messageId: String(replyId),
|
|
66
|
-
text: messageContent(rawReply).text.slice(0, 1e3)
|
|
67
|
+
text: messageContent(rawReply).text.slice(0, 1e3),
|
|
68
|
+
...typeof documentName === "string" ? { filename: documentName } : {},
|
|
69
|
+
hasAttachments: [
|
|
70
|
+
"document",
|
|
71
|
+
"photo",
|
|
72
|
+
"voice",
|
|
73
|
+
"audio",
|
|
74
|
+
"video",
|
|
75
|
+
"video_note",
|
|
76
|
+
"sticker",
|
|
77
|
+
"animation"
|
|
78
|
+
].some((kind) => rawReply[kind] !== void 0),
|
|
79
|
+
self: message.from !== void 0 && String(telegramRecord(rawReply.from).id ?? author?.id) === message.from.id
|
|
67
80
|
};
|
|
68
81
|
return {
|
|
69
82
|
originalText: text,
|
|
@@ -74,7 +87,7 @@ function messageAudience(message, bot) {
|
|
|
74
87
|
}
|
|
75
88
|
function otherRecipient(audience) {
|
|
76
89
|
if (audience === void 0 || audience.botTagged || audience.replyTo?.kind === "bot") return;
|
|
77
|
-
if (audience.replyTo?.kind === "human" || audience.replyTo?.kind === "other_bot") return "Reply targets another person or bot; this bot was not addressed.";
|
|
78
90
|
if (audience.mentions.some((item) => item.kind !== "bot")) return "Message tags another recipient; this bot was not addressed.";
|
|
91
|
+
if (audience.replyTo?.kind === "other_bot" || audience.replyTo?.kind === "human" && !audience.replyTo.hasAttachments && !audience.replyTo.self) return "Reply targets another person or bot; this bot was not addressed.";
|
|
79
92
|
}
|
|
80
93
|
export { messageAudience, otherRecipient };
|
|
@@ -9,8 +9,7 @@ import { chatPostOperationId, isChatPost } from "../chat-post-confirmation.mjs";
|
|
|
9
9
|
import { chatPostCard, chatPostCardStatus, chatPostCopy, chatPostReceipt, withChatPostCard } from "./chat-post-card.mjs";
|
|
10
10
|
import { confirmations } from "../confirmations.mjs";
|
|
11
11
|
import { conversationEvent } from "../conversations.mjs";
|
|
12
|
-
import { currentInboundRequest } from "
|
|
13
|
-
import { currentLocale, failureText, reportTelegramError } from "./customer-text.mjs";
|
|
12
|
+
import { d as failureText, m as reportTelegramError, n as currentInboundRequest, o as currentLocale } from "../../../_chunks/inbound-request-DRNPIIpb.mjs";
|
|
14
13
|
import { answerMarker, isPendingQuestion, pendingQuestions, questionKey } from "../questions.mjs";
|
|
15
14
|
import { confirmationCopy, confirmationLocale, fallbackConfirmation } from "./confirmation-text.mjs";
|
|
16
15
|
import { formatAddress, scopedAddress } from "./sessions.mjs";
|
|
@@ -10,7 +10,7 @@ export declare function captureTelegramWebhook(input: {
|
|
|
10
10
|
readonly identities: AgentIdentities;
|
|
11
11
|
readonly observed: ObservedMessages;
|
|
12
12
|
readonly chats?: AgentChats;
|
|
13
|
-
readonly next: () => Promise<Response>;
|
|
13
|
+
readonly next: (request?: Request) => Promise<Response>;
|
|
14
14
|
}): Promise<Response>;
|
|
15
15
|
/** Additive inbound hook: the router keeps ownership of the reply policy. */
|
|
16
16
|
export declare function recordGateDecision(reason: string, decision?: "reply" | "silent"): void;
|
|
@@ -27,4 +27,4 @@ export declare function telegramConversationCapture(input: {
|
|
|
27
27
|
readonly observed: ObservedMessages;
|
|
28
28
|
readonly chats: AgentChats;
|
|
29
29
|
readonly verify: (request: Request) => boolean;
|
|
30
|
-
}): (request: Request, next: () => Promise<Response>) => Promise<Response>;
|
|
30
|
+
}): (request: Request, next: (request?: Request) => Promise<Response>) => Promise<Response>;
|
|
@@ -6,12 +6,11 @@ __eveDirname(__filename);
|
|
|
6
6
|
__eveCreateRequire(import.meta.url);
|
|
7
7
|
import { collapseConversationEvents, conversationEvent } from "../conversations.mjs";
|
|
8
8
|
import { observedChatKey } from "./observed.mjs";
|
|
9
|
-
import { currentInboundRequest } from "
|
|
9
|
+
import { n as currentInboundRequest, s as currentThreadId } from "../../../_chunks/inbound-request-DRNPIIpb.mjs";
|
|
10
10
|
import { reportMembership } from "./membership.mjs";
|
|
11
|
-
import { currentThreadId } from "./customer-text.mjs";
|
|
12
11
|
import { contentShape, messageContent } from "./content.mjs";
|
|
13
12
|
import { parseTelegramUpdate } from "eve/channels/telegram";
|
|
14
|
-
import { randomUUID } from "node:crypto";
|
|
13
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
15
14
|
const captures = new WeakMap();
|
|
16
15
|
const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
17
16
|
const id = (value) => typeof value === "number" || typeof value === "string" ? String(value) : null;
|
|
@@ -124,21 +123,37 @@ async function captureTelegramWebhook(input) {
|
|
|
124
123
|
chatId: message.chat.id,
|
|
125
124
|
...message.messageThreadId === void 0 ? {} : { messageThreadId: message.messageThreadId }
|
|
126
125
|
});
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
126
|
+
const automated = message.from === void 0 || message.from.isBot || message.chat.type === "channel";
|
|
127
|
+
const history = automated ? void 0 : await input.archive.context({
|
|
128
|
+
provider: "telegram",
|
|
129
|
+
chatId: message.chat.id,
|
|
130
|
+
...event.threadId === null ? {} : { threadId: event.threadId },
|
|
131
|
+
limit: 100,
|
|
132
|
+
includeDecisions: true,
|
|
133
|
+
...lookup?.status === "paired" ? { participantId: lookup.member.id } : {}
|
|
134
|
+
});
|
|
135
|
+
const routing = automated ? [] : (await input.archive.context({
|
|
136
|
+
provider: "telegram",
|
|
137
|
+
chatId: message.chat.id,
|
|
138
|
+
messageId: message.messageId,
|
|
139
|
+
includeDecisions: true,
|
|
140
|
+
limit: 100,
|
|
141
|
+
...lookup?.status === "paired" ? { participantId: lookup.member.id } : {}
|
|
142
|
+
})).events.filter((earlier) => earlier.kind === "decision" && earlier.senderId === event.senderId);
|
|
143
|
+
const previousClaim = routing.find((earlier) => earlier.reason === "turn_claimed");
|
|
144
|
+
const admitted = routing.some((earlier) => earlier.reason === "turn_admitted");
|
|
145
|
+
const released = previousClaim !== void 0 && routing.some((earlier) => earlier.eventId === `${previousClaim.eventId}:released`);
|
|
146
|
+
const dispatched = routing.some((earlier) => earlier.reason === "turn_dispatch_started");
|
|
147
|
+
const reserved = previousClaim !== void 0 && !released && Date.now() - Date.parse(previousClaim.at) < 6e5;
|
|
148
|
+
const alreadyHandled = admitted || dispatched || reserved;
|
|
149
|
+
if (!automated && edited === void 0 && !alreadyHandled) for (const earlier of (input.observed.recent(chat).length === 0 ? collapseConversationEvents(history?.events ?? []) : []).filter((item) => item.kind === "inbound" && item.eventId !== event.eventId && !history?.events.some((decision) => decision.kind === "decision" && decision.messageId === item.messageId && decision.reason === "turn_admitted")).slice(0, 30).reverse()) input.observed.add(chat, {
|
|
150
|
+
author: earlier.senderName ?? earlier.senderId ?? "automated post",
|
|
151
|
+
text: earlier.text ?? "[attachment or service message]",
|
|
152
|
+
at: earlier.at,
|
|
153
|
+
...earlier.messageId === null ? {} : { messageId: earlier.messageId },
|
|
154
|
+
...earlier.senderId === null ? {} : { authorId: earlier.senderId }
|
|
155
|
+
});
|
|
156
|
+
else if (automated || edited !== void 0) {
|
|
142
157
|
if (message.chat.type === "group" || message.chat.type === "supergroup") await input.chats?.seen({
|
|
143
158
|
chatId: message.chat.id,
|
|
144
159
|
title: message.chat.title ?? ""
|
|
@@ -150,13 +165,64 @@ async function captureTelegramWebhook(input) {
|
|
|
150
165
|
messageId: message.messageId
|
|
151
166
|
});
|
|
152
167
|
capture.decision = "silent";
|
|
153
|
-
capture.reason =
|
|
168
|
+
capture.reason = automated ? "automated_or_channel_message" : alreadyHandled ? "message_already_handled" : "message_edit";
|
|
169
|
+
if (alreadyHandled) capture.note = "This message already started a turn; edits and retries do not repeat its task.";
|
|
170
|
+
}
|
|
171
|
+
if (alreadyHandled) {
|
|
172
|
+
capture.decision = "silent";
|
|
173
|
+
capture.reason = "message_already_handled";
|
|
174
|
+
}
|
|
175
|
+
if (inbound !== void 0) {
|
|
176
|
+
inbound.edited = edited !== void 0;
|
|
177
|
+
const nextClaim = previousClaim === void 0 ? void 0 : createHash("sha256").update(previousClaim.eventId).digest("hex");
|
|
178
|
+
const claimId = previousClaim === void 0 ? `telegram:${message.chat.id}:${message.messageId}:turn` : `telegram:${message.chat.id}:${message.messageId}:turn:${nextClaim}`;
|
|
179
|
+
let claimed = false;
|
|
180
|
+
let accepted = false;
|
|
181
|
+
let dispatchStarted = false;
|
|
182
|
+
const claimEvent = (reason, status, suffix = "") => ({
|
|
183
|
+
...event,
|
|
184
|
+
eventId: `${claimId}${suffix}`,
|
|
185
|
+
kind: "decision",
|
|
186
|
+
decision: "reply",
|
|
187
|
+
reason,
|
|
188
|
+
status,
|
|
189
|
+
at: new Date().toISOString(),
|
|
190
|
+
text: null,
|
|
191
|
+
attachments: []
|
|
192
|
+
});
|
|
193
|
+
inbound.claimTurn = async () => {
|
|
194
|
+
if (alreadyHandled) return false;
|
|
195
|
+
claimed = await input.archive.append(claimEvent("turn_claimed", "requested")) !== false;
|
|
196
|
+
return claimed;
|
|
197
|
+
};
|
|
198
|
+
inbound.beginDispatch = async () => {
|
|
199
|
+
dispatchStarted = await input.archive.append({
|
|
200
|
+
...claimEvent("turn_dispatch_started", "requested"),
|
|
201
|
+
eventId: `telegram:${message.chat.id}:${message.messageId}:dispatch`
|
|
202
|
+
}) === true;
|
|
203
|
+
return dispatchStarted;
|
|
204
|
+
};
|
|
205
|
+
inbound.finishClaim = async (delivered) => {
|
|
206
|
+
if ((accepted || dispatchStarted) && !delivered) return;
|
|
207
|
+
accepted ||= delivered;
|
|
208
|
+
if (claimed) {
|
|
209
|
+
await input.archive.append(claimEvent(delivered ? "turn_admitted" : "turn_released", delivered ? "completed" : "failed", delivered ? ":admitted" : ":released"));
|
|
210
|
+
claimed = false;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
154
213
|
}
|
|
155
214
|
let response;
|
|
156
215
|
try {
|
|
157
|
-
|
|
216
|
+
const routeEdit = edited !== void 0 && !automated && !alreadyHandled;
|
|
217
|
+
if (routeEdit) capture.reason = void 0;
|
|
218
|
+
response = await input.next(routeEdit ? new Request(input.request, { body: JSON.stringify({
|
|
219
|
+
...update,
|
|
220
|
+
edited_message: void 0,
|
|
221
|
+
message: messagePayload
|
|
222
|
+
}) }) : alreadyHandled ? new Request(input.request, { body: JSON.stringify({ update_id: update.update_id }) }) : void 0);
|
|
158
223
|
} catch (error) {
|
|
159
224
|
try {
|
|
225
|
+
await inbound?.finishClaim?.(false);
|
|
160
226
|
await input.archive.append({
|
|
161
227
|
...event,
|
|
162
228
|
eventId: `${event.eventId}:decision`,
|
|
@@ -174,6 +240,7 @@ async function captureTelegramWebhook(input) {
|
|
|
174
240
|
}
|
|
175
241
|
throw error;
|
|
176
242
|
}
|
|
243
|
+
await inbound?.finishClaim?.(false);
|
|
177
244
|
await input.archive.append({
|
|
178
245
|
...event,
|
|
179
246
|
eventId: `${event.eventId}:decision`,
|
|
@@ -192,6 +259,7 @@ function recordGateDecision(reason, decision = "silent") {
|
|
|
192
259
|
if (capture !== void 0) {
|
|
193
260
|
capture.reason = reason;
|
|
194
261
|
capture.decision = decision;
|
|
262
|
+
capture.note = void 0;
|
|
195
263
|
}
|
|
196
264
|
}
|
|
197
265
|
function recordRouteDecision(route, addressed, policy, audit) {
|
|
@@ -295,15 +363,31 @@ function conversationIdentities(identities) {
|
|
|
295
363
|
};
|
|
296
364
|
}
|
|
297
365
|
function telegramConversationCapture(input) {
|
|
366
|
+
const processing = new Map();
|
|
298
367
|
return async (request, next) => {
|
|
299
368
|
if (!input.verify(request)) return next();
|
|
300
369
|
try {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
370
|
+
const update = record(await request.clone().json().catch(() => null));
|
|
371
|
+
const payload = record(update.edited_message ?? update.message);
|
|
372
|
+
const chatId = id(record(payload.chat).id);
|
|
373
|
+
const messageId = id(payload.message_id);
|
|
374
|
+
const key = chatId === null || messageId === null ? void 0 : `${chatId}:${messageId}`;
|
|
375
|
+
const previous = key === void 0 ? void 0 : processing.get(key);
|
|
376
|
+
const task = (async () => {
|
|
377
|
+
await previous?.catch(() => void 0);
|
|
378
|
+
return captureTelegramWebhook({
|
|
379
|
+
...input,
|
|
380
|
+
request,
|
|
381
|
+
identities: input.identities(),
|
|
382
|
+
next
|
|
383
|
+
});
|
|
384
|
+
})();
|
|
385
|
+
if (key !== void 0) processing.set(key, task);
|
|
386
|
+
try {
|
|
387
|
+
return await task;
|
|
388
|
+
} finally {
|
|
389
|
+
if (key !== void 0 && processing.get(key) === task) processing.delete(key);
|
|
390
|
+
}
|
|
307
391
|
} catch (error) {
|
|
308
392
|
console.error("[segmentstream] conversation capture failed", { error });
|
|
309
393
|
return new Response("conversation storage unavailable", { status: 503 });
|
|
@@ -15,7 +15,7 @@ export declare function customerCopy(locale?: string): {
|
|
|
15
15
|
export declare function isTechnicalText(text: string): boolean;
|
|
16
16
|
/** Original failures stay in operator diagnostics, joined to the public notice by a short reference. */
|
|
17
17
|
export declare function reportTelegramError(operation: string, error: unknown, metadata?: Record<string, unknown>, secrets?: ReadonlySet<string>): string;
|
|
18
|
-
export declare function failureText(errorId: string, kind?: "failed" | "recovery" | "attachment" | "authorization" | "response", locale?: string): string;
|
|
18
|
+
export declare function failureText(errorId: string, kind?: "failed" | "cancelled" | "recovery" | "attachment" | "authorization" | "response", locale?: string): string;
|
|
19
19
|
export declare function customerText(text: string, locale?: string): string;
|
|
20
20
|
/** Check what Telegram will display, including hidden link destinations, after removing markup. */
|
|
21
21
|
export declare function customerMessage(text: string, locale?: string): TelegramText;
|