@ynhcj/xiaoyi-channel 0.0.32-next → 0.0.33-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/src/bot.js CHANGED
@@ -2,6 +2,7 @@ import { getXYRuntime } from "./runtime.js";
2
2
  import { setCachedContext } from "./steer-injector.js";
3
3
  import { createXYReplyDispatcher } from "./reply-dispatcher.js";
4
4
  import { parseA2AMessage, extractTextFromParts, extractFileParts, extractPushId, extractTriggerData } from "./parser.js";
5
+ import { downloadFilesFromParts } from "./file-download.js";
5
6
  import { resolveXYConfig } from "./config.js";
6
7
  import { sendStatusUpdate, sendClearContextResponse, sendTasksCancelResponse, sendA2AResponse } from "./formatter.js";
7
8
  import { registerSession, unregisterSession, runWithSessionContext } from "./tools/session-manager.js";
@@ -161,8 +162,9 @@ export async function handleXYMessage(params) {
161
162
  // Extract text and files from parts
162
163
  const text = extractTextFromParts(parsed.parts);
163
164
  const fileParts = extractFileParts(parsed.parts);
164
- // Build media payload directly from file URLs (no local download needed)
165
- const mediaPayload = buildXYMediaPayload(fileParts);
165
+ // Download files to local disk
166
+ const downloadedFiles = await downloadFilesFromParts(fileParts);
167
+ const mediaPayload = buildXYMediaPayload(downloadedFiles);
166
168
  // Resolve envelope format options (following feishu pattern)
167
169
  const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
168
170
  // Build message body with speaker prefix (following feishu pattern)
@@ -294,19 +296,19 @@ export async function handleXYMessage(params) {
294
296
  }
295
297
  }
296
298
  /**
297
- * Build media payload for inbound context using file URLs.
298
- * OpenClaw natively supports MediaUrl/MediaUrls — no local download needed.
299
+ * Build media payload for inbound context.
300
+ * Following feishu pattern: buildFeishuMediaPayload().
299
301
  *
300
- * @param fileParts - File parts extracted from A2A message (with uri field)
302
+ * @param mediaList - Downloaded files with local paths
301
303
  */
302
- function buildXYMediaPayload(fileParts) {
303
- const first = fileParts[0];
304
- const mediaUrls = fileParts.map((f) => f.uri);
305
- const mediaTypes = fileParts.map((f) => f.mimeType).filter(Boolean);
304
+ function buildXYMediaPayload(mediaList) {
305
+ const first = mediaList[0];
306
+ const mediaPaths = mediaList.map((media) => media.path);
307
+ const mediaTypes = mediaList.map((media) => media.mimeType).filter(Boolean);
306
308
  return {
307
- MediaUrl: first?.uri,
309
+ MediaPath: first?.path,
308
310
  MediaType: first?.mimeType,
309
- MediaUrls: mediaUrls.length > 0 ? mediaUrls : undefined,
311
+ MediaPaths: mediaPaths.length > 0 ? mediaPaths : undefined,
310
312
  MediaTypes: mediaTypes.length > 0 ? mediaTypes : undefined,
311
313
  };
312
314
  }
@@ -4,6 +4,7 @@ import { handleXYMessage } from "./bot.js";
4
4
  import { parseA2AMessage } from "./parser.js";
5
5
  import { hasActiveTask } from "./task-manager.js";
6
6
  import { handleTriggerEvent } from "./trigger-handler.js";
7
+ import { cleanupStaleTempFiles } from "./reply-dispatcher.js";
7
8
  /**
8
9
  * Per-session serial queue that ensures messages from the same session are processed
9
10
  * in arrival order while allowing different sessions to run concurrently.
@@ -211,6 +212,8 @@ export async function monitorXYProvider(opts = {}) {
211
212
  if (cleaned > 0) {
212
213
  console.log(`🧹 [HEALTH CHECK] Auto-cleaned ${cleaned} manager(s) with orphan connections`);
213
214
  }
215
+ // Cleanup stale temp files (older than 24 hours)
216
+ void cleanupStaleTempFiles();
214
217
  }, 6 * 60 * 60 * 1000); // 6 hours
215
218
  // Connect to WebSocket servers
216
219
  wsManager.connect()
@@ -8,6 +8,10 @@ export interface CreateXYReplyDispatcherParams {
8
8
  accountId: string;
9
9
  isSteerFollower?: boolean;
10
10
  }
11
+ /**
12
+ * 清理 /tmp/xy_channel 目录中超过 24 小时的旧文件
13
+ */
14
+ export declare function cleanupStaleTempFiles(tempDir?: string): Promise<void>;
11
15
  /**
12
16
  * Create a reply dispatcher for XY channel messages.
13
17
  * Follows feishu pattern with status updates and streaming support.
@@ -4,29 +4,34 @@ import { resolveXYConfig } from "./config.js";
4
4
  import { getCurrentTaskId, getCurrentMessageId } from "./task-manager.js";
5
5
  import fs from "fs/promises";
6
6
  import path from "path";
7
+ const TEMP_FILE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
7
8
  /**
8
- * 清理 /tmp/xy_channel 目录中的所有文件
9
+ * 清理 /tmp/xy_channel 目录中超过 24 小时的旧文件
9
10
  */
10
- async function cleanupTempDir(tempDir = "/tmp/xy_channel") {
11
+ export async function cleanupStaleTempFiles(tempDir = "/tmp/xy_channel") {
11
12
  try {
12
13
  const stats = await fs.stat(tempDir).catch(() => null);
13
14
  if (!stats?.isDirectory()) {
14
- return; // 目录不存在,直接返回
15
+ return;
15
16
  }
16
17
  const files = await fs.readdir(tempDir);
18
+ const now = Date.now();
17
19
  let cleanedCount = 0;
18
20
  for (const file of files) {
19
21
  const filePath = path.join(tempDir, file);
20
22
  try {
21
- await fs.unlink(filePath);
22
- cleanedCount++;
23
+ const fileStat = await fs.stat(filePath);
24
+ if (now - fileStat.mtimeMs > TEMP_FILE_TTL_MS) {
25
+ await fs.unlink(filePath);
26
+ cleanedCount++;
27
+ }
23
28
  }
24
29
  catch (err) {
25
- // 忽略单个文件删除失败,继续处理其他文件
30
+ // 忽略单个文件处理失败
26
31
  }
27
32
  }
28
33
  if (cleanedCount > 0) {
29
- console.log(`[CLEANUP] 🧹 Cleaned ${cleanedCount} files from ${tempDir}`);
34
+ console.log(`[CLEANUP] 🧹 Cleaned ${cleanedCount} stale files (>${TEMP_FILE_TTL_MS / 1000 / 3600}h) from ${tempDir}`);
30
35
  }
31
36
  }
32
37
  catch (err) {
@@ -239,7 +244,6 @@ export function createXYReplyDispatcher(params) {
239
244
  }
240
245
  }
241
246
  stopStatusInterval();
242
- void cleanupTempDir();
243
247
  },
244
248
  onCleanup: () => {
245
249
  const currentTaskId = getActiveTaskId();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi-channel",
3
- "version": "0.0.32-next",
3
+ "version": "0.0.33-next",
4
4
  "description": "OpenClaw Xiaoyi Channel plugin - Xiaoyi A2A protocol integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",