@clinebot/agents 0.0.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.
Files changed (90) hide show
  1. package/README.md +145 -0
  2. package/dist/agent-input.d.ts +2 -0
  3. package/dist/agent.d.ts +56 -0
  4. package/dist/extensions.d.ts +21 -0
  5. package/dist/hooks/engine.d.ts +42 -0
  6. package/dist/hooks/index.d.ts +2 -0
  7. package/dist/hooks/lifecycle.d.ts +5 -0
  8. package/dist/hooks/node.d.ts +2 -0
  9. package/dist/hooks/subprocess-runner.d.ts +16 -0
  10. package/dist/hooks/subprocess.d.ts +268 -0
  11. package/dist/index.browser.d.ts +1 -0
  12. package/dist/index.browser.js +49 -0
  13. package/dist/index.d.ts +15 -0
  14. package/dist/index.js +49 -0
  15. package/dist/index.node.d.ts +5 -0
  16. package/dist/index.node.js +49 -0
  17. package/dist/mcp/index.d.ts +4 -0
  18. package/dist/mcp/policies.d.ts +14 -0
  19. package/dist/mcp/tools.d.ts +9 -0
  20. package/dist/mcp/types.d.ts +35 -0
  21. package/dist/message-builder.d.ts +31 -0
  22. package/dist/prompts/cline.d.ts +1 -0
  23. package/dist/prompts/index.d.ts +1 -0
  24. package/dist/runtime/agent-runtime-bus.d.ts +13 -0
  25. package/dist/runtime/conversation-store.d.ts +16 -0
  26. package/dist/runtime/lifecycle-orchestrator.d.ts +28 -0
  27. package/dist/runtime/tool-orchestrator.d.ts +39 -0
  28. package/dist/runtime/turn-processor.d.ts +21 -0
  29. package/dist/teams/index.d.ts +3 -0
  30. package/dist/teams/multi-agent.d.ts +566 -0
  31. package/dist/teams/spawn-agent-tool.d.ts +85 -0
  32. package/dist/teams/team-tools.d.ts +51 -0
  33. package/dist/tools/ask-question.d.ts +12 -0
  34. package/dist/tools/create.d.ts +59 -0
  35. package/dist/tools/execution.d.ts +61 -0
  36. package/dist/tools/formatting.d.ts +20 -0
  37. package/dist/tools/index.d.ts +11 -0
  38. package/dist/tools/registry.d.ts +26 -0
  39. package/dist/tools/validation.d.ts +27 -0
  40. package/dist/types.d.ts +826 -0
  41. package/package.json +54 -0
  42. package/src/agent-input.ts +116 -0
  43. package/src/agent.test.ts +931 -0
  44. package/src/agent.ts +1050 -0
  45. package/src/example.test.ts +564 -0
  46. package/src/extensions.ts +337 -0
  47. package/src/hooks/engine.test.ts +163 -0
  48. package/src/hooks/engine.ts +537 -0
  49. package/src/hooks/index.ts +6 -0
  50. package/src/hooks/lifecycle.ts +239 -0
  51. package/src/hooks/node.ts +18 -0
  52. package/src/hooks/subprocess-runner.ts +140 -0
  53. package/src/hooks/subprocess.test.ts +180 -0
  54. package/src/hooks/subprocess.ts +620 -0
  55. package/src/index.browser.ts +1 -0
  56. package/src/index.node.ts +21 -0
  57. package/src/index.ts +133 -0
  58. package/src/mcp/index.ts +17 -0
  59. package/src/mcp/policies.test.ts +51 -0
  60. package/src/mcp/policies.ts +53 -0
  61. package/src/mcp/tools.test.ts +76 -0
  62. package/src/mcp/tools.ts +60 -0
  63. package/src/mcp/types.ts +41 -0
  64. package/src/message-builder.test.ts +175 -0
  65. package/src/message-builder.ts +429 -0
  66. package/src/prompts/cline.ts +49 -0
  67. package/src/prompts/index.ts +1 -0
  68. package/src/runtime/agent-runtime-bus.ts +53 -0
  69. package/src/runtime/conversation-store.ts +61 -0
  70. package/src/runtime/lifecycle-orchestrator.ts +90 -0
  71. package/src/runtime/tool-orchestrator.ts +177 -0
  72. package/src/runtime/turn-processor.ts +250 -0
  73. package/src/streaming.test.ts +197 -0
  74. package/src/streaming.ts +307 -0
  75. package/src/teams/index.ts +63 -0
  76. package/src/teams/multi-agent.lifecycle.test.ts +48 -0
  77. package/src/teams/multi-agent.ts +1866 -0
  78. package/src/teams/spawn-agent-tool.test.ts +172 -0
  79. package/src/teams/spawn-agent-tool.ts +223 -0
  80. package/src/teams/team-tools.test.ts +448 -0
  81. package/src/teams/team-tools.ts +929 -0
  82. package/src/tools/ask-question.ts +78 -0
  83. package/src/tools/create.ts +104 -0
  84. package/src/tools/execution.ts +311 -0
  85. package/src/tools/formatting.ts +73 -0
  86. package/src/tools/index.ts +45 -0
  87. package/src/tools/registry.ts +52 -0
  88. package/src/tools/tools.test.ts +292 -0
  89. package/src/tools/validation.ts +73 -0
  90. package/src/types.ts +966 -0
