zeitlich 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -180
- package/dist/index.cjs +1314 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +128 -0
- package/dist/index.d.ts +51 -75
- package/dist/index.js +741 -1091
- package/dist/index.js.map +1 -1
- package/dist/workflow-uVNF7zoe.d.cts +941 -0
- package/dist/workflow-uVNF7zoe.d.ts +941 -0
- package/dist/workflow.cjs +914 -0
- package/dist/workflow.cjs.map +1 -0
- package/dist/workflow.d.cts +5 -0
- package/dist/workflow.d.ts +2 -1
- package/dist/workflow.js +543 -423
- package/dist/workflow.js.map +1 -1
- package/package.json +19 -17
- package/src/activities.ts +112 -0
- package/src/index.ts +49 -0
- package/src/lib/fs.ts +80 -0
- package/src/lib/model-invoker.ts +75 -0
- package/src/lib/session.ts +216 -0
- package/src/lib/state-manager.ts +268 -0
- package/src/lib/thread-manager.ts +169 -0
- package/src/lib/tool-router.ts +717 -0
- package/src/lib/types.ts +354 -0
- package/src/plugin.ts +28 -0
- package/src/tools/ask-user-question/handler.ts +25 -0
- package/src/tools/ask-user-question/tool.ts +46 -0
- package/src/tools/bash/bash.test.ts +104 -0
- package/src/tools/bash/handler.ts +36 -0
- package/src/tools/bash/tool.ts +20 -0
- package/src/tools/edit/handler.ts +156 -0
- package/src/tools/edit/tool.ts +39 -0
- package/src/tools/glob/handler.ts +62 -0
- package/src/tools/glob/tool.ts +27 -0
- package/src/tools/grep/tool.ts +45 -0
- package/src/tools/read/tool.ts +33 -0
- package/src/tools/task/handler.ts +75 -0
- package/src/tools/task/tool.ts +96 -0
- package/src/tools/task-create/handler.ts +49 -0
- package/src/tools/task-create/tool.ts +66 -0
- package/src/tools/task-get/handler.ts +38 -0
- package/src/tools/task-get/tool.ts +11 -0
- package/src/tools/task-list/handler.ts +33 -0
- package/src/tools/task-list/tool.ts +9 -0
- package/src/tools/task-update/handler.ts +79 -0
- package/src/tools/task-update/tool.ts +20 -0
- package/src/tools/write/tool.ts +26 -0
- package/src/workflow.ts +138 -0
- package/tsup.config.ts +20 -0
- package/dist/index.d.mts +0 -152
- package/dist/index.mjs +0 -1587
- package/dist/index.mjs.map +0 -1
- package/dist/workflow-7_MT-5-w.d.mts +0 -1203
- package/dist/workflow-7_MT-5-w.d.ts +0 -1203
- package/dist/workflow.d.mts +0 -4
- package/dist/workflow.mjs +0 -739
- package/dist/workflow.mjs.map +0 -1
package/src/lib/types.ts
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import type { ToolMessageContent } from "./thread-manager";
|
|
2
|
+
import type {
|
|
3
|
+
BuildInToolDefinitions,
|
|
4
|
+
InferToolResults,
|
|
5
|
+
ParsedToolCallUnion,
|
|
6
|
+
ToolCallResultUnion,
|
|
7
|
+
ToolDefinition,
|
|
8
|
+
ToolMap,
|
|
9
|
+
} from "./tool-router";
|
|
10
|
+
|
|
11
|
+
import type { MessageContent, StoredMessage } from "@langchain/core/messages";
|
|
12
|
+
import type { z } from "zod";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Agent execution status
|
|
16
|
+
*/
|
|
17
|
+
export type AgentStatus =
|
|
18
|
+
| "RUNNING"
|
|
19
|
+
| "WAITING_FOR_INPUT"
|
|
20
|
+
| "COMPLETED"
|
|
21
|
+
| "FAILED"
|
|
22
|
+
| "CANCELLED";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Base state that all agents must have
|
|
26
|
+
*/
|
|
27
|
+
export interface BaseAgentState {
|
|
28
|
+
tools: ToolDefinition[];
|
|
29
|
+
status: AgentStatus;
|
|
30
|
+
version: number;
|
|
31
|
+
turns: number;
|
|
32
|
+
tasks: Map<string, WorkflowTask>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* File representation for agent workflows
|
|
37
|
+
*/
|
|
38
|
+
export interface AgentFile {
|
|
39
|
+
/** Database/S3 file ID */
|
|
40
|
+
id: string;
|
|
41
|
+
/** Virtual path for agent (e.g., "evidence/invoice.pdf") */
|
|
42
|
+
path: string;
|
|
43
|
+
/** Original filename */
|
|
44
|
+
filename: string;
|
|
45
|
+
/** Generic description for prompt */
|
|
46
|
+
description?: string;
|
|
47
|
+
/** MIME type of the file */
|
|
48
|
+
mimeType?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Agent response from LLM invocation
|
|
53
|
+
*/
|
|
54
|
+
export interface AgentResponse {
|
|
55
|
+
message: StoredMessage;
|
|
56
|
+
stopReason: string | null;
|
|
57
|
+
usage?: {
|
|
58
|
+
input_tokens?: number;
|
|
59
|
+
output_tokens?: number;
|
|
60
|
+
total_tokens?: number;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Configuration for a Zeitlich agent session
|
|
66
|
+
*/
|
|
67
|
+
export interface ZeitlichAgentConfig<T extends ToolMap> {
|
|
68
|
+
buildFileTree?: () => Promise<string>;
|
|
69
|
+
threadId: string;
|
|
70
|
+
agentName: string;
|
|
71
|
+
metadata?: Record<string, unknown>;
|
|
72
|
+
maxTurns?: number;
|
|
73
|
+
/** Workflow-specific runAgent activity (with tools pre-bound) */
|
|
74
|
+
runAgent: RunAgentActivity;
|
|
75
|
+
/** Tool router for processing tool calls (optional if agent has no tools) */
|
|
76
|
+
tools?: T;
|
|
77
|
+
/** Subagent configurations */
|
|
78
|
+
subagents?: SubagentConfig[];
|
|
79
|
+
/** Session lifecycle hooks */
|
|
80
|
+
hooks?: Hooks<T, ToolCallResultUnion<InferToolResults<T>>>;
|
|
81
|
+
/** Whether to process tools in parallel */
|
|
82
|
+
processToolsInParallel?: boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Base system prompt (e.g., Auditron identity).
|
|
85
|
+
* Can be a static string or async function.
|
|
86
|
+
*/
|
|
87
|
+
baseSystemPrompt: string | (() => string | Promise<string>);
|
|
88
|
+
/**
|
|
89
|
+
* Agent-specific instructions prompt.
|
|
90
|
+
* Can be a static string or async function.
|
|
91
|
+
*/
|
|
92
|
+
instructionsPrompt: string | (() => string | Promise<string>);
|
|
93
|
+
/**
|
|
94
|
+
* Build context message content from agent-specific context.
|
|
95
|
+
* Returns MessageContent array for the initial HumanMessage.
|
|
96
|
+
*/
|
|
97
|
+
buildContextMessage: () => MessageContent | Promise<MessageContent>;
|
|
98
|
+
/**
|
|
99
|
+
* Build in tools - accepts raw handlers or proxied activities
|
|
100
|
+
*/
|
|
101
|
+
buildInTools?: {
|
|
102
|
+
[K in keyof BuildInToolDefinitions]?: BuildInToolDefinitions[K]["handler"];
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Configuration passed to runAgent activity
|
|
108
|
+
*/
|
|
109
|
+
export interface RunAgentConfig {
|
|
110
|
+
threadId: string;
|
|
111
|
+
agentName: string;
|
|
112
|
+
metadata?: Record<string, unknown>;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Type signature for workflow-specific runAgent activity
|
|
117
|
+
*/
|
|
118
|
+
export type RunAgentActivity = (
|
|
119
|
+
config: RunAgentConfig
|
|
120
|
+
) => Promise<AgentResponse>;
|
|
121
|
+
/**
|
|
122
|
+
* Configuration for appending a tool result
|
|
123
|
+
*/
|
|
124
|
+
export interface ToolResultConfig {
|
|
125
|
+
threadId: string;
|
|
126
|
+
toolCallId: string;
|
|
127
|
+
/** Content for the tool message (string or complex content parts) */
|
|
128
|
+
content: ToolMessageContent;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ============================================================================
|
|
132
|
+
// Subagent Configuration
|
|
133
|
+
// ============================================================================
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Configuration for a subagent that can be spawned by the parent workflow.
|
|
137
|
+
*
|
|
138
|
+
* @template TResult - Zod schema type for validating the child workflow's result
|
|
139
|
+
*/
|
|
140
|
+
export interface SubagentConfig<TResult extends z.ZodType = z.ZodType> {
|
|
141
|
+
/** Identifier used in Task tool's subagent parameter */
|
|
142
|
+
name: string;
|
|
143
|
+
/** Description shown to the parent agent explaining what this subagent does */
|
|
144
|
+
description: string;
|
|
145
|
+
/** Temporal workflow type name (used with executeChild) */
|
|
146
|
+
workflowType: string;
|
|
147
|
+
/** Optional task queue - defaults to parent's queue if not specified */
|
|
148
|
+
taskQueue?: string;
|
|
149
|
+
/** Optional Zod schema to validate the child workflow's result. If omitted, result is passed through as-is. */
|
|
150
|
+
resultSchema?: TResult;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Input passed to child workflows when spawned as subagents
|
|
155
|
+
*/
|
|
156
|
+
export interface SubagentInput {
|
|
157
|
+
/** The prompt/task from the parent agent */
|
|
158
|
+
prompt: string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ============================================================================
|
|
162
|
+
// Workflow Tasks
|
|
163
|
+
// ============================================================================
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Status of a workflow task
|
|
167
|
+
*/
|
|
168
|
+
export type TaskStatus = "pending" | "in_progress" | "completed";
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* A task managed within a workflow for tracking work items
|
|
172
|
+
*/
|
|
173
|
+
export interface WorkflowTask {
|
|
174
|
+
/** Unique task identifier */
|
|
175
|
+
id: string;
|
|
176
|
+
/** Brief, actionable title in imperative form */
|
|
177
|
+
subject: string;
|
|
178
|
+
/** Detailed description of what needs to be done */
|
|
179
|
+
description: string;
|
|
180
|
+
/** Present continuous form shown in spinner when in_progress */
|
|
181
|
+
activeForm: string;
|
|
182
|
+
/** Current status of the task */
|
|
183
|
+
status: TaskStatus;
|
|
184
|
+
/** Arbitrary key-value pairs for tracking */
|
|
185
|
+
metadata: Record<string, string>;
|
|
186
|
+
/** IDs of tasks that must complete before this one can start */
|
|
187
|
+
blockedBy: string[];
|
|
188
|
+
/** IDs of tasks that are waiting for this one to complete */
|
|
189
|
+
blocks: string[];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ============================================================================
|
|
193
|
+
// Session Lifecycle Hooks
|
|
194
|
+
// ============================================================================
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Exit reasons for session termination
|
|
198
|
+
*/
|
|
199
|
+
export type SessionExitReason =
|
|
200
|
+
| "completed"
|
|
201
|
+
| "max_turns"
|
|
202
|
+
| "waiting_for_input"
|
|
203
|
+
| "failed"
|
|
204
|
+
| "cancelled";
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Context for PreToolUse hook - called before tool execution
|
|
208
|
+
*/
|
|
209
|
+
export interface PreToolUseHookContext<T extends ToolMap> {
|
|
210
|
+
/** The tool call about to be executed */
|
|
211
|
+
toolCall: ParsedToolCallUnion<T>;
|
|
212
|
+
/** Thread identifier */
|
|
213
|
+
threadId: string;
|
|
214
|
+
/** Current turn number */
|
|
215
|
+
turn: number;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Result from PreToolUse hook - can block or modify execution
|
|
220
|
+
*/
|
|
221
|
+
export interface PreToolUseHookResult {
|
|
222
|
+
/** Skip this tool call entirely */
|
|
223
|
+
skip?: boolean;
|
|
224
|
+
/** Modified args to use instead (must match schema) */
|
|
225
|
+
modifiedArgs?: unknown;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* PreToolUse hook - called before tool execution, can block or modify
|
|
230
|
+
*/
|
|
231
|
+
export type PreToolUseHook<T extends ToolMap> = (
|
|
232
|
+
ctx: PreToolUseHookContext<T>
|
|
233
|
+
) => PreToolUseHookResult | Promise<PreToolUseHookResult>;
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Context for PostToolUse hook - called after successful tool execution
|
|
237
|
+
*/
|
|
238
|
+
export interface PostToolUseHookContext<T extends ToolMap, TResult = unknown> {
|
|
239
|
+
/** The tool call that was executed */
|
|
240
|
+
toolCall: ParsedToolCallUnion<T>;
|
|
241
|
+
/** The result from the tool handler */
|
|
242
|
+
result: TResult;
|
|
243
|
+
/** Thread identifier */
|
|
244
|
+
threadId: string;
|
|
245
|
+
/** Current turn number */
|
|
246
|
+
turn: number;
|
|
247
|
+
/** Execution duration in milliseconds */
|
|
248
|
+
durationMs: number;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* PostToolUse hook - called after successful tool execution
|
|
253
|
+
*/
|
|
254
|
+
export type PostToolUseHook<T extends ToolMap, TResult = unknown> = (
|
|
255
|
+
ctx: PostToolUseHookContext<T, TResult>
|
|
256
|
+
) => void | Promise<void>;
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Context for PostToolUseFailure hook - called when tool execution fails
|
|
260
|
+
*/
|
|
261
|
+
export interface PostToolUseFailureHookContext<T extends ToolMap> {
|
|
262
|
+
/** The tool call that failed */
|
|
263
|
+
toolCall: ParsedToolCallUnion<T>;
|
|
264
|
+
/** The error that occurred */
|
|
265
|
+
error: Error;
|
|
266
|
+
/** Thread identifier */
|
|
267
|
+
threadId: string;
|
|
268
|
+
/** Current turn number */
|
|
269
|
+
turn: number;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Result from PostToolUseFailure hook - can recover from errors
|
|
274
|
+
*/
|
|
275
|
+
export interface PostToolUseFailureHookResult {
|
|
276
|
+
/** Provide a fallback result instead of throwing */
|
|
277
|
+
fallbackContent?: ToolMessageContent;
|
|
278
|
+
/** Whether to suppress the error (still logs, but continues) */
|
|
279
|
+
suppress?: boolean;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* PostToolUseFailure hook - called when tool execution fails
|
|
284
|
+
*/
|
|
285
|
+
export type PostToolUseFailureHook<T extends ToolMap> = (
|
|
286
|
+
ctx: PostToolUseFailureHookContext<T>
|
|
287
|
+
) => PostToolUseFailureHookResult | Promise<PostToolUseFailureHookResult>;
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Context for SessionStart hook - called when session begins
|
|
291
|
+
*/
|
|
292
|
+
export interface SessionStartHookContext {
|
|
293
|
+
/** Thread identifier */
|
|
294
|
+
threadId: string;
|
|
295
|
+
/** Name of the agent */
|
|
296
|
+
agentName: string;
|
|
297
|
+
/** Session metadata */
|
|
298
|
+
metadata: Record<string, unknown>;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* SessionStart hook - called when session begins
|
|
303
|
+
*/
|
|
304
|
+
export type SessionStartHook = (
|
|
305
|
+
ctx: SessionStartHookContext
|
|
306
|
+
) => void | Promise<void>;
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Context for SessionEnd hook - called when session ends
|
|
310
|
+
*/
|
|
311
|
+
export interface SessionEndHookContext {
|
|
312
|
+
/** Thread identifier */
|
|
313
|
+
threadId: string;
|
|
314
|
+
/** Name of the agent */
|
|
315
|
+
agentName: string;
|
|
316
|
+
/** Reason the session ended */
|
|
317
|
+
exitReason: SessionExitReason;
|
|
318
|
+
/** Total turns executed */
|
|
319
|
+
turns: number;
|
|
320
|
+
/** Session metadata */
|
|
321
|
+
metadata: Record<string, unknown>;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* SessionEnd hook - called when session ends
|
|
326
|
+
*/
|
|
327
|
+
export type SessionEndHook = (
|
|
328
|
+
ctx: SessionEndHookContext
|
|
329
|
+
) => void | Promise<void>;
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Combined hooks interface for session lifecycle
|
|
333
|
+
*/
|
|
334
|
+
export interface Hooks<T extends ToolMap, TResult = unknown> {
|
|
335
|
+
/** Called before each tool execution - can block or modify */
|
|
336
|
+
onPreToolUse?: PreToolUseHook<T>;
|
|
337
|
+
/** Called after each successful tool execution */
|
|
338
|
+
onPostToolUse?: PostToolUseHook<T, TResult>;
|
|
339
|
+
/** Called when tool execution fails */
|
|
340
|
+
onPostToolUseFailure?: PostToolUseFailureHook<T>;
|
|
341
|
+
/** Called when session starts */
|
|
342
|
+
onSessionStart?: SessionStartHook;
|
|
343
|
+
/** Called when session ends */
|
|
344
|
+
onSessionEnd?: SessionEndHook;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Helper to check if status is terminal
|
|
349
|
+
*/
|
|
350
|
+
export function isTerminalStatus(status: AgentStatus): boolean {
|
|
351
|
+
return (
|
|
352
|
+
status === "COMPLETED" || status === "FAILED" || status === "CANCELLED"
|
|
353
|
+
);
|
|
354
|
+
}
|
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { SimplePlugin } from "@temporalio/plugin";
|
|
2
|
+
import { createSharedActivities } from "./activities";
|
|
3
|
+
import type Redis from "ioredis";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Options for the Zeitlich plugin
|
|
7
|
+
*
|
|
8
|
+
* @experimental The Zeitlich plugin is an experimental feature; APIs may change without notice.
|
|
9
|
+
*/
|
|
10
|
+
export interface ZeitlichPluginOptions {
|
|
11
|
+
redis: Redis;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A Temporal plugin that integrates Zeitlich for use in workflows.
|
|
16
|
+
* This plugin creates shared activities for thread management.
|
|
17
|
+
* Workflow-specific activities (like runAgent) should be created separately.
|
|
18
|
+
*
|
|
19
|
+
* @experimental The Zeitlich plugin is an experimental feature; APIs may change without notice.
|
|
20
|
+
*/
|
|
21
|
+
export class ZeitlichPlugin extends SimplePlugin {
|
|
22
|
+
constructor(options: ZeitlichPluginOptions) {
|
|
23
|
+
super({
|
|
24
|
+
name: "ZeitlichPlugin",
|
|
25
|
+
activities: createSharedActivities(options.redis),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { AIMessage, type StoredMessage } from "@langchain/core/messages";
|
|
2
|
+
import type { ActivityToolHandler } from "../../lib/tool-router";
|
|
3
|
+
import type { AskUserQuestionToolSchemaType } from "./tool";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Handle user interaction tool result - creates AI messages for display.
|
|
7
|
+
*/
|
|
8
|
+
export const handleAskUserQuestionToolResult: ActivityToolHandler<
|
|
9
|
+
AskUserQuestionToolSchemaType,
|
|
10
|
+
{ chatMessages: StoredMessage[] }
|
|
11
|
+
> = async (args) => {
|
|
12
|
+
const messages = args.questions.map(
|
|
13
|
+
({ question, header, options, multiSelect }) =>
|
|
14
|
+
new AIMessage({
|
|
15
|
+
content: question,
|
|
16
|
+
additional_kwargs: {
|
|
17
|
+
header,
|
|
18
|
+
options,
|
|
19
|
+
multiSelect,
|
|
20
|
+
},
|
|
21
|
+
}).toDict()
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
return { content: "Question submitted", result: { chatMessages: messages } };
|
|
25
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
|
|
3
|
+
export const askUserQuestionTool = {
|
|
4
|
+
name: "AskUserQuestion" as const,
|
|
5
|
+
description: `Use this tool when you need to ask the user questions during execution. This allows you to:
|
|
6
|
+
|
|
7
|
+
1. Gather user preferences or requirements
|
|
8
|
+
2. Clarify ambiguous instructions
|
|
9
|
+
3. Get decisions on implementation choices as you work
|
|
10
|
+
4. Offer choices to the user about what direction to take.
|
|
11
|
+
|
|
12
|
+
Usage notes:
|
|
13
|
+
|
|
14
|
+
* Users will always be able to select "Other" to provide custom text input
|
|
15
|
+
* Use multiSelect: true to allow multiple answers to be selected for a question
|
|
16
|
+
* If you recommend a specific option, make that the first option in the list and add "(Recommended)" at the end of the label
|
|
17
|
+
`,
|
|
18
|
+
schema: z.object({
|
|
19
|
+
questions: z.array(
|
|
20
|
+
z.object({
|
|
21
|
+
question: z.string().describe("The full question text to display"),
|
|
22
|
+
header: z
|
|
23
|
+
.string()
|
|
24
|
+
.describe("Short label for the question (max 12 characters)"),
|
|
25
|
+
options: z
|
|
26
|
+
.array(
|
|
27
|
+
z.object({
|
|
28
|
+
label: z.string(),
|
|
29
|
+
description: z.string(),
|
|
30
|
+
})
|
|
31
|
+
)
|
|
32
|
+
.min(0)
|
|
33
|
+
.max(4)
|
|
34
|
+
.describe("Array of 0-4 choices, each with label and description"),
|
|
35
|
+
multiSelect: z
|
|
36
|
+
.boolean()
|
|
37
|
+
.describe("If true, users can select multiple options"),
|
|
38
|
+
})
|
|
39
|
+
),
|
|
40
|
+
}),
|
|
41
|
+
strict: true,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type AskUserQuestionToolSchemaType = z.infer<
|
|
45
|
+
typeof askUserQuestionTool.schema
|
|
46
|
+
>;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { dirname } from "path";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { handleBashTool } from "./handler";
|
|
5
|
+
import { OverlayFs } from "just-bash";
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
describe("bash with default options", () => {
|
|
10
|
+
const fs = new OverlayFs({ root: __dirname, mountPoint: "/home/user" });
|
|
11
|
+
|
|
12
|
+
it("executes echo and captures stdout", async () => {
|
|
13
|
+
const { result } = await handleBashTool(fs)(
|
|
14
|
+
{ command: "echo 'hello world'" },
|
|
15
|
+
{}
|
|
16
|
+
);
|
|
17
|
+
expect(result).not.toBeNull();
|
|
18
|
+
expect(result?.stdout.trim()).toBe("hello world");
|
|
19
|
+
expect(result?.exitCode).toBe(0);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("returns exit code 0 for successful commands", async () => {
|
|
23
|
+
const { result } = await handleBashTool(fs)({ command: "true" }, {});
|
|
24
|
+
expect(result?.exitCode).toBe(0);
|
|
25
|
+
});
|
|
26
|
+
|
|
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);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("captures stderr output", async () => {
|
|
33
|
+
const { result } = await handleBashTool(fs)(
|
|
34
|
+
{ command: "echo 'error message' >&2" },
|
|
35
|
+
{}
|
|
36
|
+
);
|
|
37
|
+
expect(result?.stderr.trim()).toBe("error message");
|
|
38
|
+
expect(result?.stdout.trim()).toBe("");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("supports piping between commands", async () => {
|
|
42
|
+
const { result } = await handleBashTool(fs)(
|
|
43
|
+
{ command: "echo 'hello world' | tr 'a-z' 'A-Z'" },
|
|
44
|
+
{}
|
|
45
|
+
);
|
|
46
|
+
expect(result?.stdout.trim()).toBe("HELLO WORLD");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("supports command chaining with &&", async () => {
|
|
50
|
+
const { result } = await handleBashTool(fs)(
|
|
51
|
+
{ command: "echo 'first' && echo 'second'" },
|
|
52
|
+
{}
|
|
53
|
+
);
|
|
54
|
+
expect(result?.stdout).toContain("first");
|
|
55
|
+
expect(result?.stdout).toContain("second");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("handles multi-line output", async () => {
|
|
59
|
+
const { result } = await handleBashTool(fs)(
|
|
60
|
+
{ command: "printf 'line1\\nline2\\nline3'" },
|
|
61
|
+
{}
|
|
62
|
+
);
|
|
63
|
+
const lines = result?.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 { result } = await handleBashTool(fs)(
|
|
71
|
+
{ command: "echo -n 'no newline'" },
|
|
72
|
+
{}
|
|
73
|
+
);
|
|
74
|
+
expect(result?.stdout).toBe("no newline");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("supports command substitution", async () => {
|
|
78
|
+
const { result } = await handleBashTool(fs)(
|
|
79
|
+
{ command: "echo \"count: $(echo 'a b c' | wc -w | tr -d ' ')\"" },
|
|
80
|
+
{}
|
|
81
|
+
);
|
|
82
|
+
expect(result?.stdout.trim()).toBe("count: 3");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("returns content string with formatted output", async () => {
|
|
86
|
+
const { content } = await handleBashTool(fs)(
|
|
87
|
+
{ command: "echo 'test'" },
|
|
88
|
+
{}
|
|
89
|
+
);
|
|
90
|
+
expect(content).toContain("Exit code: 0");
|
|
91
|
+
expect(content).toContain("stdout:");
|
|
92
|
+
expect(content).toContain("test");
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
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 { 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");
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ActivityToolHandler } from "../../workflow";
|
|
2
|
+
import type { bashToolSchemaType } from "./tool";
|
|
3
|
+
import { Bash, type IFileSystem } from "just-bash";
|
|
4
|
+
|
|
5
|
+
type BashExecOut = {
|
|
6
|
+
exitCode: number;
|
|
7
|
+
stderr: string;
|
|
8
|
+
stdout: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const handleBashTool: (
|
|
12
|
+
fs: IFileSystem
|
|
13
|
+
) => ActivityToolHandler<bashToolSchemaType, BashExecOut | null> =
|
|
14
|
+
(fs: IFileSystem) => async (args: bashToolSchemaType, _context) => {
|
|
15
|
+
const { command } = args;
|
|
16
|
+
|
|
17
|
+
const bashOptions = fs ? { fs } : {};
|
|
18
|
+
|
|
19
|
+
const bash = new Bash(bashOptions);
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const { exitCode, stderr, stdout } = await bash.exec(command);
|
|
23
|
+
const bashExecOut = { exitCode, stderr, stdout };
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
content: `Exit code: ${exitCode}\n\nstdout:\n${stdout}\n\nstderr:\n${stderr}`,
|
|
27
|
+
result: bashExecOut,
|
|
28
|
+
};
|
|
29
|
+
} catch (error) {
|
|
30
|
+
const err = error instanceof Error ? error : new Error("Unknown error");
|
|
31
|
+
return {
|
|
32
|
+
content: `Error executing bash command: ${err.message}`,
|
|
33
|
+
result: null,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
|
|
3
|
+
export const createBashToolDescription = ({
|
|
4
|
+
fileTree,
|
|
5
|
+
}: {
|
|
6
|
+
fileTree: string;
|
|
7
|
+
}): string => `tool to execute bash commands, the file tree is: ${fileTree}`;
|
|
8
|
+
|
|
9
|
+
export const bashTool = {
|
|
10
|
+
name: "Bash" as const,
|
|
11
|
+
description: "tool to execute bash commands",
|
|
12
|
+
schema: z.object({
|
|
13
|
+
command: z
|
|
14
|
+
.string()
|
|
15
|
+
.describe("stringified command to be executed inside the Bash"),
|
|
16
|
+
}),
|
|
17
|
+
strict: true,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type bashToolSchemaType = z.infer<typeof bashTool.schema>;
|