@segmentstream/agent 0.17.0 → 0.17.2

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
@@ -209,7 +209,7 @@ continue using GET lookup. Starter templates and pins remain unchanged in this s
209
209
  | `segmentstream__attachments_send` | `tools` | Sends readable stored ontology files into the current Telegram chat and topic. |
210
210
  | `segmentstream__bash`, `_read_file`, `_write_file` | `tools` | With `"sandbox": true`: eve's file tools, only in the sandbox `@segmentstream/agent/sandbox` opened. |
211
211
  | platform rules | `instructions` | Changes go through actions; `describe` indexes or inspects refs; `discover` searches metadata; tools are already narrowed; when to answer `[SILENT]`; how to handle attachments |
212
- | sandbox rules | `instructions` | With `"sandbox": true`: working copies, file contents as data, numbers from script output or `arguments_file`, no network |
212
+ | sandbox rules | `instructions` | With `"sandbox": true`: working copies, file contents as data, script values unchanged through `arguments_file`, no network |
213
213
  | sandbox cleanup | `hooks` | Deletes the session's sandbox when its history is cleared, such as by `sessions.idleResetHours`. |
214
214
 
215
215
  ### Sandbox
@@ -228,10 +228,13 @@ The selector opens each session's sandbox with `networkPolicy: "deny-all"` and 2
228
228
  opening in session state. The file tools pass a gate first: the marker must name the current
229
229
  session, or the call fails before eve's executor runs, so an agent without `sandbox.ts` never gets
230
230
  a shell in eve's default sandbox, whose network is open. The first call of a turn replaces a sandbox
231
- older than 24 hours through its handle, carrying over the attachments eve staged in the last 24
232
- hours. The cleanup hook deletes a sandbox only when the session recorded opening one, so it never
233
- opens a sandbox to delete it; `compaction.completed` is ignored, because eve also compacts mid-turn.
234
- eve's session-end cleanup deletes whatever remains.
231
+ older than 24 hours, or one without a marker, through its handle, carrying over the attachments eve
232
+ staged in the last 24 hours. eve keeps a session's sandbox across deployments, so a session that
233
+ opened one before the agent adopted `@segmentstream/agent/sandbox` gets the SDK's on its first
234
+ file-tool call; a reopened sandbox without the marker fails the call, and another session's marker
235
+ fails it without deleting anything. The cleanup hook deletes a sandbox only when the session
236
+ recorded opening one, so it never opens a sandbox to delete it; `compaction.completed` is ignored,
237
+ because eve also compacts mid-turn. eve's session-end cleanup deletes whatever remains.
235
238
 
236
239
  `segmentstream__attachments_promote` takes `path`, a file inside `/workspace`, instead of `id`
237
240
  while the sandbox is on. The runtime reads it through the gate, refusing a real path outside
@@ -612,7 +615,7 @@ Eve resolution, so member/topic routing survives a cold start. No second provide
612
615
 
613
616
  ## Release and migration
614
617
 
615
- The coordinated release uses Agent SDK 0.17.0, Eve 0.76.3, Ontology SDK 0.53.0 and App SDK 0.25.0.
618
+ The coordinated release uses Agent SDK 0.17.2, Eve 0.76.3, Ontology SDK 0.53.0 and App SDK 0.25.0.
616
619
  The compatible Cloud archive/API must be deployed first; migrations run only through its CI/CD gate.
617
620
  Publish the ontology SDK before the agent SDK. Upgrade the workspace ontology before its agent,
618
621
  with the declared Telegram provider mapping and explicit `agent_chat:post_message` grant.
@@ -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.0";
7
+ var version = "0.17.2";
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-DR4gACLZ.mjs";
7
+ import { t as agentBuild } from "../../_chunks/build-metadata-DeXkvn8O.mjs";
8
8
  export { agentBuild };
@@ -16,6 +16,8 @@ const cache = new Map();
16
16
  let cachedBytes = 0;
17
17
  const CACHE_BYTES = 8388608;
18
18
  const CACHE_TTL = 3e4;
