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
package/dist/workflow.cjs CHANGED
@@ -113,7 +113,7 @@ function createToolRouter(options) {
113
113
  });
114
114
  }
115
115
  }
116
- async function processToolCall(toolCall, turn, sandboxId) {
116
+ async function processToolCall(toolCall, turn, sandboxId, onRewindRequested) {
117
117
  const startTime = Date.now();
118
118
  const tool = toolMap.get(toolCall.name);
119
119
  const preResult = await runPreHooks(toolCall, tool, turn);
@@ -128,7 +128,7 @@ function createToolRouter(options) {
128
128
  reason: "Skipped by PreToolUse hook"
129
129
  })
130
130
  });
131
- return null;
131
+ return { kind: "skipped" };
132
132
  }
133
133
  const effectiveArgs = preResult.args;
134
134
  workflow.log.debug("tool call dispatched", {
@@ -140,6 +140,7 @@ function createToolRouter(options) {
140
140
  let content;
141
141
  let resultAppended = false;
142
142
  let metadata;
143
+ let rewindRequested = false;
143
144
  try {
144
145
  if (tool) {
145
146
  const routerContext = {
@@ -157,11 +158,15 @@ function createToolRouter(options) {
157
158
  content = response.toolResponse;
158
159
  resultAppended = response.resultAppended === true;
159
160
  metadata = response.metadata;
161
+ rewindRequested = response.rewind === true;
160
162
  } else {
161
163
  result = { error: `Unknown tool: ${toolCall.name}` };
162
164
  content = JSON.stringify(result, null, 2);
163
165
  }
164
166
  } catch (error) {
167
+ if (workflow.isCancellation(error)) {
168
+ throw error;
169
+ }
165
170
  workflow.log.warn("tool call failed", {
166
171
  toolName: toolCall.name,
167
172
  toolCallId: toolCall.id,
@@ -179,6 +184,15 @@ function createToolRouter(options) {
179
184
  result = recovery.result;
180
185
  content = recovery.content;
181
186
  }
187
+ if (rewindRequested) {
188
+ const signal = {
189
+ toolCallId: toolCall.id,
190
+ toolName: toolCall.name
191
+ };
192
+ workflow.log.info("tool requested rewind", { ...signal });
193
+ onRewindRequested?.(signal);
194
+ return { kind: "rewind", signal };
195
+ }
182
196
  if (!resultAppended) {
183
197
  const config = {
184
198
  threadId: options.threadId,
@@ -215,7 +229,7 @@ function createToolRouter(options) {
215
229
  turn,
216
230
  durationMs
217
231
  );
218
- return toolResult;
232
+ return { kind: "result", value: toolResult };
219
233
  }
220
234
  return {
221
235
  hasTools() {
@@ -250,27 +264,59 @@ function createToolRouter(options) {
250
264
  }));
251
265
  },
252
266
  async processToolCalls(toolCalls, context) {
267
+ const attachRewind = (arr, rewind) => {
268
+ if (rewind) {
269
+ arr.rewind = rewind;
270
+ }
271
+ return arr;
272
+ };
253
273
  if (toolCalls.length === 0) {
254
- return [];
274
+ return attachRewind([], void 0);
255
275
  }
256
276
  const turn = context?.turn ?? 0;
257
277
  const sandboxId = context?.sandboxId;
278
+ let rewindSignal;
258
279
  if (options.parallel) {
259
- const results2 = await Promise.all(
260
- toolCalls.map((tc) => processToolCall(tc, turn, sandboxId))
261
- );
262
- return results2.filter(
263
- (r) => r !== null
280
+ const scope = new workflow.CancellationScope({ cancellable: true });
281
+ const onRewindRequested = (signal) => {
282
+ if (!rewindSignal) {
283
+ rewindSignal = signal;
284
+ scope.cancel();
285
+ }
286
+ };
287
+ const outcomes = await scope.run(
288
+ async () => Promise.allSettled(
289
+ toolCalls.map(
290
+ (tc) => processToolCall(tc, turn, sandboxId, onRewindRequested)
291
+ )
292
+ )
264
293
  );
294
+ const results2 = [];
295
+ for (const outcome of outcomes) {
296
+ if (outcome.status === "rejected") {
297
+ if (workflow.isCancellation(outcome.reason)) {
298
+ continue;
299
+ }
300
+ throw outcome.reason;
301
+ }
302
+ if (outcome.value.kind === "result") {
303
+ results2.push(outcome.value.value);
304
+ }
305
+ }
306
+ return attachRewind(results2, rewindSignal);
265
307
  }
266
308
  const results = [];
267
309
  for (const toolCall of toolCalls) {
268
- const result = await processToolCall(toolCall, turn, sandboxId);
269
- if (result !== null) {
270
- results.push(result);
310
+ const outcome = await processToolCall(toolCall, turn, sandboxId);
311
+ if (outcome.kind === "rewind") {
312
+ rewindSignal = outcome.signal;
313
+ break;
314
+ }
315
+ if (outcome.kind === "result") {
316
+ results.push(outcome.value);
271
317
  }
272
318
  }
273
- return results;
319
+ return attachRewind(results, rewindSignal);
274
320
  },
275
321
  async processToolCallsByName(toolCalls, toolName, handler, context) {
276
322
  const matchingCalls = toolCalls.filter((tc) => tc.name === toolName);
@@ -391,9 +437,7 @@ function createSubagentTool(subagents) {
391
437
  schema
392
438
  };
393
439
  }
394
- var childResultSignal = workflow.defineSignal("childResult");
395
440
  var childSandboxReadySignal = workflow.defineSignal("childSandboxReady");
396
- var destroySandboxSignal = workflow.defineSignal("destroySandbox");
397
441
 
398
442
  // src/lib/subagent/handler.ts
399
443
  function resolveSandboxConfig(config) {
@@ -417,25 +461,27 @@ function resolveSandboxConfig(config) {
417
461
  }
418
462
  function createSubagentHandler(subagents) {
419
463
  const { taskQueue: parentTaskQueue } = workflow.workflowInfo();
420
- const childResults = /* @__PURE__ */ new Map();
464
+ const agentSandboxOps = /* @__PURE__ */ new Map();
465
+ for (const cfg of subagents) {
466
+ if (cfg.sandbox && cfg.sandbox !== "none") {
467
+ agentSandboxOps.set(cfg.agentName, cfg.sandbox.proxy(cfg.agentName));
468
+ }
469
+ }
421
470
  const pendingDestroys = /* @__PURE__ */ new Map();
422
471
  const threadSandboxes = /* @__PURE__ */ new Map();
423
472
  const persistentSandboxes = /* @__PURE__ */ new Map();
424
473
  const persistentSandboxCreating = /* @__PURE__ */ new Set();
425
474
  const lazyCreatorAgent = /* @__PURE__ */ new Map();
426
- workflow.setHandler(childResultSignal, ({ childWorkflowId, result }) => {
427
- childResults.set(childWorkflowId, result);
428
- });
429
- workflow.setHandler(
430
- childSandboxReadySignal,
431
- ({ childWorkflowId, sandboxId }) => {
432
- const agentName = lazyCreatorAgent.get(childWorkflowId);
433
- if (agentName && !persistentSandboxes.has(agentName)) {
434
- persistentSandboxes.set(agentName, sandboxId);
435
- lazyCreatorAgent.delete(childWorkflowId);
436
- }
475
+ const threadSnapshots = /* @__PURE__ */ new Map();
476
+ const persistentBaseSnapshot = /* @__PURE__ */ new Map();
477
+ const persistentBaseSnapshotCreating = /* @__PURE__ */ new Set();
478
+ workflow.setHandler(childSandboxReadySignal, ({ childWorkflowId, sandboxId }) => {
479
+ const agentName = lazyCreatorAgent.get(childWorkflowId);
480
+ if (agentName && !persistentSandboxes.has(agentName)) {
481
+ persistentSandboxes.set(agentName, sandboxId);
482
+ lazyCreatorAgent.delete(childWorkflowId);
437
483
  }
438
- );
484
+ });
439
485
  const handler = async (args, context) => {
440
486
  const config = subagents.find((s) => s.agentName === args.subagent);
441
487
  if (!config) {
@@ -446,6 +492,12 @@ function createSubagentHandler(subagents) {
446
492
  const childWorkflowId = `${args.subagent}-${getShortId()}`;
447
493
  const { sandboxId: parentSandboxId } = context;
448
494
  const sandboxCfg = resolveSandboxConfig(config.sandbox);
495
+ if (sandboxCfg.source !== "none" && !agentSandboxOps.has(config.agentName)) {
496
+ throw workflow.ApplicationFailure.create({
497
+ message: `Subagent "${config.agentName}" uses a sandbox but no \`sandbox.proxy\` is configured on its SubagentConfig`,
498
+ nonRetryable: true
499
+ });
500
+ }
449
501
  if (sandboxCfg.source === "inherit" && !parentSandboxId) {
450
502
  throw new Error(
451
503
  `Subagent "${config.agentName}" is configured with sandbox: "inherit" but the parent has no sandbox`
@@ -464,12 +516,39 @@ function createSubagentHandler(subagents) {
464
516
  let sandbox;
465
517
  let sandboxShutdownOverride;
466
518
  let isLazyCreator = false;
519
+ let isSnapshotBaseCreator = false;
467
520
  if (sandboxCfg.source === "inherit" && parentSandboxId) {
468
521
  if (sandboxCfg.continuation === "fork") {
469
522
  sandbox = { mode: "fork", sandboxId: parentSandboxId };
523
+ } else if (sandboxCfg.continuation === "snapshot") {
524
+ throw new Error(
525
+ `Subagent "${config.agentName}" has sandbox source "inherit" with continuation "snapshot" \u2014 snapshot continuation is only supported for source "own"`
526
+ );
470
527
  } else {
471
528
  sandbox = { mode: "inherit", sandboxId: parentSandboxId };
472
529
  }
530
+ } else if (sandboxCfg.source === "own" && sandboxCfg.continuation === "snapshot") {
531
+ const isLazy = sandboxCfg.init === "once";
532
+ let baseSnap;
533
+ if (continuationThreadId) {
534
+ baseSnap = threadSnapshots.get(continuationThreadId)?.snapshot;
535
+ }
536
+ if (!baseSnap && isLazy) {
537
+ baseSnap = persistentBaseSnapshot.get(config.agentName);
538
+ if (!baseSnap) {
539
+ if (persistentBaseSnapshotCreating.has(config.agentName)) {
540
+ await workflow.condition(() => persistentBaseSnapshot.has(config.agentName));
541
+ baseSnap = persistentBaseSnapshot.get(config.agentName);
542
+ } else {
543
+ persistentBaseSnapshotCreating.add(config.agentName);
544
+ isSnapshotBaseCreator = true;
545
+ }
546
+ }
547
+ }
548
+ if (baseSnap) {
549
+ sandbox = { mode: "from-snapshot", snapshot: baseSnap };
550
+ }
551
+ sandboxShutdownOverride = "snapshot";
473
552
  } else if (sandboxCfg.source === "own") {
474
553
  const isLazy = sandboxCfg.init === "once";
475
554
  let baseSandboxId;
@@ -520,31 +599,8 @@ function createSubagentHandler(subagents) {
520
599
  threadMode,
521
600
  sandboxSource: sandboxCfg.source
522
601
  });
523
- const childHandle = await workflow.startChild(config.workflow, childOpts);
602
+ const childResult = await workflow.executeChild(config.workflow, childOpts);
524
603
  const effectiveShutdown = sandboxShutdownOverride ?? sandboxCfg.shutdown ?? "destroy";
525
- if (effectiveShutdown === "pause-until-parent-close" || effectiveShutdown === "keep-until-parent-close") {
526
- const key = isLazyCreator ? `persistent:${config.agentName}` : childWorkflowId;
527
- pendingDestroys.set(key, childHandle);
528
- }
529
- await Promise.race([
530
- workflow.condition(() => childResults.has(childWorkflowId)),
531
- childHandle.result()
532
- ]);
533
- if (!childResults.has(childWorkflowId)) {
534
- await workflow.condition(() => childResults.has(childWorkflowId));
535
- }
536
- const childResult = childResults.get(childWorkflowId);
537
- childResults.delete(childWorkflowId);
538
- if (!childResult) {
539
- workflow.log.warn("subagent returned no result", {
540
- subagent: config.agentName,
541
- childWorkflowId
542
- });
543
- return {
544
- toolResponse: "Subagent workflow did not signal a result",
545
- data: null
546
- };
547
- }
548
604
  workflow.log.info("subagent completed", {
549
605
  subagent: config.agentName,
550
606
  childWorkflowId,
@@ -556,19 +612,42 @@ function createSubagentHandler(subagents) {
556
612
  usage,
557
613
  threadId: childThreadId,
558
614
  sandboxId: childSandboxId,
615
+ snapshot: childSnapshot,
616
+ baseSnapshot: childBaseSnapshot,
559
617
  metadata
560
618
  } = childResult;
561
619
  if (childSandboxId) {
562
- if (sandboxCfg.source === "own" && sandboxCfg.init === "once" && !persistentSandboxes.has(config.agentName)) {
620
+ if (sandboxCfg.source === "own" && sandboxCfg.init === "once" && sandboxCfg.continuation !== "snapshot" && !persistentSandboxes.has(config.agentName)) {
563
621
  persistentSandboxes.set(config.agentName, childSandboxId);
564
- } else if (allowsContinuation && childThreadId) {
622
+ } else if (allowsContinuation && childThreadId && sandboxCfg.source === "own" && sandboxCfg.continuation !== "snapshot") {
565
623
  threadSandboxes.set(childThreadId, childSandboxId);
566
624
  }
567
625
  }
626
+ if (childSandboxId && (effectiveShutdown === "pause-until-parent-close" || effectiveShutdown === "keep-until-parent-close")) {
627
+ const key = isLazyCreator ? `persistent:${config.agentName}` : childWorkflowId;
628
+ pendingDestroys.set(key, {
629
+ agentName: config.agentName,
630
+ sandboxId: childSandboxId
631
+ });
632
+ }
633
+ if (sandboxCfg.source === "own" && sandboxCfg.continuation === "snapshot") {
634
+ if (childSnapshot && childThreadId) {
635
+ threadSnapshots.set(childThreadId, {
636
+ agentName: config.agentName,
637
+ snapshot: childSnapshot
638
+ });
639
+ }
640
+ if (isSnapshotBaseCreator && childBaseSnapshot && !persistentBaseSnapshot.has(config.agentName)) {
641
+ persistentBaseSnapshot.set(config.agentName, childBaseSnapshot);
642
+ }
643
+ }
568
644
  if (isLazyCreator) {
569
645
  persistentSandboxCreating.delete(config.agentName);
570
646
  lazyCreatorAgent.delete(childWorkflowId);
571
647
  }
648
+ if (isSnapshotBaseCreator) {
649
+ persistentBaseSnapshotCreating.delete(config.agentName);
650
+ }
572
651
  if (!toolResponse) {
573
652
  return {
574
653
  toolResponse: "Subagent workflow returned no response",
@@ -604,22 +683,60 @@ function createSubagentHandler(subagents) {
604
683
  };
605
684
  };
606
685
  const destroySubagentSandboxes = async () => {
607
- const handles = [...pendingDestroys.values()];
686
+ const entries = [...pendingDestroys.values()];
608
687
  pendingDestroys.clear();
609
688
  await Promise.all(
610
- handles.map(async (handle) => {
689
+ entries.map(async ({ agentName, sandboxId }) => {
690
+ const ops = agentSandboxOps.get(agentName);
691
+ if (!ops) {
692
+ workflow.log.warn(
693
+ "Skipping sandbox destroy \u2014 no sandbox.proxy registered for agent",
694
+ { agentName, sandboxId }
695
+ );
696
+ return;
697
+ }
698
+ try {
699
+ await ops.destroySandbox(sandboxId);
700
+ } catch (err) {
701
+ workflow.log.warn("Failed to destroy subagent sandbox", {
702
+ agentName,
703
+ sandboxId,
704
+ error: err
705
+ });
706
+ }
707
+ })
708
+ );
709
+ };
710
+ const cleanupSubagentSnapshots = async () => {
711
+ const tagged = [];
712
+ for (const entry of threadSnapshots.values()) tagged.push(entry);
713
+ for (const [agentName, snapshot] of persistentBaseSnapshot.entries()) {
714
+ tagged.push({ agentName, snapshot });
715
+ }
716
+ threadSnapshots.clear();
717
+ persistentBaseSnapshot.clear();
718
+ await Promise.all(
719
+ tagged.map(async ({ agentName, snapshot }) => {
720
+ const ops = agentSandboxOps.get(agentName);
721
+ if (!ops) {
722
+ workflow.log.warn(
723
+ "Skipping snapshot delete \u2014 no sandbox.proxy registered for agent",
724
+ { agentName }
725
+ );
726
+ return;
727
+ }
611
728
  try {
612
- await handle.signal(destroySandboxSignal);
613
- await handle.result();
729
+ await ops.deleteSandboxSnapshot(snapshot);
614
730
  } catch (err) {
615
- workflow.log.warn("Failed to signal destroySandbox to child workflow", {
731
+ workflow.log.warn("Failed to delete subagent snapshot", {
732
+ agentName,
616
733
  error: err
617
734
  });
618
735
  }
619
736
  })
620
737
  );
621
738
  };
622
- return { handler, destroySubagentSandboxes };
739
+ return { handler, destroySubagentSandboxes, cleanupSubagentSnapshots };
623
740
  }
624
741
 
625
742
  // src/lib/subagent/register.ts
@@ -633,7 +750,7 @@ function buildSubagentRegistration(subagents) {
633
750
  if (s.hooks) subagentHooksMap.set(s.agentName, s.hooks);
634
751
  }
635
752
  const resolveSubagentName = (args) => args.subagent;
636
- const { handler, destroySubagentSandboxes } = createSubagentHandler(subagents);
753
+ const { handler, destroySubagentSandboxes, cleanupSubagentSnapshots } = createSubagentHandler(subagents);
637
754
  const registration = {
638
755
  name: SUBAGENT_TOOL_NAME,
639
756
  enabled: () => getEnabled().length > 0,
@@ -657,7 +774,7 @@ function buildSubagentRegistration(subagents) {
657
774
  }
658
775
  }
659
776
  };
660
- return { registration, destroySubagentSandboxes };
777
+ return { registration, destroySubagentSandboxes, cleanupSubagentSnapshots };
661
778
  }
662
779
  var READ_SKILL_TOOL_NAME = "ReadSkill";
663
780
  function buildReadSkillDescription(skills) {
@@ -730,9 +847,7 @@ function validateSkillNames(skills) {
730
847
  const names = skills.map((s) => s.name);
731
848
  const dupes = names.filter((n, i) => names.indexOf(n) !== i);
732
849
  if (dupes.length > 0) {
733
- throw new Error(
734
- `Duplicate skill names: ${[...new Set(dupes)].join(", ")}`
735
- );
850
+ throw new Error(`Duplicate skill names: ${[...new Set(dupes)].join(", ")}`);
736
851
  }
737
852
  }
738
853
  function buildSkillRegistration(skills) {
@@ -798,15 +913,18 @@ async function createSession({
798
913
  initializeThread,
799
914
  appendSystemMessage,
800
915
  appendAgentMessage,
801
- forkThread
916
+ forkThread,
917
+ truncateThread
802
918
  } = threadOps;
803
919
  const plugins = [];
804
920
  let destroySubagentSandboxes;
921
+ let cleanupSubagentSnapshots;
805
922
  if (subagents) {
806
923
  const result = buildSubagentRegistration(subagents);
807
924
  if (result) {
808
925
  plugins.push(result.registration);
809
926
  destroySubagentSandboxes = result.destroySubagentSandboxes;
927
+ cleanupSubagentSnapshots = result.cleanupSubagentSnapshots;
810
928
  }
811
929
  }
812
930
  if (skills) {
@@ -859,6 +977,9 @@ async function createSession({
859
977
  const sandboxMode = sandboxInit?.mode;
860
978
  let sandboxId;
861
979
  let sandboxOwned = false;
980
+ let baseSnapshot;
981
+ let exitSnapshot;
982
+ let freshlyCreated = false;
862
983
  if (sandboxMode === "inherit") {
863
984
  const inheritInit = sandboxInit;
864
985
  sandboxId = inheritInit.sandboxId;
@@ -887,8 +1008,23 @@ async function createSession({
887
1008
  nonRetryable: true
888
1009
  });
889
1010
  }
1011
+ const forkInit = sandboxInit;
890
1012
  sandboxId = await sandboxOps.forkSandbox(
891
- sandboxInit.sandboxId
1013
+ forkInit.sandboxId,
1014
+ forkInit.options
1015
+ );
1016
+ sandboxOwned = true;
1017
+ } else if (sandboxMode === "from-snapshot") {
1018
+ if (!sandboxOps) {
1019
+ throw workflow.ApplicationFailure.create({
1020
+ message: "No sandboxOps provided \u2014 cannot restore sandbox",
1021
+ nonRetryable: true
1022
+ });
1023
+ }
1024
+ const restoreInit = sandboxInit;
1025
+ sandboxId = await sandboxOps.restoreSandbox(
1026
+ restoreInit.snapshot,
1027
+ restoreInit.options
892
1028
  );
893
1029
  sandboxOwned = true;
894
1030
  } else if (sandboxOps) {
@@ -899,9 +1035,13 @@ async function createSession({
899
1035
  if (result) {
900
1036
  sandboxId = result.sandboxId;
901
1037
  sandboxOwned = true;
1038
+ freshlyCreated = true;
902
1039
  }
903
1040
  }
904
- if (sandboxId && onSandboxReady) {
1041
+ if (sandboxId && sandboxOwned && freshlyCreated && sandboxShutdown === "snapshot" && sandboxOps) {
1042
+ baseSnapshot = await sandboxOps.snapshotSandbox(sandboxId);
1043
+ }
1044
+ if (sandboxId && sandboxOwned && onSandboxReady) {
905
1045
  onSandboxReady(sandboxId);
906
1046
  }
907
1047
  if (virtualFsConfig) {
@@ -967,18 +1107,25 @@ async function createSession({
967
1107
  threadKey
968
1108
  );
969
1109
  let exitReason = "completed";
1110
+ let finalMessage = null;
970
1111
  try {
971
1112
  while (stateManager.isRunning() && !stateManager.isTerminal() && stateManager.getTurns() < maxTurns) {
972
1113
  stateManager.incrementTurns();
973
1114
  const currentTurn = stateManager.getTurns();
974
1115
  workflow.log.debug("turn started", { agentName, threadId, turn: currentTurn });
975
1116
  stateManager.setTools(toolRouter.getToolDefinitions());
976
- const { message, rawToolCalls, usage } = await runAgent({
1117
+ const {
1118
+ message,
1119
+ rawToolCalls,
1120
+ usage,
1121
+ threadLengthAtCall
1122
+ } = await runAgent({
977
1123
  threadId,
978
1124
  threadKey,
979
1125
  agentName,
980
1126
  metadata
981
1127
  });
1128
+ const preAssistantLength = threadLengthAtCall;
982
1129
  await appendAgentMessage(threadId, workflow.uuid4(), message, threadKey);
983
1130
  if (usage) {
984
1131
  stateManager.updateUsage(usage);
@@ -993,21 +1140,8 @@ async function createSession({
993
1140
  if (!toolRouter.hasTools() || rawToolCalls.length === 0) {
994
1141
  stateManager.complete();
995
1142
  exitReason = "completed";
996
- workflow.log.info("session ended", {
997
- agentName,
998
- threadId,
999
- exitReason,
1000
- turns: currentTurn,
1001
- durationMs: Date.now() - sessionStartMs,
1002
- usage: stateManager.getTotalUsage()
1003
- });
1004
- return {
1005
- threadId,
1006
- finalMessage: message,
1007
- exitReason,
1008
- usage: stateManager.getTotalUsage(),
1009
- sandboxId
1010
- };
1143
+ finalMessage = message;
1144
+ break;
1011
1145
  }
1012
1146
  const parsedToolCalls = [];
1013
1147
  for (const tc of rawToolCalls) {
@@ -1037,6 +1171,24 @@ async function createSession({
1037
1171
  stateManager.updateUsage(result.usage);
1038
1172
  }
1039
1173
  }
1174
+ const rewind = toolCallResults.rewind;
1175
+ if (rewind) {
1176
+ workflow.log.info("rewinding turn", {
1177
+ agentName,
1178
+ threadId,
1179
+ turn: currentTurn,
1180
+ toolCallId: rewind.toolCallId,
1181
+ toolName: rewind.toolName
1182
+ });
1183
+ if (preAssistantLength === void 0) {
1184
+ throw workflow.ApplicationFailure.create({
1185
+ message: "Rewind requested but runAgent did not report `threadLengthAtCall`; the adapter must populate it to support rewinds.",
1186
+ nonRetryable: true
1187
+ });
1188
+ }
1189
+ await truncateThread(threadId, preAssistantLength, threadKey);
1190
+ continue;
1191
+ }
1040
1192
  if (stateManager.getStatus() === "WAITING_FOR_INPUT") {
1041
1193
  const conditionMet = await workflow.condition(
1042
1194
  () => stateManager.getStatus() === "RUNNING",
@@ -1079,11 +1231,21 @@ async function createSession({
1079
1231
  case "pause-until-parent-close":
1080
1232
  await sandboxOps.pauseSandbox(sandboxId);
1081
1233
  break;
1234
+ case "keep":
1235
+ case "keep-until-parent-close":
1236
+ break;
1237
+ case "snapshot":
1238
+ exitSnapshot = await sandboxOps.snapshotSandbox(sandboxId);
1239
+ await sandboxOps.destroySandbox(sandboxId);
1240
+ break;
1082
1241
  }
1083
1242
  }
1084
1243
  if (destroySubagentSandboxes) {
1085
1244
  await destroySubagentSandboxes();
1086
1245
  }
1246
+ if (cleanupSubagentSnapshots) {
1247
+ await cleanupSubagentSnapshots();
1248
+ }
1087
1249
  }
1088
1250
  workflow.log.info("session ended", {
1089
1251
  agentName,
@@ -1091,14 +1253,18 @@ async function createSession({
1091
1253
  exitReason,
1092
1254
  turns: stateManager.getTurns(),
1093
1255
  durationMs: Date.now() - sessionStartMs,
1094
- usage: stateManager.getTotalUsage()
1256
+ usage: stateManager.getTotalUsage(),
1257
+ ...baseSnapshot && { hasBaseSnapshot: true },
1258
+ ...exitSnapshot && { hasExitSnapshot: true }
1095
1259
  });
1096
1260
  return {
1097
1261
  threadId,
1098
- finalMessage: null,
1262
+ finalMessage,
1099
1263
  exitReason,
1100
1264
  usage: stateManager.getTotalUsage(),
1101
- sandboxId
1265
+ sandboxId,
1266
+ ...baseSnapshot && { baseSnapshot },
1267
+ ...exitSnapshot && { snapshot: exitSnapshot }
1102
1268
  };
1103
1269
  }
1104
1270
  };
@@ -1309,44 +1475,16 @@ function defineSubagentWorkflow(config, fn) {
1309
1475
  ...workflowInput.thread && { thread: workflowInput.thread },
1310
1476
  ...workflowInput.sandbox && { sandbox: workflowInput.sandbox },
1311
1477
  onSandboxReady: (sandboxId) => {
1312
- void parentHandle.signal(childSandboxReadySignal, {
1313
- childWorkflowId: workflow.workflowInfo().workflowId,
1314
- sandboxId
1315
- });
1478
+ const isReuse = workflowInput.sandbox?.mode === "continue";
1479
+ if (!isReuse) {
1480
+ void parentHandle.signal(childSandboxReadySignal, {
1481
+ childWorkflowId: workflow.workflowInfo().workflowId,
1482
+ sandboxId
1483
+ });
1484
+ }
1316
1485
  }
1317
1486
  };
1318
- const { destroySandbox, ...result } = await fn(
1319
- prompt,
1320
- sessionInput,
1321
- context ?? {}
1322
- );
1323
- if (effectiveShutdown === "pause-until-parent-close" || effectiveShutdown === "keep-until-parent-close") {
1324
- if (!destroySandbox) {
1325
- throw workflow.ApplicationFailure.create({
1326
- message: `Subagent "${config.name}" has sandboxShutdown="${effectiveShutdown}" but fn did not return a destroySandbox callback`,
1327
- nonRetryable: true
1328
- });
1329
- }
1330
- if (!result.sandboxId) {
1331
- throw workflow.ApplicationFailure.create({
1332
- message: `Subagent "${config.name}" has sandboxShutdown="${effectiveShutdown}" but fn did not return a sandboxId`,
1333
- nonRetryable: true
1334
- });
1335
- }
1336
- }
1337
- await parentHandle.signal(childResultSignal, {
1338
- childWorkflowId: workflow.workflowInfo().workflowId,
1339
- result
1340
- });
1341
- if (destroySandbox) {
1342
- let destroyRequested = false;
1343
- workflow.setHandler(destroySandboxSignal, () => {
1344
- destroyRequested = true;
1345
- });
1346
- await workflow.condition(() => destroyRequested);
1347
- await destroySandbox();
1348
- }
1349
- return result;
1487
+ return fn(prompt, sessionInput, context ?? {});
1350
1488
  };
1351
1489
  Object.defineProperty(workflow$1, "name", { value: config.name });
1352
1490
  return Object.assign(workflow$1, {
@@ -1456,9 +1594,7 @@ function applyVirtualTreeMutations(stateManager, mutations) {
1456
1594
  tree = tree.filter((e) => e.path !== m.path);
1457
1595
  break;
1458
1596
  case "update":
1459
- tree = tree.map(
1460
- (e) => e.path === m.path ? { ...e, ...m.entry } : e
1461
- );
1597
+ tree = tree.map((e) => e.path === m.path ? { ...e, ...m.entry } : e);
1462
1598
  break;
1463
1599
  }
1464
1600
  }
@@ -1517,7 +1653,9 @@ function formatVirtualFileTree(entries, opts = {}) {
1517
1653
  // src/lib/virtual-fs/queries.ts
1518
1654
  function hasFileWithMimeType(stateManager, pattern) {
1519
1655
  const tree = stateManager.get("fileTree");
1520
- const matchers = (Array.isArray(pattern) ? pattern : [pattern]).map(buildMatcher);
1656
+ const matchers = (Array.isArray(pattern) ? pattern : [pattern]).map(
1657
+ buildMatcher
1658
+ );
1521
1659
  return tree.some((entry) => {
1522
1660
  const meta = entry.metadata;
1523
1661
  const mime = meta?.mimeType;
@@ -1578,7 +1716,9 @@ function proxyVirtualFsOps(scope, options) {
1578
1716
  // src/lib/skills/parse.ts
1579
1717
  function parseSkillFile(raw) {
1580
1718
  const trimmed = raw.replace(/^\uFEFF/, "");
1581
- const match = trimmed.match(/^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*\r?\n?([\s\S]*)$/);
1719
+ const match = trimmed.match(
1720
+ /^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*\r?\n?([\s\S]*)$/
1721
+ );
1582
1722
  if (!match) {
1583
1723
  throw new Error(
1584
1724
  "SKILL.md must start with YAML frontmatter delimited by ---"
@@ -1850,7 +1990,9 @@ function createTaskGetHandler(stateManager) {
1850
1990
  const task = stateManager.getTask(args.taskId) ?? null;
1851
1991
  if (!task) {
1852
1992
  return {
1853
- toolResponse: JSON.stringify({ error: `Task not found: ${args.taskId}` }),
1993
+ toolResponse: JSON.stringify({
1994
+ error: `Task not found: ${args.taskId}`
1995
+ }),
1854
1996
  data: null
1855
1997
  };
1856
1998
  }
@@ -1893,7 +2035,9 @@ function createTaskUpdateHandler(stateManager) {
1893
2035
  const task = stateManager.getTask(args.taskId);
1894
2036
  if (!task) {
1895
2037
  return {
1896
- toolResponse: JSON.stringify({ error: `Task not found: ${args.taskId}` }),
2038
+ toolResponse: JSON.stringify({
2039
+ error: `Task not found: ${args.taskId}`
2040
+ }),
1897
2041
  data: null
1898
2042
  };
1899
2043
  }