@segmentstream/agent 0.6.14 → 0.7.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 (51) hide show
  1. package/dist/_chunks/{customer-text-DjszxgTo.mjs → inbound-request-BwBvXk0A.mjs} +110 -109
  2. package/dist/extension/_manifest.json +2 -0
  3. package/dist/extension/channels/telegram.mjs +1 -0
  4. package/dist/extension/config.d.ts +7 -0
  5. package/dist/extension/config.mjs +13 -1
  6. package/dist/extension/connections/ontology.mjs +1 -1
  7. package/dist/extension/hooks/conversations.mjs +1 -1
  8. package/dist/extension/instructions/conversations.mjs +1 -1
  9. package/dist/extension/instructions.md +17 -0
  10. package/dist/extension/lib/confirmation-description.d.ts +5 -0
  11. package/dist/extension/lib/confirmation-description.mjs +7 -1
  12. package/dist/extension/lib/confirmation-outcome.d.ts +19 -0
  13. package/dist/extension/lib/confirmation-outcome.mjs +34 -0
  14. package/dist/extension/lib/confirmations.mjs +1 -1
  15. package/dist/extension/lib/conversation-runtime.mjs +1 -1
  16. package/dist/extension/lib/customer-text-safety.mjs +2 -1
  17. package/dist/extension/lib/deferred-confirmations.d.ts +39 -0
  18. package/dist/extension/lib/deferred-confirmations.mjs +149 -0
  19. package/dist/extension/lib/ontology-tools.d.ts +2 -0
  20. package/dist/extension/lib/ontology-tools.mjs +6 -1
  21. package/dist/extension/lib/telegram/adapter.d.ts +1 -0
  22. package/dist/extension/lib/telegram/adapter.mjs +72 -6
  23. package/dist/extension/lib/telegram/attachment-description.mjs +1 -1
  24. package/dist/extension/lib/telegram/attachments-send.mjs +3 -3
  25. package/dist/extension/lib/telegram/confirmation-expiry.d.ts +4 -0
  26. package/dist/extension/lib/telegram/confirmation-expiry.mjs +50 -0
  27. package/dist/extension/lib/telegram/confirmations.d.ts +2 -1
  28. package/dist/extension/lib/telegram/confirmations.mjs +13 -5
  29. package/dist/extension/lib/telegram/conversations.mjs +1 -1
  30. package/dist/extension/lib/telegram/customer-text.mjs +1 -1
  31. package/dist/extension/lib/telegram/deferred-confirmations.d.ts +26 -0
  32. package/dist/extension/lib/telegram/deferred-confirmations.mjs +282 -0
  33. package/dist/extension/lib/telegram/files.mjs +1 -1
  34. package/dist/extension/lib/telegram/images.mjs +1 -1
  35. package/dist/extension/lib/telegram/inbound-request.d.ts +2 -0
  36. package/dist/extension/lib/telegram/inbound-request.mjs +1 -1
  37. package/dist/extension/lib/telegram/inbound.mjs +5 -5
  38. package/dist/extension/lib/telegram/media-policy.mjs +1 -1
  39. package/dist/extension/lib/telegram/observed.d.ts +1 -0
  40. package/dist/extension/lib/telegram/routing-context.mjs +27 -2
  41. package/dist/extension/lib/telegram/silent.mjs +1 -1
  42. package/dist/extension/lib/telegram/turn-errors.mjs +1 -1
  43. package/dist/extension/lib/telegram/voice.mjs +1 -1
  44. package/dist/extension/schedules/confirmations.d.ts +6 -0
  45. package/dist/extension/schedules/confirmations.mjs +33 -0
  46. package/dist/extension/tools/ontology.d.ts +4 -0
  47. package/dist/extension/tools/ontology.mjs +30 -0
  48. package/dist/tools/index.d.ts +1 -0
  49. package/dist/tools/index.mjs +2 -1
  50. package/package.json +2 -2
  51. package/dist/_chunks/{conversation-runtime-BINKO_6b.mjs → conversation-runtime-ovs8IhXJ.mjs} +1 -1
