@johpaz/hive-sdk 0.0.14 → 0.0.15

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 (199) hide show
  1. package/.github/CODEOWNERS +9 -0
  2. package/.github/workflows/publish.yml +89 -0
  3. package/.github/workflows/version-bump.yml +102 -0
  4. package/CHANGELOG.md +38 -0
  5. package/README.md +158 -0
  6. package/bun.lock +543 -0
  7. package/bunfig.toml +7 -0
  8. package/docs/API-AGENTS.md +316 -0
  9. package/docs/API-CONTEXT-COMPILER.md +252 -0
  10. package/docs/API-DAG-SCHEDULER.md +273 -0
  11. package/docs/API-TOOLS-SKILLS-CHANNELS.md +293 -0
  12. package/docs/API-WORKERS-EVENTS.md +152 -0
  13. package/docs/INDEX.md +141 -0
  14. package/docs/README.md +68 -0
  15. package/package.json +54 -105
  16. package/packages/cli/package.json +17 -0
  17. package/packages/cli/src/commands/init.ts +56 -0
  18. package/packages/cli/src/commands/run.ts +45 -0
  19. package/packages/cli/src/commands/test.ts +42 -0
  20. package/packages/cli/src/commands/trace.ts +55 -0
  21. package/packages/cli/src/index.ts +43 -0
  22. package/packages/core/package.json +58 -0
  23. package/packages/core/src/ace/Curator.ts +158 -0
  24. package/packages/core/src/ace/Reflector.ts +200 -0
  25. package/packages/core/src/ace/Tracer.ts +100 -0
  26. package/packages/core/src/ace/index.ts +4 -0
  27. package/packages/core/src/agent/AgentRunner.ts +699 -0
  28. package/packages/core/src/agent/Compaction.ts +221 -0
  29. package/packages/core/src/agent/ContextCompiler.ts +567 -0
  30. package/packages/core/src/agent/ContextGuard.ts +91 -0
  31. package/packages/core/src/agent/ConversationStore.ts +244 -0
  32. package/packages/core/src/agent/Hooks.ts +166 -0
  33. package/packages/core/src/agent/NativeTools.ts +31 -0
  34. package/packages/core/src/agent/PromptBuilder.ts +169 -0
  35. package/packages/core/src/agent/Service.ts +267 -0
  36. package/packages/core/src/agent/StuckLoop.ts +133 -0
  37. package/packages/core/src/agent/index.ts +12 -0
  38. package/packages/core/src/agent/providers/LLMClient.ts +149 -0
  39. package/packages/core/src/agent/providers/anthropic.ts +212 -0
  40. package/packages/core/src/agent/providers/gemini.ts +215 -0
  41. package/packages/core/src/agent/providers/index.ts +199 -0
  42. package/packages/core/src/agent/providers/interface.ts +195 -0
  43. package/packages/core/src/agent/providers/ollama.ts +175 -0
  44. package/packages/core/src/agent/providers/openai-compat.ts +231 -0
  45. package/packages/core/src/agent/providers.ts +1 -0
  46. package/packages/core/src/agent/selectors/PlaybookSelector.ts +147 -0
  47. package/packages/core/src/agent/selectors/SkillSelector.ts +478 -0
  48. package/packages/core/src/agent/selectors/ToolSelector.ts +577 -0
  49. package/packages/core/src/agent/selectors/index.ts +6 -0
  50. package/packages/core/src/api/createAgent.test.ts +48 -0
  51. package/packages/core/src/api/createAgent.ts +122 -0
  52. package/packages/core/src/api/index.ts +2 -0
  53. package/packages/core/src/canvas/CanvasManager.ts +390 -0
  54. package/packages/core/src/canvas/a2ui-tools.ts +255 -0
  55. package/packages/core/src/canvas/canvas-tools.ts +448 -0
  56. package/packages/core/src/canvas/emitter.ts +149 -0
  57. package/packages/core/src/canvas/index.ts +6 -0
  58. package/packages/core/src/config/index.ts +2 -0
  59. package/packages/core/src/config/loader.ts +554 -0
  60. package/packages/core/src/ethics/EthicsGuard.test.ts +54 -0
  61. package/packages/core/src/ethics/EthicsGuard.ts +66 -0
  62. package/packages/core/src/ethics/index.ts +2 -0
  63. package/packages/core/src/gateway/channel-notify.test.ts +14 -0
  64. package/packages/core/src/gateway/channel-notify.ts +12 -0
  65. package/packages/core/src/gateway/index.ts +1 -0
  66. package/packages/core/src/index.ts +37 -0
  67. package/packages/core/src/mcp/MCPClient.ts +439 -0
  68. package/packages/core/src/mcp/MCPToolAdapter.ts +176 -0
  69. package/packages/core/src/mcp/config.ts +13 -0
  70. package/packages/core/src/mcp/hot-reload.ts +147 -0
  71. package/packages/core/src/mcp/index.ts +11 -0
  72. package/packages/core/src/mcp/logger.ts +42 -0
  73. package/packages/core/src/mcp/singleton.ts +21 -0
  74. package/packages/core/src/mcp/transports/index.ts +67 -0
  75. package/packages/core/src/mcp/transports/sse.ts +241 -0
  76. package/packages/core/src/mcp/transports/websocket.ts +159 -0
  77. package/packages/core/src/memory/Scratchpad.test.ts +47 -0
  78. package/packages/core/src/memory/Scratchpad.ts +37 -0
  79. package/packages/core/src/memory/Storage.ts +6 -0
  80. package/packages/core/src/memory/index.ts +2 -0
  81. package/packages/core/src/multimodal/VisionService.ts +293 -0
  82. package/packages/core/src/multimodal/index.ts +2 -0
  83. package/packages/core/src/multimodal/types.ts +28 -0
  84. package/packages/core/src/security/Pairing.ts +250 -0
  85. package/packages/core/src/security/RateLimit.ts +270 -0
  86. package/packages/core/src/security/index.ts +4 -0
  87. package/packages/core/src/skills/SkillLoader.ts +388 -0
  88. package/packages/core/src/skills/bundled-data.generated.ts +3332 -0
  89. package/packages/core/src/skills/defineSkill.ts +18 -0
  90. package/packages/core/src/skills/index.ts +4 -0
  91. package/packages/core/src/state/index.ts +2 -0
  92. package/packages/core/src/state/store.ts +312 -0
  93. package/packages/core/src/storage/SQLiteStorage.ts +407 -0
  94. package/packages/core/src/storage/crypto.ts +101 -0
  95. package/packages/core/src/storage/index.ts +10 -0
  96. package/packages/core/src/storage/onboarding.ts +1603 -0
  97. package/packages/core/src/storage/schema.ts +689 -0
  98. package/packages/core/src/storage/seed.ts +740 -0
  99. package/packages/core/src/storage/usage.ts +374 -0
  100. package/packages/core/src/swarm/AgentBus.ts +460 -0
  101. package/packages/core/src/swarm/AgentExecutor.ts +53 -0
  102. package/packages/core/src/swarm/Coordinator.ts +251 -0
  103. package/packages/core/src/swarm/EventBridge.ts +122 -0
  104. package/packages/core/src/swarm/EventBus.ts +169 -0
  105. package/packages/core/src/swarm/TaskGraph.ts +192 -0
  106. package/packages/core/src/swarm/TaskNode.ts +97 -0
  107. package/packages/core/src/swarm/TaskResult.ts +22 -0
  108. package/packages/core/src/swarm/WorkerPool.ts +236 -0
  109. package/packages/core/src/swarm/errors.ts +37 -0
  110. package/packages/core/src/swarm/index.ts +30 -0
  111. package/packages/core/src/swarm/presets/HiveLearnPreset.ts +99 -0
  112. package/packages/core/src/swarm/presets/ResearchPreset.ts +97 -0
  113. package/packages/core/src/swarm/presets/index.ts +4 -0
  114. package/packages/core/src/swarm/strategies/ParallelStrategy.ts +21 -0
  115. package/packages/core/src/swarm/strategies/PriorityStrategy.ts +46 -0
  116. package/packages/core/src/swarm/strategies/index.ts +3 -0
  117. package/packages/core/src/swarm/types.ts +164 -0
  118. package/packages/core/src/tools/ToolExecutor.ts +58 -0
  119. package/packages/core/src/tools/ToolRegistry.test.ts +98 -0
  120. package/packages/core/src/tools/ToolRegistry.ts +61 -0
  121. package/packages/core/src/tools/agents/get-available-models.ts +118 -0
  122. package/packages/core/src/tools/agents/index.ts +715 -0
  123. package/packages/core/src/tools/bridge-events.ts +26 -0
  124. package/packages/core/src/tools/canvas/index.ts +375 -0
  125. package/packages/core/src/tools/cli/index.ts +142 -0
  126. package/packages/core/src/tools/codebridge/index.ts +342 -0
  127. package/packages/core/src/tools/core/index.ts +476 -0
  128. package/packages/core/src/tools/cron/index.ts +626 -0
  129. package/packages/core/src/tools/filesystem/fs-delete.ts +78 -0
  130. package/packages/core/src/tools/filesystem/fs-edit.ts +106 -0
  131. package/packages/core/src/tools/filesystem/fs-exists.ts +63 -0
  132. package/packages/core/src/tools/filesystem/fs-glob.ts +108 -0
  133. package/packages/core/src/tools/filesystem/fs-list.ts +129 -0
  134. package/packages/core/src/tools/filesystem/fs-read.ts +72 -0
  135. package/packages/core/src/tools/filesystem/fs-write.ts +67 -0
  136. package/packages/core/src/tools/filesystem/index.ts +34 -0
  137. package/packages/core/src/tools/filesystem/workspace-guard.ts +62 -0
  138. package/packages/core/src/tools/index.ts +231 -0
  139. package/packages/core/src/tools/meeting/index.ts +363 -0
  140. package/packages/core/src/tools/office/index.ts +47 -0
  141. package/packages/core/src/tools/office/office-escribir-docx.ts +192 -0
  142. package/packages/core/src/tools/office/office-escribir-pdf.ts +172 -0
  143. package/packages/core/src/tools/office/office-escribir-pptx.ts +174 -0
  144. package/packages/core/src/tools/office/office-escribir-xlsx.ts +116 -0
  145. package/packages/core/src/tools/office/office-leer-docx.ts +93 -0
  146. package/packages/core/src/tools/office/office-leer-pdf.ts +114 -0
  147. package/packages/core/src/tools/office/office-leer-pptx.ts +136 -0
  148. package/packages/core/src/tools/office/office-leer-xlsx.ts +124 -0
  149. package/packages/core/src/tools/projects/index.ts +37 -0
  150. package/packages/core/src/tools/projects/project-create.ts +94 -0
  151. package/packages/core/src/tools/projects/project-done.ts +66 -0
  152. package/packages/core/src/tools/projects/project-fail.ts +66 -0
  153. package/packages/core/src/tools/projects/project-list.ts +96 -0
  154. package/packages/core/src/tools/projects/project-update.ts +72 -0
  155. package/packages/core/src/tools/projects/task-create.ts +68 -0
  156. package/packages/core/src/tools/projects/task-evaluate.ts +93 -0
  157. package/packages/core/src/tools/projects/task-update.ts +93 -0
  158. package/packages/core/src/tools/types.ts +39 -0
  159. package/packages/core/src/tools/voice/index.ts +104 -0
  160. package/packages/core/src/tools/web/browser-click.ts +78 -0
  161. package/packages/core/src/tools/web/browser-extract.ts +139 -0
  162. package/packages/core/src/tools/web/browser-navigate.ts +106 -0
  163. package/packages/core/src/tools/web/browser-screenshot.ts +87 -0
  164. package/packages/core/src/tools/web/browser-script.ts +88 -0
  165. package/packages/core/src/tools/web/browser-service.ts +554 -0
  166. package/packages/core/src/tools/web/browser-type.ts +101 -0
  167. package/packages/core/src/tools/web/browser-wait.ts +136 -0
  168. package/packages/core/src/tools/web/index.ts +41 -0
  169. package/packages/core/src/tools/web/web-fetch.ts +78 -0
  170. package/packages/core/src/tools/web/web-search.ts +123 -0
  171. package/packages/core/src/utils/benchmark.ts +80 -0
  172. package/packages/core/src/utils/crypto.ts +73 -0
  173. package/packages/core/src/utils/date.ts +42 -0
  174. package/packages/core/src/utils/index.ts +10 -0
  175. package/packages/core/src/utils/logger.ts +389 -0
  176. package/packages/core/src/utils/retry.ts +70 -0
  177. package/packages/core/src/utils/toon.ts +253 -0
  178. package/packages/core/src/voice/index.ts +656 -0
  179. package/test/setup-db.ts +216 -0
  180. package/tsconfig.json +39 -0
  181. package/src/agents.ts +0 -1
  182. package/src/canvas.ts +0 -1
  183. package/src/channels.ts +0 -1
  184. package/src/config.ts +0 -1
  185. package/src/events.ts +0 -1
  186. package/src/gateway.ts +0 -1
  187. package/src/index.ts +0 -304
  188. package/src/mcp.ts +0 -1
  189. package/src/multimodal.ts +0 -1
  190. package/src/scheduler.ts +0 -1
  191. package/src/security.ts +0 -1
  192. package/src/skills.ts +0 -1
  193. package/src/state.ts +0 -1
  194. package/src/storage.ts +0 -1
  195. package/src/tools.ts +0 -1
  196. package/src/tts.ts +0 -1
  197. package/src/types.ts +0 -82
  198. package/src/utils.ts +0 -1
  199. package/src/voice.ts +0 -1
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Bridge Events - WebSocket event subscription for CodeBridge
3
+ *
4
+ * Manages WebSocket subscriptions for real-time CodeBridge events
5
+ */
6
+
7
+ const bridgeSubscribers = new Set<{ send: (data: string) => void }>()
8
+
9
+ export function subscribeBridge(ws: { send: (data: string) => void }) {
10
+ bridgeSubscribers.add(ws)
11
+ }
12
+
13
+ export function unsubscribeBridge(ws: { send: (data: string) => void }) {
14
+ bridgeSubscribers.delete(ws)
15
+ }
16
+
17
+ export function emitBridgeEvent(event: { type: string; data: any }) {
18
+ const payload = JSON.stringify(event)
19
+ for (const ws of bridgeSubscribers) {
20
+ try {
21
+ ws.send(payload)
22
+ } catch {
23
+ bridgeSubscribers.delete(ws)
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,375 @@
1
+ /**
2
+ * Canvas Tools - 7 tools + A2UI v0.9 tools
3
+ *
4
+ * @category canvas
5
+ */
6
+
7
+ import type { Tool, ToolResult } from "../types.ts";
8
+ import { emitCanvas, removeCanvasComponent, type CanvasEventType } from "../../canvas/emitter.ts";
9
+ import { logger } from "../../utils/logger.ts";
10
+ import { canvasManager } from "../../canvas/CanvasManager.ts";
11
+ import { createA2UISurfaceTool, createA2UIUpdateComponentsTool, createA2UIUpdateDataModelTool, createA2UIDeleteSurfaceTool } from "../../canvas/a2ui-tools.ts";
12
+ import type { Config } from "../../config/loader.ts";
13
+
14
+ const log = logger.child("canvas");
15
+
16
+ // ─── Pending canvas interactions ─────────────────────────────────────────────
17
+ // Simple map indexed by componentId — no session ID required.
18
+ // Server calls resolveCanvasInteraction() when it receives canvas:interact.
19
+
20
+ interface PendingInteraction {
21
+ resolve: (data: unknown) => void;
22
+ reject: (err: Error) => void;
23
+ timeout: ReturnType<typeof setTimeout>;
24
+ }
25
+
26
+ const pendingInteractions = new Map<string, PendingInteraction>();
27
+
28
+ export function resolveCanvasInteraction(componentId: string, data: unknown): boolean {
29
+ const pending = pendingInteractions.get(componentId);
30
+ if (!pending) return false;
31
+ clearTimeout(pending.timeout);
32
+ pendingInteractions.delete(componentId);
33
+ pending.resolve(data);
34
+ return true;
35
+ }
36
+
37
+ function waitForCanvasInteraction(componentId: string, timeoutMs = 300000): Promise<unknown> {
38
+ return new Promise((resolve, reject) => {
39
+ const timeout = setTimeout(() => {
40
+ pendingInteractions.delete(componentId);
41
+ reject(new Error(`Interaction timeout for ${componentId}`));
42
+ }, timeoutMs);
43
+ pendingInteractions.set(componentId, { resolve, reject, timeout });
44
+ });
45
+ }
46
+
47
+ // ─── canvas_render ───────────────────────────────────────────────────────────
48
+
49
+ export const canvasRenderTool: Tool = {
50
+ name: "canvas_render",
51
+ description: "Render a component or visualization on the canvas. Use specific types instead of always using card+markdown. Key types: chart (bar/line/area/pie graphs), table (tabular data), markdown (rich text), form (interactive form - waits for submit), button (interactive button), alert-dialog (confirm/cancel dialog), progress (progress bars), accordion, tabs, badge, card, bee-loader. Spanish: renderizar, visualizar, gráfico, diagrama, tabla, formulario",
52
+ parameters: {
53
+ type: "object",
54
+ properties: {
55
+ component: { type: "string", description: "Component type. Visualization: chart, table, markdown, card, progress, accordion, tabs, badge, separator, bee-loader. Interactive: form, button, alert-dialog. Layout: carousel, collapsible, resizable, scroll-area, tabs. Other: alert, avatar, breadcrumb, calendar, checkbox, dialog, drawer, dropdown-menu, hover-card, input, input-otp, label, menubar, navigation-menu, pagination, popover, radio-group, select, sheet, skeleton, slider, switch, textarea, toggle, toggle-group, tooltip, aspect-ratio, command, context-menu, custom" },
56
+ data: { type: "object", description: "Props for the component. chart: {type:'bar'|'line'|'area'|'pie', data:[{name,...}], xKey:'name', keys:['value'], title}. table: {title, columns:[{header,key}], data:[{}]}. form: {title, fields:[{name,label,type:'text'|'email'|'number'|'textarea'|'select'|'checkbox',placeholder,options:[{value,label}]}], submitLabel}. alert-dialog: {title, description, confirmLabel, cancelLabel}. button: {label, variant:'default'|'outline'|'secondary'|'destructive'}. markdown: {content}. progress: {value:0-100}." },
57
+ },
58
+ required: ["component", "data"],
59
+ },
60
+ execute: async (params: Record<string, unknown>) => {
61
+ const componentType = params.component as string;
62
+ const data = params.data as Record<string, unknown>;
63
+
64
+ try {
65
+ const id = `render_${componentType}_${Date.now()}`;
66
+ emitCanvas("canvas:render", {
67
+ component: {
68
+ id,
69
+ type: componentType,
70
+ props: data ?? {},
71
+ position: { x: 0, y: 0 },
72
+ size: { width: 400, height: 300 },
73
+ agentId: "agent",
74
+ },
75
+ });
76
+ return { ok: true, message: `Rendered ${componentType}.` };
77
+ } catch (error) {
78
+ return { ok: false, error: `Failed to render: ${(error as Error).message }` };
79
+ }
80
+ },
81
+ };
82
+
83
+ // ─── canvas_ask ──────────────────────────────────────────────────────────────
84
+
85
+ export const canvasAskTool: Tool = {
86
+ name: "canvas_ask",
87
+ description: "Show interactive form and wait for user input. Spanish: formulario interactivo, preguntar usuario, input",
88
+ parameters: {
89
+ type: "object",
90
+ properties: {
91
+ questions: {
92
+ type: "array",
93
+ description: "List of questions to ask",
94
+ items: {
95
+ type: "object",
96
+ properties: {
97
+ question: { type: "string" },
98
+ type: { type: "string", enum: ["text", "select", "confirm"] },
99
+ options: { type: "array", items: { type: "string" } },
100
+ },
101
+ },
102
+ },
103
+ },
104
+ required: ["questions"],
105
+ },
106
+ execute: async (params: Record<string, unknown>, config?: any) => {
107
+ const questions = params.questions as any[];
108
+ const userId = config?.configurable?.user_id;
109
+ const threadId = config?.configurable?.thread_id;
110
+ // Use threadId (session) if available, then userId, then default
111
+ // This must match the WebSocket sessionId used by the frontend
112
+ const sessionId = threadId ? `canvas:${threadId}` : userId ? `canvas:${userId}` : "canvas:default";
113
+
114
+ // Convert questions to form fields
115
+ const fields = questions.map((q, idx) => ({
116
+ name: `field_${idx}`,
117
+ label: q.question,
118
+ type: q.type === "select" ? "select" : q.type === "confirm" ? "text" : "text",
119
+ required: true,
120
+ options: q.options?.map((opt: string) => ({ label: opt, value: opt })),
121
+ }));
122
+
123
+ const formId = `form-${Date.now()}`;
124
+
125
+ try {
126
+ // Render form via canvasManager
127
+ await canvasManager.render(sessionId, {
128
+ id: formId,
129
+ type: "form",
130
+ props: {
131
+ title: "Input Required",
132
+ fields,
133
+ },
134
+ });
135
+
136
+ // Wait for user interaction
137
+ const response = await canvasManager.waitForInteraction(sessionId, formId, 300000);
138
+
139
+ return {
140
+ ok: true,
141
+ message: "Form submitted by user",
142
+ data: response,
143
+ formId,
144
+ };
145
+ } catch (error) {
146
+ return {
147
+ ok: false,
148
+ error: `Form interaction failed: ${(error as Error).message}`,
149
+ formId,
150
+ };
151
+ }
152
+ },
153
+ };
154
+
155
+ // ─── canvas_confirm ──────────────────────────────────────────────────────────
156
+
157
+ export const canvasConfirmTool: Tool = {
158
+ name: "canvas_confirm",
159
+ description: "Show a confirmation dialog before executing an action. Spanish: confirmar acción, diálogo, aprobar",
160
+ parameters: {
161
+ type: "object",
162
+ properties: {
163
+ message: { type: "string", description: "Confirmation message" },
164
+ action: { type: "string", description: "Action to confirm" },
165
+ },
166
+ required: ["message", "action"],
167
+ },
168
+ execute: async (params: Record<string, unknown>, config?: any) => {
169
+ const message = params.message as string;
170
+ const action = params.action as string;
171
+
172
+ const confirmId = `confirm-${Date.now()}`;
173
+
174
+ emitCanvas("canvas:render", {
175
+ component: {
176
+ id: confirmId,
177
+ type: "alert-dialog",
178
+ props: { title: action, description: message, confirmLabel: "Confirmar", cancelLabel: "Cancelar" },
179
+ position: { x: 0, y: 0 },
180
+ size: { width: 400, height: 200 },
181
+ agentId: "agent",
182
+ },
183
+ });
184
+
185
+ try {
186
+ const interactionData = await waitForCanvasInteraction(confirmId, 300000) as any;
187
+ removeCanvasComponent(confirmId);
188
+ const confirmed = interactionData?.confirmed === true;
189
+ return { ok: true, confirmed, action, message };
190
+ } catch (error) {
191
+ removeCanvasComponent(confirmId);
192
+ return { ok: false, confirmed: false, error: (error as Error).message };
193
+ }
194
+ },
195
+ };
196
+
197
+ // ─── canvas_show_card ────────────────────────────────────────────────────────
198
+
199
+ export const canvasShowCardTool: Tool = {
200
+ name: "canvas_show_card",
201
+ description: "Display structured information in card format. Spanish: mostrar tarjeta, card, información estructurada",
202
+ parameters: {
203
+ type: "object",
204
+ properties: {
205
+ title: { type: "string", description: "Card title" },
206
+ content: { type: "string", description: "Card content (Markdown supported)" },
207
+ items: {
208
+ type: "array",
209
+ description: "List of key-value items",
210
+ items: {
211
+ type: "object",
212
+ properties: {
213
+ label: { type: "string" },
214
+ value: { type: "string" },
215
+ },
216
+ },
217
+ },
218
+ },
219
+ required: ["title"],
220
+ },
221
+ execute: async (params: Record<string, unknown>) => {
222
+ const title = params.title as string;
223
+ const content = params.content as string | undefined;
224
+ const items = (params.items as Array<{ label: string; value: string }>) || [];
225
+
226
+ try {
227
+ const id = `card_${Date.now()}`;
228
+ emitCanvas("canvas:render", {
229
+ component: {
230
+ id,
231
+ type: "card",
232
+ props: {
233
+ title,
234
+ children: content ?? (items.length > 0 ? items.map((item) => `**${item.label}:** ${item.value}`).join("\n") : ""),
235
+ // Pass items as table rows for richer rendering
236
+ items,
237
+ },
238
+ position: { x: 0, y: 0 },
239
+ size: { width: 320, height: 200 },
240
+ agentId: "agent",
241
+ },
242
+ });
243
+ return { ok: true, message: `Card "${title}" displayed.` };
244
+ } catch (error) {
245
+ return { ok: false, error: `Failed to display card: ${(error as Error).message }` };
246
+ }
247
+ },
248
+ };
249
+
250
+ // ─── canvas_show_progress ────────────────────────────────────────────────────
251
+
252
+ export const canvasShowProgressTool: Tool = {
253
+ name: "canvas_show_progress",
254
+ description: "Show progress bar or status indicator. Spanish: barra de progreso, indicador, progreso visual",
255
+ parameters: {
256
+ type: "object",
257
+ properties: {
258
+ bars: {
259
+ type: "array",
260
+ description: "List of progress bars",
261
+ items: {
262
+ type: "object",
263
+ properties: {
264
+ label: { type: "string" },
265
+ value: { type: "number", minimum: 0, maximum: 100 },
266
+ },
267
+ },
268
+ },
269
+ },
270
+ required: ["bars"],
271
+ },
272
+ execute: async (params: Record<string, unknown>) => {
273
+ const bars = params.bars as Array<{ label: string; value: number }>;
274
+
275
+ try {
276
+ // Render each bar as a separate progress component
277
+ for (const bar of bars) {
278
+ const id = `progress_${bar.label.replace(/\s+/g, "_")}_${Date.now()}`;
279
+ emitCanvas("canvas:render", {
280
+ component: {
281
+ id,
282
+ type: "progress",
283
+ props: { value: bar.value, label: bar.label },
284
+ position: { x: 0, y: 0 },
285
+ size: { width: 320, height: 60 },
286
+ agentId: "agent",
287
+ },
288
+ });
289
+ }
290
+ return { ok: true, message: "Progress displayed." };
291
+ } catch (error) {
292
+ return { ok: false, error: `Failed to display progress: ${(error as Error).message }` };
293
+ }
294
+ },
295
+ };
296
+
297
+ // ─── canvas_show_list ────────────────────────────────────────────────────────
298
+
299
+ export const canvasShowListTool: Tool = {
300
+ name: "canvas_show_list",
301
+ description: "Display key-value list information. Spanish: lista clave-valor, mostrar lista, información en lista",
302
+ parameters: {
303
+ type: "object",
304
+ properties: {
305
+ title: { type: "string", description: "List title" },
306
+ items: {
307
+ type: "object",
308
+ description: "Key-value pairs",
309
+ additionalProperties: { type: "string" },
310
+ },
311
+ },
312
+ required: ["title", "items"],
313
+ },
314
+ execute: async (params: Record<string, unknown>) => {
315
+ const title = params.title as string;
316
+ const items = params.items as Record<string, string>;
317
+
318
+ try {
319
+ const id = `list_${Date.now()}`;
320
+ // Render as a table with key/value columns
321
+ const columns = [{ header: "Campo", key: "key" }, { header: "Valor", key: "value" }];
322
+ const data = Object.entries(items).map(([key, value]) => ({ key, value }));
323
+
324
+ emitCanvas("canvas:render", {
325
+ component: {
326
+ id,
327
+ type: "table",
328
+ props: { title, columns, data },
329
+ position: { x: 0, y: 0 },
330
+ size: { width: 400, height: 300 },
331
+ agentId: "agent",
332
+ },
333
+ });
334
+ return { ok: true, message: `List "${title}" displayed.` };
335
+ } catch (error) {
336
+ return { ok: false, error: `Failed to display list: ${(error as Error).message }` };
337
+ }
338
+ },
339
+ };
340
+
341
+ // ─── canvas_clear ────────────────────────────────────────────────────────────
342
+
343
+ export const canvasClearTool: Tool = {
344
+ name: "canvas_clear",
345
+ description: "Clear current canvas content. Spanish: limpiar canvas, borrar visualización, resetear",
346
+ parameters: {
347
+ type: "object",
348
+ properties: {},
349
+ },
350
+ execute: async () => {
351
+ try {
352
+ emitCanvas("canvas:clear", {});
353
+ return { ok: true, message: "Canvas cleared." };
354
+ } catch (error) {
355
+ return { ok: false, error: `Failed to clear canvas: ${(error as Error).message }` };
356
+ }
357
+ },
358
+ };
359
+
360
+ export function createTools(config?: Config): Tool[] {
361
+ const a2uiConfig = config ?? {} as Config;
362
+ return [
363
+ canvasRenderTool,
364
+ canvasAskTool,
365
+ canvasConfirmTool,
366
+ canvasShowCardTool,
367
+ canvasShowProgressTool,
368
+ canvasShowListTool,
369
+ canvasClearTool,
370
+ createA2UISurfaceTool(a2uiConfig),
371
+ createA2UIUpdateComponentsTool(a2uiConfig),
372
+ createA2UIUpdateDataModelTool(a2uiConfig),
373
+ createA2UIDeleteSurfaceTool(a2uiConfig),
374
+ ];
375
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * cli_exec - Execute shell commands
3
+ *
4
+ * @category cli
5
+ * @seedId cli_exec
6
+ * @spanish ejecutar comando, terminal, bash, script, consola
7
+ */
8
+
9
+ import type { Tool } from "../types.ts";
10
+ import { logger } from "../../utils/logger.ts";
11
+ import { resolveInWorkspace, getWorkspace, expandPath } from "../filesystem/workspace-guard.ts";
12
+ import * as fs from "node:fs";
13
+
14
+ const log = logger.child("cli-exec");
15
+
16
+ /**
17
+ * Patterns that are unconditionally blocked regardless of workspace config.
18
+ * Checked against the full command string (lowercased).
19
+ */
20
+ const BLOCKED_PATTERNS: Array<{ pattern: RegExp; reason: string }> = [
21
+ { pattern: /rm\s+-rf\s+\/[^\s]*/, reason: "recursive delete from root" },
22
+ { pattern: /rm\s+-rf\s+~/, reason: "recursive delete from home" },
23
+ { pattern: />\s*\/dev\//, reason: "write to device file" },
24
+ { pattern: /mkfs/, reason: "filesystem format" },
25
+ { pattern: /dd\s+if=/, reason: "raw disk write" },
26
+ { pattern: /:\(\)\s*\{/, reason: "fork bomb pattern" },
27
+ { pattern: /del\s+\/f\s+\/s/, reason: "recursive force delete (Windows)" },
28
+ { pattern: /format\s+[a-z]:/i, reason: "disk format (Windows)" },
29
+ ];
30
+
31
+ export const cliExecTool: Tool = {
32
+ name: "cli_exec",
33
+ description: "Execute shell/bash commands in the agent workspace. NOTE: do NOT use for scheduling tasks, use cron.create instead. Spanish: ejecutar comando, terminal, bash, script, consola",
34
+ parameters: {
35
+ type: "object",
36
+ properties: {
37
+ command: {
38
+ type: "string",
39
+ description: "The shell command to execute (supports pipes, redirections, variables)",
40
+ },
41
+ timeout: {
42
+ type: "number",
43
+ description: "Timeout in seconds (default: 30, max: 300)",
44
+ },
45
+ cwd: {
46
+ type: "string",
47
+ description: "Working directory (default: agent workspace)",
48
+ },
49
+ },
50
+ required: ["command"],
51
+ },
52
+ execute: async (params: Record<string, unknown>, config?: any) => {
53
+ const command = params.command as string;
54
+ const timeoutSecs = Math.min((params.timeout as number) ?? 30, 300);
55
+ const timeoutMs = timeoutSecs * 1000;
56
+
57
+ // ── Workspace enforcement ──────────────────────────────────────────────────
58
+ const workspace = getWorkspace(config);
59
+ const defaultCwd = workspace ? expandPath(workspace) : process.cwd();
60
+
61
+ let cwd: string;
62
+ try {
63
+ const rawCwd = (params.cwd as string) ?? defaultCwd;
64
+ cwd = resolveInWorkspace(rawCwd, workspace);
65
+ } catch (e) {
66
+ return { ok: false, error: (e as Error).message };
67
+ }
68
+
69
+ // Ensure cwd exists
70
+ if (!fs.existsSync(cwd)) {
71
+ return { ok: false, error: `Working directory not found: ${cwd}` };
72
+ }
73
+
74
+ // ── Dangerous pattern check ────────────────────────────────────────────────
75
+ for (const { pattern, reason } of BLOCKED_PATTERNS) {
76
+ if (pattern.test(command)) {
77
+ return {
78
+ ok: false,
79
+ error: `Command not allowed: ${reason}`,
80
+ };
81
+ }
82
+ }
83
+
84
+ log.info(`Executing: ${command} (cwd=${cwd})`);
85
+
86
+ const t0 = performance.now();
87
+
88
+ try {
89
+ const controller = new AbortController();
90
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
91
+
92
+ // Use sh -c so pipes, redirections, variables, and quoted args all work
93
+ const proc = Bun.spawn(["/bin/sh", "-c", command], {
94
+ cwd,
95
+ signal: controller.signal,
96
+ stdout: "pipe",
97
+ stderr: "pipe",
98
+ });
99
+
100
+ let stdout = "";
101
+ let stderr = "";
102
+ let exitCode: number;
103
+
104
+ try {
105
+ [stdout, stderr] = await Promise.all([
106
+ new Response(proc.stdout).text(),
107
+ new Response(proc.stderr).text(),
108
+ ]);
109
+ exitCode = await proc.exited;
110
+ } catch {
111
+ // AbortController fired — process was killed due to timeout
112
+ exitCode = -1;
113
+ stdout = stdout || "";
114
+ stderr = stderr || `Process killed after ${timeoutSecs}s timeout`;
115
+ } finally {
116
+ clearTimeout(timeoutId);
117
+ }
118
+
119
+ const elapsedMs = Math.round(performance.now() - t0);
120
+
121
+ return {
122
+ ok: exitCode === 0,
123
+ command,
124
+ exitCode,
125
+ stdout: stdout.trim(),
126
+ stderr: stderr.trim(),
127
+ executionTimeMs: elapsedMs,
128
+ cwd,
129
+ };
130
+ } catch (error) {
131
+ log.error(`Command failed: ${(error as Error).message}`);
132
+ return {
133
+ ok: false,
134
+ error: `Command execution failed: ${(error as Error).message}`,
135
+ };
136
+ }
137
+ },
138
+ };
139
+
140
+ export function createTools(): Tool[] {
141
+ return [cliExecTool];
142
+ }