zeitlich 0.2.36 → 0.2.38

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 (204) hide show
  1. package/README.md +146 -92
  2. package/dist/{activities-BVI2lTwr.d.ts → activities-BKhMtKDd.d.ts} +4 -2
  3. package/dist/{activities-hd4aNnZE.d.cts → activities-CDcwkRZs.d.cts} +4 -2
  4. package/dist/adapters/sandbox/bedrock/index.cjs +17 -14
  5. package/dist/adapters/sandbox/bedrock/index.cjs.map +1 -1
  6. package/dist/adapters/sandbox/bedrock/index.d.cts +7 -6
  7. package/dist/adapters/sandbox/bedrock/index.d.ts +7 -6
  8. package/dist/adapters/sandbox/bedrock/index.js +17 -14
  9. package/dist/adapters/sandbox/bedrock/index.js.map +1 -1
  10. package/dist/adapters/sandbox/bedrock/workflow.cjs +2 -0
  11. package/dist/adapters/sandbox/bedrock/workflow.cjs.map +1 -1
  12. package/dist/adapters/sandbox/bedrock/workflow.d.cts +2 -2
  13. package/dist/adapters/sandbox/bedrock/workflow.d.ts +2 -2
  14. package/dist/adapters/sandbox/bedrock/workflow.js +2 -0
  15. package/dist/adapters/sandbox/bedrock/workflow.js.map +1 -1
  16. package/dist/adapters/sandbox/daytona/index.cjs +11 -3
  17. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
  18. package/dist/adapters/sandbox/daytona/index.d.cts +5 -4
  19. package/dist/adapters/sandbox/daytona/index.d.ts +5 -4
  20. package/dist/adapters/sandbox/daytona/index.js +11 -3
  21. package/dist/adapters/sandbox/daytona/index.js.map +1 -1
  22. package/dist/adapters/sandbox/daytona/workflow.cjs +2 -0
  23. package/dist/adapters/sandbox/daytona/workflow.cjs.map +1 -1
  24. package/dist/adapters/sandbox/daytona/workflow.d.cts +1 -1
  25. package/dist/adapters/sandbox/daytona/workflow.d.ts +1 -1
  26. package/dist/adapters/sandbox/daytona/workflow.js +2 -0
  27. package/dist/adapters/sandbox/daytona/workflow.js.map +1 -1
  28. package/dist/adapters/sandbox/e2b/index.cjs +73 -12
  29. package/dist/adapters/sandbox/e2b/index.cjs.map +1 -1
  30. package/dist/adapters/sandbox/e2b/index.d.cts +26 -4
  31. package/dist/adapters/sandbox/e2b/index.d.ts +26 -4
  32. package/dist/adapters/sandbox/e2b/index.js +73 -12
  33. package/dist/adapters/sandbox/e2b/index.js.map +1 -1
  34. package/dist/adapters/sandbox/e2b/workflow.cjs +2 -0
  35. package/dist/adapters/sandbox/e2b/workflow.cjs.map +1 -1
  36. package/dist/adapters/sandbox/e2b/workflow.d.cts +1 -1
  37. package/dist/adapters/sandbox/e2b/workflow.d.ts +1 -1
  38. package/dist/adapters/sandbox/e2b/workflow.js +2 -0
  39. package/dist/adapters/sandbox/e2b/workflow.js.map +1 -1
  40. package/dist/adapters/sandbox/inmemory/index.cjs +8 -3
  41. package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
  42. package/dist/adapters/sandbox/inmemory/index.d.cts +5 -4
  43. package/dist/adapters/sandbox/inmemory/index.d.ts +5 -4
  44. package/dist/adapters/sandbox/inmemory/index.js +8 -3
  45. package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
  46. package/dist/adapters/sandbox/inmemory/workflow.cjs +2 -0
  47. package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -1
  48. package/dist/adapters/sandbox/inmemory/workflow.d.cts +1 -1
  49. package/dist/adapters/sandbox/inmemory/workflow.d.ts +1 -1
  50. package/dist/adapters/sandbox/inmemory/workflow.js +2 -0
  51. package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -1
  52. package/dist/adapters/thread/anthropic/index.cjs +94 -39
  53. package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
  54. package/dist/adapters/thread/anthropic/index.d.cts +5 -5
  55. package/dist/adapters/thread/anthropic/index.d.ts +5 -5
  56. package/dist/adapters/thread/anthropic/index.js +94 -39
  57. package/dist/adapters/thread/anthropic/index.js.map +1 -1
  58. package/dist/adapters/thread/anthropic/workflow.cjs +7 -2
  59. package/dist/adapters/thread/anthropic/workflow.cjs.map +1 -1
  60. package/dist/adapters/thread/anthropic/workflow.d.cts +5 -5
  61. package/dist/adapters/thread/anthropic/workflow.d.ts +5 -5
  62. package/dist/adapters/thread/anthropic/workflow.js +7 -2
  63. package/dist/adapters/thread/anthropic/workflow.js.map +1 -1
  64. package/dist/adapters/thread/google-genai/index.cjs +77 -28
  65. package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
  66. package/dist/adapters/thread/google-genai/index.d.cts +5 -5
  67. package/dist/adapters/thread/google-genai/index.d.ts +5 -5
  68. package/dist/adapters/thread/google-genai/index.js +77 -28
  69. package/dist/adapters/thread/google-genai/index.js.map +1 -1
  70. package/dist/adapters/thread/google-genai/workflow.cjs +7 -2
  71. package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -1
  72. package/dist/adapters/thread/google-genai/workflow.d.cts +5 -5
  73. package/dist/adapters/thread/google-genai/workflow.d.ts +5 -5
  74. package/dist/adapters/thread/google-genai/workflow.js +7 -2
  75. package/dist/adapters/thread/google-genai/workflow.js.map +1 -1
  76. package/dist/adapters/thread/langchain/index.cjs +57 -10
  77. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  78. package/dist/adapters/thread/langchain/index.d.cts +5 -5
  79. package/dist/adapters/thread/langchain/index.d.ts +5 -5
  80. package/dist/adapters/thread/langchain/index.js +57 -10
  81. package/dist/adapters/thread/langchain/index.js.map +1 -1
  82. package/dist/adapters/thread/langchain/workflow.cjs +7 -2
  83. package/dist/adapters/thread/langchain/workflow.cjs.map +1 -1
  84. package/dist/adapters/thread/langchain/workflow.d.cts +5 -5
  85. package/dist/adapters/thread/langchain/workflow.d.ts +5 -5
  86. package/dist/adapters/thread/langchain/workflow.js +7 -2
  87. package/dist/adapters/thread/langchain/workflow.js.map +1 -1
  88. package/dist/index.cjs +322 -146
  89. package/dist/index.cjs.map +1 -1
  90. package/dist/index.d.cts +20 -14
  91. package/dist/index.d.ts +20 -14
  92. package/dist/index.js +323 -147
  93. package/dist/index.js.map +1 -1
  94. package/dist/{proxy-BjdFGPTm.d.ts → proxy-CUlKSvZS.d.ts} +1 -1
  95. package/dist/{proxy-7RnVaPdJ.d.cts → proxy-D_3x7RN4.d.cts} +1 -1
  96. package/dist/{thread-manager-CbpiGq1L.d.ts → thread-manager-CVu7o2cs.d.ts} +4 -2
  97. package/dist/{thread-manager-DzXm9eeI.d.cts → thread-manager-HSwyh28L.d.cts} +4 -2
  98. package/dist/{thread-manager-BBzNgQWH.d.cts → thread-manager-c1gPopAG.d.ts} +4 -2
  99. package/dist/{thread-manager-DjN5JYul.d.ts → thread-manager-wGi-LqIP.d.cts} +4 -2
  100. package/dist/{types-Mc_4BCfT.d.cts → types-BH_IRryz.d.ts} +10 -1
  101. package/dist/{types-yiXmqedU.d.ts → types-BaOw4hKI.d.cts} +10 -1
  102. package/dist/{types-DQ1l_gXL.d.cts → types-C06FwR96.d.cts} +121 -17
  103. package/dist/{types-wiGLvxWf.d.ts → types-DAsQ21Rt.d.ts} +1 -1
  104. package/dist/{types-CADc5V_P.d.ts → types-DNr31FzL.d.ts} +121 -17
  105. package/dist/{types-CBH54cwr.d.cts → types-lm8tMNJQ.d.cts} +1 -1
  106. package/dist/{types-DxCpFNv_.d.cts → types-yx0LzPGn.d.cts} +44 -5
  107. package/dist/{types-DxCpFNv_.d.ts → types-yx0LzPGn.d.ts} +44 -5
  108. package/dist/{workflow-DhtWRovz.d.cts → workflow-CSCkpwAL.d.ts} +2 -2
  109. package/dist/{workflow-P2pTSfKu.d.ts → workflow-DuvMZ8Vm.d.cts} +2 -2
  110. package/dist/workflow.cjs +274 -130
  111. package/dist/workflow.cjs.map +1 -1
  112. package/dist/workflow.d.cts +3 -3
  113. package/dist/workflow.d.ts +3 -3
  114. package/dist/workflow.js +275 -131
  115. package/dist/workflow.js.map +1 -1
  116. package/package.json +2 -2
  117. package/src/adapters/sandbox/bedrock/filesystem.ts +6 -12
  118. package/src/adapters/sandbox/bedrock/index.ts +22 -11
  119. package/src/adapters/sandbox/bedrock/proxy.ts +2 -0
  120. package/src/adapters/sandbox/daytona/index.ts +18 -3
  121. package/src/adapters/sandbox/daytona/proxy.ts +2 -0
  122. package/src/adapters/sandbox/e2b/filesystem.ts +5 -4
  123. package/src/adapters/sandbox/e2b/index.ts +87 -14
  124. package/src/adapters/sandbox/e2b/proxy.ts +2 -0
  125. package/src/adapters/sandbox/e2b/types.ts +16 -0
  126. package/src/adapters/sandbox/inmemory/index.ts +17 -3
  127. package/src/adapters/sandbox/inmemory/proxy.ts +2 -0
  128. package/src/adapters/thread/anthropic/activities.ts +58 -26
  129. package/src/adapters/thread/anthropic/model-invoker.ts +18 -7
  130. package/src/adapters/thread/anthropic/proxy.ts +6 -2
  131. package/src/adapters/thread/anthropic/thread-manager.test.ts +26 -7
  132. package/src/adapters/thread/anthropic/thread-manager.ts +63 -46
  133. package/src/adapters/thread/google-genai/activities.ts +20 -2
  134. package/src/adapters/thread/google-genai/model-invoker.ts +27 -7
  135. package/src/adapters/thread/google-genai/proxy.ts +6 -2
  136. package/src/adapters/thread/google-genai/thread-manager.test.ts +13 -3
  137. package/src/adapters/thread/google-genai/thread-manager.ts +57 -33
  138. package/src/adapters/thread/langchain/activities.ts +55 -24
  139. package/src/adapters/thread/langchain/hooks.test.ts +36 -49
  140. package/src/adapters/thread/langchain/hooks.ts +18 -5
  141. package/src/adapters/thread/langchain/model-invoker.ts +5 -4
  142. package/src/adapters/thread/langchain/proxy.ts +6 -2
  143. package/src/adapters/thread/langchain/thread-manager.test.ts +5 -1
  144. package/src/adapters/thread/langchain/thread-manager.ts +23 -9
  145. package/src/index.ts +4 -1
  146. package/src/lib/activity.ts +16 -6
  147. package/src/lib/hooks/types.ts +6 -6
  148. package/src/lib/lifecycle.ts +18 -3
  149. package/src/lib/model/proxy.ts +2 -2
  150. package/src/lib/model/types.ts +10 -0
  151. package/src/lib/observability/hooks.ts +4 -5
  152. package/src/lib/observability/index.ts +1 -4
  153. package/src/lib/sandbox/manager.ts +45 -20
  154. package/src/lib/sandbox/node-fs.ts +3 -6
  155. package/src/lib/sandbox/sandbox.test.ts +36 -3
  156. package/src/lib/sandbox/tree.integration.test.ts +10 -3
  157. package/src/lib/sandbox/types.ts +60 -6
  158. package/src/lib/session/session-edge-cases.integration.test.ts +316 -14
  159. package/src/lib/session/session.integration.test.ts +161 -1
  160. package/src/lib/session/session.ts +106 -21
  161. package/src/lib/session/types.ts +25 -5
  162. package/src/lib/skills/fs-provider.ts +12 -8
  163. package/src/lib/skills/handler.ts +1 -1
  164. package/src/lib/skills/parse.ts +3 -1
  165. package/src/lib/skills/register.ts +1 -3
  166. package/src/lib/skills/skills.integration.test.ts +25 -15
  167. package/src/lib/state/manager.integration.test.ts +12 -2
  168. package/src/lib/subagent/define.ts +1 -1
  169. package/src/lib/subagent/handler.ts +186 -71
  170. package/src/lib/subagent/index.ts +1 -5
  171. package/src/lib/subagent/register.ts +3 -2
  172. package/src/lib/subagent/signals.ts +1 -10
  173. package/src/lib/subagent/subagent.integration.test.ts +526 -248
  174. package/src/lib/subagent/tool.ts +4 -3
  175. package/src/lib/subagent/types.ts +50 -20
  176. package/src/lib/subagent/workflow.ts +9 -49
  177. package/src/lib/thread/id.test.ts +1 -1
  178. package/src/lib/thread/id.ts +1 -2
  179. package/src/lib/thread/manager.ts +18 -0
  180. package/src/lib/thread/proxy.ts +4 -4
  181. package/src/lib/thread/types.ts +20 -3
  182. package/src/lib/tool-router/index.ts +3 -5
  183. package/src/lib/tool-router/router-edge-cases.integration.test.ts +93 -1
  184. package/src/lib/tool-router/router.integration.test.ts +12 -0
  185. package/src/lib/tool-router/router.ts +90 -16
  186. package/src/lib/tool-router/types.ts +45 -4
  187. package/src/lib/tool-router/with-sandbox.ts +19 -5
  188. package/src/lib/virtual-fs/filesystem.ts +1 -1
  189. package/src/lib/virtual-fs/index.ts +5 -1
  190. package/src/lib/virtual-fs/mutations.ts +2 -4
  191. package/src/lib/virtual-fs/queries.ts +9 -5
  192. package/src/lib/virtual-fs/types.ts +4 -1
  193. package/src/lib/virtual-fs/virtual-fs.test.ts +9 -11
  194. package/src/lib/workflow.test.ts +7 -4
  195. package/src/lib/workflow.ts +1 -5
  196. package/src/tools/ask-user-question/tool.ts +1 -3
  197. package/src/tools/glob/handler.ts +1 -4
  198. package/src/tools/task-get/handler.ts +4 -5
  199. package/src/tools/task-list/handler.ts +1 -4
  200. package/src/tools/task-update/handler.ts +4 -5
  201. package/src/workflow.ts +22 -7
  202. package/tsup.config.ts +9 -6
  203. package/src/lib/.env +0 -1
  204. package/src/tools/bash/.env +0 -1
