@coolclaw/coolclaw 1.0.15 → 1.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,68 @@
1
+ // flavors/coolclaw.flavor.json
2
+ var coolclaw_flavor_default = {
3
+ productKey: "coolclaw",
4
+ displayName: "CoolClaw",
5
+ npmScope: "@coolclaw",
6
+ channelPackageName: "@coolclaw/coolclaw",
7
+ cliPackageName: "@coolclaw/coolclaw-cli",
8
+ skillsPackageName: "@coolclaw/coolclaw-skills",
9
+ channelId: "coolclaw",
10
+ pluginId: "coolclaw",
11
+ skillName: "coolclaw",
12
+ envPrefix: "COOLCLAW",
13
+ configDirName: "coolclaw",
14
+ defaultGatewayUrl: "https://agits-xa.baidu.com/riddle",
15
+ targetPrefix: "coolclaw"
16
+ };
17
+
18
+ // flavors/clawtopia.flavor.json
19
+ var clawtopia_flavor_default = {
20
+ productKey: "clawtopia",
21
+ displayName: "Clawtopia",
22
+ npmScope: "@clawtopia",
23
+ channelPackageName: "@clawtopia/clawtopia",
24
+ cliPackageName: "@clawtopia/clawtopia-cli",
25
+ skillsPackageName: "@clawtopia/clawtopia-skills",
26
+ channelId: "clawtopia",
27
+ pluginId: "clawtopia",
28
+ skillName: "clawtopia",
29
+ envPrefix: "CLAWTOPIA",
30
+ configDirName: "clawtopia",
31
+ defaultGatewayUrl: "https://clawtopia.baidu.com/riddle",
32
+ targetPrefix: "clawtopia"
33
+ };
34
+
35
+ // src/flavor-build.ts
36
+ var BUILT_PRODUCT_FLAVOR = "coolclaw";
37
+
38
+ // src/flavor.ts
39
+ var FLAVORS = {
40
+ coolclaw: coolclaw_flavor_default,
41
+ clawtopia: clawtopia_flavor_default
42
+ };
43
+ function getFlavorByKey(key) {
44
+ const flavor = FLAVORS[key];
45
+ if (!flavor) {
46
+ throw new Error(`Unknown PRODUCT_FLAVOR: ${key}`);
47
+ }
48
+ return flavor;
49
+ }
50
+ function activeFlavor(env = process.env) {
51
+ return getFlavorByKey(env.PRODUCT_FLAVOR ?? BUILT_PRODUCT_FLAVOR);
52
+ }
53
+ function resolveFlavor(input) {
54
+ if (!input) return activeFlavor();
55
+ return typeof input === "string" ? getFlavorByKey(input) : input;
56
+ }
57
+
1
58
  // src/binding.ts
2
59
  import { mkdir, readFile, rename, writeFile, chmod } from "fs/promises";
3
60
  import { homedir } from "os";
4
61
  import path from "path";
5
62
  import { fileURLToPath } from "url";
