@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
package/README.md CHANGED
@@ -189,13 +189,13 @@ continue using GET lookup. Starter templates and pins remain unchanged in this s
189
189
 
190
190
  ## What the extension contributes
191
191
 
192
- | Name | Slot | What it does |
193
- | ------------------------------------------------------------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
194
- | `segmentstream__ontology` | `connections` | The app's `/mcp`, resolved per turn for the turn's member. No member, no connection. |
195
- | `segmentstream__telegram` | `channels` | `POST /eve/v1/telegram`: Telegram through the connection, with pairing, routing and voice. |
196
- | `segmentstream__attachments_list`, `_read`, `_promote`, `_discard` | `tools` | The member's temporary attachments in this chat: list, read, keep through an action, drop. |
197
- | `segmentstream__attachments_send` | `tools` | Sends readable stored ontology files into the current Telegram chat and topic. |
198
- | platform rules | `instructions` | Changes go through actions; `describe` indexes or inspects refs; `discover` searches metadata; tools are already narrowed; when to answer `[SILENT]`; how to handle attachments |
192
+ | Name | Slot | What it does |
193
+ | -------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
194
+ | `segmentstream__ontology_describe`, `_discover`, `_inspect`, `_call` | `tools` | Permission-filtered metadata, schema inspection and action calls as the current member. |
195
+ | `segmentstream__telegram` | `channels` | `POST /eve/v1/telegram`: Telegram through the connection, with pairing, routing and voice. |
196
+ | `segmentstream__attachments_list`, `_read`, `_promote`, `_discard` | `tools` | The member's temporary attachments in this chat: list, read, keep through an action, drop. |
197
+ | `segmentstream__attachments_send` | `tools` | Sends readable stored ontology files into the current Telegram chat and topic. |
198
+ | platform rules | `instructions` | Changes go through actions; `describe` indexes or inspects refs; `discover` searches metadata; tools are already narrowed; when to answer `[SILENT]`; how to handle attachments |
199
199
 
200
200
  ### The ontology
201
201
 
@@ -222,15 +222,17 @@ share a group chat's session never share a client.
222
222
  proxy. The token stays in SegmentStream.
223
223
  - **Webhook.** A webhook is accepted only with `X-Telegram-Bot-Api-Secret-Token` equal to
224
224
  `SEGMENTSTREAM_AGENT_WEBHOOK_SECRET`, compared in constant time. The platform generates the
225
- secret and registers the webhook after each production deployment, and sets the bot's menu
226
- button, shown in private chats, to open the workspace app.
225
+ secret and registers the webhook after each production deployment. A workspace-supplied
226
+ `menuButton.text` enables the app menu button; omitting it resets the provider menu to its default
227
+ for Agent SDK 0.13 or later. Pre-0.13 archives without menu copy retain their existing provider menu.
227
228
  - **Production only.** Outside `VERCEL_ENV=production` the route answers 200 and does nothing, so a
228
229
  preview never answers a real chat. Without `channels.telegram` it answers 404.
229
230
  - **Pairing.** Bots and channel posts are archived without starting turns. The sender's Telegram
230
231
  account is checked on every message, so revocation takes effect immediately. Unlinked group
231
232
  speech and attachment metadata remain raw context, including edits; no unlinked voice is decoded or gets
232
- a reply. An unknown private sender gets a neutral connection button to `/agent/connect?code=…`,
233
- valid for ten minutes; revoked or inactive private senders get a neutral refusal marker.
233
+ a reply. An unknown private sender gets the workspace pairing prompt and button to `/agent/connect?code=…`,
234
+ valid for ten minutes; revoked or inactive private senders receive configured refusal copy,
235
+ or no message when it is absent.
234
236
  - **Who the agent is.** A paired sender's turn runs with the principal id `telegram:<user>`
235
237
  (`telegram:<chat>:<user>` in groups) and the attributes `memberId`, `memberName`, `roles`,
236
238
  `chatId`, `chatType` and `username`.
@@ -516,7 +518,7 @@ ontology SDK along with this extension. See the repository's
516
518
  [conversation storage guide](../../docs/agent-conversation-storage.md) for APIs, retention,
517
519
  eve's existing storage, and Telegram's delivery limits.
518
520
 
