@ynhcj/xiaoyi-channel 0.0.213-next → 0.0.215-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.js CHANGED
@@ -12,7 +12,6 @@ import { createBeforePromptBuildHandler } from "./src/skill-retriever/hooks.js";
12
12
  import { normalizeToolRetrieverConfig } from "./src/skill-retriever/config.js";
13
13
  import { registerCLIHook } from "./src/tools/hmos-cli.js";
14
14
  import { writeSkillUsage } from "./src/utils/skills-logger.js";
15
- import { logger } from "./src/utils/logger.js";
16
15
  /**
17
16
  * Parse a file path string to detect if it refers to a SKILL.md file within
18
17
  * a skills directory. Returns the skill name (parent directory) if so.
@@ -217,10 +216,7 @@ function registerFullHooks(api) {
217
216
  timeoutMs: pluginConfig.skillRetrieverTimeoutMs ?? 1000,
218
217
  });
219
218
  const beforePromptBuildHandler = createBeforePromptBuildHandler(skillRetrieverConfig);
220
- api.on("before_prompt_build", async (event, ctx) => {
221
- logger.log(`[BEFORE_PROMPT_BUILD] hook fired, sessionKey=${ctx.sessionKey || "undefined"}, sessionId=${ctx.sessionId || "undefined"}`);
222
- return beforePromptBuildHandler(event, ctx);
223
- });
219
+ api.on("before_prompt_build", beforePromptBuildHandler);
224
220
  registerSelfEvolutionToolResultNudge(api);
225
221
  }
226
222
  const pluginEntry = definePluginEntry({
package/dist/src/bot.js CHANGED
@@ -15,7 +15,6 @@ import { selfEvolutionManager } from "./utils/self-evolution-manager.js";
15
15
  import { saveRuntimeInfo } from "./utils/runtime-manager.js";
16
16
  import { toolCallNudgeManager } from "./utils/tool-call-nudge-manager.js";
17
17
  import { setCsplSteerContext } from "./cspl/steer-context.js";
18
- import { getSteerQueue } from "./steer-queue.js";
19
18
  import { registerTaskId, decrementTaskIdRef, hasActiveTask, } from "./task-manager.js";
20
19
  import { logger } from "./utils/logger.js";
21
20
  /**
@@ -292,35 +291,6 @@ export async function handleXYMessage(params) {
292
291
  log.log(`[BOT] Downloaded ${downloadedFiles.length} file(s)`);
293
292
  mediaPayload = buildXYMediaPayload(downloadedFiles);
294
293
  }
295
- // 🔑 对于 steer 消息,将文件路径附加到消息文本中。
296
- // auto-reply 管道的 steer 注入只携带 prompt 文本(followupRun.prompt),
297
- // 不携带 mediaPayload,所以模型需要以文本形式看到附件路径。
298
- if (isUpdate && mediaPayload.MediaPaths?.length) {
299
- const fileHint = `\n【用户上传附件】:${JSON.stringify(mediaPayload.MediaPaths)}`;
300
- textForAgent = `${textForAgent}${fileHint}`;
301
- log.log(`[BOT] Steer: appended file paths to text`);
302
- }
303
- // 🔑 Provider-level steer injection: push steer text to the shared queue.
304
- // provider.ts checks this queue on every model call (including tool-loop
305
- // model calls in agent.continue()) and drains pending steer messages
306
- // directly into context.messages before sending to the model.
307
- // Key: parsed.sessionId (A2A conversationId) matches getCurrentSessionContext().sessionId.
308
- if (isUpdate) {
309
- const steerQueue = getSteerQueue();
310
- const queueKey = parsed.sessionId;
311
- const pending = steerQueue.get(queueKey) ?? [];
312
- pending.push(textForAgent);
313
- steerQueue.set(queueKey, pending);
314
- log.log(`[BOT-STEER] Queued for provider injection, sessionId=${queueKey}, queueDepth=${pending.length}`);
315
- // Bypass dispatchReplyFromConfig entirely — steer injection is handled
316
- // at the provider level (provider.ts drains the queue on every model call).
317
- // No need for /steer command dispatch or dispatcher creation.
318
- if (!skipReg) {
319
- decrementTaskIdRef(parsed.sessionId);
320
- }
321
- params.onInitComplete?.();
322
- return;
323
- }
324
294
  // Resolve envelope format options (following feishu pattern)
325
295
  const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
326
296
  // Build message body with speaker prefix (following feishu pattern)
@@ -365,7 +335,7 @@ export async function handleXYMessage(params) {
365
335
  // response and cleanup — the first message's dispatcher handles those.
366
336
  const steerState = { steered: isUpdate };
367
337
  // 🔑 创建dispatcher
368
- log.log(`[BOT-DISPATCHER] Creating reply dispatcher, isSteer=${isUpdate}, sessionKey=${route.sessionKey}`);
338
+ log.log(`[BOT-DISPATCHER] Creating reply dispatcher`);
369
339
  // Cleanup: 必须在 onIdle 内部执行(参见 reply-dispatcher.ts 中 onIdleComplete 的注释)
370
340
  let cleaned = false;
371
341
  const cleanup = () => {
@@ -428,9 +398,8 @@ export async function handleXYMessage(params) {
428
398
  // signal init complete to release the global dispatch gate
429
399
  // for the next session.
430
400
  const dispatchPromise = runWithSessionContext(sessionContext, async () => {
431
- const isSteerDispatch = isUpdate && !skipReg;
432
- log.log(`[ALS-PROOF] bot entered dispatch scope sessionId=${sessionContext.sessionId} taskId=${sessionContext.taskId} isSteer=${isSteerDispatch}`);
433
- log.log(`[BOT-DISPATCH] dispatchReplyFromConfig starting, body.length=${ctxPayload.Body?.length ?? 0}, isSteer=${isSteerDispatch}`);
401
+ log.log(`[ALS-PROOF] bot entered dispatch scope sessionId=${sessionContext.sessionId} taskId=${sessionContext.taskId} isSteer=false`);
402
+ log.log(`[BOT-DISPATCH] dispatchReplyFromConfig starting, body.length=${ctxPayload.Body?.length ?? 0}`);
434
403
  try {
435
404
  const result = await core.channel.reply.dispatchReplyFromConfig({
436
405
  ctx: ctxPayload,
@@ -438,13 +407,7 @@ export async function handleXYMessage(params) {
438
407
  dispatcher,
439
408
  replyOptions,
440
409
  });
441
- // 区分 steer 成功(undefined)和 steer 失败/正常 run(有结果)
442
- if (result === undefined) {
443
- log.log(`[BOT-STEER] dispatchReplyFromConfig returned undefined — steer injected via fast path OK, sessionKey=${route.sessionKey}`);
444
- }
445
- else {
446
- log.log(`[BOT-DISPATCH] dispatchReplyFromConfig returned, resultType=${typeof result}, isSteer=${isSteerDispatch}, steered=${steerState.steered}`);
447
- }
410
+ log.log(`[BOT-DISPATCH] dispatchReplyFromConfig returned, result=${JSON.stringify(result)}`);
448
411
  return result;
449
412
  }
450
413
  catch (dispatchErr) {
@@ -1,8 +1,7 @@
1
1
  import { resolveXYConfig } from "./config.js";
2
2
  import { getXYWebSocketManager, diagnoseAllManagers, cleanupOrphanConnections, removeXYWebSocketManager } from "./client.js";
3
3
  import { handleXYMessage } from "./bot.js";
4
- import { parseA2AMessage } from "./parser.js";
5
- import { hasActiveTask, getAllActiveTaskBindings } from "./task-manager.js";
4
+ import { getAllActiveTaskBindings } from "./task-manager.js";
6
5
  import { sendA2AResponse } from "./formatter.js";
7
6
  import { handleTriggerEvent } from "./trigger-handler.js";
8
7
  import { handleSelfEvolutionEvent, handleSelfEvolutionStateGetEvent } from "./self-evolution-handler.js";
@@ -119,45 +118,13 @@ export async function monitorXYProvider(opts = {}) {
119
118
  activeMessages.delete(messageKey);
120
119
  }
121
120
  };
122
- // Steer detection: when queue mode is "steer" and the session has an active
123
- // run, process the message concurrently (skip the per-session serial queue).
124
- // This prevents the steer message from waiting behind the first message's
125
- // completion. The actual steer injection is handled by OpenClaw's auto-reply
126
- // pipeline (agent-runner.ts), not by the channel itself.
127
- const messageMethod = message.method;
128
- if (messageMethod === "clearContext" || messageMethod === "clear_context"
129
- || messageMethod === "tasks/cancel" || messageMethod === "tasks_cancel") {
130
- void enqueue(sessionId, task).catch((err) => {
131
- logger.error(`XY gateway: queue processing failed: ${String(err)}`);
132
- activeMessages.delete(messageKey);
133
- });
134
- }
135
- else {
136
- try {
137
- const parsed = parseA2AMessage(message);
138
- const steerMode = cfg.messages?.queue?.mode === "steer";
139
- const hasActiveRun = hasActiveTask(parsed.sessionId);
140
- if (steerMode && hasActiveRun) {
141
- logger.log(`[MONITOR] STEER MODE: executing concurrently, sessionId=${parsed.sessionId}, messageKey=${messageKey}`);
142
- void task().catch((err) => {
143
- logger.error(`XY gateway: concurrent steer task failed for ${messageKey}: ${String(err)}`);
144
- activeMessages.delete(messageKey);
145
- });
146
- }
147
- else {
148
- void enqueue(sessionId, task).catch((err) => {
149
- logger.error(`XY gateway: queue processing failed: ${String(err)}`);
150
- activeMessages.delete(messageKey);
151
- });
152
- }
153
- }
154
- catch {
155
- void enqueue(sessionId, task).catch((err) => {
156
- logger.error(`XY gateway: queue processing failed: ${String(err)}`);
157
- activeMessages.delete(messageKey);
158
- });
159
- }
160
- }
121
+ // All messages go through the per-session serial queue.
122
+ // Steer detection and injection is handled by OpenClaw's auto-reply pipeline
123
+ // (src/auto-reply/reply/agent-runner.ts), not by the channel itself.
124
+ void enqueue(sessionId, task).catch((err) => {
125
+ logger.error(`XY gateway: queue processing failed: ${String(err)}`);
126
+ activeMessages.delete(messageKey);
127
+ });
161
128
  };
162
129
  const connectedHandler = (serverId) => {
163
130
  if (!loggedServers.has(serverId)) {
@@ -11,7 +11,6 @@ import { createHash } from "crypto";
11
11
  import { logger } from "./utils/logger.js";
12
12
  import { getCurrentSessionContext, setCurrentCronJobId } from "./tools/session-manager.js";
13
13
  import { selfEvolutionManager } from "./utils/self-evolution-manager.js";
14
- import { getSteerQueue } from "./steer-queue.js";
15
14
  // ── Retry config ──────────────────────────────────────────────
16
15
  const RETRY_DELAYS_MS = [10_000, 20_000, 40_000, 60_000, 60_000];
17
16
  const MAX_RETRY_ATTEMPTS = 5;
@@ -593,26 +592,6 @@ export const xiaoyiProvider = {
593
592
  }
594
593
  // deviceType: prefer text-extracted value, ALS as fallback.
595
594
  const sessionCtx = getCurrentSessionContext();
596
- // 🔑 Provider-level steer injection: drain pending steer messages from the
597
- // shared queue and inject them as user messages before each model call.
598
- // This runs on EVERY model call including tool-loop model calls, so steer
599
- // messages injected mid-turn take effect immediately (unlike the hook-based
600
- // approach which only fires at turn boundaries).
601
- if (sessionCtx?.sessionId && context.messages) {
602
- const steerQueue = getSteerQueue();
603
- const pending = steerQueue.get(sessionCtx.sessionId);
604
- if (pending && pending.length > 0) {
605
- steerQueue.delete(sessionCtx.sessionId); // drain before injection
606
- const steerText = pending.splice(0).join("\n\n");
607
- const steerMessage = {
608
- role: "user",
609
- content: [{ type: "text", text: `用户追加诉求:${steerText}` }],
610
- timestamp: Date.now(),
611
- };
612
- context.messages.push(steerMessage);
613
- logger.log(`[PROVIDER-STEER] Injected steer into context.messages, sessionId=${sessionCtx.sessionId}, textLen=${steerText.length}, msgCount=${context.messages.length}`);
614
- }
615
- }
616
595
  const deviceType = extractedDeviceType
617
596
  ?? sessionCtx?.deviceType;
618
597
  // app_ver and sdk_api_version from session context (ALS)
@@ -230,7 +230,6 @@ export function createXYReplyDispatcher(params) {
230
230
  if (steerState.steered && !hasSentResponse) {
231
231
  scopedLog().log(`[ON-IDLE] Steered dispatch, no response generated, skipping`);
232
232
  stopStatusInterval();
233
- await onIdleComplete?.();
234
233
  return;
235
234
  }
236
235
  // 🔑 用 try/finally 确保 cleanup 在 onIdle 的 async 工作全部完成后才执行。
@@ -3,8 +3,6 @@ interface TaskIdBinding {
3
3
  currentTaskId: string;
4
4
  currentMessageId: string;
5
5
  updatedAt: number;
6
- /** 引用计数:每次 registerTaskId +1,每次 decrementTaskIdRef -1,归零时删除 */
7
- refCount: number;
8
6
  }
