@segmentstream/agent 0.12.2 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +29 -22
  2. package/dist/_chunks/{build-metadata-CLprTpsn.mjs → build-metadata-BB2uSGsI.mjs} +1 -1
  3. package/dist/_chunks/{inbound-request-BrWspFw5.mjs → inbound-request-BXhiIph-.mjs} +21 -19
  4. package/dist/_chunks/principal-VFQWqe5S.mjs +126 -0
  5. package/dist/extension/_manifest.json +0 -1
  6. package/dist/extension/config.d.ts +23 -5
  7. package/dist/extension/config.mjs +167 -18
  8. package/dist/extension/extension.d.ts +7 -1
  9. package/dist/extension/hooks/authorization.d.ts +3 -0
  10. package/dist/extension/hooks/authorization.mjs +16 -0
  11. package/dist/extension/instructions.md +3 -4
  12. package/dist/extension/lib/attachment-tools.mjs +1 -1
  13. package/dist/extension/lib/attachments.d.ts +2 -0
  14. package/dist/extension/lib/attachments.mjs +1 -0
  15. package/dist/extension/lib/build-metadata.mjs +1 -1
  16. package/dist/extension/lib/chat-post-confirmation.d.ts +1 -0
  17. package/dist/extension/lib/chat-post-confirmation.mjs +4 -2
  18. package/dist/extension/lib/confirmation-outcome.d.ts +1 -1
  19. package/dist/extension/lib/confirmations.d.ts +2 -0
  20. package/dist/extension/lib/confirmations.mjs +8 -5
  21. package/dist/extension/lib/conversation-runtime.mjs +1 -1
  22. package/dist/extension/lib/deferred-confirmations.d.ts +5 -0
  23. package/dist/extension/lib/deferred-confirmations.mjs +23 -10
  24. package/dist/extension/lib/ontology-connection.d.ts +1 -0
  25. package/dist/extension/lib/ontology-connection.mjs +18 -12
  26. package/dist/extension/lib/ontology-gateway.mjs +9 -2
  27. package/dist/extension/lib/ontology-metadata-read.mjs +1 -1
  28. package/dist/extension/lib/ontology-tools.d.ts +9 -2
  29. package/dist/extension/lib/ontology-tools.mjs +29 -5
  30. package/dist/extension/lib/runtime-envelope.d.ts +28 -0
  31. package/dist/extension/lib/runtime-envelope.mjs +8 -0
  32. package/dist/extension/lib/stored-files.mjs +1 -1
  33. package/dist/extension/lib/telegram/adapter.mjs +53 -11
  34. package/dist/extension/lib/telegram/attachment-description.mjs +1 -1
  35. package/dist/extension/lib/telegram/attachments-send.mjs +15 -12
  36. package/dist/extension/lib/telegram/callbacks.d.ts +18 -0
  37. package/dist/extension/lib/telegram/callbacks.mjs +155 -0
  38. package/dist/extension/lib/telegram/chat-post-card.d.ts +1 -1
  39. package/dist/extension/lib/telegram/chat-post-card.mjs +13 -10
  40. package/dist/extension/lib/telegram/confirmation-expiry.d.ts +2 -1
  41. package/dist/extension/lib/telegram/confirmation-expiry.mjs +6 -5
  42. package/dist/extension/lib/telegram/confirmation-text.d.ts +1 -1
  43. package/dist/extension/lib/telegram/confirmation-text.mjs +18 -12
  44. package/dist/extension/lib/telegram/confirmations.mjs +28 -23
  45. package/dist/extension/lib/telegram/conversations.d.ts +2 -0
  46. package/dist/extension/lib/telegram/conversations.mjs +14 -1
  47. package/dist/extension/lib/telegram/customer-text.d.ts +9 -8
  48. package/dist/extension/lib/telegram/customer-text.mjs +1 -1
  49. package/dist/extension/lib/telegram/deferred-confirmations.mjs +14 -13
  50. package/dist/extension/lib/telegram/files.mjs +2 -2
  51. package/dist/extension/lib/telegram/health.mjs +1 -1
  52. package/dist/extension/lib/telegram/images.mjs +1 -1
  53. package/dist/extension/lib/telegram/inbound-request.mjs +1 -1
  54. package/dist/extension/lib/telegram/inbound.mjs +25 -20
  55. package/dist/extension/lib/telegram/lifecycle.mjs +15 -3
  56. package/dist/extension/lib/telegram/media-policy.mjs +1 -1
  57. package/dist/extension/lib/telegram/principal.d.ts +1 -0
  58. package/dist/extension/lib/telegram/principal.mjs +1 -49
  59. package/dist/extension/lib/telegram/sessions.d.ts +3 -1
  60. package/dist/extension/lib/telegram/silent.mjs +3 -3
  61. package/dist/extension/lib/telegram/texts.d.ts +16 -0
  62. package/dist/extension/lib/telegram/texts.mjs +70 -0
  63. package/dist/extension/lib/telegram/turn-errors.d.ts +1 -1
  64. package/dist/extension/lib/telegram/turn-errors.mjs +38 -4
  65. package/dist/extension/lib/telegram/voice.mjs +1 -1
  66. package/dist/extension/lib/telegram/workspace-action.d.ts +21 -0
  67. package/dist/extension/lib/telegram/workspace-action.mjs +23 -0
  68. package/dist/extension/model.mjs +1 -1
  69. package/dist/extension/schedules/confirmations.mjs +1 -1
  70. package/dist/extension/tools/attachments_promote.mjs +8 -0
  71. package/dist/extension/tools/ontology.mjs +2 -2
  72. package/dist/extension/tools/questions_ask.mjs +1 -1
  73. package/package.json +2 -2
  74. package/dist/extension/connections/ontology.d.ts +0 -7
  75. package/dist/extension/connections/ontology.mjs +0 -11
