@segmentstream/agent 0.12.2 → 0.14.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 (75) hide show
  1. package/README.md +29 -22
  2. package/dist/_chunks/{build-metadata-CLprTpsn.mjs → build-metadata-BB2uSGsI.mjs} +1 -1
  3. package/dist/_chunks/{inbound-request-BrWspFw5.mjs → inbound-request-BXhiIph-.mjs} +21 -19
  4. package/dist/_chunks/principal-VFQWqe5S.mjs +126 -0
  5. package/dist/extension/_manifest.json +0 -1
  6. package/dist/extension/config.d.ts +23 -5
  7. package/dist/extension/config.mjs +167 -18
  8. package/dist/extension/extension.d.ts +7 -1
  9. package/dist/extension/hooks/authorization.d.ts +3 -0
  10. package/dist/extension/hooks/authorization.mjs +16 -0
  11. package/dist/extension/instructions.md +3 -4
  12. package/dist/extension/lib/attachment-tools.mjs +1 -1
  13. package/dist/extension/lib/attachments.d.ts +2 -0
  14. package/dist/extension/lib/attachments.mjs +1 -0
  15. package/dist/extension/lib/build-metadata.mjs +1 -1
  16. package/dist/extension/lib/chat-post-confirmation.d.ts +1 -0
  17. package/dist/extension/lib/chat-post-confirmation.mjs +4 -2
  18. package/dist/extension/lib/confirmation-outcome.d.ts +1 -1
  19. package/dist/extension/lib/confirmations.d.ts +2 -0
  20. package/dist/extension/lib/confirmations.mjs +8 -5
  21. package/dist/extension/lib/conversation-runtime.mjs +1 -1
  22. package/dist/extension/lib/deferred-confirmations.d.ts +5 -0
  23. package/dist/extension/lib/deferred-confirmations.mjs +23 -10
  24. package/dist/extension/lib/ontology-connection.d.ts +1 -0
  25. package/dist/extension/lib/ontology-connection.mjs +18 -12
  26. package/dist/extension/lib/ontology-gateway.mjs +9 -2
  27. package/dist/extension/lib/ontology-metadata-read.mjs +1 -1
  28. package/dist/extension/lib/ontology-tools.d.ts +9 -2
  29. package/dist/extension/lib/ontology-tools.mjs +29 -5
  30. package/dist/extension/lib/runtime-envelope.d.ts +28 -0
  31. package/dist/extension/lib/runtime-envelope.mjs +8 -0
  32. package/dist/extension/lib/stored-files.mjs +1 -1
  33. package/dist/extension/lib/telegram/adapter.mjs +53 -11
  34. package/dist/extension/lib/telegram/attachment-description.mjs +1 -1
  35. package/dist/extension/lib/telegram/attachments-send.mjs +15 -12
  36. package/dist/extension/lib/telegram/callbacks.d.ts +18 -0
  37. package/dist/extension/lib/telegram/callbacks.mjs +155 -0
  38. package/dist/extension/lib/telegram/chat-post-card.d.ts +1 -1
  39. package/dist/extension/lib/telegram/chat-post-card.mjs +13 -10
  40. package/dist/extension/lib/telegram/confirmation-expiry.d.ts +2 -1
  41. package/dist/extension/lib/telegram/confirmation-expiry.mjs +6 -5
  42. package/dist/extension/lib/telegram/confirmation-text.d.ts +1 -1
  43. package/dist/extension/lib/telegram/confirmation-text.mjs +18 -12
  44. package/dist/extension/lib/telegram/confirmations.mjs +28 -23
  45. package/dist/extension/lib/telegram/conversations.d.ts +2 -0
  46. package/dist/extension/lib/telegram/conversations.mjs +14 -1
  47. package/dist/extension/lib/telegram/customer-text.d.ts +9 -8
  48. package/dist/extension/lib/telegram/customer-text.mjs +1 -1
  49. package/dist/extension/lib/telegram/deferred-confirmations.mjs +14 -13
  50. package/dist/extension/lib/telegram/files.mjs +2 -2
  51. package/dist/extension/lib/telegram/health.mjs +1 -1
  52. package/dist/extension/lib/telegram/images.mjs +1 -1
  53. package/dist/extension/lib/telegram/inbound-request.mjs +1 -1
  54. package/dist/extension/lib/telegram/inbound.mjs +25 -20
  55. package/dist/extension/lib/telegram/lifecycle.mjs +15 -3
  56. package/dist/extension/lib/telegram/media-policy.mjs +1 -1
  57. package/dist/extension/lib/telegram/principal.d.ts +1 -0
  58. package/dist/extension/lib/telegram/principal.mjs +1 -49
  59. package/dist/extension/lib/telegram/sessions.d.ts +3 -1
  60. package/dist/extension/lib/telegram/silent.mjs +3 -3
  61. package/dist/extension/lib/telegram/texts.d.ts +16 -0
  62. package/dist/extension/lib/telegram/texts.mjs +70 -0
  63. package/dist/extension/lib/telegram/turn-errors.d.ts +1 -1
  64. package/dist/extension/lib/telegram/turn-errors.mjs +38 -4
  65. package/dist/extension/lib/telegram/voice.mjs +1 -1
  66. package/dist/extension/lib/telegram/workspace-action.d.ts +21 -0
  67. package/dist/extension/lib/telegram/workspace-action.mjs +23 -0
  68. package/dist/extension/model.mjs +1 -1
  69. package/dist/extension/schedules/confirmations.mjs +1 -1
  70. package/dist/extension/tools/attachments_promote.mjs +8 -0
  71. package/dist/extension/tools/ontology.mjs +2 -2
  72. package/dist/extension/tools/questions_ask.mjs +1 -1
  73. package/package.json +2 -2
  74. package/dist/extension/connections/ontology.d.ts +0 -7
  75. package/dist/extension/connections/ontology.mjs +0 -11
