@ynhcj/xiaoyi-channel 0.0.220-next → 0.0.222-next

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 CHANGED
@@ -5,7 +5,7 @@ import { xyPlugin } from "./src/channel.js";
5
5
  import registerSentinelHook from "./src/cspl/sentinel_hook.js";
6
6
  import registerSkillScopeHook from "./src/cspl/skill_scope_hook.js";
7
7
  import { setXYRuntime } from "./src/runtime.js";
8
- import { markCronToolCall, clearCronToolCall, getCurrentSessionContext, setCronToolRunInfo, clearCronToolRunInfo } from "./src/tools/session-manager.js";
8
+ import { markCronToolCall, clearCronToolCall, getCurrentSessionContext, setCronToolRunInfo, clearCronToolRunInfo, isCronActive } from "./src/tools/session-manager.js";
9
9
  import { configManager } from "./src/utils/config-manager.js";
10
10
  import { setJobPushId } from "./src/utils/cron-push-map.js";
11
11
  import { getAllPushIds } from "./src/utils/pushid-manager.js";
@@ -44,14 +44,35 @@ function extractSkillNameFromPath(filePath) {
44
44
  * the `after_tool_call` hook and write the skill name to the skills log.
45
45
  */
46
46
  function registerSkillsDiagnosticHook(api) {
47
- api.on("after_tool_call", async (event, _ctx) => {
48
- if (event.toolName !== "read")
49
- return;
50
- const skillName = extractSkillNameFromPath(event.params?.path);
47
+ // Tool name → skill name mapping for direct tool-based skill usage logging
48
+ const TOOL_SKILL_MAP = {
49
+ get_user_location: "GetCurrentLocation",
50
+ get_calendar_tool_schema: "Schedule",
51
+ get_note_tool_schema: "memorandum",
52
+ get_photo_tool_schema: "gallery",
53
+ get_contact_tool_schema: "contact",
54
+ get_device_file_tool_schema: "file",
55
+ get_alarm_tool_schema: "clock",
56
+ message: "message",
57
+ get_phone_tool_schema: "phone",
58
+ get_collection_tool_schema: "xiaoyi-collection",
59
+ };
60
+ // Log skill usage for known device tools on before_tool_call
61
+ api.on("before_tool_call", async (event, _ctx) => {
62
+ const skillName = TOOL_SKILL_MAP[event.toolName];
51
63
  if (skillName) {
52
64
  writeSkillUsage(skillName);
53
65
  }
54
66
  });
67
+ // Detect SKILL.md reads on after_tool_call (original behavior)
68
+ api.on("after_tool_call", async (event, _ctx) => {
69
+ if (event.toolName === "read") {
70
+ const skillName = extractSkillNameFromPath(event.params?.path);
71
+ if (skillName) {
72
+ writeSkillUsage(skillName);
73
+ }
74
+ }
75
+ });
55
76
  }
56
77
  /**
57
78
  * Register the cron detection hook.
@@ -63,7 +84,9 @@ function registerSkillsDiagnosticHook(api) {
63
84
  */
64
85
  function registerCronDetectionHook(api) {
65
86
  api.on("before_tool_call", async (event, ctx) => {
66
- if (ctx.sessionKey?.startsWith("cron:") && event.toolCallId) {
87
+ // sessionKey 前缀依赖 openclaw 版本行为,不一定可靠;
88
+ // isCronActive() 由 provider.ts 根据消息内容 [cron:...] 设置,更可靠。
89
+ if ((ctx.sessionKey?.startsWith("cron:") || isCronActive()) && event.toolCallId) {
67
90
  markCronToolCall(event.toolCallId);
68
91
  // 存储 runId,供 call_device_tool 在 ALS 缺失时构造合成 SessionContext
69
92
  if (event.runId) {
package/dist/src/bot.js CHANGED
@@ -322,11 +322,10 @@ export async function handleXYMessage(params) {
322
322
  });
323
323
  if (queued) {
324
324
  log.log(`[BOT-STEER] Direct steer succeeded — message injected into active run`);
325
- // Steer message taskId refCount is no longer needed since we skip the dispatcher.
326
- // Use expectedTaskId to only remove the steer's own task — passing no
327
- // expectedTaskId would wipe the entire session binding and cause the next
328
- // steer to be misdetected as a new message (isUpdate=false).
329
- decrementTaskIdRef(parsed.sessionId, parsed.taskId);
325
+ // Steer message taskId stays in the binding so getActiveTaskId()
326
+ // resolves to the latest taskId for onPartialReply replies.
327
+ // Cleanup happens via the original dispatcher's onIdleComplete →
328
+ // decrementTaskIdRef(sessionId) which removes the entire binding.
330
329
  return;
331
330
  }
332
331
  log.log(`[BOT-STEER] Direct steer failed (queued=false), falling through to dispatchReplyFromConfig`);
@@ -9,7 +9,7 @@
9
9
  // models.providers.xiaoyiprovider.models = [...]
10
10
  import { createHash } from "crypto";
11
11
  import { logger } from "./utils/logger.js";
12
- import { getCurrentSessionContext, setCurrentCronJobId } from "./tools/session-manager.js";
12
+ import { getCurrentSessionContext, setCurrentCronJobId, markCronDetected } from "./tools/session-manager.js";
13
13
  import { selfEvolutionManager } from "./utils/self-evolution-manager.js";
14
14
  import { setCompactionConfig, setCompactionSessionSnapshot } from "./compaction-provider.js";
15
15
  // ── Retry config ──────────────────────────────────────────────
@@ -560,6 +560,9 @@ export const xiaoyiProvider = {
560
560
  // 4. UID-based fallback: sha256(uid).hex[:32]_timestamp
561
561
  const isCron = isCronTriggered(context.messages);
562
562
  if (isCron) {
563
+ // 通知 before_tool_call hook:当前请求是 cron 触发。
564
+ // 不依赖 openclaw 的 sessionKey 前缀(部分版本可能不设置)。
565
+ markCronDetected();
563
566
  // fire 期 jobId 桥:把首条消息 `[cron:<jobId> ...]` 解析出的真实 jobId
564
567
  // 绑定到本次 cron run 的合成 sessionId。sendCommand 凭同一 sessionId
565
568
  // 反查 jobId → cron-push-map → 正确设备的 pushId(多设备路由)。
@@ -507,16 +507,9 @@ export function createXYReplyDispatcher(params) {
507
507
  if (text.length === 0)
508
508
  return;
509
509
  hasSentResponse = true;
510
- // 检测新模型调用:同一模型调用内 text 是递增的("好的"→"好的,已查到"),
511
- // 跨模型调用时 text 会刷新,此时 !text.startsWith(currentModelText) 为 true
512
- if (currentModelText && !text.startsWith(currentModelText)) {
513
- // 锁存上一个模型调用的完整文本
514
- prevModelText += currentModelText;
515
- }
516
- currentModelText = text;
517
- const sep = prevModelText ? "\n" : "";
518
- const fullText = crossTurnPrefix + prevModelText + sep + text;
519
- // 串行化发送,避免 ws.send 乱序
510
+ // 串行化回调进入:SDK fire-and-forget 模式下多个 onPartialReply
511
+ // 可能并发执行,processingLock 保证 currentModelText/prevModelText
512
+ // 的读写和 ws.send 都在互斥临界区内。
520
513
  const prevLock = processingLock;
521
514
  let releaseLock;
522
515
  processingLock = new Promise((resolve) => {
@@ -524,6 +517,15 @@ export function createXYReplyDispatcher(params) {
524
517
  });
525
518
  try {
526
519
  await prevLock;
520
+ // 检测新模型调用:同一模型调用内 text 是递增的("好的"→"好的,已查到"),
521
+ // 跨模型调用时 text 会刷新,此时 !text.startsWith(currentModelText) 为 true
522
+ if (currentModelText && !text.startsWith(currentModelText)) {
523
+ // 锁存上一个模型调用的完整文本
524
+ prevModelText += currentModelText;
525
+ }
526
+ currentModelText = text;
527
+ const sep = prevModelText ? "\n" : "";
528
+ const fullText = crossTurnPrefix + prevModelText + sep + text;
527
529
  await sendA2AResponse({
528
530
  config,
529
531
  sessionId,
@@ -20,6 +20,10 @@ export interface SessionContext {
20
20
  * Tools should use the push channel instead of WebSocket sendCommand. */
21
21
  isCron?: boolean;
22
22
  }
23
+ /** Mark that the current request is cron-triggered (called by provider). */
24
+ export declare function markCronDetected(): void;
25
+ /** Check whether a cron request was recently detected. */
26
+ export declare function isCronActive(ttlMs?: number): boolean;
23
27
  /** Mark a toolCallId as originating from a cron trigger. */
24
28
  export declare function markCronToolCall(toolCallId: string): void;
25
29
  /** Check whether a toolCallId is from a cron trigger. */
@@ -14,6 +14,20 @@ import { AsyncLocalStorage } from "async_hooks";
14
14
  import { logger } from "../utils/logger.js";
15
15
  import { getCurrentTaskId, getCurrentMessageId } from "../task-manager.js";
16
16
  const _g = globalThis;
17
+ // ── Cron request detection ──────────────────────────────────────────
18
+ // The provider detects cron from [cron:...] in message text and sets
19
+ // this timestamp. The before_tool_call hook checks it as a fallback
20
+ // when openclaw's sessionKey does not have the "cron:" prefix.
21
+ // Timestamp-based so it self-expires and requires no explicit cleanup.
22
+ let __xyCronDetectedTs = 0;
23
+ /** Mark that the current request is cron-triggered (called by provider). */
24
+ export function markCronDetected() {
25
+ __xyCronDetectedTs = Date.now();
26
+ }
27
+ /** Check whether a cron request was recently detected. */
28
+ export function isCronActive(ttlMs = 120_000) {
29
+ return Date.now() - __xyCronDetectedTs < ttlMs;
30
+ }
17
31
  // ── Cron tool-call tracking ─────────────────────────────────────────
18
32
  // Global Map keyed by toolCallId to track whether a specific tool call
19
33
  // originated from a cron/scheduled task. Populated by the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi-channel",
3
- "version": "0.0.220-next",
3
+ "version": "0.0.222-next",
4
4
  "description": "OpenClaw Xiaoyi Channel plugin - Xiaoyi A2A protocol integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",