zeitlich 0.2.22 → 0.2.23

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 (101) hide show
  1. package/README.md +242 -59
  2. package/dist/adapters/sandbox/daytona/index.cjs +4 -1
  3. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
  4. package/dist/adapters/sandbox/daytona/index.d.cts +2 -1
  5. package/dist/adapters/sandbox/daytona/index.d.ts +2 -1
  6. package/dist/adapters/sandbox/daytona/index.js +4 -1
  7. package/dist/adapters/sandbox/daytona/index.js.map +1 -1
  8. package/dist/adapters/sandbox/daytona/workflow.cjs +1 -0
  9. package/dist/adapters/sandbox/daytona/workflow.cjs.map +1 -1
  10. package/dist/adapters/sandbox/daytona/workflow.d.cts +1 -1
  11. package/dist/adapters/sandbox/daytona/workflow.d.ts +1 -1
  12. package/dist/adapters/sandbox/daytona/workflow.js +1 -0
  13. package/dist/adapters/sandbox/daytona/workflow.js.map +1 -1
  14. package/dist/adapters/sandbox/inmemory/index.cjs +16 -2
  15. package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
  16. package/dist/adapters/sandbox/inmemory/index.d.cts +3 -2
  17. package/dist/adapters/sandbox/inmemory/index.d.ts +3 -2
  18. package/dist/adapters/sandbox/inmemory/index.js +16 -2
  19. package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
  20. package/dist/adapters/sandbox/inmemory/workflow.cjs +1 -0
  21. package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -1
  22. package/dist/adapters/sandbox/inmemory/workflow.d.cts +1 -1
  23. package/dist/adapters/sandbox/inmemory/workflow.d.ts +1 -1
  24. package/dist/adapters/sandbox/inmemory/workflow.js +1 -0
  25. package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -1
  26. package/dist/adapters/sandbox/virtual/index.cjs +33 -9
  27. package/dist/adapters/sandbox/virtual/index.cjs.map +1 -1
  28. package/dist/adapters/sandbox/virtual/index.d.cts +6 -5
  29. package/dist/adapters/sandbox/virtual/index.d.ts +6 -5
  30. package/dist/adapters/sandbox/virtual/index.js +33 -9
  31. package/dist/adapters/sandbox/virtual/index.js.map +1 -1
  32. package/dist/adapters/sandbox/virtual/workflow.cjs +1 -0
  33. package/dist/adapters/sandbox/virtual/workflow.cjs.map +1 -1
  34. package/dist/adapters/sandbox/virtual/workflow.d.cts +3 -3
  35. package/dist/adapters/sandbox/virtual/workflow.d.ts +3 -3
  36. package/dist/adapters/sandbox/virtual/workflow.js +1 -0
  37. package/dist/adapters/sandbox/virtual/workflow.js.map +1 -1
  38. package/dist/adapters/thread/google-genai/index.d.cts +3 -3
  39. package/dist/adapters/thread/google-genai/index.d.ts +3 -3
  40. package/dist/adapters/thread/google-genai/workflow.d.cts +3 -3
  41. package/dist/adapters/thread/google-genai/workflow.d.ts +3 -3
  42. package/dist/adapters/thread/langchain/index.d.cts +3 -3
  43. package/dist/adapters/thread/langchain/index.d.ts +3 -3
  44. package/dist/adapters/thread/langchain/workflow.d.cts +3 -3
  45. package/dist/adapters/thread/langchain/workflow.d.ts +3 -3
  46. package/dist/index.cjs +247 -57
  47. package/dist/index.cjs.map +1 -1
  48. package/dist/index.d.cts +9 -8
  49. package/dist/index.d.ts +9 -8
  50. package/dist/index.js +245 -55
  51. package/dist/index.js.map +1 -1
  52. package/dist/{queries-Bw6WEPMw.d.cts → queries-DModcWRy.d.cts} +1 -1
  53. package/dist/{queries-C27raDaB.d.ts → queries-byD0jr1Y.d.ts} +1 -1
  54. package/dist/{types-ClsHhtwL.d.cts → types-B50pBPEV.d.ts} +159 -35
  55. package/dist/{types-YbL7JpEA.d.cts → types-Bll19FZJ.d.cts} +7 -0
  56. package/dist/{types-YbL7JpEA.d.ts → types-Bll19FZJ.d.ts} +7 -0
  57. package/dist/{types-BJ8itUAl.d.cts → types-BuXdFhaZ.d.cts} +6 -6
  58. package/dist/{types-HBosetv3.d.cts → types-ChAMwU3q.d.cts} +2 -0
  59. package/dist/{types-HBosetv3.d.ts → types-ChAMwU3q.d.ts} +2 -0
  60. package/dist/{types-C5bkx6kQ.d.ts → types-DQW8l7pY.d.cts} +159 -35
  61. package/dist/{types-ENYCKFBk.d.ts → types-GZ76HZSj.d.ts} +6 -6
  62. package/dist/workflow.cjs +241 -57
  63. package/dist/workflow.cjs.map +1 -1
  64. package/dist/workflow.d.cts +49 -32
  65. package/dist/workflow.d.ts +49 -32
  66. package/dist/workflow.js +239 -55
  67. package/dist/workflow.js.map +1 -1
  68. package/package.json +2 -2
  69. package/src/adapters/sandbox/daytona/filesystem.ts +1 -1
  70. package/src/adapters/sandbox/daytona/index.ts +4 -0
  71. package/src/adapters/sandbox/daytona/proxy.ts +4 -3
  72. package/src/adapters/sandbox/e2b/index.ts +5 -0
  73. package/src/adapters/sandbox/inmemory/index.ts +24 -4
  74. package/src/adapters/sandbox/inmemory/proxy.ts +2 -2
  75. package/src/adapters/sandbox/virtual/filesystem.ts +41 -17
  76. package/src/adapters/sandbox/virtual/provider.ts +4 -0
  77. package/src/adapters/sandbox/virtual/proxy.ts +1 -0
  78. package/src/adapters/sandbox/virtual/types.ts +9 -4
  79. package/src/lib/lifecycle.ts +57 -0
  80. package/src/lib/sandbox/manager.ts +13 -1
  81. package/src/lib/sandbox/types.ts +13 -4
  82. package/src/lib/session/index.ts +1 -0
  83. package/src/lib/session/session-edge-cases.integration.test.ts +447 -33
  84. package/src/lib/session/session.integration.test.ts +52 -32
  85. package/src/lib/session/session.ts +107 -33
  86. package/src/lib/session/types.ts +55 -16
  87. package/src/lib/subagent/define.ts +5 -4
  88. package/src/lib/subagent/handler.ts +139 -14
  89. package/src/lib/subagent/index.ts +3 -0
  90. package/src/lib/subagent/register.ts +10 -3
  91. package/src/lib/subagent/signals.ts +8 -0
  92. package/src/lib/subagent/subagent.integration.test.ts +853 -150
  93. package/src/lib/subagent/tool.ts +2 -2
  94. package/src/lib/subagent/types.ts +77 -19
  95. package/src/lib/subagent/workflow.ts +83 -12
  96. package/src/lib/tool-router/router.integration.test.ts +137 -4
  97. package/src/lib/tool-router/router.ts +13 -3
  98. package/src/lib/tool-router/types.ts +7 -0
  99. package/src/lib/workflow.test.ts +89 -21
  100. package/src/lib/workflow.ts +33 -18
  101. package/src/workflow.ts +6 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zeitlich",