519
- ### Exact chat posting protocol (prepared in agent SDK 0.6.0)
521
+ ### Exact chat posting protocol
520
522
 
521
523
  An authenticated `tools/list` advertises `_meta: { "segmentstream/chat-post": 1 }` on
522
524
  `chat_post_message`. Without this marker,
@@ -525,12 +527,12 @@ missing/incomplete preview metadata is denied. The versioned connection has its
525
527
 
526
528
  The runtime reads `me` for the server-derived `attribution`, and `chat_list({ chat })` for exactly
527
529
  one eligible destination: `{ items: [{ id, title, provider, external_id }], next_cursor: null }`.
528
- Provider is currently `telegram`. The final plain text is exactly `attribution + ": " + text`;
530
+ Provider is currently `telegram`. The final plain text is `text + separator + attribution`, with a blank line as the neutral separator;
529
531
  `reply_to` is optional. A preview digest is SHA-256 of the JSON array of chat ID, provider,
530
532
  external ID, title, attribution, text, final text and nullable reply target, in that order.
531
533
  The target/attribution are resolved again immediately before executing the frozen call.
532
534
 
533
- Eve supplies the model-hidden `_segmentstream` argument on **all** calls of a negotiated connection.
535
+ The SDK constructs model-hidden `_segmentstream` metadata for approved runtime posts.
534
536
  It contains `version: 1`, `session_id` and `call_id`. A consumed, unexpired chat approval additionally
535
537
  supplies `operation_id`, source provider/chat/topic coordinates and `expected: { chat_id, digest }`.
536
538
  The operation ID is `hq:` plus SHA-256 of the JSON array `[session_id, call_id]`; it stays stable
@@ -538,7 +540,8 @@ on replay and changes for a revision. The model cannot provide an idempotency ke
538
540
  The ontology must authenticate runtime provenance, consume/remove the envelope before strict input
539
541
  validation for ordinary tools, derive the posting key from the operation, and recompute attribution.
540
542
  External OAuth/service callers still supply their own `idempotency_key` and cannot use this envelope.
541
- These server changes belong to the later ontology stage; do not advertise the marker prematurely.
543
+ The coordinated ontology SDK implements this protocol; deploy the compatible ontology before
544
+ using agent exact posting.
542
545
 
543
546
  Source previews, accepted decisions and receipts use existing archive events with operation-linked
544
547
  `toolCallId`/event IDs. A cancelled Edit carries its pending revision question in the authoritative
@@ -546,10 +549,12 @@ Eve resolution, so member/topic routing survives a cold start. No second provide
546
549
 
547
550
  ## Release and migration
548
551
 
549
- The tested release uses agent SDK 0.6.4, Eve 0.76.3 and ontology SDK 0.28.3.
550
- Deploy the agent first, then backend archive migration 0033/API, then the ontology
551
- with its declared Telegram provider mapping and explicit `agent_chat:post_message`
552
- grant. Older ontologies keep ordinary tools working without the extra envelope.
552
+ The coordinated release uses Agent SDK 0.13.0, Eve 0.76.3, Ontology SDK 0.49.0 and App SDK 0.24.1.
553
+ The compatible Cloud archive/API must be deployed first; migrations run only through its CI/CD gate.
554
+ Publish the ontology SDK before the agent SDK. Upgrade the workspace ontology before its agent,
555
+ with the declared Telegram provider mapping and explicit `agent_chat:post_message` grant.
556
+ Supply the required workspace presentation before upgrading the agent to 0.13.x.
557
+ Older ontologies keep ordinary tools working without the extra envelope.
553
558
  An older archive returning only `{stored:true}` cannot authorize a send.
554
559
 
555
560
  Rollback
@@ -615,7 +620,7 @@ can redeliver the same source; the hook and attachment reservation must preserve
615
620
  idempotent source membership, storage results and the single acknowledgement.
616
621
  Such a failure must not become an HTTP success with no queued work.
617
622
 
618
- ### Legacy held approvals
623
+ ### Deferred approvals and legacy recovery
619
624
 
620
625
  Deferred mode supplies four eager tools: `segmentstream__ontology_discover`,
621
626
  `segmentstream__ontology_describe`, `segmentstream__ontology_inspect` and `segmentstream__ontology_call`. Registration
@@ -636,11 +641,13 @@ actionable explanation. Reply delivery carries the owning turn's reply anchor
636
641
  explicitly after restore. Omitting the version preserves existing closed lifecycle
