zeitlich 0.2.40 → 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 (134) hide show
  1. package/README.md +12 -1
  2. package/dist/{activities-CvUrG3YG.d.cts → activities-Coafq5zr.d.cts} +2 -2
  3. package/dist/{activities-CULxRzJ1.d.ts → activities-CrN-ghLo.d.ts} +2 -2
  4. package/dist/adapters/sandbox/daytona/index.cjs +4 -23
  5. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
  6. package/dist/adapters/sandbox/daytona/index.d.cts +18 -86
  7. package/dist/adapters/sandbox/daytona/index.d.ts +18 -86
  8. package/dist/adapters/sandbox/daytona/index.js +4 -23
  9. package/dist/adapters/sandbox/daytona/index.js.map +1 -1
  10. package/dist/adapters/sandbox/daytona/workflow.cjs +1 -7
  11. package/dist/adapters/sandbox/daytona/workflow.cjs.map +1 -1
  12. package/dist/adapters/sandbox/daytona/workflow.d.cts +9 -2
  13. package/dist/adapters/sandbox/daytona/workflow.d.ts +9 -2
  14. package/dist/adapters/sandbox/daytona/workflow.js +1 -7
  15. package/dist/adapters/sandbox/daytona/workflow.js.map +1 -1
  16. package/dist/adapters/sandbox/e2b/index.cjs +21 -3
  17. package/dist/adapters/sandbox/e2b/index.cjs.map +1 -1
  18. package/dist/adapters/sandbox/e2b/index.d.cts +48 -7
  19. package/dist/adapters/sandbox/e2b/index.d.ts +48 -7
  20. package/dist/adapters/sandbox/e2b/index.js +22 -5
  21. package/dist/adapters/sandbox/e2b/index.js.map +1 -1
  22. package/dist/adapters/sandbox/e2b/workflow.cjs.map +1 -1
  23. package/dist/adapters/sandbox/e2b/workflow.d.cts +4 -2
  24. package/dist/adapters/sandbox/e2b/workflow.d.ts +4 -2
  25. package/dist/adapters/sandbox/e2b/workflow.js.map +1 -1
  26. package/dist/adapters/sandbox/inmemory/index.cjs +11 -0
  27. package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
  28. package/dist/adapters/sandbox/inmemory/index.d.cts +11 -3
  29. package/dist/adapters/sandbox/inmemory/index.d.ts +11 -3
  30. package/dist/adapters/sandbox/inmemory/index.js +11 -1
  31. package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
  32. package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -1
  33. package/dist/adapters/sandbox/inmemory/workflow.d.cts +4 -2
  34. package/dist/adapters/sandbox/inmemory/workflow.d.ts +4 -2
  35. package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -1
  36. package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
  37. package/dist/adapters/thread/anthropic/index.d.cts +6 -6
  38. package/dist/adapters/thread/anthropic/index.d.ts +6 -6
  39. package/dist/adapters/thread/anthropic/index.js.map +1 -1
  40. package/dist/adapters/thread/anthropic/workflow.d.cts +6 -6
  41. package/dist/adapters/thread/anthropic/workflow.d.ts +6 -6
  42. package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
  43. package/dist/adapters/thread/google-genai/index.d.cts +6 -6
  44. package/dist/adapters/thread/google-genai/index.d.ts +6 -6
  45. package/dist/adapters/thread/google-genai/index.js.map +1 -1
  46. package/dist/adapters/thread/google-genai/workflow.d.cts +6 -6
  47. package/dist/adapters/thread/google-genai/workflow.d.ts +6 -6
  48. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  49. package/dist/adapters/thread/langchain/index.d.cts +6 -6
  50. package/dist/adapters/thread/langchain/index.d.ts +6 -6
  51. package/dist/adapters/thread/langchain/index.js.map +1 -1
  52. package/dist/adapters/thread/langchain/workflow.d.cts +6 -6
  53. package/dist/adapters/thread/langchain/workflow.d.ts +6 -6
  54. package/dist/index.cjs +316 -119
  55. package/dist/index.cjs.map +1 -1
  56. package/dist/index.d.cts +93 -17
  57. package/dist/index.d.ts +93 -17
  58. package/dist/index.js +317 -120
  59. package/dist/index.js.map +1 -1
  60. package/dist/{proxy-5EbwzaY4.d.cts → proxy-Bf7uI-Hw.d.cts} +1 -1
  61. package/dist/{proxy-wZufFfBh.d.ts → proxy-COqA95FW.d.ts} +1 -1
  62. package/dist/{thread-manager-BqBAIsED.d.ts → thread-manager-BhkOyQ1I.d.ts} +2 -2
  63. package/dist/{thread-manager-BNiIt5r8.d.ts → thread-manager-Bi1XlbpJ.d.ts} +2 -2
  64. package/dist/{thread-manager-DF8WuCRs.d.cts → thread-manager-BsLO3Fgc.d.cts} +2 -2
  65. package/dist/{thread-manager-BoN5DOvG.d.cts → thread-manager-wRVVBFgj.d.cts} +2 -2
  66. package/dist/{types-C7OoY7h8.d.ts → types-BkX4HLzi.d.ts} +1 -1
  67. package/dist/{types-CuISs0Ub.d.cts → types-C66-BVBr.d.cts} +1 -1
  68. package/dist/types-CJ7tCdl6.d.cts +266 -0
  69. package/dist/types-CJ7tCdl6.d.ts +266 -0
  70. package/dist/{types-DeQH84C_.d.ts → types-CdALEF3z.d.cts} +342 -23
  71. package/dist/{types-Cn2r3ol3.d.cts → types-ChAy_jSP.d.ts} +342 -23
  72. package/dist/types-CjY93AWZ.d.cts +84 -0
  73. package/dist/types-gVa5XCWD.d.ts +84 -0
  74. package/dist/{workflow-DhplIN65.d.cts → workflow-BwT5EybR.d.ts} +7 -6
  75. package/dist/{workflow-C2MZZj5K.d.ts → workflow-DMmiaw6w.d.cts} +7 -6
  76. package/dist/workflow.cjs +138 -77
  77. package/dist/workflow.cjs.map +1 -1
  78. package/dist/workflow.d.cts +4 -4
  79. package/dist/workflow.d.ts +4 -4
  80. package/dist/workflow.js +139 -78
  81. package/dist/workflow.js.map +1 -1
  82. package/package.json +17 -33
  83. package/src/adapters/sandbox/daytona/index.ts +25 -48
  84. package/src/adapters/sandbox/daytona/proxy.ts +7 -8
  85. package/src/adapters/sandbox/e2b/README.md +81 -0
  86. package/src/adapters/sandbox/e2b/index.ts +53 -11
  87. package/src/adapters/sandbox/e2b/keep-alive.test.ts +115 -0
  88. package/src/adapters/sandbox/e2b/proxy.ts +3 -2
  89. package/src/adapters/sandbox/e2b/types.ts +34 -2
  90. package/src/adapters/sandbox/inmemory/index.ts +21 -1
  91. package/src/adapters/sandbox/inmemory/proxy.ts +7 -3
  92. package/src/index.ts +1 -1
  93. package/src/lib/activity.ts +5 -0
  94. package/src/lib/sandbox/capability-types.test.ts +859 -0
  95. package/src/lib/sandbox/index.ts +1 -0
  96. package/src/lib/sandbox/manager.ts +187 -31
  97. package/src/lib/sandbox/types.ts +189 -46
  98. package/src/lib/session/index.ts +1 -0
  99. package/src/lib/session/session.integration.test.ts +58 -0
  100. package/src/lib/session/session.ts +109 -50
  101. package/src/lib/session/types.ts +189 -8
  102. package/src/lib/subagent/handler.ts +66 -43
  103. package/src/lib/subagent/subagent.integration.test.ts +2 -0
  104. package/src/lib/subagent/types.ts +492 -16
  105. package/src/lib/subagent/workflow.ts +11 -1
  106. package/src/lib/tool-router/auto-append-sandbox.integration.test.ts +158 -0
  107. package/src/lib/tool-router/index.ts +1 -1
  108. package/src/lib/tool-router/with-sandbox.ts +45 -2
  109. package/src/lib/virtual-fs/filesystem.ts +41 -16
  110. package/src/lib/virtual-fs/types.ts +19 -0
  111. package/src/lib/virtual-fs/virtual-fs.test.ts +204 -1
  112. package/src/tools/read-file/handler.test.ts +83 -0
  113. package/src/workflow.ts +3 -0
  114. package/tsup.config.ts +0 -4
  115. package/dist/adapters/sandbox/bedrock/index.cjs +0 -457
  116. package/dist/adapters/sandbox/bedrock/index.cjs.map +0 -1
  117. package/dist/adapters/sandbox/bedrock/index.d.cts +0 -25
  118. package/dist/adapters/sandbox/bedrock/index.d.ts +0 -25
  119. package/dist/adapters/sandbox/bedrock/index.js +0 -454
  120. package/dist/adapters/sandbox/bedrock/index.js.map +0 -1
  121. package/dist/adapters/sandbox/bedrock/workflow.cjs +0 -36
  122. package/dist/adapters/sandbox/bedrock/workflow.cjs.map +0 -1
  123. package/dist/adapters/sandbox/bedrock/workflow.d.cts +0 -29
  124. package/dist/adapters/sandbox/bedrock/workflow.d.ts +0 -29
  125. package/dist/adapters/sandbox/bedrock/workflow.js +0 -34
  126. package/dist/adapters/sandbox/bedrock/workflow.js.map +0 -1
  127. package/dist/types-DAsQ21Rt.d.ts +0 -74
  128. package/dist/types-lm8tMNJQ.d.cts +0 -74
  129. package/dist/types-yx0LzPGn.d.cts +0 -173
  130. package/dist/types-yx0LzPGn.d.ts +0 -173
  131. package/src/adapters/sandbox/bedrock/filesystem.ts +0 -340
  132. package/src/adapters/sandbox/bedrock/index.ts +0 -274
  133. package/src/adapters/sandbox/bedrock/proxy.ts +0 -59
  134. package/src/adapters/sandbox/bedrock/types.ts +0 -24
