@segmentstream/agent 0.12.1 → 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 (62) hide show
  1. package/README.md +29 -22
  2. package/dist/_chunks/{build-metadata-Dl-ZJp5p.mjs → build-metadata-BLeBanNg.mjs} +1 -1
  3. package/dist/_chunks/{inbound-request-BrWspFw5.mjs → inbound-request-BXhiIph-.mjs} +21 -19
  4. package/dist/_chunks/{workflow-serializable-BxrXgxjc.mjs → workflow-serializable-DVQBWK8p.mjs} +87 -4
  5. package/dist/extension/_manifest.json +0 -1
  6. package/dist/extension/config.d.ts +18 -5
  7. package/dist/extension/config.mjs +138 -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/build-metadata.mjs +1 -1
  13. package/dist/extension/lib/chat-post-confirmation.d.ts +1 -0
  14. package/dist/extension/lib/chat-post-confirmation.mjs +4 -2
  15. package/dist/extension/lib/confirmation-outcome.d.ts +1 -1
  16. package/dist/extension/lib/confirmations.d.ts +2 -0
  17. package/dist/extension/lib/confirmations.mjs +7 -4
  18. package/dist/extension/lib/conversations.mjs +1 -1
  19. package/dist/extension/lib/deferred-confirmations.d.ts +3 -0
  20. package/dist/extension/lib/deferred-confirmations.mjs +2 -0
  21. package/dist/extension/lib/ontology-tools.mjs +1 -1
  22. package/dist/extension/lib/telegram/adapter.mjs +34 -7
  23. package/dist/extension/lib/telegram/attachment-description.mjs +1 -1
  24. package/dist/extension/lib/telegram/attachments-send.mjs +13 -10
  25. package/dist/extension/lib/telegram/chat-post-card.d.ts +1 -1
  26. package/dist/extension/lib/telegram/chat-post-card.mjs +13 -10
  27. package/dist/extension/lib/telegram/confirmation-expiry.d.ts +2 -1
  28. package/dist/extension/lib/telegram/confirmation-expiry.mjs +5 -4
  29. package/dist/extension/lib/telegram/confirmation-text.d.ts +1 -1
  30. package/dist/extension/lib/telegram/confirmation-text.mjs +18 -12
  31. package/dist/extension/lib/telegram/confirmations.mjs +24 -19
  32. package/dist/extension/lib/telegram/conversations.d.ts +2 -0
  33. package/dist/extension/lib/telegram/conversations.mjs +14 -1
  34. package/dist/extension/lib/telegram/customer-text.d.ts +9 -8
  35. package/dist/extension/lib/telegram/customer-text.mjs +1 -1
  36. package/dist/extension/lib/telegram/deferred-confirmations.mjs +10 -9
  37. package/dist/extension/lib/telegram/files.mjs +1 -1
  38. package/dist/extension/lib/telegram/health.mjs +1 -1
  39. package/dist/extension/lib/telegram/images.mjs +1 -1
  40. package/dist/extension/lib/telegram/inbound-request.mjs +1 -1
  41. package/dist/extension/lib/telegram/inbound.mjs +18 -17
  42. package/dist/extension/lib/telegram/lifecycle.mjs +2 -2
  43. package/dist/extension/lib/telegram/media-policy.mjs +1 -1
  44. package/dist/extension/lib/telegram/silent.mjs +1 -1
  45. package/dist/extension/lib/telegram/texts.d.ts +16 -0
  46. package/dist/extension/lib/telegram/texts.mjs +70 -0
  47. package/dist/extension/lib/telegram/turn-errors.d.ts +1 -1
  48. package/dist/extension/lib/telegram/turn-errors.mjs +37 -3
  49. package/dist/extension/lib/telegram/voice.mjs +1 -1
  50. package/dist/extension/lib/workflow-serializable.mjs +1 -1
  51. package/dist/extension/schedules/confirmations.mjs +1 -1
  52. package/dist/extension/tools/attachments_discard.mjs +1 -1
  53. package/dist/extension/tools/attachments_list.mjs +1 -1
  54. package/dist/extension/tools/attachments_promote.mjs +3 -1
  55. package/dist/extension/tools/attachments_read.mjs +1 -1
  56. package/dist/extension/tools/attachments_send.mjs +1 -1
  57. package/dist/extension/tools/attachments_table.mjs +1 -1
  58. package/dist/extension/tools/ontology.mjs +1 -1
  59. package/dist/extension/tools/questions_ask.mjs +1 -1
  60. package/package.json +2 -2
  61. package/dist/extension/connections/ontology.d.ts +0 -7
  62. package/dist/extension/connections/ontology.mjs +0 -11
