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.js CHANGED
@@ -1,4 +1,4 @@
1
- import { defineSignal, uuid4, setHandler, defineUpdate, ApplicationFailure, log, condition, defineQuery, proxySinks, workflowInfo, proxyActivities, getExternalWorkflowHandle, startChild } from '@temporalio/workflow';
1
+ import { defineSignal, CancellationScope, isCancellation, uuid4, setHandler, defineUpdate, ApplicationFailure, log, condition, defineQuery, proxySinks, workflowInfo, proxyActivities, getExternalWorkflowHandle, executeChild } from '@temporalio/workflow';
2
2
  import z14, { z } from 'zod';
3
3
  import { ApplicationFailure as ApplicationFailure$1 } from '@temporalio/common';
4
4
 
@@ -107,7 +107,7 @@ function createToolRouter(options) {
107
107
  });
108
108
  }
109
109
  }
110
- async function processToolCall(toolCall, turn, sandboxId) {
110
+ async function processToolCall(toolCall, turn, sandboxId, onRewindRequested) {
111
111
  const startTime = Date.now();
112
112
  const tool = toolMap.get(toolCall.name);
113
113
  const preResult = await runPreHooks(toolCall, tool, turn);
@@ -122,7 +122,7 @@ function createToolRouter(options) {
122
122
  reason: "Skipped by PreToolUse hook"
123
123
  })
124
124
  });
125
- return null;
125
+ return { kind: "skipped" };
126
126
  }
127
127
  const effectiveArgs = preResult.args;
