@ynhcj/xiaoyi-channel 0.0.171-next → 0.0.172-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 (116) hide show
  1. package/dist/index.js +3 -3
  2. package/dist/src/bot.js +29 -7
  3. package/dist/src/channel.js +64 -59
  4. package/dist/src/client.d.ts +0 -5
  5. package/dist/src/client.js +0 -10
  6. package/dist/src/cspl/call_api.js +0 -2
  7. package/dist/src/cspl/sentinel_hook.js +8 -9
  8. package/dist/src/formatter.js +23 -12
  9. package/dist/src/memory-query-handler.js +33 -1
  10. package/dist/src/monitor.js +9 -20
  11. package/dist/src/provider.js +62 -33
  12. package/dist/src/reply-dispatcher.js +45 -29
  13. package/dist/src/task-manager.d.ts +6 -5
  14. package/dist/src/task-manager.js +9 -5
  15. package/dist/src/tools/agent-as-skill-tool.d.ts +2 -45
  16. package/dist/src/tools/agent-as-skill-tool.js +146 -149
  17. package/dist/src/tools/calendar-tool.d.ts +2 -24
  18. package/dist/src/tools/calendar-tool.js +117 -115
  19. package/dist/src/tools/call-device-tool.d.ts +6 -20
  20. package/dist/src/tools/call-device-tool.js +138 -116
  21. package/dist/src/tools/call-phone-tool.d.ts +2 -21
  22. package/dist/src/tools/call-phone-tool.js +114 -112
  23. package/dist/src/tools/check-plugin-privilege-tool.d.ts +2 -16
  24. package/dist/src/tools/check-plugin-privilege-tool.js +143 -141
  25. package/dist/src/tools/create-alarm-tool.d.ts +2 -39
  26. package/dist/src/tools/create-alarm-tool.js +231 -229
  27. package/dist/src/tools/create-all-tools.js +10 -5
  28. package/dist/src/tools/delete-alarm-tool.d.ts +2 -15
  29. package/dist/src/tools/delete-alarm-tool.js +136 -134
  30. package/dist/src/tools/discover-cross-devices-tool.d.ts +2 -16
  31. package/dist/src/tools/discover-cross-devices-tool.js +124 -121
  32. package/dist/src/tools/display-a2ui-card-tool.d.ts +2 -27
  33. package/dist/src/tools/display-a2ui-card-tool.js +68 -65
  34. package/dist/src/tools/get-alarm-tool-schema.d.ts +2 -1
  35. package/dist/src/tools/get-alarm-tool-schema.js +16 -10
  36. package/dist/src/tools/get-calendar-tool-schema.d.ts +2 -1
  37. package/dist/src/tools/get-calendar-tool-schema.js +12 -8
  38. package/dist/src/tools/get-collection-tool-schema.d.ts +2 -1
  39. package/dist/src/tools/get-collection-tool-schema.js +11 -9
  40. package/dist/src/tools/get-contact-tool-schema.d.ts +2 -1
  41. package/dist/src/tools/get-contact-tool-schema.js +16 -10
  42. package/dist/src/tools/get-device-file-tool-schema.d.ts +2 -1
  43. package/dist/src/tools/get-device-file-tool-schema.js +13 -9
  44. package/dist/src/tools/get-email-tool-schema.d.ts +2 -1
  45. package/dist/src/tools/get-email-tool-schema.js +11 -8
  46. package/dist/src/tools/get-note-tool-schema.d.ts +2 -1
  47. package/dist/src/tools/get-note-tool-schema.js +14 -9
  48. package/dist/src/tools/get-photo-tool-schema.d.ts +2 -1
  49. package/dist/src/tools/get-photo-tool-schema.js +12 -9
  50. package/dist/src/tools/image-reading-tool.d.ts +2 -28
  51. package/dist/src/tools/image-reading-tool.js +76 -76
  52. package/dist/src/tools/location-tool.d.ts +2 -11
  53. package/dist/src/tools/location-tool.js +92 -93
  54. package/dist/src/tools/login-token-tool.d.ts +2 -20
  55. package/dist/src/tools/login-token-tool.js +125 -122
  56. package/dist/src/tools/modify-alarm-tool.d.ts +2 -47
  57. package/dist/src/tools/modify-alarm-tool.js +252 -250
  58. package/dist/src/tools/modify-note-tool.d.ts +2 -20
  59. package/dist/src/tools/modify-note-tool.js +109 -107
  60. package/dist/src/tools/note-tool.d.ts +2 -20
  61. package/dist/src/tools/note-tool.js +108 -106
  62. package/dist/src/tools/query-app-message-tool.d.ts +2 -28
  63. package/dist/src/tools/query-app-message-tool.js +113 -111
  64. package/dist/src/tools/query-memory-data-tool.d.ts +2 -28
  65. package/dist/src/tools/query-memory-data-tool.js +114 -112
  66. package/dist/src/tools/query-todo-task-tool.d.ts +2 -24
  67. package/dist/src/tools/query-todo-task-tool.js +108 -106
  68. package/dist/src/tools/save-file-to-phone-tool.d.ts +2 -24
  69. package/dist/src/tools/save-file-to-phone-tool.js +132 -130
  70. package/dist/src/tools/save-media-to-gallery-tool.d.ts +2 -24
  71. package/dist/src/tools/save-media-to-gallery-tool.js +139 -137
  72. package/dist/src/tools/save-self-evolution-skill-tool.d.ts +2 -54
  73. package/dist/src/tools/save-self-evolution-skill-tool.js +194 -194
  74. package/dist/src/tools/search-alarm-tool.d.ts +2 -34
  75. package/dist/src/tools/search-alarm-tool.js +176 -174
  76. package/dist/src/tools/search-calendar-tool.d.ts +2 -24
  77. package/dist/src/tools/search-calendar-tool.js +150 -148
  78. package/dist/src/tools/search-contact-tool.d.ts +2 -16
  79. package/dist/src/tools/search-contact-tool.js +103 -101
  80. package/dist/src/tools/search-email-tool.d.ts +2 -21
  81. package/dist/src/tools/search-email-tool.js +112 -110
  82. package/dist/src/tools/search-file-tool.d.ts +2 -16
  83. package/dist/src/tools/search-file-tool.js +106 -104
  84. package/dist/src/tools/search-message-tool.d.ts +2 -16
  85. package/dist/src/tools/search-message-tool.js +105 -103
  86. package/dist/src/tools/search-note-tool.d.ts +2 -16
  87. package/dist/src/tools/search-note-tool.js +100 -98
  88. package/dist/src/tools/search-photo-gallery-tool.d.ts +2 -21
  89. package/dist/src/tools/search-photo-gallery-tool.js +37 -35
  90. package/dist/src/tools/send-cross-device-task-tool.d.ts +2 -35
  91. package/dist/src/tools/send-cross-device-task-tool.js +143 -138
  92. package/dist/src/tools/send-email-tool.d.ts +2 -24
  93. package/dist/src/tools/send-email-tool.js +110 -108
  94. package/dist/src/tools/send-file-to-user-tool.d.ts +2 -20
  95. package/dist/src/tools/send-file-to-user-tool.js +176 -172
  96. package/dist/src/tools/send-html-card-tool.d.ts +2 -20
  97. package/dist/src/tools/send-html-card-tool.js +87 -85
  98. package/dist/src/tools/send-message-tool.d.ts +2 -20
  99. package/dist/src/tools/send-message-tool.js +124 -122
  100. package/dist/src/tools/session-manager.d.ts +50 -7
  101. package/dist/src/tools/session-manager.js +231 -40
  102. package/dist/src/tools/upload-file-tool.d.ts +2 -20
  103. package/dist/src/tools/upload-file-tool.js +82 -80
  104. package/dist/src/tools/upload-photo-tool.d.ts +2 -20
  105. package/dist/src/tools/upload-photo-tool.js +70 -68
  106. package/dist/src/tools/xiaoyi-add-collection-tool.d.ts +2 -32
  107. package/dist/src/tools/xiaoyi-add-collection-tool.js +148 -146
  108. package/dist/src/tools/xiaoyi-collection-tool.d.ts +2 -20
  109. package/dist/src/tools/xiaoyi-collection-tool.js +116 -114
  110. package/dist/src/tools/xiaoyi-delete-collection-tool.d.ts +2 -15
  111. package/dist/src/tools/xiaoyi-delete-collection-tool.js +129 -127
  112. package/dist/src/tools/xiaoyi-gui-tool.d.ts +2 -16
  113. package/dist/src/tools/xiaoyi-gui-tool.js +95 -92
  114. package/dist/src/utils/logger.js +14 -3
  115. package/dist/src/websocket.d.ts +1 -1
  116. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { xiaoyiProvider } from "./src/provider.js";
