zeitlich 0.2.47 → 0.2.49

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 (50) hide show
  1. package/README.md +2 -0
  2. package/dist/{activities-CPwKoUlD.d.cts → activities-7OcT_vdR.d.cts} +3 -3
  3. package/dist/{activities-DlaBxNID.d.ts → activities-zG_FBoY2.d.ts} +3 -3
  4. package/dist/adapters/thread/anthropic/index.d.cts +5 -5
  5. package/dist/adapters/thread/anthropic/index.d.ts +5 -5
  6. package/dist/adapters/thread/anthropic/workflow.d.cts +5 -5
  7. package/dist/adapters/thread/anthropic/workflow.d.ts +5 -5
  8. package/dist/adapters/thread/google-genai/index.d.cts +5 -5
  9. package/dist/adapters/thread/google-genai/index.d.ts +5 -5
  10. package/dist/adapters/thread/google-genai/workflow.d.cts +6 -6
  11. package/dist/adapters/thread/google-genai/workflow.d.ts +6 -6
  12. package/dist/adapters/thread/langchain/index.d.cts +5 -5
  13. package/dist/adapters/thread/langchain/index.d.ts +5 -5
  14. package/dist/adapters/thread/langchain/workflow.d.cts +5 -5
  15. package/dist/adapters/thread/langchain/workflow.d.ts +5 -5
  16. package/dist/{cold-store-Z2wvK2cV.d.cts → cold-store-CkWoNtMh.d.cts} +1 -1
  17. package/dist/{cold-store-BDgJpwLI.d.ts → cold-store-DKMAO1Dd.d.ts} +1 -1
  18. package/dist/index.cjs +76 -10
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +8 -8
  21. package/dist/index.d.ts +8 -8
  22. package/dist/index.js +76 -10
  23. package/dist/index.js.map +1 -1
  24. package/dist/{proxy-CDh3Rsa7.d.cts → proxy-B7CWEV-T.d.cts} +1 -1
  25. package/dist/{proxy-Du8ggERu.d.ts → proxy-ByFHMVRX.d.ts} +1 -1
  26. package/dist/{thread-manager-DtHYws2F.d.ts → thread-manager-7AW4rhfu.d.ts} +2 -2
  27. package/dist/{thread-manager-D8zKNFZ9.d.cts → thread-manager-B9rtMEVn.d.cts} +2 -2
  28. package/dist/{thread-manager-BjoYYXgd.d.cts → thread-manager-Cibe0X5m.d.cts} +2 -2
  29. package/dist/{thread-manager-Dw96FKH1.d.ts → thread-manager-nK-WcFzM.d.ts} +2 -2
  30. package/dist/{types-BMJrsHo0.d.cts → types-BR-k7h0e.d.cts} +1 -1
  31. package/dist/{types-CtdOquo3.d.ts → types-DO4Tkwxo.d.ts} +1 -1
  32. package/dist/{types-qQVZfhoT.d.ts → types-DeVNWqlb.d.ts} +54 -0
  33. package/dist/{types-DNEl5uxQ.d.cts → types-XUUFvrJ9.d.cts} +54 -0
  34. package/dist/{workflow-BH9ImDGq.d.cts → workflow-KbGsxpfh.d.cts} +1 -1
  35. package/dist/{workflow-Cdw3-RNB.d.ts → workflow-uhOIj9D-.d.ts} +1 -1
  36. package/dist/workflow.cjs +76 -10
  37. package/dist/workflow.cjs.map +1 -1
  38. package/dist/workflow.d.cts +2 -2
  39. package/dist/workflow.d.ts +2 -2
  40. package/dist/workflow.js +76 -10
  41. package/dist/workflow.js.map +1 -1
  42. package/package.json +6 -6
  43. package/src/lib/lifecycle.ts +13 -1
  44. package/src/lib/session/session-edge-cases.integration.test.ts +44 -0
  45. package/src/lib/session/session.ts +26 -0
  46. package/src/lib/subagent/handler.ts +55 -6
  47. package/src/lib/subagent/subagent.integration.test.ts +239 -2
  48. package/src/lib/tool-router/router-edge-cases.integration.test.ts +36 -0
  49. package/src/lib/tool-router/router.ts +29 -3
  50. package/src/lib/tool-router/types.ts +43 -0
