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
package/dist/client/sessions.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `nylorun sessions`: the Tenant's sessions (`/v1/sessions`, Runtime API): open one
|
|
2
|
+
* `nylorun sessions`: the Tenant's sessions (`/v1/sessions`, Runtime API): open one on an Agent
|
|
3
|
+
* (protocol 16: every session runs on one, and its id is the caller's to choose), send it a
|
|
3
4
|
* message, answer what it waits for, watch its events, and read its history, usage, calls and
|
|
4
5
|
* traces. `exec` runs a command in the session's sandbox, as an app's own tool would.
|
|
5
6
|
*/
|
|
@@ -10,13 +11,17 @@ export const sessionsUsage = `nylorun sessions <command>
|
|
|
10
11
|
|
|
11
12
|
The Tenant's sessions, through the Runtime API with an application key (or NYLORUN_TOKEN).
|
|
12
13
|
|
|
13
|
-
list [--agent <id>] [--
|
|
14
|
+
list [--agent <id>] [--manifest <name>] [--status <status>] [--owner <user>]
|
|
14
15
|
[--limit <n>] [--cursor <c>] [--json]
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
--agent: on that Agent; --manifest: on the Agents made
|
|
17
|
+
from that agent manifest
|
|
18
|
+
create --agent <id> --owner <user> [--id <id>] [--info <json>]
|
|
19
|
+
open a session on Agent <id>; prints its id
|
|
17
20
|
get <id> the session: status, active turn, what it waits for
|
|
18
21
|
send <id> [<text>] [--data <json>] [--file <artifact>[@<version>]]... [--idempotency-key <k>]
|
|
19
|
-
|
|
22
|
+
[--reject-if-busy] start a turn with text, JSON data or files (artifacts); while
|
|
23
|
+
a turn runs the message waits in the session's queue, or is
|
|
24
|
+
refused with --reject-if-busy
|
|
20
25
|
approve <id> <interaction> [--deny] approve (or deny) a held tool call
|
|
21
26
|
respond <id> <interaction> <json> answer a question the agent asked
|
|
22
27
|
cancel <id> [--reason <text>] cancel the running turn
|
|
@@ -33,9 +38,13 @@ The Tenant's sessions, through the Runtime API with an application key (or NYLOR
|
|
|
33
38
|
its exit code
|
|
34
39
|
|
|
35
40
|
${runtimeFlagsUsage}`;
|
|
41
|
+
/** Protocol 16: a session runs on the Agent `--agent` names, which holds what an Environment did. */
|
|
42
|
+
const ENVIRONMENT_REMOVED = "--environment was removed in protocol 16: a session runs on an Agent, its sandbox and vault included. Pass --agent <id> (nylorun agents list).";
|
|
36
43
|
async function list(deps, args) {
|
|
37
|
-
const usage = "nylorun sessions list [--agent <id>] [--
|
|
38
|
-
|
|
44
|
+
const usage = "nylorun sessions list [--agent <id>] [--manifest <name>] [--status <s>] [--owner <user>] [--limit <n>] [--cursor <c>] [--json]";
|
|
45
|
+
if (args.includes("--environment"))
|
|
46
|
+
throw usageError(ENVIRONMENT_REMOVED);
|
|
47
|
+
const flags = parseFlags(args, { booleans: ["--json"], values: ["--agent", "--manifest", "--status", "--owner", "--limit", "--cursor"] }, usage);
|
|
39
48
|
positional(flags, 0, usage);
|
|
40
49
|
const { client } = await runtimeClient(deps, flags);
|
|
41
50
|
const value = (name) => flags.values.get(name);
|
|
@@ -43,7 +52,7 @@ async function list(deps, args) {
|
|
|
43
52
|
const page = await runtimeCall(() => client.sessions.page({
|
|
44
53
|
...(value("--agent") ? { agentId: value("--agent") } : {}),
|
|
45
54
|
...(value("--status") ? { status: value("--status") } : {}),
|
|
46
|
-
...(value("--
|
|
55
|
+
...(value("--manifest") ? { manifest: value("--manifest") } : {}),
|
|
47
56
|
...(value("--owner") ? { ownerUserId: value("--owner") } : {}),
|
|
48
57
|
...(limit === undefined ? {} : { limit }),
|
|
49
58
|
...(value("--cursor") ? { cursor: value("--cursor") } : {}),
|
|
@@ -56,13 +65,12 @@ async function list(deps, args) {
|
|
|
56
65
|
deps.out("No sessions.");
|
|
57
66
|
else
|
|
58
67
|
printTable(deps, [
|
|
59
|
-
["ID", "AGENT", "OWNER", "STATUS", "
|
|
68
|
+
["ID", "AGENT", "OWNER", "STATUS", "LAST EVENT"],
|
|
60
69
|
...page.sessions.map((session) => [
|
|
61
70
|
session.id,
|
|
62
71
|
session.agentId,
|
|
63
72
|
session.ownerUserId,
|
|
64
73
|
session.status,
|
|
65
|
-
session.environmentId ?? "-",
|
|
66
74
|
session.lastEventAt ?? "-",
|
|
67
75
|
]),
|
|
68
76
|
]);
|
|
@@ -70,8 +78,10 @@ async function list(deps, args) {
|
|
|
70
78
|
return 0;
|
|
71
79
|
}
|
|
72
80
|
async function create(deps, args) {
|
|
73
|
-
const usage = "nylorun sessions create --agent <id> --owner <user> [--
|
|
74
|
-
|
|
81
|
+
const usage = "nylorun sessions create --agent <id> --owner <user> [--id <id>] [--info <json>]";
|
|
82
|
+
if (args.includes("--environment"))
|
|
83
|
+
throw usageError(ENVIRONMENT_REMOVED);
|
|
84
|
+
const flags = parseFlags(args, { values: ["--agent", "--owner", "--id", "--info"] }, usage);
|
|
75
85
|
positional(flags, 0, usage);
|
|
76
86
|
const agentId = flags.values.get("--agent");
|
|
77
87
|
const ownerUserId = flags.values.get("--owner");
|
|
@@ -81,14 +91,12 @@ async function create(deps, args) {
|
|
|
81
91
|
const parsedInfo = info === undefined ? undefined : parseJsonArgument(info, "--info");
|
|
82
92
|
if (parsedInfo !== undefined && (!parsedInfo || typeof parsedInfo !== "object" || Array.isArray(parsedInfo)))
|
|
83
93
|
throw usageError("--info must be a JSON object.");
|
|
84
|
-
const environment = flags.values.get("--environment");
|
|
85
94
|
const id = flags.values.get("--id");
|
|
86
95
|
const { client } = await runtimeClient(deps, flags);
|
|
87
96
|
const session = await runtimeCall(() => client.createSession({
|
|
88
|
-
agentId,
|
|
97
|
+
agent: agentId,
|
|
89
98
|
ownerUserId,
|
|
90
99
|
...(id ? { id } : {}),
|
|
91
|
-
...(environment ? { environment } : {}),
|
|
92
100
|
...(parsedInfo ? { info: parsedInfo } : {}),
|
|
93
101
|
}));
|
|
94
102
|
deps.out(session.id);
|
|
@@ -113,8 +121,8 @@ function filePart(value) {
|
|
|
113
121
|
return { type: "file", artifactId: value.slice(0, at), version: Number(version) };
|
|
114
122
|
}
|
|
115
123
|
async function send(deps, args) {
|
|
116
|
-
const usage = "nylorun sessions send <id> [<text>] [--data <json>] [--file <artifact>[@<version>]]... [--idempotency-key <k>]";
|
|
117
|
-
const flags = parseFlags(args, { values: ["--data", "--idempotency-key"], lists: ["--file"] }, usage);
|
|
124
|
+
const usage = "nylorun sessions send <id> [<text>] [--data <json>] [--file <artifact>[@<version>]]... [--idempotency-key <k>] [--reject-if-busy]";
|
|
125
|
+
const flags = parseFlags(args, { values: ["--data", "--idempotency-key"], lists: ["--file"], booleans: ["--reject-if-busy"] }, usage);
|
|
118
126
|
if (flags.rest.length < 1 || flags.rest.length > 2)
|
|
119
127
|
throw usageError(`Usage: ${usage}`);
|
|
120
128
|
const [id, text] = flags.rest;
|
|
@@ -125,7 +133,10 @@ async function send(deps, args) {
|
|
|
125
133
|
if (data === undefined && text === undefined && files.length === 0)
|
|
126
134
|
throw usageError(`Nothing to send. Usage: ${usage}`);
|
|
127
135
|
const parts = files.map(filePart);
|
|
128
|
-
const options = {
|
|
136
|
+
const options = {
|
|
137
|
+
idempotencyKey: flags.values.get("--idempotency-key") ?? randomUUID(),
|
|
138
|
+
...(flags.booleans.has("--reject-if-busy") ? { delivery: "reject" } : {}),
|
|
139
|
+
};
|
|
129
140
|
const { client } = await runtimeClient(deps, flags);
|
|
130
141
|
const session = client.session(id);
|
|
131
142
|
const accepted = await runtimeCall(() => data !== undefined
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { type StackDeps } from "../stack/commands.js";
|
|
2
|
-
export declare const settingsUsage = " budgets [get | put <file|->] [--tenant <name>]\n the model spend caps; put replaces them all\n tool-policy [get | put <file|->] [--tenant <name>]\n the Tool Gate's rules; put replaces them all\n settings sandbox [get | put <file|->] [--tenant <name>]\n the
|
|
2
|
+
export declare const settingsUsage = " budgets [get | put <file|->] [--tenant <name>]\n the model spend caps (scope tenant, manifest with id an\n agent manifest's name, or turn); put replaces them all\n tool-policy [get | put <file|->] [--tenant <name>]\n the Tool Gate's rules; put replaces them all\n settings sandbox [get | put <file|->] [--tenant <name>]\n the Agents' environments: limits, stop grace, placement\n settings artifacts [get | put <file|->] [--tenant <name>]\n the artifact store's limits\n settings skills [get | put <file|->] [--tenant <name>]\n the most bytes one skill may hold\n settings limits [get | put <file|->] [--tenant <name>]\n the Runtime limits: sizes, counts and deadlines (record\n entries, request bodies, Agents, model calls, tools)\n coverage --agent <id> [--tenant <name>] [--json]\n which secret of Agent <id>'s vault each MCP server and\n HTTP tool of its manifest would get";
|
|
3
3
|
export declare const budgetsCommand: (deps: StackDeps, args: readonly string[]) => Promise<number>;
|
|
4
4
|
export declare const toolPolicyCommand: (deps: StackDeps, args: readonly string[]) => Promise<number>;
|
|
5
|
-
/** `nylorun tenant settings sandbox | artifacts ...` */
|
|
5
|
+
/** `nylorun tenant settings sandbox | artifacts | skills | limits ...` */
|
|
6
6
|
export declare function settingsCommand(deps: StackDeps, args: readonly string[]): Promise<number>;
|
|
7
7
|
/**
|
|
8
|
-
* `nylorun tenant coverage`: which secret of `--
|
|
9
|
-
* of
|
|
10
|
-
*
|
|
8
|
+
* `nylorun tenant coverage`: which secret of Agent `--agent`'s vault each remote MCP server and
|
|
9
|
+
* HTTP tool of the manifest version it runs would get in its sessions. It reads no secret and
|
|
10
|
+
* calls no server.
|
|
11
11
|
*/
|
|
12
12
|
export declare function coverageCommand(deps: StackDeps, args: readonly string[]): Promise<number>;
|
package/dist/client/settings.js
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
|
-
import { PutModelBudgetsRequestSchema, PutTenantArtifactsRequestSchema, PutTenantEnvironmentsRequestSchema, PutToolPolicyRequestSchema, } from "@nylorun/core/contracts";
|
|
1
|
+
import { PutModelBudgetsRequestSchema, PutTenantArtifactsRequestSchema, PutTenantEnvironmentsRequestSchema, PutTenantLimitsRequestSchema, PutTenantSkillsRequestSchema, PutToolPolicyRequestSchema, } from "@nylorun/core/contracts";
|
|
2
2
|
import { CliError } from "../errors.js";
|
|
3
3
|
import { parseStackFlags } from "../stack/commands.js";
|
|
4
4
|
import { printJson, printTable, readBody } from "./io.js";
|
|
5
5
|
import { connect, managementClient } from "./target.js";
|
|
6
6
|
export const settingsUsage = ` budgets [get | put <file|->] [--tenant <name>]
|
|
7
|
-
the model spend caps
|
|
7
|
+
the model spend caps (scope tenant, manifest with id an
|
|
8
|
+
agent manifest's name, or turn); put replaces them all
|
|
8
9
|
tool-policy [get | put <file|->] [--tenant <name>]
|
|
9
10
|
the Tool Gate's rules; put replaces them all
|
|
10
11
|
settings sandbox [get | put <file|->] [--tenant <name>]
|
|
11
|
-
the
|
|
12
|
+
the Agents' environments: limits, stop grace, placement
|
|
12
13
|
settings artifacts [get | put <file|->] [--tenant <name>]
|
|
13
14
|
the artifact store's limits
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
settings skills [get | put <file|->] [--tenant <name>]
|
|
16
|
+
the most bytes one skill may hold
|
|
17
|
+
settings limits [get | put <file|->] [--tenant <name>]
|
|
18
|
+
the Runtime limits: sizes, counts and deadlines (record
|
|
19
|
+
entries, request bodies, Agents, model calls, tools)
|
|
20
|
+
coverage --agent <id> [--tenant <name>] [--json]
|
|
21
|
+
which secret of Agent <id>'s vault each MCP server and
|
|
22
|
+
HTTP tool of its manifest would get`;
|
|
17
23
|
const usageError = (message = `Usage:\n${settingsUsage}`) => new CliError(message, 2);
|
|
18
24
|
/* The bodies' `requestId` is optional in the contracts; the client adds one when it is absent. */
|
|
19
25
|
const budgets = {
|
|
@@ -42,6 +48,18 @@ const artifacts = {
|
|
|
42
48
|
get: (admin) => admin.settings.artifacts.get(),
|
|
43
49
|
put: (admin, body) => admin.settings.artifacts.put(body),
|
|
44
50
|
};
|
|
51
|
+
const skillSettings = {
|
|
52
|
+
name: "settings skills",
|
|
53
|
+
schema: PutTenantSkillsRequestSchema,
|
|
54
|
+
get: (admin) => admin.settings.skills.get(),
|
|
55
|
+
put: (admin, body) => admin.settings.skills.put(body),
|
|
56
|
+
};
|
|
57
|
+
const limitSettings = {
|
|
58
|
+
name: "settings limits",
|
|
59
|
+
schema: PutTenantLimitsRequestSchema,
|
|
60
|
+
get: (admin) => admin.settings.limits.get(),
|
|
61
|
+
put: (admin, body) => admin.settings.limits.put(body),
|
|
62
|
+
};
|
|
45
63
|
/** `<document> [get | put <file|->]`: `get` when no verb is given. */
|
|
46
64
|
async function documentCommand(deps, document, args) {
|
|
47
65
|
const usage = `nylorun tenant ${document.name} [get | put <file|->] [--tenant <name>]`;
|
|
@@ -56,38 +74,40 @@ async function documentCommand(deps, document, args) {
|
|
|
56
74
|
}
|
|
57
75
|
export const budgetsCommand = (deps, args) => documentCommand(deps, budgets, args);
|
|
58
76
|
export const toolPolicyCommand = (deps, args) => documentCommand(deps, toolPolicy, args);
|
|
59
|
-
/** `nylorun tenant settings sandbox | artifacts ...` */
|
|
77
|
+
/** `nylorun tenant settings sandbox | artifacts | skills | limits ...` */
|
|
60
78
|
export async function settingsCommand(deps, args) {
|
|
61
79
|
const [which, ...rest] = args;
|
|
62
80
|
if (which === "sandbox")
|
|
63
81
|
return documentCommand(deps, sandbox, rest);
|
|
64
82
|
if (which === "artifacts")
|
|
65
83
|
return documentCommand(deps, artifacts, rest);
|
|
84
|
+
if (which === "skills")
|
|
85
|
+
return documentCommand(deps, skillSettings, rest);
|
|
86
|
+
if (which === "limits")
|
|
87
|
+
return documentCommand(deps, limitSettings, rest);
|
|
66
88
|
throw usageError();
|
|
67
89
|
}
|
|
68
|
-
const COVERAGE_USAGE = "nylorun tenant coverage --agent <id> [--
|
|
90
|
+
const COVERAGE_USAGE = "nylorun tenant coverage --agent <id> [--tenant <name>] [--json]";
|
|
69
91
|
/**
|
|
70
|
-
* `nylorun tenant coverage`: which secret of `--
|
|
71
|
-
* of
|
|
72
|
-
*
|
|
92
|
+
* `nylorun tenant coverage`: which secret of Agent `--agent`'s vault each remote MCP server and
|
|
93
|
+
* HTTP tool of the manifest version it runs would get in its sessions. It reads no secret and
|
|
94
|
+
* calls no server.
|
|
73
95
|
*/
|
|
74
96
|
export async function coverageCommand(deps, args) {
|
|
75
|
-
|
|
97
|
+
if (args.includes("--environment"))
|
|
98
|
+
throw usageError("--environment was removed in protocol 16: pass the Agent, --agent <id>, whose vault its sessions use.");
|
|
99
|
+
const flags = parseStackFlags(args, { booleans: ["--json"], values: ["--tenant", "--agent"] }, COVERAGE_USAGE);
|
|
76
100
|
const agentId = flags.values.get("--agent");
|
|
77
|
-
const environmentId = flags.values.get("--environment");
|
|
78
101
|
if (flags.rest.length || agentId === undefined)
|
|
79
102
|
throw usageError(`Usage: ${COVERAGE_USAGE}`);
|
|
80
103
|
const admin = managementClient(deps, await connect(deps, "management", flags.values.get("--tenant")));
|
|
81
|
-
const coverage = await admin.coverage({
|
|
82
|
-
agentId,
|
|
83
|
-
...(environmentId === undefined ? {} : { environmentId }),
|
|
84
|
-
});
|
|
104
|
+
const coverage = await admin.coverage({ agentId });
|
|
85
105
|
if (flags.booleans.has("--json")) {
|
|
86
106
|
printJson(deps, coverage);
|
|
87
107
|
return 0;
|
|
88
108
|
}
|
|
89
109
|
const missing = coverage.entries.filter((entry) => entry.status === "missing").length;
|
|
90
|
-
deps.out(
|
|
110
|
+
deps.out(`Agent ${coverage.agentId}: ${coverage.complete ? "every credential is covered" : `${missing} of ${coverage.entries.length} missing`}`);
|
|
91
111
|
if (coverage.entries.length)
|
|
92
112
|
printTable(deps, [
|
|
93
113
|
["STATUS", "KIND", "NAME", "URL", "SECRET"],
|
package/dist/client/tenant.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import type { ManagementClient } from "@nylorun/admin";
|
|
7
7
|
import { type StackDeps } from "../stack/commands.js";
|
|
8
8
|
import { type Connection } from "./target.js";
|
|
9
|
-
export declare const tenantUsage = "nylorun tenant <command>\n\nThe Management API of the installation's one Tenant, with a management key: the local Tenant\n--tenant names (else the linked project's, else the default one), or the installation that\nNYLORUN_RUNTIME_URL and NYLORUN_MANAGEMENT_KEY name.\n\n reset [--sessions|--
|
|
9
|
+
export declare const tenantUsage = "nylorun tenant <command>\n\nThe Management API of the installation's one Tenant, with a management key: the local Tenant\n--tenant names (else the linked project's, else the default one), or the installation that\nNYLORUN_RUNTIME_URL and NYLORUN_MANAGEMENT_KEY name.\n\n reset [--sessions|--agents|--all] [--yes] [--tenant <name>]\n clear the Tenant's sessions, its Agents (their vaults,\n disks and sessions) or all its data (the Project link and\n keys stay)\n keys put <id> [--management] [--tenant <name>]\n create the Tenant's application key <id> (--management: a\n management key, on the Runtime's machine only), or rotate it\n (the old key stops working); prints the key once\n keys list [--tenant <name>] [--json]\n the Tenant's keys: id, role, when issued (never the keys)\n keys rm <id> [--tenant <name>] delete key <id>: it stops working at once\n signing-keys list [--tenant <name>]\n the Tenant's token signing keys\n signing-keys rotate [--force] [--tenant <name>]\n rotate; --force ends outstanding links and run tokens\n signing-keys revoke <kid> [--tenant <name>]\n revoke a previous or standby key\n models catalog [--tenant <name>] [--json]\n every provider and model the Runtime knows\n models providers [--tenant <name>] [--json]\n the providers with a credential, and the one in use\n models get [--tenant <name>] [--json]\n the Tenant's model\n models set [--tenant <name>] sign in to a model provider and set the Tenant's model\n models set --provider <id> --model <id> [--base-url <url>] [--tenant <name>]\n the same with an API key piped in on stdin (CI)\n models select <provider> [--model <id>] [--tenant <name>]\n use a provider that has a credential (default: its model)\n usage [--scope tenant|manifest|turn] [--id <id>] [--period day|month|total] [--tenant <name>]\n what the model calls used and cost (JSON); --scope\n manifest: the Agents made from agent manifest <id>\n calls [--after <cursor>] [--limit <n>] [--tenant <name>]\n every recorded model call, one JSON line each, until\n caught up; prints the cursor to resume from on stderr\n budgets [get | put <file|->] [--tenant <name>]\n the model spend caps (scope tenant, manifest with id an\n agent manifest's name, or turn); put replaces them all\n tool-policy [get | put <file|->] [--tenant <name>]\n the Tool Gate's rules; put replaces them all\n settings sandbox [get | put <file|->] [--tenant <name>]\n the Agents' environments: limits, stop grace, placement\n settings artifacts [get | put <file|->] [--tenant <name>]\n the artifact store's limits\n settings skills [get | put <file|->] [--tenant <name>]\n the most bytes one skill may hold\n settings limits [get | put <file|->] [--tenant <name>]\n the Runtime limits: sizes, counts and deadlines (record\n entries, request bodies, Agents, model calls, tools)\n coverage --agent <id> [--tenant <name>] [--json]\n which secret of Agent <id>'s vault each MCP server and\n HTTP tool of its manifest would get\n mcp inspect <url> [--server <name>] [--agent <id>] [--sse] [--tenant <name>] [--json]\n list a remote MCP server's tools, with Agent <id>'s\n credential for <url> (none without it): model names, schema\n sizes, renames, or that it needs a person's sign-in (the\n Runtime calls no tool)\n\nA put reads a JSON body from a file, or from stdin for -, and replaces the whole document; what\nbudgets, tool-policy, settings artifacts, settings skills and settings limits get print, put takes back. An API key is read from\nstdin, never from the arguments.";
|
|
10
10
|
/** The Management API client of the installation `--tenant` (or the environment) names. */
|
|
11
11
|
export declare function tenantClient(deps: StackDeps, tenant: string | undefined): Promise<{
|
|
12
12
|
connection: Connection;
|
package/dist/client/tenant.js
CHANGED
|
@@ -13,9 +13,10 @@ The Management API of the installation's one Tenant, with a management key: the
|
|
|
13
13
|
--tenant names (else the linked project's, else the default one), or the installation that
|
|
14
14
|
NYLORUN_RUNTIME_URL and NYLORUN_MANAGEMENT_KEY name.
|
|
15
15
|
|
|
16
|
-
reset [--sessions|--
|
|
17
|
-
clear the Tenant's sessions,
|
|
18
|
-
(the Project link and
|
|
16
|
+
reset [--sessions|--agents|--all] [--yes] [--tenant <name>]
|
|
17
|
+
clear the Tenant's sessions, its Agents (their vaults,
|
|
18
|
+
disks and sessions) or all its data (the Project link and
|
|
19
|
+
keys stay)
|
|
19
20
|
${keyUsage}
|
|
20
21
|
${signingKeysUsage}
|
|
21
22
|
${modelsUsage}
|
|
@@ -24,7 +25,7 @@ ${settingsUsage}
|
|
|
24
25
|
${mcpUsage}
|
|
25
26
|
|
|
26
27
|
A put reads a JSON body from a file, or from stdin for -, and replaces the whole document; what
|
|
27
|
-
budgets, tool-policy and settings
|
|
28
|
+
budgets, tool-policy, settings artifacts, settings skills and settings limits get print, put takes back. An API key is read from
|
|
28
29
|
stdin, never from the arguments.`;
|
|
29
30
|
const usageError = (message) => new CliError(message, 2);
|
|
30
31
|
/** The Management API client of the installation `--tenant` (or the environment) names. */
|
|
@@ -64,16 +65,18 @@ export async function remoteStatus(deps, args) {
|
|
|
64
65
|
deps.out(`sandbox ${body.sandbox.backend ?? "none"}`);
|
|
65
66
|
return 0;
|
|
66
67
|
}
|
|
67
|
-
const RESET_USAGE = "nylorun tenant reset [--sessions|--
|
|
68
|
-
const SCOPES = { "--sessions": "sessions", "--
|
|
68
|
+
const RESET_USAGE = "nylorun tenant reset [--sessions|--agents|--all] [--yes] [--tenant <name>]";
|
|
69
|
+
const SCOPES = { "--sessions": "sessions", "--agents": "agents", "--all": "all" };
|
|
69
70
|
/** `nylorun tenant reset`; `--all` asks first. */
|
|
70
71
|
async function reset(deps, args) {
|
|
72
|
+
if (args.includes("--environments"))
|
|
73
|
+
throw usageError("--environments is --agents in protocol 16: Environments are Agents. Pass --agents.");
|
|
71
74
|
const flags = parseStackFlags(args, { booleans: ["--yes", ...Object.keys(SCOPES)], values: ["--tenant"], aliases: { "-y": "--yes" } }, RESET_USAGE);
|
|
72
75
|
if (flags.rest.length)
|
|
73
76
|
throw usageError(`Usage: ${RESET_USAGE}`);
|
|
74
77
|
const chosen = Object.keys(SCOPES).filter((flag) => flags.booleans.has(flag));
|
|
75
78
|
if (chosen.length > 1)
|
|
76
|
-
throw usageError("Pass only one of --sessions, --
|
|
79
|
+
throw usageError("Pass only one of --sessions, --agents, --all.");
|
|
77
80
|
const scope = chosen.length ? SCOPES[chosen[0]] : "sessions";
|
|
78
81
|
const { connection, admin } = await tenantClient(deps, flags.values.get("--tenant"));
|
|
79
82
|
if (scope === "all" && !flags.booleans.has("--yes")) {
|
package/dist/project/seed.js
CHANGED
|
@@ -3,6 +3,7 @@ import { readFileSync, statSync } from "node:fs";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { parseEnv } from "node:util";
|
|
5
5
|
import { PROTOCOL_HEADER, PROTOCOL_VERSION } from "@nylorun/core/compatibility";
|
|
6
|
+
import { cliTuning } from "../tuning.js";
|
|
6
7
|
/**
|
|
7
8
|
* The Project's `.env` as a value map. It is never applied to this process or written back:
|
|
8
9
|
* a Project's `.env` never configures the Host.
|
|
@@ -56,7 +57,7 @@ async function call(fetch, url, managementKey, init = {}) {
|
|
|
56
57
|
},
|
|
57
58
|
...(init.body === undefined ? {} : { body: JSON.stringify(init.body) }),
|
|
58
59
|
redirect: "error",
|
|
59
|
-
signal: AbortSignal.timeout(
|
|
60
|
+
signal: AbortSignal.timeout(cliTuning().stack.seedRequestMs),
|
|
60
61
|
});
|
|
61
62
|
const body = (await response.json().catch(() => ({})));
|
|
62
63
|
if (!response.ok)
|
package/dist/sandbox/commands.js
CHANGED
|
@@ -10,6 +10,7 @@ import { apiRoute, readClusterFile, removeClusterFiles, writeClusterFiles, } fro
|
|
|
10
10
|
import { contexts, kube } from "./kubectl.js";
|
|
11
11
|
import { CONTROLLER_DEPLOYMENT, CONTROLLER_NAMESPACE, CONTROLLER_SHA256, CONTROLLER_URL, CONTROLLER_VERSION, networkPolicy, SANDBOX_API_VERSION, SANDBOX_CRD, tenantLabel, tenantManifests, tenantNamespace, TOKEN_SECRET, } from "./manifests.js";
|
|
12
12
|
import { assertIPv4, runProbe } from "./probe.js";
|
|
13
|
+
import { cliTuning } from "../tuning.js";
|
|
13
14
|
export const sandboxUsage = ` sandbox enable --context <name> [--tenant <name>] [--host-address <ip>] [--bind-address <ip>] [--no-pull]
|
|
14
15
|
run the Tenant's sandboxes as pods on that kubeconfig context (agent-sandbox ${CONTROLLER_VERSION})
|
|
15
16
|
sandbox disable [--tenant <name>] [--delete-namespace]
|
|
@@ -52,7 +53,7 @@ async function ensureController(k, fetch, err) {
|
|
|
52
53
|
return;
|
|
53
54
|
}
|
|
54
55
|
err(`Installing agent-sandbox ${CONTROLLER_VERSION} into ${k.context}.`);
|
|
55
|
-
const response = await fetch(CONTROLLER_URL, { signal: AbortSignal.timeout(
|
|
56
|
+
const response = await fetch(CONTROLLER_URL, { signal: AbortSignal.timeout(cliTuning().sandbox.manifestDownloadMs) });
|
|
56
57
|
if (!response.ok)
|
|
57
58
|
throw new CliError(`Downloading ${CONTROLLER_URL} failed (HTTP ${response.status}).`, 1);
|
|
58
59
|
const manifest = await response.text();
|
|
@@ -60,12 +61,13 @@ async function ensureController(k, fetch, err) {
|
|
|
60
61
|
if (digest !== CONTROLLER_SHA256)
|
|
61
62
|
throw new CliError(`${CONTROLLER_URL} has sha256 ${digest}, not the pinned ${CONTROLLER_SHA256}; not applying it.`, 1);
|
|
62
63
|
await k.check("Installing agent-sandbox", ["apply", "--server-side", "-f", "-"], { input: manifest });
|
|
63
|
-
await k.check("Waiting for the Sandbox CRD", ["wait", "--for=condition=Established", `crd/${SANDBOX_CRD}`,
|
|
64
|
-
|
|
64
|
+
await k.check("Waiting for the Sandbox CRD", ["wait", "--for=condition=Established", `crd/${SANDBOX_CRD}`, `--timeout=${cliTuning().sandbox.crdWaitSeconds}s`]);
|
|
65
|
+
const tuning = cliTuning().sandbox;
|
|
66
|
+
await k.check("Waiting for the agent-sandbox controller", ["rollout", "status", `deployment/${CONTROLLER_DEPLOYMENT}`, "-n", CONTROLLER_NAMESPACE, `--timeout=${tuning.rolloutWaitSeconds}s`], { timeoutMs: tuning.rolloutWaitSeconds * 1000 + tuning.kubectlMarginMs });
|
|
65
67
|
}
|
|
66
68
|
/** The ServiceAccount token the token controller writes into the Secret. */
|
|
67
69
|
async function serviceAccountToken(k, namespace, pollMs) {
|
|
68
|
-
const deadline = Date.now() +
|
|
70
|
+
const deadline = Date.now() + cliTuning().sandbox.tokenWaitMs;
|
|
69
71
|
for (;;) {
|
|
70
72
|
const secret = await k.getJson(["secret", TOKEN_SECRET, "-n", namespace]);
|
|
71
73
|
if (secret?.data?.token)
|
|
@@ -82,7 +84,7 @@ async function serviceAccountToken(k, namespace, pollMs) {
|
|
|
82
84
|
export async function installCluster(deps, input) {
|
|
83
85
|
const { stack } = deps;
|
|
84
86
|
const k = kube(deps.kubectl, input.context);
|
|
85
|
-
const pollMs = deps.pollMs ??
|
|
87
|
+
const pollMs = deps.pollMs ?? cliTuning().sandbox.pollMs;
|
|
86
88
|
const { server, caData } = await readKubeconfig(k);
|
|
87
89
|
let route;
|
|
88
90
|
try {
|
|
@@ -189,7 +191,7 @@ async function enable(deps, args) {
|
|
|
189
191
|
bind: installed.cluster.bindAddress,
|
|
190
192
|
};
|
|
191
193
|
await restartTenant(stack, { name: tenant.name, sandboxes: settings });
|
|
192
|
-
const ready = await stack.docker.stream(tenant.compose("up", "--detach", "--wait", "--wait-timeout",
|
|
194
|
+
const ready = await stack.docker.stream(tenant.compose("up", "--detach", "--wait", "--wait-timeout", String(cliTuning().sandbox.composeWaitSeconds), "sandboxes"));
|
|
193
195
|
if (ready !== 0)
|
|
194
196
|
throw new CliError(`The sandboxes service did not become ready (image ${images.sandboxes}). See "nylorun local logs sandboxes" and "nylorun local sandbox status".`, 7);
|
|
195
197
|
const c = installed.cluster;
|
|
@@ -226,7 +228,7 @@ async function disable(deps, args) {
|
|
|
226
228
|
return 0;
|
|
227
229
|
}
|
|
228
230
|
/** Asks the sandboxes service from inside the runtime container, which holds the token. */
|
|
229
|
-
const
|
|
231
|
+
const infoScript = (timeoutMs) => `fetch(process.env.NYLORUN_SANDBOXES_URL+'/v1/info',{headers:{authorization:'Bearer '+process.env.NYLORUN_SANDBOXES_TOKEN},signal:AbortSignal.timeout(${timeoutMs})}).then(async r=>{process.stdout.write(await r.text());process.exit(r.ok?0:1)},()=>process.exit(1))`;
|
|
230
232
|
async function status(deps, args) {
|
|
231
233
|
const { stack } = deps;
|
|
232
234
|
const usage = "nylorun local sandbox status [--tenant <name>] [--json]";
|
|
@@ -245,7 +247,7 @@ async function status(deps, args) {
|
|
|
245
247
|
}
|
|
246
248
|
await dockerPreflight(stack.docker);
|
|
247
249
|
const ready = (await stack.docker.run(tenant.compose("exec", "-T", "sandboxes", "/sandboxes", "healthcheck"))).code === 0;
|
|
248
|
-
const answer = await stack.docker.run(tenant.compose("exec", "-T", "runtime", "node", "-e",
|
|
250
|
+
const answer = await stack.docker.run(tenant.compose("exec", "-T", "runtime", "node", "-e", infoScript(cliTuning().sandbox.infoRequestMs)));
|
|
249
251
|
let info;
|
|
250
252
|
try {
|
|
251
253
|
info = answer.code === 0 ? JSON.parse(answer.stdout) : undefined;
|
package/dist/sandbox/kubectl.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { CliError } from "../errors.js";
|
|
3
|
+
import { cliTuning } from "../tuning.js";
|
|
3
4
|
/** The real `kubectl` on PATH. */
|
|
4
5
|
export function spawnKubectl(env, binary = "kubectl") {
|
|
5
6
|
const childEnv = Object.fromEntries(Object.entries(env).filter((entry) => entry[1] !== undefined));
|
|
@@ -9,7 +10,7 @@ export function spawnKubectl(env, binary = "kubectl") {
|
|
|
9
10
|
let stdout = "";
|
|
10
11
|
let stderr = "";
|
|
11
12
|
const child = spawn(binary, [...args], { env: childEnv, stdio: ["pipe", "pipe", "pipe"] });
|
|
12
|
-
const timer = setTimeout(() => child.kill("SIGKILL"), options.timeoutMs ??
|
|
13
|
+
const timer = setTimeout(() => child.kill("SIGKILL"), options.timeoutMs ?? cliTuning().sandbox.kubectlTimeoutMs);
|
|
13
14
|
child.stdout.on("data", (chunk) => (stdout += chunk));
|
|
14
15
|
child.stderr.on("data", (chunk) => (stderr += chunk));
|
|
15
16
|
child.once("error", (error) => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { cliTuning } from "../tuning.js";
|
|
2
3
|
/**
|
|
3
4
|
* What `nylorun local sandbox enable` installs into a cluster. The agent-sandbox controller is
|
|
4
5
|
* pinned (D32, Experiment 2): v1.0.5, API agents.x-k8s.io/v1beta1, its release manifest
|
|
@@ -119,7 +120,7 @@ export function probePod(namespace, name, options) {
|
|
|
119
120
|
spec: {
|
|
120
121
|
automountServiceAccountToken: false,
|
|
121
122
|
enableServiceLinks: false,
|
|
122
|
-
terminationGracePeriodSeconds:
|
|
123
|
+
terminationGracePeriodSeconds: cliTuning().sandbox.probeGraceSeconds,
|
|
123
124
|
restartPolicy: options.command ? "Never" : "Always",
|
|
124
125
|
...(options.nodeName ? { nodeName: options.nodeName } : {}),
|
|
125
126
|
securityContext: { runAsNonRoot: true, runAsUser: 1000, seccompProfile: { type: "RuntimeDefault" } },
|
|
@@ -130,7 +131,7 @@ export function probePod(namespace, name, options) {
|
|
|
130
131
|
imagePullPolicy: "IfNotPresent",
|
|
131
132
|
command,
|
|
132
133
|
securityContext: { allowPrivilegeEscalation: false, capabilities: { drop: ["ALL"] } },
|
|
133
|
-
resources: { limits: { cpu:
|
|
134
|
+
resources: { limits: { cpu: cliTuning().sandbox.probeCpu, memory: cliTuning().sandbox.probeMemory } },
|
|
134
135
|
},
|
|
135
136
|
],
|
|
136
137
|
},
|
package/dist/sandbox/probe.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
2
|
import { CliError } from "../errors.js";
|
|
3
3
|
import { networkPolicy, PROBE_IMAGE, probePod } from "./manifests.js";
|
|
4
|
+
import { cliTuning } from "../tuning.js";
|
|
4
5
|
const DOCKER_DESKTOP_HOST = "192.168.65.254";
|
|
5
6
|
/** Docker Desktop and OrbStack forward the host address to this machine's loopback. */
|
|
6
7
|
export function defaultBind(context, hostAddress) {
|
|
@@ -27,7 +28,7 @@ async function sleep(ms) {
|
|
|
27
28
|
}
|
|
28
29
|
/** `wget` from a probe pod; true when the connection succeeds. */
|
|
29
30
|
async function reaches(kube, namespace, pod, address, port) {
|
|
30
|
-
const result = await kube.run(["exec", "-n", namespace, pod, "--", "wget", "-q", "-O", "-", "-T",
|
|
31
|
+
const result = await kube.run(["exec", "-n", namespace, pod, "--", "wget", "-q", "-O", "-", "-T", String(cliTuning().sandbox.probeWgetSeconds), `http://${address}:${port}/`], { timeoutMs: cliTuning().sandbox.probeExecMs });
|
|
31
32
|
return result.code === 0;
|
|
32
33
|
}
|
|
33
34
|
export async function runProbe(deps, options) {
|
|
@@ -35,7 +36,8 @@ export async function runProbe(deps, options) {
|
|
|
35
36
|
const suffix = randomBytes(3).toString("hex");
|
|
36
37
|
const namespace = `nylorun-sbx-probe-${suffix}`;
|
|
37
38
|
const listener = namespace;
|
|
38
|
-
const
|
|
39
|
+
const tuning = cliTuning().sandbox;
|
|
40
|
+
const poll = deps.pollMs ?? tuning.pollMs;
|
|
39
41
|
let listenerStarted = false;
|
|
40
42
|
const fail = (message) => new CliError(`Refusing context ${kube.context}: ${message}`, 6);
|
|
41
43
|
try {
|
|
@@ -46,8 +48,8 @@ export async function runProbe(deps, options) {
|
|
|
46
48
|
probePod(namespace, "restricted", { role: "restricted" }),
|
|
47
49
|
]);
|
|
48
50
|
await kube.check("Waiting for the probe pods", [
|
|
49
|
-
"wait", "--for=condition=Ready", "pod", "--all", "-n", namespace,
|
|
50
|
-
], { timeoutMs:
|
|
51
|
+
"wait", "--for=condition=Ready", "pod", "--all", "-n", namespace, `--timeout=${tuning.probeReadySeconds}s`,
|
|
52
|
+
], { timeoutMs: tuning.probeReadySeconds * 1000 + tuning.kubectlMarginMs });
|
|
51
53
|
let hostAddress = options.hostAddress;
|
|
52
54
|
if (!hostAddress) {
|
|
53
55
|
const lookup = await kube.run(["exec", "-n", namespace, "control", "--", "nslookup", "host.docker.internal"]);
|
|
@@ -80,7 +82,7 @@ export async function runProbe(deps, options) {
|
|
|
80
82
|
await kube.apply("Applying the probe NetworkPolicy", [
|
|
81
83
|
networkPolicy(namespace, hostAddress, [allowed], { name: "probe", podSelector: { "nylorun.dev/probe": "restricted" } }),
|
|
82
84
|
]);
|
|
83
|
-
const deadline = Date.now() + (deps.policyTimeoutMs ??
|
|
85
|
+
const deadline = Date.now() + (deps.policyTimeoutMs ?? tuning.policyTimeoutMs);
|
|
84
86
|
while (await reaches(kube, namespace, "restricted", peer, 8080)) {
|
|
85
87
|
if (Date.now() > deadline)
|
|
86
88
|
throw fail("NetworkPolicy is not enforced: a pod under a deny policy still reached another pod. Sandboxes need a CNI that enforces NetworkPolicy (kindnet with policy support, Calico, Cilium).");
|
|
@@ -117,7 +119,7 @@ async function prePull(deps, namespace, images) {
|
|
|
117
119
|
await kube.apply("Pre-pulling images", pods.map((pod) => pod.manifest));
|
|
118
120
|
const failed = new Set();
|
|
119
121
|
const done = new Set();
|
|
120
|
-
const deadline = Date.now() +
|
|
122
|
+
const deadline = Date.now() + cliTuning().sandbox.prepullMs;
|
|
121
123
|
while (done.size + failed.size < pods.length && Date.now() < deadline) {
|
|
122
124
|
const list = await kube.getJson(["pods", "-n", namespace, "-l", "nylorun.dev/probe=pull"]);
|
|
123
125
|
for (const item of list?.items ?? []) {
|
|
@@ -131,7 +133,7 @@ async function prePull(deps, namespace, images) {
|
|
|
131
133
|
done.add(pod.name);
|
|
132
134
|
}
|
|
133
135
|
if (done.size + failed.size < pods.length)
|
|
134
|
-
await sleep(deps.pollMs ??
|
|
136
|
+
await sleep(deps.pollMs ?? cliTuning().sandbox.prepullPollMs);
|
|
135
137
|
}
|
|
136
138
|
return images.filter((image) => pods.some((pod) => pod.image === image && !done.has(pod.name)));
|
|
137
139
|
}
|
package/dist/stack/commands.js
CHANGED
|
@@ -15,6 +15,7 @@ import { prepareStack, readStackEnv } from "./prepare.js";
|
|
|
15
15
|
import { assertTenantName, chooseTenantName, DEFAULT_TENANT, defaultNylorunRoot, listTenants, moveStackRoots, readTenantRecord, removeOldProxy, sanitizeTenantName, tenantRoot, tenantsDir, writeTenantRecord, } from "./stacks.js";
|
|
16
16
|
import { mintStudioLogin, studioOrigin } from "./studio-login.js";
|
|
17
17
|
import { CLI_KEY_ID, hostKey, keyAuthenticates, PROJECT_KEY_ID, } from "./operator-keys.js";
|
|
18
|
+
import { cliTuning } from "../tuning.js";
|
|
18
19
|
export const STACK_SERVICES = [
|
|
19
20
|
"postgres",
|
|
20
21
|
"restate",
|
|
@@ -199,7 +200,7 @@ async function sleep(ms) {
|
|
|
199
200
|
async function fetchHealth(deps, runtimeUrl) {
|
|
200
201
|
try {
|
|
201
202
|
const response = await deps.fetch(`${runtimeUrl}/health`, {
|
|
202
|
-
signal: AbortSignal.timeout(
|
|
203
|
+
signal: AbortSignal.timeout(cliTuning().stack.probeTimeoutMs),
|
|
203
204
|
redirect: "error",
|
|
204
205
|
});
|
|
205
206
|
if (!response.ok)
|
|
@@ -211,7 +212,7 @@ async function fetchHealth(deps, runtimeUrl) {
|
|
|
211
212
|
}
|
|
212
213
|
}
|
|
213
214
|
async function waitForHealth(ctx, runtimeUrl, hostId) {
|
|
214
|
-
const deadline = Date.now() + (ctx.deps.healthTimeoutMs ??
|
|
215
|
+
const deadline = Date.now() + (ctx.deps.healthTimeoutMs ?? cliTuning().stack.healthWaitMs);
|
|
215
216
|
for (;;) {
|
|
216
217
|
const health = await fetchHealth(ctx.deps, runtimeUrl);
|
|
217
218
|
if (health?.status === "ok") {
|
|
@@ -221,7 +222,7 @@ async function waitForHealth(ctx, runtimeUrl, hostId) {
|
|
|
221
222
|
}
|
|
222
223
|
if (Date.now() > deadline)
|
|
223
224
|
throw new CliError(`The Runtime did not answer ${runtimeUrl}/health. See "nylorun local logs runtime".`, 7);
|
|
224
|
-
await sleep(ctx.deps.pollMs ??
|
|
225
|
+
await sleep(ctx.deps.pollMs ?? cliTuning().stack.pollMs);
|
|
225
226
|
}
|
|
226
227
|
}
|
|
227
228
|
/** The Runtime's `/ready` (503 while not ready, same body); undefined when it does not answer. */
|
|
@@ -229,7 +230,7 @@ async function fetchReady(deps, runtimeUrl) {
|
|
|
229
230
|
try {
|
|
230
231
|
const response = await deps.fetch(`${runtimeUrl}/ready`, {
|
|
231
232
|
headers: { accept: "application/json" },
|
|
232
|
-
signal: AbortSignal.timeout(
|
|
233
|
+
signal: AbortSignal.timeout(cliTuning().stack.probeTimeoutMs),
|
|
233
234
|
redirect: "error",
|
|
234
235
|
});
|
|
235
236
|
const body = (await response.json());
|
|
@@ -289,11 +290,11 @@ function tenantCauseMessage(ctx, tenant) {
|
|
|
289
290
|
* Tenant cannot open (`schema-too-new`, `database-layout-old`), which ends the wait.
|
|
290
291
|
*/
|
|
291
292
|
async function waitForTenant(ctx, runtimeUrl) {
|
|
292
|
-
const deadline = Date.now() + (ctx.deps.healthTimeoutMs ??
|
|
293
|
+
const deadline = Date.now() + (ctx.deps.healthTimeoutMs ?? cliTuning().stack.tenantWaitMs);
|
|
293
294
|
for (let poll = 1;; poll += 1) {
|
|
294
295
|
const open = (await fetchReady(ctx.deps, runtimeUrl))?.tenant === true;
|
|
295
296
|
const late = Date.now() > deadline;
|
|
296
|
-
if (open || late || poll %
|
|
297
|
+
if (open || late || poll % cliTuning().stack.operateEveryPolls === 0) {
|
|
297
298
|
const tenant = await operateStatus(ctx);
|
|
298
299
|
if (tenant?.state === "open" && tenant.id)
|
|
299
300
|
return { ...tenant, id: tenant.id };
|
|
@@ -302,7 +303,7 @@ async function waitForTenant(ctx, runtimeUrl) {
|
|
|
302
303
|
if (late)
|
|
303
304
|
throw new CliError(`Tenant ${ctx.name} did not open (${runtimeUrl}/ready: ${open ? "open, but nylorun-operate status did not answer" : "not open"})${tenant?.message ? `: ${tenant.message.replace(/\.$/, "")}` : ""}. See "nylorun status" and "nylorun local logs runtime".`, 7);
|
|
304
305
|
}
|
|
305
|
-
await sleep(ctx.deps.pollMs ??
|
|
306
|
+
await sleep(ctx.deps.pollMs ?? cliTuning().stack.pollMs);
|
|
306
307
|
}
|
|
307
308
|
}
|
|
308
309
|
/**
|
|
@@ -427,7 +428,7 @@ async function bringUp(ctx, options) {
|
|
|
427
428
|
const up = await deps.docker.stream(
|
|
428
429
|
// --remove-orphans: a service this start's file no longer has (the harness after a rollback
|
|
429
430
|
// to NYLORUN_HARNESS=in-process, sandboxes after disable) is removed, not left running.
|
|
430
|
-
composeArgs(ctx, "up", "--detach", "--wait", "--wait-timeout",
|
|
431
|
+
composeArgs(ctx, "up", "--detach", "--wait", "--wait-timeout", String(cliTuning().stack.composeWaitSeconds), "--remove-orphans", ...coreServices(prepared.env.harness)));
|
|
431
432
|
if (up !== 0) {
|
|
432
433
|
// A Runtime whose Tenant cannot open fails readiness; nylorun-operate names the cause.
|
|
433
434
|
const tenant = await operateStatus(ctx);
|
|
@@ -444,7 +445,7 @@ async function bringUp(ctx, options) {
|
|
|
444
445
|
}
|
|
445
446
|
let studioStarted = false;
|
|
446
447
|
if (options.studio) {
|
|
447
|
-
const studio = await deps.docker.stream(composeArgs(ctx, "up", "--detach", "--wait", "--wait-timeout",
|
|
448
|
+
const studio = await deps.docker.stream(composeArgs(ctx, "up", "--detach", "--wait", "--wait-timeout", String(cliTuning().stack.studioWaitSeconds), "studio"));
|
|
448
449
|
studioStarted = studio === 0;
|
|
449
450
|
if (!studioStarted)
|
|
450
451
|
deps.err(`Warning: Studio did not start (image ${prepared.env.studioImage}). The Runtime is up; see "nylorun local logs studio".`);
|
|
@@ -466,7 +467,7 @@ const errorText = (error) => (error instanceof Error ? error.message : String(er
|
|
|
466
467
|
async function tryStudioLogin(ctx, studioPort, adminKey) {
|
|
467
468
|
const { deps } = ctx;
|
|
468
469
|
const origin = studioOrigin(studioPort);
|
|
469
|
-
const deadline = Date.now() + (deps.loginTimeoutMs ??
|
|
470
|
+
const deadline = Date.now() + (deps.loginTimeoutMs ?? cliTuning().stack.loginWaitMs);
|
|
470
471
|
let lastError = "";
|
|
471
472
|
for (;;) {
|
|
472
473
|
try {
|
|
@@ -477,7 +478,7 @@ async function tryStudioLogin(ctx, studioPort, adminKey) {
|
|
|
477
478
|
}
|
|
478
479
|
if (Date.now() > deadline)
|
|
479
480
|
break;
|
|
480
|
-
await sleep(deps.pollMs ??
|
|
481
|
+
await sleep(deps.pollMs ?? cliTuning().stack.pollMs);
|
|
481
482
|
}
|
|
482
483
|
deps.err(`Warning: Studio at ${origin} is not reachable for a login (${lastError}).`);
|
|
483
484
|
return undefined;
|