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.
- package/agents/ai-cs-qa/.config.json +59 -1
- package/package.json +1 -1
- package/skills/agent-install/skill.json +9 -2
- package/skills/agent-install/src/scripts/common.py +8 -1
- package/skills/agent-install/src/scripts/update_openclaw.py +5 -1
- package/skills/agent-install/src/tests/test_bot_api_exec.py +116 -0
|
@@ -1,10 +1,68 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.0.
|
|
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,12 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-install",
|
|
3
|
-
"version": "0.1.
|
|
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-
|
|
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
|
-
|
|
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
|
|
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}")
|