3
3
  import { xyPlugin } from "./src/channel.js";
4
4
  import registerSentinelHook from "./src/cspl/sentinel_hook.js";
5
5
  import { setXYRuntime } from "./src/runtime.js";
6
- import { markCronToolCall, clearCronToolCall, getCurrentSessionContext } from "./src/tools/session-manager.js";
6
+ import { markCronToolCall, clearCronToolCall, getSessionContext } from "./src/tools/session-manager.js";
7
7
  import { configManager } from "./src/utils/config-manager.js";
8
8
  import { setJobPushId } from "./src/utils/cron-push-map.js";
9
9
  import { getAllPushIds } from "./src/utils/pushid-manager.js";
@@ -54,10 +54,10 @@ async function captureCronAddMapping(event, ctx) {
54
54
  return;
55
55
  }
56
56
  console.log(`[CRONMAP] extracted jobId=${jobId}`);
57
- const sessionCtx = getCurrentSessionContext();
57
+ const sessionCtx = ctx.sessionKey ? getSessionContext(ctx.sessionKey) : null;
58
58
  const sessionId = sessionCtx?.sessionId;
59
59
  if (!sessionId) {
60
- console.log(`[CRONMAP] skip: no sessionId in ALS scope (ctxFound=${!!sessionCtx})`);
60
+ console.log(`[CRONMAP] skip: no sessionId (sessionKey=${ctx.sessionKey}, ctxFound=${!!sessionCtx})`);
61
61
  return;
62
62
  }
63
63
  const pushId = await resolvePushId(sessionId);
package/dist/src/bot.js CHANGED
@@ -6,7 +6,7 @@ import { downloadFilesFromParts } from "./file-download.js";
6
6
  import { resolveXYConfig } from "./config.js";
7
7
  import { sendStatusUpdate, sendClearContextResponse, sendTasksCancelResponse, sendA2AResponse } from "./formatter.js";
8
8
  import { appendSelfEvolutionKeywordNudge, shouldNudgeForSelfEvolutionKeyword, } from "./self-evolution-keyword.js";