@@ -1,5 +1,5 @@
1
1
  import { ActivityOptions, ActivityInterfaceFor } from '@temporalio/workflow';
2
- import { T as ThreadOps } from './types-DNEl5uxQ.cjs';
2
+ import { T as ThreadOps } from './types-XUUFvrJ9.cjs';
3
3
 
4
4
  /**
5
5
  * Shared proxy helper for thread operations.
@@ -1,5 +1,5 @@
1
1
  import { ActivityOptions, ActivityInterfaceFor } from '@temporalio/workflow';
2
- import { T as ThreadOps } from './types-qQVZfhoT.js';
2
+ import { T as ThreadOps } from './types-DeVNWqlb.js';
3
3
 
4
4
  /**
5
5
  * Shared proxy helper for thread operations.
@@ -1,7 +1,7 @@
1
1
  import Redis from 'ioredis';
2
2
  import Anthropic from '@anthropic-ai/sdk';
3
- import { J as JsonValue } from './types-qQVZfhoT.js';
4
- import { T as ThreadManagerHooks, P as ProviderThreadManager } from './types-CtdOquo3.js';
3
+ import { J as JsonValue } from './types-DeVNWqlb.js';
4
+ import { T as ThreadManagerHooks, P as ProviderThreadManager } from './types-DO4Tkwxo.js';
5
5
 
6
6
  /** SDK-native content type for Anthropic human messages */
7
7
  type AnthropicContent = string | Anthropic.Messages.ContentBlockParam[];
@@ -1,7 +1,7 @@
1
1
  import Redis from 'ioredis';
2
2
  import Anthropic from '@anthropic-ai/sdk';
3
- import { J as JsonValue } from './types-DNEl5uxQ.cjs';
4
- import { T as ThreadManagerHooks, P as ProviderThreadManager } from './types-BMJrsHo0.cjs';
3
+ import { J as JsonValue } from './types-XUUFvrJ9.cjs';
4
+ import { T as ThreadManagerHooks, P as ProviderThreadManager } from './types-BR-k7h0e.cjs';
5
5
 
6
6
  /** SDK-native content type for Anthropic human messages */
7
7
  type AnthropicContent = string | Anthropic.Messages.ContentBlockParam[];
@@ -1,7 +1,7 @@
1
1
  import Redis from 'ioredis';
2
- import { J as JsonValue } from './types-DNEl5uxQ.cjs';
2
+ import { J as JsonValue } from './types-XUUFvrJ9.cjs';
3
3
  import { MessageContent, StoredMessage, BaseMessage } from '@langchain/core/messages';
4
- import { T as ThreadManagerHooks, P as ProviderThreadManager } from './types-BMJrsHo0.cjs';
4
+ import { T as ThreadManagerHooks, P as ProviderThreadManager } from './types-BR-k7h0e.cjs';
5
5
 
6
6
  /** SDK-native content type for LangChain human messages */
7
7
  type LangChainContent = string | MessageContent;
@@ -1,7 +1,7 @@
1
1
  import Redis from 'ioredis';
2
- import { J as JsonValue } from './types-qQVZfhoT.js';
2
+ import { J as JsonValue } from './types-DeVNWqlb.js';
3
3
  import { MessageContent, StoredMessage, BaseMessage } from '@langchain/core/messages';
4
- import { T as ThreadManagerHooks, P as ProviderThreadManager } from './types-CtdOquo3.js';
4
+ import { T as ThreadManagerHooks, P as ProviderThreadManager } from './types-DO4Tkwxo.js';
5
5
 
6
6
  /** SDK-native content type for LangChain human messages */
7
7
  type LangChainContent = string | MessageContent;
@@ -1,5 +1,5 @@
1
1
  import Redis from 'ioredis';
2
- import { P as PersistedThreadState, J as JsonValue } from './types-DNEl5uxQ.cjs';
2
+ import { P as PersistedThreadState, J as JsonValue } from './types-XUUFvrJ9.cjs';
3
3
 
