@openclaw/qqbot 2026.7.1-beta.1 → 2026.7.1-beta.4

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.
@@ -1,4 +1,4 @@
1
- import { v as setOpenClawVersion } from "./sender-BHWpE_yH.js";
1
+ import { v as setOpenClawVersion } from "./sender-CjDuU-uz.js";
2
2
  import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
3
3
  //#region extensions/qqbot/src/bridge/runtime.ts
4
4
  const { setRuntime: _setRuntime, clearRuntime: resetQQBotRuntimeForTest, getRuntime: getQQBotRuntime } = createPluginRuntimeStore({
@@ -1,2 +1,2 @@
1
- import { r as setQQBotRuntime, t as getQQBotRuntime } from "./runtime-BL0xZzWe.js";
1
+ import { r as setQQBotRuntime, t as getQQBotRuntime } from "./runtime-DodcT_fQ.js";
2
2
  export { getQQBotRuntime, setQQBotRuntime };
@@ -1,16 +1,18 @@
1
1
  import { c as getPlatformAdapter, i as normalizeOptionalString, s as sanitizeFileName } from "./string-normalize-R_0cKO7Q.js";
2
- import { a as formatErrorMessage, n as debugLog, r as debugWarn, t as debugError } from "./log-SDfMMBWe.js";
2
+ import { a as formatErrorMessage, n as debugLog, r as debugWarn, t as debugError } from "./log-DEtcoDWe.js";
3
+ import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
3
4
  import * as fs$1 from "node:fs";
4
5
  import os from "node:os";
5
6
  import { readProviderTextResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
6
7
  import { fetchWithSsrFGuard, isBlockedHostnameOrIp } from "openclaw/plugin-sdk/ssrf-runtime";
7
8
  import * as crypto$1 from "node:crypto";
8
9
  import crypto from "node:crypto";
10
+ import { sleep } from "openclaw/plugin-sdk/runtime-env";
9
11
  import { FsSafeError, openLocalFileSafely, readRegularFile, statRegularFileSync } from "openclaw/plugin-sdk/security-runtime";
10
12
  import * as path$1 from "node:path";
11
13
  import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
14
+ import { asDateTimestampMs, formatByteSize, isFutureDateTimestampMs, parseStrictPositiveInteger, resolveExpiresAtMsFromDurationSeconds, resolveTimestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
12
15
  import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
13
- import { asDateTimestampMs, isFutureDateTimestampMs, parseStrictPositiveInteger, resolveExpiresAtMsFromDurationSeconds, resolveTimestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
14
16
  //#region \0rolldown/runtime.js
15
17
  var __defProp = Object.defineProperty;
16
18
  var __exportAll = (all, no_symbols) => {
@@ -175,7 +177,7 @@ var ApiClient = class {
175
177
  throw new ApiError(`API Error [${path}]: ${error.message ?? rawBody}`, res.status, path, bizCode, error.message);
176
178
  } catch (parseErr) {
177
179
  if (parseErr instanceof ApiError) throw parseErr;
178
- throw new ApiError(`API Error [${path}] HTTP ${res.status}: ${rawBody.slice(0, 200)}`, res.status, path);
180
+ throw new ApiError(`API Error [${path}] HTTP ${res.status}: ${truncateUtf16Safe(rawBody, 200)}`, res.status, path);
179
181
  }
180
182
  }
181
183
  if (isHtmlResponse) throw new ApiError(`QQ 服务端返回了非 JSON 响应(${path}),可能是临时故障,请稍后重试`, res.status, path);
@@ -286,16 +288,19 @@ async function fileExistsAsync(filePath) {
286
288
  }
287
289
  /** Format a byte count into a human-readable size string. */
288
290
  function formatFileSize(bytes) {
289
- if (bytes < 1024) return `${bytes}B`;
290
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
291
- return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
291
+ return formatByteSize(bytes, {
292
+ style: "legacy-binary",
293
+ maxUnit: "mega",
294
+ separator: "",
295
+ fractionDigits: (_value, unit) => unit === "byte" ? null : 1
296
+ });
292
297
  }
293
298
  /** Infer a MIME type from the file extension. */
294
299
  function getMimeType(filePath) {
295
300
  return mimeTypeFromFilePath(filePath) ?? "application/octet-stream";
296
301
  }
297
302
  /** Extensions accepted as image uploads by the QQ Bot media pipeline. */
298
- const IMAGE_EXTENSIONS = new Set([
303
+ const IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
299
304
  ".jpg",
300
305
  ".jpeg",
301
306
  ".png",
@@ -462,6 +467,17 @@ async function normalizeSource(raw, opts = {}) {
462
467
  //#endregion
463
468
  //#region extensions/qqbot/src/engine/api/retry.ts
464
469
  /**
470
+ * Generic retry engine for QQ Bot API requests.
471
+ *
472
+ * Replaces the three separate retry implementations in the old `api.ts`:
473
+ * - `apiRequestWithRetry` (upload retry with exponential backoff)
474
+ * - `partFinishWithRetry` (part-finish retry + persistent retry on specific biz codes)
475
+ * - `completeUploadWithRetry` (unconditional retry for complete-upload)
476
+ *
477
+ * All three patterns are expressed as a single `withRetry` function
478
+ * parameterized by `RetryPolicy` and optional `PersistentRetryPolicy`.
479
+ */
480
+ /**
465
481
  * Execute an async operation with configurable retry semantics.
466
482
  *
467
483
  * @param fn - The async operation to retry.
@@ -483,8 +499,8 @@ async function withRetry(fn, policy, persistentPolicy, logger) {
483
499
  if (policy.shouldRetry?.(lastError, attempt) === false) throw lastError;
484
500
  if (attempt < policy.maxRetries) {
485
501
  const delay = policy.backoff === "exponential" ? policy.baseDelayMs * 2 ** attempt : policy.baseDelayMs;
486
- logger?.debug?.(`[qqbot:retry] Attempt ${attempt + 1} failed, retrying in ${delay}ms: ${lastError.message.slice(0, 100)}`);
487
- await sleep$1(delay);
502
+ logger?.debug?.(`[qqbot:retry] Attempt ${attempt + 1} failed, retrying in ${delay}ms: ${truncateUtf16Safe(lastError.message, 100)}`);
503
+ await sleep(delay);
488
504
  }
489
505
  }
490
506
  throw lastError;
@@ -514,16 +530,11 @@ async function persistentRetryLoop(fn, policy, logger) {
514
530
  if (remaining <= 0) break;
515
531
  const actualDelay = Math.min(policy.intervalMs, remaining);
516
532
  (logger?.warn ?? logger?.error)?.(`[qqbot:retry] Persistent retry #${attempt}: retrying in ${actualDelay}ms (remaining=${Math.round(remaining / 1e3)}s)`);
517
- await sleep$1(actualDelay);
533
+ await sleep(actualDelay);
518
534
  }
519
535
  logger?.error?.(`[qqbot:retry] Persistent retry timed out after ${policy.timeoutMs / 1e3}s (${attempt} attempts)`);
520
536
  throw lastError ?? /* @__PURE__ */ new Error(`Persistent retry timed out (${policy.timeoutMs / 1e3}s)`);
521
537
  }
522
- function sleep$1(ms) {
523
- return new Promise((resolve) => {
524
- setTimeout(resolve, ms);
525
- });
526
- }
527
538
  /** Standard upload retry: exponential backoff, skip 400/401/timeout errors. */
528
539
  const UPLOAD_RETRY_POLICY = {
529
540
  maxRetries: 2,
@@ -564,7 +575,7 @@ function buildPartFinishPersistentPolicy(retryTimeoutMs, retryableCodes = PART_F
564
575
  };
565
576
  }
566
577
  /** Business error codes that trigger persistent part-finish retry. */
567
- const PART_FINISH_RETRYABLE_CODES = new Set([40093001]);
578
+ const PART_FINISH_RETRYABLE_CODES = /* @__PURE__ */ new Set([40093001]);
568
579
  /** upload_prepare error code indicating daily limit exceeded. */
569
580
  const UPLOAD_PREPARE_FALLBACK_CODE = 40093002;
570
581
  //#endregion
@@ -931,8 +942,8 @@ async function putToPresignedUrl(presignedUrl, data, partIndex, totalParts, logg
931
942
  const etag = response.headers.get("ETag") ?? "-";
932
943
  if (!response.ok) {
933
944
  const body = await readResponseTextLimited(response, PART_UPLOAD_ERROR_BODY_LIMIT_BYTES).catch(() => "");
934
- logger?.error?.(`${prefix} PUT part ${partIndex}/${totalParts}: HTTP ${response.status} ${response.statusText} (${elapsed}ms, requestId=${requestId}) body=${body.slice(0, 160)}`);
935
- throw new Error(`COS PUT failed: ${response.status} ${response.statusText} - ${body.slice(0, 120)}`);
945
+ logger?.error?.(`${prefix} PUT part ${partIndex}/${totalParts}: HTTP ${response.status} ${response.statusText} (${elapsed}ms, requestId=${requestId}) body=${truncateUtf16Safe(body, 160)}`);
946
+ throw new Error(`COS PUT failed: ${response.status} ${response.statusText} - ${truncateUtf16Safe(body, 120)}`);
936
947
  }
937
948
  logger?.debug?.(`${prefix} PUT part ${partIndex}/${totalParts} OK (${elapsed}ms ETag=${etag} requestId=${requestId})`);
938
949
  return;
@@ -944,7 +955,7 @@ async function putToPresignedUrl(presignedUrl, data, partIndex, totalParts, logg
944
955
  if (lastError.name === "AbortError") lastError = /* @__PURE__ */ new Error(`Part ${partIndex}/${totalParts} upload timeout after ${PART_UPLOAD_TIMEOUT_MS}ms`);
945
956
  if (attempt < PART_UPLOAD_MAX_RETRIES) {
946
957
  const delay = 1e3 * 2 ** attempt;
947
- (logger?.warn ?? logger?.error)?.(`${prefix} PUT part ${partIndex}/${totalParts} attempt ${attempt + 1} failed (${lastError.message.slice(0, 120)}), retrying in ${delay}ms`);
958
+ (logger?.warn ?? logger?.error)?.(`${prefix} PUT part ${partIndex}/${totalParts} attempt ${attempt + 1} failed (${truncateUtf16Safe(lastError.message, 120)}), retrying in ${delay}ms`);
948
959
  await sleep(delay);
949
960
  }
950
961
  } finally {
@@ -967,11 +978,6 @@ async function runWithConcurrency(tasks, maxConcurrent) {
967
978
  await Promise.all(batch.map((task) => task()));
968
979
  }
969
980
  }
970
- function sleep(ms) {
971
- return new Promise((resolve) => {
972
- setTimeout(resolve, ms);
973
- });
974
- }
975
981
  //#endregion
976
982
  //#region extensions/qqbot/src/engine/api/token.ts
977
983
  /**
@@ -1810,7 +1816,7 @@ async function withTokenRetry(creds, sendFn, log, _accountId) {
1810
1816
  }
1811
1817
  const errMsg = formatErrorMessage(err);
1812
1818
  if (errMsg.includes("401") || errMsg.includes("token") || errMsg.includes("access_token")) {
1813
- log?.warn?.(`Token retry triggered by string heuristic (err is not ApiError). Consider propagating ApiError end-to-end. msg=${errMsg.slice(0, 120)}`);
1819
+ log?.warn?.(`Token retry triggered by string heuristic (err is not ApiError). Consider propagating ApiError end-to-end. msg=${truncateUtf16Safe(errMsg, 120)}`);
1814
1820
  clearTokenCache(creds.appId);
1815
1821
  return await sendFn(await getAccessToken(creds.appId, creds.clientSecret));
1816
1822
  }
@@ -1,2 +1,2 @@
1
- import { t as qqbotSetupPlugin } from "./channel.setup-CldhUlTO.js";
1
+ import { t as qqbotSetupPlugin } from "./channel.setup-2ItDYKhz.js";
2
2
  export { qqbotSetupPlugin };
@@ -1,11 +1,12 @@
1
1
  import { a as resolveQQBotAccount, r as listQQBotAccountIds } from "./config-C1qZbh0K.js";
2
- import { a as formatErrorMessage, n as debugLog, t as debugError } from "./log-SDfMMBWe.js";
2
+ import { a as formatErrorMessage, n as debugLog, t as debugError } from "./log-DEtcoDWe.js";
3
3
  import { t as getRequestContext } from "./request-context-Bm7PTBD1.js";
4
+ import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
4
5
  import { readProviderTextResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
5
6
  import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
6
7
  import { resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
8
+ import { jsonResult } from "openclaw/plugin-sdk/tool-results";
7
9
  import { callGatewayTool } from "openclaw/plugin-sdk/agent-harness-runtime";
8
- import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
9
10
  //#region extensions/qqbot/src/engine/tools/channel-api.ts
10
11
  /**
11
12
  * QQ Channel API proxy tool core logic.
@@ -118,15 +119,6 @@ function validateDeleteConfirmation(params) {
118
119
  if (isBulkAnnouncementDeletePath(params.path) && !params.bulkConfirmed) return "Deleting all announcements requires bulkConfirmed=true after a separate bulk-delete confirmation.";
119
120
  return null;
120
121
  }
121
- function json$1(data) {
122
- return {
123
- content: [{
124
- type: "text",
125
- text: JSON.stringify(data, null, 2)
126
- }],
127
- details: data
128
- };
129
- }
130
122
  /**
131
123
  * Execute a channel API proxy request.
132
124
  * 执行频道 API 代理请求。
@@ -136,8 +128,8 @@ function json$1(data) {
136
128
  * response formatting suitable for AI tool output.
137
129
  */
138
130
  async function executeChannelApi(params, options) {
139
- if (!params.method) return json$1({ error: "method is required" });
140
- if (!params.path) return json$1({ error: "path is required" });
131
+ if (!params.method) return jsonResult({ error: "method is required" });
132
+ if (!params.path) return jsonResult({ error: "path is required" });
141
133
  const method = params.method.toUpperCase();
142
134
  if (![
143
135
  "GET",
@@ -145,14 +137,14 @@ async function executeChannelApi(params, options) {
145
137
  "PUT",
146
138
  "PATCH",
147
139
  "DELETE"
148
- ].includes(method)) return json$1({ error: `Unsupported HTTP method: ${method}. Allowed values: GET, POST, PUT, PATCH, DELETE` });
140
+ ].includes(method)) return jsonResult({ error: `Unsupported HTTP method: ${method}. Allowed values: GET, POST, PUT, PATCH, DELETE` });
149
141
  const pathError = validatePath(params.path);
150
- if (pathError) return json$1({ error: pathError });
142
+ if (pathError) return jsonResult({ error: pathError });
151
143
  const confirmationError = validateDeleteConfirmation({
152
144
  ...params,
153
145
  method
154
146
  });
155
- if (confirmationError) return json$1({
147
+ if (confirmationError) return jsonResult({
156
148
  error: confirmationError,
157
149
  path: params.path
158
150
  });
@@ -191,13 +183,13 @@ async function executeChannelApi(params, options) {
191
183
  clearTimeout(timeoutId);
192
184
  if (err instanceof Error && err.name === "AbortError") {
193
185
  debugError(`[qqbot-channel-api] <<< Request timeout after ${DEFAULT_TIMEOUT_MS}ms`);
194
- return json$1({
186
+ return jsonResult({
195
187
  error: `Request timed out after ${DEFAULT_TIMEOUT_MS}ms`,
196
188
  path: params.path
197
189
  });
198
190
  }
199
191
  debugError("[qqbot-channel-api] <<< Network error:", err);
200
- return json$1({
192
+ return jsonResult({
201
193
  error: `Network error: ${formatErrorMessage(err)}`,
202
194
  path: params.path
203
195
  });
@@ -208,12 +200,12 @@ async function executeChannelApi(params, options) {
208
200
  debugLog(`[qqbot-channel-api] <<< Status: ${res.status} ${res.statusText}`);
209
201
  const rawBody = res.ok ? await readProviderTextResponse(res, "QQ channel API response") : await readResponseTextLimited(res, CHANNEL_API_ERROR_BODY_LIMIT_BYTES);
210
202
  if (!rawBody || rawBody.trim() === "") {
211
- if (res.ok) return json$1({
203
+ if (res.ok) return jsonResult({
212
204
  success: true,
213
205
  status: res.status,
214
206
  path: params.path
215
207
  });
216
- return json$1({
208
+ return jsonResult({
217
209
  error: `API returned ${res.status} ${res.statusText}`,
218
210
  status: res.status,
219
211
  path: params.path
@@ -228,14 +220,14 @@ async function executeChannelApi(params, options) {
228
220
  if (!res.ok) {
229
221
  const errMsg = typeof parsed === "object" && parsed && "message" in parsed ? String(parsed.message) : `${res.status} ${res.statusText}`;
230
222
  debugError(`[qqbot-channel-api] Error [${method} ${params.path}]: ${errMsg}`);
231
- return json$1({
223
+ return jsonResult({
232
224
  error: errMsg,
233
225
  status: res.status,
234
226
  path: params.path,
235
227
  details: parsed
236
228
  });
237
229
  }
238
- return json$1({
230
+ return jsonResult({
239
231
  success: true,
240
232
  status: res.status,
241
233
  path: params.path,
@@ -245,7 +237,7 @@ async function executeChannelApi(params, options) {
245
237
  await release?.();
246
238
  }
247
239
  } catch (err) {
248
- return json$1({
240
+ return jsonResult({
249
241
  error: formatErrorMessage(err),
250
242
  path: params.path
251
243
  });
@@ -274,7 +266,7 @@ function registerChannelTool(api) {
274
266
  description: "Authenticated HTTP proxy for QQ Open Platform channel APIs. Use write and delete endpoints only after explicit user intent; DELETE requires confirmed=true, and bulk deletes require bulkConfirmed=true after confirming the exact target. Common endpoints: list guilds GET /users/@me/guilds | list channels GET /guilds/{guild_id}/channels | get channel GET /channels/{channel_id} | create channel POST /guilds/{guild_id}/channels | list members GET /guilds/{guild_id}/members?after=0&limit=100 | get member GET /guilds/{guild_id}/members/{user_id} | list threads GET /channels/{channel_id}/threads | create thread PUT /channels/{channel_id}/threads | create announce POST /guilds/{guild_id}/announces | create schedule POST /channels/{channel_id}/schedules. See the qqbot-channel skill for full endpoint details.",
275
267
  parameters: ChannelApiSchema,
276
268
  async execute(_toolCallId, params) {
277
- const { getAccessToken } = await import("./sender-BHWpE_yH.js").then((n) => n._);
269
+ const { getAccessToken } = await import("./sender-CjDuU-uz.js").then((n) => n._);
278
270
  return executeChannelApi(params, { accessToken: await getAccessToken(account.appId, account.clientSecret) });
279
271
  }
280
272
  }, { name: "qqbot_channel_api" });
@@ -451,15 +443,6 @@ function formatDelay(ms) {
451
443
  if (minutes === 0) return `${hours}h`;
452
444
  return `${hours}h${minutes}m`;
453
445
  }
454
- function json(data) {
455
- return {
456
- content: [{
457
- type: "text",
458
- text: JSON.stringify(data, null, 2)
459
- }],
460
- details: data
461
- };
462
- }
463
446
  function formatSchedulerError(error) {
464
447
  return error instanceof Error ? error.message : String(error);
465
448
  }
@@ -526,17 +509,17 @@ function prepareRemindCronAction(params, ctx = {}) {
526
509
  }
527
510
  async function executeScheduledRemind(params, ctx, scheduler) {
528
511
  const plan = prepareRemindCronAction(params, ctx);
529
- if (!plan.ok) return json({ error: plan.error });
512
+ if (!plan.ok) return jsonResult({ error: plan.error });
530
513
  try {
531
514
  const cronResult = await scheduler(plan.cronAction);
532
- return json({
515
+ return jsonResult({
533
516
  ok: true,
534
517
  action: plan.action,
535
518
  summary: plan.summary,
536
519
  cronResult
537
520
  });
538
521
  } catch (error) {
539
- return json({
522
+ return jsonResult({
540
523
  error: `Failed to run Gateway cron action: ${formatSchedulerError(error)}`,
541
524
  action: plan.action
542
525
  });
package/dist/tools-api.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as registerQQBotTools } from "./tools-BvXe7bTq.js";
1
+ import { t as registerQQBotTools } from "./tools-UJJ-tLHP.js";
2
2
  export { registerQQBotTools };
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@openclaw/qqbot",
3
- "version": "2026.7.1-beta.1",
3
+ "version": "2026.7.1-beta.4",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/qqbot",
9
- "version": "2026.7.1-beta.1",
9
+ "version": "2026.7.1-beta.4",
10
10
  "dependencies": {
11
11
  "@tencent-connect/qqbot-connector": "1.1.0",
12
12
  "mpg123-decoder": "1.0.3",
@@ -15,7 +15,7 @@
15
15
  "zod": "4.4.3"
16
16
  },
17
17
  "peerDependencies": {
18
- "openclaw": ">=2026.7.1-beta.1"
18
+ "openclaw": ">=2026.7.1-beta.4"
19
19
  },
20
20
  "peerDependenciesMeta": {
21
21
  "openclaw": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/qqbot",
3
- "version": "2026.7.1-beta.1",
3
+ "version": "2026.7.1-beta.4",
4
4
  "private": false,
5
5
  "description": "OpenClaw QQ Bot channel plugin for group and direct-message workflows.",
6
6
  "repository": {
@@ -16,7 +16,7 @@
16
16
  "zod": "4.4.3"
17
17
  },
18
18
  "peerDependencies": {
19
- "openclaw": ">=2026.7.1-beta.1"
19
+ "openclaw": ">=2026.7.1-beta.4"
20
20
  },
21
21
  "peerDependenciesMeta": {
22
22
  "openclaw": {
@@ -45,10 +45,10 @@
45
45
  "minHostVersion": ">=2026.4.10"
46
46
  },
47
47
  "compat": {
48
- "pluginApi": ">=2026.7.1-beta.1"
48
+ "pluginApi": ">=2026.7.1-beta.4"
49
49
  },
50
50
  "build": {
51
- "openclawVersion": "2026.7.1-beta.1"
51
+ "openclawVersion": "2026.7.1-beta.4"
52
52
  },
53
53
  "release": {
54
54
  "publishToClawHub": true,
@@ -9,7 +9,7 @@ metadata: { "openclaw": { "emoji": "📸", "requires": { "config": ["channels.qq
9
9
  ## 用法
10
10
 
11
11
  ```
12
- <qqmedia>路径或URL</qqmedia>
12
+ <qqmedia>{实际路径或URL}</qqmedia>
13
13
  ```
14
14
 
15
15
  系统根据文件扩展名自动识别类型并路由:
@@ -18,7 +18,8 @@ metadata: { "openclaw": { "emoji": "📸", "requires": { "config": ["channels.qq
18
18
  - `.silk/.wav/.mp3/.ogg/.aac/.flac` 等 → 语音
19
19
  - `.mp4/.mov/.avi/.mkv/.webm` 等 → 视频
20
20
  - 其他扩展名 → 文件
21
- - 无扩展名的 URL默认按图片处理
21
+ - 无扩展名的当前会话本地/host-read 媒体按加载出的实际媒体类型路由
22
+ - 无扩展名的远程 URL → 可能按文件发送;如需图片/语音/视频,请提供能识别类型的 URL/路径或使用明确媒体标签
22
23
 
23
24
  ## 接收媒体
24
25
 
@@ -29,12 +30,14 @@ metadata: { "openclaw": { "emoji": "📸", "requires": { "config": ["channels.qq
29
30
 
30
31
  ## 规则
31
32
 
32
- 1. **路径必须是绝对路径**(以 `/` 或 `http` 开头)
33
- 2. **标签必须用开闭标签包裹路径**:`<qqmedia>路径</qqmedia>`
34
- 3. **待发送的本地文件须落在 OpenClaw 媒体目录下**:生成、下载或复制出的文件应写入 **`~/.openclaw/media/qqbot/`**(或其子目录),再写进 `<qqmedia>`。不要只放在 `~/.openclaw/workspace/` 等工作区根目录——平台安全策略只允许从 `~/.openclaw/media/`(含 `media/qqbot`)等受信根路径上传,否则会拦截、发不出去。
35
- 4. **文件大小上限**:图片 30MB / 视频 100MB / 文件 100MB / 语音 20MB
36
- 5. **你有能力发送本地图片/文件**,直接用标签包裹路径即可,**不要说"无法发送"**
37
- 6. 发送语音时不要重复语音中已朗读的文字
38
- 7. 多个媒体用多个标签
39
- 8. 以会话上下文中的能力说明为准(如未启用语音则不要发语音)
40
- 9. 不要扫描或发送上下文之外的本地文件;只使用用户提供、工具生成,或明确位于受信 media 目录中的路径
33
+ 1. **标签必须用开闭标签包裹实际路径或 URL**:`<qqmedia>{实际路径或URL}</qqmedia>`
34
+ 2. **使用你实际看到的文件路径**:刚创建文件时,用创建结果显示的路径;只有当沙箱 workspace-write 创建结果实际显示 `/workspace/...` 时,才按原样使用该路径,例如 `<qqmedia>/workspace/report.pdf</qqmedia>`。
35
+ 3. **附件路径直接使用上下文给出的路径**:如果路径来自会话【附件】上下文,不要改写成 `/workspace/...`。
36
+ 4. **URL 可以直接发送**:例如 `<qqmedia>https://example.com/image.png</qqmedia>`。
37
+ 5. **本地路径仍受安全根限制**:只能发送当前会话授权的 agent workspace、scoped media roots、OpenClaw 媒体目录或 QQBot 媒体目录内的文件;不要使用 `..` 逃出工作区。
38
+ 6. **不要扫描或主动发送上下文之外的本地文件**:只使用用户提供、工具刚生成,或当前会话上下文明确给出的路径。
39
+ 7. **文件大小上限**:图片 30MB / 视频 100MB / 文件 100MB / 语音 20MB
40
+ 8. **你有能力发送本地图片/文件**,直接用标签包裹路径即可,**不要说"无法发送"**
41
+ 9. 发送语音时不要重复语音中已朗读的文字
42
+ 10. 多个媒体用多个标签
43
+ 11. 以会话上下文中的能力说明为准(如未启用语音则不要发语音)