9
- import { runWithSessionContext } from "./tools/session-manager.js";
9
+ import { registerSession, unregisterSession, runWithSessionContext } from "./tools/session-manager.js";
10
10
  import { configManager } from "./utils/config-manager.js";
11
11
  import { addPushId } from "./utils/pushid-manager.js";
12
12
  import { getPushDataById } from "./utils/pushdata-manager.js";
@@ -169,10 +169,21 @@ export async function handleXYMessage(params) {
169
169
  },
170
170
  });
171
171
  log.log(`[BOT] Resolved route, sessionKey=${route.sessionKey}`);
172
- // ALS only: no registerSession. The sessionContext built below is handed
173
- // to runWithSessionContext() inside withReplyDispatcher.run, which is the
174
- // single wrap point for the whole agent turn.
172
+ // Steer injections skip session registration to avoid refCount leaks
175
173
  if (!skipReg) {
174
+ registerSession(route.sessionKey, {
175
+ config,
176
+ sessionId: parsed.sessionId,
177
+ distributionSessionId,
178
+ taskId: parsed.taskId,
179
+ messageId: parsed.messageId,
180
+ agentId: route.accountId,
181
+ deviceType,
182
+ appVer: appVer ?? undefined,
183
+ displayVersion: displayVersion ?? undefined,
184
+ modelName,
185
+ runCrossTaskContext: runCrossTaskContext ?? undefined,
186
+ });
176
187
  // 🔑 Sync A2A modelName to OpenClaw session store so that session_status
177
188
  // reports the correct model. Without this, session_status returns the
178
189
  // configured default model instead of the A2A-specified one.
@@ -380,6 +391,7 @@ export async function handleXYMessage(params) {
380
391
  }
381
392
  streamingSignals.delete(parsed.sessionId);
382
393
  decrementTaskIdRef(parsed.sessionId);
394
+ unregisterSession(route.sessionKey);
383
395
  log.log(`[BOT] Cleanup completed`);
384
396
  },
