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