@@ -5,6 +5,9 @@ const __filename = __eveFileURLToPath(import.meta.url);
5
5
  __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { effectiveGroupPolicy, groupPolicy } from "./lib/group-routing/policy.mjs";
8
+ import { localizedText as localizedText$1 } from "./lib/admission.mjs";
9
+ import { TELEGRAM_TEXT_KEYS } from "./lib/telegram/texts.mjs";
10
+ import { isTechnicalText } from "./lib/customer-text-safety.mjs";
8
11
  import { readFileSync, realpathSync } from "node:fs";
9
12
  import { isAbsolute, relative, resolve } from "node:path";
10
13
  import { expandGroupFiles, groupPolicyIssues } from "@segmentstream-cloud/ontology/groups";
@@ -24,6 +27,9 @@ const DEFAULT_ADMISSION_LIMITS = {
24
27
  workspaceTurnsPerDay: 3e3
25
28
  };
26
29
  const ADMISSION_REPLY_KEYS = [
30
+ "pairingRequired",
31
+ "pairingButton",
32
+ "groupPairingButton",
27
33
  "limited",
28
34
  "perSenderLimited",
29
35
  "workspaceLimited",
@@ -72,7 +78,8 @@ function telegramSettings(value, issues) {
72
78
  "groups",
73
79
  "linkPreviews",
74
80
  "additionalOwnOrigins",
75
- "attachmentLifecycle"
81
+ "attachmentLifecycle",
82
+ "menuButton"
76
83
  ],
77
84
  issues
78
85
  });
@@ -115,6 +122,17 @@ function telegramSettings(value, issues) {
115
122
  message: "must be an array of HTTP(S) origins without paths, credentials, queries or fragments",
116
123
  path: [...path, "additionalOwnOrigins"]
117
124
  });
