zeitlich 0.2.0 → 0.2.2

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.
@@ -10,95 +10,95 @@ describe("bash with default options", () => {
10
10
  const fs = new OverlayFs({ root: __dirname, mountPoint: "/home/user" });
11
11
 
12
12
  it("executes echo and captures stdout", async () => {
13
- const { result } = await handleBashTool(fs)(
13
+ const { data } = await handleBashTool({fs})(
14
14
  { command: "echo 'hello world'" },
15
15
  {}
16
16
  );
17
- expect(result).not.toBeNull();
18
- expect(result?.stdout.trim()).toBe("hello world");
19
- expect(result?.exitCode).toBe(0);
17
+ expect(data).not.toBeNull();
18
+ expect(data?.stdout.trim()).toBe("hello world");
19
+ expect(data?.exitCode).toBe(0);
20
20
  });
21
21
 
22
22
  it("returns exit code 0 for successful commands", async () => {
23
- const { result } = await handleBashTool(fs)({ command: "true" }, {});
24
- expect(result?.exitCode).toBe(0);
23
+ const { data } = await handleBashTool({fs})({ command: "true" }, {});
24
+ expect(data?.exitCode).toBe(0);
25
25
  });
26
26
 
27
27
  it("returns non-zero exit code for failed commands", async () => {
28
- const { result } = await handleBashTool(fs)({ command: "false" }, {});
29
- expect(result?.exitCode).toBe(1);
28
+ const { data } = await handleBashTool({fs})({ command: "false" }, {});
29
+ expect(data?.exitCode).toBe(1);
30
30
  });
31
31
 
32
32
  it("captures stderr output", async () => {
33
- const { result } = await handleBashTool(fs)(
33
+ const { data } = await handleBashTool({fs})(
34
34
  { command: "echo 'error message' >&2" },
35
35
  {}
36
36
  );
37
- expect(result?.stderr.trim()).toBe("error message");
38
- expect(result?.stdout.trim()).toBe("");
37
+ expect(data?.stderr.trim()).toBe("error message");
38
+ expect(data?.stdout.trim()).toBe("");
39
39
  });
40
40
 
41
41
  it("supports piping between commands", async () => {
42
- const { result } = await handleBashTool(fs)(
42
+ const { data } = await handleBashTool({fs})(
43
43
  { command: "echo 'hello world' | tr 'a-z' 'A-Z'" },
44
44
  {}
45
45
  );
46
- expect(result?.stdout.trim()).toBe("HELLO WORLD");
46
+ expect(data?.stdout.trim()).toBe("HELLO WORLD");
47
47
  });
48
48
 
49
49
  it("supports command chaining with &&", async () => {
50
- const { result } = await handleBashTool(fs)(
50
+ const { data } = await handleBashTool({fs})(
51
51
  { command: "echo 'first' && echo 'second'" },
52
52
  {}
53
53
  );
54
- expect(result?.stdout).toContain("first");
55
- expect(result?.stdout).toContain("second");
54
+ expect(data?.stdout).toContain("first");
55
+ expect(data?.stdout).toContain("second");
56
56
  });
57
57
 
58
58
  it("handles multi-line output", async () => {
59
- const { result } = await handleBashTool(fs)(
59
+ const { data } = await handleBashTool({fs})(
60
60
  { command: "printf 'line1\\nline2\\nline3'" },
61
61
  {}
62
62
  );
63
- const lines = result?.stdout.split("\n");
63
+ const lines = data?.stdout.split("\n");
64
64
  expect(lines).toHaveLength(3);
65
65
  expect(lines?.[0]).toBe("line1");
66
66
  expect(lines?.[2]).toBe("line3");
67
67
  });
68
68
 
69
69
  it("handles commands with arguments and flags", async () => {
70
- const { result } = await handleBashTool(fs)(
70
+ const { data } = await handleBashTool({fs})(
71
71
  { command: "echo -n 'no newline'" },
72
72
  {}
73
73
  );
74
- expect(result?.stdout).toBe("no newline");
74
+ expect(data?.stdout).toBe("no newline");
75
75
  });
76
76
 
77
77
  it("supports command substitution", async () => {
78
- const { result } = await handleBashTool(fs)(
78
+ const { data } = await handleBashTool({fs})(
79
79
  { command: "echo \"count: $(echo 'a b c' | wc -w | tr -d ' ')\"" },
80
80
  {}
81
81
  );
82
- expect(result?.stdout.trim()).toBe("count: 3");
82
+ expect(data?.stdout.trim()).toBe("count: 3");
83
83
  });
84
84
 
85
- it("returns content string with formatted output", async () => {
86
- const { content } = await handleBashTool(fs)(
85
+ it("returns toolResponse string with formatted output", async () => {
86
+ const { toolResponse } = await handleBashTool({fs})(
87
87
  { command: "echo 'test'" },
88
88
  {}
89
89
  );
90
- expect(content).toContain("Exit code: 0");
91
- expect(content).toContain("stdout:");
92
- expect(content).toContain("test");
90
+ expect(toolResponse).toContain("Exit code: 0");
91
+ expect(toolResponse).toContain("stdout:");
92
+ expect(toolResponse).toContain("test");
93
93
  });
94
94
  });
95
95
 
96
96
  describe("bash with overlay filesystem", () => {
97
97
  it("sees files in the current directory", async () => {
98
98
  const fs = new OverlayFs({ root: __dirname, mountPoint: "/home/user" });
99
- const { result } = await handleBashTool(fs)({ command: "ls" }, {});
100
- expect(result?.stdout).toContain("bash.test.ts");
101
- expect(result?.stdout).toContain("handler.ts");
102
- expect(result?.stdout).toContain("tool.ts");
99
+ const { data } = await handleBashTool({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");
103
103
  });
104
104
  });
@@ -1,6 +1,6 @@
1
1
  import type { ActivityToolHandler } from "../../workflow";
2
2
  import type { bashToolSchemaType } from "./tool";
3
- import { Bash, type IFileSystem } from "just-bash";
3
+ import { Bash, type BashOptions } from "just-bash";
4
4
 
5
5
  type BashExecOut = {
6
6
  exitCode: number;
@@ -8,29 +8,38 @@ type BashExecOut = {
8
8
  stdout: string;
9
9
  };
10
10
 
11
+ /** BashOptions with `fs` required */
12
+ type BashToolOptions = Required<Pick<BashOptions, "fs">> & Omit<BashOptions, "fs">;
13
+
11
14
  export const handleBashTool: (
12
- fs: IFileSystem
15
+ bashOptions: BashToolOptions,
13
16
  ) => ActivityToolHandler<bashToolSchemaType, BashExecOut | null> =
14
- (fs: IFileSystem) => async (args: bashToolSchemaType, _context) => {
17
+ (bashOptions: BashToolOptions) => async (args: bashToolSchemaType, _context) => {
15
18
  const { command } = args;
16
19
 
17
- const bashOptions = fs ? { fs } : {};
20
+ const mergedOptions: BashOptions = {
21
+ ...bashOptions,
22
+ executionLimits: {
23
+ maxStringLength: 52428800, // 50MB default
24
+ ...bashOptions.executionLimits,
25
+ },
26
+ };
18
27
 
19
- const bash = new Bash(bashOptions);
28
+ const bash = new Bash(mergedOptions);
20
29
 
21
30
  try {
22
31
  const { exitCode, stderr, stdout } = await bash.exec(command);
23
32
  const bashExecOut = { exitCode, stderr, stdout };
24
33
 
25
34
  return {
26
- content: `Exit code: ${exitCode}\n\nstdout:\n${stdout}\n\nstderr:\n${stderr}`,
27
- result: bashExecOut,
35
+ toolResponse: `Exit code: ${exitCode}\n\nstdout:\n${stdout}\n\nstderr:\n${stderr}`,
36
+ data: bashExecOut,
28
37
  };
29
38
  } catch (error) {
30
39
  const err = error instanceof Error ? error : new Error("Unknown error");
31
40
  return {
32
- content: `Error executing bash command: ${err.message}`,
33
- result: null,
41
+ toolResponse: `Error executing bash command: ${err.message}`,
42
+ data: null,
34
43
  };
35
44
  }
36
45
  };
@@ -4,15 +4,31 @@ export const createBashToolDescription = ({
4
4
  fileTree,
5
5
  }: {
6
6
  fileTree: string;
7
- }): string => `tool to execute bash commands, the file tree is: ${fileTree}`;
7
+ }): string => `Execute shell commands in a bash environment.
8
+
9
+ Use this tool to:
10
+ - Run shell commands (ls, cat, grep, find, etc.)
11
+ - Execute scripts and chain commands with pipes (|) or logical operators (&&, ||)
12
+ - Inspect files and directories
13
+
14
+ Current file tree:
15
+ ${fileTree}`;
8
16
 
9
17
  export const bashTool = {
10
18
  name: "Bash" as const,
11
- description: "tool to execute bash commands",
19
+ description: `Execute shell commands in a sandboxed bash environment.
20
+
21
+ Use this tool to:
22
+ - Run shell commands (ls, cat, grep, find, etc.)
23
+ - Execute scripts and chain commands with pipes (|) or logical operators (&&, ||)
24
+ - Inspect files and directories
25
+ `,
12
26
  schema: z.object({
13
27
  command: z
14
28
  .string()
15
- .describe("stringified command to be executed inside the Bash"),
29
+ .describe(
30
+ "The bash command to execute. Can include pipes (|), redirects (>, >>), logical operators (&&, ||), and shell features like command substitution $(...)."
31
+ ),
16
32
  }),
17
33
  strict: true,
18
34
  };
@@ -14,8 +14,8 @@ export interface EditResult {
14
14
  * Edit handler response
15
15
  */
16
16
  export interface EditHandlerResponse {
17
- content: string;
18
- result: EditResult;
17
+ toolResponse: string;
18
+ data: EditResult;
19
19
  }
20
20
 
21
21
  /**
@@ -55,8 +55,8 @@ export async function editHandler(
55
55
  // Validate old_string !== new_string
56
56
  if (old_string === new_string) {
57
57
  return {
58
- content: `Error: old_string and new_string must be different.`,
59
- result: {
58
+ toolResponse: `Error: old_string and new_string must be different.`,
59
+ data: {
60
60
  path: file_path,
61
61
  success: false,
62
62
  replacements: 0,
@@ -69,8 +69,8 @@ export async function editHandler(
69
69
  const exists = await fs.exists(file_path);
70
70
  if (!exists) {
71
71
  return {
72
- content: `Error: File "${file_path}" does not exist.`,
73
- result: {
72
+ toolResponse: `Error: File "${file_path}" does not exist.`,
73
+ data: {
74
74
  path: file_path,
75
75
  success: false,
76
76
  replacements: 0,
@@ -84,8 +84,8 @@ export async function editHandler(
84
84
  // Check if old_string exists in the file
85
85
  if (!content.includes(old_string)) {
86
86
  return {
87
- content: `Error: Could not find the specified text in "${file_path}". Make sure old_string matches exactly (whitespace-sensitive).`,
88
- result: {
87
+ toolResponse: `Error: Could not find the specified text in "${file_path}". Make sure old_string matches exactly (whitespace-sensitive).`,
88
+ data: {
89
89
  path: file_path,
90
90
  success: false,
91
91
  replacements: 0,
@@ -101,8 +101,8 @@ export async function editHandler(
101
101
  // Check uniqueness if not replace_all
102
102
  if (!replace_all && occurrences > 1) {
103
103
  return {
104
- content: `Error: old_string appears ${occurrences} times in "${file_path}". Either provide more context to make it unique, or use replace_all: true.`,
105
- result: {
104
+ toolResponse: `Error: old_string appears ${occurrences} times in "${file_path}". Either provide more context to make it unique, or use replace_all: true.`,
105
+ data: {
106
106
  path: file_path,
107
107
  success: false,
108
108
  replacements: 0,
@@ -135,8 +135,8 @@ export async function editHandler(
135
135
  : `Replaced 1 occurrence`;
136
136
 
137
137
  return {
138
- content: `${summary} in ${file_path}`,
139
- result: {
138
+ toolResponse: `${summary} in ${file_path}`,
139
+ data: {
140
140
  path: file_path,
141
141
  success: true,
142
142
  replacements,
@@ -145,8 +145,8 @@ export async function editHandler(
145
145
  } catch (error) {
146
146
  const message = error instanceof Error ? error.message : "Unknown error";
147
147
  return {
148
- content: `Error editing file "${file_path}": ${message}`,
149
- result: {
148
+ toolResponse: `Error editing file "${file_path}": ${message}`,
149
+ data: {
150
150
  path: file_path,
151
151
  success: false,
152
152
  replacements: 0,
@@ -13,8 +13,8 @@ export interface GlobResult {
13
13
  * Glob handler response
14
14
  */
15
15
  export interface GlobHandlerResponse {
16
- content: string;
17
- result: GlobResult;
16
+ toolResponse: string;
17
+ data: GlobResult;
18
18
  }
19
19
 
20
20
  /**
@@ -31,8 +31,8 @@ export async function globHandler(
31
31
  const _bash = new Bash({ fs });
32
32
 
33
33
  return Promise.resolve({
34
- content: "Hello, world!",
35
- result: { files: [] },
34
+ toolResponse: "Hello, world!",
35
+ data: { files: [] },
36
36
  });
37
37
 
38
38
  // try {
@@ -24,7 +24,7 @@ export interface TaskHandlerResult<TResult = unknown> {
24
24
  * {
25
25
  * name: "researcher",
26
26
  * description: "Researches topics",
27
- * workflowType: "researcherWorkflow",
27
+ * workflow: "researcherWorkflow",
28
28
  * resultSchema: z.object({ findings: z.string() }),
29
29
  * },
30
30
  * ]);
@@ -47,11 +47,21 @@ export function createTaskHandler(subagents: SubagentConfig[]) {
47
47
  const childWorkflowId = `${parentWorkflowId}-${args.subagent}-${uuid4()}`;
48
48
 
49
49
  // Execute the child workflow
50
- const childResult = await executeChild(config.workflowType, {
50
+ const input: SubagentInput = {
51
+ prompt: args.prompt,
52
+ ...(config.context && { context: config.context }),
53
+ };
54
+
55
+ const childOpts = {
51
56
  workflowId: childWorkflowId,
52
- args: [{ prompt: args.prompt } satisfies SubagentInput],
57
+ args: [input],
53
58
  taskQueue: config.taskQueue ?? parentTaskQueue,
54
- });
59
+ };
60
+
61
+ const childResult =
62
+ typeof config.workflow === "string"
63
+ ? await executeChild(config.workflow, childOpts)
64
+ : await executeChild(config.workflow, childOpts);
55
65
 
56
66
  // Validate result if schema provided, otherwise pass through as-is
57
67
  const validated = config.resultSchema
@@ -59,14 +69,14 @@ export function createTaskHandler(subagents: SubagentConfig[]) {
59
69
  : childResult;
60
70
 
61
71
  // Format content - stringify objects, pass strings through
62
- const content =
72
+ const toolResponse =
63
73
  typeof validated === "string"
64
74
  ? validated
65
75
  : JSON.stringify(validated, null, 2);
66
76
 
67
77
  return {
68
- content,
69
- result: {
78
+ toolResponse,
79
+ data: {
70
80
  result: validated,
71
81
  childWorkflowId,
72
82
  },
@@ -44,7 +44,7 @@ Usage notes:
44
44
  * {
45
45
  * name: "researcher",
46
46
  * description: "Researches topics and gathers information",
47
- * workflowType: "researcherWorkflow",
47
+ * workflow: "researcherWorkflow",
48
48
  * resultSchema: z.object({ findings: z.string() }),
49
49
  * },
50
50
  * ]);
@@ -5,6 +5,7 @@ import type {
5
5
  import type { ToolHandlerResponse } from "../../lib/tool-router";
6
6
  import type { WorkflowTask } from "../../lib/types";
7
7
  import type { TaskCreateToolSchemaType } from "./tool";
8
+ import { uuid4 } from "@temporalio/workflow";
8
9
 
9
10
  /**
10
11
  * Creates a TaskCreate handler that adds tasks to the workflow state.
@@ -18,18 +19,14 @@ import type { TaskCreateToolSchemaType } from "./tool";
18
19
  */
19
20
  export function createTaskCreateHandler<
20
21
  TCustom extends JsonSerializable<TCustom>,
21
- >({
22
- stateManager,
23
- idGenerator,
24
- }: {
25
- stateManager: AgentStateManager<TCustom>;
26
- idGenerator: () => string;
27
- }): (args: TaskCreateToolSchemaType) => ToolHandlerResponse<WorkflowTask> {
22
+ >(
23
+ stateManager: AgentStateManager<TCustom>
24
+ ): (args: TaskCreateToolSchemaType) => ToolHandlerResponse<WorkflowTask> {
28
25
  return (
29
26
  args: TaskCreateToolSchemaType
30
27
  ): ToolHandlerResponse<WorkflowTask> => {
31
28
  const task: WorkflowTask = {
32
- id: idGenerator(),
29
+ id: uuid4(),
33
30
  subject: args.subject,
34
31
  description: args.description,
35
32
  activeForm: args.activeForm,
@@ -42,8 +39,8 @@ export function createTaskCreateHandler<
42
39
  stateManager.setTask(task);
43
40
 
44
41
  return {
45
- content: JSON.stringify(task, null, 2),
46
- result: task,
42
+ toolResponse: JSON.stringify(task, null, 2),
43
+ data: task,
47
44
  };
48
45
  };
49
46
  }
@@ -25,14 +25,14 @@ export function createTaskGetHandler<TCustom extends JsonSerializable<TCustom>>(
25
25
 
26
26
  if (!task) {
27
27
  return {
28
- content: JSON.stringify({ error: `Task not found: ${args.taskId}` }),
29
- result: null,
28
+ toolResponse: JSON.stringify({ error: `Task not found: ${args.taskId}` }),
29
+ data: null,
30
30
  };
31
31
  }
32
32
 
33
33
  return {
34
- content: JSON.stringify(task, null, 2),
35
- result: task,
34
+ toolResponse: JSON.stringify(task, null, 2),
35
+ data: task,
36
36
  };
37
37
  };
38
38
  }
@@ -26,8 +26,8 @@ export function createTaskListHandler<
26
26
  const taskList = stateManager.getTasks();
27
27
 
28
28
  return {
29
- content: JSON.stringify(taskList, null, 2),
30
- result: taskList,
29
+ toolResponse: JSON.stringify(taskList, null, 2),
30
+ data: taskList,
31
31
  };
32
32
  };
33
33
  }
@@ -29,8 +29,8 @@ export function createTaskUpdateHandler<
29
29
 
30
30
  if (!task) {
31
31
  return {
32
- content: JSON.stringify({ error: `Task not found: ${args.taskId}` }),
33
- result: null,
32
+ toolResponse: JSON.stringify({ error: `Task not found: ${args.taskId}` }),
33
+ data: null,
34
34
  };
35
35
  }
36
36
 
@@ -72,8 +72,8 @@ export function createTaskUpdateHandler<
72
72
  stateManager.setTask(task);
73
73
 
74
74
  return {
75
- content: JSON.stringify(task, null, 2),
76
- result: task,
75
+ toolResponse: JSON.stringify(task, null, 2),
76
+ data: task,
77
77
  };
78
78
  };
79
79
  }
package/src/workflow.ts CHANGED
@@ -33,7 +33,7 @@ export type {
33
33
  } from "./lib/state-manager";
34
34
 
35
35
  // Tool router (includes registry functionality)
36
- export { createToolRouter, hasNoOtherToolCalls } from "./lib/tool-router";
36
+ export { createToolRouter, hasNoOtherToolCalls, defineTool, defineSubagent } from "./lib/tool-router";
37
37
  export type {
38
38
  // Tool definition types
39
39
  ToolDefinition,
@@ -81,11 +81,13 @@ export type {
81
81
  PostToolUseFailureHook,
82
82
  PostToolUseFailureHookContext,
83
83
  PostToolUseFailureHookResult,
84
+ ToolHooks,
84
85
  SessionStartHook,
85
86
  SessionStartHookContext,
86
87
  SessionEndHook,
87
88
  SessionEndHookContext,
88
89
  SubagentConfig,
90
+ SubagentHooks,
89
91
  SubagentInput,
90
92
  TaskStatus,
91
93
  WorkflowTask,
package/tsup.config.ts CHANGED
@@ -5,7 +5,7 @@ export default defineConfig({
5
5
  index: "src/index.ts",
6
6
  workflow: "src/workflow.ts",
7
7
  },
8
- format: ["cjs", "esm"],
8
+ format: ["esm", "cjs"],
9
9
  dts: true,
10
10
  clean: true,
11
11
  sourcemap: true,
@@ -16,5 +16,7 @@ export default defineConfig({
16
16
  /^@temporalio\//,
17
17
  /^@langchain\//,
18
18
  "ioredis",
19
+ "@mongodb-js/zstd",
20
+ "node-liblzma",
19
21
  ],
20
22
  });