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
@@ -6,9 +6,10 @@ export const SUBAGENT_TOOL_NAME = "Subagent" as const;
6
6
  function buildSubagentDescription(subagents: SubagentConfig[]): string {
7
7
  const subagentList = subagents
8
8
  .map((s) => {
9
- const continuation = s.thread && s.thread !== "new"
10
- ? "\n*(Supports thread continuation — pass a threadId to resume a previous conversation)*"
11
- : "";
9
+ const continuation =
10
+ s.thread && s.thread !== "new"
11
+ ? "\n*(Supports thread continuation — pass a threadId to resume a previous conversation)*"
12
+ : "";
12
13
  return `## ${s.agentName}\n${s.description}${continuation}`;
13
14
  })
14
15
  .join("\n\n");
@@ -10,10 +10,24 @@ import type {
10
10
  SandboxInit,
11
11
  SubagentSandboxShutdown,
12
12
  } from "../lifecycle";
13
+ import type { SandboxOps, SandboxSnapshot } from "../sandbox/types";
13
14
 
14
15
  /** ToolHandlerResponse with threadId required (subagents must always surface their thread) */
15
- export type SubagentHandlerResponse<TResult = null, TToolResponse = JsonValue> =
16
- ToolHandlerResponse<TResult, TToolResponse> & { threadId: string; sandboxId?: string };
16
+ export type SubagentHandlerResponse<
17
+ TResult = null,
18
+ TToolResponse = JsonValue,
19
+ > = ToolHandlerResponse<TResult, TToolResponse> & {
20
+ threadId: string;
21
+ sandboxId?: string;
22
+ /** Snapshot captured on session exit when `sandboxShutdown === "snapshot"`. */
23
+ snapshot?: SandboxSnapshot;
24
+ /**
25
+ * Snapshot captured immediately after the sandbox was seeded (before the
26
+ * first agent turn) when `continuation === "snapshot"`. Only set on the
27
+ * first call that actually created the sandbox.
28
+ */
29
+ baseSnapshot?: SandboxSnapshot;
30
+ };
17
31
 
18
32
  /**
19
33
  * Raw workflow input fields passed from parent to child workflow.
@@ -64,13 +78,19 @@ export type InferSubagentResult<T extends SubagentConfig> =
64
78
  * Sandbox configuration for a subagent.
65
79
  *
66
80
  * - `"none"` — no sandbox (default).
67
- * - `{ source: "inherit", continuation }` — reuse the parent's sandbox.
81
+ * - `{ source: "inherit", continuation, proxy }` — reuse the parent's sandbox.
68
82
  * `continuation: "continue"` shares the parent sandbox directly;
69
83
  * `continuation: "fork"` forks from the parent on every call.
70
- * - `{ source: "own", init?, continuation }` — the child gets its own sandbox.
71
- * `init: "per-call"` (default) creates fresh each call (thread continuation
72
- * uses the previous sandbox). `init: "once"` creates on the first call and
73
- * stores it for all subsequent calls.
84
+ * - `{ source: "own", init?, continuation, proxy }` — the child gets its own
85
+ * sandbox. `init: "per-call"` (default) creates fresh each call (thread
86
+ * continuation uses the previous sandbox). `init: "once"` creates on the
87
+ * first call and stores it for all subsequent calls.
88
+ *
89
+ * `proxy` is a factory that returns workflow-safe sandbox ops matching the
90
+ * subagent's own activities. Called once inside `createSubagentHandler` with
91
+ * `scope = agentName`, so the returned proxy resolves to the same activity
92
+ * prefix the child session uses. The parent uses it to destroy lingering
93
+ * sandboxes and delete stored snapshots at shutdown.
74
94
  */
75
95
  export type SubagentSandboxConfig =
76
96
  | "none"
@@ -78,12 +98,14 @@ export type SubagentSandboxConfig =
78
98
  source: "inherit";
79
99
  continuation: "continue" | "fork";
80
100
  shutdown?: SubagentSandboxShutdown;
101
+ proxy: (scope: string) => SandboxOps;
81
102
  }
82
103
  | {
83
104
  source: "own";
84
105
  init?: "per-call" | "once";
85
- continuation: "continue" | "fork";
106
+ continuation: "continue" | "fork" | "snapshot";
86
107
  shutdown?: SubagentSandboxShutdown;
108
+ proxy: (scope: string) => SandboxOps;
87
109
  };
