@nylorun/runtime 0.16.0-beta → 0.19.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +207 -0
- package/README.md +105 -63
- package/dist/adapters/execution/restate.d.ts +1 -3
- package/dist/adapters/execution/restate.js +4 -31
- package/dist/adapters/replication/pgoutput.js +1 -1
- package/dist/adapters/sandbox/local.js +1 -1
- package/dist/adapters/sandbox/virtual.js +1 -1
- package/dist/api/ag-ui/endpoint.js +1 -2
- package/dist/api/ag-ui/routes.d.ts +1 -1
- package/dist/api/ag-ui/routes.js +11 -13
- package/dist/api/ag-ui/translate.d.ts +1 -3
- package/dist/api/ag-ui/translate.js +2 -17
- package/dist/api/components.d.ts +4783 -2588
- package/dist/api/components.js +19 -21
- package/dist/api/gate/routes.d.ts +7 -8
- package/dist/api/gate/routes.js +58 -52
- package/dist/api/http/app.d.ts +6 -0
- package/dist/api/http/app.js +20 -14
- package/dist/api/http/define.d.ts +22 -13
- package/dist/api/http/define.js +43 -39
- package/dist/api/http/routes/access.d.ts +3 -19
- package/dist/api/http/routes/access.js +25 -184
- package/dist/api/http/routes/artifacts.js +3 -7
- package/dist/api/http/routes/files.d.ts +8 -0
- package/dist/api/http/routes/files.js +77 -0
- package/dist/api/http/routes/keys.d.ts +10 -0
- package/dist/api/http/routes/keys.js +64 -0
- package/dist/api/http/routes/me.d.ts +13 -0
- package/dist/api/http/routes/me.js +48 -0
- package/dist/api/http/routes/reads.d.ts +66 -0
- package/dist/api/http/routes/reads.js +127 -0
- package/dist/api/http/routes/sandboxes.d.ts +2 -2
- package/dist/api/http/routes/sandboxes.js +8 -7
- package/dist/api/http/routes/sessions.js +31 -20
- package/dist/api/http/routes/tenant.js +15 -18
- package/dist/api/http/routes/vaults.d.ts +13 -3
- package/dist/api/http/routes/vaults.js +136 -64
- package/dist/api/openapi.d.ts +10 -11
- package/dist/api/openapi.js +292 -68
- package/dist/artifacts/links.js +6 -6
- package/dist/artifacts/service.js +1 -1
- package/dist/core/flow-host.d.ts +44 -23
- package/dist/core/flow-host.js +88 -106
- package/dist/core/provider.d.ts +4 -1
- package/dist/core/provider.js +6 -1
- package/dist/core/sandbox-routes.d.ts +0 -9
- package/dist/core/sandbox-routes.js +2 -21
- package/dist/execution/memory.d.ts +4 -5
- package/dist/execution/memory.js +11 -25
- package/dist/execution/types.d.ts +3 -33
- package/dist/execution/types.js +2 -12
- package/dist/gates/http-tool.d.ts +86 -0
- package/dist/gates/http-tool.js +197 -0
- package/dist/gates/mcp-handler.d.ts +3 -0
- package/dist/gates/mcp-handler.js +6 -4
- package/dist/gates/meter.d.ts +2 -2
- package/dist/gates/meter.js +6 -0
- package/dist/gates/tenant-vaults.d.ts +19 -14
- package/dist/gates/tenant-vaults.js +17 -12
- package/dist/gates/tool-calls.d.ts +7 -15
- package/dist/gates/tool-calls.js +4 -4
- package/dist/gates/tool-client.d.ts +1 -1
- package/dist/gates/tool-client.js +23 -47
- package/dist/gates/tool-contract.d.ts +27 -39
- package/dist/gates/tool-contract.js +30 -21
- package/dist/gates/tool-gate.d.ts +25 -24
- package/dist/gates/tool-gate.js +11 -7
- package/dist/harness/calls.d.ts +10 -4
- package/dist/harness/calls.js +15 -6
- package/dist/harness/executors.d.ts +1 -1
- package/dist/harness/executors.js +17 -12
- package/dist/harness/main.d.ts +2 -2
- package/dist/harness/main.js +5 -13
- package/dist/harness/service.d.ts +4 -5
- package/dist/harness/service.js +9 -2
- package/dist/harness-api/in-process.js +2 -2
- package/dist/harness-api/record.d.ts +6 -3
- package/dist/harness-api/record.js +65 -72
- package/dist/harness-api/server.d.ts +1 -9
- package/dist/harness-api/server.js +19 -17
- package/dist/harness-api/start.js +1 -5
- package/dist/harness-api/workspace.js +2 -3
- package/dist/harness-api/ws-server.js +1 -1
- package/dist/host/app.d.ts +2 -10
- package/dist/host/app.js +24 -44
- package/dist/host/config-for.d.ts +5 -5
- package/dist/host/config-for.js +2 -3
- package/dist/host/config.d.ts +0 -11
- package/dist/host/create-host.d.ts +3 -40
- package/dist/host/create-host.js +18 -91
- package/dist/host/environment.d.ts +0 -3
- package/dist/host/environment.js +0 -9
- package/dist/host/gates.d.ts +4 -1
- package/dist/host/gates.js +9 -3
- package/dist/host/http.d.ts +1 -2
- package/dist/host/http.js +1 -7
- package/dist/host/index.d.ts +2 -2
- package/dist/host/index.js +2 -2
- package/dist/host/main.js +55 -40
- package/dist/host/operate.d.ts +29 -0
- package/dist/host/operate.js +223 -0
- package/dist/host/stack-config.d.ts +23 -20
- package/dist/host/stack-config.js +28 -44
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -1
- package/dist/infra/workers.d.ts +1 -1
- package/dist/keys/client.js +9 -1
- package/dist/keys/keys.d.ts +12 -2
- package/dist/keys/keys.js +10 -2
- package/dist/management-openapi.json +6491 -0
- package/dist/mcp/connect.d.ts +8 -4
- package/dist/mcp/connect.js +8 -27
- package/dist/mcp/pool.d.ts +6 -9
- package/dist/mcp/pool.js +8 -18
- package/dist/mcp/snapshot.d.ts +8 -2
- package/dist/mcp/snapshot.js +29 -2
- package/dist/media.d.ts +1 -1
- package/dist/media.js +1 -1
- package/dist/model/pi-model.js +3 -0
- package/dist/openapi.json +6228 -15615
- package/dist/reads/access.d.ts +4 -0
- package/dist/reads/access.js +12 -0
- package/dist/reads/cursor.d.ts +11 -0
- package/dist/reads/cursor.js +80 -0
- package/dist/reads/types.d.ts +25 -0
- package/dist/reads/types.js +1 -0
- package/dist/sandbox/manager.d.ts +16 -0
- package/dist/sandbox/manager.js +40 -0
- package/dist/sandbox/session-sandbox.js +13 -3
- package/dist/sandbox/skills.d.ts +13 -0
- package/dist/sandbox/skills.js +46 -0
- package/dist/sandbox/types.d.ts +2 -2
- package/dist/store/ownership.d.ts +9 -0
- package/dist/store/ownership.js +13 -0
- package/dist/store/postgres/connect.d.ts +2 -0
- package/dist/store/postgres/connect.js +29 -0
- package/dist/store/postgres/drizzle/0008_installation_vaults.sql +2 -0
- package/dist/store/postgres/drizzle/0009_oauth_pending.sql +19 -0
- package/dist/store/postgres/drizzle/0010_oss_auth_removals.sql +3 -0
- package/dist/store/postgres/drizzle/0011_key_roles.sql +3 -0
- package/dist/store/postgres/drizzle/0012_session_reads.sql +8 -0
- package/dist/store/postgres/drizzle/0013_definition_files.sql +16 -0
- package/dist/store/postgres/drizzle/0014_action_endpoints_removed.sql +2 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +49 -0
- package/dist/store/postgres/reads.d.ts +3 -0
- package/dist/store/postgres/reads.js +272 -0
- package/dist/store/postgres/schema.d.ts +423 -594
- package/dist/store/postgres/schema.js +84 -101
- package/dist/store/postgres/store.js +96 -248
- package/dist/store/postgres/tenant.d.ts +34 -2
- package/dist/store/postgres/tenant.js +16 -32
- package/dist/store/types.d.ts +44 -119
- package/dist/store/types.js +0 -1
- package/dist/streams/control.d.ts +0 -7
- package/dist/streams/control.js +0 -13
- package/dist/streams/types.d.ts +2 -20
- package/dist/streams/types.js +1 -1
- package/dist/tenant/advance.d.ts +3 -3
- package/dist/tenant/advance.js +10 -7
- package/dist/tenant/auth.d.ts +24 -15
- package/dist/tenant/auth.js +44 -55
- package/dist/tenant/cause.d.ts +3 -3
- package/dist/tenant/cause.js +1 -1
- package/dist/tenant/commands.d.ts +2 -18
- package/dist/tenant/commands.js +38 -179
- package/dist/tenant/context.d.ts +33 -45
- package/dist/tenant/definition-files.d.ts +24 -0
- package/dist/tenant/definition-files.js +103 -0
- package/dist/tenant/effects.d.ts +20 -18
- package/dist/tenant/effects.js +59 -133
- package/dist/tenant/ephemeral.d.ts +21 -8
- package/dist/tenant/ephemeral.js +11 -12
- package/dist/tenant/host-token.js +5 -5
- package/dist/tenant/identity-file.d.ts +75 -0
- package/dist/tenant/identity-file.js +202 -0
- package/dist/tenant/issuers.d.ts +48 -0
- package/dist/tenant/issuers.js +311 -0
- package/dist/tenant/jwt.js +3 -3
- package/dist/tenant/module.d.ts +1 -4
- package/dist/tenant/module.js +3 -12
- package/dist/tenant/operator-keys.d.ts +44 -0
- package/dist/tenant/operator-keys.js +87 -0
- package/dist/tenant/outbound.d.ts +40 -1
- package/dist/tenant/outbound.js +167 -18
- package/dist/tenant/paths.d.ts +1 -1
- package/dist/tenant/paths.js +1 -3
- package/dist/tenant/principals.d.ts +13 -13
- package/dist/tenant/principals.js +35 -36
- package/dist/tenant/run-token.js +6 -6
- package/dist/tenant/runtime.d.ts +5 -3
- package/dist/tenant/runtime.js +29 -15
- package/dist/tenant/sandboxes.d.ts +9 -6
- package/dist/tenant/sandboxes.js +16 -5
- package/dist/tenant/scheduler.d.ts +1 -5
- package/dist/tenant/scheduler.js +2 -5
- package/dist/tenant/session-streams.d.ts +8 -11
- package/dist/tenant/session-streams.js +48 -32
- package/dist/tenant/session.d.ts +8 -41
- package/dist/tenant/session.js +0 -37
- package/dist/tenant/sessions.d.ts +2 -2
- package/dist/tenant/sessions.js +17 -9
- package/dist/tenant/signing-keys.d.ts +3 -2
- package/dist/tenant/signing-keys.js +5 -5
- package/dist/tenant/skill-tool.d.ts +20 -0
- package/dist/tenant/skill-tool.js +72 -0
- package/dist/tenant/slim.d.ts +1 -1
- package/dist/tenant/slim.js +1 -1
- package/dist/tenant/status.js +7 -12
- package/dist/tenant/store-pg.d.ts +15 -0
- package/dist/tenant/store-pg.js +7 -3
- package/dist/tenant/streams.d.ts +3 -11
- package/dist/tenant/streams.js +2 -29
- package/dist/tenant/sweep.js +0 -5
- package/dist/tenant/transcript.d.ts +2 -2
- package/dist/tenant/transcript.js +2 -2
- package/dist/tenant/types.d.ts +33 -19
- package/dist/tenant/worker.d.ts +1 -3
- package/dist/tenant/worker.js +3 -8
- package/dist/vault/crypto.d.ts +7 -0
- package/dist/vault/crypto.js +16 -0
- package/dist/vault/oauth.d.ts +55 -0
- package/dist/vault/oauth.js +186 -0
- package/dist/vault/service.d.ts +54 -7
- package/dist/vault/service.js +261 -17
- package/dist/vault/sources.d.ts +60 -0
- package/dist/vault/sources.js +196 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -4
- package/dist/admin-openapi.json +0 -604
- package/dist/api/http/routes/actions.d.ts +0 -8
- package/dist/api/http/routes/actions.js +0 -76
- package/dist/api/http/routes/endpoints.d.ts +0 -7
- package/dist/api/http/routes/endpoints.js +0 -100
- package/dist/host/admin-api.d.ts +0 -23
- package/dist/host/admin-api.js +0 -74
- package/dist/host/cors.d.ts +0 -27
- package/dist/host/cors.js +0 -64
- package/dist/mcp/stdio.d.ts +0 -17
- package/dist/mcp/stdio.js +0 -82
- package/dist/tenant/access-policy.d.ts +0 -30
- package/dist/tenant/access-policy.js +0 -52
- package/dist/tenant/actions.d.ts +0 -14
- package/dist/tenant/actions.js +0 -29
- package/dist/tenant/browser.d.ts +0 -15
- package/dist/tenant/browser.js +0 -39
- package/dist/tenant/delivery-token.d.ts +0 -41
- package/dist/tenant/delivery-token.js +0 -104
- package/dist/tenant/delivery.d.ts +0 -62
- package/dist/tenant/delivery.js +0 -481
- package/dist/tenant/endpoints.d.ts +0 -67
- package/dist/tenant/endpoints.js +0 -90
- package/dist/tenant/subject-limits.d.ts +0 -16
- package/dist/tenant/subject-limits.js +0 -45
- package/dist/tenant/tokens.d.ts +0 -14
- package/dist/tenant/tokens.js +0 -187
package/dist/tenant/effects.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import { embeddedAgent, flowDelegateManifest, hashManifest,
|
|
2
|
-
import { countActiveFlowWork, deriveAgentEffectSessionId, isFlowToolEffect, isWorkflowManifest, linkedMessageKey, linkedTurnEnd, } from "../core/flow-host.js";
|
|
1
|
+
import { embeddedAgent, flowDelegateManifest, hashManifest, } from "@nylorun/core/define";
|
|
2
|
+
import { countActiveFlowWork, deriveAgentEffectSessionId, isFlowToolEffect, isWorkflowManifest, linkedMessageInput, linkedMessageKey, linkedTurnEnd, settleAgentEffect, } from "../core/flow-host.js";
|
|
3
3
|
import { mayDispatchMore } from "../core/limits.js";
|
|
4
|
-
import { scrub } from "../redact.js";
|
|
5
4
|
import { isRemoteMcpCall } from "../harness/calls.js";
|
|
5
|
+
import { isHttpToolCall } from "../gates/http-tool.js";
|
|
6
6
|
import { owningSandboxSessionId, sandboxSpecOf, sessionSandboxSpec, } from "../sandbox/share.js";
|
|
7
7
|
import { withSandboxCapability } from "../sandbox/session-sandbox.js";
|
|
8
8
|
import { loadSession, ownedSession, sandboxLookup, sessionOf, } from "./context.js";
|
|
9
9
|
import { fail } from "./http.js";
|
|
10
|
-
import {
|
|
10
|
+
import { turnManifestOf } from "./session.js";
|
|
11
11
|
import { command } from "./commands.js";
|
|
12
|
-
import { offerAction } from "./delivery.js";
|
|
13
12
|
/**
|
|
14
13
|
* The outcome of a flow `agent` effect once the linked turn it started ended, or undefined
|
|
15
14
|
* while it has not. The linked session may still show an earlier iteration's turn.
|
|
@@ -19,7 +18,7 @@ export async function linkedOutcome(t, effect, agent) {
|
|
|
19
18
|
if (!end || !agent)
|
|
20
19
|
return undefined;
|
|
21
20
|
if (end.status === "completed")
|
|
22
|
-
return { value:
|
|
21
|
+
return { value: end.output };
|
|
23
22
|
return {
|
|
24
23
|
value: {
|
|
25
24
|
kind: "failed",
|
|
@@ -32,16 +31,17 @@ export async function linkedOutcome(t, effect, agent) {
|
|
|
32
31
|
};
|
|
33
32
|
}
|
|
34
33
|
/**
|
|
35
|
-
* True when this Tenant's remote MCP calls outlive the process that sent them
|
|
36
|
-
* service, F4.1 G3): after a takeover or a shutdown, the journaled call is re-sent
|
|
37
|
-
* the running call or gets its answer, instead of becoming `uncertain`.
|
|
34
|
+
* True when this Tenant's remote MCP and HTTP tool calls outlive the process that sent them
|
|
35
|
+
* (the gates service, F4.1 G3): after a takeover or a shutdown, the journaled call is re-sent
|
|
36
|
+
* and joins the running call or gets its answer, instead of becoming `uncertain`.
|
|
38
37
|
*/
|
|
39
|
-
export function
|
|
38
|
+
export function recoversToolCalls(ctx) {
|
|
40
39
|
return ctx.toolGate.recovers === true;
|
|
41
40
|
}
|
|
42
|
-
/** True when `request` calls a tool of a
|
|
43
|
-
export function
|
|
44
|
-
return isRemoteMcpCall({ rootManifest: s.manifest, mcpSnapshot: s.mcpSnapshot }, request)
|
|
41
|
+
/** True when `request` calls a tool of a declared MCP server (every one is remote) or an HTTP tool. */
|
|
42
|
+
export function isGateToolEffect(s, request) {
|
|
43
|
+
return (isRemoteMcpCall({ rootManifest: s.manifest, mcpSnapshot: s.mcpSnapshot }, request) ||
|
|
44
|
+
isHttpToolCall(s.manifest, request));
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
47
|
* True when this Tenant's vault-backed model calls outlive the process that sent them (the
|
|
@@ -51,7 +51,7 @@ export function isRemoteMcpEffect(s, request) {
|
|
|
51
51
|
export function recoversModelCalls(ctx) {
|
|
52
52
|
return ctx.useVaultModel && ctx.modelGate.recovers === true;
|
|
53
53
|
}
|
|
54
|
-
/** Journal and dispatch a new flow effect (agent
|
|
54
|
+
/** Journal and dispatch a new flow effect (agent or tool node). */
|
|
55
55
|
export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
56
56
|
const { store } = ctx;
|
|
57
57
|
signal.throwIfAborted();
|
|
@@ -76,9 +76,7 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
76
76
|
const outcome = await linkedOutcome(t, existing, agent);
|
|
77
77
|
if (!outcome)
|
|
78
78
|
return resolved({ status: "pending" });
|
|
79
|
-
existing
|
|
80
|
-
existing.outcome = outcome;
|
|
81
|
-
await t.put("effects", request.effectId, existing);
|
|
79
|
+
await settleAgentEffect({ t, effect: existing, outcome });
|
|
82
80
|
return resolved({ status: "completed", outcome });
|
|
83
81
|
}
|
|
84
82
|
else {
|
|
@@ -94,64 +92,25 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
94
92
|
});
|
|
95
93
|
return resolved({ status: "pending" });
|
|
96
94
|
}
|
|
97
|
-
if (request
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
95
|
+
if (isFlowToolEffect(request)) {
|
|
96
|
+
// A tool stage without `http` would run the developer's code: refused at save, so this
|
|
97
|
+
// is a backstop (HTTP stages are executed by the harness, `harness-api/record.ts`). The
|
|
98
|
+
// stage fails; nothing runs.
|
|
99
|
+
const outcome = {
|
|
100
|
+
value: {
|
|
101
|
+
kind: "failed",
|
|
102
|
+
code: "tool.unavailable",
|
|
103
|
+
message: `The tool stage '${request.key ?? request.path ?? ""}' runs your code, and the Runtime runs no code of yours during a session`,
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
await t.put("effects", request.effectId, { request, status: "completed", outcome });
|
|
107
|
+
await t.event(workflow.id, workflow.activeTurnId, "node.started", {
|
|
108
|
+
path: request.path,
|
|
109
|
+
kind: "tool",
|
|
110
|
+
key: request.key,
|
|
111
|
+
...(request.iterations !== undefined ? { iterations: request.iterations } : {}),
|
|
103
112
|
});
|
|
104
|
-
|
|
105
|
-
? {
|
|
106
|
-
actionId: request.effectId,
|
|
107
|
-
sessionId: request.sessionId,
|
|
108
|
-
turnId: request.turnId,
|
|
109
|
-
agentId: request.agentId,
|
|
110
|
-
manifestHash: request.manifestHash,
|
|
111
|
-
implementationVersion: workflow.implementationVersion,
|
|
112
|
-
input: request.input,
|
|
113
|
-
context: request.context,
|
|
114
|
-
status: "pending",
|
|
115
|
-
generation: 0,
|
|
116
|
-
kind: request.kind,
|
|
117
|
-
path: request.path,
|
|
118
|
-
key: request.key,
|
|
119
|
-
}
|
|
120
|
-
: {
|
|
121
|
-
actionId: request.effectId,
|
|
122
|
-
sessionId: request.sessionId,
|
|
123
|
-
turnId: request.turnId,
|
|
124
|
-
agentId: request.agentId,
|
|
125
|
-
manifestHash: request.manifestHash,
|
|
126
|
-
implementationVersion: workflow.implementationVersion,
|
|
127
|
-
input: request.input,
|
|
128
|
-
context: request.context,
|
|
129
|
-
status: "pending",
|
|
130
|
-
generation: 0,
|
|
131
|
-
kind: "tool",
|
|
132
|
-
path: request.path,
|
|
133
|
-
key: request.key,
|
|
134
|
-
};
|
|
135
|
-
await t.put("actions", action.actionId, action);
|
|
136
|
-
if (isFlowToolEffect(request)) {
|
|
137
|
-
await t.event(workflow.id, workflow.activeTurnId, "node.started", {
|
|
138
|
-
path: request.path,
|
|
139
|
-
kind: "tool",
|
|
140
|
-
key: request.key,
|
|
141
|
-
...(request.iterations !== undefined
|
|
142
|
-
? { iterations: request.iterations }
|
|
143
|
-
: {}),
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
await t.event(workflow.id, workflow.activeTurnId, "action.pending", {
|
|
147
|
-
actionId: action.actionId,
|
|
148
|
-
kind: action.kind,
|
|
149
|
-
path: action.path,
|
|
150
|
-
key: action.key,
|
|
151
|
-
input: action.input,
|
|
152
|
-
});
|
|
153
|
-
await offerAction(t, ctx, action);
|
|
154
|
-
return resolved({ status: "pending" });
|
|
113
|
+
return resolved({ status: "completed", outcome });
|
|
155
114
|
}
|
|
156
115
|
return { kind: "agent", workflow };
|
|
157
116
|
});
|
|
@@ -184,7 +143,7 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
184
143
|
await ownedSession(t, lease, workflow.id);
|
|
185
144
|
if (exists)
|
|
186
145
|
return;
|
|
187
|
-
const definition =
|
|
146
|
+
const definition = leafDefinition(workflow, body, request);
|
|
188
147
|
const lookup = await sandboxLookup(t, workflow.sandboxOwnerId);
|
|
189
148
|
const sandboxOwnerId = sessionSandboxSpec(workflow) || workflow.sandboxOwnerId
|
|
190
149
|
? owningSandboxSessionId(workflow, lookup)
|
|
@@ -208,7 +167,6 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
208
167
|
},
|
|
209
168
|
vaultIds: workflow.vaultIds,
|
|
210
169
|
credentialSelections: workflow.credentialSelections,
|
|
211
|
-
pluginRoots: definition.pluginRoots ?? {},
|
|
212
170
|
...(sandboxOwnerId ? { sandboxOwnerId } : {}),
|
|
213
171
|
...(inherited
|
|
214
172
|
? { sandbox: inherited.spec, sandboxSource: "shared" }
|
|
@@ -218,21 +176,19 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
218
176
|
});
|
|
219
177
|
// Binds this effect to the one linked turn its message opens (`linkedTurnEnd`).
|
|
220
178
|
const idempotencyKey = linkedMessageKey(request);
|
|
221
|
-
const messageInput = body
|
|
179
|
+
const messageInput = linkedMessageInput(body);
|
|
222
180
|
const messageCommand = typeof messageInput === "string"
|
|
223
181
|
? {
|
|
224
182
|
type: "message",
|
|
225
183
|
content: messageInput,
|
|
226
184
|
requestId: `flow-${request.effectId}`,
|
|
227
185
|
idempotencyKey,
|
|
228
|
-
...(body.manifest ? { manifest: body.manifest } : {}),
|
|
229
186
|
}
|
|
230
187
|
: {
|
|
231
188
|
type: "message",
|
|
232
189
|
data: messageInput,
|
|
233
190
|
requestId: `flow-${request.effectId}`,
|
|
234
191
|
idempotencyKey,
|
|
235
|
-
...(body.manifest ? { manifest: body.manifest } : {}),
|
|
236
192
|
};
|
|
237
193
|
const accepted = (await command(ctx, agentSessionId, messageCommand, {
|
|
238
194
|
kind: "application",
|
|
@@ -242,8 +198,8 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
242
198
|
return store.tx(async (t) => {
|
|
243
199
|
const s = await ownedSession(t, lease, request.sessionId);
|
|
244
200
|
const agent = await sessionOf(t, agentSessionId);
|
|
245
|
-
// Only a Loop's
|
|
246
|
-
if (typeof request.context.loopPath === "string")
|
|
201
|
+
// Only a Loop body's agent turns are iterations: not its verifier, nor a step elsewhere.
|
|
202
|
+
if (typeof request.context.loopPath === "string" && request.context.role !== "verify-agent")
|
|
247
203
|
await t.event(s.id, s.activeTurnId, "loop.iteration", {
|
|
248
204
|
path: request.context.loopPath,
|
|
249
205
|
n,
|
|
@@ -260,11 +216,10 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
260
216
|
// May already be settled if the agent was fast / replayed.
|
|
261
217
|
const outcome = await linkedOutcome(t, { request, agentSessionId }, agent);
|
|
262
218
|
if (outcome) {
|
|
263
|
-
await
|
|
264
|
-
|
|
265
|
-
status: "
|
|
219
|
+
await settleAgentEffect({
|
|
220
|
+
t,
|
|
221
|
+
effect: { request, status: "pending", agentSessionId },
|
|
266
222
|
outcome,
|
|
267
|
-
agentSessionId,
|
|
268
223
|
});
|
|
269
224
|
return { status: "completed", outcome };
|
|
270
225
|
}
|
|
@@ -286,30 +241,19 @@ function inheritedSandbox(owner, definition) {
|
|
|
286
241
|
throw new Error(sandboxed.message);
|
|
287
242
|
return { spec, manifest: sandboxed.manifest, manifestHash: sandboxed.manifestHash };
|
|
288
243
|
}
|
|
289
|
-
/**
|
|
244
|
+
/**
|
|
245
|
+
* The credential of an MCP server or HTTP tool request made on behalf of a session: from its
|
|
246
|
+
* attached vaults, else the operator's credential resolver (`vault/sources.ts`).
|
|
247
|
+
*/
|
|
290
248
|
export async function authorize(ctx, sessionId, request) {
|
|
291
249
|
const s = await loadSession(ctx, sessionId);
|
|
292
|
-
|
|
293
|
-
sessionId,
|
|
294
|
-
vaultIds: s.vaultIds ?? [],
|
|
295
|
-
credentialSelections: s.credentialSelections ?? [],
|
|
296
|
-
url: request.url,
|
|
297
|
-
serverName: request.serverName,
|
|
298
|
-
});
|
|
299
|
-
if (result.status === "authorized") {
|
|
300
|
-
const token = result.headers.authorization.slice("Bearer ".length);
|
|
301
|
-
scrub({ authorization: result.headers.authorization, url: result.url }, [
|
|
302
|
-
token,
|
|
303
|
-
]);
|
|
304
|
-
}
|
|
305
|
-
return result;
|
|
250
|
+
return ctx.credentials.authorize({ ...s, id: sessionId }, request);
|
|
306
251
|
}
|
|
307
252
|
/**
|
|
308
|
-
* The definition a flow leaf's session runs. A
|
|
309
|
-
*
|
|
310
|
-
* registry by id.
|
|
253
|
+
* The definition a flow leaf's session runs. A flow agent embeds its leaves, so they come
|
|
254
|
+
* from the workflow's own manifest and can't drift from it.
|
|
311
255
|
*/
|
|
312
|
-
|
|
256
|
+
function leafDefinition(workflow, body, request) {
|
|
313
257
|
// A flow agent used as a tool: its manifest is inlined in the parent's pinned tool.
|
|
314
258
|
if (request.context.role === "delegate" && !isWorkflowManifest(workflow.manifest)) {
|
|
315
259
|
const flow = flowDelegateManifest(turnManifestOf(workflow), body.agentId);
|
|
@@ -319,35 +263,17 @@ async function leafDefinition(t, workflow, body, request) {
|
|
|
319
263
|
manifest: flow,
|
|
320
264
|
manifestHash: hashManifest(flow),
|
|
321
265
|
implementationVersion: workflow.implementationVersion,
|
|
322
|
-
pluginRoots: leafPluginRoots(workflow.pluginRoots, body.agentId),
|
|
323
266
|
};
|
|
324
267
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
const definition = await t.get("definitions", body.agentId);
|
|
338
|
-
if (!definition)
|
|
339
|
-
fail(404, "Definition not found");
|
|
340
|
-
return definition;
|
|
341
|
-
}
|
|
342
|
-
/**
|
|
343
|
-
* A v2 workflow keys its leaves' plugin roots `<agentId>/<capability>`, and an agent keys
|
|
344
|
-
* a flow agent it uses as a tool the same way (`<flowId>/<leafId>/<capability>`).
|
|
345
|
-
*/
|
|
346
|
-
function leafPluginRoots(roots, agentId) {
|
|
347
|
-
const own = {};
|
|
348
|
-
const prefix = `${agentId}/`;
|
|
349
|
-
for (const [key, root] of Object.entries(roots ?? {}))
|
|
350
|
-
if (key.startsWith(prefix))
|
|
351
|
-
own[key.slice(prefix.length)] = root;
|
|
352
|
-
return own;
|
|
268
|
+
const leaf = isWorkflowManifest(workflow.manifest)
|
|
269
|
+
? embeddedAgent(workflow.manifest, body.flow ?? [], body.agentId)
|
|
270
|
+
: undefined;
|
|
271
|
+
if (!leaf || isWorkflowManifest(leaf))
|
|
272
|
+
return fail(404, `Agent '${body.agentId}' is not embedded in workflow '${workflow.manifest.id}'`);
|
|
273
|
+
const manifest = leaf;
|
|
274
|
+
return {
|
|
275
|
+
manifest,
|
|
276
|
+
manifestHash: hashManifest(manifest),
|
|
277
|
+
implementationVersion: workflow.implementationVersion,
|
|
278
|
+
};
|
|
353
279
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { type PostgresClient } from "../store/postgres/connect.js";
|
|
2
|
+
import type { TrustedIssuerConfig } from "./identity-file.js";
|
|
3
|
+
import { type TrustedIssuers } from "./issuers.js";
|
|
2
4
|
import type { Logger, TenantModelConfig } from "./types.js";
|
|
5
|
+
import type { ResolverConfig } from "../vault/sources.js";
|
|
3
6
|
export interface StartEphemeralRuntimeOptions {
|
|
4
7
|
/**
|
|
5
8
|
* Absolute Host root for `host.json`, the admin key and the Tenant's files (vault key,
|
|
@@ -12,6 +15,11 @@ export interface StartEphemeralRuntimeOptions {
|
|
|
12
15
|
name?: string;
|
|
13
16
|
/** An application key for an application principal of the Tenant. Default: a new key. */
|
|
14
17
|
applicationKey?: string;
|
|
18
|
+
/**
|
|
19
|
+
* A management key (protocol 8), registered as the key `bootstrap` like
|
|
20
|
+
* `NYLORUN_MANAGEMENT_KEY_FILE`. Default: a new key.
|
|
21
|
+
*/
|
|
22
|
+
managementKey?: string;
|
|
15
23
|
adminKey?: string;
|
|
16
24
|
principalId?: string;
|
|
17
25
|
/**
|
|
@@ -19,16 +27,21 @@ export interface StartEphemeralRuntimeOptions {
|
|
|
19
27
|
* admin key derives (`deriveStudioToken` in `@nylorun/admin`).
|
|
20
28
|
*/
|
|
21
29
|
studioCredentialHash?: string;
|
|
22
|
-
/** Allowlisted baseline for childEnv (e.g. PATH). Never read from ambient here. */
|
|
23
|
-
baseline?: Readonly<Record<string, string>>;
|
|
24
30
|
model?: TenantModelConfig;
|
|
25
31
|
sandboxBackend?: "auto" | "virtual";
|
|
26
32
|
/** When true, close() leaves hostRoot on disk. */
|
|
27
33
|
retainRoot?: boolean;
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Trusted issuers whose JWTs the Tenant API accepts (Host feature `trusted-issuers`): the
|
|
36
|
+
* issuers of an identity file (`parseIdentityFile`), or ones already built with
|
|
37
|
+
* `createTrustedIssuers`. Default none.
|
|
38
|
+
*/
|
|
39
|
+
issuers?: readonly TrustedIssuerConfig[] | TrustedIssuers;
|
|
40
|
+
/**
|
|
41
|
+
* The operator's credential resolver (F9 C1): asked for a person's MCP credential when the
|
|
42
|
+
* session's vaults hold none. Default: none (vaults only).
|
|
43
|
+
*/
|
|
44
|
+
resolver?: ResolverConfig;
|
|
32
45
|
logger?: Logger;
|
|
33
46
|
/**
|
|
34
47
|
* The Postgres database of the Tenant (one Tenant per database): a pool, which the caller
|
|
@@ -40,10 +53,10 @@ export interface StartEphemeralRuntimeOptions {
|
|
|
40
53
|
}
|
|
41
54
|
export interface EphemeralRuntime {
|
|
42
55
|
url: string;
|
|
43
|
-
/** Where the Admin API answers: `url`, or the operator listener when requested. */
|
|
44
|
-
adminUrl: string;
|
|
45
56
|
tenantId: string;
|
|
46
57
|
applicationKey: string;
|
|
58
|
+
/** The Management API's key (`/v1/tenant/*`), principal `bootstrap`. */
|
|
59
|
+
managementKey: string;
|
|
47
60
|
adminKey: string;
|
|
48
61
|
principalId: string;
|
|
49
62
|
hostRoot: string;
|
package/dist/tenant/ephemeral.js
CHANGED
|
@@ -24,6 +24,7 @@ import { createTenantLogger } from "./logger.js";
|
|
|
24
24
|
import { hostPaths, tenantPaths } from "./paths.js";
|
|
25
25
|
import { hostPrincipals } from "./principals.js";
|
|
26
26
|
import { openTenantRuntime } from "./runtime.js";
|
|
27
|
+
import { createTrustedIssuers } from "./issuers.js";
|
|
27
28
|
const nodeRequire = createRequire(import.meta.url);
|
|
28
29
|
function coreVersion() {
|
|
29
30
|
try {
|
|
@@ -64,8 +65,12 @@ export async function startEphemeralRuntime(options) {
|
|
|
64
65
|
warn() { },
|
|
65
66
|
error() { },
|
|
66
67
|
};
|
|
67
|
-
const baseline = options.baseline ?? {};
|
|
68
68
|
const model = options.model ?? { kind: "scripted", output: "ok" };
|
|
69
|
+
const issuers = options.issuers === undefined
|
|
70
|
+
? undefined
|
|
71
|
+
: Array.isArray(options.issuers)
|
|
72
|
+
? createTrustedIssuers(options.issuers)
|
|
73
|
+
: options.issuers;
|
|
69
74
|
const configFor = (tenantId) => {
|
|
70
75
|
const tenant = tenantPaths(hostRoot);
|
|
71
76
|
// A seeded `sandbox.backend` setting overrides this when the Tenant opens.
|
|
@@ -76,11 +81,8 @@ export async function startEphemeralRuntime(options) {
|
|
|
76
81
|
paths: tenant,
|
|
77
82
|
sandbox: { backend: sandboxBackend },
|
|
78
83
|
model,
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
HOME: tenant.home,
|
|
82
|
-
TMPDIR: tenant.tmp,
|
|
83
|
-
}),
|
|
84
|
+
...(issuers ? { issuers } : {}),
|
|
85
|
+
...(options.resolver ? { resolver: options.resolver } : {}),
|
|
84
86
|
logger: createTenantLogger({
|
|
85
87
|
tenantId,
|
|
86
88
|
logPath: tenant.log,
|
|
@@ -92,6 +94,7 @@ export async function startEphemeralRuntime(options) {
|
|
|
92
94
|
? createPostgresClient(options.database)
|
|
93
95
|
: undefined;
|
|
94
96
|
const applicationKey = options.applicationKey ?? mintBearerToken();
|
|
97
|
+
const managementKey = options.managementKey ?? mintBearerToken();
|
|
95
98
|
const principalId = options.principalId ?? `principal_${randomBytes(8).toString("hex")}`;
|
|
96
99
|
const module = createTenantModule({
|
|
97
100
|
open: createPostgresTenantOpener({
|
|
@@ -103,6 +106,7 @@ export async function startEphemeralRuntime(options) {
|
|
|
103
106
|
principals: hostPrincipals({
|
|
104
107
|
adminKey,
|
|
105
108
|
application: { principalId, key: applicationKey },
|
|
109
|
+
bootstrapKey: managementKey,
|
|
106
110
|
...(options.studioCredentialHash
|
|
107
111
|
? { studioCredentialHash: options.studioCredentialHash }
|
|
108
112
|
: {}),
|
|
@@ -139,13 +143,8 @@ export async function startEphemeralRuntime(options) {
|
|
|
139
143
|
hostRoot,
|
|
140
144
|
module,
|
|
141
145
|
config: hostConfig,
|
|
142
|
-
credentials,
|
|
143
146
|
logger,
|
|
144
147
|
coreVersion: coreVersion(),
|
|
145
|
-
browserAccess: options.browserAccess === true,
|
|
146
|
-
...(options.operatorListener
|
|
147
|
-
? { operator: { host: "127.0.0.1", port: 0 } }
|
|
148
|
-
: {}),
|
|
149
148
|
});
|
|
150
149
|
// Opens the Tenant: creates it in the database first when it holds none.
|
|
151
150
|
await host.listen();
|
|
@@ -166,9 +165,9 @@ export async function startEphemeralRuntime(options) {
|
|
|
166
165
|
let closed = false;
|
|
167
166
|
return {
|
|
168
167
|
url: started.url,
|
|
169
|
-
adminUrl: started.adminUrl,
|
|
170
168
|
tenantId,
|
|
171
169
|
applicationKey,
|
|
170
|
+
managementKey,
|
|
172
171
|
adminKey,
|
|
173
172
|
principalId,
|
|
174
173
|
hostRoot,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* The host token is accepted only by the Harness API listener (a connection serving that one
|
|
18
18
|
* sandbox, and `host.renew`); the egress token only by egress-gate, as the proxy credential
|
|
19
19
|
* (`Proxy-Authorization`). Neither is a bearer anywhere else: the Tenant API's bearer check
|
|
20
|
-
* accepts
|
|
20
|
+
* accepts trusted issuers' tokens only, and the gates run tokens only.
|
|
21
21
|
*
|
|
22
22
|
* Verification reads the key row on every call (a revoked key refuses at once) and, with
|
|
23
23
|
* `currentHost`, the sandbox's row (one indexed read): the epoch must be the sandbox's, the
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import { randomUUID } from "node:crypto";
|
|
27
27
|
import { errors, importJWK, jwtVerify } from "jose";
|
|
28
|
-
import {
|
|
28
|
+
import { tenantTokenIssuer } from "@nylorun/core/contracts";
|
|
29
29
|
import { CLOCK_TOLERANCE_SECONDS, tokenKeyId } from "./jwt.js";
|
|
30
30
|
export const HOST_TOKEN_TYP = "nylorun-host+jwt";
|
|
31
31
|
export const HOST_TOKEN_AUD = "nylorun-harness-api";
|
|
@@ -52,7 +52,7 @@ export async function mintEgressToken(signer, request) {
|
|
|
52
52
|
const { token } = await signer.keys.sign({
|
|
53
53
|
typ: EGRESS_TOKEN_TYP,
|
|
54
54
|
claims: {
|
|
55
|
-
iss:
|
|
55
|
+
iss: tenantTokenIssuer(tenantId),
|
|
56
56
|
aud: EGRESS_TOKEN_AUD,
|
|
57
57
|
sbx: sandboxId,
|
|
58
58
|
epc: epoch,
|
|
@@ -73,7 +73,7 @@ export async function mintHostTokens(signer, host) {
|
|
|
73
73
|
signer.keys.sign({
|
|
74
74
|
typ: HOST_TOKEN_TYP,
|
|
75
75
|
claims: {
|
|
76
|
-
iss:
|
|
76
|
+
iss: tenantTokenIssuer(tenantId),
|
|
77
77
|
aud: HOST_TOKEN_AUD,
|
|
78
78
|
sub: host.sandboxId,
|
|
79
79
|
sbx: host.sandboxId,
|
|
@@ -126,7 +126,7 @@ export async function verifyHostToken(store, tenantId, raw, kind, cache = new Ma
|
|
|
126
126
|
try {
|
|
127
127
|
const verified = await jwtVerify(raw, key, {
|
|
128
128
|
algorithms: ["ES256"],
|
|
129
|
-
issuer:
|
|
129
|
+
issuer: tenantTokenIssuer(tenantId),
|
|
130
130
|
audience: aud,
|
|
131
131
|
typ,
|
|
132
132
|
clockTolerance: CLOCK_TOLERANCE_SECONDS,
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The identity file (Host feature `trusted-issuers`, F9 I2): the YAML file
|
|
3
|
+
* `NYLORUN_IDENTITY_FILE` names, listing the issuers whose JWTs the Tenant API accepts as
|
|
4
|
+
* bearers. Read once at boot (`host/main.ts`); a change takes a restart. A malformed file stops
|
|
5
|
+
* the boot with a message naming the issuer and the field.
|
|
6
|
+
*
|
|
7
|
+
* ```yaml
|
|
8
|
+
* issuers:
|
|
9
|
+
* - name: keycloak
|
|
10
|
+
* issuer: https://sso.acme.dev/realms/eng
|
|
11
|
+
* audience: nylorun
|
|
12
|
+
* jwks: https://sso.acme.dev/realms/eng/protocol/openid-connect/certs # or keys: [<PEM>]
|
|
13
|
+
* subject: "u:{sub}" # scalar claims only
|
|
14
|
+
* scopes: { claim: nylorun_scopes } # or { fixed: [sessions:own, agents:read] }
|
|
15
|
+
* allowedScopes: [agents:read, sessions:own, sandboxes:write, studio]
|
|
16
|
+
* agents: [support] # optional allowlist; absent means all
|
|
17
|
+
* sandboxes: ["{org_id}/*"] # optional grants; absent reaches none
|
|
18
|
+
* maxLifetime: 15m
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
import { type KeyObject } from "node:crypto";
|
|
22
|
+
import { type IssuerScope } from "@nylorun/core/contracts";
|
|
23
|
+
/** The JWT algorithms an issuer may sign with. */
|
|
24
|
+
export declare const ISSUER_ALGORITHMS: readonly ["RS256", "ES256", "EdDSA"];
|
|
25
|
+
export type IssuerAlgorithm = (typeof ISSUER_ALGORITHMS)[number];
|
|
26
|
+
/** A public key an issuer signs with, and the one algorithm it verifies. */
|
|
27
|
+
export interface IssuerKey {
|
|
28
|
+
readonly key: KeyObject;
|
|
29
|
+
readonly alg: IssuerAlgorithm;
|
|
30
|
+
readonly kid?: string;
|
|
31
|
+
}
|
|
32
|
+
/** One trusted issuer, as the identity file declares it. */
|
|
33
|
+
export interface TrustedIssuerConfig {
|
|
34
|
+
/** Its name: `issuer:<name>` in `GET /v1/me` and the token's role. */
|
|
35
|
+
readonly name: string;
|
|
36
|
+
/** The `iss` its tokens carry, exactly. */
|
|
37
|
+
readonly issuer: string;
|
|
38
|
+
/** The `aud` its tokens must carry. */
|
|
39
|
+
readonly audience: string;
|
|
40
|
+
/** Where its public keys are fetched (a JWKS URL); or `keys`. */
|
|
41
|
+
readonly jwks?: string;
|
|
42
|
+
/** Its public keys, given in the file (PEM); or `jwks`. */
|
|
43
|
+
readonly keys?: readonly IssuerKey[];
|
|
44
|
+
/** The subject template, such as `u:{sub}`: at least one claim reference. */
|
|
45
|
+
readonly subject: string;
|
|
46
|
+
/** Where its tokens' scopes come from: a claim, or a fixed list. */
|
|
47
|
+
readonly scopes: {
|
|
48
|
+
readonly claim: string;
|
|
49
|
+
} | {
|
|
50
|
+
readonly fixed: readonly IssuerScope[];
|
|
51
|
+
};
|
|
52
|
+
/** The scopes its tokens may carry; others are dropped. */
|
|
53
|
+
readonly allowedScopes: readonly IssuerScope[];
|
|
54
|
+
/** The agents its tokens reach; absent reaches all. */
|
|
55
|
+
readonly agents?: readonly string[];
|
|
56
|
+
/** Sandbox grant templates, such as `{org_id}/*`; absent reaches no sandbox. */
|
|
57
|
+
readonly sandboxes?: readonly string[];
|
|
58
|
+
/** The longest `exp - iat` it may sign, in seconds. */
|
|
59
|
+
readonly maxLifetimeSeconds: number;
|
|
60
|
+
}
|
|
61
|
+
/** A malformed identity file: the message names the issuer and the field. */
|
|
62
|
+
export declare class IdentityFileError extends Error {
|
|
63
|
+
constructor(message: string);
|
|
64
|
+
}
|
|
65
|
+
/** The claim names a template references. */
|
|
66
|
+
export declare function claimReferences(template: string): string[];
|
|
67
|
+
/** `template` with every reference replaced by `value(claim)`; undefined when one has none. */
|
|
68
|
+
export declare function renderTemplate(template: string, value: (claim: string) => string | undefined): string | undefined;
|
|
69
|
+
/** A PEM public key an issuer may sign with, and its algorithm; throws a message otherwise. */
|
|
70
|
+
export declare function issuerKey(key: KeyObject, kid?: string): IssuerKey;
|
|
71
|
+
/**
|
|
72
|
+
* The issuers of an identity file's text. Throws `IdentityFileError` naming the file, the issuer
|
|
73
|
+
* and the field when the file is malformed.
|
|
74
|
+
*/
|
|
75
|
+
export declare function parseIdentityFile(text: string, source?: string): TrustedIssuerConfig[];
|