@segmentstream/agent 0.9.1 → 0.10.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 +8 -0
  2. package/dist/_chunks/{build-metadata-DMD3azjO.mjs → build-metadata-CYEMFYjL.mjs} +1 -1
  3. package/dist/_chunks/{inbound-request-B8Eyjz1O.mjs → inbound-request-BrWspFw5.mjs} +2 -1
  4. package/dist/extension/extension.d.ts +2 -0
  5. package/dist/extension/extension.mjs +3 -1
  6. package/dist/extension/hooks/group-routing.d.ts +2 -0
  7. package/dist/extension/hooks/group-routing.mjs +10 -0
  8. package/dist/extension/lib/build-metadata.mjs +1 -1
  9. package/dist/extension/lib/conversation-runtime.d.ts +1 -1
  10. package/dist/extension/lib/conversation-runtime.mjs +23 -1
  11. package/dist/extension/lib/group-routing/burst.d.ts +28 -0
  12. package/dist/extension/lib/group-routing/burst.mjs +64 -0
  13. package/dist/extension/lib/group-routing/conversation-prompt.d.ts +10 -0
  14. package/dist/extension/lib/group-routing/conversation-prompt.mjs +16 -0
  15. package/dist/extension/lib/group-routing/conversation-router.d.ts +58 -0
  16. package/dist/extension/lib/group-routing/conversation-router.mjs +182 -0
  17. package/dist/extension/lib/group-routing/conversation.d.ts +46 -0
  18. package/dist/extension/lib/group-routing/conversation.mjs +262 -0
  19. package/dist/extension/lib/group-routing/dequeue.d.ts +5 -0
  20. package/dist/extension/lib/group-routing/dequeue.mjs +141 -0
  21. package/dist/extension/lib/group-routing/observed.d.ts +2 -0
  22. package/dist/extension/lib/group-routing/policy.d.ts +19 -1
  23. package/dist/extension/lib/group-routing/policy.mjs +49 -11
  24. package/dist/extension/lib/group-routing/replay.d.ts +2 -2
  25. package/dist/extension/lib/group-routing/replay.mjs +51 -11
  26. package/dist/extension/lib/group-routing/router.d.ts +8 -1
  27. package/dist/extension/lib/group-routing/router.mjs +65 -0
  28. package/dist/extension/lib/group-routing/routing-context.mjs +10 -0
  29. package/dist/extension/lib/telegram/adapter.mjs +10 -2
  30. package/dist/extension/lib/telegram/attachment-description.mjs +1 -1
  31. package/dist/extension/lib/telegram/attachments-send.mjs +1 -1
  32. package/dist/extension/lib/telegram/confirmation-expiry.mjs +1 -1
  33. package/dist/extension/lib/telegram/confirmations.mjs +1 -1
  34. package/dist/extension/lib/telegram/conversations.mjs +1 -1
  35. package/dist/extension/lib/telegram/customer-text.mjs +1 -1
  36. package/dist/extension/lib/telegram/deferred-confirmations.mjs +1 -1
  37. package/dist/extension/lib/telegram/files.mjs +1 -1
  38. package/dist/extension/lib/telegram/health.mjs +1 -1
  39. package/dist/extension/lib/telegram/images.mjs +1 -1
  40. package/dist/extension/lib/telegram/inbound-request.d.ts +2 -0
  41. package/dist/extension/lib/telegram/inbound-request.mjs +1 -1
  42. package/dist/extension/lib/telegram/inbound.d.ts +2 -0
  43. package/dist/extension/lib/telegram/inbound.mjs +122 -11
  44. package/dist/extension/lib/telegram/lifecycle.mjs +2 -2
  45. package/dist/extension/lib/telegram/media-policy.mjs +1 -1
  46. package/dist/extension/lib/telegram/silent.mjs +1 -1
  47. package/dist/extension/lib/telegram/turn-errors.mjs +1 -1
  48. package/dist/extension/lib/telegram/voice.mjs +1 -1
  49. package/package.json +2 -2