9
7
  /**
10
8
  * 注册或更新session的活跃taskId。
@@ -19,11 +19,10 @@ const activeTaskIds = _g.__xyActiveTaskIds;
19
19
  export function registerTaskId(sessionId, taskId, messageId) {
20
20
  const existing = activeTaskIds.get(sessionId);
21
21
  if (existing) {
22
- logger.log(`[TASK_MANAGER] Updating taskId: ${existing.currentTaskId} → ${taskId}, refCount: ${existing.refCount} → ${existing.refCount + 1}`);
22
+ logger.log(`[TASK_MANAGER] Updating taskId: ${existing.currentTaskId} → ${taskId}`);
23
23
  existing.currentTaskId = taskId;
24
24
  existing.currentMessageId = messageId;
25
25
  existing.updatedAt = Date.now();
26
- existing.refCount++;
27
26
  return true; // isUpdate
28
27
  }
29
28
  else {
@@ -32,9 +31,8 @@ export function registerTaskId(sessionId, taskId, messageId) {
32
31
  currentTaskId: taskId,
33
32
  currentMessageId: messageId,
34
33
  updatedAt: Date.now(),
35
- refCount: 1,
36
34
  });
37
- logger.log(`[TASK_MANAGER] Registered new taskId: ${taskId}, refCount: 1`);
35
+ logger.log(`[TASK_MANAGER] Registered new taskId: ${taskId}`);
38
36
  return false;
39
37
  }
40
38
  }
@@ -42,17 +40,8 @@ export function registerTaskId(sessionId, taskId, messageId) {
42
40
  * 移除session的活跃taskId(消息处理完成时调用)。
43
41
  */
