@sleep2agi/commhub-server 0.9.0-preview.4 → 0.9.0-preview.41
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 +125 -7
- package/bin/commhub.ts +10 -2
- package/package.json +5 -5
- package/src/ack-create-request-transport.test.ts +159 -0
- package/src/ack-create-request.test.ts +331 -0
- package/src/admin-networks-http.test.ts +84 -0
- package/src/alias-filter.test.ts +72 -0
- package/src/alias-filter.ts +32 -0
- package/src/api-host-supervisors-fallback.test.ts +267 -0
- package/src/api-nodes-shape.test.ts +107 -13
- package/src/auth.ts +93 -14
- package/src/avatar-validate.test.ts +163 -0
- package/src/avatar-validate.ts +103 -0
- package/src/boot-side-effect.test.ts +124 -0
- package/src/config-apply-sec1.test.ts +107 -0
- package/src/config-apply-validate.test.ts +92 -0
- package/src/config-apply-validate.ts +81 -3
- package/src/create-node-tool-schema.test.ts +39 -0
- package/src/create-node-validate.test.ts +80 -1
- package/src/create-node-validate.ts +49 -5
- package/src/create-node.ts +51 -1
- package/src/dashboard-slash-routing-http.test.ts +103 -0
- package/src/db-adapter-guard.test.ts +85 -0
- package/src/db-adapter.ts +64 -23
- package/src/db-timestamp.test.ts +38 -0
- package/src/db-timestamp.ts +15 -0
- package/src/db.ts +654 -33
- package/src/delivered-without-push.test.ts +140 -0
- package/src/desktop-message-seam.test.ts +150 -0
- package/src/desktop-message.test.ts +368 -0
- package/src/ext-token-shared.test.ts +60 -0
- package/src/ext-validation-regression.test.ts +131 -0
- package/src/external-schedule-edits-http.test.ts +316 -0
- package/src/external-schedule-edits.ts +314 -0
- package/src/file-download-authz-dev-open.test.ts +135 -0
- package/src/file-download-authz.test.ts +403 -0
- package/src/file-download-date-bucket.test.ts +436 -0
- package/src/file-network-scope-dev-open.test.ts +97 -0
- package/src/file-network-scope.test.ts +731 -0
- package/src/health-redaction.test.ts +637 -0
- package/src/inbox-count-sse.test.ts +310 -0
- package/src/inbox-count.ts +29 -0
- package/src/index.ts +15 -2187
- package/src/lifecycle-guard.ts +50 -0
- package/src/list-host-supervisors.test.ts +314 -0
- package/src/mcp-write-network-resolution.test.ts +429 -0
- package/src/network-name-http.test.ts +71 -0
- package/src/network-scope.ts +101 -0
- package/src/node-attrs-validate.ts +88 -0
- package/src/node-attrs.test.ts +213 -0
- package/src/node-id-alias.test.ts +147 -0
- package/src/node-lifecycle-controllable.test.ts +64 -0
- package/src/node-lifecycle-controllable.ts +51 -0
- package/src/observer-avatar-http.test.ts +424 -0
- package/src/observer-push.test.ts +252 -0
- package/src/package-version-consistency.test.ts +25 -0
- package/src/peer-reply-atomic.test.ts +455 -0
- package/src/peer-reply-capability.test.ts +70 -0
- package/src/probe-validate.test.ts +221 -0
- package/src/probe-validate.ts +104 -0
- package/src/probe.test.ts +80 -0
- package/src/probe.ts +200 -0
- package/src/push.ts +163 -7
- package/src/redact-tokens.test.ts +65 -0
- package/src/redact-tokens.ts +40 -0
- package/src/reply-target-derivation.test.ts +96 -0
- package/src/require-explicit-test-db.ts +9 -0
- package/src/resolve-port.test.ts +57 -0
- package/src/resolve-port.ts +44 -0
- package/src/response-charset.test.ts +130 -0
- package/src/rest-explicit-columns-http.test.ts +212 -0
- package/src/rest-identity.test.ts +129 -0
- package/src/rest-identity.ts +100 -0
- package/src/rest-projections.ts +67 -0
- package/src/rest-write-network-resolution.test.ts +133 -0
- package/src/runtime-label.test.ts +22 -0
- package/src/runtime-label.ts +21 -0
- package/src/scheduled-run-terminal.test.ts +268 -0
- package/src/scheduled-tasks-http.test.ts +479 -0
- package/src/scheduled-tasks.ts +549 -0
- package/src/send-reply-agent-warning.test.ts +202 -0
- package/src/send-reply-alias-optional.test.ts +131 -0
- package/src/send-reply-attachments.test.ts +437 -0
- package/src/server.ts +3664 -0
- package/src/shared/external-schedule-contract.ts +60 -0
- package/src/shared/probe-host-allowlist-drift.test.ts +54 -0
- package/src/shared/probe-host-allowlist.ts +128 -0
- package/src/side-thread-command-transport.test.ts +302 -0
- package/src/side-thread-command-transport.ts +318 -0
- package/src/side-thread-http-integration.test.ts +229 -0
- package/src/side-thread-http.ts +504 -0
- package/src/side-thread-production.test.ts +72 -0
- package/src/side-thread-production.ts +118 -0
- package/src/side-thread.test.ts +1067 -0
- package/src/side-thread.ts +2495 -0
- package/src/skillhub-http.test.ts +88 -0
- package/src/start-node.test.ts +109 -0
- package/src/stop-delete-node.test.ts +952 -0
- package/src/task-auth-origin-http.test.ts +87 -0
- package/src/task-auth-origin.test.ts +24 -0
- package/src/task-auth-origin.ts +7 -0
- package/src/task-consumption.test.ts +471 -0
- package/src/task-diagnostic-http.test.ts +145 -0
- package/src/task-diagnostic.test.ts +115 -0
- package/src/task-diagnostic.ts +118 -0
- package/src/task-idempotency-mcp.test.ts +142 -0
- package/src/task-idempotency.test.ts +60 -0
- package/src/task-idempotency.ts +75 -0
- package/src/task-lifecycle-watcher.test.ts +200 -0
- package/src/task-lifecycle-watcher.ts +49 -0
- package/src/task-network-resolution-http.test.ts +137 -0
- package/src/tasks-pagination-http.test.ts +137 -0
- package/src/tools.ts +2994 -191
- package/src/update-provider.test.ts +234 -0
- package/src/upload-early-reject-guard.test.ts +155 -0
- package/src/uploads-http.test.ts +92 -14
- package/src/uploads.test.ts +66 -0
- package/src/uploads.ts +130 -5
- package/src/user-inbox-read-http.test.ts +220 -0
- package/src/user-inbox-schema.test.ts +53 -0
- package/src/vault.test.ts +200 -0
- package/src/vault.ts +156 -0
package/README.md
CHANGED
|
@@ -7,10 +7,27 @@
|
|
|
7
7
|
|
|
8
8
|
CommHub: MCP Streamable HTTP + SSE push + REST API for an AI agent network. Single-process Bun server, SQLite-backed, zero config when launched through `anet`.
|
|
9
9
|
|
|
10
|
-
The supported path is to install the `anet` CLI (`@sleep2agi/agent-network
|
|
10
|
+
The supported path is to install the `anet` CLI (`@sleep2agi/agent-network`) and run `anet hub start`, which wires up the port, default admin account, recovery admin `utok_`, and local config for you.
|
|
11
11
|
|
|
12
12
|
## Quick start (verified)
|
|
13
13
|
|
|
14
|
+
> ⚠️ **Do not run `bunx @sleep2agi/commhub-server` without a version**
|
|
15
|
+
> Without a version this resolves to the npm `latest` dist-tag, and **`latest` is currently `0.8.8` (published 2026-06-24)**.
|
|
16
|
+
>
|
|
17
|
+
> On `0.8.8` an anonymous `GET /health` **returns the `{networkId}:{alias}` of every live SSE connection** — no token required.
|
|
18
|
+
> The redaction fix is [`7bacb729`](https://github.com/sleep2agi/agent-network/commit/7bacb729) (`security(#473)`, **2026-07-29**),
|
|
19
|
+
> **35 days after** `0.8.8` shipped, so `0.8.8` does not contain it.
|
|
20
|
+
>
|
|
21
|
+
> **Pin a version, or use the preview channel:**
|
|
22
|
+
>
|
|
23
|
+
> ```bash
|
|
24
|
+
> bunx --bun @sleep2agi/commhub-server@preview # contains the redaction fix
|
|
25
|
+
> ```
|
|
26
|
+
>
|
|
27
|
+
> Or use the supported path, `anet hub start`, which pulls the version in `PINNED_SERVER_VERSION` rather than `latest`.
|
|
28
|
+
>
|
|
29
|
+
> Self-check: `curl -sS http://<host>:9200/health | jq 'has("sse_sessions")'` — `true` means you are on an affected build.
|
|
30
|
+
|
|
14
31
|
```bash
|
|
15
32
|
# Recommended — through the anet CLI
|
|
16
33
|
npm install -g @sleep2agi/agent-network
|
|
@@ -38,13 +55,16 @@ Once running:
|
|
|
38
55
|
|
|
39
56
|
## Pairs with
|
|
40
57
|
|
|
41
|
-
| Package |
|
|
58
|
+
| Package | |
|
|
42
59
|
|---|---|
|
|
43
|
-
| [`@sleep2agi/agent-network`](https://www.npmjs.com/package/@sleep2agi/agent-network) |
|
|
44
|
-
| [`@sleep2agi/agent-network-dashboard`](https://www.npmjs.com/package/@sleep2agi/agent-network-dashboard) |
|
|
45
|
-
| [`@sleep2agi/agent-node`](https://www.npmjs.com/package/@sleep2agi/agent-node) |
|
|
60
|
+
| [`@sleep2agi/agent-network`](https://www.npmjs.com/package/@sleep2agi/agent-network) | CLI |
|
|
61
|
+
| [`@sleep2agi/agent-network-dashboard`](https://www.npmjs.com/package/@sleep2agi/agent-network-dashboard) | Web UI |
|
|
62
|
+
| [`@sleep2agi/agent-node`](https://www.npmjs.com/package/@sleep2agi/agent-node) | Agent runtime host |
|
|
63
|
+
|
|
64
|
+
Install the matching versions with `npm install -g <pkg>` — the `latest` dist-tag on npm is
|
|
65
|
+
authoritative. Pinning versions here goes stale on every release and nobody comes back to fix it.
|
|
46
66
|
|
|
47
|
-
## MCP tools
|
|
67
|
+
## MCP tools
|
|
48
68
|
|
|
49
69
|
### Agent-side
|
|
50
70
|
| Tool | Description |
|
|
@@ -71,6 +91,8 @@ Once running:
|
|
|
71
91
|
| `list_tasks` | Task list, filterable by `network_id` |
|
|
72
92
|
| `get_completions` | Completion history |
|
|
73
93
|
|
|
94
|
+
> The table above lists the 17 **collaboration-core** tools. Node lifecycle / provider ops tools ship on the same MCP surface — the authoritative full list is [docs-site/docs/api/mcp-tools.md](../docs-site/docs/api/mcp-tools.md). Don't read the count above as "17 tools total".
|
|
95
|
+
|
|
74
96
|
## REST API
|
|
75
97
|
|
|
76
98
|
The server exposes ~33 endpoints across health, auth, networks, and observability surfaces. The endpoints in use today by the verified flow are:
|
|
@@ -96,7 +118,7 @@ Network-management endpoints (`/api/networks…`) are present and used by the cu
|
|
|
96
118
|
|
|
97
119
|
Auth: `Authorization: Bearer <token>` header, or `?token=<token>` query.
|
|
98
120
|
|
|
99
|
-
## SQLite schema
|
|
121
|
+
## SQLite schema
|
|
100
122
|
|
|
101
123
|
Auto-created on first run.
|
|
102
124
|
|
|
@@ -146,6 +168,102 @@ DATABASE_URL=postgres://user:pass@host:5432/commhub bunx @sleep2agi/commhub-serv
|
|
|
146
168
|
| `COMMHUB_DB` | `~/.commhub/commhub.db` | SQLite path |
|
|
147
169
|
| `DATABASE_URL` | (none) | switches to PostgreSQL when set (unverified) |
|
|
148
170
|
|
|
171
|
+
## Running the unit tests
|
|
172
|
+
|
|
173
|
+
### 先说该走哪条路:Docker
|
|
174
|
+
|
|
175
|
+
仓库规约是测试在 Docker 里跑(`CLAUDE.md`「所有测试在 Docker 里跑:不碰本地
|
|
176
|
+
环境,不改生产」)。`server/src` 的测试在 `tests/` 下已经有 **24 个**按主题分片的
|
|
177
|
+
套件覆盖(实测:遍历 main 上 204 个套件目录,run.sh 里跑 `server/src/*.test.ts`
|
|
178
|
+
的有 24 个),每个都在容器内自带 `COMMHUB_DB`。跑其中任意一个都是这个形状:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# 从仓根跑
|
|
182
|
+
docker build --build-arg SOURCE_COMMIT="$(git rev-parse HEAD)" \
|
|
183
|
+
-t anet-test624 -f tests/test624-task-cursor-pagination/Dockerfile .
|
|
184
|
+
docker run --rm anet-test624
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
(实跑过:退出码 0,`6 pass / 0 fail`,`RESULT: PASS`。换成 `tests/` 下别的
|
|
188
|
+
`test*-*/Dockerfile` 同理,但 **build-arg 的名字要照抄那个 Dockerfile** ——
|
|
189
|
+
main 上 66 处写的是 `SOURCE_COMMIT`,另有若干写成 `TEST<编号>_SOURCE_COMMIT`
|
|
190
|
+
(如 `TEST686_SOURCE_COMMIT`、`TEST765_SOURCE_COMMIT`)。传错名字不会报错,
|
|
191
|
+
只会让套件收到 `unknown` 然后按它自己的规则红或绿 —— 这不是你想要的那种绿。)
|
|
192
|
+
|
|
193
|
+
> ⚠️ **目前没有一个套件跑「`server/src` 下的全部」单测。** 上面那些是按主题
|
|
194
|
+
> 分片的;聚合门在 #798 里,尚未合入 main。所以「所有分片套件都绿」**不等于**
|
|
195
|
+
> 69 个单测文件都跑过 —— 在 #798 合入之前,不要把前者当成后者。
|
|
196
|
+
|
|
197
|
+
### 直接在宿主上跑时的两条契约
|
|
198
|
+
|
|
199
|
+
下面这两条是上面那些套件在容器内**已经替你满足**的东西。你只有在本地迭代
|
|
200
|
+
单个文件时才需要自己处理它们 —— 而那种跑法不构成任何门禁证据。
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
1. **`COMMHUB_DB` must be set.** It is not optional — `~/.commhub/commhub.db` is a
|
|
204
|
+
*live* database on any machine that has ever run a hub. This one is enforced:
|
|
205
|
+
with `NODE_ENV=test` (which `bun test` sets) and no `COMMHUB_DB`, the adapter
|
|
206
|
+
refuses with `REFUSING to open the default SQLite database`.
|
|
207
|
+
|
|
208
|
+
2. **Each test *file* needs its own database.** This is *not* enforced — a single
|
|
209
|
+
shared `COMMHUB_DB` passes the guard above and then fails a handful of tests
|
|
210
|
+
for reasons that look like product bugs but are not. `scripts/qa.sh` encodes
|
|
211
|
+
the real contract:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
# 从仓根跑(见下),每个文件一个全新的库
|
|
215
|
+
f=server/src/auth-validate.test.ts
|
|
216
|
+
db=/tmp/anet-$(basename "$f" .test.ts).db
|
|
217
|
+
rm -f "$db" # 复用上一次的库同样会破坏隔离
|
|
218
|
+
COMMHUB_DB="$db" bun test "$f"
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
整套跑法:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
rc=0
|
|
225
|
+
# 用 find 而不是 `server/src/*.test.ts` —— glob 不进子目录,会漏掉
|
|
226
|
+
# server/src/shared/ 下的 3 个(实测:深度 1 是 66 个,递归是 69 个)。
|
|
227
|
+
# scripts/qa.sh 与 test798 用的就是 find,这里保持一致。
|
|
228
|
+
while IFS= read -r f; do
|
|
229
|
+
# 用完整路径派生库名,不用 basename:加入子目录之后,
|
|
230
|
+
# 两个不同目录下的同名文件会派生出同一个库(原来只扫单层时不可能)。
|
|
231
|
+
db=/tmp/anet-$(printf '%s' "${f%.test.ts}" | tr '/.' '--').db
|
|
232
|
+
rm -f "$db" "$db-wal" "$db-shm"
|
|
233
|
+
COMMHUB_DB="$db" bun test "$f" || { echo "FAILED: $f"; rc=1; }
|
|
234
|
+
done < <(find server/src -type f -name '*.test.ts' | sort)
|
|
235
|
+
[ "$rc" -eq 0 ]
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
⚠️ 最后那行 `[ "$rc" -eq 0 ]` 不是装饰,是这段的**门**。写成
|
|
239
|
+
`bun test "$f" || echo "FAILED: $f"` 会让循环打印一行然后继续,
|
|
240
|
+
整段的退出码恒为 0 —— **测试全红,脚本报绿**。用 `[ ... ]` 而不是
|
|
241
|
+
`exit "$rc"`,是为了这段直接粘进终端时不会把你的 shell 关掉;
|
|
242
|
+
存成脚本跑时,它是最后一条命令,脚本退出码就等于它。
|
|
243
|
+
|
|
244
|
+
⚠️ 别把 `$name` 之类的占位符原样抄进命令行 —— 未定义的变量会展开成空串,
|
|
245
|
+
于是每个文件都写进**同一个** `/tmp/…-.db`,正好是这一节要避免的事。
|
|
246
|
+
|
|
247
|
+
Symptom if you ignore it: `bun test src/` under one database turns
|
|
248
|
+
`admin-networks-http` and `scheduled-tasks-http` red. Both are **global-count
|
|
249
|
+
assertions that are correct under the contract**:
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
// admin-networks-http.test.ts — the admin sees EXACTLY this file's two networks
|
|
253
|
+
expect(new Set(rows.map((row) => row.network_id)))
|
|
254
|
+
.toEqual(new Set([adminNetworkId, memberNetworkId]));
|
|
255
|
+
|
|
256
|
+
// scheduled-tasks-http.test.ts — EXACTLY one due occurrence was processed
|
|
257
|
+
expect(runDueScheduledTasks().processed).toBe(1);
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Share a database and other files' networks and due schedules join the count.
|
|
261
|
+
Nothing is broken; the run violated the contract.
|
|
262
|
+
|
|
263
|
+
`scripts/qa.sh` 的 L0 目前只点名跑 5 个 server 测试(白名单),不是全量;
|
|
264
|
+
把 `server/src` 全域逐文件跑起来的那道 CI 门还在评审中(`#798`),
|
|
265
|
+
未进 `main` —— 所以本节描述的是**契约**,不是「已经有东西在替你执行它」。
|
|
266
|
+
|
|
149
267
|
## Auth modes
|
|
150
268
|
|
|
151
269
|
1. **V3 user system (default)** — `POST /api/auth/register` and `/api/auth/login` issue `utok_…` tokens; nodes get `ntok_…`.
|
package/bin/commhub.ts
CHANGED
|
@@ -54,5 +54,13 @@ Examples:
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
//
|
|
58
|
-
|
|
57
|
+
// Opt into the default production DB path before loading the server graph.
|
|
58
|
+
// Ordinary imports / bun -e probes do not receive this capability.
|
|
59
|
+
process.env.COMMHUB_SERVER = "1";
|
|
60
|
+
|
|
61
|
+
// Load the server module (side-effect-free since the #438 corrective:
|
|
62
|
+
// importing binds nothing) and start the hub EXPLICITLY. Never rely on
|
|
63
|
+
// import side effects or import.meta.main here — this bin is a dynamic
|
|
64
|
+
// importer, where import.meta.main is false inside the module.
|
|
65
|
+
const { startHub } = await import("../src/server.js");
|
|
66
|
+
startHub();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sleep2agi/commhub-server",
|
|
3
|
-
"version": "0.9.0-preview.
|
|
4
|
-
"description": "CommHub Server
|
|
3
|
+
"version": "0.9.0-preview.41",
|
|
4
|
+
"description": "CommHub Server — AI Agent communication hub with MCP protocol, multi-network isolation, user auth, and MCP tools (17 collaboration-core + node/provider ops tools; authoritative list: docs-site/docs/api/mcp-tools.md).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"bin": {
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
"bin"
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
|
-
"start": "bun run src/index.ts",
|
|
16
|
-
"dev": "bun --watch run src/index.ts",
|
|
17
|
-
"test": "
|
|
15
|
+
"start": "COMMHUB_SERVER=1 bun run src/index.ts",
|
|
16
|
+
"dev": "COMMHUB_SERVER=1 bun --watch run src/index.ts",
|
|
17
|
+
"test": "bun run scripts/test-aggregate.ts"
|
|
18
18
|
},
|
|
19
19
|
"keywords": [
|
|
20
20
|
"commhub",
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { afterAll, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
|
+
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { db } from "./db.js";
|
|
6
|
+
import { registerTools } from "./tools.js";
|
|
7
|
+
|
|
8
|
+
const NET = "net_ack_transport";
|
|
9
|
+
const DAEMON_NODE_ID = "node_ack_transport_daemon";
|
|
10
|
+
const DAEMON_ALIAS = "ack-transport-daemon";
|
|
11
|
+
const DAEMON_USER = "u_ack_transport_owner";
|
|
12
|
+
const DAEMON_TOKEN_ID = "tok_ack_transport_daemon";
|
|
13
|
+
|
|
14
|
+
function cleanup() {
|
|
15
|
+
try { db.run("DELETE FROM audit_log WHERE network_id = ?1", [NET]); } catch {}
|
|
16
|
+
try { db.run("DELETE FROM node_create_requests WHERE network_id = ?1", [NET]); } catch {}
|
|
17
|
+
try { db.run("DELETE FROM nodes WHERE network_id = ?1", [NET]); } catch {}
|
|
18
|
+
try { db.run("DELETE FROM api_tokens WHERE network_id = ?1", [NET]); } catch {}
|
|
19
|
+
try { db.run("DELETE FROM network_members WHERE network_id = ?1", [NET]); } catch {}
|
|
20
|
+
try { db.run("DELETE FROM networks WHERE network_id = ?1", [NET]); } catch {}
|
|
21
|
+
try { db.run("DELETE FROM users WHERE user_id = ?1", [DAEMON_USER]); } catch {}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function seedWorld() {
|
|
25
|
+
db.run(
|
|
26
|
+
`INSERT INTO users (user_id, username, password_hash, role, created_at)
|
|
27
|
+
VALUES (?1, ?2, 'x', 'user', datetime('now'))`,
|
|
28
|
+
[DAEMON_USER, DAEMON_USER],
|
|
29
|
+
);
|
|
30
|
+
db.run(
|
|
31
|
+
`INSERT INTO networks (network_id, network_name, owner_id, created_at)
|
|
32
|
+
VALUES (?1, ?2, ?3, datetime('now'))`,
|
|
33
|
+
[NET, NET, DAEMON_USER],
|
|
34
|
+
);
|
|
35
|
+
db.run(
|
|
36
|
+
`INSERT INTO network_members (user_id, network_id, role, joined_at)
|
|
37
|
+
VALUES (?1, ?2, 'owner', datetime('now'))`,
|
|
38
|
+
[DAEMON_USER, NET],
|
|
39
|
+
);
|
|
40
|
+
db.run(
|
|
41
|
+
`INSERT INTO nodes (node_id, node_name, alias, network_id, created_at, updated_at)
|
|
42
|
+
VALUES (?1, ?2, ?3, ?4, datetime('now'), datetime('now'))`,
|
|
43
|
+
[DAEMON_NODE_ID, DAEMON_ALIAS, DAEMON_ALIAS, NET],
|
|
44
|
+
);
|
|
45
|
+
db.run(
|
|
46
|
+
`INSERT INTO api_tokens (token_id, user_id, network_id, scope, name, token_hash, expires_at, revoked_at)
|
|
47
|
+
VALUES (?1, ?2, ?3, 'network', ?4, ?5, NULL, NULL)`,
|
|
48
|
+
[DAEMON_TOKEN_ID, DAEMON_USER, NET, `node:${DAEMON_ALIAS}`, "hash_ack_transport_daemon"],
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function seedRequest(requestId: string) {
|
|
53
|
+
db.run(
|
|
54
|
+
`INSERT INTO node_create_requests
|
|
55
|
+
(request_id, daemon_node_id, child_name, network_id, runtime, model, flags_json, env_keys, status, created_at, created_by_token)
|
|
56
|
+
VALUES (?1, ?2, ?3, ?4, 'codex-sdk', 'x', '{}', '[]', 'delivered', ?5, ?6)`,
|
|
57
|
+
[requestId, DAEMON_NODE_ID, `child_${requestId}`, NET, Date.now(), DAEMON_TOKEN_ID],
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function connectClient() {
|
|
62
|
+
const server = new McpServer({ name: "ack-transport-test", version: "1" });
|
|
63
|
+
registerTools(
|
|
64
|
+
server,
|
|
65
|
+
undefined,
|
|
66
|
+
NET,
|
|
67
|
+
DAEMON_USER,
|
|
68
|
+
DAEMON_ALIAS,
|
|
69
|
+
true,
|
|
70
|
+
DAEMON_TOKEN_ID,
|
|
71
|
+
);
|
|
72
|
+
const client = new Client({ name: "ack-transport-client", version: "1" });
|
|
73
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
74
|
+
await server.connect(serverTransport);
|
|
75
|
+
await client.connect(clientTransport);
|
|
76
|
+
return { client, server };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type ToolResult = { content: Array<{ type: string; text?: string }>; isError?: boolean };
|
|
80
|
+
|
|
81
|
+
function responseJson(result: ToolResult) {
|
|
82
|
+
const first = result.content[0];
|
|
83
|
+
if (!first || first.type !== "text" || typeof first.text !== "string") throw new Error("missing text tool result");
|
|
84
|
+
return JSON.parse(first.text) as { ok?: boolean; status?: string };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function expectTransportValidationError(result: ToolResult) {
|
|
88
|
+
expect(result.isError).toBe(true);
|
|
89
|
+
const first = result.content[0];
|
|
90
|
+
expect(first?.type).toBe("text");
|
|
91
|
+
expect(first?.text).toMatch(/Input validation error.*ack_create_request/i);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
beforeEach(() => {
|
|
95
|
+
cleanup();
|
|
96
|
+
seedWorld();
|
|
97
|
+
});
|
|
98
|
+
afterAll(cleanup);
|
|
99
|
+
|
|
100
|
+
describe("#344 ack_create_request real in-process MCP transport", () => {
|
|
101
|
+
test("accepts runtime_capability_check_failed plus string runtime through the SDK zod gate", async () => {
|
|
102
|
+
const requestId = "cr_transport_accept";
|
|
103
|
+
seedRequest(requestId);
|
|
104
|
+
const { client, server } = await connectClient();
|
|
105
|
+
try {
|
|
106
|
+
const result = await client.callTool({
|
|
107
|
+
name: "ack_create_request",
|
|
108
|
+
arguments: {
|
|
109
|
+
request_id: requestId,
|
|
110
|
+
status: "runtime_capability_check_failed",
|
|
111
|
+
error: "child died within 5000ms post-spawn",
|
|
112
|
+
runtime: "codex-sdk",
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
expect(responseJson(result)).toMatchObject({ ok: true, status: "runtime_capability_check_failed" });
|
|
116
|
+
expect(db.get<{ status: string }>("SELECT status FROM node_create_requests WHERE request_id = ?1", requestId)?.status)
|
|
117
|
+
.toBe("runtime_capability_check_failed");
|
|
118
|
+
} finally {
|
|
119
|
+
await client.close();
|
|
120
|
+
await server.close();
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("rejects unknown status at the transport gate before the handler mutates the row", async () => {
|
|
125
|
+
const requestId = "cr_transport_bad_status";
|
|
126
|
+
seedRequest(requestId);
|
|
127
|
+
const { client, server } = await connectClient();
|
|
128
|
+
try {
|
|
129
|
+
const result = await client.callTool({
|
|
130
|
+
name: "ack_create_request",
|
|
131
|
+
arguments: { request_id: requestId, status: "capability_failed_typo", runtime: "codex-sdk" },
|
|
132
|
+
});
|
|
133
|
+
expectTransportValidationError(result);
|
|
134
|
+
expect(db.get<{ status: string }>("SELECT status FROM node_create_requests WHERE request_id = ?1", requestId)?.status)
|
|
135
|
+
.toBe("delivered");
|
|
136
|
+
} finally {
|
|
137
|
+
await client.close();
|
|
138
|
+
await server.close();
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("rejects non-string runtime at the transport gate before the handler mutates the row", async () => {
|
|
143
|
+
const requestId = "cr_transport_bad_runtime";
|
|
144
|
+
seedRequest(requestId);
|
|
145
|
+
const { client, server } = await connectClient();
|
|
146
|
+
try {
|
|
147
|
+
const result = await client.callTool({
|
|
148
|
+
name: "ack_create_request",
|
|
149
|
+
arguments: { request_id: requestId, status: "runtime_capability_check_failed", runtime: 42 },
|
|
150
|
+
});
|
|
151
|
+
expectTransportValidationError(result);
|
|
152
|
+
expect(db.get<{ status: string }>("SELECT status FROM node_create_requests WHERE request_id = ?1", requestId)?.status)
|
|
153
|
+
.toBe("delivered");
|
|
154
|
+
} finally {
|
|
155
|
+
await client.close();
|
|
156
|
+
await server.close();
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
});
|