@nylorun/runtime 0.17.0-beta → 0.19.1-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 +155 -0
- package/README.md +89 -58
- 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/routes.js +1 -1
- 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 +4871 -2614
- package/dist/api/components.js +12 -11
- 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 +18 -14
- package/dist/api/http/define.d.ts +14 -2
- package/dist/api/http/define.js +34 -15
- package/dist/api/http/routes/access.d.ts +3 -3
- package/dist/api/http/routes/access.js +18 -17
- package/dist/api/http/routes/artifacts.js +2 -4
- 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.js +5 -2
- 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.js +7 -4
- package/dist/api/http/routes/sessions.js +25 -8
- package/dist/api/http/routes/tenant.js +15 -18
- package/dist/api/http/routes/vaults.d.ts +10 -9
- package/dist/api/http/routes/vaults.js +27 -23
- package/dist/api/openapi.d.ts +10 -11
- package/dist/api/openapi.js +288 -63
- package/dist/artifacts/links.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 +1 -3
- package/dist/gates/meter.d.ts +2 -2
- package/dist/gates/meter.js +6 -0
- package/dist/gates/tenant-vaults.d.ts +5 -9
- 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 -9
- package/dist/host/app.js +15 -25
- package/dist/host/config-for.d.ts +1 -5
- package/dist/host/config-for.js +0 -3
- package/dist/host/config.d.ts +0 -5
- package/dist/host/create-host.d.ts +3 -33
- package/dist/host/create-host.js +18 -94
- 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 +3 -2
- 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 +27 -34
- package/dist/host/operate.d.ts +29 -0
- package/dist/host/operate.js +223 -0
- package/dist/host/stack-config.d.ts +12 -12
- package/dist/host/stack-config.js +5 -32
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/infra/workers.d.ts +1 -1
- package/dist/keys/keys.js +2 -2
- package/dist/management-openapi.json +6491 -0
- package/dist/mcp/connect.d.ts +8 -4
- package/dist/mcp/connect.js +6 -26
- package/dist/mcp/pool.d.ts +5 -9
- package/dist/mcp/pool.js +8 -19
- 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 +9372 -17030
- 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/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 +28 -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 +294 -489
- package/dist/store/postgres/schema.js +41 -76
- package/dist/store/postgres/store.js +51 -154
- package/dist/store/postgres/tenant.d.ts +34 -2
- package/dist/store/postgres/tenant.js +16 -32
- package/dist/store/types.d.ts +27 -96
- package/dist/store/types.js +0 -1
- package/dist/streams/control.d.ts +0 -2
- package/dist/streams/control.js +0 -5
- package/dist/streams/types.d.ts +1 -10
- package/dist/tenant/advance.d.ts +3 -3
- package/dist/tenant/advance.js +10 -7
- package/dist/tenant/auth.d.ts +14 -9
- package/dist/tenant/auth.js +24 -28
- 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 +35 -172
- package/dist/tenant/context.d.ts +14 -24
- package/dist/tenant/definition-files.d.ts +24 -0
- package/dist/tenant/definition-files.js +103 -0
- package/dist/tenant/effects.d.ts +18 -20
- package/dist/tenant/effects.js +56 -119
- package/dist/tenant/ephemeral.d.ts +7 -6
- package/dist/tenant/ephemeral.js +3 -11
- package/dist/tenant/host-token.js +1 -1
- package/dist/tenant/jwt.js +1 -1
- package/dist/tenant/module.d.ts +1 -4
- package/dist/tenant/module.js +3 -12
- package/dist/tenant/operator-keys.d.ts +34 -8
- package/dist/tenant/operator-keys.js +50 -18
- package/dist/tenant/outbound.d.ts +10 -1
- package/dist/tenant/outbound.js +44 -27
- package/dist/tenant/paths.d.ts +1 -1
- package/dist/tenant/paths.js +1 -3
- package/dist/tenant/principals.d.ts +11 -2
- package/dist/tenant/principals.js +27 -8
- package/dist/tenant/run-token.js +3 -3
- package/dist/tenant/runtime.d.ts +4 -6
- package/dist/tenant/runtime.js +19 -18
- package/dist/tenant/sandboxes.d.ts +4 -1
- package/dist/tenant/sandboxes.js +11 -0
- package/dist/tenant/scheduler.d.ts +1 -5
- package/dist/tenant/scheduler.js +2 -5
- package/dist/tenant/session-streams.d.ts +2 -1
- package/dist/tenant/session-streams.js +39 -5
- package/dist/tenant/session.d.ts +8 -41
- package/dist/tenant/session.js +0 -37
- package/dist/tenant/sessions.js +16 -8
- package/dist/tenant/signing-keys.d.ts +3 -3
- 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 -10
- package/dist/tenant/streams.js +1 -24
- 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 +15 -22
- package/dist/tenant/worker.d.ts +1 -3
- package/dist/tenant/worker.js +3 -8
- package/dist/vault/sources.d.ts +7 -1
- package/dist/vault/sources.js +22 -11
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
- package/dist/admin-openapi.json +0 -904
- package/dist/api/http/routes/actions.d.ts +0 -8
- package/dist/api/http/routes/actions.js +0 -75
- 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 -30
- package/dist/host/admin-api.js +0 -140
- package/dist/mcp/stdio.d.ts +0 -17
- package/dist/mcp/stdio.js +0 -82
- package/dist/tenant/actions.d.ts +0 -14
- package/dist/tenant/actions.js +0 -29
- 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/effects.js
CHANGED
|
@@ -1,14 +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
4
|
import { isRemoteMcpCall } from "../harness/calls.js";
|
|
5
|
+
import { isHttpToolCall } from "../gates/http-tool.js";
|
|
5
6
|
import { owningSandboxSessionId, sandboxSpecOf, sessionSandboxSpec, } from "../sandbox/share.js";
|
|
6
7
|
import { withSandboxCapability } from "../sandbox/session-sandbox.js";
|
|
7
8
|
import { loadSession, ownedSession, sandboxLookup, sessionOf, } from "./context.js";
|
|
8
9
|
import { fail } from "./http.js";
|
|
9
|
-
import {
|
|
10
|
+
import { turnManifestOf } from "./session.js";
|
|
10
11
|
import { command } from "./commands.js";
|
|
11
|
-
import { offerAction } from "./delivery.js";
|
|
12
12
|
/**
|
|
13
13
|
* The outcome of a flow `agent` effect once the linked turn it started ended, or undefined
|
|
14
14
|
* while it has not. The linked session may still show an earlier iteration's turn.
|
|
@@ -18,7 +18,7 @@ export async function linkedOutcome(t, effect, agent) {
|
|
|
18
18
|
if (!end || !agent)
|
|
19
19
|
return undefined;
|
|
20
20
|
if (end.status === "completed")
|
|
21
|
-
return { value:
|
|
21
|
+
return { value: end.output };
|
|
22
22
|
return {
|
|
23
23
|
value: {
|
|
24
24
|
kind: "failed",
|
|
@@ -31,16 +31,17 @@ export async function linkedOutcome(t, effect, agent) {
|
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
|
-
* True when this Tenant's remote MCP calls outlive the process that sent them
|
|
35
|
-
* service, F4.1 G3): after a takeover or a shutdown, the journaled call is re-sent
|
|
36
|
-
* 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`.
|
|
37
37
|
*/
|
|
38
|
-
export function
|
|
38
|
+
export function recoversToolCalls(ctx) {
|
|
39
39
|
return ctx.toolGate.recovers === true;
|
|
40
40
|
}
|
|
41
|
-
/** True when `request` calls a tool of a
|
|
42
|
-
export function
|
|
43
|
-
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));
|
|
44
45
|
}
|
|
45
46
|
/**
|
|
46
47
|
* True when this Tenant's vault-backed model calls outlive the process that sent them (the
|
|
@@ -50,7 +51,7 @@ export function isRemoteMcpEffect(s, request) {
|
|
|
50
51
|
export function recoversModelCalls(ctx) {
|
|
51
52
|
return ctx.useVaultModel && ctx.modelGate.recovers === true;
|
|
52
53
|
}
|
|
53
|
-
/** Journal and dispatch a new flow effect (agent
|
|
54
|
+
/** Journal and dispatch a new flow effect (agent or tool node). */
|
|
54
55
|
export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
55
56
|
const { store } = ctx;
|
|
56
57
|
signal.throwIfAborted();
|
|
@@ -75,9 +76,7 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
75
76
|
const outcome = await linkedOutcome(t, existing, agent);
|
|
76
77
|
if (!outcome)
|
|
77
78
|
return resolved({ status: "pending" });
|
|
78
|
-
existing
|
|
79
|
-
existing.outcome = outcome;
|
|
80
|
-
await t.put("effects", request.effectId, existing);
|
|
79
|
+
await settleAgentEffect({ t, effect: existing, outcome });
|
|
81
80
|
return resolved({ status: "completed", outcome });
|
|
82
81
|
}
|
|
83
82
|
else {
|
|
@@ -93,64 +92,25 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
93
92
|
});
|
|
94
93
|
return resolved({ status: "pending" });
|
|
95
94
|
}
|
|
96
|
-
if (request
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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 } : {}),
|
|
102
112
|
});
|
|
103
|
-
|
|
104
|
-
? {
|
|
105
|
-
actionId: request.effectId,
|
|
106
|
-
sessionId: request.sessionId,
|
|
107
|
-
turnId: request.turnId,
|
|
108
|
-
agentId: request.agentId,
|
|
109
|
-
manifestHash: request.manifestHash,
|
|
110
|
-
implementationVersion: workflow.implementationVersion,
|
|
111
|
-
input: request.input,
|
|
112
|
-
context: request.context,
|
|
113
|
-
status: "pending",
|
|
114
|
-
generation: 0,
|
|
115
|
-
kind: request.kind,
|
|
116
|
-
path: request.path,
|
|
117
|
-
key: request.key,
|
|
118
|
-
}
|
|
119
|
-
: {
|
|
120
|
-
actionId: request.effectId,
|
|
121
|
-
sessionId: request.sessionId,
|
|
122
|
-
turnId: request.turnId,
|
|
123
|
-
agentId: request.agentId,
|
|
124
|
-
manifestHash: request.manifestHash,
|
|
125
|
-
implementationVersion: workflow.implementationVersion,
|
|
126
|
-
input: request.input,
|
|
127
|
-
context: request.context,
|
|
128
|
-
status: "pending",
|
|
129
|
-
generation: 0,
|
|
130
|
-
kind: "tool",
|
|
131
|
-
path: request.path,
|
|
132
|
-
key: request.key,
|
|
133
|
-
};
|
|
134
|
-
await t.put("actions", action.actionId, action);
|
|
135
|
-
if (isFlowToolEffect(request)) {
|
|
136
|
-
await t.event(workflow.id, workflow.activeTurnId, "node.started", {
|
|
137
|
-
path: request.path,
|
|
138
|
-
kind: "tool",
|
|
139
|
-
key: request.key,
|
|
140
|
-
...(request.iterations !== undefined
|
|
141
|
-
? { iterations: request.iterations }
|
|
142
|
-
: {}),
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
await t.event(workflow.id, workflow.activeTurnId, "action.pending", {
|
|
146
|
-
actionId: action.actionId,
|
|
147
|
-
kind: action.kind,
|
|
148
|
-
path: action.path,
|
|
149
|
-
key: action.key,
|
|
150
|
-
input: action.input,
|
|
151
|
-
});
|
|
152
|
-
await offerAction(t, ctx, action);
|
|
153
|
-
return resolved({ status: "pending" });
|
|
113
|
+
return resolved({ status: "completed", outcome });
|
|
154
114
|
}
|
|
155
115
|
return { kind: "agent", workflow };
|
|
156
116
|
});
|
|
@@ -183,7 +143,7 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
183
143
|
await ownedSession(t, lease, workflow.id);
|
|
184
144
|
if (exists)
|
|
185
145
|
return;
|
|
186
|
-
const definition =
|
|
146
|
+
const definition = leafDefinition(workflow, body, request);
|
|
187
147
|
const lookup = await sandboxLookup(t, workflow.sandboxOwnerId);
|
|
188
148
|
const sandboxOwnerId = sessionSandboxSpec(workflow) || workflow.sandboxOwnerId
|
|
189
149
|
? owningSandboxSessionId(workflow, lookup)
|
|
@@ -207,7 +167,6 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
207
167
|
},
|
|
208
168
|
vaultIds: workflow.vaultIds,
|
|
209
169
|
credentialSelections: workflow.credentialSelections,
|
|
210
|
-
pluginRoots: definition.pluginRoots ?? {},
|
|
211
170
|
...(sandboxOwnerId ? { sandboxOwnerId } : {}),
|
|
212
171
|
...(inherited
|
|
213
172
|
? { sandbox: inherited.spec, sandboxSource: "shared" }
|
|
@@ -217,21 +176,19 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
217
176
|
});
|
|
218
177
|
// Binds this effect to the one linked turn its message opens (`linkedTurnEnd`).
|
|
219
178
|
const idempotencyKey = linkedMessageKey(request);
|
|
220
|
-
const messageInput = body
|
|
179
|
+
const messageInput = linkedMessageInput(body);
|
|
221
180
|
const messageCommand = typeof messageInput === "string"
|
|
222
181
|
? {
|
|
223
182
|
type: "message",
|
|
224
183
|
content: messageInput,
|
|
225
184
|
requestId: `flow-${request.effectId}`,
|
|
226
185
|
idempotencyKey,
|
|
227
|
-
...(body.manifest ? { manifest: body.manifest } : {}),
|
|
228
186
|
}
|
|
229
187
|
: {
|
|
230
188
|
type: "message",
|
|
231
189
|
data: messageInput,
|
|
232
190
|
requestId: `flow-${request.effectId}`,
|
|
233
191
|
idempotencyKey,
|
|
234
|
-
...(body.manifest ? { manifest: body.manifest } : {}),
|
|
235
192
|
};
|
|
236
193
|
const accepted = (await command(ctx, agentSessionId, messageCommand, {
|
|
237
194
|
kind: "application",
|
|
@@ -241,8 +198,8 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
241
198
|
return store.tx(async (t) => {
|
|
242
199
|
const s = await ownedSession(t, lease, request.sessionId);
|
|
243
200
|
const agent = await sessionOf(t, agentSessionId);
|
|
244
|
-
// Only a Loop's
|
|
245
|
-
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")
|
|
246
203
|
await t.event(s.id, s.activeTurnId, "loop.iteration", {
|
|
247
204
|
path: request.context.loopPath,
|
|
248
205
|
n,
|
|
@@ -259,11 +216,10 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
259
216
|
// May already be settled if the agent was fast / replayed.
|
|
260
217
|
const outcome = await linkedOutcome(t, { request, agentSessionId }, agent);
|
|
261
218
|
if (outcome) {
|
|
262
|
-
await
|
|
263
|
-
|
|
264
|
-
status: "
|
|
219
|
+
await settleAgentEffect({
|
|
220
|
+
t,
|
|
221
|
+
effect: { request, status: "pending", agentSessionId },
|
|
265
222
|
outcome,
|
|
266
|
-
agentSessionId,
|
|
267
223
|
});
|
|
268
224
|
return { status: "completed", outcome };
|
|
269
225
|
}
|
|
@@ -286,19 +242,18 @@ function inheritedSandbox(owner, definition) {
|
|
|
286
242
|
return { spec, manifest: sandboxed.manifest, manifestHash: sandboxed.manifestHash };
|
|
287
243
|
}
|
|
288
244
|
/**
|
|
289
|
-
* The credential of an MCP server request made on behalf of a session: from its
|
|
290
|
-
* else the operator's credential resolver (`vault/sources.ts`).
|
|
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`).
|
|
291
247
|
*/
|
|
292
248
|
export async function authorize(ctx, sessionId, request) {
|
|
293
249
|
const s = await loadSession(ctx, sessionId);
|
|
294
250
|
return ctx.credentials.authorize({ ...s, id: sessionId }, request);
|
|
295
251
|
}
|
|
296
252
|
/**
|
|
297
|
-
* The definition a flow leaf's session runs. A
|
|
298
|
-
*
|
|
299
|
-
* 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.
|
|
300
255
|
*/
|
|
301
|
-
|
|
256
|
+
function leafDefinition(workflow, body, request) {
|
|
302
257
|
// A flow agent used as a tool: its manifest is inlined in the parent's pinned tool.
|
|
303
258
|
if (request.context.role === "delegate" && !isWorkflowManifest(workflow.manifest)) {
|
|
304
259
|
const flow = flowDelegateManifest(turnManifestOf(workflow), body.agentId);
|
|
@@ -308,35 +263,17 @@ async function leafDefinition(t, workflow, body, request) {
|
|
|
308
263
|
manifest: flow,
|
|
309
264
|
manifestHash: hashManifest(flow),
|
|
310
265
|
implementationVersion: workflow.implementationVersion,
|
|
311
|
-
pluginRoots: leafPluginRoots(workflow.pluginRoots, body.agentId),
|
|
312
266
|
};
|
|
313
267
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
}
|
|
326
|
-
const definition = await t.get("definitions", body.agentId);
|
|
327
|
-
if (!definition)
|
|
328
|
-
fail(404, "Definition not found");
|
|
329
|
-
return definition;
|
|
330
|
-
}
|
|
331
|
-
/**
|
|
332
|
-
* A v2 workflow keys its leaves' plugin roots `<agentId>/<capability>`, and an agent keys
|
|
333
|
-
* a flow agent it uses as a tool the same way (`<flowId>/<leafId>/<capability>`).
|
|
334
|
-
*/
|
|
335
|
-
function leafPluginRoots(roots, agentId) {
|
|
336
|
-
const own = {};
|
|
337
|
-
const prefix = `${agentId}/`;
|
|
338
|
-
for (const [key, root] of Object.entries(roots ?? {}))
|
|
339
|
-
if (key.startsWith(prefix))
|
|
340
|
-
own[key.slice(prefix.length)] = root;
|
|
341
|
-
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
|
+
};
|
|
342
279
|
}
|
|
@@ -15,6 +15,11 @@ export interface StartEphemeralRuntimeOptions {
|
|
|
15
15
|
name?: string;
|
|
16
16
|
/** An application key for an application principal of the Tenant. Default: a new key. */
|
|
17
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;
|
|
18
23
|
adminKey?: string;
|
|
19
24
|
principalId?: string;
|
|
20
25
|
/**
|
|
@@ -22,14 +27,10 @@ export interface StartEphemeralRuntimeOptions {
|
|
|
22
27
|
* admin key derives (`deriveStudioToken` in `@nylorun/admin`).
|
|
23
28
|
*/
|
|
24
29
|
studioCredentialHash?: string;
|
|
25
|
-
/** Allowlisted baseline for childEnv (e.g. PATH). Never read from ambient here. */
|
|
26
|
-
baseline?: Readonly<Record<string, string>>;
|
|
27
30
|
model?: TenantModelConfig;
|
|
28
31
|
sandboxBackend?: "auto" | "virtual";
|
|
29
32
|
/** When true, close() leaves hostRoot on disk. */
|
|
30
33
|
retainRoot?: boolean;
|
|
31
|
-
/** Serve the Admin API on its own loopback listener (`adminUrl`). Default off. */
|
|
32
|
-
operatorListener?: boolean;
|
|
33
34
|
/**
|
|
34
35
|
* Trusted issuers whose JWTs the Tenant API accepts (Host feature `trusted-issuers`): the
|
|
35
36
|
* issuers of an identity file (`parseIdentityFile`), or ones already built with
|
|
@@ -52,10 +53,10 @@ export interface StartEphemeralRuntimeOptions {
|
|
|
52
53
|
}
|
|
53
54
|
export interface EphemeralRuntime {
|
|
54
55
|
url: string;
|
|
55
|
-
/** Where the Admin API answers: `url`, or the operator listener when requested. */
|
|
56
|
-
adminUrl: string;
|
|
57
56
|
tenantId: string;
|
|
58
57
|
applicationKey: string;
|
|
58
|
+
/** The Management API's key (`/v1/tenant/*`), principal `bootstrap`. */
|
|
59
|
+
managementKey: string;
|
|
59
60
|
adminKey: string;
|
|
60
61
|
principalId: string;
|
|
61
62
|
hostRoot: string;
|
package/dist/tenant/ephemeral.js
CHANGED
|
@@ -65,7 +65,6 @@ export async function startEphemeralRuntime(options) {
|
|
|
65
65
|
warn() { },
|
|
66
66
|
error() { },
|
|
67
67
|
};
|
|
68
|
-
const baseline = options.baseline ?? {};
|
|
69
68
|
const model = options.model ?? { kind: "scripted", output: "ok" };
|
|
70
69
|
const issuers = options.issuers === undefined
|
|
71
70
|
? undefined
|
|
@@ -84,11 +83,6 @@ export async function startEphemeralRuntime(options) {
|
|
|
84
83
|
model,
|
|
85
84
|
...(issuers ? { issuers } : {}),
|
|
86
85
|
...(options.resolver ? { resolver: options.resolver } : {}),
|
|
87
|
-
childEnv: Object.freeze({
|
|
88
|
-
...baseline,
|
|
89
|
-
HOME: tenant.home,
|
|
90
|
-
TMPDIR: tenant.tmp,
|
|
91
|
-
}),
|
|
92
86
|
logger: createTenantLogger({
|
|
93
87
|
tenantId,
|
|
94
88
|
logPath: tenant.log,
|
|
@@ -100,6 +94,7 @@ export async function startEphemeralRuntime(options) {
|
|
|
100
94
|
? createPostgresClient(options.database)
|
|
101
95
|
: undefined;
|
|
102
96
|
const applicationKey = options.applicationKey ?? mintBearerToken();
|
|
97
|
+
const managementKey = options.managementKey ?? mintBearerToken();
|
|
103
98
|
const principalId = options.principalId ?? `principal_${randomBytes(8).toString("hex")}`;
|
|
104
99
|
const module = createTenantModule({
|
|
105
100
|
open: createPostgresTenantOpener({
|
|
@@ -111,6 +106,7 @@ export async function startEphemeralRuntime(options) {
|
|
|
111
106
|
principals: hostPrincipals({
|
|
112
107
|
adminKey,
|
|
113
108
|
application: { principalId, key: applicationKey },
|
|
109
|
+
bootstrapKey: managementKey,
|
|
114
110
|
...(options.studioCredentialHash
|
|
115
111
|
? { studioCredentialHash: options.studioCredentialHash }
|
|
116
112
|
: {}),
|
|
@@ -147,12 +143,8 @@ export async function startEphemeralRuntime(options) {
|
|
|
147
143
|
hostRoot,
|
|
148
144
|
module,
|
|
149
145
|
config: hostConfig,
|
|
150
|
-
credentials,
|
|
151
146
|
logger,
|
|
152
147
|
coreVersion: coreVersion(),
|
|
153
|
-
...(options.operatorListener
|
|
154
|
-
? { operator: { host: "127.0.0.1", port: 0 } }
|
|
155
|
-
: {}),
|
|
156
148
|
});
|
|
157
149
|
// Opens the Tenant: creates it in the database first when it holds none.
|
|
158
150
|
await host.listen();
|
|
@@ -173,9 +165,9 @@ export async function startEphemeralRuntime(options) {
|
|
|
173
165
|
let closed = false;
|
|
174
166
|
return {
|
|
175
167
|
url: started.url,
|
|
176
|
-
adminUrl: started.adminUrl,
|
|
177
168
|
tenantId,
|
|
178
169
|
applicationKey,
|
|
170
|
+
managementKey,
|
|
179
171
|
adminKey,
|
|
180
172
|
principalId,
|
|
181
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 trusted issuers'
|
|
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
|
package/dist/tenant/jwt.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* What the Runtime's own tokens share (
|
|
2
|
+
* What the Runtime's own tokens share (capability links, run and host tokens):
|
|
3
3
|
* the Tenant's ES256 JWTs, recognised by shape, told apart by their `typ`, and refused before
|
|
4
4
|
* any key is read when the header is not one the Runtime issues.
|
|
5
5
|
*/
|
package/dist/tenant/module.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { StreamRelayStatus } from "@nylorun/core/contracts";
|
|
2
1
|
import { TenantUnavailableError, type Logger, type TenantHandle, type TenantModule, type TenantOpener } from "./types.js";
|
|
3
2
|
export interface CreateTenantModuleOptions {
|
|
4
3
|
/** Opens the Host's Tenant (`store-pg.ts`). */
|
|
@@ -11,15 +10,13 @@ export interface CreateTenantModuleOptions {
|
|
|
11
10
|
* Tenant's id (its stream relay). A failure is logged; the Tenant stays open.
|
|
12
11
|
*/
|
|
13
12
|
onOpen?: (handle: TenantHandle) => void | Promise<void>;
|
|
14
|
-
/** This process's stream relay, for the Host aggregate (a Host with S2 runs one). */
|
|
15
|
-
relayStatus?: () => Promise<StreamRelayStatus>;
|
|
16
13
|
}
|
|
17
14
|
/**
|
|
18
15
|
* Deep Tenant module (§8): the Host's one Tenant over an injected opener (tenancy.md §5).
|
|
19
16
|
*
|
|
20
17
|
* `start` opens it once, even when requests or Worker calls ask at the same time. An open that
|
|
21
18
|
* fails for a reason in the Tenant (a `TenantOpenError`, or a timeout) is kept as the Tenant's
|
|
22
|
-
* cause: the Host is not ready,
|
|
19
|
+
* cause: the Host is not ready, `nylorun-operate status` names it, requests get the opaque 404.
|
|
23
20
|
* One that fails outside the Tenant (`TenantUnavailableError`) is retried in the background
|
|
24
21
|
* with backoff, and by the next request, which gets 503 meanwhile.
|
|
25
22
|
*/
|
package/dist/tenant/module.js
CHANGED
|
@@ -10,7 +10,7 @@ const RETRY_MAX_MS = 30_000;
|
|
|
10
10
|
*
|
|
11
11
|
* `start` opens it once, even when requests or Worker calls ask at the same time. An open that
|
|
12
12
|
* fails for a reason in the Tenant (a `TenantOpenError`, or a timeout) is kept as the Tenant's
|
|
13
|
-
* cause: the Host is not ready,
|
|
13
|
+
* cause: the Host is not ready, `nylorun-operate status` names it, requests get the opaque 404.
|
|
14
14
|
* One that fails outside the Tenant (`TenantUnavailableError`) is retried in the background
|
|
15
15
|
* with backoff, and by the next request, which gets 503 meanwhile.
|
|
16
16
|
*/
|
|
@@ -148,18 +148,9 @@ export function createTenantModule(options) {
|
|
|
148
148
|
return { id: null, name: null, state: "unavailable", envelope: null };
|
|
149
149
|
}
|
|
150
150
|
},
|
|
151
|
-
|
|
151
|
+
harnessStatus() {
|
|
152
152
|
const now = current();
|
|
153
|
-
|
|
154
|
-
const relay = await options.relayStatus?.().catch(() => undefined);
|
|
155
|
-
return {
|
|
156
|
-
runningSessions: summary?.runningSessions ?? 0,
|
|
157
|
-
inFlightDeliveries: summary?.inFlightDeliveries ?? 0,
|
|
158
|
-
pendingActions: summary?.pendingActions ?? 0,
|
|
159
|
-
uncertainEffects: summary?.uncertainEffects ?? 0,
|
|
160
|
-
...(relay ? { relay } : {}),
|
|
161
|
-
...(summary?.harness ? { harness: summary.harness } : {}),
|
|
162
|
-
};
|
|
153
|
+
return now.kind === "open" ? now.handle.harnessStatus?.() : undefined;
|
|
163
154
|
},
|
|
164
155
|
async close() {
|
|
165
156
|
const previous = current();
|
|
@@ -1,18 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Tenant's keys by name (F9 I1), managed through the Management API (`/v1/tenant/keys`,
|
|
3
|
+
* application keys only) and `nylorun-operate keys`. A key is a principal (F9-D11): its id is the key's name
|
|
4
|
+
* (`APPLICATION_KEY_ID_PATTERN`), the key is 64 hex characters (`mintBearerToken`), and the
|
|
5
|
+
* Tenant keeps only its SHA-256. Putting a key creates the principal or rotates its key;
|
|
6
|
+
* deleting it removes the row. Either way the old key stops authenticating on its next request,
|
|
7
|
+
* since authentication reads the principal on every request (`auth.ts`).
|
|
8
|
+
*
|
|
9
|
+
* `studio` is derived from the admin key (F9-D9): it is never put or deleted here. `bootstrap` is
|
|
10
|
+
* the management key a Host registers from `NYLORUN_MANAGEMENT_KEY_FILE`: its file is its only
|
|
11
|
+
* source. A key has a role (protocol 8): `application` reaches the Runtime API, `management` the
|
|
12
|
+
* Management API; a key keeps its role when it is rotated.
|
|
13
|
+
*/
|
|
14
|
+
import { type KeyRole } from "@nylorun/core/compatibility";
|
|
1
15
|
import type { OperatorKey, PutOperatorKeyResponse } from "@nylorun/core/contracts";
|
|
2
16
|
import type { SessionStore } from "../store/types.js";
|
|
3
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Why a key is refused: not a key name, a reserved id (`studio`, `bootstrap`), or an id that
|
|
19
|
+
* holds the other role (`role`).
|
|
20
|
+
*/
|
|
4
21
|
export type OperatorKeyRefusal = {
|
|
5
|
-
reason: "invalid" | "reserved";
|
|
22
|
+
reason: "invalid" | "reserved" | "role";
|
|
6
23
|
message: string;
|
|
7
24
|
};
|
|
8
|
-
/**
|
|
9
|
-
|
|
25
|
+
/**
|
|
26
|
+
* The refusal for `id`, or undefined when it may be `put` or deleted. `bootstrap` is never put
|
|
27
|
+
* here (its file is its only source) but may be deleted on the machine (AP18).
|
|
28
|
+
*/
|
|
29
|
+
export declare function refuseOperatorKeyId(id: string, purpose?: "put" | "delete"): OperatorKeyRefusal | undefined;
|
|
10
30
|
export interface OperatorKeys {
|
|
11
31
|
/** Every principal of the Tenant, by id (Studio's and derived ones included). */
|
|
12
32
|
list(): Promise<OperatorKey[]>;
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Creates key `id` with `role` (default `application`) or rotates it; the key is returned
|
|
35
|
+
* this once. An id that holds the other role is refused.
|
|
36
|
+
*/
|
|
37
|
+
put(id: string, role?: Exclude<KeyRole, "studio">): Promise<PutOperatorKeyResponse | OperatorKeyRefusal>;
|
|
38
|
+
/**
|
|
39
|
+
* Deletes key `id`: false when there is none. With `role`, a key of another role is refused
|
|
40
|
+
* (the Management API deletes application keys only).
|
|
41
|
+
*/
|
|
42
|
+
delete(id: string, role?: Exclude<KeyRole, "studio">): Promise<boolean | OperatorKeyRefusal>;
|
|
17
43
|
}
|
|
18
44
|
export declare function operatorKeys(store: SessionStore, now?: () => string): OperatorKeys;
|
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* The Tenant's keys by name (F9 I1), managed through the Management API (`/v1/tenant/keys`,
|
|
3
|
+
* application keys only) and `nylorun-operate keys`. A key is a principal (F9-D11): its id is the key's name
|
|
4
4
|
* (`APPLICATION_KEY_ID_PATTERN`), the key is 64 hex characters (`mintBearerToken`), and the
|
|
5
5
|
* Tenant keeps only its SHA-256. Putting a key creates the principal or rotates its key;
|
|
6
6
|
* deleting it removes the row. Either way the old key stops authenticating on its next request,
|
|
7
7
|
* since authentication reads the principal on every request (`auth.ts`).
|
|
8
8
|
*
|
|
9
|
-
* `studio` is derived from the admin key (F9-D9): it is never put or deleted here.
|
|
9
|
+
* `studio` is derived from the admin key (F9-D9): it is never put or deleted here. `bootstrap` is
|
|
10
|
+
* the management key a Host registers from `NYLORUN_MANAGEMENT_KEY_FILE`: its file is its only
|
|
11
|
+
* source. A key has a role (protocol 8): `application` reaches the Runtime API, `management` the
|
|
12
|
+
* Management API; a key keeps its role when it is rotated.
|
|
10
13
|
*/
|
|
11
|
-
import { APPLICATION_KEY_ID_PATTERN } from "@nylorun/core/compatibility";
|
|
14
|
+
import { APPLICATION_KEY_ID_PATTERN, BOOTSTRAP_KEY_ID, } from "@nylorun/core/compatibility";
|
|
12
15
|
import { hashToken, mintBearerToken } from "../core/bearer.js";
|
|
16
|
+
import { PrincipalRoleConflict } from "../store/ownership.js";
|
|
13
17
|
import { STUDIO_PRINCIPAL_ID } from "./principals.js";
|
|
14
|
-
/**
|
|
15
|
-
|
|
18
|
+
/**
|
|
19
|
+
* The refusal for `id`, or undefined when it may be `put` or deleted. `bootstrap` is never put
|
|
20
|
+
* here (its file is its only source) but may be deleted on the machine (AP18).
|
|
21
|
+
*/
|
|
22
|
+
export function refuseOperatorKeyId(id, purpose = "put") {
|
|
16
23
|
if (!APPLICATION_KEY_ID_PATTERN.test(id))
|
|
17
24
|
return {
|
|
18
25
|
reason: "invalid",
|
|
@@ -23,6 +30,11 @@ export function refuseOperatorKeyId(id) {
|
|
|
23
30
|
reason: "reserved",
|
|
24
31
|
message: "The studio key is derived from the admin key: it is not put or deleted here",
|
|
25
32
|
};
|
|
33
|
+
if (id === BOOTSTRAP_KEY_ID && purpose === "put")
|
|
34
|
+
return {
|
|
35
|
+
reason: "reserved",
|
|
36
|
+
message: "The bootstrap key comes from NYLORUN_MANAGEMENT_KEY_FILE: change that file to rotate it",
|
|
37
|
+
};
|
|
26
38
|
return undefined;
|
|
27
39
|
}
|
|
28
40
|
export function operatorKeys(store, now = () => new Date().toISOString()) {
|
|
@@ -31,25 +43,45 @@ export function operatorKeys(store, now = () => new Date().toISOString()) {
|
|
|
31
43
|
const rows = await store.tx((t) => t.listPrincipals());
|
|
32
44
|
return rows.map(({ id, role, createdAt }) => ({ id, role, createdAt }));
|
|
33
45
|
},
|
|
34
|
-
async put(id) {
|
|
46
|
+
async put(id, role = "application") {
|
|
35
47
|
const refused = refuseOperatorKeyId(id);
|
|
36
48
|
if (refused)
|
|
37
49
|
return refused;
|
|
38
50
|
const key = mintBearerToken();
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
try {
|
|
52
|
+
const { row, rotated } = await store.tx(async (t) => {
|
|
53
|
+
const existing = await t.principalById(id);
|
|
54
|
+
return {
|
|
55
|
+
row: await t.putPrincipal(id, hashToken(key), now(), role),
|
|
56
|
+
rotated: existing !== undefined,
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
return { id: row.id, role: row.role, createdAt: row.createdAt, key, rotated };
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (error instanceof PrincipalRoleConflict)
|
|
63
|
+
return {
|
|
64
|
+
reason: "role",
|
|
65
|
+
message: `Key ${id} is not a${role === "application" ? "n application" : " management"} key: delete it first, or choose another name`,
|
|
66
|
+
};
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
47
69
|
},
|
|
48
|
-
async delete(id) {
|
|
49
|
-
const refused = refuseOperatorKeyId(id);
|
|
70
|
+
async delete(id, role) {
|
|
71
|
+
const refused = refuseOperatorKeyId(id, "delete");
|
|
50
72
|
if (refused)
|
|
51
73
|
return refused;
|
|
52
|
-
return await store.tx((t) =>
|
|
74
|
+
return await store.tx(async (t) => {
|
|
75
|
+
if (role !== undefined) {
|
|
76
|
+
const existing = await t.principalById(id);
|
|
77
|
+
if (existing && existing.role !== role)
|
|
78
|
+
return {
|
|
79
|
+
reason: "role",
|
|
80
|
+
message: `Key ${id} is a ${existing.role} key: only the Tenant's machine manages it (nylorun-operate)`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return await t.deletePrincipal(id);
|
|
84
|
+
});
|
|
53
85
|
},
|
|
54
86
|
};
|
|
55
87
|
}
|
|
@@ -36,11 +36,15 @@ export declare const MAX_RESPONSE_BYTES: number;
|
|
|
36
36
|
export declare function isPrivateAddress(address: string): boolean;
|
|
37
37
|
/** Why the Host refuses `url`, or `undefined` when it may be called. */
|
|
38
38
|
export declare function refusal(url: URL, policy: OutboundPolicy): string | undefined;
|
|
39
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* POSTs (or, with `method`, PUTs or PATCHes) the JSON `body` to `url` under `policy`. Never
|
|
41
|
+
* throws; `signal` aborts the request.
|
|
42
|
+
*/
|
|
40
43
|
export declare function post(target: string, body: string, headers: Record<string, string>, options: {
|
|
41
44
|
policy: OutboundPolicy;
|
|
42
45
|
signal: AbortSignal;
|
|
43
46
|
maxResponseBytes?: number;
|
|
47
|
+
method?: "POST" | "PUT" | "PATCH";
|
|
44
48
|
}): Promise<OutboundResult>;
|
|
45
49
|
/**
|
|
46
50
|
* A refusal of `guardedFetch`: the Host's policy forbids the URL or every address it resolves
|
|
@@ -67,8 +71,13 @@ export declare const FETCH_TIMEOUT_MS = 30000;
|
|
|
67
71
|
* unless the caller passes a signal. The gateway calls OAuth discovery, registration, the code
|
|
68
72
|
* exchange and refresh with it. A refused URL or address rejects with `OutboundRefused`, any
|
|
69
73
|
* other failure with `OutboundFailed`.
|
|
74
|
+
*
|
|
75
|
+
* `stream`: the Response resolves once the headers arrive and its body streams, unbounded and
|
|
76
|
+
* with no timeout but the caller's signal: remote MCP servers, whose answers and event streams
|
|
77
|
+
* stay open (`mcp/connect.ts`).
|
|
70
78
|
*/
|
|
71
79
|
export declare function guardedFetch(policy: OutboundPolicy, options?: {
|
|
72
80
|
maxResponseBytes?: number;
|
|
73
81
|
timeoutMs?: number;
|
|
82
|
+
stream?: boolean;
|
|
74
83
|
}): typeof fetch;
|