@spzhongwin/skill-logger-plugin 1.0.16 → 1.0.17

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,158 +1,247 @@
1
- import { describe, it } from "node:test";
2
- import assert from "node:assert/strict";
3
- import fs from "node:fs";
4
- import os from "node:os";
5
- import path from "node:path";
6
- import { DatabaseSync } from "node:sqlite";
7
- import {
8
- normalizeAssistantUserId,
9
- normalizeCommandCode,
10
- parseAssistantWorkspaceAgentId,
11
- readCronJobsByAgentId,
12
- findInstalledExpertSkillsRoot,
13
- shouldSyncBuiltInTemplate,
14
- } from "./ws-client.ts";
15
-
16
- describe("command code boundary", () => {
17
- it("只接受单一路径段,不静默改写路径穿越输入", () => {
18
- assert.equal(normalizeCommandCode("demo-skill"), "demo-skill");
19
- assert.equal(normalizeCommandCode("../demo"), undefined);
20
- assert.equal(normalizeCommandCode("a/b"), undefined);
21
- assert.equal(normalizeCommandCode("a\\b"), undefined);
22
- assert.equal(normalizeCommandCode("."), undefined);
23
- assert.equal(normalizeCommandCode(".."), undefined);
24
- assert.equal(normalizeCommandCode(""), undefined);
25
- assert.equal(normalizeCommandCode(123), undefined);
26
- });
27
- });
28
-
29
- describe("assistant workspace naming", () => {
30
- it("accepts workspace-assistant-* only when suffix is at least 5 digits", () => {
31
- assert.equal(parseAssistantWorkspaceAgentId("workspace-assistant-12345"), "assistant-12345");
32
- assert.equal(
33
- parseAssistantWorkspaceAgentId("workspace-assistant-12342325232333223223"),
34
- "assistant-12342325232333223223"
35
- );
36
- assert.equal(parseAssistantWorkspaceAgentId("workspace-assistant-000001"), "assistant-000001");
37
- });
38
-
39
- it("rejects short, non-numeric, and malformed workspace names", () => {
40
- assert.equal(parseAssistantWorkspaceAgentId("workspace-assistant-1234"), undefined);
41
- assert.equal(parseAssistantWorkspaceAgentId("workspace-assistant-1234a"), undefined);
42
- assert.equal(parseAssistantWorkspaceAgentId("workspace-assistant-abcde"), undefined);
43
- assert.equal(parseAssistantWorkspaceAgentId("workspace-coder-12345"), undefined);
44
- });
45
-
46
- it("normalizes command userId using the same numeric suffix rule", () => {
47
- assert.equal(normalizeAssistantUserId("12345"), "12345");
48
- assert.equal(normalizeAssistantUserId("assistant-12345"), "12345");
49
- assert.equal(normalizeAssistantUserId("assistant-000001"), "000001");
50
- });
51
-
52
- it("rejects unsafe or non-conforming command userId values", () => {
53
- assert.equal(normalizeAssistantUserId("1234"), undefined);
54
- assert.equal(normalizeAssistantUserId("assistant-1234"), undefined);
55
- assert.equal(normalizeAssistantUserId("assistant-1234a"), undefined);
56
- assert.equal(normalizeAssistantUserId("../assistant-12345"), undefined);
57
- });
58
- });
59
-
60
- describe("built-in template update boundary", () => {
61
- it("only syncs the template for an explicit built-in UPDATE_SKILL command", () => {
62
- assert.equal(shouldSyncBuiltInTemplate("UPDATE_SKILL", true), true);
63
- assert.equal(shouldSyncBuiltInTemplate("UPDATE_SKILL", false), false);
64
- assert.equal(shouldSyncBuiltInTemplate("UPDATE_SKILL", undefined), false);
65
- assert.equal(shouldSyncBuiltInTemplate("INSTALL_SKILL", true), false);
66
- assert.equal(shouldSyncBuiltInTemplate("UNINSTALL_SKILL", true), false);
67
- });
68
- });
69
-
70
- describe("expert skill update target", () => {
71
- it("返回 .user/skills 父目录,由安装器统一追加 skill code", async () => {
72
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-expert-skill-"));
73
- try {
74
- const skillsRoot = path.join(root, "workspace-assistant-12345", ".user", "skills");
75
- fs.mkdirSync(path.join(skillsRoot, "demo"), { recursive: true });
76
-
77
- assert.equal(await findInstalledExpertSkillsRoot(root, "12345", "demo"), skillsRoot);
78
- assert.equal(await findInstalledExpertSkillsRoot(root, "12345", "missing"), undefined);
79
- } finally {
80
- fs.rmSync(root, { recursive: true, force: true });
81
- }
82
- });
83
- });
84
-
85
- describe("cron job sqlite lookup", () => {
86
- it("returns all cron_jobs rows for the requested agent_id", () => {
87
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-cron-jobs-"));
88
- const dbPath = path.join(dir, "openclaw.sqlite");
89
- const db = new DatabaseSync(dbPath);
90
- try {
91
- db.exec(`
92
- CREATE TABLE cron_jobs (
93
- id INTEGER PRIMARY KEY,
94
- agent_id TEXT NOT NULL,
95
- name TEXT NOT NULL,
96
- cron TEXT NOT NULL
97
- );
98
- `);
99
- db.prepare("INSERT INTO cron_jobs (agent_id, name, cron) VALUES (?, ?, ?)").run(
100
- "assistant-1579723293989240833",
101
- "morning",
102
- "0 9 * * *"
103
- );
104
- db.prepare("INSERT INTO cron_jobs (agent_id, name, cron) VALUES (?, ?, ?)").run(
105
- "assistant-1579723293989240833",
106
- "evening",
107
- "0 18 * * *"
108
- );
109
- db.prepare("INSERT INTO cron_jobs (agent_id, name, cron) VALUES (?, ?, ?)").run(
110
- "assistant-000001",
111
- "other",
112
- "0 12 * * *"
113
- );
114
- } finally {
115
- db.close();
116
- }
117
-
118
- const rows = readCronJobsByAgentId("assistant-1579723293989240833", dbPath);
119
-
120
- assert.equal(rows.length, 2);
121
- assert.deepEqual(
122
- rows.map((row) => row.name),
123
- ["morning", "evening"]
124
- );
125
- });
126
-
127
- it("returns an empty list when no cron_jobs row matches", () => {
128
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-cron-jobs-"));
129
- const dbPath = path.join(dir, "openclaw.sqlite");
130
- const db = new DatabaseSync(dbPath);
131
- try {
132
- db.exec("CREATE TABLE cron_jobs (id INTEGER PRIMARY KEY, agent_id TEXT NOT NULL);");
133
- } finally {
134
- db.close();
135
- }
136
-
137
- assert.deepEqual(readCronJobsByAgentId("assistant-1579723293989240833", dbPath), []);
138
- });
139
-
140
- it("returns an empty list when the sqlite file is not readable", () => {
141
- const missingPath = path.join(os.tmpdir(), `missing-openclaw-${Date.now()}.sqlite`);
142
-
143
- assert.deepEqual(readCronJobsByAgentId("assistant-1579723293989240833", missingPath), []);
144
- });
145
-
146
- it("returns an empty list when cron_jobs table does not exist", () => {
147
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-cron-jobs-"));
148
- const dbPath = path.join(dir, "openclaw.sqlite");
149
- const db = new DatabaseSync(dbPath);
150
- try {
151
- db.exec("CREATE TABLE other_table (id INTEGER PRIMARY KEY, agent_id TEXT NOT NULL);");
152
- } finally {
153
- db.close();
154
- }
155
-
156
- assert.deepEqual(readCronJobsByAgentId("assistant-1579723293989240833", dbPath), []);
157
- });
158
- });
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { DatabaseSync } from "node:sqlite";
7
+ import {
8
+ normalizeAssistantUserId,
9
+ normalizeCommandCode,
10
+ enabledAgentIdsFromAccounts,
11
+ resolveSkillInstallTarget,
12
+ parseAssistantWorkspaceAgentId,
13
+ readCronJobsByAgentId,
14
+ findInstalledExpertSkillsRoot,
15
+ shouldSyncBuiltInTemplate,
16
+ } from "./ws-client.ts";
17
+
18
+ describe("command code boundary", () => {
19
+ it("只接受单一路径段,不静默改写路径穿越输入", () => {
20
+ assert.equal(normalizeCommandCode("demo-skill"), "demo-skill");
21
+ assert.equal(normalizeCommandCode("../demo"), undefined);
22
+ assert.equal(normalizeCommandCode("a/b"), undefined);
23
+ assert.equal(normalizeCommandCode("a\\b"), undefined);
24
+ assert.equal(normalizeCommandCode("."), undefined);
25
+ assert.equal(normalizeCommandCode(".."), undefined);
26
+ assert.equal(normalizeCommandCode(""), undefined);
27
+ assert.equal(normalizeCommandCode(123), undefined);
28
+ });
29
+ });
30
+
31
+ describe("gateway heartbeat agent accounts", () => {
32
+ it("reports enabled accounts and accounts without an explicit enabled field", () => {
33
+ const agentIds = enabledAgentIdsFromAccounts({
34
+ channels: {
35
+ xg_cwork_im: {
36
+ accounts: {
37
+ "assistant-enabled": { agentId: "assistant-enabled", enabled: true },
38
+ "assistant-legacy": { agentId: "assistant-legacy" },
39
+ default: { groupPolicy: "mention" },
40
+ },
41
+ },
42
+ },
43
+ });
44
+
45
+ assert.deepEqual(agentIds, ["assistant-enabled", "assistant-legacy"]);
46
+ });
47
+
48
+ it("filters accounts explicitly disabled in openclaw.json", () => {
49
+ const agentIds = enabledAgentIdsFromAccounts({
50
+ channels: {
51
+ xg_cwork_im: {
52
+ accounts: {
53
+ "assistant-1514822133731725314": {
54
+ agentId: "assistant-1514822133731725314",
55
+ enabled: false,
56
+ name: "个人助理",
57
+ robotKey: "secret-is-not-used-for-heartbeats",
58
+ },
59
+ "assistant-active": { agentId: "assistant-active", enabled: true },
60
+ },
61
+ },
62
+ },
63
+ });
64
+
65
+ assert.deepEqual(agentIds, ["assistant-active"]);
66
+ });
67
+
68
+ it("returns no agents when the account path is absent or malformed", () => {
69
+ assert.deepEqual(enabledAgentIdsFromAccounts({}), []);
70
+ assert.deepEqual(enabledAgentIdsFromAccounts({ channels: { xg_cwork_im: { accounts: [] } } }), []);
71
+ });
72
+ });
73
+
74
+ describe("普通 Skill 配置驱动的安装目录", () => {
75
+ const config = {
76
+ channels: {
77
+ xg_cwork_im: {
78
+ accounts: {
79
+ "assistant-12345": { agentId: "assistant-12345", enabled: true },
80
+ "assistant-67890": { agentId: "assistant-67890", enabled: false },
81
+ },
82
+ },
83
+ },
84
+ bindings: [{
85
+ agentId: "sales-agent",
86
+ match: { channel: "xg_cwork_im", accountId: "assistant-12345" },
87
+ }],
88
+ agents: {
89
+ defaults: { workspace: "/var/openclaw/default" },
90
+ list: [{ id: "sales-agent", workspace: "/var/openclaw/sales" }],
91
+ },
92
+ };
93
+
94
+ it("通过 account、binding agent workspace 解析目录", () => {
95
+ assert.deepEqual(resolveSkillInstallTarget(config, "assistant-12345"), {
96
+ localAgentId: "sales-agent",
97
+ skillsDir: "/var/openclaw/sales/skills",
98
+ });
99
+ });
100
+
101
+ it("拒绝显式 disabled 的 account", () => {
102
+ assert.throws(() => resolveSkillInstallTarget(config, "assistant-67890"), /已禁用/);
103
+ });
104
+
105
+ it("找不到 account、binding 后的 agent 或 workspace 时拒绝回退到命名目录", () => {
106
+ assert.throws(() => resolveSkillInstallTarget(config, "assistant-99999"), /未找到 userId/);
107
+ assert.throws(() => resolveSkillInstallTarget({
108
+ ...config,
109
+ bindings: [{ agentId: "missing-agent", match: { channel: "xg_cwork_im", accountId: "assistant-12345" } }],
110
+ }, "assistant-12345"), /未找到本地 Agent/);
111
+ assert.throws(() => resolveSkillInstallTarget({
112
+ ...config,
113
+ agents: { list: [{ id: "sales-agent" }] },
114
+ }, "assistant-12345"), /未配置 workspace/);
115
+ });
116
+ });
117
+
118
+ describe("assistant workspace naming", () => {
119
+ it("accepts workspace-assistant-* only when suffix is at least 5 digits", () => {
120
+ assert.equal(parseAssistantWorkspaceAgentId("workspace-assistant-12345"), "assistant-12345");
121
+ assert.equal(
122
+ parseAssistantWorkspaceAgentId("workspace-assistant-12342325232333223223"),
123
+ "assistant-12342325232333223223"
124
+ );
125
+ assert.equal(parseAssistantWorkspaceAgentId("workspace-assistant-000001"), "assistant-000001");
126
+ });
127
+
128
+ it("rejects short, non-numeric, and malformed workspace names", () => {
129
+ assert.equal(parseAssistantWorkspaceAgentId("workspace-assistant-1234"), undefined);
130
+ assert.equal(parseAssistantWorkspaceAgentId("workspace-assistant-1234a"), undefined);
131
+ assert.equal(parseAssistantWorkspaceAgentId("workspace-assistant-abcde"), undefined);
132
+ assert.equal(parseAssistantWorkspaceAgentId("workspace-coder-12345"), undefined);
133
+ });
134
+
135
+ it("normalizes command userId using the same numeric suffix rule", () => {
136
+ assert.equal(normalizeAssistantUserId("12345"), "12345");
137
+ assert.equal(normalizeAssistantUserId("assistant-12345"), "12345");
138
+ assert.equal(normalizeAssistantUserId("assistant-000001"), "000001");
139
+ });
140
+
141
+ it("rejects unsafe or non-conforming command userId values", () => {
142
+ assert.equal(normalizeAssistantUserId("1234"), undefined);
143
+ assert.equal(normalizeAssistantUserId("assistant-1234"), undefined);
144
+ assert.equal(normalizeAssistantUserId("assistant-1234a"), undefined);
145
+ assert.equal(normalizeAssistantUserId("../assistant-12345"), undefined);
146
+ });
147
+ });
148
+
149
+ describe("built-in template update boundary", () => {
150
+ it("only syncs the template for an explicit built-in UPDATE_SKILL command", () => {
151
+ assert.equal(shouldSyncBuiltInTemplate("UPDATE_SKILL", true), true);
152
+ assert.equal(shouldSyncBuiltInTemplate("UPDATE_SKILL", false), false);
153
+ assert.equal(shouldSyncBuiltInTemplate("UPDATE_SKILL", undefined), false);
154
+ assert.equal(shouldSyncBuiltInTemplate("INSTALL_SKILL", true), false);
155
+ assert.equal(shouldSyncBuiltInTemplate("UNINSTALL_SKILL", true), false);
156
+ });
157
+ });
158
+
159
+ describe("expert skill update target", () => {
160
+ it("返回 .user/skills 父目录,由安装器统一追加 skill code", async () => {
161
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-expert-skill-"));
162
+ try {
163
+ const skillsRoot = path.join(root, "workspace-assistant-12345", ".user", "skills");
164
+ fs.mkdirSync(path.join(skillsRoot, "demo"), { recursive: true });
165
+
166
+ assert.equal(await findInstalledExpertSkillsRoot(root, "12345", "demo"), skillsRoot);
167
+ assert.equal(await findInstalledExpertSkillsRoot(root, "12345", "missing"), undefined);
168
+ } finally {
169
+ fs.rmSync(root, { recursive: true, force: true });
170
+ }
171
+ });
172
+ });
173
+
174
+ describe("cron job sqlite lookup", () => {
175
+ it("returns all cron_jobs rows for the requested agent_id", () => {
176
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-cron-jobs-"));
177
+ const dbPath = path.join(dir, "openclaw.sqlite");
178
+ const db = new DatabaseSync(dbPath);
179
+ try {
180
+ db.exec(`
181
+ CREATE TABLE cron_jobs (
182
+ id INTEGER PRIMARY KEY,
183
+ agent_id TEXT NOT NULL,
184
+ name TEXT NOT NULL,
185
+ cron TEXT NOT NULL
186
+ );
187
+ `);
188
+ db.prepare("INSERT INTO cron_jobs (agent_id, name, cron) VALUES (?, ?, ?)").run(
189
+ "assistant-1579723293989240833",
190
+ "morning",
191
+ "0 9 * * *"
192
+ );
193
+ db.prepare("INSERT INTO cron_jobs (agent_id, name, cron) VALUES (?, ?, ?)").run(
194
+ "assistant-1579723293989240833",
195
+ "evening",
196
+ "0 18 * * *"
197
+ );
198
+ db.prepare("INSERT INTO cron_jobs (agent_id, name, cron) VALUES (?, ?, ?)").run(
199
+ "assistant-000001",
200
+ "other",
201
+ "0 12 * * *"
202
+ );
203
+ } finally {
204
+ db.close();
205
+ }
206
+
207
+ const rows = readCronJobsByAgentId("assistant-1579723293989240833", dbPath);
208
+
209
+ assert.equal(rows.length, 2);
210
+ assert.deepEqual(
211
+ rows.map((row) => row.name),
212
+ ["morning", "evening"]
213
+ );
214
+ });
215
+
216
+ it("returns an empty list when no cron_jobs row matches", () => {
217
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-cron-jobs-"));
218
+ const dbPath = path.join(dir, "openclaw.sqlite");
219
+ const db = new DatabaseSync(dbPath);
220
+ try {
221
+ db.exec("CREATE TABLE cron_jobs (id INTEGER PRIMARY KEY, agent_id TEXT NOT NULL);");
222
+ } finally {
223
+ db.close();
224
+ }
225
+
226
+ assert.deepEqual(readCronJobsByAgentId("assistant-1579723293989240833", dbPath), []);
227
+ });
228
+
229
+ it("returns an empty list when the sqlite file is not readable", () => {
230
+ const missingPath = path.join(os.tmpdir(), `missing-openclaw-${Date.now()}.sqlite`);
231
+
232
+ assert.deepEqual(readCronJobsByAgentId("assistant-1579723293989240833", missingPath), []);
233
+ });
234
+
235
+ it("returns an empty list when cron_jobs table does not exist", () => {
236
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-cron-jobs-"));
237
+ const dbPath = path.join(dir, "openclaw.sqlite");
238
+ const db = new DatabaseSync(dbPath);
239
+ try {
240
+ db.exec("CREATE TABLE other_table (id INTEGER PRIMARY KEY, agent_id TEXT NOT NULL);");
241
+ } finally {
242
+ db.close();
243
+ }
244
+
245
+ assert.deepEqual(readCronJobsByAgentId("assistant-1579723293989240833", dbPath), []);
246
+ });
247
+ });