@segmentstream/agent 0.26.1 → 0.27.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.
Files changed (49) hide show
  1. package/README.md +41 -43
  2. package/dist/_chunks/{build-metadata-D_Gqlb5f.mjs → build-metadata-R_ssK2uR.mjs} +1 -1
  3. package/dist/extension/config.d.ts +11 -12
  4. package/dist/extension/config.mjs +13 -36
  5. package/dist/extension/extension.d.ts +1 -1
  6. package/dist/extension/extension.mjs +2 -2
  7. package/dist/extension/hooks/chat-context.d.ts +3 -0
  8. package/dist/extension/hooks/chat-context.mjs +10 -0
  9. package/dist/extension/hooks/sandbox.d.ts +2 -3
  10. package/dist/extension/instructions/conversations.d.ts +3 -2
  11. package/dist/extension/instructions/conversations.mjs +7 -11
  12. package/dist/extension/instructions.md +18 -0
  13. package/dist/extension/lib/build-metadata.mjs +1 -1
  14. package/dist/extension/lib/chat-context.d.ts +108 -0
  15. package/dist/extension/lib/chat-context.mjs +217 -0
  16. package/dist/extension/lib/conversations.d.ts +10 -6
  17. package/dist/extension/lib/conversations.mjs +28 -42
  18. package/dist/extension/lib/group-routing/burst.d.ts +1 -1
  19. package/dist/extension/lib/group-routing/conversation.d.ts +1 -1
  20. package/dist/extension/lib/group-routing/engagement.d.ts +1 -1
  21. package/dist/extension/lib/group-routing/{observed.d.ts → messages.d.ts} +9 -31
  22. package/dist/extension/lib/group-routing/messages.mjs +38 -0
  23. package/dist/extension/lib/group-routing/router.d.ts +3 -9
  24. package/dist/extension/lib/group-routing/router.mjs +2 -4
  25. package/dist/extension/lib/group-routing/routing-context.d.ts +6 -2
  26. package/dist/extension/lib/group-routing/routing-context.mjs +12 -1
  27. package/dist/extension/lib/telegram/adapter.d.ts +1 -3
  28. package/dist/extension/lib/telegram/adapter.mjs +9 -18
  29. package/dist/extension/lib/telegram/confirmations.d.ts +3 -3
  30. package/dist/extension/lib/telegram/confirmations.mjs +3 -25
  31. package/dist/extension/lib/telegram/conversations.d.ts +14 -3
  32. package/dist/extension/lib/telegram/conversations.mjs +16 -37
  33. package/dist/extension/lib/telegram/delivery-diagnostics.d.ts +4 -1
  34. package/dist/extension/lib/telegram/delivery-diagnostics.mjs +9 -3
  35. package/dist/extension/lib/telegram/health.mjs +1 -1
  36. package/dist/extension/lib/telegram/inbound.d.ts +14 -14
  37. package/dist/extension/lib/telegram/inbound.mjs +72 -68
  38. package/dist/extension/lib/telegram/lifecycle.mjs +1 -1
  39. package/dist/extension/lib/telegram/{observed.d.ts → message-metadata.d.ts} +2 -6
  40. package/dist/extension/lib/telegram/{observed.mjs → message-metadata.mjs} +1 -14
  41. package/dist/extension/lib/telegram/routing-context.d.ts +4 -3
  42. package/dist/extension/lib/telegram/routing-context.mjs +2 -2
  43. package/dist/extension/lib/telegram/sessions.d.ts +5 -52
  44. package/dist/extension/lib/telegram/sessions.mjs +1 -188
  45. package/dist/extension/lib/telegram/signals.mjs +1 -1
  46. package/dist/extension/lib/telegram/silent.d.ts +5 -6
  47. package/dist/extension/lib/telegram/silent.mjs +4 -31
  48. package/package.json +1 -1
  49. package/dist/extension/lib/group-routing/observed.mjs +0 -103
@@ -13,7 +13,7 @@ import { n as conversationLanguage, o as telegramPrincipal } from "../../../_chu
13
13
  import { object } from "../confirmation-description.mjs";
14
14
  import { confirmationLocale } from "./confirmation-text.mjs";
15
15
  import { telegramSendScope } from "./send-queue.mjs";
