@segmentstream/agent 0.25.4 → 0.25.6

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
@@ -717,12 +717,15 @@ See [migration and verification](../../docs/agent-chat-delivery-migration.md).
717
717
 
718
718
  If Eve's session lookup raises `RunKeyFetchError` with HTTP 404 and Vercel's
719
719
  `not_found` / `Deployment not found.` response, the Telegram adapter records the
720
- original diagnostic and uses a fresh continuation scoped to the serving deployment.
721
- The smart gate still decides whether to reply; observing a message creates no session
722
- and sends no failure notice. Independent requests and cold starts select the same
723
- replacement. Healthy sessions continue across deployments as before. Once an address
724
- needs recovery, later deployments start it fresh; archived conversation context remains
720
+ original diagnostic and continues the address at a successor named by the address and
721
+ the unreachable run. The smart gate still decides whether to reply; observing a message
722
+ creates no session and sends no failure notice. Every deployment, independent request and
723
+ cold start selects the same successor, and Eve hands it over between deployments like any
724
+ healthy session. If the successor's own deployment is deleted later, the next successor
725
+ follows the same rule, up to four in a row. Archived conversation context remains
725
726
  available, but the inaccessible session's model history and pending inputs cannot migrate.
727
+ `segmentstream history squash` deletes earlier deployments and so ends the sessions
728
+ they own; each affected address continues once at its successor.
726
729
 
727
730
  Recovery applies only to session reads. Dispatch failures are never retried because
728
731
  the inbox may already have accepted them. Other provider errors propagate to operator