@@ -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";
9
+ import { telegramSendScope } from "./send-queue.mjs";
10
+ import { m as safeTelegramFetch, p as reportTelegramError } from "../../../_chunks/inbound-request-BXhiIph-.mjs";
8
11
  import { runtimeToken } from "../runtime-token.mjs";
9
12
  import { conversations } from "../conversations.mjs";
10
- import { telegramSendScope } from "./send-queue.mjs";
11
- import { m as safeTelegramFetch, p as reportTelegramError } from "../../../_chunks/inbound-request-BrWspFw5.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 };
@@ -4,21 +4,22 @@ 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 { memberIdOf, telegramPrincipal } from "./principal.mjs";
8
- import { agentMessage, observedChatKey, withConfirmationPrompt } from "../group-routing/observed.mjs";
9
- import { conversationEvent } from "../conversations.mjs";
7
+ import { telegramText } from "./texts.mjs";
8
+ import { i as memberIdOf, o as telegramPrincipal } from "../../../_chunks/principal-VFQWqe5S.mjs";
10
9
  import { humanText } from "../confirmation-description.mjs";
11
10
  import { renderConfirmation, withConfirmationText } from "./confirmation-text.mjs";
12
11
  import { chatPostCard, chatPostCardStatus, chatPostReceipt, withChatPostCard } from "./chat-post-card.mjs";
13
- import { n as currentInboundRequest, p as reportTelegramError } from "../../../_chunks/inbound-request-BrWspFw5.mjs";
12
+ import { n as currentInboundRequest, p as reportTelegramError } from "../../../_chunks/inbound-request-BXhiIph-.mjs";
13
+ import { agentMessage, observedChatKey, withConfirmationPrompt } from "../group-routing/observed.mjs";
14
+ import { conversationEvent } from "../conversations.mjs";
15
+ import { formatAddress, scopedAddress } from "./sessions.mjs";
14
16
  import { chatPostOperationId, isChatPost } from "../chat-post-confirmation.mjs";
15
17
  import { confirmations } from "../confirmations.mjs";
16
- import { formatAddress, scopedAddress } from "./sessions.mjs";
17
18
  import { postDeferredCard } from "./deferred-confirmations.mjs";
18
19
  import { answerMarker, isPendingQuestion, pendingQuestions, questionKey } from "../questions.mjs";
19
- import { telegramTextLinks } from "@segmentstream-cloud/ontology/telegram";
20
20
  import { createHash } from "node:crypto";