@@ -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,9 @@ function telegramSettings(value, issues) {
72
78
  "groups",
73
79
  "linkPreviews",
74
80
  "additionalOwnOrigins",
75
- "attachmentLifecycle"
81
+ "attachmentLifecycle",
82
+ "menuButton",
83
+ "callbacks"
76
84
  ],
77
85
  issues
78
86
  });
@@ -115,6 +123,18 @@ function telegramSettings(value, issues) {
115
123
  message: "must be an array of HTTP(S) origins without paths, credentials, queries or fragments",
116
124
  path: [...path, "additionalOwnOrigins"]
117
125
  });
126
+ const callbacks = workspaceHook(telegram.callbacks, [...path, "callbacks"], issues);
127
+ const menuButton = section({
128
+ value: telegram.menuButton,
129
+ path: [...path, "menuButton"],
130
+ allowed: ["text"],
131
+ issues
132
+ });
133
+ const menuText = menuButton === void 0 ? void 0 : localizedText(menuButton.text, [
134
+ ...path,
135
+ "menuButton",
136
+ "text"
137
+ ], issues, 64);
118
138
  const lifecycle = section({
119
139
  value: telegram.attachmentLifecycle,
120
140
  path: [...path, "attachmentLifecycle"],
@@ -141,8 +161,10 @@ function telegramSettings(value, issues) {
141
161
  connection,
142
162
  policy,
143
163
  ...signalSettings === void 0 ? {} : { groups: signalSettings },
164
+ ...callbacks === void 0 ? {} : { callbacks },
144
165
  linkPreviews,
145
166
  additionalOwnOrigins,
167
+ ...menuText === void 0 ? {} : { menuButton: { text: menuText } },
146
168
  ...lifecycle === void 0 ? {} : { attachmentLifecycle: {
147
169
  entity: String(lifecycle.entity),
148
170
  action: String(lifecycle.action),
@@ -150,6 +172,32 @@ function telegramSettings(value, issues) {
150
172
  } }
151
173
  } : null;
152
174
  }
175
+ function workspaceHook(value, path, issues) {
176
+ const configured = section({
177
+ value,
178
+ path,
179
+ allowed: [
180
+ "entity",
181
+ "action",
182
+ "contextVersion"
183
+ ],
184
+ issues
185
+ });
186
+ if (configured === void 0) return;
187
+ for (const key of ["entity", "action"]) if (typeof configured[key] !== "string" || !/^(?:[a-z][a-zA-Z0-9]*|[a-z][a-z0-9]*(?:_[a-z0-9]+)*)$/.test(String(configured[key]))) issues.push({
188
+ message: "must name an ontology entity or action",
189
+ path: [...path, key]
190
+ });
191
+ if (configured.contextVersion !== 2) issues.push({
192
+ message: "must be 2",
193
+ path: [...path, "contextVersion"]
194
+ });
195
+ return {
196
+ entity: String(configured.entity),
197
+ action: String(configured.action),
198
+ contextVersion: 2
199
+ };
200
+ }
153
201
  function modelId(input) {
154
202
  if (input.value === void 0) return input.fallback;
155
203
  if (typeof input.value !== "string" || input.value === "" || input.value !== input.value.trim()) {
@@ -265,7 +313,7 @@ function sessionSettings(value, issues) {
265
313
  idleResetHours: typeof idleResetHours === "number" && Number.isFinite(idleResetHours) && idleResetHours > 0 ? idleResetHours : false
266
314
  };
267
315
  }
268
- function localizedText(value, path, issues) {
316
+ function localizedText(value, path, issues, maximum = 4e3, safe = true) {
269
317
  if (!isRecord(value) || Object.keys(value).length === 0) {
270
318
  issues.push({
271
319
  message: "must be a non-empty locale-to-text object",
@@ -282,14 +330,14 @@ function localizedText(value, path, issues) {
282
330
  path: [...path, locale]
283
331
  });
284
332
  }
285
- if (typeof text !== "string" || text.trim() === "" || text.length > 4e3) issues.push({
286
- message: "must be non-empty text of at most 4000 characters",
333
+ if (typeof text !== "string" || text.trim() === "" || text.length > maximum || safe && typeof text === "string" && isTechnicalText(text)) issues.push({
334
+ message: `must be ${safe ? "safe " : ""}non-empty text of at most ${maximum} characters`,
287
335
  path: [...path, locale]
288
336
  });
289
337
  }
290
338
  return value;
291
339
  }
292
- function firstContactSettings(value, issues) {
340
+ function firstContactSettings(value, issues, safe = true) {
293
341
  const path = ["firstContact"];
294
342
  const settings = section({
295
343
  value,
@@ -303,7 +351,7 @@ function firstContactSettings(value, issues) {
303
351
  message: "must be non-empty text of at most 200 characters without surrounding whitespace",
304
352
  path: [...path, "version"]
305
353
  });
306
- const notice = localizedText(settings.notice, [...path, "notice"], issues);
354
+ const notice = localizedText(settings.notice, [...path, "notice"], issues, 4e3, safe);
307
355
  return {
308
356
  version: typeof version === "string" ? version : "",
309
357
  notice
@@ -351,7 +399,7 @@ function admissionLimits(value, issues) {
351
399
  workspaceTurnsPerDay: limit(limits?.workspaceTurnsPerDay, DEFAULT_ADMISSION_LIMITS.workspaceTurnsPerDay, [...path, "workspaceTurnsPerDay"])
352
400
  };
353
401
  }
354
- function admissionReplies(value, issues) {
402
+ function admissionReplies(value, issues, safe = true) {
355
403
  const replies = section({
356
404
  value,
357
405
  path: ["replies"],
@@ -359,10 +407,90 @@ function admissionReplies(value, issues) {
359
407
  issues
360
408
  });
361
409
  const configured = {};
362
- for (const key of ADMISSION_REPLY_KEYS) if (replies?.[key] !== void 0) configured[key] = localizedText(replies[key], ["replies", key], issues);
410
+ 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
411
  return configured;
364
412
  }
365
- function validateConfig(input) {
413
+ function telegramTexts(value, issues) {
414
+ const root = section({
415
+ value,
416
+ path: ["texts"],
417
+ allowed: Object.keys(TELEGRAM_TEXT_KEYS),
418
+ issues
419
+ });
420
+ const output = {};
421
+ for (const [group, keys] of Object.entries(TELEGRAM_TEXT_KEYS)) {
422
+ const sectionValue = section({
423
+ value: root?.[group],
424
+ path: ["texts", group],
425
+ allowed: keys,
426
+ issues
427
+ });
428
+ if (sectionValue === void 0) continue;
429
+ output[group] = {};
430
+ for (const key of keys) if (sectionValue[key] !== void 0) {
431
+ const maximum = group === "confirmation" ? key === "question" ? 300 : 24 : group === "callback" ? 200 : group === "authorization" && key === "button" ? 24 : 4e3;
432
+ output[group][key] = localizedText(sectionValue[key], [
433
+ "texts",
434
+ group,
435
+ key
436
+ ], issues, maximum);
437
+ }
438
+ }
439
+ const confirmation = output.confirmation;
440
+ if (confirmation?.confirm !== void 0 && confirmation.cancel !== void 0) {
441
+ const locales = new Set([
442
+ "en",
443
+ ...Object.keys(confirmation.confirm),
444
+ ...Object.keys(confirmation.cancel)
445
+ ]);
446
+ for (const locale of locales) {
447
+ const confirm = localizedText$1(confirmation.confirm, locale);
448
+ const cancel = localizedText$1(confirmation.cancel, locale);
449
+ if (typeof confirm === "string" && typeof cancel === "string" && confirm.trim() === cancel.trim()) issues.push({
450
+ message: "confirm and cancel must have distinct effective localized labels",
451
+ path: [
452
+ "texts",
453
+ "confirmation",
454
+ "cancel",
455
+ locale
456
+ ]
457
+ });
458
+ }
459
+ }
460
+ const authorization = output.authorization;
461
+ if (authorization?.url !== void 0) for (const [locale, value] of Object.entries(authorization.url)) try {
462
+ const url = new URL(value);
463
+ if (!["https:", "http:"].includes(url.protocol) || url.username || url.password) throw new Error("Invalid authorization URL");
464
+ } catch {
465
+ issues.push({
466
+ message: "must be an HTTP(S) URL without credentials",
467
+ path: [
468
+ "texts",
469
+ "authorization",
470
+ "url",
471
+ locale
472
+ ]
473
+ });
474
+ }
475
+ if ((authorization?.url !== void 0 || authorization?.button !== void 0) && authorization?.prompt === void 0) issues.push({
476
+ message: "is required when an authorization button is configured",
477
+ path: [
478
+ "texts",
479
+ "authorization",
480
+ "prompt"
481
+ ]
482
+ });
483
+ if (authorization?.url === void 0 !== (authorization?.button === void 0)) issues.push({
484
+ message: "url and button must be supplied together",
485
+ path: ["texts", "authorization"]
486
+ });
487
+ return output;
488
+ }
489
+ function isLegacyAgentArchive(version) {
490
+ return /^0\.(?:[0-9]|1[0-2])\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version ?? "");
491
+ }
492
+ function validateConfig(input, options = {}) {
493
+ const legacyArchive = isLegacyAgentArchive(options.archiveSdkVersion);
366
494
  const issues = [];
367
495
  const root = section({
368
496
  value: input ?? {},
@@ -376,7 +504,8 @@ function validateConfig(input) {
376
504
  "confirmations",
377
505
  "firstContact",
378
506
  "limits",
379
- "replies"
507
+ "replies",
508
+ "texts"
380
509
  ],
381
510
  issues
382
511
  });
@@ -410,8 +539,8 @@ function validateConfig(input) {
410
539
  allowed: ["mode"],
411
540
  issues
412
541
  })?.mode ?? "deferred";
413
- if (mode !== "deferred" && mode !== "held") issues.push({
414
- message: "must be \"deferred\" or \"held\"",
542
+ if (mode !== "deferred" && !(legacyArchive && mode === "held")) issues.push({
543
+ message: "must be \"deferred\"; Telegram Eve approval buttons are unsupported",
415
544
  path: ["confirmations", "mode"]
416
545
  });
417
546
  const telegram = telegramSettings(channels?.telegram, issues);
@@ -502,9 +631,28 @@ function validateConfig(input) {
502
631
  });
503
632
  const connection = ontology?.connection ?? true;
504
633
  const sessions = sessionSettings(root?.sessions, issues);
505
- const firstContact = firstContactSettings(root?.firstContact, issues);
634
+ const firstContact = firstContactSettings(root?.firstContact, issues, !legacyArchive);
506
635
  const limits = admissionLimits(root?.limits, issues);
507
- const replies = admissionReplies(root?.replies, issues);
636
+ const replies = admissionReplies(root?.replies, issues, !legacyArchive);
637
+ const texts = telegramTexts(root?.texts, issues);
638
+ if (!legacyArchive && replies.groupPairingButton !== void 0 && replies.groupPairingRequired === void 0) issues.push({
639
+ message: "is required when groupPairingButton is configured",
640
+ path: ["replies", "groupPairingRequired"]
641
+ });
642
+ if (telegram !== null && !legacyArchive) {
643
+ for (const key of ["pairingRequired", "pairingButton"]) if (replies[key] === void 0) issues.push({
644
+ message: "is required for Telegram",
645
+ path: ["replies", key]
646
+ });
647
+ for (const key of TELEGRAM_TEXT_KEYS.confirmation) if (texts.confirmation?.[key] === void 0) issues.push({
648
+ message: "is required for Telegram confirmations",
649
+ path: [
650
+ "texts",
651
+ "confirmation",
652
+ key
653
+ ]
654
+ });
655
+ }
508
656
  if (typeof connection !== "boolean") issues.push({
509
657
  message: "must be true or false",
510
658
  path: ["ontology", "connection"]
@@ -524,17 +672,18 @@ function validateConfig(input) {
524
672
  attachments
525
673
  },
526
674
  ontology: { connection: connection === true },
527
- confirmations: { mode: mode === "held" ? "held" : "deferred" },
675
+ confirmations: { mode: "deferred" },
528
676
  sessions,
529
677
  firstContact,
530
678
  limits,
531
- replies
679
+ replies,
680
+ texts
532
681
  } };
533
682
  }
534
683
  const configSchema = { "~standard": {
535
684
  version: 1,
536
685
  vendor: "segmentstream",
537
- validate: validateConfig,
686
+ validate: (input) => validateConfig(input),
538
687
  types: void 0
539
688
  } };
540
689
  function manifestOf(config) {
@@ -542,4 +691,4 @@ function manifestOf(config) {
542
691
  if (validation.issues !== void 0) throw new SegmentStreamAgentConfigError(validation.issues);
543
692
  return validation.value;
544
693
  }
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 };
694
+ 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 "../../_chunks/principal-VFQWqe5S.mjs";
9
+ import { confirmationLocale } from "../lib/telegram/confirmation-text.mjs";
10
+ import segmentstream from "../extension.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
@@ -5,7 +5,7 @@ const __filename = __eveFileURLToPath(import.meta.url);
5
5
  __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { appOrigin } from "./environment.mjs";
8
- import { memberIdOf } from "./telegram/principal.mjs";
8
+ import { i as memberIdOf } from "../../_chunks/principal-VFQWqe5S.mjs";
9
9
  import { runtimeToken } from "./runtime-token.mjs";
10
10
  import { agentAttachments } from "./attachments.mjs";
11
11
  import { telegramConversation } from "./telegram/files.mjs";
@@ -1,3 +1,4 @@
1
+ import type { RuntimeEnvelope } from "./runtime-envelope.js";
1
2
  import type { RuntimeToken } from "./runtime-token.js";
2
3
  /**
3
4
  * The ontology service's attachment routes (`docs/agent.md`, "Attachments"). A file a member sends
@@ -35,6 +36,7 @@ export interface Attachment {
35
36
  readonly promotion: unknown;
36
37
  }
37
38
  export interface PromotionRequest {
39
+ readonly runtime?: RuntimeEnvelope;
38
40
  readonly entity: string;
39
41
  readonly action: string;
40
42
  readonly input?: Readonly<Record<string, unknown>>;
@@ -192,6 +192,7 @@ function agentAttachments(input) {
192
192
  const answer = await call(owner, `/${encodeURIComponent(id)}/promote`, "promotion", {
193
193
  entity: request.entity,
194
194
  action: request.action,
195
+ ...request.runtime === void 0 ? {} : { _meta: { _segmentstream: request.runtime } },
195
196
  input: request.input ?? {},
196
197
  ...request.field === void 0 ? {} : { field: request.field },
197
198
  ...request.recordId === void 0 ? {} : { record_id: request.recordId }
@@ -4,5 +4,5 @@ import { createRequire as __eveCreateRequire } from "node:module";
4
4
  const __filename = __eveFileURLToPath(import.meta.url);
5
5
  __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
- import { t as agentBuild } from "../../_chunks/build-metadata-CLprTpsn.mjs";
7
+ import { t as agentBuild } from "../../_chunks/build-metadata-BB2uSGsI.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 { memberIdOf } from "./telegram/principal.mjs";
8
- import { confirmationCopy, renderConfirmation } from "./telegram/confirmation-text.mjs";
7
+ import { withTelegramTexts } from "./telegram/texts.mjs";
8
+ import { i as memberIdOf } from "../../_chunks/principal-VFQWqe5S.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
  }
@@ -5,7 +5,7 @@ const __filename = __eveFileURLToPath(import.meta.url);
5
5
  __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { ENDS_CONVERSATION_INSTRUCTIONS } from "./group-routing/conversation-prompt.mjs";
8
- import { memberIdOf } from "./telegram/principal.mjs";
8
+ import { i as memberIdOf } from "../../_chunks/principal-VFQWqe5S.mjs";
9
9
  import { n as contentText, t as CONTENT_CAPS } from "../../_chunks/content-CvFsrmSD.mjs";
10
10
  import { conversationEvent, conversations, summarizeArguments } from "./conversations.mjs";
11
11
  import { parseAddress } from "./telegram/sessions.mjs";
@@ -1,7 +1,9 @@
1
+ import { type RuntimeEnvelope } from "./runtime-envelope.js";
1
2
  import type { ToolContext } from "eve/tools";
2
3
  import { type Confirmation } from "./confirmations.js";
3
4
  import { type Environment } from "./environment.js";
4
5
  import { type RuntimeToken } from "./runtime-token.js";
6
+ import type { TelegramTexts } from "./telegram/texts.js";
5
7
  export interface DeferredConfirmation {
6
8
  readonly id: string;
7
9
  readonly key: string;
@@ -10,6 +12,7 @@ export interface DeferredConfirmation {
10
12
  readonly expiresAt: number;
11
13
  readonly status: "pending" | "approved" | "cancelled" | "expired" | "done" | "failed";
12
14
  readonly data: {
15
+ readonly runtimeSource?: RuntimeEnvelope["source"];
13
16
  readonly action: string;
14
17
  readonly input: Readonly<Record<string, unknown>>;
15
18
  readonly confirmation: Confirmation;
@@ -18,12 +21,14 @@ export interface DeferredConfirmation {
18
21
  readonly chatId: string;
19
22
  readonly threadId: string;
20
23
  readonly requester: string;
24
+ readonly locale?: string;
21
25
  readonly requesterName?: string;
22
26
  readonly messageId?: string;
23
27
  readonly address?: string;
24
28
  };
25
29
  }
26
30
  export type DeferredOptions = {
31
+ readonly texts?: () => TelegramTexts;
27
32
  readonly environment?: Environment;
28
33
  readonly token?: RuntimeToken;
29
34
  readonly fetch?: typeof fetch;
@@ -5,10 +5,10 @@ const __filename = __eveFileURLToPath(import.meta.url);
5
5
  __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { appOrigin } from "./environment.mjs";
8
- import { memberIdOf } from "./telegram/principal.mjs";
9
- import { runtimeToken } from "./runtime-token.mjs";
8
+ import { i as memberIdOf, l as principalRuntimeSource, u as runtimeEnvelope } from "../../_chunks/principal-VFQWqe5S.mjs";
10
9
  import { redactDiagnostic } from "./diagnostics.mjs";
11
10
  import { humanText, object } from "./confirmation-description.mjs";
11
+ import { runtimeToken } from "./runtime-token.mjs";
12
12
  import { chatPostDigest, chatPostOperationId, isChatPost } from "./chat-post-confirmation.mjs";
13
13
  import { actionApproval, inputHash } from "./confirmations.mjs";
14
14
  import { ontologyTools } from "./ontology-tools.mjs";
@@ -37,7 +37,7 @@ function deferredConfirmations(options = {}) {
37
37
  }
38
38
  async function executeDeferredTool(memberId, action, input, ctx, options = {}) {
39
39
  if (memberIdOf(ctx.session.auth.current) !== memberId) throw new Error("The ontology tool belongs to another member.");
40
- if (Object.hasOwn(input, "_segmentstream")) throw new Error("Runtime call metadata is application-owned.");
40
+ input = Object.fromEntries(Object.entries(input).filter(([key]) => key !== "_segmentstream"));
41
41
  const principal = ctx.session.auth.current;
42
42
  const attributes = principal?.attributes;
43
43
  const id = chatPostOperationId(ctx.session.id, ctx.callId);
@@ -56,10 +56,19 @@ async function executeDeferredTool(memberId, action, input, ctx, options = {}) {
56
56
  };
57
57
  };
58
58
  if (existing !== null) return boundResult(existing);
59
- const tools = ontologyTools(options);
59
+ const tools = ontologyTools({
60
+ ...options,
61
+ principal: ctx.session.auth.current,
62
+ envelope: runtimeEnvelope({
63
+ principal: ctx.session.auth.current,
64
+ sessionId: ctx.session.id,
65
+ callId: ctx.callId
66
+ })
67
+ });
60
68
  let saved = {};
61
69
  const result = await actionApproval({
62
70
  ...tools,
71
+ ...options.texts === void 0 ? {} : { texts: options.texts },
63
72
  state: {
64
73
  get: () => saved,
65
74
  update: (update) => {
@@ -95,12 +104,14 @@ async function executeDeferredTool(memberId, action, input, ctx, options = {}) {
95
104
  data: {
96
105
  action,
97
106
  input,
107
+ ...principalRuntimeSource(principal) === void 0 ? {} : { runtimeSource: principalRuntimeSource(principal) },
98
108
  confirmation,
99
109
  sessionId: ctx.session.id,
100
110
  callId: ctx.callId,
101
111
  chatId: attributes.chatId,
102
112
  threadId: String(attributes.messageThreadId ?? ""),
103
113
  requester: principal.subject,
114
+ locale: String(attributes.conversationLanguage ?? attributes.locale ?? "en"),
104
115
  ...humanText(attributes.memberName) === void 0 ? {} : { requesterName: humanText(attributes.memberName) }
105
116
  }
106
117
  }
@@ -123,11 +134,13 @@ async function runDeferredAction(record, options) {
123
134
  session_id: record.data.sessionId,
124
135
  call_id: record.data.callId,
125
136
  operation_id: record.id,
137
+ ...record.data.runtimeSource === void 0 ? {} : { source: record.data.runtimeSource },
126
138
  ...preview ? {
127
139
  source: {
128
140
  provider: "telegram",
129
141
  chat_id: record.data.chatId,
130
- thread_id: record.data.threadId
142
+ thread_id: record.data.threadId,
143
+ ...record.data.runtimeSource
131
144
  },
132
145
  expected: {
133
146
  chat_id: preview.chatId,
@@ -135,12 +148,12 @@ async function runDeferredAction(record, options) {
135
148
  }
136
149
  } : {}
137
150
  };
138
- const result = await tools.rpc(record.memberId, "tools/call", {
151
+ const result = await ontologyTools({
152
+ ...options,
153
+ envelope
154
+ }).rpc(record.memberId, "tools/call", {
139
155
  name: record.data.action.split("__").at(-1),
140
- arguments: {
141
- ...record.data.input,
142
- _segmentstream: envelope
143
- }
156
+ arguments: record.data.input
144
157
  });
145
158
  const output = result.structuredContent;
146
159
  if (result.isError === true || (output?.validation)?.ok === false || preview !== void 0 && output?.status !== "sent") throw new Error("The confirmed action did not complete; its outcome may be uncertain.", { cause: result });