@nylorun/runtime 0.16.0-beta → 0.19.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. package/CHANGELOG.md +207 -0
  2. package/README.md +105 -63
  3. package/dist/adapters/execution/restate.d.ts +1 -3
  4. package/dist/adapters/execution/restate.js +4 -31
  5. package/dist/adapters/replication/pgoutput.js +1 -1
  6. package/dist/adapters/sandbox/local.js +1 -1
  7. package/dist/adapters/sandbox/virtual.js +1 -1
  8. package/dist/api/ag-ui/endpoint.js +1 -2
  9. package/dist/api/ag-ui/routes.d.ts +1 -1
  10. package/dist/api/ag-ui/routes.js +11 -13
  11. package/dist/api/ag-ui/translate.d.ts +1 -3
  12. package/dist/api/ag-ui/translate.js +2 -17
  13. package/dist/api/components.d.ts +4783 -2588
  14. package/dist/api/components.js +19 -21
  15. package/dist/api/gate/routes.d.ts +7 -8
  16. package/dist/api/gate/routes.js +58 -52
  17. package/dist/api/http/app.d.ts +6 -0
  18. package/dist/api/http/app.js +20 -14
  19. package/dist/api/http/define.d.ts +22 -13
  20. package/dist/api/http/define.js +43 -39
  21. package/dist/api/http/routes/access.d.ts +3 -19
  22. package/dist/api/http/routes/access.js +25 -184
  23. package/dist/api/http/routes/artifacts.js +3 -7
  24. package/dist/api/http/routes/files.d.ts +8 -0
  25. package/dist/api/http/routes/files.js +77 -0
  26. package/dist/api/http/routes/keys.d.ts +10 -0
  27. package/dist/api/http/routes/keys.js +64 -0
  28. package/dist/api/http/routes/me.d.ts +13 -0
  29. package/dist/api/http/routes/me.js +48 -0
  30. package/dist/api/http/routes/reads.d.ts +66 -0
  31. package/dist/api/http/routes/reads.js +127 -0
  32. package/dist/api/http/routes/sandboxes.d.ts +2 -2
  33. package/dist/api/http/routes/sandboxes.js +8 -7
  34. package/dist/api/http/routes/sessions.js +31 -20
  35. package/dist/api/http/routes/tenant.js +15 -18
  36. package/dist/api/http/routes/vaults.d.ts +13 -3
  37. package/dist/api/http/routes/vaults.js +136 -64
  38. package/dist/api/openapi.d.ts +10 -11
  39. package/dist/api/openapi.js +292 -68
  40. package/dist/artifacts/links.js +6 -6
  41. package/dist/artifacts/service.js +1 -1
  42. package/dist/core/flow-host.d.ts +44 -23
  43. package/dist/core/flow-host.js +88 -106
  44. package/dist/core/provider.d.ts +4 -1
  45. package/dist/core/provider.js +6 -1
  46. package/dist/core/sandbox-routes.d.ts +0 -9
  47. package/dist/core/sandbox-routes.js +2 -21
  48. package/dist/execution/memory.d.ts +4 -5
  49. package/dist/execution/memory.js +11 -25
  50. package/dist/execution/types.d.ts +3 -33
  51. package/dist/execution/types.js +2 -12
  52. package/dist/gates/http-tool.d.ts +86 -0
  53. package/dist/gates/http-tool.js +197 -0
  54. package/dist/gates/mcp-handler.d.ts +3 -0
  55. package/dist/gates/mcp-handler.js +6 -4
  56. package/dist/gates/meter.d.ts +2 -2
  57. package/dist/gates/meter.js +6 -0
  58. package/dist/gates/tenant-vaults.d.ts +19 -14
  59. package/dist/gates/tenant-vaults.js +17 -12
  60. package/dist/gates/tool-calls.d.ts +7 -15
  61. package/dist/gates/tool-calls.js +4 -4
  62. package/dist/gates/tool-client.d.ts +1 -1
  63. package/dist/gates/tool-client.js +23 -47
  64. package/dist/gates/tool-contract.d.ts +27 -39
  65. package/dist/gates/tool-contract.js +30 -21
  66. package/dist/gates/tool-gate.d.ts +25 -24
  67. package/dist/gates/tool-gate.js +11 -7
  68. package/dist/harness/calls.d.ts +10 -4
  69. package/dist/harness/calls.js +15 -6
  70. package/dist/harness/executors.d.ts +1 -1
  71. package/dist/harness/executors.js +17 -12
  72. package/dist/harness/main.d.ts +2 -2
  73. package/dist/harness/main.js +5 -13
  74. package/dist/harness/service.d.ts +4 -5
  75. package/dist/harness/service.js +9 -2
  76. package/dist/harness-api/in-process.js +2 -2
  77. package/dist/harness-api/record.d.ts +6 -3
  78. package/dist/harness-api/record.js +65 -72
  79. package/dist/harness-api/server.d.ts +1 -9
  80. package/dist/harness-api/server.js +19 -17
  81. package/dist/harness-api/start.js +1 -5
  82. package/dist/harness-api/workspace.js +2 -3
  83. package/dist/harness-api/ws-server.js +1 -1
  84. package/dist/host/app.d.ts +2 -10
  85. package/dist/host/app.js +24 -44
  86. package/dist/host/config-for.d.ts +5 -5
  87. package/dist/host/config-for.js +2 -3
  88. package/dist/host/config.d.ts +0 -11
  89. package/dist/host/create-host.d.ts +3 -40
  90. package/dist/host/create-host.js +18 -91
  91. package/dist/host/environment.d.ts +0 -3
  92. package/dist/host/environment.js +0 -9
  93. package/dist/host/gates.d.ts +4 -1
  94. package/dist/host/gates.js +9 -3
  95. package/dist/host/http.d.ts +1 -2
  96. package/dist/host/http.js +1 -7
  97. package/dist/host/index.d.ts +2 -2
  98. package/dist/host/index.js +2 -2
  99. package/dist/host/main.js +55 -40
  100. package/dist/host/operate.d.ts +29 -0
  101. package/dist/host/operate.js +223 -0
  102. package/dist/host/stack-config.d.ts +23 -20
  103. package/dist/host/stack-config.js +28 -44
  104. package/dist/index.d.ts +4 -1
  105. package/dist/index.js +3 -1
  106. package/dist/infra/workers.d.ts +1 -1
  107. package/dist/keys/client.js +9 -1
  108. package/dist/keys/keys.d.ts +12 -2
  109. package/dist/keys/keys.js +10 -2
  110. package/dist/management-openapi.json +6491 -0
  111. package/dist/mcp/connect.d.ts +8 -4
  112. package/dist/mcp/connect.js +8 -27
  113. package/dist/mcp/pool.d.ts +6 -9
  114. package/dist/mcp/pool.js +8 -18
  115. package/dist/mcp/snapshot.d.ts +8 -2
  116. package/dist/mcp/snapshot.js +29 -2
  117. package/dist/media.d.ts +1 -1
  118. package/dist/media.js +1 -1
  119. package/dist/model/pi-model.js +3 -0
  120. package/dist/openapi.json +6228 -15615
  121. package/dist/reads/access.d.ts +4 -0
  122. package/dist/reads/access.js +12 -0
  123. package/dist/reads/cursor.d.ts +11 -0
  124. package/dist/reads/cursor.js +80 -0
  125. package/dist/reads/types.d.ts +25 -0
  126. package/dist/reads/types.js +1 -0
  127. package/dist/sandbox/manager.d.ts +16 -0
  128. package/dist/sandbox/manager.js +40 -0
  129. package/dist/sandbox/session-sandbox.js +13 -3
  130. package/dist/sandbox/skills.d.ts +13 -0
  131. package/dist/sandbox/skills.js +46 -0
  132. package/dist/sandbox/types.d.ts +2 -2
  133. package/dist/store/ownership.d.ts +9 -0
  134. package/dist/store/ownership.js +13 -0
  135. package/dist/store/postgres/connect.d.ts +2 -0
  136. package/dist/store/postgres/connect.js +29 -0
  137. package/dist/store/postgres/drizzle/0008_installation_vaults.sql +2 -0
  138. package/dist/store/postgres/drizzle/0009_oauth_pending.sql +19 -0
  139. package/dist/store/postgres/drizzle/0010_oss_auth_removals.sql +3 -0
  140. package/dist/store/postgres/drizzle/0011_key_roles.sql +3 -0
  141. package/dist/store/postgres/drizzle/0012_session_reads.sql +8 -0
  142. package/dist/store/postgres/drizzle/0013_definition_files.sql +16 -0
  143. package/dist/store/postgres/drizzle/0014_action_endpoints_removed.sql +2 -0
  144. package/dist/store/postgres/drizzle/meta/_journal.json +49 -0
  145. package/dist/store/postgres/reads.d.ts +3 -0
  146. package/dist/store/postgres/reads.js +272 -0
  147. package/dist/store/postgres/schema.d.ts +423 -594
  148. package/dist/store/postgres/schema.js +84 -101
  149. package/dist/store/postgres/store.js +96 -248
  150. package/dist/store/postgres/tenant.d.ts +34 -2
  151. package/dist/store/postgres/tenant.js +16 -32
  152. package/dist/store/types.d.ts +44 -119
  153. package/dist/store/types.js +0 -1
  154. package/dist/streams/control.d.ts +0 -7
  155. package/dist/streams/control.js +0 -13
  156. package/dist/streams/types.d.ts +2 -20
  157. package/dist/streams/types.js +1 -1
  158. package/dist/tenant/advance.d.ts +3 -3
  159. package/dist/tenant/advance.js +10 -7
  160. package/dist/tenant/auth.d.ts +24 -15
  161. package/dist/tenant/auth.js +44 -55
  162. package/dist/tenant/cause.d.ts +3 -3
  163. package/dist/tenant/cause.js +1 -1
  164. package/dist/tenant/commands.d.ts +2 -18
  165. package/dist/tenant/commands.js +38 -179
  166. package/dist/tenant/context.d.ts +33 -45
  167. package/dist/tenant/definition-files.d.ts +24 -0
  168. package/dist/tenant/definition-files.js +103 -0
  169. package/dist/tenant/effects.d.ts +20 -18
  170. package/dist/tenant/effects.js +59 -133
  171. package/dist/tenant/ephemeral.d.ts +21 -8
  172. package/dist/tenant/ephemeral.js +11 -12
  173. package/dist/tenant/host-token.js +5 -5
  174. package/dist/tenant/identity-file.d.ts +75 -0
  175. package/dist/tenant/identity-file.js +202 -0
  176. package/dist/tenant/issuers.d.ts +48 -0
  177. package/dist/tenant/issuers.js +311 -0
  178. package/dist/tenant/jwt.js +3 -3
  179. package/dist/tenant/module.d.ts +1 -4
  180. package/dist/tenant/module.js +3 -12
  181. package/dist/tenant/operator-keys.d.ts +44 -0
  182. package/dist/tenant/operator-keys.js +87 -0
  183. package/dist/tenant/outbound.d.ts +40 -1
  184. package/dist/tenant/outbound.js +167 -18
  185. package/dist/tenant/paths.d.ts +1 -1
  186. package/dist/tenant/paths.js +1 -3
  187. package/dist/tenant/principals.d.ts +13 -13
  188. package/dist/tenant/principals.js +35 -36
  189. package/dist/tenant/run-token.js +6 -6
  190. package/dist/tenant/runtime.d.ts +5 -3
  191. package/dist/tenant/runtime.js +29 -15
  192. package/dist/tenant/sandboxes.d.ts +9 -6
  193. package/dist/tenant/sandboxes.js +16 -5
  194. package/dist/tenant/scheduler.d.ts +1 -5
  195. package/dist/tenant/scheduler.js +2 -5
  196. package/dist/tenant/session-streams.d.ts +8 -11
  197. package/dist/tenant/session-streams.js +48 -32
  198. package/dist/tenant/session.d.ts +8 -41
  199. package/dist/tenant/session.js +0 -37
  200. package/dist/tenant/sessions.d.ts +2 -2
  201. package/dist/tenant/sessions.js +17 -9
  202. package/dist/tenant/signing-keys.d.ts +3 -2
  203. package/dist/tenant/signing-keys.js +5 -5
  204. package/dist/tenant/skill-tool.d.ts +20 -0
  205. package/dist/tenant/skill-tool.js +72 -0
  206. package/dist/tenant/slim.d.ts +1 -1
  207. package/dist/tenant/slim.js +1 -1
  208. package/dist/tenant/status.js +7 -12
  209. package/dist/tenant/store-pg.d.ts +15 -0
  210. package/dist/tenant/store-pg.js +7 -3
  211. package/dist/tenant/streams.d.ts +3 -11
  212. package/dist/tenant/streams.js +2 -29
  213. package/dist/tenant/sweep.js +0 -5
  214. package/dist/tenant/transcript.d.ts +2 -2
  215. package/dist/tenant/transcript.js +2 -2
  216. package/dist/tenant/types.d.ts +33 -19
  217. package/dist/tenant/worker.d.ts +1 -3
  218. package/dist/tenant/worker.js +3 -8
  219. package/dist/vault/crypto.d.ts +7 -0
  220. package/dist/vault/crypto.js +16 -0
  221. package/dist/vault/oauth.d.ts +55 -0
  222. package/dist/vault/oauth.js +186 -0
  223. package/dist/vault/service.d.ts +54 -7
  224. package/dist/vault/service.js +261 -17
  225. package/dist/vault/sources.d.ts +60 -0
  226. package/dist/vault/sources.js +196 -0
  227. package/dist/version.d.ts +1 -1
  228. package/dist/version.js +1 -1
  229. package/package.json +5 -4
  230. package/dist/admin-openapi.json +0 -604
  231. package/dist/api/http/routes/actions.d.ts +0 -8
  232. package/dist/api/http/routes/actions.js +0 -76
  233. package/dist/api/http/routes/endpoints.d.ts +0 -7
  234. package/dist/api/http/routes/endpoints.js +0 -100
  235. package/dist/host/admin-api.d.ts +0 -23
  236. package/dist/host/admin-api.js +0 -74
  237. package/dist/host/cors.d.ts +0 -27
  238. package/dist/host/cors.js +0 -64
  239. package/dist/mcp/stdio.d.ts +0 -17
  240. package/dist/mcp/stdio.js +0 -82
  241. package/dist/tenant/access-policy.d.ts +0 -30
  242. package/dist/tenant/access-policy.js +0 -52
  243. package/dist/tenant/actions.d.ts +0 -14
  244. package/dist/tenant/actions.js +0 -29
  245. package/dist/tenant/browser.d.ts +0 -15
  246. package/dist/tenant/browser.js +0 -39
  247. package/dist/tenant/delivery-token.d.ts +0 -41
  248. package/dist/tenant/delivery-token.js +0 -104
  249. package/dist/tenant/delivery.d.ts +0 -62
  250. package/dist/tenant/delivery.js +0 -481
  251. package/dist/tenant/endpoints.d.ts +0 -67
  252. package/dist/tenant/endpoints.js +0 -90
  253. package/dist/tenant/subject-limits.d.ts +0 -16
  254. package/dist/tenant/subject-limits.js +0 -45
  255. package/dist/tenant/tokens.d.ts +0 -14
  256. package/dist/tenant/tokens.js +0 -187