44
42
  export function decrementTaskIdRef(sessionId) {
45
- const binding = activeTaskIds.get(sessionId);
46
- if (!binding) {
47
- logger.log(`[TASK_MANAGER] decrementTaskIdRef: no binding for ${sessionId}`);
48
- return;
49
- }
50
- binding.refCount--;
51
- logger.log(`[TASK_MANAGER] decrementTaskIdRef: taskId=${binding.currentTaskId}, refCount: ${binding.refCount + 1} → ${binding.refCount}`);
52
- if (binding.refCount <= 0) {
53
- activeTaskIds.delete(sessionId);
54
- logger.log(`[TASK_MANAGER] Removed taskId binding (refCount reached 0)`);
55
- }
43
+ logger.log(`[TASK_MANAGER] Removing taskId`);
44
+ activeTaskIds.delete(sessionId);
56
45
  }
57
46
  /**
58
47
  * 获取session的当前活跃taskId。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi-channel",
3
- "version": "0.0.213-next",
3
+ "version": "0.0.215-beta",
4
4
  "description": "OpenClaw Xiaoyi Channel plugin - Xiaoyi A2A protocol integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1 +0,0 @@
1
- export declare function getSteerQueue(): Map<string, string[]>;
@@ -1,9 +0,0 @@
1
- // Shared steer injection queue — bot.ts writes, provider.ts reads.
2
- // Uses globalThis to survive module deduplication.
3
- const _g = globalThis;
4
- if (!_g.__xySteerInjectionQueue) {
5
- _g.__xySteerInjectionQueue = new Map();
6
- }
7
- export function getSteerQueue() {
8
- return _g.__xySteerInjectionQueue;
9
- }