@nylorun/runtime 0.17.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 +149 -0
- package/README.md +88 -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 +291 -61
- 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 +4700 -12350
- 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/commands.js
CHANGED
|
@@ -7,80 +7,26 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Wakes (`ctx.wake`, architecture §12.3) carry the command type as the reason and a dedupe
|
|
9
9
|
* key naming the cause: `<type>:<turnId>:<segment>` for `message`, `approve` and `respond`
|
|
10
|
-
* (every accepted one writes a new checkpoint segment)
|
|
11
|
-
*
|
|
12
|
-
* `action_result:<turnId>:<actionId>:<generation>`.
|
|
10
|
+
* (every accepted one writes a new checkpoint segment), except that a workflow's `approve` and
|
|
11
|
+
* `respond` name `<type>:<turnId>:<interactionId>`: a flow resumes in the same segment.
|
|
13
12
|
*
|
|
14
13
|
* Cancel commits `cancelled` first; the engine host sees it before its next effect and before
|
|
15
14
|
* settlement on any Worker. It then aborts an advance running on this process
|
|
16
15
|
* (`ctx.abortLocal`); reaching an advance on another process is the control stream's job.
|
|
17
16
|
*/
|
|
18
17
|
import { randomUUID } from "node:crypto";
|
|
19
|
-
import { createDurableCheckpoint, createFlowCheckpoint, } from "@nylorun/harness/run";
|
|
20
|
-
import {
|
|
21
|
-
import { commandKey, fenceWorkflowActions, foreignInteractionConflict, isWorkflowManifest, planCancelCascade, wakeForQueuedEffects, wakeLinkedWorkflow, } from "../core/flow-host.js";
|
|
18
|
+
import { createDurableCheckpoint, createFlowCheckpoint, resumeFlowCheckpoint, } from "@nylorun/harness/run";
|
|
19
|
+
import { commandKey, cancelQueuedEffects, flowInteractionOf, foreignInteractionConflict, isWorkflowManifest, planCancelCascade, wakeLinkedWorkflow, } from "../core/flow-host.js";
|
|
22
20
|
import { resolveMessageManifest } from "../core/turn-manifest.js";
|
|
23
21
|
import { canonical } from "../store/canonical.js";
|
|
24
22
|
import { lockedSession, } from "./context.js";
|
|
25
23
|
import { fail } from "./http.js";
|
|
26
24
|
import { accessOf } from "./auth.js";
|
|
27
25
|
import { checkSandboxTurn } from "./sandboxes.js";
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import { toolIds } from "./transcript.js";
|
|
26
|
+
import { rebaseSessionState, turnManifestOf, variantStore } from "./session.js";
|
|
27
|
+
import { signalSessionCancel } from "./streams.js";
|
|
31
28
|
import { slimModelEffects } from "./slim.js";
|
|
32
29
|
import { resolveMessageParts } from "../artifacts/parts.js";
|
|
33
|
-
/** `outcome`, or a failed one when a tool's output does not match its stored output schema. */
|
|
34
|
-
export function acceptedOutcome(action, outcome) {
|
|
35
|
-
if (action.kind !== "tool" || !action.outputSchema)
|
|
36
|
-
return outcome;
|
|
37
|
-
const value = outcome.value;
|
|
38
|
-
// Only a result carries output: failures, denials, interactions and deferrals pass as sent.
|
|
39
|
-
if (!isResultToolValue(value))
|
|
40
|
-
return outcome;
|
|
41
|
-
// Endpoints wrap successful tool output as `{ kind: "completed", output }`.
|
|
42
|
-
// Validate the tool payload, not the outcome envelope.
|
|
43
|
-
const candidate = completedToolOutput(value);
|
|
44
|
-
let matches = false;
|
|
45
|
-
try {
|
|
46
|
-
matches = schemaFromJSON(action.outputSchema).validate(candidate).ok;
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
matches = false;
|
|
50
|
-
}
|
|
51
|
-
if (matches)
|
|
52
|
-
return outcome;
|
|
53
|
-
return {
|
|
54
|
-
...outcome,
|
|
55
|
-
value: {
|
|
56
|
-
kind: "failed",
|
|
57
|
-
code: "tool.invalid-output",
|
|
58
|
-
message: "Tool result does not match the output schema stored on the action",
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
function completedToolOutput(value) {
|
|
63
|
-
if (value &&
|
|
64
|
-
typeof value === "object" &&
|
|
65
|
-
!Array.isArray(value) &&
|
|
66
|
-
value.kind === "completed" &&
|
|
67
|
-
"output" in value) {
|
|
68
|
-
return value.output;
|
|
69
|
-
}
|
|
70
|
-
return value;
|
|
71
|
-
}
|
|
72
|
-
const NON_RESULT_KINDS = new Set([
|
|
73
|
-
"failed",
|
|
74
|
-
"denied",
|
|
75
|
-
"interaction-required",
|
|
76
|
-
"deferred",
|
|
77
|
-
]);
|
|
78
|
-
function isResultToolValue(value) {
|
|
79
|
-
return !(!!value &&
|
|
80
|
-
typeof value === "object" &&
|
|
81
|
-
!Array.isArray(value) &&
|
|
82
|
-
NON_RESULT_KINDS.has(value.kind));
|
|
83
|
-
}
|
|
84
30
|
/** A command's identity for idempotency: everything but the request id. */
|
|
85
31
|
const semantic = (value) => {
|
|
86
32
|
const { requestId: _, ...body } = value;
|
|
@@ -120,28 +66,12 @@ export async function command(ctx, id, input, scope) {
|
|
|
120
66
|
turnId: cancelledTurnId,
|
|
121
67
|
});
|
|
122
68
|
s.status = "cancelled";
|
|
123
|
-
if (workflowCancel)
|
|
124
|
-
await
|
|
69
|
+
if (workflowCancel)
|
|
70
|
+
await cancelQueuedEffects({
|
|
125
71
|
t,
|
|
126
72
|
workflowSessionId: id,
|
|
127
73
|
turnId: cancelledTurnId,
|
|
128
74
|
});
|
|
129
|
-
}
|
|
130
|
-
else if (cancelledTurnId !== null) {
|
|
131
|
-
for (const a of await t.actionsForSession(id, {
|
|
132
|
-
turnId: cancelledTurnId,
|
|
133
|
-
statuses: ["pending", "delivering"],
|
|
134
|
-
})) {
|
|
135
|
-
// Delivered work may already have an external effect. Preserve it for reconciliation.
|
|
136
|
-
a.status = a.status === "pending" ? "cancelled" : "uncertain";
|
|
137
|
-
await t.put("actions", a.actionId, a);
|
|
138
|
-
const effect = await t.get("effects", a.actionId);
|
|
139
|
-
if (effect) {
|
|
140
|
-
effect.status = a.status;
|
|
141
|
-
await t.put("effects", a.actionId, effect);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
75
|
if (cancelledTurnId !== null)
|
|
146
76
|
for (const effect of await t.effectsForTurn(id, cancelledTurnId, ["invoking"])) {
|
|
147
77
|
effect.status = "uncertain";
|
|
@@ -235,6 +165,28 @@ export async function command(ctx, id, input, scope) {
|
|
|
235
165
|
}
|
|
236
166
|
}
|
|
237
167
|
}
|
|
168
|
+
else if (isWorkflowManifest(s.manifest)) {
|
|
169
|
+
// Approvals owned by linked agent sessions must be answered there (WF-R52).
|
|
170
|
+
const conflict = await foreignInteractionConflict({
|
|
171
|
+
t,
|
|
172
|
+
workflowSessionId: id,
|
|
173
|
+
interactionId: command.interactionId,
|
|
174
|
+
});
|
|
175
|
+
if (conflict)
|
|
176
|
+
fail(409, conflict.message);
|
|
177
|
+
if (s.status !== "paused" || !s.checkpoint || !s.activeTurnId)
|
|
178
|
+
fail(409, "Session is not awaiting a response");
|
|
179
|
+
// The flow's own interactions: a tool node that asked (`ctx.approve`, `ctx.ask`).
|
|
180
|
+
const asked = flowInteractionOf(s.waits, id, command.interactionId);
|
|
181
|
+
if (!asked)
|
|
182
|
+
fail(409, "Unknown interaction");
|
|
183
|
+
else if ((command.type === "approve") !== (asked.kind === "approval"))
|
|
184
|
+
fail(409, "Interaction response kind does not match the saved request");
|
|
185
|
+
// Same segment: the flow replays its journal and runs the tool again with the answer.
|
|
186
|
+
s.checkpoint = resumeFlowCheckpoint(s.checkpoint, command.interactionId, command.type === "approve"
|
|
187
|
+
? { kind: "approval", approved: command.approved }
|
|
188
|
+
: { kind: "response", value: command.value });
|
|
189
|
+
}
|
|
238
190
|
else {
|
|
239
191
|
if (s.status !== "paused" || !s.state || !s.activeTurnId)
|
|
240
192
|
fail(409, "Session is not awaiting a response");
|
|
@@ -255,19 +207,6 @@ export async function command(ctx, id, input, scope) {
|
|
|
255
207
|
interactionId: command.interactionId,
|
|
256
208
|
value: command.value,
|
|
257
209
|
};
|
|
258
|
-
if (isWorkflowManifest(s.manifest)) {
|
|
259
|
-
// Approvals owned by linked agent sessions must be answered there (WF-R52).
|
|
260
|
-
const conflict = await foreignInteractionConflict({
|
|
261
|
-
t,
|
|
262
|
-
workflowSessionId: id,
|
|
263
|
-
interactionId: command.interactionId,
|
|
264
|
-
});
|
|
265
|
-
if (conflict)
|
|
266
|
-
fail(409, conflict.message);
|
|
267
|
-
// Workflow-owned interactions (tool-node / verify) resume on this session once
|
|
268
|
-
// the flow engine supports pause segments; tracer root Loop has none yet.
|
|
269
|
-
fail(409, "Workflow sessions do not accept approve/respond on the root without a pending interaction");
|
|
270
|
-
}
|
|
271
210
|
s.checkpoint = createDurableCheckpoint({
|
|
272
211
|
manifest: turnManifestOf(s),
|
|
273
212
|
sessionId: id,
|
|
@@ -280,9 +219,13 @@ export async function command(ctx, id, input, scope) {
|
|
|
280
219
|
}
|
|
281
220
|
s.status = "runnable";
|
|
282
221
|
s.waits = undefined;
|
|
222
|
+
// A flow answers each interaction in the same segment: the interaction names the cause.
|
|
223
|
+
const cause = command.type !== "message" && isWorkflowManifest(s.manifest)
|
|
224
|
+
? command.interactionId
|
|
225
|
+
: s.checkpoint.segment;
|
|
283
226
|
const commandWake = {
|
|
284
227
|
reason: command.type,
|
|
285
|
-
dedupeKey: `${command.type}:${s.activeTurnId}:${
|
|
228
|
+
dedupeKey: `${command.type}:${s.activeTurnId}:${cause}`,
|
|
286
229
|
};
|
|
287
230
|
t.afterCommit(() => ctx.wake(id, commandWake));
|
|
288
231
|
event = await t.event(id, s.activeTurnId, `command.${command.type}`, command);
|
|
@@ -326,83 +269,3 @@ export async function command(ctx, id, input, scope) {
|
|
|
326
269
|
}
|
|
327
270
|
return response;
|
|
328
271
|
}
|
|
329
|
-
/**
|
|
330
|
-
* Completes a delivered Action with `outcome`, in the caller's transaction, which
|
|
331
|
-
* holds the lock of the Action's session `s`: the Action and its effect, the
|
|
332
|
-
* `action.completed` event (and `loop.verified` or `loop.decided`), the wake that resumes the
|
|
333
|
-
* turn, and queued workflow effects. The Action deliverer and the background-result callback
|
|
334
|
-
* both record outcomes here. It sets `s.status`; the caller writes `s`.
|
|
335
|
-
*/
|
|
336
|
-
export async function recordActionOutcome(t, ctx, s, action, received, options = {}) {
|
|
337
|
-
const outcome = acceptedOutcome(action, received);
|
|
338
|
-
const prior = await t.get("effects", action.actionId);
|
|
339
|
-
action.status = "completed";
|
|
340
|
-
await t.put("actions", action.actionId, action);
|
|
341
|
-
prior.status = "completed";
|
|
342
|
-
prior.outcome = outcome;
|
|
343
|
-
s.status = "runnable";
|
|
344
|
-
const resultWake = {
|
|
345
|
-
reason: "action_result",
|
|
346
|
-
dedupeKey: `action_result:${action.turnId}:${action.actionId}:${action.generation}`,
|
|
347
|
-
};
|
|
348
|
-
t.afterCommit(() => ctx.wake(s.id, resultWake));
|
|
349
|
-
// A run held while the Action was pending goes on with it in the same lease (F6.2): here, or
|
|
350
|
-
// on the process whose harness holds it. The wake finds the session settled, or resumes it
|
|
351
|
-
// by replay when the run had already ended.
|
|
352
|
-
t.afterCommit(() => {
|
|
353
|
-
if (ctx.harness.holds(s.id))
|
|
354
|
-
ctx.harness.resolved(s.id, action.actionId, outcome);
|
|
355
|
-
else
|
|
356
|
-
signalActionOutcome(ctx, s.id, action.actionId);
|
|
357
|
-
});
|
|
358
|
-
const event = await t.event(s.id, s.activeTurnId, "action.completed", {
|
|
359
|
-
actionId: action.actionId,
|
|
360
|
-
...actionTarget(action),
|
|
361
|
-
kind: action.kind,
|
|
362
|
-
...(action.kind === "tool" ? toolIds(action.context) : {}),
|
|
363
|
-
result: outcome.value,
|
|
364
|
-
});
|
|
365
|
-
if (action.kind === "verify") {
|
|
366
|
-
const verdict = outcome.value;
|
|
367
|
-
if (verdict?.kind !== "failed") {
|
|
368
|
-
await t.event(s.id, s.activeTurnId, "loop.verified", {
|
|
369
|
-
path: String(action.context?.loopPath ?? action.path ?? ""),
|
|
370
|
-
n: Number(action.context?.n ?? 1),
|
|
371
|
-
pass: Boolean(verdict?.pass),
|
|
372
|
-
...(verdict?.feedback !== undefined
|
|
373
|
-
? { feedback: verdict.feedback }
|
|
374
|
-
: {}),
|
|
375
|
-
...(verdict?.data !== undefined ? { data: verdict.data } : {}),
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
else if (action.kind === "fn" && action.context?.role === "decide") {
|
|
380
|
-
const decision = outcome.value;
|
|
381
|
-
await t.event(s.id, s.activeTurnId, "loop.decided", {
|
|
382
|
-
path: String(action.context?.loopPath ?? action.path ?? ""),
|
|
383
|
-
n: Number(action.context?.n ?? 1),
|
|
384
|
-
next: "output" in decision && !("input" in decision)
|
|
385
|
-
? "output"
|
|
386
|
-
: "input",
|
|
387
|
-
patched: Boolean(decision.agent),
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
const receipt = {
|
|
391
|
-
status: "accepted",
|
|
392
|
-
turnId: s.activeTurnId,
|
|
393
|
-
cursor: event.cursor,
|
|
394
|
-
...(options.requestId === undefined ? {} : { requestId: options.requestId }),
|
|
395
|
-
};
|
|
396
|
-
prior.receipt = receipt;
|
|
397
|
-
await t.put("effects", action.actionId, prior);
|
|
398
|
-
if (isWorkflowManifest(s.manifest) && s.activeTurnId) {
|
|
399
|
-
await wakeForQueuedEffects({
|
|
400
|
-
t,
|
|
401
|
-
workflowSessionId: s.id,
|
|
402
|
-
turnId: s.activeTurnId,
|
|
403
|
-
limits: ctx.flowLimits,
|
|
404
|
-
schedule: ctx.wake,
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
return { event, receipt };
|
|
408
|
-
}
|
package/dist/tenant/context.d.ts
CHANGED
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Business code changes state only inside `ctx.store.tx(async (t) => …)` and follows the
|
|
7
7
|
* seam rules: events through `t.event(...)` (the relay appends them to Durable Streams after
|
|
8
|
-
* commit),
|
|
9
|
-
* `t.afterCommit(() => ctx.wake(id, { reason, dedupeKey }))`. It never publishes or notifies
|
|
8
|
+
* commit), and advances through `t.afterCommit(() => ctx.wake(id, { reason, dedupeKey }))`. It never publishes or notifies
|
|
10
9
|
* itself; `runtime.ts` wires the streams with `wireStreams()`. No external I/O runs inside a
|
|
11
10
|
* tx.
|
|
12
11
|
*
|
|
@@ -72,7 +71,6 @@ export interface Session {
|
|
|
72
71
|
creation: unknown;
|
|
73
72
|
vaultIds?: readonly string[];
|
|
74
73
|
credentialSelections?: readonly CredentialSelection[];
|
|
75
|
-
pluginRoots?: Readonly<Record<string, string>>;
|
|
76
74
|
mcpSnapshot?: McpSnapshot;
|
|
77
75
|
mcpDiagnostics?: readonly McpDiagnostic[];
|
|
78
76
|
/** Session id that keys the shared sandbox; absent means this session owns it. */
|
|
@@ -95,6 +93,15 @@ export type AuthScope = {
|
|
|
95
93
|
kind: "application";
|
|
96
94
|
principalId: string;
|
|
97
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* A management key (role `management`), or Studio's key on a Management API route: the
|
|
98
|
+
* principal acting as itself, never for a subject. It reaches only the Management API
|
|
99
|
+
* (`/v1/tenant/*`) and `/v1/me`.
|
|
100
|
+
*/
|
|
101
|
+
| {
|
|
102
|
+
kind: "management";
|
|
103
|
+
principalId: string;
|
|
104
|
+
}
|
|
98
105
|
/**
|
|
99
106
|
* A trusted issuer's token (Host feature `trusted-issuers`, F9-D12): one subject, with the
|
|
100
107
|
* issuer's scopes, agents and sandbox grants, until `expiresAt` (ms). Only its expiry ends it.
|
|
@@ -125,21 +132,9 @@ export type AuthScope = {
|
|
|
125
132
|
/** No credential, on a route that serves public data (`RouteAccess.anonymous`). */
|
|
126
133
|
| {
|
|
127
134
|
kind: "anonymous";
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* A delivery token (Action endpoints): the Runtime's own token for one delivery of one
|
|
131
|
-
* Action, presented back by the Action endpoint on that Action's callbacks only.
|
|
132
|
-
*/
|
|
133
|
-
| {
|
|
134
|
-
kind: "delivery";
|
|
135
|
-
actionId: string;
|
|
136
|
-
agentId: string;
|
|
137
|
-
generation: number;
|
|
138
|
-
expiresAt: number;
|
|
139
|
-
tokenId: string;
|
|
140
|
-
keyId: string;
|
|
141
135
|
};
|
|
142
136
|
export interface TenantContext {
|
|
137
|
+
readonly reads?: import("../reads/types.js").ReadStore;
|
|
143
138
|
readonly config: TenantConfig;
|
|
144
139
|
readonly envelope: TenantEnvelope;
|
|
145
140
|
readonly store: SessionStore;
|
|
@@ -166,8 +161,8 @@ export interface TenantContext {
|
|
|
166
161
|
/** Serves vault-backed model calls (blueprint §15): in this process, or the gates service. */
|
|
167
162
|
readonly modelGate: ModelGate;
|
|
168
163
|
/**
|
|
169
|
-
* Serves remote MCP servers and
|
|
170
|
-
*
|
|
164
|
+
* Serves remote MCP servers and HTTP tools (blueprint §12, F4.1): in this process, or the
|
|
165
|
+
* gates service.
|
|
171
166
|
*/
|
|
172
167
|
readonly toolGate: ToolGate;
|
|
173
168
|
/**
|
|
@@ -206,7 +201,7 @@ export interface TenantContext {
|
|
|
206
201
|
readonly work: WorkState;
|
|
207
202
|
/** Live delivery over Durable Streams: one `SessionStream` per observed session, and the streams wiring. */
|
|
208
203
|
readonly sessionStreams: SessionStreams;
|
|
209
|
-
/** The Tenant's signing keys:
|
|
204
|
+
/** The Tenant's signing keys: capability links, run and host tokens. */
|
|
210
205
|
readonly signingKeys: SigningKeys;
|
|
211
206
|
/** The Worker id this process writes as session `owner` (§10.6). */
|
|
212
207
|
readonly workerId: string;
|
|
@@ -223,11 +218,6 @@ export interface TenantContext {
|
|
|
223
218
|
* an advance of that turn (a cancel signal names the turn it cancelled).
|
|
224
219
|
*/
|
|
225
220
|
abortLocal(sessionId: string, turnId?: string): void;
|
|
226
|
-
/**
|
|
227
|
-
* Seam: deliver an Action to its agent's Action endpoint (`DurableExecution.deliver`). Call it
|
|
228
|
-
* from `t.afterCommit`. Dropped while the Tenant is closing; the sweep re-sends it.
|
|
229
|
-
*/
|
|
230
|
-
deliver(actionId: string): Promise<void>;
|
|
231
221
|
/**
|
|
232
222
|
* Sandbox pods (F7.2): the sandboxes service and the Runtime image pods copy the engine from.
|
|
233
223
|
* Absent without a cluster (`nylorun sandbox enable`): kind `pod` is `sandbox_unavailable`.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type DefinitionFileView } from "@nylorun/core/contracts";
|
|
2
|
+
import type { BlobStore } from "../blob/index.js";
|
|
3
|
+
import type { Tx } from "../store/types.js";
|
|
4
|
+
import type { TenantContext } from "./context.js";
|
|
5
|
+
/** Where a definition file's bytes are in the Object store. */
|
|
6
|
+
export declare function definitionFileKey(sha256: string): string;
|
|
7
|
+
/** Whether the Tenant holds definition file `file` (`sha256:<hex>`). */
|
|
8
|
+
export declare function hasDefinitionFile(ctx: TenantContext, file: string): Promise<boolean>;
|
|
9
|
+
/**
|
|
10
|
+
* Stores the bytes of definition file `file`: `201` when they are new, `200` when the Tenant
|
|
11
|
+
* holds them already (the body is not read). Bytes that do not hash to `file` are a `400`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function putDefinitionFile(ctx: TenantContext, file: string, request: {
|
|
14
|
+
body: ReadableStream<Uint8Array> | null;
|
|
15
|
+
declaredBytes?: number;
|
|
16
|
+
contentType?: string;
|
|
17
|
+
}, signal?: AbortSignal): Promise<{
|
|
18
|
+
status: 200 | 201;
|
|
19
|
+
view: DefinitionFileView;
|
|
20
|
+
}>;
|
|
21
|
+
/** The definition files `document` names that the Tenant does not hold, in order. */
|
|
22
|
+
export declare function missingDefinitionFiles(t: Tx, document: unknown): Promise<string[]>;
|
|
23
|
+
/** A definition file's bytes, or undefined when the Object store does not have them. */
|
|
24
|
+
export declare function readDefinitionFile(blobs: BlobStore, sha256: string, signal?: AbortSignal): Promise<Uint8Array | undefined>;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Definition files (track R2 M4): the files a definition names by the SHA-256 of their bytes,
|
|
3
|
+
* today a skill's folder (`SkillManifest.files`). A client uploads each one once
|
|
4
|
+
* (`PUT /v1/files/sha256:<hex>`); its bytes go to the Object store at
|
|
5
|
+
* `definitions/sha256/<hex>`, and a `definition_files` row says the Tenant holds it. A
|
|
6
|
+
* definition naming a file the Tenant does not hold is refused (`definition_files_missing`).
|
|
7
|
+
* The Runtime reads them to serve the skill tools and to give a sandbox the skills' files.
|
|
8
|
+
*
|
|
9
|
+
* Files are never deleted here: `definition_file_uses` records which definition version names
|
|
10
|
+
* which file, for a later sweep.
|
|
11
|
+
*/
|
|
12
|
+
import { createHash } from "node:crypto";
|
|
13
|
+
import { DEFINITION_FILE_MAX_BYTES, definitionFilesOf, isDefinitionFileHash, } from "@nylorun/core/contracts";
|
|
14
|
+
import { fail } from "./http.js";
|
|
15
|
+
/** Where a definition file's bytes are in the Object store. */
|
|
16
|
+
export function definitionFileKey(sha256) {
|
|
17
|
+
return `definitions/sha256/${sha256.slice("sha256:".length)}`;
|
|
18
|
+
}
|
|
19
|
+
function checkHash(file) {
|
|
20
|
+
if (!isDefinitionFileHash(file))
|
|
21
|
+
fail(400, "A definition file is named sha256:<64 lowercase hex>", { code: "invalid_request" });
|
|
22
|
+
return file;
|
|
23
|
+
}
|
|
24
|
+
/** Whether the Tenant holds definition file `file` (`sha256:<hex>`). */
|
|
25
|
+
export async function hasDefinitionFile(ctx, file) {
|
|
26
|
+
const sha256 = checkHash(file);
|
|
27
|
+
return (await ctx.store.tx((t) => t.definitionFile(sha256))) !== undefined;
|
|
28
|
+
}
|
|
29
|
+
/** The body, at most `DEFINITION_FILE_MAX_BYTES`; past it, `413 limit_exceeded`. */
|
|
30
|
+
async function readBody(body, declared) {
|
|
31
|
+
const tooLarge = () => fail(413, `A definition file may hold at most ${DEFINITION_FILE_MAX_BYTES} bytes`, {
|
|
32
|
+
code: "limit_exceeded",
|
|
33
|
+
details: { limitBytes: DEFINITION_FILE_MAX_BYTES },
|
|
34
|
+
});
|
|
35
|
+
if (declared !== undefined && declared > DEFINITION_FILE_MAX_BYTES) {
|
|
36
|
+
await body?.cancel().catch(() => undefined);
|
|
37
|
+
tooLarge();
|
|
38
|
+
}
|
|
39
|
+
if (body === null)
|
|
40
|
+
return new Uint8Array();
|
|
41
|
+
const chunks = [];
|
|
42
|
+
let size = 0;
|
|
43
|
+
const reader = body.getReader();
|
|
44
|
+
for (;;) {
|
|
45
|
+
const { done, value } = await reader.read();
|
|
46
|
+
if (done)
|
|
47
|
+
break;
|
|
48
|
+
size += value.byteLength;
|
|
49
|
+
if (size > DEFINITION_FILE_MAX_BYTES) {
|
|
50
|
+
await reader.cancel().catch(() => undefined);
|
|
51
|
+
tooLarge();
|
|
52
|
+
}
|
|
53
|
+
chunks.push(value);
|
|
54
|
+
}
|
|
55
|
+
return Buffer.concat(chunks);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Stores the bytes of definition file `file`: `201` when they are new, `200` when the Tenant
|
|
59
|
+
* holds them already (the body is not read). Bytes that do not hash to `file` are a `400`.
|
|
60
|
+
*/
|
|
61
|
+
export async function putDefinitionFile(ctx, file, request, signal) {
|
|
62
|
+
const sha256 = checkHash(file);
|
|
63
|
+
const held = await ctx.store.tx((t) => t.definitionFile(sha256));
|
|
64
|
+
if (held) {
|
|
65
|
+
await request.body?.cancel().catch(() => undefined);
|
|
66
|
+
return { status: 200, view: { sha256, size: held.size } };
|
|
67
|
+
}
|
|
68
|
+
const bytes = await readBody(request.body, request.declaredBytes);
|
|
69
|
+
const actual = `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
|
|
70
|
+
if (actual !== sha256)
|
|
71
|
+
fail(400, `The body's SHA-256 is ${actual}, not ${sha256}: upload a file at its own hash`, {
|
|
72
|
+
code: "invalid_request",
|
|
73
|
+
details: { expected: sha256, actual },
|
|
74
|
+
});
|
|
75
|
+
// The key is the content's hash: a concurrent upload of the same file writes the same bytes.
|
|
76
|
+
await ctx.blobs.put(definitionFileKey(sha256), bytes, {
|
|
77
|
+
...(request.contentType === undefined ? {} : { contentType: request.contentType }),
|
|
78
|
+
...(signal ? { signal } : {}),
|
|
79
|
+
});
|
|
80
|
+
const inserted = await ctx.store.tx((t) => t.insertDefinitionFile({
|
|
81
|
+
sha256,
|
|
82
|
+
kind: "skill",
|
|
83
|
+
size: bytes.byteLength,
|
|
84
|
+
contentType: request.contentType ?? null,
|
|
85
|
+
createdAt: new Date().toISOString(),
|
|
86
|
+
}));
|
|
87
|
+
return { status: inserted ? 201 : 200, view: { sha256, size: bytes.byteLength } };
|
|
88
|
+
}
|
|
89
|
+
/** The definition files `document` names that the Tenant does not hold, in order. */
|
|
90
|
+
export async function missingDefinitionFiles(t, document) {
|
|
91
|
+
const named = [...definitionFilesOf(document)].sort();
|
|
92
|
+
if (named.length === 0)
|
|
93
|
+
return [];
|
|
94
|
+
const held = await t.heldDefinitionFiles(named);
|
|
95
|
+
return named.filter((sha256) => !held.has(sha256));
|
|
96
|
+
}
|
|
97
|
+
/** A definition file's bytes, or undefined when the Object store does not have them. */
|
|
98
|
+
export async function readDefinitionFile(blobs, sha256, signal) {
|
|
99
|
+
const got = await blobs.get(definitionFileKey(sha256), signal ? { signal } : {});
|
|
100
|
+
if (!got)
|
|
101
|
+
return undefined;
|
|
102
|
+
return new Uint8Array(await new Response(got.body).arrayBuffer());
|
|
103
|
+
}
|
package/dist/tenant/effects.d.ts
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Core's side of effects: `resolveNewFlowEffect` journals and dispatches new workflow effects
|
|
3
|
-
* (linked agent sessions
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* (linked agent sessions; a tool node without `http` fails, as it would run the developer's
|
|
4
|
+
* code); the takeover helpers say which calls their gate recovers. Also vault authorization
|
|
5
|
+
* for MCP servers and HTTP tools. The journal of a run's effects is `harness-api/record.ts`; a
|
|
6
|
+
* harness readies the session's MCP servers itself (`session.mcp`).
|
|
6
7
|
*
|
|
7
8
|
* Every journal write runs in a transaction that locks the effect's session first and checks
|
|
8
9
|
* the advance's ownership epoch (`ownedSession`): after another Worker takes over, the next
|
|
9
|
-
* write throws `ownership.lost` and nothing is written. The model, MCP, sandbox and vault
|
|
10
|
+
* write throws `ownership.lost` and nothing is written. The model, MCP, HTTP, sandbox and vault
|
|
10
11
|
* calls run between transactions, never inside one.
|
|
11
12
|
*/
|
|
12
|
-
import type {
|
|
13
|
+
import type { EffectOutcome } from "@nylorun/core/contracts";
|
|
13
14
|
import type { EffectResolution, HostEffect } from "@nylorun/harness/run";
|
|
14
15
|
import type { Tx } from "../store/types.js";
|
|
15
16
|
import type { AuthorizeResult } from "../vault/service.js";
|
|
17
|
+
import type { McpCredentialRequest } from "../vault/sources.js";
|
|
16
18
|
import { type Lease, type Session, type TenantContext } from "./context.js";
|
|
17
19
|
/**
|
|
18
20
|
* The outcome of a flow `agent` effect once the linked turn it started ended, or undefined
|
|
@@ -21,29 +23,25 @@ import { type Lease, type Session, type TenantContext } from "./context.js";
|
|
|
21
23
|
export declare function linkedOutcome(t: Tx, effect: {
|
|
22
24
|
request: HostEffect;
|
|
23
25
|
agentSessionId?: string;
|
|
24
|
-
}, agent: Session | undefined): Promise<
|
|
26
|
+
}, agent: Session | undefined): Promise<EffectOutcome | undefined>;
|
|
25
27
|
/**
|
|
26
|
-
* True when this Tenant's remote MCP calls outlive the process that sent them
|
|
27
|
-
* service, F4.1 G3): after a takeover or a shutdown, the journaled call is re-sent
|
|
28
|
-
* the running call or gets its answer, instead of becoming `uncertain`.
|
|
28
|
+
* True when this Tenant's remote MCP and HTTP tool calls outlive the process that sent them
|
|
29
|
+
* (the gates service, F4.1 G3): after a takeover or a shutdown, the journaled call is re-sent
|
|
30
|
+
* and joins the running call or gets its answer, instead of becoming `uncertain`.
|
|
29
31
|
*/
|
|
30
|
-
export declare function
|
|
31
|
-
/** True when `request` calls a tool of a
|
|
32
|
-
export declare function
|
|
32
|
+
export declare function recoversToolCalls(ctx: TenantContext): boolean;
|
|
33
|
+
/** True when `request` calls a tool of a declared MCP server (every one is remote) or an HTTP tool. */
|
|
34
|
+
export declare function isGateToolEffect(s: Session, request: HostEffect): boolean;
|
|
33
35
|
/**
|
|
34
36
|
* True when this Tenant's vault-backed model calls outlive the process that sent them (the
|
|
35
37
|
* gates service, P1.2): after a takeover or a shutdown, the journaled call is re-sent and joins
|
|
36
38
|
* the running call or gets its outcome, instead of becoming `uncertain`.
|
|
37
39
|
*/
|
|
38
40
|
export declare function recoversModelCalls(ctx: TenantContext): boolean;
|
|
39
|
-
/** Journal and dispatch a new flow effect (agent
|
|
41
|
+
/** Journal and dispatch a new flow effect (agent or tool node). */
|
|
40
42
|
export declare function resolveNewFlowEffect(ctx: TenantContext, request: HostEffect, signal: AbortSignal, lease: Lease): Promise<EffectResolution>;
|
|
41
43
|
/**
|
|
42
|
-
* The credential of an MCP server request made on behalf of a session: from its
|
|
43
|
-
* else the operator's credential resolver (`vault/sources.ts`).
|
|
44
|
+
* The credential of an MCP server or HTTP tool request made on behalf of a session: from its
|
|
45
|
+
* attached vaults, else the operator's credential resolver (`vault/sources.ts`).
|
|
44
46
|
*/
|
|
45
|
-
export declare function authorize(ctx: TenantContext, sessionId: string, request:
|
|
46
|
-
url: string;
|
|
47
|
-
serverName?: string;
|
|
48
|
-
agentId?: string;
|
|
49
|
-
}): Promise<AuthorizeResult>;
|
|
47
|
+
export declare function authorize(ctx: TenantContext, sessionId: string, request: McpCredentialRequest): Promise<AuthorizeResult>;
|