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
@@ -15,12 +15,19 @@ import type {
15
15
  ToolArgs,
16
16
  ToolResult,
17
17
  ProcessToolCallsContext,
18
+ ProcessToolCallsResult,
19
+ RewindSignal,
18
20
  ToolWithHandler,
19
21
  } from "./types";
20
22
 
21
23
  import type { JsonValue } from "../state/types";
22
24
  import type { z } from "zod";
23
- import { uuid4, log } from "@temporalio/workflow";
25
+ import {
26
+ uuid4,
27
+ log,
28
+ CancellationScope,
29
+ isCancellation,
30
+ } from "@temporalio/workflow";
24
31
 
25
32
  /**
26
33
  * Creates a tool router for declarative tool call processing.
@@ -162,7 +169,8 @@ export function createToolRouter<T extends ToolMap>(
162
169
 
163
170
  return {
164
171
  content: JSON.stringify({
165
- error: "The tool encountered an error. Please try again or use a different approach.",
172
+ error:
173
+ "The tool encountered an error. Please try again or use a different approach.",
166
174
  }),
167
175
  result: { error: errorStr, suppressed: true },
168
176
  };
@@ -198,11 +206,22 @@ export function createToolRouter<T extends ToolMap>(
198
206
  }
199
207
  }
200
208
 
209
+ /**
210
+ * Internal per-tool-call outcome. `rewind` signals the caller that the
211
+ * handler requested a session-level rewind; when present, the result is
212
+ * not appended to the thread and siblings should be cancelled.
213
+ */
214
+ type ProcessedToolCall =
215
+ | { kind: "result"; value: ToolCallResultUnion<TResults> }
216
+ | { kind: "rewind"; signal: RewindSignal }
217
+ | { kind: "skipped" };
218
+
201
219
  async function processToolCall(
202
220
  toolCall: ParsedToolCallUnion<T>,
203
221
  turn: number,
204
222
  sandboxId?: string,
205
- ): Promise<ToolCallResultUnion<TResults> | null> {
223
+ onRewindRequested?: (signal: RewindSignal) => void
224
+ ): Promise<ProcessedToolCall> {
206
225
  const startTime = Date.now();
207
226
  const tool = toolMap.get(toolCall.name);
208
227
 
@@ -219,7 +238,7 @@ export function createToolRouter<T extends ToolMap>(
219
238
  reason: "Skipped by PreToolUse hook",
220
239
  }),
221
240
  });
222
- return null;
241
+ return { kind: "skipped" };
223
242
  }
224
243
  const effectiveArgs = preResult.args;
225
244
 
