@segmentstream/agent 0.26.1 → 0.28.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/README.md +41 -43
- package/dist/_chunks/{build-metadata-D_Gqlb5f.mjs → build-metadata-CMmCRD8l.mjs} +1 -1
- package/dist/extension/config.d.ts +11 -12
- package/dist/extension/config.mjs +20 -37
- package/dist/extension/extension.d.ts +2 -2
- package/dist/extension/extension.mjs +3 -3
- package/dist/extension/hooks/chat-context.d.ts +3 -0
- package/dist/extension/hooks/chat-context.mjs +10 -0
- package/dist/extension/hooks/sandbox.d.ts +2 -3
- package/dist/extension/instructions/conversations.d.ts +3 -2
- package/dist/extension/instructions/conversations.mjs +7 -11
- package/dist/extension/instructions.md +18 -0
- package/dist/extension/lib/build-metadata.mjs +1 -1
- package/dist/extension/lib/chat-context.d.ts +108 -0
- package/dist/extension/lib/chat-context.mjs +217 -0
- package/dist/extension/lib/conversation-runtime.d.ts +1 -1
- package/dist/extension/lib/conversation-runtime.mjs +1 -23
- package/dist/extension/lib/conversations.d.ts +10 -6
- package/dist/extension/lib/conversations.mjs +28 -42
- package/dist/extension/lib/group-routing/addressing.d.ts +18 -0
- package/dist/extension/lib/group-routing/addressing.mjs +108 -0
- package/dist/extension/lib/group-routing/burst.d.ts +1 -1
- package/dist/extension/lib/group-routing/burst.mjs +1 -1
- package/dist/extension/lib/group-routing/conversation-prompt.d.ts +5 -2
- package/dist/extension/lib/group-routing/conversation-prompt.mjs +26 -6
- package/dist/extension/lib/group-routing/conversation-router.d.ts +8 -16
- package/dist/extension/lib/group-routing/conversation-router.mjs +49 -93
- package/dist/extension/lib/group-routing/dequeue.mjs +1 -1
- package/dist/extension/lib/group-routing/engagement.d.ts +1 -1
- package/dist/extension/lib/group-routing/{observed.d.ts → messages.d.ts} +9 -33
- package/dist/extension/lib/group-routing/messages.mjs +38 -0
- package/dist/extension/lib/group-routing/policy.d.ts +13 -9
- package/dist/extension/lib/group-routing/policy.mjs +35 -25
- package/dist/extension/lib/group-routing/router.d.ts +3 -11
- package/dist/extension/lib/group-routing/router.mjs +11 -35
- package/dist/extension/lib/group-routing/routing-context.d.ts +6 -2
- package/dist/extension/lib/group-routing/routing-context.mjs +12 -11
- package/dist/extension/lib/telegram/adapter.d.ts +1 -3
- package/dist/extension/lib/telegram/adapter.mjs +9 -18
- package/dist/extension/lib/telegram/confirmations.d.ts +3 -3
- package/dist/extension/lib/telegram/confirmations.mjs +3 -25
- package/dist/extension/lib/telegram/conversations.d.ts +14 -3
- package/dist/extension/lib/telegram/conversations.mjs +16 -37
- package/dist/extension/lib/telegram/delivery-diagnostics.d.ts +4 -1
- package/dist/extension/lib/telegram/delivery-diagnostics.mjs +9 -3
- package/dist/extension/lib/telegram/health.mjs +1 -1
- package/dist/extension/lib/telegram/inbound.d.ts +14 -14
- package/dist/extension/lib/telegram/inbound.mjs +73 -78
- package/dist/extension/lib/telegram/lifecycle.mjs +1 -1
- package/dist/extension/lib/telegram/{observed.d.ts → message-metadata.d.ts} +2 -6
- package/dist/extension/lib/telegram/{observed.mjs → message-metadata.mjs} +1 -14
- package/dist/extension/lib/telegram/routing-context.d.ts +4 -3
- package/dist/extension/lib/telegram/routing-context.mjs +2 -2
- package/dist/extension/lib/telegram/sessions.d.ts +5 -52
- package/dist/extension/lib/telegram/sessions.mjs +1 -188
- package/dist/extension/lib/telegram/signals.mjs +1 -1
- package/dist/extension/lib/telegram/silent.d.ts +5 -6
- package/dist/extension/lib/telegram/silent.mjs +4 -31
- package/package.json +1 -1
- package/dist/extension/lib/group-routing/conversation.d.ts +0 -46
- package/dist/extension/lib/group-routing/conversation.mjs +0 -269
- package/dist/extension/lib/group-routing/observed.mjs +0 -103
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type StructuredInbound, type StructuredMessage, type StructuredResult } from "./structured.js";
|
|
2
|
-
import type { RecentMessage } from "../group-routing/
|
|
2
|
+
import type { RecentMessage } from "../group-routing/messages.js";
|
|
3
3
|
import { type GroupPolicy } from "../group-routing/policy.js";
|
|
4
4
|
import type { TelegramSettings } from "../../config.js";
|
|
5
5
|
import { attachmentLifecycle } from "./lifecycle.js";
|
|
@@ -12,9 +12,8 @@ import { type AgentChats } from "./chats.js";
|
|
|
12
12
|
import type { InboundRequest } from "./inbound-request.js";
|
|
13
13
|
import type { ImageDescriber } from "./images.js";
|
|
14
14
|
import type { MediaPlan } from "./media-policy.js";
|
|
15
|
-
import { type ObservedMessages } from "./observed.js";
|
|
16
15
|
import { type RouteDecider } from "./router.js";
|
|
17
|
-
import { type HistoryReader
|
|
16
|
+
import { type HistoryReader } from "./sessions.js";
|
|
18
17
|
import { type Transcriber } from "./voice.js";
|
|
19
18
|
import { type AttachmentDescriber } from "./attachment-description.js";
|
|
20
19
|
import type { PdfTextExtractor } from "./pdf-text.js";
|
|
@@ -30,16 +29,18 @@ import type { PdfTextExtractor } from "./pdf-text.js";
|
|
|
30
29
|
* 2. Voice becomes text, so the router and the agent read what was said.
|
|
31
30
|
* 3. The router answers or observes. Group files follow the same addressing rules as text;
|
|
32
31
|
* captionless uploads use smart routing and recent agent requests. Files are stored after the
|
|
33
|
-
* registry knows the sender. Observing starts no session or typing indication;
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
32
|
+
* registry knows the sender. Observing starts no session or typing indication; the
|
|
33
|
+
* conversation archive keeps the message and its stored files' references for routing, the
|
|
34
|
+
* next turn's chat context and the native images that turn carries.
|
|
35
|
+
* 4. An answered message continues the session its address names under the configured scope; eve
|
|
36
|
+
* compacts and expires sessions itself.
|
|
37
37
|
* 5. An answered message reads the stored attachments in place of the Telegram files.
|
|
38
|
-
* 6. An answered message dispatches as the paired member, with
|
|
39
|
-
*
|
|
40
|
-
* an `<attachments>` block naming its stored files
|
|
41
|
-
*
|
|
42
|
-
* its
|
|
38
|
+
* 6. An answered message dispatches as the paired member, with only its own facts as context: a
|
|
39
|
+
* `<speaker>` block naming the member, their roles and the message they replied to, its
|
|
40
|
+
* `<message_audience>` and an `<attachments>` block naming its stored files. The chat around
|
|
41
|
+
* it, observed messages and the registry's `<chat>` guidance, reaches the session once through
|
|
42
|
+
* its chat context (`lib/chat-context.ts`). An answered group message waits for its chat's
|
|
43
|
+
* report first, so that a chat new to the process already carries its guidance.
|
|
43
44
|
*/
|
|
44
45
|
export interface InboundDependencies {
|
|
45
46
|
readonly structured?: {
|
|
@@ -49,12 +50,12 @@ export interface InboundDependencies {
|
|
|
49
50
|
readonly policy: TelegramPolicy;
|
|
50
51
|
readonly decisionModel?: string;
|
|
51
52
|
readonly groups?: GroupPolicy;
|
|
53
|
+
/** The chat's or forum topic's recent messages from the conversation archive, oldest first. */
|
|
52
54
|
readonly recentConversation?: (chatId: string, threadId?: string) => Promise<ReadonlyArray<RecentMessage>>;
|
|
53
55
|
readonly signalSettings?: TelegramSettings["groups"];
|
|
54
56
|
readonly lifecycle?: (principal: import("eve/context").SessionAuthContext, memberId: string, message: TelegramMessage) => ReturnType<typeof attachmentLifecycle>;
|
|
55
57
|
readonly admission: () => AdmissionSettings;
|
|
56
58
|
readonly sessions: () => SessionSettings;
|
|
57
|
-
readonly sessionPolicy: SessionPolicy;
|
|
58
59
|
readonly sttModel: string | false;
|
|
59
60
|
readonly attachmentsModel: string | false;
|
|
60
61
|
readonly describeAttachment: AttachmentDescriber;
|
|
@@ -73,7 +74,6 @@ export interface InboundDependencies {
|
|
|
73
74
|
}) => Promise<Uint8Array>;
|
|
74
75
|
/** The attachment store; without it, files reach the turn as eve's Telegram file parts. */
|
|
75
76
|
readonly attachments?: AgentAttachments;
|
|
76
|
-
readonly observed: ObservedMessages;
|
|
77
77
|
/** Recent session events for smart routing. */
|
|
78
78
|
readonly readHistory: HistoryReader;
|
|
79
79
|
readonly chats: AgentChats;
|
|
@@ -9,29 +9,28 @@ import { admitMessage, localizedText } from "../admission.mjs";
|
|
|
9
9
|
import { d as telegramRuntimeSource, n as conversationLanguage, o as telegramPrincipal, u as runtimeEnvelope } from "../../../_chunks/principal-D99Lleae.mjs";
|
|
10
10
|
import { p as reportTelegramError } from "../../../_chunks/inbound-request-D1r0FkDI.mjs";
|
|
11
11
|
import { addressingReason, conversationKey, otherRecipient } from "../group-routing/signals.mjs";
|
|
12
|
-
import { conversationAddressing
|
|
12
|
+
import { conversationAddressing } from "../group-routing/addressing.mjs";
|
|
13
13
|
import { questionInvitation } from "../group-routing/engagement.mjs";
|
|
14
|
-
import {
|
|
15
|
-
import { observedContext } from "../group-routing/observed.mjs";
|
|
14
|
+
import { routeMessage } from "../group-routing/router.mjs";
|
|
16
15
|
import { debounceBurst, routingBurst } from "../group-routing/burst.mjs";
|
|
17
16
|
import { WorkspaceActionAlreadyDispatched } from "./workspace-action.mjs";
|
|
18
17
|
import { structuredMessage } from "./structured.mjs";
|
|
19
|
-
import {
|
|
18
|
+
import { formatAddress, messageAddress, scopedAddress } from "./sessions.mjs";
|
|
20
19
|
import { AttachmentLifecycleNotPermitted } from "./lifecycle.mjs";
|
|
21
20
|
import { MAXIMUM_NATIVE_IMAGE_BYTES } from "./images.mjs";
|
|
22
21
|
import { MAXIMUM_ROUTING_ATTACHMENT_BYTES, describeRoutingAttachment, routingAttachmentDeadline } from "./attachment-description.mjs";
|
|
23
22
|
import { attachmentUrl } from "../attachments.mjs";
|
|
24
23
|
import { contentShape, messageContent } from "./content.mjs";
|
|
25
24
|
import { messageAudience } from "./audience.mjs";
|
|
26
|
-
import { attachmentKind, messageMetadata } from "./
|
|
27
|
-
import {
|
|
25
|
+
import { attachmentKind, messageMetadata } from "./message-metadata.mjs";
|
|
26
|
+
import { recordGateDecision, recordInboundContent, recordRouteDecision } from "./conversations.mjs";
|
|
28
27
|
import { answerMarker } from "../questions.mjs";
|
|
29
28
|
import { questionAddress, questionFromHistory } from "./confirmations.mjs";
|
|
30
|
-
import { routingContext } from "./routing-context.mjs";
|
|
31
|
-
import { recordGateDecision, recordInboundContent, recordRouteDecision } from "./conversations.mjs";
|
|
32
29
|
import { chatContext } from "./chats.mjs";
|
|
30
|
+
import { telegramSignals } from "./signals.mjs";
|
|
33
31
|
import { attachmentsContext, inboundAttachments, stageFiles } from "./files.mjs";
|
|
34
32
|
import { heldApprovals, releaseHeldApproval } from "./held-approvals.mjs";
|
|
33
|
+
import { routingContext } from "./routing-context.mjs";
|
|
35
34
|
import { voiceOf, voiceText } from "./voice.mjs";
|
|
36
35
|
const MAXIMUM_QUOTE_LENGTH = 500;
|
|
37
36
|
function buttonTo(text, url) {
|
|
@@ -68,6 +67,10 @@ function observedMessage(message, text, member, media, bot) {
|
|
|
68
67
|
...media ? { media } : {}
|
|
69
68
|
};
|
|
70
69
|
}
|
|
70
|
+
function observedImages(recent, current) {
|
|
71
|
+
const answered = recent.findLastIndex((message) => message.admitted === true);
|
|
72
|
+
return recent.slice(answered + 1).filter((message) => !("role" in message) && message.messageId !== current).flatMap((message) => message.images ?? []);
|
|
73
|
+
}
|
|
71
74
|
function isRecord(value) {
|
|
72
75
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
73
76
|
}
|
|
@@ -268,7 +271,19 @@ async function handleInboundMessage(input) {
|
|
|
268
271
|
const turn = structured === void 0 && input.request?.structuredContext === void 0 && addressed !== void 0 && hasContent && (addressed !== "reply-to-bot" || elsewhere === void 0);
|
|
269
272
|
const lookup = await resolve(turn);
|
|
270
273
|
const group = isGroupChat(sent);
|
|
271
|
-
const
|
|
274
|
+
const freshConversation = async () => await dependencies.recentConversation?.(sent.chat.id, sent.messageThreadId === void 0 ? void 0 : String(sent.messageThreadId)) ?? [];
|
|
275
|
+
let archived;
|
|
276
|
+
const recentConversation = () => archived ??= freshConversation();
|
|
277
|
+
const languageOf = async (current) => {
|
|
278
|
+
const own = conversationLanguage([current]);
|
|
279
|
+
if (own !== void 0) return own;
|
|
280
|
+
try {
|
|
281
|
+
return conversationLanguage((await recentConversation()).map((message) => message.text).reverse());
|
|
282
|
+
} catch (error) {
|
|
283
|
+
reportTelegramError("conversation_language_unavailable", error, { chatId: sent.chat.id });
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
};
|
|
272
287
|
const report = group ? dependencies.chats.seen({
|
|
273
288
|
chatId: sent.chat.id,
|
|
274
289
|
title: sent.chat.title ?? "",
|
|
@@ -349,9 +364,9 @@ async function handleInboundMessage(input) {
|
|
|
349
364
|
async function observeRaw() {
|
|
350
365
|
await recordInboundContent({
|
|
351
366
|
text,
|
|
352
|
-
decoded
|
|
367
|
+
decoded,
|
|
368
|
+
author: authorOf(sent.from, void 0)
|
|
353
369
|
});
|
|
354
|
-
if (group) dependencies.observed.add(chat, observedMessage(sent, text || "[attachment]", void 0, void 0, bot));
|
|
355
370
|
}
|
|
356
371
|
if (lookup.status !== "existing" && lookup.status !== "created") {
|
|
357
372
|
const configuredGroupPairing = lookup.status === "pairing_required" && localizedText(dependencies.admission().replies.groupPairingRequired, sent.from.languageCode) !== void 0;
|
|
@@ -360,6 +375,10 @@ async function handleInboundMessage(input) {
|
|
|
360
375
|
return null;
|
|
361
376
|
}
|
|
362
377
|
let member = lookup.member;
|
|
378
|
+
const recordContent = (content) => recordInboundContent({
|
|
379
|
+
...content,
|
|
380
|
+
author: authorOf(sent.from, member)
|
|
381
|
+
});
|
|
363
382
|
if (input.request?.structuredContext !== void 0 && input.request.structuredContext.memberId !== member.id) {
|
|
364
383
|
recordGateDecision("structured_context_actor_changed");
|
|
365
384
|
return null;
|
|
@@ -393,7 +412,7 @@ async function handleInboundMessage(input) {
|
|
|
393
412
|
const principal = telegramPrincipal({
|
|
394
413
|
message: sent,
|
|
395
414
|
member,
|
|
396
|
-
conversationLanguage:
|
|
415
|
+
conversationLanguage: await languageOf(sent.text || sent.caption)
|
|
397
416
|
});
|
|
398
417
|
input.request.lifecycle = dependencies.lifecycle(principal, member.id, sent);
|
|
399
418
|
await context.telegram.startTyping();
|
|
@@ -481,8 +500,7 @@ async function handleInboundMessage(input) {
|
|
|
481
500
|
bot?.firstName,
|
|
482
501
|
bot?.displayName
|
|
483
502
|
].filter((n) => !!n)];
|
|
484
|
-
|
|
485
|
-
let conversationRecent = conversationPolicy ? await freshConversation() : dependencies.observed.recent(chat);
|
|
503
|
+
let conversationRecent = conversationPolicy ? await recentConversation() : [];
|
|
486
504
|
if (conversationPolicy && input.request && input.request.lifecycleKey === void 0 && input.request.structuredContext === void 0) {
|
|
487
505
|
address = topicAddress;
|
|
488
506
|
input.request.address = topicAddress;
|
|
@@ -497,26 +515,16 @@ async function handleInboundMessage(input) {
|
|
|
497
515
|
});
|
|
498
516
|
const questionAge = Date.parse(sentAt(sent)) - Date.parse(asked?.meta.at ?? "");
|
|
499
517
|
if (answering && addressed === void 0 && !questionInvitation(asked?.meta.at, sentAt(sent), groups.smart.engagement.openQuestionMinutes)) answering = false;
|
|
500
|
-
if (conversationPolicy && answering) {
|
|
501
|
-
if (!conversationState({
|
|
502
|
-
signals: policySignals,
|
|
503
|
-
recent: conversationRecent,
|
|
504
|
-
policy: groups,
|
|
505
|
-
agentNames: agentAliases,
|
|
506
|
-
pendingQuestionAt: asked?.meta.at
|
|
507
|
-
}).pending_question_live) answering = false;
|
|
508
|
-
}
|
|
509
518
|
const recipientAnswer = !conversationPolicy && answering && (groups.smart.engagement.policy === "engaged" || questionAge >= 0 && questionAge <= groups.smart.engagement.followUpMinutes * 6e4) && audience.replyTo?.kind === "bot" && isRecipientAnswer(audience);
|
|
510
519
|
if (!conversationPolicy && answering && pending !== void 0 && input.request !== void 0) {
|
|
511
520
|
address = pending.address || (questionSession === void 0 ? topicAddress : memberQuestionAddress);
|
|
512
521
|
input.request.address = address;
|
|
513
522
|
}
|
|
514
523
|
if (input.request?.lifecycleKey === void 0 && input.request?.structuredContext === void 0 && group && (addressed === void 0 && !answering && groups.mode === "mention" || !conversationPolicy && elsewhere !== void 0 && (addressed === void 0 || addressed === "reply-to-bot" && !recipientAnswer))) {
|
|
515
|
-
await
|
|
516
|
-
text,
|
|
524
|
+
await recordContent({
|
|
525
|
+
text: text || (structured === void 0 ? text : `[${structured.kind}]`),
|
|
517
526
|
decoded
|
|
518
527
|
});
|
|
519
|
-
dependencies.observed.add(chat, observedMessage(sent, text || (structured === void 0 ? "[attachment]" : `[${structured.kind}]`), member, void 0, bot));
|
|
520
528
|
recordRouteDecision("observe", addressed, groups.mode, {
|
|
521
529
|
reason: elsewhere === void 0 ? "mention_policy" : "other_recipient",
|
|
522
530
|
note: elsewhere ?? "Mention policy observes unaddressed messages."
|
|
@@ -531,7 +539,12 @@ async function handleInboundMessage(input) {
|
|
|
531
539
|
await observeRaw();
|
|
532
540
|
return null;
|
|
533
541
|
}
|
|
534
|
-
|
|
542
|
+
if (group && !conversationPolicy) conversationRecent = (await recentConversation()).filter((message) => message.messageId !== sent.messageId).map(({ signedAuthor, ...message }) => signedAuthor === void 0 ? message : {
|
|
543
|
+
...message,
|
|
544
|
+
author: signedAuthor
|
|
545
|
+
});
|
|
546
|
+
const carried = observedImages(conversationRecent, sent.messageId);
|
|
547
|
+
const mediaPlan = voice !== void 0 || sent.attachments.length > 0 || carried.length > 0 ? await dependencies.mediaPlan?.(member, owner?.id) : void 0;
|
|
535
548
|
if (voice !== void 0 && !group) await transcribeVoice(false);
|
|
536
549
|
if (voice !== void 0 && mediaPlan?.audio) notes.push("[The chat model supports audio, but this host cannot deliver native audio. The configured speech-to-text fallback was used if enabled.]");
|
|
537
550
|
if (structured !== void 0) {
|
|
@@ -575,7 +588,7 @@ async function handleInboundMessage(input) {
|
|
|
575
588
|
address,
|
|
576
589
|
operations,
|
|
577
590
|
read: dependencies.history ?? dependencies.readHistory,
|
|
578
|
-
remembered:
|
|
591
|
+
remembered: conversationRecent,
|
|
579
592
|
at: sentAt(sent),
|
|
580
593
|
questionHistory,
|
|
581
594
|
questionSessionId: owner?.id
|
|
@@ -625,7 +638,7 @@ async function handleInboundMessage(input) {
|
|
|
625
638
|
};
|
|
626
639
|
const evidence = conversationAddressing(routingSignals, conversationRecent, groups, agentAliases);
|
|
627
640
|
if (!evidence.agent_named && !evidence.reply_to_agent && evidence.human_targets.length === 0) {
|
|
628
|
-
await
|
|
641
|
+
await recordContent({
|
|
629
642
|
text,
|
|
630
643
|
decoded
|
|
631
644
|
});
|
|
@@ -718,7 +731,7 @@ async function handleInboundMessage(input) {
|
|
|
718
731
|
...notes
|
|
719
732
|
].join("\n");
|
|
720
733
|
} else if (voice !== void 0) media = [...attachmentMetadata, ...notes].join("\n");
|
|
721
|
-
await
|
|
734
|
+
await recordContent({
|
|
722
735
|
text,
|
|
723
736
|
decoded,
|
|
724
737
|
...processedMedia === void 0 ? {} : { media: processedMedia }
|
|
@@ -743,11 +756,10 @@ async function handleInboundMessage(input) {
|
|
|
743
756
|
let structuredDispatched = false;
|
|
744
757
|
if (structured !== void 0 && dependencies.structured !== void 0) {
|
|
745
758
|
if (route === "observe" || admissionRefused) {
|
|
746
|
-
await
|
|
759
|
+
await recordContent({
|
|
747
760
|
text,
|
|
748
761
|
decoded
|
|
749
762
|
});
|
|
750
|
-
if (group) dependencies.observed.add(chat, observedMessage(sent, text, member, media, bot));
|
|
751
763
|
return null;
|
|
752
764
|
}
|
|
753
765
|
if (!await admitTurn(true)) return null;
|
|
@@ -813,14 +825,12 @@ async function handleInboundMessage(input) {
|
|
|
813
825
|
note: "The configured workspace action admitted this attachment task."
|
|
814
826
|
});
|
|
815
827
|
if (admissionRefused) {
|
|
816
|
-
await
|
|
828
|
+
await recordContent({
|
|
817
829
|
text,
|
|
818
830
|
decoded
|
|
819
831
|
});
|
|
820
|
-
dependencies.observed.add(chat, observedMessage(sent, text || "[attachment]", member, void 0, bot));
|
|
821
832
|
return null;
|
|
822
833
|
}
|
|
823
|
-
if (group && route === "observe") dependencies.observed.add(chat, observedMessage(sent, text, member, media, bot));
|
|
824
834
|
if (sent.attachments.length > 0) await report;
|
|
825
835
|
let files = input.request?.edited && !input.request.attachmentsChanged && (input.request.previousContent?.decoded.length ?? 0) > 0 || sent.attachments.length === 0 || dependencies.attachments === void 0 || input.request === void 0 ? new Map() : await stageFiles({
|
|
826
836
|
message: sent,
|
|
@@ -937,24 +947,24 @@ async function handleInboundMessage(input) {
|
|
|
937
947
|
if (input.request !== void 0 && (voice !== void 0 || processedMedia !== void 0)) input.request.text = text;
|
|
938
948
|
const retainedMedia = processedMedia ?? ([...files.values()].flatMap((file) => file.status === "stored" && file.description !== void 0 ? [file.description] : []).join("\n") || void 0);
|
|
939
949
|
if (decoded.length === 0) decoded.push(...[...files.values()].flatMap((file) => file.status === "stored" && file.decoded !== void 0 ? [file.decoded] : []));
|
|
940
|
-
await
|
|
950
|
+
await recordContent({
|
|
941
951
|
text,
|
|
942
952
|
decoded,
|
|
943
|
-
...retainedMedia === void 0 ? {} : { media: retainedMedia }
|
|
953
|
+
...retainedMedia === void 0 ? {} : { media: retainedMedia },
|
|
954
|
+
files: [...files.values()].flatMap((file) => file.status === "stored" ? [{
|
|
955
|
+
id: file.attachment.id,
|
|
956
|
+
filename: file.attachment.filename,
|
|
957
|
+
contentType: file.attachment.contentType,
|
|
958
|
+
size: file.attachment.size,
|
|
959
|
+
expiresAt: file.attachment.expiresAt,
|
|
960
|
+
...file.imageInput === "native" ? { image: true } : {}
|
|
961
|
+
}] : [])
|
|
944
962
|
});
|
|
945
|
-
if (route === "observe")
|
|
946
|
-
const images = [...files.values()].flatMap((file) => file.status === "stored" && file.imageInput === "native" ? [file.attachment] : []);
|
|
947
|
-
dependencies.observed.add(chat, {
|
|
948
|
-
...observedMessage(sent, text === "" ? "[attachment]" : text, member, media, bot),
|
|
949
|
-
images
|
|
950
|
-
});
|
|
951
|
-
return null;
|
|
952
|
-
}
|
|
963
|
+
if (route === "observe") return null;
|
|
953
964
|
if (!structuredDispatched && input.request?.claimTurn !== void 0 && !await input.request.claimTurn()) {
|
|
954
965
|
recordGateDecision("message_already_handled");
|
|
955
966
|
return null;
|
|
956
967
|
}
|
|
957
|
-
let receivingHistory;
|
|
958
968
|
if (operations !== void 0 && dependencies.history !== void 0) {
|
|
959
969
|
let recoverySessionId;
|
|
960
970
|
try {
|
|
@@ -962,7 +972,6 @@ async function handleInboundMessage(input) {
|
|
|
962
972
|
if (session !== void 0) {
|
|
963
973
|
recoverySessionId = session.id;
|
|
964
974
|
const previous = session.id === owner?.id ? questionHistory : await dependencies.history(session.id);
|
|
965
|
-
receivingHistory = previous;
|
|
966
975
|
const recovery = heldApprovals(previous).length === 0 ? void 0 : await releaseHeldApproval({
|
|
967
976
|
events: await dependencies.history(session.id),
|
|
968
977
|
now: Date.now(),
|
|
@@ -996,24 +1005,6 @@ async function handleInboundMessage(input) {
|
|
|
996
1005
|
notes.push(`<pending_question>\nTask: ${pending.task}\nQuestion: ${pending.question}\nUse this member's answer to continue the pending task with their current permissions.
|
|
997
1006
|
</pending_question>`);
|
|
998
1007
|
}
|
|
999
|
-
if (operations !== void 0) {
|
|
1000
|
-
const newest = dependencies.observed.list(chat).at(-1);
|
|
1001
|
-
if (await dependencies.sessionPolicy.apply({
|
|
1002
|
-
address,
|
|
1003
|
-
operations,
|
|
1004
|
-
...newest === void 0 ? {} : { activeAt: Date.parse(newest.at) || 0 }
|
|
1005
|
-
}) === "cleared") {
|
|
1006
|
-
dependencies.observed.clear(chat);
|
|
1007
|
-
question = void 0;
|
|
1008
|
-
if (answering && input.request !== void 0) {
|
|
1009
|
-
answering = false;
|
|
1010
|
-
input.request.text = text;
|
|
1011
|
-
const questionNote = notes.findIndex((note) => note.startsWith("<pending_question>"));
|
|
1012
|
-
if (questionNote !== -1) notes.splice(questionNote, 1);
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
dependencies.sessionPolicy.delivered(address);
|
|
1016
|
-
}
|
|
1017
1008
|
await context.telegram.startTyping();
|
|
1018
1009
|
await report;
|
|
1019
1010
|
if (input.request !== void 0 && files.size > 0) input.request.files = files;
|
|
@@ -1021,7 +1012,7 @@ async function handleInboundMessage(input) {
|
|
|
1021
1012
|
const currentImages = [...files.values()].flatMap((file) => file.status === "stored" && file.imageInput === "native" ? [file.attachment] : []);
|
|
1022
1013
|
let remaining = Math.max(0, MAXIMUM_NATIVE_IMAGE_BYTES - currentImages.reduce((sum, file) => sum + file.size, 0));
|
|
1023
1014
|
let count = currentImages.length;
|
|
1024
|
-
input.request.extraFiles =
|
|
1015
|
+
input.request.extraFiles = carried.flatMap((image) => {
|
|
1025
1016
|
if (Date.parse(image.expiresAt) <= Date.now() || image.size > remaining || count >= 5) {
|
|
1026
1017
|
notes.push(`[Earlier image ${JSON.stringify(image.filename)} omitted: expired or native context limit.]`);
|
|
1027
1018
|
return [];
|
|
@@ -1038,16 +1029,15 @@ async function handleInboundMessage(input) {
|
|
|
1038
1029
|
mediaType: image.contentType,
|
|
1039
1030
|
filename: image.filename
|
|
1040
1031
|
}];
|
|
1041
|
-
})
|
|
1032
|
+
});
|
|
1042
1033
|
}
|
|
1043
|
-
const language =
|
|
1044
|
-
const
|
|
1034
|
+
const language = await languageOf(text);
|
|
1035
|
+
const confirmationReply = (group ? await conversation() : void 0)?.recent.some((entry) => "role" in entry && entry.confirmation !== void 0 && entry.messageId === audience.replyTo?.messageId) === true;
|
|
1045
1036
|
const beginTurnDispatch = structuredDispatched ? input.request?.beginStructuredContinuation : input.request?.beginDispatch;
|
|
1046
1037
|
if (beginTurnDispatch !== void 0 && !await beginTurnDispatch()) {
|
|
1047
1038
|
recordGateDecision("message_already_handled");
|
|
1048
1039
|
return null;
|
|
1049
1040
|
}
|
|
1050
|
-
dependencies.observed.remember(chat, observedMessage(sent, text, member, retainedMedia, bot));
|
|
1051
1041
|
let principal = telegramPrincipal({
|
|
1052
1042
|
message: {
|
|
1053
1043
|
...sent,
|
|
@@ -1056,6 +1046,14 @@ async function handleInboundMessage(input) {
|
|
|
1056
1046
|
member,
|
|
1057
1047
|
conversationLanguage: language
|
|
1058
1048
|
});
|
|
1049
|
+
const [guidance] = group ? chatContext(dependencies.chats.chat(sent.chat.id)) : [];
|
|
1050
|
+
if (guidance !== void 0) principal = {
|
|
1051
|
+
...principal,
|
|
1052
|
+
attributes: {
|
|
1053
|
+
...principal.attributes,
|
|
1054
|
+
chatGuidance: guidance
|
|
1055
|
+
}
|
|
1056
|
+
};
|
|
1059
1057
|
if (conversationPolicy && input.request?.lifecycleKey === void 0 && input.request?.structuredContext === void 0) principal = {
|
|
1060
1058
|
...principal,
|
|
1061
1059
|
attributes: {
|
|
@@ -1109,27 +1107,24 @@ async function handleInboundMessage(input) {
|
|
|
1109
1107
|
},
|
|
1110
1108
|
context: [
|
|
1111
1109
|
...input.request?.lifecycleContext === void 0 ? [] : [input.request.lifecycleContext],
|
|
1112
|
-
...observedContext(dependencies.observed.take(chat).slice(-groups.smart.context.messages), sharedContext),
|
|
1113
|
-
...group ? chatContext(dependencies.chats.chat(sent.chat.id)) : [],
|
|
1114
1110
|
speakerBlock({
|
|
1115
1111
|
message: sent,
|
|
1116
1112
|
member,
|
|
1117
1113
|
bot,
|
|
1118
|
-
omitQuote:
|
|
1114
|
+
omitQuote: confirmationReply
|
|
1119
1115
|
}),
|
|
1120
1116
|
"<message_audience>\n" + JSON.stringify({
|
|
1121
1117
|
bot_addressed: addressed !== void 0 || answering,
|
|
1122
1118
|
bot_tagged: audience.botTagged,
|
|
1123
|
-
reply_target:
|
|
1124
|
-
tagged_users: audience.mentions
|
|
1125
|
-
|
|
1126
|
-
|
|
1119
|
+
reply_target: confirmationReply && audience.replyTo !== null ? Object.fromEntries(Object.entries(audience.replyTo).filter(([key]) => key !== "text")) : audience.replyTo,
|
|
1120
|
+
tagged_users: audience.mentions,
|
|
1121
|
+
...conversationPolicy ? { engagement_policy: "conversation" } : {}
|
|
1122
|
+
}) + "\n</message_audience>",
|
|
1127
1123
|
...attachmentsContext({
|
|
1128
1124
|
files,
|
|
1129
1125
|
group
|
|
1130
1126
|
}),
|
|
1131
1127
|
...processedMedia === void 0 ? [] : [processedMedia],
|
|
1132
|
-
...conversationPolicy ? ["If this member declines your involvement or closes the conversation, output exactly [SILENT]. Do not acknowledge the rejection or perform actions."] : [],
|
|
1133
1128
|
...notes
|
|
1134
1129
|
]
|
|
1135
1130
|
};
|
|
@@ -10,7 +10,7 @@ import { n as currentInboundRequest } from "../../../_chunks/inbound-request-D1r
|
|
|
10
10
|
import { member } from "../actor.mjs";
|
|
11
11
|
import { needsConfirmation } from "../confirmations.mjs";
|
|
12
12
|
import { OntologyActionNotPermitted, actionToolName, ontologyTools } from "../ontology-tools.mjs";
|
|
13
|
-
import { t as agentBuild } from "../../../_chunks/build-metadata-
|
|
13
|
+
import { t as agentBuild } from "../../../_chunks/build-metadata-CMmCRD8l.mjs";
|
|
14
14
|
var AttachmentLifecycleNotPermitted = class extends Error {
|
|
15
15
|
code = "attachment_lifecycle_not_permitted";
|
|
16
16
|
};
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import { type ObservedMessages } from "../group-routing/observed.js";
|
|
2
1
|
import type { TelegramMessage } from "eve/channels/telegram";
|
|
3
2
|
import type { BotIdentity } from "./router.js";
|
|
4
|
-
import type { ObservedMessage } from "../group-routing/
|
|
5
|
-
export * from "../group-routing/observed.js";
|
|
3
|
+
import type { ObservedMessage } from "../group-routing/messages.js";
|
|
6
4
|
/** Eve represents non-image media as documents; retain the actual Telegram kind in context. */
|
|
7
5
|
export declare function attachmentKind(message: TelegramMessage, file: {
|
|
8
6
|
readonly fileId: string;
|
|
9
7
|
readonly kind: string;
|
|
10
8
|
}): string;
|
|
11
|
-
/** Bounded provider structure,
|
|
9
|
+
/** Bounded provider structure, archived with each message for routing. No file grants. */
|
|
12
10
|
export declare function messageMetadata(message: TelegramMessage, bot?: BotIdentity): Pick<ObservedMessage, "invitation" | "kind" | "username" | "replyTo" | "taggedUsers" | "attachmentKinds" | "hasCaption" | "revision">;
|
|
13
|
-
/** Compatibility boundary for callers that used Telegram's former unqualified ring keys. */
|
|
14
|
-
export declare function observedMessages(capacity?: number): ObservedMessages;
|
|
@@ -4,7 +4,6 @@ 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 { OBSERVED_MESSAGES, agentMessage, currentConfirmationPrompt, observedChatKey, observedContext, observedMessages as observedMessages$1, withConfirmationPrompt } from "../group-routing/observed.mjs";
|
|
8
7
|
import { messageAudience } from "./audience.mjs";
|
|
9
8
|
const PROVIDER_MEDIA_KINDS = [
|
|
10
9
|
"voice",
|
|
@@ -49,16 +48,4 @@ function messageMetadata(message, bot) {
|
|
|
49
48
|
}
|
|
50
49
|
};
|
|
51
50
|
}
|
|
52
|
-
|
|
53
|
-
const ring = observedMessages$1(capacity);
|
|
54
|
-
const key = (value) => /^[a-z][a-z0-9_-]*:/u.test(value) ? value : `telegram:${value.replace(/:/u, "/")}`;
|
|
55
|
-
return {
|
|
56
|
-
add: (chat, message) => ring.add(key(chat), message),
|
|
57
|
-
remember: (chat, message) => ring.remember(key(chat), message),
|
|
58
|
-
list: (chat) => ring.list(key(chat)),
|
|
59
|
-
recent: (chat) => ring.recent(key(chat)),
|
|
60
|
-
take: (chat) => ring.take(key(chat)),
|
|
61
|
-
clear: (chat) => ring.clear(key(chat))
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
export { OBSERVED_MESSAGES, agentMessage, attachmentKind, currentConfirmationPrompt, messageMetadata, observedChatKey, observedContext, observedMessages, withConfirmationPrompt };
|
|
51
|
+
export { attachmentKind, messageMetadata };
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { type RecentMessage, type PendingConfirmation } from "
|
|
1
|
+
import { type RecentMessage, type PendingConfirmation } from "../group-routing/messages.js";
|
|
2
2
|
import type { HistoryReader, RecordedEvent, SessionOperations } from "./sessions.js";
|
|
3
3
|
import { type PendingQuestion } from "../questions.js";
|
|
4
4
|
export { archivedRoutingMessages } from "../group-routing/routing-context.js";
|
|
5
5
|
/**
|
|
6
|
-
* Recover the agent's questions from the existing session without starting a turn. The
|
|
7
|
-
*
|
|
6
|
+
* Recover the agent's questions from the existing session without starting a turn. The archived
|
|
7
|
+
* messages (`remembered`) supplement this with recipient identities and chatter not written to
|
|
8
|
+
* Eve's stream.
|
|
8
9
|
*/
|
|
9
10
|
export declare function routingContext(input: {
|
|
10
11
|
readonly address: string;
|
|
@@ -5,11 +5,11 @@ const __filename = __eveFileURLToPath(import.meta.url);
|
|
|
5
5
|
__eveDirname(__filename);
|
|
6
6
|
__eveCreateRequire(import.meta.url);
|
|
7
7
|
import "../group-routing/router.mjs";
|
|
8
|
-
import { agentMessage } from "../group-routing/
|
|
8
|
+
import { agentMessage } from "../group-routing/messages.mjs";
|
|
9
9
|
import { archivedRoutingMessages } from "../group-routing/routing-context.mjs";
|
|
10
|
-
import { isSilentReply } from "./silent.mjs";
|
|
11
10
|
import { questionKey } from "../questions.mjs";
|
|
12
11
|
import { questionsFromHistory, routingConfirmationState, routingConfirmations } from "./confirmations.mjs";
|
|
12
|
+
import { isSilentReply } from "./silent.mjs";
|
|
13
13
|
const DELIVERY_CLOCK_TOLERANCE_MS = 6e4;
|
|
14
14
|
async function routingContext(input) {
|
|
15
15
|
const histories = new Map();
|
|
@@ -3,11 +3,9 @@ import type { ChannelSource } from "eve/channels";
|
|
|
3
3
|
import type { TelegramChannelState } from "eve/channels/telegram";
|
|
4
4
|
import type { SessionSettings } from "../../config.js";
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* on a continuation address (`resolveSession`, `compact`, `clear`); the history it needs is read
|
|
10
|
-
* from the session's own durable stream, so nothing is stored beside eve.
|
|
6
|
+
* Telegram session addressing (`agent-design.md`, section 4, and docs/agent.md, "Sessions"): what
|
|
7
|
+
* a group session spans. Compaction and idle expiry are eve's own: automatic compaction near the
|
|
8
|
+
* context window and `limits.sessionTimeoutMs` in the workspace's `agent.ts`.
|
|
11
9
|
*/
|
|
12
10
|
/** eve's Telegram continuation address: `chat:thread:conversation`, the last two possibly empty. */
|
|
13
11
|
export interface TelegramAddress {
|
|
@@ -28,18 +26,7 @@ export declare function messageAddress(message: TelegramMessage): string;
|
|
|
28
26
|
* eve's own address. A private chat has no anchor either way.
|
|
29
27
|
*/
|
|
30
28
|
export declare function scopedAddress(address: string, scope: SessionSettings["scope"]): string;
|
|
31
|
-
/**
|
|
32
|
-
export declare function compactBoundary(now: Date, compactAt: string, timezone: string): Date;
|
|
33
|
-
/** What the policy knows about one session: from its stream, then kept per process. */
|
|
34
|
-
export interface SessionHistory {
|
|
35
|
-
/** When the session last did anything. */
|
|
36
|
-
readonly lastAt: number;
|
|
37
|
-
/** When its history last started afresh: creation, compaction or clear. */
|
|
38
|
-
readonly resetAt: number;
|
|
39
|
-
/** Turns completed since `resetAt`; a compaction is worth nothing before the first. */
|
|
40
|
-
readonly turnsSinceReset: number;
|
|
41
|
-
}
|
|
42
|
-
/** One recorded event used by session policy, routing context and interaction projections. */
|
|
29
|
+
/** One recorded event used by routing context and interaction projections. */
|
|
43
30
|
export interface RecordedEvent {
|
|
44
31
|
readonly type: string;
|
|
45
32
|
readonly scope?: {
|
|
@@ -53,18 +40,8 @@ export interface RecordedEvent {
|
|
|
53
40
|
}
|
|
54
41
|
/** Session events, oldest first; readers choose a tail or full stream for their consumers. */
|
|
55
42
|
export type HistoryReader = (sessionId: string) => Promise<ReadonlyArray<RecordedEvent>>;
|
|
56
|
-
/** How many events of the tail the reader fetches
|
|
43
|
+
/** How many events of the tail the adapter's tail reader fetches. */
|
|
57
44
|
export declare const HISTORY_TAIL = 400;
|
|
58
|
-
/** Folds a tail into a history; with no reset marker in it, `resetAt` is its earliest event. */
|
|
59
|
-
export declare function historyOf(events: ReadonlyArray<RecordedEvent>): SessionHistory;
|
|
60
|
-
/**
|
|
61
|
-
* The earliest message time a group turn's conversation evidence still needs: each member
|
|
62
|
-
* message the session took in carried the chat's evidence up to then, and its history keeps it
|
|
63
|
-
* until a compaction or clear. Undefined when the session has none since its last reset, so the
|
|
64
|
-
* turn carries the whole window. Schedule sends and button answers carry no evidence and are
|
|
65
|
-
* skipped; a workspace callback turn is indistinguishable and may hide earlier unseen chatter.
|
|
66
|
-
*/
|
|
67
|
-
export declare function evidenceSince(events: ReadonlyArray<RecordedEvent>): number | undefined;
|
|
68
45
|
/** The route's operations on a continuation address, as eve gives them to the handler. */
|
|
69
46
|
export interface SessionOperations {
|
|
70
47
|
readonly resolveSession: (address: string) => Promise<{
|
|
@@ -75,29 +52,5 @@ export interface SessionOperations {
|
|
|
75
52
|
} | undefined>;
|
|
76
53
|
readonly from: (address: string) => {
|
|
77
54
|
readonly send?: ChannelSource<TelegramChannelState>["send"];
|
|
78
|
-
compact: ChannelSource<TelegramChannelState>["compact"];
|
|
79
|
-
clear: ChannelSource<TelegramChannelState>["clear"];
|
|
80
55
|
};
|
|
81
56
|
}
|
|
82
|
-
export type SessionAction = "none" | "cleared" | "compacted";
|
|
83
|
-
export interface SessionPolicy {
|
|
84
|
-
/**
|
|
85
|
-
* Applies the policy to the session `address` continues, before a message is delivered to it:
|
|
86
|
-
* clears a history idle for longer than `idleResetHours`, else compacts it once after each
|
|
87
|
-
* day's `compactAt`. A session that does not exist yet needs neither.
|
|
88
|
-
*/
|
|
89
|
-
apply(input: {
|
|
90
|
-
readonly address: string;
|
|
91
|
-
readonly operations: SessionOperations;
|
|
92
|
-
/** The newest activity the stream does not show, such as an observed group message. */
|
|
93
|
-
readonly activeAt?: number;
|
|
94
|
-
readonly now?: Date;
|
|
95
|
-
}): Promise<SessionAction>;
|
|
96
|
-
/** Records that a message was just delivered to the session. */
|
|
97
|
-
delivered(address: string, now?: Date): void;
|
|
98
|
-
}
|
|
99
|
-
export declare function sessionPolicy(input: {
|
|
100
|
-
readonly settings: () => SessionSettings;
|
|
101
|
-
readonly read: HistoryReader;
|
|
102
|
-
readonly log?: (message: string, error: unknown) => void;
|
|
103
|
-
}): SessionPolicy;
|