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.
Files changed (46) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/README.md +136 -74
  3. package/dist/cli.js +31 -12
  4. package/dist/client/agent-manifests.d.ts +4 -0
  5. package/dist/client/agent-manifests.js +100 -0
  6. package/dist/client/agents.d.ts +1 -3
  7. package/dist/client/agents.js +459 -117
  8. package/dist/client/apply.d.ts +7 -0
  9. package/dist/client/apply.js +99 -0
  10. package/dist/client/artifacts.js +3 -3
  11. package/dist/client/doctor.d.ts +1 -1
  12. package/dist/client/doctor.js +2 -2
  13. package/dist/client/io.d.ts +2 -0
  14. package/dist/client/io.js +11 -0
  15. package/dist/client/mcp.d.ts +5 -3
  16. package/dist/client/mcp.js +28 -25
  17. package/dist/client/migrate.d.ts +4 -0
  18. package/dist/client/migrate.js +140 -0
  19. package/dist/client/model/pi-ai.d.ts +1 -1
  20. package/dist/client/model/pi-ai.js +1 -1
  21. package/dist/client/models.d.ts +5 -2
  22. package/dist/client/models.js +17 -8
  23. package/dist/client/runtime.js +3 -3
  24. package/dist/client/sessions.js +29 -18
  25. package/dist/client/settings.d.ts +5 -5
  26. package/dist/client/settings.js +38 -18
  27. package/dist/client/tenant.d.ts +1 -1
  28. package/dist/client/tenant.js +10 -7
  29. package/dist/project/seed.js +2 -1
  30. package/dist/sandbox/commands.js +10 -8
  31. package/dist/sandbox/kubectl.js +2 -1
  32. package/dist/sandbox/manifests.js +3 -2
  33. package/dist/sandbox/probe.js +9 -7
  34. package/dist/stack/commands.js +12 -11
  35. package/dist/stack/compose-file.d.ts +3 -0
  36. package/dist/stack/compose-file.js +32 -36
  37. package/dist/stack/operator-keys.js +7 -6
  38. package/dist/stack/ports.js +2 -1
  39. package/dist/stack/studio-login.js +2 -1
  40. package/dist/tuning.d.ts +90 -0
  41. package/dist/tuning.js +143 -0
  42. package/package.json +9 -9
  43. package/dist/client/environment-manifests.d.ts +0 -10
  44. package/dist/client/environment-manifests.js +0 -173
  45. package/dist/client/environments.d.ts +0 -4
  46. 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 at 4 GB of WAL.
90
- command: ["postgres", "-c", "wal_level=logical", "-c", "max_slot_wal_keep_size=4GB"]
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
- interval: 2s
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
- interval: 2s
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
- interval: 2s
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${sandboxes ? SANDBOXES_GATEWAY_ENV : ""}
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
- interval: 2s
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:-}${sandboxes ? SANDBOXES_RUNTIME_ENV : ""}${identity ? IDENTITY_RUNTIME_ENV : ""}
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
- interval: 2s
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
- interval: 2s
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
- interval: 2s
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
- interval: 5s
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(10_000),
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 LOCK_TIMEOUT_MS = 30_000;
73
+ const lockTimeoutMs = () => cliTuning().stack.lockWaitMs;
73
74
  /** A lock older than this was left by a process that died. */
74
- const LOCK_STALE_MS = 60_000;
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() + LOCK_TIMEOUT_MS;
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 > LOCK_STALE_MS) {
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, 100));
95
+ await new Promise((resolve) => setTimeout(resolve, cliTuning().stack.lockPollMs));
95
96
  }
96
97
  }
97
98
  try {
@@ -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 < 20; attempt += 1) {
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 ?? 5000),
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}).`);
@@ -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.15.1-beta",
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.17.1-beta",
34
- "@nylorun/agents": "0.22.0-beta",
35
- "@nylorun/core": "0.22.0-beta"
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": "0.99.1",
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.4",
42
- "vitest": "^5.0.2"
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.26.0-beta",
50
- "studio": "0.25.1-beta"
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>;