@nylorun/runtime 0.12.0-beta → 0.14.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +361 -0
- package/README.md +69 -15
- package/dist/adapters/execution/restate.d.ts +7 -2
- package/dist/adapters/execution/restate.js +38 -8
- package/dist/adapters/replication/pgoutput.d.ts +16 -0
- package/dist/adapters/replication/pgoutput.js +169 -0
- package/dist/adapters/streams/s2.js +37 -20
- package/dist/admin-openapi.json +1071 -0
- package/dist/api/a2a/card.d.ts +15 -0
- package/dist/api/a2a/card.js +24 -0
- package/dist/api/a2a/endpoint.d.ts +8 -0
- package/dist/api/a2a/endpoint.js +81 -0
- package/dist/api/a2a/protocol.d.ts +144 -0
- package/dist/api/a2a/protocol.js +188 -0
- package/dist/api/a2a/routes.d.ts +19 -0
- package/dist/api/a2a/routes.js +289 -0
- package/dist/api/a2a/tasks.d.ts +50 -0
- package/dist/api/a2a/tasks.js +186 -0
- package/dist/api/ag-ui/endpoint.d.ts +8 -0
- package/dist/api/ag-ui/endpoint.js +126 -0
- package/dist/api/ag-ui/history.d.ts +7 -0
- package/dist/api/ag-ui/history.js +60 -0
- package/dist/api/ag-ui/routes.d.ts +32 -0
- package/dist/api/ag-ui/routes.js +305 -0
- package/dist/api/ag-ui/session-id.d.ts +1 -0
- package/dist/api/ag-ui/session-id.js +12 -0
- package/dist/api/ag-ui/sse.d.ts +9 -0
- package/dist/api/ag-ui/sse.js +7 -0
- package/dist/api/ag-ui/translate.d.ts +38 -0
- package/dist/api/ag-ui/translate.js +222 -0
- package/dist/api/components.d.ts +7825 -0
- package/dist/api/components.js +83 -0
- package/dist/api/http/app.d.ts +18 -0
- package/dist/api/http/app.js +58 -0
- package/dist/api/http/body.d.ts +4 -0
- package/dist/api/http/body.js +37 -0
- package/dist/api/http/define.d.ts +39 -0
- package/dist/api/http/define.js +130 -0
- package/dist/api/http/respond.d.ts +9 -0
- package/dist/api/http/respond.js +44 -0
- package/dist/api/http/routes/access.d.ts +24 -0
- package/dist/api/http/routes/access.js +259 -0
- package/dist/api/http/routes/actions.d.ts +8 -0
- package/dist/api/http/routes/actions.js +76 -0
- package/dist/api/http/routes/endpoints.d.ts +7 -0
- package/dist/api/http/routes/endpoints.js +100 -0
- package/dist/api/http/routes/sessions.d.ts +3 -0
- package/dist/api/http/routes/sessions.js +205 -0
- package/dist/api/http/routes/tenant.d.ts +10 -0
- package/dist/api/http/routes/tenant.js +172 -0
- package/dist/api/http/routes/vaults.d.ts +8 -0
- package/dist/api/http/routes/vaults.js +167 -0
- package/dist/api/openapi.d.ts +19 -0
- package/dist/api/openapi.js +138 -0
- package/dist/api/route.d.ts +12 -0
- package/dist/api/route.js +4 -0
- package/dist/core/bearer.d.ts +8 -0
- package/dist/core/bearer.js +11 -0
- package/dist/core/flow-host.d.ts +7 -15
- package/dist/core/flow-host.js +19 -41
- package/dist/core/sandbox-routes.d.ts +12 -7
- package/dist/core/sandbox-routes.js +24 -25
- package/dist/execution/memory.d.ts +8 -5
- package/dist/execution/memory.js +33 -13
- package/dist/execution/types.d.ts +38 -6
- package/dist/execution/types.js +12 -3
- package/dist/host/admin-api.d.ts +22 -0
- package/dist/host/admin-api.js +178 -0
- package/dist/host/app.d.ts +34 -0
- package/dist/host/app.js +185 -0
- package/dist/host/config-for.d.ts +2 -0
- package/dist/host/config-for.js +1 -0
- package/dist/host/config.d.ts +11 -0
- package/dist/host/cors.d.ts +27 -0
- package/dist/host/cors.js +64 -0
- package/dist/host/create-host.d.ts +31 -3
- package/dist/host/create-host.js +168 -327
- package/dist/host/http.d.ts +14 -3
- package/dist/host/http.js +64 -23
- package/dist/host/main.js +55 -4
- package/dist/host/stack-config.d.ts +20 -0
- package/dist/host/stack-config.js +55 -0
- package/dist/mcp/pool.d.ts +19 -1
- package/dist/mcp/pool.js +60 -10
- package/dist/model/catalog.d.ts +2 -10
- package/dist/model/classify.d.ts +13 -0
- package/dist/model/classify.js +61 -0
- package/dist/model/models.d.ts +10 -1
- package/dist/model/models.js +14 -6
- package/dist/model/pi-model.d.ts +17 -2
- package/dist/model/pi-model.js +366 -222
- package/dist/openapi.json +25696 -0
- package/dist/sandbox/manager.d.ts +13 -1
- package/dist/sandbox/manager.js +8 -11
- package/dist/sandbox/resolve.d.ts +33 -0
- package/dist/sandbox/resolve.js +81 -0
- package/dist/sandbox/session-sandbox.d.ts +11 -0
- package/dist/sandbox/session-sandbox.js +51 -0
- package/dist/sandbox/share.d.ts +14 -2
- package/dist/sandbox/share.js +33 -3
- package/dist/sandbox/tenant-config.d.ts +37 -0
- package/dist/sandbox/tenant-config.js +51 -0
- package/dist/sandbox/tools.d.ts +7 -9
- package/dist/sandbox/tools.js +11 -3
- package/dist/sandbox/types.d.ts +8 -2
- package/dist/store/event.d.ts +19 -0
- package/dist/store/event.js +49 -0
- package/dist/store/memory.d.ts +20 -8
- package/dist/store/memory.js +265 -100
- package/dist/store/postgres/connect.d.ts +13 -0
- package/dist/store/postgres/connect.js +5 -0
- package/dist/store/postgres/migrations/003_subject_tokens.d.ts +8 -0
- package/dist/store/postgres/migrations/003_subject_tokens.js +42 -0
- package/dist/store/postgres/migrations/004_publishable_keys.d.ts +6 -0
- package/dist/store/postgres/migrations/004_publishable_keys.js +18 -0
- package/dist/store/postgres/migrations/005_action_endpoints.d.ts +6 -0
- package/dist/store/postgres/migrations/005_action_endpoints.js +32 -0
- package/dist/store/postgres/migrations/006_drop_executors.d.ts +11 -0
- package/dist/store/postgres/migrations/006_drop_executors.js +32 -0
- package/dist/store/postgres/migrations/007_event_time.d.ts +7 -0
- package/dist/store/postgres/migrations/007_event_time.js +14 -0
- package/dist/store/postgres/migrations/008_durable_streams.d.ts +13 -0
- package/dist/store/postgres/migrations/008_durable_streams.js +35 -0
- package/dist/store/postgres/migrations/index.d.ts +5 -1
- package/dist/store/postgres/migrations/index.js +22 -2
- package/dist/store/postgres/migrations/shared/index.d.ts +31 -0
- package/dist/store/postgres/migrations/shared/index.js +92 -0
- package/dist/store/postgres/record.d.ts +11 -0
- package/dist/store/postgres/record.js +64 -0
- package/dist/store/postgres/store.js +337 -95
- package/dist/store/postgres/tenants.js +14 -0
- package/dist/store/types.d.ts +176 -81
- package/dist/streams/basin.d.ts +18 -1
- package/dist/streams/basin.js +34 -2
- package/dist/streams/control.d.ts +18 -0
- package/dist/streams/control.js +29 -0
- package/dist/streams/memory.d.ts +3 -1
- package/dist/streams/memory.js +14 -8
- package/dist/streams/relay/core.d.ts +35 -0
- package/dist/streams/relay/core.js +366 -0
- package/dist/streams/relay/memory.d.ts +69 -0
- package/dist/streams/relay/memory.js +176 -0
- package/dist/streams/relay/types.d.ts +77 -0
- package/dist/streams/relay/types.js +7 -0
- package/dist/streams/types.d.ts +66 -36
- package/dist/streams/types.js +18 -53
- package/dist/tenant/access-policy.d.ts +30 -0
- package/dist/tenant/access-policy.js +52 -0
- package/dist/tenant/actions.d.ts +10 -149
- package/dist/tenant/actions.js +5 -167
- package/dist/tenant/advance.js +32 -0
- package/dist/tenant/auth.d.ts +19 -17
- package/dist/tenant/auth.js +90 -93
- package/dist/tenant/browser.d.ts +15 -0
- package/dist/tenant/browser.js +39 -0
- package/dist/tenant/commands.d.ts +18 -2
- package/dist/tenant/commands.js +121 -129
- package/dist/tenant/context.d.ts +75 -18
- package/dist/tenant/context.js +14 -10
- package/dist/tenant/delivery-token.d.ts +41 -0
- package/dist/tenant/delivery-token.js +105 -0
- package/dist/tenant/delivery.d.ts +62 -0
- package/dist/tenant/delivery.js +476 -0
- package/dist/tenant/effects.d.ts +5 -0
- package/dist/tenant/effects.js +138 -48
- package/dist/tenant/endpoints.d.ts +67 -0
- package/dist/tenant/endpoints.js +90 -0
- package/dist/tenant/ephemeral.d.ts +6 -0
- package/dist/tenant/ephemeral.js +60 -36
- package/dist/tenant/http.d.ts +8 -9
- package/dist/tenant/http.js +13 -27
- package/dist/tenant/jwt.d.ts +16 -0
- package/dist/tenant/jwt.js +50 -0
- package/dist/tenant/module.d.ts +3 -0
- package/dist/tenant/module.js +6 -12
- package/dist/tenant/outbound.d.ts +44 -0
- package/dist/tenant/outbound.js +127 -0
- package/dist/tenant/pool.d.ts +0 -2
- package/dist/tenant/pool.js +0 -18
- package/dist/tenant/reset.d.ts +0 -4
- package/dist/tenant/reset.js +0 -5
- package/dist/tenant/runtime.d.ts +10 -20
- package/dist/tenant/runtime.js +59 -42
- package/dist/tenant/scheduler.d.ts +7 -2
- package/dist/tenant/scheduler.js +7 -3
- package/dist/tenant/session-streams.d.ts +125 -0
- package/dist/tenant/session-streams.js +399 -0
- package/dist/tenant/sessions.d.ts +28 -4
- package/dist/tenant/sessions.js +118 -24
- package/dist/tenant/signing-keys.d.ts +55 -0
- package/dist/tenant/signing-keys.js +172 -0
- package/dist/tenant/slim.d.ts +8 -0
- package/dist/tenant/slim.js +21 -0
- package/dist/tenant/status.d.ts +0 -3
- package/dist/tenant/status.js +19 -12
- package/dist/tenant/streams.d.ts +60 -67
- package/dist/tenant/streams.js +140 -208
- package/dist/tenant/subject-limits.d.ts +16 -0
- package/dist/tenant/subject-limits.js +45 -0
- package/dist/tenant/sweep.d.ts +1 -10
- package/dist/tenant/sweep.js +29 -69
- package/dist/tenant/tokens.d.ts +14 -0
- package/dist/tenant/tokens.js +178 -0
- package/dist/tenant/transcript.d.ts +48 -5
- package/dist/tenant/transcript.js +58 -1
- package/dist/tenant/types.d.ts +29 -7
- package/dist/tenant/worker.d.ts +3 -1
- package/dist/tenant/worker.js +9 -3
- package/dist/vault/service.d.ts +18 -2
- package/dist/vault/service.js +57 -13
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +20 -8
- package/dist/core/executors.d.ts +0 -44
- package/dist/core/executors.js +0 -95
- package/dist/streams/relay.d.ts +0 -37
- package/dist/streams/relay.js +0 -254
- package/dist/tenant/live.d.ts +0 -92
- package/dist/tenant/live.js +0 -290
- package/dist/tenant/routes-tenant.d.ts +0 -12
- package/dist/tenant/routes-tenant.js +0 -137
- package/dist/tenant/routes.d.ts +0 -9
- package/dist/tenant/routes.js +0 -151
package/dist/tenant/commands.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The session command service: `message`, `approve`/`respond
|
|
2
|
+
* The session command service: `message`, `approve`/`respond` and `cancel`,
|
|
3
3
|
* with per-session idempotency keys. A command locks its session and commits its state
|
|
4
4
|
* change, events and wakes in one transaction (the store publishes the events and runs the
|
|
5
5
|
* `afterCommit` wakes), then aborts a cancelled advance and cascades workflow cancels to the
|
|
@@ -7,8 +7,9 @@
|
|
|
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
|
-
* `
|
|
10
|
+
* (every accepted one writes a new checkpoint segment). An Action's outcome
|
|
11
|
+
* (`recordActionOutcome`, from the deliverer) wakes with
|
|
12
|
+
* `action_result:<turnId>:<actionId>:<generation>`.
|
|
12
13
|
*
|
|
13
14
|
* Cancel commits `cancelled` first; the engine host sees it before its next effect and before
|
|
14
15
|
* settlement on any Worker. It then aborts an advance running on this process
|
|
@@ -22,19 +23,21 @@ import { resolveMessageManifest } from "../core/turn-manifest.js";
|
|
|
22
23
|
import { canonical } from "../store/canonical.js";
|
|
23
24
|
import { lockedSession, } from "./context.js";
|
|
24
25
|
import { fail } from "./http.js";
|
|
25
|
-
import {
|
|
26
|
+
import { accessOf } from "./auth.js";
|
|
27
|
+
import { chargeTurn } from "./subject-limits.js";
|
|
26
28
|
import { actionTarget, rebaseSessionState, turnManifestOf, variantStore, } from "./session.js";
|
|
27
29
|
import { signalSessionCancel } from "./streams.js";
|
|
28
30
|
import { toolIds } from "./transcript.js";
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
import { slimModelEffects } from "./slim.js";
|
|
32
|
+
/** `outcome`, or a failed one when a tool's output does not match its stored output schema. */
|
|
33
|
+
export function acceptedOutcome(action, outcome) {
|
|
31
34
|
if (action.kind !== "tool" || !action.outputSchema)
|
|
32
|
-
return
|
|
33
|
-
const value =
|
|
35
|
+
return outcome;
|
|
36
|
+
const value = outcome.value;
|
|
34
37
|
// Only a result carries output: failures, denials, interactions and deferrals pass as sent.
|
|
35
38
|
if (!isResultToolValue(value))
|
|
36
|
-
return
|
|
37
|
-
//
|
|
39
|
+
return outcome;
|
|
40
|
+
// Endpoints wrap successful tool output as `{ kind: "completed", output }`.
|
|
38
41
|
// Validate the tool payload, not the outcome envelope.
|
|
39
42
|
const candidate = completedToolOutput(value);
|
|
40
43
|
let matches = false;
|
|
@@ -45,16 +48,13 @@ function acceptedToolResult(action, command) {
|
|
|
45
48
|
matches = false;
|
|
46
49
|
}
|
|
47
50
|
if (matches)
|
|
48
|
-
return
|
|
51
|
+
return outcome;
|
|
49
52
|
return {
|
|
50
|
-
...
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
code: "tool.invalid-output",
|
|
56
|
-
message: "Tool result does not match the output schema stored on the action",
|
|
57
|
-
},
|
|
53
|
+
...outcome,
|
|
54
|
+
value: {
|
|
55
|
+
kind: "failed",
|
|
56
|
+
code: "tool.invalid-output",
|
|
57
|
+
message: "Tool result does not match the output schema stored on the action",
|
|
58
58
|
},
|
|
59
59
|
};
|
|
60
60
|
}
|
|
@@ -89,18 +89,15 @@ export async function command(ctx, id, input, scope) {
|
|
|
89
89
|
const { store } = ctx;
|
|
90
90
|
const cascadeCancelIds = [];
|
|
91
91
|
const response = await store.tx(async (t) => {
|
|
92
|
-
const s = await lockedSession(t, id,
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
else if (scope.kind === "executor")
|
|
103
|
-
fail(403, "Application credential required");
|
|
92
|
+
const s = await lockedSession(t, id, accessOf(scope));
|
|
93
|
+
const command = input;
|
|
94
|
+
// A person's token may not replace the instructions of a turn.
|
|
95
|
+
if (scope.kind === "token" &&
|
|
96
|
+
command.type === "message" &&
|
|
97
|
+
command.manifest !== undefined)
|
|
98
|
+
fail(403, "A subject token cannot send message.manifest", {
|
|
99
|
+
code: "scope_required",
|
|
100
|
+
});
|
|
104
101
|
const key = commandKey(id, command.idempotencyKey);
|
|
105
102
|
const existing = await t.get("commands", key);
|
|
106
103
|
if (existing) {
|
|
@@ -111,94 +108,16 @@ export async function command(ctx, id, input, scope) {
|
|
|
111
108
|
let event;
|
|
112
109
|
// The turn a `cancel` ended, if one was active.
|
|
113
110
|
let cancelledTurnId = null;
|
|
114
|
-
if (command.type === "
|
|
115
|
-
const action = (await t.get("actions", command.actionId));
|
|
116
|
-
const prior = await t.get("effects", command.actionId);
|
|
117
|
-
if (action.status === "completed") {
|
|
118
|
-
if (action.claimId !== command.claimId ||
|
|
119
|
-
action.generation !== command.generation ||
|
|
120
|
-
canonical(prior.outcome) !== canonical(command.outcome))
|
|
121
|
-
fail(409, "Conflicting action result");
|
|
122
|
-
await t.put("commands", key, { command, response: prior.receipt });
|
|
123
|
-
return prior.receipt;
|
|
124
|
-
}
|
|
125
|
-
if (s.status === "cancelled" ||
|
|
126
|
-
s.activeTurnId !== action.turnId ||
|
|
127
|
-
action.status !== "claimed" ||
|
|
128
|
-
action.claimId !== command.claimId ||
|
|
129
|
-
action.generation !== command.generation ||
|
|
130
|
-
Date.parse(action.leaseExpiresAt) <= Date.now())
|
|
131
|
-
fail(409, "Stale, expired, or cancelled claim");
|
|
132
|
-
action.status = "completed";
|
|
133
|
-
await t.put("actions", action.actionId, action);
|
|
134
|
-
prior.status = "completed";
|
|
135
|
-
prior.outcome = command.outcome;
|
|
136
|
-
s.status = "runnable";
|
|
137
|
-
const resultWake = {
|
|
138
|
-
reason: "action_result",
|
|
139
|
-
dedupeKey: `action_result:${action.turnId}:${action.actionId}:${action.generation}`,
|
|
140
|
-
};
|
|
141
|
-
t.afterCommit(() => ctx.wake(id, resultWake));
|
|
142
|
-
event = await t.event(id, s.activeTurnId, "action.completed", {
|
|
143
|
-
actionId: action.actionId,
|
|
144
|
-
...actionTarget(action),
|
|
145
|
-
kind: action.kind,
|
|
146
|
-
...(action.kind === "tool" ? toolIds(action.context) : {}),
|
|
147
|
-
result: command.outcome.value,
|
|
148
|
-
});
|
|
149
|
-
if (action.kind === "verify") {
|
|
150
|
-
const verdict = command.outcome.value;
|
|
151
|
-
if (verdict?.kind !== "failed") {
|
|
152
|
-
await t.event(id, s.activeTurnId, "loop.verified", {
|
|
153
|
-
path: String(action.context?.loopPath ?? action.path ?? ""),
|
|
154
|
-
n: Number(action.context?.n ?? 1),
|
|
155
|
-
pass: Boolean(verdict?.pass),
|
|
156
|
-
...(verdict?.feedback !== undefined
|
|
157
|
-
? { feedback: verdict.feedback }
|
|
158
|
-
: {}),
|
|
159
|
-
...(verdict?.data !== undefined ? { data: verdict.data } : {}),
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
else if (action.kind === "fn" && action.context?.role === "decide") {
|
|
164
|
-
const decision = command.outcome.value;
|
|
165
|
-
await t.event(id, s.activeTurnId, "loop.decided", {
|
|
166
|
-
path: String(action.context?.loopPath ?? action.path ?? ""),
|
|
167
|
-
n: Number(action.context?.n ?? 1),
|
|
168
|
-
next: "output" in decision && !("input" in decision)
|
|
169
|
-
? "output"
|
|
170
|
-
: "input",
|
|
171
|
-
patched: Boolean(decision.agent),
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
const receipt = {
|
|
175
|
-
status: "accepted",
|
|
176
|
-
turnId: s.activeTurnId,
|
|
177
|
-
cursor: event.cursor,
|
|
178
|
-
requestId: command.requestId,
|
|
179
|
-
};
|
|
180
|
-
prior.receipt = receipt;
|
|
181
|
-
await t.put("effects", action.actionId, prior);
|
|
182
|
-
if (isWorkflowManifest(s.manifest) && s.activeTurnId) {
|
|
183
|
-
await wakeForQueuedEffects({
|
|
184
|
-
t,
|
|
185
|
-
workflowSessionId: id,
|
|
186
|
-
turnId: s.activeTurnId,
|
|
187
|
-
limits: ctx.flowLimits,
|
|
188
|
-
schedule: ctx.wake,
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
else if (command.type === "cancel") {
|
|
111
|
+
if (command.type === "cancel") {
|
|
193
112
|
cancelledTurnId = s.activeTurnId;
|
|
194
113
|
const workflowCancel = isWorkflowManifest(s.manifest);
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
114
|
+
// Linked sessions this one started: a workflow's agents, or flow agents an agent
|
|
115
|
+
// uses as tools.
|
|
116
|
+
const cascade = await planCancelCascade({
|
|
117
|
+
t,
|
|
118
|
+
workflowSessionId: id,
|
|
119
|
+
turnId: cancelledTurnId,
|
|
120
|
+
});
|
|
202
121
|
s.status = "cancelled";
|
|
203
122
|
if (workflowCancel) {
|
|
204
123
|
await fenceWorkflowActions({
|
|
@@ -210,10 +129,10 @@ export async function command(ctx, id, input, scope) {
|
|
|
210
129
|
else if (cancelledTurnId !== null) {
|
|
211
130
|
for (const a of await t.actionsForSession(id, {
|
|
212
131
|
turnId: cancelledTurnId,
|
|
213
|
-
statuses: ["pending", "
|
|
132
|
+
statuses: ["pending", "delivering"],
|
|
214
133
|
})) {
|
|
215
|
-
//
|
|
216
|
-
a.status = a.status === "
|
|
134
|
+
// Delivered work may already have an external effect. Preserve it for reconciliation.
|
|
135
|
+
a.status = a.status === "pending" ? "cancelled" : "uncertain";
|
|
217
136
|
await t.put("actions", a.actionId, a);
|
|
218
137
|
const effect = await t.get("effects", a.actionId);
|
|
219
138
|
if (effect) {
|
|
@@ -232,6 +151,7 @@ export async function command(ctx, id, input, scope) {
|
|
|
232
151
|
event = await t.event(id, cancelledTurnId, "turn.cancelled", {
|
|
233
152
|
reason: command.reason,
|
|
234
153
|
});
|
|
154
|
+
await slimModelEffects(t, id, cancelledTurnId);
|
|
235
155
|
// The process running the advance aborts it on `session.cancel` (tenant/control).
|
|
236
156
|
t.afterCommit(() => signalSessionCancel(ctx, id));
|
|
237
157
|
s.activeTurnId = null;
|
|
@@ -245,21 +165,22 @@ export async function command(ctx, id, input, scope) {
|
|
|
245
165
|
s.error = undefined;
|
|
246
166
|
// Cascade: cancel linked agent sessions deepest-first (after fencing this session), each
|
|
247
167
|
// in its own transaction after this one commits (child sessions are never locked here).
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
cascadeCancelIds.push(agentId);
|
|
256
|
-
}
|
|
168
|
+
for (const agentId of cascade.agentSessionIds) {
|
|
169
|
+
const agent = await t.get("sessions", agentId);
|
|
170
|
+
if (!agent ||
|
|
171
|
+
!agent.activeTurnId ||
|
|
172
|
+
["idle", "completed", "failed", "cancelled"].includes(agent.status))
|
|
173
|
+
continue;
|
|
174
|
+
cascadeCancelIds.push(agentId);
|
|
257
175
|
}
|
|
258
176
|
}
|
|
259
177
|
else {
|
|
260
178
|
if (command.type === "message") {
|
|
261
179
|
if (!["idle", "completed", "failed", "cancelled"].includes(s.status))
|
|
262
180
|
fail(409, "Session has active or unresolved work");
|
|
181
|
+
// After the replay check above, so a retried message is never charged twice.
|
|
182
|
+
if (scope.kind === "token" && scope.limits)
|
|
183
|
+
await chargeTurn(t, scope.subject, scope.limits);
|
|
263
184
|
s.turnStartState = s.state;
|
|
264
185
|
s.activeTurnId = randomUUID();
|
|
265
186
|
if (isWorkflowManifest(s.manifest)) {
|
|
@@ -391,3 +312,74 @@ export async function command(ctx, id, input, scope) {
|
|
|
391
312
|
}
|
|
392
313
|
return response;
|
|
393
314
|
}
|
|
315
|
+
/**
|
|
316
|
+
* Completes a delivered Action with `outcome`, in the caller's transaction, which
|
|
317
|
+
* holds the lock of the Action's session `s`: the Action and its effect, the
|
|
318
|
+
* `action.completed` event (and `loop.verified` or `loop.decided`), the wake that resumes the
|
|
319
|
+
* turn, and queued workflow effects. The Action deliverer and the background-result callback
|
|
320
|
+
* both record outcomes here. It sets `s.status`; the caller writes `s`.
|
|
321
|
+
*/
|
|
322
|
+
export async function recordActionOutcome(t, ctx, s, action, received, options = {}) {
|
|
323
|
+
const outcome = acceptedOutcome(action, received);
|
|
324
|
+
const prior = await t.get("effects", action.actionId);
|
|
325
|
+
action.status = "completed";
|
|
326
|
+
await t.put("actions", action.actionId, action);
|
|
327
|
+
prior.status = "completed";
|
|
328
|
+
prior.outcome = outcome;
|
|
329
|
+
s.status = "runnable";
|
|
330
|
+
const resultWake = {
|
|
331
|
+
reason: "action_result",
|
|
332
|
+
dedupeKey: `action_result:${action.turnId}:${action.actionId}:${action.generation}`,
|
|
333
|
+
};
|
|
334
|
+
t.afterCommit(() => ctx.wake(s.id, resultWake));
|
|
335
|
+
const event = await t.event(s.id, s.activeTurnId, "action.completed", {
|
|
336
|
+
actionId: action.actionId,
|
|
337
|
+
...actionTarget(action),
|
|
338
|
+
kind: action.kind,
|
|
339
|
+
...(action.kind === "tool" ? toolIds(action.context) : {}),
|
|
340
|
+
result: outcome.value,
|
|
341
|
+
});
|
|
342
|
+
if (action.kind === "verify") {
|
|
343
|
+
const verdict = outcome.value;
|
|
344
|
+
if (verdict?.kind !== "failed") {
|
|
345
|
+
await t.event(s.id, s.activeTurnId, "loop.verified", {
|
|
346
|
+
path: String(action.context?.loopPath ?? action.path ?? ""),
|
|
347
|
+
n: Number(action.context?.n ?? 1),
|
|
348
|
+
pass: Boolean(verdict?.pass),
|
|
349
|
+
...(verdict?.feedback !== undefined
|
|
350
|
+
? { feedback: verdict.feedback }
|
|
351
|
+
: {}),
|
|
352
|
+
...(verdict?.data !== undefined ? { data: verdict.data } : {}),
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
else if (action.kind === "fn" && action.context?.role === "decide") {
|
|
357
|
+
const decision = outcome.value;
|
|
358
|
+
await t.event(s.id, s.activeTurnId, "loop.decided", {
|
|
359
|
+
path: String(action.context?.loopPath ?? action.path ?? ""),
|
|
360
|
+
n: Number(action.context?.n ?? 1),
|
|
361
|
+
next: "output" in decision && !("input" in decision)
|
|
362
|
+
? "output"
|
|
363
|
+
: "input",
|
|
364
|
+
patched: Boolean(decision.agent),
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
const receipt = {
|
|
368
|
+
status: "accepted",
|
|
369
|
+
turnId: s.activeTurnId,
|
|
370
|
+
cursor: event.cursor,
|
|
371
|
+
...(options.requestId === undefined ? {} : { requestId: options.requestId }),
|
|
372
|
+
};
|
|
373
|
+
prior.receipt = receipt;
|
|
374
|
+
await t.put("effects", action.actionId, prior);
|
|
375
|
+
if (isWorkflowManifest(s.manifest) && s.activeTurnId) {
|
|
376
|
+
await wakeForQueuedEffects({
|
|
377
|
+
t,
|
|
378
|
+
workflowSessionId: s.id,
|
|
379
|
+
turnId: s.activeTurnId,
|
|
380
|
+
limits: ctx.flowLimits,
|
|
381
|
+
schedule: ctx.wake,
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
return { event, receipt };
|
|
385
|
+
}
|
package/dist/tenant/context.d.ts
CHANGED
|
@@ -5,7 +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),
|
|
8
|
+
* commit), deliveries through `t.afterCommit(() => ctx.deliver(actionId))`, and advances through
|
|
9
9
|
* `t.afterCommit(() => ctx.wake(id, { reason, dedupeKey }))`. It never publishes or notifies
|
|
10
10
|
* itself; `runtime.ts` wires the streams with `wireStreams()`. No external I/O runs inside a
|
|
11
11
|
* tx.
|
|
@@ -13,12 +13,11 @@
|
|
|
13
13
|
* `wake` goes to `DurableExecution.wake`, which calls the Tenant's `advance` (`advance.ts`)
|
|
14
14
|
* under ownership (§10.6); `abortLocal` aborts an advance running on this process.
|
|
15
15
|
*/
|
|
16
|
-
import type { SubjectScope, TenantEnvelope } from "@nylorun/core/contracts";
|
|
16
|
+
import type { RoleLimits, SubjectScope, TenantEnvelope } from "@nylorun/core/contracts";
|
|
17
17
|
import type { DurableCheckpoint, FlowCheckpoint } from "@nylorun/harness/run";
|
|
18
|
-
import type { AgentManifest, JsonValue } from "@nylorun/core/define";
|
|
18
|
+
import type { AgentManifest, JsonValue, SandboxManifest } from "@nylorun/core/define";
|
|
19
19
|
import type { CredentialSelection } from "@nylorun/core/contracts";
|
|
20
20
|
import type { SessionStore, Tx } from "../store/types.js";
|
|
21
|
-
import type { ExecutorRecord, ExecutorRegistry } from "../core/executors.js";
|
|
22
21
|
import type { FlowLimits } from "../core/limits.js";
|
|
23
22
|
import type { ModelProvider } from "../core/provider.js";
|
|
24
23
|
import type { VaultService } from "../vault/service.js";
|
|
@@ -26,9 +25,10 @@ import type { McpPool } from "../mcp/pool.js";
|
|
|
26
25
|
import type { McpDiagnostic, McpSnapshot } from "../mcp/snapshot.js";
|
|
27
26
|
import type { SandboxManager } from "../sandbox/manager.js";
|
|
28
27
|
import type { TenantConfig } from "./types.js";
|
|
29
|
-
import type {
|
|
28
|
+
import type { SessionStreams } from "./session-streams.js";
|
|
30
29
|
import type { StuckInvocation, Wake } from "../execution/types.js";
|
|
31
30
|
import type { WorkState } from "./scheduler.js";
|
|
31
|
+
import type { SigningKeys } from "./signing-keys.js";
|
|
32
32
|
export interface Session {
|
|
33
33
|
id: string;
|
|
34
34
|
agentId: string;
|
|
@@ -64,31 +64,73 @@ export interface Session {
|
|
|
64
64
|
/** Session id that keys the shared sandbox; absent means this session owns it. */
|
|
65
65
|
sandboxOwnerId?: string;
|
|
66
66
|
/**
|
|
67
|
-
* The
|
|
68
|
-
*
|
|
67
|
+
* The sandbox chosen when the session was opened, resolved against the Tenant's limits, or
|
|
68
|
+
* inherited from the session it shares with. Absent when the session has no sandbox or its
|
|
69
|
+
* definition declares one (`.sandbox()`).
|
|
69
70
|
*/
|
|
70
|
-
|
|
71
|
+
sandbox?: SandboxManifest;
|
|
72
|
+
/** Where `sandbox` came from. */
|
|
73
|
+
sandboxSource?: "default" | "inline" | "shared";
|
|
71
74
|
}
|
|
72
75
|
export type AuthScope = {
|
|
73
76
|
kind: "application";
|
|
74
77
|
principalId: string;
|
|
75
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* A subject token (Host feature `subject-tokens`): one subject, the role's scopes and agents
|
|
81
|
+
* narrowed by the token, until `expiresAt` (ms) or the subject's epoch moves past `epoch`.
|
|
82
|
+
*/
|
|
83
|
+
| {
|
|
84
|
+
kind: "token";
|
|
85
|
+
subject: string;
|
|
86
|
+
scopes: ReadonlySet<SubjectScope>;
|
|
87
|
+
agents: ReadonlySet<string> | "*";
|
|
88
|
+
role: string;
|
|
89
|
+
limits?: RoleLimits;
|
|
90
|
+
epoch: number;
|
|
91
|
+
expiresAt: number;
|
|
92
|
+
tokenId: string;
|
|
93
|
+
keyId: string;
|
|
94
|
+
}
|
|
76
95
|
/** An application principal acting for `subject` (`Nylorun-Subject`), narrowed to `scopes`. */
|
|
77
96
|
| {
|
|
78
97
|
kind: "subject";
|
|
79
98
|
principalId: string;
|
|
80
99
|
subject: string;
|
|
81
100
|
scopes: ReadonlySet<SubjectScope>;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
101
|
+
}
|
|
102
|
+
/** No credential, on a route that serves public data (`RouteAccess.anonymous`). */
|
|
103
|
+
| {
|
|
104
|
+
kind: "anonymous";
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* A delivery token (Action endpoints): the Runtime's own token for one delivery of one
|
|
108
|
+
* Action, presented back by the Action endpoint on that Action's callbacks only.
|
|
109
|
+
*/
|
|
110
|
+
| {
|
|
111
|
+
kind: "delivery";
|
|
112
|
+
actionId: string;
|
|
113
|
+
agentId: string;
|
|
114
|
+
generation: number;
|
|
115
|
+
expiresAt: number;
|
|
116
|
+
tokenId: string;
|
|
117
|
+
keyId: string;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* A publishable key with no bearer (Host feature `browser-access`): what the policy grants
|
|
121
|
+
* `anon`, at most `agents:read`. It owns no session or vault.
|
|
122
|
+
*/
|
|
123
|
+
| {
|
|
124
|
+
kind: "publishable";
|
|
125
|
+
keyId: string;
|
|
126
|
+
scopes: ReadonlySet<SubjectScope>;
|
|
127
|
+
agents: ReadonlySet<string> | "*";
|
|
85
128
|
};
|
|
86
129
|
export interface TenantContext {
|
|
87
130
|
readonly config: TenantConfig;
|
|
88
131
|
readonly envelope: TenantEnvelope;
|
|
89
132
|
readonly store: SessionStore;
|
|
90
133
|
readonly vault: VaultService;
|
|
91
|
-
readonly registry: ExecutorRegistry;
|
|
92
134
|
readonly mcp: McpPool;
|
|
93
135
|
readonly sandbox: SandboxManager;
|
|
94
136
|
readonly flowLimits: FlowLimits;
|
|
@@ -101,8 +143,10 @@ export interface TenantContext {
|
|
|
101
143
|
closed: boolean;
|
|
102
144
|
/** Advances running on this process, for `abortLocal`, drain and close. */
|
|
103
145
|
readonly work: WorkState;
|
|
104
|
-
/** Live delivery over Durable Streams:
|
|
105
|
-
readonly
|
|
146
|
+
/** Live delivery over Durable Streams: one `SessionStream` per observed session, and the streams wiring. */
|
|
147
|
+
readonly sessionStreams: SessionStreams;
|
|
148
|
+
/** The Tenant's signing keys for subject tokens. */
|
|
149
|
+
readonly signingKeys: SigningKeys;
|
|
106
150
|
/** The Worker id this process writes as session `owner` (§10.6). */
|
|
107
151
|
readonly workerId: string;
|
|
108
152
|
/** How long an advance's ownership lease lasts; the heartbeat renews it. */
|
|
@@ -115,17 +159,30 @@ export interface TenantContext {
|
|
|
115
159
|
wake(sessionId: string, wake: Wake): Promise<void>;
|
|
116
160
|
/** Seam: abort the advance of a session running in this process, if any. */
|
|
117
161
|
abortLocal(sessionId: string): void;
|
|
162
|
+
/**
|
|
163
|
+
* Seam: deliver an Action to its agent's Action endpoint (`DurableExecution.deliver`). Call it
|
|
164
|
+
* from `t.afterCommit`. Dropped while the Tenant is closing; the sweep re-sends it.
|
|
165
|
+
*/
|
|
166
|
+
deliver(actionId: string): Promise<void>;
|
|
118
167
|
/** This Tenant's execution invocations that need an operator, for Tenant status. */
|
|
119
168
|
readonly stuckInvocations?: () => Promise<StuckInvocation[]>;
|
|
120
169
|
/** Callbacks the Tenant sweep runs after its own steps (`sweep.ts`). */
|
|
121
170
|
readonly sweepHooks: ReadonlySet<() => Promise<void>>;
|
|
122
|
-
/** Adds a sweep callback
|
|
171
|
+
/** Adds a sweep callback. Returns a function that removes it. */
|
|
123
172
|
onSweep(hook: () => Promise<void>): () => void;
|
|
124
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* What a request acting for a person may reach: that person's sessions and vaults, and, for a
|
|
176
|
+
* subject token, only sessions of the agents its role allows. Undefined means the whole Tenant.
|
|
177
|
+
*/
|
|
178
|
+
export interface SessionAccess {
|
|
179
|
+
readonly owner: string;
|
|
180
|
+
readonly agents?: ReadonlySet<string>;
|
|
181
|
+
}
|
|
125
182
|
/** The session, or a 404. Reads without locking. */
|
|
126
|
-
export declare function sessionOf(t: Tx, id: string,
|
|
183
|
+
export declare function sessionOf(t: Tx, id: string, access?: SessionAccess): Promise<Session>;
|
|
127
184
|
/** Locks the session for the rest of the transaction and returns it, or a 404. */
|
|
128
|
-
export declare function lockedSession(t: Tx, id: string,
|
|
185
|
+
export declare function lockedSession(t: Tx, id: string, access?: SessionAccess): Promise<Session>;
|
|
129
186
|
/** An advance's hold on its session (§10.6): every write the advance makes presents `epoch`. */
|
|
130
187
|
export interface Lease {
|
|
131
188
|
readonly sessionId: string;
|
|
@@ -139,7 +196,7 @@ export interface Lease {
|
|
|
139
196
|
*/
|
|
140
197
|
export declare function ownedSession(t: Tx, lease: Lease, id: string): Promise<Session>;
|
|
141
198
|
/** The session read in its own transaction, or a 404. */
|
|
142
|
-
export declare function loadSession(ctx: TenantContext, id: string,
|
|
199
|
+
export declare function loadSession(ctx: TenantContext, id: string, access?: SessionAccess): Promise<Session>;
|
|
143
200
|
/**
|
|
144
201
|
* A synchronous lookup over `start` and every session its `sandboxOwnerId` chain names, for
|
|
145
202
|
* the sandbox-sharing helpers, which walk that chain synchronously.
|
package/dist/tenant/context.js
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
import { fail } from "./http.js";
|
|
2
2
|
/**
|
|
3
|
-
* `
|
|
4
|
-
* 404 as a missing
|
|
3
|
+
* `access`, when set, limits the request to the owner's sessions of the allowed agents:
|
|
4
|
+
* anything else is the same 404 as a missing session, so a subject cannot learn which session
|
|
5
|
+
* ids exist.
|
|
5
6
|
*/
|
|
6
|
-
function owned(session,
|
|
7
|
-
if (!session ||
|
|
7
|
+
function owned(session, access) {
|
|
8
|
+
if (!session ||
|
|
9
|
+
(access !== undefined &&
|
|
10
|
+
(session.ownerUserId !== access.owner ||
|
|
11
|
+
(access.agents !== undefined && !access.agents.has(session.agentId)))))
|
|
8
12
|
return fail(404, "Session not found");
|
|
9
13
|
return session;
|
|
10
14
|
}
|
|
11
15
|
/** The session, or a 404. Reads without locking. */
|
|
12
|
-
export async function sessionOf(t, id,
|
|
13
|
-
return owned(await t.get("sessions", id),
|
|
16
|
+
export async function sessionOf(t, id, access) {
|
|
17
|
+
return owned(await t.get("sessions", id), access);
|
|
14
18
|
}
|
|
15
19
|
/** Locks the session for the rest of the transaction and returns it, or a 404. */
|
|
16
|
-
export async function lockedSession(t, id,
|
|
17
|
-
return owned(await t.lockSession(id),
|
|
20
|
+
export async function lockedSession(t, id, access) {
|
|
21
|
+
return owned(await t.lockSession(id), access);
|
|
18
22
|
}
|
|
19
23
|
/**
|
|
20
24
|
* Locks `id` for the advance holding `lease`. The advance's own session is epoch-checked: a
|
|
@@ -27,8 +31,8 @@ export async function ownedSession(t, lease, id) {
|
|
|
27
31
|
return lockedSession(t, id);
|
|
28
32
|
}
|
|
29
33
|
/** The session read in its own transaction, or a 404. */
|
|
30
|
-
export function loadSession(ctx, id,
|
|
31
|
-
return ctx.store.tx((t) => sessionOf(t, id,
|
|
34
|
+
export function loadSession(ctx, id, access) {
|
|
35
|
+
return ctx.store.tx((t) => sessionOf(t, id, access));
|
|
32
36
|
}
|
|
33
37
|
/**
|
|
34
38
|
* A synchronous lookup over `start` and every session its `sandboxOwnerId` chain names, for
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { AuthScope, TenantContext } from "./context.js";
|
|
2
|
+
/** What a delivery token is for: one delivery of an Action, or a ping. */
|
|
3
|
+
export type DeliveryTokenFor = {
|
|
4
|
+
kind: "action";
|
|
5
|
+
actionId: string;
|
|
6
|
+
agentId: string;
|
|
7
|
+
generation: number;
|
|
8
|
+
} | {
|
|
9
|
+
kind: "ping";
|
|
10
|
+
agentId: string;
|
|
11
|
+
};
|
|
12
|
+
export interface MintedDeliveryToken {
|
|
13
|
+
token: string;
|
|
14
|
+
/** Milliseconds since the epoch. */
|
|
15
|
+
expiresAt: number;
|
|
16
|
+
tokenId: string;
|
|
17
|
+
keyId: string;
|
|
18
|
+
}
|
|
19
|
+
/** The request scope of a verified delivery token (`AuthScope` kind `delivery`). */
|
|
20
|
+
export type DeliveryScope = Extract<AuthScope, {
|
|
21
|
+
kind: "delivery";
|
|
22
|
+
}>;
|
|
23
|
+
/** base64url SHA-256 of a request body: the token's `bdy` claim. */
|
|
24
|
+
export declare function bodyHash(body: string | Uint8Array): string;
|
|
25
|
+
/**
|
|
26
|
+
* Signs a token for `delivery`, for the endpoint at `audience`, covering `body`. `ttlSeconds`
|
|
27
|
+
* is capped at `DELIVERY_TOKEN_MAX_TTL_SECONDS`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function mintDeliveryToken(ctx: TenantContext, options: {
|
|
30
|
+
for: DeliveryTokenFor;
|
|
31
|
+
audience: string;
|
|
32
|
+
body: string | Uint8Array;
|
|
33
|
+
ttlSeconds: number;
|
|
34
|
+
}): Promise<MintedDeliveryToken>;
|
|
35
|
+
/**
|
|
36
|
+
* Verifies a delivery token presented as a bearer. Answers `401 token_expired` for an expired
|
|
37
|
+
* token or a revoked key, and the opaque 404 for everything else, a ping token included: only
|
|
38
|
+
* an Action's token reaches its callbacks. Whether the Action is still being delivered at the
|
|
39
|
+
* token's generation is checked where the Action is read (`auth.ts` `scoped`).
|
|
40
|
+
*/
|
|
41
|
+
export declare function verifyDeliveryToken(ctx: TenantContext, raw: string): Promise<DeliveryScope>;
|