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.
- package/README.md +146 -92
- package/dist/{activities-BVI2lTwr.d.ts → activities-BKhMtKDd.d.ts} +4 -2
- package/dist/{activities-hd4aNnZE.d.cts → activities-CDcwkRZs.d.cts} +4 -2
- package/dist/adapters/sandbox/bedrock/index.cjs +17 -14
- package/dist/adapters/sandbox/bedrock/index.cjs.map +1 -1
- package/dist/adapters/sandbox/bedrock/index.d.cts +7 -6
- package/dist/adapters/sandbox/bedrock/index.d.ts +7 -6
- package/dist/adapters/sandbox/bedrock/index.js +17 -14
- package/dist/adapters/sandbox/bedrock/index.js.map +1 -1
- package/dist/adapters/sandbox/bedrock/workflow.cjs +2 -0
- package/dist/adapters/sandbox/bedrock/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/bedrock/workflow.d.cts +2 -2
- package/dist/adapters/sandbox/bedrock/workflow.d.ts +2 -2
- package/dist/adapters/sandbox/bedrock/workflow.js +2 -0
- package/dist/adapters/sandbox/bedrock/workflow.js.map +1 -1
- package/dist/adapters/sandbox/daytona/index.cjs +11 -3
- package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/index.d.cts +5 -4
- package/dist/adapters/sandbox/daytona/index.d.ts +5 -4
- package/dist/adapters/sandbox/daytona/index.js +11 -3
- package/dist/adapters/sandbox/daytona/index.js.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.cjs +2 -0
- package/dist/adapters/sandbox/daytona/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.d.cts +1 -1
- package/dist/adapters/sandbox/daytona/workflow.d.ts +1 -1
- package/dist/adapters/sandbox/daytona/workflow.js +2 -0
- package/dist/adapters/sandbox/daytona/workflow.js.map +1 -1
- package/dist/adapters/sandbox/e2b/index.cjs +73 -12
- package/dist/adapters/sandbox/e2b/index.cjs.map +1 -1
- package/dist/adapters/sandbox/e2b/index.d.cts +26 -4
- package/dist/adapters/sandbox/e2b/index.d.ts +26 -4
- package/dist/adapters/sandbox/e2b/index.js +73 -12
- package/dist/adapters/sandbox/e2b/index.js.map +1 -1
- package/dist/adapters/sandbox/e2b/workflow.cjs +2 -0
- package/dist/adapters/sandbox/e2b/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/e2b/workflow.d.cts +1 -1
- package/dist/adapters/sandbox/e2b/workflow.d.ts +1 -1
- package/dist/adapters/sandbox/e2b/workflow.js +2 -0
- package/dist/adapters/sandbox/e2b/workflow.js.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.cjs +8 -3
- package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.d.cts +5 -4
- package/dist/adapters/sandbox/inmemory/index.d.ts +5 -4
- package/dist/adapters/sandbox/inmemory/index.js +8 -3
- package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.cjs +2 -0
- package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.d.cts +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.d.ts +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.js +2 -0
- package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -1
- package/dist/adapters/thread/anthropic/index.cjs +94 -39
- package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
- 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/index.js +94 -39
- package/dist/adapters/thread/anthropic/index.js.map +1 -1
- package/dist/adapters/thread/anthropic/workflow.cjs +7 -2
- package/dist/adapters/thread/anthropic/workflow.cjs.map +1 -1
- package/dist/adapters/thread/anthropic/workflow.d.cts +5 -5
- package/dist/adapters/thread/anthropic/workflow.d.ts +5 -5
- package/dist/adapters/thread/anthropic/workflow.js +7 -2
- package/dist/adapters/thread/anthropic/workflow.js.map +1 -1
- package/dist/adapters/thread/google-genai/index.cjs +77 -28
- package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
- 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/index.js +77 -28
- package/dist/adapters/thread/google-genai/index.js.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.cjs +7 -2
- package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.d.cts +5 -5
- package/dist/adapters/thread/google-genai/workflow.d.ts +5 -5
- package/dist/adapters/thread/google-genai/workflow.js +7 -2
- package/dist/adapters/thread/google-genai/workflow.js.map +1 -1
- package/dist/adapters/thread/langchain/index.cjs +57 -10
- package/dist/adapters/thread/langchain/index.cjs.map +1 -1
- 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/index.js +57 -10
- package/dist/adapters/thread/langchain/index.js.map +1 -1
- package/dist/adapters/thread/langchain/workflow.cjs +7 -2
- package/dist/adapters/thread/langchain/workflow.cjs.map +1 -1
- package/dist/adapters/thread/langchain/workflow.d.cts +5 -5
- package/dist/adapters/thread/langchain/workflow.d.ts +5 -5
- package/dist/adapters/thread/langchain/workflow.js +7 -2
- package/dist/adapters/thread/langchain/workflow.js.map +1 -1
- package/dist/index.cjs +322 -146
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -14
- package/dist/index.d.ts +20 -14
- package/dist/index.js +323 -147
- package/dist/index.js.map +1 -1
- package/dist/{proxy-BjdFGPTm.d.ts → proxy-CUlKSvZS.d.ts} +1 -1
- package/dist/{proxy-7RnVaPdJ.d.cts → proxy-D_3x7RN4.d.cts} +1 -1
- package/dist/{thread-manager-CbpiGq1L.d.ts → thread-manager-CVu7o2cs.d.ts} +4 -2
- package/dist/{thread-manager-DzXm9eeI.d.cts → thread-manager-HSwyh28L.d.cts} +4 -2
- package/dist/{thread-manager-BBzNgQWH.d.cts → thread-manager-c1gPopAG.d.ts} +4 -2
- package/dist/{thread-manager-DjN5JYul.d.ts → thread-manager-wGi-LqIP.d.cts} +4 -2
- package/dist/{types-Mc_4BCfT.d.cts → types-BH_IRryz.d.ts} +10 -1
- package/dist/{types-yiXmqedU.d.ts → types-BaOw4hKI.d.cts} +10 -1
- package/dist/{types-DQ1l_gXL.d.cts → types-C06FwR96.d.cts} +121 -17
- package/dist/{types-wiGLvxWf.d.ts → types-DAsQ21Rt.d.ts} +1 -1
- package/dist/{types-CADc5V_P.d.ts → types-DNr31FzL.d.ts} +121 -17
- package/dist/{types-CBH54cwr.d.cts → types-lm8tMNJQ.d.cts} +1 -1
- package/dist/{types-DxCpFNv_.d.cts → types-yx0LzPGn.d.cts} +44 -5
- package/dist/{types-DxCpFNv_.d.ts → types-yx0LzPGn.d.ts} +44 -5
- package/dist/{workflow-DhtWRovz.d.cts → workflow-CSCkpwAL.d.ts} +2 -2
- package/dist/{workflow-P2pTSfKu.d.ts → workflow-DuvMZ8Vm.d.cts} +2 -2
- package/dist/workflow.cjs +274 -130
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +3 -3
- package/dist/workflow.d.ts +3 -3
- package/dist/workflow.js +275 -131
- package/dist/workflow.js.map +1 -1
- package/package.json +2 -2
- package/src/adapters/sandbox/bedrock/filesystem.ts +6 -12
- package/src/adapters/sandbox/bedrock/index.ts +22 -11
- package/src/adapters/sandbox/bedrock/proxy.ts +2 -0
- package/src/adapters/sandbox/daytona/index.ts +18 -3
- package/src/adapters/sandbox/daytona/proxy.ts +2 -0
- package/src/adapters/sandbox/e2b/filesystem.ts +5 -4
- package/src/adapters/sandbox/e2b/index.ts +87 -14
- package/src/adapters/sandbox/e2b/proxy.ts +2 -0
- package/src/adapters/sandbox/e2b/types.ts +16 -0
- package/src/adapters/sandbox/inmemory/index.ts +17 -3
- package/src/adapters/sandbox/inmemory/proxy.ts +2 -0
- package/src/adapters/thread/anthropic/activities.ts +58 -26
- package/src/adapters/thread/anthropic/model-invoker.ts +18 -7
- package/src/adapters/thread/anthropic/proxy.ts +6 -2
- package/src/adapters/thread/anthropic/thread-manager.test.ts +26 -7
- package/src/adapters/thread/anthropic/thread-manager.ts +63 -46
- package/src/adapters/thread/google-genai/activities.ts +20 -2
- package/src/adapters/thread/google-genai/model-invoker.ts +27 -7
- package/src/adapters/thread/google-genai/proxy.ts +6 -2
- package/src/adapters/thread/google-genai/thread-manager.test.ts +13 -3
- package/src/adapters/thread/google-genai/thread-manager.ts +57 -33
- package/src/adapters/thread/langchain/activities.ts +55 -24
- package/src/adapters/thread/langchain/hooks.test.ts +36 -49
- package/src/adapters/thread/langchain/hooks.ts +18 -5
- package/src/adapters/thread/langchain/model-invoker.ts +5 -4
- package/src/adapters/thread/langchain/proxy.ts +6 -2
- package/src/adapters/thread/langchain/thread-manager.test.ts +5 -1
- package/src/adapters/thread/langchain/thread-manager.ts +23 -9
- package/src/index.ts +4 -1
- package/src/lib/activity.ts +16 -6
- package/src/lib/hooks/types.ts +6 -6
- package/src/lib/lifecycle.ts +18 -3
- package/src/lib/model/proxy.ts +2 -2
- package/src/lib/model/types.ts +10 -0
- package/src/lib/observability/hooks.ts +4 -5
- package/src/lib/observability/index.ts +1 -4
- package/src/lib/sandbox/manager.ts +45 -20
- package/src/lib/sandbox/node-fs.ts +3 -6
- package/src/lib/sandbox/sandbox.test.ts +36 -3
- package/src/lib/sandbox/tree.integration.test.ts +10 -3
- package/src/lib/sandbox/types.ts +60 -6
- package/src/lib/session/session-edge-cases.integration.test.ts +316 -14
- package/src/lib/session/session.integration.test.ts +161 -1
- package/src/lib/session/session.ts +106 -21
- package/src/lib/session/types.ts +25 -5
- package/src/lib/skills/fs-provider.ts +12 -8
- package/src/lib/skills/handler.ts +1 -1
- package/src/lib/skills/parse.ts +3 -1
- package/src/lib/skills/register.ts +1 -3
- package/src/lib/skills/skills.integration.test.ts +25 -15
- package/src/lib/state/manager.integration.test.ts +12 -2
- package/src/lib/subagent/define.ts +1 -1
- package/src/lib/subagent/handler.ts +186 -71
- package/src/lib/subagent/index.ts +1 -5
- package/src/lib/subagent/register.ts +3 -2
- package/src/lib/subagent/signals.ts +1 -10
- package/src/lib/subagent/subagent.integration.test.ts +526 -248
- package/src/lib/subagent/tool.ts +4 -3
- package/src/lib/subagent/types.ts +50 -20
- package/src/lib/subagent/workflow.ts +9 -49
- package/src/lib/thread/id.test.ts +1 -1
- package/src/lib/thread/id.ts +1 -2
- package/src/lib/thread/manager.ts +18 -0
- package/src/lib/thread/proxy.ts +4 -4
- package/src/lib/thread/types.ts +20 -3
- package/src/lib/tool-router/index.ts +3 -5
- package/src/lib/tool-router/router-edge-cases.integration.test.ts +93 -1
- package/src/lib/tool-router/router.integration.test.ts +12 -0
- package/src/lib/tool-router/router.ts +90 -16
- package/src/lib/tool-router/types.ts +45 -4
- package/src/lib/tool-router/with-sandbox.ts +19 -5
- package/src/lib/virtual-fs/filesystem.ts +1 -1
- package/src/lib/virtual-fs/index.ts +5 -1
- package/src/lib/virtual-fs/mutations.ts +2 -4
- package/src/lib/virtual-fs/queries.ts +9 -5
- package/src/lib/virtual-fs/types.ts +4 -1
- package/src/lib/virtual-fs/virtual-fs.test.ts +9 -11
- package/src/lib/workflow.test.ts +7 -4
- package/src/lib/workflow.ts +1 -5
- package/src/tools/ask-user-question/tool.ts +1 -3
- package/src/tools/glob/handler.ts +1 -4
- package/src/tools/task-get/handler.ts +4 -5
- package/src/tools/task-list/handler.ts +1 -4
- package/src/tools/task-update/handler.ts +4 -5
- package/src/workflow.ts +22 -7
- package/tsup.config.ts +9 -6
- package/src/lib/.env +0 -1
- package/src/tools/bash/.env +0 -1
|
@@ -33,6 +33,16 @@ vi.mock("@temporalio/workflow", () => {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
class MockCancellationScope {
|
|
37
|
+
cancellable: boolean;
|
|
38
|
+
constructor(opts?: { cancellable?: boolean }) {
|
|
39
|
+
this.cancellable = opts?.cancellable ?? true;
|
|
40
|
+
}
|
|
41
|
+
async run<T>(fn: () => Promise<T>): Promise<T> {
|
|
42
|
+
return fn();
|
|
43
|
+
}
|
|
44
|
+
cancel(): void {}
|
|
45
|
+
}
|
|
36
46
|
return {
|
|
37
47
|
proxyActivities: <T>() => ({}) as T,
|
|
38
48
|
condition: async (fn: () => boolean) => fn(),
|
|
@@ -46,6 +56,8 @@ vi.mock("@temporalio/workflow", () => {
|
|
|
46
56
|
uuid4: () =>
|
|
47
57
|
`00000000-0000-0000-0000-${String(++idCounter).padStart(12, "0")}`,
|
|
48
58
|
ApplicationFailure: MockApplicationFailure,
|
|
59
|
+
CancellationScope: MockCancellationScope,
|
|
60
|
+
isCancellation: (_err: unknown) => false,
|
|
49
61
|
log: {
|
|
50
62
|
trace: () => {},
|
|
51
63
|
debug: () => {},
|
|
@@ -105,6 +117,9 @@ function createMockThreadOps() {
|
|
|
105
117
|
forkThread: async (source, target) => {
|
|
106
118
|
log.push({ op: "forkThread", args: [source, target] });
|
|
107
119
|
},
|
|
120
|
+
truncateThread: async (threadId, length) => {
|
|
121
|
+
log.push({ op: "truncateThread", args: [threadId, length] });
|
|
122
|
+
},
|
|
108
123
|
});
|
|
109
124
|
|
|
110
125
|
return { ops, log };
|
|
@@ -123,12 +138,18 @@ function createScriptedRunAgent(
|
|
|
123
138
|
return async () => {
|
|
124
139
|
const turn = turns[call++];
|
|
125
140
|
if (!turn) {
|
|
126
|
-
return {
|
|
141
|
+
return {
|
|
142
|
+
message: "done",
|
|
143
|
+
rawToolCalls: [],
|
|
144
|
+
usage: undefined,
|
|
145
|
+
threadLengthAtCall: 0,
|
|
146
|
+
};
|
|
127
147
|
}
|
|
128
148
|
return {
|
|
129
149
|
message: turn.message,
|
|
130
150
|
rawToolCalls: turn.toolCalls,
|
|
131
151
|
usage: turn.usage,
|
|
152
|
+
threadLengthAtCall: 0,
|
|
132
153
|
};
|
|
133
154
|
};
|
|
134
155
|
}
|
|
@@ -517,6 +538,8 @@ describe("createSession integration", () => {
|
|
|
517
538
|
createdAt: new Date().toISOString(),
|
|
518
539
|
}),
|
|
519
540
|
forkSandbox: async () => "forked-sandbox-id",
|
|
541
|
+
restoreSandbox: async () => "restored-sandbox-id",
|
|
542
|
+
deleteSandboxSnapshot: async () => {},
|
|
520
543
|
pauseSandbox: async () => {},
|
|
521
544
|
resumeSandbox: async () => {},
|
|
522
545
|
};
|
|
@@ -559,6 +582,8 @@ describe("createSession integration", () => {
|
|
|
559
582
|
createdAt: new Date().toISOString(),
|
|
560
583
|
}),
|
|
561
584
|
forkSandbox: async () => "forked-sandbox-id",
|
|
585
|
+
restoreSandbox: async () => "restored-sandbox-id",
|
|
586
|
+
deleteSandboxSnapshot: async () => {},
|
|
562
587
|
pauseSandbox: async () => {},
|
|
563
588
|
resumeSandbox: async () => {},
|
|
564
589
|
};
|
|
@@ -610,6 +635,8 @@ describe("createSession integration", () => {
|
|
|
610
635
|
createdAt: new Date().toISOString(),
|
|
611
636
|
}),
|
|
612
637
|
forkSandbox: async () => "forked-sb",
|
|
638
|
+
restoreSandbox: async () => "restored-sb",
|
|
639
|
+
deleteSandboxSnapshot: async () => {},
|
|
613
640
|
};
|
|
614
641
|
|
|
615
642
|
const session = await createSession({
|
|
@@ -822,6 +849,8 @@ describe("createSession integration", () => {
|
|
|
822
849
|
createdAt: new Date().toISOString(),
|
|
823
850
|
}),
|
|
824
851
|
forkSandbox: async () => "forked-sandbox-id",
|
|
852
|
+
restoreSandbox: async () => "restored-sandbox-id",
|
|
853
|
+
deleteSandboxSnapshot: async () => {},
|
|
825
854
|
pauseSandbox: async () => {},
|
|
826
855
|
resumeSandbox: async () => {},
|
|
827
856
|
};
|
|
@@ -873,6 +902,8 @@ describe("createSession integration", () => {
|
|
|
873
902
|
createdAt: new Date().toISOString(),
|
|
874
903
|
}),
|
|
875
904
|
forkSandbox: async () => "forked-sandbox-id",
|
|
905
|
+
restoreSandbox: async () => "restored-sandbox-id",
|
|
906
|
+
deleteSandboxSnapshot: async () => {},
|
|
876
907
|
pauseSandbox: async () => {},
|
|
877
908
|
resumeSandbox: async () => {},
|
|
878
909
|
};
|
|
@@ -949,4 +980,133 @@ describe("createSession integration", () => {
|
|
|
949
980
|
expect(result.usage.totalInputTokens).toBe(180);
|
|
950
981
|
expect(result.usage.totalOutputTokens).toBe(90);
|
|
951
982
|
});
|
|
983
|
+
|
|
984
|
+
// --- Snapshot-driven shutdown ---
|
|
985
|
+
|
|
986
|
+
it("captures base + exit snapshot and destroys sandbox on sandboxShutdown=snapshot", async () => {
|
|
987
|
+
const { ops } = createMockThreadOps();
|
|
988
|
+
const sandboxLog: string[] = [];
|
|
989
|
+
let snapCounter = 0;
|
|
990
|
+
|
|
991
|
+
const sandboxOps: SandboxOps = {
|
|
992
|
+
createSandbox: async () => {
|
|
993
|
+
sandboxLog.push("create");
|
|
994
|
+
return { sandboxId: "sb-snap" };
|
|
995
|
+
},
|
|
996
|
+
destroySandbox: async (id: string) => {
|
|
997
|
+
sandboxLog.push(`destroy:${id}`);
|
|
998
|
+
},
|
|
999
|
+
pauseSandbox: async () => {
|
|
1000
|
+
sandboxLog.push("pause");
|
|
1001
|
+
},
|
|
1002
|
+
resumeSandbox: async () => {},
|
|
1003
|
+
snapshotSandbox: async (id: string) => {
|
|
1004
|
+
snapCounter += 1;
|
|
1005
|
+
sandboxLog.push(`snapshot:${id}`);
|
|
1006
|
+
return {
|
|
1007
|
+
sandboxId: id,
|
|
1008
|
+
providerId: "test",
|
|
1009
|
+
data: { tag: `snap-${snapCounter}` },
|
|
1010
|
+
createdAt: new Date().toISOString(),
|
|
1011
|
+
};
|
|
1012
|
+
},
|
|
1013
|
+
restoreSandbox: async () => "restored-sb",
|
|
1014
|
+
deleteSandboxSnapshot: async () => {},
|
|
1015
|
+
forkSandbox: async () => "forked-sb",
|
|
1016
|
+
};
|
|
1017
|
+
|
|
1018
|
+
const session = await createSession({
|
|
1019
|
+
agentName: "TestAgent",
|
|
1020
|
+
thread: { mode: "new", threadId: "thread-snap" },
|
|
1021
|
+
runAgent: createScriptedRunAgent([{ message: "done", toolCalls: [] }]),
|
|
1022
|
+
threadOps: ops,
|
|
1023
|
+
buildContextMessage: () => "go",
|
|
1024
|
+
sandboxOps,
|
|
1025
|
+
sandboxShutdown: "snapshot",
|
|
1026
|
+
});
|
|
1027
|
+
|
|
1028
|
+
const stateManager = createAgentStateManager({
|
|
1029
|
+
initialState: { systemPrompt: "test" },
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
const result = await session.runSession({ stateManager });
|
|
1033
|
+
|
|
1034
|
+
expect(result.exitReason).toBe("completed");
|
|
1035
|
+
expect(result.sandboxId).toBe("sb-snap");
|
|
1036
|
+
expect(result.baseSnapshot?.data).toEqual({ tag: "snap-1" });
|
|
1037
|
+
expect(result.snapshot?.data).toEqual({ tag: "snap-2" });
|
|
1038
|
+
expect(sandboxLog).toEqual([
|
|
1039
|
+
"create",
|
|
1040
|
+
"snapshot:sb-snap",
|
|
1041
|
+
"snapshot:sb-snap",
|
|
1042
|
+
"destroy:sb-snap",
|
|
1043
|
+
]);
|
|
1044
|
+
expect(sandboxLog).not.toContain("pause");
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
it("restores a sandbox when sandbox.mode=from-snapshot and skips base snapshot", async () => {
|
|
1048
|
+
const { ops } = createMockThreadOps();
|
|
1049
|
+
const sandboxLog: string[] = [];
|
|
1050
|
+
const priorSnapshot = {
|
|
1051
|
+
sandboxId: "sb-prior",
|
|
1052
|
+
providerId: "test",
|
|
1053
|
+
data: { tag: "prior" },
|
|
1054
|
+
createdAt: new Date().toISOString(),
|
|
1055
|
+
};
|
|
1056
|
+
|
|
1057
|
+
const sandboxOps: SandboxOps = {
|
|
1058
|
+
createSandbox: async () => {
|
|
1059
|
+
sandboxLog.push("create");
|
|
1060
|
+
return { sandboxId: "sb-should-not-be-created" };
|
|
1061
|
+
},
|
|
1062
|
+
destroySandbox: async (id: string) => {
|
|
1063
|
+
sandboxLog.push(`destroy:${id}`);
|
|
1064
|
+
},
|
|
1065
|
+
pauseSandbox: async () => {},
|
|
1066
|
+
resumeSandbox: async () => {},
|
|
1067
|
+
snapshotSandbox: async (id: string) => {
|
|
1068
|
+
sandboxLog.push(`snapshot:${id}`);
|
|
1069
|
+
return {
|
|
1070
|
+
sandboxId: id,
|
|
1071
|
+
providerId: "test",
|
|
1072
|
+
data: { tag: "exit" },
|
|
1073
|
+
createdAt: new Date().toISOString(),
|
|
1074
|
+
};
|
|
1075
|
+
},
|
|
1076
|
+
restoreSandbox: async (snap) => {
|
|
1077
|
+
sandboxLog.push(`restore:${(snap.data as { tag: string }).tag}`);
|
|
1078
|
+
return "sb-restored";
|
|
1079
|
+
},
|
|
1080
|
+
deleteSandboxSnapshot: async () => {},
|
|
1081
|
+
forkSandbox: async () => "forked-sb",
|
|
1082
|
+
};
|
|
1083
|
+
|
|
1084
|
+
const session = await createSession({
|
|
1085
|
+
agentName: "TestAgent",
|
|
1086
|
+
thread: { mode: "new", threadId: "thread-restore" },
|
|
1087
|
+
runAgent: createScriptedRunAgent([{ message: "done", toolCalls: [] }]),
|
|
1088
|
+
threadOps: ops,
|
|
1089
|
+
buildContextMessage: () => "go",
|
|
1090
|
+
sandboxOps,
|
|
1091
|
+
sandbox: { mode: "from-snapshot", snapshot: priorSnapshot },
|
|
1092
|
+
sandboxShutdown: "snapshot",
|
|
1093
|
+
});
|
|
1094
|
+
|
|
1095
|
+
const stateManager = createAgentStateManager({
|
|
1096
|
+
initialState: { systemPrompt: "test" },
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
const result = await session.runSession({ stateManager });
|
|
1100
|
+
|
|
1101
|
+
expect(result.sandboxId).toBe("sb-restored");
|
|
1102
|
+
// No base snapshot because the sandbox was restored, not freshly created.
|
|
1103
|
+
expect(result.baseSnapshot).toBeUndefined();
|
|
1104
|
+
expect(result.snapshot?.data).toEqual({ tag: "exit" });
|
|
1105
|
+
expect(sandboxLog).toEqual([
|
|
1106
|
+
"restore:prior",
|
|
1107
|
+
"snapshot:sb-restored",
|
|
1108
|
+
"destroy:sb-restored",
|
|
1109
|
+
]);
|
|
1110
|
+
expect(sandboxLog).not.toContain("create");
|
|
1111
|
+
});
|
|
952
1112
|
});
|
|
@@ -7,7 +7,11 @@ import {
|
|
|
7
7
|
} from "@temporalio/workflow";
|
|
8
8
|
import type { SessionExitReason } from "../types";
|
|
9
9
|
import type { SessionConfig, ZeitlichSession } from "./types";
|
|
10
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
SandboxCreateOptions,
|
|
12
|
+
SandboxOps,
|
|
13
|
+
SandboxSnapshot,
|
|
14
|
+
} from "../sandbox/types";
|
|
11
15
|
import type {
|
|
12
16
|
AgentState,
|
|
13
17
|
AgentStateManager,
|
|
@@ -142,16 +146,19 @@ export async function createSession<
|
|
|
142
146
|
appendSystemMessage,
|
|
143
147
|
appendAgentMessage,
|
|
144
148
|
forkThread,
|
|
149
|
+
truncateThread,
|
|
145
150
|
} = threadOps;
|
|
146
151
|
|
|
147
152
|
const plugins: ToolMap[string][] = [];
|
|
148
153
|
let destroySubagentSandboxes: (() => Promise<void>) | undefined;
|
|
154
|
+
let cleanupSubagentSnapshots: (() => Promise<void>) | undefined;
|
|
149
155
|
|
|
150
156
|
if (subagents) {
|
|
151
157
|
const result = buildSubagentRegistration(subagents);
|
|
152
158
|
if (result) {
|
|
153
159
|
plugins.push(result.registration);
|
|
154
160
|
destroySubagentSandboxes = result.destroySubagentSandboxes;
|
|
161
|
+
cleanupSubagentSnapshots = result.cleanupSubagentSnapshots;
|
|
155
162
|
}
|
|
156
163
|
}
|
|
157
164
|
if (skills) {
|
|
@@ -210,10 +217,13 @@ export async function createSession<
|
|
|
210
217
|
}
|
|
211
218
|
);
|
|
212
219
|
|
|
213
|
-
// --- Sandbox lifecycle: create, continue, fork, or inherit
|
|
220
|
+
// --- Sandbox lifecycle: create, continue, fork, from-snapshot, or inherit ---
|
|
214
221
|
const sandboxMode = sandboxInit?.mode;
|
|
215
222
|
let sandboxId: string | undefined;
|
|
216
223
|
let sandboxOwned = false;
|
|
224
|
+
let baseSnapshot: SandboxSnapshot | undefined;
|
|
225
|
+
let exitSnapshot: SandboxSnapshot | undefined;
|
|
226
|
+
let freshlyCreated = false;
|
|
217
227
|
|
|
218
228
|
if (sandboxMode === "inherit") {
|
|
219
229
|
const inheritInit = sandboxInit as {
|
|
@@ -248,8 +258,31 @@ export async function createSession<
|
|
|
248
258
|
nonRetryable: true,
|
|
249
259
|
});
|
|
250
260
|
}
|
|
261
|
+
const forkInit = sandboxInit as {
|
|
262
|
+
mode: "fork";
|
|
263
|
+
sandboxId: string;
|
|
264
|
+
options?: SandboxCreateOptions;
|
|
265
|
+
};
|
|
251
266
|
sandboxId = await sandboxOps.forkSandbox(
|
|
252
|
-
|
|
267
|
+
forkInit.sandboxId,
|
|
268
|
+
forkInit.options
|
|
269
|
+
);
|
|
270
|
+
sandboxOwned = true;
|
|
271
|
+
} else if (sandboxMode === "from-snapshot") {
|
|
272
|
+
if (!sandboxOps) {
|
|
273
|
+
throw ApplicationFailure.create({
|
|
274
|
+
message: "No sandboxOps provided — cannot restore sandbox",
|
|
275
|
+
nonRetryable: true,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
const restoreInit = sandboxInit as {
|
|
279
|
+
mode: "from-snapshot";
|
|
280
|
+
snapshot: SandboxSnapshot;
|
|
281
|
+
options?: SandboxCreateOptions;
|
|
282
|
+
};
|
|
283
|
+
sandboxId = await sandboxOps.restoreSandbox(
|
|
284
|
+
restoreInit.snapshot,
|
|
285
|
+
restoreInit.options
|
|
253
286
|
);
|
|
254
287
|
sandboxOwned = true;
|
|
255
288
|
} else if (sandboxOps) {
|
|
@@ -263,10 +296,24 @@ export async function createSession<
|
|
|
263
296
|
if (result) {
|
|
264
297
|
sandboxId = result.sandboxId;
|
|
265
298
|
sandboxOwned = true;
|
|
299
|
+
freshlyCreated = true;
|
|
266
300
|
}
|
|
267
301
|
}
|
|
268
302
|
|
|
269
|
-
|
|
303
|
+
// Capture a base snapshot immediately after seeding so it can be reused
|
|
304
|
+
// as a template for future runs that want to skip the (potentially
|
|
305
|
+
// expensive) seed step.
|
|
306
|
+
if (
|
|
307
|
+
sandboxId &&
|
|
308
|
+
sandboxOwned &&
|
|
309
|
+
freshlyCreated &&
|
|
310
|
+
sandboxShutdown === "snapshot" &&
|
|
311
|
+
sandboxOps
|
|
312
|
+
) {
|
|
313
|
+
baseSnapshot = await sandboxOps.snapshotSandbox(sandboxId);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (sandboxId && sandboxOwned && onSandboxReady) {
|
|
270
317
|
onSandboxReady(sandboxId);
|
|
271
318
|
}
|
|
272
319
|
|
|
@@ -347,6 +394,7 @@ export async function createSession<
|
|
|
347
394
|
);
|
|
348
395
|
|
|
349
396
|
let exitReason: SessionExitReason = "completed";
|
|
397
|
+
let finalMessage: M | null = null;
|
|
350
398
|
|
|
351
399
|
try {
|
|
352
400
|
while (
|
|
@@ -361,13 +409,24 @@ export async function createSession<
|
|
|
361
409
|
|
|
362
410
|
stateManager.setTools(toolRouter.getToolDefinitions());
|
|
363
411
|
|
|
364
|
-
const {
|
|
412
|
+
const {
|
|
413
|
+
message,
|
|
414
|
+
rawToolCalls,
|
|
415
|
+
usage,
|
|
416
|
+
threadLengthAtCall,
|
|
417
|
+
} = await runAgent({
|
|
365
418
|
threadId,
|
|
366
419
|
threadKey,
|
|
367
420
|
agentName,
|
|
368
421
|
metadata,
|
|
369
422
|
});
|
|
370
423
|
|
|
424
|
+
// The invoker loaded the thread right before calling the LLM,
|
|
425
|
+
// so it already knows how many messages were stored at that
|
|
426
|
+
// point — we use that directly as the rewind snapshot instead
|
|
427
|
+
// of a separate activity round-trip.
|
|
428
|
+
const preAssistantLength = threadLengthAtCall;
|
|
429
|
+
|
|
371
430
|
await appendAgentMessage(threadId, uuid4(), message, threadKey);
|
|
372
431
|
|
|
373
432
|
if (usage) {
|
|
@@ -385,21 +444,8 @@ export async function createSession<
|
|
|
385
444
|
if (!toolRouter.hasTools() || rawToolCalls.length === 0) {
|
|
386
445
|
stateManager.complete();
|
|
387
446
|
exitReason = "completed";
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
threadId,
|
|
391
|
-
exitReason,
|
|
392
|
-
turns: currentTurn,
|
|
393
|
-
durationMs: Date.now() - sessionStartMs,
|
|
394
|
-
usage: stateManager.getTotalUsage(),
|
|
395
|
-
});
|
|
396
|
-
return {
|
|
397
|
-
threadId,
|
|
398
|
-
finalMessage: message,
|
|
399
|
-
exitReason,
|
|
400
|
-
usage: stateManager.getTotalUsage(),
|
|
401
|
-
sandboxId,
|
|
402
|
-
} as Awaited<ReturnType<ZeitlichSession<M, boolean>["runSession"]>>;
|
|
447
|
+
finalMessage = message;
|
|
448
|
+
break;
|
|
403
449
|
}
|
|
404
450
|
|
|
405
451
|
const parsedToolCalls: ParsedToolCallUnion<T>[] = [];
|
|
@@ -433,6 +479,33 @@ export async function createSession<
|
|
|
433
479
|
}
|
|
434
480
|
}
|
|
435
481
|
|
|
482
|
+
const rewind = toolCallResults.rewind;
|
|
483
|
+
if (rewind) {
|
|
484
|
+
log.info("rewinding turn", {
|
|
485
|
+
agentName,
|
|
486
|
+
threadId,
|
|
487
|
+
turn: currentTurn,
|
|
488
|
+
toolCallId: rewind.toolCallId,
|
|
489
|
+
toolName: rewind.toolName,
|
|
490
|
+
});
|
|
491
|
+
if (preAssistantLength === undefined) {
|
|
492
|
+
throw ApplicationFailure.create({
|
|
493
|
+
message:
|
|
494
|
+
"Rewind requested but runAgent did not report " +
|
|
495
|
+
"`threadLengthAtCall`; the adapter must populate it to " +
|
|
496
|
+
"support rewinds.",
|
|
497
|
+
nonRetryable: true,
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
// Drop the assistant message + any already-saved tool results
|
|
501
|
+
// so the LLM call can be retried from the pre-assistant state.
|
|
502
|
+
// The turn counter is intentionally NOT rolled back — each
|
|
503
|
+
// rewind still consumes one of the `maxTurns` budget so a
|
|
504
|
+
// misbehaving tool cannot spin the session forever.
|
|
505
|
+
await truncateThread(threadId, preAssistantLength, threadKey);
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
|
|
436
509
|
if (stateManager.getStatus() === "WAITING_FOR_INPUT") {
|
|
437
510
|
const conditionMet = await condition(
|
|
438
511
|
() => stateManager.getStatus() === "RUNNING",
|
|
@@ -480,12 +553,20 @@ export async function createSession<
|
|
|
480
553
|
case "keep":
|
|
481
554
|
case "keep-until-parent-close":
|
|
482
555
|
break;
|
|
556
|
+
case "snapshot":
|
|
557
|
+
exitSnapshot = await sandboxOps.snapshotSandbox(sandboxId);
|
|
558
|
+
await sandboxOps.destroySandbox(sandboxId);
|
|
559
|
+
break;
|
|
483
560
|
}
|
|
484
561
|
}
|
|
485
562
|
|
|
486
563
|
if (destroySubagentSandboxes) {
|
|
487
564
|
await destroySubagentSandboxes();
|
|
488
565
|
}
|
|
566
|
+
|
|
567
|
+
if (cleanupSubagentSnapshots) {
|
|
568
|
+
await cleanupSubagentSnapshots();
|
|
569
|
+
}
|
|
489
570
|
}
|
|
490
571
|
|
|
491
572
|
log.info("session ended", {
|
|
@@ -495,14 +576,18 @@ export async function createSession<
|
|
|
495
576
|
turns: stateManager.getTurns(),
|
|
496
577
|
durationMs: Date.now() - sessionStartMs,
|
|
497
578
|
usage: stateManager.getTotalUsage(),
|
|
579
|
+
...(baseSnapshot && { hasBaseSnapshot: true }),
|
|
580
|
+
...(exitSnapshot && { hasExitSnapshot: true }),
|
|
498
581
|
});
|
|
499
582
|
|
|
500
583
|
return {
|
|
501
584
|
threadId,
|
|
502
|
-
finalMessage
|
|
585
|
+
finalMessage,
|
|
503
586
|
exitReason,
|
|
504
587
|
usage: stateManager.getTotalUsage(),
|
|
505
588
|
sandboxId,
|
|
589
|
+
...(baseSnapshot && { baseSnapshot }),
|
|
590
|
+
...(exitSnapshot && { snapshot: exitSnapshot }),
|
|
506
591
|
} as Awaited<ReturnType<ZeitlichSession<M, boolean>["runSession"]>>;
|
|
507
592
|
},
|
|
508
593
|
};
|
package/src/lib/session/types.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import type { Duration } from "@temporalio/common";
|
|
2
|
-
import type {
|
|
3
|
-
SessionExitReason,
|
|
4
|
-
ToolResultConfig,
|
|
5
|
-
} from "../types";
|
|
2
|
+
import type { SessionExitReason, ToolResultConfig } from "../types";
|
|
6
3
|
import type {
|
|
7
4
|
ToolMap,
|
|
8
5
|
ToolCallResultUnion,
|
|
@@ -11,7 +8,7 @@ import type {
|
|
|
11
8
|
import type { Hooks } from "../hooks/types";
|
|
12
9
|
import type { SubagentConfig } from "../subagent/types";
|
|
13
10
|
import type { Skill } from "../skills/types";
|
|
14
|
-
import type { SandboxOps } from "../sandbox/types";
|
|
11
|
+
import type { SandboxOps, SandboxSnapshot } from "../sandbox/types";
|
|
15
12
|
import type { VirtualFsOps } from "../virtual-fs/types";
|
|
16
13
|
import type { RunAgentActivity } from "../model/types";
|
|
17
14
|
import type { AgentStateManager, JsonSerializable } from "../state/types";
|
|
@@ -62,6 +59,18 @@ export interface ThreadOps<TContent = string> {
|
|
|
62
59
|
targetThreadId: string,
|
|
63
60
|
threadKey?: string
|
|
64
61
|
): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Truncate the thread back to `length` messages. Used by the session's
|
|
64
|
+
* rewind flow to roll the thread back before retrying a turn. The
|
|
65
|
+
* session obtains `length` from `AgentResponse.threadLengthAtCall`,
|
|
66
|
+
* which the model invoker computes for free from the messages it
|
|
67
|
+
* loaded before invoking the LLM.
|
|
68
|
+
*/
|
|
69
|
+
truncateThread(
|
|
70
|
+
threadId: string,
|
|
71
|
+
length: number,
|
|
72
|
+
threadKey?: string
|
|
73
|
+
): Promise<void>;
|
|
65
74
|
}
|
|
66
75
|
|
|
67
76
|
/**
|
|
@@ -219,6 +228,17 @@ export type SessionResult<
|
|
|
219
228
|
finalMessage: M | null;
|
|
220
229
|
exitReason: SessionExitReason;
|
|
221
230
|
usage: ReturnType<AgentStateManager<TState>["getTotalUsage"]>;
|
|
231
|
+
/**
|
|
232
|
+
* Snapshot captured on exit when `sandboxShutdown === "snapshot"`.
|
|
233
|
+
*/
|
|
234
|
+
snapshot?: SandboxSnapshot;
|
|
235
|
+
/**
|
|
236
|
+
* Snapshot captured immediately after sandbox seeding (before the agent
|
|
237
|
+
* loop starts) when `sandbox.mode === "new"` and
|
|
238
|
+
* `sandboxShutdown === "snapshot"`. Intended as a reusable "base" for new
|
|
239
|
+
* threads that want to skip re-seeding.
|
|
240
|
+
*/
|
|
241
|
+
baseSnapshot?: SandboxSnapshot;
|
|
222
242
|
} & (HasSandbox extends true
|
|
223
243
|
? { sandboxId: string }
|
|
224
244
|
: { sandboxId?: undefined });
|
|
@@ -24,7 +24,7 @@ import { parseSkillFile } from "./parse";
|
|
|
24
24
|
export class FileSystemSkillProvider implements SkillProvider {
|
|
25
25
|
constructor(
|
|
26
26
|
private readonly fs: SandboxFileSystem,
|
|
27
|
-
private readonly baseDir: string
|
|
27
|
+
private readonly baseDir: string
|
|
28
28
|
) {}
|
|
29
29
|
|
|
30
30
|
async listSkills(): Promise<SkillMetadata[]> {
|
|
@@ -45,20 +45,21 @@ export class FileSystemSkillProvider implements SkillProvider {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
async getSkill(name: string): Promise<Skill> {
|
|
48
|
-
const raw = await this.fs.readFile(
|
|
49
|
-
join(this.baseDir, name, "SKILL.md"),
|
|
50
|
-
);
|
|
48
|
+
const raw = await this.fs.readFile(join(this.baseDir, name, "SKILL.md"));
|
|
51
49
|
const { frontmatter, body } = parseSkillFile(raw);
|
|
52
50
|
|
|
53
51
|
if (frontmatter.name !== name) {
|
|
54
52
|
throw new Error(
|
|
55
|
-
`Skill directory "${name}" contains SKILL.md with mismatched name "${frontmatter.name}"
|
|
53
|
+
`Skill directory "${name}" contains SKILL.md with mismatched name "${frontmatter.name}"`
|
|
56
54
|
);
|
|
57
55
|
}
|
|
58
56
|
|
|
59
57
|
const location = join(this.baseDir, name);
|
|
60
58
|
const resourcePaths = await this.discoverResources(name);
|
|
61
|
-
const resourceContents = await this.readResourceContents(
|
|
59
|
+
const resourceContents = await this.readResourceContents(
|
|
60
|
+
location,
|
|
61
|
+
resourcePaths
|
|
62
|
+
);
|
|
62
63
|
return {
|
|
63
64
|
...frontmatter,
|
|
64
65
|
instructions: body,
|
|
@@ -80,7 +81,10 @@ export class FileSystemSkillProvider implements SkillProvider {
|
|
|
80
81
|
const { frontmatter, body } = parseSkillFile(raw);
|
|
81
82
|
const location = join(this.baseDir, dir);
|
|
82
83
|
const resourcePaths = await this.discoverResources(dir);
|
|
83
|
-
const resourceContents = await this.readResourceContents(
|
|
84
|
+
const resourceContents = await this.readResourceContents(
|
|
85
|
+
location,
|
|
86
|
+
resourcePaths
|
|
87
|
+
);
|
|
84
88
|
skills.push({
|
|
85
89
|
...frontmatter,
|
|
86
90
|
instructions: body,
|
|
@@ -119,7 +123,7 @@ export class FileSystemSkillProvider implements SkillProvider {
|
|
|
119
123
|
|
|
120
124
|
private async readResourceContents(
|
|
121
125
|
location: string,
|
|
122
|
-
resources: string[]
|
|
126
|
+
resources: string[]
|
|
123
127
|
): Promise<Record<string, string> | undefined> {
|
|
124
128
|
if (resources.length === 0) return undefined;
|
|
125
129
|
const contents: Record<string, string> = {};
|
|
@@ -18,7 +18,7 @@ function formatSkillResponse(skill: Skill): string {
|
|
|
18
18
|
if (skill.location) {
|
|
19
19
|
parts.push(`\nSkill directory: ${skill.location}`);
|
|
20
20
|
parts.push(
|
|
21
|
-
"Relative paths in this skill resolve against the skill directory above."
|
|
21
|
+
"Relative paths in this skill resolve against the skill directory above."
|
|
22
22
|
);
|
|
23
23
|
}
|
|
24
24
|
|
package/src/lib/skills/parse.ts
CHANGED
|
@@ -12,7 +12,9 @@ export function parseSkillFile(raw: string): {
|
|
|
12
12
|
body: string;
|
|
13
13
|
} {
|
|
14
14
|
const trimmed = raw.replace(/^\uFEFF/, ""); // strip BOM
|
|
15
|
-
const match = trimmed.match(
|
|
15
|
+
const match = trimmed.match(
|
|
16
|
+
/^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*\r?\n?([\s\S]*)$/
|
|
17
|
+
);
|
|
16
18
|
|
|
17
19
|
if (!match) {
|
|
18
20
|
throw new Error(
|
|
@@ -11,9 +11,7 @@ function validateSkillNames(skills: SkillMetadata[]): void {
|
|
|
11
11
|
const names = skills.map((s) => s.name);
|
|
12
12
|
const dupes = names.filter((n, i) => names.indexOf(n) !== i);
|
|
13
13
|
if (dupes.length > 0) {
|
|
14
|
-
throw new Error(
|
|
15
|
-
`Duplicate skill names: ${[...new Set(dupes)].join(", ")}`
|
|
16
|
-
);
|
|
14
|
+
throw new Error(`Duplicate skill names: ${[...new Set(dupes)].join(", ")}`);
|
|
17
15
|
}
|
|
18
16
|
}
|
|
19
17
|
|