@nylorun/agents 0.5.0-beta → 0.7.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 CHANGED
@@ -1,5 +1,61 @@
1
1
  # @nylorun/agents
2
2
 
3
+ ## 0.7.0-beta
4
+
5
+ ### Minor Changes
6
+
7
+ - bf1c2da: **Tenants can register a Studio principal.** `CreateTenantRequest` gains optional `studioCredentialHash` behind the new protocol feature `studio-principal`; the Runtime stores it as application principal `studio`, and idempotent create compares it too. `@nylorun/admin` exports `deriveStudioToken(adminKey, tenantId)` (HMAC-SHA256 over `nylorun/studio/v1`, NUL, Tenant id) and `createTenant` sends the hash of that key, so Studio can reach any Tenant's API with a key derived from the admin key. Clients require the new feature, so upgrade the Runtime with them.
8
+
9
+ ### Patch Changes
10
+
11
+ - ba1b239: **`nylorun` sets up the local stack; `@nylorun/cli` becomes the Runtime client `nylo` (breaking beta).**
12
+
13
+ - New unscoped package **`nylorun`**: `npx nylorun up` sets up the Docker stack on the first run and starts it after that, from any directory; `npx nylorun down` stops it (volumes are kept). `up` and `down` are aliases of `start` and `stop`. It also owns `status`, `logs`, `studio`, `reset` and `doctor`, pins the Runtime and Studio images (`package.json` `nylorun.runtime` / `nylorun.studio`), depends on `@nylorun/core` only, and never creates Tenants.
14
+ - **`@nylorun/cli`** is the Runtime client with the command **`nylo`**: `nylo tenant create [name]` (creates the Tenant; in a project, writes the Project link and seeds the model provider and sandbox from `.env`), `nylo tenant current|list|use|status|reset|delete`, `nylo configure`, `nylo env` (was `nylorun status --env`) and `nylo doctor sandbox`. It no longer provides the `nylorun` command or the stack commands.
15
+ - **Removed:** `nylorun dev` and `nylorun dev --ephemeral`. Link once with `nylo tenant create`, then run the project's own `npm run dev`. Moved commands exit 2 and name their replacement.
16
+ - **Starter:** the generated project depends on `@nylorun/agents` only (no Nylorun devDependency); `dev` is `tsx watch --env-file-if-exists=.env src/main.ts`. `npm create @nylorun/agent` installs the project and prints the next steps (`npx nylorun up`, `npx @nylorun/cli tenant create`, `npm run dev`) instead of starting development; `--no-open` is accepted and ignored.
17
+ - `connectAgents` names those steps when it finds no Runtime connection. Runtime repair hints, model errors and core's sandbox message name `nylo` and `nylorun up`.
18
+
19
+ - bf1c2da: **The microsandbox backend is removed; the Runtime runs sandbox tools on the virtual backend only.** The optional `microsandbox` dependency is gone. `sandbox.backend` and `NYLORUN_SANDBOX` accept `auto` or `virtual`, and `auto` selects `virtual`. A Tenant that stored `microsandbox` reads it as `auto`. `nylorun doctor sandbox` and the `nylorun dev` banner report only the virtual shell.
20
+ - Pin core to the tested release.
21
+ - Updated dependencies [bf1c2da]
22
+ - Updated dependencies [ba1b239]
23
+ - Updated dependencies [bf1c2da]
24
+ - Updated dependencies [bf1c2da]
25
+ - Updated dependencies [bf1c2da]
26
+ - @nylorun/core@0.6.0-beta
27
+
28
+ ## 0.6.0-beta
29
+
30
+ ### Minor Changes
31
+
32
+ - c49efed: **Breaking (pre-1.0 minor):** Tenant API connection and executor credentials for Runtime Clients.
33
+
34
+ - `resolveConnection()` — options → environment → Project link; sources never mix.
35
+ - `createClient()` with no arguments uses `resolveConnection`.
36
+ - `connectAgents` application mode: save agents, `PUT /v1/executors` with **derived** tokens (HMAC of application key + Tenant + agent id), connect; tokens are not stored in Project credentials.
37
+ - Re-exports `PROTOCOL_FEATURES`, `ERROR_CODES`, `compareVersions`.
38
+
39
+ - c49efed: **Breaking (pre-1.0 minor):** Replace a single SQLite Runtime per Project or home directory with a **Runtime Host** that serves isolated **Tenants**, selected by `Nylorun-Tenant` and negotiated with `Nylorun-Protocol` (protocol `2`, feature `runtime-tenants`). Vocabulary: Host root + Tenant + Project link.
40
+
41
+ - **core:** `PROTOCOL_VERSION = 2`, `HOST_PROTOCOL`, `TENANT_HEADER`, `PROTOCOL_HEADER`, `newTenantId` / `isTenantId`, `checkCompatibility`; health schema gains `hostId` + `protocol` (`service: "nylorun-runtime"`); Tenant/admin wire schemas; Tenant model routes under `/v1/tenant/*`.
42
+ - **runtime:** Host process + Tenant module; Tenant model routes under `/v1/tenant/*`; `startEphemeralRuntime` for tests/embeds; executors via `PUT /v1/executors`; sandbox prefix `nylorun-<tenant-id>-`.
43
+ - **agents:** `createClient({ url, key, tenant })`; Transport sends Tenant + protocol headers; `/health` compatibility cache; `IncompatibleRuntimeError` with upgrade remedies.
44
+ - **cli:** Host root lifecycle (`runtime up|down|status|logs|restart|run`); Project link (`.nylorun/link.json` + `credentials.json`); `tenant` commands; `runtime status --env` exports `NYLORUN_RUNTIME_URL`, `NYLORUN_SERVER_KEY`, `NYLORUN_TENANT`; removed Project/home SQLite selectors.
45
+ - **studio:** `startStudio({ …, tenant: { id, name } })`; proxy forwards Tenant + protocol headers; UI shows Tenant name/short id.
46
+
47
+ - fd9fd87: Add workflows: compose agents and `tool()` with `Chain`, `Switch`, `Parallel`, `Map`, and `Loop`. A workflow is a registered runnable (`kind: "workflow"`) with the same session API as an agent — `export const agents`, `saveAgent` (saves referenced agents first), `createSession`, `input` (`content` or `data`), `observe({ follow })`, `pending`, `approve`, `cancel`. Slots (`{ run, id?, input? }`) reshape data between nodes. The flow engine (`runFlowDurable`) returns effects only; `harness/src/loop/` and `runDurable` are unchanged.
48
+
49
+ HostEffect gains flow kinds `agent`, `tool` (node), `fn`, and `verify`, each with `path`, `key`, and `iterations`. The Runtime drives agent nodes through the public session contract (linked sessions, shared sandbox via `PutSession.sandbox`), offers `fn` / `verify` again on lease expiry, and routes executor Actions by `(workflowId, key)` with claim-scoped `ctx.sandbox`. Optional `message.manifest` is a turn-only variant of the session pin (turn manifests). Studio shows the manifest tree, live node status, and session links. Examples under `examples/agents/{chain,switch,parallel,map,loop,ship-feature}/`.
50
+
51
+ ### Patch Changes
52
+
53
+ - Pin core to the tested release.
54
+ - Updated dependencies [c49efed]
55
+ - Updated dependencies [c49efed]
56
+ - Updated dependencies [fd9fd87]
57
+ - @nylorun/core@0.5.0-beta
58
+
3
59
  ## 0.5.0-beta
