zeitlich 0.2.48 → 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.
- package/dist/{activities-BlQR5gX4.d.cts → activities-7OcT_vdR.d.cts} +3 -3
- package/dist/{activities-DCaIPQBT.d.ts → activities-zG_FBoY2.d.ts} +3 -3
- package/dist/adapters/thread/anthropic/index.d.cts +5 -5
- package/dist/adapters/thread/anthropic/index.d.ts +5 -5
- package/dist/adapters/thread/anthropic/workflow.d.cts +5 -5
- package/dist/adapters/thread/anthropic/workflow.d.ts +5 -5
- package/dist/adapters/thread/google-genai/index.d.cts +5 -5
- package/dist/adapters/thread/google-genai/index.d.ts +5 -5
- package/dist/adapters/thread/google-genai/workflow.d.cts +6 -6
- package/dist/adapters/thread/google-genai/workflow.d.ts +6 -6
- package/dist/adapters/thread/langchain/index.d.cts +5 -5
- package/dist/adapters/thread/langchain/index.d.ts +5 -5
- package/dist/adapters/thread/langchain/workflow.d.cts +5 -5
- package/dist/adapters/thread/langchain/workflow.d.ts +5 -5
- package/dist/{cold-store-UL13Sstw.d.cts → cold-store-CkWoNtMh.d.cts} +1 -1
- package/dist/{cold-store-aD4TSKlU.d.ts → cold-store-DKMAO1Dd.d.ts} +1 -1
- package/dist/index.cjs +33 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -8
- package/dist/index.d.ts +8 -8
- package/dist/index.js +33 -5
- package/dist/index.js.map +1 -1
- package/dist/{proxy-BAty3CWM.d.cts → proxy-B7CWEV-T.d.cts} +1 -1
- package/dist/{proxy-mbnwBhHw.d.ts → proxy-ByFHMVRX.d.ts} +1 -1
- package/dist/{thread-manager-DtEtbUkp.d.ts → thread-manager-7AW4rhfu.d.ts} +2 -2
- package/dist/{thread-manager-R6c3lnJy.d.cts → thread-manager-B9rtMEVn.d.cts} +2 -2
- package/dist/{thread-manager-DsXvJ5cJ.d.cts → thread-manager-Cibe0X5m.d.cts} +2 -2
- package/dist/{thread-manager-CICj68PI.d.ts → thread-manager-nK-WcFzM.d.ts} +2 -2
- package/dist/{types-DDLPnxBh.d.cts → types-BR-k7h0e.d.cts} +1 -1
- package/dist/{types-DF4wzWQG.d.ts → types-DO4Tkwxo.d.ts} +1 -1
- package/dist/{types-DwBYd0ij.d.ts → types-DeVNWqlb.d.ts} +23 -0
- package/dist/{types-DWeyCTYK.d.cts → types-XUUFvrJ9.d.cts} +23 -0
- package/dist/{workflow-DVNPR7eX.d.cts → workflow-KbGsxpfh.d.cts} +1 -1
- package/dist/{workflow-DdaU7_j4.d.ts → workflow-uhOIj9D-.d.ts} +1 -1
- package/dist/workflow.cjs +33 -5
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +2 -2
- package/dist/workflow.d.ts +2 -2
- package/dist/workflow.js +33 -5
- package/dist/workflow.js.map +1 -1
- package/package.json +1 -1
- package/src/lib/session/session.ts +11 -0
- package/src/lib/subagent/handler.ts +23 -0
- package/src/lib/subagent/subagent.integration.test.ts +198 -0
- package/src/lib/tool-router/router.ts +11 -3
- package/src/lib/tool-router/types.ts +23 -0
package/package.json
CHANGED
|
@@ -585,6 +585,17 @@ export async function createSession<
|
|
|
585
585
|
...(assistantId !== undefined && {
|
|
586
586
|
assistantMessageId: assistantId,
|
|
587
587
|
}),
|
|
588
|
+
// Hand handlers a way to persist the parent's slice
|
|
589
|
+
// mid-loop (subagents that fork or continue the parent's
|
|
590
|
+
// thread need this — otherwise the child loads a stale
|
|
591
|
+
// snapshot from the prior session, since `saveThreadState`
|
|
592
|
+
// would otherwise only run in the `finally` below).
|
|
593
|
+
persistThreadState: () =>
|
|
594
|
+
saveThreadState(
|
|
595
|
+
threadId,
|
|
596
|
+
stateManager.getPersistedSlice(),
|
|
597
|
+
threadKey
|
|
598
|
+
),
|
|
588
599
|
}
|
|
589
600
|
);
|
|
590
601
|
|
|
@@ -439,6 +439,29 @@ export function createSubagentHandler<
|
|
|
439
439
|
snapshotBaseCreatorAgent.set(childWorkflowId, config.agentName);
|
|
440
440
|
}
|
|
441
441
|
|
|
442
|
+
// The parent's `PersistedThreadState` slice (`tasks` + custom
|
|
443
|
+
// state) only lands in storage in the session's `finally`. When
|
|
444
|
+
// the child reads from the parent's thread (`from-parent`
|
|
445
|
+
// fallback or an explicit args.threadId pointing at the parent),
|
|
446
|
+
// that `loadThreadState` would otherwise see the prior session's
|
|
447
|
+
// snapshot. Flush the live slice now via the session-supplied
|
|
448
|
+
// callback so the child sees the parent's current state.
|
|
449
|
+
if (
|
|
450
|
+
continuationThreadId &&
|
|
451
|
+
continuationThreadId === context.threadId &&
|
|
452
|
+
context.persistThreadState
|
|
453
|
+
) {
|
|
454
|
+
try {
|
|
455
|
+
await context.persistThreadState();
|
|
456
|
+
} catch (err) {
|
|
457
|
+
log.warn("failed to persist parent thread state for subagent", {
|
|
458
|
+
subagent: config.agentName,
|
|
459
|
+
childWorkflowId,
|
|
460
|
+
error: err instanceof Error ? err.message : String(err),
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
442
465
|
log.info("subagent spawned", {
|
|
443
466
|
subagent: config.agentName,
|
|
444
467
|
childWorkflowId,
|
|
@@ -813,6 +813,204 @@ describe("createSubagentHandler", () => {
|
|
|
813
813
|
expect(workflowInput.thread).toBeUndefined();
|
|
814
814
|
});
|
|
815
815
|
|
|
816
|
+
// --- persistThreadState: parent slice flush before child loads ---
|
|
817
|
+
|
|
818
|
+
it("calls persistThreadState before executeChild when forking parent's thread", async () => {
|
|
819
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
820
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
821
|
+
const persistThreadState = vi.fn(async () => undefined);
|
|
822
|
+
|
|
823
|
+
const subagent: SubagentConfig = {
|
|
824
|
+
agentName: "parent-fork-persist",
|
|
825
|
+
description: "Forks parent thread",
|
|
826
|
+
workflow: mockWorkflow(),
|
|
827
|
+
thread: "fork",
|
|
828
|
+
newThreadSource: "from-parent",
|
|
829
|
+
};
|
|
830
|
+
|
|
831
|
+
const { handler } = createSubagentHandler([subagent]);
|
|
832
|
+
|
|
833
|
+
await handler(
|
|
834
|
+
{
|
|
835
|
+
subagent: "parent-fork-persist",
|
|
836
|
+
description: "test",
|
|
837
|
+
prompt: "test",
|
|
838
|
+
},
|
|
839
|
+
{
|
|
840
|
+
threadId: "parent-t",
|
|
841
|
+
toolCallId: "tc",
|
|
842
|
+
toolName: "Subagent",
|
|
843
|
+
persistThreadState,
|
|
844
|
+
}
|
|
845
|
+
);
|
|
846
|
+
|
|
847
|
+
expect(persistThreadState).toHaveBeenCalledTimes(1);
|
|
848
|
+
const lastExecOrder =
|
|
849
|
+
execMock.mock.invocationCallOrder[
|
|
850
|
+
execMock.mock.invocationCallOrder.length - 1
|
|
851
|
+
] ?? Infinity;
|
|
852
|
+
expect(persistThreadState.mock.invocationCallOrder[0]).toBeLessThan(
|
|
853
|
+
lastExecOrder
|
|
854
|
+
);
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
it("calls persistThreadState before executeChild when continuing parent's thread", async () => {
|
|
858
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
859
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
860
|
+
const persistThreadState = vi.fn(async () => undefined);
|
|
861
|
+
|
|
862
|
+
const subagent: SubagentConfig = {
|
|
863
|
+
agentName: "parent-continue-persist",
|
|
864
|
+
description: "Continues parent thread",
|
|
865
|
+
workflow: mockWorkflow(),
|
|
866
|
+
thread: "continue",
|
|
867
|
+
newThreadSource: "from-parent",
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
const { handler } = createSubagentHandler([subagent]);
|
|
871
|
+
|
|
872
|
+
await handler(
|
|
873
|
+
{
|
|
874
|
+
subagent: "parent-continue-persist",
|
|
875
|
+
description: "test",
|
|
876
|
+
prompt: "test",
|
|
877
|
+
},
|
|
878
|
+
{
|
|
879
|
+
threadId: "parent-t",
|
|
880
|
+
toolCallId: "tc",
|
|
881
|
+
toolName: "Subagent",
|
|
882
|
+
persistThreadState,
|
|
883
|
+
}
|
|
884
|
+
);
|
|
885
|
+
|
|
886
|
+
expect(persistThreadState).toHaveBeenCalledTimes(1);
|
|
887
|
+
const lastExecOrder =
|
|
888
|
+
execMock.mock.invocationCallOrder[
|
|
889
|
+
execMock.mock.invocationCallOrder.length - 1
|
|
890
|
+
] ?? Infinity;
|
|
891
|
+
expect(persistThreadState.mock.invocationCallOrder[0]).toBeLessThan(
|
|
892
|
+
lastExecOrder
|
|
893
|
+
);
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
it("calls persistThreadState when args.threadId points at the parent's thread", async () => {
|
|
897
|
+
const persistThreadState = vi.fn(async () => undefined);
|
|
898
|
+
|
|
899
|
+
const subagent: SubagentConfig = {
|
|
900
|
+
agentName: "explicit-parent",
|
|
901
|
+
description: "Explicit parent threadId",
|
|
902
|
+
workflow: mockWorkflow(),
|
|
903
|
+
thread: "continue",
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
const { handler } = createSubagentHandler([subagent]);
|
|
907
|
+
|
|
908
|
+
await handler(
|
|
909
|
+
{
|
|
910
|
+
subagent: "explicit-parent",
|
|
911
|
+
description: "test",
|
|
912
|
+
prompt: "test",
|
|
913
|
+
threadId: "parent-t",
|
|
914
|
+
},
|
|
915
|
+
{
|
|
916
|
+
threadId: "parent-t",
|
|
917
|
+
toolCallId: "tc",
|
|
918
|
+
toolName: "Subagent",
|
|
919
|
+
persistThreadState,
|
|
920
|
+
}
|
|
921
|
+
);
|
|
922
|
+
|
|
923
|
+
expect(persistThreadState).toHaveBeenCalledTimes(1);
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
it("does not call persistThreadState when child thread is independent of the parent", async () => {
|
|
927
|
+
const persistThreadState = vi.fn(async () => undefined);
|
|
928
|
+
|
|
929
|
+
const subagent: SubagentConfig = {
|
|
930
|
+
agentName: "independent-thread",
|
|
931
|
+
description: "Continues a sibling thread",
|
|
932
|
+
workflow: mockWorkflow(),
|
|
933
|
+
thread: "continue",
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
const { handler } = createSubagentHandler([subagent]);
|
|
937
|
+
|
|
938
|
+
await handler(
|
|
939
|
+
{
|
|
940
|
+
subagent: "independent-thread",
|
|
941
|
+
description: "test",
|
|
942
|
+
prompt: "test",
|
|
943
|
+
threadId: "sibling-thread",
|
|
944
|
+
},
|
|
945
|
+
{
|
|
946
|
+
threadId: "parent-t",
|
|
947
|
+
toolCallId: "tc",
|
|
948
|
+
toolName: "Subagent",
|
|
949
|
+
persistThreadState,
|
|
950
|
+
}
|
|
951
|
+
);
|
|
952
|
+
|
|
953
|
+
expect(persistThreadState).not.toHaveBeenCalled();
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
it("does not call persistThreadState when the child starts a fresh thread", async () => {
|
|
957
|
+
const persistThreadState = vi.fn(async () => undefined);
|
|
958
|
+
|
|
959
|
+
const subagent: SubagentConfig = {
|
|
960
|
+
agentName: "fresh-thread",
|
|
961
|
+
description: "Always starts fresh",
|
|
962
|
+
workflow: mockWorkflow(),
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
const { handler } = createSubagentHandler([subagent]);
|
|
966
|
+
|
|
967
|
+
await handler(
|
|
968
|
+
{ subagent: "fresh-thread", description: "test", prompt: "test" },
|
|
969
|
+
{
|
|
970
|
+
threadId: "parent-t",
|
|
971
|
+
toolCallId: "tc",
|
|
972
|
+
toolName: "Subagent",
|
|
973
|
+
persistThreadState,
|
|
974
|
+
}
|
|
975
|
+
);
|
|
976
|
+
|
|
977
|
+
expect(persistThreadState).not.toHaveBeenCalled();
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
it("still spawns the child when persistThreadState throws", async () => {
|
|
981
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
982
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
983
|
+
const persistThreadState = vi.fn(async () => {
|
|
984
|
+
throw new Error("redis down");
|
|
985
|
+
});
|
|
986
|
+
const callsBefore = execMock.mock.calls.length;
|
|
987
|
+
|
|
988
|
+
const subagent: SubagentConfig = {
|
|
989
|
+
agentName: "persist-fails",
|
|
990
|
+
description: "Persist failure should not block child",
|
|
991
|
+
workflow: mockWorkflow(),
|
|
992
|
+
thread: "fork",
|
|
993
|
+
newThreadSource: "from-parent",
|
|
994
|
+
};
|
|
995
|
+
|
|
996
|
+
const { handler } = createSubagentHandler([subagent]);
|
|
997
|
+
|
|
998
|
+
await expect(
|
|
999
|
+
handler(
|
|
1000
|
+
{ subagent: "persist-fails", description: "test", prompt: "test" },
|
|
1001
|
+
{
|
|
1002
|
+
threadId: "parent-t",
|
|
1003
|
+
toolCallId: "tc",
|
|
1004
|
+
toolName: "Subagent",
|
|
1005
|
+
persistThreadState,
|
|
1006
|
+
}
|
|
1007
|
+
)
|
|
1008
|
+
).resolves.toBeDefined();
|
|
1009
|
+
|
|
1010
|
+
expect(persistThreadState).toHaveBeenCalledTimes(1);
|
|
1011
|
+
expect(execMock.mock.calls.length).toBe(callsBefore + 1);
|
|
1012
|
+
});
|
|
1013
|
+
|
|
816
1014
|
// --- Sandbox continuation ---
|
|
817
1015
|
|
|
818
1016
|
it("does not pass sandbox when thread is fork (own sandbox)", async () => {
|
|
@@ -221,7 +221,8 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
221
221
|
turn: number,
|
|
222
222
|
sandboxId?: string,
|
|
223
223
|
onRewindRequested?: (signal: RewindSignal) => void,
|
|
224
|
-
assistantMessageId?: string
|
|
224
|
+
assistantMessageId?: string,
|
|
225
|
+
persistThreadState?: () => Promise<void>
|
|
225
226
|
): Promise<ProcessedToolCall> {
|
|
226
227
|
const startTime = Date.now();
|
|
227
228
|
const tool = toolMap.get(toolCall.name);
|
|
@@ -265,6 +266,7 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
265
266
|
toolName: toolCall.name,
|
|
266
267
|
...(sandboxId !== undefined && { sandboxId }),
|
|
267
268
|
...(assistantMessageId !== undefined && { assistantMessageId }),
|
|
269
|
+
...(persistThreadState !== undefined && { persistThreadState }),
|
|
268
270
|
};
|
|
269
271
|
const response = await tool.handler(
|
|
270
272
|
effectiveArgs as Parameters<typeof tool.handler>[0],
|
|
@@ -422,6 +424,7 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
422
424
|
const turn = context?.turn ?? 0;
|
|
423
425
|
const sandboxId = context?.sandboxId;
|
|
424
426
|
const assistantMessageId = context?.assistantMessageId;
|
|
427
|
+
const persistThreadState = context?.persistThreadState;
|
|
425
428
|
|
|
426
429
|
let rewindSignal: RewindSignal | undefined;
|
|
427
430
|
|
|
@@ -443,7 +446,8 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
443
446
|
turn,
|
|
444
447
|
sandboxId,
|
|
445
448
|
onRewindRequested,
|
|
446
|
-
assistantMessageId
|
|
449
|
+
assistantMessageId,
|
|
450
|
+
persistThreadState
|
|
447
451
|
)
|
|
448
452
|
)
|
|
449
453
|
)
|
|
@@ -471,7 +475,8 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
471
475
|
turn,
|
|
472
476
|
sandboxId,
|
|
473
477
|
undefined,
|
|
474
|
-
assistantMessageId
|
|
478
|
+
assistantMessageId,
|
|
479
|
+
persistThreadState
|
|
475
480
|
);
|
|
476
481
|
if (outcome.kind === "rewind") {
|
|
477
482
|
rewindSignal = outcome.signal;
|
|
@@ -510,6 +515,9 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
510
515
|
...(context?.assistantMessageId !== undefined && {
|
|
511
516
|
assistantMessageId: context.assistantMessageId,
|
|
512
517
|
}),
|
|
518
|
+
...(context?.persistThreadState !== undefined && {
|
|
519
|
+
persistThreadState: context.persistThreadState,
|
|
520
|
+
}),
|
|
513
521
|
};
|
|
514
522
|
const response = await handler(
|
|
515
523
|
toolCall.args as ToolArgs<T, TName>,
|
|
@@ -190,6 +190,20 @@ export interface RouterContext {
|
|
|
190
190
|
* thread so the child's first model call sees a well-formed history.
|
|
191
191
|
*/
|
|
192
192
|
assistantMessageId?: string;
|
|
193
|
+
/**
|
|
194
|
+
* Persist the parent session's current `PersistedThreadState` slice
|
|
195
|
+
* (tasks + custom state) to the durable thread store. Wired up by
|
|
196
|
+
* the session — absent for manually-driven routers (tests, custom
|
|
197
|
+
* orchestrators).
|
|
198
|
+
*
|
|
199
|
+
* Subagent handlers invoke this before spawning a child that will
|
|
200
|
+
* read the parent's thread (`newThreadSource: "from-parent"` or an
|
|
201
|
+
* explicit parent threadId): the parent's slice otherwise only
|
|
202
|
+
* lands in storage at session-exit time, so the child would load a
|
|
203
|
+
* stale (or empty) snapshot. Best-effort — failures are logged by
|
|
204
|
+
* the session but never thrown.
|
|
205
|
+
*/
|
|
206
|
+
persistThreadState?: () => Promise<void>;
|
|
193
207
|
}
|
|
194
208
|
|
|
195
209
|
/**
|
|
@@ -314,6 +328,15 @@ export interface ProcessToolCallsContext {
|
|
|
314
328
|
* out of a parent-forked thread).
|
|
315
329
|
*/
|
|
316
330
|
assistantMessageId?: string;
|
|
331
|
+
/**
|
|
332
|
+
* Optional callback that flushes the session's in-memory
|
|
333
|
+
* `PersistedThreadState` slice to the durable thread store. The
|
|
334
|
+
* router forwards it into every handler's {@link RouterContext}
|
|
335
|
+
* verbatim. The session uses this to let mid-loop tool handlers
|
|
336
|
+
* (notably subagents that fork or continue the parent's thread)
|
|
337
|
+
* persist the parent's slice before the child reads it.
|
|
338
|
+
*/
|
|
339
|
+
persistThreadState?: () => Promise<void>;
|
|
317
340
|
}
|
|
318
341
|
|
|
319
342
|
/**
|