6
- function defaultBindingFile(home = homedir()) {
7
- return path.join(home, ".config", "coolclaw", "agent_binding.json");
63
+ function defaultBindingFile(home = homedir(), flavorInput) {
64
+ const flavor = resolveFlavor(flavorInput);
65
+ return path.join(home, ".config", flavor.configDirName, "agent_binding.json");
8
66
  }
9
67
  async function readTokenRef(tokenRef) {
10
68
  if (!tokenRef) return void 0;
@@ -358,14 +416,19 @@ function isRecord2(value) {
358
416
  // src/inbound.ts
359
417
  var ARENA_REPORT_SHARE_NOTIFY_TYPE = "ARENA_REPORT_SHARE_REQUEST";
360
418
  var ARENA_MODEL_QUERY_NOTIFY_TYPE = "ARENA_MODEL_QUERY_REQUEST";
419
+ var ARENA_VOICE_SELECT_NOTIFY_TYPE = "ARENA_VOICE_SELECT_REQUEST";
361
420
  var REPORT_SHARE_DEDUPE_LIMIT = 500;
362
421
  var MODEL_QUERY_DEDUPE_LIMIT = 500;
422
+ var VOICE_SELECT_DEDUPE_LIMIT = 500;
363
423
  var processedArenaReportShareEventIds = /* @__PURE__ */ new Set();
364
424
  var processedArenaReportShareEventOrder = [];
365
425
  var inFlightArenaReportShareEventIds = /* @__PURE__ */ new Map();
366
426
  var processedArenaModelQueryEventIds = /* @__PURE__ */ new Set();
367
427
  var processedArenaModelQueryEventOrder = [];
368
428
  var inFlightArenaModelQueryEventIds = /* @__PURE__ */ new Map();
429
+ var processedArenaVoiceSelectEventIds = /* @__PURE__ */ new Set();
430
+ var processedArenaVoiceSelectEventOrder = [];
431
+ var inFlightArenaVoiceSelectEventIds = /* @__PURE__ */ new Map();
369
432
  function mapInboundFrame(frame) {
370
433
  if (frame.type === "PRIVATE_MESSAGE") {
371
434
  const payload = assertPrivatePayload(frame.payload);
@@ -441,6 +504,13 @@ async function handleInboundFrame(input) {
441
504
  inFlight: inFlightArenaModelQueryEventIds,
442
505
  remember: rememberArenaModelQueryEventId
443
506
  };
507
+ } else if (isArenaVoiceSelectEnvelope(envelope)) {
508
+ dedupeState = {
509
+ eventId: String(envelope.metadata.eventId),
510
+ processed: processedArenaVoiceSelectEventIds,
511
+ inFlight: inFlightArenaVoiceSelectEventIds,
512
+ remember: rememberArenaVoiceSelectEventId
513
+ };
444
514
  }
445
515
  if (dedupeState) {
446
516
  if (dedupeState.processed.has(dedupeState.eventId)) {
@@ -493,6 +563,9 @@ function isArenaReportShareEnvelope(envelope) {
493
563
  function isArenaModelQueryEnvelope(envelope) {
494
564
  return envelope.metadata?.arenaModelQueryRequest === true && typeof envelope.metadata.eventId === "string" && envelope.metadata.eventId.length > 0 && typeof envelope.metadata.callbackUrl === "string" && envelope.metadata.callbackUrl.length > 0;
495
565
  }
566
+ function isArenaVoiceSelectEnvelope(envelope) {
567
+ return envelope.metadata?.arenaVoiceSelectRequest === true && typeof envelope.metadata.eventId === "string" && envelope.metadata.eventId.length > 0 && typeof envelope.metadata.callbackUrl === "string" && envelope.metadata.callbackUrl.length > 0;
568
+ }
496
569
  function mapNotificationFrame(frame) {
497
570
  const payload = isRecord3(frame.payload) ? frame.payload : {};
498
571
  const seq = typeof payload.seq === "number" ? payload.seq : void 0;
@@ -534,6 +607,9 @@ function mapNotificationFrame(frame) {
534
607
  if (notifyType === ARENA_MODEL_QUERY_NOTIFY_TYPE) {
535
608
  return mapArenaModelQueryFrame(frame, payload, seq);
536
609
  }
610
+ if (notifyType === ARENA_VOICE_SELECT_NOTIFY_TYPE) {
611
+ return mapArenaVoiceSelectFrame(frame, payload, seq);
612
+ }
537
613
  const postId = payload.postId != null ? String(payload.postId) : "";
538
614
  return {
539
615
  id: frame.id,
@@ -548,6 +624,43 @@ function mapNotificationFrame(frame) {
548
624
  }
549
625
  return null;
550
626
  }
627
+ function mapArenaVoiceSelectFrame(frame, payload, seq) {
628
+ const voicePayload = isRecord3(payload.payload) ? payload.payload : {};
629
+ const eventId = firstText(payload.eventId, voicePayload.eventId) ?? frame.id;
630
+ const traceId = typeof payload.traceId === "string" ? payload.traceId : "";
631
+ const roomId = Number(voicePayload.roomId ?? 0);
632
+ const seatNumber = Number(voicePayload.seatNumber ?? 0);
633
+ const seatEpoch = voicePayload.seatEpoch != null ? String(voicePayload.seatEpoch) : "";
634
+ const callbackUrl = typeof voicePayload.callbackUrl === "string" ? voicePayload.callbackUrl : "";
635
+ if (!callbackUrl || !roomId || !seatNumber || !seatEpoch) {
636
+ return null;
637
+ }
638
+ const deadlineEpochMs = Number(voicePayload.deadlineEpochMs ?? 0);
639
+ const voiceOptions = Array.isArray(voicePayload.voiceOptions) ? voicePayload.voiceOptions.filter(isRecord3).map((option) => option) : [];
640
+ return {
641
+ id: eventId,
642
+ channel: "coolclaw",
643
+ conversationId: `notification:arena_voice_select:${eventId}`,
644
+ text: "/arena-voice-select",
645
+ messageType: frame.type,
646
+ seq,
647
+ shouldReply: true,
648
+ metadata: {
649
+ sourceFrameId: frame.id,
650
+ payload: frame.payload,
651
+ voiceSelectPayload: voicePayload,
652
+ arenaVoiceSelectRequest: true,
653
+ eventId,
654
+ traceId,
655
+ roomId,
656
+ seatNumber,
657
+ seatEpoch,
658
+ callbackUrl,
659
+ deadlineEpochMs,
660
+ voiceOptions
661
+ }
662
+ };
663
+ }
551
664
  function mapArenaModelQueryFrame(frame, payload, seq) {
552
665
  const eventId = typeof payload.eventId === "string" && payload.eventId.length > 0 ? payload.eventId : frame.id;
553
666
  const traceId = typeof payload.traceId === "string" ? payload.traceId : "";
@@ -583,6 +696,14 @@ function mapArenaModelQueryFrame(frame, payload, seq) {
583
696
  }
584
697
  };
585
698
  }
699
+ function firstText(...values) {
700
+ for (const value of values) {
701
+ if (typeof value === "string" && value.length > 0) {
702
+ return value;
703
+ }
704
+ }
705
+ return null;
706
+ }
586
707
  function mapArenaReportShareFrame(frame, payload, seq) {
587
708
  const eventId = typeof payload.eventId === "string" && payload.eventId.length > 0 ? payload.eventId : frame.id;
588
709
  const traceId = typeof payload.traceId === "string" ? payload.traceId : "";
@@ -624,6 +745,15 @@ function rememberArenaModelQueryEventId(eventId) {
624
745
  if (expired) processedArenaModelQueryEventIds.delete(expired);
625
746
  }
626
747
  }
748
+ function rememberArenaVoiceSelectEventId(eventId) {
749
+ if (processedArenaVoiceSelectEventIds.has(eventId)) return;
750
+ processedArenaVoiceSelectEventIds.add(eventId);
751
+ processedArenaVoiceSelectEventOrder.push(eventId);
752
+ while (processedArenaVoiceSelectEventOrder.length > VOICE_SELECT_DEDUPE_LIMIT) {
753
+ const expired = processedArenaVoiceSelectEventOrder.shift();
754
+ if (expired) processedArenaVoiceSelectEventIds.delete(expired);
755
+ }
756
+ }
627
757
  function mapGameEventFrame(frame, payload) {
628
758
  const eventType = typeof payload.eventType === "string" ? payload.eventType : "UNKNOWN";
629
759
  const agentTask = normalizeAgentTask(payload.agentTask);
@@ -782,15 +912,16 @@ var TargetParseError = class extends Error {
782
912
  this.name = "TargetParseError";
783
913
  }
784
914
  };
785
- function parseCoolclawTarget(raw) {
786
- const normalized = normalizeCoolclawTarget(raw);
915
+ function parseCoolclawTarget(raw, flavorInput) {
916
+ const flavor = resolveFlavor(flavorInput);
917
+ const normalized = normalizeCoolclawTarget(raw, flavor);
787
918
  const parts = normalized.split(":");
788
919
  if (parts.length !== 3) {
789
- throw new TargetParseError(`Invalid CoolClaw target: ${raw}`);
920
+ throw new TargetParseError(`Invalid ${flavor.displayName} target: ${raw}`);
790
921
  }
791
922
  const [channel, type, id] = parts;
792
- if (channel !== "coolclaw" || id.length === 0) {
793
- throw new TargetParseError(`Invalid CoolClaw target: ${raw}`);
923
+ if (channel !== flavor.targetPrefix || id.length === 0) {
924
+ throw new TargetParseError(`Invalid ${flavor.displayName} target: ${raw}`);
794
925
  }
795
926
  if (type === "human") {
796
927
  return { kind: "private", userType: "HUMAN", userId: id };
@@ -801,9 +932,10 @@ function parseCoolclawTarget(raw) {
801
932
  if (type === "group") {
802
933
  return { kind: "group", groupId: id };
803
934
  }
804
- throw new TargetParseError(`Invalid CoolClaw target type: ${type}`);
935
+ throw new TargetParseError(`Invalid ${flavor.displayName} target type: ${type}`);
805
936
  }
806
- function normalizeCoolclawTarget(raw) {
937
+ function normalizeCoolclawTarget(raw, flavorInput) {
938
+ resolveFlavor(flavorInput);
807
939
  const trimmed = raw.trim();
808
940
  const parts = trimmed.split(":");
809
941
  if (parts.length !== 3) {
@@ -812,25 +944,27 @@ function normalizeCoolclawTarget(raw) {
812
944
  const [channel, type, id] = parts;
813
945
  return `${channel.toLowerCase()}:${type.toLowerCase()}:${id.trim()}`;
814
946
  }
815
- function inferCoolclawTargetChatType(raw) {
947
+ function inferCoolclawTargetChatType(raw, flavorInput) {
816
948
  try {
817
- const target = parseCoolclawTarget(raw);
949
+ const target = parseCoolclawTarget(raw, flavorInput);
818
950
  return target.kind === "private" ? "direct" : "group";
819
951
  } catch {
820
952
  return void 0;
821
953
  }
822
954
  }
823
- function isCoolclawTargetId(raw, normalized = normalizeCoolclawTarget(raw)) {
955
+ function isCoolclawTargetId(raw, normalized = normalizeCoolclawTarget(raw), flavorInput) {
956
+ const flavor = resolveFlavor(flavorInput);
824
957
  try {
825
- parseCoolclawTarget(normalized);
826
- return normalized.startsWith("coolclaw:");
958
+ parseCoolclawTarget(normalized, flavor);
959
+ return normalized.startsWith(`${flavor.targetPrefix}:`);
827
960
  } catch {
828
961
  return false;
829
962
  }
830
963
  }
831
- async function resolveCoolclawMessagingTarget(raw, preferredKind) {
832
- const normalized = normalizeCoolclawTarget(raw);
833
- const target = parseCoolclawTarget(normalized);
964
+ async function resolveCoolclawMessagingTarget(raw, preferredKind, flavorInput) {
965
+ const flavor = resolveFlavor(flavorInput);
966
+ const normalized = normalizeCoolclawTarget(raw, flavor);
967
+ const target = parseCoolclawTarget(normalized, flavor);
834
968
  const kind = target.kind === "private" ? "user" : "group";
835
969
  if (preferredKind && preferredKind !== kind) {
836
970
  return null;
@@ -1207,6 +1341,223 @@ function isRecord4(value) {
1207
1341
  return typeof value === "object" && value !== null && !Array.isArray(value);
1208
1342
  }
1209
1343
 
1344
+ // src/arena-voice-select.ts
1345
+ var FALLBACK_REASON = "\u672A\u89E3\u6790\u5230\u6A21\u578B\u97F3\u8272\u504F\u597D\uFF0C\u4EA4\u7531\u540E\u7AEF\u515C\u5E95\u9009\u62E9\u3002";
1346
+ var DEFAULT_REASON = "\u57FA\u4E8E\u6A21\u578B\u8F93\u51FA\u9009\u62E9\u5019\u9009\u73A9\u5BB6\u97F3\u8272\u3002";
1347
+ function parseArenaVoiceSelection(rawText, voiceOptions) {
1348
+ const structured = parseStructuredVoiceSelection(rawText, voiceOptions);
1349
+ if (structured) return structured;
1350
+ const topVoiceIds = extractVoiceIdsFromText(rawText, voiceOptions);
1351
+ return {
1352
+ topVoiceIds,
1353
+ reason: topVoiceIds.length > 0 ? DEFAULT_REASON : FALLBACK_REASON
1354
+ };
1355
+ }
1356
+ async function submitArenaVoiceSelectCallback(input) {
1357
+ const start = Date.now();
1358
+ const rawHash = input.rawText ? sha256Hex(input.rawText) : "";
1359
+ const rawPreview = rawResponsePreview(input.rawText) ?? "";
1360
+ const selection = parseArenaVoiceSelection(input.rawText, input.meta.voiceOptions);
1361
+ try {
1362
+ if (!input.meta.callbackUrl || !input.token) {
1363
+ input.log?.warn?.(
1364
+ `[ARENA-VOICE] callback skipped eventId=${input.meta.eventId} reason=missing_callback_or_token rawHash=${rawHash} rawPreview=${rawPreview}`
1365
+ );
1366
+ return { ok: false, error: "missing_callback_or_token" };
1367
+ }
1368
+ const fetchImpl = input.fetchImpl ?? globalThis.fetch;
1369
+ if (typeof fetchImpl !== "function") {
1370
+ input.log?.warn?.(`[ARENA-VOICE] callback skipped eventId=${input.meta.eventId} reason=fetch_unavailable`);
1371
+ return { ok: false, error: "fetch_unavailable" };
1372
+ }
1373
+ const response = await fetchImpl(input.meta.callbackUrl, {
1374
+ method: "POST",
1375
+ headers: {
1376
+ Authorization: `Bearer ${input.token}`,
1377
+ "Content-Type": "application/json"
1378
+ },
1379
+ body: JSON.stringify({
1380
+ eventId: input.meta.eventId,
1381
+ roomId: input.meta.roomId,
1382
+ seatNumber: input.meta.seatNumber,
1383
+ seatEpoch: input.meta.seatEpoch,
1384
+ topVoiceIds: selection.topVoiceIds,
1385
+ reason: selection.reason
1386
+ })
1387
+ });
1388
+ const body = await readJsonObject2(response);
1389
+ const data = isRecord5(body.data) ? body.data : body;
1390
+ const accepted = data.accepted === true;
1391
+ const result = { ok: response.ok, status: response.status, accepted };
1392
+ const level = response.ok ? "info" : "warn";
1393
+ input.log?.[level]?.(
1394
+ `[ARENA-VOICE] callback status=${response.status} accepted=${accepted} eventId=${input.meta.eventId} roomId=${input.meta.roomId} seatEpoch=${input.meta.seatEpoch} selected=${selection.topVoiceIds.length} elapsedMs=${Date.now() - start} rawHash=${rawHash} rawPreview=${rawPreview}`
1395
+ );
1396
+ return result;
1397
+ } catch (error) {
1398
+ const message = error instanceof Error ? error.message : String(error);
1399
+ input.log?.warn?.(
1400
+ `[ARENA-VOICE] callback failed eventId=${input.meta.eventId} roomId=${input.meta.roomId} seatEpoch=${input.meta.seatEpoch} elapsedMs=${Date.now() - start} err=${message} rawHash=${rawHash} rawPreview=${rawPreview}`
1401
+ );
1402
+ return { ok: false, error: message };
1403
+ }
1404
+ }
1405
+ function createArenaVoiceSelectReplyCollector(input) {
1406
+ const blocks = [];
1407
+ let submitted = false;
1408
+ const submit = input.submit ?? ((rawText) => submitArenaVoiceSelectCallback({
1409
+ meta: input.meta,
1410
+ token: input.token,
1411
+ rawText,
1412
+ log: input.log
1413
+ }));
1414
+ async function submitOnce(rawText, reason) {
1415
+ if (submitted) return;
1416
+ submitted = true;
1417
+ const parsed = parseArenaVoiceSelection(rawText, input.meta.voiceOptions);
1418
+ input.log?.info?.(
1419
+ `[ARENA-VOICE] dispatch submit reason=${reason} eventId=${input.meta.eventId} roomId=${input.meta.roomId} seatEpoch=${input.meta.seatEpoch} blocks=${blocks.length} selected=${parsed.topVoiceIds.length}`
1420
+ );
1421
+ await submit(rawText);
1422
+ }
1423
+ return {
1424
+ async deliver(text) {
1425
+ if (submitted || !text) return;
1426
+ blocks.push(text);
1427
+ const full = blocks.join("");
1428
+ const structured = parseStructuredVoiceSelection(full, input.meta.voiceOptions);
1429
+ input.log?.info?.(
1430
+ `[ARENA-VOICE] dispatch block eventId=${input.meta.eventId} roomId=${input.meta.roomId} seatEpoch=${input.meta.seatEpoch} blocks=${blocks.length} parsed=${structured?.topVoiceIds.length ? "true" : "false"}`
1431
+ );
1432
+ if (structured?.topVoiceIds.length) {
1433
+ await submitOnce(full, "parsed");
1434
+ }
1435
+ },
1436
+ async finalize() {
1437
+ if (submitted) return;
1438
+ await submitOnce(blocks.join(""), "final");
1439
+ }
1440
+ };
1441
+ }
1442
+ function parseStructuredVoiceSelection(rawText, voiceOptions) {
1443
+ if (!rawText) return null;
1444
+ const body = parseFirstJsonObject(rawText);
1445
+ if (!body) return null;
1446
+ const rawIds = Array.isArray(body.topVoiceIds) ? body.topVoiceIds : [];
1447
+ const topVoiceIds = sanitizeVoiceIds(rawIds, voiceOptions);
1448
+ const reason = normalizeReason(body.reason);
1449
+ if (topVoiceIds.length === 0 && !reason) return null;
1450
+ return {
1451
+ topVoiceIds,
1452
+ reason: reason || (topVoiceIds.length > 0 ? DEFAULT_REASON : FALLBACK_REASON)
1453
+ };
1454
+ }
1455
+ function extractVoiceIdsFromText(rawText, voiceOptions) {
1456
+ if (!rawText) return [];
1457
+ const allowed = allowedVoiceIds(voiceOptions);
1458
+ const result = [];
1459
+ const seen = /* @__PURE__ */ new Set();
1460
+ const regex = /\b\d{3,8}\b/g;
1461
+ for (const match of rawText.matchAll(regex)) {
1462
+ const voiceId = match[0];
1463
+ if (!allowed.has(voiceId) || seen.has(voiceId)) continue;
1464
+ seen.add(voiceId);
1465
+ result.push(voiceId);
1466
+ if (result.length >= 3) break;
1467
+ }
1468
+ return result;
1469
+ }
1470
+ function sanitizeVoiceIds(rawIds, voiceOptions) {
1471
+ const allowed = allowedVoiceIds(voiceOptions);
1472
+ const result = [];
1473
+ const seen = /* @__PURE__ */ new Set();
1474
+ for (const rawId of rawIds) {
1475
+ const voiceId = typeof rawId === "string" ? rawId.trim() : "";
1476
+ if (!voiceId || !allowed.has(voiceId) || seen.has(voiceId)) continue;
1477
+ seen.add(voiceId);
1478
+ result.push(voiceId);
1479
+ if (result.length >= 3) break;
1480
+ }
1481
+ return result;
1482
+ }
1483
+ function allowedVoiceIds(voiceOptions) {
1484
+ const ids = /* @__PURE__ */ new Set();
1485
+ if (!Array.isArray(voiceOptions)) return ids;
1486
+ for (const option of voiceOptions) {
1487
+ const voiceId = typeof option?.voiceId === "string" ? option.voiceId.trim() : "";
1488
+ if (voiceId) ids.add(voiceId);
1489
+ }
1490
+ return ids;
1491
+ }
1492
+ function normalizeReason(value) {
1493
+ if (typeof value !== "string") return "";
1494
+ return value.replace(/\s+/g, " ").trim().slice(0, 160);
1495
+ }
1496
+ function parseFirstJsonObject(rawText) {
1497
+ const direct = tryParseJsonObject(rawText.trim());
1498
+ if (direct) return direct;
1499
+ const candidates = extractJsonObjectCandidates(rawText);
1500
+ for (const candidate of candidates) {
1501
+ const parsed = tryParseJsonObject(candidate);
1502
+ if (parsed) return parsed;
1503
+ }
1504
+ return null;
1505
+ }
1506
+ function extractJsonObjectCandidates(rawText) {
1507
+ const candidates = [];
1508
+ let start = -1;
1509
+ let depth = 0;
1510
+ let inString = false;
1511
+ let escaped = false;
1512
+ for (let index = 0; index < rawText.length; index += 1) {
1513
+ const char = rawText[index];
1514
+ if (inString) {
1515
+ if (escaped) {
1516
+ escaped = false;
1517
+ } else if (char === "\\") {
1518
+ escaped = true;
1519
+ } else if (char === '"') {
1520
+ inString = false;
1521
+ }
1522
+ continue;
1523
+ }
1524
+ if (char === '"') {
1525
+ inString = true;
1526
+ continue;
1527
+ }
1528
+ if (char === "{") {
1529
+ if (depth === 0) start = index;
1530
+ depth += 1;
1531
+ } else if (char === "}" && depth > 0) {
1532
+ depth -= 1;
1533
+ if (depth === 0 && start >= 0) {
1534
+ candidates.push(rawText.slice(start, index + 1));
1535
+ start = -1;
1536
+ }
1537
+ }
1538
+ }
1539
+ return candidates;
1540
+ }
1541
+ async function readJsonObject2(response) {
1542
+ try {
1543
+ const body = await response.json();
1544
+ return isRecord5(body) ? body : {};
1545
+ } catch {
1546
+ return {};
1547
+ }
1548
+ }
1549
+ function isRecord5(value) {
1550
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1551
+ }
1552
+ function tryParseJsonObject(value) {
1553
+ try {
1554
+ const parsed = JSON.parse(value);
1555
+ return isRecord5(parsed) ? parsed : null;
1556
+ } catch {
1557
+ return null;
1558
+ }
1559
+ }
1560
+
1210
1561
  // src/ws-client.ts
1211
1562
  import WebSocket from "ws";
1212
1563
  var CoolclawWsClient = class {
@@ -1419,18 +1770,18 @@ var CoolclawWsClient = class {
1419
1770
  }
1420
1771
  };
1421
1772
  function readPingInterval(payload) {
1422
- if (!isRecord5(payload) || typeof payload.pingIntervalMs !== "number" || payload.pingIntervalMs <= 0) {
1773
+ if (!isRecord6(payload) || typeof payload.pingIntervalMs !== "number" || payload.pingIntervalMs <= 0) {
1423
1774
  return void 0;
1424
1775
  }
1425
1776
  return payload.pingIntervalMs;
1426
1777
  }
1427
1778
  function readErrorMessage(payload) {
1428
- if (isRecord5(payload) && typeof payload.message === "string") {
1779
+ if (isRecord6(payload) && typeof payload.message === "string") {
1429
1780
  return payload.message;
1430
1781
  }
1431
1782
  return "CoolClaw request failed";
1432
1783
  }
1433
- function isRecord5(value) {
1784
+ function isRecord6(value) {
1434
1785
  return typeof value === "object" && value !== null;
1435
1786
  }
1436
1787
 
@@ -1452,9 +1803,12 @@ function getPluginVersion() {
1452
1803
  }
1453
1804
 
1454
1805
  // src/channel.ts
1806
+ import { homedir as homedir3 } from "os";
1807
+ import path2 from "path";
1455
1808
  import {
1456
1809
  createChatChannelPlugin
1457
1810
  } from "openclaw/plugin-sdk/core";
1811
+ var productFlavor = activeFlavor();
1458
1812
  function createAccountStatusSink(params) {
1459
1813
  return (patch) => {
1460
1814
  params.setStatus({ accountId: params.accountId, ...patch });
@@ -1587,7 +1941,7 @@ function isNoReplyText(text) {
1587
1941
  return lastLine ? noReplyTokens.has(lastLine) : false;
1588
1942
  }
1589
1943
  function shouldSuppressCoolclawTextDelivery(envelope) {
1590
- return envelope.metadata?.gameEvent === true || isArenaReportShareEnvelope(envelope) || isArenaModelQueryEnvelope(envelope);
1944
+ return envelope.metadata?.gameEvent === true || isArenaReportShareEnvelope(envelope) || isArenaModelQueryEnvelope(envelope) || isArenaVoiceSelectEnvelope(envelope);
1591
1945
  }
1592
1946
  async function finalizeArenaModelQueryAfterDispatchError(params) {
1593
1947
  if (!params.collector) {
@@ -1609,19 +1963,19 @@ function clearRuntimeClient(accountKey) {
1609
1963
  runtimeClients.delete(accountKey);
1610
1964
  }
1611
1965
  function extractAccountFromConfig(cfg, accountId) {
1612
- const coolclawSection = cfg.channels?.coolclaw;
1613
- const accounts = coolclawSection?.accounts;
1966
+ const channelSection = cfg.channels?.[productFlavor.channelId];
1967
+ const accounts = channelSection?.accounts;
1614
1968
  return accounts?.[accountId ?? "default"] ?? {};
1615
1969
  }
1616
1970
  var coolclawChannelPlugin = createChatChannelPlugin({
1617
1971
  base: {
1618
- id: "coolclaw",
1972
+ id: productFlavor.channelId,
1619
1973
  meta: {
1620
- id: "coolclaw",
1621
- label: "CoolClaw",
1622
- selectionLabel: "CoolClaw",
1623
- docsPath: "/plugins/coolclaw",
1624
- blurb: "Connect OpenClaw to the CoolClaw/Riddle chat platform."
1974
+ id: productFlavor.channelId,
1975
+ label: productFlavor.displayName,
1976
+ selectionLabel: productFlavor.displayName,
1977
+ docsPath: `/plugins/${productFlavor.channelId}`,
1978
+ blurb: `Connect OpenClaw to the ${productFlavor.displayName} chat platform.`
1625
1979
  },
1626
1980
  capabilities: {
1627
1981
  chatTypes: ["direct", "group"],
@@ -1636,15 +1990,15 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1636
1990
  },
1637
1991
  agentPrompt: {
1638
1992
  messageToolHints: () => [
1639
- "To send a message on CoolClaw/Riddle, use the message tool with action='send' and set 'to' to a CoolClaw target like 'coolclaw:human:<userId>', 'coolclaw:agent:<agentId>', or 'coolclaw:group:<groupId>'.",
1993
+ `To send a message on ${productFlavor.displayName}, use the message tool with action='send' and set 'to' to a ${productFlavor.displayName} target like '${productFlavor.targetPrefix}:human:<userId>', '${productFlavor.targetPrefix}:agent:<agentId>', or '${productFlavor.targetPrefix}:group:<groupId>'.`,
1640
1994
  "To send an image or file, use the message tool with action='send' and set 'media' to a local file path or a remote URL.",
1641
- "When sending a message to a CoolClaw group, the agent will only reply if it was mentioned in the group message.",
1642
- "When creating a cron job for CoolClaw, set delivery.to to the target CoolClaw ID and delivery.accountId to the current accountId."
1995
+ `When sending a message to a ${productFlavor.displayName} group, the agent will only reply if it was mentioned in the group message.`,
1996
+ `When creating a cron job for ${productFlavor.displayName}, set delivery.to to the target ${productFlavor.displayName} ID and delivery.accountId to the current accountId.`
1643
1997
  ]
1644
1998
  },
1645
1999
  config: {
1646
2000
  listAccountIds(cfg) {
1647
- return Object.keys(cfg.channels?.coolclaw?.accounts ?? {});
2001
+ return Object.keys(cfg.channels?.[productFlavor.channelId]?.accounts ?? {});
1648
2002
  },
1649
2003
  resolveAccount(cfg, accountId) {
1650
2004
  return extractAccountFromConfig(cfg, accountId);
@@ -1676,9 +2030,9 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1676
2030
  async resolveTargets({ inputs }) {
1677
2031
  return inputs.map((input) => {
1678
2032
  try {
1679
- const normalized = normalizeCoolclawTarget(input);
2033
+ const normalized = normalizeCoolclawTarget(input, productFlavor);
1680
2034
  const [, type, id] = normalized.split(":");
1681
- parseCoolclawTarget(normalized);
2035
+ parseCoolclawTarget(normalized, productFlavor);
1682
2036
  return { input, resolved: true, id: normalized, name: `${type}:${id}` };
1683
2037
  } catch (error) {
1684
2038
  return { input, resolved: false, note: error instanceof Error ? error.message : String(error) };
@@ -1689,23 +2043,23 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1689
2043
  messaging: {
1690
2044
  normalizeTarget(raw) {
1691
2045
  try {
1692
- const normalized = normalizeCoolclawTarget(raw);
1693
- parseCoolclawTarget(normalized);
2046
+ const normalized = normalizeCoolclawTarget(raw, productFlavor);
2047
+ parseCoolclawTarget(normalized, productFlavor);
1694
2048
  return normalized;
1695
2049
  } catch {
1696
2050
  return void 0;
1697
2051
  }
1698
2052
  },
1699
2053
  inferTargetChatType({ to }) {
1700
- return inferCoolclawTargetChatType(to);
2054
+ return inferCoolclawTargetChatType(to, productFlavor);
1701
2055
  },
1702
2056
  targetResolver: {
1703
- hint: "Use coolclaw:human:<id>, coolclaw:agent:<id>, or coolclaw:group:<id>.",
2057
+ hint: `Use ${productFlavor.targetPrefix}:human:<id>, ${productFlavor.targetPrefix}:agent:<id>, or ${productFlavor.targetPrefix}:group:<id>.`,
1704
2058
  looksLikeId(raw, normalized) {
1705
- return isCoolclawTargetId(raw, normalized);
2059
+ return isCoolclawTargetId(raw, normalized, productFlavor);
1706
2060
  },
1707
2061
  resolveTarget({ input, normalized, preferredKind }) {
1708
- return resolveCoolclawMessagingTarget(normalized || input, preferredKind);
2062
+ return resolveCoolclawMessagingTarget(normalized || input, preferredKind, productFlavor);
1709
2063
  }
1710
2064
  }
1711
2065
  },
@@ -1714,17 +2068,19 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1714
2068
  const account = coolclawChannelPlugin.config.resolveAccount(ctx.cfg, ctx.accountId);
1715
2069
  const token = await resolveAccountToken(account);
1716
2070
  if (!account.gatewayUrl || !account.agentId || !token) {
1717
- ctx.log?.error(`[${ctx.accountId}] CoolClaw account is not fully configured`);
2071
+ ctx.log?.error(`[${ctx.accountId}] ${productFlavor.displayName} account is not fully configured`);
1718
2072
  return;
1719
2073
  }
1720
- const accountKey = `coolclaw:${ctx.accountId ?? "default"}`;
1721
- const ackStore = new FileAckStore();
2074
+ const accountKey = `${productFlavor.channelId}:${ctx.accountId ?? "default"}`;
2075
+ const ackStore = new FileAckStore(
2076
+ path2.join(homedir3(), ".openclaw", "extensions", productFlavor.channelId, ".ack-store")
2077
+ );
1722
2078
  const statusSink = createAccountStatusSink({
1723
2079
  accountId: ctx.accountId,
1724
2080
  setStatus: ctx.setStatus
1725
2081
  });
1726
2082
  statusSink({ statusState: "connecting" });
1727
- ctx.log?.info(`[${ctx.accountId}] starting CoolClaw provider (${account.gatewayUrl})`);
2083
+ ctx.log?.info(`[${ctx.accountId}] starting ${productFlavor.displayName} provider (${account.gatewayUrl})`);
1728
2084
  await runPassiveAccountLifecycle({
1729
2085
  abortSignal: ctx.abortSignal,
1730
2086
  start: async () => {
@@ -1748,9 +2104,11 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1748
2104
  const isGameEvent = envelope.metadata?.gameEvent === true;
1749
2105
  const isArenaReportShare = isArenaReportShareEnvelope(envelope);
1750
2106
  const isArenaModelQuery = isArenaModelQueryEnvelope(envelope);
2107
+ const isArenaVoiceSelect = isArenaVoiceSelectEnvelope(envelope);
1751
2108
  const suppressChatTextDelivery = shouldSuppressCoolclawTextDelivery(envelope);
1752
2109
  const gameMeta = isGameEvent ? envelope.metadata : null;
1753
2110
  const modelQueryMeta = isArenaModelQuery ? envelope.metadata : null;
2111
+ const voiceSelectMeta = isArenaVoiceSelect ? envelope.metadata : null;
1754
2112
  let gameSubmitted = false;
1755
2113
  let gameFallbackReason = null;
1756
2114
  let gameModelActionType;
@@ -1762,6 +2120,11 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1762
2120
  token,
1763
2121
  log: ctx.log
1764
2122
  }) : null;
2123
+ const voiceSelectCollector = voiceSelectMeta ? createArenaVoiceSelectReplyCollector({
2124
+ meta: voiceSelectMeta,
2125
+ token,
2126
+ log: ctx.log
2127
+ }) : null;
1765
2128
  if (isGameEvent && gameMeta) {
1766
2129
  ctx.log?.info?.(
1767
2130
  `[GAME-TASK] dispatch start gameId=${gameMeta.gameId} roomId=${gameMeta.roomId} eventType=${gameMeta.eventType} eventId=${gameMeta.eventId} promptPolicyVersion=${gameMeta.promptPolicyVersion ?? ""} renderedPromptHash=${gameMeta.renderedPromptHash ?? ""} conversationId=${envelope.conversationId}`
@@ -1778,6 +2141,17 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1778
2141
  `[ARENA-MODEL] inbound eventId=${modelQueryMeta.eventId} traceId=${modelQueryMeta.traceId ?? ""} roomId=${modelQueryMeta.roomId} seatEpoch=${modelQueryMeta.seatEpoch} callbackHost=${callbackHost} conversationId=${envelope.conversationId}`
1779
2142
  );
1780
2143
  }
2144
+ if (voiceSelectMeta) {
2145
+ let callbackHost = "";
2146
+ try {
2147
+ callbackHost = new URL(voiceSelectMeta.callbackUrl).host;
2148
+ } catch {
2149
+ callbackHost = "invalid";
2150
+ }
2151
+ ctx.log?.info?.(
2152
+ `[ARENA-VOICE] inbound eventId=${voiceSelectMeta.eventId} traceId=${voiceSelectMeta.traceId ?? ""} roomId=${voiceSelectMeta.roomId} seatEpoch=${voiceSelectMeta.seatEpoch} callbackHost=${callbackHost} conversationId=${envelope.conversationId}`
2153
+ );
2154
+ }
1781
2155
  const runtime = getCoolclawRuntime();
1782
2156
  let runtimeChannel;
1783
2157
  try {
@@ -1787,7 +2161,7 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1787
2161
  });
1788
2162
  } catch (err) {
1789
2163
  logInboundDrop({ log: ctx.log?.warn?.bind(ctx.log) ?? (() => {
1790
- }), channel: "coolclaw", reason: "runtime not available; skipping dispatch" });
2164
+ }), channel: productFlavor.channelId, reason: "runtime not available; skipping dispatch" });
1791
2165
  if (isGameEvent && gameMeta) {
1792
2166
  const submitted = await submitBackendFallbackWithLog({
1793
2167
  meta: gameMeta,
@@ -1809,6 +2183,15 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1809
2183
  });
1810
2184
  return;
1811
2185
  }
2186
+ if (voiceSelectMeta) {
2187
+ await submitArenaVoiceSelectCallback({
2188
+ meta: voiceSelectMeta,
2189
+ token,
2190
+ rawText: "",
2191
+ log: ctx.log
2192
+ });
2193
+ return;
2194
+ }
1812
2195
  throw err;
1813
2196
  }
1814
2197
  try {
@@ -1821,7 +2204,7 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1821
2204
  }
1822
2205
  const route = await runtimeChannel.routing.resolveAgentRoute({
1823
2206
  cfg: ctx.cfg,
1824
- channel: "coolclaw",
2207
+ channel: productFlavor.channelId,
1825
2208
  accountId: ctx.accountId,
1826
2209
  peer
1827
2210
  });
@@ -1834,18 +2217,18 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1834
2217
  ctx.cfg.session?.store,
1835
2218
  { agentId: route.agentId }
1836
2219
  );
1837
- const senderLabel = envelope.sender ? `${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}` : isGameEvent ? `game:${gameMeta.gameId}` : modelQueryMeta ? `arena-model:${modelQueryMeta.roomId}` : "unknown";
2220
+ const senderLabel = envelope.sender ? `${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}` : isGameEvent ? `game:${gameMeta.gameId}` : modelQueryMeta ? `arena-model:${modelQueryMeta.roomId}` : voiceSelectMeta ? `arena-voice:${voiceSelectMeta.roomId}` : "unknown";
1838
2221
  let deliveryTarget;
1839
2222
  if (envelope.group) {
1840
- deliveryTarget = `coolclaw:group:${envelope.group.groupId}`;
2223
+ deliveryTarget = `${productFlavor.targetPrefix}:group:${envelope.group.groupId}`;
1841
2224
  } else if (envelope.sender) {
1842
- deliveryTarget = `coolclaw:${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}`;
1843
- } else if (isGameEvent || isArenaModelQuery) {
1844
- deliveryTarget = `coolclaw:agent:${account.agentId}`;
2225
+ deliveryTarget = `${productFlavor.targetPrefix}:${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}`;
2226
+ } else if (isGameEvent || isArenaModelQuery || isArenaVoiceSelect) {
2227
+ deliveryTarget = `${productFlavor.targetPrefix}:agent:${account.agentId}`;
1845
2228
  } else {
1846
- deliveryTarget = normalizeCoolclawTarget(envelope.conversationId);
2229
+ deliveryTarget = normalizeCoolclawTarget(envelope.conversationId, productFlavor);
1847
2230
  }
1848
- const bodyForAgent = buildBodyForAgent(envelope);
2231
+ const bodyForAgent = buildBodyForAgent(envelope, productFlavor);
1849
2232
  if (typeof runtimeChannel.reply?.finalizeInboundContext !== "function") {
1850
2233
  throw new Error(
1851
2234
  "CoolClaw requires runtime.channel.reply.finalizeInboundContext. Please upgrade OpenClaw to >=2026.3.22."
@@ -1866,15 +2249,15 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1866
2249
  BodyForAgent: bodyForAgent,
1867
2250
  RawBody: envelope.text,
1868
2251
  CommandBody: envelope.text,
1869
- From: `coolclaw:${senderLabel}`,
2252
+ From: `${productFlavor.targetPrefix}:${senderLabel}`,
1870
2253
  To: deliveryTarget,
1871
2254
  SessionKey: route.sessionKey,
1872
2255
  AccountId: ctx.accountId,
1873
2256
  ChatType: isGroup ? "channel" : "direct",
1874
2257
  CommandAuthorized: true,
1875
- Provider: "coolclaw",
1876
- Surface: "coolclaw",
1877
- Channel: "coolclaw",
2258
+ Provider: productFlavor.channelId,
2259
+ Surface: productFlavor.channelId,
2260
+ Channel: productFlavor.channelId,
1878
2261
  Peer: peer,
1879
2262
  WasMentioned: envelope.shouldReply,
1880
2263
  Mentioned: envelope.shouldReply
@@ -1885,7 +2268,7 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1885
2268
  storePath,
1886
2269
  sessionKey,
1887
2270
  ctx: ctxPayload,
1888
- updateLastRoute: mainSessionKey && mainSessionKey !== sessionKey ? { sessionKey: mainSessionKey, channel: "coolclaw", to: deliveryTarget, accountId: ctx.accountId ?? void 0 } : void 0,
2271
+ updateLastRoute: mainSessionKey && mainSessionKey !== sessionKey ? { sessionKey: mainSessionKey, channel: productFlavor.channelId, to: deliveryTarget, accountId: ctx.accountId ?? void 0 } : void 0,
1889
2272
  onRecordError: (err) => {
1890
2273
  ctx.log?.warn(`recordInboundSession failed: ${err instanceof Error ? err.message : String(err)}`);
1891
2274
  }
@@ -1908,6 +2291,10 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1908
2291
  await modelQueryCollector.deliver(replyText);
1909
2292
  return;
1910
2293
  }
2294
+ if (voiceSelectCollector) {
2295
+ await voiceSelectCollector.deliver(replyText);
2296
+ return;
2297
+ }
1911
2298
  if (isGameEvent && gameMeta) {
1912
2299
  if (gameSubmitted) return;
1913
2300
  gameBuffer.push(String(payload.text));
@@ -1952,17 +2339,17 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1952
2339
  return;
1953
2340
  }
1954
2341
  if (suppressChatTextDelivery) {
1955
- ctx.log?.error?.(`[ARENA-MODEL] chat delivery blocked eventId=${envelope.metadata.eventId ?? ""}`);
2342
+ ctx.log?.error?.(`[ARENA-CALLBACK] chat delivery blocked eventId=${envelope.metadata.eventId ?? ""}`);
1956
2343
  return;
1957
2344
  }
1958
2345
  try {
1959
2346
  let replyTarget;
1960
2347
  if (envelope.group) {
1961
- replyTarget = `coolclaw:group:${envelope.group.groupId}`;
2348
+ replyTarget = `${productFlavor.targetPrefix}:group:${envelope.group.groupId}`;
1962
2349
  } else if (envelope.sender) {
1963
- replyTarget = `coolclaw:${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}`;
2350
+ replyTarget = `${productFlavor.targetPrefix}:${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}`;
1964
2351
  } else {
1965
- replyTarget = normalizeCoolclawTarget(envelope.conversationId);
2352
+ replyTarget = normalizeCoolclawTarget(envelope.conversationId, productFlavor);
1966
2353
  }
1967
2354
  await sendText({ client: wsClient, target: replyTarget, text: replyText });
1968
2355
  } catch (err) {
@@ -1982,6 +2369,9 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1982
2369
  if (modelQueryCollector) {
1983
2370
  await modelQueryCollector.finalize();
1984
2371
  }
2372
+ if (voiceSelectCollector) {
2373
+ await voiceSelectCollector.finalize();
2374
+ }
1985
2375
  } catch (err) {
1986
2376
  const errMsg = err instanceof Error ? err.message : String(err);
1987
2377
  ctx.log?.error(`Inbound dispatch error: ${errMsg}`);
@@ -1993,6 +2383,11 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1993
2383
  })) {
1994
2384
  return;
1995
2385
  }
2386
+ if (voiceSelectCollector) {
2387
+ ctx.log?.warn?.(`[ARENA-VOICE] dispatch failed; submitting fallback callback eventId=${voiceSelectMeta?.eventId ?? ""} err=${errMsg}`);
2388
+ await voiceSelectCollector.finalize();
2389
+ return;
2390
+ }
1996
2391
  if (isGameEvent && gameMeta && !gameSubmitted) {
1997
2392
  gameFallbackReason = `dispatch_error: ${errMsg}`;
1998
2393
  }
@@ -2043,7 +2438,7 @@ var coolclawChannelPlugin = createChatChannelPlugin({
2043
2438
  ctx.log?.debug?.(`ACK sent: type=${ackFrame.type} lastAckedSeq=${ackFrame.payload?.lastAckedSeq}`);
2044
2439
  } catch (err) {
2045
2440
  logAckFailure({ log: ctx.log?.warn?.bind(ctx.log) ?? (() => {
2046
- }), channel: "coolclaw", error: err });
2441
+ }), channel: productFlavor.channelId, error: err });
2047
2442
  throw err;
2048
2443
  }
2049
2444
  }
@@ -2056,7 +2451,7 @@ var coolclawChannelPlugin = createChatChannelPlugin({
2056
2451
  await client.start();
2057
2452
  setRuntimeClient(accountKey, client);
2058
2453
  statusSink({ statusState: "connected" });
2059
- ctx.log?.info(`[${ctx.accountId}] CoolClaw provider connected`);
2454
+ ctx.log?.info(`[${ctx.accountId}] ${productFlavor.displayName} provider connected`);
2060
2455
  return client;
2061
2456
  },
2062
2457
  stop: async (client) => {
@@ -2065,25 +2460,25 @@ var coolclawChannelPlugin = createChatChannelPlugin({
2065
2460
  },
2066
2461
  onStop: () => {
2067
2462
  statusSink({ statusState: "disconnected" });
2068
- ctx.log?.info(`[${ctx.accountId}] CoolClaw provider stopped`);
2463
+ ctx.log?.info(`[${ctx.accountId}] ${productFlavor.displayName} provider stopped`);
2069
2464
  }
2070
2465
  });
2071
2466
  },
2072
2467
  /** 显式停止账户连接,清理 WebSocket 客户端资源 */
2073
2468
  async stopAccount(ctx) {
2074
- const accountKey = `coolclaw:${ctx.accountId ?? "default"}`;
2469
+ const accountKey = `${productFlavor.channelId}:${ctx.accountId ?? "default"}`;
2075
2470
  const client = getRuntimeClient(accountKey);
2076
2471
  if (client) {
2077
2472
  await client.stop();
2078
2473
  clearRuntimeClient(accountKey);
2079
- ctx.log?.info(`[${ctx.accountId}] CoolClaw client stopped via stopAccount`);
2474
+ ctx.log?.info(`[${ctx.accountId}] ${productFlavor.displayName} client stopped via stopAccount`);
2080
2475
  }
2081
2476
  }
2082
2477
  }
2083
2478
  },
2084
2479
  security: {
2085
2480
  dm: {
2086
- channelKey: "coolclaw",
2481
+ channelKey: productFlavor.channelId,
2087
2482
  resolvePolicy: (account) => account.dmPolicy ?? "allowlist",
2088
2483
  resolveAllowFrom: (account) => account.allowFrom ?? [],
2089
2484
  defaultPolicy: "allowlist",
@@ -2092,10 +2487,10 @@ var coolclawChannelPlugin = createChatChannelPlugin({
2092
2487
  },
2093
2488
  pairing: {
2094
2489
  text: {
2095
- idLabel: "CoolClaw user ID",
2490
+ idLabel: `${productFlavor.displayName} user ID`,
2096
2491
  message: "You are not authorized to message this agent. Send this pairing code to verify:",
2097
2492
  notify: async ({ id, message }) => {
2098
- const client = getRuntimeClient("coolclaw:default");
2493
+ const client = getRuntimeClient(`${productFlavor.channelId}:default`);
2099
2494
  if (client) {
2100
2495
  await sendText({
2101
2496
  client,
@@ -2114,11 +2509,11 @@ var coolclawChannelPlugin = createChatChannelPlugin({
2114
2509
  deliveryMode: "direct",
2115
2510
  resolveTarget({ to }) {
2116
2511
  if (!to) {
2117
- return { ok: false, error: new Error("CoolClaw target is required") };
2512
+ return { ok: false, error: new Error(`${productFlavor.displayName} target is required`) };
2118
2513
  }
2119
2514
  try {
2120
- const normalized = normalizeCoolclawTarget(to);
2121
- parseCoolclawTarget(normalized);
2515
+ const normalized = normalizeCoolclawTarget(to, productFlavor);
2516
+ parseCoolclawTarget(normalized, productFlavor);
2122
2517
  return { ok: true, to: normalized };
2123
2518
  } catch (error) {
2124
2519
  return { ok: false, error: error instanceof Error ? error : new Error(String(error)) };
@@ -2126,14 +2521,14 @@ var coolclawChannelPlugin = createChatChannelPlugin({
2126
2521
  }
2127
2522
  },
2128
2523
  attachedResults: {
2129
- channel: "coolclaw",
2524
+ channel: productFlavor.channelId,
2130
2525
  async sendText(ctx) {
2131
2526
  const account = coolclawChannelPlugin.config.resolveAccount(ctx.cfg, ctx.accountId);
2132
2527
  const token = await resolveAccountToken(account);
2133
2528
  if (!account.gatewayUrl || !account.agentId || !token) {
2134
- throw new Error("CoolClaw account is not fully configured");
2529
+ throw new Error(`${productFlavor.displayName} account is not fully configured`);
2135
2530
  }
2136
- const accountKey = `coolclaw:${ctx.accountId ?? "default"}`;
2531
+ const accountKey = `${productFlavor.channelId}:${ctx.accountId ?? "default"}`;
2137
2532
  let client = getRuntimeClient(accountKey);
2138
2533
  if (!client || !client.isConnected()) {
2139
2534
  client = new CoolclawWsClient({
@@ -2159,9 +2554,9 @@ var coolclawChannelPlugin = createChatChannelPlugin({
2159
2554
  const account = coolclawChannelPlugin.config.resolveAccount(ctx.cfg, ctx.accountId);
2160
2555
  const token = await resolveAccountToken(account);
2161
2556
  if (!account.gatewayUrl || !account.agentId || !token) {
2162
- throw new Error("CoolClaw account is not fully configured");
2557
+ throw new Error(`${productFlavor.displayName} account is not fully configured`);
2163
2558
  }
2164
- const accountKey = `coolclaw:${ctx.accountId ?? "default"}`;
2559
+ const accountKey = `${productFlavor.channelId}:${ctx.accountId ?? "default"}`;
2165
2560
  let client = getRuntimeClient(accountKey);
2166
2561
  if (!client || !client.isConnected()) {
2167
2562
  client = new CoolclawWsClient({
@@ -2186,13 +2581,17 @@ var coolclawChannelPlugin = createChatChannelPlugin({
2186
2581
  }
2187
2582
  }
2188
2583
  });
2189
- function buildBodyForAgent(envelope) {
2584
+ function buildBodyForAgent(envelope, flavorInput) {
2585
+ const flavor = resolveFlavor(flavorInput);
2586
+ if (isArenaVoiceSelectEnvelope(envelope)) {
2587
+ return buildArenaVoiceSelectBodyForAgent(envelope, flavor);
2588
+ }
2190
2589
  if (isArenaReportShareEnvelope(envelope)) {
2191
- return buildArenaReportShareBodyForAgent(envelope);
2590
+ return buildArenaReportShareBodyForAgent(envelope, flavor);
2192
2591
  }
2193
2592
  if (!envelope.group) {
2194
2593
  if (envelope.sender && envelope.recipient) {
2195
- return buildPrivateBodyForAgent(envelope);
2594
+ return buildPrivateBodyForAgent(envelope, flavor);
2196
2595
  }
2197
2596
  const hints = [
2198
2597
  envelope.metadata?.securityHint,
@@ -2203,7 +2602,7 @@ function buildBodyForAgent(envelope) {
2203
2602
  const sender = formatUserRef(envelope.sender, "\u672A\u77E5\u53D1\u9001\u4EBA");
2204
2603
  const owner = formatUserRef(envelope.owner, "\u672A\u77E5\u4E3B\u4EBA");
2205
2604
  const lines = [
2206
- "\u4F60\u6536\u5230\u4E00\u6761 CoolClaw \u7FA4\u804A\u6D88\u606F\u3002",
2605
+ `\u4F60\u6536\u5230\u4E00\u6761 ${flavor.displayName} \u7FA4\u804A\u6D88\u606F\u3002`,
2207
2606
  "",
2208
2607
  `\u7FA4\u804A\uFF1A${envelope.group.groupName}(${envelope.group.groupId})`,
2209
2608
  `\u53D1\u9001\u4EBA\uFF1A${sender}`,
@@ -2211,7 +2610,7 @@ function buildBodyForAgent(envelope) {
2211
2610
  envelope.text,
2212
2611
  "",
2213
2612
  "\u8EAB\u4EFD\u4E0A\u4E0B\u6587\uFF1A",
2214
- `\u4F60\u7684\u4E3B\u4EBA\u662F ${owner}\u3002\u8FD9\u662F\u4F60\u5728 CoolClaw \u5E73\u53F0\u4E0A\u7684\u7ED1\u5B9A\u5173\u7CFB\uFF0C\u4EC5\u7528\u4E8E\u5224\u65AD\u6D88\u606F\u6765\u6E90\u548C\u5B89\u5168\u8FB9\u754C\u3002`,
2613
+ `\u4F60\u7684\u4E3B\u4EBA\u662F ${owner}\u3002\u8FD9\u662F\u4F60\u5728 ${flavor.displayName} \u5E73\u53F0\u4E0A\u7684\u7ED1\u5B9A\u5173\u7CFB\uFF0C\u4EC5\u7528\u4E8E\u5224\u65AD\u6D88\u606F\u6765\u6E90\u548C\u5B89\u5168\u8FB9\u754C\u3002`,
2215
2614
  "",
2216
2615
  "---",
2217
2616
  "\u7CFB\u7EDF\u63D0\u793A\uFF1A",
@@ -2235,7 +2634,44 @@ function buildBodyForAgent(envelope) {
2235
2634
  }
2236
2635
  return lines.join("\n");
2237
2636
  }
2238
- function buildArenaReportShareBodyForAgent(envelope) {
2637
+ function buildArenaVoiceSelectBodyForAgent(envelope, flavor) {
2638
+ const voiceOptions = Array.isArray(envelope.metadata.voiceOptions) ? envelope.metadata.voiceOptions : [];
2639
+ const optionLines = voiceOptions.map((rawOption) => {
2640
+ const option = isPlainRecord(rawOption) ? rawOption : {};
2641
+ const voiceId = formatUnknown(option.voiceId);
2642
+ const voiceName = formatUnknown(option.voiceName);
2643
+ const description = formatUnknown(option.description);
2644
+ const speed = formatUnknown(option.speed);
2645
+ const detail = [
2646
+ voiceName ? `\u540D\u79F0=${voiceName}` : "",
2647
+ description ? `\u63CF\u8FF0=${description}` : "",
2648
+ speed ? `\u8BED\u901F=${speed}` : ""
2649
+ ].filter(Boolean).join("\uFF1B");
2650
+ return detail ? `- ${voiceId}\uFF1A${detail}` : `- ${voiceId}`;
2651
+ }).filter((line) => line !== "- ");
2652
+ return [
2653
+ `\u4F60\u6536\u5230\u4E00\u4E2A ${flavor.displayName} \u72FC\u4EBA\u6740\u8D5B\u524D\u97F3\u8272\u9009\u62E9\u4EFB\u52A1\u3002`,
2654
+ "",
2655
+ "\u4EFB\u52A1\u6027\u8D28\uFF1A\u8BF7\u9009\u62E9\u8D5B\u524D\u73A9\u5BB6\u97F3\u8272\u3002\u4F60\u53EA\u8D1F\u8D23\u7ED9\u51FA\u504F\u597D\uFF0C\u63D2\u4EF6\u4F1A\u8D1F\u8D23\u9274\u6743\u548C\u63D0\u4EA4\u3002",
2656
+ `eventId\uFF1A${formatUnknown(envelope.metadata.eventId)}`,
2657
+ `roomId\uFF1A${formatUnknown(envelope.metadata.roomId)}`,
2658
+ `seatNumber\uFF1A${formatUnknown(envelope.metadata.seatNumber)}`,
2659
+ `seatEpoch\uFF1A${formatUnknown(envelope.metadata.seatEpoch)}`,
2660
+ "",
2661
+ "\u53EF\u9009\u73A9\u5BB6\u97F3\u8272\uFF1A",
2662
+ ...optionLines.length > 0 ? optionLines : ["- \u65E0"],
2663
+ "",
2664
+ "\u8F93\u51FA\u8981\u6C42\uFF1A",
2665
+ "\u53EA\u8F93\u51FA\u4E00\u4E2A JSON \u5BF9\u8C61\uFF0C\u4E0D\u8981\u8F93\u51FA Markdown\uFF0C\u4E0D\u8981\u8C03\u7528\u63A5\u53E3\uFF0C\u4E0D\u8981\u4F7F\u7528\u5DE5\u5177\uFF0C\u4E0D\u8981\u89E3\u91CA\u63D0\u4EA4\u8FC7\u7A0B\u3002",
2666
+ '{"topVoiceIds":["4139","4172","5977"],"reason":"\u4E00\u53E5\u8BDD\u8BF4\u660E\u6574\u4F53\u9009\u62E9\u503E\u5411"}',
2667
+ "",
2668
+ "\u7EA6\u675F\uFF1A",
2669
+ "1. topVoiceIds \u53EA\u80FD\u5305\u542B\u4E0A\u65B9\u53EF\u9009\u5217\u8868\u91CC\u7684 voiceId \u5B57\u7B26\u4E32\uFF0C\u6700\u591A 3 \u4E2A\uFF0C\u6309\u504F\u597D\u6392\u5E8F\u3002",
2670
+ "2. \u4E0D\u8981\u628A voiceId \u8F6C\u6210\u6570\u5B57\u3002",
2671
+ "3. \u97F3\u8272\u53EA\u5F71\u54CD\u53D1\u58F0\u98CE\u683C\uFF0C\u4E0D\u4EE3\u8868\u4F60\u7684\u6E38\u620F\u8EAB\u4EFD\u3001\u63A8\u7406\u4F9D\u636E\u6216\u53EF\u4FE1\u5EA6\u3002"
2672
+ ].join("\n");
2673
+ }
2674
+ function buildArenaReportShareBodyForAgent(envelope, flavor) {
2239
2675
  const payload = isPlainRecord(envelope.metadata.reportPayload) ? envelope.metadata.reportPayload : {};
2240
2676
  const gameId = formatUnknown(payload.gameId);
2241
2677
  const roomId = formatUnknown(payload.roomId);
@@ -2248,7 +2684,7 @@ function buildArenaReportShareBodyForAgent(envelope) {
2248
2684
  const eventId = formatUnknown(envelope.metadata.eventId);
2249
2685
  const traceId = formatUnknown(envelope.metadata.traceId);
2250
2686
  return [
2251
- "\u4F60\u6536\u5230\u4E00\u4E2A CoolClaw \u7ADE\u6280\u573A\u6218\u62A5\u5206\u4EAB\u59D4\u6258\u4EFB\u52A1\u3002",
2687
+ `\u4F60\u6536\u5230\u4E00\u4E2A ${flavor.displayName} \u7ADE\u6280\u573A\u6218\u62A5\u5206\u4EAB\u59D4\u6258\u4EFB\u52A1\u3002`,
2252
2688
  "",
2253
2689
  "\u4EFB\u52A1\u6027\u8D28\uFF1A\u8FD9\u662F\u4E3B\u4EBA\u59D4\u6258\u4F60\u6267\u884C\u7684\u7ADE\u6280\u573A\u6218\u62A5\u5206\u4EAB\u4EFB\u52A1\uFF0C\u4E0D\u662F\u666E\u901A\u8BC4\u8BBA\u901A\u77E5\uFF0C\u4E5F\u4E0D\u662F\u804A\u5929\u56DE\u590D\u3002",
2254
2690
  `eventId\uFF1A${eventId}`,
@@ -2264,14 +2700,14 @@ function buildArenaReportShareBodyForAgent(envelope) {
2264
2700
  `result\uFF1A${result}`,
2265
2701
  "",
2266
2702
  "\u6267\u884C\u6B65\u9AA4\uFF1A",
2267
- "1. \u8BFB\u53D6\u5F53\u524D CoolClaw channel/plugin \u914D\u7F6E\u7684 Gateway Base URL\uFF1B\u4F18\u5148\u4F7F\u7528 channels.coolclaw.accounts.default.gatewayUrl\uFF0C\u5176\u6B21\u4F7F\u7528 COOLCLAW_GATEWAY_URL \u73AF\u5883\u53D8\u91CF\u3002",
2703
+ `1. \u8BFB\u53D6\u5F53\u524D ${flavor.displayName} channel/plugin \u914D\u7F6E\u7684 Gateway Base URL\uFF1B\u4F18\u5148\u4F7F\u7528 channels.${flavor.channelId}.accounts.default.gatewayUrl\uFF0C\u5176\u6B21\u4F7F\u7528 ${flavor.envPrefix}_GATEWAY_URL \u73AF\u5883\u53D8\u91CF\u3002`,
2268
2704
  "2. \u5982\u679C\u65E0\u6CD5\u89E3\u6790 Gateway Base URL\uFF0C\u505C\u6B62\u4EFB\u52A1\u5E76\u62A5\u544A\u914D\u7F6E\u7F3A\u5931\uFF1B\u4E0D\u8981\u81C6\u9020\u57DF\u540D\u3002",
2269
2705
  `3. \u4F7F\u7528 Gateway Base URL \u62FC\u63A5 replayApiPath=${replayApiPath} \u8BFB\u53D6\u672C\u5C40\u6218\u62A5\u3002`,
2270
2706
  `4. \u4F7F\u7528\u540C\u4E00\u4E2A Gateway Base URL \u62FC\u63A5 boardListApiPath=${boardListApiPath} \u67E5\u8BE2\u5185\u5BB9\u5E7F\u573A\u677F\u5757\uFF0C\u4F18\u5148\u9009\u62E9\u201C\u7ADE\u6280\u573A\u6218\u62A5 & \u590D\u76D8\u201D\u3002`,
2271
2707
  `5. \u4F7F\u7528\u540C\u4E00\u4E2A Gateway Base URL \u62FC\u63A5 createPostApiPath=${createPostApiPath} \u53D1\u5E03\u5E16\u5B50\u3002`,
2272
2708
  "6. \u53D1\u5E16\u6807\u9898\u548C\u6B63\u6587\u7531\u4F60\u57FA\u4E8E\u6218\u62A5\u5185\u5BB9\u751F\u6210\uFF0C\u6B63\u6587\u4E0D\u8981\u5305\u542B\u6218\u62A5\u94FE\u63A5\u3002",
2273
2709
  "7. eventId \u662F\u672C\u4EFB\u52A1\u7684\u5E42\u7B49\u952E\uFF1B\u5982\u679C\u5F53\u524D\u8FD0\u884C\u8FDB\u7A0B\u5DF2\u5904\u7406\u8FC7\u540C\u4E00 eventId\uFF0C\u4E0D\u8981\u518D\u6B21\u53D1\u5E16\u3002",
2274
- "8. \u5B8C\u6210\u540E\u53EA\u4FDD\u7559\u7B80\u77ED\u6267\u884C\u6458\u8981\uFF0C\u4E0D\u8981\u628A\u5B8C\u6210\u6458\u8981\u53D1\u9001\u5230 CoolClaw \u804A\u5929\u7A97\u53E3\u3002",
2710
+ `8. \u5B8C\u6210\u540E\u53EA\u4FDD\u7559\u7B80\u77ED\u6267\u884C\u6458\u8981\uFF0C\u4E0D\u8981\u628A\u5B8C\u6210\u6458\u8981\u53D1\u9001\u5230 ${flavor.displayName} \u804A\u5929\u7A97\u53E3\u3002`,
2275
2711
  "",
2276
2712
  "\u7981\u6B62\u4E8B\u9879\uFF1A\u4E0D\u8981\u4F7F\u7528 arena prompt \u4E2D\u7684\u56FA\u5B9A host\uFF0C\u4E0D\u8981\u4F7F\u7528\u672C\u5730/\u5185\u7F51\u5730\u5740\uFF0C\u4E0D\u8981\u6784\u9020\u6218\u62A5\u9875\u9762\u8DEF\u5F84\uFF0C\u4E0D\u8981\u8981\u6C42\u6B63\u6587\u9644\u6218\u62A5\u94FE\u63A5\u3002"
2277
2713
  ].join("\n");
@@ -2285,18 +2721,18 @@ function formatUnknown(value) {
2285
2721
  if (typeof value === "number" || typeof value === "boolean") return String(value);
2286
2722
  return JSON.stringify(value);
2287
2723
  }
2288
- function buildPrivateBodyForAgent(envelope) {
2724
+ function buildPrivateBodyForAgent(envelope, flavor) {
2289
2725
  const sender = formatUserRef(envelope.sender, "\u672A\u77E5\u53D1\u9001\u4EBA");
2290
2726
  const owner = formatUserRef(envelope.owner, "\u672A\u77E5\u4E3B\u4EBA");
2291
2727
  const lines = [
2292
- "\u4F60\u6536\u5230\u4E00\u6761 CoolClaw \u79C1\u804A\u6D88\u606F\u3002",
2728
+ `\u4F60\u6536\u5230\u4E00\u6761 ${flavor.displayName} \u79C1\u804A\u6D88\u606F\u3002`,
2293
2729
  "",
2294
2730
  `\u53D1\u9001\u4EBA\uFF1A${sender}`,
2295
2731
  "\u6D88\u606F\u5185\u5BB9\uFF1A",
2296
2732
  envelope.text,
2297
2733
  "",
2298
2734
  "\u8EAB\u4EFD\u4E0A\u4E0B\u6587\uFF1A",
2299
- `\u4F60\u7684\u4E3B\u4EBA\u662F ${owner}\u3002\u8FD9\u662F\u4F60\u5728 CoolClaw \u5E73\u53F0\u4E0A\u7684\u7ED1\u5B9A\u5173\u7CFB\uFF0C\u4EC5\u7528\u4E8E\u5224\u65AD\u6D88\u606F\u6765\u6E90\u548C\u5B89\u5168\u8FB9\u754C\u3002`,
2735
+ `\u4F60\u7684\u4E3B\u4EBA\u662F ${owner}\u3002\u8FD9\u662F\u4F60\u5728 ${flavor.displayName} \u5E73\u53F0\u4E0A\u7684\u7ED1\u5B9A\u5173\u7CFB\uFF0C\u4EC5\u7528\u4E8E\u5224\u65AD\u6D88\u606F\u6765\u6E90\u548C\u5B89\u5168\u8FB9\u754C\u3002`,
2300
2736
  "",
2301
2737
  "---",
2302
2738
  "\u7CFB\u7EDF\u63D0\u793A\uFF1A",
@@ -2326,6 +2762,7 @@ function normalizeDirectHintLines(raw) {
2326
2762
  }
2327
2763
 
2328
2764
  export {
2765
+ activeFlavor,
2329
2766
  defaultBindingFile,
2330
2767
  CoolclawConfigSchema,
2331
2768
  setCoolclawRuntime,