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.
- package/README.md +12 -1
- package/dist/{activities-CvUrG3YG.d.cts → activities-Coafq5zr.d.cts} +2 -2
- package/dist/{activities-CULxRzJ1.d.ts → activities-CrN-ghLo.d.ts} +2 -2
- package/dist/adapters/sandbox/daytona/index.cjs +4 -23
- package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/index.d.cts +18 -86
- package/dist/adapters/sandbox/daytona/index.d.ts +18 -86
- package/dist/adapters/sandbox/daytona/index.js +4 -23
- package/dist/adapters/sandbox/daytona/index.js.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.cjs +1 -7
- package/dist/adapters/sandbox/daytona/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.d.cts +9 -2
- package/dist/adapters/sandbox/daytona/workflow.d.ts +9 -2
- package/dist/adapters/sandbox/daytona/workflow.js +1 -7
- package/dist/adapters/sandbox/daytona/workflow.js.map +1 -1
- package/dist/adapters/sandbox/e2b/index.cjs +21 -3
- package/dist/adapters/sandbox/e2b/index.cjs.map +1 -1
- package/dist/adapters/sandbox/e2b/index.d.cts +48 -7
- package/dist/adapters/sandbox/e2b/index.d.ts +48 -7
- package/dist/adapters/sandbox/e2b/index.js +22 -5
- package/dist/adapters/sandbox/e2b/index.js.map +1 -1
- package/dist/adapters/sandbox/e2b/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/e2b/workflow.d.cts +4 -2
- package/dist/adapters/sandbox/e2b/workflow.d.ts +4 -2
- package/dist/adapters/sandbox/e2b/workflow.js.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.cjs +11 -0
- package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.d.cts +11 -3
- package/dist/adapters/sandbox/inmemory/index.d.ts +11 -3
- package/dist/adapters/sandbox/inmemory/index.js +11 -1
- package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.d.cts +4 -2
- package/dist/adapters/sandbox/inmemory/workflow.d.ts +4 -2
- package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -1
- package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
- package/dist/adapters/thread/anthropic/index.d.cts +6 -6
- package/dist/adapters/thread/anthropic/index.d.ts +6 -6
- package/dist/adapters/thread/anthropic/index.js.map +1 -1
- package/dist/adapters/thread/anthropic/workflow.d.cts +6 -6
- package/dist/adapters/thread/anthropic/workflow.d.ts +6 -6
- package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/index.d.cts +6 -6
- package/dist/adapters/thread/google-genai/index.d.ts +6 -6
- package/dist/adapters/thread/google-genai/index.js.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.d.cts +6 -6
- package/dist/adapters/thread/google-genai/workflow.d.ts +6 -6
- package/dist/adapters/thread/langchain/index.cjs.map +1 -1
- package/dist/adapters/thread/langchain/index.d.cts +6 -6
- package/dist/adapters/thread/langchain/index.d.ts +6 -6
- package/dist/adapters/thread/langchain/index.js.map +1 -1
- package/dist/adapters/thread/langchain/workflow.d.cts +6 -6
- package/dist/adapters/thread/langchain/workflow.d.ts +6 -6
- package/dist/index.cjs +316 -119
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +93 -17
- package/dist/index.d.ts +93 -17
- package/dist/index.js +317 -120
- package/dist/index.js.map +1 -1
- package/dist/{proxy-5EbwzaY4.d.cts → proxy-Bf7uI-Hw.d.cts} +1 -1
- package/dist/{proxy-wZufFfBh.d.ts → proxy-COqA95FW.d.ts} +1 -1
- package/dist/{thread-manager-BqBAIsED.d.ts → thread-manager-BhkOyQ1I.d.ts} +2 -2
- package/dist/{thread-manager-BNiIt5r8.d.ts → thread-manager-Bi1XlbpJ.d.ts} +2 -2
- package/dist/{thread-manager-DF8WuCRs.d.cts → thread-manager-BsLO3Fgc.d.cts} +2 -2
- package/dist/{thread-manager-BoN5DOvG.d.cts → thread-manager-wRVVBFgj.d.cts} +2 -2
- package/dist/{types-C7OoY7h8.d.ts → types-BkX4HLzi.d.ts} +1 -1
- package/dist/{types-CuISs0Ub.d.cts → types-C66-BVBr.d.cts} +1 -1
- package/dist/types-CJ7tCdl6.d.cts +266 -0
- package/dist/types-CJ7tCdl6.d.ts +266 -0
- package/dist/{types-DeQH84C_.d.ts → types-CdALEF3z.d.cts} +342 -23
- package/dist/{types-Cn2r3ol3.d.cts → types-ChAy_jSP.d.ts} +342 -23
- package/dist/types-CjY93AWZ.d.cts +84 -0
- package/dist/types-gVa5XCWD.d.ts +84 -0
- package/dist/{workflow-DhplIN65.d.cts → workflow-BwT5EybR.d.ts} +7 -6
- package/dist/{workflow-C2MZZj5K.d.ts → workflow-DMmiaw6w.d.cts} +7 -6
- package/dist/workflow.cjs +138 -77
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +4 -4
- package/dist/workflow.d.ts +4 -4
- package/dist/workflow.js +139 -78
- package/dist/workflow.js.map +1 -1
- package/package.json +17 -33
- package/src/adapters/sandbox/daytona/index.ts +25 -48
- package/src/adapters/sandbox/daytona/proxy.ts +7 -8
- package/src/adapters/sandbox/e2b/README.md +81 -0
- package/src/adapters/sandbox/e2b/index.ts +53 -11
- package/src/adapters/sandbox/e2b/keep-alive.test.ts +115 -0
- package/src/adapters/sandbox/e2b/proxy.ts +3 -2
- package/src/adapters/sandbox/e2b/types.ts +34 -2
- package/src/adapters/sandbox/inmemory/index.ts +21 -1
- package/src/adapters/sandbox/inmemory/proxy.ts +7 -3
- package/src/index.ts +1 -1
- package/src/lib/activity.ts +5 -0
- package/src/lib/sandbox/capability-types.test.ts +859 -0
- package/src/lib/sandbox/index.ts +1 -0
- package/src/lib/sandbox/manager.ts +187 -31
- package/src/lib/sandbox/types.ts +189 -46
- package/src/lib/session/index.ts +1 -0
- package/src/lib/session/session.integration.test.ts +58 -0
- package/src/lib/session/session.ts +109 -50
- package/src/lib/session/types.ts +189 -8
- package/src/lib/subagent/handler.ts +66 -43
- package/src/lib/subagent/subagent.integration.test.ts +2 -0
- package/src/lib/subagent/types.ts +492 -16
- package/src/lib/subagent/workflow.ts +11 -1
- package/src/lib/tool-router/auto-append-sandbox.integration.test.ts +158 -0
- package/src/lib/tool-router/index.ts +1 -1
- package/src/lib/tool-router/with-sandbox.ts +45 -2
- package/src/lib/virtual-fs/filesystem.ts +41 -16
- package/src/lib/virtual-fs/types.ts +19 -0
- package/src/lib/virtual-fs/virtual-fs.test.ts +204 -1
- package/src/tools/read-file/handler.test.ts +83 -0
- package/src/workflow.ts +3 -0
- package/tsup.config.ts +0 -4
- package/dist/adapters/sandbox/bedrock/index.cjs +0 -457
- package/dist/adapters/sandbox/bedrock/index.cjs.map +0 -1
- package/dist/adapters/sandbox/bedrock/index.d.cts +0 -25
- package/dist/adapters/sandbox/bedrock/index.d.ts +0 -25
- package/dist/adapters/sandbox/bedrock/index.js +0 -454
- package/dist/adapters/sandbox/bedrock/index.js.map +0 -1
- package/dist/adapters/sandbox/bedrock/workflow.cjs +0 -36
- package/dist/adapters/sandbox/bedrock/workflow.cjs.map +0 -1
- package/dist/adapters/sandbox/bedrock/workflow.d.cts +0 -29
- package/dist/adapters/sandbox/bedrock/workflow.d.ts +0 -29
- package/dist/adapters/sandbox/bedrock/workflow.js +0 -34
- package/dist/adapters/sandbox/bedrock/workflow.js.map +0 -1
- package/dist/types-DAsQ21Rt.d.ts +0 -74
- package/dist/types-lm8tMNJQ.d.cts +0 -74
- package/dist/types-yx0LzPGn.d.cts +0 -173
- package/dist/types-yx0LzPGn.d.ts +0 -173
- package/src/adapters/sandbox/bedrock/filesystem.ts +0 -340
- package/src/adapters/sandbox/bedrock/index.ts +0 -274
- package/src/adapters/sandbox/bedrock/proxy.ts +0 -59
- 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
|
+
});
|