@segmentstream/agent 0.8.23 → 0.9.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/dist/_chunks/{build-metadata-26dmE9nn.mjs → build-metadata-DuHeGf1m.mjs} +1 -1
- package/dist/_chunks/content-CvFsrmSD.mjs +102 -0
- package/dist/extension/channels/telegram.mjs +1 -0
- package/dist/extension/config.d.ts +16 -2
- package/dist/extension/config.mjs +92 -3
- package/dist/extension/extension.d.ts +7 -0
- package/dist/extension/extension.mjs +8 -1
- package/dist/extension/hooks/conversations.mjs +1 -1
- package/dist/extension/instructions/conversations.mjs +1 -1
- package/dist/extension/lib/build-metadata.mjs +1 -1
- package/dist/extension/lib/conversation-runtime.mjs +132 -1
- package/dist/extension/lib/deferred-confirmations.mjs +1 -1
- package/dist/extension/lib/group-routing/engagement.d.ts +18 -0
- package/dist/extension/lib/group-routing/engagement.mjs +65 -0
- package/dist/extension/lib/group-routing/health.d.ts +11 -0
- package/dist/extension/lib/group-routing/health.mjs +36 -0
- package/dist/extension/lib/group-routing/observed.d.ts +100 -0
- package/dist/extension/lib/group-routing/observed.mjs +103 -0
- package/dist/extension/lib/group-routing/policy.d.ts +63 -0
- package/dist/extension/lib/group-routing/policy.mjs +162 -0
- package/dist/extension/lib/group-routing/record.d.ts +3 -0
- package/dist/extension/lib/group-routing/record.mjs +35 -0
- package/dist/extension/lib/group-routing/replay.d.ts +18 -0
- package/dist/extension/lib/group-routing/replay.mjs +70 -0
- package/dist/extension/lib/group-routing/router.d.ts +109 -0
- package/dist/extension/lib/group-routing/router.mjs +383 -0
- package/dist/extension/lib/group-routing/routing-context.d.ts +4 -0
- package/dist/extension/lib/group-routing/routing-context.mjs +96 -0
- package/dist/extension/lib/group-routing/settings.d.ts +14 -0
- package/dist/extension/lib/group-routing/settings.mjs +58 -0
- package/dist/extension/lib/group-routing/signals.d.ts +110 -0
- package/dist/extension/lib/group-routing/signals.mjs +47 -0
- package/dist/extension/lib/identities.d.ts +3 -0
- package/dist/extension/lib/identities.mjs +13 -1
- package/dist/extension/lib/ontology-connection.mjs +1 -1
- package/dist/extension/lib/ontology-tools.mjs +1 -1
- package/dist/extension/lib/telegram/adapter.d.ts +2 -0
- package/dist/extension/lib/telegram/adapter.mjs +17 -5
- package/dist/extension/lib/telegram/attachments-send.mjs +1 -1
- package/dist/extension/lib/telegram/audience.d.ts +3 -26
- package/dist/extension/lib/telegram/audience.mjs +1 -5
- package/dist/extension/lib/telegram/confirmation-expiry.mjs +2 -2
- package/dist/extension/lib/telegram/confirmations.mjs +2 -2
- package/dist/extension/lib/telegram/conversations.d.ts +1 -1
- package/dist/extension/lib/telegram/conversations.mjs +17 -4
- package/dist/extension/lib/telegram/deferred-confirmations.mjs +2 -2
- package/dist/extension/lib/telegram/health.d.ts +1 -0
- package/dist/extension/lib/telegram/health.mjs +2 -1
- package/dist/extension/lib/telegram/inbound.d.ts +7 -0
- package/dist/extension/lib/telegram/inbound.mjs +47 -15
- package/dist/extension/lib/telegram/lifecycle.mjs +1 -1
- package/dist/extension/lib/telegram/observed.d.ts +5 -98
- package/dist/extension/lib/telegram/observed.mjs +11 -92
- package/dist/extension/lib/telegram/router.d.ts +4 -114
- package/dist/extension/lib/telegram/router.mjs +4 -250
- package/dist/extension/lib/telegram/routing-context.d.ts +3 -3
- package/dist/extension/lib/telegram/routing-context.mjs +7 -86
- package/dist/extension/lib/telegram/signals.d.ts +5 -0
- package/dist/extension/lib/telegram/signals.mjs +60 -0
- package/dist/extension/lib/telegram/silent.mjs +1 -1
- package/dist/extension/tools/attachments_promote.mjs +1 -1
- package/dist/extension/tools/attachments_send.mjs +1 -1
- package/package.json +2 -2
- package/dist/_chunks/conversation-runtime-ovs8IhXJ.mjs +0 -212
|
@@ -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
|
-
var version = "0.
|
|
7
|
+
var version = "0.9.0";
|
|
8
8
|
function agentBuild(environment = process.env) {
|
|
9
9
|
return {
|
|
10
10
|
agent: version,
|
|
@@ -0,0 +1,102 @@
|
|
|
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
|
+
const CONTENT_CAPS = {
|
|
8
|
+
args: 16384,
|
|
9
|
+
result: 32768,
|
|
10
|
+
error: 4096
|
|
11
|
+
};
|
|
12
|
+
const sensitive = /token|password|passwd|secret|authorization|cookie|credential|api.?key|private.?key|session|iban|card|cvv|ssn|email|phone|address|birth|document.?text|raw.?content|body|bytes|base64/i;
|
|
13
|
+
const encoder = new TextEncoder();
|
|
14
|
+
function luhn(value) {
|
|
15
|
+
const digits = value.replace(/\D/g, "");
|
|
16
|
+
if (digits.length < 13 || digits.length > 19) return false;
|
|
17
|
+
let sum = 0;
|
|
18
|
+
let double = false;
|
|
19
|
+
for (let i = digits.length - 1; i >= 0; i--) {
|
|
20
|
+
let n = Number(digits[i]);
|
|
21
|
+
if (double && (n *= 2) > 9) n -= 9;
|
|
22
|
+
sum += n;
|
|
23
|
+
double = !double;
|
|
24
|
+
}
|
|
25
|
+
return sum % 10 === 0;
|
|
26
|
+
}
|
|
27
|
+
function redactText(value) {
|
|
28
|
+
return value.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g, "[binary data]").replace(/-----BEGIN [^-\r\n]+-----[\s\S]*?(?:-----END [^-\r\n]+-----|$)/g, "[redacted PEM]").replace(/data:[^\s"']+/gi, "[binary data]").replace(/\b(?:Bearer|Basic)\s+[A-Za-z0-9+/_=.:~\-]+/gi, "[redacted authorization]").replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, "[redacted JWT]").replace(/\b(?:ssw_|sscli_|sk[-_]|rk[-_])[A-Za-z0-9._~\-]+/gi, "[redacted key]").replace(/\b([a-z][a-z0-9+.-]*:\/\/)[^\s/@]+@/gi, "$1[redacted]@").replace(/([?&](?:[^=&#\s]*(?:token|password|secret|key|signature|credential|session|auth|cookie)[^=&#\s]*|sig)=)[^&#\s"']*/gi, "$1[redacted]").replace(/((?:["']?)(?:[\w-]*(?:token|password|passwd|secret|authorization|cookie|credential|api.?key|private.?key|session|iban|card|cvv|ssn)[\w-]*)(?:["']?)\s*[:=]\s*)(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^\s,;})]+)/gi, "$1[redacted]").replace(/\b[A-Z]{2}\d{2}(?:[ -]?[A-Z0-9]){11,30}\b/gi, "[redacted IBAN]").replace(/\b\d(?:[ -]?\d){12,18}\b/g, (candidate) => luhn(candidate) ? "[redacted card]" : candidate).replace(/[A-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Z0-9](?:[A-Z0-9.-]*[A-Z0-9])?\.[A-Z]{2,}/gi, "[email]").replace(/\b\d{3}-\d{2}-\d{4}\b/g, "[redacted SSN]").replace(/\b[A-Za-z0-9+/]{128,}={0,2}/g, "[binary data]");
|
|
29
|
+
}
|
|
30
|
+
const documentField = /^(?:text|content|body|csv|tsv|file[_-]?body|email[_-]?body)$/i;
|
|
31
|
+
const omittedDocument = "[omitted: unstructured document content]";
|
|
32
|
+
function redactContent(value) {
|
|
33
|
+
const seen = new WeakSet();
|
|
34
|
+
let nodes = 0;
|
|
35
|
+
const visit = (item, depth, field) => {
|
|
36
|
+
if (++nodes > 5e3 || depth > 12) return "[omitted: content complexity]";
|
|
37
|
+
if (typeof item === "string") {
|
|
38
|
+
try {
|
|
39
|
+
const embedded = JSON.parse(item);
|
|
40
|
+
if (embedded !== null && typeof embedded === "object") return JSON.stringify(visit(embedded, depth + 1, field));
|
|
41
|
+
} catch {}
|
|
42
|
+
if (documentField.test(field ?? "")) return omittedDocument;
|
|
43
|
+
return redactText(item);
|
|
44
|
+
}
|
|
45
|
+
if (typeof item === "bigint") return item.toString();
|
|
46
|
+
if (item === null || typeof item !== "object") return item;
|
|
47
|
+
if (ArrayBuffer.isView(item) || item instanceof ArrayBuffer) return "[binary data]";
|
|
48
|
+
if (seen.has(item)) return "[omitted: circular]";
|
|
49
|
+
seen.add(item);
|
|
50
|
+
if (Array.isArray(item)) {
|
|
51
|
+
const entries = item.slice(0, 200).map((entry) => visit(entry, depth + 1, field));
|
|
52
|
+
if (item.length > 200) entries.push("[omitted: array entries]");
|
|
53
|
+
return entries;
|
|
54
|
+
}
|
|
55
|
+
const entries = Object.entries(item);
|
|
56
|
+
return Object.fromEntries([...entries.slice(0, 200).map(([key, entry]) => [redactText(key), sensitive.test(key) && !documentField.test(key) ? "[redacted]" : visit(entry, depth + 1, key)]), ...entries.length > 200 ? [["_omitted", "[omitted: object entries]"]] : []]);
|
|
57
|
+
};
|
|
58
|
+
return visit(value, 0);
|
|
59
|
+
}
|
|
60
|
+
function contentText(value, cap) {
|
|
61
|
+
if (value == null) return null;
|
|
62
|
+
let parsed = value;
|
|
63
|
+
if (typeof value === "string") try {
|
|
64
|
+
parsed = JSON.parse(value);
|
|
65
|
+
} catch {}
|
|
66
|
+
const redacted = redactContent(parsed);
|
|
67
|
+
const text = typeof redacted === "string" ? redacted : JSON.stringify(redacted) ?? "[omitted]";
|
|
68
|
+
if (encoder.encode(text).length <= cap) return text;
|
|
69
|
+
let preview = text;
|
|
70
|
+
while (true) {
|
|
71
|
+
const result = JSON.stringify({
|
|
72
|
+
truncated: true,
|
|
73
|
+
preview
|
|
74
|
+
});
|
|
75
|
+
const bytes = encoder.encode(result).length;
|
|
76
|
+
if (bytes <= cap) return result;
|
|
77
|
+
const available = Math.max(0, encoder.encode(preview).length - (bytes - cap) - 4);
|
|
78
|
+
preview = new TextDecoder().decode(encoder.encode(preview).slice(0, available), { stream: true });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function redactRouterPayload(value) {
|
|
82
|
+
const seen = new WeakSet();
|
|
83
|
+
let nodes = 0;
|
|
84
|
+
const visit = (item, depth) => {
|
|
85
|
+
if (++nodes > 5e3 || depth > 16) return "[omitted: routing complexity]";
|
|
86
|
+
if (typeof item === "string") {
|
|
87
|
+
try {
|
|
88
|
+
const embedded = JSON.parse(item);
|
|
89
|
+
if (embedded !== null && typeof embedded === "object") return JSON.stringify(visit(embedded, depth + 1));
|
|
90
|
+
} catch {}
|
|
91
|
+
return redactText(item);
|
|
92
|
+
}
|
|
93
|
+
if (item === null || typeof item !== "object") return item;
|
|
94
|
+
if (ArrayBuffer.isView(item) || item instanceof ArrayBuffer) return "[binary data]";
|
|
95
|
+
if (seen.has(item)) return "[omitted: circular]";
|
|
96
|
+
seen.add(item);
|
|
97
|
+
if (Array.isArray(item)) return item.slice(0, 200).map((entry) => visit(entry, depth + 1));
|
|
98
|
+
return Object.fromEntries(Object.entries(item).slice(0, 200).map(([key, entry]) => [key, /token|password|secret|authorization|cookie|credential|api.?key|bytes|base64/i.test(key) ? "[redacted]" : visit(entry, depth + 1)]));
|
|
99
|
+
};
|
|
100
|
+
return visit(value, 0);
|
|
101
|
+
}
|
|
102
|
+
export { contentText as n, redactRouterPayload as r, CONTENT_CAPS as t };
|
|
@@ -9,6 +9,7 @@ import { segmentstreamTelegram } from "../lib/telegram/adapter.mjs";
|
|
|
9
9
|
import { decide } from "eve/ai";
|
|
10
10
|
var telegram_default = segmentstreamTelegram({
|
|
11
11
|
settings: () => segmentstream.config.channels.telegram,
|
|
12
|
+
groups: () => segmentstream.config.groups,
|
|
12
13
|
models: () => segmentstream.config.models,
|
|
13
14
|
confirmationMode: () => segmentstream.config.confirmations.mode,
|
|
14
15
|
sessions: () => segmentstream.config.sessions,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type GroupPolicyInput } from "@segmentstream-cloud/ontology/groups";
|
|
2
|
+
import { type GroupPolicy } from "./lib/group-routing/policy.js";
|
|
1
3
|
import type { AttachmentLifecycle } from "./lib/telegram/lifecycle.js";
|
|
2
4
|
import { type TelegramLinkPreviews } from "@segmentstream-cloud/ontology/telegram";
|
|
3
5
|
export declare const TELEGRAM_POLICIES: readonly ["mention", "smart"];
|
|
@@ -51,6 +53,7 @@ export interface AdmissionSettings {
|
|
|
51
53
|
* the values validation allows.
|
|
52
54
|
*/
|
|
53
55
|
export interface SegmentStreamAgentConfigInput {
|
|
56
|
+
readonly groups?: GroupPolicyInput;
|
|
54
57
|
readonly firstContact?: FirstContactSettings;
|
|
55
58
|
readonly limits?: AdmissionLimitsInput;
|
|
56
59
|
readonly replies?: AdmissionReplies;
|
|
@@ -58,8 +61,13 @@ export interface SegmentStreamAgentConfigInput {
|
|
|
58
61
|
readonly telegram?: {
|
|
59
62
|
/** The Connect key whose credential is the bot token, such as `telegram`. */
|
|
60
63
|
readonly connection: string;
|
|
61
|
-
/**
|
|
64
|
+
/** @deprecated Use groups.mode; conflicting values are rejected. */
|
|
62
65
|
readonly policy?: string;
|
|
66
|
+
readonly groups?: {
|
|
67
|
+
readonly replyEdgeIsAddress?: boolean;
|
|
68
|
+
readonly topicScope?: boolean;
|
|
69
|
+
readonly commandsAddress?: boolean;
|
|
70
|
+
};
|
|
63
71
|
/** External links only (default), Telegram's native first link, or no previews. */
|
|
64
72
|
readonly linkPreviews?: string;
|
|
65
73
|
/** Additional exact HTTP(S) origins belonging to this workspace. */
|
|
@@ -86,7 +94,7 @@ export interface SegmentStreamAgentConfigInput {
|
|
|
86
94
|
readonly imageDescription?: string | boolean;
|
|
87
95
|
/** @deprecated Compatibility alias for imageDescription; the canonical key takes precedence. */
|
|
88
96
|
readonly attachments?: string | boolean;
|
|
89
|
-
/**
|
|
97
|
+
/** @deprecated Use groups.smart.model; conflicting values are rejected. */
|
|
90
98
|
readonly decision?: string;
|
|
91
99
|
};
|
|
92
100
|
readonly ontology?: {
|
|
@@ -115,6 +123,11 @@ export interface SegmentStreamAgentConfigInput {
|
|
|
115
123
|
};
|
|
116
124
|
}
|
|
117
125
|
export interface TelegramSettings {
|
|
126
|
+
readonly groups?: {
|
|
127
|
+
readonly replyEdgeIsAddress?: boolean;
|
|
128
|
+
readonly topicScope?: boolean;
|
|
129
|
+
readonly commandsAddress?: boolean;
|
|
130
|
+
};
|
|
118
131
|
readonly connection: string;
|
|
119
132
|
readonly policy: TelegramPolicy;
|
|
120
133
|
readonly linkPreviews?: TelegramLinkPreviews;
|
|
@@ -146,6 +159,7 @@ export interface SessionSettings {
|
|
|
146
159
|
}
|
|
147
160
|
/** The validated configuration with defaults applied. It stays JSON-serializable. */
|
|
148
161
|
export interface SegmentStreamAgentConfig extends AdmissionSettings {
|
|
162
|
+
readonly groups: GroupPolicy;
|
|
149
163
|
readonly channels: {
|
|
150
164
|
readonly telegram: TelegramSettings | null;
|
|
151
165
|
};
|
|
@@ -4,6 +4,10 @@ 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 { effectiveGroupPolicy, groupPolicy } from "./lib/group-routing/policy.mjs";
|
|
8
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
9
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
10
|
+
import { expandGroupFiles, groupPolicyIssues } from "@segmentstream-cloud/ontology/groups";
|
|
7
11
|
import { TELEGRAM_LINK_PREVIEWS, isHttpOrigin } from "@segmentstream-cloud/ontology/telegram";
|
|
8
12
|
const CONNECTION_KEY = /^[a-z][a-z0-9_]{0,63}$/;
|
|
9
13
|
const TELEGRAM_POLICIES = ["mention", "smart"];
|
|
@@ -65,6 +69,7 @@ function telegramSettings(value, issues) {
|
|
|
65
69
|
allowed: [
|
|
66
70
|
"connection",
|
|
67
71
|
"policy",
|
|
72
|
+
"groups",
|
|
68
73
|
"linkPreviews",
|
|
69
74
|
"additionalOwnOrigins",
|
|
70
75
|
"attachmentLifecycle"
|
|
@@ -72,6 +77,24 @@ function telegramSettings(value, issues) {
|
|
|
72
77
|
issues
|
|
73
78
|
});
|
|
74
79
|
if (telegram === void 0) return null;
|
|
80
|
+
const signalSettings = section({
|
|
81
|
+
value: telegram.groups,
|
|
82
|
+
path: [...path, "groups"],
|
|
83
|
+
allowed: [
|
|
84
|
+
"replyEdgeIsAddress",
|
|
85
|
+
"topicScope",
|
|
86
|
+
"commandsAddress"
|
|
87
|
+
],
|
|
88
|
+
issues
|
|
89
|
+
});
|
|
90
|
+
for (const [key, value] of Object.entries(signalSettings ?? {})) if (typeof value !== "boolean") issues.push({
|
|
91
|
+
message: "must be a boolean",
|
|
92
|
+
path: [
|
|
93
|
+
...path,
|
|
94
|
+
"groups",
|
|
95
|
+
key
|
|
96
|
+
]
|
|
97
|
+
});
|
|
75
98
|
const connection = telegram.connection;
|
|
76
99
|
const policy = telegram.policy ?? "smart";
|
|
77
100
|
if (typeof connection !== "string" || !CONNECTION_KEY.test(connection)) issues.push({
|
|
@@ -117,6 +140,7 @@ function telegramSettings(value, issues) {
|
|
|
117
140
|
return typeof connection === "string" && isTelegramPolicy(policy) ? {
|
|
118
141
|
connection,
|
|
119
142
|
policy,
|
|
143
|
+
...signalSettings === void 0 ? {} : { groups: signalSettings },
|
|
120
144
|
linkPreviews,
|
|
121
145
|
additionalOwnOrigins,
|
|
122
146
|
...lifecycle === void 0 ? {} : { attachmentLifecycle: {
|
|
@@ -345,6 +369,7 @@ function validateConfig(input) {
|
|
|
345
369
|
path: [],
|
|
346
370
|
allowed: [
|
|
347
371
|
"channels",
|
|
372
|
+
"groups",
|
|
348
373
|
"models",
|
|
349
374
|
"ontology",
|
|
350
375
|
"sessions",
|
|
@@ -390,6 +415,66 @@ function validateConfig(input) {
|
|
|
390
415
|
path: ["confirmations", "mode"]
|
|
391
416
|
});
|
|
392
417
|
const telegram = telegramSettings(channels?.telegram, issues);
|
|
418
|
+
for (const issue of groupPolicyIssues(root?.groups)) issues.push({
|
|
419
|
+
...issue,
|
|
420
|
+
path: ["groups", ...issue.path]
|
|
421
|
+
});
|
|
422
|
+
let authoredGroups = root?.groups;
|
|
423
|
+
if (authoredGroups !== void 0 && groupPolicyIssues(authoredGroups).length === 0) try {
|
|
424
|
+
authoredGroups = expandGroupFiles(authoredGroups, (path) => {
|
|
425
|
+
const root = realpathSync(process.cwd());
|
|
426
|
+
const file = realpathSync(resolve(root, path));
|
|
427
|
+
const local = relative(root, file);
|
|
428
|
+
if (local.startsWith("..") || isAbsolute(local)) throw new Error("Group instruction file is outside agent/.");
|
|
429
|
+
return readFileSync(file, "utf8");
|
|
430
|
+
});
|
|
431
|
+
} catch (cause) {
|
|
432
|
+
issues.push({
|
|
433
|
+
path: [
|
|
434
|
+
"groups",
|
|
435
|
+
"smart",
|
|
436
|
+
"instructions"
|
|
437
|
+
],
|
|
438
|
+
message: cause instanceof Error ? cause.message : "instruction file could not be read"
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
const legacyMode = isRecord(channels?.telegram) ? channels.telegram.policy : void 0;
|
|
442
|
+
if (authoredGroups?.mode !== void 0 && legacyMode !== void 0 && authoredGroups.mode !== legacyMode) issues.push({
|
|
443
|
+
path: ["groups", "mode"],
|
|
444
|
+
message: "conflicts with channels.telegram.policy"
|
|
445
|
+
});
|
|
446
|
+
if (authoredGroups?.smart?.model !== void 0 && models?.decision !== void 0 && authoredGroups.smart.model !== models.decision) issues.push({
|
|
447
|
+
path: [
|
|
448
|
+
"groups",
|
|
449
|
+
"smart",
|
|
450
|
+
"model"
|
|
451
|
+
],
|
|
452
|
+
message: "conflicts with models.decision"
|
|
453
|
+
});
|
|
454
|
+
let groups = groupPolicy();
|
|
455
|
+
try {
|
|
456
|
+
groups = groupPolicy(issues.length === 0 ? authoredGroups : void 0, {
|
|
457
|
+
mode: typeof legacyMode === "string" ? legacyMode : void 0,
|
|
458
|
+
model: typeof models?.decision === "string" ? models.decision : void 0
|
|
459
|
+
});
|
|
460
|
+
} catch (cause) {
|
|
461
|
+
issues.push({
|
|
462
|
+
path: ["groups"],
|
|
463
|
+
message: cause instanceof Error ? cause.message : "invalid inherited group policy"
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
if (issues.length === 0) for (const key of Object.keys(groups.chats)) try {
|
|
467
|
+
effectiveGroupPolicy(groups, key);
|
|
468
|
+
} catch (cause) {
|
|
469
|
+
issues.push({
|
|
470
|
+
path: [
|
|
471
|
+
"groups",
|
|
472
|
+
"chats",
|
|
473
|
+
key
|
|
474
|
+
],
|
|
475
|
+
message: cause instanceof Error ? cause.message : "invalid inherited group policy"
|
|
476
|
+
});
|
|
477
|
+
}
|
|
393
478
|
const chat = chatModelChoice(models?.chat, issues);
|
|
394
479
|
const stt = models?.stt === false ? false : modelId({
|
|
395
480
|
value: models?.stt,
|
|
@@ -397,7 +482,7 @@ function validateConfig(input) {
|
|
|
397
482
|
path: ["models", "stt"],
|
|
398
483
|
issues
|
|
399
484
|
});
|
|
400
|
-
|
|
485
|
+
modelId({
|
|
401
486
|
value: models?.decision,
|
|
402
487
|
fallback: DEFAULT_DECISION_MODEL,
|
|
403
488
|
path: ["models", "decision"],
|
|
@@ -426,11 +511,15 @@ function validateConfig(input) {
|
|
|
426
511
|
});
|
|
427
512
|
if (issues.length > 0) return { issues };
|
|
428
513
|
return { value: {
|
|
429
|
-
|
|
514
|
+
groups,
|
|
515
|
+
channels: { telegram: telegram === null ? null : {
|
|
516
|
+
...telegram,
|
|
517
|
+
policy: groups.mode
|
|
518
|
+
} },
|
|
430
519
|
models: {
|
|
431
520
|
chat,
|
|
432
521
|
stt,
|
|
433
|
-
decision,
|
|
522
|
+
decision: groups.smart.model,
|
|
434
523
|
imageDescription,
|
|
435
524
|
attachments
|
|
436
525
|
},
|
|
@@ -17,3 +17,10 @@ export declare const segmentstream: import("eve/extension").ExtensionHandle<{
|
|
|
17
17
|
export default segmentstream;
|
|
18
18
|
export { DEFAULT_DECISION_MODEL, DEFAULT_STT_MODEL, DEFAULT_ADMISSION_LIMITS, SegmentStreamAgentConfigError, manifestOf, type ChatModelChoice, type AdmissionLimits, type AdmissionLimitsInput, type AdmissionSettings, type AdmissionReplies, type FirstContactSettings, type LocalizedText, type SegmentStreamAgentConfig, type SegmentStreamAgentConfigInput, type SessionScope, type SessionSettings, type TelegramPolicy, type TelegramSettings, } from "./config.js";
|
|
19
19
|
export { chooseModel, model, type DynamicModel, type ModelChoice } from "./model.js";
|
|
20
|
+
export { groupPolicy, effectiveGroupPolicy, DEFAULT_GROUP_POLICY, type GroupPolicy, type GroupPolicyInput, type SmartPolicyInput, type ChatOverrideInput, } from "./lib/group-routing/policy.js";
|
|
21
|
+
export { addressingReason as groupAddressingReason, conversationKey, type GroupMessageSignals, type AgentMessageSignals, type GroupChannelAdapter, } from "./lib/group-routing/signals.js";
|
|
22
|
+
export { routeMessage as routeGroupMessage, decisionRouter as groupDecisionRouter, } from "./lib/group-routing/router.js";
|
|
23
|
+
export { groupSettings as readGroupSettings, type CloudGroupSettings, } from "./lib/group-routing/settings.js";
|
|
24
|
+
export { observedMessages as groupObservedMessages, agentMessage as groupAgentMessage, type RecentMessage, type AgentMessage, type ObservedMessage, } from "./lib/group-routing/observed.js";
|
|
25
|
+
export { archivedRoutingMessages } from "./lib/group-routing/routing-context.js";
|
|
26
|
+
export { replayRouterPayload } from "./lib/group-routing/replay.js";
|
|
@@ -4,12 +4,19 @@ 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 { DEFAULT_GROUP_POLICY, effectiveGroupPolicy, groupPolicy } from "./lib/group-routing/policy.mjs";
|
|
7
8
|
import { DEFAULT_ADMISSION_LIMITS, DEFAULT_DECISION_MODEL, DEFAULT_STT_MODEL, SegmentStreamAgentConfigError, configSchema, manifestOf } from "./config.mjs";
|
|
8
9
|
import { chooseModel, model } from "./model.mjs";
|
|
10
|
+
import { addressingReason, conversationKey } from "./lib/group-routing/signals.mjs";
|
|
11
|
+
import { decisionRouter, routeMessage } from "./lib/group-routing/router.mjs";
|
|
12
|
+
import { groupSettings } from "./lib/group-routing/settings.mjs";
|
|
13
|
+
import { agentMessage, observedMessages } from "./lib/group-routing/observed.mjs";
|
|
14
|
+
import { archivedRoutingMessages } from "./lib/group-routing/routing-context.mjs";
|
|
15
|
+
import { replayRouterPayload } from "./lib/group-routing/replay.mjs";
|
|
9
16
|
import { defineExtension } from "eve/extension";
|
|
10
17
|
const extension = defineExtension({ config: configSchema });
|
|
11
18
|
const segmentstream = Object.assign(extension, {
|
|
12
19
|
model: (choice) => model(choice ?? (() => extension.config.models.chat)),
|
|
13
20
|
manifestOf
|
|
14
21
|
});
|
|
15
|
-
export { DEFAULT_ADMISSION_LIMITS, DEFAULT_DECISION_MODEL, DEFAULT_STT_MODEL, SegmentStreamAgentConfigError, chooseModel, segmentstream as default, segmentstream, manifestOf, model };
|
|
22
|
+
export { DEFAULT_ADMISSION_LIMITS, DEFAULT_DECISION_MODEL, DEFAULT_GROUP_POLICY, DEFAULT_STT_MODEL, SegmentStreamAgentConfigError, archivedRoutingMessages, chooseModel, conversationKey, segmentstream as default, segmentstream, effectiveGroupPolicy, addressingReason as groupAddressingReason, agentMessage as groupAgentMessage, decisionRouter as groupDecisionRouter, observedMessages as groupObservedMessages, groupPolicy, manifestOf, model, groupSettings as readGroupSettings, replayRouterPayload, routeMessage as routeGroupMessage };
|
|
@@ -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 {
|
|
7
|
+
import { conversationRecorder } from "../lib/conversation-runtime.mjs";
|
|
8
8
|
import { defineHook } from "eve/hooks";
|
|
9
9
|
var conversations_default = defineHook({ events: { "*": conversationRecorder() } });
|
|
10
10
|
export { conversations_default as default };
|
|
@@ -5,7 +5,7 @@ const __filename = __eveFileURLToPath(import.meta.url);
|
|
|
5
5
|
__eveDirname(__filename);
|
|
6
6
|
__eveCreateRequire(import.meta.url);
|
|
7
7
|
import { conversations, storedConversationContext } from "../lib/conversations.mjs";
|
|
8
|
-
import {
|
|
8
|
+
import { conversationCoordinates } from "../lib/conversation-runtime.mjs";
|
|
9
9
|
import { defineDynamic, defineInstructions } from "eve/instructions";
|
|
10
10
|
const archive = conversations();
|
|
11
11
|
var conversations_default = defineDynamic({ events: { "turn.started": async (_event, context) => {
|
|
@@ -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 { t as agentBuild } from "../../_chunks/build-metadata-
|
|
7
|
+
import { t as agentBuild } from "../../_chunks/build-metadata-DuHeGf1m.mjs";
|
|
8
8
|
export { agentBuild };
|
|
@@ -4,5 +4,136 @@ 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 {
|
|
7
|
+
import { memberIdOf } from "./telegram/principal.mjs";
|
|
8
|
+
import { n as contentText, t as CONTENT_CAPS } from "../../_chunks/content-CvFsrmSD.mjs";
|
|
9
|
+
import { conversationEvent, conversations, summarizeArguments } from "./conversations.mjs";
|
|
10
|
+
import { parseAddress } from "./telegram/sessions.mjs";
|
|
11
|
+
import { completionKind } from "./telegram/silent.mjs";
|
|
12
|
+
function conversationCoordinates(context) {
|
|
13
|
+
const principal = context.session.auth.current;
|
|
14
|
+
const telegram = principal?.authenticator === "segmentstream-telegram" || context.channel.kind?.includes("telegram");
|
|
15
|
+
const address = context.channel.continuationToken;
|
|
16
|
+
const attributes = principal?.attributes;
|
|
17
|
+
const fields = address?.split(":");
|
|
18
|
+
const localAddress = telegram && fields?.length === 4 ? fields.slice(1).join(":") : address;
|
|
19
|
+
const parsed = telegram && localAddress !== void 0 && /^-?\d+:\d*:\d*$/u.test(localAddress) ? parseAddress(localAddress) : void 0;
|
|
20
|
+
const attributeChatId = typeof attributes?.chatId === "string" && (!telegram || /^-?\d+$/u.test(attributes.chatId)) ? attributes.chatId : void 0;
|
|
21
|
+
const chatId = telegram ? parsed?.chatId ?? attributeChatId : attributeChatId !== void 0 ? attributeChatId : address;
|
|
22
|
+
if (!chatId) return;
|
|
23
|
+
const memberId = memberIdOf(principal);
|
|
24
|
+
const chatType = attributes?.chatType;
|
|
25
|
+
return {
|
|
26
|
+
provider: telegram ? "telegram" : context.channel.kind ?? "eve",
|
|
27
|
+
chatId,
|
|
28
|
+
chatType: chatType === "group" || chatType === "supergroup" || chatType === void 0 && telegram && chatId.startsWith("-") ? "group" : chatType === "channel" ? "channel" : "private",
|
|
29
|
+
threadId: !telegram ? null : parsed !== void 0 ? parsed.messageThreadId || null : typeof attributes?.messageThreadId === "string" ? attributes.messageThreadId : null,
|
|
30
|
+
participantIds: memberId === void 0 ? [] : [memberId],
|
|
31
|
+
senderId: principal?.subject ?? null
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function conversationRecorder(archive = conversations()) {
|
|
35
|
+
return async (event, context) => {
|
|
36
|
+
const coordinates = conversationCoordinates(context);
|
|
37
|
+
if (coordinates === void 0) return;
|
|
38
|
+
const base = {
|
|
39
|
+
...coordinates,
|
|
40
|
+
sessionId: context.session.id,
|
|
41
|
+
turnId: event.data !== void 0 && "turnId" in event.data ? event.data.turnId : context.session.turn.id,
|
|
42
|
+
at: event.meta.at
|
|
43
|
+
};
|
|
44
|
+
const eventId = `eve:${context.session.id}:${event.meta.id}`;
|
|
45
|
+
if (event.type === "actions.requested") for (const action of event.data.actions) {
|
|
46
|
+
const name = "toolName" in action ? action.toolName : action.name;
|
|
47
|
+
await archive.append(conversationEvent({
|
|
48
|
+
...base,
|
|
49
|
+
eventId: `${eventId}:${action.callId}`,
|
|
50
|
+
kind: "tool_call",
|
|
51
|
+
toolCallId: action.callId,
|
|
52
|
+
toolName: name,
|
|
53
|
+
argsSummary: summarizeArguments(action.input),
|
|
54
|
+
argsText: contentText(action.input, CONTENT_CAPS.args),
|
|
55
|
+
status: "requested"
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
else if (event.type === "action.result") {
|
|
59
|
+
const result = event.data.result;
|
|
60
|
+
const name = "toolName" in result ? result.toolName : "name" in result ? result.name : result.subagentName;
|
|
61
|
+
await archive.append(conversationEvent({
|
|
62
|
+
...base,
|
|
63
|
+
eventId,
|
|
64
|
+
kind: "tool_result",
|
|
65
|
+
toolCallId: result.callId,
|
|
66
|
+
toolName: name,
|
|
67
|
+
status: event.data.status,
|
|
68
|
+
resultText: contentText("output" in result ? result.output : null, CONTENT_CAPS.result),
|
|
69
|
+
errorText: contentText(event.data.error?.message ?? (event.data.status === "failed" ? resultErrorMessage(result.output) : null), CONTENT_CAPS.error)
|
|
70
|
+
}));
|
|
71
|
+
} else if (event.type === "task.started" || event.type === "task.settled") await archive.append(conversationEvent({
|
|
72
|
+
...base,
|
|
73
|
+
eventId,
|
|
74
|
+
kind: event.type === "task.started" ? "tool_call" : "tool_result",
|
|
75
|
+
toolCallId: event.data.callId,
|
|
76
|
+
toolName: event.data.name ?? "task",
|
|
77
|
+
argsSummary: "{}",
|
|
78
|
+
...event.type === "task.settled" ? {
|
|
79
|
+
resultText: contentText(event.data.output, CONTENT_CAPS.result),
|
|
80
|
+
errorText: contentText(event.data.error?.message, CONTENT_CAPS.error)
|
|
81
|
+
} : {},
|
|
82
|
+
status: event.type === "task.started" ? "requested" : event.data.status === "cancelled" ? "cancelled" : event.data.status === "failed" ? "failed" : "completed"
|
|
83
|
+
}));
|
|
84
|
+
else if (event.type === "turn.failed" || event.type === "step.failed") await archive.append(conversationEvent({
|
|
85
|
+
...base,
|
|
86
|
+
turnId: event.data.turnId,
|
|
87
|
+
eventId,
|
|
88
|
+
kind: "decision",
|
|
89
|
+
decision: "silent",
|
|
90
|
+
reason: event.type,
|
|
91
|
+
status: "failed",
|
|
92
|
+
errorText: contentText(event.data.message, CONTENT_CAPS.error)
|
|
93
|
+
}));
|
|
94
|
+
else if (event.type === "message.completed" && event.data.finishReason !== "tool-calls") {
|
|
95
|
+
const kind = completionKind(event.data.message, event.data.finishReason);
|
|
96
|
+
const silent = kind !== "reply";
|
|
97
|
+
const note = kind === "silent_marker" ? "Model returned an explicit silence marker; no message delivery." : kind === "empty" ? "Model completed without final text; no message delivery." : kind === "whitespace" ? "Model returned only whitespace; no message delivery." : "Model produced a reply; transport delivery is recorded separately.";
|
|
98
|
+
await archive.append(conversationEvent({
|
|
99
|
+
...base,
|
|
100
|
+
eventId,
|
|
101
|
+
kind: "decision",
|
|
102
|
+
decision: silent ? "silent" : "reply",
|
|
103
|
+
reason: kind === "reply" ? "model_reply" : `model_${kind}_output`,
|
|
104
|
+
text: note,
|
|
105
|
+
argsSummary: JSON.stringify({
|
|
106
|
+
outputKind: kind,
|
|
107
|
+
outputCharacters: event.data.message?.length ?? 0,
|
|
108
|
+
finishReason: event.data.finishReason
|
|
109
|
+
})
|
|
110
|
+
}));
|
|
111
|
+
if (coordinates.provider !== "telegram" && !silent) await archive.append(conversationEvent({
|
|
112
|
+
...base,
|
|
113
|
+
eventId: `${eventId}:outbound`,
|
|
114
|
+
kind: "outbound",
|
|
115
|
+
text: event.data.message,
|
|
116
|
+
senderId: "agent",
|
|
117
|
+
senderIsBot: true,
|
|
118
|
+
status: "completed"
|
|
119
|
+
}));
|
|
120
|
+
} else if (event.type === "message.received" && coordinates.provider !== "telegram") await archive.append(conversationEvent({
|
|
121
|
+
...base,
|
|
122
|
+
eventId,
|
|
123
|
+
kind: "inbound",
|
|
124
|
+
text: event.data.message,
|
|
125
|
+
attachments: (event.data.parts ?? []).filter((part) => part.type === "file").map((part, index) => ({
|
|
126
|
+
id: `${eventId}:file:${index}`,
|
|
127
|
+
kind: "file",
|
|
128
|
+
...part.filename ? { filename: part.filename } : {},
|
|
129
|
+
contentType: part.mediaType
|
|
130
|
+
}))
|
|
131
|
+
}));
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function resultErrorMessage(output) {
|
|
135
|
+
if (typeof output === "string") return output;
|
|
136
|
+
if (output !== null && typeof output === "object" && "message" in output && typeof output.message === "string") return output.message;
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
8
139
|
export { conversationCoordinates, conversationRecorder };
|
|
@@ -6,11 +6,11 @@ __eveDirname(__filename);
|
|
|
6
6
|
__eveCreateRequire(import.meta.url);
|
|
7
7
|
import { appOrigin } from "./environment.mjs";
|
|
8
8
|
import { memberIdOf } from "./telegram/principal.mjs";
|
|
9
|
+
import { runtimeToken } from "./runtime-token.mjs";
|
|
9
10
|
import { redactDiagnostic } from "./diagnostics.mjs";
|
|
10
11
|
import { humanText, object } from "./confirmation-description.mjs";
|
|
11
12
|
import { chatPostDigest, chatPostOperationId, isChatPost } from "./chat-post-confirmation.mjs";
|
|
12
13
|
import { actionApproval, inputHash } from "./confirmations.mjs";
|
|
13
|
-
import { runtimeToken } from "./runtime-token.mjs";
|
|
14
14
|
import { ontologyTools } from "./ontology-tools.mjs";
|
|
15
15
|
import { createHash } from "node:crypto";
|
|
16
16
|
const deferredKey = (id) => createHash("sha256").update(id).digest("hex").slice(0, 20);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { GroupPolicy, Participants } from "./policy.js";
|
|
2
|
+
import type { GroupMessageSignals } from "./signals.js";
|
|
3
|
+
import type { RecentMessage } from "./observed.js";
|
|
4
|
+
export interface Engagement {
|
|
5
|
+
readonly memberId: string;
|
|
6
|
+
readonly since: string;
|
|
7
|
+
readonly lastAgentReplyAt: string;
|
|
8
|
+
readonly anchorMessageId?: string;
|
|
9
|
+
readonly source: string;
|
|
10
|
+
readonly strength: "explicit" | "model";
|
|
11
|
+
}
|
|
12
|
+
/** Compare workspace-provided names, never infer translations or personal aliases. */
|
|
13
|
+
export declare function humanAddressee(signals: GroupMessageSignals, recent: ReadonlyArray<RecentMessage>, participants: Participants): string | undefined;
|
|
14
|
+
/** A participant's own human reply edge followed by that human's response is a human thread. */
|
|
15
|
+
export declare function humanThread(signals: GroupMessageSignals, recent: ReadonlyArray<RecentMessage>, minutes: number): boolean;
|
|
16
|
+
/** Recover from delivered replies and their inbound anchors; model replies remain weak. */
|
|
17
|
+
export declare function restoreEngagement(signals: GroupMessageSignals, recent: ReadonlyArray<RecentMessage>, policy: GroupPolicy): Engagement | undefined;
|
|
18
|
+
export declare function questionInvitation(at: string | undefined, now: string, minutes: number | "unlimited"): boolean;
|
|
@@ -0,0 +1,65 @@
|
|
|
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
|
+
const own = (m) => "role" in m && m.role === "agent";
|
|
8
|
+
const normalize = (text) => text.normalize("NFKC").toLocaleLowerCase().replace(/^@/, "");
|
|
9
|
+
function humanAddressee(signals, recent, participants) {
|
|
10
|
+
const first = /^[\s«"“'([]*([\p{L}\p{N}_-]{3,})(?=[\s,!:;?.»"”')\]]|$)/u.exec(signals.message.text)?.[1];
|
|
11
|
+
if (first === void 0) return;
|
|
12
|
+
const name = normalize(first);
|
|
13
|
+
for (const [id, participant] of Object.entries(participants)) {
|
|
14
|
+
if (id === signals.sender.externalId || id === signals.sender.member?.id || normalize(id) === normalize(signals.sender.username ?? "")) continue;
|
|
15
|
+
if ([id, ...participant.aliases].some((alias) => normalize(alias) === name)) return id;
|
|
16
|
+
}
|
|
17
|
+
for (const member of recent) {
|
|
18
|
+
if (own(member) || member.kind === "bot" || member.kind === "other_bot" || member.authorId === signals.sender.externalId) continue;
|
|
19
|
+
if ([member.username, member.author.split(/\s+/u)[0]].some((alias) => alias !== void 0 && normalize(alias) === name)) return member.authorId ?? member.username ?? member.author;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function humanThread(signals, recent, minutes) {
|
|
23
|
+
const now = Date.parse(signals.message.at);
|
|
24
|
+
const edge = recent.filter((m) => now >= Date.parse(m.at) && now - Date.parse(m.at) <= minutes * 6e4).findLast((m) => !own(m) && m.authorId === signals.sender.externalId && (m.replyTo?.kind === "human" || m.invitation !== void 0));
|
|
25
|
+
return edge?.replyTo?.kind === "human" && edge.replyTo.id !== void 0 && edge.replyTo.id !== signals.sender.externalId;
|
|
26
|
+
}
|
|
27
|
+
function restoreEngagement(signals, recent, policy) {
|
|
28
|
+
let state;
|
|
29
|
+
for (let i = 0; i < recent.length; i++) {
|
|
30
|
+
const message = recent[i];
|
|
31
|
+
if (!own(message)) {
|
|
32
|
+
if (message.authorId === signals.sender.externalId && (message.replyTo?.kind === "human" && message.replyTo.id !== message.authorId || message.taggedUsers?.some((target) => target.kind !== "bot") || humanAddressee({
|
|
33
|
+
...signals,
|
|
34
|
+
message: {
|
|
35
|
+
...signals.message,
|
|
36
|
+
text: message.text
|
|
37
|
+
}
|
|
38
|
+
}, recent, policy.participants) !== void 0)) state = void 0;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (message.confirmation !== void 0 || !message.addressedTo.some((target) => target.id === signals.sender.externalId || target.username !== void 0 && normalize(target.username) === normalize(signals.sender.username ?? ""))) continue;
|
|
42
|
+
const anchor = recent.slice(0, i).findLast((entry) => !own(entry) && entry.authorId === signals.sender.externalId && (message.replyTo === void 0 || entry.messageId === message.replyTo.messageId));
|
|
43
|
+
const invitation = message.invitation ?? anchor?.invitation ?? (anchor?.replyTo?.kind === "bot" ? "reply-to-bot" : anchor?.taggedUsers?.some((target) => target.kind === "bot") ? "mention" : void 0);
|
|
44
|
+
const continuous = state?.strength === "explicit" && Date.parse(message.at) - Date.parse(state.lastAgentReplyAt) <= policy.smart.engagement.conversationMinutes * 6e4;
|
|
45
|
+
state = invitation === void 0 && continuous ? {
|
|
46
|
+
...state,
|
|
47
|
+
lastAgentReplyAt: message.at
|
|
48
|
+
} : {
|
|
49
|
+
memberId: signals.sender.member?.id ?? signals.sender.externalId,
|
|
50
|
+
since: anchor?.at ?? message.at,
|
|
51
|
+
lastAgentReplyAt: message.at,
|
|
52
|
+
anchorMessageId: anchor?.messageId,
|
|
53
|
+
source: invitation ?? "model",
|
|
54
|
+
strength: invitation === void 0 ? "model" : "explicit"
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const age = Date.parse(signals.message.at) - Date.parse(state?.lastAgentReplyAt ?? "");
|
|
58
|
+
return state !== void 0 && age >= 0 && age <= policy.smart.engagement.conversationMinutes * 6e4 ? state : void 0;
|
|
59
|
+
}
|
|
60
|
+
function questionInvitation(at, now, minutes) {
|
|
61
|
+
if (minutes === "unlimited") return true;
|
|
62
|
+
const age = Date.parse(now) - Date.parse(at ?? "");
|
|
63
|
+
return minutes > 0 && age >= 0 && age <= minutes * 6e4;
|
|
64
|
+
}
|
|
65
|
+
export { humanAddressee, humanThread, questionInvitation, restoreEngagement };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Route } from "./router.js";
|
|
2
|
+
/** Aggregate facts only: no chat, member or message identifiers enter process health. */
|
|
3
|
+
export declare function recordRoutingMetric(provider: string, route: Route, reason: string, latencyMs?: number): void;
|
|
4
|
+
export declare function groupRoutingHealth(): {
|
|
5
|
+
provider: string;
|
|
6
|
+
route: Route;
|
|
7
|
+
reason: string;
|
|
8
|
+
count: number;
|
|
9
|
+
modelCalls: number;
|
|
10
|
+
averageLatencyMs: number | null;
|
|
11
|
+
}[];
|