zeitlich 0.2.22 → 0.2.24

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 (129) hide show
  1. package/README.md +278 -59
  2. package/dist/adapters/sandbox/bedrock/index.cjs +427 -0
  3. package/dist/adapters/sandbox/bedrock/index.cjs.map +1 -0
  4. package/dist/adapters/sandbox/bedrock/index.d.cts +23 -0
  5. package/dist/adapters/sandbox/bedrock/index.d.ts +23 -0
  6. package/dist/adapters/sandbox/bedrock/index.js +424 -0
  7. package/dist/adapters/sandbox/bedrock/index.js.map +1 -0
  8. package/dist/adapters/sandbox/bedrock/workflow.cjs +33 -0
  9. package/dist/adapters/sandbox/bedrock/workflow.cjs.map +1 -0
  10. package/dist/adapters/sandbox/bedrock/workflow.d.cts +29 -0
  11. package/dist/adapters/sandbox/bedrock/workflow.d.ts +29 -0
  12. package/dist/adapters/sandbox/bedrock/workflow.js +31 -0
  13. package/dist/adapters/sandbox/bedrock/workflow.js.map +1 -0
  14. package/dist/adapters/sandbox/daytona/index.cjs +4 -1
  15. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
  16. package/dist/adapters/sandbox/daytona/index.d.cts +2 -1
  17. package/dist/adapters/sandbox/daytona/index.d.ts +2 -1
  18. package/dist/adapters/sandbox/daytona/index.js +4 -1
  19. package/dist/adapters/sandbox/daytona/index.js.map +1 -1
  20. package/dist/adapters/sandbox/daytona/workflow.cjs +1 -0
  21. package/dist/adapters/sandbox/daytona/workflow.cjs.map +1 -1
  22. package/dist/adapters/sandbox/daytona/workflow.d.cts +1 -1
  23. package/dist/adapters/sandbox/daytona/workflow.d.ts +1 -1
  24. package/dist/adapters/sandbox/daytona/workflow.js +1 -0
  25. package/dist/adapters/sandbox/daytona/workflow.js.map +1 -1
  26. package/dist/adapters/sandbox/inmemory/index.cjs +16 -2
  27. package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
  28. package/dist/adapters/sandbox/inmemory/index.d.cts +3 -2
  29. package/dist/adapters/sandbox/inmemory/index.d.ts +3 -2
  30. package/dist/adapters/sandbox/inmemory/index.js +16 -2
  31. package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
  32. package/dist/adapters/sandbox/inmemory/workflow.cjs +1 -0
  33. package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -1
  34. package/dist/adapters/sandbox/inmemory/workflow.d.cts +1 -1
  35. package/dist/adapters/sandbox/inmemory/workflow.d.ts +1 -1
  36. package/dist/adapters/sandbox/inmemory/workflow.js +1 -0
  37. package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -1
  38. package/dist/adapters/sandbox/virtual/index.cjs +45 -11
  39. package/dist/adapters/sandbox/virtual/index.cjs.map +1 -1
  40. package/dist/adapters/sandbox/virtual/index.d.cts +6 -5
  41. package/dist/adapters/sandbox/virtual/index.d.ts +6 -5
  42. package/dist/adapters/sandbox/virtual/index.js +45 -11
  43. package/dist/adapters/sandbox/virtual/index.js.map +1 -1
  44. package/dist/adapters/sandbox/virtual/workflow.cjs +1 -0
  45. package/dist/adapters/sandbox/virtual/workflow.cjs.map +1 -1
  46. package/dist/adapters/sandbox/virtual/workflow.d.cts +3 -3
  47. package/dist/adapters/sandbox/virtual/workflow.d.ts +3 -3
  48. package/dist/adapters/sandbox/virtual/workflow.js +1 -0
  49. package/dist/adapters/sandbox/virtual/workflow.js.map +1 -1
  50. package/dist/adapters/thread/google-genai/index.d.cts +3 -3
  51. package/dist/adapters/thread/google-genai/index.d.ts +3 -3
  52. package/dist/adapters/thread/google-genai/workflow.d.cts +3 -3
  53. package/dist/adapters/thread/google-genai/workflow.d.ts +3 -3
  54. package/dist/adapters/thread/langchain/index.d.cts +3 -3
  55. package/dist/adapters/thread/langchain/index.d.ts +3 -3
  56. package/dist/adapters/thread/langchain/workflow.d.cts +3 -3
  57. package/dist/adapters/thread/langchain/workflow.d.ts +3 -3
  58. package/dist/index.cjs +443 -71
  59. package/dist/index.cjs.map +1 -1
  60. package/dist/index.d.cts +64 -10
  61. package/dist/index.d.ts +64 -10
  62. package/dist/index.js +442 -71
  63. package/dist/index.js.map +1 -1
  64. package/dist/{queries-Bw6WEPMw.d.cts → queries-BYGBImeC.d.cts} +1 -1
  65. package/dist/{queries-C27raDaB.d.ts → queries-DwBe2CAA.d.ts} +1 -1
  66. package/dist/{types-C5bkx6kQ.d.ts → types-7PeMi1bD.d.cts} +167 -36
  67. package/dist/{types-BJ8itUAl.d.cts → types-Bf8KV0Ci.d.cts} +6 -6
  68. package/dist/{types-HBosetv3.d.cts → types-ChAMwU3q.d.cts} +2 -0
  69. package/dist/{types-HBosetv3.d.ts → types-ChAMwU3q.d.ts} +2 -0
  70. package/dist/{types-YbL7JpEA.d.cts → types-D_igp10o.d.cts} +11 -0
  71. package/dist/{types-YbL7JpEA.d.ts → types-D_igp10o.d.ts} +11 -0
  72. package/dist/types-DhTCEMhr.d.cts +64 -0
  73. package/dist/{types-ENYCKFBk.d.ts → types-LVKmCNds.d.ts} +6 -6
  74. package/dist/types-d9NznUqd.d.ts +64 -0
  75. package/dist/{types-ClsHhtwL.d.cts → types-hmferhc2.d.ts} +167 -36
  76. package/dist/workflow.cjs +308 -63
  77. package/dist/workflow.cjs.map +1 -1
  78. package/dist/workflow.d.cts +54 -32
  79. package/dist/workflow.d.ts +54 -32
  80. package/dist/workflow.js +306 -61
  81. package/dist/workflow.js.map +1 -1
  82. package/package.json +27 -2
  83. package/src/adapters/sandbox/bedrock/filesystem.ts +313 -0
  84. package/src/adapters/sandbox/bedrock/index.ts +259 -0
  85. package/src/adapters/sandbox/bedrock/proxy.ts +56 -0
  86. package/src/adapters/sandbox/bedrock/types.ts +24 -0
  87. package/src/adapters/sandbox/daytona/filesystem.ts +1 -1
  88. package/src/adapters/sandbox/daytona/index.ts +4 -0
  89. package/src/adapters/sandbox/daytona/proxy.ts +4 -3
  90. package/src/adapters/sandbox/e2b/index.ts +5 -0
  91. package/src/adapters/sandbox/inmemory/index.ts +24 -4
  92. package/src/adapters/sandbox/inmemory/proxy.ts +2 -2
  93. package/src/adapters/sandbox/virtual/filesystem.ts +44 -18
  94. package/src/adapters/sandbox/virtual/provider.ts +13 -0
  95. package/src/adapters/sandbox/virtual/proxy.ts +1 -0
  96. package/src/adapters/sandbox/virtual/types.ts +9 -4
  97. package/src/adapters/sandbox/virtual/virtual-sandbox.test.ts +26 -0
  98. package/src/index.ts +2 -1
  99. package/src/lib/lifecycle.ts +57 -0
  100. package/src/lib/sandbox/manager.ts +13 -1
  101. package/src/lib/sandbox/node-fs.ts +115 -0
  102. package/src/lib/sandbox/types.ts +13 -4
  103. package/src/lib/session/index.ts +1 -0
  104. package/src/lib/session/session-edge-cases.integration.test.ts +447 -33
  105. package/src/lib/session/session.integration.test.ts +149 -32
  106. package/src/lib/session/session.ts +138 -33
  107. package/src/lib/session/types.ts +56 -17
  108. package/src/lib/skills/fs-provider.ts +65 -4
  109. package/src/lib/skills/handler.ts +43 -1
  110. package/src/lib/skills/index.ts +0 -1
  111. package/src/lib/skills/register.ts +17 -1
  112. package/src/lib/skills/skills.integration.test.ts +308 -24
  113. package/src/lib/skills/types.ts +6 -0
  114. package/src/lib/subagent/define.ts +5 -4
  115. package/src/lib/subagent/handler.ts +143 -14
  116. package/src/lib/subagent/index.ts +3 -0
  117. package/src/lib/subagent/register.ts +10 -3
  118. package/src/lib/subagent/signals.ts +8 -0
  119. package/src/lib/subagent/subagent.integration.test.ts +853 -150
  120. package/src/lib/subagent/tool.ts +2 -2
  121. package/src/lib/subagent/types.ts +77 -19
  122. package/src/lib/subagent/workflow.ts +83 -12
  123. package/src/lib/tool-router/router.integration.test.ts +137 -4
  124. package/src/lib/tool-router/router.ts +19 -6
  125. package/src/lib/tool-router/types.ts +11 -0
  126. package/src/lib/workflow.test.ts +89 -21
  127. package/src/lib/workflow.ts +33 -18
  128. package/src/workflow.ts +6 -1
  129. package/tsup.config.ts +3 -0
