@segmentstream/agent 0.27.0 → 0.29.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 (36) hide show
  1. package/dist/_chunks/{build-metadata-R_ssK2uR.mjs → build-metadata-DZs-ivRD.mjs} +1 -1
  2. package/dist/extension/config.mjs +7 -1
  3. package/dist/extension/extension.d.ts +2 -2
  4. package/dist/extension/extension.mjs +3 -3
  5. package/dist/extension/lib/build-metadata.mjs +1 -1
  6. package/dist/extension/lib/conversation-runtime.d.ts +1 -1
  7. package/dist/extension/lib/conversation-runtime.mjs +1 -23
  8. package/dist/extension/lib/group-routing/addressing.d.ts +18 -0
  9. package/dist/extension/lib/group-routing/addressing.mjs +108 -0
  10. package/dist/extension/lib/group-routing/burst.d.ts +0 -17
  11. package/dist/extension/lib/group-routing/burst.mjs +2 -18
  12. package/dist/extension/lib/group-routing/conversation-prompt.d.ts +5 -2
  13. package/dist/extension/lib/group-routing/conversation-prompt.mjs +26 -6
  14. package/dist/extension/lib/group-routing/conversation-router.d.ts +8 -16
  15. package/dist/extension/lib/group-routing/conversation-router.mjs +49 -93
  16. package/dist/extension/lib/group-routing/messages.d.ts +0 -2
  17. package/dist/extension/lib/group-routing/policy.d.ts +15 -11
  18. package/dist/extension/lib/group-routing/policy.mjs +37 -29
  19. package/dist/extension/lib/group-routing/router.d.ts +0 -2
  20. package/dist/extension/lib/group-routing/router.mjs +9 -31
  21. package/dist/extension/lib/group-routing/routing-context.mjs +0 -10
  22. package/dist/extension/lib/telegram/adapter.mjs +1 -1
  23. package/dist/extension/lib/telegram/health.mjs +1 -1
  24. package/dist/extension/lib/telegram/inbound-request.d.ts +1 -1
  25. package/dist/extension/lib/telegram/inbound.mjs +1 -62
  26. package/dist/extension/lib/telegram/lifecycle.mjs +1 -1
  27. package/dist/extension/lib/telegram/turn-errors.mjs +0 -19
  28. package/package.json +1 -1
  29. package/dist/extension/hooks/group-routing.d.ts +0 -2
  30. package/dist/extension/hooks/group-routing.mjs +0 -10
  31. package/dist/extension/lib/group-routing/cancellation.d.ts +0 -4
  32. package/dist/extension/lib/group-routing/cancellation.mjs +0 -22
  33. package/dist/extension/lib/group-routing/conversation.d.ts +0 -46
  34. package/dist/extension/lib/group-routing/conversation.mjs +0 -269
  35. package/dist/extension/lib/group-routing/dequeue.d.ts +0 -5
  36. package/dist/extension/lib/group-routing/dequeue.mjs +0 -145
@@ -4,8 +4,15 @@ 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 { ADDRESSEE_CRITERIA, ENDS_CONVERSATION_INSTRUCTIONS, NEEDS_RESPONSE_INSTRUCTIONS } from "./conversation-prompt.mjs";
8
- import { conversationState, conversationThreshold } from "./conversation.mjs";
7
+ import { ADDRESSEE_CRITERIA, NEEDS_RESPONSE_INSTRUCTIONS } from "./conversation-prompt.mjs";
8
+ const ADDRESSED_TO_AGENT = {
9
+ addressee: .5,
10
+ needsResponse: .5
11
+ };
12
+ const RECENT_CHARACTERS = 24e3;
13
+ function addressedToAgent(answers) {
14
+ return answers !== void 0 && answers.addressee.choice === "agent" && (answers.addressee.probabilities?.agent ?? NaN) >= ADDRESSED_TO_AGENT.addressee && (answers.needs_response.probabilities?.true ?? NaN) >= ADDRESSED_TO_AGENT.needsResponse;
15
+ }
9
16
  function conversationQuestions(instructions) {
10
17
  return {
11
18
  addressee: {
@@ -20,28 +27,38 @@ function conversationQuestions(instructions) {
20
27
  true: "Asks for or needs an answer or action.",
21
28
  false: "Acknowledgement, reaction, status, closing or remark."
22
29
  }
23
- },
24
- ends_conversation: {
25
- type: "boolean",
26
- instructions: ENDS_CONVERSATION_INSTRUCTIONS,
27
- criteria: {
28
- true: "Ends or declines the agent's involvement.",
29
- false: "Anything else."
30
- }
31
30
  }
32
31
  };
33
32
  }