637
642
  data schemas; workspaces opt in only after their hook accepts the additional fields.
638
643
 
639
- Explicit `confirmations.mode: "held"` overrides the deferred default. Switching a package version
640
- does not migrate already-running held turns. In deferred mode the Telegram adapter reads the owning session's
644
+ `confirmations.mode: "held"` is rejected. Already-running held turns still need recovery.
645
+ The Telegram adapter reads the owning session's
641
646
  recorded approval inputs and requests cancellation of that exact turn before admitting a new
642
647
  addressed message. It never approves the old call or reuses the old member's authorization.
643
648
  Cancellation failure requests webhook replay; terminal/resolved approvals are left alone.
644
649
  Deferred mode retains its ten-minute database deadline and minute expiry sweep. Runtime health
645
650
  reports the effective confirmation mode and attachment-lifecycle presence behind its existing
646
651
  runtime authentication; source pins and public health alone are not settings-adoption proof.
652
+
653
+ Telegram 0.13.0 requires workspace-authored pairing and confirmation text. See [workspace Telegram text](../../docs/agent.md#workspace-telegram-text) for keys, limits, neutral fallbacks and unsupported Eve buttons.
@@ -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
- var version = "0.12.2";
7
+ var version = "0.13.0";
8
8
  function agentBuild(environment = process.env) {
9
9
  return {
10
10
  agent: version,
@@ -4,15 +4,16 @@ 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 "../extension/lib/telegram/texts.mjs";
7
8
  import { isTechnicalText } from "../extension/lib/customer-text-safety.mjs";
8
9
  import { redactDiagnostic } from "../extension/lib/diagnostics.mjs";
9
10
  import { isConfirmationText } from "../extension/lib/telegram/confirmation-text.mjs";
10
11
  import { chatPostCardLinks, isExactChatPostCard } from "../extension/lib/telegram/chat-post-card.mjs";
11
12
  import { plainTelegramText, splitTelegramText, telegramMarkdown } from "../extension/lib/telegram/format.mjs";
12
13
  import { inTelegramSendQueue } from "../extension/lib/telegram/send-queue.mjs";
13
- import { telegramPreviewOptions, telegramTextLinks } from "@segmentstream-cloud/ontology/telegram";
14
14
  import { randomBytes, randomUUID } from "node:crypto";
15
15
  import { AsyncLocalStorage } from "node:async_hooks";
16
+ import { telegramPreviewOptions, telegramTextLinks } from "@segmentstream-cloud/ontology/telegram";
16
17
  const deliveryLocale = new AsyncLocalStorage();
17
18
  function withDeliveryLocale(locale, run, threadId, replyTo, chatId) {
18
19
  return deliveryLocale.run({
@@ -28,15 +29,16 @@ function currentLocale() {
28
29
  function currentThreadId() {
29
30
  return deliveryLocale.getStore()?.threadId ?? currentInboundRequest()?.threadId;
30
31
  }
31
- function customerCopy() {
32
+ function customerCopy(locale = currentLocale()) {
32
33
  return {
33
- failed: "⚠️",
34
- recovery: "⚠️",
35
- attachment: "⚠️",
36
- authorization: "⚠️",
37
- response: "⚠️",
38
- connected: "✅",
39
- disconnected: "❌"
34
+ failed: telegramText("notice", "failed", locale),
35
+ recovery: telegramText("notice", "recovery", locale),
36
+ attachment: telegramText("notice", "attachment", locale),
37
+ authorization: telegramText("notice", "authorization", locale),
38
+ response: telegramText("notice", "response", locale),
39
+ connected: telegramText("notice", "connected", locale),
40
+ disconnected: telegramText("notice", "disconnected", locale),
41
+ cancelled: telegramText("notice", "cancelled", locale)
40
42
  };
41
43
  }
42
44
  function redactCredentials(value) {
@@ -46,7 +48,7 @@ function redactCredentials(value) {
46
48
  return value;
47
49
  }
48
50
  function reportTelegramError(operation, error, metadata = {}, secrets = new Set()) {
49
- const errorId = randomBytes(4).toString("hex").toUpperCase();
51
+ const errorId = randomBytes(4).toString("hex");
50
52
  const credentials = new Set([...secrets, ...[process.env.AI_GATEWAY_API_KEY, process.env.VERCEL_OIDC_TOKEN].filter((value) => value !== void 0)]);
51
53
  console.error("[segmentstream] Telegram delivery diagnostic", {
52
54
  ...metadata,
@@ -57,8 +59,8 @@ function reportTelegramError(operation, error, metadata = {}, secrets = new Set(
57
59
  return errorId;
58
60
  }
59
61
  function failureText(errorId, kind = "failed", _locale = currentLocale()) {
60
- if (kind === "cancelled") return `⏹ ${errorId}`;
61
- return `${customerCopy()[kind]} ${errorId}`;
62
+ const text = customerCopy(_locale)[kind];
63
+ return text === void 0 ? errorId : `${text} ${errorId}`;
62
64
  }
63
65
  function customerText(text, locale = currentLocale()) {
64
66
  if (!isTechnicalText(text)) return text;
@@ -118,6 +120,7 @@ function safeTelegramFetch(fetchImpl, scope = "telegram", previews = {}) {
118
120
  "sendMessage",
119
121
  "editMessageText",
120
122
  "editMessageCaption",
123
+ "editMessageReplyMarkup",
121
124
  "answerCallbackQuery",
122
125
  "sendPhoto",
123
126
  "sendDocument",
@@ -160,14 +163,13 @@ function safeTelegramFetch(fetchImpl, scope = "telegram", previews = {}) {
160
163
  const markup = body.reply_markup;
161
164
  if (Array.isArray(markup?.inline_keyboard)) {
162
165
  if ((textBlocked || typeof body.text === "string" && body.text.length > 4096) && markup.inline_keyboard.some((row) => row.some((button) => typeof button.callback_data === "string" && button.callback_data.startsWith("ss-confirm:")))) throw new Error("Approval buttons require the complete, safe confirmation text.");
163
- markup.inline_keyboard = markup.inline_keyboard.map((row) => row.map((button) => {
164
- if (typeof button.text !== "string" || !isTechnicalText(button.text)) return button;
166
+ const rows = markup.inline_keyboard.map((row) => row.filter((button) => {
167
+ if (typeof button.text !== "string" || !isTechnicalText(button.text)) return true;
165
168
  reportTelegramError("technical_button_label_blocked", { text: button.text });
166
- return {
167
- ...button,
168
- text: "➡️"
169
- };
170
- }));
169
+ return false;
170
+ })).filter((row) => row.length > 0);
171
+ if (rows.length === 0 && markup.inline_keyboard.length > 0) delete body.reply_markup;
172
+ else markup.inline_keyboard = rows;
171
173
  }
172
174
  const send = async () => {
173
175
  const parts = url.endsWith("/sendMessage") && typeof body.text === "string" && !isExactChatPostCard(body.text) ? splitTelegramText({
@@ -8,7 +8,6 @@
8
8
  "dynamicTool": 79,
9
9
  "channel": 58,
10
10
  "schedule": 31,
11
- "connection": 46,
12
11
  "hook": 44,
13
12
  "instructions": 2,
14
13
  "dynamicInstructions": 37,
@@ -1,5 +1,6 @@
1
1
  import { type GroupPolicyInput } from "@segmentstream-cloud/ontology/groups";
2
2
  import { type GroupPolicy } from "./lib/group-routing/policy.js";
3
+ import { type TelegramTexts } from "./lib/telegram/texts.js";
3
4
  import type { AttachmentLifecycle } from "./lib/telegram/lifecycle.js";
4
5
  import { type TelegramLinkPreviews } from "@segmentstream-cloud/ontology/telegram";
5
6
  export declare const TELEGRAM_POLICIES: readonly ["mention", "smart"];
@@ -40,12 +41,13 @@ export interface FirstContactSettings {
40
41
  readonly version: string;
41
42
  readonly notice: LocalizedText;
42
43
  }
43
- export declare const ADMISSION_REPLY_KEYS: readonly ["limited", "perSenderLimited", "workspaceLimited", "registrationsLimited", "groupPairingRequired", "refused"];
44
+ export declare const ADMISSION_REPLY_KEYS: readonly ["pairingRequired", "pairingButton", "groupPairingButton", "limited", "perSenderLimited", "workspaceLimited", "registrationsLimited", "groupPairingRequired", "refused"];
44
45
  export type AdmissionReplies = Partial<Record<(typeof ADMISSION_REPLY_KEYS)[number], LocalizedText>>;
45
46
  export interface AdmissionSettings {
46
47
  readonly firstContact: FirstContactSettings | null;
47
48
  readonly limits: AdmissionLimits;
48
49
  readonly replies: AdmissionReplies;
50
+ readonly texts?: TelegramTexts;
49
51
  }
50
52
  /**
51
53
  * `agent/segmentstream.json` as authored. A JSON import widens string literals to `string` and
@@ -54,6 +56,7 @@ export interface AdmissionSettings {
54
56
  */
55
57
  export interface SegmentStreamAgentConfigInput {
56
58
  readonly groups?: GroupPolicyInput;
59
+ readonly texts?: TelegramTexts;
57
60
  readonly firstContact?: FirstContactSettings;
58
61
  readonly limits?: AdmissionLimitsInput;
59
62
  readonly replies?: AdmissionReplies;
@@ -72,6 +75,9 @@ export interface SegmentStreamAgentConfigInput {
72
75
  readonly linkPreviews?: string;
73
76
  /** Additional exact HTTP(S) origins belonging to this workspace. */
74
77
  readonly additionalOwnOrigins?: readonly string[];
78
+ readonly menuButton?: {
79
+ readonly text: LocalizedText;
80
+ };
75
81
  readonly attachmentLifecycle?: Omit<AttachmentLifecycle, "contextVersion"> & {
76
82
  readonly contextVersion?: number;
77
83
  };
@@ -102,7 +108,7 @@ export interface SegmentStreamAgentConfigInput {
102
108
  readonly connection?: boolean;
103
109
  };
104
110
  readonly confirmations?: {
105
- /** `deferred` (default) returns immediately; `held` uses eve approval. */
111
+ /** `deferred` (default) returns immediately. Eve button approvals are unsupported. */
106
112
  readonly mode?: string;
107
113
  };
108
114
  readonly sessions?: {
@@ -133,6 +139,9 @@ export interface TelegramSettings {
133
139
  readonly linkPreviews?: TelegramLinkPreviews;
134
140
  readonly additionalOwnOrigins?: readonly string[];
135
141
  readonly attachmentLifecycle?: AttachmentLifecycle;
142
+ readonly menuButton?: {
143
+ readonly text: LocalizedText;
144
+ };
136
145
  }
137
146
  /** The chat models `segmentstream.model()` chooses between, as AI Gateway model ids. */
138
147
  export interface ChatModelChoice {
@@ -175,7 +184,7 @@ export interface SegmentStreamAgentConfig extends AdmissionSettings {
175
184
  readonly connection: boolean;
176
185
  };
177
186
  readonly confirmations: {
178
- readonly mode: "deferred" | "held";
187
+ readonly mode: "deferred";
179
188
  };
180
189
  readonly sessions: SessionSettings;
181
190
  }
@@ -196,8 +205,12 @@ export declare class SegmentStreamAgentConfigError extends Error {
196
205
  readonly name = "SegmentStreamAgentConfigError";
197
206
  constructor(issues: ReadonlyArray<ConfigIssue>);
198
207
  }
208
+ /** Exact historical pins retain the pre-0.13 presentation contract at the archive boundary. */
209
+ export declare function isLegacyAgentArchive(version: string | undefined): boolean;
199
210
  /** Validates the authored configuration and applies the defaults. */
200
- export declare function validateConfig(input: unknown): Validation;
211
+ export declare function validateConfig(input: unknown, options?: {
212
+ readonly archiveSdkVersion?: string;
213
+ }): Validation;
201
214
  /**
202
215
  * The Standard Schema eve validates the mount with. `types` exists only for type inference, as the
203
216
  * Standard Schema specification allows.
@@ -206,7 +219,7 @@ export declare const configSchema: {
206
219
  readonly "~standard": {
207
220
  readonly version: 1;
208
221
  readonly vendor: "segmentstream";
209
- readonly validate: typeof validateConfig;
222
+ readonly validate: (input: unknown) => Validation;
210
223
  readonly types: {
211
224
  readonly input: SegmentStreamAgentConfigInput;
212
225
  readonly output: SegmentStreamAgentConfig;
@@ -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 };