zeitlich 0.2.13 → 0.2.14

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 (135) hide show
  1. package/README.md +49 -38
  2. package/dist/adapters/sandbox/daytona/index.cjs +205 -0
  3. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -0
  4. package/dist/adapters/sandbox/daytona/index.d.cts +86 -0
  5. package/dist/adapters/sandbox/daytona/index.d.ts +86 -0
  6. package/dist/adapters/sandbox/daytona/index.js +202 -0
  7. package/dist/adapters/sandbox/daytona/index.js.map +1 -0
  8. package/dist/adapters/sandbox/inmemory/index.cjs +174 -0
  9. package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -0
  10. package/dist/adapters/sandbox/inmemory/index.d.cts +28 -0
  11. package/dist/adapters/sandbox/inmemory/index.d.ts +28 -0
  12. package/dist/adapters/sandbox/inmemory/index.js +172 -0
  13. package/dist/adapters/sandbox/inmemory/index.js.map +1 -0
  14. package/dist/adapters/sandbox/virtual/index.cjs +405 -0
  15. package/dist/adapters/sandbox/virtual/index.cjs.map +1 -0
  16. package/dist/adapters/sandbox/virtual/index.d.cts +85 -0
  17. package/dist/adapters/sandbox/virtual/index.d.ts +85 -0
  18. package/dist/adapters/sandbox/virtual/index.js +400 -0
  19. package/dist/adapters/sandbox/virtual/index.js.map +1 -0
  20. package/dist/adapters/thread/google-genai/index.cjs +284 -0
  21. package/dist/adapters/thread/google-genai/index.cjs.map +1 -0
  22. package/dist/adapters/thread/google-genai/index.d.cts +145 -0
  23. package/dist/adapters/thread/google-genai/index.d.ts +145 -0
  24. package/dist/adapters/thread/google-genai/index.js +278 -0
  25. package/dist/adapters/thread/google-genai/index.js.map +1 -0
  26. package/dist/adapters/{langchain → thread/langchain}/index.cjs +7 -9
  27. package/dist/adapters/thread/langchain/index.cjs.map +1 -0
  28. package/dist/adapters/{langchain → thread/langchain}/index.d.cts +17 -21
  29. package/dist/adapters/{langchain → thread/langchain}/index.d.ts +17 -21
  30. package/dist/adapters/{langchain → thread/langchain}/index.js +7 -9
  31. package/dist/adapters/thread/langchain/index.js.map +1 -0
  32. package/dist/index.cjs +816 -545
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.d.cts +235 -74
  35. package/dist/index.d.ts +235 -74
  36. package/dist/index.js +804 -540
  37. package/dist/index.js.map +1 -1
  38. package/dist/types-B4C9txdq.d.ts +389 -0
  39. package/dist/{thread-manager-qc0g5Rvd.d.cts → types-B9ljZewB.d.cts} +1 -6
  40. package/dist/{thread-manager-qc0g5Rvd.d.ts → types-B9ljZewB.d.ts} +1 -6
  41. package/dist/types-BMXzv7TN.d.cts +476 -0
  42. package/dist/types-BMXzv7TN.d.ts +476 -0
  43. package/dist/types-BVP87m_W.d.cts +121 -0
  44. package/dist/types-CDubRtad.d.cts +115 -0
  45. package/dist/types-CDubRtad.d.ts +115 -0
  46. package/dist/types-CwwgQ_9H.d.ts +121 -0
  47. package/dist/types-GpMU4b0w.d.cts +389 -0
  48. package/dist/workflow.cjs +444 -318
  49. package/dist/workflow.cjs.map +1 -1
  50. package/dist/workflow.d.cts +271 -222
  51. package/dist/workflow.d.ts +271 -222
  52. package/dist/workflow.js +440 -316
  53. package/dist/workflow.js.map +1 -1
  54. package/package.json +59 -6
  55. package/src/adapters/sandbox/daytona/filesystem.ts +136 -0
  56. package/src/adapters/sandbox/daytona/index.ts +149 -0
  57. package/src/adapters/sandbox/daytona/types.ts +34 -0
  58. package/src/adapters/sandbox/inmemory/index.ts +213 -0
  59. package/src/adapters/sandbox/virtual/filesystem.ts +345 -0
  60. package/src/adapters/sandbox/virtual/index.ts +88 -0
  61. package/src/adapters/sandbox/virtual/mutations.ts +38 -0
  62. package/src/adapters/sandbox/virtual/provider.ts +101 -0
  63. package/src/adapters/sandbox/virtual/tree.ts +82 -0
  64. package/src/adapters/sandbox/virtual/types.ts +127 -0
  65. package/src/adapters/sandbox/virtual/virtual-sandbox.test.ts +523 -0
  66. package/src/adapters/sandbox/virtual/with-virtual-sandbox.ts +91 -0
  67. package/src/adapters/thread/google-genai/activities.ts +121 -0
  68. package/src/adapters/thread/google-genai/index.ts +41 -0
  69. package/src/adapters/thread/google-genai/model-invoker.ts +154 -0
  70. package/src/adapters/thread/google-genai/thread-manager.ts +169 -0
  71. package/src/adapters/{langchain → thread/langchain}/activities.ts +11 -15
  72. package/src/adapters/{langchain → thread/langchain}/index.ts +1 -1
  73. package/src/adapters/{langchain → thread/langchain}/model-invoker.ts +15 -18
  74. package/src/adapters/{langchain → thread/langchain}/thread-manager.ts +1 -1
  75. package/src/index.ts +32 -24
  76. package/src/lib/activity.ts +87 -0
  77. package/src/lib/hooks/index.ts +11 -0
  78. package/src/lib/hooks/types.ts +98 -0
  79. package/src/lib/model/helpers.ts +6 -0
  80. package/src/lib/model/index.ts +13 -0
  81. package/src/lib/{model-invoker.ts → model/types.ts} +18 -1
  82. package/src/lib/sandbox/index.ts +19 -0
  83. package/src/lib/sandbox/manager.ts +76 -0
  84. package/src/lib/sandbox/sandbox.test.ts +158 -0
  85. package/src/lib/{fs.ts → sandbox/tree.ts} +6 -6
  86. package/src/lib/sandbox/types.ts +164 -0
  87. package/src/lib/session/index.ts +11 -0
  88. package/src/lib/{session.ts → session/session.ts} +76 -48
  89. package/src/lib/session/types.ts +93 -0
  90. package/src/lib/skills/fs-provider.ts +16 -15
  91. package/src/lib/skills/handler.ts +31 -0
  92. package/src/lib/skills/index.ts +5 -1
  93. package/src/lib/skills/register.ts +20 -0
  94. package/src/lib/skills/tool.ts +47 -0
  95. package/src/lib/state/index.ts +9 -0
  96. package/src/lib/{state-manager.ts → state/manager.ts} +10 -147
  97. package/src/lib/state/types.ts +134 -0
  98. package/src/lib/subagent/define.ts +71 -0
  99. package/src/lib/subagent/handler.ts +99 -0
  100. package/src/lib/subagent/index.ts +13 -0
  101. package/src/lib/subagent/register.ts +53 -0
  102. package/src/lib/subagent/tool.ts +80 -0
  103. package/src/lib/subagent/types.ts +92 -0
  104. package/src/lib/thread/index.ts +7 -0
  105. package/src/lib/{thread-manager.ts → thread/manager.ts} +1 -33
  106. package/src/lib/thread/types.ts +33 -0
  107. package/src/lib/tool-router/auto-append.ts +55 -0
  108. package/src/lib/tool-router/index.ts +41 -0
  109. package/src/lib/tool-router/router.ts +462 -0
  110. package/src/lib/tool-router/types.ts +478 -0
  111. package/src/lib/tool-router/with-sandbox.ts +70 -0
  112. package/src/lib/types.ts +5 -382
  113. package/src/tools/bash/bash.test.ts +53 -55
  114. package/src/tools/bash/handler.ts +23 -51
  115. package/src/tools/edit/handler.ts +67 -81
  116. package/src/tools/glob/handler.ts +60 -17
  117. package/src/tools/read-file/handler.ts +67 -0
  118. package/src/tools/read-skill/handler.ts +1 -31
  119. package/src/tools/read-skill/tool.ts +5 -47
  120. package/src/tools/subagent/handler.ts +1 -100
  121. package/src/tools/subagent/tool.ts +5 -93
  122. package/src/tools/task-create/handler.ts +1 -1
  123. package/src/tools/task-get/handler.ts +1 -1
  124. package/src/tools/task-list/handler.ts +1 -1
  125. package/src/tools/task-update/handler.ts +1 -1
  126. package/src/tools/write-file/handler.ts +47 -0
  127. package/src/workflow.ts +88 -47
  128. package/tsup.config.ts +8 -1
  129. package/dist/adapters/langchain/index.cjs.map +0 -1
  130. package/dist/adapters/langchain/index.js.map +0 -1
  131. package/dist/model-invoker-y_zlyMqu.d.cts +0 -892
  132. package/dist/model-invoker-y_zlyMqu.d.ts +0 -892
  133. package/src/lib/tool-router.ts +0 -977
  134. package/src/lib/workflow-helpers.ts +0 -50
  135. /package/src/lib/{thread-id.ts → thread/id.ts} +0 -0
