@ynhcj/xiaoyi-channel 0.0.209-next → 0.0.210-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 +2 -47
- package/dist/src/bot.js +7 -38
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12,19 +12,7 @@ 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 { getSteerQueue } from "./src/steer-queue.js";
|
|
16
15
|
import { logger } from "./src/utils/logger.js";
|
|
17
|
-
import fs from "node:fs";
|
|
18
|
-
import path from "node:path";
|
|
19
|
-
// Diagnostic: write marker file to confirm hook fires
|
|
20
|
-
function touchHookMarker(label) {
|
|
21
|
-
try {
|
|
22
|
-
const dir = "/tmp/xy-steer-diag";
|
|
23
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
24
|
-
fs.writeFileSync(path.join(dir, `${Date.now()}_${label}.txt`), `${new Date().toISOString()} ${label}\n`);
|
|
25
|
-
}
|
|
26
|
-
catch { }
|
|
27
|
-
}
|
|
28
16
|
/**
|
|
29
17
|
* Parse a file path string to detect if it refers to a SKILL.md file within
|
|
30
18
|
* a skills directory. Returns the skill name (parent directory) if so.
|
|
@@ -228,43 +216,10 @@ function registerFullHooks(api) {
|
|
|
228
216
|
envFilePath: "~/.openclaw/.xiaoyienv",
|
|
229
217
|
timeoutMs: pluginConfig.skillRetrieverTimeoutMs ?? 1000,
|
|
230
218
|
});
|
|
231
|
-
const
|
|
232
|
-
// STEER INJECTION: merged into the before_prompt_build handler so it runs
|
|
233
|
-
// in the same hook registration call that we know already works (skill-retriever).
|
|
234
|
-
// This avoids any issues with separate api.on() registrations not being
|
|
235
|
-
// picked up by the global hook runner.
|
|
236
|
-
touchHookMarker("register_hook");
|
|
219
|
+
const beforePromptBuildHandler = createBeforePromptBuildHandler(skillRetrieverConfig);
|
|
237
220
|
api.on("before_prompt_build", async (event, ctx) => {
|
|
238
221
|
logger.log(`[BEFORE_PROMPT_BUILD] hook fired, sessionKey=${ctx.sessionKey || "undefined"}, sessionId=${ctx.sessionId || "undefined"}`);
|
|
239
|
-
|
|
240
|
-
const baseResult = await baseBeforePromptBuildHandler(event, ctx);
|
|
241
|
-
// Check for pending steer messages queued by bot.ts.
|
|
242
|
-
const sessionKey = ctx.sessionKey;
|
|
243
|
-
const queue = getSteerQueue();
|
|
244
|
-
const pending = sessionKey ? queue.get(sessionKey) : undefined;
|
|
245
|
-
const allKeys = Array.from(queue.keys());
|
|
246
|
-
// Diagnostic: write state to marker file for each hook fire
|
|
247
|
-
touchHookMarker(`hook_sessionKey=${sessionKey || "undefined"}_` +
|
|
248
|
-
`pendingLen=${pending?.length ?? 0}_` +
|
|
249
|
-
`queueSize=${queue.size}_` +
|
|
250
|
-
`queueKeys=${allKeys.join(",") || "empty"}`);
|
|
251
|
-
if (!sessionKey || !pending || pending.length === 0)
|
|
252
|
-
return baseResult;
|
|
253
|
-
// Drain pending steer messages
|
|
254
|
-
queue.delete(sessionKey);
|
|
255
|
-
const steerText = pending.splice(0).join("\n\n");
|
|
256
|
-
const steerAppend = `\n用户追加诉求:${steerText}`;
|
|
257
|
-
logger.log(`[STEER-HOOK] Injecting steer: sessionKey=${sessionKey} textLen=${steerText.length}`);
|
|
258
|
-
touchHookMarker(`steer_ok_${sessionKey}`);
|
|
259
|
-
// Merge steer appendContext with skill-retriever's result
|
|
260
|
-
const merged = { ...(baseResult ?? {}) };
|
|
261
|
-
if (merged.appendContext) {
|
|
262
|
-
merged.appendContext = `${merged.appendContext}\n\n${steerAppend}`;
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
merged.appendContext = steerAppend;
|
|
266
|
-
}
|
|
267
|
-
return merged;
|
|
222
|
+
return beforePromptBuildHandler(event, ctx);
|
|
268
223
|
});
|
|
269
224
|
registerSelfEvolutionToolResultNudge(api);
|
|
270
225
|
}
|
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
|
/**
|
|
@@ -315,12 +314,17 @@ export async function handleXYMessage(params) {
|
|
|
315
314
|
envelope: envelopeOptions,
|
|
316
315
|
body: messageBody,
|
|
317
316
|
});
|
|
317
|
+
// 🔑 Steer messages use /steer prefix to trigger the native slash command
|
|
318
|
+
// fast path in get-reply, which calls handleSteerCommand → queueEmbedded-
|
|
319
|
+
// AgentMessageWithOutcomeAsync without going through admitReplyTurn or
|
|
320
|
+
// the isStreaming guard in runReplyAgent.
|
|
321
|
+
const steerCommandBody = isUpdate ? `/steer ${textForAgent}` : textForAgent;
|
|
318
322
|
// ✅ Finalize inbound context (following feishu pattern)
|
|
319
323
|
// Use route.accountId and route.sessionKey instead of parsed fields
|
|
320
324
|
const ctxPayload = core.channel.reply.finalizeInboundContext({
|
|
321
325
|
Body: body,
|
|
322
|
-
RawBody:
|
|
323
|
-
CommandBody:
|
|
326
|
+
RawBody: steerCommandBody,
|
|
327
|
+
CommandBody: steerCommandBody,
|
|
324
328
|
From: parsed.sessionId,
|
|
325
329
|
To: parsed.sessionId, // ✅ Simplified: use sessionId as target (context is managed by SessionKey)
|
|
326
330
|
SessionKey: route.sessionKey, // ✅ Use route.sessionKey
|
|
@@ -340,41 +344,6 @@ export async function handleXYMessage(params) {
|
|
|
340
344
|
ReplyToBody: undefined, // A2A protocol doesn't support reply/quote
|
|
341
345
|
...mediaPayload,
|
|
342
346
|
});
|
|
343
|
-
// 🔑 Steer bypass: queue the message for agent_turn_prepare hook injection
|
|
344
|
-
// instead of going through dispatchReplyFromConfig which blocks on admitReplyTurn
|
|
345
|
-
// waiting for the active reply operation to complete.
|
|
346
|
-
//
|
|
347
|
-
// This covers BOTH:
|
|
348
|
-
// 1. Normal A2A steer (isUpdate && !skipReg) — user sends a message during
|
|
349
|
-
// an active agent run
|
|
350
|
-
// 2. CSPL/self-evolution steer (isUpdate && skipReg) — hook-triggered
|
|
351
|
-
// injection via tryInjectSteer. Without the bypass, CSPL steer would
|
|
352
|
-
// deadlock: it runs inside the agent turn, so admitReplyTurn's
|
|
353
|
-
// waitForIdle waits for the reply operation to finish, but the
|
|
354
|
-
// operation is owned by the very same agent turn.
|
|
355
|
-
//
|
|
356
|
-
// The hook fires before every agent prompt build, so the steer text reaches
|
|
357
|
-
// the model on the next turn without blocking.
|
|
358
|
-
if (isUpdate) {
|
|
359
|
-
const steerQueue = getSteerQueue();
|
|
360
|
-
// Use route.sessionKey as the queue key. The hook looks up by
|
|
361
|
-
// ctx.sessionKey which is the same value (agent:main:direct:<peerId>).
|
|
362
|
-
// Do NOT use parsed.sessionId because ctx.sessionId in the hook
|
|
363
|
-
// context is OpenClaw's internal random UUID, not the A2A sessionId.
|
|
364
|
-
const queueKey = route.sessionKey;
|
|
365
|
-
const pending = steerQueue.get(queueKey) ?? [];
|
|
366
|
-
pending.push(textForAgent);
|
|
367
|
-
steerQueue.set(queueKey, pending);
|
|
368
|
-
log.log(`[BOT] Steer bypass: queued for agent_turn_prepare hook, queueKey=${queueKey}, sessionKey=${route.sessionKey}, queueDepth=${pending.length}, skipReg=${skipReg}`);
|
|
369
|
-
// Only decrement refCount when registerTaskId was called (non-skipReg).
|
|
370
|
-
// skipReg callers (CSPL/self-evolution) don't increment refCount.
|
|
371
|
-
if (!skipReg) {
|
|
372
|
-
decrementTaskIdRef(parsed.sessionId);
|
|
373
|
-
}
|
|
374
|
-
// Release the global dispatch init gate — steer doesn't need it.
|
|
375
|
-
params.onInitComplete?.();
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
347
|
// 🔑 For steer messages, pre-set steered=true so the dispatcher skips final
|
|
379
348
|
// response and cleanup — the first message's dispatcher handles those.
|
|
380
349
|
const steerState = { steered: isUpdate };
|