@coolclaw/coolclaw 1.0.14 → 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;
@@ -357,10 +415,20 @@ function isRecord2(value) {
357
415
 
358
416
  // src/inbound.ts
359
417
  var ARENA_REPORT_SHARE_NOTIFY_TYPE = "ARENA_REPORT_SHARE_REQUEST";
418
+ var ARENA_MODEL_QUERY_NOTIFY_TYPE = "ARENA_MODEL_QUERY_REQUEST";
419
+ var ARENA_VOICE_SELECT_NOTIFY_TYPE = "ARENA_VOICE_SELECT_REQUEST";
360
420
  var REPORT_SHARE_DEDUPE_LIMIT = 500;
421
+ var MODEL_QUERY_DEDUPE_LIMIT = 500;
422
+ var VOICE_SELECT_DEDUPE_LIMIT = 500;
361
423
  var processedArenaReportShareEventIds = /* @__PURE__ */ new Set();
362
424
  var processedArenaReportShareEventOrder = [];
363
425
  var inFlightArenaReportShareEventIds = /* @__PURE__ */ new Map();
426
+ var processedArenaModelQueryEventIds = /* @__PURE__ */ new Set();
427
+ var processedArenaModelQueryEventOrder = [];
428
+ var inFlightArenaModelQueryEventIds = /* @__PURE__ */ new Map();
429
+ var processedArenaVoiceSelectEventIds = /* @__PURE__ */ new Set();
430
+ var processedArenaVoiceSelectEventOrder = [];
431
+ var inFlightArenaVoiceSelectEventIds = /* @__PURE__ */ new Map();
364
432
  function mapInboundFrame(frame) {
365
433
  if (frame.type === "PRIVATE_MESSAGE") {
366
434
  const payload = assertPrivatePayload(frame.payload);
@@ -421,40 +489,61 @@ async function handleInboundFrame(input) {
421
489
  await ackFrameSeq(input);
422
490
  return;
423
491
  }
424
- let arenaReportShareEventId = null;
492
+ let dedupeState = null;
425
493
  if (isArenaReportShareEnvelope(envelope)) {
426
- arenaReportShareEventId = String(envelope.metadata.eventId);
427
- if (processedArenaReportShareEventIds.has(arenaReportShareEventId)) {
494
+ dedupeState = {
495
+ eventId: String(envelope.metadata.eventId),
496
+ processed: processedArenaReportShareEventIds,
497
+ inFlight: inFlightArenaReportShareEventIds,
498
+ remember: rememberArenaReportShareEventId
499
+ };
500
+ } else if (isArenaModelQueryEnvelope(envelope)) {
501
+ dedupeState = {
502
+ eventId: String(envelope.metadata.eventId),
503
+ processed: processedArenaModelQueryEventIds,
504
+ inFlight: inFlightArenaModelQueryEventIds,
505
+ remember: rememberArenaModelQueryEventId
506
+ };
507
+ } else if (isArenaVoiceSelectEnvelope(envelope)) {
508
+ dedupeState = {
509
+ eventId: String(envelope.metadata.eventId),
510
+ processed: processedArenaVoiceSelectEventIds,
511
+ inFlight: inFlightArenaVoiceSelectEventIds,
512
+ remember: rememberArenaVoiceSelectEventId
513
+ };
514
+ }
515
+ if (dedupeState) {
516
+ if (dedupeState.processed.has(dedupeState.eventId)) {
428
517
  await ackFrameSeq(input);
429
518
  return;
430
519
  }
431
- const inFlight = inFlightArenaReportShareEventIds.get(arenaReportShareEventId);
520
+ const inFlight = dedupeState.inFlight.get(dedupeState.eventId);
432
521
  if (inFlight) {
433
522
  await inFlight;
434
- if (processedArenaReportShareEventIds.has(arenaReportShareEventId)) {
523
+ if (dedupeState.processed.has(dedupeState.eventId)) {
435
524
  await ackFrameSeq(input);
436
525
  return;
437
526
  }
438
527
  }
439
528
  }
440
529
  let inFlightDeferred = null;
441
- if (arenaReportShareEventId) {
530
+ if (dedupeState) {
442
531
  inFlightDeferred = createDeferred();
443
532
  inFlightDeferred.promise.catch(() => void 0);
444
- inFlightArenaReportShareEventIds.set(arenaReportShareEventId, inFlightDeferred.promise);
533
+ dedupeState.inFlight.set(dedupeState.eventId, inFlightDeferred.promise);
445
534
  }
446
535
  try {
447
536
  await input.dispatch(envelope);
448
- if (arenaReportShareEventId) {
449
- rememberArenaReportShareEventId(arenaReportShareEventId);
537
+ if (dedupeState) {
538
+ dedupeState.remember(dedupeState.eventId);
450
539
  inFlightDeferred?.resolve();
451
540
  }
452
541
  } catch (error) {
453
542
  inFlightDeferred?.reject(error);
454
543
  throw error;
455
544
  } finally {
456
- if (arenaReportShareEventId) {
457
- inFlightArenaReportShareEventIds.delete(arenaReportShareEventId);
545
+ if (dedupeState) {
546
+ dedupeState.inFlight.delete(dedupeState.eventId);
458
547
  }
459
548
  }
460
549
  await ackProcessedSeq(input, envelope);
@@ -471,6 +560,12 @@ function createDeferred() {
471
560
  function isArenaReportShareEnvelope(envelope) {
472
561
  return envelope.metadata?.arenaReportShareRequest === true && typeof envelope.metadata.eventId === "string" && envelope.metadata.eventId.length > 0;
473
562
  }
563
+ function isArenaModelQueryEnvelope(envelope) {
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;
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
+ }
474
569
  function mapNotificationFrame(frame) {
475
570
  const payload = isRecord3(frame.payload) ? frame.payload : {};
476
571
  const seq = typeof payload.seq === "number" ? payload.seq : void 0;
@@ -509,6 +604,12 @@ function mapNotificationFrame(frame) {
509
604
  if (notifyType === ARENA_REPORT_SHARE_NOTIFY_TYPE) {
510
605
  return mapArenaReportShareFrame(frame, payload, seq);
511
606
  }
607
+ if (notifyType === ARENA_MODEL_QUERY_NOTIFY_TYPE) {
608
+ return mapArenaModelQueryFrame(frame, payload, seq);
609
+ }
610
+ if (notifyType === ARENA_VOICE_SELECT_NOTIFY_TYPE) {
611
+ return mapArenaVoiceSelectFrame(frame, payload, seq);
612
+ }
512
613
  const postId = payload.postId != null ? String(payload.postId) : "";
513
614
  return {
514
615
  id: frame.id,
@@ -523,6 +624,86 @@ function mapNotificationFrame(frame) {
523
624
  }
524
625
  return null;
525
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
+ }
664
+ function mapArenaModelQueryFrame(frame, payload, seq) {
665
+ const eventId = typeof payload.eventId === "string" && payload.eventId.length > 0 ? payload.eventId : frame.id;
666
+ const traceId = typeof payload.traceId === "string" ? payload.traceId : "";
667
+ const modelQueryPayload = isRecord3(payload.payload) ? payload.payload : {};
668
+ const roomId = Number(modelQueryPayload.roomId ?? 0);
669
+ const seatNumber = Number(modelQueryPayload.seatNumber ?? 0);
670
+ const seatEpoch = modelQueryPayload.seatEpoch != null ? String(modelQueryPayload.seatEpoch) : "";
671
+ const callbackUrl = typeof modelQueryPayload.callbackUrl === "string" ? modelQueryPayload.callbackUrl : "";
672
+ if (!callbackUrl) {
673
+ return null;
674
+ }
675
+ const deadlineEpochMs = Number(modelQueryPayload.deadlineEpochMs ?? 0);
676
+ return {
677
+ id: eventId,
678
+ channel: "coolclaw",
679
+ conversationId: `notification:arena_model_query:${eventId}`,
680
+ text: "/model",
681
+ messageType: frame.type,
682
+ seq,
683
+ shouldReply: true,
684
+ metadata: {
685
+ sourceFrameId: frame.id,
686
+ payload: frame.payload,
687
+ modelQueryPayload,
688
+ arenaModelQueryRequest: true,
689
+ eventId,
690
+ traceId,
691
+ roomId,
692
+ seatNumber,
693
+ seatEpoch,
694
+ callbackUrl,
695
+ deadlineEpochMs
696
+ }
697
+ };
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
+ }
526
707
  function mapArenaReportShareFrame(frame, payload, seq) {
527
708
  const eventId = typeof payload.eventId === "string" && payload.eventId.length > 0 ? payload.eventId : frame.id;
528
709
  const traceId = typeof payload.traceId === "string" ? payload.traceId : "";
@@ -555,6 +736,24 @@ function rememberArenaReportShareEventId(eventId) {
555
736
  if (expired) processedArenaReportShareEventIds.delete(expired);
556
737
  }
557
738
  }
739
+ function rememberArenaModelQueryEventId(eventId) {
740
+ if (processedArenaModelQueryEventIds.has(eventId)) return;
741
+ processedArenaModelQueryEventIds.add(eventId);
742
+ processedArenaModelQueryEventOrder.push(eventId);
743
+ while (processedArenaModelQueryEventOrder.length > MODEL_QUERY_DEDUPE_LIMIT) {
744
+ const expired = processedArenaModelQueryEventOrder.shift();
745
+ if (expired) processedArenaModelQueryEventIds.delete(expired);
746
+ }
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
+ }
558
757
  function mapGameEventFrame(frame, payload) {
559
758
  const eventType = typeof payload.eventType === "string" ? payload.eventType : "UNKNOWN";
560
759
  const agentTask = normalizeAgentTask(payload.agentTask);
@@ -713,15 +912,16 @@ var TargetParseError = class extends Error {
713
912
  this.name = "TargetParseError";
714
913
  }
715
914
  };
716
- function parseCoolclawTarget(raw) {
717
- const normalized = normalizeCoolclawTarget(raw);
915
+ function parseCoolclawTarget(raw, flavorInput) {
916
+ const flavor = resolveFlavor(flavorInput);
917
+ const normalized = normalizeCoolclawTarget(raw, flavor);
718
918
  const parts = normalized.split(":");
719
919
  if (parts.length !== 3) {
720
- throw new TargetParseError(`Invalid CoolClaw target: ${raw}`);
920
+ throw new TargetParseError(`Invalid ${flavor.displayName} target: ${raw}`);
721
921
  }
722
922
  const [channel, type, id] = parts;
723
- if (channel !== "coolclaw" || id.length === 0) {
724
- throw new TargetParseError(`Invalid CoolClaw target: ${raw}`);
923
+ if (channel !== flavor.targetPrefix || id.length === 0) {
924
+ throw new TargetParseError(`Invalid ${flavor.displayName} target: ${raw}`);
725
925
  }
726
926
  if (type === "human") {
727
927
  return { kind: "private", userType: "HUMAN", userId: id };
@@ -732,9 +932,10 @@ function parseCoolclawTarget(raw) {
732
932
  if (type === "group") {
733
933
  return { kind: "group", groupId: id };
734
934
  }
735
- throw new TargetParseError(`Invalid CoolClaw target type: ${type}`);
935
+ throw new TargetParseError(`Invalid ${flavor.displayName} target type: ${type}`);
736
936
  }
737
- function normalizeCoolclawTarget(raw) {
937
+ function normalizeCoolclawTarget(raw, flavorInput) {
938
+ resolveFlavor(flavorInput);
738
939
  const trimmed = raw.trim();
739
940
  const parts = trimmed.split(":");
740
941
  if (parts.length !== 3) {
@@ -743,25 +944,27 @@ function normalizeCoolclawTarget(raw) {
743
944
  const [channel, type, id] = parts;
744
945
  return `${channel.toLowerCase()}:${type.toLowerCase()}:${id.trim()}`;
745
946
  }
746
- function inferCoolclawTargetChatType(raw) {
947
+ function inferCoolclawTargetChatType(raw, flavorInput) {
747
948
  try {
748
- const target = parseCoolclawTarget(raw);
949
+ const target = parseCoolclawTarget(raw, flavorInput);
749
950
  return target.kind === "private" ? "direct" : "group";
750
951
  } catch {
751
952
  return void 0;
752
953
  }
753
954
  }
754
- function isCoolclawTargetId(raw, normalized = normalizeCoolclawTarget(raw)) {
955
+ function isCoolclawTargetId(raw, normalized = normalizeCoolclawTarget(raw), flavorInput) {
956
+ const flavor = resolveFlavor(flavorInput);
755
957
  try {
756
- parseCoolclawTarget(normalized);
757
- return normalized.startsWith("coolclaw:");
958
+ parseCoolclawTarget(normalized, flavor);
959
+ return normalized.startsWith(`${flavor.targetPrefix}:`);
758
960
  } catch {
759
961
  return false;
760
962
  }
761
963
  }
762
- async function resolveCoolclawMessagingTarget(raw, preferredKind) {
763
- const normalized = normalizeCoolclawTarget(raw);
764
- 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);
765
968
  const kind = target.kind === "private" ? "user" : "group";
766
969
  if (preferredKind && preferredKind !== kind) {
767
970
  return null;
@@ -1027,6 +1230,334 @@ function parseErrorReason(error) {
1027
1230
  return error.error;
1028
1231
  }
1029
1232
 
1233
+ // src/arena-model-query.ts
1234
+ function parseArenaModelCurrent(rawText) {
1235
+ if (!rawText) return null;
1236
+ const match = /^\s*Current\s*[::]\s*(.+)$/im.exec(rawText);
1237
+ if (!match) return null;
1238
+ const firstToken = match[1].trim().split(/\s+/)[0] ?? "";
1239
+ const normalized = trimModelToken(firstToken);
1240
+ if (!normalized || normalized.length > 255 || normalized.endsWith("/")) return null;
1241
+ if (/^(usage|help|available|models?|current)$/i.test(normalized)) return null;
1242
+ return normalized;
1243
+ }
1244
+ async function submitArenaModelQueryCallback(input) {
1245
+ const start = Date.now();
1246
+ const rawHash = input.rawText ? sha256Hex(input.rawText) : "";
1247
+ const rawPreview = rawResponsePreview(input.rawText) ?? "";
1248
+ try {
1249
+ if (!input.meta.callbackUrl || !input.token) {
1250
+ input.log?.warn?.(
1251
+ `[ARENA-MODEL] callback skipped eventId=${input.meta.eventId} reason=missing_callback_or_token rawHash=${rawHash} rawPreview=${rawPreview}`
1252
+ );
1253
+ return { ok: false, error: "missing_callback_or_token" };
1254
+ }
1255
+ const fetchImpl = input.fetchImpl ?? globalThis.fetch;
1256
+ if (typeof fetchImpl !== "function") {
1257
+ input.log?.warn?.(`[ARENA-MODEL] callback skipped eventId=${input.meta.eventId} reason=fetch_unavailable`);
1258
+ return { ok: false, error: "fetch_unavailable" };
1259
+ }
1260
+ const response = await fetchImpl(input.meta.callbackUrl, {
1261
+ method: "POST",
1262
+ headers: {
1263
+ Authorization: `Bearer ${input.token}`,
1264
+ "Content-Type": "application/json"
1265
+ },
1266
+ body: JSON.stringify({
1267
+ eventId: input.meta.eventId,
1268
+ roomId: input.meta.roomId,
1269
+ seatNumber: input.meta.seatNumber,
1270
+ seatEpoch: input.meta.seatEpoch,
1271
+ rawText: input.rawText
1272
+ })
1273
+ });
1274
+ const body = await readJsonObject(response);
1275
+ const data = isRecord4(body.data) ? body.data : body;
1276
+ const accepted = data.accepted === true;
1277
+ const result = { ok: response.ok, status: response.status, accepted };
1278
+ const level = response.ok ? "info" : "warn";
1279
+ input.log?.[level]?.(
1280
+ `[ARENA-MODEL] callback status=${response.status} accepted=${accepted} eventId=${input.meta.eventId} roomId=${input.meta.roomId} seatEpoch=${input.meta.seatEpoch} elapsedMs=${Date.now() - start} rawHash=${rawHash} rawPreview=${rawPreview}`
1281
+ );
1282
+ return result;
1283
+ } catch (error) {
1284
+ const message = error instanceof Error ? error.message : String(error);
1285
+ input.log?.warn?.(
1286
+ `[ARENA-MODEL] callback failed eventId=${input.meta.eventId} roomId=${input.meta.roomId} seatEpoch=${input.meta.seatEpoch} elapsedMs=${Date.now() - start} err=${message} rawHash=${rawHash} rawPreview=${rawPreview}`
1287
+ );
1288
+ return { ok: false, error: message };
1289
+ }
1290
+ }
1291
+ function createArenaModelQueryReplyCollector(input) {
1292
+ const blocks = [];
1293
+ let submitted = false;
1294
+ const submit = input.submit ?? ((rawText) => submitArenaModelQueryCallback({
1295
+ meta: input.meta,
1296
+ token: input.token,
1297
+ rawText,
1298
+ log: input.log
1299
+ }));
1300
+ async function submitOnce(rawText, reason) {
1301
+ if (submitted) return;
1302
+ submitted = true;
1303
+ input.log?.info?.(
1304
+ `[ARENA-MODEL] dispatch submit reason=${reason} eventId=${input.meta.eventId} roomId=${input.meta.roomId} seatEpoch=${input.meta.seatEpoch} blocks=${blocks.length} parsed=${parseArenaModelCurrent(rawText) ? "true" : "false"}`
1305
+ );
1306
+ await submit(rawText);
1307
+ }
1308
+ return {
1309
+ async deliver(text) {
1310
+ if (submitted || !text) return;
1311
+ blocks.push(text);
1312
+ const full = blocks.join("");
1313
+ input.log?.info?.(
1314
+ `[ARENA-MODEL] dispatch block eventId=${input.meta.eventId} roomId=${input.meta.roomId} seatEpoch=${input.meta.seatEpoch} blocks=${blocks.length} parsed=${parseArenaModelCurrent(full) ? "true" : "false"}`
1315
+ );
1316
+ if (parseArenaModelCurrent(full)) {
1317
+ await submitOnce(full, "parsed");
1318
+ }
1319
+ },
1320
+ async finalize() {
1321
+ if (submitted) return;
1322
+ await submitOnce(blocks.join(""), "final");
1323
+ }
1324
+ };
1325
+ }
1326
+ function trimModelToken(token) {
1327
+ let value = token.trim();
1328
+ value = value.replace(/^[`"'“”‘’<({\[【]+/, "");
1329
+ value = value.replace(/[`"'“”‘’>)}\]】。..,,;;::]+$/g, "");
1330
+ return value.trim();
1331
+ }
1332
+ async function readJsonObject(response) {
1333
+ try {
1334
+ const body = await response.json();
1335
+ return isRecord4(body) ? body : {};
1336
+ } catch {
1337
+ return {};
1338
+ }
1339
+ }
1340
+ function isRecord4(value) {
1341
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1342
+ }
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
+
1030
1561
  // src/ws-client.ts
1031
1562
  import WebSocket from "ws";
1032
1563
  var CoolclawWsClient = class {
@@ -1239,18 +1770,18 @@ var CoolclawWsClient = class {
1239
1770
  }
1240
1771
  };
1241
1772
  function readPingInterval(payload) {
1242
- if (!isRecord4(payload) || typeof payload.pingIntervalMs !== "number" || payload.pingIntervalMs <= 0) {
1773
+ if (!isRecord6(payload) || typeof payload.pingIntervalMs !== "number" || payload.pingIntervalMs <= 0) {
1243
1774
  return void 0;
1244
1775
  }
1245
1776
  return payload.pingIntervalMs;
1246
1777
  }
1247
1778
  function readErrorMessage(payload) {
1248
- if (isRecord4(payload) && typeof payload.message === "string") {
1779
+ if (isRecord6(payload) && typeof payload.message === "string") {
1249
1780
  return payload.message;
1250
1781
  }
1251
1782
  return "CoolClaw request failed";
1252
1783
  }
1253
- function isRecord4(value) {
1784
+ function isRecord6(value) {
1254
1785
  return typeof value === "object" && value !== null;
1255
1786
  }
1256
1787
 
@@ -1272,9 +1803,12 @@ function getPluginVersion() {
1272
1803
  }
1273
1804
 
1274
1805
  // src/channel.ts
1806
+ import { homedir as homedir3 } from "os";
1807
+ import path2 from "path";
1275
1808
  import {
1276
1809
  createChatChannelPlugin
1277
1810
  } from "openclaw/plugin-sdk/core";
1811
+ var productFlavor = activeFlavor();
1278
1812
  function createAccountStatusSink(params) {
1279
1813
  return (patch) => {
1280
1814
  params.setStatus({ accountId: params.accountId, ...patch });
@@ -1406,6 +1940,18 @@ function isNoReplyText(text) {
1406
1940
  const lastLine = lines.at(-1)?.toUpperCase();
1407
1941
  return lastLine ? noReplyTokens.has(lastLine) : false;
1408
1942
  }
1943
+ function shouldSuppressCoolclawTextDelivery(envelope) {
1944
+ return envelope.metadata?.gameEvent === true || isArenaReportShareEnvelope(envelope) || isArenaModelQueryEnvelope(envelope) || isArenaVoiceSelectEnvelope(envelope);
1945
+ }
1946
+ async function finalizeArenaModelQueryAfterDispatchError(params) {
1947
+ if (!params.collector) {
1948
+ return false;
1949
+ }
1950
+ const errMsg = params.error instanceof Error ? params.error.message : String(params.error);
1951
+ params.log?.warn?.(`[ARENA-MODEL] dispatch failed; submitting fallback callback eventId=${params.eventId ?? ""} err=${errMsg}`);
1952
+ await params.collector.finalize();
1953
+ return true;
1954
+ }
1409
1955
  var runtimeClients = /* @__PURE__ */ new Map();
1410
1956
  function setRuntimeClient(accountKey, client) {
1411
1957
  runtimeClients.set(accountKey, client);
@@ -1417,19 +1963,19 @@ function clearRuntimeClient(accountKey) {
1417
1963
  runtimeClients.delete(accountKey);
1418
1964
  }
1419
1965
  function extractAccountFromConfig(cfg, accountId) {
1420
- const coolclawSection = cfg.channels?.coolclaw;
1421
- const accounts = coolclawSection?.accounts;
1966
+ const channelSection = cfg.channels?.[productFlavor.channelId];
1967
+ const accounts = channelSection?.accounts;
1422
1968
  return accounts?.[accountId ?? "default"] ?? {};
1423
1969
  }
1424
1970
  var coolclawChannelPlugin = createChatChannelPlugin({
1425
1971
  base: {
1426
- id: "coolclaw",
1972
+ id: productFlavor.channelId,
1427
1973
  meta: {
1428
- id: "coolclaw",
1429
- label: "CoolClaw",
1430
- selectionLabel: "CoolClaw",
1431
- docsPath: "/plugins/coolclaw",
1432
- 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.`
1433
1979
  },
1434
1980
  capabilities: {
1435
1981
  chatTypes: ["direct", "group"],
@@ -1444,15 +1990,15 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1444
1990
  },
1445
1991
  agentPrompt: {
1446
1992
  messageToolHints: () => [
1447
- "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>'.`,
1448
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.",
1449
- "When sending a message to a CoolClaw group, the agent will only reply if it was mentioned in the group message.",
1450
- "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.`
1451
1997
  ]
1452
1998
  },
1453
1999
  config: {
1454
2000
  listAccountIds(cfg) {
1455
- return Object.keys(cfg.channels?.coolclaw?.accounts ?? {});
2001
+ return Object.keys(cfg.channels?.[productFlavor.channelId]?.accounts ?? {});
1456
2002
  },
1457
2003
  resolveAccount(cfg, accountId) {
1458
2004
  return extractAccountFromConfig(cfg, accountId);
@@ -1484,9 +2030,9 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1484
2030
  async resolveTargets({ inputs }) {
1485
2031
  return inputs.map((input) => {
1486
2032
  try {
1487
- const normalized = normalizeCoolclawTarget(input);
2033
+ const normalized = normalizeCoolclawTarget(input, productFlavor);
1488
2034
  const [, type, id] = normalized.split(":");
1489
- parseCoolclawTarget(normalized);
2035
+ parseCoolclawTarget(normalized, productFlavor);
1490
2036
  return { input, resolved: true, id: normalized, name: `${type}:${id}` };
1491
2037
  } catch (error) {
1492
2038
  return { input, resolved: false, note: error instanceof Error ? error.message : String(error) };
@@ -1497,23 +2043,23 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1497
2043
  messaging: {
1498
2044
  normalizeTarget(raw) {
1499
2045
  try {
1500
- const normalized = normalizeCoolclawTarget(raw);
1501
- parseCoolclawTarget(normalized);
2046
+ const normalized = normalizeCoolclawTarget(raw, productFlavor);
2047
+ parseCoolclawTarget(normalized, productFlavor);
1502
2048
  return normalized;
1503
2049
  } catch {
1504
2050
  return void 0;
1505
2051
  }
1506
2052
  },
1507
2053
  inferTargetChatType({ to }) {
1508
- return inferCoolclawTargetChatType(to);
2054
+ return inferCoolclawTargetChatType(to, productFlavor);
1509
2055
  },
1510
2056
  targetResolver: {
1511
- 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>.`,
1512
2058
  looksLikeId(raw, normalized) {
1513
- return isCoolclawTargetId(raw, normalized);
2059
+ return isCoolclawTargetId(raw, normalized, productFlavor);
1514
2060
  },
1515
2061
  resolveTarget({ input, normalized, preferredKind }) {
1516
- return resolveCoolclawMessagingTarget(normalized || input, preferredKind);
2062
+ return resolveCoolclawMessagingTarget(normalized || input, preferredKind, productFlavor);
1517
2063
  }
1518
2064
  }
1519
2065
  },
@@ -1522,17 +2068,19 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1522
2068
  const account = coolclawChannelPlugin.config.resolveAccount(ctx.cfg, ctx.accountId);
1523
2069
  const token = await resolveAccountToken(account);
1524
2070
  if (!account.gatewayUrl || !account.agentId || !token) {
1525
- ctx.log?.error(`[${ctx.accountId}] CoolClaw account is not fully configured`);
2071
+ ctx.log?.error(`[${ctx.accountId}] ${productFlavor.displayName} account is not fully configured`);
1526
2072
  return;
1527
2073
  }
1528
- const accountKey = `coolclaw:${ctx.accountId ?? "default"}`;
1529
- 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
+ );
1530
2078
  const statusSink = createAccountStatusSink({
1531
2079
  accountId: ctx.accountId,
1532
2080
  setStatus: ctx.setStatus
1533
2081
  });
1534
2082
  statusSink({ statusState: "connecting" });
1535
- ctx.log?.info(`[${ctx.accountId}] starting CoolClaw provider (${account.gatewayUrl})`);
2083
+ ctx.log?.info(`[${ctx.accountId}] starting ${productFlavor.displayName} provider (${account.gatewayUrl})`);
1536
2084
  await runPassiveAccountLifecycle({
1537
2085
  abortSignal: ctx.abortSignal,
1538
2086
  start: async () => {
@@ -1555,18 +2103,55 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1555
2103
  dispatch: async (envelope) => {
1556
2104
  const isGameEvent = envelope.metadata?.gameEvent === true;
1557
2105
  const isArenaReportShare = isArenaReportShareEnvelope(envelope);
2106
+ const isArenaModelQuery = isArenaModelQueryEnvelope(envelope);
2107
+ const isArenaVoiceSelect = isArenaVoiceSelectEnvelope(envelope);
2108
+ const suppressChatTextDelivery = shouldSuppressCoolclawTextDelivery(envelope);
1558
2109
  const gameMeta = isGameEvent ? envelope.metadata : null;
2110
+ const modelQueryMeta = isArenaModelQuery ? envelope.metadata : null;
2111
+ const voiceSelectMeta = isArenaVoiceSelect ? envelope.metadata : null;
1559
2112
  let gameSubmitted = false;
1560
2113
  let gameFallbackReason = null;
1561
2114
  let gameModelActionType;
1562
2115
  let gameValidationReason;
1563
2116
  let gameModelActionRejected;
1564
2117
  const gameBuffer = [];
2118
+ const modelQueryCollector = modelQueryMeta ? createArenaModelQueryReplyCollector({
2119
+ meta: modelQueryMeta,
2120
+ token,
2121
+ log: ctx.log
2122
+ }) : null;
2123
+ const voiceSelectCollector = voiceSelectMeta ? createArenaVoiceSelectReplyCollector({
2124
+ meta: voiceSelectMeta,
2125
+ token,
2126
+ log: ctx.log
2127
+ }) : null;
1565
2128
  if (isGameEvent && gameMeta) {
1566
2129
  ctx.log?.info?.(
1567
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}`
1568
2131
  );
1569
2132
  }
2133
+ if (modelQueryMeta) {
2134
+ let callbackHost = "";
2135
+ try {
2136
+ callbackHost = new URL(modelQueryMeta.callbackUrl).host;
2137
+ } catch {
2138
+ callbackHost = "invalid";
2139
+ }
2140
+ ctx.log?.info?.(
2141
+ `[ARENA-MODEL] inbound eventId=${modelQueryMeta.eventId} traceId=${modelQueryMeta.traceId ?? ""} roomId=${modelQueryMeta.roomId} seatEpoch=${modelQueryMeta.seatEpoch} callbackHost=${callbackHost} conversationId=${envelope.conversationId}`
2142
+ );
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
+ }
1570
2155
  const runtime = getCoolclawRuntime();
1571
2156
  let runtimeChannel;
1572
2157
  try {
@@ -1576,7 +2161,7 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1576
2161
  });
1577
2162
  } catch (err) {
1578
2163
  logInboundDrop({ log: ctx.log?.warn?.bind(ctx.log) ?? (() => {
1579
- }), channel: "coolclaw", reason: "runtime not available; skipping dispatch" });
2164
+ }), channel: productFlavor.channelId, reason: "runtime not available; skipping dispatch" });
1580
2165
  if (isGameEvent && gameMeta) {
1581
2166
  const submitted = await submitBackendFallbackWithLog({
1582
2167
  meta: gameMeta,
@@ -1589,6 +2174,24 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1589
2174
  }
1590
2175
  return;
1591
2176
  }
2177
+ if (modelQueryMeta) {
2178
+ await submitArenaModelQueryCallback({
2179
+ meta: modelQueryMeta,
2180
+ token,
2181
+ rawText: "",
2182
+ log: ctx.log
2183
+ });
2184
+ return;
2185
+ }
2186
+ if (voiceSelectMeta) {
2187
+ await submitArenaVoiceSelectCallback({
2188
+ meta: voiceSelectMeta,
2189
+ token,
2190
+ rawText: "",
2191
+ log: ctx.log
2192
+ });
2193
+ return;
2194
+ }
1592
2195
  throw err;
1593
2196
  }
1594
2197
  try {
@@ -1601,7 +2204,7 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1601
2204
  }
1602
2205
  const route = await runtimeChannel.routing.resolveAgentRoute({
1603
2206
  cfg: ctx.cfg,
1604
- channel: "coolclaw",
2207
+ channel: productFlavor.channelId,
1605
2208
  accountId: ctx.accountId,
1606
2209
  peer
1607
2210
  });
@@ -1614,18 +2217,18 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1614
2217
  ctx.cfg.session?.store,
1615
2218
  { agentId: route.agentId }
1616
2219
  );
1617
- const senderLabel = envelope.sender ? `${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}` : isGameEvent ? `game:${gameMeta.gameId}` : "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";
1618
2221
  let deliveryTarget;
1619
2222
  if (envelope.group) {
1620
- deliveryTarget = `coolclaw:group:${envelope.group.groupId}`;
2223
+ deliveryTarget = `${productFlavor.targetPrefix}:group:${envelope.group.groupId}`;
1621
2224
  } else if (envelope.sender) {
1622
- deliveryTarget = `coolclaw:${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}`;
1623
- } else if (isGameEvent) {
1624
- 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}`;
1625
2228
  } else {
1626
- deliveryTarget = normalizeCoolclawTarget(envelope.conversationId);
2229
+ deliveryTarget = normalizeCoolclawTarget(envelope.conversationId, productFlavor);
1627
2230
  }
1628
- const bodyForAgent = buildBodyForAgent(envelope);
2231
+ const bodyForAgent = buildBodyForAgent(envelope, productFlavor);
1629
2232
  if (typeof runtimeChannel.reply?.finalizeInboundContext !== "function") {
1630
2233
  throw new Error(
1631
2234
  "CoolClaw requires runtime.channel.reply.finalizeInboundContext. Please upgrade OpenClaw to >=2026.3.22."
@@ -1646,15 +2249,15 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1646
2249
  BodyForAgent: bodyForAgent,
1647
2250
  RawBody: envelope.text,
1648
2251
  CommandBody: envelope.text,
1649
- From: `coolclaw:${senderLabel}`,
2252
+ From: `${productFlavor.targetPrefix}:${senderLabel}`,
1650
2253
  To: deliveryTarget,
1651
2254
  SessionKey: route.sessionKey,
1652
2255
  AccountId: ctx.accountId,
1653
2256
  ChatType: isGroup ? "channel" : "direct",
1654
2257
  CommandAuthorized: true,
1655
- Provider: "coolclaw",
1656
- Surface: "coolclaw",
1657
- Channel: "coolclaw",
2258
+ Provider: productFlavor.channelId,
2259
+ Surface: productFlavor.channelId,
2260
+ Channel: productFlavor.channelId,
1658
2261
  Peer: peer,
1659
2262
  WasMentioned: envelope.shouldReply,
1660
2263
  Mentioned: envelope.shouldReply
@@ -1665,7 +2268,7 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1665
2268
  storePath,
1666
2269
  sessionKey,
1667
2270
  ctx: ctxPayload,
1668
- 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,
1669
2272
  onRecordError: (err) => {
1670
2273
  ctx.log?.warn(`recordInboundSession failed: ${err instanceof Error ? err.message : String(err)}`);
1671
2274
  }
@@ -1684,6 +2287,14 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1684
2287
  if (!payload.text) return;
1685
2288
  const replyText = String(payload.text);
1686
2289
  if (!isGameEvent && isNoReplyText(replyText)) return;
2290
+ if (modelQueryCollector) {
2291
+ await modelQueryCollector.deliver(replyText);
2292
+ return;
2293
+ }
2294
+ if (voiceSelectCollector) {
2295
+ await voiceSelectCollector.deliver(replyText);
2296
+ return;
2297
+ }
1687
2298
  if (isGameEvent && gameMeta) {
1688
2299
  if (gameSubmitted) return;
1689
2300
  gameBuffer.push(String(payload.text));
@@ -1727,14 +2338,18 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1727
2338
  ctx.log?.info?.(`[ARENA-REPORT-SHARE] ignored non-chat completion text eventId=${envelope.metadata.eventId ?? ""}`);
1728
2339
  return;
1729
2340
  }
2341
+ if (suppressChatTextDelivery) {
2342
+ ctx.log?.error?.(`[ARENA-CALLBACK] chat delivery blocked eventId=${envelope.metadata.eventId ?? ""}`);
2343
+ return;
2344
+ }
1730
2345
  try {
1731
2346
  let replyTarget;
1732
2347
  if (envelope.group) {
1733
- replyTarget = `coolclaw:group:${envelope.group.groupId}`;
2348
+ replyTarget = `${productFlavor.targetPrefix}:group:${envelope.group.groupId}`;
1734
2349
  } else if (envelope.sender) {
1735
- replyTarget = `coolclaw:${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}`;
2350
+ replyTarget = `${productFlavor.targetPrefix}:${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}`;
1736
2351
  } else {
1737
- replyTarget = normalizeCoolclawTarget(envelope.conversationId);
2352
+ replyTarget = normalizeCoolclawTarget(envelope.conversationId, productFlavor);
1738
2353
  }
1739
2354
  await sendText({ client: wsClient, target: replyTarget, text: replyText });
1740
2355
  } catch (err) {
@@ -1751,9 +2366,28 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1751
2366
  gameFallbackReason = "no_valid_action_in_llm_output";
1752
2367
  }
1753
2368
  }
2369
+ if (modelQueryCollector) {
2370
+ await modelQueryCollector.finalize();
2371
+ }
2372
+ if (voiceSelectCollector) {
2373
+ await voiceSelectCollector.finalize();
2374
+ }
1754
2375
  } catch (err) {
1755
2376
  const errMsg = err instanceof Error ? err.message : String(err);
1756
2377
  ctx.log?.error(`Inbound dispatch error: ${errMsg}`);
2378
+ if (await finalizeArenaModelQueryAfterDispatchError({
2379
+ collector: modelQueryCollector,
2380
+ eventId: modelQueryMeta?.eventId,
2381
+ error: err,
2382
+ log: ctx.log
2383
+ })) {
2384
+ return;
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
+ }
1757
2391
  if (isGameEvent && gameMeta && !gameSubmitted) {
1758
2392
  gameFallbackReason = `dispatch_error: ${errMsg}`;
1759
2393
  }
@@ -1804,7 +2438,7 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1804
2438
  ctx.log?.debug?.(`ACK sent: type=${ackFrame.type} lastAckedSeq=${ackFrame.payload?.lastAckedSeq}`);
1805
2439
  } catch (err) {
1806
2440
  logAckFailure({ log: ctx.log?.warn?.bind(ctx.log) ?? (() => {
1807
- }), channel: "coolclaw", error: err });
2441
+ }), channel: productFlavor.channelId, error: err });
1808
2442
  throw err;
1809
2443
  }
1810
2444
  }
@@ -1817,7 +2451,7 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1817
2451
  await client.start();
1818
2452
  setRuntimeClient(accountKey, client);
1819
2453
  statusSink({ statusState: "connected" });
1820
- ctx.log?.info(`[${ctx.accountId}] CoolClaw provider connected`);
2454
+ ctx.log?.info(`[${ctx.accountId}] ${productFlavor.displayName} provider connected`);
1821
2455
  return client;
1822
2456
  },
1823
2457
  stop: async (client) => {
@@ -1826,25 +2460,25 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1826
2460
  },
1827
2461
  onStop: () => {
1828
2462
  statusSink({ statusState: "disconnected" });
1829
- ctx.log?.info(`[${ctx.accountId}] CoolClaw provider stopped`);
2463
+ ctx.log?.info(`[${ctx.accountId}] ${productFlavor.displayName} provider stopped`);
1830
2464
  }
1831
2465
  });
1832
2466
  },
1833
2467
  /** 显式停止账户连接,清理 WebSocket 客户端资源 */
1834
2468
  async stopAccount(ctx) {
1835
- const accountKey = `coolclaw:${ctx.accountId ?? "default"}`;
2469
+ const accountKey = `${productFlavor.channelId}:${ctx.accountId ?? "default"}`;
1836
2470
  const client = getRuntimeClient(accountKey);
1837
2471
  if (client) {
1838
2472
  await client.stop();
1839
2473
  clearRuntimeClient(accountKey);
1840
- ctx.log?.info(`[${ctx.accountId}] CoolClaw client stopped via stopAccount`);
2474
+ ctx.log?.info(`[${ctx.accountId}] ${productFlavor.displayName} client stopped via stopAccount`);
1841
2475
  }
1842
2476
  }
1843
2477
  }
1844
2478
  },
1845
2479
  security: {
1846
2480
  dm: {
1847
- channelKey: "coolclaw",
2481
+ channelKey: productFlavor.channelId,
1848
2482
  resolvePolicy: (account) => account.dmPolicy ?? "allowlist",
1849
2483
  resolveAllowFrom: (account) => account.allowFrom ?? [],
1850
2484
  defaultPolicy: "allowlist",
@@ -1853,10 +2487,10 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1853
2487
  },
1854
2488
  pairing: {
1855
2489
  text: {
1856
- idLabel: "CoolClaw user ID",
2490
+ idLabel: `${productFlavor.displayName} user ID`,
1857
2491
  message: "You are not authorized to message this agent. Send this pairing code to verify:",
1858
2492
  notify: async ({ id, message }) => {
1859
- const client = getRuntimeClient("coolclaw:default");
2493
+ const client = getRuntimeClient(`${productFlavor.channelId}:default`);
1860
2494
  if (client) {
1861
2495
  await sendText({
1862
2496
  client,
@@ -1875,11 +2509,11 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1875
2509
  deliveryMode: "direct",
1876
2510
  resolveTarget({ to }) {
1877
2511
  if (!to) {
1878
- return { ok: false, error: new Error("CoolClaw target is required") };
2512
+ return { ok: false, error: new Error(`${productFlavor.displayName} target is required`) };
1879
2513
  }
1880
2514
  try {
1881
- const normalized = normalizeCoolclawTarget(to);
1882
- parseCoolclawTarget(normalized);
2515
+ const normalized = normalizeCoolclawTarget(to, productFlavor);
2516
+ parseCoolclawTarget(normalized, productFlavor);
1883
2517
  return { ok: true, to: normalized };
1884
2518
  } catch (error) {
1885
2519
  return { ok: false, error: error instanceof Error ? error : new Error(String(error)) };
@@ -1887,14 +2521,14 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1887
2521
  }
1888
2522
  },
1889
2523
  attachedResults: {
1890
- channel: "coolclaw",
2524
+ channel: productFlavor.channelId,
1891
2525
  async sendText(ctx) {
1892
2526
  const account = coolclawChannelPlugin.config.resolveAccount(ctx.cfg, ctx.accountId);
1893
2527
  const token = await resolveAccountToken(account);
1894
2528
  if (!account.gatewayUrl || !account.agentId || !token) {
1895
- throw new Error("CoolClaw account is not fully configured");
2529
+ throw new Error(`${productFlavor.displayName} account is not fully configured`);
1896
2530
  }
1897
- const accountKey = `coolclaw:${ctx.accountId ?? "default"}`;
2531
+ const accountKey = `${productFlavor.channelId}:${ctx.accountId ?? "default"}`;
1898
2532
  let client = getRuntimeClient(accountKey);
1899
2533
  if (!client || !client.isConnected()) {
1900
2534
  client = new CoolclawWsClient({
@@ -1920,9 +2554,9 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1920
2554
  const account = coolclawChannelPlugin.config.resolveAccount(ctx.cfg, ctx.accountId);
1921
2555
  const token = await resolveAccountToken(account);
1922
2556
  if (!account.gatewayUrl || !account.agentId || !token) {
1923
- throw new Error("CoolClaw account is not fully configured");
2557
+ throw new Error(`${productFlavor.displayName} account is not fully configured`);
1924
2558
  }
1925
- const accountKey = `coolclaw:${ctx.accountId ?? "default"}`;
2559
+ const accountKey = `${productFlavor.channelId}:${ctx.accountId ?? "default"}`;
1926
2560
  let client = getRuntimeClient(accountKey);
1927
2561
  if (!client || !client.isConnected()) {
1928
2562
  client = new CoolclawWsClient({
@@ -1947,13 +2581,17 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1947
2581
  }
1948
2582
  }
1949
2583
  });
1950
- function buildBodyForAgent(envelope) {
2584
+ function buildBodyForAgent(envelope, flavorInput) {
2585
+ const flavor = resolveFlavor(flavorInput);
2586
+ if (isArenaVoiceSelectEnvelope(envelope)) {
2587
+ return buildArenaVoiceSelectBodyForAgent(envelope, flavor);
2588
+ }
1951
2589
  if (isArenaReportShareEnvelope(envelope)) {
1952
- return buildArenaReportShareBodyForAgent(envelope);
2590
+ return buildArenaReportShareBodyForAgent(envelope, flavor);
1953
2591
  }
1954
2592
  if (!envelope.group) {
1955
2593
  if (envelope.sender && envelope.recipient) {
1956
- return buildPrivateBodyForAgent(envelope);
2594
+ return buildPrivateBodyForAgent(envelope, flavor);
1957
2595
  }
1958
2596
  const hints = [
1959
2597
  envelope.metadata?.securityHint,
@@ -1964,7 +2602,7 @@ function buildBodyForAgent(envelope) {
1964
2602
  const sender = formatUserRef(envelope.sender, "\u672A\u77E5\u53D1\u9001\u4EBA");
1965
2603
  const owner = formatUserRef(envelope.owner, "\u672A\u77E5\u4E3B\u4EBA");
1966
2604
  const lines = [
1967
- "\u4F60\u6536\u5230\u4E00\u6761 CoolClaw \u7FA4\u804A\u6D88\u606F\u3002",
2605
+ `\u4F60\u6536\u5230\u4E00\u6761 ${flavor.displayName} \u7FA4\u804A\u6D88\u606F\u3002`,
1968
2606
  "",
1969
2607
  `\u7FA4\u804A\uFF1A${envelope.group.groupName}(${envelope.group.groupId})`,
1970
2608
  `\u53D1\u9001\u4EBA\uFF1A${sender}`,
@@ -1972,7 +2610,7 @@ function buildBodyForAgent(envelope) {
1972
2610
  envelope.text,
1973
2611
  "",
1974
2612
  "\u8EAB\u4EFD\u4E0A\u4E0B\u6587\uFF1A",
1975
- `\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`,
1976
2614
  "",
1977
2615
  "---",
1978
2616
  "\u7CFB\u7EDF\u63D0\u793A\uFF1A",
@@ -1996,7 +2634,44 @@ function buildBodyForAgent(envelope) {
1996
2634
  }
1997
2635
  return lines.join("\n");
1998
2636
  }
1999
- 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) {
2000
2675
  const payload = isPlainRecord(envelope.metadata.reportPayload) ? envelope.metadata.reportPayload : {};
2001
2676
  const gameId = formatUnknown(payload.gameId);
2002
2677
  const roomId = formatUnknown(payload.roomId);
@@ -2009,7 +2684,7 @@ function buildArenaReportShareBodyForAgent(envelope) {
2009
2684
  const eventId = formatUnknown(envelope.metadata.eventId);
2010
2685
  const traceId = formatUnknown(envelope.metadata.traceId);
2011
2686
  return [
2012
- "\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`,
2013
2688
  "",
2014
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",
2015
2690
  `eventId\uFF1A${eventId}`,
@@ -2025,14 +2700,14 @@ function buildArenaReportShareBodyForAgent(envelope) {
2025
2700
  `result\uFF1A${result}`,
2026
2701
  "",
2027
2702
  "\u6267\u884C\u6B65\u9AA4\uFF1A",
2028
- "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`,
2029
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",
2030
2705
  `3. \u4F7F\u7528 Gateway Base URL \u62FC\u63A5 replayApiPath=${replayApiPath} \u8BFB\u53D6\u672C\u5C40\u6218\u62A5\u3002`,
2031
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`,
2032
2707
  `5. \u4F7F\u7528\u540C\u4E00\u4E2A Gateway Base URL \u62FC\u63A5 createPostApiPath=${createPostApiPath} \u53D1\u5E03\u5E16\u5B50\u3002`,
2033
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",
2034
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",
2035
- "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`,
2036
2711
  "",
2037
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"
2038
2713
  ].join("\n");
@@ -2046,18 +2721,18 @@ function formatUnknown(value) {
2046
2721
  if (typeof value === "number" || typeof value === "boolean") return String(value);
2047
2722
  return JSON.stringify(value);
2048
2723
  }
2049
- function buildPrivateBodyForAgent(envelope) {
2724
+ function buildPrivateBodyForAgent(envelope, flavor) {
2050
2725
  const sender = formatUserRef(envelope.sender, "\u672A\u77E5\u53D1\u9001\u4EBA");
2051
2726
  const owner = formatUserRef(envelope.owner, "\u672A\u77E5\u4E3B\u4EBA");
2052
2727
  const lines = [
2053
- "\u4F60\u6536\u5230\u4E00\u6761 CoolClaw \u79C1\u804A\u6D88\u606F\u3002",
2728
+ `\u4F60\u6536\u5230\u4E00\u6761 ${flavor.displayName} \u79C1\u804A\u6D88\u606F\u3002`,
2054
2729
  "",
2055
2730
  `\u53D1\u9001\u4EBA\uFF1A${sender}`,
2056
2731
  "\u6D88\u606F\u5185\u5BB9\uFF1A",
2057
2732
  envelope.text,
2058
2733
  "",
2059
2734
  "\u8EAB\u4EFD\u4E0A\u4E0B\u6587\uFF1A",
2060
- `\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`,
2061
2736
  "",
2062
2737
  "---",
2063
2738
  "\u7CFB\u7EDF\u63D0\u793A\uFF1A",
@@ -2087,6 +2762,7 @@ function normalizeDirectHintLines(raw) {
2087
2762
  }
2088
2763
 
2089
2764
  export {
2765
+ activeFlavor,
2090
2766
  defaultBindingFile,
2091
2767
  CoolclawConfigSchema,
2092
2768
  setCoolclawRuntime,