package/src/lib/types.ts CHANGED
@@ -1,16 +1,3 @@
1
- import type {
2
- InferToolResults,
3
- ParsedToolCallUnion,
4
- RawToolCall,
5
- ToolCallResultUnion,
6
- ToolHandlerResponse,
7
- ToolMap,
8
- } from "./tool-router";
9
- import type { Skill } from "./skills/types";
10
-
11
- import type { Duration } from "@temporalio/common";
12
- import type { z } from "zod";
13
-
14
1
  // ============================================================================
15
2
  // Framework-agnostic message types
16
3
  // ============================================================================
@@ -24,6 +11,10 @@ export type MessageContent = string | ContentPart[];
24
11
  /** Content returned by a tool handler */
25
12
  export type ToolMessageContent = MessageContent;
26
13
 
14
+ // ============================================================================
15
+ // Agent core types
16
+ // ============================================================================
17
+
27
18
  /**
28
19
  * Agent execution status
29
20
  */
@@ -74,33 +65,6 @@ export interface TokenUsage {
74
65
  reasonTokens?: number;
75
66
  }
76
67
 
77
- /**
78
- * Agent response from LLM invocation
79
- */
80
- export interface AgentResponse<M = unknown> {
81
- message: M;
82
- rawToolCalls: RawToolCall[];
83
- usage?: TokenUsage;
84
- }
85
-
86
- /**
87
- * Thread operations required by a session.
88
- * Consumers provide these — typically by wrapping Temporal activities.
89
- */
90
- export interface ThreadOps {
91
- /** Initialize an empty thread */
92
- initializeThread(threadId: string): Promise<void>;
93
- /** Append a human message to the thread */
94
- appendHumanMessage(
95
- threadId: string,
96
- content: string | MessageContent
97
- ): Promise<void>;
98
- /** Append a tool result to the thread */
99
- appendToolResult(config: ToolResultConfig): Promise<void>;
100
- /** Append a system message to the thread */
101
- appendSystemMessage(threadId: string, content: string): Promise<void>;
102
- }
103
-
104
68
  /**
105
69
  * Configuration for a Zeitlich agent
106
70
  */
