@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.
Files changed (156) hide show
  1. package/CONTRIBUTING.md +44 -0
  2. package/README.md +310 -0
  3. package/package.json +96 -0
  4. package/packages/cli/package.json +28 -0
  5. package/packages/cli/src/commands/agent-run.ts +168 -0
  6. package/packages/cli/src/commands/agents.ts +398 -0
  7. package/packages/cli/src/commands/chat.ts +142 -0
  8. package/packages/cli/src/commands/config.ts +50 -0
  9. package/packages/cli/src/commands/cron.ts +161 -0
  10. package/packages/cli/src/commands/dev.ts +95 -0
  11. package/packages/cli/src/commands/doctor.ts +133 -0
  12. package/packages/cli/src/commands/gateway.ts +443 -0
  13. package/packages/cli/src/commands/logs.ts +57 -0
  14. package/packages/cli/src/commands/mcp.ts +175 -0
  15. package/packages/cli/src/commands/message.ts +77 -0
  16. package/packages/cli/src/commands/onboard.ts +1868 -0
  17. package/packages/cli/src/commands/security.ts +144 -0
  18. package/packages/cli/src/commands/service.ts +50 -0
  19. package/packages/cli/src/commands/sessions.ts +116 -0
  20. package/packages/cli/src/commands/skills.ts +187 -0
  21. package/packages/cli/src/commands/update.ts +25 -0
  22. package/packages/cli/src/index.ts +185 -0
  23. package/packages/cli/src/utils/token.ts +6 -0
  24. package/packages/code-bridge/README.md +78 -0
  25. package/packages/code-bridge/package.json +18 -0
  26. package/packages/code-bridge/src/index.ts +95 -0
  27. package/packages/code-bridge/src/process-manager.ts +212 -0
  28. package/packages/code-bridge/src/schemas.ts +133 -0
  29. package/packages/core/package.json +46 -0
  30. package/packages/core/src/agent/agent-loop.ts +369 -0
  31. package/packages/core/src/agent/compaction.ts +140 -0
  32. package/packages/core/src/agent/context-compiler.ts +378 -0
  33. package/packages/core/src/agent/context-guard.ts +91 -0
  34. package/packages/core/src/agent/context.ts +138 -0
  35. package/packages/core/src/agent/conversation-store.ts +198 -0
  36. package/packages/core/src/agent/curator.ts +158 -0
  37. package/packages/core/src/agent/hooks.ts +166 -0
  38. package/packages/core/src/agent/index.ts +116 -0
  39. package/packages/core/src/agent/llm-client.ts +503 -0
  40. package/packages/core/src/agent/native-tools.ts +505 -0
  41. package/packages/core/src/agent/prompt-builder.ts +532 -0
  42. package/packages/core/src/agent/providers/index.ts +167 -0
  43. package/packages/core/src/agent/providers.ts +1 -0
  44. package/packages/core/src/agent/reflector.ts +170 -0
  45. package/packages/core/src/agent/service.ts +64 -0
  46. package/packages/core/src/agent/stuck-loop.ts +133 -0
  47. package/packages/core/src/agent/supervisor.ts +39 -0
  48. package/packages/core/src/agent/tracer.ts +102 -0
  49. package/packages/core/src/agent/workspace.ts +110 -0
  50. package/packages/core/src/canvas/canvas-manager.test.ts +161 -0
  51. package/packages/core/src/canvas/canvas-manager.ts +319 -0
  52. package/packages/core/src/canvas/canvas-tools.ts +420 -0
  53. package/packages/core/src/canvas/emitter.ts +115 -0
  54. package/packages/core/src/canvas/index.ts +2 -0
  55. package/packages/core/src/channels/base.ts +138 -0
  56. package/packages/core/src/channels/discord.ts +260 -0
  57. package/packages/core/src/channels/index.ts +7 -0
  58. package/packages/core/src/channels/manager.ts +383 -0
  59. package/packages/core/src/channels/slack.ts +287 -0
  60. package/packages/core/src/channels/telegram.ts +502 -0
  61. package/packages/core/src/channels/webchat.ts +128 -0
  62. package/packages/core/src/channels/whatsapp.ts +375 -0
  63. package/packages/core/src/config/index.ts +12 -0
  64. package/packages/core/src/config/loader.ts +529 -0
  65. package/packages/core/src/events/event-bus.ts +169 -0
  66. package/packages/core/src/gateway/index.ts +5 -0
  67. package/packages/core/src/gateway/initializer.ts +290 -0
  68. package/packages/core/src/gateway/lane-queue.ts +169 -0
  69. package/packages/core/src/gateway/resolver.ts +108 -0
  70. package/packages/core/src/gateway/router.ts +124 -0
  71. package/packages/core/src/gateway/server.ts +3317 -0
  72. package/packages/core/src/gateway/session.ts +95 -0
  73. package/packages/core/src/gateway/slash-commands.ts +192 -0
  74. package/packages/core/src/heartbeat/index.ts +157 -0
  75. package/packages/core/src/index.ts +19 -0
  76. package/packages/core/src/integrations/catalog.ts +286 -0
  77. package/packages/core/src/integrations/env.ts +64 -0
  78. package/packages/core/src/integrations/index.ts +2 -0
  79. package/packages/core/src/memory/index.ts +1 -0
  80. package/packages/core/src/memory/notes.ts +68 -0
  81. package/packages/core/src/plugins/api.ts +128 -0
  82. package/packages/core/src/plugins/index.ts +2 -0
  83. package/packages/core/src/plugins/loader.ts +365 -0
  84. package/packages/core/src/resilience/circuit-breaker.ts +225 -0
  85. package/packages/core/src/security/google-chat.ts +269 -0
  86. package/packages/core/src/security/index.ts +192 -0
  87. package/packages/core/src/security/pairing.ts +250 -0
  88. package/packages/core/src/security/rate-limit.ts +270 -0
  89. package/packages/core/src/security/signal.ts +321 -0
  90. package/packages/core/src/state/store.ts +312 -0
  91. package/packages/core/src/storage/bun-sqlite-store.ts +188 -0
  92. package/packages/core/src/storage/crypto.ts +101 -0
  93. package/packages/core/src/storage/db-context.ts +333 -0
  94. package/packages/core/src/storage/onboarding.ts +1087 -0
  95. package/packages/core/src/storage/schema.ts +541 -0
  96. package/packages/core/src/storage/seed.ts +571 -0
  97. package/packages/core/src/storage/sqlite.ts +387 -0
  98. package/packages/core/src/storage/usage.ts +212 -0
  99. package/packages/core/src/tools/bridge-events.ts +74 -0
  100. package/packages/core/src/tools/browser.ts +275 -0
  101. package/packages/core/src/tools/codebridge.ts +421 -0
  102. package/packages/core/src/tools/coordinator-tools.ts +179 -0
  103. package/packages/core/src/tools/cron.ts +611 -0
  104. package/packages/core/src/tools/exec.ts +140 -0
  105. package/packages/core/src/tools/fs.ts +364 -0
  106. package/packages/core/src/tools/index.ts +12 -0
  107. package/packages/core/src/tools/memory.ts +176 -0
  108. package/packages/core/src/tools/notify.ts +113 -0
  109. package/packages/core/src/tools/project-management.ts +376 -0
  110. package/packages/core/src/tools/project.ts +375 -0
  111. package/packages/core/src/tools/read.ts +158 -0
  112. package/packages/core/src/tools/web.ts +436 -0
  113. package/packages/core/src/tools/workspace.ts +171 -0
  114. package/packages/core/src/utils/benchmark.ts +80 -0
  115. package/packages/core/src/utils/crypto.ts +73 -0
  116. package/packages/core/src/utils/date.ts +42 -0
  117. package/packages/core/src/utils/index.ts +4 -0
  118. package/packages/core/src/utils/logger.ts +388 -0
  119. package/packages/core/src/utils/retry.ts +70 -0
  120. package/packages/core/src/voice/index.ts +583 -0
  121. package/packages/core/tsconfig.json +9 -0
  122. package/packages/mcp/package.json +26 -0
  123. package/packages/mcp/src/config.ts +13 -0
  124. package/packages/mcp/src/index.ts +1 -0
  125. package/packages/mcp/src/logger.ts +42 -0
  126. package/packages/mcp/src/manager.ts +434 -0
  127. package/packages/mcp/src/transports/index.ts +67 -0
  128. package/packages/mcp/src/transports/sse.ts +241 -0
  129. package/packages/mcp/src/transports/websocket.ts +159 -0
  130. package/packages/skills/package.json +21 -0
  131. package/packages/skills/src/bundled/agent_management/SKILL.md +24 -0
  132. package/packages/skills/src/bundled/browser_automation/SKILL.md +30 -0
  133. package/packages/skills/src/bundled/context_compact/SKILL.md +35 -0
  134. package/packages/skills/src/bundled/cron_manager/SKILL.md +52 -0
  135. package/packages/skills/src/bundled/file_manager/SKILL.md +76 -0
  136. package/packages/skills/src/bundled/http_client/SKILL.md +24 -0
  137. package/packages/skills/src/bundled/memory/SKILL.md +42 -0
  138. package/packages/skills/src/bundled/project_management/SKILL.md +26 -0
  139. package/packages/skills/src/bundled/shell/SKILL.md +43 -0
  140. package/packages/skills/src/bundled/system_notify/SKILL.md +52 -0
  141. package/packages/skills/src/bundled/voice/SKILL.md +25 -0
  142. package/packages/skills/src/bundled/web_search/SKILL.md +29 -0
  143. package/packages/skills/src/index.ts +1 -0
  144. package/packages/skills/src/loader.ts +282 -0
  145. package/packages/tools/package.json +43 -0
  146. package/packages/tools/src/browser/browser.test.ts +111 -0
  147. package/packages/tools/src/browser/index.ts +272 -0
  148. package/packages/tools/src/canvas/index.ts +220 -0
  149. package/packages/tools/src/cron/cron.test.ts +164 -0
  150. package/packages/tools/src/cron/index.ts +304 -0
  151. package/packages/tools/src/filesystem/filesystem.test.ts +240 -0
  152. package/packages/tools/src/filesystem/index.ts +379 -0
  153. package/packages/tools/src/git/index.ts +239 -0
  154. package/packages/tools/src/index.ts +4 -0
  155. package/packages/tools/src/shell/detect-env.ts +70 -0
  156. package/packages/tools/tsconfig.json +9 -0
