@segmentstream/agent 0.5.0 → 0.5.2
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/extension/lib/attachment-tools.d.ts +4 -3
- package/dist/extension/lib/attachment-tools.mjs +4 -2
- package/dist/extension/lib/attachments.d.ts +2 -2
- package/dist/extension/lib/confirmation-description.d.ts +25 -0
- package/dist/extension/lib/confirmation-description.mjs +81 -0
- package/dist/extension/lib/confirmations.d.ts +4 -0
- package/dist/extension/lib/confirmations.mjs +9 -2
- package/dist/extension/lib/conversations.d.ts +2 -0
- package/dist/extension/lib/conversations.mjs +15 -3
- package/dist/extension/lib/identities.d.ts +1 -0
- package/dist/extension/lib/identities.mjs +2 -1
- package/dist/extension/lib/ontology-connection.mjs +2 -2
- package/dist/extension/lib/ontology-tools.d.ts +6 -2
- package/dist/extension/lib/ontology-tools.mjs +95 -25
- package/dist/extension/lib/telegram/adapter.mjs +75 -40
- package/dist/extension/lib/telegram/attachments-send.d.ts +1 -0
- package/dist/extension/lib/telegram/attachments-send.mjs +24 -29
- package/dist/extension/lib/telegram/confirmation-text.d.ts +20 -0
- package/dist/extension/lib/telegram/confirmation-text.mjs +88 -0
- package/dist/extension/lib/telegram/confirmations.d.ts +2 -0
- package/dist/extension/lib/telegram/confirmations.mjs +173 -144
- package/dist/extension/lib/telegram/conversations.d.ts +1 -1
- package/dist/extension/lib/telegram/conversations.mjs +10 -5
- package/dist/extension/lib/telegram/customer-text.d.ts +20 -0
- package/dist/extension/lib/telegram/customer-text.mjs +120 -0
- package/dist/extension/lib/telegram/files.mjs +6 -5
- package/dist/extension/lib/telegram/inbound-request.d.ts +2 -0
- package/dist/extension/lib/telegram/inbound-request.mjs +2 -0
- package/dist/extension/lib/telegram/inbound.mjs +10 -4
- package/dist/extension/lib/telegram/principal.d.ts +3 -0
- package/dist/extension/lib/telegram/principal.mjs +11 -1
- package/dist/extension/lib/telegram/requests.mjs +5 -1
- package/dist/extension/lib/telegram/silent.mjs +5 -2
- package/dist/extension/lib/telegram/turn-errors.d.ts +2 -9
- package/dist/extension/lib/telegram/turn-errors.mjs +36 -10
- package/dist/extension/tools/attachments_promote.mjs +11 -2
- package/dist/extension/tools/attachments_send.d.ts +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare function withDeliveryLocale<T>(locale: string, run: () => T, threadId?: string): T;
|
|
2
|
+
export declare function currentLocale(): string;
|
|
3
|
+
export declare function currentThreadId(): string | undefined;
|
|
4
|
+
export declare function customerCopy(locale?: string): {
|
|
5
|
+
failed: string;
|
|
6
|
+
recovery: string;
|
|
7
|
+
attachment: string;
|
|
8
|
+
authorization: string;
|
|
9
|
+
response: string;
|
|
10
|
+
reference: string;
|
|
11
|
+
connected: string;
|
|
12
|
+
disconnected: string;
|
|
13
|
+
};
|
|
14
|
+
export declare function isTechnicalText(text: string): boolean;
|
|
15
|
+
/** Original failures stay in operator diagnostics, joined to the public notice by a short reference. */
|
|
16
|
+
export declare function reportTelegramError(operation: string, error: unknown, metadata?: Record<string, unknown>, secrets?: ReadonlySet<string>): string;
|
|
17
|
+
export declare function failureText(errorId: string, kind?: "failed" | "recovery" | "attachment" | "authorization" | "response", locale?: string): string;
|
|
18
|
+
export declare function customerText(text: string, locale?: string): string;
|
|
19
|
+
/** Covers framework defaults, edits and callback notices as well as ordinary agent replies. */
|
|
20
|
+
export declare function safeTelegramFetch(fetchImpl: typeof fetch): typeof fetch;
|
|
@@ -0,0 +1,120 @@
|
|
|
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 { redactDiagnostic } from "../diagnostics.mjs";
|
|
8
|
+
import { currentInboundRequest } from "./inbound-request.mjs";
|
|
9
|
+
import { randomBytes } from "node:crypto";
|
|
10
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
11
|
+
const deliveryLocale = new AsyncLocalStorage();
|
|
12
|
+
function withDeliveryLocale(locale, run, threadId) {
|
|
13
|
+
return deliveryLocale.run({
|
|
14
|
+
locale,
|
|
15
|
+
threadId
|
|
16
|
+
}, run);
|
|
17
|
+
}
|
|
18
|
+
function currentLocale() {
|
|
19
|
+
return deliveryLocale.getStore()?.locale ?? currentInboundRequest()?.locale ?? "en";
|
|
20
|
+
}
|
|
21
|
+
function currentThreadId() {
|
|
22
|
+
return deliveryLocale.getStore()?.threadId ?? currentInboundRequest()?.threadId;
|
|
23
|
+
}
|
|
24
|
+
function customerCopy(locale = currentLocale()) {
|
|
25
|
+
return locale.toLowerCase().split(/[-_]/)[0] === "ru" ? {
|
|
26
|
+
failed: "Не удалось выполнить запрос. Попробуйте ещё раз.",
|
|
27
|
+
recovery: "Не удалось продолжить разговор. Отправьте новое сообщение.",
|
|
28
|
+
attachment: "Не удалось обработать файл. Попробуйте ещё раз.",
|
|
29
|
+
authorization: "Не удалось подключиться. Попробуйте ещё раз.",
|
|
30
|
+
response: "Не удалось показать ответ. Попробуйте ещё раз.",
|
|
31
|
+
reference: "Код ошибки",
|
|
32
|
+
connected: "Доступ подключён.",
|
|
33
|
+
disconnected: "Подключение отменено."
|
|
34
|
+
} : {
|
|
35
|
+
failed: "I couldn't complete the request. Please try again.",
|
|
36
|
+
recovery: "I couldn't continue the conversation. Please send a new message.",
|
|
37
|
+
attachment: "I couldn't process the file. Please try again.",
|
|
38
|
+
authorization: "I couldn't connect. Please try again.",
|
|
39
|
+
response: "I couldn't display the response. Please try again.",
|
|
40
|
+
reference: "Error reference",
|
|
41
|
+
connected: "Access connected.",
|
|
42
|
+
disconnected: "Connection cancelled."
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const TECHNICAL_PATTERNS = [
|
|
46
|
+
/[{}]|```/u,
|
|
47
|
+
/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/iu,
|
|
48
|
+
/\b[0-9a-hjkmnp-tv-z]{26}\b/iu,
|
|
49
|
+
/\b\w+__\w+/u,
|
|
50
|
+
/\b[a-z][a-z0-9]*_[a-z][a-z0-9_]*\b(?!\.[a-z0-9])/iu,
|
|
51
|
+
/\b(?:Serialization|Type|Reference|Syntax|Tool|Provider|API|Workflow|InternalServer)Error\b/u,
|
|
52
|
+
/\bError:\s|(?:^|\n)\s*at\s+\S+|\b(?:stack trace|traceback|SQLSTATE|node_modules)\b/imu,
|
|
53
|
+
/\b(?:duplicate key|ECONN\w+|HTTP [45]\d\d)\b/iu,
|
|
54
|
+
/\[\s*(?:"[^"]*"|\d+|true|false|null)\s*[,\]]/u
|
|
55
|
+
];
|
|
56
|
+
function isTechnicalText(text) {
|
|
57
|
+
if (/https?:\/\/[^\s/@]+:[^\s/@]+@|[?&](?:token|access_token|api_key|secret|password)=/iu.test(text)) return true;
|
|
58
|
+
const visible = text.replace(/https?:\/\/[^\s)<>]+/giu, "link");
|
|
59
|
+
return TECHNICAL_PATTERNS.some((pattern) => pattern.test(visible));
|
|
60
|
+
}
|
|
61
|
+
function redactCredentials(value) {
|
|
62
|
+
if (typeof value === "string") return value.replace(/\bBearer [A-Za-z0-9_.~+/-]+=*/giu, "Bearer [redacted]").replace(/(https?:\/\/)[^/@\s]+:[^/@\s]+@/gu, "$1[redacted]@").replace(/([?&](?:token|access_token|api_key|secret|password)=)[^&#\s]*/giu, "$1[redacted]").replace(/("(?:token|api_key|password|secret)"\s*:\s*)"(?:\\.|[^"\\])*"/giu, "$1\"[redacted]\"");
|
|
63
|
+
if (Array.isArray(value)) return value.map(redactCredentials);
|
|
64
|
+
if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, /^(?:token|password|secret|authorization|cookie|api_?key|access_?token|refresh_?token)$/iu.test(key) && typeof item === "string" ? "[redacted]" : redactCredentials(item)]));
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
function reportTelegramError(operation, error, metadata = {}, secrets = new Set()) {
|
|
68
|
+
const errorId = randomBytes(4).toString("hex").toUpperCase();
|
|
69
|
+
const credentials = new Set([...secrets, ...[process.env.AI_GATEWAY_API_KEY, process.env.VERCEL_OIDC_TOKEN].filter((value) => value !== void 0)]);
|
|
70
|
+
console.error("[segmentstream] Telegram delivery diagnostic", {
|
|
71
|
+
...metadata,
|
|
72
|
+
errorId,
|
|
73
|
+
operation,
|
|
74
|
+
error: redactCredentials(redactDiagnostic(error, credentials))
|
|
75
|
+
});
|
|
76
|
+
return errorId;
|
|
77
|
+
}
|
|
78
|
+
function failureText(errorId, kind = "failed", locale = currentLocale()) {
|
|
79
|
+
const copy = customerCopy(locale);
|
|
80
|
+
return `${copy[kind]} ${copy.reference}: ${errorId}.`;
|
|
81
|
+
}
|
|
82
|
+
function customerText(text, locale = currentLocale()) {
|
|
83
|
+
if (!isTechnicalText(text)) return text;
|
|
84
|
+
return failureText(reportTelegramError("technical_text_blocked", { text }), "response", locale);
|
|
85
|
+
}
|
|
86
|
+
function safeTelegramFetch(fetchImpl) {
|
|
87
|
+
const methods = new Set([
|
|
88
|
+
"sendMessage",
|
|
89
|
+
"editMessageText",
|
|
90
|
+
"editMessageCaption",
|
|
91
|
+
"answerCallbackQuery",
|
|
92
|
+
"sendPhoto",
|
|
93
|
+
"sendDocument",
|
|
94
|
+
"sendMediaGroup"
|
|
95
|
+
]);
|
|
96
|
+
return async (input, init) => {
|
|
97
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
98
|
+
if (!methods.has(url.split("/").at(-1) ?? "") || typeof init?.body !== "string") return fetchImpl(input, init);
|
|
99
|
+
const body = JSON.parse(init.body);
|
|
100
|
+
for (const field of ["text", "caption"]) if (typeof body[field] === "string") body[field] = customerText(body[field]);
|
|
101
|
+
if (Array.isArray(body.media)) body.media = body.media.map((item) => ({
|
|
102
|
+
...item,
|
|
103
|
+
...typeof item.caption === "string" ? { caption: customerText(item.caption) } : {}
|
|
104
|
+
}));
|
|
105
|
+
const markup = body.reply_markup;
|
|
106
|
+
if (Array.isArray(markup?.inline_keyboard)) markup.inline_keyboard = markup.inline_keyboard.map((row) => row.map((button) => {
|
|
107
|
+
if (typeof button.text !== "string" || !isTechnicalText(button.text)) return button;
|
|
108
|
+
reportTelegramError("technical_button_label_blocked", { text: button.text });
|
|
109
|
+
return {
|
|
110
|
+
...button,
|
|
111
|
+
text: currentLocale().startsWith("ru") ? "Продолжить" : "Continue"
|
|
112
|
+
};
|
|
113
|
+
}));
|
|
114
|
+
return fetchImpl(input, {
|
|
115
|
+
...init,
|
|
116
|
+
body: JSON.stringify(body)
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
export { currentLocale, currentThreadId, customerCopy, customerText, failureText, isTechnicalText, reportTelegramError, safeTelegramFetch, withDeliveryLocale };
|
|
@@ -4,7 +4,9 @@ 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 { conversationLanguage } from "./principal.mjs";
|
|
7
8
|
import { MAXIMUM_ATTACHMENT_BYTES, attachmentUrl } from "../attachments.mjs";
|
|
9
|
+
import { failureText, reportTelegramError } from "./customer-text.mjs";
|
|
8
10
|
function telegramConversation(chatId) {
|
|
9
11
|
return `telegram:${chatId}`;
|
|
10
12
|
}
|
|
@@ -52,15 +54,14 @@ async function stageFiles(input) {
|
|
|
52
54
|
})
|
|
53
55
|
});
|
|
54
56
|
} catch (error) {
|
|
55
|
-
|
|
57
|
+
const errorId = reportTelegramError("attachment_storage_failed", error, {
|
|
56
58
|
kind: file.kind,
|
|
57
|
-
size: file.size
|
|
58
|
-
error
|
|
59
|
+
size: file.size
|
|
59
60
|
});
|
|
60
61
|
staged.set(file.fileId, {
|
|
61
62
|
status: "refused",
|
|
62
63
|
filename,
|
|
63
|
-
reason:
|
|
64
|
+
reason: failureText(errorId, "attachment", conversationLanguage([message.text || message.caption]) ?? message.from?.languageCode ?? "en")
|
|
64
65
|
});
|
|
65
66
|
}
|
|
66
67
|
}
|
|
@@ -68,7 +69,7 @@ async function stageFiles(input) {
|
|
|
68
69
|
}
|
|
69
70
|
function attachmentsContext(input) {
|
|
70
71
|
if (input.files.size === 0) return [];
|
|
71
|
-
const lines = ["<attachments>", `
|
|
72
|
+
const lines = ["<attachments>", `Files from this message, kept for 24 hours ` + (input.group ? "for authorized members of this group only. " : "for this member in this private chat only. ") + "Read, discard or promote them with the segmentstream__attachments_* tools and these ids; promote only what should be kept."];
|
|
72
73
|
for (const staged of input.files.values()) lines.push(staged.status === "stored" ? `- id: ${staged.attachment.id}, filename: ${JSON.stringify(staged.attachment.filename)}, type: ${staged.attachment.contentType}, size: ${staged.attachment.size} bytes, expires_at: ${staged.attachment.expiresAt}` : `- ${JSON.stringify(staged.filename)}: not received, ${staged.reason}`);
|
|
73
74
|
if (input.group) lines.push("This is a group chat: everyone in it shares this conversation's history, so do not repeat a private file's contents here. Suggest sending private files in a direct message.");
|
|
74
75
|
lines.push("</attachments>");
|
|
@@ -22,6 +22,8 @@ export type StagedFile = {
|
|
|
22
22
|
export declare class InboundRequest {
|
|
23
23
|
/** The agent's description from the compiled manifest, for the routing decision. */
|
|
24
24
|
readonly agentDescription: () => Promise<string>;
|
|
25
|
+
locale: string | undefined;
|
|
26
|
+
threadId: string | undefined;
|
|
25
27
|
/** The text the turn should carry instead of the parsed message, once a transcript exists. */
|
|
26
28
|
text: string | undefined;
|
|
27
29
|
/**
|
|
@@ -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 { telegramPrincipal } from "./principal.mjs";
|
|
7
|
+
import { conversationLanguage, telegramPrincipal } from "./principal.mjs";
|
|
8
8
|
import { observedChatKey, observedContext } from "./observed.mjs";
|
|
9
9
|
import { recordGateDecision, recordRouteDecision } from "./conversations.mjs";
|
|
10
10
|
import { answerMarker } from "../questions.mjs";
|
|
@@ -284,6 +284,7 @@ async function handleInboundMessage(input) {
|
|
|
284
284
|
dependencies.sessionPolicy.delivered(address);
|
|
285
285
|
}
|
|
286
286
|
await context.telegram.startTyping();
|
|
287
|
+
await report;
|
|
287
288
|
const files = sent.attachments.length === 0 || dependencies.attachments === void 0 || input.request === void 0 ? new Map() : await stageFiles({
|
|
288
289
|
message: sent,
|
|
289
290
|
memberId: lookup.member.id,
|
|
@@ -291,11 +292,15 @@ async function handleInboundMessage(input) {
|
|
|
291
292
|
download: dependencies.download
|
|
292
293
|
});
|
|
293
294
|
if (input.request !== void 0 && files.size > 0) input.request.files = files;
|
|
294
|
-
|
|
295
|
+
const language = conversationLanguage([text, ...dependencies.observed.recent(chat).map((message) => message.text).reverse()]);
|
|
295
296
|
dependencies.observed.remember(chat, observedMessage(sent, text, lookup.member));
|
|
296
297
|
const principal = telegramPrincipal({
|
|
297
|
-
message:
|
|
298
|
-
|
|
298
|
+
message: {
|
|
299
|
+
...sent,
|
|
300
|
+
text
|
|
301
|
+
},
|
|
302
|
+
member: lookup.member,
|
|
303
|
+
conversationLanguage: language
|
|
299
304
|
});
|
|
300
305
|
return {
|
|
301
306
|
auth: answering && pending !== void 0 ? {
|
|
@@ -313,6 +318,7 @@ async function handleInboundMessage(input) {
|
|
|
313
318
|
member: lookup.member,
|
|
314
319
|
bot
|
|
315
320
|
}),
|
|
321
|
+
"Reply in the member's conversation language. After an action, use at most one short human sentence saying what changed and where, with a link to the record in the app. Confirmations already show their status. Never quote tool output, tool names, record ids, JSON, stack traces or internal errors; explain failures briefly with an error reference when available.",
|
|
316
322
|
...attachmentsContext({
|
|
317
323
|
files,
|
|
318
324
|
group
|
|
@@ -13,7 +13,10 @@ export declare function telegramPrincipal(input: {
|
|
|
13
13
|
readonly from: NonNullable<TelegramMessage["from"]>;
|
|
14
14
|
};
|
|
15
15
|
readonly member: PairedMember;
|
|
16
|
+
readonly conversationLanguage?: string;
|
|
16
17
|
}): SessionAuthContext;
|
|
18
|
+
/** Short answers and attachment-only turns retain the recent chat language when known. */
|
|
19
|
+
export declare function conversationLanguage(texts: readonly string[]): "ru" | "en" | undefined;
|
|
17
20
|
/** The member a session principal acts for: `memberId` stamped by a SegmentStream channel. */
|
|
18
21
|
export declare function memberIdOf(principal: SessionAuthContext | null | undefined): string | undefined;
|
|
19
22
|
/** The member's roles on a session principal; empty when it carries none. */
|
|
@@ -20,6 +20,10 @@ function telegramPrincipal(input) {
|
|
|
20
20
|
if (member.name !== null) attributes.memberName = member.name;
|
|
21
21
|
if (message.messageThreadId !== void 0) attributes.messageThreadId = String(message.messageThreadId);
|
|
22
22
|
if (message.from.username !== void 0) attributes.username = message.from.username;
|
|
23
|
+
const locale = member.locale ?? message.from.languageCode;
|
|
24
|
+
if (locale !== void 0) attributes.locale = locale;
|
|
25
|
+
const language = input.conversationLanguage ?? conversationLanguage([message.text || message.caption]);
|
|
26
|
+
if (language !== void 0) attributes.conversationLanguage = language;
|
|
23
27
|
return {
|
|
24
28
|
authenticator: TELEGRAM_AUTHENTICATOR,
|
|
25
29
|
issuer: "telegram",
|
|
@@ -29,6 +33,12 @@ function telegramPrincipal(input) {
|
|
|
29
33
|
attributes
|
|
30
34
|
};
|
|
31
35
|
}
|
|
36
|
+
function conversationLanguage(texts) {
|
|
37
|
+
for (const text of texts) {
|
|
38
|
+
if (/[а-яё]/iu.test(text)) return "ru";
|
|
39
|
+
if ((text.match(/\b[a-z]+\b/gi)?.length ?? 0) >= 3) return "en";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
32
42
|
function memberIdOf(principal) {
|
|
33
43
|
const memberId = principal?.attributes.memberId;
|
|
34
44
|
return typeof memberId === "string" && memberId !== "" ? memberId : void 0;
|
|
@@ -38,4 +48,4 @@ function rolesOf(principal) {
|
|
|
38
48
|
if (typeof roles === "string") return [roles];
|
|
39
49
|
return roles ?? [];
|
|
40
50
|
}
|
|
41
|
-
export { TELEGRAM_AUTHENTICATOR, memberIdOf, rolesOf, telegramPrincipal };
|
|
51
|
+
export { TELEGRAM_AUTHENTICATOR, conversationLanguage, memberIdOf, rolesOf, telegramPrincipal };
|
|
@@ -76,6 +76,7 @@ function requestPrincipal(request) {
|
|
|
76
76
|
subject: requester.id,
|
|
77
77
|
attributes: {
|
|
78
78
|
requesterId: requester.id,
|
|
79
|
+
requesterKind: requester.kind,
|
|
79
80
|
requesterName: requester.name,
|
|
80
81
|
roles: requester.roles,
|
|
81
82
|
chatId: target.chatId,
|
|
@@ -94,7 +95,10 @@ function requestContext(request) {
|
|
|
94
95
|
"If the addressed person has a linked Telegram username, mention them with @username; otherwise address them by name without a tag. Never invent a username."
|
|
95
96
|
];
|
|
96
97
|
if (request.documentIds !== void 0 && request.documentIds.length > 0) lines.push(`documentIds: ${JSON.stringify(request.documentIds)}`, "Send these documents with the question using segmentstream__attachments_send in this chat. Use the question as the caption (at most 1024 characters), one question per message. In groups send documents only when the chat's purpose allows; never send restricted documents there.");
|
|
97
|
-
if (requester.kind === "service" || !requester.actAs)
|
|
98
|
+
if (requester.kind === "service" || !requester.actAs) {
|
|
99
|
+
lines.push(`${requester.kind === "service" ? "The requester is a service, not a member" : "The requester has not paired a Telegram account with the agent"}, so this turn runs without a member and the ontology is not available in it. Each member who replies acts with their own permissions.`);
|
|
100
|
+
if (requester.kind === "member" && request.target.kind === "group") lines.push("Temporary attachments in this group are available through segmentstream__attachments_* when the requester is a current chat participant or chat administrator. Promotion still requires the requester's permission for the target ontology action.");
|
|
101
|
+
}
|
|
98
102
|
lines.push("</agent_request>");
|
|
99
103
|
return lines.join("\n");
|
|
100
104
|
}
|
|
@@ -6,6 +6,8 @@ __eveDirname(__filename);
|
|
|
6
6
|
__eveCreateRequire(import.meta.url);
|
|
7
7
|
import "./principal.mjs";
|
|
8
8
|
import { agentMessage, observedChatKey } from "./observed.mjs";
|
|
9
|
+
import { currentLocale, customerText } from "./customer-text.mjs";
|
|
10
|
+
import { confirmationLocale } from "./confirmation-text.mjs";
|
|
9
11
|
const SILENT_MARKERS = new Set(["[silent]", "no_reply"]);
|
|
10
12
|
function isSilentReply(text) {
|
|
11
13
|
return SILENT_MARKERS.has(text.trim().toLowerCase());
|
|
@@ -13,7 +15,8 @@ function isSilentReply(text) {
|
|
|
13
15
|
function replyDelivery(observed) {
|
|
14
16
|
return async (event, channel, context) => {
|
|
15
17
|
if (event.finishReason === "tool-calls" || !event.message || isSilentReply(event.message)) return;
|
|
16
|
-
|
|
18
|
+
const text = customerText(event.message, confirmationLocale(context.session?.auth.current ?? null, currentLocale()));
|
|
19
|
+
await channel.telegram.post(text);
|
|
17
20
|
if (observed === void 0 || channel.telegram.chatType === "private") return;
|
|
18
21
|
const principal = context.session.auth.current;
|
|
19
22
|
const sender = principal?.authenticator === "segmentstream-telegram" ? principal.subject : void 0;
|
|
@@ -30,7 +33,7 @@ function replyDelivery(observed) {
|
|
|
30
33
|
...channel.telegram.messageThreadId === void 0 ? {} : { messageThreadId: channel.telegram.messageThreadId }
|
|
31
34
|
});
|
|
32
35
|
observed.remember(chat, agentMessage({
|
|
33
|
-
text
|
|
36
|
+
text,
|
|
34
37
|
at: new Date().toISOString(),
|
|
35
38
|
...inReplyTo === void 0 ? {} : { inReplyTo }
|
|
36
39
|
}));
|
|
@@ -1,9 +1,2 @@
|
|
|
1
|
-
import type { TelegramChannelEvents
|
|
2
|
-
|
|
3
|
-
/** The adapter callback shape; Eve's public channel handler is wrapped inside this callback. */
|
|
4
|
-
export type TurnFailedHandler = (event: Parameters<NonNullable<TelegramChannelEvents["turn.failed"]>>[0], context: {
|
|
5
|
-
readonly state: TelegramChannelState;
|
|
6
|
-
readonly session: Pick<SessionContext["session"], "id" | "auth">;
|
|
7
|
-
}) => void | Promise<void>;
|
|
8
|
-
/** Log the original failure before Eve delivers its existing customer-visible error notice. */
|
|
9
|
-
export declare function withTurnErrorLogging(deliver: TurnFailedHandler): TurnFailedHandler;
|
|
1
|
+
import type { TelegramChannelEvents } from "eve/channels/telegram";
|
|
2
|
+
export declare const telegramFailureEvents: Pick<TelegramChannelEvents, "turn.failed" | "session.failed" | "authorization.completed">;
|
|
@@ -5,16 +5,42 @@ const __filename = __eveFileURLToPath(import.meta.url);
|
|
|
5
5
|
__eveDirname(__filename);
|
|
6
6
|
__eveCreateRequire(import.meta.url);
|
|
7
7
|
import { memberIdOf } from "./principal.mjs";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
import { currentLocale, customerCopy, failureText, reportTelegramError } from "./customer-text.mjs";
|
|
9
|
+
import { confirmationLocale } from "./confirmation-text.mjs";
|
|
10
|
+
const telegramFailureEvents = {
|
|
11
|
+
async "turn.failed"(event, channel, context) {
|
|
12
|
+
const errorId = reportTelegramError("turn_failed", event, {
|
|
13
|
+
chatId: channel.state.chatId,
|
|
12
14
|
memberId: memberIdOf(context.session.auth.current) ?? null,
|
|
13
15
|
sessionId: context.session.id,
|
|
14
|
-
turnId: event.turnId
|
|
15
|
-
error: event
|
|
16
|
+
turnId: event.turnId
|
|
16
17
|
});
|
|
17
|
-
await
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
await channel.telegram.post(failureText(errorId, "failed", confirmationLocale(context.session.auth.current, currentLocale())));
|
|
19
|
+
},
|
|
20
|
+
async "session.failed"(event, channel) {
|
|
21
|
+
const errorId = reportTelegramError("session_recovery_failed", event, {
|
|
22
|
+
chatId: channel.state.chatId,
|
|
23
|
+
sessionId: event.sessionId
|
|
24
|
+
});
|
|
25
|
+
const locale = channel.state.customerLocale ?? "en";
|
|
26
|
+
await channel.telegram.post(failureText(errorId, "recovery", locale));
|
|
27
|
+
},
|
|
28
|
+
async "authorization.completed"(event, channel, context) {
|
|
29
|
+
const messageId = channel.state.pendingAuthMessageIds?.[event.name];
|
|
30
|
+
if (event.candidateId !== void 0 || messageId === void 0) return;
|
|
31
|
+
const locale = confirmationLocale(context.session.auth.current);
|
|
32
|
+
const copy = customerCopy(locale);
|
|
33
|
+
let text = event.outcome === "authorized" ? copy.connected : copy.disconnected;
|
|
34
|
+
if (event.reason !== void 0) {
|
|
35
|
+
const errorId = reportTelegramError("authorization_not_completed", event, { chatId: channel.state.chatId });
|
|
36
|
+
text = failureText(errorId, "authorization", locale);
|
|
37
|
+
}
|
|
38
|
+
await channel.telegram.editMessageText({
|
|
39
|
+
messageId,
|
|
40
|
+
text,
|
|
41
|
+
replyMarkup: { inline_keyboard: [] }
|
|
42
|
+
});
|
|
43
|
+
delete channel.state.pendingAuthMessageIds?.[event.name];
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
export { telegramFailureEvents };
|
|
@@ -5,11 +5,20 @@ const __filename = __eveFileURLToPath(import.meta.url);
|
|
|
5
5
|
__eveDirname(__filename);
|
|
6
6
|
__eveCreateRequire(import.meta.url);
|
|
7
7
|
import { actionApproval } from "../lib/confirmations.mjs";
|
|
8
|
-
import { actionToolName,
|
|
8
|
+
import { actionToolName, ontologyTools } from "../lib/ontology-tools.mjs";
|
|
9
9
|
import { attachmentForModel, attachmentOwner, optionalObject, optionalString, stringArgument, toolAttachments } from "../lib/attachment-tools.mjs";
|
|
10
10
|
import { defineTool } from "eve/tools";
|
|
11
11
|
var attachments_promote_default = defineTool({
|
|
12
|
-
approval: actionApproval({
|
|
12
|
+
approval: actionApproval({
|
|
13
|
+
labels: (memberId, _tool, input) => ontologyTools().labels(memberId, actionToolName(stringArgument(input, "entity"), stringArgument(input, "action"))),
|
|
14
|
+
describe: (memberId, _tool, input) => {
|
|
15
|
+
const recordId = optionalString(input, "record_id");
|
|
16
|
+
return ontologyTools().describe(memberId, actionToolName(stringArgument(input, "entity"), stringArgument(input, "action")), {
|
|
17
|
+
...optionalObject(input, "input"),
|
|
18
|
+
...recordId === void 0 ? {} : { id: recordId }
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}),
|
|
13
22
|
description: "Keep a file the member sent by storing it in a record through an ontology action that takes a file, such as document.upload. Give the entity, the action and the action's input without the file field: the platform fills it with the attachment, and fills sha256 when the action takes one. Runs with the member's permissions. The same file promoted again through the same action returns the earlier result instead of a duplicate.",
|
|
14
23
|
inputSchema: {
|
|
15
24
|
type: "object",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@segmentstream/agent",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Mount a SegmentStream workspace's agent on eve: the ontology as tools, acting as the member who writes, and Telegram through the workspace's connection.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "SegmentStream",
|