@@ -111,43 +75,6 @@ export interface AgentConfig {
111
75
  description?: string;
112
76
  }
113
77
 
114
- /**
115
- * Configuration for a Zeitlich agent session
116
- */
117
- export interface SessionConfig<T extends ToolMap, M = unknown> {
118
- /** The thread ID to use for the session (defaults to a short generated ID) */
119
- threadId?: string;
120
- /** Metadata for the session */
121
- metadata?: Record<string, unknown>;
122
- /** Whether to append the system prompt as message to the thread */
123
- appendSystemPrompt?: boolean;
124
- /** How many turns to run the session for */
125
- maxTurns?: number;
126
- /** Workflow-specific runAgent activity (with tools pre-bound) */
127
- runAgent: RunAgentActivity<M>;
128
- /** Thread operations (initialize, append messages, parse tool calls) */
129
- threadOps?: ThreadOps;
130
- /** Tool router for processing tool calls (optional if agent has no tools) */
131
- tools?: T;
132
- /** Subagent configurations */
133
- subagents?: SubagentConfig[];
134
- /** Skills available to this agent (metadata + instructions, loaded activity-side) */
135
- skills?: Skill[];
136
- /** Session lifecycle hooks */
137
- hooks?: Hooks<T, ToolCallResultUnion<InferToolResults<T>>>;
138
- /** Whether to process tools in parallel */
139
- processToolsInParallel?: boolean;
140
- /**
141
- * Build context message content from agent-specific context.
142
- * Returns MessageContent array for the initial HumanMessage.
143
- */
144
- buildContextMessage: () => MessageContent | Promise<MessageContent>;
145
- /** When true, skip thread initialization and system prompt — append only the new human message to the existing thread. */
146
- continueThread?: boolean;
147
- /** How long to wait for input before cancelling the workflow */
148
- waitForInputTimeout?: Duration;
149
- }
150
-
151
78
  /**
152
79
  * A JSON-serializable tool definition for state storage.
153
80
  * Uses a plain JSON Schema object instead of a live Zod instance,
@@ -171,13 +98,6 @@ export interface RunAgentConfig extends AgentConfig {
171
98
  metadata?: Record<string, unknown>;
172
99
  }
173
100
 
174
- /**
175
- * Type signature for workflow-specific runAgent activity
176
- */
177
- export type RunAgentActivity<M = unknown> = (
178
- config: RunAgentConfig
179
- ) => Promise<AgentResponse<M>>;
180
-
181
101
  /**
182
102
  * Configuration for appending a tool result
183
103
  */