125
+ const menuButton = section({
126
+ value: telegram.menuButton,
127
+ path: [...path, "menuButton"],
128
+ allowed: ["text"],
129
+ issues
130
+ });
131
+ const menuText = menuButton === void 0 ? void 0 : localizedText(menuButton.text, [
132
+ ...path,
133
+ "menuButton",
134
+ "text"
135
+ ], issues, 64);
118
136
  const lifecycle = section({
119
137
  value: telegram.attachmentLifecycle,
120
138
  path: [...path, "attachmentLifecycle"],
@@ -143,6 +161,7 @@ function telegramSettings(value, issues) {
143
161
  ...signalSettings === void 0 ? {} : { groups: signalSettings },
144
162
  linkPreviews,
145
163
  additionalOwnOrigins,
164
+ ...menuText === void 0 ? {} : { menuButton: { text: menuText } },
146
165
  ...lifecycle === void 0 ? {} : { attachmentLifecycle: {
147
166
  entity: String(lifecycle.entity),
148
167
  action: String(lifecycle.action),
@@ -265,7 +284,7 @@ function sessionSettings(value, issues) {
265
284
  idleResetHours: typeof idleResetHours === "number" && Number.isFinite(idleResetHours) && idleResetHours > 0 ? idleResetHours : false
266
285
  };
267
286
  }
268
- function localizedText(value, path, issues) {
287
+ function localizedText(value, path, issues, maximum = 4e3, safe = true) {
269
288
  if (!isRecord(value) || Object.keys(value).length === 0) {
270
289
  issues.push({
271
290
  message: "must be a non-empty locale-to-text object",
@@ -282,14 +301,14 @@ function localizedText(value, path, issues) {
282
301
  path: [...path, locale]
283
302
  });
284
303
  }
285
- if (typeof text !== "string" || text.trim() === "" || text.length > 4e3) issues.push({
286
- message: "must be non-empty text of at most 4000 characters",
304
+ if (typeof text !== "string" || text.trim() === "" || text.length > maximum || safe && typeof text === "string" && isTechnicalText(text)) issues.push({
305
+ message: `must be ${safe ? "safe " : ""}non-empty text of at most ${maximum} characters`,
287
306
  path: [...path, locale]
288
307
  });
289
308
  }
290
309
  return value;
291
310
  }
292
- function firstContactSettings(value, issues) {
311
+ function firstContactSettings(value, issues, safe = true) {
293
312
  const path = ["firstContact"];
294
313
  const settings = section({
295
314
  value,
@@ -303,7 +322,7 @@ function firstContactSettings(value, issues) {
303
322
  message: "must be non-empty text of at most 200 characters without surrounding whitespace",
304
323
  path: [...path, "version"]
305
324
  });
306
- const notice = localizedText(settings.notice, [...path, "notice"], issues);
325
+ const notice = localizedText(settings.notice, [...path, "notice"], issues, 4e3, safe);
307
326
  return {
308
327
  version: typeof version === "string" ? version : "",
309
328
  notice
@@ -351,7 +370,7 @@ function admissionLimits(value, issues) {
351
370
  workspaceTurnsPerDay: limit(limits?.workspaceTurnsPerDay, DEFAULT_ADMISSION_LIMITS.workspaceTurnsPerDay, [...path, "workspaceTurnsPerDay"])
352
371
  };
353
372
  }
354
- function admissionReplies(value, issues) {
373
+ function admissionReplies(value, issues, safe = true) {
355
374
  const replies = section({
356
375
  value,
357
376
  path: ["replies"],
@@ -359,10 +378,90 @@ function admissionReplies(value, issues) {
359
378
  issues
360
379
  });
361
380
  const configured = {};
362
- for (const key of ADMISSION_REPLY_KEYS) if (replies?.[key] !== void 0) configured[key] = localizedText(replies[key], ["replies", key], issues);
381
+ for (const key of ADMISSION_REPLY_KEYS) if (replies?.[key] !== void 0) configured[key] = localizedText(replies[key], ["replies", key], issues, safe && key.endsWith("Button") ? 24 : 4e3, safe);
363
382
  return configured;
364
383
  }
365
- function validateConfig(input) {
384
+ function telegramTexts(value, issues) {
385
+ const root = section({
386
+ value,
387
+ path: ["texts"],
388
+ allowed: Object.keys(TELEGRAM_TEXT_KEYS),
389
+ issues
390
+ });
391
+ const output = {};
392
+ for (const [group, keys] of Object.entries(TELEGRAM_TEXT_KEYS)) {
393
+ const sectionValue = section({
394
+ value: root?.[group],
395
+ path: ["texts", group],
396
+ allowed: keys,
397
+ issues
398
+ });
399
+ if (sectionValue === void 0) continue;
400
+ output[group] = {};
401
+ for (const key of keys) if (sectionValue[key] !== void 0) {
402
+ const maximum = group === "confirmation" ? key === "question" ? 300 : 24 : group === "callback" ? 200 : group === "authorization" && key === "button" ? 24 : 4e3;
403
+ output[group][key] = localizedText(sectionValue[key], [
404
+ "texts",
405
+ group,
406
+ key
407
+ ], issues, maximum);
408
+ }
409
+ }
410
+ const confirmation = output.confirmation;
411
+ if (confirmation?.confirm !== void 0 && confirmation.cancel !== void 0) {
412
+ const locales = new Set([
413
+ "en",
414
+ ...Object.keys(confirmation.confirm),
415
+ ...Object.keys(confirmation.cancel)
416
+ ]);
417
+ for (const locale of locales) {
418
+ const confirm = localizedText$1(confirmation.confirm, locale);
419
+ const cancel = localizedText$1(confirmation.cancel, locale);
420
+ if (typeof confirm === "string" && typeof cancel === "string" && confirm.trim() === cancel.trim()) issues.push({
421
+ message: "confirm and cancel must have distinct effective localized labels",
422
+ path: [
423
+ "texts",
424
+ "confirmation",
425
+ "cancel",
426
+ locale
427
+ ]
428
+ });
429
+ }
430
+ }
431
+ const authorization = output.authorization;
432
+ if (authorization?.url !== void 0) for (const [locale, value] of Object.entries(authorization.url)) try {
433
+ const url = new URL(value);
434
+ if (!["https:", "http:"].includes(url.protocol) || url.username || url.password) throw new Error("Invalid authorization URL");
435
+ } catch {
436
+ issues.push({
437
+ message: "must be an HTTP(S) URL without credentials",
438
+ path: [
439
+ "texts",
440
+ "authorization",
441
+ "url",
442
+ locale
443
+ ]
444
+ });
445
+ }
446
+ if ((authorization?.url !== void 0 || authorization?.button !== void 0) && authorization?.prompt === void 0) issues.push({
447
+ message: "is required when an authorization button is configured",
448
+ path: [
449
+ "texts",
450
+ "authorization",
451
+ "prompt"
452
+ ]
453
+ });
454
+ if (authorization?.url === void 0 !== (authorization?.button === void 0)) issues.push({
455
+ message: "url and button must be supplied together",
456
+ path: ["texts", "authorization"]
457
+ });
458
+ return output;
459
+ }
460
+ function isLegacyAgentArchive(version) {
461
+ return /^0\.(?:[0-9]|1[0-2])\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version ?? "");
462
+ }
463
+ function validateConfig(input, options = {}) {
464
+ const legacyArchive = isLegacyAgentArchive(options.archiveSdkVersion);
366
465
  const issues = [];
367
466
  const root = section({
368
467
  value: input ?? {},
@@ -376,7 +475,8 @@ function validateConfig(input) {
376
475
  "confirmations",
377
476
  "firstContact",
378
477
  "limits",
379
- "replies"
478
+ "replies",
479
+ "texts"
380
480
  ],
381
481
  issues
382
482
  });
@@ -410,8 +510,8 @@ function validateConfig(input) {
410
510
  allowed: ["mode"],
411
511
  issues
412
512
  })?.mode ?? "deferred";
413
- if (mode !== "deferred" && mode !== "held") issues.push({
414
- message: "must be \"deferred\" or \"held\"",
513
+ if (mode !== "deferred" && !(legacyArchive && mode === "held")) issues.push({
514
+ message: "must be \"deferred\"; Telegram Eve approval buttons are unsupported",
415
515
  path: ["confirmations", "mode"]
416
516
  });
417
517
  const telegram = telegramSettings(channels?.telegram, issues);
@@ -502,9 +602,28 @@ function validateConfig(input) {
502
602
  });
503
603
  const connection = ontology?.connection ?? true;
504
604
  const sessions = sessionSettings(root?.sessions, issues);
505
- const firstContact = firstContactSettings(root?.firstContact, issues);
605
+ const firstContact = firstContactSettings(root?.firstContact, issues, !legacyArchive);
506
606
  const limits = admissionLimits(root?.limits, issues);
507
- const replies = admissionReplies(root?.replies, issues);
607
+ const replies = admissionReplies(root?.replies, issues, !legacyArchive);
608
+ const texts = telegramTexts(root?.texts, issues);
609
+ if (!legacyArchive && replies.groupPairingButton !== void 0 && replies.groupPairingRequired === void 0) issues.push({
610
+ message: "is required when groupPairingButton is configured",
611
+ path: ["replies", "groupPairingRequired"]
612
+ });
613
+ if (telegram !== null && !legacyArchive) {
614
+ for (const key of ["pairingRequired", "pairingButton"]) if (replies[key] === void 0) issues.push({
615
+ message: "is required for Telegram",
616
+ path: ["replies", key]
617
+ });
618
+ for (const key of TELEGRAM_TEXT_KEYS.confirmation) if (texts.confirmation?.[key] === void 0) issues.push({
619
+ message: "is required for Telegram confirmations",
620
+ path: [
621
+ "texts",
622
+ "confirmation",
623
+ key
624
+ ]
625
+ });
626
+ }
508
627
  if (typeof connection !== "boolean") issues.push({
509
628
  message: "must be true or false",
510
629
  path: ["ontology", "connection"]
@@ -524,17 +643,18 @@ function validateConfig(input) {
524
643
  attachments
525
644
  },
526
645
  ontology: { connection: connection === true },
527
- confirmations: { mode: mode === "held" ? "held" : "deferred" },
646
+ confirmations: { mode: "deferred" },
528
647
  sessions,
529
648
  firstContact,
530
649
  limits,
531
- replies
650
+ replies,
651
+ texts
532
652
  } };
533
653
  }
534
654
  const configSchema = { "~standard": {
535
655
  version: 1,
536
656
  vendor: "segmentstream",
537
- validate: validateConfig,
657
+ validate: (input) => validateConfig(input),
538
658
  types: void 0
539
659
  } };
540
660
  function manifestOf(config) {
@@ -542,4 +662,4 @@ function manifestOf(config) {
542
662
  if (validation.issues !== void 0) throw new SegmentStreamAgentConfigError(validation.issues);
543
663
  return validation.value;
544
664
  }
545
- export { ADMISSION_REPLY_KEYS, DEFAULT_ADMISSION_LIMITS, DEFAULT_ATTACHMENTS_MODEL, DEFAULT_DECISION_MODEL, DEFAULT_IMAGE_DESCRIPTION_MODEL, DEFAULT_STT_MODEL, SESSION_SCOPES, SegmentStreamAgentConfigError, TELEGRAM_POLICIES, configSchema, manifestOf, validateConfig };
665
+ export { ADMISSION_REPLY_KEYS, DEFAULT_ADMISSION_LIMITS, DEFAULT_ATTACHMENTS_MODEL, DEFAULT_DECISION_MODEL, DEFAULT_IMAGE_DESCRIPTION_MODEL, DEFAULT_STT_MODEL, SESSION_SCOPES, SegmentStreamAgentConfigError, TELEGRAM_POLICIES, configSchema, isLegacyAgentArchive, manifestOf, validateConfig };
@@ -4,7 +4,12 @@ export declare const segmentstream: import("eve/extension").ExtensionHandle<{
4
4
  readonly "~standard": {
5
5
  readonly version: 1;
6
6
  readonly vendor: "segmentstream";
7
- readonly validate: typeof import("./config.js").validateConfig;
7
+ readonly validate: (input: unknown) => {
8
+ readonly value: import("./config.js").SegmentStreamAgentConfig;
9
+ readonly issues?: undefined;
10
+ } | {
11
+ readonly issues: ReadonlyArray<import("./config.js").ConfigIssue>;
12
+ };
8
13
  readonly types: {
9
14
  readonly input: import("./config.js").SegmentStreamAgentConfigInput;
10
15
  readonly output: import("./config.js").SegmentStreamAgentConfig;
@@ -25,3 +30,4 @@ export { archivedRoutingMessages } from "./lib/group-routing/routing-context.js"
25
30
  export { replayRouterPayload } from "./lib/group-routing/replay.js";
26
31
  export { conversationState as groupConversationState, conversationAddressing as groupConversationAddressing, type ConversationAnalysis, type ConversationPath, } from "./lib/group-routing/conversation.js";
27
32
  export { routingBurst, debounceBurst } from "./lib/group-routing/burst.js";
33
+ export type { TelegramTexts } from "./lib/telegram/texts.js";
@@ -0,0 +1,3 @@
1
+ /** Channel delivery reports the reference; the public hook owns cancellation of the running turn. */
2
+ declare const _default: import("eve/hooks").HookDefinition<"authorization.required">;
3
+ export default _default;
@@ -0,0 +1,16 @@
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 { localizedText } from "../lib/admission.mjs";
8
+ import "../lib/telegram/principal.mjs";
9
+ import segmentstream from "../extension.mjs";
10
+ import { confirmationLocale } from "../lib/telegram/confirmation-text.mjs";
11
+ import { defineHook } from "eve/hooks";
12
+ var authorization_default = defineHook({ events: { "authorization.required": (event, context) => {
13
+ const principal = context.session.auth.current;
14
+ if (principal?.authenticator === "segmentstream-telegram" && event.data.candidateId === void 0 && localizedText(segmentstream.config.texts?.authorization?.prompt, confirmationLocale(principal)) === void 0) context.cancel();
15
+ } } });
16
+ export { authorization_default as default };
@@ -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-Dl-ZJp5p.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
  }
@@ -6,7 +6,7 @@ __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { proxyEnvironment } from "./environment.mjs";
8
8
  import { runtimeToken } from "./runtime-token.mjs";
9
- import { r as workflowValue } from "../../_chunks/workflow-serializable-BxrXgxjc.mjs";
9
+ import { r as workflowValue } from "../../_chunks/workflow-serializable-DVQBWK8p.mjs";
10
10
  function collapseConversationEvents(events) {
11
11
  const latest = new Map();
12
12
  for (const event of events) {
@@ -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-Dl-ZJp5p.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";