sophhub 0.4.64 → 0.4.65

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.
@@ -1,10 +1,68 @@
1
1
  {
2
- "version": "1.0.10",
2
+ "version": "1.0.11",
3
3
  "agent_id": "ai-cs-qa",
4
4
  "description": "智能客服,通过 bot API 为客户提供服务",
5
5
  "bot_api_enabled": true,
6
6
  "workspace": "/home/node/.openclaw/workspace-knowledge/workspace-qa",
7
7
  "agent_dependencies": ["ai-cs-admin"],
8
+ "install": {
9
+ "bot_api": {
10
+ "commands": {
11
+ "text": false
12
+ },
13
+ "exec": {
14
+ "induceCommandStarters": [
15
+ "uv run",
16
+ "python",
17
+ "python3",
18
+ "node",
19
+ "bash",
20
+ "curl",
21
+ "kubectl",
22
+ "ls",
23
+ "env",
24
+ "cat",
25
+ "head",
26
+ "tail",
27
+ "find",
28
+ "grep"
29
+ ],
30
+ "commandContentDeny": [
31
+ "sophnet_tools",
32
+ "get_api_key",
33
+ "get-api-key",
34
+ "getApiKey",
35
+ "openclaw.json",
36
+ "models.providers",
37
+ "apiSecret",
38
+ "api_secret",
39
+ "apiKey",
40
+ "api_key",
41
+ ".base.json"
42
+ ],
43
+ "networkDenyTools": [
44
+ "curl",
45
+ "wget",
46
+ "ncat",
47
+ "nc",
48
+ "ssh"
49
+ ],
50
+ "networkDenyCode": [
51
+ "requests.",
52
+ "httpx",
53
+ "urllib",
54
+ "fetch(",
55
+ "http.client"
56
+ ],
57
+ "networkDetectUrls": true,
58
+ "networkAllowHosts": [
59
+ "sophnet.com",
60
+ "www.sophnet.com"
61
+ ],
62
+ "blockUnlistedNetwork": true
63
+ }
64
+ }
65
+ },
8
66
  "post_install": [
9
67
  {
10
68
  "name": "setup-links",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sophhub",
3
- "version": "0.4.64",
3
+ "version": "0.4.65",
4
4
  "description": "SophHub CLI - Manage and download AI Agent skills and agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,12 +1,19 @@
1
1
  {
2
2
  "name": "agent-install",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "types": [
5
5
  "store"
6
6
  ],
7
7
  "displayName": "Agent安装",
8
8
  "description": "安装或升级 Sophclaw Agent(含占位替换、备份、post_install 脚本与自动安装 skill)。",
9
9
  "changelog": [
10
+ {
11
+ "changes": [
12
+ "bot-api 账号安装透传 commands/exec,升级改 deep-merge 保留已有黑名单,修复升级清空 exec 的 BUG"
13
+ ],
14
+ "date": "2026-07-24",
15
+ "version": "0.1.16"
16
+ },
10
17
  {
11
18
  "changes": [
12
19
  "修复 update_openclaw.py 新建实例时覆盖原实例的严重 BUG:改为按目标 openclaw_id 定位条目,新建时追加、不覆盖同源原实例"
@@ -103,5 +110,5 @@
103
110
  }
104
111
  ],
105
112
  "createdAt": "2026-04-21",
106
- "updatedAt": "2026-07-09"
113
+ "updatedAt": "2026-07-24"
107
114
  }
@@ -354,12 +354,19 @@ def build_bot_api_account(
354
354
  account_name = bot_api.get("account_name") or resolved_name or identity_name_str or agent_label_str or source_agent_id
355
355
  enabled = bot_api.get("enabled", True)
356
356
 
357
- return account_id, {
357
+ account: dict[str, Any] = {
358
358
  "agentId": agent_id,
359
359
  "name": account_name,
360
360
  "apiSecret": existing_secret or secrets.token_hex(32),
361
361
  "enabled": enabled,
362
362
  }
363
+ commands = bot_api.get("commands")
364
+ if isinstance(commands, dict):
365
+ account["commands"] = commands
366
+ exec_config = bot_api.get("exec")
367
+ if isinstance(exec_config, dict):
368
+ account["exec"] = exec_config
369
+ return account_id, account
363
370
 
364
371
 
365
372
  def load_openclaw_config(config_path: Path) -> dict[str, Any]:
@@ -141,7 +141,11 @@ def ensure_bot_api(
141
141
  bot_api = channels.setdefault("bot-api", {})
142
142
  accounts = bot_api.setdefault("accounts", {})
143
143
  action = "updated" if account_id in accounts else "created"
144
- accounts[account_id] = account
144
+ existing_account = accounts.get(account_id, {})
145
+ if isinstance(existing_account, dict):
146
+ accounts[account_id] = deep_merge_dict(existing_account, account)
147
+ else:
148
+ accounts[account_id] = account
145
149
 
146
150
  return {
147
151
  "account_id": account_id,
@@ -0,0 +1,116 @@
1
+ """验证 bot-api 账号的 commands/exec 黑名单能从 .config.json 透传进 openclaw.json,
2
+ 且升级时不会覆盖已有黑名单。"""
3
+
4
+ from __future__ import annotations
5
+
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ SCRIPTS = Path(__file__).resolve().parent.parent / "scripts"
10
+ sys.path.insert(0, str(SCRIPTS))
11
+
12
+ from common import build_bot_api_account, deep_merge_dict # noqa: E402
13
+ from update_openclaw import ensure_bot_api # noqa: E402
14
+
15
+
16
+ def _agent_def(bot_api: dict | None = None) -> dict:
17
+ d = {"agent_id": "ai-cs-qa", "bot_api_enabled": True}
18
+ if bot_api is not None:
19
+ d["install"] = {"bot_api": bot_api}
20
+ return d
21
+
22
+
23
+ def test_commands_and_exec_propagated():
24
+ bot_api = {
25
+ "commands": {"text": False},
26
+ "exec": {"commandContentDeny": ["apiSecret", "openclaw.json"]},
27
+ }
28
+ _, account = build_bot_api_account(_agent_def(bot_api))
29
+ assert account["commands"] == {"text": False}
30
+ assert account["exec"]["commandContentDeny"] == ["apiSecret", "openclaw.json"]
31
+ assert {"agentId", "name", "apiSecret", "enabled", "commands", "exec"} == set(account)
32
+
33
+
34
+ def test_omitted_commands_exec_not_added():
35
+ _, account = build_bot_api_account(_agent_def())
36
+ assert "commands" not in account
37
+ assert "exec" not in account
38
+
39
+
40
+ def test_upgrade_preserves_existing_exec_not_in_config():
41
+ """配置里没声明 exec 时,升级不得清掉 openclaw.json 已有 exec。"""
42
+ config = {
43
+ "channels": {
44
+ "bot-api": {
45
+ "accounts": {
46
+ "ai-cs-qa": {
47
+ "agentId": "ai-cs-qa",
48
+ "name": "ai-cs-qa",
49
+ "apiSecret": "old-secret",
50
+ "enabled": True,
51
+ "exec": {"commandContentDeny": ["hand-added"]},
52
+ }
53
+ }
54
+ }
55
+ }
56
+ }
57
+ ensure_bot_api(config, _agent_def(), target_agent_id="ai-cs-qa")
58
+ account = config["channels"]["bot-api"]["accounts"]["ai-cs-qa"]
59
+ assert account["exec"]["commandContentDeny"] == ["hand-added"]
60
+ assert account["apiSecret"] == "old-secret" # 复用旧密钥
61
+
62
+
63
+ def test_upgrade_exec_in_config_overrides_existing():
64
+ """配置里声明了 exec,则以配置为准(source of truth),替换而非追加。"""
65
+ config = {
66
+ "channels": {
67
+ "bot-api": {
68
+ "accounts": {
69
+ "ai-cs-qa": {
70
+ "agentId": "ai-cs-qa",
71
+ "name": "old-name",
72
+ "apiSecret": "old-secret",
73
+ "enabled": True,
74
+ "exec": {"commandContentDeny": ["hand-added"], "networkDenyTools": ["curl"]},
75
+ }
76
+ }
77
+ }
78
+ }
79
+ }
80
+ bot_api = {"exec": {"commandContentDeny": ["apiSecret", "openclaw.json"]}}
81
+ ensure_bot_api(config, _agent_def(bot_api), target_agent_id="ai-cs-qa")
82
+ account = config["channels"]["bot-api"]["accounts"]["ai-cs-qa"]
83
+ # 配置声明的列表整体替换
84
+ assert account["exec"]["commandContentDeny"] == ["apiSecret", "openclaw.json"]
85
+ # 配置未声明的子键保留
86
+ assert account["exec"]["networkDenyTools"] == ["curl"]
87
+ assert account["apiSecret"] == "old-secret"
88
+
89
+
90
+ def test_deep_merge_lists_replace():
91
+ assert deep_merge_dict({"a": [1, 2]}, {"a": [3]}) == {"a": [3]}
92
+
93
+
94
+ def test_corrupt_non_dict_account_does_not_crash():
95
+ """旧账号被写成 null/非 dict 时,退化为整体覆盖,不崩。"""
96
+ for bad in (None, "x", 0):
97
+ config = {"channels": {"bot-api": {"accounts": {"ai-cs-qa": bad}}}}
98
+ ensure_bot_api(config, _agent_def(), target_agent_id="ai-cs-qa")
99
+ account = config["channels"]["bot-api"]["accounts"]["ai-cs-qa"]
100
+ assert isinstance(account, dict)
101
+ assert {"agentId", "name", "apiSecret", "enabled"} <= set(account)
102
+
103
+
104
+ def test_old_agent_without_install_bot_api_unchanged_shape():
105
+ """没有 install.bot_api 的老 agent,账号仍只有原四字段。"""
106
+ config = {"channels": {"bot-api": {"accounts": {}}}}
107
+ ensure_bot_api(config, _agent_def(), target_agent_id="ai-cs-qa")
108
+ account = config["channels"]["bot-api"]["accounts"]["ai-cs-qa"]
109
+ assert set(account) == {"agentId", "name", "apiSecret", "enabled"}
110
+
111
+
112
+ if __name__ == "__main__":
113
+ for name, fn in list(globals().items()):
114
+ if name.startswith("test_") and callable(fn):
115
+ fn()
116
+ print(f"ok: {name}")