zeitlich 0.2.41 → 0.2.42

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 (119) hide show
  1. package/dist/{activities-qUflxmfS.d.cts → activities-Coafq5zr.d.cts} +2 -2
  2. package/dist/{activities-D_g13S3y.d.ts → activities-CrN-ghLo.d.ts} +2 -2
  3. package/dist/adapters/sandbox/daytona/index.cjs +4 -23
  4. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
  5. package/dist/adapters/sandbox/daytona/index.d.cts +18 -86
  6. package/dist/adapters/sandbox/daytona/index.d.ts +18 -86
  7. package/dist/adapters/sandbox/daytona/index.js +4 -23
  8. package/dist/adapters/sandbox/daytona/index.js.map +1 -1
  9. package/dist/adapters/sandbox/daytona/workflow.cjs +1 -7
  10. package/dist/adapters/sandbox/daytona/workflow.cjs.map +1 -1
  11. package/dist/adapters/sandbox/daytona/workflow.d.cts +9 -2
  12. package/dist/adapters/sandbox/daytona/workflow.d.ts +9 -2
  13. package/dist/adapters/sandbox/daytona/workflow.js +1 -7
  14. package/dist/adapters/sandbox/daytona/workflow.js.map +1 -1
  15. package/dist/adapters/sandbox/e2b/index.cjs +9 -0
  16. package/dist/adapters/sandbox/e2b/index.cjs.map +1 -1
  17. package/dist/adapters/sandbox/e2b/index.d.cts +13 -5
  18. package/dist/adapters/sandbox/e2b/index.d.ts +13 -5
  19. package/dist/adapters/sandbox/e2b/index.js +9 -1
  20. package/dist/adapters/sandbox/e2b/index.js.map +1 -1
  21. package/dist/adapters/sandbox/e2b/workflow.cjs.map +1 -1
  22. package/dist/adapters/sandbox/e2b/workflow.d.cts +4 -2
  23. package/dist/adapters/sandbox/e2b/workflow.d.ts +4 -2
  24. package/dist/adapters/sandbox/e2b/workflow.js.map +1 -1
  25. package/dist/adapters/sandbox/inmemory/index.cjs +11 -0
  26. package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
  27. package/dist/adapters/sandbox/inmemory/index.d.cts +11 -3
  28. package/dist/adapters/sandbox/inmemory/index.d.ts +11 -3
  29. package/dist/adapters/sandbox/inmemory/index.js +11 -1
  30. package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
  31. package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -1
  32. package/dist/adapters/sandbox/inmemory/workflow.d.cts +4 -2
  33. package/dist/adapters/sandbox/inmemory/workflow.d.ts +4 -2
  34. package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -1
  35. package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
  36. package/dist/adapters/thread/anthropic/index.d.cts +5 -5
  37. package/dist/adapters/thread/anthropic/index.d.ts +5 -5
  38. package/dist/adapters/thread/anthropic/index.js.map +1 -1
  39. package/dist/adapters/thread/anthropic/workflow.d.cts +5 -5
  40. package/dist/adapters/thread/anthropic/workflow.d.ts +5 -5
  41. package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
  42. package/dist/adapters/thread/google-genai/index.d.cts +5 -5
  43. package/dist/adapters/thread/google-genai/index.d.ts +5 -5
  44. package/dist/adapters/thread/google-genai/index.js.map +1 -1
  45. package/dist/adapters/thread/google-genai/workflow.d.cts +5 -5
  46. package/dist/adapters/thread/google-genai/workflow.d.ts +5 -5
  47. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  48. package/dist/adapters/thread/langchain/index.d.cts +5 -5
  49. package/dist/adapters/thread/langchain/index.d.ts +5 -5
  50. package/dist/adapters/thread/langchain/index.js.map +1 -1
  51. package/dist/adapters/thread/langchain/workflow.d.cts +5 -5
  52. package/dist/adapters/thread/langchain/workflow.d.ts +5 -5
  53. package/dist/index.cjs +224 -70
  54. package/dist/index.cjs.map +1 -1
  55. package/dist/index.d.cts +66 -16
  56. package/dist/index.d.ts +66 -16
  57. package/dist/index.js +224 -70
  58. package/dist/index.js.map +1 -1
  59. package/dist/{proxy-D7mvDEO6.d.cts → proxy-Bf7uI-Hw.d.cts} +1 -1
  60. package/dist/{proxy-BbcgoXg1.d.ts → proxy-COqA95FW.d.ts} +1 -1
  61. package/dist/{thread-manager-CTXPCu9W.d.ts → thread-manager-BhkOyQ1I.d.ts} +2 -2
  62. package/dist/{thread-manager-Dqstsw4i.d.ts → thread-manager-Bi1XlbpJ.d.ts} +2 -2
  63. package/dist/{thread-manager-cLhDhRRc.d.cts → thread-manager-BsLO3Fgc.d.cts} +2 -2
  64. package/dist/{thread-manager-DrWfVjlj.d.cts → thread-manager-wRVVBFgj.d.cts} +2 -2
  65. package/dist/{types-CjF1_Idx.d.ts → types-BkX4HLzi.d.ts} +1 -1
  66. package/dist/{types-CdvcmXb6.d.cts → types-C66-BVBr.d.cts} +1 -1
  67. package/dist/types-CJ7tCdl6.d.cts +266 -0
  68. package/dist/types-CJ7tCdl6.d.ts +266 -0
  69. package/dist/{types-DjaQKUJx.d.cts → types-CdALEF3z.d.cts} +300 -20
  70. package/dist/{types-BqTmyH31.d.ts → types-ChAy_jSP.d.ts} +300 -20
  71. package/dist/types-CjY93AWZ.d.cts +84 -0
  72. package/dist/types-gVa5XCWD.d.ts +84 -0
  73. package/dist/{workflow-N1MNDoul.d.ts → workflow-BwT5EybR.d.ts} +7 -6
  74. package/dist/{workflow-CuqxgS6X.d.cts → workflow-DMmiaw6w.d.cts} +7 -6
  75. package/dist/workflow.cjs +99 -46
  76. package/dist/workflow.cjs.map +1 -1
  77. package/dist/workflow.d.cts +4 -4
  78. package/dist/workflow.d.ts +4 -4
  79. package/dist/workflow.js +99 -46
  80. package/dist/workflow.js.map +1 -1
  81. package/package.json +7 -32
  82. package/src/adapters/sandbox/daytona/index.ts +25 -48
  83. package/src/adapters/sandbox/daytona/proxy.ts +7 -8
  84. package/src/adapters/sandbox/e2b/index.ts +21 -6
  85. package/src/adapters/sandbox/e2b/proxy.ts +3 -2
  86. package/src/adapters/sandbox/inmemory/index.ts +21 -1
  87. package/src/adapters/sandbox/inmemory/proxy.ts +7 -3
  88. package/src/lib/activity.ts +5 -0
  89. package/src/lib/sandbox/capability-types.test.ts +859 -0
  90. package/src/lib/sandbox/index.ts +1 -0
  91. package/src/lib/sandbox/manager.ts +187 -31
  92. package/src/lib/sandbox/types.ts +189 -46
  93. package/src/lib/session/index.ts +1 -0
  94. package/src/lib/session/session.ts +97 -35
  95. package/src/lib/session/types.ts +181 -5
  96. package/src/lib/subagent/handler.ts +66 -43
  97. package/src/lib/subagent/types.ts +484 -16
  98. package/src/workflow.ts +3 -0
  99. package/tsup.config.ts +0 -4
  100. package/dist/adapters/sandbox/bedrock/index.cjs +0 -457
  101. package/dist/adapters/sandbox/bedrock/index.cjs.map +0 -1
  102. package/dist/adapters/sandbox/bedrock/index.d.cts +0 -25
  103. package/dist/adapters/sandbox/bedrock/index.d.ts +0 -25
  104. package/dist/adapters/sandbox/bedrock/index.js +0 -454
  105. package/dist/adapters/sandbox/bedrock/index.js.map +0 -1
  106. package/dist/adapters/sandbox/bedrock/workflow.cjs +0 -36
  107. package/dist/adapters/sandbox/bedrock/workflow.cjs.map +0 -1
  108. package/dist/adapters/sandbox/bedrock/workflow.d.cts +0 -29
  109. package/dist/adapters/sandbox/bedrock/workflow.d.ts +0 -29
  110. package/dist/adapters/sandbox/bedrock/workflow.js +0 -34
  111. package/dist/adapters/sandbox/bedrock/workflow.js.map +0 -1
  112. package/dist/types-DAsQ21Rt.d.ts +0 -74
  113. package/dist/types-lm8tMNJQ.d.cts +0 -74
  114. package/dist/types-yx0LzPGn.d.cts +0 -173
  115. package/dist/types-yx0LzPGn.d.ts +0 -173
  116. package/src/adapters/sandbox/bedrock/filesystem.ts +0 -340
  117. package/src/adapters/sandbox/bedrock/index.ts +0 -274
  118. package/src/adapters/sandbox/bedrock/proxy.ts +0 -59
  119. package/src/adapters/sandbox/bedrock/types.ts +0 -24