4
60
 
5
61
  ### Minor Changes
package/README.md CHANGED
@@ -1,6 +1,41 @@
1
1
  # @nylorun/agents
2
2
 
3
- One application install provides definition authoring, a session client, and a connected customer executor. This beta is currently built locally; no packages were published in this pass. Use the workspace or install packed SDK and core artifacts together until publishing is enabled.
3
+ Tenant API client package: definition authoring, a session client, and a
4
+ connected customer executor. Depends only on `@nylorun/core` among Nylorun
5
+ packages. A developer application's production tree should contain only this
6
+ package and `@nylorun/core` from Nylorun. Vocabulary:
7
+ [runtime/src/CONTEXT.md](../runtime/src/CONTEXT.md).
8
+
9
+ ## Application entry (preferred)
10
+
11
+ ```ts
12
+ // src/main.ts
13
+ import { connectAgents } from "@nylorun/agents";
14
+ import { agents } from "../agents/index.js";
15
+
16
+ await connectAgents({ agents }).ready;
17
+ ```
18
+
19
+ Application mode saves definitions, registers **derived** executor credentials
20
+ (HMAC of the application key + Tenant + agent id), and connects. Restarts and
21
+ replicas re-register the same hashes; tokens are never stored in the Project.
22
+ The same entry runs under the project's `npm run dev` (`tsx watch`) and as
23
+ `node dist/src/main.js` (`npm start`). It finds the Runtime through the three
24
+ `NYLORUN_*` variables or the Project link that `npx @nylorun/cli tenant create`
25
+ writes; with neither, it fails with `connection_missing` and names those steps. See [MIGRATION.md](../MIGRATION.md#runtime-clients-and-admin-api-breaking-beta)
26
+ for upgrading from `nylorun serve`.
27
+
28
+ ## Connection resolution
29
+
30
+ `resolveConnection` / `createClient()` / `connectAgents({ agents })`:
31
+
32
+ 1. Explicit `{ url, tenant, key }`
33
+ 2. Environment — if any of `NYLORUN_RUNTIME_URL`, `NYLORUN_TENANT`,
34
+ `NYLORUN_SERVER_KEY` or `NYLORUN_EXECUTOR_KEY` is set, all required pieces
35
+ must be present (role `executor` when `NYLORUN_EXECUTOR_KEY` is set)
36
+ 3. Project link — `.nylorun/link.json` + `credentials.json` (application role)
37
+
38
+ Sources never mix. Partial environment fails with `connection_missing`.
4
39
 
5
40
  ```ts
6
41
  import { Agent, createClient, connectAgents, tool } from "@nylorun/agents";
@@ -21,10 +56,11 @@ const assistant = Agent({
21
56
  ],
22
57
  });
23
58
 
24
- // Run these application operations on your trusted backend.
59
+ // Explicit Tenant API client (options or env / link via createClient()).
25
60
  const client = createClient({
26
61
  url: process.env.NYLORUN_RUNTIME_URL,
27
62
  key: process.env.NYLORUN_SERVER_KEY,
63
+ tenant: process.env.NYLORUN_TENANT,
28
64
  });
29
65
  await client.saveAgent(assistant, { implementationVersion: "app-1" });
30
66
  const session = await client.createSession({
@@ -34,21 +70,33 @@ const session = await client.createSession({
34
70
  await session.input("Look up item 123", { idempotencyKey: requestId });
35
71
  const history = await session.history();
36
72
 
37
- // Customer executor process: credential is separately scoped by the Runtime.
73
+ // Executor-only mode when you already hold an executor key:
38
74
  const connection = connectAgents({
39
75
  agents: [assistant],
40
76
  implementationVersion: "app-1",
41
77
  runtime: {
42
78
  url: process.env.NYLORUN_RUNTIME_URL,
43
79
  key: process.env.NYLORUN_EXECUTOR_KEY,
80
+ tenant: process.env.NYLORUN_TENANT,
44
81
  },
45
82
  onError: console.error,
46
83
  });
47
84
  await connection.ready;
48
- // On shutdown:
49
85
  await connection.close();
50
86
  ```
51
87
 
88
+ ```sh
89
+ eval "$(npx @nylorun/cli env)"
90
+ # → NYLORUN_RUNTIME_URL, NYLORUN_SERVER_KEY, NYLORUN_TENANT
91
+ ```
92
+
93
+ Every request sets `Nylorun-Tenant` and `Nylorun-Protocol`. A `tenant` field in a
94
+ body or query is never read from caller input. Before the first authenticated
95
+ request, `Transport` fetches `/health` once, checks protocol compatibility, and
96
+ throws `IncompatibleRuntimeError` / `incompatible_host` when the Host range or
97
+ required features do not match. Re-exports include `PROTOCOL_FEATURES`,
98
+ `ERROR_CODES` and `compareVersions`.
99
+
52
100
  Load Agent Skills from a local catalog folder with `skills(path)`:
53
101
 
54
102
  ```ts
@@ -80,7 +128,7 @@ const assistant = Agent({
80
128
  type: "streamable-http",
81
129
  url: "https://mcp.example.com/github",
82
130
  },
83
- })
131
+ }),
84
132
  );
85
133
  ```
86
134
 
@@ -97,7 +145,7 @@ const analyst = Agent({
97
145
  }).use(sandbox());
98
146
  ```
99
147
 
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:
148
+ 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 (today an emulated shell in the Runtime process). Every option is optional plain data:
101
149
 
102
150
  ```ts
103
151
  .use(sandbox({
@@ -108,7 +156,7 @@ The model gets `bash`, `read`, `write`, `edit`, `grep` and `glob` on a Linux mac
108
156
  }))
109
157
  ```
110
158
 
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).
159
+ 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`, …) throw a `SandboxError` that says so.
112
160
 
