@sleep2agi/commhub-server 0.5.0 → 0.5.2-preview.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/README.md +127 -131
- package/package.json +1 -1
- package/src/index.ts +35 -0
package/README.md
CHANGED
|
@@ -1,161 +1,157 @@
|
|
|
1
1
|
# @sleep2agi/commhub-server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CommHub: MCP Streamable HTTP + SSE push + REST API for an AI agent network. Single-process Bun server, SQLite-backed, zero config.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**v0.5.0 stable.** The supported path is to install the `anet` CLI (`@sleep2agi/agent-network` 2.0.0) and run `anet hub start`, which wires up port, default account, and config for you.
|
|
6
|
+
|
|
7
|
+
## Quick start (verified)
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
|
-
#
|
|
10
|
+
# Recommended — through the anet CLI
|
|
11
|
+
npm install -g @sleep2agi/agent-network
|
|
12
|
+
anet hub start
|
|
13
|
+
# • http://127.0.0.1:9200 (also bound to LAN)
|
|
14
|
+
# • SQLite at ~/.commhub/commhub.db
|
|
15
|
+
# • Default admin account auto-created: admin / anethub
|
|
16
|
+
# • Reset hint printed in the launch banner
|
|
17
|
+
|
|
18
|
+
# Or directly via bunx (Bun required)
|
|
9
19
|
bunx @sleep2agi/commhub-server
|
|
10
20
|
|
|
11
|
-
#
|
|
21
|
+
# With custom port / auth token
|
|
12
22
|
PORT=9200 COMMHUB_AUTH_TOKEN=your-secret bunx @sleep2agi/commhub-server
|
|
13
23
|
```
|
|
14
24
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
|
27
|
-
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
|
36
|
-
|
|
37
|
-
| `
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
|
49
|
-
|
|
50
|
-
|
|
|
51
|
-
|
|
|
52
|
-
|
|
|
53
|
-
|
|
|
54
|
-
|
|
|
55
|
-
|
|
|
56
|
-
|
|
|
57
|
-
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
|
64
|
-
|
|
65
|
-
|
|
|
66
|
-
| `/
|
|
67
|
-
| `/api/
|
|
68
|
-
| `/api/
|
|
69
|
-
| `/api/
|
|
70
|
-
| `/api/
|
|
71
|
-
| `/api/
|
|
72
|
-
| `/api/
|
|
73
|
-
| `/api/
|
|
74
|
-
|
|
|
75
|
-
| `/api/
|
|
76
|
-
| `/api/
|
|
77
|
-
| `/api/
|
|
78
|
-
| `/api/
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
|
92
|
-
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
任务状态机:
|
|
25
|
+
Once running:
|
|
26
|
+
|
|
27
|
+
| Surface | URL |
|
|
28
|
+
|---|---|
|
|
29
|
+
| Health | `GET /health` |
|
|
30
|
+
| MCP (Streamable HTTP) | `POST /mcp` |
|
|
31
|
+
| SSE per-agent push | `GET /events/:alias` |
|
|
32
|
+
| REST | `/api/*` |
|
|
33
|
+
|
|
34
|
+
## Pairs with
|
|
35
|
+
|
|
36
|
+
| Package | Version |
|
|
37
|
+
|---|---|
|
|
38
|
+
| [`@sleep2agi/agent-network`](https://www.npmjs.com/package/@sleep2agi/agent-network) | 2.0.0 |
|
|
39
|
+
| [`@sleep2agi/agent-network-dashboard`](https://www.npmjs.com/package/@sleep2agi/agent-network-dashboard) | 0.1.0 |
|
|
40
|
+
| [`@sleep2agi/agent-node`](https://www.npmjs.com/package/@sleep2agi/agent-node) | 2.1.1 |
|
|
41
|
+
|
|
42
|
+
## MCP tools (18)
|
|
43
|
+
|
|
44
|
+
### Agent-side
|
|
45
|
+
| Tool | Description |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `report_status` | Heartbeat + status (idle / working / blocked / error / offline) |
|
|
48
|
+
| `report_completion` | Final completion payload |
|
|
49
|
+
| `get_inbox` | Pull pending tasks |
|
|
50
|
+
| `ack_inbox` | Acknowledge receipt |
|
|
51
|
+
|
|
52
|
+
### Hub-side (used by Dashboard, Claude Code, peer agents)
|
|
53
|
+
| Tool | Description |
|
|
54
|
+
|---|---|
|
|
55
|
+
| `send_task` | Dispatch a task (supports `ttl_seconds`) |
|
|
56
|
+
| `send_message` | Send a chat message (no task lifecycle) |
|
|
57
|
+
| `send_reply` | Reply to a task (`replied` / `failed` / `cancelled`, plus `in_reply_to`) |
|
|
58
|
+
| `send_ack` | Acknowledge without inbox |
|
|
59
|
+
| `retry_task` | Retry failed / expired / cancelled tasks |
|
|
60
|
+
| `cancel_task` | Cancel a pending task |
|
|
61
|
+
| `reassign_task` | Move a task to a different agent |
|
|
62
|
+
| `get_task` | Fetch task details (used by peer-coordination polling) |
|
|
63
|
+
| `get_all_status` | Global presence panel |
|
|
64
|
+
| `get_session_status` | Per-session detail |
|
|
65
|
+
| `broadcast` | Group send |
|
|
66
|
+
| `list_tasks` | Task list, filterable by `network_id` |
|
|
67
|
+
| `get_completions` | Completion history |
|
|
68
|
+
|
|
69
|
+
## REST API
|
|
70
|
+
|
|
71
|
+
The server exposes ~33 endpoints across health, auth, networks, and observability surfaces. The endpoints in use today by the verified flow are:
|
|
72
|
+
|
|
73
|
+
| Method | Endpoint | Notes |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| GET | `/health` | No auth |
|
|
76
|
+
| POST | `/mcp` | MCP entry |
|
|
77
|
+
| POST | `/api/auth/register` | Bootstrap admin |
|
|
78
|
+
| POST | `/api/auth/login` | Returns user token |
|
|
79
|
+
| GET | `/api/auth/me` | Current user |
|
|
80
|
+
| PUT | `/api/auth/me` | Edit profile |
|
|
81
|
+
| POST | `/api/auth/password` | Change password |
|
|
82
|
+
| GET / POST / DELETE | `/api/auth/tokens[…]` | Manage API tokens |
|
|
83
|
+
| GET | `/api/status` | Sessions snapshot |
|
|
84
|
+
| GET | `/api/tasks` | Task list (Dashboard) |
|
|
85
|
+
| GET | `/api/messages` | Message list (Dashboard) |
|
|
86
|
+
| GET | `/api/nodes` | Node directory |
|
|
87
|
+
| GET | `/api/stats` | Aggregate stats |
|
|
88
|
+
| GET | `/api/audit-log` | Audit trail |
|
|
89
|
+
|
|
90
|
+
Network-management endpoints (`/api/networks…`) and `/api/license[…]` are present but are **not** part of the v2.0.0 verified flow — see *Not verified* below.
|
|
91
|
+
|
|
92
|
+
Auth: `Authorization: Bearer <token>` header, or `?token=<token>` query.
|
|
93
|
+
|
|
94
|
+
## SQLite schema (13 tables)
|
|
95
|
+
|
|
96
|
+
Auto-created on first run.
|
|
97
|
+
|
|
98
|
+
| Table | Purpose |
|
|
99
|
+
|---|---|
|
|
100
|
+
| `sessions` | Live agent sessions |
|
|
101
|
+
| `inbox` | Pending messages and tasks |
|
|
102
|
+
| `tasks` | Task state machine |
|
|
103
|
+
| `nodes` | Persistent node identity |
|
|
104
|
+
| `completions` | Final completion records |
|
|
105
|
+
| `task_events` | Per-state audit |
|
|
106
|
+
| `users` | Accounts |
|
|
107
|
+
| `networks` | Workspaces |
|
|
108
|
+
| `api_tokens` | `utok_` / `ntok_` / `atok_` tokens |
|
|
109
|
+
| `audit_log` | Operation audit |
|
|
110
|
+
| `licenses` | License placeholder |
|
|
111
|
+
| `network_members` | Workspace membership |
|
|
112
|
+
| `network_invites` | Invite codes |
|
|
113
|
+
|
|
114
|
+
Task state machine:
|
|
115
|
+
|
|
108
116
|
```
|
|
109
117
|
created → delivered → acked → running → replied
|
|
110
118
|
→ failed → retry → delivered
|
|
111
119
|
→ cancelled
|
|
112
|
-
delivered → expired (5min
|
|
113
|
-
delivered/acked/running → reassign → delivered (
|
|
120
|
+
delivered → expired (5min watchdog)
|
|
121
|
+
delivered/acked/running → reassign → delivered (new agent)
|
|
114
122
|
```
|
|
115
123
|
|
|
116
|
-
##
|
|
124
|
+
## PostgreSQL (experimental)
|
|
117
125
|
|
|
118
|
-
|
|
126
|
+
Set `DATABASE_URL` to switch to PostgreSQL — the SQL layer auto-translates SQLite-isms (datetime, parameter placeholders) so application code is unchanged. Requires `bun add pg`. **Not in the v2.0.0 verified path.**
|
|
119
127
|
|
|
120
128
|
```bash
|
|
121
|
-
|
|
122
|
-
bunx @sleep2agi/commhub-server
|
|
123
|
-
|
|
124
|
-
# PostgreSQL
|
|
125
|
-
DATABASE_URL=postgres://user:pass@localhost:5432/commhub bunx @sleep2agi/commhub-server
|
|
129
|
+
DATABASE_URL=postgres://user:pass@host:5432/commhub bunx @sleep2agi/commhub-server
|
|
126
130
|
```
|
|
127
131
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
所有 SQL 自动翻译(datetime→NOW, 参数占位符→$N 等),代码零修改。
|
|
131
|
-
|
|
132
|
-
## 环境变量
|
|
132
|
+
## Environment
|
|
133
133
|
|
|
134
|
-
|
|
|
135
|
-
|
|
136
|
-
| `PORT` | 9200 |
|
|
137
|
-
| `HOST` | 0.0.0.0 |
|
|
138
|
-
| `COMMHUB_AUTH_TOKEN` | (
|
|
139
|
-
| `COMMHUB_DB` |
|
|
140
|
-
| `DATABASE_URL` | (
|
|
134
|
+
| Variable | Default | Notes |
|
|
135
|
+
|---|---|---|
|
|
136
|
+
| `PORT` | `9200` | listen port |
|
|
137
|
+
| `HOST` | `0.0.0.0` | listen address |
|
|
138
|
+
| `COMMHUB_AUTH_TOKEN` | (none) | Bearer token gate (legacy) |
|
|
139
|
+
| `COMMHUB_DB` | `~/.commhub/commhub.db` | SQLite path |
|
|
140
|
+
| `DATABASE_URL` | (none) | switches to PostgreSQL when set (unverified) |
|
|
141
141
|
|
|
142
|
-
##
|
|
142
|
+
## Auth modes
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
2. **全局 token** (传统): `COMMHUB_AUTH_TOKEN` 环境变量
|
|
144
|
+
1. **V3 user system (default)** — `POST /api/auth/register` and `/api/auth/login` issue `utok_…` tokens; nodes get `ntok_…`.
|
|
145
|
+
2. **Legacy global token** — set `COMMHUB_AUTH_TOKEN` and pass it as Bearer / query.
|
|
147
146
|
|
|
148
|
-
|
|
147
|
+
`/health` is always public.
|
|
149
148
|
|
|
150
|
-
##
|
|
149
|
+
## Not verified
|
|
151
150
|
|
|
152
|
-
-
|
|
153
|
-
-
|
|
154
|
-
-
|
|
155
|
-
-
|
|
156
|
-
- **审计日志**: 所有操作记录
|
|
157
|
-
- **限流**: 注册 30/min, 登录 10/min (per IP)
|
|
158
|
-
- `/health` 不需要 auth
|
|
151
|
+
- `/api/networks*` (multi-network create / invite / member management) — code present, not E2E regressed.
|
|
152
|
+
- `/api/license*` — placeholder for a future paid tier.
|
|
153
|
+
- PostgreSQL backend — translation layer exists, no E2E run.
|
|
154
|
+
- Telegram / WeChat / Feishu channel endpoints — out of scope for v2.0.0 verification.
|
|
159
155
|
|
|
160
156
|
## License
|
|
161
157
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sleep2agi/commhub-server",
|
|
3
|
-
"version": "0.5.0",
|
|
3
|
+
"version": "0.5.2-preview.0",
|
|
4
4
|
"description": "CommHub Server \u2014 AI Agent communication hub with MCP protocol, multi-network isolation, user auth, and 18 MCP tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,24 @@ const SERVER_VERSION = (() => {
|
|
|
18
18
|
} catch { return "?"; }
|
|
19
19
|
})();
|
|
20
20
|
|
|
21
|
+
// In-memory log ring buffer — last N lines streamed via /api/server-logs.
|
|
22
|
+
// Wraps console.log/info/warn/error so EVERY existing log call lands here
|
|
23
|
+
// without source changes. Dashboard tails this buffer for "hub server log
|
|
24
|
+
// view" feature.
|
|
25
|
+
const LOG_RING_CAP = Number(process.env.COMMHUB_LOG_RING || 500);
|
|
26
|
+
type LogEntry = { ts: string; level: "log" | "info" | "warn" | "error"; line: string };
|
|
27
|
+
const logRing: LogEntry[] = [];
|
|
28
|
+
const _origConsole = { log: console.log.bind(console), info: console.info.bind(console), warn: console.warn.bind(console), error: console.error.bind(console) };
|
|
29
|
+
function pushLog(level: LogEntry["level"], args: any[]) {
|
|
30
|
+
const line = args.map(a => typeof a === "string" ? a : (() => { try { return JSON.stringify(a); } catch { return String(a); } })()).join(" ");
|
|
31
|
+
logRing.push({ ts: new Date().toISOString(), level, line: line.slice(0, 4000) });
|
|
32
|
+
if (logRing.length > LOG_RING_CAP) logRing.splice(0, logRing.length - LOG_RING_CAP);
|
|
33
|
+
}
|
|
34
|
+
console.log = (...args: any[]) => { pushLog("log", args); _origConsole.log(...args); };
|
|
35
|
+
console.info = (...args: any[]) => { pushLog("info", args); _origConsole.info(...args); };
|
|
36
|
+
console.warn = (...args: any[]) => { pushLog("warn", args); _origConsole.warn(...args); };
|
|
37
|
+
console.error = (...args: any[]) => { pushLog("error", args); _origConsole.error(...args); };
|
|
38
|
+
|
|
21
39
|
// ── Rate limiter (in-memory, per IP) ──
|
|
22
40
|
const rateLimits = new Map<string, { count: number; resetAt: number }>();
|
|
23
41
|
function checkRateLimit(ip: string, maxPerMinute = 60): boolean {
|
|
@@ -838,6 +856,23 @@ Bun.serve({
|
|
|
838
856
|
}));
|
|
839
857
|
}
|
|
840
858
|
|
|
859
|
+
// ── REST: server log tail (in-memory ring buffer, last LOG_RING_CAP lines) ──
|
|
860
|
+
// Admin-only because logs may include user names + task content.
|
|
861
|
+
if (url.pathname === "/api/server-logs") {
|
|
862
|
+
const token = req.headers.get("Authorization")?.replace("Bearer ", "") || url.searchParams.get("token");
|
|
863
|
+
if (!token) return withCors(req, Response.json({ ok: false, error: "auth required" }, { status: 401 }));
|
|
864
|
+
const resolved = resolveToken(token);
|
|
865
|
+
if (!resolved) return withCors(req, Response.json({ ok: false, error: "invalid token" }, { status: 401 }));
|
|
866
|
+
if (resolved.user.role !== "admin") return withCors(req, Response.json({ ok: false, error: "admin only" }, { status: 403 }));
|
|
867
|
+
const limit = Math.min(Number(url.searchParams.get("limit")) || 200, LOG_RING_CAP);
|
|
868
|
+
const since = url.searchParams.get("since"); // ISO timestamp; only return logs newer
|
|
869
|
+
let entries = logRing.slice(-limit);
|
|
870
|
+
if (since) entries = entries.filter(e => e.ts > since);
|
|
871
|
+
// Newest first
|
|
872
|
+
entries = entries.slice().reverse();
|
|
873
|
+
return withCors(req, Response.json({ ok: true, logs: entries, capacity: LOG_RING_CAP }));
|
|
874
|
+
}
|
|
875
|
+
|
|
841
876
|
// ── REST: audit log (V3) ──
|
|
842
877
|
if (url.pathname === "/api/audit-log") {
|
|
843
878
|
const token = req.headers.get("Authorization")?.replace("Bearer ", "") || url.searchParams.get("token");
|