@nylorun/runtime 0.45.0-beta → 0.46.0-beta

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/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.46.0-beta
4
+
5
+ ### Minor Changes
6
+
7
+ - b6a7c1d: An agent sees the images and PDFs it makes in its sandbox (ADR-139). `read` of a file the turn's model reads as media, known by its extension (an image, a PDF, an Office document, audio or video), comes back as the file itself, shown to the model in the turn's files, instead of numbered lines of decoded bytes. Our engine runs it in the harness: it reads the bytes, checks they start as the type says, and stores them as an artifact for the model only. A model that can't read the kind is told so before anything is read or stored (`sandbox.unreadable`), and a file that isn't what its name says fails `sandbox.binary_file`.
8
+
9
+ - **Model-only artifacts.** `artifact.write` `begin` takes `audience: "model"` (Harness API 5, schema revision 5, `harness-api/v5.5.json`; only our own engine sends it). Such an artifact is readable by id and carries `audience: "model"` in its view and its `artifact.created` event, but is never listed (`GET /v1/artifacts`, a session's artifacts, pages) and is no A2A task file. Runtime store migration 0046 (`artifacts.audience`, nullable) runs at start.
10
+ - **Binary files.** The sandbox's `read` of any other binary file fails `sandbox.binary_file`, in words, instead of answering decoded bytes as text.
11
+ - **A cap per call.** A model call carries at most the 6 newest files from tool results as media; older ones in the same turn go as a note naming them, which `read_artifact` brings back. A person's own files are never capped.
12
+ - `read`'s description says what it now does, so the prompt prefix of every agent with a sandbox changes once: its first call after the upgrade misses the prompt cache.
13
+
14
+ ### Patch Changes
15
+
16
+ - b6a7c1d: Trigger runs fired in the same millisecond now list in the order they fired: run ids a Runtime makes strictly increase, instead of breaking a same-millisecond tie on their random part.
17
+ - 522e6d7: A pod Agent's idle period now runs from when its pod became ready, not from when it was asked to start. A slow start, such as a cold node pulling the image, no longer uses up the idle period: on the Agent Substrate exit test, a pod that took 56 s to start was suspended as idle 3.6 s after it could first take a turn. Becoming ready moves the pod's last activity to that moment, which the idle timer, the idle decision and the Tenant's sweep all read.
18
+ - f1ac19f: Pods on Agent Substrate (preview): a wake no longer waits on a DNS lookup of the edge. The sandboxes service pins the edge's name to the address `enable` found in each actor's `/etc/hosts` (when the image lets the engine's user write it), so a restored actor's first lookup, which can be lost, is never made; the exit test's rerun measured 1.15–1.51 s in four of ten wakes from that lookup's retry. `nylorun local sandbox enable --backend substrate` also tries the pod's lookup of the Docker host three times before refusing the context, since a pod's first lookup can find no address on a cluster that just started.
19
+ - Pin core to the tested release.
20
+ - Pin harness to the tested release.
21
+ - Updated dependencies [b6a7c1d]
22
+ - Updated dependencies
23
+ - @nylorun/core@0.40.0-beta
24
+ - @nylorun/harness@0.36.0-beta
25
+
26
+ ## 0.45.1-beta
27
+
28
+ ### Patch Changes
29
+
30
+ - 40820b8: Pods on Agent Substrate (preview): `nylorun local sandbox enable --backend substrate` no longer fails at the Tenant's atespace (`access_policy.metadata.atespace: Forbidden: must not be set`), and a pod that ran before wakes again instead of failing every later apply with `name: Required value`. The sandboxes service now writes the atespace's access policy without an atespace, as Substrate v1.0 requires of a global-scoped policy, and names the actor's egress policy (`default`) when it reads it back.
31
+ - 1740a16: Pods on Agent Substrate (preview): a pod's first start on a node that lacks its image no longer fails. The sandboxes service tries a resume that times out or finds Substrate busy again, every 5 s for up to 10 minutes while the pod should still be running; before, the first resume gave up after 2 minutes during a long image pull and the actor stayed RESUMING. Actors also get `RES_OPTIONS=timeout:1 attempts:3`, so a wake whose first DNS lookup of the edge is lost waits about 1 s instead of 5 s (an Agent's own `RES_OPTIONS` wins).
32
+
3
33
  ## 0.45.0-beta
4
34
 
5
35
  ### Minor Changes
@@ -84,7 +84,8 @@ export declare function telemetryOf(payload: unknown, traceId: string | undefine
84
84
  export declare function fileUrl(baseUrl: string | undefined, artifactId: string, version: number): string;
85
85
  /**
86
86
  * The A2A artifact of a file the turn wrote (`artifact.created`, `artifact.version.created`), with
87
- * `files/v1`, or undefined for the caller's own uploads. A folder is served as its zip.
87
+ * `files/v1`, or undefined for the caller's own uploads and for a file stored only for the model
88
+ * (ADR-139). A folder is served as its zip.
88
89
  */
89
90
  export declare function fileArtifact(payload: unknown, baseUrl: string | undefined): Artifact | undefined;
90
91
  /**
@@ -246,13 +246,17 @@ export function fileUrl(baseUrl, artifactId, version) {
246
246
  }
247
247
  /**
248
248
  * The A2A artifact of a file the turn wrote (`artifact.created`, `artifact.version.created`), with
249
- * `files/v1`, or undefined for the caller's own uploads. A folder is served as its zip.
249
+ * `files/v1`, or undefined for the caller's own uploads and for a file stored only for the model
250
+ * (ADR-139). A folder is served as its zip.
250
251
  */
251
252
  export function fileArtifact(payload, baseUrl) {
252
253
  if (!isRecord(payload) || typeof payload.artifactId !== "string" || typeof payload.name !== "string")
253
254
  return undefined;
254
255
  if (payload.source === "upload")
255
256
  return undefined;
257
+ // A file stored only for the model to see (ADR-139) is no output of the task.
258
+ if (payload.audience === "model")
259
+ return undefined;
256
260
  const version = Number(payload.version);
257
261
  const folder = payload.kind === "folder";
258
262
  return {
@@ -1805,6 +1805,7 @@ export declare const ArtifactView: z.ZodObject<{
1805
1805
  agentId: z.ZodOptional<z.ZodString>;
1806
1806
  latestVersion: z.ZodNumber;
1807
1807
  labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1808
+ audience: z.ZodOptional<z.ZodLiteral<"model">>;
1808
1809
  createdAt: z.ZodString;
1809
1810
  updatedAt: z.ZodString;
1810
1811
  versions: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -1833,6 +1834,7 @@ export declare const ArtifactListItem: z.ZodObject<{
1833
1834
  agentId: z.ZodOptional<z.ZodString>;
1834
1835
  latestVersion: z.ZodNumber;
1835
1836
  labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1837
+ audience: z.ZodOptional<z.ZodLiteral<"model">>;
1836
1838
  createdAt: z.ZodString;
1837
1839
  updatedAt: z.ZodString;
1838
1840
  }, z.core.$strict>;
@@ -1850,6 +1852,7 @@ export declare const ArtifactPage: z.ZodObject<{
1850
1852
  agentId: z.ZodOptional<z.ZodString>;
1851
1853
  latestVersion: z.ZodNumber;
1852
1854
  labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1855
+ audience: z.ZodOptional<z.ZodLiteral<"model">>;
1853
1856
  createdAt: z.ZodString;
1854
1857
  updatedAt: z.ZodString;
1855
1858
  }, z.core.$strict>>;
@@ -1867,6 +1870,7 @@ export declare const ListArtifactsResponse: z.ZodObject<{
1867
1870
  agentId: z.ZodOptional<z.ZodString>;
1868
1871
  latestVersion: z.ZodNumber;
1869
1872
  labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1873
+ audience: z.ZodOptional<z.ZodLiteral<"model">>;
1870
1874
  createdAt: z.ZodString;
1871
1875
  updatedAt: z.ZodString;
1872
1876
  versions: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -1897,6 +1901,7 @@ export declare const UploadArtifactResponse: z.ZodObject<{
1897
1901
  agentId: z.ZodOptional<z.ZodString>;
1898
1902
  latestVersion: z.ZodNumber;
1899
1903
  labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1904
+ audience: z.ZodOptional<z.ZodLiteral<"model">>;
1900
1905
  createdAt: z.ZodString;
1901
1906
  updatedAt: z.ZodString;
1902
1907
  versions: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -2844,6 +2849,7 @@ export declare const SessionEvent: z.ZodDiscriminatedUnion<[z.ZodObject<{
2844
2849
  snapshot: "snapshot";
2845
2850
  upload: "upload";
2846
2851
  }>;
2852
+ audience: z.ZodOptional<z.ZodLiteral<"model">>;
2847
2853
  callId: z.ZodOptional<z.ZodString>;
2848
2854
  fileCount: z.ZodOptional<z.ZodNumber>;
2849
2855
  claimed: z.ZodOptional<z.ZodBoolean>;
@@ -3154,6 +3160,7 @@ export declare const SessionEvent: z.ZodDiscriminatedUnion<[z.ZodObject<{
3154
3160
  snapshot: "snapshot";
3155
3161
  upload: "upload";
3156
3162
  }>;
3163
+ audience: z.ZodOptional<z.ZodLiteral<"model">>;
3157
3164
  callId: z.ZodOptional<z.ZodString>;
3158
3165
  fileCount: z.ZodOptional<z.ZodNumber>;
3159
3166
  claimed: z.ZodOptional<z.ZodBoolean>;
@@ -6075,6 +6082,7 @@ export declare const SessionEvent: z.ZodDiscriminatedUnion<[z.ZodObject<{
6075
6082
  snapshot: "snapshot";
6076
6083
  upload: "upload";
6077
6084
  }>;
6085
+ audience: z.ZodOptional<z.ZodLiteral<"model">>;
6078
6086
  callId: z.ZodOptional<z.ZodString>;
6079
6087
  fileCount: z.ZodOptional<z.ZodNumber>;
6080
6088
  claimed: z.ZodOptional<z.ZodBoolean>;
@@ -6385,6 +6393,7 @@ export declare const SessionEvent: z.ZodDiscriminatedUnion<[z.ZodObject<{
6385
6393
  snapshot: "snapshot";
6386
6394
  upload: "upload";
6387
6395
  }>;
6396
+ audience: z.ZodOptional<z.ZodLiteral<"model">>;
6388
6397
  callId: z.ZodOptional<z.ZodString>;
6389
6398
  fileCount: z.ZodOptional<z.ZodNumber>;
6390
6399
  claimed: z.ZodOptional<z.ZodBoolean>;
@@ -9309,6 +9318,7 @@ export declare const SessionItemsResponse: z.ZodObject<{
9309
9318
  snapshot: "snapshot";
9310
9319
  upload: "upload";
9311
9320
  }>;
9321
+ audience: z.ZodOptional<z.ZodLiteral<"model">>;
9312
9322
  callId: z.ZodOptional<z.ZodString>;
9313
9323
  fileCount: z.ZodOptional<z.ZodNumber>;
9314
9324
  claimed: z.ZodOptional<z.ZodBoolean>;
@@ -9619,6 +9629,7 @@ export declare const SessionItemsResponse: z.ZodObject<{
9619
9629
  snapshot: "snapshot";
9620
9630
  upload: "upload";
9621
9631
  }>;
9632
+ audience: z.ZodOptional<z.ZodLiteral<"model">>;
9622
9633
  callId: z.ZodOptional<z.ZodString>;
9623
9634
  fileCount: z.ZodOptional<z.ZodNumber>;
9624
9635
  claimed: z.ZodOptional<z.ZodBoolean>;
@@ -12540,6 +12551,7 @@ export declare const SessionItemsResponse: z.ZodObject<{
12540
12551
  snapshot: "snapshot";
12541
12552
  upload: "upload";
12542
12553
  }>;
12554
+ audience: z.ZodOptional<z.ZodLiteral<"model">>;
12543
12555
  callId: z.ZodOptional<z.ZodString>;
12544
12556
  fileCount: z.ZodOptional<z.ZodNumber>;
12545
12557
  claimed: z.ZodOptional<z.ZodBoolean>;
@@ -12850,6 +12862,7 @@ export declare const SessionItemsResponse: z.ZodObject<{
12850
12862
  snapshot: "snapshot";
12851
12863
  upload: "upload";
12852
12864
  }>;
12865
+ audience: z.ZodOptional<z.ZodLiteral<"model">>;
12853
12866
  callId: z.ZodOptional<z.ZodString>;
12854
12867
  fileCount: z.ZodOptional<z.ZodNumber>;
12855
12868
  claimed: z.ZodOptional<z.ZodBoolean>;
@@ -16694,6 +16707,7 @@ export declare const HistoryPage: z.ZodObject<{
16694
16707
  snapshot: "snapshot";
16695
16708
  upload: "upload";
16696
16709
  }>;
16710
+ audience: z.ZodOptional<z.ZodLiteral<"model">>;
16697
16711
  callId: z.ZodOptional<z.ZodString>;
16698
16712
  fileCount: z.ZodOptional<z.ZodNumber>;
16699
16713
  claimed: z.ZodOptional<z.ZodBoolean>;
@@ -17004,6 +17018,7 @@ export declare const HistoryPage: z.ZodObject<{
17004
17018
  snapshot: "snapshot";
17005
17019
  upload: "upload";
17006
17020
  }>;
17021
+ audience: z.ZodOptional<z.ZodLiteral<"model">>;
17007
17022
  callId: z.ZodOptional<z.ZodString>;
17008
17023
  fileCount: z.ZodOptional<z.ZodNumber>;
17009
17024
  claimed: z.ZodOptional<z.ZodBoolean>;
@@ -19925,6 +19940,7 @@ export declare const HistoryPage: z.ZodObject<{
19925
19940
  snapshot: "snapshot";
19926
19941
  upload: "upload";
19927
19942
  }>;
19943
+ audience: z.ZodOptional<z.ZodLiteral<"model">>;
19928
19944
  callId: z.ZodOptional<z.ZodString>;
19929
19945
  fileCount: z.ZodOptional<z.ZodNumber>;
19930
19946
  claimed: z.ZodOptional<z.ZodBoolean>;
@@ -20235,6 +20251,7 @@ export declare const HistoryPage: z.ZodObject<{
20235
20251
  snapshot: "snapshot";
20236
20252
  upload: "upload";
20237
20253
  }>;
20254
+ audience: z.ZodOptional<z.ZodLiteral<"model">>;
20238
20255
  callId: z.ZodOptional<z.ZodString>;
20239
20256
  fileCount: z.ZodOptional<z.ZodNumber>;
20240
20257
  claimed: z.ZodOptional<z.ZodBoolean>;
@@ -324,6 +324,7 @@ async function commitDiskVersion(ctx, id, entries, epoch) {
324
324
  createdAt,
325
325
  updatedAt: createdAt,
326
326
  environmentId: id,
327
+ audience: null,
327
328
  };
328
329
  await t.insertArtifact(artifact, version);
329
330
  }
@@ -195,6 +195,7 @@ export async function commitFolderVersion(ctx, input) {
195
195
  createdAt,
196
196
  updatedAt: createdAt,
197
197
  environmentId: null,
198
+ audience: null,
198
199
  };
199
200
  await t.insertArtifact(row, version);
200
201
  }
@@ -1,4 +1,4 @@
1
- import type { ArtifactSource, ArtifactVersionView, ArtifactView, FolderManifest } from "@nylorun/core/contracts";
1
+ import type { ArtifactAudience, ArtifactSource, ArtifactVersionView, ArtifactView, FolderManifest } from "@nylorun/core/contracts";
2
2
  import { type BlobBody } from "../blob/index.js";
3
3
  import type { ArtifactRow, ArtifactVersionRow, Tx } from "../store/types.js";
4
4
  import { type SessionAccess, type TenantContext } from "../tenant/context.js";
@@ -18,6 +18,8 @@ export interface UploadInput {
18
18
  /** The body's declared length (`Content-Length`): one past the limits is refused unread. */
19
19
  readonly declaredBytes?: number;
20
20
  readonly source: ArtifactSource;
21
+ /** `model`: a new artifact stored only for the model to see (ADR-139). */
22
+ readonly audience?: ArtifactAudience;
21
23
  /** The turn that saved it (a harness's `artifact.write`), for the event. */
22
24
  readonly turnId?: string | null;
23
25
  /** The tool call that saved it, for the event. */
@@ -85,6 +85,7 @@ export function artifactView(row, versions) {
85
85
  ...(row.environmentId === null ? {} : { agentId: row.environmentId }),
86
86
  latestVersion: row.latestVersion,
87
87
  ...(row.labelsJson === null ? {} : { labels: JSON.parse(row.labelsJson) }),
88
+ ...(row.audience === null ? {} : { audience: row.audience }),
88
89
  createdAt: row.createdAt,
89
90
  updatedAt: row.updatedAt,
90
91
  ...(versions === undefined ? {} : { versions: versions.map(versionView) }),
@@ -236,6 +237,7 @@ export async function uploadArtifact(ctx, input, body, access, signal) {
236
237
  createdAt,
237
238
  updatedAt: createdAt,
238
239
  environmentId: null,
240
+ audience: input.audience ?? null,
239
241
  };
240
242
  version = {
241
243
  artifactId,
@@ -260,6 +262,7 @@ export async function uploadArtifact(ctx, input, body, access, signal) {
260
262
  size: version.size,
261
263
  sha256: version.sha256,
262
264
  source: version.source,
265
+ ...(row.audience === null ? {} : { audience: row.audience }),
263
266
  ...(input.callId === undefined ? {} : { callId: input.callId }),
264
267
  });
265
268
  return { artifact: artifactView(row), version: versionView(version), created: true };
@@ -202,6 +202,7 @@ export function createArtifactSeam(ctx) {
202
202
  ...(contentType === undefined ? {} : { contentType }),
203
203
  sessionId,
204
204
  source: "engine",
205
+ ...(params.audience === undefined || params.artifactId !== undefined ? {} : { audience: params.audience }),
205
206
  turnId: scope.turnId,
206
207
  ...(begun.callId === undefined ? {} : { callId: begun.callId }),
207
208
  writeKey,
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.2.0",
3
3
  "info": {
4
4
  "title": "Nylorun Management API",
5
- "version": "0.45.0-beta",
5
+ "version": "0.46.0-beta",
6
6
  "x-nylorun-protocol": {
7
7
  "min": 16,
8
8
  "max": 18,
package/dist/openapi.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.2.0",
3
3
  "info": {
4
4
  "title": "Nylorun Runtime API",
5
- "version": "0.45.0-beta",
5
+ "version": "0.46.0-beta",
6
6
  "x-nylorun-protocol": {
7
7
  "min": 16,
8
8
  "max": 18,
@@ -17563,6 +17563,12 @@
17563
17563
  "snapshot"
17564
17564
  ]
17565
17565
  },
17566
+ "audience": {
17567
+ "type": "string",
17568
+ "enum": [
17569
+ "model"
17570
+ ]
17571
+ },
17566
17572
  "callId": {
17567
17573
  "type": "string"
17568
17574
  },
@@ -17805,6 +17811,12 @@
17805
17811
  "snapshot"
17806
17812
  ]
17807
17813
  },
17814
+ "audience": {
17815
+ "type": "string",
17816
+ "enum": [
17817
+ "model"
17818
+ ]
17819
+ },
17808
17820
  "callId": {
17809
17821
  "type": "string"
17810
17822
  },
@@ -25717,6 +25729,12 @@
25717
25729
  "type": "string"
25718
25730
  }
25719
25731
  },
25732
+ "audience": {
25733
+ "type": "string",
25734
+ "enum": [
25735
+ "model"
25736
+ ]
25737
+ },
25720
25738
  "createdAt": {
25721
25739
  "type": "string"
25722
25740
  },
@@ -25868,6 +25886,12 @@
25868
25886
  "type": "string"
25869
25887
  }
25870
25888
  },
25889
+ "audience": {
25890
+ "type": "string",
25891
+ "enum": [
25892
+ "model"
25893
+ ]
25894
+ },
25871
25895
  "createdAt": {
25872
25896
  "type": "string"
25873
25897
  },
@@ -25995,6 +26019,12 @@
25995
26019
  "type": "string"
25996
26020
  }
25997
26021
  },
26022
+ "audience": {
26023
+ "type": "string",
26024
+ "enum": [
26025
+ "model"
26026
+ ]
26027
+ },
25998
26028
  "createdAt": {
25999
26029
  "type": "string"
26000
26030
  },
@@ -26049,6 +26079,12 @@
26049
26079
  "type": "string"
26050
26080
  }
26051
26081
  },
26082
+ "audience": {
26083
+ "type": "string",
26084
+ "enum": [
26085
+ "model"
26086
+ ]
26087
+ },
26052
26088
  "createdAt": {
26053
26089
  "type": "string"
26054
26090
  },
@@ -19,6 +19,8 @@
19
19
  * reconciled, whichever path woke it: a turn's end arms it too, but a wake with no turn end
20
20
  * (a disk copy's `wakePod`, commands through the session's sandbox route) armed nothing and
21
21
  * ran on for days. A timer that fires during a turn looks again one idle period later.
22
+ * Becoming ready moves `lastActiveAt` to that moment, so a slow start doesn't use up the
23
+ * idle period before the pod can take a turn.
22
24
  * - **Lost**: an incarnation that has run (a pod joined) whose Sandbox is gone or whose volume
23
25
  * is missing is `lost` (`agent.lost`): its host epoch moves, its Sandbox is deleted, and
24
26
  * only a reset brings the Environment back. agent-sandbox recreates a deleted claim empty, so
@@ -171,9 +171,17 @@ export function decide(facts) {
171
171
  if (pod.observed === "failed" && pod.reason?.startsWith(SETUP_FAILED))
172
172
  return done();
173
173
  if (status.ready) {
174
+ // Becoming ready counts as activity: the idle period runs from when the pod could first take
175
+ // a turn, so a slow start (a cold node's image pull) doesn't use it up (P1.8 rerun: a 56 s
176
+ // start was suspended as idle 3.6 s after it ran).
177
+ let lastActive = Date.parse(pod.lastActiveAt ?? row.createdAt);
174
178
  if (pod.observed !== "running") {
175
179
  patch.observed = "running";
176
180
  patch.reason = null;
181
+ if (now > lastActive) {
182
+ patch.lastActiveAt = new Date(now).toISOString();
183
+ lastActive = now;
184
+ }
177
185
  }
178
186
  // The image it runs, pinned by digest from now on (Q14).
179
187
  const digest = pod.imageDigest === undefined ? imageDigestOf(status.imageID, facts.image) : undefined;
@@ -181,7 +189,7 @@ export function decide(facts) {
181
189
  patch.imageDigest = digest;
182
190
  // Whatever woke it, a running pod has an idle timer (the idle trigger armed its own above).
183
191
  if (facts.trigger !== "idle")
184
- arm.push({ timer: "idle", at: Date.parse(pod.lastActiveAt ?? row.createdAt) + facts.config.idleMs });
192
+ arm.push({ timer: "idle", at: lastActive + facts.config.idleMs });
185
193
  return done();
186
194
  }
187
195
  const startedAt = Date.parse(pod.startedAt ?? row.updatedAt);
@@ -15,6 +15,11 @@ export interface SandboxToolReport {
15
15
  * mounting a skill, a tool's own helper command): `harness.helperCommandTimeoutMs`.
16
16
  */
17
17
  export declare const helperCommandTimeoutMs: () => number;
18
+ /**
19
+ * Whether decoded file content is binary: a NUL among its first 8000 characters, or a run of
20
+ * replacement characters where invalid UTF-8 was (more than 1 in 10). Text never has NULs.
21
+ */
22
+ export declare function looksBinary(content: string): boolean;
18
23
  export declare function quote(value: string): string;
19
24
  /**
20
25
  * An absolute path inside the sandbox. Relative paths are relative to the workspace; on a backend
@@ -13,6 +13,20 @@ import { hostTuning } from "../tuning.js";
13
13
  * mounting a skill, a tool's own helper command): `harness.helperCommandTimeoutMs`.
14
14
  */
15
15
  export const helperCommandTimeoutMs = () => hostTuning().harness.helperCommandTimeoutMs;
16
+ /**
17
+ * Whether decoded file content is binary: a NUL among its first 8000 characters, or a run of
18
+ * replacement characters where invalid UTF-8 was (more than 1 in 10). Text never has NULs.
19
+ */
20
+ export function looksBinary(content) {
21
+ const head = content.slice(0, 8000);
22
+ if (head.includes("\u0000"))
23
+ return true;
24
+ let replaced = 0;
25
+ for (const char of head)
26
+ if (char === "\ufffd")
27
+ replaced += 1;
28
+ return head.length > 0 && replaced / head.length > 0.1;
29
+ }
16
30
  export function quote(value) {
17
31
  return `'${value.replaceAll("'", `'\\''`)}'`;
18
32
  }
@@ -75,6 +89,9 @@ environment) {
75
89
  const content = await handle.readFile(path);
76
90
  if (content === undefined)
77
91
  return failed("sandbox.not_found", `No file at ${path}`);
92
+ // A binary file decodes to garbage the model can't tell from text (ADR-139): say so instead.
93
+ if (looksBinary(content))
94
+ return failed("sandbox.binary_file", `${path} is a binary file, not text. read shows text files, and images, PDFs and other files your model reads by their extension; use bash to inspect this one.`);
78
95
  const lines = content.split("\n");
79
96
  if (lines.at(-1) === "")
80
97
  lines.pop();
@@ -0,0 +1 @@
1
+ ALTER TABLE "nylorun"."artifacts" ADD COLUMN "audience" text;
@@ -323,6 +323,13 @@
323
323
  "when": 1793954783377,
324
324
  "tag": "0045_session_queue_delivery",
325
325
  "breakpoints": true
326
+ },
327
+ {
328
+ "idx": 46,
329
+ "version": "7",
330
+ "when": 1794240246972,
331
+ "tag": "0046_artifacts_audience",
332
+ "breakpoints": true
326
333
  }
327
334
  ]
328
335
  }
@@ -179,6 +179,8 @@ export function createPostgresReadStore(source, tenantId) {
179
179
  .where(and(eq(sessions.id, artifacts.sessionId), accessWhere(access)))), filters.sessionId === undefined
180
180
  ? undefined
181
181
  : eq(artifacts.sessionId, filters.sessionId), filters.kind === undefined ? undefined : eq(artifacts.kind, filters.kind),
182
+ // A file stored only for the model (ADR-139) is never listed.
183
+ isNull(artifacts.audience),
182
184
  // Labels are a flat string map serialized by JSON.stringify. Match its exact
183
185
  // key/value pair: jsonb rejects NUL/unpaired surrogate escapes labels may hold.
184
186
  ...Object.entries(filters.labels).map(([k, v]) => sql `strpos(${artifacts.labelsJson}, ${JSON.stringify(k) + ":" + JSON.stringify(v)}) > 0`), key ? sql `(${artifacts.createdAt}, ${artifacts.id}) < (${key[0]}, ${key[1]})` : undefined))
@@ -4995,6 +4995,23 @@ export declare const artifacts: import("drizzle-orm/pg-core").PgTableWithColumns
4995
4995
  }, {}, {
4996
4996
  pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
4997
4997
  }>;
4998
+ audience: import("drizzle-orm/pg-core").PgColumn<{
4999
+ name: "audience";
5000
+ tableName: "artifacts";
5001
+ dataType: "string";
5002
+ columnType: "PgText";
5003
+ data: "model";
5004
+ driverParam: string;
5005
+ notNull: false;
5006
+ hasDefault: false;
5007
+ isPrimaryKey: false;
5008
+ isAutoincrement: false;
5009
+ hasRuntimeDefault: false;
5010
+ enumValues: ["model"];
5011
+ baseColumn: never;
5012
+ identity: undefined;
5013
+ generated: undefined;
5014
+ }, {}, {}>;
4998
5015
  };
4999
5016
  dialect: 'pg';
5000
5017
  }>;
@@ -626,6 +626,11 @@ export const artifacts = nylorun.table("artifacts", {
626
626
  updatedAt: text().notNull(),
627
627
  /** The Agent whose disk versions this folder holds (R2d step 3); one per Agent. */
628
628
  environmentId: textC(),
629
+ /**
630
+ * `model`: our engine stored it only for its model to see (ADR-139); null for an output.
631
+ * Such an artifact is readable by id and never listed.
632
+ */
633
+ audience: text({ enum: ["model"] }),
629
634
  }, (t) => [
630
635
  index("artifacts_session").on(t.sessionId, t.createdAt, t.id),
631
636
  uniqueIndex("artifacts_environment_disk").on(t.environmentId).where(sql `environment_id IS NOT NULL`),
@@ -1581,7 +1581,8 @@ class PostgresTx {
1581
1581
  return this.db
1582
1582
  .select()
1583
1583
  .from(artifacts)
1584
- .where(filter.sessionId === undefined ? undefined : eq(artifacts.sessionId, filter.sessionId))
1584
+ // A file stored only for the model (ADR-139) is never listed.
1585
+ .where(and(isNull(artifacts.audience), filter.sessionId === undefined ? undefined : eq(artifacts.sessionId, filter.sessionId)))
1585
1586
  .orderBy(artifacts.createdAt, artifacts.id);
1586
1587
  }
1587
1588
  async insertArtifact(row, version) {
@@ -8,7 +8,12 @@ export declare const triggerTimerKey: (agentId: string, triggerId: string) => st
8
8
  export declare const newEndpointId: () => string;
9
9
  /** A webhook endpoint's path, under the Runtime API's origin. */
10
10
  export declare const webhookPath: (endpointId: string) => string;
11
- /** A new run id: increasing with time, so runs list newest first by id. */
11
+ /**
12
+ * A new run id: its time in ms then a random 64-bit suffix, so runs list newest first by id. Ids
13
+ * this process makes strictly increase: one in the same ms as the last (or with the clock moved
14
+ * back) keeps the last's time and adds one to its suffix, so two runs fired back to back never
15
+ * list in the wrong order.
16
+ */
12
17
  export declare function newRunId(now?: number): string;
13
18
  /** A schedule's cron in its time zone (UTC by default); throws on a bad pattern or zone. */
14
19
  export declare function cronOf(trigger: Pick<Trigger, "schedule" | "timezone">): Cron;
@@ -59,9 +59,28 @@ function parseTimerKey(key) {
59
59
  export const newEndpointId = () => `wh_${randomBytes(16).toString("hex")}`;
60
60
  /** A webhook endpoint's path, under the Runtime API's origin. */
61
61
  export const webhookPath = (endpointId) => `/v1/webhooks/${endpointId}`;
62
- /** A new run id: increasing with time, so runs list newest first by id. */
62
+ /** The last run id this process made: its time and 64-bit suffix, for `newRunId`'s tie-break. */
63
+ let lastRunId;
64
+ const SUFFIX_MAX = (1n << 64n) - 1n;
65
+ /**
66
+ * A new run id: its time in ms then a random 64-bit suffix, so runs list newest first by id. Ids
67
+ * this process makes strictly increase: one in the same ms as the last (or with the clock moved
68
+ * back) keeps the last's time and adds one to its suffix, so two runs fired back to back never
69
+ * list in the wrong order.
70
+ */
63
71
  export function newRunId(now = Date.now()) {
64
- return `run_${now.toString(36).padStart(9, "0")}${randomBytes(8).toString("hex")}`;
72
+ let ms = now;
73
+ let suffix = BigInt(`0x${randomBytes(8).toString("hex")}`);
74
+ if (lastRunId && ms <= lastRunId.ms) {
75
+ ms = lastRunId.ms;
76
+ suffix = lastRunId.suffix + 1n;
77
+ if (suffix > SUFFIX_MAX) {
78
+ ms += 1;
79
+ suffix = 0n;
80
+ }
81
+ }
82
+ lastRunId = { ms, suffix };
83
+ return `run_${ms.toString(36).padStart(9, "0")}${suffix.toString(16).padStart(16, "0")}`;
65
84
  }
66
85
  /** A schedule's cron in its time zone (UTC by default); throws on a bad pattern or zone. */
67
86
  export function cronOf(trigger) {
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const RUNTIME_VERSION = "0.45.0-beta";
1
+ export declare const RUNTIME_VERSION = "0.46.0-beta";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Kept in step with package.json by runtime/server/scripts/check-package.mjs, which runs in npm run check.
2
2
  // release:prepare also rewrites this when it bumps @nylorun/runtime.
3
- export const RUNTIME_VERSION = "0.45.0-beta";
3
+ export const RUNTIME_VERSION = "0.46.0-beta";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nylorun/runtime",
3
- "version": "0.45.0-beta",
3
+ "version": "0.46.0-beta",
4
4
  "description": "Nylorun Runtime: the agent execution Host on Postgres, Restate and S2, and model providers.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -65,8 +65,8 @@
65
65
  "@hono/node-server": "^2.1.5",
66
66
  "@hono/zod-openapi": "^1.6.3",
67
67
  "@modelcontextprotocol/sdk": "^1.32.1",
68
- "@nylorun/core": "0.39.0-beta",
69
- "@nylorun/harness": "0.35.1-beta",
68
+ "@nylorun/core": "0.40.0-beta",
69
+ "@nylorun/harness": "0.36.0-beta",
70
70
  "@restatedev/restate-sdk": "1.17.3",
71
71
  "@restatedev/restate-sdk-clients": "1.17.3",
72
72
  "@s2-dev/streamstore": "0.28.0",