385
397
  run: () => {
@@ -389,7 +401,6 @@ export async function handleXYMessage(params) {
389
401
  // signal init complete to release the global dispatch gate
390
402
  // for the next session.
391
403
  const dispatchPromise = runWithSessionContext(sessionContext, async () => {
392
- log.log(`[ALS-PROOF] bot entered dispatch scope sessionId=${sessionContext.sessionId} taskId=${sessionContext.taskId} isSteer=false`);
393
404
  log.log(`[BOT-DISPATCH] dispatchReplyFromConfig starting, body.length=${ctxPayload.Body?.length ?? 0}`);
394
405
  try {
395
406
  const result = await core.channel.reply.dispatchReplyFromConfig({
@@ -422,7 +433,7 @@ export async function handleXYMessage(params) {
422
433
  errLog.error("Failed to handle XY message:", err);
423
434
  runtime.error?.(`xy: Failed to handle message: ${String(err)}`);
424
435
  errLog.log(`[BOT] Error occurred, attempting cleanup`);
425
- // 🔑 错误时也要清理taskIdsession 走 ALS,作用域退出自动清理)
436
+ // 🔑 错误时也要清理taskIdsession
426
437
  try {
427
438
  const params = message.params;
428
439
  const sessionId = params?.sessionId;
@@ -430,6 +441,18 @@ export async function handleXYMessage(params) {
430
441
  errLog.log(`[BOT] Cleaning up after error`);
431
442
  // 清理 taskId
432
443
  decrementTaskIdRef(sessionId);
444
+ // 清理 session
445
+ const core = getXYRuntime();
446
+ const route = core.channel.routing.resolveAgentRoute({
447
+ cfg,
448
+ channel: "xiaoyi-channel",
449
+ accountId,
450
+ peer: {
451
+ kind: "direct",
452
+ id: sessionId,
453
+ },
454
+ });
455
+ unregisterSession(route.sessionKey);
433
456
  errLog.log(`[BOT] Cleanup completed after error`);
434
457
  }
435
458
  }
@@ -614,7 +637,6 @@ async function dispatchSteerWhenReady(params) {
614
637
  },
615
638
  run: () => {
616
639
  return runWithSessionContext(sessionContext, async () => {
617
- log.log(`[ALS-PROOF] bot entered steer dispatch scope sessionId=${sessionContext.sessionId} taskId=${sessionContext.taskId} isSteer=true`);
618
640
  const result = await core.channel.reply.dispatchReplyFromConfig({
619
641
  ctx: ctxPayload,
620
642
  cfg: params.cfg,
@@ -2,55 +2,15 @@ import { resolveXYConfig, listXYAccountIds, getDefaultXYAccountId } from "./conf
2
2
  import { xyConfigSchema } from "./config-schema.js";
3
3
  import { xyOutbound } from "./outbound.js";
4
4
  import { filterToolsByDevice } from "./tools/device-tool-map.js";
5
- import { getCurrentSessionContext } from "./tools/session-manager.js";
5
+ import { getCurrentSessionContext, registerSession } from "./tools/session-manager.js";
6
+ import { createAllTools } from "./tools/create-all-tools.js";
6
7
  import { logger } from "./utils/logger.js";
7
- // Static tool imports (3.24 pattern)
8
- import { locationTool } from "./tools/location-tool.js";
9
- import { xiaoyiGuiTool } from "./tools/xiaoyi-gui-tool.js";
10
- import { sendFileToUserTool } from "./tools/send-file-to-user-tool.js";
11
- import { sendHtmlCardTool } from "./tools/send-html-card-tool.js";
12
- import { viewPushResultTool } from "./tools/view-push-result-tool.js";
13
- import { imageReadingTool } from "./tools/image-reading-tool.js";
14
- import { timestampToUtc8Tool } from "./tools/timestamp-to-utc8-tool.js";
15
- import { saveSelfEvolutionSkillTool } from "./tools/save-self-evolution-skill-tool.js";
16
- import { callDeviceTool } from "./tools/call-device-tool.js";
17
- import { getNoteToolSchemaTool } from "./tools/get-note-tool-schema.js";
18
- import { getCalendarToolSchemaTool } from "./tools/get-calendar-tool-schema.js";
19
- import { getContactToolSchemaTool } from "./tools/get-contact-tool-schema.js";
20
- import { getPhotoToolSchemaTool } from "./tools/get-photo-tool-schema.js";
21
- import { getDeviceFileToolSchemaTool } from "./tools/get-device-file-tool-schema.js";
22
- import { getAlarmToolSchemaTool } from "./tools/get-alarm-tool-schema.js";
23
- import { getCollectionToolSchemaTool } from "./tools/get-collection-tool-schema.js";
24
- import { loginTokenTool } from "./tools/login-token-tool.js";
25
- import { agentAsSkillTool } from "./tools/agent-as-skill-tool.js";
26
- import { discoverCrossDevicesTool } from "./tools/discover-cross-devices-tool.js";
27
- import { sendCrossDeviceTaskTool } from "./tools/send-cross-device-task-tool.js";
28
- import { displayA2UICardTool } from "./tools/display-a2ui-card-tool.js";
29
- import { checkPluginPrivilegeTool } from "./tools/check-plugin-privilege-tool.js";
30
- const ALL_TOOLS = [
31
- locationTool,
32
- discoverCrossDevicesTool,
33
- sendCrossDeviceTaskTool,
34
- displayA2UICardTool,
35
- callDeviceTool,
36
- getNoteToolSchemaTool,
37
- getCalendarToolSchemaTool,
38
- getContactToolSchemaTool,
39
- getPhotoToolSchemaTool,
40
- xiaoyiGuiTool,
41
- getDeviceFileToolSchemaTool,
42
- getAlarmToolSchemaTool,
43
- getCollectionToolSchemaTool,
44
- sendFileToUserTool,
45
- sendHtmlCardTool,
46
- viewPushResultTool,
47
- imageReadingTool,
48
- timestampToUtc8Tool,
49
- saveSelfEvolutionSkillTool,
50
- loginTokenTool,
51
- agentAsSkillTool,
52
- checkPluginPrivilegeTool,
53
- ];
8
+ /**
9
+ * Prefix used for synthetic sessionIds created during cron-triggered tool
10
+ * execution. `sendCommand()` checks this prefix to route commands through
11
+ * the push channel instead of the (non-existent) WebSocket session.
12
+ */
13
+ const CRON_SESSION_PREFIX = "cron-";
54
14
  /**
55
15
  * Xiaoyi Channel Plugin for OpenClaw.
56
16
  * Implements Xiaoyi A2A protocol with dual WebSocket connections.
@@ -72,7 +32,7 @@ export const xyPlugin = {
72
32
  ],
73
33
  },
74
34
  capabilities: {
75
- chatTypes: ["direct"],
35
+ chatTypes: ["direct"], // Only private chat (no group support)
76
36
  polls: false,
77
37
  threads: false,
78
38
  media: true,
@@ -89,11 +49,59 @@ export const xyPlugin = {
89
49
  schema: xyConfigSchema,
90
50
  },
91
51
  outbound: xyOutbound,
92
- /** Static tool list (3.24 pattern). Tools read SessionContext at execute time via ALS. */
93
- agentTools: () => {
94
- const ctx = getCurrentSessionContext();
95
- const filtered = filterToolsByDevice(ALL_TOOLS, ctx?.deviceType);
96
- logger.log(`[DEVICE-FILTER] deviceType=${ctx?.deviceType ?? "(none)"}, tools: ${ALL_TOOLS.length} → ${filtered.length} (${filtered.map(t => t.name).join(", ")})`);
52
+ /**
53
+ * Provide channel-specific agent tools.
54
+ *
55
+ * Two execution contexts are supported:
56
+ *
57
+ * 1. **Normal (WebSocket) session** – `getCurrentSessionContext()` returns
58
+ * a context that was registered by bot.ts during message processing.
59
+ * Tools send commands through the WebSocket and listen for responses.
60
+ *
61
+ * 2. **Cron / scheduled-task session** – openclaw's cron runner calls
62
+ * `agentTools({ cfg })` without an active WebSocket session. When no
63
+ * session context exists but `cfg` is provided, we create a synthetic
64
+ * "cron session" with `isCron: true` and a `cron-`-prefixed sessionId.
65
+ * `sendCommand()` detects this prefix and routes commands through the
66
+ * push channel. Response listening (WebSocket events) works unchanged
67
+ * because the gateway WebSocket connection is always active.
68
+ */
69
+ agentTools: (params) => {
70
+ let ctx = getCurrentSessionContext();
71
+ // ── Cron / non-session fallback ──────────────────────────────
72
+ // When no active xy WebSocket session exists but the openclaw cfg
73
+ // is provided (framework calls agentTools({ cfg })), create a
74
+ // synthetic "cron session". This enables cron-triggered agent
75
+ // turns and cross-channel tool calls to use xiaoyi tools via the
76
+ // push channel. sendCommand() detects the "cron-" sessionId
77
+ // prefix and routes commands through push instead of WebSocket.
78
+ if (!ctx && params?.cfg) {
79
+ try {
80
+ const config = resolveXYConfig(params.cfg);
81
+ const cronId = `${CRON_SESSION_PREFIX}${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
82
+ ctx = {
83
+ config,
84
+ sessionId: cronId,
85
+ taskId: cronId,
86
+ messageId: cronId,
87
+ agentId: "default",
88
+ isCron: true,
89
+ };
90
+ // Register so getCurrentSessionContext() fallback can find it
91
+ registerSession(`__cron__${cronId}`, ctx);
92
+ logger.log(`[CRON-TOOLS] Created cron session context: ${cronId}`);
93
+ }
94
+ catch (err) {
95
+ logger.error("[CRON-TOOLS] Failed to create cron context:", err);
96
+ }
97
+ }
98
+ if (!ctx) {
99
+ logger.log("[CREATE-ALL-TOOLS] no session context, returning empty tools list");
100
+ return [];
101
+ }
102
+ const allTools = createAllTools(ctx);
103
+ const filtered = filterToolsByDevice(allTools, ctx.deviceType);
104
+ logger.log(`[DEVICE-FILTER] deviceType=${ctx.deviceType ?? "(none)"}, tools: ${allTools.length} → ${filtered.length} (${filtered.map(t => t.name).join(", ")})`);
97
105
  return filtered;
98
106
  },
99
107
  messaging: {
@@ -105,6 +113,7 @@ export const xyPlugin = {
105
113
  },
106
114
  targetResolver: {
107
115
  looksLikeId: (raw) => {
116
+ // 信任所有非空字符串作为有效的 sessionId
108
117
  const trimmed = raw.trim();
109
118
  return trimmed.length > 0;
110
119
  },
@@ -130,20 +139,16 @@ export const xyPlugin = {
130
139
  reload: {
131
140
  configPrefixes: ["channels.xiaoyi-channel"],
132
141
  },
142
+ // Gateway adapter for receiving messages
133
143
  gateway: {
134
144
  async startAccount(context) {
135
145
  const { monitorXYProvider } = await import("./monitor.js");
136
- const { createXyAcpBindingManager } = await import("./acp-session-binding.js");
137
146
  const account = resolveXYConfig(context.cfg);
138
147
  context.setStatus?.({
139
148
  accountId: context.accountId,
140
149
  wsUrl: account.wsUrl,
141
150
  });
142
151
  context.log?.info(`[${context.accountId}] starting xiaoyi channel (wsUrl: ${account.wsUrl})`);
143
- // Register ACP session binding adapter for this account.
144
- // Enables sessions_spawn(runtime="acp") to bind subagent sessions
145
- // to the current A2A conversation.
146
- createXyAcpBindingManager({ accountId: context.accountId, cfg: context.cfg });
147
152
  return monitorXYProvider({
148
153
  config: context.cfg,
149
154
  runtime: context.runtime,
@@ -1,11 +1,6 @@
1
1
  import { XYWebSocketManager } from "./websocket.js";
2
2
  import type { XYChannelConfig } from "./types.js";
3
3
  import type { RuntimeEnv } from "openclaw/plugin-sdk";
4
- /**
5
- * Get a cached WebSocket manager without requiring config.
6
- * Returns the first available manager. Use when ALS has no SessionContext.
7
- */
8
- export declare function getCachedXYWebSocketManager(): XYWebSocketManager;
9
4
  /**
10
5
  * Get or create a WebSocket manager for the given configuration.
11
6
  * Reuses existing managers if config matches.
@@ -13,16 +13,6 @@ if (!_g.__xyWsManagerCache) {
13
13
  _g.__xyWsManagerCache = new Map();
14
14
  }
15
15
  const wsManagerCache = _g.__xyWsManagerCache;
16
- /**
17
- * Get a cached WebSocket manager without requiring config.
18
- * Returns the first available manager. Use when ALS has no SessionContext.
19
- */
20
- export function getCachedXYWebSocketManager() {
21
- if (wsManagerCache.size === 0) {
22
- throw new Error("No WebSocket manager available in cache");
23
- }
24
- return wsManagerCache.values().next().value;
25
- }
26
16
  /**
27
17
  * Get or create a WebSocket manager for the given configuration.
28
18
  * Reuses existing managers if config matches.
@@ -5,7 +5,6 @@ import https from 'https';
5
5
  import { URL } from 'url';
6
6
  import { getConfig } from './config.js';
7
7
  import { DEFAULT_HTTPS_PORT, HTTP_STATUS_BAD_REQUEST, API_URL_SUFFIX } from './constants.js';
8
- import { logger } from '../utils/logger.js';
9
8
  function buildHeadersForCelia(config, sessionId) {
10
9
  if (!config.uid || !config.apiKey || !config.skillId || !config.requestFrom) {
11
10
  throw new Error('[SENTINEL HOOK] Missing required configuration: uid, apiKey, skillId, or requestFrom is not defined');
@@ -90,7 +89,6 @@ export async function callApi(questionText, api, sessionId, action) {
90
89
  };
91
90
  const httpBody = JSON.stringify(payload);
92
91
  const apiUrl = `${config.api.url}${API_URL_SUFFIX}`;
93
- logger.log(`[SENTINEL HOOK] callApi: action=${action}, x-hag-trace-id=${sessionId}, url=${apiUrl}`);
94
92
  return new Promise((resolve, reject) => {
95
93
  const options = buildRequestOptions(apiUrl, headersForCelia, config.api.timeout);
96
94
  const req = https.request(options, (res) => {
@@ -6,16 +6,15 @@ import { callApi } from './call_api.js';
6
6
  import { processText, extractResultText, validateAndTruncateText, parseSecurityResult, handleExecToolInput, handleMessageToolInput, handleOtherToolInput } from './utils.js';
7
7
  import { ALLOWED_TOOLS, MAX_TEXT_LENGTH, MAX_TOTAL_LENGTH, MIN_TEXT_LENGTH, STEER_ABORT_MESSAGE, TOOL_OUTPUT_ACTION } from './constants.js';
8
8
  import { logger } from '../utils/logger.js';
9
- import { getCurrentSessionContext } from '../tools/session-manager.js';
9
+ import { getSessionContext } from '../tools/session-manager.js';
10
10
  import { tryInjectSteer } from './steer-context.js';
11
11
  // 主入口模块
12
12
  export default function register(api) {
13
13
  api.on("before_tool_call", async (event, ctx) => {
14
14
  logger.log(`[SENTINEL HOOK] before_tool_call_event toolName: ${event.toolName}`);
15
- // 获取真实sessionID:优先使用ALS中的A2A sessionId,降级到OpenClaw runId或随机值
16
- const sessionCtx = getCurrentSessionContext();
17
- const sessionId = sessionCtx?.sessionId || (event.runId?.replace(/-/g, '') || crypto.randomBytes(16).toString('hex'));
18
- logger.log(`[SENTINEL HOOK] Session ID: ${sessionId} (fromALS: ${!!sessionCtx?.sessionId})`);
15
+ // 生成sessionID
16
+ const sessionId = (event.runId?.replace(/-/g, '') || crypto.randomBytes(16).toString('hex'));
17
+ logger.log(`[SENTINEL HOOK] Generated Session ID: ${sessionId}`);
19
18
  // 处理 TOOL_INPUT 数据采集、发送数据,根据扫描结果决定是否阻塞
20
19
  try {
21
20
  let scanResult = null;
@@ -44,10 +43,9 @@ export default function register(api) {
44
43
  }
45
44
  try {
46
45
  logger.log(`[SENTINEL HOOK] after_tool_call_event toolName: ${event.toolName}`);
47
- // 获取真实sessionID:优先使用ALS中的A2A sessionId,降级到OpenClaw runId或随机值
48
- const sessionCtx = getCurrentSessionContext();
49
- const sessionId = sessionCtx?.sessionId || (event.runId?.replace(/-/g, '') || crypto.randomBytes(16).toString('hex'));
50
- logger.log(`[SENTINEL HOOK] Session ID: ${sessionId} (fromALS: ${!!sessionCtx?.sessionId})`);
46
+ // 生成sessionID
47
+ const sessionId = (event.runId?.replace(/-/g, '') || crypto.randomBytes(16).toString('hex'));
48
+ logger.log(`[SENTINEL HOOK] Generated Session ID: ${sessionId}`);
51
49
  // 处理TOOL_OUTPUT数据采集(保持现有逻辑)
52
50
  const resultText = extractResultText(event, event.toolName);
53
51
  const resultTextLength = resultText.length;
@@ -80,6 +78,7 @@ export default function register(api) {
80
78
  logger.log(`[SENTINEL HOOK] TOOL_OUTPUT response: status=${result.status}.`);
81
79
  if (result.status === 'REJECT') {
82
80
  logger.warn('[SENTINEL HOOK] REJECT detected, attempting steer injection');
81
+ const sessionCtx = ctx.sessionKey ? getSessionContext(ctx.sessionKey) : null;
83
82
  if (sessionCtx?.sessionId && sessionCtx?.taskId) {
84
83
  await tryInjectSteer({
85
84
  sessionId: sessionCtx.sessionId,
@@ -2,6 +2,7 @@
2
2
  import { v4 as uuidv4 } from "uuid";
3
3
  import { getXYWebSocketManager } from "./client.js";
4
4
  import { logger } from "./utils/logger.js";
5
+ import { getCurrentTaskId, getCurrentMessageId } from "./task-manager.js";
5
6
  import { redactSensitiveText, containsSensitiveInfo } from "./sensitive-redactor.js";
6
7
  import { rewriteOutboundApprovalText } from "./approval-bridge.js";
7
8
  import { isCronToolCall, getCurrentCronJobId } from "./tools/session-manager.js";
@@ -157,7 +158,11 @@ export async function sendReasoningTextUpdate(params) {
157
158
  */
158
159
  export async function sendStatusUpdate(params) {
159
160
  const { config, sessionId, taskId, messageId, text, state } = params;
160
- const log = logger.withContext(sessionId, taskId);
161
+ // Dynamic lookup: use latest taskId/messageId from task-manager (handles steer/interrupt),
162
+ // fall back to closure-captured values
163
+ const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
164
+ const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
165
+ const log = logger.withContext(sessionId, currentTaskId);
161
166
  // 审批桥接和脱敏
162
167
  const bridgedText = rewriteOutboundApprovalText(sessionId, text);
163
168
  const redactedText = redactSensitiveText(bridgedText);
@@ -172,7 +177,7 @@ export async function sendStatusUpdate(params) {
172
177
  ],
173
178
  });
174
179
  const statusUpdate = {
175
- taskId,
180
+ taskId: currentTaskId,
176
181
  kind: "status-update",
177
182
  final: false, // Status updates should not end the stream
178
183
  status: {
@@ -183,7 +188,7 @@ export async function sendStatusUpdate(params) {
183
188
  // Build JSON-RPC response
184
189
  const jsonRpcResponse = {
185
190
  jsonrpc: "2.0",
186
- id: messageId,
191
+ id: currentMessageId,
187
192
  result: statusUpdate,
188
193
  };
189
194
  // Send via WebSocket
@@ -192,7 +197,7 @@ export async function sendStatusUpdate(params) {
192
197
  msgType: "agent_response",
193
198
  agentId: config.agentId,
194
199
  sessionId,
195
- taskId,
200
+ taskId: currentTaskId,
196
201
  msgDetail: JSON.stringify(jsonRpcResponse),
197
202
  };
198
203
  // Log complete response body
@@ -265,11 +270,15 @@ export async function sendCommand(params) {
265
270
  return sendCommandViaPush({ config, command: commands[0], pushId });
266
271
  }
267
272
  // ── Normal mode: WebSocket ─────────────────────────────────────
268
- const log = logger.withContext(sessionId, taskId);
273
+ // Dynamic lookup: use latest taskId/messageId from task-manager (handles steer/interrupt),
274
+ // fall back to closure-captured values
275
+ const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
276
+ const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
277
+ const log = logger.withContext(sessionId, currentTaskId);
269
278
  // Build artifact update with command as data
270
279
  // Wrap command in commands array as per protocol requirement
271
280
  const artifact = {
272
- taskId,
281
+ taskId: currentTaskId,
273
282
  kind: "artifact-update",
274
283
  append: false,
275
284
  lastChunk: true,
@@ -291,7 +300,7 @@ export async function sendCommand(params) {
291
300
  // Build JSON-RPC response
292
301
  const jsonRpcResponse = {
293
302
  jsonrpc: "2.0",
294
- id: messageId,
303
+ id: currentMessageId,
295
304
  result: artifact,
296
305
  };
297
306
  // Send via WebSocket
@@ -300,7 +309,7 @@ export async function sendCommand(params) {
300
309
  msgType: "agent_response",
301
310
  agentId: config.agentId,
302
311
  sessionId,
303
- taskId,
312
+ taskId: currentTaskId,
304
313
  msgDetail: JSON.stringify(jsonRpcResponse),
305
314
  };
306
315
  // Log complete response body
@@ -320,10 +329,12 @@ export async function sendCard(params) {
320
329
  throw new Error("sendCard does not support cron mode");
321
330
  }
322
331
  // ── Normal mode: WebSocket ─────────────────────────────────────
323
- const log = logger.withContext(sessionId, taskId);
332
+ const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
333
+ const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
334
+ const log = logger.withContext(sessionId, currentTaskId);
324
335
  // Build artifact update with cardsInfo as data
325
336
  const artifact = {
326
- taskId,
337
+ taskId: currentTaskId,
327
338
  kind: "artifact-update",
328
339
  append: false,
329
340
  lastChunk: true,
@@ -343,7 +354,7 @@ export async function sendCard(params) {
343
354
  // Build JSON-RPC response
344
355
  const jsonRpcResponse = {
345
356
  jsonrpc: "2.0",
346
- id: messageId,
357
+ id: currentMessageId,
347
358
  result: artifact,
348
359
  };
349
360
  // Send via WebSocket
@@ -352,7 +363,7 @@ export async function sendCard(params) {
352
363
  msgType: "agent_response",
353
364
  agentId: config.agentId,
354
365
  sessionId,
355
- taskId,
366
+ taskId: currentTaskId,
356
367
  msgDetail: JSON.stringify(jsonRpcResponse),
357
368
  };
358
369
  log.log(`[A2A_CARD] Sending card`);
@@ -42,6 +42,9 @@ export async function handleMemoryQueryEvent(context, cfg) {
42
42
  case "MemoryStateSet":
43
43
  result = handleMemoryStateSet(params);
44
44
  break;
45
+ case "MemoryStateGet":
46
+ result = handleMemoryStateGet();
47
+ break;
45
48
  case "UserMdQuery":
46
49
  result = handleUserMdQuery();
47
50
  break;
@@ -135,6 +138,35 @@ function handleMemoryStateSet(params) {
135
138
  logger.log(`[MEMORY-QUERY] updated ${MEMORY_STATE_KEY}=${value} in ${filePath}`);
136
139
  return { code: 0 };
137
140
  }
141
+ /**
142
+ * Read MEMORYSTATE from .xiaoyiruntime and return its boolean value.
143
+ * Missing file or key defaults to false.
144
+ */
145
+ function handleMemoryStateGet() {
146
+ const filePath = resolveXiaoyiRuntimePath();
147
+ let content;
148
+ try {
149
+ content = readFileSync(filePath, "utf-8");
150
+ }
151
+ catch (err) {
152
+ if (err.code === "ENOENT") {
153
+ logger.log(`[MEMORY-QUERY] ${filePath} not found`);
154
+ }
155
+ else {
156
+ logger.error(`[MEMORY-QUERY] Failed to read ${filePath}:`, err);
157
+ }
158
+ return { memoryState: false };
159
+ }
160
+ for (const line of content.split("\n")) {
161
+ if (line.startsWith(`${MEMORY_STATE_KEY}=`)) {
162
+ const value = line.slice(`${MEMORY_STATE_KEY}=`.length).trim();
163
+ logger.log(`[MEMORY-QUERY] read ${MEMORY_STATE_KEY}=${value} from ${filePath}`);
164
+ return { memoryState: value === "true" };
165
+ }
166
+ }
167
+ logger.log(`[MEMORY-QUERY] ${MEMORY_STATE_KEY} not found in ${filePath}`);
168
+ return { memoryState: false };
169
+ }
138
170
  /**
139
171
  * Read ~/.openclaw/workspace/USER.md and return content in fileDetail.
140
172
  */
@@ -235,7 +267,7 @@ function handleMemoryHistory() {
235
267
  // Build ans array sorted by date ascending, each entry is { <date>: [...] }.
236
268
  const ans = Array.from(byDate.keys())
237
269
  .sort()
238
- .map((dateStr) => ({ [dateStr]: byDate.get(dateStr) }));
270
+ .map((dateStr) => ({ [dateStr]: byDate.get(dateStr).reverse() }));
239
271
  // Prune memory.log: keep only the last 30 days.
240
272
  try {
241
273
  const newContent = keptLines.length > 0 ? `${keptLines.join("\n")}\n` : "";
@@ -10,9 +10,8 @@ import { handleLoginTokenEvent } from "./login-token-handler.js";
10
10
  import { handleCronQueryEvent } from "./cron-query-handler.js";
11
11
  import { handleMemoryQueryEvent } from "./memory-query-handler.js";
12
12
  import { cleanupStaleTempFiles } from "./reply-dispatcher.js";
13
+ import { cleanupStaleSessions, getActiveSessionCount, cleanupAllSessions } from "./tools/session-manager.js";
13
14
  import { logger } from "./utils/logger.js";
14
- import { XYFileUploadService } from "./file-upload.js";
15
- import { startLogReporter } from "./log-reporter/index.js";
16
15
  /**
17
16
  * Per-session serial queue that ensures messages from the same session are processed
18
17
  * in arrival order while allowing different sessions to run concurrently.
@@ -76,8 +75,6 @@ export async function monitorXYProvider(opts = {}) {
76
75
  let globalDispatchInitGate = Promise.resolve();
77
76
  // Health check interval
78
77
  let healthCheckInterval = null;
79
- // Log reporter stop handle
80
- let stopLogReporter = null;
81
78
  return new Promise((resolve, reject) => {
82
79
  // Event handlers (defined early so they can be referenced in cleanup)
83
80
  const messageHandler = (message, sessionId, serverId) => {
@@ -217,11 +214,6 @@ export async function monitorXYProvider(opts = {}) {
217
214
  };
218
215
  const cleanup = () => {
219
216
  logger.log("XY gateway: cleaning up...");
220
- // Stop log reporter
221
- if (stopLogReporter) {
222
- stopLogReporter();
223
- stopLogReporter = null;
224
- }
225
217
  // 🔍 Diagnose before cleanup
226
218
  logger.log("[DIAGNOSTICS] Checking WebSocket managers before cleanup...");
227
219
  diagnoseAllManagers();
@@ -247,7 +239,8 @@ export async function monitorXYProvider(opts = {}) {
247
239
  wsManager.disconnect();
248
240
  // ✅ Remove manager from cache to prevent reusing dirty state
249
241
  removeXYWebSocketManager(account);
250
- // Session context is ALS-scoped now — nothing global to clean up.
242
+ // Clean up all active sessions
243
+ cleanupAllSessions();
251
244
  loggedServers.clear();
252
245
  activeMessages.clear();
253
246
  logger.log(`[MONITOR-HANDLER] Cleanup complete, cleared active messages and sessions`);
@@ -316,7 +309,12 @@ export async function monitorXYProvider(opts = {}) {
316
309
  if (cleaned > 0) {
317
310
  logger.log(`[HEALTH CHECK] Auto-cleaned ${cleaned} manager(s) with orphan connections`);
318
311
  }
319
- // Session context is ALS-scoped no global session cleanup needed.
312
+ // Cleanup stale sessions (older than 10min TTL)
313
+ const cleanedSessions = cleanupStaleSessions();
314
+ const remainingSessions = getActiveSessionCount();
315
+ if (cleanedSessions > 0 || remainingSessions > 0) {
316
+ logger.log(`[HEALTH CHECK] Sessions: cleaned=${cleanedSessions}, active=${remainingSessions}`);
317
+ }
320
318
  // Cleanup stale temp files (older than 24 hours)
321
319
  void cleanupStaleTempFiles();
322
320
  }, 6 * 60 * 60 * 1000); // 6 hours
@@ -324,15 +322,6 @@ export async function monitorXYProvider(opts = {}) {
324
322
  wsManager.connect()
325
323
  .then(() => {
326
324
  logger.log("XY gateway: started successfully");
327
- // Start log reporter (independent periodic scanner + uploader)
328
- startLogReporter({
329
- configPath: "/home/ynhcj/.openclaw/log-reporter-config.json",
330
- uploadService: new XYFileUploadService(account.fileUploadUrl, account.apiKey, account.uid),
331
- }).then((stop) => {
332
- stopLogReporter = stop;
333
- }).catch((err) => {
334
- logger.warn(`Log reporter not started: ${String(err)}`);
335
- });
336
325
  })
337
326
  .catch((err) => {
338
327
  // Connection failed but don't reject - continue monitoring for reconnection