3
- "version": "0.2.22",
3
+ "version": "0.2.23",
4
4
  "description": "[EXPERIMENTAL] An opinionated AI agent implementation for Temporal",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -189,7 +189,7 @@
189
189
  "vitest": "^4.0.18"
190
190
  },
191
191
  "peerDependencies": {
192
- "@daytonaio/sdk": ">=0.100.0",
192
+ "@daytonaio/sdk": ">=0.153.0",
193
193
  "@e2b/code-interpreter": "^2.3.3",
194
194
  "@google/genai": "^1.43.0",
195
195
  "@langchain/core": ">=1.0.0",
@@ -55,7 +55,7 @@ export class DaytonaSandboxFileSystem implements SandboxFileSystem {
55
55
  await this.sandbox.fs.uploadFiles(
56
56
  files.map((f) => ({
57
57
  source: Buffer.from(f.content),
58
- destination: f.path,
58
+ destination: this.normalisePath(f.path),
59
59
  }))
60
60
  );
61
61
  }
@@ -141,6 +141,10 @@ export class DaytonaSandboxProvider implements SandboxProvider<
141
141
  }
142
142
  }
143
143
 
144
+ async pause(_sandboxId: string, _ttlSeconds?: number): Promise<void> {
145
+ throw new SandboxNotSupportedError("pause");
146
+ }
147
+
144
148
  async fork(_sandboxId: string): Promise<Sandbox> {
145
149
  throw new Error("Not implemented");
146
150
  }
