@segmentstream/agent 0.12.2 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +29 -22
  2. package/dist/_chunks/{build-metadata-CLprTpsn.mjs → build-metadata-BLeBanNg.mjs} +1 -1
  3. package/dist/_chunks/{inbound-request-BrWspFw5.mjs → inbound-request-BXhiIph-.mjs} +21 -19
  4. package/dist/extension/_manifest.json +0 -1
  5. package/dist/extension/config.d.ts +18 -5
  6. package/dist/extension/config.mjs +138 -18
  7. package/dist/extension/extension.d.ts +7 -1
  8. package/dist/extension/hooks/authorization.d.ts +3 -0
  9. package/dist/extension/hooks/authorization.mjs +16 -0
  10. package/dist/extension/instructions.md +3 -4
  11. package/dist/extension/lib/build-metadata.mjs +1 -1
  12. package/dist/extension/lib/chat-post-confirmation.d.ts +1 -0
  13. package/dist/extension/lib/chat-post-confirmation.mjs +4 -2
  14. package/dist/extension/lib/confirmation-outcome.d.ts +1 -1
  15. package/dist/extension/lib/confirmations.d.ts +2 -0
  16. package/dist/extension/lib/confirmations.mjs +7 -4
  17. package/dist/extension/lib/deferred-confirmations.d.ts +3 -0
  18. package/dist/extension/lib/deferred-confirmations.mjs +2 -0
  19. package/dist/extension/lib/ontology-tools.mjs +1 -1
  20. package/dist/extension/lib/telegram/adapter.mjs +34 -7
  21. package/dist/extension/lib/telegram/attachment-description.mjs +1 -1
  22. package/dist/extension/lib/telegram/attachments-send.mjs +13 -10
  23. package/dist/extension/lib/telegram/chat-post-card.d.ts +1 -1
  24. package/dist/extension/lib/telegram/chat-post-card.mjs +13 -10
  25. package/dist/extension/lib/telegram/confirmation-expiry.d.ts +2 -1
  26. package/dist/extension/lib/telegram/confirmation-expiry.mjs +5 -4
  27. package/dist/extension/lib/telegram/confirmation-text.d.ts +1 -1
  28. package/dist/extension/lib/telegram/confirmation-text.mjs +18 -12
  29. package/dist/extension/lib/telegram/confirmations.mjs +24 -19
  30. package/dist/extension/lib/telegram/conversations.d.ts +2 -0
  31. package/dist/extension/lib/telegram/conversations.mjs +14 -1
  32. package/dist/extension/lib/telegram/customer-text.d.ts +9 -8
  33. package/dist/extension/lib/telegram/customer-text.mjs +1 -1
  34. package/dist/extension/lib/telegram/deferred-confirmations.mjs +10 -9
  35. package/dist/extension/lib/telegram/files.mjs +1 -1
  36. package/dist/extension/lib/telegram/health.mjs +1 -1
  37. package/dist/extension/lib/telegram/images.mjs +1 -1
  38. package/dist/extension/lib/telegram/inbound-request.mjs +1 -1
  39. package/dist/extension/lib/telegram/inbound.mjs +18 -17
  40. package/dist/extension/lib/telegram/lifecycle.mjs +2 -2
  41. package/dist/extension/lib/telegram/media-policy.mjs +1 -1
  42. package/dist/extension/lib/telegram/silent.mjs +1 -1
  43. package/dist/extension/lib/telegram/texts.d.ts +16 -0
  44. package/dist/extension/lib/telegram/texts.mjs +70 -0
  45. package/dist/extension/lib/telegram/turn-errors.d.ts +1 -1
  46. package/dist/extension/lib/telegram/turn-errors.mjs +37 -3
  47. package/dist/extension/lib/telegram/voice.mjs +1 -1
  48. package/dist/extension/schedules/confirmations.mjs +1 -1
  49. package/dist/extension/tools/attachments_promote.mjs +2 -0
  50. package/dist/extension/tools/ontology.mjs +1 -1
  51. package/package.json +2 -2
  52. package/dist/extension/connections/ontology.d.ts +0 -7
  53. package/dist/extension/connections/ontology.mjs +0 -11
@@ -11,7 +11,6 @@ talking to you, and you act with exactly their permissions.
11
11
  exact MCP names and input schemas. Pass returned ontology hashes as `expected_hash`
12
12
  on subsequent describe/discover requests so a redeployment cannot mix definitions.
13
13
  These tools are directly available; do not search a connection namespace to find them.
14
- With held confirmations the same operations are reached through `segmentstream__ontology`.
15
14
  Call `me` when you need to check whom you act for. For an unfamiliar task, call `describe`
16
15
  without a ref for a compact entity index, or `discover` to search names and descriptions.
