@ynhcj/xiaoyi-channel 0.0.192-next → 0.0.193-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
@@ -10,8 +10,6 @@ import { getAllPushIds } from "./src/utils/pushid-manager.js";
10
10
  import { registerSelfEvolutionToolResultNudge } from "./src/self-evolution-tool-result-nudge.js";
11
11
  import { createBeforePromptBuildHandler } from "./src/skill-retriever/hooks.js";
12
12
  import { normalizeToolRetrieverConfig } from "./src/skill-retriever/config.js";
13
- import { markSubagentSpawned, markSubagentEnded, } from "./src/subagent-wait-state.js";
14
- import { logger } from "./src/utils/logger.js";
15
13
  /**
16
14
  * Register the cron detection hook.
17
15
  *
@@ -169,58 +167,7 @@ function readJobIdFromResult(result) {
169
167
  }
170
168
  return undefined;
171
169
  }
172
- function registerSubagentHooks(api) {
173
- // subagent_spawned: fires after a subagent run is successfully registered.
174
- // We increment the expected completion count so that onIdle knows to wait.
175
- api.on("subagent_spawned", async (_event, ctx) => {
176
- const requesterSessionKey = ctx?.requesterSessionKey;
177
- if (!requesterSessionKey)
178
- return;
179
- const count = markSubagentSpawned(requesterSessionKey);
180
- if (count > 0) {
181
- logger.log(`[XY-SUBAGENT] spawned, requesterSessionKey=${requesterSessionKey.slice(0, 30)}, expected=${count}`);
182
- }
183
- });
184
- // subagent_ended: fires when a subagent run terminates (complete/error/killed).
185
- // This is the PRIMARY delivery tracking mechanism. When all expected
186
- // subagents have ended and parent has settled, we finalize the A2A session.
187
- api.on("subagent_ended", async (event, ctx) => {
188
- try {
189
- const requesterSessionKey = ctx?.requesterSessionKey;
190
- if (!requesterSessionKey) {
191
- logger.log(`[XY-SUBAGENT-END] no requesterSessionKey in ctx`);
192
- return;
193
- }
194
- const transition = markSubagentEnded(requesterSessionKey);
195
- 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}`);
196
- if (transition?.shouldFinalize) {
197
- logger.log(`[XY-SUBAGENT-END] Starting finalization...`);
198
- const [{ resolveXYConfig }, { deliverSubagentFinalResult }, { getXYRuntime }] = await Promise.all([
199
- import("./src/config.js"),
200
- import("./src/outbound.js"),
201
- import("./src/runtime.js"),
202
- ]);
203
- const rt = getXYRuntime();
204
- logger.log(`[XY-SUBAGENT-END] Runtime config type: ${typeof rt.config}, hasConfig=${!!rt.config}`);
205
- const cfg = rt.config;
206
- const config = resolveXYConfig(cfg);
207
- logger.log(`[XY-SUBAGENT-END] XY config resolved, wsUrl=${config.wsUrl?.slice(0, 20)}`);
208
- await deliverSubagentFinalResult({
209
- config,
210
- state: transition.state,
211
- reason: "all-subagents-ended-after-parent-settled",
212
- });
213
- logger.log(`[XY-SUBAGENT-END] Finalized A2A session after all subagents ended`);
214
- }
215
- }
216
- catch (err) {
217
- logger.error(`[XY-SUBAGENT-END] Error in subagent_ended hook:`, err);
218
- }
219
- });
220
- }
221
170
  function registerFullHooks(api) {
222
- // SUBAGENT HOOKS: track subagent spawn/end lifecycle for session keep-alive
223
- registerSubagentHooks(api);
224
171
  // SKILL RETRIEVER HOOK: before_prompt_build hook
225
172
  const pluginConfig = api.pluginConfig || {};
226
173
  const skillRetrieverConfig = normalizeToolRetrieverConfig({
package/dist/src/bot.js CHANGED
@@ -16,7 +16,6 @@ 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
18
  import { registerTaskId, decrementTaskIdRef, hasActiveTask, } from "./task-manager.js";
19
- import { registerSessionKeyMapping, getWaitState, hasWaitState, markParentSettled, } from "./subagent-wait-state.js";
20
19
  import { logger } from "./utils/logger.js";
21
20
  /**
22
21
  * Handle an incoming A2A message.
@@ -178,11 +177,6 @@ export async function handleXYMessage(params) {
178
177
  },
179
178
  });
180
179
  log.log(`[BOT] Resolved route, sessionKey=${route.sessionKey}`);
181
- // Register sessionKey→A2A sessionId mapping for subagent hook translation.
182
- // Hooks receive openclaw sessionKey (e.g. "agent:main:direct:xxx") but
183
- // xy_channel operates on A2A sessionId. This bridge lets subagent_spawned/
184
- // subagent_ended hooks find the correct wait state.
185
- registerSessionKeyMapping(route.sessionKey, parsed.sessionId, parsed.taskId, parsed.messageId);
186
180
  // Check for ACP runtime binding on this A2A conversation
187
181
  const runtimeRoute = resolveRuntimeConversationBindingRoute({
188
182
  route,
@@ -375,16 +369,6 @@ export async function handleXYMessage(params) {
375
369
  const cleanup = () => {
376
370
  if (cleaned)
377
371
  return;
378
- // Check for pending subagent wait on this session (any taskId).
379
- // Steer dispatches have a different taskId, so we check both
380
- // exact match and session-wide presence.
381
- const pendingWait = getWaitState(parsed.sessionId, parsed.taskId) ??
382
- (hasWaitState(parsed.sessionId) ? { deliveredCompletions: 0, expectedCompletions: 1 } : null);
383
- if (pendingWait && pendingWait.deliveredCompletions < pendingWait.expectedCompletions) {
384
- // Subagent wait active — skip cleanup, session stays alive
385
- log.log(`[BOT] Cleanup suppressed — subagent wait active on session, taskId=${parsed.taskId}`);
386
- return;
387
- }
388
372
  cleaned = true;
389
373
  log.log(`[BOT] Cleanup started`);
390
374
  streamingSignals.delete(parsed.sessionId);
@@ -424,35 +408,13 @@ export async function handleXYMessage(params) {
424
408
  log.log(`[BOT-DISPATCH] withReplyDispatcher starting, sessionKey=${route.sessionKey}`);
425
409
  await core.channel.reply.withReplyDispatcher({
426
410
  dispatcher,
427
- onSettled: async () => {
411
+ onSettled: () => {
428
412
  log.log(`[BOT] onSettled, steered=${steerState.steered}`);
429
413
  // 🔑 When steered, skip cleanup — the first message's dispatcher is still running
430
414
  if (steerState.steered) {
431
415
  log.log(`[BOT] Steered dispatch settled, skipping cleanup`);
432
416
  return;
433
417
  }
434
- // Subagent wait state: if parent has pending subagents, mark settled
435
- // and defer finalization. Cleanup is suppressed so the session stays alive.
436
- const pendingWait = getWaitState(parsed.sessionId, parsed.taskId);
437
- if (pendingWait && !pendingWait.parentSettled) {
438
- const transition = markParentSettled(parsed.sessionId, parsed.taskId);
439
- if (transition?.shouldFinalize) {
440
- // All completions arrived before parent settled → finalize now
441
- const { deliverSubagentFinalResult } = await import("./outbound.js");
442
- await deliverSubagentFinalResult({
443
- config,
444
- state: transition.state,
445
- reason: "all-subagent-results-delivered-before-parent-settled",
446
- });
447
- streamingSignals.delete(parsed.sessionId);
448
- log.log(`[BOT] Subagent wait complete on parent settled; final response delivered`);
449
- }
450
- else {
451
- log.log(`[BOT] Subagent wait active, preserving session context for completion`);
452
- }
453
- dispatcherUpdaters.delete(parsed.sessionId);
454
- return;
455
- }
456
418
  // cleanup 已由 onIdleComplete 在 onIdle 的 finally 中执行。
457
419
  // onSettled 不做任何清理(直接在这里清理会发生 race condition)。
458
420
  dispatcherUpdaters.delete(parsed.sessionId);
@@ -126,14 +126,27 @@ export const xyPlugin = {
126
126
  ? { conversationId, matchPriority: 2 }
127
127
  : null;
128
128
  },
129
- resolveCommandConversation: ({ accountId }) => {
129
+ resolveCommandConversation: ({ accountId, sessionKey }) => {
130
130
  // xiaoyi-channel: the A2A sessionId IS the conversationId.
131
- // Read from the current ALS session context.
131
+ // 1. Prefer the current ALS session context (works when the command
132
+ // is processed inside an active A2A message-handling turn).
132
133
  const ctx = getCurrentSessionContext();
133
- const sessionId = ctx?.sessionId?.trim();
134
- if (!sessionId)
135
- return null;
136
- return { conversationId: sessionId };
134
+ const alsSessionId = ctx?.sessionId?.trim();
135
+ if (alsSessionId)
136
+ return { conversationId: alsSessionId };
137
+ // 2. Fall back to parsing the session key. For xiaoyi-channel the
138
+ // session key has the form:
139
+ // agent:<agentId>:xiaoyi-channel:<accountId>:<sessionId>
140
+ // The last `:`-delimited segment is the A2A sessionId.
141
+ if (sessionKey) {
142
+ const lastColon = sessionKey.lastIndexOf(":");
143
+ if (lastColon >= 0) {
144
+ const sessionId = sessionKey.slice(lastColon + 1).trim();
145
+ if (sessionId)
146
+ return { conversationId: sessionId };
147
+ }
148
+ }
149
+ return null;
137
150
  },
138
151
  },
139
152
  reload: {
@@ -10,9 +10,6 @@ export interface SendA2AResponseParams {
10
10
  text?: string;
11
11
  append: boolean;
12
12
  final: boolean;
13
- /** Optional: reuse an existing artifactId instead of generating a new one.
14
- * Used by subagent completion to link results to the original artifact. */
15
- artifactId?: string;
16
13
  files?: Array<{
17
14
  fileName: string;
18
15
  fileType: string;
@@ -53,8 +50,6 @@ export interface SendStatusUpdateParams {
53
50
  messageId: string;
54
51
  text: string;
55
52
  state: "submitted" | "working" | "input-required" | "completed" | "canceled" | "failed" | "unknown";
56
- /** When false, use the exact taskId/messageId passed in. Default true (dynamic lookup). */
57
- useLatestTask?: boolean;
58
53
  }
59
54
  /**
60
55
  * Send an A2A task status update.
@@ -45,7 +45,7 @@ function buildTextPreview(text) {
45
45
  * Send an A2A artifact update response.
46
46
  */
47
47
  export async function sendA2AResponse(params) {
48
- const { config, sessionId, taskId, messageId, text, append, final, artifactId, files, errorCode, errorMessage, log: shouldLog = true } = params;
48
+ const { config, sessionId, taskId, messageId, text, append, final, files, errorCode, errorMessage, log: shouldLog = true } = params;
49
49
  const log = logger.withContext(sessionId, taskId);
50
50
  // 审批桥接:将 OpenClaw 的审批提示翻译成用户友好的确认文案
51
51
  const bridgedText = text === undefined ? text : rewriteOutboundApprovalText(sessionId, text);
@@ -57,7 +57,7 @@ export async function sendA2AResponse(params) {
57
57
  lastChunk: true,
58
58
  final,
59
59
  artifact: {
60
- artifactId: artifactId ?? uuidv4(),
60
+ artifactId: uuidv4(),
61
61
  parts: [],
62
62
  },
63
63
  };
@@ -157,7 +157,7 @@ export async function sendReasoningTextUpdate(params) {
157
157
  * Follows A2A protocol standard format with nested status object.
158
158
  */
159
159
  export async function sendStatusUpdate(params) {
160
- const { config, sessionId, taskId, messageId, text, state, useLatestTask = true } = params;
160
+ const { config, sessionId, taskId, messageId, text, state } = params;
161
161
  const log = logger.withContext(sessionId, taskId);
162
162
  // 审批桥接和脱敏
163
163
  const bridgedText = rewriteOutboundApprovalText(sessionId, text);
@@ -1,11 +1,4 @@
1
1
  type ChannelOutboundAdapter = any;
2
- import { resolveXYConfig } from "./config.js";
3
- export declare function deliverSubagentFinalResult(params: {
4
- config: ReturnType<typeof resolveXYConfig>;
5
- state: import("./subagent-wait-state.js").SubagentWaitState;
6
- reason?: string;
7
- text?: string;
8
- }): Promise<void>;
9
2
  /**
10
3
  * Outbound adapter for sending messages from OpenClaw to XY.
11
4
  * Uses Push service for direct message delivery.
@@ -6,45 +6,11 @@ import { resolveXYConfig } from "./config.js";
6
6
  import { XYFileUploadService } from "./file-upload.js";
7
7
  import { XYPushService } from "./push.js";
8
8
  import { getCurrentSessionContext } from "./tools/session-manager.js";
9
- import { getWaitState, addCompletionText, clearWaitState, } from "./subagent-wait-state.js";
10
- import { decrementTaskIdRef } from "./task-manager.js";
11
- import { sendA2AResponse, sendStatusUpdate } from "./formatter.js";
12
9
  import { savePushData } from "./utils/pushdata-manager.js";
13
10
  import { getAllPushIds } from "./utils/pushid-manager.js";
14
11
  import { logger } from "./utils/logger.js";
15
12
  // Special marker for default push delivery when no target is specified
16
13
  const DEFAULT_PUSH_MARKER = "default";
17
- // ─── Subagent completion delivery ──────────────────────────────
18
- export async function deliverSubagentFinalResult(params) {
19
- const { config, state, reason, text } = params;
20
- const log = logger.withContext(state.sessionId, state.taskId);
21
- const finalText = state.completionTexts.length > 0
22
- ? state.completionTexts.join("\n\n")
23
- : (text || undefined) ?? "子任务已完成";
24
- await sendStatusUpdate({
25
- config,
26
- sessionId: state.sessionId,
27
- taskId: state.taskId,
28
- messageId: state.messageId,
29
- text: "任务处理已完成~",
30
- state: "completed",
31
- useLatestTask: false,
32
- });
33
- await sendA2AResponse({
34
- config,
35
- sessionId: state.sessionId,
36
- taskId: state.taskId,
37
- messageId: state.messageId,
38
- text: finalText,
39
- append: false,
40
- final: true,
41
- artifactId: state.artifactId,
42
- });
43
- clearWaitState(state.sessionId, reason ?? "all-subagent-results-delivered", state.taskId);
44
- decrementTaskIdRef(state.sessionId, state.taskId);
45
- log.log(`[xyOutbound] Subagent final delivered to original A2A task, reason=${reason}`);
46
- }
47
- // ─── MIME types ─────────────────────────────────────────────────
48
14
  // File extension to MIME type mapping
49
15
  const FILE_TYPE_TO_MIME_TYPE = {
50
16
  txt: "text/plain",
@@ -106,28 +72,20 @@ export const xyOutbound = {
106
72
  // If the target doesn't contain "::", try to enhance it with taskId from session context
107
73
  if (!trimmedTo.includes("::")) {
108
74
  logger.log(`[xyOutbound.resolveTarget] Target "${trimmedTo}" missing taskId, looking up session context`);
109
- // Try ALS context first (normal agent turn)
75
+ // Try to get the current session context
110
76
  const sessionContext = getCurrentSessionContext();
111
77
  if (sessionContext && sessionContext.sessionId === trimmedTo) {
112
78
  const enhancedTarget = `${trimmedTo}::${sessionContext.taskId}`;
113
- logger.log(`[xyOutbound.resolveTarget] Enhanced target from ALS: ${enhancedTarget}`);
79
+ logger.log(`[xyOutbound.resolveTarget] Enhanced target: ${enhancedTarget}`);
114
80
  return {
115
81
  ok: true,
116
82
  to: enhancedTarget,
117
83
  };
118
84
  }
119
- // Fallback: subagent wait state (ALS is null for subagent completion deliveries)
120
- const waitState = getWaitState(trimmedTo);
121
- if (waitState) {
122
- const enhancedTarget = `${waitState.sessionId}::${waitState.taskId}`;
123
- logger.withContext(waitState.sessionId, waitState.taskId).log(`[xyOutbound.resolveTarget] Enhanced target from subagent wait state: ${enhancedTarget}`);
124
- return {
125
- ok: true,
126
- to: enhancedTarget,
127
- };
85
+ else {
86
+ logger.log(`[xyOutbound.resolveTarget] Could not find matching session context for "${trimmedTo}"`);
87
+ // Still return the original target, but it may fail in sendMedia
128
88
  }
129
- logger.log(`[xyOutbound.resolveTarget] Could not find matching session context or wait state for "${trimmedTo}"`);
130
- // Still return the original target, but it may fail in sendMedia
131
89
  }
132
90
  // Otherwise, use the provided target (either already in correct format or for sendText)
133
91
  logger.log(`[xyOutbound.resolveTarget] Using provided target:`, trimmedTo);
@@ -139,31 +97,6 @@ export const xyOutbound = {
139
97
  sendText: async ({ cfg, to, text, accountId }) => {
140
98
  // Resolve configuration
141
99
  const config = resolveXYConfig(cfg);
142
- // ── Subagent completion text capture ─────────────────────────
143
- // Subagent completions may arrive here when openclaw's announce
144
- // flow delivers through the channel outbound. We capture the text
145
- // for the final A2A response but do NOT track delivery count here.
146
- // Delivery tracking is done by the subagent_ended hook (index.ts).
147
- if (to && to !== DEFAULT_PUSH_MARKER) {
148
- const [targetSessionId, targetTaskId] = String(to).split("::");
149
- const waitState = getWaitState(targetSessionId, targetTaskId);
150
- const alsCtx = getCurrentSessionContext();
151
- const isFromWaitState = waitState &&
152
- (!alsCtx || alsCtx.taskId !== targetTaskId);
153
- if (isFromWaitState && !waitState.finalizationClaimed) {
154
- const log = logger.withContext(waitState.sessionId, waitState.taskId);
155
- log.log(`[xyOutbound.sendText] Subagent completion text captured, len=${text?.length ?? 0}`);
156
- // Store the text for later use in final response
157
- addCompletionText(targetSessionId, targetTaskId, text);
158
- // Skip push delivery — subagent completions go through A2A
159
- return {
160
- channel: "xiaoyi-channel",
161
- messageId: waitState.artifactId,
162
- chatId: to,
163
- };
164
- }
165
- }
166
- // ── End subagent text capture ───────────────────────────────
167
100
  // Handle default push marker (for cron jobs without explicit target)
168
101
  let actualTo = to;
169
102
  if (to === DEFAULT_PUSH_MARKER) {
@@ -2,7 +2,6 @@ import { getXYRuntime } from "./runtime.js";
2
2
  import { sendA2AResponse, sendStatusUpdate, sendReasoningTextUpdate, sendCommand } from "./formatter.js";
3
3
  import { resolveXYConfig } from "./config.js";
4
4
  import { clearRunCrossTaskSentFiles, getCurrentSessionContext } from "./tools/session-manager.js";
5
- import { getWaitState, clearWaitState, attachHeartbeat, } from "./subagent-wait-state.js";
6
5
  import fs from "fs/promises";
7
6
  import path from "path";
8
7
  import { logger } from "./utils/logger.js";
@@ -233,34 +232,6 @@ export function createXYReplyDispatcher(params) {
233
232
  stopStatusInterval();
234
233
  return;
235
234
  }
236
- // ── Subagent wait state check ─────────────────────────────
237
- // If this session has pending subagent completions, suppress final:true
238
- // and keep the A2A session alive. The final response will be sent when
239
- // all completions arrive via xyOutbound.sendText interception.
240
- const waitState = getWaitState(sessionId, taskId);
241
- if (waitState &&
242
- waitState.deliveredCompletions < waitState.expectedCompletions &&
243
- !finalSent) {
244
- scopedLog().log(`[ON-IDLE] Waiting for subagent completions ${waitState.deliveredCompletions}/${waitState.expectedCompletions}, suppressing final:true`);
245
- try {
246
- await sendStatusUpdate({
247
- config,
248
- sessionId,
249
- taskId,
250
- messageId,
251
- text: "子任务正在处理中,请稍候~",
252
- state: "working",
253
- useLatestTask: false,
254
- });
255
- }
256
- catch (err) {
257
- scopedLog().error(`[ON-IDLE] Failed to send subagent waiting status:`, err);
258
- }
259
- // Attach heartbeat so the status interval stays alive during wait
260
- attachHeartbeat(sessionId, taskId, stopStatusInterval);
261
- return;
262
- }
263
- // ── End subagent wait state check ─────────────────────────
264
235
  // 🔑 用 try/finally 确保 cleanup 在 onIdle 的 async 工作全部完成后才执行。
265
236
  // openclaw 的 waitForIdle() 以 void options.onIdle?.() 调用 onIdle,
266
237
  // 不会 await 返回的 Promise,因此 onSettled 可能在 onIdle 中途触发。
@@ -307,9 +278,6 @@ export function createXYReplyDispatcher(params) {
307
278
  final: true,
308
279
  });
309
280
  finalSent = true;
310
- if (waitState) {
311
- clearWaitState(sessionId, "main-final-delivered", taskId);
312
- }
313
281
  scopedLog().log(`[ON-IDLE] Sent final response (empty, stream end)`);
314
282
  }
315
283
  catch (err) {
@@ -1,14 +1,8 @@
1
- interface TaskIdEntry {
2
- taskId: string;
3
- messageId: string;
4
- updatedAt: number;
5
- }
6
1
  interface TaskIdBinding {
7
2
  sessionId: string;
8
3
  currentTaskId: string;
9
4
  currentMessageId: string;
10
5
  updatedAt: number;
11
- tasks?: TaskIdEntry[];
12
6
  }
13
7
  /**
14
8
  * 注册或更新session的活跃taskId。
@@ -17,10 +11,8 @@ interface TaskIdBinding {
17
11
  export declare function registerTaskId(sessionId: string, taskId: string, messageId: string): boolean;
18
12
  /**
19
13
  * 移除session的活跃taskId(消息处理完成时调用)。
20
- * @param expectedTaskId 可选:精确移除指定的 taskId,而非整个 session 绑定。
21
- * 用于 subagent completion 等需要精确清理特定 task 的场景。
22
14
  */
23
- export declare function decrementTaskIdRef(sessionId: string, expectedTaskId?: string): void;
15
+ export declare function decrementTaskIdRef(sessionId: string): void;
24
16
  /**
25
17
  * 获取session的当前活跃taskId。
26
18
  * 仅供 reply-dispatcher 跨链读取 steer 更新后的 taskId。
@@ -12,27 +12,6 @@ if (!_g.__xyActiveTaskIds) {
12
12
  _g.__xyActiveTaskIds = new Map();
13
13
  }
14
14
  const activeTaskIds = _g.__xyActiveTaskIds;
15
- function normalizeTasks(binding) {
16
- if (Array.isArray(binding.tasks) && binding.tasks.length > 0) {
17
- return binding.tasks;
18
- }
19
- return [{
20
- taskId: binding.currentTaskId,
21
- messageId: binding.currentMessageId,
22
- updatedAt: binding.updatedAt,
23
- }];
24
- }
25
- function syncCurrent(binding, tasks) {
26
- binding.tasks = tasks;
27
- const current = tasks[tasks.length - 1];
28
- if (!current) {
29
- activeTaskIds.delete(binding.sessionId);
30
- return;
31
- }
32
- binding.currentTaskId = current.taskId;
33
- binding.currentMessageId = current.messageId;
34
- binding.updatedAt = current.updatedAt;
35
- }
36
15
  /**
37
16
  * 注册或更新session的活跃taskId。
38
17
  * Returns true if this was an update (session already had an active task).
@@ -41,9 +20,9 @@ export function registerTaskId(sessionId, taskId, messageId) {
41
20
  const existing = activeTaskIds.get(sessionId);
42
21
  if (existing) {
43
22
  logger.log(`[TASK_MANAGER] Updating taskId: ${existing.currentTaskId} → ${taskId}`);
44
- const tasks = normalizeTasks(existing).filter((entry) => entry.taskId !== taskId);
45
- tasks.push({ taskId, messageId, updatedAt: Date.now() });
46
- syncCurrent(existing, tasks);
23
+ existing.currentTaskId = taskId;
24
+ existing.currentMessageId = messageId;
25
+ existing.updatedAt = Date.now();
47
26
  return true; // isUpdate
48
27
  }
49
28
  else {
@@ -52,7 +31,6 @@ export function registerTaskId(sessionId, taskId, messageId) {
52
31
  currentTaskId: taskId,
53
32
  currentMessageId: messageId,
54
33
  updatedAt: Date.now(),
55
- tasks: [{ taskId, messageId, updatedAt: Date.now() }],
56
34
  });
57
35
  logger.log(`[TASK_MANAGER] Registered new taskId: ${taskId}`);
58
36
  return false;
@@ -60,31 +38,10 @@ export function registerTaskId(sessionId, taskId, messageId) {
60
38
  }
61
39
  /**
62
40
  * 移除session的活跃taskId(消息处理完成时调用)。
63
- * @param expectedTaskId 可选:精确移除指定的 taskId,而非整个 session 绑定。
64
- * 用于 subagent completion 等需要精确清理特定 task 的场景。
65
41
  */
66
- export function decrementTaskIdRef(sessionId, expectedTaskId) {
67
- if (!expectedTaskId) {
68
- logger.log(`[TASK_MANAGER] Removing taskId`);
69
- activeTaskIds.delete(sessionId);
70
- return;
71
- }
72
- const existing = activeTaskIds.get(sessionId);
73
- if (!existing)
74
- return;
75
- const tasks = normalizeTasks(existing);
76
- const nextTasks = tasks.filter((entry) => entry.taskId !== expectedTaskId);
77
- if (nextTasks.length === tasks.length) {
78
- logger.log(`[TASK_MANAGER] Preserving taskId ${existing.currentTaskId}; completed task ${expectedTaskId} is not active`);
79
- return;
80
- }
81
- syncCurrent(existing, nextTasks);
82
- if (nextTasks.length > 0) {
83
- logger.log(`[TASK_MANAGER] Removed taskId ${expectedTaskId}, restored current taskId ${existing.currentTaskId}`);
84
- }
85
- else {
86
- logger.log(`[TASK_MANAGER] Removing taskId (last task ${expectedTaskId})`);
87
- }
42
+ export function decrementTaskIdRef(sessionId) {
43
+ logger.log(`[TASK_MANAGER] Removing taskId`);
44
+ activeTaskIds.delete(sessionId);
88
45
  }
89
46
  /**
90
47
  * 获取session的当前活跃taskId。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi-channel",
3
- "version": "0.0.192-next",
3
+ "version": "0.0.193-next",
4
4
  "description": "OpenClaw Xiaoyi Channel plugin - Xiaoyi A2A protocol integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",