@segmentstream/agent 0.6.12 → 0.6.13

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 (31) hide show
  1. package/dist/_chunks/{inbound-request-DgyI7oR9.mjs → inbound-request-CysnjMdw.mjs} +51 -59
  2. package/dist/extension/instructions.md +8 -1
  3. package/dist/extension/lib/chat-post-confirmation.mjs +1 -1
  4. package/dist/extension/lib/confirmation-description.d.ts +1 -0
  5. package/dist/extension/lib/confirmation-description.mjs +29 -13
  6. package/dist/extension/lib/confirmations.d.ts +3 -0
  7. package/dist/extension/lib/confirmations.mjs +22 -5
  8. package/dist/extension/lib/customer-text-safety.d.ts +1 -0
  9. package/dist/extension/lib/customer-text-safety.mjs +23 -0
  10. package/dist/extension/lib/ontology-tools.mjs +13 -6
  11. package/dist/extension/lib/telegram/adapter.mjs +5 -5
  12. package/dist/extension/lib/telegram/attachments-send.mjs +6 -6
  13. package/dist/extension/lib/telegram/chat-post-card.d.ts +4 -12
  14. package/dist/extension/lib/telegram/chat-post-card.mjs +15 -36
  15. package/dist/extension/lib/telegram/confirmation-text.d.ts +19 -8
  16. package/dist/extension/lib/telegram/confirmation-text.mjs +55 -65
  17. package/dist/extension/lib/telegram/confirmations.d.ts +1 -2
  18. package/dist/extension/lib/telegram/confirmations.mjs +36 -94
  19. package/dist/extension/lib/telegram/conversations.mjs +1 -1
  20. package/dist/extension/lib/telegram/customer-text.d.ts +3 -4
  21. package/dist/extension/lib/telegram/customer-text.mjs +2 -1
  22. package/dist/extension/lib/telegram/files.mjs +1 -1
  23. package/dist/extension/lib/telegram/images.mjs +1 -1
  24. package/dist/extension/lib/telegram/inbound-request.mjs +1 -1
  25. package/dist/extension/lib/telegram/inbound.mjs +5 -5
  26. package/dist/extension/lib/telegram/media-policy.mjs +1 -1
  27. package/dist/extension/lib/telegram/principal.mjs +1 -1
  28. package/dist/extension/lib/telegram/silent.mjs +2 -2
  29. package/dist/extension/lib/telegram/turn-errors.mjs +2 -2
  30. package/dist/extension/lib/telegram/voice.mjs +1 -1
  31. package/package.json +2 -2
@@ -4,6 +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 { isTechnicalText } from "../extension/lib/customer-text-safety.mjs";
8
+ import { isConfirmationText } from "../extension/lib/telegram/confirmation-text.mjs";
7
9
  import { chatPostCardLinks, isExactChatPostCard } from "../extension/lib/telegram/chat-post-card.mjs";
8
10
  import { redactDiagnostic } from "../extension/lib/diagnostics.mjs";
9
11
  import { plainTelegramText, splitTelegramText, telegramMarkdown } from "../extension/lib/telegram/format.mjs";
