@sleep2agi/commhub-server 0.9.0-preview.37 → 0.9.0-preview.38
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/package.json +1 -1
- package/src/create-node.ts +1 -0
- package/src/stop-delete-node.test.ts +55 -0
- package/src/tools.ts +28 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sleep2agi/commhub-server",
|
|
3
|
-
"version": "0.9.0-preview.
|
|
3
|
+
"version": "0.9.0-preview.38",
|
|
4
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",
|
package/src/create-node.ts
CHANGED
|
@@ -306,6 +306,7 @@ export function auditCreateNodeStrict(input: {
|
|
|
306
306
|
export function auditCreateNode(input: {
|
|
307
307
|
action:
|
|
308
308
|
| "create_node_dispatched"
|
|
309
|
+
| "create_node_dispatch_attempted"
|
|
309
310
|
| "create_node_rejected"
|
|
310
311
|
| "create_node_succeeded"
|
|
311
312
|
| "create_node_sweeper_revoked"
|
|
@@ -36,6 +36,7 @@ interface Reply { ok?: boolean; error?: string; [k: string]: unknown; }
|
|
|
36
36
|
function cleanup() {
|
|
37
37
|
for (const n of [NET_A, NET_B]) {
|
|
38
38
|
try { db.run("DELETE FROM node_stop_requests WHERE network_id = ?1", [n]); } catch {}
|
|
39
|
+
try { db.run("DELETE FROM node_create_requests WHERE network_id = ?1", [n]); } catch {}
|
|
39
40
|
try { db.run("DELETE FROM audit_log WHERE network_id = ?1", [n]); } catch {}
|
|
40
41
|
try { db.run("DELETE FROM inbox WHERE network_id = ?1", [n]); } catch {}
|
|
41
42
|
// PR1.2a restart_node tests leave node_config_updates rows; clear
|
|
@@ -676,6 +677,60 @@ describe("list_my_children HANDLER (RFC-027 PR1.1)", () => {
|
|
|
676
677
|
});
|
|
677
678
|
});
|
|
678
679
|
|
|
680
|
+
describe("create_node audit wording (#1362)", () => {
|
|
681
|
+
test("create_node records a dispatch attempt, not guaranteed SSE delivery", async () => {
|
|
682
|
+
setupAlphaNetwork();
|
|
683
|
+
const tools = buildHandlers(USER_A_ID);
|
|
684
|
+
const r = await call(tools.create_node, {
|
|
685
|
+
daemon_node_id: DAEMON_A_ID,
|
|
686
|
+
node_spec: {
|
|
687
|
+
name: "sd-new-child",
|
|
688
|
+
runtime: "claude-agent-sdk",
|
|
689
|
+
model: "x",
|
|
690
|
+
},
|
|
691
|
+
network_id: NET_A,
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
expect(r.ok).toBe(true);
|
|
695
|
+
expect(typeof r.request_id).toBe("string");
|
|
696
|
+
expect(readAudit("create_node_dispatch_attempted", NET_A).length).toBe(1);
|
|
697
|
+
expect(readAudit("create_node_dispatched", NET_A).length).toBe(0);
|
|
698
|
+
});
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
describe("list_my_pending_create_requests HANDLER (#1362 SSE reconnect compensation)", () => {
|
|
702
|
+
test("daemon-bound caller gets only its own pending create requests", async () => {
|
|
703
|
+
setupAlphaNetwork();
|
|
704
|
+
const otherDaemonId = "node_sd_daemon_other";
|
|
705
|
+
seedDaemon(NET_A, otherDaemonId, "sd-daemon-other", USER_A_ID, "tok_sd_daemon_other");
|
|
706
|
+
db.run(
|
|
707
|
+
`INSERT INTO node_create_requests
|
|
708
|
+
(request_id, daemon_node_id, child_name, network_id, runtime, model, flags_json, env_keys, status, child_token_id, created_at, created_by_token)
|
|
709
|
+
VALUES
|
|
710
|
+
('cr_pending_mine', ?1, 'child-pending-mine', ?2, 'claude-agent-sdk', 'x', '{}', '[]', 'pending', NULL, ?3, 'tok_test'),
|
|
711
|
+
('cr_delivered_mine', ?1, 'child-delivered-mine', ?2, 'claude-agent-sdk', 'x', '{}', '[]', 'delivered', NULL, ?4, 'tok_test'),
|
|
712
|
+
('cr_pending_other', ?5, 'child-pending-other', ?2, 'claude-agent-sdk', 'x', '{}', '[]', 'pending', NULL, ?6, 'tok_test')`,
|
|
713
|
+
[DAEMON_A_ID, NET_A, Date.now(), Date.now() + 1, otherDaemonId, Date.now() + 2],
|
|
714
|
+
);
|
|
715
|
+
|
|
716
|
+
const daemonTools = buildHandlers(USER_A_ID, true, DAEMON_A_TOK, NET_A);
|
|
717
|
+
const r = await call(daemonTools.list_my_pending_create_requests, {});
|
|
718
|
+
|
|
719
|
+
expect(r.ok).toBe(true);
|
|
720
|
+
expect(r.count).toBe(1);
|
|
721
|
+
expect(r.requests).toEqual([
|
|
722
|
+
expect.objectContaining({ request_id: "cr_pending_mine", child_name: "child-pending-mine" }),
|
|
723
|
+
]);
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
test("non-daemon caller (utok) refused", async () => {
|
|
727
|
+
setupAlphaNetwork();
|
|
728
|
+
const userTools = buildHandlers(USER_A_ID);
|
|
729
|
+
const r = await call(userTools.list_my_pending_create_requests, {});
|
|
730
|
+
expect(r.ok).toBe(false);
|
|
731
|
+
});
|
|
732
|
+
});
|
|
733
|
+
|
|
679
734
|
// ─── PR1.2a — restart_node resets lifecycle_state ──────────────────
|
|
680
735
|
//
|
|
681
736
|
// 通信龙 #346 ack latent: without this, a stopped node restart-ed via
|
package/src/tools.ts
CHANGED
|
@@ -3215,9 +3215,10 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
3215
3215
|
// SSE handler resolves the rest via MCP call.
|
|
3216
3216
|
pushEvent(daemon.alias, { type: "create_node", request_id: requestId }, networkIdForChild);
|
|
3217
3217
|
|
|
3218
|
-
// §4.5 audit —
|
|
3218
|
+
// §4.5 audit — request queued and doorbell attempted. pushEvent is
|
|
3219
|
+
// fire-and-forget, so actual SSE delivery is intentionally not claimed.
|
|
3219
3220
|
auditCreateNode({
|
|
3220
|
-
action: "
|
|
3221
|
+
action: "create_node_dispatch_attempted",
|
|
3221
3222
|
user_id: enforceUserId,
|
|
3222
3223
|
network_id: networkIdForChild,
|
|
3223
3224
|
target_id: requestId,
|
|
@@ -3237,6 +3238,31 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
3237
3238
|
},
|
|
3238
3239
|
);
|
|
3239
3240
|
|
|
3241
|
+
// #1362 — daemon-side compensation for missed SSE doorbells. pushEvent is
|
|
3242
|
+
// intentionally best-effort; after a daemon connects or reconnects, it pulls
|
|
3243
|
+
// any still-pending requests bound to its own daemon token.
|
|
3244
|
+
server.tool(
|
|
3245
|
+
"list_my_pending_create_requests",
|
|
3246
|
+
"Daemon lists pending create-node requests bound to itself for SSE reconnect compensation. RFC-026/#1362.",
|
|
3247
|
+
{},
|
|
3248
|
+
async () => {
|
|
3249
|
+
const callerDaemon = resolveCallerDaemonTokenBound();
|
|
3250
|
+
if (!callerDaemon.ok) {
|
|
3251
|
+
return { content: [{ type: "text" as const, text: JSON.stringify({ ok: false, error: callerDaemon.error }) }] };
|
|
3252
|
+
}
|
|
3253
|
+
const rows = db.all<{ request_id: string; child_name: string; created_at: number }>(
|
|
3254
|
+
`SELECT request_id, child_name, created_at
|
|
3255
|
+
FROM node_create_requests
|
|
3256
|
+
WHERE daemon_node_id = ?1
|
|
3257
|
+
AND network_id = ?2
|
|
3258
|
+
AND status = 'pending'
|
|
3259
|
+
ORDER BY created_at ASC`,
|
|
3260
|
+
callerDaemon.daemonNodeId, callerDaemon.networkId,
|
|
3261
|
+
);
|
|
3262
|
+
return { content: [{ type: "text" as const, text: JSON.stringify({ ok: true, count: rows.length, requests: rows }) }] };
|
|
3263
|
+
},
|
|
3264
|
+
);
|
|
3265
|
+
|
|
3240
3266
|
// §2.5 step 3 — daemon-facing pull. Returns full spec + env_blob +
|
|
3241
3267
|
// child_ntok in one shot. Map evicted on take (one-shot consume).
|
|
3242
3268
|
//
|