@sleep2agi/commhub-server 0.9.0-preview.104 → 0.9.0-preview.105
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/stop-delete-node.test.ts +28 -0
- package/src/tools.ts +12 -1
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.105",
|
|
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",
|
|
@@ -435,6 +435,34 @@ describe("get_stop_request + ack_stop_request — daemon flow", () => {
|
|
|
435
435
|
expect((await call(u.stop_node, { child_node_id: CHILD_A_ID, daemon_node_id: DAEMON_A_ID, network_id: NET_A })).ok).toBe(true);
|
|
436
436
|
});
|
|
437
437
|
|
|
438
|
+
// #571 — 手工起的节点(id `n_…`)没有任何 daemon 链接。改前:authority==null 时
|
|
439
|
+
// 放行调用方点名的任意同网 daemon → 门铃发过去,那台 daemon 本地无记录,按 alias
|
|
440
|
+
// 全机 pgrep+SIGTERM,误杀同 alias 的别的 agent-node。改后:不论传没传 daemon 一律拒。
|
|
441
|
+
test("#571: stop/delete of a hand-started node refuse even with an explicit same-network daemon", async () => {
|
|
442
|
+
setupAlphaNetwork();
|
|
443
|
+
const HAND_ID = "n_571abcde";
|
|
444
|
+
const HAND_ALIAS = "sd-hand-571";
|
|
445
|
+
seedChild(NET_A, HAND_ID, HAND_ALIAS, USER_A_ID, "tok_sd_hand_571");
|
|
446
|
+
const u = buildHandlers(USER_A_ID);
|
|
447
|
+
const s = await call(u.stop_node, { child_node_id: HAND_ID, daemon_node_id: DAEMON_A_ID, network_id: NET_A });
|
|
448
|
+
expect(s.ok).toBe(false);
|
|
449
|
+
expect(s.error).toBe("not_daemon_managed");
|
|
450
|
+
expect(String(s.message)).toContain("anet node stop");
|
|
451
|
+
const d = await call(u.delete_node, { child_node_id: HAND_ID, daemon_node_id: DAEMON_A_ID, confirm_alias: HAND_ALIAS, network_id: NET_A });
|
|
452
|
+
expect(d.error).toBe("not_daemon_managed");
|
|
453
|
+
// 没有任何副作用:状态没动、没写 stop 请求行
|
|
454
|
+
expect(readNode(HAND_ID)?.lifecycle_state).toBe("active");
|
|
455
|
+
const rows = db.get<{ n: number }>(`SELECT COUNT(*) AS n FROM node_stop_requests WHERE child_node_id = ?1`, HAND_ID);
|
|
456
|
+
expect(Number(rows?.n ?? 0)).toBe(0);
|
|
457
|
+
// 回归钉:daemon 建的 child(有 create 记录)照常可停
|
|
458
|
+
db.run(
|
|
459
|
+
`INSERT INTO node_create_requests(request_id,daemon_node_id,child_name,network_id,runtime,model,flags_json,env_keys,status,created_at,created_by_token,child_node_id)
|
|
460
|
+
VALUES(?1,?2,?3,?4,'claude-agent-sdk','x','{}','[]','succeeded',1,'t',?5)`,
|
|
461
|
+
[`cr_${CHILD_A_ID.slice(5)}`, DAEMON_A_ID, CHILD_A_ALIAS, NET_A, CHILD_A_ID],
|
|
462
|
+
);
|
|
463
|
+
expect((await call(u.stop_node, { child_node_id: CHILD_A_ID, network_id: NET_A })).ok).toBe(true);
|
|
464
|
+
});
|
|
465
|
+
|
|
438
466
|
// #1448 finding-5 — delete 完成时按派发时定住的 token_id 精确撤 ntok,而非按
|
|
439
467
|
// name='node:<alias>' 广撤。改前:同 alias 在 delete 未收敛窗口内被重建、拿到同名新
|
|
440
468
|
// token,ack 会把新 token 一并误撤(下面 REBUILT_TOK 断言 not revoked 会红)。
|
package/src/tools.ts
CHANGED
|
@@ -4698,7 +4698,8 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
4698
4698
|
ok: false, error: "not_daemon_managed",
|
|
4699
4699
|
message:
|
|
4700
4700
|
`node ${child_node_id} was not created by a daemon (it was started by hand with ` +
|
|
4701
|
-
`\`anet node start\` on some machine), so no daemon on the Hub can stop it
|
|
4701
|
+
`\`anet node start\` on some machine), so no daemon on the Hub can stop it ` +
|
|
4702
|
+
`(naming a daemon_node_id does not change that). ` +
|
|
4702
4703
|
`Run \`anet node stop <alias>\` on that machine instead. ` +
|
|
4703
4704
|
`Daemon-created children have ids beginning with \`node_\`; this one does not.`,
|
|
4704
4705
|
};
|
|
@@ -4826,6 +4827,16 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
4826
4827
|
// authority==null(create 记录被裁剪的旧 child)加拒,避免给它们的 stop/delete 引入
|
|
4827
4828
|
// 新失败面——handler 的 daemon_node_id??resolveDaemonForChild 已兜住无法解析的情形。
|
|
4828
4829
|
const authoritativeDaemonId = resolveDaemonForChild(args.child_node_id);
|
|
4830
|
+
// #571 — 手工起的节点(id 不以 `node_` 开头)**没有**任何 daemon 链接。此前
|
|
4831
|
+
// authority==null 时放行调用方点名的任意同网 daemon → 门铃发给那台 daemon,
|
|
4832
|
+
// 它本地没这个 child 的记录,会走「按 alias 全机 pgrep + SIGTERM」的收敛分支,
|
|
4833
|
+
// 杀掉那台机器上所有同 alias 的 agent-node(不分工作目录/HOME/网络)。
|
|
4834
|
+
// 对 hand-started 节点这条路在概念上就不成立(见 explainUnresolvableDaemon ①),
|
|
4835
|
+
// 所以不管调用方传没传 daemon_node_id 一律拒。`node_` 开头但缺 create 记录的
|
|
4836
|
+
// (②,显式传 daemon 是文档化的出路)保持原行为。
|
|
4837
|
+
if (!authoritativeDaemonId && !args.child_node_id.startsWith("node_")) {
|
|
4838
|
+
return explainUnresolvableDaemon(args.child_node_id);
|
|
4839
|
+
}
|
|
4829
4840
|
if (authoritativeDaemonId && authoritativeDaemonId !== args.daemon_node_id) {
|
|
4830
4841
|
return { ok: false, error: "daemon_child_mismatch", message: "daemon_node_id is not this child's authoritative creator daemon" };
|
|
4831
4842
|
}
|