88
110
 
89
111
  /**
@@ -122,6 +144,21 @@ export interface SubagentConfig<TResult extends z.ZodType = z.ZodType> {
122
144
  * Sandbox strategy for this subagent.
123
145
  *
124
146
  * @see {@link SubagentSandboxConfig}
147
+ *
148
+ * @example
149
+ * ```ts
150
+ * import { proxyDaytonaSandboxOps } from "zeitlich/adapters/sandbox/daytona/workflow";
151
+ *
152
+ * const researcher: SubagentConfig = {
153
+ * agentName: "researcher",
154
+ * workflow: researcherWorkflow,
155
+ * sandbox: {
156
+ * source: "own",
157
+ * continuation: "snapshot",
158
+ * proxy: proxyDaytonaSandboxOps,
159
+ * },
160
+ * };
161
+ * ```
125
162
  */
126
163
  sandbox?: SubagentSandboxConfig;
127
164
  }
@@ -157,12 +194,11 @@ export interface SubagentHooks<TArgs = unknown, TResult = unknown> {
157
194
  }
158
195
 
159
196
  /**
160
- * Extended response from the subagent `fn` — includes optional cleanup callbacks
161
- * stripped before signaling the parent.
197
+ * Response returned from a subagent workflow `fn`.
162
198
  *
163
199
  * When `TSandboxShutdown` is `"pause-until-parent-close"` or
164
- * `"keep-until-parent-close"`, both `destroySandbox` and `sandboxId` become
165
- * required so the parent can coordinate cleanup.
200
+ * `"keep-until-parent-close"`, the parent needs the `sandboxId` to destroy
201
+ * the sandbox at its own shutdown, so the field becomes required.
166
202
  */
167
203
  export type SubagentFnResult<
168
204
  TResult = null,
@@ -171,14 +207,8 @@ export type SubagentFnResult<
171
207
  (TSandboxShutdown extends
172
208
  | "pause-until-parent-close"
173
209
  | "keep-until-parent-close"
174
- ? { destroySandbox: () => Promise<void>; sandboxId: string }
175
- : { destroySandbox?: () => Promise<void> });
176
-
177
- /** Payload sent by a child workflow to signal its result back to the parent */
178
- export interface ChildResultSignalPayload {
179
- childWorkflowId: string;
180
- result: SubagentHandlerResponse;
181
- }
210
+ ? { sandboxId: string }
211
+ : { sandboxId?: string });
182
212
 
183
213
  /** Payload sent by a child workflow as soon as its sandbox is ready */