@@ -6,11 +6,14 @@ import {
6
6
  } from "@temporalio/workflow";
7
7
  import type { SessionExitReason } from "../types";
8
8
  import type { SessionConfig, ZeitlichSession } from "./types";
9
+ import { resolveSessionLifecycle } from "./types";
9
10
  import type {
11
+ SandboxCapability,
10
12
  SandboxCreateOptions,
11
13
  SandboxOps,
12
14
  SandboxSnapshot,
13
15
  } from "../sandbox/types";
16
+ import type { SandboxInit, SubagentSandboxShutdown } from "../lifecycle";
14
17
  import type {
15
18
  AgentState,
16
19
  AgentStateManager,
@@ -79,41 +82,89 @@ export async function createSession<
79
82
  T extends ToolMap,
80
83
  M = unknown,
81
84
  TContent = string,
85
+ TInit extends SandboxInit | undefined = undefined,
86
+ TShutdown extends SubagentSandboxShutdown | undefined = undefined,
82
87
  >(
83
- config: SessionConfig<T, M, TContent> & { sandboxOps: SandboxOps }
88
+ config: SessionConfig<T, M, TContent, TInit, TShutdown> & {
89
+ sandboxOps: NonNullable<
90
+ SessionConfig<T, M, TContent, TInit, TShutdown>["sandboxOps"]
91
+ >;
92
+ }
84
93
  ): Promise<ZeitlichSession<M, true>>;
85
94
  export async function createSession<
86
95
  T extends ToolMap,
87
96
  M = unknown,
88
97
  TContent = string,
89
- >(config: SessionConfig<T, M, TContent>): Promise<ZeitlichSession<M, false>>;
98
+ TInit extends SandboxInit | undefined = undefined,
99
+ TShutdown extends SubagentSandboxShutdown | undefined = undefined,
100
+ >(
101
+ config: SessionConfig<T, M, TContent, TInit, TShutdown>
102
+ ): Promise<ZeitlichSession<M, false>>;
103
+ // Implementation. The overloads above narrow the public contract per
104
+ // `SessionRequiredCaps<TInit, TShutdown>`. The impl signature uses the
105
+ // `never` cap floor — the structurally-narrowest shape that every
106
+ // adapter (including Daytona/Bedrock) satisfies — so each overload's
107
+ // possibly-narrow `sandboxOps` is assignable here. Gated method calls
108
+ // inside the body go through `wideOps()` below, which casts the
109
+ // narrow-typed binding to the wide shape. This is structurally safe:
110
+ // the runtime gates each gated call on `sandboxMode === …` /
111
+ // `resolvedShutdown === …`, and the type system has already ruled out
112
+ // any (mode, shutdown, adapter) cell that would invoke a missing
113
+ // method.
90
114
  export async function createSession<
91
115
  T extends ToolMap,
92
116
  M = unknown,
93
117
  TContent = string,
94
- >({
95
- agentName,
96
- maxTurns = 50,
97
- metadata = {},
98
- runAgent,
99
- threadOps,
100
- buildContextMessage,
101
- subagents,
102
- skills,
103
- tools = {} as T,
104
- processToolsInParallel = true,
105
- hooks = {},
106
- appendSystemPrompt = true,
107
- threadKey,
108
- sandboxOps,
109
- thread: threadInit,
110
- sandbox: sandboxInit,
111
- sandboxShutdown = "destroy",
112
- onSandboxReady,
113
- onSessionExit,
114
- virtualFs: virtualFsConfig,
115
- virtualFsOps,
116
- }: SessionConfig<T, M, TContent>): Promise<ZeitlichSession<M, boolean>> {
118
+ >(
119
+ config: Omit<
120
+ SessionConfig<T, M, TContent, SandboxInit | undefined, undefined>,
121
+ "sandboxOps"
122
+ > & {
123
+ sandboxOps?: SandboxOps<SandboxCreateOptions, unknown, never>;
124
+ }
125
+ ): Promise<ZeitlichSession<M, boolean>> {
126
+ const {
127
+ agentName,
128
+ maxTurns = 50,
129
+ metadata = {},
130
+ runAgent,
131
+ threadOps,
132
+ buildContextMessage,
133
+ subagents,
134
+ skills,
135
+ tools = {} as T,
136
+ processToolsInParallel = true,
137
+ hooks = {},
138
+ appendSystemPrompt = true,
139
+ threadKey,
140
+ sandboxOps,
141
+ thread: threadInit,
142
+ sandbox: sandboxInit,
143
+ sandboxShutdown,
144
+ onSandboxReady,
145
+ onSessionExit,
146
+ virtualFs: virtualFsConfig,
147
+ virtualFsOps,
148
+ } = config;
149
+
150
+ /**
151
+ * The narrow-typed `sandboxOps` binding cast to its wide
152
+ * (`SandboxCapability`) form. The overload signatures +
153
+ * `SessionRequiredCaps` SSOT have already ruled out `(sandbox.mode,
154
+ * sandboxShutdown, adapter)` combinations where a referenced method
155
+ * would be missing on the proxy, and each gated call site below is
156
+ * guarded by an `if (sandboxOps)` / `sandboxOps &&` runtime check,
157
+ * so the cast is structurally safe. `wideOps` returns the wide-cap
158
+ * shape; callers use it after their own runtime guard establishes
159
+ * that `sandboxOps` is defined.
160
+ */
161
+ type WideSandboxOps = SandboxOps<
162
+ SandboxCreateOptions,
163
+ unknown,
164
+ SandboxCapability
165
+ >;
166
+ const wideOps = (): WideSandboxOps =>
167
+ sandboxOps as unknown as WideSandboxOps;
117
168
  // ---------------------------------------------------------------------------
118
169
  // Thread resolution
119
170
  // ---------------------------------------------------------------------------
@@ -219,7 +270,18 @@ export async function createSession<
219
270
  );
220
271
 
221
272
  // --- Sandbox lifecycle: create, continue, fork, from-snapshot, or inherit ---
222
- const sandboxMode = sandboxInit?.mode;
273
+ // Resolve `sandbox` / `sandboxShutdown` defaults through the SSOT
274
+ // so the runtime dispatch below and the type-level
275
+ // `SessionRequiredCaps` cannot disagree on what the documented
276
+ // defaults are. Both surfaces consult `resolveSessionLifecycle`
277
+ // (or its type-level equivalent) before checking individual
278
+ // mode/shutdown values.
279
+ const lifecycle = resolveSessionLifecycle(
280
+ sandboxInit,
281
+ sandboxShutdown
282
+ );
283
+ const sandboxMode: SandboxInit["mode"] | undefined = lifecycle.mode;
284
+ const resolvedShutdown: SubagentSandboxShutdown = lifecycle.shutdown;
223
285
  let sandboxId: string | undefined;
224
286
  let sandboxOwned = false;
225
287
  let baseSnapshot: SandboxSnapshot | undefined;
@@ -248,8 +310,8 @@ export async function createSession<
248
310
  }
249
311
  sandboxId = (sandboxInit as { mode: "continue"; sandboxId: string })
250
312
  .sandboxId;
251
- if (sandboxShutdown === "pause-until-parent-close") {
252
- await sandboxOps.resumeSandbox(sandboxId);
313
+ if (resolvedShutdown === "pause-until-parent-close") {
314
+ await wideOps().resumeSandbox(sandboxId);
253
315
  }
254
316
  sandboxOwned = true;
255
317
  } else if (sandboxMode === "fork") {
@@ -264,7 +326,7 @@ export async function createSession<
264
326
  sandboxId: string;
265
327
  options?: SandboxCreateOptions;
266
328
  };
267
- sandboxId = await sandboxOps.forkSandbox(
329
+ sandboxId = await wideOps().forkSandbox(
268
330
  forkInit.sandboxId,
269
331
  forkInit.options
270
332
  );
@@ -281,7 +343,7 @@ export async function createSession<
281
343
  snapshot: SandboxSnapshot;
282
344
  options?: SandboxCreateOptions;
283
345
  };
284
- sandboxId = await sandboxOps.restoreSandbox(
346
+ sandboxId = await wideOps().restoreSandbox(
285
347
  restoreInit.snapshot,
286
348
  restoreInit.options
287
349
  );
@@ -308,10 +370,10 @@ export async function createSession<
308
370
  sandboxId &&
309
371
  sandboxOwned &&
310
372
  freshlyCreated &&
311
- sandboxShutdown === "snapshot" &&
373
+ resolvedShutdown === "snapshot" &&
312
374
  sandboxOps
313
375
  ) {
314
- baseSnapshot = await sandboxOps.snapshotSandbox(sandboxId);
376
+ baseSnapshot = await wideOps().snapshotSandbox(sandboxId);
315
377
  }
316
378
 
317
379
  if (sandboxId && sandboxOwned && onSandboxReady) {
@@ -571,19 +633,19 @@ export async function createSession<
571
633
  await callSessionEnd(exitReason, stateManager.getTurns());
572
634
 
573
635
  if (sandboxOwned && sandboxId && sandboxOps) {
574
- switch (sandboxShutdown) {
636
+ switch (resolvedShutdown) {
575
637
  case "destroy":
576
638
  await sandboxOps.destroySandbox(sandboxId);
577
639
  break;
578
640
  case "pause":
579
641
  case "pause-until-parent-close":
580
- await sandboxOps.pauseSandbox(sandboxId);
642
+ await wideOps().pauseSandbox(sandboxId);
581
643
  break;
582
644
  case "keep":
583
645
  case "keep-until-parent-close":
584
646
  break;
585
647
  case "snapshot":
586
- exitSnapshot = await sandboxOps.snapshotSandbox(sandboxId);
648
+ exitSnapshot = await wideOps().snapshotSandbox(sandboxId);
587
649
  await sandboxOps.destroySandbox(sandboxId);
588
650
  break;
589
651
  }
@@ -7,7 +7,12 @@ import type {
7
7
  import type { Hooks } from "../hooks/types";
8
8
  import type { SubagentConfig } from "../subagent/types";
9
9
  import type { Skill } from "../skills/types";
10
- import type { SandboxOps, SandboxSnapshot } from "../sandbox/types";
10
+ import type {
11
+ SandboxCapability,
12
+ SandboxCreateOptions,
13
+ SandboxOps,
14
+ SandboxSnapshot,
15
+ } from "../sandbox/types";
11
16
  import type { VirtualFsOps } from "../virtual-fs/types";
12
17
  import type { RunAgentActivity } from "../model/types";
13
18
  import type {
@@ -137,17 +142,171 @@ export type PrefixedThreadOps<TPrefix extends string, TContent = string> = {
137
142
  [K in keyof ThreadOps<TContent> as `${TPrefix}${Capitalize<K & string>}`]: ThreadOps<TContent>[K];
138
143
  };
139
144
 
145
+ // ============================================================================
146
+ // Session sandbox-lifecycle decision table (SSOT)
147
+ //
148
+ // `createSession` (`src/lib/session/session.ts`) dispatches gated
149
+ // sandbox methods based on `(sandbox.mode, sandboxShutdown)`. When a
150
+ // caller omits either field, the session uses the documented defaults
151
+ // (`sandbox: { mode: "new" }` and `sandboxShutdown: "destroy"`),
152
+ // which trigger only base ops (`createSandbox` / `destroySandbox`).
153
+ //
154
+ // Two surfaces have to agree on what gated methods may fire for a
155
+ // given config:
156
+ //
157
+ // 1. The runtime — what the session dispatches per `(mode,
158
+ // shutdown)` pair.
159
+ // 2. The type level — what caps `sandboxOps` has to advertise.
160
+ //
161
+ // `SessionRequiredCaps<TInit, TShutdown>` is the SSOT for the type
162
+ // level. `resolveSessionLifecycle(init, shutdown)` (in `session.ts`,
163
+ // re-exported below) is the runtime mirror. Adding a new branch to
164
+ // `session.ts` requires extending **both**; the
165
+ // `(sandbox.mode × sandboxShutdown × adapter)` matrix in
166
+ // `src/lib/sandbox/capability-types.test.ts` enforces agreement.
167
+ // ============================================================================
168
+
169
+ /**
170
+ * Caps the session's sandbox-init dispatch may invoke for a given
171
+ * `(mode, shutdown)`. Mirror of `src/lib/session/session.ts:241-313`.
172
+ *
173
+ * The conditional branches are written non-distributively (each cap
174
+ * checked against the input `M` / `S` rather than over a union) so
175
+ * that the *omitted* case (defaults: M = "new", S = "destroy") flows
176
+ * through to `never`, not to "I don't know, assume everything."
177
+ */
178
+ type _SessionInitCaps<M, S> =
179
+ | (M extends "fork" ? "fork" : never)
180
+ | (M extends "from-snapshot" ? "restore" : never)
181
+ | (M extends "continue"
182
+ ? S extends "pause-until-parent-close"
183
+ ? "resume"
184
+ : never
185
+ : never);
186
+
187
+ /**
188
+ * Caps the session's exit dispatch may invoke. `"inherit"` mode keeps
189
+ * `sandboxOwned = false` so exit-shutdown caps NEVER fire — mirror of
190
+ * `src/lib/session/session.ts:598-615`.
191
+ */
192
+ type _SessionExitCaps<M, S> = M extends "inherit"
193
+ ? never
194
+ :
195
+ | (S extends "snapshot" ? "snapshot" : never)
196
+ | (S extends "pause" | "pause-until-parent-close" ? "pause" : never);
197
+
198
+ /**
199
+ * Cap captured on entry for `mode: "new"` + `shutdown: "snapshot"` (the
200
+ * seeding-base-snapshot path). Mirror of
201
+ * `src/lib/session/session.ts:316-317`.
202
+ */
203
+ type _SessionSeedCaps<M, S> = M extends "new"
204
+ ? S extends "snapshot"
205
+ ? "snapshot"
206
+ : never
207
+ : never;
208
+
209
+ /**
210
+ * Resolves an omitted `sandbox` field to the runtime default
211
+ * `{ mode: "new" }`, and an omitted `sandboxShutdown` field to the
212
+ * runtime default `"destroy"`. The two `_Resolve…` helpers are the
213
+ * sole bridge between "user didn't set the field" and the SSOT below
214
+ * — keeping the defaults out of the conditional table itself.
215
+ */
216
+ type _ResolveInit<TInit> = [TInit] extends [undefined]
217
+ ? { mode: "new" }
218
+ : Exclude<TInit, undefined> extends infer Defined
219
+ ? // If the user passed `SandboxInit | undefined` (the wide default),
220
+ // collapse `undefined` to the runtime default.
221
+ undefined extends TInit
222
+ ? Defined | { mode: "new" }
223
+ : Defined
224
+ : never;
225
+
226
+ type _ResolveShutdown<TShutdown> = [TShutdown] extends [undefined]
227
+ ? "destroy"
228
+ : Exclude<TShutdown, undefined> extends infer Defined
229
+ ? undefined extends TShutdown
230
+ ? Defined | "destroy"
231
+ : Defined
232
+ : never;
233
+
234
+ /**
235
+ * Sandbox capabilities a session actually invokes on its
236
+ * `sandboxOps`, derived from the literal types of the surrounding
237
+ * `sandbox` and `sandboxShutdown` fields.
238
+ *
239
+ * `TInit` / `TShutdown` default to `undefined` so an un-parameterised
240
+ * `SessionRequiredCaps` resolves to the caps the runtime *defaults*
241
+ * (`{ mode: "new" }` + `"destroy"`) actually require — i.e.
242
+ * `never`. This lets a narrow adapter (e.g. `proxyDaytonaSandboxOps`)
243
+ * satisfy `sandboxOps?: SandboxOps<…, SessionRequiredCaps<…>>` when
244
+ * the caller doesn't pin literals, matching what's runtime-safe.
245
+ *
246
+ * Pin literals via `as const` or by ascribing `SessionConfig<…, TInit,
247
+ * TShutdown>` to tighten the requirement on a per-call basis.
248
+ */
249
+ export type SessionRequiredCaps<
250
+ TInit extends SandboxInit | undefined = undefined,
251
+ TShutdown extends SubagentSandboxShutdown | undefined = undefined,
252
+ > = _ResolveInit<TInit> extends infer M
253
+ ? _ResolveShutdown<TShutdown> extends infer S
254
+ ? M extends { mode: infer Mode }
255
+ ? S extends SubagentSandboxShutdown
256
+ ?
257
+ | _SessionInitCaps<Mode, S>
258
+ | _SessionExitCaps<Mode, S>
259
+ | _SessionSeedCaps<Mode, S>
260
+ : never
261
+ : never
262
+ : never
263
+ : never;
264
+
265
+ /**
266
+ * Runtime mirror of `SessionRequiredCaps`. Returns the names of the
267
+ * gated `sandboxOps` methods the session will invoke for a given
268
+ * `(sandboxInit, sandboxShutdown)` pair (after resolving documented
269
+ * defaults). Mirror of the dispatch in
270
+ * `src/lib/session/session.ts:241-313` and `:598-615`.
271
+ *
272
+ * Both surfaces consult this same table — the `(mode × shutdown ×
273
+ * adapter)` matrix in `src/lib/sandbox/capability-types.test.ts`
274
+ * enforces agreement.
275
+ */
276
+ export function resolveSessionLifecycle(
277
+ init: SandboxInit | undefined,
278
+ shutdown: SubagentSandboxShutdown | undefined
279
+ ): { mode: SandboxInit["mode"]; shutdown: SubagentSandboxShutdown } {
280
+ const resolvedInit: SandboxInit = init ?? { mode: "new" };
281
+ const resolvedShutdown: SubagentSandboxShutdown = shutdown ?? "destroy";
282
+ return {
283
+ mode: resolvedInit.mode,
284
+ shutdown: resolvedShutdown,
285
+ };
286
+ }
287
+
140
288
  /**
141
289
  * Configuration for a Zeitlich agent session.
142
290
  *
143
291
  * @typeParam T - Tool map
144
292
  * @typeParam M - SDK-native message type returned by the model invoker
145
293
  * @typeParam TContent - SDK-native content type for human messages (defaults to `string`)
294
+ * @typeParam TInit - Literal type of `sandbox` (a {@link SandboxInit}
295
+ * variant or `undefined`). Defaults to the wide union; pin it via
296
+ * `as const` on the call site to narrow the cap requirement on
297
+ * `sandboxOps`.
298
+ * @typeParam TShutdown - Literal type of `sandboxShutdown`. Defaults
299
+ * to the wide union; pin it via `as const` on the call site to
300
+ * narrow the cap requirement on `sandboxOps`.
146
301
  */
147
302
  export interface SessionConfig<
148
303
  T extends ToolMap,
149
304
  M = unknown,
150
305
  TContent = string,
306
+ TInit extends SandboxInit | undefined = SandboxInit | undefined,
307
+ TShutdown extends
308
+ | SubagentSandboxShutdown
309
+ | undefined = SubagentSandboxShutdown | undefined,
151
310
  > {
152
311
  /** The name of the agent, should be unique within the workflows */
153
312
  agentName: string;
@@ -203,8 +362,25 @@ export interface SessionConfig<
203
362
  // Sandbox lifecycle
204
363
  // ---------------------------------------------------------------------------
205
364
 
206
- /** Sandbox lifecycle operations (optional — omit for agents that don't need a sandbox) */
207
- sandboxOps?: SandboxOps;
365
+ /**
366
+ * Sandbox lifecycle operations (optional — omit for agents that don't
367
+ * need a sandbox).
368
+ *
369
+ * The `TCaps` argument is derived from {@link SessionRequiredCaps}
370
+ * over the literal types of the surrounding `sandbox` and
371
+ * `sandboxShutdown` fields. With the default wide
372
+ * `TInit` / `TShutdown`, this resolves to the full
373
+ * {@link SandboxCapability} union (current behaviour). When the
374
+ * caller pins those literals via `as const`, the cap requirement
375
+ * tightens so a narrow adapter (e.g. Daytona's
376
+ * `SandboxOps<…, never>`) can satisfy combinations that don't need
377
+ * a gated method.
378
+ */
379
+ sandboxOps?: SandboxOps<
380
+ SandboxCreateOptions,
381
+ unknown,
382
+ SessionRequiredCaps<TInit, TShutdown> & SandboxCapability
383
+ >;
208
384
  /**
209
385
  * Sandbox initialization strategy.
210
386
  *
@@ -215,14 +391,14 @@ export interface SessionConfig<
215
391
  *
216
392
  * When omitted and `sandboxOps` is provided, defaults to `{ mode: "new" }`.
217
393
  */
218
- sandbox?: SandboxInit;
394
+ sandbox?: TInit;
219
395
  /**
220
396
  * What to do with the sandbox when this session exits.
221
397
  *
222
398
  * Defaults to `"destroy"` when omitted.
223
399
  * Has no effect when the sandbox is inherited (`sandbox.mode === "inherit"`).
224
400
  */
225
- sandboxShutdown?: SubagentSandboxShutdown;
401
+ sandboxShutdown?: TShutdown;
226
402
  /**
227
403
  * Called as soon as the sandbox is created (or resumed/forked), before the
228
404
  * agent loop starts. Useful for signalling sandbox readiness to a parent.
@@ -6,16 +6,19 @@ import {
6
6
  ApplicationFailure,
7
7
  executeChild,
8
8
  } from "@temporalio/workflow";
9
+ import type { Duration } from "@temporalio/common";
9
10
  import { getShortId } from "../thread/id";
10
11
  import type { ToolHandlerResponse, RouterContext } from "../tool-router";
11
12
  import type { JsonValue } from "../state/types";
12
13
  import type {
13
14
  InferSubagentResult,
15
+ ResolvedSubagentSandboxConfig,
14
16
  SubagentConfig,
15
17
  SubagentFnResult,
16
18
  SubagentSandboxConfig,
17
19
  SubagentWorkflowInput,
18
20
  } from "./types";
21
+ import { resolveSubagentLifecycle } from "./types";
19
22
  import type { SubagentArgs } from "./tool";
20
23
  import type { z } from "zod";
21
24
  import type {
@@ -23,9 +26,33 @@ import type {
23
26
  SandboxInit,
24
27
  SubagentSandboxShutdown,
25
28
  } from "../lifecycle";
26
- import type { SandboxOps, SandboxSnapshot } from "../sandbox/types";
29
+ import type {
30
+ SandboxCreateOptions,
31
+ SandboxOps,
32
+ SandboxSnapshot,
33
+ } from "../sandbox/types";
27
34
  import { childSandboxReadySignal } from "./signals";
28
35
 
36
+ /**
37
+ * Methods the parent's subagent handler invokes on a subagent's `proxy`.
38
+ * Kept narrow so the handler's internal maps accept `SandboxOps<…, never>`
39
+ * (e.g. Daytona) and `SandboxOps<…, "snapshot">` (e.g. E2B for
40
+ * snapshot-driven continuations) alike.
41
+ *
42
+ * `destroySandbox` is base — always available.
43
+ * `deleteSandboxSnapshot` is only called for continuations that produce
44
+ * snapshots; it's stored separately and only populated when present on
45
+ * the cfg-specific proxy.
46
+ */
47
+ type ParentDestroyOps = Pick<
48
+ SandboxOps<SandboxCreateOptions, unknown, never>,
49
+ "destroySandbox"
50
+ >;
51
+ type ParentDeleteSnapshotOps = Pick<
52
+ SandboxOps<SandboxCreateOptions, unknown, "snapshot">,
53
+ "deleteSandboxSnapshot"
54
+ >;
55
+
29
56
  /**
30
57
  * Default `workflowRunTimeout` applied to every subagent child workflow
31
58
  * unless overridden via `SubagentConfig.workflowOptions.workflowRunTimeout`.
@@ -40,19 +67,11 @@ import { childSandboxReadySignal } from "./signals";
40
67
  * still catching hangs; agents that legitimately need longer should set an
41
68
  * explicit `workflowOptions.workflowRunTimeout`.
42
69
  */
43
- export const DEFAULT_SUBAGENT_WORKFLOW_RUN_TIMEOUT = "1h";
44
-
45
- /** Normalized sandbox config after resolving the union. */
46
- interface ResolvedSandboxConfig {
47
- source: "none" | "inherit" | "own";
48
- init: "per-call" | "once";
49
- continuation: "continue" | "fork" | "snapshot";
50
- shutdown?: SubagentSandboxShutdown;
51
- }
70
+ export const DEFAULT_SUBAGENT_WORKFLOW_RUN_TIMEOUT: Duration = "1h";
52
71
 
53
72
  function resolveSandboxConfig(
54
73
  config?: SubagentSandboxConfig
55
- ): ResolvedSandboxConfig {
74
+ ): ResolvedSubagentSandboxConfig {
56
75
  if (!config || config === "none") {
57
76
  return { source: "none", init: "per-call", continuation: "fork" };
58
77
  }
@@ -96,11 +115,31 @@ export function createSubagentHandler<
96
115
  } {
97
116
  const { taskQueue: parentTaskQueue } = workflowInfo();
98
117
 
99
- /** Sandbox ops proxy per subagent, built eagerly from `sandbox.proxy` factories. */
100
- const agentSandboxOps = new Map<string, SandboxOps>();
118
+ /**
119
+ * Sandbox ops proxy per subagent, built eagerly from `sandbox.proxy`
120
+ * factories.
121
+ *
122
+ * Split into two maps so each accepts only the cap-narrowed slice the
123
+ * parent actually consumes. `destroyOps` accepts every adapter (base
124
+ * `destroySandbox` is always present); `deleteSnapshotOps` is only
125
+ * populated for `continuation: "snapshot"` configs, and the
126
+ * `SubagentSandboxConfig` type guarantees the proxy carries
127
+ * `deleteSandboxSnapshot` when that continuation is selected.
128
+ */
129
+ const agentDestroyOps = new Map<string, ParentDestroyOps>();
130
+ const agentDeleteSnapshotOps = new Map<string, ParentDeleteSnapshotOps>();
101
131
  for (const cfg of subagents) {
102
- if (cfg.sandbox && cfg.sandbox !== "none") {
103
- agentSandboxOps.set(cfg.agentName, cfg.sandbox.proxy(cfg.agentName));
132
+ const cfgSandbox = cfg.sandbox;
133
+ if (!cfgSandbox || cfgSandbox === "none") continue;
134
+ if (cfgSandbox.continuation === "snapshot") {
135
+ // Pull the proxy here so the per-branch narrowing keeps
136
+ // `deleteSandboxSnapshot` in the inferred return type.
137
+ const proxy = cfgSandbox.proxy(cfg.agentName);
138
+ agentDestroyOps.set(cfg.agentName, proxy);
139
+ agentDeleteSnapshotOps.set(cfg.agentName, proxy);
140
+ } else {
141
+ const proxy = cfgSandbox.proxy(cfg.agentName);
142
+ agentDestroyOps.set(cfg.agentName, proxy);
104
143
  }
105
144
  }
106
145
 
@@ -182,7 +221,7 @@ export function createSubagentHandler<
182
221
 
183
222
  if (
184
223
  sandboxCfg.source !== "none" &&
185
- !agentSandboxOps.has(config.agentName)
224
+ !agentDestroyOps.has(config.agentName)
186
225
  ) {
187
226
  throw ApplicationFailure.create({
188
227
  message: `Subagent "${config.agentName}" uses a sandbox but no \`sandbox.proxy\` is configured on its SubagentConfig`,
@@ -272,7 +311,6 @@ export function createSubagentHandler<
272
311
  if (baseSnap) {
273
312
  sandbox = { mode: "from-snapshot", snapshot: baseSnap };
274
313
  }
275
- sandboxShutdownOverride = "snapshot";
276
314
  } else if (sandboxCfg.source === "own") {
277
315
  const isLazy = sandboxCfg.init === "once";
278
316
 
@@ -318,31 +356,16 @@ export function createSubagentHandler<
318
356
  sandboxId: baseSandboxId,
319
357
  };
320
358
  }
359
+ }
321
360
 
322
- // Ensure the sandbox survives for future continuation/fork:
323
- // - first lazy call (creator): pause-until-parent-close so parent can clean up
324
- // - continuation=continue: sandbox must survive for next call
325
- // - lazy+fork (non-creator): template must survive for future forks
326
- //
327
- // Skip the override when the user already configured a *-until-parent-close
328
- // shutdown that already guarantees survival.
329
- const userShutdown = sandboxCfg.shutdown;
330
- const alreadySurvives =
331
- userShutdown === "pause-until-parent-close" ||
332
- userShutdown === "keep-until-parent-close" ||
333
- userShutdown === "pause" ||
334
- userShutdown === "keep";
335
-
336
- const mustSurvive =
337
- isLazyCreator ||
338
- sandboxCfg.continuation === "continue" ||
339
- (isLazy && sandboxCfg.continuation === "fork");
340
-
341
- if (mustSurvive && !alreadySurvives) {
342
- sandboxShutdownOverride = isLazyCreator
343
- ? "pause-until-parent-close"
344
- : "pause";
345
- }
361
+ // Resolve the lifecycle decision (auto-inject pause/snapshot, etc.)
362
+ // through the SSOT same table the type-level `SubagentRequiredCaps`
363
+ // reads. Adding a new branch here means changing both. The matrix
364
+ // in `src/lib/sandbox/capability-types.test.ts` enforces the
365
+ // type-level / runtime agreement.
366
+ {
367
+ const lifecycle = resolveSubagentLifecycle(sandboxCfg, isLazyCreator);
368
+ sandboxShutdownOverride = lifecycle.shutdownOverride;
346
369
  }
347
370
 
348
371
  const workflowInput: SubagentWorkflowInput = {
@@ -555,7 +578,7 @@ export function createSubagentHandler<
555
578
  pendingDestroys.clear();
556
579
  await Promise.all(
557
580
  entries.map(async ({ agentName, sandboxId }) => {
558
- const ops = agentSandboxOps.get(agentName);
581
+ const ops = agentDestroyOps.get(agentName);
559
582
  if (!ops) {
560
583
  log.warn(
561
584
  "Skipping sandbox destroy — no sandbox.proxy registered for agent",
@@ -587,7 +610,7 @@ export function createSubagentHandler<
587
610
 
588
611
  await Promise.all(
589
612
  tagged.map(async ({ agentName, snapshot }) => {
590
- const ops = agentSandboxOps.get(agentName);
613
+ const ops = agentDeleteSnapshotOps.get(agentName);
591
614
  if (!ops) {
592
615
  log.warn(
593
616
  "Skipping snapshot delete — no sandbox.proxy registered for agent",