113
161
  Put an agent in another agent's `tools` to let the model delegate to it:
114
162
 
@@ -118,15 +166,21 @@ import { z } from "zod";
118
166
 
119
167
  const researcher = Agent({
120
168
  id: "researcher",
121
- description: "Investigates an order's history. Returns a short summary with the ids it relied on.",
122
- instructions: "Investigate one question about one order. Be exhaustive, then be brief.",
169
+ description:
170
+ "Investigates an order's history. Returns a short summary with the ids it relied on.",
171
+ instructions:
172
+ "Investigate one question about one order. Be exhaustive, then be brief.",
123
173
  tools: [searchOrders, readTicket],
124
- outputSchema: z.object({ summary: z.string(), evidence: z.array(z.string()) }),
174
+ outputSchema: z.object({
175
+ summary: z.string(),
176
+ evidence: z.array(z.string()),
177
+ }),
125
178
  });
126
179
 
127
180
  const support = Agent({
128
181
  id: "support",
129
- instructions: "For anything needing more than two lookups, delegate to researcher with a complete, self-contained task.",
182
+ instructions:
183
+ "For anything needing more than two lookups, delegate to researcher with a complete, self-contained task.",
130
184
  tools: [lookupOrder, refundOrder, researcher],
131
185
  });
132
186
  ```
@@ -135,11 +189,100 @@ The tool is named after the agent's `id` and takes `{ task: string }`; the agent
135
189
 
136
190
  Delegate when the parent should keep the answer. When a specialist should own the rest of the conversation, switch capabilities with a `before("step")` patch instead. This version is one level deep and non-interactive: a delegated agent cannot use agents as tools, its tools cannot declare `approval` (keep those on the parent), and `ctx.ask`, `ctx.approve`, `ctx.sleep` or `ctx.waitFor` inside it fail with `delegation.interaction-unsupported`. Delegation is not an approval boundary; approvals live on tools.
137
191
 
192
+ ## Workflows
193
+
194
+ Nest `Chain`, `Switch`, `Parallel`, `Map`, and `Loop` around agents and `tool()` to
195
+ build durable flows. A workflow is a definition with `kind: "workflow"`: register
196
+ it in `export const agents` (or `saveAgent(workflow)` — referenced agents are
197
+ saved first), then `createSession({ agentId })` and `session.input(value)` like
198
+ any agent. `input` sends string values as `content` and other JSON as `data`.
199
+ `connectAgents({ agents: [workflow] })` saves the tree and connects executors for
200
+ the workflow id and every referenced agent. Tool nodes, pure functions (slot
201
+ `input`, Switch `on`, Map `over`, Loop `decide`), and Loop `verify` run as Actions
202
+ routed by `(workflowId, key)`.
203
+
204
+ ```ts
205
+ import { Agent, Chain, Loop, Map, tool } from "@nylorun/agents";
206
+ import { z } from "zod";
207
+
208
+ const planner = Agent({
209
+ id: "planner",
210
+ instructions: "Return { tasks: string[] }.",
211
+ outputSchema: z.object({ tasks: z.array(z.string()) }),
212
+ }).build();
213
+
214
+ const coder = Agent({
215
+ id: "coder",
216
+ instructions: "Implement one task. Return { summary }.",
217
+ outputSchema: z.object({ summary: z.string() }),
218
+ }).build();
219
+
220
+ const openPr = tool({
221
+ name: "open-pr",
222
+ input: z.object({ summaries: z.array(z.string()) }),
223
+ async run({ summaries }, ctx) {
224
+ if (!(await ctx.approve("Open the PR?"))) throw new Error("Rejected");
225
+ return { opened: true, count: summaries.length };
226
+ },
227
+ });
228
+
229
+ export const shipFeature = Chain({
230
+ id: "ship-feature",
231
+ steps: [
232
+ planner,
233
+ Map({
234
+ id: "implement",
235
+ over: (plan) => plan.tasks,
236
+ each: Loop({
237
+ id: "code",
238
+ run: coder,
239
+ verify: () => ({ pass: true }),
240
+ decide: ({ output }) => ({ output }),
241
+ }),
242
+ }),
243
+ {
244
+ run: openPr,
245
+ input: ({ value }) => ({
246
+ summaries: value.map((item: { summary: string }) => item.summary),
247
+ }),
248
+ },
249
+ ],
250
+ });
251
+
252
+ export const agents = [shipFeature];
253
+ ```
254
+
255
+ | Primitive | Role |
256
+ | --- | --- |
257
+ | **Chain** | Steps in order; slots reshape with `{ run, id?, input? }` |
258
+ | **Switch** | Pure `on(input) → key`, then the matching case (or `default`) |
259
+ | **Parallel** | Fixed named branches at once; output is an object |
260
+ | **Map** | Pure `over(input) → items`, one child per item; output is an array |
261
+ | **Loop** | `run` → `verify` → `decide` until `{ output }` |
262
+
263
+ Agents inside a workflow keep their own sessions (linked from the workflow
264
+ session) and share one sandbox: declare identical specs on every agent that
265
+ uses one, attach with `createSession({ …, sandbox: { session } })`, call
266
+ built-ins via `session.sandbox` (application) or `ctx.sandbox` (executor).
267
+ Observe with `session.observe({ follow: true })` to merge linked agent streams;
268
+ `pending()` lists waits across the tree. Studio renders the manifest tree and
269
+ live node status. Source examples:
270
+ `examples/agents/{chain,switch,parallel,map,loop,ship-feature}/` (outside the
271
+ default release registry). Host contract:
272
+ [HOST_CONTRACT.md](../harness/HOST_CONTRACT.md). Vocabulary (turn loop vs
273
+ workflow Loop): [harness/src/CONTEXT.md](../harness/src/CONTEXT.md).
274
+
138
275
  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.
139
276
 
140
- 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.
277
+ Connection defaults follow `resolveConnection` (options → environment → Project
278
+ link). Implementation version defaults from `NYLORUN_IMPLEMENTATION_VERSION`,
279
+ then `dev`. Application mode of `connectAgents({ agents })` registers derived
280
+ executor principals; executor-only mode still takes an explicit executor key.
281
+ Agents do not hash the manifest, and an in-flight action stays claimable after
282
+ the registered digest changes. Model selection and model credentials belong to
283
+ the Tenant.
141
284
 
142
- `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.
285
+ After registration, `connectAgents` 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.
143
286
 