@@ -0,0 +1,859 @@
1
+ /**
2
+ * Type-level + runtime fixture covering the capability-generic
3
+ * {@link SandboxProvider} / {@link SandboxOps} contracts.
4
+ *
5
+ * The unused functions in this file are intentional: their bodies are
6
+ * meaningful at compile time only — `tsc --noEmit` (run via the
7
+ * project's `npm run typecheck` and the husky pre-commit hook) is what
8
+ * enforces the type-level contract here. The runtime `expect` calls
9
+ * pin the parts of the contract TypeScript can't enforce on its own
10
+ * (the runtime `supportedCapabilities` set), so the file can't be
11
+ * silently dropped from the suite without notice.
12
+ *
13
+ * The negative assertions use `// @ts-expect-error` so a regression in
14
+ * the type-level gating (e.g. accidentally re-widening a narrowed
15
+ * adapter) flips them from "expected" to "unexpected" and breaks the
16
+ * type check.
17
+ */
18
+ import { describe, expect, it } from "vitest";
19
+ import { SandboxManager } from "./manager";
20
+ import type {
21
+ Sandbox,
22
+ SandboxCapabilities,
23
+ SandboxCapability,
24
+ SandboxCreateOptions,
25
+ SandboxCreateResult,
26
+ SandboxOps,
27
+ SandboxProvider,
28
+ SandboxSnapshot,
29
+ } from "./types";
30
+ import { InMemorySandboxProvider } from "../../adapters/sandbox/inmemory/index";
31
+ import { DaytonaSandboxProvider } from "../../adapters/sandbox/daytona/index";
32
+ import type { E2bSandboxProvider } from "../../adapters/sandbox/e2b/index";
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Positive: wide-cap providers expose the gated lifecycle methods
36
+ // ---------------------------------------------------------------------------
37
+
38
+ function _wideProviderCallsCompile(p: InMemorySandboxProvider): void {
39
+ void p.pause("id");
40
+ void p.resume("id");
41
+ void p.snapshot("id");
42
+ void p.deleteSnapshot({} as SandboxSnapshot);
43
+ void p.restore({} as SandboxSnapshot);
44
+ void p.fork("id");
45
+ }
46
+
47
+ function _e2bProviderCallsCompile(p: E2bSandboxProvider): void {
48
+ void p.pause("id");
49
+ void p.snapshot("id");
50
+ void p.fork("id");
51
+ }
52
+
53
+ function _wideOpsCallsCompile(ops: SandboxOps): void {
54
+ void ops.createSandbox();
55
+ void ops.destroySandbox("id");
56
+ void ops.pauseSandbox("id");
57
+ void ops.resumeSandbox("id");
58
+ void ops.snapshotSandbox("id");
59
+ void ops.deleteSandboxSnapshot({} as SandboxSnapshot);
60
+ void ops.restoreSandbox({} as SandboxSnapshot);
61
+ void ops.forkSandbox("id");
62
+ }
63
+
64
+ // ---------------------------------------------------------------------------
65
+ // Negative: narrowed (`TCaps = never`) adapters drop the gated methods
66
+ // ---------------------------------------------------------------------------
67
+
68
+ function _daytonaCallsRejected(p: DaytonaSandboxProvider): void {
69
+ void p.create();
70
+ void p.get("id");
71
+ void p.destroy("id");
72
+ // @ts-expect-error daytona declares `TCaps = never`, so `pause` is gone
73
+ void p.pause("id");
74
+ // @ts-expect-error daytona declares `TCaps = never`, so `resume` is gone
75
+ void p.resume("id");
76
+ // @ts-expect-error daytona declares `TCaps = never`, so `snapshot` is gone
77
+ void p.snapshot("id");
78
+ // @ts-expect-error daytona declares `TCaps = never`, so `restore` is gone
79
+ void p.restore({} as SandboxSnapshot);
80
+ // @ts-expect-error daytona declares `TCaps = never`, so `fork` is gone
81
+ void p.fork("id");
82
+ // @ts-expect-error daytona declares `TCaps = never`, so `deleteSnapshot` is gone
83
+ void p.deleteSnapshot({} as SandboxSnapshot);
84
+ }
85
+
86
+ function _narrowOpsRejected(
87
+ daytonaOps: SandboxOps<{ id?: string }, unknown, never>
88
+ ): void {
89
+ void daytonaOps.createSandbox();
90
+ void daytonaOps.destroySandbox("id");
91
+ // @ts-expect-error narrowed ops drop the gated method entirely
92
+ void daytonaOps.pauseSandbox("id");
93
+ // @ts-expect-error narrowed ops drop the gated method entirely
94
+ void daytonaOps.snapshotSandbox("id");
95
+ // @ts-expect-error narrowed ops drop the gated method entirely
96
+ void daytonaOps.forkSandbox("id");
97
+ // @ts-expect-error narrowed ops drop the gated method entirely
98
+ void daytonaOps.restoreSandbox({} as SandboxSnapshot);
99
+ }
100
+
101
+ // ---------------------------------------------------------------------------
102
+ // Synthetic partial-cap fixture (`TCaps = "pause" | "resume"`)
103
+ //
104
+ // Mirrors the shape of the bedrock-runtime adapter (which lives on a
105
+ // separate branch) so the contract for partial-cap providers is pinned
106
+ // here and stays stable across the bedrock-runtime rebase.
107
+ // ---------------------------------------------------------------------------
108
+
109
+ const PAUSABLE_CAPS = [
110
+ "pause",
111
+ "resume",
112
+ ] as const satisfies readonly SandboxCapability[];
113
+ type PausableCaps = (typeof PAUSABLE_CAPS)[number];
114
+
115
+ class FakePausableProvider
116
+ implements SandboxProvider<SandboxCreateOptions, Sandbox, PausableCaps>
117
+ {
118
+ readonly id = "fakePausable";
119
+ readonly capabilities: SandboxCapabilities = {
120
+ filesystem: false,
121
+ execution: false,
122
+ persistence: false,
123
+ };
124
+ readonly supportedCapabilities: ReadonlySet<PausableCaps> = new Set(
125
+ PAUSABLE_CAPS
126
+ );
127
+
128
+ async create(_options?: SandboxCreateOptions): Promise<SandboxCreateResult> {
129
+ throw new Error("not used in type-level fixture");
130
+ }
131
+ async get(_id: string): Promise<Sandbox> {
132
+ throw new Error("not used in type-level fixture");
133
+ }
134
+ async destroy(_id: string): Promise<void> {
135
+ /* not used */
136
+ }
137
+ async pause(_id: string, _ttlSeconds?: number): Promise<void> {
138
+ /* not used */
139
+ }
140
+ async resume(_id: string): Promise<void> {
141
+ /* not used */
142
+ }
143
+ }
144
+
145
+ function _partialCapPositiveCalls(p: FakePausableProvider): void {
146
+ void p.pause("id");
147
+ void p.pause("id", 60);
148
+ void p.resume("id");
149
+ void p.create();
150
+ void p.destroy("id");
151
+ }
152
+
153
+ function _partialCapNegativeCalls(p: FakePausableProvider): void {
154
+ // @ts-expect-error TCaps = "pause" | "resume" omits "snapshot"
155
+ void p.snapshot("id");
156
+ // @ts-expect-error TCaps = "pause" | "resume" omits "deleteSnapshot"
157
+ void p.deleteSnapshot({} as SandboxSnapshot);
158
+ // @ts-expect-error TCaps = "pause" | "resume" omits "restore"
159
+ void p.restore({} as SandboxSnapshot);
160
+ // @ts-expect-error TCaps = "pause" | "resume" omits "fork"
161
+ void p.fork("id");
162
+ }
163
+
164
+ function _partialCapOpsGating(
165
+ ops: SandboxOps<SandboxCreateOptions, unknown, PausableCaps>
166
+ ): void {
167
+ void ops.pauseSandbox("id");
168
+ void ops.resumeSandbox("id");
169
+ // @ts-expect-error TCaps = "pause" | "resume" omits "snapshot"
170
+ void ops.snapshotSandbox("id");
171
+ // @ts-expect-error TCaps = "pause" | "resume" omits "fork"
172
+ void ops.forkSandbox("id");
173
+ }
174
+
175
+ // ---------------------------------------------------------------------------
176
+ // Type-level guard: the runtime `supportedCapabilities` set element type
177
+ // must be a subset of `TCaps`.
178
+ //
179
+ // The constraint on `SandboxProvider` is
180
+ // `ReadonlySet<TCaps & SandboxCapability>`. This block proves that an
181
+ // adapter declared with `TCaps = never` cannot smuggle "pause" through
182
+ // the runtime set.
183
+ // ---------------------------------------------------------------------------
184
+
185
+ function _runtimeSetCannotExceedTCaps(): void {
186
+ // @ts-expect-error a never-cap provider cannot ship a non-empty set
187
+ const _badSet: ReadonlySet<never> = new Set<SandboxCapability>(["pause"]);
188
+ // The well-typed empty case still works:
189
+ const _goodSet: ReadonlySet<never> = new Set();
190
+ void _badSet;
191
+ void _goodSet;
192
+ }
193
+
194
+ // ---------------------------------------------------------------------------
195
+ // Runtime introspection: supportedCapabilities matches the type-level set
196
+ // ---------------------------------------------------------------------------
197
+
198
+ describe("SandboxCapability fixture — type ↔ runtime alignment", () => {
199
+ it("InMemorySandboxProvider exposes the full capability set at runtime", () => {
200
+ const inMem = new InMemorySandboxProvider();
201
+ expect([...inMem.supportedCapabilities].sort()).toEqual(
202
+ ["fork", "pause", "restore", "resume", "snapshot"].sort()
203
+ );
204
+ expect(inMem.supportedCapabilities.size).toBe(5);
205
+ });
206
+
207
+ it("DaytonaSandboxProvider's runtime supportedCapabilities is empty", () => {
208
+ const daytona = new DaytonaSandboxProvider({
209
+ apiKey: "test",
210
+ apiUrl: "https://example.invalid",
211
+ });
212
+ expect(daytona.supportedCapabilities.size).toBe(0);
213
+ expect([...daytona.supportedCapabilities]).toEqual([]);
214
+ });
215
+
216
+ it("partial-cap provider's runtime set matches its declared TCaps", () => {
217
+ const partial = new FakePausableProvider();
218
+ expect([...partial.supportedCapabilities].sort()).toEqual(
219
+ ["pause", "resume"].sort()
220
+ );
221
+ expect(partial.supportedCapabilities.size).toBe(2);
222
+ });
223
+ });
224
+
225
+ // ---------------------------------------------------------------------------
226
+ // SandboxManager constructor-time runtime consistency check
227
+ //
228
+ // Belt-and-suspenders for the type-level subset constraint: TS prevents
229
+ // the runtime set from containing capabilities not in `TCaps`, but it
230
+ // can't catch a provider that declares a cap and forgets to ship the
231
+ // matching method (or vice versa). The manager trips a loud failure at
232
+ // construction time for both shapes; this fixture pins that contract.
233
+ // ---------------------------------------------------------------------------
234
+
235
+ class _DriftedProvider
236
+ implements SandboxProvider<SandboxCreateOptions, Sandbox, never>
237
+ {
238
+ readonly id = "drifted";
239
+ readonly capabilities: SandboxCapabilities = {
240
+ filesystem: false,
241
+ execution: false,
242
+ persistence: false,
243
+ };
244
+ // Force a runtime set that exceeds `TCaps = never`. We cast through
245
+ // `unknown` because the type-level constraint already forbids this
246
+ // shape — exactly the scenario the runtime guard is meant to catch.
247
+ readonly supportedCapabilities: ReadonlySet<never> = new Set<unknown>([
248
+ "pause",
249
+ ]) as unknown as ReadonlySet<never>;
250
+
251
+ async create(): Promise<SandboxCreateResult> {
252
+ throw new Error("not used");
253
+ }
254
+ async get(): Promise<Sandbox> {
255
+ throw new Error("not used");
256
+ }
257
+ async destroy(): Promise<void> {}
258
+ }
259
+
260
+ class _ImplWithoutDeclProvider {
261
+ readonly id = "impl-without-decl";
262
+ readonly capabilities: SandboxCapabilities = {
263
+ filesystem: false,
264
+ execution: false,
265
+ persistence: false,
266
+ };
267
+ // Empty runtime set, but ships a `pause` method below — drift in the
268
+ // opposite direction.
269
+ readonly supportedCapabilities: ReadonlySet<SandboxCapability> = new Set();
270
+
271
+ async create(): Promise<SandboxCreateResult> {
272
+ throw new Error("not used");
273
+ }
274
+ async get(): Promise<Sandbox> {
275
+ throw new Error("not used");
276
+ }
277
+ async destroy(): Promise<void> {}
278
+ async pause(_id: string): Promise<void> {}
279
+ }
280
+
281
+ // ---------------------------------------------------------------------------
282
+ // SubagentSandboxConfig.proxy × continuation × adapter matrix
283
+ //
284
+ // `SubagentContinuationCaps` encodes the caps the chosen continuation
285
+ // **strategy** needs anywhere in the codebase (parent shutdown handler
286
+ // AND child session lifecycle), pinning the contract on the proxy
287
+ // field — which is structurally identical to the child's adapter type
288
+ // — so any `(continuation, adapter)` combination that can't execute
289
+ // at runtime fails to typecheck here:
290
+ //
291
+ // continuation: "continue" → no gated cap required (any adapter)
292
+ // continuation: "fork" → "fork" cap required (child calls forkSandbox)
293
+ // continuation: "snapshot" → "snapshot" | "restore" required
294
+ // (child calls snapshotSandbox + restoreSandbox;
295
+ // parent calls deleteSandboxSnapshot)
296
+ //
297
+ // The matrix is structured by (source × continuation × adapter), with
298
+ // expected pass/fail labelled per cell so a future regression that
299
+ // re-widens (over-reject) or re-narrows (under-reject) one cell trips
300
+ // the type check immediately.
301
+ // ---------------------------------------------------------------------------
302
+
303
+ import type { SubagentSandboxConfig } from "../subagent/types";
304
+ import { proxyDaytonaSandboxOps } from "../../adapters/sandbox/daytona/proxy";
305
+ import { proxyE2bSandboxOps } from "../../adapters/sandbox/e2b/proxy";
306
+ import { proxyInMemorySandboxOps } from "../../adapters/sandbox/inmemory/proxy";
307
+
308
+ // Helper that pins the matrix cell type to `SubagentSandboxConfig` so
309
+ // `@ts-expect-error` directives consistently land on the call line. The
310
+ // helper is never invoked at runtime — the matrix is a pure
311
+ // type-level fixture wrapped in `_subagentMatrix()` below.
312
+ const subagentCfg = <T extends SubagentSandboxConfig>(c: T): T => c;
313
+
314
+ function _subagentMatrix(): void {
315
+ // ===============================================================
316
+ // own × continue
317
+ // ===============================================================
318
+ //
319
+ // `mustSurvive=true` for `continuation: "continue"`. When the
320
+ // user's shutdown isn't a survival value (`pause` / `keep` /
321
+ // `pause-until-parent-close` / `keep-until-parent-close`), the
322
+ // handler auto-injects `"pause"` (subsequent calls) or
323
+ // `"pause-until-parent-close"` (creator first call). Each cell
324
+ // below reflects the resulting required cap union.
325
+
326
+ // shutdown omitted → auto-injected pause/pause-until-parent-close.
327
+ // @ts-expect-error pauseSandbox/resumeSandbox missing — auto-injection on continue requires "pause" (and "resume" via pause-until-parent-close)
328
+ subagentCfg({
329
+ source: "own",
330
+ continuation: "continue",
331
+ proxy: proxyDaytonaSandboxOps,
332
+ });
333
+ subagentCfg({
334
+ source: "own",
335
+ continuation: "continue",
336
+ proxy: proxyE2bSandboxOps,
337
+ });
338
+ subagentCfg({
339
+ source: "own",
340
+ continuation: "continue",
341
+ proxy: proxyInMemorySandboxOps,
342
+ });
343
+
344
+ // shutdown: "keep" → alreadySurvives, no auto-inject. All compile.
345
+ subagentCfg({
346
+ source: "own",
347
+ continuation: "continue",
348
+ shutdown: "keep",
349
+ proxy: proxyDaytonaSandboxOps,
350
+ });
351
+ subagentCfg({
352
+ source: "own",
353
+ continuation: "continue",
354
+ shutdown: "keep",
355
+ proxy: proxyE2bSandboxOps,
356
+ });
357
+
358
+ // shutdown: "pause" → propagates → "pause" cap.
359
+ subagentCfg({
360
+ source: "own",
361
+ continuation: "continue",
362
+ shutdown: "pause",
363
+ // @ts-expect-error pauseSandbox missing — shutdown: "pause" requires "pause"
364
+ proxy: proxyDaytonaSandboxOps,
365
+ });
366
+ subagentCfg({
367
+ source: "own",
368
+ continuation: "continue",
369
+ shutdown: "pause",
370
+ proxy: proxyE2bSandboxOps,
371
+ });
372
+
373
+ // shutdown: "destroy" → NOT alreadySurvives, auto-injection applies.
374
+ subagentCfg({
375
+ source: "own",
376
+ continuation: "continue",
377
+ shutdown: "destroy",
378
+ // @ts-expect-error pauseSandbox missing — shutdown: "destroy" on continue still triggers pause auto-injection
379
+ proxy: proxyDaytonaSandboxOps,
380
+ });
381
+
382
+ // ===============================================================
383
+ // own × fork × per-call (default init)
384
+ // ===============================================================
385
+
386
+ // not mustSurvive, no auto-inject, needs "fork" only.
387
+ subagentCfg({
388
+ source: "own",
389
+ continuation: "fork",
390
+ init: "per-call",
391
+ // @ts-expect-error forkSandbox missing — continuation: "fork" requires "fork"
392
+ proxy: proxyDaytonaSandboxOps,
393
+ });
394
+ subagentCfg({
395
+ source: "own",
396
+ continuation: "fork",
397
+ init: "per-call",
398
+ proxy: proxyE2bSandboxOps,
399
+ });
400
+ subagentCfg({
401
+ source: "own",
402
+ continuation: "fork",
403
+ init: "per-call",
404
+ proxy: proxyInMemorySandboxOps,
405
+ });
406
+
407
+ // ===============================================================
408
+ // own × fork × once (mustSurvive → auto-inject pause)
409
+ // ===============================================================
410
+
411
+ subagentCfg({
412
+ source: "own",
413
+ continuation: "fork",
414
+ init: "once",
415
+ // @ts-expect-error forkSandbox AND pauseSandbox missing — fork+once auto-injects pause and needs "fork"
416
+ proxy: proxyDaytonaSandboxOps,
417
+ });
418
+ subagentCfg({
419
+ source: "own",
420
+ continuation: "fork",
421
+ init: "once",
422
+ proxy: proxyE2bSandboxOps,
423
+ });
424
+
425
+ // ===============================================================
426
+ // own × snapshot (overrides shutdown to "snapshot")
427
+ // ===============================================================
428
+
429
+ subagentCfg({
430
+ source: "own",
431
+ continuation: "snapshot",
432
+ proxy: proxyE2bSandboxOps,
433
+ });
434
+ subagentCfg({
435
+ source: "own",
436
+ continuation: "snapshot",
437
+ proxy: proxyInMemorySandboxOps,
438
+ });
439
+ subagentCfg({
440
+ source: "own",
441
+ continuation: "snapshot",
442
+ // @ts-expect-error snapshotSandbox / restoreSandbox / deleteSandboxSnapshot missing
443
+ proxy: proxyDaytonaSandboxOps,
444
+ });
445
+
446
+ // ===============================================================
447
+ // inherit × continue (mode = "inherit" → sandboxOwned=false)
448
+ // ===============================================================
449
+ //
450
+ // No exit-shutdown caps fire regardless of the shutdown value.
451
+
452
+ subagentCfg({
453
+ source: "inherit",
454
+ continuation: "continue",
455
+ proxy: proxyDaytonaSandboxOps,
456
+ });
457
+ subagentCfg({
458
+ source: "inherit",
459
+ continuation: "continue",
460
+ shutdown: "pause",
461
+ proxy: proxyDaytonaSandboxOps,
462
+ });
463
+ subagentCfg({
464
+ source: "inherit",
465
+ continuation: "continue",
466
+ proxy: proxyE2bSandboxOps,
467
+ });
468
+
469
+ // ===============================================================
470
+ // inherit × fork (child runs mode: "fork" against parent's sandbox)
471
+ // ===============================================================
472
+
473
+ subagentCfg({
474
+ source: "inherit",
475
+ continuation: "fork",
476
+ proxy: proxyE2bSandboxOps,
477
+ });
478
+ subagentCfg({
479
+ source: "inherit",
480
+ continuation: "fork",
481
+ // @ts-expect-error forkSandbox missing — inherit+fork still requires "fork"
482
+ proxy: proxyDaytonaSandboxOps,
483
+ });
484
+
485
+ // inherit + snapshot is structurally invalid (continuation domain).
486
+ subagentCfg({
487
+ source: "inherit",
488
+ // @ts-expect-error inherit + snapshot is invalid by design
489
+ continuation: "snapshot",
490
+ proxy: proxyE2bSandboxOps,
491
+ });
492
+ }
493
+ void _subagentMatrix;
494
+
495
+ // --- Synthetic adapter coverage of the "snapshot strategy needs both
496
+ // `snapshot` and `restore`" half of the (B) invariant: a proxy that ships
497
+ // only `snapshot` (no `restore`) must still be rejected for snapshot
498
+ // continuations, otherwise the child session's restoreSandbox call would
499
+ // throw at runtime. The synthetic proxies use `declare const` so they
500
+ // only exist at compile time; the wrapping `_syntheticAdapterMatrix`
501
+ // function is never called, keeping these checks pure type-level.
502
+
503
+ declare const proxySnapshotOnly: (
504
+ scope: string
505
+ ) => SandboxOps<SandboxCreateOptions, unknown, "snapshot">;
506
+ declare const proxyForkOnly: (
507
+ scope: string
508
+ ) => SandboxOps<SandboxCreateOptions, unknown, "fork">;
509
+
510
+ function _syntheticAdapterMatrix(): void {
511
+ const _ownSnapshotSnapshotOnly: SubagentSandboxConfig = {
512
+ source: "own",
513
+ continuation: "snapshot",
514
+ // @ts-expect-error snapshot continuation needs `restore` too — proxy is missing restoreSandbox
515
+ proxy: proxySnapshotOnly,
516
+ };
517
+
518
+ // Symmetric positive: a fork-only proxy is enough for fork continuation
519
+ // (no snapshot/restore needed). Pins (B) — the strategy's required cap
520
+ // set is the *minimum* the adapter must expose.
521
+ const _ownForkForkOnly: SubagentSandboxConfig = {
522
+ source: "own",
523
+ continuation: "fork",
524
+ proxy: proxyForkOnly,
525
+ };
526
+
527
+ void [_ownSnapshotSnapshotOnly, _ownForkForkOnly];
528
+ }
529
+ void _syntheticAdapterMatrix;
530
+
531
+ // ---------------------------------------------------------------------------
532
+ // SessionConfig.sandboxOps × (sandbox.mode × sandboxShutdown × adapter) matrix
533
+ //
534
+ // Mirror of the subagent matrix for `createSession`. The session's
535
+ // `sandboxOps` field is gated on the literal types of the surrounding
536
+ // `sandbox` and `sandboxShutdown` fields via `SessionRequiredCaps`:
537
+ //
538
+ // sandbox.mode === "fork" → "fork" cap
539
+ // sandbox.mode === "from-snapshot" → "restore" cap
540
+ // sandbox.mode === "continue" +
541
+ // sandboxShutdown ===
542
+ // "pause-until-parent-close" → "resume" cap
543
+ // sandboxShutdown === "snapshot" → "snapshot" cap
544
+ // sandboxShutdown === "pause" |
545
+ // "pause-until-parent-close" → "pause" cap
546
+ //
547
+ // The default wide `TInit` / `TShutdown` resolve to the full union, so
548
+ // existing call sites that don't pin the literals still require the
549
+ // full cap set (current behaviour). The matrix here pins both
550
+ // directions: when literals are passed explicitly, narrow adapters
551
+ // satisfy safe combinations and are rejected on unsafe ones.
552
+ // ---------------------------------------------------------------------------
553
+
554
+ import type {
555
+ SessionConfig,
556
+ SessionRequiredCaps,
557
+ ThreadOps,
558
+ } from "../session/types";
559
+ import type { ToolMap } from "../tool-router/types";
560
+ import type { ActivityInterfaceFor } from "@temporalio/workflow";
561
+
562
+ declare const fakeThreadOps: ActivityInterfaceFor<ThreadOps<string>>;
563
+ declare const fakeTools: ToolMap;
564
+
565
+ function _sessionMatrix(): void {
566
+ // Common config slot — all the non-sandbox required fields.
567
+ type Base = Omit<
568
+ SessionConfig<ToolMap, unknown, string>,
569
+ "sandboxOps" | "sandbox" | "sandboxShutdown"
570
+ >;
571
+ const base: Base = {
572
+ agentName: "a",
573
+ runAgent: async () => ({ message: null, rawToolCalls: [] }),
574
+ threadOps: fakeThreadOps,
575
+ tools: fakeTools,
576
+ buildContextMessage: () => "",
577
+ };
578
+
579
+ // --- mode: "new" + shutdown: "destroy" requires no caps. Daytona OK. -----
580
+ const _newDestroyDaytona: SessionConfig<
581
+ ToolMap,
582
+ unknown,
583
+ string,
584
+ { mode: "new" },
585
+ "destroy"
586
+ > = {
587
+ ...base,
588
+ sandbox: { mode: "new" },
589
+ sandboxShutdown: "destroy",
590
+ sandboxOps: proxyDaytonaSandboxOps("scope"),
591
+ };
592
+
593
+ // --- mode: "fork" requires "fork" cap → daytona rejected. ----------------
594
+ const _forkDaytona: SessionConfig<
595
+ ToolMap,
596
+ unknown,
597
+ string,
598
+ { mode: "fork"; sandboxId: string },
599
+ "destroy"
600
+ > = {
601
+ ...base,
602
+ sandbox: { mode: "fork", sandboxId: "x" },
603
+ sandboxShutdown: "destroy",
604
+ // @ts-expect-error mode: "fork" requires "fork" cap; daytona's proxy doesn't expose forkSandbox
605
+ sandboxOps: proxyDaytonaSandboxOps("scope"),
606
+ };
607
+ // e2b satisfies "fork".
608
+ const _forkE2b: SessionConfig<
609
+ ToolMap,
610
+ unknown,
611
+ string,
612
+ { mode: "fork"; sandboxId: string },
613
+ "destroy"
614
+ > = {
615
+ ...base,
616
+ sandbox: { mode: "fork", sandboxId: "x" },
617
+ sandboxShutdown: "destroy",
618
+ sandboxOps: proxyE2bSandboxOps("scope"),
619
+ };
620
+
621
+ // --- mode: "from-snapshot" requires "restore". Daytona rejected. ---------
622
+ const _fromSnapshotDaytona: SessionConfig<
623
+ ToolMap,
624
+ unknown,
625
+ string,
626
+ { mode: "from-snapshot"; snapshot: SandboxSnapshot },
627
+ "destroy"
628
+ > = {
629
+ ...base,
630
+ sandbox: {
631
+ mode: "from-snapshot",
632
+ snapshot: {} as SandboxSnapshot,
633
+ },
634
+ sandboxShutdown: "destroy",
635
+ // @ts-expect-error mode: "from-snapshot" requires "restore"; daytona doesn't have restoreSandbox
636
+ sandboxOps: proxyDaytonaSandboxOps("scope"),
637
+ };
638
+
639
+ // --- shutdown: "snapshot" requires "snapshot" cap. Daytona rejected. -----
640
+ const _snapshotShutdownDaytona: SessionConfig<
641
+ ToolMap,
642
+ unknown,
643
+ string,
644
+ { mode: "new" },
645
+ "snapshot"
646
+ > = {
647
+ ...base,
648
+ sandbox: { mode: "new" },
649
+ sandboxShutdown: "snapshot",
650
+ // @ts-expect-error shutdown: "snapshot" requires "snapshot" cap; daytona doesn't have snapshotSandbox
651
+ sandboxOps: proxyDaytonaSandboxOps("scope"),
652
+ };
653
+
654
+ // --- shutdown: "pause" requires "pause" cap. Daytona rejected. -----------
655
+ const _pauseShutdownDaytona: SessionConfig<
656
+ ToolMap,
657
+ unknown,
658
+ string,
659
+ { mode: "new" },
660
+ "pause"
661
+ > = {
662
+ ...base,
663
+ sandbox: { mode: "new" },
664
+ sandboxShutdown: "pause",
665
+ // @ts-expect-error shutdown: "pause" requires "pause" cap; daytona doesn't have pauseSandbox
666
+ sandboxOps: proxyDaytonaSandboxOps("scope"),
667
+ };
668
+
669
+ // --- mode: "continue" + shutdown: "pause-until-parent-close" requires
670
+ // "resume" + "pause" caps. Daytona rejected. ------------------------------
671
+ const _continueResumeDaytona: SessionConfig<
672
+ ToolMap,
673
+ unknown,
674
+ string,
675
+ { mode: "continue"; sandboxId: string },
676
+ "pause-until-parent-close"
677
+ > = {
678
+ ...base,
679
+ sandbox: { mode: "continue", sandboxId: "x" },
680
+ sandboxShutdown: "pause-until-parent-close",
681
+ // @ts-expect-error continue + pause-until-parent-close requires "resume" + "pause"
682
+ sandboxOps: proxyDaytonaSandboxOps("scope"),
683
+ };
684
+
685
+ // --- mode: "continue" + shutdown: "destroy" needs no gated cap.
686
+ // Daytona accepted. -------------------------------------------------------
687
+ const _continueDestroyDaytona: SessionConfig<
688
+ ToolMap,
689
+ unknown,
690
+ string,
691
+ { mode: "continue"; sandboxId: string },
692
+ "destroy"
693
+ > = {
694
+ ...base,
695
+ sandbox: { mode: "continue", sandboxId: "x" },
696
+ sandboxShutdown: "destroy",
697
+ sandboxOps: proxyDaytonaSandboxOps("scope"),
698
+ };
699
+
700
+ // --- Annotated alias `SessionConfig<ToolMap>` (no literal pin on
701
+ // `TInit` / `TShutdown`) is intentionally a broad shape: callers
702
+ // may fill `sandbox` / `sandboxShutdown` with any variant later, so
703
+ // `SessionRequiredCaps` resolves to the wide cap union and a narrow
704
+ // adapter cannot satisfy it. To get narrow caps from an annotated
705
+ // alias, pin the generics (see `_aliasPinnedDaytona` below). The
706
+ // runtime-default-narrow path is reached at the `createSession()`
707
+ // call site (where `TInit` infers from the literal argument).
708
+ const _defaultE2b: SessionConfig<ToolMap, unknown, string> = {
709
+ ...base,
710
+ sandboxOps: proxyE2bSandboxOps("scope"),
711
+ };
712
+ const _defaultDaytona: SessionConfig<ToolMap, unknown, string> = {
713
+ ...base,
714
+ // @ts-expect-error broad alias requires wide caps; daytona is narrow. Pin generics to get narrow caps.
715
+ sandboxOps: proxyDaytonaSandboxOps("scope"),
716
+ };
717
+ // --- Annotated alias with pinned generics is the documented path
718
+ // for narrow caps when type-annotating a reusable config. With
719
+ // `{ mode: "new" }` + `"destroy"` pinned, no gated cap is required
720
+ // and Daytona is accepted. ------------------------------------------------
721
+ const _aliasPinnedDaytona: SessionConfig<
722
+ ToolMap,
723
+ unknown,
724
+ string,
725
+ { mode: "new" },
726
+ "destroy"
727
+ > = {
728
+ ...base,
729
+ sandbox: { mode: "new" },
730
+ sandboxShutdown: "destroy",
731
+ sandboxOps: proxyDaytonaSandboxOps("scope"),
732
+ };
733
+
734
+ // --- Annotated alias with `sandbox: { mode: "new" }` literal must
735
+ // compile when `TInit` defaults to the wide union. Regression test
736
+ // for the Codex P1 finding: the unpinned alias must accept any
737
+ // valid `SandboxInit` variant for `sandbox` without forcing the
738
+ // caller to spell out the 4th and 5th generic arguments. ----------------
739
+ const _aliasWithLiteralSandbox: SessionConfig<ToolMap, unknown, string> = {
740
+ ...base,
741
+ sandbox: { mode: "new" },
742
+ };
743
+
744
+ // --- Specifying `sandbox: { mode: "fork" }` only (no shutdown) tightens
745
+ // to "fork" and rejects narrow adapters precisely. ----------------------
746
+ const _forkOnlyDaytona: SessionConfig<
747
+ ToolMap,
748
+ unknown,
749
+ string,
750
+ { mode: "fork"; sandboxId: string }
751
+ > = {
752
+ ...base,
753
+ sandbox: { mode: "fork", sandboxId: "x" },
754
+ // @ts-expect-error mode: "fork" requires "fork" cap; daytona is missing forkSandbox
755
+ sandboxOps: proxyDaytonaSandboxOps("scope"),
756
+ };
757
+
758
+ // --- Specifying only `sandboxShutdown: "pause"` (no sandbox) tightens
759
+ // to "pause" and rejects narrow adapters. -------------------------------
760
+ const _pauseOnlyDaytona: SessionConfig<
761
+ ToolMap,
762
+ unknown,
763
+ string,
764
+ undefined,
765
+ "pause"
766
+ > = {
767
+ ...base,
768
+ sandboxShutdown: "pause",
769
+ // @ts-expect-error shutdown: "pause" requires "pause" cap; daytona is missing pauseSandbox
770
+ sandboxOps: proxyDaytonaSandboxOps("scope"),
771
+ };
772
+
773
+ void [
774
+ _newDestroyDaytona,
775
+ _forkDaytona,
776
+ _forkE2b,
777
+ _fromSnapshotDaytona,
778
+ _snapshotShutdownDaytona,
779
+ _pauseShutdownDaytona,
780
+ _continueResumeDaytona,
781
+ _continueDestroyDaytona,
782
+ _defaultDaytona,
783
+ _defaultE2b,
784
+ _forkOnlyDaytona,
785
+ _pauseOnlyDaytona,
786
+ ];
787
+ }
788
+ void _sessionMatrix;
789
+
790
+ // Sanity check that `SessionRequiredCaps` resolves to the expected
791
+ // literal cap unions for each (init, shutdown) combination — locks the
792
+ // type-level mapping independently of the call-site matrix above.
793
+
794
+ type _Eq<A, B> =
795
+ (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2
796
+ ? true
797
+ : false;
798
+
799
+ const _capsCheck: {
800
+ newDestroy: _Eq<SessionRequiredCaps<{ mode: "new" }, "destroy">, never>;
801
+ fork: _Eq<
802
+ SessionRequiredCaps<{ mode: "fork"; sandboxId: string }, "destroy">,
803
+ "fork"
804
+ >;
805
+ fromSnapshot: _Eq<
806
+ SessionRequiredCaps<
807
+ { mode: "from-snapshot"; snapshot: SandboxSnapshot },
808
+ "destroy"
809
+ >,
810
+ "restore"
811
+ >;
812
+ snapshotShutdown: _Eq<
813
+ SessionRequiredCaps<{ mode: "new" }, "snapshot">,
814
+ "snapshot"
815
+ >;
816
+ pauseShutdown: _Eq<SessionRequiredCaps<{ mode: "new" }, "pause">, "pause">;
817
+ continueResume: _Eq<
818
+ SessionRequiredCaps<
819
+ { mode: "continue"; sandboxId: string },
820
+ "pause-until-parent-close"
821
+ >,
822
+ "resume" | "pause"
823
+ >;
824
+ // No literals pinned → defaults to the runtime's `{mode:"new"} +
825
+ // "destroy"`, which invokes no gated methods (Bug 2: previously
826
+ // resolved to the wide cap union, over-rejecting narrow adapters
827
+ // even on the runtime-safe default path).
828
+ defaultOmittedSafe: _Eq<SessionRequiredCaps, never>;
829
+ } = {
830
+ newDestroy: true,
831
+ fork: true,
832
+ fromSnapshot: true,
833
+ snapshotShutdown: true,
834
+ pauseShutdown: true,
835
+ continueResume: true,
836
+ defaultOmittedSafe: true,
837
+ };
838
+ void _capsCheck;
839
+
840
+ describe("SandboxManager runtime cap consistency check", () => {
841
+ it("rejects a provider that lists a capability it does not implement", () => {
842
+ expect(() => new SandboxManager(new _DriftedProvider())).toThrow(
843
+ /lists "pause" in supportedCapabilities but does not implement pause/
844
+ );
845
+ });
846
+
847
+ it("rejects a provider that implements a method without listing the cap", () => {
848
+ expect(
849
+ () =>
850
+ new SandboxManager(
851
+ new _ImplWithoutDeclProvider() as unknown as ConstructorParameters<
852
+ typeof SandboxManager
853
+ >[0]
854
+ )
855
+ ).toThrow(
856
+ /implements pause\(\) but does not list "pause" in supportedCapabilities/
857
+ );
858
+ });
859
+ });