@@ -8,6 +8,16 @@ export interface AgentResponse<M = unknown> {
8
8
  message: M;
9
9
  rawToolCalls: RawToolCall[];
10
10
  usage?: TokenUsage;
11
+ /**
12
+ * Number of stored messages in the thread at the moment the LLM was
13
+ * invoked — i.e. *before* the assistant message is appended. The
14
+ * session uses this as a rewind snapshot so it can roll the thread
15
+ * back to this exact state if a tool requests a rewind.
16
+ *
17
+ * Adapters compute this for free from the array of stored messages
18
+ * they load when preparing the payload.
19
+ */
20
+ threadLengthAtCall?: number;
11
21
  }
12
22
 
13
23
  /**
@@ -1,9 +1,6 @@
1
1
  import { proxySinks } from "@temporalio/workflow";
2
2
  import type { ZeitlichObservabilitySinks } from "./sinks";
3
- import type {
4
- SessionStartHook,
5
- SessionEndHook,
6
- } from "../hooks/types";
3
+ import type { SessionStartHook, SessionEndHook } from "../hooks/types";
7
4
  import type {
8
5
  PostToolUseHook,
9
6
  PostToolUseFailureHook,
@@ -39,7 +36,9 @@ export interface ObservabilityHooks {
39
36
  *
40
37
  * @param agentName - Agent name attached to every emitted event
41
38
  */
