@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,113 @@
1
+ import type { Tool } from "../agent/native-tools.ts";
2
+ import { logger } from "../utils/logger.ts";
3
+
4
+ export function createReportProgressTool(): Tool {
5
+ const log = logger.child("report-progress");
6
+
7
+ return {
8
+ name: "report_progress",
9
+ description: "Report progress to the user while processing tasks in background. Use this to keep the user informed about what you're doing.",
10
+ parameters: {
11
+ type: "object",
12
+ properties: {
13
+ status: {
14
+ type: "string",
15
+ enum: ["started", "thinking", "searching", "processing", "writing", "completed", "error", "info"],
16
+ description: "Current status of the task",
17
+ },
18
+ message: {
19
+ type: "string",
20
+ description: "Progress message to show the user (keep it short and clear)",
21
+ },
22
+ progress: {
23
+ type: "number",
24
+ description: "Optional progress percentage (0-100)",
25
+ },
26
+ },
27
+ required: ["status", "message"],
28
+ },
29
+ execute: async (params: Record<string, unknown>) => {
30
+ const status = params.status as string;
31
+ const message = params.message as string;
32
+ const progress = params.progress as number | undefined;
33
+
34
+ log.debug(`Progress: [${status}] ${message}`);
35
+
36
+ // Return the progress info - it will be sent to the user by the framework
37
+ return {
38
+ success: true,
39
+ status,
40
+ message,
41
+ progress,
42
+ _sendToUser: true, // Flag to indicate this should be sent to the user
43
+ };
44
+ },
45
+ };
46
+ }
47
+
48
+ export function createNotifyTool(): Tool {
49
+ const log = logger.child("notify");
50
+
51
+ return {
52
+ name: "notify",
53
+ description: "Send a system notification or report progress to user",
54
+ parameters: {
55
+ type: "object",
56
+ properties: {
57
+ title: {
58
+ type: "string",
59
+ description: "Notification title or status (info, searching, processing, completed, error)",
60
+ },
61
+ message: {
62
+ type: "string",
63
+ description: "Message to show the user",
64
+ },
65
+ urgency: {
66
+ type: "string",
67
+ enum: ["low", "normal", "critical"],
68
+ description: "Notification urgency level (for system notifications)",
69
+ },
70
+ type: {
71
+ type: "string",
72
+ enum: ["progress", "notification"],
73
+ description: "Type of notification (progress shows to user, notification shows system popup)",
74
+ },
75
+ },
76
+ required: ["title", "message"],
77
+ },
78
+ execute: async (params: Record<string, unknown>) => {
79
+ const title = params.title as string;
80
+ const message = params.message as string;
81
+ const urgency = (params.urgency as string) ?? "normal";
82
+ const type = (params.type as string) ?? "progress";
83
+
84
+ log.debug(`Notify: [${type}] ${title} - ${message}`);
85
+
86
+ // Si es tipo progress, retornar con flag para enviar al usuario
87
+ if (type === "progress") {
88
+ return {
89
+ success: true,
90
+ status: title,
91
+ message,
92
+ _sendToUser: true,
93
+ };
94
+ }
95
+
96
+ // Para notificaciones del sistema
97
+ if (process.platform === "darwin") {
98
+ const cmd = `osascript -e 'display notification "${message}" with title "${title}"'`;
99
+ await Bun.$`${{ raw: cmd }}`.quiet();
100
+ } else if (process.platform === "linux") {
101
+ const urgencyFlag = urgency === "critical" ? "-u critical" : urgency === "low" ? "-u low" : "";
102
+ const cmd = `notify-send ${urgencyFlag} "${title}" "${message}"`;
103
+ await Bun.$`${{ raw: cmd }}`.quiet().catch(() => {
104
+ log.warn("notify-send not available");
105
+ });
106
+ } else {
107
+ log.warn(`Notifications not supported on ${process.platform}`);
108
+ }
109
+
110
+ return { success: true, title, message };
111
+ },
112
+ };
113
+ }
@@ -0,0 +1,376 @@
1
+ import { getDb } from "../storage/sqlite";
2
+ import { dbService } from "../storage/sqlite";
3
+ import type { Tool } from "../agent/native-tools";
4
+ import { logger } from "../utils/logger";
5
+ import { getUserDate, getUserTime } from "../utils/date";
6
+ import { emitCanvas } from "../canvas/emitter";
7
+
8
+ const log = logger.child("project-tools");
9
+
10
+ // ── project_create ────────────────────────────────────────────────────────────
11
+ // Crea un proyecto completo con sus tareas iniciales en una sola llamada.
12
+ // Emite nodos canvas para el proyecto y cada tarea.
13
+ export const projectCreateTool: Tool = {
14
+ name: "project_create",
15
+ description: "Crea un proyecto con sus tareas. Úsalo al inicio de cualquier tarea compleja (más de 2 pasos, código, investigación, etc.). Permite definir el tipo de proyecto y las tareas atómicas que lo componen.",
16
+ parameters: {
17
+ type: "object",
18
+ properties: {
19
+ name: { type: "string", description: "Nombre descriptivo del proyecto" },
20
+ description: { type: "string", description: "Descripción detallada del objetivo del proyecto" },
21
+ type: {
22
+ type: "string",
23
+ enum: ["general", "code", "research", "content", "data"],
24
+ description: "Tipo de proyecto: code (desarrollo), research (investigación), content (escritura/docs), data (análisis), general (otro)"
25
+ },
26
+ tasks: {
27
+ type: "array",
28
+ description: "Lista de tareas atómicas que componen el proyecto",
29
+ items: {
30
+ type: "object",
31
+ properties: {
32
+ name: { type: "string", description: "Nombre corto de la tarea" },
33
+ description: { type: "string", description: "Qué debe hacer esta tarea" },
34
+ agent_id: { type: "string", description: "ID del agente asignado (opcional)" },
35
+ },
36
+ required: ["name"],
37
+ },
38
+ },
39
+ },
40
+ required: ["name", "type"],
41
+ },
42
+ execute: async (params: Record<string, unknown>, config?: any) => {
43
+ const name = params.name as string;
44
+ const description = (params.description as string) ?? "";
45
+ const type = (params.type as string) ?? "general";
46
+ const tasks = (params.tasks as any[]) ?? [];
47
+
48
+ const db = getDb();
49
+ const userId = config?.configurable?.user_id || process.env.HIVE_USER_ID;
50
+ if (!userId) throw new Error("User ID requerido.");
51
+
52
+ const agentId = config?.configurable?.agent_id ?? null;
53
+
54
+ const userRow = db.query<any, [string]>("SELECT timezone FROM users WHERE id = ?").get(userId);
55
+ const userTimezone = userRow?.timezone || "UTC";
56
+ const fecha = getUserDate(userTimezone);
57
+ const hora = getUserTime(userTimezone);
58
+
59
+ const projectId = crypto.randomUUID().replace(/-/g, "").slice(0, 16);
60
+ log.info(`Creating project: ${name} (${type}) ID: ${projectId} [${fecha} ${hora}]`);
61
+
62
+ db.query(`
63
+ INSERT INTO projects (id, user_id, agent_id, name, description, type, task, status, created_at, updated_at)
64
+ VALUES (?, ?, ?, ?, ?, ?, ?, 'active', unixepoch(), unixepoch())
65
+ `).run(projectId, userId, agentId, name, description, type, description);
66
+
67
+ // Emitir nodo de proyecto al canvas
68
+ emitCanvas("canvas:node_add", {
69
+ id: `project_${projectId}`,
70
+ type: "project",
71
+ label: name,
72
+ data: { status: "active", progress: 0, projectType: type, taskCount: tasks.length, agentId },
73
+ });
74
+
75
+ // Crear tareas
76
+ const taskIds: number[] = [];
77
+ for (const t of tasks) {
78
+ const taskId = dbService.createTask({
79
+ project_id: projectId,
80
+ agent_id: t.agent_id ?? agentId,
81
+ name: t.name,
82
+ description: t.description ?? null,
83
+ });
84
+ taskIds.push(taskId);
85
+
86
+ emitCanvas("canvas:node_add", {
87
+ id: `task_${taskId}`,
88
+ type: "task",
89
+ label: t.name,
90
+ data: { status: "pending", progress: 0, projectId, agentId: t.agent_id ?? agentId },
91
+ });
92
+ emitCanvas("canvas:edge_add", {
93
+ id: `edge_proj_task_${taskId}`,
94
+ source: `project_${projectId}`,
95
+ target: `task_${taskId}`,
96
+ edgeType: "contains",
97
+ });
98
+
99
+ // Si hay agente asignado, conectar tarea → agente
100
+ if (t.agent_id || agentId) {
101
+ emitCanvas("canvas:edge_add", {
102
+ id: `edge_task_agent_${taskId}`,
103
+ source: `task_${taskId}`,
104
+ target: t.agent_id ?? agentId,
105
+ edgeType: "assigned_to",
106
+ });
107
+ }
108
+ }
109
+
110
+ return {
111
+ success: true,
112
+ projectId,
113
+ taskIds,
114
+ message: `Proyecto "${name}" creado con ${tasks.length} tarea(s).`,
115
+ };
116
+ },
117
+ };
118
+
119
+ // ── project_start (legacy, mantiene compatibilidad) ───────────────────────────
120
+ export const projectStartTool: Tool = {
121
+ name: "project_start",
122
+ description: "Crea un nuevo proyecto para tareas que requieren más de dos pasos. Proporciona un nombre descriptivo y la tarea general.",
123
+ parameters: {
124
+ type: "object",
125
+ properties: {
126
+ name: { type: "string", description: "Nombre descriptivo del proyecto" },
127
+ task: { type: "string", description: "Descripción de la tarea general" },
128
+ parentId: { type: "string", description: "ID del proyecto padre (opcional, para subproyectos)" },
129
+ },
130
+ required: ["name", "task"],
131
+ },
132
+ execute: async (params: Record<string, unknown>, config?: any) => {
133
+ const name = params.name as string;
134
+ const task = params.task as string;
135
+ const parentId = params.parentId as string | undefined;
136
+ const db = getDb();
137
+ const id = crypto.randomUUID().replace(/-/g, "").slice(0, 16);
138
+ const userId = config?.configurable?.user_id || process.env.HIVE_USER_ID;
139
+ if (!userId) throw new Error("User ID is required to create a project.");
140
+ const agentId = config?.configurable?.agent_id || "main";
141
+
142
+ const userRow = db.query<any, [string]>("SELECT timezone FROM users WHERE id = ?").get(userId);
143
+ const userTimezone = userRow?.timezone || "UTC";
144
+ const fecha_usuario = getUserDate(userTimezone);
145
+ const hora_usuario = getUserTime(userTimezone);
146
+
147
+ log.info(`Creating project: ${name} (ID: ${id}) [${fecha_usuario} ${hora_usuario}]`);
148
+
149
+ db.query(`
150
+ INSERT INTO projects (id, user_id, agent_id, name, task, parent_id, status, created_at, updated_at)
151
+ VALUES (?, ?, ?, ?, ?, ?, 'active', unixepoch(), unixepoch())
152
+ `).run(id, userId, agentId, name, task, parentId ?? null);
153
+
154
+ emitCanvas("canvas:node_add", {
155
+ id: `project_${id}`,
156
+ type: "project",
157
+ label: name,
158
+ data: { status: "active", progress: 0, projectType: "general", agentId },
159
+ });
160
+
161
+ return { success: true, projectId: id, message: `Proyecto "${name}" iniciado.` };
162
+ },
163
+ };
164
+
165
+ // ── project_update ────────────────────────────────────────────────────────────
166
+ export const projectUpdateTool: Tool = {
167
+ name: "project_update",
168
+ description: "Actualiza el progreso y describe el siguiente paso de un proyecto activo. Úsalo antes de cada paso importante.",
169
+ parameters: {
170
+ type: "object",
171
+ properties: {
172
+ projectId: { type: "string", description: "ID del proyecto a actualizar" },
173
+ progress: { type: "number", description: "Progreso actual (0-100)" },
174
+ stepDescription: { type: "string", description: "Descripción del paso actual o siguiente" },
175
+ },
176
+ required: ["projectId", "progress", "stepDescription"],
177
+ },
178
+ execute: async (params: { projectId: string; progress: number; stepDescription: string }) => {
179
+ const db = getDb();
180
+ log.info(`Updating project ${params.projectId}: ${params.progress}% - ${params.stepDescription}`);
181
+
182
+ const result = db.query(`
183
+ UPDATE projects SET progress = ?, context = ?, updated_at = unixepoch() WHERE id = ?
184
+ `).run(params.progress, params.stepDescription, params.projectId);
185
+
186
+ if (result.changes === 0) throw new Error(`Proyecto con ID ${params.projectId} no encontrado.`);
187
+
188
+ emitCanvas("canvas:node_update", {
189
+ id: `project_${params.projectId}`,
190
+ data: { progress: params.progress, lastStep: params.stepDescription },
191
+ });
192
+
193
+ return { success: true, message: `Proyecto actualizado: ${params.progress}% - ${params.stepDescription}` };
194
+ },
195
+ };
196
+
197
+ // ── project_done ──────────────────────────────────────────────────────────────
198
+ export const projectDoneTool: Tool = {
199
+ name: "project_done",
200
+ description: "Marca un proyecto como completado exitosamente cuando la tarea ha terminado.",
201
+ parameters: {
202
+ type: "object",
203
+ properties: {
204
+ projectId: { type: "string", description: "ID del proyecto completado" },
205
+ summary: { type: "string", description: "Resumen final de lo logrado" },
206
+ },
207
+ required: ["projectId"],
208
+ },
209
+ execute: async (params: { projectId: string; summary?: string }) => {
210
+ const db = getDb();
211
+ log.info(`Completing project ${params.projectId}`);
212
+
213
+ const result = db.query(`
214
+ UPDATE projects SET status = 'done', progress = 100, updated_at = unixepoch(), completed_at = unixepoch(), context = ?
215
+ WHERE id = ?
216
+ `).run(params.summary ?? "Tarea completada exitosamente.", params.projectId);
217
+
218
+ if (result.changes === 0) throw new Error(`Proyecto con ID ${params.projectId} no encontrado.`);
219
+
220
+ emitCanvas("canvas:node_update", {
221
+ id: `project_${params.projectId}`,
222
+ data: { status: "done", progress: 100 },
223
+ });
224
+
225
+ return { success: true, message: "Proyecto marcado como completado." };
226
+ },
227
+ };
228
+
229
+ // ── project_fail ──────────────────────────────────────────────────────────────
230
+ export const projectFailTool: Tool = {
231
+ name: "project_fail",
232
+ description: "Marca un proyecto como fallido cuando ocurre un error irrecuperable.",
233
+ parameters: {
234
+ type: "object",
235
+ properties: {
236
+ projectId: { type: "string", description: "ID del proyecto fallido" },
237
+ reason: { type: "string", description: "Razón del fallo" },
238
+ },
239
+ required: ["projectId", "reason"],
240
+ },
241
+ execute: async (params: { projectId: string; reason: string }) => {
242
+ const db = getDb();
243
+ log.error(`Project ${params.projectId} failed: ${params.reason}`);
244
+
245
+ const result = db.query(`
246
+ UPDATE projects SET status = 'failed', updated_at = unixepoch(), context = ? WHERE id = ?
247
+ `).run(params.reason, params.projectId);
248
+
249
+ if (result.changes === 0) throw new Error(`Proyecto con ID ${params.projectId} no encontrado.`);
250
+
251
+ emitCanvas("canvas:node_update", {
252
+ id: `project_${params.projectId}`,
253
+ data: { status: "failed" },
254
+ });
255
+
256
+ return { success: true, message: "Proyecto marcado como fallido." };
257
+ },
258
+ };
259
+
260
+ // ── task_create ───────────────────────────────────────────────────────────────
261
+ export const taskCreateTool: Tool = {
262
+ name: "task_create",
263
+ description: "Agrega una nueva tarea a un proyecto existente. Opcionalmente asigna un agente responsable.",
264
+ parameters: {
265
+ type: "object",
266
+ properties: {
267
+ project_id: { type: "string", description: "ID del proyecto al que pertenece la tarea" },
268
+ name: { type: "string", description: "Nombre corto de la tarea" },
269
+ description: { type: "string", description: "Qué debe realizar esta tarea" },
270
+ agent_id: { type: "string", description: "ID del agente responsable (opcional)" },
271
+ },
272
+ required: ["project_id", "name"],
273
+ },
274
+ execute: async (params: Record<string, unknown>) => {
275
+ const taskId = dbService.createTask({
276
+ project_id: params.project_id as string,
277
+ agent_id: (params.agent_id as string) ?? null,
278
+ name: params.name as string,
279
+ description: (params.description as string) ?? null,
280
+ });
281
+
282
+ emitCanvas("canvas:node_add", {
283
+ id: `task_${taskId}`,
284
+ type: "task",
285
+ label: params.name,
286
+ data: { status: "pending", progress: 0, projectId: params.project_id, agentId: params.agent_id ?? null },
287
+ });
288
+ emitCanvas("canvas:edge_add", {
289
+ id: `edge_proj_task_${taskId}`,
290
+ source: `project_${params.project_id}`,
291
+ target: `task_${taskId}`,
292
+ edgeType: "contains",
293
+ });
294
+
295
+ if (params.agent_id) {
296
+ emitCanvas("canvas:edge_add", {
297
+ id: `edge_task_agent_${taskId}`,
298
+ source: `task_${taskId}`,
299
+ target: params.agent_id as string,
300
+ edgeType: "assigned_to",
301
+ });
302
+ }
303
+
304
+ return { success: true, taskId, message: `Tarea "${params.name}" creada (ID: ${taskId}).` };
305
+ },
306
+ };
307
+
308
+ // ── task_update ───────────────────────────────────────────────────────────────
309
+ export const taskUpdateTool: Tool = {
310
+ name: "task_update",
311
+ description: "Actualiza el estado y progreso de una tarea. Recalcula automáticamente el progreso del proyecto padre.",
312
+ parameters: {
313
+ type: "object",
314
+ properties: {
315
+ task_id: { type: "number", description: "ID numérico de la tarea" },
316
+ status: {
317
+ type: "string",
318
+ enum: ["pending", "in_progress", "completed", "failed", "blocked"],
319
+ description: "Nuevo estado de la tarea"
320
+ },
321
+ progress: { type: "number", description: "Progreso de la tarea (0-100)" },
322
+ result: { type: "string", description: "Resultado o output de la tarea (al completarla)" },
323
+ agent_id: { type: "string", description: "Reasignar a otro agente (opcional)" },
324
+ },
325
+ required: ["task_id"],
326
+ },
327
+ execute: async (params: Record<string, unknown>) => {
328
+ const taskId = params.task_id as number;
329
+ const db = getDb();
330
+
331
+ const updated = dbService.updateTask(taskId, {
332
+ status: params.status as string | undefined,
333
+ progress: params.progress as number | undefined,
334
+ result: params.result as string | undefined,
335
+ agent_id: params.agent_id as string | undefined,
336
+ });
337
+ if (!updated) throw new Error(`Tarea con ID ${taskId} no encontrada.`);
338
+
339
+ // Obtener project_id para recalcular progreso del proyecto
340
+ const taskRow = db.query<any, [number]>("SELECT project_id FROM tasks WHERE id = ?").get(taskId);
341
+ let projectProgress = 0;
342
+ if (taskRow?.project_id) {
343
+ projectProgress = dbService.recalcProjectProgress(taskRow.project_id);
344
+ emitCanvas("canvas:node_update", {
345
+ id: `project_${taskRow.project_id}`,
346
+ data: { progress: projectProgress },
347
+ });
348
+ }
349
+
350
+ // Si hay nuevo agente asignado, agregar edge canvas
351
+ if (params.agent_id && taskRow?.project_id) {
352
+ emitCanvas("canvas:edge_add", {
353
+ id: `edge_task_agent_${taskId}`,
354
+ source: `task_${taskId}`,
355
+ target: params.agent_id as string,
356
+ edgeType: "assigned_to",
357
+ });
358
+ }
359
+
360
+ emitCanvas("canvas:node_update", {
361
+ id: `task_${taskId}`,
362
+ data: {
363
+ status: params.status,
364
+ progress: params.progress,
365
+ agentId: params.agent_id,
366
+ },
367
+ });
368
+
369
+ return {
370
+ success: true,
371
+ taskId,
372
+ projectProgress,
373
+ message: `Tarea ${taskId} actualizada: ${params.status ?? ""}${params.progress !== undefined ? ` ${params.progress}%` : ""}`,
374
+ };
375
+ },
376
+ };