package/dist/index.cjs CHANGED
@@ -6,6 +6,7 @@ var crypto = require('crypto');
6
6
  var common = require('@temporalio/common');
7
7
  var path = require('path');
8
8
  var activity = require('@temporalio/activity');
9
+ var fs = require('fs');
9
10
 
10
11
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
12
 
@@ -18,8 +19,8 @@ function createToolRouter(options) {
18
19
  for (const [_key, tool] of Object.entries(options.tools)) {
19
20
  toolMap.set(tool.name, tool);
20
21
  }
21
- const resolve = (v) => typeof v === "function" ? v() : v;
22
- const isEnabled = (tool) => resolve(tool.enabled) ?? true;
22
+ const resolve2 = (v) => typeof v === "function" ? v() : v;
23
+ const isEnabled = (tool) => resolve2(tool.enabled) ?? true;
23
24
  if (options.plugins) {
24
25
  for (const plugin of options.plugins) {
25
26
  toolMap.set(plugin.name, plugin);
@@ -88,7 +89,12 @@ function createToolRouter(options) {
88
89
  result: { error: errorStr, suppressed: true }
89
90
  };
90
91
  }
91
- throw workflow.ApplicationFailure.fromError(error, { nonRetryable: true });
92
+ return {
93
+ content: JSON.stringify({
94
+ error: "The tool encountered an error. Please try again or use a different approach."
95
+ }),
96
+ result: { error: errorStr, suppressed: true }
97
+ };
92
98
  }
93
99
  async function runPostHooks(toolCall, tool, toolResult, effectiveArgs, turn, durationMs) {
94
100
  if (tool?.hooks?.onPostToolUse) {
@@ -97,7 +103,8 @@ function createToolRouter(options) {
97
103
  result: toolResult.data,
98
104
  threadId: options.threadId,
99
105
  turn,
100
- durationMs
106
+ durationMs,
107
+ ...toolResult.metadata && { metadata: toolResult.metadata }
101
108
  });
102
109
  }
103
110
  if (options.hooks?.onPostToolUse) {
@@ -110,7 +117,7 @@ function createToolRouter(options) {
110
117
  });
111
118
  }
112
119
  }