144
287
  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`.
145
288
 
package/dist/client.d.ts CHANGED
@@ -1,11 +1,15 @@
1
- import type { AgentManifest, BuiltAgent, JsonValue } from "@nylorun/core/define";
1
+ import type { AgentManifest, BuiltAgent, BuiltWorkflow, JsonValue, SandboxToolName, WorkflowManifest } from "@nylorun/core/define";
2
2
  import { type CredentialInfo, type CredentialSelection, type SessionCommand, type VaultInfo } from "@nylorun/core/contracts";
3
3
  import { Transport, type Destination } from "./http.js";
4
+ /** Application-principal access to a session's sandbox built-ins (workflows.md §8). */
5
+ export type SessionSandbox = {
6
+ readonly [K in SandboxToolName]: (args: Record<string, unknown>) => Promise<unknown>;
7
+ };
4
8
  export interface AgentSource {
5
9
  readonly id: string;
6
- readonly manifest: AgentManifest;
7
- build?(): BuiltAgent;
8
- getBinding?: BuiltAgent["getBinding"];
10
+ readonly manifest: AgentManifest | WorkflowManifest;
11
+ build?(): BuiltAgent | BuiltWorkflow;
12
+ getBinding?: BuiltAgent["getBinding"] | BuiltWorkflow["getBinding"];
9
13
  }
10
14
  /** Closures stay on the live binding. The published manifest cannot name them. */
11
15
  export declare function assertNoMiddlewareClosures(agent: AgentSource): void;
@@ -15,6 +19,7 @@ export interface SessionView {
15
19
  ownerUserId: string;
16
20
  status: string;
17
21
  activeTurnId: string | null;
22
+ waits?: unknown;
18
23
  [key: string]: unknown;
19
24
  }
20
25
  export declare class AgentsClient {
@@ -33,7 +38,7 @@ export declare class AgentsClient {
33
38
  }): Promise<{
34
39
  sessions: SessionView[];
35
40
  }>;
36
- saveAgent(agent: AgentSource, options: {
41
+ saveAgent(agent: AgentSource | BuiltWorkflow, options: {
37
42
  implementationVersion: string;
38
43
  requestId?: string;
39
44
  }): Promise<unknown>;
@@ -45,6 +50,10 @@ export declare class AgentsClient {
45
50
  requestId?: string;
46
51
  vaultIds?: readonly string[];
47
52
  credentialSelections?: readonly CredentialSelection[];
53
+ /** Share another session's sandbox (public PutSession.sandbox). */
54
+ sandbox?: {
55
+ session: string;
56
+ };
48
57
  }): Promise<SessionClient>;
49
58
  createVault(options: {
50
59
  name: string;
@@ -118,16 +127,23 @@ export type CommandOptions = {
118
127
  export declare class SessionClient {
119
128
  private readonly transport;
120
129
  readonly id: string;
130
+ readonly sandbox: SessionSandbox;
121
131
  constructor(transport: Transport, id: string);
122
132
  private get path();
123
133
  inspect(signal?: AbortSignal): Promise<SessionView>;
134
+ /** Pending waits / interactions from the session inspect view. */
135
+ pending(signal?: AbortSignal): Promise<{}>;
124
136
  command(command: SessionCommand, signal?: AbortSignal): Promise<{
125
137
  status: "accepted";
126
138
  turnId: string | null;
127
139
  cursor: string | null;
128
140
  requestId: string;
129
141
  }>;
130
- input(content: string, options: CommandOptions): Promise<{
142
+ /**
143
+ * Start a turn. Strings become `message.content`; any other JSON value becomes
144
+ * `message.data` (workflows.md §5).
145
+ */
146
+ input(value: string | JsonValue, options: CommandOptions): Promise<{
131
147
  status: "accepted";
132
148
  turnId: string | null;
133
149
  cursor: string | null;
@@ -162,6 +178,7 @@ export declare class SessionClient {
162
178
  items: {
163
179
  eventId: string;
164
180
  sessionId: string;
181
+ tenantId: string;
165
182
  turnId: string | null;
166
183
  cursor: string;
167
184
  createdAt: string;
@@ -173,17 +190,32 @@ export declare class SessionClient {
173
190
  observe(options?: {
174
191
  cursor?: string;
175
192
  signal?: AbortSignal;
193
+ follow?: boolean;
176
194
  }): AsyncGenerator<{
177
195
  eventId: string;
178
196
  sessionId: string;
197
+ tenantId: string;
179
198
  turnId: string | null;
180
199
  cursor: string;
181
200
  createdAt: string;
182
201
  type: string;
183
202
  payload: unknown;
184
203
  }, void, unknown>;
204
+ private observeOne;
205
+ /**
206
+ * Own session stream plus linked agent sessions announced in `node.agent`
207
+ * events, merged by `createdAt` then arrival order (workflows.md §11).
208
+ */
209
+ private observeFollowing;
185
210
  }
186
- export declare function createClient(destination?: Destination): AgentsClient;
187
- export { RuntimeError } from "./http.js";
188
- export type { Destination } from "./http.js";
211
+ /**
212
+ * Build a Tenant API client.
213
+ * Explicit connection fields keep today's sync Transport resolution (including
214
+ * environment fill-ins). With no connection fields, resolves via
215
+ * `resolveConnection` (environment → project link).
216
+ */
217
+ export declare function createClient(destination: Destination): AgentsClient;
218
+ export declare function createClient(destination?: undefined): Promise<AgentsClient>;
219
+ export { RuntimeError, IncompatibleRuntimeError } from "./http.js";
220
+ export type { Destination, IncompatibleReason } from "./http.js";
189
221
  export type { LiveEvent } from "@nylorun/core/contracts";
package/dist/client.js CHANGED
@@ -1,14 +1,42 @@
1
- import { delegateOf } from "@nylorun/core/define";
1
+ import { SANDBOX_TOOL_NAMES, delegateOf, isBuiltWorkflow, isSandboxToolName, } from "@nylorun/core/define";
2
2
  import { LiveEventSchema, SessionItemsResponseSchema, } from "@nylorun/core/contracts";
3
+ import { resolveConnection } from "./connection.js";
3
4
  import { Transport, id, segment } from "./http.js";
4
5
  import { observeSSE } from "./sse.js";
6
+ function sessionSandboxOf(transport, sessionId) {
7
+ const call = (tool, args) => transport.json(`/v1/sessions/${segment(sessionId)}/sandbox/${segment(tool)}`, "POST", args);
8
+ const sandbox = {};
9
+ for (const tool of SANDBOX_TOOL_NAMES) {
10
+ if (!isSandboxToolName(tool))
11
+ continue;
12
+ sandbox[tool] = (args) => call(tool, args);
13
+ }
14
+ return sandbox;
15
+ }
16
+ function linkedSessionId(event) {
17
+ if (event.type !== "node.agent")
18
+ return undefined;
19
+ const payload = event.payload;
20
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
21
+ return undefined;
22
+ const sessionId = payload.sessionId;
23
+ return typeof sessionId === "string" && sessionId.length > 0
24
+ ? sessionId
25
+ : undefined;
26
+ }
5
27
  const middlewareClosure = "Hosted definitions support declarative capabilities and before/after hooks, not middleware closures";
6
28
  /** The agent's declarations, then those of each agent it uses as a tool, keyed `<child>/<capability>`. */
7
29
  function declarationsOf(agent) {
30
+ if (isBuiltWorkflow(agent) || agent.manifest.kind === "workflow")
31
+ return [];
8
32
  const built = agent.build?.() ?? agent;
33
+ if (isBuiltWorkflow(built))
34
+ return [];
9
35
  const binding = built.getBinding?.();
10
- const found = (binding?.declarations ?? []).map((item) => ({ key: item.id, item }));
11
- for (const tool of binding?.tools ?? []) {
36
+ if (!binding || !("declarations" in binding))
37
+ return [];
38
+ const found = (binding.declarations ?? []).map((item) => ({ key: item.id, item }));
39
+ for (const tool of binding.tools ?? []) {
12
40
  const child = delegateOf(tool)?.agent;
13
41
  for (const item of child?.getBinding().declarations ?? [])
14
42
  found.push({ key: `${child.id}/${item.id}`, item });
@@ -41,7 +69,21 @@ export class AgentsClient {
41
69
  : "";
42
70
  return this.transport.json(`/v1/sessions${query}`, "GET", undefined, options.signal);
43
71
  }
44
- saveAgent(agent, options) {
72
+ async saveAgent(agent, options) {
73
+ if (isBuiltWorkflow(agent)) {
74
+ for (const binding of Object.values(agent.getBinding().agents)) {
75
+ await this.saveAgent({
76
+ id: binding.manifest.id,
77
+ manifest: binding.manifest,
78
+ getBinding: () => binding,
79
+ }, options);
80
+ }
81
+ return this.transport.json(`/v1/agents/${segment(agent.id)}`, "PUT", {
82
+ requestId: options.requestId ?? id(),
83
+ manifest: agent.manifest,
84
+ implementationVersion: options.implementationVersion,
85
+ });
86
+ }
45
87
  assertNoMiddlewareClosures(agent);
46
88
  const pluginRoots = pluginRootsOf(agent);
47
89
  return this.transport.json(`/v1/agents/${segment(agent.id)}`, "PUT", {
@@ -62,6 +104,7 @@ export class AgentsClient {
62
104
  ...(options.credentialSelections
63
105
  ? { credentialSelections: options.credentialSelections }
64
106
  : {}),
107
+ ...(options.sandbox ? { sandbox: options.sandbox } : {}),
65
108
  });
66
109
  return this.session(sessionId);
67
110
  }
@@ -114,9 +157,11 @@ export class AgentsClient {
114
157
  export class SessionClient {
115
158
  transport;
116
159
  id;
160
+ sandbox;
117
161
  constructor(transport, id) {
118
162
  this.transport = transport;
119
163
  this.id = id;
164
+ this.sandbox = sessionSandboxOf(transport, id);
120
165
  }
121
166
  get path() {
122
167
  return `/v1/sessions/${segment(this.id)}`;
@@ -124,16 +169,27 @@ export class SessionClient {
124
169
  inspect(signal) {
125
170
  return this.transport.json(this.path, "GET", undefined, signal);
126
171
  }
172
+ /** Pending waits / interactions from the session inspect view. */
173
+ async pending(signal) {
174
+ const view = await this.inspect(signal);
175
+ return view.waits ?? [];
176
+ }
127
177
  command(command, signal) {
128
178
  return this.transport.json(`${this.path}/commands`, "POST", command, signal);
129
179
  }
130
- input(content, options) {
131
- return this.command({
180
+ /**
181
+ * Start a turn. Strings become `message.content`; any other JSON value becomes
182
+ * `message.data` (workflows.md §5).
183
+ */
184
+ input(value, options) {
185
+ const base = {
132
186
  type: "message",
133
- content,
134
187
  requestId: options.requestId ?? id(),
135
188
  idempotencyKey: options.idempotencyKey,
136
- }, options.signal);
189
+ };
190
+ return this.command(typeof value === "string"
191
+ ? { ...base, content: value }
192
+ : { ...base, data: value }, options.signal);
137
193
  }
138
194
  approve(interactionId, approved, options) {
139
195
  return this.command({
@@ -172,14 +228,92 @@ export class SessionClient {
172
228
  return SessionItemsResponseSchema.parse(await this.transport.json(`${this.path}/items${search ? `?${search}` : ""}`, "GET", undefined, options.signal));
173
229
  }
174
230
  async *observe(options = {}) {
175
- for await (const frame of observeSSE(this.transport, `${this.path}/events`, options)) {
231
+ if (!options.follow) {
232
+ yield* this.observeOne(this.id, options);
233
+ return;
234
+ }
235
+ yield* this.observeFollowing(options);
236
+ }
237
+ async *observeOne(sessionId, options) {
238
+ for await (const frame of observeSSE(this.transport, `/v1/sessions/${segment(sessionId)}/events`, options)) {
176
239
  if (frame.event === "heartbeat" || frame.event === "ready")
177
240
  continue;
178
241
  yield LiveEventSchema.parse(JSON.parse(frame.data));
179
242
  }
180
243
  }
244
+ /**
245
+ * Own session stream plus linked agent sessions announced in `node.agent`
246
+ * events, merged by `createdAt` then arrival order (workflows.md §11).
247
+ */
248
+ async *observeFollowing(options) {
249
+ const signal = options.signal;
250
+ const linked = new Set();
251
+ const queue = [];
252
+ let wait;
253
+ let open = 1;
254
+ let failed;
255
+ const wake = () => {
256
+ wait?.();
257
+ wait = undefined;
258
+ };
259
+ const push = (event) => {
260
+ queue.push(event);
261
+ queue.sort((a, b) => {
262
+ const byTime = a.createdAt.localeCompare(b.createdAt);
263
+ return byTime !== 0 ? byTime : a.cursor.localeCompare(b.cursor);
264
+ });
265
+ wake();
266
+ };
267
+ const pump = async (sessionId, cursor) => {
268
+ try {
269
+ for await (const event of this.observeOne(sessionId, { cursor, signal })) {
270
+ push(event);
271
+ const link = linkedSessionId(event);
272
+ if (link && link !== this.id && !linked.has(link)) {
273
+ linked.add(link);
274
+ open += 1;
275
+ void pump(link).finally(() => {
276
+ open -= 1;
277
+ wake();
278
+ });
279
+ }
280
+ }
281
+ }
282
+ catch (error) {
283
+ if (!signal?.aborted)
284
+ failed = error;
285
+ }
286
+ };
287
+ void pump(this.id, options.cursor).finally(() => {
288
+ open -= 1;
289
+ wake();
290
+ });
291
+ while (!signal?.aborted) {
292
+ if (failed)
293
+ throw failed;
294
+ if (queue.length) {
295
+ yield queue.shift();
296
+ continue;
297
+ }
298
+ if (open <= 0)
299
+ return;
300
+ await new Promise((resolve) => {
301
+ wait = resolve;
302
+ });
303
+ }
304
+ }
181
305
  }
182
306
  export function createClient(destination = {}) {
183
- return new AgentsClient(destination);
307
+ if (destination.url !== undefined ||
308
+ destination.key !== undefined ||
309
+ destination.tenant !== undefined) {
310
+ return new AgentsClient(destination);
311
+ }
312
+ return resolveConnection().then((connection) => new AgentsClient({
313
+ url: connection.url,
314
+ key: connection.key,
315
+ tenant: connection.tenant,
316
+ ...(destination.fetch ? { fetch: destination.fetch } : {}),
317
+ }));
184
318
  }
185
- export { RuntimeError } from "./http.js";
319
+ export { RuntimeError, IncompatibleRuntimeError } from "./http.js";
@@ -0,0 +1,19 @@
1
+ import type { ErrorCode } from "@nylorun/core/compatibility";
2
+ export interface ResolvedConnection {
3
+ url: string;
4
+ tenant: string;
5
+ key: string;
6
+ role: "application" | "executor";
7
+ source: "options" | "environment" | "project-link";
8
+ }
9
+ export declare class ConnectionError extends Error {
10
+ readonly code: ErrorCode;
11
+ constructor(message: string);
12
+ }
13
+ /** Resolve Tenant API connection: options → environment → project link (D§7.1). */
14
+ export declare function resolveConnection(options?: {
15
+ url?: string;
16
+ tenant?: string;
17
+ key?: string;
18
+ cwd?: string;
19
+ }): Promise<ResolvedConnection>;