42
- export function createObservabilityHooks(agentName: string): ObservabilityHooks {
39
+ export function createObservabilityHooks(
40
+ agentName: string
41
+ ): ObservabilityHooks {
43
42
  const { zeitlichMetrics } = proxySinks<ZeitlichObservabilitySinks>();
44
43
  let sessionStartMs = Date.now();
45
44
 
@@ -1,7 +1,4 @@
1
- export {
2
- createObservabilityHooks,
3
- composeHooks,
4
- } from "./hooks";
1
+ export { createObservabilityHooks, composeHooks } from "./hooks";
5
2
  export type { ObservabilityHooks } from "./hooks";
6
3
 
7
4
  export type {
@@ -50,8 +50,12 @@ export interface SandboxManagerHooks<
50
50
 
51
51
  /**
52
52
  * Called after a sandbox has been successfully created.
53
+ *
54
+ * Receives the live {@link Sandbox} instance so the hook can run setup
55
+ * commands, seed files, or capture identifiers without an extra
56
+ * `provider.get()` round-trip.
53
57
  */
54
- onPostCreate?: (sandboxId: string) => Promise<void>;
58
+ onPostCreate?: (sandbox: Sandbox, ctx: TCtx) => Promise<void>;
55
59
  }
56
60
 
57
61
  /**
@@ -87,8 +91,9 @@ export interface SandboxManagerHooks<
87
91
  * for (const p of filePaths) files[p] = await db.readFile(projectId, p);
88
92
  * return { modifiedOptions: { initialFiles: files } };
89
93
  * },
90
- * onPostCreate: async (sandboxId) => {
91
- * console.log("Sandbox created:", sandboxId);
94
+ * onPostCreate: async (sandbox) => {
95
+ * console.log("Sandbox created:", sandbox.id);
96
+ * await sandbox.exec("git init");
92
97
  * },
93
98
  * },
94
99
  * },
@@ -115,9 +120,7 @@ export class SandboxManager<
115
120
  async create(
116
121
  options?: TOptions,
117
122
  ctx?: TCtx
118
- ): Promise<{
119
- sandboxId: string;
120
- } | null> {
123
+ ): Promise<{ sandboxId: string } | null> {
121
124
  let providerOptions = options;
122
125
 
123
126
  if (this.hooks.onPreCreate) {
@@ -148,7 +151,7 @@ export class SandboxManager<
148
151
  const { sandbox } = await this.provider.create(providerOptions);
149
152
 
150
153
  if (this.hooks.onPostCreate) {
151
- await this.hooks.onPostCreate(sandbox.id);
154
+ await this.hooks.onPostCreate(sandbox, ctx ?? ({} as TCtx));
152
155
  }
153
156
 
154
157
  return { sandboxId: sandbox.id };
@@ -170,17 +173,24 @@ export class SandboxManager<
170
173
  await this.provider.resume(id);
171
174
  }
172
175
 
173
- async snapshot(id: string): Promise<SandboxSnapshot> {
174
- return this.provider.snapshot(id);
176
+ async snapshot(id: string, options?: TOptions): Promise<SandboxSnapshot> {
177
+ return this.provider.snapshot(id, options);
175
178
  }
176
179
 
177
- async restore(snapshot: SandboxSnapshot): Promise<string> {
178
- const sandbox = await this.provider.restore(snapshot);
180
+ async restore(
181
+ snapshot: SandboxSnapshot,
182
+ options?: TOptions
183
+ ): Promise<string> {
184
+ const sandbox = await this.provider.restore(snapshot, options);
179
185
  return sandbox.id;
180
186
  }
181
187
 
182
- async fork(sandboxId: string): Promise<string> {
183
- const sandbox = await this.provider.fork(sandboxId);
188
+ async deleteSnapshot(snapshot: SandboxSnapshot): Promise<void> {
189
+ await this.provider.deleteSnapshot(snapshot);
190
+ }
191
+
192
+ async fork(sandboxId: string, options?: TOptions): Promise<string> {
193
+ const sandbox = await this.provider.fork(sandboxId, options);
184
194
  return sandbox.id;
185
195
  }
186
196
 
@@ -212,9 +222,7 @@ export class SandboxManager<
212
222
  createSandbox: async (
213
223
  options?: TOptions,
214
224
  ctx?: TCtx
215
- ): Promise<{
216
- sandboxId: string;
217
- } | null> => {
225
+ ): Promise<{ sandboxId: string } | null> => {
218
226
  return this.create(options, ctx);
219
227
  },
220
228
  destroySandbox: async (sandboxId: string): Promise<void> => {
@@ -229,11 +237,28 @@ export class SandboxManager<
229
237
  resumeSandbox: async (sandboxId: string): Promise<void> => {
230
238
  await this.resume(sandboxId);
231
239
  },
232
- snapshotSandbox: async (sandboxId: string): Promise<SandboxSnapshot> => {
233
- return this.snapshot(sandboxId);
240
+ snapshotSandbox: async (
241
+ sandboxId: string,
242
+ options?: TOptions
243
+ ): Promise<SandboxSnapshot> => {
244
+ return this.snapshot(sandboxId, options);
245
+ },
246
+ restoreSandbox: async (
247
+ snapshot: SandboxSnapshot,
248
+ options?: TOptions
249
+ ): Promise<string> => {
250
+ return this.restore(snapshot, options);
251
+ },
252
+ deleteSandboxSnapshot: async (
253
+ snapshot: SandboxSnapshot
254
+ ): Promise<void> => {
255
+ await this.deleteSnapshot(snapshot);
234
256
  },
235
- forkSandbox: async (sandboxId: string): Promise<string> => {
236
- return this.fork(sandboxId);
257
+ forkSandbox: async (
258
+ sandboxId: string,
259
+ options?: TOptions
260
+ ): Promise<string> => {
261
+ return this.fork(sandboxId, options);
237
262
  },
238
263
  };
239
264
  const cap = (s: string): string => s.charAt(0).toUpperCase() + s.slice(1);
@@ -65,10 +65,7 @@ export class NodeFsSandboxFileSystem implements SandboxFileSystem {
65
65
  };
66
66
  }
67
67
 
68
- async mkdir(
69
- path: string,
70
- options?: { recursive?: boolean },
71
- ): Promise<void> {
68
+ async mkdir(path: string, options?: { recursive?: boolean }): Promise<void> {
72
69
  await fsp.mkdir(this.abs(path), options);
73
70
  }
74
71
 
@@ -88,7 +85,7 @@ export class NodeFsSandboxFileSystem implements SandboxFileSystem {
88
85
 
89
86
  async rm(
90
87
  path: string,
91
- options?: { recursive?: boolean; force?: boolean },
88
+ options?: { recursive?: boolean; force?: boolean }
92
89
  ): Promise<void> {
93
90
  await fsp.rm(this.abs(path), options);
94
91
  }
@@ -96,7 +93,7 @@ export class NodeFsSandboxFileSystem implements SandboxFileSystem {
96
93
  async cp(
97
94
  src: string,
98
95
  dest: string,
99
- options?: { recursive?: boolean },
96
+ options?: { recursive?: boolean }
100
97
  ): Promise<void> {
101
98
  await fsp.cp(this.abs(src), this.abs(dest), options);
102
99
  }
@@ -157,18 +157,21 @@ describe("SandboxManager", () => {
157
157
  expect(await sandbox.fs.readFile("/file.txt")).toBe("explicit");
158
158
  });
159
159
 
160
- it("onPostCreate hook receives sandboxId", async () => {
160
+ it("onPostCreate hook receives the live sandbox", async () => {
161
161
  let capturedId: string | undefined;
162
162
  const mgr = new SandboxManager(new InMemorySandboxProvider(), {
163
163
  hooks: {
164
- onPostCreate: async (sandboxId) => {
165
- capturedId = sandboxId;
164
+ onPostCreate: async (sandbox) => {
165
+ capturedId = sandbox.id;
166
+ await sandbox.fs.writeFile("/hook.txt", "written-by-hook");
166
167
  },
167
168
  },
168
169
  });
169
170
 
170
171
  const { sandboxId } = await mustCreate(mgr);
171
172
  expect(capturedId).toBe(sandboxId);
173
+ const sandbox = await mgr.getSandbox(sandboxId);
174
+ expect(await sandbox.fs.readFile("/hook.txt")).toBe("written-by-hook");
172
175
  });
173
176
 
174
177
  it("onPostCreate hook does not run when creation is skipped", async () => {
@@ -192,6 +195,8 @@ describe("SandboxManager", () => {
192
195
  expect(activities.inMemoryTestCreateSandbox).toBeTypeOf("function");
193
196
  expect(activities.inMemoryTestDestroySandbox).toBeTypeOf("function");
194
197
  expect(activities.inMemoryTestSnapshotSandbox).toBeTypeOf("function");
198
+ expect(activities.inMemoryTestRestoreSandbox).toBeTypeOf("function");
199
+ expect(activities.inMemoryTestDeleteSandboxSnapshot).toBeTypeOf("function");
195
200
 
196
201
  const result = await activities.inMemoryTestCreateSandbox();
197
202
  expect(result).not.toBeNull();
@@ -203,6 +208,34 @@ describe("SandboxManager", () => {
203
208
  SandboxNotFoundError
204
209
  );
205
210
  });
211
+
212
+ it("restoreSandbox activity creates a new sandbox from a snapshot", async () => {
213
+ const activities = manager.createActivities("Test");
214
+
215
+ const created = await activities.inMemoryTestCreateSandbox({
216
+ initialFiles: { "/greeting.txt": "hello" },
217
+ });
218
+ const { sandboxId } = created as { sandboxId: string };
219
+
220
+ const snapshot = await activities.inMemoryTestSnapshotSandbox(sandboxId);
221
+ await activities.inMemoryTestDestroySandbox(sandboxId);
222
+
223
+ const restoredId = await activities.inMemoryTestRestoreSandbox(snapshot);
224
+ const restored = await manager.getSandbox(restoredId);
225
+ expect(await restored.fs.readFile("/greeting.txt")).toBe("hello");
226
+ });
227
+
228
+ it("deleteSandboxSnapshot activity is a no-op for in-memory snapshots", async () => {
229
+ const activities = manager.createActivities("Test");
230
+
231
+ const created = await activities.inMemoryTestCreateSandbox();
232
+ const { sandboxId } = created as { sandboxId: string };
233
+
234
+ const snapshot = await activities.inMemoryTestSnapshotSandbox(sandboxId);
235
+ await expect(
236
+ activities.inMemoryTestDeleteSandboxSnapshot(snapshot)
237
+ ).resolves.toBeUndefined();
238
+ });
206
239
  });
207
240
 
208
241
  describe("InMemorySandboxProvider", () => {
@@ -3,7 +3,10 @@ import { toTree } from "./tree";
3
3
  import type { SandboxFileSystem, DirentEntry } from "./types";
4
4
 
5
5
  function createMockFs(
6
- structure: Record<string, { isDir: boolean; isLink?: boolean; linkTarget?: string }>,
6
+ structure: Record<
7
+ string,
8
+ { isDir: boolean; isLink?: boolean; linkTarget?: string }
9
+ >
7
10
  ): SandboxFileSystem {
8
11
  return {
9
12
  workspaceBase: "/",
@@ -21,13 +24,17 @@ function createMockFs(
21
24
  readdir: async (dir: string) => {
22
25
  const prefix = dir.endsWith("/") ? dir : dir + "/";
23
26
  return Object.keys(structure)
24
- .filter((p) => p.startsWith(prefix) && !p.slice(prefix.length).includes("/"))
27
+ .filter(
28
+ (p) => p.startsWith(prefix) && !p.slice(prefix.length).includes("/")
29
+ )
25
30
  .map((p) => p.slice(prefix.length));
26
31
  },
27
32
  readdirWithFileTypes: async (dir: string): Promise<DirentEntry[]> => {
28
33
  const prefix = dir.endsWith("/") ? dir : dir + "/";
29
34
  return Object.entries(structure)
30
- .filter(([p]) => p.startsWith(prefix) && !p.slice(prefix.length).includes("/"))
35
+ .filter(
36
+ ([p]) => p.startsWith(prefix) && !p.slice(prefix.length).includes("/")
37
+ )
31
38
  .map(([p, meta]) => ({
32
39
  name: p.slice(prefix.length),
33
40
  isFile: !meta.isDir && !meta.isLink,
@@ -17,6 +17,21 @@ export interface FileStat {
17
17
  mtime: Date;
18
18
  }
19
19
 
20
+ // ============================================================================
21
+ // Network & lifecycle
22
+ // ============================================================================
23
+
24
+ export interface SandboxNetworkConfig {
25
+ allowOut?: string[];
26
+ denyOut?: string[];
27
+ allowPublicTraffic?: boolean;
28
+ }
29
+
30
+ export interface SandboxLifecycleConfig {
31
+ onTimeout: "kill" | "pause";
32
+ autoResume?: boolean;
33
+ }
34
+
20
35
  /**
21
36
  * Provider-agnostic filesystem interface.
22
37
  *
@@ -114,6 +129,16 @@ export interface SandboxCreateOptions {
114
129
  initialFiles?: Record<string, string | Uint8Array>;
115
130
  /** Environment variables available inside the sandbox */
116
131
  env?: Record<string, string>;
132
+ /** Key-value metadata surfaced via provider list/query APIs */
133
+ metadata?: Record<string, string>;
134
+ /** Sandbox idle timeout in milliseconds */
135
+ timeoutMs?: number;
136
+ /** Enable or disable outbound internet access */
137
+ allowInternetAccess?: boolean;
138
+ /** Outbound network allow/deny rules */
139
+ network?: SandboxNetworkConfig;
140
+ /** Sandbox timeout behaviour */
141
+ lifecycle?: SandboxLifecycleConfig;
117
142
  }
118
143
 
119
144
  export interface SandboxCreateResult {
@@ -133,9 +158,23 @@ export interface SandboxProvider<
133
158
  pause(sandboxId: string, ttlSeconds?: number): Promise<void>;
134
159
  /** Resume a paused sandbox. No-op if already running. */
135
160
  resume(sandboxId: string): Promise<void>;
136
- snapshot(sandboxId: string): Promise<SandboxSnapshot>;
137
- restore(snapshot: SandboxSnapshot): Promise<Sandbox>;
138
- fork(sandboxId: string): Promise<Sandbox>;
161
+ /**
162
+ * Capture a snapshot of a running sandbox. `options` is a per-call override
163
+ * merged on top of the provider's static defaults.
164
+ */
165
+ snapshot(sandboxId: string, options?: TOptions): Promise<SandboxSnapshot>;
166
+ /**
167
+ * Restore a sandbox from a snapshot. `options` is a per-call override
168
+ * merged on top of the provider's static defaults.
169
+ */
170
+ restore(snapshot: SandboxSnapshot, options?: TOptions): Promise<Sandbox>;
171
+ /** Delete a previously captured snapshot. No-op if already deleted. */
172
+ deleteSnapshot(snapshot: SandboxSnapshot): Promise<void>;
173
+ /**
174
+ * Fork a running sandbox into a new one. `options` is a per-call override
175
+ * merged on top of the provider's static defaults.
176
+ */
177
+ fork(sandboxId: string, options?: TOptions): Promise<Sandbox>;
139
178
  }
140
179
 
141
180
  // ============================================================================
@@ -154,8 +193,20 @@ export interface SandboxOps<
154
193
  pauseSandbox(sandboxId: string): Promise<void>;
155
194
  /** Resume a paused sandbox. No-op if already running. */
156
195
  resumeSandbox(sandboxId: string): Promise<void>;
157
- snapshotSandbox(sandboxId: string): Promise<SandboxSnapshot>;
158
- forkSandbox(sandboxId: string): Promise<string>;
196
+ /** Capture a snapshot. `options` is a per-call override merged on top of provider defaults. */
197
+ snapshotSandbox(
198
+ sandboxId: string,
199
+ options?: TOptions
200
+ ): Promise<SandboxSnapshot>;
201
+ /** Create a fresh sandbox from a snapshot. `options` is a per-call override merged on top of provider defaults. */
202
+ restoreSandbox(
203
+ snapshot: SandboxSnapshot,
204
+ options?: TOptions
205
+ ): Promise<string>;
206
+ /** Delete a previously captured snapshot. No-op if already deleted. */
207
+ deleteSandboxSnapshot(snapshot: SandboxSnapshot): Promise<void>;
208
+ /** Fork a running sandbox. `options` is a per-call override merged on top of provider defaults. */
209
+ forkSandbox(sandboxId: string, options?: TOptions): Promise<string>;
159
210
  }
160
211
 
161
212
  /**
@@ -172,7 +223,10 @@ export type PrefixedSandboxOps<
172
223
  TOptions extends SandboxCreateOptions = SandboxCreateOptions,
173
224
  TCtx = unknown,
174
225
  > = {
175
- [K in keyof SandboxOps<TOptions, TCtx> as `${TPrefix}${Capitalize<K & string>}`]: SandboxOps<TOptions, TCtx>[K];
226
+ [K in keyof SandboxOps<
227
+ TOptions,
228
+ TCtx
229
+ > as `${TPrefix}${Capitalize<K & string>}`]: SandboxOps<TOptions, TCtx>[K];
176
230
  };
177
231
 
178
232
  // ============================================================================