@ynhcj/xiaoyi-channel 0.0.101-beta → 0.0.103-beta

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.d.ts CHANGED
@@ -1,9 +1,4 @@
1
1
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
- /**
3
- * Xiaoyi Channel Plugin Entry Point.
4
- * Exports the plugin for OpenClaw to load.
5
- * Located at root level following feishu pattern for proper plugin registration.
6
- */
7
2
  declare const plugin: {
8
3
  id: string;
9
4
  name: string;
package/dist/index.js CHANGED
@@ -4,13 +4,40 @@ import { xiaoyiProvider } from "./src/provider.js";
4
4
  import { setXYRuntime } from "./src/runtime.js";
5
5
  import { tryInjectSteer } from "./src/steer-injector.js";
6
6
  import { callCsplApi } from "./src/cspl/call-api.js";
7
- import { extractResultText, processText, parseSecurityResult, validateAndTruncateText } from "./src/cspl/utils.js";
7
+ import { extractResultText, processText, parseSecurityResult, validateAndTruncateText, } from "./src/cspl/utils.js";
8
8
  import { selfEvolutionManager } from "./src/utils/self-evolution-manager.js";
9
- import { TOOL_CALL_NUDGE_THRESHOLD, toolCallNudgeManager } from "./src/utils/tool-call-nudge-manager.js";
9
+ import { TOOL_CALL_NUDGE_THRESHOLD, toolCallNudgeManager, } from "./src/utils/tool-call-nudge-manager.js";
10
10
  import { ALLOWED_TOOLS, MIN_TEXT_LENGTH, MAX_TOTAL_LENGTH, MAX_TEXT_LENGTH, STEER_ABORT_MESSAGE, } from "./src/cspl/constants.js";
11
- import { createBeforePromptBuildHandler } from "./src/skill-retriever/hooks.js";
12
- import { normalizeToolRetrieverConfig } from "./src/skill-retriever/config.js";
13
- const SELF_EVOLUTION_NUDGE_MESSAGE = `[系统消息,非用户发送] 当前任务已经调用了较多工具。如果你识别到存在稳定、可复用的踩坑经验或排错模式,可以在完成当前主要任务后,正式回复用户前,阅读evolution skill开启自进化。注意,不要打断当前主任务,已完成任务为最高优先级。`;
11
+ const SELF_EVOLUTION_NUDGE_MESSAGE = "[系统消息,非用户发言,不需要告知用户] 当前任务已经调用了较多工具。如果你识别到其中存在稳定、可复用的踩坑经验或排错模式,可以在完成当前主要任务后,正式回复用户前,阅读xiaoyi-self-evolution技能了解自进化规则。注意,不要打断当前主任务,已完成任务为最高优先级。";
12
+ const SELF_EVOLUTION_KEYWORD_NUDGE_MESSAGE = "[系统消息,非用户发言,不需要告知用户] 当前用户请求中可能包含希望沉淀经验、避免重复踩坑的意图。如果你已经从当前问题中识别出稳定、可复用的处理规则或排错经验,可以在完成当前主要任务后,正式回复用户前,阅读xiaoyi-self-evolution技能了解自进化规则。注意,不要打断当前主任务,已完成任务为最高优先级。";
13
+ const SELF_EVOLUTION_KEYWORD_PATTERNS = [
14
+ /进化/u,
15
+ /记住/u,
16
+ /永远/u,
17
+ /下次必须/u,
18
+ /沉淀为/u,
19
+ /总结为/u,
20
+ /归纳为/u,
21
+ /以后(?:不要再犯|别再犯|必须|注意|记住)/u,
22
+ /记住这个坑/u,
23
+ /避免下次/u,
24
+ /别再踩坑/u,
25
+ /不要再踩坑/u,
26
+ /下次(?:别再|不要再)/u,
27
+ /以后(?:别再|不要再)(?:出错|犯错|漏掉|踩坑)/u,
28
+ /这个坑要记住/u,
29
+ /记住这次(?:教训|经验|问题)/u,
30
+ /吸取这次(?:教训|经验)/u,
31
+ /总结(?:一下)?这个坑/u,
32
+ /把这个(?:经验|教训|规则)记住/u,
33
+ /以后按这个规则/u,
34
+ /以后都按这个来/u,
35
+ /以后遇到这种情况/u,
36
+ /类似情况(?:下)?不要再/u,
37
+ /这种问题下次不能再出现/u,
38
+ /永远不要再(?:犯|踩|漏)/u,
39
+ /永远记住这次(?:教训|经验)/u,
40
+ ];
14
41
  function shouldCountToolCall(toolName) {
15
42
  if (toolName === "save_self_evolution_skill") {
16
43
  return false;
@@ -23,11 +50,15 @@ function shouldCountToolCall(toolName) {
23
50
  }
24
51
  return true;
25
52
  }
26
- /**
27
- * Xiaoyi Channel Plugin Entry Point.
28
- * Exports the plugin for OpenClaw to load.
29
- * Located at root level following feishu pattern for proper plugin registration.
30
- */
53
+ function getUserMessageForKeywordDetection(event) {
54
+ return event.body?.trim() || event.content.trim();
55
+ }
56
+ function shouldNudgeForSelfEvolutionKeyword(text) {
57
+ if (!text) {
58
+ return false;
59
+ }
60
+ return SELF_EVOLUTION_KEYWORD_PATTERNS.some((pattern) => pattern.test(text));
61
+ }
31
62
  const plugin = {
32
63
  id: "xiaoyi-channel",
33
64
  name: "Xiaoyi Channel",
@@ -37,11 +68,31 @@ const plugin = {
37
68
  setXYRuntime(api.runtime);
38
69
  api.registerChannel({ plugin: xyPlugin });
39
70
  api.registerProvider(xiaoyiProvider);
40
- // SENTINEL HOOK after_tool_call hook: 监听工具结果,发送至安全检测 API 进行安全检测
41
- // 如果响应为 REJECT,注入 steer 消息中止当前对话
71
+ api.on("before_dispatch", async (event, ctx) => {
72
+ const selfEvolutionEnabled = await selfEvolutionManager.isEnabled();
73
+ if (!ctx.sessionKey || !selfEvolutionEnabled) {
74
+ return;
75
+ }
76
+ const userText = getUserMessageForKeywordDetection(event);
77
+ if (!shouldNudgeForSelfEvolutionKeyword(userText)) {
78
+ return;
79
+ }
80
+ try {
81
+ const shouldNudge = toolCallNudgeManager.tryMarkKeywordNudge(ctx.sessionKey);
82
+ api.logger.debug?.(`[SELF_EVOLUTION] Keyword check hit: sessionKey=${ctx.sessionKey}, shouldNudge=${shouldNudge}`);
83
+ if (shouldNudge) {
84
+ api.logger.info?.(`[SELF_EVOLUTION] Keyword-triggered nudge injected: sessionKey=${ctx.sessionKey}`);
85
+ await tryInjectSteer(ctx.sessionKey, SELF_EVOLUTION_KEYWORD_NUDGE_MESSAGE);
86
+ }
87
+ }
88
+ catch (err) {
89
+ api.logger.error(`[SELF_EVOLUTION] before_dispatch keyword nudge error: ${err}`);
90
+ }
91
+ });
42
92
  api.on("after_tool_call", async (event, ctx) => {
93
+ const selfEvolutionEnabled = await selfEvolutionManager.isEnabled();
43
94
  if (ctx.sessionKey &&
44
- await selfEvolutionManager.isEnabled() &&
95
+ selfEvolutionEnabled &&
45
96
  shouldCountToolCall(event.toolName)) {
46
97
  try {
47
98
  const { count, shouldNudge } = toolCallNudgeManager.recordToolCall(ctx.sessionKey);
@@ -65,9 +116,8 @@ const plugin = {
65
116
  if (resultLength <= MIN_TEXT_LENGTH || resultLength > MAX_TOTAL_LENGTH) {
66
117
  return;
67
118
  }
68
- // 构造 sentinel_hook 格式的 payload: { tool, output: [{ content }] }
69
119
  const questionText = {
70
- subSceneID: 'TOOL_OUTPUT',
120
+ subSceneID: "TOOL_OUTPUT",
71
121
  tool: event.toolName,
72
122
  output: [{ content: "" }],
73
123
  };
@@ -91,17 +141,6 @@ const plugin = {
91
141
  api.logger.error(`[SENTINEL HOOK] after_tool_call error: ${err}`);
92
142
  }
93
143
  });
94
- // SKILL RETRIEVER HOOK: before_prompt_build hook
95
- const pluginConfig = api.pluginConfig || {};
96
- const skillRetrieverConfig = normalizeToolRetrieverConfig({
97
- enabled: pluginConfig.skillRetrieverEnabled ?? true,
98
- maxTools: pluginConfig.skillRetrieverMaxTools ?? 2,
99
- includeUninstalledOnly: true,
100
- envFilePath: "~/.openclaw/.xiaoyienv",
101
- timeoutMs: pluginConfig.skillRetrieverTimeoutMs ?? 1000,
102
- });
103
- const beforePromptBuildHandler = createBeforePromptBuildHandler(skillRetrieverConfig);
104
- api.on("before_prompt_build", beforePromptBuildHandler);
105
144
  },
106
145
  };
107
146
  export default plugin;
@@ -12,7 +12,7 @@ import { getCurrentSessionContext } from "./tools/session-manager.js";
12
12
  import { selfEvolutionManager } from "./utils/self-evolution-manager.js";
13
13
  // ── Retry config ──────────────────────────────────────────────
14
14
  const RETRY_DELAYS_MS = [10_000, 20_000, 40_000, 60_000];
15
- const MAX_RETRY_ATTEMPTS = 8;
15
+ const MAX_RETRY_ATTEMPTS = 4;
16
16
  /** Check if an errorMessage indicates a retryable provider error by type. */
17
17
  function isRetryableProviderError(message) {
18
18
  if (!message)
@@ -22,6 +22,8 @@ function isRetryableProviderError(message) {
22
22
  return true;
23
23
  if (lower.includes("rate limit reached for requests"))
24
24
  return true;
25
+ if (lower.includes("现在访问有点拥挤,稍等一下再试会更顺畅哦~"))
26
+ return true;
25
27
  return false;
26
28
  }
27
29
  /** Check if the request is triggered by a cron job by inspecting the first user message. */
@@ -252,7 +254,7 @@ ${SELF_EVOLUTION_PROMPT_BEGIN}
252
254
  - 隐式经验与避坑沉淀:当前任务执行过程中,你识别出了可以持久化为长期经验的标准工作流,或者在执行中遇到了工具调用错误、命令参数错误等明确的“踩坑点”,以后需要规避。
253
255
 
254
256
  [触发后处理]
255
- - 遵循 "self-evolution" 技能指导逐步进行技能评估。
257
+ - 遵循 "xiaoyi-self-evolution" 技能指导逐步进行技能评估。
256
258
  - 只能在任务完成后,正式回复用户前读取该技能。
257
259
  - 禁止直接修改文件。需要先创建提案,向用户发送进化请求,用户同意后才能进行更改。
258
260
  ${SELF_EVOLUTION_PROMPT_END}
@@ -350,6 +352,8 @@ export const xiaoyiProvider = {
350
352
  const cronTitle = extractCronTitle(context.messages);
351
353
  if (cronTitle)
352
354
  dynamicHeaders["x-cron-title"] = cronTitle;
355
+ if (context.messages?.length === 1)
356
+ dynamicHeaders["x-cron-flag"] = "begin";
353
357
  }
354
358
  }
355
359
  else {
@@ -365,6 +369,8 @@ export const xiaoyiProvider = {
365
369
  const cronTitle = extractCronTitle(context.messages);
366
370
  if (cronTitle)
367
371
  dynamicHeaders["x-cron-title"] = cronTitle;
372
+ if (context.messages?.length === 1)
373
+ dynamicHeaders["x-cron-flag"] = "begin";
368
374
  }
369
375
  }
370
376
  if (typeof sessionId === "string")
@@ -3,18 +3,22 @@ const TOOL_RETRIEVER_HEADER = `## 用户查询相关skill列表如下:
3
3
 
4
4
  `;
5
5
  const TOOL_RETRIEVER_FOOTER = `
6
- 以上是检索到的、与当前查询相关但用户尚未安装的 skill,优先推荐强相关的 skill(通用兜底工具如 web-search、xiaoyi-gui-agent 仅在无强相关skill时调用)。
7
- 对于强相关skill,你需要主动提醒用户可以考虑安装,但禁止直接给用户安装。用户明确希望安装后,使用find-skill执行安装。
8
- 若用户已有的skill已能很好地完成当前任务,则无需提醒安装功能相似的skill。
6
+ 以上是检索到的、与当前查询相关但用户尚未安装的skill,请按照以下规则判断是否需要推荐:
7
+ 1.判断用户当前请求的意图类型:
8
+ -若用户请求为查询已有信息、查看状态、执行已有功能或进行常规操作(例如查看定时任务列表),且这些操作无需额外skill即可完成,则不推荐任何skill。
9
+ 2.仅在以下条件全部满足时,才考虑推荐skill:
10
+ -用户请求明确表示需要完成某个具体任务;
11
+ -现有能力(包括已安装的skill或系统自带功能)不足以满足该任务。此时,优先推荐与任务强相关的skill。
12
+ 3.对于强相关且用户尚未安装的skill:
13
+ -可主动提醒用户考虑安装,但禁止直接安装;
14
+ -用户明确同意后,使用find-skills执行安装。
15
+ 4.若用户已安装的skill已能很好地完成当前任务,即使存在功能相似的未安装skill,也无需提醒。
9
16
  ---以下是用户原始请求---
10
17
  `;
11
18
  const PLUGIN_LOG_PREFIX = "[skill-retriever]";
12
- const SKIP_KEYWORDS = ["安装", "装一下", "下载", "查询", "查找", "install", "卸载", "删除", "重载"];
19
+ const SKIP_KEYWORDS = ["安装", "装一下", "下载", "查询", "查找", "install", "卸载", "删除", "重载", "定时任务", "重装"];
13
20
  const SKIP_PATTERNS = [
14
- "/new",
15
- "/reset",
16
- "session was started",
17
- "a new session was started",
21
+ "/new", "/reset", "/compact", "/stop", "/think", "/model", "/fast", "/verbose", "/config", "/debug", "/status", "/tasks", "/whoami", "/context", "/skill", "/commands", "/tools"
18
22
  ];
19
23
  function shouldSkipSearch(prompt) {
20
24
  const trimmedPrompt = prompt.trim();
@@ -6,7 +6,9 @@ declare class ToolCallNudgeManager {
6
6
  private readonly threshold;
7
7
  private readonly sessions;
8
8
  constructor(threshold?: number);
9
+ private getSessionState;
9
10
  recordToolCall(sessionKey: string): RecordToolCallResult;
11
+ tryMarkKeywordNudge(sessionKey: string): boolean;
10
12
  clearSession(sessionKey: string): void;
11
13
  }
12
14
  export declare const TOOL_CALL_NUDGE_THRESHOLD = 5;
@@ -5,7 +5,7 @@ class ToolCallNudgeManager {
5
5
  constructor(threshold = DEFAULT_TOOL_CALL_NUDGE_THRESHOLD) {
6
6
  this.threshold = threshold;
7
7
  }
8
- recordToolCall(sessionKey) {
8
+ getSessionState(sessionKey) {
9
9
  let state = this.sessions.get(sessionKey);
10
10
  if (!state) {
11
11
  state = {
@@ -14,6 +14,10 @@ class ToolCallNudgeManager {
14
14
  };
15
15
  this.sessions.set(sessionKey, state);
16
16
  }
17
+ return state;
18
+ }
19
+ recordToolCall(sessionKey) {
20
+ const state = this.getSessionState(sessionKey);
17
21
  state.count += 1;
18
22
  if (!state.nudged && state.count >= this.threshold) {
19
23
  state.nudged = true;
@@ -27,6 +31,14 @@ class ToolCallNudgeManager {
27
31
  shouldNudge: false,
28
32
  };
29
33
  }
34
+ tryMarkKeywordNudge(sessionKey) {
35
+ const state = this.getSessionState(sessionKey);
36
+ if (state.nudged) {
37
+ return false;
38
+ }
39
+ state.nudged = true;
40
+ return true;
41
+ }
30
42
  clearSession(sessionKey) {
31
43
  this.sessions.delete(sessionKey);
32
44
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi-channel",
3
- "version": "0.0.101-beta",
3
+ "version": "0.0.103-beta",
4
4
  "description": "OpenClaw Xiaoyi Channel plugin - Xiaoyi A2A protocol integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",