@@ -19,6 +19,7 @@
19
19
  */
20
20
  import { proxyActivities, workflowInfo } from "@temporalio/workflow";
21
21
  import type { SandboxOps } from "../../../lib/sandbox/types";
22
+ import type { DaytonaSandboxCreateOptions } from "./types";
22
23
 
23
24
  const ADAPTER_PREFIX = "daytona";
24
25
 
@@ -41,15 +42,15 @@ export function proxyDaytonaSandboxOps(
41
42
  }
42
43
  );
43
44
 
44
- const prefix =
45
- `${ADAPTER_PREFIX}${resolvedScope.charAt(0).toUpperCase()}${resolvedScope.slice(1)}`;
45
+ const prefix = `${ADAPTER_PREFIX}${resolvedScope.charAt(0).toUpperCase()}${resolvedScope.slice(1)}`;
46
46
  const p = (key: string): string =>
47
47
  `${prefix}${key.charAt(0).toUpperCase()}${key.slice(1)}`;
48
48
 
49
49
  return {
50
50
  createSandbox: acts[p("createSandbox")],
51
51
  destroySandbox: acts[p("destroySandbox")],
52
+ pauseSandbox: acts[p("pauseSandbox")],
52
53
  snapshotSandbox: acts[p("snapshotSandbox")],
53
54
  forkSandbox: acts[p("forkSandbox")],
54
- } as SandboxOps;
55
+ } as SandboxOps<DaytonaSandboxCreateOptions>;
55
56
  }
@@ -131,6 +131,11 @@ export class E2bSandboxProvider
131
131
  }
132
132
  }
133
133
 
134
+ async pause(sandboxId: string, _ttlSeconds?: number): Promise<void> {
135
+ const sdkSandbox = await E2bSdkSandbox.connect(sandboxId);
136
+ await sdkSandbox.pause();
137
+ }
138
+
134
139
  async snapshot(_sandboxId: string): Promise<SandboxSnapshot> {
135
140
  throw new SandboxNotSupportedError("snapshot");
136
141
  }
@@ -27,7 +27,8 @@ import { getShortId } from "../../../lib/thread/id";
27
27
 