17
16
  Inspect relevant `entity:`, `action:`, or `measure:` refs with `describe` for their complete
@@ -42,14 +41,14 @@ talking to you, and you act with exactly their permissions.
42
41
  the member explicitly cancels or replaces the earlier task. If work is interrupted or blocked,
43
42
  say what remains unfinished; never silently replace it with the latest question.
44
43
  - Destructive or irreversible actions, and legacy custom actions without safety labels, require
45
- the tool boundary's Confirm / Cancel buttons. Call the action to start that flow; never ask for
44
+ the tool boundary's workspace-authored confirmation buttons. Call the action to start that flow; never ask for
46
45
  approval in prose or treat a text acknowledgement as approval. Wait for the button outcome.
47
46
  When the tool schema offers `_confirmation`, supply {question, confirm, cancel, done,
48
47
  cancelled, failed, expired?, edit?} in the chat's language. Describe the action and key
49
48
  parameters in a plain single-line question (at most 300 characters); labels are at most
50
49
  24 characters. No IDs, tool names, snake_case, URLs, credentials, or markup. The platform
51
- adds trusted facts and symbols. Omit this argument on older tools without schema support.
52
- Leave decision symbols to the platform and use distinct confirm/cancel labels. If trusted
50
+ adds trusted facts. Omit this argument on older tools without schema support.
51
+ Use distinct confirm/cancel labels; all visible wording comes from the workspace. If trusted
53
52
  target titles are unavailable or the facts are too long, resolve the targets or narrow the call.
54
53
  Cancel ends that call; retry it only after a new instruction from the member.
55
54
  - Every change goes through an action. Report results after success; never claim a record changed
@@ -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-CLprTpsn.mjs";
7
+ import { t as agentBuild } from "../../_chunks/build-metadata-BLeBanNg.mjs";
8
8
  export { agentBuild };
@@ -8,6 +8,7 @@ export interface ChatPostPreview {
8
8
  readonly externalId: string;
9
9
  readonly title: string;
10
10
  readonly attribution: string;
11
+ readonly attributionSeparator?: string;
11
12
  readonly text: string;
12
13
  readonly finalText: string;
13
14
  readonly replyTo: string | null;
@@ -47,8 +47,9 @@ function describeChatPost(input) {
47
47
  caller.attribution
48
48
  ].every((value) => typeof value === "string" && value.length > 0) || target.provider !== "telegram" || target.id !== values.chat && target.title !== values.chat) throw new Error("An unambiguous destination and a complete exact chat preview are required.");
49
49
  const footer = escapeMarkdownV2(caller.attribution);
50
+ const separator = typeof caller.attribution_separator === "string" ? caller.attribution_separator : "\n\n";
50
51
  return {
51
- action: "❓",
52
+ action: "",
52
53
  fields: [],
53
54
  chatPost: {
54
55
  chatId: target.id,
@@ -56,8 +57,9 @@ function describeChatPost(input) {
56
57
  externalId: target.external_id,
57
58
  title: target.title,
58
59
  attribution: caller.attribution,
60
+ ...separator === "\n\n" ? {} : { attributionSeparator: separator },
59
61
  text: values.text,
60
- finalText: `${converter.fromMarkdown(values.text)}\n\n──────────\n_${footer}_`,
62
+ finalText: `${converter.fromMarkdown(values.text)}${escapeMarkdownV2(separator)}_${footer}_`,
61
63
  replyTo: typeof values.reply_to === "string" ? values.reply_to : null
62
64
  }
63
65
  };
@@ -22,6 +22,6 @@ export declare function confirmationOutcome(record: DeferredConfirmation, status
22
22
  request?: string | undefined;
23
23
  } | undefined;
24
24
  confirmation: string;
25
- status: "done" | "cancelled" | "failed" | "expired" | "approved";
25
+ status: "expired" | "done" | "cancelled" | "failed" | "approved";
26
26
  summary: string;
27
27
  };
@@ -2,6 +2,7 @@ import { type StateHandle } from "eve/context";
2
2
  import type { ApprovalConfiguration } from "eve/tools/approval";
3
3
  import type { ConfirmationDescription } from "./confirmation-description.js";
4
4
  import { type ConfirmationCopy } from "./telegram/confirmation-text.js";
5
+ import { type TelegramTexts } from "./telegram/texts.js";
5
6
  export declare const CONFIRMATION_TTL_MS: number;