@@ -190,84 +110,6 @@ export interface ToolResultConfig {
190
110
  content: ToolMessageContent;
191
111
  }
192
112
 
193
- // ============================================================================
194
- // Subagent Configuration
195
- // ============================================================================
196
-
197
- export type SubagentWorkflow<TResult extends z.ZodType = z.ZodType> = (
198
- input: SubagentInput
199
- ) => Promise<ToolHandlerResponse<z.infer<TResult> | null>>;
200
-
201
- /** Infer the z.infer'd result type from a SubagentConfig, or null if no schema */
202
- export type InferSubagentResult<T extends SubagentConfig> =
203
- T extends SubagentConfig<infer S> ? z.infer<S> : null;
204
-
205
- /**
206
- * Configuration for a subagent that can be spawned by the parent workflow.
207
- *
208
- * @template TResult - Zod schema type for validating the child workflow's result
209
- */
210
- export interface SubagentConfig<TResult extends z.ZodType = z.ZodType> {
211
- /** Identifier used in Task tool's subagent parameter */
212
- agentName: string;
213
- /** Description shown to the parent agent explaining what this subagent does */
214
- description: string;
215
- /** Whether this subagent is available (default: true). Disabled subagents are excluded from the Subagent tool. */
216
- enabled?: boolean;
217
- /** Temporal workflow function or type name (used with executeChild) */
218
- workflow: string | SubagentWorkflow<TResult>;
219
- /** Optional task queue - defaults to parent's queue if not specified */
220
- taskQueue?: string;
221
- /** Optional Zod schema to validate the child workflow's result. If omitted, result is passed through as-is. */
222
- resultSchema?: TResult;
223
- /** Optional static context passed to the subagent on every invocation */
224
- context?: Record<string, unknown>;
225
- /** Allow the parent agent to pass a threadId for this subagent to continue (default: false) */
226
- allowThreadContinuation?: boolean;
227
- /** Per-subagent lifecycle hooks */
228
- hooks?: SubagentHooks;
229
- }
230
-
231
- /**
232
- * Per-subagent lifecycle hooks - defined on a SubagentConfig.
233
- * Runs in addition to global hooks (global pre → subagent pre → execute → subagent post → global post).
234
- */
235
- export interface SubagentHooks<TArgs = unknown, TResult = unknown> {
236
- /** Called before this subagent executes - can skip or modify args */
237
- onPreExecution?: (ctx: {
238
- args: TArgs;
239
- threadId: string;
240
- turn: number;
241
- }) => PreToolUseHookResult | Promise<PreToolUseHookResult>;
242
- /** Called after this subagent executes successfully */
243
- onPostExecution?: (ctx: {
244
- args: TArgs;
245
- result: TResult;
246
- threadId: string;
247
- turn: number;
248
- durationMs: number;
249
- }) => void | Promise<void>;
250
- /** Called when this subagent execution fails */
251
- onExecutionFailure?: (ctx: {
252
- args: TArgs;
253
- error: Error;
254
- threadId: string;
255
- turn: number;
256
- }) => PostToolUseFailureHookResult | Promise<PostToolUseFailureHookResult>;
257
- }
258
-
259
- /**
260
- * Input passed to child workflows when spawned as subagents
261
- */
262
- export interface SubagentInput {
263
- /** The prompt/task from the parent agent */
264
- prompt: string;
265
- /** Optional context parameters passed from the parent agent */
266
- context?: Record<string, unknown>;
267
- /** When set, the subagent should continue this thread instead of starting a new one */
268
- threadId?: string;
269
- }
270
-
271
113
  // ============================================================================
272
114
  // Workflow Tasks
273
115
  // ============================================================================
@@ -300,7 +142,7 @@ export interface WorkflowTask {
300
142
  }
301
143
 
302
144
  // ============================================================================
303
- // Session Lifecycle Hooks
145
+ // Session exit
304
146
  // ============================================================================
305
147
 
