zeitlich 0.2.21 → 0.2.23
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 +303 -105
- package/dist/adapters/sandbox/daytona/index.cjs +7 -1
- package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/index.d.cts +3 -1
- package/dist/adapters/sandbox/daytona/index.d.ts +3 -1
- package/dist/adapters/sandbox/daytona/index.js +7 -1
- package/dist/adapters/sandbox/daytona/index.js.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.cjs +33 -0
- package/dist/adapters/sandbox/daytona/workflow.cjs.map +1 -0
- package/dist/adapters/sandbox/daytona/workflow.d.cts +27 -0
- package/dist/adapters/sandbox/daytona/workflow.d.ts +27 -0
- package/dist/adapters/sandbox/daytona/workflow.js +31 -0
- package/dist/adapters/sandbox/daytona/workflow.js.map +1 -0
- package/dist/adapters/sandbox/inmemory/index.cjs +18 -1
- package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.d.cts +4 -2
- package/dist/adapters/sandbox/inmemory/index.d.ts +4 -2
- package/dist/adapters/sandbox/inmemory/index.js +18 -1
- package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.cjs +33 -0
- package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -0
- package/dist/adapters/sandbox/inmemory/workflow.d.cts +25 -0
- package/dist/adapters/sandbox/inmemory/workflow.d.ts +25 -0
- package/dist/adapters/sandbox/inmemory/workflow.js +31 -0
- package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -0
- package/dist/adapters/sandbox/virtual/index.cjs +36 -9
- package/dist/adapters/sandbox/virtual/index.cjs.map +1 -1
- package/dist/adapters/sandbox/virtual/index.d.cts +8 -5
- package/dist/adapters/sandbox/virtual/index.d.ts +8 -5
- package/dist/adapters/sandbox/virtual/index.js +36 -9
- package/dist/adapters/sandbox/virtual/index.js.map +1 -1
- package/dist/adapters/sandbox/virtual/workflow.cjs +33 -0
- package/dist/adapters/sandbox/virtual/workflow.cjs.map +1 -0
- package/dist/adapters/sandbox/virtual/workflow.d.cts +27 -0
- package/dist/adapters/sandbox/virtual/workflow.d.ts +27 -0
- package/dist/adapters/sandbox/virtual/workflow.js +31 -0
- package/dist/adapters/sandbox/virtual/workflow.js.map +1 -0
- package/dist/adapters/thread/google-genai/index.cjs +9 -1
- package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/index.d.cts +31 -19
- package/dist/adapters/thread/google-genai/index.d.ts +31 -19
- package/dist/adapters/thread/google-genai/index.js +9 -1
- package/dist/adapters/thread/google-genai/index.js.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.cjs +33 -0
- package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -0
- package/dist/adapters/thread/google-genai/workflow.d.cts +32 -0
- package/dist/adapters/thread/google-genai/workflow.d.ts +32 -0
- package/dist/adapters/thread/google-genai/workflow.js +31 -0
- package/dist/adapters/thread/google-genai/workflow.js.map +1 -0
- package/dist/adapters/thread/langchain/index.cjs +9 -1
- package/dist/adapters/thread/langchain/index.cjs.map +1 -1
- package/dist/adapters/thread/langchain/index.d.cts +27 -16
- package/dist/adapters/thread/langchain/index.d.ts +27 -16
- package/dist/adapters/thread/langchain/index.js +9 -1
- package/dist/adapters/thread/langchain/index.js.map +1 -1
- package/dist/adapters/thread/langchain/workflow.cjs +33 -0
- package/dist/adapters/thread/langchain/workflow.cjs.map +1 -0
- package/dist/adapters/thread/langchain/workflow.d.cts +32 -0
- package/dist/adapters/thread/langchain/workflow.d.ts +32 -0
- package/dist/adapters/thread/langchain/workflow.js +31 -0
- package/dist/adapters/thread/langchain/workflow.js.map +1 -0
- package/dist/index.cjs +282 -90
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -16
- package/dist/index.d.ts +38 -16
- package/dist/index.js +281 -87
- package/dist/index.js.map +1 -1
- package/dist/queries-DModcWRy.d.cts +44 -0
- package/dist/queries-byD0jr1Y.d.ts +44 -0
- package/dist/{types-BkAYmc96.d.ts → types-B50pBPEV.d.ts} +190 -38
- package/dist/{types-YbL7JpEA.d.cts → types-Bll19FZJ.d.cts} +7 -0
- package/dist/{types-YbL7JpEA.d.ts → types-Bll19FZJ.d.ts} +7 -0
- package/dist/{queries-6Avfh74U.d.ts → types-BuXdFhaZ.d.cts} +7 -48
- package/dist/{types-BMRzfELQ.d.cts → types-ChAMwU3q.d.cts} +17 -1
- package/dist/{types-BMRzfELQ.d.ts → types-ChAMwU3q.d.ts} +17 -1
- package/dist/{types-CES_30qx.d.cts → types-DQW8l7pY.d.cts} +190 -38
- package/dist/{queries-CHa2iv_I.d.cts → types-GZ76HZSj.d.ts} +7 -48
- package/dist/workflow.cjs +244 -86
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +54 -65
- package/dist/workflow.d.ts +54 -65
- package/dist/workflow.js +243 -83
- package/dist/workflow.js.map +1 -1
- package/package.json +54 -2
- package/src/adapters/sandbox/daytona/filesystem.ts +1 -1
- package/src/adapters/sandbox/daytona/index.ts +8 -0
- package/src/adapters/sandbox/daytona/proxy.ts +56 -0
- package/src/adapters/sandbox/e2b/filesystem.ts +147 -0
- package/src/adapters/sandbox/e2b/index.ts +164 -0
- package/src/adapters/sandbox/e2b/types.ts +23 -0
- package/src/adapters/sandbox/inmemory/index.ts +27 -3
- package/src/adapters/sandbox/inmemory/proxy.ts +53 -0
- package/src/adapters/sandbox/virtual/filesystem.ts +41 -17
- package/src/adapters/sandbox/virtual/provider.ts +9 -1
- package/src/adapters/sandbox/virtual/proxy.ts +53 -0
- package/src/adapters/sandbox/virtual/types.ts +9 -4
- package/src/adapters/thread/google-genai/activities.ts +51 -17
- package/src/adapters/thread/google-genai/index.ts +1 -0
- package/src/adapters/thread/google-genai/proxy.ts +61 -0
- package/src/adapters/thread/langchain/activities.ts +47 -14
- package/src/adapters/thread/langchain/index.ts +1 -0
- package/src/adapters/thread/langchain/proxy.ts +61 -0
- package/src/lib/lifecycle.ts +57 -0
- package/src/lib/sandbox/manager.ts +52 -6
- package/src/lib/sandbox/sandbox.test.ts +12 -11
- package/src/lib/sandbox/types.ts +31 -4
- package/src/lib/session/index.ts +4 -5
- package/src/lib/session/session-edge-cases.integration.test.ts +491 -66
- package/src/lib/session/session.integration.test.ts +92 -80
- package/src/lib/session/session.ts +108 -96
- package/src/lib/session/types.ts +87 -17
- package/src/lib/subagent/define.ts +6 -5
- package/src/lib/subagent/handler.ts +148 -16
- package/src/lib/subagent/index.ts +4 -0
- package/src/lib/subagent/register.ts +10 -3
- package/src/lib/subagent/signals.ts +8 -0
- package/src/lib/subagent/subagent.integration.test.ts +893 -128
- package/src/lib/subagent/tool.ts +2 -2
- package/src/lib/subagent/types.ts +84 -21
- package/src/lib/subagent/workflow.ts +83 -12
- package/src/lib/tool-router/router-edge-cases.integration.test.ts +4 -1
- package/src/lib/tool-router/router.integration.test.ts +141 -5
- package/src/lib/tool-router/router.ts +13 -3
- package/src/lib/tool-router/types.ts +7 -0
- package/src/lib/workflow.test.ts +104 -27
- package/src/lib/workflow.ts +37 -19
- package/src/tools/bash/bash.test.ts +16 -7
- package/src/workflow.ts +11 -14
- package/tsup.config.ts +6 -0
package/src/lib/subagent/tool.ts
CHANGED
|
@@ -6,7 +6,7 @@ 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 = s.
|
|
9
|
+
const continuation = s.thread && s.thread !== "new"
|
|
10
10
|
? "\n*(Supports thread continuation — pass a threadId to resume a previous conversation)*"
|
|
11
11
|
: "";
|
|
12
12
|
return `## ${s.agentName}\n${s.description}${continuation}`;
|
|
@@ -39,7 +39,7 @@ export function createSubagentTool<T extends SubagentConfig[]>(
|
|
|
39
39
|
|
|
40
40
|
const names = subagents.map((s) => s.agentName);
|
|
41
41
|
const hasThreadContinuation = subagents.some(
|
|
42
|
-
(s) => s.
|
|
42
|
+
(s) => s.thread && s.thread !== "new"
|
|
43
43
|
);
|
|
44
44
|
|
|
45
45
|
const baseFields = {
|
|
@@ -4,26 +4,33 @@ import type {
|
|
|
4
4
|
PreToolUseHookResult,
|
|
5
5
|
PostToolUseFailureHookResult,
|
|
6
6
|
} from "../tool-router/types";
|
|
7
|
+
import type {
|
|
8
|
+
ThreadInit,
|
|
9
|
+
SandboxInit,
|
|
10
|
+
SubagentSandboxShutdown,
|
|
11
|
+
} from "../lifecycle";
|
|
7
12
|
|
|
8
13
|
/** ToolHandlerResponse with threadId required (subagents must always surface their thread) */
|
|
9
14
|
export type SubagentHandlerResponse<TResult = null> =
|
|
10
|
-
ToolHandlerResponse<TResult> & { threadId: string };
|
|
15
|
+
ToolHandlerResponse<TResult> & { threadId: string; sandboxId?: string };
|
|
11
16
|
|
|
12
17
|
/**
|
|
13
18
|
* Raw workflow input fields passed from parent to child workflow.
|
|
14
19
|
* `defineSubagentWorkflow` maps this into `SubagentSessionInput`.
|
|
15
20
|
*/
|
|
16
21
|
export interface SubagentWorkflowInput {
|
|
17
|
-
/** Thread
|
|
18
|
-
|
|
19
|
-
/** Sandbox
|
|
20
|
-
|
|
22
|
+
/** Thread initialization strategy forwarded from the parent */
|
|
23
|
+
thread?: ThreadInit;
|
|
24
|
+
/** Sandbox initialization strategy forwarded from the parent */
|
|
25
|
+
sandbox?: SandboxInit;
|
|
26
|
+
/** Sandbox shutdown override from the parent (takes precedence over workflow default) */
|
|
27
|
+
sandboxShutdown?: SubagentSandboxShutdown;
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
export type SubagentWorkflow<TResult extends z.ZodType = z.ZodType> = (
|
|
24
31
|
prompt: string,
|
|
25
32
|
workflowInput: SubagentWorkflowInput,
|
|
26
|
-
context?: Record<string, unknown
|
|
33
|
+
context?: Record<string, unknown>
|
|
27
34
|
) => Promise<SubagentHandlerResponse<z.infer<TResult> | null>>;
|
|
28
35
|
|
|
29
36
|
/**
|
|
@@ -36,17 +43,39 @@ export type SubagentDefinition<
|
|
|
36
43
|
> = ((
|
|
37
44
|
prompt: string,
|
|
38
45
|
workflowInput: SubagentWorkflowInput,
|
|
39
|
-
context?: TContext
|
|
46
|
+
context?: TContext
|
|
40
47
|
) => Promise<SubagentHandlerResponse<z.infer<TResult> | null>>) & {
|
|
41
48
|
readonly agentName: string;
|
|
42
49
|
readonly description: string;
|
|
43
50
|
readonly resultSchema?: TResult;
|
|
44
51
|
};
|
|
45
52
|
|
|
53
|
+
/** Context value or factory — resolved at invocation time when a function is provided */
|
|
54
|
+
export type SubagentContext =
|
|
55
|
+
| Record<string, unknown>
|
|
56
|
+
| (() => Record<string, unknown>);
|
|
57
|
+
|
|
46
58
|
/** Infer the z.infer'd result type from a SubagentConfig, or null if no schema */
|
|
47
59
|
export type InferSubagentResult<T extends SubagentConfig> =
|
|
48
60
|
T extends SubagentConfig<infer S> ? z.infer<S> : null;
|
|
49
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Sandbox configuration for a subagent.
|
|
64
|
+
*
|
|
65
|
+
* String shorthands:
|
|
66
|
+
* - `"none"` — no sandbox (default).
|
|
67
|
+
* - `"inherit"` — reuse the parent's sandbox (shared filesystem/exec).
|
|
68
|
+
* - `"own"` — the child creates and owns its own sandbox (shutdown defaults to `"destroy"`).
|
|
69
|
+
*
|
|
70
|
+
* Object form (only for `source: "own"`):
|
|
71
|
+
* - `{ source: "own", shutdown?: SubagentSandboxShutdown }` — own sandbox with explicit shutdown policy.
|
|
72
|
+
*/
|
|
73
|
+
export type SubagentSandboxConfig =
|
|
74
|
+
| "none"
|
|
75
|
+
| "inherit"
|
|
76
|
+
| "own"
|
|
77
|
+
| { source: "own"; shutdown?: SubagentSandboxShutdown };
|
|
78
|
+
|
|
50
79
|
/**
|
|
51
80
|
* Configuration for a subagent that can be spawned by the parent workflow.
|
|
52
81
|
*
|
|
@@ -60,23 +89,34 @@ export interface SubagentConfig<TResult extends z.ZodType = z.ZodType> {
|
|
|
60
89
|
/** Whether this subagent is available (default: true). Disabled subagents are excluded from the Subagent tool. */
|
|
61
90
|
enabled?: boolean | (() => boolean);
|
|
62
91
|
/** Temporal workflow function or type name (used with executeChild) */
|
|
63
|
-
workflow:
|
|
92
|
+
workflow: SubagentWorkflow<TResult>;
|
|
64
93
|
/** Optional task queue - defaults to parent's queue if not specified */
|
|
65
94
|
taskQueue?: string;
|
|
66
95
|
/** Optional Zod schema to validate the child workflow's result. If omitted, result is passed through as-is. */
|
|
67
96
|
resultSchema?: TResult;
|
|
68
|
-
/** Optional
|
|
69
|
-
context?:
|
|
70
|
-
/** Allow the parent agent to pass a threadId for this subagent to continue (default: false) */
|
|
71
|
-
allowThreadContinuation?: boolean;
|
|
97
|
+
/** Optional context passed to the subagent — a static object or a function evaluated at invocation time */
|
|
98
|
+
context?: SubagentContext;
|
|
72
99
|
/** Per-subagent lifecycle hooks */
|
|
73
100
|
hooks?: SubagentHooks;
|
|
101
|
+
/**
|
|
102
|
+
* Thread mode for this subagent.
|
|
103
|
+
*
|
|
104
|
+
* - `"new"` (default) — always start a fresh thread.
|
|
105
|
+
* - `"fork"` — the parent can pass a `threadId`; messages are copied into
|
|
106
|
+
* a new thread and the subagent continues there.
|
|
107
|
+
* - `"continue"` — the parent can pass a `threadId`; the subagent appends
|
|
108
|
+
* directly to the existing thread in-place.
|
|
109
|
+
*/
|
|
110
|
+
thread?: "new" | "fork" | "continue";
|
|
74
111
|
/**
|
|
75
112
|
* Sandbox strategy for this subagent.
|
|
76
|
-
*
|
|
77
|
-
*
|
|
113
|
+
*
|
|
114
|
+
* String shorthands: `"none"` (default) | `"inherit"` | `"own"`.
|
|
115
|
+
* Object form: `{ source: "own", shutdown?: SubagentSandboxShutdown }`.
|
|
116
|
+
*
|
|
117
|
+
* @see {@link SubagentSandboxConfig}
|
|
78
118
|
*/
|
|
79
|
-
sandbox?:
|
|
119
|
+
sandbox?: SubagentSandboxConfig;
|
|
80
120
|
}
|
|
81
121
|
|
|
82
122
|
/**
|
|
@@ -97,6 +137,8 @@ export interface SubagentHooks<TArgs = unknown, TResult = unknown> {
|
|
|
97
137
|
threadId: string;
|
|
98
138
|
turn: number;
|
|
99
139
|
durationMs: number;
|
|
140
|
+
/** Unvalidated metadata from the child workflow (e.g. infrastructure state) */
|
|
141
|
+
metadata?: Record<string, unknown>;
|
|
100
142
|
}) => void | Promise<void>;
|
|
101
143
|
/** Called when this subagent execution fails */
|
|
102
144
|
onExecutionFailure?: (ctx: {
|
|
@@ -107,16 +149,37 @@ export interface SubagentHooks<TArgs = unknown, TResult = unknown> {
|
|
|
107
149
|
}) => PostToolUseFailureHookResult | Promise<PostToolUseFailureHookResult>;
|
|
108
150
|
}
|
|
109
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Extended response from the subagent `fn` — includes optional cleanup callbacks
|
|
154
|
+
* stripped before signaling the parent.
|
|
155
|
+
*
|
|
156
|
+
* When `TSandboxShutdown` is `"pause-until-parent-close"`, both `destroySandbox`
|
|
157
|
+
* and `sandboxId` become required so the parent can coordinate cleanup.
|
|
158
|
+
*/
|
|
159
|
+
export type SubagentFnResult<
|
|
160
|
+
TResult = null,
|
|
161
|
+
TSandboxShutdown extends SubagentSandboxShutdown = SubagentSandboxShutdown,
|
|
162
|
+
> = SubagentHandlerResponse<TResult> &
|
|
163
|
+
(TSandboxShutdown extends "pause-until-parent-close"
|
|
164
|
+
? { destroySandbox: () => Promise<void>; sandboxId: string }
|
|
165
|
+
: { destroySandbox?: () => Promise<void> });
|
|
166
|
+
|
|
167
|
+
/** Payload sent by a child workflow to signal its result back to the parent */
|
|
168
|
+
export interface ChildResultSignalPayload {
|
|
169
|
+
childWorkflowId: string;
|
|
170
|
+
result: SubagentHandlerResponse;
|
|
171
|
+
}
|
|
172
|
+
|
|
110
173
|
/**
|
|
111
174
|
* Session config fields passed from parent to child workflow.
|
|
112
175
|
*/
|
|
113
176
|
export interface SubagentSessionInput {
|
|
114
177
|
/** Agent name — spread directly into `createSession` */
|
|
115
178
|
agentName: string;
|
|
116
|
-
/** Thread
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
|
|
120
|
-
/** Sandbox
|
|
121
|
-
|
|
179
|
+
/** Thread initialization strategy */
|
|
180
|
+
thread?: ThreadInit;
|
|
181
|
+
/** Sandbox initialization strategy */
|
|
182
|
+
sandbox?: SandboxInit;
|
|
183
|
+
/** Sandbox shutdown policy (default: "destroy") */
|
|
184
|
+
sandboxShutdown?: SubagentSandboxShutdown;
|
|
122
185
|
}
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import type { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
workflowInfo,
|
|
4
|
+
getExternalWorkflowHandle,
|
|
5
|
+
setHandler,
|
|
6
|
+
condition,
|
|
7
|
+
ApplicationFailure,
|
|
8
|
+
} from "@temporalio/workflow";
|
|
2
9
|
import type {
|
|
3
10
|
SubagentDefinition,
|
|
11
|
+
SubagentFnResult,
|
|
4
12
|
SubagentHandlerResponse,
|
|
5
13
|
SubagentWorkflowInput,
|
|
6
14
|
SubagentSessionInput,
|
|
7
15
|
} from "./types";
|
|
16
|
+
import type { SubagentSandboxShutdown } from "../lifecycle";
|
|
17
|
+
import { childResultSignal, destroySandboxSignal } from "./signals";
|
|
8
18
|
|
|
9
19
|
/**
|
|
10
20
|
* Defines a subagent workflow with embedded metadata (name, description, resultSchema).
|
|
@@ -54,34 +64,50 @@ import type {
|
|
|
54
64
|
*/
|
|
55
65
|
// Without resultSchema — data is null
|
|
56
66
|
export function defineSubagentWorkflow<
|
|
67
|
+
TSandboxShutdown extends SubagentSandboxShutdown = "destroy",
|
|
57
68
|
TContext extends Record<string, unknown> = Record<string, unknown>,
|
|
58
69
|
>(
|
|
59
|
-
config: {
|
|
70
|
+
config: {
|
|
71
|
+
name: string;
|
|
72
|
+
description: string;
|
|
73
|
+
sandboxShutdown?: TSandboxShutdown;
|
|
74
|
+
},
|
|
60
75
|
fn: (
|
|
61
76
|
prompt: string,
|
|
62
77
|
sessionInput: SubagentSessionInput,
|
|
63
78
|
context: TContext
|
|
64
|
-
) => Promise<
|
|
79
|
+
) => Promise<SubagentFnResult<null, TSandboxShutdown>>
|
|
65
80
|
): SubagentDefinition<z.ZodNull, TContext>;
|
|
66
81
|
// With resultSchema — data is inferred from the schema
|
|
67
82
|
export function defineSubagentWorkflow<
|
|
68
83
|
TResult extends z.ZodType,
|
|
84
|
+
TSandboxShutdown extends SubagentSandboxShutdown = "destroy",
|
|
69
85
|
TContext extends Record<string, unknown> = Record<string, unknown>,
|
|
70
86
|
>(
|
|
71
|
-
config: {
|
|
87
|
+
config: {
|
|
88
|
+
name: string;
|
|
89
|
+
description: string;
|
|
90
|
+
resultSchema: TResult;
|
|
91
|
+
sandboxShutdown?: TSandboxShutdown;
|
|
92
|
+
},
|
|
72
93
|
fn: (
|
|
73
94
|
prompt: string,
|
|
74
95
|
sessionInput: SubagentSessionInput,
|
|
75
96
|
context: TContext
|
|
76
|
-
) => Promise<
|
|
97
|
+
) => Promise<SubagentFnResult<z.infer<TResult> | null, TSandboxShutdown>>
|
|
77
98
|
): SubagentDefinition<TResult, TContext>;
|
|
78
99
|
export function defineSubagentWorkflow(
|
|
79
|
-
config: {
|
|
100
|
+
config: {
|
|
101
|
+
name: string;
|
|
102
|
+
description: string;
|
|
103
|
+
resultSchema?: z.ZodType;
|
|
104
|
+
sandboxShutdown?: SubagentSandboxShutdown;
|
|
105
|
+
},
|
|
80
106
|
fn: (
|
|
81
107
|
prompt: string,
|
|
82
108
|
sessionInput: SubagentSessionInput,
|
|
83
109
|
context: Record<string, unknown>
|
|
84
|
-
) => Promise<
|
|
110
|
+
) => Promise<SubagentFnResult<unknown>>
|
|
85
111
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
86
112
|
): SubagentDefinition<any, any> {
|
|
87
113
|
const workflow = async (
|
|
@@ -89,15 +115,60 @@ export function defineSubagentWorkflow(
|
|
|
89
115
|
workflowInput: SubagentWorkflowInput,
|
|
90
116
|
context?: Record<string, unknown>
|
|
91
117
|
): Promise<SubagentHandlerResponse<unknown>> => {
|
|
118
|
+
const effectiveShutdown =
|
|
119
|
+
workflowInput.sandboxShutdown ?? config.sandboxShutdown ?? "destroy";
|
|
120
|
+
|
|
92
121
|
const sessionInput: SubagentSessionInput = {
|
|
93
122
|
agentName: config.name,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}),
|
|
98
|
-
...(workflowInput.sandboxId && { sandboxId: workflowInput.sandboxId }),
|
|
123
|
+
sandboxShutdown: effectiveShutdown,
|
|
124
|
+
...(workflowInput.thread && { thread: workflowInput.thread }),
|
|
125
|
+
...(workflowInput.sandbox && { sandbox: workflowInput.sandbox }),
|
|
99
126
|
};
|
|
100
|
-
|
|
127
|
+
const { destroySandbox, ...result } = await fn(
|
|
128
|
+
prompt,
|
|
129
|
+
sessionInput,
|
|
130
|
+
context ?? {}
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
if (effectiveShutdown === "pause-until-parent-close") {
|
|
134
|
+
if (!destroySandbox) {
|
|
135
|
+
throw ApplicationFailure.create({
|
|
136
|
+
message: `Subagent "${config.name}" has sandboxShutdown="pause-until-parent-close" but fn did not return a destroySandbox callback`,
|
|
137
|
+
nonRetryable: true,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
if (!result.sandboxId) {
|
|
141
|
+
throw ApplicationFailure.create({
|
|
142
|
+
message: `Subagent "${config.name}" has sandboxShutdown="pause-until-parent-close" but fn did not return a sandboxId`,
|
|
143
|
+
nonRetryable: true,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const { parent } = workflowInfo();
|
|
149
|
+
if (!parent) {
|
|
150
|
+
throw ApplicationFailure.create({
|
|
151
|
+
message: "Subagent workflow called without a parent workflow",
|
|
152
|
+
nonRetryable: true,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const parentHandle = getExternalWorkflowHandle(parent.workflowId);
|
|
157
|
+
await parentHandle.signal(childResultSignal, {
|
|
158
|
+
childWorkflowId: workflowInfo().workflowId,
|
|
159
|
+
result,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
if (destroySandbox) {
|
|
163
|
+
let destroyRequested = false;
|
|
164
|
+
setHandler(destroySandboxSignal, () => {
|
|
165
|
+
destroyRequested = true;
|
|
166
|
+
});
|
|
167
|
+
await condition(() => destroyRequested);
|
|
168
|
+
await destroySandbox();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return result;
|
|
101
172
|
};
|
|
102
173
|
|
|
103
174
|
// for temporal workflow name
|
|
@@ -22,7 +22,10 @@ vi.mock("@temporalio/workflow", () => {
|
|
|
22
22
|
return err;
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
-
return {
|
|
25
|
+
return {
|
|
26
|
+
ApplicationFailure: MockApplicationFailure,
|
|
27
|
+
uuid4: () => "00000000-0000-0000-0000-000000000000",
|
|
28
|
+
};
|
|
26
29
|
});
|
|
27
30
|
|
|
28
31
|
import { createToolRouter, defineTool, hasNoOtherToolCalls } from "./router";
|
|
@@ -22,7 +22,10 @@ vi.mock("@temporalio/workflow", () => {
|
|
|
22
22
|
return err;
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
-
return {
|
|
25
|
+
return {
|
|
26
|
+
ApplicationFailure: MockApplicationFailure,
|
|
27
|
+
uuid4: () => "00000000-0000-0000-0000-000000000000",
|
|
28
|
+
};
|
|
26
29
|
});
|
|
27
30
|
|
|
28
31
|
import { createToolRouter, defineTool } from "./router";
|
|
@@ -576,7 +579,7 @@ describe("createToolRouter integration", () => {
|
|
|
576
579
|
});
|
|
577
580
|
});
|
|
578
581
|
|
|
579
|
-
it("
|
|
582
|
+
it("suppresses error when handler fails and no hook recovers", async () => {
|
|
580
583
|
const router = createToolRouter({
|
|
581
584
|
tools: { Fail: failingTool } as const,
|
|
582
585
|
threadId: "t-1",
|
|
@@ -589,9 +592,12 @@ describe("createToolRouter integration", () => {
|
|
|
589
592
|
args: { reason: "unrecoverable" },
|
|
590
593
|
});
|
|
591
594
|
|
|
592
|
-
await
|
|
593
|
-
|
|
594
|
-
).
|
|
595
|
+
const results = await router.processToolCalls([parsed], { turn: 1 });
|
|
596
|
+
expect(results).toHaveLength(1);
|
|
597
|
+
expect(at(results, 0).data).toEqual({
|
|
598
|
+
error: "Error: unrecoverable",
|
|
599
|
+
suppressed: true,
|
|
600
|
+
});
|
|
595
601
|
});
|
|
596
602
|
|
|
597
603
|
// --- Disabled tools ---
|
|
@@ -704,6 +710,136 @@ describe("createToolRouter integration", () => {
|
|
|
704
710
|
expect(router.getResultsByName(results, "Add")).toHaveLength(1);
|
|
705
711
|
});
|
|
706
712
|
|
|
713
|
+
// --- Metadata passthrough ---
|
|
714
|
+
|
|
715
|
+
it("handler metadata flows to ToolCallResult", async () => {
|
|
716
|
+
const metaTool = defineTool({
|
|
717
|
+
name: "Meta" as const,
|
|
718
|
+
description: "returns metadata",
|
|
719
|
+
schema: z.object({}),
|
|
720
|
+
handler: async () => ({
|
|
721
|
+
toolResponse: "ok",
|
|
722
|
+
data: null,
|
|
723
|
+
metadata: { jobId: "j-99", env: "prod" },
|
|
724
|
+
}),
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
const router = createToolRouter({
|
|
728
|
+
tools: { Meta: metaTool } as const,
|
|
729
|
+
threadId: "t-1",
|
|
730
|
+
appendToolResult: appendSpy.fn,
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
const parsed = router.parseToolCall({ id: "tc-1", name: "Meta", args: {} });
|
|
734
|
+
const results = await router.processToolCalls([parsed], { turn: 1 });
|
|
735
|
+
|
|
736
|
+
expect(at(results, 0).metadata).toEqual({ jobId: "j-99", env: "prod" });
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
it("handler metadata flows to per-tool post-hook", async () => {
|
|
740
|
+
let hookMetadata: Record<string, unknown> | undefined;
|
|
741
|
+
|
|
742
|
+
const metaTool = defineTool({
|
|
743
|
+
name: "Meta" as const,
|
|
744
|
+
description: "returns metadata",
|
|
745
|
+
schema: z.object({}),
|
|
746
|
+
handler: async () => ({
|
|
747
|
+
toolResponse: "ok",
|
|
748
|
+
data: null,
|
|
749
|
+
metadata: { region: "us-east-1" },
|
|
750
|
+
}),
|
|
751
|
+
hooks: {
|
|
752
|
+
onPostToolUse: async ({ metadata }) => {
|
|
753
|
+
hookMetadata = metadata;
|
|
754
|
+
},
|
|
755
|
+
},
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
const router = createToolRouter({
|
|
759
|
+
tools: { Meta: metaTool } as const,
|
|
760
|
+
threadId: "t-1",
|
|
761
|
+
appendToolResult: appendSpy.fn,
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
const parsed = router.parseToolCall({ id: "tc-1", name: "Meta", args: {} });
|
|
765
|
+
await router.processToolCalls([parsed], { turn: 1 });
|
|
766
|
+
|
|
767
|
+
expect(hookMetadata).toEqual({ region: "us-east-1" });
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
it("handler metadata flows to global post-hook via result", async () => {
|
|
771
|
+
let resultMetadata: Record<string, unknown> | undefined;
|
|
772
|
+
|
|
773
|
+
const metaTool = defineTool({
|
|
774
|
+
name: "Meta" as const,
|
|
775
|
+
description: "returns metadata",
|
|
776
|
+
schema: z.object({}),
|
|
777
|
+
handler: async () => ({
|
|
778
|
+
toolResponse: "ok",
|
|
779
|
+
data: null,
|
|
780
|
+
metadata: { traceId: "abc" },
|
|
781
|
+
}),
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
const router = createToolRouter({
|
|
785
|
+
tools: { Meta: metaTool } as const,
|
|
786
|
+
threadId: "t-1",
|
|
787
|
+
appendToolResult: appendSpy.fn,
|
|
788
|
+
hooks: {
|
|
789
|
+
onPostToolUse: async ({ result }) => {
|
|
790
|
+
resultMetadata = result.metadata;
|
|
791
|
+
},
|
|
792
|
+
},
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
const parsed = router.parseToolCall({ id: "tc-1", name: "Meta", args: {} });
|
|
796
|
+
await router.processToolCalls([parsed], { turn: 1 });
|
|
797
|
+
|
|
798
|
+
expect(resultMetadata).toEqual({ traceId: "abc" });
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
it("metadata is undefined when handler does not set it", async () => {
|
|
802
|
+
const router = createToolRouter({
|
|
803
|
+
tools: createTools(),
|
|
804
|
+
threadId: "t-1",
|
|
805
|
+
appendToolResult: appendSpy.fn,
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
const parsed = router.parseToolCall({
|
|
809
|
+
id: "tc-1",
|
|
810
|
+
name: "Echo",
|
|
811
|
+
args: { text: "hi" },
|
|
812
|
+
});
|
|
813
|
+
const results = await router.processToolCalls([parsed], { turn: 1 });
|
|
814
|
+
|
|
815
|
+
expect(at(results, 0).metadata).toBeUndefined();
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
it("processToolCallsByName passes metadata through", async () => {
|
|
819
|
+
const router = createToolRouter({
|
|
820
|
+
tools: createTools(),
|
|
821
|
+
threadId: "t-1",
|
|
822
|
+
appendToolResult: appendSpy.fn,
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
const calls = [
|
|
826
|
+
router.parseToolCall({ id: "tc-1", name: "Echo", args: { text: "a" } }),
|
|
827
|
+
];
|
|
828
|
+
|
|
829
|
+
const results = await router.processToolCallsByName(
|
|
830
|
+
calls,
|
|
831
|
+
"Echo",
|
|
832
|
+
async (args: { text: string }) => ({
|
|
833
|
+
toolResponse: `custom: ${args.text}`,
|
|
834
|
+
data: { custom: args.text },
|
|
835
|
+
metadata: { source: "custom-handler" },
|
|
836
|
+
})
|
|
837
|
+
);
|
|
838
|
+
|
|
839
|
+
expect(results).toHaveLength(1);
|
|
840
|
+
expect(at(results, 0).metadata).toEqual({ source: "custom-handler" });
|
|
841
|
+
});
|
|
842
|
+
|
|
707
843
|
// --- resultAppended flag ---
|
|
708
844
|
|
|
709
845
|
it("skips appendToolResult when handler sets resultAppended", async () => {
|
|
@@ -20,7 +20,7 @@ import type {
|
|
|
20
20
|
} from "./types";
|
|
21
21
|
|
|
22
22
|
import type { z } from "zod";
|
|
23
|
-
import {
|
|
23
|
+
import { uuid4 } from "@temporalio/workflow";
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Creates a tool router for declarative tool call processing.
|
|
@@ -110,7 +110,7 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
110
110
|
|
|
111
111
|
/**
|
|
112
112
|
* Run per-tool → global failure hooks. Returns recovery content/result,
|
|
113
|
-
* or
|
|
113
|
+
* or a generic error response if no hook recovers.
|
|
114
114
|
*/
|
|
115
115
|
async function runFailureHooks(
|
|
116
116
|
toolCall: ParsedToolCallUnion<T>,
|
|
@@ -160,7 +160,12 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
160
160
|
};
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
|
|
163
|
+
return {
|
|
164
|
+
content: JSON.stringify({
|
|
165
|
+
error: "The tool encountered an error. Please try again or use a different approach.",
|
|
166
|
+
}),
|
|
167
|
+
result: { error: errorStr, suppressed: true },
|
|
168
|
+
};
|
|
164
169
|
}
|
|
165
170
|
|
|
166
171
|
/** Run per-tool → global post-hooks. */
|
|
@@ -179,6 +184,7 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
179
184
|
threadId: options.threadId,
|
|
180
185
|
turn,
|
|
181
186
|
durationMs,
|
|
187
|
+
...(toolResult.metadata && { metadata: toolResult.metadata }),
|
|
182
188
|
});
|
|
183
189
|
}
|
|
184
190
|
if (options.hooks?.onPostToolUse) {
|
|
@@ -220,6 +226,7 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
220
226
|
let result: unknown;
|
|
221
227
|
let content!: ToolMessageContent;
|
|
222
228
|
let resultAppended = false;
|
|
229
|
+
let metadata: Record<string, unknown> | undefined;
|
|
223
230
|
|
|
224
231
|
try {
|
|
225
232
|
if (tool) {
|
|
@@ -236,6 +243,7 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
236
243
|
result = response.data;
|
|
237
244
|
content = response.toolResponse;
|
|
238
245
|
resultAppended = response.resultAppended === true;
|
|
246
|
+
metadata = response.metadata;
|
|
239
247
|
} else {
|
|
240
248
|
result = { error: `Unknown tool: ${toolCall.name}` };
|
|
241
249
|
content = JSON.stringify(result, null, 2);
|
|
@@ -272,6 +280,7 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
272
280
|
toolCallId: toolCall.id,
|
|
273
281
|
name: toolCall.name,
|
|
274
282
|
data: result,
|
|
283
|
+
...(metadata && { metadata }),
|
|
275
284
|
} as ToolCallResultUnion<TResults>;
|
|
276
285
|
|
|
277
286
|
// --- Post-hooks ---
|
|
@@ -410,6 +419,7 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
410
419
|
toolCallId: toolCall.id,
|
|
411
420
|
name: toolCall.name as TName,
|
|
412
421
|
data: response.data,
|
|
422
|
+
...(response.metadata && { metadata: response.metadata }),
|
|
413
423
|
};
|
|
414
424
|
};
|
|
415
425
|
|
|
@@ -142,6 +142,10 @@ export interface ToolHandlerResponse<TResult = null> {
|
|
|
142
142
|
usage?: TokenUsage;
|
|
143
143
|
/** Thread ID used by the handler (surfaced to the LLM for subagent thread continuation) */
|
|
144
144
|
threadId?: string;
|
|
145
|
+
/** Sandbox ID created or used by the handler (e.g. child agent sandbox) */
|
|
146
|
+
sandboxId?: string;
|
|
147
|
+
/** Unvalidated metadata passthrough from handler to hooks (e.g. infrastructure state) */
|
|
148
|
+
metadata?: Record<string, unknown>;
|
|
145
149
|
}
|
|
146
150
|
|
|
147
151
|
/**
|
|
@@ -225,6 +229,8 @@ export interface ToolCallResult<
|
|
|
225
229
|
name: TName;
|
|
226
230
|
data: TResult;
|
|
227
231
|
usage?: TokenUsage;
|
|
232
|
+
/** Unvalidated metadata passthrough from handler to hooks (e.g. infrastructure state) */
|
|
233
|
+
metadata?: Record<string, unknown>;
|
|
228
234
|
}
|
|
229
235
|
|
|
230
236
|
/**
|
|
@@ -301,6 +307,7 @@ export interface ToolHooks<TArgs = unknown, TResult = unknown> {
|
|
|
301
307
|
threadId: string;
|
|
302
308
|
turn: number;
|
|
303
309
|
durationMs: number;
|
|
310
|
+
metadata?: Record<string, unknown>;
|
|
304
311
|
}) => void | Promise<void>;
|
|
305
312
|
/** Called when this tool execution fails */
|
|
306
313
|
onPostToolUseFailure?: (ctx: {
|