@nylorun/runtime 0.45.1-beta → 0.46.1-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 +29 -0
- package/dist/api/a2a/tasks.d.ts +2 -1
- package/dist/api/a2a/tasks.js +5 -1
- package/dist/api/components.d.ts +17 -0
- package/dist/artifacts/disk.js +1 -0
- package/dist/artifacts/folders.js +1 -0
- package/dist/artifacts/service.d.ts +3 -1
- package/dist/artifacts/service.js +3 -0
- package/dist/harness/clock-jump.d.ts +18 -0
- package/dist/harness/clock-jump.js +15 -0
- package/dist/harness/main.js +3 -1
- package/dist/harness/pod.d.ts +5 -2
- package/dist/harness/pod.js +9 -8
- package/dist/harness/service.d.ts +1 -0
- package/dist/harness/service.js +1 -0
- package/dist/harness/ws-client.d.ts +15 -2
- package/dist/harness/ws-client.js +55 -21
- package/dist/harness-api/artifacts.js +1 -0
- package/dist/management-openapi.json +1 -1
- package/dist/openapi.json +37 -1
- package/dist/sandbox/pods/lifecycle.d.ts +2 -0
- package/dist/sandbox/pods/lifecycle.js +9 -1
- package/dist/sandbox/tools.d.ts +5 -0
- package/dist/sandbox/tools.js +17 -0
- package/dist/store/postgres/drizzle/0046_artifacts_audience.sql +1 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +7 -0
- package/dist/store/postgres/reads.js +2 -0
- package/dist/store/postgres/schema.d.ts +17 -0
- package/dist/store/postgres/schema.js +5 -0
- package/dist/store/postgres/store.js +2 -1
- package/dist/tenant/triggers.d.ts +6 -1
- package/dist/tenant/triggers.js +21 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.46.1-beta
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 3a9933c: A pod's engine rejoins at once after an Agent Substrate restore. When the process resumes from a memory snapshot, the engine notices the jump in the wall clock (a 100 ms timer finding more than 1.5 s gone), drops the connection attempt the freeze caught in flight, its host-token renew or join included, and dials again without a backoff pause. Before, it waited for TCP to resend the packet lost with the old network: 1 s, or 3 s on a second loss, which the Phase 1 exit test measured in 4 of 10 wakes. Only pod engines watch for this (`harness_clock_jumped` in their log); an open connection is never dropped by it.
|
|
8
|
+
|
|
9
|
+
## 0.46.0-beta
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 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`.
|
|
14
|
+
|
|
15
|
+
- **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.
|
|
16
|
+
- **Binary files.** The sandbox's `read` of any other binary file fails `sandbox.binary_file`, in words, instead of answering decoded bytes as text.
|
|
17
|
+
- **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.
|
|
18
|
+
- `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.
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- 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.
|
|
23
|
+
- 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.
|
|
24
|
+
- 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.
|
|
25
|
+
- Pin core to the tested release.
|
|
26
|
+
- Pin harness to the tested release.
|
|
27
|
+
- Updated dependencies [b6a7c1d]
|
|
28
|
+
- Updated dependencies
|
|
29
|
+
- @nylorun/core@0.40.0-beta
|
|
30
|
+
- @nylorun/harness@0.36.0-beta
|
|
31
|
+
|
|
3
32
|
## 0.45.1-beta
|
|
4
33
|
|
|
5
34
|
### Patch Changes
|
package/dist/api/a2a/tasks.d.ts
CHANGED
|
@@ -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
|
|
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
|
/**
|
package/dist/api/a2a/tasks.js
CHANGED
|
@@ -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
|
|
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 {
|
package/dist/api/components.d.ts
CHANGED
|
@@ -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>;
|
package/dist/artifacts/disk.js
CHANGED
|
@@ -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 };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notices that the process was frozen and resumed, as an Agent Substrate actor is by a memory
|
|
3
|
+
* snapshot's restore: a timer that should fire every `intervalMs` finds the wall clock moved on
|
|
4
|
+
* by more than `jumpMs` beyond that. A pod's engine uses it to drop a connection attempt the
|
|
5
|
+
* freeze caught in flight (its packets went with the old network, so it would wait for TCP to
|
|
6
|
+
* send them again, 1 s and then 3 s; P1.8's third run) and to dial again at once.
|
|
7
|
+
*
|
|
8
|
+
* An event loop held up as long (a long GC pause) reads as a jump too; the caller only redials,
|
|
9
|
+
* so that costs one connection attempt, never a working connection.
|
|
10
|
+
*/
|
|
11
|
+
export interface ClockJumpWatch {
|
|
12
|
+
stop(): void;
|
|
13
|
+
}
|
|
14
|
+
export declare function watchClockJumps(onJump: (jumpedMs: number) => void, options?: {
|
|
15
|
+
intervalMs?: number;
|
|
16
|
+
jumpMs?: number;
|
|
17
|
+
now?: () => number;
|
|
18
|
+
}): ClockJumpWatch;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function watchClockJumps(onJump, options = {}) {
|
|
2
|
+
const intervalMs = options.intervalMs ?? 100;
|
|
3
|
+
const jumpMs = options.jumpMs ?? 1_500;
|
|
4
|
+
const now = options.now ?? Date.now;
|
|
5
|
+
let last = now();
|
|
6
|
+
const timer = setInterval(() => {
|
|
7
|
+
const at = now();
|
|
8
|
+
const late = at - last - intervalMs;
|
|
9
|
+
last = at;
|
|
10
|
+
if (late > jumpMs)
|
|
11
|
+
onJump(late);
|
|
12
|
+
}, intervalMs);
|
|
13
|
+
timer.unref();
|
|
14
|
+
return { stop: () => clearInterval(timer) };
|
|
15
|
+
}
|
package/dist/harness/main.js
CHANGED
|
@@ -70,13 +70,15 @@ export async function runHarness(stack, baseline) {
|
|
|
70
70
|
if (!pod.afterSeed)
|
|
71
71
|
await prepare();
|
|
72
72
|
}
|
|
73
|
-
const token = pod ? () => pod.token() : config.token;
|
|
73
|
+
const token = pod ? (signal) => pod.token(signal) : config.token;
|
|
74
74
|
if (token === undefined)
|
|
75
75
|
throw new Error("--service harness needs NYLORUN_HARNESS_TOKEN");
|
|
76
76
|
const runTokens = harnessRunTokens();
|
|
77
77
|
const service = startHarnessService({
|
|
78
78
|
url: config.url,
|
|
79
79
|
token,
|
|
80
|
+
// A pod's engine may be frozen and resumed (an Agent Substrate restore): redial at once.
|
|
81
|
+
...(pod ? { redialOnClockJump: true } : {}),
|
|
80
82
|
...(pod
|
|
81
83
|
? { sandboxBackends: [localBackend({ env: baseline, proxyEnv: () => pod.proxyEnv(), harnessRoot: config.root })] }
|
|
82
84
|
: {}),
|
package/dist/harness/pod.d.ts
CHANGED
|
@@ -23,8 +23,11 @@ export interface DiskSetupReport {
|
|
|
23
23
|
readonly output?: string;
|
|
24
24
|
}
|
|
25
25
|
export interface PodHost {
|
|
26
|
-
/**
|
|
27
|
-
|
|
26
|
+
/**
|
|
27
|
+
* A current host token, for the next connection. Throws when neither renewal nor join works,
|
|
28
|
+
* or when `signal` drops the attempt.
|
|
29
|
+
*/
|
|
30
|
+
token(signal?: AbortSignal): Promise<string>;
|
|
28
31
|
/** `HTTPS_PROXY` and friends with the current egress token; none before the first join. */
|
|
29
32
|
proxyEnv(): Record<string, string>;
|
|
30
33
|
/** The host epoch of the tokens held. */
|
package/dist/harness/pod.js
CHANGED
|
@@ -38,7 +38,8 @@ export function podHost(config, logger, options = {}) {
|
|
|
38
38
|
let afterSeed = false;
|
|
39
39
|
/** The CA in the bundle on disk, once one is written. */
|
|
40
40
|
let bundled;
|
|
41
|
-
const post = async (path, init) => {
|
|
41
|
+
const post = async (path, init, signal) => {
|
|
42
|
+
const timeout = AbortSignal.timeout(hostTuning().harness.podJoinTimeoutMs);
|
|
42
43
|
const response = await call(`${config.httpUrl}/nylorun/harness/v1/host/${path}`, {
|
|
43
44
|
method: "POST",
|
|
44
45
|
headers: {
|
|
@@ -48,7 +49,7 @@ export function podHost(config, logger, options = {}) {
|
|
|
48
49
|
...(path === "join" && imageEngine ? { [IMAGE_ENGINE_HEADER]: RUNTIME_VERSION } : {}),
|
|
49
50
|
},
|
|
50
51
|
...(init.body === undefined ? {} : { body: JSON.stringify(init.body) }),
|
|
51
|
-
signal: AbortSignal.timeout
|
|
52
|
+
signal: signal ? AbortSignal.any([timeout, signal]) : timeout,
|
|
52
53
|
});
|
|
53
54
|
const text = await response.text();
|
|
54
55
|
if (response.status === 401 || response.status === 400)
|
|
@@ -97,7 +98,7 @@ export function podHost(config, logger, options = {}) {
|
|
|
97
98
|
return id;
|
|
98
99
|
}
|
|
99
100
|
};
|
|
100
|
-
const join = async () => {
|
|
101
|
+
const join = async (signal) => {
|
|
101
102
|
const joinToken = (await readFile(config.joinFile, "utf8")).trim();
|
|
102
103
|
const volume = await volumeId();
|
|
103
104
|
held = await post("join", {
|
|
@@ -107,7 +108,7 @@ export function podHost(config, logger, options = {}) {
|
|
|
107
108
|
joinToken,
|
|
108
109
|
...(volume === undefined ? {} : { volumeId: volume }),
|
|
109
110
|
},
|
|
110
|
-
});
|
|
111
|
+
}, signal);
|
|
111
112
|
logger.info("sandbox_host_joined", { sandboxId: config.sandboxId, epoch: held.epoch });
|
|
112
113
|
return held;
|
|
113
114
|
};
|
|
@@ -122,11 +123,11 @@ export function podHost(config, logger, options = {}) {
|
|
|
122
123
|
signal: AbortSignal.timeout(hostTuning().harness.podRequestTimeoutMs),
|
|
123
124
|
});
|
|
124
125
|
};
|
|
125
|
-
const renew = async () => {
|
|
126
|
+
const renew = async (signal) => {
|
|
126
127
|
if (!held)
|
|
127
128
|
return undefined;
|
|
128
129
|
try {
|
|
129
|
-
held = await post("renew", { bearer: held.hostToken });
|
|
130
|
+
held = await post("renew", { bearer: held.hostToken }, signal);
|
|
130
131
|
return held;
|
|
131
132
|
}
|
|
132
133
|
catch (error) {
|
|
@@ -143,8 +144,8 @@ export function podHost(config, logger, options = {}) {
|
|
|
143
144
|
}, options.renewMs ?? hostTuning().harness.podRenewMs);
|
|
144
145
|
timer.unref();
|
|
145
146
|
return {
|
|
146
|
-
async token() {
|
|
147
|
-
const current = (await renew()) ?? (await join());
|
|
147
|
+
async token(signal) {
|
|
148
|
+
const current = (await renew(signal)) ?? (await join(signal));
|
|
148
149
|
return current.hostToken;
|
|
149
150
|
},
|
|
150
151
|
proxyEnv() {
|
|
@@ -7,6 +7,7 @@ import { type EngineTuning } from "../tuning.js";
|
|
|
7
7
|
export interface HarnessServiceOptions {
|
|
8
8
|
readonly url: string;
|
|
9
9
|
readonly token: HarnessClientOptions["token"];
|
|
10
|
+
readonly redialOnClockJump?: boolean;
|
|
10
11
|
/** Where workspaces (and their records) live. */
|
|
11
12
|
readonly paths: {
|
|
12
13
|
readonly sandboxes: string;
|
package/dist/harness/service.js
CHANGED
|
@@ -5,18 +5,24 @@
|
|
|
5
5
|
* core's `workspace.*` requests (`serve`). When the connection is lost its runs stop (core gives
|
|
6
6
|
* their segments to the next advance, which resumes them by replay) and the client connects
|
|
7
7
|
* again after a jittered backoff (`harness.reconnect` in the Host tuning, 0.5 to 10 s).
|
|
8
|
+
*
|
|
9
|
+
* A pod's engine (`redialOnClockJump`) also watches for a freeze and resume (an Agent Substrate
|
|
10
|
+
* restore): it drops the connection attempt in flight, token request included, and dials again
|
|
11
|
+
* at once, instead of waiting for TCP to resend what the freeze lost.
|
|
8
12
|
*/
|
|
9
13
|
import { type HarnessChannel, type RequestHandler } from "@nylorun/core/harness-api";
|
|
10
14
|
import { type HarnessOptions } from "@nylorun/harness/api";
|
|
11
15
|
import { type WsPortOptions } from "./ws-port.js";
|
|
16
|
+
import { watchClockJumps } from "./clock-jump.js";
|
|
12
17
|
export interface HarnessClientOptions extends Omit<HarnessOptions, "channel" | "logger"> {
|
|
13
18
|
/** The Harness API, e.g. `ws://runtime:4200/nylorun/harness/v1` (`NYLORUN_HARNESS_URL`). */
|
|
14
19
|
readonly url: string;
|
|
15
20
|
/**
|
|
16
21
|
* `NYLORUN_HARNESS_TOKEN`, or, for a pod sandbox's engine (F7.2), a function that answers
|
|
17
|
-
* a current host token before each connection (it renews, or joins again).
|
|
22
|
+
* a current host token before each connection (it renews, or joins again). The signal aborts
|
|
23
|
+
* it when the attempt is dropped.
|
|
18
24
|
*/
|
|
19
|
-
readonly token: string | (() => Promise<string>);
|
|
25
|
+
readonly token: string | ((signal?: AbortSignal) => Promise<string>);
|
|
20
26
|
readonly logger: {
|
|
21
27
|
info(message: string, fields?: Record<string, unknown>): void;
|
|
22
28
|
warn(message: string, fields?: Record<string, unknown>): void;
|
|
@@ -29,6 +35,13 @@ export interface HarnessClientOptions extends Omit<HarnessOptions, "channel" | "
|
|
|
29
35
|
readonly maxMs: number;
|
|
30
36
|
};
|
|
31
37
|
readonly liveness?: Omit<WsPortOptions, "ping">;
|
|
38
|
+
/**
|
|
39
|
+
* Drop an attempt in flight and dial again at once when the process was frozen and resumed
|
|
40
|
+
* (`clock-jump.ts`): a pod's engine, which Agent Substrate suspends to a memory snapshot.
|
|
41
|
+
*/
|
|
42
|
+
readonly redialOnClockJump?: boolean;
|
|
43
|
+
/** The clock and timings `redialOnClockJump` watches with (tests set them). */
|
|
44
|
+
readonly clockJumps?: Parameters<typeof watchClockJumps>[1];
|
|
32
45
|
}
|
|
33
46
|
export interface HarnessClient {
|
|
34
47
|
/** Resolves once the first connection's `hello` is answered. */
|
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
* core's `workspace.*` requests (`serve`). When the connection is lost its runs stop (core gives
|
|
6
6
|
* their segments to the next advance, which resumes them by replay) and the client connects
|
|
7
7
|
* again after a jittered backoff (`harness.reconnect` in the Host tuning, 0.5 to 10 s).
|
|
8
|
+
*
|
|
9
|
+
* A pod's engine (`redialOnClockJump`) also watches for a freeze and resume (an Agent Substrate
|
|
10
|
+
* restore): it drops the connection attempt in flight, token request included, and dials again
|
|
11
|
+
* at once, instead of waiting for TCP to resend what the freeze lost.
|
|
8
12
|
*/
|
|
9
13
|
import { createChannel } from "@nylorun/core/harness-api";
|
|
10
14
|
import { createHarness } from "@nylorun/harness/api";
|
|
@@ -12,6 +16,7 @@ import { WebSocket } from "ws";
|
|
|
12
16
|
import { HARNESS_API_HEADER, HARNESS_API_TRANSPORT, HARNESS_MAX_PAYLOAD } from "@nylorun/core/harness-api";
|
|
13
17
|
import { wsPort } from "./ws-port.js";
|
|
14
18
|
import { hostTuning } from "../tuning.js";
|
|
19
|
+
import { watchClockJumps } from "./clock-jump.js";
|
|
15
20
|
export function connectHarness(options) {
|
|
16
21
|
const { logger } = options;
|
|
17
22
|
const tuning = hostTuning().harness;
|
|
@@ -20,30 +25,49 @@ export function connectHarness(options) {
|
|
|
20
25
|
let connected = false;
|
|
21
26
|
let current;
|
|
22
27
|
let wake;
|
|
28
|
+
/** The connection attempt in flight (token, then handshake), until it opens or fails. */
|
|
29
|
+
let attempting;
|
|
30
|
+
let attempt = 0;
|
|
23
31
|
let markReady;
|
|
24
32
|
const ready = new Promise((resolve) => (markReady = resolve));
|
|
25
33
|
const open = async () => {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
socket.once("unexpected-response", (_request, response) => {
|
|
38
|
-
socket.removeAllListeners("error");
|
|
39
|
-
socket.on("error", () => undefined);
|
|
40
|
-
reject(new Error(`The Harness API refused the connection: ${response.statusCode ?? 0}`));
|
|
41
|
-
response.resume();
|
|
42
|
-
socket.terminate();
|
|
43
|
-
});
|
|
44
|
-
socket.once("error", reject);
|
|
45
|
-
});
|
|
34
|
+
const controller = new AbortController();
|
|
35
|
+
attempting = controller;
|
|
36
|
+
try {
|
|
37
|
+
const token = typeof options.token === "string" ? options.token : await options.token(controller.signal);
|
|
38
|
+
controller.signal.throwIfAborted();
|
|
39
|
+
return await dial(token, controller.signal);
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
if (attempting === controller)
|
|
43
|
+
attempting = undefined;
|
|
44
|
+
}
|
|
46
45
|
};
|
|
46
|
+
const dial = (token, signal) => new Promise((resolve, reject) => {
|
|
47
|
+
const socket = new WebSocket(options.url, {
|
|
48
|
+
headers: { authorization: `Bearer ${token}`, [HARNESS_API_HEADER]: HARNESS_API_TRANSPORT },
|
|
49
|
+
maxPayload: HARNESS_MAX_PAYLOAD,
|
|
50
|
+
handshakeTimeout: tuning.handshakeTimeoutMs,
|
|
51
|
+
});
|
|
52
|
+
socket.once("open", () => {
|
|
53
|
+
socket.removeAllListeners("error");
|
|
54
|
+
resolve(socket);
|
|
55
|
+
});
|
|
56
|
+
socket.once("unexpected-response", (_request, response) => {
|
|
57
|
+
socket.removeAllListeners("error");
|
|
58
|
+
socket.on("error", () => undefined);
|
|
59
|
+
reject(new Error(`The Harness API refused the connection: ${response.statusCode ?? 0}`));
|
|
60
|
+
response.resume();
|
|
61
|
+
socket.terminate();
|
|
62
|
+
});
|
|
63
|
+
socket.once("error", reject);
|
|
64
|
+
signal.addEventListener("abort", () => {
|
|
65
|
+
socket.removeAllListeners("error");
|
|
66
|
+
socket.on("error", () => undefined);
|
|
67
|
+
socket.terminate();
|
|
68
|
+
reject(signal.reason instanceof Error ? signal.reason : new Error("connection attempt dropped"));
|
|
69
|
+
}, { once: true });
|
|
70
|
+
});
|
|
47
71
|
const pause = (ms) => new Promise((resolve) => {
|
|
48
72
|
const timer = setTimeout(done, ms);
|
|
49
73
|
timer.unref();
|
|
@@ -81,8 +105,16 @@ export function connectHarness(options) {
|
|
|
81
105
|
current = undefined;
|
|
82
106
|
}
|
|
83
107
|
};
|
|
108
|
+
const watch = options.redialOnClockJump
|
|
109
|
+
? watchClockJumps((jumpedMs) => {
|
|
110
|
+
const inFlight = attempting !== undefined;
|
|
111
|
+
logger.info("harness_clock_jumped", { jumpedMs, redial: inFlight || wake !== undefined });
|
|
112
|
+
attempt = 0;
|
|
113
|
+
attempting?.abort(new Error(`the process resumed after ${jumpedMs} ms`));
|
|
114
|
+
wake?.();
|
|
115
|
+
}, options.clockJumps)
|
|
116
|
+
: undefined;
|
|
84
117
|
const loop = (async () => {
|
|
85
|
-
let attempt = 0;
|
|
86
118
|
while (!stopped) {
|
|
87
119
|
const started = Date.now();
|
|
88
120
|
try {
|
|
@@ -113,6 +145,8 @@ export function connectHarness(options) {
|
|
|
113
145
|
channel: () => current?.channel,
|
|
114
146
|
async stop(waitMs = tuning.stopMs) {
|
|
115
147
|
stopped = true;
|
|
148
|
+
watch?.stop();
|
|
149
|
+
attempting?.abort(new Error("harness stopped"));
|
|
116
150
|
wake?.();
|
|
117
151
|
const live = current;
|
|
118
152
|
if (live) {
|
|
@@ -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,
|
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.
|
|
5
|
+
"version": "0.46.1-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:
|
|
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);
|
package/dist/sandbox/tools.d.ts
CHANGED
|
@@ -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
|
package/dist/sandbox/tools.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
/**
|
|
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;
|
package/dist/tenant/triggers.js
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
|
|
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.
|
|
1
|
+
export declare const RUNTIME_VERSION = "0.46.1-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.
|
|
3
|
+
export const RUNTIME_VERSION = "0.46.1-beta";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nylorun/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.46.1-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.
|
|
69
|
-
"@nylorun/harness": "0.
|
|
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",
|