4
4
  interface ThreadManagerConfig<T> {
5
5
  redis: Redis;
@@ -1,5 +1,5 @@
1
1
  import Redis from 'ioredis';
2
- import { P as PersistedThreadState, J as JsonValue } from './types-qQVZfhoT.js';
2
+ import { P as PersistedThreadState, J as JsonValue } from './types-DeVNWqlb.js';
3
3
 
4
4
  interface ThreadManagerConfig<T> {
5
5
  redis: Redis;
@@ -134,6 +134,32 @@ interface RouterContext {
134
134
  toolCallId: string;
135
135
  toolName: string;
136
136
  sandboxId?: string;
137
+ /**
138
+ * Id of the assistant message that issued this tool call (the message
139
+ * the session passed as `assistantMessageId` into `runAgent`). Present
140
+ * for any tool call processed through `processToolCalls` from a
141
+ * session; may be absent when the router is driven manually (e.g.
142
+ * tests, custom orchestrators).
143
+ *
144
+ * Subagent handlers that fork the parent's thread mid-call use this
145
+ * to truncate the orphan trailing assistant message from the forked
146
+ * thread so the child's first model call sees a well-formed history.
147
+ */
148
+ assistantMessageId?: string;
149
+ /**
150
+ * Persist the parent session's current `PersistedThreadState` slice
151
+ * (tasks + custom state) to the durable thread store. Wired up by
152
+ * the session — absent for manually-driven routers (tests, custom
153
+ * orchestrators).
154
+ *
155
+ * Subagent handlers invoke this before spawning a child that will
156
+ * read the parent's thread (`newThreadSource: "from-parent"` or an
157
+ * explicit parent threadId): the parent's slice otherwise only
158
+ * lands in storage at session-exit time, so the child would load a
159
+ * stale (or empty) snapshot. Best-effort — failures are logged by
160
+ * the session but never thrown.
161
+ */
162
+ persistThreadState?: () => Promise<void>;
137
163
  }
138
164
  /**
139
165
  * A handler function for a specific tool.
@@ -201,6 +227,23 @@ interface ProcessToolCallsContext {
201
227
  turn?: number;
202
228
  /** Active sandbox ID (when a sandbox is configured for this session) */
203
229
  sandboxId?: string;
230
+ /**
231
+ * Id of the assistant message that produced these tool calls. The
232
+ * router forwards it into every handler's {@link RouterContext} so
233
+ * handlers can reference the message they were issued from (e.g.
234
+ * subagent forks that need to truncate the orphan assistant message
235
+ * out of a parent-forked thread).
236
+ */
237
+ assistantMessageId?: string;
238
+ /**
239
+ * Optional callback that flushes the session's in-memory
240
+ * `PersistedThreadState` slice to the durable thread store. The
241
+ * router forwards it into every handler's {@link RouterContext}
242
+ * verbatim. The session uses this to let mid-loop tool handlers
243
+ * (notably subagents that fork or continue the parent's thread)
244
+ * persist the parent's slice before the child reads it.
245
+ */
246
+ persistThreadState?: () => Promise<void>;
204
247
  }
205
248
  /**
206
249
  * Signal that a tool handler requested a rewind. Attached to the
@@ -887,6 +930,14 @@ interface Hooks<T extends ToolMap, TResult = unknown, TContent = unknown> extend
887
930
  * continue there. When the adapter has `onForkPrepareThread` and/or
888
931
  * `onForkTransform` hooks configured, they are applied once to the forked
889
932
  * thread before the session starts.
933
+ *
934
+ * The optional `truncateAfterFork.fromMessageId` directs the session to
935
+ * call `truncateThread` on the freshly forked thread immediately after
936
+ * the fork, dropping that message and everything after. Used by
937
+ * subagents that fork their parent's thread mid-tool-call to strip the
938
+ * orphan assistant `tool_use` block (the one whose `tool_result` will
939
+ * never arrive in the child's thread) so the first model call doesn't
940
+ * reject on an unmatched tool-use/tool-result pair.
890
941
  */
891
942
  type ThreadInit = {
892
943
  mode: "new";
@@ -897,6 +948,9 @@ type ThreadInit = {
897
948
  } | {
898
949
  mode: "fork";
899
950
  threadId: string;
951
+ truncateAfterFork?: {
952
+ fromMessageId: string;
953
+ };
900
954
  };
901
955
 
902
956
  /**
@@ -134,6 +134,32 @@ interface RouterContext {
134
134
  toolCallId: string;
135
135
  toolName: string;
136
136
  sandboxId?: string;
137
+ /**
138
+ * Id of the assistant message that issued this tool call (the message
139
+ * the session passed as `assistantMessageId` into `runAgent`). Present
140
+ * for any tool call processed through `processToolCalls` from a
141
+ * session; may be absent when the router is driven manually (e.g.
142
+ * tests, custom orchestrators).
143
+ *
144
+ * Subagent handlers that fork the parent's thread mid-call use this
145
+ * to truncate the orphan trailing assistant message from the forked
146
+ * thread so the child's first model call sees a well-formed history.
147
+ */
148
+ assistantMessageId?: string;
149
+ /**
150
+ * Persist the parent session's current `PersistedThreadState` slice
151
+ * (tasks + custom state) to the durable thread store. Wired up by
152
+ * the session — absent for manually-driven routers (tests, custom
153
+ * orchestrators).
154
+ *
155
+ * Subagent handlers invoke this before spawning a child that will
156
+ * read the parent's thread (`newThreadSource: "from-parent"` or an
157
+ * explicit parent threadId): the parent's slice otherwise only
158
+ * lands in storage at session-exit time, so the child would load a
159
+ * stale (or empty) snapshot. Best-effort — failures are logged by
160
+ * the session but never thrown.
161
+ */
162
+ persistThreadState?: () => Promise<void>;
137
163
  }
138
164
  /**
139
165
  * A handler function for a specific tool.
@@ -201,6 +227,23 @@ interface ProcessToolCallsContext {
201
227
  turn?: number;
202
228
  /** Active sandbox ID (when a sandbox is configured for this session) */
203
229
  sandboxId?: string;
230
+ /**
231
+ * Id of the assistant message that produced these tool calls. The
232
+ * router forwards it into every handler's {@link RouterContext} so
233
+ * handlers can reference the message they were issued from (e.g.
234
+ * subagent forks that need to truncate the orphan assistant message
235
+ * out of a parent-forked thread).
236
+ */
237
+ assistantMessageId?: string;
238
+ /**
239
+ * Optional callback that flushes the session's in-memory
240
+ * `PersistedThreadState` slice to the durable thread store. The
241
+ * router forwards it into every handler's {@link RouterContext}
242
+ * verbatim. The session uses this to let mid-loop tool handlers
243
+ * (notably subagents that fork or continue the parent's thread)
244
+ * persist the parent's slice before the child reads it.
245
+ */
246
+ persistThreadState?: () => Promise<void>;
204
247
  }