19
+ const RUNTIME_SOURCE_TTL = 3e5;
20
+ const METADATA_TIMEOUT = 1e4;
19
21
  function remove(key) {
20
22
  cachedBytes -= cache.get(key)?.bytes ?? 0;
21
23
  cache.delete(key);
@@ -56,8 +58,14 @@ async function readOntologyMetadata(memberId, request, context, options = {}) {
56
58
  if (scope !== void 0 && principal) {
57
59
  if (!state || !isDeepStrictEqual(state.principal, principal)) state = {
58
60
  principal: structuredClone(principal),
59
- generation: 0
61
+ generation: 0,
62
+ runtimeSources: new Map(),
63
+ runtimeSourcesUntil: 0
60
64
  };
65
+ if (state.runtimeSourcesUntil <= Date.now()) {
66
+ state.runtimeSources.clear();
67
+ state.runtimeSourcesUntil = Date.now() + RUNTIME_SOURCE_TTL;
68
+ }
61
69
  scopes.delete(scope);
62
70
  scopes.set(scope, state);
63
71
  while (scopes.size > 128) scopes.delete(scopes.keys().next().value);
@@ -73,13 +81,14 @@ async function readOntologyMetadata(memberId, request, context, options = {}) {
73
81
  if (key !== void 0) remove(key);
74
82
  const client = ontologyTools({
75
83
  ...options,
76
- principal
84
+ principal,
85
+ ...state ? { runtimeSources: state.runtimeSources } : {}
77
86
  });
78
87
  let attempts = 0;
79
88
  for (;;) {
80
89
  attempts++;
81
90
  try {
82
- const result = await client.rpc(memberId, request.method, request.params, AbortSignal.timeout(4e3));
91
+ const result = await client.rpc(memberId, request.method, request.params, AbortSignal.timeout(METADATA_TIMEOUT));
83
92
  if (key !== void 0 && scope !== void 0 && state && scopes.get(scope) === state && state.generation === generation) {
84
93
  const bytes = Buffer.byteLength(JSON.stringify(result));
85
94
  if (bytes <= 2097152) {
@@ -99,6 +108,7 @@ async function readOntologyMetadata(memberId, request, context, options = {}) {
99
108
  } catch (cause) {
100
109
  if (state && invalidatesSnapshot(cause)) {
101
110
  state.generation++;
111
+ state.runtimeSources.clear();
102
112
  for (const [cachedKey, entry] of cache) if (entry.state === state) remove(cachedKey);
103
113
  }
104
114
  const mayRetry = retryable(cause);
@@ -20,6 +20,12 @@ export declare function ontologyTools(options?: {
20
20
  readonly fetch?: typeof fetch;
21
21
  /** Install a durable dispatch fence after safe metadata/auth work, immediately before the action request. */
22
22
  readonly beforeCall?: (name: string) => Promise<void>;
23
+ /**
24
+ * What a tools/list said about runtime-source support, per member, shared by the clients of
25
+ * one caller. A large catalog's listing takes megabytes and seconds; without it, every client
26
+ * lists the whole catalog again before its first call.
27
+ */
28
+ readonly runtimeSources?: Map<string, boolean>;
23
29
  }): {
24
30
  rpc: (memberId: string, method: string, params: Record<string, unknown>, signal?: AbortSignal) => Promise<Record<string, unknown>>;
25
31
  list(memberId: string): Promise<Record<string, unknown>[]>;
@@ -18,7 +18,7 @@ var OntologyActionNotPermitted = class extends Error {
18
18
  code = "ontology_action_not_permitted";
19
19
  };
20
20
  function ontologyTools(options = {}) {
21
- const runtimeSources = new Map();
21
+ const runtimeSources = options.runtimeSources ?? new Map();
22
22
  async function rpc(memberId, method, params, signal) {
23
23
  if (method === "tools/call") {
24
24
  const envelope = options.envelope ?? runtimeEnvelope({ principal: memberIdOf(options.principal) === memberId ? options.principal : null });
@@ -5,9 +5,10 @@ import type { RuntimeSandboxSession, SandboxSession } from "eve/sandbox";
5
5
  * writes, the session state recording when it opened, the gate every file tool passes and the
6
6
  * cleanup. eve cannot say which environment a session's sandbox came from without opening it, and
7
7
  * the configured environment object need not survive bundling, so the SDK's selector writes a
8
- * marker into the sandbox it opens and the tools refuse a sandbox without one. The marker guards
9
- * against a missing or replaced `agent/agent/sandbox.ts`; it is not a boundary against the model,
10
- * which can only reach the sandbox after the gate has passed.
8
+ * marker into the sandbox it opens, and the tools replace a sandbox without one and refuse to run
9
+ * unless the replacement carries it. The marker guards against a missing or replaced
10
+ * `agent/agent/sandbox.ts`; it is not a boundary against the model, which can only reach the
11
+ * sandbox after the gate has passed.
11
12
  */
12
13
  /** Where the SDK's selector records that it opened the sandbox. */
13
14
  export declare const SANDBOX_MARKER_PATH = "/workspace/.segmentstream/sandbox.json";
@@ -66,9 +67,13 @@ export declare function initializeSandbox(sandbox: SandboxSession, input: {
66
67
  /**
67
68
  * The session's sandbox, once it has passed the gate: it must carry the SDK's marker for this
68
69
  * session, so the file tools never run in eve's default sandbox, whose network is open. The first
69
- * call in each turn checks the marker; a sandbox older than a day is deleted through its handle
70
- * and reopened, carrying over the attachments staged within the last day so that the files of the
71
- * current turn stay where eve's references point.
70
+ * call in each turn checks the marker. A sandbox older than a day, or one without a marker, is
71
+ * deleted through its handle and reopened, carrying over the attachments staged within the last
72
+ * day so that the files of the current turn stay where eve's references point. eve keeps a
73
+ * session's sandbox across deployments, so a session that opened one before the agent adopted
74
+ * `@segmentstream/agent/sandbox`, through eve's default or the workspace's own selector, gets the
75
+ * SDK's on its first file-tool call; the reopened sandbox must carry the marker, or the call
76
+ * fails. Another session's marker fails at once: that sandbox is not this session's to delete.
72
77
  */
73
78
  export declare function sessionSandbox(context: SandboxContext, options?: {
74
79
  readonly now?: number;
@@ -54,9 +54,15 @@ async function initializeSandbox(sandbox, input) {
54
54
  } catch {}
55
55
  return marker;
56
56
  }
57
+ async function sandboxMarker(sandbox) {
58
+ return parseSandboxMarker(await sandbox.readBinaryFile({ path: SANDBOX_MARKER_PATH }));
59
+ }
60
+ function sandboxUnavailable() {
61
+ return new SandboxUnavailableError("The sandbox tools are unavailable: this session's sandbox was not opened by @segmentstream/agent/sandbox. agent/agent/sandbox.ts must re-export it.");
62
+ }
57
63
  async function verifiedMarker(sandbox, sessionId) {
58
- const marker = parseSandboxMarker(await sandbox.readBinaryFile({ path: SANDBOX_MARKER_PATH }));
59
- if (marker === void 0 || marker.sessionId !== sessionId) throw new SandboxUnavailableError("The sandbox tools are unavailable: this session's sandbox was not opened by @segmentstream/agent/sandbox. agent/agent/sandbox.ts must re-export it.");
64
+ const marker = await sandboxMarker(sandbox);
65
+ if (marker === void 0 || marker.sessionId !== sessionId) throw sandboxUnavailable();
60
66
  return marker;
61
67
  }
62
68
  async function recentAttachments(sandbox, since) {
@@ -83,8 +89,9 @@ async function sessionSandbox(context, options = {}) {
83
89
  const turn = context.session.turn.id;
84
90
  const sandbox = await context.getSandbox();
85
91
  if (record.get()?.checkedTurn === turn) return sandbox;
86
- const marker = await verifiedMarker(sandbox, context.session.id);
87
- if (now - Date.parse(marker.openedAt) < 864e5) {
92
+ const marker = await sandboxMarker(sandbox);
93
+ if (marker !== void 0 && marker.sessionId !== context.session.id) throw sandboxUnavailable();
94
+ if (marker !== void 0 && now - Date.parse(marker.openedAt) < 864e5) {
88
95
  record.update(() => ({
89
96
  openedAt: marker.openedAt,
90
97
  checkedTurn: turn
@@ -20,7 +20,7 @@ import { archivedRoutingMessages } from "../group-routing/routing-context.mjs";
20
20
  import { dispatchStructuredMessage, structuredMessage } from "./structured.mjs";
21
21
  import { scopedAddress, sessionPolicy } from "./sessions.mjs";
22
22
  import { handleWorkspaceCallback, isWorkspaceCallback } from "./callbacks.mjs";
23
- import { t as agentBuild } from "../../../_chunks/build-metadata-DR4gACLZ.mjs";
23
+ import { t as agentBuild } from "../../../_chunks/build-metadata-DeXkvn8O.mjs";
24
24
  import { attachmentLifecycle } from "./lifecycle.mjs";
25
25
  import { observedMessages } from "./observed.mjs";
26
26
  import { expireDeferredCards, handleDeferredCallback, isDeferredCallback } from "./deferred-confirmations.mjs";
@@ -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-DR4gACLZ.mjs";
7
+ import { t as agentBuild } from "../../../_chunks/build-metadata-DeXkvn8O.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-RIRNBxUC.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-DR4gACLZ.mjs";
12
+ import { t as agentBuild } from "../../../_chunks/build-metadata-DeXkvn8O.mjs";
13
13
  var AttachmentLifecycleNotPermitted = class extends Error {
14
14
  code = "attachment_lifecycle_not_permitted";
15
15
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@segmentstream/agent",
3
- "version": "0.17.0",
3
+ "version": "0.17.2",
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",