@ynhcj/xiaoyi-channel 0.0.222-next → 0.0.224-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 +35 -9
- package/dist/src/bot.js +34 -68
- package/dist/src/channel.js +2 -2
- package/dist/src/conversation/conversation-manager.d.ts +87 -0
- package/dist/src/conversation/conversation-manager.js +387 -0
- package/dist/src/conversation/conversation-session.d.ts +38 -0
- package/dist/src/conversation/conversation-session.js +17 -0
- package/dist/src/conversation/cron-buffer.d.ts +25 -0
- package/dist/src/conversation/cron-buffer.js +168 -0
- package/dist/src/conversation/outbound-gateway.d.ts +39 -0
- package/dist/src/conversation/outbound-gateway.js +81 -0
- package/dist/src/conversation/steer-service.d.ts +29 -0
- package/dist/src/conversation/steer-service.js +52 -0
- package/dist/src/cron-command.js +2 -24
- package/dist/src/cspl/configs.json +10 -10
- package/dist/src/cspl/sentinel_hook.js +7 -10
- package/dist/src/dispatch/bot.d.ts +20 -0
- package/dist/src/dispatch/bot.js +510 -0
- package/dist/src/dispatch/monitor.d.ts +17 -0
- package/dist/src/dispatch/monitor.js +333 -0
- package/dist/src/dispatch/outbound.d.ts +7 -0
- package/dist/src/dispatch/outbound.js +255 -0
- package/dist/src/dispatch/reply-dispatcher.d.ts +22 -0
- package/dist/src/dispatch/reply-dispatcher.js +505 -0
- package/dist/src/formatter.js +13 -91
- package/dist/src/log-reporter/index.js +7 -7
- package/dist/src/outbound.d.ts +0 -7
- package/dist/src/outbound.js +6 -62
- package/dist/src/provider.js +4 -0
- package/dist/src/reply-dispatcher.d.ts +0 -2
- package/dist/src/reply-dispatcher.js +47 -131
- package/dist/src/self-evolution-handler.d.ts +1 -1
- package/dist/src/task-manager.d.ts +3 -9
- package/dist/src/task-manager.js +16 -48
- package/dist/src/tools/agent-as-skill-tool.js +1 -1
- package/dist/src/tools/calendar-tool.js +1 -1
- package/dist/src/tools/call-device-tool.js +1 -1
- package/dist/src/tools/call-phone-tool.js +1 -1
- package/dist/src/tools/check-plugin-privilege-tool.js +1 -1
- package/dist/src/tools/create-alarm-tool.js +1 -1
- package/dist/src/tools/delete-alarm-tool.js +1 -1
- package/dist/src/tools/discover-cross-devices-tool.js +1 -1
- package/dist/src/tools/hmos-cli.js +2 -2
- package/dist/src/tools/invoke.js +2 -2
- package/dist/src/tools/location-tool.js +1 -1
- package/dist/src/tools/login-token-tool.js +1 -1
- package/dist/src/tools/modify-alarm-tool.js +1 -1
- package/dist/src/tools/modify-note-tool.js +1 -1
- package/dist/src/tools/note-tool.js +1 -1
- package/dist/src/tools/query-app-message-tool.js +1 -1
- package/dist/src/tools/query-memory-data-tool.js +1 -1
- package/dist/src/tools/query-todo-task-tool.js +1 -1
- package/dist/src/tools/save-file-to-phone-tool.js +1 -1
- package/dist/src/tools/save-media-to-gallery-tool.js +1 -1
- package/dist/src/tools/search-alarm-tool.js +1 -1
- package/dist/src/tools/search-calendar-tool.js +1 -1
- package/dist/src/tools/search-contact-tool.js +1 -1
- package/dist/src/tools/search-email-tool.js +1 -1
- package/dist/src/tools/search-file-tool.js +1 -1
- package/dist/src/tools/search-message-tool.js +1 -1
- package/dist/src/tools/search-note-tool.js +1 -1
- package/dist/src/tools/search-photo-gallery-tool.js +1 -1
- package/dist/src/tools/send-cross-device-task-tool.js +1 -1
- package/dist/src/tools/send-email-tool.js +1 -1
- package/dist/src/tools/send-file-to-user-tool.js +1 -1
- package/dist/src/tools/send-message-tool.js +1 -1
- package/dist/src/tools/session-manager.js +1 -1
- package/dist/src/tools/upload-file-tool.js +1 -1
- package/dist/src/tools/upload-photo-tool.js +1 -1
- package/dist/src/tools/xiaoyi-add-collection-tool.js +1 -1
- package/dist/src/tools/xiaoyi-collection-tool.js +1 -1
- package/dist/src/tools/xiaoyi-delete-collection-tool.js +1 -1
- package/dist/src/tools/xiaoyi-gui-tool.js +3 -3
- package/dist/src/transport/client.d.ts +36 -0
- package/dist/src/transport/client.js +120 -0
- package/dist/src/transport/heartbeat.d.ts +39 -0
- package/dist/src/transport/heartbeat.js +99 -0
- package/dist/src/transport/message-queue.d.ts +17 -0
- package/dist/src/transport/message-queue.js +52 -0
- package/dist/src/transport/push.d.ts +40 -0
- package/dist/src/transport/push.js +218 -0
- package/dist/src/transport/websocket.d.ts +124 -0
- package/dist/src/transport/websocket.js +839 -0
- package/dist/src/trigger-handler.js +1 -1
- package/dist/src/websocket.js +3 -11
- package/package.json +1 -1
- package/dist/src/cspl/call-api.d.ts +0 -9
- package/dist/src/cspl/call-api.js +0 -110
- package/dist/src/cspl/middleware.d.ts +0 -8
- package/dist/src/cspl/middleware.js +0 -90
- package/dist/src/log-reporter/config-loader.d.ts +0 -11
- package/dist/src/log-reporter/config-loader.js +0 -68
- package/dist/src/steer-injector.d.ts +0 -16
- package/dist/src/steer-injector.js +0 -74
- package/dist/src/steer-queue.d.ts +0 -1
- package/dist/src/steer-queue.js +0 -9
- package/dist/src/thread-bindings.d.ts +0 -54
- package/dist/src/thread-bindings.js +0 -214
- package/dist/src/tools/create-all-tools.d.ts +0 -16
- package/dist/src/tools/create-all-tools.js +0 -62
- package/dist/src/tools/display-a2ui-card-tool.d.ts +0 -2
- package/dist/src/tools/display-a2ui-card-tool.js +0 -85
- package/dist/src/tools/find-pc-devices-tool.d.ts +0 -6
- package/dist/src/tools/find-pc-devices-tool.js +0 -95
- package/dist/src/tools/send-command-to-car-tool.d.ts +0 -5
- package/dist/src/tools/send-command-to-car-tool.js +0 -85
- package/dist/src/tools/session-helper.d.ts +0 -24
- package/dist/src/tools/session-helper.js +0 -45
- package/dist/src/xy-session-store.d.ts +0 -79
- package/dist/src/xy-session-store.js +0 -153
package/dist/index.js
CHANGED
|
@@ -15,7 +15,8 @@ import { normalizeToolRetrieverConfig } from "./src/skill-retriever/config.js";
|
|
|
15
15
|
import { registerCLIHook } from "./src/tools/hmos-cli.js";
|
|
16
16
|
import { recoverCronState } from "./src/cron-recovery.js";
|
|
17
17
|
import { writeSkillUsage } from "./src/utils/skills-logger.js";
|
|
18
|
-
import { markSubagentSpawned, markSubagentEnded,
|
|
18
|
+
import { markSubagentSpawned, markSubagentEnded, deliverSubagentFinalResult, } from "./src/conversation/conversation-manager.js";
|
|
19
|
+
import { notifyCronAgentEnd } from "./src/conversation/cron-buffer.js";
|
|
19
20
|
import { logger } from "./src/utils/logger.js";
|
|
20
21
|
/**
|
|
21
22
|
* Parse a file path string to detect if it refers to a SKILL.md file within
|
|
@@ -56,6 +57,7 @@ function registerSkillsDiagnosticHook(api) {
|
|
|
56
57
|
message: "message",
|
|
57
58
|
get_phone_tool_schema: "phone",
|
|
58
59
|
get_collection_tool_schema: "xiaoyi-collection",
|
|
60
|
+
image_reading: "xiaoyi-image-understanding"
|
|
59
61
|
};
|
|
60
62
|
// Log skill usage for known device tools on before_tool_call
|
|
61
63
|
api.on("before_tool_call", async (event, _ctx) => {
|
|
@@ -330,15 +332,12 @@ function registerSubagentHooks(api) {
|
|
|
330
332
|
logger.log(`[XY-SUBAGENT-END] ended, targetSessionKey=${event?.targetSessionKey?.slice(0, 30)}, outcome=${event?.outcome}, complete=${transition?.isComplete ?? false}, shouldFinalize=${transition?.shouldFinalize ?? false}, transition=${!!transition}`);
|
|
331
333
|
if (transition?.shouldFinalize) {
|
|
332
334
|
logger.log(`[XY-SUBAGENT-END] Starting finalization...`);
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
const { deliverSubagentFinalResult } = await import("./src/outbound.js");
|
|
339
|
-
logger.log(`[XY-SUBAGENT-END] Using cached XY config`);
|
|
335
|
+
// Grace: announce 投递的 sendText 与 subagent_ended hook 并发到达
|
|
336
|
+
// (实测 sendText 可能先于 hook 约 1s,也可能略晚于 hook),
|
|
337
|
+
// 短等让最后一条完成文本先落入 completionTexts 再合并发 final 帧。
|
|
338
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
339
|
+
// 最终交付由对话管理层统一负责(含 config 解析、final 帧、清理)
|
|
340
340
|
await deliverSubagentFinalResult({
|
|
341
|
-
config: config,
|
|
342
341
|
state: transition.state,
|
|
343
342
|
reason: "all-subagents-ended-after-parent-settled",
|
|
344
343
|
});
|
|
@@ -353,6 +352,33 @@ function registerSubagentHooks(api) {
|
|
|
353
352
|
function registerFullHooks(api) {
|
|
354
353
|
// SUBAGENT HOOKS: track subagent spawn/end lifecycle for session keep-alive
|
|
355
354
|
registerSubagentHooks(api);
|
|
355
|
+
// CRON AGGREGATION: 通过 agent 事件总线感知 cron isolated run 结束。
|
|
356
|
+
// 注意:agent_end 插件 hook 只在 dispatch-from-config 路径触发,cron
|
|
357
|
+
// isolated run 不会触发它;embedded run 的 lifecycle 事件才是可靠信号。
|
|
358
|
+
// phase=end/error 后到达的 sendText 即为 announce 最终结果(放行)。
|
|
359
|
+
api.agent.events.registerAgentEventSubscription({
|
|
360
|
+
id: "xy-cron-turn-tracker",
|
|
361
|
+
description: "Track cron isolated run completion for sendText aggregation",
|
|
362
|
+
streams: ["lifecycle"],
|
|
363
|
+
handle: (event) => {
|
|
364
|
+
try {
|
|
365
|
+
const phase = event?.data?.phase;
|
|
366
|
+
// 仅 phase=end 是 run 级终态信号;phase=error 可能是单次模型调用
|
|
367
|
+
// 失败(后续还有重试/start),不能据此放行 announce。
|
|
368
|
+
// 真正失败的 run(无 end)由 120s 安全超时兜底 flush 缓冲文本。
|
|
369
|
+
if (phase !== "end")
|
|
370
|
+
return;
|
|
371
|
+
const sessionKey = event?.sessionKey ?? "";
|
|
372
|
+
if (sessionKey.startsWith("cron:") || sessionKey.includes(":cron:") || isCronActive()) {
|
|
373
|
+
logger.log(`[XY-CRON] Cron run lifecycle end, sessionKey=${sessionKey || "unknown"}`);
|
|
374
|
+
notifyCronAgentEnd();
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
catch (err) {
|
|
378
|
+
logger.error(`[XY-CRON] Error in cron lifecycle subscription:`, err);
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
});
|
|
356
382
|
// SKILL RETRIEVER HOOK: before_prompt_build hook
|
|
357
383
|
const pluginConfig = api.pluginConfig || {};
|
|
358
384
|
const skillRetrieverConfig = normalizeToolRetrieverConfig({
|
package/dist/src/bot.js
CHANGED
|
@@ -17,7 +17,6 @@ import { toolCallNudgeManager } from "./utils/tool-call-nudge-manager.js";
|
|
|
17
17
|
import { setCsplSteerContext } from "./cspl/steer-context.js";
|
|
18
18
|
import { resolveActiveEmbeddedRunSessionId, queueAgentHarnessMessage, } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
19
19
|
import { registerTaskId, decrementTaskIdRef, hasActiveTask, } from "./task-manager.js";
|
|
20
|
-
import { registerSessionKeyMapping, getWaitState, hasWaitState, markParentSettled, cacheXYConfig, } from "./subagent-wait-state.js";
|
|
21
20
|
import { logger } from "./utils/logger.js";
|
|
22
21
|
/**
|
|
23
22
|
* Handle an incoming A2A message.
|
|
@@ -166,7 +165,6 @@ export async function handleXYMessage(params) {
|
|
|
166
165
|
const runCrossTaskContext = extractRunCrossTaskContext(parsed.parts);
|
|
167
166
|
// Resolve configuration (needed for status updates)
|
|
168
167
|
const config = resolveXYConfig(cfg);
|
|
169
|
-
cacheXYConfig(config);
|
|
170
168
|
// ✅ Resolve agent route (following feishu pattern)
|
|
171
169
|
// accountId is "default" for XY (single account mode)
|
|
172
170
|
// Use sessionId as peer.id to ensure all messages in the same session share context
|
|
@@ -180,16 +178,6 @@ export async function handleXYMessage(params) {
|
|
|
180
178
|
},
|
|
181
179
|
});
|
|
182
180
|
log.log(`[BOT] Resolved route, sessionKey=${route.sessionKey}`);
|
|
183
|
-
// Register sessionKey→A2A sessionId mapping for subagent hook translation.
|
|
184
|
-
// Hooks receive openclaw sessionKey (e.g. "agent:main:direct:xxx") but
|
|
185
|
-
// xy_channel operates on A2A sessionId. This bridge lets subagent_spawned/
|
|
186
|
-
// subagent_ended hooks find the correct wait state.
|
|
187
|
-
// IMPORTANT: Only register on the FIRST message. Steer messages have a
|
|
188
|
-
// different taskId — overwriting the mapping would break subagent wait
|
|
189
|
-
// state tracking for the original message's subagents.
|
|
190
|
-
if (!isUpdate) {
|
|
191
|
-
registerSessionKeyMapping(route.sessionKey, parsed.sessionId, parsed.taskId, parsed.messageId);
|
|
192
|
-
}
|
|
193
181
|
// Check for ACP runtime binding on this A2A conversation
|
|
194
182
|
const runtimeRoute = resolveRuntimeConversationBindingRoute({
|
|
195
183
|
route,
|
|
@@ -254,18 +242,20 @@ export async function handleXYMessage(params) {
|
|
|
254
242
|
log.error(`[BOT] Failed to patch session model override:`, patchErr);
|
|
255
243
|
}
|
|
256
244
|
}
|
|
257
|
-
// 🔑
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
245
|
+
// 🔑 发送初始状态更新(仅首条消息,steer 不重复发送)
|
|
246
|
+
if (!isUpdate) {
|
|
247
|
+
log.log(`[BOT] Sending initial status update`);
|
|
248
|
+
void sendStatusUpdate({
|
|
249
|
+
config,
|
|
250
|
+
sessionId: parsed.sessionId,
|
|
251
|
+
taskId: parsed.taskId,
|
|
252
|
+
messageId: parsed.messageId,
|
|
253
|
+
text: "任务正在处理中,请稍候~",
|
|
254
|
+
state: "working",
|
|
255
|
+
}).catch((err) => {
|
|
256
|
+
log.error(`Failed to send initial status update:`, err);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
269
259
|
}
|
|
270
260
|
// Extract text and files from parts
|
|
271
261
|
const text = extractTextFromParts(parsed.parts);
|
|
@@ -291,8 +281,10 @@ export async function handleXYMessage(params) {
|
|
|
291
281
|
}
|
|
292
282
|
}
|
|
293
283
|
// ── Build message and dispatch via auto-reply pipeline ──────────
|
|
294
|
-
//
|
|
295
|
-
//
|
|
284
|
+
// OpenClaw's auto-reply pipeline (src/auto-reply/reply/agent-runner.ts)
|
|
285
|
+
// detects active runs and handles steer injection natively — the channel
|
|
286
|
+
// does not need its own steer detection or polling logic.
|
|
287
|
+
// File download
|
|
296
288
|
let mediaPayload = {};
|
|
297
289
|
if (!skipReg || isUpdate) {
|
|
298
290
|
const fileParts = extractFileParts(parsed.parts);
|
|
@@ -322,10 +314,8 @@ export async function handleXYMessage(params) {
|
|
|
322
314
|
});
|
|
323
315
|
if (queued) {
|
|
324
316
|
log.log(`[BOT-STEER] Direct steer succeeded — message injected into active run`);
|
|
325
|
-
// Steer message taskId
|
|
326
|
-
|
|
327
|
-
// Cleanup happens via the original dispatcher's onIdleComplete →
|
|
328
|
-
// decrementTaskIdRef(sessionId) which removes the entire binding.
|
|
317
|
+
// Steer message taskId refCount is no longer needed since we skip the dispatcher.
|
|
318
|
+
decrementTaskIdRef(parsed.sessionId);
|
|
329
319
|
return;
|
|
330
320
|
}
|
|
331
321
|
log.log(`[BOT-STEER] Direct steer failed (queued=false), falling through to dispatchReplyFromConfig`);
|
|
@@ -337,8 +327,10 @@ export async function handleXYMessage(params) {
|
|
|
337
327
|
// Resolve envelope format options (following feishu pattern)
|
|
338
328
|
const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
|
|
339
329
|
// Build message body with speaker prefix (following feishu pattern)
|
|
330
|
+
let messageBody = textForAgent;
|
|
331
|
+
// Add speaker prefix for clarity
|
|
340
332
|
const speaker = parsed.sessionId;
|
|
341
|
-
|
|
333
|
+
messageBody = `${speaker}: ${messageBody}`;
|
|
342
334
|
// Format agent envelope (following feishu pattern)
|
|
343
335
|
const body = core.channel.reply.formatAgentEnvelope({
|
|
344
336
|
channel: "xiaoyi-channel",
|
|
@@ -397,15 +389,6 @@ export async function handleXYMessage(params) {
|
|
|
397
389
|
cleaned = true;
|
|
398
390
|
log.log(`[BOT] Cleanup started, steered=${steerState.steered}, skipReg=${skipReg}`);
|
|
399
391
|
if (!skipReg) {
|
|
400
|
-
// Check for pending subagent wait on this session
|
|
401
|
-
const pendingWait = getWaitState(parsed.sessionId, parsed.taskId) ??
|
|
402
|
-
(hasWaitState(parsed.sessionId) ? { deliveredCompletions: 0, expectedCompletions: 1 } : null);
|
|
403
|
-
if (pendingWait && pendingWait.deliveredCompletions < pendingWait.expectedCompletions) {
|
|
404
|
-
// Subagent wait active — skip cleanup, session stays alive
|
|
405
|
-
log.log(`[BOT] Cleanup suppressed — subagent wait active on session, taskId=${parsed.taskId}`);
|
|
406
|
-
cleaned = false;
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
392
|
decrementTaskIdRef(parsed.sessionId);
|
|
410
393
|
}
|
|
411
394
|
else {
|
|
@@ -447,34 +430,13 @@ export async function handleXYMessage(params) {
|
|
|
447
430
|
log.log(`[BOT-DISPATCH] withReplyDispatcher starting, sessionKey=${route.sessionKey}`);
|
|
448
431
|
await core.channel.reply.withReplyDispatcher({
|
|
449
432
|
dispatcher,
|
|
450
|
-
onSettled:
|
|
433
|
+
onSettled: () => {
|
|
451
434
|
log.log(`[BOT] onSettled, steered=${steerState.steered}`);
|
|
452
435
|
// 🔑 When steered, skip cleanup — the first message's dispatcher is still running
|
|
453
436
|
if (steerState.steered) {
|
|
454
437
|
log.log(`[BOT] Steered dispatch settled, skipping cleanup`);
|
|
455
438
|
return;
|
|
456
439
|
}
|
|
457
|
-
// Subagent wait state: if parent has pending subagents, mark settled
|
|
458
|
-
// and defer finalization. Cleanup is suppressed so the session stays alive.
|
|
459
|
-
const pendingWait = getWaitState(parsed.sessionId, parsed.taskId);
|
|
460
|
-
if (pendingWait && !pendingWait.parentSettled) {
|
|
461
|
-
const transition = markParentSettled(parsed.sessionId, parsed.taskId);
|
|
462
|
-
if (transition?.shouldFinalize) {
|
|
463
|
-
// All completions arrived before parent settled → finalize now
|
|
464
|
-
const { deliverSubagentFinalResult } = await import("./outbound.js");
|
|
465
|
-
await deliverSubagentFinalResult({
|
|
466
|
-
config,
|
|
467
|
-
state: transition.state,
|
|
468
|
-
reason: "all-subagent-results-delivered-before-parent-settled",
|
|
469
|
-
});
|
|
470
|
-
log.log(`[BOT] Subagent wait complete on parent settled; final response delivered`);
|
|
471
|
-
}
|
|
472
|
-
else {
|
|
473
|
-
log.log(`[BOT] Subagent wait active, preserving session context for completion`);
|
|
474
|
-
}
|
|
475
|
-
dispatcherUpdaters.delete(parsed.sessionId);
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
440
|
// cleanup 已由 onIdleComplete 在 onIdle 的 finally 中执行。
|
|
479
441
|
// onSettled 不做任何清理(直接在这里清理会发生 race condition)。
|
|
480
442
|
dispatcherUpdaters.delete(parsed.sessionId);
|
|
@@ -488,7 +450,7 @@ export async function handleXYMessage(params) {
|
|
|
488
450
|
const dispatchPromise = runWithSessionContext(sessionContext, async () => {
|
|
489
451
|
const isSteerDispatch = isUpdate && !skipReg;
|
|
490
452
|
log.log(`[ALS-PROOF] bot entered dispatch scope sessionId=${sessionContext.sessionId} taskId=${sessionContext.taskId} isSteer=${isSteerDispatch}`);
|
|
491
|
-
log.log(`[BOT-DISPATCH] dispatchReplyFromConfig starting, body.length=${ctxPayload.Body?.length ?? 0}`);
|
|
453
|
+
log.log(`[BOT-DISPATCH] dispatchReplyFromConfig starting, body.length=${ctxPayload.Body?.length ?? 0}, isSteer=${isSteerDispatch}`);
|
|
492
454
|
try {
|
|
493
455
|
const result = await core.channel.reply.dispatchReplyFromConfig({
|
|
494
456
|
ctx: ctxPayload,
|
|
@@ -496,7 +458,13 @@ export async function handleXYMessage(params) {
|
|
|
496
458
|
dispatcher,
|
|
497
459
|
replyOptions,
|
|
498
460
|
});
|
|
499
|
-
|
|
461
|
+
// 区分 steer 成功(undefined)和 steer 失败/正常 run(有结果)
|
|
462
|
+
if (result === undefined) {
|
|
463
|
+
log.log(`[BOT-STEER] dispatchReplyFromConfig returned undefined — steer injected via fast path OK, sessionKey=${route.sessionKey}`);
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
log.log(`[BOT-DISPATCH] dispatchReplyFromConfig returned, resultType=${typeof result}, isSteer=${isSteerDispatch}, steered=${steerState.steered}`);
|
|
467
|
+
}
|
|
500
468
|
return result;
|
|
501
469
|
}
|
|
502
470
|
catch (dispatchErr) {
|
|
@@ -547,21 +515,19 @@ export async function handleXYMessage(params) {
|
|
|
547
515
|
function buildXYMediaPayload(mediaList) {
|
|
548
516
|
const first = mediaList[0];
|
|
549
517
|
const mediaPaths = mediaList.map((media) => media.path);
|
|
550
|
-
const mediaUrls = mediaList.map((media) => media.uri);
|
|
551
518
|
const mediaTypes = mediaList.map((media) => media.mimeType).filter(Boolean);
|
|
552
519
|
return {
|
|
553
520
|
MediaPath: first?.path,
|
|
554
521
|
MediaType: first?.mimeType,
|
|
555
|
-
MediaUrl: first?.uri,
|
|
556
522
|
MediaPaths: mediaPaths.length > 0 ? mediaPaths : undefined,
|
|
557
|
-
MediaUrls: mediaUrls.length > 0 ? mediaUrls : undefined,
|
|
558
523
|
MediaTypes: mediaTypes.length > 0 ? mediaTypes : undefined,
|
|
559
524
|
};
|
|
560
525
|
}
|
|
561
526
|
// ─────────────────────────────────────────────────────────────
|
|
562
527
|
// Dispatcher updaters (cross-chain taskId bridging)
|
|
563
528
|
// ─────────────────────────────────────────────────────────────
|
|
564
|
-
// Use globalThis to survive module deduplication
|
|
529
|
+
// Use globalThis to survive module deduplication — provider.ts may load a
|
|
530
|
+
// different copy of bot.ts, so a plain module-level Map would be two objects.
|
|
565
531
|
const _g = globalThis;
|
|
566
532
|
if (!_g.__xyDispatcherUpdaters)
|
|
567
533
|
_g.__xyDispatcherUpdaters = new Map();
|
package/dist/src/channel.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolveXYConfig, listXYAccountIds, getDefaultXYAccountId } from "./config.js";
|
|
2
2
|
import { xyConfigSchema } from "./config-schema.js";
|
|
3
|
-
import { xyOutbound } from "./outbound.js";
|
|
3
|
+
import { xyOutbound } from "./dispatch/outbound.js";
|
|
4
4
|
import { filterToolsByDevice } from "./tools/device-tool-map.js";
|
|
5
5
|
import { getCurrentSessionContext } from "./tools/session-manager.js";
|
|
6
6
|
import { logger } from "./utils/logger.js";
|
|
@@ -158,7 +158,7 @@ export const xyPlugin = {
|
|
|
158
158
|
},
|
|
159
159
|
gateway: {
|
|
160
160
|
async startAccount(context) {
|
|
161
|
-
const { monitorXYProvider } = await import("./monitor.js");
|
|
161
|
+
const { monitorXYProvider } = await import("./dispatch/monitor.js");
|
|
162
162
|
const { createXyAcpBindingManager } = await import("./acp-session-binding.js");
|
|
163
163
|
const account = resolveXYConfig(context.cfg);
|
|
164
164
|
context.setStatus?.({
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { ConversationSession, SubagentWaitState, SubagentWaitTransition, TaskEntry } from "./conversation-session.js";
|
|
2
|
+
export declare function getOrCreateSession(sessionId: string): ConversationSession;
|
|
3
|
+
export declare function getSession(sessionId: string): ConversationSession | null;
|
|
4
|
+
export declare function deleteSession(sessionId: string): void;
|
|
5
|
+
export declare function listSessions(): ConversationSession[];
|
|
6
|
+
/** 会话状态迁移(working / waiting-subagent / completing / 终态)。 */
|
|
7
|
+
export declare function setSessionState(sessionId: string, state: ConversationSession["state"]): void;
|
|
8
|
+
/**
|
|
9
|
+
* 注册或更新 session 的活跃 taskId。
|
|
10
|
+
* Returns true if this was an update (session already had an active task).
|
|
11
|
+
*/
|
|
12
|
+
export declare function registerTask(sessionId: string, taskId: string, messageId: string): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* 移除 session 的活跃 taskId(消息处理完成时调用)。
|
|
15
|
+
* @param expectedTaskId 可选:精确移除指定的 taskId,而非清空整个任务链。
|
|
16
|
+
*/
|
|
17
|
+
export declare function completeTask(sessionId: string, expectedTaskId?: string): void;
|
|
18
|
+
/** 获取 session 当前活跃 task(任务链末尾)。 */
|
|
19
|
+
export declare function getCurrentTask(sessionId: string): TaskEntry | null;
|
|
20
|
+
/** 获取 session 当前活跃 taskId(reply-dispatcher 跨链读取 / 工具富化用)。 */
|
|
21
|
+
export declare function getCurrentTaskId(sessionId: string): string | null;
|
|
22
|
+
/** 获取 session 当前活跃 messageId。 */
|
|
23
|
+
export declare function getCurrentMessageId(sessionId: string): string | null;
|
|
24
|
+
export declare function hasActiveTask(sessionId: string): boolean;
|
|
25
|
+
/** 所有活跃 task 绑定(gateway_stop 通知等场景)。 */
|
|
26
|
+
export declare function listActiveTaskBindings(): Array<{
|
|
27
|
+
sessionId: string;
|
|
28
|
+
currentTaskId: string;
|
|
29
|
+
currentMessageId: string;
|
|
30
|
+
updatedAt: number;
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* 绑定 openclaw sessionKey 到首条消息的 A2A 身份。
|
|
34
|
+
* 仅在首条消息时调用(steer 消息不覆盖,保持 subagent 等待态跟踪稳定)。
|
|
35
|
+
*/
|
|
36
|
+
export declare function bindSessionKey(sessionKey: string, sessionId: string, taskId: string, messageId: string): void;
|
|
37
|
+
export declare function resolveBySessionKey(sessionKey: string): {
|
|
38
|
+
sessionId: string;
|
|
39
|
+
taskId: string;
|
|
40
|
+
messageId: string;
|
|
41
|
+
} | null;
|
|
42
|
+
export declare function unbindSessionKey(sessionKey: string): void;
|
|
43
|
+
/**
|
|
44
|
+
* 全局缓存 resolved XY config,供 hook 上下文(无 raw ClawdbotConfig)使用。
|
|
45
|
+
*/
|
|
46
|
+
export declare function cacheXYConfig(config: unknown): void;
|
|
47
|
+
export declare function getCachedXYConfig(): unknown;
|
|
48
|
+
/**
|
|
49
|
+
* 启动(或重启)session 的 30s 状态心跳。
|
|
50
|
+
* 心跳由 manager 拥有:dispatcher 销毁不影响心跳;
|
|
51
|
+
* tick 时读取任务链末尾的当前 taskId/messageId。
|
|
52
|
+
*/
|
|
53
|
+
export declare function startStatusHeartbeat(sessionId: string, text?: string): void;
|
|
54
|
+
export declare function stopStatusHeartbeat(sessionId: string): void;
|
|
55
|
+
/**
|
|
56
|
+
* subagent_spawned hook 调用。递增 expected completion 计数。
|
|
57
|
+
* 若等待态尚不存在则创建(sessions_spawn 可能先于 sessions_yield)。
|
|
58
|
+
*/
|
|
59
|
+
export declare function markSubagentSpawned(sessionKey: string): number;
|
|
60
|
+
/**
|
|
61
|
+
* subagent_ended hook 调用。标记一个 subagent run 结束并计为一次交付。
|
|
62
|
+
* 这是主要的完成跟踪机制(subagent 完成可能不经 xyOutbound.sendText)。
|
|
63
|
+
*/
|
|
64
|
+
export declare function markSubagentEnded(sessionKey: string): SubagentWaitTransition | null;
|
|
65
|
+
/**
|
|
66
|
+
* bot.ts onSettled 调用。标记父 dispatcher 已 settle。
|
|
67
|
+
*/
|
|
68
|
+
export declare function markParentSettled(sessionId: string, taskId: string): SubagentWaitTransition | null;
|
|
69
|
+
/**
|
|
70
|
+
* 存储从 xyOutbound.sendText 捕获的完成文本。不递增交付计数。
|
|
71
|
+
*/
|
|
72
|
+
export declare function addCompletionText(sessionId: string, taskId: string, text: string): void;
|
|
73
|
+
export declare function getWaitState(sessionId: string, taskId?: string): SubagentWaitState | null;
|
|
74
|
+
export declare function hasWaitState(sessionId: string, taskId?: string): boolean;
|
|
75
|
+
export declare function clearWaitState(sessionId: string, reason: string, taskId?: string): SubagentWaitState | null;
|
|
76
|
+
/**
|
|
77
|
+
* 当所有 subagent 完成且父 turn 已 settle 时,由 manager 发送唯一的
|
|
78
|
+
* final 帧并真正结束当前 A2A session:
|
|
79
|
+
* 1. 合并 completionTexts 发送 final:true 帧
|
|
80
|
+
* 2. 清理等待态、停止心跳、移除任务链
|
|
81
|
+
* 3. 会话状态迁移到 completed
|
|
82
|
+
*/
|
|
83
|
+
export declare function deliverSubagentFinalResult(params: {
|
|
84
|
+
state: SubagentWaitState;
|
|
85
|
+
reason?: string;
|
|
86
|
+
text?: string;
|
|
87
|
+
}): Promise<void>;
|