@@ -0,0 +1,149 @@
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 { appOrigin } from "./environment.mjs";
8
+ import { memberIdOf } from "./telegram/principal.mjs";
9
+ import { humanText, object } from "./confirmation-description.mjs";
10
+ import { chatPostDigest, chatPostOperationId, isChatPost } from "./chat-post-confirmation.mjs";
11
+ import { actionApproval, inputHash } from "./confirmations.mjs";
12
+ import { redactDiagnostic } from "./diagnostics.mjs";
13
+ import { runtimeToken } from "./runtime-token.mjs";
14
+ import { ontologyTools } from "./ontology-tools.mjs";
15
+ import { createHash } from "node:crypto";
16
+ const deferredKey = (id) => createHash("sha256").update(id).digest("hex").slice(0, 20);
17
+ function deferredConfirmations(options = {}) {
18
+ return { async command(command) {
19
+ const credential = await (options.token ?? runtimeToken)();
20
+ try {
21
+ const response = await (options.fetch ?? fetch)(`${appOrigin(options.environment ?? process.env)}/api/ontology/agent/confirmations`, {
22
+ method: "POST",
23
+ redirect: "error",
24
+ signal: AbortSignal.timeout(15e3),
25
+ headers: {
26
+ authorization: `Bearer ${credential}`,
27
+ "content-type": "application/json"
28
+ },
29
+ body: JSON.stringify(command)
30
+ });
31
+ if (!response.ok) throw new Error(`Confirmation ledger unavailable (HTTP ${response.status}).`, { cause: await response.text() });
32
+ return (await response.json()).result;
33
+ } catch (error) {
34
+ throw new Error("Confirmation ledger request failed.", { cause: redactDiagnostic(error, new Set([credential])) });
35
+ }
36
+ } };
37
+ }
38
+ async function executeDeferredTool(memberId, action, input, ctx, options = {}) {
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.");
41
+ const principal = ctx.session.auth.current;
42
+ const attributes = principal?.attributes;
43
+ const id = chatPostOperationId(ctx.session.id, ctx.callId);
44
+ const key = deferredKey(id);
45
+ const hash = inputHash(input);
46
+ const store = deferredConfirmations(options);
47
+ const existing = await store.command({
48
+ operation: "read",
49
+ key
50
+ });
51
+ const boundResult = (record) => {
52
+ if (record.id !== id || record.key !== key || record.memberId !== memberId || record.inputHash !== hash || inputHash(record.data.input) !== hash || record.data.action !== action || record.data.sessionId !== ctx.session.id || record.data.callId !== ctx.callId || principal?.authenticator !== "segmentstream-telegram" || !principal.subject || typeof attributes?.chatId !== "string" || record.data.requester !== principal.subject || record.data.chatId !== attributes?.chatId || record.data.threadId !== String(attributes?.messageThreadId ?? "")) throw new Error("The confirmation call was replayed with another identity, action or input.");
53
+ return {
54
+ status: record.status === "pending" ? "awaiting_confirmation" : record.status,
55
+ confirmation: record.id
56
+ };
57
+ };
58
+ if (existing !== null) return boundResult(existing);
59
+ const tools = ontologyTools(options);
60
+ let saved = {};
61
+ const result = await actionApproval({
62
+ ...tools,
63
+ state: {
64
+ get: () => saved,
65
+ update: (update) => {
66
+ saved = update(saved);
67
+ }
68
+ }
69
+ }).request({
70
+ ...ctx,
71
+ callId: ctx.callId,
72
+ toolName: action,
73
+ toolInput: input,
74
+ approvedTools: new Set()
75
+ });
76
+ if (typeof result === "object" && result.type === "denied") throw new Error(result.reason);
77
+ const confirmation = saved[ctx.callId];
78
+ if (confirmation === void 0) {
79
+ const response = await tools.rpc(memberId, "tools/call", {
80
+ name: action,
81
+ arguments: input
82
+ });
83
+ return object(response.structuredContent ?? response);
84
+ }
85
+ if (principal?.authenticator !== "segmentstream-telegram" || !principal.subject || typeof attributes?.chatId !== "string") throw new Error("Deferred confirmation requires an authenticated Telegram requester.");
86
+ const record = await store.command({
87
+ operation: "create",
88
+ record: {
89
+ id,
90
+ key,
91
+ memberId,
92
+ inputHash: hash,
93
+ expiresAt: confirmation.expiresAt,
94
+ status: "pending",
95
+ data: {
96
+ action,
97
+ input,
98
+ confirmation,
99
+ sessionId: ctx.session.id,
100
+ callId: ctx.callId,
101
+ chatId: attributes.chatId,
102
+ threadId: String(attributes.messageThreadId ?? ""),
103
+ requester: principal.subject,
104
+ ...humanText(attributes.memberName) === void 0 ? {} : { requesterName: humanText(attributes.memberName) }
105
+ }
106
+ }
107
+ });
108
+ if (record === null) throw new Error("The confirmation call was replayed with different input.");
109
+ return boundResult(record);
110
+ }
111
+ async function runDeferredAction(record, options) {
112
+ if (record.inputHash !== inputHash(record.data.input)) throw new Error("The confirmed input changed.");
113
+ const tools = ontologyTools(options);
114
+ await tools.labels(record.memberId, record.data.action);
115
+ const preview = record.data.confirmation.description?.chatPost;
116
+ if (isChatPost(record.data.action)) {
117
+ const input = Object.fromEntries(Object.entries(record.data.input).filter(([key]) => key !== "_confirmation"));
118
+ const current = (await tools.describe(record.memberId, record.data.action, input)).chatPost;
119
+ if (!preview || !current || chatPostDigest(preview) !== chatPostDigest(current)) throw new Error("The confirmed chat destination or attribution changed.");
120
+ }
121
+ const envelope = {
122
+ version: 1,
123
+ session_id: record.data.sessionId,
124
+ call_id: record.data.callId,
125
+ operation_id: record.id,
126
+ ...preview ? {
127
+ source: {
128
+ provider: "telegram",
129
+ chat_id: record.data.chatId,
130
+ thread_id: record.data.threadId
131
+ },
132
+ expected: {
133
+ chat_id: preview.chatId,
134
+ digest: chatPostDigest(preview)
135
+ }
136
+ } : {}
137
+ };
138
+ const result = await tools.rpc(record.memberId, "tools/call", {
139
+ name: record.data.action.split("__").at(-1),
140
+ arguments: {
141
+ ...record.data.input,
142
+ _segmentstream: envelope
143
+ }
144
+ });
145
+ const output = result.structuredContent;
146
+ 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 });
147
+ return result;
148
+ }
149
+ export { deferredConfirmations, deferredKey, executeDeferredTool, runDeferredAction };
@@ -7,6 +7,8 @@ export declare function ontologyTools(options?: {
7
7
  readonly token?: RuntimeToken;
8
8
  readonly fetch?: typeof fetch;
9
9
  }): {
10
+ rpc: (memberId: string, method: string, params: Record<string, unknown>, signal?: AbortSignal) => Promise<Record<string, unknown>>;
11
+ list(memberId: string): Promise<Record<string, unknown>[]>;
10
12
  supportsChatPosting(memberId: string): Promise<boolean>;
11
13
  labels(memberId: string, action: string): Promise<ActionAnnotations>;
12
14
  describe(memberId: string, action: string, input: Readonly<Record<string, unknown>>): Promise<import("./confirmation-description.js").ConfirmationDescription>;
@@ -5,9 +5,9 @@ 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 { redactDiagnostic } from "./diagnostics.mjs";
9
8
  import { describeConfirmation, object } from "./confirmation-description.mjs";
10
9
  import { describeChatPost, isChatPost, supportsChatPost } from "./chat-post-confirmation.mjs";
10
+ import { redactDiagnostic } from "./diagnostics.mjs";
11
11
  import { runtimeToken } from "./runtime-token.mjs";
12
12
  function ontologyTools(options = {}) {
13
13
  async function rpc(memberId, method, params, signal) {
@@ -54,6 +54,11 @@ function ontologyTools(options = {}) {
54
54
  };
55
55
  }
56
56
  return {
57
+ rpc,
58
+ async list(memberId) {
59
+ const result = await rpc(memberId, "tools/list", {});
60
+ return Array.isArray(result.tools) ? result.tools.map(object) : [];
61
+ },
57
62
  async supportsChatPosting(memberId) {
58
63
  const result = await rpc(memberId, "tools/list", {});
59
64
  return Array.isArray(result.tools) && result.tools.map(object).some(supportsChatPost);
@@ -30,6 +30,7 @@ export interface TelegramAdapterOptions {
30
30
  };
31
31
  /** The mounted `sessions`. */
32
32
  readonly sessions: () => SessionSettings;
33
+ readonly confirmationMode?: () => "deferred" | "held";
33
34
  readonly decide: DecideFunction;
34
35
  readonly environment?: Environment;
35
36
  readonly token?: RuntimeToken;
@@ -8,20 +8,21 @@ import "../../config.mjs";
8
8
  import { appOrigin, channelsEnabled, proxyEnvironment, runtimeIdentityEnvironment, webhookSecret } from "../environment.mjs";
9
9
  import { conversationLanguage, telegramPrincipal } from "./principal.mjs";
10
10
  import { chooseModel } from "../../model.mjs";
11
- import { d as withDeliveryLocale, f as InboundRequest, h as withReplacedText, l as reportTelegramError, m as runInboundRequest, p as currentInboundRequest, s as failureText, u as safeTelegramFetch } from "../../../_chunks/customer-text-DjszxgTo.mjs";
12
11
  import { confirmationLocale } from "./confirmation-text.mjs";
12
+ import { runtimeToken } from "../runtime-token.mjs";
13
+ import { conversations } from "../conversations.mjs";
13
14
  import { telegramSendScope } from "./send-queue.mjs";
15
+ 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-BwBvXk0A.mjs";
16
+ import { scopedAddress, sessionPolicy } from "./sessions.mjs";
17
+ import { observedMessages } from "./observed.mjs";
18
+ import { expireDeferredCards, handleDeferredCallback, isDeferredCallback } from "./deferred-confirmations.mjs";
14
19
  import { gatewayImageDescriber } from "./images.mjs";
15
20
  import { gatewayAttachmentDescriber } from "./attachment-description.mjs";
16
21
  import { MAXIMUM_DOWNLOAD_BYTES, connectionProxy } from "../connection-proxy.mjs";
17
- import { runtimeToken } from "../runtime-token.mjs";
18
22
  import { agentAttachments, parseAttachmentUrl } from "../attachments.mjs";
19
23
  import { agentIdentities } from "../identities.mjs";
20
- import { conversations } from "../conversations.mjs";
21
- import { observedMessages } from "./observed.mjs";
22
24
  import { decisionRouter } from "./router.mjs";
23
25
  import { replyDelivery } from "./silent.mjs";
24
- import { scopedAddress, sessionPolicy } from "./sessions.mjs";
25
26
  import { handleConfirmationCallback, telegramConfirmationEvents } from "./confirmations.mjs";
26
27
  import { captureRouteDecider, captureTelegramFetch, conversationIdentities, recordGateDecision, telegramConversationCapture } from "./conversations.mjs";
27
28
  import { runtimeTokenVerifier } from "../runtime-identity.mjs";
@@ -142,6 +143,15 @@ function segmentstreamTelegram(options) {
142
143
  },
143
144
  uploadPolicy: { maxBytes: MAXIMUM_DOWNLOAD_BYTES },
144
145
  onMessage: async (context, message) => {
146
+ if (options.confirmationMode?.() === "deferred") try {
147
+ await expireDeferredCards(context, {
148
+ environment,
149
+ token,
150
+ ...options.fetch ? { fetch: options.fetch } : {}
151
+ }, archive, observed);
152
+ } catch (error) {
153
+ reportTelegramError("deferred_expiry_failed", error);
154
+ }
145
155
  const inbound = currentInboundRequest();
146
156
  if (inbound !== void 0) {
147
157
  inbound.locale = conversationLanguage([message.text || message.caption]) ?? message.from?.languageCode ?? "en";
@@ -236,6 +246,21 @@ function segmentstreamTelegram(options) {
236
246
  },
237
247
  onCallbackQuery: async (context, query) => {
238
248
  try {
249
+ if (isDeferredCallback(query.data)) {
250
+ await handleDeferredCallback({
251
+ context,
252
+ query,
253
+ identities: pairing(),
254
+ archive,
255
+ options: {
256
+ environment,
257
+ token,
258
+ ...options.fetch ? { fetch: options.fetch } : {}
259
+ },
260
+ observed
261
+ });
262
+ return;
263
+ }
239
264
  await handleConfirmationCallback({
240
265
  context,
241
266
  query,
@@ -253,7 +278,11 @@ function segmentstreamTelegram(options) {
253
278
  },
254
279
  events: {
255
280
  ...telegramFailureEvents,
256
- ...telegramConfirmationEvents(archive, observed),
281
+ ...telegramConfirmationEvents(archive, observed, {
282
+ environment,
283
+ token,
284
+ ...options.fetch ? { fetch: options.fetch } : {}
285
+ }),
257
286
  "message.completed": replyDelivery(observed, () => telegramSendScope(environment, settings().connection))
258
287
  }
259
288
  });
@@ -276,6 +305,25 @@ function segmentstreamTelegram(options) {
276
305
  }
277
306
  return {
278
307
  ...channel,
308
+ async receive(input, operations) {
309
+ const target = input.target;
310
+ if (target.confirmationSessionId !== void 0) {
311
+ const address = target.confirmationAddress;
312
+ if (address === void 0 || (await operations.resolveSession(address))?.id !== target.confirmationSessionId) throw new Error("The confirmation session was replaced.");
313
+ return operations.from(address).send(input.message, {
314
+ auth: null,
315
+ turnPolicy: "queue",
316
+ state: {
317
+ chatId: String(target.chatId),
318
+ chatType: String(target.chatId).startsWith("-") ? "supergroup" : "private",
319
+ conversationId: null,
320
+ messageThreadId: target.messageThreadId ?? null
321
+ }
322
+ });
323
+ }
324
+ if (channel.receive === void 0) throw new Error("The Telegram channel cannot receive events.");
325
+ return channel.receive(input, operations);
326
+ },
279
327
  ...adapter === void 0 ? {} : { adapter: {
280
328
  ...adapter,
281
329
  fetchFile,
@@ -321,6 +369,24 @@ function gatedRoute(input) {
321
369
  resolveSession: args.resolveSession,
322
370
  from: args.from
323
371
  };
372
+ inbound.confirmationEvent = async (record, result, auth) => {
373
+ const address = record.data.address;
374
+ if (address === void 0) throw new Error("Confirmation has no owning session address.");
375
+ if ((await args.resolveSession(address))?.id !== record.data.sessionId) throw new Error("The confirmation session was replaced.");
376
+ return args.from(address).send(JSON.stringify({
377
+ type: "confirmation_result",
378
+ ...result
379
+ }), {
380
+ auth,
381
+ turnPolicy: "queue",
382
+ state: {
383
+ chatId: record.data.chatId,
384
+ chatType: record.data.chatId.startsWith("-") ? "supergroup" : "private",
385
+ messageThreadId: record.data.threadId ? Number(record.data.threadId) : null,
386
+ conversationId: null
387
+ }
388
+ });
389
+ };
324
390
  inbound.respond = (address, requestId, decision, auth, text) => args.from(scoped(address)).respond([{
325
391
  requestId,
326
392
  optionId: decision,
@@ -4,7 +4,7 @@ import { createRequire as __eveCreateRequire } from "node:module";
4
4
  const __filename = __eveFileURLToPath(import.meta.url);
5
5
  __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
- import { l as reportTelegramError } from "../../../_chunks/customer-text-DjszxgTo.mjs";
7
+ import { p as reportTelegramError } from "../../../_chunks/inbound-request-BwBvXk0A.mjs";
8
8
  import { describableImage } from "./images.mjs";
9
9
  import { MAXIMUM_ROUTING_PDF_CHARACTERS, extractRoutingPdf } from "./pdf-text.mjs";
10
10
  import { gateway, generateText } from "ai";
@@ -6,12 +6,12 @@ __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { appOrigin, channelsEnabled, proxyEnvironment } from "../environment.mjs";
8
8
  import "./principal.mjs";
9
- import { redactDiagnostic } from "../diagnostics.mjs";
10
- import { a as customerMessage, c as formattingRefused, i as customerCopy, l as reportTelegramError } from "../../../_chunks/customer-text-DjszxgTo.mjs";
11
9
  import { confirmationLocale } from "./confirmation-text.mjs";
10
+ import { redactDiagnostic } from "../diagnostics.mjs";
11
+ import { runtimeToken } from "../runtime-token.mjs";
12
12
  import { inTelegramSendQueue, telegramSendScope } from "./send-queue.mjs";
13
+ import { c as customerCopy, f as formattingRefused, l as customerMessage, p as reportTelegramError } from "../../../_chunks/inbound-request-BwBvXk0A.mjs";
13
14
  import { MAXIMUM_PROXY_BODY_BYTES, connectionProxy } from "../connection-proxy.mjs";
14
- import { runtimeToken } from "../runtime-token.mjs";
15
15
  import { ACT_AS_HEADER } from "../ontology-connection.mjs";
16
16
  import { AttachmentError, CONVERSATION_HEADER, SESSION_HEADER } from "../attachments.mjs";
17
17
  import { TELEGRAM_PLACEHOLDER_TOKEN, TELEGRAM_PROXY_ORIGIN, telegramProxyFetch } from "./proxy-fetch.mjs";
@@ -0,0 +1,4 @@
1
+ import type { TelegramSettings } from "../../config.js";
2
+ import { type DeferredConfirmation, type DeferredOptions } from "../deferred-confirmations.js";
3
+ /** A bounded sweep; concurrent webhooks/schedules compete for the same atomic expiry claim. */
4
+ export declare function expireScheduledConfirmations(connection: string, notify: (record: DeferredConfirmation, result: Readonly<Record<string, unknown>>) => Promise<unknown>, options?: DeferredOptions, settings?: TelegramSettings): Promise<void>;
@@ -0,0 +1,50 @@
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 { proxyEnvironment } from "../environment.mjs";
8
+ import { runtimeToken } from "../runtime-token.mjs";
9
+ import { deferredConfirmations } from "../deferred-confirmations.mjs";
10
+ import { conversations } from "../conversations.mjs";
11
+ import { telegramSendScope } from "./send-queue.mjs";
12
+ import { m as safeTelegramFetch, p as reportTelegramError } from "../../../_chunks/inbound-request-BwBvXk0A.mjs";
13
+ import { expireDeferredRecord } from "./deferred-confirmations.mjs";
14
+ import { connectionProxy } from "../connection-proxy.mjs";
15
+ import { TELEGRAM_PLACEHOLDER_TOKEN, TELEGRAM_PROXY_ORIGIN, telegramProxyFetch } from "./proxy-fetch.mjs";
16
+ import { telegramPreviewEnvironment } from "@segmentstream-cloud/ontology/telegram";
17
+ import { editTelegramMessageText } from "eve/channels/telegram";
18
+ async function expireScheduledConfirmations(connection, notify, options = {}, settings) {
19
+ const environment = options.environment ?? process.env;
20
+ const token = options.token ?? runtimeToken;
21
+ const archive = conversations({
22
+ environment,
23
+ token,
24
+ fetch: options.fetch
25
+ });
26
+ const scope = telegramSendScope(environment, connection);
27
+ const proxy = connectionProxy({
28
+ environment: proxyEnvironment(environment),
29
+ connection,
30
+ token,
31
+ fetch: options.fetch
32
+ });
33
+ const transport = safeTelegramFetch(telegramProxyFetch(proxy, scope), scope, {
34
+ settings,
35
+ ownOrigins: telegramPreviewEnvironment(environment).appOrigins
36
+ });
37
+ const records = await deferredConfirmations(options).command({ operation: "expired" });
38
+ for (const record of records) try {
39
+ await expireDeferredRecord(record, { telegram: { editMessageText: (input) => editTelegramMessageText({
40
+ apiBaseUrl: TELEGRAM_PROXY_ORIGIN,
41
+ credentials: { botToken: TELEGRAM_PLACEHOLDER_TOKEN },
42
+ fetch: transport,
43
+ chatId: record.data.chatId,
44
+ ...input
45
+ }) } }, options, archive, notify);
46
+ } catch (error) {
47
+ reportTelegramError("deferred_expiry_failed", error);
48
+ }
49
+ }
50
+ export { expireScheduledConfirmations };
@@ -1,3 +1,4 @@
1
+ import type { DeferredOptions } from "../deferred-confirmations.js";
1
2
  import type { ChannelEvents } from "eve/channels";
2
3
  import type { TelegramCallbackQuery, TelegramChannelEvents, TelegramContext, TelegramEventContext } from "eve/channels/telegram";
3
4
  import { type ObservedMessages, type PendingConfirmation, type RecentMessage } from "./observed.js";
@@ -23,5 +24,5 @@ export declare function handleConfirmationCallback(input: {
23
24
  readonly scope: "chat" | "thread";
24
25
  readonly now?: number;
25
26
  }): Promise<void>;
26
- export declare function telegramConfirmationEvents(archive?: Conversations, observed?: ObservedMessages): TelegramChannelEvents & Pick<ChannelEvents<TelegramEventContext>, "context.cleared">;
27
+ export declare function telegramConfirmationEvents(archive?: Conversations, observed?: ObservedMessages, deferredOptions?: DeferredOptions): TelegramChannelEvents & Pick<ChannelEvents<TelegramEventContext>, "context.cleared">;
27
28
  export declare const confirmationEvents: TelegramChannelEvents & Pick<ChannelEvents<TelegramEventContext>, "context.cleared">;
@@ -5,16 +5,17 @@ const __filename = __eveFileURLToPath(import.meta.url);
5
5
  __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { memberIdOf, telegramPrincipal } from "./principal.mjs";
8
- import { l as reportTelegramError, p as currentInboundRequest } from "../../../_chunks/customer-text-DjszxgTo.mjs";
9
8
  import { humanText } from "../confirmation-description.mjs";
10
9
  import { renderConfirmation, withConfirmationText } from "./confirmation-text.mjs";
11
- import { chatPostCard, chatPostCardStatus, chatPostReceipt, withChatPostCard } from "./chat-post-card.mjs";
12
10
  import { chatPostOperationId, isChatPost } from "../chat-post-confirmation.mjs";
11
+ import { chatPostCard, chatPostCardStatus, chatPostReceipt, withChatPostCard } from "./chat-post-card.mjs";
13
12
  import { confirmations } from "../confirmations.mjs";
14
13
  import { conversationEvent } from "../conversations.mjs";
14
+ import { n as currentInboundRequest, p as reportTelegramError } from "../../../_chunks/inbound-request-BwBvXk0A.mjs";
15
+ import { formatAddress, scopedAddress } from "./sessions.mjs";
15
16
  import { agentMessage, observedChatKey, withConfirmationPrompt } from "./observed.mjs";
17
+ import { postDeferredCard } from "./deferred-confirmations.mjs";
16
18
  import { answerMarker, isPendingQuestion, pendingQuestions, questionKey } from "../questions.mjs";
17
- import { formatAddress, scopedAddress } from "./sessions.mjs";
18
19
  import { telegramTextLinks } from "@segmentstream-cloud/ontology/telegram";
19
20
  import { createHash } from "node:crypto";
20
21
  import { registerTelegramFreeformPrompt, renderTelegramInputRequest } from "eve/channels/telegram";
@@ -59,7 +60,7 @@ function routingConfirmationState(events, prompt, at) {
59
60
  if (prompt.state !== void 0 && prompt.state !== "pending" && prompt.state !== "approved") return prompt.state;
60
61
  let state = prompt.state ?? "pending";
61
62
  let callId;
62
- for (const event of events) {
63
+ for (const event of prompt.mode === "deferred" ? [] : events) {
63
64
  const data = object(event.data);
64
65
  if ((event.type === "context.cleared" || event.type === "session.started") && Date.parse(event.meta.at) >= Date.parse(prompt.at)) state = "expired";
65
66
  if (event.type === "input.requested" && Array.isArray(data.requests)) {
@@ -191,7 +192,7 @@ function messagePrompt(message) {
191
192
  at: new Date().toISOString()
192
193
  };
193
194
  }
194
- function telegramConfirmationEvents(archive, observed) {
195
+ function telegramConfirmationEvents(archive, observed, deferredOptions) {
195
196
  const remember = (channel, ctx, messageId, text, confirmation) => {
196
197
  const principal = ctx.session.auth.current;
197
198
  const name = humanText(principal?.attributes.memberName);
@@ -389,6 +390,13 @@ function telegramConfirmationEvents(archive, observed) {
389
390
  },
390
391
  async "action.result"(data, channel, ctx) {
391
392
  const state = channel.state;
393
+ if (data.result.kind === "tool-result" && deferredOptions !== void 0 && archive !== void 0) {
394
+ const output = object(data.result.output);
395
+ if (output.status === "awaiting_confirmation" && typeof output.confirmation === "string") {
396
+ await postDeferredCard(output.confirmation, channel, ctx, deferredOptions, archive, observed);
397
+ return;
398
+ }
399
+ }
392
400
  if (data.result.kind === "tool-result") {
393
401
  const question = object(data.result.output).pendingQuestion;
394
402
  if (data.result.toolName.endsWith("__questions_ask") && isPendingQuestion(question)) {
@@ -4,8 +4,8 @@ import { createRequire as __eveCreateRequire } from "node:module";
4
4
  const __filename = __eveFileURLToPath(import.meta.url);
5
5
  __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
- import { p as currentInboundRequest, r as currentThreadId } from "../../../_chunks/customer-text-DjszxgTo.mjs";
8
7
  import { conversationEvent } from "../conversations.mjs";
8
+ import { n as currentInboundRequest, s as currentThreadId } from "../../../_chunks/inbound-request-BwBvXk0A.mjs";
9
9
  import { contentShape, messageContent } from "./content.mjs";
10
10
  import { currentConfirmationPrompt, messageMetadata, observedChatKey } from "./observed.mjs";
11
11
  import { archivedRoutingMessages } from "./routing-context.mjs";
@@ -5,5 +5,5 @@ const __filename = __eveFileURLToPath(import.meta.url);
5
5
  __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { isTechnicalText } from "../customer-text-safety.mjs";
8
- import { a as customerMessage, c as formattingRefused, d as withDeliveryLocale, i as customerCopy, l as reportTelegramError, n as currentLocale, o as customerText, r as currentThreadId, s as failureText, t as TelegramDeliveryError, u as safeTelegramFetch } from "../../../_chunks/customer-text-DjszxgTo.mjs";
8
+ import { a as TelegramDeliveryError, c as customerCopy, d as failureText, f as formattingRefused, h as withDeliveryLocale, l as customerMessage, m as safeTelegramFetch, o as currentLocale, p as reportTelegramError, s as currentThreadId, u as customerText } from "../../../_chunks/inbound-request-BwBvXk0A.mjs";
9
9
  export { TelegramDeliveryError, currentLocale, currentThreadId, customerCopy, customerMessage, customerText, failureText, formattingRefused, isTechnicalText, reportTelegramError, safeTelegramFetch, withDeliveryLocale };
@@ -0,0 +1,26 @@
1
+ import type { TelegramCallbackQuery, TelegramContext, TelegramHandle, TelegramEventContext } from "eve/channels/telegram";
2
+ import type { SessionContext } from "eve/context";
3
+ import { type DeferredConfirmation, type DeferredOptions } from "../deferred-confirmations.js";
4
+ import { type Conversations } from "../conversations.js";
5
+ import type { AgentIdentities } from "../identities.js";
6
+ import { type ObservedMessages } from "./observed.js";
7
+ export declare const isDeferredCallback: (data?: string) => boolean;
8
+ export declare const deferredCallback: (record: DeferredConfirmation, choice: "y" | "n" | "e") => string;
9
+ export declare function archiveDeferred(record: DeferredConfirmation, phase: string, archive: Conversations): Promise<void>;
10
+ /** Called at the durable tool-result boundary. Pending cards never become eve input requests. */
11
+ export declare function postDeferredCard(id: string, channel: TelegramEventContext, ctx: SessionContext, options: DeferredOptions, archive: Conversations, observed?: ObservedMessages): Promise<void>;
12
+ /** Every callback re-resolves the live identity. The database arbitrates concurrent presses. */
13
+ export declare function handleDeferredCallback(input: {
14
+ readonly context: TelegramContext;
15
+ readonly query: TelegramCallbackQuery;
16
+ readonly identities: AgentIdentities;
17
+ readonly archive: Conversations;
18
+ readonly options: DeferredOptions;
19
+ readonly observed?: ObservedMessages;
20
+ }): Promise<void>;
21
+ /** A database-clock expiry claim is shared by webhooks and the minute schedule. */
22
+ export declare function expireDeferredRecord(record: DeferredConfirmation, context: {
23
+ readonly telegram: Pick<TelegramHandle, "editMessageText">;
24
+ }, options: DeferredOptions, archive: Conversations, notify: NonNullable<import("./inbound-request.js").InboundRequest["confirmationEvent"]>, observed?: ObservedMessages): Promise<void>;
25
+ /** Webhooks also reconcile expiry if the host's minute scheduler is delayed. */
26
+ export declare function expireDeferredCards(context: TelegramContext, options: DeferredOptions, archive: Conversations, observed?: ObservedMessages): Promise<void>;