@workclaw/openclaw-workchat 1.0.4 → 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
@@ -279,7 +280,8 @@ __export(accounts_exports, {
279
280
  listIndexedWorkChatAccountIds: () => listIndexedWorkChatAccountIds,
280
281
  listWorkChatAccountIds: () => listWorkChatAccountIds,
281
282
  loadWorkChatAccount: () => loadWorkChatAccount,
282
- pruneStaleWorkChatAccounts: () => pruneStaleWorkChatAccounts,
283
+ migrateLegacyDefaultWorkChatAccount: () => migrateLegacyDefaultWorkChatAccount,
284
+ pruneOtherWorkChatAccounts: () => pruneOtherWorkChatAccounts,
283
285
  registerWorkChatAccountId: () => registerWorkChatAccountId,
284
286
  removeWorkChatAccountBinding: () => removeWorkChatAccountBinding,
285
287
  resolveWorkChatAccount: () => resolveWorkChatAccount,
@@ -296,6 +298,9 @@ function resolveAccountIndexPath() {
296
298
  return path4.join(resolveWorkChatStateDir(), "accounts.json");
297
299
  }
298
300
  function listIndexedWorkChatAccountIds() {
301
+ return readAccountIndex();
302
+ }
303
+ function readAccountIndex() {
299
304
  const filePath = resolveAccountIndexPath();
300
305
  try {
301
306
  if (!fs3.existsSync(filePath)) return [];
@@ -306,19 +311,23 @@ function listIndexedWorkChatAccountIds() {
306
311
  return [];
307
312
  }
308
313
  }
309
- function registerWorkChatAccountId(accountId) {
310
- const dir = resolveWorkChatStateDir();
311
- fs3.mkdirSync(dir, { recursive: true });
312
- const existing = listIndexedWorkChatAccountIds();
313
- if (existing.includes(accountId)) return;
314
- fs3.writeFileSync(resolveAccountIndexPath(), JSON.stringify([...existing, accountId], null, 2), "utf-8");
314
+ async function updateAccountIndex(mutator) {
315
+ const run = indexUpdateChain.then(() => {
316
+ const filePath = resolveAccountIndexPath();
317
+ fs3.mkdirSync(path4.dirname(filePath), { recursive: true });
318
+ const updated = mutator(readAccountIndex());
319
+ fs3.writeFileSync(filePath, JSON.stringify(updated, null, 2), "utf-8");
320
+ });
321
+ indexUpdateChain = run.then(() => void 0, () => void 0);
322
+ await run;
315
323
  }
316
- function unregisterWorkChatAccountId(accountId) {
317
- const existing = listIndexedWorkChatAccountIds();
318
- const updated = existing.filter((id) => id !== accountId);
319
- if (updated.length !== existing.length) {
320
- fs3.writeFileSync(resolveAccountIndexPath(), JSON.stringify(updated, null, 2), "utf-8");
321
- }
324
+ async function registerWorkChatAccountId(accountId) {
325
+ await updateAccountIndex(
326
+ (ids) => ids.includes(accountId) ? ids : [...ids, accountId]
327
+ );
328
+ }
329
+ async function unregisterWorkChatAccountId(accountId) {
330
+ await updateAccountIndex((ids) => ids.filter((id) => id !== accountId));
322
331
  }
323
332
  function resolveAccountsDir() {
324
333
  return path4.join(resolveWorkChatStateDir(), "accounts");
@@ -355,14 +364,14 @@ function saveWorkChatAccount(accountId, update) {
355
364
  } catch {
356
365
  }
357
366
  }
358
- function clearWorkChatAccount(accountId) {
367
+ async function clearWorkChatAccount(accountId) {
359
368
  const filePath = resolveAccountPath(accountId);
360
369
  try {
361
370
  if (fs3.existsSync(filePath)) fs3.unlinkSync(filePath);
362
371
  } catch (err) {
363
372
  logger.warn(`clearWorkChatAccount: failed to remove ${filePath}: ${String(err)}`);
364
373
  }
365
- unregisterWorkChatAccountId(accountId);
374
+ await unregisterWorkChatAccountId(accountId);
366
375
  }
367
376
  async function removeWorkChatAccountBinding(accountId) {
368
377
  clearWorkChatAccount(accountId);
@@ -378,21 +387,57 @@ async function removeWorkChatAccountBinding(accountId) {
378
387
  logger.warn(`removeWorkChatAccountBinding: channel reload trigger failed: ${String(err)}`);
379
388
  }
380
389
  }
381
- async function pruneStaleWorkChatAccounts(keepAgentId) {
390
+ async function pruneOtherWorkChatAccounts(keepAccountId) {
391
+ const keep = keepAccountId.trim();
382
392
  const removed = [];
383
393
  for (const accountId of listIndexedWorkChatAccountIds()) {
384
- const data = loadWorkChatAccount(accountId);
385
- const storedAgentId = data?.agentId?.trim();
386
- if (storedAgentId && storedAgentId !== keepAgentId) {
387
- await removeWorkChatAccountBinding(accountId);
388
- removed.push(accountId);
389
- logger.info(
390
- `pruneStaleWorkChatAccounts: removed account=${accountId} agent=${storedAgentId} (superseded by agent=${keepAgentId})`
391
- );
392
- }
394
+ if (accountId === keep) continue;
395
+ await removeWorkChatAccountBinding(accountId);
396
+ removed.push(accountId);
397
+ logger.info(
398
+ `pruneOtherWorkChatAccounts: removed account=${accountId} (superseded by ${keep})`
399
+ );
393
400
  }
394
401
  return removed;
395
402
  }
403
+ async function migrateLegacyDefaultWorkChatAccount() {
404
+ const ids = listIndexedWorkChatAccountIds();
405
+ if (!ids.includes("default")) return false;
406
+ const data = loadWorkChatAccount("default");
407
+ const agentId = data?.agentId?.trim();
408
+ if (!agentId) return false;
409
+ const targetId = `agent-${agentId}`;
410
+ if (!ids.includes(targetId)) {
411
+ saveWorkChatAccount(targetId, { ...data });
412
+ }
413
+ try {
414
+ const { readFrameworkAllowFromList: readFrameworkAllowFromList2, registerUserInFrameworkStore: registerUserInFrameworkStore2 } = await Promise.resolve().then(() => (init_pairing(), pairing_exports));
415
+ for (const userId of readFrameworkAllowFromList2("default")) {
416
+ await registerUserInFrameworkStore2({ accountId: targetId, userId });
417
+ }
418
+ } catch (err) {
419
+ logger.warn(`migrateLegacyDefaultWorkChatAccount: allowFrom move failed: ${String(err)}`);
420
+ }
421
+ await updateAccountIndex((existing) => {
422
+ const next = existing.filter((id) => id !== "default" && id !== targetId);
423
+ next.push(targetId);
424
+ return next;
425
+ });
426
+ try {
427
+ fs3.rmSync(path4.join(resolveWorkChatStateDir(), "accounts", "default.json"), { force: true });
428
+ } catch {
429
+ }
430
+ try {
431
+ const { clearFrameworkAllowFromList: clearFrameworkAllowFromList2 } = await Promise.resolve().then(() => (init_pairing(), pairing_exports));
432
+ clearFrameworkAllowFromList2("default");
433
+ } catch (err) {
434
+ logger.warn(`migrateLegacyDefaultWorkChatAccount: allowFrom cleanup failed: ${String(err)}`);
435
+ }
436
+ logger.info(
437
+ `migrateLegacyDefaultWorkChatAccount: renamed default -> ${targetId} (agent=${agentId})`
438
+ );
439
+ return true;
440
+ }
396
441
  function listWorkChatAccountIds(_cfg) {
397
442
  return listIndexedWorkChatAccountIds();
398
443
  }
@@ -442,12 +487,14 @@ async function triggerWorkChatChannelReload() {
442
487
  logger.warn(`triggerWorkChatChannelReload: failed to update config: ${String(err)}`);
443
488
  }
444
489
  }
490
+ var indexUpdateChain;
445
491
  var init_accounts = __esm({
446
492
  "src/auth/accounts.ts"() {
447
493
  "use strict";
448
494
  init_types();
449
495
  init_state_dir();
450
496
  init_logger();
497
+ indexUpdateChain = Promise.resolve();
451
498
  }
452
499
  });
453
500
 
@@ -1559,14 +1606,14 @@ var require_util = __commonJS({
1559
1606
  }
1560
1607
  const port = url2.port != null ? url2.port : url2.protocol === "https:" ? 443 : 80;
1561
1608
  let origin = url2.origin != null ? url2.origin : `${url2.protocol || ""}//${url2.hostname || ""}:${port}`;
1562
- let path6 = url2.path != null ? url2.path : `${url2.pathname || ""}${url2.search || ""}`;
1609
+ let path8 = url2.path != null ? url2.path : `${url2.pathname || ""}${url2.search || ""}`;
1563
1610
  if (origin[origin.length - 1] === "/") {
1564
1611
  origin = origin.slice(0, origin.length - 1);
1565
1612
  }
1566
- if (path6 && path6[0] !== "/") {
1567
- path6 = `/${path6}`;
1613
+ if (path8 && path8[0] !== "/") {
1614
+ path8 = `/${path8}`;
1568
1615
  }
1569
- return new URL(`${origin}${path6}`);
1616
+ return new URL(`${origin}${path8}`);
1570
1617
  }
1571
1618
  if (!isHttpOrHttpsPrefixed(url2.origin || url2.protocol)) {
1572
1619
  throw new InvalidArgumentError("Invalid URL protocol: the URL must start with `http:` or `https:`.");
@@ -2387,9 +2434,9 @@ var require_diagnostics = __commonJS({
2387
2434
  "undici:client:sendHeaders",
2388
2435
  (evt) => {
2389
2436
  const {
2390
- request: { method, path: path6, origin }
2437
+ request: { method, path: path8, origin }
2391
2438
  } = evt;
2392
- debugLog("sending request to %s %s%s", method, origin, path6);
2439
+ debugLog("sending request to %s %s%s", method, origin, path8);
2393
2440
  }
2394
2441
  );
2395
2442
  }
@@ -2407,14 +2454,14 @@ var require_diagnostics = __commonJS({
2407
2454
  "undici:request:headers",
2408
2455
  (evt) => {
2409
2456
  const {
2410
- request: { method, path: path6, origin },
2457
+ request: { method, path: path8, origin },
2411
2458
  response: { statusCode }
2412
2459
  } = evt;
2413
2460
  debugLog(
2414
2461
  "received response to %s %s%s - HTTP %d",
2415
2462
  method,
2416
2463
  origin,
2417
- path6,
2464
+ path8,
2418
2465
  statusCode
2419
2466
  );
2420
2467
  }
@@ -2423,23 +2470,23 @@ var require_diagnostics = __commonJS({
2423
2470
  "undici:request:trailers",
2424
2471
  (evt) => {
2425
2472
  const {
2426
- request: { method, path: path6, origin }
2473
+ request: { method, path: path8, origin }
2427
2474
  } = evt;
2428
- debugLog("trailers received from %s %s%s", method, origin, path6);
2475
+ debugLog("trailers received from %s %s%s", method, origin, path8);
2429
2476
  }
2430
2477
  );
2431
2478
  diagnosticsChannel.subscribe(
2432
2479
  "undici:request:error",
2433
2480
  (evt) => {
2434
2481
  const {
2435
- request: { method, path: path6, origin },
2482
+ request: { method, path: path8, origin },
2436
2483
  error: error62
2437
2484
  } = evt;
2438
2485
  debugLog(
2439
2486
  "request to %s %s%s errored - %s",
2440
2487
  method,
2441
2488
  origin,
2442
- path6,
2489
+ path8,
2443
2490
  error62.message
2444
2491
  );
2445
2492
  }
@@ -2554,7 +2601,7 @@ var require_request = __commonJS({
2554
2601
  var kHandler = Symbol("handler");
2555
2602
  var Request = class {
2556
2603
  constructor(origin, {
2557
- path: path6,
2604
+ path: path8,
2558
2605
  method,
2559
2606
  body,
2560
2607
  headers,
@@ -2571,11 +2618,11 @@ var require_request = __commonJS({
2571
2618
  maxRedirections,
2572
2619
  typeOfService
2573
2620
  }, handler) {
2574
- if (typeof path6 !== "string") {
2621
+ if (typeof path8 !== "string") {
2575
2622
  throw new InvalidArgumentError("path must be a string");
2576
- } 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") {
2577
2624
  throw new InvalidArgumentError("path must be an absolute URL or start with a slash");
2578
- } else if (invalidPathRegex.test(path6)) {
2625
+ } else if (invalidPathRegex.test(path8)) {
2579
2626
  throw new InvalidArgumentError("invalid request path");
2580
2627
  }
2581
2628
  if (typeof method !== "string") {
@@ -2650,7 +2697,7 @@ var require_request = __commonJS({
2650
2697
  this.completed = false;
2651
2698
  this.aborted = false;
2652
2699
  this.upgrade = upgrade || null;
2653
- this.path = query ? serializePathWithQuery(path6, query) : path6;
2700
+ this.path = query ? serializePathWithQuery(path8, query) : path8;
2654
2701
  this.origin = origin;
2655
2702
  this.protocol = getProtocolFromUrlString(origin);
2656
2703
  this.idempotent = idempotent == null ? method === "HEAD" || method === "GET" : idempotent;
@@ -6219,7 +6266,7 @@ var require_formdata = __commonJS({
6219
6266
  var { kEnumerableProperty } = require_util();
6220
6267
  var { webidl } = require_webidl();
6221
6268
  var nodeUtil = __require("util");
6222
- var FormData = class _FormData {
6269
+ var FormData2 = class _FormData {
6223
6270
  #state = [];
6224
6271
  constructor(form = void 0) {
6225
6272
  webidl.util.markAsUncloneable(this);
@@ -6336,11 +6383,11 @@ var require_formdata = __commonJS({
6336
6383
  formData.#state = newState;
6337
6384
  }
6338
6385
  };
6339
- var { getFormDataState, setFormDataState } = FormData;
6340
- Reflect.deleteProperty(FormData, "getFormDataState");
6341
- Reflect.deleteProperty(FormData, "setFormDataState");
6342
- iteratorMixin("FormData", FormData, getFormDataState, "name", "value");
6343
- 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, {
6344
6391
  append: kEnumerableProperty,
6345
6392
  delete: kEnumerableProperty,
6346
6393
  get: kEnumerableProperty,
@@ -6368,8 +6415,8 @@ var require_formdata = __commonJS({
6368
6415
  }
6369
6416
  return { name, value };
6370
6417
  }
6371
- webidl.is.FormData = webidl.util.MakeTypeAssertion(FormData);
6372
- module.exports = { FormData, makeEntry, setFormDataState };
6418
+ webidl.is.FormData = webidl.util.MakeTypeAssertion(FormData2);
6419
+ module.exports = { FormData: FormData2, makeEntry, setFormDataState };
6373
6420
  }
6374
6421
  });
6375
6422
 
@@ -6733,7 +6780,7 @@ var require_body = __commonJS({
6733
6780
  fullyReadBody,
6734
6781
  extractMimeType
6735
6782
  } = require_util2();
6736
- var { FormData, setFormDataState } = require_formdata();
6783
+ var { FormData: FormData2, setFormDataState } = require_formdata();
6737
6784
  var { webidl } = require_webidl();
6738
6785
  var assert2 = __require("assert");
6739
6786
  var { isErrored, isDisturbed } = __require("stream");
@@ -6926,13 +6973,13 @@ Content-Type: ${value.type || "application/octet-stream"}\r
6926
6973
  switch (mimeType.essence) {
6927
6974
  case "multipart/form-data": {
6928
6975
  const parsed = multipartFormDataParser(value, mimeType);
6929
- const fd = new FormData();
6976
+ const fd = new FormData2();
6930
6977
  setFormDataState(fd, parsed);
6931
6978
  return fd;
6932
6979
  }
6933
6980
  case "application/x-www-form-urlencoded": {
6934
6981
  const entries = new URLSearchParams(value.toString());
6935
- const fd = new FormData();
6982
+ const fd = new FormData2();
6936
6983
  for (const [name, value2] of entries) {
6937
6984
  fd.append(name, value2);
6938
6985
  }
@@ -7856,7 +7903,7 @@ var require_client_h1 = __commonJS({
7856
7903
  return method !== "GET" && method !== "HEAD" && method !== "OPTIONS" && method !== "TRACE" && method !== "CONNECT";
7857
7904
  }
7858
7905
  function writeH1(client, request) {
7859
- const { method, path: path6, host, upgrade, blocking, reset } = request;
7906
+ const { method, path: path8, host, upgrade, blocking, reset } = request;
7860
7907
  let { body, headers, contentLength } = request;
7861
7908
  const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH" || method === "QUERY" || method === "PROPFIND" || method === "PROPPATCH";
7862
7909
  if (util.isFormDataLike(body)) {
@@ -7934,7 +7981,7 @@ var require_client_h1 = __commonJS({
7934
7981
  if (socket.setTypeOfService) {
7935
7982
  socket.setTypeOfService(request.typeOfService);
7936
7983
  }
7937
- let header = `${method} ${path6} HTTP/1.1\r
7984
+ let header = `${method} ${path8} HTTP/1.1\r
7938
7985
  `;
7939
7986
  if (typeof host === "string") {
7940
7987
  header += `host: ${host}\r
@@ -8613,7 +8660,7 @@ var require_client_h2 = __commonJS({
8613
8660
  const headersTimeout = request.headersTimeout ?? client[kHeadersTimeout];
8614
8661
  const bodyTimeout = request.bodyTimeout ?? client[kBodyTimeout];
8615
8662
  const session = client[kHTTP2Session];
8616
- 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;
8617
8664
  let { body } = request;
8618
8665
  if (upgrade != null && upgrade !== "websocket") {
8619
8666
  util.errorRequest(client, request, new InvalidArgumentError(`Custom upgrade "${upgrade}" not supported over HTTP/2`));
@@ -8682,7 +8729,7 @@ var require_client_h2 = __commonJS({
8682
8729
  }
8683
8730
  headers[HTTP2_HEADER_METHOD] = "CONNECT";
8684
8731
  headers[HTTP2_HEADER_PROTOCOL] = "websocket";
8685
- headers[HTTP2_HEADER_PATH] = path6;
8732
+ headers[HTTP2_HEADER_PATH] = path8;
8686
8733
  if (protocol === "ws:" || protocol === "wss:") {
8687
8734
  headers[HTTP2_HEADER_SCHEME] = protocol === "ws:" ? "http" : "https";
8688
8735
  } else {
@@ -8734,7 +8781,7 @@ var require_client_h2 = __commonJS({
8734
8781
  stream.setTimeout(headersTimeout);
8735
8782
  return true;
8736
8783
  }
8737
- headers[HTTP2_HEADER_PATH] = path6;
8784
+ headers[HTTP2_HEADER_PATH] = path8;
8738
8785
  headers[HTTP2_HEADER_SCHEME] = protocol === "http:" ? "http" : "https";
8739
8786
  const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH";
8740
8787
  if (body && typeof body.read === "function") {
@@ -10215,7 +10262,7 @@ var require_agent = __commonJS({
10215
10262
  function defaultFactory(origin, opts) {
10216
10263
  return opts && opts.connections === 1 ? new Client(origin, opts) : new Pool(origin, opts);
10217
10264
  }
10218
- var Agent4 = class extends DispatcherBase {
10265
+ var Agent3 = class extends DispatcherBase {
10219
10266
  constructor({ factory = defaultFactory, maxOrigins = Infinity, connect, ...options } = {}) {
10220
10267
  if (typeof factory !== "function") {
10221
10268
  throw new InvalidArgumentError("factory must be a function.");
@@ -10324,7 +10371,7 @@ var require_agent = __commonJS({
10324
10371
  return allClientStats;
10325
10372
  }
10326
10373
  };
10327
- module.exports = Agent4;
10374
+ module.exports = Agent3;
10328
10375
  }
10329
10376
  });
10330
10377
 
@@ -11030,7 +11077,7 @@ var require_proxy_agent = __commonJS({
11030
11077
  "node_modules/undici/lib/dispatcher/proxy-agent.js"(exports, module) {
11031
11078
  "use strict";
11032
11079
  var { kProxy, kClose, kDestroy, kDispatch } = require_symbols();
11033
- var Agent4 = require_agent();
11080
+ var Agent3 = require_agent();
11034
11081
  var Pool = require_pool();
11035
11082
  var DispatcherBase = require_dispatcher_base();
11036
11083
  var { InvalidArgumentError, RequestAbortedError, SecureProxyConnectionError } = require_errors();
@@ -11086,10 +11133,10 @@ var require_proxy_agent = __commonJS({
11086
11133
  };
11087
11134
  const {
11088
11135
  origin,
11089
- path: path6 = "/",
11136
+ path: path8 = "/",
11090
11137
  headers = {}
11091
11138
  } = opts;
11092
- opts.path = origin + path6;
11139
+ opts.path = origin + path8;
11093
11140
  if (!("host" in headers) && !("Host" in headers)) {
11094
11141
  const { host } = new URL(origin);
11095
11142
  headers.host = host;
@@ -11161,7 +11208,7 @@ var require_proxy_agent = __commonJS({
11161
11208
  } else {
11162
11209
  this[kClient] = clientFactory(url2, { connect });
11163
11210
  }
11164
- this[kAgent] = new Agent4({
11211
+ this[kAgent] = new Agent3({
11165
11212
  ...opts,
11166
11213
  factory,
11167
11214
  connect: async (opts2, callback) => {
@@ -11289,7 +11336,7 @@ var require_env_http_proxy_agent = __commonJS({
11289
11336
  var DispatcherBase = require_dispatcher_base();
11290
11337
  var { kClose, kDestroy, kClosed, kDestroyed, kDispatch, kNoProxyAgent, kHttpProxyAgent, kHttpsProxyAgent } = require_symbols();
11291
11338
  var ProxyAgent = require_proxy_agent();
11292
- var Agent4 = require_agent();
11339
+ var Agent3 = require_agent();
11293
11340
  var DEFAULT_PORTS = {
11294
11341
  "http:": 80,
11295
11342
  "https:": 443
@@ -11302,7 +11349,7 @@ var require_env_http_proxy_agent = __commonJS({
11302
11349
  super();
11303
11350
  this.#opts = opts;
11304
11351
  const { httpProxy, httpsProxy, noProxy, ...agentOpts } = opts;
11305
- this[kNoProxyAgent] = new Agent4(agentOpts);
11352
+ this[kNoProxyAgent] = new Agent3(agentOpts);
11306
11353
  const HTTP_PROXY = httpProxy ?? process.env.http_proxy ?? process.env.HTTP_PROXY;
11307
11354
  if (HTTP_PROXY) {
11308
11355
  this[kHttpProxyAgent] = new ProxyAgent({ ...agentOpts, uri: HTTP_PROXY });
@@ -13249,20 +13296,20 @@ var require_mock_utils = __commonJS({
13249
13296
  }
13250
13297
  return normalizedQp;
13251
13298
  }
13252
- function safeUrl(path6) {
13253
- if (typeof path6 !== "string") {
13254
- return path6;
13299
+ function safeUrl(path8) {
13300
+ if (typeof path8 !== "string") {
13301
+ return path8;
13255
13302
  }
13256
- const pathSegments = path6.split("?", 3);
13303
+ const pathSegments = path8.split("?", 3);
13257
13304
  if (pathSegments.length !== 2) {
13258
- return path6;
13305
+ return path8;
13259
13306
  }
13260
13307
  const qp = new URLSearchParams(pathSegments.pop());
13261
13308
  qp.sort();
13262
13309
  return [...pathSegments, qp.toString()].join("?");
13263
13310
  }
13264
- function matchKey(mockDispatch2, { path: path6, method, body, headers }) {
13265
- const pathMatch = matchValue(mockDispatch2.path, path6);
13311
+ function matchKey(mockDispatch2, { path: path8, method, body, headers }) {
13312
+ const pathMatch = matchValue(mockDispatch2.path, path8);
13266
13313
  const methodMatch = matchValue(mockDispatch2.method, method);
13267
13314
  const bodyMatch = typeof mockDispatch2.body !== "undefined" ? matchValue(mockDispatch2.body, body) : true;
13268
13315
  const headersMatch = matchHeaders(mockDispatch2, headers);
@@ -13287,8 +13334,8 @@ var require_mock_utils = __commonJS({
13287
13334
  const basePath = key.query ? serializePathWithQuery(key.path, key.query) : key.path;
13288
13335
  const resolvedPath = typeof basePath === "string" ? safeUrl(basePath) : basePath;
13289
13336
  const resolvedPathWithoutTrailingSlash = removeTrailingSlash(resolvedPath);
13290
- let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path: path6, ignoreTrailingSlash }) => {
13291
- 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);
13292
13339
  });
13293
13340
  if (matchedMockDispatches.length === 0) {
13294
13341
  throw new MockNotMatchedError(`Mock dispatch not matched for path '${resolvedPath}'`);
@@ -13327,19 +13374,19 @@ var require_mock_utils = __commonJS({
13327
13374
  mockDispatches.splice(index, 1);
13328
13375
  }
13329
13376
  }
13330
- function removeTrailingSlash(path6) {
13331
- while (path6.endsWith("/")) {
13332
- path6 = path6.slice(0, -1);
13377
+ function removeTrailingSlash(path8) {
13378
+ while (path8.endsWith("/")) {
13379
+ path8 = path8.slice(0, -1);
13333
13380
  }
13334
- if (path6.length === 0) {
13335
- path6 = "/";
13381
+ if (path8.length === 0) {
13382
+ path8 = "/";
13336
13383
  }
13337
- return path6;
13384
+ return path8;
13338
13385
  }
13339
13386
  function buildKey(opts) {
13340
- const { path: path6, method, body, headers, query } = opts;
13387
+ const { path: path8, method, body, headers, query } = opts;
13341
13388
  return {
13342
- path: path6,
13389
+ path: path8,
13343
13390
  method,
13344
13391
  body,
13345
13392
  headers,
@@ -14029,10 +14076,10 @@ var require_pending_interceptors_formatter = __commonJS({
14029
14076
  }
14030
14077
  format(pendingInterceptors) {
14031
14078
  const withPrettyHeaders = pendingInterceptors.map(
14032
- ({ method, path: path6, data: { statusCode }, persist, times, timesInvoked, origin }) => ({
14079
+ ({ method, path: path8, data: { statusCode }, persist, times, timesInvoked, origin }) => ({
14033
14080
  Method: method,
14034
14081
  Origin: origin,
14035
- Path: path6,
14082
+ Path: path8,
14036
14083
  "Status code": statusCode,
14037
14084
  Persistent: persist ? PERSISTENT : NOT_PERSISTENT,
14038
14085
  Invocations: timesInvoked,
@@ -14051,7 +14098,7 @@ var require_mock_agent = __commonJS({
14051
14098
  "node_modules/undici/lib/mock/mock-agent.js"(exports, module) {
14052
14099
  "use strict";
14053
14100
  var { kClients } = require_symbols();
14054
- var Agent4 = require_agent();
14101
+ var Agent3 = require_agent();
14055
14102
  var {
14056
14103
  kAgent,
14057
14104
  kMockAgentSet,
@@ -14089,7 +14136,7 @@ var require_mock_agent = __commonJS({
14089
14136
  if (opts?.agent && typeof opts.agent.dispatch !== "function") {
14090
14137
  throw new InvalidArgumentError("Argument opts.agent must implement Agent");
14091
14138
  }
14092
- const agent = opts?.agent ? opts.agent : new Agent4(opts);
14139
+ const agent = opts?.agent ? opts.agent : new Agent3(opts);
14093
14140
  this[kAgent] = agent;
14094
14141
  this[kClients] = agent[kClients];
14095
14142
  this[kOptions] = mockOptions;
@@ -14114,9 +14161,9 @@ var require_mock_agent = __commonJS({
14114
14161
  const acceptNonStandardSearchParameters = this[kMockAgentAcceptsNonStandardSearchParameters];
14115
14162
  const dispatchOpts = { ...opts };
14116
14163
  if (acceptNonStandardSearchParameters && dispatchOpts.path) {
14117
- const [path6, searchParams] = dispatchOpts.path.split("?");
14164
+ const [path8, searchParams] = dispatchOpts.path.split("?");
14118
14165
  const normalizedSearchParams = normalizeSearchParams(searchParams, acceptNonStandardSearchParameters);
14119
- dispatchOpts.path = `${path6}?${normalizedSearchParams}`;
14166
+ dispatchOpts.path = `${path8}?${normalizedSearchParams}`;
14120
14167
  }
14121
14168
  return this[kAgent].dispatch(dispatchOpts, handler);
14122
14169
  }
@@ -14320,7 +14367,7 @@ var require_snapshot_utils = __commonJS({
14320
14367
  var require_snapshot_recorder = __commonJS({
14321
14368
  "node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
14322
14369
  "use strict";
14323
- var { writeFile, readFile, mkdir } = __require("fs/promises");
14370
+ var { writeFile: writeFile2, readFile, mkdir: mkdir2 } = __require("fs/promises");
14324
14371
  var { dirname, resolve } = __require("path");
14325
14372
  var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("timers");
14326
14373
  var { InvalidArgumentError, UndiciError } = require_errors();
@@ -14517,12 +14564,12 @@ var require_snapshot_recorder = __commonJS({
14517
14564
  * @return {Promise<void>} - Resolves when snapshots are loaded
14518
14565
  */
14519
14566
  async loadSnapshots(filePath) {
14520
- const path6 = filePath || this.#snapshotPath;
14521
- if (!path6) {
14567
+ const path8 = filePath || this.#snapshotPath;
14568
+ if (!path8) {
14522
14569
  throw new InvalidArgumentError("Snapshot path is required");
14523
14570
  }
14524
14571
  try {
14525
- const data = await readFile(resolve(path6), "utf8");
14572
+ const data = await readFile(resolve(path8), "utf8");
14526
14573
  const parsed = JSON.parse(data);
14527
14574
  if (Array.isArray(parsed)) {
14528
14575
  this.#snapshots.clear();
@@ -14536,7 +14583,7 @@ var require_snapshot_recorder = __commonJS({
14536
14583
  if (error62.code === "ENOENT") {
14537
14584
  this.#snapshots.clear();
14538
14585
  } else {
14539
- throw new UndiciError(`Failed to load snapshots from ${path6}`, { cause: error62 });
14586
+ throw new UndiciError(`Failed to load snapshots from ${path8}`, { cause: error62 });
14540
14587
  }
14541
14588
  }
14542
14589
  }
@@ -14547,17 +14594,17 @@ var require_snapshot_recorder = __commonJS({
14547
14594
  * @returns {Promise<void>} - Resolves when snapshots are saved
14548
14595
  */
14549
14596
  async saveSnapshots(filePath) {
14550
- const path6 = filePath || this.#snapshotPath;
14551
- if (!path6) {
14597
+ const path8 = filePath || this.#snapshotPath;
14598
+ if (!path8) {
14552
14599
  throw new InvalidArgumentError("Snapshot path is required");
14553
14600
  }
14554
- const resolvedPath = resolve(path6);
14555
- await mkdir(dirname(resolvedPath), { recursive: true });
14601
+ const resolvedPath = resolve(path8);
14602
+ await mkdir2(dirname(resolvedPath), { recursive: true });
14556
14603
  const data = Array.from(this.#snapshots.entries()).map(([hash2, snapshot]) => ({
14557
14604
  hash: hash2,
14558
14605
  snapshot
14559
14606
  }));
14560
- await writeFile(resolvedPath, JSON.stringify(data, null, 2), { flush: true });
14607
+ await writeFile2(resolvedPath, JSON.stringify(data, null, 2), { flush: true });
14561
14608
  }
14562
14609
  /**
14563
14610
  * Clears all recorded snapshots
@@ -14696,7 +14743,7 @@ var require_snapshot_recorder = __commonJS({
14696
14743
  var require_snapshot_agent = __commonJS({
14697
14744
  "node_modules/undici/lib/mock/snapshot-agent.js"(exports, module) {
14698
14745
  "use strict";
14699
- var Agent4 = require_agent();
14746
+ var Agent3 = require_agent();
14700
14747
  var MockAgent = require_mock_agent();
14701
14748
  var { SnapshotRecorder } = require_snapshot_recorder();
14702
14749
  var WrapHandler = require_wrap_handler();
@@ -14747,7 +14794,7 @@ var require_snapshot_agent = __commonJS({
14747
14794
  });
14748
14795
  this[kSnapshotLoaded] = false;
14749
14796
  if (this[kSnapshotMode] === "record" || this[kSnapshotMode] === "update" || this[kSnapshotMode] === "playback" && opts.excludeUrls && opts.excludeUrls.length > 0) {
14750
- this[kRealAgent] = new Agent4(opts);
14797
+ this[kRealAgent] = new Agent3(opts);
14751
14798
  }
14752
14799
  if ((this[kSnapshotMode] === "playback" || this[kSnapshotMode] === "update") && this[kSnapshotPath]) {
14753
14800
  this.loadSnapshots().catch(() => {
@@ -14986,9 +15033,9 @@ var require_global2 = __commonJS({
14986
15033
  var globalDispatcher = Symbol.for("undici.globalDispatcher.2");
14987
15034
  var legacyGlobalDispatcher = Symbol.for("undici.globalDispatcher.1");
14988
15035
  var { InvalidArgumentError } = require_errors();
14989
- var Agent4 = require_agent();
15036
+ var Agent3 = require_agent();
14990
15037
  if (getGlobalDispatcher() === void 0) {
14991
- setGlobalDispatcher(new Agent4());
15038
+ setGlobalDispatcher(new Agent3());
14992
15039
  }
14993
15040
  function setGlobalDispatcher(agent) {
14994
15041
  if (!agent || typeof agent.dispatch !== "function") {
@@ -15183,15 +15230,15 @@ var require_redirect_handler = __commonJS({
15183
15230
  return;
15184
15231
  }
15185
15232
  const { origin, pathname, search } = util.parseURL(new URL(this.location, this.opts.origin && new URL(this.opts.path, this.opts.origin)));
15186
- const path6 = search ? `${pathname}${search}` : pathname;
15187
- const redirectUrlString = `${origin}${path6}`;
15233
+ const path8 = search ? `${pathname}${search}` : pathname;
15234
+ const redirectUrlString = `${origin}${path8}`;
15188
15235
  for (const historyUrl of this.history) {
15189
15236
  if (historyUrl.toString() === redirectUrlString) {
15190
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.`);
15191
15238
  }
15192
15239
  }
15193
15240
  this.opts.headers = cleanRequestHeaders(this.opts.headers, statusCode === 303, this.opts.origin !== origin);
15194
- this.opts.path = path6;
15241
+ this.opts.path = path8;
15195
15242
  this.opts.origin = origin;
15196
15243
  this.opts.query = null;
15197
15244
  }
@@ -16950,10 +16997,10 @@ var require_cache_handler = __commonJS({
16950
16997
  }
16951
16998
  return locationUrl.pathname + locationUrl.search;
16952
16999
  }
16953
- function deleteCachedUri(store, cacheKey, path6) {
17000
+ function deleteCachedUri(store, cacheKey, path8) {
16954
17001
  deleteCachedValue(store, {
16955
17002
  ...cacheKey,
16956
- path: path6
17003
+ path: path8
16957
17004
  });
16958
17005
  for (let i = 0; i < util.safeHTTPMethods.length; i++) {
16959
17006
  const method = util.safeHTTPMethods[i];
@@ -16961,7 +17008,7 @@ var require_cache_handler = __commonJS({
16961
17008
  deleteCachedValue(store, {
16962
17009
  ...cacheKey,
16963
17010
  method,
16964
- path: path6
17011
+ path: path8
16965
17012
  });
16966
17013
  }
16967
17014
  }
@@ -16972,9 +17019,9 @@ var require_cache_handler = __commonJS({
16972
17019
  }
16973
17020
  const values = Array.isArray(headerValue) ? headerValue : [headerValue];
16974
17021
  for (let i = 0; i < values.length; i++) {
16975
- const path6 = getSameOriginPath(cacheKey, values[i]);
16976
- if (path6 !== void 0) {
16977
- deleteCachedUri(store, cacheKey, path6);
17022
+ const path8 = getSameOriginPath(cacheKey, values[i]);
17023
+ if (path8 !== void 0) {
17024
+ deleteCachedUri(store, cacheKey, path8);
16978
17025
  }
16979
17026
  }
16980
17027
  }
@@ -22121,11 +22168,11 @@ var require_fetch = __commonJS({
22121
22168
  function dispatch({ body }) {
22122
22169
  const url2 = requestCurrentURL(request);
22123
22170
  const agent = fetchParams.controller.dispatcher;
22124
- const path6 = url2.pathname + url2.search;
22171
+ const path8 = url2.pathname + url2.search;
22125
22172
  const hasTrailingQuestionMark = url2.search.length === 0 && url2.href[url2.href.length - url2.hash.length - 1] === "?";
22126
22173
  return new Promise((resolve, reject) => agent.dispatch(
22127
22174
  {
22128
- path: hasTrailingQuestionMark ? `${path6}?` : path6,
22175
+ path: hasTrailingQuestionMark ? `${path8}?` : path8,
22129
22176
  origin: url2.origin,
22130
22177
  method: request.method,
22131
22178
  body: agent.isMockActive ? request.body && (request.body.source || request.body.stream) : body,
@@ -22675,8 +22722,8 @@ var require_cache3 = __commonJS({
22675
22722
  * @returns {requestResponseList}
22676
22723
  */
22677
22724
  #batchCacheOperations(operations) {
22678
- const cache = this.#relevantRequestResponseList;
22679
- const backupCache = [...cache];
22725
+ const cache2 = this.#relevantRequestResponseList;
22726
+ const backupCache = [...cache2];
22680
22727
  const addedItems = [];
22681
22728
  const resultList = [];
22682
22729
  try {
@@ -22703,9 +22750,9 @@ var require_cache3 = __commonJS({
22703
22750
  return [];
22704
22751
  }
22705
22752
  for (const requestResponse of requestResponses) {
22706
- const idx = cache.indexOf(requestResponse);
22753
+ const idx = cache2.indexOf(requestResponse);
22707
22754
  assert2(idx !== -1);
22708
- cache.splice(idx, 1);
22755
+ cache2.splice(idx, 1);
22709
22756
  }
22710
22757
  } else if (operation.type === "put") {
22711
22758
  if (operation.response == null) {
@@ -22735,11 +22782,11 @@ var require_cache3 = __commonJS({
22735
22782
  }
22736
22783
  requestResponses = this.#queryCache(operation.request);
22737
22784
  for (const requestResponse of requestResponses) {
22738
- const idx = cache.indexOf(requestResponse);
22785
+ const idx = cache2.indexOf(requestResponse);
22739
22786
  assert2(idx !== -1);
22740
- cache.splice(idx, 1);
22787
+ cache2.splice(idx, 1);
22741
22788
  }
22742
- cache.push([operation.request, operation.response]);
22789
+ cache2.push([operation.request, operation.response]);
22743
22790
  addedItems.push([operation.request, operation.response]);
22744
22791
  }
22745
22792
  resultList.push([operation.request, operation.response]);
@@ -22916,13 +22963,13 @@ var require_cachestorage = __commonJS({
22916
22963
  if (options.cacheName != null) {
22917
22964
  if (this.#caches.has(options.cacheName)) {
22918
22965
  const cacheList = this.#caches.get(options.cacheName);
22919
- const cache = new Cache(kConstruct, cacheList);
22920
- return await cache.match(request, options);
22966
+ const cache2 = new Cache(kConstruct, cacheList);
22967
+ return await cache2.match(request, options);
22921
22968
  }
22922
22969
  } else {
22923
22970
  for (const cacheList of this.#caches.values()) {
22924
- const cache = new Cache(kConstruct, cacheList);
22925
- const response = await cache.match(request, options);
22971
+ const cache2 = new Cache(kConstruct, cacheList);
22972
+ const response = await cache2.match(request, options);
22926
22973
  if (response !== void 0) {
22927
22974
  return response;
22928
22975
  }
@@ -22952,12 +22999,12 @@ var require_cachestorage = __commonJS({
22952
22999
  webidl.argumentLengthCheck(arguments, 1, prefix);
22953
23000
  cacheName = webidl.converters.DOMString(cacheName, prefix, "cacheName");
22954
23001
  if (this.#caches.has(cacheName)) {
22955
- const cache2 = this.#caches.get(cacheName);
22956
- return new Cache(kConstruct, cache2);
23002
+ const cache3 = this.#caches.get(cacheName);
23003
+ return new Cache(kConstruct, cache3);
22957
23004
  }
22958
- const cache = [];
22959
- this.#caches.set(cacheName, cache);
22960
- return new Cache(kConstruct, cache);
23005
+ const cache2 = [];
23006
+ this.#caches.set(cacheName, cache2);
23007
+ return new Cache(kConstruct, cache2);
22961
23008
  }
22962
23009
  /**
22963
23010
  * @see https://w3c.github.io/ServiceWorker/#cache-storage-delete
@@ -23072,9 +23119,9 @@ var require_util4 = __commonJS({
23072
23119
  }
23073
23120
  }
23074
23121
  }
23075
- function validateCookiePath(path6) {
23076
- for (let i = 0; i < path6.length; ++i) {
23077
- const code = path6.charCodeAt(i);
23122
+ function validateCookiePath(path8) {
23123
+ for (let i = 0; i < path8.length; ++i) {
23124
+ const code = path8.charCodeAt(i);
23078
23125
  if (code < 32 || // exclude CTLs (0-31)
23079
23126
  code > 126 || // exclude DEL and non-ascii
23080
23127
  code === 59) {
@@ -26355,7 +26402,7 @@ var require_undici = __commonJS({
26355
26402
  var Pool = require_pool();
26356
26403
  var BalancedPool = require_balanced_pool();
26357
26404
  var RoundRobinPool = require_round_robin_pool();
26358
- var Agent4 = require_agent();
26405
+ var Agent3 = require_agent();
26359
26406
  var ProxyAgent = require_proxy_agent();
26360
26407
  var Socks5ProxyAgent = require_socks5_proxy_agent();
26361
26408
  var EnvHttpProxyAgent = require_env_http_proxy_agent();
@@ -26382,7 +26429,7 @@ var require_undici = __commonJS({
26382
26429
  module.exports.Pool = Pool;
26383
26430
  module.exports.BalancedPool = BalancedPool;
26384
26431
  module.exports.RoundRobinPool = RoundRobinPool;
26385
- module.exports.Agent = Agent4;
26432
+ module.exports.Agent = Agent3;
26386
26433
  module.exports.ProxyAgent = ProxyAgent;
26387
26434
  module.exports.Socks5ProxyAgent = Socks5ProxyAgent;
26388
26435
  module.exports.EnvHttpProxyAgent = EnvHttpProxyAgent;
@@ -26428,11 +26475,11 @@ var require_undici = __commonJS({
26428
26475
  if (typeof opts.path !== "string") {
26429
26476
  throw new InvalidArgumentError("invalid opts.path");
26430
26477
  }
26431
- let path6 = opts.path;
26478
+ let path8 = opts.path;
26432
26479
  if (!opts.path.startsWith("/")) {
26433
- path6 = `/${path6}`;
26480
+ path8 = `/${path8}`;
26434
26481
  }
26435
- url2 = new URL(util.parseOrigin(url2).origin + path6);
26482
+ url2 = new URL(util.parseOrigin(url2).origin + path8);
26436
26483
  } else {
26437
26484
  if (!opts) {
26438
26485
  opts = typeof url2 === "object" ? url2 : {};
@@ -26576,8 +26623,7 @@ var init_redact = __esm({
26576
26623
  // src/api/api.ts
26577
26624
  async function fetchJson(params) {
26578
26625
  const { url: url2, init, timeoutMs, allowInsecureTls } = params;
26579
- const dispatcher = allowInsecureTls ? new import_undici.Agent({ connect: { rejectUnauthorized: false } }) : void 0;
26580
- const fetcher = allowInsecureTls ? import_undici.fetch : globalThis.fetch;
26626
+ const fetcher = globalThis.fetch;
26581
26627
  const controller = new AbortController();
26582
26628
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
26583
26629
  let response;
@@ -26585,8 +26631,7 @@ async function fetchJson(params) {
26585
26631
  try {
26586
26632
  response = await fetcher(url2, {
26587
26633
  ...init,
26588
- signal: controller.signal,
26589
- ...dispatcher ? { dispatcher } : {}
26634
+ signal: controller.signal
26590
26635
  });
26591
26636
  text = await response.text().catch(() => "");
26592
26637
  } finally {
@@ -26728,6 +26773,42 @@ async function sendWorkChatReplyMessage(params) {
26728
26773
  if (!data?.msgId) throw new Error("workchat: msgId missing in reply response");
26729
26774
  return { msgId: String(data.msgId) };
26730
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
+ }
26731
26812
  async function notifyAgentCreatedStatus(opts, agentId) {
26732
26813
  try {
26733
26814
  const token = await getAccessToken(opts);
@@ -26742,11 +26823,10 @@ async function notifyAgentCreatedStatus(opts, agentId) {
26742
26823
  logger.error(`agent-created/status failed: ${String(err)}`);
26743
26824
  }
26744
26825
  }
26745
- var import_undici, tokenCache;
26826
+ var tokenCache;
26746
26827
  var init_api = __esm({
26747
26828
  "src/api/api.ts"() {
26748
26829
  "use strict";
26749
- import_undici = __toESM(require_undici(), 1);
26750
26830
  init_types();
26751
26831
  init_logger();
26752
26832
  init_redact();
@@ -26807,7 +26887,17 @@ function parseWorkChatFrame(data) {
26807
26887
  if (type === "CALLBACK" && String(envelope?.metadata?.topic || "").trim().toUpperCase() === "AGENT_MESSAGE") {
26808
26888
  const payload = parseMaybeJson(envelope?.data) ?? {};
26809
26889
  const message = payload?.message;
26810
- 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
+ }
26811
26901
  return {
26812
26902
  type: "agent_message",
26813
26903
  message: {
@@ -26815,12 +26905,26 @@ function parseWorkChatFrame(data) {
26815
26905
  userId: String(payload?.userId ?? ""),
26816
26906
  openConversationId: String(payload?.conversationId ?? ""),
26817
26907
  messageId: String(payload?.msgId ?? ""),
26818
- ...payload?.agentId !== void 0 ? { agentId: String(payload.agentId) } : {}
26908
+ ...payload?.agentId !== void 0 ? { agentId: String(payload.agentId) } : {},
26909
+ ...mediaUrl ? { mediaUrl, mediaType: messageType || "file" } : {}
26819
26910
  }
26820
26911
  };
26821
26912
  }
26822
26913
  return { type: "ignored", ignoredKind: `${type}/${topicLower || "(no topic)"}` };
26823
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
+ }
26824
26928
  function buildPongFrame(pongData) {
26825
26929
  return JSON.stringify({
26826
26930
  code: 200,
@@ -26834,8 +26938,10 @@ function generateMessageSid() {
26834
26938
  }
26835
26939
  function workChatMessageToMsgContext(message, accountId) {
26836
26940
  const fromUserId = message.userId ?? "";
26941
+ const body = message.mediaUrl ? `${message.text ?? ""}
26942
+ [media] ${message.mediaUrl}` : message.text ?? "";
26837
26943
  const ctx = {
26838
- Body: message.text ?? "",
26944
+ Body: body,
26839
26945
  From: fromUserId,
26840
26946
  To: fromUserId,
26841
26947
  AccountId: accountId,
@@ -26853,6 +26959,10 @@ function workChatMessageToMsgContext(message, accountId) {
26853
26959
  if (message.agentId) {
26854
26960
  ctx.AgentId = message.agentId;
26855
26961
  }
26962
+ if (message.mediaUrl) {
26963
+ ctx.MediaUrl = message.mediaUrl;
26964
+ ctx.MediaType = message.mediaType ?? "file";
26965
+ }
26856
26966
  return ctx;
26857
26967
  }
26858
26968
  var init_inbound = __esm({
@@ -27059,8 +27169,8 @@ function startBindingListener(sessionKey, opts, timeoutMs) {
27059
27169
  clearWorkChatTokenCache(opts.appKey || "default");
27060
27170
  const { endpoint, ticket } = await openConnection(opts);
27061
27171
  const url2 = `${endpoint}?ticket=${encodeURIComponent(ticket)}`;
27062
- const dispatcher = opts.allowInsecureTls ? new import_undici2.Agent({ connect: { rejectUnauthorized: false } }) : void 0;
27063
- 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);
27064
27174
  const entryNow = bindingListeners.get(sessionKey);
27065
27175
  if (!entryNow || entryNow.done) {
27066
27176
  try {
@@ -27127,28 +27237,27 @@ async function handleListenerFrame(sessionKey, data, ws) {
27127
27237
  if (result.type !== "init_agent") return;
27128
27238
  const eventData = result.eventData;
27129
27239
  if (!eventData) return;
27130
- const agentId = String(eventData.id ?? eventData.agentId ?? "");
27240
+ const agentId = String(eventData.id ?? eventData.agentId ?? "").trim();
27131
27241
  if (!agentId) return;
27132
- const userId = String(eventData.futureId ?? eventData.userId ?? "");
27242
+ const userId = String(eventData.futureId ?? eventData.userId ?? "").trim();
27133
27243
  const entry = bindingListeners.get(sessionKey);
27134
27244
  if (entry) entry.done = true;
27135
- const isRebind = listIndexedWorkChatAccountIds().length > 0;
27245
+ const accountId = `agent-${agentId}`;
27136
27246
  try {
27137
- const { pruneStaleWorkChatAccounts: pruneStaleWorkChatAccounts2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
27138
- const removed = await pruneStaleWorkChatAccounts2(agentId);
27247
+ const { pruneOtherWorkChatAccounts: pruneOtherWorkChatAccounts2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
27248
+ const removed = await pruneOtherWorkChatAccounts2(accountId);
27139
27249
  if (removed.length > 0) {
27140
- logger.info(`binding listener: pruned stale account(s): ${removed.join(", ")}`);
27250
+ logger.info(`binding listener: pruned previous account(s): ${removed.join(", ")}`);
27141
27251
  }
27142
27252
  } catch (err) {
27143
27253
  logger.error(`binding listener: rebind cleanup failed: ${String(err)}`);
27144
27254
  }
27145
- const accountId = isRebind ? `agent-${agentId}` : "default";
27146
27255
  try {
27147
27256
  saveWorkChatAccount(accountId, {
27148
27257
  agentId,
27149
27258
  ...userId ? { userId } : {}
27150
27259
  });
27151
- registerWorkChatAccountId(accountId);
27260
+ await registerWorkChatAccountId(accountId);
27152
27261
  const { bindAgentToAccountFromBinding: bindAgentToAccountFromBinding2 } = await Promise.resolve().then(() => (init_bind_agent(), bind_agent_exports));
27153
27262
  await bindAgentToAccountFromBinding2(accountId, agentId, userId);
27154
27263
  logger.info(`binding listener: account=${accountId} agent=${agentId} persisted`);
@@ -27187,12 +27296,12 @@ function completeWorkChatBinding(completion) {
27187
27296
  );
27188
27297
  return true;
27189
27298
  }
27190
- 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;
27191
27300
  var init_login_qr = __esm({
27192
27301
  "src/auth/login-qr.ts"() {
27193
27302
  "use strict";
27194
27303
  init_logger();
27195
- import_undici2 = __toESM(require_undici(), 1);
27304
+ import_undici = __toESM(require_undici(), 1);
27196
27305
  init_api();
27197
27306
  init_inbound();
27198
27307
  init_inbound();
@@ -27207,9 +27316,74 @@ var init_login_qr = __esm({
27207
27316
  }
27208
27317
  });
27209
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
+
27210
27383
  // src/messaging/send.ts
27211
27384
  var send_exports = {};
27212
27385
  __export(send_exports, {
27386
+ buildApiOptions: () => buildApiOptions,
27213
27387
  pushWorkChatMessage: () => pushWorkChatMessage,
27214
27388
  replyWorkChatMessage: () => replyWorkChatMessage
27215
27389
  });
@@ -27255,6 +27429,7 @@ async function replyWorkChatMessage(params) {
27255
27429
  logger.info(`reply sent to=${to} messageId=${result.msgId}`);
27256
27430
  return { messageId: result.msgId };
27257
27431
  } catch (err) {
27432
+ pauseOnPermanentError(account.accountId, err instanceof Error ? err.message : String(err));
27258
27433
  logger.error(`replyWorkChatMessage: failed to=${to} err=${String(err)}`);
27259
27434
  throw err;
27260
27435
  }
@@ -27280,6 +27455,7 @@ async function pushWorkChatMessage(params) {
27280
27455
  logger.info(`push sent to=${to} messageId=${result.msgId}`);
27281
27456
  return { messageId: result.msgId };
27282
27457
  } catch (err) {
27458
+ pauseOnPermanentError(account.accountId, err instanceof Error ? err.message : String(err));
27283
27459
  logger.error(`pushWorkChatMessage: failed to=${to} err=${String(err)}`);
27284
27460
  throw err;
27285
27461
  }
@@ -27289,15 +27465,230 @@ var init_send = __esm({
27289
27465
  "use strict";
27290
27466
  init_types();
27291
27467
  init_api();
27468
+ init_session_guard();
27292
27469
  init_logger();
27293
27470
  }
27294
27471
  });
27295
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";
27682
+ init_logger();
27683
+ CHANNEL_ID = "openclaw-workchat";
27684
+ }
27685
+ });
27686
+
27296
27687
  // src/messaging/debug-mode.ts
27297
27688
  import fs5 from "fs";
27298
- import path5 from "path";
27689
+ import path6 from "path";
27299
27690
  function resolveDebugModePath() {
27300
- return path5.join(resolveStateDir(), "openclaw-workchat", "debug-mode.json");
27691
+ return path6.join(resolveStateDir(), "openclaw-workchat", "debug-mode.json");
27301
27692
  }
27302
27693
  function loadState() {
27303
27694
  try {
@@ -27310,7 +27701,7 @@ function loadState() {
27310
27701
  }
27311
27702
  function saveState(state) {
27312
27703
  const filePath = resolveDebugModePath();
27313
- fs5.mkdirSync(path5.dirname(filePath), { recursive: true });
27704
+ fs5.mkdirSync(path6.dirname(filePath), { recursive: true });
27314
27705
  fs5.writeFileSync(filePath, JSON.stringify(state, null, 2), "utf-8");
27315
27706
  }
27316
27707
  function toggleDebugMode(accountId) {
@@ -27420,77 +27811,73 @@ var init_slash_commands = __esm({
27420
27811
  }
27421
27812
  });
27422
27813
 
27423
- // src/messaging/outbound-hooks.ts
27424
- var outbound_hooks_exports = {};
27425
- __export(outbound_hooks_exports, {
27426
- applyWorkChatMessageSendingHook: () => applyWorkChatMessageSendingHook,
27427
- emitWorkChatMessageSent: () => emitWorkChatMessageSent
27428
- });
27429
- import {
27430
- fireAndForgetHook,
27431
- buildCanonicalSentMessageHookContext,
27432
- toPluginMessageContext,
27433
- toPluginMessageSentEvent
27434
- } from "openclaw/plugin-sdk/hook-runtime";
27435
- import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
27436
- async function applyWorkChatMessageSendingHook(params) {
27437
- const hookRunner = getGlobalHookRunner();
27438
- if (!hookRunner?.hasHooks("message_sending")) {
27439
- return { cancelled: false, text: params.text };
27440
- }
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
27820
+ });
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 = [];
27441
27831
  try {
27442
- const hookResult = await hookRunner.runMessageSending(
27443
- {
27444
- to: params.to,
27445
- content: params.text,
27446
- metadata: {
27447
- channel: CHANNEL_ID,
27448
- accountId: params.accountId,
27449
- ...params.mediaUrl ? { mediaUrls: [params.mediaUrl] } : {}
27450
- }
27451
- },
27452
- { channelId: CHANNEL_ID, accountId: params.accountId }
27453
- );
27454
- if (hookResult?.cancel) {
27455
- 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 !== "");
27456
27836
  }
27457
- return {
27458
- cancelled: false,
27459
- text: hookResult?.content ?? params.text
27460
- };
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");
27461
27848
  } catch (err) {
27462
- logger.warn(`message_sending hook error, proceeding with send: ${String(err)}`);
27463
- return { cancelled: false, text: params.text };
27849
+ logger.warn(`seen-msgids persist failed accountId=${accountId}: ${String(err)}`);
27464
27850
  }
27465
27851
  }
27466
- function emitWorkChatMessageSent(params) {
27467
- const hookRunner = getGlobalHookRunner();
27468
- if (!hookRunner?.hasHooks("message_sent")) return;
27469
- const canonical = buildCanonicalSentMessageHookContext({
27470
- to: params.to,
27471
- content: params.content,
27472
- success: params.success,
27473
- error: params.error,
27474
- channelId: CHANNEL_ID,
27475
- accountId: params.accountId,
27476
- conversationId: params.to
27477
- });
27478
- fireAndForgetHook(
27479
- Promise.resolve(
27480
- hookRunner.runMessageSent(
27481
- toPluginMessageSentEvent(canonical),
27482
- toPluginMessageContext(canonical)
27483
- )
27484
- ),
27485
- "workchat: message_sent plugin hook failed"
27486
- );
27852
+ function isDuplicateMessage(accountId, msgId) {
27853
+ if (!msgId) return false;
27854
+ const seen = loadSeen(accountId);
27855
+ return seen.index.has(msgId);
27487
27856
  }
27488
- var CHANNEL_ID;
27489
- var init_outbound_hooks = __esm({
27490
- "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"() {
27491
27876
  "use strict";
27877
+ init_state_dir();
27492
27878
  init_logger();
27493
- CHANNEL_ID = "openclaw-workchat";
27879
+ SEEN_LIMIT = 500;
27880
+ cache = /* @__PURE__ */ new Map();
27494
27881
  }
27495
27882
  });
27496
27883
 
@@ -27828,6 +28215,28 @@ function loadWorkChatOutboundHooks() {
27828
28215
  workchatOutboundHooksPromise ??= Promise.resolve().then(() => (init_outbound_hooks(), outbound_hooks_exports));
27829
28216
  return workchatOutboundHooksPromise;
27830
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
+ }
27831
28240
  async function applyWorkChatMessageSendingHook2(params) {
27832
28241
  try {
27833
28242
  const hooks = await loadWorkChatOutboundHooks();
@@ -27843,31 +28252,30 @@ function emitWorkChatMessageSent2(params) {
27843
28252
  async function handleAgentLifecycleEvent(frameType, result, deps) {
27844
28253
  const eventData = result.eventData;
27845
28254
  if (!eventData) return;
27846
- const agentId = String(eventData.id ?? eventData.agentId ?? "");
28255
+ const agentId = String(eventData.id ?? eventData.agentId ?? "").trim();
27847
28256
  if (!agentId) {
27848
28257
  logger.warn(`${frameType}: missing agent id, ignoring`);
27849
28258
  return;
27850
28259
  }
27851
- const userId = String(eventData.futureId ?? eventData.userId ?? "");
28260
+ const userId = String(eventData.futureId ?? eventData.userId ?? "").trim();
27852
28261
  logger.info(`${frameType}: agentId=${agentId} userId=${userId ? "present" : "missing"}`);
27853
28262
  deps.log(`${frameType}: agentId=${agentId}`);
27854
- const isRebind = listWorkChatAccountIdsCount() > 0;
28263
+ const { completeWorkChatBinding: completeWorkChatBinding2 } = await Promise.resolve().then(() => (init_login_qr(), login_qr_exports));
28264
+ const accountId = frameType === "init_agent" ? `agent-${agentId}` : deps.accountId;
27855
28265
  try {
27856
- const removed = await pruneStaleWorkChatAccounts(agentId);
28266
+ const removed = await pruneOtherWorkChatAccounts(accountId);
27857
28267
  if (removed.length > 0) {
27858
- deps.log(`rebind: removed stale account(s): ${removed.join(", ")}`);
28268
+ deps.log(`rebind: removed previous account(s): ${removed.join(", ")}`);
27859
28269
  }
27860
28270
  } catch (err) {
27861
28271
  logger.error(`rebind cleanup failed: ${String(err)}`);
27862
28272
  }
27863
- const { completeWorkChatBinding: completeWorkChatBinding2 } = await Promise.resolve().then(() => (init_login_qr(), login_qr_exports));
27864
- const accountId = frameType === "init_agent" ? isRebind ? `agent-${agentId}` : "default" : deps.accountId;
27865
28273
  try {
27866
28274
  saveWorkChatAccount(accountId, {
27867
28275
  agentId,
27868
28276
  ...userId ? { userId } : {}
27869
28277
  });
27870
- registerWorkChatAccountId(accountId);
28278
+ await registerWorkChatAccountId(accountId);
27871
28279
  const { bindAgentToAccount: bindAgentToAccount2 } = await Promise.resolve().then(() => (init_bind_agent(), bind_agent_exports));
27872
28280
  await bindAgentToAccount2(accountId, agentId, userId);
27873
28281
  logger.info(`${frameType}: bound agent to account accountId=${accountId}`);
@@ -27889,9 +28297,6 @@ async function handleAgentLifecycleEvent(frameType, result, deps) {
27889
28297
  void notifyAgentCreatedStatus(deps.apiOpts, agentId);
27890
28298
  }
27891
28299
  }
27892
- function listWorkChatAccountIdsCount() {
27893
- return listIndexedWorkChatAccountIds().length;
27894
- }
27895
28300
  function loadCreateTypingCallbacks() {
27896
28301
  createTypingCallbacksPromise ??= (async () => {
27897
28302
  for (const specifier of ["openclaw/plugin-sdk/channel-runtime", "openclaw/plugin-sdk/channel-message"]) {
@@ -27975,13 +28380,21 @@ async function processOneFrame(result, rawFrame, deps) {
27975
28380
  logger.debug("inbound: empty body, ignoring");
27976
28381
  return;
27977
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
+ }
27978
28391
  const receivedAt = Date.now();
27979
28392
  const debug = isDebugMode(deps.accountId);
27980
28393
  const debugTrace = [];
27981
28394
  const debugTs = { received: receivedAt };
27982
28395
  const ctx = workChatMessageToMsgContext(message, deps.accountId);
27983
28396
  logger.info(
27984
- `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)}`
27985
28398
  );
27986
28399
  logger.debug(`inbound raw frame: ${rawFrame.slice(0, 200)}`);
27987
28400
  if (ctx.Body.trim().startsWith("/")) {
@@ -27996,6 +28409,24 @@ async function processOneFrame(result, rawFrame, deps) {
27996
28409
  return;
27997
28410
  }
27998
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
+ }
27999
28430
  const rawBody = ctx.Body?.trim() ?? "";
28000
28431
  ctx.CommandBody = rawBody;
28001
28432
  const senderId = ctx.From;
@@ -28096,7 +28527,10 @@ async function processOneFrame(result, rawFrame, deps) {
28096
28527
  const rawText = String(payload.text ?? "");
28097
28528
  const f = new StreamingMarkdownFilter2();
28098
28529
  let text = f.feed(rawText) + f.flush();
28099
- 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
+ );
28100
28534
  if (debug) {
28101
28535
  debugDeliveries.push({
28102
28536
  textLen: text.length,
@@ -28107,7 +28541,8 @@ async function processOneFrame(result, rawFrame, deps) {
28107
28541
  const sendingResult = await applyWorkChatMessageSendingHook2({
28108
28542
  to: ctx.To,
28109
28543
  text,
28110
- accountId: deps.accountId
28544
+ accountId: deps.accountId,
28545
+ mediaUrl
28111
28546
  });
28112
28547
  if (sendingResult.cancelled) {
28113
28548
  logger.info(`outbound: cancelled by message_sending hook to=${ctx.To}`);
@@ -28115,6 +28550,40 @@ async function processOneFrame(result, rawFrame, deps) {
28115
28550
  }
28116
28551
  text = sendingResult.text;
28117
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
+ }
28118
28587
  const result2 = await replyWorkChatMessage2({
28119
28588
  account: {
28120
28589
  ...deps.account,
@@ -28139,7 +28608,14 @@ async function processOneFrame(result, rawFrame, deps) {
28139
28608
  onError: (err, info) => {
28140
28609
  deps.errLog(`workchat reply ${info.kind}: ${String(err)}`);
28141
28610
  const errMsg = err instanceof Error ? err.message : String(err);
28142
- 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
+ }
28143
28619
  void sendWorkChatErrorNotice({
28144
28620
  account: deps.account,
28145
28621
  to: ctx.To,
@@ -28209,7 +28685,7 @@ ${debugTrace.join("\n")}`;
28209
28685
  }
28210
28686
  }
28211
28687
  }
28212
- var workchatOutboundHooksPromise, createTypingCallbacksPromise;
28688
+ var workchatOutboundHooksPromise, WORKCHAT_MEDIA_MAX_BYTES, createTypingCallbacksPromise;
28213
28689
  var init_process_message = __esm({
28214
28690
  "src/messaging/process-message.ts"() {
28215
28691
  "use strict";
@@ -28223,6 +28699,7 @@ var init_process_message = __esm({
28223
28699
  init_inbound();
28224
28700
  init_slash_commands();
28225
28701
  workchatOutboundHooksPromise = null;
28702
+ WORKCHAT_MEDIA_MAX_BYTES = 100 * 1024 * 1024;
28226
28703
  }
28227
28704
  });
28228
28705
 
@@ -28277,8 +28754,8 @@ async function monitorWorkChatProvider(opts) {
28277
28754
  localIp: opts.localIp
28278
28755
  });
28279
28756
  const url2 = `${endpoint}?ticket=${encodeURIComponent(ticket)}`;
28280
- const dispatcher = apiOpts.allowInsecureTls ? new import_undici3.Agent({ connect: { rejectUnauthorized: false } }) : void 0;
28281
- 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);
28282
28759
  await new Promise((resolve, reject) => {
28283
28760
  const timer = setTimeout(
28284
28761
  () => reject(new Error("workchat websocket connection timeout")),
@@ -28411,11 +28888,11 @@ async function monitorWorkChatProvider(opts) {
28411
28888
  }
28412
28889
  }
28413
28890
  }
28414
- 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;
28415
28892
  var init_monitor = __esm({
28416
28893
  "src/monitor/monitor.ts"() {
28417
28894
  "use strict";
28418
- import_undici3 = __toESM(require_undici(), 1);
28895
+ import_undici2 = __toESM(require_undici(), 1);
28419
28896
  init_api();
28420
28897
  init_api();
28421
28898
  init_inbound();
@@ -29529,8 +30006,10 @@ import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-sch
29529
30006
 
29530
30007
  // src/channel.ts
29531
30008
  init_accounts();
30009
+ init_types();
29532
30010
  init_login_qr();
29533
30011
  init_send();
30012
+ init_send_media();
29534
30013
  init_logger();
29535
30014
  function resolveOutboundAccount(cfg, accountId) {
29536
30015
  const allIds = listWorkChatAccountIds(cfg);
@@ -29583,7 +30062,8 @@ var workchatPlugin = {
29583
30062
  messageToolHints: () => [
29584
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.",
29585
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>' }.",
29586
- "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."
29587
30067
  ]
29588
30068
  },
29589
30069
  reload: { configPrefixes: ["channels.openclaw-workchat"] },
@@ -29618,13 +30098,62 @@ var workchatPlugin = {
29618
30098
  },
29619
30099
  sendMedia: async (ctx) => {
29620
30100
  const account = resolveOutboundAccount(ctx.cfg, ctx.accountId);
29621
- const text = ctx.text?.trim() ? ctx.text : ctx.mediaUrl ?? "";
29622
- const result = await pushWorkChatMessage({
29623
- 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({
29624
30106
  to: ctx.to,
29625
- 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 };
29626
30113
  });
29627
- 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
+ }
29628
30157
  }
29629
30158
  },
29630
30159
  status: {
@@ -29705,11 +30234,13 @@ var workchatPlugin = {
29705
30234
  loginWithQrStart: async ({ accountId, force, verbose }) => {
29706
30235
  const { loadConfig } = await import("openclaw/plugin-sdk/config-runtime");
29707
30236
  const cfg = loadConfig();
29708
- const account = resolveWorkChatAccount(cfg, accountId ?? "default");
30237
+ const requestedAccountId = accountId?.trim() || listWorkChatAccountIds(cfg)[0];
30238
+ const existing = requestedAccountId ? resolveWorkChatAccount(cfg, requestedAccountId) : void 0;
30239
+ const section = cfg.channels?.["openclaw-workchat"];
29709
30240
  const result = await startWorkChatLoginWithQr({
29710
- baseUrl: account.baseUrl,
29711
- appKey: account.appKey,
29712
- appSecret: account.appSecret
30241
+ baseUrl: existing?.baseUrl || section?.baseUrl || DEFAULT_BASE_URL,
30242
+ appKey: existing?.appKey || section?.appKey?.trim() || void 0,
30243
+ appSecret: existing?.appSecret || section?.appSecret?.trim() || void 0
29713
30244
  });
29714
30245
  return {
29715
30246
  qrDataUrl: result.qrcodeUrl,
@@ -29816,6 +30347,9 @@ function assertHostCompatibility(hostVersion) {
29816
30347
  );
29817
30348
  }
29818
30349
 
30350
+ // index.ts
30351
+ init_accounts();
30352
+
29819
30353
  // node_modules/zod/v4/classic/external.js
29820
30354
  var external_exports = {};
29821
30355
  __export(external_exports, {
@@ -30633,10 +31167,10 @@ function mergeDefs(...defs) {
30633
31167
  function cloneDef(schema) {
30634
31168
  return mergeDefs(schema._zod.def);
30635
31169
  }
30636
- function getElementAtPath(obj, path6) {
30637
- if (!path6)
31170
+ function getElementAtPath(obj, path8) {
31171
+ if (!path8)
30638
31172
  return obj;
30639
- return path6.reduce((acc, key) => acc?.[key], obj);
31173
+ return path8.reduce((acc, key) => acc?.[key], obj);
30640
31174
  }
30641
31175
  function promiseAllObject(promisesObj) {
30642
31176
  const keys = Object.keys(promisesObj);
@@ -30976,11 +31510,11 @@ function explicitlyAborted(x, startIndex = 0) {
30976
31510
  }
30977
31511
  return false;
30978
31512
  }
30979
- function prefixIssues(path6, issues) {
31513
+ function prefixIssues(path8, issues) {
30980
31514
  return issues.map((iss) => {
30981
31515
  var _a3;
30982
31516
  (_a3 = iss).path ?? (_a3.path = []);
30983
- iss.path.unshift(path6);
31517
+ iss.path.unshift(path8);
30984
31518
  return iss;
30985
31519
  });
30986
31520
  }
@@ -31442,16 +31976,16 @@ function flattenError(error62, mapper = (issue2) => issue2.message) {
31442
31976
  }
31443
31977
  function formatError(error62, mapper = (issue2) => issue2.message) {
31444
31978
  const fieldErrors = { _errors: [] };
31445
- const processError = (error63, path6 = []) => {
31979
+ const processError = (error63, path8 = []) => {
31446
31980
  for (const issue2 of error63.issues) {
31447
31981
  if (issue2.code === "invalid_union" && issue2.errors.length) {
31448
- issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
31982
+ issue2.errors.map((issues) => processError({ issues }, [...path8, ...issue2.path]));
31449
31983
  } else if (issue2.code === "invalid_key") {
31450
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
31984
+ processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
31451
31985
  } else if (issue2.code === "invalid_element") {
31452
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
31986
+ processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
31453
31987
  } else {
31454
- const fullpath = [...path6, ...issue2.path];
31988
+ const fullpath = [...path8, ...issue2.path];
31455
31989
  if (fullpath.length === 0) {
31456
31990
  fieldErrors._errors.push(mapper(issue2));
31457
31991
  } else {
@@ -31490,17 +32024,17 @@ function formatError(error62, mapper = (issue2) => issue2.message) {
31490
32024
  }
31491
32025
  function treeifyError(error62, mapper = (issue2) => issue2.message) {
31492
32026
  const result = { errors: [] };
31493
- const processError = (error63, path6 = []) => {
32027
+ const processError = (error63, path8 = []) => {
31494
32028
  var _a3;
31495
32029
  for (const issue2 of error63.issues) {
31496
32030
  if (issue2.code === "invalid_union" && issue2.errors.length) {
31497
- issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
32031
+ issue2.errors.map((issues) => processError({ issues }, [...path8, ...issue2.path]));
31498
32032
  } else if (issue2.code === "invalid_key") {
31499
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
32033
+ processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
31500
32034
  } else if (issue2.code === "invalid_element") {
31501
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
32035
+ processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
31502
32036
  } else {
31503
- const fullpath = [...path6, ...issue2.path];
32037
+ const fullpath = [...path8, ...issue2.path];
31504
32038
  if (fullpath.length === 0) {
31505
32039
  result.errors.push(mapper(issue2));
31506
32040
  continue;
@@ -31539,8 +32073,8 @@ function treeifyError(error62, mapper = (issue2) => issue2.message) {
31539
32073
  }
31540
32074
  function toDotPath(_path) {
31541
32075
  const segs = [];
31542
- const path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
31543
- for (const seg of path6) {
32076
+ const path8 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
32077
+ for (const seg of path8) {
31544
32078
  if (typeof seg === "number")
31545
32079
  segs.push(`[${seg}]`);
31546
32080
  else if (typeof seg === "symbol")
@@ -48669,13 +49203,13 @@ function resolveRef(ref, ctx) {
48669
49203
  if (!ref.startsWith("#")) {
48670
49204
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
48671
49205
  }
48672
- const path6 = ref.slice(1).split("/").filter(Boolean);
48673
- if (path6.length === 0) {
49206
+ const path8 = ref.slice(1).split("/").filter(Boolean);
49207
+ if (path8.length === 0) {
48674
49208
  return ctx.rootSchema;
48675
49209
  }
48676
49210
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
48677
- if (path6[0] === defsKey) {
48678
- 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]);
48679
49213
  if (!key || !ctx.defs[key]) {
48680
49214
  throw new Error(`Reference not found: ${ref}`);
48681
49215
  }
@@ -49309,21 +49843,21 @@ function visit(schema, fnOrHandlers) {
49309
49843
  const h = fnOrHandlers[node2._zod.def.type];
49310
49844
  return h ? h(node2, rewritten) : node2;
49311
49845
  };
49312
- const cache = /* @__PURE__ */ new Map();
49846
+ const cache2 = /* @__PURE__ */ new Map();
49313
49847
  function run(s) {
49314
- const cached2 = cache.get(s);
49848
+ const cached2 = cache2.get(s);
49315
49849
  if (cached2 === RESOLVING) {
49316
49850
  return new $ZodLazy({
49317
49851
  type: "lazy",
49318
- getter: () => cache.get(s)
49852
+ getter: () => cache2.get(s)
49319
49853
  });
49320
49854
  }
49321
49855
  if (cached2 !== void 0)
49322
49856
  return cached2;
49323
- cache.set(s, RESOLVING);
49857
+ cache2.set(s, RESOLVING);
49324
49858
  const inner = mapInner(s);
49325
49859
  const mapped = fn(inner, inner !== s);
49326
- cache.set(s, mapped);
49860
+ cache2.set(s, mapped);
49327
49861
  return mapped;
49328
49862
  }
49329
49863
  function mapInner(s) {
@@ -49552,6 +50086,9 @@ var openclaw_workchat_default = {
49552
50086
  configSchema: buildChannelConfigSchema(WorkChatConfigSchema),
49553
50087
  register(api) {
49554
50088
  assertHostCompatibility(api.runtime?.version);
50089
+ void migrateLegacyDefaultWorkChatAccount().catch((err) => {
50090
+ console.error("[openclaw-workchat] legacy default account migration failed:", err);
50091
+ });
49555
50092
  api.registerChannel({ plugin: workchatPlugin });
49556
50093
  }
49557
50094
  };