34
- function conversationModelState(question, analysis) {
33
+ function conversationModelState(question) {
35
34
  const groups = question.groups;
36
35
  const now = Date.parse(question.processingAt ?? question.message.at);
37
36
  const own = (m) => "role" in m && m.role === "agent";
38
- const classifiedIds = new Set((question.messagesToClassify ?? [question.message]).map((m) => m.messageId));
39
- const conversationStart = analysis.conversation.status === "active" && analysis.conversation.opened_at !== void 0 ? Date.parse(analysis.conversation.opened_at) : now - 9e5;
40
- const recent = question.recent.filter((m) => !classifiedIds.has(m.messageId)).filter((m) => now - Date.parse(m.at) >= 0 && now - Date.parse(m.at) <= 9e5 && Date.parse(m.at) >= conversationStart).slice(-Math.min(20, groups.smart.context.messages));
41
- const last = recent.findLast((m) => own(m) && "addressedTo" in m && m.addressedTo.some((p) => p.id === question.signals?.sender.externalId || p.username && p.username === question.signals?.sender.username));
42
- const pending = analysis.pending_question_live ? question.pendingQuestions?.at(-1) : void 0;
37
+ const classified = question.messagesToClassify ?? [question.message];
38
+ const classifiedIds = new Set(classified.map((m) => m.messageId));
39
+ const format = (m, media) => JSON.stringify({
40
+ at: m.at,
41
+ id: m.messageId ?? null,
42
+ author: own(m) ? "THE AGENT" : m.author,
43
+ author_id: m.authorId ?? null,
44
+ text: m.text.slice(0, 1e3),
45
+ media: m.media?.slice(0, media),
46
+ reply_to: m.replyTo ?? null,
47
+ tags: m.taggedUsers ?? [],
48
+ attachments: m.attachmentKinds ?? []
49
+ });
50
+ const candidates = question.recent.filter((m) => !classifiedIds.has(m.messageId) && Date.parse(m.at) <= now).slice(-groups.smart.context.messages);
51
+ const lines = [];
52
+ let characters = 0;
53
+ for (const m of [...candidates].reverse()) {
54
+ const line = format(m, 500);
55
+ if (lines.length > 0 && characters + line.length > RECENT_CHARACTERS) break;
56
+ lines.unshift(line);
57
+ characters += line.length;
58
+ }
59
+ const recent = candidates.slice(candidates.length - lines.length);
43
60
  const roster = new Map();
44
- for (const m of [...recent, question.message]) {
61
+ for (const m of [...recent, ...classified]) {
45
62
  if (own(m)) continue;
46
63
  const id = m.authorId ?? m.username ?? m.author;
47
64
  roster.set(id, {
@@ -52,16 +69,6 @@ function conversationModelState(question, analysis) {
52
69
  kind: m.kind ?? "human"
53
70
  });
54
71
  }
55
- const format = (m) => JSON.stringify({
56
- at: m.at,
57
- author: own(m) ? "THE AGENT" : m.author,
58
- author_id: m.authorId ?? null,
59
- text: m.text.slice(0, 1e3),
60
- media: m.media?.slice(0, 2e3),
61
- reply_to: m.replyTo ?? null,
62
- tags: m.taggedUsers ?? [],
63
- attachments: m.attachmentKinds ?? []
64
- });
65
72
  return {
66
73
  agent: JSON.stringify({
67
74
  names: [...new Set([
@@ -77,47 +84,14 @@ function conversationModelState(question, analysis) {
77
84
  purpose: (groups.smart.context.includeChatGuidance ? question.chat ?? [] : []).join("\n").slice(0, 500)
78
85
  }),
79
86
  participants: groups.smart.context.includeParticipants ? [...roster.values()].map((p) => JSON.stringify(p)) : [],
80
- route_context: JSON.stringify({
81
- path: {
82
- A: "explicit",
83
- H: "human_target",
84
- C: "continuation",
85
- Ci: "inferred_continuation",
86
- C2: "contested",
87
- B: "ambient",
88
- X: "human_exchange"
89
- }[analysis.path],
90
- conversation_with_sender: {
91
- ...analysis.conversation,
92
- seconds_since_last_exchange: analysis.conversation.last_exchange_at ? Math.floor((now - Date.parse(analysis.conversation.last_exchange_at)) / 1e3) : null,
93
- agent_last_message_to_sender: last ? {
94
- text: last.text.slice(0, 600),
95
- seconds_ago: Math.floor((now - Date.parse(last.at)) / 1e3)
96
- } : null,
97
- agent_open_question: pending ? {
98
- text: pending.question.slice(0, 300),
99
- seconds_ago: pending.at ? Math.floor((now - Date.parse(pending.at)) / 1e3) : null
100
- } : null
101
- },
102
- human_exchange: analysis.human_exchange,
103
- other_live_agent_conversations: analysis.other_live_agent_conversations
104
- }),
105
- recent_messages: recent.map(format),
106
- messages_to_classify: (question.messagesToClassify ?? [question.message]).map(format)
87
+ recent_messages: lines,
88
+ messages_to_classify: classified.map((m) => format(m, 2e3))
107
89
  };
108
90
  }
109
91
  function conversationDecisionRouter(decide) {
110
92
  return async (question) => {
111
93
  const policy = question.groups;
112
- const analysis = question.conversation ?? conversationState({
113
- signals: question.signals,
114
- recent: question.recent,
115
- policy,
116
- agentNames: question.agentIdentity?.aliases,
117
- pendingQuestionAt: question.pendingQuestions?.at(-1)?.at,
118
- asOf: question.processingAt
119
- });
120
- const state = conversationModelState(question, analysis);
94
+ const state = conversationModelState(question);
121
95
  const questions = conversationQuestions(policy.smart.instructions);
122
96
  const started = performance.now();
123
97
  const signal = AbortSignal.timeout(Math.ceil(policy.smart.timeoutSeconds * 1e3));
@@ -127,56 +101,38 @@ function conversationDecisionRouter(decide) {
127
101
  questions,
128
102
  signal
129
103
  });
130
- const normalize = (answer) => {
131
- const p = answer.probabilities?.true ?? answer.probability ?? NaN;
132
- return {
104
+ const p = raw.needs_response.probabilities?.true ?? raw.needs_response.probability ?? NaN;
105
+ const answers = {
106
+ ...raw,
107
+ needs_response: {
133
108
  choice: p >= .5,
134
109
  probabilities: {
135
110
  true: p,
136
111
  false: 1 - p
137
112
  }
138
- };
139
- };
140
- const answers = {
141
- ...raw,
142
- needs_response: normalize(raw.needs_response),
143
- ends_conversation: normalize(raw.ends_conversation)
113
+ }
144
114
  };
145
- const p = answers.addressee.probabilities?.agent ?? NaN, needs = answers.needs_response.probabilities?.true ?? NaN, ends = answers.ends_conversation.probabilities?.true ?? NaN;
146
- if ([
147
- p,
148
- needs,
149
- ends
150
- ].some((x) => !Number.isFinite(x) || x < 0 || x > 1)) throw new Error("Conversation classifier probabilities unavailable.");
151
- const threshold = conversationThreshold(analysis.path, policy);
152
- const reply = answers.addressee.choice === "agent" && p >= threshold && needs >= (analysis.path === "B" || analysis.path === "X" ? .7 : .5) && ends < .7;
115
+ const agent = answers.addressee.probabilities?.agent ?? NaN;
116
+ if ([agent, p].some((x) => !Number.isFinite(x) || x < 0 || x > 1)) throw new Error("Conversation classifier probabilities unavailable.");
153
117
  const diagnostics = {
154
118
  model: policy.smart.model,
155
119
  latencyMs: Math.round(performance.now() - started),
156
120
  probabilities: {
157
- reply: p,
158
- observe: 1 - p
121
+ reply: agent,
122
+ observe: 1 - agent
159
123
  },
160
124
  addressing: answers
161
125
  };
162
126
  return {
163
- route: reply ? "reply" : "observe",
127
+ route: addressedToAgent(answers) ? "reply" : "observe",
164
128
  diagnostics,
165
129
  router: {
166
130
  state,
167
131
  questions,
168
132
  answer: answers,
169
- path: analysis.path,
170
- conversation: analysis.conversation,
171
- human_exchange: analysis.human_exchange,
172
- thresholds_applied: {
173
- threshold,
174
- needs_response: analysis.path === "B" || analysis.path === "X" ? .7 : .5,
175
- ends_conversation: .7
176
- },
177
- conversation_ended: ends >= .7
133
+ thresholds_applied: ADDRESSED_TO_AGENT
178
134
  }
179
135
  };
180
136
  };
181
137
  }
182
- export { conversationDecisionRouter, conversationModelState, conversationQuestions };
138
+ export { ADDRESSED_TO_AGENT, addressedToAgent, conversationDecisionRouter, conversationModelState, conversationQuestions };
@@ -12,8 +12,6 @@ export interface ObservedMessage {
12
12
  /** How intake named the sender: a linked member's name and roles, then their handle. */
13
13
  readonly signedAuthor?: string;
14
14
  readonly invitation?: string;
15
- /** Classifier rejection closes routing hints without cancelling the underlying task. */
16
- readonly conversationEnded?: boolean;
17
15
  readonly text: string;
18
16
  readonly media?: string;
19
17
  /** Stored native images, by reference only: never image bytes or a sender's download grant. */
@@ -29,13 +29,7 @@ export interface SmartPolicy {
29
29
  readonly engagement: {
30
30
  readonly policy: "legacy" | "engaged" | "conversation";
31
31
  readonly agentAliases: ReadonlyArray<string>;
32
- readonly idleMinutes: number;
33
- readonly humanExchangeMinutes: number;
34
- readonly humanQuestionMinutes: number;
35
- readonly inferredMaxMinutes: number;
36
32
  readonly burst: {
37
- readonly quietSeconds: number;
38
- readonly maxSeconds: number;
39
33
  readonly gapSeconds: number;
40
34
  };
41
35
  readonly followUpMinutes: number;
@@ -44,11 +38,6 @@ export interface SmartPolicy {
44
38
  readonly addresseeGate: boolean;
45
39
  };
46
40
  readonly thresholds: {
47
- readonly continuation: number;
48
- readonly inferredContinuation: number;
49
- readonly contested: number;
50
- readonly ambient: number;
51
- readonly humanExchange: number;
52
41
  readonly engaged: number;
53
42
  readonly neutral: number;
54
43
  readonly humanThread: number;
@@ -67,6 +56,21 @@ export interface GroupPolicy {
67
56
  readonly sources: Readonly<Record<string, string>>;
68
57
  }
69
58
  export declare function smartPolicy(input?: SmartPolicyInput, base?: SmartPolicy): SmartPolicy;
59
+ /**
60
+ * Settings of former SDK mechanisms: the conversation state machine's timers and per-state
61
+ * thresholds, and the webhook's wait for the rest of a burst. They are still accepted so that
62
+ * existing configurations deploy, and are ignored: the classifier reads the chat's recent
63
+ * messages, and a member's next message joins their active turn through eve's steering.
64
+ */
65
+ export declare const IGNORED_GROUP_SETTINGS: {
66
+ readonly engagement: readonly ["idleMinutes", "humanExchangeMinutes", "humanQuestionMinutes", "inferredMaxMinutes"];
67
+ readonly thresholds: readonly ["continuation", "inferredContinuation", "contested", "ambient", "humanExchange"];
68
+ readonly burst: readonly ["quietSeconds", "maxSeconds"];
69
+ };
70
+ /** Whether a settings path names an ignored setting, in the agent, a chat or the shadow policy. */
71
+ export declare function ignoredGroupSetting(path: ReadonlyArray<string>): boolean;
72
+ /** The dotted paths of ignored settings an authored `groups` value sets, for a deploy warning. */
73
+ export declare function ignoredGroupSettings(value: unknown, prefix?: ReadonlyArray<string>): string[];
70
74
  export declare function groupPolicy(input?: GroupPolicyInput, aliases?: {
71
75
  readonly mode?: string;
72
76
  readonly model?: string;
@@ -48,10 +48,6 @@ function smartPolicy(input = {}, base) {
48
48
  engagement: {
49
49
  policy: selected,
50
50
  agentAliases: [],
51
- idleMinutes: 10,
52
- humanExchangeMinutes: 5,
53
- humanQuestionMinutes: 30,
54
- inferredMaxMinutes: 30,
55
51
  followUpMinutes: 30,
56
52
  conversationMinutes: 10,
57
53
  ...base?.engagement,
@@ -63,38 +59,55 @@ function smartPolicy(input = {}, base) {
63
59
  openQuestionMinutes: engaged ? 30 : "unlimited",
64
60
  addresseeGate: engaged
65
61
  } : {},
66
- ...input.engagement,
62
+ ...withoutIgnored(input.engagement, IGNORED_GROUP_SETTINGS.engagement),
67
63
  burst: {
68
- quietSeconds: 6,
69
- maxSeconds: 15,
70
64
  gapSeconds: 30,
71
65
  ...base?.engagement.burst,
72
- ...input.engagement?.burst
66
+ ...withoutIgnored(input.engagement?.burst, IGNORED_GROUP_SETTINGS.burst)
73
67
  }
74
68
  },
75
69
  thresholds: {
76
70
  engaged: .5,
77
71
  neutral: conversation ? .9 : engaged ? .75 : .5,
78
72
  humanThread: conversation ? .95 : engaged ? .9 : .5,
79
- continuation: .5,
80
- inferredContinuation: .7,
81
- contested: .8,
82
- ambient: .9,
83
- humanExchange: .95,
84
73
  ...changed ? {} : base?.thresholds,
85
- ...input.thresholds,
86
- ...conversation ? {
87
- continuation: input.thresholds?.continuation ?? input.thresholds?.engaged ?? (changed ? .5 : base?.thresholds.continuation) ?? .5,
88
- ambient: input.thresholds?.ambient ?? input.thresholds?.neutral ?? (changed ? .9 : base?.thresholds.ambient) ?? .9,
89
- humanExchange: input.thresholds?.humanExchange ?? input.thresholds?.humanThread ?? (changed ? .95 : base?.thresholds.humanExchange) ?? .95
90
- } : {}
74
+ ...withoutIgnored(input.thresholds, IGNORED_GROUP_SETTINGS.thresholds)
91
75
  },
92
76
  timeoutSeconds: input.timeoutSeconds ?? base?.timeoutSeconds ?? 15
93
77
  };
94
- if (policy.engagement.burst.quietSeconds > policy.engagement.burst.maxSeconds) throw new RangeError("Burst quietSeconds must not exceed maxSeconds after inheritance.");
95
- if ((conversation ? policy.instructions.length + JSON.stringify(ADDRESSEE_CRITERIA).length + 260 + 185 : policy.instructions.length + policy.criteria.observe.length + policy.criteria.reply.length) > 8e3) throw new RangeError("Group instructions and criteria must fit 8000 characters after inheritance.");
78
+ if ((conversation ? policy.instructions.length + JSON.stringify(ADDRESSEE_CRITERIA).length + 260 : policy.instructions.length + policy.criteria.observe.length + policy.criteria.reply.length) > 8e3) throw new RangeError("Group instructions and criteria must fit 8000 characters after inheritance.");
96
79
  return policy;
97
80
  }
81
+ const IGNORED_GROUP_SETTINGS = {
82
+ engagement: [
83
+ "idleMinutes",
84
+ "humanExchangeMinutes",
85
+ "humanQuestionMinutes",
86
+ "inferredMaxMinutes"
87
+ ],
88
+ thresholds: [
89
+ "continuation",
90
+ "inferredContinuation",
91
+ "contested",
92
+ "ambient",
93
+ "humanExchange"
94
+ ],
95
+ burst: ["quietSeconds", "maxSeconds"]
96
+ };
97
+ function withoutIgnored(value, ignored) {
98
+ return Object.fromEntries(Object.entries(value ?? {}).filter(([key]) => !ignored.includes(key)));
99
+ }
100
+ const IGNORED_SUFFIXES = Object.entries(IGNORED_GROUP_SETTINGS).flatMap(([section, keys]) => keys.map((key) => `${section}.${key}`));
101
+ function ignoredGroupSetting(path) {
102
+ return IGNORED_SUFFIXES.includes(path.slice(-2).join("."));
103
+ }
104
+ function ignoredGroupSettings(value, prefix = []) {
105
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return ignoredGroupSetting(prefix) ? [prefix.join(".")] : [];
106
+ return Object.entries(value).flatMap(([key, child]) => ignoredGroupSettings(child, [...prefix, key]));
107
+ }
108
+ function authored(value, source) {
109
+ return Object.fromEntries(Object.entries(sources(value, source)).filter(([path]) => !ignoredGroupSetting(path.split("."))));
110
+ }
98
111
  function sources(value, source, prefix = "") {
99
112
  return Object.fromEntries(Object.entries(value).flatMap(([key, value]) => {
100
113
  const path = prefix ? `${prefix}.${key}` : key;
@@ -122,7 +135,7 @@ function groupPolicy(input = {}, aliases) {
122
135
  }, "platform"),
123
136
  ...aliases?.mode ? { mode: "channels.telegram.policy" } : {},
124
137
  ...aliases?.model ? { "smart.model": "models.decision" } : {},
125
- ...sources(input, "agent")
138
+ ...authored(input, "agent")
126
139
  }
127
140
  };
128
141
  }
@@ -144,11 +157,6 @@ function overlaySmartPolicy(input, base) {
144
157
  addresseeGate: inherited("engagement.addresseeGate") ? defaults.engagement.addresseeGate : base.smart.engagement.addresseeGate
145
158
  },
146
159
  thresholds: {
147
- continuation: inherited("thresholds.continuation") ? defaults.thresholds.continuation : base.smart.thresholds.continuation,
148
- inferredContinuation: inherited("thresholds.inferredContinuation") ? defaults.thresholds.inferredContinuation : base.smart.thresholds.inferredContinuation,
149
- contested: inherited("thresholds.contested") ? defaults.thresholds.contested : base.smart.thresholds.contested,
150
- ambient: inherited("thresholds.ambient") ? defaults.thresholds.ambient : base.smart.thresholds.ambient,
151
- humanExchange: inherited("thresholds.humanExchange") ? defaults.thresholds.humanExchange : base.smart.thresholds.humanExchange,
152
160
  engaged: inherited("thresholds.engaged") ? defaults.thresholds.engaged : base.smart.thresholds.engaged,
153
161
  neutral: inherited("thresholds.neutral") ? defaults.thresholds.neutral : base.smart.thresholds.neutral,
154
162
  humanThread: inherited("thresholds.humanThread") ? defaults.thresholds.humanThread : base.smart.thresholds.humanThread
@@ -169,11 +177,11 @@ function effectiveGroupPolicy(base, key) {
169
177
  },
170
178
  sources: {
171
179
  ...result.sources,
172
- ...sources(layer, "agent.chat")
180
+ ...authored(layer, "agent.chat")
173
181
  }
174
182
  };
175
183
  const thresholds = result.smart.thresholds;
176
184
  if (thresholds.engaged > thresholds.neutral || thresholds.neutral > thresholds.humanThread) throw new RangeError("Group thresholds must satisfy engaged <= neutral <= humanThread after inheritance.");
177
185
  return result;
178
186
  }
179
- export { DEFAULT_CRITERIA, DEFAULT_GROUP_POLICY, ENGAGED_CRITERIA, ENGAGED_INSTRUCTIONS, LEGACY_INSTRUCTIONS, effectiveGroupPolicy, groupPolicy, overlaySmartPolicy, smartPolicy };
187
+ export { DEFAULT_CRITERIA, DEFAULT_GROUP_POLICY, ENGAGED_CRITERIA, ENGAGED_INSTRUCTIONS, IGNORED_GROUP_SETTINGS, LEGACY_INSTRUCTIONS, effectiveGroupPolicy, groupPolicy, ignoredGroupSetting, ignoredGroupSettings, overlaySmartPolicy, smartPolicy };
@@ -1,4 +1,3 @@
1
- import { type ConversationAnalysis } from "./conversation.js";
2
1
  import { type AddressingAnswers } from "./conversation-router.js";
3
2
  import { type GroupPolicy } from "./policy.js";
4
3
  import type { ObservedMessage, PendingConfirmation, RecentMessage } from "./messages.js";
@@ -9,7 +8,6 @@ export type Route = "observe" | "reply";
9
8
  /** What the decision model sees. */
10
9
  export interface RouteQuestion {
11
10
  readonly groups?: GroupPolicy;
12
- readonly conversation?: ConversationAnalysis;
13
11
  readonly burstId?: string;
14
12
  readonly processingAt?: string;
15
13
  readonly messagesToClassify?: ReadonlyArray<ObservedMessage>;
@@ -6,8 +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
+ import { conversationAddressing } from "./addressing.mjs";
10
+ import { addressedToAgent, conversationDecisionRouter } from "./conversation-router.mjs";
11
11
  import { recordRoutingMetric } from "./health.mjs";
12
12
  import { humanAddressee, humanThread, questionInvitation, restoreEngagement } from "./engagement.mjs";
13
13
  import { boundedRouterPayload, policyHash } from "./record.mjs";
@@ -300,25 +300,11 @@ async function routeMessage(input) {
300
300
  groups,
301
301
  signals
302
302
  };
303
- const analysis = conversationState({
304
- signals,
305
- recent: question.recent,
306
- policy: groups,
307
- agentNames: [...question.agentIdentity?.aliases ?? [], ...question.agentIdentity?.username ? [question.agentIdentity.username] : []],
308
- pendingQuestionAt: input.pendingQuestionAt,
309
- asOf: question.processingAt
310
- });
311
- question = {
312
- ...question,
313
- conversation: analysis
314
- };
303
+ const evidence = conversationAddressing(signals, question.recent, groups, [...question.agentIdentity?.aliases ?? [], ...question.agentIdentity?.username ? [question.agentIdentity.username] : []]);
315
304
  payload = {
316
305
  ...payload,
317
306
  burst_id: question.burstId,
318
- path: analysis.path,
319
- conversation: analysis.conversation,
320
- human_exchange: analysis.human_exchange,
321
- pending_question_voided: input.pendingQuestionAt !== void 0 && !analysis.pending_question_live,
307
+ addressing_evidence: evidence,
322
308
  question: {
323
309
  recent: question.recent,
324
310
  pendingQuestions: question.pendingQuestions,
@@ -328,30 +314,22 @@ async function routeMessage(input) {
328
314
  messagesToClassify: question.messagesToClassify
329
315
  }
330
316
  };
331
- if (analysis.path === "A") {
317
+ if (evidence.agent_named || evidence.reply_to_agent && evidence.human_targets.length === 0 && evidence.soft_target === void 0) {
332
318
  if (input.beforeDecide && !await input.beforeDecide(true)) return complete("observe", "admission_refused", "Admission refused before direct turn.");
333
319
  return complete("reply", addressed && addressed !== "pending-question" ? addressed : "mention", "Agent explicitly addressed.");
334
320
  }
335
- if (analysis.path === "H") return complete("observe", "human_addressee", "Message addresses another participant; conversation and question invitation closed.");
321
+ if (evidence.human_targets.length > 0) return complete("observe", "human_addressee", "Message replies to, tags or names another participant.");
336
322
  if (signals.addressing.eligible === false) return complete("observe", "channel_ineligible", "Channel excludes ambient messages.");
337
323
  if (groups.mode === "mention") return complete("observe", "mention_policy", "Mention policy observes unaddressed messages.");
338
324
  if (input.beforeDecide && !await input.beforeDecide()) return complete("observe", "admission_refused", "Admission refused before inference.");
339
325
  const result = await input.decide(question);
340
326
  const diagnostics = typeof result === "string" ? void 0 : result.diagnostics;
341
- const probabilities = diagnostics?.addressing;
342
- const threshold = conversationThreshold(analysis.path, groups);
343
- 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;
327
+ const valid = addressedToAgent(diagnostics?.addressing);
344
328
  payload = {
345
329
  ...payload,
346
- ...typeof result === "string" ? {} : result.router,
347
- thresholds_applied: {
348
- threshold,
349
- path: analysis.path,
350
- needs_response: analysis.path === "B" || analysis.path === "X" ? .7 : .5,
351
- ends_conversation: .7
352
- }
330
+ ...typeof result === "string" ? {} : result.router
353
331
  };
354
- return complete(valid ? "reply" : "observe", `decision_model_${valid ? "reply" : "observe"}`, "Conversation addressing probabilities evaluated.", "decision-model", diagnostics);
332
+ return complete(valid ? "reply" : "observe", `decision_model_${valid ? "reply" : "observe"}`, "Addressee read from the chat's recent messages.", "decision-model", diagnostics);
355
333
  } catch (error) {
356
334
  console.error("[segmentstream] conversation routing failed", { error });
357
335
  return complete("observe", error instanceof Error && error.name === "TimeoutError" ? "decision_model_timeout" : "decision_model_failed", "Conversation routing unavailable; observe fallback.");
@@ -34,15 +34,6 @@ 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));
46
37
  const admitted = new Set(events.flatMap((event) => event.kind === "decision" && event.reason === "turn_admitted" && event.messageId !== null ? [event.messageId] : []));
47
38
  const membership = new Map();
48
39
  for (const event of [...events].reverse()) {
@@ -80,7 +71,6 @@ function archivedRoutingMessages(events, limit = 30) {
80
71
  invitation: metadata.invitation ?? invitations.get(event.messageId ?? ""),
81
72
  ...event.kind === "inbound" && admitted.has(event.messageId ?? "") ? { admitted: true } : {},
82
73
  ...images.length === 0 ? {} : { images },
83
- ...ended.has(event.messageId) ? { conversationEnded: true } : {},
84
74
  author: event.senderName ?? event.senderId ?? "unknown sender",
85
75
  ...typeof metadata.author === "string" && event.kind === "inbound" ? { signedAuthor: metadata.author } : {},
86
76
  text,
@@ -23,7 +23,7 @@ import { archivedRoutingMessages } from "../group-routing/routing-context.mjs";
23
23
  import { dispatchStructuredMessage, structuredMessage } from "./structured.mjs";
24
24
  import { scopedAddress } from "./sessions.mjs";
25
25
  import { handleWorkspaceCallback, isWorkspaceCallback } from "./callbacks.mjs";
26
- import { t as agentBuild } from "../../../_chunks/build-metadata-R_ssK2uR.mjs";
26
+ import { t as agentBuild } from "../../../_chunks/build-metadata-DZs-ivRD.mjs";
27
27
  import { attachmentLifecycle } from "./lifecycle.mjs";
28
28
  import { gatewayImageDescriber } from "./images.mjs";
29
29
  import { gatewayAttachmentDescriber } from "./attachment-description.mjs";
@@ -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-R_ssK2uR.mjs";
8
+ import { t as agentBuild } from "../../../_chunks/build-metadata-DZs-ivRD.mjs";
9
9
  import { GET } from "eve/channels";
10
10
  const HEALTH_ROUTE = "/eve/v1/telegram/health";
11
11
  function healthRoute(input) {
@@ -29,7 +29,7 @@ export declare class InboundRequest {
29
29
  readonly agentDescription: () => Promise<string>;
30
30
  readonly agentName: () => Promise<string>;
31
31
  readonly traceId: `${string}-${string}-${string}-${string}-${string}`;
32
- /** Conversation policy coalesces only the active caller; Eve queues other callers. */
32
+ /** Conversation policy steers the active caller's turn; Eve queues other callers. */
33
33
  turnPolicy: "queue" | "steer";
34
34
  edited: boolean;
35
35
  triggeringMessageId: string | undefined;
@@ -9,10 +9,9 @@ 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, conversationState } from "../group-routing/conversation.mjs";
13
12
  import { questionInvitation } from "../group-routing/engagement.mjs";
14
13
  import { routeMessage } from "../group-routing/router.mjs";
15
- import { debounceBurst, routingBurst } from "../group-routing/burst.mjs";
14
+ import { routingBurst } from "../group-routing/burst.mjs";
16
15
  import { WorkspaceActionAlreadyDispatched } from "./workspace-action.mjs";
17
16
  import { structuredMessage } from "./structured.mjs";
18
17
  import { formatAddress, messageAddress, scopedAddress } from "./sessions.mjs";
@@ -495,11 +494,6 @@ async function handleInboundMessage(input) {
495
494
  model: dependencies.decisionModel
496
495
  }), conversationKey(policySignals));
497
496
  const conversationPolicy = group && groups.smart.engagement.policy === "conversation";
498
- const agentAliases = [...groups.smart.engagement.agentAliases, ...[
499
- bot?.username,
500
- bot?.firstName,
501
- bot?.displayName
502
- ].filter((n) => !!n)];
503
497
  let conversationRecent = conversationPolicy ? await recentConversation() : [];
504
498
  if (conversationPolicy && input.request && input.request.lifecycleKey === void 0 && input.request.structuredContext === void 0) {
505
499
  address = topicAddress;
@@ -515,15 +509,6 @@ async function handleInboundMessage(input) {
515
509
  });
516
510
  const questionAge = Date.parse(sentAt(sent)) - Date.parse(asked?.meta.at ?? "");
517
511
  if (answering && addressed === void 0 && !questionInvitation(asked?.meta.at, sentAt(sent), groups.smart.engagement.openQuestionMinutes)) answering = false;
518
- if (conversationPolicy && answering) {
519
- if (!conversationState({
520
- signals: policySignals,
521
- recent: conversationRecent,
522
- policy: groups,
523
- agentNames: agentAliases,
524
- pendingQuestionAt: asked?.meta.at
525
- }).pending_question_live) answering = false;
526
- }
527
512
  const recipientAnswer = !conversationPolicy && answering && (groups.smart.engagement.policy === "engaged" || questionAge >= 0 && questionAge <= groups.smart.engagement.followUpMinutes * 6e4) && audience.replyTo?.kind === "bot" && isRecipientAnswer(audience);
528
513
  if (!conversationPolicy && answering && pending !== void 0 && input.request !== void 0) {
529
514
  address = pending.address || (questionSession === void 0 ? topicAddress : memberQuestionAddress);
@@ -635,7 +620,6 @@ async function handleInboundMessage(input) {
635
620
  };
636
621
  return question;
637
622
  };
638
- let burstId;
639
623
  if (conversationPolicy && input.request?.lifecycleKey === void 0 && input.request?.structuredContext === void 0) {
640
624
  if (voice !== void 0) await transcribeVoice(true);
641
625
  const routingSignals = {
@@ -645,26 +629,7 @@ async function handleInboundMessage(input) {
645
629
  text
646
630
  }
647
631
  };
648
- const evidence = conversationAddressing(routingSignals, conversationRecent, groups, agentAliases);
649
- if (!evidence.agent_named && !evidence.reply_to_agent && evidence.human_targets.length === 0) {
650
- await recordContent({
651
- text,
652
- decoded
653
- });
654
- const refreshed = await debounceBurst({
655
- signals: routingSignals,
656
- policy: groups,
657
- read: freshConversation
658
- });
659
- conversationRecent = refreshed.recent;
660
- question = void 0;
661
- if (refreshed.burst.superseded) {
662
- recordGateDecision("burst_superseded");
663
- return null;
664
- }
665
- }
666
632
  const burst = routingBurst(routingSignals, conversationRecent, groups);
667
- burstId = burst.id;
668
633
  const q = await conversation();
669
634
  question = {
670
635
  ...q,
@@ -1063,32 +1028,6 @@ async function handleInboundMessage(input) {
1063
1028
  chatGuidance: guidance
1064
1029
  }
1065
1030
  };
1066
- if (conversationPolicy && input.request?.lifecycleKey === void 0 && input.request?.structuredContext === void 0) principal = {
1067
- ...principal,
1068
- attributes: {
1069
- ...principal.attributes,
1070
- groupRouting: JSON.stringify({
1071
- policy: {
1072
- ...groups,
1073
- chats: {}
1074
- },
1075
- signals: {
1076
- ...policySignals,
1077
- message: {
1078
- ...policySignals.message,
1079
- text
1080
- }
1081
- },
1082
- agent: question?.agent.slice(0, 600) ?? "",
1083
- agentIdentity: question?.agentIdentity,
1084
- chat: question?.chat,
1085
- burst_id: burstId ?? routingBurst(policySignals, conversationRecent, groups).id,
1086
- message: question?.message,
1087
- pendingQuestions: question?.pendingQuestions,
1088
- pendingQuestionAt: asked?.meta.at
1089
- })
1090
- }
1091
- };
1092
1031
  return {
1093
1032
  auth: answering && pending !== void 0 ? {
1094
1033
  ...principal,
@@ -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-R_ssK2uR.mjs";
13
+ import { t as agentBuild } from "../../../_chunks/build-metadata-DZs-ivRD.mjs";
14
14
  var AttachmentLifecycleNotPermitted = class extends Error {
15
15
  code = "attachment_lifecycle_not_permitted";
16
16
  };