184
214
  export interface ChildSandboxReadySignalPayload {
@@ -2,8 +2,6 @@ import type { z } from "zod";
2
2
  import {
3
3
  workflowInfo,
4
4
  getExternalWorkflowHandle,
5
- setHandler,
6
- condition,
7
5
  ApplicationFailure,
8
6
  } from "@temporalio/workflow";
9
7
  import type {
@@ -14,11 +12,7 @@ import type {
14
12
  SubagentSessionInput,
15
13
  } from "./types";
16
14
  import type { SubagentSandboxShutdown } from "../lifecycle";
17
- import {
18
- childResultSignal,
19
- childSandboxReadySignal,
20
- destroySandboxSignal,
21
- } from "./signals";
15
+ import { childSandboxReadySignal } from "./signals";
22
16
 
23
17
  /**
24
18
  * Defines a subagent workflow with embedded metadata (name, description, resultSchema).
@@ -137,51 +131,17 @@ export function defineSubagentWorkflow(
137
131
  ...(workflowInput.thread && { thread: workflowInput.thread }),
138
132
  ...(workflowInput.sandbox && { sandbox: workflowInput.sandbox }),
139
133
  onSandboxReady: (sandboxId: string) => {
140
- void parentHandle.signal(childSandboxReadySignal, {
141
- childWorkflowId: workflowInfo().workflowId,
142
- sandboxId,
143
- });
134
+ const isReuse = workflowInput.sandbox?.mode === "continue";
135
+ if (!isReuse) {
136
+ void parentHandle.signal(childSandboxReadySignal, {
137
+ childWorkflowId: workflowInfo().workflowId,
138
+ sandboxId,
139
+ });
140
+ }
144
141
  },
145
142
  };
146
- const { destroySandbox, ...result } = await fn(
147
- prompt,
148
- sessionInput,
149
- context ?? {}
150
- );
151
-
152
- if (
153
- effectiveShutdown === "pause-until-parent-close" ||
154
- effectiveShutdown === "keep-until-parent-close"
155
- ) {
156
- if (!destroySandbox) {
157
- throw ApplicationFailure.create({
158
- message: `Subagent "${config.name}" has sandboxShutdown="${effectiveShutdown}" but fn did not return a destroySandbox callback`,
159
- nonRetryable: true,
160
- });
161
- }
162
- if (!result.sandboxId) {
163
- throw ApplicationFailure.create({
164
- message: `Subagent "${config.name}" has sandboxShutdown="${effectiveShutdown}" but fn did not return a sandboxId`,
165
- nonRetryable: true,
166
- });
167
- }
168
- }
169
-
170
- await parentHandle.signal(childResultSignal, {
171
- childWorkflowId: workflowInfo().workflowId,
172
- result,
173
- });
174
-
175
- if (destroySandbox) {
176
- let destroyRequested = false;
177
- setHandler(destroySandboxSignal, () => {
178
- destroyRequested = true;
179
- });
180
- await condition(() => destroyRequested);
181
- await destroySandbox();
182
- }
183
143
 
184
- return result;
144
+ return fn(prompt, sessionInput, context ?? {});
185
145
  };
186
146
 
187
147
  // for temporal workflow name
@@ -8,7 +8,7 @@ vi.mock("@temporalio/workflow", () => ({
8
8
  const bytes = Array.from({ length: 16 }, (_, i) =>
9
9
  ((uuidCounter * 31 + i * 7 + uuidCounter * i) & 0xff)
10
10
  .toString(16)
11
- .padStart(2, "0"),
11
+ .padStart(2, "0")
12
12
  ).join("");
13
13
  return `${bytes.slice(0, 8)}-${bytes.slice(8, 12)}-${bytes.slice(12, 16)}-${bytes.slice(16, 20)}-${bytes.slice(20, 32)}`;
14
14
  },
@@ -1,7 +1,6 @@
1
1
  import { uuid4 } from "@temporalio/workflow";
2
2
 
3
- const BASE62 =
4
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
3
+ const BASE62 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
5
4
 
6
5
  /**
7
6
  * Generate a compact, workflow-deterministic identifier.
@@ -104,5 +104,23 @@ export function createThreadManager<T>(
104
104
  async delete(): Promise<void> {
105
105
  await redis.del(redisKey, metaKey);
106
106
  },
107
+
108
+ async length(): Promise<number> {
109
+ await assertThreadExists();
110
+ return redis.llen(redisKey);
111
+ },
112
+
113
+ async truncate(length: number): Promise<void> {
114
+ await assertThreadExists();
115
+ if (length <= 0) {
116
+ await redis.del(redisKey);
117
+ await redis.expire(metaKey, THREAD_TTL_SECONDS);
118
+ } else {
119
+ await redis.ltrim(redisKey, 0, length - 1);
120
+ await redis.expire(redisKey, THREAD_TTL_SECONDS);
121
+ }
122
+ // Dedup keys for removed messages are left to expire via their TTL.
123
+ // Post-truncate appends use fresh ids so collisions do not occur in practice.
124
+ },
107
125
  };
108
126
  }
@@ -25,7 +25,7 @@ import type { ThreadOps } from "../session/types";
25
25
  export function createThreadOpsProxy(
26
26
  adapterPrefix: string,
27
27
  scope?: string,
28
- options?: Parameters<typeof proxyActivities>[0],
28
+ options?: Parameters<typeof proxyActivities>[0]
29
29
  ): ActivityInterfaceFor<ThreadOps> {
30
30
  const resolvedScope = scope ?? workflowInfo().workflowType;
31
31
 
@@ -39,11 +39,10 @@ export function createThreadOpsProxy(
39
39
  maximumInterval: "15m",
40
40
  backoffCoefficient: 4,
41
41
  },
42
- },
42
+ }
43
43
  );
44
44
 
45
- const prefix =
46
- `${adapterPrefix}${resolvedScope.charAt(0).toUpperCase()}${resolvedScope.slice(1)}`;
45
+ const prefix = `${adapterPrefix}${resolvedScope.charAt(0).toUpperCase()}${resolvedScope.slice(1)}`;
47
46
  const p = (key: string): string =>
48
47
  `${prefix}${key.charAt(0).toUpperCase()}${key.slice(1)}`;
49
48
 
@@ -54,5 +53,6 @@ export function createThreadOpsProxy(
54
53
  appendAgentMessage: acts[p("appendAgentMessage")],
55
54
  appendSystemMessage: acts[p("appendSystemMessage")],
56
55
  forkThread: acts[p("forkThread")],
56
+ truncateThread: acts[p("truncateThread")],
57
57
  } as ActivityInterfaceFor<ThreadOps>;
58
58
  }
@@ -36,6 +36,15 @@ export interface BaseThreadManager<T> {
36
36
  fork(newThreadId: string): Promise<BaseThreadManager<T>>;
37
37
  /** Delete the thread */
38
38
  delete(): Promise<void>;
39
+ /** Get the number of stored messages currently in the thread */
40
+ length(): Promise<number>;
41
+ /**
42
+ * Truncate the thread to the given length (inclusive). Any messages
43
+ * beyond `length` are removed. When `length` is 0 the thread ends up
44
+ * empty (but still exists). Also clears any dedup markers so that
45
+ * subsequent appends with the same ids replay correctly.
46
+ */
47
+ truncate(length: number): Promise<void>;
39
48
  }
40
49
 
41
50
  /**
@@ -61,9 +70,17 @@ export interface BaseThreadManager<T> {
61
70
  */
62
71
  export interface ThreadManagerHooks<TStored, TPrepared = TStored> {
63
72
  /** Called for each stored message before SDK-specific processing (system extraction, role merging, format conversion) */
64
- onPrepareMessage?: (message: TStored, index: number, thread: readonly TStored[]) => TStored;
73
+ onPrepareMessage?: (
74
+ message: TStored,
75
+ index: number,
76
+ thread: readonly TStored[]
77
+ ) => TStored;
65
78
  /** Called for each SDK-native message after all processing, right before the payload is returned */
66
- onPreparedMessage?: (message: TPrepared, index: number, messages: readonly TPrepared[]) => TPrepared;
79
+ onPreparedMessage?: (
80
+ message: TPrepared,
81
+ index: number,
82
+ messages: readonly TPrepared[]
83
+ ) => TPrepared;
67
84
  }
68
85
 
69
86
  export interface ProviderThreadManager<
@@ -78,6 +95,6 @@ export interface ProviderThreadManager<
78
95
  id: string,
79
96
  toolCallId: string,
80
97
  toolName: string,
81
- content: TToolContent,
98
+ content: TToolContent
82
99
  ): Promise<void>;