128
128
  log.debug("tool call dispatched", {
@@ -134,6 +134,7 @@ function createToolRouter(options) {
134
134
  let content;
135
135
  let resultAppended = false;
136
136
  let metadata;
137
+ let rewindRequested = false;
137
138
  try {
138
139
  if (tool) {
139
140
  const routerContext = {
@@ -151,11 +152,15 @@ function createToolRouter(options) {
151
152
  content = response.toolResponse;
152
153
  resultAppended = response.resultAppended === true;
153
154
  metadata = response.metadata;
155
+ rewindRequested = response.rewind === true;
154
156
  } else {
155
157
  result = { error: `Unknown tool: ${toolCall.name}` };
156
158
  content = JSON.stringify(result, null, 2);
157
159
  }
158
160
  } catch (error) {
161
+ if (isCancellation(error)) {
162
+ throw error;
163
+ }
159
164
  log.warn("tool call failed", {
160
165
  toolName: toolCall.name,
161
166
  toolCallId: toolCall.id,
@@ -173,6 +178,15 @@ function createToolRouter(options) {
173
178
  result = recovery.result;
174
179
  content = recovery.content;
175
180
  }
181
+ if (rewindRequested) {
182
+ const signal = {
183
+ toolCallId: toolCall.id,
184
+ toolName: toolCall.name
185
+ };
186
+ log.info("tool requested rewind", { ...signal });
187
+ onRewindRequested?.(signal);
188
+ return { kind: "rewind", signal };
189
+ }
176
190
  if (!resultAppended) {
177
191
  const config = {
178
192
  threadId: options.threadId,
@@ -209,7 +223,7 @@ function createToolRouter(options) {
209
223
  turn,
210
224
  durationMs
211
225
  );
212
- return toolResult;
226
+ return { kind: "result", value: toolResult };
213
227
  }
214
228
  return {
215
229
  hasTools() {
@@ -244,27 +258,59 @@ function createToolRouter(options) {
244
258
  }));
245
259
  },
246
260
  async processToolCalls(toolCalls, context) {
261
+ const attachRewind = (arr, rewind) => {
262
+ if (rewind) {
263
+ arr.rewind = rewind;
264
+ }
265
+ return arr;
266
+ };
247
267
  if (toolCalls.length === 0) {
248
- return [];
268
+ return attachRewind([], void 0);
249
269
  }
250
270
  const turn = context?.turn ?? 0;
251
271
  const sandboxId = context?.sandboxId;
272
+ let rewindSignal;
252
273
  if (options.parallel) {
253
- const results2 = await Promise.all(
254
- toolCalls.map((tc) => processToolCall(tc, turn, sandboxId))
255
- );
256
- return results2.filter(
257
- (r) => r !== null
274
+ const scope = new CancellationScope({ cancellable: true });
275
+ const onRewindRequested = (signal) => {
276
+ if (!rewindSignal) {
277
+ rewindSignal = signal;
278
+ scope.cancel();
279
+ }
280
+ };
281
+ const outcomes = await scope.run(
282
+ async () => Promise.allSettled(
283
+ toolCalls.map(
284
+ (tc) => processToolCall(tc, turn, sandboxId, onRewindRequested)
285
+ )
286
+ )
258
287
  );
288
+ const results2 = [];
289
+ for (const outcome of outcomes) {
290
+ if (outcome.status === "rejected") {
291
+ if (isCancellation(outcome.reason)) {
292
+ continue;
293
+ }
294
+ throw outcome.reason;
295
+ }
296
+ if (outcome.value.kind === "result") {
297
+ results2.push(outcome.value.value);
298
+ }
299
+ }
300
+ return attachRewind(results2, rewindSignal);
259
301
  }
260
302
  const results = [];
261
303
  for (const toolCall of toolCalls) {
262
- const result = await processToolCall(toolCall, turn, sandboxId);
263
- if (result !== null) {
264
- results.push(result);
304
+ const outcome = await processToolCall(toolCall, turn, sandboxId);
305
+ if (outcome.kind === "rewind") {
306
+ rewindSignal = outcome.signal;
307
+ break;
308
+ }
309
+ if (outcome.kind === "result") {
310
+ results.push(outcome.value);
265
311
  }
266
312
  }
267
- return results;
313
+ return attachRewind(results, rewindSignal);
268
314
  },
269
315
  async processToolCallsByName(toolCalls, toolName, handler, context) {
270
316
  const matchingCalls = toolCalls.filter((tc) => tc.name === toolName);
@@ -385,9 +431,7 @@ function createSubagentTool(subagents) {
385
431
  schema
386
432
  };
387
433
  }
388
- var childResultSignal = defineSignal("childResult");
389
434
  var childSandboxReadySignal = defineSignal("childSandboxReady");
390
- var destroySandboxSignal = defineSignal("destroySandbox");
391
435
 
392
436
  // src/lib/subagent/handler.ts
393
437
  function resolveSandboxConfig(config) {
@@ -411,25 +455,27 @@ function resolveSandboxConfig(config) {
411
455
  }
412
456
  function createSubagentHandler(subagents) {
413
457
  const { taskQueue: parentTaskQueue } = workflowInfo();
414
- const childResults = /* @__PURE__ */ new Map();
458
+ const agentSandboxOps = /* @__PURE__ */ new Map();
459
+ for (const cfg of subagents) {
460
+ if (cfg.sandbox && cfg.sandbox !== "none") {
461
+ agentSandboxOps.set(cfg.agentName, cfg.sandbox.proxy(cfg.agentName));
462
+ }
463
+ }
415
464
  const pendingDestroys = /* @__PURE__ */ new Map();
416
465
  const threadSandboxes = /* @__PURE__ */ new Map();
417
466
  const persistentSandboxes = /* @__PURE__ */ new Map();
418
467
  const persistentSandboxCreating = /* @__PURE__ */ new Set();
419
468
  const lazyCreatorAgent = /* @__PURE__ */ new Map();
420
- setHandler(childResultSignal, ({ childWorkflowId, result }) => {
421
- childResults.set(childWorkflowId, result);
422
- });
423
- setHandler(
424
- childSandboxReadySignal,
425
- ({ childWorkflowId, sandboxId }) => {
426
- const agentName = lazyCreatorAgent.get(childWorkflowId);
427
- if (agentName && !persistentSandboxes.has(agentName)) {
428
- persistentSandboxes.set(agentName, sandboxId);
429
- lazyCreatorAgent.delete(childWorkflowId);
430
- }
469
+ const threadSnapshots = /* @__PURE__ */ new Map();
470
+ const persistentBaseSnapshot = /* @__PURE__ */ new Map();
471
+ const persistentBaseSnapshotCreating = /* @__PURE__ */ new Set();
472
+ setHandler(childSandboxReadySignal, ({ childWorkflowId, sandboxId }) => {
473
+ const agentName = lazyCreatorAgent.get(childWorkflowId);
474
+ if (agentName && !persistentSandboxes.has(agentName)) {
475
+ persistentSandboxes.set(agentName, sandboxId);
476
+ lazyCreatorAgent.delete(childWorkflowId);
431
477
  }
432
- );
478
+ });
433
479
  const handler = async (args, context) => {
434
480
  const config = subagents.find((s) => s.agentName === args.subagent);
435
481
  if (!config) {
@@ -440,6 +486,12 @@ function createSubagentHandler(subagents) {
440
486
  const childWorkflowId = `${args.subagent}-${getShortId()}`;
441
487
  const { sandboxId: parentSandboxId } = context;
442
488
  const sandboxCfg = resolveSandboxConfig(config.sandbox);
489
+ if (sandboxCfg.source !== "none" && !agentSandboxOps.has(config.agentName)) {
490
+ throw ApplicationFailure.create({
491
+ message: `Subagent "${config.agentName}" uses a sandbox but no \`sandbox.proxy\` is configured on its SubagentConfig`,
492
+ nonRetryable: true
493
+ });
494
+ }
443
495
  if (sandboxCfg.source === "inherit" && !parentSandboxId) {
444
496
  throw new Error(
445
497
  `Subagent "${config.agentName}" is configured with sandbox: "inherit" but the parent has no sandbox`
@@ -458,12 +510,39 @@ function createSubagentHandler(subagents) {
458
510
  let sandbox;
459
511
  let sandboxShutdownOverride;
460
512
  let isLazyCreator = false;
513
+ let isSnapshotBaseCreator = false;
461
514
  if (sandboxCfg.source === "inherit" && parentSandboxId) {
462
515
  if (sandboxCfg.continuation === "fork") {
463
516
  sandbox = { mode: "fork", sandboxId: parentSandboxId };
517
+ } else if (sandboxCfg.continuation === "snapshot") {
518
+ throw new Error(
519
+ `Subagent "${config.agentName}" has sandbox source "inherit" with continuation "snapshot" \u2014 snapshot continuation is only supported for source "own"`
520
+ );
464
521
  } else {
465
522
  sandbox = { mode: "inherit", sandboxId: parentSandboxId };
466
523
  }
524
+ } else if (sandboxCfg.source === "own" && sandboxCfg.continuation === "snapshot") {
525
+ const isLazy = sandboxCfg.init === "once";
526
+ let baseSnap;
527
+ if (continuationThreadId) {
528
+ baseSnap = threadSnapshots.get(continuationThreadId)?.snapshot;
529
+ }
530
+ if (!baseSnap && isLazy) {
531
+ baseSnap = persistentBaseSnapshot.get(config.agentName);
532
+ if (!baseSnap) {
533
+ if (persistentBaseSnapshotCreating.has(config.agentName)) {
534
+ await condition(() => persistentBaseSnapshot.has(config.agentName));
535
+ baseSnap = persistentBaseSnapshot.get(config.agentName);
536
+ } else {
537
+ persistentBaseSnapshotCreating.add(config.agentName);
538
+ isSnapshotBaseCreator = true;
539
+ }
540
+ }
541
+ }
542
+ if (baseSnap) {
543
+ sandbox = { mode: "from-snapshot", snapshot: baseSnap };
544
+ }
545
+ sandboxShutdownOverride = "snapshot";
467
546
  } else if (sandboxCfg.source === "own") {
468
547
  const isLazy = sandboxCfg.init === "once";
469
548
  let baseSandboxId;
@@ -514,31 +593,8 @@ function createSubagentHandler(subagents) {
514
593
  threadMode,
515
594
  sandboxSource: sandboxCfg.source
516
595
  });
517
- const childHandle = await startChild(config.workflow, childOpts);
596
+ const childResult = await executeChild(config.workflow, childOpts);
518
597
  const effectiveShutdown = sandboxShutdownOverride ?? sandboxCfg.shutdown ?? "destroy";
519
- if (effectiveShutdown === "pause-until-parent-close" || effectiveShutdown === "keep-until-parent-close") {
520
- const key = isLazyCreator ? `persistent:${config.agentName}` : childWorkflowId;
521
- pendingDestroys.set(key, childHandle);
522
- }
523
- await Promise.race([
524
- condition(() => childResults.has(childWorkflowId)),
525
- childHandle.result()
526
- ]);
527
- if (!childResults.has(childWorkflowId)) {
528
- await condition(() => childResults.has(childWorkflowId));
529
- }
530
- const childResult = childResults.get(childWorkflowId);
531
- childResults.delete(childWorkflowId);
532
- if (!childResult) {
533
- log.warn("subagent returned no result", {
534
- subagent: config.agentName,
535
- childWorkflowId
536
- });
537
- return {
538
- toolResponse: "Subagent workflow did not signal a result",
539
- data: null
540
- };
541
- }
542
598
  log.info("subagent completed", {
543
599
  subagent: config.agentName,
544
600
  childWorkflowId,
@@ -550,19 +606,42 @@ function createSubagentHandler(subagents) {
550
606
  usage,
551
607
  threadId: childThreadId,
552
608
  sandboxId: childSandboxId,
609
+ snapshot: childSnapshot,
610
+ baseSnapshot: childBaseSnapshot,
553
611
  metadata
554
612
  } = childResult;
555
613
  if (childSandboxId) {
556
- if (sandboxCfg.source === "own" && sandboxCfg.init === "once" && !persistentSandboxes.has(config.agentName)) {
614
+ if (sandboxCfg.source === "own" && sandboxCfg.init === "once" && sandboxCfg.continuation !== "snapshot" && !persistentSandboxes.has(config.agentName)) {
557
615
  persistentSandboxes.set(config.agentName, childSandboxId);
558
- } else if (allowsContinuation && childThreadId) {
616
+ } else if (allowsContinuation && childThreadId && sandboxCfg.source === "own" && sandboxCfg.continuation !== "snapshot") {
559
617
  threadSandboxes.set(childThreadId, childSandboxId);
560
618
  }
561
619
  }
620
+ if (childSandboxId && (effectiveShutdown === "pause-until-parent-close" || effectiveShutdown === "keep-until-parent-close")) {
621
+ const key = isLazyCreator ? `persistent:${config.agentName}` : childWorkflowId;
622
+ pendingDestroys.set(key, {
623
+ agentName: config.agentName,
624
+ sandboxId: childSandboxId
625
+ });
626
+ }
627
+ if (sandboxCfg.source === "own" && sandboxCfg.continuation === "snapshot") {
628
+ if (childSnapshot && childThreadId) {
629
+ threadSnapshots.set(childThreadId, {
630
+ agentName: config.agentName,
631
+ snapshot: childSnapshot
632
+ });
633
+ }
634
+ if (isSnapshotBaseCreator && childBaseSnapshot && !persistentBaseSnapshot.has(config.agentName)) {
635
+ persistentBaseSnapshot.set(config.agentName, childBaseSnapshot);
636
+ }
637
+ }
562
638
  if (isLazyCreator) {
563
639
  persistentSandboxCreating.delete(config.agentName);
564
640
  lazyCreatorAgent.delete(childWorkflowId);
565
641
  }
642
+ if (isSnapshotBaseCreator) {
643
+ persistentBaseSnapshotCreating.delete(config.agentName);
644
+ }
566
645
  if (!toolResponse) {
567
646
  return {
568
647
  toolResponse: "Subagent workflow returned no response",
@@ -598,22 +677,60 @@ function createSubagentHandler(subagents) {
598
677
  };
599
678
  };
600
679
  const destroySubagentSandboxes = async () => {
601
- const handles = [...pendingDestroys.values()];
680
+ const entries = [...pendingDestroys.values()];
602
681
  pendingDestroys.clear();
603
682
  await Promise.all(
604
- handles.map(async (handle) => {
683
+ entries.map(async ({ agentName, sandboxId }) => {
684
+ const ops = agentSandboxOps.get(agentName);
685
+ if (!ops) {
686
+ log.warn(
687
+ "Skipping sandbox destroy \u2014 no sandbox.proxy registered for agent",
688
+ { agentName, sandboxId }
689
+ );
690
+ return;
691
+ }
692
+ try {
693
+ await ops.destroySandbox(sandboxId);
694
+ } catch (err) {
695
+ log.warn("Failed to destroy subagent sandbox", {
696
+ agentName,
697
+ sandboxId,
698
+ error: err
699
+ });
700
+ }
701
+ })
702
+ );
703
+ };
704
+ const cleanupSubagentSnapshots = async () => {
705
+ const tagged = [];
706
+ for (const entry of threadSnapshots.values()) tagged.push(entry);
707
+ for (const [agentName, snapshot] of persistentBaseSnapshot.entries()) {
708
+ tagged.push({ agentName, snapshot });
709
+ }
710
+ threadSnapshots.clear();
711
+ persistentBaseSnapshot.clear();
712
+ await Promise.all(
713
+ tagged.map(async ({ agentName, snapshot }) => {
714
+ const ops = agentSandboxOps.get(agentName);
715
+ if (!ops) {
716
+ log.warn(
717
+ "Skipping snapshot delete \u2014 no sandbox.proxy registered for agent",
718
+ { agentName }
719
+ );
720
+ return;
721
+ }
605
722
  try {
606
- await handle.signal(destroySandboxSignal);
607
- await handle.result();
723
+ await ops.deleteSandboxSnapshot(snapshot);
608
724
  } catch (err) {
609
- log.warn("Failed to signal destroySandbox to child workflow", {
725
+ log.warn("Failed to delete subagent snapshot", {
726
+ agentName,
610
727
  error: err
611
728
  });
612
729
  }
613
730
  })
614
731
  );
615
732
  };
616
- return { handler, destroySubagentSandboxes };
733
+ return { handler, destroySubagentSandboxes, cleanupSubagentSnapshots };
617
734
  }
618
735
 
619
736
  // src/lib/subagent/register.ts
@@ -627,7 +744,7 @@ function buildSubagentRegistration(subagents) {
627
744
  if (s.hooks) subagentHooksMap.set(s.agentName, s.hooks);
628
745
  }
629
746
  const resolveSubagentName = (args) => args.subagent;
630
- const { handler, destroySubagentSandboxes } = createSubagentHandler(subagents);
747
+ const { handler, destroySubagentSandboxes, cleanupSubagentSnapshots } = createSubagentHandler(subagents);
631
748
  const registration = {
632
749
  name: SUBAGENT_TOOL_NAME,
633
750
  enabled: () => getEnabled().length > 0,
@@ -651,7 +768,7 @@ function buildSubagentRegistration(subagents) {
651
768
  }
652
769
  }
653
770
  };
654
- return { registration, destroySubagentSandboxes };
771
+ return { registration, destroySubagentSandboxes, cleanupSubagentSnapshots };
655
772
  }
656
773
  var READ_SKILL_TOOL_NAME = "ReadSkill";
657
774
  function buildReadSkillDescription(skills) {
@@ -724,9 +841,7 @@ function validateSkillNames(skills) {
724
841
  const names = skills.map((s) => s.name);
725
842
  const dupes = names.filter((n, i) => names.indexOf(n) !== i);
726
843
  if (dupes.length > 0) {
727
- throw new Error(
728
- `Duplicate skill names: ${[...new Set(dupes)].join(", ")}`
729
- );
844
+ throw new Error(`Duplicate skill names: ${[...new Set(dupes)].join(", ")}`);
730
845
  }
731
846
  }
732
847
  function buildSkillRegistration(skills) {
@@ -792,15 +907,18 @@ async function createSession({
792
907
  initializeThread,
793
908
  appendSystemMessage,
794
909
  appendAgentMessage,
795
- forkThread
910
+ forkThread,
911
+ truncateThread
796
912
  } = threadOps;
797
913
  const plugins = [];
798
914
  let destroySubagentSandboxes;
915
+ let cleanupSubagentSnapshots;
799
916
  if (subagents) {
800
917
  const result = buildSubagentRegistration(subagents);
801
918
  if (result) {
802
919
  plugins.push(result.registration);
803
920
  destroySubagentSandboxes = result.destroySubagentSandboxes;
921
+ cleanupSubagentSnapshots = result.cleanupSubagentSnapshots;
804
922
  }
805
923
  }
806
924
  if (skills) {
@@ -853,6 +971,9 @@ async function createSession({
853
971
  const sandboxMode = sandboxInit?.mode;
854
972
  let sandboxId;
855
973
  let sandboxOwned = false;
974
+ let baseSnapshot;
975
+ let exitSnapshot;
976
+ let freshlyCreated = false;
856
977
  if (sandboxMode === "inherit") {
857
978
  const inheritInit = sandboxInit;
858
979
  sandboxId = inheritInit.sandboxId;
@@ -881,8 +1002,23 @@ async function createSession({
881
1002
  nonRetryable: true
882
1003
  });
883
1004
  }
1005
+ const forkInit = sandboxInit;
884
1006
  sandboxId = await sandboxOps.forkSandbox(
885
- sandboxInit.sandboxId
1007
+ forkInit.sandboxId,
1008
+ forkInit.options
1009
+ );
1010
+ sandboxOwned = true;
1011
+ } else if (sandboxMode === "from-snapshot") {
1012
+ if (!sandboxOps) {
1013
+ throw ApplicationFailure.create({
1014
+ message: "No sandboxOps provided \u2014 cannot restore sandbox",
1015
+ nonRetryable: true
1016
+ });
1017
+ }
1018
+ const restoreInit = sandboxInit;
1019
+ sandboxId = await sandboxOps.restoreSandbox(
1020
+ restoreInit.snapshot,
1021
+ restoreInit.options
886
1022
  );
887
1023
  sandboxOwned = true;
888
1024
  } else if (sandboxOps) {
@@ -893,9 +1029,13 @@ async function createSession({
893
1029
  if (result) {
894
1030
  sandboxId = result.sandboxId;
895
1031
  sandboxOwned = true;
1032
+ freshlyCreated = true;
896
1033
  }
897
1034
  }
898
- if (sandboxId && onSandboxReady) {
1035
+ if (sandboxId && sandboxOwned && freshlyCreated && sandboxShutdown === "snapshot" && sandboxOps) {
1036
+ baseSnapshot = await sandboxOps.snapshotSandbox(sandboxId);
1037
+ }
1038
+ if (sandboxId && sandboxOwned && onSandboxReady) {
899
1039
  onSandboxReady(sandboxId);
900
1040
  }
901
1041
  if (virtualFsConfig) {
@@ -961,18 +1101,25 @@ async function createSession({
961
1101
  threadKey
962
1102
  );
963
1103
  let exitReason = "completed";
1104
+ let finalMessage = null;
964
1105
  try {
965
1106
  while (stateManager.isRunning() && !stateManager.isTerminal() && stateManager.getTurns() < maxTurns) {
966
1107
  stateManager.incrementTurns();
967
1108
  const currentTurn = stateManager.getTurns();
968
1109
  log.debug("turn started", { agentName, threadId, turn: currentTurn });
969
1110
  stateManager.setTools(toolRouter.getToolDefinitions());
970
- const { message, rawToolCalls, usage } = await runAgent({
1111
+ const {
1112
+ message,
1113
+ rawToolCalls,
1114
+ usage,
1115
+ threadLengthAtCall
1116
+ } = await runAgent({
971
1117
  threadId,
972
1118
  threadKey,
973
1119
  agentName,
974
1120
  metadata
975
1121
  });
1122
+ const preAssistantLength = threadLengthAtCall;
976
1123
  await appendAgentMessage(threadId, uuid4(), message, threadKey);
977
1124
  if (usage) {
978
1125
  stateManager.updateUsage(usage);
@@ -987,21 +1134,8 @@ async function createSession({
987
1134
  if (!toolRouter.hasTools() || rawToolCalls.length === 0) {
988
1135
  stateManager.complete();
989
1136
  exitReason = "completed";
990
- log.info("session ended", {
991
- agentName,
992
- threadId,
993
- exitReason,
994
- turns: currentTurn,
995
- durationMs: Date.now() - sessionStartMs,
996
- usage: stateManager.getTotalUsage()
997
- });
998
- return {
999
- threadId,
1000
- finalMessage: message,
1001
- exitReason,
1002
- usage: stateManager.getTotalUsage(),
1003
- sandboxId
1004
- };
1137
+ finalMessage = message;
1138
+ break;
1005
1139
  }
1006
1140
  const parsedToolCalls = [];
1007
1141
  for (const tc of rawToolCalls) {
@@ -1031,6 +1165,24 @@ async function createSession({
1031
1165
  stateManager.updateUsage(result.usage);
1032
1166
  }
1033
1167
  }
1168
+ const rewind = toolCallResults.rewind;
1169
+ if (rewind) {
1170
+ log.info("rewinding turn", {
1171
+ agentName,
1172
+ threadId,
1173
+ turn: currentTurn,
1174
+ toolCallId: rewind.toolCallId,
1175
+ toolName: rewind.toolName
1176
+ });
1177
+ if (preAssistantLength === void 0) {
1178
+ throw ApplicationFailure.create({
1179
+ message: "Rewind requested but runAgent did not report `threadLengthAtCall`; the adapter must populate it to support rewinds.",
1180
+ nonRetryable: true
1181
+ });
1182
+ }
1183
+ await truncateThread(threadId, preAssistantLength, threadKey);
1184
+ continue;
1185
+ }
1034
1186
  if (stateManager.getStatus() === "WAITING_FOR_INPUT") {
1035
1187
  const conditionMet = await condition(
1036
1188
  () => stateManager.getStatus() === "RUNNING",
@@ -1073,11 +1225,21 @@ async function createSession({
1073
1225
  case "pause-until-parent-close":
1074
1226
  await sandboxOps.pauseSandbox(sandboxId);
1075
1227
  break;
1228
+ case "keep":
1229
+ case "keep-until-parent-close":
1230
+ break;
1231
+ case "snapshot":
1232
+ exitSnapshot = await sandboxOps.snapshotSandbox(sandboxId);
1233
+ await sandboxOps.destroySandbox(sandboxId);
1234
+ break;
1076
1235
  }
1077
1236
  }
1078
1237
  if (destroySubagentSandboxes) {
1079
1238
  await destroySubagentSandboxes();
1080
1239
  }
1240
+ if (cleanupSubagentSnapshots) {
1241
+ await cleanupSubagentSnapshots();
1242
+ }
1081
1243
  }
1082
1244
  log.info("session ended", {
1083
1245
  agentName,
@@ -1085,14 +1247,18 @@ async function createSession({
1085
1247
  exitReason,
1086
1248
  turns: stateManager.getTurns(),
1087
1249
  durationMs: Date.now() - sessionStartMs,
1088
- usage: stateManager.getTotalUsage()
1250
+ usage: stateManager.getTotalUsage(),
1251
+ ...baseSnapshot && { hasBaseSnapshot: true },
1252
+ ...exitSnapshot && { hasExitSnapshot: true }
1089
1253
  });
1090
1254
  return {
1091
1255
  threadId,
1092
- finalMessage: null,
1256
+ finalMessage,
1093
1257
  exitReason,
1094
1258
  usage: stateManager.getTotalUsage(),
1095
- sandboxId
1259
+ sandboxId,
1260
+ ...baseSnapshot && { baseSnapshot },
1261
+ ...exitSnapshot && { snapshot: exitSnapshot }
1096
1262
  };
1097
1263
  }
1098
1264
  };
@@ -1303,44 +1469,16 @@ function defineSubagentWorkflow(config, fn) {
1303
1469
  ...workflowInput.thread && { thread: workflowInput.thread },
1304
1470
  ...workflowInput.sandbox && { sandbox: workflowInput.sandbox },
1305
1471
  onSandboxReady: (sandboxId) => {
1306
- void parentHandle.signal(childSandboxReadySignal, {
1307
- childWorkflowId: workflowInfo().workflowId,
1308
- sandboxId
1309
- });
1472
+ const isReuse = workflowInput.sandbox?.mode === "continue";
1473
+ if (!isReuse) {
1474
+ void parentHandle.signal(childSandboxReadySignal, {
1475
+ childWorkflowId: workflowInfo().workflowId,
1476
+ sandboxId
1477
+ });
1478
+ }
1310
1479
  }
1311
1480
  };
1312
- const { destroySandbox, ...result } = await fn(
1313
- prompt,
1314
- sessionInput,
1315
- context ?? {}
1316
- );
1317
- if (effectiveShutdown === "pause-until-parent-close" || effectiveShutdown === "keep-until-parent-close") {
1318
- if (!destroySandbox) {
1319
- throw ApplicationFailure.create({
1320
- message: `Subagent "${config.name}" has sandboxShutdown="${effectiveShutdown}" but fn did not return a destroySandbox callback`,
1321
- nonRetryable: true
1322
- });
1323
- }
1324
- if (!result.sandboxId) {
1325
- throw ApplicationFailure.create({
1326
- message: `Subagent "${config.name}" has sandboxShutdown="${effectiveShutdown}" but fn did not return a sandboxId`,
1327
- nonRetryable: true
1328
- });
1329
- }
1330
- }
1331
- await parentHandle.signal(childResultSignal, {
1332
- childWorkflowId: workflowInfo().workflowId,
1333
- result
1334
- });
1335
- if (destroySandbox) {
1336
- let destroyRequested = false;
1337
- setHandler(destroySandboxSignal, () => {
1338
- destroyRequested = true;
1339
- });
1340
- await condition(() => destroyRequested);
1341
- await destroySandbox();
1342
- }
1343
- return result;
1481
+ return fn(prompt, sessionInput, context ?? {});
1344
1482
  };
1345
1483
  Object.defineProperty(workflow, "name", { value: config.name });
1346
1484
  return Object.assign(workflow, {
@@ -1450,9 +1588,7 @@ function applyVirtualTreeMutations(stateManager, mutations) {
1450
1588
  tree = tree.filter((e) => e.path !== m.path);
1451
1589
  break;
1452
1590
  case "update":
1453
- tree = tree.map(
1454
- (e) => e.path === m.path ? { ...e, ...m.entry } : e
1455
- );
1591
+ tree = tree.map((e) => e.path === m.path ? { ...e, ...m.entry } : e);
1456
1592
  break;
1457
1593
  }
1458
1594
  }
@@ -1511,7 +1647,9 @@ function formatVirtualFileTree(entries, opts = {}) {
1511
1647
  // src/lib/virtual-fs/queries.ts
1512
1648
  function hasFileWithMimeType(stateManager, pattern) {
1513
1649
  const tree = stateManager.get("fileTree");
1514
- const matchers = (Array.isArray(pattern) ? pattern : [pattern]).map(buildMatcher);
1650
+ const matchers = (Array.isArray(pattern) ? pattern : [pattern]).map(
1651
+ buildMatcher
1652
+ );
1515
1653
  return tree.some((entry) => {
1516
1654
  const meta = entry.metadata;
1517
1655
  const mime = meta?.mimeType;
@@ -1572,7 +1710,9 @@ function proxyVirtualFsOps(scope, options) {
1572
1710
  // src/lib/skills/parse.ts
1573
1711
  function parseSkillFile(raw) {
1574
1712
  const trimmed = raw.replace(/^\uFEFF/, "");
1575
- const match = trimmed.match(/^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*\r?\n?([\s\S]*)$/);
1713
+ const match = trimmed.match(
1714
+ /^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*\r?\n?([\s\S]*)$/
1715
+ );
1576
1716
  if (!match) {
1577
1717
  throw new Error(
1578
1718
  "SKILL.md must start with YAML frontmatter delimited by ---"
@@ -1844,7 +1984,9 @@ function createTaskGetHandler(stateManager) {
1844
1984
  const task = stateManager.getTask(args.taskId) ?? null;
1845
1985
  if (!task) {
1846
1986
  return {
1847
- toolResponse: JSON.stringify({ error: `Task not found: ${args.taskId}` }),
1987
+ toolResponse: JSON.stringify({
1988
+ error: `Task not found: ${args.taskId}`
1989
+ }),
1848
1990
  data: null
1849
1991
  };
1850
1992
  }
@@ -1887,7 +2029,9 @@ function createTaskUpdateHandler(stateManager) {
1887
2029
  const task = stateManager.getTask(args.taskId);
1888
2030
  if (!task) {
1889
2031
  return {
1890
- toolResponse: JSON.stringify({ error: `Task not found: ${args.taskId}` }),
2032
+ toolResponse: JSON.stringify({
2033
+ error: `Task not found: ${args.taskId}`
2034
+ }),
1891
2035
  data: null
1892
2036
  };
1893
2037
  }