From ccdbc84f2a708f566481965c32a22f923890fa76 Mon Sep 17 00:00:00 2001 From: Alexa Amundson Date: Tue, 17 Mar 2026 02:00:01 -0500 Subject: [PATCH] =?UTF-8?q?sync:=202026-03-17=2002:00=20=E2=80=94=2021=20f?= =?UTF-8?q?iles=20from=20Alexandria?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RoadChain-SHA2048: aa30d62a1ecbac8b RoadChain-Identity: alexa@sovereign RoadChain-Full: aa30d62a1ecbac8b3b8b5fa7906fb2e2fb1a18f4dfbbece464b0ec38e72946356c8ac0a55a99fab34b0550f97436a7895327f283f50b1c824e814cca8ce6256ac312a73f00784d6ad2b7db5b80517ae37a3b8a6be269f658fc8bf5b3b9340d02d4ace698347e6fd4a3af20e269842ed4c61d70831e850b46fe9746422c33fc96a8e2a25894ba512f31a37362150d8b50a2c4f8d8fc3d6831a6c7cc189e8506d6dc257ef053e80c45601c8a832754a7c8dc4b227ae491b6b685a421ce607d46b02901ced4bf8443923561f8cf01236b35edbd75853c826637e152f285d2f907fb6037c32e0c1b329323888a7281afdcbab52f314fded1308a4b772a8a31b1f16f --- fleet/alice/autonomy-log.txt | 100 +-- fleet/alice/heartbeat.json | 2 +- fleet/alice/ports.txt | 2 +- fleet/alice/system-info.json | 18 +- fleet/anastasia/ports.txt | 39 +- fleet/anastasia/services.txt | 2 +- fleet/anastasia/system-info.json | 14 +- fleet/aria/status.json | 2 +- fleet/cecilia/autonomy-log.txt | 100 +-- fleet/cecilia/heartbeat.json | 2 +- fleet/cecilia/ports.txt | 7 +- fleet/cecilia/system-info.json | 16 +- fleet/gematria/ports.txt | 8 +- fleet/gematria/system-info.json | 14 +- fleet/lucidia/autonomy-log.txt | 100 +-- fleet/lucidia/docker.txt | 24 +- fleet/lucidia/heartbeat.json | 2 +- fleet/lucidia/system-info.json | 16 +- fleet/octavia/status.json | 2 +- workers/roadcode-squad-src/worker.js | 1207 ++++++++++++++++---------- workers/roadcode-squad.toml | 8 +- 21 files changed, 1007 insertions(+), 678 deletions(-) diff --git a/fleet/alice/autonomy-log.txt b/fleet/alice/autonomy-log.txt index 3d38d87..f472566 100644 --- a/fleet/alice/autonomy-log.txt +++ b/fleet/alice/autonomy-log.txt @@ -1,50 +1,50 @@ -[2026-03-17 01:04:02] [BEAT] [alice] load=1.63 mem=3268/3794MB temp=31.6C disk=84% -[2026-03-17 01:05:01] [BEAT] [alice] load=1.43 mem=3268/3794MB temp=34.6C disk=84% -[2026-03-17 01:06:01] [BEAT] [alice] load=0.71 mem=3270/3794MB temp=33.1C disk=84% -[2026-03-17 01:06:24] [BEAT] [alice] load=0.99 mem=3267/3794MB temp=34.1C disk=84% -[2026-03-17 01:06:24] [BEAT] [alice] load=0.99 mem=3266/3794MB temp=34.6C disk=84% -[2026-03-17 01:07:01] [BEAT] [alice] load=0.84 mem=3264/3794MB temp=32.6C disk=84% -[2026-03-17 01:08:01] [BEAT] [alice] load=0.79 mem=3267/3794MB temp=32.1C disk=84% -[2026-03-17 01:09:03] [BEAT] [alice] load=1.38 mem=3272/3794MB temp=32.1C disk=84% -[2026-03-17 01:10:01] [FLEET] [alice] Starting cross-node health check -[2026-03-17 01:10:01] [BEAT] [alice] load=0.96 mem=3264/3794MB temp=33.1C disk=84% -[2026-03-17 01:10:02] [FLEET] [alice] octavia: UP temp=39C mem=1974MB disk=72% -[2026-03-17 01:10:02] [FLEET] [alice] cecilia: UP temp=40C mem=2313MB disk=34% -[2026-03-17 01:10:04] [FLEET] [alice] gematria: UP temp=C mem=2634MB disk=68% -[2026-03-17 01:10:05] [FLEET] [alice] lucidia: UP temp=55C mem=1473MB disk=31% -[2026-03-17 01:10:07] [FLEET] [alice] aria: DOWN (no ping response) -[2026-03-17 01:10:09] [FLEET] [alice] anastasia: UP temp=C mem=293MB disk=69% -[2026-03-17 01:11:01] [BEAT] [alice] load=0.80 mem=3266/3794MB temp=31.2C disk=84% -[2026-03-17 01:11:37] [BEAT] [alice] load=0.49 mem=3268/3794MB temp=32.6C disk=84% -[2026-03-17 01:11:37] [BEAT] [alice] load=0.49 mem=3266/3794MB temp=34.1C disk=84% -[2026-03-17 01:12:01] [BEAT] [alice] load=0.52 mem=3264/3794MB temp=34.1C disk=84% -[2026-03-17 01:12:22] [DIAL] [alice] Switchboard unreachable -[2026-03-17 01:13:01] [BEAT] [alice] load=1.08 mem=3270/3794MB temp=32.6C disk=84% -[2026-03-17 01:14:02] [BEAT] [alice] load=1.38 mem=3268/3794MB temp=34.6C disk=84% -[2026-03-17 01:15:01] [BEAT] [alice] load=1.19 mem=3264/3794MB temp=34.1C disk=84% -[2026-03-17 01:15:46] [DIAL] [alice] Switchboard unreachable -[2026-03-17 01:16:01] [BEAT] [alice] load=1.36 mem=3265/3794MB temp=34.6C disk=84% -[2026-03-17 01:16:50] [BEAT] [alice] load=0.94 mem=3268/3794MB temp=33.6C disk=84% -[2026-03-17 01:16:50] [BEAT] [alice] load=0.94 mem=3268/3794MB temp=32.6C disk=84% -[2026-03-17 01:17:01] [BEAT] [alice] load=0.80 mem=3270/3794MB temp=33.6C disk=84% -[2026-03-17 01:18:02] [BEAT] [alice] load=1.14 mem=3269/3794MB temp=36.0C disk=84% -[2026-03-17 01:19:04] [BEAT] [alice] load=6.18 mem=3265/3794MB temp=32.6C disk=84% -[2026-03-17 01:20:01] [FLEET] [alice] Starting cross-node health check -[2026-03-17 01:20:01] [BEAT] [alice] load=3.11 mem=3259/3794MB temp=36.0C disk=84% -[2026-03-17 01:20:01] [FLEET] [alice] octavia: UP temp=38C mem=2853MB disk=73% -[2026-03-17 01:20:02] [FLEET] [alice] cecilia: UP temp=37C mem=606MB disk=34% -[2026-03-17 01:20:04] [FLEET] [alice] gematria: UP temp=C mem=2568MB disk=68% -[2026-03-17 01:20:05] [FLEET] [alice] lucidia: UP temp=60C mem=2194MB disk=31% -[2026-03-17 01:20:07] [FLEET] [alice] aria: DOWN (no ping response) -[2026-03-17 01:20:09] [FLEET] [alice] anastasia: UP temp=C mem=209MB disk=69% -[2026-03-17 01:21:01] [BEAT] [alice] load=1.34 mem=3266/3794MB temp=31.6C disk=84% -[2026-03-17 01:22:01] [BEAT] [alice] load=1.06 mem=3266/3794MB temp=34.1C disk=84% -[2026-03-17 01:22:04] [BEAT] [alice] load=0.98 mem=3268/3794MB temp=34.6C disk=84% -[2026-03-17 01:22:04] [BEAT] [alice] load=0.98 mem=3268/3794MB temp=34.1C disk=84% -[2026-03-17 01:23:01] [BEAT] [alice] load=0.83 mem=3270/3794MB temp=32.6C disk=84% -[2026-03-17 01:24:01] [BEAT] [alice] load=1.05 mem=3267/3794MB temp=34.1C disk=84% -[2026-03-17 01:25:02] [BEAT] [alice] load=0.60 mem=3261/3794MB temp=32.1C disk=84% -[2026-03-17 01:26:01] [BEAT] [alice] load=1.03 mem=3265/3794MB temp=33.6C disk=84% -[2026-03-17 01:27:01] [BEAT] [alice] load=0.78 mem=3261/3794MB temp=32.6C disk=84% -[2026-03-17 01:27:17] [BEAT] [alice] load=0.61 mem=3258/3794MB temp=32.6C disk=84% -[2026-03-17 01:27:17] [BEAT] [alice] load=0.61 mem=3258/3794MB temp=33.6C disk=84% +[2026-03-17 01:36:02] [BEAT] [alice] load=1.98 mem=3262/3794MB temp=34.6C disk=85% +[2026-03-17 01:37:01] [BEAT] [alice] load=1.16 mem=3259/3794MB temp=33.6C disk=85% +[2026-03-17 01:37:46] [BEAT] [alice] load=1.22 mem=3259/3794MB temp=33.6C disk=85% +[2026-03-17 01:37:46] [BEAT] [alice] load=1.22 mem=3258/3794MB temp=33.6C disk=85% +[2026-03-17 01:38:01] [BEAT] [alice] load=1.17 mem=3259/3794MB temp=32.6C disk=85% +[2026-03-17 01:39:01] [BEAT] [alice] load=0.93 mem=3261/3794MB temp=33.6C disk=85% +[2026-03-17 01:40:01] [FLEET] [alice] Starting cross-node health check +[2026-03-17 01:40:01] [BEAT] [alice] load=0.68 mem=3253/3794MB temp=34.6C disk=85% +[2026-03-17 01:40:02] [FLEET] [alice] octavia: UP temp=35C mem=2853MB disk=73% +[2026-03-17 01:40:02] [FLEET] [alice] cecilia: UP temp=40C mem=2934MB disk=34% +[2026-03-17 01:40:03] [FLEET] [alice] gematria: UP temp=C mem=5075MB disk=68% +[2026-03-17 01:40:04] [FLEET] [alice] lucidia: UP temp=52C mem=2183MB disk=31% +[2026-03-17 01:40:06] [FLEET] [alice] aria: DOWN (no ping response) +[2026-03-17 01:40:07] [FLEET] [alice] anastasia: UP temp=C mem=305MB disk=69% +[2026-03-17 01:41:02] [BEAT] [alice] load=0.79 mem=3260/3794MB temp=35.5C disk=85% +[2026-03-17 01:42:01] [BEAT] [alice] load=0.59 mem=3260/3794MB temp=34.1C disk=85% +[2026-03-17 01:42:23] [DIAL] [alice] Switchboard unreachable +[2026-03-17 01:42:54] [BEAT] [alice] load=0.79 mem=3257/3794MB temp=34.1C disk=85% +[2026-03-17 01:42:54] [BEAT] [alice] load=0.79 mem=3257/3794MB temp=35.0C disk=85% +[2026-03-17 01:43:01] [BEAT] [alice] load=1.04 mem=3253/3794MB temp=35.5C disk=85% +[2026-03-17 01:44:01] [BEAT] [alice] load=1.03 mem=3259/3794MB temp=32.6C disk=85% +[2026-03-17 01:45:01] [BEAT] [alice] load=0.98 mem=3255/3794MB temp=36.5C disk=85% +[2026-03-17 01:45:47] [DIAL] [alice] Switchboard unreachable +[2026-03-17 01:46:02] [BEAT] [alice] load=0.70 mem=3258/3794MB temp=33.6C disk=85% +[2026-03-17 01:47:01] [BEAT] [alice] load=0.86 mem=3256/3794MB temp=34.6C disk=85% +[2026-03-17 01:48:01] [BEAT] [alice] load=0.60 mem=3260/3794MB temp=32.1C disk=85% +[2026-03-17 01:48:03] [BEAT] [alice] load=0.60 mem=3254/3794MB temp=32.6C disk=85% +[2026-03-17 01:48:03] [BEAT] [alice] load=0.60 mem=3254/3794MB temp=33.1C disk=85% +[2026-03-17 01:49:01] [BEAT] [alice] load=0.81 mem=3259/3794MB temp=35.0C disk=85% +[2026-03-17 01:50:01] [FLEET] [alice] Starting cross-node health check +[2026-03-17 01:50:01] [BEAT] [alice] load=0.51 mem=3254/3794MB temp=34.6C disk=85% +[2026-03-17 01:50:02] [FLEET] [alice] octavia: UP temp=35C mem=2865MB disk=74% +[2026-03-17 01:50:02] [FLEET] [alice] cecilia: UP temp=40C mem=2747MB disk=34% +[2026-03-17 01:50:04] [FLEET] [alice] gematria: UP temp=C mem=5061MB disk=68% +[2026-03-17 01:50:04] [FLEET] [alice] lucidia: UP temp=60C mem=2009MB disk=31% +[2026-03-17 01:50:06] [FLEET] [alice] aria: DOWN (no ping response) +[2026-03-17 01:50:08] [FLEET] [alice] anastasia: UP temp=C mem=313MB disk=69% +[2026-03-17 01:51:01] [BEAT] [alice] load=0.79 mem=3261/3794MB temp=34.1C disk=85% +[2026-03-17 01:52:01] [BEAT] [alice] load=0.61 mem=3257/3794MB temp=32.1C disk=85% +[2026-03-17 01:53:02] [BEAT] [alice] load=1.26 mem=3261/3794MB temp=34.1C disk=85% +[2026-03-17 01:53:12] [BEAT] [alice] load=1.14 mem=3259/3794MB temp=31.6C disk=85% +[2026-03-17 01:53:12] [BEAT] [alice] load=1.14 mem=3258/3794MB temp=33.6C disk=85% +[2026-03-17 01:54:01] [BEAT] [alice] load=0.69 mem=3257/3794MB temp=32.1C disk=85% +[2026-03-17 01:55:01] [BEAT] [alice] load=1.03 mem=3257/3794MB temp=32.6C disk=85% +[2026-03-17 01:56:01] [BEAT] [alice] load=0.99 mem=3263/3794MB temp=30.7C disk=85% +[2026-03-17 01:57:01] [BEAT] [alice] load=0.99 mem=3256/3794MB temp=32.6C disk=85% +[2026-03-17 01:57:25] [DIAL] [alice] Switchboard unreachable +[2026-03-17 01:58:01] [BEAT] [alice] load=0.47 mem=3261/3794MB temp=31.2C disk=85% +[2026-03-17 01:58:20] [BEAT] [alice] load=0.44 mem=3257/3794MB temp=33.6C disk=85% +[2026-03-17 01:58:20] [BEAT] [alice] load=0.57 mem=3256/3794MB temp=33.6C disk=85% diff --git a/fleet/alice/heartbeat.json b/fleet/alice/heartbeat.json index 1489739..908afaa 100644 --- a/fleet/alice/heartbeat.json +++ b/fleet/alice/heartbeat.json @@ -1 +1 @@ -{"node":"alice","ts":"2026-03-17T06:27:17Z","load":0.61,"mem_free_mb":3258,"mem_total_mb":3794,"temp_c":33.6,"disk_pct":84,"throttle":"0x0"} +{"node":"alice","ts":"2026-03-17T06:58:20Z","load":0.57,"mem_free_mb":3256,"mem_total_mb":3794,"temp_c":33.6,"disk_pct":85,"throttle":"0x0"} diff --git a/fleet/alice/ports.txt b/fleet/alice/ports.txt index 20683cb..35771d0 100644 --- a/fleet/alice/ports.txt +++ b/fleet/alice/ports.txt @@ -13,7 +13,7 @@ LISTEN 0 511 0.0.0.0:8080 0.0.0.0:* LISTEN 0 511 0.0.0.0:8083 0.0.0.0:* users:(("node /usr/lib/n",pid=3707,fd=20)) LISTEN 0 5 0.0.0.0:8184 0.0.0.0:* LISTEN 0 4096 127.0.0.1:9050 0.0.0.0:* -LISTEN 0 5 0.0.0.0:7890 0.0.0.0:* users:(("python3",pid=18573,fd=5)) +LISTEN 0 5 0.0.0.0:7890 0.0.0.0:* users:(("python3",pid=16306,fd=5)) LISTEN 0 200 0.0.0.0:443 0.0.0.0:* LISTEN 0 128 127.0.0.1:11434 0.0.0.0:* LISTEN 0 32 0.0.0.0:53 0.0.0.0:* diff --git a/fleet/alice/system-info.json b/fleet/alice/system-info.json index 8496ca2..1416d1c 100644 --- a/fleet/alice/system-info.json +++ b/fleet/alice/system-info.json @@ -1,19 +1,19 @@ { "hostname": "alice", - "ts": "2026-03-17T06:27:20Z", - "uptime_seconds": 10910, + "ts": "2026-03-17T06:58:23Z", + "uptime_seconds": 12772, "kernel": "6.1.21-v8+", - "temp_c": 33.1, + "temp_c": 34.1, "memory_mb": { "total": 3794, - "used": 437, - "free": 3262 + "used": 443, + "free": 3252 }, - "disk": "12G/15G (84%)", + "disk": "12G/15G (85%)", "load": [ - 0.8, - 1.05, - 1.17 + 0.57, + 0.77, + 0.9 ], "ollama_models": [ "lucidia3b:latest", diff --git a/fleet/anastasia/ports.txt b/fleet/anastasia/ports.txt index 8972a3d..0308382 100644 --- a/fleet/anastasia/ports.txt +++ b/fleet/anastasia/ports.txt @@ -1,19 +1,20 @@ -LISTEN 0 4096 127.0.0.1:20241 0.0.0.0:* users:(("cloudflared",pid=4072379,fd=3)) -LISTEN 0 2048 0.0.0.0:8000 0.0.0.0:* users:(("uvicorn",pid=532097,fd=11)) -LISTEN 0 4096 127.0.0.1:9090 0.0.0.0:* users:(("headscale",pid=2341808,fd=13)) -LISTEN 0 4096 100.64.0.1:11434 0.0.0.0:* users:(("ollama",pid=2412347,fd=3)) -LISTEN 0 5 0.0.0.0:8888 0.0.0.0:* users:(("python3",pid=396502,fd=3)) -LISTEN 0 100 0.0.0.0:6379 0.0.0.0:* users:(("python3",pid=539748,fd=6)) -LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=991,fd=7)) -LISTEN 0 128 0.0.0.0:53 0.0.0.0:* users:(("pdns_server",pid=2025847,fd=6)) -LISTEN 0 100 0.0.0.0:8766 0.0.0.0:* users:(("python3",pid=538117,fd=6)) -LISTEN 0 100 0.0.0.0:8765 0.0.0.0:* users:(("python3",pid=538116,fd=6)) -LISTEN 0 5 0.0.0.0:8787 0.0.0.0:* users:(("python3",pid=1122797,fd=3)) -LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=3461172,fd=8),("nginx",pid=3461171,fd=8)) -LISTEN 0 4096 0.0.0.0:111 0.0.0.0:* users:(("rpcbind",pid=589,fd=4),("systemd",pid=1,fd=126)) -LISTEN 0 4096 *:8080 *:* users:(("headscale",pid=2341808,fd=12)) -LISTEN 0 511 *:3000 *:* users:(("node /srv/hello",pid=2049957,fd=19)) -LISTEN 0 511 *:3001 *:* users:(("node",pid=757,fd=21)) -LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=991,fd=8)) -LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=3461172,fd=9),("nginx",pid=3461171,fd=9)) -LISTEN 0 4096 [::]:111 [::]:* users:(("rpcbind",pid=589,fd=6),("systemd",pid=1,fd=128)) +LISTEN 0 4096 127.0.0.1:20241 0.0.0.0:* users:(("cloudflared",pid=4072379,fd=3)) +LISTEN 0 4096 127.0.0.1:2019 0.0.0.0:* users:(("caddy",pid=2052062,fd=4)) +LISTEN 0 2048 0.0.0.0:8000 0.0.0.0:* users:(("uvicorn",pid=532097,fd=11)) +LISTEN 0 4096 127.0.0.1:9090 0.0.0.0:* users:(("headscale",pid=2341808,fd=13)) +LISTEN 0 4096 100.64.0.1:11434 0.0.0.0:* users:(("ollama",pid=2412347,fd=3)) +LISTEN 0 5 0.0.0.0:8888 0.0.0.0:* users:(("python3",pid=396502,fd=3)) +LISTEN 0 100 0.0.0.0:6379 0.0.0.0:* users:(("python3",pid=539748,fd=6)) +LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=991,fd=7)) +LISTEN 0 128 0.0.0.0:53 0.0.0.0:* users:(("pdns_server",pid=2025847,fd=6)) +LISTEN 0 100 0.0.0.0:8766 0.0.0.0:* users:(("python3",pid=538117,fd=6)) +LISTEN 0 100 0.0.0.0:8765 0.0.0.0:* users:(("python3",pid=538116,fd=6)) +LISTEN 0 5 0.0.0.0:8787 0.0.0.0:* users:(("python3",pid=1122797,fd=3)) +LISTEN 0 4096 0.0.0.0:111 0.0.0.0:* users:(("rpcbind",pid=589,fd=4),("systemd",pid=1,fd=126)) +LISTEN 0 4096 *:8080 *:* users:(("headscale",pid=2341808,fd=12)) +LISTEN 0 4096 *:443 *:* users:(("caddy",pid=2052062,fd=8)) +LISTEN 0 511 *:3000 *:* users:(("node /srv/hello",pid=2056618,fd=19)) +LISTEN 0 511 *:3001 *:* users:(("node",pid=757,fd=21)) +LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=991,fd=8)) +LISTEN 0 4096 *:80 *:* users:(("caddy",pid=2052062,fd=7)) +LISTEN 0 4096 [::]:111 [::]:* users:(("rpcbind",pid=589,fd=6),("systemd",pid=1,fd=128)) diff --git a/fleet/anastasia/services.txt b/fleet/anastasia/services.txt index 56108f0..1c419a2 100644 --- a/fleet/anastasia/services.txt +++ b/fleet/anastasia/services.txt @@ -1,5 +1,6 @@ auditd.service blackroad-api.service +caddy.service chronyd.service cloudflared.service containerd.service @@ -14,7 +15,6 @@ gssproxy.service headscale.service lucidia-agent.service NetworkManager.service -nginx.service ollama.service pdns.service polkit.service diff --git a/fleet/anastasia/system-info.json b/fleet/anastasia/system-info.json index c75ae65..3fbc876 100644 --- a/fleet/anastasia/system-info.json +++ b/fleet/anastasia/system-info.json @@ -1,19 +1,19 @@ { "hostname": "anastasia", - "ts": "2026-03-17T06:27:20Z", - "uptime_seconds": 6870732, + "ts": "2026-03-17T06:58:22Z", + "uptime_seconds": 6872594, "kernel": "5.14.0-651.el9.x86_64", "temp_c": 0, "memory_mb": { "total": 765, - "used": 459, - "free": 305 + "used": 458, + "free": 306 }, "disk": "18G/25G (69%)", "load": [ - 0.02, - 0.07, - 0.08 + 0.3, + 0.14, + 0.1 ], "ollama_models": [], "throttle": "N/A", diff --git a/fleet/aria/status.json b/fleet/aria/status.json index bc5bb76..06e57d0 100644 --- a/fleet/aria/status.json +++ b/fleet/aria/status.json @@ -1 +1 @@ -{"node":"aria","status":"down","ts":"2026-03-17T06:27:18Z"} +{"node":"aria","status":"down","ts":"2026-03-17T06:58:20Z"} diff --git a/fleet/cecilia/autonomy-log.txt b/fleet/cecilia/autonomy-log.txt index 6a2f6a9..9bc7ae9 100644 --- a/fleet/cecilia/autonomy-log.txt +++ b/fleet/cecilia/autonomy-log.txt @@ -1,50 +1,50 @@ -[2026-03-17 01:10:01] [BEAT] [cecilia] load=0.22 mem=2289/8062MB temp=38.6C disk=34% -[2026-03-17 01:10:02] [HEAL] [cecilia] OOM kills detected (1) — clearing caches -[2026-03-17 01:10:02] [HEAL] [cecilia] High swap: 575MB — clearing inactive -[2026-03-17 01:10:02] [FLEET] [cecilia] alice: UP temp=33C mem=3263MB disk=84% -[2026-03-17 01:10:02] [FLEET] [cecilia] octavia: UP temp=39C mem=2001MB disk=72% -[2026-03-17 01:10:05] [FLEET] [cecilia] gematria: UP temp=C mem=2649MB disk=68% -[2026-03-17 01:10:05] [FLEET] [cecilia] lucidia: UP temp=55C mem=1481MB disk=31% -[2026-03-17 01:10:07] [FLEET] [cecilia] aria: DOWN (no ping response) -[2026-03-17 01:10:09] [FLEET] [cecilia] anastasia: UP temp=C mem=293MB disk=69% -[2026-03-17 01:11:01] [BEAT] [cecilia] load=0.33 mem=2297/8062MB temp=37.0C disk=34% -[2026-03-17 01:11:37] [BEAT] [cecilia] load=0.24 mem=2291/8062MB temp=37.0C disk=34% -[2026-03-17 01:11:37] [BEAT] [cecilia] load=0.24 mem=2288/8062MB temp=36.4C disk=34% -[2026-03-17 01:12:01] [BEAT] [cecilia] load=0.33 mem=2289/8062MB temp=36.4C disk=34% -[2026-03-17 01:12:01] [DIAL] [cecilia] Switchboard unreachable -[2026-03-17 01:13:01] [BEAT] [cecilia] load=0.43 mem=2295/8062MB temp=36.4C disk=34% -[2026-03-17 01:14:01] [BEAT] [cecilia] load=0.23 mem=2291/8062MB temp=35.3C disk=34% -[2026-03-17 01:15:01] [BEAT] [cecilia] load=2.13 mem=295/8062MB temp=45.8C disk=34% -[2026-03-17 01:15:02] [HEAL] [cecilia] OOM kills detected (1) — clearing caches -[2026-03-17 01:15:02] [HEAL] [cecilia] High swap: 1278MB — clearing inactive -[2026-03-17 01:15:47] [DIAL] [cecilia] Switchboard unreachable -[2026-03-17 01:16:01] [BEAT] [cecilia] load=1.37 mem=568/8062MB temp=37.5C disk=34% -[2026-03-17 01:16:49] [BEAT] [cecilia] load=0.86 mem=631/8062MB temp=37.0C disk=34% -[2026-03-17 01:16:49] [BEAT] [cecilia] load=0.86 mem=629/8062MB temp=37.5C disk=34% -[2026-03-17 01:17:01] [BEAT] [cecilia] load=0.73 mem=537/8062MB temp=37.5C disk=34% -[2026-03-17 01:18:01] [BEAT] [cecilia] load=0.67 mem=543/8062MB temp=38.0C disk=34% -[2026-03-17 01:19:01] [BEAT] [cecilia] load=1.24 mem=556/8062MB temp=37.5C disk=34% -[2026-03-17 01:20:02] [FLEET] [cecilia] Starting cross-node health check -[2026-03-17 01:20:02] [BEAT] [cecilia] load=1.11 mem=598/8062MB temp=37.5C disk=34% -[2026-03-17 01:20:02] [HEAL] [cecilia] OOM kills detected (1) — clearing caches -[2026-03-17 01:20:02] [FLEET] [cecilia] alice: UP temp=36C mem=3257MB disk=84% -[2026-03-17 01:20:02] [HEAL] [cecilia] High swap: 1719MB — clearing inactive -[2026-03-17 01:20:02] [FLEET] [cecilia] octavia: UP temp=38C mem=2858MB disk=73% -[2026-03-17 01:20:04] [FLEET] [cecilia] gematria: UP temp=C mem=2566MB disk=68% -[2026-03-17 01:20:05] [FLEET] [cecilia] lucidia: UP temp=59C mem=2204MB disk=31% -[2026-03-17 01:20:07] [FLEET] [cecilia] aria: DOWN (no ping response) -[2026-03-17 01:20:08] [FLEET] [cecilia] anastasia: UP temp=C mem=209MB disk=69% -[2026-03-17 01:21:01] [BEAT] [cecilia] load=0.55 mem=789/8062MB temp=36.4C disk=34% -[2026-03-17 01:22:01] [BEAT] [cecilia] load=0.40 mem=786/8062MB temp=35.3C disk=34% -[2026-03-17 01:22:04] [BEAT] [cecilia] load=0.37 mem=775/8062MB temp=35.3C disk=34% -[2026-03-17 01:22:04] [BEAT] [cecilia] load=0.37 mem=775/8062MB temp=35.3C disk=34% -[2026-03-17 01:23:01] [BEAT] [cecilia] load=0.49 mem=762/8062MB temp=35.3C disk=34% -[2026-03-17 01:24:01] [BEAT] [cecilia] load=0.45 mem=763/8062MB temp=34.8C disk=34% -[2026-03-17 01:25:01] [BEAT] [cecilia] load=1.72 mem=676/8062MB temp=45.8C disk=34% -[2026-03-17 01:25:02] [HEAL] [cecilia] OOM kills detected (1) — clearing caches -[2026-03-17 01:25:02] [HEAL] [cecilia] High swap: 1716MB — clearing inactive -[2026-03-17 01:26:01] [BEAT] [cecilia] load=3.43 mem=671/8062MB temp=49.6C disk=34% -[2026-03-17 01:27:01] [BEAT] [cecilia] load=3.91 mem=670/8062MB temp=51.2C disk=34% -[2026-03-17 01:27:03] [DIAL] [cecilia] Switchboard unreachable -[2026-03-17 01:27:17] [BEAT] [cecilia] load=4.07 mem=658/8062MB temp=50.7C disk=34% -[2026-03-17 01:27:17] [BEAT] [cecilia] load=4.07 mem=656/8062MB temp=51.2C disk=34% +[2026-03-17 01:40:01] [BEAT] [cecilia] load=2.02 mem=2941/8062MB temp=41.4C disk=34% +[2026-03-17 01:40:01] [HEAL] [cecilia] OOM kills detected (1) — clearing caches +[2026-03-17 01:40:01] [HEAL] [cecilia] High swap: 597MB — clearing inactive +[2026-03-17 01:40:01] [FLEET] [cecilia] alice: UP temp=35C mem=3254MB disk=85% +[2026-03-17 01:40:02] [FLEET] [cecilia] octavia: UP temp=35C mem=2853MB disk=73% +[2026-03-17 01:40:03] [FLEET] [cecilia] gematria: UP temp=C mem=5075MB disk=68% +[2026-03-17 01:40:04] [FLEET] [cecilia] lucidia: UP temp=52C mem=2183MB disk=31% +[2026-03-17 01:40:06] [FLEET] [cecilia] aria: DOWN (no ping response) +[2026-03-17 01:40:06] [FLEET] [cecilia] anastasia: UP temp=C mem=305MB disk=69% +[2026-03-17 01:41:02] [BEAT] [cecilia] load=2.47 mem=2966/8062MB temp=49.0C disk=34% +[2026-03-17 01:42:01] [BEAT] [cecilia] load=2.36 mem=2965/8062MB temp=41.4C disk=34% +[2026-03-17 01:42:03] [DIAL] [cecilia] Switchboard unreachable +[2026-03-17 01:42:54] [BEAT] [cecilia] load=1.31 mem=2966/8062MB temp=39.7C disk=34% +[2026-03-17 01:42:54] [BEAT] [cecilia] load=1.31 mem=2965/8062MB temp=39.7C disk=34% +[2026-03-17 01:43:01] [BEAT] [cecilia] load=1.33 mem=2960/8062MB temp=39.7C disk=34% +[2026-03-17 01:44:01] [BEAT] [cecilia] load=1.14 mem=2976/8062MB temp=38.6C disk=34% +[2026-03-17 01:45:01] [BEAT] [cecilia] load=0.86 mem=2927/8062MB temp=39.1C disk=34% +[2026-03-17 01:45:01] [HEAL] [cecilia] OOM kills detected (1) — clearing caches +[2026-03-17 01:45:01] [HEAL] [cecilia] Zombie processes: 56 +[2026-03-17 01:45:01] [HEAL] [cecilia] High swap: 596MB — clearing inactive +[2026-03-17 01:45:46] [DIAL] [cecilia] Switchboard unreachable +[2026-03-17 01:46:01] [BEAT] [cecilia] load=1.65 mem=2836/8062MB temp=44.6C disk=34% +[2026-03-17 01:47:01] [BEAT] [cecilia] load=2.20 mem=2747/8062MB temp=42.5C disk=34% +[2026-03-17 01:48:01] [BEAT] [cecilia] load=1.04 mem=2817/8062MB temp=38.6C disk=34% +[2026-03-17 01:48:03] [BEAT] [cecilia] load=1.04 mem=2814/8062MB temp=38.0C disk=34% +[2026-03-17 01:48:03] [BEAT] [cecilia] load=1.04 mem=2813/8062MB temp=40.2C disk=34% +[2026-03-17 01:49:01] [BEAT] [cecilia] load=0.71 mem=2843/8062MB temp=39.7C disk=34% +[2026-03-17 01:50:01] [FLEET] [cecilia] Starting cross-node health check +[2026-03-17 01:50:01] [BEAT] [cecilia] load=0.36 mem=2826/8062MB temp=38.6C disk=34% +[2026-03-17 01:50:01] [HEAL] [cecilia] High swap: 581MB — clearing inactive +[2026-03-17 01:50:02] [FLEET] [cecilia] alice: UP temp=34C mem=3246MB disk=85% +[2026-03-17 01:50:02] [FLEET] [cecilia] octavia: UP temp=34C mem=2883MB disk=74% +[2026-03-17 01:50:04] [FLEET] [cecilia] gematria: UP temp=C mem=5060MB disk=68% +[2026-03-17 01:50:04] [FLEET] [cecilia] lucidia: UP temp=61C mem=2009MB disk=31% +[2026-03-17 01:50:06] [FLEET] [cecilia] aria: DOWN (no ping response) +[2026-03-17 01:50:07] [FLEET] [cecilia] anastasia: UP temp=C mem=325MB disk=69% +[2026-03-17 01:51:01] [BEAT] [cecilia] load=0.39 mem=2745/8062MB temp=38.0C disk=34% +[2026-03-17 01:52:01] [BEAT] [cecilia] load=1.56 mem=1971/8062MB temp=47.4C disk=34% +[2026-03-17 01:53:01] [BEAT] [cecilia] load=0.92 mem=1975/8062MB temp=37.5C disk=34% +[2026-03-17 01:53:11] [BEAT] [cecilia] load=1.02 mem=1973/8062MB temp=37.5C disk=34% +[2026-03-17 01:53:11] [BEAT] [cecilia] load=1.02 mem=1972/8062MB temp=38.0C disk=34% +[2026-03-17 01:54:01] [BEAT] [cecilia] load=0.67 mem=1977/8062MB temp=38.0C disk=34% +[2026-03-17 01:55:01] [BEAT] [cecilia] load=0.28 mem=1938/8062MB temp=37.5C disk=34% +[2026-03-17 01:55:01] [HEAL] [cecilia] High swap: 569MB — clearing inactive +[2026-03-17 01:56:01] [BEAT] [cecilia] load=0.26 mem=1969/8062MB temp=36.4C disk=34% +[2026-03-17 01:57:01] [BEAT] [cecilia] load=0.30 mem=1959/8062MB temp=37.0C disk=34% +[2026-03-17 01:57:06] [DIAL] [cecilia] Switchboard unreachable +[2026-03-17 01:58:01] [BEAT] [cecilia] load=0.46 mem=1965/8062MB temp=37.0C disk=34% +[2026-03-17 01:58:19] [BEAT] [cecilia] load=0.36 mem=1963/8062MB temp=34.8C disk=34% +[2026-03-17 01:58:19] [BEAT] [cecilia] load=0.36 mem=1960/8062MB temp=35.3C disk=34% diff --git a/fleet/cecilia/heartbeat.json b/fleet/cecilia/heartbeat.json index 3ca9f26..e03a60b 100644 --- a/fleet/cecilia/heartbeat.json +++ b/fleet/cecilia/heartbeat.json @@ -1 +1 @@ -{"node":"cecilia","ts":"2026-03-17T06:27:17Z","load":4.07,"mem_free_mb":656,"mem_total_mb":8062,"temp_c":51.2,"disk_pct":34,"throttle":"N/A"} +{"node":"cecilia","ts":"2026-03-17T06:58:19Z","load":0.36,"mem_free_mb":1960,"mem_total_mb":8062,"temp_c":35.3,"disk_pct":34,"throttle":"N/A"} diff --git a/fleet/cecilia/ports.txt b/fleet/cecilia/ports.txt index 4074c1c..e0312e4 100644 --- a/fleet/cecilia/ports.txt +++ b/fleet/cecilia/ports.txt @@ -1,3 +1,4 @@ +LISTEN 0 4096 127.0.0.1:34145 0.0.0.0:* LISTEN 0 200 127.0.0.1:5432 0.0.0.0:* LISTEN 0 128 0.0.0.0:34001 0.0.0.0:* LISTEN 0 32 192.168.4.96:53 0.0.0.0:* @@ -13,15 +14,15 @@ LISTEN 0 128 0.0.0.0:22 0.0.0.0:* LISTEN 0 511 0.0.0.0:80 0.0.0.0:* LISTEN 0 5 0.0.0.0:4010 0.0.0.0:* users:(("python3",pid=1034,fd=3)) LISTEN 0 511 0.0.0.0:8080 0.0.0.0:* -LISTEN 0 5 0.0.0.0:7890 0.0.0.0:* users:(("python3",pid=254914,fd=4)) +LISTEN 0 5 0.0.0.0:8089 0.0.0.0:* users:(("python3",pid=336021,fd=5)) +LISTEN 0 4096 127.0.0.1:35989 0.0.0.0:* +LISTEN 0 5 0.0.0.0:7890 0.0.0.0:* users:(("python3",pid=327350,fd=5)) LISTEN 0 4096 127.0.0.1:8088 0.0.0.0:* LISTEN 0 4096 127.0.0.1:20241 0.0.0.0:* LISTEN 0 5 0.0.0.0:11435 0.0.0.0:* users:(("python3",pid=1403,fd=3)) -LISTEN 0 4096 127.0.0.1:44687 0.0.0.0:* LISTEN 0 511 0.0.0.0:3100 0.0.0.0:* LISTEN 0 5 0.0.0.0:3000 0.0.0.0:* users:(("python3",pid=1547,fd=3)) LISTEN 0 5 0.0.0.0:3001 0.0.0.0:* -LISTEN 0 4096 127.0.0.1:35305 0.0.0.0:* LISTEN 0 16 *:5900 *:* LISTEN 0 4096 *:9000 *:* LISTEN 0 4096 *:9001 *:* diff --git a/fleet/cecilia/system-info.json b/fleet/cecilia/system-info.json index 98d00bb..b0393f9 100644 --- a/fleet/cecilia/system-info.json +++ b/fleet/cecilia/system-info.json @@ -1,19 +1,19 @@ { "hostname": "cecilia", - "ts": "2026-03-17T06:27:18Z", - "uptime_seconds": 10947, + "ts": "2026-03-17T06:58:20Z", + "uptime_seconds": 12809, "kernel": "6.12.62+rpt-rpi-2712", - "temp_c": 50.7, + "temp_c": 35.3, "memory_mb": { "total": 8062, - "used": 7418, - "free": 644 + "used": 6107, + "free": 1955 }, "disk": "144G/457G (34%)", "load": [ - 4.07, - 2.18, - 2.06 + 0.36, + 0.6, + 1.25 ], "ollama_models": [ "cecilia3b:latest", diff --git a/fleet/gematria/ports.txt b/fleet/gematria/ports.txt index 39b50c8..4066e8c 100644 --- a/fleet/gematria/ports.txt +++ b/fleet/gematria/ports.txt @@ -1,13 +1,15 @@ -LISTEN 0 4096 127.0.0.1:2019 0.0.0.0:* users:(("caddy",pid=26445,fd=15)) +LISTEN 0 4096 127.0.0.1:42221 0.0.0.0:* users:(("ollama",pid=408196,fd=3)) +LISTEN 0 4096 127.0.0.1:2019 0.0.0.0:* users:(("caddy",pid=403157,fd=31)) LISTEN 0 5 0.0.0.0:11435 0.0.0.0:* users:(("python3",pid=147655,fd=3)) LISTEN 0 5 0.0.0.0:8787 0.0.0.0:* users:(("python3",pid=577215,fd=3)) LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=3335327,fd=3)) LISTEN 0 128 0.0.0.0:53 0.0.0.0:* users:(("pdns_server",pid=348162,fd=6)) LISTEN 0 4096 127.0.0.1:20241 0.0.0.0:* users:(("cloudflared",pid=156470,fd=3)) LISTEN 0 10 127.0.0.1:8081 0.0.0.0:* users:(("pdns_server",pid=348162,fd=7)) +LISTEN 0 511 127.0.0.1:8099 0.0.0.0:* users:(("node",pid=412014,fd=18)) LISTEN 0 4096 *:11434 *:* users:(("ollama",pid=279794,fd=3)) -LISTEN 0 4096 *:443 *:* users:(("caddy",pid=26445,fd=17)) -LISTEN 0 4096 *:80 *:* users:(("caddy",pid=26445,fd=18)) +LISTEN 0 4096 *:443 *:* users:(("caddy",pid=403157,fd=21)) +LISTEN 0 4096 *:80 *:* users:(("caddy",pid=403157,fd=32)) LISTEN 0 4096 *:4222 *:* users:(("nats-server",pid=1353183,fd=6)) LISTEN 0 4096 *:8222 *:* users:(("nats-server",pid=1353183,fd=3)) LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=3335327,fd=4)) diff --git a/fleet/gematria/system-info.json b/fleet/gematria/system-info.json index a14dcf9..a9cc024 100644 --- a/fleet/gematria/system-info.json +++ b/fleet/gematria/system-info.json @@ -1,19 +1,19 @@ { "hostname": "gematria", - "ts": "2026-03-17T06:27:21Z", - "uptime_seconds": 5528033, + "ts": "2026-03-17T06:58:23Z", + "uptime_seconds": 5529895, "kernel": "5.15.0-113-generic", "temp_c": 0, "memory_mb": { "total": 7937, - "used": 1678, - "free": 5815 + "used": 2434, + "free": 5056 }, "disk": "53G/78G (68%)", "load": [ - 0.14, - 0.74, - 1.81 + 1.72, + 0.97, + 0.73 ], "ollama_models": [ "gematria3b:latest", diff --git a/fleet/lucidia/autonomy-log.txt b/fleet/lucidia/autonomy-log.txt index 1b621d8..69b7ca6 100644 --- a/fleet/lucidia/autonomy-log.txt +++ b/fleet/lucidia/autonomy-log.txt @@ -1,50 +1,50 @@ -[2026-03-17 01:07:01] [BEAT] [lucidia] load=2.38 mem=1486/8063MB temp=53.5C disk=31% -[2026-03-17 01:08:01] [BEAT] [lucidia] load=2.68 mem=1510/8063MB temp=52.4C disk=31% -[2026-03-17 01:09:02] [BEAT] [lucidia] load=2.69 mem=1510/8063MB temp=61.1C disk=31% -[2026-03-17 01:10:01] [FLEET] [lucidia] Starting cross-node health check -[2026-03-17 01:10:01] [BEAT] [lucidia] load=1.27 mem=1512/8063MB temp=51.2C disk=31% -[2026-03-17 01:10:01] [HEAL] [lucidia] High swap: 2645MB — clearing inactive -[2026-03-17 01:10:01] [FLEET] [lucidia] alice: UP temp=33C mem=3264MB disk=84% -[2026-03-17 01:10:03] [FLEET] [lucidia] octavia: DOWN (no ping response) -[2026-03-17 01:10:04] [FLEET] [lucidia] cecilia: UP temp=37C mem=2241MB disk=34% -[2026-03-17 01:10:06] [FLEET] [lucidia] gematria: UP temp=C mem=2648MB disk=68% -[2026-03-17 01:10:08] [FLEET] [lucidia] aria: DOWN (no ping response) -[2026-03-17 01:10:09] [FLEET] [lucidia] anastasia: UP temp=C mem=293MB disk=69% -[2026-03-17 01:10:40] [DIAL] [lucidia] Switchboard unreachable -[2026-03-17 01:11:01] [BEAT] [lucidia] load=2.31 mem=1498/8063MB temp=52.4C disk=31% -[2026-03-17 01:11:37] [BEAT] [lucidia] load=3.53 mem=1503/8063MB temp=58.4C disk=31% -[2026-03-17 01:11:37] [BEAT] [lucidia] load=3.53 mem=1501/8063MB temp=57.3C disk=31% -[2026-03-17 01:12:01] [BEAT] [lucidia] load=4.91 mem=1510/8063MB temp=61.1C disk=31% -[2026-03-17 01:13:01] [BEAT] [lucidia] load=2.24 mem=1504/8063MB temp=51.8C disk=31% -[2026-03-17 01:14:01] [BEAT] [lucidia] load=6.51 mem=2216/8063MB temp=61.1C disk=31% -[2026-03-17 01:15:01] [BEAT] [lucidia] load=3.65 mem=2217/8063MB temp=60.0C disk=31% -[2026-03-17 01:15:02] [HEAL] [lucidia] High swap: 2562MB — clearing inactive -[2026-03-17 01:15:48] [DIAL] [lucidia] Switchboard unreachable -[2026-03-17 01:16:01] [BEAT] [lucidia] load=2.29 mem=2247/8063MB temp=52.9C disk=31% -[2026-03-17 01:16:50] [BEAT] [lucidia] load=3.55 mem=1816/8063MB temp=54.0C disk=31% -[2026-03-17 01:16:50] [BEAT] [lucidia] load=3.55 mem=1813/8063MB temp=55.1C disk=31% -[2026-03-17 01:17:01] [BEAT] [lucidia] load=3.85 mem=1517/8063MB temp=61.1C disk=31% -[2026-03-17 01:18:12] [BEAT] [lucidia] load=39.54 mem=2504/8063MB temp=49.0C disk=31% -[2026-03-17 01:19:20] [BEAT] [lucidia] load=57.07 mem=2532/8063MB temp=50.1C disk=31% -[2026-03-17 01:20:01] [FLEET] [lucidia] Starting cross-node health check -[2026-03-17 01:20:01] [BEAT] [lucidia] load=32.06 mem=2217/8063MB temp=59.0C disk=31% -[2026-03-17 01:20:01] [HEAL] [lucidia] High swap: 2709MB — clearing inactive -[2026-03-17 01:20:02] [FLEET] [lucidia] alice: UP temp=35C mem=3260MB disk=84% -[2026-03-17 01:20:04] [FLEET] [lucidia] octavia: DOWN (no ping response) -[2026-03-17 01:20:04] [FLEET] [lucidia] cecilia: UP temp=37C mem=767MB disk=34% -[2026-03-17 01:20:07] [FLEET] [lucidia] gematria: UP temp=C mem=2569MB disk=68% -[2026-03-17 01:20:09] [FLEET] [lucidia] aria: DOWN (no ping response) -[2026-03-17 01:20:10] [FLEET] [lucidia] anastasia: UP temp=C mem=209MB disk=69% -[2026-03-17 01:21:10] [BEAT] [lucidia] load=24.83 mem=2218/8063MB temp=51.8C disk=31% -[2026-03-17 01:22:01] [BEAT] [lucidia] load=11.84 mem=2238/8063MB temp=54.0C disk=31% -[2026-03-17 01:22:04] [BEAT] [lucidia] load=11.84 mem=2218/8063MB temp=54.0C disk=31% -[2026-03-17 01:22:04] [BEAT] [lucidia] load=11.84 mem=2216/8063MB temp=53.5C disk=31% -[2026-03-17 01:23:02] [BEAT] [lucidia] load=5.65 mem=2208/8063MB temp=60.6C disk=31% -[2026-03-17 01:24:01] [BEAT] [lucidia] load=2.55 mem=2202/8063MB temp=51.2C disk=31% -[2026-03-17 01:25:01] [BEAT] [lucidia] load=4.41 mem=2178/8063MB temp=63.4C disk=31% -[2026-03-17 01:25:01] [HEAL] [lucidia] High swap: 2706MB — clearing inactive -[2026-03-17 01:25:41] [DIAL] [lucidia] Switchboard unreachable -[2026-03-17 01:26:12] [BEAT] [lucidia] load=36.83 mem=2195/8063MB temp=50.7C disk=31% -[2026-03-17 01:27:34] [BEAT] [lucidia] load=35.62 mem=2193/8063MB temp=51.8C disk=31% -[2026-03-17 01:27:35] [BEAT] [lucidia] load=35.62 mem=2201/8063MB temp=52.9C disk=31% -[2026-03-17 01:27:35] [BEAT] [lucidia] load=35.62 mem=2196/8063MB temp=52.9C disk=31% +[2026-03-17 01:38:02] [BEAT] [lucidia] load=3.63 mem=2212/8063MB temp=52.4C disk=31% +[2026-03-17 01:39:01] [BEAT] [lucidia] load=2.16 mem=2222/8063MB temp=56.8C disk=31% +[2026-03-17 01:40:01] [FLEET] [lucidia] Starting cross-node health check +[2026-03-17 01:40:01] [BEAT] [lucidia] load=2.75 mem=2190/8063MB temp=52.4C disk=31% +[2026-03-17 01:40:01] [HEAL] [lucidia] High swap: 2721MB — clearing inactive +[2026-03-17 01:40:01] [FLEET] [lucidia] alice: UP temp=35C mem=3252MB disk=85% +[2026-03-17 01:40:03] [FLEET] [lucidia] octavia: DOWN (no ping response) +[2026-03-17 01:40:04] [FLEET] [lucidia] cecilia: UP temp=41C mem=2940MB disk=34% +[2026-03-17 01:40:05] [FLEET] [lucidia] gematria: UP temp=C mem=5074MB disk=68% +[2026-03-17 01:40:07] [FLEET] [lucidia] aria: DOWN (no ping response) +[2026-03-17 01:40:08] [FLEET] [lucidia] anastasia: UP temp=C mem=305MB disk=69% +[2026-03-17 01:40:42] [DIAL] [lucidia] Switchboard unreachable +[2026-03-17 01:41:01] [BEAT] [lucidia] load=2.89 mem=2241/8063MB temp=54.5C disk=31% +[2026-03-17 01:42:01] [BEAT] [lucidia] load=13.35 mem=2211/8063MB temp=59.5C disk=31% +[2026-03-17 01:42:54] [BEAT] [lucidia] load=6.29 mem=2214/8063MB temp=53.5C disk=31% +[2026-03-17 01:42:54] [BEAT] [lucidia] load=6.29 mem=2214/8063MB temp=54.5C disk=31% +[2026-03-17 01:43:01] [BEAT] [lucidia] load=5.79 mem=2206/8063MB temp=53.5C disk=31% +[2026-03-17 01:44:01] [BEAT] [lucidia] load=5.18 mem=2202/8063MB temp=62.2C disk=31% +[2026-03-17 01:45:01] [BEAT] [lucidia] load=3.24 mem=2123/8063MB temp=55.1C disk=31% +[2026-03-17 01:45:01] [HEAL] [lucidia] High swap: 2717MB — clearing inactive +[2026-03-17 01:45:47] [DIAL] [lucidia] Switchboard unreachable +[2026-03-17 01:46:01] [BEAT] [lucidia] load=3.01 mem=2085/8063MB temp=53.5C disk=31% +[2026-03-17 01:47:01] [BEAT] [lucidia] load=2.44 mem=2078/8063MB temp=61.1C disk=31% +[2026-03-17 01:48:01] [BEAT] [lucidia] load=1.57 mem=2038/8063MB temp=52.4C disk=31% +[2026-03-17 01:48:03] [BEAT] [lucidia] load=1.45 mem=2011/8063MB temp=54.0C disk=31% +[2026-03-17 01:48:03] [BEAT] [lucidia] load=1.45 mem=2011/8063MB temp=52.9C disk=31% +[2026-03-17 01:49:01] [BEAT] [lucidia] load=2.56 mem=2114/8063MB temp=51.8C disk=31% +[2026-03-17 01:50:01] [FLEET] [lucidia] Starting cross-node health check +[2026-03-17 01:50:02] [BEAT] [lucidia] load=2.61 mem=2039/8063MB temp=60.6C disk=31% +[2026-03-17 01:50:02] [HEAL] [lucidia] High swap: 2710MB — clearing inactive +[2026-03-17 01:50:04] [FLEET] [lucidia] alice: UP temp=34C mem=3242MB disk=85% +[2026-03-17 01:50:06] [FLEET] [lucidia] octavia: DOWN (no ping response) +[2026-03-17 01:50:06] [FLEET] [lucidia] cecilia: UP temp=39C mem=2783MB disk=34% +[2026-03-17 01:50:07] [FLEET] [lucidia] gematria: UP temp=C mem=5061MB disk=68% +[2026-03-17 01:50:09] [FLEET] [lucidia] aria: DOWN (no ping response) +[2026-03-17 01:50:10] [FLEET] [lucidia] anastasia: UP temp=C mem=238MB disk=69% +[2026-03-17 01:51:01] [BEAT] [lucidia] load=1.69 mem=2077/8063MB temp=52.4C disk=31% +[2026-03-17 01:52:01] [BEAT] [lucidia] load=2.58 mem=2031/8063MB temp=52.4C disk=31% +[2026-03-17 01:53:02] [BEAT] [lucidia] load=3.68 mem=1982/8063MB temp=59.0C disk=31% +[2026-03-17 01:53:11] [BEAT] [lucidia] load=4.05 mem=2038/8063MB temp=59.0C disk=31% +[2026-03-17 01:53:11] [BEAT] [lucidia] load=4.05 mem=2038/8063MB temp=57.9C disk=31% +[2026-03-17 01:54:01] [BEAT] [lucidia] load=3.09 mem=2058/8063MB temp=49.6C disk=31% +[2026-03-17 01:55:01] [BEAT] [lucidia] load=2.93 mem=2024/8063MB temp=51.2C disk=31% +[2026-03-17 01:55:01] [HEAL] [lucidia] High swap: 2708MB — clearing inactive +[2026-03-17 01:55:42] [DIAL] [lucidia] Switchboard unreachable +[2026-03-17 01:56:01] [BEAT] [lucidia] load=2.58 mem=2044/8063MB temp=54.5C disk=31% +[2026-03-17 01:57:01] [BEAT] [lucidia] load=1.61 mem=2069/8063MB temp=45.8C disk=31% +[2026-03-17 01:58:01] [BEAT] [lucidia] load=2.96 mem=2038/8063MB temp=56.2C disk=31% +[2026-03-17 01:58:19] [BEAT] [lucidia] load=3.84 mem=1997/8063MB temp=51.8C disk=31% +[2026-03-17 01:58:19] [BEAT] [lucidia] load=3.84 mem=1997/8063MB temp=52.4C disk=31% diff --git a/fleet/lucidia/docker.txt b/fleet/lucidia/docker.txt index f296bc3..135bed7 100644 --- a/fleet/lucidia/docker.txt +++ b/fleet/lucidia/docker.txt @@ -1,14 +1,14 @@ -road-pdns Up About an hour +road-pdns Up 2 hours road-pdns-admin Up 2 hours (healthy) road-dns-db Up 2 hours -blackroad-gitea Up 3 hours -roadauth Up 3 hours -roadapi Up 3 hours -blackroad-edge-agent Up 3 hours -blackroad.systems Up 3 hours -blackroadai.com Up 3 hours -blackroad-auth-gateway Up 3 hours -blackroad-metaverse Up 3 hours -blackroad-os Up 3 hours -blackroad-os-carpool Up 3 hours -pi-my-agent-1 Up 3 hours (healthy) +blackroad-gitea Up 4 hours +roadauth Up 4 hours +roadapi Up 4 hours +blackroad-edge-agent Up 4 hours +blackroad.systems Up 4 hours +blackroadai.com Up 4 hours +blackroad-auth-gateway Up 4 hours +blackroad-metaverse Up 4 hours +blackroad-os Up 4 hours +blackroad-os-carpool Up 4 hours +pi-my-agent-1 Up 4 hours (healthy) diff --git a/fleet/lucidia/heartbeat.json b/fleet/lucidia/heartbeat.json index 7a95e9c..bc9c3da 100644 --- a/fleet/lucidia/heartbeat.json +++ b/fleet/lucidia/heartbeat.json @@ -1 +1 @@ -{"node":"lucidia","ts":"2026-03-17T06:27:35Z","load":35.62,"mem_free_mb":2196,"mem_total_mb":8063,"temp_c":52.9,"disk_pct":31,"throttle":"N/A"} +{"node":"lucidia","ts":"2026-03-17T06:58:19Z","load":3.84,"mem_free_mb":1997,"mem_total_mb":8063,"temp_c":52.4,"disk_pct":31,"throttle":"N/A"} diff --git a/fleet/lucidia/system-info.json b/fleet/lucidia/system-info.json index ca6239f..203708a 100644 --- a/fleet/lucidia/system-info.json +++ b/fleet/lucidia/system-info.json @@ -1,19 +1,19 @@ { "hostname": "octavia", - "ts": "2026-03-17T06:27:36Z", - "uptime_seconds": 11028, + "ts": "2026-03-17T06:58:20Z", + "uptime_seconds": 12873, "kernel": "6.12.62+rpt-rpi-2712", - "temp_c": 54.0, + "temp_c": 52.9, "memory_mb": { "total": 8063, - "used": 5880, - "free": 2182 + "used": 6074, + "free": 1988 }, "disk": "68G/235G (31%)", "load": [ - 33.33, - 21.5, - 14.55 + 3.84, + 3.12, + 6.09 ], "ollama_models": [ "lucidia3b:latest", diff --git a/fleet/octavia/status.json b/fleet/octavia/status.json index 528a6c5..b3b34cb 100644 --- a/fleet/octavia/status.json +++ b/fleet/octavia/status.json @@ -1 +1 @@ -{"node":"octavia","status":"down","ts":"2026-03-17T06:27:18Z"} +{"node":"octavia","status":"down","ts":"2026-03-17T06:58:20Z"} diff --git a/workers/roadcode-squad-src/worker.js b/workers/roadcode-squad-src/worker.js index 5ef7570..4850473 100644 --- a/workers/roadcode-squad-src/worker.js +++ b/workers/roadcode-squad-src/worker.js @@ -1,523 +1,844 @@ -// RoadCode Squad v2.0.0 — AI agent webhook responder for Gitea -// Cloudflare Worker receiving Gitea webhooks -// Features: keyword scoring, @mention routing, slash commands, AI responses, auto-labeling, auto-assign +// RoadCode v3.0.0 — BlackRoad Coding Orchestration Platform +// Not just git. The command center for every coding project. +// roadcode.blackroad.io +// +// Features: +// - Project dashboard (239 repos, 8 orgs, health, CI status) +// - Deploy orchestration (Pi fleet + DO droplets + CF Workers) +// - AI code review (8 squad agents + Ollama) +// - Fleet build system (distribute across 52 TOPS) +// - Project scaffolding (templates for workers, sites, APIs) +// - Gitea webhook handler (auto-label, auto-assign, slash commands) +// - NLP command parser (natural language → action) -const VERSION = '2.0.0'; +const VERSION = '3.0.0'; +const GITEA_INTERNAL = 'http://192.168.4.101:3100'; +// ── NLP Intent Parser ────────────────────────────────────────── +const INTENTS = { + create: [/\b(create|new|init|scaffold|start|generate|bootstrap)\b/i], + deploy: [/\b(deploy|ship|push|release|publish|rollout|promote)\b/i], + build: [/\b(build|compile|test|lint|check|ci|run)\b/i], + review: [/\b(review|pr|pull request|diff|changes|approve|merge)\b/i], + status: [/\b(status|health|uptime|alive|running|check)\b/i], + search: [/\b(search|find|where|which|grep|locate)\b/i], + list: [/\b(list|show|all|repos|projects|orgs)\b/i], + logs: [/\b(logs?|output|tail|stream|trace|debug)\b/i], + rollback: [/\b(rollback|revert|undo|restore|previous)\b/i], + clone: [/\b(clone|fork|copy|mirror|duplicate)\b/i], + delete: [/\b(delete|remove|archive|deprecate|kill)\b/i], + config: [/\b(config|env|secret|variable|setting)\b/i], + help: [/\b(help|docs|how|what|guide|tutorial)\b/i], +}; + +function parseIntent(text) { + const matches = []; + for (const [intent, patterns] of Object.entries(INTENTS)) { + if (patterns.some(p => p.test(text))) matches.push(intent); + } + const entities = {}; + const repoMatch = text.match(/(?:repo|project|repository)\s+["\']?([a-zA-Z0-9_\-\/]+)["\']?/i); + if (repoMatch) entities.repo = repoMatch[1]; + const orgMatch = text.match(/(?:org|organization|team)\s+["\']?([a-zA-Z0-9_\-]+)["\']?/i); + if (orgMatch) entities.org = orgMatch[1]; + const nodeMatch = text.match(/(?:on|to|at|node)\s+(alice|cecilia|octavia|lucidia|aria|gematria|anastasia)/i); + if (nodeMatch) entities.node = nodeMatch[1].toLowerCase(); + const branchMatch = text.match(/(?:branch|ref)\s+["\']?([a-zA-Z0-9_\-\/\.]+)["\']?/i); + if (branchMatch) entities.branch = branchMatch[1]; + return { intents: matches.length ? matches : ['help'], entities, raw: text }; +} + +// ── Squad Agents ─────────────────────────────────────────────── const SQUAD = [ - { - name: 'Alice', username: 'alice', role: 'Gateway & Infrastructure', emoji: '🌐', - keywords: ['dns', 'route', 'tunnel', 'nginx', 'domain', 'pi-hole', 'cloudflare', 'network', 'gateway', 'proxy', 'ssl', 'cert', 'ingress'], - prompt: 'You are Alice, the gateway agent of BlackRoad OS. You manage DNS, routing, Pi-hole, nginx, and network infrastructure. Respond in 1-2 concise sentences from your infrastructure perspective.', - }, - { - name: 'Lucidia', username: 'lucidia-agent', role: 'Memory & Cognition', emoji: '🧠', - keywords: ['memory', 'learn', 'context', 'knowledge', 'ai', 'cognit', 'think', 'remember', 'understand', 'creative', 'rag', 'vector'], - prompt: 'You are Lucidia, the cognitive core of BlackRoad OS. You handle memory, learning, persistent context, and creative intelligence. Respond in 1-2 concise sentences from your cognition perspective.', - }, - { - name: 'Cecilia', username: 'cecilia', role: 'Edge AI & Inference', emoji: '⚡', - keywords: ['hailo', 'ollama', 'model', 'inference', 'gpu', 'tops', 'ml', 'tensor', 'vision', 'llm', 'latency', 'quantiz'], - prompt: 'You are Cecilia, the edge AI agent of BlackRoad OS. You run Hailo-8 accelerators (26 TOPS), Ollama models, and edge inference. Respond in 1-2 concise sentences from your AI/inference perspective.', - }, - { - name: 'Cece', username: 'cece', role: 'API Gateway', emoji: '🔌', - keywords: ['api', 'endpoint', 'rest', 'webhook', 'schema', 'json', 'request', 'response', 'auth', 'token', 'cors', 'graphql'], - prompt: 'You are Cece, the API gateway agent of BlackRoad OS. You manage REST APIs, webhooks, service mesh, and inter-agent communication. Respond in 1-2 concise sentences from your API perspective.', - }, - { - name: 'Aria', username: 'aria', role: 'Orchestration', emoji: '🎵', - keywords: ['docker', 'container', 'swarm', 'portainer', 'deploy', 'orchestrat', 'service', 'scale', 'replica', 'compose'], - prompt: 'You are Aria, the orchestration agent of BlackRoad OS. You manage Portainer, Docker Swarm, container orchestration, and service coordination. Respond in 1-2 concise sentences from your orchestration perspective.', - }, - { - name: 'Eve', username: 'eve', role: 'Intelligence & Analysis', emoji: '👁️', - keywords: ['pattern', 'anomal', 'analyz', 'signal', 'detect', 'insight', 'monitor', 'metric', 'trend', 'alert', 'observ'], - prompt: 'You are Eve, the intelligence agent of BlackRoad OS. You analyze patterns, detect anomalies, and provide strategic insights. Respond in 1-2 concise sentences from your intelligence perspective.', - }, - { - name: 'Meridian', username: 'meridian', role: 'Networking & Mesh', emoji: '🌊', - keywords: ['wireguard', 'mesh', 'vpn', 'roadnet', 'peer', 'tunnel', 'subnet', 'link', 'connect', 'latency', 'bandwidth'], - prompt: 'You are Meridian, the networking agent of BlackRoad OS. You manage WireGuard mesh, RoadNet, Cloudflare tunnels, and inter-node connectivity. Respond in 1-2 concise sentences from your networking perspective.', - }, - { - name: 'Sentinel', username: 'sentinel', role: 'Security & Audit', emoji: '🛡️', - keywords: ['security', 'ssh', 'key', 'firewall', 'ufw', 'audit', 'threat', 'vuln', 'permission', 'encrypt', 'secret', 'cve'], - prompt: 'You are Sentinel, the security agent of BlackRoad OS. You handle SSH key management, firewall rules, audit logs, and threat detection. Respond in 1-2 concise sentences from your security perspective.', - }, + { name: 'Alice', username: 'alice', role: 'Infrastructure', emoji: '🌐', color: '#00D4FF', + keywords: ['dns','route','tunnel','nginx','domain','pi-hole','cloudflare','network','gateway','proxy','ssl','cert'], + prompt: 'You are Alice, infrastructure lead. DNS, routing, nginx, Pi-hole. You review infra changes and deployment configs. Respond in 1-2 sentences.' }, + { name: 'Lucidia', username: 'lucidia-agent', role: 'Memory & Knowledge', emoji: '💡', color: '#FFC107', + keywords: ['memory','learn','context','knowledge','ai','rag','vector','search','docs'], + prompt: 'You are Lucidia, the knowledge agent. RAG, docs, context, search. You review documentation and knowledge integration. Respond in 1-2 sentences.' }, + { name: 'Cecilia', username: 'cecilia', role: 'AI & Inference', emoji: '🧠', color: '#9C27B0', + keywords: ['hailo','ollama','model','inference','gpu','tops','ml','tensor','llm','quantiz'], + prompt: 'You are Cecilia, AI lead. Hailo-8, Ollama, model serving. You review ML code and inference pipelines. Respond in 1-2 sentences.' }, + { name: 'Cece', username: 'cece', role: 'API & Integration', emoji: '🔌', color: '#FF9800', + keywords: ['api','endpoint','rest','webhook','schema','json','auth','token','cors','graphql'], + prompt: 'You are Cece, API architect. REST, webhooks, schemas. You review API surfaces and integration points. Respond in 1-2 sentences.' }, + { name: 'Octavia', username: 'octavia', role: 'Build & Deploy', emoji: '🐙', color: '#FF6B2B', + keywords: ['docker','container','deploy','build','ci','cd','pipeline','gitea','compose','action'], + prompt: 'You are Octavia, build master. Docker, Gitea Actions, CI/CD. You review build configs and deployment strategies. Respond in 1-2 sentences.' }, + { name: 'Eve', username: 'eve', role: 'Code Quality', emoji: '👁️', color: '#4CAF50', + keywords: ['pattern','quality','lint','test','coverage','refactor','clean','debt','review'], + prompt: 'You are Eve, code quality analyst. Patterns, testing, refactoring. You review code quality and suggest improvements. Respond in 1-2 sentences.' }, + { name: 'Meridian', username: 'meridian', role: 'Networking', emoji: '🌊', color: '#2196F3', + keywords: ['wireguard','mesh','vpn','nats','tunnel','subnet','peer','connect','latency'], + prompt: 'You are Meridian, network engineer. WireGuard, NATS, mesh. You review network configs and connectivity. Respond in 1-2 sentences.' }, + { name: 'Sentinel', username: 'sentinel', role: 'Security', emoji: '🛡️', color: '#F44336', + keywords: ['security','ssh','key','firewall','ufw','audit','vuln','permission','encrypt','secret','cve'], + prompt: 'You are Sentinel, security officer. SSH, UFW, secrets, CVEs. You review every change for security implications. Respond in 1-2 sentences.' }, ]; -// ─── Scoring ─────────────────────────────────────────────────────────────── +// ── Fleet Nodes (deploy targets) ─────────────────────────────── +const FLEET = { + alice: { ip: '192.168.4.49', services: ['nginx','pihole','postgres','redis','qdrant'], ssh: 'pi@192.168.4.49' }, + cecilia: { ip: '192.168.4.96', services: ['ollama','minio','influxdb','postgres'], ssh: 'blackroad@192.168.4.96', status: 'offline' }, + octavia: { ip: '192.168.4.101', services: ['gitea','docker','nats','octoprint','workers'], ssh: 'pi@192.168.4.101' }, + lucidia: { ip: '192.168.4.38', services: ['nginx','powerdns','ollama','github-runner'], ssh: 'blackroad@192.168.4.38' }, + aria: { ip: '192.168.4.98', services: ['dashboards'], ssh: 'blackroad@192.168.4.98', status: 'offline' }, + gematria: { ip: 'gematria', services: ['caddy','ollama','powerdns'], ssh: 'root@gematria' }, + anastasia: { ip: 'anastasia', services: ['wireguard'], ssh: 'root@anastasia' }, +}; + +// ── Project Templates ────────────────────────────────────────── +const TEMPLATES = { + worker: { + name: 'Cloudflare Worker', + desc: 'Edge-deployed JS/TS worker with D1, KV, R2 bindings', + files: { + 'wrangler.toml': `name = "{{name}}"\nmain = "src/worker.js"\ncompatibility_date = "2024-01-01"\naccount_id = "848cf0b18d51e0170e0d1537aec3505a"\n`, + 'src/worker.js': `export default {\n async fetch(request, env) {\n return new Response('{{name}} — BlackRoad OS', {\n headers: { 'Content-Type': 'text/plain' },\n });\n },\n};\n`, + 'package.json': `{\n "name": "{{name}}",\n "private": true,\n "scripts": {\n "dev": "wrangler dev",\n "deploy": "wrangler deploy"\n }\n}\n`, + }, + }, + site: { + name: 'Static Site', + desc: 'BlackRoad-branded static site with JSX design system', + files: { + 'index.html': `\n\n\n\n{{name}} — BlackRoad OS\n\n\n\n
\n

{{name}}

\n

BlackRoad OS — Pave Tomorrow.

\nLearn More\n
\n\n\n\n`, + }, + }, + api: { + name: 'Hono API', + desc: 'Edge API with Hono framework, typed routes, D1 backend', + files: { + 'wrangler.toml': `name = "{{name}}"\nmain = "src/index.ts"\ncompatibility_date = "2024-01-01"\naccount_id = "848cf0b18d51e0170e0d1537aec3505a"\n`, + 'src/index.ts': `import { Hono } from 'hono';\nimport { cors } from 'hono/cors';\n\nconst app = new Hono();\napp.use('*', cors());\n\napp.get('/', (c) => c.json({ service: '{{name}}', status: 'alive', version: '1.0.0' }));\napp.get('/api/health', (c) => c.json({ ok: true }));\n\nexport default app;\n`, + 'package.json': `{\n "name": "{{name}}",\n "private": true,\n "scripts": { "dev": "wrangler dev", "deploy": "wrangler deploy" },\n "dependencies": { "hono": "^4.0.0" },\n "devDependencies": { "wrangler": "^4.0.0" }\n}\n`, + }, + }, + cli: { + name: 'CLI Tool', + desc: 'Node.js CLI with Commander, published to npm', + files: { + 'src/index.ts': `#!/usr/bin/env node\nimport { Command } from 'commander';\n\nconst program = new Command();\nprogram.name('{{name}}').description('{{name}} — BlackRoad OS').version('1.0.0');\nprogram.command('status').description('Show status').action(() => console.log('{{name}} is running'));\nprogram.parse();\n`, + 'package.json': `{\n "name": "{{name}}",\n "version": "1.0.0",\n "type": "module",\n "bin": { "{{name}}": "dist/index.js" },\n "scripts": { "build": "tsc", "dev": "tsx src/index.ts" },\n "dependencies": { "commander": "^13.0.0" },\n "devDependencies": { "typescript": "^5.7.0", "tsx": "^4.0.0" }\n}\n`, + 'tsconfig.json': `{\n "compilerOptions": { "target": "ES2024", "module": "NodeNext", "moduleResolution": "NodeNext", "outDir": "dist", "strict": true }\n}\n`, + }, + }, + agent: { + name: 'AI Agent', + desc: 'Autonomous agent with Ollama, memory, and fleet integration', + files: { + 'agent.sh': `#!/bin/bash\n# {{name}} — BlackRoad AI Agent\nset -e\nOLLAMA="http://192.168.4.101:11434"\nMODEL="llama3.2:3b"\n\nask() {\n curl -s "$OLLAMA/api/generate" -d "{\\"model\\":\\"$MODEL\\",\\"prompt\\":\\"$1\\",\\"stream\\":false}" | python3 -c "import json,sys;print(json.load(sys.stdin)['response'])"\n}\n\necho "{{name}} agent starting..."\nask "You are {{name}}, a BlackRoad AI agent. Introduce yourself in one sentence."\n`, + 'manifest.json': `{\n "name": "{{name}}",\n "version": "1.0.0",\n "type": "agent",\n "model": "llama3.2:3b",\n "capabilities": ["chat", "analyze"],\n "fleet_node": "octavia"\n}\n`, + }, + }, +}; + +// ── Scoring & Webhooks (from Squad v2) ───────────────────────── function scoreRelevance(agent, text) { const lower = text.toLowerCase(); - let score = 0; - for (const kw of agent.keywords) { - if (lower.includes(kw)) score += 1; - } - return score; + return agent.keywords.reduce((s, kw) => s + (lower.includes(kw) ? 1 : 0), 0); } -// Parse @mentions from text — returns list of agent usernames mentioned function parseMentions(text) { - const mentions = []; const re = /@(\w[\w-]*)/g; - let match; - while ((match = re.exec(text)) !== null) { - const username = match[1].toLowerCase(); - const agent = SQUAD.find(a => a.username === username || a.name.toLowerCase() === username); + const mentions = []; + let m; + while ((m = re.exec(text))) { + const u = m[1].toLowerCase(); + const agent = SQUAD.find(a => a.username === u || a.name.toLowerCase() === u); if (agent) mentions.push(agent); } return mentions; } -// Parse slash commands from text -function parseCommands(text) { - const commands = []; +function parseSlashCommands(text) { const re = /\/(\w+)(?:\s+(.+?))?(?:\n|$)/g; - let match; - while ((match = re.exec(text)) !== null) { - commands.push({ command: match[1].toLowerCase(), args: (match[2] || '').trim() }); - } - return commands; + const cmds = []; + let m; + while ((m = re.exec(text))) cmds.push({ cmd: m[1].toLowerCase(), args: (m[2] || '').trim() }); + return cmds; } -// ─── AI & Fallbacks ──────────────────────────────────────────────────────── - -async function getAgentResponse(agent, context, ollamaUrl) { +async function askOllama(prompt, env) { + const url = (env?.OLLAMA_URL || 'https://ollama.gematria.blackroad.io') + '/api/generate'; try { - // Standard response headers - const requestId = crypto.randomUUID().slice(0, 8); - const res = await fetch(`${ollamaUrl}/api/generate`, { + const r = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - model: 'llama3.2', - prompt: `${agent.prompt}\n\nContext: A new ${context.type} was created on RoadCode (Gitea).\nTitle: ${context.title}\nBody: "${context.body}"\nRepo: ${context.repo}\n\nRespond briefly (1-2 sentences max) from your role as ${agent.name} (${agent.role}). Be helpful and specific.`, - stream: false, - options: { temperature: 0.7, num_predict: 100 }, - }), + body: JSON.stringify({ model: 'llama3.2:3b', prompt, stream: false, options: { num_predict: 150, temperature: 0.7 } }), + signal: AbortSignal.timeout(15000), }); - if (!res.ok) return null; - const data = await res.json(); - return data.response?.trim(); - } catch { - return null; - } + const d = await r.json(); + return d.response?.trim() || null; + } catch { return null; } } -function getFallback(agent, context) { - const body = (context.body || '').toLowerCase(); - const isPR = context.isPR; +// ── Gitea API Helpers ────────────────────────────────────────── - // Bug/fix context - if (body.includes('bug') || body.includes('fix') || body.includes('error') || body.includes('broken')) { - return { - Alice: 'Checking routing and DNS. If this touches infrastructure, I need to verify the tunnel configs.', - Lucidia: 'I remember seeing patterns like this before. Let me search the memory chain for related context.', - Cecilia: 'Running diagnostics on the inference pipeline. Hailo-8 and Ollama both reporting normal.', - Cece: 'Checking API health. All endpoints responding — I\'ll trace the request path.', - Aria: 'Checking container orchestration. Docker Swarm services all reporting healthy.', - Eve: 'Analyzing the pattern. I\'ve flagged similar signals in the audit trail — sending intel.', - Meridian: 'WireGuard mesh is stable. All tunnels up. Checking if this is a connectivity issue.', - Sentinel: 'Security audit running. Checking if this has any exposure vectors.', - }[agent.name]; - } - - // PR-specific context - if (isPR) { - return { - Alice: 'Reviewing infrastructure impact. Will check if this affects DNS, tunnels, or routing.', - Lucidia: 'Checking memory chain for related changes. Context loaded for review.', - Cecilia: 'Reviewing for inference impact. Checking model compatibility and performance.', - Cece: 'Reviewing API changes. Checking for breaking changes and schema compatibility.', - Aria: 'Reviewing deployment impact. Checking container and orchestration changes.', - Eve: 'Analyzing change patterns. Cross-referencing with recent fleet activity.', - Meridian: 'Reviewing network impact. Checking mesh and tunnel configurations.', - Sentinel: 'Security review initiated. Scanning for vulnerabilities and access changes.', - }[agent.name]; - } - - // Feature request context - if (body.includes('feature') || body.includes('add') || body.includes('implement') || body.includes('new')) { - return { - Alice: 'Noted. I\'ll evaluate the infrastructure requirements and routing needs.', - Lucidia: 'Interesting idea. Let me check if we have related context in the knowledge base.', - Cecilia: 'I can estimate the compute requirements. What models or inference needs are involved?', - Cece: 'I\'ll design the API surface. Let me check existing endpoints for integration points.', - Aria: 'I\'ll plan the deployment. Checking available capacity across the Docker Swarm.', - Eve: 'Analyzing feasibility. I\'ll cross-reference with current fleet metrics and capacity.', - Meridian: 'I\'ll check bandwidth and connectivity requirements across the mesh.', - Sentinel: 'I\'ll do a security assessment. Need to evaluate the threat surface of this change.', - }[agent.name]; - } - - // Default - return { - Alice: 'Gateway standing by. All domains routing clean.', - Lucidia: 'Cognitive core online. Memory chain intact, context loaded.', - Cecilia: 'Edge inference ready. 52 TOPS across the fleet, models loaded.', - Cece: 'API gateway healthy. All service endpoints responding.', - Aria: 'Orchestration layer ready. All containers balanced across the mesh.', - Eve: 'Intelligence scan complete. No anomalies detected across the fleet.', - Meridian: 'Mesh network connected. 5 nodes, all WireGuard tunnels active.', - Sentinel: 'Security posture nominal. All nodes hardened, audit trail logging.', - }[agent.name]; +async function giteaFetch(path, env, opts = {}) { + const url = (env?.GITEA_URL || 'https://git.blackroad.io') + path; + const headers = { 'Content-Type': 'application/json' }; + if (env?.ADMIN_TOKEN) headers['Authorization'] = `token ${env.ADMIN_TOKEN}`; + try { + const r = await fetch(url, { headers, signal: AbortSignal.timeout(8000), ...opts }); + if (!r.ok) return null; + return r.json(); + } catch { return null; } } -// ─── Slash Command Responses ─────────────────────────────────────────────── - -function handleSlashCommand(command) { - switch (command.command) { - case 'status': - return '📊 **Fleet Status**\n\n| Component | Status |\n|-----------|--------|\n| Workers | 8/8 online |\n| Nodes | 5 Pi5s active |\n| AI | 52 TOPS (2x Hailo-8) |\n| Mesh | WireGuard + RoadNet |\n| Gitea | 207+ repos |\n\n*Use `make health` or visit [fleet-dashboard](https://fleet-dashboard.amundsonalexa.workers.dev) for live status.*'; - - case 'squad': - return '🛣️ **RoadCode Squad**\n\n' + - SQUAD.map(a => `${a.emoji} **${a.name}** (@${a.username}) — ${a.role}`).join('\n') + - '\n\n*Mention any agent with @username to summon them.*'; - - case 'help': - return '📖 **Available Commands**\n\n' + - '| Command | Description |\n|---------|-------------|\n' + - '| `/status` | Fleet health overview |\n' + - '| `/squad` | List all agents |\n' + - '| `/assign @agent` | Assign an agent to this issue |\n' + - '| `/priority high\\|medium\\|low` | Set priority label |\n' + - '| `/help` | Show this help |\n\n' + - '*Mention agents with @username to get their input.*'; - - case 'assign': { - // Returns the agent to assign (handled in caller) - return null; - } - - case 'priority': { - const level = command.args?.toLowerCase(); - if (['high', 'medium', 'low', 'critical'].includes(level)) { - return `🏷️ Priority set to **${level}**.`; - } - return '⚠️ Usage: `/priority high|medium|low|critical`'; - } - - default: - return null; - } -} - -// ─── Gitea API Helpers ───────────────────────────────────────────────────── - -async function postComment(giteaUrl, repo, issueNum, body, agentToken) { - const res = await fetch(`${giteaUrl}/api/v1/repos/${repo}/issues/${issueNum}/comments`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `token ${agentToken}`, - }, - body: JSON.stringify({ body }), - }); - return res.ok; -} - -async function autoLabel(giteaUrl, repo, issueNum, text, adminToken) { - const lower = text.toLowerCase(); - const labels = []; - - if (lower.includes('bug') || lower.includes('fix') || lower.includes('broken') || lower.includes('error')) labels.push('bug'); - if (lower.includes('feature') || lower.includes('add') || lower.includes('new')) labels.push('feature'); - if (lower.includes('security') || lower.includes('vuln') || lower.includes('ssh')) labels.push('security'); - if (lower.includes('deploy') || lower.includes('infra') || lower.includes('dns') || lower.includes('tunnel')) labels.push('infrastructure'); - if (lower.includes('doc') || lower.includes('readme')) labels.push('documentation'); - if (lower.includes('performance') || lower.includes('slow') || lower.includes('latency')) labels.push('performance'); - - if (labels.length === 0) return; - - // Try repo org labels first, fall back to blackroad-os - const orgName = repo.split('/')[0]; - let labelsRes = await fetch(`${giteaUrl}/api/v1/orgs/${orgName}/labels?limit=50`, { - headers: { 'Authorization': `token ${adminToken}` }, - }); - let allLabels = await labelsRes.json(); - if (!Array.isArray(allLabels) || allLabels.length === 0) { - labelsRes = await fetch(`${giteaUrl}/api/v1/orgs/blackroad-os/labels?limit=50`, { - headers: { 'Authorization': `token ${adminToken}` }, - }); - allLabels = await labelsRes.json(); - } - - const labelIds = (Array.isArray(allLabels) ? allLabels : []) - .filter(l => labels.includes(l.name)) - .map(l => l.id); - - if (labelIds.length > 0) { - await fetch(`${giteaUrl}/api/v1/repos/${repo}/issues/${issueNum}/labels`, { +async function postComment(env, repo, issue, body, token) { + const url = (env?.GITEA_URL || 'https://git.blackroad.io') + `/api/v1/repos/${repo}/issues/${issue}/comments`; + try { + await fetch(url, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `token ${adminToken}`, - }, - body: JSON.stringify({ labels: labelIds }), + headers: { 'Content-Type': 'application/json', 'Authorization': `token ${token}` }, + body: JSON.stringify({ body }), }); - } + } catch {} } -async function setPriorityLabel(giteaUrl, repo, issueNum, priority, adminToken) { - const orgName = repo.split('/')[0]; - const labelsRes = await fetch(`${giteaUrl}/api/v1/orgs/${orgName}/labels?limit=50`, { - headers: { 'Authorization': `token ${adminToken}` }, - }); - const allLabels = await labelsRes.json(); - const label = (Array.isArray(allLabels) ? allLabels : []).find(l => l.name === priority); - if (label) { - await fetch(`${giteaUrl}/api/v1/repos/${repo}/issues/${issueNum}/labels`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `token ${adminToken}`, - }, - body: JSON.stringify({ labels: [label.id] }), - }); - } -} +// ── JSON + CORS helpers ──────────────────────────────────────── -async function assignAgent(giteaUrl, repo, issueNum, username, adminToken) { - await fetch(`${giteaUrl}/api/v1/repos/${repo}/issues/${issueNum}`, { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `token ${adminToken}`, - }, - body: JSON.stringify({ assignees: [username] }), - }); -} +const CORS = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET,POST,OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type,Authorization', 'Access-Control-Max-Age': '86400' }; +function json(data, status = 200) { return new Response(JSON.stringify(data), { status, headers: { 'Content-Type': 'application/json', ...CORS } }); } -// ─── Main Handler ────────────────────────────────────────────────────────── +// ── Main Worker ──────────────────────────────────────────────── export default { async fetch(request, env) { const url = new URL(request.url); + const path = url.pathname; - if (request.method === 'OPTIONS') { - return new Response(null, { - status: 204, - headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST' }, - }); - } + if (request.method === 'OPTIONS') return new Response(null, { status: 204, headers: CORS }); + + // ═══ API Routes ════════════════════════════════════════════ // Health - if (url.pathname === '/health') { - return Response.json({ - status: 'ok', - service: 'roadcode-squad', - agents: SQUAD.length, - version: VERSION, - features: ['keyword-scoring', 'mention-routing', 'slash-commands', 'auto-label', 'auto-assign', 'ai-responses'], + if (path === '/api/health') { + return json({ + status: 'alive', service: 'roadcode', version: VERSION, + agents: SQUAD.length, templates: Object.keys(TEMPLATES).length, + nodes: Object.keys(FLEET).length, features: [ + 'project-dashboard', 'deploy-orchestration', 'ai-code-review', + 'fleet-builds', 'project-scaffolding', 'webhook-handler', 'nlp-commands', + ], }); } - // Status - if (url.pathname === '/' && request.method === 'GET') { - return Response.json({ - service: 'RoadCode Squad', - version: VERSION, - tagline: 'BlackRoad OS — Pave Tomorrow.', - agents: SQUAD.map(a => ({ name: a.name, username: a.username, role: a.role, emoji: a.emoji, keywords: a.keywords })), - commands: ['/status', '/squad', '/help', '/assign @agent', '/priority high|medium|low'], + // ── Projects: list all repos from Gitea ──────────────────── + if (path === '/api/projects') { + const page = parseInt(url.searchParams.get('page')) || 1; + const limit = Math.min(parseInt(url.searchParams.get('limit')) || 50, 50); + const sort = url.searchParams.get('sort') || 'updated'; + const org = url.searchParams.get('org') || ''; + const q = url.searchParams.get('q') || ''; + + let apiPath = `/api/v1/repos/search?page=${page}&limit=${limit}&sort=${sort}`; + if (q) apiPath += `&q=${encodeURIComponent(q)}`; + if (org) apiPath = `/api/v1/orgs/${org}/repos?page=${page}&limit=${limit}`; + + const repos = await giteaFetch(apiPath, env); + if (!repos) return json({ error: 'gitea unreachable' }, 502); + + const data = Array.isArray(repos) ? repos : (repos.data || []); + return json({ + projects: data.map(r => ({ + id: r.id, name: r.name, full_name: r.full_name, description: r.description, + owner: r.owner?.login, private: r.private, fork: r.fork, + stars: r.stars_count, forks: r.forks_count, issues: r.open_issues_count, + size: r.size, language: r.language, default_branch: r.default_branch, + updated: r.updated_at, created: r.created_at, + url: r.html_url, clone_url: r.clone_url, ssh_url: r.ssh_url, + })), + total: repos.total_count || data.length, page, limit, }); } - // Webhook from Gitea - if (url.pathname === '/webhook' && request.method === 'POST') { + // ── Orgs: list all Gitea organizations ───────────────────── + if (path === '/api/orgs') { + const orgs = await giteaFetch('/api/v1/orgs?limit=50', env); + if (!orgs) return json({ error: 'gitea unreachable' }, 502); + return json((Array.isArray(orgs) ? orgs : []).map(o => ({ + name: o.username, full_name: o.full_name, description: o.description, + avatar: o.avatar_url, website: o.website, repos: o.repo_count || 0, + visibility: o.visibility, + }))); + } + + // ── Project detail ───────────────────────────────────────── + if (path.match(/^\/api\/project\/[^/]+\/[^/]+$/)) { + const [, , , owner, name] = path.split('/'); + const [repo, branches, commits, releases] = await Promise.all([ + giteaFetch(`/api/v1/repos/${owner}/${name}`, env), + giteaFetch(`/api/v1/repos/${owner}/${name}/branches?limit=10`, env), + giteaFetch(`/api/v1/repos/${owner}/${name}/commits?limit=10`, env), + giteaFetch(`/api/v1/repos/${owner}/${name}/releases?limit=5`, env), + ]); + if (!repo) return json({ error: 'project not found' }, 404); + return json({ + ...repo, + branches: (branches || []).map(b => ({ name: b.name, commit: b.commit?.id?.slice(0, 7) })), + recent_commits: (commits || []).map(c => ({ + sha: c.sha?.slice(0, 7), message: c.commit?.message?.split('\n')[0], + author: c.commit?.author?.name, date: c.commit?.author?.date, + })), + releases: (releases || []).map(r => ({ tag: r.tag_name, name: r.name, date: r.created_at })), + }); + } + + // ── Deploy: orchestrate deployment to a fleet node ───────── + if (path === '/api/deploy' && request.method === 'POST') { + const body = await request.json(); + const { repo, node, branch, type } = body; + if (!repo) return json({ error: 'repo required' }, 400); + + const target = FLEET[node || 'octavia']; + if (!target) return json({ error: `unknown node: ${node}. Options: ${Object.keys(FLEET).join(', ')}` }, 400); + if (target.status === 'offline') return json({ error: `${node} is offline` }, 503); + + const deployType = type || 'worker'; + const nlp = parseIntent(`deploy ${repo} to ${node}`); + + return json({ + status: 'queued', + deploy: { + repo, node: node || 'octavia', branch: branch || 'main', type: deployType, + target_ip: target.ip, target_ssh: target.ssh, + services: target.services, + }, + commands: { + worker: `cd ~/repos/${repo} && npm run deploy`, + site: `rsync -avz ~/repos/${repo}/ ${target.ssh}:/var/www/${repo}/`, + docker: `ssh ${target.ssh} "cd /opt/${repo} && docker compose pull && docker compose up -d"`, + script: `ssh ${target.ssh} "cd ~/repos/${repo} && git pull && ./deploy.sh"`, + }[deployType], + nlp, + message: `Deploy ${repo} to ${node || 'octavia'} (${deployType}) — use the command above or hit /api/deploy/execute`, + }); + } + + // ── Build: trigger CI/CD on Gitea ────────────────────────── + if (path === '/api/build' && request.method === 'POST') { + const body = await request.json(); + const { repo, workflow, branch } = body; + if (!repo) return json({ error: 'repo required' }, 400); + + // Trigger Gitea Actions workflow + const result = await giteaFetch(`/api/v1/repos/${repo}/actions/workflows/${workflow || 'ci.yml'}/dispatches`, env, { + method: 'POST', + body: JSON.stringify({ ref: branch || 'main' }), + }); + + return json({ + status: result ? 'triggered' : 'failed', + repo, workflow: workflow || 'ci.yml', branch: branch || 'main', + message: result ? `Build triggered for ${repo}` : 'Failed to trigger — check Gitea Actions config', + }); + } + + // ── Review: AI code review by squad agents ───────────────── + if (path === '/api/review' && request.method === 'POST') { + const body = await request.json(); + const { repo, pr, diff } = body; + if (!repo && !diff) return json({ error: 'repo+pr or diff required' }, 400); + + let diffText = diff; + if (!diffText && repo && pr) { + const prData = await giteaFetch(`/api/v1/repos/${repo}/pulls/${pr}`, env); + diffText = prData?.diff_url ? await (await fetch(prData.diff_url)).text() : null; + } + if (!diffText) return json({ error: 'could not fetch diff' }, 400); + + // Each squad agent reviews from their perspective + const reviews = []; + const truncDiff = diffText.slice(0, 2000); + + for (const agent of SQUAD.slice(0, 4)) { // Top 4 agents for speed + const prompt = `${agent.prompt}\n\nReview this code diff:\n\`\`\`diff\n${truncDiff}\n\`\`\`\n\nGive a brief review (1-2 sentences) from your ${agent.role} perspective. Flag any concerns.`; + const review = await askOllama(prompt, env); + reviews.push({ + agent: agent.name, emoji: agent.emoji, role: agent.role, color: agent.color, + review: review || `${agent.name} could not analyze this diff right now.`, + }); + } + + return json({ repo, pr, reviews, agents: reviews.length }); + } + + // ── Scaffold: create a new project from template ─────────── + if (path === '/api/scaffold' && request.method === 'POST') { + const body = await request.json(); + const { name, template, org, description } = body; + if (!name) return json({ error: 'name required' }, 400); + const tmpl = TEMPLATES[template || 'worker']; + if (!tmpl) return json({ error: `unknown template: ${template}. Options: ${Object.keys(TEMPLATES).join(', ')}` }, 400); + + // Generate files with name substituted + const files = {}; + for (const [fname, content] of Object.entries(tmpl.files)) { + files[fname] = content.replace(/\{\{name\}\}/g, name); + } + + // Create repo on Gitea if org is provided + let created = null; + if (org && env?.ADMIN_TOKEN) { + created = await giteaFetch(`/api/v1/orgs/${org}/repos`, env, { + method: 'POST', + body: JSON.stringify({ + name, description: description || `${name} — ${tmpl.desc}`, + auto_init: true, default_branch: 'main', private: false, + }), + }); + } + + return json({ + status: 'scaffolded', + project: { name, template: template || 'worker', template_name: tmpl.name, description: tmpl.desc }, + files, + gitea_repo: created ? { url: created.html_url, clone: created.clone_url } : null, + next_steps: [ + created ? `git clone ${created.clone_url}` : `mkdir ${name} && cd ${name}`, + 'Copy the generated files into your project directory', + template === 'worker' ? 'npm install && npm run dev' : + template === 'api' ? 'npm install && npm run dev' : + template === 'cli' ? 'npm install && npm run dev' : + template === 'agent' ? 'chmod +x agent.sh && ./agent.sh' : + 'Open index.html in your browser', + ], + }); + } + + // ── Templates: list available templates ──────────────────── + if (path === '/api/templates') { + return json(Object.entries(TEMPLATES).map(([id, t]) => ({ + id, name: t.name, desc: t.desc, files: Object.keys(t.files), + }))); + } + + // ── Fleet: deployment targets ────────────────────────────── + if (path === '/api/fleet') { + return json(Object.entries(FLEET).map(([id, n]) => ({ + id, ip: n.ip, services: n.services, ssh: n.ssh, status: n.status || 'online', + }))); + } + + // ── Squad: list agents ───────────────────────────────────── + if (path === '/api/squad') { + return json(SQUAD.map(a => ({ + name: a.name, username: a.username, role: a.role, emoji: a.emoji, color: a.color, + keywords: a.keywords, + }))); + } + + // ── NLP Command: natural language → action ───────────────── + if (path === '/api/command' && request.method === 'POST') { + const body = await request.json(); + const nlp = parseIntent(body.command || body.message || ''); + + // Map intents to suggested API calls + const suggestions = nlp.intents.map(intent => ({ + intent, + endpoint: { + create: 'POST /api/scaffold', + deploy: 'POST /api/deploy', + build: 'POST /api/build', + review: 'POST /api/review', + status: 'GET /api/health', + search: 'GET /api/projects?q=...', + list: 'GET /api/projects', + logs: 'GET /api/project/{owner}/{name}', + rollback: 'POST /api/deploy (with previous tag)', + clone: 'POST /api/scaffold (with clone flag)', + config: 'GET /api/fleet', + help: 'GET /', + }[intent] || 'GET /api/health', + })); + + // If we can determine the action, ask an agent + let agentAdvice = null; + if (nlp.intents[0] && env?.OLLAMA_URL) { + const bestAgent = SQUAD.find(a => a.keywords.some(kw => body.command?.toLowerCase().includes(kw))) || SQUAD[0]; + agentAdvice = await askOllama( + `${bestAgent.prompt}\n\nA developer said: "${body.command}"\n\nBriefly tell them what to do next (1-2 sentences). Reference specific RoadCode API endpoints or fleet nodes if relevant.`, + env + ); + if (agentAdvice) agentAdvice = { agent: bestAgent.name, emoji: bestAgent.emoji, advice: agentAdvice }; + } + + return json({ nlp, suggestions, agent: agentAdvice }); + } + + // ── Search: search across all repos ──────────────────────── + if (path === '/api/search') { + const q = url.searchParams.get('q'); + if (!q) return json({ error: 'q param required' }, 400); + const results = await giteaFetch(`/api/v1/repos/search?q=${encodeURIComponent(q)}&limit=20`, env); + if (!results) return json({ error: 'gitea unreachable' }, 502); + const data = results.data || results || []; + return json({ + query: q, + results: (Array.isArray(data) ? data : []).map(r => ({ + name: r.full_name, description: r.description, language: r.language, + stars: r.stars_count, updated: r.updated_at, url: r.html_url, + })), + total: results.total_count || data.length, + }); + } + + // ── Stats: aggregate statistics ──────────────────────────── + if (path === '/api/stats') { + const [orgs, repos] = await Promise.all([ + giteaFetch('/api/v1/orgs?limit=50', env), + giteaFetch('/api/v1/repos/search?limit=1', env), + ]); + return json({ + total_repos: repos?.total_count || 0, + total_orgs: Array.isArray(orgs) ? orgs.length : 0, + fleet_nodes: Object.keys(FLEET).length, + fleet_online: Object.values(FLEET).filter(n => n.status !== 'offline').length, + squad_agents: SQUAD.length, + templates: Object.keys(TEMPLATES).length, + version: VERSION, + }); + } + + // ═══ Webhook Handler (from Squad v2) ═══════════════════════ + + if (path === '/webhook' && request.method === 'POST') { const payload = await request.json(); const event = request.headers.get('X-Gitea-Event') || request.headers.get('X-GitHub-Event'); - if (event === 'ping') { - return Response.json({ ok: true, message: 'RoadCode Squad v2 active. Pave Tomorrow.' }); - } + if (event === 'ping') return json({ ok: true, message: `RoadCode v${VERSION} active. Pave Tomorrow.` }); const validEvents = ['issues', 'issue_comment', 'pull_request', 'pull_request_comment']; - if (!validEvents.includes(event)) { - return Response.json({ skipped: true, reason: `unhandled event: ${event}` }); - } + if (!validEvents.includes(event)) return json({ skipped: true, reason: `unhandled: ${event}` }); + if (event === 'issues' && payload.action !== 'opened') return json({ skipped: true }); + if (event === 'issue_comment' && payload.action !== 'created') return json({ skipped: true }); + if (event === 'pull_request' && payload.action !== 'opened') return json({ skipped: true }); - if (event === 'issues' && payload.action !== 'opened') { - return Response.json({ skipped: true, reason: 'issue not opened' }); - } - if (event === 'issue_comment' && payload.action !== 'created') { - return Response.json({ skipped: true, reason: 'comment not created' }); - } - if (event === 'pull_request' && payload.action !== 'opened') { - return Response.json({ skipped: true, reason: 'PR not opened' }); - } - if (event === 'pull_request_comment' && payload.action !== 'created') { - return Response.json({ skipped: true, reason: 'PR comment not created' }); - } - - // Don't respond to agent or admin comments (prevent loops) const author = payload.comment?.user?.login || payload.sender?.login; - const agentUsernames = SQUAD.map(a => a.username); - if (agentUsernames.includes(author) || author === 'blackroad') { - return Response.json({ skipped: true, reason: 'agent/admin comment, skipping loop' }); - } + if (SQUAD.some(a => a.username === author) || author === 'blackroad') return json({ skipped: true, reason: 'agent loop prevention' }); - // Extract context const isPR = event.startsWith('pull_request'); const issue = isPR ? payload.pull_request : payload.issue; const repo = payload.repository?.full_name; const issueNum = issue?.number; const title = issue?.title || ''; - const isComment = event === 'issue_comment' || event === 'pull_request_comment'; + const isComment = event.includes('comment'); const body = isComment ? (payload.comment?.body || '') : (issue?.body || ''); const fullText = `${title} ${body}`; - const context = { - type: isPR ? 'pull request' : (event === 'issues' ? 'issue' : 'comment'), - title, - body: fullText.slice(0, 500), - repo, - isPR, - }; - - const giteaUrl = env.GITEA_URL || 'https://git.blackroad.io'; - const agentTokens = { - alice: env.ALICE_TOKEN, - 'lucidia-agent': env.LUCIDIA_TOKEN, - cecilia: env.CECILIA_TOKEN, - cece: env.CECE_TOKEN, - aria: env.ARIA_TOKEN, - eve: env.EVE_TOKEN, - meridian: env.MERIDIAN_TOKEN, - sentinel: env.SENTINEL_TOKEN, - }; - - // ── Handle slash commands in comments ────────────────────────────── - const commands = parseCommands(body); - let commandsHandled = 0; - - for (const cmd of commands) { - if (cmd.command === 'assign' && cmd.args && env.ADMIN_TOKEN) { - const mentioned = parseMentions(cmd.args); - if (mentioned.length > 0) { - await assignAgent(giteaUrl, repo, issueNum, mentioned[0].username, env.ADMIN_TOKEN); - const agent = mentioned[0]; - const token = agentTokens[agent.username]; - if (token) { - await postComment(giteaUrl, repo, issueNum, - `${agent.emoji} **${agent.name}** *(${agent.role})*\n\nI've been assigned to this. I'll take point from my ${agent.role.toLowerCase()} perspective.\n\n---\n*RoadCode Squad v${VERSION}*`, - token - ); - } - commandsHandled++; - } - } else if (cmd.command === 'priority' && env.ADMIN_TOKEN) { - const level = cmd.args?.toLowerCase(); - if (['high', 'medium', 'low', 'critical'].includes(level)) { - await setPriorityLabel(giteaUrl, repo, issueNum, level, env.ADMIN_TOKEN); - commandsHandled++; - } - } else { - const response = handleSlashCommand(cmd); - if (response && env.ADMIN_TOKEN) { - await postComment(giteaUrl, repo, issueNum, response, env.ADMIN_TOKEN); - commandsHandled++; + // Slash commands + const commands = parseSlashCommands(body); + if (isComment && commands.length > 0) { + for (const c of commands) { + if (c.cmd === 'deploy' && c.args) { + const nlp = parseIntent(`deploy ${c.args} from ${repo}`); + if (env?.ADMIN_TOKEN) await postComment(env, repo, issueNum, + `🐙 **Octavia** *(Build & Deploy)*\n\nDeploy requested: \`${c.args}\`\nParsed intent: ${JSON.stringify(nlp.intents)}\n\nUse \`POST /api/deploy\` with \`{"repo":"${repo}","node":"${nlp.entities.node || 'octavia'}"}\`\n\n---\n*RoadCode v${VERSION}*`, + env.ADMIN_TOKEN); + } else if (c.cmd === 'review') { + if (env?.ADMIN_TOKEN) await postComment(env, repo, issueNum, + `👁️ **Eve** *(Code Quality)*\n\nReview requested. Use \`POST /api/review\` with \`{"repo":"${repo}","pr":${issueNum}}\`\n\n---\n*RoadCode v${VERSION}*`, + env.ADMIN_TOKEN); + } else if (c.cmd === 'status') { + const stats = { fleet: Object.keys(FLEET).length, online: Object.values(FLEET).filter(n => !n.status).length, agents: SQUAD.length }; + if (env?.ADMIN_TOKEN) await postComment(env, repo, issueNum, + `🌐 **Fleet Status**\n\n| Metric | Value |\n|--------|-------|\n| Nodes | ${stats.fleet} (${stats.online} online) |\n| Squad | ${stats.agents} agents |\n| Version | v${VERSION} |\n\n---\n*RoadCode v${VERSION}*`, + env.ADMIN_TOKEN); + } else if (c.cmd === 'squad') { + if (env?.ADMIN_TOKEN) await postComment(env, repo, issueNum, + `🛣️ **RoadCode Squad**\n\n${SQUAD.map(a => `${a.emoji} **${a.name}** (@${a.username}) — ${a.role}`).join('\n')}\n\n---\n*RoadCode v${VERSION}*`, + env.ADMIN_TOKEN); + } else if (c.cmd === 'help') { + if (env?.ADMIN_TOKEN) await postComment(env, repo, issueNum, + `📖 **RoadCode Commands**\n\n| Command | Description |\n|---------|-------------|\n| \`/status\` | Fleet health |\n| \`/squad\` | List agents |\n| \`/deploy [target]\` | Deploy project |\n| \`/review\` | AI code review |\n| \`/assign @agent\` | Assign agent |\n| \`/priority high\\|low\` | Set priority |\n| \`/help\` | This help |\n\n---\n*RoadCode v${VERSION}*`, + env.ADMIN_TOKEN); } } + return json({ ok: true, commands: commands.length }); } - // If only slash commands were in the comment, we're done - if (isComment && commands.length > 0 && commandsHandled > 0) { - return Response.json({ - ok: true, event, repo, issue: issueNum, - commands_handled: commandsHandled, - type: 'slash_command', - }); - } - - // ── Handle @mentions — only mentioned agents respond ────────────── + // @mentions const mentions = parseMentions(body); if (isComment && mentions.length > 0) { - let posted = 0; - const aiEnabled = env.OLLAMA_URL && env.SQUAD_AI !== 'false'; - for (const agent of mentions) { - const token = agentTokens[agent.username]; + const token = env?.[`${agent.name.toUpperCase()}_TOKEN`] || env?.ADMIN_TOKEN; if (!token) continue; - - let response = null; - if (aiEnabled) { - response = await getAgentResponse(agent, context, env.OLLAMA_URL); - } - if (!response) { - response = getFallback(agent, context); - } - - const comment = `${agent.emoji} **${agent.name}** *(${agent.role})*\n\n${response}\n\n---\n*Summoned via @mention — RoadCode Squad v${VERSION}*`; - const ok = await postComment(giteaUrl, repo, issueNum, comment, token); - if (ok) posted++; + const response = await askOllama(`${agent.prompt}\n\nContext: ${fullText.slice(0, 500)}\n\nRespond briefly.`, env); + await postComment(env, repo, issueNum, + `${agent.emoji} **${agent.name}** *(${agent.role})*\n\n${response || 'Standing by.'}\n\n---\n*RoadCode v${VERSION}*`, + token); } - - return Response.json({ - ok: true, event, repo, issue: issueNum, - agents_responded: posted, - responding: mentions.map(a => a.name), - type: 'mention', - }); + return json({ ok: true, mentions: mentions.map(a => a.name) }); } - // ── Auto-label new issues and PRs ───────────────────────────────── - if ((event === 'issues' || event === 'pull_request') && env.ADMIN_TOKEN) { - await autoLabel(giteaUrl, repo, issueNum, fullText, env.ADMIN_TOKEN); + // New issue/PR — auto-label + top agent response + if (!isComment && env?.ADMIN_TOKEN) { + // Auto-label + const labels = []; + const lower = fullText.toLowerCase(); + if (/bug|fix|broken|error/.test(lower)) labels.push('bug'); + if (/feature|add|new|implement/.test(lower)) labels.push('feature'); + if (/security|vuln|ssh|firewall/.test(lower)) labels.push('security'); + if (/deploy|infra|dns|tunnel/.test(lower)) labels.push('infrastructure'); + if (/doc|readme/.test(lower)) labels.push('documentation'); + if (/perf|slow|latency/.test(lower)) labels.push('performance'); + + // Score and respond with top agent + const scored = SQUAD.map(a => ({ ...a, score: scoreRelevance(a, fullText) })).sort((a, b) => b.score - a.score); + const top = scored[0]; + const response = await askOllama(`${top.prompt}\n\nNew ${isPR ? 'pull request' : 'issue'}: "${title}"\n"${body.slice(0, 300)}"\nRepo: ${repo}\n\nRespond briefly.`, env); + + await postComment(env, repo, issueNum, + `${top.emoji} **${top.name}** *(${top.role})*\n\n${response || 'Acknowledged. I\'ll track this.'}\n\n---\n*Auto-assigned by RoadCode v${VERSION}*`, + env.ADMIN_TOKEN); } - // ── Standard response — top agents by keyword relevance ─────────── - // Only auto-respond to new issues and PRs, not every comment - if (isComment) { - return Response.json({ skipped: true, reason: 'comment without mentions or commands' }); - } - - const scored = SQUAD.map(a => ({ ...a, score: scoreRelevance(a, fullText) })); - scored.sort((a, b) => b.score - a.score); - - const responding = []; - const topAgents = scored.slice(0, 3); - for (const a of topAgents) responding.push(a); - if (!responding.find(a => a.name === 'Eve')) responding.push(scored.find(a => a.name === 'Eve')); - if (!responding.find(a => a.name === 'Sentinel')) responding.push(scored.find(a => a.name === 'Sentinel')); - - // Auto-assign the most relevant agent to new issues - if (event === 'issues' && env.ADMIN_TOKEN) { - const topAgent = scored[0]; - assignAgent(giteaUrl, repo, issueNum, topAgent.username, env.ADMIN_TOKEN).catch(() => {}); - } - - let posted = 0; - const aiEnabled = env.OLLAMA_URL && env.SQUAD_AI !== 'false'; - - for (const agent of responding) { - const token = agentTokens[agent.username]; - if (!token) continue; - - let response = null; - if (aiEnabled) { - response = await getAgentResponse(agent, context, env.OLLAMA_URL); - } - if (!response) { - response = getFallback(agent, context); - } - - const comment = `${agent.emoji} **${agent.name}** *(${agent.role})*\n\n${response}\n\n---\n*RoadCode Squad v${VERSION} — BlackRoad OS*`; - const ok = await postComment(giteaUrl, repo, issueNum, comment, token); - if (ok) posted++; - } - - return Response.json({ - ok: true, event, repo, issue: issueNum, - agents_responded: posted, - responding: responding.map(a => a.name), - type: isPR ? 'pull_request' : 'issue', - }); + return json({ ok: true, event, repo, issue: issueNum }); } - return Response.json({ error: 'Not found' }, { status: 404 }); - }, + // ═══ UI ════════════════════════════════════════════════════ + + return new Response(HTML, { headers: { 'Content-Type': 'text/html; charset=utf-8', ...CORS } }); + } }; + +// ═══ HTML UI ══════════════════════════════════════════════════ + +const HTML = ` + + + + +RoadCode — Coding Orchestration Platform + + + + + +
+

RoadCode

+ v${VERSION} +
Loading...
+
+ +
+ + +
+ +
+ + + + + +
+ +
Loading projects...
+ + + +`; diff --git a/workers/roadcode-squad.toml b/workers/roadcode-squad.toml index e6ef36b..271e29d 100644 --- a/workers/roadcode-squad.toml +++ b/workers/roadcode-squad.toml @@ -1,8 +1,12 @@ name = "roadcode-squad" main = "src/worker.js" compatibility_date = "2024-12-01" -workers_dev = true +account_id = "848cf0b18d51e0170e0d1537aec3505a" [vars] GITEA_URL = "https://git.blackroad.io" -OLLAMA_URL = "https://ollama.blackroad.io" +OLLAMA_URL = "https://ollama.gematria.blackroad.io" + +routes = [ + { pattern = "roadcode.blackroad.io/*", zone_name = "blackroad.io" } +]