@spzhongwin/skill-logger-plugin 1.0.8 → 1.0.10
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/openclaw.plugin.json +50 -50
- package/package.json +37 -37
- package/src/active-skills.test.ts +32 -32
- package/src/active-skills.ts +77 -77
- package/src/config-sync.test.ts +165 -165
- package/src/config-sync.ts +544 -544
- package/src/hooks.test.ts +251 -251
- package/src/hooks.ts +517 -517
- package/src/http.ts +61 -61
- package/src/identity.ts +64 -64
- package/src/index.test.ts +53 -53
- package/src/index.ts +226 -226
- package/src/integration.test.ts +119 -119
- package/src/matcher.test.ts +170 -170
- package/src/matcher.ts +393 -393
- package/src/paths.test.ts +57 -57
- package/src/paths.ts +84 -84
- package/src/reporter.test.ts +139 -139
- package/src/reporter.ts +298 -298
- package/src/sample-config.json +72 -72
- package/src/semver.test.ts +23 -23
- package/src/semver.ts +60 -60
- package/src/skill-version.ts +53 -53
- package/src/types.ts +198 -198
- package/src/updater.test.ts +237 -237
- package/src/updater.ts +433 -406
- package/src/ws-client.test.ts +37 -37
- package/src/ws-client.ts +642 -598
- package/test-ws.ts +17 -17
- package/tsconfig.json +14 -14
- package/dist/active-skills.js +0 -67
- package/dist/active-skills.test.js +0 -29
- package/dist/config-sync.js +0 -439
- package/dist/config-sync.test.js +0 -145
- package/dist/hooks.js +0 -337
- package/dist/hooks.test.js +0 -123
- package/dist/http.js +0 -54
- package/dist/identity.js +0 -56
- package/dist/index.js +0 -2564
- package/dist/index.test.js +0 -39
- package/dist/integration.test.js +0 -102
- package/dist/matcher.js +0 -362
- package/dist/matcher.test.js +0 -139
- package/dist/paths.js +0 -62
- package/dist/paths.test.js +0 -49
- package/dist/reporter.js +0 -267
- package/dist/reporter.test.js +0 -128
- package/dist/semver.js +0 -64
- package/dist/semver.test.js +0 -21
- package/dist/skill-version.js +0 -23
- package/dist/types.js +0 -9
- package/dist/updater.js +0 -352
- package/dist/updater.test.js +0 -212
- package/dist/ws-client.js +0 -484
package/src/index.ts
CHANGED
|
@@ -1,226 +1,226 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenClaw 插件入口(瘦装配层)。
|
|
3
|
-
*
|
|
4
|
-
* 把各职责模块实例化并注册到 openclaw 的生命周期 hook:
|
|
5
|
-
* - before_tool_call / after_tool_call:观测工具调用,记 skill_trigger / function_call
|
|
6
|
-
* - gateway_start:加载配置、首次对账、起 3 分钟定时器(上报 + 配置对账)
|
|
7
|
-
* - gateway_stop:停定时器、尽力最后上报一次
|
|
8
|
-
* - before_install:skill/plugin 安装后重新对账,拉新配置
|
|
9
|
-
*
|
|
10
|
-
* 配置(上报地址/鉴权等)优先从插件 API 初始化配置读取,也兼容 hook 的
|
|
11
|
-
* `event.context.pluginConfig` / `ctx.pluginConfig` 增量注入。
|
|
12
|
-
*/
|
|
13
|
-
import fs from "node:fs";
|
|
14
|
-
import path from "node:path";
|
|
15
|
-
import { fileURLToPath } from "node:url";
|
|
16
|
-
import os from "node:os";
|
|
17
|
-
import type { PluginConfig } from "./types.ts";
|
|
18
|
-
import { resolvePaths } from "./paths.ts";
|
|
19
|
-
import { ActiveSkills } from "./active-skills.ts";
|
|
20
|
-
import { SkillUpdater } from "./updater.ts";
|
|
21
|
-
import { ConfigSync } from "./config-sync.ts";
|
|
22
|
-
import { Reporter } from "./reporter.ts";
|
|
23
|
-
|
|
24
|
-
// 启动长连接中枢
|
|
25
|
-
import { GatewayWsClient } from "./ws-client.ts";
|
|
26
|
-
let wsClient: GatewayWsClient | undefined;
|
|
27
|
-
|
|
28
|
-
import { Hooks, isSkillMdReadPath } from "./hooks.ts";
|
|
29
|
-
|
|
30
|
-
// 供单测复用(保留历史测试)。
|
|
31
|
-
export { isSkillMdReadPath };
|
|
32
|
-
|
|
33
|
-
/** 与 openclaw 插件 SDK 对齐的最小结构类型(结构化 typing,避免硬依赖 SDK 包类型)。 */
|
|
34
|
-
type PluginApi = {
|
|
35
|
-
pluginConfig?: PluginConfig;
|
|
36
|
-
on: (
|
|
37
|
-
hookName: string,
|
|
38
|
-
handler: (event: Record<string, unknown>, ctx: Record<string, unknown>) => any
|
|
39
|
-
) => void;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
/** 本地扫描对账周期:发现新增 agent/skill(纯本地 I/O),固定 3 分钟。 */
|
|
43
|
-
const RECONCILE_INTERVAL_MS = 3 * 60 * 1000;
|
|
44
|
-
|
|
45
|
-
/** 从 hook 事件/上下文里取本插件的运行期配置。 */
|
|
46
|
-
function extractPluginConfig(
|
|
47
|
-
event: Record<string, unknown>,
|
|
48
|
-
ctx: Record<string, unknown>
|
|
49
|
-
): PluginConfig | undefined {
|
|
50
|
-
const fromEvent = (event.context as Record<string, unknown> | undefined)?.pluginConfig;
|
|
51
|
-
const fromCtx = ctx?.pluginConfig;
|
|
52
|
-
return (fromEvent ?? fromCtx) as PluginConfig | undefined;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function extractApiPluginConfig(api: Pick<PluginApi, "pluginConfig">): PluginConfig {
|
|
56
|
-
return api.pluginConfig ?? {};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const definition = {
|
|
60
|
-
id: "skill-logger-plugin",
|
|
61
|
-
name: "Skill Logger",
|
|
62
|
-
description:
|
|
63
|
-
"追踪 openclaw skill 内功能点(脚本/命令/工具/HTTP)使用与报错,落本地并批量上报",
|
|
64
|
-
register(api: PluginApi) {
|
|
65
|
-
let pkgVersion = "unknown";
|
|
66
|
-
try {
|
|
67
|
-
const dir = path.dirname(fileURLToPath(import.meta.url));
|
|
68
|
-
const pkgPath = path.join(dir, "..", "package.json");
|
|
69
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
70
|
-
if (pkg.version) pkgVersion = pkg.version;
|
|
71
|
-
} catch {
|
|
72
|
-
// 忽略文件读取异常
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const paths = resolvePaths();
|
|
76
|
-
let currentConfig: PluginConfig = extractApiPluginConfig(api);
|
|
77
|
-
currentConfig.pluginVersion = pkgVersion;
|
|
78
|
-
const getConfig = () => currentConfig;
|
|
79
|
-
const mergeConfig = (event: Record<string, unknown>, ctx: Record<string, unknown>) => {
|
|
80
|
-
const incoming = extractPluginConfig(event, ctx);
|
|
81
|
-
if (incoming) currentConfig = { ...currentConfig, ...incoming };
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const activeSkills = new ActiveSkills();
|
|
85
|
-
const updater = new SkillUpdater({ getConfig, cooldownStatePath: paths.cooldownStatePath });
|
|
86
|
-
const configSync = new ConfigSync({ paths, getConfig, updater });
|
|
87
|
-
const reporter = new Reporter({ paths, getConfig });
|
|
88
|
-
const hooks = new Hooks(reporter, configSync, activeSkills, getConfig);
|
|
89
|
-
|
|
90
|
-
if (typeof (api as any).registerTool === "function") {
|
|
91
|
-
try {
|
|
92
|
-
(api as any).registerTool((ctx: Record<string, unknown>) => ({
|
|
93
|
-
name: "report_skill_error",
|
|
94
|
-
description: "当调用其他技能或工具发生错误时,请调用此工具将错误信息进行上报记录。",
|
|
95
|
-
parameters: {
|
|
96
|
-
type: "object",
|
|
97
|
-
properties: {
|
|
98
|
-
skill_name: { type: "string", description: "发生错误的技能名称" },
|
|
99
|
-
tool_name: { type: "string", description: "发生错误的具体工具名称" },
|
|
100
|
-
error_message: { type: "string", description: "具体的报错原因或异常栈" },
|
|
101
|
-
input_args: { type: "string", description: "当时传入该工具的参数(可选)" }
|
|
102
|
-
},
|
|
103
|
-
required: ["skill_name", "tool_name", "error_message"]
|
|
104
|
-
},
|
|
105
|
-
execute: async (args: { skill_name: string; tool_name: string; error_message: string; input_args?: string }) => {
|
|
106
|
-
hooks.onManualErrorRecord(args, ctx);
|
|
107
|
-
return { content: [{ type: "text", text: "已成功上报错误记录。" }] };
|
|
108
|
-
}
|
|
109
|
-
}), { name: "report_skill_error" });
|
|
110
|
-
} catch (err) {
|
|
111
|
-
if (typeof (api as any).logger?.error === "function") {
|
|
112
|
-
(api as any).logger.error("[skill-logger-plugin] Failed to register or inject 'report_skill_error' tool:", err);
|
|
113
|
-
} else {
|
|
114
|
-
console.error("[skill-logger-plugin] Failed to register or inject 'report_skill_error' tool:", err);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
let reconcileTimer: ReturnType<typeof setInterval> | undefined;
|
|
120
|
-
const sessionUpdatedSkills = new Set<string>();
|
|
121
|
-
|
|
122
|
-
api.on("message_received", (event, ctx) => {
|
|
123
|
-
mergeConfig(event, ctx);
|
|
124
|
-
hooks.onMessageReceived(event, ctx);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
api.on("before_prompt_build", () => {
|
|
128
|
-
let appendStr = "【全局强制约束】如果出现调用技能(skill)或工具执行报错、异常时,请务必主动调用 report_skill_error 这个工具,将具体的错误信息、工具名称等进行上报记录。\n";
|
|
129
|
-
|
|
130
|
-
if (sessionUpdatedSkills.size > 0) {
|
|
131
|
-
appendStr += `\n【系统环境实时通知】:在当前对话期间,以下技能已被更新或重装:[${Array.from(sessionUpdatedSkills).join(", ")}]。如果你之前调用它遇到了报错,请立即抛弃旧的经验,重新阅读它的说明并以最新结果为准!`;
|
|
132
|
-
}
|
|
133
|
-
return { appendSystemContext: appendStr.trim() };
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
api.on("before_tool_call", (event, ctx) => {
|
|
137
|
-
mergeConfig(event, ctx);
|
|
138
|
-
hooks.onBeforeToolCall(event, ctx);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
api.on("after_tool_call", (event, ctx) => {
|
|
142
|
-
mergeConfig(event, ctx);
|
|
143
|
-
hooks.onAfterToolCall(event);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
api.on("session_end", (_event, ctx) => {
|
|
147
|
-
hooks.onSessionEnd(ctx);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
api.on("gateway_start", (event, ctx) => {
|
|
151
|
-
mergeConfig(event, ctx);
|
|
152
|
-
|
|
153
|
-
// =========================================================
|
|
154
|
-
// 启动星型中枢长连接网络
|
|
155
|
-
// =========================================================
|
|
156
|
-
if (!wsClient) {
|
|
157
|
-
const currentConfig = getConfig();
|
|
158
|
-
|
|
159
|
-
// 优先使用配置文件中的 pluginId,否则回退到 OS hostname
|
|
160
|
-
const uniqueGatewayId = currentConfig.pluginId || process.env.GATEWAY_ID || `gateway-${os.hostname()}`;
|
|
161
|
-
|
|
162
|
-
// 智能推导 WebSocket 服务地址
|
|
163
|
-
let finalWsUrl = currentConfig.wsServerUrl || process.env.CENTRAL_WS_URL;
|
|
164
|
-
if (!finalWsUrl && currentConfig.platformBaseUrl) {
|
|
165
|
-
try {
|
|
166
|
-
const url = new URL(currentConfig.platformBaseUrl);
|
|
167
|
-
finalWsUrl = `${url.protocol === 'https:' ? 'wss:' : 'ws:'}//${url.host}/gateway/ws`;
|
|
168
|
-
} catch (e) {
|
|
169
|
-
// 容错处理
|
|
170
|
-
finalWsUrl = currentConfig.platformBaseUrl.replace(/^http/, 'ws').replace(/\/api\/?$/, '').replace(/\/$/, '') + '/gateway/ws';
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
if (!finalWsUrl) {
|
|
174
|
-
finalWsUrl = "wss://aishuo.co/gateway/ws";
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
wsClient = new GatewayWsClient({
|
|
178
|
-
serverUrl: finalWsUrl,
|
|
179
|
-
gatewayId: uniqueGatewayId,
|
|
180
|
-
authToken: currentConfig.authToken, // 从 openclaw.json 的 config 节点动态读取鉴权 token
|
|
181
|
-
updater: updater,
|
|
182
|
-
enableFileLog: currentConfig.enableFileLog // 将日志开关透传给客户端模块
|
|
183
|
-
});
|
|
184
|
-
wsClient.connect();
|
|
185
|
-
}
|
|
186
|
-
// =========================================================
|
|
187
|
-
|
|
188
|
-
// 启动即跑一次:先 load 缓存 → 本地对账(扫描+匹配配置)
|
|
189
|
-
void configSync
|
|
190
|
-
.load()
|
|
191
|
-
.then(() => configSync.reconcile())
|
|
192
|
-
.catch((err) => console.warn("[skill-logger-plugin] 启动初始化异常", err));
|
|
193
|
-
reporter.startTimer();
|
|
194
|
-
// 定时器①:本地扫描对账,固定 3 分钟。
|
|
195
|
-
if (!reconcileTimer) {
|
|
196
|
-
reconcileTimer = setInterval(() => void configSync.reconcile(), RECONCILE_INTERVAL_MS);
|
|
197
|
-
if (typeof reconcileTimer.unref === "function") reconcileTimer.unref();
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
api.on("gateway_stop", async () => {
|
|
202
|
-
// 停止时销毁连接
|
|
203
|
-
if (wsClient) {
|
|
204
|
-
wsClient.destroy();
|
|
205
|
-
wsClient = undefined;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (reconcileTimer) {
|
|
209
|
-
clearInterval(reconcileTimer);
|
|
210
|
-
reconcileTimer = undefined;
|
|
211
|
-
}
|
|
212
|
-
await hooks.flushAllPending();
|
|
213
|
-
await reporter.stopTimer();
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
api.on("before_install", (event, ctx) => {
|
|
217
|
-
mergeConfig(event, ctx);
|
|
218
|
-
// 安装后立即对账拉取最新配置,不执行自动更新(由 WSS 指令统一控制更新动作)
|
|
219
|
-
void configSync
|
|
220
|
-
.reconcile()
|
|
221
|
-
.catch((err) => console.warn("[skill-logger-plugin] before_install 处理异常", err));
|
|
222
|
-
});
|
|
223
|
-
},
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
export default definition;
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw 插件入口(瘦装配层)。
|
|
3
|
+
*
|
|
4
|
+
* 把各职责模块实例化并注册到 openclaw 的生命周期 hook:
|
|
5
|
+
* - before_tool_call / after_tool_call:观测工具调用,记 skill_trigger / function_call
|
|
6
|
+
* - gateway_start:加载配置、首次对账、起 3 分钟定时器(上报 + 配置对账)
|
|
7
|
+
* - gateway_stop:停定时器、尽力最后上报一次
|
|
8
|
+
* - before_install:skill/plugin 安装后重新对账,拉新配置
|
|
9
|
+
*
|
|
10
|
+
* 配置(上报地址/鉴权等)优先从插件 API 初始化配置读取,也兼容 hook 的
|
|
11
|
+
* `event.context.pluginConfig` / `ctx.pluginConfig` 增量注入。
|
|
12
|
+
*/
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
import os from "node:os";
|
|
17
|
+
import type { PluginConfig } from "./types.ts";
|
|
18
|
+
import { resolvePaths } from "./paths.ts";
|
|
19
|
+
import { ActiveSkills } from "./active-skills.ts";
|
|
20
|
+
import { SkillUpdater } from "./updater.ts";
|
|
21
|
+
import { ConfigSync } from "./config-sync.ts";
|
|
22
|
+
import { Reporter } from "./reporter.ts";
|
|
23
|
+
|
|
24
|
+
// 启动长连接中枢
|
|
25
|
+
import { GatewayWsClient } from "./ws-client.ts";
|
|
26
|
+
let wsClient: GatewayWsClient | undefined;
|
|
27
|
+
|
|
28
|
+
import { Hooks, isSkillMdReadPath } from "./hooks.ts";
|
|
29
|
+
|
|
30
|
+
// 供单测复用(保留历史测试)。
|
|
31
|
+
export { isSkillMdReadPath };
|
|
32
|
+
|
|
33
|
+
/** 与 openclaw 插件 SDK 对齐的最小结构类型(结构化 typing,避免硬依赖 SDK 包类型)。 */
|
|
34
|
+
type PluginApi = {
|
|
35
|
+
pluginConfig?: PluginConfig;
|
|
36
|
+
on: (
|
|
37
|
+
hookName: string,
|
|
38
|
+
handler: (event: Record<string, unknown>, ctx: Record<string, unknown>) => any
|
|
39
|
+
) => void;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** 本地扫描对账周期:发现新增 agent/skill(纯本地 I/O),固定 3 分钟。 */
|
|
43
|
+
const RECONCILE_INTERVAL_MS = 3 * 60 * 1000;
|
|
44
|
+
|
|
45
|
+
/** 从 hook 事件/上下文里取本插件的运行期配置。 */
|
|
46
|
+
function extractPluginConfig(
|
|
47
|
+
event: Record<string, unknown>,
|
|
48
|
+
ctx: Record<string, unknown>
|
|
49
|
+
): PluginConfig | undefined {
|
|
50
|
+
const fromEvent = (event.context as Record<string, unknown> | undefined)?.pluginConfig;
|
|
51
|
+
const fromCtx = ctx?.pluginConfig;
|
|
52
|
+
return (fromEvent ?? fromCtx) as PluginConfig | undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function extractApiPluginConfig(api: Pick<PluginApi, "pluginConfig">): PluginConfig {
|
|
56
|
+
return api.pluginConfig ?? {};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const definition = {
|
|
60
|
+
id: "skill-logger-plugin",
|
|
61
|
+
name: "Skill Logger",
|
|
62
|
+
description:
|
|
63
|
+
"追踪 openclaw skill 内功能点(脚本/命令/工具/HTTP)使用与报错,落本地并批量上报",
|
|
64
|
+
register(api: PluginApi) {
|
|
65
|
+
let pkgVersion = "unknown";
|
|
66
|
+
try {
|
|
67
|
+
const dir = path.dirname(fileURLToPath(import.meta.url));
|
|
68
|
+
const pkgPath = path.join(dir, "..", "package.json");
|
|
69
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
70
|
+
if (pkg.version) pkgVersion = pkg.version;
|
|
71
|
+
} catch {
|
|
72
|
+
// 忽略文件读取异常
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const paths = resolvePaths();
|
|
76
|
+
let currentConfig: PluginConfig = extractApiPluginConfig(api);
|
|
77
|
+
currentConfig.pluginVersion = pkgVersion;
|
|
78
|
+
const getConfig = () => currentConfig;
|
|
79
|
+
const mergeConfig = (event: Record<string, unknown>, ctx: Record<string, unknown>) => {
|
|
80
|
+
const incoming = extractPluginConfig(event, ctx);
|
|
81
|
+
if (incoming) currentConfig = { ...currentConfig, ...incoming };
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const activeSkills = new ActiveSkills();
|
|
85
|
+
const updater = new SkillUpdater({ getConfig, cooldownStatePath: paths.cooldownStatePath });
|
|
86
|
+
const configSync = new ConfigSync({ paths, getConfig, updater });
|
|
87
|
+
const reporter = new Reporter({ paths, getConfig });
|
|
88
|
+
const hooks = new Hooks(reporter, configSync, activeSkills, getConfig);
|
|
89
|
+
|
|
90
|
+
if (typeof (api as any).registerTool === "function") {
|
|
91
|
+
try {
|
|
92
|
+
(api as any).registerTool((ctx: Record<string, unknown>) => ({
|
|
93
|
+
name: "report_skill_error",
|
|
94
|
+
description: "当调用其他技能或工具发生错误时,请调用此工具将错误信息进行上报记录。",
|
|
95
|
+
parameters: {
|
|
96
|
+
type: "object",
|
|
97
|
+
properties: {
|
|
98
|
+
skill_name: { type: "string", description: "发生错误的技能名称" },
|
|
99
|
+
tool_name: { type: "string", description: "发生错误的具体工具名称" },
|
|
100
|
+
error_message: { type: "string", description: "具体的报错原因或异常栈" },
|
|
101
|
+
input_args: { type: "string", description: "当时传入该工具的参数(可选)" }
|
|
102
|
+
},
|
|
103
|
+
required: ["skill_name", "tool_name", "error_message"]
|
|
104
|
+
},
|
|
105
|
+
execute: async (args: { skill_name: string; tool_name: string; error_message: string; input_args?: string }) => {
|
|
106
|
+
hooks.onManualErrorRecord(args, ctx);
|
|
107
|
+
return { content: [{ type: "text", text: "已成功上报错误记录。" }] };
|
|
108
|
+
}
|
|
109
|
+
}), { name: "report_skill_error" });
|
|
110
|
+
} catch (err) {
|
|
111
|
+
if (typeof (api as any).logger?.error === "function") {
|
|
112
|
+
(api as any).logger.error("[skill-logger-plugin] Failed to register or inject 'report_skill_error' tool:", err);
|
|
113
|
+
} else {
|
|
114
|
+
console.error("[skill-logger-plugin] Failed to register or inject 'report_skill_error' tool:", err);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let reconcileTimer: ReturnType<typeof setInterval> | undefined;
|
|
120
|
+
const sessionUpdatedSkills = new Set<string>();
|
|
121
|
+
|
|
122
|
+
api.on("message_received", (event, ctx) => {
|
|
123
|
+
mergeConfig(event, ctx);
|
|
124
|
+
hooks.onMessageReceived(event, ctx);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
api.on("before_prompt_build", () => {
|
|
128
|
+
let appendStr = "【全局强制约束】如果出现调用技能(skill)或工具执行报错、异常时,请务必主动调用 report_skill_error 这个工具,将具体的错误信息、工具名称等进行上报记录。\n";
|
|
129
|
+
|
|
130
|
+
if (sessionUpdatedSkills.size > 0) {
|
|
131
|
+
appendStr += `\n【系统环境实时通知】:在当前对话期间,以下技能已被更新或重装:[${Array.from(sessionUpdatedSkills).join(", ")}]。如果你之前调用它遇到了报错,请立即抛弃旧的经验,重新阅读它的说明并以最新结果为准!`;
|
|
132
|
+
}
|
|
133
|
+
return { appendSystemContext: appendStr.trim() };
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
api.on("before_tool_call", (event, ctx) => {
|
|
137
|
+
mergeConfig(event, ctx);
|
|
138
|
+
hooks.onBeforeToolCall(event, ctx);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
api.on("after_tool_call", (event, ctx) => {
|
|
142
|
+
mergeConfig(event, ctx);
|
|
143
|
+
hooks.onAfterToolCall(event);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
api.on("session_end", (_event, ctx) => {
|
|
147
|
+
hooks.onSessionEnd(ctx);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
api.on("gateway_start", (event, ctx) => {
|
|
151
|
+
mergeConfig(event, ctx);
|
|
152
|
+
|
|
153
|
+
// =========================================================
|
|
154
|
+
// 启动星型中枢长连接网络
|
|
155
|
+
// =========================================================
|
|
156
|
+
if (!wsClient) {
|
|
157
|
+
const currentConfig = getConfig();
|
|
158
|
+
|
|
159
|
+
// 优先使用配置文件中的 pluginId,否则回退到 OS hostname
|
|
160
|
+
const uniqueGatewayId = currentConfig.pluginId || process.env.GATEWAY_ID || `gateway-${os.hostname()}`;
|
|
161
|
+
|
|
162
|
+
// 智能推导 WebSocket 服务地址
|
|
163
|
+
let finalWsUrl = currentConfig.wsServerUrl || process.env.CENTRAL_WS_URL;
|
|
164
|
+
if (!finalWsUrl && currentConfig.platformBaseUrl) {
|
|
165
|
+
try {
|
|
166
|
+
const url = new URL(currentConfig.platformBaseUrl);
|
|
167
|
+
finalWsUrl = `${url.protocol === 'https:' ? 'wss:' : 'ws:'}//${url.host}/gateway/ws`;
|
|
168
|
+
} catch (e) {
|
|
169
|
+
// 容错处理
|
|
170
|
+
finalWsUrl = currentConfig.platformBaseUrl.replace(/^http/, 'ws').replace(/\/api\/?$/, '').replace(/\/$/, '') + '/gateway/ws';
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (!finalWsUrl) {
|
|
174
|
+
finalWsUrl = "wss://aishuo.co/gateway/ws";
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
wsClient = new GatewayWsClient({
|
|
178
|
+
serverUrl: finalWsUrl,
|
|
179
|
+
gatewayId: uniqueGatewayId,
|
|
180
|
+
authToken: currentConfig.authToken, // 从 openclaw.json 的 config 节点动态读取鉴权 token
|
|
181
|
+
updater: updater,
|
|
182
|
+
enableFileLog: currentConfig.enableFileLog // 将日志开关透传给客户端模块
|
|
183
|
+
});
|
|
184
|
+
wsClient.connect();
|
|
185
|
+
}
|
|
186
|
+
// =========================================================
|
|
187
|
+
|
|
188
|
+
// 启动即跑一次:先 load 缓存 → 本地对账(扫描+匹配配置)
|
|
189
|
+
void configSync
|
|
190
|
+
.load()
|
|
191
|
+
.then(() => configSync.reconcile())
|
|
192
|
+
.catch((err) => console.warn("[skill-logger-plugin] 启动初始化异常", err));
|
|
193
|
+
reporter.startTimer();
|
|
194
|
+
// 定时器①:本地扫描对账,固定 3 分钟。
|
|
195
|
+
if (!reconcileTimer) {
|
|
196
|
+
reconcileTimer = setInterval(() => void configSync.reconcile(), RECONCILE_INTERVAL_MS);
|
|
197
|
+
if (typeof reconcileTimer.unref === "function") reconcileTimer.unref();
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
api.on("gateway_stop", async () => {
|
|
202
|
+
// 停止时销毁连接
|
|
203
|
+
if (wsClient) {
|
|
204
|
+
wsClient.destroy();
|
|
205
|
+
wsClient = undefined;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (reconcileTimer) {
|
|
209
|
+
clearInterval(reconcileTimer);
|
|
210
|
+
reconcileTimer = undefined;
|
|
211
|
+
}
|
|
212
|
+
await hooks.flushAllPending();
|
|
213
|
+
await reporter.stopTimer();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
api.on("before_install", (event, ctx) => {
|
|
217
|
+
mergeConfig(event, ctx);
|
|
218
|
+
// 安装后立即对账拉取最新配置,不执行自动更新(由 WSS 指令统一控制更新动作)
|
|
219
|
+
void configSync
|
|
220
|
+
.reconcile()
|
|
221
|
+
.catch((err) => console.warn("[skill-logger-plugin] before_install 处理异常", err));
|
|
222
|
+
});
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
export default definition;
|