83
100
  }
@@ -1,8 +1,4 @@
1
- export {
2
- createToolRouter,
3
- defineTool,
4
- hasNoOtherToolCalls,
5
- } from "./router";
1
+ export { createToolRouter, defineTool, hasNoOtherToolCalls } from "./router";
6
2
  export { withAutoAppend } from "./auto-append";
7
3
  export { withSandbox } from "./with-sandbox";
8
4
  export type { SandboxContext } from "./with-sandbox";
@@ -26,6 +22,8 @@ export type {
26
22
  InferToolResults,
27
23
  ToolCallResultUnion,
28
24
  ProcessToolCallsContext,
25
+ ProcessToolCallsResult,
26
+ RewindSignal,
29
27
  PreToolUseHookResult,
30
28
  PostToolUseFailureHookResult,
31
29
  ToolHooks,
@@ -23,8 +23,20 @@ vi.mock("@temporalio/workflow", () => {
23
23
  }
24
24
  }
25
25
  const noop = () => {};
26
+ class MockCancellationScope {
27
+ cancellable: boolean;
28
+ constructor(opts?: { cancellable?: boolean }) {
29
+ this.cancellable = opts?.cancellable ?? true;
30
+ }
31
+ async run<T>(fn: () => Promise<T>): Promise<T> {
32
+ return fn();
33
+ }
34
+ cancel(): void {}
35
+ }
26
36
  return {
27
37
  ApplicationFailure: MockApplicationFailure,
38
+ CancellationScope: MockCancellationScope,
39
+ isCancellation: (_err: unknown) => false,
28
40
  uuid4: () => "00000000-0000-0000-0000-000000000000",
29
41
  log: { trace: noop, debug: noop, info: noop, warn: noop, error: noop },
30
42
  };
@@ -348,7 +360,7 @@ describe("createToolRouter edge cases", () => {
348
360
  parallel: true,
349
361
  });
350
362
 
351
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
363
+
352
364
  const results = await router.processToolCalls([
353
365
  { id: "tc-1", name: "Unknown1", args: {} },
354
366
  { id: "tc-2", name: "Unknown2", args: {} },
@@ -648,6 +660,86 @@ describe("createToolRouter edge cases", () => {
648
660
  recovered: true,
649
661
  });
650
662
  });
663
+
664
+ // --- Rewind signal -------------------------------------------------------
665
+
666
+ it("attaches a rewind signal and skips result append when handler returns rewind:true", async () => {
667
+ const rewindTool = defineTool({
668
+ name: "Rewind" as const,
669
+ description: "rewinds",
670
+ schema: z.object({}),
671
+ handler: async () => ({
672
+ toolResponse: "ignored",
673
+ data: null,
674
+ rewind: true,
675
+ }),
676
+ });
677
+
678
+ const router = createToolRouter({
679
+ tools: { Rewind: rewindTool } as const,
680
+ threadId: "t-1",
681
+ appendToolResult: appendSpy.fn,
682
+ });
683
+
684
+ const parsed = router.parseToolCall({
685
+ id: "tc-1",
686
+ name: "Rewind",
687
+ args: {},
688
+ });
689
+
690
+ const results = await router.processToolCalls([parsed]);
691
+
692
+ expect(results).toHaveLength(0);
693
+ expect(results.rewind).toEqual({
694
+ toolCallId: "tc-1",
695
+ toolName: "Rewind",
696
+ });
697
+ expect(appendSpy.calls).toHaveLength(0);
698
+ });
699
+
700
+ it("short-circuits further sequential tool calls when one requests rewind", async () => {
701
+ let laterCalled = false;
702
+ const laterTool = defineTool({
703
+ name: "Later" as const,
704
+ description: "runs after rewind",
705
+ schema: z.object({}),
706
+ handler: async () => {
707
+ laterCalled = true;
708
+ return { toolResponse: "ok", data: null };
709
+ },
710
+ });
711
+ const rewindTool = defineTool({
712
+ name: "Rewind" as const,
713
+ description: "rewinds",
714
+ schema: z.object({}),
715
+ handler: async () => ({
716
+ toolResponse: "ignored",
717
+ data: null,
718
+ rewind: true,
719
+ }),
720
+ });
721
+
722
+ const router = createToolRouter({
723
+ tools: { Rewind: rewindTool, Later: laterTool } as const,
724
+ threadId: "t-1",
725
+ appendToolResult: appendSpy.fn,
726
+ parallel: false,
727
+ });
728
+
729
+ const calls = [
730
+ router.parseToolCall({ id: "tc-1", name: "Rewind", args: {} }),
731
+ router.parseToolCall({ id: "tc-2", name: "Later", args: {} }),
732
+ ];
733
+
734
+ const results = await router.processToolCalls(calls);
735
+
736
+ expect(results).toHaveLength(0);
737
+ expect(results.rewind).toEqual({
738
+ toolCallId: "tc-1",
739
+ toolName: "Rewind",
740
+ });
741
+ expect(laterCalled).toBe(false);
742
+ });
651
743
  });
652
744
 
653
745
  describe("hasNoOtherToolCalls", () => {
@@ -23,8 +23,20 @@ vi.mock("@temporalio/workflow", () => {
23
23
  }
24
24
  }
25
25
  const noop = () => {};
26
+ class MockCancellationScope {
27
+ cancellable: boolean;
28
+ constructor(opts?: { cancellable?: boolean }) {
29
+ this.cancellable = opts?.cancellable ?? true;
30
+ }
31
+ async run<T>(fn: () => Promise<T>): Promise<T> {
32
+ return fn();
33
+ }
34
+ cancel(): void {}
35
+ }
26
36
  return {
27
37
  ApplicationFailure: MockApplicationFailure,
38
+ CancellationScope: MockCancellationScope,
39
+ isCancellation: (_err: unknown) => false,
28
40
  uuid4: () => "00000000-0000-0000-0000-000000000000",
29
41
  log: { trace: noop, debug: noop, info: noop, warn: noop, error: noop },
30
42
  };