@nylorun/agents 0.17.0-beta → 0.19.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 +53 -0
- package/README.md +15 -6
- package/dist/client.d.ts +2 -1
- package/dist/client.js +8 -6
- package/dist/connection.js +26 -34
- package/dist/http.d.ts +3 -4
- package/dist/http.js +20 -68
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/plugins/load.js +22 -37
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,58 @@
|
|
|
1
1
|
# @nylorun/agents
|
|
2
2
|
|
|
3
|
+
## 0.19.0-beta
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 7780b2f: **One client and connection layer.** The Project link and credentials are read one way, and every Runtime client shares one `/health` probe.
|
|
8
|
+
|
|
9
|
+
- `@nylorun/core`: `@nylorun/core/project` (Node only, the one Core subpath that imports Node modules) reads the Nylorun home, a local Tenant's Host root (`tenantHostRoot`), the Project root (`findProjectRoot`) and the Project link and credentials (`findLinkedProject`, `readProjectLink`, `readCredentialsFile`), validated with `ProjectLinkFileSchema` and `ProjectCredentialsFileSchema`; a broken or newer file throws `ProjectFileError`. `@nylorun/core/transport` (browser-safe) holds the `/health` compatibility probe (`checkHealth`), `parseProtocolRange` (on `ProtocolRangeSchema`, ignoring fields a newer Host adds), `describeIncompatibility`, `requestHeaders` and `readBody`.
|
|
10
|
+
- `@nylorun/agents`: `resolveConnection` and `createClient()` find the Project as every reader does: the nearest directory with `.nylorun/` from `cwd` upwards, never the home directory or above it (they used to walk to the filesystem root, and past a `.nylorun/` without a link). A link without `credentials.json`, or a link or credentials file that does not validate, is now `connection_missing` naming the file and `npx nylorun start`, instead of a raw file-system or parse error. `@nylorun/agents/client` loads the link reader only when `createClient()` resolves a connection, so it imports no Node module. The root entry re-exports `checkHealth`, `describeIncompatibility` and `parseProtocolRange`.
|
|
11
|
+
- `@nylorun/admin`: `createAdmin` reads the Project link with the same reader. A link that does not validate is now refused like one from an older nylorun (when nothing else names the Host root), and a linked Project's `credentials.json` that does not validate is `connection_missing` instead of being skipped. `@nylorun/admin/project` re-exports `@nylorun/core/project` for tools that depend on this package. The `/health` check and request headers come from `@nylorun/core/transport`.
|
|
12
|
+
- `nylorun`: reads the Project link and credentials with `@nylorun/core/project`; a credentials file whose keys are not 64 hex characters is replaced by `start`, as an unreadable one was. No command changes.
|
|
13
|
+
- `@nylorun/cli`: `nylo` reads the Project root, link and credentials through `@nylorun/admin/project` instead of its own copies, with the same messages. No command changes.
|
|
14
|
+
- `@nylorun/studio`: the server's Runtime compatibility probe is `checkHealth` from `@nylorun/agents`.
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- 070674d: **One source of truth for the manifest and Harness API schemas.**
|
|
19
|
+
|
|
20
|
+
- `@nylorun/core`: `Agent.from` (`agentFrom`) checks a manifest with `AgentManifestSchema`, the Runtime's own check, instead of a validator of its own, and refuses with `agent.build-failed` and the schema's messages (each prefixed with its path, such as `capabilities.0.tools.0.name: …`). It now refuses what the Runtime refuses at registration and it let through, such as unknown fields (it dropped them) or a tool or MCP server name that not every model provider accepts. The schema names a top-level `model` or `schemaVersion` and a capability's `model` as before, and the skill whose `files` are wrong (`Skill 'triage' files must include SKILL.md`). The rebuilt manifest is a frozen copy; the object passed in is no longer frozen. `AgentManifestSchema` and `WorkflowManifestSchema` are no longer force-cast to their types: the build checks that each accepts exactly `AgentManifest` and `WorkflowManifest`. `@nylorun/core/contracts` exports `McpServerManifestSchema`, `HEADER_NAME_PATTERN` and `LOOPBACK_HOSTS`. The Harness API's types (`@nylorun/core/harness-api`) are inferred from the schemas that validate its frames, so they match them: `TurnStart.manifest`, `TurnStart.checkpoint` and `RunRouting.rootManifest` are `object`, `WorkspaceCall.tool` is a sandbox tool name, `ABORT_REASONS` is a tuple, and properties are no longer `readonly` (arrays that were stay so).
|
|
21
|
+
- `@nylorun/harness`: `runDurable` checks the manifest once, when it rebuilds the agent, instead of twice. The unpublished, stale `schemas/manifest.schema.json` (manifest `schemaVersion` 2) is gone; `z.toJSONSchema(AgentManifestSchema)` is the manifest's JSON Schema.
|
|
22
|
+
- `@nylorun/agents`: a plugin's MCP servers are checked with core's `McpServerManifestSchema`, header-name pattern and loopback hosts. A server whose key is not a name every model provider accepts is skipped with a `plugin.mcp-server-skipped` warning, since the Runtime would refuse the manifest declaring it.
|
|
23
|
+
- `@nylorun/runtime`: the stack configuration reads its four listeners (`NYLORUN_LISTEN_*`, `NYLORUN_GATES_LISTEN_*`, `NYLORUN_HARNESS_LISTEN_*`, `NYLORUN_EGRESS_LISTEN_*`) with one parser; the variables and their errors are unchanged.
|
|
24
|
+
|
|
25
|
+
- Pin core to the tested release.
|
|
26
|
+
- Updated dependencies [7780b2f]
|
|
27
|
+
- Updated dependencies [070674d]
|
|
28
|
+
- @nylorun/core@0.18.0-beta
|
|
29
|
+
|
|
30
|
+
## 0.18.0-beta
|
|
31
|
+
|
|
32
|
+
### Major Changes
|
|
33
|
+
|
|
34
|
+
- 50e2f7e: **Protocol 10: MCP credentials come from a session's vaults only.** Nylorun no longer signs the installation in to MCP servers with OAuth and no longer asks a credential resolver. Upgrade every package together; MIGRATION.md has the details.
|
|
35
|
+
|
|
36
|
+
- **Breaking (`@nylorun/runtime`): the MCP OAuth connect is gone.** `POST /v1/tenant/vaults/{vaultId}/oauth/start` and `GET /v1/oauth/callback` answer `404`. The vault credential type `oauth` and its refresh are gone: a credential is a `bearer` token or a `headers` map bound to a URL. Migration `0016_mcp_oauth_removed` drops the table of pending connects and deletes every `oauth` credential, writing one audit row each (actor `migration`); the Runtime logs `oauth_credential_removed` once for each, naming its vault, id and URL.
|
|
37
|
+
- **Breaking (`@nylorun/runtime`): the credential resolver is gone.** The gateway no longer asks the operator's resolver for a person's credential when the session's vaults hold none. A process that still sets a `NYLORUN_RESOLVER_*` variable logs `resolver_removed` and ignores it. Keep a person's own keys in their user vault and attach it to their sessions (`vaultIds`). `TenantConfig.resolver`, `TenantConfig.publicUrl`, `TenantConfig.vaultFetch`, `startEphemeralRuntime({ resolver })`, the `ResolverConfig` export and `VaultService`'s `fetch` option are removed; `NYLORUN_PUBLIC_URL` still sets the protected resource metadata's `resource`.
|
|
38
|
+
- **Breaking (`@nylorun/core`):** `PROTOCOL_VERSION` is 10 and `HOST_PROTOCOL` 4–10. `StartOAuthRequest`, `StartOAuthResponse`, the `oauth` variants of `CreateCredentialRequest` and `RotateCredentialRequest`, `oauth` in `CredentialInfo.type` and `CredentialInfo.expiresAt` are removed, and `ERROR_CODES` drops `oauth_client_required`, `oauth_state_invalid` and `oauth_failed`. `@nylorun/agents` and `@nylorun/cli` send protocol 10.
|
|
39
|
+
- **Breaking (`@nylorun/admin`):** `admin.vaults` loses its OAuth start method.
|
|
40
|
+
- **Breaking (`nylorun`):** the `connect` subcommand of `nylorun mcp` is removed (`nylorun mcp inspect` lists a server's tools instead), and the gateway's Compose service no longer passes the `NYLORUN_RESOLVER_*` variables.
|
|
41
|
+
- `@nylorun/studio`: the Credentials page loses the OAuth type, the Expires column and the OAuth connect hint.
|
|
42
|
+
|
|
43
|
+
### Patch Changes
|
|
44
|
+
|
|
45
|
+
- fed5e58: **Docs: reaching a person's accounts** (R2b C5). The READMEs describe MCP credentials as protocol 10 has them (a `bearer` token or a `headers` map per URL, with `via` and an identity header for an MCP gateway), and point to "MCP servers and HTTP tools" in DEPLOYMENT.md: the operator's flow from credential to preview, tool settings, deferral, stored results and the error codes a model sees, with gateway recipes for Arcade, ToolHive, Obot and Nylorun Cloud and the proxy pattern for gateways that mint per person. `HttpToolTarget.credential`'s doc (`@nylorun/core`) no longer names the removed credential resolver.
|
|
46
|
+
- Pin core to the tested release.
|
|
47
|
+
- Updated dependencies [50e2f7e]
|
|
48
|
+
- Updated dependencies [e0e39ff]
|
|
49
|
+
- Updated dependencies [fed5e58]
|
|
50
|
+
- Updated dependencies [18f9a2f]
|
|
51
|
+
- Updated dependencies [da93711]
|
|
52
|
+
- Updated dependencies [90a817d]
|
|
53
|
+
- Updated dependencies [b8d10cb]
|
|
54
|
+
- @nylorun/core@0.17.0-beta
|
|
55
|
+
|
|
3
56
|
## 0.17.0-beta
|
|
4
57
|
|
|
5
58
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -69,8 +69,9 @@ answer was lost with the gateway is `uncertain` and never sent again.
|
|
|
69
69
|
`credential` works as for a remote MCP server: the session's attached vaults
|
|
70
70
|
must hold a credential bound to the tool's exact URL (a `credentialSelections`
|
|
71
71
|
entry whose `serverName` is the `credential` picks one when several are), else
|
|
72
|
-
the
|
|
73
|
-
|
|
72
|
+
the call fails before it is sent (`http.credential`). A credential with `via`
|
|
73
|
+
sends the call there instead, such as a gateway, with the session owner in its
|
|
74
|
+
identity header, and a `401` is `credential_rejected` to the model, not retried. `approval: "always"` pauses the turn for `session.approve()`; a
|
|
74
75
|
denied call never runs and the model sees the denial. A remote MCP server takes
|
|
75
76
|
`approval: "always"` too, for every one of its tools:
|
|
76
77
|
`.mcp({ shop: { type: "streamable-http", url, approval: "always" } })`.
|
|
@@ -85,9 +86,13 @@ only on the Runtime (a local `run()` reports `http.runtime-only`).
|
|
|
85
86
|
2. Environment — if `NYLORUN_RUNTIME_URL` or `NYLORUN_SERVER_KEY` is set, both
|
|
86
87
|
must be present
|
|
87
88
|
3. Project link — `.nylorun/link.json` + `credentials.json` (the application
|
|
88
|
-
key), written by `npx nylorun start
|
|
89
|
-
|
|
90
|
-
the
|
|
89
|
+
key), written by `npx nylorun start`, in the nearest directory with
|
|
90
|
+
`.nylorun/` from `cwd` upwards (never the home directory or above it, whose
|
|
91
|
+
`.nylorun/` is the Nylorun home). A link from an older nylorun
|
|
92
|
+
(format 0 to 2), a link without credentials, or a file that does not
|
|
93
|
+
validate fails with `connection_missing`: run `npx nylorun start` in the
|
|
94
|
+
project again. The Project files are read by `@nylorun/core/project`, as
|
|
95
|
+
`@nylorun/admin`, `nylorun` and `nylo` read them.
|
|
91
96
|
|
|
92
97
|
Sources never mix. Partial environment fails with `connection_missing`. A
|
|
93
98
|
Runtime serves one Tenant, so nothing names it: the `tenant` option is gone
|
|
@@ -163,6 +168,10 @@ const assistant = Agent({ id: "assistant", name: "Assistant" })
|
|
|
163
168
|
|
|
164
169
|
Each key names a server; its `name` defaults to the key and, when given, must equal it. Repeated `.mcp()` calls add servers to the same capability. Nylorun accepts remote servers only: `streamable-http` and `sse` ([Agent Plugins MCP servers](https://agent-plugins.org/plugin-authors/mcp-servers)); a `stdio` server is refused, so run it behind an HTTP transport and declare its URL. Attach an Agent Plugin package with `.plugin(path)`: its skills and remote MCP servers join the agent, and a stdio server in its `mcp.json` throws.
|
|
165
170
|
|
|
171
|
+
Set a server's tools one by one with `tools`, keyed by the server's own tool names, `"*"` for the rest: `{ "*": { enabled: false }, search_issues: { enabled: true }, create_issue: { enabled: true, approval: "always" } }` is an allowlist with one approval. When an agent's MCP tools would fill more than a tenth of the model's context window, the Runtime defers them: the model finds them with `tool_search` and runs them with `tool_call`. `deferred: true` or `false`, on a tool or on the server, decides instead. Either setting makes the manifest v6 (see MIGRATION.md).
|
|
172
|
+
|
|
173
|
+
A manifest never carries a server's credential, and its `headers` hold no secret. The operator gives the server's URL a vault credential (a token or a header map, or a gateway's key with `via` and an identity header for servers that need each person's sign-in), and the session's attached vaults supply it: [DEPLOYMENT.md](../DEPLOYMENT.md#mcp-servers-and-http-tools), which also shows `nylorun mcp inspect` for listing a server's tools first. The model knows each tool as `server__tool`, with characters outside `[A-Za-z0-9_-]` replaced by `_`; a failed call reaches it with a code (`credential_rejected` on a `401`, `mcp.unreachable`, …), and a result past 32 KiB, or an image, becomes an artifact of the session that it reads with `read_artifact`.
|
|
174
|
+
|
|
166
175
|
Give a session a sandbox when you open it. The agent declares nothing, so the same agent runs with or without one, in any Tenant:
|
|
167
176
|
|
|
168
177
|
```ts
|
|
@@ -618,7 +627,7 @@ nothing, and no subject reaches the Management API (`/v1/tenant/*`). Vaults are
|
|
|
618
627
|
the installation's, created through the Management API with a management key
|
|
619
628
|
(`admin.vaults.create({ scope: "installation", … })` in
|
|
620
629
|
[`@nylorun/admin`](../admin/README.md)); a session attaches them with
|
|
621
|
-
`vaultIds`, and a person's own
|
|
630
|
+
`vaultIds`, and a person's own keys go in their user vault
|
|
622
631
|
([DEPLOYMENT.md](../DEPLOYMENT.md#credentials)). A subject is 1–200 visible ASCII
|
|
623
632
|
characters (spaces only inside) and `host` is reserved. Your server must drop
|
|
624
633
|
any `Nylorun-*` header its own clients send, and only an application key can act
|
package/dist/client.d.ts
CHANGED
|
@@ -5,7 +5,8 @@ import type { Destination } from "./http.js";
|
|
|
5
5
|
* Build a Tenant API client.
|
|
6
6
|
* Explicit connection fields keep today's sync Transport resolution (including
|
|
7
7
|
* environment fill-ins). With no connection fields, resolves via
|
|
8
|
-
* `resolveConnection` (environment → project link)
|
|
8
|
+
* `resolveConnection` (environment → project link), which reads the Project's files: it is
|
|
9
|
+
* loaded only then, so this entry point imports no Node module (Studio's web bundle uses it).
|
|
9
10
|
*/
|
|
10
11
|
export declare function createClient(destination: Destination): AgentsClient;
|
|
11
12
|
export declare function createClient(destination?: undefined): Promise<AgentsClient>;
|
package/dist/client.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
export * from "./session-client.js";
|
|
2
2
|
import { AgentsClient } from "./session-client.js";
|
|
3
|
-
import { resolveConnection } from "./connection.js";
|
|
4
3
|
export function createClient(destination = {}) {
|
|
5
4
|
if (destination.url !== undefined || destination.key !== undefined) {
|
|
6
5
|
return new AgentsClient(destination);
|
|
7
6
|
}
|
|
8
|
-
return
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
return import("./connection.js").then(async ({ resolveConnection }) => {
|
|
8
|
+
const connection = await resolveConnection();
|
|
9
|
+
return new AgentsClient({
|
|
10
|
+
url: connection.url,
|
|
11
|
+
key: connection.key,
|
|
12
|
+
...(destination.fetch ? { fetch: destination.fetch } : {}),
|
|
13
|
+
});
|
|
14
|
+
});
|
|
13
15
|
}
|
package/dist/connection.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { dirname, join, resolve } from "node:path";
|
|
3
|
-
import { ProjectCredentialsFileSchema, ProjectLinkFileSchema, } from "@nylorun/core/contracts";
|
|
1
|
+
import { ProjectFileError, findLinkedProject } from "@nylorun/core/project";
|
|
4
2
|
import { env } from "./http.js";
|
|
5
3
|
export class ConnectionError extends Error {
|
|
6
4
|
code = "connection_missing";
|
|
@@ -17,37 +15,31 @@ function missing(tried) {
|
|
|
17
15
|
function stripTrailingSlash(url) {
|
|
18
16
|
return url.replace(/\/$/, "");
|
|
19
17
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (error.code === "ENOENT") {
|
|
31
|
-
const parent = dirname(dir);
|
|
32
|
-
if (parent === dir)
|
|
33
|
-
return undefined;
|
|
34
|
-
dir = parent;
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
throw error;
|
|
38
|
-
}
|
|
39
|
-
const link = ProjectLinkFileSchema.parse(JSON.parse(linkRaw));
|
|
40
|
-
// Formats 0 to 2 are from older releases.
|
|
41
|
-
if (link.format < 3)
|
|
42
|
-
throw new ConnectionError(`connection_missing: the Project link at ${linkPath} is from an older nylorun. ` +
|
|
43
|
-
`Run "npx nylorun start" in this project to link it again.`);
|
|
44
|
-
const credentials = ProjectCredentialsFileSchema.parse(JSON.parse(await readFile(join(nylorun, "credentials.json"), "utf8")));
|
|
45
|
-
return {
|
|
46
|
-
url: stripTrailingSlash(link.hostUrl),
|
|
47
|
-
key: credentials.applicationKey,
|
|
48
|
-
source: "project-link",
|
|
49
|
-
};
|
|
18
|
+
/** The application key of the linked Project from `cwd` upwards (`@nylorun/core/project`). */
|
|
19
|
+
function readProjectLink(cwd) {
|
|
20
|
+
let project;
|
|
21
|
+
try {
|
|
22
|
+
project = findLinkedProject(cwd);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (error instanceof ProjectFileError)
|
|
26
|
+
throw new ConnectionError(`connection_missing: ${error.message}`);
|
|
27
|
+
throw error;
|
|
50
28
|
}
|
|
29
|
+
if (!project)
|
|
30
|
+
return undefined;
|
|
31
|
+
// Formats 0 to 2 are from older releases.
|
|
32
|
+
if (project.link.format < 3)
|
|
33
|
+
throw new ConnectionError(`connection_missing: the Project link at ${project.linkPath} is from an older nylorun. ` +
|
|
34
|
+
`Run "npx nylorun start" in this project to link it again.`);
|
|
35
|
+
if (!project.credentials)
|
|
36
|
+
throw new ConnectionError(`connection_missing: the Project at ${project.root} has a link but no credentials ` +
|
|
37
|
+
`(.nylorun/credentials.json). Run "npx nylorun start" in this project to link it again.`);
|
|
38
|
+
return {
|
|
39
|
+
url: project.link.hostUrl,
|
|
40
|
+
key: project.credentials.applicationKey,
|
|
41
|
+
source: "project-link",
|
|
42
|
+
};
|
|
51
43
|
}
|
|
52
44
|
/**
|
|
53
45
|
* Resolve Tenant API connection: options → environment → project link (D§7.1). A connection is a
|
|
@@ -83,7 +75,7 @@ export async function resolveConnection(options) {
|
|
|
83
75
|
}
|
|
84
76
|
tried.push("project-link");
|
|
85
77
|
const cwd = options?.cwd ?? (typeof process !== "undefined" ? process.cwd() : ".");
|
|
86
|
-
const fromLink =
|
|
78
|
+
const fromLink = readProjectLink(cwd);
|
|
87
79
|
if (fromLink)
|
|
88
80
|
return fromLink;
|
|
89
81
|
missing(tried);
|
package/dist/http.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ProtocolRange } from "@nylorun/core/compatibility";
|
|
2
|
+
import { type Incompatibility } from "@nylorun/core/transport";
|
|
2
3
|
/**
|
|
3
4
|
* Bearer tokens for a client that holds no Tenant key: a trusted issuer's tokens (Host feature
|
|
4
5
|
* `trusted-issuers`), from the operator's identity provider. `get` returns a current token and
|
|
@@ -26,9 +27,7 @@ export declare class RuntimeError extends Error {
|
|
|
26
27
|
readonly body: unknown;
|
|
27
28
|
constructor(status: number, body: unknown);
|
|
28
29
|
}
|
|
29
|
-
export type IncompatibleReason =
|
|
30
|
-
ok: false;
|
|
31
|
-
}>;
|
|
30
|
+
export type IncompatibleReason = Incompatibility;
|
|
32
31
|
/** Host protocol is outside the client's supported range or missing required features. */
|
|
33
32
|
export declare class IncompatibleRuntimeError extends Error {
|
|
34
33
|
readonly compatibility: IncompatibleReason;
|
package/dist/http.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { PROTOCOL_FEATURES,
|
|
1
|
+
import { PROTOCOL_FEATURES, } from "@nylorun/core/compatibility";
|
|
2
|
+
import { advertisedProtocol, checkHealth, clientCompatibility, describeIncompatibility, readBody, requestHeaders, } from "@nylorun/core/transport";
|
|
2
3
|
export function env(name) {
|
|
3
4
|
return typeof process !== "undefined" ? process.env[name] : undefined;
|
|
4
5
|
}
|
|
@@ -18,10 +19,7 @@ export class IncompatibleRuntimeError extends Error {
|
|
|
18
19
|
host;
|
|
19
20
|
constructor(compatibility) {
|
|
20
21
|
const remedy = remedyFor(compatibility);
|
|
21
|
-
|
|
22
|
-
? `client protocol ${compatibility.client} is outside Host range ${compatibility.host.min}–${compatibility.host.max}`
|
|
23
|
-
: `Host is missing required features: ${compatibility.missing.join(", ")}`;
|
|
24
|
-
super(`Incompatible Runtime: ${detail}. ${remedy}`);
|
|
22
|
+
super(`Incompatible Runtime: ${describeIncompatibility(compatibility)}. ${remedy}`);
|
|
25
23
|
this.name = "IncompatibleRuntimeError";
|
|
26
24
|
this.compatibility = compatibility;
|
|
27
25
|
this.remedy = remedy;
|
|
@@ -37,21 +35,6 @@ function remedyFor(compatibility) {
|
|
|
37
35
|
}
|
|
38
36
|
return "Upgrade the Runtime Host with nylorun runtime restart from a newer CLI, or install a matching older client (npm i -D @nylorun/cli@<compatible>).";
|
|
39
37
|
}
|
|
40
|
-
function parseProtocolRange(value) {
|
|
41
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
42
|
-
return undefined;
|
|
43
|
-
const record = value;
|
|
44
|
-
if (typeof record.min !== "number" ||
|
|
45
|
-
typeof record.max !== "number" ||
|
|
46
|
-
!Array.isArray(record.features) ||
|
|
47
|
-
!record.features.every((f) => typeof f === "string"))
|
|
48
|
-
return undefined;
|
|
49
|
-
return {
|
|
50
|
-
min: record.min,
|
|
51
|
-
max: record.max,
|
|
52
|
-
features: record.features,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
38
|
export class Transport {
|
|
56
39
|
url;
|
|
57
40
|
/** The Tenant key; empty for a client that uses issuer tokens. */
|
|
@@ -99,36 +82,23 @@ export class Transport {
|
|
|
99
82
|
async ensureCompatible(signal) {
|
|
100
83
|
if (this.check.compatible)
|
|
101
84
|
return;
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
signal,
|
|
85
|
+
const health = await checkHealth(this.url, {
|
|
86
|
+
fetch: this.fetcher,
|
|
87
|
+
...(signal ? { signal } : {}),
|
|
106
88
|
});
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
body = JSON.parse(text);
|
|
111
|
-
}
|
|
112
|
-
catch {
|
|
113
|
-
/* keep text */
|
|
114
|
-
}
|
|
115
|
-
if (!response.ok)
|
|
116
|
-
throw new RuntimeError(response.status, body);
|
|
117
|
-
const protocol = parseProtocolRange(body && typeof body === "object" && !Array.isArray(body)
|
|
118
|
-
? body.protocol
|
|
119
|
-
: undefined);
|
|
120
|
-
if (!protocol)
|
|
89
|
+
if (health.result === "failed")
|
|
90
|
+
throw new RuntimeError(health.status, health.body);
|
|
91
|
+
if (health.result === "unadvertised")
|
|
121
92
|
throw new IncompatibleRuntimeError({
|
|
122
93
|
ok: false,
|
|
123
94
|
reason: "feature",
|
|
124
95
|
missing: [...PROTOCOL_FEATURES],
|
|
125
96
|
host: { min: 0, max: 0, features: [] },
|
|
126
97
|
});
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
this.check.
|
|
131
|
-
this.check.range = protocol;
|
|
98
|
+
if (health.result === "incompatible")
|
|
99
|
+
throw new IncompatibleRuntimeError(health.compatibility);
|
|
100
|
+
this.check.features = [...health.protocol.features];
|
|
101
|
+
this.check.range = health.protocol;
|
|
132
102
|
this.check.compatible = true;
|
|
133
103
|
}
|
|
134
104
|
/** Throws `IncompatibleRuntimeError` before anything is sent when the Host lacks `feature`. */
|
|
@@ -154,8 +124,8 @@ export class Transport {
|
|
|
154
124
|
const bearer = this.token
|
|
155
125
|
? await this.token.get(init.signal ?? undefined)
|
|
156
126
|
: this.key;
|
|
157
|
-
|
|
158
|
-
|
|
127
|
+
for (const [name, value] of Object.entries(requestHeaders(bearer)))
|
|
128
|
+
headers.set(name, value);
|
|
159
129
|
// JSON unless the caller says otherwise (an artifact upload sends the file's type).
|
|
160
130
|
if (init.body && !headers.has("content-type"))
|
|
161
131
|
headers.set("Content-Type", "application/json");
|
|
@@ -183,35 +153,17 @@ export class Transport {
|
|
|
183
153
|
await this.ensureCompatible(init.signal === null ? undefined : init.signal);
|
|
184
154
|
return this.request(path, init, { retried426: true });
|
|
185
155
|
}
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
try {
|
|
189
|
-
body = JSON.parse(text);
|
|
190
|
-
}
|
|
191
|
-
catch {
|
|
192
|
-
/* keep text */
|
|
193
|
-
}
|
|
194
|
-
const protocol = parseProtocolRange(body && typeof body === "object" && !Array.isArray(body)
|
|
195
|
-
? body.protocol
|
|
196
|
-
: undefined);
|
|
156
|
+
const body = await readBody(response);
|
|
157
|
+
const protocol = advertisedProtocol(body);
|
|
197
158
|
if (protocol) {
|
|
198
|
-
const result =
|
|
159
|
+
const result = clientCompatibility(protocol);
|
|
199
160
|
if (!result.ok)
|
|
200
161
|
throw new IncompatibleRuntimeError(result);
|
|
201
162
|
}
|
|
202
163
|
throw new RuntimeError(426, body);
|
|
203
164
|
}
|
|
204
|
-
if (!response.ok)
|
|
205
|
-
|
|
206
|
-
let body = text;
|
|
207
|
-
try {
|
|
208
|
-
body = JSON.parse(text);
|
|
209
|
-
}
|
|
210
|
-
catch {
|
|
211
|
-
/* keep text */
|
|
212
|
-
}
|
|
213
|
-
throw new RuntimeError(response.status, body);
|
|
214
|
-
}
|
|
165
|
+
if (!response.ok)
|
|
166
|
+
throw new RuntimeError(response.status, await readBody(response));
|
|
215
167
|
return response;
|
|
216
168
|
}
|
|
217
169
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { hashManifest, PROTOCOL_VERSION, PROTOCOL_FEATURES, HOST_PROTOCOL, DEFINITION_SCHEMA_VERSION, PROTOCOL_HEADER, SUBJECT_HEADER, SCOPES_HEADER, SESSION_ID_HEADER, TURN_ID_HEADER, AGENT_ID_HEADER, TENANT_ID_PATTERN, isTenantId, newTenantId, newPrincipalId, checkCompatibility, compareVersions, ERROR_CODES, } from "@nylorun/core/compatibility";
|
|
2
2
|
export type { ProtocolFeature, ProtocolRange, Compatibility, ErrorCode, } from "@nylorun/core/compatibility";
|
|
3
|
+
export { checkHealth, describeIncompatibility, parseProtocolRange, } from "@nylorun/core/transport";
|
|
4
|
+
export type { HealthCheck, Incompatibility } from "@nylorun/core/transport";
|
|
3
5
|
export { Agent, AgentBuilder } from "./builder.js";
|
|
4
6
|
export { AgentBuildError, AgentLifecycleError, VerdictSchema, isVerdict, WorkflowBuildError, isBuiltWorkflow, capability, CapabilityBuilder, flow, tool, http, defineSchema, ToolError, } from "@nylorun/core/define";
|
|
5
7
|
export type { FlowAgentBuilder, Flow, Named, LoopOptions, StageOptions, CapabilityIdentity, HttpTarget, } from "@nylorun/core/define";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Definition authoring and wire contracts are the only harness runtime imports.
|
|
2
2
|
export { hashManifest, PROTOCOL_VERSION, PROTOCOL_FEATURES, HOST_PROTOCOL, DEFINITION_SCHEMA_VERSION, PROTOCOL_HEADER, SUBJECT_HEADER, SCOPES_HEADER, SESSION_ID_HEADER, TURN_ID_HEADER, AGENT_ID_HEADER, TENANT_ID_PATTERN, isTenantId, newTenantId, newPrincipalId, checkCompatibility, compareVersions, ERROR_CODES, } from "@nylorun/core/compatibility";
|
|
3
|
+
// The `/health` compatibility probe every Runtime client shares (Studio's server uses it).
|
|
4
|
+
export { checkHealth, describeIncompatibility, parseProtocolRange, } from "@nylorun/core/transport";
|
|
3
5
|
export { Agent, AgentBuilder } from "./builder.js";
|
|
4
6
|
export { AgentBuildError, AgentLifecycleError, VerdictSchema, isVerdict, WorkflowBuildError, isBuiltWorkflow, capability, CapabilityBuilder, flow, tool, http, defineSchema, ToolError, } from "@nylorun/core/define";
|
|
5
7
|
export { SUBJECT_SCOPES } from "@nylorun/core/contracts";
|
package/dist/plugins/load.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
2
2
|
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
3
3
|
import { stdioMcpRefusal } from "@nylorun/core/define";
|
|
4
|
+
import { HEADER_NAME_PATTERN, LOOPBACK_HOSTS, McpServerManifestSchema, } from "@nylorun/core/contracts";
|
|
4
5
|
import { loadSkillsFromDirectory } from "../skills/load.js";
|
|
5
6
|
export const PLUGIN_SCHEMA = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json";
|
|
6
7
|
export const MCP_SCHEMA = "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json";
|
|
@@ -211,38 +212,34 @@ function validateServer(name, value) {
|
|
|
211
212
|
return validateRemote(name, value);
|
|
212
213
|
return `type '${value.type}' is not streamable-http or sse`;
|
|
213
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* A plugin's server as a manifest declares it (`McpServerManifestSchema`), named by its key. A
|
|
217
|
+
* plugin may set only type, url and headers, and the URL and headers must be fit to send.
|
|
218
|
+
*/
|
|
214
219
|
function validateRemote(name, value) {
|
|
215
220
|
const allowed = new Set(["type", "url", "headers"]);
|
|
216
221
|
const unknown = Object.keys(value).filter((key) => !allowed.has(key));
|
|
217
222
|
if (unknown.length > 0)
|
|
218
223
|
return `${value.type} servers take only type, url and headers, not ${unknown.join(", ")}`;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
224
|
+
const parsed = McpServerManifestSchema.safeParse({ ...value, name });
|
|
225
|
+
if (!parsed.success)
|
|
226
|
+
return parsed.error.issues
|
|
227
|
+
.map((issue) => (issue.path.length ? `${issue.path.join(".")}: ${issue.message}` : issue.message))
|
|
228
|
+
.join("; ");
|
|
229
|
+
const server = parsed.data;
|
|
230
|
+
const problem = remoteUrlProblem(server.url);
|
|
222
231
|
if (problem)
|
|
223
232
|
return problem;
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if (!
|
|
227
|
-
return
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
return `header '${key}' is not a valid header`;
|
|
233
|
-
const folded = key.toLowerCase();
|
|
234
|
-
if (seen.has(folded))
|
|
235
|
-
return `header '${key}' is repeated`;
|
|
236
|
-
seen.add(folded);
|
|
237
|
-
headers[key] = item;
|
|
238
|
-
}
|
|
233
|
+
const seen = new Set();
|
|
234
|
+
for (const [key, item] of Object.entries(server.headers ?? {})) {
|
|
235
|
+
if (!HEADER_NAME_PATTERN.test(key) || /[\r\n\0]/.test(item))
|
|
236
|
+
return `header '${key}' is not a valid header`;
|
|
237
|
+
const folded = key.toLowerCase();
|
|
238
|
+
if (seen.has(folded))
|
|
239
|
+
return `header '${key}' is repeated`;
|
|
240
|
+
seen.add(folded);
|
|
239
241
|
}
|
|
240
|
-
return
|
|
241
|
-
name,
|
|
242
|
-
type: value.type,
|
|
243
|
-
url: value.url,
|
|
244
|
-
...(headers === undefined ? {} : { headers }),
|
|
245
|
-
};
|
|
242
|
+
return server;
|
|
246
243
|
}
|
|
247
244
|
/**
|
|
248
245
|
* Why `value` is not a remote server URL: https, or plain http to this machine's loopback (a
|
|
@@ -264,22 +261,10 @@ function remoteUrlProblem(value) {
|
|
|
264
261
|
return undefined;
|
|
265
262
|
if (url.protocol !== "http:")
|
|
266
263
|
return `url must be https, not ${url.protocol.slice(0, -1)}`;
|
|
267
|
-
if (
|
|
264
|
+
if (LOOPBACK_HOSTS.test(url.hostname))
|
|
268
265
|
return undefined;
|
|
269
266
|
return `plain http is allowed only for localhost, 127.0.0.1 or [::1]; use https for ${url.hostname}`;
|
|
270
267
|
}
|
|
271
|
-
function isLoopback(hostname) {
|
|
272
|
-
const host = hostname.replace(/^\[|\]$/g, "");
|
|
273
|
-
if (host === "localhost" || host === "::1")
|
|
274
|
-
return true;
|
|
275
|
-
const match = /^127\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
|
|
276
|
-
if (!match)
|
|
277
|
-
return false;
|
|
278
|
-
return match.slice(1).every((part) => Number(part) <= 255);
|
|
279
|
-
}
|
|
280
|
-
function isHeaderName(name) {
|
|
281
|
-
return /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(name);
|
|
282
|
-
}
|
|
283
268
|
function isPluginName(value) {
|
|
284
269
|
if (typeof value !== "string" || value.length < 1 || value.length > 64)
|
|
285
270
|
return false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nylorun/agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0-beta",
|
|
4
4
|
"description": "Nylorun agent definitions and session client",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"test": "vitest run --passWithNoTests"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@nylorun/core": "0.
|
|
46
|
+
"@nylorun/core": "0.18.0-beta",
|
|
47
47
|
"gray-matter": "^4.0.3"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|