zeitlich 0.2.33 → 0.2.35

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 (141) hide show
  1. package/README.md +17 -6
  2. package/dist/{activities-YBD5BaHh.d.ts → activities-BVI2lTwr.d.ts} +6 -4
  3. package/dist/{activities-fnX8-vhR.d.cts → activities-hd4aNnZE.d.cts} +6 -4
  4. package/dist/adapters/sandbox/bedrock/index.cjs +2 -0
  5. package/dist/adapters/sandbox/bedrock/index.cjs.map +1 -1
  6. package/dist/adapters/sandbox/bedrock/index.d.cts +4 -3
  7. package/dist/adapters/sandbox/bedrock/index.d.ts +4 -3
  8. package/dist/adapters/sandbox/bedrock/index.js +2 -0
  9. package/dist/adapters/sandbox/bedrock/index.js.map +1 -1
  10. package/dist/adapters/sandbox/bedrock/workflow.cjs +1 -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 +1 -0
  15. package/dist/adapters/sandbox/bedrock/workflow.js.map +1 -1
  16. package/dist/adapters/sandbox/daytona/index.cjs +2 -0
  17. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
  18. package/dist/adapters/sandbox/daytona/index.d.cts +2 -1
  19. package/dist/adapters/sandbox/daytona/index.d.ts +2 -1
  20. package/dist/adapters/sandbox/daytona/index.js +2 -0
  21. package/dist/adapters/sandbox/daytona/index.js.map +1 -1
  22. package/dist/adapters/sandbox/daytona/workflow.cjs +1 -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 +1 -0
  27. package/dist/adapters/sandbox/daytona/workflow.js.map +1 -1
  28. package/dist/adapters/sandbox/e2b/index.cjs +3 -0
  29. package/dist/adapters/sandbox/e2b/index.cjs.map +1 -1
  30. package/dist/adapters/sandbox/e2b/index.d.cts +2 -1
  31. package/dist/adapters/sandbox/e2b/index.d.ts +2 -1
  32. package/dist/adapters/sandbox/e2b/index.js +3 -0
  33. package/dist/adapters/sandbox/e2b/index.js.map +1 -1
  34. package/dist/adapters/sandbox/e2b/workflow.cjs +1 -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 +1 -0
  39. package/dist/adapters/sandbox/e2b/workflow.js.map +1 -1
  40. package/dist/adapters/sandbox/inmemory/index.cjs +2 -0
  41. package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
  42. package/dist/adapters/sandbox/inmemory/index.d.cts +2 -1
  43. package/dist/adapters/sandbox/inmemory/index.d.ts +2 -1
  44. package/dist/adapters/sandbox/inmemory/index.js +2 -0
  45. package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
  46. package/dist/adapters/sandbox/inmemory/workflow.cjs +1 -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 +1 -0
  51. package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -1
  52. package/dist/adapters/thread/anthropic/index.cjs +7 -2
  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 +7 -2
  57. package/dist/adapters/thread/anthropic/index.js.map +1 -1
  58. package/dist/adapters/thread/anthropic/workflow.d.cts +5 -5
  59. package/dist/adapters/thread/anthropic/workflow.d.ts +5 -5
  60. package/dist/adapters/thread/google-genai/index.cjs +4 -3
  61. package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
  62. package/dist/adapters/thread/google-genai/index.d.cts +5 -5
  63. package/dist/adapters/thread/google-genai/index.d.ts +5 -5
  64. package/dist/adapters/thread/google-genai/index.js +4 -3
  65. package/dist/adapters/thread/google-genai/index.js.map +1 -1
  66. package/dist/adapters/thread/google-genai/workflow.d.cts +5 -5
  67. package/dist/adapters/thread/google-genai/workflow.d.ts +5 -5
  68. package/dist/adapters/thread/langchain/index.cjs +4 -1
  69. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  70. package/dist/adapters/thread/langchain/index.d.cts +5 -5
  71. package/dist/adapters/thread/langchain/index.d.ts +5 -5
  72. package/dist/adapters/thread/langchain/index.js +4 -1
  73. package/dist/adapters/thread/langchain/index.js.map +1 -1
  74. package/dist/adapters/thread/langchain/workflow.d.cts +5 -5
  75. package/dist/adapters/thread/langchain/workflow.d.ts +5 -5
  76. package/dist/index.cjs +115 -31
  77. package/dist/index.cjs.map +1 -1
  78. package/dist/index.d.cts +10 -9
  79. package/dist/index.d.ts +10 -9
  80. package/dist/index.js +115 -31
  81. package/dist/index.js.map +1 -1
  82. package/dist/{proxy-CTCYWjkr.d.cts → proxy-7RnVaPdJ.d.cts} +1 -1
  83. package/dist/{proxy-Br4unLTC.d.ts → proxy-BjdFGPTm.d.ts} +1 -1
  84. package/dist/{thread-manager-DKWxHUzD.d.ts → thread-manager-BBzNgQWH.d.cts} +5 -2
  85. package/dist/{thread-manager-CUubPYPH.d.cts → thread-manager-CbpiGq1L.d.ts} +6 -3
  86. package/dist/{thread-manager-Cv_BR28i.d.cts → thread-manager-DjN5JYul.d.ts} +5 -2
  87. package/dist/{thread-manager-YJLoc1vH.d.ts → thread-manager-DzXm9eeI.d.cts} +6 -3
  88. package/dist/{types-Bpq5fDI5.d.cts → types-CADc5V_P.d.ts} +39 -24
  89. package/dist/{types-DUvEZSDe.d.cts → types-CBH54cwr.d.cts} +1 -1
  90. package/dist/{types-CheCTLeV.d.ts → types-DQ1l_gXL.d.cts} +39 -24
  91. package/dist/{types-AujBIMMn.d.cts → types-DxCpFNv_.d.cts} +4 -0
  92. package/dist/{types-AujBIMMn.d.ts → types-DxCpFNv_.d.ts} +4 -0
  93. package/dist/{types-NJDyMyUx.d.cts → types-Mc_4BCfT.d.cts} +3 -3
  94. package/dist/{types-DBk-C8zM.d.ts → types-wiGLvxWf.d.ts} +1 -1
  95. package/dist/{types-BxiT8w9d.d.ts → types-yiXmqedU.d.ts} +3 -3
  96. package/dist/{workflow-Od9vx5Jk.d.cts → workflow-DhtWRovz.d.cts} +3 -3
  97. package/dist/{workflow-D9nNERvs.d.ts → workflow-P2pTSfKu.d.ts} +3 -3
  98. package/dist/workflow.cjs +109 -31
  99. package/dist/workflow.cjs.map +1 -1
  100. package/dist/workflow.d.cts +3 -3
  101. package/dist/workflow.d.ts +3 -3
  102. package/dist/workflow.js +109 -31
  103. package/dist/workflow.js.map +1 -1
  104. package/package.json +1 -1
  105. package/src/adapters/sandbox/bedrock/index.ts +4 -0
  106. package/src/adapters/sandbox/bedrock/proxy.ts +1 -0
  107. package/src/adapters/sandbox/daytona/index.ts +4 -0
  108. package/src/adapters/sandbox/daytona/proxy.ts +1 -0
  109. package/src/adapters/sandbox/e2b/index.ts +4 -0
  110. package/src/adapters/sandbox/e2b/proxy.ts +1 -0
  111. package/src/adapters/sandbox/inmemory/index.ts +4 -0
  112. package/src/adapters/sandbox/inmemory/proxy.ts +1 -0
  113. package/src/adapters/thread/anthropic/activities.ts +2 -1
  114. package/src/adapters/thread/anthropic/thread-manager.ts +21 -9
  115. package/src/adapters/thread/google-genai/activities.ts +2 -1
  116. package/src/adapters/thread/google-genai/thread-manager.test.ts +1 -1
  117. package/src/adapters/thread/google-genai/thread-manager.ts +15 -7
  118. package/src/adapters/thread/langchain/activities.ts +2 -1
  119. package/src/adapters/thread/langchain/thread-manager.ts +12 -3
  120. package/src/lib/lifecycle.ts +7 -3
  121. package/src/lib/sandbox/manager.ts +7 -0
  122. package/src/lib/sandbox/types.ts +4 -0
  123. package/src/lib/session/session-edge-cases.integration.test.ts +194 -0
  124. package/src/lib/session/session.integration.test.ts +5 -0
  125. package/src/lib/session/session.ts +13 -1
  126. package/src/lib/session/types.ts +10 -2
  127. package/src/lib/state/manager.ts +2 -2
  128. package/src/lib/state/types.ts +2 -2
  129. package/src/lib/subagent/define.ts +1 -1
  130. package/src/lib/subagent/handler.ts +142 -32
  131. package/src/lib/subagent/index.ts +5 -1
  132. package/src/lib/subagent/signals.ts +8 -1
  133. package/src/lib/subagent/subagent.integration.test.ts +532 -25
  134. package/src/lib/subagent/types.ts +32 -15
  135. package/src/lib/subagent/workflow.ts +26 -13
  136. package/src/lib/thread/types.ts +2 -2
  137. package/src/lib/types.ts +1 -1
  138. package/src/lib/virtual-fs/manager.ts +1 -1
  139. package/src/lib/virtual-fs/types.ts +2 -2
  140. package/src/lib/virtual-fs/virtual-fs.test.ts +2 -2
  141. package/src/workflow.ts +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zeitlich",
