@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/core/flow-host.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type
|
|
1
|
+
import type { EffectOutcome } from "@nylorun/core/contracts";
|
|
2
|
+
import { type JsonValue, type WorkflowManifest } from "@nylorun/core/define";
|
|
3
3
|
import type { HostEffect } from "@nylorun/harness/run";
|
|
4
4
|
import type { Wake } from "../execution/types.js";
|
|
5
5
|
import type { EffectDoc, Tx } from "../store/types.js";
|
|
@@ -20,6 +20,31 @@ export declare function isWorkflowManifest(manifest: unknown): manifest is Workf
|
|
|
20
20
|
/** Flow tool-node effect: `tool` with path/key and no agent capabilityId. */
|
|
21
21
|
export declare function isFlowToolEffect(request: HostEffect): boolean;
|
|
22
22
|
export declare function isFlowEffect(request: HostEffect): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* What a flow `agent` effect sends its linked session (D12): the stage's input, and, when the
|
|
25
|
+
* engine sent the flow's own input beside it, that input first as the original request. Pure,
|
|
26
|
+
* so a replayed effect sends the same message.
|
|
27
|
+
*/
|
|
28
|
+
export declare function linkedMessageInput(body: {
|
|
29
|
+
readonly input: JsonValue;
|
|
30
|
+
readonly flowInput?: JsonValue;
|
|
31
|
+
}): JsonValue;
|
|
32
|
+
/**
|
|
33
|
+
* Settle a flow `agent` effect with its linked turn's outcome, in the caller's transaction,
|
|
34
|
+
* which holds the workflow session's lock. A verifier agent's verdict is recorded as
|
|
35
|
+
* `loop.verified`; an outcome that is not a verdict fails the Loop in the engine instead.
|
|
36
|
+
*/
|
|
37
|
+
export declare function settleAgentEffect(input: {
|
|
38
|
+
readonly t: Tx;
|
|
39
|
+
readonly effect: FlowEffect;
|
|
40
|
+
readonly outcome: EffectOutcome;
|
|
41
|
+
}): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Record a Loop verifier's verdict as `loop.verified`: a verifier agent's output, or an HTTP
|
|
44
|
+
* verifier's answer (`role: "verify-http"`, a completed HTTP outcome). Anything that is not a
|
|
45
|
+
* verdict records nothing; the engine fails the Loop on it.
|
|
46
|
+
*/
|
|
47
|
+
export declare function recordVerdict(t: Tx, request: HostEffect, value: unknown): Promise<void>;
|
|
23
48
|
export type FlowLink = {
|
|
24
49
|
readonly workflowSessionId: string;
|
|
25
50
|
readonly path: string;
|
|
@@ -60,30 +85,28 @@ export type FlowWait = {
|
|
|
60
85
|
readonly path: string;
|
|
61
86
|
readonly interactionId: string;
|
|
62
87
|
readonly kind: string;
|
|
88
|
+
/** The interaction as asked (`prompt`, `metadata`), when the wait records it. */
|
|
89
|
+
readonly interaction?: unknown;
|
|
63
90
|
readonly invocationId?: string;
|
|
64
91
|
readonly wait?: unknown;
|
|
65
92
|
readonly status?: string;
|
|
66
93
|
};
|
|
67
94
|
export type CancelSiblingResult = {
|
|
68
|
-
readonly cancelledActions: string[];
|
|
69
|
-
readonly uncertainActions: string[];
|
|
70
95
|
readonly agentSessionIds: string[];
|
|
71
96
|
};
|
|
72
97
|
export type CascadeCancelPlan = {
|
|
73
98
|
/** Linked agent sessions to cancel, deepest path first. */
|
|
74
99
|
readonly agentSessionIds: string[];
|
|
75
|
-
readonly pendingActionIds: string[];
|
|
76
|
-
readonly deliveringActionIds: string[];
|
|
77
100
|
};
|
|
78
101
|
/** Wakes a session after commit (`DurableExecution.wake` through the Tenant context). */
|
|
79
102
|
type Schedule = (sessionId: string, wake: Wake) => void | Promise<void>;
|
|
80
103
|
/** Effect documents as the flow host writes them. */
|
|
81
104
|
export type FlowEffect = EffectDoc & {
|
|
82
105
|
agentSessionId?: string;
|
|
83
|
-
outcome?:
|
|
106
|
+
outcome?: EffectOutcome;
|
|
84
107
|
error?: string;
|
|
85
108
|
};
|
|
86
|
-
/** Active agent turns +
|
|
109
|
+
/** Active agent turns + tool nodes in flight for one workflow turn. */
|
|
87
110
|
export declare function countActiveFlowWork(t: Tx, workflowSessionId: string, turnId: string): Promise<number>;
|
|
88
111
|
/** The `commands` document key of a session command's idempotency key (`tenant/commands.ts`). */
|
|
89
112
|
export declare function commandKey(sessionId: string, idempotencyKey: string): string;
|
|
@@ -134,10 +157,7 @@ export declare function reconcilePendingAgentEffect(input: {
|
|
|
134
157
|
}): Promise<boolean>;
|
|
135
158
|
/** Path depth for deepest-first cancel ordering. */
|
|
136
159
|
export declare function pathDepth(path: string): number;
|
|
137
|
-
/**
|
|
138
|
-
* Plan a cancel cascade for a workflow session: linked agents deepest first,
|
|
139
|
-
* then pending → cancelled and delivering → uncertain actions (SD-P11 / WF-R53).
|
|
140
|
-
*/
|
|
160
|
+
/** Plan a cancel cascade for a workflow session: linked agents deepest first (SD-P11 / WF-R53). */
|
|
141
161
|
export declare function planCancelCascade(input: {
|
|
142
162
|
readonly t: Tx;
|
|
143
163
|
readonly workflowSessionId: string;
|
|
@@ -145,8 +165,7 @@ export declare function planCancelCascade(input: {
|
|
|
145
165
|
}): Promise<CascadeCancelPlan>;
|
|
146
166
|
/**
|
|
147
167
|
* Cancel sibling work under a Parallel/Map parent when one branch fails (PAR-R6).
|
|
148
|
-
* Agent turns listed in `agentSessionIds` are returned for the caller to cancel
|
|
149
|
-
* pending actions → cancelled, delivering tool actions → uncertain.
|
|
168
|
+
* Agent turns listed in `agentSessionIds` are returned for the caller to cancel.
|
|
150
169
|
* When `cancelEffectIds` is provided, those effects are marked cancelled and their
|
|
151
170
|
* paths are included in the sibling path set.
|
|
152
171
|
*/
|
|
@@ -159,18 +178,12 @@ export declare function cancelSiblingWork(input: {
|
|
|
159
178
|
/** Effect ids the flow engine marked for fail-fast cancel. */
|
|
160
179
|
readonly cancelEffectIds?: readonly string[];
|
|
161
180
|
}): Promise<CancelSiblingResult>;
|
|
162
|
-
/**
|
|
163
|
-
|
|
164
|
-
* delivering→uncertain).
|
|
165
|
-
*/
|
|
166
|
-
export declare function fenceWorkflowActions(input: {
|
|
181
|
+
/** Cancel a cancelled workflow turn's queued effects, which never started. */
|
|
182
|
+
export declare function cancelQueuedEffects(input: {
|
|
167
183
|
readonly t: Tx;
|
|
168
184
|
readonly workflowSessionId: string;
|
|
169
185
|
readonly turnId: string | null;
|
|
170
|
-
}): Promise<
|
|
171
|
-
cancelled: string[];
|
|
172
|
-
uncertain: string[];
|
|
173
|
-
}>;
|
|
186
|
+
}): Promise<string[]>;
|
|
174
187
|
/**
|
|
175
188
|
* Aggregate human waits across the workflow session and linked agent sessions (WF-R51).
|
|
176
189
|
*/
|
|
@@ -178,6 +191,14 @@ export declare function aggregateWaits(input: {
|
|
|
178
191
|
readonly t: Tx;
|
|
179
192
|
readonly workflowSessionId: string;
|
|
180
193
|
}): Promise<FlowWait[]>;
|
|
194
|
+
/**
|
|
195
|
+
* The workflow-owned interaction `interactionId` among a workflow's `waits` (a tool node that
|
|
196
|
+
* asked), or undefined.
|
|
197
|
+
*/
|
|
198
|
+
export declare function flowInteractionOf(raw: unknown, workflowSessionId: string, interactionId: string): {
|
|
199
|
+
readonly id: string;
|
|
200
|
+
readonly kind: string;
|
|
201
|
+
} | undefined;
|
|
181
202
|
/**
|
|
182
203
|
* Look up which session owns an interaction id. Used for 409 foreign approvals (WF-R52).
|
|
183
204
|
*/
|
package/dist/core/flow-host.js
CHANGED
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
* - `deriveAgentEffectSessionId(workflowSessionId, path, request): string`
|
|
15
15
|
* - `isWorkflowManifest(manifest): manifest is WorkflowManifest`
|
|
16
16
|
* - `isFlowToolEffect(request): boolean`, `isFlowEffect(request): boolean`
|
|
17
|
+
* - `linkedMessageInput(body): JsonValue`
|
|
18
|
+
* - `settleAgentEffect({ t, effect, outcome }): Promise<void>`
|
|
19
|
+
* - `recordVerdict(t, request, value): Promise<void>`
|
|
17
20
|
* - `pathDepth(path): number`
|
|
18
21
|
* - `countActiveFlowWork(t, workflowSessionId, turnId): Promise<number>`
|
|
19
22
|
* - `commandKey(sessionId, idempotencyKey): string`
|
|
@@ -25,8 +28,9 @@
|
|
|
25
28
|
* - `reconcilePendingAgentEffect({ t, effectId, schedule }): Promise<boolean>`
|
|
26
29
|
* - `planCancelCascade({ t, workflowSessionId, turnId }): Promise<CascadeCancelPlan>`
|
|
27
30
|
* - `cancelSiblingWork({ t, workflowSessionId, turnId, siblingPaths?, cancelEffectIds? }): Promise<CancelSiblingResult>`
|
|
28
|
-
* - `
|
|
31
|
+
* - `cancelQueuedEffects({ t, workflowSessionId, turnId }): Promise<string[]>`
|
|
29
32
|
* - `aggregateWaits({ t, workflowSessionId }): Promise<FlowWait[]>`
|
|
33
|
+
* - `flowInteractionOf(waits, workflowSessionId, interactionId): { id; kind } | undefined`
|
|
30
34
|
* - `findInteractionOwner({ t, workflowSessionId, interactionId }): Promise<{ sessionId; path } | undefined>`
|
|
31
35
|
* - `foreignInteractionConflict({ t, workflowSessionId, interactionId }): Promise<{ status: 409; message; ownerSessionId } | undefined>`
|
|
32
36
|
* - `wakeForQueuedEffects({ t, workflowSessionId, turnId, limits, schedule }): Promise<boolean>`
|
|
@@ -40,10 +44,11 @@
|
|
|
40
44
|
* commits. The effect is bound to its turn through the message's idempotency key
|
|
41
45
|
* (`linkedMessageKey`): the `commands` document of that key names the turn it opened.
|
|
42
46
|
*
|
|
43
|
-
* The Tenant sweep (`tenant/sweep.ts`) calls the reconcile
|
|
44
|
-
*
|
|
47
|
+
* The Tenant sweep (`tenant/sweep.ts`) calls the reconcile functions, one transaction per
|
|
48
|
+
* effect.
|
|
45
49
|
*/
|
|
46
50
|
import { createHash } from "node:crypto";
|
|
51
|
+
import { isVerdict } from "@nylorun/core/define";
|
|
47
52
|
import { mayDispatchMore } from "./limits.js";
|
|
48
53
|
/** Deterministic agent session id: derive(workflowSessionId, path, …parts). */
|
|
49
54
|
export function deriveSessionId(workflowSessionId, path, ...parts) {
|
|
@@ -84,23 +89,61 @@ export function isFlowToolEffect(request) {
|
|
|
84
89
|
request.capabilityId === undefined);
|
|
85
90
|
}
|
|
86
91
|
export function isFlowEffect(request) {
|
|
87
|
-
return
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
92
|
+
return request.kind === "agent" || isFlowToolEffect(request);
|
|
93
|
+
}
|
|
94
|
+
/** Pretty JSON for a message, or a string as it is. */
|
|
95
|
+
function text(value) {
|
|
96
|
+
return typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* What a flow `agent` effect sends its linked session (D12): the stage's input, and, when the
|
|
100
|
+
* engine sent the flow's own input beside it, that input first as the original request. Pure,
|
|
101
|
+
* so a replayed effect sends the same message.
|
|
102
|
+
*/
|
|
103
|
+
export function linkedMessageInput(body) {
|
|
104
|
+
if (body.flowInput === undefined)
|
|
105
|
+
return body.input;
|
|
106
|
+
return `Original request:\n${text(body.flowInput)}\n\n${text(body.input)}`;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Settle a flow `agent` effect with its linked turn's outcome, in the caller's transaction,
|
|
110
|
+
* which holds the workflow session's lock. A verifier agent's verdict is recorded as
|
|
111
|
+
* `loop.verified`; an outcome that is not a verdict fails the Loop in the engine instead.
|
|
112
|
+
*/
|
|
113
|
+
export async function settleAgentEffect(input) {
|
|
114
|
+
const { t, effect, outcome } = input;
|
|
115
|
+
await t.put("effects", effect.request.effectId, { ...effect, status: "completed", outcome });
|
|
116
|
+
if (effect.request.context?.role === "verify-agent")
|
|
117
|
+
await recordVerdict(t, effect.request, outcome.value);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Record a Loop verifier's verdict as `loop.verified`: a verifier agent's output, or an HTTP
|
|
121
|
+
* verifier's answer (`role: "verify-http"`, a completed HTTP outcome). Anything that is not a
|
|
122
|
+
* verdict records nothing; the engine fails the Loop on it.
|
|
123
|
+
*/
|
|
124
|
+
export async function recordVerdict(t, request, value) {
|
|
125
|
+
const answer = value;
|
|
126
|
+
const verdict = request.context?.role !== "verify-http"
|
|
127
|
+
? value
|
|
128
|
+
: answer?.kind === "completed"
|
|
129
|
+
? answer.output
|
|
130
|
+
: undefined;
|
|
131
|
+
if (!isVerdict(verdict))
|
|
132
|
+
return;
|
|
133
|
+
await t.event(request.sessionId, request.turnId, "loop.verified", {
|
|
134
|
+
path: String(request.context.loopPath ?? request.path ?? ""),
|
|
135
|
+
n: Number(request.context.n ?? 1),
|
|
136
|
+
pass: verdict.pass,
|
|
137
|
+
...(verdict.pass ? {} : { feedback: verdict.feedback }),
|
|
138
|
+
...(verdict.data !== undefined ? { data: verdict.data } : {}),
|
|
139
|
+
});
|
|
91
140
|
}
|
|
92
|
-
/** Actions still to be settled: waiting to be delivered, or being delivered. */
|
|
93
|
-
const OPEN_ACTION = ["pending", "delivering"];
|
|
94
141
|
function scheduleAfterCommit(t, schedule, id, wake) {
|
|
95
142
|
t.afterCommit(() => schedule(id, wake));
|
|
96
143
|
}
|
|
97
|
-
/** Active agent turns +
|
|
144
|
+
/** Active agent turns + tool nodes in flight for one workflow turn. */
|
|
98
145
|
export async function countActiveFlowWork(t, workflowSessionId, turnId) {
|
|
99
146
|
const effects = await t.effectsForTurn(workflowSessionId, turnId);
|
|
100
|
-
const openActions = new Set((await t.actionsForSession(workflowSessionId, {
|
|
101
|
-
turnId,
|
|
102
|
-
statuses: OPEN_ACTION,
|
|
103
|
-
})).map((action) => action.actionId));
|
|
104
147
|
let n = 0;
|
|
105
148
|
for (const effect of effects) {
|
|
106
149
|
if (effect.status === "queued")
|
|
@@ -111,10 +154,7 @@ export async function countActiveFlowWork(t, workflowSessionId, turnId) {
|
|
|
111
154
|
n += 1;
|
|
112
155
|
continue;
|
|
113
156
|
}
|
|
114
|
-
if ((effect.request
|
|
115
|
-
effect.request?.kind === "verify" ||
|
|
116
|
-
isFlowToolEffect(effect.request)) &&
|
|
117
|
-
openActions.has(effect.request.effectId))
|
|
157
|
+
if (isFlowToolEffect(effect.request) && effect.status === "invoking")
|
|
118
158
|
n += 1;
|
|
119
159
|
}
|
|
120
160
|
return n;
|
|
@@ -199,11 +239,7 @@ export async function wakeLinkedWorkflow(input) {
|
|
|
199
239
|
},
|
|
200
240
|
}
|
|
201
241
|
: { value: input.output ?? null };
|
|
202
|
-
await t
|
|
203
|
-
...effect,
|
|
204
|
-
status: "completed",
|
|
205
|
-
outcome,
|
|
206
|
-
});
|
|
242
|
+
await settleAgentEffect({ t, effect, outcome });
|
|
207
243
|
workflow.status = "runnable";
|
|
208
244
|
await t.put("sessions", workflow.id, workflow);
|
|
209
245
|
scheduleAfterCommit(t, input.schedule, workflow.id, {
|
|
@@ -259,45 +295,22 @@ export function pathDepth(path) {
|
|
|
259
295
|
return 0;
|
|
260
296
|
return path.split("/").filter(Boolean).length;
|
|
261
297
|
}
|
|
262
|
-
/**
|
|
263
|
-
* Plan a cancel cascade for a workflow session: linked agents deepest first,
|
|
264
|
-
* then pending → cancelled and delivering → uncertain actions (SD-P11 / WF-R53).
|
|
265
|
-
*/
|
|
298
|
+
/** Plan a cancel cascade for a workflow session: linked agents deepest first (SD-P11 / WF-R53). */
|
|
266
299
|
export async function planCancelCascade(input) {
|
|
267
300
|
const { t } = input;
|
|
268
301
|
const agentEntries = (await t.linkedSessions(input.workflowSessionId)).map((linked) => ({ sessionId: linked.agentSessionId, path: linked.link.path }));
|
|
269
302
|
agentEntries.sort((a, b) => pathDepth(b.path) - pathDepth(a.path));
|
|
270
|
-
|
|
271
|
-
const deliveringActionIds = [];
|
|
272
|
-
const actions = await t.actionsForSession(input.workflowSessionId, {
|
|
273
|
-
...(input.turnId !== null ? { turnId: input.turnId } : {}),
|
|
274
|
-
statuses: OPEN_ACTION,
|
|
275
|
-
});
|
|
276
|
-
for (const action of actions) {
|
|
277
|
-
if (action.status === "pending")
|
|
278
|
-
pendingActionIds.push(action.actionId);
|
|
279
|
-
// Being delivered: the code may already be running.
|
|
280
|
-
else
|
|
281
|
-
deliveringActionIds.push(action.actionId);
|
|
282
|
-
}
|
|
283
|
-
return {
|
|
284
|
-
agentSessionIds: agentEntries.map((e) => e.sessionId),
|
|
285
|
-
pendingActionIds,
|
|
286
|
-
deliveringActionIds,
|
|
287
|
-
};
|
|
303
|
+
return { agentSessionIds: agentEntries.map((e) => e.sessionId) };
|
|
288
304
|
}
|
|
289
305
|
/**
|
|
290
306
|
* Cancel sibling work under a Parallel/Map parent when one branch fails (PAR-R6).
|
|
291
|
-
* Agent turns listed in `agentSessionIds` are returned for the caller to cancel
|
|
292
|
-
* pending actions → cancelled, delivering tool actions → uncertain.
|
|
307
|
+
* Agent turns listed in `agentSessionIds` are returned for the caller to cancel.
|
|
293
308
|
* When `cancelEffectIds` is provided, those effects are marked cancelled and their
|
|
294
309
|
* paths are included in the sibling path set.
|
|
295
310
|
*/
|
|
296
311
|
export async function cancelSiblingWork(input) {
|
|
297
312
|
const { t } = input;
|
|
298
313
|
await t.lockSession(input.workflowSessionId);
|
|
299
|
-
const cancelledActions = [];
|
|
300
|
-
const uncertainActions = [];
|
|
301
314
|
const agentSessionIds = [];
|
|
302
315
|
const siblingPaths = new Set(input.siblingPaths ?? []);
|
|
303
316
|
for (const effectId of input.cancelEffectIds ?? []) {
|
|
@@ -328,66 +341,21 @@ export async function cancelSiblingWork(input) {
|
|
|
328
341
|
["running", "runnable", "paused", "waiting"].includes(session.status))
|
|
329
342
|
agentSessionIds.push(agentSessionId);
|
|
330
343
|
}
|
|
331
|
-
|
|
332
|
-
turnId: input.turnId,
|
|
333
|
-
statuses: OPEN_ACTION,
|
|
334
|
-
});
|
|
335
|
-
for (const action of actions) {
|
|
336
|
-
if (!matchesSibling(action.path))
|
|
337
|
-
continue;
|
|
338
|
-
// Delivered work may already have had an external effect.
|
|
339
|
-
const next = action.status === "pending" ? "cancelled" : "uncertain";
|
|
340
|
-
action.status = next;
|
|
341
|
-
await t.put("actions", action.actionId, action);
|
|
342
|
-
const effect = await t.get("effects", action.actionId);
|
|
343
|
-
if (effect) {
|
|
344
|
-
effect.status = next;
|
|
345
|
-
await t.put("effects", action.actionId, effect);
|
|
346
|
-
}
|
|
347
|
-
if (next === "cancelled")
|
|
348
|
-
cancelledActions.push(action.actionId);
|
|
349
|
-
else
|
|
350
|
-
uncertainActions.push(action.actionId);
|
|
351
|
-
}
|
|
352
|
-
return { cancelledActions, uncertainActions, agentSessionIds };
|
|
344
|
+
return { agentSessionIds };
|
|
353
345
|
}
|
|
354
|
-
/**
|
|
355
|
-
|
|
356
|
-
* delivering→uncertain).
|
|
357
|
-
*/
|
|
358
|
-
export async function fenceWorkflowActions(input) {
|
|
346
|
+
/** Cancel a cancelled workflow turn's queued effects, which never started. */
|
|
347
|
+
export async function cancelQueuedEffects(input) {
|
|
359
348
|
const { t } = input;
|
|
360
349
|
await t.lockSession(input.workflowSessionId);
|
|
361
350
|
const turn = input.turnId !== null ? { turnId: input.turnId } : {};
|
|
362
351
|
const cancelled = [];
|
|
363
|
-
const uncertain = [];
|
|
364
|
-
const actions = await t.actionsForSession(input.workflowSessionId, {
|
|
365
|
-
...turn,
|
|
366
|
-
statuses: OPEN_ACTION,
|
|
367
|
-
});
|
|
368
|
-
for (const action of actions) {
|
|
369
|
-
// Delivered work may already have had an external effect.
|
|
370
|
-
const next = action.status === "pending" ? "cancelled" : "uncertain";
|
|
371
|
-
action.status = next;
|
|
372
|
-
await t.put("actions", action.actionId, action);
|
|
373
|
-
const effect = await t.get("effects", action.actionId);
|
|
374
|
-
if (effect) {
|
|
375
|
-
effect.status = next;
|
|
376
|
-
await t.put("effects", action.actionId, effect);
|
|
377
|
-
}
|
|
378
|
-
if (next === "cancelled")
|
|
379
|
-
cancelled.push(action.actionId);
|
|
380
|
-
else
|
|
381
|
-
uncertain.push(action.actionId);
|
|
382
|
-
}
|
|
383
|
-
// Drop queued effects that never started.
|
|
384
352
|
const queued = await t.effectsForSession(input.workflowSessionId, { ...turn, statuses: ["queued"] });
|
|
385
353
|
for (const effect of queued) {
|
|
386
354
|
effect.status = "cancelled";
|
|
387
355
|
await t.put("effects", effect.request.effectId, effect);
|
|
388
356
|
cancelled.push(effect.request.effectId);
|
|
389
357
|
}
|
|
390
|
-
return
|
|
358
|
+
return cancelled;
|
|
391
359
|
}
|
|
392
360
|
function waitsFromSession(sessionId, path, raw) {
|
|
393
361
|
if (!Array.isArray(raw))
|
|
@@ -400,9 +368,10 @@ function waitsFromSession(sessionId, path, raw) {
|
|
|
400
368
|
continue;
|
|
401
369
|
waits.push({
|
|
402
370
|
sessionId,
|
|
403
|
-
path,
|
|
371
|
+
path: typeof call.path === "string" ? call.path : path,
|
|
404
372
|
interactionId,
|
|
405
373
|
kind: String(interaction?.kind ?? call.kind ?? "approval"),
|
|
374
|
+
...(call.interaction !== undefined ? { interaction: call.interaction } : {}),
|
|
406
375
|
...(call.invocationId !== undefined
|
|
407
376
|
? { invocationId: String(call.invocationId) }
|
|
408
377
|
: {}),
|
|
@@ -421,13 +390,9 @@ export async function aggregateWaits(input) {
|
|
|
421
390
|
if (!workflow)
|
|
422
391
|
return [];
|
|
423
392
|
const waits = [];
|
|
424
|
-
// Workflow-owned interactions (tool-node
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
else if (workflow.waits &&
|
|
428
|
-
typeof workflow.waits === "object" &&
|
|
429
|
-
Array.isArray(workflow.waits.interactions))
|
|
430
|
-
waits.push(...waitsFromSession(workflow.id, "", workflow.waits.interactions));
|
|
393
|
+
// Workflow-owned interactions (tool-node approvals) live on the workflow session. Waits it
|
|
394
|
+
// copied from linked sessions are read from those sessions below, never from this copy.
|
|
395
|
+
waits.push(...waitsFromSession(workflow.id, "", ownWaits(workflow.waits, workflow.id)));
|
|
431
396
|
for (const { agentSessionId, link, session } of await t.linkedSessions(input.workflowSessionId)) {
|
|
432
397
|
if (session.status !== "paused")
|
|
433
398
|
continue;
|
|
@@ -435,6 +400,23 @@ export async function aggregateWaits(input) {
|
|
|
435
400
|
}
|
|
436
401
|
return waits;
|
|
437
402
|
}
|
|
403
|
+
/** The entries of a workflow's `waits` that the workflow itself owns. */
|
|
404
|
+
function ownWaits(raw, workflowSessionId) {
|
|
405
|
+
const list = Array.isArray(raw)
|
|
406
|
+
? raw
|
|
407
|
+
: raw && typeof raw === "object" && Array.isArray(raw.interactions)
|
|
408
|
+
? raw.interactions
|
|
409
|
+
: [];
|
|
410
|
+
return list.filter((call) => call?.sessionId === undefined || call.sessionId === workflowSessionId);
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* The workflow-owned interaction `interactionId` among a workflow's `waits` (a tool node that
|
|
414
|
+
* asked), or undefined.
|
|
415
|
+
*/
|
|
416
|
+
export function flowInteractionOf(raw, workflowSessionId, interactionId) {
|
|
417
|
+
const hit = waitsFromSession(workflowSessionId, "", ownWaits(raw, workflowSessionId)).find((wait) => wait.interactionId === interactionId);
|
|
418
|
+
return hit && { id: hit.interactionId, kind: hit.kind };
|
|
419
|
+
}
|
|
438
420
|
/**
|
|
439
421
|
* Look up which session owns an interaction id. Used for 409 foreign approvals (WF-R52).
|
|
440
422
|
*/
|
package/dist/core/provider.d.ts
CHANGED
|
@@ -8,5 +8,8 @@ export declare function gatewayModel(options: {
|
|
|
8
8
|
token: string;
|
|
9
9
|
model: string;
|
|
10
10
|
}): ModelProvider;
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Deterministic release fixture; still uses the real engine. With a `lookup_order` tool it looks
|
|
13
|
+
* up the demo order through it once; without one it answers in text.
|
|
14
|
+
*/
|
|
12
15
|
export declare function toolFixtureModel(): ModelProvider;
|
package/dist/core/provider.js
CHANGED
|
@@ -22,7 +22,10 @@ export function gatewayModel(options) {
|
|
|
22
22
|
return response.json();
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* Deterministic release fixture; still uses the real engine. With a `lookup_order` tool it looks
|
|
27
|
+
* up the demo order through it once; without one it answers in text.
|
|
28
|
+
*/
|
|
26
29
|
export function toolFixtureModel() {
|
|
27
30
|
return async (effect) => {
|
|
28
31
|
const call = effect.input;
|
|
@@ -46,6 +49,8 @@ export function toolFixtureModel() {
|
|
|
46
49
|
},
|
|
47
50
|
],
|
|
48
51
|
};
|
|
52
|
+
if (!call.tools.some((tool) => tool.name === "lookup_order"))
|
|
53
|
+
return { output: [{ type: "text", text: "Hello from the fixture model." }] };
|
|
49
54
|
return {
|
|
50
55
|
output: [
|
|
51
56
|
{
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { Action } from "@nylorun/core/contracts";
|
|
2
1
|
import type { WorkspacePort } from "../harness-api/workspace.js";
|
|
3
2
|
import type { SandboxToolOutcome } from "../sandbox/tools.js";
|
|
4
3
|
import { type SessionSandboxRef } from "../sandbox/share.js";
|
|
@@ -19,20 +18,12 @@ export type SandboxRouteDeps = {
|
|
|
19
18
|
readonly sandbox: Pick<WorkspacePort, "run">;
|
|
20
19
|
/** The session (rejects when missing) and its owner chain, read in one transaction. */
|
|
21
20
|
readonly session: (id: string) => Promise<SandboxRouteRead>;
|
|
22
|
-
readonly getAction: (id: string) => Promise<Action | undefined>;
|
|
23
21
|
};
|
|
24
22
|
/**
|
|
25
23
|
* `POST /v1/sessions/:id/sandbox/:tool` — application principal.
|
|
26
24
|
* Refused while the session has an active agent turn.
|
|
27
25
|
*/
|
|
28
26
|
export declare function handleSessionSandboxTool(deps: SandboxRouteDeps, sessionId: string, toolName: string, body: unknown, signal: AbortSignal): Promise<SandboxToolOutcome>;
|
|
29
|
-
/**
|
|
30
|
-
* `POST /v1/actions/:id/sandbox/:tool` — the Action endpoint, authorized by the delivery token
|
|
31
|
-
* of the Action's current delivery (`delivery.generation`). The body is the tool's input.
|
|
32
|
-
*/
|
|
33
|
-
export declare function handleActionSandboxTool(deps: SandboxRouteDeps, actionId: string, toolName: string, body: unknown, signal: AbortSignal, delivery: {
|
|
34
|
-
generation: number;
|
|
35
|
-
}): Promise<SandboxToolOutcome>;
|
|
36
27
|
/**
|
|
37
28
|
* Validate PutSession.sandbox attach: same owner, and a sandbox to share. A session whose
|
|
38
29
|
* definition declares a sandbox must declare the owner's spec; one whose definition declares
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* HTTP handlers for session-scoped
|
|
3
|
-
*
|
|
2
|
+
* HTTP handlers for the session-scoped sandbox tool route (`POST /v1/sessions/:id/sandbox/:tool`)
|
|
3
|
+
* and the sandbox attach check of `PUT /v1/sessions/:id`.
|
|
4
4
|
*/
|
|
5
5
|
import { isSandboxToolName, } from "@nylorun/core/define";
|
|
6
6
|
import { capabilityForSandbox, owningSandboxSessionId, sandboxSpecOf, sandboxWorkspaceOf, sandboxSpecsEqual, sessionSandboxSpec, } from "../sandbox/share.js";
|
|
@@ -56,25 +56,6 @@ export async function handleSessionSandboxTool(deps, sessionId, toolName, body,
|
|
|
56
56
|
const tool = parseTool(toolName);
|
|
57
57
|
return runTool(deps, read, tool, toolInput(body), signal);
|
|
58
58
|
}
|
|
59
|
-
/**
|
|
60
|
-
* `POST /v1/actions/:id/sandbox/:tool` — the Action endpoint, authorized by the delivery token
|
|
61
|
-
* of the Action's current delivery (`delivery.generation`). The body is the tool's input.
|
|
62
|
-
*/
|
|
63
|
-
export async function handleActionSandboxTool(deps, actionId, toolName, body, signal, delivery) {
|
|
64
|
-
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
65
|
-
throw new SandboxRouteError(400, "Sandbox tool body must be an object");
|
|
66
|
-
const action = await deps.getAction(actionId);
|
|
67
|
-
if (!action)
|
|
68
|
-
throw new SandboxRouteError(404, "Action not found");
|
|
69
|
-
if (action.status !== "delivering" ||
|
|
70
|
-
action.generation !== delivery.generation ||
|
|
71
|
-
Date.parse(action.deadlineAt ?? "") <= Date.now())
|
|
72
|
-
throw new SandboxRouteError(409, "The delivery was cancelled, lost or delivered again");
|
|
73
|
-
const read = await deps.session(action.sessionId);
|
|
74
|
-
if (read.session.activeTurnId !== action.turnId)
|
|
75
|
-
throw new SandboxRouteError(409, "Action unavailable");
|
|
76
|
-
return runTool(deps, read, parseTool(toolName), toolInput(body), signal);
|
|
77
|
-
}
|
|
78
59
|
/**
|
|
79
60
|
* Validate PutSession.sandbox attach: same owner, and a sandbox to share. A session whose
|
|
80
61
|
* definition declares a sandbox must declare the owner's spec; one whose definition declares
|
|
@@ -2,7 +2,7 @@ import { type DurableExecution, type SandboxSignal, type Wake, type WorkerHandle
|
|
|
2
2
|
export interface MemoryExecutionOptions {
|
|
3
3
|
/** Delay between sweep passes of one Tenant. Default 5000. */
|
|
4
4
|
sweepIntervalMs?: number;
|
|
5
|
-
/** First retry delay after `advance`
|
|
5
|
+
/** First retry delay after `advance` throws; doubles per attempt. Default 50. */
|
|
6
6
|
retryDelayMs?: number;
|
|
7
7
|
/** Attempts before giving up on a throwing handler and reporting it. Default 5. */
|
|
8
8
|
maxAttempts?: number;
|
|
@@ -39,7 +39,6 @@ export declare class MemoryExecution implements DurableExecution {
|
|
|
39
39
|
private readonly onError;
|
|
40
40
|
constructor(options?: MemoryExecutionOptions);
|
|
41
41
|
wake(tenantId: string, sessionId: string, wake: Wake): Promise<void>;
|
|
42
|
-
deliver(tenantId: string, actionId: string): Promise<void>;
|
|
43
42
|
sandbox(tenantId: string, sandboxId: string, signal: SandboxSignal): Promise<void>;
|
|
44
43
|
private armSandbox;
|
|
45
44
|
private runSandbox;
|
|
@@ -49,8 +48,8 @@ export declare class MemoryExecution implements DurableExecution {
|
|
|
49
48
|
start(handlers: WorkerHandlers): Promise<void>;
|
|
50
49
|
stop(): Promise<void>;
|
|
51
50
|
/**
|
|
52
|
-
* Resolves once no advance
|
|
53
|
-
*
|
|
51
|
+
* Resolves once no advance is running or queued and no busy or retry re-run is
|
|
52
|
+
* pending. Timers and sweeps are not waited for. Test helper.
|
|
54
53
|
*/
|
|
55
54
|
idle(): Promise<void>;
|
|
56
55
|
private enqueue;
|
|
@@ -58,7 +57,7 @@ export declare class MemoryExecution implements DurableExecution {
|
|
|
58
57
|
/** Drops dedupe keys older than the retention window. */
|
|
59
58
|
private forgetDedupeKeys;
|
|
60
59
|
private runOnce;
|
|
61
|
-
/** Runs the
|
|
60
|
+
/** Runs the session's advance; resolves to a delay when it must run again. */
|
|
62
61
|
private call;
|
|
63
62
|
private armTimer;
|
|
64
63
|
private scheduleSweep;
|
package/dist/execution/memory.js
CHANGED
|
@@ -44,10 +44,7 @@ export class MemoryExecution {
|
|
|
44
44
|
return;
|
|
45
45
|
this.seen.set(dedupe, now);
|
|
46
46
|
}
|
|
47
|
-
this.enqueue(
|
|
48
|
-
}
|
|
49
|
-
async deliver(tenantId, actionId) {
|
|
50
|
-
this.enqueue("deliver", tenantId, actionId);
|
|
47
|
+
this.enqueue(tenantId, sessionId);
|
|
51
48
|
}
|
|
52
49
|
async sandbox(tenantId, sandboxId, signal) {
|
|
53
50
|
const key = `${tenantId}\u0000${sandboxId}`;
|
|
@@ -173,8 +170,8 @@ export class MemoryExecution {
|
|
|
173
170
|
await settleWithin(this.inflight, this.stopGraceMs);
|
|
174
171
|
}
|
|
175
172
|
/**
|
|
176
|
-
* Resolves once no advance
|
|
177
|
-
*
|
|
173
|
+
* Resolves once no advance is running or queued and no busy or retry re-run is
|
|
174
|
+
* pending. Timers and sweeps are not waited for. Test helper.
|
|
178
175
|
*/
|
|
179
176
|
async idle() {
|
|
180
177
|
while (this.delayed.size > 0 ||
|
|
@@ -182,14 +179,13 @@ export class MemoryExecution {
|
|
|
182
179
|
[...this.keys.values()].some((s) => s.running || (s.queued && this.handlers)))
|
|
183
180
|
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
184
181
|
}
|
|
185
|
-
enqueue(
|
|
186
|
-
const key =
|
|
182
|
+
enqueue(tenantId, sessionId) {
|
|
183
|
+
const key = sessionKey(tenantId, sessionId);
|
|
187
184
|
let state = this.keys.get(key);
|
|
188
185
|
if (!state)
|
|
189
186
|
this.keys.set(key, (state = {
|
|
190
|
-
kind,
|
|
191
187
|
tenantId,
|
|
192
|
-
|
|
188
|
+
sessionId,
|
|
193
189
|
running: false,
|
|
194
190
|
queued: false,
|
|
195
191
|
attempts: 0,
|
|
@@ -211,7 +207,7 @@ export class MemoryExecution {
|
|
|
211
207
|
state.running = false;
|
|
212
208
|
// Idle keys hold nothing (a retry keeps its attempt count); forget them so
|
|
213
209
|
// long-lived processes stay small.
|
|
214
|
-
const key =
|
|
210
|
+
const key = sessionKey(state.tenantId, state.sessionId);
|
|
215
211
|
if (!state.queued && state.attempts === 0 && this.keys.get(key) === state)
|
|
216
212
|
this.keys.delete(key);
|
|
217
213
|
}
|
|
@@ -228,7 +224,7 @@ export class MemoryExecution {
|
|
|
228
224
|
async runOnce(state, handlers) {
|
|
229
225
|
const controller = new AbortController();
|
|
230
226
|
state.controller = controller;
|
|
231
|
-
const again = () => this.enqueue(state.
|
|
227
|
+
const again = () => this.enqueue(state.tenantId, state.sessionId);
|
|
232
228
|
try {
|
|
233
229
|
const retryAfterMs = await this.call(state, handlers, controller.signal);
|
|
234
230
|
state.attempts = 0;
|
|
@@ -248,16 +244,10 @@ export class MemoryExecution {
|
|
|
248
244
|
state.controller = undefined;
|
|
249
245
|
}
|
|
250
246
|
}
|
|
251
|
-
/** Runs the
|
|
247
|
+
/** Runs the session's advance; resolves to a delay when it must run again. */
|
|
252
248
|
async call(state, handlers, signal) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
return result.status === "busy" ? result.retryAfterMs : undefined;
|
|
256
|
-
}
|
|
257
|
-
if (!handlers.deliver)
|
|
258
|
-
throw new Error("WorkerHandlers.deliver is required for deliveries");
|
|
259
|
-
const result = await handlers.deliver(state.tenantId, state.id, signal);
|
|
260
|
-
return result.status === "retry" ? result.retryAfterMs : undefined;
|
|
249
|
+
const result = await handlers.advance(state.tenantId, state.sessionId, signal);
|
|
250
|
+
return result.status === "busy" ? result.retryAfterMs : undefined;
|
|
261
251
|
}
|
|
262
252
|
armTimer(id, timer) {
|
|
263
253
|
timer.handle = setTimeout(() => {
|
|
@@ -305,7 +295,3 @@ export class MemoryExecution {
|
|
|
305
295
|
void promise.finally(() => this.inflight.delete(promise));
|
|
306
296
|
}
|
|
307
297
|
}
|
|
308
|
-
/** Advances and deliveries are queued apart: a session id and an Action id may be equal. */
|
|
309
|
-
function stateKey(kind, tenantId, id) {
|
|
310
|
-
return `${kind}\u0000${sessionKey(tenantId, id)}`;
|
|
311
|
-
}
|