@@ -0,0 +1,275 @@
1
+ import type { Tool } from "../agent/native-tools.ts";
2
+ import { logger } from "../utils/logger.ts";
3
+
4
+ export interface BrowserConfig {
5
+ headless?: boolean;
6
+ timeout?: number;
7
+ userAgent?: string;
8
+ }
9
+
10
+ export function createBrowserNavigateTool(config: BrowserConfig = {}): Tool {
11
+ const log = logger.child("browser");
12
+
13
+ return {
14
+ name: "browser_navigate",
15
+ description: "Navigate to a URL and retrieve the page content. Useful for scraping websites or getting current information.",
16
+ parameters: {
17
+ type: "object",
18
+ properties: {
19
+ url: {
20
+ type: "string",
21
+ description: "The URL to navigate to",
22
+ },
23
+ waitFor: {
24
+ type: "string",
25
+ description: "CSS selector to wait for before returning (optional)",
26
+ },
27
+ timeout: {
28
+ type: "number",
29
+ description: "Timeout in milliseconds (default: 30000)",
30
+ },
31
+ },
32
+ required: ["url"],
33
+ },
34
+ execute: async (params: Record<string, unknown>) => {
35
+ const url = params.url as string;
36
+ const timeout = (params.timeout as number) ?? config.timeout ?? 30000;
37
+
38
+ log.info(`Browser navigation started: ${url}`, { timeout });
39
+
40
+ const controller = new AbortController();
41
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
42
+
43
+ try {
44
+ const response = await fetch(url, {
45
+ signal: controller.signal,
46
+ headers: {
47
+ "User-Agent": config.userAgent || "Mozilla/5.0 (compatible; HiveBot/1.0)",
48
+ },
49
+ });
50
+
51
+ if (!response.ok) {
52
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
53
+ }
54
+
55
+ const contentType = response.headers.get("content-type") || "";
56
+ let content: string;
57
+
58
+ if (contentType.includes("application/json")) {
59
+ const json = await response.json();
60
+ content = JSON.stringify(json, null, 2);
61
+ } else {
62
+ content = await response.text();
63
+
64
+ content = content
65
+ .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "")
66
+ .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "")
67
+ .replace(/<[^>]+>/g, " ")
68
+ .replace(/\s+/g, " ")
69
+ .trim();
70
+ }
71
+
72
+ return {
73
+ success: true,
74
+ url: url,
75
+ finalUrl: response.url,
76
+ content: content.slice(0, 50000),
77
+ contentType,
78
+ };
79
+ } catch (error) {
80
+ const err = error as Error;
81
+ if (err.name === "AbortError") {
82
+ throw new Error(`Timeout after ${timeout}ms`);
83
+ }
84
+ throw error;
85
+ } finally {
86
+ clearTimeout(timeoutId);
87
+ }
88
+ },
89
+ };
90
+ }
91
+
92
+ export function createBrowserFetchTool(config: BrowserConfig = {}): Tool {
93
+ const log = logger.child("browser");
94
+
95
+ return {
96
+ name: "browser_fetch",
97
+ description: "Fetch content from a URL with custom HTTP method, headers, and body.",
98
+ parameters: {
99
+ type: "object",
100
+ properties: {
101
+ url: {
102
+ type: "string",
103
+ description: "The URL to fetch",
104
+ },
105
+ method: {
106
+ type: "string",
107
+ enum: ["GET", "POST", "PUT", "DELETE", "PATCH"],
108
+ description: "HTTP method (default: GET)",
109
+ },
110
+ headers: {
111
+ type: "object",
112
+ description: "Custom headers as key-value pairs",
113
+ },
114
+ body: {
115
+ type: "string",
116
+ description: "Request body for POST/PUT/PATCH",
117
+ },
118
+ timeout: {
119
+ type: "number",
120
+ description: "Timeout in milliseconds (default: 30000)",
121
+ },
122
+ },
123
+ required: ["url"],
124
+ },
125
+ execute: async (params: Record<string, unknown>) => {
126
+ const url = params.url as string;
127
+ const method = (params.method as string) ?? "GET";
128
+ const headers = (params.headers as Record<string, string>) ?? {};
129
+ const body = params.body as string | undefined;
130
+ const timeout = (params.timeout as number) ?? config.timeout ?? 30000;
131
+
132
+ log.info(`Browser fetch started: ${url} [${method}]`, { timeout });
133
+
134
+ const controller = new AbortController();
135
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
136
+
137
+ try {
138
+ const response = await fetch(url, {
139
+ method,
140
+ headers: {
141
+ "User-Agent": config.userAgent || "Mozilla/5.0 (compatible; HiveBot/1.0)",
142
+ ...headers,
143
+ },
144
+ body: method !== "GET" && method !== "DELETE" ? body : undefined,
145
+ signal: controller.signal,
146
+ });
147
+
148
+ if (!response.ok) {
149
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
150
+ }
151
+
152
+ const contentType = response.headers.get("content-type") || "";
153
+ let data: unknown;
154
+
155
+ if (contentType.includes("application/json")) {
156
+ data = await response.json();
157
+ } else {
158
+ data = await response.text();
159
+ }
160
+
161
+ return {
162
+ success: true,
163
+ status: response.status,
164
+ statusText: response.statusText,
165
+ headers: Object.fromEntries(response.headers.entries()),
166
+ data,
167
+ };
168
+ } catch (error) {
169
+ const err = error as Error;
170
+ if (err.name === "AbortError") {
171
+ throw new Error(`Timeout after ${timeout}ms`);
172
+ }
173
+ throw error;
174
+ } finally {
175
+ clearTimeout(timeoutId);
176
+ }
177
+ },
178
+ };
179
+ }
180
+
181
+ export function createBrowserScreenshotTool(_config: BrowserConfig = {}): Tool {
182
+ return {
183
+ name: "browser_screenshot",
184
+ description: "Take a screenshot of a webpage (requires puppeteer/playwright integration)",
185
+ parameters: {
186
+ type: "object",
187
+ properties: {
188
+ url: {
189
+ type: "string",
190
+ description: "The URL to screenshot",
191
+ },
192
+ width: {
193
+ type: "number",
194
+ description: "Viewport width (default: 1280)",
195
+ },
196
+ height: {
197
+ type: "number",
198
+ description: "Viewport height (default: 720)",
199
+ },
200
+ },
201
+ required: ["url"],
202
+ },
203
+ execute: async (params: Record<string, unknown>) => {
204
+ return {
205
+ success: false,
206
+ error: "Screenshot requires puppeteer/playwright integration. Use browser_navigate to get page content instead.",
207
+ url: params.url,
208
+ note: "Install puppeteer and implement headless browser capture for screenshots.",
209
+ };
210
+ },
211
+ };
212
+ }
213
+
214
+ export function createBrowserClickTool(_config: BrowserConfig = {}): Tool {
215
+ return {
216
+ name: "browser_click",
217
+ description: "Click an element on a page (requires puppeteer/playwright integration)",
218
+ parameters: {
219
+ type: "object",
220
+ properties: {
221
+ selector: {
222
+ type: "string",
223
+ description: "CSS selector for the element to click",
224
+ },
225
+ },
226
+ required: ["selector"],
227
+ },
228
+ execute: async (params: Record<string, unknown>) => {
229
+ return {
230
+ success: false,
231
+ error: "Click interaction requires puppeteer/playwright integration.",
232
+ selector: params.selector,
233
+ };
234
+ },
235
+ };
236
+ }
237
+
238
+ export function createBrowserTypeTool(_config: BrowserConfig = {}): Tool {
239
+ return {
240
+ name: "browser_type",
241
+ description: "Type text into an input field (requires puppeteer/playwright integration)",
242
+ parameters: {
243
+ type: "object",
244
+ properties: {
245
+ selector: {
246
+ type: "string",
247
+ description: "CSS selector for the input field",
248
+ },
249
+ text: {
250
+ type: "string",
251
+ description: "Text to type",
252
+ },
253
+ },
254
+ required: ["selector", "text"],
255
+ },
256
+ execute: async (params: Record<string, unknown>) => {
257
+ return {
258
+ success: false,
259
+ error: "Type interaction requires puppeteer/playwright integration.",
260
+ selector: params.selector,
261
+ text: params.text,
262
+ };
263
+ },
264
+ };
265
+ }
266
+
267
+ export function createBrowserTools(config: BrowserConfig = {}): Tool[] {
268
+ return [
269
+ createBrowserNavigateTool(config),
270
+ createBrowserFetchTool(config),
271
+ createBrowserScreenshotTool(config),
272
+ createBrowserClickTool(config),
273
+ createBrowserTypeTool(config),
274
+ ];
275
+ }
@@ -0,0 +1,421 @@
1
+ import type { Tool } from "../agent/native-tools.ts";
2
+ import { logger } from "../utils/logger.ts";
3
+ import { emitBridge } from "./bridge-events.ts";
4
+
5
+ const CODE_BRIDGE_URL = "ws://localhost:18791";
6
+
7
+ interface CodeBridgeMessage {
8
+ cmd: string;
9
+ taskId?: string;
10
+ config?: {
11
+ role: string;
12
+ cli: string;
13
+ args?: string[];
14
+ cwd?: string;
15
+ timeoutSeconds?: number;
16
+ };
17
+ prompt?: string;
18
+ }
19
+
20
+ interface CodeBridgeEvent {
21
+ type: string;
22
+ taskId?: string;
23
+ role?: string;
24
+ pid?: number;
25
+ stream?: string;
26
+ chunk?: string;
27
+ message?: string;
28
+ exitCode?: number;
29
+ percent?: number;
30
+ ok?: boolean;
31
+ agents?: Array<{
32
+ taskId: string;
33
+ role: string;
34
+ cli: string;
35
+ pid: number;
36
+ state: string;
37
+ }>;
38
+ }
39
+
40
+ async function checkCodeBridgeAvailable(): Promise<boolean> {
41
+ try {
42
+ const controller = new AbortController();
43
+ const timeout = setTimeout(() => controller.abort(), 1000);
44
+ const response = await fetch("http://localhost:18791/health", {
45
+ signal: controller.signal,
46
+ });
47
+ clearTimeout(timeout);
48
+ return response.ok;
49
+ } catch {
50
+ return false;
51
+ }
52
+ }
53
+
54
+ export function createCodeBridgeLaunchTool(): Tool {
55
+ const log = logger.child("codebridge-launch");
56
+
57
+ return {
58
+ name: "codebridge_launch",
59
+ description: "Launch a subagent process (qwen, claude-code, opencode, etc.) via Code Bridge. Use this to delegate tasks to AI coding assistants. Requires Code Bridge to be running on port 18791.",
60
+ parameters: {
61
+ type: "object",
62
+ properties: {
63
+ cli: {
64
+ type: "string",
65
+ description: "CLI command to run (e.g., 'qwen', 'claude', 'opencode', 'gemini')",
66
+ },
67
+ args: {
68
+ type: "array",
69
+ items: { type: "string" },
70
+ description: "Arguments to pass to the CLI (optional)",
71
+ },
72
+ prompt: {
73
+ type: "string",
74
+ description: "The prompt/task for the subagent",
75
+ },
76
+ role: {
77
+ type: "string",
78
+ enum: ["architecture", "development", "testing", "documentation"],
79
+ description: "Role of the subagent (optional, default: development)",
80
+ },
81
+ cwd: {
82
+ type: "string",
83
+ description: "Working directory (optional)",
84
+ },
85
+ timeoutSeconds: {
86
+ type: "number",
87
+ description: "Timeout in seconds (default: 600)",
88
+ },
89
+ },
90
+ required: ["cli", "prompt"],
91
+ },
92
+ execute: async (params: Record<string, unknown>) => {
93
+ // First check if Code Bridge is available
94
+ const available = await checkCodeBridgeAvailable();
95
+
96
+ if (!available) {
97
+ log.warn("Code Bridge not available. Falling back to terminal execution.");
98
+ const cli = params.cli as string;
99
+ const prompt = params.prompt as string;
100
+ const cwd = params.cwd as string | undefined;
101
+ const fbProcessId = `codebridge-${Date.now()}-${Math.random().toString(36).substring(7)}`;
102
+
103
+ emitBridge("bridge:cmd_start", { processId: fbProcessId, command: `${cli} "${prompt}"`, cwd, name: cli });
104
+
105
+ try {
106
+ const shell = process.platform === "win32"
107
+ ? ["cmd", "/c", `${cli} "${prompt}"`]
108
+ : ["/bin/bash", "-c", `${cli} "${prompt.replace(/"/g, '\\"')}"`];
109
+
110
+ const proc = Bun.spawn(shell, {
111
+ cwd: cwd ?? process.cwd(),
112
+ timeout: 60000,
113
+ maxBuffer: 10 * 1024 * 1024,
114
+ });
115
+
116
+ const stdout = await new Response(proc.stdout).text();
117
+ const stderr = await new Response(proc.stderr).text();
118
+ const exitCode = await proc.exited;
119
+
120
+ if (stdout) emitBridge("bridge:cmd_output", { processId: fbProcessId, chunk: stdout.slice(-50000), stream: "stdout" });
121
+ if (stderr) emitBridge("bridge:cmd_output", { processId: fbProcessId, chunk: stderr.slice(-10000), stream: "stderr" });
122
+ emitBridge("bridge:cmd_done", { processId: fbProcessId, exitCode, success: exitCode === 0 });
123
+
124
+ return {
125
+ success: exitCode === 0,
126
+ stdout: stdout.slice(-50000),
127
+ stderr: stderr.slice(-10000),
128
+ exitCode,
129
+ message: "Code Bridge not available, executed via terminal instead",
130
+ };
131
+ } catch (error) {
132
+ emitBridge("bridge:cmd_error", { processId: fbProcessId, message: (error as Error).message });
133
+ return {
134
+ success: false,
135
+ error: `Code Bridge not available and terminal fallback failed: ${(error as Error).message}`,
136
+ };
137
+ }
138
+ }
139
+
140
+ const cli = params.cli as string;
141
+ const args = (params.args as string[]) ?? [];
142
+ const prompt = params.prompt as string;
143
+ const role = (params.role as string) ?? "development";
144
+ const cwd = params.cwd as string | undefined;
145
+ const timeoutSeconds = (params.timeoutSeconds as number) ?? 600;
146
+ const taskId = `task-${Date.now()}-${Math.random().toString(36).substring(7)}`;
147
+
148
+ log.info(`Launching ${cli} with task ${taskId}`);
149
+
150
+ return new Promise((resolve, reject) => {
151
+ let ws: WebSocket | null = null;
152
+ let output = "";
153
+ let connected = false;
154
+ let timeoutHandle: NodeJS.Timeout | null = null;
155
+
156
+ const cleanup = () => {
157
+ if (timeoutHandle) clearTimeout(timeoutHandle);
158
+ if (ws) {
159
+ ws.onopen = null;
160
+ ws.onmessage = null;
161
+ ws.onerror = null;
162
+ ws.onclose = null;
163
+ }
164
+ };
165
+
166
+ try {
167
+ ws = new WebSocket(CODE_BRIDGE_URL);
168
+
169
+ ws.onopen = () => {
170
+ connected = true;
171
+ const msg: CodeBridgeMessage = {
172
+ cmd: "launch",
173
+ taskId,
174
+ config: {
175
+ role,
176
+ cli,
177
+ args,
178
+ cwd,
179
+ timeoutSeconds,
180
+ },
181
+ prompt,
182
+ };
183
+ ws!.send(JSON.stringify(msg));
184
+ };
185
+
186
+ ws.onmessage = (event) => {
187
+ try {
188
+ const data = JSON.parse(event.data) as CodeBridgeEvent;
189
+
190
+ if (data.type === "ack" && data.taskId === taskId) {
191
+ log.debug(`Launch acknowledged: ${data.taskId}, PID: ${data.pid}`);
192
+ }
193
+
194
+ if (data.type === "agent:output" && data.taskId === taskId) {
195
+ output += data.chunk || "";
196
+ }
197
+
198
+ if (data.type === "agent:finished" && data.taskId === taskId) {
199
+ log.info(`Task ${taskId} finished with code ${data.exitCode}`);
200
+ cleanup();
201
+ resolve({
202
+ success: true,
203
+ taskId,
204
+ exitCode: data.exitCode,
205
+ output: output.slice(-50000),
206
+ });
207
+ ws?.close();
208
+ }
209
+
210
+ if (data.type === "agent:error" && data.taskId === taskId) {
211
+ log.error(`Task ${taskId} error: ${data.message}`);
212
+ cleanup();
213
+ reject(new Error(data.message ?? "Subagent error"));
214
+ ws?.close();
215
+ }
216
+ } catch (e) {
217
+ // Ignore parse errors for non-JSON messages
218
+ }
219
+ };
220
+
221
+ ws.onerror = (error) => {
222
+ log.error(`WebSocket error: ${error}`);
223
+ };
224
+
225
+ ws.onclose = () => {
226
+ if (!connected) {
227
+ cleanup();
228
+ reject(new Error("Failed to connect to Code Bridge"));
229
+ }
230
+ };
231
+
232
+ timeoutHandle = setTimeout(() => {
233
+ if (ws && ws.readyState === WebSocket.OPEN) {
234
+ cleanup();
235
+ ws.close();
236
+ resolve({
237
+ success: true,
238
+ taskId,
239
+ output: output.slice(-50000),
240
+ message: "Timeout reached, task may still be running",
241
+ });
242
+ }
243
+ }, timeoutSeconds * 1000);
244
+ } catch (error) {
245
+ cleanup();
246
+ reject(new Error(`Failed to connect to Code Bridge: ${(error as Error).message}`));
247
+ }
248
+ });
249
+ },
250
+ };
251
+ }
252
+
253
+ export function createCodeBridgeStatusTool(): Tool {
254
+ const log = logger.child("codebridge-status");
255
+
256
+ return {
257
+ name: "codebridge_status",
258
+ description: "Get the status of all running subagent processes from Code Bridge",
259
+ parameters: {
260
+ type: "object",
261
+ properties: {},
262
+ },
263
+ execute: async () => {
264
+ try {
265
+ const response = await fetch("http://localhost:18791/status");
266
+ const data = await response.json();
267
+ return data;
268
+ } catch (error) {
269
+ log.error(`Status failed: ${(error as Error).message}`);
270
+ return {
271
+ type: "code-bridge:status",
272
+ agents: [],
273
+ error: (error as Error).message,
274
+ };
275
+ }
276
+ },
277
+ };
278
+ }
279
+
280
+ export function createCodeBridgeCancelTool(): Tool {
281
+ const log = logger.child("codebridge-cancel");
282
+
283
+ return {
284
+ name: "codebridge_cancel",
285
+ description: "Cancel a running subagent process by task ID",
286
+ parameters: {
287
+ type: "object",
288
+ properties: {
289
+ taskId: {
290
+ type: "string",
291
+ description: "The task ID to cancel",
292
+ },
293
+ },
294
+ required: ["taskId"],
295
+ },
296
+ execute: async (params: Record<string, unknown>) => {
297
+ const taskId = params.taskId as string;
298
+ log.info(`Cancelling task ${taskId}`);
299
+
300
+ return new Promise((resolve, reject) => {
301
+ const ws = new WebSocket(CODE_BRIDGE_URL);
302
+
303
+ ws.onopen = () => {
304
+ const msg = { cmd: "cancel", taskId };
305
+ ws.send(JSON.stringify(msg));
306
+ };
307
+
308
+ ws.onmessage = (event) => {
309
+ try {
310
+ const data = JSON.parse(event.data) as CodeBridgeEvent & { cmd?: string };
311
+ if (data.type === "ack" && (data as any).cmd === "cancel") {
312
+ resolve({
313
+ success: data.ok ?? true,
314
+ taskId,
315
+ message: data.ok ? "Task cancelled" : "Task not found",
316
+ });
317
+ ws.close();
318
+ }
319
+ } catch {
320
+ // Ignore
321
+ }
322
+ };
323
+
324
+ ws.onerror = () => {
325
+ reject(new Error("Code Bridge WebSocket error"));
326
+ };
327
+
328
+ ws.onclose = () => {
329
+ setTimeout(() => {
330
+ reject(new Error("Cancellation timeout"));
331
+ }, 5000);
332
+ };
333
+ });
334
+ },
335
+ };
336
+ }
337
+
338
+ export function createTerminalTool(): Tool {
339
+ const log = logger.child("terminal");
340
+
341
+ return {
342
+ name: "terminal",
343
+ description: "Execute a terminal command directly. Use this for shell commands, git operations, npm scripts, etc.",
344
+ parameters: {
345
+ type: "object",
346
+ properties: {
347
+ command: {
348
+ type: "string",
349
+ description: "The terminal command to execute",
350
+ },
351
+ cwd: {
352
+ type: "string",
353
+ description: "Working directory (optional)",
354
+ },
355
+ timeoutSeconds: {
356
+ type: "number",
357
+ description: "Timeout in seconds (default: 60)",
358
+ },
359
+ },
360
+ required: ["command"],
361
+ },
362
+ execute: async (params: Record<string, unknown>) => {
363
+ const command = params.command as string;
364
+ const cwd = params.cwd as string | undefined;
365
+ const timeoutSeconds = (params.timeoutSeconds as number) ?? 60;
366
+ const processId = `terminal-${Date.now()}-${Math.random().toString(36).substring(7)}`;
367
+
368
+ const platform = process.platform;
369
+ let shell: string[];
370
+
371
+ if (platform === "win32") {
372
+ shell = ["cmd", "/c", command];
373
+ } else if (platform === "darwin") {
374
+ shell = ["/bin/zsh", "-c", command];
375
+ } else {
376
+ shell = ["/bin/bash", "-c", command];
377
+ }
378
+
379
+ log.debug(`Executing: ${command}`);
380
+
381
+ emitBridge("bridge:cmd_start", { processId, command, cwd, name: "terminal" });
382
+
383
+ try {
384
+ const proc = Bun.spawn(shell, {
385
+ cwd: cwd ?? process.cwd(),
386
+ timeout: timeoutSeconds * 1000,
387
+ maxBuffer: 10 * 1024 * 1024,
388
+ });
389
+
390
+ const stdout = await new Response(proc.stdout).text();
391
+ const stderr = await new Response(proc.stderr).text();
392
+ const exitCode = await proc.exited;
393
+
394
+ if (stdout) emitBridge("bridge:cmd_output", { processId, chunk: stdout.slice(-50000), stream: "stdout" });
395
+ if (stderr) emitBridge("bridge:cmd_output", { processId, chunk: stderr.slice(-10000), stream: "stderr" });
396
+ emitBridge("bridge:cmd_done", { processId, exitCode, success: exitCode === 0 });
397
+
398
+ return {
399
+ success: exitCode === 0,
400
+ stdout: stdout.slice(-50000),
401
+ stderr: stderr.slice(-10000),
402
+ exitCode,
403
+ platform,
404
+ };
405
+ } catch (error) {
406
+ log.error(`Terminal failed: ${(error as Error).message}`);
407
+ emitBridge("bridge:cmd_error", { processId, message: (error as Error).message });
408
+ throw new Error(`Terminal execution failed: ${(error as Error).message}`);
409
+ }
410
+ },
411
+ };
412
+ }
413
+
414
+ export function createCodeBridgeTools(): Tool[] {
415
+ return [
416
+ createCodeBridgeLaunchTool(),
417
+ createCodeBridgeStatusTool(),
418
+ createCodeBridgeCancelTool(),
419
+ createTerminalTool(),
420
+ ];
421
+ }