28
28
  function toSandboxFs(fs: IFileSystem): SandboxFileSystem {
29
29
  const workspaceBase = "/";
30
- const normalisePath = (path: string): string => fs.resolvePath(workspaceBase, path);
30
+ const normalisePath = (path: string): string =>
31
+ fs.resolvePath(workspaceBase, path);
31
32
 
32
33
  return {
33
34
  workspaceBase,
@@ -68,7 +69,8 @@ function toSandboxFs(fs: IFileSystem): SandboxFileSystem {
68
69
  return fs.readdirWithFileTypes(dirPath);
69
70
  },
70
71
  rm: (path, opts) => fs.rm(normalisePath(path), opts),
71
- cp: (src, dest, opts) => fs.cp(normalisePath(src), normalisePath(dest), opts),
72
+ cp: (src, dest, opts) =>
73
+ fs.cp(normalisePath(src), normalisePath(dest), opts),
72
74
  mv: (src, dest) => fs.mv(normalisePath(src), normalisePath(dest)),
73
75
  readlink: (path) => fs.readlink(normalisePath(path)),
74
76
  resolvePath: (base, p) => fs.resolvePath(normalisePath(base), p),
@@ -157,6 +159,10 @@ export class InMemorySandboxProvider implements SandboxProvider {
157
159
  }
158
160
  }
159
161
 
162
+ async pause(_sandboxId: string, _ttlSeconds?: number): Promise<void> {
163
+ // In-memory: nothing to pause
164
+ }
165
+
160
166
  async create(options?: SandboxCreateOptions): Promise<SandboxCreateResult> {
161
167
  const id = options?.id ?? getShortId();
162
168
  const initialFiles: InitialFiles = {};
@@ -200,8 +206,22 @@ export class InMemorySandboxProvider implements SandboxProvider {
200
206
  };
201
207
  }
202
208
 
203
- async fork(_sandboxId: string): Promise<Sandbox> {
204
- throw new Error("Not implemented");
209
+ async fork(sandboxId: string): Promise<Sandbox> {
210
+ const sandbox = await this.get(sandboxId);
211
+
212
+ const entries = await sandbox.fs.readdirWithFileTypes("/");
213
+ const initialFiles: Record<string, Uint8Array> = {};
214
+ for (const entry of entries) {
215
+ if (entry.isFile) {
216
+ initialFiles[entry.name] = await sandbox.fs.readFileBuffer(entry.name);
217
+ }
218
+ }
219
+
220
+ const newSandbox = await this.create({
221
+ id: getShortId(),
222
+ initialFiles,
223
+ });
224
+ return newSandbox.sandbox;
205
225
  }
206
226
 
207
227
  async restore(snapshot: SandboxSnapshot): Promise<Sandbox> {
@@ -39,14 +39,14 @@ export function proxyInMemorySandboxOps(
39
39
  }
40
40
  );
41
41
 
42
- const prefix =
43
- `${ADAPTER_PREFIX}${resolvedScope.charAt(0).toUpperCase()}${resolvedScope.slice(1)}`;
42
+ const prefix = `${ADAPTER_PREFIX}${resolvedScope.charAt(0).toUpperCase()}${resolvedScope.slice(1)}`;
44
43
  const p = (key: string): string =>
45
44
  `${prefix}${key.charAt(0).toUpperCase()}${key.slice(1)}`;
46
45
 
47
46
  return {
48
47
  createSandbox: acts[p("createSandbox")],
49
48
  destroySandbox: acts[p("destroySandbox")],
49
+ pauseSandbox: acts[p("pauseSandbox")],
50
50
  snapshotSandbox: acts[p("snapshotSandbox")],
51
51
  forkSandbox: acts[p("forkSandbox")],
52
52
  } as SandboxOps;
@@ -29,7 +29,7 @@ function parentDir(p: string): string {
29
29
  */
30
30
  function inferDirectories(
31
31
  entries: { path: string }[],
32
- workspaceBase: string,
32
+ workspaceBase: string
33
33
  ): Set<string> {
34
34
  const dirs = new Set<string>();
35
35
  dirs.add("/");
@@ -54,8 +54,7 @@ function inferDirectories(
54
54
  export class VirtualSandboxFileSystem<
55
55
  TCtx = unknown,
56
56
  TMeta = FileEntryMetadata,
57
- > implements SandboxFileSystem
58
- {
57
+ > implements SandboxFileSystem {
59
58
  readonly workspaceBase: string;
60
59
  private entries: Map<string, FileEntry<TMeta>>;
61
60
  private directories: Set<string>;
@@ -65,11 +64,11 @@ export class VirtualSandboxFileSystem<
65
64
  tree: FileEntry<TMeta>[],
66
65
  private resolver: FileResolver<TCtx, TMeta>,
67
66
  private ctx: TCtx,
68
- workspaceBase = "/",
67
+ workspaceBase = "/"
69
68
  ) {
70
69
  this.workspaceBase = normalisePath(workspaceBase);
71
70
  this.entries = new Map(
72
- tree.map((e) => [normalisePath(e.path, this.workspaceBase), e]),
71
+ tree.map((e) => [normalisePath(e.path, this.workspaceBase), e])
73
72
  );
74
73
  this.directories = inferDirectories(tree, this.workspaceBase);
75
74
  }
@@ -91,13 +90,13 @@ export class VirtualSandboxFileSystem<
91
90
  async readFile(path: string): Promise<string> {
92
91
  const entry = this.entries.get(normalisePath(path, this.workspaceBase));
93
92
  if (!entry) throw new Error(`ENOENT: no such file: ${path}`);
94
- return this.resolver.readFile(entry.id, this.ctx);
93
+ return this.resolver.readFile(entry.id, this.ctx, entry.metadata);
95
94
  }
96
95
 
97
96
  async readFileBuffer(path: string): Promise<Uint8Array> {
98
97
  const entry = this.entries.get(normalisePath(path, this.workspaceBase));
99
98
  if (!entry) throw new Error(`ENOENT: no such file: ${path}`);
100
- return this.resolver.readFileBuffer(entry.id, this.ctx);
99
+ return this.resolver.readFileBuffer(entry.id, this.ctx, entry.metadata);
101
100
  }
102
101
 
103
102
  // --------------------------------------------------------------------------
@@ -180,7 +179,12 @@ export class VirtualSandboxFileSystem<
180
179
  const existing = this.entries.get(norm);
181
180
 
182
181
  if (existing) {
183
- await this.resolver.writeFile(existing.id, content, this.ctx);
182
+ await this.resolver.writeFile(
183
+ existing.id,
184
+ content,
185
+ this.ctx,
186
+ existing.metadata
187
+ );
184
188
  const size =
185
189
  typeof content === "string"
186
190
  ? new TextEncoder().encode(content).byteLength
@@ -209,12 +213,21 @@ export class VirtualSandboxFileSystem<
209
213
  return this.writeFile(path, content);
210
214
  }
211
215
 
212
- const current = await this.resolver.readFile(existing.id, this.ctx);
216
+ const current = await this.resolver.readFile(
217
+ existing.id,
218
+ this.ctx,
219
+ existing.metadata
220
+ );
213
221
  const appended =
214
222
  typeof content === "string"
215
223
  ? current + content
216
224
  : current + new TextDecoder().decode(content);
217
- await this.resolver.writeFile(existing.id, appended, this.ctx);
225
+ await this.resolver.writeFile(
226
+ existing.id,
227
+ appended,
228
+ this.ctx,
229
+ existing.metadata
230
+ );
218
231
 
219
232
  const size = new TextEncoder().encode(appended).byteLength;
220
233
  const updated: FileEntry<TMeta> = {
@@ -226,7 +239,10 @@ export class VirtualSandboxFileSystem<
226
239
  this.mutations.push({ type: "update", path: norm, entry: updated });
227
240
  }
228
241
 
229
- async mkdir(_path: string, _options?: { recursive?: boolean }): Promise<void> {
242
+ async mkdir(
243
+ _path: string,
244
+ _options?: { recursive?: boolean }
245
+ ): Promise<void> {
230
246
  const norm = normalisePath(_path, this.workspaceBase);
231
247
  if (this.directories.has(norm)) return;
232
248
 
@@ -244,13 +260,13 @@ export class VirtualSandboxFileSystem<
244
260
 
245
261
  async rm(
246
262
  path: string,
247
- options?: { recursive?: boolean; force?: boolean },
263
+ options?: { recursive?: boolean; force?: boolean }
248
264
  ): Promise<void> {
249
265
  const norm = normalisePath(path, this.workspaceBase);
250
266
  const entry = this.entries.get(norm);
251
267
 
252
268
  if (entry) {
253
- await this.resolver.deleteFile(entry.id, this.ctx);
269
+ await this.resolver.deleteFile(entry.id, this.ctx, entry.metadata);
254
270
  this.entries.delete(norm);
255
271
  this.mutations.push({ type: "remove", path: norm });
256
272
  return;
@@ -263,7 +279,7 @@ export class VirtualSandboxFileSystem<
263
279
  const prefix = norm === "/" ? "/" : norm + "/";
264
280
  for (const [p, e] of this.entries) {
265
281
  if (p.startsWith(prefix)) {
266
- await this.resolver.deleteFile(e.id, this.ctx);
282
+ await this.resolver.deleteFile(e.id, this.ctx, e.metadata);
267
283
  this.entries.delete(p);
268
284
  this.mutations.push({ type: "remove", path: p });
269
285
  }
@@ -283,14 +299,18 @@ export class VirtualSandboxFileSystem<
283
299
  async cp(
284
300
  src: string,
285
301
  dest: string,
286
- _options?: { recursive?: boolean },
302
+ _options?: { recursive?: boolean }
287
303
  ): Promise<void> {
288
304
  const normSrc = normalisePath(src, this.workspaceBase);
289
305
  const normDest = normalisePath(dest, this.workspaceBase);
290
306
 
291
307
  const entry = this.entries.get(normSrc);
292
308
  if (entry) {
293
- const content = await this.resolver.readFile(entry.id, this.ctx);
309
+ const content = await this.resolver.readFile(
310
+ entry.id,
311
+ this.ctx,
312
+ entry.metadata
313
+ );
294
314
  await this.writeFile(normDest, content);
295
315
  return;
296
316
  }
@@ -306,7 +326,11 @@ export class VirtualSandboxFileSystem<
306
326
  for (const [p, e] of this.entries) {
307
327
  if (p.startsWith(prefix)) {
308
328
  const relative = p.slice(normSrc.length);
309
- const content = await this.resolver.readFile(e.id, this.ctx);
329
+ const content = await this.resolver.readFile(
330
+ e.id,
331
+ this.ctx,
332
+ e.metadata
333
+ );
310
334
  await this.writeFile(normDest + relative, content);
311
335
  }
312
336
  }
@@ -90,6 +90,10 @@ export class VirtualSandboxProvider<
90
90
  // No-op — no internal state to clean up
91
91
  }
92
92
 
93
+ async pause(): Promise<void> {
94
+ // No-op — virtual sandbox state lives in workflow AgentState
95
+ }
96
+
93
97
  async fork(_sandboxId: string): Promise<never> {
94
98
  throw new Error("Not implemented");
95
99
  }
@@ -46,6 +46,7 @@ export function proxyVirtualSandboxOps(
46
46
  return {
47
47
  createSandbox: acts[p("createSandbox")],
48
48
  destroySandbox: acts[p("destroySandbox")],
49
+ pauseSandbox: acts[p("pauseSandbox")],
49
50
  snapshotSandbox: acts[p("snapshotSandbox")],
50
51
  forkSandbox: acts[p("forkSandbox")],
51
52
  } as SandboxOps<VirtualSandboxCreateOptions<unknown>>;
@@ -56,15 +56,20 @@ export type TreeMutation<TMeta = FileEntryMetadata> =
56
56
  */
57
57
  export interface FileResolver<TCtx = unknown, TMeta = FileEntryMetadata> {
58
58
  resolveEntries(ctx: TCtx): Promise<FileEntry<TMeta>[]>;
59
- readFile(id: string, ctx: TCtx): Promise<string>;
60
- readFileBuffer(id: string, ctx: TCtx): Promise<Uint8Array>;
61
- writeFile(id: string, content: string | Uint8Array, ctx: TCtx): Promise<void>;
59
+ readFile(id: string, ctx: TCtx, metadata: TMeta): Promise<string>;
60
+ readFileBuffer(id: string, ctx: TCtx, metadata: TMeta): Promise<Uint8Array>;
61
+ writeFile(
62
+ id: string,
63
+ content: string | Uint8Array,
64
+ ctx: TCtx,
65
+ metadata: TMeta
66
+ ): Promise<void>;
62
67
  createFile(
63
68
  path: string,
64
69
  content: string | Uint8Array,
65
70
  ctx: TCtx
66
71
  ): Promise<FileEntry<TMeta>>;
67
- deleteFile(id: string, ctx: TCtx): Promise<void>;
72
+ deleteFile(id: string, ctx: TCtx, metadata: TMeta): Promise<void>;
68
73
  }
69
74
 
70
75
  // ============================================================================
@@ -0,0 +1,57 @@
1
+ // ============================================================================
2
+ // Thread lifecycle
3
+ // ============================================================================
4
+
5
+ /**
6
+ * Thread initialization strategy.
7
+ *
8
+ * - `"new"` — start a fresh thread (optionally specify its ID).
9
+ * - `"continue"` — append directly to an existing thread in-place.
10
+ * - `"fork"` — copy all messages from an existing thread into a new one and
11
+ * continue there.
12
+ */
13
+ export type ThreadInit =
14
+ | { mode: "new"; threadId?: string }
15
+ | { mode: "continue"; threadId: string }
16
+ | { mode: "fork"; threadId: string };
17
+
18
+ // ============================================================================
19
+ // Sandbox lifecycle
20
+ // ============================================================================
21
+
22
+ /**
23
+ * Sandbox initialization strategy.
24
+ *
25
+ * - `"new"` — create a fresh sandbox.
26
+ * - `"continue"` — resume a previously-paused sandbox (this session takes
27
+ * ownership and the shutdown policy applies on exit).
28
+ * - `"fork"` — fork from an existing (or paused) sandbox; a new sandbox is
29
+ * created and owned by this session.
30
+ * - `"inherit"` — use a sandbox owned by someone else (e.g. a parent agent).
31
+ * The session will **not** manage its lifecycle on exit.
32
+ */
33
+ export type SandboxInit =
34
+ | { mode: "new" }
35
+ | { mode: "continue"; sandboxId: string }
36
+ | { mode: "fork"; sandboxId: string }
37
+ | { mode: "inherit"; sandboxId: string };
38
+
39
+ /**
40
+ * What to do with the sandbox when the session exits.
41
+ *
42
+ * - `"destroy"` — tear down the sandbox entirely.
43
+ * - `"pause"` — pause the sandbox so it can be resumed later.
44
+ * - `"keep"` — leave the sandbox running (no-op on exit).
45
+ */
46
+ export type SandboxShutdown = "destroy" | "pause" | "keep";
47
+
48
+ /**
49
+ * Extended shutdown options available to subagent workflows.
50
+ *
51
+ * Includes all base {@link SandboxShutdown} values plus:
52
+ * - `"pause-until-parent-close"` — pause the sandbox on exit, then wait for
53
+ * the parent workflow to signal when to destroy it.
54
+ */
55
+ export type SubagentSandboxShutdown =
56
+ | SandboxShutdown
57
+ | "pause-until-parent-close";
@@ -28,7 +28,9 @@ export class SandboxManager<
28
28
  TSandbox extends Sandbox = Sandbox,
29
29
  TId extends string = string,
30
30
  > {
31
- constructor(private provider: SandboxProvider<TOptions, TSandbox> & { readonly id: TId }) {}
31
+ constructor(
32
+ private provider: SandboxProvider<TOptions, TSandbox> & { readonly id: TId }
33
+ ) {}
32
34
 
33
35
  async create(
34
36
  options?: TOptions
@@ -45,6 +47,10 @@ export class SandboxManager<
45
47
  await this.provider.destroy(id);
46
48
  }
47
49
 
50
+ async pause(id: string, ttlSeconds?: number): Promise<void> {
51
+ await this.provider.pause(id, ttlSeconds);
52
+ }
53
+
48
54
  async snapshot(id: string): Promise<SandboxSnapshot> {
49
55
  return this.provider.snapshot(id);
50
56
  }
@@ -95,6 +101,12 @@ export class SandboxManager<
95
101
  destroySandbox: async (sandboxId: string): Promise<void> => {
96
102
  await this.destroy(sandboxId);
97
103
  },
104
+ pauseSandbox: async (
105
+ sandboxId: string,
106
+ ttlSeconds?: number
107
+ ): Promise<void> => {
108
+ await this.pause(sandboxId, ttlSeconds);
109
+ },
98
110
  snapshotSandbox: async (sandboxId: string): Promise<SandboxSnapshot> => {
99
111
  return this.snapshot(sandboxId);
100
112
  },
@@ -35,8 +35,15 @@ export interface SandboxFileSystem {
35
35
  mkdir(path: string, options?: { recursive?: boolean }): Promise<void>;
36
36
  readdir(path: string): Promise<string[]>;
37
37
  readdirWithFileTypes(path: string): Promise<DirentEntry[]>;
38
- rm(path: string, options?: { recursive?: boolean; force?: boolean }): Promise<void>;
39
- cp(src: string, dest: string, options?: { recursive?: boolean }): Promise<void>;
38
+ rm(
39
+ path: string,
40
+ options?: { recursive?: boolean; force?: boolean }
41
+ ): Promise<void>;
42
+ cp(
43
+ src: string,
44
+ dest: string,
45
+ options?: { recursive?: boolean }
46
+ ): Promise<void>;
40
47
  mv(src: string, dest: string): Promise<void>;
41
48
  readlink(path: string): Promise<string>;
42
49
  resolvePath(base: string, path: string): string;
@@ -125,6 +132,7 @@ export interface SandboxProvider<
125
132
  create(options?: TOptions): Promise<SandboxCreateResult>;
126
133
  get(sandboxId: string): Promise<TSandbox>;
127
134
  destroy(sandboxId: string): Promise<void>;
135
+ pause(sandboxId: string, ttlSeconds?: number): Promise<void>;
128
136
  snapshot(sandboxId: string): Promise<SandboxSnapshot>;
129
137
  restore(snapshot: SandboxSnapshot): Promise<Sandbox>;
130
138
  fork(sandboxId: string): Promise<Sandbox>;
@@ -138,9 +146,10 @@ export interface SandboxOps<
138
146
  TOptions extends SandboxCreateOptions = SandboxCreateOptions,
139
147
  > {
140
148
  createSandbox(
141
- options?: TOptions,
149
+ options?: TOptions
142
150
  ): Promise<{ sandboxId: string; stateUpdate?: Record<string, unknown> }>;
143
151
  destroySandbox(sandboxId: string): Promise<void>;
152
+ pauseSandbox(sandboxId: string): Promise<void>;
144
153
  snapshotSandbox(sandboxId: string): Promise<SandboxSnapshot>;
145
154
  forkSandbox(sandboxId: string): Promise<string>;
146
155
  }
@@ -172,7 +181,7 @@ export class SandboxNotSupportedError extends ApplicationFailure {
172
181
  super(
173
182
  `Sandbox does not support: ${operation}`,
174
183
  "SandboxNotSupportedError",
175
- true,
184
+ true
176
185
  );
177
186
  }
178
187
  }
@@ -5,5 +5,6 @@ export type {
5
5
  PrefixedThreadOps,
6
6
  ScopedPrefix,
7
7
  SessionConfig,
8
+ SessionResult,
8
9
  ZeitlichSession,
9
10
  } from "./types";