@segmentstream/agent 0.17.16 → 0.17.17

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.
package/README.md CHANGED
@@ -748,3 +748,24 @@ Workspace [callback actions](../../docs/agent.md#workspace-callback-actions) and
748
748
  mappings. They require the ontology runtime-source capability and dispatch only a currently
749
749
  permitted routine action as the verified member. The workspace owns callback tokens, presentation
750
750
  and business idempotency.
751
+
752
+ ### Durable typing
753
+
754
+ The webhook monitor ends with its host request. For turns that can outlive that
755
+ request, an application's authored Workflow can call `telegramTypingBlock` from
756
+ `@segmentstream/agent/extension` in a `"use step"` helper. Each activity runs for
757
+ at most about thirty seconds, follows a bounded cursor through one session's
758
+ durable stream, and sends only `sendChatAction` through the declared connection.
759
+ It stops for its source turn's completion, failure, cancellation or input wait,
760
+ or when the source session is terminal. Pass an exact `turnId` from a real
761
+ `turn.started` hook, or a unique `marker` in a private acceptance message.
762
+
763
+ The application owns the Workflow directives: Eve does not permit them in an
764
+ SDK extension. Its step reads `getRun(source.sessionId).status` through
765
+ `workflow/api` and calls `telegramTypingBlock(source, cursor, status)`. Its
766
+ workflow carries the returned cursor between activities and ends when `done` is
767
+ true. Start a continuation periodically to bound the workflow history, then
768
+ retire the previous monitor. `recordTelegramTypingStart(source, workflowRunId)`
769
+ records the monitor reference in the conversation archive. Source arguments
770
+ contain transport coordinates and a member ID, never credentials or message
771
+ contents. Repeated typing actions are idempotent; the activity never posts text.
@@ -4,7 +4,7 @@ import { createRequire as __eveCreateRequire } from "node:module";
4
4
  const __filename = __eveFileURLToPath(import.meta.url);
5
5
  __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
- var version = "0.17.16";
7
+ var version = "0.17.17";
8
8
  function agentBuild(environment = process.env) {
9
9
  return {
10
10
  agent: version,
@@ -31,3 +31,4 @@ export { replayRouterPayload } from "./lib/group-routing/replay.js";
31
31
  export { conversationState as groupConversationState, conversationAddressing as groupConversationAddressing, type ConversationAnalysis, type ConversationPath, } from "./lib/group-routing/conversation.js";
32
32
  export { routingBurst, debounceBurst } from "./lib/group-routing/burst.js";
33
33
  export type { TelegramTexts } from "./lib/telegram/texts.js";
34
+ export { telegramTypingBlock, recordTelegramTypingStart, type TelegramTypingSource, type TypingCursor, type TypingBlockResult, } from "./lib/telegram/durable-typing.js";
@@ -14,10 +14,11 @@ import { agentMessage, observedMessages } from "./lib/group-routing/observed.mjs
14
14
  import { archivedRoutingMessages } from "./lib/group-routing/routing-context.mjs";
15
15
  import { replayRouterPayload } from "./lib/group-routing/replay.mjs";
16
16
  import { debounceBurst, routingBurst } from "./lib/group-routing/burst.mjs";
17
+ import { recordTelegramTypingStart, telegramTypingBlock } from "./lib/telegram/durable-typing.mjs";
17
18
  import { defineExtension } from "eve/extension";
18
19
  const extension = defineExtension({ config: configSchema });
19
20
  const segmentstream = Object.assign(extension, {
20
21
  model: (choice) => model(choice ?? (() => extension.config.models.chat)),
21
22
  manifestOf
22
23
  });
23
- export { DEFAULT_ADMISSION_LIMITS, DEFAULT_DECISION_MODEL, DEFAULT_GROUP_POLICY, DEFAULT_STT_MODEL, SegmentStreamAgentConfigError, archivedRoutingMessages, chooseModel, conversationKey, debounceBurst, segmentstream as default, segmentstream, effectiveGroupPolicy, addressingReason as groupAddressingReason, agentMessage as groupAgentMessage, conversationAddressing as groupConversationAddressing, conversationState as groupConversationState, decisionRouter as groupDecisionRouter, observedMessages as groupObservedMessages, groupPolicy, manifestOf, model, replayRouterPayload, routeMessage as routeGroupMessage, routingBurst };
24
+ export { DEFAULT_ADMISSION_LIMITS, DEFAULT_DECISION_MODEL, DEFAULT_GROUP_POLICY, DEFAULT_STT_MODEL, SegmentStreamAgentConfigError, archivedRoutingMessages, chooseModel, conversationKey, debounceBurst, segmentstream as default, segmentstream, effectiveGroupPolicy, addressingReason as groupAddressingReason, agentMessage as groupAgentMessage, conversationAddressing as groupConversationAddressing, conversationState as groupConversationState, decisionRouter as groupDecisionRouter, observedMessages as groupObservedMessages, groupPolicy, manifestOf, model, recordTelegramTypingStart, replayRouterPayload, routeMessage as routeGroupMessage, routingBurst, telegramTypingBlock };
@@ -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-D7rtiSxQ.mjs";
7
+ import { t as agentBuild } from "../../_chunks/build-metadata-9SHckxvW.mjs";
8
8
  export { agentBuild };
@@ -18,16 +18,16 @@ import { chooseModel } from "../../model.mjs";
18
18
  import { groupRoutingHealth } from "../group-routing/health.mjs";
19
19
  import { decisionRouter } from "../group-routing/router.mjs";
20
20
  import { archivedRoutingMessages } from "../group-routing/routing-context.mjs";
21
+ import { MAXIMUM_DOWNLOAD_BYTES, connectionProxy } from "../connection-proxy.mjs";
21
22
  import { dispatchStructuredMessage, structuredMessage } from "./structured.mjs";
22
23
  import { scopedAddress, sessionPolicy } from "./sessions.mjs";
23
24
  import { handleWorkspaceCallback, isWorkspaceCallback } from "./callbacks.mjs";
24
- import { t as agentBuild } from "../../../_chunks/build-metadata-D7rtiSxQ.mjs";
25
+ import { t as agentBuild } from "../../../_chunks/build-metadata-9SHckxvW.mjs";
25
26
  import { attachmentLifecycle } from "./lifecycle.mjs";
26
27
  import { observedMessages } from "./observed.mjs";
27
28
  import { expireDeferredCards, handleDeferredCallback, isDeferredCallback } from "./deferred-confirmations.mjs";
28
29
  import { gatewayImageDescriber } from "./images.mjs";
29
30
  import { gatewayAttachmentDescriber } from "./attachment-description.mjs";
30
- import { MAXIMUM_DOWNLOAD_BYTES, connectionProxy } from "../connection-proxy.mjs";
31
31
  import { agentAttachments, parseAttachmentUrl } from "../attachments.mjs";
32
32
  import { agentIdentities } from "../identities.mjs";
33
33
  import { interimTexts, replyDelivery } from "./silent.mjs";
@@ -10,9 +10,9 @@ import { telegramSendScope } from "./send-queue.mjs";
10
10
  import { runtimeToken } from "../runtime-token.mjs";
11
11
  import { conversations } from "../conversations.mjs";
12
12
  import { m as safeTelegramFetch, p as reportTelegramError } from "../../../_chunks/inbound-request-Buuv7b1I.mjs";
13
+ import { connectionProxy } from "../connection-proxy.mjs";
13
14
  import { deferredConfirmations } from "../deferred-confirmations.mjs";
14
15
  import { expireDeferredRecord } from "./deferred-confirmations.mjs";
15
- import { connectionProxy } from "../connection-proxy.mjs";
16
16
  import { TELEGRAM_PLACEHOLDER_TOKEN, TELEGRAM_PROXY_ORIGIN, telegramProxyFetch } from "./proxy-fetch.mjs";
17
17
  import { telegramPreviewEnvironment } from "@segmentstream-cloud/ontology/telegram";
18
18
  import { editTelegramMessageText } from "eve/channels/telegram";
@@ -0,0 +1,37 @@
1
+ /** Transport coordinates from an admitted turn; never a token or provider credential. */
2
+ export interface TelegramTypingSource {
3
+ readonly sessionId: string;
4
+ readonly marker?: string;
5
+ readonly turnId?: string;
6
+ readonly connection: string;
7
+ readonly chatId: string;
8
+ readonly threadId?: number;
9
+ readonly memberId: string;
10
+ }
11
+ export interface TypingCursor {
12
+ readonly index: number;
13
+ readonly turnId: string | null;
14
+ }
15
+ interface TypingEvent {
16
+ readonly type: string;
17
+ readonly data?: unknown;
18
+ }
19
+ interface TypingBlockDependencies {
20
+ readonly events: (index: number, signal: AbortSignal) => AsyncIterable<TypingEvent>;
21
+ readonly status: () => Promise<string>;
22
+ readonly send: () => Promise<boolean>;
23
+ readonly now: () => number;
24
+ readonly pause: (milliseconds: number) => Promise<void>;
25
+ }
26
+ export interface TypingBlockResult {
27
+ readonly cursor: TypingCursor;
28
+ readonly done: boolean;
29
+ readonly reason: string;
30
+ readonly sent: number;
31
+ }
32
+ /** One bounded activity. Its durable parent continues independently of the webhook host. */
33
+ export declare function runTypingBlock(source: TelegramTypingSource, cursor: TypingCursor, dependencies: TypingBlockDependencies): Promise<TypingBlockResult>;
34
+ export declare function telegramTypingBlock(source: TelegramTypingSource, cursor: TypingCursor, sessionStatus: string): Promise<TypingBlockResult>;
35
+ /** Link the durable monitor to the source turn without any customer-visible message. */
36
+ export declare function recordTelegramTypingStart(source: TelegramTypingSource, workflowRunId: string): Promise<void>;
37
+ export {};
@@ -0,0 +1,178 @@
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 { channelsEnabled, proxyEnvironment } from "../environment.mjs";
8
+ import { redactTelegramDiagnostic } from "./diagnostic-redaction.mjs";
9
+ import { runtimeToken } from "../runtime-token.mjs";
10
+ import { conversationEvent, conversations } from "../conversations.mjs";
11
+ import { p as reportTelegramError } from "../../../_chunks/inbound-request-Buuv7b1I.mjs";
12
+ import { connectionProxy } from "../connection-proxy.mjs";
13
+ import { sessions } from "eve/server";
14
+ const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
15
+ async function runTypingBlock(source, cursor, dependencies) {
16
+ const deadline = dependencies.now() + 3e4;
17
+ let index = cursor.index, turnId = cursor.turnId ?? source.turnId ?? null, sent = 0;
18
+ const result = (done, reason) => ({
19
+ cursor: {
20
+ index,
21
+ turnId
22
+ },
23
+ done,
24
+ reason,
25
+ sent
26
+ });
27
+ if ([
28
+ "completed",
29
+ "failed",
30
+ "cancelled"
31
+ ].includes(await dependencies.status())) return result(true, "session_terminal");
32
+ while (dependencies.now() < deadline) {
33
+ const started = dependencies.now();
34
+ const controller = new AbortController();
35
+ const iterator = dependencies.events(index, controller.signal)[Symbol.asyncIterator]();
36
+ let timer;
37
+ const expired = new Promise((resolve) => {
38
+ timer = setTimeout(() => {
39
+ resolve(null);
40
+ controller.abort();
41
+ }, Math.min(1500, Math.max(0, deadline - dependencies.now())));
42
+ });
43
+ try {
44
+ while (dependencies.now() < deadline) {
45
+ const next = await Promise.race([iterator.next(), expired]);
46
+ if (next === null) return result(false, "history_read_timeout");
47
+ if (next.done) break;
48
+ const event = next.value;
49
+ index += 1;
50
+ const data = object(event.data) ? event.data : {};
51
+ if (event.type === "message.received" && typeof data.message === "string" && typeof source.marker === "string" && source.marker !== "" && data.message.includes(source.marker) && typeof data.turnId === "string") turnId = data.turnId;
52
+ if (["session.failed", "session.completed"].includes(event.type)) return result(true, event.type);
53
+ if (turnId !== null && data.turnId === turnId && ([
54
+ "turn.completed",
55
+ "turn.failed",
56
+ "turn.cancelled",
57
+ "input.requested"
58
+ ].includes(event.type) || event.type === "turn.waiting" && data.on === "input")) return result(true, event.type);
59
+ }
60
+ } finally {
61
+ clearTimeout(timer);
62
+ controller.abort();
63
+ iterator.return?.().catch(() => {});
64
+ }
65
+ if (dependencies.now() >= deadline) return result(false, "continue");
66
+ if (turnId !== null) {
67
+ if (await dependencies.send()) sent += 1;
68
+ }
69
+ await dependencies.pause(Math.max(0, 2e3 - (dependencies.now() - started)));
70
+ }
71
+ return result(false, "continue");
72
+ }
73
+ async function telegramTypingBlock(source, cursor, sessionStatus) {
74
+ try {
75
+ if (!channelsEnabled()) return {
76
+ cursor,
77
+ done: true,
78
+ reason: "channels_disabled",
79
+ sent: 0
80
+ };
81
+ const proxy = connectionProxy({
82
+ environment: proxyEnvironment(),
83
+ connection: source.connection,
84
+ token: runtimeToken
85
+ });
86
+ const result = await runTypingBlock(source, cursor, {
87
+ status: async () => sessionStatus,
88
+ events: (index, signal) => sessions.attach(source.sessionId).stream({
89
+ startIndex: index,
90
+ follow: false,
91
+ signal
92
+ }),
93
+ now: Date.now,
94
+ pause: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
95
+ send: async () => {
96
+ try {
97
+ const response = await proxy.request({
98
+ api: "bot",
99
+ method: "POST",
100
+ path: "/sendChatAction",
101
+ headers: [["content-type", "application/json"]],
102
+ body: Buffer.from(JSON.stringify({
103
+ chat_id: source.chatId,
104
+ action: "typing",
105
+ ...source.threadId === void 0 ? {} : { message_thread_id: source.threadId }
106
+ }))
107
+ }, AbortSignal.timeout(5e3));
108
+ const body = JSON.parse(Buffer.from(response.body).toString("utf8"));
109
+ const accepted = response.status >= 200 && response.status < 300 && object(body) && body.ok === true;
110
+ console.info("[segmentstream] telegram.method", {
111
+ method: "sendChatAction",
112
+ at: new Date().toISOString(),
113
+ chatId: source.chatId,
114
+ status: response.status,
115
+ accepted,
116
+ sessionId: source.sessionId,
117
+ durable: true
118
+ });
119
+ if (!accepted) throw new Error("Telegram rejected sendChatAction.", { cause: {
120
+ status: response.status,
121
+ body
122
+ } });
123
+ return true;
124
+ } catch (error) {
125
+ reportTelegramError("typing_failed", error, {
126
+ chatId: source.chatId,
127
+ sessionId: source.sessionId,
128
+ durable: true
129
+ });
130
+ return false;
131
+ }
132
+ }
133
+ });
134
+ console.info("[segmentstream] telegram.typing_block", {
135
+ sessionId: source.sessionId,
136
+ turnId: result.cursor.turnId,
137
+ cursor: result.cursor.index,
138
+ done: result.done,
139
+ reason: result.reason,
140
+ sent: result.sent
141
+ });
142
+ return result;
143
+ } catch (error) {
144
+ reportTelegramError("typing_monitor_failed", error, {
145
+ sessionId: source.sessionId,
146
+ durable: true
147
+ });
148
+ throw new Error("Durable typing monitor failed.", { cause: redactTelegramDiagnostic(error) });
149
+ }
150
+ }
151
+ async function recordTelegramTypingStart(source, workflowRunId) {
152
+ console.info("[segmentstream] telegram.typing_started", {
153
+ sessionId: source.sessionId,
154
+ workflowRunId
155
+ });
156
+ try {
157
+ await conversations().append(conversationEvent({
158
+ provider: "telegram",
159
+ chatId: source.chatId,
160
+ chatType: source.chatId.startsWith("-") ? "group" : "private",
161
+ threadId: source.threadId === void 0 ? null : String(source.threadId),
162
+ eventId: `telegram:typing:${workflowRunId}`,
163
+ sessionId: source.sessionId,
164
+ turnId: source.turnId ?? null,
165
+ participantIds: [source.memberId],
166
+ kind: "decision",
167
+ decision: "silent",
168
+ reason: "typing_monitor_started",
169
+ argsSummary: JSON.stringify({ workflowRunId })
170
+ }));
171
+ } catch (error) {
172
+ reportTelegramError("typing_monitor_recording_failed", error, {
173
+ sessionId: source.sessionId,
174
+ workflowRunId
175
+ });
176
+ }
177
+ }
178
+ export { recordTelegramTypingStart, runTypingBlock, telegramTypingBlock };
@@ -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 { t as agentBuild } from "../../../_chunks/build-metadata-D7rtiSxQ.mjs";
7
+ import { t as agentBuild } from "../../../_chunks/build-metadata-9SHckxvW.mjs";
8
8
  import { runtimeRequestFailure } from "./runtime-request.mjs";
9
9
  import { GET } from "eve/channels";
10
10
  const HEALTH_ROUTE = "/eve/v1/telegram/health";
@@ -9,7 +9,7 @@ import { object } from "../confirmation-description.mjs";
9
9
  import { n as currentInboundRequest, p as reportTelegramError } from "../../../_chunks/inbound-request-Buuv7b1I.mjs";
10
10
  import { needsConfirmation } from "../confirmations.mjs";
11
11
  import { OntologyActionNotPermitted, actionToolName, ontologyTools } from "../ontology-tools.mjs";
12
- import { t as agentBuild } from "../../../_chunks/build-metadata-D7rtiSxQ.mjs";
12
+ import { t as agentBuild } from "../../../_chunks/build-metadata-9SHckxvW.mjs";
13
13
  var AttachmentLifecycleNotPermitted = class extends Error {
14
14
  code = "attachment_lifecycle_not_permitted";
15
15
  };
@@ -12,7 +12,9 @@ import { readFile } from "eve/tools/read_file";
12
12
  import { writeFile } from "eve/tools/write_file";
13
13
  function gated(tool, description = tool.description) {
14
14
  return defineTool({
15
- ...tool,
15
+ inputSchema: tool.inputSchema,
16
+ outputSchema: tool.outputSchema,
17
+ label: tool.label,
16
18
  description,
17
19
  async execute(input, ctx) {
18
20
  await sessionSandbox(ctx);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@segmentstream/agent",
3
- "version": "0.17.16",
3
+ "version": "0.17.17",
4
4
  "description": "Mount a SegmentStream workspace's agent on eve: the ontology as tools, acting as the member who writes, and Telegram through the workspace's connection.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "SegmentStream",