openclaw-quiubo 2.6.21 → 2.6.24

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/README.md CHANGED
@@ -8,6 +8,12 @@ OpenClaw channel plugin for [Quiubo](https://quiubo.io) — chat with AI assista
8
8
  npm install openclaw-quiubo
9
9
  ```
10
10
 
11
+ ## Updating
12
+
13
+ ```bash
14
+ openclaw plugins update openclaw-quiubo
15
+ ```
16
+
11
17
  ## Setup
12
18
 
13
19
  ```bash
package/dist/index.js CHANGED
@@ -8952,11 +8952,20 @@ function getQuiuboRuntime() {
8952
8952
  }
8953
8953
 
8954
8954
  // src/channel.ts
8955
- import { readFile as readFile2 } from "fs/promises";
8956
- import { basename } from "path";
8955
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
8956
+ import { basename, join as join2 } from "path";
8957
8957
 
8958
8958
  // src/api.ts
8959
8959
  import { randomUUID } from "crypto";
8960
+ var QuiuboApiError = class extends Error {
8961
+ constructor(status, statusText, body) {
8962
+ super(`Quiubo API error: ${status} ${statusText} - ${body}`);
8963
+ this.status = status;
8964
+ this.statusText = statusText;
8965
+ this.body = body;
8966
+ this.name = "QuiuboApiError";
8967
+ }
8968
+ };
8960
8969
  var QuiuboApiClient = class {
8961
8970
  baseUrl;
8962
8971
  apiKey;
@@ -8977,7 +8986,7 @@ var QuiuboApiClient = class {
8977
8986
  });
8978
8987
  if (!response.ok) {
8979
8988
  const errorBody = await response.text();
8980
- throw new Error(`Quiubo API error: ${response.status} ${response.statusText} - ${errorBody}`);
8989
+ throw new QuiuboApiError(response.status, response.statusText, errorBody);
8981
8990
  }
8982
8991
  if (response.status === 204) {
8983
8992
  return void 0;
@@ -13256,14 +13265,36 @@ var AgentKeyManager = class {
13256
13265
  };
13257
13266
 
13258
13267
  // src/channel.ts
13259
- function loadOrGenerateKeys(cfg) {
13268
+ var KEYS_DIR = join2(process.env.HOME ?? process.env.USERPROFILE ?? "", ".openclaw", "cron");
13269
+ async function loadOrGenerateKeys(cfg, accountId) {
13270
+ const seedFile = join2(KEYS_DIR, `quiubo-keys-${accountId}.json`);
13271
+ try {
13272
+ const raw = await readFile2(seedFile, "utf-8");
13273
+ const { seed: seedB642 } = JSON.parse(raw);
13274
+ if (seedB642) {
13275
+ const seed = new Uint8Array(Buffer.from(seedB642, "base64"));
13276
+ return { ...deriveKeypairFromSeed(seed), freshlyGenerated: false };
13277
+ }
13278
+ } catch {
13279
+ }
13260
13280
  if (cfg.keys?.seed) {
13261
13281
  const seed = new Uint8Array(Buffer.from(cfg.keys.seed, "base64"));
13262
- return deriveKeypairFromSeed(seed);
13282
+ const keys2 = deriveKeypairFromSeed(seed);
13283
+ await persistSeed(seedFile, cfg.keys.seed);
13284
+ return { ...keys2, freshlyGenerated: false };
13263
13285
  }
13264
13286
  const keys = generateAgentKeypair();
13265
- cfg.keys = { seed: Buffer.from(keys.seed).toString("base64") };
13266
- return keys;
13287
+ const seedB64 = Buffer.from(keys.seed).toString("base64");
13288
+ cfg.keys = { seed: seedB64 };
13289
+ await persistSeed(seedFile, seedB64);
13290
+ return { ...keys, freshlyGenerated: true };
13291
+ }
13292
+ async function persistSeed(seedFile, seedB64) {
13293
+ try {
13294
+ await mkdir2(KEYS_DIR, { recursive: true });
13295
+ await writeFile2(seedFile, JSON.stringify({ seed: seedB64 }), "utf-8");
13296
+ } catch {
13297
+ }
13267
13298
  }
13268
13299
  async function readMdAttachments(mediaUrls, source, log, accountId) {
13269
13300
  const attachments = [];
@@ -13623,8 +13654,12 @@ var quiuboPlugin = {
13623
13654
  log?.info?.(`[${accountId}] [outbound:sendText] sent to group ${groupId} (realtime=${apiResp?.realtimeDelivered})`);
13624
13655
  return { ok: true };
13625
13656
  } catch (error) {
13657
+ if (error instanceof QuiuboApiError) {
13658
+ log?.error?.(`[${accountId}] [outbound:sendText] SEND FAILED [${error.status}] group=${groupId} \u2014 ${error.body}`);
13659
+ return { ok: false, error: `${error.status}: ${error.body}` };
13660
+ }
13626
13661
  const msg = error instanceof Error ? error.message : String(error);
13627
- log?.error?.(`[${accountId}] [outbound:sendText] failed: ${msg}`);
13662
+ log?.error?.(`[${accountId}] [outbound:sendText] SEND FAILED [network] group=${groupId} \u2014 ${msg}`);
13628
13663
  return { ok: false, error: msg };
13629
13664
  }
13630
13665
  },
@@ -13675,8 +13710,12 @@ var quiuboPlugin = {
13675
13710
  log?.info?.(`[${accountId}] [outbound:sendMedia] sent to group ${groupId} (realtime=${apiResp?.realtimeDelivered})`);
13676
13711
  return { ok: true };
13677
13712
  } catch (error) {
13713
+ if (error instanceof QuiuboApiError) {
13714
+ log?.error?.(`[${accountId}] [outbound:sendMedia] SEND FAILED [${error.status}] group=${groupId} \u2014 ${error.body}`);
13715
+ return { ok: false, error: `${error.status}: ${error.body}` };
13716
+ }
13678
13717
  const msg = error instanceof Error ? error.message : String(error);
13679
- log?.error?.(`[${accountId}] [outbound:sendMedia] failed: ${msg}`);
13718
+ log?.error?.(`[${accountId}] [outbound:sendMedia] SEND FAILED [network] group=${groupId} \u2014 ${msg}`);
13680
13719
  return { ok: false, error: msg };
13681
13720
  }
13682
13721
  }
@@ -13692,8 +13731,15 @@ var quiuboPlugin = {
13692
13731
  startAccount: async (ctx) => {
13693
13732
  const { cfg, accountId, abortSignal, log } = ctx;
13694
13733
  const quiuboConfig = getChannelConfig(cfg)?.accounts?.[accountId];
13695
- if (!quiuboConfig?.apiKey || !quiuboConfig?.botIdentityId) {
13696
- log?.warn?.(`[${accountId}] Quiubo: missing apiKey or botIdentityId, skipping`);
13734
+ if (!quiuboConfig) {
13735
+ log?.error?.(`[${accountId}] Quiubo: [FATAL] STARTUP FAILED \u2014 no config found for account "${accountId}". Check channels.quiubo.accounts.${accountId} in your OpenClaw config.`);
13736
+ return;
13737
+ }
13738
+ const missing = [];
13739
+ if (!quiuboConfig.apiKey) missing.push("apiKey");
13740
+ if (!quiuboConfig.botIdentityId) missing.push("botIdentityId");
13741
+ if (missing.length > 0) {
13742
+ log?.error?.(`[${accountId}] Quiubo: [FATAL] STARTUP FAILED \u2014 missing required config: ${missing.join(", ")}. Run "openclaw accounts configure quiubo" to set them.`);
13697
13743
  return;
13698
13744
  }
13699
13745
  if (quiuboConfig.enabled === false) {
@@ -13725,7 +13771,17 @@ var quiuboPlugin = {
13725
13771
  log?.info?.(`[${accountId}] Quiubo: Pusher not configured \u2014 using polling`);
13726
13772
  }
13727
13773
  } catch (error) {
13728
- log?.error?.(`[${accountId}] Quiubo: authentication failed: ${error}`);
13774
+ if (error instanceof QuiuboApiError) {
13775
+ if (error.status === 401) {
13776
+ log?.error?.(`[${accountId}] Quiubo: [FATAL] STARTUP FAILED \u2014 API key is invalid or expired (401). Regenerate your SDK API key in the Quiubo dashboard.`);
13777
+ } else if (error.status === 403) {
13778
+ log?.error?.(`[${accountId}] Quiubo: [FATAL] STARTUP FAILED \u2014 API key lacks permissions (403). Check your SDK app tier and permissions.`);
13779
+ } else {
13780
+ log?.error?.(`[${accountId}] Quiubo: [FATAL] STARTUP FAILED \u2014 authentication error (${error.status}): ${error.body}`);
13781
+ }
13782
+ } else {
13783
+ log?.error?.(`[${accountId}] Quiubo: [FATAL] STARTUP FAILED \u2014 cannot reach API at ${apiUrl}: ${error instanceof Error ? error.message : error}`);
13784
+ }
13729
13785
  return;
13730
13786
  }
13731
13787
  try {
@@ -13814,9 +13870,9 @@ var quiuboPlugin = {
13814
13870
  resolvedAgentName = match.name;
13815
13871
  log?.info?.(`[${accountId}] Quiubo: resolved agent ${match.name} (${match.id}) for heartbeat, displayName=${resolvedAgentDisplayName}`);
13816
13872
  try {
13817
- const keys = loadOrGenerateKeys(quiuboConfig);
13818
- if (!match.e2eeConfigured) {
13819
- log?.info?.(`[${accountId}] Quiubo: enrolling E2EE keys for agent ${match.id}`);
13873
+ const keys = await loadOrGenerateKeys(quiuboConfig, accountId);
13874
+ if (!match.e2eeConfigured || keys.freshlyGenerated) {
13875
+ log?.info?.(`[${accountId}] Quiubo: ${keys.freshlyGenerated ? "re-" : ""}enrolling E2EE keys for agent ${match.id}`);
13820
13876
  const { challengeId, challenge } = await client.requestKeyChallenge(match.id, {
13821
13877
  signingPublicKey: toBase64(keys.signingPublicKey),
13822
13878
  encryptionPublicKey: toBase64(keys.encryptionPublicKey),
@@ -14036,7 +14092,7 @@ var quiuboPlugin = {
14036
14092
  }
14037
14093
  await gateway.loadCursors();
14038
14094
  await gateway.start();
14039
- log?.info?.(`[${accountId}] Quiubo: gateway started`);
14095
+ log?.info?.(`[${accountId}] Quiubo: [OK] STARTUP COMPLETE \u2014 realtime=${pusherConfig ? "pusher" : "polling"}, agent=${resolvedAgentId ? resolvedAgentName : "none"}, e2ee=${keyManager ? "ready" : "off"}`);
14040
14096
  return new Promise((resolve) => {
14041
14097
  const cleanup = () => {
14042
14098
  log?.info?.(`[${accountId}] Quiubo: gateway stopping`);
@@ -14081,9 +14137,9 @@ function resolveOutboundGroupId(ctx) {
14081
14137
  async function resolveAnnounceGroupId(accountId, log) {
14082
14138
  try {
14083
14139
  const { readFile: readFile3 } = await import("node:fs/promises");
14084
- const { join: join2 } = await import("node:path");
14140
+ const { join: join3 } = await import("node:path");
14085
14141
  const homeDir = process.env.HOME ?? process.env.USERPROFILE ?? "";
14086
- const cronPath = join2(homeDir, ".openclaw", "cron", "jobs.json");
14142
+ const cronPath = join3(homeDir, ".openclaw", "cron", "jobs.json");
14087
14143
  const raw = await readFile3(cronPath, "utf-8");
14088
14144
  const parsed = JSON.parse(raw);
14089
14145
  const jobs = parsed?.jobs ?? [];
@@ -14105,9 +14161,9 @@ async function resolveAnnounceGroupId(accountId, log) {
14105
14161
  async function getActivityData(runtime2, log, agentId) {
14106
14162
  try {
14107
14163
  const { readFile: readFile3 } = await import("node:fs/promises");
14108
- const { join: join2 } = await import("node:path");
14164
+ const { join: join3 } = await import("node:path");
14109
14165
  const homeDir = process.env.HOME ?? process.env.USERPROFILE ?? "";
14110
- const cronPath = join2(homeDir, ".openclaw", "cron", "jobs.json");
14166
+ const cronPath = join3(homeDir, ".openclaw", "cron", "jobs.json");
14111
14167
  log?.info?.(`getActivityData: reading ${cronPath} (agentId=${agentId})`);
14112
14168
  const raw = await readFile3(cronPath, "utf-8");
14113
14169
  const parsed = JSON.parse(raw);
@@ -14253,6 +14309,13 @@ async function routeInboundMessage(opts) {
14253
14309
  log?.warn?.(`[${accountId}] Quiubo: skipping outbound \u2014 agent lacks send_messages scope for group ${groupId}`);
14254
14310
  return;
14255
14311
  }
14312
+ if (payload.text) {
14313
+ payload.text = payload.text.replace(/\n*Reasoning:\n_[^_]*_\n*/g, "").trim();
14314
+ }
14315
+ if (!payload.text && mdAttachments.length === 0) {
14316
+ log?.debug?.(`[${accountId}] Quiubo: skipping empty reply after reasoning strip`);
14317
+ return;
14318
+ }
14256
14319
  log?.info?.(`[${accountId}] Quiubo: delivering ${info.kind} reply [${payload.text?.length ?? 0} chars, ${mdAttachments.length} attachments]`);
14257
14320
  try {
14258
14321
  let ciphertextOut;
@@ -14282,7 +14345,11 @@ async function routeInboundMessage(opts) {
14282
14345
  log?.info?.(`[${accountId}] Quiubo: reply sent to group ${groupId} (realtime=${apiResp?.realtimeDelivered})`);
14283
14346
  }
14284
14347
  } catch (error) {
14285
- log?.error?.(`[${accountId}] Quiubo: failed to send reply: ${error}`);
14348
+ if (error instanceof QuiuboApiError) {
14349
+ log?.error?.(`[${accountId}] Quiubo: SEND FAILED [${error.status}] group=${groupId} \u2014 ${error.body}`);
14350
+ } else {
14351
+ log?.error?.(`[${accountId}] Quiubo: SEND FAILED [network] group=${groupId} \u2014 ${error instanceof Error ? error.message : error}`);
14352
+ }
14286
14353
  }
14287
14354
  },
14288
14355
  // eslint-disable-next-line @typescript-eslint/no-explicit-any