3
- "version": "0.2.33",
3
+ "version": "0.2.35",
4
4
  "description": "[EXPERIMENTAL] An opinionated AI agent implementation for Temporal",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -237,6 +237,10 @@ export class BedrockSandboxProvider
237
237
  throw new SandboxNotSupportedError("pause");
238
238
  }
239
239
 
240
+ async resume(_sandboxId: string): Promise<void> {
241
+ // Bedrock sandboxes don't support pause, so resume is a no-op
242
+ }
243
+
240
244
  async snapshot(_sandboxId: string): Promise<SandboxSnapshot> {
241
245
  throw new SandboxNotSupportedError("snapshot");
242
246
  }
@@ -50,6 +50,7 @@ export function proxyBedrockSandboxOps(
50
50
  createSandbox: acts[p("createSandbox")],
51
51
  destroySandbox: acts[p("destroySandbox")],
52
52
  pauseSandbox: acts[p("pauseSandbox")],
53
+ resumeSandbox: acts[p("resumeSandbox")],
53
54
  snapshotSandbox: acts[p("snapshotSandbox")],
54
55
  forkSandbox: acts[p("forkSandbox")],
55
56
  } as SandboxOps<BedrockSandboxCreateOptions>;
@@ -145,6 +145,10 @@ export class DaytonaSandboxProvider implements SandboxProvider<
145
145
  throw new SandboxNotSupportedError("pause");
