@spzhongwin/skill-logger-plugin 1.0.12 → 1.0.14
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/dist/index.js +74 -9
- package/package.json +1 -1
- package/src/updater.test.ts +12 -1
- package/src/updater.ts +37 -6
- package/src/ws-client.test.ts +80 -0
- package/src/ws-client.ts +55 -3
package/dist/index.js
CHANGED
|
@@ -106,7 +106,6 @@ import fsSync from "node:fs";
|
|
|
106
106
|
import path3 from "node:path";
|
|
107
107
|
import os2 from "node:os";
|
|
108
108
|
import { execFile } from "node:child_process";
|
|
109
|
-
import { promisify } from "node:util";
|
|
110
109
|
import { randomUUID, createHash } from "node:crypto";
|
|
111
110
|
|
|
112
111
|
// src/skill-version.ts
|
|
@@ -139,14 +138,37 @@ function parseSkillVersion(content) {
|
|
|
139
138
|
}
|
|
140
139
|
|
|
141
140
|
// src/updater.ts
|
|
142
|
-
var
|
|
141
|
+
var EXTRACT_TIMEOUT_MS = 3e4;
|
|
143
142
|
var ATTEMPT_COOLDOWN_MS = 30 * 60 * 1e3;
|
|
144
143
|
function skillIdentityHash(code, version) {
|
|
145
144
|
return createHash("sha256").update(`${code} ${code} ${version}`).digest("hex");
|
|
146
145
|
}
|
|
146
|
+
function extractorCommandForPlatform(platform, zipPath, destDir) {
|
|
147
|
+
return platform === "darwin" ? { command: "ditto", args: ["-x", "-k", zipPath, destDir] } : { command: "unzip", args: ["-o", "-q", zipPath, "-d", destDir] };
|
|
148
|
+
}
|
|
149
|
+
function runExtractor(command, args) {
|
|
150
|
+
return new Promise((resolve, reject) => {
|
|
151
|
+
const child = execFile(command, args, {
|
|
152
|
+
encoding: "utf8",
|
|
153
|
+
timeout: EXTRACT_TIMEOUT_MS,
|
|
154
|
+
killSignal: "SIGKILL",
|
|
155
|
+
maxBuffer: 1024 * 1024
|
|
156
|
+
}, (error, _stdout, stderr) => {
|
|
157
|
+
if (!error) {
|
|
158
|
+
resolve();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const detail = String(stderr || "").trim();
|
|
162
|
+
const timeout = error.killed ? `\uFF0C\u8D85\u8FC7 ${EXTRACT_TIMEOUT_MS}ms \u5DF2\u7EC8\u6B62` : "";
|
|
163
|
+
reject(new Error(`${command} \u89E3\u538B\u5931\u8D25${timeout}: ${detail || error.message}`));
|
|
164
|
+
});
|
|
165
|
+
child.stdin?.end();
|
|
166
|
+
});
|
|
167
|
+
}
|
|
147
168
|
async function systemUnzip(zipPath, destDir) {
|
|
148
169
|
await fs3.mkdir(destDir, { recursive: true });
|
|
149
|
-
|
|
170
|
+
const { command, args } = extractorCommandForPlatform(process.platform, zipPath, destDir);
|
|
171
|
+
await runExtractor(command, args);
|
|
150
172
|
}
|
|
151
173
|
var SkillUpdater = class {
|
|
152
174
|
getConfig;
|
|
@@ -464,7 +486,7 @@ var SkillUpdater = class {
|
|
|
464
486
|
emit("download.file.written", { bytes: buf.byteLength });
|
|
465
487
|
const staging = path3.join(work, "staging");
|
|
466
488
|
const unzipStartedAt = Date.now();
|
|
467
|
-
emit("unzip.start");
|
|
489
|
+
emit("unzip.start", { extractor: process.platform === "darwin" ? "ditto" : "unzip" });
|
|
468
490
|
await this.unzip(zipPath, staging);
|
|
469
491
|
emit("unzip.completed", { elapsedMs: Date.now() - unzipStartedAt });
|
|
470
492
|
const srcRoot = await this.locateSkillRoot(staging, 0);
|
|
@@ -1354,11 +1376,11 @@ import path6 from "node:path";
|
|
|
1354
1376
|
// src/identity.ts
|
|
1355
1377
|
import os3 from "node:os";
|
|
1356
1378
|
import { execFile as execFile2 } from "node:child_process";
|
|
1357
|
-
import { promisify
|
|
1358
|
-
var
|
|
1379
|
+
import { promisify } from "node:util";
|
|
1380
|
+
var execFileAsync = promisify(execFile2);
|
|
1359
1381
|
async function getGitConfigValue(key) {
|
|
1360
1382
|
try {
|
|
1361
|
-
const { stdout } = await
|
|
1383
|
+
const { stdout } = await execFileAsync("git", ["config", "--global", key]);
|
|
1362
1384
|
return stdout.trim();
|
|
1363
1385
|
} catch {
|
|
1364
1386
|
console.warn(
|
|
@@ -1616,6 +1638,7 @@ var Reporter = class {
|
|
|
1616
1638
|
import WebSocket from "ws";
|
|
1617
1639
|
import path7 from "path";
|
|
1618
1640
|
import fs6 from "fs/promises";
|
|
1641
|
+
import { DatabaseSync } from "node:sqlite";
|
|
1619
1642
|
var HEARTBEAT_INTERVAL_MS = 3e4;
|
|
1620
1643
|
var HEARTBEAT_ACK_TIMEOUT_MS = 75e3;
|
|
1621
1644
|
var AGENT_SCAN_INTERVAL_MS = 3 * 60 * 1e3;
|
|
@@ -1638,6 +1661,21 @@ function normalizeAssistantUserId(userId) {
|
|
|
1638
1661
|
function shouldSyncBuiltInTemplate(action, isBuiltIn) {
|
|
1639
1662
|
return action === "UPDATE_SKILL" && isBuiltIn === true;
|
|
1640
1663
|
}
|
|
1664
|
+
function defaultOpenclawSqlitePath() {
|
|
1665
|
+
return path7.join(openclawHome(), "state", "openclaw.sqlite");
|
|
1666
|
+
}
|
|
1667
|
+
function readCronJobsByAgentId(agentId, sqlitePath = defaultOpenclawSqlitePath(), onError) {
|
|
1668
|
+
let db;
|
|
1669
|
+
try {
|
|
1670
|
+
db = new DatabaseSync(sqlitePath, { readOnly: true });
|
|
1671
|
+
return db.prepare("SELECT * FROM cron_jobs WHERE agent_id = ?").all(agentId);
|
|
1672
|
+
} catch (err) {
|
|
1673
|
+
onError?.(err);
|
|
1674
|
+
return [];
|
|
1675
|
+
} finally {
|
|
1676
|
+
db?.close();
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1641
1679
|
var GatewayWsClient = class {
|
|
1642
1680
|
ws = null;
|
|
1643
1681
|
options;
|
|
@@ -1943,8 +1981,29 @@ var GatewayWsClient = class {
|
|
|
1943
1981
|
isBuiltIn,
|
|
1944
1982
|
hasDirectUrl: Boolean(url)
|
|
1945
1983
|
});
|
|
1946
|
-
if (!action
|
|
1947
|
-
this.appendLogToFile("WARN", "Command", `Message dropped: missing action
|
|
1984
|
+
if (!action) {
|
|
1985
|
+
this.appendLogToFile("WARN", "Command", `Message dropped: missing action`, msg);
|
|
1986
|
+
return;
|
|
1987
|
+
}
|
|
1988
|
+
if (action === "GET_CRON_JOBS_BY_AGENT_ID") {
|
|
1989
|
+
const agentId = typeof msg.agent_id === "string" ? msg.agent_id : msg.agentId;
|
|
1990
|
+
if (!agentId) {
|
|
1991
|
+
this.reply(replyId, { success: false, message: "Missing agent_id parameter", action });
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
try {
|
|
1995
|
+
const data = readCronJobsByAgentId(agentId, defaultOpenclawSqlitePath(), (err) => {
|
|
1996
|
+
this.appendLogToFile("WARN", "Command", `GET_CRON_JOBS_BY_AGENT_ID sqlite lookup skipped`, err);
|
|
1997
|
+
});
|
|
1998
|
+
this.reply(replyId, { success: true, data, action });
|
|
1999
|
+
} catch (err) {
|
|
2000
|
+
this.appendLogToFile("ERROR", "Command", `GET_CRON_JOBS_BY_AGENT_ID threw`, err);
|
|
2001
|
+
this.reply(replyId, { success: false, message: err.message, action });
|
|
2002
|
+
}
|
|
2003
|
+
return;
|
|
2004
|
+
}
|
|
2005
|
+
if (!userId) {
|
|
2006
|
+
this.appendLogToFile("WARN", "Command", `Message dropped: missing userId`, msg);
|
|
1948
2007
|
return;
|
|
1949
2008
|
}
|
|
1950
2009
|
const safeCode = code ? path7.basename(code) : void 0;
|
|
@@ -2066,6 +2125,12 @@ var GatewayWsClient = class {
|
|
|
2066
2125
|
setTimeout(async () => {
|
|
2067
2126
|
try {
|
|
2068
2127
|
const additionalTargetDirs = syncBuiltInTemplate ? [path7.join(openclawHome(), "workspace-xgjk-assistant-template", "skills")] : [];
|
|
2128
|
+
const userSkillDir = path7.join(openclawHome(), `workspace-assistant-${pureId}`, ".user", "skills", safeCode);
|
|
2129
|
+
try {
|
|
2130
|
+
await fs6.stat(userSkillDir);
|
|
2131
|
+
additionalTargetDirs.push(userSkillDir);
|
|
2132
|
+
} catch {
|
|
2133
|
+
}
|
|
2069
2134
|
const result = await this.options.updater.manualInstall({
|
|
2070
2135
|
code: safeCode,
|
|
2071
2136
|
url,
|
package/package.json
CHANGED
package/src/updater.test.ts
CHANGED
|
@@ -4,7 +4,7 @@ import fs from "node:fs/promises";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
|
-
import { SkillUpdater, type OutdatedCopy } from "./updater.ts";
|
|
7
|
+
import { extractorCommandForPlatform, SkillUpdater, type OutdatedCopy } from "./updater.ts";
|
|
8
8
|
import type { PluginConfig } from "./types.ts";
|
|
9
9
|
|
|
10
10
|
/** 与 updater/服务端一致的身份哈希:code code version(空格分隔)。 */
|
|
@@ -72,6 +72,17 @@ const outdatedFor = (target: string): OutdatedCopy[] => [
|
|
|
72
72
|
];
|
|
73
73
|
|
|
74
74
|
describe("SkillUpdater", () => {
|
|
75
|
+
it("macOS 使用 ditto 解压,其他平台保持 unzip", () => {
|
|
76
|
+
assert.deepEqual(extractorCommandForPlatform("darwin", "/tmp/a.zip", "/tmp/out"), {
|
|
77
|
+
command: "ditto",
|
|
78
|
+
args: ["-x", "-k", "/tmp/a.zip", "/tmp/out"],
|
|
79
|
+
});
|
|
80
|
+
assert.deepEqual(extractorCommandForPlatform("linux", "/tmp/a.zip", "/tmp/out"), {
|
|
81
|
+
command: "unzip",
|
|
82
|
+
args: ["-o", "-q", "/tmp/a.zip", "-d", "/tmp/out"],
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
75
86
|
it("手动更新可复用同一次下载,同时覆盖用户与内置模板目录并记录关键阶段", async () => {
|
|
76
87
|
const calls: string[] = [];
|
|
77
88
|
const traces: Array<{ stage: string; data?: Record<string, unknown> }> = [];
|
package/src/updater.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 流程(每个 skill@version 仅下载一次,覆盖其全部 workspace 副本):
|
|
6
6
|
* 1. POST {platformBaseUrl}/skill_package/pull {skillName, version} → { url }
|
|
7
7
|
* 2. GET url → zip 字节 → 写入临时文件
|
|
8
|
-
* 3. 解压到临时 staging
|
|
8
|
+
* 3. 解压到临时 staging 目录(macOS 使用 ditto,其他系统使用 unzip)
|
|
9
9
|
* 4. 在 staging 内定位含 SKILL.md 的目录作为源根(兼容包内带或不带顶层目录)
|
|
10
10
|
* 5. 逐个目标目录「直接覆盖、不备份」:先复制到同级临时目录,再删旧、原子 rename 换入,
|
|
11
11
|
* 避免中途失败留下损坏的半成品;不保留 .bak。
|
|
@@ -20,12 +20,11 @@ import fsSync from "node:fs";
|
|
|
20
20
|
import path from "node:path";
|
|
21
21
|
import os from "node:os";
|
|
22
22
|
import { execFile } from "node:child_process";
|
|
23
|
-
import { promisify } from "node:util";
|
|
24
23
|
import { randomUUID, createHash } from "node:crypto";
|
|
25
24
|
import type { PluginConfig } from "./types.ts";
|
|
26
25
|
import { parseSkillVersion, readSkillVersion } from "./skill-version.ts";
|
|
27
26
|
|
|
28
|
-
const
|
|
27
|
+
const EXTRACT_TIMEOUT_MS = 30_000;
|
|
29
28
|
|
|
30
29
|
/**
|
|
31
30
|
* 同一 skill@version 的更新尝试冷却期。
|
|
@@ -77,10 +76,42 @@ export type UpdaterOptions = {
|
|
|
77
76
|
cooldownStatePath?: string;
|
|
78
77
|
};
|
|
79
78
|
|
|
80
|
-
|
|
79
|
+
export function extractorCommandForPlatform(
|
|
80
|
+
platform: NodeJS.Platform,
|
|
81
|
+
zipPath: string,
|
|
82
|
+
destDir: string,
|
|
83
|
+
): { command: string; args: string[] } {
|
|
84
|
+
return platform === "darwin"
|
|
85
|
+
? { command: "ditto", args: ["-x", "-k", zipPath, destDir] }
|
|
86
|
+
: { command: "unzip", args: ["-o", "-q", zipPath, "-d", destDir] };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function runExtractor(command: string, args: string[]): Promise<void> {
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
const child = execFile(command, args, {
|
|
92
|
+
encoding: "utf8",
|
|
93
|
+
timeout: EXTRACT_TIMEOUT_MS,
|
|
94
|
+
killSignal: "SIGKILL",
|
|
95
|
+
maxBuffer: 1024 * 1024,
|
|
96
|
+
}, (error, _stdout, stderr) => {
|
|
97
|
+
if (!error) {
|
|
98
|
+
resolve();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const detail = String(stderr || "").trim();
|
|
102
|
+
const timeout = error.killed ? `,超过 ${EXTRACT_TIMEOUT_MS}ms 已终止` : "";
|
|
103
|
+
reject(new Error(`${command} 解压失败${timeout}: ${detail || error.message}`));
|
|
104
|
+
});
|
|
105
|
+
// 防止 unzip 遇到异常包时弹出 Continue? 并永久等待输入。
|
|
106
|
+
child.stdin?.end();
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** 默认解压:macOS 的 ditto 正确支持 ZIP 中文文件名;其余平台保持原 unzip 行为。 */
|
|
81
111
|
async function systemUnzip(zipPath: string, destDir: string): Promise<void> {
|
|
82
112
|
await fs.mkdir(destDir, { recursive: true });
|
|
83
|
-
|
|
113
|
+
const { command, args } = extractorCommandForPlatform(process.platform, zipPath, destDir);
|
|
114
|
+
await runExtractor(command, args);
|
|
84
115
|
}
|
|
85
116
|
|
|
86
117
|
export class SkillUpdater {
|
|
@@ -448,7 +479,7 @@ export class SkillUpdater {
|
|
|
448
479
|
// 2. 调用系统解压工具 unzip
|
|
449
480
|
const staging = path.join(work, "staging");
|
|
450
481
|
const unzipStartedAt = Date.now();
|
|
451
|
-
emit("unzip.start");
|
|
482
|
+
emit("unzip.start", { extractor: process.platform === "darwin" ? "ditto" : "unzip" });
|
|
452
483
|
await this.unzip(zipPath, staging);
|
|
453
484
|
emit("unzip.completed", { elapsedMs: Date.now() - unzipStartedAt });
|
|
454
485
|
|
package/src/ws-client.test.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { describe, it } from "node:test";
|
|
2
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";
|
|
3
7
|
import {
|
|
4
8
|
normalizeAssistantUserId,
|
|
5
9
|
parseAssistantWorkspaceAgentId,
|
|
10
|
+
readCronJobsByAgentId,
|
|
6
11
|
shouldSyncBuiltInTemplate,
|
|
7
12
|
} from "./ws-client.ts";
|
|
8
13
|
|
|
@@ -46,3 +51,78 @@ describe("built-in template update boundary", () => {
|
|
|
46
51
|
assert.equal(shouldSyncBuiltInTemplate("UNINSTALL_SKILL", true), false);
|
|
47
52
|
});
|
|
48
53
|
});
|
|
54
|
+
|
|
55
|
+
describe("cron job sqlite lookup", () => {
|
|
56
|
+
it("returns all cron_jobs rows for the requested agent_id", () => {
|
|
57
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-cron-jobs-"));
|
|
58
|
+
const dbPath = path.join(dir, "openclaw.sqlite");
|
|
59
|
+
const db = new DatabaseSync(dbPath);
|
|
60
|
+
try {
|
|
61
|
+
db.exec(`
|
|
62
|
+
CREATE TABLE cron_jobs (
|
|
63
|
+
id INTEGER PRIMARY KEY,
|
|
64
|
+
agent_id TEXT NOT NULL,
|
|
65
|
+
name TEXT NOT NULL,
|
|
66
|
+
cron TEXT NOT NULL
|
|
67
|
+
);
|
|
68
|
+
`);
|
|
69
|
+
db.prepare("INSERT INTO cron_jobs (agent_id, name, cron) VALUES (?, ?, ?)").run(
|
|
70
|
+
"assistant-1579723293989240833",
|
|
71
|
+
"morning",
|
|
72
|
+
"0 9 * * *"
|
|
73
|
+
);
|
|
74
|
+
db.prepare("INSERT INTO cron_jobs (agent_id, name, cron) VALUES (?, ?, ?)").run(
|
|
75
|
+
"assistant-1579723293989240833",
|
|
76
|
+
"evening",
|
|
77
|
+
"0 18 * * *"
|
|
78
|
+
);
|
|
79
|
+
db.prepare("INSERT INTO cron_jobs (agent_id, name, cron) VALUES (?, ?, ?)").run(
|
|
80
|
+
"assistant-000001",
|
|
81
|
+
"other",
|
|
82
|
+
"0 12 * * *"
|
|
83
|
+
);
|
|
84
|
+
} finally {
|
|
85
|
+
db.close();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const rows = readCronJobsByAgentId("assistant-1579723293989240833", dbPath);
|
|
89
|
+
|
|
90
|
+
assert.equal(rows.length, 2);
|
|
91
|
+
assert.deepEqual(
|
|
92
|
+
rows.map((row) => row.name),
|
|
93
|
+
["morning", "evening"]
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("returns an empty list when no cron_jobs row matches", () => {
|
|
98
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-cron-jobs-"));
|
|
99
|
+
const dbPath = path.join(dir, "openclaw.sqlite");
|
|
100
|
+
const db = new DatabaseSync(dbPath);
|
|
101
|
+
try {
|
|
102
|
+
db.exec("CREATE TABLE cron_jobs (id INTEGER PRIMARY KEY, agent_id TEXT NOT NULL);");
|
|
103
|
+
} finally {
|
|
104
|
+
db.close();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
assert.deepEqual(readCronJobsByAgentId("assistant-1579723293989240833", dbPath), []);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("returns an empty list when the sqlite file is not readable", () => {
|
|
111
|
+
const missingPath = path.join(os.tmpdir(), `missing-openclaw-${Date.now()}.sqlite`);
|
|
112
|
+
|
|
113
|
+
assert.deepEqual(readCronJobsByAgentId("assistant-1579723293989240833", missingPath), []);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("returns an empty list when cron_jobs table does not exist", () => {
|
|
117
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-cron-jobs-"));
|
|
118
|
+
const dbPath = path.join(dir, "openclaw.sqlite");
|
|
119
|
+
const db = new DatabaseSync(dbPath);
|
|
120
|
+
try {
|
|
121
|
+
db.exec("CREATE TABLE other_table (id INTEGER PRIMARY KEY, agent_id TEXT NOT NULL);");
|
|
122
|
+
} finally {
|
|
123
|
+
db.close();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
assert.deepEqual(readCronJobsByAgentId("assistant-1579723293989240833", dbPath), []);
|
|
127
|
+
});
|
|
128
|
+
});
|
package/src/ws-client.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import WebSocket from "ws";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import fs from "fs/promises";
|
|
4
|
+
import { DatabaseSync } from "node:sqlite";
|
|
4
5
|
import { SkillUpdater } from "./updater.ts";
|
|
5
6
|
import { openclawHome } from "./paths.ts";
|
|
6
7
|
import { readSkillVersion } from "./skill-version.ts";
|
|
@@ -33,6 +34,27 @@ export function shouldSyncBuiltInTemplate(action: string, isBuiltIn: unknown): b
|
|
|
33
34
|
return action === "UPDATE_SKILL" && isBuiltIn === true;
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
export function defaultOpenclawSqlitePath(): string {
|
|
38
|
+
return path.join(openclawHome(), "state", "openclaw.sqlite");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function readCronJobsByAgentId(
|
|
42
|
+
agentId: string,
|
|
43
|
+
sqlitePath = defaultOpenclawSqlitePath(),
|
|
44
|
+
onError?: (err: unknown) => void
|
|
45
|
+
): any[] {
|
|
46
|
+
let db: DatabaseSync | undefined;
|
|
47
|
+
try {
|
|
48
|
+
db = new DatabaseSync(sqlitePath, { readOnly: true });
|
|
49
|
+
return db.prepare("SELECT * FROM cron_jobs WHERE agent_id = ?").all(agentId);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
onError?.(err);
|
|
52
|
+
return [];
|
|
53
|
+
} finally {
|
|
54
|
+
db?.close();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
36
58
|
export interface WsClientOptions {
|
|
37
59
|
serverUrl: string; // 例如: wss://api.aishuo.co/gateway/ws
|
|
38
60
|
authToken?: string; // 用于网关鉴权
|
|
@@ -396,8 +418,31 @@ export class GatewayWsClient {
|
|
|
396
418
|
hasDirectUrl: Boolean(url),
|
|
397
419
|
});
|
|
398
420
|
|
|
399
|
-
if (!action
|
|
400
|
-
this.appendLogToFile("WARN", "Command", `Message dropped: missing action
|
|
421
|
+
if (!action) {
|
|
422
|
+
this.appendLogToFile("WARN", "Command", `Message dropped: missing action`, msg);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (action === "GET_CRON_JOBS_BY_AGENT_ID") {
|
|
427
|
+
const agentId = typeof msg.agent_id === "string" ? msg.agent_id : msg.agentId;
|
|
428
|
+
if (!agentId) {
|
|
429
|
+
this.reply(replyId, { success: false, message: "Missing agent_id parameter", action });
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
try {
|
|
433
|
+
const data = readCronJobsByAgentId(agentId, defaultOpenclawSqlitePath(), (err) => {
|
|
434
|
+
this.appendLogToFile("WARN", "Command", `GET_CRON_JOBS_BY_AGENT_ID sqlite lookup skipped`, err);
|
|
435
|
+
});
|
|
436
|
+
this.reply(replyId, { success: true, data, action });
|
|
437
|
+
} catch (err: any) {
|
|
438
|
+
this.appendLogToFile("ERROR", "Command", `GET_CRON_JOBS_BY_AGENT_ID threw`, err);
|
|
439
|
+
this.reply(replyId, { success: false, message: err.message, action });
|
|
440
|
+
}
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (!userId) {
|
|
445
|
+
this.appendLogToFile("WARN", "Command", `Message dropped: missing userId`, msg);
|
|
401
446
|
return;
|
|
402
447
|
}
|
|
403
448
|
|
|
@@ -536,9 +581,16 @@ export class GatewayWsClient {
|
|
|
536
581
|
|
|
537
582
|
setTimeout(async () => {
|
|
538
583
|
try {
|
|
539
|
-
const additionalTargetDirs = syncBuiltInTemplate
|
|
584
|
+
const additionalTargetDirs: string[] = syncBuiltInTemplate
|
|
540
585
|
? [path.join(openclawHome(), "workspace-xgjk-assistant-template", "skills")]
|
|
541
586
|
: [];
|
|
587
|
+
|
|
588
|
+
// 同步更新用户的 expert skill 目录
|
|
589
|
+
const userSkillDir = path.join(openclawHome(), `workspace-assistant-${pureId}`, ".user", "skills", safeCode);
|
|
590
|
+
try {
|
|
591
|
+
await fs.stat(userSkillDir);
|
|
592
|
+
additionalTargetDirs.push(userSkillDir);
|
|
593
|
+
} catch {} // 目录不存在则跳过
|
|
542
594
|
const result = await this.options.updater.manualInstall({
|
|
543
595
|
code: safeCode,
|
|
544
596
|
url,
|