21
- import { registerTelegramFreeformPrompt, renderTelegramInputRequest } from "eve/channels/telegram";
21
+ import { telegramTextLinks } from "@segmentstream-cloud/ontology/telegram";
22
+ import { registerTelegramFreeformPrompt } from "eve/channels/telegram";
22
23
  const PREFIX = "ss-confirm:";
23
24
  function shortHash(value) {
24
25
  return createHash("sha256").update(value).digest("hex").slice(0, 20);
@@ -130,7 +131,7 @@ async function handleConfirmationCallback(input) {
130
131
  showAlert: false
131
132
  });
132
133
  if (!query.data?.startsWith(PREFIX) || query.message === void 0 || query.from.isBot) {
133
- await notice("⚠️");
134
+ await notice(telegramText("callback", "unknown"));
134
135
  return;
135
136
  }
136
137
  const [requestHash, memberHash, choice] = query.data.slice(11).split(":");
@@ -139,7 +140,7 @@ async function handleConfirmationCallback(input) {
139
140
  externalId: query.from.id
140
141
  });
141
142
  if (lookup.status !== "paired" || shortHash(lookup.member.id) !== memberHash) {
142
- await notice("🔒");
143
+ await notice(telegramText("callback", "forbidden"));
143
144
  return;
144
145
  }
145
146
  const address = scopedAddress(formatAddress({
@@ -154,7 +155,7 @@ async function handleConfirmationCallback(input) {
154
155
  "n",
155
156
  "e"
156
157
  ].includes(choice ?? "") || choice === "e" && !isChatPost(prompt.toolName)) {
157
- await notice("⌛");
158
+ await notice(telegramText("callback", "already"));
158
159
  return;
159
160
  }
160
161
  const expired = !Number.isFinite(prompt.at) || prompt.at + 6e5 <= (input.now ?? Date.now());
@@ -182,7 +183,7 @@ async function handleConfirmationCallback(input) {
182
183
  ...expired ? { confirmationReason: "expired" } : edit ? { confirmationReason: "edit" } : {}
183
184
  }
184
185
  }, edit ? "The chat post was cancelled for editing. Ask the member what to change using questions_ask, then make a NEW chat_post_message call and confirmation. Never reuse the cancelled call." : void 0);
185
- await notice(expired ? "⌛" : void 0);
186
+ await notice(expired ? telegramText("callback", "expired") : void 0);
186
187
  }