@@ -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.25.4";
7
+ var version = "0.25.6";
8
8
  function agentBuild(environment = process.env) {
9
9
  return {
10
10
  agent: version,
@@ -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-CInUGTLd.mjs";
7
+ import { t as agentBuild } from "../../_chunks/build-metadata-C_OoLu9T.mjs";
8
8
  export { agentBuild };
@@ -24,7 +24,7 @@ import { MAXIMUM_DOWNLOAD_BYTES, connectionProxy } from "../connection-proxy.mjs
24
24
  import { dispatchStructuredMessage, structuredMessage } from "./structured.mjs";
25
25
  import { scopedAddress, sessionPolicy } from "./sessions.mjs";
26
26
  import { handleWorkspaceCallback, isWorkspaceCallback } from "./callbacks.mjs";
27
- import { t as agentBuild } from "../../../_chunks/build-metadata-CInUGTLd.mjs";
27
+ import { t as agentBuild } from "../../../_chunks/build-metadata-C_OoLu9T.mjs";
28
28
  import { attachmentLifecycle } from "./lifecycle.mjs";
29
29
  import { gatewayImageDescriber } from "./images.mjs";
30
30
  import { gatewayAttachmentDescriber } from "./attachment-description.mjs";
@@ -446,7 +446,7 @@ function segmentstreamTelegram(options) {
446
446
  return {
447
447
  ...channel,
448
448
  async receive(input, operations) {
449
- operations = recoverableSessions(operations, environment.VERCEL_DEPLOYMENT_ID, readHistory);
449
+ operations = recoverableSessions(operations, readHistory);
450
450
  const target = input.target;
451
451
  if (target.confirmationSessionId !== void 0) {
452
452
  const address = target.confirmationAddress;
@@ -516,7 +516,7 @@ function gatedRoute(input) {
516
516
  if (options.settings() === null) return new Response("not found", { status: 404 });
517
517
  args = {
518
518
  ...args,
519
- ...recoverableSessions(args, environment.VERCEL_DEPLOYMENT_ID, input.readHistory)
519
+ ...recoverableSessions(args, input.readHistory)
520
520
  };
521
521
  let description;
522
522
  const describe = () => description ??= args.describe();
@@ -11,6 +11,8 @@ export interface TelegramTypingSource {
11
11
  export interface TypingCursor {
12
12
  readonly index: number;
13
13
  readonly turnId: string | null;
14
+ /** When the monitor first ran, in epoch milliseconds; continuations carry it forward. */
15
+ readonly startedAt?: number;
14
16
  }
15
17
  interface TypingEvent {
16
18
  readonly type: string;
@@ -40,6 +42,8 @@ export interface TypingBlockResult {
40
42
  readonly reason: string;
41
43
  readonly sent: number;
42
44
  }
45
+ /** The longest a turn shows typing; a longer turn keeps working without the indicator. */
46
+ export declare const MAX_TYPING_MS: number;
43
47
  /** One bounded activity. Its durable parent continues independently of the webhook host. */
44
48
  export declare function runTypingBlock(source: TelegramTypingSource, cursor: TypingCursor, dependencies: TypingBlockDependencies): Promise<TypingBlockResult>;
45
49
  export declare function telegramTypingBlock(source: TelegramTypingSource, cursor: TypingCursor, sessionStatus: string): Promise<TypingBlockResult>;
@@ -12,18 +12,22 @@ import { p as reportTelegramError } from "../../../_chunks/inbound-request-xmrm-
12
12
  import { connectionProxy } from "../connection-proxy.mjs";
13
13
  import { sessions } from "eve/server";
14
14
  const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
15
+ const MAX_TYPING_MS = 18e5;
15
16
  async function runTypingBlock(source, cursor, dependencies) {
16
17
  const deadline = dependencies.now() + 3e4;
18
+ const startedAt = cursor.startedAt ?? dependencies.now();
17
19
  let index = cursor.index, turnId = cursor.turnId ?? source.turnId ?? null, sent = 0;
18
20
  const result = (done, reason) => ({
19
21
  cursor: {
20
22
  index,
21
- turnId
23
+ turnId,
24
+ startedAt
22
25
  },
23
26
  done,
24
27
  reason,
25
28
  sent
26
29
  });
30
+ if (dependencies.now() - startedAt >= 18e5) return result(true, "typing_expired");
27
31
  if ([
28
32
  "completed",
29
33
  "failed",
@@ -175,4 +179,4 @@ async function recordTelegramTypingStart(source, workflowRunId) {
175
179
  });
176
180
  }
177
181
  }
178
- export { recordTelegramTypingStart, runTypingBlock, telegramTypingBlock };
182
+ export { MAX_TYPING_MS, recordTelegramTypingStart, runTypingBlock, telegramTypingBlock };
@@ -5,7 +5,7 @@ const __filename = __eveFileURLToPath(import.meta.url);
5
5
  __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { runtimeRequestFailure } from "./runtime-request.mjs";
8
- import { t as agentBuild } from "../../../_chunks/build-metadata-CInUGTLd.mjs";
8
+ import { t as agentBuild } from "../../../_chunks/build-metadata-C_OoLu9T.mjs";
9
9
  import { GET } from "eve/channels";
10
10
  const HEALTH_ROUTE = "/eve/v1/telegram/health";
11
11
  function healthRoute(input) {
@@ -10,7 +10,7 @@ import { n as currentInboundRequest, p as reportTelegramError } from "../../../_
10
10
  import { member } from "../actor.mjs";
11
11
  import { needsConfirmation } from "../confirmations.mjs";
12
12
  import { OntologyActionNotPermitted, actionToolName, ontologyTools } from "../ontology-tools.mjs";
13
- import { t as agentBuild } from "../../../_chunks/build-metadata-CInUGTLd.mjs";
13
+ import { t as agentBuild } from "../../../_chunks/build-metadata-C_OoLu9T.mjs";
14
14
  var AttachmentLifecycleNotPermitted = class extends Error {
15
15
  code = "attachment_lifecycle_not_permitted";
16
16
  };
@@ -2,9 +2,10 @@ import type { ChannelReceiveContext } from "eve/channels";
2
2
  import type { HistoryReader } from "./sessions.js";
3
3
  /**
4
4
  * A deleted deployment can make the Workflow metadata key unreadable before Eve can hand off a session.
5
- * Recover on a read, before dispatch: independent requests on this deployment use the same fresh
6
- * continuation. Healthy addresses keep their owner across deploys. An already broken address
7
- * gets a fresh owner on each deployment; the archive remains the source of routing context.
5
+ * Eve cannot reset that owner, so the address continues at a successor named by the address and the
6
+ * unreachable run. Every deployment derives the same successor, and Eve hands it over between
7
+ * deployments like any healthy session; if its own deployment is later deleted, the next successor
8
+ * follows the same rule. Recover on a read, before dispatch. Healthy addresses keep their owner.
8
9
  * No send is retried: a dispatch error can mean the durable inbox already accepted the message.
9
10
  */
10
- export declare function recoverableSessions<State>(operations: ChannelReceiveContext<State>, deploymentId: string | undefined, readHistory?: HistoryReader): ChannelReceiveContext<State>;
11
+ export declare function recoverableSessions<State>(operations: ChannelReceiveContext<State>, readHistory?: HistoryReader): ChannelReceiveContext<State>;
@@ -6,17 +6,18 @@ __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { p as reportTelegramError } from "../../../_chunks/inbound-request-xmrm-49O.mjs";
8
8
  import { createHash } from "node:crypto";
9
- function missingDeployment(error) {
10
- if (!(error instanceof Error) || error.name !== "RunKeyFetchError" || !("status" in error) || error.status !== 404) return false;
11
- const body = /^Failed to fetch run key for wrun_[\w-]+ \(deployment dpl_[\w-]+\): HTTP 404 [^\n]+? — (.+)$/u.exec(error.message)?.[1];
9
+ function strandedRun(error) {
10
+ if (!(error instanceof Error) || error.name !== "RunKeyFetchError" || !("status" in error) || error.status !== 404) return;
11
+ const match = /^Failed to fetch run key for (wrun_[\w-]+) \(deployment dpl_[\w-]+\): HTTP 404 [^\n]+? — (.+)$/u.exec(error.message);
12
12
  try {
13
- const response = JSON.parse(body ?? "");
14
- return response?.error?.code === "not_found" && response.error.message === "Deployment not found.";
13
+ const response = JSON.parse(match?.[2] ?? "");
14
+ return response?.error?.code === "not_found" && response.error.message === "Deployment not found." ? match?.[1] : void 0;
15
15
  } catch {
16
- return false;
16
+ return;
17
17
  }
18
18
  }
19
- function recoverableSessions(operations, deploymentId, readHistory) {
19
+ const MAX_SUCCESSORS = 4;
20
+ function recoverableSessions(operations, readHistory) {
20
21
  const replacements = new Map();
21
22
  const readable = new Map();
22
23
  const target = (address) => replacements.get(address) ?? address;
@@ -33,18 +34,21 @@ function recoverableSessions(operations, deploymentId, readHistory) {
33
34
  return session;
34
35
  }
35
36
  async function resolveSession(address) {
36
- try {
37
- return await lookup(target(address));
37
+ let current = target(address);
38
+ for (let successors = 0;; successors += 1) try {
39
+ return await lookup(current);
38
40
  } catch (error) {
39
- if (!deploymentId || replacements.has(address) || !missingDeployment(error)) throw error;
40
- const replacement = `${address}:recovery:${createHash("sha256").update(JSON.stringify([address, deploymentId])).digest("hex")}`;
41
+ const run = successors < MAX_SUCCESSORS ? strandedRun(error) : void 0;
42
+ if (run === void 0) throw error;
43
+ const digest = createHash("sha256").update(JSON.stringify([current, run])).digest("hex");
44
+ const replacement = `${current}:recovery:${digest}`;
41
45
  reportTelegramError("session_resume_recovered", error, {
42
- address,
46
+ address: current,
43
47
  replacement,
44
- deploymentId
48
+ strandedRun: run
45
49
  });
46
50
  replacements.set(address, replacement);
47
- return lookup(replacement);
51
+ current = replacement;
48
52
  }
49
53
  }
50
54
  async function source(address) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@segmentstream/agent",
3
- "version": "0.25.4",
3
+ "version": "0.25.6",
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",
@@ -74,7 +74,7 @@
74
74
  },
75
75
  "dependencies": {
76
76
  "@chat-adapter/telegram": "4.41.0",
77
- "@segmentstream-cloud/ontology": "0.79.4",
77
+ "@segmentstream-cloud/ontology": "0.80.0",
78
78
  "@vercel/oidc": "^3.8.4",
79
79
  "ai": "^7.0.137",
80
80
  "csv-parse": "^7.0.3",