@johpaz/hive 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +44 -0
- package/README.md +310 -0
- package/package.json +96 -0
- package/packages/cli/package.json +28 -0
- package/packages/cli/src/commands/agent-run.ts +168 -0
- package/packages/cli/src/commands/agents.ts +398 -0
- package/packages/cli/src/commands/chat.ts +142 -0
- package/packages/cli/src/commands/config.ts +50 -0
- package/packages/cli/src/commands/cron.ts +161 -0
- package/packages/cli/src/commands/dev.ts +95 -0
- package/packages/cli/src/commands/doctor.ts +133 -0
- package/packages/cli/src/commands/gateway.ts +443 -0
- package/packages/cli/src/commands/logs.ts +57 -0
- package/packages/cli/src/commands/mcp.ts +175 -0
- package/packages/cli/src/commands/message.ts +77 -0
- package/packages/cli/src/commands/onboard.ts +1868 -0
- package/packages/cli/src/commands/security.ts +144 -0
- package/packages/cli/src/commands/service.ts +50 -0
- package/packages/cli/src/commands/sessions.ts +116 -0
- package/packages/cli/src/commands/skills.ts +187 -0
- package/packages/cli/src/commands/update.ts +25 -0
- package/packages/cli/src/index.ts +185 -0
- package/packages/cli/src/utils/token.ts +6 -0
- package/packages/code-bridge/README.md +78 -0
- package/packages/code-bridge/package.json +18 -0
- package/packages/code-bridge/src/index.ts +95 -0
- package/packages/code-bridge/src/process-manager.ts +212 -0
- package/packages/code-bridge/src/schemas.ts +133 -0
- package/packages/core/package.json +46 -0
- package/packages/core/src/agent/agent-loop.ts +369 -0
- package/packages/core/src/agent/compaction.ts +140 -0
- package/packages/core/src/agent/context-compiler.ts +378 -0
- package/packages/core/src/agent/context-guard.ts +91 -0
- package/packages/core/src/agent/context.ts +138 -0
- package/packages/core/src/agent/conversation-store.ts +198 -0
- package/packages/core/src/agent/curator.ts +158 -0
- package/packages/core/src/agent/hooks.ts +166 -0
- package/packages/core/src/agent/index.ts +116 -0
- package/packages/core/src/agent/llm-client.ts +503 -0
- package/packages/core/src/agent/native-tools.ts +505 -0
- package/packages/core/src/agent/prompt-builder.ts +532 -0
- package/packages/core/src/agent/providers/index.ts +167 -0
- package/packages/core/src/agent/providers.ts +1 -0
- package/packages/core/src/agent/reflector.ts +170 -0
- package/packages/core/src/agent/service.ts +64 -0
- package/packages/core/src/agent/stuck-loop.ts +133 -0
- package/packages/core/src/agent/supervisor.ts +39 -0
- package/packages/core/src/agent/tracer.ts +102 -0
- package/packages/core/src/agent/workspace.ts +110 -0
- package/packages/core/src/canvas/canvas-manager.test.ts +161 -0
- package/packages/core/src/canvas/canvas-manager.ts +319 -0
- package/packages/core/src/canvas/canvas-tools.ts +420 -0
- package/packages/core/src/canvas/emitter.ts +115 -0
- package/packages/core/src/canvas/index.ts +2 -0
- package/packages/core/src/channels/base.ts +138 -0
- package/packages/core/src/channels/discord.ts +260 -0
- package/packages/core/src/channels/index.ts +7 -0
- package/packages/core/src/channels/manager.ts +383 -0
- package/packages/core/src/channels/slack.ts +287 -0
- package/packages/core/src/channels/telegram.ts +502 -0
- package/packages/core/src/channels/webchat.ts +128 -0
- package/packages/core/src/channels/whatsapp.ts +375 -0
- package/packages/core/src/config/index.ts +12 -0
- package/packages/core/src/config/loader.ts +529 -0
- package/packages/core/src/events/event-bus.ts +169 -0
- package/packages/core/src/gateway/index.ts +5 -0
- package/packages/core/src/gateway/initializer.ts +290 -0
- package/packages/core/src/gateway/lane-queue.ts +169 -0
- package/packages/core/src/gateway/resolver.ts +108 -0
- package/packages/core/src/gateway/router.ts +124 -0
- package/packages/core/src/gateway/server.ts +3317 -0
- package/packages/core/src/gateway/session.ts +95 -0
- package/packages/core/src/gateway/slash-commands.ts +192 -0
- package/packages/core/src/heartbeat/index.ts +157 -0
- package/packages/core/src/index.ts +19 -0
- package/packages/core/src/integrations/catalog.ts +286 -0
- package/packages/core/src/integrations/env.ts +64 -0
- package/packages/core/src/integrations/index.ts +2 -0
- package/packages/core/src/memory/index.ts +1 -0
- package/packages/core/src/memory/notes.ts +68 -0
- package/packages/core/src/plugins/api.ts +128 -0
- package/packages/core/src/plugins/index.ts +2 -0
- package/packages/core/src/plugins/loader.ts +365 -0
- package/packages/core/src/resilience/circuit-breaker.ts +225 -0
- package/packages/core/src/security/google-chat.ts +269 -0
- package/packages/core/src/security/index.ts +192 -0
- package/packages/core/src/security/pairing.ts +250 -0
- package/packages/core/src/security/rate-limit.ts +270 -0
- package/packages/core/src/security/signal.ts +321 -0
- package/packages/core/src/state/store.ts +312 -0
- package/packages/core/src/storage/bun-sqlite-store.ts +188 -0
- package/packages/core/src/storage/crypto.ts +101 -0
- package/packages/core/src/storage/db-context.ts +333 -0
- package/packages/core/src/storage/onboarding.ts +1087 -0
- package/packages/core/src/storage/schema.ts +541 -0
- package/packages/core/src/storage/seed.ts +571 -0
- package/packages/core/src/storage/sqlite.ts +387 -0
- package/packages/core/src/storage/usage.ts +212 -0
- package/packages/core/src/tools/bridge-events.ts +74 -0
- package/packages/core/src/tools/browser.ts +275 -0
- package/packages/core/src/tools/codebridge.ts +421 -0
- package/packages/core/src/tools/coordinator-tools.ts +179 -0
- package/packages/core/src/tools/cron.ts +611 -0
- package/packages/core/src/tools/exec.ts +140 -0
- package/packages/core/src/tools/fs.ts +364 -0
- package/packages/core/src/tools/index.ts +12 -0
- package/packages/core/src/tools/memory.ts +176 -0
- package/packages/core/src/tools/notify.ts +113 -0
- package/packages/core/src/tools/project-management.ts +376 -0
- package/packages/core/src/tools/project.ts +375 -0
- package/packages/core/src/tools/read.ts +158 -0
- package/packages/core/src/tools/web.ts +436 -0
- package/packages/core/src/tools/workspace.ts +171 -0
- package/packages/core/src/utils/benchmark.ts +80 -0
- package/packages/core/src/utils/crypto.ts +73 -0
- package/packages/core/src/utils/date.ts +42 -0
- package/packages/core/src/utils/index.ts +4 -0
- package/packages/core/src/utils/logger.ts +388 -0
- package/packages/core/src/utils/retry.ts +70 -0
- package/packages/core/src/voice/index.ts +583 -0
- package/packages/core/tsconfig.json +9 -0
- package/packages/mcp/package.json +26 -0
- package/packages/mcp/src/config.ts +13 -0
- package/packages/mcp/src/index.ts +1 -0
- package/packages/mcp/src/logger.ts +42 -0
- package/packages/mcp/src/manager.ts +434 -0
- package/packages/mcp/src/transports/index.ts +67 -0
- package/packages/mcp/src/transports/sse.ts +241 -0
- package/packages/mcp/src/transports/websocket.ts +159 -0
- package/packages/skills/package.json +21 -0
- package/packages/skills/src/bundled/agent_management/SKILL.md +24 -0
- package/packages/skills/src/bundled/browser_automation/SKILL.md +30 -0
- package/packages/skills/src/bundled/context_compact/SKILL.md +35 -0
- package/packages/skills/src/bundled/cron_manager/SKILL.md +52 -0
- package/packages/skills/src/bundled/file_manager/SKILL.md +76 -0
- package/packages/skills/src/bundled/http_client/SKILL.md +24 -0
- package/packages/skills/src/bundled/memory/SKILL.md +42 -0
- package/packages/skills/src/bundled/project_management/SKILL.md +26 -0
- package/packages/skills/src/bundled/shell/SKILL.md +43 -0
- package/packages/skills/src/bundled/system_notify/SKILL.md +52 -0
- package/packages/skills/src/bundled/voice/SKILL.md +25 -0
- package/packages/skills/src/bundled/web_search/SKILL.md +29 -0
- package/packages/skills/src/index.ts +1 -0
- package/packages/skills/src/loader.ts +282 -0
- package/packages/tools/package.json +43 -0
- package/packages/tools/src/browser/browser.test.ts +111 -0
- package/packages/tools/src/browser/index.ts +272 -0
- package/packages/tools/src/canvas/index.ts +220 -0
- package/packages/tools/src/cron/cron.test.ts +164 -0
- package/packages/tools/src/cron/index.ts +304 -0
- package/packages/tools/src/filesystem/filesystem.test.ts +240 -0
- package/packages/tools/src/filesystem/index.ts +379 -0
- package/packages/tools/src/git/index.ts +239 -0
- package/packages/tools/src/index.ts +4 -0
- package/packages/tools/src/shell/detect-env.ts +70 -0
- package/packages/tools/tsconfig.json +9 -0
|
@@ -0,0 +1,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,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
|
+
}
|