146
146
  }
147
147
 
148
+ async resume(_sandboxId: string): Promise<void> {
149
+ // Daytona sandboxes don't support pause, so resume is a no-op
150
+ }
151
+
148
152
  async fork(_sandboxId: string): Promise<Sandbox> {
149
153
  throw new Error("Not implemented");
150
154
  }
@@ -50,6 +50,7 @@ export function proxyDaytonaSandboxOps(
50
50
  createSandbox: acts[p("createSandbox")],
51
51
  destroySandbox: acts[p("destroySandbox")],
52
52
  pauseSandbox: acts[p("pauseSandbox")],
53
+ resumeSandbox: acts[p("resumeSandbox")],
53
54
  snapshotSandbox: acts[p("snapshotSandbox")],
54
55
  forkSandbox: acts[p("forkSandbox")],
55
56
  } as SandboxOps<DaytonaSandboxCreateOptions>;
@@ -136,6 +136,10 @@ export class E2bSandboxProvider
136
136
  await sdkSandbox.pause();
137
137
  }
138
138
 
139
+ async resume(sandboxId: string): Promise<void> {
140
+ await E2bSdkSandbox.connect(sandboxId);
141
+ }
142
+
139
143
  async snapshot(_sandboxId: string): Promise<SandboxSnapshot> {
140
144
  throw new SandboxNotSupportedError("snapshot");
141
145
  }
@@ -50,6 +50,7 @@ export function proxyE2bSandboxOps(
50
50
  createSandbox: acts[p("createSandbox")],
51
51
  destroySandbox: acts[p("destroySandbox")],
52
52
  pauseSandbox: acts[p("pauseSandbox")],
53
+ resumeSandbox: acts[p("resumeSandbox")],
53
54
  snapshotSandbox: acts[p("snapshotSandbox")],
54
55
  forkSandbox: acts[p("forkSandbox")],
55
56
  } as SandboxOps<E2bSandboxCreateOptions>;
@@ -163,6 +163,10 @@ export class InMemorySandboxProvider implements SandboxProvider {
163
163
  // In-memory: nothing to pause
164
164
  }
165
165
 
