@nylorun/agents 0.2.0-beta → 0.4.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 +30 -0
- package/README.md +65 -4
- package/dist/client.d.ts +68 -2
- package/dist/client.js +66 -0
- package/dist/execute-action.js +9 -24
- package/dist/executor.d.ts +1 -1
- package/dist/executor.js +11 -10
- package/dist/index.d.ts +14 -2
- package/dist/index.js +6 -0
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +1 -0
- package/dist/mcp/mcp.d.ts +30 -0
- package/dist/mcp/mcp.js +80 -0
- package/dist/plugins/launch.d.ts +17 -0
- package/dist/plugins/launch.js +71 -0
- package/dist/plugins/load.d.ts +23 -0
- package/dist/plugins/load.js +369 -0
- package/dist/plugins/plugin.d.ts +8 -0
- package/dist/plugins/plugin.js +35 -0
- package/dist/sandbox/index.d.ts +2 -0
- package/dist/sandbox/index.js +1 -0
- package/dist/sandbox/sandbox.d.ts +28 -0
- package/dist/sandbox/sandbox.js +93 -0
- package/dist/skills/catalog.d.ts +5 -0
- package/dist/skills/catalog.js +14 -0
- package/dist/skills/index.d.ts +5 -0
- package/dist/skills/index.js +3 -0
- package/dist/skills/load.d.ts +27 -0
- package/dist/skills/load.js +171 -0
- package/dist/skills/skills.d.ts +28 -0
- package/dist/skills/skills.js +47 -0
- package/package.json +6 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @nylorun/agents
|
|
2
2
|
|
|
3
|
+
## 0.4.0-beta
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- b8d822a: Add `mcp(...)` to declare MCP servers as one capability for `Agent.use`, matching agent-plugins / manifest v3 `mcpServers` shape.
|
|
8
|
+
- b8d822a: Add `skills(path)` to load an agentskills.io catalog folder into `Agent.use` without duplicating `skills` and `skillRecords`. Parse `SKILL.md` frontmatter with `gray-matter`.
|
|
9
|
+
- b8d822a: Add `sandbox()`: one `.use(sandbox())` gives an agent Runtime-executed `bash`, `read`, `write`, `edit`, `grep` and `glob` tools on an isolated machine with a persistent `/workspace`. The Runtime selects a microsandbox microVM where available, otherwise an in-process virtual shell, enforces deny-by-default egress presets, owns sandbox lifecycle, and reports its choice through `GET /v1/host/sandbox`, the `nylorun dev` banner and `nylorun doctor sandbox`.
|
|
10
|
+
- b8d822a: Breaking beta: replace `beforeModelCall` / `afterModelCall` with scoped hooks. Register `before("turn" | "step", fn)` and `after("step" | "turn", fn)` on the agent, or `before: { turn, step }` / `after: { step, turn }` on a capability. `before("turn")` runs once per turn and its `Patch` applies to every model call in the turn; the new `after("turn")` returns a `TurnDecision` for the final answer. `after` hooks take one argument and receive `attempt`, and `retry` now retries instead of failing the run. The manifest moves to `manifestSchemaVersion: 4` with `capabilities[].hooks`, and `BeforeModelCallFn`, `AfterModelCallFn` and the `beforeModelCall` / `afterModelCall` action kinds are removed. Every capability registered at a hook point now runs in one `hook` executor action, an expired hook claim is offered again instead of becoming uncertain, and the durable engine version is `hosted-2`. Hook toggles now hide a capability's tools, or one tool of a multi-tool capability, instead of having no effect or failing. Studio lists each capability's hooks with how often they run and labels hook actions. See MIGRATION.md.
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Pin core to the tested release.
|
|
15
|
+
- Updated dependencies [b8d822a]
|
|
16
|
+
- Updated dependencies [b8d822a]
|
|
17
|
+
- Updated dependencies [b8d822a]
|
|
18
|
+
- Updated dependencies [b8d822a]
|
|
19
|
+
- @nylorun/core@0.3.0-beta
|
|
20
|
+
|
|
21
|
+
## 0.3.0-beta
|
|
22
|
+
|
|
23
|
+
### Minor Changes
|
|
24
|
+
|
|
25
|
+
- 3a88f51: Ship Agent-Plugins (`plugin()` / `loadPlugin`), Skills (`load_skill` / skill resources), Runtime MCP pool + vault credentials, and manifest v3 capability fields. Validate completed tool `output` against the tool output schema so ordinary tools with `outputSchema` no longer false-fail as `tool.invalid-output`.
|
|
26
|
+
|
|
27
|
+
### Patch Changes
|
|
28
|
+
|
|
29
|
+
- Pin core to the tested release.
|
|
30
|
+
- Updated dependencies [3a88f51]
|
|
31
|
+
- @nylorun/core@0.2.0-beta
|
|
32
|
+
|
|
3
33
|
## 0.2.0-beta
|
|
4
34
|
|
|
5
35
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -49,12 +49,73 @@ await connection.ready;
|
|
|
49
49
|
await connection.close();
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
Load Agent Skills from a local catalog folder with `skills(path)`:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { Agent, skills } from "@nylorun/agents";
|
|
56
|
+
|
|
57
|
+
const assistant = Agent({
|
|
58
|
+
id: "assistant",
|
|
59
|
+
name: "Order assistant",
|
|
60
|
+
instructions: "Use lookup_order for orders.",
|
|
61
|
+
tools: [lookupOrder],
|
|
62
|
+
}).use(skills("./assistant-skills"));
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Each subdirectory under the catalog must contain a `SKILL.md` with YAML frontmatter (`name`, `description`) per [Agent Skills](https://agentskills.io/home). Supporting files (for example `references/`) are available through `read_skill_resource` after `load_skill`. The helper sets both the manifest skill catalog and the on-disk skill records so you do not duplicate content.
|
|
66
|
+
|
|
67
|
+
Declare MCP servers with `mcp(...)` (same map shape as agent-plugins `mcpServers` / manifest v3):
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { Agent, mcp } from "@nylorun/agents";
|
|
71
|
+
|
|
72
|
+
const assistant = Agent({
|
|
73
|
+
id: "assistant",
|
|
74
|
+
name: "Assistant",
|
|
75
|
+
instructions: "Use the available tools.",
|
|
76
|
+
}).use(
|
|
77
|
+
mcp({
|
|
78
|
+
github: {
|
|
79
|
+
name: "github",
|
|
80
|
+
type: "streamable-http",
|
|
81
|
+
url: "https://mcp.example.com/github",
|
|
82
|
+
},
|
|
83
|
+
})
|
|
84
|
+
);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Each key must equal that server's `name`. Transports follow [Agent Plugins MCP servers](https://agent-plugins.org/plugin-authors/mcp-servers): `stdio`, `streamable-http`, and `sse`. Pass `{ id: "…" }` as the second argument to override the default capability id `"mcp"`.
|
|
88
|
+
|
|
89
|
+
Give an agent an isolated computer with `sandbox()`:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { Agent, sandbox } from "@nylorun/agents";
|
|
93
|
+
|
|
94
|
+
const analyst = Agent({
|
|
95
|
+
id: "analyst",
|
|
96
|
+
instructions: "Analyse the data the user gives you. Use Python.",
|
|
97
|
+
}).use(sandbox());
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The model gets `bash`, `read`, `write`, `edit`, `grep` and `glob` on a Linux machine with a persistent `/workspace`. These tools run in the Runtime, not in your process, so sandbox-only agents need no connected executor. The agent declares what it needs; the Runtime decides where it runs (a microsandbox VM where the machine supports it, otherwise an emulated shell). Every option is optional plain data:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
.use(sandbox({
|
|
104
|
+
image: "python:3.13", // any OCI image; default python:3.13-slim
|
|
105
|
+
network: { preset: "dev", allow: ["api.example.com"] }, // "none" | "dev" (default) | "open"
|
|
106
|
+
resources: { cpus: 2, memory: "2GiB" },
|
|
107
|
+
idle: "15m", // stop compute when idle; files persist
|
|
108
|
+
}))
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The `dev` preset allows package registries and code hosts. Private networks, loopback, the host and cloud metadata endpoints are always blocked. Options from the full design that are not in this version (`setup`, `files`, `secrets`, `mount`, `onStart`, `scope`, ...) throw a `SandboxError` that says so. See [the sandbox design](../docs/design/sandboxes.md).
|
|
112
|
+
|
|
52
113
|
Use `session.observe({ cursor, signal })` for resumable canonical events, `session.inspect()` for waiting/uncertain state, and `approve`, `respond`, or `cancel` with an explicit stable idempotency key. Retry the same semantic command with the same key. `ownerUserId` must come from trusted server authentication. Application credentials are not browser credentials; browser applications need an authorized backend. Definition authoring is browser-bundleable.
|
|
53
114
|
|
|
54
|
-
The Runtime destination is explicit, or defaults from `NYLORUN_RUNTIME_URL`. Application and executor keys default from `NYLORUN_SERVER_KEY` and `NYLORUN_EXECUTOR_KEY`. Implementation version defaults from `NYLORUN_IMPLEMENTATION_VERSION`, then `dev`. A host must provision executor scope for the
|
|
115
|
+
The Runtime destination is explicit, or defaults from `NYLORUN_RUNTIME_URL`. Application and executor keys default from `NYLORUN_SERVER_KEY` and `NYLORUN_EXECUTOR_KEY`. Implementation version defaults from `NYLORUN_IMPLEMENTATION_VERSION`, then `dev`. A host must provision executor scope for the agent id. Agents do not hash the manifest, and an in-flight action stays claimable after the registered digest changes. Model selection and model credentials belong to the Runtime.
|
|
55
116
|
|
|
56
|
-
`connectAgents({ agents })` opens authenticated fetch SSE before discovering actions, rediscovers after reconnection, and claims
|
|
117
|
+
`connectAgents({ agents })` opens authenticated fetch SSE before discovering actions, rediscovers after reconnection, and claims pending actions for a connected agent id. It renews leases while executing, then retries HTTP result delivery with the same recorded outcome and idempotency key. Reconnect delay is bounded at 30 seconds. Notifications confer no execution authority. There is no periodic action-discovery polling. Close aborts the stream, HTTP requests and lease timers and signals running functions; JavaScript cannot forcibly terminate a function that ignores its signal. The host makes expired in-flight actions uncertain instead of automatically repeating external effects.
|
|
57
118
|
|
|
58
|
-
Tools receive state, info, identity, resume, signal, approval/response helpers and memoized `step`. Step outcomes survive a persisted wait result; they do not establish exactly-once external effects after an unacknowledged crash. `sleep` and `waitFor` currently return inspectable deferred outcomes; automatic timer/event wakeups remain runtime implementation work. Remote `onModelCall` convenience and progress-event transport are not supplied in this pass. Arbitrary middleware closures are rejected for durable definitions; use before
|
|
119
|
+
Tools receive state, info, identity, resume, signal, approval/response helpers and memoized `step`. Step outcomes survive a persisted wait result; they do not establish exactly-once external effects after an unacknowledged crash. `sleep` and `waitFor` currently return inspectable deferred outcomes; automatic timer/event wakeups remain runtime implementation work. Remote `onModelCall` convenience and progress-event transport are not supplied in this pass. Arbitrary middleware closures are rejected for durable definitions; use `before`/`after` hooks. The executor runs every capability registered at a hook point in one action. Agent definitions have no `.run()`; explicit local execution is available through `@nylorun/harness/run`.
|
|
59
120
|
|
|
60
|
-
The SDK depends only on core within the Nylorun packages; installing it does not install harness. Use `/define`, `/client`, or `/executor` for focused imports, or the root for convenience. Studio uses `/client`. See [the adopted host contract](../harness/HOST_CONTRACT.md)
|
|
121
|
+
The SDK depends only on core within the Nylorun packages; installing it does not install harness. Use `/define`, `/client`, or `/executor` for focused imports, or the root for convenience. Studio uses `/client`. See [the adopted host contract](../harness/HOST_CONTRACT.md).
|
package/dist/client.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { AgentManifest, BuiltAgent, JsonValue } from "@nylorun/core/define";
|
|
2
|
-
import { type SessionCommand } from "@nylorun/core/contracts";
|
|
2
|
+
import { type CredentialInfo, type CredentialSelection, type SessionCommand, type VaultInfo } from "@nylorun/core/contracts";
|
|
3
3
|
import { Transport, type Destination } from "./http.js";
|
|
4
4
|
export interface AgentSource {
|
|
5
5
|
readonly id: string;
|
|
6
6
|
readonly manifest: AgentManifest;
|
|
7
|
-
readonly hash: string;
|
|
8
7
|
build?(): BuiltAgent;
|
|
8
|
+
getBinding?: BuiltAgent["getBinding"];
|
|
9
9
|
}
|
|
10
|
+
/** Closures stay on the live binding. The published manifest cannot name them. */
|
|
11
|
+
export declare function assertNoMiddlewareClosures(agent: AgentSource): void;
|
|
10
12
|
export interface SessionView {
|
|
11
13
|
id: string;
|
|
12
14
|
agentId: string;
|
|
@@ -41,7 +43,71 @@ export declare class AgentsClient {
|
|
|
41
43
|
ownerUserId: string;
|
|
42
44
|
info?: Record<string, unknown>;
|
|
43
45
|
requestId?: string;
|
|
46
|
+
vaultIds?: readonly string[];
|
|
47
|
+
credentialSelections?: readonly CredentialSelection[];
|
|
44
48
|
}): Promise<SessionClient>;
|
|
49
|
+
createVault(options: {
|
|
50
|
+
name: string;
|
|
51
|
+
ownerUserId: string;
|
|
52
|
+
metadata?: Record<string, string>;
|
|
53
|
+
idempotencyKey: string;
|
|
54
|
+
requestId?: string;
|
|
55
|
+
}): Promise<VaultInfo>;
|
|
56
|
+
listVaults(ownerUserId: string, signal?: AbortSignal): Promise<{
|
|
57
|
+
vaults: VaultInfo[];
|
|
58
|
+
}>;
|
|
59
|
+
getVault(vaultId: string, signal?: AbortSignal): Promise<VaultInfo>;
|
|
60
|
+
deleteVault(vaultId: string): Promise<{
|
|
61
|
+
id: string;
|
|
62
|
+
}>;
|
|
63
|
+
createCredential(vaultId: string, options: {
|
|
64
|
+
name: string;
|
|
65
|
+
idempotencyKey: string;
|
|
66
|
+
requestId?: string;
|
|
67
|
+
auth: {
|
|
68
|
+
type: "bearer";
|
|
69
|
+
url: string;
|
|
70
|
+
token: string;
|
|
71
|
+
} | {
|
|
72
|
+
type: "oauth";
|
|
73
|
+
url: string;
|
|
74
|
+
accessToken: string;
|
|
75
|
+
expiresAt?: string | null;
|
|
76
|
+
refresh?: {
|
|
77
|
+
tokenEndpoint: string;
|
|
78
|
+
clientId: string;
|
|
79
|
+
refreshToken: string;
|
|
80
|
+
tokenEndpointAuth: {
|
|
81
|
+
type: "none";
|
|
82
|
+
} | {
|
|
83
|
+
type: "client_secret_basic";
|
|
84
|
+
clientSecret: string;
|
|
85
|
+
} | {
|
|
86
|
+
type: "client_secret_post";
|
|
87
|
+
clientSecret: string;
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
}): Promise<CredentialInfo>;
|
|
92
|
+
listCredentials(vaultId: string, signal?: AbortSignal): Promise<{
|
|
93
|
+
credentials: CredentialInfo[];
|
|
94
|
+
}>;
|
|
95
|
+
getCredential(vaultId: string, credentialId: string, signal?: AbortSignal): Promise<CredentialInfo>;
|
|
96
|
+
rotateCredential(vaultId: string, credentialId: string, options: {
|
|
97
|
+
idempotencyKey: string;
|
|
98
|
+
requestId?: string;
|
|
99
|
+
auth: {
|
|
100
|
+
type: "bearer";
|
|
101
|
+
token: string;
|
|
102
|
+
} | {
|
|
103
|
+
type: "oauth";
|
|
104
|
+
accessToken: string;
|
|
105
|
+
expiresAt?: string | null;
|
|
106
|
+
};
|
|
107
|
+
}): Promise<CredentialInfo>;
|
|
108
|
+
deleteCredential(vaultId: string, credentialId: string): Promise<{
|
|
109
|
+
id: string;
|
|
110
|
+
}>;
|
|
45
111
|
session(sessionId: string): SessionClient;
|
|
46
112
|
}
|
|
47
113
|
export type CommandOptions = {
|
package/dist/client.js
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
import { LiveEventSchema, SessionItemsResponseSchema, } from "@nylorun/core/contracts";
|
|
2
2
|
import { Transport, id, segment } from "./http.js";
|
|
3
3
|
import { observeSSE } from "./sse.js";
|
|
4
|
+
const middlewareClosure = "Hosted definitions support declarative capabilities and before/after hooks, not middleware closures";
|
|
5
|
+
function pluginRootsOf(agent) {
|
|
6
|
+
const built = agent.build?.() ?? agent;
|
|
7
|
+
const declarations = built.getBinding?.().declarations ?? [];
|
|
8
|
+
const roots = {};
|
|
9
|
+
for (const item of declarations) {
|
|
10
|
+
if (item.pluginRoot)
|
|
11
|
+
roots[item.id] = item.pluginRoot;
|
|
12
|
+
}
|
|
13
|
+
return Object.keys(roots).length === 0 ? undefined : roots;
|
|
14
|
+
}
|
|
15
|
+
/** Closures stay on the live binding. The published manifest cannot name them. */
|
|
16
|
+
export function assertNoMiddlewareClosures(agent) {
|
|
17
|
+
const built = agent.build?.() ?? agent;
|
|
18
|
+
if (built.getBinding?.().declarations.some((item) => item.hasMiddleware))
|
|
19
|
+
throw new Error(middlewareClosure);
|
|
20
|
+
}
|
|
4
21
|
export class AgentsClient {
|
|
5
22
|
transport;
|
|
6
23
|
constructor(destination = {}) {
|
|
@@ -16,10 +33,13 @@ export class AgentsClient {
|
|
|
16
33
|
return this.transport.json(`/v1/sessions${query}`, "GET", undefined, options.signal);
|
|
17
34
|
}
|
|
18
35
|
saveAgent(agent, options) {
|
|
36
|
+
assertNoMiddlewareClosures(agent);
|
|
37
|
+
const pluginRoots = pluginRootsOf(agent);
|
|
19
38
|
return this.transport.json(`/v1/agents/${segment(agent.id)}`, "PUT", {
|
|
20
39
|
requestId: options.requestId ?? id(),
|
|
21
40
|
manifest: agent.manifest,
|
|
22
41
|
implementationVersion: options.implementationVersion,
|
|
42
|
+
...(pluginRoots === undefined ? {} : { pluginRoots }),
|
|
23
43
|
});
|
|
24
44
|
}
|
|
25
45
|
async createSession(options) {
|
|
@@ -29,9 +49,55 @@ export class AgentsClient {
|
|
|
29
49
|
agentId: options.agentId,
|
|
30
50
|
ownerUserId: options.ownerUserId,
|
|
31
51
|
...(options.info ? { info: options.info } : {}),
|
|
52
|
+
...(options.vaultIds ? { vaultIds: options.vaultIds } : {}),
|
|
53
|
+
...(options.credentialSelections
|
|
54
|
+
? { credentialSelections: options.credentialSelections }
|
|
55
|
+
: {}),
|
|
32
56
|
});
|
|
33
57
|
return this.session(sessionId);
|
|
34
58
|
}
|
|
59
|
+
createVault(options) {
|
|
60
|
+
return this.transport.json("/v1/vaults", "POST", {
|
|
61
|
+
requestId: options.requestId ?? id(),
|
|
62
|
+
idempotencyKey: options.idempotencyKey,
|
|
63
|
+
name: options.name,
|
|
64
|
+
ownerUserId: options.ownerUserId,
|
|
65
|
+
...(options.metadata ? { metadata: options.metadata } : {}),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
listVaults(ownerUserId, signal) {
|
|
69
|
+
return this.transport.json(`/v1/vaults?ownerUserId=${encodeURIComponent(ownerUserId)}`, "GET", undefined, signal);
|
|
70
|
+
}
|
|
71
|
+
getVault(vaultId, signal) {
|
|
72
|
+
return this.transport.json(`/v1/vaults/${segment(vaultId)}`, "GET", undefined, signal);
|
|
73
|
+
}
|
|
74
|
+
deleteVault(vaultId) {
|
|
75
|
+
return this.transport.json(`/v1/vaults/${segment(vaultId)}`, "DELETE");
|
|
76
|
+
}
|
|
77
|
+
createCredential(vaultId, options) {
|
|
78
|
+
return this.transport.json(`/v1/vaults/${segment(vaultId)}/credentials`, "POST", {
|
|
79
|
+
requestId: options.requestId ?? id(),
|
|
80
|
+
idempotencyKey: options.idempotencyKey,
|
|
81
|
+
name: options.name,
|
|
82
|
+
auth: options.auth,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
listCredentials(vaultId, signal) {
|
|
86
|
+
return this.transport.json(`/v1/vaults/${segment(vaultId)}/credentials`, "GET", undefined, signal);
|
|
87
|
+
}
|
|
88
|
+
getCredential(vaultId, credentialId, signal) {
|
|
89
|
+
return this.transport.json(`/v1/vaults/${segment(vaultId)}/credentials/${segment(credentialId)}`, "GET", undefined, signal);
|
|
90
|
+
}
|
|
91
|
+
rotateCredential(vaultId, credentialId, options) {
|
|
92
|
+
return this.transport.json(`/v1/vaults/${segment(vaultId)}/credentials/${segment(credentialId)}`, "POST", {
|
|
93
|
+
requestId: options.requestId ?? id(),
|
|
94
|
+
idempotencyKey: options.idempotencyKey,
|
|
95
|
+
auth: options.auth,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
deleteCredential(vaultId, credentialId) {
|
|
99
|
+
return this.transport.json(`/v1/vaults/${segment(vaultId)}/credentials/${segment(credentialId)}`, "DELETE");
|
|
100
|
+
}
|
|
35
101
|
session(sessionId) {
|
|
36
102
|
return new SessionClient(this.transport, sessionId);
|
|
37
103
|
}
|
package/dist/execute-action.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { implementationsFor, isToolError, normalizeToolDefinition, normalizedSchemasFor, } from "@nylorun/core/define";
|
|
1
|
+
import { implementationsFor, isToolError, normalizeToolDefinition, normalizedSchemasFor, runHookPoint, } from "@nylorun/core/define";
|
|
2
2
|
class Suspend {
|
|
3
3
|
outcome;
|
|
4
4
|
constructor(outcome) {
|
|
@@ -10,32 +10,17 @@ const object = (value) => value !== null && typeof value === "object" && !Array.
|
|
|
10
10
|
: {};
|
|
11
11
|
/** Executes code only after a host-issued claim. No engine dependency. */
|
|
12
12
|
export async function executeAction(action, agent, signal) {
|
|
13
|
+
if (action.kind === "hook")
|
|
14
|
+
// All capabilities at this hook point run concurrently in one action. Never throws.
|
|
15
|
+
return {
|
|
16
|
+
value: {
|
|
17
|
+
results: await runHookPoint(implementationsFor(agent), action.hook, action.input),
|
|
18
|
+
},
|
|
19
|
+
};
|
|
13
20
|
const impl = implementationsFor(agent)[action.capabilityId];
|
|
14
21
|
if (!impl)
|
|
15
22
|
throw new Error(`No capability ${action.capabilityId}`);
|
|
16
|
-
|
|
17
|
-
if (!impl.beforeModelCall)
|
|
18
|
-
throw new Error("Missing beforeModelCall implementation");
|
|
19
|
-
try {
|
|
20
|
-
return { value: (await impl.beforeModelCall(action.input)) ?? {} };
|
|
21
|
-
}
|
|
22
|
-
catch (error) {
|
|
23
|
-
return { value: { block: message(error) } };
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
if (action.kind === "afterModelCall") {
|
|
27
|
-
if (!impl.afterModelCall)
|
|
28
|
-
throw new Error("Missing afterModelCall implementation");
|
|
29
|
-
try {
|
|
30
|
-
return {
|
|
31
|
-
value: (await impl.afterModelCall(action.input, action.context)) ?? {},
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
catch (error) {
|
|
35
|
-
return { value: { block: message(error) } };
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
const raw = impl.tools?.[action.toolName ?? ""];
|
|
23
|
+
const raw = impl.tools?.[action.toolName];
|
|
39
24
|
if (!raw)
|
|
40
25
|
throw new Error("Missing tool implementation");
|
|
41
26
|
const tool = normalizeToolDefinition(raw);
|
package/dist/executor.d.ts
CHANGED
package/dist/executor.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AgentManifestSchema, ActionSchema, ActionClaimResponseSchema, } from "@nylorun/core/contracts";
|
|
2
|
+
import { assertNoMiddlewareClosures } from "./client.js";
|
|
2
3
|
import { Transport, RuntimeError, delay, env, id, segment, } from "./http.js";
|
|
3
4
|
import { readSSE } from "./sse.js";
|
|
4
5
|
import { executeAction } from "./execute-action.js";
|
|
@@ -10,6 +11,7 @@ export function connectAgents(options) {
|
|
|
10
11
|
const agents = new Map();
|
|
11
12
|
for (const source of options.agents) {
|
|
12
13
|
const built = source.build?.() ?? source;
|
|
14
|
+
assertNoMiddlewareClosures(built);
|
|
13
15
|
AgentManifestSchema.parse(built.manifest);
|
|
14
16
|
if (agents.has(built.id))
|
|
15
17
|
throw new Error(`Duplicate connected agent ${built.id}`);
|
|
@@ -45,13 +47,15 @@ export function connectAgents(options) {
|
|
|
45
47
|
dirty = false;
|
|
46
48
|
const response = await transport.json("/v1/actions", "GET", undefined, signal);
|
|
47
49
|
for (const item of response.actions) {
|
|
48
|
-
|
|
50
|
+
// Skip actions this SDK cannot run (e.g. from an older Runtime) instead of stalling discovery.
|
|
51
|
+
const parsed = ActionSchema.safeParse(item);
|
|
52
|
+
if (!parsed.success) {
|
|
53
|
+
report(parsed.error);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const action = parsed.data;
|
|
49
57
|
const agent = agents.get(action.agentId);
|
|
50
|
-
if (!agent ||
|
|
51
|
-
agent.hash !== action.manifestHash ||
|
|
52
|
-
action.implementationVersion !== version ||
|
|
53
|
-
action.status !== "pending" ||
|
|
54
|
-
active.has(action.actionId))
|
|
58
|
+
if (!agent || action.status !== "pending" || active.has(action.actionId))
|
|
55
59
|
continue;
|
|
56
60
|
const work = new AbortController();
|
|
57
61
|
active.set(action.actionId, work);
|
|
@@ -86,12 +90,9 @@ export function connectAgents(options) {
|
|
|
86
90
|
try {
|
|
87
91
|
const claim = ActionClaimResponseSchema.parse(await transport.json(`/v1/actions/${segment(action.actionId)}/claim`, "POST", {
|
|
88
92
|
requestId: id(),
|
|
89
|
-
manifestHash: agent.hash,
|
|
90
93
|
implementationVersion: version,
|
|
91
94
|
}, work.signal));
|
|
92
|
-
if (claim.action.actionId !== action.actionId
|
|
93
|
-
claim.action.manifestHash !== agent.hash ||
|
|
94
|
-
claim.action.implementationVersion !== version)
|
|
95
|
+
if (claim.action.actionId !== action.actionId)
|
|
95
96
|
throw new Error("Claim definition mismatch");
|
|
96
97
|
let expires = Date.parse(claim.leaseExpiresAt);
|
|
97
98
|
renewal = (async () => {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
export { Agent, AgentBuilder, AgentBuildError, AgentLifecycleError, capability, tool, defineSchema, ToolError, } from "@nylorun/core/define";
|
|
2
|
-
export type { AgentOptions, AgentManifest, BuiltAgent, ToolDefinition, ToolExecutionContext, ToolOutcome, Patch, Decision, JsonValue, JsonObject, } from "@nylorun/core/define";
|
|
3
|
-
export type { LiveEvent, SessionCommand, Action, ActionOutcome, } from "@nylorun/core/contracts";
|
|
2
|
+
export type { AgentOptions, AgentManifest, BuiltAgent, ToolDefinition, ToolExecutionContext, ToolOutcome, Patch, Decision, TurnDecision, HookScope, BeforeHook, AfterHook, JsonValue, JsonObject, } from "@nylorun/core/define";
|
|
3
|
+
export type { LiveEvent, SessionCommand, Action, ActionOutcome, CredentialInfo, CredentialSelection, VaultInfo, } from "@nylorun/core/contracts";
|
|
4
4
|
export { AgentsClient, SessionClient, createClient } from "./client.js";
|
|
5
5
|
export type { AgentSource, SessionView, CommandOptions } from "./client.js";
|
|
6
6
|
export { connectAgents } from "./executor.js";
|
|
7
7
|
export type { ConnectOptions, AgentConnection } from "./executor.js";
|
|
8
8
|
export { RuntimeError } from "./http.js";
|
|
9
9
|
export type { Destination } from "./http.js";
|
|
10
|
+
export { plugin } from "./plugins/plugin.js";
|
|
11
|
+
export type { PluginCapability } from "./plugins/plugin.js";
|
|
12
|
+
export { loadPlugin, PluginError } from "./plugins/load.js";
|
|
13
|
+
export type { LoadedPlugin, PluginDiagnostic } from "./plugins/load.js";
|
|
14
|
+
export { prepareStdioLaunch, expandPluginPlaceholders } from "./plugins/launch.js";
|
|
15
|
+
export type { StdioLaunch } from "./plugins/launch.js";
|
|
16
|
+
export { skills, loadSkillsFromDirectory, resolveSkillsRoot, parseSkill, SkillsError, formatSkillCatalog, SKILLS_USAGE, } from "./skills/index.js";
|
|
17
|
+
export type { SkillsCapability, SkillsOptions, SkillDiagnostic, } from "./skills/index.js";
|
|
18
|
+
export { mcp, McpError } from "./mcp/index.js";
|
|
19
|
+
export type { McpCapability, McpOptions } from "./mcp/index.js";
|
|
20
|
+
export { sandbox, SandboxError } from "./sandbox/index.js";
|
|
21
|
+
export type { SandboxCapability, SandboxCapabilityOptions, SandboxOptions, } from "./sandbox/index.js";
|
package/dist/index.js
CHANGED
|
@@ -3,3 +3,9 @@ export { Agent, AgentBuilder, AgentBuildError, AgentLifecycleError, capability,
|
|
|
3
3
|
export { AgentsClient, SessionClient, createClient } from "./client.js";
|
|
4
4
|
export { connectAgents } from "./executor.js";
|
|
5
5
|
export { RuntimeError } from "./http.js";
|
|
6
|
+
export { plugin } from "./plugins/plugin.js";
|
|
7
|
+
export { loadPlugin, PluginError } from "./plugins/load.js";
|
|
8
|
+
export { prepareStdioLaunch, expandPluginPlaceholders } from "./plugins/launch.js";
|
|
9
|
+
export { skills, loadSkillsFromDirectory, resolveSkillsRoot, parseSkill, SkillsError, formatSkillCatalog, SKILLS_USAGE, } from "./skills/index.js";
|
|
10
|
+
export { mcp, McpError } from "./mcp/index.js";
|
|
11
|
+
export { sandbox, SandboxError } from "./sandbox/index.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { mcp, McpError } from "./mcp.js";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { CapabilityDeclaration, McpServerManifest } from "@nylorun/core/define";
|
|
2
|
+
export interface McpOptions {
|
|
3
|
+
/** Capability id. Defaults to `"mcp"`. */
|
|
4
|
+
readonly id?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface McpCapability extends CapabilityDeclaration {
|
|
7
|
+
readonly mcpServers: Readonly<Record<string, McpServerManifest>>;
|
|
8
|
+
}
|
|
9
|
+
export declare class McpError extends Error {
|
|
10
|
+
readonly code: string;
|
|
11
|
+
constructor(code: string, message: string);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Declare MCP servers as one capability for `Agent.use`.
|
|
15
|
+
*
|
|
16
|
+
* The map shape matches agent-plugins `mcpServers` and manifest v3: each key
|
|
17
|
+
* must equal that server's `name`. Supported transports are `stdio`,
|
|
18
|
+
* `streamable-http`, and `sse` (see https://agent-plugins.org/plugin-authors/mcp-servers).
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* Agent({...}).use(mcp({
|
|
22
|
+
* github: {
|
|
23
|
+
* name: "github",
|
|
24
|
+
* type: "streamable-http",
|
|
25
|
+
* url: "https://mcp.example.com/github",
|
|
26
|
+
* },
|
|
27
|
+
* }))
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function mcp(servers: Readonly<Record<string, McpServerManifest>>, options?: McpOptions): McpCapability;
|
package/dist/mcp/mcp.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export class McpError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
constructor(code, message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "McpError";
|
|
6
|
+
this.code = code;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Declare MCP servers as one capability for `Agent.use`.
|
|
11
|
+
*
|
|
12
|
+
* The map shape matches agent-plugins `mcpServers` and manifest v3: each key
|
|
13
|
+
* must equal that server's `name`. Supported transports are `stdio`,
|
|
14
|
+
* `streamable-http`, and `sse` (see https://agent-plugins.org/plugin-authors/mcp-servers).
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* Agent({...}).use(mcp({
|
|
18
|
+
* github: {
|
|
19
|
+
* name: "github",
|
|
20
|
+
* type: "streamable-http",
|
|
21
|
+
* url: "https://mcp.example.com/github",
|
|
22
|
+
* },
|
|
23
|
+
* }))
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export function mcp(servers, options = {}) {
|
|
27
|
+
const id = options.id ?? "mcp";
|
|
28
|
+
if (!isRecord(servers)) {
|
|
29
|
+
throw new McpError("mcp.invalid", "mcp() expects a non-empty map of MCP servers");
|
|
30
|
+
}
|
|
31
|
+
const entries = Object.entries(servers);
|
|
32
|
+
if (entries.length === 0) {
|
|
33
|
+
throw new McpError("mcp.empty", "mcp() requires at least one MCP server");
|
|
34
|
+
}
|
|
35
|
+
const mcpServers = {};
|
|
36
|
+
for (const [key, server] of entries) {
|
|
37
|
+
if (!isMcpServer(server)) {
|
|
38
|
+
throw new McpError("mcp.invalid-server", `MCP server '${key}' is not a valid stdio, streamable-http, or sse declaration`);
|
|
39
|
+
}
|
|
40
|
+
if (server.name !== key) {
|
|
41
|
+
throw new McpError("mcp.name-mismatch", `MCP server key '${key}' must equal the server name '${server.name}'`);
|
|
42
|
+
}
|
|
43
|
+
mcpServers[key] = freezeServer(server);
|
|
44
|
+
}
|
|
45
|
+
return { id, mcpServers: Object.freeze(mcpServers) };
|
|
46
|
+
}
|
|
47
|
+
function freezeServer(server) {
|
|
48
|
+
if (server.type === "stdio") {
|
|
49
|
+
return Object.freeze({
|
|
50
|
+
name: server.name,
|
|
51
|
+
type: "stdio",
|
|
52
|
+
command: server.command,
|
|
53
|
+
...(server.args === undefined ? {} : { args: Object.freeze([...server.args]) }),
|
|
54
|
+
...(server.env === undefined
|
|
55
|
+
? {}
|
|
56
|
+
: { env: Object.freeze({ ...server.env }) }),
|
|
57
|
+
...(server.cwd === undefined ? {} : { cwd: server.cwd }),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return Object.freeze({
|
|
61
|
+
name: server.name,
|
|
62
|
+
type: server.type,
|
|
63
|
+
url: server.url,
|
|
64
|
+
...(server.headers === undefined
|
|
65
|
+
? {}
|
|
66
|
+
: { headers: Object.freeze({ ...server.headers }) }),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function isMcpServer(value) {
|
|
70
|
+
if (!isRecord(value) || typeof value.name !== "string" || !value.name)
|
|
71
|
+
return false;
|
|
72
|
+
if (value.type === "stdio")
|
|
73
|
+
return typeof value.command === "string" && value.command.length > 0;
|
|
74
|
+
if (value.type === "streamable-http" || value.type === "sse")
|
|
75
|
+
return typeof value.url === "string" && value.url.length > 0;
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
function isRecord(value) {
|
|
79
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
80
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { McpServerManifest } from "@nylorun/core/define";
|
|
2
|
+
/** Single-pass replacement. Text inserted for a token is not scanned again. */
|
|
3
|
+
export declare function expandPluginPlaceholders(value: string, pluginRoot: string, pluginData: string): string;
|
|
4
|
+
export interface StdioLaunch {
|
|
5
|
+
readonly command: string;
|
|
6
|
+
readonly args: readonly string[];
|
|
7
|
+
readonly cwd: string;
|
|
8
|
+
readonly env: Readonly<Record<string, string>>;
|
|
9
|
+
}
|
|
10
|
+
export declare function prepareStdioLaunch(server: Extract<McpServerManifest, {
|
|
11
|
+
type: "stdio";
|
|
12
|
+
}>, options: {
|
|
13
|
+
readonly pluginRoot: string;
|
|
14
|
+
readonly pluginData: string;
|
|
15
|
+
readonly baseEnv?: Readonly<Record<string, string | undefined>>;
|
|
16
|
+
}): StdioLaunch;
|
|
17
|
+
export declare function resolveContainedCwd(cwd: string, pluginRoot: string, pluginData: string): string;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
|
+
const ROOT_TOKEN = "${PLUGIN_ROOT}";
|
|
4
|
+
const DATA_TOKEN = "${PLUGIN_DATA}";
|
|
5
|
+
/** Single-pass replacement. Text inserted for a token is not scanned again. */
|
|
6
|
+
export function expandPluginPlaceholders(value, pluginRoot, pluginData) {
|
|
7
|
+
let out = "";
|
|
8
|
+
for (let index = 0; index < value.length;) {
|
|
9
|
+
if (value.startsWith(ROOT_TOKEN, index)) {
|
|
10
|
+
out += pluginRoot;
|
|
11
|
+
index += ROOT_TOKEN.length;
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (value.startsWith(DATA_TOKEN, index)) {
|
|
15
|
+
out += pluginData;
|
|
16
|
+
index += DATA_TOKEN.length;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
out += value[index];
|
|
20
|
+
index += 1;
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
export function prepareStdioLaunch(server, options) {
|
|
25
|
+
const pluginRoot = options.pluginRoot;
|
|
26
|
+
const pluginData = options.pluginData;
|
|
27
|
+
mkdirSync(pluginData, { recursive: true });
|
|
28
|
+
const command = resolveCommand(server.command, pluginRoot);
|
|
29
|
+
const args = (server.args ?? []).map((arg) => expandPluginPlaceholders(arg, pluginRoot, pluginData));
|
|
30
|
+
const cwd = server.cwd
|
|
31
|
+
? resolveContainedCwd(server.cwd, pluginRoot, pluginData)
|
|
32
|
+
: pluginRoot;
|
|
33
|
+
const env = {};
|
|
34
|
+
for (const [key, value] of Object.entries(options.baseEnv ?? {})) {
|
|
35
|
+
if (typeof value === "string")
|
|
36
|
+
setEnv(env, key, value);
|
|
37
|
+
}
|
|
38
|
+
for (const [key, value] of Object.entries(server.env ?? {}))
|
|
39
|
+
setEnv(env, key, expandPluginPlaceholders(value, pluginRoot, pluginData));
|
|
40
|
+
setEnv(env, "PLUGIN_ROOT", pluginRoot);
|
|
41
|
+
setEnv(env, "PLUGIN_DATA", pluginData);
|
|
42
|
+
return { command, args, cwd, env };
|
|
43
|
+
}
|
|
44
|
+
function resolveCommand(command, pluginRoot) {
|
|
45
|
+
if (command.startsWith("./"))
|
|
46
|
+
return resolve(pluginRoot, command);
|
|
47
|
+
return command;
|
|
48
|
+
}
|
|
49
|
+
export function resolveContainedCwd(cwd, pluginRoot, pluginData) {
|
|
50
|
+
const expanded = expandPluginPlaceholders(cwd, pluginRoot, pluginData);
|
|
51
|
+
const absolute = isAbsolute(expanded) ? expanded : resolve(pluginRoot, expanded);
|
|
52
|
+
const root = cwdTargetsData(cwd) ? pluginData : pluginRoot;
|
|
53
|
+
if (!isInside(root, absolute))
|
|
54
|
+
throw new Error(`Working directory escapes ${root}`);
|
|
55
|
+
return absolute;
|
|
56
|
+
}
|
|
57
|
+
function cwdTargetsData(cwd) {
|
|
58
|
+
return cwd === DATA_TOKEN || cwd.startsWith(`${DATA_TOKEN}/`);
|
|
59
|
+
}
|
|
60
|
+
function isInside(root, target) {
|
|
61
|
+
const rel = relative(root, target);
|
|
62
|
+
return rel === "" || (!rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute(rel));
|
|
63
|
+
}
|
|
64
|
+
function setEnv(env, key, value) {
|
|
65
|
+
if (process.platform === "win32") {
|
|
66
|
+
const existing = Object.keys(env).find((name) => name.toLowerCase() === key.toLowerCase());
|
|
67
|
+
if (existing && existing !== key)
|
|
68
|
+
delete env[existing];
|
|
69
|
+
}
|
|
70
|
+
env[key] = value;
|
|
71
|
+
}
|