@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.
Files changed (38) hide show
  1. package/dist/extension/lib/attachment-tools.d.ts +4 -3
  2. package/dist/extension/lib/attachment-tools.mjs +4 -2
  3. package/dist/extension/lib/attachments.d.ts +2 -2
  4. package/dist/extension/lib/confirmation-description.d.ts +25 -0
  5. package/dist/extension/lib/confirmation-description.mjs +81 -0
  6. package/dist/extension/lib/confirmations.d.ts +4 -0
  7. package/dist/extension/lib/confirmations.mjs +9 -2
  8. package/dist/extension/lib/conversations.d.ts +2 -0
  9. package/dist/extension/lib/conversations.mjs +15 -3
  10. package/dist/extension/lib/identities.d.ts +1 -0
  11. package/dist/extension/lib/identities.mjs +2 -1
  12. package/dist/extension/lib/ontology-connection.mjs +2 -2
  13. package/dist/extension/lib/ontology-tools.d.ts +6 -2
  14. package/dist/extension/lib/ontology-tools.mjs +95 -25
  15. package/dist/extension/lib/telegram/adapter.mjs +75 -40
  16. package/dist/extension/lib/telegram/attachments-send.d.ts +1 -0
  17. package/dist/extension/lib/telegram/attachments-send.mjs +24 -29
  18. package/dist/extension/lib/telegram/confirmation-text.d.ts +20 -0
  19. package/dist/extension/lib/telegram/confirmation-text.mjs +88 -0
  20. package/dist/extension/lib/telegram/confirmations.d.ts +2 -0
  21. package/dist/extension/lib/telegram/confirmations.mjs +173 -144
  22. package/dist/extension/lib/telegram/conversations.d.ts +1 -1
  23. package/dist/extension/lib/telegram/conversations.mjs +10 -5
  24. package/dist/extension/lib/telegram/customer-text.d.ts +20 -0
  25. package/dist/extension/lib/telegram/customer-text.mjs +120 -0
  26. package/dist/extension/lib/telegram/files.mjs +6 -5
  27. package/dist/extension/lib/telegram/inbound-request.d.ts +2 -0
  28. package/dist/extension/lib/telegram/inbound-request.mjs +2 -0
  29. package/dist/extension/lib/telegram/inbound.mjs +10 -4
  30. package/dist/extension/lib/telegram/principal.d.ts +3 -0
  31. package/dist/extension/lib/telegram/principal.mjs +11 -1
  32. package/dist/extension/lib/telegram/requests.mjs +5 -1
  33. package/dist/extension/lib/telegram/silent.mjs +5 -2
  34. package/dist/extension/lib/telegram/turn-errors.d.ts +2 -9
  35. package/dist/extension/lib/telegram/turn-errors.mjs +36 -10
  36. package/dist/extension/tools/attachments_promote.mjs +11 -2
  37. package/dist/extension/tools/attachments_send.d.ts +1 -0
  38. 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
- console.error("[segmentstream] attachment could not be stored", {
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: error instanceof Error ? error.message : "it could not be stored"
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>", `The member's files from this message, kept for 24 hours for this member in this chat only. Read, discard or promote them with the segmentstream__attachments_* tools and these ids; promote only what should be kept.`];
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
  /**
@@ -7,6 +7,8 @@ __eveCreateRequire(import.meta.url);
7
7
  import { AsyncLocalStorage } from "node:async_hooks";
8
8
  var InboundRequest = class {
9
9
  agentDescription;
10
+ locale;
11
+ threadId;
10
12
  text;
11
13
  files;
12
14
  sessions;
@@ -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
- await report;
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: sent,
298
- member: lookup.member
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) 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.`);
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
- await channel.telegram.post(event.message);
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: event.message,
36
+ text,
34
37
  at: new Date().toISOString(),
35
38
  ...inReplyTo === void 0 ? {} : { inReplyTo }
36
39
  }));
@@ -1,9 +1,2 @@
1
- import type { TelegramChannelEvents, TelegramChannelState } from "eve/channels/telegram";
2
- import type { SessionContext } from "eve/context";
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
- function withTurnErrorLogging(deliver) {
9
- return async (event, context) => {
10
- console.error("[segmentstream] telegram turn failed", {
11
- chatId: context.state.chatId,
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 deliver(event, context);
18
- };
19
- }
20
- export { withTurnErrorLogging };
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, ontologyToolLabels } from "../lib/ontology-tools.mjs";
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({ labels: (memberId, _tool, input) => ontologyToolLabels()(memberId, actionToolName(stringArgument(input, "entity"), stringArgument(input, "action"))) }),
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",
@@ -3,5 +3,6 @@ declare const _default: import("eve/tools").ToolDefinition<Record<string, unknow
3
3
  } | {
4
4
  readonly error: string;
5
5
  readonly detail: string;
6
+ readonly reference: string;
6
7
  }>;
7
8
  export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@segmentstream/agent",
3
- "version": "0.5.0",
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",