@ynhcj/xiaoyi-channel 0.0.63-beta → 0.0.64-beta

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/src/bot.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { getXYRuntime } from "./runtime.js";
2
2
  import { setCachedContext } from "./steer-injector.js";
3
3
  import { createXYReplyDispatcher } from "./reply-dispatcher.js";
4
- import { parseA2AMessage, extractTextFromParts, extractFileParts, extractPushId, extractTriggerData } from "./parser.js";
4
+ import { parseA2AMessage, extractTextFromParts, extractFileParts, extractPushId, extractDeviceType, extractTriggerData } from "./parser.js";
5
5
  import { downloadFilesFromParts } from "./file-download.js";
6
6
  import { resolveXYConfig } from "./config.js";
7
7
  import { sendStatusUpdate, sendClearContextResponse, sendTasksCancelResponse, sendA2AResponse } from "./formatter.js";
@@ -126,6 +126,11 @@ export async function handleXYMessage(params) {
126
126
  else {
127
127
  log(`[BOT] ℹ️ No push_id found in message, will use config default`);
128
128
  }
129
+ // Extract deviceType if present (same level as push_id in systemVariables)
130
+ const deviceType = extractDeviceType(parsed.parts);
131
+ if (deviceType) {
132
+ log(`[BOT] 📱 Extracted deviceType from user message: ${deviceType}`);
133
+ }
129
134
  // 保存 runtime 信息到 .xiaoyiruntime 文件(异步,不阻塞主流程)
130
135
  saveRuntimeInfo(webSocketSessionId || parsed.sessionId, // SESSION_ID (WebSocket 层级,如果没有则 fallback)
131
136
  parsed.sessionId, // CONVERSATION_ID (param 里的 sessionId)
@@ -238,6 +243,7 @@ export async function handleXYMessage(params) {
238
243
  taskId: parsed.taskId,
239
244
  messageId: parsed.messageId,
240
245
  agentId: route.accountId,
246
+ deviceType,
241
247
  };
242
248
  log(`[BOT-DISPATCH] ⏳ withReplyDispatcher starting, sessionKey=${route.sessionKey}`);
243
249
  await core.channel.reply.withReplyDispatcher({
@@ -25,6 +25,9 @@ import { viewPushResultTool } from "./tools/view-push-result-tool.js";
25
25
  import { imageReadingTool } from "./tools/image-reading-tool.js";
26
26
  import { timestampToUtc8Tool } from "./tools/timestamp-to-utc8-tool.js";
27
27
  import { sendCommandToCarTool } from "./tools/send-command-to-car-tool.js";
28
+ import { filterToolsByDevice } from "./tools/device-tool-map.js";
29
+ import { getCurrentSessionContext } from "./tools/session-manager.js";
30
+ import { logger } from "./utils/logger.js";
28
31
  /**
29
32
  * Xiaoyi Channel Plugin for OpenClaw.
30
33
  * Implements Xiaoyi A2A protocol with dual WebSocket connections.
@@ -63,7 +66,13 @@ export const xyPlugin = {
63
66
  schema: xyConfigSchema,
64
67
  },
65
68
  outbound: xyOutbound,
66
- agentTools: [locationTool, noteTool, searchNoteTool, modifyNoteTool, calendarTool, searchCalendarTool, searchContactTool, searchPhotoGalleryTool, uploadPhotoTool, xiaoyiGuiTool, callPhoneTool, searchMessageTool, sendMessageTool, searchFileTool, uploadFileTool, createAlarmTool, searchAlarmTool, modifyAlarmTool, deleteAlarmTool, sendFileToUserTool, viewPushResultTool, imageReadingTool, timestampToUtc8Tool, sendCommandToCarTool],
69
+ agentTools: () => {
70
+ const allTools = [locationTool, noteTool, searchNoteTool, modifyNoteTool, calendarTool, searchCalendarTool, searchContactTool, searchPhotoGalleryTool, uploadPhotoTool, xiaoyiGuiTool, callPhoneTool, searchMessageTool, sendMessageTool, searchFileTool, uploadFileTool, createAlarmTool, searchAlarmTool, modifyAlarmTool, deleteAlarmTool, sendFileToUserTool, viewPushResultTool, imageReadingTool, timestampToUtc8Tool, sendCommandToCarTool];
71
+ const ctx = getCurrentSessionContext();
72
+ const filtered = filterToolsByDevice(allTools, ctx?.deviceType);
73
+ logger.log(`[DEVICE-FILTER] deviceType=${ctx?.deviceType ?? "(none)"}, tools: ${allTools.length} → ${filtered.length} (${filtered.map(t => t.name).join(", ")})`);
74
+ return filtered;
75
+ },
67
76
  messaging: {
68
77
  normalizeTarget: (raw) => {
69
78
  const trimmed = raw.trim();
@@ -43,6 +43,12 @@ export declare function isTasksCancelMessage(method: string): boolean;
43
43
  * Looks for push_id in data parts under variables.systemVariables.push_id
44
44
  */
45
45
  export declare function extractPushId(parts: A2AMessagePart[]): string | null;
46
+ /**
47
+ * Extract deviceType from message parts.
48
+ * Looks for deviceType in data parts under variables.systemVariables.deviceType
49
+ * (same level as push_id).
50
+ */
51
+ export declare function extractDeviceType(parts: A2AMessagePart[]): string | null;
46
52
  /**
47
53
  * Extract Trigger event data from message parts.
48
54
  * Looks for Trigger events with pushDataId in data parts.
@@ -72,6 +72,22 @@ export function extractPushId(parts) {
72
72
  }
73
73
  return null;
74
74
  }
75
+ /**
76
+ * Extract deviceType from message parts.
77
+ * Looks for deviceType in data parts under variables.systemVariables.deviceType
78
+ * (same level as push_id).
79
+ */
80
+ export function extractDeviceType(parts) {
81
+ for (const part of parts) {
82
+ if (part.kind === "data" && part.data) {
83
+ const deviceType = part.data.variables?.systemVariables?.device_type;
84
+ if (deviceType && typeof deviceType === "string") {
85
+ return deviceType;
86
+ }
87
+ }
88
+ }
89
+ return null;
90
+ }
75
91
  /**
76
92
  * Extract Trigger event data from message parts.
77
93
  * Looks for Trigger events with pushDataId in data parts.
@@ -0,0 +1,4 @@
1
+ /** Known device type enum. */
2
+ export declare const DEVICE_TYPES: readonly ["car", "pc", "phone"];
3
+ export type DeviceType = (typeof DEVICE_TYPES)[number];
4
+ export declare function filterToolsByDevice(tools: any[], deviceType?: string): any[];
@@ -0,0 +1,31 @@
1
+ // Device type to tool name mapping.
2
+ // Supports two modes:
3
+ // - allowlist: only listed tools are available (used for restrictive devices like car)
4
+ // - denylist: listed tools are blocked, everything else is available (used for permissive devices like pc)
5
+ // Tools NOT listed in any device entry → available to all devices (no restriction).
6
+ /** Known device type enum. */
7
+ export const DEVICE_TYPES = ["car", "pc", "phone"];
8
+ const DEVICE_TOOL_POLICY = {
9
+ pc: {
10
+ allowlist: false,
11
+ tools: [
12
+ "xiaoyi_gui_agent",
13
+ "call_phone",
14
+ "send_message",
15
+ "search_contact",
16
+ ],
17
+ },
18
+ };
19
+ export function filterToolsByDevice(tools, deviceType) {
20
+ if (!deviceType)
21
+ return tools;
22
+ const policy = DEVICE_TOOL_POLICY[deviceType];
23
+ if (!policy)
24
+ return tools; // unrecognized device → no filtering
25
+ if (policy.allowlist) {
26
+ return tools.filter((tool) => policy.tools.includes(tool.name));
27
+ }
28
+ else {
29
+ return tools.filter((tool) => !policy.tools.includes(tool.name));
30
+ }
31
+ }
@@ -5,6 +5,7 @@ export interface SessionContext {
5
5
  taskId: string;
6
6
  messageId: string;
7
7
  agentId: string;
8
+ deviceType?: string;
8
9
  }
9
10
  /**
10
11
  * Register a session context for tool access.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi-channel",
3
- "version": "0.0.63-beta",
3
+ "version": "0.0.64-beta",
4
4
  "description": "OpenClaw Xiaoyi Channel plugin - Xiaoyi A2A protocol integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",