306
148
  /**
@@ -313,225 +155,6 @@ export type SessionExitReason =
313
155
  | "failed"
314
156
  | "cancelled";
315
157
 
316
- /**
317
- * Context for PreToolUse hook - called before tool execution
318
- */
319
- export interface PreToolUseHookContext<T extends ToolMap> {
320
- /** The tool call about to be executed */
321
- toolCall: ParsedToolCallUnion<T>;
322
- /** Thread identifier */
323
- threadId: string;
324
- /** Current turn number */
325
- turn: number;
326
- }
327
-
328
- /**
329
- * Result from PreToolUse hook - can block or modify execution
330
- */
331
- export interface PreToolUseHookResult {
332
- /** Skip this tool call entirely */
333
- skip?: boolean;
334
- /** Modified args to use instead (must match schema) */
335
- modifiedArgs?: unknown;
336
- }
337
-
338
- /**
339
- * PreToolUse hook - called before tool execution, can block or modify
340
- */
341
- export type PreToolUseHook<T extends ToolMap> = (
342
- ctx: PreToolUseHookContext<T>
343
- ) => PreToolUseHookResult | Promise<PreToolUseHookResult>;
344
-
345
- /**
346
- * Context for PostToolUse hook - called after successful tool execution
347
- */
348
- export interface PostToolUseHookContext<T extends ToolMap, TResult = unknown> {
349
- /** The tool call that was executed */
350
- toolCall: ParsedToolCallUnion<T>;
351
- /** The result from the tool handler */
352
- result: TResult;
353
- /** Thread identifier */
354
- threadId: string;
355
- /** Current turn number */
356
- turn: number;
357
- /** Execution duration in milliseconds */
358
- durationMs: number;
359
- }
360
-
361
- /**
362
- * PostToolUse hook - called after successful tool execution
363
- */
364
- export type PostToolUseHook<T extends ToolMap, TResult = unknown> = (
365
- ctx: PostToolUseHookContext<T, TResult>
366
- ) => void | Promise<void>;
367
-
368
- /**
369
- * Context for PostToolUseFailure hook - called when tool execution fails
370
- */
371
- export interface PostToolUseFailureHookContext<T extends ToolMap> {
372
- /** The tool call that failed */
373
- toolCall: ParsedToolCallUnion<T>;
374
- /** The error that occurred */
375
- error: Error;
376
- /** Thread identifier */
377
- threadId: string;
378
- /** Current turn number */
379
- turn: number;
380
- }
381
-
382
- /**
383
- * Result from PostToolUseFailure hook - can recover from errors
384
- */
385
- export interface PostToolUseFailureHookResult {
386
- /** Provide a fallback result instead of throwing */
387
- fallbackContent?: ToolMessageContent;
388
- /** Whether to suppress the error (still logs, but continues) */
389
- suppress?: boolean;
390
- }
391
-
392
- /**
393
- * PostToolUseFailure hook - called when tool execution fails
394
- */
395
- export type PostToolUseFailureHook<T extends ToolMap> = (
396
- ctx: PostToolUseFailureHookContext<T>
397
- ) => PostToolUseFailureHookResult | Promise<PostToolUseFailureHookResult>;
398
-
399
- /**
400
- * Context for SessionStart hook - called when session begins
401
- */
402
- export interface SessionStartHookContext {
403
- /** Thread identifier */
404
- threadId: string;
405
- /** Name of the agent */
406
- agentName: string;
407
- /** Session metadata */
408
- metadata: Record<string, unknown>;
409
- }
410
-
411
- /**
412
- * SessionStart hook - called when session begins
413
- */
414
- export type SessionStartHook = (
415
- ctx: SessionStartHookContext
416
- ) => void | Promise<void>;
417
-
418
- /**
419
- * Context for PreHumanMessageAppend hook - called before each human message is appended to the thread
420
- */
421
- export interface PreHumanMessageAppendHookContext {
422
- /** The message about to be appended */
423
- message: MessageContent;
424
- /** Thread identifier */
425
- threadId: string;
426
- }
427
-
428
- /**
429
- * PreHumanMessageAppend hook - called before each human message is appended to the thread
430
- */
431
- export type PreHumanMessageAppendHook = (
432
- ctx: PreHumanMessageAppendHookContext
433
- ) => void | Promise<void>;
434
-
435
- /**
436
- * PostHumanMessageAppend hook - called after each human message is appended to the thread
437
- */
438
- export type PostHumanMessageAppendHook = (
439
- ctx: PostHumanMessageAppendHookContext
440
- ) => void | Promise<void>;
441
-
442
- /**
443
- * Context for PostHumanMessageAppend hook - called after each human message is appended to the thread
444
- */
445
- export interface PostHumanMessageAppendHookContext {
446
- /** The message that was appended */
447
- message: MessageContent;
448
- /** Thread identifier */
449
- threadId: string;
450
- }
451
-
452
- /**
453
- * Context for SessionEnd hook - called when session ends
454
- */
455
- export interface SessionEndHookContext {
456
- /** Thread identifier */
457
- threadId: string;
458
- /** Name of the agent */
459
- agentName: string;
460
- /** Reason the session ended */
461
- exitReason: SessionExitReason;
462
- /** Total turns executed */
463
- turns: number;
464
- /** Session metadata */
465
- metadata: Record<string, unknown>;
466
- }
467
-
468
- /**
469
- * SessionEnd hook - called when session ends
470
- */
471
- export type SessionEndHook = (
472
- ctx: SessionEndHookContext
473
- ) => void | Promise<void>;
474
-
475
- /**
476
- * Per-tool lifecycle hooks - defined directly on a tool definition.
477
- * Runs in addition to global hooks (global pre → tool pre → execute → tool post → global post).
478
- */
479
- export interface ToolHooks<TArgs = unknown, TResult = unknown> {
480
- /** Called before this tool executes - can skip or modify args */
481
- onPreToolUse?: (ctx: {
482
- args: TArgs;
483
- threadId: string;
484
- turn: number;
485
- }) => PreToolUseHookResult | Promise<PreToolUseHookResult>;
486
- /** Called after this tool executes successfully */
487
- onPostToolUse?: (ctx: {
488
- args: TArgs;
489
- result: TResult;
490
- threadId: string;
491
- turn: number;
492
- durationMs: number;
493
- }) => void | Promise<void>;
494
- /** Called when this tool execution fails */
495
- onPostToolUseFailure?: (ctx: {
496
- args: TArgs;
497
- error: Error;
498
- threadId: string;
499
- turn: number;
500
- }) => PostToolUseFailureHookResult | Promise<PostToolUseFailureHookResult>;
501
- }
502
-
503
- /**
504
- * Combined hooks interface for session lifecycle
505
- */
506
- export interface Hooks<T extends ToolMap, TResult = unknown> {
507
- /** Called before each human message is appended to the thread */
508
- onPreHumanMessageAppend?: PreHumanMessageAppendHook;
509
- /** Called after each human message is appended to the thread */
510
- onPostHumanMessageAppend?: PostHumanMessageAppendHook;
511
- /** Called before each tool execution - can block or modify */
512
- onPreToolUse?: PreToolUseHook<T>;
513
- /** Called after each successful tool execution */
514
- onPostToolUse?: PostToolUseHook<T, TResult>;
515
- /** Called when tool execution fails */
516
- onPostToolUseFailure?: PostToolUseFailureHook<T>;
517
- /** Called when session starts */
518
- onSessionStart?: SessionStartHook;
519
- /** Called when session ends */
520
- onSessionEnd?: SessionEndHook;
521
- }
522
-
523
- // ============================================================================
524
- // Agent Query/Update Name Helpers
525
- // ============================================================================
526
-
527
- /** Derives the query name for an agent's state (usable in both workflow and activity code) */
528
- export const agentQueryName = (agentName: string) =>
529
- `get${agentName}State` as const;
530
-
531
- /** Derives the update name for waiting on an agent's state change */
532
- export const agentStateChangeUpdateName = (agentName: string) =>
533
- `waitFor${agentName}StateChange` as const;
534
-
535
158
  /**
536
159
  * Helper to check if status is terminal
537
160
  */
