@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,185 @@
1
+ import { onboard } from "./commands/onboard";
2
+ import { start, stop, status, reload } from "./commands/gateway";
3
+ import { dev } from "./commands/dev";
4
+ import { agents } from "./commands/agents";
5
+ import { mcp } from "./commands/mcp";
6
+ import { skills } from "./commands/skills";
7
+ import { config } from "./commands/config";
8
+ import { logs } from "./commands/logs";
9
+ import { chat } from "./commands/chat";
10
+ import { sessions } from "./commands/sessions";
11
+ import { cron } from "./commands/cron";
12
+ import { doctor } from "./commands/doctor";
13
+ import { securityAudit } from "./commands/security";
14
+ import { installService } from "./commands/service";
15
+ import { update } from "./commands/update";
16
+ import { message } from "./commands/message";
17
+ import { agent } from "./commands/agent-run";
18
+
19
+ const VERSION = "1.1.0";
20
+
21
+ const HELP = `
22
+ 🐝 Hive — Personal Swarm AI Gateway v${VERSION}
23
+
24
+ Usage: hive <command> [subcommand] [options]
25
+
26
+ Commands:
27
+ onboard Wizard de configuración inicial
28
+ dev Modo desarrollo (onboard + start)
29
+ start [--daemon] Arrancar el Gateway
30
+ stop Detener el Gateway
31
+ reload Recargar config sin reiniciar
32
+ status Estado del Gateway y agentes
33
+ chat [--agent <id>] Chat directo en terminal
34
+ logs [--follow] [--level] Ver logs del Gateway
35
+
36
+ message send --to <id> --content <text>
37
+ Enviar mensaje por canal
38
+ agent run --message <text> Ejecutar agente con mensaje
39
+
40
+ agents add <id> Crear nuevo agente
41
+ agents list [--bindings] Listar agentes
42
+ agents remove <id> Eliminar agente
43
+
44
+ mcp list Listar servidores MCP
45
+ mcp add Añadir servidor MCP
46
+ mcp test <nombre> Verificar servidor MCP
47
+ mcp tools <nombre> Listar tools de un servidor
48
+ mcp remove <nombre> Eliminar servidor MCP
49
+
50
+ skills list Listar skills instaladas
51
+ skills search <query> Buscar skills
52
+ skills install <slug> Instalar skill
53
+ skills remove <nombre> Eliminar skill
54
+ skills update Actualizar skills
55
+
56
+ config get <key> Leer valor de config
57
+ config set <key> <value> Escribir valor de config
58
+ config show Mostrar config completa
59
+
60
+ sessions list Listar sesiones
61
+ sessions view <id> Ver transcripción
62
+ sessions prune Eliminar sesiones inactivas
63
+
64
+ cron list Listar cron jobs
65
+ cron add Añadir cron job
66
+ cron remove <id> Eliminar cron job
67
+ cron logs Ver logs de cron
68
+
69
+ doctor Diagnóstico y auto-reparación
70
+ security audit Auditoría de seguridad
71
+ install-service Instalar servicio systemd
72
+ update Actualizar Hive
73
+
74
+ Options:
75
+ --help, -h Mostrar ayuda
76
+ --version, -v Mostrar versión
77
+
78
+ Examples:
79
+ hive onboard Configurar Hive por primera vez
80
+ hive start Arrancar el Gateway
81
+ hive chat Chatear con el agente en terminal
82
+ hive message send --to 123 --content "Hola"
83
+ hive agent run --message "Analiza README.md" --wait
84
+ hive agents add work Crear agente "work"
85
+ hive mcp add Añadir servidor MCP
86
+ hive doctor Diagnosticar problemas
87
+
88
+ Documentation:
89
+ English: https://github.com/johpaz/hive/docs
90
+ Español: https://github.com/johpaz/hive/docs/es
91
+ `;
92
+
93
+ async function main(): Promise<void> {
94
+ const args = process.argv.slice(2);
95
+ const command = args[0];
96
+ const subcommand = args[1];
97
+ const flags = args.filter((a) => a.startsWith("--"));
98
+
99
+ switch (command) {
100
+ case "onboard":
101
+ await onboard();
102
+ break;
103
+ case "dev":
104
+ await dev();
105
+ break;
106
+ case "start":
107
+ await start(flags);
108
+ break;
109
+ case "stop":
110
+ await stop();
111
+ break;
112
+ case "reload":
113
+ await reload();
114
+ break;
115
+ case "status":
116
+ await status(flags);
117
+ break;
118
+ case "chat":
119
+ await chat(flags);
120
+ break;
121
+ case "logs":
122
+ await logs(flags);
123
+ break;
124
+ case "message":
125
+ await message(subcommand, args.slice(2));
126
+ break;
127
+ case "agent":
128
+ await agent(subcommand, args.slice(2));
129
+ break;
130
+ case "agents":
131
+ await agents(subcommand, args.slice(2));
132
+ break;
133
+ case "mcp":
134
+ await mcp(subcommand, args.slice(2));
135
+ break;
136
+ case "skills":
137
+ await skills(subcommand, args.slice(2));
138
+ break;
139
+ case "config":
140
+ await config(subcommand, args.slice(2));
141
+ break;
142
+ case "sessions":
143
+ await sessions(subcommand, args.slice(2));
144
+ break;
145
+ case "cron":
146
+ await cron(subcommand, args.slice(2));
147
+ break;
148
+ case "doctor":
149
+ await doctor();
150
+ break;
151
+ case "security":
152
+ if (subcommand === "audit") {
153
+ await securityAudit();
154
+ } else {
155
+ console.log("Usage: hive security audit");
156
+ }
157
+ break;
158
+ case "install-service":
159
+ await installService();
160
+ break;
161
+ case "update":
162
+ await update();
163
+ break;
164
+ case "--version":
165
+ case "-v":
166
+ case "version":
167
+ console.log(`Hive v${VERSION}`);
168
+ break;
169
+ case "--help":
170
+ case "-h":
171
+ case "help":
172
+ case undefined:
173
+ console.log(HELP);
174
+ break;
175
+ default:
176
+ console.error(`❌ Comando desconocido: "${command}"\n`);
177
+ console.log(HELP);
178
+ process.exit(1);
179
+ }
180
+ }
181
+
182
+ main().catch((error) => {
183
+ console.error("Fatal error:", error.message);
184
+ process.exit(1);
185
+ });
@@ -0,0 +1,6 @@
1
+ import { randomBytes } from "node:crypto"
2
+
3
+ export function generateAuthToken(): string {
4
+ // 32 bytes → 43 caracteres base64url seguros
5
+ return randomBytes(32).toString("base64url")
6
+ }
@@ -0,0 +1,78 @@
1
+ # @johpaz/hive-code-bridge
2
+
3
+ Code Bridge for the Hive ecosystem. Exposes a **local WebSocket mesh** (via `Bun.serve`) that spawns and supervises CLI AI tools (e.g. `opencode`, `gemini`, `qwen`) as child processes, streaming their output and telemetry to any connected dashboard client.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ Dashboard (Angular) ──WS──► @johpaz/hive-code-bridge ──Bun.spawn──► CLI tool (opencode / gemini / qwen…)
9
+ ◄──WS──── telemetry events (stdout chunks, progress, tokens)
10
+ ```
11
+
12
+ ## Files
13
+
14
+ | File | Purpose |
15
+ |---|---|
16
+ | `src/index.ts` | Bun.serve entry point — HTTP health + WebSocket handler |
17
+ | `src/schemas.ts` | Zod schemas: `AgentRole`, `SubagentConfig`, `TelemetryEvent`, `DashboardCommand` |
18
+ | `src/process-manager.ts` | Spawns/kills processes, streams stdout/stderr, parses `HIVE_PROGRESS` and `HIVE_TOKENS` hints |
19
+
20
+ ## Running
21
+
22
+ ```bash
23
+ # From the monorepo root:
24
+ bun run packages/code-bridge/src/index.ts
25
+
26
+ # Or from this package:
27
+ bun run src/index.ts
28
+ ```
29
+
30
+ Server starts on `ws://localhost:18791` by default (`CODE_BRIDGE_PORT` env override).
31
+
32
+ ## WebSocket Protocol
33
+
34
+ ### Dashboard → Code Bridge
35
+
36
+ ```jsonc
37
+ // Launch a CLI subagent
38
+ { "cmd": "launch", "taskId": "uuid", "prompt": "Refactor auth module", "config": { "role": "development", "cli": "opencode" } }
39
+
40
+ // Cancel a running agent
41
+ { "cmd": "cancel", "taskId": "uuid" }
42
+
43
+ // Request current status snapshot
44
+ { "cmd": "status" }
45
+ ```
46
+
47
+ ### Code Bridge → Dashboard (broadcast)
48
+
49
+ | Event type | Description |
50
+ |---|---|
51
+ | `code-bridge:status` | Full snapshot of all agents (sent on connect and on `status` command) |
52
+ | `agent:started` | Process PID and CLI tool |
53
+ | `agent:output` | stdout/stderr chunk |
54
+ | `agent:progress` | 0–100 percent (from `HIVE_PROGRESS:<n>` in stdout) |
55
+ | `agent:token_usage` | Token counters + model (from `HIVE_TOKENS:input=<n>,output=<n>,model=<name>`) |
56
+ | `agent:finished` | Exit code |
57
+ | `agent:cancelled` | Cancelled by dashboard |
58
+ | `agent:error` | Non-zero exit or spawn error |
59
+
60
+ ## HIVE Protocol Hints
61
+
62
+ CLI tools can emit structured hints in their stdout to feed the dashboard:
63
+
64
+ ```
65
+ HIVE_PROGRESS:42
66
+ HIVE_TOKENS:input=1200,output=350,model=gemini-2.0-flash
67
+ ```
68
+
69
+ ## Tests
70
+
71
+ ```bash
72
+ bun test src/schemas.test.ts
73
+ bun test src/process-manager.test.ts
74
+ # or all at once:
75
+ bun test
76
+ ```
77
+
78
+ Tests cover: schema validation, launch/cancel lifecycle, stdout streaming, progress/token hint parsing.
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "@johpaz/hive-code-bridge",
3
+ "version": "1.1.0",
4
+ "private": true,
5
+ "description": "Code Bridge - Bridge between CLI tools and Dashboard via WebSocket",
6
+ "type": "module",
7
+ "main": "src/index.ts",
8
+ "scripts": {
9
+ "dev": "bun run src/index.ts"
10
+ },
11
+ "dependencies": {
12
+ "@johpaz/hive-mcp": "^1.1.0",
13
+ "zod": "^4.3.6"
14
+ },
15
+ "peerDependencies": {
16
+ "@johpaz/hive-core": "^1.1.0"
17
+ }
18
+ }
@@ -0,0 +1,95 @@
1
+ import { ProcessManager } from "./process-manager.ts";
2
+ import { DashboardCommand } from "./schemas.ts";
3
+
4
+ const CODE_BRIDGE_PORT = parseInt(process.env.CODE_BRIDGE_PORT ?? "18791", 10);
5
+
6
+ const manager = new ProcessManager();
7
+
8
+ const server = Bun.serve<{ id: string }>({
9
+ port: CODE_BRIDGE_PORT,
10
+
11
+ // ── HTTP routes ──────────────────────────────────────────────────────────
12
+ fetch(req, server) {
13
+ const url = new URL(req.url);
14
+
15
+ // Upgrade WebSocket connections
16
+ if (url.pathname === "/ws") {
17
+ const id = crypto.randomUUID();
18
+ const upgraded = server.upgrade(req, { data: { id } });
19
+ if (upgraded) return undefined;
20
+ return new Response("WebSocket upgrade failed", { status: 400 });
21
+ }
22
+
23
+ // Simple REST ping
24
+ if (url.pathname === "/health") {
25
+ return new Response(JSON.stringify({ ok: true, port: CODE_BRIDGE_PORT }), {
26
+ headers: { "Content-Type": "application/json" },
27
+ });
28
+ }
29
+
30
+ // Status snapshot (REST fallback for the dashboard initial load)
31
+ if (url.pathname === "/status") {
32
+ return new Response(JSON.stringify(manager.status()), {
33
+ headers: { "Content-Type": "application/json" },
34
+ });
35
+ }
36
+
37
+ return new Response("Not found", { status: 404 });
38
+ },
39
+
40
+ // ── WebSocket handlers ───────────────────────────────────────────────────
41
+ websocket: {
42
+ open(ws) {
43
+ manager.subscribe(ws);
44
+ // Immediately send the current snapshot to the newly connected client
45
+ ws.send(JSON.stringify(manager.status()));
46
+ },
47
+
48
+ async message(ws, raw) {
49
+ let parsed: unknown;
50
+ try {
51
+ parsed = JSON.parse(typeof raw === "string" ? raw : new TextDecoder().decode(raw));
52
+ } catch {
53
+ ws.send(JSON.stringify({ type: "error", message: "Invalid JSON" }));
54
+ return;
55
+ }
56
+
57
+ const cmd = DashboardCommand.safeParse(parsed);
58
+ if (!cmd.success) {
59
+ ws.send(JSON.stringify({ type: "error", message: cmd.error.message }));
60
+ return;
61
+ }
62
+
63
+ const command = cmd.data;
64
+
65
+ switch (command.cmd) {
66
+ case "launch": {
67
+ try {
68
+ const pid = await manager.launch(command.taskId, command.config, command.prompt);
69
+ ws.send(JSON.stringify({ type: "ack", cmd: "launch", taskId: command.taskId, pid }));
70
+ } catch (err: any) {
71
+ ws.send(JSON.stringify({ type: "error", message: err.message }));
72
+ }
73
+ break;
74
+ }
75
+
76
+ case "cancel": {
77
+ const ok = manager.cancel(command.taskId);
78
+ ws.send(JSON.stringify({ type: "ack", cmd: "cancel", taskId: command.taskId, ok }));
79
+ break;
80
+ }
81
+
82
+ case "status": {
83
+ ws.send(JSON.stringify(manager.status()));
84
+ break;
85
+ }
86
+ }
87
+ },
88
+
89
+ close(ws) {
90
+ manager.unsubscribe(ws);
91
+ },
92
+ },
93
+ });
94
+
95
+ console.log(`🌉 Hive Code Bridge running on ws://localhost:${server.port}`);
@@ -0,0 +1,212 @@
1
+ import type { ServerWebSocket } from "bun";
2
+ import type { SubagentConfig, TelemetryEvent } from "./schemas.ts";
3
+ import { AgentRole } from "./schemas.ts";
4
+
5
+ /** Live record of a running subagent process */
6
+ interface AgentRecord {
7
+ taskId: string;
8
+ config: SubagentConfig;
9
+ pid: number;
10
+ proc: ReturnType<typeof Bun.spawn>;
11
+ state: "running" | "finished" | "cancelled" | "error";
12
+ progress: number;
13
+ tokens: { input: number; output: number };
14
+ model?: string;
15
+ startedAt: number;
16
+ }
17
+
18
+ type WsData = { id: string };
19
+
20
+ export class ProcessManager {
21
+ private agents = new Map<string, AgentRecord>();
22
+ private sockets = new Set<ServerWebSocket<WsData>>();
23
+
24
+ // ── Socket subscription ──────────────────────────────────────────────────
25
+
26
+ subscribe(ws: ServerWebSocket<WsData>) {
27
+ this.sockets.add(ws);
28
+ }
29
+
30
+ unsubscribe(ws: ServerWebSocket<WsData>) {
31
+ this.sockets.delete(ws);
32
+ }
33
+
34
+ private broadcast(event: TelemetryEvent) {
35
+ const payload = JSON.stringify(event);
36
+ for (const ws of this.sockets) {
37
+ try {
38
+ ws.send(payload);
39
+ } catch {
40
+ this.sockets.delete(ws);
41
+ }
42
+ }
43
+ }
44
+
45
+ // ── Launch ───────────────────────────────────────────────────────────────
46
+
47
+ async launch(taskId: string, config: SubagentConfig, prompt: string) {
48
+ if (this.agents.has(taskId)) {
49
+ throw new Error(`Task ${taskId} is already running`);
50
+ }
51
+
52
+ const args = [config.cli, ...config.args];
53
+ const proc = Bun.spawn(args, {
54
+ cwd: config.cwd ?? process.cwd(),
55
+ stdin: "pipe",
56
+ stdout: "pipe",
57
+ stderr: "pipe",
58
+ env: { ...process.env, HIVE_ROLE: config.role },
59
+ });
60
+
61
+ const record: AgentRecord = {
62
+ taskId,
63
+ config,
64
+ pid: proc.pid,
65
+ proc,
66
+ state: "running",
67
+ progress: 0,
68
+ tokens: { input: 0, output: 0 },
69
+ startedAt: Date.now(),
70
+ };
71
+
72
+ this.agents.set(taskId, record);
73
+
74
+ this.broadcast({
75
+ type: "agent:started",
76
+ ts: Date.now(),
77
+ role: config.role,
78
+ pid: proc.pid,
79
+ cli: config.cli,
80
+ taskId,
81
+ });
82
+
83
+ // Write the prompt to stdin then close it
84
+ proc.stdin.write(prompt);
85
+ proc.stdin.end();
86
+
87
+ // Stream stdout
88
+ this.pipeStream(record, proc.stdout, "stdout");
89
+ // Stream stderr
90
+ this.pipeStream(record, proc.stderr, "stderr");
91
+
92
+ // Wait for process exit
93
+ proc.exited.then((exitCode) => {
94
+ const r = this.agents.get(taskId);
95
+ if (!r || r.state === "cancelled") return;
96
+ r.state = exitCode === 0 ? "finished" : "error";
97
+ if (exitCode === 0) {
98
+ this.broadcast({
99
+ type: "agent:finished",
100
+ ts: Date.now(),
101
+ role: config.role,
102
+ taskId,
103
+ exitCode,
104
+ });
105
+ } else {
106
+ this.broadcast({
107
+ type: "agent:error",
108
+ ts: Date.now(),
109
+ role: config.role,
110
+ taskId,
111
+ message: `Process exited with code ${exitCode}`,
112
+ });
113
+ }
114
+ });
115
+
116
+ return record.pid;
117
+ }
118
+
119
+ // ── Cancel ───────────────────────────────────────────────────────────────
120
+
121
+ cancel(taskId: string) {
122
+ const record = this.agents.get(taskId);
123
+ if (!record) return false;
124
+ record.state = "cancelled";
125
+ record.proc.kill();
126
+ this.broadcast({
127
+ type: "agent:cancelled",
128
+ ts: Date.now(),
129
+ role: record.config.role,
130
+ taskId,
131
+ });
132
+ return true;
133
+ }
134
+
135
+ // ── Status snapshot ──────────────────────────────────────────────────────
136
+
137
+ status(): TelemetryEvent {
138
+ return {
139
+ type: "code-bridge:status",
140
+ ts: Date.now(),
141
+ agents: [...this.agents.values()].map((r) => ({
142
+ taskId: r.taskId,
143
+ role: r.config.role,
144
+ cli: r.config.cli,
145
+ pid: r.pid,
146
+ state: r.state,
147
+ progress: r.progress,
148
+ tokens: r.tokens,
149
+ model: r.model,
150
+ })),
151
+ };
152
+ }
153
+
154
+ // ── Helpers ──────────────────────────────────────────────────────────────
155
+
156
+ private async pipeStream(
157
+ record: AgentRecord,
158
+ stream: ReadableStream<Uint8Array>,
159
+ kind: "stdout" | "stderr"
160
+ ) {
161
+ const reader = stream.getReader();
162
+ const decoder = new TextDecoder();
163
+ try {
164
+ while (true) {
165
+ const { done, value } = await reader.read();
166
+ if (done) break;
167
+ const chunk = decoder.decode(value);
168
+
169
+ // Parse progress hints: lines containing "HIVE_PROGRESS:<n>"
170
+ for (const line of chunk.split("\n")) {
171
+ const m = line.match(/HIVE_PROGRESS:(\d+)/);
172
+ if (m) {
173
+ record.progress = Math.min(100, parseInt(m[1], 10));
174
+ this.broadcast({
175
+ type: "agent:progress",
176
+ ts: Date.now(),
177
+ role: record.config.role,
178
+ taskId: record.taskId,
179
+ percent: record.progress,
180
+ });
181
+ }
182
+ // Parse token hints: "HIVE_TOKENS:input=<n>,output=<n>,model=<name>"
183
+ const t = line.match(/HIVE_TOKENS:input=(\d+),output=(\d+)(?:,model=(.+))?/);
184
+ if (t) {
185
+ record.tokens = { input: parseInt(t[1], 10), output: parseInt(t[2], 10) };
186
+ if (t[3]) record.model = t[3].trim();
187
+ this.broadcast({
188
+ type: "agent:token_usage",
189
+ ts: Date.now(),
190
+ role: record.config.role,
191
+ taskId: record.taskId,
192
+ inputTokens: record.tokens.input,
193
+ outputTokens: record.tokens.output,
194
+ model: record.model ?? "unknown",
195
+ });
196
+ }
197
+ }
198
+
199
+ this.broadcast({
200
+ type: "agent:output",
201
+ ts: Date.now(),
202
+ role: record.config.role,
203
+ taskId: record.taskId,
204
+ stream: kind,
205
+ chunk,
206
+ });
207
+ }
208
+ } catch {
209
+ // stream closed
210
+ }
211
+ }
212
+ }