187
188
  function messagePrompt(message) {
188
189
  return message.prompt ?? {
@@ -251,21 +252,25 @@ function telegramConfirmationEvents(archive, observed, deferredOptions) {
251
252
  async "input.requested"(data, channel, ctx) {
252
253
  for (const request of data.requests) {
253
254
  if (request.kind !== "tool-approval") {
254
- const rendered = renderTelegramInputRequest(request, channel.state);
255
- const sent = await channel.telegram.post({
256
- text: rendered.text,
257
- reply_markup: rendered.replyMarkup
258
- });
259
- remember(channel, ctx, sent.id, rendered.text);
260
- if (rendered.freeformRequestId !== void 0) registerTelegramFreeformPrompt(channel.state, {
255
+ const text = [request.prompt, ...(request.options ?? []).map((option, index) => `${index + 1}. ${option.label}`)].join("\n");
256
+ if (text.length > 4096) throw new Error("A typed Telegram question must fit in one message.");
257
+ const sent = await withConfirmationText(text, () => channel.telegram.post({
258
+ text,
259
+ reply_markup: {
260
+ force_reply: true,
261
+ selective: true
262
+ }
263
+ }));
264
+ remember(channel, ctx, sent.id, text);
265
+ registerTelegramFreeformPrompt(channel.state, {
261
266
  messageId: sent.id,
262
267
  requestId: request.requestId
263
268
  });
264
- else channel.state.hitlPrompts = {
269
+ channel.state.hitlPrompts = {
265
270
  ...channel.state.hitlPrompts,
266
271
  [request.requestId]: {
267
272
  messageId: sent.id,
268
- text: rendered.text,
273
+ text,
269
274
  options: (request.options ?? []).map(({ id, label }) => ({
270
275
  id,
271
276
  label
@@ -352,7 +357,7 @@ function telegramConfirmationEvents(archive, observed, deferredOptions) {
352
357
  await channel.telegram.editMessageText({
353
358
  messageId: prompt.messageId,
354
359
  replyMarkup: { inline_keyboard: [] },
355
- text: `${prompt.text}\n\n${label ?? "✅"}`
360
+ text: label === void 0 ? prompt.text : `${prompt.text}\n\n${label}`
356
361
  });
357
362
  delete channel.state.hitlPrompts?.[resolution.requestId];
358
363
  }
@@ -438,9 +443,9 @@ function telegramConfirmationEvents(archive, observed, deferredOptions) {
438
443
  const failed = data.status !== "completed" || data.result.kind === "tool-result" && (data.result.isError === true || object(data.result.output).isError === true || object(object(data.result.output).validation).ok === false);
439
444
  if (message.exactPost) {
440
445
  const copy = message.copy ?? renderConfirmation();
441
- const receipt = !failed && data.result.kind === "tool-result" ? chatPostReceipt(data.result.output, copy.completed) : void 0;
446
+ const receipt = !failed && data.result.kind === "tool-result" ? chatPostReceipt(data.result.output) : void 0;
442
447
  const errorId = failed ? reportTelegramError("confirmed_chat_post_failed", data, { callId: data.result.callId }) : void 0;
443
- const status = receipt ?? (failed ? `${copy.failed} ${errorId}\n⚠️ ❓` : "⚠️ ❓");
448
+ const status = receipt ?? (failed ? `${copy.failed} ${errorId}` : telegramText("chatPost", "uncertain") ?? "");
444
449
  const text = chatPostCardStatus(message.text, status);
445
450
  const prompt = {
446
451
  ...messagePrompt(message),
@@ -8,6 +8,7 @@ import type { AddressingReason, RouteDecider, RouteDecision } from "./router.js"
8
8
  export declare const REVISION_CLAIM_TTL_MS: number;
9
9
  /** Runs after webhook verification, before eve drops bot/channel/service updates. */
10
10
  export declare function captureTelegramWebhook(input: {
11
+ readonly answerCallback?: (id: string, locale?: string) => Promise<unknown>;
11
12
  readonly request: Request;
12
13
  readonly archive: Conversations;
13
14
  readonly identities: AgentIdentities;
@@ -31,6 +32,7 @@ export declare function captureTelegramFetch(fetchImpl: typeof fetch, archive: C
31
32
  export declare function conversationIdentities(identities: AgentIdentities): AgentIdentities;
32
33
  /** Verification and failure acknowledgement stay outside eve's swallowed onMessage failures. */
33
34
  export declare function telegramConversationCapture(input: {
35
+ readonly answerCallback?: (id: string, locale?: string) => Promise<unknown>;
34
36
  readonly archive: Conversations;
35
37
  readonly identities: () => AgentIdentities;
36
38
  readonly observed: ObservedMessages;
@@ -4,10 +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 { n as currentInboundRequest, p as reportTelegramError, s as currentThreadId } from "../../../_chunks/inbound-request-BXhiIph-.mjs";
7
8
  import { currentConfirmationPrompt, observedChatKey } from "../group-routing/observed.mjs";
8
9
  import { conversationEvent } from "../conversations.mjs";
9
10
  import { archivedRoutingMessages } from "../group-routing/routing-context.mjs";
10
- import { n as currentInboundRequest, p as reportTelegramError, s as currentThreadId } from "../../../_chunks/inbound-request-BrWspFw5.mjs";
11
11
  import { contentShape, messageContent } from "./content.mjs";
12
12
  import { messageMetadata } from "./observed.mjs";
13
13
  import { reportMembership } from "./membership.mjs";
@@ -79,6 +79,19 @@ async function captureTelegramWebhook(input) {
79
79
  return input.next();
80
80
  }
81
81
  const update = record(raw);
82
+ const callback = record(update.callback_query);
83
+ const data = callback.data;
84
+ if (typeof data === "string" && (data.startsWith("eve_auth:") || data.startsWith("eve:"))) {
85
+ if (typeof callback.id === "string" && input.answerCallback !== void 0) {
86
+ const locale = record(callback.from).language_code;
87
+ try {
88
+ await input.answerCallback(callback.id, typeof locale === "string" ? locale : void 0);
89
+ } catch (error) {
90
+ reportTelegramError("unsupported_callback_ack_failed", error, { callbackQueryId: callback.id });
91
+ }
92
+ }
93
+ return new Response("ok");
94
+ }
82
95
  if (input.chats !== void 0 && (update.my_chat_member !== void 0 || update.chat_member !== void 0)) {
83
96
  const task = reportMembership({
84
97
  update,
@@ -4,14 +4,15 @@ export { isTechnicalText } from "../customer-text-safety.js";
4
4
  export declare function withDeliveryLocale<T>(locale: string, run: () => T, threadId?: string, replyTo?: string, chatId?: string): T;
5
5
  export declare function currentLocale(): string;
6
6
  export declare function currentThreadId(): string | undefined;
7
- export declare function customerCopy(): {
8
- failed: string;
9
- recovery: string;
10
- attachment: string;
11
- authorization: string;
12
- response: string;
13
- connected: string;
14
- disconnected: string;
7
+ export declare function customerCopy(locale?: string): {
8
+ failed: string | undefined;
9
+ recovery: string | undefined;
10
+ attachment: string | undefined;
11
+ authorization: string | undefined;
12
+ response: string | undefined;
13
+ connected: string | undefined;
14
+ disconnected: string | undefined;
15
+ cancelled: string | undefined;
15
16
  };
16
17
  /** Original failures stay in operator diagnostics, joined to the public notice by a short reference. */
17
18
  export declare function reportTelegramError(operation: string, error: unknown, metadata?: Record<string, unknown>, secrets?: ReadonlySet<string>): string;
@@ -5,5 +5,5 @@ const __filename = __eveFileURLToPath(import.meta.url);
5
5
  __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { isTechnicalText } from "../customer-text-safety.mjs";
8
- import { a as TelegramDeliveryError, c as customerCopy, d as failureText, f as formattingRefused, h as withDeliveryLocale, l as customerMessage, m as safeTelegramFetch, o as currentLocale, p as reportTelegramError, s as currentThreadId, u as customerText } from "../../../_chunks/inbound-request-BrWspFw5.mjs";
8
+ import { a as TelegramDeliveryError, c as customerCopy, d as failureText, f as formattingRefused, h as withDeliveryLocale, l as customerMessage, m as safeTelegramFetch, o as currentLocale, p as reportTelegramError, s as currentThreadId, u as customerText } from "../../../_chunks/inbound-request-BXhiIph-.mjs";
9
9
  export { TelegramDeliveryError, currentLocale, currentThreadId, customerCopy, customerMessage, customerText, failureText, formattingRefused, isTechnicalText, reportTelegramError, safeTelegramFetch, withDeliveryLocale };
@@ -4,23 +4,24 @@ 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";
8
- import { agentMessage, observedChatKey, withConfirmationPrompt } from "../group-routing/observed.mjs";
9
- import { conversationEvent } from "../conversations.mjs";
7
+ import { telegramText, withTelegramLocale } from "./texts.mjs";
8
+ import { o as telegramPrincipal } from "../../../_chunks/principal-VFQWqe5S.mjs";
10
9
  import { humanText } from "../confirmation-description.mjs";
11
10
  import { renderConfirmation, withConfirmationText } from "./confirmation-text.mjs";
12
11
  import { chatPostCard, chatPostCardStatus, chatPostReceipt, withChatPostCard } from "./chat-post-card.mjs";
13
- import { n as currentInboundRequest, p as reportTelegramError } from "../../../_chunks/inbound-request-BrWspFw5.mjs";
12
+ import { n as currentInboundRequest, p as reportTelegramError } from "../../../_chunks/inbound-request-BXhiIph-.mjs";
13
+ import { agentMessage, observedChatKey, withConfirmationPrompt } from "../group-routing/observed.mjs";
14
+ import { conversationEvent } from "../conversations.mjs";
15
+ import { formatAddress } from "./sessions.mjs";
14
16
  import { CONFIRMATION_TTL_MS, inputHash } from "../confirmations.mjs";
15
17
  import { OntologyToolRejected } from "../ontology-tools.mjs";
16
18
  import { confirmationOutcome } from "../confirmation-outcome.mjs";
17
19
  import { deferredConfirmations, deferredKey, runDeferredAction } from "../deferred-confirmations.mjs";
18
- import { formatAddress } from "./sessions.mjs";
19
20
  import { telegramTextLinks } from "@segmentstream-cloud/ontology/telegram";
20
21
  const PREFIX = "ss-confirm:d:";
21
22
  const isDeferredCallback = (data) => data?.startsWith(PREFIX) === true;
22
23
  const deferredCallback = (record, choice) => `${PREFIX}${record.key}:${choice}`;
23
- const copyOf = (record) => record.data.confirmation.description?.chatPost ? chatPostCard(record.data.confirmation.description, record.data.confirmation.copy) : renderConfirmation(record.data.confirmation.description, record.data.confirmation.copy);
24
+ const copyOf = (record) => withTelegramLocale(record.data.locale, () => record.data.confirmation.description?.chatPost ? chatPostCard(record.data.confirmation.description, record.data.confirmation.copy) : renderConfirmation(record.data.confirmation.description, record.data.confirmation.copy));
24
25
  function promptOf(record, state) {
25
26
  const name = humanText(record.data.requesterName);
26
27
  return {
@@ -28,7 +29,7 @@ function promptOf(record, state) {
28
29
  requestId: record.id,
29
30
  sessionId: record.data.sessionId,
30
31
  address: record.data.address,
31
- action: humanText(record.data.confirmation.description?.action) ?? "❓",
32
+ action: humanText(record.data.confirmation.description?.action) ?? "",
32
33
  requestedBy: name ?? null,
33
34
  at: new Date(record.expiresAt - CONFIRMATION_TTL_MS).toISOString(),
34
35
  state,
@@ -151,7 +152,7 @@ async function handleDeferredCallback(input) {
151
152
  };
152
153
  const parts = /^ss-confirm:d:([0-9a-f]{20}):([yne])$/.exec(query.data ?? "");
153
154
  if (!parts || !query.message || query.from.isBot) {
154
- await notice("⚠️");
155
+ await notice(telegramText("callback", "unknown"));
155
156
  return;
156
157
  }
157
158
  const store = deferredConfirmations(options);
@@ -164,11 +165,11 @@ async function handleDeferredCallback(input) {
164
165
  externalId: query.from.id
165
166
  });
166
167
  if (!record || lookup.status !== "paired" || lookup.member.id !== record.memberId || record.data.requester !== query.from.id || record.data.chatId !== query.message.chat.id || record.data.threadId !== String(query.message.messageThreadId ?? "") || record.data.messageId !== query.message.messageId) {
167
- await notice("🔒");
168
+ await notice(telegramText("callback", "forbidden"));
168
169
  return;
169
170
  }
170
171
  if (record.inputHash !== inputHash(record.data.input) || parts[2] === "e" && record.data.confirmation.description?.chatPost === void 0) {
171
- await notice("⚠️");
172
+ await notice(telegramText("callback", "unknown"));
172
173
  return;
173
174
  }
174
175
  const copy = copyOf(record);
@@ -181,10 +182,10 @@ async function handleDeferredCallback(input) {
181
182
  decision
182
183
  });
183
184
  if (!claimed || claimed.status === "pending") {
184
- await notice("⌛");
185
+ await notice(telegramText("callback", "already"));
185
186
  return;
186
187
  }
187
- await notice(claimed.status === "expired" ? "⌛" : void 0);
188
+ await notice(claimed.status === "expired" ? telegramText("callback", "expired") : void 0);
188
189
  let status = claimed.status;
189
190
  let cardStatus = status === "expired" ? copy.expired : copy.cancelled;
190
191
  let execution;
@@ -213,7 +214,7 @@ async function handleDeferredCallback(input) {
213
214
  try {
214
215
  const result = await runDeferredAction(claimed, options);
215
216
  status = "done";
216
- cardStatus = claimed.data.confirmation.description?.chatPost ? chatPostReceipt(result.structuredContent, copy.completed) ?? "⚠️ ❓" : copy.completed;
217
+ cardStatus = claimed.data.confirmation.description?.chatPost ? withTelegramLocale(claimed.data.locale, () => chatPostReceipt(result.structuredContent) ?? telegramText("chatPost", "uncertain") ?? "") : copy.completed;
217
218
  } catch (error) {
218
219
  status = "failed";
219
220
  const reference = reportTelegramError("deferred_action_failed", error);
@@ -4,8 +4,8 @@ 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";
8
- import { d as failureText, p as reportTelegramError } from "../../../_chunks/inbound-request-BrWspFw5.mjs";
7
+ import { n as conversationLanguage } from "../../../_chunks/principal-VFQWqe5S.mjs";
8
+ import { d as failureText, p as reportTelegramError } from "../../../_chunks/inbound-request-BXhiIph-.mjs";
9
9
  import { describableImage, describeReceivedImage } from "./images.mjs";
10
10
  import { MAXIMUM_ATTACHMENT_BYTES, attachmentUrl } from "../attachments.mjs";
11
11
  function telegramConversation(chatId) {
@@ -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 { t as agentBuild } from "../../../_chunks/build-metadata-CLprTpsn.mjs";
7
+ import { t as agentBuild } from "../../../_chunks/build-metadata-BB2uSGsI.mjs";
8
8
  import { runtimeRequestFailure } from "./runtime-request.mjs";
9
9
  import { GET } from "eve/channels";
10
10
  const HEALTH_ROUTE = "/eve/v1/telegram/health";
@@ -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 { gateway, generateText } from "ai";
9
9
  const MAXIMUM_IMAGE_DESCRIPTION_BYTES = 5242880;
10
10
  const MAXIMUM_NATIVE_IMAGE_BYTES = 3145728;
@@ -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 { i as withReplacedText, n as currentInboundRequest, r as runInboundRequest, t as InboundRequest } from "../../../_chunks/inbound-request-BrWspFw5.mjs";
7
+ import { i as withReplacedText, n as currentInboundRequest, r as runInboundRequest, t as InboundRequest } from "../../../_chunks/inbound-request-BXhiIph-.mjs";
8
8
  export { InboundRequest, currentInboundRequest, runInboundRequest, withReplacedText };
@@ -5,16 +5,17 @@ const __filename = __eveFileURLToPath(import.meta.url);
5
5
  __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { effectiveGroupPolicy, groupPolicy } from "../group-routing/policy.mjs";
8
- import { conversationLanguage, telegramPrincipal } from "./principal.mjs";
8
+ import { admitMessage, localizedText } from "../admission.mjs";
9
+ import { d as telegramRuntimeSource, n as conversationLanguage, o as telegramPrincipal, u as runtimeEnvelope } from "../../../_chunks/principal-VFQWqe5S.mjs";
10
+ import { p as reportTelegramError } from "../../../_chunks/inbound-request-BXhiIph-.mjs";
9
11
  import { addressingReason, conversationKey, otherRecipient } from "../group-routing/signals.mjs";
10
12
  import { conversationAddressing, conversationState } from "../group-routing/conversation.mjs";
11
13
  import { questionInvitation } from "../group-routing/engagement.mjs";
12
14
  import { conversationContext, routeMessage } from "../group-routing/router.mjs";
13
15
  import { observedContext } from "../group-routing/observed.mjs";
14
16
  import { debounceBurst, routingBurst } from "../group-routing/burst.mjs";
15
- import { p as reportTelegramError } from "../../../_chunks/inbound-request-BrWspFw5.mjs";
16
- import { AttachmentLifecycleNotPermitted, startTypingHeartbeat, typingUntilComplete } from "./lifecycle.mjs";
17
17
  import { formatAddress, messageAddress, scopedAddress } from "./sessions.mjs";
18
+ import { AttachmentLifecycleNotPermitted, startTypingHeartbeat, typingUntilComplete } from "./lifecycle.mjs";
18
19
  import { contentShape, messageContent } from "./content.mjs";
19
20
  import { messageAudience } from "./audience.mjs";
20
21
  import { attachmentKind, messageMetadata } from "./observed.mjs";
@@ -27,13 +28,9 @@ import { questionAddress, questionFromHistory } from "./confirmations.mjs";
27
28
  import { routingContext } from "./routing-context.mjs";
28
29
  import { recordGateDecision, recordInboundContent, recordRouteDecision } from "./conversations.mjs";
29
30
  import { chatContext } from "./chats.mjs";
30
- import { admitMessage, localizedText } from "../admission.mjs";
31
31
  import { attachmentsContext, inboundAttachments, stageFiles } from "./files.mjs";
32
32
  import { heldApprovals, releaseHeldApproval } from "./held-approvals.mjs";
33
33
  import { voiceOf, voiceText } from "./voice.mjs";
34
- const UNKNOWN_PRIVATE = "🔗";
35
- const UNKNOWN_IN_GROUP = "🔒";
36
- const REFUSED = "🔒";
37
34
  const MAXIMUM_QUOTE_LENGTH = 500;
38
35
  function buttonTo(text, url) {
39
36
  return { inline_keyboard: [[{
@@ -96,12 +93,6 @@ function speakerBlock(input) {
96
93
  lines.push("</speaker>");
97
94
  return lines.join("\n");
98
95
  }
99
- function unknownInGroup(bot) {
100
- return bot?.username === void 0 ? { text: UNKNOWN_IN_GROUP } : {
101
- text: UNKNOWN_IN_GROUP,
102
- reply_markup: buttonTo("✉️", `https://t.me/${bot.username}`)
103
- };
104
- }
105
96
  async function answerUnpaired(input) {
106
97
  const { context, message, lookup, dependencies } = input;
107
98
  if (lookup.status === "limited") {
@@ -131,12 +122,19 @@ async function answerUnpaired(input) {
131
122
  }
132
123
  if (lookup.status === "refused") {
133
124
  const text = localizedText(dependencies.admission().replies.refused, message.from.languageCode);
134
- await context.telegram.post(text ?? REFUSED);
135
- return true;
125
+ if (text !== void 0) await context.telegram.post(text);
126
+ return text !== void 0;
136
127
  }
128
+ const replies = dependencies.admission().replies;
129
+ const locale = message.from.languageCode;
137
130
  if (message.chat.type !== "private") {
138
- await context.telegram.post(localizedText(dependencies.admission().replies.groupPairingRequired, message.from.languageCode) ?? unknownInGroup(input.bot));
139
- return true;
131
+ const text = localizedText(replies.groupPairingRequired, locale);
132
+ const button = localizedText(replies.groupPairingButton, locale);
133
+ if (text !== void 0) await context.telegram.post({
134
+ text,
135
+ ...button === void 0 || input.bot?.username === void 0 ? {} : { reply_markup: buttonTo(button, `https://t.me/${input.bot.username}`) }
136
+ });
137
+ return text !== void 0;
140
138
  }
141
139
  const sender = display(message.from);
142
140
  const pairing = await dependencies.identities.createPairing({
@@ -144,9 +142,12 @@ async function answerUnpaired(input) {
144
142
  externalId: message.from.id,
145
143
  ...sender === void 0 ? {} : { display: sender }
146
144
  });
145
+ const text = localizedText(replies.pairingRequired, locale);
146
+ const label = localizedText(replies.pairingButton, locale);
147
+ if (text === void 0 || label === void 0) throw new Error("replies.pairingRequired and replies.pairingButton are required for Telegram.");
147
148
  await context.telegram.post({
148
- text: UNKNOWN_PRIVATE,
149
- reply_markup: buttonTo("🔗", dependencies.connectUrl(pairing.code))
149
+ text,
150
+ reply_markup: buttonTo(label, dependencies.connectUrl(pairing.code))
150
151
  });
151
152
  return true;
152
153
  }
@@ -756,6 +757,7 @@ async function handleInboundMessage(input) {
756
757
  } : mediaPlan },
757
758
  ...dependencies.describeImage === void 0 ? {} : { describeImage: dependencies.describeImage }
758
759
  });
760
+ if (input.request !== void 0) input.request.files = files;
759
761
  if (input.request?.lifecycle !== void 0) {
760
762
  const lifecycle = input.request.lifecycle;
761
763
  const key = input.request.lifecycleKey;
@@ -786,7 +788,10 @@ async function handleInboundMessage(input) {
786
788
  outcome = { result: (await dependencies.attachments.promote({
787
789
  memberId: member.id,
788
790
  conversation: `telegram:${sent.chat.id}`
789
- }, file.attachment.id, stored.retain)).result };
791
+ }, file.attachment.id, {
792
+ ...stored.retain,
793
+ runtime: runtimeEnvelope({ source: telegramRuntimeSource(sent, files) })
794
+ })).result };
790
795
  } catch (error) {
791
796
  outcome = { error: reportTelegramError("attachment_retention_failed", error, { attachmentId: file.attachment.id }) };
792
797
  }