16
- import { conversations } from "../conversations.mjs";
16
+ import { conversations, topicContext } from "../conversations.mjs";
17
17
  import { withDeliveryDiagnostics } from "./delivery-diagnostics.mjs";
18
18
  import { d as failureText, h as withDeliveryLocale, i as withReplacedText, m as safeTelegramFetch, n as currentInboundRequest, o as currentLocale, p as reportTelegramError, r as runInboundRequest, t as InboundRequest } from "../../../_chunks/inbound-request-D1r0FkDI.mjs";
19
19
  import { chooseModel } from "../../model.mjs";
@@ -21,22 +21,21 @@ import { groupRoutingHealth } from "../group-routing/health.mjs";
21
21
  import { decisionRouter } from "../group-routing/router.mjs";
22
22
  import { archivedRoutingMessages } from "../group-routing/routing-context.mjs";
23
23
  import { dispatchStructuredMessage, structuredMessage } from "./structured.mjs";
24
- import { scopedAddress, sessionPolicy } from "./sessions.mjs";
24
+ import { scopedAddress } from "./sessions.mjs";
25
25
  import { handleWorkspaceCallback, isWorkspaceCallback } from "./callbacks.mjs";
26
- import { t as agentBuild } from "../../../_chunks/build-metadata-D_Gqlb5f.mjs";
26
+ import { t as agentBuild } from "../../../_chunks/build-metadata-R_ssK2uR.mjs";
27
27
  import { attachmentLifecycle } from "./lifecycle.mjs";
28
28
  import { gatewayImageDescriber } from "./images.mjs";
29
29
  import { gatewayAttachmentDescriber } from "./attachment-description.mjs";
30
30
  import { MAXIMUM_DOWNLOAD_BYTES, connectionProxy } from "../connection-proxy.mjs";
31
31
  import { agentAttachments, parseAttachmentUrl } from "../attachments.mjs";
32
32
  import { agentIdentities } from "../identities.mjs";
33
- import { observedMessages } from "./observed.mjs";
34
- import { interimTexts, replyDelivery } from "./silent.mjs";
35
- import { handleConfirmationCallback, telegramConfirmationEvents } from "./confirmations.mjs";
36
33
  import { captureRouteDecider, captureTelegramFetch, conversationIdentities, recordGateDecision, telegramConversationCapture } from "./conversations.mjs";
37
34
  import { botIdentityCache } from "./bot.mjs";
35
+ import { handleConfirmationCallback, telegramConfirmationEvents } from "./confirmations.mjs";
38
36
  import { gatewayMediaCapabilities, planMedia } from "./media-policy.mjs";
39
37
  import { agentChats } from "./chats.mjs";
38
+ import { interimTexts, replyDelivery } from "./silent.mjs";
40
39
  import { downloadTelegramFile, gatewayTranscriber } from "./voice.mjs";
41
40
  import { handleInboundMessage } from "./inbound.mjs";
42
41
  import { recoverableSessions } from "./session-recovery.mjs";
