nylorun 0.15.1-beta → 0.17.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 +134 -0
- package/README.md +136 -74
- package/dist/cli.js +31 -12
- package/dist/client/agent-manifests.d.ts +4 -0
- package/dist/client/agent-manifests.js +100 -0
- package/dist/client/agents.d.ts +1 -3
- package/dist/client/agents.js +459 -117
- package/dist/client/apply.d.ts +7 -0
- package/dist/client/apply.js +99 -0
- package/dist/client/artifacts.js +3 -3
- package/dist/client/doctor.d.ts +1 -1
- package/dist/client/doctor.js +2 -2
- package/dist/client/io.d.ts +2 -0
- package/dist/client/io.js +11 -0
- package/dist/client/mcp.d.ts +5 -3
- package/dist/client/mcp.js +28 -25
- package/dist/client/migrate.d.ts +4 -0
- package/dist/client/migrate.js +140 -0
- package/dist/client/model/pi-ai.d.ts +1 -1
- package/dist/client/model/pi-ai.js +1 -1
- package/dist/client/models.d.ts +5 -2
- package/dist/client/models.js +17 -8
- package/dist/client/runtime.js +3 -3
- package/dist/client/sessions.js +29 -18
- package/dist/client/settings.d.ts +5 -5
- package/dist/client/settings.js +38 -18
- package/dist/client/tenant.d.ts +1 -1
- package/dist/client/tenant.js +10 -7
- package/dist/project/seed.js +2 -1
- package/dist/sandbox/commands.js +10 -8
- package/dist/sandbox/kubectl.js +2 -1
- package/dist/sandbox/manifests.js +3 -2
- package/dist/sandbox/probe.js +9 -7
- package/dist/stack/commands.js +12 -11
- package/dist/stack/compose-file.d.ts +3 -0
- package/dist/stack/compose-file.js +32 -36
- package/dist/stack/operator-keys.js +7 -6
- package/dist/stack/ports.js +2 -1
- package/dist/stack/studio-login.js +2 -1
- package/dist/tuning.d.ts +90 -0
- package/dist/tuning.js +143 -0
- package/package.json +9 -9
- package/dist/client/environment-manifests.d.ts +0 -10
- package/dist/client/environment-manifests.js +0 -173
- package/dist/client/environments.d.ts +0 -4
- package/dist/client/environments.js +0 -295
|
@@ -68,6 +68,9 @@
|
|
|
68
68
|
*
|
|
69
69
|
* With `identity` (when `<Host root>/identity.yaml` exists) the runtime reads its trusted issuers
|
|
70
70
|
* from it, through the Host root mount (NYLORUN_IDENTITY_FILE=/nylorun/identity.yaml).
|
|
71
|
+
*
|
|
72
|
+
* The healthchecks and Postgres's WAL cap come from the CLI tuning (`NYLORUN_CLI_TUNING`). The
|
|
73
|
+
* `tmpfs` masks are empty: 4096 bytes is one page, the least a tmpfs takes (Linux's page size).
|
|
71
74
|
*/
|
|
72
75
|
export declare function renderComposeFile(project: string, name: string, options?: {
|
|
73
76
|
sandboxes?: true;
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import { PINNED_IMAGES } from "./images.js";
|
|
2
|
+
import { cliTuning } from "../tuning.js";
|
|
3
|
+
/** A service's healthcheck lines, from the CLI tuning (`compose.healthchecks`). */
|
|
4
|
+
function healthcheck(service) {
|
|
5
|
+
const { intervalSeconds, timeoutSeconds, retries } = cliTuning().compose.healthchecks[service];
|
|
6
|
+
return ` interval: ${intervalSeconds}s\n timeout: ${timeoutSeconds}s\n retries: ${retries}\n`;
|
|
7
|
+
}
|
|
8
|
+
/** Postgres's `max_slot_wal_keep_size` (`compose.walKeepSizeMb`), in GB when it is whole GB. */
|
|
9
|
+
function walKeepSize() {
|
|
10
|
+
const mb = cliTuning().compose.walKeepSizeMb;
|
|
11
|
+
return mb % 1024 === 0 ? `${mb / 1024}GB` : `${mb}MB`;
|
|
12
|
+
}
|
|
2
13
|
/**
|
|
3
14
|
* A local Tenant's Compose file (Runtime Architecture §14.3), written to
|
|
4
15
|
* `<Host root>/docker/compose.yaml` by `nylorun start`. Each local Tenant is one installation
|
|
@@ -69,6 +80,9 @@ import { PINNED_IMAGES } from "./images.js";
|
|
|
69
80
|
*
|
|
70
81
|
* With `identity` (when `<Host root>/identity.yaml` exists) the runtime reads its trusted issuers
|
|
71
82
|
* from it, through the Host root mount (NYLORUN_IDENTITY_FILE=/nylorun/identity.yaml).
|
|
83
|
+
*
|
|
84
|
+
* The healthchecks and Postgres's WAL cap come from the CLI tuning (`NYLORUN_CLI_TUNING`). The
|
|
85
|
+
* `tmpfs` masks are empty: 4096 bytes is one page, the least a tmpfs takes (Linux's page size).
|
|
72
86
|
*/
|
|
73
87
|
export function renderComposeFile(project, name, options = {}) {
|
|
74
88
|
const sandboxes = options.sandboxes === true;
|
|
@@ -86,8 +100,8 @@ services:
|
|
|
86
100
|
image: ${PINNED_IMAGES.postgres}
|
|
87
101
|
container_name: ${project}-postgres
|
|
88
102
|
labels: *tenant
|
|
89
|
-
# Logical replication feeds the stream relay; a stuck slot is capped
|
|
90
|
-
command: ["postgres", "-c", "wal_level=logical", "-c", "max_slot_wal_keep_size
|
|
103
|
+
# Logical replication feeds the stream relay; a stuck slot's WAL is capped (compose.walKeepSizeMb).
|
|
104
|
+
command: ["postgres", "-c", "wal_level=logical", "-c", "max_slot_wal_keep_size=${walKeepSize()}"]
|
|
91
105
|
environment:
|
|
92
106
|
POSTGRES_USER: nylorun
|
|
93
107
|
POSTGRES_PASSWORD: \${NYLORUN_POSTGRES_PASSWORD:?run nylorun start}
|
|
@@ -98,10 +112,7 @@ services:
|
|
|
98
112
|
networks: [store]
|
|
99
113
|
healthcheck:
|
|
100
114
|
test: ["CMD-SHELL", "pg_isready -U nylorun -d nylorun"]
|
|
101
|
-
|
|
102
|
-
timeout: 5s
|
|
103
|
-
retries: 30
|
|
104
|
-
restart: unless-stopped
|
|
115
|
+
${healthcheck("postgres")} restart: unless-stopped
|
|
105
116
|
|
|
106
117
|
restate: # Durable Session Execution
|
|
107
118
|
image: ${PINNED_IMAGES.restate}
|
|
@@ -115,10 +126,7 @@ services:
|
|
|
115
126
|
- \${NYLORUN_HOST_ROOT:?run nylorun start}/docker/restate-identity.pem:/run/nylorun/restate-identity.pem:ro
|
|
116
127
|
${restateUi ? RESTATE_UI : RESTATE_CLOSED} healthcheck:
|
|
117
128
|
test: ["CMD", "curl", "-fsS", "http://localhost:9070/health"]
|
|
118
|
-
|
|
119
|
-
timeout: 5s
|
|
120
|
-
retries: 30
|
|
121
|
-
restart: unless-stopped
|
|
129
|
+
${healthcheck("restate")} restart: unless-stopped
|
|
122
130
|
|
|
123
131
|
s2-lite: # Durable Streams; not published, only the Runtime reaches it
|
|
124
132
|
image: ${PINNED_IMAGES.s2}
|
|
@@ -150,10 +158,7 @@ ${restateUi ? RESTATE_UI : RESTATE_CLOSED} healthcheck:
|
|
|
150
158
|
networks: [store]
|
|
151
159
|
healthcheck:
|
|
152
160
|
test: ["CMD", "curl", "-fsS", "http://localhost:9000/health"]
|
|
153
|
-
|
|
154
|
-
timeout: 5s
|
|
155
|
-
retries: 30
|
|
156
|
-
restart: unless-stopped
|
|
161
|
+
${healthcheck("rustfs")} restart: unless-stopped
|
|
157
162
|
|
|
158
163
|
gateway: # the Model and Tool Gates and keys: the vault key, credentials, signing, outbound calls; not published${sandboxes ? ", but for sandbox pods" : ""}
|
|
159
164
|
image: \${NYLORUN_RUNTIME_IMAGE:?run nylorun start}
|
|
@@ -175,7 +180,9 @@ ${restateUi ? RESTATE_UI : RESTATE_CLOSED} healthcheck:
|
|
|
175
180
|
NYLORUN_OBJECT_STORE_ACCESS_KEY: nylorun
|
|
176
181
|
NYLORUN_OBJECT_STORE_SECRET_KEY: \${NYLORUN_OBJECT_STORE_SECRET_KEY:?run nylorun start}
|
|
177
182
|
# MCP servers and HTTP tools on this machine: \`localhost\` in their URLs means the Docker host.
|
|
178
|
-
NYLORUN_ENDPOINT_LOOPBACK: docker-host
|
|
183
|
+
NYLORUN_ENDPOINT_LOOPBACK: docker-host
|
|
184
|
+
# Host tuning (timings, retries, internal sizes; JSON): from the shell that runs \`nylorun start\`.
|
|
185
|
+
NYLORUN_TUNING: \${NYLORUN_TUNING:-}${sandboxes ? SANDBOXES_GATEWAY_ENV : ""}
|
|
179
186
|
extra_hosts:
|
|
180
187
|
host.docker.internal: host-gateway # model servers, MCP servers and HTTP tools on this machine
|
|
181
188
|
volumes:
|
|
@@ -186,10 +193,7 @@ ${sandboxes ? SANDBOXES_GATES_PORT : ""} # Egress and the stores; the harness
|
|
|
186
193
|
networks: [default, store, harness]
|
|
187
194
|
healthcheck:
|
|
188
195
|
test: ["CMD", "node", "-e", "fetch('http://localhost:4100/ready').then(r=>process.exit(r.ok?0:1),()=>process.exit(1))"]
|
|
189
|
-
|
|
190
|
-
timeout: 5s
|
|
191
|
-
retries: 60
|
|
192
|
-
restart: unless-stopped
|
|
196
|
+
${healthcheck("gateway")} restart: unless-stopped
|
|
193
197
|
|
|
194
198
|
runtime:
|
|
195
199
|
image: \${NYLORUN_RUNTIME_IMAGE:?run nylorun start}
|
|
@@ -247,7 +251,10 @@ ${sandboxes ? SANDBOXES_GATES_PORT : ""} # Egress and the stores; the harness
|
|
|
247
251
|
OTEL_SERVICE_NAME: \${OTEL_SERVICE_NAME:-}
|
|
248
252
|
OTEL_RESOURCE_ATTRIBUTES: \${OTEL_RESOURCE_ATTRIBUTES:-}
|
|
249
253
|
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT: \${OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT:-}
|
|
250
|
-
NYLORUN_TRACE_CONTENT: \${NYLORUN_TRACE_CONTENT:-}
|
|
254
|
+
NYLORUN_TRACE_CONTENT: \${NYLORUN_TRACE_CONTENT:-}
|
|
255
|
+
# Host tuning (timings, retries, internal sizes; JSON): from the shell that runs \`nylorun start\`.
|
|
256
|
+
# Sandbox pods get its harness and engine groups from the runtime.
|
|
257
|
+
NYLORUN_TUNING: \${NYLORUN_TUNING:-}${sandboxes ? SANDBOXES_RUNTIME_ENV : ""}${identity ? IDENTITY_RUNTIME_ENV : ""}
|
|
251
258
|
extra_hosts:
|
|
252
259
|
host.docker.internal: host-gateway # the Docker host, also on Linux Docker Engine
|
|
253
260
|
volumes:
|
|
@@ -268,10 +275,7 @@ ${sandboxes ? SANDBOXES_GATES_PORT : ""} # Egress and the stores; the harness
|
|
|
268
275
|
- "127.0.0.1:\${NYLORUN_PORT:?run nylorun start}:4000" # Runtime and Management APIs, SSE, browsers${sandboxes ? SANDBOXES_RUNTIME_PORT : ""}
|
|
269
276
|
healthcheck: # liveness: a Tenant that cannot open is reported by nylorun start (/ready, nylorun-operate status), not by a 300 s wait
|
|
270
277
|
test: ["CMD", "node", "-e", "fetch('http://localhost:4000/health').then(r=>process.exit(r.ok?0:1),()=>process.exit(1))"]
|
|
271
|
-
|
|
272
|
-
timeout: 5s
|
|
273
|
-
retries: 60
|
|
274
|
-
restart: unless-stopped
|
|
278
|
+
${healthcheck("runtime")} restart: unless-stopped
|
|
275
279
|
|
|
276
280
|
studio: # dashboard + trusted proxy
|
|
277
281
|
image: \${NYLORUN_STUDIO_IMAGE:?run nylorun start}
|
|
@@ -303,10 +307,7 @@ ${sandboxes ? SANDBOXES_GATES_PORT : ""} # Egress and the stores; the harness
|
|
|
303
307
|
- "127.0.0.1:\${NYLORUN_STUDIO_PORT:?run nylorun start}:3000" # opened as http://localhost:<port>
|
|
304
308
|
healthcheck: # /healthz needs no session; Host studio:3000 is accepted
|
|
305
309
|
test: ["CMD", "node", "-e", "require('http').get({port:3000,path:'/healthz',headers:{host:'studio:3000'}},r=>process.exit(r.statusCode===200?0:1)).on('error',()=>process.exit(1))"]
|
|
306
|
-
|
|
307
|
-
timeout: 5s
|
|
308
|
-
retries: 30
|
|
309
|
-
restart: unless-stopped
|
|
310
|
+
${healthcheck("studio")} restart: unless-stopped
|
|
310
311
|
${remote ? harnessService(project) : ""}${sandboxes ? sandboxesService(project) : ""}
|
|
311
312
|
networks:
|
|
312
313
|
default:
|
|
@@ -357,16 +358,14 @@ function harnessService(project) {
|
|
|
357
358
|
NYLORUN_HARNESS_TOKEN: \${NYLORUN_HARNESS_TOKEN:?run nylorun start}
|
|
358
359
|
NYLORUN_GATES_URL: http://gateway:4100
|
|
359
360
|
NYLORUN_HARNESS_ROOT: /harness
|
|
361
|
+
NYLORUN_TUNING: \${NYLORUN_TUNING:-} # Host tuning, as the runtime has it
|
|
360
362
|
volumes:
|
|
361
363
|
# The Tenant's workspaces; nothing else.
|
|
362
364
|
- \${NYLORUN_HOST_ROOT:?run nylorun start}/tenant/sandboxes:/harness/sandboxes
|
|
363
365
|
networks: [harness]
|
|
364
366
|
healthcheck: # healthy once connected to core's Harness API
|
|
365
367
|
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:4300/health').then(r=>r.json()).then(b=>process.exit(b.connected?0:1),()=>process.exit(1))"]
|
|
366
|
-
|
|
367
|
-
timeout: 5s
|
|
368
|
-
retries: 90
|
|
369
|
-
restart: unless-stopped
|
|
368
|
+
${healthcheck("harness")} restart: unless-stopped
|
|
370
369
|
`;
|
|
371
370
|
}
|
|
372
371
|
/**
|
|
@@ -428,9 +427,6 @@ function sandboxesService(project) {
|
|
|
428
427
|
networks: [default] # the cluster's API server, through host.docker.internal
|
|
429
428
|
healthcheck: # /ready: informers synced and the API server answers
|
|
430
429
|
test: ["CMD", "/sandboxes", "healthcheck"]
|
|
431
|
-
|
|
432
|
-
timeout: 5s
|
|
433
|
-
retries: 24
|
|
434
|
-
restart: unless-stopped
|
|
430
|
+
${healthcheck("sandboxes")} restart: unless-stopped
|
|
435
431
|
`;
|
|
436
432
|
}
|
|
@@ -8,6 +8,7 @@ import { open, rm, stat } from "node:fs/promises";
|
|
|
8
8
|
import { PROTOCOL_HEADER, PROTOCOL_VERSION } from "@nylorun/core/compatibility";
|
|
9
9
|
import { CliError } from "../errors.js";
|
|
10
10
|
import { readCredentialsFile, writeCredentialsFile, } from "../project/link.js";
|
|
11
|
+
import { cliTuning } from "../tuning.js";
|
|
11
12
|
/** The keys `nylorun start` gives the projects it links (and `-management`). */
|
|
12
13
|
export const PROJECT_KEY_ID = "project";
|
|
13
14
|
/** The keys nylorun commands use outside a linked project (and `-management`). */
|
|
@@ -56,7 +57,7 @@ export async function keyAuthenticates(fetch, runtimeUrl, key) {
|
|
|
56
57
|
accept: "application/json",
|
|
57
58
|
},
|
|
58
59
|
redirect: "error",
|
|
59
|
-
signal: AbortSignal.timeout(
|
|
60
|
+
signal: AbortSignal.timeout(cliTuning().stack.keyCheckMs),
|
|
60
61
|
});
|
|
61
62
|
}
|
|
62
63
|
catch (error) {
|
|
@@ -69,12 +70,12 @@ export async function keyAuthenticates(fetch, runtimeUrl, key) {
|
|
|
69
70
|
return false;
|
|
70
71
|
throw new CliError(`GET ${runtimeUrl}/v1/me returned ${response.status}.`, 7);
|
|
71
72
|
}
|
|
72
|
-
const
|
|
73
|
+
const lockTimeoutMs = () => cliTuning().stack.lockWaitMs;
|
|
73
74
|
/** A lock older than this was left by a process that died. */
|
|
74
|
-
const
|
|
75
|
+
const lockStaleMs = () => cliTuning().stack.lockStaleMs;
|
|
75
76
|
/** Runs `fn` holding the lock file `path` (created exclusively), so one process puts a key. */
|
|
76
77
|
async function withLock(path, fn) {
|
|
77
|
-
const deadline = Date.now() +
|
|
78
|
+
const deadline = Date.now() + lockTimeoutMs();
|
|
78
79
|
for (;;) {
|
|
79
80
|
try {
|
|
80
81
|
const handle = await open(path, "wx", 0o600);
|
|
@@ -85,13 +86,13 @@ async function withLock(path, fn) {
|
|
|
85
86
|
if (error.code !== "EEXIST")
|
|
86
87
|
throw error;
|
|
87
88
|
const held = await stat(path).catch(() => undefined);
|
|
88
|
-
if (held && Date.now() - held.mtimeMs >
|
|
89
|
+
if (held && Date.now() - held.mtimeMs > lockStaleMs()) {
|
|
89
90
|
await rm(path, { force: true });
|
|
90
91
|
continue;
|
|
91
92
|
}
|
|
92
93
|
if (Date.now() > deadline)
|
|
93
94
|
throw new CliError(`Another nylorun command holds ${path}. Remove it if none runs.`, 1);
|
|
94
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
95
|
+
await new Promise((resolve) => setTimeout(resolve, cliTuning().stack.lockPollMs));
|
|
95
96
|
}
|
|
96
97
|
}
|
|
97
98
|
try {
|
package/dist/stack/ports.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createServer } from "node:net";
|
|
2
|
+
import { cliTuning } from "../tuning.js";
|
|
2
3
|
export const DEFAULT_PORTS = {
|
|
3
4
|
runtime: 8787,
|
|
4
5
|
studio: 4161,
|
|
@@ -36,7 +37,7 @@ export async function choosePort(probe, preferred, persisted, taken) {
|
|
|
36
37
|
return persisted;
|
|
37
38
|
if (!taken.has(preferred) && (await probe.isFree(preferred)))
|
|
38
39
|
return preferred;
|
|
39
|
-
for (let attempt = 0; attempt <
|
|
40
|
+
for (let attempt = 0; attempt < cliTuning().stack.portAttempts; attempt += 1) {
|
|
40
41
|
const port = await probe.pickFree();
|
|
41
42
|
if (!taken.has(port))
|
|
42
43
|
return port;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { STACK_CLIENT_HOST } from "./host-files.js";
|
|
2
|
+
import { cliTuning } from "../tuning.js";
|
|
2
3
|
/** Studio's published origin: always `localhost` (the cookie's host). */
|
|
3
4
|
export function studioOrigin(port) {
|
|
4
5
|
return `http://${STACK_CLIENT_HOST}:${port}`;
|
|
@@ -20,7 +21,7 @@ export async function mintStudioLogin(input) {
|
|
|
20
21
|
},
|
|
21
22
|
body: "{}",
|
|
22
23
|
redirect: "error",
|
|
23
|
-
signal: AbortSignal.timeout(input.timeoutMs ??
|
|
24
|
+
signal: AbortSignal.timeout(input.timeoutMs ?? cliTuning().stack.loginRequestMs),
|
|
24
25
|
});
|
|
25
26
|
if (!response.ok)
|
|
26
27
|
throw new Error(`Studio refused a login token (HTTP ${response.status}).`);
|
package/dist/tuning.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/** A Compose healthcheck: seconds between checks, seconds one check may take, and failures in a row. */
|
|
2
|
+
export interface Healthcheck {
|
|
3
|
+
readonly intervalSeconds: number;
|
|
4
|
+
readonly timeoutSeconds: number;
|
|
5
|
+
readonly retries: number;
|
|
6
|
+
}
|
|
7
|
+
export declare const DEFAULT_CLI_TUNING: {
|
|
8
|
+
/** `nylorun start` and the commands that reach the local stack. */
|
|
9
|
+
stack: {
|
|
10
|
+
/** One `/health` or `/ready` probe of the Runtime. */
|
|
11
|
+
probeTimeoutMs: number;
|
|
12
|
+
/** How long `start` waits for the Runtime's `/health`, and then for the Tenant to open. */
|
|
13
|
+
healthWaitMs: number;
|
|
14
|
+
tenantWaitMs: number;
|
|
15
|
+
/** How often `start` polls while it waits. */
|
|
16
|
+
pollMs: number;
|
|
17
|
+
/** While the Tenant opens, `nylorun-operate status` is asked every this many polls. */
|
|
18
|
+
operateEveryPolls: number;
|
|
19
|
+
/** `docker compose up --wait-timeout` of the core services, and of Studio, in seconds. */
|
|
20
|
+
composeWaitSeconds: number;
|
|
21
|
+
studioWaitSeconds: number;
|
|
22
|
+
/** How long a Studio login is retried, and how long one request for it may take. */
|
|
23
|
+
loginWaitMs: number;
|
|
24
|
+
loginRequestMs: number;
|
|
25
|
+
/** How long a key check against the Runtime (`/v1/me`) may take. */
|
|
26
|
+
keyCheckMs: number;
|
|
27
|
+
/** The local key file's lock: how long to wait for it, when a held one is stale, how often to look. */
|
|
28
|
+
lockWaitMs: number;
|
|
29
|
+
lockStaleMs: number;
|
|
30
|
+
lockPollMs: number;
|
|
31
|
+
/** Tries at picking a free port. */
|
|
32
|
+
portAttempts: number;
|
|
33
|
+
/** How long one seed or model call to the Management API may take (`nylorun dev`). */
|
|
34
|
+
seedRequestMs: number;
|
|
35
|
+
};
|
|
36
|
+
/** What `nylorun start` writes into the Compose file. */
|
|
37
|
+
compose: {
|
|
38
|
+
/** Postgres's `max_slot_wal_keep_size`: the WAL a stuck replication slot may hold, in MiB. */
|
|
39
|
+
walKeepSizeMb: number;
|
|
40
|
+
healthchecks: {
|
|
41
|
+
postgres: Healthcheck;
|
|
42
|
+
restate: Healthcheck;
|
|
43
|
+
rustfs: Healthcheck;
|
|
44
|
+
gateway: Healthcheck;
|
|
45
|
+
runtime: Healthcheck;
|
|
46
|
+
studio: Healthcheck;
|
|
47
|
+
harness: Healthcheck;
|
|
48
|
+
sandboxes: Healthcheck;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
/** `nylorun local sandbox` (agent-sandbox on a local Kubernetes cluster). */
|
|
52
|
+
sandbox: {
|
|
53
|
+
/** How long a `kubectl` call may run before it is killed, and how much longer than a wait it was told. */
|
|
54
|
+
kubectlTimeoutMs: number;
|
|
55
|
+
kubectlMarginMs: number;
|
|
56
|
+
/** Downloading the agent-sandbox manifest. */
|
|
57
|
+
manifestDownloadMs: number;
|
|
58
|
+
/** `kubectl wait` for the CRD, and `rollout status` of the controller, in seconds. */
|
|
59
|
+
crdWaitSeconds: number;
|
|
60
|
+
rolloutWaitSeconds: number;
|
|
61
|
+
/** How long to wait for the service account's token Secret. */
|
|
62
|
+
tokenWaitMs: number;
|
|
63
|
+
/** How often install and the network probe poll. */
|
|
64
|
+
pollMs: number;
|
|
65
|
+
/** `docker compose up --wait-timeout` of the sandboxes service, in seconds. */
|
|
66
|
+
composeWaitSeconds: number;
|
|
67
|
+
/** How long the sandboxes service's `/v1/info`, read from the runtime container, may take. */
|
|
68
|
+
infoRequestMs: number;
|
|
69
|
+
/** The reachability probe: `wget -T` in seconds, and the `kubectl exec` around it. */
|
|
70
|
+
probeWgetSeconds: number;
|
|
71
|
+
probeExecMs: number;
|
|
72
|
+
/** How long probe pods may take to be Ready, in seconds, and the NetworkPolicy to apply. */
|
|
73
|
+
probeReadySeconds: number;
|
|
74
|
+
policyTimeoutMs: number;
|
|
75
|
+
/** How long image pre-pulls may take, and how often they are checked. */
|
|
76
|
+
prepullMs: number;
|
|
77
|
+
prepullPollMs: number;
|
|
78
|
+
/** A probe pod's `terminationGracePeriodSeconds`, and its resources (Kubernetes quantities). */
|
|
79
|
+
probeGraceSeconds: number;
|
|
80
|
+
probeCpu: string;
|
|
81
|
+
probeMemory: string;
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
type Widen<T> = T extends number ? number : T extends string ? string : {
|
|
85
|
+
readonly [K in keyof T]: Widen<T[K]>;
|
|
86
|
+
};
|
|
87
|
+
export type CliTuning = Widen<typeof DEFAULT_CLI_TUNING>;
|
|
88
|
+
/** The CLI's tuning: `NYLORUN_CLI_TUNING` over the defaults, read now. */
|
|
89
|
+
export declare function cliTuning(env?: Readonly<Record<string, string | undefined>>): CliTuning;
|
|
90
|
+
export {};
|
package/dist/tuning.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CLI's own timings and sizes (B15): how long `nylorun` waits for the local stack, how often
|
|
3
|
+
* it polls, the healthchecks it writes into the Compose file, and the waits of `nylorun local
|
|
4
|
+
* sandbox`. Each has a default, the value the CLI used before it was configuration. Set any part
|
|
5
|
+
* in `NYLORUN_CLI_TUNING`, a JSON object of the groups below
|
|
6
|
+
* (`{"stack":{"healthWaitMs":120000}}`), read each time a value is used; an unknown key or a value
|
|
7
|
+
* that is not a positive whole number stops the command naming it.
|
|
8
|
+
*
|
|
9
|
+
* The Runtime's own timings are its Host tuning (`NYLORUN_TUNING`), which `nylorun start` passes
|
|
10
|
+
* to its containers; Tenant-facing limits are the Runtime limits (`nylorun tenant settings
|
|
11
|
+
* limits`). Neither is here.
|
|
12
|
+
*/
|
|
13
|
+
import { CliError } from "./errors.js";
|
|
14
|
+
const check = (retries, intervalSeconds = 2, timeoutSeconds = 5) => ({
|
|
15
|
+
intervalSeconds,
|
|
16
|
+
timeoutSeconds,
|
|
17
|
+
retries,
|
|
18
|
+
});
|
|
19
|
+
export const DEFAULT_CLI_TUNING = deepFreeze({
|
|
20
|
+
/** `nylorun start` and the commands that reach the local stack. */
|
|
21
|
+
stack: {
|
|
22
|
+
/** One `/health` or `/ready` probe of the Runtime. */
|
|
23
|
+
probeTimeoutMs: 3_000,
|
|
24
|
+
/** How long `start` waits for the Runtime's `/health`, and then for the Tenant to open. */
|
|
25
|
+
healthWaitMs: 60_000,
|
|
26
|
+
tenantWaitMs: 60_000,
|
|
27
|
+
/** How often `start` polls while it waits. */
|
|
28
|
+
pollMs: 500,
|
|
29
|
+
/** While the Tenant opens, `nylorun-operate status` is asked every this many polls. */
|
|
30
|
+
operateEveryPolls: 10,
|
|
31
|
+
/** `docker compose up --wait-timeout` of the core services, and of Studio, in seconds. */
|
|
32
|
+
composeWaitSeconds: 300,
|
|
33
|
+
studioWaitSeconds: 120,
|
|
34
|
+
/** How long a Studio login is retried, and how long one request for it may take. */
|
|
35
|
+
loginWaitMs: 15_000,
|
|
36
|
+
loginRequestMs: 5_000,
|
|
37
|
+
/** How long a key check against the Runtime (`/v1/me`) may take. */
|
|
38
|
+
keyCheckMs: 10_000,
|
|
39
|
+
/** The local key file's lock: how long to wait for it, when a held one is stale, how often to look. */
|
|
40
|
+
lockWaitMs: 30_000,
|
|
41
|
+
lockStaleMs: 60_000,
|
|
42
|
+
lockPollMs: 100,
|
|
43
|
+
/** Tries at picking a free port. */
|
|
44
|
+
portAttempts: 20,
|
|
45
|
+
/** How long one seed or model call to the Management API may take (`nylorun dev`). */
|
|
46
|
+
seedRequestMs: 15_000,
|
|
47
|
+
},
|
|
48
|
+
/** What `nylorun start` writes into the Compose file. */
|
|
49
|
+
compose: {
|
|
50
|
+
/** Postgres's `max_slot_wal_keep_size`: the WAL a stuck replication slot may hold, in MiB. */
|
|
51
|
+
walKeepSizeMb: 4_096,
|
|
52
|
+
healthchecks: {
|
|
53
|
+
postgres: check(30),
|
|
54
|
+
restate: check(30),
|
|
55
|
+
rustfs: check(30),
|
|
56
|
+
gateway: check(60),
|
|
57
|
+
runtime: check(60),
|
|
58
|
+
studio: check(30),
|
|
59
|
+
harness: check(90),
|
|
60
|
+
sandboxes: check(24, 5, 5),
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
/** `nylorun local sandbox` (agent-sandbox on a local Kubernetes cluster). */
|
|
64
|
+
sandbox: {
|
|
65
|
+
/** How long a `kubectl` call may run before it is killed, and how much longer than a wait it was told. */
|
|
66
|
+
kubectlTimeoutMs: 300_000,
|
|
67
|
+
kubectlMarginMs: 20_000,
|
|
68
|
+
/** Downloading the agent-sandbox manifest. */
|
|
69
|
+
manifestDownloadMs: 60_000,
|
|
70
|
+
/** `kubectl wait` for the CRD, and `rollout status` of the controller, in seconds. */
|
|
71
|
+
crdWaitSeconds: 60,
|
|
72
|
+
rolloutWaitSeconds: 180,
|
|
73
|
+
/** How long to wait for the service account's token Secret. */
|
|
74
|
+
tokenWaitMs: 60_000,
|
|
75
|
+
/** How often install and the network probe poll. */
|
|
76
|
+
pollMs: 1_000,
|
|
77
|
+
/** `docker compose up --wait-timeout` of the sandboxes service, in seconds. */
|
|
78
|
+
composeWaitSeconds: 120,
|
|
79
|
+
/** How long the sandboxes service's `/v1/info`, read from the runtime container, may take. */
|
|
80
|
+
infoRequestMs: 5_000,
|
|
81
|
+
/** The reachability probe: `wget -T` in seconds, and the `kubectl exec` around it. */
|
|
82
|
+
probeWgetSeconds: 3,
|
|
83
|
+
probeExecMs: 20_000,
|
|
84
|
+
/** How long probe pods may take to be Ready, in seconds, and the NetworkPolicy to apply. */
|
|
85
|
+
probeReadySeconds: 180,
|
|
86
|
+
policyTimeoutMs: 30_000,
|
|
87
|
+
/** How long image pre-pulls may take, and how often they are checked. */
|
|
88
|
+
prepullMs: 300_000,
|
|
89
|
+
prepullPollMs: 2_000,
|
|
90
|
+
/** A probe pod's `terminationGracePeriodSeconds`, and its resources (Kubernetes quantities). */
|
|
91
|
+
probeGraceSeconds: 1,
|
|
92
|
+
probeCpu: "200m",
|
|
93
|
+
probeMemory: "64Mi",
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
function deepFreeze(value) {
|
|
97
|
+
if (value && typeof value === "object") {
|
|
98
|
+
for (const item of Object.values(value))
|
|
99
|
+
deepFreeze(item);
|
|
100
|
+
Object.freeze(value);
|
|
101
|
+
}
|
|
102
|
+
return value;
|
|
103
|
+
}
|
|
104
|
+
function merge(defaults, set, path) {
|
|
105
|
+
if (set === undefined)
|
|
106
|
+
return defaults;
|
|
107
|
+
if (typeof defaults === "number") {
|
|
108
|
+
// A grace period may be 0; everything else is at least 1.
|
|
109
|
+
const least = path.endsWith("GraceSeconds") ? 0 : 1;
|
|
110
|
+
if (typeof set !== "number" || !Number.isSafeInteger(set) || set < least)
|
|
111
|
+
throw new CliError(`NYLORUN_CLI_TUNING: ${path} must be a whole number of at least ${least}`, 2);
|
|
112
|
+
return set;
|
|
113
|
+
}
|
|
114
|
+
if (typeof defaults === "string") {
|
|
115
|
+
if (typeof set !== "string" || set.trim() === "")
|
|
116
|
+
throw new CliError(`NYLORUN_CLI_TUNING: ${path} must be a non-empty string`, 2);
|
|
117
|
+
return set;
|
|
118
|
+
}
|
|
119
|
+
if (!set || typeof set !== "object" || Array.isArray(set))
|
|
120
|
+
throw new CliError(`NYLORUN_CLI_TUNING: ${path || "(root)"} must be an object`, 2);
|
|
121
|
+
const out = {};
|
|
122
|
+
const known = defaults;
|
|
123
|
+
for (const key of Object.keys(set))
|
|
124
|
+
if (!(key in known))
|
|
125
|
+
throw new CliError(`NYLORUN_CLI_TUNING: ${path ? `${path}.` : ""}${key} is not a setting`, 2);
|
|
126
|
+
for (const [key, value] of Object.entries(known))
|
|
127
|
+
out[key] = merge(value, set[key], path ? `${path}.${key}` : key);
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
/** The CLI's tuning: `NYLORUN_CLI_TUNING` over the defaults, read now. */
|
|
131
|
+
export function cliTuning(env = process.env) {
|
|
132
|
+
const raw = env.NYLORUN_CLI_TUNING;
|
|
133
|
+
if (raw === undefined || raw.trim() === "")
|
|
134
|
+
return DEFAULT_CLI_TUNING;
|
|
135
|
+
let set;
|
|
136
|
+
try {
|
|
137
|
+
set = JSON.parse(raw);
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
throw new CliError(`NYLORUN_CLI_TUNING must be a JSON object: ${error instanceof Error ? error.message : String(error)}`, 2);
|
|
141
|
+
}
|
|
142
|
+
return merge(DEFAULT_CLI_TUNING, set, "");
|
|
143
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nylorun",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0-beta",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"description": "Set up and run local Nylorun Tenants (Runtime and Studio) with Docker Compose, and act on a Tenant through its Runtime and Management APIs",
|
|
@@ -30,23 +30,23 @@
|
|
|
30
30
|
"prepack": "npm run build"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@nylorun/admin": "0.
|
|
34
|
-
"@nylorun/agents": "0.
|
|
35
|
-
"@nylorun/core": "0.
|
|
33
|
+
"@nylorun/admin": "0.19.0-beta",
|
|
34
|
+
"@nylorun/agents": "0.24.0-beta",
|
|
35
|
+
"@nylorun/core": "0.24.0-beta"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@earendil-works/pi-ai": "
|
|
38
|
+
"@earendil-works/pi-ai": "1.2.0",
|
|
39
39
|
"@nylorun/agents": "file:../../sdks/agents",
|
|
40
40
|
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
41
|
-
"@types/node": "^26.6.
|
|
42
|
-
"vitest": "^5.0.
|
|
41
|
+
"@types/node": "^26.6.5",
|
|
42
|
+
"vitest": "^5.0.4"
|
|
43
43
|
},
|
|
44
44
|
"bin": {
|
|
45
45
|
"nylorun": "dist/cli.js",
|
|
46
46
|
"nylo": "dist/nylo.js"
|
|
47
47
|
},
|
|
48
48
|
"nylorun": {
|
|
49
|
-
"runtime": "0.
|
|
50
|
-
"studio": "0.
|
|
49
|
+
"runtime": "0.28.0-beta",
|
|
50
|
+
"studio": "0.27.0-beta"
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { type EnvironmentManifestInput } from "@nylorun/agents";
|
|
2
|
-
import type { StackDeps } from "../stack/commands.js";
|
|
3
|
-
export declare const manifestsUsage = " manifests list [--json] each environment manifest's latest version\n manifests get <name> [--version <n>]\n a manifest's latest version (or version n), as JSON\n manifests versions <name> its versions, with the Environments made from each\n manifests save <manifest.json>... [--if-match <n>] [--dry-run]\n save each manifest's next version (a files entry with\n \"from\": a folder beside the file, uploaded by hash);\n --dry-run prints the plan and saves nothing\n manifests rm <name> delete every version (refused while an Environment\n was made from one)";
|
|
4
|
-
/** The manifest in `path`, its `from` folders hashed: the body to save and the bytes to upload. */
|
|
5
|
-
export declare function manifestFrom(path: string): Promise<{
|
|
6
|
-
manifest: EnvironmentManifestInput;
|
|
7
|
-
bytes: Record<string, Uint8Array>;
|
|
8
|
-
}>;
|
|
9
|
-
/** `nylorun environments manifests <command> ...` */
|
|
10
|
-
export declare function manifestsCommand(deps: StackDeps, args: readonly string[], usage: string): Promise<number>;
|