@workclaw/openclaw-workchat 1.0.5 → 1.1.0

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/index.js CHANGED
@@ -46,7 +46,8 @@ var init_types = __esm({
46
46
  DEFAULT_WS_URL = "wss://workbrain.cn/backend-api/open-apis/connect";
47
47
  WorkChatMsgType = {
48
48
  Text: "1",
49
- Rich: "26"
49
+ Image: "2",
50
+ Audio: "110"
50
51
  };
51
52
  RequestDefaults = {
52
53
  TimeoutMs: 3e4
@@ -1605,14 +1606,14 @@ var require_util = __commonJS({
1605
1606
  }
1606
1607
  const port = url2.port != null ? url2.port : url2.protocol === "https:" ? 443 : 80;
1607
1608
  let origin = url2.origin != null ? url2.origin : `${url2.protocol || ""}//${url2.hostname || ""}:${port}`;
1608
- let path6 = url2.path != null ? url2.path : `${url2.pathname || ""}${url2.search || ""}`;
1609
+ let path8 = url2.path != null ? url2.path : `${url2.pathname || ""}${url2.search || ""}`;
1609
1610
  if (origin[origin.length - 1] === "/") {
1610
1611
  origin = origin.slice(0, origin.length - 1);
1611
1612
  }
1612
- if (path6 && path6[0] !== "/") {
1613
- path6 = `/${path6}`;
1613
+ if (path8 && path8[0] !== "/") {
1614
+ path8 = `/${path8}`;
1614
1615
  }
1615
- return new URL(`${origin}${path6}`);
1616
+ return new URL(`${origin}${path8}`);
1616
1617
  }
1617
1618
  if (!isHttpOrHttpsPrefixed(url2.origin || url2.protocol)) {
1618
1619
  throw new InvalidArgumentError("Invalid URL protocol: the URL must start with `http:` or `https:`.");
@@ -2433,9 +2434,9 @@ var require_diagnostics = __commonJS({
2433
2434
  "undici:client:sendHeaders",
2434
2435
  (evt) => {
2435
2436
  const {
2436
- request: { method, path: path6, origin }
2437
+ request: { method, path: path8, origin }
2437
2438
  } = evt;
2438
- debugLog("sending request to %s %s%s", method, origin, path6);
2439
+ debugLog("sending request to %s %s%s", method, origin, path8);
2439
2440
  }
2440
2441
  );
2441
2442
  }
@@ -2453,14 +2454,14 @@ var require_diagnostics = __commonJS({
2453
2454
  "undici:request:headers",
2454
2455
  (evt) => {
2455
2456
  const {
2456
- request: { method, path: path6, origin },
2457
+ request: { method, path: path8, origin },
2457
2458
  response: { statusCode }
2458
2459
  } = evt;
2459
2460
  debugLog(
2460
2461
  "received response to %s %s%s - HTTP %d",
2461
2462
  method,
2462
2463
  origin,
2463
- path6,
2464
+ path8,
2464
2465
  statusCode
2465
2466
  );
2466
2467
  }
@@ -2469,23 +2470,23 @@ var require_diagnostics = __commonJS({
2469
2470
  "undici:request:trailers",
2470
2471
  (evt) => {
2471
2472
  const {
2472
- request: { method, path: path6, origin }
2473
+ request: { method, path: path8, origin }
2473
2474
  } = evt;
2474
- debugLog("trailers received from %s %s%s", method, origin, path6);
2475
+ debugLog("trailers received from %s %s%s", method, origin, path8);
2475
2476
  }
2476
2477
  );
2477
2478
  diagnosticsChannel.subscribe(
2478
2479
  "undici:request:error",
2479
2480
  (evt) => {
2480
2481
  const {
2481
- request: { method, path: path6, origin },
2482
+ request: { method, path: path8, origin },
2482
2483
  error: error62
2483
2484
  } = evt;
2484
2485
  debugLog(
2485
2486
  "request to %s %s%s errored - %s",
2486
2487
  method,
2487
2488
  origin,
2488
- path6,
2489
+ path8,
2489
2490
  error62.message
2490
2491
  );
2491
2492
  }
@@ -2600,7 +2601,7 @@ var require_request = __commonJS({
2600
2601
  var kHandler = Symbol("handler");
2601
2602
  var Request = class {
2602
2603
  constructor(origin, {
2603
- path: path6,
2604
+ path: path8,
2604
2605
  method,
2605
2606
  body,
2606
2607
  headers,
@@ -2617,11 +2618,11 @@ var require_request = __commonJS({
2617
2618
  maxRedirections,
2618
2619
  typeOfService
2619
2620
  }, handler) {
2620
- if (typeof path6 !== "string") {
2621
+ if (typeof path8 !== "string") {
2621
2622
  throw new InvalidArgumentError("path must be a string");
2622
- } else if (path6[0] !== "/" && !(path6.startsWith("http://") || path6.startsWith("https://")) && method !== "CONNECT") {
2623
+ } else if (path8[0] !== "/" && !(path8.startsWith("http://") || path8.startsWith("https://")) && method !== "CONNECT") {
2623
2624
  throw new InvalidArgumentError("path must be an absolute URL or start with a slash");
2624
- } else if (invalidPathRegex.test(path6)) {
2625
+ } else if (invalidPathRegex.test(path8)) {
2625
2626
  throw new InvalidArgumentError("invalid request path");
2626
2627
  }
2627
2628
  if (typeof method !== "string") {
@@ -2696,7 +2697,7 @@ var require_request = __commonJS({
2696
2697
  this.completed = false;
2697
2698
  this.aborted = false;
2698
2699
  this.upgrade = upgrade || null;
2699
- this.path = query ? serializePathWithQuery(path6, query) : path6;
2700
+ this.path = query ? serializePathWithQuery(path8, query) : path8;
2700
2701
  this.origin = origin;
2701
2702
  this.protocol = getProtocolFromUrlString(origin);
2702
2703
  this.idempotent = idempotent == null ? method === "HEAD" || method === "GET" : idempotent;
@@ -6265,7 +6266,7 @@ var require_formdata = __commonJS({
6265
6266
  var { kEnumerableProperty } = require_util();
6266
6267
  var { webidl } = require_webidl();
6267
6268
  var nodeUtil = __require("util");
6268
- var FormData = class _FormData {
6269
+ var FormData2 = class _FormData {
6269
6270
  #state = [];
6270
6271
  constructor(form = void 0) {
6271
6272
  webidl.util.markAsUncloneable(this);
@@ -6382,11 +6383,11 @@ var require_formdata = __commonJS({
6382
6383
  formData.#state = newState;
6383
6384
  }
6384
6385
  };
6385
- var { getFormDataState, setFormDataState } = FormData;
6386
- Reflect.deleteProperty(FormData, "getFormDataState");
6387
- Reflect.deleteProperty(FormData, "setFormDataState");
6388
- iteratorMixin("FormData", FormData, getFormDataState, "name", "value");
6389
- Object.defineProperties(FormData.prototype, {
6386
+ var { getFormDataState, setFormDataState } = FormData2;
6387
+ Reflect.deleteProperty(FormData2, "getFormDataState");
6388
+ Reflect.deleteProperty(FormData2, "setFormDataState");
6389
+ iteratorMixin("FormData", FormData2, getFormDataState, "name", "value");
6390
+ Object.defineProperties(FormData2.prototype, {
6390
6391
  append: kEnumerableProperty,
6391
6392
  delete: kEnumerableProperty,
6392
6393
  get: kEnumerableProperty,
@@ -6414,8 +6415,8 @@ var require_formdata = __commonJS({
6414
6415
  }
6415
6416
  return { name, value };
6416
6417
  }
6417
- webidl.is.FormData = webidl.util.MakeTypeAssertion(FormData);
6418
- module.exports = { FormData, makeEntry, setFormDataState };
6418
+ webidl.is.FormData = webidl.util.MakeTypeAssertion(FormData2);
6419
+ module.exports = { FormData: FormData2, makeEntry, setFormDataState };
6419
6420
  }
6420
6421
  });
6421
6422
 
@@ -6779,7 +6780,7 @@ var require_body = __commonJS({
6779
6780
  fullyReadBody,
6780
6781
  extractMimeType
6781
6782
  } = require_util2();
6782
- var { FormData, setFormDataState } = require_formdata();
6783
+ var { FormData: FormData2, setFormDataState } = require_formdata();
6783
6784
  var { webidl } = require_webidl();
6784
6785
  var assert2 = __require("assert");
6785
6786
  var { isErrored, isDisturbed } = __require("stream");
@@ -6972,13 +6973,13 @@ Content-Type: ${value.type || "application/octet-stream"}\r
6972
6973
  switch (mimeType.essence) {
6973
6974
  case "multipart/form-data": {
6974
6975
  const parsed = multipartFormDataParser(value, mimeType);
6975
- const fd = new FormData();
6976
+ const fd = new FormData2();
6976
6977
  setFormDataState(fd, parsed);
6977
6978
  return fd;
6978
6979
  }
6979
6980
  case "application/x-www-form-urlencoded": {
6980
6981
  const entries = new URLSearchParams(value.toString());
6981
- const fd = new FormData();
6982
+ const fd = new FormData2();
6982
6983
  for (const [name, value2] of entries) {
6983
6984
  fd.append(name, value2);
6984
6985
  }
@@ -7902,7 +7903,7 @@ var require_client_h1 = __commonJS({
7902
7903
  return method !== "GET" && method !== "HEAD" && method !== "OPTIONS" && method !== "TRACE" && method !== "CONNECT";
7903
7904
  }
7904
7905
  function writeH1(client, request) {
7905
- const { method, path: path6, host, upgrade, blocking, reset } = request;
7906
+ const { method, path: path8, host, upgrade, blocking, reset } = request;
7906
7907
  let { body, headers, contentLength } = request;
7907
7908
  const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH" || method === "QUERY" || method === "PROPFIND" || method === "PROPPATCH";
7908
7909
  if (util.isFormDataLike(body)) {
@@ -7980,7 +7981,7 @@ var require_client_h1 = __commonJS({
7980
7981
  if (socket.setTypeOfService) {
7981
7982
  socket.setTypeOfService(request.typeOfService);
7982
7983
  }
7983
- let header = `${method} ${path6} HTTP/1.1\r
7984
+ let header = `${method} ${path8} HTTP/1.1\r
7984
7985
  `;
7985
7986
  if (typeof host === "string") {
7986
7987
  header += `host: ${host}\r
@@ -8659,7 +8660,7 @@ var require_client_h2 = __commonJS({
8659
8660
  const headersTimeout = request.headersTimeout ?? client[kHeadersTimeout];
8660
8661
  const bodyTimeout = request.bodyTimeout ?? client[kBodyTimeout];
8661
8662
  const session = client[kHTTP2Session];
8662
- const { method, path: path6, host, upgrade, expectContinue, signal, protocol, headers: reqHeaders } = request;
8663
+ const { method, path: path8, host, upgrade, expectContinue, signal, protocol, headers: reqHeaders } = request;
8663
8664
  let { body } = request;
8664
8665
  if (upgrade != null && upgrade !== "websocket") {
8665
8666
  util.errorRequest(client, request, new InvalidArgumentError(`Custom upgrade "${upgrade}" not supported over HTTP/2`));
@@ -8728,7 +8729,7 @@ var require_client_h2 = __commonJS({
8728
8729
  }
8729
8730
  headers[HTTP2_HEADER_METHOD] = "CONNECT";
8730
8731
  headers[HTTP2_HEADER_PROTOCOL] = "websocket";
8731
- headers[HTTP2_HEADER_PATH] = path6;
8732
+ headers[HTTP2_HEADER_PATH] = path8;
8732
8733
  if (protocol === "ws:" || protocol === "wss:") {
8733
8734
  headers[HTTP2_HEADER_SCHEME] = protocol === "ws:" ? "http" : "https";
8734
8735
  } else {
@@ -8780,7 +8781,7 @@ var require_client_h2 = __commonJS({
8780
8781
  stream.setTimeout(headersTimeout);
8781
8782
  return true;
8782
8783
  }
8783
- headers[HTTP2_HEADER_PATH] = path6;
8784
+ headers[HTTP2_HEADER_PATH] = path8;
8784
8785
  headers[HTTP2_HEADER_SCHEME] = protocol === "http:" ? "http" : "https";
8785
8786
  const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH";
8786
8787
  if (body && typeof body.read === "function") {
@@ -10261,7 +10262,7 @@ var require_agent = __commonJS({
10261
10262
  function defaultFactory(origin, opts) {
10262
10263
  return opts && opts.connections === 1 ? new Client(origin, opts) : new Pool(origin, opts);
10263
10264
  }
10264
- var Agent4 = class extends DispatcherBase {
10265
+ var Agent3 = class extends DispatcherBase {
10265
10266
  constructor({ factory = defaultFactory, maxOrigins = Infinity, connect, ...options } = {}) {
10266
10267
  if (typeof factory !== "function") {
10267
10268
  throw new InvalidArgumentError("factory must be a function.");
@@ -10370,7 +10371,7 @@ var require_agent = __commonJS({
10370
10371
  return allClientStats;
10371
10372
  }
10372
10373
  };
10373
- module.exports = Agent4;
10374
+ module.exports = Agent3;
10374
10375
  }
10375
10376
  });
10376
10377
 
@@ -11076,7 +11077,7 @@ var require_proxy_agent = __commonJS({
11076
11077
  "node_modules/undici/lib/dispatcher/proxy-agent.js"(exports, module) {
11077
11078
  "use strict";
11078
11079
  var { kProxy, kClose, kDestroy, kDispatch } = require_symbols();
11079
- var Agent4 = require_agent();
11080
+ var Agent3 = require_agent();
11080
11081
  var Pool = require_pool();
11081
11082
  var DispatcherBase = require_dispatcher_base();
11082
11083
  var { InvalidArgumentError, RequestAbortedError, SecureProxyConnectionError } = require_errors();
@@ -11132,10 +11133,10 @@ var require_proxy_agent = __commonJS({
11132
11133
  };
11133
11134
  const {
11134
11135
  origin,
11135
- path: path6 = "/",
11136
+ path: path8 = "/",
11136
11137
  headers = {}
11137
11138
  } = opts;
11138
- opts.path = origin + path6;
11139
+ opts.path = origin + path8;
11139
11140
  if (!("host" in headers) && !("Host" in headers)) {
11140
11141
  const { host } = new URL(origin);
11141
11142
  headers.host = host;
@@ -11207,7 +11208,7 @@ var require_proxy_agent = __commonJS({
11207
11208
  } else {
11208
11209
  this[kClient] = clientFactory(url2, { connect });
11209
11210
  }
11210
- this[kAgent] = new Agent4({
11211
+ this[kAgent] = new Agent3({
11211
11212
  ...opts,
11212
11213
  factory,
11213
11214
  connect: async (opts2, callback) => {
@@ -11335,7 +11336,7 @@ var require_env_http_proxy_agent = __commonJS({
11335
11336
  var DispatcherBase = require_dispatcher_base();
11336
11337
  var { kClose, kDestroy, kClosed, kDestroyed, kDispatch, kNoProxyAgent, kHttpProxyAgent, kHttpsProxyAgent } = require_symbols();
11337
11338
  var ProxyAgent = require_proxy_agent();
11338
- var Agent4 = require_agent();
11339
+ var Agent3 = require_agent();
11339
11340
  var DEFAULT_PORTS = {
11340
11341
  "http:": 80,
11341
11342
  "https:": 443
@@ -11348,7 +11349,7 @@ var require_env_http_proxy_agent = __commonJS({
11348
11349
  super();
11349
11350
  this.#opts = opts;
11350
11351
  const { httpProxy, httpsProxy, noProxy, ...agentOpts } = opts;
11351
- this[kNoProxyAgent] = new Agent4(agentOpts);
11352
+ this[kNoProxyAgent] = new Agent3(agentOpts);
11352
11353
  const HTTP_PROXY = httpProxy ?? process.env.http_proxy ?? process.env.HTTP_PROXY;
11353
11354
  if (HTTP_PROXY) {
11354
11355
  this[kHttpProxyAgent] = new ProxyAgent({ ...agentOpts, uri: HTTP_PROXY });
@@ -13295,20 +13296,20 @@ var require_mock_utils = __commonJS({
13295
13296
  }
13296
13297
  return normalizedQp;
13297
13298
  }
13298
- function safeUrl(path6) {
13299
- if (typeof path6 !== "string") {
13300
- return path6;
13299
+ function safeUrl(path8) {
13300
+ if (typeof path8 !== "string") {
13301
+ return path8;
13301
13302
  }
13302
- const pathSegments = path6.split("?", 3);
13303
+ const pathSegments = path8.split("?", 3);
13303
13304
  if (pathSegments.length !== 2) {
13304
- return path6;
13305
+ return path8;
13305
13306
  }
13306
13307
  const qp = new URLSearchParams(pathSegments.pop());
13307
13308
  qp.sort();
13308
13309
  return [...pathSegments, qp.toString()].join("?");
13309
13310
  }
13310
- function matchKey(mockDispatch2, { path: path6, method, body, headers }) {
13311
- const pathMatch = matchValue(mockDispatch2.path, path6);
13311
+ function matchKey(mockDispatch2, { path: path8, method, body, headers }) {
13312
+ const pathMatch = matchValue(mockDispatch2.path, path8);
13312
13313
  const methodMatch = matchValue(mockDispatch2.method, method);
13313
13314
  const bodyMatch = typeof mockDispatch2.body !== "undefined" ? matchValue(mockDispatch2.body, body) : true;
13314
13315
  const headersMatch = matchHeaders(mockDispatch2, headers);
@@ -13333,8 +13334,8 @@ var require_mock_utils = __commonJS({
13333
13334
  const basePath = key.query ? serializePathWithQuery(key.path, key.query) : key.path;
13334
13335
  const resolvedPath = typeof basePath === "string" ? safeUrl(basePath) : basePath;
13335
13336
  const resolvedPathWithoutTrailingSlash = removeTrailingSlash(resolvedPath);
13336
- let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path: path6, ignoreTrailingSlash }) => {
13337
- return ignoreTrailingSlash ? matchValue(removeTrailingSlash(safeUrl(path6)), resolvedPathWithoutTrailingSlash) : matchValue(safeUrl(path6), resolvedPath);
13337
+ let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path: path8, ignoreTrailingSlash }) => {
13338
+ return ignoreTrailingSlash ? matchValue(removeTrailingSlash(safeUrl(path8)), resolvedPathWithoutTrailingSlash) : matchValue(safeUrl(path8), resolvedPath);
13338
13339
  });
13339
13340
  if (matchedMockDispatches.length === 0) {
13340
13341
  throw new MockNotMatchedError(`Mock dispatch not matched for path '${resolvedPath}'`);
@@ -13373,19 +13374,19 @@ var require_mock_utils = __commonJS({
13373
13374
  mockDispatches.splice(index, 1);
13374
13375
  }
13375
13376
  }
13376
- function removeTrailingSlash(path6) {
13377
- while (path6.endsWith("/")) {
13378
- path6 = path6.slice(0, -1);
13377
+ function removeTrailingSlash(path8) {
13378
+ while (path8.endsWith("/")) {
13379
+ path8 = path8.slice(0, -1);
13379
13380
  }
13380
- if (path6.length === 0) {
13381
- path6 = "/";
13381
+ if (path8.length === 0) {
13382
+ path8 = "/";
13382
13383
  }
13383
- return path6;
13384
+ return path8;
13384
13385
  }
13385
13386
  function buildKey(opts) {
13386
- const { path: path6, method, body, headers, query } = opts;
13387
+ const { path: path8, method, body, headers, query } = opts;
13387
13388
  return {
13388
- path: path6,
13389
+ path: path8,
13389
13390
  method,
13390
13391
  body,
13391
13392
  headers,
@@ -14075,10 +14076,10 @@ var require_pending_interceptors_formatter = __commonJS({
14075
14076
  }
14076
14077
  format(pendingInterceptors) {
14077
14078
  const withPrettyHeaders = pendingInterceptors.map(
14078
- ({ method, path: path6, data: { statusCode }, persist, times, timesInvoked, origin }) => ({
14079
+ ({ method, path: path8, data: { statusCode }, persist, times, timesInvoked, origin }) => ({
14079
14080
  Method: method,
14080
14081
  Origin: origin,
14081
- Path: path6,
14082
+ Path: path8,
14082
14083
  "Status code": statusCode,
14083
14084
  Persistent: persist ? PERSISTENT : NOT_PERSISTENT,
14084
14085
  Invocations: timesInvoked,
@@ -14097,7 +14098,7 @@ var require_mock_agent = __commonJS({
14097
14098
  "node_modules/undici/lib/mock/mock-agent.js"(exports, module) {
14098
14099
  "use strict";
14099
14100
  var { kClients } = require_symbols();
14100
- var Agent4 = require_agent();
14101
+ var Agent3 = require_agent();
14101
14102
  var {
14102
14103
  kAgent,
14103
14104
  kMockAgentSet,
@@ -14135,7 +14136,7 @@ var require_mock_agent = __commonJS({
14135
14136
  if (opts?.agent && typeof opts.agent.dispatch !== "function") {
14136
14137
  throw new InvalidArgumentError("Argument opts.agent must implement Agent");
14137
14138
  }
14138
- const agent = opts?.agent ? opts.agent : new Agent4(opts);
14139
+ const agent = opts?.agent ? opts.agent : new Agent3(opts);
14139
14140
  this[kAgent] = agent;
14140
14141
  this[kClients] = agent[kClients];
14141
14142
  this[kOptions] = mockOptions;
@@ -14160,9 +14161,9 @@ var require_mock_agent = __commonJS({
14160
14161
  const acceptNonStandardSearchParameters = this[kMockAgentAcceptsNonStandardSearchParameters];
14161
14162
  const dispatchOpts = { ...opts };
14162
14163
  if (acceptNonStandardSearchParameters && dispatchOpts.path) {
14163
- const [path6, searchParams] = dispatchOpts.path.split("?");
14164
+ const [path8, searchParams] = dispatchOpts.path.split("?");
14164
14165
  const normalizedSearchParams = normalizeSearchParams(searchParams, acceptNonStandardSearchParameters);
14165
- dispatchOpts.path = `${path6}?${normalizedSearchParams}`;
14166
+ dispatchOpts.path = `${path8}?${normalizedSearchParams}`;
14166
14167
  }
14167
14168
  return this[kAgent].dispatch(dispatchOpts, handler);
14168
14169
  }
@@ -14366,7 +14367,7 @@ var require_snapshot_utils = __commonJS({
14366
14367
  var require_snapshot_recorder = __commonJS({
14367
14368
  "node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
14368
14369
  "use strict";
14369
- var { writeFile, readFile, mkdir } = __require("fs/promises");
14370
+ var { writeFile: writeFile2, readFile, mkdir: mkdir2 } = __require("fs/promises");
14370
14371
  var { dirname, resolve } = __require("path");
14371
14372
  var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("timers");
14372
14373
  var { InvalidArgumentError, UndiciError } = require_errors();
@@ -14563,12 +14564,12 @@ var require_snapshot_recorder = __commonJS({
14563
14564
  * @return {Promise<void>} - Resolves when snapshots are loaded
14564
14565
  */
14565
14566
  async loadSnapshots(filePath) {
14566
- const path6 = filePath || this.#snapshotPath;
14567
- if (!path6) {
14567
+ const path8 = filePath || this.#snapshotPath;
14568
+ if (!path8) {
14568
14569
  throw new InvalidArgumentError("Snapshot path is required");
14569
14570
  }
14570
14571
  try {
14571
- const data = await readFile(resolve(path6), "utf8");
14572
+ const data = await readFile(resolve(path8), "utf8");
14572
14573
  const parsed = JSON.parse(data);
14573
14574
  if (Array.isArray(parsed)) {
14574
14575
  this.#snapshots.clear();
@@ -14582,7 +14583,7 @@ var require_snapshot_recorder = __commonJS({
14582
14583
  if (error62.code === "ENOENT") {
14583
14584
  this.#snapshots.clear();
14584
14585
  } else {
14585
- throw new UndiciError(`Failed to load snapshots from ${path6}`, { cause: error62 });
14586
+ throw new UndiciError(`Failed to load snapshots from ${path8}`, { cause: error62 });
14586
14587
  }
14587
14588
  }
14588
14589
  }
@@ -14593,17 +14594,17 @@ var require_snapshot_recorder = __commonJS({
14593
14594
  * @returns {Promise<void>} - Resolves when snapshots are saved
14594
14595
  */
14595
14596
  async saveSnapshots(filePath) {
14596
- const path6 = filePath || this.#snapshotPath;
14597
- if (!path6) {
14597
+ const path8 = filePath || this.#snapshotPath;
14598
+ if (!path8) {
14598
14599
  throw new InvalidArgumentError("Snapshot path is required");
14599
14600
  }
14600
- const resolvedPath = resolve(path6);
14601
- await mkdir(dirname(resolvedPath), { recursive: true });
14601
+ const resolvedPath = resolve(path8);
14602
+ await mkdir2(dirname(resolvedPath), { recursive: true });
14602
14603
  const data = Array.from(this.#snapshots.entries()).map(([hash2, snapshot]) => ({
14603
14604
  hash: hash2,
14604
14605
  snapshot
14605
14606
  }));
14606
- await writeFile(resolvedPath, JSON.stringify(data, null, 2), { flush: true });
14607
+ await writeFile2(resolvedPath, JSON.stringify(data, null, 2), { flush: true });
14607
14608
  }
14608
14609
  /**
14609
14610
  * Clears all recorded snapshots
@@ -14742,7 +14743,7 @@ var require_snapshot_recorder = __commonJS({
14742
14743
  var require_snapshot_agent = __commonJS({
14743
14744
  "node_modules/undici/lib/mock/snapshot-agent.js"(exports, module) {
14744
14745
  "use strict";
14745
- var Agent4 = require_agent();
14746
+ var Agent3 = require_agent();
14746
14747
  var MockAgent = require_mock_agent();
14747
14748
  var { SnapshotRecorder } = require_snapshot_recorder();
14748
14749
  var WrapHandler = require_wrap_handler();
@@ -14793,7 +14794,7 @@ var require_snapshot_agent = __commonJS({
14793
14794
  });
14794
14795
  this[kSnapshotLoaded] = false;
14795
14796
  if (this[kSnapshotMode] === "record" || this[kSnapshotMode] === "update" || this[kSnapshotMode] === "playback" && opts.excludeUrls && opts.excludeUrls.length > 0) {
14796
- this[kRealAgent] = new Agent4(opts);
14797
+ this[kRealAgent] = new Agent3(opts);
14797
14798
  }
14798
14799
  if ((this[kSnapshotMode] === "playback" || this[kSnapshotMode] === "update") && this[kSnapshotPath]) {
14799
14800
  this.loadSnapshots().catch(() => {
@@ -15032,9 +15033,9 @@ var require_global2 = __commonJS({
15032
15033
  var globalDispatcher = Symbol.for("undici.globalDispatcher.2");
15033
15034
  var legacyGlobalDispatcher = Symbol.for("undici.globalDispatcher.1");
15034
15035
  var { InvalidArgumentError } = require_errors();
15035
- var Agent4 = require_agent();
15036
+ var Agent3 = require_agent();
15036
15037
  if (getGlobalDispatcher() === void 0) {
15037
- setGlobalDispatcher(new Agent4());
15038
+ setGlobalDispatcher(new Agent3());
15038
15039
  }
15039
15040
  function setGlobalDispatcher(agent) {
15040
15041
  if (!agent || typeof agent.dispatch !== "function") {
@@ -15229,15 +15230,15 @@ var require_redirect_handler = __commonJS({
15229
15230
  return;
15230
15231
  }
15231
15232
  const { origin, pathname, search } = util.parseURL(new URL(this.location, this.opts.origin && new URL(this.opts.path, this.opts.origin)));
15232
- const path6 = search ? `${pathname}${search}` : pathname;
15233
- const redirectUrlString = `${origin}${path6}`;
15233
+ const path8 = search ? `${pathname}${search}` : pathname;
15234
+ const redirectUrlString = `${origin}${path8}`;
15234
15235
  for (const historyUrl of this.history) {
15235
15236
  if (historyUrl.toString() === redirectUrlString) {
15236
15237
  throw new InvalidArgumentError(`Redirect loop detected. Cannot redirect to ${origin}. This typically happens when using a Client or Pool with cross-origin redirects. Use an Agent for cross-origin redirects.`);
15237
15238
  }
15238
15239
  }
15239
15240
  this.opts.headers = cleanRequestHeaders(this.opts.headers, statusCode === 303, this.opts.origin !== origin);
15240
- this.opts.path = path6;
15241
+ this.opts.path = path8;
15241
15242
  this.opts.origin = origin;
15242
15243
  this.opts.query = null;
15243
15244
  }
@@ -16996,10 +16997,10 @@ var require_cache_handler = __commonJS({
16996
16997
  }
16997
16998
  return locationUrl.pathname + locationUrl.search;
16998
16999
  }
16999
- function deleteCachedUri(store, cacheKey, path6) {
17000
+ function deleteCachedUri(store, cacheKey, path8) {
17000
17001
  deleteCachedValue(store, {
17001
17002
  ...cacheKey,
17002
- path: path6
17003
+ path: path8
17003
17004
  });
17004
17005
  for (let i = 0; i < util.safeHTTPMethods.length; i++) {
17005
17006
  const method = util.safeHTTPMethods[i];
@@ -17007,7 +17008,7 @@ var require_cache_handler = __commonJS({
17007
17008
  deleteCachedValue(store, {
17008
17009
  ...cacheKey,
17009
17010
  method,
17010
- path: path6
17011
+ path: path8
17011
17012
  });
17012
17013
  }
17013
17014
  }
@@ -17018,9 +17019,9 @@ var require_cache_handler = __commonJS({
17018
17019
  }
17019
17020
  const values = Array.isArray(headerValue) ? headerValue : [headerValue];
17020
17021
  for (let i = 0; i < values.length; i++) {
17021
- const path6 = getSameOriginPath(cacheKey, values[i]);
17022
- if (path6 !== void 0) {
17023
- deleteCachedUri(store, cacheKey, path6);
17022
+ const path8 = getSameOriginPath(cacheKey, values[i]);
17023
+ if (path8 !== void 0) {
17024
+ deleteCachedUri(store, cacheKey, path8);
17024
17025
  }
17025
17026
  }
17026
17027
  }
@@ -22167,11 +22168,11 @@ var require_fetch = __commonJS({
22167
22168
  function dispatch({ body }) {
22168
22169
  const url2 = requestCurrentURL(request);
22169
22170
  const agent = fetchParams.controller.dispatcher;
22170
- const path6 = url2.pathname + url2.search;
22171
+ const path8 = url2.pathname + url2.search;
22171
22172
  const hasTrailingQuestionMark = url2.search.length === 0 && url2.href[url2.href.length - url2.hash.length - 1] === "?";
22172
22173
  return new Promise((resolve, reject) => agent.dispatch(
22173
22174
  {
22174
- path: hasTrailingQuestionMark ? `${path6}?` : path6,
22175
+ path: hasTrailingQuestionMark ? `${path8}?` : path8,
22175
22176
  origin: url2.origin,
22176
22177
  method: request.method,
22177
22178
  body: agent.isMockActive ? request.body && (request.body.source || request.body.stream) : body,
@@ -22721,8 +22722,8 @@ var require_cache3 = __commonJS({
22721
22722
  * @returns {requestResponseList}
22722
22723
  */
22723
22724
  #batchCacheOperations(operations) {
22724
- const cache = this.#relevantRequestResponseList;
22725
- const backupCache = [...cache];
22725
+ const cache2 = this.#relevantRequestResponseList;
22726
+ const backupCache = [...cache2];
22726
22727
  const addedItems = [];
22727
22728
  const resultList = [];
22728
22729
  try {
@@ -22749,9 +22750,9 @@ var require_cache3 = __commonJS({
22749
22750
  return [];
22750
22751
  }
22751
22752
  for (const requestResponse of requestResponses) {
22752
- const idx = cache.indexOf(requestResponse);
22753
+ const idx = cache2.indexOf(requestResponse);
22753
22754
  assert2(idx !== -1);
22754
- cache.splice(idx, 1);
22755
+ cache2.splice(idx, 1);
22755
22756
  }
22756
22757
  } else if (operation.type === "put") {
22757
22758
  if (operation.response == null) {
@@ -22781,11 +22782,11 @@ var require_cache3 = __commonJS({
22781
22782
  }
22782
22783
  requestResponses = this.#queryCache(operation.request);
22783
22784
  for (const requestResponse of requestResponses) {
22784
- const idx = cache.indexOf(requestResponse);
22785
+ const idx = cache2.indexOf(requestResponse);
22785
22786
  assert2(idx !== -1);
22786
- cache.splice(idx, 1);
22787
+ cache2.splice(idx, 1);
22787
22788
  }
22788
- cache.push([operation.request, operation.response]);
22789
+ cache2.push([operation.request, operation.response]);
22789
22790
  addedItems.push([operation.request, operation.response]);
22790
22791
  }
22791
22792
  resultList.push([operation.request, operation.response]);
@@ -22962,13 +22963,13 @@ var require_cachestorage = __commonJS({
22962
22963
  if (options.cacheName != null) {
22963
22964
  if (this.#caches.has(options.cacheName)) {
22964
22965
  const cacheList = this.#caches.get(options.cacheName);
22965
- const cache = new Cache(kConstruct, cacheList);
22966
- return await cache.match(request, options);
22966
+ const cache2 = new Cache(kConstruct, cacheList);
22967
+ return await cache2.match(request, options);
22967
22968
  }
22968
22969
  } else {
22969
22970
  for (const cacheList of this.#caches.values()) {
22970
- const cache = new Cache(kConstruct, cacheList);
22971
- const response = await cache.match(request, options);
22971
+ const cache2 = new Cache(kConstruct, cacheList);
22972
+ const response = await cache2.match(request, options);
22972
22973
  if (response !== void 0) {
22973
22974
  return response;
22974
22975
  }
@@ -22998,12 +22999,12 @@ var require_cachestorage = __commonJS({
22998
22999
  webidl.argumentLengthCheck(arguments, 1, prefix);
22999
23000
  cacheName = webidl.converters.DOMString(cacheName, prefix, "cacheName");
23000
23001
  if (this.#caches.has(cacheName)) {
23001
- const cache2 = this.#caches.get(cacheName);
23002
- return new Cache(kConstruct, cache2);
23002
+ const cache3 = this.#caches.get(cacheName);
23003
+ return new Cache(kConstruct, cache3);
23003
23004
  }
23004
- const cache = [];
23005
- this.#caches.set(cacheName, cache);
23006
- return new Cache(kConstruct, cache);
23005
+ const cache2 = [];
23006
+ this.#caches.set(cacheName, cache2);
23007
+ return new Cache(kConstruct, cache2);
23007
23008
  }
23008
23009
  /**
23009
23010
  * @see https://w3c.github.io/ServiceWorker/#cache-storage-delete
@@ -23118,9 +23119,9 @@ var require_util4 = __commonJS({
23118
23119
  }
23119
23120
  }
23120
23121
  }
23121
- function validateCookiePath(path6) {
23122
- for (let i = 0; i < path6.length; ++i) {
23123
- const code = path6.charCodeAt(i);
23122
+ function validateCookiePath(path8) {
23123
+ for (let i = 0; i < path8.length; ++i) {
23124
+ const code = path8.charCodeAt(i);
23124
23125
  if (code < 32 || // exclude CTLs (0-31)
23125
23126
  code > 126 || // exclude DEL and non-ascii
23126
23127
  code === 59) {
@@ -26401,7 +26402,7 @@ var require_undici = __commonJS({
26401
26402
  var Pool = require_pool();
26402
26403
  var BalancedPool = require_balanced_pool();
26403
26404
  var RoundRobinPool = require_round_robin_pool();
26404
- var Agent4 = require_agent();
26405
+ var Agent3 = require_agent();
26405
26406
  var ProxyAgent = require_proxy_agent();
26406
26407
  var Socks5ProxyAgent = require_socks5_proxy_agent();
26407
26408
  var EnvHttpProxyAgent = require_env_http_proxy_agent();
@@ -26428,7 +26429,7 @@ var require_undici = __commonJS({
26428
26429
  module.exports.Pool = Pool;
26429
26430
  module.exports.BalancedPool = BalancedPool;
26430
26431
  module.exports.RoundRobinPool = RoundRobinPool;
26431
- module.exports.Agent = Agent4;
26432
+ module.exports.Agent = Agent3;
26432
26433
  module.exports.ProxyAgent = ProxyAgent;
26433
26434
  module.exports.Socks5ProxyAgent = Socks5ProxyAgent;
26434
26435
  module.exports.EnvHttpProxyAgent = EnvHttpProxyAgent;
@@ -26474,11 +26475,11 @@ var require_undici = __commonJS({
26474
26475
  if (typeof opts.path !== "string") {
26475
26476
  throw new InvalidArgumentError("invalid opts.path");
26476
26477
  }
26477
- let path6 = opts.path;
26478
+ let path8 = opts.path;
26478
26479
  if (!opts.path.startsWith("/")) {
26479
- path6 = `/${path6}`;
26480
+ path8 = `/${path8}`;
26480
26481
  }
26481
- url2 = new URL(util.parseOrigin(url2).origin + path6);
26482
+ url2 = new URL(util.parseOrigin(url2).origin + path8);
26482
26483
  } else {
26483
26484
  if (!opts) {
26484
26485
  opts = typeof url2 === "object" ? url2 : {};
@@ -26622,8 +26623,7 @@ var init_redact = __esm({
26622
26623
  // src/api/api.ts
26623
26624
  async function fetchJson(params) {
26624
26625
  const { url: url2, init, timeoutMs, allowInsecureTls } = params;
26625
- const dispatcher = allowInsecureTls ? new import_undici.Agent({ connect: { rejectUnauthorized: false } }) : void 0;
26626
- const fetcher = allowInsecureTls ? import_undici.fetch : globalThis.fetch;
26626
+ const fetcher = globalThis.fetch;
26627
26627
  const controller = new AbortController();
26628
26628
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
26629
26629
  let response;
@@ -26631,8 +26631,7 @@ async function fetchJson(params) {
26631
26631
  try {
26632
26632
  response = await fetcher(url2, {
26633
26633
  ...init,
26634
- signal: controller.signal,
26635
- ...dispatcher ? { dispatcher } : {}
26634
+ signal: controller.signal
26636
26635
  });
26637
26636
  text = await response.text().catch(() => "");
26638
26637
  } finally {
@@ -26774,6 +26773,42 @@ async function sendWorkChatReplyMessage(params) {
26774
26773
  if (!data?.msgId) throw new Error("workchat: msgId missing in reply response");
26775
26774
  return { msgId: String(data.msgId) };
26776
26775
  }
26776
+ async function uploadWorkChatFile(params) {
26777
+ const { opts, filePath, userId } = params;
26778
+ const token = await getAccessToken(opts);
26779
+ const { readFile } = await import("fs/promises");
26780
+ const pathMod = await import("path");
26781
+ const content = await readFile(filePath);
26782
+ const fileName = pathMod.basename(filePath);
26783
+ const ext = pathMod.extname(fileName).replace(/^\./, "").toLowerCase();
26784
+ const mime = ext === "mp3" || ext === "m4a" ? "audio/mpeg" : ext === "wav" ? "audio/wav" : `image/${ext === "jpg" ? "jpeg" : ext || "png"}`;
26785
+ const form = new FormData();
26786
+ form.set("file", new Blob([new Uint8Array(content)], { type: mime }), fileName);
26787
+ const ownerUserId = userId !== void 0 && String(userId).trim() !== "" ? String(userId) : "0";
26788
+ form.set("userId", ownerUserId);
26789
+ const resp = await fetchJson({
26790
+ url: `${normalizeBaseUrl(opts.baseUrl)}/open-apis/v1/files/upload`,
26791
+ timeoutMs: opts.timeoutMs ?? RequestDefaults.TimeoutMs,
26792
+ allowInsecureTls: opts.allowInsecureTls,
26793
+ init: {
26794
+ method: "POST",
26795
+ headers: {
26796
+ Authorization: `Bearer ${token}`,
26797
+ "x-user-id": ownerUserId
26798
+ },
26799
+ body: form
26800
+ }
26801
+ });
26802
+ const data = unwrapEnvelope(resp);
26803
+ const url2 = typeof data?.url === "string" ? data.url.trim() : "";
26804
+ if (!url2) throw new Error("workchat: url missing in file upload response");
26805
+ return {
26806
+ url: url2,
26807
+ fileName: typeof data?.fileName === "string" ? data.fileName : void 0,
26808
+ suffix: typeof data?.suffix === "string" ? data.suffix : void 0,
26809
+ fileSize: typeof data?.fileSize === "number" ? data.fileSize : void 0
26810
+ };
26811
+ }
26777
26812
  async function notifyAgentCreatedStatus(opts, agentId) {
26778
26813
  try {
26779
26814
  const token = await getAccessToken(opts);
@@ -26788,11 +26823,10 @@ async function notifyAgentCreatedStatus(opts, agentId) {
26788
26823
  logger.error(`agent-created/status failed: ${String(err)}`);
26789
26824
  }
26790
26825
  }
26791
- var import_undici, tokenCache;
26826
+ var tokenCache;
26792
26827
  var init_api = __esm({
26793
26828
  "src/api/api.ts"() {
26794
26829
  "use strict";
26795
- import_undici = __toESM(require_undici(), 1);
26796
26830
  init_types();
26797
26831
  init_logger();
26798
26832
  init_redact();
@@ -26853,7 +26887,17 @@ function parseWorkChatFrame(data) {
26853
26887
  if (type === "CALLBACK" && String(envelope?.metadata?.topic || "").trim().toUpperCase() === "AGENT_MESSAGE") {
26854
26888
  const payload = parseMaybeJson(envelope?.data) ?? {};
26855
26889
  const message = payload?.message;
26856
- const text = typeof message === "string" ? message : typeof message?.content === "string" ? message.content : "";
26890
+ const messageType = typeof message === "object" && message !== null ? String(message.type ?? "").trim().toLowerCase() : "";
26891
+ const rawContent = typeof message === "string" ? message : typeof message?.content === "string" ? message.content : "";
26892
+ let mediaUrl;
26893
+ let text = rawContent;
26894
+ if (messageType === "image" || messageType === "audio") {
26895
+ mediaUrl = extractMediaUrl(rawContent) ?? firstFileUrl(payload);
26896
+ text = messageType === "image" ? "[\u7528\u6237\u53D1\u9001\u4E86\u4E00\u5F20\u56FE\u7247]" : "[\u7528\u6237\u53D1\u9001\u4E86\u4E00\u6761\u8BED\u97F3]";
26897
+ } else if (!text.trim()) {
26898
+ mediaUrl = firstFileUrl(payload);
26899
+ if (mediaUrl) text = "[\u7528\u6237\u53D1\u9001\u4E86\u4E00\u4E2A\u6587\u4EF6]";
26900
+ }
26857
26901
  return {
26858
26902
  type: "agent_message",
26859
26903
  message: {
@@ -26861,12 +26905,26 @@ function parseWorkChatFrame(data) {
26861
26905
  userId: String(payload?.userId ?? ""),
26862
26906
  openConversationId: String(payload?.conversationId ?? ""),
26863
26907
  messageId: String(payload?.msgId ?? ""),
26864
- ...payload?.agentId !== void 0 ? { agentId: String(payload.agentId) } : {}
26908
+ ...payload?.agentId !== void 0 ? { agentId: String(payload.agentId) } : {},
26909
+ ...mediaUrl ? { mediaUrl, mediaType: messageType || "file" } : {}
26865
26910
  }
26866
26911
  };
26867
26912
  }
26868
26913
  return { type: "ignored", ignoredKind: `${type}/${topicLower || "(no topic)"}` };
26869
26914
  }
26915
+ function extractMediaUrl(content) {
26916
+ if (!content.trim()) return void 0;
26917
+ try {
26918
+ const parsed = JSON.parse(content);
26919
+ return typeof parsed?.url === "string" && parsed.url.trim() ? parsed.url.trim() : void 0;
26920
+ } catch {
26921
+ return void 0;
26922
+ }
26923
+ }
26924
+ function firstFileUrl(payload) {
26925
+ const url2 = Array.isArray(payload?.fileUrl) ? payload.fileUrl[0] : void 0;
26926
+ return typeof url2 === "string" && url2.trim() ? url2.trim() : void 0;
26927
+ }
26870
26928
  function buildPongFrame(pongData) {
26871
26929
  return JSON.stringify({
26872
26930
  code: 200,
@@ -26880,8 +26938,10 @@ function generateMessageSid() {
26880
26938
  }
26881
26939
  function workChatMessageToMsgContext(message, accountId) {
26882
26940
  const fromUserId = message.userId ?? "";
26941
+ const body = message.mediaUrl ? `${message.text ?? ""}
26942
+ [media] ${message.mediaUrl}` : message.text ?? "";
26883
26943
  const ctx = {
26884
- Body: message.text ?? "",
26944
+ Body: body,
26885
26945
  From: fromUserId,
26886
26946
  To: fromUserId,
26887
26947
  AccountId: accountId,
@@ -26899,6 +26959,10 @@ function workChatMessageToMsgContext(message, accountId) {
26899
26959
  if (message.agentId) {
26900
26960
  ctx.AgentId = message.agentId;
26901
26961
  }
26962
+ if (message.mediaUrl) {
26963
+ ctx.MediaUrl = message.mediaUrl;
26964
+ ctx.MediaType = message.mediaType ?? "file";
26965
+ }
26902
26966
  return ctx;
26903
26967
  }
26904
26968
  var init_inbound = __esm({
@@ -27105,8 +27169,8 @@ function startBindingListener(sessionKey, opts, timeoutMs) {
27105
27169
  clearWorkChatTokenCache(opts.appKey || "default");
27106
27170
  const { endpoint, ticket } = await openConnection(opts);
27107
27171
  const url2 = `${endpoint}?ticket=${encodeURIComponent(ticket)}`;
27108
- const dispatcher = opts.allowInsecureTls ? new import_undici2.Agent({ connect: { rejectUnauthorized: false } }) : void 0;
27109
- ws = new import_undici2.WebSocket(url2, dispatcher ? { dispatcher } : void 0);
27172
+ const dispatcher = opts.allowInsecureTls ? new import_undici.Agent({ connect: { rejectUnauthorized: false } }) : void 0;
27173
+ ws = new import_undici.WebSocket(url2, dispatcher ? { dispatcher } : void 0);
27110
27174
  const entryNow = bindingListeners.get(sessionKey);
27111
27175
  if (!entryNow || entryNow.done) {
27112
27176
  try {
@@ -27232,12 +27296,12 @@ function completeWorkChatBinding(completion) {
27232
27296
  );
27233
27297
  return true;
27234
27298
  }
27235
- var import_undici2, BIND_CMD, BIND_TYPE, BIND_NAME, ACTIVE_BINDING_TTL_MS, activeBindings, pendingWaiters, bindingListeners;
27299
+ var import_undici, BIND_CMD, BIND_TYPE, BIND_NAME, ACTIVE_BINDING_TTL_MS, activeBindings, pendingWaiters, bindingListeners;
27236
27300
  var init_login_qr = __esm({
27237
27301
  "src/auth/login-qr.ts"() {
27238
27302
  "use strict";
27239
27303
  init_logger();
27240
- import_undici2 = __toESM(require_undici(), 1);
27304
+ import_undici = __toESM(require_undici(), 1);
27241
27305
  init_api();
27242
27306
  init_inbound();
27243
27307
  init_inbound();
@@ -27252,9 +27316,74 @@ var init_login_qr = __esm({
27252
27316
  }
27253
27317
  });
27254
27318
 
27319
+ // src/api/session-guard.ts
27320
+ function pauseSession(accountId) {
27321
+ const until = Date.now() + SESSION_PAUSE_DURATION_MS;
27322
+ pauseUntilMap.set(accountId, until);
27323
+ logger.info(
27324
+ `session-guard: paused accountId=${accountId} until=${new Date(until).toISOString()} (${SESSION_PAUSE_DURATION_MS / 1e3}s)`
27325
+ );
27326
+ }
27327
+ function isSessionPaused(accountId) {
27328
+ const until = pauseUntilMap.get(accountId);
27329
+ if (until === void 0) return false;
27330
+ if (Date.now() >= until) {
27331
+ pauseUntilMap.delete(accountId);
27332
+ return false;
27333
+ }
27334
+ return true;
27335
+ }
27336
+ function getRemainingPauseMs(accountId) {
27337
+ const until = pauseUntilMap.get(accountId);
27338
+ if (until === void 0) return 0;
27339
+ const remaining = until - Date.now();
27340
+ if (remaining <= 0) {
27341
+ pauseUntilMap.delete(accountId);
27342
+ return 0;
27343
+ }
27344
+ return remaining;
27345
+ }
27346
+ function assertSessionActive(accountId) {
27347
+ if (isSessionPaused(accountId)) {
27348
+ const remainingMin = Math.ceil(getRemainingPauseMs(accountId) / 6e4);
27349
+ throw new Error(
27350
+ `session paused for accountId=${accountId}, ${remainingMin} min remaining \u2014 the WorkChat binding is invalid, re-scan the QR code to fix`
27351
+ );
27352
+ }
27353
+ }
27354
+ function isPermanentBindingError(errMsg) {
27355
+ return PERMANENT_ERROR_PATTERNS.some((p) => errMsg.includes(p));
27356
+ }
27357
+ function pauseOnPermanentError(accountId, errMsg) {
27358
+ if (!isPermanentBindingError(errMsg)) return false;
27359
+ if (!isSessionPaused(accountId)) {
27360
+ pauseSession(accountId);
27361
+ logger.warn(
27362
+ `session-guard: permanent binding error, pausing accountId=${accountId} err="${errMsg.slice(0, 120)}" \u2014 re-scan required`
27363
+ );
27364
+ }
27365
+ return true;
27366
+ }
27367
+ var SESSION_PAUSE_DURATION_MS, PERMANENT_ERROR_PATTERNS, pauseUntilMap;
27368
+ var init_session_guard = __esm({
27369
+ "src/api/session-guard.ts"() {
27370
+ "use strict";
27371
+ init_logger();
27372
+ SESSION_PAUSE_DURATION_MS = 60 * 60 * 1e3;
27373
+ PERMANENT_ERROR_PATTERNS = [
27374
+ "\u667A\u80FD\u4F53\u4E0D\u80FD\u4E3A\u7A7A",
27375
+ "\u667A\u80FD\u4F53\u4E0D\u662F\u63A5\u6536\u4EBA\u7684\u667A\u80FD\u4F53",
27376
+ "\u8BBE\u5907\u94FE\u63A5\u5F02\u5E38",
27377
+ "\u7533\u8BF7\u7ED1\u5B9A\u7684AppKey\u4E0D\u5B58\u5728"
27378
+ ];
27379
+ pauseUntilMap = /* @__PURE__ */ new Map();
27380
+ }
27381
+ });
27382
+
27255
27383
  // src/messaging/send.ts
27256
27384
  var send_exports = {};
27257
27385
  __export(send_exports, {
27386
+ buildApiOptions: () => buildApiOptions,
27258
27387
  pushWorkChatMessage: () => pushWorkChatMessage,
27259
27388
  replyWorkChatMessage: () => replyWorkChatMessage
27260
27389
  });
@@ -27300,6 +27429,7 @@ async function replyWorkChatMessage(params) {
27300
27429
  logger.info(`reply sent to=${to} messageId=${result.msgId}`);
27301
27430
  return { messageId: result.msgId };
27302
27431
  } catch (err) {
27432
+ pauseOnPermanentError(account.accountId, err instanceof Error ? err.message : String(err));
27303
27433
  logger.error(`replyWorkChatMessage: failed to=${to} err=${String(err)}`);
27304
27434
  throw err;
27305
27435
  }
@@ -27325,6 +27455,7 @@ async function pushWorkChatMessage(params) {
27325
27455
  logger.info(`push sent to=${to} messageId=${result.msgId}`);
27326
27456
  return { messageId: result.msgId };
27327
27457
  } catch (err) {
27458
+ pauseOnPermanentError(account.accountId, err instanceof Error ? err.message : String(err));
27328
27459
  logger.error(`pushWorkChatMessage: failed to=${to} err=${String(err)}`);
27329
27460
  throw err;
27330
27461
  }
@@ -27334,15 +27465,230 @@ var init_send = __esm({
27334
27465
  "use strict";
27335
27466
  init_types();
27336
27467
  init_api();
27468
+ init_session_guard();
27469
+ init_logger();
27470
+ }
27471
+ });
27472
+
27473
+ // src/messaging/send-media.ts
27474
+ var send_media_exports = {};
27475
+ __export(send_media_exports, {
27476
+ downloadRemoteMediaToTemp: () => downloadRemoteMediaToTemp,
27477
+ isLocalMediaSource: () => isLocalMediaSource,
27478
+ resolveLocalMediaPath: () => resolveLocalMediaPath,
27479
+ sendWorkChatMediaFile: () => sendWorkChatMediaFile
27480
+ });
27481
+ import { mkdir, writeFile } from "fs/promises";
27482
+ import os4 from "os";
27483
+ import path5 from "path";
27484
+ function isLocalMediaSource(mediaUrl) {
27485
+ const trimmed = mediaUrl.trim();
27486
+ if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return false;
27487
+ return trimmed.startsWith("/") || trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("~") || trimmed.startsWith("file://") || path5.isAbsolute(trimmed);
27488
+ }
27489
+ function resolveLocalMediaPath(mediaUrl) {
27490
+ if (mediaUrl.startsWith("file://")) {
27491
+ const parsed = new URL(mediaUrl);
27492
+ return process.platform === "win32" ? parsed.pathname.replace(/^\/([A-Za-z]:)/, "$1") : parsed.pathname;
27493
+ }
27494
+ if (mediaUrl.startsWith("~")) {
27495
+ return path5.join(os4.homedir(), mediaUrl.slice(1));
27496
+ }
27497
+ return path5.resolve(mediaUrl);
27498
+ }
27499
+ function resolveMediaMsgType(filePath) {
27500
+ const ext = path5.extname(filePath).replace(/^\./, "").toLowerCase();
27501
+ return AUDIO_EXTENSIONS.has(ext) ? "audio" : "image";
27502
+ }
27503
+ function requireAgentId(account, agentId) {
27504
+ const resolved = agentId ?? account.agentId;
27505
+ if (!resolved) {
27506
+ throw new Error("workchat: agentId is required \u2014 complete the binding QR flow first");
27507
+ }
27508
+ return resolved;
27509
+ }
27510
+ async function sendMediaRequest(params) {
27511
+ const agentId = requireAgentId(params.account, params.agentId);
27512
+ assertSessionActive(params.account.accountId);
27513
+ const opts = buildApiOptions(params.account);
27514
+ const body = {
27515
+ agentId,
27516
+ receiveId: params.account.userId ?? params.to,
27517
+ msgType: params.msgType,
27518
+ content: params.content,
27519
+ ...params.conversationId ? { conversationId: params.conversationId } : {}
27520
+ };
27521
+ if (params.replyToMessageId) body.is_last = true;
27522
+ try {
27523
+ if (params.replyToMessageId) {
27524
+ const result2 = await sendWorkChatReplyMessage({ opts, messageId: params.replyToMessageId, body });
27525
+ logger.info(`reply media sent to=${params.to} messageId=${result2.msgId}`);
27526
+ return { messageId: result2.msgId };
27527
+ }
27528
+ const result = await sendWorkChatMessage({ opts, body });
27529
+ logger.info(`push media sent to=${params.to} messageId=${result.msgId}`);
27530
+ return { messageId: result.msgId };
27531
+ } catch (err) {
27532
+ pauseOnPermanentError(params.account.accountId, err instanceof Error ? err.message : String(err));
27533
+ throw err;
27534
+ }
27535
+ }
27536
+ async function sendWorkChatMediaFile(params) {
27537
+ const { account, to, filePath } = params;
27538
+ const opts = buildApiOptions(account);
27539
+ logger.info(`sendMedia: uploading filePath=${filePath} to=${to}`);
27540
+ const uploaded = await uploadWorkChatFile({ opts, filePath, userId: account.userId ?? to });
27541
+ logger.info(`sendMedia: upload done urlLen=${uploaded.url.length} suffix=${uploaded.suffix ?? "(none)"}`);
27542
+ const caption = params.text?.trim() ?? "";
27543
+ if (caption) {
27544
+ await pushWorkChatMessage({
27545
+ account,
27546
+ to,
27547
+ text: caption,
27548
+ conversationId: params.conversationId,
27549
+ agentId: params.agentId
27550
+ });
27551
+ }
27552
+ const msgType = resolveMediaMsgType(filePath) === "audio" ? WorkChatMsgType.Audio : WorkChatMsgType.Image;
27553
+ return sendMediaRequest({
27554
+ account,
27555
+ to,
27556
+ msgType,
27557
+ content: JSON.stringify({ url: uploaded.url }),
27558
+ replyToMessageId: params.replyToMessageId,
27559
+ conversationId: params.conversationId,
27560
+ agentId: params.agentId
27561
+ });
27562
+ }
27563
+ function tempMediaPath(ext) {
27564
+ return path5.join(REMOTE_MEDIA_DIR, `${generateId("remote").replace(/:/g, "-")}${ext}`);
27565
+ }
27566
+ function extensionFromUrlOrContentType(contentType, url2) {
27567
+ const fromUrl = path5.extname(new URL(url2).pathname).toLowerCase();
27568
+ if (fromUrl) return fromUrl;
27569
+ if (contentType?.startsWith("audio/mpeg")) return ".mp3";
27570
+ if (contentType?.startsWith("audio/wav")) return ".wav";
27571
+ if (contentType?.startsWith("audio/mp4")) return ".m4a";
27572
+ if (contentType?.startsWith("image/")) return ".png";
27573
+ return ".bin";
27574
+ }
27575
+ async function downloadRemoteMediaToTemp(url2) {
27576
+ const controller = new AbortController();
27577
+ const timeoutId = setTimeout(() => controller.abort(), REMOTE_MEDIA_DOWNLOAD_TIMEOUT_MS);
27578
+ let response;
27579
+ try {
27580
+ response = await globalThis.fetch(url2, { signal: controller.signal });
27581
+ } finally {
27582
+ clearTimeout(timeoutId);
27583
+ }
27584
+ if (!response.ok) {
27585
+ throw new Error(
27586
+ `remote media download failed: ${response.status} ${response.statusText} url=${url2.slice(0, 80)}`
27587
+ );
27588
+ }
27589
+ const buf = Buffer.from(await response.arrayBuffer());
27590
+ const ext = extensionFromUrlOrContentType(response.headers.get("content-type"), url2);
27591
+ await mkdir(REMOTE_MEDIA_DIR, { recursive: true });
27592
+ const filePath = tempMediaPath(ext);
27593
+ await writeFile(filePath, buf);
27594
+ logger.debug(`downloadRemoteMediaToTemp: saved ${buf.length} bytes to ${filePath}`);
27595
+ return filePath;
27596
+ }
27597
+ var AUDIO_EXTENSIONS, REMOTE_MEDIA_DIR, REMOTE_MEDIA_DOWNLOAD_TIMEOUT_MS;
27598
+ var init_send_media = __esm({
27599
+ "src/messaging/send-media.ts"() {
27600
+ "use strict";
27601
+ init_api();
27602
+ init_session_guard();
27603
+ init_types();
27604
+ init_logger();
27605
+ init_random();
27606
+ init_send();
27607
+ AUDIO_EXTENSIONS = /* @__PURE__ */ new Set(["wav", "m4a", "mp3"]);
27608
+ REMOTE_MEDIA_DIR = path5.join(os4.tmpdir(), "openclaw", "workchat", "media");
27609
+ REMOTE_MEDIA_DOWNLOAD_TIMEOUT_MS = 6e4;
27610
+ }
27611
+ });
27612
+
27613
+ // src/messaging/outbound-hooks.ts
27614
+ var outbound_hooks_exports = {};
27615
+ __export(outbound_hooks_exports, {
27616
+ applyWorkChatMessageSendingHook: () => applyWorkChatMessageSendingHook,
27617
+ emitWorkChatMessageSent: () => emitWorkChatMessageSent
27618
+ });
27619
+ import {
27620
+ fireAndForgetHook,
27621
+ buildCanonicalSentMessageHookContext,
27622
+ toPluginMessageContext,
27623
+ toPluginMessageSentEvent
27624
+ } from "openclaw/plugin-sdk/hook-runtime";
27625
+ import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
27626
+ async function applyWorkChatMessageSendingHook(params) {
27627
+ const hookRunner = getGlobalHookRunner();
27628
+ if (!hookRunner?.hasHooks("message_sending")) {
27629
+ return { cancelled: false, text: params.text };
27630
+ }
27631
+ try {
27632
+ const hookResult = await hookRunner.runMessageSending(
27633
+ {
27634
+ to: params.to,
27635
+ content: params.text,
27636
+ metadata: {
27637
+ channel: CHANNEL_ID,
27638
+ accountId: params.accountId,
27639
+ ...params.mediaUrl ? { mediaUrls: [params.mediaUrl] } : {}
27640
+ }
27641
+ },
27642
+ { channelId: CHANNEL_ID, accountId: params.accountId }
27643
+ );
27644
+ if (hookResult?.cancel) {
27645
+ return { cancelled: true, text: params.text };
27646
+ }
27647
+ return {
27648
+ cancelled: false,
27649
+ text: hookResult?.content ?? params.text
27650
+ };
27651
+ } catch (err) {
27652
+ logger.warn(`message_sending hook error, proceeding with send: ${String(err)}`);
27653
+ return { cancelled: false, text: params.text };
27654
+ }
27655
+ }
27656
+ function emitWorkChatMessageSent(params) {
27657
+ const hookRunner = getGlobalHookRunner();
27658
+ if (!hookRunner?.hasHooks("message_sent")) return;
27659
+ const canonical = buildCanonicalSentMessageHookContext({
27660
+ to: params.to,
27661
+ content: params.content,
27662
+ success: params.success,
27663
+ error: params.error,
27664
+ channelId: CHANNEL_ID,
27665
+ accountId: params.accountId,
27666
+ conversationId: params.to
27667
+ });
27668
+ fireAndForgetHook(
27669
+ Promise.resolve(
27670
+ hookRunner.runMessageSent(
27671
+ toPluginMessageSentEvent(canonical),
27672
+ toPluginMessageContext(canonical)
27673
+ )
27674
+ ),
27675
+ "workchat: message_sent plugin hook failed"
27676
+ );
27677
+ }
27678
+ var CHANNEL_ID;
27679
+ var init_outbound_hooks = __esm({
27680
+ "src/messaging/outbound-hooks.ts"() {
27681
+ "use strict";
27337
27682
  init_logger();
27683
+ CHANNEL_ID = "openclaw-workchat";
27338
27684
  }
27339
27685
  });
27340
27686
 
27341
27687
  // src/messaging/debug-mode.ts
27342
27688
  import fs5 from "fs";
27343
- import path5 from "path";
27689
+ import path6 from "path";
27344
27690
  function resolveDebugModePath() {
27345
- return path5.join(resolveStateDir(), "openclaw-workchat", "debug-mode.json");
27691
+ return path6.join(resolveStateDir(), "openclaw-workchat", "debug-mode.json");
27346
27692
  }
27347
27693
  function loadState() {
27348
27694
  try {
@@ -27355,7 +27701,7 @@ function loadState() {
27355
27701
  }
27356
27702
  function saveState(state) {
27357
27703
  const filePath = resolveDebugModePath();
27358
- fs5.mkdirSync(path5.dirname(filePath), { recursive: true });
27704
+ fs5.mkdirSync(path6.dirname(filePath), { recursive: true });
27359
27705
  fs5.writeFileSync(filePath, JSON.stringify(state, null, 2), "utf-8");
27360
27706
  }
27361
27707
  function toggleDebugMode(accountId) {
@@ -27465,77 +27811,73 @@ var init_slash_commands = __esm({
27465
27811
  }
27466
27812
  });
27467
27813
 
27468
- // src/messaging/outbound-hooks.ts
27469
- var outbound_hooks_exports = {};
27470
- __export(outbound_hooks_exports, {
27471
- applyWorkChatMessageSendingHook: () => applyWorkChatMessageSendingHook,
27472
- emitWorkChatMessageSent: () => emitWorkChatMessageSent
27814
+ // src/storage/seen-msgids.ts
27815
+ var seen_msgids_exports = {};
27816
+ __export(seen_msgids_exports, {
27817
+ _resetSeenMsgIdsForTest: () => _resetSeenMsgIdsForTest,
27818
+ isDuplicateMessage: () => isDuplicateMessage,
27819
+ recordMessageProcessed: () => recordMessageProcessed
27473
27820
  });
27474
- import {
27475
- fireAndForgetHook,
27476
- buildCanonicalSentMessageHookContext,
27477
- toPluginMessageContext,
27478
- toPluginMessageSentEvent
27479
- } from "openclaw/plugin-sdk/hook-runtime";
27480
- import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
27481
- async function applyWorkChatMessageSendingHook(params) {
27482
- const hookRunner = getGlobalHookRunner();
27483
- if (!hookRunner?.hasHooks("message_sending")) {
27484
- return { cancelled: false, text: params.text };
27485
- }
27821
+ import fs6 from "fs";
27822
+ import path7 from "path";
27823
+ function resolveSeenFilePath(accountId) {
27824
+ const safe = accountId.replace(/[^a-zA-Z0-9_-]/g, "_");
27825
+ return path7.join(resolveStateDir(), "openclaw-workchat", "accounts", `${safe}.seen-msgids.json`);
27826
+ }
27827
+ function loadSeen(accountId) {
27828
+ const cached2 = cache.get(accountId);
27829
+ if (cached2) return cached2;
27830
+ let ids = [];
27486
27831
  try {
27487
- const hookResult = await hookRunner.runMessageSending(
27488
- {
27489
- to: params.to,
27490
- content: params.text,
27491
- metadata: {
27492
- channel: CHANNEL_ID,
27493
- accountId: params.accountId,
27494
- ...params.mediaUrl ? { mediaUrls: [params.mediaUrl] } : {}
27495
- }
27496
- },
27497
- { channelId: CHANNEL_ID, accountId: params.accountId }
27498
- );
27499
- if (hookResult?.cancel) {
27500
- return { cancelled: true, text: params.text };
27832
+ const raw = fs6.readFileSync(resolveSeenFilePath(accountId), "utf-8");
27833
+ const parsed = JSON.parse(raw);
27834
+ if (Array.isArray(parsed.msgIds)) {
27835
+ ids = parsed.msgIds.filter((x) => typeof x === "string" && x !== "");
27501
27836
  }
27502
- return {
27503
- cancelled: false,
27504
- text: hookResult?.content ?? params.text
27505
- };
27837
+ } catch {
27838
+ }
27839
+ const entry = { ids, index: new Set(ids) };
27840
+ cache.set(accountId, entry);
27841
+ return entry;
27842
+ }
27843
+ function persistSeen(accountId, seen) {
27844
+ try {
27845
+ const filePath = resolveSeenFilePath(accountId);
27846
+ fs6.mkdirSync(path7.dirname(filePath), { recursive: true });
27847
+ fs6.writeFileSync(filePath, JSON.stringify({ msgIds: seen.ids }), "utf-8");
27506
27848
  } catch (err) {
27507
- logger.warn(`message_sending hook error, proceeding with send: ${String(err)}`);
27508
- return { cancelled: false, text: params.text };
27849
+ logger.warn(`seen-msgids persist failed accountId=${accountId}: ${String(err)}`);
27509
27850
  }
27510
27851
  }
27511
- function emitWorkChatMessageSent(params) {
27512
- const hookRunner = getGlobalHookRunner();
27513
- if (!hookRunner?.hasHooks("message_sent")) return;
27514
- const canonical = buildCanonicalSentMessageHookContext({
27515
- to: params.to,
27516
- content: params.content,
27517
- success: params.success,
27518
- error: params.error,
27519
- channelId: CHANNEL_ID,
27520
- accountId: params.accountId,
27521
- conversationId: params.to
27522
- });
27523
- fireAndForgetHook(
27524
- Promise.resolve(
27525
- hookRunner.runMessageSent(
27526
- toPluginMessageSentEvent(canonical),
27527
- toPluginMessageContext(canonical)
27528
- )
27529
- ),
27530
- "workchat: message_sent plugin hook failed"
27531
- );
27852
+ function isDuplicateMessage(accountId, msgId) {
27853
+ if (!msgId) return false;
27854
+ const seen = loadSeen(accountId);
27855
+ return seen.index.has(msgId);
27532
27856
  }
27533
- var CHANNEL_ID;
27534
- var init_outbound_hooks = __esm({
27535
- "src/messaging/outbound-hooks.ts"() {
27857
+ function recordMessageProcessed(accountId, msgId) {
27858
+ if (!msgId) return true;
27859
+ const seen = loadSeen(accountId);
27860
+ if (seen.index.has(msgId)) return false;
27861
+ seen.index.add(msgId);
27862
+ seen.ids.push(msgId);
27863
+ if (seen.ids.length > SEEN_LIMIT) {
27864
+ const drop = seen.ids.length - SEEN_LIMIT;
27865
+ for (const old of seen.ids.splice(0, drop)) seen.index.delete(old);
27866
+ }
27867
+ persistSeen(accountId, seen);
27868
+ return true;
27869
+ }
27870
+ function _resetSeenMsgIdsForTest() {
27871
+ cache.clear();
27872
+ }
27873
+ var SEEN_LIMIT, cache;
27874
+ var init_seen_msgids = __esm({
27875
+ "src/storage/seen-msgids.ts"() {
27536
27876
  "use strict";
27877
+ init_state_dir();
27537
27878
  init_logger();
27538
- CHANNEL_ID = "openclaw-workchat";
27879
+ SEEN_LIMIT = 500;
27880
+ cache = /* @__PURE__ */ new Map();
27539
27881
  }
27540
27882
  });
27541
27883
 
@@ -27873,6 +28215,28 @@ function loadWorkChatOutboundHooks() {
27873
28215
  workchatOutboundHooksPromise ??= Promise.resolve().then(() => (init_outbound_hooks(), outbound_hooks_exports));
27874
28216
  return workchatOutboundHooksPromise;
27875
28217
  }
28218
+ async function downloadInboundMedia(url2, deps) {
28219
+ try {
28220
+ if (deps.channelRuntime?.media?.fetchRemoteMedia) {
28221
+ const fetched = await deps.channelRuntime.media.fetchRemoteMedia({
28222
+ url: url2,
28223
+ maxBytes: WORKCHAT_MEDIA_MAX_BYTES
28224
+ });
28225
+ return { buffer: fetched.buffer, contentType: fetched.contentType };
28226
+ }
28227
+ } catch (err) {
28228
+ logger.warn(`fetchRemoteMedia failed, trying plain fetch: ${String(err)}`);
28229
+ }
28230
+ const res = await globalThis.fetch(url2);
28231
+ if (!res.ok) {
28232
+ throw new Error(`inbound media download failed: HTTP ${res.status}`);
28233
+ }
28234
+ const buffer = Buffer.from(await res.arrayBuffer());
28235
+ if (buffer.length > WORKCHAT_MEDIA_MAX_BYTES) {
28236
+ throw new Error(`inbound media exceeds limit (${buffer.length} bytes)`);
28237
+ }
28238
+ return { buffer, contentType: res.headers.get("content-type") ?? void 0 };
28239
+ }
27876
28240
  async function applyWorkChatMessageSendingHook2(params) {
27877
28241
  try {
27878
28242
  const hooks = await loadWorkChatOutboundHooks();
@@ -28016,13 +28380,21 @@ async function processOneFrame(result, rawFrame, deps) {
28016
28380
  logger.debug("inbound: empty body, ignoring");
28017
28381
  return;
28018
28382
  }
28383
+ if (message.messageId) {
28384
+ const { isDuplicateMessage: isDuplicateMessage2, recordMessageProcessed: recordMessageProcessed2 } = await Promise.resolve().then(() => (init_seen_msgids(), seen_msgids_exports));
28385
+ if (isDuplicateMessage2(deps.accountId, message.messageId)) {
28386
+ logger.info(`inbound: duplicate msgId=${message.messageId}, dropping`);
28387
+ return;
28388
+ }
28389
+ recordMessageProcessed2(deps.accountId, message.messageId);
28390
+ }
28019
28391
  const receivedAt = Date.now();
28020
28392
  const debug = isDebugMode(deps.accountId);
28021
28393
  const debugTrace = [];
28022
28394
  const debugTs = { received: receivedAt };
28023
28395
  const ctx = workChatMessageToMsgContext(message, deps.accountId);
28024
28396
  logger.info(
28025
- `inbound: from=${ctx.From} to=${ctx.To} bodyLen=${ctx.Body.length} conversationId=${ctx.ConversationId ?? "(none)"}`
28397
+ `inbound: from=${ctx.From} to=${ctx.To} bodyLen=${ctx.Body.length} conversationId=${ctx.ConversationId ?? "(none)"} hasMedia=${Boolean(ctx.MediaUrl)}`
28026
28398
  );
28027
28399
  logger.debug(`inbound raw frame: ${rawFrame.slice(0, 200)}`);
28028
28400
  if (ctx.Body.trim().startsWith("/")) {
@@ -28037,6 +28409,24 @@ async function processOneFrame(result, rawFrame, deps) {
28037
28409
  return;
28038
28410
  }
28039
28411
  }
28412
+ if (ctx.MediaUrl && deps.channelRuntime?.media?.saveMediaBuffer) {
28413
+ try {
28414
+ const media = await downloadInboundMedia(ctx.MediaUrl, deps);
28415
+ if (media) {
28416
+ const saved = await deps.channelRuntime.media.saveMediaBuffer(
28417
+ media.buffer,
28418
+ media.contentType,
28419
+ "inbound",
28420
+ WORKCHAT_MEDIA_MAX_BYTES
28421
+ );
28422
+ ctx.MediaPath = saved.path;
28423
+ ctx.MediaType = media.contentType ?? ctx.MediaType;
28424
+ logger.info(`inbound media saved: path=${saved.path} type=${media.contentType}`);
28425
+ }
28426
+ } catch (err) {
28427
+ deps.errLog(`inbound media download failed url=${redactBody(ctx.MediaUrl)}: ${String(err)}`);
28428
+ }
28429
+ }
28040
28430
  const rawBody = ctx.Body?.trim() ?? "";
28041
28431
  ctx.CommandBody = rawBody;
28042
28432
  const senderId = ctx.From;
@@ -28137,7 +28527,10 @@ async function processOneFrame(result, rawFrame, deps) {
28137
28527
  const rawText = String(payload.text ?? "");
28138
28528
  const f = new StreamingMarkdownFilter2();
28139
28529
  let text = f.feed(rawText) + f.flush();
28140
- logger.info(`outbound: to=${ctx.To} textLen=${text.length}`);
28530
+ const mediaUrl = payload.mediaUrl ?? payload.mediaUrls?.[0];
28531
+ logger.info(
28532
+ `outbound: to=${ctx.To} textLen=${text.length} mediaUrl=${mediaUrl ? "present" : "none"}`
28533
+ );
28141
28534
  if (debug) {
28142
28535
  debugDeliveries.push({
28143
28536
  textLen: text.length,
@@ -28148,7 +28541,8 @@ async function processOneFrame(result, rawFrame, deps) {
28148
28541
  const sendingResult = await applyWorkChatMessageSendingHook2({
28149
28542
  to: ctx.To,
28150
28543
  text,
28151
- accountId: deps.accountId
28544
+ accountId: deps.accountId,
28545
+ mediaUrl
28152
28546
  });
28153
28547
  if (sendingResult.cancelled) {
28154
28548
  logger.info(`outbound: cancelled by message_sending hook to=${ctx.To}`);
@@ -28156,6 +28550,40 @@ async function processOneFrame(result, rawFrame, deps) {
28156
28550
  }
28157
28551
  text = sendingResult.text;
28158
28552
  try {
28553
+ if (mediaUrl) {
28554
+ const { isLocalMediaSource: isLocalMediaSource2, resolveLocalMediaPath: resolveLocalMediaPath2, downloadRemoteMediaToTemp: downloadRemoteMediaToTemp2, sendWorkChatMediaFile: sendWorkChatMediaFile2 } = await Promise.resolve().then(() => (init_send_media(), send_media_exports));
28555
+ let filePath;
28556
+ if (isLocalMediaSource2(mediaUrl)) {
28557
+ filePath = resolveLocalMediaPath2(mediaUrl);
28558
+ logger.debug(`outbound: local media resolved filePath=${filePath}`);
28559
+ } else if (mediaUrl.startsWith("http://") || mediaUrl.startsWith("https://")) {
28560
+ logger.debug(`outbound: downloading remote mediaUrl=${mediaUrl.slice(0, 80)}...`);
28561
+ filePath = await downloadRemoteMediaToTemp2(mediaUrl);
28562
+ } else {
28563
+ logger.warn(
28564
+ `outbound: unrecognized mediaUrl scheme, sending text only mediaUrl=${mediaUrl.slice(0, 80)}`
28565
+ );
28566
+ filePath = "";
28567
+ }
28568
+ if (filePath) {
28569
+ await sendWorkChatMediaFile2({
28570
+ account: {
28571
+ ...deps.account,
28572
+ agentId: ctx.AgentId ?? deps.account.agentId,
28573
+ userId: ctx.From || deps.account.userId
28574
+ },
28575
+ to: ctx.To,
28576
+ filePath,
28577
+ text,
28578
+ replyToMessageId: ctx.MessageSid,
28579
+ conversationId: ctx.ConversationId,
28580
+ agentId: ctx.AgentId
28581
+ });
28582
+ emitWorkChatMessageSent2({ to: ctx.To, content: text, success: true, accountId: deps.accountId });
28583
+ logger.info(`outbound: media reply sent OK to=${ctx.To}`);
28584
+ return;
28585
+ }
28586
+ }
28159
28587
  const result2 = await replyWorkChatMessage2({
28160
28588
  account: {
28161
28589
  ...deps.account,
@@ -28180,7 +28608,14 @@ async function processOneFrame(result, rawFrame, deps) {
28180
28608
  onError: (err, info) => {
28181
28609
  deps.errLog(`workchat reply ${info.kind}: ${String(err)}`);
28182
28610
  const errMsg = err instanceof Error ? err.message : String(err);
28183
- const notice = `\u26A0\uFE0F \u6D88\u606F\u53D1\u9001\u5931\u8D25\uFF1A${errMsg}`;
28611
+ let notice;
28612
+ if (errMsg.includes("remote media download failed")) {
28613
+ notice = `\u26A0\uFE0F \u5A92\u4F53\u6587\u4EF6\u4E0B\u8F7D\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u94FE\u63A5\u662F\u5426\u53EF\u8BBF\u95EE\u3002`;
28614
+ } else if (errMsg.includes("file upload response") || errMsg.includes("upload failed")) {
28615
+ notice = `\u26A0\uFE0F \u5A92\u4F53\u6587\u4EF6\u4E0A\u4F20\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002`;
28616
+ } else {
28617
+ notice = `\u26A0\uFE0F \u6D88\u606F\u53D1\u9001\u5931\u8D25\uFF1A${errMsg}`;
28618
+ }
28184
28619
  void sendWorkChatErrorNotice({
28185
28620
  account: deps.account,
28186
28621
  to: ctx.To,
@@ -28250,7 +28685,7 @@ ${debugTrace.join("\n")}`;
28250
28685
  }
28251
28686
  }
28252
28687
  }
28253
- var workchatOutboundHooksPromise, createTypingCallbacksPromise;
28688
+ var workchatOutboundHooksPromise, WORKCHAT_MEDIA_MAX_BYTES, createTypingCallbacksPromise;
28254
28689
  var init_process_message = __esm({
28255
28690
  "src/messaging/process-message.ts"() {
28256
28691
  "use strict";
@@ -28264,6 +28699,7 @@ var init_process_message = __esm({
28264
28699
  init_inbound();
28265
28700
  init_slash_commands();
28266
28701
  workchatOutboundHooksPromise = null;
28702
+ WORKCHAT_MEDIA_MAX_BYTES = 100 * 1024 * 1024;
28267
28703
  }
28268
28704
  });
28269
28705
 
@@ -28318,8 +28754,8 @@ async function monitorWorkChatProvider(opts) {
28318
28754
  localIp: opts.localIp
28319
28755
  });
28320
28756
  const url2 = `${endpoint}?ticket=${encodeURIComponent(ticket)}`;
28321
- const dispatcher = apiOpts.allowInsecureTls ? new import_undici3.Agent({ connect: { rejectUnauthorized: false } }) : void 0;
28322
- ws = new import_undici3.WebSocket(url2, dispatcher ? { dispatcher } : void 0);
28757
+ const dispatcher = apiOpts.allowInsecureTls ? new import_undici2.Agent({ connect: { rejectUnauthorized: false } }) : void 0;
28758
+ ws = new import_undici2.WebSocket(url2, dispatcher ? { dispatcher } : void 0);
28323
28759
  await new Promise((resolve, reject) => {
28324
28760
  const timer = setTimeout(
28325
28761
  () => reject(new Error("workchat websocket connection timeout")),
@@ -28452,11 +28888,11 @@ async function monitorWorkChatProvider(opts) {
28452
28888
  }
28453
28889
  }
28454
28890
  }
28455
- var import_undici3, WS_CONNECT_TIMEOUT_MS, RECONNECT_BACKOFF_BASE_MS, RECONNECT_BACKOFF_MAX_MS, RATE_LIMIT_BACKOFF_MS;
28891
+ var import_undici2, WS_CONNECT_TIMEOUT_MS, RECONNECT_BACKOFF_BASE_MS, RECONNECT_BACKOFF_MAX_MS, RATE_LIMIT_BACKOFF_MS;
28456
28892
  var init_monitor = __esm({
28457
28893
  "src/monitor/monitor.ts"() {
28458
28894
  "use strict";
28459
- import_undici3 = __toESM(require_undici(), 1);
28895
+ import_undici2 = __toESM(require_undici(), 1);
28460
28896
  init_api();
28461
28897
  init_api();
28462
28898
  init_inbound();
@@ -29573,6 +30009,7 @@ init_accounts();
29573
30009
  init_types();
29574
30010
  init_login_qr();
29575
30011
  init_send();
30012
+ init_send_media();
29576
30013
  init_logger();
29577
30014
  function resolveOutboundAccount(cfg, accountId) {
29578
30015
  const allIds = listWorkChatAccountIds(cfg);
@@ -29625,7 +30062,8 @@ var workchatPlugin = {
29625
30062
  messageToolHints: () => [
29626
30063
  "To send a message to the current WorkChat user, use the message tool with action='send'. You do not need to specify 'to' \u2014 the current conversation recipient is used automatically.",
29627
30064
  "IMPORTANT: When creating a cron job (scheduled task) for the current WorkChat user, you MUST set delivery.to to the user's WorkChat ID (the numeric ID from the current conversation) AND set delivery.accountId to the current AccountId. Without an explicit 'to', the cron delivery will fail with 'requires target'. Without an explicit 'accountId', the message may be sent from the wrong bot account. Example: delivery: { mode: 'announce', channel: 'openclaw-workchat', to: '<current_user_id>', accountId: '<current_AccountId>' }.",
29628
- "IMPORTANT: When sending media, set 'media' to a URL the WorkChat platform can fetch (an HTTPS URL). Local file paths are not supported by the WorkChat send API yet \u2014 include the link in the message text instead."
30065
+ "To send an image or audio file to the current user, use the message tool with action='send' and set 'media' to a local file path or a remote HTTPS URL. Supported audio formats: wav, m4a, mp3; supported image formats: bmp, gif, jpg, jpeg, png, webp. Remote URLs are downloaded and re-uploaded to WorkChat storage automatically \u2014 the client renders image/audio cards, never bare links.",
30066
+ "IMPORTANT: When generating or saving a file to send, always use an absolute path (e.g. /tmp/photo.png or C:\\tmp\\photo.png on Windows), never a relative path like ./photo.png. Relative paths cannot be resolved and the file will not be delivered."
29629
30067
  ]
29630
30068
  },
29631
30069
  reload: { configPrefixes: ["channels.openclaw-workchat"] },
@@ -29660,13 +30098,62 @@ var workchatPlugin = {
29660
30098
  },
29661
30099
  sendMedia: async (ctx) => {
29662
30100
  const account = resolveOutboundAccount(ctx.cfg, ctx.accountId);
29663
- const text = ctx.text?.trim() ? ctx.text : ctx.mediaUrl ?? "";
29664
- const result = await pushWorkChatMessage({
29665
- account,
30101
+ const aLog = logger.withAccount(account.accountId);
30102
+ const mediaUrl = ctx.mediaUrl?.trim();
30103
+ let text = ctx.text ?? "";
30104
+ const { applyWorkChatMessageSendingHook: applyWorkChatMessageSendingHook3, emitWorkChatMessageSent: emitWorkChatMessageSent3 } = await Promise.resolve().then(() => (init_outbound_hooks(), outbound_hooks_exports));
30105
+ const sendingResult = await applyWorkChatMessageSendingHook3({
29666
30106
  to: ctx.to,
29667
- text
30107
+ text,
30108
+ accountId: account.accountId,
30109
+ mediaUrl
30110
+ }).catch((err) => {
30111
+ aLog.warn(`message_sending hook error, proceeding: ${String(err)}`);
30112
+ return { cancelled: false, text };
29668
30113
  });
29669
- return { channel: "openclaw-workchat", messageId: result.messageId };
30114
+ if (sendingResult.cancelled) {
30115
+ aLog.info(`sendMedia: cancelled by message_sending hook to=${ctx.to}`);
30116
+ return { channel: "openclaw-workchat", messageId: "" };
30117
+ }
30118
+ text = sendingResult.text;
30119
+ if (!mediaUrl) {
30120
+ try {
30121
+ const result = await pushWorkChatMessage({ account, to: ctx.to, text });
30122
+ emitWorkChatMessageSent3({ to: ctx.to, content: text, success: true, accountId: account.accountId });
30123
+ return { channel: "openclaw-workchat", messageId: result.messageId };
30124
+ } catch (err) {
30125
+ emitWorkChatMessageSent3({ to: ctx.to, content: text, success: false, error: String(err), accountId: account.accountId });
30126
+ throw err;
30127
+ }
30128
+ }
30129
+ let filePath;
30130
+ try {
30131
+ if (isLocalMediaSource(mediaUrl)) {
30132
+ filePath = resolveLocalMediaPath(mediaUrl);
30133
+ aLog.debug(`sendMedia: uploading local file ${filePath}`);
30134
+ } else {
30135
+ aLog.debug(`sendMedia: downloading remote mediaUrl=${mediaUrl.slice(0, 80)}...`);
30136
+ filePath = await downloadRemoteMediaToTemp(mediaUrl);
30137
+ aLog.debug(`sendMedia: remote media downloaded to ${filePath}`);
30138
+ }
30139
+ } catch (err) {
30140
+ emitWorkChatMessageSent3({ to: ctx.to, content: text, success: false, error: String(err), accountId: account.accountId });
30141
+ throw err;
30142
+ }
30143
+ try {
30144
+ const result = await sendWorkChatMediaFile({
30145
+ account,
30146
+ to: ctx.to,
30147
+ filePath,
30148
+ text,
30149
+ replyToMessageId: ctx.replyToId?.trim() || void 0
30150
+ });
30151
+ emitWorkChatMessageSent3({ to: ctx.to, content: text, success: true, accountId: account.accountId });
30152
+ return { channel: "openclaw-workchat", messageId: result.messageId };
30153
+ } catch (err) {
30154
+ emitWorkChatMessageSent3({ to: ctx.to, content: text, success: false, error: String(err), accountId: account.accountId });
30155
+ throw err;
30156
+ }
29670
30157
  }
29671
30158
  },
29672
30159
  status: {
@@ -30680,10 +31167,10 @@ function mergeDefs(...defs) {
30680
31167
  function cloneDef(schema) {
30681
31168
  return mergeDefs(schema._zod.def);
30682
31169
  }
30683
- function getElementAtPath(obj, path6) {
30684
- if (!path6)
31170
+ function getElementAtPath(obj, path8) {
31171
+ if (!path8)
30685
31172
  return obj;
30686
- return path6.reduce((acc, key) => acc?.[key], obj);
31173
+ return path8.reduce((acc, key) => acc?.[key], obj);
30687
31174
  }
30688
31175
  function promiseAllObject(promisesObj) {
30689
31176
  const keys = Object.keys(promisesObj);
@@ -31023,11 +31510,11 @@ function explicitlyAborted(x, startIndex = 0) {
31023
31510
  }
31024
31511
  return false;
31025
31512
  }
31026
- function prefixIssues(path6, issues) {
31513
+ function prefixIssues(path8, issues) {
31027
31514
  return issues.map((iss) => {
31028
31515
  var _a3;
31029
31516
  (_a3 = iss).path ?? (_a3.path = []);
31030
- iss.path.unshift(path6);
31517
+ iss.path.unshift(path8);
31031
31518
  return iss;
31032
31519
  });
31033
31520
  }
@@ -31489,16 +31976,16 @@ function flattenError(error62, mapper = (issue2) => issue2.message) {
31489
31976
  }
31490
31977
  function formatError(error62, mapper = (issue2) => issue2.message) {
31491
31978
  const fieldErrors = { _errors: [] };
31492
- const processError = (error63, path6 = []) => {
31979
+ const processError = (error63, path8 = []) => {
31493
31980
  for (const issue2 of error63.issues) {
31494
31981
  if (issue2.code === "invalid_union" && issue2.errors.length) {
31495
- issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
31982
+ issue2.errors.map((issues) => processError({ issues }, [...path8, ...issue2.path]));
31496
31983
  } else if (issue2.code === "invalid_key") {
31497
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
31984
+ processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
31498
31985
  } else if (issue2.code === "invalid_element") {
31499
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
31986
+ processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
31500
31987
  } else {
31501
- const fullpath = [...path6, ...issue2.path];
31988
+ const fullpath = [...path8, ...issue2.path];
31502
31989
  if (fullpath.length === 0) {
31503
31990
  fieldErrors._errors.push(mapper(issue2));
31504
31991
  } else {
@@ -31537,17 +32024,17 @@ function formatError(error62, mapper = (issue2) => issue2.message) {
31537
32024
  }
31538
32025
  function treeifyError(error62, mapper = (issue2) => issue2.message) {
31539
32026
  const result = { errors: [] };
31540
- const processError = (error63, path6 = []) => {
32027
+ const processError = (error63, path8 = []) => {
31541
32028
  var _a3;
31542
32029
  for (const issue2 of error63.issues) {
31543
32030
  if (issue2.code === "invalid_union" && issue2.errors.length) {
31544
- issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
32031
+ issue2.errors.map((issues) => processError({ issues }, [...path8, ...issue2.path]));
31545
32032
  } else if (issue2.code === "invalid_key") {
31546
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
32033
+ processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
31547
32034
  } else if (issue2.code === "invalid_element") {
31548
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
32035
+ processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
31549
32036
  } else {
31550
- const fullpath = [...path6, ...issue2.path];
32037
+ const fullpath = [...path8, ...issue2.path];
31551
32038
  if (fullpath.length === 0) {
31552
32039
  result.errors.push(mapper(issue2));
31553
32040
  continue;
@@ -31586,8 +32073,8 @@ function treeifyError(error62, mapper = (issue2) => issue2.message) {
31586
32073
  }
31587
32074
  function toDotPath(_path) {
31588
32075
  const segs = [];
31589
- const path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
31590
- for (const seg of path6) {
32076
+ const path8 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
32077
+ for (const seg of path8) {
31591
32078
  if (typeof seg === "number")
31592
32079
  segs.push(`[${seg}]`);
31593
32080
  else if (typeof seg === "symbol")
@@ -48716,13 +49203,13 @@ function resolveRef(ref, ctx) {
48716
49203
  if (!ref.startsWith("#")) {
48717
49204
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
48718
49205
  }
48719
- const path6 = ref.slice(1).split("/").filter(Boolean);
48720
- if (path6.length === 0) {
49206
+ const path8 = ref.slice(1).split("/").filter(Boolean);
49207
+ if (path8.length === 0) {
48721
49208
  return ctx.rootSchema;
48722
49209
  }
48723
49210
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
48724
- if (path6[0] === defsKey) {
48725
- const key = path6[1] === void 0 ? void 0 : decodeJSONPointerSegment(path6[1]);
49211
+ if (path8[0] === defsKey) {
49212
+ const key = path8[1] === void 0 ? void 0 : decodeJSONPointerSegment(path8[1]);
48726
49213
  if (!key || !ctx.defs[key]) {
48727
49214
  throw new Error(`Reference not found: ${ref}`);
48728
49215
  }
@@ -49356,21 +49843,21 @@ function visit(schema, fnOrHandlers) {
49356
49843
  const h = fnOrHandlers[node2._zod.def.type];
49357
49844
  return h ? h(node2, rewritten) : node2;
49358
49845
  };
49359
- const cache = /* @__PURE__ */ new Map();
49846
+ const cache2 = /* @__PURE__ */ new Map();
49360
49847
  function run(s) {
49361
- const cached2 = cache.get(s);
49848
+ const cached2 = cache2.get(s);
49362
49849
  if (cached2 === RESOLVING) {
49363
49850
  return new $ZodLazy({
49364
49851
  type: "lazy",
49365
- getter: () => cache.get(s)
49852
+ getter: () => cache2.get(s)
49366
49853
  });
49367
49854
  }
49368
49855
  if (cached2 !== void 0)
49369
49856
  return cached2;
49370
- cache.set(s, RESOLVING);
49857
+ cache2.set(s, RESOLVING);
49371
49858
  const inner = mapInner(s);
49372
49859
  const mapped = fn(inner, inner !== s);
49373
- cache.set(s, mapped);
49860
+ cache2.set(s, mapped);
49374
49861
  return mapped;
49375
49862
  }
49376
49863
  function mapInner(s) {