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.
Files changed (100) hide show
  1. package/dist/{activities-DOViDCTE.d.ts → activities-DRSdt8Y3.d.ts} +2 -2
  2. package/dist/{activities-1xrWRrGJ.d.cts → activities-qPkJDAiq.d.cts} +2 -2
  3. package/dist/adapters/sandbox/bedrock/index.cjs.map +1 -1
  4. package/dist/adapters/sandbox/bedrock/index.d.cts +3 -3
  5. package/dist/adapters/sandbox/bedrock/index.d.ts +3 -3
  6. package/dist/adapters/sandbox/bedrock/index.js.map +1 -1
  7. package/dist/adapters/sandbox/bedrock/workflow.d.cts +2 -2
  8. package/dist/adapters/sandbox/bedrock/workflow.d.ts +2 -2
  9. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
  10. package/dist/adapters/sandbox/daytona/index.d.cts +1 -1
  11. package/dist/adapters/sandbox/daytona/index.d.ts +1 -1
  12. package/dist/adapters/sandbox/daytona/index.js.map +1 -1
  13. package/dist/adapters/sandbox/daytona/workflow.d.cts +1 -1
  14. package/dist/adapters/sandbox/daytona/workflow.d.ts +1 -1
  15. package/dist/adapters/sandbox/e2b/index.cjs.map +1 -1
  16. package/dist/adapters/sandbox/e2b/index.d.cts +1 -1
  17. package/dist/adapters/sandbox/e2b/index.d.ts +1 -1
  18. package/dist/adapters/sandbox/e2b/index.js.map +1 -1
  19. package/dist/adapters/sandbox/e2b/workflow.d.cts +1 -1
  20. package/dist/adapters/sandbox/e2b/workflow.d.ts +1 -1
  21. package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
  22. package/dist/adapters/sandbox/inmemory/index.d.cts +1 -1
  23. package/dist/adapters/sandbox/inmemory/index.d.ts +1 -1
  24. package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
  25. package/dist/adapters/sandbox/inmemory/workflow.d.cts +1 -1
  26. package/dist/adapters/sandbox/inmemory/workflow.d.ts +1 -1
  27. package/dist/adapters/thread/anthropic/index.cjs +0 -1
  28. package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
  29. package/dist/adapters/thread/anthropic/index.d.cts +5 -5
  30. package/dist/adapters/thread/anthropic/index.d.ts +5 -5
  31. package/dist/adapters/thread/anthropic/index.js +0 -1
  32. package/dist/adapters/thread/anthropic/index.js.map +1 -1
  33. package/dist/adapters/thread/anthropic/workflow.d.cts +5 -5
  34. package/dist/adapters/thread/anthropic/workflow.d.ts +5 -5
  35. package/dist/adapters/thread/google-genai/index.cjs +0 -1
  36. package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
  37. package/dist/adapters/thread/google-genai/index.d.cts +5 -5
  38. package/dist/adapters/thread/google-genai/index.d.ts +5 -5
  39. package/dist/adapters/thread/google-genai/index.js +0 -1
  40. package/dist/adapters/thread/google-genai/index.js.map +1 -1
  41. package/dist/adapters/thread/google-genai/workflow.d.cts +5 -5
  42. package/dist/adapters/thread/google-genai/workflow.d.ts +5 -5
  43. package/dist/adapters/thread/langchain/index.cjs +0 -1
  44. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  45. package/dist/adapters/thread/langchain/index.d.cts +5 -5
  46. package/dist/adapters/thread/langchain/index.d.ts +5 -5
  47. package/dist/adapters/thread/langchain/index.js +0 -1
  48. package/dist/adapters/thread/langchain/index.js.map +1 -1
  49. package/dist/adapters/thread/langchain/workflow.d.cts +5 -5
  50. package/dist/adapters/thread/langchain/workflow.d.ts +5 -5
  51. package/dist/index.cjs +95 -54
  52. package/dist/index.cjs.map +1 -1
  53. package/dist/index.d.cts +78 -15
  54. package/dist/index.d.ts +78 -15
  55. package/dist/index.js +95 -54
  56. package/dist/index.js.map +1 -1
  57. package/dist/{proxy-Bm2UTiO_.d.cts → proxy-BDQ3Rj6R.d.cts} +1 -1
  58. package/dist/{proxy-78nc985d.d.ts → proxy-BkvkV2oU.d.ts} +1 -1
  59. package/dist/{thread-manager-CatBkarc.d.cts → thread-manager-BLgvv9Gf.d.cts} +1 -1
  60. package/dist/{thread-manager-CxbWo7q_.d.ts → thread-manager-Cv82H1wi.d.ts} +1 -1
  61. package/dist/{thread-manager-BRE5KkHB.d.cts → thread-manager-DowU4ntB.d.cts} +1 -1
  62. package/dist/{thread-manager-07BaYu_z.d.ts → thread-manager-HsAYkyAV.d.ts} +1 -1
  63. package/dist/{types-ChAMwU3q.d.ts → types-AujBIMMn.d.cts} +5 -8
  64. package/dist/{types-ChAMwU3q.d.cts → types-AujBIMMn.d.ts} +5 -8
  65. package/dist/{types-BkVoEyiH.d.ts → types-BmS-Huc0.d.ts} +145 -139
  66. package/dist/{types-seDYom4M.d.cts → types-CjeGWQm1.d.cts} +145 -139
  67. package/dist/{types-DAv_SLN8.d.ts → types-D6UKZZtj.d.ts} +1 -1
  68. package/dist/{types-BdCdR41N.d.ts → types-DBk-C8zM.d.ts} +1 -1
  69. package/dist/{types-ZHs2v9Ap.d.cts → types-DUvEZSDe.d.cts} +1 -1
  70. package/dist/{types-Dpz2gXLk.d.cts → types-e_38QaKo.d.cts} +1 -1
  71. package/dist/{workflow-B4T3la0p.d.cts → workflow-CNshfqSO.d.cts} +2 -2
  72. package/dist/{workflow-DCmaXLZ_.d.ts → workflow-CTcrPZAV.d.ts} +2 -2
  73. package/dist/workflow.cjs +43 -43
  74. package/dist/workflow.cjs.map +1 -1
  75. package/dist/workflow.d.cts +3 -3
  76. package/dist/workflow.d.ts +3 -3
  77. package/dist/workflow.js +43 -43
  78. package/dist/workflow.js.map +1 -1
  79. package/package.json +1 -1
  80. package/src/adapters/thread/anthropic/thread-manager.ts +6 -6
  81. package/src/adapters/thread/google-genai/thread-manager.ts +6 -6
  82. package/src/adapters/thread/langchain/thread-manager.ts +6 -6
  83. package/src/index.ts +1 -0
  84. package/src/lib/lifecycle.ts +8 -3
  85. package/src/lib/sandbox/index.ts +2 -4
  86. package/src/lib/sandbox/manager.ts +128 -13
  87. package/src/lib/sandbox/sandbox.test.ts +136 -16
  88. package/src/lib/sandbox/types.ts +6 -5
  89. package/src/lib/session/session.integration.test.ts +7 -40
  90. package/src/lib/session/session.ts +78 -50
  91. package/src/lib/session/types.ts +22 -13
  92. package/src/lib/state/types.ts +9 -6
  93. package/src/lib/subagent/handler.ts +18 -12
  94. package/src/lib/subagent/register.ts +11 -12
  95. package/src/lib/types.ts +4 -0
  96. package/src/lib/virtual-fs/filesystem.ts +15 -0
  97. package/src/lib/virtual-fs/types.ts +10 -14
  98. package/src/lib/virtual-fs/virtual-fs.test.ts +64 -0
  99. package/src/lib/virtual-fs/with-virtual-fs.ts +4 -3
  100. 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 { type AgentStateManager, type JsonSerializable } from "../state/types";
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<T extends ToolMap, M = unknown, TContent = string>(
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<T extends ToolMap, M = unknown, TContent = string>(
74
- config: SessionConfig<T, M, TContent>
75
- ): Promise<ZeitlichSession<M, false>>;
76
- export async function createSession<T extends ToolMap, M = unknown, TContent = string>({
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 = threadInit?.mode === "new" && threadInit.threadId
107
- ? threadInit.threadId
108
- : getShortId();
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 }).threadId;
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 }).threadId;
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
- let sandboxStateGetter: (() => Record<string, unknown> | undefined) | undefined;
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 { mode: "inherit"; sandboxId: string; stateUpdate?: Record<string, unknown> };
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: "sandboxId provided but no sandboxOps — cannot manage sandbox lifecycle",
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 }).sandboxId;
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 result = await sandboxOps.createSandbox(
247
- skillFiles ? { initialFiles: skillFiles } : undefined,
248
- );
249
- sandboxId = result.sandboxId;
250
- sandboxOwned = true;
251
- if (result.stateUpdate) {
252
- stateManager.mergeUpdate(result.stateUpdate as Partial<TState>);
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
- const result = await virtualFsConfig.ops.resolveFileTree(
259
- virtualFsConfig.resolverContext,
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: result.fileTree,
263
- resolverContext: virtualFsConfig.resolverContext,
264
- workspaceBase: virtualFsConfig.workspaceBase ?? "/",
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(threadId, uuid4(), await buildContextMessage(), threadKey);
329
+ await appendHumanMessage(
330
+ threadId,
331
+ uuid4(),
332
+ await buildContextMessage(),
333
+ threadKey
334
+ );
307
335
 
308
336
  let exitReason: SessionExitReason = "completed";
309
337
 
@@ -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 { ThreadInit, SandboxInit, SubagentSandboxShutdown } from "../lifecycle";
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(sourceThreadId: string, targetThreadId: string, threadKey?: string): Promise<void>;
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<T extends ToolMap, M = unknown, TContent = string> {
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`, `resolverContext`, and `workspaceBase` into `AgentState`.
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
- ops: VirtualFsOps;
184
- resolverContext: unknown;
185
- workspaceBase?: string;
194
+ ctx: unknown;
186
195
  };
187
196
  }
188
197
 
@@ -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 { AgentStatus, BaseAgentState, TokenUsage, WorkflowTask } from "../types";
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 (e.g. from sandbox stateUpdate) */
102
- mergeUpdate(update: Partial<TCustom>): void;
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 { ThreadInit, SandboxInit, SubagentSandboxShutdown } from "../lifecycle";
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
- >(subagents: [...T], options?: {
49
- getSandboxStateForInheritance?: () => Record<string, unknown> | undefined;
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 = { mode: threadMode as "fork" | "continue", threadId: continuationThreadId };
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" || (allowsContinuation && sandboxCfg.source !== "inherit"));
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 = typeof toolResponse === "string"
235
- ? toolResponse
236
- : JSON.stringify(toolResponse);
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 { createSubagentTool, SUBAGENT_TOOL_NAME, type SubagentArgs } from "./tool";
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 } = createSubagentHandler(subagents, {
47
- getSandboxStateForInheritance: options?.getSandboxStateForInheritance,
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> => createSubagentTool(getEnabled()).schema,
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 = unknown,
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<TCtx, TMeta> as `${TPrefix}${Capitalize<K & string>}`]: VirtualFsOps<TCtx, TMeta>[K];
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
- resolverContext: TCtx;
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`, `resolverContext`, and `workspaceBase`
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, resolverContext, workspaceBase } = state;
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
- resolverContext,
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
- sandboxId = result.sandboxId;
24
+ expect(result).not.toBeNull();
25
+ sandboxId = (result as NonNullable<typeof result>).sandboxId;
25
26
  handler = withSandbox(manager, bashHandler);
26
27
  });
27
28