205
248
  /**
206
249
  * Signal that a tool handler requested a rewind. Attached to the
@@ -887,6 +930,14 @@ interface Hooks<T extends ToolMap, TResult = unknown, TContent = unknown> extend
887
930
  * continue there. When the adapter has `onForkPrepareThread` and/or
888
931
  * `onForkTransform` hooks configured, they are applied once to the forked
889
932
  * thread before the session starts.
933
+ *
934
+ * The optional `truncateAfterFork.fromMessageId` directs the session to
935
+ * call `truncateThread` on the freshly forked thread immediately after
936
+ * the fork, dropping that message and everything after. Used by
937
+ * subagents that fork their parent's thread mid-tool-call to strip the
938
+ * orphan assistant `tool_use` block (the one whose `tool_result` will
939
+ * never arrive in the child's thread) so the first model call doesn't
940
+ * reject on an unmatched tool-use/tool-result pair.
890
941
  */
891
942
  type ThreadInit = {
892
943
  mode: "new";
@@ -897,6 +948,9 @@ type ThreadInit = {
897
948
  } | {
898
949
  mode: "fork";
899
950
  threadId: string;
951
+ truncateAfterFork?: {
952
+ fromMessageId: string;
953
+ };
900
954
  };
901
955
 
902
956
  /**
@@ -1,4 +1,4 @@
1
- import { au as ToolMap, _ as SandboxInit, ah as SubagentSandboxShutdown, a1 as SessionConfig, aF as ZeitlichSession, $ as SandboxShutdown, am as ThreadInit, u as JsonSerializable, B as BaseAgentState, p as AgentStateManager, aH as ToolRouterOptions, ax as ToolRouter, R as RouterContext, J as JsonValue, az as ToolWithHandler, w as ParsedToolCallUnion, av as ToolNames, ac as SubagentDefinition, af as SubagentHooks, ag as SubagentSandboxConfig, aa as SubagentConfig, ai as SubagentSessionInput, ad as SubagentFnResult, a7 as SessionStartHook, a2 as SessionEndHook, E as PostToolUseHook, z as PostToolUseFailureHook, a4 as SessionExitReason, an as TokenUsage, f as RunAgentConfig, A as AgentResponse, F as FileEntryMetadata, aB as VirtualFileTree, k as TreeMutation, s as FileEntry, aC as VirtualFsOps, h as SkillMetadata, i as Skill, c as ToolHandlerResponse, as as ToolHandler, aE as WorkflowTask, d as ActivityToolHandler } from './types-DNEl5uxQ.cjs';
1
+ import { au as ToolMap, _ as SandboxInit, ah as SubagentSandboxShutdown, a1 as SessionConfig, aF as ZeitlichSession, $ as SandboxShutdown, am as ThreadInit, u as JsonSerializable, B as BaseAgentState, p as AgentStateManager, aH as ToolRouterOptions, ax as ToolRouter, R as RouterContext, J as JsonValue, az as ToolWithHandler, w as ParsedToolCallUnion, av as ToolNames, ac as SubagentDefinition, af as SubagentHooks, ag as SubagentSandboxConfig, aa as SubagentConfig, ai as SubagentSessionInput, ad as SubagentFnResult, a7 as SessionStartHook, a2 as SessionEndHook, E as PostToolUseHook, z as PostToolUseFailureHook, a4 as SessionExitReason, an as TokenUsage, f as RunAgentConfig, A as AgentResponse, F as FileEntryMetadata, aB as VirtualFileTree, k as TreeMutation, s as FileEntry, aC as VirtualFsOps, h as SkillMetadata, i as Skill, c as ToolHandlerResponse, as as ToolHandler, aE as WorkflowTask, d as ActivityToolHandler } from './types-XUUFvrJ9.cjs';
2
2
  import z$1, { z } from 'zod';
3
3
  import './types-CJ7tCdl6.cjs';
4
4
  import { Duration } from '@temporalio/common';
@@ -1,4 +1,4 @@
1
- import { au as ToolMap, _ as SandboxInit, ah as SubagentSandboxShutdown, a1 as SessionConfig, aF as ZeitlichSession, $ as SandboxShutdown, am as ThreadInit, u as JsonSerializable, B as BaseAgentState, p as AgentStateManager, aH as ToolRouterOptions, ax as ToolRouter, R as RouterContext, J as JsonValue, az as ToolWithHandler, w as ParsedToolCallUnion, av as ToolNames, ac as SubagentDefinition, af as SubagentHooks, ag as SubagentSandboxConfig, aa as SubagentConfig, ai as SubagentSessionInput, ad as SubagentFnResult, a7 as SessionStartHook, a2 as SessionEndHook, E as PostToolUseHook, z as PostToolUseFailureHook, a4 as SessionExitReason, an as TokenUsage, f as RunAgentConfig, A as AgentResponse, F as FileEntryMetadata, aB as VirtualFileTree, k as TreeMutation, s as FileEntry, aC as VirtualFsOps, h as SkillMetadata, i as Skill, c as ToolHandlerResponse, as as ToolHandler, aE as WorkflowTask, d as ActivityToolHandler } from './types-qQVZfhoT.js';
1
+ import { au as ToolMap, _ as SandboxInit, ah as SubagentSandboxShutdown, a1 as SessionConfig, aF as ZeitlichSession, $ as SandboxShutdown, am as ThreadInit, u as JsonSerializable, B as BaseAgentState, p as AgentStateManager, aH as ToolRouterOptions, ax as ToolRouter, R as RouterContext, J as JsonValue, az as ToolWithHandler, w as ParsedToolCallUnion, av as ToolNames, ac as SubagentDefinition, af as SubagentHooks, ag as SubagentSandboxConfig, aa as SubagentConfig, ai as SubagentSessionInput, ad as SubagentFnResult, a7 as SessionStartHook, a2 as SessionEndHook, E as PostToolUseHook, z as PostToolUseFailureHook, a4 as SessionExitReason, an as TokenUsage, f as RunAgentConfig, A as AgentResponse, F as FileEntryMetadata, aB as VirtualFileTree, k as TreeMutation, s as FileEntry, aC as VirtualFsOps, h as SkillMetadata, i as Skill, c as ToolHandlerResponse, as as ToolHandler, aE as WorkflowTask, d as ActivityToolHandler } from './types-DeVNWqlb.js';
2
2
  import z$1, { z } from 'zod';
3
3
  import './types-CJ7tCdl6.js';
4
4
  import { Duration } from '@temporalio/common';
package/dist/workflow.cjs CHANGED
@@ -123,7 +123,7 @@ function createToolRouter(options) {
123
123
  });
124
124
  }
125
125
  }
126
- async function processToolCall(toolCall, turn, sandboxId, onRewindRequested) {
126
+ async function processToolCall(toolCall, turn, sandboxId, onRewindRequested, assistantMessageId, persistThreadState) {
127
127
  const startTime = Date.now();
128
128
  const tool = toolMap.get(toolCall.name);
129
129
  const preResult = await runPreHooks(toolCall, tool, turn);
@@ -158,7 +158,9 @@ function createToolRouter(options) {
158
158
  ...options.threadKey && { threadKey: options.threadKey },
159
159
  toolCallId: toolCall.id,
160
160
  toolName: toolCall.name,
161
- ...sandboxId !== void 0 && { sandboxId }
161
+ ...sandboxId !== void 0 && { sandboxId },
162
+ ...assistantMessageId !== void 0 && { assistantMessageId },
163
+ ...persistThreadState !== void 0 && { persistThreadState }
162
164
  };
163
165
  const response = await tool.handler(
164
166
  effectiveArgs,
@@ -285,6 +287,8 @@ function createToolRouter(options) {
285
287
  }
286
288
  const turn = context?.turn ?? 0;
287
289
  const sandboxId = context?.sandboxId;
290
+ const assistantMessageId = context?.assistantMessageId;
291
+ const persistThreadState = context?.persistThreadState;
288
292
  let rewindSignal;
289
293
  if (options.parallel) {
290
294
  const scope = new workflow.CancellationScope({ cancellable: true });
@@ -297,7 +301,14 @@ function createToolRouter(options) {
297
301
  const outcomes = await scope.run(
298
302
  async () => Promise.allSettled(
299
303
  toolCalls.map(
300
- (tc) => processToolCall(tc, turn, sandboxId, onRewindRequested)
304
+ (tc) => processToolCall(
305
+ tc,
306
+ turn,
307
+ sandboxId,
308
+ onRewindRequested,
309
+ assistantMessageId,
310
+ persistThreadState
311
+ )
301
312
  )
302
313
  )
303
314
  );
@@ -317,7 +328,14 @@ function createToolRouter(options) {
317
328
  }
318
329
  const results = [];
319
330
  for (const toolCall of toolCalls) {
320
- const outcome = await processToolCall(toolCall, turn, sandboxId);
331
+ const outcome = await processToolCall(
332
+ toolCall,
333
+ turn,
334
+ sandboxId,
335
+ void 0,
336
+ assistantMessageId,
337
+ persistThreadState
338
+ );
321
339
  if (outcome.kind === "rewind") {
322
340
  rewindSignal = outcome.signal;
323
341
  break;
@@ -341,6 +359,12 @@ function createToolRouter(options) {
341
359
  toolName: toolCall.name,
342
360
  ...context?.sandboxId !== void 0 && {
343
361
  sandboxId: context.sandboxId
362
+ },
363
+ ...context?.assistantMessageId !== void 0 && {
364
+ assistantMessageId: context.assistantMessageId
365
+ },
366
+ ...context?.persistThreadState !== void 0 && {
367
+ persistThreadState: context.persistThreadState
344
368
  }
345
369
  };
346
370
  const response = await handler(
@@ -567,13 +591,26 @@ function createSubagentHandler(subagents) {
567
591
  const threadMode = config.thread ?? "new";
568
592
  const allowsContinuation = threadMode !== "new";
569
593
  const newThreadSource = config.newThreadSource ?? "new";
570
- const continuationThreadId = !allowsContinuation ? void 0 : args.threadId ?? (newThreadSource === "from-parent" ? context.threadId : void 0);
594
+ const usingParentFallback = allowsContinuation && !args.threadId && newThreadSource === "from-parent";
595
+ const continuationThreadId = !allowsContinuation ? void 0 : args.threadId ?? (usingParentFallback ? context.threadId : void 0);
571
596
  let thread;
572
597
  if (continuationThreadId) {
573
- thread = {
574
- mode: threadMode,
575
- threadId: continuationThreadId
576
- };
598
+ if (threadMode === "fork") {
599
+ thread = {
600
+ mode: "fork",
601
+ threadId: continuationThreadId,
602
+ ...usingParentFallback && context.assistantMessageId ? {
603
+ truncateAfterFork: {
604
+ fromMessageId: context.assistantMessageId
605
+ }
606
+ } : {}
607
+ };
608
+ } else {
609
+ thread = {
610
+ mode: "continue",
611
+ threadId: continuationThreadId
612
+ };
613
+ }
577
614
  }
578
615
  let sandbox;
579
616
  let sandboxShutdownOverride;
@@ -685,6 +722,17 @@ function createSubagentHandler(subagents) {
685
722
  if (isSnapshotBaseCreator) {
686
723
  snapshotBaseCreatorAgent.set(childWorkflowId, config.agentName);
687
724
  }
725
+ if (continuationThreadId && continuationThreadId === context.threadId && context.persistThreadState) {
726
+ try {
727
+ await context.persistThreadState();
728
+ } catch (err) {
729
+ workflow.log.warn("failed to persist parent thread state for subagent", {
730
+ subagent: config.agentName,
731
+ childWorkflowId,
732
+ error: err instanceof Error ? err.message : String(err)
733
+ });
734
+ }
735
+ }
688
736
  workflow.log.info("subagent spawned", {
689
737
  subagent: config.agentName,
690
738
  childWorkflowId,
@@ -1034,6 +1082,7 @@ async function createSession(config) {
1034
1082
  appendSystemMessage,
1035
1083
  appendAgentMessage,
1036
1084
  forkThread,
1085
+ truncateThread,
1037
1086
  loadThreadState,
1038
1087
  saveThreadState,
1039
1088
  hydrateThread,
@@ -1183,6 +1232,10 @@ async function createSession(config) {
1183
1232
  if (threadMode === "fork" && sourceThreadId) {
1184
1233
  await hydrateThread(sourceThreadId, threadKey);
1185
1234
  await forkThread(sourceThreadId, threadId, threadKey);
1235
+ const truncate = threadInit.truncateAfterFork;
1236
+ if (truncate?.fromMessageId) {
1237
+ await truncateThread(threadId, truncate.fromMessageId, threadKey);
1238
+ }
1186
1239
  const forkedSlice = await loadThreadState(threadId, threadKey);
1187
1240
  if (forkedSlice) rehydrateFromSlice(forkedSlice);
1188
1241
  } else if (threadMode === "continue") {
@@ -1312,7 +1365,20 @@ async function createSession(config) {
1312
1365
  parsedToolCalls,
1313
1366
  {
1314
1367
  turn: currentTurn,
1315
- ...sandboxId !== void 0 && { sandboxId }
1368
+ ...sandboxId !== void 0 && { sandboxId },
1369
+ ...assistantId !== void 0 && {
1370
+ assistantMessageId: assistantId
1371
+ },
1372
+ // Hand handlers a way to persist the parent's slice
1373
+ // mid-loop (subagents that fork or continue the parent's
1374
+ // thread need this — otherwise the child loads a stale
1375
+ // snapshot from the prior session, since `saveThreadState`
1376
+ // would otherwise only run in the `finally` below).
1377
+ persistThreadState: () => saveThreadState(
1378
+ threadId,
1379
+ stateManager.getPersistedSlice(),
1380
+ threadKey
1381
+ )
1316
1382
  }
1317
1383
  );
1318
1384
  for (const result of toolCallResults) {