nylorun 0.16.0-beta → 0.18.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 +70 -0
- package/README.md +118 -70
- package/dist/cli.js +39 -14
- package/dist/client/agent-manifests.d.ts +4 -0
- package/dist/client/agent-manifests.js +100 -0
- package/dist/client/agents.d.ts +2 -4
- package/dist/client/agents.js +467 -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 +30 -23
- 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 -7
- package/dist/client/runtime.d.ts +1 -1
- package/dist/client/runtime.js +8 -6
- package/dist/client/sessions.js +30 -19
- package/dist/client/settings.d.ts +4 -4
- package/dist/client/settings.js +21 -20
- package/dist/client/signing-keys.js +3 -1
- package/dist/client/tenant.d.ts +1 -1
- package/dist/client/tenant.js +12 -7
- package/dist/command.d.ts +1 -1
- package/dist/command.js +6 -2
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +9 -0
- package/dist/nylo.js +4 -4
- package/dist/stack/commands.d.ts +11 -0
- package/dist/stack/commands.js +24 -1
- package/dist/stack/compose-file.js +5 -3
- package/dist/telemetry.js +3 -1
- 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
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nylorun apply [dir…]`: saves the agent manifest each folder describes (protocol 16, track T2),
|
|
3
|
+
* through the Runtime API. A folder's `agent.manifest.json` names its harness and runtime files;
|
|
4
|
+
* `composeManifest` (`@nylorun/agents`) resolves them, hashes the skills, plugins and runtime
|
|
5
|
+
* files they name and checks the harness file's `requires`. Each manifest is planned first
|
|
6
|
+
* (a dry run: from → to, each change, warnings, errors), and saved under `If-Match` set to the
|
|
7
|
+
* version the plan was made against, so a save that raced another fails (`412 manifest_changed`)
|
|
8
|
+
* instead of overwriting it. A folder without an `agent.manifest.json` applies each subfolder
|
|
9
|
+
* that has one (`nylorun apply agents/`).
|
|
10
|
+
*/
|
|
11
|
+
import { resolve } from "node:path";
|
|
12
|
+
import { ComposeError, PluginError, SkillsError, composeManifest, manifestFolders, } from "@nylorun/agents";
|
|
13
|
+
import { CliError } from "../errors.js";
|
|
14
|
+
import { integerFlag, parseFlags, runtimeCall, runtimeClient, runtimeFlagsUsage, usageError } from "./runtime.js";
|
|
15
|
+
export const applyUsage = `nylorun apply [<dir>...] [--dry-run] [--if-match <n>]
|
|
16
|
+
|
|
17
|
+
Saves the agent manifest each folder describes (default .), through the Runtime API with an
|
|
18
|
+
application key (or NYLORUN_TOKEN). A folder holds agent.manifest.json, which names its
|
|
19
|
+
harness.manifest.json and runtime.manifest.json (paths relative to the file that names them);
|
|
20
|
+
a folder without one applies each subfolder that has one.
|
|
21
|
+
|
|
22
|
+
Each manifest is planned first: the version it moves from and to, each change (section, path,
|
|
23
|
+
op), warnings and errors. With no errors it is saved, its skills', plugins' and runtime files
|
|
24
|
+
uploaded first, under If-Match set to the version planned against (0 for a new manifest).
|
|
25
|
+
Errors, the gaps between its sections included, save nothing and exit 1.
|
|
26
|
+
|
|
27
|
+
--dry-run print the plans and save nothing
|
|
28
|
+
--if-match <n> save only over version n (one folder)
|
|
29
|
+
|
|
30
|
+
${runtimeFlagsUsage}`;
|
|
31
|
+
/** What a folder could not be composed for, as a person reads it. */
|
|
32
|
+
function composeFailure(folder, error) {
|
|
33
|
+
if (error instanceof ComposeError || error instanceof SkillsError || error instanceof PluginError || error instanceof TypeError)
|
|
34
|
+
throw new CliError(`${folder}: ${error.message}`, 1);
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
/** A plan as lines: the move, then each change, warning and error. */
|
|
38
|
+
export function planLines(plan) {
|
|
39
|
+
const move = plan.unchanged
|
|
40
|
+
? `unchanged at version ${plan.to}`
|
|
41
|
+
: plan.from === null
|
|
42
|
+
? `new, version ${plan.to}`
|
|
43
|
+
: `version ${plan.from} → ${plan.to}`;
|
|
44
|
+
return [
|
|
45
|
+
`${plan.name}: ${move}${plan.behind > 0 ? ` (${plan.behind} ${plan.behind === 1 ? "Agent" : "Agents"} on older versions)` : ""}`,
|
|
46
|
+
...plan.changes.map((change) => ` ${change.section} ${change.path} ${change.op}`),
|
|
47
|
+
...plan.warnings.map((warning) => ` warning: ${warning.message}`),
|
|
48
|
+
...plan.errors.map((error) => ` error: ${error}`),
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
/** `nylorun apply [dir…] [--dry-run] [--if-match <n>]` */
|
|
52
|
+
export async function applyCommand(deps, args) {
|
|
53
|
+
if (args[0] === "--help" || args[0] === "-h") {
|
|
54
|
+
deps.out(applyUsage);
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
const usage = "nylorun apply [<dir>...] [--dry-run] [--if-match <n>]";
|
|
58
|
+
const flags = parseFlags(args, { booleans: ["--dry-run"], values: ["--if-match"] }, usage);
|
|
59
|
+
const ifMatch = integerFlag(flags, "--if-match");
|
|
60
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
61
|
+
const folders = [];
|
|
62
|
+
for (const dir of flags.rest.length > 0 ? flags.rest : ["."]) {
|
|
63
|
+
const found = await manifestFolders(resolve(cwd, dir)).catch((error) => composeFailure(dir, error));
|
|
64
|
+
if (found.length === 0)
|
|
65
|
+
throw usageError(`${dir} holds no agent.manifest.json, and no subfolder does.`);
|
|
66
|
+
folders.push(...found);
|
|
67
|
+
}
|
|
68
|
+
if (ifMatch !== undefined && folders.length > 1)
|
|
69
|
+
throw usageError("--if-match takes one manifest: name one folder.");
|
|
70
|
+
// Every folder composes before anything is sent: a broken file stops the whole apply.
|
|
71
|
+
const composed = [];
|
|
72
|
+
for (const folder of folders)
|
|
73
|
+
composed.push({ folder, manifest: await composeManifest(folder).catch((error) => composeFailure(folder, error)) });
|
|
74
|
+
const { client, connection } = await runtimeClient(deps, flags);
|
|
75
|
+
let blocked = false;
|
|
76
|
+
for (const { manifest } of composed) {
|
|
77
|
+
for (const warning of manifest.warnings)
|
|
78
|
+
deps.err(`${manifest.manifest.name}: warning: ${warning}`);
|
|
79
|
+
// The plan's errors include the gaps the composer found (its sections, the harness's requires).
|
|
80
|
+
const plan = await runtimeCall(() => client.agentManifests.plan(manifest, ifMatch === undefined ? {} : { ifMatch }));
|
|
81
|
+
for (const line of planLines(plan))
|
|
82
|
+
deps.out(line);
|
|
83
|
+
if (plan.errors.length > 0) {
|
|
84
|
+
blocked = true;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (flags.booleans.has("--dry-run") || plan.unchanged)
|
|
88
|
+
continue;
|
|
89
|
+
const saved = await runtimeCall(() => client.agentManifests.save(manifest, { ifMatch: ifMatch ?? plan.from ?? 0 }));
|
|
90
|
+
deps.err(saved.unchanged
|
|
91
|
+
? `Agent manifest ${saved.manifest.name} is unchanged at version ${saved.manifest.version} on ${connection.label}.`
|
|
92
|
+
: `Saved agent manifest ${saved.manifest.name} version ${saved.manifest.version} on ${connection.label}.`);
|
|
93
|
+
for (const warning of saved.warnings)
|
|
94
|
+
deps.err(` warning: ${warning.message}`);
|
|
95
|
+
}
|
|
96
|
+
if (blocked)
|
|
97
|
+
deps.err("Nothing was saved for a manifest with errors.");
|
|
98
|
+
return blocked ? 1 : 0;
|
|
99
|
+
}
|
package/dist/client/artifacts.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `nylorun artifacts`: the Tenant's file artifacts (`/v1/artifacts`, Runtime API): upload a file
|
|
3
3
|
* (to name in a message with `sessions send --file`), list, download, browse a folder artifact
|
|
4
|
-
* (an
|
|
4
|
+
* (an Agent's disk versions), share a version with a capability link, and delete.
|
|
5
5
|
*/
|
|
6
6
|
import { createWriteStream } from "node:fs";
|
|
7
7
|
import { readFile } from "node:fs/promises";
|
|
@@ -62,8 +62,8 @@ async function list(deps, args) {
|
|
|
62
62
|
String(item.latestVersion),
|
|
63
63
|
item.sessionId
|
|
64
64
|
? `session ${item.sessionId}`
|
|
65
|
-
: item.
|
|
66
|
-
? `
|
|
65
|
+
: item.agentId
|
|
66
|
+
? `agent ${item.agentId}`
|
|
67
67
|
: "tenant",
|
|
68
68
|
]),
|
|
69
69
|
]);
|
package/dist/client/doctor.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export type SandboxReport = {
|
|
|
12
12
|
version?: string;
|
|
13
13
|
}[];
|
|
14
14
|
defaultImage?: string;
|
|
15
|
-
/** The Tenant's
|
|
15
|
+
/** The Tenant's settings for Agents' environments in force (`environments.config`). */
|
|
16
16
|
config?: {
|
|
17
17
|
limits?: {
|
|
18
18
|
network?: readonly string[];
|
package/dist/client/doctor.js
CHANGED
|
@@ -45,7 +45,7 @@ export async function doctorSandbox(deps, options) {
|
|
|
45
45
|
]);
|
|
46
46
|
rows.push([
|
|
47
47
|
"sessions",
|
|
48
|
-
"
|
|
48
|
+
"run on an Agent, with the sandbox its manifest's runtime.kind gives (createSession({ agent }))",
|
|
49
49
|
]);
|
|
50
50
|
const limits = report.config?.limits;
|
|
51
51
|
if (limits)
|
|
@@ -53,7 +53,7 @@ export async function doctorSandbox(deps, options) {
|
|
|
53
53
|
"limits",
|
|
54
54
|
[
|
|
55
55
|
limits.idle ? `idle ${limits.idle}` : undefined,
|
|
56
|
-
limits.environments === undefined ? undefined : `${limits.environments}
|
|
56
|
+
limits.environments === undefined ? undefined : `${limits.environments} Agents`,
|
|
57
57
|
limits.network ? `${limits.network.length} allowed hosts` : undefined,
|
|
58
58
|
]
|
|
59
59
|
.filter((item) => item !== undefined)
|
package/dist/client/io.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export interface BodySchema<T> {
|
|
|
14
14
|
};
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
|
+
/** The text in file `source`, or on standard input for `-`, as it is. */
|
|
18
|
+
export declare function readText(deps: StackDeps, source: string): Promise<string>;
|
|
17
19
|
/**
|
|
18
20
|
* The JSON body in file `source`, or on standard input for `-`, as `schema` parses it. Fields
|
|
19
21
|
* named in `readOnly` (a view's, so a `get` can be edited and put back) are dropped first. A
|
package/dist/client/io.js
CHANGED
|
@@ -13,6 +13,17 @@ async function readStdin(deps, refusal) {
|
|
|
13
13
|
throw usageError(refusal);
|
|
14
14
|
return await deps.stdin.text();
|
|
15
15
|
}
|
|
16
|
+
/** The text in file `source`, or on standard input for `-`, as it is. */
|
|
17
|
+
export async function readText(deps, source) {
|
|
18
|
+
if (source === "-")
|
|
19
|
+
return readStdin(deps, "Pipe the text in on standard input, or name a file.");
|
|
20
|
+
try {
|
|
21
|
+
return await readFile(resolve(deps.cwd ?? process.cwd(), source), "utf8");
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
throw usageError(`Cannot read ${source}: ${error.code ?? error}.`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
16
27
|
/**
|
|
17
28
|
* The JSON body in file `source`, or on standard input for `-`, as `schema` parses it. Fields
|
|
18
29
|
* named in `readOnly` (a view's, so a `get` can be edited and put back) are dropped first. A
|
package/dist/client/mcp.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { McpPreview } from "@nylorun/core/contracts";
|
|
2
2
|
import { type StackDeps } from "../stack/commands.js";
|
|
3
|
-
export declare const mcpUsage = " mcp inspect <url> [--server <name>] [--
|
|
3
|
+
export declare const mcpUsage = " 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)";
|
|
4
4
|
/** What `--vault` says since protocol 11. */
|
|
5
|
-
export declare const MCP_VAULT_REMOVED = "--vault was removed in protocol 11: a credential is a secret of an
|
|
5
|
+
export declare const MCP_VAULT_REMOVED = "--vault was removed in protocol 11: a credential is a secret of an Agent, in its own vault. Pass --agent <id> to send that Agent's credential for the URL.";
|
|
6
|
+
/** What `--environment` says since protocol 16. */
|
|
7
|
+
export declare const MCP_ENVIRONMENT_REMOVED = "--environment was removed in protocol 16: Environments are Agents. Pass --agent <id> to send that Agent's credential for the URL.";
|
|
6
8
|
/** What the removed `connect` subcommand says since protocol 10. */
|
|
7
9
|
export declare const MCP_CONNECT_REMOVED: string;
|
|
8
10
|
/** A preview as a person reads it: the server, a table of tools, the renames, or the sign-in hint. */
|
|
9
|
-
export declare function previewLines(preview: McpPreview,
|
|
11
|
+
export declare function previewLines(preview: McpPreview, agentId?: string): string[];
|
|
10
12
|
/** `nylorun tenant mcp <inspect|connect> ...` */
|
|
11
13
|
export declare function mcpCommand(deps: StackDeps, args: readonly string[]): Promise<number>;
|
package/dist/client/mcp.js
CHANGED
|
@@ -1,38 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `nylorun tenant mcp inspect <url>`: a remote MCP server's tools before an agent names it (R2b
|
|
3
3
|
* C12), through the Management API (`POST /v1/tenant/mcp/preview`) of the installation
|
|
4
|
-
* `target.ts` picks. It never starts a local Tenant, and the Runtime never calls a tool. `--
|
|
5
|
-
* credential for the URL (protocol
|
|
6
|
-
* written on the Runtime API
|
|
7
|
-
* `connect` subcommand (MCP OAuth) was
|
|
4
|
+
* `target.ts` picks. It never starts a local Tenant, and the Runtime never calls a tool. `--agent <id>`
|
|
5
|
+
* sends that Agent's credential for the URL (protocol 16; it replaced `--environment`, which
|
|
6
|
+
* replaced `--vault`); an Agent's secrets are written on the Runtime API
|
|
7
|
+
* (`client.agents.secrets.put` of `@nylorun/agents`). Its `connect` subcommand (MCP OAuth) was
|
|
8
|
+
* removed in protocol 10, and says so.
|
|
8
9
|
*/
|
|
9
10
|
import { PROTOCOL_HEADER, PROTOCOL_VERSION } from "@nylorun/core/compatibility";
|
|
10
|
-
import { CliError } from "../errors.js";
|
|
11
|
+
import { CliError, HelpRequest, isHelp } from "../errors.js";
|
|
11
12
|
import { parseStackFlags } from "../stack/commands.js";
|
|
12
13
|
import { connect } from "./target.js";
|
|
13
|
-
export const mcpUsage = ` mcp inspect <url> [--server <name>] [--
|
|
14
|
-
list a remote MCP server's tools, with
|
|
14
|
+
export const mcpUsage = ` mcp inspect <url> [--server <name>] [--agent <id>] [--sse] [--tenant <name>] [--json]
|
|
15
|
+
list a remote MCP server's tools, with Agent <id>'s
|
|
15
16
|
credential for <url> (none without it): model names, schema
|
|
16
17
|
sizes, renames, or that it needs a person's sign-in (the
|
|
17
18
|
Runtime calls no tool)`;
|
|
18
|
-
const INSPECT_USAGE = "nylorun tenant mcp inspect <url> [--server <name>] [--
|
|
19
|
-
/** Where a credential for an MCP server goes since protocol
|
|
20
|
-
const SECRET_HINT = "write it as a bearer or headers secret of an
|
|
19
|
+
const INSPECT_USAGE = "nylorun tenant mcp inspect <url> [--server <name>] [--agent <id>] [--sse] [--tenant <name>] [--json]";
|
|
20
|
+
/** Where a credential for an MCP server goes since protocol 16. */
|
|
21
|
+
const SECRET_HINT = "write it as a bearer or headers secret of an Agent (nylorun agents secrets put, client.agents.secrets.put of @nylorun/agents, or Studio)";
|
|
21
22
|
/** What `--vault` says since protocol 11. */
|
|
22
|
-
export const MCP_VAULT_REMOVED = "--vault was removed in protocol 11: a credential is a secret of an
|
|
23
|
+
export const MCP_VAULT_REMOVED = "--vault was removed in protocol 11: a credential is a secret of an Agent, in its own vault. Pass --agent <id> to send that Agent's credential for the URL.";
|
|
24
|
+
/** What `--environment` says since protocol 16. */
|
|
25
|
+
export const MCP_ENVIRONMENT_REMOVED = "--environment was removed in protocol 16: Environments are Agents. Pass --agent <id> to send that Agent's credential for the URL.";
|
|
23
26
|
/** What the removed `connect` subcommand says since protocol 10. */
|
|
24
|
-
export const MCP_CONNECT_REMOVED = `The connect subcommand of nylorun mcp was removed with MCP OAuth (protocol 10): ${SECRET_HINT.replace("write it", "write the server's key")}, or reach a server that needs a person's sign-in through a gateway (a secret with via). See its tools with nylorun tenant mcp inspect <url> --
|
|
27
|
+
export const MCP_CONNECT_REMOVED = `The connect subcommand of nylorun mcp was removed with MCP OAuth (protocol 10): ${SECRET_HINT.replace("write it", "write the server's key")}, or reach a server that needs a person's sign-in through a gateway (a secret with via). See its tools with nylorun tenant mcp inspect <url> --agent <id>.`;
|
|
25
28
|
const DESCRIPTION_CHARS = 60;
|
|
26
29
|
const usageError = (message) => new CliError(message, 2);
|
|
27
30
|
async function inspect(deps, args) {
|
|
28
31
|
if (args.some((arg) => arg === "--vault" || arg.startsWith("--vault=")))
|
|
29
32
|
throw usageError(MCP_VAULT_REMOVED);
|
|
30
|
-
|
|
33
|
+
if (args.some((arg) => arg === "--environment" || arg.startsWith("--environment=")))
|
|
34
|
+
throw usageError(MCP_ENVIRONMENT_REMOVED);
|
|
35
|
+
const flags = parseStackFlags(args, { booleans: ["--json", "--sse"], values: ["--server", "--agent", "--tenant"] }, INSPECT_USAGE);
|
|
31
36
|
if (flags.rest.length !== 1)
|
|
32
37
|
throw usageError(`Usage: ${INSPECT_USAGE}`);
|
|
33
38
|
const url = flags.rest[0];
|
|
34
39
|
const name = flags.values.get("--server");
|
|
35
|
-
const
|
|
40
|
+
const agentId = flags.values.get("--agent");
|
|
36
41
|
const tenant = flags.values.get("--tenant");
|
|
37
42
|
const api = await connect(deps, "management", tenant);
|
|
38
43
|
if (!api.managementKey)
|
|
@@ -49,7 +54,7 @@ async function inspect(deps, args) {
|
|
|
49
54
|
url,
|
|
50
55
|
...(flags.booleans.has("--sse") ? { type: "sse" } : {}),
|
|
51
56
|
...(name === undefined ? {} : { name }),
|
|
52
|
-
...(
|
|
57
|
+
...(agentId === undefined ? {} : { agentId }),
|
|
53
58
|
}),
|
|
54
59
|
// The Runtime bounds the preview (its Runtime limit mcpPreviewMs, each step); the CLI waits.
|
|
55
60
|
redirect: "error",
|
|
@@ -69,17 +74,17 @@ async function inspect(deps, args) {
|
|
|
69
74
|
deps.out(JSON.stringify(preview, null, 2));
|
|
70
75
|
return 0;
|
|
71
76
|
}
|
|
72
|
-
for (const line of previewLines(preview,
|
|
77
|
+
for (const line of previewLines(preview, agentId))
|
|
73
78
|
deps.out(line);
|
|
74
79
|
return 0;
|
|
75
80
|
}
|
|
76
81
|
/** A preview as a person reads it: the server, a table of tools, the renames, or the sign-in hint. */
|
|
77
|
-
export function previewLines(preview,
|
|
82
|
+
export function previewLines(preview, agentId) {
|
|
78
83
|
const credential = preview.credentialSent
|
|
79
|
-
? `with
|
|
80
|
-
:
|
|
81
|
-
? "with no credential (pass --
|
|
82
|
-
: `with no credential (
|
|
84
|
+
? `with Agent ${agentId ?? "(unnamed)"}'s credential`
|
|
85
|
+
: agentId === undefined
|
|
86
|
+
? "with no credential (pass --agent <id> to send an Agent's)"
|
|
87
|
+
: `with no credential (Agent ${agentId} holds none for this URL)`;
|
|
83
88
|
if (preview.authRequired) {
|
|
84
89
|
const metadata = preview.authRequired.resourceMetadata;
|
|
85
90
|
const servers = Array.isArray(metadata?.authorization_servers)
|
|
@@ -87,13 +92,13 @@ export function previewLines(preview, environmentId) {
|
|
|
87
92
|
: [];
|
|
88
93
|
return [
|
|
89
94
|
preview.credentialSent
|
|
90
|
-
? `The MCP server at ${preview.url} answered 401: it rejected
|
|
95
|
+
? `The MCP server at ${preview.url} answered 401: it rejected Agent ${agentId ?? "(unnamed)"}'s credential.`
|
|
91
96
|
: `The MCP server at ${preview.url} answered 401: it needs a credential.`,
|
|
92
97
|
...(servers.length > 0
|
|
93
98
|
? [`It signs people in with ${servers.join(", ")} (${preview.authRequired.resourceMetadataUrl}).`]
|
|
94
99
|
: []),
|
|
95
100
|
`Nylorun holds no OAuth client: ${SECRET_HINT.replace("write it", "write the server's API key")},`,
|
|
96
|
-
"and pass --
|
|
101
|
+
"and pass --agent <id>; or reach it through a gateway that keeps each person's sign-in",
|
|
97
102
|
"(a secret with via and an identity header).",
|
|
98
103
|
];
|
|
99
104
|
}
|
|
@@ -146,6 +151,8 @@ function oneLine(text) {
|
|
|
146
151
|
/** `nylorun tenant mcp <inspect|connect> ...` */
|
|
147
152
|
export async function mcpCommand(deps, args) {
|
|
148
153
|
const [sub, ...rest] = args;
|
|
154
|
+
if (isHelp(sub))
|
|
155
|
+
throw new HelpRequest(`Usage:\n${mcpUsage}`);
|
|
149
156
|
if (sub === "inspect")
|
|
150
157
|
return inspect(deps, rest);
|
|
151
158
|
if (sub === "connect")
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type StackDeps } from "../stack/commands.js";
|
|
2
|
+
export declare const migrateUsage = "nylorun migrate v8 <agent.json> [--runtime <environment-manifest.json>] [--name <name>] [--out <dir>]\n\nWrites agent.manifest.json, harness.manifest.json and runtime.manifest.json (in --out, default\n.) from an agent manifest v7 or a workflow manifest v3, as nylorun agents save took it, and the\nenvironment manifest v1 its sessions ran in. Then save it with nylorun apply <dir>.\n\n --runtime <file> the environment manifest: its fields become the runtime\n section, its harness the harness file's id\n --name <name> the manifest's name (default: the agent's id)\n --out <dir> where the files are written; none is overwritten";
|
|
3
|
+
/** `nylorun migrate v8 <agent.json> [--runtime <file>] [--name <name>] [--out <dir>]` */
|
|
4
|
+
export declare function migrateCommand(deps: StackDeps, args: readonly string[]): Promise<number>;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nylorun migrate v8 <agent.json>`: a one-off for protocol 16 (track T2). It writes the three
|
|
3
|
+
* authoring files `nylorun apply` reads from an agent manifest v7 (or a workflow manifest v3), as
|
|
4
|
+
* `agents save` took it, and the environment manifest v1 its sessions ran in, if any:
|
|
5
|
+
*
|
|
6
|
+
* - `agent.manifest.json`: the name (the v7 id), description and metadata, naming the two files;
|
|
7
|
+
* - `harness.manifest.json`: the loop (`harnessSectionOf`: instructions, tools, skills, MCP
|
|
8
|
+
* servers, output schema, the plugins they came from), the environment manifest's `harness` as
|
|
9
|
+
* `id`, and v7's `requires`, which the composer checks against the runtime;
|
|
10
|
+
* - `runtime.manifest.json`: the environment manifest's fields without its identity; with none,
|
|
11
|
+
* kind `pod` when the agent requires one, `virtual` when it has skills or requires a sandbox,
|
|
12
|
+
* else `none`.
|
|
13
|
+
*
|
|
14
|
+
* It writes nothing over an existing file, reads nothing from a Runtime, and prints the gaps
|
|
15
|
+
* `nylorun apply` would refuse, to fix in the files first.
|
|
16
|
+
*/
|
|
17
|
+
import { existsSync } from "node:fs";
|
|
18
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
19
|
+
import { join, relative, resolve } from "node:path";
|
|
20
|
+
import { requirementGaps } from "@nylorun/agents";
|
|
21
|
+
import { AgentManifestSchema, RuntimeManifestFileSchema, WorkflowManifestSchema, isAgentManifestName, } from "@nylorun/core/contracts";
|
|
22
|
+
import { harnessSectionOf, sectionGaps } from "@nylorun/core/define";
|
|
23
|
+
import { CliError } from "../errors.js";
|
|
24
|
+
import { parseStackFlags } from "../stack/commands.js";
|
|
25
|
+
import { readJson, usageError } from "./runtime.js";
|
|
26
|
+
export const migrateUsage = `nylorun migrate v8 <agent.json> [--runtime <environment-manifest.json>] [--name <name>] [--out <dir>]
|
|
27
|
+
|
|
28
|
+
Writes agent.manifest.json, harness.manifest.json and runtime.manifest.json (in --out, default
|
|
29
|
+
.) from an agent manifest v7 or a workflow manifest v3, as nylorun agents save took it, and the
|
|
30
|
+
environment manifest v1 its sessions ran in. Then save it with nylorun apply <dir>.
|
|
31
|
+
|
|
32
|
+
--runtime <file> the environment manifest: its fields become the runtime
|
|
33
|
+
section, its harness the harness file's id
|
|
34
|
+
--name <name> the manifest's name (default: the agent's id)
|
|
35
|
+
--out <dir> where the files are written; none is overwritten`;
|
|
36
|
+
const SCHEMAS = {
|
|
37
|
+
agent: "https://docs.nylorun.com/schemas/agent-manifest/v8.json",
|
|
38
|
+
harness: "https://docs.nylorun.com/schemas/harness-manifest/v1.json",
|
|
39
|
+
runtime: "https://docs.nylorun.com/schemas/runtime-manifest/v1.json",
|
|
40
|
+
};
|
|
41
|
+
/** Environment manifest v1's identity: the agent manifest holds it now, or nothing does. */
|
|
42
|
+
const ENVIRONMENT_IDENTITY = ["$schema", "name", "description", "manifestSchemaVersion", "harness"];
|
|
43
|
+
function issuesOf(error) {
|
|
44
|
+
return error.issues
|
|
45
|
+
.map((issue) => ` ${issue.path.length ? `${issue.path.map(String).join(".")}: ` : ""}${issue.message}`)
|
|
46
|
+
.join("\n");
|
|
47
|
+
}
|
|
48
|
+
/** The loop manifest in `path`: an agent manifest v7 or a workflow manifest v3. */
|
|
49
|
+
async function loopManifest(path) {
|
|
50
|
+
const { $schema: _, ...raw } = await readJson(path, "The agent manifest");
|
|
51
|
+
const parsed = raw.kind === "workflow" ? WorkflowManifestSchema.safeParse(raw) : AgentManifestSchema.safeParse(raw);
|
|
52
|
+
if (!parsed.success)
|
|
53
|
+
throw usageError(`${path} is not an agent manifest v7 or a workflow manifest v3:\n${issuesOf(parsed.error)}`);
|
|
54
|
+
return parsed.data;
|
|
55
|
+
}
|
|
56
|
+
/** Whether a loop manifest, or an agent it embeds or delegates to, carries skills. */
|
|
57
|
+
function hasSkills(loop) {
|
|
58
|
+
if ("kind" in loop)
|
|
59
|
+
return Object.values(loop.agents).some(hasSkills);
|
|
60
|
+
if (Object.keys(loop.skills ?? {}).length > 0)
|
|
61
|
+
return true;
|
|
62
|
+
return (loop.tools ?? []).some((tool) => tool.agent !== undefined && hasSkills(tool.agent));
|
|
63
|
+
}
|
|
64
|
+
/** `nylorun migrate v8 <agent.json> [--runtime <file>] [--name <name>] [--out <dir>]` */
|
|
65
|
+
export async function migrateCommand(deps, args) {
|
|
66
|
+
const [version, ...rest] = args;
|
|
67
|
+
if (version === "--help" || version === "-h") {
|
|
68
|
+
deps.out(migrateUsage);
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
if (version !== "v8")
|
|
72
|
+
throw usageError(version === undefined ? migrateUsage : `nylorun migrate knows v8 only.\n${migrateUsage}`);
|
|
73
|
+
const flags = parseStackFlags(rest, { values: ["--runtime", "--name", "--out"] }, migrateUsage);
|
|
74
|
+
if (flags.rest.length !== 1)
|
|
75
|
+
throw usageError(`Usage: ${migrateUsage.split("\n")[0]}`);
|
|
76
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
77
|
+
const loop = await loopManifest(resolve(cwd, flags.rest[0]));
|
|
78
|
+
const name = flags.values.get("--name") ?? loop.id;
|
|
79
|
+
if (!isAgentManifestName(name))
|
|
80
|
+
throw usageError(`${name} is not an agent manifest's name (1-64 letters, digits, '.', '_' and '-', starting with a letter or digit): pass --name.`);
|
|
81
|
+
// The environment manifest: its fields are the runtime section, its harness the loop's.
|
|
82
|
+
let harnessId;
|
|
83
|
+
let runtime;
|
|
84
|
+
const runtimePath = flags.values.get("--runtime");
|
|
85
|
+
const requires = "kind" in loop ? undefined : loop.requires;
|
|
86
|
+
if (runtimePath !== undefined) {
|
|
87
|
+
const environment = await readJson(resolve(cwd, runtimePath), "The environment manifest");
|
|
88
|
+
if (typeof environment.harness === "string")
|
|
89
|
+
harnessId = environment.harness;
|
|
90
|
+
const fields = Object.fromEntries(Object.entries(environment).filter(([key]) => !ENVIRONMENT_IDENTITY.includes(key)));
|
|
91
|
+
// Environment manifest v1 made kind `virtual` when it named none.
|
|
92
|
+
const parsed = RuntimeManifestFileSchema.safeParse({ kind: "virtual", ...fields });
|
|
93
|
+
if (!parsed.success)
|
|
94
|
+
throw usageError(`${runtimePath} does not make a runtime section:\n${issuesOf(parsed.error)}`);
|
|
95
|
+
const { $schema: _, ...section } = parsed.data;
|
|
96
|
+
runtime = section;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
const kind = requires?.sandbox === "pod" ? "pod" : hasSkills(loop) || requires?.sandbox === "any" ? "virtual" : "none";
|
|
100
|
+
runtime = { kind };
|
|
101
|
+
}
|
|
102
|
+
const harness = harnessSectionOf(loop, harnessId === undefined ? {} : { harness: harnessId });
|
|
103
|
+
const out = resolve(cwd, flags.values.get("--out") ?? ".");
|
|
104
|
+
const files = {
|
|
105
|
+
"agent.manifest.json": {
|
|
106
|
+
$schema: SCHEMAS.agent,
|
|
107
|
+
manifestSchemaVersion: 8,
|
|
108
|
+
name,
|
|
109
|
+
...(loop.description === undefined ? {} : { description: loop.description }),
|
|
110
|
+
...(loop.metadata === undefined ? {} : { metadata: loop.metadata }),
|
|
111
|
+
harness: "./harness.manifest.json",
|
|
112
|
+
runtime: "./runtime.manifest.json",
|
|
113
|
+
},
|
|
114
|
+
// A flow's harness section is the flow itself; an agent's keeps `requires` for the composer.
|
|
115
|
+
"harness.manifest.json": {
|
|
116
|
+
...("kind" in harness ? {} : { $schema: SCHEMAS.harness }),
|
|
117
|
+
...harness,
|
|
118
|
+
...(requires === undefined ? {} : { requires }),
|
|
119
|
+
},
|
|
120
|
+
"runtime.manifest.json": { $schema: SCHEMAS.runtime, ...runtime },
|
|
121
|
+
};
|
|
122
|
+
const existing = Object.keys(files).filter((file) => existsSync(join(out, file)));
|
|
123
|
+
if (existing.length > 0)
|
|
124
|
+
throw new CliError(`${relative(cwd, out) || "."} already holds ${existing.join(", ")}: nothing was written. Pass another --out.`, 1);
|
|
125
|
+
await mkdir(out, { recursive: true });
|
|
126
|
+
for (const [file, body] of Object.entries(files))
|
|
127
|
+
await writeFile(join(out, file), `${JSON.stringify(body, null, 2)}\n`);
|
|
128
|
+
const where = relative(cwd, out) || ".";
|
|
129
|
+
deps.out(`Wrote ${Object.keys(files).map((file) => join(where, file)).join(", ")}.`);
|
|
130
|
+
const gaps = [
|
|
131
|
+
...requirementGaps(requires, runtime).map((gap) => `harness.${gap}`),
|
|
132
|
+
...sectionGaps({ harness, runtime }),
|
|
133
|
+
];
|
|
134
|
+
for (const gap of gaps)
|
|
135
|
+
deps.err(` fix before apply: ${gap}`);
|
|
136
|
+
if (!("kind" in harness) && harness.skills !== undefined)
|
|
137
|
+
deps.err(" its skills stay inline, by hash: the Runtime must hold their files. To upload them from source, set harness.manifest.json skills to their folder.");
|
|
138
|
+
deps.err(`Save it with nylorun apply ${where}.`);
|
|
139
|
+
return 0;
|
|
140
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The pi-ai `nylorun tenant models set` loads; nylorun's devDependency has the same version
|
|
3
3
|
* (scripts/check-package.mjs).
|
|
4
4
|
*/
|
|
5
|
-
export declare const PI_AI_VERSION = "
|
|
5
|
+
export declare const PI_AI_VERSION = "1.2.0";
|
|
6
6
|
/** What `nylorun tenant models set` uses of pi-ai: its provider registry and login flows. */
|
|
7
7
|
export interface PiAi {
|
|
8
8
|
createProvider: typeof import("@earendil-works/pi-ai").createProvider;
|
|
@@ -10,7 +10,7 @@ import { CliError } from "../../errors.js";
|
|
|
10
10
|
* The pi-ai `nylorun tenant models set` loads; nylorun's devDependency has the same version
|
|
11
11
|
* (scripts/check-package.mjs).
|
|
12
12
|
*/
|
|
13
|
-
export const PI_AI_VERSION = "
|
|
13
|
+
export const PI_AI_VERSION = "1.2.0";
|
|
14
14
|
const PI_AI = "@earendil-works/pi-ai";
|
|
15
15
|
/** The module written beside an on-demand install, which resolves pi-ai from there. */
|
|
16
16
|
const ENTRY = "pi-ai.js";
|
package/dist/client/models.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { type StackDeps } from "../stack/commands.js";
|
|
2
2
|
export declare const modelsUsage = " 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)";
|
|
3
|
-
export declare const usageUsage = " usage [--scope tenant|
|
|
3
|
+
export declare const usageUsage = " 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";
|
|
4
4
|
/** `nylorun tenant models <verb> ...` */
|
|
5
5
|
export declare function modelsCommand(deps: StackDeps, args: readonly string[]): Promise<number>;
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* `nylorun tenant usage`: one scope's totals for one period, as JSON: the Tenant's, the Agents
|
|
8
|
+
* made from one agent manifest (`--scope manifest --id <name>`), or one turn's.
|
|
9
|
+
*/
|
|
7
10
|
export declare function usageCommand(deps: StackDeps, args: readonly string[]): Promise<number>;
|
|
8
11
|
/**
|
|
9
12
|
* `nylorun tenant calls`: every recorded model call from `--after`, one JSON object per line
|
package/dist/client/models.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* for CI. A key is never an argument.
|
|
6
6
|
*/
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
|
8
|
-
import { CliError } from "../errors.js";
|
|
8
|
+
import { CliError, HelpRequest, isHelp } from "../errors.js";
|
|
9
9
|
import { parseStackFlags } from "../stack/commands.js";
|
|
10
10
|
import { printJson, printTable, readSecret } from "./io.js";
|
|
11
11
|
import { connect, managementClient } from "./target.js";
|
|
@@ -20,8 +20,9 @@ export const modelsUsage = ` models catalog [--tenant <name>] [--json]
|
|
|
20
20
|
the same with an API key piped in on stdin (CI)
|
|
21
21
|
models select <provider> [--model <id>] [--tenant <name>]
|
|
22
22
|
use a provider that has a credential (default: its model)`;
|
|
23
|
-
export const usageUsage = ` usage [--scope tenant|
|
|
24
|
-
what the model calls used and cost (JSON)
|
|
23
|
+
export const usageUsage = ` usage [--scope tenant|manifest|turn] [--id <id>] [--period day|month|total] [--tenant <name>]
|
|
24
|
+
what the model calls used and cost (JSON); --scope
|
|
25
|
+
manifest: the Agents made from agent manifest <id>
|
|
25
26
|
calls [--after <cursor>] [--limit <n>] [--tenant <name>]
|
|
26
27
|
every recorded model call, one JSON line each, until
|
|
27
28
|
caught up; prints the cursor to resume from on stderr`;
|
|
@@ -139,6 +140,8 @@ async function select(deps, args) {
|
|
|
139
140
|
/** `nylorun tenant models <verb> ...` */
|
|
140
141
|
export async function modelsCommand(deps, args) {
|
|
141
142
|
const [verb, ...rest] = args;
|
|
143
|
+
if (isHelp(verb))
|
|
144
|
+
throw new HelpRequest(`Usage:\n${modelsUsage}`);
|
|
142
145
|
if (verb === "catalog" || verb === "providers" || verb === "get")
|
|
143
146
|
return read(deps, verb, rest);
|
|
144
147
|
if (verb === "set")
|
|
@@ -147,15 +150,20 @@ export async function modelsCommand(deps, args) {
|
|
|
147
150
|
return select(deps, rest);
|
|
148
151
|
throw usageError();
|
|
149
152
|
}
|
|
150
|
-
const SCOPES = ["tenant", "
|
|
153
|
+
const SCOPES = ["tenant", "manifest", "turn"];
|
|
151
154
|
const PERIODS = ["day", "month", "total"];
|
|
152
|
-
const USAGE_USAGE = "nylorun tenant usage [--scope tenant|
|
|
153
|
-
/**
|
|
155
|
+
const USAGE_USAGE = "nylorun tenant usage [--scope tenant|manifest|turn] [--id <id>] [--period day|month|total] [--tenant <name>]";
|
|
156
|
+
/**
|
|
157
|
+
* `nylorun tenant usage`: one scope's totals for one period, as JSON: the Tenant's, the Agents
|
|
158
|
+
* made from one agent manifest (`--scope manifest --id <name>`), or one turn's.
|
|
159
|
+
*/
|
|
154
160
|
export async function usageCommand(deps, args) {
|
|
155
161
|
const flags = parseStackFlags(args, { values: ["--tenant", "--scope", "--id", "--period"] }, USAGE_USAGE);
|
|
156
162
|
if (flags.rest.length)
|
|
157
163
|
throw usageError(`Usage: ${USAGE_USAGE}`);
|
|
158
164
|
const scope = flags.values.get("--scope") ?? "tenant";
|
|
165
|
+
if (scope === "agent")
|
|
166
|
+
throw usageError("--scope agent is --scope manifest in protocol 16: --id names an agent manifest.");
|
|
159
167
|
const period = flags.values.get("--period") ?? "total";
|
|
160
168
|
const id = flags.values.get("--id");
|
|
161
169
|
if (!SCOPES.includes(scope))
|
|
@@ -163,7 +171,9 @@ export async function usageCommand(deps, args) {
|
|
|
163
171
|
if (!PERIODS.includes(period))
|
|
164
172
|
throw usageError(`--period is one of ${PERIODS.join(", ")}.`);
|
|
165
173
|
if ((scope === "tenant") !== (id === undefined))
|
|
166
|
-
throw usageError(scope === "tenant"
|
|
174
|
+
throw usageError(scope === "tenant"
|
|
175
|
+
? "--id names an agent manifest or a turn: pass --scope manifest or turn."
|
|
176
|
+
: `--scope ${scope} needs --id.`);
|
|
167
177
|
const query = {
|
|
168
178
|
scope: scope,
|
|
169
179
|
period: period,
|
package/dist/client/runtime.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export declare const RUNTIME_FLAGS: {
|
|
|
10
10
|
export declare const runtimeFlagsUsage: string;
|
|
11
11
|
export declare const usageError: (message: string) => CliError;
|
|
12
12
|
export type Flags = ReturnType<typeof parseStackFlags>;
|
|
13
|
-
/** `args` parsed with the Runtime flags added to `allowed
|
|
13
|
+
/** `args` parsed with the Runtime flags added to `allowed`; `--help` names them too. */
|
|
14
14
|
export declare function parseFlags(args: readonly string[], allowed: {
|
|
15
15
|
booleans?: readonly string[];
|
|
16
16
|
values?: readonly string[];
|
package/dist/client/runtime.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* What the Runtime API
|
|
3
|
-
* flags, the `@nylorun/agents` client of the installation `target.ts` picks, acting
|
|
4
|
-
* with `--as`, and how they read input and print output.
|
|
2
|
+
* What the Runtime API commands (`apply`, `agent-manifests`, `agents`, `sessions`, `artifacts`)
|
|
3
|
+
* share: their flags, the `@nylorun/agents` client of the installation `target.ts` picks, acting
|
|
4
|
+
* for a subject with `--as`, and how they read input and print output.
|
|
5
5
|
*/
|
|
6
6
|
import { readFile } from "node:fs/promises";
|
|
7
7
|
import { AgentsClient, RuntimeError, SUBJECT_SCOPES, } from "@nylorun/agents";
|
|
8
|
-
import { CliError } from "../errors.js";
|
|
9
|
-
import { parseStackFlags } from "../stack/commands.js";
|
|
8
|
+
import { CliError, HelpRequest, isHelp } from "../errors.js";
|
|
9
|
+
import { parseStackFlags, usageText } from "../stack/commands.js";
|
|
10
10
|
import { connect } from "./target.js";
|
|
11
11
|
/** Flags every Runtime API command takes. */
|
|
12
12
|
export const RUNTIME_FLAGS = { values: ["--tenant", "--as"], lists: ["--scope"] };
|
|
13
13
|
export const runtimeFlagsUsage = `Every command takes --tenant <name>, and --as <subject> [--scope <scope>]... to act for a
|
|
14
14
|
person (default scope sessions:own; scopes: ${SUBJECT_SCOPES.join(", ")}).`;
|
|
15
15
|
export const usageError = (message) => new CliError(message, 2);
|
|
16
|
-
/** `args` parsed with the Runtime flags added to `allowed
|
|
16
|
+
/** `args` parsed with the Runtime flags added to `allowed`; `--help` names them too. */
|
|
17
17
|
export function parseFlags(args, allowed, usage) {
|
|
18
|
+
if (args.some(isHelp))
|
|
19
|
+
throw new HelpRequest(`${usageText(usage)}\n\n${runtimeFlagsUsage}`);
|
|
18
20
|
return parseStackFlags(args, {
|
|
19
21
|
booleans: allowed.booleans ?? [],
|
|
20
22
|
values: [...RUNTIME_FLAGS.values, ...(allowed.values ?? [])],
|