@@ -0,0 +1,292 @@
1
+ import { getEventListeners } from "node:events";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { z } from "zod";
4
+ import type { PendingToolCall, Tool, ToolContext } from "../types.js";
5
+ import {
6
+ createTool,
7
+ createToolRegistry,
8
+ executeTool,
9
+ executeToolsInParallel,
10
+ executeToolsSequentially,
11
+ executeToolWithRetry,
12
+ formatToolCallRecord,
13
+ formatToolResult,
14
+ formatToolResultsSummary,
15
+ getToolNames,
16
+ validateToolDefinition,
17
+ validateToolInput,
18
+ validateTools,
19
+ } from "./index.js";
20
+
21
+ const baseContext: ToolContext = {
22
+ agentId: "agent-1",
23
+ conversationId: "conv-1",
24
+ iteration: 1,
25
+ };
26
+
27
+ describe("tools utilities", () => {
28
+ beforeEach(() => {
29
+ vi.useRealTimers();
30
+ });
31
+
32
+ it("creates tools from zod/json schema and validates definitions", () => {
33
+ const jsonTool = createTool({
34
+ name: "echo",
35
+ description: "Echo value",
36
+ inputSchema: {
37
+ type: "object",
38
+ properties: { value: { type: "string" } },
39
+ required: ["value"],
40
+ },
41
+ execute: async ({ value }: { value: string }) => value,
42
+ });
43
+
44
+ const zodTool = createTool({
45
+ name: "math",
46
+ description: "Math tool",
47
+ inputSchema: z.object({ a: z.number(), b: z.number() }),
48
+ execute: async ({ a, b }) => a + b,
49
+ });
50
+
51
+ expect(jsonTool.timeoutMs).toBe(30000);
52
+ expect(zodTool.inputSchema.type).toBe("object");
53
+ expect(validateToolDefinition(jsonTool as Tool)).toEqual({
54
+ valid: true,
55
+ errors: [],
56
+ });
57
+ });
58
+
59
+ it("validates tool collections and inputs", () => {
60
+ const tool = createTool({
61
+ name: "parse",
62
+ description: "Parse record",
63
+ inputSchema: {
64
+ type: "object",
65
+ properties: {
66
+ count: { type: "integer" },
67
+ name: { type: "string" },
68
+ },
69
+ required: ["count"],
70
+ },
71
+ execute: async () => "ok",
72
+ });
73
+
74
+ validateTools([tool]);
75
+ expect(() => validateTools([tool, tool])).toThrow(
76
+ "Duplicate tool name: parse",
77
+ );
78
+
79
+ expect(validateToolInput(tool, { count: 3, name: "x" }).valid).toBe(true);
80
+ const missingCount = validateToolInput(tool, { name: "x" });
81
+ expect(missingCount.valid).toBe(false);
82
+ expect(missingCount.error).toContain("expected number");
83
+ expect(missingCount.error).toContain("count");
84
+ const nonInteger = validateToolInput(tool, { count: 1.2 });
85
+ expect(nonInteger.valid).toBe(false);
86
+ expect(nonInteger.error).toContain("expected int");
87
+ expect(nonInteger.error).toContain("count");
88
+ });
89
+
90
+ it("builds registries and handles duplicate tool names", () => {
91
+ const a = createTool({
92
+ name: "a",
93
+ description: "a",
94
+ inputSchema: { type: "object", properties: {} },
95
+ execute: async () => "a",
96
+ });
97
+ const b = createTool({
98
+ name: "b",
99
+ description: "b",
100
+ inputSchema: { type: "object", properties: {} },
101
+ execute: async () => "b",
102
+ });
103
+
104
+ const registry = createToolRegistry([a, b]);
105
+ expect(getToolNames(registry)).toEqual(["a", "b"]);
106
+ expect(() => createToolRegistry([a, a])).toThrow("Duplicate tool name: a");
107
+ });
108
+
109
+ it("executes tools with timeout, retry, and authorization", async () => {
110
+ const transient = vi
111
+ .fn()
112
+ .mockRejectedValueOnce(new Error("transient"))
113
+ .mockResolvedValueOnce({ ok: true });
114
+ const retryTool = createTool({
115
+ name: "retry_tool",
116
+ description: "retry",
117
+ inputSchema: { type: "object", properties: {} },
118
+ execute: transient,
119
+ maxRetries: 1,
120
+ retryable: true,
121
+ });
122
+
123
+ const retryResult = await executeToolWithRetry(retryTool, {}, baseContext);
124
+ expect(retryResult.error).toBeUndefined();
125
+ expect(retryResult.output).toEqual({ ok: true });
126
+
127
+ const timeoutTool = createTool({
128
+ name: "slow_tool",
129
+ description: "slow",
130
+ inputSchema: { type: "object", properties: {} },
131
+ timeoutMs: 5,
132
+ execute: async () => {
133
+ await new Promise((resolve) => setTimeout(resolve, 50));
134
+ return "late";
135
+ },
136
+ });
137
+ const timeoutResult = await executeTool(timeoutTool, {}, baseContext);
138
+ expect(timeoutResult.error).toContain("timed out");
139
+ });
140
+
141
+ it("does not leak abort listeners across tool executions", async () => {
142
+ const controller = new AbortController();
143
+ const context: ToolContext = {
144
+ ...baseContext,
145
+ abortSignal: controller.signal,
146
+ };
147
+ const tool = createTool({
148
+ name: "noop",
149
+ description: "noop",
150
+ inputSchema: { type: "object", properties: {} },
151
+ execute: async () => "ok",
152
+ });
153
+
154
+ for (let i = 0; i < 25; i++) {
155
+ const result = await executeTool(tool, {}, context);
156
+ expect(result.error).toBeUndefined();
157
+ }
158
+
159
+ expect(getEventListeners(controller.signal, "abort")).toHaveLength(0);
160
+ });
161
+
162
+ it("executes parallel and sequential calls with observer + authorizer", async () => {
163
+ const successTool = createTool({
164
+ name: "success",
165
+ description: "ok",
166
+ inputSchema: { type: "object", properties: {} },
167
+ execute: async () => ({ ok: true }),
168
+ retryable: false,
169
+ });
170
+ const denyTool = createTool({
171
+ name: "deny",
172
+ description: "deny",
173
+ inputSchema: { type: "object", properties: {} },
174
+ execute: async () => ({ denied: false }),
175
+ retryable: false,
176
+ });
177
+
178
+ const registry = createToolRegistry([successTool, denyTool]);
179
+ const calls: PendingToolCall[] = [
180
+ { id: "1", name: "success", input: {} },
181
+ { id: "2", name: "deny", input: {} },
182
+ { id: "3", name: "missing", input: {} },
183
+ ];
184
+
185
+ const starts: string[] = [];
186
+ const ends: string[] = [];
187
+
188
+ const observer = {
189
+ onToolCallStart: async (call: PendingToolCall) => {
190
+ starts.push(call.name);
191
+ },
192
+ onToolCallEnd: async (record: { name: string }) => {
193
+ ends.push(record.name);
194
+ },
195
+ };
196
+
197
+ const authorizer = {
198
+ authorize: async (call: PendingToolCall) =>
199
+ call.name === "deny"
200
+ ? { allowed: false as const, reason: "blocked by policy" }
201
+ : { allowed: true as const },
202
+ };
203
+
204
+ const parallel = await executeToolsInParallel(
205
+ registry,
206
+ calls,
207
+ baseContext,
208
+ observer,
209
+ authorizer,
210
+ );
211
+ expect(parallel).toHaveLength(3);
212
+ expect(parallel.find((r) => r.name === "success")?.error).toBeUndefined();
213
+ expect(parallel.find((r) => r.name === "deny")?.error).toBe(
214
+ "blocked by policy",
215
+ );
216
+ expect(parallel.find((r) => r.name === "missing")?.error).toContain(
217
+ "Unknown tool",
218
+ );
219
+
220
+ const sequential = await executeToolsSequentially(
221
+ registry,
222
+ calls,
223
+ baseContext,
224
+ observer,
225
+ authorizer,
226
+ );
227
+ expect(sequential).toHaveLength(3);
228
+ expect(starts).toContain("success");
229
+ expect(ends).toContain("missing");
230
+ });
231
+
232
+ it("respects max concurrency in parallel tool execution", async () => {
233
+ let active = 0;
234
+ let maxActive = 0;
235
+ const slowTool = createTool({
236
+ name: "slow",
237
+ description: "slow",
238
+ inputSchema: { type: "object", properties: {} },
239
+ execute: async () => {
240
+ active += 1;
241
+ maxActive = Math.max(maxActive, active);
242
+ await new Promise((resolve) => setTimeout(resolve, 10));
243
+ active -= 1;
244
+ return { ok: true };
245
+ },
246
+ retryable: false,
247
+ });
248
+ const registry = createToolRegistry([slowTool]);
249
+ const calls: PendingToolCall[] = [
250
+ { id: "1", name: "slow", input: {} },
251
+ { id: "2", name: "slow", input: {} },
252
+ { id: "3", name: "slow", input: {} },
253
+ { id: "4", name: "slow", input: {} },
254
+ ];
255
+
256
+ const result = await executeToolsInParallel(
257
+ registry,
258
+ calls,
259
+ baseContext,
260
+ undefined,
261
+ undefined,
262
+ { maxConcurrency: 2 },
263
+ );
264
+
265
+ expect(result).toHaveLength(4);
266
+ expect(maxActive).toBeLessThanOrEqual(2);
267
+ });
268
+
269
+ it("formats tool output and summaries", () => {
270
+ expect(formatToolResult("hello")).toBe("hello");
271
+ expect(formatToolResult({ ok: true })).toBe('{"ok":true}');
272
+ expect(formatToolResult(undefined)).toBe("null");
273
+ expect(formatToolResult(null, "boom")).toBe('{"error":"boom"}');
274
+
275
+ const record = {
276
+ id: "call-1",
277
+ name: "echo",
278
+ input: { value: "x" },
279
+ output: "x",
280
+ durationMs: 12,
281
+ startedAt: new Date("2026-01-01T00:00:00.000Z"),
282
+ endedAt: new Date("2026-01-01T00:00:00.012Z"),
283
+ };
284
+ const summary = formatToolResultsSummary([record]);
285
+ expect(summary).toContain("echo: SUCCESS (12ms)");
286
+ expect(formatToolResultsSummary([])).toBe("No tools were called.");
287
+
288
+ const detail = formatToolCallRecord(record);
289
+ expect(detail).toContain("Tool: echo");
290
+ expect(detail).toContain("Status: SUCCESS");
291
+ });
292
+ });
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Tool Validation
3
+ *
4
+ * Functions for validating tools and tool inputs.
5
+ */
6
+
7
+ import { z } from "zod";
8
+ import type { Tool } from "../types.js";
9
+
10
+ /**
11
+ * Validate that all tools have unique names
12
+ */
13
+ export function validateTools(tools: Tool[]): void {
14
+ const names = new Set<string>();
15
+ for (const tool of tools) {
16
+ if (names.has(tool.name)) {
17
+ throw new Error(`Duplicate tool name: ${tool.name}`);
18
+ }
19
+ names.add(tool.name);
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Validate tool input against its schema (basic validation)
25
+ *
26
+ * Note: This is a simplified validation. For full JSON Schema validation,
27
+ * consider using a library like ajv.
28
+ */
29
+ export function validateToolInput(
30
+ tool: Tool,
31
+ input: unknown,
32
+ ): { valid: boolean; error?: string } {
33
+ const schema = z.fromJSONSchema(tool.inputSchema);
34
+ if (!schema) {
35
+ return { valid: false, error: "Input schema must be an object" };
36
+ }
37
+ const result = schema.safeParse(input);
38
+ if (result.success) {
39
+ return { valid: true };
40
+ }
41
+ return { valid: false, error: z.prettifyError(result.error) };
42
+ }
43
+
44
+ /**
45
+ * Validate a tool definition
46
+ */
47
+ export function validateToolDefinition(tool: Tool): {
48
+ valid: boolean;
49
+ errors: string[];
50
+ } {
51
+ const errors: string[] = [];
52
+
53
+ if (!tool.name || typeof tool.name !== "string") {
54
+ errors.push("Tool must have a valid name");
55
+ }
56
+
57
+ if (!tool.description || typeof tool.description !== "string") {
58
+ errors.push("Tool must have a description");
59
+ }
60
+
61
+ if (!tool.inputSchema || tool.inputSchema.type !== "object") {
62
+ errors.push("Tool must have an inputSchema with type 'object'");
63
+ }
64
+
65
+ if (typeof tool.execute !== "function") {
66
+ errors.push("Tool must have an execute function");
67
+ }
68
+
69
+ return {
70
+ valid: errors.length === 0,
71
+ errors,
72
+ };
73
+ }