6
7
  export interface ActionAnnotations {
7
8
  readonly readOnlyHint?: boolean;
@@ -33,6 +34,7 @@ export declare const confirmations: StateHandle<Record<string, Confirmation>>;
33
34
  export declare function actionApproval(options: {
34
35
  readonly labels: (memberId: string, action: string, input: Readonly<Record<string, unknown>>) => Promise<ActionAnnotations>;
35
36
  readonly describe?: (memberId: string, action: string, input: Readonly<Record<string, unknown>>) => Promise<ConfirmationDescription>;
37
+ readonly texts?: () => TelegramTexts;
36
38
  readonly state?: StateHandle<Record<string, Confirmation>>;
37
39
  readonly now?: () => number;
38
40
  }): ApprovalConfiguration;
@@ -4,8 +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 { withTelegramTexts } from "./telegram/texts.mjs";
7
8
  import { memberIdOf } from "./telegram/principal.mjs";
8
- import { confirmationCopy, renderConfirmation } from "./telegram/confirmation-text.mjs";
9
+ import { confirmationCopy, confirmationLocale, renderConfirmation } from "./telegram/confirmation-text.mjs";
9
10
  import { chatPostCard } from "./telegram/chat-post-card.mjs";
10
11
  import { chatPostDigest, isChatPost } from "./chat-post-confirmation.mjs";
11
12
  import { createHash } from "node:crypto";
@@ -36,6 +37,7 @@ function actionApproval(options) {
36
37
  async request(ctx) {
37
38
  const memberId = memberIdOf(ctx.session.auth.current);
38
39
  if (memberId === void 0) return denied("A paired member is required.");
40
+ const present = (run) => options.texts === void 0 ? run() : withTelegramTexts(options.texts(), run, () => confirmationLocale(ctx.session.auth.current));
39
41
  const hash = inputHash(ctx.toolInput ?? {});
40
42
  const saved = state.get()[ctx.callId];
41
43
  if (saved !== void 0) {
@@ -59,10 +61,11 @@ function actionApproval(options) {
59
61
  const preview = description?.chatPost;
60
62
  const source = ctx.session.auth.current?.attributes;
61
63
  if (description === void 0 || preview === void 0 || typeof source?.chatId !== "string" || source.chatId === preview.externalId) return denied("A complete preview for a different chat is required. Reply normally in this chat.");
62
- chatPostCard({
64
+ const previewDescription = {
63
65
  ...description,
64
66
  warning: labels.destructiveHint === true || labels.irreversibleHint === true
65
- }, copy);
67
+ };
68
+ present(() => chatPostCard(previewDescription, copy));
66
69
  if (saved !== void 0 && (saved.description?.chatPost === void 0 || chatPostDigest(saved.description.chatPost) !== chatPostDigest(preview) || saved.source?.chatId !== source.chatId || saved.source.threadId !== String(source.messageThreadId ?? ""))) return denied("The destination or attribution changed. Make a new call and confirmation.");
67
70
  } catch (error) {
68
71
  console.warn("The exact chat confirmation could not be prepared.", error);
@@ -92,7 +95,7 @@ function actionApproval(options) {
92
95
  warning: labels.destructiveHint === true || labels.irreversibleHint === true
93
96
  };
94
97
  try {
95
- if (!posting) renderConfirmation(description, copy);
98
+ if (!posting) present(() => renderConfirmation(description, copy));
96
99
  } catch {
97
100
  return denied("The complete confirmation facts are unsafe or too long. Narrow the action input.");
98
101
  }
@@ -2,6 +2,7 @@ import type { ToolContext } from "eve/tools";
2
2
  import { type Confirmation } from "./confirmations.js";
3
3
  import { type Environment } from "./environment.js";
4
4
  import { type RuntimeToken } from "./runtime-token.js";
5
+ import type { TelegramTexts } from "./telegram/texts.js";
5
6
  export interface DeferredConfirmation {
6
7
  readonly id: string;
7
8
  readonly key: string;
@@ -18,12 +19,14 @@ export interface DeferredConfirmation {
18
19
  readonly chatId: string;
19
20
  readonly threadId: string;
20
21
  readonly requester: string;
22
+ readonly locale?: string;
21
23
  readonly requesterName?: string;
22
24
  readonly messageId?: string;
23
25
  readonly address?: string;
24
26
  };
25
27
  }
26
28
  export type DeferredOptions = {
29
+ readonly texts?: () => TelegramTexts;
27
30
  readonly environment?: Environment;
28
31
  readonly token?: RuntimeToken;
29
32
  readonly fetch?: typeof fetch;
@@ -60,6 +60,7 @@ async function executeDeferredTool(memberId, action, input, ctx, options = {}) {
60
60
  let saved = {};
61
61
  const result = await actionApproval({
62
62
  ...tools,
63
+ ...options.texts === void 0 ? {} : { texts: options.texts },
63
64
  state: {
64
65
  get: () => saved,
65
66
  update: (update) => {
@@ -101,6 +102,7 @@ async function executeDeferredTool(memberId, action, input, ctx, options = {}) {
101
102
  chatId: attributes.chatId,
102
103
  threadId: String(attributes.messageThreadId ?? ""),
103
104
  requester: principal.subject,
105
+ locale: String(attributes.conversationLanguage ?? attributes.locale ?? "en"),
104
106
  ...humanText(attributes.memberName) === void 0 ? {} : { requesterName: humanText(attributes.memberName) }
105
107
  }
106
108
  }
@@ -154,7 +154,7 @@ function ontologyTools(options = {}) {
154
154
  caller,
155
155
  destinations
156
156
  }),
157
- action: typeof tool.title === "string" ? tool.title : "❓"
157
+ action: typeof tool.title === "string" ? tool.title : typeof tool.description === "string" ? tool.description.split("\n\n")[0] : ""
158
158
  };
159
159
  }
160
160
  let entities = [];
@@ -4,6 +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 { telegramText, withTelegramTexts } from "./texts.mjs";
7
8
  import { manifestOf } from "../../config.mjs";
8
9
  import { appOrigin, channelsEnabled, proxyEnvironment, runtimeIdentityEnvironment, webhookSecret } from "../environment.mjs";
9
10
  import { conversationLanguage, telegramPrincipal } from "./principal.mjs";
@@ -13,10 +14,10 @@ import { decisionRouter } from "../group-routing/router.mjs";
13
14
  import { runtimeToken } from "../runtime-token.mjs";
14
15
  import { conversations } from "../conversations.mjs";
15
16
  import { archivedRoutingMessages } from "../group-routing/routing-context.mjs";
16
- import { t as agentBuild } from "../../../_chunks/build-metadata-CLprTpsn.mjs";
17
+ import { t as agentBuild } from "../../../_chunks/build-metadata-BLeBanNg.mjs";
17
18
  import { confirmationLocale } from "./confirmation-text.mjs";
18
19
  import { telegramSendScope } from "./send-queue.mjs";
19
- import { d as failureText, h as withDeliveryLocale, i as withReplacedText, m as safeTelegramFetch, n as currentInboundRequest, p as reportTelegramError, r as runInboundRequest, t as InboundRequest } from "../../../_chunks/inbound-request-BrWspFw5.mjs";
20
+ import { d as failureText, h as withDeliveryLocale, i as withReplacedText, m as safeTelegramFetch, n as currentInboundRequest, o as currentLocale, p as reportTelegramError, r as runInboundRequest, t as InboundRequest } from "../../../_chunks/inbound-request-BXhiIph-.mjs";
20
21
  import { attachmentLifecycle } from "./lifecycle.mjs";
21
22
  import { scopedAddress, sessionPolicy } from "./sessions.mjs";
22
23
  import { observedMessages } from "./observed.mjs";
@@ -120,7 +121,31 @@ function segmentstreamTelegram(options) {
120
121
  identities: pairing,
121
122
  observed,
122
123
  chats,
123
- verify: telegramWebhookVerifier(() => webhookSecret(environment))
124
+ verify: telegramWebhookVerifier(() => webhookSecret(environment)),
125
+ answerCallback: async (callbackQueryId, locale) => {
126
+ const text = telegramText("callback", "unsupported", locale);
127
+ const reply = await proxy().request({
128
+ api: "bot",
129
+ method: "POST",
130
+ path: "/answerCallbackQuery",
131
+ headers: [["content-type", "application/json"]],
132
+ body: new TextEncoder().encode(JSON.stringify({
133
+ callback_query_id: callbackQueryId,
134
+ ...text === void 0 ? {} : { text }
135
+ }))
136
+ });
137
+ const body = new TextDecoder().decode(reply.body);
138
+ let answer;
139
+ try {
140
+ answer = JSON.parse(body);
141
+ } catch (cause) {
142
+ throw new Error(`Telegram callback acknowledgement was invalid (HTTP ${reply.status}): ${body}`, { cause });
143
+ }
144
+ if (reply.status < 200 || reply.status >= 300 || answer === null || typeof answer !== "object" || !("ok" in answer) || answer.ok !== true) throw new Error("Telegram callback acknowledgement was refused.", { cause: {
145
+ status: reply.status,
146
+ body
147
+ } });
148
+ }
124
149
  });
125
150
  let store = options.attachments;
126
151
  function attachments() {
@@ -279,6 +304,8 @@ function segmentstreamTelegram(options) {
279
304
  }
280
305
  },
281
306
  onCallbackQuery: async (context, query) => {
307
+ const inbound = currentInboundRequest();
308
+ if (inbound !== void 0) inbound.locale = query.from.languageCode;
282
309
  try {
283
310
  if (isDeferredCallback(query.data)) {
284
311
  await handleDeferredCallback({
@@ -306,7 +333,7 @@ function segmentstreamTelegram(options) {
306
333
  reportTelegramError("confirmation_callback_failed", error);
307
334
  await context.telegram.answerCallbackQuery({
308
335
  callbackQueryId: query.id,
309
- text: "⚠️"
336
+ ...telegramText("callback", "error") === void 0 ? {} : { text: telegramText("callback", "error") }
310
337
  });
311
338
  }
312
339
  },
@@ -404,7 +431,7 @@ function segmentstreamTelegram(options) {
404
431
  const locale = confirmationLocale(principal ?? null, context.state.customerLocale ?? "en");
405
432
  context.state.customerLocale = locale;
406
433
  await withDeliveryLocale(locale, async () => {
407
- await handler(event, context);
434
+ await withTelegramTexts(options.admission?.().texts ?? {}, () => handler(event, context), currentLocale);
408
435
  }, context.state.messageThreadId === null ? void 0 : String(context.state.messageThreadId), typeof principal?.attributes.replyToMessageId === "string" ? principal.attributes.replyToMessageId : void 0, typeof principal?.attributes.chatId === "string" ? principal.attributes.chatId : void 0);
409
436
  }]))
410
437
  } },
@@ -482,7 +509,7 @@ function gatedRoute(input) {
482
509
  };
483
510
  inbound.waitUntil = waitUntil;
484
511
  inbound.backgroundUntil = (task) => args.waitUntil(task);
485
- return runInboundRequest(inbound, () => input.capture(request, async (routedRequest = request) => {
512
+ return withTelegramTexts(options.admission?.().texts ?? {}, () => runInboundRequest(inbound, () => input.capture(request, async (routedRequest = request) => {
486
513
  const response = await route.handler(routedRequest, {
487
514
  ...args,
488
515
  waitUntil,
@@ -501,7 +528,7 @@ function gatedRoute(input) {
501
528
  headers: { "retry-after": "1" }
502
529
  });
503
530
  return response;
504
- }));
531
+ })), currentLocale);
505
532
  }
506
533
  };
507
534
  }
@@ -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 { p as reportTelegramError } from "../../../_chunks/inbound-request-BrWspFw5.mjs";
7
+ import { p as reportTelegramError } from "../../../_chunks/inbound-request-BXhiIph-.mjs";
8
8
  import { describableImage } from "./images.mjs";
9
9
  import { MAXIMUM_ROUTING_PDF_CHARACTERS, extractRoutingPdf } from "./pdf-text.mjs";
10
10
  import { gateway, generateText } from "ai";
@@ -10,7 +10,7 @@ import { runtimeToken } from "../runtime-token.mjs";
10
10
  import { redactDiagnostic } from "../diagnostics.mjs";
11
11
  import { confirmationLocale } from "./confirmation-text.mjs";
12
12
  import { inTelegramSendQueue, telegramSendScope } from "./send-queue.mjs";
13
- import { c as customerCopy, f as formattingRefused, l as customerMessage, p as reportTelegramError } from "../../../_chunks/inbound-request-BrWspFw5.mjs";
13
+ import { d as failureText, f as formattingRefused, l as customerMessage, p as reportTelegramError } from "../../../_chunks/inbound-request-BXhiIph-.mjs";
14
14
  import { MAXIMUM_PROXY_BODY_BYTES, connectionProxy } from "../connection-proxy.mjs";
15
15
  import { ACT_AS_HEADER } from "../ontology-connection.mjs";
16
16
  import { AttachmentError, CONVERSATION_HEADER, SESSION_HEADER } from "../attachments.mjs";
@@ -83,14 +83,17 @@ async function sendAttachmentsInOrder(options) {
83
83
  const environment = options.environment ?? process.env;
84
84
  const principal = options.principal;
85
85
  const locale = confirmationLocale(principal);
86
- const unavailable = (error, diagnostic) => ({
87
- error,
88
- detail: customerCopy().attachment,
89
- reference: reportTelegramError("attachment_send_refused", {
86
+ const unavailable = (error, diagnostic) => {
87
+ const reference = reportTelegramError("attachment_send_refused", {
90
88
  error,
91
89
  diagnostic
92
- })
93
- });
90
+ });
91
+ return {
92
+ error,
93
+ detail: failureText(reference, "attachment", locale),
94
+ reference
95
+ };
96
+ };
94
97
  if (!channelsEnabled(environment)) return unavailable("channels_disabled", "Attachments can be sent only in production.");
95
98
  if (options.connection === void 0) return unavailable("unsupported", "The Telegram channel is not configured.");
96
99
  if (principal?.authenticator !== "segmentstream-telegram" || typeof principal.attributes.chatId !== "string" || !/^-?\d{1,20}$/.test(principal.attributes.chatId)) return unavailable("no_telegram_chat", "This turn has no Telegram chat.");
@@ -104,7 +107,7 @@ async function sendAttachmentsInOrder(options) {
104
107
  const reference = reportTelegramError("attachment_owner_refused", error);
105
108
  return {
106
109
  error: "forbidden",
107
- detail: customerCopy().attachment,
110
+ detail: failureText(reference, "attachment", locale),
108
111
  reference
109
112
  };
110
113
  }
@@ -187,7 +190,7 @@ async function sendAttachmentsInOrder(options) {
187
190
  id: reference.id,
188
191
  filename,
189
192
  error: error instanceof AttachmentError ? error.code : "unavailable",
190
- detail: customerCopy().attachment,
193
+ detail: failureText(errorId, "attachment", locale),
191
194
  reference: errorId
192
195
  };
193
196
  }
@@ -271,7 +274,7 @@ async function sendAttachmentsInOrder(options) {
271
274
  id: file.id,
272
275
  filename: file.filename,
273
276
  error: error instanceof AttachmentError ? error.code : "send_failed",
274
- detail: customerCopy().attachment,
277
+ detail: failureText(errorId, "attachment", locale),
275
278
  reference: errorId
276
279
  };
277
280
  });
@@ -7,4 +7,4 @@ export declare function chatPostCardLinks(text: string): readonly string[] | und
7
7
  export declare function chatPostCard(description: ConfirmationDescription, copy?: ConfirmationCopy): ConfirmationText;
8
8
  /** Status/receipt strings are data outside the already rendered preview. */
9
9
  export declare function chatPostCardStatus(text: string, status: string): string;
10
- export declare function chatPostReceipt(output: unknown, completed?: string): string | undefined;
10
+ export declare function chatPostReceipt(output: unknown): string | undefined;
@@ -4,11 +4,12 @@ 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 { telegramText } from "./texts.mjs";
7
8
  import { renderConfirmation } from "./confirmation-text.mjs";
8
- import { telegramMarkdownLinks, telegramTextLinks } from "@segmentstream-cloud/ontology/telegram";
9
9
  import { AsyncLocalStorage } from "node:async_hooks";
10
+ import { telegramMarkdownLinks, telegramTextLinks } from "@segmentstream-cloud/ontology/telegram";
10
11
  import { escapeMarkdownV2 } from "@chat-adapter/telegram";
11
- const MAX_PREVIEW_LENGTH = 3584;
12
+ const RECEIPT_DATA_SPACE = 512;
12
13
  const exactText = new AsyncLocalStorage();
13
14
  function withChatPostCard(text, run, links = []) {
14
15
  if (text.length > 4096) throw new Error("The complete chat confirmation cannot fit in one Telegram message.");
@@ -30,31 +31,33 @@ function chatPostCard(description, copy) {
30
31
  const base = renderConfirmation(description, copy);
31
32
  const text = [
32
33
  base.text,
33
- `→ ${preview.title} (${preview.provider})`,
34
- ...preview.replyTo === null ? [] : [`↩️ ${preview.replyTo}`]
34
+ `${preview.title} (${preview.provider})`,
35
+ ...preview.replyTo === null ? [] : [preview.replyTo]
35
36
  ].map(escapeMarkdownV2).join("\n") + `\n\n${preview.finalText}`;
36
- if (text.length > MAX_PREVIEW_LENGTH) throw new Error("The full message and receipt cannot fit in one Telegram confirmation. Make a shorter draft.");
37
+ const receiptSpace = RECEIPT_DATA_SPACE + Math.max(...["sent", "uncertain"].map((status) => escapeMarkdownV2(telegramText("chatPost", status) ?? "").length)) + escapeMarkdownV2(telegramText("chatPost", "historyNotRecorded") ?? "").length;
38
+ if (text.length > 4096 - receiptSpace) throw new Error("The full message and receipt cannot fit in one Telegram confirmation. Make a shorter draft.");
37
39
  return {
38
40
  ...base,
39
41
  text,
40
42
  previewLinks: [
41
43
  ...telegramTextLinks(`${preview.title}\n${preview.replyTo ?? ""}`),
42
44
  ...telegramMarkdownLinks(preview.text),
45
+ ...telegramTextLinks(preview.attributionSeparator ?? "\n\n"),
43
46
  ...telegramTextLinks(preview.attribution)
44
47
  ]
45
48
  };
46
49
  }
47
50
  function chatPostCardStatus(text, status) {
48
- return `${text}\n\n${escapeMarkdownV2(status)}`;
51
+ return status === "" ? text : `${text}\n\n${escapeMarkdownV2(status)}`;
49
52
  }
50
- function chatPostReceipt(output, completed = "✅") {
53
+ function chatPostReceipt(output) {
51
54
  const value = typeof output === "object" && output !== null ? output : {};
52
55
  if (value.status !== "sent" && value.status !== "uncertain" || typeof value.history_recorded !== "boolean" || typeof value.duplicate !== "boolean" || value.message_id !== null && typeof value.message_id !== "string" || value.url !== null && typeof value.url !== "string") return;
53
56
  const url = typeof value.url === "string" && value.url.length <= 256 && /^https:\/\/t\.me\/[A-Za-z0-9_/-]+$/u.test(value.url) ? value.url : null;
54
57
  return [
55
- value.status === "sent" ? completed : "⚠️ ❓",
58
+ telegramText("chatPost", value.status === "sent" ? "sent" : "uncertain"),
56
59
  ...url === null ? [] : [url],
57
- ...value.history_recorded ? [] : ["⚠️"]
58
- ].join("\n");
60
+ ...value.history_recorded ? [] : [telegramText("chatPost", "historyNotRecorded")]
61
+ ].filter((line) => line !== void 0).join("\n");
59
62
  }
60
63
  export { chatPostCard, chatPostCardLinks, chatPostCardStatus, chatPostReceipt, isExactChatPostCard, withChatPostCard };
@@ -1,4 +1,5 @@
1
1
  import type { TelegramSettings } from "../../config.js";
2
2
  import { type DeferredConfirmation, type DeferredOptions } from "../deferred-confirmations.js";
3
+ import { type TelegramTexts } from "./texts.js";
3
4
  /** A bounded sweep; concurrent webhooks/schedules compete for the same atomic expiry claim. */
4
- export declare function expireScheduledConfirmations(connection: string, notify: (record: DeferredConfirmation, result: Readonly<Record<string, unknown>>) => Promise<unknown>, options?: DeferredOptions, settings?: TelegramSettings): Promise<void>;
5
+ export declare function expireScheduledConfirmations(connection: string, notify: (record: DeferredConfirmation, result: Readonly<Record<string, unknown>>) => Promise<unknown>, options?: DeferredOptions, settings?: TelegramSettings, texts?: TelegramTexts): Promise<void>;
@@ -4,18 +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 { withTelegramTexts } from "./texts.mjs";
7
8
  import { proxyEnvironment } from "../environment.mjs";
8
9
  import { runtimeToken } from "../runtime-token.mjs";
9
10
  import { conversations } from "../conversations.mjs";
10
11
  import { telegramSendScope } from "./send-queue.mjs";
11
- import { m as safeTelegramFetch, p as reportTelegramError } from "../../../_chunks/inbound-request-BrWspFw5.mjs";
12
+ import { m as safeTelegramFetch, p as reportTelegramError } from "../../../_chunks/inbound-request-BXhiIph-.mjs";
12
13
  import { deferredConfirmations } from "../deferred-confirmations.mjs";
13
14
  import { expireDeferredRecord } from "./deferred-confirmations.mjs";
14
15
  import { connectionProxy } from "../connection-proxy.mjs";
15
16
  import { TELEGRAM_PLACEHOLDER_TOKEN, TELEGRAM_PROXY_ORIGIN, telegramProxyFetch } from "./proxy-fetch.mjs";
16
17
  import { telegramPreviewEnvironment } from "@segmentstream-cloud/ontology/telegram";
17
18
  import { editTelegramMessageText } from "eve/channels/telegram";
18
- async function expireScheduledConfirmations(connection, notify, options = {}, settings) {
19
+ async function expireScheduledConfirmations(connection, notify, options = {}, settings, texts = {}) {
19
20
  const environment = options.environment ?? process.env;
20
21
  const token = options.token ?? runtimeToken;
21
22
  const archive = conversations({
@@ -36,13 +37,13 @@ async function expireScheduledConfirmations(connection, notify, options = {}, se
36
37
  });
37
38
  const records = await deferredConfirmations(options).command({ operation: "expired" });
38
39
  for (const record of records) try {
39
- await expireDeferredRecord(record, { telegram: { editMessageText: (input) => editTelegramMessageText({
40
+ await withTelegramTexts(texts, () => expireDeferredRecord(record, { telegram: { editMessageText: (input) => editTelegramMessageText({
40
41
  apiBaseUrl: TELEGRAM_PROXY_ORIGIN,
41
42
  credentials: { botToken: TELEGRAM_PLACEHOLDER_TOKEN },
42
43
  fetch: transport,
43
44
  chatId: record.data.chatId,
44
45
  ...input
45
- }) } }, options, archive, notify);
46
+ }) } }, options, archive, notify), () => record.data.locale ?? "en");
46
47
  } catch (error) {
47
48
  reportTelegramError("deferred_expiry_failed", error);
48
49
  }
@@ -30,5 +30,5 @@ export interface ConfirmationText {
30
30
  export declare function confirmationLocale(principal: SessionAuthContext | null, fallback?: string): string;
31
31
  /** Reject the entire presentation object; never partially trust malformed model copy. */
32
32
  export declare function confirmationCopy(value?: unknown): ConfirmationCopy | undefined;
33
- /** Fixed markers retain callback meaning even if the model writes misleading labels. */
33
+ /** Button positions retain callback meaning; workspace copy supplies every label and status. */
34
34
  export declare function renderConfirmation(description?: ConfirmationDescription, copy?: ConfirmationCopy): ConfirmationText;
@@ -4,6 +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 { telegramText } from "./texts.mjs";
7
8
  import { isTechnicalText } from "../customer-text-safety.mjs";
8
9
  import { humanText, object } from "../confirmation-description.mjs";
9
10
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -48,7 +49,7 @@ function confirmationCopy(value) {
48
49
  return typeof text === "string" && text === text.trim() && (key === "question" || !/[✅❌⌛⚠❓✏]/u.test(text)) && humanText(text, key === "question" ? 300 : 24) === text;
49
50
  };
50
51
  if (required.some((key) => !valid(key)) || optional.some((key) => copy[key] !== void 0 && !valid(key)) || copy.confirm === copy.cancel || Object.keys(copy).some((key) => ![...required, ...optional].includes(key))) {
51
- console.warn("Invalid confirmation presentation; using metadata and symbols.");
52
+ console.warn("Invalid confirmation presentation; using workspace presentation.");
52
53
  return;
53
54
  }
54
55
  return { ...copy };
@@ -57,27 +58,32 @@ function renderConfirmation(description, copy) {
57
58
  copy = confirmationCopy(copy);
58
59
  const action = humanText(description?.action);
59
60
  if (description === void 0 || action === void 0 || action === "❓") throw new Error("A confirmation requires a safe declared action title.");
60
- const facts = [[description?.warning ? "⚠️" : "", action].filter(Boolean).join(" "), ...(description?.fields ?? []).map(({ label, value }) => {
61
+ const facts = [action, ...(description?.fields ?? []).map(({ label, value }) => {
61
62
  if (value.length > 3584) throw new Error("The complete confirmation facts cannot fit in one Telegram message.");
62
63
  const safeLabel = humanText(label);
63
64
  const safeValue = humanText(value, 3584);
64
65
  if (safeLabel === void 0 || safeValue === void 0) throw new Error("The complete confirmation facts contain an unsafe field.");
65
66
  return `${safeLabel}: ${safeValue}`;
66
67
  })].filter(Boolean).join("\n");
67
- const text = [copy?.question ?? "❓", facts].filter(Boolean).join("\n\n");
68
+ const configured = (key) => {
69
+ const value = telegramText("confirmation", key);
70
+ if (value === void 0) throw new Error(`texts.confirmation.${key} is required for Telegram confirmations.`);
71
+ return value;
72
+ };
73
+ const text = [copy?.question ?? configured("question"), facts].filter(Boolean).join("\n\n");
68
74
  if (text.length > 3584) throw new Error("The complete confirmation facts cannot fit in one Telegram message.");
69
75
  if (isTechnicalText(text)) throw new Error("The complete confirmation cannot pass the delivery safety check.");
70
76
  return {
71
77
  text,
72
- confirm: copy === void 0 ? "✅" : `✅ ${copy.confirm}`,
73
- cancel: copy === void 0 ? "❌" : `❌ ${copy.cancel}`,
74
- executing: "⌛",
75
- cancelled: copy === void 0 ? "❌" : `❌ ${copy.cancelled}`,
76
- completed: copy === void 0 ? "✅" : `✅ ${copy.done}`,
77
- failed: copy === void 0 ? "⚠️" : `⚠️ ${copy.failed}`,
78
- expired: copy?.expired === void 0 ? "⌛" : `⌛ ${copy.expired}`,
79
- interrupted: copy?.interrupted === void 0 ? "⏸" : `⏸ ${copy.interrupted}`,
80
- edit: copy?.edit === void 0 ? "✏️" : `✏️ ${copy.edit}`
78
+ confirm: copy?.confirm ?? configured("confirm"),
79
+ cancel: copy?.cancel ?? configured("cancel"),
80
+ executing: configured("executing"),
81
+ cancelled: copy?.cancelled ?? configured("cancelled"),
82
+ completed: copy?.done ?? configured("done"),
83
+ failed: copy?.failed ?? configured("failed"),
84
+ expired: copy?.expired ?? configured("expired"),
85
+ interrupted: copy?.interrupted ?? configured("interrupted"),
86
+ edit: copy?.edit ?? configured("edit")
81
87
  };
82
88
  }
83
89
  export { confirmationCopy, confirmationLocale, isConfirmationText, renderConfirmation, withConfirmationText };