@nylorun/runtime 0.12.0-beta → 0.14.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 +361 -0
- package/README.md +69 -15
- package/dist/adapters/execution/restate.d.ts +7 -2
- package/dist/adapters/execution/restate.js +38 -8
- package/dist/adapters/replication/pgoutput.d.ts +16 -0
- package/dist/adapters/replication/pgoutput.js +169 -0
- package/dist/adapters/streams/s2.js +37 -20
- package/dist/admin-openapi.json +1071 -0
- package/dist/api/a2a/card.d.ts +15 -0
- package/dist/api/a2a/card.js +24 -0
- package/dist/api/a2a/endpoint.d.ts +8 -0
- package/dist/api/a2a/endpoint.js +81 -0
- package/dist/api/a2a/protocol.d.ts +144 -0
- package/dist/api/a2a/protocol.js +188 -0
- package/dist/api/a2a/routes.d.ts +19 -0
- package/dist/api/a2a/routes.js +289 -0
- package/dist/api/a2a/tasks.d.ts +50 -0
- package/dist/api/a2a/tasks.js +186 -0
- package/dist/api/ag-ui/endpoint.d.ts +8 -0
- package/dist/api/ag-ui/endpoint.js +126 -0
- package/dist/api/ag-ui/history.d.ts +7 -0
- package/dist/api/ag-ui/history.js +60 -0
- package/dist/api/ag-ui/routes.d.ts +32 -0
- package/dist/api/ag-ui/routes.js +305 -0
- package/dist/api/ag-ui/session-id.d.ts +1 -0
- package/dist/api/ag-ui/session-id.js +12 -0
- package/dist/api/ag-ui/sse.d.ts +9 -0
- package/dist/api/ag-ui/sse.js +7 -0
- package/dist/api/ag-ui/translate.d.ts +38 -0
- package/dist/api/ag-ui/translate.js +222 -0
- package/dist/api/components.d.ts +7825 -0
- package/dist/api/components.js +83 -0
- package/dist/api/http/app.d.ts +18 -0
- package/dist/api/http/app.js +58 -0
- package/dist/api/http/body.d.ts +4 -0
- package/dist/api/http/body.js +37 -0
- package/dist/api/http/define.d.ts +39 -0
- package/dist/api/http/define.js +130 -0
- package/dist/api/http/respond.d.ts +9 -0
- package/dist/api/http/respond.js +44 -0
- package/dist/api/http/routes/access.d.ts +24 -0
- package/dist/api/http/routes/access.js +259 -0
- package/dist/api/http/routes/actions.d.ts +8 -0
- package/dist/api/http/routes/actions.js +76 -0
- package/dist/api/http/routes/endpoints.d.ts +7 -0
- package/dist/api/http/routes/endpoints.js +100 -0
- package/dist/api/http/routes/sessions.d.ts +3 -0
- package/dist/api/http/routes/sessions.js +205 -0
- package/dist/api/http/routes/tenant.d.ts +10 -0
- package/dist/api/http/routes/tenant.js +172 -0
- package/dist/api/http/routes/vaults.d.ts +8 -0
- package/dist/api/http/routes/vaults.js +167 -0
- package/dist/api/openapi.d.ts +19 -0
- package/dist/api/openapi.js +138 -0
- package/dist/api/route.d.ts +12 -0
- package/dist/api/route.js +4 -0
- package/dist/core/bearer.d.ts +8 -0
- package/dist/core/bearer.js +11 -0
- package/dist/core/flow-host.d.ts +7 -15
- package/dist/core/flow-host.js +19 -41
- package/dist/core/sandbox-routes.d.ts +12 -7
- package/dist/core/sandbox-routes.js +24 -25
- package/dist/execution/memory.d.ts +8 -5
- package/dist/execution/memory.js +33 -13
- package/dist/execution/types.d.ts +38 -6
- package/dist/execution/types.js +12 -3
- package/dist/host/admin-api.d.ts +22 -0
- package/dist/host/admin-api.js +178 -0
- package/dist/host/app.d.ts +34 -0
- package/dist/host/app.js +185 -0
- package/dist/host/config-for.d.ts +2 -0
- package/dist/host/config-for.js +1 -0
- package/dist/host/config.d.ts +11 -0
- package/dist/host/cors.d.ts +27 -0
- package/dist/host/cors.js +64 -0
- package/dist/host/create-host.d.ts +31 -3
- package/dist/host/create-host.js +168 -327
- package/dist/host/http.d.ts +14 -3
- package/dist/host/http.js +64 -23
- package/dist/host/main.js +55 -4
- package/dist/host/stack-config.d.ts +20 -0
- package/dist/host/stack-config.js +55 -0
- package/dist/mcp/pool.d.ts +19 -1
- package/dist/mcp/pool.js +60 -10
- package/dist/model/catalog.d.ts +2 -10
- package/dist/model/classify.d.ts +13 -0
- package/dist/model/classify.js +61 -0
- package/dist/model/models.d.ts +10 -1
- package/dist/model/models.js +14 -6
- package/dist/model/pi-model.d.ts +17 -2
- package/dist/model/pi-model.js +366 -222
- package/dist/openapi.json +25696 -0
- package/dist/sandbox/manager.d.ts +13 -1
- package/dist/sandbox/manager.js +8 -11
- package/dist/sandbox/resolve.d.ts +33 -0
- package/dist/sandbox/resolve.js +81 -0
- package/dist/sandbox/session-sandbox.d.ts +11 -0
- package/dist/sandbox/session-sandbox.js +51 -0
- package/dist/sandbox/share.d.ts +14 -2
- package/dist/sandbox/share.js +33 -3
- package/dist/sandbox/tenant-config.d.ts +37 -0
- package/dist/sandbox/tenant-config.js +51 -0
- package/dist/sandbox/tools.d.ts +7 -9
- package/dist/sandbox/tools.js +11 -3
- package/dist/sandbox/types.d.ts +8 -2
- package/dist/store/event.d.ts +19 -0
- package/dist/store/event.js +49 -0
- package/dist/store/memory.d.ts +20 -8
- package/dist/store/memory.js +265 -100
- package/dist/store/postgres/connect.d.ts +13 -0
- package/dist/store/postgres/connect.js +5 -0
- package/dist/store/postgres/migrations/003_subject_tokens.d.ts +8 -0
- package/dist/store/postgres/migrations/003_subject_tokens.js +42 -0
- package/dist/store/postgres/migrations/004_publishable_keys.d.ts +6 -0
- package/dist/store/postgres/migrations/004_publishable_keys.js +18 -0
- package/dist/store/postgres/migrations/005_action_endpoints.d.ts +6 -0
- package/dist/store/postgres/migrations/005_action_endpoints.js +32 -0
- package/dist/store/postgres/migrations/006_drop_executors.d.ts +11 -0
- package/dist/store/postgres/migrations/006_drop_executors.js +32 -0
- package/dist/store/postgres/migrations/007_event_time.d.ts +7 -0
- package/dist/store/postgres/migrations/007_event_time.js +14 -0
- package/dist/store/postgres/migrations/008_durable_streams.d.ts +13 -0
- package/dist/store/postgres/migrations/008_durable_streams.js +35 -0
- package/dist/store/postgres/migrations/index.d.ts +5 -1
- package/dist/store/postgres/migrations/index.js +22 -2
- package/dist/store/postgres/migrations/shared/index.d.ts +31 -0
- package/dist/store/postgres/migrations/shared/index.js +92 -0
- package/dist/store/postgres/record.d.ts +11 -0
- package/dist/store/postgres/record.js +64 -0
- package/dist/store/postgres/store.js +337 -95
- package/dist/store/postgres/tenants.js +14 -0
- package/dist/store/types.d.ts +176 -81
- package/dist/streams/basin.d.ts +18 -1
- package/dist/streams/basin.js +34 -2
- package/dist/streams/control.d.ts +18 -0
- package/dist/streams/control.js +29 -0
- package/dist/streams/memory.d.ts +3 -1
- package/dist/streams/memory.js +14 -8
- package/dist/streams/relay/core.d.ts +35 -0
- package/dist/streams/relay/core.js +366 -0
- package/dist/streams/relay/memory.d.ts +69 -0
- package/dist/streams/relay/memory.js +176 -0
- package/dist/streams/relay/types.d.ts +77 -0
- package/dist/streams/relay/types.js +7 -0
- package/dist/streams/types.d.ts +66 -36
- package/dist/streams/types.js +18 -53
- package/dist/tenant/access-policy.d.ts +30 -0
- package/dist/tenant/access-policy.js +52 -0
- package/dist/tenant/actions.d.ts +10 -149
- package/dist/tenant/actions.js +5 -167
- package/dist/tenant/advance.js +32 -0
- package/dist/tenant/auth.d.ts +19 -17
- package/dist/tenant/auth.js +90 -93
- package/dist/tenant/browser.d.ts +15 -0
- package/dist/tenant/browser.js +39 -0
- package/dist/tenant/commands.d.ts +18 -2
- package/dist/tenant/commands.js +121 -129
- package/dist/tenant/context.d.ts +75 -18
- package/dist/tenant/context.js +14 -10
- package/dist/tenant/delivery-token.d.ts +41 -0
- package/dist/tenant/delivery-token.js +105 -0
- package/dist/tenant/delivery.d.ts +62 -0
- package/dist/tenant/delivery.js +476 -0
- package/dist/tenant/effects.d.ts +5 -0
- package/dist/tenant/effects.js +138 -48
- package/dist/tenant/endpoints.d.ts +67 -0
- package/dist/tenant/endpoints.js +90 -0
- package/dist/tenant/ephemeral.d.ts +6 -0
- package/dist/tenant/ephemeral.js +60 -36
- package/dist/tenant/http.d.ts +8 -9
- package/dist/tenant/http.js +13 -27
- package/dist/tenant/jwt.d.ts +16 -0
- package/dist/tenant/jwt.js +50 -0
- package/dist/tenant/module.d.ts +3 -0
- package/dist/tenant/module.js +6 -12
- package/dist/tenant/outbound.d.ts +44 -0
- package/dist/tenant/outbound.js +127 -0
- package/dist/tenant/pool.d.ts +0 -2
- package/dist/tenant/pool.js +0 -18
- package/dist/tenant/reset.d.ts +0 -4
- package/dist/tenant/reset.js +0 -5
- package/dist/tenant/runtime.d.ts +10 -20
- package/dist/tenant/runtime.js +59 -42
- package/dist/tenant/scheduler.d.ts +7 -2
- package/dist/tenant/scheduler.js +7 -3
- package/dist/tenant/session-streams.d.ts +125 -0
- package/dist/tenant/session-streams.js +399 -0
- package/dist/tenant/sessions.d.ts +28 -4
- package/dist/tenant/sessions.js +118 -24
- package/dist/tenant/signing-keys.d.ts +55 -0
- package/dist/tenant/signing-keys.js +172 -0
- package/dist/tenant/slim.d.ts +8 -0
- package/dist/tenant/slim.js +21 -0
- package/dist/tenant/status.d.ts +0 -3
- package/dist/tenant/status.js +19 -12
- package/dist/tenant/streams.d.ts +60 -67
- package/dist/tenant/streams.js +140 -208
- package/dist/tenant/subject-limits.d.ts +16 -0
- package/dist/tenant/subject-limits.js +45 -0
- package/dist/tenant/sweep.d.ts +1 -10
- package/dist/tenant/sweep.js +29 -69
- package/dist/tenant/tokens.d.ts +14 -0
- package/dist/tenant/tokens.js +178 -0
- package/dist/tenant/transcript.d.ts +48 -5
- package/dist/tenant/transcript.js +58 -1
- package/dist/tenant/types.d.ts +29 -7
- package/dist/tenant/worker.d.ts +3 -1
- package/dist/tenant/worker.js +9 -3
- package/dist/vault/service.d.ts +18 -2
- package/dist/vault/service.js +57 -13
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +20 -8
- package/dist/core/executors.d.ts +0 -44
- package/dist/core/executors.js +0 -95
- package/dist/streams/relay.d.ts +0 -37
- package/dist/streams/relay.js +0 -254
- package/dist/tenant/live.d.ts +0 -92
- package/dist/tenant/live.js +0 -290
- package/dist/tenant/routes-tenant.d.ts +0 -12
- package/dist/tenant/routes-tenant.js +0 -137
- package/dist/tenant/routes.d.ts +0 -9
- package/dist/tenant/routes.js +0 -151
package/dist/tenant/effects.js
CHANGED
|
@@ -1,35 +1,51 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { embeddedAgent, flowDelegateManifest, hashManifest, isWorkflowManifestV2, } from "@nylorun/core/define";
|
|
2
|
+
import { countActiveFlowWork, deriveAgentEffectSessionId, isFlowEffect, isFlowToolEffect, isWorkflowManifest, linkedMessageKey, linkedTurnEnd, } from "../core/flow-host.js";
|
|
2
3
|
import { mayDispatchMore } from "../core/limits.js";
|
|
3
4
|
import { canonical } from "../store/canonical.js";
|
|
4
5
|
import { isOwnershipLost } from "../store/ownership.js";
|
|
5
|
-
import { newStreamIncarnation } from "../streams/types.js";
|
|
6
6
|
import { piModel } from "../model/pi-model.js";
|
|
7
7
|
import { scrub } from "../redact.js";
|
|
8
8
|
import { serversOf } from "../mcp/pool.js";
|
|
9
9
|
import { sandboxCapabilityOf } from "../sandbox/manager.js";
|
|
10
|
-
import { owningSandboxSessionId, sandboxSpecOf } from "../sandbox/share.js";
|
|
10
|
+
import { owningSandboxSessionId, sandboxSpecOf, sessionSandboxSpec, } from "../sandbox/share.js";
|
|
11
|
+
import { withSandboxCapability } from "../sandbox/session-sandbox.js";
|
|
11
12
|
import { loadSession, ownedSession, sandboxLookup, sessionOf, } from "./context.js";
|
|
12
13
|
import { fail } from "./http.js";
|
|
13
|
-
import { actionTarget, linkedAgentOutput, manifestFor, mcpToolOf, pinnedTool, } from "./session.js";
|
|
14
|
+
import { actionTarget, linkedAgentOutput, manifestFor, mcpToolOf, pinnedTool, turnManifestOf, } from "./session.js";
|
|
14
15
|
import { command } from "./commands.js";
|
|
15
|
-
import {
|
|
16
|
+
import { offerAction } from "./delivery.js";
|
|
17
|
+
import { assistantMessage, contextCompacted, modelFailed, toolCompleted, toolIds, } from "./transcript.js";
|
|
18
|
+
import { classifyThrown } from "../model/classify.js";
|
|
16
19
|
import { abortKind } from "./worker.js";
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Call the model for one effect. A provider failure comes back as a failure outcome
|
|
22
|
+
* (Model Calls §6), whichever provider serves the call; only an abort throws, and the
|
|
23
|
+
* advance decides what the abort means.
|
|
24
|
+
*/
|
|
25
|
+
export async function invokeModel(ctx, request, signal, model) {
|
|
26
|
+
try {
|
|
27
|
+
if (model)
|
|
28
|
+
return await model(request, signal);
|
|
29
|
+
if (!ctx.useVaultModel)
|
|
30
|
+
return await ctx.modelProvider(request, signal);
|
|
31
|
+
const adapter = piModel({
|
|
32
|
+
root: ctx.config.paths.home,
|
|
33
|
+
readHostModel: () => ctx.vault.readHostModel(),
|
|
34
|
+
writeHostCredential: (credential) => ctx.vault.updateHostCredential(credential),
|
|
35
|
+
...(ctx.config.modelCall ? { settings: ctx.config.modelCall } : {}),
|
|
36
|
+
});
|
|
37
|
+
return await adapter(request.input, {
|
|
38
|
+
request: request.context.request,
|
|
39
|
+
invocationId: String(request.context.invocationId),
|
|
40
|
+
signal,
|
|
41
|
+
reportPreparedCall() { },
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (signal.aborted)
|
|
46
|
+
throw error;
|
|
47
|
+
return classifyThrown(error);
|
|
48
|
+
}
|
|
33
49
|
}
|
|
34
50
|
/**
|
|
35
51
|
* The outcome of a flow `agent` effect once the linked turn it started ended, or undefined
|
|
@@ -158,8 +174,6 @@ export async function resolveEffect(ctx, request, signal, lease, segment = {}) {
|
|
|
158
174
|
context: request.context,
|
|
159
175
|
status: "pending",
|
|
160
176
|
generation: 0,
|
|
161
|
-
claimId: null,
|
|
162
|
-
leaseExpiresAt: null,
|
|
163
177
|
...(request.agent ? { agent: request.agent } : {}),
|
|
164
178
|
};
|
|
165
179
|
const action = request.kind === "hook"
|
|
@@ -188,7 +202,7 @@ export async function resolveEffect(ctx, request, signal, lease, segment = {}) {
|
|
|
188
202
|
...(action.kind === "tool" ? toolIds(request.context) : {}),
|
|
189
203
|
input: action.input,
|
|
190
204
|
});
|
|
191
|
-
t
|
|
205
|
+
await offerAction(t, ctx, action);
|
|
192
206
|
return resolved({ status: "pending" });
|
|
193
207
|
});
|
|
194
208
|
if (journaled.kind === "resolved")
|
|
@@ -218,11 +232,23 @@ export async function resolveEffect(ctx, request, signal, lease, segment = {}) {
|
|
|
218
232
|
effect.status = "completed";
|
|
219
233
|
effect.outcome = { value };
|
|
220
234
|
await t.put("effects", request.effectId, effect);
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
235
|
+
const failed = invoke === "model" ? modelFailed(request, value) : undefined;
|
|
236
|
+
// A summary call is never an assistant message; the last one publishes context.compacted.
|
|
237
|
+
const summarizing = invoke === "model" && Boolean(request.context.compaction);
|
|
238
|
+
const compacted = summarizing && !failed ? contextCompacted(request, value) : undefined;
|
|
239
|
+
const transcript = failed || summarizing
|
|
240
|
+
? undefined
|
|
241
|
+
: invoke === "model"
|
|
242
|
+
? assistantMessage(request, value)
|
|
243
|
+
: toolCompleted(request, value);
|
|
244
|
+
if (failed)
|
|
245
|
+
await t.event(s.id, request.turnId, "model.failed", failed);
|
|
246
|
+
if (compacted)
|
|
247
|
+
await t.event(s.id, request.turnId, "context.compacted", compacted);
|
|
248
|
+
if (transcript && invoke === "model")
|
|
249
|
+
await t.event(s.id, request.turnId, "message.assistant", transcript);
|
|
250
|
+
else if (transcript)
|
|
251
|
+
await t.event(s.id, request.turnId, "tool.completed", transcript);
|
|
226
252
|
return { status: "completed", outcome: effect.outcome };
|
|
227
253
|
});
|
|
228
254
|
}
|
|
@@ -311,8 +337,6 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
311
337
|
context: request.context,
|
|
312
338
|
status: "pending",
|
|
313
339
|
generation: 0,
|
|
314
|
-
claimId: null,
|
|
315
|
-
leaseExpiresAt: null,
|
|
316
340
|
kind: request.kind,
|
|
317
341
|
path: request.path,
|
|
318
342
|
key: request.key,
|
|
@@ -328,8 +352,6 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
328
352
|
context: request.context,
|
|
329
353
|
status: "pending",
|
|
330
354
|
generation: 0,
|
|
331
|
-
claimId: null,
|
|
332
|
-
leaseExpiresAt: null,
|
|
333
355
|
kind: "tool",
|
|
334
356
|
path: request.path,
|
|
335
357
|
key: request.key,
|
|
@@ -352,7 +374,7 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
352
374
|
key: action.key,
|
|
353
375
|
input: action.input,
|
|
354
376
|
});
|
|
355
|
-
t
|
|
377
|
+
await offerAction(t, ctx, action);
|
|
356
378
|
return resolved({ status: "pending" });
|
|
357
379
|
}
|
|
358
380
|
return { kind: "agent", workflow };
|
|
@@ -386,17 +408,19 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
386
408
|
await ownedSession(t, lease, workflow.id);
|
|
387
409
|
if (exists)
|
|
388
410
|
return;
|
|
389
|
-
const definition =
|
|
390
|
-
|
|
391
|
-
const sandboxOwnerId =
|
|
392
|
-
? owningSandboxSessionId(workflow,
|
|
411
|
+
const definition = await leafDefinition(t, workflow, body, request);
|
|
412
|
+
const lookup = await sandboxLookup(t, workflow.sandboxOwnerId);
|
|
413
|
+
const sandboxOwnerId = sessionSandboxSpec(workflow) || workflow.sandboxOwnerId
|
|
414
|
+
? owningSandboxSessionId(workflow, lookup)
|
|
393
415
|
: undefined;
|
|
416
|
+
// A sandbox chosen when the tree was opened reaches every agent in it, with its tools.
|
|
417
|
+
const inherited = inheritedSandbox(sandboxOwnerId === undefined ? undefined : lookup(sandboxOwnerId) ?? workflow, definition);
|
|
394
418
|
const created = {
|
|
395
419
|
id: agentSessionId,
|
|
396
420
|
agentId: body.agentId,
|
|
397
421
|
ownerUserId: workflow.ownerUserId,
|
|
398
|
-
manifest: definition.manifest,
|
|
399
|
-
manifestHash: definition.manifestHash,
|
|
422
|
+
manifest: inherited?.manifest ?? definition.manifest,
|
|
423
|
+
manifestHash: inherited?.manifestHash ?? definition.manifestHash,
|
|
400
424
|
implementationVersion: definition.implementationVersion,
|
|
401
425
|
status: "idle",
|
|
402
426
|
activeTurnId: null,
|
|
@@ -410,7 +434,9 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
410
434
|
credentialSelections: workflow.credentialSelections,
|
|
411
435
|
pluginRoots: definition.pluginRoots ?? {},
|
|
412
436
|
...(sandboxOwnerId ? { sandboxOwnerId } : {}),
|
|
413
|
-
|
|
437
|
+
...(inherited
|
|
438
|
+
? { sandbox: inherited.spec, sandboxSource: "shared" }
|
|
439
|
+
: {}),
|
|
414
440
|
};
|
|
415
441
|
await t.put("sessions", agentSessionId, created);
|
|
416
442
|
});
|
|
@@ -440,13 +466,15 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
440
466
|
return store.tx(async (t) => {
|
|
441
467
|
const s = await ownedSession(t, lease, request.sessionId);
|
|
442
468
|
const agent = await sessionOf(t, agentSessionId);
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
469
|
+
// Only a Loop's own agent turns are iterations; a step elsewhere in the flow is not.
|
|
470
|
+
if (typeof request.context.loopPath === "string")
|
|
471
|
+
await t.event(s.id, s.activeTurnId, "loop.iteration", {
|
|
472
|
+
path: request.context.loopPath,
|
|
473
|
+
n,
|
|
474
|
+
sessionId: agentSessionId,
|
|
475
|
+
turnId: accepted.turnId ?? undefined,
|
|
476
|
+
manifestHash: agent.checkpoint?.manifestHash,
|
|
477
|
+
});
|
|
450
478
|
await t.event(s.id, s.activeTurnId, "node.agent", {
|
|
451
479
|
path,
|
|
452
480
|
iterations,
|
|
@@ -532,6 +560,21 @@ async function callMcpTool(ctx, request) {
|
|
|
532
560
|
pluginRoots: s.pluginRoots ?? {},
|
|
533
561
|
});
|
|
534
562
|
}
|
|
563
|
+
/**
|
|
564
|
+
* The sandbox a linked session inherits from its tree's owner when that sandbox was chosen at
|
|
565
|
+
* open (pinned on the owner). A definition that declares its own sandbox keeps it, as before.
|
|
566
|
+
*/
|
|
567
|
+
function inheritedSandbox(owner, definition) {
|
|
568
|
+
const spec = owner?.sandbox;
|
|
569
|
+
if (!spec || sandboxSpecOf(definition.manifest))
|
|
570
|
+
return undefined;
|
|
571
|
+
if (isWorkflowManifest(definition.manifest))
|
|
572
|
+
return { spec };
|
|
573
|
+
const sandboxed = withSandboxCapability(definition.manifest, spec);
|
|
574
|
+
if (!sandboxed.ok)
|
|
575
|
+
throw new Error(sandboxed.message);
|
|
576
|
+
return { spec, manifest: sandboxed.manifest, manifestHash: sandboxed.manifestHash };
|
|
577
|
+
}
|
|
535
578
|
async function callSandboxTool(ctx, request, signal) {
|
|
536
579
|
const { s, ownerId } = await ctx.store.tx(async (t) => {
|
|
537
580
|
const s = await sessionOf(t, request.sessionId);
|
|
@@ -562,3 +605,50 @@ export async function authorize(ctx, sessionId, request) {
|
|
|
562
605
|
}
|
|
563
606
|
return result;
|
|
564
607
|
}
|
|
608
|
+
/**
|
|
609
|
+
* The definition a flow leaf's session runs. A v2 workflow embeds its leaves, so they
|
|
610
|
+
* come from the workflow's own manifest and can't drift from it; v1 leaves come from the
|
|
611
|
+
* registry by id.
|
|
612
|
+
*/
|
|
613
|
+
async function leafDefinition(t, workflow, body, request) {
|
|
614
|
+
// A flow agent used as a tool: its manifest is inlined in the parent's pinned tool.
|
|
615
|
+
if (request.context.role === "delegate" && !isWorkflowManifest(workflow.manifest)) {
|
|
616
|
+
const flow = flowDelegateManifest(turnManifestOf(workflow), body.agentId);
|
|
617
|
+
if (!flow)
|
|
618
|
+
return fail(404, `Agent '${body.agentId}' is not a flow agent used as a tool`);
|
|
619
|
+
return {
|
|
620
|
+
manifest: flow,
|
|
621
|
+
manifestHash: hashManifest(flow),
|
|
622
|
+
implementationVersion: workflow.implementationVersion,
|
|
623
|
+
pluginRoots: leafPluginRoots(workflow.pluginRoots, body.agentId),
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
if (isWorkflowManifestV2(workflow.manifest)) {
|
|
627
|
+
const leaf = embeddedAgent(workflow.manifest, body.flow ?? [], body.agentId);
|
|
628
|
+
if (!leaf || isWorkflowManifestV2(leaf))
|
|
629
|
+
fail(404, `Agent '${body.agentId}' is not embedded in workflow '${workflow.manifest.id}'`);
|
|
630
|
+
const manifest = leaf;
|
|
631
|
+
return {
|
|
632
|
+
manifest,
|
|
633
|
+
manifestHash: hashManifest(manifest),
|
|
634
|
+
implementationVersion: workflow.implementationVersion,
|
|
635
|
+
pluginRoots: leafPluginRoots(workflow.pluginRoots, body.agentId),
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
const definition = await t.get("definitions", body.agentId);
|
|
639
|
+
if (!definition)
|
|
640
|
+
fail(404, "Definition not found");
|
|
641
|
+
return definition;
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* A v2 workflow keys its leaves' plugin roots `<agentId>/<capability>`, and an agent keys
|
|
645
|
+
* a flow agent it uses as a tool the same way (`<flowId>/<leafId>/<capability>`).
|
|
646
|
+
*/
|
|
647
|
+
function leafPluginRoots(roots, agentId) {
|
|
648
|
+
const own = {};
|
|
649
|
+
const prefix = `${agentId}/`;
|
|
650
|
+
for (const [key, root] of Object.entries(roots ?? {}))
|
|
651
|
+
if (key.startsWith(prefix))
|
|
652
|
+
own[key.slice(prefix.length)] = root;
|
|
653
|
+
return own;
|
|
654
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Action endpoints (design: Action endpoints §4.2): where the Runtime delivers each agent's
|
|
3
|
+
* Actions, registered with the application key.
|
|
4
|
+
*/
|
|
5
|
+
import { type Endpoint, type PutEndpointsRequest } from "@nylorun/core/contracts";
|
|
6
|
+
import type { EndpointRow } from "../store/types.js";
|
|
7
|
+
import type { TenantContext } from "./context.js";
|
|
8
|
+
/** An endpoint as the Tenant API answers it. */
|
|
9
|
+
export declare function endpointView(row: EndpointRow): Endpoint;
|
|
10
|
+
/** `GET /v1/endpoints`. */
|
|
11
|
+
export declare function listEndpoints(ctx: TenantContext): Promise<{
|
|
12
|
+
endpoints: {
|
|
13
|
+
agentId: string;
|
|
14
|
+
url: string;
|
|
15
|
+
implementationVersion: string;
|
|
16
|
+
manifestHash?: string | undefined;
|
|
17
|
+
timeoutMs: number;
|
|
18
|
+
maxConcurrent: number;
|
|
19
|
+
health: {
|
|
20
|
+
lastDeliveryAt?: string | undefined;
|
|
21
|
+
lastSuccessAt?: string | undefined;
|
|
22
|
+
lastError?: {
|
|
23
|
+
code: string;
|
|
24
|
+
message: string;
|
|
25
|
+
} | undefined;
|
|
26
|
+
consecutiveFailures: number;
|
|
27
|
+
served?: {
|
|
28
|
+
implementationVersion: string;
|
|
29
|
+
manifestHash?: string | undefined;
|
|
30
|
+
} | undefined;
|
|
31
|
+
};
|
|
32
|
+
updatedAt: string;
|
|
33
|
+
}[];
|
|
34
|
+
}>;
|
|
35
|
+
/**
|
|
36
|
+
* `PUT /v1/endpoints`: upsert the batch in one transaction, then send the Actions already
|
|
37
|
+
* waiting for these agents. Answers every registered endpoint of the batch.
|
|
38
|
+
*/
|
|
39
|
+
export declare function putEndpoints(ctx: TenantContext, principalId: string, body: PutEndpointsRequest): Promise<{
|
|
40
|
+
endpoints: {
|
|
41
|
+
agentId: string;
|
|
42
|
+
url: string;
|
|
43
|
+
implementationVersion: string;
|
|
44
|
+
manifestHash?: string | undefined;
|
|
45
|
+
timeoutMs: number;
|
|
46
|
+
maxConcurrent: number;
|
|
47
|
+
health: {
|
|
48
|
+
lastDeliveryAt?: string | undefined;
|
|
49
|
+
lastSuccessAt?: string | undefined;
|
|
50
|
+
lastError?: {
|
|
51
|
+
code: string;
|
|
52
|
+
message: string;
|
|
53
|
+
} | undefined;
|
|
54
|
+
consecutiveFailures: number;
|
|
55
|
+
served?: {
|
|
56
|
+
implementationVersion: string;
|
|
57
|
+
manifestHash?: string | undefined;
|
|
58
|
+
} | undefined;
|
|
59
|
+
};
|
|
60
|
+
updatedAt: string;
|
|
61
|
+
}[];
|
|
62
|
+
}>;
|
|
63
|
+
/** `DELETE /v1/endpoints/:agentId`. The agent's pending Actions wait for a new endpoint. */
|
|
64
|
+
export declare function deleteEndpoint(ctx: TenantContext, agentId: string): Promise<{
|
|
65
|
+
agentId: string;
|
|
66
|
+
deleted: true;
|
|
67
|
+
}>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Action endpoints (design: Action endpoints §4.2): where the Runtime delivers each agent's
|
|
3
|
+
* Actions, registered with the application key.
|
|
4
|
+
*/
|
|
5
|
+
import { ENDPOINT_MAX_CONCURRENT_DEFAULT, ENDPOINT_TIMEOUT_DEFAULT_MS, } from "@nylorun/core/contracts";
|
|
6
|
+
import { fail } from "./http.js";
|
|
7
|
+
/** An endpoint as the Tenant API answers it. */
|
|
8
|
+
export function endpointView(row) {
|
|
9
|
+
const served = row.servedImplementationVersion === undefined
|
|
10
|
+
? undefined
|
|
11
|
+
: {
|
|
12
|
+
implementationVersion: row.servedImplementationVersion,
|
|
13
|
+
...(row.servedManifestHash === undefined
|
|
14
|
+
? {}
|
|
15
|
+
: { manifestHash: row.servedManifestHash }),
|
|
16
|
+
};
|
|
17
|
+
return {
|
|
18
|
+
agentId: row.agentId,
|
|
19
|
+
url: row.url,
|
|
20
|
+
implementationVersion: row.implementationVersion,
|
|
21
|
+
...(row.manifestHash === undefined ? {} : { manifestHash: row.manifestHash }),
|
|
22
|
+
timeoutMs: row.timeoutMs,
|
|
23
|
+
maxConcurrent: row.maxConcurrent,
|
|
24
|
+
health: {
|
|
25
|
+
...(row.lastDeliveryAt === undefined ? {} : { lastDeliveryAt: row.lastDeliveryAt }),
|
|
26
|
+
...(row.lastSuccessAt === undefined ? {} : { lastSuccessAt: row.lastSuccessAt }),
|
|
27
|
+
...(row.lastErrorCode === undefined
|
|
28
|
+
? {}
|
|
29
|
+
: {
|
|
30
|
+
lastError: {
|
|
31
|
+
code: row.lastErrorCode,
|
|
32
|
+
message: row.lastErrorMessage ?? "",
|
|
33
|
+
},
|
|
34
|
+
}),
|
|
35
|
+
consecutiveFailures: row.consecutiveFailures,
|
|
36
|
+
...(served ? { served } : {}),
|
|
37
|
+
},
|
|
38
|
+
updatedAt: row.updatedAt,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** `GET /v1/endpoints`. */
|
|
42
|
+
export async function listEndpoints(ctx) {
|
|
43
|
+
const rows = await ctx.store.tx((t) => t.listEndpoints());
|
|
44
|
+
return { endpoints: rows.map(endpointView) };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* `PUT /v1/endpoints`: upsert the batch in one transaction, then send the Actions already
|
|
48
|
+
* waiting for these agents. Answers every registered endpoint of the batch.
|
|
49
|
+
*/
|
|
50
|
+
export async function putEndpoints(ctx, principalId, body) {
|
|
51
|
+
const updatedAt = new Date().toISOString();
|
|
52
|
+
const rows = await ctx.store.tx(async (t) => {
|
|
53
|
+
const saved = [];
|
|
54
|
+
for (const endpoint of body.endpoints) {
|
|
55
|
+
await t.putEndpoint({
|
|
56
|
+
agentId: endpoint.agentId,
|
|
57
|
+
url: endpoint.url,
|
|
58
|
+
implementationVersion: endpoint.implementationVersion,
|
|
59
|
+
...(endpoint.manifestHash === undefined
|
|
60
|
+
? {}
|
|
61
|
+
: { manifestHash: endpoint.manifestHash }),
|
|
62
|
+
timeoutMs: endpoint.timeoutMs ?? ENDPOINT_TIMEOUT_DEFAULT_MS,
|
|
63
|
+
maxConcurrent: endpoint.maxConcurrent ?? ENDPOINT_MAX_CONCURRENT_DEFAULT,
|
|
64
|
+
principalId,
|
|
65
|
+
updatedAt,
|
|
66
|
+
});
|
|
67
|
+
saved.push((await t.getEndpoint(endpoint.agentId)));
|
|
68
|
+
}
|
|
69
|
+
return saved;
|
|
70
|
+
});
|
|
71
|
+
// Actions already waiting for these agents go to their endpoint now.
|
|
72
|
+
const waiting = await ctx.store.tx(async (t) => {
|
|
73
|
+
const actions = [];
|
|
74
|
+
for (const endpoint of body.endpoints)
|
|
75
|
+
actions.push(...(await t.pendingActions(endpoint.agentId)));
|
|
76
|
+
return actions;
|
|
77
|
+
});
|
|
78
|
+
for (const action of waiting)
|
|
79
|
+
await ctx.deliver(action.actionId);
|
|
80
|
+
return { endpoints: rows.map(endpointView) };
|
|
81
|
+
}
|
|
82
|
+
/** `DELETE /v1/endpoints/:agentId`. The agent's pending Actions wait for a new endpoint. */
|
|
83
|
+
export async function deleteEndpoint(ctx, agentId) {
|
|
84
|
+
await ctx.store.tx(async (t) => {
|
|
85
|
+
if (!(await t.getEndpoint(agentId)))
|
|
86
|
+
fail(404, "Endpoint not found");
|
|
87
|
+
await t.deleteEndpoint(agentId);
|
|
88
|
+
});
|
|
89
|
+
return { agentId, deleted: true };
|
|
90
|
+
}
|
|
@@ -19,6 +19,10 @@ export interface StartEphemeralRuntimeOptions {
|
|
|
19
19
|
sandboxBackend?: "auto" | "virtual";
|
|
20
20
|
/** When true, close() leaves hostRoot on disk. */
|
|
21
21
|
retainRoot?: boolean;
|
|
22
|
+
/** Allow browser requests (an `Origin` with a publishable key). Default off. */
|
|
23
|
+
browserAccess?: boolean;
|
|
24
|
+
/** Serve the Admin API on its own loopback listener (`adminUrl`). Default off. */
|
|
25
|
+
operatorListener?: boolean;
|
|
22
26
|
logger?: Logger;
|
|
23
27
|
/**
|
|
24
28
|
* A Postgres pool: Tenants become schemas in it (`store-pg.ts`) instead of living in memory.
|
|
@@ -28,6 +32,8 @@ export interface StartEphemeralRuntimeOptions {
|
|
|
28
32
|
}
|
|
29
33
|
export interface EphemeralRuntime {
|
|
30
34
|
url: string;
|
|
35
|
+
/** Where the Admin API answers: `url`, or the operator listener when requested. */
|
|
36
|
+
adminUrl: string;
|
|
31
37
|
tenantId: string;
|
|
32
38
|
applicationKey: string;
|
|
33
39
|
adminKey: string;
|
package/dist/tenant/ephemeral.js
CHANGED
|
@@ -13,7 +13,7 @@ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
|
13
13
|
import { randomBytes } from "node:crypto";
|
|
14
14
|
import { createRequire } from "node:module";
|
|
15
15
|
import { newTenantId } from "@nylorun/core/compatibility";
|
|
16
|
-
import { hashToken, mintBearerToken } from "../core/
|
|
16
|
+
import { hashToken, mintBearerToken } from "../core/bearer.js";
|
|
17
17
|
import { createHost } from "../host/create-host.js";
|
|
18
18
|
import { createKekFile } from "../vault/kek.js";
|
|
19
19
|
import { MemorySessionStore } from "../store/memory.js";
|
|
@@ -95,6 +95,7 @@ function kept(store) {
|
|
|
95
95
|
tenantId: store.tenantId,
|
|
96
96
|
tx: (fn) => store.tx(fn),
|
|
97
97
|
onCommit: (listener) => store.onCommit(listener),
|
|
98
|
+
record: () => store.record(),
|
|
98
99
|
health: () => store.health(),
|
|
99
100
|
close: async () => { },
|
|
100
101
|
};
|
|
@@ -173,37 +174,65 @@ export async function startEphemeralRuntime(options) {
|
|
|
173
174
|
logger,
|
|
174
175
|
onDeleted: (tenantId) => streams.deleteTenant(tenantId),
|
|
175
176
|
});
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
177
|
+
let host;
|
|
178
|
+
/** Closes what was opened, in reverse order; `close()` and a failed start share it. */
|
|
179
|
+
const release = async () => {
|
|
180
|
+
await host?.close();
|
|
181
|
+
await module.close();
|
|
182
|
+
await memory?.close();
|
|
183
|
+
await streams.close();
|
|
184
|
+
if (!options.retainRoot)
|
|
185
|
+
rmSync(hostRoot, { recursive: true, force: true });
|
|
186
|
+
};
|
|
187
|
+
let tenantId;
|
|
188
|
+
let applicationKey;
|
|
189
|
+
let principalId;
|
|
190
|
+
try {
|
|
191
|
+
await module.start();
|
|
192
|
+
tenantId = options.tenantId ?? newTenantId();
|
|
193
|
+
applicationKey = options.applicationKey ?? mintBearerToken();
|
|
194
|
+
principalId =
|
|
195
|
+
options.principalId ?? `principal_${randomBytes(8).toString("hex")}`;
|
|
196
|
+
const credentialHash = hashToken(applicationKey);
|
|
197
|
+
await module.create({
|
|
198
|
+
tenantId,
|
|
199
|
+
name: options.name ?? "ephemeral",
|
|
200
|
+
principalId,
|
|
201
|
+
credentialHash,
|
|
202
|
+
idempotencyKey: `ephemeral-${tenantId}`,
|
|
203
|
+
...(options.studioCredentialHash
|
|
204
|
+
? { studioCredentialHash: options.studioCredentialHash }
|
|
205
|
+
: {}),
|
|
206
|
+
});
|
|
207
|
+
// KEK for first vault write; openTenantRuntime also creates when hooks allow.
|
|
208
|
+
const kekPath = tenantPaths(hostRoot, tenantId).kek;
|
|
209
|
+
if (!existsSync(kekPath))
|
|
210
|
+
createKekFile(kekPath);
|
|
211
|
+
host = createHost({
|
|
212
|
+
hostRoot,
|
|
213
|
+
module,
|
|
214
|
+
config: hostConfig,
|
|
215
|
+
credentials,
|
|
216
|
+
logger,
|
|
217
|
+
coreVersion: coreVersion(),
|
|
218
|
+
browserAccess: options.browserAccess === true,
|
|
219
|
+
...(options.operatorListener
|
|
220
|
+
? { operator: { host: "127.0.0.1", port: 0 } }
|
|
221
|
+
: {}),
|
|
222
|
+
});
|
|
223
|
+
await host.listen();
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
await release().catch((cleanup) => logger.warn("ephemeral runtime cleanup failed", {
|
|
227
|
+
message: cleanup instanceof Error ? cleanup.message : String(cleanup),
|
|
228
|
+
}));
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
231
|
+
const started = host;
|
|
204
232
|
let closed = false;
|
|
205
233
|
return {
|
|
206
|
-
url:
|
|
234
|
+
url: started.url,
|
|
235
|
+
adminUrl: started.adminUrl,
|
|
207
236
|
tenantId,
|
|
208
237
|
applicationKey,
|
|
209
238
|
adminKey,
|
|
@@ -213,12 +242,7 @@ export async function startEphemeralRuntime(options) {
|
|
|
213
242
|
if (closed)
|
|
214
243
|
return;
|
|
215
244
|
closed = true;
|
|
216
|
-
await
|
|
217
|
-
await module.close();
|
|
218
|
-
await memory?.close();
|
|
219
|
-
await streams.close();
|
|
220
|
-
if (!options.retainRoot)
|
|
221
|
-
rmSync(hostRoot, { recursive: true, force: true });
|
|
245
|
+
await release();
|
|
222
246
|
},
|
|
223
247
|
};
|
|
224
248
|
}
|
package/dist/tenant/http.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* The Tenant's typed rejections: what a route or the code it calls throws for the client to
|
|
3
|
+
* see (`api/http/respond.ts` answers it), and the opaque 404 for unknown credentials (D5).
|
|
4
4
|
*
|
|
5
5
|
* Later waves: stable; the streams seam (Wave 2 / Y) keeps using these helpers.
|
|
6
6
|
*/
|
|
7
|
-
import type { IncomingMessage } from "node:http";
|
|
8
7
|
/** D5 opaque failure for unknown/rejected credentials on Tenant routes. */
|
|
9
8
|
export declare const OPAQUE_NOT_FOUND: {
|
|
10
9
|
status: "rejected";
|
|
@@ -19,9 +18,13 @@ export interface Rejection {
|
|
|
19
18
|
export declare class HttpError extends Error {
|
|
20
19
|
readonly status: number;
|
|
21
20
|
readonly rejection: Rejection;
|
|
22
|
-
|
|
21
|
+
/** Response headers the rejection carries, such as `retry-after` or `www-authenticate`. */
|
|
22
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
23
|
+
constructor(status: number, message: string, rejection?: Rejection,
|
|
24
|
+
/** Response headers the rejection carries, such as `retry-after` or `www-authenticate`. */
|
|
25
|
+
headers?: Readonly<Record<string, string>>);
|
|
23
26
|
}
|
|
24
|
-
export declare const fail: (status: number, message: string, rejection?: Rejection) => never;
|
|
27
|
+
export declare const fail: (status: number, message: string, rejection?: Rejection, headers?: Readonly<Record<string, string>>) => never;
|
|
25
28
|
export declare class OpaqueAuthError extends Error {
|
|
26
29
|
readonly status = 404;
|
|
27
30
|
readonly body: {
|
|
@@ -32,7 +35,3 @@ export declare class OpaqueAuthError extends Error {
|
|
|
32
35
|
constructor();
|
|
33
36
|
}
|
|
34
37
|
export declare const failOpaque: () => never;
|
|
35
|
-
/** AbortSignal tied to the HTTP request being closed by the client. */
|
|
36
|
-
export declare function requestAborted(request: IncomingMessage): AbortSignal;
|
|
37
|
-
/** Read a JSON request body, capped at 1 MiB. */
|
|
38
|
-
export declare function readBody(request: IncomingMessage): Promise<unknown>;
|