zeitlich 0.2.29 → 0.2.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{activities-DOViDCTE.d.ts → activities-DRSdt8Y3.d.ts} +2 -2
- package/dist/{activities-1xrWRrGJ.d.cts → activities-qPkJDAiq.d.cts} +2 -2
- package/dist/adapters/sandbox/bedrock/index.cjs.map +1 -1
- package/dist/adapters/sandbox/bedrock/index.d.cts +3 -3
- package/dist/adapters/sandbox/bedrock/index.d.ts +3 -3
- package/dist/adapters/sandbox/bedrock/index.js.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/daytona/index.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/index.d.cts +1 -1
- package/dist/adapters/sandbox/daytona/index.d.ts +1 -1
- package/dist/adapters/sandbox/daytona/index.js.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/e2b/index.cjs.map +1 -1
- package/dist/adapters/sandbox/e2b/index.d.cts +1 -1
- package/dist/adapters/sandbox/e2b/index.d.ts +1 -1
- package/dist/adapters/sandbox/e2b/index.js.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/inmemory/index.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.d.cts +1 -1
- package/dist/adapters/sandbox/inmemory/index.d.ts +1 -1
- package/dist/adapters/sandbox/inmemory/index.js.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/thread/anthropic/index.cjs +0 -1
- 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 +0 -1
- package/dist/adapters/thread/anthropic/index.js.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/google-genai/index.cjs +0 -1
- 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 +0 -1
- package/dist/adapters/thread/google-genai/index.js.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/langchain/index.cjs +0 -1
- 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 +0 -1
- package/dist/adapters/thread/langchain/index.js.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/index.cjs +95 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +78 -15
- package/dist/index.d.ts +78 -15
- package/dist/index.js +95 -54
- package/dist/index.js.map +1 -1
- package/dist/{proxy-Bm2UTiO_.d.cts → proxy-BDQ3Rj6R.d.cts} +1 -1
- package/dist/{proxy-78nc985d.d.ts → proxy-BkvkV2oU.d.ts} +1 -1
- package/dist/{thread-manager-CatBkarc.d.cts → thread-manager-BLgvv9Gf.d.cts} +1 -1
- package/dist/{thread-manager-CxbWo7q_.d.ts → thread-manager-Cv82H1wi.d.ts} +1 -1
- package/dist/{thread-manager-BRE5KkHB.d.cts → thread-manager-DowU4ntB.d.cts} +1 -1
- package/dist/{thread-manager-07BaYu_z.d.ts → thread-manager-HsAYkyAV.d.ts} +1 -1
- package/dist/{types-ChAMwU3q.d.ts → types-AujBIMMn.d.cts} +5 -8
- package/dist/{types-ChAMwU3q.d.cts → types-AujBIMMn.d.ts} +5 -8
- package/dist/{types-BkVoEyiH.d.ts → types-BmS-Huc0.d.ts} +145 -139
- package/dist/{types-seDYom4M.d.cts → types-CjeGWQm1.d.cts} +145 -139
- package/dist/{types-DAv_SLN8.d.ts → types-D6UKZZtj.d.ts} +1 -1
- package/dist/{types-BdCdR41N.d.ts → types-DBk-C8zM.d.ts} +1 -1
- package/dist/{types-ZHs2v9Ap.d.cts → types-DUvEZSDe.d.cts} +1 -1
- package/dist/{types-Dpz2gXLk.d.cts → types-e_38QaKo.d.cts} +1 -1
- package/dist/{workflow-B4T3la0p.d.cts → workflow-CNshfqSO.d.cts} +2 -2
- package/dist/{workflow-DCmaXLZ_.d.ts → workflow-CTcrPZAV.d.ts} +2 -2
- package/dist/workflow.cjs +43 -43
- 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 +43 -43
- package/dist/workflow.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/thread/anthropic/thread-manager.ts +6 -6
- package/src/adapters/thread/google-genai/thread-manager.ts +6 -6
- package/src/adapters/thread/langchain/thread-manager.ts +6 -6
- package/src/index.ts +1 -0
- package/src/lib/lifecycle.ts +8 -3
- package/src/lib/sandbox/index.ts +2 -4
- package/src/lib/sandbox/manager.ts +128 -13
- package/src/lib/sandbox/sandbox.test.ts +136 -16
- package/src/lib/sandbox/types.ts +6 -5
- package/src/lib/session/session.integration.test.ts +7 -40
- package/src/lib/session/session.ts +78 -50
- package/src/lib/session/types.ts +22 -13
- package/src/lib/state/types.ts +9 -6
- package/src/lib/subagent/handler.ts +18 -12
- package/src/lib/subagent/register.ts +11 -12
- package/src/lib/types.ts +4 -0
- package/src/lib/virtual-fs/filesystem.ts +15 -0
- package/src/lib/virtual-fs/types.ts +10 -14
- package/src/lib/virtual-fs/virtual-fs.test.ts +64 -0
- package/src/lib/virtual-fs/with-virtual-fs.ts +4 -3
- package/src/tools/bash/bash.test.ts +2 -1
|
@@ -8,7 +8,11 @@ import {
|
|
|
8
8
|
import type { SessionExitReason } from "../types";
|
|
9
9
|
import type { SessionConfig, ZeitlichSession } from "./types";
|
|
10
10
|
import type { SandboxOps } from "../sandbox/types";
|
|
11
|
-
import
|
|
11
|
+
import type {
|
|
12
|
+
AgentState,
|
|
13
|
+
AgentStateManager,
|
|
14
|
+
JsonSerializable,
|
|
15
|
+
} from "../state/types";
|
|
12
16
|
import { createToolRouter } from "../tool-router/router";
|
|
13
17
|
import type { ParsedToolCallUnion, ToolMap } from "../tool-router/types";
|
|
14
18
|
import { getShortId } from "../thread/id";
|
|
@@ -23,7 +27,7 @@ import { uuid4 } from "@temporalio/workflow";
|
|
|
23
27
|
* Returns undefined when no skills carry resource contents.
|
|
24
28
|
*/
|
|
25
29
|
function collectSkillFiles(
|
|
26
|
-
skills: Skill[]
|
|
30
|
+
skills: Skill[]
|
|
27
31
|
): Record<string, string> | undefined {
|
|
28
32
|
let files: Record<string, string> | undefined;
|
|
29
33
|
for (const skill of skills) {
|
|
@@ -67,13 +71,23 @@ function collectSkillFiles(
|
|
|
67
71
|
* const { finalMessage, exitReason } = await session.runSession({ stateManager });
|
|
68
72
|
* ```
|
|
69
73
|
*/
|
|
70
|
-
export async function createSession<
|
|
74
|
+
export async function createSession<
|
|
75
|
+
T extends ToolMap,
|
|
76
|
+
M = unknown,
|
|
77
|
+
TContent = string,
|
|
78
|
+
>(
|
|
71
79
|
config: SessionConfig<T, M, TContent> & { sandboxOps: SandboxOps }
|
|
72
80
|
): Promise<ZeitlichSession<M, true>>;
|
|
73
|
-
export async function createSession<
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
81
|
+
export async function createSession<
|
|
82
|
+
T extends ToolMap,
|
|
83
|
+
M = unknown,
|
|
84
|
+
TContent = string,
|
|
85
|
+
>(config: SessionConfig<T, M, TContent>): Promise<ZeitlichSession<M, false>>;
|
|
86
|
+
export async function createSession<
|
|
87
|
+
T extends ToolMap,
|
|
88
|
+
M = unknown,
|
|
89
|
+
TContent = string,
|
|
90
|
+
>({
|
|
77
91
|
agentName,
|
|
78
92
|
maxTurns = 50,
|
|
79
93
|
metadata = {},
|
|
@@ -93,6 +107,7 @@ export async function createSession<T extends ToolMap, M = unknown, TContent = s
|
|
|
93
107
|
sandbox: sandboxInit,
|
|
94
108
|
sandboxShutdown = "destroy",
|
|
95
109
|
virtualFs: virtualFsConfig,
|
|
110
|
+
virtualFsOps,
|
|
96
111
|
}: SessionConfig<T, M, TContent>): Promise<ZeitlichSession<M, boolean>> {
|
|
97
112
|
// ---------------------------------------------------------------------------
|
|
98
113
|
// Thread resolution
|
|
@@ -103,15 +118,18 @@ export async function createSession<T extends ToolMap, M = unknown, TContent = s
|
|
|
103
118
|
|
|
104
119
|
switch (threadMode) {
|
|
105
120
|
case "new":
|
|
106
|
-
threadId =
|
|
107
|
-
|
|
108
|
-
|
|
121
|
+
threadId =
|
|
122
|
+
threadInit?.mode === "new" && threadInit.threadId
|
|
123
|
+
? threadInit.threadId
|
|
124
|
+
: getShortId();
|
|
109
125
|
break;
|
|
110
126
|
case "continue":
|
|
111
|
-
threadId = (threadInit as { mode: "continue"; threadId: string })
|
|
127
|
+
threadId = (threadInit as { mode: "continue"; threadId: string })
|
|
128
|
+
.threadId;
|
|
112
129
|
break;
|
|
113
130
|
case "fork":
|
|
114
|
-
sourceThreadId = (threadInit as { mode: "fork"; threadId: string })
|
|
131
|
+
sourceThreadId = (threadInit as { mode: "fork"; threadId: string })
|
|
132
|
+
.threadId;
|
|
115
133
|
threadId = getShortId();
|
|
116
134
|
break;
|
|
117
135
|
}
|
|
@@ -126,11 +144,9 @@ export async function createSession<T extends ToolMap, M = unknown, TContent = s
|
|
|
126
144
|
|
|
127
145
|
const plugins: ToolMap[string][] = [];
|
|
128
146
|
let destroySubagentSandboxes: (() => Promise<void>) | undefined;
|
|
129
|
-
|
|
147
|
+
|
|
130
148
|
if (subagents) {
|
|
131
|
-
const result = buildSubagentRegistration(subagents
|
|
132
|
-
getSandboxStateForInheritance: () => sandboxStateGetter?.(),
|
|
133
|
-
});
|
|
149
|
+
const result = buildSubagentRegistration(subagents);
|
|
134
150
|
if (result) {
|
|
135
151
|
plugins.push(result.registration);
|
|
136
152
|
destroySubagentSandboxes = result.destroySubagentSandboxes;
|
|
@@ -197,27 +213,16 @@ export async function createSession<T extends ToolMap, M = unknown, TContent = s
|
|
|
197
213
|
let sandboxId: string | undefined;
|
|
198
214
|
let sandboxOwned = false;
|
|
199
215
|
|
|
200
|
-
sandboxStateGetter = () => {
|
|
201
|
-
const fileTree = stateManager.get("fileTree" as keyof TState);
|
|
202
|
-
const resolverContext = stateManager.get("resolverContext" as keyof TState);
|
|
203
|
-
const workspaceBase = stateManager.get("workspaceBase" as keyof TState);
|
|
204
|
-
if (!fileTree && !resolverContext && !workspaceBase) return undefined;
|
|
205
|
-
return {
|
|
206
|
-
...(fileTree !== undefined && { fileTree }),
|
|
207
|
-
...(resolverContext !== undefined && { resolverContext }),
|
|
208
|
-
...(workspaceBase !== undefined && { workspaceBase }),
|
|
209
|
-
};
|
|
210
|
-
};
|
|
211
|
-
|
|
212
216
|
if (sandboxMode === "inherit") {
|
|
213
|
-
const inheritInit = sandboxInit as {
|
|
217
|
+
const inheritInit = sandboxInit as {
|
|
218
|
+
mode: "inherit";
|
|
219
|
+
sandboxId: string;
|
|
220
|
+
};
|
|
214
221
|
sandboxId = inheritInit.sandboxId;
|
|
215
|
-
if (inheritInit.stateUpdate) {
|
|
216
|
-
stateManager.mergeUpdate(inheritInit.stateUpdate as Partial<TState>);
|
|
217
|
-
}
|
|
218
222
|
if (!sandboxOps) {
|
|
219
223
|
throw ApplicationFailure.create({
|
|
220
|
-
message:
|
|
224
|
+
message:
|
|
225
|
+
"sandboxId provided but no sandboxOps — cannot manage sandbox lifecycle",
|
|
221
226
|
nonRetryable: true,
|
|
222
227
|
});
|
|
223
228
|
}
|
|
@@ -228,7 +233,8 @@ export async function createSession<T extends ToolMap, M = unknown, TContent = s
|
|
|
228
233
|
nonRetryable: true,
|
|
229
234
|
});
|
|
230
235
|
}
|
|
231
|
-
sandboxId = (sandboxInit as { mode: "continue"; sandboxId: string })
|
|
236
|
+
sandboxId = (sandboxInit as { mode: "continue"; sandboxId: string })
|
|
237
|
+
.sandboxId;
|
|
232
238
|
sandboxOwned = true;
|
|
233
239
|
} else if (sandboxMode === "fork") {
|
|
234
240
|
if (!sandboxOps) {
|
|
@@ -243,27 +249,44 @@ export async function createSession<T extends ToolMap, M = unknown, TContent = s
|
|
|
243
249
|
sandboxOwned = true;
|
|
244
250
|
} else if (sandboxOps) {
|
|
245
251
|
const skillFiles = skills ? collectSkillFiles(skills) : undefined;
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
252
|
+
const ctx = (sandboxInit as { mode: "new"; ctx?: unknown } | undefined)
|
|
253
|
+
?.ctx;
|
|
254
|
+
const createOptions = skillFiles
|
|
255
|
+
? { initialFiles: skillFiles }
|
|
256
|
+
: undefined;
|
|
257
|
+
const result = await sandboxOps.createSandbox(createOptions, ctx);
|
|
258
|
+
if (result) {
|
|
259
|
+
sandboxId = result.sandboxId;
|
|
260
|
+
sandboxOwned = true;
|
|
253
261
|
}
|
|
254
262
|
}
|
|
255
263
|
|
|
256
264
|
// --- Virtual filesystem init (independent of sandbox) ----------------
|
|
257
265
|
if (virtualFsConfig) {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
266
|
+
if (!virtualFsOps) {
|
|
267
|
+
throw ApplicationFailure.create({
|
|
268
|
+
message: "No virtualFsOps provided — cannot resolve file tree",
|
|
269
|
+
nonRetryable: true,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
const result = await virtualFsOps.resolveFileTree(virtualFsConfig.ctx);
|
|
273
|
+
const skillFiles = skills ? collectSkillFiles(skills) : undefined;
|
|
274
|
+
const fileTree = skillFiles
|
|
275
|
+
? [
|
|
276
|
+
...result.fileTree,
|
|
277
|
+
...Object.entries(skillFiles).map(([path, content]) => ({
|
|
278
|
+
id: `skill:${path}`,
|
|
279
|
+
path,
|
|
280
|
+
size: content.length,
|
|
281
|
+
mtime: new Date().toISOString(),
|
|
282
|
+
metadata: {},
|
|
283
|
+
})),
|
|
284
|
+
]
|
|
285
|
+
: result.fileTree;
|
|
261
286
|
stateManager.mergeUpdate({
|
|
262
|
-
fileTree
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
...result.stateUpdate,
|
|
266
|
-
} as unknown as Partial<TState>);
|
|
287
|
+
fileTree,
|
|
288
|
+
...(skillFiles && { inlineFiles: skillFiles }),
|
|
289
|
+
} as Partial<AgentState<TState>>);
|
|
267
290
|
}
|
|
268
291
|
|
|
269
292
|
if (hooks.onSessionStart) {
|
|
@@ -303,7 +326,12 @@ export async function createSession<T extends ToolMap, M = unknown, TContent = s
|
|
|
303
326
|
await initializeThread(threadId, threadKey);
|
|
304
327
|
}
|
|
305
328
|
}
|
|
306
|
-
await appendHumanMessage(
|
|
329
|
+
await appendHumanMessage(
|
|
330
|
+
threadId,
|
|
331
|
+
uuid4(),
|
|
332
|
+
await buildContextMessage(),
|
|
333
|
+
threadKey
|
|
334
|
+
);
|
|
307
335
|
|
|
308
336
|
let exitReason: SessionExitReason = "completed";
|
|
309
337
|
|
package/src/lib/session/types.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import type { Duration } from "@temporalio/common";
|
|
2
|
-
import type {
|
|
3
|
-
ToolResultConfig,
|
|
4
|
-
SessionExitReason,
|
|
5
|
-
} from "../types";
|
|
2
|
+
import type { ToolResultConfig, SessionExitReason } from "../types";
|
|
6
3
|
import type {
|
|
7
4
|
ToolMap,
|
|
8
5
|
ToolCallResultUnion,
|
|
@@ -16,7 +13,11 @@ import type { VirtualFsOps } from "../virtual-fs/types";
|
|
|
16
13
|
import type { RunAgentActivity } from "../model/types";
|
|
17
14
|
import type { AgentStateManager, JsonSerializable } from "../state/types";
|
|
18
15
|
import type { ActivityInterfaceFor } from "@temporalio/workflow";
|
|
19
|
-
import type {
|
|
16
|
+
import type {
|
|
17
|
+
ThreadInit,
|
|
18
|
+
SandboxInit,
|
|
19
|
+
SubagentSandboxShutdown,
|
|
20
|
+
} from "../lifecycle";
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Thread operations required by a session.
|
|
@@ -34,7 +35,7 @@ export interface ThreadOps<TContent = string> {
|
|
|
34
35
|
threadId: string,
|
|
35
36
|
id: string,
|
|
36
37
|
content: TContent,
|
|
37
|
-
threadKey?: string
|
|
38
|
+
threadKey?: string
|
|
38
39
|
): Promise<void>;
|
|
39
40
|
/** Append a tool result to the thread */
|
|
40
41
|
appendToolResult(id: string, config: ToolResultConfig): Promise<void>;
|
|
@@ -43,10 +44,14 @@ export interface ThreadOps<TContent = string> {
|
|
|
43
44
|
threadId: string,
|
|
44
45
|
id: string,
|
|
45
46
|
content: string,
|
|
46
|
-
threadKey?: string
|
|
47
|
+
threadKey?: string
|
|
47
48
|
): Promise<void>;
|
|
48
49
|
/** Copy all messages from sourceThreadId into a new thread at targetThreadId */
|
|
49
|
-
forkThread(
|
|
50
|
+
forkThread(
|
|
51
|
+
sourceThreadId: string,
|
|
52
|
+
targetThreadId: string,
|
|
53
|
+
threadKey?: string
|
|
54
|
+
): Promise<void>;
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
/**
|
|
@@ -87,7 +92,11 @@ export type PrefixedThreadOps<TPrefix extends string, TContent = string> = {
|
|
|
87
92
|
* @typeParam M - SDK-native message type returned by the model invoker
|
|
88
93
|
* @typeParam TContent - SDK-native content type for human messages (defaults to `string`)
|
|
89
94
|
*/
|
|
90
|
-
export interface SessionConfig<
|
|
95
|
+
export interface SessionConfig<
|
|
96
|
+
T extends ToolMap,
|
|
97
|
+
M = unknown,
|
|
98
|
+
TContent = string,
|
|
99
|
+
> {
|
|
91
100
|
/** The name of the agent, should be unique within the workflows */
|
|
92
101
|
agentName: string;
|
|
93
102
|
/** Metadata for the session */
|
|
@@ -169,20 +178,20 @@ export interface SessionConfig<T extends ToolMap, M = unknown, TContent = string
|
|
|
169
178
|
// Virtual filesystem
|
|
170
179
|
// ---------------------------------------------------------------------------
|
|
171
180
|
|
|
181
|
+
virtualFsOps?: VirtualFsOps;
|
|
182
|
+
|
|
172
183
|
/**
|
|
173
184
|
* Virtual filesystem configuration (optional — independent of sandbox).
|
|
174
185
|
*
|
|
175
186
|
* When provided, the session resolves the file tree on start and merges
|
|
176
|
-
* `fileTree`, `
|
|
187
|
+
* `fileTree`, `ctx`, and `workspaceBase` into `AgentState`.
|
|
177
188
|
* Tool handlers wrapped with `withVirtualFs` can then read this state.
|
|
178
189
|
*
|
|
179
190
|
* Can be used alongside `sandboxOps` for agents that need both a real
|
|
180
191
|
* sandbox (e.g. for execution) and a virtual filesystem.
|
|
181
192
|
*/
|
|
182
193
|
virtualFs?: {
|
|
183
|
-
|
|
184
|
-
resolverContext: unknown;
|
|
185
|
-
workspaceBase?: string;
|
|
194
|
+
ctx: unknown;
|
|
186
195
|
};
|
|
187
196
|
}
|
|
188
197
|
|
package/src/lib/state/types.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
QueryDefinition,
|
|
3
|
-
} from "@temporalio/workflow";
|
|
1
|
+
import type { QueryDefinition } from "@temporalio/workflow";
|
|
4
2
|
import type { UpdateDefinition } from "@temporalio/common/lib/interfaces";
|
|
5
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
AgentStatus,
|
|
5
|
+
BaseAgentState,
|
|
6
|
+
TokenUsage,
|
|
7
|
+
WorkflowTask,
|
|
8
|
+
} from "../types";
|
|
6
9
|
import type { ToolDefinition } from "../tool-router/types";
|
|
7
10
|
|
|
8
11
|
/**
|
|
@@ -98,8 +101,8 @@ export interface AgentStateManager<TCustom extends JsonSerializable<TCustom>> {
|
|
|
98
101
|
/** Set a custom state value by key */
|
|
99
102
|
set<K extends keyof TCustom>(key: K, value: TCustom[K]): void;
|
|
100
103
|
|
|
101
|
-
/** Bulk-merge a partial update into custom state
|
|
102
|
-
mergeUpdate(update: Partial<TCustom
|
|
104
|
+
/** Bulk-merge a partial update into custom state */
|
|
105
|
+
mergeUpdate(update: Partial<AgentState<TCustom>>): void;
|
|
103
106
|
|
|
104
107
|
/** Get full state for query handler */
|
|
105
108
|
getCurrentState(): AgentState<TCustom>;
|
|
@@ -17,7 +17,11 @@ import type {
|
|
|
17
17
|
} from "./types";
|
|
18
18
|
import type { SubagentArgs } from "./tool";
|
|
19
19
|
import type { z } from "zod";
|
|
20
|
-
import type {
|
|
20
|
+
import type {
|
|
21
|
+
ThreadInit,
|
|
22
|
+
SandboxInit,
|
|
23
|
+
SubagentSandboxShutdown,
|
|
24
|
+
} from "../lifecycle";
|
|
21
25
|
import { childResultSignal, destroySandboxSignal } from "./signals";
|
|
22
26
|
|
|
23
27
|
/**
|
|
@@ -45,9 +49,9 @@ function resolveSandboxConfig(config?: SubagentSandboxConfig): {
|
|
|
45
49
|
*/
|
|
46
50
|
export function createSubagentHandler<
|
|
47
51
|
const T extends readonly SubagentConfig[],
|
|
48
|
-
>(
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
>(
|
|
53
|
+
subagents: [...T]
|
|
54
|
+
): {
|
|
51
55
|
handler: (
|
|
52
56
|
args: SubagentArgs,
|
|
53
57
|
context: RouterContext
|
|
@@ -99,18 +103,18 @@ export function createSubagentHandler<
|
|
|
99
103
|
// --- Build thread init ---
|
|
100
104
|
let thread: ThreadInit | undefined;
|
|
101
105
|
if (continuationThreadId) {
|
|
102
|
-
thread = {
|
|
103
|
-
|
|
106
|
+
thread = {
|
|
107
|
+
mode: threadMode as "fork" | "continue",
|
|
108
|
+
threadId: continuationThreadId,
|
|
109
|
+
};
|
|
104
110
|
}
|
|
105
111
|
|
|
106
112
|
// --- Build sandbox init ---
|
|
107
113
|
let sandbox: SandboxInit | undefined;
|
|
108
114
|
if (sandboxCfg.source === "inherit" && parentSandboxId) {
|
|
109
|
-
const stateUpdate = options?.getSandboxStateForInheritance?.();
|
|
110
115
|
sandbox = {
|
|
111
116
|
mode: "inherit",
|
|
112
117
|
sandboxId: parentSandboxId,
|
|
113
|
-
...(stateUpdate && { stateUpdate }),
|
|
114
118
|
};
|
|
115
119
|
} else if (sandboxCfg.source === "own") {
|
|
116
120
|
const prevSbId = continuationThreadId
|
|
@@ -156,7 +160,8 @@ export function createSubagentHandler<
|
|
|
156
160
|
const effectiveShutdown = sandboxCfg.shutdown ?? "destroy";
|
|
157
161
|
const shouldDeferDestroy =
|
|
158
162
|
effectiveShutdown === "pause-until-parent-close" &&
|
|
159
|
-
(sandboxCfg.source === "own" ||
|
|
163
|
+
(sandboxCfg.source === "own" ||
|
|
164
|
+
(allowsContinuation && sandboxCfg.source !== "inherit"));
|
|
160
165
|
|
|
161
166
|
if (shouldDeferDestroy) {
|
|
162
167
|
pendingDestroys.set(childWorkflowId, childHandle);
|
|
@@ -231,9 +236,10 @@ export function createSubagentHandler<
|
|
|
231
236
|
let finalToolResponse: JsonValue = toolResponse;
|
|
232
237
|
|
|
233
238
|
if (allowsContinuation && childThreadId) {
|
|
234
|
-
const responseStr =
|
|
235
|
-
|
|
236
|
-
|
|
239
|
+
const responseStr =
|
|
240
|
+
typeof toolResponse === "string"
|
|
241
|
+
? toolResponse
|
|
242
|
+
: JSON.stringify(toolResponse);
|
|
237
243
|
finalToolResponse = `${responseStr}\n\n[${config.agentName} Thread ID: ${childThreadId}]`;
|
|
238
244
|
}
|
|
239
245
|
|
|
@@ -6,7 +6,11 @@ import type {
|
|
|
6
6
|
} from "../tool-router/types";
|
|
7
7
|
import type { SubagentConfig, SubagentHooks } from "./types";
|
|
8
8
|
import type { z } from "zod";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
createSubagentTool,
|
|
11
|
+
SUBAGENT_TOOL_NAME,
|
|
12
|
+
type SubagentArgs,
|
|
13
|
+
} from "./tool";
|
|
10
14
|
import { createSubagentHandler } from "./handler";
|
|
11
15
|
|
|
12
16
|
/**
|
|
@@ -19,12 +23,7 @@ import { createSubagentHandler } from "./handler";
|
|
|
19
23
|
*
|
|
20
24
|
* Returns null if no subagents are configured.
|
|
21
25
|
*/
|
|
22
|
-
export function buildSubagentRegistration(
|
|
23
|
-
subagents: SubagentConfig[],
|
|
24
|
-
options?: {
|
|
25
|
-
getSandboxStateForInheritance?: () => Record<string, unknown> | undefined;
|
|
26
|
-
},
|
|
27
|
-
): {
|
|
26
|
+
export function buildSubagentRegistration(subagents: SubagentConfig[]): {
|
|
28
27
|
registration: ToolMap[string];
|
|
29
28
|
destroySubagentSandboxes: () => Promise<void>;
|
|
30
29
|
} | null {
|
|
@@ -32,7 +31,7 @@ export function buildSubagentRegistration(
|
|
|
32
31
|
|
|
33
32
|
const getEnabled = (): SubagentConfig[] =>
|
|
34
33
|
subagents.filter((s) =>
|
|
35
|
-
typeof s.enabled === "function" ? s.enabled() : (s.enabled ?? true)
|
|
34
|
+
typeof s.enabled === "function" ? s.enabled() : (s.enabled ?? true)
|
|
36
35
|
);
|
|
37
36
|
|
|
38
37
|
const subagentHooksMap = new Map<string, SubagentHooks>();
|
|
@@ -43,15 +42,15 @@ export function buildSubagentRegistration(
|
|
|
43
42
|
const resolveSubagentName = (args: unknown): string =>
|
|
44
43
|
(args as SubagentArgs).subagent;
|
|
45
44
|
|
|
46
|
-
const { handler, destroySubagentSandboxes } =
|
|
47
|
-
|
|
48
|
-
});
|
|
45
|
+
const { handler, destroySubagentSandboxes } =
|
|
46
|
+
createSubagentHandler(subagents);
|
|
49
47
|
|
|
50
48
|
const registration: ToolMap[string] = {
|
|
51
49
|
name: SUBAGENT_TOOL_NAME,
|
|
52
50
|
enabled: (): boolean => getEnabled().length > 0,
|
|
53
51
|
description: (): string => createSubagentTool(getEnabled()).description,
|
|
54
|
-
schema: (): z.ZodObject<z.ZodRawShape> =>
|
|
52
|
+
schema: (): z.ZodObject<z.ZodRawShape> =>
|
|
53
|
+
createSubagentTool(getEnabled()).schema,
|
|
55
54
|
handler,
|
|
56
55
|
...(subagentHooksMap.size > 0 && {
|
|
57
56
|
hooks: {
|
package/src/lib/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { JsonValue } from "./state/types";
|
|
2
|
+
import type { FileEntry } from "./virtual-fs/types";
|
|
2
3
|
|
|
3
4
|
// ============================================================================
|
|
4
5
|
// Agent core types
|
|
@@ -23,6 +24,9 @@ export interface BaseAgentState {
|
|
|
23
24
|
version: number;
|
|
24
25
|
turns: number;
|
|
25
26
|
tasks: Map<string, WorkflowTask>;
|
|
27
|
+
fileTree: FileEntry[];
|
|
28
|
+
/** In-memory file contents keyed by path, bypassing the resolver (e.g. skill resources). */
|
|
29
|
+
inlineFiles?: Record<string, string>;
|
|
26
30
|
systemPrompt?: string;
|
|
27
31
|
totalInputTokens: number;
|
|
28
32
|
totalOutputTokens: number;
|
|
@@ -60,17 +60,28 @@ export class VirtualFileSystem<
|
|
|
60
60
|
private directories: Set<string>;
|
|
61
61
|
private mutations: TreeMutation<TMeta>[] = [];
|
|
62
62
|
|
|
63
|
+
private inlineFiles: Map<string, string>;
|
|
64
|
+
|
|
63
65
|
constructor(
|
|
64
66
|
tree: FileEntry<TMeta>[],
|
|
65
67
|
private resolver: FileResolver<TCtx, TMeta>,
|
|
66
68
|
private ctx: TCtx,
|
|
67
69
|
workspaceBase = "/",
|
|
70
|
+
inlineFiles?: Record<string, string>,
|
|
68
71
|
) {
|
|
69
72
|
this.workspaceBase = normalisePath(workspaceBase);
|
|
70
73
|
this.entries = new Map(
|
|
71
74
|
tree.map((e) => [normalisePath(e.path, this.workspaceBase), e])
|
|
72
75
|
);
|
|
73
76
|
this.directories = inferDirectories(tree, this.workspaceBase);
|
|
77
|
+
this.inlineFiles = new Map(
|
|
78
|
+
inlineFiles
|
|
79
|
+
? Object.entries(inlineFiles).map(([p, c]) => [
|
|
80
|
+
normalisePath(p, this.workspaceBase),
|
|
81
|
+
c,
|
|
82
|
+
])
|
|
83
|
+
: []
|
|
84
|
+
);
|
|
74
85
|
}
|
|
75
86
|
|
|
76
87
|
/** Return all mutations accumulated during this invocation. */
|
|
@@ -89,6 +100,8 @@ export class VirtualFileSystem<
|
|
|
89
100
|
|
|
90
101
|
async readFile(path: string): Promise<string> {
|
|
91
102
|
const norm = normalisePath(path, this.workspaceBase);
|
|
103
|
+
const inline = this.inlineFiles.get(norm);
|
|
104
|
+
if (inline !== undefined) return inline;
|
|
92
105
|
const entry = this.entries.get(norm);
|
|
93
106
|
if (!entry) throw new Error(`ENOENT: no such file: ${path}`);
|
|
94
107
|
return this.resolver.readFile(entry.id, this.ctx, entry.metadata);
|
|
@@ -96,6 +109,8 @@ export class VirtualFileSystem<
|
|
|
96
109
|
|
|
97
110
|
async readFileBuffer(path: string): Promise<Uint8Array> {
|
|
98
111
|
const norm = normalisePath(path, this.workspaceBase);
|
|
112
|
+
const inline = this.inlineFiles.get(norm);
|
|
113
|
+
if (inline !== undefined) return new TextEncoder().encode(inline);
|
|
99
114
|
const entry = this.entries.get(norm);
|
|
100
115
|
if (!entry) throw new Error(`ENOENT: no such file: ${path}`);
|
|
101
116
|
return this.resolver.readFileBuffer(entry.id, this.ctx, entry.metadata);
|
|
@@ -81,15 +81,9 @@ export interface FileResolver<TCtx = unknown, TMeta = FileEntryMetadata> {
|
|
|
81
81
|
* Unlike {@link SandboxOps}, this only exposes what is actually needed:
|
|
82
82
|
* resolving the initial file tree from the consumer's data layer.
|
|
83
83
|
*/
|
|
84
|
-
export interface VirtualFsOps<
|
|
85
|
-
TCtx
|
|
86
|
-
TMeta = FileEntryMetadata,
|
|
87
|
-
> {
|
|
88
|
-
resolveFileTree(
|
|
89
|
-
ctx: TCtx,
|
|
90
|
-
): Promise<{
|
|
84
|
+
export interface VirtualFsOps<TCtx = unknown, TMeta = FileEntryMetadata> {
|
|
85
|
+
resolveFileTree(ctx: TCtx): Promise<{
|
|
91
86
|
fileTree: FileEntry<TMeta>[];
|
|
92
|
-
stateUpdate?: Record<string, unknown>;
|
|
93
87
|
}>;
|
|
94
88
|
}
|
|
95
89
|
|
|
@@ -107,7 +101,10 @@ export type PrefixedVirtualFsOps<
|
|
|
107
101
|
TCtx = unknown,
|
|
108
102
|
TMeta = FileEntryMetadata,
|
|
109
103
|
> = {
|
|
110
|
-
[K in keyof VirtualFsOps<
|
|
104
|
+
[K in keyof VirtualFsOps<
|
|
105
|
+
TCtx,
|
|
106
|
+
TMeta
|
|
107
|
+
> as `${TPrefix}${Capitalize<K & string>}`]: VirtualFsOps<TCtx, TMeta>[K];
|
|
111
108
|
};
|
|
112
109
|
|
|
113
110
|
// ============================================================================
|
|
@@ -119,13 +116,12 @@ export type PrefixedVirtualFsOps<
|
|
|
119
116
|
* {@link queryParentWorkflowState}. Populated automatically by the session
|
|
120
117
|
* when `virtualFs` config is provided.
|
|
121
118
|
*/
|
|
122
|
-
export interface VirtualFsState<
|
|
123
|
-
TCtx = unknown,
|
|
124
|
-
TMeta = FileEntryMetadata,
|
|
125
|
-
> {
|
|
119
|
+
export interface VirtualFsState<TCtx = unknown, TMeta = FileEntryMetadata> {
|
|
126
120
|
fileTree: FileEntry<TMeta>[];
|
|
127
|
-
|
|
121
|
+
ctx: TCtx;
|
|
128
122
|
workspaceBase?: string;
|
|
123
|
+
/** In-memory file contents keyed by path, bypassing the resolver (e.g. skill resources). */
|
|
124
|
+
inlineFiles?: Record<string, string>;
|
|
129
125
|
}
|
|
130
126
|
|
|
131
127
|
// ============================================================================
|
|
@@ -325,6 +325,70 @@ describe("VirtualFileSystem", () => {
|
|
|
325
325
|
});
|
|
326
326
|
});
|
|
327
327
|
|
|
328
|
+
// ============================================================================
|
|
329
|
+
// VirtualFileSystem — inlineFiles
|
|
330
|
+
// ============================================================================
|
|
331
|
+
|
|
332
|
+
describe("VirtualFileSystem — inlineFiles", () => {
|
|
333
|
+
const skillEntry: FileEntry = {
|
|
334
|
+
id: "skill:code-review:checklist.md",
|
|
335
|
+
path: "/skills/code-review/checklist.md",
|
|
336
|
+
size: 18,
|
|
337
|
+
mtime: "2025-01-01T00:00:00.000Z",
|
|
338
|
+
metadata: {},
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const inlineFiles: Record<string, string> = {
|
|
342
|
+
"/skills/code-review/checklist.md": "# Review checklist",
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
function createFsWithInline() {
|
|
346
|
+
const { resolver } = createMockResolver();
|
|
347
|
+
return new VirtualFileSystem(
|
|
348
|
+
[...sampleTree, skillEntry],
|
|
349
|
+
resolver,
|
|
350
|
+
ctx,
|
|
351
|
+
"/",
|
|
352
|
+
inlineFiles,
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
it("readFile returns inline content instead of hitting the resolver", async () => {
|
|
357
|
+
const fs = createFsWithInline();
|
|
358
|
+
const content = await fs.readFile("/skills/code-review/checklist.md");
|
|
359
|
+
expect(content).toBe("# Review checklist");
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it("readFileBuffer returns inline content as Uint8Array", async () => {
|
|
363
|
+
const fs = createFsWithInline();
|
|
364
|
+
const buf = await fs.readFileBuffer("/skills/code-review/checklist.md");
|
|
365
|
+
expect(new TextDecoder().decode(buf)).toBe("# Review checklist");
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it("inline files are visible in readdir", async () => {
|
|
369
|
+
const fs = createFsWithInline();
|
|
370
|
+
const names = await fs.readdir("/skills/code-review");
|
|
371
|
+
expect(names).toContain("checklist.md");
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it("inline file directories are inferred", async () => {
|
|
375
|
+
const fs = createFsWithInline();
|
|
376
|
+
expect(await fs.exists("/skills")).toBe(true);
|
|
377
|
+
expect(await fs.exists("/skills/code-review")).toBe(true);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it("readFile still falls back to resolver for non-inline files", async () => {
|
|
381
|
+
const fs = createFsWithInline();
|
|
382
|
+
const content = await fs.readFile("/src/index.ts");
|
|
383
|
+
expect(content).toBe('console.log("hello");');
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it("readFile throws ENOENT for paths not in tree or inline", async () => {
|
|
387
|
+
const fs = createFsWithInline();
|
|
388
|
+
await expect(fs.readFile("/nope.txt")).rejects.toThrow("ENOENT");
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
|
|
328
392
|
// ============================================================================
|
|
329
393
|
// createVirtualFsActivities
|
|
330
394
|
// ============================================================================
|
|
@@ -16,7 +16,7 @@ import { VirtualFileSystem } from "./filesystem";
|
|
|
16
16
|
* the parent workflow for the current file tree and resolver context.
|
|
17
17
|
*
|
|
18
18
|
* On each invocation the wrapper:
|
|
19
|
-
* 1. Queries the workflow's `AgentState` for `fileTree`, `
|
|
19
|
+
* 1. Queries the workflow's `AgentState` for `fileTree`, `ctx`, and `workspaceBase`
|
|
20
20
|
* 2. Creates an ephemeral {@link VirtualFileSystem} from tree + resolver
|
|
21
21
|
* 3. Runs the inner handler
|
|
22
22
|
* 4. Returns the handler's result together with any {@link TreeMutation}s
|
|
@@ -66,7 +66,7 @@ export function withVirtualFs<
|
|
|
66
66
|
const state =
|
|
67
67
|
await queryParentWorkflowState<VirtualFsState<TCtx, TMeta>>(client);
|
|
68
68
|
|
|
69
|
-
const { fileTree,
|
|
69
|
+
const { fileTree, ctx, workspaceBase, inlineFiles } = state;
|
|
70
70
|
if (!fileTree) {
|
|
71
71
|
return {
|
|
72
72
|
toolResponse: `Error: No fileTree in agent state. The ${context.toolName} tool requires a virtual filesystem.`,
|
|
@@ -77,8 +77,9 @@ export function withVirtualFs<
|
|
|
77
77
|
const virtualFs = new VirtualFileSystem(
|
|
78
78
|
fileTree,
|
|
79
79
|
resolver,
|
|
80
|
-
|
|
80
|
+
ctx,
|
|
81
81
|
workspaceBase ?? "/",
|
|
82
|
+
inlineFiles,
|
|
82
83
|
);
|
|
83
84
|
const response = await handler(args, { ...context, virtualFs });
|
|
84
85
|
const mutations = virtualFs.getMutations();
|
|
@@ -21,7 +21,8 @@ describe("bash handler with sandbox", () => {
|
|
|
21
21
|
const result = await manager.create({
|
|
22
22
|
initialFiles: { "/home/user/hello.txt": "world" },
|
|
23
23
|
});
|
|
24
|
-
|
|
24
|
+
expect(result).not.toBeNull();
|
|
25
|
+
sandboxId = (result as NonNullable<typeof result>).sandboxId;
|
|
25
26
|
handler = withSandbox(manager, bashHandler);
|
|
26
27
|
});
|
|
27
28
|
|