@segmentstream/agent 0.26.1 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -43
- package/dist/_chunks/{build-metadata-D_Gqlb5f.mjs → build-metadata-CMmCRD8l.mjs} +1 -1
- package/dist/extension/config.d.ts +11 -12
- package/dist/extension/config.mjs +20 -37
- package/dist/extension/extension.d.ts +2 -2
- package/dist/extension/extension.mjs +3 -3
- package/dist/extension/hooks/chat-context.d.ts +3 -0
- package/dist/extension/hooks/chat-context.mjs +10 -0
- package/dist/extension/hooks/sandbox.d.ts +2 -3
- package/dist/extension/instructions/conversations.d.ts +3 -2
- package/dist/extension/instructions/conversations.mjs +7 -11
- package/dist/extension/instructions.md +18 -0
- package/dist/extension/lib/build-metadata.mjs +1 -1
- package/dist/extension/lib/chat-context.d.ts +108 -0
- package/dist/extension/lib/chat-context.mjs +217 -0
- package/dist/extension/lib/conversation-runtime.d.ts +1 -1
- package/dist/extension/lib/conversation-runtime.mjs +1 -23
- package/dist/extension/lib/conversations.d.ts +10 -6
- package/dist/extension/lib/conversations.mjs +28 -42
- package/dist/extension/lib/group-routing/addressing.d.ts +18 -0
- package/dist/extension/lib/group-routing/addressing.mjs +108 -0
- package/dist/extension/lib/group-routing/burst.d.ts +1 -1
- package/dist/extension/lib/group-routing/burst.mjs +1 -1
- package/dist/extension/lib/group-routing/conversation-prompt.d.ts +5 -2
- package/dist/extension/lib/group-routing/conversation-prompt.mjs +26 -6
- package/dist/extension/lib/group-routing/conversation-router.d.ts +8 -16
- package/dist/extension/lib/group-routing/conversation-router.mjs +49 -93
- package/dist/extension/lib/group-routing/dequeue.mjs +1 -1
- package/dist/extension/lib/group-routing/engagement.d.ts +1 -1
- package/dist/extension/lib/group-routing/{observed.d.ts → messages.d.ts} +9 -33
- package/dist/extension/lib/group-routing/messages.mjs +38 -0
- package/dist/extension/lib/group-routing/policy.d.ts +13 -9
- package/dist/extension/lib/group-routing/policy.mjs +35 -25
- package/dist/extension/lib/group-routing/router.d.ts +3 -11
- package/dist/extension/lib/group-routing/router.mjs +11 -35
- package/dist/extension/lib/group-routing/routing-context.d.ts +6 -2
- package/dist/extension/lib/group-routing/routing-context.mjs +12 -11
- package/dist/extension/lib/telegram/adapter.d.ts +1 -3
- package/dist/extension/lib/telegram/adapter.mjs +9 -18
- package/dist/extension/lib/telegram/confirmations.d.ts +3 -3
- package/dist/extension/lib/telegram/confirmations.mjs +3 -25
- package/dist/extension/lib/telegram/conversations.d.ts +14 -3
- package/dist/extension/lib/telegram/conversations.mjs +16 -37
- package/dist/extension/lib/telegram/delivery-diagnostics.d.ts +4 -1
- package/dist/extension/lib/telegram/delivery-diagnostics.mjs +9 -3
- package/dist/extension/lib/telegram/health.mjs +1 -1
- package/dist/extension/lib/telegram/inbound.d.ts +14 -14
- package/dist/extension/lib/telegram/inbound.mjs +73 -78
- package/dist/extension/lib/telegram/lifecycle.mjs +1 -1
- package/dist/extension/lib/telegram/{observed.d.ts → message-metadata.d.ts} +2 -6
- package/dist/extension/lib/telegram/{observed.mjs → message-metadata.mjs} +1 -14
- package/dist/extension/lib/telegram/routing-context.d.ts +4 -3
- package/dist/extension/lib/telegram/routing-context.mjs +2 -2
- package/dist/extension/lib/telegram/sessions.d.ts +5 -52
- package/dist/extension/lib/telegram/sessions.mjs +1 -188
- package/dist/extension/lib/telegram/signals.mjs +1 -1
- package/dist/extension/lib/telegram/silent.d.ts +5 -6
- package/dist/extension/lib/telegram/silent.mjs +4 -31
- package/package.json +1 -1
- package/dist/extension/lib/group-routing/conversation.d.ts +0 -46
- package/dist/extension/lib/group-routing/conversation.mjs +0 -269
- package/dist/extension/lib/group-routing/observed.mjs +0 -103
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type ConversationAnalysis } from "./conversation.js";
|
|
2
1
|
import type { RouteQuestion, RouteDecider } from "./router.js";
|
|
3
2
|
export interface AddressingAnswers {
|
|
4
3
|
readonly addressee: {
|
|
@@ -11,13 +10,13 @@ export interface AddressingAnswers {
|
|
|
11
10
|
readonly type?: "boolean";
|
|
12
11
|
readonly probabilities?: Readonly<Record<"true" | "false", number>>;
|
|
13
12
|
};
|
|
14
|
-
readonly ends_conversation: {
|
|
15
|
-
readonly choice?: boolean;
|
|
16
|
-
readonly probability?: number;
|
|
17
|
-
readonly type?: "boolean";
|
|
18
|
-
readonly probabilities?: Readonly<Record<"true" | "false", number>>;
|
|
19
|
-
};
|
|
20
13
|
}
|
|
14
|
+
/** The agent answers when it is the likeliest addressee, by a majority, and a reply is needed. */
|
|
15
|
+
export declare const ADDRESSED_TO_AGENT: {
|
|
16
|
+
readonly addressee: 0.5;
|
|
17
|
+
readonly needsResponse: 0.5;
|
|
18
|
+
};
|
|
19
|
+
export declare function addressedToAgent(answers: AddressingAnswers | undefined): boolean;
|
|
21
20
|
export declare function conversationQuestions(instructions: string): {
|
|
22
21
|
addressee: {
|
|
23
22
|
type: "choice";
|
|
@@ -37,14 +36,6 @@ export declare function conversationQuestions(instructions: string): {
|
|
|
37
36
|
false: string;
|
|
38
37
|
};
|
|
39
38
|
};
|
|
40
|
-
ends_conversation: {
|
|
41
|
-
type: "boolean";
|
|
42
|
-
instructions: string;
|
|
43
|
-
criteria: {
|
|
44
|
-
true: string;
|
|
45
|
-
false: string;
|
|
46
|
-
};
|
|
47
|
-
};
|
|
48
39
|
};
|
|
49
40
|
export type ConversationDecideFunction = (options: {
|
|
50
41
|
readonly model: string;
|
|
@@ -54,5 +45,6 @@ export type ConversationDecideFunction = (options: {
|
|
|
54
45
|
}) => Promise<{
|
|
55
46
|
readonly answers: AddressingAnswers;
|
|
56
47
|
}>;
|
|
57
|
-
|
|
48
|
+
/** The chat's latest messages as the classifier reads them, however old: no verdicts, only facts. */
|
|
49
|
+
export declare function conversationModelState(question: RouteQuestion): Parameters<ConversationDecideFunction>[0]["state"];
|
|
58
50
|
export declare function conversationDecisionRouter(decide: ConversationDecideFunction): RouteDecider;
|
|
@@ -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,
|
|
8
|
-
|
|
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
|
|
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
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
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,
|
|
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
|
-
|
|
81
|
-
|
|
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
|
|
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
|
|
131
|
-
|
|
132
|
-
|
|
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
|
|
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:
|
|
158
|
-
observe: 1 -
|
|
121
|
+
reply: agent,
|
|
122
|
+
observe: 1 - agent
|
|
159
123
|
},
|
|
160
124
|
addressing: answers
|
|
161
125
|
};
|
|
162
126
|
return {
|
|
163
|
-
route:
|
|
127
|
+
route: addressedToAgent(answers) ? "reply" : "observe",
|
|
164
128
|
diagnostics,
|
|
165
129
|
router: {
|
|
166
130
|
state,
|
|
167
131
|
questions,
|
|
168
132
|
answer: answers,
|
|
169
|
-
|
|
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 };
|
|
@@ -6,7 +6,7 @@ __eveDirname(__filename);
|
|
|
6
6
|
__eveCreateRequire(import.meta.url);
|
|
7
7
|
import { conversationEvent, conversations } from "../conversations.mjs";
|
|
8
8
|
import { addressingReason } from "./signals.mjs";
|
|
9
|
-
import { recordedMessageSignals } from "./
|
|
9
|
+
import { recordedMessageSignals } from "./addressing.mjs";
|
|
10
10
|
import { conversationDecisionRouter } from "./conversation-router.mjs";
|
|
11
11
|
import { routeMessage } from "./router.mjs";
|
|
12
12
|
import { archivedRoutingMessages } from "./routing-context.mjs";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { GroupPolicy, Participants } from "./policy.js";
|
|
2
2
|
import type { GroupMessageSignals } from "./signals.js";
|
|
3
|
-
import type { RecentMessage } from "./
|
|
3
|
+
import type { RecentMessage } from "./messages.js";
|
|
4
4
|
export interface Engagement {
|
|
5
5
|
readonly memberId: string;
|
|
6
6
|
readonly since: string;
|
|
@@ -1,27 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* available to decide whether a later unaddressed message answers one of the agent's questions.
|
|
5
|
-
*
|
|
6
|
-
* eve's `defineState` would be the durable home, but it is session state and needs an active eve
|
|
7
|
-
* context; an observed message has neither, because observing must not start a session. The ring
|
|
8
|
-
* lives in process memory, per chat and forum topic; webhook capture restores it from the existing
|
|
9
|
-
* conversation archive after a cold start.
|
|
2
|
+
* Group conversation messages as routing reads them from the conversation archive: received
|
|
3
|
+
* messages, observed or answered, and the agent's delivered replies and confirmation cards.
|
|
10
4
|
*/
|
|
11
5
|
import type { Attachment } from "../attachments.js";
|
|
12
6
|
import type { MessageAudience } from "./signals.js";
|
|
13
|
-
/** How many
|
|
14
|
-
export declare const
|
|
7
|
+
/** How many recent messages routing reads by default. */
|
|
8
|
+
export declare const RECENT_MESSAGES = 30;
|
|
15
9
|
export interface ObservedMessage {
|
|
16
10
|
/** How the chat names the author: their username, else their first name. */
|
|
17
11
|
readonly author: string;
|
|
12
|
+
/** How intake named the sender: a linked member's name and roles, then their handle. */
|
|
13
|
+
readonly signedAuthor?: string;
|
|
18
14
|
readonly invitation?: string;
|
|
19
|
-
/** Classifier rejection closes routing hints without cancelling the underlying task. */
|
|
20
|
-
readonly conversationEnded?: boolean;
|
|
21
15
|
readonly text: string;
|
|
22
16
|
readonly media?: string;
|
|
23
|
-
/**
|
|
17
|
+
/** Stored native images, by reference only: never image bytes or a sender's download grant. */
|
|
24
18
|
readonly images?: ReadonlyArray<Pick<Attachment, "id" | "filename" | "contentType" | "size" | "expiresAt">>;
|
|
19
|
+
/** The message started a turn, so a session received it. */
|
|
20
|
+
readonly admitted?: true;
|
|
25
21
|
/** ISO time the message was sent. */
|
|
26
22
|
readonly at: string;
|
|
27
23
|
/** Stable Telegram identity, independent of changes to the member's display name or roles. */
|
|
@@ -72,18 +68,6 @@ export interface ConfirmationPrompt extends PendingConfirmation {
|
|
|
72
68
|
export declare function withConfirmationPrompt<T>(prompt: ConfirmationPrompt, run: () => T): T;
|
|
73
69
|
export declare function currentConfirmationPrompt(): ConfirmationPrompt | undefined;
|
|
74
70
|
export type RecentMessage = ObservedMessage | AgentMessage;
|
|
75
|
-
export interface ObservedMessages {
|
|
76
|
-
add(chat: string, message: ObservedMessage): void;
|
|
77
|
-
/** Records a received message that starts a turn, or a successfully delivered agent reply. */
|
|
78
|
-
remember(chat: string, message: RecentMessage): void;
|
|
79
|
-
list(chat: string): ReadonlyArray<ObservedMessage>;
|
|
80
|
-
recent(chat: string): ReadonlyArray<RecentMessage>;
|
|
81
|
-
/** Returns and forgets the chat's ring, once a reply has carried it into a turn. */
|
|
82
|
-
take(chat: string): ReadonlyArray<ObservedMessage>;
|
|
83
|
-
/** An idle reset forgets both the backlog and the earlier conversation. */
|
|
84
|
-
clear(chat: string): void;
|
|
85
|
-
}
|
|
86
|
-
export declare function observedMessages(capacity?: number): ObservedMessages;
|
|
87
71
|
/** Recipient metadata for an agent reply; explicit mentions are visible even in proactive turns. */
|
|
88
72
|
export declare function agentMessage(input: {
|
|
89
73
|
readonly text: string;
|
|
@@ -92,11 +76,3 @@ export declare function agentMessage(input: {
|
|
|
92
76
|
readonly messageId?: string;
|
|
93
77
|
readonly replyTo?: ObservedMessage["replyTo"];
|
|
94
78
|
}): AgentMessage;
|
|
95
|
-
/** The key of a chat's ring: the chat and, in a forum, the topic. */
|
|
96
|
-
export declare function observedChatKey(input: {
|
|
97
|
-
readonly chatId: string;
|
|
98
|
-
readonly messageThreadId?: number;
|
|
99
|
-
readonly provider?: string;
|
|
100
|
-
}): string;
|
|
101
|
-
/** The ring as turn context: one block the model reads before the message it answers. */
|
|
102
|
-
export declare function observedContext(messages: ReadonlyArray<ObservedMessage>, conversation?: Readonly<Record<string, string | ReadonlyArray<string>>>): ReadonlyArray<string>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { fileURLToPath as __eveFileURLToPath } from "node:url";
|
|
2
|
+
import { dirname as __eveDirname } from "node:path";
|
|
3
|
+
import { createRequire as __eveCreateRequire } from "node:module";
|
|
4
|
+
const __filename = __eveFileURLToPath(import.meta.url);
|
|
5
|
+
__eveDirname(__filename);
|
|
6
|
+
__eveCreateRequire(import.meta.url);
|
|
7
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
8
|
+
const RECENT_MESSAGES = 30;
|
|
9
|
+
const MAXIMUM_TEXT_LENGTH = 1e3;
|
|
10
|
+
const confirmationPrompt = new AsyncLocalStorage();
|
|
11
|
+
function withConfirmationPrompt(prompt, run) {
|
|
12
|
+
return confirmationPrompt.run(prompt, run);
|
|
13
|
+
}
|
|
14
|
+
function currentConfirmationPrompt() {
|
|
15
|
+
return confirmationPrompt.getStore();
|
|
16
|
+
}
|
|
17
|
+
function agentMessage(input) {
|
|
18
|
+
const usernames = Array.from(input.text.matchAll(/(?:^|[^A-Za-z0-9_])@([A-Za-z0-9_]+)/gu), (match) => match[1] ?? "");
|
|
19
|
+
const addressedTo = usernames.length > 0 ? [...new Set(usernames)].map((username) => ({
|
|
20
|
+
author: `@${username}`,
|
|
21
|
+
username
|
|
22
|
+
})) : input.inReplyTo === void 0 ? [] : [input.inReplyTo];
|
|
23
|
+
return {
|
|
24
|
+
...input,
|
|
25
|
+
text: input.text.slice(0, MAXIMUM_TEXT_LENGTH),
|
|
26
|
+
author: "the agent",
|
|
27
|
+
role: "agent",
|
|
28
|
+
kind: "bot",
|
|
29
|
+
taggedUsers: [...new Set(usernames)].slice(0, 50).map((username) => ({
|
|
30
|
+
type: "mention",
|
|
31
|
+
kind: "unknown",
|
|
32
|
+
username,
|
|
33
|
+
text: `@${username}`
|
|
34
|
+
})),
|
|
35
|
+
addressedTo
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export { RECENT_MESSAGES, agentMessage, currentConfirmationPrompt, withConfirmationPrompt };
|
|
@@ -29,10 +29,6 @@ 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
33
|
readonly quietSeconds: number;
|
|
38
34
|
readonly maxSeconds: number;
|
|
@@ -44,11 +40,6 @@ export interface SmartPolicy {
|
|
|
44
40
|
readonly addresseeGate: boolean;
|
|
45
41
|
};
|
|
46
42
|
readonly thresholds: {
|
|
47
|
-
readonly continuation: number;
|
|
48
|
-
readonly inferredContinuation: number;
|
|
49
|
-
readonly contested: number;
|
|
50
|
-
readonly ambient: number;
|
|
51
|
-
readonly humanExchange: number;
|
|
52
43
|
readonly engaged: number;
|
|
53
44
|
readonly neutral: number;
|
|
54
45
|
readonly humanThread: number;
|
|
@@ -67,6 +58,19 @@ export interface GroupPolicy {
|
|
|
67
58
|
readonly sources: Readonly<Record<string, string>>;
|
|
68
59
|
}
|
|
69
60
|
export declare function smartPolicy(input?: SmartPolicyInput, base?: SmartPolicy): SmartPolicy;
|
|
61
|
+
/**
|
|
62
|
+
* Settings of the former conversation state machine (timers and per-state thresholds). They are
|
|
63
|
+
* still accepted so that existing configurations deploy, and are ignored: the classifier reads the
|
|
64
|
+
* chat's recent messages instead.
|
|
65
|
+
*/
|
|
66
|
+
export declare const IGNORED_GROUP_SETTINGS: {
|
|
67
|
+
readonly engagement: readonly ["idleMinutes", "humanExchangeMinutes", "humanQuestionMinutes", "inferredMaxMinutes"];
|
|
68
|
+
readonly thresholds: readonly ["continuation", "inferredContinuation", "contested", "ambient", "humanExchange"];
|
|
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,7 +59,7 @@ 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
64
|
quietSeconds: 6,
|
|
69
65
|
maxSeconds: 15,
|
|
@@ -76,25 +72,44 @@ function smartPolicy(input = {}, base) {
|
|
|
76
72
|
engaged: .5,
|
|
77
73
|
neutral: conversation ? .9 : engaged ? .75 : .5,
|
|
78
74
|
humanThread: conversation ? .95 : engaged ? .9 : .5,
|
|
79
|
-
continuation: .5,
|
|
80
|
-
inferredContinuation: .7,
|
|
81
|
-
contested: .8,
|
|
82
|
-
ambient: .9,
|
|
83
|
-
humanExchange: .95,
|
|
84
75
|
...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
|
-
} : {}
|
|
76
|
+
...withoutIgnored(input.thresholds, IGNORED_GROUP_SETTINGS.thresholds)
|
|
91
77
|
},
|
|
92
78
|
timeoutSeconds: input.timeoutSeconds ?? base?.timeoutSeconds ?? 15
|
|
93
79
|
};
|
|
94
80
|
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
|
|
81
|
+
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
82
|
return policy;
|
|
97
83
|
}
|
|
84
|
+
const IGNORED_GROUP_SETTINGS = {
|
|
85
|
+
engagement: [
|
|
86
|
+
"idleMinutes",
|
|
87
|
+
"humanExchangeMinutes",
|
|
88
|
+
"humanQuestionMinutes",
|
|
89
|
+
"inferredMaxMinutes"
|
|
90
|
+
],
|
|
91
|
+
thresholds: [
|
|
92
|
+
"continuation",
|
|
93
|
+
"inferredContinuation",
|
|
94
|
+
"contested",
|
|
95
|
+
"ambient",
|
|
96
|
+
"humanExchange"
|
|
97
|
+
]
|
|
98
|
+
};
|
|
99
|
+
function withoutIgnored(value, ignored) {
|
|
100
|
+
return Object.fromEntries(Object.entries(value ?? {}).filter(([key]) => !ignored.includes(key)));
|
|
101
|
+
}
|
|
102
|
+
const IGNORED_SUFFIXES = Object.entries(IGNORED_GROUP_SETTINGS).flatMap(([section, keys]) => keys.map((key) => `${section}.${key}`));
|
|
103
|
+
function ignoredGroupSetting(path) {
|
|
104
|
+
return IGNORED_SUFFIXES.includes(path.slice(-2).join("."));
|
|
105
|
+
}
|
|
106
|
+
function ignoredGroupSettings(value, prefix = []) {
|
|
107
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return ignoredGroupSetting(prefix) ? [prefix.join(".")] : [];
|
|
108
|
+
return Object.entries(value).flatMap(([key, child]) => ignoredGroupSettings(child, [...prefix, key]));
|
|
109
|
+
}
|
|
110
|
+
function authored(value, source) {
|
|
111
|
+
return Object.fromEntries(Object.entries(sources(value, source)).filter(([path]) => !ignoredGroupSetting(path.split("."))));
|
|
112
|
+
}
|
|
98
113
|
function sources(value, source, prefix = "") {
|
|
99
114
|
return Object.fromEntries(Object.entries(value).flatMap(([key, value]) => {
|
|
100
115
|
const path = prefix ? `${prefix}.${key}` : key;
|
|
@@ -122,7 +137,7 @@ function groupPolicy(input = {}, aliases) {
|
|
|
122
137
|
}, "platform"),
|
|
123
138
|
...aliases?.mode ? { mode: "channels.telegram.policy" } : {},
|
|
124
139
|
...aliases?.model ? { "smart.model": "models.decision" } : {},
|
|
125
|
-
...
|
|
140
|
+
...authored(input, "agent")
|
|
126
141
|
}
|
|
127
142
|
};
|
|
128
143
|
}
|
|
@@ -144,11 +159,6 @@ function overlaySmartPolicy(input, base) {
|
|
|
144
159
|
addresseeGate: inherited("engagement.addresseeGate") ? defaults.engagement.addresseeGate : base.smart.engagement.addresseeGate
|
|
145
160
|
},
|
|
146
161
|
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
162
|
engaged: inherited("thresholds.engaged") ? defaults.thresholds.engaged : base.smart.thresholds.engaged,
|
|
153
163
|
neutral: inherited("thresholds.neutral") ? defaults.thresholds.neutral : base.smart.thresholds.neutral,
|
|
154
164
|
humanThread: inherited("thresholds.humanThread") ? defaults.thresholds.humanThread : base.smart.thresholds.humanThread
|
|
@@ -169,11 +179,11 @@ function effectiveGroupPolicy(base, key) {
|
|
|
169
179
|
},
|
|
170
180
|
sources: {
|
|
171
181
|
...result.sources,
|
|
172
|
-
...
|
|
182
|
+
...authored(layer, "agent.chat")
|
|
173
183
|
}
|
|
174
184
|
};
|
|
175
185
|
const thresholds = result.smart.thresholds;
|
|
176
186
|
if (thresholds.engaged > thresholds.neutral || thresholds.neutral > thresholds.humanThread) throw new RangeError("Group thresholds must satisfy engaged <= neutral <= humanThread after inheritance.");
|
|
177
187
|
return result;
|
|
178
188
|
}
|
|
179
|
-
export { DEFAULT_CRITERIA, DEFAULT_GROUP_POLICY, ENGAGED_CRITERIA, ENGAGED_INSTRUCTIONS, LEGACY_INSTRUCTIONS, effectiveGroupPolicy, groupPolicy, overlaySmartPolicy, smartPolicy };
|
|
189
|
+
export { DEFAULT_CRITERIA, DEFAULT_GROUP_POLICY, ENGAGED_CRITERIA, ENGAGED_INSTRUCTIONS, IGNORED_GROUP_SETTINGS, LEGACY_INSTRUCTIONS, effectiveGroupPolicy, groupPolicy, ignoredGroupSetting, ignoredGroupSettings, overlaySmartPolicy, smartPolicy };
|
|
@@ -1,7 +1,6 @@
|
|
|
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
|
-
import type { ObservedMessage, PendingConfirmation, RecentMessage } from "./
|
|
3
|
+
import type { ObservedMessage, PendingConfirmation, RecentMessage } from "./messages.js";
|
|
5
4
|
import { type MessageAudience, type AgentIdentity as BotIdentity, type AddressingReason, type GroupMessageSignals } from "./signals.js";
|
|
6
5
|
import type { PendingQuestion } from "../questions.js";
|
|
7
6
|
export type { AddressingReason } from "./signals.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>;
|
|
@@ -84,14 +82,8 @@ export type DecideFunction = (options: {
|
|
|
84
82
|
/** One decision should take a moment; a slow one gives up and observes. */
|
|
85
83
|
/** An unthreaded answer minutes later may continue a question, but old questions are not hints. */
|
|
86
84
|
export declare const FOLLOW_UP_WINDOW_MS: number;
|
|
87
|
-
/**
|
|
88
|
-
|
|
89
|
-
* With `since`, `recent_messages` keep only messages at or after that time, for a main-model turn
|
|
90
|
-
* whose session history already holds what earlier turns received; routing omits it.
|
|
91
|
-
*/
|
|
92
|
-
export declare function conversationContext(question: RouteQuestion, options?: {
|
|
93
|
-
readonly since?: number;
|
|
94
|
-
}): Parameters<DecideFunction>[0]["state"];
|
|
85
|
+
/** Bounded conversation evidence for the routing decision. Facts are hints only. */
|
|
86
|
+
export declare function conversationContext(question: RouteQuestion): Parameters<DecideFunction>[0]["state"];
|
|
95
87
|
/** A {@link RouteDecider} that asks one `decide` question on the configured decision model. */
|
|
96
88
|
export declare function decisionRouter(input: {
|
|
97
89
|
readonly decide: DecideFunction;
|