@sleep2agi/commhub-server 0.9.0-preview.13 → 0.9.0-preview.131
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 +130 -10
- package/bin/commhub.ts +99 -11
- package/package.json +7 -7
- package/src/ack-create-request-transport.test.ts +182 -0
- package/src/ack-create-request.test.ts +50 -7
- package/src/admin-delete-network-http.test.ts +228 -0
- package/src/admin-networks-http.test.ts +88 -0
- package/src/adoption-candidates-http.test.ts +137 -0
- package/src/adoption-candidates.ts +158 -0
- package/src/agent-access-batch.test.ts +210 -0
- package/src/agent-access.ts +487 -0
- package/src/agent-acl-groups-http.test.ts +247 -0
- package/src/agent-acl-http.test.ts +659 -0
- package/src/agent-acl-slice3-http.test.ts +267 -0
- package/src/agent-groups-perf.test.ts +223 -0
- package/src/agent-notice.ts +48 -0
- package/src/agent-team-whoami-http.test.ts +98 -0
- package/src/agent-teams-http.test.ts +323 -0
- package/src/agent-teams.ts +244 -0
- package/src/alias-filter.test.ts +72 -0
- package/src/alias-filter.ts +42 -0
- package/src/api-host-supervisors-can-create-nodes.test.ts +350 -0
- package/src/api-host-supervisors-fallback.test.ts +271 -0
- package/src/api-host-supervisors-runtime-readiness.test.ts +195 -0
- package/src/api-nodes-shape.test.ts +26 -10
- package/src/auth-password-change-cli-tokens-http.test.ts +294 -0
- package/src/auth-sessions-http.test.ts +250 -0
- package/src/auth-validate.test.ts +5 -1
- package/src/auth-weak-password-login.test.ts +176 -0
- package/src/auth.ts +598 -49
- package/src/avatar-validate.test.ts +163 -0
- package/src/avatar-validate.ts +103 -0
- package/src/binding-generation-http.test.ts +121 -0
- package/src/blocked-exit-on-reply.test.ts +122 -0
- package/src/boot-side-effect.test.ts +124 -0
- package/src/clone-node.test.ts +248 -0
- package/src/clone-node.ts +289 -0
- package/src/codex-config-flags.ts +77 -0
- package/src/codex-fork-contract.ts +23 -0
- package/src/codex-fork-migration.test.ts +38 -0
- package/src/commhub-bin-args.test.ts +85 -0
- package/src/config-apply-sec1.test.ts +107 -0
- package/src/config-apply-validate.test.ts +134 -1
- package/src/config-apply-validate.ts +111 -5
- package/src/create-node-blocked-gate.test.ts +356 -0
- package/src/create-node-network-id.test.ts +79 -0
- package/src/create-node-tool-schema.test.ts +48 -0
- package/src/create-node-unicode-name.test.ts +120 -0
- package/src/create-node-validate.test.ts +193 -7
- package/src/create-node-validate.ts +227 -15
- package/src/create-node-workdir.test.ts +289 -0
- package/src/create-node.ts +77 -23
- package/src/daemon-provider-snapshots.test.ts +421 -0
- package/src/daemon-provider-snapshots.ts +327 -0
- package/src/daemon-token-owner-http.test.ts +236 -0
- package/src/dashboard-slash-routing-http.test.ts +107 -0
- package/src/db-adapter-guard.test.ts +148 -0
- package/src/db-adapter-translate.test.ts +128 -0
- package/src/db-adapter.ts +323 -87
- package/src/db-timestamp.test.ts +38 -0
- package/src/db-timestamp.ts +15 -0
- package/src/db.ts +933 -36
- package/src/delivered-without-push.test.ts +140 -0
- package/src/department-groups-http.test.ts +209 -0
- package/src/department-groups-sync-http.test.ts +328 -0
- package/src/department-groups.ts +434 -0
- package/src/department-heads-http.test.ts +330 -0
- package/src/department-heads.ts +226 -0
- package/src/departments-http.test.ts +185 -0
- package/src/departments.ts +260 -0
- package/src/desktop-message-seam.test.ts +150 -0
- package/src/desktop-message.test.ts +368 -0
- package/src/dm-file-scope-http.test.ts +178 -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 +321 -0
- package/src/external-schedule-edits-timestamps.test.ts +64 -0
- package/src/external-schedule-edits.ts +327 -0
- package/src/file-download-authz-dev-open.test.ts +135 -0
- package/src/file-download-authz.test.ts +404 -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 +732 -0
- package/src/group-membership-events-http.test.ts +264 -0
- package/src/group-messages-http.test.ts +482 -0
- package/src/group-messages.ts +297 -0
- package/src/health-redaction.test.ts +642 -0
- package/src/http-gzip.test.ts +94 -0
- package/src/http-gzip.ts +112 -0
- package/src/hub-provider-config.test.ts +139 -0
- package/src/hub-provider-config.ts +105 -0
- package/src/hub-timestamp.test.ts +38 -0
- package/src/hub-timestamp.ts +27 -0
- package/src/human-dm.ts +215 -0
- package/src/inbox-caller.ts +49 -0
- package/src/inbox-count-sse.test.ts +310 -0
- package/src/inbox-count.ts +29 -0
- package/src/index.ts +15 -2362
- package/src/lifecycle-guard.ts +1 -1
- package/src/mcp-context-cost-http.test.ts +123 -0
- package/src/mcp-projects-events-http.test.ts +162 -0
- package/src/mcp-route-label-http.test.ts +120 -0
- package/src/mcp-write-network-resolution.test.ts +432 -0
- package/src/member-audit-network-id-http.test.ts +93 -0
- package/src/member-presence-http.test.ts +210 -0
- package/src/messages-ack-by-agent-http.test.ts +142 -0
- package/src/migrate-sqlite-to-pg.test.ts +47 -0
- package/src/migrate-sqlite-to-pg.ts +263 -0
- package/src/model-auth-notify-http.test.ts +181 -0
- package/src/model-auth-notify.ts +116 -0
- package/src/network-name-http.test.ts +75 -0
- package/src/network-scope.ts +276 -0
- package/src/network-token-membership-http.test.ts +139 -0
- package/src/node-alias-control.ts +12 -0
- package/src/node-attrs-validate.ts +88 -0
- package/src/node-attrs.test.ts +217 -0
- package/src/node-caller-identity-http.test.ts +781 -0
- package/src/node-create-request-status-http.test.ts +105 -0
- package/src/node-daemon-bindings.test.ts +132 -0
- package/src/node-daemon-bindings.ts +130 -0
- package/src/node-files-transport.test.ts +344 -0
- package/src/node-health-codex-login.test.ts +120 -0
- package/src/node-health-dispatch-http.test.ts +279 -0
- package/src/node-health-guard.ts +105 -0
- package/src/node-health-passthrough.test.ts +115 -0
- package/src/node-health-store.ts +101 -0
- package/src/node-id-alias.test.ts +190 -0
- package/src/node-identity-conflict-http.test.ts +269 -0
- package/src/node-identity-conflict.ts +142 -0
- package/src/node-identity-mismatch.test.ts +74 -0
- package/src/node-lifecycle-controllable.test.ts +64 -0
- package/src/node-lifecycle-controllable.ts +53 -0
- package/src/node-lifecycle-read-http.test.ts +245 -0
- package/src/node-lifecycle-read.ts +121 -0
- package/src/node-logs-transport.test.ts +263 -0
- package/src/node-permissions-http.test.ts +276 -0
- package/src/node-permissions.ts +285 -0
- package/src/node-request-retention.test.ts +281 -0
- package/src/node-request-retention.ts +120 -0
- package/src/node-skills-transport.test.ts +209 -0
- package/src/node-token-ownership.ts +51 -0
- package/src/node-token-privilege-http.test.ts +158 -0
- package/src/observer-avatar-http.test.ts +431 -0
- package/src/observer-push.test.ts +252 -0
- package/src/opencode-v2-catalog.test.ts +268 -0
- package/src/opencode-v2-catalog.ts +352 -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/pg-worker.ts +87 -0
- package/src/project-dir-warn-clear-http.test.ts +132 -0
- package/src/project-viewer-can-http.test.ts +114 -0
- package/src/push.ts +429 -10
- package/src/redact-tokens.test.ts +65 -0
- package/src/redact-tokens.ts +40 -0
- package/src/rename.ts +22 -2
- package/src/reply-target-derivation.test.ts +96 -0
- package/src/report-status-handover-capabilities-http.test.ts +124 -0
- package/src/report-status-host-schema.test.ts +235 -0
- package/src/report-status-resume-id-handover.test.ts +150 -0
- package/src/require-explicit-test-db.ts +9 -0
- package/src/requirement-due-reminders-http.test.ts +620 -0
- package/src/requirement-due-reminders.ts +416 -0
- package/src/requirement-events-http.test.ts +268 -0
- package/src/requirement-events.test.ts +45 -0
- package/src/requirement-events.ts +144 -0
- package/src/requirement-notify.ts +165 -0
- package/src/requirement-participant-notify-http.test.ts +309 -0
- package/src/requirement-tag-ops-http.test.ts +266 -0
- package/src/requirement-tags.ts +89 -0
- package/src/requirements-abandoned-http.test.ts +236 -0
- package/src/requirements-comment-http.test.ts +167 -0
- package/src/requirements-completed-at-migration.test.ts +70 -0
- package/src/requirements-errors-mcp-http.test.ts +164 -0
- package/src/requirements-errors.ts +143 -0
- package/src/requirements-etag-http.test.ts +91 -0
- package/src/requirements-http.test.ts +709 -0
- package/src/requirements-last-event-http.test.ts +349 -0
- package/src/requirements-list-mcp-slim-http.test.ts +155 -0
- package/src/requirements-list-slim-http.test.ts +319 -0
- package/src/requirements-mcp.test.ts +163 -0
- package/src/requirements-migrate.ts +259 -0
- package/src/requirements-owner-strict-mcp-http.test.ts +145 -0
- package/src/requirements-people-http.test.ts +114 -0
- package/src/requirements-people-mcp-http.test.ts +225 -0
- package/src/requirements-people.ts +127 -0
- package/src/requirements-priority-lowest.test.ts +114 -0
- package/src/requirements-search-http.test.ts +182 -0
- package/src/requirements-search.test.ts +32 -0
- package/src/requirements-search.ts +145 -0
- package/src/requirements-seq-http.test.ts +193 -0
- package/src/requirements-seq-migration.test.ts +63 -0
- package/src/requirements-start-date.test.ts +125 -0
- package/src/requirements-stats-http.test.ts +288 -0
- package/src/requirements-stats.ts +167 -0
- package/src/requirements-status-participants-http.test.ts +222 -0
- package/src/requirements-upgrade-e2e.test.ts +126 -0
- package/src/requirements-upgrade-migration.test.ts +98 -0
- package/src/requirements.ts +1806 -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 +222 -0
- package/src/rest-identity.test.ts +130 -0
- package/src/rest-identity.ts +139 -0
- package/src/rest-projections.ts +68 -0
- package/src/rest-write-network-resolution.test.ts +133 -0
- package/src/restricted-files.ts +76 -0
- package/src/retention.ts +15 -3
- package/src/route-callers-http.test.ts +120 -0
- package/src/route-timing.test.ts +211 -0
- package/src/route-timing.ts +258 -0
- package/src/rules-file-duplicate-alias.test.ts +234 -0
- package/src/rules-file-session-target.test.ts +207 -0
- package/src/rules-file-transport.test.ts +237 -0
- package/src/runtime-label.test.ts +22 -0
- package/src/runtime-label.ts +21 -0
- package/src/runtime-readiness.ts +90 -0
- package/src/schedule-agent-mcp-http.test.ts +381 -0
- package/src/schedule-agent.ts +192 -0
- package/src/scheduled-doorbell-nodeless-session.test.ts +134 -0
- package/src/scheduled-failures-http.test.ts +233 -0
- package/src/scheduled-failures.ts +179 -0
- package/src/scheduled-offline.test.ts +131 -0
- package/src/scheduled-offline.ts +66 -0
- package/src/scheduled-reply-unread-http.test.ts +212 -0
- package/src/scheduled-run-terminal.test.ts +268 -0
- package/src/scheduled-skip-blocking-task.test.ts +120 -0
- package/src/scheduled-stuck-http.test.ts +238 -0
- package/src/scheduled-stuck.ts +153 -0
- package/src/scheduled-tasks-http.test.ts +638 -0
- package/src/scheduled-tasks.ts +766 -0
- package/src/scoped-secrets.ts +124 -0
- package/src/send-dedup-request-id.test.ts +229 -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 +458 -0
- package/src/send_dedup.test.ts +54 -0
- package/src/send_dedup.ts +52 -6
- package/src/serve-error.test.ts +38 -0
- package/src/serve-error.ts +27 -0
- package/src/server.ts +5257 -0
- package/src/session-task-on-dispatch.ts +14 -0
- package/src/session-token-ownership-http.test.ts +122 -0
- package/src/shared/codex-inventory.ts +41 -0
- package/src/shared/daemon-provider-login.ts +31 -0
- package/src/shared/daemon-provider-snapshot.ts +52 -0
- package/src/shared/external-schedule-contract.ts +60 -0
- package/src/shared/node-name.test.ts +62 -0
- package/src/shared/node-name.ts +161 -0
- package/src/shared/provider-application.ts +29 -0
- package/src/shared/provider-collection.ts +14 -0
- package/src/shared/provider-initialization.ts +8 -0
- package/src/shared/reserved-env-drift.test.ts +10 -0
- package/src/shared/scoped-secret-contract.ts +5 -0
- package/src/shared/task-content-limit-drift.test.ts +28 -0
- package/src/shared/task-content-limit.ts +25 -0
- package/src/side-thread-command-transport.test.ts +312 -0
- package/src/side-thread-command-transport.ts +318 -0
- package/src/side-thread-http-integration.test.ts +233 -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 +2499 -0
- package/src/skillhub-http.test.ts +108 -0
- package/src/sqlite-close-checkpoint.test.ts +78 -0
- package/src/start-node.test.ts +264 -0
- package/src/status-alias-filter-http.test.ts +106 -0
- package/src/status-alias-resolver-http.test.ts +163 -0
- package/src/status-alias-resolver.ts +19 -0
- package/src/status-node-id-filter-http.test.ts +161 -0
- package/src/status-read-cache-http.test.ts +229 -0
- package/src/status-read-cache.ts +122 -0
- package/src/stop-delete-node.test.ts +509 -1
- package/src/task-access-http.test.ts +402 -0
- package/src/task-access.ts +420 -0
- package/src/task-auth-origin-http.test.ts +93 -0
- package/src/task-auth-origin.test.ts +24 -0
- package/src/task-auth-origin.ts +7 -0
- package/src/task-consumption.test.ts +482 -0
- package/src/task-diagnostic-http.test.ts +149 -0
- package/src/task-diagnostic.test.ts +115 -0
- package/src/task-diagnostic.ts +118 -0
- package/src/task-expiry-notice-http.test.ts +287 -0
- package/src/task-expiry-notice.ts +315 -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-late-replies.ts +54 -0
- package/src/task-late-reply-http.test.ts +152 -0
- package/src/task-lifecycle-watcher.test.ts +204 -0
- package/src/task-lifecycle-watcher.ts +49 -0
- package/src/task-network-resolution-http.test.ts +140 -0
- package/src/task-orphan-http.test.ts +287 -0
- package/src/task-orphan.ts +119 -0
- package/src/task-queue-ahead-http.test.ts +309 -0
- package/src/task-queue-ahead-scale.test.ts +236 -0
- package/src/task-queue-ahead.ts +186 -0
- package/src/task-rejection-log-http.test.ts +160 -0
- package/src/task-rejection-log.ts +49 -0
- package/src/task-sender-http.test.ts +143 -0
- package/src/task-stale-open-http.test.ts +290 -0
- package/src/task-stale-open.ts +103 -0
- package/src/tasks-pagination-http.test.ts +141 -0
- package/src/tool-audience-http.test.ts +220 -0
- package/src/tool-audience.ts +118 -0
- package/src/tools-list-json-schema.test.ts +67 -0
- package/src/tools.ts +3940 -325
- package/src/upload-early-reject-guard.test.ts +155 -0
- package/src/uploads-http.test.ts +97 -15
- package/src/uploads.ts +103 -6
- package/src/user-avatar-http.test.ts +74 -0
- package/src/user-avatar-migration.test.ts +25 -0
- package/src/user-inbox-by-agent-http.test.ts +161 -0
- package/src/user-inbox-netid-notnull.test.ts +122 -0
- package/src/user-inbox-read-http.test.ts +223 -0
- package/src/user-inbox-schema.test.ts +54 -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
|
|
|
@@ -128,9 +150,11 @@ delivered/acked/running → reassign → delivered (new agent)
|
|
|
128
150
|
|
|
129
151
|
## PostgreSQL (community extension point — not on the maintained roadmap)
|
|
130
152
|
|
|
153
|
+
> **PostgreSQL support is still in development and does not work yet** (PostgreSQL 支持尚在开发中,目前不可用). With `DATABASE_URL=postgres://…` the hub builds its schema, but `startHub()` refuses to run on PostgreSQL unless you also set `COMMHUB_PG_EXPERIMENTAL=1` (experimental, for testing only: it switches on the scheduler, task runtime evidence and side-thread commands on PostgreSQL before their PostgreSQL tests exist; RFC-039 S4 removes the flag). Progress: [RFC-039](https://github.com/sleep2agi/agent-network/blob/main/docs/rfcs/RFC-039-hub-postgresql-backend.md).
|
|
154
|
+
|
|
131
155
|
> v0.8+ product direction is **SQLite only** (see [docs/v3-postgresql-design.md banner](https://github.com/sleep2agi/agent-network/blob/main/docs/v3-postgresql-design.md)). The PostgreSQL adapter interface is preserved as a community extension point — no E2E coverage on the current stable line; **not recommended for mainline production**.
|
|
132
156
|
|
|
133
|
-
|
|
157
|
+
Design intent (not working today): setting `DATABASE_URL` switches to PostgreSQL, and the SQL layer translates SQLite-isms (datetime, parameter placeholders). Requires `bun add pg`.
|
|
134
158
|
|
|
135
159
|
```bash
|
|
136
160
|
DATABASE_URL=postgres://user:pass@host:5432/commhub bunx @sleep2agi/commhub-server
|
|
@@ -144,7 +168,103 @@ DATABASE_URL=postgres://user:pass@host:5432/commhub bunx @sleep2agi/commhub-serv
|
|
|
144
168
|
| `HOST` | `0.0.0.0` in the server package, `127.0.0.1` when launched by `anet hub start` | listen address |
|
|
145
169
|
| `COMMHUB_AUTH_TOKEN` | (none) | Bearer token gate (legacy) |
|
|
146
170
|
| `COMMHUB_DB` | `~/.commhub/commhub.db` | SQLite path |
|
|
147
|
-
| `DATABASE_URL` | (none) | switches to PostgreSQL when set
|
|
171
|
+
| `DATABASE_URL` | (none) | switches to PostgreSQL when set — **does not work yet**, see above |
|
|
172
|
+
|
|
173
|
+
## Running the unit tests
|
|
174
|
+
|
|
175
|
+
### 先说该走哪条路:Docker
|
|
176
|
+
|
|
177
|
+
仓库规约是测试在 Docker 里跑(`CLAUDE.md`「所有测试在 Docker 里跑:不碰本地
|
|
178
|
+
环境,不改生产」)。`server/src` 的测试在 `tests/` 下已经有 **24 个**按主题分片的
|
|
179
|
+
套件覆盖(实测:遍历 main 上 204 个套件目录,run.sh 里跑 `server/src/*.test.ts`
|
|
180
|
+
的有 24 个),每个都在容器内自带 `COMMHUB_DB`。跑其中任意一个都是这个形状:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
# 从仓根跑
|
|
184
|
+
docker build --build-arg SOURCE_COMMIT="$(git rev-parse HEAD)" \
|
|
185
|
+
-t anet-test624 -f tests/test624-task-cursor-pagination/Dockerfile .
|
|
186
|
+
docker run --rm anet-test624
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
(实跑过:退出码 0,`6 pass / 0 fail`,`RESULT: PASS`。换成 `tests/` 下别的
|
|
190
|
+
`test*-*/Dockerfile` 同理,但 **build-arg 的名字要照抄那个 Dockerfile** ——
|
|
191
|
+
main 上 66 处写的是 `SOURCE_COMMIT`,另有若干写成 `TEST<编号>_SOURCE_COMMIT`
|
|
192
|
+
(如 `TEST686_SOURCE_COMMIT`、`TEST765_SOURCE_COMMIT`)。传错名字不会报错,
|
|
193
|
+
只会让套件收到 `unknown` 然后按它自己的规则红或绿 —— 这不是你想要的那种绿。)
|
|
194
|
+
|
|
195
|
+
> ⚠️ **目前没有一个套件跑「`server/src` 下的全部」单测。** 上面那些是按主题
|
|
196
|
+
> 分片的;聚合门在 #798 里,尚未合入 main。所以「所有分片套件都绿」**不等于**
|
|
197
|
+
> 69 个单测文件都跑过 —— 在 #798 合入之前,不要把前者当成后者。
|
|
198
|
+
|
|
199
|
+
### 直接在宿主上跑时的两条契约
|
|
200
|
+
|
|
201
|
+
下面这两条是上面那些套件在容器内**已经替你满足**的东西。你只有在本地迭代
|
|
202
|
+
单个文件时才需要自己处理它们 —— 而那种跑法不构成任何门禁证据。
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
1. **`COMMHUB_DB` must be set.** It is not optional — `~/.commhub/commhub.db` is a
|
|
206
|
+
*live* database on any machine that has ever run a hub. This one is enforced:
|
|
207
|
+
with `NODE_ENV=test` (which `bun test` sets) and no `COMMHUB_DB`, the adapter
|
|
208
|
+
refuses with `REFUSING to open the default SQLite database`.
|
|
209
|
+
|
|
210
|
+
2. **Each test *file* needs its own database.** This is *not* enforced — a single
|
|
211
|
+
shared `COMMHUB_DB` passes the guard above and then fails a handful of tests
|
|
212
|
+
for reasons that look like product bugs but are not. `scripts/qa.sh` encodes
|
|
213
|
+
the real contract:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
# 从仓根跑(见下),每个文件一个全新的库
|
|
217
|
+
f=server/src/auth-validate.test.ts
|
|
218
|
+
db=/tmp/anet-$(basename "$f" .test.ts).db
|
|
219
|
+
rm -f "$db" # 复用上一次的库同样会破坏隔离
|
|
220
|
+
COMMHUB_DB="$db" bun test "$f"
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
整套跑法:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
rc=0
|
|
227
|
+
# 用 find 而不是 `server/src/*.test.ts` —— glob 不进子目录,会漏掉
|
|
228
|
+
# server/src/shared/ 下的 3 个(实测:深度 1 是 66 个,递归是 69 个)。
|
|
229
|
+
# scripts/qa.sh 与 test798 用的就是 find,这里保持一致。
|
|
230
|
+
while IFS= read -r f; do
|
|
231
|
+
# 用完整路径派生库名,不用 basename:加入子目录之后,
|
|
232
|
+
# 两个不同目录下的同名文件会派生出同一个库(原来只扫单层时不可能)。
|
|
233
|
+
db=/tmp/anet-$(printf '%s' "${f%.test.ts}" | tr '/.' '--').db
|
|
234
|
+
rm -f "$db" "$db-wal" "$db-shm"
|
|
235
|
+
COMMHUB_DB="$db" bun test "$f" || { echo "FAILED: $f"; rc=1; }
|
|
236
|
+
done < <(find server/src -type f -name '*.test.ts' | sort)
|
|
237
|
+
[ "$rc" -eq 0 ]
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
⚠️ 最后那行 `[ "$rc" -eq 0 ]` 不是装饰,是这段的**门**。写成
|
|
241
|
+
`bun test "$f" || echo "FAILED: $f"` 会让循环打印一行然后继续,
|
|
242
|
+
整段的退出码恒为 0 —— **测试全红,脚本报绿**。用 `[ ... ]` 而不是
|
|
243
|
+
`exit "$rc"`,是为了这段直接粘进终端时不会把你的 shell 关掉;
|
|
244
|
+
存成脚本跑时,它是最后一条命令,脚本退出码就等于它。
|
|
245
|
+
|
|
246
|
+
⚠️ 别把 `$name` 之类的占位符原样抄进命令行 —— 未定义的变量会展开成空串,
|
|
247
|
+
于是每个文件都写进**同一个** `/tmp/…-.db`,正好是这一节要避免的事。
|
|
248
|
+
|
|
249
|
+
Symptom if you ignore it: `bun test src/` under one database turns
|
|
250
|
+
`admin-networks-http` and `scheduled-tasks-http` red. Both are **global-count
|
|
251
|
+
assertions that are correct under the contract**:
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
// admin-networks-http.test.ts — the admin sees EXACTLY this file's two networks
|
|
255
|
+
expect(new Set(rows.map((row) => row.network_id)))
|
|
256
|
+
.toEqual(new Set([adminNetworkId, memberNetworkId]));
|
|
257
|
+
|
|
258
|
+
// scheduled-tasks-http.test.ts — EXACTLY one due occurrence was processed
|
|
259
|
+
expect(runDueScheduledTasks().processed).toBe(1);
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Share a database and other files' networks and due schedules join the count.
|
|
263
|
+
Nothing is broken; the run violated the contract.
|
|
264
|
+
|
|
265
|
+
`scripts/qa.sh` 的 L0 目前只点名跑 5 个 server 测试(白名单),不是全量;
|
|
266
|
+
把 `server/src` 全域逐文件跑起来的那道 CI 门还在评审中(`#798`),
|
|
267
|
+
未进 `main` —— 所以本节描述的是**契约**,不是「已经有东西在替你执行它」。
|
|
148
268
|
|
|
149
269
|
## Auth modes
|
|
150
270
|
|
|
@@ -156,7 +276,7 @@ DATABASE_URL=postgres://user:pass@host:5432/commhub bunx @sleep2agi/commhub-serv
|
|
|
156
276
|
## Not verified
|
|
157
277
|
|
|
158
278
|
- `/api/license*` — experimental legacy trial/pro-license endpoints.
|
|
159
|
-
- PostgreSQL backend —
|
|
279
|
+
- PostgreSQL backend — in development, does not work yet (the hub refuses to start on it without the experimental opt-in; RFC-039).
|
|
160
280
|
- Telegram / WeChat / Feishu channel endpoints — channel code exists, but only Telegram-oriented agent-node paths are actively exercised.
|
|
161
281
|
|
|
162
282
|
## License
|
package/bin/commhub.ts
CHANGED
|
@@ -12,19 +12,56 @@
|
|
|
12
12
|
|
|
13
13
|
const args = process.argv.slice(2);
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
// RFC-039 S5 — offline SQLite → PostgreSQL copy. Handled before any server
|
|
16
|
+
// option parsing: it never starts a Hub and never opens the default database.
|
|
17
|
+
if (args[0] === "migrate-to-pg") {
|
|
18
|
+
const flag = (name: string) => args.includes(name);
|
|
19
|
+
const value = (name: string) => { const i = args.indexOf(name); return i > 0 ? args[i + 1] : undefined; };
|
|
20
|
+
const MIGRATE_VALUE_FLAGS = new Set(["--from", "--to"]);
|
|
21
|
+
const MIGRATE_FLAGS = new Set(["--dry-run", "--i-know-this-is-a-copy", "--help", "-h"]);
|
|
22
|
+
for (let i = 1; i < args.length; i++) {
|
|
23
|
+
if (MIGRATE_VALUE_FLAGS.has(args[i])) { i++; continue; }
|
|
24
|
+
if (!MIGRATE_FLAGS.has(args[i])) {
|
|
25
|
+
console.error(`commhub-server migrate-to-pg: unknown argument ${args[i]}\nRun "commhub-server migrate-to-pg --help" for usage.`);
|
|
26
|
+
process.exit(2);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (flag("--help") || flag("-h") || !value("--from") || !value("--to")) {
|
|
23
30
|
console.log(`
|
|
31
|
+
Usage:
|
|
32
|
+
commhub-server migrate-to-pg --from <sqlite file> --to <postgres url> [--dry-run] [--i-know-this-is-a-copy]
|
|
33
|
+
|
|
34
|
+
Copies a stopped Hub's SQLite database into an EMPTY PostgreSQL database, in one
|
|
35
|
+
transaction, verifying per-table row counts and content hashes before COMMIT.
|
|
36
|
+
Experimental (RFC-039); available from commhub-server 0.9.0-preview.73.
|
|
37
|
+
Files under the uploads directory are not copied.
|
|
38
|
+
|
|
39
|
+
--dry-run do everything, then roll back and leave the target empty
|
|
40
|
+
--i-know-this-is-a-copy allow a source under ~/.commhub (refused by default)
|
|
41
|
+
`);
|
|
42
|
+
process.exit(flag("--help") || flag("-h") ? 0 : 2);
|
|
43
|
+
}
|
|
44
|
+
const { migrateSqliteToPg, MigrateRefused } = await import("../src/migrate-sqlite-to-pg.ts");
|
|
45
|
+
try {
|
|
46
|
+
await migrateSqliteToPg({
|
|
47
|
+
from: value("--from")!,
|
|
48
|
+
to: value("--to")!,
|
|
49
|
+
dryRun: flag("--dry-run"),
|
|
50
|
+
allowCommhubCopy: flag("--i-know-this-is-a-copy"),
|
|
51
|
+
});
|
|
52
|
+
process.exit(0);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error(error instanceof MigrateRefused ? error.message : `[migrate-to-pg] FAILED, rolled back: ${(error as Error)?.message ?? error}`);
|
|
55
|
+
process.exit(error instanceof MigrateRefused ? 2 : 1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const HELP = `
|
|
24
60
|
CommHub MCP Server — AI Agent 通信中枢
|
|
25
61
|
|
|
26
62
|
Usage:
|
|
27
63
|
commhub-server [options]
|
|
64
|
+
commhub-server migrate-to-pg --help Copy a stopped Hub's SQLite database into PostgreSQL (experimental)
|
|
28
65
|
|
|
29
66
|
Options:
|
|
30
67
|
--port, -p <port> Port to listen on (default: 9200, env: PORT)
|
|
@@ -34,6 +71,7 @@ Options:
|
|
|
34
71
|
--cors <origins> CORS origins, comma-separated (env: COMMHUB_CORS_ORIGINS)
|
|
35
72
|
--dev-open Explicit unauthenticated local development mode
|
|
36
73
|
--help, -h Show this help
|
|
74
|
+
--version, -v Print the version and exit
|
|
37
75
|
|
|
38
76
|
Environment Variables:
|
|
39
77
|
PORT Server port (default: 9200)
|
|
@@ -49,10 +87,60 @@ Examples:
|
|
|
49
87
|
commhub-server --port 9200 --token my-secret-token
|
|
50
88
|
commhub-server --port 9200 --dev-open
|
|
51
89
|
PORT=9200 COMMHUB_AUTH_TOKEN=secret commhub-server
|
|
52
|
-
|
|
90
|
+
`;
|
|
91
|
+
|
|
92
|
+
// Anything this bin does not recognise is a usage error: exit 2 and never
|
|
93
|
+
// start a Hub. Before this, unknown words and flags were ignored and a Hub
|
|
94
|
+
// started on the defaults — `commhub-server <some-subcommand>` on a version
|
|
95
|
+
// without that subcommand opened ~/.commhub/commhub.db on :9200.
|
|
96
|
+
function usageError(message: string): never {
|
|
97
|
+
console.error(`commhub-server: ${message}\nRun "commhub-server --help" for usage.`);
|
|
98
|
+
process.exit(2);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const VALUE_FLAGS: Record<string, string> = {
|
|
102
|
+
"--port": "PORT", "-p": "PORT",
|
|
103
|
+
"--host": "HOST",
|
|
104
|
+
"--token": "COMMHUB_AUTH_TOKEN", "-t": "COMMHUB_AUTH_TOKEN",
|
|
105
|
+
"--db": "COMMHUB_DB",
|
|
106
|
+
"--cors": "COMMHUB_CORS_ORIGINS",
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
for (let i = 0; i < args.length; i++) {
|
|
110
|
+
const arg = args[i];
|
|
111
|
+
if (Object.hasOwn(VALUE_FLAGS, arg)) {
|
|
112
|
+
const value = args[i + 1];
|
|
113
|
+
if (value === undefined || value.startsWith("--")) usageError(`${arg} needs a value`);
|
|
114
|
+
process.env[VALUE_FLAGS[arg]] = value;
|
|
115
|
+
i++;
|
|
116
|
+
} else if (arg === "--dev-open") {
|
|
117
|
+
process.env.COMMHUB_DEV_OPEN = "1";
|
|
118
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
119
|
+
console.log(HELP);
|
|
53
120
|
process.exit(0);
|
|
121
|
+
} else if (arg === "--version" || arg === "-v") {
|
|
122
|
+
// Set by bin/anet-bin.ts before this file is imported. A compiled binary
|
|
123
|
+
// has no package.json beside import.meta.url (/$bunfs). npm leaves it unset.
|
|
124
|
+
const embedded = process.env.ANET_EMBEDDED_COMMHUB_VERSION?.trim() ?? "";
|
|
125
|
+
if (/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(embedded)) {
|
|
126
|
+
console.log(embedded);
|
|
127
|
+
process.exit(0);
|
|
128
|
+
}
|
|
129
|
+
const pkg = (await import("../package.json", { with: { type: "json" } })).default as { version: string };
|
|
130
|
+
console.log(pkg.version);
|
|
131
|
+
process.exit(0);
|
|
132
|
+
} else {
|
|
133
|
+
usageError(arg.startsWith("-") ? `unknown option ${arg}` : `unknown command ${arg}`);
|
|
54
134
|
}
|
|
55
135
|
}
|
|
56
136
|
|
|
57
|
-
//
|
|
58
|
-
|
|
137
|
+
// Opt into the default production DB path before loading the server graph.
|
|
138
|
+
// Ordinary imports / bun -e probes do not receive this capability.
|
|
139
|
+
process.env.COMMHUB_SERVER = "1";
|
|
140
|
+
|
|
141
|
+
// Load the server module (side-effect-free since the #438 corrective:
|
|
142
|
+
// importing binds nothing) and start the hub EXPLICITLY. Never rely on
|
|
143
|
+
// import side effects or import.meta.main here — this bin is a dynamic
|
|
144
|
+
// importer, where import.meta.main is false inside the module.
|
|
145
|
+
const { startHub } = await import("../src/server.js");
|
|
146
|
+
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 — AI Agent communication hub with MCP protocol, multi-network isolation, user auth, and 17
|
|
3
|
+
"version": "0.9.0-preview.131",
|
|
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",
|
|
@@ -46,9 +46,9 @@
|
|
|
46
46
|
"bun": ">=1.2.0"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@modelcontextprotocol/sdk": "
|
|
49
|
+
"@modelcontextprotocol/sdk": "1.32.0",
|
|
50
50
|
"bun-types": "^1.3.13",
|
|
51
51
|
"hono": "^4.12.25",
|
|
52
|
-
"zod": "
|
|
52
|
+
"zod": "4.6.5"
|
|
53
53
|
}
|
|
54
54
|
}
|
|
@@ -0,0 +1,182 @@
|
|
|
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("explicit launch proof survives transport while malformed booleans are rejected", async () => {
|
|
102
|
+
const requestId = 'cr_transport_verified';
|
|
103
|
+
seedRequest(requestId);
|
|
104
|
+
const { client, server } = await connectClient();
|
|
105
|
+
try {
|
|
106
|
+
const read = () => db.get<{ launch_verified_at: number | null }>(
|
|
107
|
+
'SELECT launch_verified_at FROM node_create_requests WHERE request_id=?1', requestId);
|
|
108
|
+
const bad = await client.callTool({ name: 'ack_create_request', arguments: {
|
|
109
|
+
request_id: requestId, status: 'started', child_pid: 7777, launch_verified: 'true',
|
|
110
|
+
} });
|
|
111
|
+
expectTransportValidationError(bad);
|
|
112
|
+
expect(read()?.launch_verified_at).toBeNull();
|
|
113
|
+
const good = await client.callTool({ name: 'ack_create_request', arguments: {
|
|
114
|
+
request_id: requestId, status: 'started', child_pid: 7777, launch_verified: true,
|
|
115
|
+
} });
|
|
116
|
+
expect(responseJson(good).ok).toBe(true);
|
|
117
|
+
expect(read()?.launch_verified_at).toBeGreaterThan(0);
|
|
118
|
+
} finally {
|
|
119
|
+
await client.close();
|
|
120
|
+
await server.close();
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("accepts runtime_capability_check_failed plus string runtime through the SDK zod gate", async () => {
|
|
125
|
+
const requestId = "cr_transport_accept";
|
|
126
|
+
seedRequest(requestId);
|
|
127
|
+
const { client, server } = await connectClient();
|
|
128
|
+
try {
|
|
129
|
+
const result = await client.callTool({
|
|
130
|
+
name: "ack_create_request",
|
|
131
|
+
arguments: {
|
|
132
|
+
request_id: requestId,
|
|
133
|
+
status: "runtime_capability_check_failed",
|
|
134
|
+
error: "child died within 5000ms post-spawn",
|
|
135
|
+
runtime: "codex-sdk",
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
expect(responseJson(result)).toMatchObject({ ok: true, status: "runtime_capability_check_failed" });
|
|
139
|
+
expect(db.get<{ status: string }>("SELECT status FROM node_create_requests WHERE request_id = ?1", requestId)?.status)
|
|
140
|
+
.toBe("runtime_capability_check_failed");
|
|
141
|
+
} finally {
|
|
142
|
+
await client.close();
|
|
143
|
+
await server.close();
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("rejects unknown status at the transport gate before the handler mutates the row", async () => {
|
|
148
|
+
const requestId = "cr_transport_bad_status";
|
|
149
|
+
seedRequest(requestId);
|
|
150
|
+
const { client, server } = await connectClient();
|
|
151
|
+
try {
|
|
152
|
+
const result = await client.callTool({
|
|
153
|
+
name: "ack_create_request",
|
|
154
|
+
arguments: { request_id: requestId, status: "capability_failed_typo", runtime: "codex-sdk" },
|
|
155
|
+
});
|
|
156
|
+
expectTransportValidationError(result);
|
|
157
|
+
expect(db.get<{ status: string }>("SELECT status FROM node_create_requests WHERE request_id = ?1", requestId)?.status)
|
|
158
|
+
.toBe("delivered");
|
|
159
|
+
} finally {
|
|
160
|
+
await client.close();
|
|
161
|
+
await server.close();
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("rejects non-string runtime at the transport gate before the handler mutates the row", async () => {
|
|
166
|
+
const requestId = "cr_transport_bad_runtime";
|
|
167
|
+
seedRequest(requestId);
|
|
168
|
+
const { client, server } = await connectClient();
|
|
169
|
+
try {
|
|
170
|
+
const result = await client.callTool({
|
|
171
|
+
name: "ack_create_request",
|
|
172
|
+
arguments: { request_id: requestId, status: "runtime_capability_check_failed", runtime: 42 },
|
|
173
|
+
});
|
|
174
|
+
expectTransportValidationError(result);
|
|
175
|
+
expect(db.get<{ status: string }>("SELECT status FROM node_create_requests WHERE request_id = ?1", requestId)?.status)
|
|
176
|
+
.toBe("delivered");
|
|
177
|
+
} finally {
|
|
178
|
+
await client.close();
|
|
179
|
+
await server.close();
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
});
|
|
@@ -13,11 +13,11 @@ import { registerTools } from "./tools.js";
|
|
|
13
13
|
// `daemon_capability_lied` audit was written when in fact the action wasn't
|
|
14
14
|
// in the audit enum and no insert ran.
|
|
15
15
|
//
|
|
16
|
-
// This handler-driven suite
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
16
|
+
// This handler-driven suite captures and invokes the registered callback
|
|
17
|
+
// directly. It validates handler branches and DB effects, but deliberately
|
|
18
|
+
// does NOT claim to exercise the MCP SDK's zod transport gate. The
|
|
19
|
+
// complementary in-process transport coverage lives in
|
|
20
|
+
// ack-create-request-transport.test.ts (#344).
|
|
21
21
|
|
|
22
22
|
const NET = "net_ack_t";
|
|
23
23
|
const DAEMON_NODE_ID = "node_ack_daemon";
|
|
@@ -138,8 +138,8 @@ async function callAck(handler: ToolHandler, args: any): Promise<AckReply> {
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
function readRequest(request_id: string) {
|
|
141
|
-
return db.get<{ status: string; error: string | null; acked_at: number | null }>(
|
|
142
|
-
`SELECT status, error, acked_at FROM node_create_requests WHERE request_id = ?1`,
|
|
141
|
+
return db.get<{ status: string; error: string | null; acked_at: number | null; launch_verified_at: number | null }>(
|
|
142
|
+
`SELECT status, error, acked_at, launch_verified_at FROM node_create_requests WHERE request_id = ?1`,
|
|
143
143
|
request_id,
|
|
144
144
|
);
|
|
145
145
|
}
|
|
@@ -297,6 +297,29 @@ describe("ack_create_request HANDLER — B2 audit_log daemon_capability_lied", (
|
|
|
297
297
|
});
|
|
298
298
|
|
|
299
299
|
describe("ack_create_request HANDLER — pre-existing happy-path stays green (regression)", () => {
|
|
300
|
+
for (const status of ['failed', 'rejected', 'runtime_capability_check_failed']) {
|
|
301
|
+
test(`late ${status} after registration cannot leave succeeded with a revoked token`, async () => {
|
|
302
|
+
seedDaemonWorld();
|
|
303
|
+
const request_id = `cr_late_${status}`;
|
|
304
|
+
const child_token_id = `tok_late_${status}`;
|
|
305
|
+
seedCreateRequest({ request_id, child_token_id, status: 'succeeded' });
|
|
306
|
+
const handler = buildAckHandler();
|
|
307
|
+
await callAck(handler, { request_id, status: 'started', child_pid: 7777, launch_verified: true });
|
|
308
|
+
expect(readRequest(request_id)?.launch_verified_at).toBeGreaterThan(0);
|
|
309
|
+
const reply = await callAck(handler, { request_id, status, error: 'post-register launch verdict' });
|
|
310
|
+
expect(reply).toMatchObject({ ok: true, status });
|
|
311
|
+
expect(readRequest(request_id)?.status).toBe(status);
|
|
312
|
+
expect(readRequest(request_id)?.error).toBe('post-register launch verdict');
|
|
313
|
+
expect(readRequest(request_id)?.acked_at).toBeGreaterThan(0);
|
|
314
|
+
expect(readToken(child_token_id)?.revoked_at).not.toBeNull();
|
|
315
|
+
// Delayed success must not resurrect either the request or its token.
|
|
316
|
+
await callAck(handler, { request_id, status: 'started', child_pid: 7777, launch_verified: true });
|
|
317
|
+
expect(readRequest(request_id)?.status).toBe(status);
|
|
318
|
+
expect(readRequest(request_id)?.launch_verified_at).toBeNull();
|
|
319
|
+
expect(readToken(child_token_id)?.revoked_at).not.toBeNull();
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
300
323
|
test("status='started' still flips acked_at without terminal teardown", async () => {
|
|
301
324
|
seedDaemonWorld();
|
|
302
325
|
const reqId = "cr_ack_started_regression";
|
|
@@ -310,10 +333,30 @@ describe("ack_create_request HANDLER — pre-existing happy-path stays green (re
|
|
|
310
333
|
// 'started' doesn't flip to terminal; status stays 'delivered'.
|
|
311
334
|
expect(row?.status).toBe("delivered");
|
|
312
335
|
expect(row?.acked_at).toBeGreaterThan(0);
|
|
336
|
+
expect(row?.launch_verified_at).toBeNull();
|
|
313
337
|
// child ntok is NOT revoked on 'started'.
|
|
314
338
|
expect(readToken(childTok)?.revoked_at).toBeNull();
|
|
315
339
|
});
|
|
316
340
|
|
|
341
|
+
for (const status of ['pending', 'delivered', 'succeeded'] as const) {
|
|
342
|
+
test(`verified launch persists independently of registration: ${status}`, async () => {
|
|
343
|
+
seedDaemonWorld();
|
|
344
|
+
const request_id = `cr_verified_${status}`;
|
|
345
|
+
seedCreateRequest({ request_id, child_token_id: `tok_verified_${status}`, status });
|
|
346
|
+
const handler = buildAckHandler();
|
|
347
|
+
expect(readRequest(request_id)?.launch_verified_at).toBeNull();
|
|
348
|
+
const bad = await callAck(handler, { request_id, status: 'started', launch_verified: true });
|
|
349
|
+
expect(bad.ok).toBe(false);
|
|
350
|
+
expect(readRequest(request_id)?.launch_verified_at).toBeNull();
|
|
351
|
+
await callAck(handler, { request_id, status: 'started', child_pid: 7777, launch_verified: true });
|
|
352
|
+
const first = readRequest(request_id)!;
|
|
353
|
+
expect(first.status).toBe(status);
|
|
354
|
+
expect(first.launch_verified_at).toBeGreaterThan(0);
|
|
355
|
+
await callAck(handler, { request_id, status: 'started', child_pid: 7777, launch_verified: true });
|
|
356
|
+
expect(readRequest(request_id)?.launch_verified_at).toBe(first.launch_verified_at);
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
317
360
|
test("status='failed' still revokes child + flips to failed (no audit_log daemon_capability_lied)", async () => {
|
|
318
361
|
seedDaemonWorld();
|
|
319
362
|
const reqId = "cr_ack_failed_regression";
|