@johpaz/hive 1.1.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/CONTRIBUTING.md +44 -0
- package/README.md +310 -0
- package/package.json +96 -0
- package/packages/cli/package.json +28 -0
- package/packages/cli/src/commands/agent-run.ts +168 -0
- package/packages/cli/src/commands/agents.ts +398 -0
- package/packages/cli/src/commands/chat.ts +142 -0
- package/packages/cli/src/commands/config.ts +50 -0
- package/packages/cli/src/commands/cron.ts +161 -0
- package/packages/cli/src/commands/dev.ts +95 -0
- package/packages/cli/src/commands/doctor.ts +133 -0
- package/packages/cli/src/commands/gateway.ts +443 -0
- package/packages/cli/src/commands/logs.ts +57 -0
- package/packages/cli/src/commands/mcp.ts +175 -0
- package/packages/cli/src/commands/message.ts +77 -0
- package/packages/cli/src/commands/onboard.ts +1868 -0
- package/packages/cli/src/commands/security.ts +144 -0
- package/packages/cli/src/commands/service.ts +50 -0
- package/packages/cli/src/commands/sessions.ts +116 -0
- package/packages/cli/src/commands/skills.ts +187 -0
- package/packages/cli/src/commands/update.ts +25 -0
- package/packages/cli/src/index.ts +185 -0
- package/packages/cli/src/utils/token.ts +6 -0
- package/packages/code-bridge/README.md +78 -0
- package/packages/code-bridge/package.json +18 -0
- package/packages/code-bridge/src/index.ts +95 -0
- package/packages/code-bridge/src/process-manager.ts +212 -0
- package/packages/code-bridge/src/schemas.ts +133 -0
- package/packages/core/package.json +46 -0
- package/packages/core/src/agent/agent-loop.ts +369 -0
- package/packages/core/src/agent/compaction.ts +140 -0
- package/packages/core/src/agent/context-compiler.ts +378 -0
- package/packages/core/src/agent/context-guard.ts +91 -0
- package/packages/core/src/agent/context.ts +138 -0
- package/packages/core/src/agent/conversation-store.ts +198 -0
- package/packages/core/src/agent/curator.ts +158 -0
- package/packages/core/src/agent/hooks.ts +166 -0
- package/packages/core/src/agent/index.ts +116 -0
- package/packages/core/src/agent/llm-client.ts +503 -0
- package/packages/core/src/agent/native-tools.ts +505 -0
- package/packages/core/src/agent/prompt-builder.ts +532 -0
- package/packages/core/src/agent/providers/index.ts +167 -0
- package/packages/core/src/agent/providers.ts +1 -0
- package/packages/core/src/agent/reflector.ts +170 -0
- package/packages/core/src/agent/service.ts +64 -0
- package/packages/core/src/agent/stuck-loop.ts +133 -0
- package/packages/core/src/agent/supervisor.ts +39 -0
- package/packages/core/src/agent/tracer.ts +102 -0
- package/packages/core/src/agent/workspace.ts +110 -0
- package/packages/core/src/canvas/canvas-manager.test.ts +161 -0
- package/packages/core/src/canvas/canvas-manager.ts +319 -0
- package/packages/core/src/canvas/canvas-tools.ts +420 -0
- package/packages/core/src/canvas/emitter.ts +115 -0
- package/packages/core/src/canvas/index.ts +2 -0
- package/packages/core/src/channels/base.ts +138 -0
- package/packages/core/src/channels/discord.ts +260 -0
- package/packages/core/src/channels/index.ts +7 -0
- package/packages/core/src/channels/manager.ts +383 -0
- package/packages/core/src/channels/slack.ts +287 -0
- package/packages/core/src/channels/telegram.ts +502 -0
- package/packages/core/src/channels/webchat.ts +128 -0
- package/packages/core/src/channels/whatsapp.ts +375 -0
- package/packages/core/src/config/index.ts +12 -0
- package/packages/core/src/config/loader.ts +529 -0
- package/packages/core/src/events/event-bus.ts +169 -0
- package/packages/core/src/gateway/index.ts +5 -0
- package/packages/core/src/gateway/initializer.ts +290 -0
- package/packages/core/src/gateway/lane-queue.ts +169 -0
- package/packages/core/src/gateway/resolver.ts +108 -0
- package/packages/core/src/gateway/router.ts +124 -0
- package/packages/core/src/gateway/server.ts +3317 -0
- package/packages/core/src/gateway/session.ts +95 -0
- package/packages/core/src/gateway/slash-commands.ts +192 -0
- package/packages/core/src/heartbeat/index.ts +157 -0
- package/packages/core/src/index.ts +19 -0
- package/packages/core/src/integrations/catalog.ts +286 -0
- package/packages/core/src/integrations/env.ts +64 -0
- package/packages/core/src/integrations/index.ts +2 -0
- package/packages/core/src/memory/index.ts +1 -0
- package/packages/core/src/memory/notes.ts +68 -0
- package/packages/core/src/plugins/api.ts +128 -0
- package/packages/core/src/plugins/index.ts +2 -0
- package/packages/core/src/plugins/loader.ts +365 -0
- package/packages/core/src/resilience/circuit-breaker.ts +225 -0
- package/packages/core/src/security/google-chat.ts +269 -0
- package/packages/core/src/security/index.ts +192 -0
- package/packages/core/src/security/pairing.ts +250 -0
- package/packages/core/src/security/rate-limit.ts +270 -0
- package/packages/core/src/security/signal.ts +321 -0
- package/packages/core/src/state/store.ts +312 -0
- package/packages/core/src/storage/bun-sqlite-store.ts +188 -0
- package/packages/core/src/storage/crypto.ts +101 -0
- package/packages/core/src/storage/db-context.ts +333 -0
- package/packages/core/src/storage/onboarding.ts +1087 -0
- package/packages/core/src/storage/schema.ts +541 -0
- package/packages/core/src/storage/seed.ts +571 -0
- package/packages/core/src/storage/sqlite.ts +387 -0
- package/packages/core/src/storage/usage.ts +212 -0
- package/packages/core/src/tools/bridge-events.ts +74 -0
- package/packages/core/src/tools/browser.ts +275 -0
- package/packages/core/src/tools/codebridge.ts +421 -0
- package/packages/core/src/tools/coordinator-tools.ts +179 -0
- package/packages/core/src/tools/cron.ts +611 -0
- package/packages/core/src/tools/exec.ts +140 -0
- package/packages/core/src/tools/fs.ts +364 -0
- package/packages/core/src/tools/index.ts +12 -0
- package/packages/core/src/tools/memory.ts +176 -0
- package/packages/core/src/tools/notify.ts +113 -0
- package/packages/core/src/tools/project-management.ts +376 -0
- package/packages/core/src/tools/project.ts +375 -0
- package/packages/core/src/tools/read.ts +158 -0
- package/packages/core/src/tools/web.ts +436 -0
- package/packages/core/src/tools/workspace.ts +171 -0
- package/packages/core/src/utils/benchmark.ts +80 -0
- package/packages/core/src/utils/crypto.ts +73 -0
- package/packages/core/src/utils/date.ts +42 -0
- package/packages/core/src/utils/index.ts +4 -0
- package/packages/core/src/utils/logger.ts +388 -0
- package/packages/core/src/utils/retry.ts +70 -0
- package/packages/core/src/voice/index.ts +583 -0
- package/packages/core/tsconfig.json +9 -0
- package/packages/mcp/package.json +26 -0
- package/packages/mcp/src/config.ts +13 -0
- package/packages/mcp/src/index.ts +1 -0
- package/packages/mcp/src/logger.ts +42 -0
- package/packages/mcp/src/manager.ts +434 -0
- package/packages/mcp/src/transports/index.ts +67 -0
- package/packages/mcp/src/transports/sse.ts +241 -0
- package/packages/mcp/src/transports/websocket.ts +159 -0
- package/packages/skills/package.json +21 -0
- package/packages/skills/src/bundled/agent_management/SKILL.md +24 -0
- package/packages/skills/src/bundled/browser_automation/SKILL.md +30 -0
- package/packages/skills/src/bundled/context_compact/SKILL.md +35 -0
- package/packages/skills/src/bundled/cron_manager/SKILL.md +52 -0
- package/packages/skills/src/bundled/file_manager/SKILL.md +76 -0
- package/packages/skills/src/bundled/http_client/SKILL.md +24 -0
- package/packages/skills/src/bundled/memory/SKILL.md +42 -0
- package/packages/skills/src/bundled/project_management/SKILL.md +26 -0
- package/packages/skills/src/bundled/shell/SKILL.md +43 -0
- package/packages/skills/src/bundled/system_notify/SKILL.md +52 -0
- package/packages/skills/src/bundled/voice/SKILL.md +25 -0
- package/packages/skills/src/bundled/web_search/SKILL.md +29 -0
- package/packages/skills/src/index.ts +1 -0
- package/packages/skills/src/loader.ts +282 -0
- package/packages/tools/package.json +43 -0
- package/packages/tools/src/browser/browser.test.ts +111 -0
- package/packages/tools/src/browser/index.ts +272 -0
- package/packages/tools/src/canvas/index.ts +220 -0
- package/packages/tools/src/cron/cron.test.ts +164 -0
- package/packages/tools/src/cron/index.ts +304 -0
- package/packages/tools/src/filesystem/filesystem.test.ts +240 -0
- package/packages/tools/src/filesystem/index.ts +379 -0
- package/packages/tools/src/git/index.ts +239 -0
- package/packages/tools/src/index.ts +4 -0
- package/packages/tools/src/shell/detect-env.ts +70 -0
- package/packages/tools/tsconfig.json +9 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import type { Tool } from "@johpaz/hive-core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export interface BrowserConfig {
|
|
5
|
+
headless?: boolean;
|
|
6
|
+
timeout?: number;
|
|
7
|
+
userAgent?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type ZodObjectSchema = z.ZodObject<z.ZodRawShape>;
|
|
11
|
+
|
|
12
|
+
const createBrowserTool = <T extends ZodObjectSchema>(
|
|
13
|
+
name: string,
|
|
14
|
+
description: string,
|
|
15
|
+
paramsSchema: T,
|
|
16
|
+
execute: (params: z.infer<T>, config: BrowserConfig) => Promise<unknown>
|
|
17
|
+
): Tool => {
|
|
18
|
+
const shape = paramsSchema.shape;
|
|
19
|
+
return {
|
|
20
|
+
name,
|
|
21
|
+
description,
|
|
22
|
+
parameters: {
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: Object.fromEntries(
|
|
25
|
+
Object.entries(shape).map(([key, schema]) => [
|
|
26
|
+
key,
|
|
27
|
+
{
|
|
28
|
+
type: schema instanceof z.ZodString
|
|
29
|
+
? "string"
|
|
30
|
+
: schema instanceof z.ZodNumber
|
|
31
|
+
? "number"
|
|
32
|
+
: schema instanceof z.ZodBoolean
|
|
33
|
+
? "boolean"
|
|
34
|
+
: schema instanceof z.ZodArray
|
|
35
|
+
? "array"
|
|
36
|
+
: "object",
|
|
37
|
+
description: ((schema as unknown as { _def?: { description?: string } })._def?.description) || key,
|
|
38
|
+
},
|
|
39
|
+
])
|
|
40
|
+
),
|
|
41
|
+
required: Object.entries(shape)
|
|
42
|
+
.filter(([_, schema]) => !(schema instanceof z.ZodOptional))
|
|
43
|
+
.map(([key]) => key),
|
|
44
|
+
},
|
|
45
|
+
execute: async (params: Record<string, unknown>) => {
|
|
46
|
+
const parsed = paramsSchema.safeParse(params);
|
|
47
|
+
if (!parsed.success) {
|
|
48
|
+
throw new Error(`Invalid parameters: ${parsed.error.message}`);
|
|
49
|
+
}
|
|
50
|
+
return execute(parsed.data, {});
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export function createBrowserNavigateTool(config: BrowserConfig = {}): Tool {
|
|
56
|
+
return createBrowserTool(
|
|
57
|
+
"browser_navigate",
|
|
58
|
+
"Navigate to a URL and retrieve the page content",
|
|
59
|
+
z.object({
|
|
60
|
+
url: z.string().url().describe("The URL to navigate to"),
|
|
61
|
+
waitFor: z.string().optional().describe("CSS selector to wait for before returning"),
|
|
62
|
+
timeout: z.number().optional().describe("Timeout in milliseconds"),
|
|
63
|
+
}),
|
|
64
|
+
async (params, cfg) => {
|
|
65
|
+
const timeout = params.timeout || cfg.timeout || 30000;
|
|
66
|
+
|
|
67
|
+
const controller = new AbortController();
|
|
68
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const response = await fetch(params.url, {
|
|
72
|
+
signal: controller.signal,
|
|
73
|
+
headers: {
|
|
74
|
+
"User-Agent": cfg.userAgent || "HiveBot/1.0",
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
if (!response.ok) {
|
|
79
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const contentType = response.headers.get("content-type") || "";
|
|
83
|
+
let content: string;
|
|
84
|
+
|
|
85
|
+
if (contentType.includes("application/json")) {
|
|
86
|
+
const json = await response.json();
|
|
87
|
+
content = JSON.stringify(json, null, 2);
|
|
88
|
+
} else {
|
|
89
|
+
content = await response.text();
|
|
90
|
+
|
|
91
|
+
content = content
|
|
92
|
+
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "")
|
|
93
|
+
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "")
|
|
94
|
+
.replace(/<[^>]+>/g, " ")
|
|
95
|
+
.replace(/\s+/g, " ")
|
|
96
|
+
.trim();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
success: true,
|
|
101
|
+
url: params.url,
|
|
102
|
+
finalUrl: response.url,
|
|
103
|
+
title: extractTitle(content),
|
|
104
|
+
content: content.slice(0, 50000),
|
|
105
|
+
contentType,
|
|
106
|
+
};
|
|
107
|
+
} finally {
|
|
108
|
+
clearTimeout(timeoutId);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function createBrowserFetchTool(config: BrowserConfig = {}): Tool {
|
|
115
|
+
return createBrowserTool(
|
|
116
|
+
"browser_fetch",
|
|
117
|
+
"Fetch content from a URL with custom options",
|
|
118
|
+
z.object({
|
|
119
|
+
url: z.string().url().describe("The URL to fetch"),
|
|
120
|
+
method: z.enum(["GET", "POST", "PUT", "DELETE"]).optional().default("GET"),
|
|
121
|
+
headers: z.record(z.string(), z.string()).optional().describe("Custom headers"),
|
|
122
|
+
body: z.string().optional().describe("Request body for POST/PUT"),
|
|
123
|
+
timeout: z.number().optional().describe("Timeout in milliseconds"),
|
|
124
|
+
}),
|
|
125
|
+
async (params, cfg) => {
|
|
126
|
+
const timeout = params.timeout || cfg.timeout || 30000;
|
|
127
|
+
|
|
128
|
+
const controller = new AbortController();
|
|
129
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const response = await fetch(params.url, {
|
|
133
|
+
method: params.method,
|
|
134
|
+
headers: {
|
|
135
|
+
"User-Agent": cfg.userAgent || "HiveBot/1.0",
|
|
136
|
+
...params.headers,
|
|
137
|
+
},
|
|
138
|
+
body: params.body,
|
|
139
|
+
signal: controller.signal,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if (!response.ok) {
|
|
143
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const contentType = response.headers.get("content-type") || "";
|
|
147
|
+
let data: unknown;
|
|
148
|
+
|
|
149
|
+
if (contentType.includes("application/json")) {
|
|
150
|
+
data = await response.json();
|
|
151
|
+
} else {
|
|
152
|
+
data = await response.text();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
success: true,
|
|
157
|
+
status: response.status,
|
|
158
|
+
statusText: response.statusText,
|
|
159
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
160
|
+
data,
|
|
161
|
+
};
|
|
162
|
+
} finally {
|
|
163
|
+
clearTimeout(timeoutId);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function createBrowserScreenshotTool(_config: BrowserConfig = {}): Tool {
|
|
170
|
+
return {
|
|
171
|
+
name: "browser_screenshot",
|
|
172
|
+
description: "Take a screenshot of a webpage (returns placeholder - requires puppeteer integration)",
|
|
173
|
+
parameters: {
|
|
174
|
+
type: "object",
|
|
175
|
+
properties: {
|
|
176
|
+
url: {
|
|
177
|
+
type: "string",
|
|
178
|
+
description: "The URL to screenshot",
|
|
179
|
+
},
|
|
180
|
+
width: {
|
|
181
|
+
type: "number",
|
|
182
|
+
description: "Viewport width (default: 1280)",
|
|
183
|
+
},
|
|
184
|
+
height: {
|
|
185
|
+
type: "number",
|
|
186
|
+
description: "Viewport height (default: 720)",
|
|
187
|
+
},
|
|
188
|
+
fullPage: {
|
|
189
|
+
type: "boolean",
|
|
190
|
+
description: "Capture full page (default: false)",
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
required: ["url"],
|
|
194
|
+
},
|
|
195
|
+
execute: async (params: Record<string, unknown>) => {
|
|
196
|
+
return {
|
|
197
|
+
success: false,
|
|
198
|
+
error: "Screenshot requires puppeteer/playwright integration. Use browser_navigate to get page content instead.",
|
|
199
|
+
url: params.url,
|
|
200
|
+
note: "Install puppeteer and implement headless browser capture for screenshots.",
|
|
201
|
+
};
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function createBrowserClickTool(_config: BrowserConfig = {}): Tool {
|
|
207
|
+
return {
|
|
208
|
+
name: "browser_click",
|
|
209
|
+
description: "Click an element on a page (requires puppeteer integration)",
|
|
210
|
+
parameters: {
|
|
211
|
+
type: "object",
|
|
212
|
+
properties: {
|
|
213
|
+
selector: {
|
|
214
|
+
type: "string",
|
|
215
|
+
description: "CSS selector for the element to click",
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
required: ["selector"],
|
|
219
|
+
},
|
|
220
|
+
execute: async (params: Record<string, unknown>) => {
|
|
221
|
+
return {
|
|
222
|
+
success: false,
|
|
223
|
+
error: "Click interaction requires puppeteer/playwright integration.",
|
|
224
|
+
selector: params.selector,
|
|
225
|
+
};
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function createBrowserTypeTool(_config: BrowserConfig = {}): Tool {
|
|
231
|
+
return {
|
|
232
|
+
name: "browser_type",
|
|
233
|
+
description: "Type text into an input field (requires puppeteer integration)",
|
|
234
|
+
parameters: {
|
|
235
|
+
type: "object",
|
|
236
|
+
properties: {
|
|
237
|
+
selector: {
|
|
238
|
+
type: "string",
|
|
239
|
+
description: "CSS selector for the input field",
|
|
240
|
+
},
|
|
241
|
+
text: {
|
|
242
|
+
type: "string",
|
|
243
|
+
description: "Text to type",
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
required: ["selector", "text"],
|
|
247
|
+
},
|
|
248
|
+
execute: async (params: Record<string, unknown>) => {
|
|
249
|
+
return {
|
|
250
|
+
success: false,
|
|
251
|
+
error: "Type interaction requires puppeteer/playwright integration.",
|
|
252
|
+
selector: params.selector,
|
|
253
|
+
text: params.text,
|
|
254
|
+
};
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function extractTitle(content: string): string {
|
|
260
|
+
const match = content.match(/<title[^>]*>([^<]+)<\/title>/i);
|
|
261
|
+
return match ? match[1]!.trim() : "";
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function createBrowserTools(config: BrowserConfig = {}): Tool[] {
|
|
265
|
+
return [
|
|
266
|
+
createBrowserNavigateTool(config),
|
|
267
|
+
createBrowserFetchTool(config),
|
|
268
|
+
createBrowserScreenshotTool(config),
|
|
269
|
+
createBrowserClickTool(config),
|
|
270
|
+
createBrowserTypeTool(config),
|
|
271
|
+
];
|
|
272
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import type { Tool } from "@johpaz/hive-core";
|
|
2
|
+
import { canvasManager, type CanvasComponent } from "@johpaz/hive-core/canvas";
|
|
3
|
+
|
|
4
|
+
export interface CanvasToolConfig {
|
|
5
|
+
defaultTimeout?: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function createCanvasRenderTool(_config: CanvasToolConfig = {}): Tool {
|
|
9
|
+
return {
|
|
10
|
+
name: "canvas_render",
|
|
11
|
+
description: "Render a component on the user's canvas",
|
|
12
|
+
parameters: {
|
|
13
|
+
type: "object",
|
|
14
|
+
properties: {
|
|
15
|
+
sessionId: {
|
|
16
|
+
type: "string",
|
|
17
|
+
description: "Session ID to render to",
|
|
18
|
+
},
|
|
19
|
+
component: {
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {
|
|
22
|
+
id: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description: "Unique component ID",
|
|
25
|
+
},
|
|
26
|
+
type: {
|
|
27
|
+
type: "string",
|
|
28
|
+
enum: ["button", "form", "chart", "table", "markdown", "text", "image"],
|
|
29
|
+
description: "Component type",
|
|
30
|
+
},
|
|
31
|
+
props: {
|
|
32
|
+
type: "object",
|
|
33
|
+
description: "Component properties",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
required: ["id", "type", "props"],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
required: ["sessionId", "component"],
|
|
40
|
+
},
|
|
41
|
+
execute: async (params: Record<string, unknown>) => {
|
|
42
|
+
const sessionId = params.sessionId as string;
|
|
43
|
+
const component = params.component as CanvasComponent;
|
|
44
|
+
|
|
45
|
+
await canvasManager.render(sessionId, component);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
success: true,
|
|
49
|
+
componentId: component.id,
|
|
50
|
+
sessionId,
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function createCanvasAskTool(config: CanvasToolConfig = {}): Tool {
|
|
57
|
+
return {
|
|
58
|
+
name: "canvas_ask",
|
|
59
|
+
description: "Display a form and wait for user response",
|
|
60
|
+
parameters: {
|
|
61
|
+
type: "object",
|
|
62
|
+
properties: {
|
|
63
|
+
sessionId: {
|
|
64
|
+
type: "string",
|
|
65
|
+
description: "Session ID",
|
|
66
|
+
},
|
|
67
|
+
title: {
|
|
68
|
+
type: "string",
|
|
69
|
+
description: "Form title",
|
|
70
|
+
},
|
|
71
|
+
fields: {
|
|
72
|
+
type: "array",
|
|
73
|
+
items: {
|
|
74
|
+
type: "object",
|
|
75
|
+
properties: {
|
|
76
|
+
name: { type: "string" },
|
|
77
|
+
label: { type: "string" },
|
|
78
|
+
type: { type: "string", enum: ["text", "email", "textarea", "select"] },
|
|
79
|
+
required: { type: "boolean" },
|
|
80
|
+
options: {
|
|
81
|
+
type: "array",
|
|
82
|
+
items: {
|
|
83
|
+
type: "object",
|
|
84
|
+
properties: {
|
|
85
|
+
label: { type: "string" },
|
|
86
|
+
value: { type: "string" },
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
required: ["name", "label", "type"],
|
|
92
|
+
},
|
|
93
|
+
description: "Form fields",
|
|
94
|
+
},
|
|
95
|
+
timeout: {
|
|
96
|
+
type: "number",
|
|
97
|
+
description: `Timeout in milliseconds (default: ${config.defaultTimeout || 300000})`,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
required: ["sessionId", "fields"],
|
|
101
|
+
},
|
|
102
|
+
execute: async (params: Record<string, unknown>) => {
|
|
103
|
+
const sessionId = params.sessionId as string;
|
|
104
|
+
const title = (params.title as string) ?? "Form";
|
|
105
|
+
const fields = params.fields as Array<{
|
|
106
|
+
name: string;
|
|
107
|
+
label: string;
|
|
108
|
+
type: string;
|
|
109
|
+
required?: boolean;
|
|
110
|
+
options?: Array<{ label: string; value: string }>;
|
|
111
|
+
}>;
|
|
112
|
+
const timeout = (params.timeout as number) ?? config.defaultTimeout ?? 300000;
|
|
113
|
+
|
|
114
|
+
const formId = `form-${Date.now()}`;
|
|
115
|
+
|
|
116
|
+
await canvasManager.render(sessionId, {
|
|
117
|
+
id: formId,
|
|
118
|
+
type: "form",
|
|
119
|
+
props: { title, fields },
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const response = await canvasManager.waitForInteraction(sessionId, formId, timeout);
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
success: true,
|
|
127
|
+
formId,
|
|
128
|
+
data: response,
|
|
129
|
+
};
|
|
130
|
+
} catch (error) {
|
|
131
|
+
return {
|
|
132
|
+
success: false,
|
|
133
|
+
formId,
|
|
134
|
+
error: (error as Error).message,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function createCanvasClearTool(_config: CanvasToolConfig = {}): Tool {
|
|
142
|
+
return {
|
|
143
|
+
name: "canvas_clear",
|
|
144
|
+
description: "Clear the canvas for a session",
|
|
145
|
+
parameters: {
|
|
146
|
+
type: "object",
|
|
147
|
+
properties: {
|
|
148
|
+
sessionId: {
|
|
149
|
+
type: "string",
|
|
150
|
+
description: "Session ID to clear",
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
required: ["sessionId"],
|
|
154
|
+
},
|
|
155
|
+
execute: async (params: Record<string, unknown>) => {
|
|
156
|
+
const sessionId = params.sessionId as string;
|
|
157
|
+
|
|
158
|
+
await canvasManager.clear(sessionId);
|
|
159
|
+
|
|
160
|
+
return { success: true, sessionId };
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function createCanvasUpdateTool(_config: CanvasToolConfig = {}): Tool {
|
|
166
|
+
return {
|
|
167
|
+
name: "canvas_update",
|
|
168
|
+
description: "Update an existing component on the canvas",
|
|
169
|
+
parameters: {
|
|
170
|
+
type: "object",
|
|
171
|
+
properties: {
|
|
172
|
+
sessionId: {
|
|
173
|
+
type: "string",
|
|
174
|
+
description: "Session ID",
|
|
175
|
+
},
|
|
176
|
+
component: {
|
|
177
|
+
type: "object",
|
|
178
|
+
properties: {
|
|
179
|
+
id: {
|
|
180
|
+
type: "string",
|
|
181
|
+
description: "Component ID to update",
|
|
182
|
+
},
|
|
183
|
+
type: {
|
|
184
|
+
type: "string",
|
|
185
|
+
enum: ["button", "form", "chart", "table", "markdown", "text", "image"],
|
|
186
|
+
description: "Component type",
|
|
187
|
+
},
|
|
188
|
+
props: {
|
|
189
|
+
type: "object",
|
|
190
|
+
description: "Updated properties",
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
required: ["id"],
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
required: ["sessionId", "component"],
|
|
197
|
+
},
|
|
198
|
+
execute: async (params: Record<string, unknown>) => {
|
|
199
|
+
const sessionId = params.sessionId as string;
|
|
200
|
+
const component = params.component as Partial<CanvasComponent> & { id: string };
|
|
201
|
+
|
|
202
|
+
await canvasManager.update(sessionId, component as CanvasComponent);
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
success: true,
|
|
206
|
+
componentId: component.id,
|
|
207
|
+
sessionId,
|
|
208
|
+
};
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function createCanvasTools(config: CanvasToolConfig = {}): Tool[] {
|
|
214
|
+
return [
|
|
215
|
+
createCanvasRenderTool(config),
|
|
216
|
+
createCanvasAskTool(config),
|
|
217
|
+
createCanvasClearTool(config),
|
|
218
|
+
createCanvasUpdateTool(config),
|
|
219
|
+
];
|
|
220
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
createCronAddTool,
|
|
4
|
+
createCronListTool,
|
|
5
|
+
createCronRemoveTool,
|
|
6
|
+
createCronEditTool,
|
|
7
|
+
createCronTools,
|
|
8
|
+
getAllCronJobs,
|
|
9
|
+
clearAllCronJobs,
|
|
10
|
+
} from "./index";
|
|
11
|
+
|
|
12
|
+
describe("Cron Tools", () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
clearAllCronJobs();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
clearAllCronJobs();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe("createCronAddTool", () => {
|
|
22
|
+
it("creates tool with correct name", () => {
|
|
23
|
+
const tool = createCronAddTool();
|
|
24
|
+
expect(tool.name).toBe("cron_add");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("adds a cron job", async () => {
|
|
28
|
+
const tool = createCronAddTool();
|
|
29
|
+
const result = await tool.execute({
|
|
30
|
+
expression: "0 9 * * *",
|
|
31
|
+
task: "Daily reminder",
|
|
32
|
+
}) as { success: boolean; jobId: string };
|
|
33
|
+
|
|
34
|
+
expect(result.success).toBe(true);
|
|
35
|
+
expect(result.jobId).toMatch(/^cron-\d+$/);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("rejects invalid cron expression", async () => {
|
|
39
|
+
const tool = createCronAddTool();
|
|
40
|
+
await expect(
|
|
41
|
+
tool.execute({
|
|
42
|
+
expression: "invalid",
|
|
43
|
+
task: "Test task",
|
|
44
|
+
})
|
|
45
|
+
).rejects.toThrow("Cron expression must have 5 fields");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("rejects when max jobs reached", async () => {
|
|
49
|
+
const tool = createCronAddTool({ maxJobs: 1 });
|
|
50
|
+
await tool.execute({
|
|
51
|
+
expression: "* * * * *",
|
|
52
|
+
task: "Task 1",
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
await expect(
|
|
56
|
+
tool.execute({
|
|
57
|
+
expression: "* * * * *",
|
|
58
|
+
task: "Task 2",
|
|
59
|
+
})
|
|
60
|
+
).rejects.toThrow("Maximum number of cron jobs reached");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("createCronListTool", () => {
|
|
65
|
+
it("creates tool with correct name", () => {
|
|
66
|
+
const tool = createCronListTool();
|
|
67
|
+
expect(tool.name).toBe("cron_list");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("lists all cron jobs", async () => {
|
|
71
|
+
const addTool = createCronAddTool();
|
|
72
|
+
await addTool.execute({
|
|
73
|
+
expression: "* * * * *",
|
|
74
|
+
task: "Task 1",
|
|
75
|
+
});
|
|
76
|
+
await addTool.execute({
|
|
77
|
+
expression: "0 0 * * *",
|
|
78
|
+
task: "Task 2",
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const listTool = createCronListTool();
|
|
82
|
+
const result = await listTool.execute({}) as { success: boolean; jobs: unknown[]; count: number };
|
|
83
|
+
|
|
84
|
+
expect(result.success).toBe(true);
|
|
85
|
+
expect(result.count).toBe(2);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("createCronRemoveTool", () => {
|
|
90
|
+
it("creates tool with correct name", () => {
|
|
91
|
+
const tool = createCronRemoveTool();
|
|
92
|
+
expect(tool.name).toBe("cron_remove");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("removes a cron job", async () => {
|
|
96
|
+
const addTool = createCronAddTool();
|
|
97
|
+
const addResult = await addTool.execute({
|
|
98
|
+
expression: "* * * * *",
|
|
99
|
+
task: "To remove",
|
|
100
|
+
}) as { jobId: string };
|
|
101
|
+
|
|
102
|
+
const removeTool = createCronRemoveTool();
|
|
103
|
+
const result = await removeTool.execute({
|
|
104
|
+
jobId: addResult.jobId,
|
|
105
|
+
}) as { success: boolean; removedJob: string };
|
|
106
|
+
|
|
107
|
+
expect(result.success).toBe(true);
|
|
108
|
+
expect(result.removedJob).toBe(addResult.jobId);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("throws for non-existent job", async () => {
|
|
112
|
+
const tool = createCronRemoveTool();
|
|
113
|
+
await expect(
|
|
114
|
+
tool.execute({ jobId: "non-existent" })
|
|
115
|
+
).rejects.toThrow("Job not found");
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("createCronEditTool", () => {
|
|
120
|
+
it("creates tool with correct name", () => {
|
|
121
|
+
const tool = createCronEditTool();
|
|
122
|
+
expect(tool.name).toBe("cron_edit");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("edits a cron job", async () => {
|
|
126
|
+
const addTool = createCronAddTool();
|
|
127
|
+
const addResult = await addTool.execute({
|
|
128
|
+
expression: "* * * * *",
|
|
129
|
+
task: "Original",
|
|
130
|
+
}) as { jobId: string };
|
|
131
|
+
|
|
132
|
+
const editTool = createCronEditTool();
|
|
133
|
+
const result = await editTool.execute({
|
|
134
|
+
jobId: addResult.jobId,
|
|
135
|
+
task: "Updated",
|
|
136
|
+
enabled: false,
|
|
137
|
+
}) as { success: boolean; job: { task: string; enabled: boolean } };
|
|
138
|
+
|
|
139
|
+
expect(result.success).toBe(true);
|
|
140
|
+
expect(result.job.task).toBe("Updated");
|
|
141
|
+
expect(result.job.enabled).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("throws for non-existent job", async () => {
|
|
145
|
+
const tool = createCronEditTool();
|
|
146
|
+
await expect(
|
|
147
|
+
tool.execute({ jobId: "non-existent" })
|
|
148
|
+
).rejects.toThrow("Job not found");
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("createCronTools", () => {
|
|
153
|
+
it("returns all cron tools", () => {
|
|
154
|
+
const tools = createCronTools();
|
|
155
|
+
expect(tools.length).toBe(4);
|
|
156
|
+
expect(tools.map((t) => t.name)).toEqual([
|
|
157
|
+
"cron_add",
|
|
158
|
+
"cron_list",
|
|
159
|
+
"cron_remove",
|
|
160
|
+
"cron_edit",
|
|
161
|
+
]);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|