@@ -1,18 +1,35 @@
1
- import { dirname } from "path";
2
- import { fileURLToPath } from "url";
3
- import { describe, expect, it } from "vitest";
4
- import { createBashHandler } from "./handler";
5
- import { OverlayFs } from "just-bash";
1
+ import { describe, expect, it, beforeEach } from "vitest";
2
+ import { bashHandler } from "./handler";
3
+ import { withSandbox } from "../../lib/tool-router/with-sandbox";
4
+ import { SandboxManager } from "../../lib/sandbox/manager";
5
+ import { InMemorySandboxProvider } from "../../adapters/sandbox/inmemory/index";
6
+ import type { RouterContext } from "../../lib/tool-router/types";
6
7
 
7
- const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ describe("bash handler with sandbox", () => {
9
+ let manager: SandboxManager;
10
+ let sandboxId: string;
11
+ let handler: ReturnType<typeof withSandbox<Parameters<typeof bashHandler>[0], Awaited<ReturnType<typeof bashHandler>>["data"]>>;
8
12
 
9
- describe("bash with default options", () => {
10
- const fs = new OverlayFs({ root: __dirname, mountPoint: "/home/user" });
13
+ beforeEach(async () => {
14
+ manager = new SandboxManager(new InMemorySandboxProvider());
15
+ const result = await manager.create({
16
+ initialFiles: { "/home/user/hello.txt": "world" },
17
+ });
18
+ sandboxId = result.sandboxId;
19
+ handler = withSandbox(manager, bashHandler);
20
+ });
21
+
22
+ const ctx = (id: string): RouterContext => ({
23
+ sandboxId: id,
24
+ threadId: "test-thread",
25
+ toolCallId: "test-call",
26
+ toolName: "Bash",
27
+ });
11
28
 
12
29
  it("executes echo and captures stdout", async () => {
13
- const { data } = await createBashHandler({fs})(
30
+ const { data } = await handler(
14
31
  { command: "echo 'hello world'" },
15
- {}
32
+ ctx(sandboxId)
16
33
  );
17
34
  expect(data).not.toBeNull();
18
35
  expect(data?.stdout.trim()).toBe("hello world");
@@ -20,85 +37,66 @@ describe("bash with default options", () => {
20
37
  });
21
38
 
22
39
  it("returns exit code 0 for successful commands", async () => {
23
- const { data } = await createBashHandler({fs})({ command: "true" }, {});
40
+ const { data } = await handler({ command: "true" }, ctx(sandboxId));
24
41
  expect(data?.exitCode).toBe(0);
25
42
  });
26
43
 
27
44
  it("returns non-zero exit code for failed commands", async () => {
28
- const { data } = await createBashHandler({fs})({ command: "false" }, {});
45
+ const { data } = await handler({ command: "false" }, ctx(sandboxId));
29
46
  expect(data?.exitCode).toBe(1);
30
47
  });
31
48
 
32
49
  it("captures stderr output", async () => {
33
- const { data } = await createBashHandler({fs})(
50
+ const { data } = await handler(
34
51
  { command: "echo 'error message' >&2" },
35
- {}
52
+ ctx(sandboxId)
36
53
  );
37
54
  expect(data?.stderr.trim()).toBe("error message");
38
55
  expect(data?.stdout.trim()).toBe("");
39
56
  });
40
57
 
41
58
  it("supports piping between commands", async () => {
42
- const { data } = await createBashHandler({fs})(
59
+ const { data } = await handler(
43
60
  { command: "echo 'hello world' | tr 'a-z' 'A-Z'" },
44
- {}
61
+ ctx(sandboxId)
45
62
  );
46
63
  expect(data?.stdout.trim()).toBe("HELLO WORLD");
47
64
  });
48
65
 
49
66
  it("supports command chaining with &&", async () => {
50
- const { data } = await createBashHandler({fs})(
67
+ const { data } = await handler(
51
68
  { command: "echo 'first' && echo 'second'" },
52
- {}
69
+ ctx(sandboxId)
53
70
  );
54
71
  expect(data?.stdout).toContain("first");
55
72
  expect(data?.stdout).toContain("second");
56
73
  });
57
74
 
58
- it("handles multi-line output", async () => {
59
- const { data } = await createBashHandler({fs})(
60
- { command: "printf 'line1\\nline2\\nline3'" },
61
- {}
62
- );
63
- const lines = data?.stdout.split("\n");
64
- expect(lines).toHaveLength(3);
65
- expect(lines?.[0]).toBe("line1");
66
- expect(lines?.[2]).toBe("line3");
67
- });
68
-
69
- it("handles commands with arguments and flags", async () => {
70
- const { data } = await createBashHandler({fs})(
71
- { command: "echo -n 'no newline'" },
72
- {}
73
- );
74
- expect(data?.stdout).toBe("no newline");
75
- });
76
-
77
- it("supports command substitution", async () => {
78
- const { data } = await createBashHandler({fs})(
79
- { command: "echo \"count: $(echo 'a b c' | wc -w | tr -d ' ')\"" },
80
- {}
81
- );
82
- expect(data?.stdout.trim()).toBe("count: 3");
83
- });
84
-
85
75
  it("returns toolResponse string with formatted output", async () => {
86
- const { toolResponse } = await createBashHandler({fs})(
76
+ const { toolResponse } = await handler(
87
77
  { command: "echo 'test'" },
88
- {}
78
+ ctx(sandboxId)
89
79
  );
90
80
  expect(toolResponse).toContain("Exit code: 0");
91
81
  expect(toolResponse).toContain("stdout:");
92
82
  expect(toolResponse).toContain("test");
93
83
  });
94
- });
95
84
 
96
- describe("bash with overlay filesystem", () => {
97
- it("sees files in the current directory", async () => {
98
- const fs = new OverlayFs({ root: __dirname, mountPoint: "/home/user" });
99
- const { data } = await createBashHandler({fs})({ command: "ls" }, {});
100
- expect(data?.stdout).toContain("bash.test.ts");
101
- expect(data?.stdout).toContain("handler.ts");
102
- expect(data?.stdout).toContain("tool.ts");
85
+ it("returns error when no sandboxId in context", async () => {
86
+ const { toolResponse, data } = await handler({ command: "echo hi" }, {
87
+ threadId: "test-thread",
88
+ toolCallId: "test-call",
89
+ toolName: "Bash",
90
+ });
91
+ expect(toolResponse).toContain("No sandbox configured");
92
+ expect(data).toBeNull();
93
+ });
94
+
95
+ it("can read files from the sandbox filesystem", async () => {
96
+ const { data } = await handler(
97
+ { command: "cat /home/user/hello.txt" },
98
+ ctx(sandboxId)
99
+ );
100
+ expect(data?.stdout).toBe("world");
103
101
  });
104
102
  });
@@ -1,58 +1,30 @@
1
1
  import type { ActivityToolHandler } from "../../lib/tool-router";
2
+ import type { SandboxContext } from "../../lib/tool-router/with-sandbox";
2
3
  import type { BashArgs } from "./tool";
3
- import { Bash, type BashOptions } from "just-bash";
4
-
5
- type BashExecOut = {
6
- exitCode: number;
7
- stderr: string;
8
- stdout: string;
9
- };
10
-
11
- /** BashOptions with `fs` required */
12
- type BashToolOptions = Required<Pick<BashOptions, "fs">> & Omit<BashOptions, "fs">;
4
+ import type { ExecResult } from "../../lib/sandbox/types";
13
5
 
14
6
  /**
15
- * Creates a Bash tool handler that executes shell commands in a sandboxed environment.
7
+ * Bash tool handler executes shell commands inside a {@link Sandbox}.
16
8
  *
17
- * @param bashOptions - Options including a required `fs` (file system implementation from `just-bash`)
18
- * @returns Activity tool handler for Bash tool calls
19
- *
20
- * @example
21
- * ```typescript
22
- * import { createBashHandler } from 'zeitlich';
23
- *
24
- * const bashHandlerActivity = createBashHandler({ fs: inMemoryFileSystem });
25
- * ```
9
+ * Wrap with {@link withSandbox} at activity registration time to inject the
10
+ * sandbox automatically.
26
11
  */
27
- export const createBashHandler: (
28
- bashOptions: BashToolOptions,
29
- ) => ActivityToolHandler<BashArgs, BashExecOut | null> =
30
- (bashOptions: BashToolOptions) => async (args: BashArgs, _context) => {
31
- const { command } = args;
32
-
33
- const mergedOptions: BashOptions = {
34
- ...bashOptions,
35
- executionLimits: {
36
- maxStringLength: 52428800, // 50MB default
37
- ...bashOptions.executionLimits,
38
- },
12
+ export const bashHandler: ActivityToolHandler<
13
+ BashArgs,
14
+ ExecResult | null,
15
+ SandboxContext
16
+ > = async (args, { sandbox }) => {
17
+ try {
18
+ const result = await sandbox.exec(args.command);
19
+ return {
20
+ toolResponse: `Exit code: ${result.exitCode}\n\nstdout:\n${result.stdout}\n\nstderr:\n${result.stderr}`,
21
+ data: result,
39
22
  };
40
-
41
- const bash = new Bash(mergedOptions);
42
-
43
- try {
44
- const { exitCode, stderr, stdout } = await bash.exec(command);
45
- const bashExecOut = { exitCode, stderr, stdout };
46
-
47
- return {
48
- toolResponse: `Exit code: ${exitCode}\n\nstdout:\n${stdout}\n\nstderr:\n${stderr}`,
49
- data: bashExecOut,
50
- };
51
- } catch (error) {
52
- const err = error instanceof Error ? error : new Error("Unknown error");
53
- return {
54
- toolResponse: `Error executing bash command: ${err.message}`,
55
- data: null,
56
- };
57
- }
58
- };
23
+ } catch (error) {
24
+ const err = error instanceof Error ? error : new Error("Unknown error");
25
+ return {
26
+ toolResponse: `Error executing bash command: ${err.message}`,
27
+ data: null,
28
+ };
29
+ }
30
+ };