@@ -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
- * - `fenceWorkflowActions({ t, workflowSessionId, turnId }): Promise<{ cancelled; uncertain }>`
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 and re-offer functions, one
44
- * transaction per effect or Action.
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 (request.kind === "agent" ||
88
- request.kind === "fn" ||
89
- request.kind === "verify" ||
90
- isFlowToolEffect(request));
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 + pending/delivering actions for one workflow turn. */
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?.kind === "fn" ||
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.put("effects", link.effectId, {
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
- const pendingActionIds = [];
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
- const actions = await t.actionsForSession(input.workflowSessionId, {
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
- * Apply cancel fencing to workflow-session actions (pending→cancelled,
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 { cancelled, uncertain };
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 / verify approvals) live on the workflow session.
425
- if (Array.isArray(workflow.waits))
426
- waits.push(...waitsFromSession(workflow.id, "", workflow.waits));
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
  */
@@ -8,5 +8,8 @@ export declare function gatewayModel(options: {
8
8
  token: string;
9
9
  model: string;
10
10
  }): ModelProvider;
11
- /** Deterministic release fixture; still uses the real engine and remote customer tool. */
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;
@@ -22,7 +22,10 @@ export function gatewayModel(options) {
22
22
  return response.json();
23
23
  };
24
24
  }
25
- /** Deterministic release fixture; still uses the real engine and remote customer tool. */
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 and claim-scoped sandbox tool endpoints.
3
- * Wired from TenantRuntime; keeps route bodies out of the hot runtime file.
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` or `deliver` throws; doubles per attempt. Default 50. */
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 or delivery is running or queued and no busy or
53
- * retry re-run is pending. Timers and sweeps are not waited for. Test helper.
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 key's handler; resolves to a delay when the key must run again. */
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;
@@ -44,10 +44,7 @@ export class MemoryExecution {
44
44
  return;
45
45
  this.seen.set(dedupe, now);
46
46
  }
47
- this.enqueue("advance", tenantId, sessionId);
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 or delivery is running or queued and no busy or
177
- * retry re-run is pending. Timers and sweeps are not waited for. Test helper.
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(kind, tenantId, id) {
186
- const key = stateKey(kind, tenantId, id);
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
- id,
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 = stateKey(state.kind, state.tenantId, state.id);
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.kind, state.tenantId, state.id);
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 key's handler; resolves to a delay when the key must run again. */
247
+ /** Runs the session's advance; resolves to a delay when it must run again. */
252
248
  async call(state, handlers, signal) {
253
- if (state.kind === "advance") {
254
- const result = await handlers.advance(state.tenantId, state.id, signal);
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
- }
@@ -27,16 +27,7 @@
27
27
  * becomes `uncertain` is settled in the Session Store and `advance` returns
28
28
  * `done`.
29
29
  * - **Sweeps.** `armSweep` arms one self-re-arming sweep per Tenant; arming an
30
- * armed Tenant is a no-op. The sweep settles lapsed deliveries and re-wakes
31
- * orphaned `runnable` sessions.
32
- * - **One delivery per Action at a time.** `deliver` runs
33
- * `WorkerHandlers.deliver` for the key `<tenantId>:<actionId>` at least once
34
- * after it resolves. Two deliveries for one key never overlap; repeated
35
- * `deliver` calls may each run it, so the handler reads the Action's state
36
- * and does nothing when there is nothing to deliver.
37
- * - **Retry after `retry`.** A delivery that returns `retry` is run again for
38
- * the same key after `retryAfterMs`. This is how an Action endpoint that is
39
- * down or busy is retried; the handler throws only on infrastructure errors.
30
+ * armed Tenant is a no-op. The sweep re-wakes orphaned `runnable` sessions.
40
31
  * - **One reconcile per sandbox at a time** (F7.2). `sandbox` runs
41
32
  * `WorkerHandlers.sandbox` for the key `<tenantId>:<sandboxId>` at least once
42
33
  * after a `reconcile` resolves, never overlapping another run for the key. A
@@ -49,8 +40,6 @@
49
40
  export type WakeReason =
50
41
  /** Session commands. */
51
42
  "message" | "approve" | "respond"
52
- /** An Action's outcome was recorded (a delivery's answer or a background result). */
53
- | "action_result"
54
43
  /** A linked agent turn completed, failed or was cancelled. */
55
44
  | "linked"
56
45
  /** Queued flow effects, pending agent effects reconciled. */
@@ -98,14 +87,6 @@ export interface SandboxResult {
98
87
  at: number;
99
88
  }[];
100
89
  }
101
- export type DeliverResult = {
102
- status: "done";
103
- }
104
- /** The Action is still to be delivered; run again for this key after `retryAfterMs`. */
105
- | {
106
- status: "retry";
107
- retryAfterMs: number;
108
- };
109
90
  /** What a Worker runs when Durable Session Execution calls it. */
110
91
  export interface WorkerHandlers {
111
92
  /**
@@ -118,11 +99,6 @@ export interface WorkerHandlers {
118
99
  sweep(tenantId: string): Promise<void>;
119
100
  /** A durable timer set with `timer` fired. Required when `timer` is used. */
120
101
  fire?(tenantId: string, key: string): Promise<void>;
121
- /**
122
- * Delivers one Action to its Action endpoint. `signal` aborts when the Worker
123
- * stops. Throw only on infrastructure errors. Required when `deliver` is used.
124
- */
125
- deliver?(tenantId: string, actionId: string, signal: AbortSignal): Promise<DeliverResult>;
126
102
  /**
127
103
  * Reconciles one pod sandbox (F7.2), serialized per sandbox. Throw only on infrastructure
128
104
  * errors. Required when `sandbox` is used.
@@ -138,12 +114,6 @@ export interface DurableExecution {
138
114
  * implementation can; `fire` must be idempotent either way.
139
115
  */
140
116
  timer(tenantId: string, key: string, at: Date): Promise<void>;
141
- /**
142
- * At-least-once: `WorkerHandlers.deliver(tenantId, actionId)` runs after this
143
- * resolves, never overlapping another delivery of the same Action. Send it
144
- * after the transaction that made the Action pending commits.
145
- */
146
- deliver(tenantId: string, actionId: string): Promise<void>;
147
117
  /**
148
118
  * A pod sandbox's reconcile, or one of its timers (F7.2): see "One reconcile per sandbox
149
119
  * at a time". Absent where pods are not supported.
@@ -183,7 +153,7 @@ export interface StuckInvocation {
183
153
  handler: string;
184
154
  /**
185
155
  * Object key: `<tenantId>:<sessionId>`, `<tenantId>`, `<tenantId>:<timer key>` or
186
- * `<tenantId>:<actionId>`.
156
+ * `<tenantId>:<sandboxId>`.
187
157
  */
188
158
  key: string;
189
159
  tenantId?: string;
@@ -191,7 +161,7 @@ export interface StuckInvocation {
191
161
  lastFailure?: string;
192
162
  modifiedAt?: string;
193
163
  }
194
- /** The key one advance (or one delivery, with an Action id) at a time is serialized on. */
164
+ /** The key one advance (or one sandbox reconcile, with a sandbox id) at a time is serialized on. */
195
165
  export declare function sessionKey(tenantId: string, sessionId: string): string;
196
166
  /** Splits a key made by `sessionKey`. Tenant ids never contain `:`. */
197
167
  export declare function parseSessionKey(key: string): {
@@ -27,16 +27,7 @@
27
27
  * becomes `uncertain` is settled in the Session Store and `advance` returns
28
28
  * `done`.
29
29
  * - **Sweeps.** `armSweep` arms one self-re-arming sweep per Tenant; arming an
30
- * armed Tenant is a no-op. The sweep settles lapsed deliveries and re-wakes
31
- * orphaned `runnable` sessions.
32
- * - **One delivery per Action at a time.** `deliver` runs
33
- * `WorkerHandlers.deliver` for the key `<tenantId>:<actionId>` at least once
34
- * after it resolves. Two deliveries for one key never overlap; repeated
35
- * `deliver` calls may each run it, so the handler reads the Action's state
36
- * and does nothing when there is nothing to deliver.
37
- * - **Retry after `retry`.** A delivery that returns `retry` is run again for
38
- * the same key after `retryAfterMs`. This is how an Action endpoint that is
39
- * down or busy is retried; the handler throws only on infrastructure errors.
30
+ * armed Tenant is a no-op. The sweep re-wakes orphaned `runnable` sessions.
40
31
  * - **One reconcile per sandbox at a time** (F7.2). `sandbox` runs
41
32
  * `WorkerHandlers.sandbox` for the key `<tenantId>:<sandboxId>` at least once
42
33
  * after a `reconcile` resolves, never overlapping another run for the key. A
@@ -49,13 +40,12 @@ export const WAKE_REASONS = [
49
40
  "message",
50
41
  "approve",
51
42
  "respond",
52
- "action_result",
53
43
  "linked",
54
44
  "flow",
55
45
  "recover",
56
46
  "rollover",
57
47
  ];
58
- /** The key one advance (or one delivery, with an Action id) at a time is serialized on. */
48
+ /** The key one advance (or one sandbox reconcile, with a sandbox id) at a time is serialized on. */
59
49
  export function sessionKey(tenantId, sessionId) {
60
50
  return `${tenantId}:${sessionId}`;
61
51
  }