@@ -78,7 +77,6 @@ const eveInteractions = async (sessionId) => {
78
77
  function segmentstreamTelegram(options) {
79
78
  const environment = options.environment ?? process.env;
80
79
  const token = options.token ?? runtimeToken;
81
- const observed = options.observed ?? observedMessages(60);
82
80
  const readHistory = options.history ?? eveHistory;
83
81
  const readInteractions = options.history ?? eveInteractions;
84
82
  const capabilities = options.modelCapabilities ?? gatewayMediaCapabilities(options.fetch);
@@ -87,10 +85,6 @@ function segmentstreamTelegram(options) {
87
85
  token,
88
86
  fetch: options.fetch
89
87
  });
90
- const policy = sessionPolicy({
91
- settings: options.sessions,
92
- read: readHistory
93
- });
94
88
  const bot = botIdentityCache();
95
89
  const proxies = new Map();
96
90
  const interimDelivered = interimTexts();
@@ -127,7 +121,6 @@ function segmentstreamTelegram(options) {
127
121
  const capture = telegramConversationCapture({
128
122
  archive,
129
123
  identities: pairing,
130
- observed,
131
124
  chats,
132
125
  structured: (raw) => {
133
126
  const inbound = settings().inbound;
@@ -172,7 +165,7 @@ function segmentstreamTelegram(options) {
172
165
  archive,
173
166
  identities: pairing
174
167
  });
175
- const approvalEvents = telegramConfirmationEvents(archive, observed, () => options.sessions().scope);
168
+ const approvalEvents = telegramConfirmationEvents(archive, () => options.sessions().scope);
176
169
  const channel = telegramChannel({
177
170
  turnPolicy: "queue",
178
171
  api: {
@@ -203,10 +196,10 @@ function segmentstreamTelegram(options) {
203
196
  policy: settings().policy,
204
197
  decisionModel: options.models().decision,
205
198
  groups: options.groups?.(),
206
- recentConversation: async (chatId, threadId) => archivedRoutingMessages((await archive.context({
199
+ recentConversation: async (chatId, threadId) => archivedRoutingMessages((await topicContext(archive, {
207
200
  provider: "telegram",
208
201
  chatId,
209
- ...threadId === void 0 ? {} : { threadId },
202
+ threadId: threadId ?? null,
210
203
  limit: 200,
211
204
  includeDecisions: true
212
205
  })).events, 100),
@@ -236,7 +229,6 @@ function segmentstreamTelegram(options) {
236
229
  }) },
237
230
  sessions: options.sessions,
238
231
  admission: options.admission ?? (() => manifestOf({})),
239
- sessionPolicy: policy,
240
232
  sttModel: options.models().stt,
241
233
  attachmentsModel: options.models().imageDescription ?? options.models().attachments ?? "google/gemini-2.5-flash-lite",
242
234
  describeAttachment: options.describeAttachment ?? gatewayAttachmentDescriber,
@@ -285,7 +277,6 @@ function segmentstreamTelegram(options) {
285
277
  ...options
286
278
  }),
287
279
  attachments: attachments(),
288
- observed,
289
280
  readHistory,
290
281
  history: readInteractions,
291
282
  chats,
@@ -425,7 +416,7 @@ function segmentstreamTelegram(options) {
425
416
  await channel.telegram.post(failureText(reference));
426
417
  return;
427
418
  }
428
- await replyDelivery(observed, () => telegramSendScope(environment, settings().connection), {
419
+ await replyDelivery(() => telegramSendScope(environment, settings().connection), {
429
420
  enabled: () => settings().interimMessages === true,
430
421
  delivered: interimDelivered
431
422
  })(event, context);
@@ -1,13 +1,13 @@
1
1
  import type { ChannelEvents } from "eve/channels";
2
2
  import type { TelegramCallbackQuery, TelegramChannelEvents, TelegramContext, TelegramEventContext } from "eve/channels/telegram";
3
- import { type ObservedMessages, type PendingConfirmation, type RecentMessage } from "./observed.js";
3
+ import { type ConfirmationPrompt, type PendingConfirmation, type RecentMessage } from "../group-routing/messages.js";
4
4
  import { type Conversations } from "../conversations.js";
5
5
  import type { AgentIdentities } from "../identities.js";
6
6
  import { type PendingQuestion } from "../questions.js";
7
7
  import { type HistoryReader, type RecordedEvent } from "./sessions.js";
8
8
  export declare function confirmationCallback(requestId: string, memberId: string, decision: "approve" | "cancel" | "edit"): string;
9
9
  /** Terminal facts override stale presentation, including when a Telegram edit failed. */
10
- export declare function routingConfirmationState(events: readonly RecordedEvent[], prompt: import("./observed.js").ConfirmationPrompt, at: string): NonNullable<PendingConfirmation["state"]>;
10
+ export declare function routingConfirmationState(events: readonly RecordedEvent[], prompt: ConfirmationPrompt, at: string): NonNullable<PendingConfirmation["state"]>;
11
11
  /** Read-only projection of the same durable approval lifecycle used by the callback gate. */
12
12
  export declare function routingConfirmations(events: readonly RecordedEvent[], recent: readonly RecentMessage[], at: string): ReadonlyArray<PendingConfirmation>;
13
13
  export declare function questionFromHistory(events: readonly RecordedEvent[], owner?: Pick<PendingQuestion, "memberId" | "chatId" | "messageThreadId">): PendingQuestion | undefined;
@@ -23,5 +23,5 @@ export declare function handleConfirmationCallback(input: {
23
23
  readonly scope: "chat" | "thread";
24
24
  readonly now?: number;
25
25
  }): Promise<void>;
26
- export declare function telegramConfirmationEvents(archive?: Conversations, observed?: ObservedMessages, scope?: () => "chat" | "thread"): TelegramChannelEvents & Pick<ChannelEvents<TelegramEventContext>, "context.settled">;
26
+ export declare function telegramConfirmationEvents(archive?: Conversations, scope?: () => "chat" | "thread"): TelegramChannelEvents & Pick<ChannelEvents<TelegramEventContext>, "context.settled">;
27
27
  export declare const confirmationEvents: TelegramChannelEvents & Pick<ChannelEvents<TelegramEventContext>, "context.settled">;
@@ -11,7 +11,7 @@ import { renderConfirmation, withConfirmationText } from "./confirmation-text.mj
11
11
  import { chatPostCard, chatPostCardStatus, chatPostReceipt, withChatPostCard } from "./chat-post-card.mjs";
12
12
  import { conversationEvent } from "../conversations.mjs";
13
13
  import { n as currentInboundRequest, p as reportTelegramError } from "../../../_chunks/inbound-request-D1r0FkDI.mjs";
14
- import { agentMessage, observedChatKey, withConfirmationPrompt } from "../group-routing/observed.mjs";
14
+ import { withConfirmationPrompt } from "../group-routing/messages.mjs";
15
15
  import { chatPostOperationId, isChatPost } from "../chat-post-confirmation.mjs";
16
16
  import { confirmations } from "../confirmations.mjs";
17
17
  import { formatAddress, scopedAddress } from "./sessions.mjs";
@@ -201,23 +201,7 @@ function messagePrompt(message) {
201
201
  at: new Date().toISOString()
202
202
  };
203
203
  }
204
- function telegramConfirmationEvents(archive, observed, scope) {
205
- const remember = (channel, ctx, messageId, text, confirmation) => {
206
- const principal = ctx.session.auth.current;
207
- const name = humanText(principal?.attributes.memberName);
208
- observed?.remember(observedChatKey(channel.telegram), {
209
- ...agentMessage({
210
- text,
211
- at: new Date().toISOString(),
212
- messageId,
213
- ...principal?.subject === void 0 || name === void 0 ? {} : { inReplyTo: {
214
- id: principal.subject,
215
- author: name
216
- } }
217
- }),
218
- ...confirmation === void 0 ? {} : { confirmation }
219
- });
220
- };
204
+ function telegramConfirmationEvents(archive, scope) {
221
205
  const record = async (callId, phase, text, status, ctx) => {
222
206
  const saved = confirmations.get()[callId];
223
207
  if (archive === void 0 || saved?.description?.chatPost === void 0 || saved.source === void 0) return;
@@ -275,7 +259,6 @@ function telegramConfirmationEvents(archive, observed, scope) {
275
259
  selective: true
276
260
  }
277
261
  }));
278
- remember(channel, ctx, sent.id, text);
279
262
  channel.state.pendingFreeformReplies = {
280
263
  ...channel.state.pendingFreeformReplies,
281
264
  [sent.id]: requestId
@@ -341,7 +324,6 @@ function telegramConfirmationEvents(archive, observed, scope) {
341
324
  } }
342
325
  };
343
326
  const sent = await withConfirmationPrompt(prompt, () => exactPost ? withChatPostCard(text, send, copy.previewLinks) : withConfirmationText(text, send));
344
- remember(channel, ctx, sent.id, text, prompt);
345
327
  if (aliasesPendingTopic(scope?.(), channel.continuation?.token)) channel.continuation?.alias(formatAddress({
346
328
  chatId: channel.telegram.chatId,
347
329
  messageThreadId: String(channel.telegram.messageThreadId ?? ""),
@@ -405,7 +387,6 @@ function telegramConfirmationEvents(archive, observed, scope) {
405
387
  };
406
388
  message.prompt = prompt;
407
389
  await withConfirmationPrompt(prompt, () => message.exactPost ? withChatPostCard(text, edit, [...message.previewLinks ?? [], ...telegramTextLinks(status)]) : withConfirmationText(text, edit));
408
- remember(channel, ctx, message.messageId, text, prompt);
409
390
  await record(callId, reason ?? (resolution.outcome === "accepted" ? "approved" : resolution.outcome === "declined" ? "cancelled" : "ignored"), text, resolution.outcome === "accepted" ? "requested" : "cancelled", ctx);
410
391
  if (resolution.outcome !== "accepted") {
411
392
  delete state.confirmationMessages?.[callId];
@@ -451,14 +432,13 @@ function telegramConfirmationEvents(archive, observed, scope) {
451
432
  }));
452
433
  channel.continuation?.alias(questionAddress(saved));
453
434
  }
454
- const sent = await channel.telegram.post({
435
+ await channel.telegram.post({
455
436
  text: question.question,
456
437
  reply_markup: {
457
438
  force_reply: true,
458
439
  selective: true
459
440
  }
460
441
  });
461
- remember(channel, ctx, sent.id, question.question);
462
442
  }
463
443
  }
464
444
  const message = state.confirmationMessages?.[data.callId];
@@ -479,7 +459,6 @@ function telegramConfirmationEvents(archive, observed, scope) {
479
459
  replyMarkup: { inline_keyboard: [] },
480
460
  text
481
461
  }), [...message.previewLinks ?? [], ...telegramTextLinks(status)]));
482
- remember(channel, ctx, message.messageId, text, prompt);
483
462
  await record(data.callId, "receipt", text, failed ? "failed" : object(action?.kind === "tool" ? data.output : void 0).status === "sent" ? "completed" : "unknown", ctx);
484
463
  } else {
485
464
  const copy = message.copy ?? renderConfirmation();
@@ -495,7 +474,6 @@ function telegramConfirmationEvents(archive, observed, scope) {
495
474
  replyMarkup: { inline_keyboard: [] },
496
475
  text
497
476
  })));
498
- remember(channel, ctx, message.messageId, text, prompt);
499
477
  }
500
478
  delete state.confirmationMessages?.[data.callId];
501
479
  },
@@ -2,7 +2,6 @@ import type { StructuredMessage } from "./structured.js";
2
2
  import type { AgentIdentities } from "../identities.js";
3
3
  import { type Conversations } from "../conversations.js";
4
4
  import type { AgentChats } from "./chats.js";
5
- import type { ObservedMessages } from "./observed.js";
6
5
  import type { DecodedAttachment } from "./attachment-description.js";
7
6
  import type { AddressingReason, RouteDecider, RouteDecision } from "./router.js";
8
7
  /** A crashed routing attempt can be reclaimed; the per-message turn claim still fences dispatch. */
@@ -14,7 +13,6 @@ export declare function captureTelegramWebhook(input: {
14
13
  readonly request: Request;
15
14
  readonly archive: Conversations;
16
15
  readonly identities: AgentIdentities;
17
- readonly observed: ObservedMessages;
18
16
  readonly chats?: AgentChats;
19
17
  readonly next: (request?: Request) => Promise<Response>;
20
18
  }): Promise<Response>;
@@ -22,11 +20,25 @@ export declare function captureTelegramWebhook(input: {
22
20
  export declare function recordGateDecision(reason: string, decision?: "reply" | "silent"): void;
23
21
  export declare function recordRouteDecision(route: "reply" | "observe", addressed: boolean | AddressingReason | undefined, policy: string, audit?: Pick<RouteDecision, "reason" | "note" | "diagnostics" | "router" | "shadow">): void;
24
22
  export declare function captureRouteDecider(decide: RouteDecider): RouteDecider;
23
+ /** A stored attachment of an archived message: what a later turn needs to read or show it. */
24
+ export interface ArchivedFile {
25
+ readonly id: string;
26
+ readonly filename: string;
27
+ readonly contentType: string;
28
+ readonly size: number;
29
+ readonly expiresAt: string;
30
+ /** Whether it can reach a model as a native image. */
31
+ readonly image?: true;
32
+ }
25
33
  /** Update the existing inbound event after STT/description, under the same text retention policy. */
26
34
  export declare function recordInboundContent(content: {
27
35
  readonly text: string;
28
36
  readonly media?: string;
29
37
  readonly decoded?: ReadonlyArray<DecodedAttachment>;
38
+ /** How the chat context names a linked member: their name and roles, then their handle. */
39
+ readonly author?: string;
40
+ /** The message's stored attachments, by reference: never bytes or download grants. */
41
+ readonly files?: ReadonlyArray<ArchivedFile>;
30
42
  }): Promise<void>;
31
43
  /**
32
44
  * Records eve's unrecoverable session failure from the channel. The channel receives it even when
@@ -54,7 +66,6 @@ export declare function telegramConversationCapture(input: {
54
66
  readonly structured?: (raw: Readonly<Record<string, unknown>>) => StructuredMessage | undefined;
55
67
  readonly archive: Conversations;
56
68
  readonly identities: () => AgentIdentities;
57
- readonly observed: ObservedMessages;
58
69
  readonly chats: AgentChats;
59
70
  readonly verify: (request: Request) => boolean;
60
71
  }): (request: Request, next: (request?: Request) => Promise<Response>) => Promise<Response>;
@@ -6,12 +6,12 @@ __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { n as contentText, t as CONTENT_CAPS } from "../../../_chunks/content-cDtVnr8Y.mjs";
8
8
  import { conversationEvent, sessionFailureEventId } from "../conversations.mjs";
9
+ import { currentDeliveryScope } from "./delivery-diagnostics.mjs";
9
10
  import { n as currentInboundRequest, p as reportTelegramError, s as currentThreadId } from "../../../_chunks/inbound-request-D1r0FkDI.mjs";
10
- import { currentConfirmationPrompt, observedChatKey } from "../group-routing/observed.mjs";
11
- import { archivedRoutingMessages } from "../group-routing/routing-context.mjs";
11
+ import { currentConfirmationPrompt } from "../group-routing/messages.mjs";
12
12
  import { parseArchivedMessage } from "./archive-message.mjs";
13
13
  import { contentShape, messageContent } from "./content.mjs";
14
- import { messageMetadata } from "./observed.mjs";
14
+ import { messageMetadata } from "./message-metadata.mjs";
15
15
  import { reportMembership } from "./membership.mjs";
16
16
  import { createHash, randomUUID } from "node:crypto";
17
17
  const captures = new WeakMap();
@@ -177,20 +177,8 @@ async function captureTelegramWebhook(input) {
177
177
  };
178
178
  const inbound = currentInboundRequest();
179
179
  if (inbound !== void 0) captures.set(inbound, capture);
180
- const chat = observedChatKey({
181
- chatId: message.chat.id,
182
- ...message.messageThreadId === void 0 ? {} : { messageThreadId: message.messageThreadId }
183
- });
184
180
  const automated = message.from === void 0 || message.from.isBot || message.chat.type === "channel";
185
181
  if (automated || edited !== void 0 || message.raw.left_chat_member || message.raw.migrate_to_chat_id || message.raw.migrate_from_chat_id) await capture.persist();
186
- const history = automated ? void 0 : await input.archive.context({
187
- provider: "telegram",
188
- chatId: message.chat.id,
189
- ...event.threadId === null ? {} : { threadId: event.threadId },
190
- limit: 100,
191
- includeDecisions: true,
192
- ...lookup?.status === "paired" ? { participantId: lookup.member.id } : {}
193
- });
194
182
  const messageHistory = automated ? [] : (await input.archive.context({
195
183
  provider: "telegram",
196
184
  chatId: message.chat.id,
@@ -265,35 +253,16 @@ async function captureTelegramWebhook(input) {
265
253
  await recordInboundContent({
266
254
  text: [content.text, ...transcripts].filter(Boolean).join("\n\n"),
267
255
  decoded,
268
- ...media === void 0 ? {} : { media }
256
+ ...media === void 0 ? {} : { media },
257
+ ...Array.isArray(previousRouting.files) ? { files: previousRouting.files } : {}
269
258
  });
270
259
  }
271
260
  }
272
- if (!automated) for (const earlier of input.observed.recent(chat).length === 0 ? archivedRoutingMessages((history?.events ?? []).filter((item) => item.eventId !== event.eventId), 60) : []) if ("role" in earlier || history?.events.some((decision) => decision.kind === "decision" && decision.messageId === earlier.messageId && decision.reason === "turn_admitted")) input.observed.remember(chat, earlier);
273
- else input.observed.add(chat, earlier);
274
261
  if (automated || edited !== void 0) {
275
262
  if (message.chat.type === "group" || message.chat.type === "supergroup") await input.chats?.seen({
276
263
  chatId: message.chat.id,
277
264
  title: message.chat.title ?? ""
278
265
  });
279
- const remembered = input.observed.recent(chat).findLast((entry) => entry.messageId === message.messageId);
280
- const retained = alreadyHandled && edited === void 0 ? remembered : void 0;
281
- const retainedContent = archivedRoutingMessages([{
282
- ...capture.event,
283
- sequence: "1",
284
- storedAt: event.at
285
- }])[0];
286
- input.observed.add(chat, retained ?? (retainedContent === void 0 ? void 0 : {
287
- ...retainedContent,
288
- ...unchangedAttachments && remembered?.images !== void 0 ? { images: remembered.images } : {}
289
- }) ?? {
290
- ...messageMetadata(message),
291
- author: event.senderName ?? event.senderId ?? "automated post",
292
- text: event.text ?? "[attachment or service message]",
293
- at: event.at,
294
- messageId: message.messageId,
295
- ...event.senderId === null ? {} : { authorId: event.senderId }
296
- });
297
266
  capture.decision = "silent";
298
267
  capture.reason = automated ? "automated_or_channel_message" : alreadyHandled ? "message_already_handled" : "message_edit";
299
268
  if (alreadyHandled) capture.note = "This message already started a turn; edits and retries do not repeat its task.";
@@ -495,7 +464,9 @@ async function recordInboundContent(content) {
495
464
  await capture.persist();
496
465
  const metadata = record(JSON.parse(capture.event.argsSummary ?? "{}"));
497
466
  const membership = capture.lookup === void 0 ? record(metadata.routing).membership : capture.lookup.status === "paired" || capture.lookup.status === "existing" || capture.lookup.status === "created" ? "linked" : "unlinked";
498
- if (content.text === capture.event.text && content.media === record(metadata.routing).media && membership === record(metadata.routing).membership && JSON.stringify(content.decoded) === JSON.stringify(record(metadata.routing).decoded)) return;
467
+ const author = content.author ?? record(metadata.routing).author;
468
+ const files = content.files ?? record(metadata.routing).files;
469
+ if (content.text === capture.event.text && content.media === record(metadata.routing).media && membership === record(metadata.routing).membership && author === record(metadata.routing).author && JSON.stringify(files) === JSON.stringify(record(metadata.routing).files) && JSON.stringify(content.decoded) === JSON.stringify(record(metadata.routing).decoded)) return;
499
470
  const contentHash = createHash("sha256").update(JSON.stringify({
500
471
  ...content,
501
472
  membership
@@ -509,6 +480,8 @@ async function recordInboundContent(content) {
509
480
  routing: {
510
481
  ...record(metadata.routing),
511
482
  membership,
483
+ ...typeof author === "string" ? { author: author.slice(0, 500) } : {},
484
+ ...Array.isArray(files) && files.length > 0 ? { files: files.slice(0, 10) } : {},
512
485
  media: content.media?.slice(0, 8e3),
513
486
  decoded: content.decoded
514
487
  }
@@ -582,6 +555,8 @@ function captureTelegramFetch(fetchImpl, archive, identities) {
582
555
  const chatId = id(body.chat_id);
583
556
  if (chatId === null) return fetchImpl(input, init);
584
557
  const { capture, chatType, participantIds } = await outboundAudience(chatId, identities);
558
+ const delivery = currentDeliveryScope();
559
+ const own = delivery?.chatId === chatId && delivery.sessionId !== null;
585
560
  const event = conversationEvent({
586
561
  eventId: `telegram:${edit ? "edit" : "send"}:${randomUUID()}`,
587
562
  provider: "telegram",
@@ -620,6 +595,10 @@ function captureTelegramFetch(fetchImpl, archive, identities) {
620
595
  participantIds,
621
596
  threadId: id(body.message_thread_id) ?? currentThreadId() ?? null,
622
597
  replyToMessageId: id(body.reply_to_message_id) ?? id(record(body.reply_parameters).message_id),
598
+ ...own ? {
599
+ sessionId: delivery.sessionId,
600
+ turnId: delivery.turnId
601
+ } : {},
623
602
  status: "requested"
624
603
  });
625
604
  await archive.append(event);
@@ -5,8 +5,11 @@ interface Diagnostic {
5
5
  readonly error: unknown;
6
6
  readonly metadata: Record<string, unknown>;
7
7
  }
8
+ type DeliveryScope = Pick<ConversationEvent, "chatId" | "chatType" | "threadId" | "sessionId" | "turnId">;
8
9
  /** Receives the same credential-redacted payload as operator logs, never raw provider errors. */
9
10
  export declare function retainDeliveryDiagnostic(diagnostic: Diagnostic): void;
11
+ /** The session and turn a channel callback delivers for, so its sends are archived as its own. */
12
+ export declare function currentDeliveryScope(): DeliveryScope | undefined;
10
13
  /** Await archive writes inside the channel callback, including failed or restored callbacks. */
11
- export declare function withDeliveryDiagnostics<T>(archive: Conversations, scope: Pick<ConversationEvent, "chatId" | "chatType" | "threadId" | "sessionId" | "turnId">, run: () => T | Promise<T>): Promise<T>;
14
+ export declare function withDeliveryDiagnostics<T>(archive: Conversations, scope: DeliveryScope, run: () => T | Promise<T>): Promise<T>;
12
15
  export {};
@@ -10,11 +10,17 @@ import { conversationEvent } from "../conversations.mjs";
10
10
  import { AsyncLocalStorage } from "node:async_hooks";
11
11
  const pending = new AsyncLocalStorage();
12
12
  function retainDeliveryDiagnostic(diagnostic) {
13
- pending.getStore()?.push(diagnostic);
13
+ pending.getStore()?.diagnostics.push(diagnostic);
14
+ }
15
+ function currentDeliveryScope() {
16
+ return pending.getStore()?.scope;
14
17
  }
15
18
  async function withDeliveryDiagnostics(archive, scope, run) {
16
19
  const diagnostics = [];
17
- return pending.run(diagnostics, async () => {
20
+ return pending.run({
21
+ diagnostics,
22
+ scope
23
+ }, async () => {
18
24
  try {
19
25
  return await run();
20
26
  } finally {
@@ -43,4 +49,4 @@ async function withDeliveryDiagnostics(archive, scope, run) {
43
49
  }
44
50
  });
45
51
  }
46
- export { retainDeliveryDiagnostic, withDeliveryDiagnostics };
52
+ export { currentDeliveryScope, retainDeliveryDiagnostic, withDeliveryDiagnostics };
@@ -5,7 +5,7 @@ const __filename = __eveFileURLToPath(import.meta.url);
5
5
  __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { runtimeRequestFailure } from "./runtime-request.mjs";
8
- import { t as agentBuild } from "../../../_chunks/build-metadata-D_Gqlb5f.mjs";
8
+ import { t as agentBuild } from "../../../_chunks/build-metadata-R_ssK2uR.mjs";
9
9
  import { GET } from "eve/channels";
10
10
  const HEALTH_ROUTE = "/eve/v1/telegram/health";
11
11
  function healthRoute(input) {
@@ -1,5 +1,5 @@
1
1
  import { type StructuredInbound, type StructuredMessage, type StructuredResult } from "./structured.js";
2
- import type { RecentMessage } from "../group-routing/observed.js";
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, type SessionPolicy } from "./sessions.js";
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
- * the ring carries the stored files' references (or storage failures) into the next reply.
35
- * 4. The session policy runs on the session an answered message continues: an idle one is cleared, the
36
- * first message after the day's compaction time compacts it.
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 the ring, the registry's `<chat>`
39
- * block, a `<speaker>` block naming the member, their roles and the message they replied to and
40
- * an `<attachments>` block naming its stored files ahead of it as context. An answered group
41
- * message waits for its chat's report first, so that a chat new to the process already carries
42
- * its `<chat>` block.
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;