@@ -234,6 +253,7 @@ export function createToolRouter<T extends ToolMap>(
234
253
  let content!: JsonValue;
235
254
  let resultAppended = false;
236
255
  let metadata: Record<string, unknown> | undefined;
256
+ let rewindRequested = false;
237
257
 
238
258
  try {
239
259
  if (tool) {
@@ -252,11 +272,15 @@ export function createToolRouter<T extends ToolMap>(
252
272
  content = response.toolResponse as JsonValue;
253
273
  resultAppended = response.resultAppended === true;
254
274
  metadata = response.metadata;
275
+ rewindRequested = response.rewind === true;
255
276
  } else {
256
277
  result = { error: `Unknown tool: ${toolCall.name}` };
257
278
  content = JSON.stringify(result, null, 2);
258
279
  }
259
280
  } catch (error) {
281
+ if (isCancellation(error)) {
282
+ throw error;
283
+ }
260
284
  log.warn("tool call failed", {
261
285
  toolName: toolCall.name,
262
286
  toolCallId: toolCall.id,
@@ -275,6 +299,16 @@ export function createToolRouter<T extends ToolMap>(
275
299
  content = recovery.content;
276
300
  }
277
301
 
302
+ if (rewindRequested) {
303
+ const signal: RewindSignal = {
304
+ toolCallId: toolCall.id,
305
+ toolName: toolCall.name,
306
+ };
307
+ log.info("tool requested rewind", { ...signal });
308
+ onRewindRequested?.(signal);
309
+ return { kind: "rewind", signal };
310
+ }
311
+
278
312
  // --- Append result to thread (unless handler already did) ---
279
313
  if (!resultAppended) {
280
314
  const config = {
@@ -318,7 +352,7 @@ export function createToolRouter<T extends ToolMap>(
318
352
  durationMs
319
353
  );
320
354
 
321
- return toolResult;
355
+ return { kind: "result", value: toolResult };
322
356
  }
323
357
 
324
358
  return {
@@ -368,31 +402,71 @@ export function createToolRouter<T extends ToolMap>(
368
402
  async processToolCalls(
369
403
  toolCalls: ParsedToolCallUnion<T>[],
370
404
  context?: ProcessToolCallsContext
371
- ): Promise<ToolCallResultUnion<TResults>[]> {
405
+ ): Promise<ProcessToolCallsResult<TResults>> {
406
+ const attachRewind = (
407
+ arr: ToolCallResultUnion<TResults>[],
408
+ rewind: RewindSignal | undefined,
409
+ ): ProcessToolCallsResult<TResults> => {
410
+ if (rewind) {
411
+ (arr as ProcessToolCallsResult<TResults>).rewind = rewind;
412
+ }
413
+ return arr as ProcessToolCallsResult<TResults>;
414
+ };
415
+
372
416
  if (toolCalls.length === 0) {
373
- return [];
417
+ return attachRewind([], undefined);
374
418
  }
375
419
 
376
420
  const turn = context?.turn ?? 0;
377
421
  const sandboxId = context?.sandboxId;
378
422
 
423
+ let rewindSignal: RewindSignal | undefined;
424
+
379
425
  if (options.parallel) {
380
- const results = await Promise.all(
381
- toolCalls.map((tc) => processToolCall(tc, turn, sandboxId))
426
+ const scope = new CancellationScope({ cancellable: true });
427
+ const onRewindRequested = (signal: RewindSignal): void => {
428
+ if (!rewindSignal) {
429
+ rewindSignal = signal;
430
+ // Cancel all other in-flight tool calls in this batch.
431
+ scope.cancel();
432
+ }
433
+ };
434
+
435
+ const outcomes = await scope.run(async () =>
436
+ Promise.allSettled(
437
+ toolCalls.map((tc) =>
438
+ processToolCall(tc, turn, sandboxId, onRewindRequested)
439
+ )
440
+ )
382
441
  );
383
- return results.filter(
384
- (r): r is NonNullable<typeof r> => r !== null
385
- ) as ToolCallResultUnion<TResults>[];
442
+
443
+ const results: ToolCallResultUnion<TResults>[] = [];
444
+ for (const outcome of outcomes) {
445
+ if (outcome.status === "rejected") {
446
+ if (isCancellation(outcome.reason)) {
447
+ continue;
448
+ }
449
+ throw outcome.reason;
450
+ }
451
+ if (outcome.value.kind === "result") {
452
+ results.push(outcome.value.value);
453
+ }
454
+ }
455
+ return attachRewind(results, rewindSignal);
386
456
  }
387
457
 
388
458
  const results: ToolCallResultUnion<TResults>[] = [];
389
459
  for (const toolCall of toolCalls) {
390
- const result = await processToolCall(toolCall, turn, sandboxId);
391
- if (result !== null) {
392
- results.push(result);
460
+ const outcome = await processToolCall(toolCall, turn, sandboxId);
461
+ if (outcome.kind === "rewind") {
462
+ rewindSignal = outcome.signal;
463
+ break;
464
+ }
465
+ if (outcome.kind === "result") {
466
+ results.push(outcome.value);
393
467
  }
394
468
  }
395
- return results;
469
+ return attachRewind(results, rewindSignal);
396
470
  },
397
471
 
398
472
  async processToolCallsByName<TName extends ToolNames<T>, TResult>(
@@ -124,7 +124,10 @@ export type AppendToolResultFn = ActivityFunctionWithOptions<
124
124
  * Tools that don't return additional data should use `data: null` (TResult defaults to null).
125
125
  * Tools that may fail to produce data should type TResult as `SomeType | null`.
126
126
  */
127
- export interface ToolHandlerResponse<TResult = null, TToolResponse = JsonValue> {
127
+ export interface ToolHandlerResponse<
128
+ TResult = null,
129
+ TToolResponse = JsonValue,
130
+ > {
128
131
  /** Content sent back to the LLM as the tool call response */
129
132
  toolResponse: TToolResponse;
130
133
  /** Data returned to the workflow and hooks for further processing */
@@ -136,6 +139,17 @@ export interface ToolHandlerResponse<TResult = null, TToolResponse = JsonValue>
136
139
  * payloads through Temporal's activity payload limit.
137
140
  */
138
141
  resultAppended?: boolean;
142
+ /**
143
+ * When true, the session will rewind: any in-flight parallel tool calls
144
+ * are cancelled, previously appended tool results and the triggering
145
+ * assistant message are removed from the thread, and the LLM call that
146
+ * produced the tool calls is retried.
147
+ *
148
+ * The `toolResponse` for a rewinding tool call is ignored (never
149
+ * appended) since the session truncates the thread back to the
150
+ * pre-invocation state.
151
+ */
152
+ rewind?: boolean;
139
153
  /** Token usage from the tool execution (e.g. child agent invocations) */
140
154
  usage?: TokenUsage;
141
155
  /** Thread ID used by the handler (surfaced to the LLM for subagent thread continuation) */
@@ -172,7 +186,9 @@ export type ToolHandler<
172
186
  > = (
173
187
  args: TArgs,
174
188
  context: TContext
175
- ) => ToolHandlerResponse<TResult, TToolResponse> | Promise<ToolHandlerResponse<TResult, TToolResponse>>;
189
+ ) =>
190
+ | ToolHandlerResponse<TResult, TToolResponse>
191
+ | Promise<ToolHandlerResponse<TResult, TToolResponse>>;
176
192
 
177
193
  /**
178
194
  * Activity-compatible tool handler that always returns a Promise.
@@ -195,7 +211,10 @@ export type ActivityToolHandler<
195
211
  TResult,
196
212
  TContext extends RouterContext = RouterContext,
197
213
  TToolResponse = JsonValue,
198
- > = (args: TArgs, context: TContext) => Promise<ToolHandlerResponse<TResult, TToolResponse>>;
214
+ > = (
215
+ args: TArgs,
216
+ context: TContext
217
+ ) => Promise<ToolHandlerResponse<TResult, TToolResponse>>;
199
218
 
200
219
  /**
201
220
  * Extract the args type for a specific tool name from a tool map.
@@ -270,6 +289,28 @@ export interface ProcessToolCallsContext {
270
289
  sandboxId?: string;
271
290
  }
272
291
 
292
+ /**
293
+ * Signal that a tool handler requested a rewind. Attached to the
294
+ * {@link ProcessToolCallsResult} so the session can roll the thread
295
+ * back to the pre-invocation snapshot and retry the LLM call.
296
+ */
297
+ export interface RewindSignal {
298
+ toolCallId: string;
299
+ toolName: string;
300
+ }
301
+
302
+ /**
303
+ * Result returned by {@link ToolRouter.processToolCalls}.
304
+ *
305
+ * The object is a standard array of tool call results for successful
306
+ * tool calls (cancelled or rewinding siblings are omitted), extended
307
+ * with a `rewind` property when any tool in the batch requested a
308
+ * rewind. Using an array-with-property lets existing code that treats
309
+ * the return value as `ToolCallResultUnion[]` continue to work.
310
+ */
311
+ export type ProcessToolCallsResult<TResults extends Record<string, unknown>> =
312
+ ToolCallResultUnion<TResults>[] & { rewind?: RewindSignal };
313
+
273
314
  // ============================================================================
274
315
  // Hook Types
275
316
  // ============================================================================
@@ -461,7 +502,7 @@ export interface ToolRouter<T extends ToolMap> {
461
502
  processToolCalls(
462
503
  toolCalls: ParsedToolCallUnion<T>[],
463
504
  context?: ProcessToolCallsContext
464
- ): Promise<ToolCallResultUnion<InferToolResults<T>>[]>;
505
+ ): Promise<ProcessToolCallsResult<InferToolResults<T>>>;
465
506
 
466
507
  /**
467
508
  * Process tool calls matching a specific name with a custom handler.
@@ -1,6 +1,10 @@
1
1
  import type { Sandbox } from "../sandbox/types";
2
2
  import type { JsonValue } from "../state/types";
3
- import type { ActivityToolHandler, RouterContext, ToolHandlerResponse } from "./types";
3
+ import type {
4
+ ActivityToolHandler,
5
+ RouterContext,
6
+ ToolHandlerResponse,
7
+ } from "./types";
4
8
 
5
9
  /**
6
10
  * Extended router context with a resolved {@link Sandbox} instance.
@@ -51,13 +55,23 @@ export interface SandboxContext extends RouterContext {
51
55
  * });
52
56
  * ```
53
57
  */
54
- export function withSandbox<TArgs, TResult, TSandbox extends Sandbox = Sandbox, TToolResponse = JsonValue>(
58
+ export function withSandbox<
59
+ TArgs,
60
+ TResult,
61
+ TSandbox extends Sandbox = Sandbox,
62
+ TToolResponse = JsonValue,
63
+ >(
55
64
  manager: { getSandbox(id: string): Promise<TSandbox> },
56
65
  handler: (
57
66
  args: TArgs,
58
- context: RouterContext & { sandbox: TSandbox; sandboxId: string },
59
- ) => Promise<ToolHandlerResponse<TResult, TToolResponse>>,
60
- ): ActivityToolHandler<TArgs, TResult | null, RouterContext, TToolResponse | string> {
67
+ context: RouterContext & { sandbox: TSandbox; sandboxId: string }
68
+ ) => Promise<ToolHandlerResponse<TResult, TToolResponse>>
69
+ ): ActivityToolHandler<
70
+ TArgs,
71
+ TResult | null,
72
+ RouterContext,
73
+ TToolResponse | string
74
+ > {
61
75
  return async (args, context) => {
62
76
  if (!context.sandboxId) {
63
77
  return {
@@ -67,7 +67,7 @@ export class VirtualFileSystem<
67
67
  private resolver: FileResolver<TCtx, TMeta>,
68
68
  private ctx: TCtx,
69
69
  workspaceBase = "/",
70
- inlineFiles?: Record<string, string>,
70
+ inlineFiles?: Record<string, string>
71
71
  ) {
72
72
  this.workspaceBase = normalisePath(workspaceBase);
73
73
  this.entries = new Map(
@@ -1,7 +1,11 @@
1
1
  export { VirtualFileSystem } from "./filesystem";
2
2
  export { withVirtualFs } from "./with-virtual-fs";
3
3
  export { createVirtualFsActivities } from "./manager";
4
- export { hasFileWithMimeType, filesWithMimeType, hasDirectory } from "./queries";
4
+ export {
5
+ hasFileWithMimeType,
6
+ filesWithMimeType,
7
+ hasDirectory,
8
+ } from "./queries";
5
9
  export { applyVirtualTreeMutations } from "./mutations";
6
10
  export { formatVirtualFileTree } from "./tree";
7
11
  export type { FileTreeAccessor } from "./queries";
@@ -13,7 +13,7 @@ export function applyVirtualTreeMutations<TMeta = FileEntryMetadata>(
13
13
  get(key: "fileTree"): VirtualFileTree<TMeta>;
14
14
  set(key: "fileTree", value: VirtualFileTree<TMeta>): void;
15
15
  },
16
- mutations: TreeMutation<TMeta>[],
16
+ mutations: TreeMutation<TMeta>[]
17
17
  ): VirtualFileTree<TMeta> {
18
18
  let tree = [...stateManager.get("fileTree")];
19
19
 
@@ -26,9 +26,7 @@ export function applyVirtualTreeMutations<TMeta = FileEntryMetadata>(
26
26
  tree = tree.filter((e) => e.path !== m.path);
27
27
  break;
28
28
  case "update":
29
- tree = tree.map((e) =>
30
- e.path === m.path ? { ...e, ...m.entry } : e
31
- );
29
+ tree = tree.map((e) => (e.path === m.path ? { ...e, ...m.entry } : e));
32
30
  break;
33
31
  }
34
32
  }
@@ -25,10 +25,12 @@ export interface FileTreeAccessor<TMeta> {
25
25
  */
26
26
  export function hasFileWithMimeType<TMeta>(
27
27
  stateManager: FileTreeAccessor<TMeta>,
28
- pattern: string | string[],
28
+ pattern: string | string[]
29
29
  ): boolean {
30
30
  const tree = stateManager.get("fileTree");
31
- const matchers = (Array.isArray(pattern) ? pattern : [pattern]).map(buildMatcher);
31
+ const matchers = (Array.isArray(pattern) ? pattern : [pattern]).map(
32
+ buildMatcher
33
+ );
32
34
  return tree.some((entry) => {
33
35
  const meta = entry.metadata as Record<string, unknown> | undefined;
34
36
  const mime = meta?.mimeType;
@@ -41,7 +43,7 @@ export function hasFileWithMimeType<TMeta>(
41
43
  */
42
44
  export function filesWithMimeType<TMeta>(
43
45
  stateManager: FileTreeAccessor<TMeta>,
44
- pattern: string,
46
+ pattern: string
45
47
  ): FileEntry<TMeta>[] {
46
48
  const tree = stateManager.get("fileTree");
47
49
  const match = buildMatcher(pattern);
@@ -66,7 +68,7 @@ export function filesWithMimeType<TMeta>(
66
68
  */
67
69
  export function hasDirectory<TMeta>(
68
70
  stateManager: FileTreeAccessor<TMeta>,
69
- pattern: string,
71
+ pattern: string
70
72
  ): boolean {
71
73
  const tree = stateManager.get("fileTree");
72
74
  const match = buildGlobMatcher(pattern);
@@ -91,7 +93,9 @@ function buildMatcher(pattern: string): (value: string) => boolean {
91
93
  function buildGlobMatcher(pattern: string): (value: string) => boolean {
92
94
  if (!pattern.includes("*")) return (v) => v === pattern;
93
95
  const re = new RegExp(
94
- "^" + pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") + "$",
96
+ "^" +
97
+ pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") +
98
+ "$"
95
99
  );
96
100
  return (v) => re.test(v);
97
101
  }
@@ -104,7 +104,10 @@ export type PrefixedVirtualFsOps<
104
104
  [K in keyof VirtualFsOps<
105
105
  TCtx,
106
106
  TMeta
107
- > as `virtualFs${Capitalize<TPrefix>}${Capitalize<K & string>}`]: VirtualFsOps<TCtx, TMeta>[K];
107
+ > as `virtualFs${Capitalize<TPrefix>}${Capitalize<K & string>}`]: VirtualFsOps<
108
+ TCtx,
109
+ TMeta
110
+ >[K];
108
111
  };
109
112
 
110
113
  // ============================================================================
@@ -53,7 +53,7 @@ function createMockResolver(): {
53
53
  id,
54
54
  typeof content === "string"
55
55
  ? content
56
- : new TextDecoder().decode(content),
56
+ : new TextDecoder().decode(content)
57
57
  );
58
58
  },
59
59
 
@@ -63,7 +63,7 @@ function createMockResolver(): {
63
63
  id,
64
64
  typeof content === "string"
65
65
  ? content
66
- : new TextDecoder().decode(content),
66
+ : new TextDecoder().decode(content)
67
67
  );
68
68
  const size =
69
69
  typeof content === "string"
@@ -286,9 +286,7 @@ describe("VirtualFileSystem", () => {
286
286
  });
287
287
 
288
288
  it("rm force does not throw for missing path", async () => {
289
- await expect(
290
- fs.rm("/nonexistent", { force: true }),
291
- ).resolves.not.toThrow();
289
+ await expect(fs.rm("/nonexistent", { force: true })).resolves.not.toThrow();
292
290
  });
293
291
 
294
292
  // --- cp / mv ---
@@ -320,7 +318,7 @@ describe("VirtualFileSystem", () => {
320
318
 
321
319
  it("readlink throws SandboxNotSupportedError", async () => {
322
320
  await expect(fs.readlink("/src/index.ts")).rejects.toThrow(
323
- SandboxNotSupportedError,
321
+ SandboxNotSupportedError
324
322
  );
325
323
  });
326
324
  });
@@ -349,7 +347,7 @@ describe("VirtualFileSystem — inlineFiles", () => {
349
347
  resolver,
350
348
  ctx,
351
349
  "/",
352
- inlineFiles,
350
+ inlineFiles
353
351
  );
354
352
  }
355
353
 
@@ -418,7 +416,9 @@ describe("applyVirtualTreeMutations", () => {
418
416
  let fileTree = tree;
419
417
  return {
420
418
  get: (_key: "fileTree"): FileEntry[] => fileTree,
421
- set: (_key: "fileTree", value: FileEntry[]): void => { fileTree = value; },
419
+ set: (_key: "fileTree", value: FileEntry[]): void => {
420
+ fileTree = value;
421
+ },
422
422
  current: (): FileEntry[] => fileTree,
423
423
  };
424
424
  }
@@ -490,9 +490,7 @@ describe("applyVirtualTreeMutations", () => {
490
490
  it("does not mutate the original array passed to the state manager", () => {
491
491
  const original = [...sampleTree];
492
492
  const sm = mockStateManager(sampleTree);
493
- applyVirtualTreeMutations(sm, [
494
- { type: "remove", path: "/src/index.ts" },
495
- ]);
493
+ applyVirtualTreeMutations(sm, [{ type: "remove", path: "/src/index.ts" }]);
496
494
  expect(sampleTree).toEqual(original);
497
495
  });
498
496
  });
@@ -50,10 +50,13 @@ describe("defineWorkflow", () => {
50
50
  return { ok: true };
51
51
  });
52
52
 
53
- await workflow({}, {
54
- thread: { mode: "fork", threadId: "prev-1" },
55
- sandbox: { mode: "continue", sandboxId: "sb-1" },
56
- });
53
+ await workflow(
54
+ {},
55
+ {
56
+ thread: { mode: "fork", threadId: "prev-1" },
57
+ sandbox: { mode: "continue", sandboxId: "sb-1" },
58
+ }
59
+ );
57
60
 
58
61
  expect(capturedSession).toEqual({
59
62
  agentName: "test-workflow",
@@ -1,8 +1,4 @@
1
- import type {
2
- ThreadInit,
3
- SandboxInit,
4
- SandboxShutdown,
5
- } from "./lifecycle";
1
+ import type { ThreadInit, SandboxInit, SandboxShutdown } from "./lifecycle";
6
2
 
7
3
  /**
8
4
  * Session config fields derived from a main workflow input, ready to spread
@@ -42,6 +42,4 @@ Usage notes:
42
42
  strict: true,
43
43
  } satisfies ToolDefinition;
44
44
 
45
- export type AskUserQuestionArgs = z.infer<
46
- typeof askUserQuestionTool.schema
47
- >;
45
+ export type AskUserQuestionArgs = z.infer<typeof askUserQuestionTool.schema>;
@@ -19,10 +19,7 @@ function matchGlob(pattern: string, path: string): boolean {
19
19
  return new RegExp(`^${regex}$`).test(path);
20
20
  }
21
21
 
22
- async function walk(
23
- fs: Sandbox["fs"],
24
- dir: string,
25
- ): Promise<string[]> {
22
+ async function walk(fs: Sandbox["fs"], dir: string): Promise<string[]> {
26
23
  const results: string[] = [];
27
24
  const entries = await fs.readdirWithFileTypes(dir);
28
25
  for (const entry of entries) {
@@ -1,7 +1,4 @@
1
- import type {
2
- AgentStateManager,
3
- JsonSerializable,
4
- } from "../../lib/state";
1
+ import type { AgentStateManager, JsonSerializable } from "../../lib/state";
5
2
  import type { ToolHandler } from "../../lib/tool-router";
6
3
  import type { WorkflowTask } from "../../lib/types";
7
4
  import type { TaskGetArgs } from "./tool";
@@ -20,7 +17,9 @@ export function createTaskGetHandler<TCustom extends JsonSerializable<TCustom>>(
20
17
 
21
18
  if (!task) {
22
19
  return {
23
- toolResponse: JSON.stringify({ error: `Task not found: ${args.taskId}` }),
20
+ toolResponse: JSON.stringify({
21
+ error: `Task not found: ${args.taskId}`,
22
+ }),
24
23
  data: null,
25
24
  };
26
25
  }
@@ -1,7 +1,4 @@
1
- import type {
2
- AgentStateManager,
3
- JsonSerializable,
4
- } from "../../lib/state";
1
+ import type { AgentStateManager, JsonSerializable } from "../../lib/state";
5
2
  import type { ToolHandler } from "../../lib/tool-router";
6
3
  import type { WorkflowTask } from "../../lib/types";
7
4
  import type { TaskListArgs } from "./tool";
@@ -1,7 +1,4 @@
1
- import type {
2
- AgentStateManager,
3
- JsonSerializable,
4
- } from "../../lib/state";
1
+ import type { AgentStateManager, JsonSerializable } from "../../lib/state";
5
2
  import type { ToolHandler } from "../../lib/tool-router";
6
3
  import type { WorkflowTask } from "../../lib/types";
7
4
  import type { TaskUpdateArgs } from "./tool";
@@ -22,7 +19,9 @@ export function createTaskUpdateHandler<
22
19
 
23
20
  if (!task) {
24
21
  return {
25
- toolResponse: JSON.stringify({ error: `Task not found: ${args.taskId}` }),
22
+ toolResponse: JSON.stringify({
23
+ error: `Task not found: ${args.taskId}`,
24
+ }),
26
25
  data: null,
27
26
  };
28
27
  }
package/src/workflow.ts CHANGED
@@ -15,12 +15,28 @@
15
15
 
16
16
  // Session
17
17
  export { createSession } from "./lib/session";
18
- export type { ZeitlichSession, SessionResult, ThreadOps, PrefixedThreadOps, ScopedPrefix, SessionConfig } from "./lib/session";
18
+ export type {
19
+ ZeitlichSession,
20
+ SessionResult,
21
+ ThreadOps,
22
+ PrefixedThreadOps,
23
+ ScopedPrefix,
24
+ SessionConfig,
25
+ } from "./lib/session";
19
26
  export { defineWorkflow } from "./lib/workflow";
20
- export type { WorkflowConfig, WorkflowInput, WorkflowSessionInput } from "./lib/workflow";
27
+ export type {
28
+ WorkflowConfig,
29
+ WorkflowInput,
30
+ WorkflowSessionInput,
31
+ } from "./lib/workflow";
21
32
 
22
33
  // Lifecycle types
23
- export type { ThreadInit, SandboxInit, SandboxShutdown, SubagentSandboxShutdown } from "./lib/lifecycle";
34
+ export type {
35
+ ThreadInit,
36
+ SandboxInit,
37
+ SandboxShutdown,
38
+ SubagentSandboxShutdown,
39
+ } from "./lib/lifecycle";
24
40
 
25
41
  // Thread utilities
26
42
  export { getShortId } from "./lib/thread/id";
@@ -78,6 +94,8 @@ export type {
78
94
  // Other
79
95
  AppendToolResultFn,
80
96
  ProcessToolCallsContext,
97
+ ProcessToolCallsResult,
98
+ RewindSignal,
81
99
  } from "./lib/tool-router";
82
100
 
83
101
  // Session & message lifecycle hooks
@@ -94,10 +112,7 @@ export type {
94
112
  } from "./lib/hooks";
95
113
 
96
114
  // Observability
97
- export {
98
- createObservabilityHooks,
99
- composeHooks,
100
- } from "./lib/observability";
115
+ export { createObservabilityHooks, composeHooks } from "./lib/observability";
101
116
  export type {
102
117
  ObservabilityHooks,
103
118
  ZeitlichObservabilitySinks,