@@ -4,19 +4,34 @@ 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 { routeMessage } from "./router.mjs";
7
+ import { decisionRouter, routeMessage } from "./router.mjs";
8
8
  async function replayRouterPayload(input) {
9
9
  const policy = input.policy ?? input.payload.policy;
10
10
  const signals = input.payload.signals;
11
11
  const state = input.payload.state;
12
12
  const questions = input.payload.questions;
13
13
  const answer = input.payload.answer;
14
- if (signals === void 0 || policy === void 0 || input.payload.truncated === true) return {
14
+ if (!signals || !policy || input.payload.truncated === true) return {
15
15
  status: "unavailable",
16
16
  reason: "stored_router_input_unavailable"
17
17
  };
18
- const recent = input.payload.question?.recent ?? [];
18
+ const conversation = policy.smart.engagement.policy === "conversation";
19
+ const captured = input.payload.question;
20
+ const storedAddressing = answer && "addressee" in answer ? answer : void 0;
21
+ if (conversation && !input.decide && !storedAddressing) return {
22
+ status: "unavailable",
23
+ reason: "stored_conversation_classifier_unavailable"
24
+ };
25
+ let description = String(state?.agent ?? "");
26
+ if (conversation) try {
27
+ const a = JSON.parse(description);
28
+ if (typeof a.role === "string") description = a.role;
29
+ } catch {}
19
30
  const decisions = [];
31
+ const fresh = input.decide ? decisionRouter({
32
+ decide: input.decide,
33
+ model: policy.smart.model
34
+ }) : void 0;
20
35
  return {
21
36
  status: "replayed",
22
37
  route: await routeMessage({
@@ -29,25 +44,50 @@ async function replayRouterPayload(input) {
29
44
  pendingQuestionAt: typeof input.payload.pending_question_at === "string" ? input.payload.pending_question_at : void 0,
30
45
  recipientAnswer: input.payload.recipient_answer === true,
31
46
  question: async () => ({
47
+ ...captured,
32
48
  signals,
33
49
  groups: policy,
34
- agent: String(state?.agent ?? ""),
35
- recent,
50
+ agent: description,
51
+ recent: captured?.recent ?? [],
36
52
  message: {
37
53
  author: signals.sender.displayName ?? signals.sender.externalId,
38
54
  authorId: signals.sender.externalId,
55
+ messageId: signals.message.id,
39
56
  text: signals.message.text,
40
57
  at: signals.message.at
41
- }
58
+ },
59
+ burstId: typeof input.payload.burst_id === "string" ? input.payload.burst_id : void 0
42
60
  }),
43
- decide: async () => {
44
- if (state === void 0 || questions === void 0 || answer === void 0) throw new Error("Stored classifier contract unavailable.");
45
- const result = input.decide === void 0 ? answer : (await input.decide({
61
+ decide: async (q) => {
62
+ if (conversation) {
63
+ if (fresh) return fresh(q);
64
+ const p = storedAddressing?.addressee.probabilities?.agent;
65
+ if (p === void 0) throw new Error("Stored addressing probability unavailable.");
66
+ return {
67
+ route: "reply",
68
+ diagnostics: {
69
+ model: policy.smart.model,
70
+ latencyMs: 0,
71
+ probabilities: {
72
+ reply: p,
73
+ observe: 1 - p
74
+ },
75
+ addressing: storedAddressing
76
+ },
77
+ router: {
78
+ state,
79
+ questions,
80
+ answer
81
+ }
82
+ };
83
+ }
84
+ if (!state || !questions || !answer || !("choice" in answer)) throw new Error("Stored classifier contract unavailable.");
85
+ const result = input.decide ? (await input.decide({
46
86
  model: policy.smart.model,
47
87
  state,
48
88
  questions,
49
89
  abortSignal: AbortSignal.timeout(Math.ceil(policy.smart.timeoutSeconds * 1e3))
50
- })).answers.route;
90
+ })).answers.route : answer;
51
91
  return {
52
92
  route: result.choice,
53
93
  diagnostics: {
@@ -62,7 +102,7 @@ async function replayRouterPayload(input) {
62
102
  }
63
103
  };
64
104
  },
65
- record: (decision) => decisions.push(decision)
105
+ record: (d) => decisions.push(d)
66
106
  }),
67
107
  decisions
68
108
  };
@@ -1,3 +1,5 @@
1
+ import { type ConversationAnalysis } from "./conversation.js";
2
+ import { type AddressingAnswers } from "./conversation-router.js";
1
3
  import { type GroupPolicy } from "./policy.js";
2
4
  import type { ObservedMessage, PendingConfirmation, RecentMessage } from "./observed.js";
3
5
  import { type MessageAudience, type AgentIdentity as BotIdentity, type AddressingReason, type GroupMessageSignals } from "./signals.js";
@@ -7,6 +9,10 @@ export type Route = "observe" | "reply";
7
9
  /** What the decision model sees. */
8
10
  export interface RouteQuestion {
9
11
  readonly groups?: GroupPolicy;
12
+ readonly conversation?: ConversationAnalysis;
13
+ readonly burstId?: string;
14
+ readonly processingAt?: string;
15
+ readonly messagesToClassify?: ReadonlyArray<ObservedMessage>;
10
16
  readonly signals?: GroupMessageSignals;
11
17
  readonly agentInstructions?: string;
12
18
  /** The agent's own description, so the model knows what it can be asked for. */
@@ -39,6 +45,7 @@ export interface RouteDecision {
39
45
  export interface RouteDiagnostics {
40
46
  readonly model: string;
41
47
  readonly latencyMs: number;
48
+ readonly addressing?: AddressingAnswers;
42
49
  readonly probabilities?: Readonly<Record<Route, number>>;
43
50
  }
44
51
  /** Decides whether a group message not addressed to the bot is still for it. */
@@ -104,6 +111,6 @@ export declare function routeMessage(input: {
104
111
  readonly record?: (decision: RouteDecision) => void;
105
112
  /** Builds the question only when a decision is needed: it reads the agent's description. */
106
113
  readonly question: () => Promise<RouteQuestion>;
107
- readonly beforeDecide?: () => Promise<boolean>;
114
+ readonly beforeDecide?: (direct?: boolean) => Promise<boolean>;
108
115
  readonly decide: RouteDecider;
109
116
  }): Promise<Route>;
@@ -6,6 +6,8 @@ __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { DEFAULT_GROUP_POLICY, groupPolicy, overlaySmartPolicy } from "./policy.mjs";
8
8
  import { addressingReason, otherRecipient, signalAudience } from "./signals.mjs";
9
+ import { conversationState, conversationThreshold } from "./conversation.mjs";
10
+ import { conversationDecisionRouter } from "./conversation-router.mjs";
9
11
  import { recordRoutingMetric } from "./health.mjs";
10
12
  import { humanAddressee, humanThread, questionInvitation, restoreEngagement } from "./engagement.mjs";
11
13
  import { boundedRouterPayload, policyHash } from "./record.mjs";
@@ -172,6 +174,7 @@ function conversationContext(question) {
172
174
  }
173
175
  function decisionRouter(input) {
174
176
  return async (question) => {
177
+ if (question.groups?.smart.engagement.policy === "conversation") return conversationDecisionRouter(input.decide)(question);
175
178
  const policy = question.groups?.smart;
176
179
  const started = performance.now();
177
180
  const abortSignal = AbortSignal.timeout(Math.ceil((policy?.timeoutSeconds ?? 15) * 1e3));
@@ -292,6 +295,68 @@ async function routeMessage(input) {
292
295
  });
293
296
  return route;
294
297
  };
298
+ if (groups.smart.engagement.policy === "conversation" && signals !== void 0 && signals.conversation.kind !== "direct") try {
299
+ question = {
300
+ ...await input.question(),
301
+ groups,
302
+ signals
303
+ };
304
+ const analysis = conversationState({
305
+ signals,
306
+ recent: question.recent,
307
+ policy: groups,
308
+ agentNames: [...question.agentIdentity?.aliases ?? [], ...question.agentIdentity?.username ? [question.agentIdentity.username] : []],
309
+ pendingQuestionAt: input.pendingQuestionAt,
310
+ asOf: question.processingAt
311
+ });
312
+ question = {
313
+ ...question,
314
+ conversation: analysis
315
+ };
316
+ payload = {
317
+ ...payload,
318
+ burst_id: question.burstId,
319
+ path: analysis.path,
320
+ conversation: analysis.conversation,
321
+ human_exchange: analysis.human_exchange,
322
+ pending_question_voided: input.pendingQuestionAt !== void 0 && !analysis.pending_question_live,
323
+ question: {
324
+ recent: question.recent,
325
+ pendingQuestions: question.pendingQuestions,
326
+ agentIdentity: question.agentIdentity,
327
+ chat: question.chat,
328
+ processingAt: question.processingAt,
329
+ messagesToClassify: question.messagesToClassify
330
+ }
331
+ };
332
+ if (analysis.path === "A") {
333
+ if (input.beforeDecide && !await input.beforeDecide(true)) return complete("observe", "admission_refused", "Admission refused before direct turn.");
334
+ return complete("reply", addressed && addressed !== "pending-question" ? addressed : "mention", "Agent explicitly addressed.");
335
+ }
336
+ if (analysis.path === "H") return complete("observe", "human_addressee", "Message addresses another participant; conversation and question invitation closed.");
337
+ if (signals.addressing.eligible === false) return complete("observe", "channel_ineligible", "Channel excludes ambient messages.");
338
+ if (groups.mode === "mention") return complete("observe", "mention_policy", "Mention policy observes unaddressed messages.");
339
+ if (input.beforeDecide && !await input.beforeDecide()) return complete("observe", "admission_refused", "Admission refused before inference.");
340
+ const result = await input.decide(question);
341
+ const diagnostics = typeof result === "string" ? void 0 : result.diagnostics;
342
+ const probabilities = diagnostics?.addressing;
343
+ const threshold = conversationThreshold(analysis.path, groups);
344
+ const valid = probabilities !== void 0 && probabilities.addressee.choice === "agent" && (probabilities.addressee.probabilities?.agent ?? NaN) >= threshold && (probabilities.needs_response.probabilities?.true ?? NaN) >= (analysis.path === "B" || analysis.path === "X" ? .7 : .5) && (probabilities.ends_conversation.probabilities?.true ?? NaN) < .7;
345
+ payload = {
346
+ ...payload,
347
+ ...typeof result === "string" ? {} : result.router,
348
+ thresholds_applied: {
349
+ threshold,
350
+ path: analysis.path,
351
+ needs_response: analysis.path === "B" || analysis.path === "X" ? .7 : .5,
352
+ ends_conversation: .7
353
+ }
354
+ };
355
+ return complete(valid ? "reply" : "observe", `decision_model_${valid ? "reply" : "observe"}`, "Conversation addressing probabilities evaluated.", "decision-model", diagnostics);
356
+ } catch (error) {
357
+ console.error("[segmentstream] conversation routing failed", { error });
358
+ return complete("observe", error instanceof Error && error.name === "TimeoutError" ? "decision_model_timeout" : "decision_model_failed", "Conversation routing unavailable; observe fallback.");
359
+ }
295
360
  if (addressed !== void 0 && addressed !== "pending-question" && addressed !== "reply-to-bot") return complete("reply", addressed, `Bot addressed directly: ${addressed}.`);
296
361
  if (input.recipientAnswer && addressed === "reply-to-bot") return complete("reply", "pending-question", "Recipient supplied as the pending question's answer.");
297
362
  const elsewhere = otherRecipient(audience);
@@ -34,6 +34,15 @@ function archivedRoutingMessages(events, limit = 30) {
34
34
  "thread-with-agent",
35
35
  "pending-question"
36
36
  ].includes(event.reason ?? "")).map((event) => [event.messageId, event.reason]));
37
+ const ended = new Set(events.filter((event) => {
38
+ if (event.kind !== "decision" || event.messageId === null) return false;
39
+ try {
40
+ const data = JSON.parse(event.argsSummary ?? "{}");
41
+ return data.router?.conversation_ended === true || data.routing?.conversationEnded === true;
42
+ } catch {
43
+ return false;
44
+ }
45
+ }).map((event) => event.messageId));
37
46
  const membership = new Map();
38
47
  for (const event of [...events].reverse()) {
39
48
  if (event.kind !== "decision" || event.senderId === null) continue;
@@ -56,6 +65,7 @@ function archivedRoutingMessages(events, limit = 30) {
56
65
  const text = (event.text || derivedText || (metadata.media ? "" : "[attachment or service message]")).slice(0, 2e3);
57
66
  const common = {
58
67
  invitation: metadata.invitation ?? invitations.get(event.messageId ?? ""),
68
+ ...ended.has(event.messageId) ? { conversationEnded: true } : {},
59
69
  author: event.senderName ?? event.senderId ?? "unknown sender",
60
70
  text,
61
71
  ...metadata.media === void 0 ? {} : { media: metadata.media },
@@ -13,10 +13,11 @@ import { decisionRouter } from "../group-routing/router.mjs";
13
13
  import { runtimeToken } from "../runtime-token.mjs";
14
14
  import { groupSettings } from "../group-routing/settings.mjs";
15
15
  import { conversations } from "../conversations.mjs";
16
- import { t as agentBuild } from "../../../_chunks/build-metadata-DMD3azjO.mjs";
16
+ import { archivedRoutingMessages } from "../group-routing/routing-context.mjs";
17
+ import { t as agentBuild } from "../../../_chunks/build-metadata-CYEMFYjL.mjs";
17
18
  import { confirmationLocale } from "./confirmation-text.mjs";
18
19
  import { telegramSendScope } from "./send-queue.mjs";
19
- import { d as failureText, h as withDeliveryLocale, i as withReplacedText, m as safeTelegramFetch, n as currentInboundRequest, p as reportTelegramError, r as runInboundRequest, t as InboundRequest } from "../../../_chunks/inbound-request-B8Eyjz1O.mjs";
20
+ import { d as failureText, h as withDeliveryLocale, i as withReplacedText, m as safeTelegramFetch, n as currentInboundRequest, p as reportTelegramError, r as runInboundRequest, t as InboundRequest } from "../../../_chunks/inbound-request-BrWspFw5.mjs";
20
21
  import { attachmentLifecycle } from "./lifecycle.mjs";
21
22
  import { scopedAddress, sessionPolicy } from "./sessions.mjs";
22
23
  import { observedMessages } from "./observed.mjs";
@@ -175,6 +176,13 @@ function segmentstreamTelegram(options) {
175
176
  policy: settings().policy,
176
177
  decisionModel: options.models().decision,
177
178
  groups: options.groups?.(),
179
+ recentConversation: async (chatId, threadId) => archivedRoutingMessages((await archive.context({
180
+ provider: "telegram",
181
+ chatId,
182
+ ...threadId === void 0 ? {} : { threadId },
183
+ limit: 200,
184
+ includeDecisions: true
185
+ })).events, 100),
178
186
  cloudGroups: options.groups === void 0 ? void 0 : readGroups,
179
187
  signalSettings: settings().groups,
180
188
  recoverHeldApprovals: options.confirmationMode?.() === "deferred",
@@ -4,7 +4,7 @@ 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 { p as reportTelegramError } from "../../../_chunks/inbound-request-B8Eyjz1O.mjs";
7
+ import { p as reportTelegramError } from "../../../_chunks/inbound-request-BrWspFw5.mjs";
8
8
  import { describableImage } from "./images.mjs";
9
9
  import { MAXIMUM_ROUTING_PDF_CHARACTERS, extractRoutingPdf } from "./pdf-text.mjs";
10
10
  import { gateway, generateText } from "ai";
@@ -10,7 +10,7 @@ import { runtimeToken } from "../runtime-token.mjs";
10
10
  import { redactDiagnostic } from "../diagnostics.mjs";
11
11
  import { confirmationLocale } from "./confirmation-text.mjs";
12
12
  import { inTelegramSendQueue, telegramSendScope } from "./send-queue.mjs";
13
- import { c as customerCopy, f as formattingRefused, l as customerMessage, p as reportTelegramError } from "../../../_chunks/inbound-request-B8Eyjz1O.mjs";
13
+ import { c as customerCopy, f as formattingRefused, l as customerMessage, p as reportTelegramError } from "../../../_chunks/inbound-request-BrWspFw5.mjs";
14
14
  import { MAXIMUM_PROXY_BODY_BYTES, connectionProxy } from "../connection-proxy.mjs";
15
15
  import { ACT_AS_HEADER } from "../ontology-connection.mjs";
16
16
  import { AttachmentError, CONVERSATION_HEADER, SESSION_HEADER } from "../attachments.mjs";
@@ -8,7 +8,7 @@ import { proxyEnvironment } from "../environment.mjs";
8
8
  import { runtimeToken } from "../runtime-token.mjs";
9
9
  import { conversations } from "../conversations.mjs";
10
10
  import { telegramSendScope } from "./send-queue.mjs";
11
- import { m as safeTelegramFetch, p as reportTelegramError } from "../../../_chunks/inbound-request-B8Eyjz1O.mjs";
11
+ import { m as safeTelegramFetch, p as reportTelegramError } from "../../../_chunks/inbound-request-BrWspFw5.mjs";
12
12
  import { deferredConfirmations } from "../deferred-confirmations.mjs";
13
13
  import { expireDeferredRecord } from "./deferred-confirmations.mjs";
14
14
  import { connectionProxy } from "../connection-proxy.mjs";
@@ -10,7 +10,7 @@ import { conversationEvent } from "../conversations.mjs";
10
10
  import { humanText } from "../confirmation-description.mjs";
11
11
  import { renderConfirmation, withConfirmationText } from "./confirmation-text.mjs";
12
12
  import { chatPostCard, chatPostCardStatus, chatPostReceipt, withChatPostCard } from "./chat-post-card.mjs";
13
- import { n as currentInboundRequest, p as reportTelegramError } from "../../../_chunks/inbound-request-B8Eyjz1O.mjs";
13
+ import { n as currentInboundRequest, p as reportTelegramError } from "../../../_chunks/inbound-request-BrWspFw5.mjs";
14
14
  import { chatPostOperationId, isChatPost } from "../chat-post-confirmation.mjs";
15
15
  import { confirmations } from "../confirmations.mjs";
16
16
  import { formatAddress, scopedAddress } from "./sessions.mjs";
@@ -7,7 +7,7 @@ __eveCreateRequire(import.meta.url);
7
7
  import { currentConfirmationPrompt, observedChatKey } from "../group-routing/observed.mjs";
8
8
  import { conversationEvent } from "../conversations.mjs";
9
9
  import { archivedRoutingMessages } from "../group-routing/routing-context.mjs";
10
- import { n as currentInboundRequest, p as reportTelegramError, s as currentThreadId } from "../../../_chunks/inbound-request-B8Eyjz1O.mjs";
10
+ import { n as currentInboundRequest, p as reportTelegramError, s as currentThreadId } from "../../../_chunks/inbound-request-BrWspFw5.mjs";
11
11
  import { contentShape, messageContent } from "./content.mjs";
12
12
  import { messageMetadata } from "./observed.mjs";
13
13
  import { reportMembership } from "./membership.mjs";
@@ -5,5 +5,5 @@ const __filename = __eveFileURLToPath(import.meta.url);
5
5
  __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { isTechnicalText } from "../customer-text-safety.mjs";
8
- import { a as TelegramDeliveryError, c as customerCopy, d as failureText, f as formattingRefused, h as withDeliveryLocale, l as customerMessage, m as safeTelegramFetch, o as currentLocale, p as reportTelegramError, s as currentThreadId, u as customerText } from "../../../_chunks/inbound-request-B8Eyjz1O.mjs";
8
+ import { a as TelegramDeliveryError, c as customerCopy, d as failureText, f as formattingRefused, h as withDeliveryLocale, l as customerMessage, m as safeTelegramFetch, o as currentLocale, p as reportTelegramError, s as currentThreadId, u as customerText } from "../../../_chunks/inbound-request-BrWspFw5.mjs";
9
9
  export { TelegramDeliveryError, currentLocale, currentThreadId, customerCopy, customerMessage, customerText, failureText, formattingRefused, isTechnicalText, reportTelegramError, safeTelegramFetch, withDeliveryLocale };
@@ -10,7 +10,7 @@ import { conversationEvent } from "../conversations.mjs";
10
10
  import { humanText } from "../confirmation-description.mjs";
11
11
  import { renderConfirmation, withConfirmationText } from "./confirmation-text.mjs";
12
12
  import { chatPostCard, chatPostCardStatus, chatPostReceipt, withChatPostCard } from "./chat-post-card.mjs";
13
- import { n as currentInboundRequest, p as reportTelegramError } from "../../../_chunks/inbound-request-B8Eyjz1O.mjs";
13
+ import { n as currentInboundRequest, p as reportTelegramError } from "../../../_chunks/inbound-request-BrWspFw5.mjs";
14
14
  import { CONFIRMATION_TTL_MS, inputHash } from "../confirmations.mjs";
15
15
  import { OntologyToolRejected } from "../ontology-tools.mjs";
16
16
  import { confirmationOutcome } from "../confirmation-outcome.mjs";
@@ -5,7 +5,7 @@ const __filename = __eveFileURLToPath(import.meta.url);
5
5
  __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { conversationLanguage } from "./principal.mjs";
8
- import { d as failureText, p as reportTelegramError } from "../../../_chunks/inbound-request-B8Eyjz1O.mjs";
8
+ import { d as failureText, p as reportTelegramError } from "../../../_chunks/inbound-request-BrWspFw5.mjs";
9
9
  import { describableImage, describeReceivedImage } from "./images.mjs";
10
10
  import { MAXIMUM_ATTACHMENT_BYTES, attachmentUrl } from "../attachments.mjs";
11
11
  function telegramConversation(chatId) {
@@ -4,7 +4,7 @@ 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 { t as agentBuild } from "../../../_chunks/build-metadata-DMD3azjO.mjs";
7
+ import { t as agentBuild } from "../../../_chunks/build-metadata-CYEMFYjL.mjs";
8
8
  import { runtimeRequestFailure } from "./runtime-request.mjs";
9
9
  import { GET } from "eve/channels";
10
10
  const HEALTH_ROUTE = "/eve/v1/telegram/health";
@@ -4,7 +4,7 @@ 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 { p as reportTelegramError } from "../../../_chunks/inbound-request-B8Eyjz1O.mjs";
7
+ import { p as reportTelegramError } from "../../../_chunks/inbound-request-BrWspFw5.mjs";
8
8
  import { gateway, generateText } from "ai";
9
9
  const MAXIMUM_IMAGE_DESCRIPTION_BYTES = 5242880;
10
10
  const MAXIMUM_NATIVE_IMAGE_BYTES = 3145728;
@@ -30,6 +30,8 @@ export declare class InboundRequest {
30
30
  readonly agentDescription: () => Promise<string>;
31
31
  readonly agentName: () => Promise<string>;
32
32
  readonly traceId: `${string}-${string}-${string}-${string}-${string}`;
33
+ /** Conversation policy coalesces only the active caller; Eve queues other callers. */
34
+ turnPolicy: "queue" | "steer";
33
35
  edited: boolean;
34
36
  triggeringMessageId: string | undefined;
35
37
  chatId: string | undefined;
@@ -4,5 +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 { i as withReplacedText, n as currentInboundRequest, r as runInboundRequest, t as InboundRequest } from "../../../_chunks/inbound-request-B8Eyjz1O.mjs";
7
+ import { i as withReplacedText, n as currentInboundRequest, r as runInboundRequest, t as InboundRequest } from "../../../_chunks/inbound-request-BrWspFw5.mjs";
8
8
  export { InboundRequest, currentInboundRequest, runInboundRequest, withReplacedText };
@@ -1,3 +1,4 @@
1
+ import type { RecentMessage } from "../group-routing/observed.js";
1
2
  import type { CloudGroupSettings } from "../group-routing/settings.js";
2
3
  import { type GroupPolicy } from "../group-routing/policy.js";
3
4
  import type { TelegramSettings } from "../../config.js";
@@ -44,6 +45,7 @@ export interface InboundDependencies {
44
45
  readonly policy: TelegramPolicy;
45
46
  readonly decisionModel?: string;
46
47
  readonly groups?: GroupPolicy;
48
+ readonly recentConversation?: (chatId: string, threadId?: string) => Promise<ReadonlyArray<RecentMessage>>;
47
49
  readonly cloudGroups?: () => Promise<CloudGroupSettings | undefined>;
48
50
  readonly signalSettings?: TelegramSettings["groups"];
49
51
  /** Recover old held sessions only when the workspace has chosen non-blocking confirmations. */
@@ -7,10 +7,12 @@ __eveCreateRequire(import.meta.url);
7
7
  import { effectiveGroupPolicy, groupPolicy } from "../group-routing/policy.mjs";
8
8
  import { conversationLanguage, telegramPrincipal } from "./principal.mjs";
9
9
  import { addressingReason, conversationKey, otherRecipient } from "../group-routing/signals.mjs";
10
+ import { conversationAddressing, conversationState } from "../group-routing/conversation.mjs";
10
11
  import { questionInvitation } from "../group-routing/engagement.mjs";
11
12
  import { conversationContext, routeMessage } from "../group-routing/router.mjs";
12
13
  import { observedContext } from "../group-routing/observed.mjs";
13
- import { p as reportTelegramError } from "../../../_chunks/inbound-request-B8Eyjz1O.mjs";
14
+ import { debounceBurst, routingBurst } from "../group-routing/burst.mjs";
15
+ import { p as reportTelegramError } from "../../../_chunks/inbound-request-BrWspFw5.mjs";
14
16
  import { startTypingHeartbeat, typingUntilComplete } from "./lifecycle.mjs";
15
17
  import { formatAddress, messageAddress, scopedAddress } from "./sessions.mjs";
16
18
  import { contentShape, messageContent } from "./content.mjs";
@@ -453,6 +455,19 @@ async function handleInboundMessage(input) {
453
455
  mode: dependencies.policy,
454
456
  model: dependencies.decisionModel
455
457
  }), conversationKey(policySignals), registryChat?.scope !== void 0 && registryChat.scope !== policySignals.conversation.scope ? void 0 : registryChat?.routing, cloudGroups?.settings);
458
+ const conversationPolicy = group && groups.smart.engagement.policy === "conversation";
459
+ const agentAliases = [...groups.smart.engagement.agentAliases, ...[
460
+ bot?.username,
461
+ bot?.firstName,
462
+ bot?.displayName
463
+ ].filter((n) => !!n)];
464
+ const freshConversation = async () => dependencies.recentConversation ? dependencies.recentConversation(sent.chat.id, sent.messageThreadId === void 0 ? void 0 : String(sent.messageThreadId)) : dependencies.observed.recent(chat);
465
+ let conversationRecent = conversationPolicy ? await freshConversation() : dependencies.observed.recent(chat);
466
+ if (conversationPolicy && input.request) {
467
+ address = topicAddress;
468
+ input.request.address = topicAddress;
469
+ input.request.turnPolicy = "steer";
470
+ }
456
471
  const pending = questionFromHistory(questionHistory, questionOwner);
457
472
  let answering = pending !== void 0 && (input.request?.lifecycleKey === void 0 || pending.taskId === input.request.lifecycleKey) && pending.memberId === member.id && pending.chatId === sent.chat.id && pending.messageThreadId === String(sent.messageThreadId ?? "");
458
473
  const asked = questionHistory.findLast((event) => {
@@ -463,12 +478,21 @@ async function handleInboundMessage(input) {
463
478
  });
464
479
  const questionAge = Date.parse(sentAt(sent)) - Date.parse(asked?.meta.at ?? "");
465
480
  if (answering && addressed === void 0 && !questionInvitation(asked?.meta.at, sentAt(sent), groups.smart.engagement.openQuestionMinutes)) answering = false;
466
- const recipientAnswer = answering && (groups.smart.engagement.policy === "engaged" || questionAge >= 0 && questionAge <= groups.smart.engagement.followUpMinutes * 6e4) && audience.replyTo?.kind === "bot" && isRecipientAnswer(audience);
467
- if (answering && pending !== void 0 && input.request !== void 0) {
481
+ if (conversationPolicy && answering) {
482
+ if (!conversationState({
483
+ signals: policySignals,
484
+ recent: conversationRecent,
485
+ policy: groups,
486
+ agentNames: agentAliases,
487
+ pendingQuestionAt: asked?.meta.at
488
+ }).pending_question_live) answering = false;
489
+ }
490
+ const recipientAnswer = !conversationPolicy && answering && (groups.smart.engagement.policy === "engaged" || questionAge >= 0 && questionAge <= groups.smart.engagement.followUpMinutes * 6e4) && audience.replyTo?.kind === "bot" && isRecipientAnswer(audience);
491
+ if (!conversationPolicy && answering && pending !== void 0 && input.request !== void 0) {
468
492
  address = pending.address || (questionSession === void 0 ? topicAddress : memberQuestionAddress);
469
493
  input.request.address = address;
470
494
  }
471
- if (input.request?.lifecycleKey === void 0 && group && (addressed === void 0 && !answering && groups.mode === "mention" || elsewhere !== void 0 && (addressed === void 0 || addressed === "reply-to-bot" && !recipientAnswer))) {
495
+ if (input.request?.lifecycleKey === void 0 && group && (addressed === void 0 && !answering && groups.mode === "mention" || !conversationPolicy && elsewhere !== void 0 && (addressed === void 0 || addressed === "reply-to-bot" && !recipientAnswer))) {
472
496
  await recordInboundContent({
473
497
  text,
474
498
  decoded
@@ -480,7 +504,7 @@ async function handleInboundMessage(input) {
480
504
  });
481
505
  return null;
482
506
  }
483
- if (hasContent && answering && !await admitTurn(true)) {
507
+ if (!conversationPolicy && hasContent && answering && !await admitTurn(true)) {
484
508
  await observeRaw();
485
509
  return null;
486
510
  }
@@ -529,7 +553,7 @@ async function handleInboundMessage(input) {
529
553
  address,
530
554
  operations,
531
555
  read: dependencies.history ?? dependencies.readHistory,
532
- remembered: dependencies.observed.recent(chat),
556
+ remembered: conversationPolicy ? conversationRecent : dependencies.observed.recent(chat),
533
557
  at: sentAt(sent),
534
558
  questionHistory,
535
559
  questionSessionId: owner?.id
@@ -539,7 +563,8 @@ async function handleInboundMessage(input) {
539
563
  name,
540
564
  bot?.displayName,
541
565
  bot?.firstName,
542
- bot?.username
566
+ bot?.username,
567
+ ...groups.smart.engagement.agentAliases
543
568
  ].filter((alias) => alias !== void 0 && alias !== ""))].slice(0, 8).map((alias) => alias.slice(0, 128));
544
569
  question = {
545
570
  groups,
@@ -558,7 +583,7 @@ async function handleInboundMessage(input) {
558
583
  aliases
559
584
  },
560
585
  chat: group ? chatContext(dependencies.chats.chat(sent.chat.id)) : [],
561
- recent: context.recent,
586
+ recent: conversationPolicy ? conversationRecent : context.recent,
562
587
  pendingConfirmations: context.pendingConfirmations,
563
588
  pendingQuestions: context.pendingQuestions.filter((question) => question.chatId === sent.chat.id && question.messageThreadId === String(sent.messageThreadId ?? "")),
564
589
  message: observedMessage(sent, text, member, media, bot),
@@ -567,6 +592,48 @@ async function handleInboundMessage(input) {
567
592
  };
568
593
  return question;
569
594
  };
595
+ let burstId;
596
+ if (conversationPolicy && input.request?.lifecycleKey === void 0) {
597
+ if (voice !== void 0) await transcribeVoice(true);
598
+ const routingSignals = {
599
+ ...policySignals,
600
+ message: {
601
+ ...policySignals.message,
602
+ text
603
+ }
604
+ };
605
+ const evidence = conversationAddressing(routingSignals, conversationRecent, groups, agentAliases);
606
+ if (!evidence.agent_named && !evidence.reply_to_agent && evidence.human_targets.length === 0) {
607
+ await recordInboundContent({
608
+ text,
609
+ decoded
610
+ });
611
+ const refreshed = await debounceBurst({
612
+ signals: routingSignals,
613
+ policy: groups,
614
+ read: freshConversation
615
+ });
616
+ conversationRecent = refreshed.recent;
617
+ question = void 0;
618
+ if (refreshed.burst.superseded) {
619
+ recordGateDecision("burst_superseded");
620
+ return null;
621
+ }
622
+ }
623
+ const burst = routingBurst(routingSignals, conversationRecent, groups);
624
+ burstId = burst.id;
625
+ const q = await conversation();
626
+ question = {
627
+ ...q,
628
+ signals: routingSignals,
629
+ message: {
630
+ ...q.message,
631
+ text
632
+ },
633
+ burstId: burst.id,
634
+ messagesToClassify: burst.messages
635
+ };
636
+ }
570
637
  const route = input.request?.lifecycleKey !== void 0 ? "reply" : await routeMessage({
571
638
  chatId: sent.chat.id,
572
639
  memberId: member.id,
@@ -574,6 +641,10 @@ async function handleInboundMessage(input) {
574
641
  groups,
575
642
  signals: {
576
643
  ...policySignals,
644
+ message: {
645
+ ...policySignals.message,
646
+ text
647
+ },
577
648
  sender: {
578
649
  ...policySignals.sender,
579
650
  member
@@ -586,7 +657,7 @@ async function handleInboundMessage(input) {
586
657
  record: (decision) => {
587
658
  if (decision.reason !== "admission_refused") recordRouteDecision(decision.route, addressed, groups.mode, decision);
588
659
  },
589
- beforeDecide: () => admitTurn(false),
660
+ beforeDecide: (direct) => admitTurn(direct ?? false),
590
661
  decide: dependencies.decide,
591
662
  question: async () => {
592
663
  classified = true;
@@ -631,6 +702,19 @@ async function handleInboundMessage(input) {
631
702
  decoded,
632
703
  ...processedMedia === void 0 ? {} : { media: processedMedia }
633
704
  });
705
+ if (conversationPolicy) {
706
+ const current = await conversation();
707
+ const message = {
708
+ ...current.message,
709
+ text,
710
+ media
711
+ };
712
+ question = {
713
+ ...current,
714
+ message,
715
+ messagesToClassify: current.messagesToClassify?.map((m) => m.messageId === message.messageId ? message : m)
716
+ };
717
+ }
634
718
  return conversation();
635
719
  }
636
720
  });
@@ -810,7 +894,7 @@ async function handleInboundMessage(input) {
810
894
  }
811
895
  if (voice !== void 0 && input.request !== void 0) input.request.text = text;
812
896
  else if (voice !== void 0) notes.push(text);
813
- if (answering && pending !== void 0 && input.request !== void 0) {
897
+ if (!conversationPolicy && answering && pending !== void 0 && input.request !== void 0) {
814
898
  input.request.replyTo = pending.replyToMessageId ?? input.request.replyTo;
815
899
  input.request.lifecycleKey = pending.taskId ?? input.request.lifecycleKey;
816
900
  input.request.text = `${answerMarker(pending.id)}\n${text}`;
@@ -868,7 +952,7 @@ async function handleInboundMessage(input) {
868
952
  return null;
869
953
  }
870
954
  dependencies.observed.remember(chat, observedMessage(sent, text, member, retainedMedia, bot));
871
- const principal = telegramPrincipal({
955
+ let principal = telegramPrincipal({
872
956
  message: {
873
957
  ...sent,
874
958
  text
@@ -876,6 +960,32 @@ async function handleInboundMessage(input) {
876
960
  member,
877
961
  conversationLanguage: language
878
962
  });
963
+ if (conversationPolicy && input.request?.lifecycleKey === void 0) principal = {
964
+ ...principal,
965
+ attributes: {
966
+ ...principal.attributes,
967
+ groupRouting: JSON.stringify({
968
+ policy: {
969
+ ...groups,
970
+ chats: {}
971
+ },
972
+ signals: {
973
+ ...policySignals,
974
+ message: {
975
+ ...policySignals.message,
976
+ text
977
+ }
978
+ },
979
+ agent: question?.agent.slice(0, 600) ?? "",
980
+ agentIdentity: question?.agentIdentity,
981
+ chat: question?.chat,
982
+ burst_id: burstId ?? routingBurst(policySignals, conversationRecent, groups).id,
983
+ message: question?.message,
984
+ pendingQuestions: question?.pendingQuestions,
985
+ pendingQuestionAt: asked?.meta.at
986
+ })
987
+ }
988
+ };
879
989
  return {
880
990
  auth: answering && pending !== void 0 ? {
881
991
  ...principal,
@@ -923,6 +1033,7 @@ async function handleInboundMessage(input) {
923
1033
  group
924
1034
  }),
925
1035
  ...processedMedia === void 0 ? [] : [processedMedia],
1036
+ ...conversationPolicy ? ["If this member declines your involvement or closes the conversation, output exactly [SILENT]. Do not acknowledge the rejection or perform actions."] : [],
926
1037
  ...notes
927
1038
  ]
928
1039
  };