@@ -24,43 +26,17 @@ function currentLocale() {
24
26
  function currentThreadId() {
25
27
  return deliveryLocale.getStore()?.threadId ?? currentInboundRequest()?.threadId;
26
28
  }
27
- function customerCopy(locale = currentLocale()) {
28
- return locale.toLowerCase().split(/[-_]/)[0] === "ru" ? {
29
- failed: "Не удалось выполнить запрос. Попробуйте ещё раз.",
30
- recovery: "Не удалось продолжить разговор. Отправьте новое сообщение.",
31
- attachment: "Не удалось обработать файл. Попробуйте ещё раз.",
32
- authorization: "Не удалось подключиться. Попробуйте ещё раз.",
33
- response: "Не удалось показать ответ. Попробуйте ещё раз.",
34
- reference: "Код ошибки",
35
- connected: "Доступ подключён.",
36
- disconnected: "Подключение отменено."
37
- } : {
38
- failed: "I couldn't complete the request. Please try again.",
39
- recovery: "I couldn't continue the conversation. Please send a new message.",
40
- attachment: "I couldn't process the file. Please try again.",
41
- authorization: "I couldn't connect. Please try again.",
42
- response: "I couldn't display the response. Please try again.",
43
- reference: "Error reference",
44
- connected: "Access connected.",
45
- disconnected: "Connection cancelled."
29
+ function customerCopy() {
30
+ return {
31
+ failed: "⚠️",
32
+ recovery: "⚠️",
33
+ attachment: "⚠️",
34
+ authorization: "⚠️",
35
+ response: "⚠️",
36
+ connected: "✅",
37
+ disconnected: "❌"
46
38
  };
47
39
  }
48
- const TECHNICAL_PATTERNS = [
49
- /[{}]|```/u,
50
- /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/iu,
51
- /\b[0-9a-hjkmnp-tv-z]{26}\b/iu,
52
- /\b\w+__\w+/u,
53
- /\b(?:Serialization|Type|Reference|Syntax|Tool|Provider|API|Workflow|InternalServer)Error\b/u,
54
- /\bError:\s|(?:^|\n)\s*at\s+\S+|\b(?:stack trace|traceback|SQLSTATE|node_modules)\b/imu,
55
- /\b(?:duplicate key|ECONN\w+|HTTP [45]\d\d)\b/iu,
56
- /\[\s*(?:"[^"]*"|\d+|true|false|null)\s*[,\]]/u
57
- ];
58
- function isTechnicalText(text) {
59
- if (/https?:\/\/[^\s/@]+:[^\s/@]+@|[?&](?:token|access_token|api_key|secret|password)=/iu.test(text)) return true;
60
- const visible = text.replace(/https?:\/\/[^\s)<>]+/giu, "link");
61
- const prose = visible.replace(/(?<![\w@])@[a-z][a-z0-9_]{4,31}(?!\w)/giu, "recipient");
62
- return TECHNICAL_PATTERNS.some((pattern) => pattern.test(visible)) || /\b[a-z][a-z0-9]*_[a-z][a-z0-9_]*\b(?!\.[a-z0-9])/iu.test(prose);
63
- }
64
40
  function redactCredentials(value) {
65
41
  if (typeof value === "string") return value.replace(/\bBearer [A-Za-z0-9_.~+/-]+=*/giu, "Bearer [redacted]").replace(/(https?:\/\/)[^/@\s]+:[^/@\s]+@/gu, "$1[redacted]@").replace(/([?&](?:token|access_token|api_key|secret|password)=)[^&#\s]*/giu, "$1[redacted]").replace(/("(?:token|api_key|password|secret)"\s*:\s*)"(?:\\.|[^"\\])*"/giu, "$1\"[redacted]\"");
66
42
  if (Array.isArray(value)) return value.map(redactCredentials);
@@ -78,16 +54,18 @@ function reportTelegramError(operation, error, metadata = {}, secrets = new Set(
78
54
  });
79
55
  return errorId;
80
56
  }
81
- function failureText(errorId, kind = "failed", locale = currentLocale()) {
57
+ function failureText(errorId, kind = "failed", _locale = currentLocale()) {
82
58
  if (kind === "cancelled") return `⏹ ${errorId}`;
83
- const copy = customerCopy(locale);
84
- return `${copy[kind]} ${copy.reference}: ${errorId}.`;
59
+ return `${customerCopy()[kind]} ${errorId}`;
85
60
  }
86
61
  function customerText(text, locale = currentLocale()) {
87
62
  if (!isTechnicalText(text)) return text;
88
63
  return failureText(reportTelegramError("technical_text_blocked", { text }), "response", locale);
89
64
  }
90
65
  function customerMessage(text, locale = currentLocale()) {
66
+ return guardedCustomerMessage(text, locale);
67
+ }
68
+ function guardedCustomerMessage(text, locale, blocked) {
91
69
  let message;
92
70
  try {
93
71
  message = telegramMarkdown(text);
@@ -100,14 +78,20 @@ function customerMessage(text, locale = currentLocale()) {
100
78
  }
101
79
  const links = message.entities.flatMap((entity) => entity.url === void 0 ? [] : [entity.url]);
102
80
  const visible = customerText(message.text, locale);
103
- if (visible !== message.text) return {
104
- text: visible,
105
- entities: []
106
- };
107
- if (links.some(isTechnicalText)) return {
108
- text: failureText(reportTelegramError("unsafe_markdown_link", new Error("Credential-bearing link refused.")), "response", locale),
109
- entities: []
110
- };
81
+ if (visible !== message.text) {
82
+ blocked?.();
83
+ return {
84
+ text: visible,
85
+ entities: []
86
+ };
87
+ }
88
+ if (links.some(isTechnicalText)) {
89
+ blocked?.();
90
+ return {
91
+ text: failureText(reportTelegramError("unsafe_markdown_link", new Error("Credential-bearing link refused.")), "response", locale),
92
+ entities: []
93
+ };
94
+ }
111
95
  return message;
112
96
  }
113
97
  async function formattingRefused(response) {
@@ -141,17 +125,22 @@ function safeTelegramFetch(fetchImpl, scope = "telegram", previews = {}) {
141
125
  const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
142
126
  if (!methods.has(url.split("/").at(-1) ?? "") || typeof init?.body !== "string") return fetchImpl(input, init);
143
127
  const body = JSON.parse(init.body);
128
+ let textBlocked = false;
144
129
  for (const field of ["text", "caption"]) {
145
130
  if (typeof body[field] === "string" && isExactChatPostCard(body[field])) body.parse_mode = "MarkdownV2";
146
131
  if (typeof body[field] === "string" && !isExactChatPostCard(body[field])) {
147
132
  const entityField = field === "text" ? "entities" : "caption_entities";
148
133
  const existing = body[entityField];
149
- const message = Array.isArray(existing) ? {
134
+ const message = Array.isArray(existing) || isConfirmationText(body[field]) ? {
150
135
  text: body[field],
151
- entities: existing
152
- } : customerMessage(body[field]);
136
+ entities: existing ?? []
137
+ } : guardedCustomerMessage(body[field], currentLocale(), () => {
138
+ textBlocked = true;
139
+ });
153
140
  const guarded = customerText(message.text);
154
- body[field] = message.entities.some((entity) => typeof entity.url === "string" && isTechnicalText(entity.url)) ? failureText(reportTelegramError("unsafe_entity_link", new Error("Credential-bearing link refused.")), "response") : guarded;
141
+ const unsafeLink = message.entities.some((entity) => typeof entity.url === "string" && isTechnicalText(entity.url));
142
+ textBlocked ||= guarded !== message.text || unsafeLink;
143
+ body[field] = unsafeLink ? failureText(reportTelegramError("unsafe_entity_link", new Error("Credential-bearing link refused.")), "response") : guarded;
155
144
  if (!url.endsWith("/answerCallbackQuery")) body[entityField] = body[field] === message.text ? message.entities : [];
156
145
  delete body.parse_mode;
157
146
  }
@@ -161,14 +150,17 @@ function safeTelegramFetch(fetchImpl, scope = "telegram", previews = {}) {
161
150
  ...typeof item.caption === "string" ? { caption: customerText(item.caption) } : {}
162
151
  }));
163
152
  const markup = body.reply_markup;
164
- if (Array.isArray(markup?.inline_keyboard)) markup.inline_keyboard = markup.inline_keyboard.map((row) => row.map((button) => {
165
- if (typeof button.text !== "string" || !isTechnicalText(button.text)) return button;
166
- reportTelegramError("technical_button_label_blocked", { text: button.text });
167
- return {
168
- ...button,
169
- text: currentLocale().startsWith("ru") ? "Продолжить" : "Continue"
170
- };
171
- }));
153
+ if (Array.isArray(markup?.inline_keyboard)) {
154
+ 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.");
155
+ markup.inline_keyboard = markup.inline_keyboard.map((row) => row.map((button) => {
156
+ if (typeof button.text !== "string" || !isTechnicalText(button.text)) return button;
157
+ reportTelegramError("technical_button_label_blocked", { text: button.text });
158
+ return {
159
+ ...button,
160
+ text: "➡️"
161
+ };
162
+ }));
163
+ }
172
164
  const send = async () => {
173
165
  const parts = url.endsWith("/sendMessage") && typeof body.text === "string" && !isExactChatPostCard(body.text) ? splitTelegramText({
174
166
  text: body.text,
@@ -330,4 +322,4 @@ function withReplacedText(source, request) {
330
322
  }
331
323
  };
332
324
  }
333
- export { TelegramDeliveryError as a, customerCopy as c, failureText as d, formattingRefused as f, withDeliveryLocale as g, safeTelegramFetch as h, withReplacedText as i, customerMessage as l, reportTelegramError as m, currentInboundRequest as n, currentLocale as o, isTechnicalText as p, runInboundRequest as r, currentThreadId as s, InboundRequest as t, customerText as u };
325
+ export { TelegramDeliveryError as a, customerCopy as c, failureText as d, formattingRefused as f, withDeliveryLocale as h, withReplacedText as i, customerMessage as l, safeTelegramFetch as m, currentInboundRequest as n, currentLocale as o, reportTelegramError as p, runInboundRequest as r, currentThreadId as s, InboundRequest as t, customerText as u };
@@ -26,6 +26,13 @@ talking to you, and you act with exactly their permissions.
26
26
  - Destructive or irreversible actions, and legacy custom actions without safety labels, require
27
27
  the tool boundary's Confirm / Cancel buttons. Call the action to start that flow; never ask for
28
28
  approval in prose or treat a text acknowledgement as approval. Wait for the button outcome.
29
+ When the tool schema offers `_confirmation`, supply {question, confirm, cancel, done,
30
+ cancelled, failed, expired?, edit?} in the chat's language. Describe the action and key
31
+ parameters in a plain single-line question (at most 300 characters); labels are at most
32
+ 24 characters. No IDs, tool names, snake_case, URLs, credentials, or markup. The platform
33
+ adds trusted facts and symbols. Omit this argument on older tools without schema support.
34
+ Leave decision symbols to the platform and use distinct confirm/cancel labels. If trusted
35
+ target titles are unavailable or the facts are too long, resolve the targets or narrow the call.
29
36
  Cancel ends that call; retry it only after a new instruction from the member.
30
37
  - Every change goes through an action. Report results after success; never claim a record changed
31
38
  unless an action reported it.
@@ -36,7 +43,7 @@ talking to you, and you act with exactly their permissions.
36
43
  Read `chat_history` or `chat_search` only when permitted, then compose the exact draft and call
37
44
  `chat_post_message`. The runtime supplies the operation key. Its Confirm / Edit / Cancel card
38
45
  shows the full attributed text in the requesting chat; only the requester can act on it and it
39
- expires after ten minutes. Text such as "yes" or "да" cannot approve a post. After Edit, ask what
46
+ expires after ten minutes. A text acknowledgement cannot approve a post. After Edit, ask what
40
47
  to change and make a new call/card for the revision; the cancelled call cannot be reused.
41
48
  An uncertain delivery must never be automatically resent, including through a fresh call/key.
42
49
  For a reply in the current chat, answer normally. If chat posting is unavailable, say so.
@@ -48,7 +48,7 @@ function describeChatPost(input) {
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
50
  return {
51
- action: "Post message",
51
+ action: "❓",
52
52
  fields: [],
53
53
  chatPost: {
54
54
  chatId: target.id,
@@ -9,6 +9,7 @@ export interface ConfirmationDescription {
9
9
  readonly value: string;
10
10
  }[];
11
11
  readonly chatPost?: ChatPostPreview;
12
+ readonly warning?: boolean;
12
13
  }
13
14
  export declare function object(value: unknown): Record<string, unknown>;
14
15
  export declare function humanize(value: string): string;
@@ -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 { isTechnicalText } from "./customer-text-safety.mjs";
7
8
  function object(value) {
8
9
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
9
10
  }
@@ -12,9 +13,20 @@ function humanize(value) {
12
13
  }
13
14
  function humanText(value, maximum = 180) {
14
15
  if (typeof value !== "string") return;
15
- const text = value.replace(/\s+/g, " ").trim();
16
- if (text === "" || /[{}\[\]]|__|\b[0-9a-f]{8}-[0-9a-f-]{27,}\b|\b[0-9A-HJKMNP-TV-Z]{26}\b|\b[a-z0-9]{24,}\b/iu.test(text)) return;
17
- return text.slice(0, maximum);
16
+ const text = value.trim();
17
+ if (text === "" || text.length > maximum || isTechnicalText(text) || [
18
+ /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}<>`*_~|\\\[\]{}]|^(?:#{1,6}|[-+])\s/u,
19
+ /[a-z][a-z0-9+.-]*:\/\/|www\.|\b[a-z][a-z0-9]*\.[a-z][a-z_0-9]*\b/iu,
20
+ /&(?:#\d+|#x[0-9a-f]+|[a-z]+);/iu,
21
+ /\bBearer\s+|\b(?:password|secret|token|credential|api[ _-]?key)\s*[:=]/iu,
22
+ /\b(?:sk|ssw|ghp)[_-][a-z0-9]+/iu,
23
+ /\b[0-9a-f]{8}-[0-9a-f-]{27,}\b|\b[0-9A-HJKMNP-TV-Z]{26}\b|\b[a-z0-9]{24,}\b/iu
24
+ ].some((pattern) => pattern.test(text))) return;
25
+ return text;
26
+ }
27
+ function factText(value, maximum = 180) {
28
+ if (typeof value === "string" && value.trim().length > maximum) throw new Error("The complete confirmation facts cannot fit in one Telegram message.");
29
+ return humanText(value, maximum);
18
30
  }
19
31
  function fieldSchema(value, root, depth = 0) {
20
32
  const schema = object(value);
@@ -33,46 +45,50 @@ async function describeConfirmation(input) {
33
45
  const properties = Array.isArray(entity.properties) ? entity.properties.map(object) : [];
34
46
  const fields = [];
35
47
  const read = new Map();
36
- for (const [key, value] of Object.entries(input.values).slice(0, 20)) {
48
+ for (const [key, value] of Object.entries(input.values)) {
37
49
  const declared = object(input.schema.properties)[key];
38
- if (declared === void 0 || /password|secret|token|credential|authorization/i.test(key)) continue;
50
+ if (declared === void 0 || /password|secret|token|credential|authorization|api.?key|private.?key|cookie/i.test(key)) continue;
39
51
  const schema = fieldSchema(declared, input.schema);
40
52
  const itemSchema = schema.type === "array" ? fieldSchema(schema.items, input.schema) : schema;
41
53
  const property = properties.find((candidate) => candidate.name === key);
42
54
  const reference = itemSchema["x-ontology-ref"];
43
55
  const target = input.entities.find((candidate) => candidate.name === reference);
44
- const label = humanText(schema.title) ?? humanText(property?.label) ?? humanText(humanize(key));
45
- if (label === void 0 || schema.writeOnly === true || schema.format === "password") continue;
56
+ if (schema.writeOnly === true || schema.format === "password" || itemSchema.writeOnly === true || itemSchema.format === "password") continue;
57
+ const label = factText(schema.title) ?? factText(property?.label) ?? factText(humanize(key));
58
+ if (label === void 0) throw new Error("A confirmation field has no safe label.");
46
59
  const options = Array.isArray(property?.options) ? property.options.map(object) : [];
47
60
  const choices = [itemSchema.oneOf, itemSchema.anyOf].flatMap((items) => Array.isArray(items) ? items.map(object) : []);
48
61
  const values = [];
49
- for (const item of (Array.isArray(value) ? value : [value]).slice(0, 5)) {
62
+ for (const item of Array.isArray(value) ? value : [value]) {
50
63
  if (typeof reference === "string") {
64
+ if (item === null || item === void 0) continue;
51
65
  let title;
52
66
  if (target !== void 0 && typeof item === "string" && typeof target.title === "string") {
53
67
  const cacheKey = `${reference}:${item}`;
54
68
  if (!read.has(cacheKey)) read.set(cacheKey, input.read(reference, item));
55
69
  const value = (await read.get(cacheKey))?.[target.title];
56
- title = value === item ? void 0 : humanText(value);
70
+ title = value === item ? void 0 : factText(value, 3584);
57
71
  }
58
- values.push(title ?? humanText(target?.label) ?? label);
72
+ if (title === void 0) throw new Error("Every confirmation target requires an authorized, safe record title.");
73
+ values.push(title);
59
74
  continue;
60
75
  }
61
76
  if (/^(id|.*_id)$/i.test(key) || itemSchema.format === "uuid" || typeof item === "object") continue;
62
77
  const option = options.find((candidate) => candidate.value === item);
63
78
  const choice = choices.find((candidate) => candidate.const === item);
64
79
  const enumeration = Array.isArray(itemSchema.enum) || choices.length > 0 || options.length > 0;
65
- const text = humanText(option?.label) ?? humanText(choice?.title) ?? (enumeration ? void 0 : typeof item === "string" ? humanText(humanize(item)) : typeof item === "number" || typeof item === "boolean" ? String(item) : void 0);
80
+ const text = factText(option?.label) ?? factText(choice?.title) ?? (enumeration ? void 0 : typeof item === "string" ? factText(item, 3584) : typeof item === "number" || typeof item === "boolean" ? String(item) : void 0);
66
81
  if (text !== void 0) values.push(text);
67
82
  }
68
83
  if (values.length > 0) fields.push({
69
84
  label,
70
85
  value: values.join(", ")
71
86
  });
72
- if (fields.length === 6) break;
73
87
  }
88
+ const action = factText(input.title);
89
+ if (action === void 0) throw new Error("A confirmation requires a safe declared action title.");
74
90
  return {
75
- action: humanText(input.title) ?? "the requested action",
91
+ action,
76
92
  ...humanText(entity.label) === void 0 ? {} : { entity: humanText(entity.label) },
77
93
  ...humanText(input.description?.split("\n\n")[0]) === void 0 ? {} : { description: humanText(input.description?.split("\n\n")[0]) },
78
94
  fields
@@ -1,9 +1,11 @@
1
1
  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
+ import { type ConfirmationCopy } from "./telegram/confirmation-text.js";
4
5
  export declare const CONFIRMATION_TTL_MS: number;
5
6
  export interface ActionAnnotations {
6
7
  readonly readOnlyHint?: boolean;
8
+ readonly confirmation?: boolean;
7
9
  readonly destructiveHint?: boolean;
8
10
  readonly irreversibleHint?: boolean;
9
11
  readonly idempotentHint?: boolean;
@@ -19,6 +21,7 @@ export interface Confirmation {
19
21
  readonly expiresAt: number;
20
22
  readonly status: "pending" | "approved" | "consumed" | "cancelled";
21
23
  readonly description?: ConfirmationDescription;
24
+ readonly copy?: ConfirmationCopy;
22
25
  readonly source?: {
23
26
  readonly chatId: string;
24
27
  readonly threadId: string;
@@ -6,6 +6,7 @@ __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { memberIdOf } from "./telegram/principal.mjs";
8
8
  import { chatPostDigest, isChatPost } from "./chat-post-confirmation.mjs";
9
+ import { confirmationCopy, renderConfirmation } from "./telegram/confirmation-text.mjs";
9
10
  import { chatPostCard } from "./telegram/chat-post-card.mjs";
10
11
  import { createHash } from "node:crypto";
11
12
  import { defineState } from "eve/context";
@@ -49,15 +50,19 @@ function actionApproval(options) {
49
50
  console.warn("The ontology action permission check failed.", error);
50
51
  return denied("The action is unavailable or the member no longer has permission.");
51
52
  }
53
+ const copy = labels.confirmation === false ? void 0 : confirmationCopy(ctx.toolInput?._confirmation);
54
+ const businessInput = Object.fromEntries(Object.entries(ctx.toolInput ?? {}).filter(([key]) => key !== "_confirmation"));
52
55
  const posting = isChatPost(ctx.toolName);
53
56
  let description;
54
57
  if (posting) try {
55
- description = await options.describe?.(memberId, ctx.toolName, ctx.toolInput ?? {});
58
+ description = await options.describe?.(memberId, ctx.toolName, businessInput);
56
59
  const preview = description?.chatPost;
57
60
  const source = ctx.session.auth.current?.attributes;
58
- if (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.");
59
- chatPostCard(preview, "en");
60
- chatPostCard(preview, "ru");
61
+ 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({
63
+ ...description,
64
+ warning: labels.destructiveHint === true || labels.irreversibleHint === true
65
+ }, copy);
61
66
  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.");
62
67
  } catch (error) {
63
68
  console.warn("The exact chat confirmation could not be prepared.", error);
@@ -76,9 +81,20 @@ function actionApproval(options) {
76
81
  if (saved !== void 0) return denied("The original call is waiting for its Confirm / Cancel buttons.");
77
82
  if (!posting && !needsConfirmation(labels)) return "approved";
78
83
  try {
79
- if (!posting) description = await options.describe?.(memberId, ctx.toolName, ctx.toolInput ?? {});
84
+ if (!posting) description = await options.describe?.(memberId, ctx.toolName, businessInput);
85
+ if (description === void 0) throw new Error("A trusted confirmation description is required.");
80
86
  } catch (error) {
81
87
  console.warn("The confirmation description could not be read.", error);
88
+ return denied("Trusted confirmation facts are incomplete, unsafe or too long. Resolve every target title and narrow the input before making a new call.");
89
+ }
90
+ description = description === void 0 ? void 0 : {
91
+ ...description,
92
+ warning: labels.destructiveHint === true || labels.irreversibleHint === true
93
+ };
94
+ try {
95
+ if (!posting) renderConfirmation(description, copy);
96
+ } catch {
97
+ return denied("The complete confirmation facts are unsafe or too long. Narrow the action input.");
82
98
  }
83
99
  state.update((all) => ({
84
100
  ...all,
@@ -88,6 +104,7 @@ function actionApproval(options) {
88
104
  inputHash: hash,
89
105
  expiresAt: now() + CONFIRMATION_TTL_MS,
90
106
  status: "pending",
107
+ ...copy === void 0 ? {} : { copy },
91
108
  ...description === void 0 ? {} : { description },
92
109
  ...posting ? { source: {
93
110
  chatId: String(ctx.session.auth.current?.attributes.chatId),
@@ -0,0 +1 @@
1
+ export declare function isTechnicalText(text: string): boolean;
@@ -0,0 +1,23 @@
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
+ const TECHNICAL_PATTERNS = [
8
+ /[{}]|```/u,
9
+ /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/iu,
10
+ /\b[0-9a-hjkmnp-tv-z]{26}\b/iu,
11
+ /\b\w+__\w+/u,
12
+ /\b(?:Serialization|Type|Reference|Syntax|Tool|Provider|API|Workflow|InternalServer)Error\b/u,
13
+ /\bError:\s|(?:^|\n)\s*at\s+\S+|\b(?:stack trace|traceback|SQLSTATE|node_modules)\b/imu,
14
+ /\b(?:duplicate key|ECONN\w+|HTTP [45]\d\d)\b/iu,
15
+ /\[\s*(?:"[^"]*"|\d+|true|false|null)\s*[,\]]/u
16
+ ];
17
+ function isTechnicalText(text) {
18
+ if (/https?:\/\/[^\s/@]+:[^\s/@]+@|[?&](?:token|access_token|api_key|secret|password)=/iu.test(text)) return true;
19
+ const visible = text.replace(/https?:\/\/[^\s)<>]+/giu, "link");
20
+ const prose = visible.replace(/(?<![\w@])@[a-z][a-z0-9_]{4,31}(?!\w)/giu, "recipient");
21
+ return TECHNICAL_PATTERNS.some((pattern) => pattern.test(visible)) || /\b[a-z][a-z0-9]*_[a-z][a-z0-9_]*\b(?!\.[a-z0-9])/iu.test(prose);
22
+ }
23
+ export { isTechnicalText };
@@ -59,7 +59,11 @@ function ontologyTools(options = {}) {
59
59
  return Array.isArray(result.tools) && result.tools.map(object).some(supportsChatPost);
60
60
  },
61
61
  async labels(memberId, action) {
62
- return object((await toolFor(memberId, action)).tool.annotations);
62
+ const { tool } = await toolFor(memberId, action);
63
+ return {
64
+ ...object(tool.annotations),
65
+ confirmation: object(object(tool.inputSchema).properties)._confirmation !== void 0
66
+ };
63
67
  },
64
68
  async describe(memberId, action, input) {
65
69
  const signal = AbortSignal.timeout(15e3);
@@ -76,11 +80,14 @@ function ontologyTools(options = {}) {
76
80
  return object(result.structuredContent);
77
81
  };
78
82
  const [caller, destinations] = await Promise.all([read("me", {}), read("chat_list", { chat: input.chat })]);
79
- return describeChatPost({
80
- values: input,
81
- caller,
82
- destinations
83
- });
83
+ return {
84
+ ...describeChatPost({
85
+ values: input,
86
+ caller,
87
+ destinations
88
+ }),
89
+ action: typeof tool.title === "string" ? tool.title : "❓"
90
+ };
84
91
  }
85
92
  let entities = [];
86
93
  try {
@@ -8,17 +8,17 @@ import { appOrigin, channelsEnabled, proxyEnvironment, runtimeIdentityEnvironmen
8
8
  import { conversationLanguage, telegramPrincipal } from "./principal.mjs";
9
9
  import { chooseModel } from "../../model.mjs";
10
10
  import { MAXIMUM_DOWNLOAD_BYTES, connectionProxy } from "../connection-proxy.mjs";
11
+ import { confirmationLocale } from "./confirmation-text.mjs";
11
12
  import { runtimeToken } from "../runtime-token.mjs";
12
13
  import { agentAttachments, parseAttachmentUrl } from "../attachments.mjs";
13
14
  import { agentIdentities } from "../identities.mjs";
14
15
  import { conversations } from "../conversations.mjs";
15
16
  import { observedMessages } from "./observed.mjs";
16
17
  import { telegramSendScope } from "./send-queue.mjs";
17
- import { d as failureText, g as withDeliveryLocale, h as safeTelegramFetch, i as withReplacedText, m as reportTelegramError, n as currentInboundRequest, r as runInboundRequest, t as InboundRequest } from "../../../_chunks/inbound-request-DgyI7oR9.mjs";
18
+ 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-CysnjMdw.mjs";
18
19
  import { captureRouteDecider, captureTelegramFetch, conversationIdentities, recordGateDecision, telegramConversationCapture } from "./conversations.mjs";
19
20
  import { runtimeTokenVerifier } from "../runtime-identity.mjs";
20
21
  import { botIdentityCache } from "./bot.mjs";
21
- import { confirmationLocale, confirmationRenderer } from "./confirmation-text.mjs";
22
22
  import { scopedAddress, sessionPolicy } from "./sessions.mjs";
23
23
  import { handleConfirmationCallback, telegramConfirmationEvents } from "./confirmations.mjs";
24
24
  import { gatewayImageDescriber } from "./images.mjs";
@@ -237,16 +237,16 @@ function segmentstreamTelegram(options) {
237
237
  scope: options.sessions().scope
238
238
  });
239
239
  } catch (error) {
240
- const errorId = reportTelegramError("confirmation_callback_failed", error);
240
+ reportTelegramError("confirmation_callback_failed", error);
241
241
  await context.telegram.answerCallbackQuery({
242
242
  callbackQueryId: query.id,
243
- text: failureText(errorId)
243
+ text: "⚠️"
244
244
  });
245
245
  }
246
246
  },
247
247
  events: {
248
248
  ...telegramFailureEvents,
249
- ...telegramConfirmationEvents(confirmationRenderer({ model: () => options.models().decision }), archive),
249
+ ...telegramConfirmationEvents(archive),
250
250
  "message.completed": replyDelivery(observed, () => telegramSendScope(environment, settings().connection))
251
251
  }
252
252
  });
@@ -7,13 +7,13 @@ __eveCreateRequire(import.meta.url);
7
7
  import { appOrigin, channelsEnabled, proxyEnvironment } from "../environment.mjs";
8
8
  import "./principal.mjs";
9
9
  import { MAXIMUM_PROXY_BODY_BYTES, connectionProxy } from "../connection-proxy.mjs";
10
+ import { confirmationLocale } from "./confirmation-text.mjs";
10
11
  import { redactDiagnostic } from "../diagnostics.mjs";
11
12
  import { runtimeToken } from "../runtime-token.mjs";
12
13
  import { ACT_AS_HEADER } from "../ontology-connection.mjs";
13
14
  import { AttachmentError, CONVERSATION_HEADER, SESSION_HEADER } from "../attachments.mjs";
14
15
  import { inTelegramSendQueue, telegramSendScope } from "./send-queue.mjs";
15
- import { c as customerCopy, f as formattingRefused, l as customerMessage, m as reportTelegramError } from "../../../_chunks/inbound-request-DgyI7oR9.mjs";
16
- import { confirmationLocale } from "./confirmation-text.mjs";
16
+ import { c as customerCopy, f as formattingRefused, l as customerMessage, p as reportTelegramError } from "../../../_chunks/inbound-request-CysnjMdw.mjs";
17
17
  import { TELEGRAM_PLACEHOLDER_TOKEN, TELEGRAM_PROXY_ORIGIN, telegramProxyFetch } from "./proxy-fetch.mjs";
18
18
  import { attachmentOwner } from "../attachment-tools.mjs";
19
19
  const MAXIMUM_PHOTO_BYTES = 10485760;
@@ -85,7 +85,7 @@ async function sendAttachmentsInOrder(options) {
85
85
  const locale = confirmationLocale(principal);
86
86
  const unavailable = (error, diagnostic) => ({
87
87
  error,
88
- detail: customerCopy(locale).attachment,
88
+ detail: customerCopy().attachment,
89
89
  reference: reportTelegramError("attachment_send_refused", {
90
90
  error,
91
91
  diagnostic
@@ -104,7 +104,7 @@ async function sendAttachmentsInOrder(options) {
104
104
  const reference = reportTelegramError("attachment_owner_refused", error);
105
105
  return {
106
106
  error: "forbidden",
107
- detail: customerCopy(locale).attachment,
107
+ detail: customerCopy().attachment,
108
108
  reference
109
109
  };
110
110
  }
@@ -187,7 +187,7 @@ async function sendAttachmentsInOrder(options) {
187
187
  id: reference.id,
188
188
  filename,
189
189
  error: error instanceof AttachmentError ? error.code : "unavailable",
190
- detail: customerCopy(locale).attachment,
190
+ detail: customerCopy().attachment,
191
191
  reference: errorId
192
192
  };
193
193
  }
@@ -271,7 +271,7 @@ async function sendAttachmentsInOrder(options) {
271
271
  id: file.id,
272
272
  filename: file.filename,
273
273
  error: error instanceof AttachmentError ? error.code : "send_failed",
274
- detail: customerCopy(locale).attachment,
274
+ detail: customerCopy().attachment,
275
275
  reference: errorId
276
276
  };
277
277
  });
@@ -1,18 +1,10 @@
1
- import type { ChatPostPreview } from "../chat-post-confirmation.js";
2
- import type { ConfirmationText } from "./confirmation-text.js";
1
+ import { type ConfirmationCopy, type ConfirmationText } from "./confirmation-text.js";
2
+ import type { ConfirmationDescription } from "../confirmation-description.js";
3
3
  /** Only the frozen deterministic card may bypass the generic technical-prose filter. */
4
4
  export declare function withChatPostCard<T>(text: string, run: () => T, links?: readonly string[]): T;
5
5
  export declare function isExactChatPostCard(text: string): boolean;
6
6
  export declare function chatPostCardLinks(text: string): readonly string[] | undefined;
7
- export declare function chatPostCopy(locale: string): {
8
- edit: string;
9
- changes: string;
10
- uncertain: string;
11
- sent: string;
12
- history: string;
13
- expired: string;
14
- };
15
- export declare function chatPostCard(preview: ChatPostPreview, locale: string): ConfirmationText;
7
+ export declare function chatPostCard(description: ConfirmationDescription, copy?: ConfirmationCopy): ConfirmationText;
16
8
  /** Status/receipt strings are data outside the already rendered preview. */
17
9
  export declare function chatPostCardStatus(text: string, status: string): string;
18
- export declare function chatPostReceipt(output: unknown, locale: string): string | undefined;
10
+ export declare function chatPostReceipt(output: unknown, completed?: string): string | undefined;