113
- async function processToolCall(toolCall, turn, sandboxId) {
120
+ async function processToolCall(toolCall, turn, sandboxId, sandboxStateUpdate) {
114
121
  const startTime = Date.now();
115
122
  const tool = toolMap.get(toolCall.name);
116
123
  const preResult = await runPreHooks(toolCall, tool, turn);
@@ -130,13 +137,15 @@ function createToolRouter(options) {
130
137
  let result;
131
138
  let content;
132
139
  let resultAppended = false;
140
+ let metadata;
133
141
  try {
134
142
  if (tool) {
135
143
  const routerContext = {
136
144
  threadId: options.threadId,
137
145
  toolCallId: toolCall.id,
138
146
  toolName: toolCall.name,
139
- ...sandboxId !== void 0 && { sandboxId }
147
+ ...sandboxId !== void 0 && { sandboxId },
148
+ ...sandboxStateUpdate && { sandboxStateUpdate }
140
149
  };
141
150
  const response = await tool.handler(
142
151
  effectiveArgs,
@@ -145,6 +154,7 @@ function createToolRouter(options) {
145
154
  result = response.data;
146
155
  content = response.toolResponse;
147
156
  resultAppended = response.resultAppended === true;
157
+ metadata = response.metadata;
148
158
  } else {
149
159
  result = { error: `Unknown tool: ${toolCall.name}` };
150
160
  content = JSON.stringify(result, null, 2);
@@ -177,7 +187,8 @@ function createToolRouter(options) {
177
187
  const toolResult = {
178
188
  toolCallId: toolCall.id,
179
189
  name: toolCall.name,
180
- data: result
190
+ data: result,
191
+ ...metadata && { metadata }
181
192
  };
182
193
  await runPostHooks(
183
194
  toolCall,
@@ -198,7 +209,7 @@ function createToolRouter(options) {
198
209
  if (!tool || !isEnabled(tool)) {
199
210
  throw new Error(`Tool ${toolCall.name} not found`);
200
211
  }
201
- const parsedArgs = resolve(tool.schema).parse(toolCall.args);
212
+ const parsedArgs = resolve2(tool.schema).parse(toolCall.args);
202
213
  return {
203
214
  id: toolCall.id ?? "",
204
215
  name: toolCall.name,
@@ -215,8 +226,8 @@ function createToolRouter(options) {
215
226
  getToolDefinitions() {
216
227
  return Array.from(toolMap).filter(([, tool]) => isEnabled(tool)).map(([name, tool]) => ({
217
228
  name,
218
- description: resolve(tool.description),
219
- schema: resolve(tool.schema),
229
+ description: resolve2(tool.description),
230
+ schema: resolve2(tool.schema),
220
231
  strict: tool.strict,
221
232
  max_uses: tool.max_uses
222
233
  }));
@@ -227,9 +238,10 @@ function createToolRouter(options) {
227
238
  }
228
239
  const turn = context?.turn ?? 0;
229
240
  const sandboxId = context?.sandboxId;
241
+ const sandboxStateUpdate = context?.sandboxStateUpdate;
230
242
  if (options.parallel) {
231
243
  const results2 = await Promise.all(
232
- toolCalls.map((tc) => processToolCall(tc, turn, sandboxId))
244
+ toolCalls.map((tc) => processToolCall(tc, turn, sandboxId, sandboxStateUpdate))
233
245
  );
234
246
  return results2.filter(
235
247
  (r) => r !== null
@@ -237,7 +249,7 @@ function createToolRouter(options) {
237
249
  }
238
250
  const results = [];
239
251
  for (const toolCall of toolCalls) {
240
- const result = await processToolCall(toolCall, turn, sandboxId);
252
+ const result = await processToolCall(toolCall, turn, sandboxId, sandboxStateUpdate);
241
253
  if (result !== null) {
242
254
  results.push(result);
243
255
  }
@@ -281,7 +293,8 @@ function createToolRouter(options) {
281
293
  return {
282
294
  toolCallId: toolCall.id,
283
295
  name: toolCall.name,
284
- data: response.data
296
+ data: response.data,
297
+ ...response.metadata && { metadata: response.metadata }
285
298
  };
286
299
  };
287
300
  if (options.parallel) {
@@ -325,7 +338,7 @@ function getShortId(length = 12) {
325
338
  var SUBAGENT_TOOL_NAME = "Subagent";
326
339
  function buildSubagentDescription(subagents) {
327
340
  const subagentList = subagents.map((s) => {
328
- const continuation = s.allowThreadContinuation ? "\n*(Supports thread continuation \u2014 pass a threadId to resume a previous conversation)*" : "";
341
+ const continuation = s.thread && s.thread !== "new" ? "\n*(Supports thread continuation \u2014 pass a threadId to resume a previous conversation)*" : "";
329
342
  return `## ${s.agentName}
330
343
  ${s.description}${continuation}`;
331
344
  }).join("\n\n");
@@ -341,7 +354,7 @@ function createSubagentTool(subagents) {
341
354
  }
342
355
  const names = subagents.map((s) => s.agentName);
343
356
  const hasThreadContinuation = subagents.some(
344
- (s) => s.allowThreadContinuation
357
+ (s) => s.thread && s.thread !== "new"
345
358
  );
346
359
  const baseFields = {
347
360
  subagent: z14__default.default.enum(names).describe("The type of subagent to launch"),
@@ -360,9 +373,25 @@ function createSubagentTool(subagents) {
360
373
  schema
361
374
  };
362
375
  }
376
+ var childResultSignal = workflow.defineSignal("childResult");
377
+ var destroySandboxSignal = workflow.defineSignal("destroySandbox");
378
+
379
+ // src/lib/subagent/handler.ts
380
+ function resolveSandboxConfig(config) {
381
+ if (!config || config === "none") return { source: "none" };
382
+ if (config === "inherit") return { source: "inherit" };
383
+ if (config === "own") return { source: "own" };
384
+ return { source: "own", shutdown: config.shutdown };
385
+ }
363
386
  function createSubagentHandler(subagents) {
364
387
  const { taskQueue: parentTaskQueue } = workflow.workflowInfo();
365
- return async (args, context) => {
388
+ const childResults = /* @__PURE__ */ new Map();
389
+ const pendingDestroys = /* @__PURE__ */ new Map();
390
+ const threadSandboxes = /* @__PURE__ */ new Map();
391
+ workflow.setHandler(childResultSignal, ({ childWorkflowId, result }) => {
392
+ childResults.set(childWorkflowId, result);
393
+ });
394
+ const handler = async (args, context) => {
366
395
  const config = subagents.find((s) => s.agentName === args.subagent);
367
396
  if (!config) {
368
397
  throw new Error(
@@ -371,12 +400,36 @@ function createSubagentHandler(subagents) {
371
400
  }
372
401
  const childWorkflowId = `${args.subagent}-${getShortId()}`;
373
402
  const { sandboxId: parentSandboxId } = context;
374
- const inheritSandbox = config.sandbox !== "own" && !!parentSandboxId;
403
+ const sandboxCfg = resolveSandboxConfig(config.sandbox);
404
+ if (sandboxCfg.source === "inherit" && !parentSandboxId) {
405
+ throw new Error(
406
+ `Subagent "${config.agentName}" is configured with sandbox: "inherit" but the parent has no sandbox`
407
+ );
408
+ }
409
+ const threadMode = config.thread ?? "new";
410
+ const allowsContinuation = threadMode !== "new";
411
+ const continuationThreadId = args.threadId && allowsContinuation ? args.threadId : void 0;
412
+ let thread;
413
+ if (continuationThreadId) {
414
+ thread = { mode: threadMode, threadId: continuationThreadId };
415
+ }
416
+ let sandbox;
417
+ if (sandboxCfg.source === "inherit" && parentSandboxId) {
418
+ sandbox = {
419
+ mode: "inherit",
420
+ sandboxId: parentSandboxId,
421
+ ...context.sandboxStateUpdate && { stateUpdate: context.sandboxStateUpdate }
422
+ };
423
+ } else if (sandboxCfg.source === "own") {
424
+ const prevSbId = continuationThreadId ? threadSandboxes.get(continuationThreadId) : void 0;
425
+ if (prevSbId) {
426
+ sandbox = { mode: "fork", sandboxId: prevSbId };
427
+ }
428
+ }
375
429
  const workflowInput = {
376
- ...args.threadId && args.threadId !== null && config.allowThreadContinuation && {
377
- previousThreadId: args.threadId
378
- },
379
- ...inheritSandbox && { sandboxId: parentSandboxId }
430
+ ...thread && { thread },
431
+ ...sandbox && { sandbox },
432
+ ...sandboxCfg.shutdown && { sandboxShutdown: sandboxCfg.shutdown }
380
433
  };
381
434
  const resolvedContext = config.context === void 0 ? void 0 : typeof config.context === "function" ? config.context() : config.context;
382
435
  const childOpts = {
@@ -384,17 +437,44 @@ function createSubagentHandler(subagents) {
384
437
  args: resolvedContext === void 0 ? [args.prompt, workflowInput] : [args.prompt, workflowInput, resolvedContext],
385
438
  taskQueue: config.taskQueue ?? parentTaskQueue
386
439
  };
440
+ const childHandle = await workflow.startChild(config.workflow, childOpts);
441
+ const usesOwnSandbox = sandboxCfg.source === "own" || allowsContinuation && sandboxCfg.source !== "inherit";
442
+ if (usesOwnSandbox) {
443
+ pendingDestroys.set(childWorkflowId, childHandle);
444
+ }
445
+ await Promise.race([
446
+ workflow.condition(() => childResults.has(childWorkflowId)),
447
+ childHandle.result()
448
+ ]);
449
+ if (!childResults.has(childWorkflowId)) {
450
+ await workflow.condition(() => childResults.has(childWorkflowId));
451
+ }
452
+ const childResult = childResults.get(childWorkflowId);
453
+ childResults.delete(childWorkflowId);
454
+ if (!childResult) {
455
+ return {
456
+ toolResponse: "Subagent workflow did not signal a result",
457
+ data: null
458
+ };
459
+ }
387
460
  const {
388
461
  toolResponse,
389
462
  data,
390
463
  usage,
391
- threadId: childThreadId
392
- } = typeof config.workflow === "string" ? await workflow.executeChild(config.workflow, childOpts) : await workflow.executeChild(config.workflow, childOpts);
464
+ threadId: childThreadId,
465
+ sandboxId: childSandboxId,
466
+ metadata
467
+ } = childResult;
468
+ if (allowsContinuation && childSandboxId && childThreadId) {
469
+ threadSandboxes.set(childThreadId, childSandboxId);
470
+ }
393
471
  if (!toolResponse) {
394
472
  return {
395
473
  toolResponse: "Subagent workflow returned no response",
396
474
  data: null,
397
- ...usage && { usage }
475
+ ...usage && { usage },
476
+ ...childSandboxId && { sandboxId: childSandboxId },
477
+ ...metadata && { metadata }
398
478
  };
399
479
  }
400
480
  const validated = config.resultSchema ? config.resultSchema.safeParse(data) : null;
@@ -402,11 +482,13 @@ function createSubagentHandler(subagents) {
402
482
  return {
403
483
  toolResponse: `Subagent workflow returned invalid data: ${validated.error.message}`,
404
484
  data: null,
405
- ...usage && { usage }
485
+ ...usage && { usage },
486
+ ...childSandboxId && { sandboxId: childSandboxId },
487
+ ...metadata && { metadata }
406
488
  };
407
489
  }
408
490
  let finalToolResponse = toolResponse;
409
- if (config.allowThreadContinuation && childThreadId) {
491
+ if (allowsContinuation && childThreadId) {
410
492
  finalToolResponse = typeof toolResponse === "string" ? `${toolResponse}
411
493
 
412
494
  [${config.agentName} Thread ID: ${childThreadId}]` : toolResponse;
@@ -414,9 +496,22 @@ function createSubagentHandler(subagents) {
414
496
  return {
415
497
  toolResponse: finalToolResponse,
416
498
  data: validated ? validated.data : data,
417
- ...usage && { usage }
499
+ ...usage && { usage },
500
+ ...childSandboxId && { sandboxId: childSandboxId },
501
+ ...metadata && { metadata }
418
502
  };
419
503
  };
504
+ const destroySubagentSandboxes = async () => {
505
+ const handles = [...pendingDestroys.values()];
506
+ pendingDestroys.clear();
507
+ await Promise.all(
508
+ handles.map(async (handle) => {
509
+ await handle.signal(destroySandboxSignal);
510
+ await handle.result();
511
+ })
512
+ );
513
+ };
514
+ return { handler, destroySubagentSandboxes };
420
515
  }
421
516
 
422
517
  // src/lib/subagent/register.ts
@@ -430,12 +525,13 @@ function buildSubagentRegistration(subagents) {
430
525
  if (s.hooks) subagentHooksMap.set(s.agentName, s.hooks);
431
526
  }
432
527
  const resolveSubagentName = (args) => args.subagent;
433
- return {
528
+ const { handler, destroySubagentSandboxes } = createSubagentHandler(subagents);
529
+ const registration = {
434
530
  name: SUBAGENT_TOOL_NAME,
435
531
  enabled: () => getEnabled().length > 0,
436
532
  description: () => createSubagentTool(getEnabled()).description,
437
533
  schema: () => createSubagentTool(getEnabled()).schema,
438
- handler: createSubagentHandler(subagents),
534
+ handler,
439
535
  ...subagentHooksMap.size > 0 && {
440
536
  hooks: {
441
537
  onPreToolUse: async (ctx) => {
@@ -453,6 +549,7 @@ function buildSubagentRegistration(subagents) {
453
549
  }
454
550
  }
455
551
  };
552
+ return { registration, destroySubagentSandboxes };
456
553
  }
457
554
  var READ_SKILL_TOOL_NAME = "ReadSkill";
458
555
  function buildReadSkillDescription(skills) {
@@ -478,6 +575,29 @@ function createReadSkillTool(skills) {
478
575
  }
479
576
 
480
577
  // src/lib/skills/handler.ts
578
+ function formatSkillResponse(skill) {
579
+ const parts = [];
580
+ parts.push(`<skill_content name="${skill.name}">`);
581
+ parts.push(skill.instructions);
582
+ if (skill.location) {
583
+ parts.push(`
584
+ Skill directory: ${skill.location}`);
585
+ parts.push(
586
+ "Relative paths in this skill resolve against the skill directory above."
587
+ );
588
+ }
589
+ const resources = skill.resourceContents ? Object.keys(skill.resourceContents) : [];
590
+ if (resources.length > 0) {
591
+ parts.push("");
592
+ parts.push("<skill_resources>");
593
+ for (const r of resources) {
594
+ parts.push(` <file>${r}</file>`);
595
+ }
596
+ parts.push("</skill_resources>");
597
+ }
598
+ parts.push("</skill_content>");
599
+ return parts.join("\n");
600
+ }
481
601
  function createReadSkillHandler(skills) {
482
602
  const skillMap = new Map(skills.map((s) => [s.name, s]));
483
603
  return (args) => {
@@ -491,22 +611,42 @@ function createReadSkillHandler(skills) {
491
611
  };
492
612
  }
493
613
  return {
494
- toolResponse: skill.instructions,
614
+ toolResponse: formatSkillResponse(skill),
495
615
  data: null
496
616
  };
497
617
  };
498
618
  }
499
619
 
500
620
  // src/lib/skills/register.ts
621
+ function validateSkillNames(skills) {
622
+ const names = skills.map((s) => s.name);
623
+ const dupes = names.filter((n, i) => names.indexOf(n) !== i);
624
+ if (dupes.length > 0) {
625
+ throw new Error(
626
+ `Duplicate skill names: ${[...new Set(dupes)].join(", ")}`
627
+ );
628
+ }
629
+ }
501
630
  function buildSkillRegistration(skills) {
502
631
  if (skills.length === 0) return null;
632
+ validateSkillNames(skills);
503
633
  return {
504
634
  ...createReadSkillTool(skills),
505
635
  handler: createReadSkillHandler(skills)
506
636
  };
507
637
  }
508
- var createSession = async ({
509
- threadId: providedThreadId,
638
+ function collectSkillFiles(skills) {
639
+ let files;
640
+ for (const skill of skills) {
641
+ if (!skill.resourceContents || !skill.location) continue;
642
+ for (const [relPath, content] of Object.entries(skill.resourceContents)) {
643
+ files ??= {};
644
+ files[`${skill.location}/${relPath}`] = content;
645
+ }
646
+ }
647
+ return files;
648
+ }
649
+ async function createSession({
510
650
  agentName,
511
651
  maxTurns = 50,
512
652
  metadata = {},
@@ -519,13 +659,27 @@ var createSession = async ({
519
659
  processToolsInParallel = true,
520
660
  hooks = {},
521
661
  appendSystemPrompt = true,
522
- continueThread = false,
523
662
  waitForInputTimeout = "48h",
524
- sandbox: sandboxOps,
525
- sandboxId: inheritedSandboxId
526
- }) => {
527
- const sourceThreadId = continueThread ? providedThreadId : void 0;
528
- const threadId = continueThread && providedThreadId ? getShortId() : providedThreadId ?? getShortId();
663
+ sandboxOps,
664
+ thread: threadInit,
665
+ sandbox: sandboxInit,
666
+ sandboxShutdown = "destroy"
667
+ }) {
668
+ const threadMode = threadInit?.mode ?? "new";
669
+ let threadId;
670
+ let sourceThreadId;
671
+ switch (threadMode) {
672
+ case "new":
673
+ threadId = threadInit?.mode === "new" && threadInit.threadId ? threadInit.threadId : getShortId();
674
+ break;
675
+ case "continue":
676
+ threadId = threadInit.threadId;
677
+ break;
678
+ case "fork":
679
+ sourceThreadId = threadInit.threadId;
680
+ threadId = getShortId();
681
+ break;
682
+ }
529
683
  const {
530
684
  appendToolResult,
531
685
  appendHumanMessage,
@@ -534,9 +688,13 @@ var createSession = async ({
534
688
  forkThread
535
689
  } = threadOps;
536
690
  const plugins = [];
691
+ let destroySubagentSandboxes;
537
692
  if (subagents) {
538
- const reg = buildSubagentRegistration(subagents);
539
- if (reg) plugins.push(reg);
693
+ const result = buildSubagentRegistration(subagents);
694
+ if (result) {
695
+ plugins.push(result.registration);
696
+ destroySubagentSandboxes = result.destroySubagentSandboxes;
697
+ }
540
698
  }
541
699
  if (skills) {
542
700
  const reg = buildSkillRegistration(skills);
@@ -584,12 +742,52 @@ var createSession = async ({
584
742
  stateManager.run();
585
743
  }
586
744
  );
587
- let sandboxId = inheritedSandboxId;
588
- const ownsSandbox = !sandboxId && !!sandboxOps;
589
- if (ownsSandbox) {
590
- const result = await sandboxOps.createSandbox({ id: threadId });
745
+ const sandboxMode = sandboxInit?.mode;
746
+ let sandboxId;
747
+ let sandboxOwned = false;
748
+ let sandboxStateUpdate;
749
+ if (sandboxMode === "inherit") {
750
+ const inheritInit = sandboxInit;
751
+ sandboxId = inheritInit.sandboxId;
752
+ if (inheritInit.stateUpdate) {
753
+ sandboxStateUpdate = inheritInit.stateUpdate;
754
+ stateManager.mergeUpdate(inheritInit.stateUpdate);
755
+ }
756
+ if (!sandboxOps) {
757
+ throw workflow.ApplicationFailure.create({
758
+ message: "sandboxId provided but no sandboxOps \u2014 cannot manage sandbox lifecycle",
759
+ nonRetryable: true
760
+ });
761
+ }
762
+ } else if (sandboxMode === "continue") {
763
+ if (!sandboxOps) {
764
+ throw workflow.ApplicationFailure.create({
765
+ message: "No sandboxOps provided \u2014 cannot continue sandbox",
766
+ nonRetryable: true
767
+ });
768
+ }
769
+ sandboxId = sandboxInit.sandboxId;
770
+ sandboxOwned = true;
771
+ } else if (sandboxMode === "fork") {
772
+ if (!sandboxOps) {
773
+ throw workflow.ApplicationFailure.create({
774
+ message: "No sandboxOps provided \u2014 cannot fork sandbox",
775
+ nonRetryable: true
776
+ });
777
+ }
778
+ sandboxId = await sandboxOps.forkSandbox(
779
+ sandboxInit.sandboxId
780
+ );
781
+ sandboxOwned = true;
782
+ } else if (sandboxOps) {
783
+ const skillFiles = skills ? collectSkillFiles(skills) : void 0;
784
+ const result = await sandboxOps.createSandbox(
785
+ skillFiles ? { initialFiles: skillFiles } : void 0
786
+ );
591
787
  sandboxId = result.sandboxId;
788
+ sandboxOwned = true;
592
789
  if (result.stateUpdate) {
790
+ sandboxStateUpdate = result.stateUpdate;
593
791
  stateManager.mergeUpdate(result.stateUpdate);
594
792
  }
595
793
  }
@@ -601,9 +799,9 @@ var createSession = async ({
601
799
  });
602
800
  }
603
801
  const systemPrompt = stateManager.getSystemPrompt();
604
- if (continueThread && sourceThreadId) {
802
+ if (threadMode === "fork" && sourceThreadId) {
605
803
  await forkThread(sourceThreadId, threadId);
606
- } else {
804
+ } else if (threadMode === "continue") ; else {
607
805
  if (appendSystemPrompt) {
608
806
  if (!systemPrompt || systemPrompt.trim() === "") {
609
807
  throw workflow.ApplicationFailure.create({
@@ -638,7 +836,8 @@ var createSession = async ({
638
836
  threadId,
639
837
  finalMessage: message,
640
838
  exitReason,
641
- usage: stateManager.getTotalUsage()
839
+ usage: stateManager.getTotalUsage(),
840
+ sandboxId
642
841
  };
643
842
  }
644
843
  const parsedToolCalls = [];
@@ -660,7 +859,8 @@ var createSession = async ({
660
859
  parsedToolCalls,
661
860
  {
662
861
  turn: currentTurn,
663
- ...sandboxId !== void 0 && { sandboxId }
862
+ ...sandboxId !== void 0 && { sandboxId },
863
+ ...sandboxStateUpdate && { sandboxStateUpdate }
664
864
  }
665
865
  );
666
866
  for (const result of toolCallResults) {
@@ -689,30 +889,40 @@ var createSession = async ({
689
889
  throw workflow.ApplicationFailure.fromError(error);
690
890
  } finally {
691
891
  await callSessionEnd(exitReason, stateManager.getTurns());
692
- if (ownsSandbox && sandboxId && sandboxOps) {
693
- await sandboxOps.destroySandbox(sandboxId);
892
+ if (sandboxOwned && sandboxId && sandboxOps) {
893
+ switch (sandboxShutdown) {
894
+ case "destroy":
895
+ await sandboxOps.destroySandbox(sandboxId);
896
+ break;
897
+ case "pause":
898
+ case "pause-until-parent-close":
899
+ await sandboxOps.pauseSandbox(sandboxId);
900
+ break;
901
+ }
902
+ }
903
+ if (destroySubagentSandboxes) {
904
+ await destroySubagentSandboxes();
694
905
  }
695
906
  }
696
907
  return {
697
908
  threadId,
698
909
  finalMessage: null,
699
910
  exitReason,
700
- usage: stateManager.getTotalUsage()
911
+ usage: stateManager.getTotalUsage(),
912
+ sandboxId
701
913
  };
702
914
  }
703
915
  };
704
- };
916
+ }
705
917
 
706
918
  // src/lib/workflow.ts
707
919
  function defineWorkflow(config, fn) {
708
920
  const workflow = async (input, workflowInput = {}) => {
709
921
  const sessionInput = {
710
922
  agentName: config.name,
711
- ...workflowInput.previousThreadId && {
712
- threadId: workflowInput.previousThreadId,
713
- continueThread: true
714
- },
715
- ...workflowInput.sandboxId && { sandboxId: workflowInput.sandboxId }
923
+ sandboxShutdown: config.sandboxShutdown ?? "destroy",
924
+ ...workflowInput.thread && { thread: workflowInput.thread },
925
+ ...workflowInput.sandbox && { sandbox: workflowInput.sandbox }
716
926
  };
717
927
  return fn(input, sessionInput);
718
928
  };
@@ -970,22 +1180,58 @@ function defineSubagent(definition, overrides) {
970
1180
  ...overrides
971
1181
  };
972
1182
  }
973
-
974
- // src/lib/subagent/workflow.ts
975
1183
  function defineSubagentWorkflow(config, fn) {
976
- const workflow = async (prompt, workflowInput, context) => {
1184
+ const workflow$1 = async (prompt, workflowInput, context) => {
1185
+ const effectiveShutdown = workflowInput.sandboxShutdown ?? config.sandboxShutdown ?? "destroy";
977
1186
  const sessionInput = {
978
1187
  agentName: config.name,
979
- ...workflowInput.previousThreadId && {
980
- threadId: workflowInput.previousThreadId,
981
- continueThread: true
982
- },
983
- ...workflowInput.sandboxId && { sandboxId: workflowInput.sandboxId }
1188
+ sandboxShutdown: effectiveShutdown,
1189
+ ...workflowInput.thread && { thread: workflowInput.thread },
1190
+ ...workflowInput.sandbox && { sandbox: workflowInput.sandbox }
984
1191
  };
985
- return fn(prompt, sessionInput, context ?? {});
1192
+ const { destroySandbox, ...result } = await fn(
1193
+ prompt,
1194
+ sessionInput,
1195
+ context ?? {}
1196
+ );
1197
+ if (effectiveShutdown === "pause-until-parent-close") {
1198
+ if (!destroySandbox) {
1199
+ throw workflow.ApplicationFailure.create({
1200
+ message: `Subagent "${config.name}" has sandboxShutdown="pause-until-parent-close" but fn did not return a destroySandbox callback`,
1201
+ nonRetryable: true
1202
+ });
1203
+ }
1204
+ if (!result.sandboxId) {
1205
+ throw workflow.ApplicationFailure.create({
1206
+ message: `Subagent "${config.name}" has sandboxShutdown="pause-until-parent-close" but fn did not return a sandboxId`,
1207
+ nonRetryable: true
1208
+ });
1209
+ }
1210
+ }
1211
+ const { parent } = workflow.workflowInfo();
1212
+ if (!parent) {
1213
+ throw workflow.ApplicationFailure.create({
1214
+ message: "Subagent workflow called without a parent workflow",
1215
+ nonRetryable: true
1216
+ });
1217
+ }
1218
+ const parentHandle = workflow.getExternalWorkflowHandle(parent.workflowId);
1219
+ await parentHandle.signal(childResultSignal, {
1220
+ childWorkflowId: workflow.workflowInfo().workflowId,
1221
+ result
1222
+ });
1223
+ if (destroySandbox) {
1224
+ let destroyRequested = false;
1225
+ workflow.setHandler(destroySandboxSignal, () => {
1226
+ destroyRequested = true;
1227
+ });
1228
+ await workflow.condition(() => destroyRequested);
1229
+ await destroySandbox();
1230
+ }
1231
+ return result;
986
1232
  };
987
- Object.defineProperty(workflow, "name", { value: config.name });
988
- return Object.assign(workflow, {
1233
+ Object.defineProperty(workflow$1, "name", { value: config.name });
1234
+ return Object.assign(workflow$1, {
989
1235
  agentName: config.name,
990
1236
  description: config.description,
991
1237
  ...config.resultSchema !== void 0 && {
@@ -1555,7 +1801,11 @@ var FileSystemSkillProvider = class {
1555
1801
  for (const dir of dirs) {
1556
1802
  const raw = await this.fs.readFile(path.join(this.baseDir, dir, "SKILL.md"));
1557
1803
  const { frontmatter } = parseSkillFile(raw);
1558
- skills.push(frontmatter);
1804
+ const location = path.join(this.baseDir, dir);
1805
+ skills.push({
1806
+ ...frontmatter,
1807
+ location
1808
+ });
1559
1809
  }
1560
1810
  return skills;
1561
1811
  }
@@ -1569,7 +1819,15 @@ var FileSystemSkillProvider = class {
1569
1819
  `Skill directory "${name}" contains SKILL.md with mismatched name "${frontmatter.name}"`
1570
1820
  );
1571
1821
  }
1572
- return { ...frontmatter, instructions: body };
1822
+ const location = path.join(this.baseDir, name);
1823
+ const resourcePaths = await this.discoverResources(name);
1824
+ const resourceContents = await this.readResourceContents(location, resourcePaths);
1825
+ return {
1826
+ ...frontmatter,
1827
+ instructions: body,
1828
+ location,
1829
+ ...resourceContents && { resourceContents }
1830
+ };
1573
1831
  }
1574
1832
  /**
1575
1833
  * Convenience method to load all skills with full instructions.
@@ -1581,10 +1839,48 @@ var FileSystemSkillProvider = class {
1581
1839
  for (const dir of dirs) {
1582
1840
  const raw = await this.fs.readFile(path.join(this.baseDir, dir, "SKILL.md"));
1583
1841
  const { frontmatter, body } = parseSkillFile(raw);
1584
- skills.push({ ...frontmatter, instructions: body });
1842
+ const location = path.join(this.baseDir, dir);
1843
+ const resourcePaths = await this.discoverResources(dir);
1844
+ const resourceContents = await this.readResourceContents(location, resourcePaths);
1845
+ skills.push({
1846
+ ...frontmatter,
1847
+ instructions: body,
1848
+ location,
1849
+ ...resourceContents && { resourceContents }
1850
+ });
1585
1851
  }
1586
1852
  return skills;
1587
1853
  }
1854
+ /**
1855
+ * Recursively discovers all non-SKILL.md files inside the skill directory
1856
+ * and returns their paths relative to the skill root.
1857
+ */
1858
+ async discoverResources(skillDir) {
1859
+ const skillRoot = path.join(this.baseDir, skillDir);
1860
+ const resources = [];
1861
+ const walk2 = async (dir, prefix) => {
1862
+ const entries = await this.fs.readdirWithFileTypes(dir);
1863
+ for (const e of entries) {
1864
+ if (e.name.startsWith(".")) continue;
1865
+ const relPath = prefix ? `${prefix}/${e.name}` : e.name;
1866
+ if (e.isDirectory) {
1867
+ await walk2(path.join(dir, e.name), relPath);
1868
+ } else if (e.isFile && e.name !== "SKILL.md") {
1869
+ resources.push(relPath);
1870
+ }
1871
+ }
1872
+ };
1873
+ await walk2(skillRoot, "");
1874
+ return resources;
1875
+ }
1876
+ async readResourceContents(location, resources) {
1877
+ if (resources.length === 0) return void 0;
1878
+ const contents = {};
1879
+ for (const r of resources) {
1880
+ contents[r] = await this.fs.readFile(path.join(location, r));
1881
+ }
1882
+ return contents;
1883
+ }
1588
1884
  async discoverSkillDirs() {
1589
1885
  const entries = await this.fs.readdirWithFileTypes(this.baseDir);
1590
1886
  const dirs = [];
@@ -1717,6 +2013,9 @@ var SandboxManager = class {
1717
2013
  async destroy(id) {
1718
2014
  await this.provider.destroy(id);
1719
2015
  }
2016
+ async pause(id, ttlSeconds) {
2017
+ await this.provider.pause(id, ttlSeconds);
2018
+ }
1720
2019
  async snapshot(id) {
1721
2020
  return this.provider.snapshot(id);
1722
2021
  }
@@ -1757,6 +2056,9 @@ var SandboxManager = class {
1757
2056
  destroySandbox: async (sandboxId) => {
1758
2057
  await this.destroy(sandboxId);
1759
2058
  },
2059
+ pauseSandbox: async (sandboxId, ttlSeconds) => {
2060
+ await this.pause(sandboxId, ttlSeconds);
2061
+ },
1760
2062
  snapshotSandbox: async (sandboxId) => {
1761
2063
  return this.snapshot(sandboxId);
1762
2064
  },
@@ -1770,6 +2072,75 @@ var SandboxManager = class {
1770
2072
  );
1771
2073
  }
1772
2074
  };
2075
+ var NodeFsSandboxFileSystem = class {
2076
+ workspaceBase;
2077
+ constructor(workspaceBase) {
2078
+ this.workspaceBase = workspaceBase;
2079
+ }
2080
+ abs(path$1) {
2081
+ return path.resolve(this.workspaceBase, path$1);
2082
+ }
2083
+ async readFile(path) {
2084
+ return fs.promises.readFile(this.abs(path), "utf-8");
2085
+ }
2086
+ async readFileBuffer(path) {
2087
+ return fs.promises.readFile(this.abs(path));
2088
+ }
2089
+ async writeFile(path, content) {
2090
+ await fs.promises.writeFile(this.abs(path), content);
2091
+ }
2092
+ async appendFile(path, content) {
2093
+ await fs.promises.appendFile(this.abs(path), content);
2094
+ }
2095
+ async exists(path) {
2096
+ try {
2097
+ await fs.promises.access(this.abs(path));
2098
+ return true;
2099
+ } catch {
2100
+ return false;
2101
+ }
2102
+ }
2103
+ async stat(path) {
2104
+ const s = await fs.promises.stat(this.abs(path));
2105
+ return {
2106
+ isFile: s.isFile(),
2107
+ isDirectory: s.isDirectory(),
2108
+ isSymbolicLink: s.isSymbolicLink(),
2109
+ size: s.size,
2110
+ mtime: s.mtime
2111
+ };
2112
+ }
2113
+ async mkdir(path, options) {
2114
+ await fs.promises.mkdir(this.abs(path), options);
2115
+ }
2116
+ async readdir(path) {
2117
+ return fs.promises.readdir(this.abs(path));
2118
+ }
2119
+ async readdirWithFileTypes(path) {
2120
+ const entries = await fs.promises.readdir(this.abs(path), { withFileTypes: true });
2121
+ return entries.map((e) => ({
2122
+ name: e.name,
2123
+ isFile: e.isFile(),
2124
+ isDirectory: e.isDirectory(),
2125
+ isSymbolicLink: e.isSymbolicLink()
2126
+ }));
2127
+ }
2128
+ async rm(path, options) {
2129
+ await fs.promises.rm(this.abs(path), options);
2130
+ }
2131
+ async cp(src, dest, options) {
2132
+ await fs.promises.cp(this.abs(src), this.abs(dest), options);
2133
+ }
2134
+ async mv(src, dest) {
2135
+ await fs.promises.rename(this.abs(src), this.abs(dest));
2136
+ }
2137
+ async readlink(path) {
2138
+ return fs.promises.readlink(this.abs(path));
2139
+ }
2140
+ resolvePath(base, path$1) {
2141
+ return path.posix.resolve(base, path$1);
2142
+ }
2143
+ };
1773
2144
 
1774
2145
  // src/tools/bash/handler.ts
1775
2146
  var bashHandler = async (args, { sandbox }) => {
@@ -2026,6 +2397,7 @@ var toTree = async (fs, opts = {}) => {
2026
2397
  };
2027
2398
 
2028
2399
  exports.FileSystemSkillProvider = FileSystemSkillProvider;
2400
+ exports.NodeFsSandboxFileSystem = NodeFsSandboxFileSystem;
2029
2401
  exports.SandboxManager = SandboxManager;
2030
2402
  exports.SandboxNotFoundError = SandboxNotFoundError;
2031
2403
  exports.SandboxNotSupportedError = SandboxNotSupportedError;