166
+ async resume(_sandboxId: string): Promise<void> {
167
+ // In-memory: nothing to resume
168
+ }
169
+
166
170
  async create(options?: SandboxCreateOptions): Promise<SandboxCreateResult> {
167
171
  const id = options?.id ?? getShortId();
168
172
  const initialFiles: InitialFiles = {};
@@ -47,6 +47,7 @@ export function proxyInMemorySandboxOps(
47
47
  createSandbox: acts[p("createSandbox")],
48
48
  destroySandbox: acts[p("destroySandbox")],
49
49
  pauseSandbox: acts[p("pauseSandbox")],
50
+ resumeSandbox: acts[p("resumeSandbox")],
50
51
  snapshotSandbox: acts[p("snapshotSandbox")],
51
52
  forkSandbox: acts[p("forkSandbox")],
52
53
  } as SandboxOps;
@@ -15,6 +15,7 @@ import type { ModelInvoker } from "../../../lib/model";
15
15
  import {
16
16
  createAnthropicThreadManager,
17
17
  type AnthropicContent,
18
+ type AnthropicSystemContent,
18
19
  type AnthropicThreadManagerHooks,
19
20
  } from "./thread-manager";
20
21
  import {
@@ -152,7 +153,7 @@ export function createAnthropicAdapter(
152
153
  async appendSystemMessage(
153
154
  threadId: string,
154
155
  id: string,
155
- content: string,
156
+ content: AnthropicSystemContent,
156
157
  threadKey?: string,
157
158
  ): Promise<void> {
158
159
  const thread = createAnthropicThreadManager({ redis, threadId, key: threadKey });
@@ -13,6 +13,11 @@ export type AnthropicContent =
13
13
  | string
14
14
  | Anthropic.Messages.ContentBlockParam[];
15
15
 
16
+ /** SDK-native content type for Anthropic system prompts (supports cache_control blocks) */
17
+ export type AnthropicSystemContent =
18
+ | string
19
+ | Anthropic.Messages.TextBlockParam[];
20
+
16
21
  /** A MessageParam with a unique ID for idempotent Redis storage */
17
22
  export interface StoredMessage {
18
23
  id: string;
@@ -34,12 +39,12 @@ export interface AnthropicThreadManagerConfig {
34
39
  /** Prepared payload ready to send to the Anthropic API */
35
40
  export interface AnthropicInvocationPayload {
36
41
  messages: Anthropic.Messages.MessageParam[];
37
- system?: string;
42
+ system?: string | Anthropic.Messages.TextBlockParam[];
38
43
  }
39
44
 
40
45
  /** Thread manager with Anthropic MessageParam convenience helpers */
41
46
  export interface AnthropicThreadManager
42
- extends ProviderThreadManager<StoredMessage, AnthropicContent> {
47
+ extends ProviderThreadManager<StoredMessage, AnthropicContent, JsonValue, AnthropicSystemContent> {
43
48
  appendAssistantMessage(
44
49
  id: string,
45
50
  content: Anthropic.Messages.ContentBlock[],
@@ -120,11 +125,19 @@ export function createAnthropicThreadManager(
120
125
  }]);
121
126
  },
122
127
 
123
- async appendSystemMessage(id: string, content: string): Promise<void> {
128
+ async appendSystemMessage(
129
+ id: string,
130
+ content: AnthropicSystemContent,
131
+ ): Promise<void> {
124
132
  await base.initialize();
125
133
  await base.append([{
126
134
  id,
127
- message: { role: "user", content },
135
+ // Stored under a user-role placeholder to satisfy the MessageParam
136
+ // shape; the `isSystem` flag steers extraction in prepareForInvocation.
137
+ message: {
138
+ role: "user",
139
+ content: content as Anthropic.Messages.MessageParam["content"],
140
+ },
128
141
  isSystem: true,
129
142
  }]);
130
143
  },
@@ -174,15 +187,14 @@ export function createAnthropicThreadManager(
174
187
  ? stored.map((msg, i) => onPrepareMessage(msg, i, stored))
175
188
  : stored;
176
189
 
177
- let system: string | undefined;
190
+ let system: string | Anthropic.Messages.TextBlockParam[] | undefined;
178
191
  const conversationMessages: Anthropic.Messages.MessageParam[] = [];
179
192
 
180
193
  for (const item of mapped) {
181
194
  if (item.isSystem) {
182
- system =
183
- typeof item.message.content === "string"
184
- ? item.message.content
185
- : undefined;
195
+ system = item.message.content as
196
+ | string
197
+ | Anthropic.Messages.TextBlockParam[];
186
198
  } else {
187
199
  conversationMessages.push(item.message);
188
200
  }
@@ -15,6 +15,7 @@ import type { ModelInvoker } from "../../../lib/model";
15
15
  import {
16
16
  createGoogleGenAIThreadManager,
17
17
  type GoogleGenAIContent,
18
+ type GoogleGenAISystemContent,
18
19
  type GoogleGenAIThreadManagerHooks,
19
20
  } from "./thread-manager";
20
21
  import { createGoogleGenAIModelInvoker } from "./model-invoker";
@@ -169,7 +170,7 @@ export function createGoogleGenAIAdapter(
169
170
  async appendSystemMessage(
170
171
  threadId: string,
171
172
  id: string,
172
- content: string,
173
+ content: GoogleGenAISystemContent,
173
174
  threadKey?: string
174
175
  ): Promise<void> {
175
176
  const thread = createGoogleGenAIThreadManager({
@@ -55,7 +55,7 @@ describe("Google GenAI thread manager hooks", () => {
55
55
 
56
56
  expect(hook).toHaveBeenCalledTimes(3);
57
57
  expect(hook).toHaveBeenCalledWith(systemContent, 0, [systemContent, userContent, modelContent]);
58
- expect(systemInstruction).toBe("You are helpful.");
58
+ expect(systemInstruction).toEqual([{ text: "You are helpful." }]);
59
59
  expect(contents[0]?.parts?.[0]?.text).toBe("[modified] Hello");
60
60
  expect(contents[1]?.parts?.[0]?.text).toBe("[modified] Hi there!");
61
61
  });
@@ -11,6 +11,9 @@ import type { GoogleGenAIToolResponse } from "./activities";
11
11
  /** SDK-native content type for Google GenAI human messages */
12
12
  export type GoogleGenAIContent = string | Part[];
13
13
 
14
+ /** SDK-native content type for Google GenAI system instructions */
15
+ export type GoogleGenAISystemContent = string | Part[];
16
+
14
17
  /** A Content with a unique ID for idempotent Redis storage */
15
18
  export interface StoredContent {
16
19
  id: string;
@@ -30,12 +33,12 @@ export interface GoogleGenAIThreadManagerConfig {
30
33
  /** Prepared payload ready to send to the Google GenAI API */
31
34
  export interface GoogleGenAIInvocationPayload {
32
35
  contents: Content[];
33
- systemInstruction?: string;
36
+ systemInstruction?: Part[];
34
37
  }
35
38
 
36
39
  /** Thread manager with Google GenAI Content convenience helpers */
37
40
  export interface GoogleGenAIThreadManager
38
- extends ProviderThreadManager<StoredContent, GoogleGenAIContent, GoogleGenAIToolResponse> {
41
+ extends ProviderThreadManager<StoredContent, GoogleGenAIContent, GoogleGenAIToolResponse, GoogleGenAISystemContent> {
39
42
  appendModelContent(id: string, parts: Part[]): Promise<void>;
40
43
  prepareForInvocation(): Promise<GoogleGenAIInvocationPayload>;
41
44
  }
@@ -106,11 +109,16 @@ export function createGoogleGenAIThreadManager(
106
109
  }]);
107
110
  },
108
111
 
109
- async appendSystemMessage(id: string, content: string): Promise<void> {
112
+ async appendSystemMessage(
113
+ id: string,
114
+ content: GoogleGenAISystemContent,
115
+ ): Promise<void> {
116
+ const parts: Part[] =
117
+ typeof content === "string" ? [{ text: content }] : content;
110
118
  await base.initialize();
111
119
  await base.append([{
112
120
  id,
113
- content: { role: "system", parts: [{ text: content }] },
121
+ content: { role: "system", parts },
114
122
  }]);
115
123
  },
116
124
 
@@ -150,12 +158,12 @@ export function createGoogleGenAIThreadManager(
150
158
  ? stored.map((msg, i) => onPrepareMessage(msg, i, stored))
151
159
  : stored;
152
160
 
153
- let systemInstruction: string | undefined;
161
+ let systemInstruction: Part[] | undefined;
154
162
  const conversationContents: Content[] = [];
155
163
 
156
164
  for (const item of mapped) {
157
165
  if (item.content.role === "system") {
158
- systemInstruction = item.content.parts?.[0]?.text;
166
+ systemInstruction = item.content.parts ?? [];
159
167
  } else {
160
168
  conversationContents.push(item.content);
161
169
  }
@@ -166,7 +174,7 @@ export function createGoogleGenAIThreadManager(
166
174
  contents: onPreparedMessage
167
175
  ? contents.map((msg, i) => onPreparedMessage(msg, i, contents))
168
176
  : contents,
169
- ...(systemInstruction ? { systemInstruction } : {}),
177
+ ...(systemInstruction && systemInstruction.length > 0 ? { systemInstruction } : {}),
170
178
  };
171
179
  },
172
180
  };
@@ -17,6 +17,7 @@ import type { BaseChatModel } from "@langchain/core/language_models/chat_models"
17
17
  import {
18
18
  createLangChainThreadManager,
19
19
  type LangChainContent,
20
+ type LangChainSystemContent,
20
21
  type LangChainThreadManagerHooks,
21
22
  } from "./thread-manager";
22
23
  import { createLangChainModelInvoker } from "./model-invoker";
@@ -135,7 +136,7 @@ export function createLangChainAdapter(
135
136
  async appendSystemMessage(
136
137
  threadId: string,
137
138
  id: string,
138
- content: string,
139
+ content: LangChainSystemContent,
139
140
  threadKey?: string,
140
141
  ): Promise<void> {
141
142
  const thread = createLangChainThreadManager({ redis, threadId, key: threadKey });
@@ -20,6 +20,9 @@ import type {
20
20
  /** SDK-native content type for LangChain human messages */
21
21
  export type LangChainContent = string | MessageContent;
22
22
 
23
+ /** SDK-native content type for LangChain system messages */
24
+ export type LangChainSystemContent = string | MessageContent;
25
+
23
26
  export type LangChainThreadManagerHooks = ThreadManagerHooks<StoredMessage, BaseMessage>;
24
27
 
25
28
  export interface LangChainThreadManagerConfig {
@@ -37,7 +40,7 @@ export interface LangChainInvocationPayload {
37
40
 
38
41
  /** Thread manager with LangChain StoredMessage convenience helpers */
39
42
  export interface LangChainThreadManager
40
- extends ProviderThreadManager<StoredMessage, LangChainContent> {
43
+ extends ProviderThreadManager<StoredMessage, LangChainContent, JsonValue, LangChainSystemContent> {
41
44
  appendAIMessage(id: string, content: string | MessageContent): Promise<void>;
42
45
  prepareForInvocation(): Promise<LangChainInvocationPayload>;
43
46
  }
@@ -81,10 +84,16 @@ export function createLangChainThreadManager(
81
84
  ]);
82
85
  },
83
86
 
84
- async appendSystemMessage(id: string, content: string): Promise<void> {
87
+ async appendSystemMessage(
88
+ id: string,
89
+ content: LangChainSystemContent,
90
+ ): Promise<void> {
85
91
  await base.initialize();
86
92
  await base.append([
87
- new SystemMessage({ id, content }).toDict(),
93
+ new SystemMessage({
94
+ id,
95
+ content: content as MessageContent,
96
+ }).toDict(),
88
97
  ]);
89
98
  },
90
99
 
@@ -25,8 +25,9 @@ export type ThreadInit =
25
25
  * - `"new"` — create a fresh sandbox. Optionally pass `ctx` to
26
26
  * have the {@link SandboxManager}'s resolver produce creation options
27
27
  * (e.g. initial files) from workflow arguments.
28
- * - `"continue"` — resume a previously-paused sandbox (this session takes
29
- * ownership and the shutdown policy applies on exit).
28
+ * - `"continue"` — take ownership of an existing sandbox (paused or running).
29
+ * Paused sandboxes are automatically resumed. The shutdown policy applies
30
+ * on exit.
30
31
  * - `"fork"` — fork from an existing (or paused) sandbox; a new sandbox is
31
32
  * created and owned by this session.
32
33
  * - `"inherit"` — use a sandbox owned by someone else (e.g. a parent agent).
@@ -56,7 +57,10 @@ export type SandboxShutdown = "destroy" | "pause" | "keep";
56
57
  * Includes all base {@link SandboxShutdown} values plus:
57
58
  * - `"pause-until-parent-close"` — pause the sandbox on exit, then wait for
58
59
  * the parent workflow to signal when to destroy it.
60
+ * - `"keep-until-parent-close"` — leave the sandbox running on exit, then
61
+ * wait for the parent workflow to signal when to destroy it.
59
62
  */
60
63
  export type SubagentSandboxShutdown =
61
64
  | SandboxShutdown
62
- | "pause-until-parent-close";
65
+ | "pause-until-parent-close"
66
+ | "keep-until-parent-close";
@@ -166,6 +166,10 @@ export class SandboxManager<
166
166
  await this.provider.pause(id, ttlSeconds);
167
167
  }
168
168
 
169
+ async resume(id: string): Promise<void> {
170
+ await this.provider.resume(id);
171
+ }
172
+
169
173
  async snapshot(id: string): Promise<SandboxSnapshot> {
170
174
  return this.provider.snapshot(id);
171
175
  }
@@ -222,6 +226,9 @@ export class SandboxManager<
222
226
  ): Promise<void> => {
223
227
  await this.pause(sandboxId, ttlSeconds);
224
228
  },
229
+ resumeSandbox: async (sandboxId: string): Promise<void> => {
230
+ await this.resume(sandboxId);
231
+ },
225
232
  snapshotSandbox: async (sandboxId: string): Promise<SandboxSnapshot> => {
226
233
  return this.snapshot(sandboxId);
227
234
  },
@@ -131,6 +131,8 @@ export interface SandboxProvider<
131
131
  get(sandboxId: string): Promise<TSandbox>;
132
132
  destroy(sandboxId: string): Promise<void>;
133
133
  pause(sandboxId: string, ttlSeconds?: number): Promise<void>;
134
+ /** Resume a paused sandbox. No-op if already running. */
135
+ resume(sandboxId: string): Promise<void>;
134
136
  snapshot(sandboxId: string): Promise<SandboxSnapshot>;
135
137
  restore(snapshot: SandboxSnapshot): Promise<Sandbox>;
136
138
  fork(sandboxId: string): Promise<Sandbox>;
@@ -150,6 +152,8 @@ export interface SandboxOps<
150
152
  ): Promise<{ sandboxId: string } | null>;
151
153
  destroySandbox(sandboxId: string): Promise<void>;
152
154
  pauseSandbox(sandboxId: string): Promise<void>;
155
+ /** Resume a paused sandbox. No-op if already running. */
156
+ resumeSandbox(sandboxId: string): Promise<void>;
153
157
  snapshotSandbox(sandboxId: string): Promise<SandboxSnapshot>;
154
158
  forkSandbox(sandboxId: string): Promise<string>;
155
159
  }
@@ -434,6 +434,7 @@ describe("createSession edge cases", () => {
434
434
  }),
435
435
  forkSandbox: async () => "forked-sandbox-id",
436
436
  pauseSandbox: async () => {},
437
+ resumeSandbox: async () => {},
437
438
  };
438
439
 
439
440
  const session = await createSession({
@@ -476,6 +477,7 @@ describe("createSession edge cases", () => {
476
477
  }),
477
478
  forkSandbox: async () => "forked-sandbox-id",
478
479
  pauseSandbox: async () => {},
480
+ resumeSandbox: async () => {},
479
481
  };
480
482
 
481
483
  const session = await createSession({
@@ -939,6 +941,7 @@ describe("createSession edge cases", () => {
939
941
  snapshotSandbox: snapshotSpy,
940
942
  forkSandbox: async () => "forked-sandbox-id",
941
943
  pauseSandbox: async () => {},
944
+ resumeSandbox: async () => {},
942
945
  };
943
946
 
944
947
  const session = await createSession({
@@ -1029,6 +1032,7 @@ describe("createSession edge cases", () => {
1029
1032
  createSandbox: async () => ({ sandboxId: "sb-created" }),
1030
1033
  destroySandbox: async () => {},
1031
1034
  pauseSandbox: async () => {},
1035
+ resumeSandbox: async () => {},
1032
1036
  snapshotSandbox: async () => ({
1033
1037
  sandboxId: "sb-1",
1034
1038
  providerId: "test",
@@ -1062,6 +1066,7 @@ describe("createSession edge cases", () => {
1062
1066
  createSandbox: async () => ({ sandboxId: "sb" }),
1063
1067
  destroySandbox: async () => {},
1064
1068
  pauseSandbox: async () => {},
1069
+ resumeSandbox: async () => {},
1065
1070
  snapshotSandbox: async () => ({
1066
1071
  sandboxId: "sb",
1067
1072
  providerId: "test",
@@ -1106,6 +1111,7 @@ describe("createSession edge cases", () => {
1106
1111
  pauseSandbox: async (id: string) => {
1107
1112
  sandboxLog.push(`pause:${id}`);
1108
1113
  },
1114
+ resumeSandbox: async () => {},
1109
1115
  snapshotSandbox: async () => ({
1110
1116
  sandboxId: "sb-1",
1111
1117
  providerId: "test",
@@ -1149,6 +1155,7 @@ describe("createSession edge cases", () => {
1149
1155
  sandboxLog.push(`destroy:${id}`);
1150
1156
  },
1151
1157
  pauseSandbox: async () => {},
1158
+ resumeSandbox: async () => {},
1152
1159
  snapshotSandbox: async () => ({
1153
1160
  sandboxId: "sb-1",
1154
1161
  providerId: "test",
@@ -1195,6 +1202,7 @@ describe("createSession edge cases", () => {
1195
1202
  sandboxLog.push(`destroy:${id}`);
1196
1203
  },
1197
1204
  pauseSandbox: async () => {},
1205
+ resumeSandbox: async () => {},
1198
1206
  snapshotSandbox: async () => ({
1199
1207
  sandboxId: "sb-1",
1200
1208
  providerId: "test",
@@ -1237,6 +1245,7 @@ describe("createSession edge cases", () => {
1237
1245
  pauseSandbox: async (id: string) => {
1238
1246
  sandboxLog.push(`pause:${id}`);
1239
1247
  },
1248
+ resumeSandbox: async () => {},
1240
1249
  snapshotSandbox: async () => ({
1241
1250
  sandboxId: "sb-1",
1242
1251
  providerId: "test",
@@ -1280,6 +1289,7 @@ describe("createSession edge cases", () => {
1280
1289
  pauseSandbox: async (id: string) => {
1281
1290
  sandboxLog.push(`pause:${id}`);
1282
1291
  },
1292
+ resumeSandbox: async () => {},
1283
1293
  snapshotSandbox: async () => ({
1284
1294
  sandboxId: "sb-1",
1285
1295
  providerId: "test",
@@ -1395,6 +1405,7 @@ describe("createSession edge cases", () => {
1395
1405
  pauseSandbox: async (id: string) => {
1396
1406
  sandboxLog.push(`pause:${id}`);
1397
1407
  },
1408
+ resumeSandbox: async () => {},
1398
1409
  snapshotSandbox: async () => ({
1399
1410
  sandboxId: "sb-1",
1400
1411
  providerId: "test",
@@ -1427,4 +1438,187 @@ describe("createSession edge cases", () => {
1427
1438
  expect(sandboxLog).toContain("pause:sb-err");
1428
1439
  expect(sandboxLog).not.toContain("destroy:sb-err");
1429
1440
  });
1441
+
1442
+ // --- sandboxShutdown: "keep-until-parent-close" ---
1443
+
1444
+ it("keeps sandbox running on exit when sandboxShutdown is keep-until-parent-close", async () => {
1445
+ const { ops } = createMockThreadOps();
1446
+ const sandboxLog: string[] = [];
1447
+
1448
+ const sandboxOps: SandboxOps = {
1449
+ createSandbox: async () => ({ sandboxId: "sb-keep-parent" }),
1450
+ destroySandbox: async (id: string) => {
1451
+ sandboxLog.push(`destroy:${id}`);
1452
+ },
1453
+ pauseSandbox: async (id: string) => {
1454
+ sandboxLog.push(`pause:${id}`);
1455
+ },
1456
+ resumeSandbox: async () => {},
1457
+ snapshotSandbox: async () => ({
1458
+ sandboxId: "sb-1",
1459
+ providerId: "test",
1460
+ data: null,
1461
+ createdAt: new Date().toISOString(),
1462
+ }),
1463
+ forkSandbox: async () => "forked-sb",
1464
+ };
1465
+
1466
+ const session = await createSession({
1467
+ agentName: "TestAgent",
1468
+ thread: { mode: "new", threadId: "thread-1" },
1469
+ runAgent: createScriptedRunAgent([{ message: "done", toolCalls: [] }]),
1470
+ threadOps: ops,
1471
+ buildContextMessage: () => "go",
1472
+ sandboxOps,
1473
+ sandboxShutdown: "keep-until-parent-close",
1474
+ });
1475
+
1476
+ const stateManager = createAgentStateManager({
1477
+ initialState: { systemPrompt: "test" },
1478
+ });
1479
+
1480
+ await session.runSession({ stateManager });
1481
+
1482
+ expect(sandboxLog).not.toContain("pause:sb-keep-parent");
1483
+ expect(sandboxLog).not.toContain("destroy:sb-keep-parent");
1484
+ });
1485
+
1486
+ it("keeps sandbox running on error when sandboxShutdown is keep-until-parent-close", async () => {
1487
+ const { ops } = createMockThreadOps();
1488
+ const sandboxLog: string[] = [];
1489
+
1490
+ const sandboxOps: SandboxOps = {
1491
+ createSandbox: async () => ({ sandboxId: "sb-keep-err" }),
1492
+ destroySandbox: async (id: string) => {
1493
+ sandboxLog.push(`destroy:${id}`);
1494
+ },
1495
+ pauseSandbox: async (id: string) => {
1496
+ sandboxLog.push(`pause:${id}`);
1497
+ },
1498
+ resumeSandbox: async () => {},
1499
+ snapshotSandbox: async () => ({
1500
+ sandboxId: "sb-1",
1501
+ providerId: "test",
1502
+ data: null,
1503
+ createdAt: new Date().toISOString(),
1504
+ }),
1505
+ forkSandbox: async () => "forked-sb",
1506
+ };
1507
+
1508
+ const session = await createSession({
1509
+ agentName: "TestAgent",
1510
+ thread: { mode: "new", threadId: "thread-1" },
1511
+ runAgent: async () => {
1512
+ throw new Error("crash");
1513
+ },
1514
+ threadOps: ops,
1515
+ buildContextMessage: () => "go",
1516
+ sandboxOps,
1517
+ sandboxShutdown: "keep-until-parent-close",
1518
+ });
1519
+
1520
+ const stateManager = createAgentStateManager({
1521
+ initialState: { systemPrompt: "test" },
1522
+ });
1523
+
1524
+ await expect(session.runSession({ stateManager })).rejects.toThrow(
1525
+ "crash"
1526
+ );
1527
+
1528
+ expect(sandboxLog).not.toContain("pause:sb-keep-err");
1529
+ expect(sandboxLog).not.toContain("destroy:sb-keep-err");
1530
+ });
1531
+
1532
+ // --- sandbox continue calls resumeSandbox only for pause-until-parent-close ---
1533
+
1534
+ it("calls resumeSandbox when sandbox mode is continue with pause-until-parent-close", async () => {
1535
+ const { ops } = createMockThreadOps();
1536
+ const sandboxLog: string[] = [];
1537
+
1538
+ const sandboxOps: SandboxOps = {
1539
+ createSandbox: async () => ({ sandboxId: "new-sb" }),
1540
+ destroySandbox: async (id: string) => {
1541
+ sandboxLog.push(`destroy:${id}`);
1542
+ },
1543
+ pauseSandbox: async (id: string) => {
1544
+ sandboxLog.push(`pause:${id}`);
1545
+ },
1546
+ resumeSandbox: async (id: string) => {
1547
+ sandboxLog.push(`resume:${id}`);
1548
+ },
1549
+ snapshotSandbox: async () => ({
1550
+ sandboxId: "sb-1",
1551
+ providerId: "test",
1552
+ data: null,
1553
+ createdAt: new Date().toISOString(),
1554
+ }),
1555
+ forkSandbox: async () => "forked-sb",
1556
+ };
1557
+
1558
+ const session = await createSession({
1559
+ agentName: "TestAgent",
1560
+ thread: { mode: "new", threadId: "thread-1" },
1561
+ runAgent: createScriptedRunAgent([{ message: "done", toolCalls: [] }]),
1562
+ threadOps: ops,
1563
+ buildContextMessage: () => "go",
1564
+ sandboxOps,
1565
+ sandbox: { mode: "continue", sandboxId: "paused-sb" },
1566
+ sandboxShutdown: "pause-until-parent-close",
1567
+ });
1568
+
1569
+ const stateManager = createAgentStateManager({
1570
+ initialState: { systemPrompt: "test" },
1571
+ });
1572
+
1573
+ await session.runSession({ stateManager });
1574
+
1575
+ expect(sandboxLog).toContain("resume:paused-sb");
1576
+ expect(sandboxLog).toContain("pause:paused-sb");
1577
+ });
1578
+
1579
+ it("skips resumeSandbox when sandbox mode is continue with keep-until-parent-close", async () => {
1580
+ const { ops } = createMockThreadOps();
1581
+ const sandboxLog: string[] = [];
1582
+
1583
+ const sandboxOps: SandboxOps = {
1584
+ createSandbox: async () => ({ sandboxId: "new-sb" }),
1585
+ destroySandbox: async (id: string) => {
1586
+ sandboxLog.push(`destroy:${id}`);
1587
+ },
1588
+ pauseSandbox: async (id: string) => {
1589
+ sandboxLog.push(`pause:${id}`);
1590
+ },
1591
+ resumeSandbox: async (id: string) => {
1592
+ sandboxLog.push(`resume:${id}`);
1593
+ },
1594
+ snapshotSandbox: async () => ({
1595
+ sandboxId: "sb-1",
1596
+ providerId: "test",
1597
+ data: null,
1598
+ createdAt: new Date().toISOString(),
1599
+ }),
1600
+ forkSandbox: async () => "forked-sb",
1601
+ };
1602
+
1603
+ const session = await createSession({
1604
+ agentName: "TestAgent",
1605
+ thread: { mode: "new", threadId: "thread-1" },
1606
+ runAgent: createScriptedRunAgent([{ message: "done", toolCalls: [] }]),
1607
+ threadOps: ops,
1608
+ buildContextMessage: () => "go",
1609
+ sandboxOps,
1610
+ sandbox: { mode: "continue", sandboxId: "kept-sb" },
1611
+ sandboxShutdown: "keep-until-parent-close",
1612
+ });
1613
+
1614
+ const stateManager = createAgentStateManager({
1615
+ initialState: { systemPrompt: "test" },
1616
+ });
1617
+
1618
+ await session.runSession({ stateManager });
1619
+
1620
+ expect(sandboxLog).not.toContain("resume:kept-sb");
1621
+ expect(sandboxLog).not.toContain("pause:kept-sb");
1622
+ expect(sandboxLog).not.toContain("destroy:kept-sb");
1623
+ });
1430
1624
  });