@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.
Files changed (110) hide show
  1. package/dist/index.js +35 -9
  2. package/dist/src/bot.js +34 -68
  3. package/dist/src/channel.js +2 -2
  4. package/dist/src/conversation/conversation-manager.d.ts +87 -0
  5. package/dist/src/conversation/conversation-manager.js +387 -0
  6. package/dist/src/conversation/conversation-session.d.ts +38 -0
  7. package/dist/src/conversation/conversation-session.js +17 -0
  8. package/dist/src/conversation/cron-buffer.d.ts +25 -0
  9. package/dist/src/conversation/cron-buffer.js +168 -0
  10. package/dist/src/conversation/outbound-gateway.d.ts +39 -0
  11. package/dist/src/conversation/outbound-gateway.js +81 -0
  12. package/dist/src/conversation/steer-service.d.ts +29 -0
  13. package/dist/src/conversation/steer-service.js +52 -0
  14. package/dist/src/cron-command.js +2 -24
  15. package/dist/src/cspl/configs.json +10 -10
  16. package/dist/src/cspl/sentinel_hook.js +7 -10
  17. package/dist/src/dispatch/bot.d.ts +20 -0
  18. package/dist/src/dispatch/bot.js +510 -0
  19. package/dist/src/dispatch/monitor.d.ts +17 -0
  20. package/dist/src/dispatch/monitor.js +333 -0
  21. package/dist/src/dispatch/outbound.d.ts +7 -0
  22. package/dist/src/dispatch/outbound.js +255 -0
  23. package/dist/src/dispatch/reply-dispatcher.d.ts +22 -0
  24. package/dist/src/dispatch/reply-dispatcher.js +505 -0
  25. package/dist/src/formatter.js +13 -91
  26. package/dist/src/log-reporter/index.js +7 -7
  27. package/dist/src/outbound.d.ts +0 -7
  28. package/dist/src/outbound.js +6 -62
  29. package/dist/src/provider.js +4 -0
  30. package/dist/src/reply-dispatcher.d.ts +0 -2
  31. package/dist/src/reply-dispatcher.js +47 -131
  32. package/dist/src/self-evolution-handler.d.ts +1 -1
  33. package/dist/src/task-manager.d.ts +3 -9
  34. package/dist/src/task-manager.js +16 -48
  35. package/dist/src/tools/agent-as-skill-tool.js +1 -1
  36. package/dist/src/tools/calendar-tool.js +1 -1
  37. package/dist/src/tools/call-device-tool.js +1 -1
  38. package/dist/src/tools/call-phone-tool.js +1 -1
  39. package/dist/src/tools/check-plugin-privilege-tool.js +1 -1
  40. package/dist/src/tools/create-alarm-tool.js +1 -1
  41. package/dist/src/tools/delete-alarm-tool.js +1 -1
  42. package/dist/src/tools/discover-cross-devices-tool.js +1 -1
  43. package/dist/src/tools/hmos-cli.js +2 -2
  44. package/dist/src/tools/invoke.js +2 -2
  45. package/dist/src/tools/location-tool.js +1 -1
  46. package/dist/src/tools/login-token-tool.js +1 -1
  47. package/dist/src/tools/modify-alarm-tool.js +1 -1
  48. package/dist/src/tools/modify-note-tool.js +1 -1
  49. package/dist/src/tools/note-tool.js +1 -1
  50. package/dist/src/tools/query-app-message-tool.js +1 -1
  51. package/dist/src/tools/query-memory-data-tool.js +1 -1
  52. package/dist/src/tools/query-todo-task-tool.js +1 -1
  53. package/dist/src/tools/save-file-to-phone-tool.js +1 -1
  54. package/dist/src/tools/save-media-to-gallery-tool.js +1 -1
  55. package/dist/src/tools/search-alarm-tool.js +1 -1
  56. package/dist/src/tools/search-calendar-tool.js +1 -1
  57. package/dist/src/tools/search-contact-tool.js +1 -1
  58. package/dist/src/tools/search-email-tool.js +1 -1
  59. package/dist/src/tools/search-file-tool.js +1 -1
  60. package/dist/src/tools/search-message-tool.js +1 -1
  61. package/dist/src/tools/search-note-tool.js +1 -1
  62. package/dist/src/tools/search-photo-gallery-tool.js +1 -1
  63. package/dist/src/tools/send-cross-device-task-tool.js +1 -1
  64. package/dist/src/tools/send-email-tool.js +1 -1
  65. package/dist/src/tools/send-file-to-user-tool.js +1 -1
  66. package/dist/src/tools/send-message-tool.js +1 -1
  67. package/dist/src/tools/session-manager.js +1 -1
  68. package/dist/src/tools/upload-file-tool.js +1 -1
  69. package/dist/src/tools/upload-photo-tool.js +1 -1
  70. package/dist/src/tools/xiaoyi-add-collection-tool.js +1 -1
  71. package/dist/src/tools/xiaoyi-collection-tool.js +1 -1
  72. package/dist/src/tools/xiaoyi-delete-collection-tool.js +1 -1
  73. package/dist/src/tools/xiaoyi-gui-tool.js +3 -3
  74. package/dist/src/transport/client.d.ts +36 -0
  75. package/dist/src/transport/client.js +120 -0
  76. package/dist/src/transport/heartbeat.d.ts +39 -0
  77. package/dist/src/transport/heartbeat.js +99 -0
  78. package/dist/src/transport/message-queue.d.ts +17 -0
  79. package/dist/src/transport/message-queue.js +52 -0
  80. package/dist/src/transport/push.d.ts +40 -0
  81. package/dist/src/transport/push.js +218 -0
  82. package/dist/src/transport/websocket.d.ts +124 -0
  83. package/dist/src/transport/websocket.js +839 -0
  84. package/dist/src/trigger-handler.js +1 -1
  85. package/dist/src/websocket.js +3 -11
  86. package/package.json +1 -1
  87. package/dist/src/cspl/call-api.d.ts +0 -9
  88. package/dist/src/cspl/call-api.js +0 -110
  89. package/dist/src/cspl/middleware.d.ts +0 -8
  90. package/dist/src/cspl/middleware.js +0 -90
  91. package/dist/src/log-reporter/config-loader.d.ts +0 -11
  92. package/dist/src/log-reporter/config-loader.js +0 -68
  93. package/dist/src/steer-injector.d.ts +0 -16
  94. package/dist/src/steer-injector.js +0 -74
  95. package/dist/src/steer-queue.d.ts +0 -1
  96. package/dist/src/steer-queue.js +0 -9
  97. package/dist/src/thread-bindings.d.ts +0 -54
  98. package/dist/src/thread-bindings.js +0 -214
  99. package/dist/src/tools/create-all-tools.d.ts +0 -16
  100. package/dist/src/tools/create-all-tools.js +0 -62
  101. package/dist/src/tools/display-a2ui-card-tool.d.ts +0 -2
  102. package/dist/src/tools/display-a2ui-card-tool.js +0 -85
  103. package/dist/src/tools/find-pc-devices-tool.d.ts +0 -6
  104. package/dist/src/tools/find-pc-devices-tool.js +0 -95
  105. package/dist/src/tools/send-command-to-car-tool.d.ts +0 -5
  106. package/dist/src/tools/send-command-to-car-tool.js +0 -85
  107. package/dist/src/tools/session-helper.d.ts +0 -24
  108. package/dist/src/tools/session-helper.js +0 -45
  109. package/dist/src/xy-session-store.d.ts +0 -79
  110. 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, getCachedXYConfig, } from "./src/subagent-wait-state.js";
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
- const config = getCachedXYConfig();
334
- if (!config) {
335
- logger.error(`[XY-SUBAGENT-END] No cached XY config, cannot deliver final result`);
336
- return;
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
- log.log(`[BOT] Sending initial status update`);
259
- void sendStatusUpdate({
260
- config,
261
- sessionId: parsed.sessionId,
262
- taskId: parsed.taskId,
263
- messageId: parsed.messageId,
264
- text: "任务正在处理中,请稍候~",
265
- state: "working",
266
- }).catch((err) => {
267
- log.error(`Failed to send initial status update:`, err);
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
- // 首条消息和 steer 回退都走此统一 dispatch 路径。
295
- // File download — for both first message and steer fallthrough
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 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.
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
- const messageBody = `${speaker}: ${textForAgent}`;
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: async () => {
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
- log.log(`[BOT-DISPATCH] dispatchReplyFromConfig returned, result=${JSON.stringify(result)}`);
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();
@@ -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>;