@coolclaw/coolclaw 1.0.15 → 1.0.17

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;
@@ -122,6 +180,7 @@ var FileAckStore = class {
122
180
 
123
181
  // src/agent-task.ts
124
182
  import { createHash } from "crypto";
183
+ var WEREWOLF_STRUCTURED_ACTION_PROTOCOL_VERSION = "WEREWOLF_STRUCTURED_JSON_MESSAGE_V1";
125
184
  function normalizeAgentTask(value) {
126
185
  if (!isRecord(value)) return null;
127
186
  const actionContract = normalizeActionContract(value.actionContract);
@@ -133,6 +192,9 @@ function normalizeAgentTask(value) {
133
192
  renderedPrompt: readString(value.renderedPrompt) ?? "",
134
193
  renderedPromptHash: readString(value.renderedPromptHash),
135
194
  actionFormat: readString(value.actionFormat),
195
+ actionProtocolVersion: readString(value.actionProtocolVersion),
196
+ outputSchema: isRecord(value.outputSchema) ? value.outputSchema : void 0,
197
+ retryPolicy: normalizeRetryPolicy(value.retryPolicy),
136
198
  conversationKey: readString(value.conversationKey),
137
199
  actionContract,
138
200
  fallbackAction,
@@ -142,6 +204,9 @@ function normalizeAgentTask(value) {
142
204
  function isDispatchableAgentTask(task) {
143
205
  return task.requiresReply === true && task.renderedPrompt.trim().length > 0 && allowedActionTypes(task).length > 0;
144
206
  }
207
+ function isStructuredJsonTask(task) {
208
+ return task.actionProtocolVersion === WEREWOLF_STRUCTURED_ACTION_PROTOCOL_VERSION || task.actionFormat === "STRICT_JSON_OBJECT";
209
+ }
145
210
  function validateAgentAction(action, task) {
146
211
  if (!isRecord(action.actionData)) {
147
212
  return { ok: false, reason: "invalid_action_shape" };
@@ -158,12 +223,18 @@ function validateAgentAction(action, task) {
158
223
  if (!matchesActionDataSchema(repaired.actionData, option.actionDataSchema)) {
159
224
  return { ok: false, reason: "invalid_action_shape" };
160
225
  }
226
+ if (isStructuredJsonTask(task) && !matchesRootOutputSchema(action, task.outputSchema)) {
227
+ return { ok: false, reason: "invalid_action_shape" };
228
+ }
229
+ const normalizedAction = {
230
+ actionType: action.actionType,
231
+ actionData: repaired.actionData
232
+ };
233
+ if (typeof action.speech === "string") normalizedAction.speech = action.speech;
234
+ if (typeof action.reason === "string") normalizedAction.reason = action.reason;
161
235
  return {
162
236
  ok: true,
163
- action: {
164
- actionType: action.actionType,
165
- actionData: repaired.actionData
166
- },
237
+ action: normalizedAction,
167
238
  repairReason: repaired.repairReason
168
239
  };
169
240
  }
@@ -193,6 +264,14 @@ function normalizeActionContract(value) {
193
264
  finalOutputRules: Array.isArray(value.finalOutputRules) ? value.finalOutputRules.filter((rule) => typeof rule === "string") : void 0
194
265
  };
195
266
  }
267
+ function normalizeRetryPolicy(value) {
268
+ if (!isRecord(value)) return void 0;
269
+ return {
270
+ maxRetries: readNumber(value.maxRetries),
271
+ shareDeadline: typeof value.shareDeadline === "boolean" ? value.shareDeadline : void 0,
272
+ rejectedOutputActionType: readString(value.rejectedOutputActionType)
273
+ };
274
+ }
196
275
  function normalizeActionOption(value) {
197
276
  if (!isRecord(value) || typeof value.actionType !== "string" || value.actionType.length === 0) {
198
277
  return [];
@@ -208,20 +287,17 @@ function allowedActionTypes(task) {
208
287
  }
209
288
  function matchesActionDataSchema(actionData, schema) {
210
289
  if (!schema || Object.keys(schema).length === 0) return true;
211
- if (typeof schema.type === "string" && schema.type !== "object") return false;
212
- const properties = isRecord(schema.properties) ? schema.properties : {};
213
- const required = Array.isArray(schema.required) ? schema.required.filter((field) => typeof field === "string" && field.length > 0) : [];
214
- for (const field of required) {
215
- if (!Object.prototype.hasOwnProperty.call(actionData, field)) return false;
216
- if (!matchesSchemaValue(actionData[field], properties[field], true)) return false;
217
- }
218
- for (const [field, value] of Object.entries(actionData)) {
219
- const propertySchema = properties[field];
220
- if (propertySchema !== void 0 && !matchesSchemaValue(value, propertySchema, false)) {
221
- return false;
222
- }
223
- }
224
- return true;
290
+ return matchesSchemaValue(actionData, schema, true);
291
+ }
292
+ function matchesRootOutputSchema(action, schema) {
293
+ if (!schema || Object.keys(schema).length === 0) return true;
294
+ const root = {
295
+ actionType: action.actionType,
296
+ actionData: action.actionData
297
+ };
298
+ if (typeof action.speech === "string") root.speech = action.speech;
299
+ if (typeof action.reason === "string") root.reason = action.reason;
300
+ return matchesSchemaValue(root, schema, true);
225
301
  }
226
302
  function repairActionDataForSchema(actionData, schema) {
227
303
  if (!schema || Object.keys(schema).length === 0) {
@@ -257,12 +333,28 @@ function repairActionDataForSchema(actionData, schema) {
257
333
  };
258
334
  }
259
335
  function matchesSchemaValue(value, schema, required) {
260
- if (value == null) return !required;
261
336
  if (!isRecord(schema)) return true;
337
+ if (value == null) {
338
+ return !required || schemaAllowsType(schema.type, "null");
339
+ }
340
+ const oneOf = Array.isArray(schema.oneOf) ? schema.oneOf : [];
341
+ if (oneOf.length > 0) {
342
+ return oneOf.some((variant) => matchesSchemaValue(value, variant, required));
343
+ }
262
344
  if (Array.isArray(schema.enum) && !schema.enum.includes(value)) {
263
345
  return false;
264
346
  }
265
- switch (schema.type) {
347
+ const allowedTypes = Array.isArray(schema.type) ? schema.type.filter((type) => typeof type === "string") : typeof schema.type === "string" ? [schema.type] : [];
348
+ if (allowedTypes.length === 0) {
349
+ return true;
350
+ }
351
+ return allowedTypes.some((type) => matchesSchemaTypedValue(value, schema, type));
352
+ }
353
+ function schemaAllowsType(typeNode, type) {
354
+ return Array.isArray(typeNode) ? typeNode.includes(type) : typeNode === type;
355
+ }
356
+ function matchesSchemaTypedValue(value, schema, type) {
357
+ switch (type) {
266
358
  case "integer":
267
359
  return typeof value === "number" && Number.isInteger(value);
268
360
  case "number":
@@ -272,13 +364,33 @@ function matchesSchemaValue(value, schema, required) {
272
364
  case "boolean":
273
365
  return typeof value === "boolean";
274
366
  case "object":
275
- return isRecord(value);
367
+ return matchesObjectSchema(value, schema);
276
368
  case "array":
277
369
  return Array.isArray(value);
370
+ case "null":
371
+ return value == null;
278
372
  default:
279
373
  return true;
280
374
  }
281
375
  }
376
+ function matchesObjectSchema(value, schema) {
377
+ if (!isRecord(value)) return false;
378
+ const properties = isRecord(schema.properties) ? schema.properties : {};
379
+ const required = Array.isArray(schema.required) ? schema.required.filter((field) => typeof field === "string" && field.length > 0) : [];
380
+ for (const field of required) {
381
+ if (!Object.prototype.hasOwnProperty.call(value, field)) return false;
382
+ if (!matchesSchemaValue(value[field], properties[field], true)) return false;
383
+ }
384
+ for (const [field, fieldValue] of Object.entries(value)) {
385
+ const propertySchema = properties[field];
386
+ if (propertySchema === void 0) {
387
+ if (schema.additionalProperties === false) return false;
388
+ continue;
389
+ }
390
+ if (!matchesSchemaValue(fieldValue, propertySchema, false)) return false;
391
+ }
392
+ return true;
393
+ }
282
394
  function readString(value) {
283
395
  return typeof value === "string" && value.length > 0 ? value : void 0;
284
396
  }
@@ -358,14 +470,19 @@ function isRecord2(value) {
358
470
  // src/inbound.ts
359
471
  var ARENA_REPORT_SHARE_NOTIFY_TYPE = "ARENA_REPORT_SHARE_REQUEST";
360
472
  var ARENA_MODEL_QUERY_NOTIFY_TYPE = "ARENA_MODEL_QUERY_REQUEST";
473
+ var ARENA_VOICE_SELECT_NOTIFY_TYPE = "ARENA_VOICE_SELECT_REQUEST";
361
474
  var REPORT_SHARE_DEDUPE_LIMIT = 500;
362
475
  var MODEL_QUERY_DEDUPE_LIMIT = 500;
476
+ var VOICE_SELECT_DEDUPE_LIMIT = 500;
363
477
  var processedArenaReportShareEventIds = /* @__PURE__ */ new Set();
364
478
  var processedArenaReportShareEventOrder = [];
365
479
  var inFlightArenaReportShareEventIds = /* @__PURE__ */ new Map();
366
480
  var processedArenaModelQueryEventIds = /* @__PURE__ */ new Set();
367
481
  var processedArenaModelQueryEventOrder = [];
368
482
  var inFlightArenaModelQueryEventIds = /* @__PURE__ */ new Map();
483
+ var processedArenaVoiceSelectEventIds = /* @__PURE__ */ new Set();
484
+ var processedArenaVoiceSelectEventOrder = [];
485
+ var inFlightArenaVoiceSelectEventIds = /* @__PURE__ */ new Map();
369
486
  function mapInboundFrame(frame) {
370
487
  if (frame.type === "PRIVATE_MESSAGE") {
371
488
  const payload = assertPrivatePayload(frame.payload);
@@ -441,6 +558,13 @@ async function handleInboundFrame(input) {
441
558
  inFlight: inFlightArenaModelQueryEventIds,
442
559
  remember: rememberArenaModelQueryEventId
443
560
  };
561
+ } else if (isArenaVoiceSelectEnvelope(envelope)) {
562
+ dedupeState = {
563
+ eventId: String(envelope.metadata.eventId),
564
+ processed: processedArenaVoiceSelectEventIds,
565
+ inFlight: inFlightArenaVoiceSelectEventIds,
566
+ remember: rememberArenaVoiceSelectEventId
567
+ };
444
568
  }
445
569
  if (dedupeState) {
446
570
  if (dedupeState.processed.has(dedupeState.eventId)) {
@@ -493,6 +617,9 @@ function isArenaReportShareEnvelope(envelope) {
493
617
  function isArenaModelQueryEnvelope(envelope) {
494
618
  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
619
  }
620
+ function isArenaVoiceSelectEnvelope(envelope) {
621
+ 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;
622
+ }
496
623
  function mapNotificationFrame(frame) {
497
624
  const payload = isRecord3(frame.payload) ? frame.payload : {};
498
625
  const seq = typeof payload.seq === "number" ? payload.seq : void 0;
@@ -534,6 +661,9 @@ function mapNotificationFrame(frame) {
534
661
  if (notifyType === ARENA_MODEL_QUERY_NOTIFY_TYPE) {
535
662
  return mapArenaModelQueryFrame(frame, payload, seq);
536
663
  }
664
+ if (notifyType === ARENA_VOICE_SELECT_NOTIFY_TYPE) {
665
+ return mapArenaVoiceSelectFrame(frame, payload, seq);
666
+ }
537
667
  const postId = payload.postId != null ? String(payload.postId) : "";
538
668
  return {
539
669
  id: frame.id,
@@ -548,6 +678,43 @@ function mapNotificationFrame(frame) {
548
678
  }
549
679
  return null;
550
680
  }
681
+ function mapArenaVoiceSelectFrame(frame, payload, seq) {
682
+ const voicePayload = isRecord3(payload.payload) ? payload.payload : {};
683
+ const eventId = firstText(payload.eventId, voicePayload.eventId) ?? frame.id;
684
+ const traceId = typeof payload.traceId === "string" ? payload.traceId : "";
685
+ const roomId = Number(voicePayload.roomId ?? 0);
686
+ const seatNumber = Number(voicePayload.seatNumber ?? 0);
687
+ const seatEpoch = voicePayload.seatEpoch != null ? String(voicePayload.seatEpoch) : "";
688
+ const callbackUrl = typeof voicePayload.callbackUrl === "string" ? voicePayload.callbackUrl : "";
689
+ if (!callbackUrl || !roomId || !seatNumber || !seatEpoch) {
690
+ return null;
691
+ }
692
+ const deadlineEpochMs = Number(voicePayload.deadlineEpochMs ?? 0);
693
+ const voiceOptions = Array.isArray(voicePayload.voiceOptions) ? voicePayload.voiceOptions.filter(isRecord3).map((option) => option) : [];
694
+ return {
695
+ id: eventId,
696
+ channel: "coolclaw",
697
+ conversationId: `notification:arena_voice_select:${eventId}`,
698
+ text: "/arena-voice-select",
699
+ messageType: frame.type,
700
+ seq,
701
+ shouldReply: true,
702
+ metadata: {
703
+ sourceFrameId: frame.id,
704
+ payload: frame.payload,
705
+ voiceSelectPayload: voicePayload,
706
+ arenaVoiceSelectRequest: true,
707
+ eventId,
708
+ traceId,
709
+ roomId,
710
+ seatNumber,
711
+ seatEpoch,
712
+ callbackUrl,
713
+ deadlineEpochMs,
714
+ voiceOptions
715
+ }
716
+ };
717
+ }
551
718
  function mapArenaModelQueryFrame(frame, payload, seq) {
552
719
  const eventId = typeof payload.eventId === "string" && payload.eventId.length > 0 ? payload.eventId : frame.id;
553
720
  const traceId = typeof payload.traceId === "string" ? payload.traceId : "";
@@ -583,6 +750,14 @@ function mapArenaModelQueryFrame(frame, payload, seq) {
583
750
  }
584
751
  };
585
752
  }
753
+ function firstText(...values) {
754
+ for (const value of values) {
755
+ if (typeof value === "string" && value.length > 0) {
756
+ return value;
757
+ }
758
+ }
759
+ return null;
760
+ }
586
761
  function mapArenaReportShareFrame(frame, payload, seq) {
587
762
  const eventId = typeof payload.eventId === "string" && payload.eventId.length > 0 ? payload.eventId : frame.id;
588
763
  const traceId = typeof payload.traceId === "string" ? payload.traceId : "";
@@ -624,6 +799,15 @@ function rememberArenaModelQueryEventId(eventId) {
624
799
  if (expired) processedArenaModelQueryEventIds.delete(expired);
625
800
  }
626
801
  }
802
+ function rememberArenaVoiceSelectEventId(eventId) {
803
+ if (processedArenaVoiceSelectEventIds.has(eventId)) return;
804
+ processedArenaVoiceSelectEventIds.add(eventId);
805
+ processedArenaVoiceSelectEventOrder.push(eventId);
806
+ while (processedArenaVoiceSelectEventOrder.length > VOICE_SELECT_DEDUPE_LIMIT) {
807
+ const expired = processedArenaVoiceSelectEventOrder.shift();
808
+ if (expired) processedArenaVoiceSelectEventIds.delete(expired);
809
+ }
810
+ }
627
811
  function mapGameEventFrame(frame, payload) {
628
812
  const eventType = typeof payload.eventType === "string" ? payload.eventType : "UNKNOWN";
629
813
  const agentTask = normalizeAgentTask(payload.agentTask);
@@ -782,15 +966,16 @@ var TargetParseError = class extends Error {
782
966
  this.name = "TargetParseError";
783
967
  }
784
968
  };
785
- function parseCoolclawTarget(raw) {
786
- const normalized = normalizeCoolclawTarget(raw);
969
+ function parseCoolclawTarget(raw, flavorInput) {
970
+ const flavor = resolveFlavor(flavorInput);
971
+ const normalized = normalizeCoolclawTarget(raw, flavor);
787
972
  const parts = normalized.split(":");
788
973
  if (parts.length !== 3) {
789
- throw new TargetParseError(`Invalid CoolClaw target: ${raw}`);
974
+ throw new TargetParseError(`Invalid ${flavor.displayName} target: ${raw}`);
790
975
  }
791
976
  const [channel, type, id] = parts;
792
- if (channel !== "coolclaw" || id.length === 0) {
793
- throw new TargetParseError(`Invalid CoolClaw target: ${raw}`);
977
+ if (channel !== flavor.targetPrefix || id.length === 0) {
978
+ throw new TargetParseError(`Invalid ${flavor.displayName} target: ${raw}`);
794
979
  }
795
980
  if (type === "human") {
796
981
  return { kind: "private", userType: "HUMAN", userId: id };
@@ -801,9 +986,10 @@ function parseCoolclawTarget(raw) {
801
986
  if (type === "group") {
802
987
  return { kind: "group", groupId: id };
803
988
  }
804
- throw new TargetParseError(`Invalid CoolClaw target type: ${type}`);
989
+ throw new TargetParseError(`Invalid ${flavor.displayName} target type: ${type}`);
805
990
  }
806
- function normalizeCoolclawTarget(raw) {
991
+ function normalizeCoolclawTarget(raw, flavorInput) {
992
+ resolveFlavor(flavorInput);
807
993
  const trimmed = raw.trim();
808
994
  const parts = trimmed.split(":");
809
995
  if (parts.length !== 3) {
@@ -812,25 +998,27 @@ function normalizeCoolclawTarget(raw) {
812
998
  const [channel, type, id] = parts;
813
999
  return `${channel.toLowerCase()}:${type.toLowerCase()}:${id.trim()}`;
814
1000
  }
815
- function inferCoolclawTargetChatType(raw) {
1001
+ function inferCoolclawTargetChatType(raw, flavorInput) {
816
1002
  try {
817
- const target = parseCoolclawTarget(raw);
1003
+ const target = parseCoolclawTarget(raw, flavorInput);
818
1004
  return target.kind === "private" ? "direct" : "group";
819
1005
  } catch {
820
1006
  return void 0;
821
1007
  }
822
1008
  }
823
- function isCoolclawTargetId(raw, normalized = normalizeCoolclawTarget(raw)) {
1009
+ function isCoolclawTargetId(raw, normalized = normalizeCoolclawTarget(raw), flavorInput) {
1010
+ const flavor = resolveFlavor(flavorInput);
824
1011
  try {
825
- parseCoolclawTarget(normalized);
826
- return normalized.startsWith("coolclaw:");
1012
+ parseCoolclawTarget(normalized, flavor);
1013
+ return normalized.startsWith(`${flavor.targetPrefix}:`);
827
1014
  } catch {
828
1015
  return false;
829
1016
  }
830
1017
  }
831
- async function resolveCoolclawMessagingTarget(raw, preferredKind) {
832
- const normalized = normalizeCoolclawTarget(raw);
833
- const target = parseCoolclawTarget(normalized);
1018
+ async function resolveCoolclawMessagingTarget(raw, preferredKind, flavorInput) {
1019
+ const flavor = resolveFlavor(flavorInput);
1020
+ const normalized = normalizeCoolclawTarget(raw, flavor);
1021
+ const target = parseCoolclawTarget(normalized, flavor);
834
1022
  const kind = target.kind === "private" ? "user" : "group";
835
1023
  if (preferredKind && preferredKind !== kind) {
836
1024
  return null;
@@ -913,7 +1101,17 @@ async function sendGameAction(input) {
913
1101
  rawResponsePreview: input.rawResponsePreview,
914
1102
  modelActionRejected: input.modelActionRejected,
915
1103
  modelActionType: input.modelActionType,
916
- validationReason: input.validationReason
1104
+ validationReason: input.validationReason,
1105
+ submissionStatus: input.submissionStatus,
1106
+ structuredProtocolVersion: input.structuredProtocolVersion,
1107
+ retryCount: input.retryCount,
1108
+ firstRawResponseHash: input.firstRawResponseHash,
1109
+ firstModelActionType: input.firstModelActionType,
1110
+ firstValidationReason: input.firstValidationReason,
1111
+ retryRawResponseHash: input.retryRawResponseHash,
1112
+ retryValidationReason: input.retryValidationReason,
1113
+ speech: input.speech,
1114
+ reason: input.reason
917
1115
  });
918
1116
  const response = await input.client.request(frame);
919
1117
  if (response.ok === false) {
@@ -989,6 +1187,59 @@ function parseAgentAction(text) {
989
1187
  }
990
1188
  return lastError ?? { error: "no_action_block" };
991
1189
  }
1190
+ function parseStrictJsonAgentAction(text) {
1191
+ if (typeof text !== "string" || text.length === 0) {
1192
+ return { error: "no_action_block" };
1193
+ }
1194
+ const trimmed = text.trim();
1195
+ if (!trimmed.startsWith("{")) {
1196
+ return { error: "invalid_json", detail: "strict_json_object_required" };
1197
+ }
1198
+ const objectEnd = topLevelJsonObjectEnd(trimmed);
1199
+ if (objectEnd === null) {
1200
+ return { error: "invalid_json", detail: "strict_json_object_required" };
1201
+ }
1202
+ if (objectEnd !== trimmed.length - 1) {
1203
+ return { error: "invalid_json", detail: "trailing_text_after_json" };
1204
+ }
1205
+ return parseActionJsonStrict(trimmed);
1206
+ }
1207
+ function topLevelJsonObjectEnd(text) {
1208
+ let depth = 0;
1209
+ let inString = false;
1210
+ let escaped = false;
1211
+ for (let i = 0; i < text.length; i += 1) {
1212
+ const ch = text[i];
1213
+ if (inString) {
1214
+ if (escaped) {
1215
+ escaped = false;
1216
+ } else if (ch === "\\") {
1217
+ escaped = true;
1218
+ } else if (ch === '"') {
1219
+ inString = false;
1220
+ }
1221
+ continue;
1222
+ }
1223
+ if (ch === '"') {
1224
+ inString = true;
1225
+ continue;
1226
+ }
1227
+ if (ch === "{") {
1228
+ depth += 1;
1229
+ continue;
1230
+ }
1231
+ if (ch === "}") {
1232
+ depth -= 1;
1233
+ if (depth === 0) {
1234
+ return i;
1235
+ }
1236
+ if (depth < 0) {
1237
+ return null;
1238
+ }
1239
+ }
1240
+ }
1241
+ return null;
1242
+ }
992
1243
  function parseActionJson(body) {
993
1244
  let obj;
994
1245
  try {
@@ -1017,7 +1268,38 @@ function parseActionJson(body) {
1017
1268
  }
1018
1269
  return {
1019
1270
  actionType: rec.actionType,
1020
- actionData: rec.actionData
1271
+ actionData: rec.actionData,
1272
+ ...typeof rec.speech === "string" ? { speech: rec.speech } : {},
1273
+ ...typeof rec.reason === "string" ? { reason: rec.reason } : {}
1274
+ };
1275
+ }
1276
+ function parseActionJsonStrict(body) {
1277
+ let obj;
1278
+ try {
1279
+ obj = JSON.parse(body);
1280
+ } catch (e) {
1281
+ return { error: "invalid_json", detail: e instanceof Error ? e.message : String(e) };
1282
+ }
1283
+ if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
1284
+ return { error: "invalid_json", detail: "not an object" };
1285
+ }
1286
+ const rec = obj;
1287
+ for (const field of Object.keys(rec)) {
1288
+ if (!["speech", "reason", "actionType", "actionData"].includes(field)) {
1289
+ return { error: "invalid_json", detail: `unknown_top_level_field:${field}` };
1290
+ }
1291
+ }
1292
+ if (typeof rec.actionType !== "string" || rec.actionType.length === 0) {
1293
+ return { error: "missing_action_type" };
1294
+ }
1295
+ if (typeof rec.actionData !== "object" || rec.actionData === null || Array.isArray(rec.actionData)) {
1296
+ return { error: "missing_action_data" };
1297
+ }
1298
+ return {
1299
+ actionType: rec.actionType,
1300
+ actionData: rec.actionData,
1301
+ ...typeof rec.speech === "string" ? { speech: rec.speech } : {},
1302
+ ...typeof rec.reason === "string" ? { reason: rec.reason } : {}
1021
1303
  };
1022
1304
  }
1023
1305
  function removeTrailingCommas(body) {
@@ -1068,7 +1350,7 @@ function inferRejectedModelAction(rawResponse, task) {
1068
1350
  validationReason: "no_model_output"
1069
1351
  };
1070
1352
  }
1071
- const parsed = parseAgentAction(rawResponse);
1353
+ const parsed = isStructuredJsonTask(task) ? parseStrictJsonAgentAction(rawResponse) : parseAgentAction(rawResponse);
1072
1354
  if ("error" in parsed) {
1073
1355
  return {
1074
1356
  modelActionRejected: true,
@@ -1207,6 +1489,223 @@ function isRecord4(value) {
1207
1489
  return typeof value === "object" && value !== null && !Array.isArray(value);
1208
1490
  }
1209
1491
 
1492
+ // src/arena-voice-select.ts
1493
+ var FALLBACK_REASON = "\u672A\u89E3\u6790\u5230\u6A21\u578B\u97F3\u8272\u504F\u597D\uFF0C\u4EA4\u7531\u540E\u7AEF\u515C\u5E95\u9009\u62E9\u3002";
1494
+ var DEFAULT_REASON = "\u57FA\u4E8E\u6A21\u578B\u8F93\u51FA\u9009\u62E9\u5019\u9009\u73A9\u5BB6\u97F3\u8272\u3002";
1495
+ function parseArenaVoiceSelection(rawText, voiceOptions) {
1496
+ const structured = parseStructuredVoiceSelection(rawText, voiceOptions);
1497
+ if (structured) return structured;
1498
+ const topVoiceIds = extractVoiceIdsFromText(rawText, voiceOptions);
1499
+ return {
1500
+ topVoiceIds,
1501
+ reason: topVoiceIds.length > 0 ? DEFAULT_REASON : FALLBACK_REASON
1502
+ };
1503
+ }
1504
+ async function submitArenaVoiceSelectCallback(input) {
1505
+ const start = Date.now();
1506
+ const rawHash = input.rawText ? sha256Hex(input.rawText) : "";
1507
+ const rawPreview = rawResponsePreview(input.rawText) ?? "";
1508
+ const selection = parseArenaVoiceSelection(input.rawText, input.meta.voiceOptions);
1509
+ try {
1510
+ if (!input.meta.callbackUrl || !input.token) {
1511
+ input.log?.warn?.(
1512
+ `[ARENA-VOICE] callback skipped eventId=${input.meta.eventId} reason=missing_callback_or_token rawHash=${rawHash} rawPreview=${rawPreview}`
1513
+ );
1514
+ return { ok: false, error: "missing_callback_or_token" };
1515
+ }
1516
+ const fetchImpl = input.fetchImpl ?? globalThis.fetch;
1517
+ if (typeof fetchImpl !== "function") {
1518
+ input.log?.warn?.(`[ARENA-VOICE] callback skipped eventId=${input.meta.eventId} reason=fetch_unavailable`);
1519
+ return { ok: false, error: "fetch_unavailable" };
1520
+ }
1521
+ const response = await fetchImpl(input.meta.callbackUrl, {
1522
+ method: "POST",
1523
+ headers: {
1524
+ Authorization: `Bearer ${input.token}`,
1525
+ "Content-Type": "application/json"
1526
+ },
1527
+ body: JSON.stringify({
1528
+ eventId: input.meta.eventId,
1529
+ roomId: input.meta.roomId,
1530
+ seatNumber: input.meta.seatNumber,
1531
+ seatEpoch: input.meta.seatEpoch,
1532
+ topVoiceIds: selection.topVoiceIds,
1533
+ reason: selection.reason
1534
+ })
1535
+ });
1536
+ const body = await readJsonObject2(response);
1537
+ const data = isRecord5(body.data) ? body.data : body;
1538
+ const accepted = data.accepted === true;
1539
+ const result = { ok: response.ok, status: response.status, accepted };
1540
+ const level = response.ok ? "info" : "warn";
1541
+ input.log?.[level]?.(
1542
+ `[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}`
1543
+ );
1544
+ return result;
1545
+ } catch (error) {
1546
+ const message = error instanceof Error ? error.message : String(error);
1547
+ input.log?.warn?.(
1548
+ `[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}`
1549
+ );
1550
+ return { ok: false, error: message };
1551
+ }
1552
+ }
1553
+ function createArenaVoiceSelectReplyCollector(input) {
1554
+ const blocks = [];
1555
+ let submitted = false;
1556
+ const submit = input.submit ?? ((rawText) => submitArenaVoiceSelectCallback({
1557
+ meta: input.meta,
1558
+ token: input.token,
1559
+ rawText,
1560
+ log: input.log
1561
+ }));
1562
+ async function submitOnce(rawText, reason) {
1563
+ if (submitted) return;
1564
+ submitted = true;
1565
+ const parsed = parseArenaVoiceSelection(rawText, input.meta.voiceOptions);
1566
+ input.log?.info?.(
1567
+ `[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}`
1568
+ );
1569
+ await submit(rawText);
1570
+ }
1571
+ return {
1572
+ async deliver(text) {
1573
+ if (submitted || !text) return;
1574
+ blocks.push(text);
1575
+ const full = blocks.join("");
1576
+ const structured = parseStructuredVoiceSelection(full, input.meta.voiceOptions);
1577
+ input.log?.info?.(
1578
+ `[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"}`
1579
+ );
1580
+ if (structured?.topVoiceIds.length) {
1581
+ await submitOnce(full, "parsed");
1582
+ }
1583
+ },
1584
+ async finalize() {
1585
+ if (submitted) return;
1586
+ await submitOnce(blocks.join(""), "final");
1587
+ }
1588
+ };
1589
+ }
1590
+ function parseStructuredVoiceSelection(rawText, voiceOptions) {
1591
+ if (!rawText) return null;
1592
+ const body = parseFirstJsonObject(rawText);
1593
+ if (!body) return null;
1594
+ const rawIds = Array.isArray(body.topVoiceIds) ? body.topVoiceIds : [];
1595
+ const topVoiceIds = sanitizeVoiceIds(rawIds, voiceOptions);
1596
+ const reason = normalizeReason(body.reason);
1597
+ if (topVoiceIds.length === 0 && !reason) return null;
1598
+ return {
1599
+ topVoiceIds,
1600
+ reason: reason || (topVoiceIds.length > 0 ? DEFAULT_REASON : FALLBACK_REASON)
1601
+ };
1602
+ }
1603
+ function extractVoiceIdsFromText(rawText, voiceOptions) {
1604
+ if (!rawText) return [];
1605
+ const allowed = allowedVoiceIds(voiceOptions);
1606
+ const result = [];
1607
+ const seen = /* @__PURE__ */ new Set();
1608
+ const regex = /\b\d{3,8}\b/g;
1609
+ for (const match of rawText.matchAll(regex)) {
1610
+ const voiceId = match[0];
1611
+ if (!allowed.has(voiceId) || seen.has(voiceId)) continue;
1612
+ seen.add(voiceId);
1613
+ result.push(voiceId);
1614
+ if (result.length >= 3) break;
1615
+ }
1616
+ return result;
1617
+ }
1618
+ function sanitizeVoiceIds(rawIds, voiceOptions) {
1619
+ const allowed = allowedVoiceIds(voiceOptions);
1620
+ const result = [];
1621
+ const seen = /* @__PURE__ */ new Set();
1622
+ for (const rawId of rawIds) {
1623
+ const voiceId = typeof rawId === "string" ? rawId.trim() : "";
1624
+ if (!voiceId || !allowed.has(voiceId) || seen.has(voiceId)) continue;
1625
+ seen.add(voiceId);
1626
+ result.push(voiceId);
1627
+ if (result.length >= 3) break;
1628
+ }
1629
+ return result;
1630
+ }
1631
+ function allowedVoiceIds(voiceOptions) {
1632
+ const ids = /* @__PURE__ */ new Set();
1633
+ if (!Array.isArray(voiceOptions)) return ids;
1634
+ for (const option of voiceOptions) {
1635
+ const voiceId = typeof option?.voiceId === "string" ? option.voiceId.trim() : "";
1636
+ if (voiceId) ids.add(voiceId);
1637
+ }
1638
+ return ids;
1639
+ }
1640
+ function normalizeReason(value) {
1641
+ if (typeof value !== "string") return "";
1642
+ return value.replace(/\s+/g, " ").trim().slice(0, 160);
1643
+ }
1644
+ function parseFirstJsonObject(rawText) {
1645
+ const direct = tryParseJsonObject(rawText.trim());
1646
+ if (direct) return direct;
1647
+ const candidates = extractJsonObjectCandidates(rawText);
1648
+ for (const candidate of candidates) {
1649
+ const parsed = tryParseJsonObject(candidate);
1650
+ if (parsed) return parsed;
1651
+ }
1652
+ return null;
1653
+ }
1654
+ function extractJsonObjectCandidates(rawText) {
1655
+ const candidates = [];
1656
+ let start = -1;
1657
+ let depth = 0;
1658
+ let inString = false;
1659
+ let escaped = false;
1660
+ for (let index = 0; index < rawText.length; index += 1) {
1661
+ const char = rawText[index];
1662
+ if (inString) {
1663
+ if (escaped) {
1664
+ escaped = false;
1665
+ } else if (char === "\\") {
1666
+ escaped = true;
1667
+ } else if (char === '"') {
1668
+ inString = false;
1669
+ }
1670
+ continue;
1671
+ }
1672
+ if (char === '"') {
1673
+ inString = true;
1674
+ continue;
1675
+ }
1676
+ if (char === "{") {
1677
+ if (depth === 0) start = index;
1678
+ depth += 1;
1679
+ } else if (char === "}" && depth > 0) {
1680
+ depth -= 1;
1681
+ if (depth === 0 && start >= 0) {
1682
+ candidates.push(rawText.slice(start, index + 1));
1683
+ start = -1;
1684
+ }
1685
+ }
1686
+ }
1687
+ return candidates;
1688
+ }
1689
+ async function readJsonObject2(response) {
1690
+ try {
1691
+ const body = await response.json();
1692
+ return isRecord5(body) ? body : {};
1693
+ } catch {
1694
+ return {};
1695
+ }
1696
+ }
1697
+ function isRecord5(value) {
1698
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1699
+ }
1700
+ function tryParseJsonObject(value) {
1701
+ try {
1702
+ const parsed = JSON.parse(value);
1703
+ return isRecord5(parsed) ? parsed : null;
1704
+ } catch {
1705
+ return null;
1706
+ }
1707
+ }
1708
+
1210
1709
  // src/ws-client.ts
1211
1710
  import WebSocket from "ws";
1212
1711
  var CoolclawWsClient = class {
@@ -1269,11 +1768,13 @@ var CoolclawWsClient = class {
1269
1768
  async connect() {
1270
1769
  this.notifyState("connecting");
1271
1770
  const lastAckedSeq = await this.options.ackStore.getLastAckedSeq(this.options.accountKey);
1771
+ const capabilities = this.options.capabilities ?? [WEREWOLF_STRUCTURED_ACTION_PROTOCOL_VERSION];
1272
1772
  const socket = new WebSocket(buildWsUrl(this.options.gatewayUrl, lastAckedSeq), {
1273
1773
  headers: {
1274
1774
  Authorization: `Bearer ${this.options.token}`,
1275
1775
  "X-CoolClaw-Agent-Id": this.options.agentId,
1276
- "X-CoolClaw-Plugin-Version": this.options.pluginVersion
1776
+ "X-CoolClaw-Plugin-Version": this.options.pluginVersion,
1777
+ "X-CoolClaw-Capabilities": capabilities.join(",")
1277
1778
  }
1278
1779
  });
1279
1780
  this.socket = socket;
@@ -1419,18 +1920,18 @@ var CoolclawWsClient = class {
1419
1920
  }
1420
1921
  };
1421
1922
  function readPingInterval(payload) {
1422
- if (!isRecord5(payload) || typeof payload.pingIntervalMs !== "number" || payload.pingIntervalMs <= 0) {
1923
+ if (!isRecord6(payload) || typeof payload.pingIntervalMs !== "number" || payload.pingIntervalMs <= 0) {
1423
1924
  return void 0;
1424
1925
  }
1425
1926
  return payload.pingIntervalMs;
1426
1927
  }
1427
1928
  function readErrorMessage(payload) {
1428
- if (isRecord5(payload) && typeof payload.message === "string") {
1929
+ if (isRecord6(payload) && typeof payload.message === "string") {
1429
1930
  return payload.message;
1430
1931
  }
1431
1932
  return "CoolClaw request failed";
1432
1933
  }
1433
- function isRecord5(value) {
1934
+ function isRecord6(value) {
1434
1935
  return typeof value === "object" && value !== null;
1435
1936
  }
1436
1937
 
@@ -1452,9 +1953,12 @@ function getPluginVersion() {
1452
1953
  }
1453
1954
 
1454
1955
  // src/channel.ts
1956
+ import { homedir as homedir3 } from "os";
1957
+ import path2 from "path";
1455
1958
  import {
1456
1959
  createChatChannelPlugin
1457
1960
  } from "openclaw/plugin-sdk/core";
1961
+ var productFlavor = activeFlavor();
1458
1962
  function createAccountStatusSink(params) {
1459
1963
  return (patch) => {
1460
1964
  params.setStatus({ accountId: params.accountId, ...patch });
@@ -1484,6 +1988,42 @@ function logAckFailure(params) {
1484
1988
  const target = params.target ? ` target=${params.target}` : "";
1485
1989
  params.log(`${params.channel} ack cleanup failed${target}: ${String(params.error)}`);
1486
1990
  }
1991
+ function buildStructuredActionRetryPrompt(renderedPrompt, reason) {
1992
+ const reasonText = reason && reason.trim().length > 0 ? reason.trim() : "invalid_output";
1993
+ return `${renderedPrompt}
1994
+
1995
+ \u4E0A\u4E00\u6B21\u8F93\u51FA\u672A\u88AB\u72FC\u4EBA\u6740\u7ED3\u6784\u5316\u52A8\u4F5C\u534F\u8BAE\u63A5\u53D7\uFF0C\u5931\u8D25\u539F\u56E0\uFF1A${reasonText}\u3002
1996
+ \u8BF7\u91CD\u65B0\u4F5C\u7B54\uFF1A\u53EA\u8F93\u51FA\u4E00\u4E2A\u5B8C\u6574 JSON \u5BF9\u8C61\uFF0C\u4E0D\u8981\u8F93\u51FA Markdown\u3001\u89E3\u91CA\u6587\u5B57\u3001\u4EE3\u7801\u5757\u6216\u65E7\u52A8\u4F5C\u6807\u7B7E\u3002`;
1997
+ }
1998
+ function hasStructuredRetryBudget(deadlineEpochMs, nowEpochMs = Date.now(), safetyMarginMs = 1e3) {
1999
+ if (!deadlineEpochMs || deadlineEpochMs <= 0) {
2000
+ return true;
2001
+ }
2002
+ return nowEpochMs < deadlineEpochMs - Math.max(0, safetyMarginMs);
2003
+ }
2004
+ function shouldSubmitStructuredRejectedOutput(params) {
2005
+ if (!params.structuredTask || params.gameSubmitted) {
2006
+ return false;
2007
+ }
2008
+ if (params.modelActionRejected === false) {
2009
+ return false;
2010
+ }
2011
+ if (params.fallbackReason?.startsWith("llm_action_submit_failed:")) {
2012
+ return false;
2013
+ }
2014
+ return true;
2015
+ }
2016
+ function flattenForLog(text) {
2017
+ return text.replace(/\r/g, "").replace(/\n/g, " \\n ");
2018
+ }
2019
+ function logStructuredActionValidation(params) {
2020
+ const message = `[COOLCLAW-GAME-ACTION-VALIDATE] gameId=${params.meta.gameId} roomId=${params.meta.roomId} eventType=${params.meta.eventType} eventId=${params.meta.eventId} turnSeq=${params.meta.turnSeq} retryCount=${params.retryCount} submissionStatus=${params.submissionStatus} result=${params.result} reason=${params.reason ?? ""} actionType=${params.actionType ?? ""} promptPolicyVersion=${params.meta.promptPolicyVersion ?? ""} renderedPromptHash=${params.meta.renderedPromptHash ?? ""} rawResponseHash=${params.rawResponseHash ?? ""}`;
2021
+ if (params.result === "pass") {
2022
+ params.log?.info?.(message);
2023
+ } else {
2024
+ params.log?.warn?.(message);
2025
+ }
2026
+ }
1487
2027
  function assertInboundRuntimeAvailable(runtime, context) {
1488
2028
  if (runtime?.channel) {
1489
2029
  return runtime.channel;
@@ -1498,7 +2038,10 @@ async function submitGameActionWithLog(action, meta, wsClient, log, source, rawR
1498
2038
  log?.error?.(`[GAME-ACTION] submit skipped: ws not connected eventId=${meta.eventId}`);
1499
2039
  return "failed";
1500
2040
  }
1501
- const responseHash = rawResponse && rawResponse.length > 0 ? sha256Hex(rawResponse) : void 0;
2041
+ const responseHash = typeof rawResponse === "string" ? sha256Hex(rawResponse) : void 0;
2042
+ const structured = isStructuredJsonTask(meta.agentTask);
2043
+ const retryCount = auditMeta?.retryCount ?? 0;
2044
+ const structuredProtocolVersion = auditMeta?.structuredProtocolVersion ?? meta.agentTask.actionProtocolVersion;
1502
2045
  log?.info?.(
1503
2046
  `[GAME-ACTION] submit start source=${source} eventType=${meta.eventType} actionType=${action.actionType} gameId=${meta.gameId} roomId=${meta.roomId} turnSeq=${meta.turnSeq} eventId=${meta.eventId} promptPolicyVersion=${meta.promptPolicyVersion ?? ""} renderedPromptHash=${meta.renderedPromptHash ?? ""} rawResponseHash=${responseHash ?? ""}`
1504
2047
  );
@@ -1519,10 +2062,20 @@ async function submitGameActionWithLog(action, meta, wsClient, log, source, rawR
1519
2062
  renderedPromptHash: meta.renderedPromptHash,
1520
2063
  parseSource: source,
1521
2064
  rawResponseHash: responseHash,
1522
- rawResponsePreview: rawResponse ? rawResponsePreview(rawResponse) : void 0,
2065
+ rawResponsePreview: typeof rawResponse === "string" ? rawResponsePreview(rawResponse, Number.MAX_SAFE_INTEGER) : void 0,
1523
2066
  modelActionRejected: auditMeta?.modelActionRejected,
1524
2067
  modelActionType: auditMeta?.modelActionType,
1525
- validationReason: normalizeAuditText(auditMeta?.validationReason)
2068
+ validationReason: normalizeAuditText(auditMeta?.validationReason),
2069
+ submissionStatus: auditMeta?.submissionStatus ?? (structured ? "VALID" : void 0),
2070
+ structuredProtocolVersion,
2071
+ retryCount: structured ? retryCount : void 0,
2072
+ firstRawResponseHash: auditMeta?.firstRawResponseHash ?? (structured ? responseHash : void 0),
2073
+ firstModelActionType: auditMeta?.firstModelActionType,
2074
+ firstValidationReason: normalizeAuditText(auditMeta?.firstValidationReason),
2075
+ retryRawResponseHash: auditMeta?.retryRawResponseHash,
2076
+ retryValidationReason: normalizeAuditText(auditMeta?.retryValidationReason),
2077
+ speech: action.speech,
2078
+ reason: action.reason
1526
2079
  });
1527
2080
  if (response.uncertain === true) {
1528
2081
  log?.warn?.(
@@ -1548,6 +2101,128 @@ async function submitGameActionWithLog(action, meta, wsClient, log, source, rawR
1548
2101
  return "failed";
1549
2102
  }
1550
2103
  }
2104
+ async function submitRejectedOutputWithLog(params) {
2105
+ const rawResponse = params.rawResponse ?? "";
2106
+ const rawHash = sha256Hex(rawResponse);
2107
+ const retryCount = params.auditMeta?.retryCount ?? 0;
2108
+ params.log?.warn?.(
2109
+ `[COOLCLAW-GAME-ACTION-VALIDATE] gameId=${params.meta.gameId} roomId=${params.meta.roomId} eventType=${params.meta.eventType} eventId=${params.meta.eventId} turnSeq=${params.meta.turnSeq} retryCount=${retryCount} submissionStatus=REJECTED_OUTPUT result=submit reason=${params.reason} actionType=INVALID_OUTPUT promptPolicyVersion=${params.meta.promptPolicyVersion ?? ""} renderedPromptHash=${params.meta.renderedPromptHash ?? ""} rawResponseHash=${rawHash ?? ""}`
2110
+ );
2111
+ return submitGameActionWithLog(
2112
+ { actionType: "INVALID_OUTPUT", actionData: {} },
2113
+ params.meta,
2114
+ params.wsClient,
2115
+ params.log,
2116
+ "rejected_output",
2117
+ rawResponse,
2118
+ {
2119
+ modelActionRejected: true,
2120
+ validationReason: params.reason,
2121
+ submissionStatus: "REJECTED_OUTPUT",
2122
+ structuredProtocolVersion: params.meta.agentTask.actionProtocolVersion,
2123
+ retryCount,
2124
+ firstRawResponseHash: params.auditMeta?.firstRawResponseHash ?? rawHash,
2125
+ firstModelActionType: params.auditMeta?.firstModelActionType,
2126
+ firstValidationReason: params.auditMeta?.firstValidationReason,
2127
+ retryRawResponseHash: params.auditMeta?.retryRawResponseHash ?? (retryCount > 0 ? rawHash : void 0),
2128
+ retryValidationReason: params.auditMeta?.retryValidationReason ?? (retryCount > 0 ? params.reason : void 0)
2129
+ }
2130
+ );
2131
+ }
2132
+ async function submitStructuredFinalActionWithLog(params) {
2133
+ const rawResponse = params.rawResponse ?? "";
2134
+ const rawHash = sha256Hex(rawResponse);
2135
+ if (rawResponse.trim().length === 0) {
2136
+ logStructuredActionValidation({
2137
+ log: params.log,
2138
+ meta: params.meta,
2139
+ retryCount: params.retryCount,
2140
+ submissionStatus: "REJECTED_OUTPUT",
2141
+ result: "reject",
2142
+ reason: "no_model_output",
2143
+ rawResponseHash: rawHash
2144
+ });
2145
+ return { submitted: false, reason: "no_model_output", rawHash };
2146
+ }
2147
+ const parsed = parseStrictJsonAgentAction(rawResponse);
2148
+ if ("error" in parsed) {
2149
+ const reason = parsed.error === "invalid_json" ? `invalid_json:${parsed.detail}` : parsed.error;
2150
+ logStructuredActionValidation({
2151
+ log: params.log,
2152
+ meta: params.meta,
2153
+ retryCount: params.retryCount,
2154
+ submissionStatus: "REJECTED_OUTPUT",
2155
+ result: "reject",
2156
+ reason,
2157
+ rawResponseHash: rawHash
2158
+ });
2159
+ return {
2160
+ submitted: false,
2161
+ reason,
2162
+ rawHash
2163
+ };
2164
+ }
2165
+ const validation = validateAgentAction(parsed, params.meta.agentTask);
2166
+ if (!validation.ok) {
2167
+ logStructuredActionValidation({
2168
+ log: params.log,
2169
+ meta: params.meta,
2170
+ retryCount: params.retryCount,
2171
+ submissionStatus: "REJECTED_OUTPUT",
2172
+ result: "reject",
2173
+ reason: validation.reason,
2174
+ actionType: parsed.actionType,
2175
+ rawResponseHash: rawHash
2176
+ });
2177
+ return {
2178
+ submitted: false,
2179
+ reason: validation.reason,
2180
+ rawHash,
2181
+ modelActionType: parsed.actionType
2182
+ };
2183
+ }
2184
+ logStructuredActionValidation({
2185
+ log: params.log,
2186
+ meta: params.meta,
2187
+ retryCount: params.retryCount,
2188
+ submissionStatus: "VALID",
2189
+ result: "pass",
2190
+ reason: validation.repairReason,
2191
+ actionType: validation.action.actionType,
2192
+ rawResponseHash: rawHash
2193
+ });
2194
+ const status = await submitGameActionWithLog(
2195
+ validation.action,
2196
+ params.meta,
2197
+ params.wsClient,
2198
+ params.log,
2199
+ "llm",
2200
+ rawResponse,
2201
+ {
2202
+ ...params.auditMeta,
2203
+ modelActionRejected: false,
2204
+ modelActionType: validation.action.actionType,
2205
+ validationReason: validation.repairReason,
2206
+ submissionStatus: "VALID",
2207
+ structuredProtocolVersion: params.meta.agentTask.actionProtocolVersion,
2208
+ retryCount: params.retryCount,
2209
+ firstRawResponseHash: params.auditMeta?.firstRawResponseHash ?? rawHash,
2210
+ firstModelActionType: params.auditMeta?.firstModelActionType,
2211
+ firstValidationReason: params.auditMeta?.firstValidationReason,
2212
+ retryRawResponseHash: params.retryCount > 0 ? params.auditMeta?.retryRawResponseHash ?? rawHash : params.auditMeta?.retryRawResponseHash,
2213
+ retryValidationReason: params.retryCount > 0 ? params.auditMeta?.retryValidationReason ?? validation.repairReason : params.auditMeta?.retryValidationReason
2214
+ }
2215
+ );
2216
+ return {
2217
+ submitted: status === "submitted" || status === "uncertain",
2218
+ status,
2219
+ reason: status === "failed" ? `llm_action_submit_failed:${validation.action.actionType}` : void 0,
2220
+ rawHash,
2221
+ modelActionType: validation.action.actionType,
2222
+ validationReason: validation.repairReason,
2223
+ validAction: true
2224
+ };
2225
+ }
1551
2226
  async function submitBackendFallbackWithLog(params) {
1552
2227
  const fb = backendFallbackAction(params.meta.agentTask);
1553
2228
  if (!fb) {
@@ -1587,7 +2262,7 @@ function isNoReplyText(text) {
1587
2262
  return lastLine ? noReplyTokens.has(lastLine) : false;
1588
2263
  }
1589
2264
  function shouldSuppressCoolclawTextDelivery(envelope) {
1590
- return envelope.metadata?.gameEvent === true || isArenaReportShareEnvelope(envelope) || isArenaModelQueryEnvelope(envelope);
2265
+ return envelope.metadata?.gameEvent === true || isArenaReportShareEnvelope(envelope) || isArenaModelQueryEnvelope(envelope) || isArenaVoiceSelectEnvelope(envelope);
1591
2266
  }
1592
2267
  async function finalizeArenaModelQueryAfterDispatchError(params) {
1593
2268
  if (!params.collector) {
@@ -1609,19 +2284,19 @@ function clearRuntimeClient(accountKey) {
1609
2284
  runtimeClients.delete(accountKey);
1610
2285
  }
1611
2286
  function extractAccountFromConfig(cfg, accountId) {
1612
- const coolclawSection = cfg.channels?.coolclaw;
1613
- const accounts = coolclawSection?.accounts;
2287
+ const channelSection = cfg.channels?.[productFlavor.channelId];
2288
+ const accounts = channelSection?.accounts;
1614
2289
  return accounts?.[accountId ?? "default"] ?? {};
1615
2290
  }
1616
2291
  var coolclawChannelPlugin = createChatChannelPlugin({
1617
2292
  base: {
1618
- id: "coolclaw",
2293
+ id: productFlavor.channelId,
1619
2294
  meta: {
1620
- id: "coolclaw",
1621
- label: "CoolClaw",
1622
- selectionLabel: "CoolClaw",
1623
- docsPath: "/plugins/coolclaw",
1624
- blurb: "Connect OpenClaw to the CoolClaw/Riddle chat platform."
2295
+ id: productFlavor.channelId,
2296
+ label: productFlavor.displayName,
2297
+ selectionLabel: productFlavor.displayName,
2298
+ docsPath: `/plugins/${productFlavor.channelId}`,
2299
+ blurb: `Connect OpenClaw to the ${productFlavor.displayName} chat platform.`
1625
2300
  },
1626
2301
  capabilities: {
1627
2302
  chatTypes: ["direct", "group"],
@@ -1636,15 +2311,15 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1636
2311
  },
1637
2312
  agentPrompt: {
1638
2313
  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>'.",
2314
+ `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
2315
  "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."
2316
+ `When sending a message to a ${productFlavor.displayName} group, the agent will only reply if it was mentioned in the group message.`,
2317
+ `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
2318
  ]
1644
2319
  },
1645
2320
  config: {
1646
2321
  listAccountIds(cfg) {
1647
- return Object.keys(cfg.channels?.coolclaw?.accounts ?? {});
2322
+ return Object.keys(cfg.channels?.[productFlavor.channelId]?.accounts ?? {});
1648
2323
  },
1649
2324
  resolveAccount(cfg, accountId) {
1650
2325
  return extractAccountFromConfig(cfg, accountId);
@@ -1676,9 +2351,9 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1676
2351
  async resolveTargets({ inputs }) {
1677
2352
  return inputs.map((input) => {
1678
2353
  try {
1679
- const normalized = normalizeCoolclawTarget(input);
2354
+ const normalized = normalizeCoolclawTarget(input, productFlavor);
1680
2355
  const [, type, id] = normalized.split(":");
1681
- parseCoolclawTarget(normalized);
2356
+ parseCoolclawTarget(normalized, productFlavor);
1682
2357
  return { input, resolved: true, id: normalized, name: `${type}:${id}` };
1683
2358
  } catch (error) {
1684
2359
  return { input, resolved: false, note: error instanceof Error ? error.message : String(error) };
@@ -1689,23 +2364,23 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1689
2364
  messaging: {
1690
2365
  normalizeTarget(raw) {
1691
2366
  try {
1692
- const normalized = normalizeCoolclawTarget(raw);
1693
- parseCoolclawTarget(normalized);
2367
+ const normalized = normalizeCoolclawTarget(raw, productFlavor);
2368
+ parseCoolclawTarget(normalized, productFlavor);
1694
2369
  return normalized;
1695
2370
  } catch {
1696
2371
  return void 0;
1697
2372
  }
1698
2373
  },
1699
2374
  inferTargetChatType({ to }) {
1700
- return inferCoolclawTargetChatType(to);
2375
+ return inferCoolclawTargetChatType(to, productFlavor);
1701
2376
  },
1702
2377
  targetResolver: {
1703
- hint: "Use coolclaw:human:<id>, coolclaw:agent:<id>, or coolclaw:group:<id>.",
2378
+ hint: `Use ${productFlavor.targetPrefix}:human:<id>, ${productFlavor.targetPrefix}:agent:<id>, or ${productFlavor.targetPrefix}:group:<id>.`,
1704
2379
  looksLikeId(raw, normalized) {
1705
- return isCoolclawTargetId(raw, normalized);
2380
+ return isCoolclawTargetId(raw, normalized, productFlavor);
1706
2381
  },
1707
2382
  resolveTarget({ input, normalized, preferredKind }) {
1708
- return resolveCoolclawMessagingTarget(normalized || input, preferredKind);
2383
+ return resolveCoolclawMessagingTarget(normalized || input, preferredKind, productFlavor);
1709
2384
  }
1710
2385
  }
1711
2386
  },
@@ -1714,17 +2389,19 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1714
2389
  const account = coolclawChannelPlugin.config.resolveAccount(ctx.cfg, ctx.accountId);
1715
2390
  const token = await resolveAccountToken(account);
1716
2391
  if (!account.gatewayUrl || !account.agentId || !token) {
1717
- ctx.log?.error(`[${ctx.accountId}] CoolClaw account is not fully configured`);
2392
+ ctx.log?.error(`[${ctx.accountId}] ${productFlavor.displayName} account is not fully configured`);
1718
2393
  return;
1719
2394
  }
1720
- const accountKey = `coolclaw:${ctx.accountId ?? "default"}`;
1721
- const ackStore = new FileAckStore();
2395
+ const accountKey = `${productFlavor.channelId}:${ctx.accountId ?? "default"}`;
2396
+ const ackStore = new FileAckStore(
2397
+ path2.join(homedir3(), ".openclaw", "extensions", productFlavor.channelId, ".ack-store")
2398
+ );
1722
2399
  const statusSink = createAccountStatusSink({
1723
2400
  accountId: ctx.accountId,
1724
2401
  setStatus: ctx.setStatus
1725
2402
  });
1726
2403
  statusSink({ statusState: "connecting" });
1727
- ctx.log?.info(`[${ctx.accountId}] starting CoolClaw provider (${account.gatewayUrl})`);
2404
+ ctx.log?.info(`[${ctx.accountId}] starting ${productFlavor.displayName} provider (${account.gatewayUrl})`);
1728
2405
  await runPassiveAccountLifecycle({
1729
2406
  abortSignal: ctx.abortSignal,
1730
2407
  start: async () => {
@@ -1748,20 +2425,35 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1748
2425
  const isGameEvent = envelope.metadata?.gameEvent === true;
1749
2426
  const isArenaReportShare = isArenaReportShareEnvelope(envelope);
1750
2427
  const isArenaModelQuery = isArenaModelQueryEnvelope(envelope);
2428
+ const isArenaVoiceSelect = isArenaVoiceSelectEnvelope(envelope);
1751
2429
  const suppressChatTextDelivery = shouldSuppressCoolclawTextDelivery(envelope);
1752
2430
  const gameMeta = isGameEvent ? envelope.metadata : null;
1753
2431
  const modelQueryMeta = isArenaModelQuery ? envelope.metadata : null;
2432
+ const voiceSelectMeta = isArenaVoiceSelect ? envelope.metadata : null;
1754
2433
  let gameSubmitted = false;
1755
2434
  let gameFallbackReason = null;
1756
2435
  let gameModelActionType;
1757
2436
  let gameValidationReason;
1758
2437
  let gameModelActionRejected;
2438
+ let gameReplyAttempt = 0;
2439
+ let gameFirstRawResponseHash;
2440
+ let gameFirstModelActionType;
2441
+ let gameFirstValidationReason;
2442
+ let gameRetryRawResponseHash;
2443
+ let gameRetryValidationReason;
2444
+ let gameBaseReplyContext = null;
2445
+ let dispatchGameReply = null;
1759
2446
  const gameBuffer = [];
1760
2447
  const modelQueryCollector = modelQueryMeta ? createArenaModelQueryReplyCollector({
1761
2448
  meta: modelQueryMeta,
1762
2449
  token,
1763
2450
  log: ctx.log
1764
2451
  }) : null;
2452
+ const voiceSelectCollector = voiceSelectMeta ? createArenaVoiceSelectReplyCollector({
2453
+ meta: voiceSelectMeta,
2454
+ token,
2455
+ log: ctx.log
2456
+ }) : null;
1765
2457
  if (isGameEvent && gameMeta) {
1766
2458
  ctx.log?.info?.(
1767
2459
  `[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 +2470,17 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1778
2470
  `[ARENA-MODEL] inbound eventId=${modelQueryMeta.eventId} traceId=${modelQueryMeta.traceId ?? ""} roomId=${modelQueryMeta.roomId} seatEpoch=${modelQueryMeta.seatEpoch} callbackHost=${callbackHost} conversationId=${envelope.conversationId}`
1779
2471
  );
1780
2472
  }
2473
+ if (voiceSelectMeta) {
2474
+ let callbackHost = "";
2475
+ try {
2476
+ callbackHost = new URL(voiceSelectMeta.callbackUrl).host;
2477
+ } catch {
2478
+ callbackHost = "invalid";
2479
+ }
2480
+ ctx.log?.info?.(
2481
+ `[ARENA-VOICE] inbound eventId=${voiceSelectMeta.eventId} traceId=${voiceSelectMeta.traceId ?? ""} roomId=${voiceSelectMeta.roomId} seatEpoch=${voiceSelectMeta.seatEpoch} callbackHost=${callbackHost} conversationId=${envelope.conversationId}`
2482
+ );
2483
+ }
1781
2484
  const runtime = getCoolclawRuntime();
1782
2485
  let runtimeChannel;
1783
2486
  try {
@@ -1787,8 +2490,25 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1787
2490
  });
1788
2491
  } catch (err) {
1789
2492
  logInboundDrop({ log: ctx.log?.warn?.bind(ctx.log) ?? (() => {
1790
- }), channel: "coolclaw", reason: "runtime not available; skipping dispatch" });
2493
+ }), channel: productFlavor.channelId, reason: "runtime not available; skipping dispatch" });
1791
2494
  if (isGameEvent && gameMeta) {
2495
+ if (isStructuredJsonTask(gameMeta.agentTask)) {
2496
+ const submitted2 = await submitRejectedOutputWithLog({
2497
+ meta: gameMeta,
2498
+ wsClient,
2499
+ log: ctx.log,
2500
+ reason: "runtime_not_available",
2501
+ rawResponse: "",
2502
+ auditMeta: {
2503
+ retryCount: 0,
2504
+ firstValidationReason: "runtime_not_available"
2505
+ }
2506
+ });
2507
+ if (submitted2 === "failed") {
2508
+ throw new Error("game rejected-output submit failed: runtime_not_available");
2509
+ }
2510
+ return;
2511
+ }
1792
2512
  const submitted = await submitBackendFallbackWithLog({
1793
2513
  meta: gameMeta,
1794
2514
  wsClient,
@@ -1809,6 +2529,15 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1809
2529
  });
1810
2530
  return;
1811
2531
  }
2532
+ if (voiceSelectMeta) {
2533
+ await submitArenaVoiceSelectCallback({
2534
+ meta: voiceSelectMeta,
2535
+ token,
2536
+ rawText: "",
2537
+ log: ctx.log
2538
+ });
2539
+ return;
2540
+ }
1812
2541
  throw err;
1813
2542
  }
1814
2543
  try {
@@ -1821,7 +2550,7 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1821
2550
  }
1822
2551
  const route = await runtimeChannel.routing.resolveAgentRoute({
1823
2552
  cfg: ctx.cfg,
1824
- channel: "coolclaw",
2553
+ channel: productFlavor.channelId,
1825
2554
  accountId: ctx.accountId,
1826
2555
  peer
1827
2556
  });
@@ -1834,18 +2563,18 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1834
2563
  ctx.cfg.session?.store,
1835
2564
  { agentId: route.agentId }
1836
2565
  );
1837
- const senderLabel = envelope.sender ? `${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}` : isGameEvent ? `game:${gameMeta.gameId}` : modelQueryMeta ? `arena-model:${modelQueryMeta.roomId}` : "unknown";
2566
+ 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
2567
  let deliveryTarget;
1839
2568
  if (envelope.group) {
1840
- deliveryTarget = `coolclaw:group:${envelope.group.groupId}`;
2569
+ deliveryTarget = `${productFlavor.targetPrefix}:group:${envelope.group.groupId}`;
1841
2570
  } 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}`;
2571
+ deliveryTarget = `${productFlavor.targetPrefix}:${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}`;
2572
+ } else if (isGameEvent || isArenaModelQuery || isArenaVoiceSelect) {
2573
+ deliveryTarget = `${productFlavor.targetPrefix}:agent:${account.agentId}`;
1845
2574
  } else {
1846
- deliveryTarget = normalizeCoolclawTarget(envelope.conversationId);
2575
+ deliveryTarget = normalizeCoolclawTarget(envelope.conversationId, productFlavor);
1847
2576
  }
1848
- const bodyForAgent = buildBodyForAgent(envelope);
2577
+ const bodyForAgent = buildBodyForAgent(envelope, productFlavor);
1849
2578
  if (typeof runtimeChannel.reply?.finalizeInboundContext !== "function") {
1850
2579
  throw new Error(
1851
2580
  "CoolClaw requires runtime.channel.reply.finalizeInboundContext. Please upgrade OpenClaw to >=2026.3.22."
@@ -1866,32 +2595,33 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1866
2595
  BodyForAgent: bodyForAgent,
1867
2596
  RawBody: envelope.text,
1868
2597
  CommandBody: envelope.text,
1869
- From: `coolclaw:${senderLabel}`,
2598
+ From: `${productFlavor.targetPrefix}:${senderLabel}`,
1870
2599
  To: deliveryTarget,
1871
2600
  SessionKey: route.sessionKey,
1872
2601
  AccountId: ctx.accountId,
1873
2602
  ChatType: isGroup ? "channel" : "direct",
1874
2603
  CommandAuthorized: true,
1875
- Provider: "coolclaw",
1876
- Surface: "coolclaw",
1877
- Channel: "coolclaw",
2604
+ Provider: productFlavor.channelId,
2605
+ Surface: productFlavor.channelId,
2606
+ Channel: productFlavor.channelId,
1878
2607
  Peer: peer,
1879
2608
  WasMentioned: envelope.shouldReply,
1880
2609
  Mentioned: envelope.shouldReply
1881
2610
  });
2611
+ gameBaseReplyContext = ctxPayload;
1882
2612
  const sessionKey = ctxPayload.SessionKey ?? route.sessionKey;
1883
2613
  const mainSessionKey = route.mainSessionKey;
1884
2614
  await runtimeChannel.session.recordInboundSession({
1885
2615
  storePath,
1886
2616
  sessionKey,
1887
2617
  ctx: ctxPayload,
1888
- updateLastRoute: mainSessionKey && mainSessionKey !== sessionKey ? { sessionKey: mainSessionKey, channel: "coolclaw", to: deliveryTarget, accountId: ctx.accountId ?? void 0 } : void 0,
2618
+ updateLastRoute: mainSessionKey && mainSessionKey !== sessionKey ? { sessionKey: mainSessionKey, channel: productFlavor.channelId, to: deliveryTarget, accountId: ctx.accountId ?? void 0 } : void 0,
1889
2619
  onRecordError: (err) => {
1890
2620
  ctx.log?.warn(`recordInboundSession failed: ${err instanceof Error ? err.message : String(err)}`);
1891
2621
  }
1892
2622
  });
1893
- await runtimeChannel.reply.dispatchReplyWithBufferedBlockDispatcher({
1894
- ctx: ctxPayload,
2623
+ dispatchGameReply = async (replyCtxPayload) => runtimeChannel.reply.dispatchReplyWithBufferedBlockDispatcher({
2624
+ ctx: replyCtxPayload,
1895
2625
  cfg: ctx.cfg,
1896
2626
  // 群聊强制走 automatic:CoolClaw 群聊业务语义就是 @ 即回,
1897
2627
  // 而 OpenClaw 默认对 group/channel 走 message_tool_only,
@@ -1908,11 +2638,17 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1908
2638
  await modelQueryCollector.deliver(replyText);
1909
2639
  return;
1910
2640
  }
2641
+ if (voiceSelectCollector) {
2642
+ await voiceSelectCollector.deliver(replyText);
2643
+ return;
2644
+ }
1911
2645
  if (isGameEvent && gameMeta) {
1912
2646
  if (gameSubmitted) return;
1913
2647
  gameBuffer.push(String(payload.text));
2648
+ const structuredTask = isStructuredJsonTask(gameMeta.agentTask);
2649
+ if (structuredTask) return;
1914
2650
  const full = gameBuffer.join("");
1915
- const parsed = parseAgentAction(full);
2651
+ const parsed = structuredTask ? parseStrictJsonAgentAction(full) : parseAgentAction(full);
1916
2652
  if ("error" in parsed) return;
1917
2653
  const validation = validateAgentAction(parsed, gameMeta.agentTask);
1918
2654
  if (!validation.ok) {
@@ -1920,11 +2656,30 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1920
2656
  gameModelActionType = parsed.actionType;
1921
2657
  gameValidationReason = validation.reason;
1922
2658
  gameFallbackReason = validation.reason;
2659
+ if (structuredTask) {
2660
+ const rawHash2 = sha256Hex(full);
2661
+ if (gameReplyAttempt === 0) {
2662
+ gameFirstRawResponseHash = rawHash2;
2663
+ gameFirstModelActionType = parsed.actionType;
2664
+ gameFirstValidationReason = validation.reason;
2665
+ } else {
2666
+ gameRetryRawResponseHash = rawHash2;
2667
+ gameRetryValidationReason = validation.reason;
2668
+ }
2669
+ }
1923
2670
  ctx.log?.warn?.(
1924
- `[GAME-ACTION] rejected model action reason=${validation.reason} actionType=${parsed.actionType} eventType=${gameMeta.eventType} eventId=${gameMeta.eventId} promptPolicyVersion=${gameMeta.promptPolicyVersion ?? ""} renderedPromptHash=${gameMeta.renderedPromptHash ?? ""} rawResponseHash=${sha256Hex(full)}`
2671
+ `[COOLCLAW-GAME-ACTION-VALIDATE] gameId=${gameMeta.gameId} roomId=${gameMeta.roomId} eventType=${gameMeta.eventType} eventId=${gameMeta.eventId} turnSeq=${gameMeta.turnSeq} retryCount=${gameReplyAttempt} submissionStatus=REJECTED_OUTPUT result=reject reason=${validation.reason} actionType=${parsed.actionType} promptPolicyVersion=${gameMeta.promptPolicyVersion ?? ""} renderedPromptHash=${gameMeta.renderedPromptHash ?? ""} rawResponseHash=${sha256Hex(full)}`
1925
2672
  );
1926
2673
  return;
1927
2674
  }
2675
+ if (structuredTask) {
2676
+ gameModelActionRejected = false;
2677
+ gameModelActionType = validation.action.actionType;
2678
+ gameValidationReason = validation.repairReason;
2679
+ gameFallbackReason = null;
2680
+ return;
2681
+ }
2682
+ const rawHash = sha256Hex(full);
1928
2683
  const submitted = await submitGameActionWithLog(
1929
2684
  validation.action,
1930
2685
  gameMeta,
@@ -1935,7 +2690,15 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1935
2690
  {
1936
2691
  modelActionRejected: false,
1937
2692
  modelActionType: validation.action.actionType,
1938
- validationReason: validation.repairReason
2693
+ validationReason: validation.repairReason,
2694
+ submissionStatus: structuredTask ? "VALID" : void 0,
2695
+ structuredProtocolVersion: structuredTask ? gameMeta.agentTask.actionProtocolVersion : void 0,
2696
+ retryCount: structuredTask ? gameReplyAttempt : void 0,
2697
+ firstRawResponseHash: structuredTask ? gameFirstRawResponseHash ?? rawHash : void 0,
2698
+ firstModelActionType: structuredTask ? gameFirstModelActionType : void 0,
2699
+ firstValidationReason: structuredTask ? gameFirstValidationReason : void 0,
2700
+ retryRawResponseHash: structuredTask && gameReplyAttempt > 0 ? rawHash : void 0,
2701
+ retryValidationReason: structuredTask && gameReplyAttempt > 0 ? validation.repairReason : void 0
1939
2702
  }
1940
2703
  );
1941
2704
  if (submitted === "submitted" || submitted === "uncertain") {
@@ -1952,17 +2715,17 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1952
2715
  return;
1953
2716
  }
1954
2717
  if (suppressChatTextDelivery) {
1955
- ctx.log?.error?.(`[ARENA-MODEL] chat delivery blocked eventId=${envelope.metadata.eventId ?? ""}`);
2718
+ ctx.log?.error?.(`[ARENA-CALLBACK] chat delivery blocked eventId=${envelope.metadata.eventId ?? ""}`);
1956
2719
  return;
1957
2720
  }
1958
2721
  try {
1959
2722
  let replyTarget;
1960
2723
  if (envelope.group) {
1961
- replyTarget = `coolclaw:group:${envelope.group.groupId}`;
2724
+ replyTarget = `${productFlavor.targetPrefix}:group:${envelope.group.groupId}`;
1962
2725
  } else if (envelope.sender) {
1963
- replyTarget = `coolclaw:${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}`;
2726
+ replyTarget = `${productFlavor.targetPrefix}:${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}`;
1964
2727
  } else {
1965
- replyTarget = normalizeCoolclawTarget(envelope.conversationId);
2728
+ replyTarget = normalizeCoolclawTarget(envelope.conversationId, productFlavor);
1966
2729
  }
1967
2730
  await sendText({ client: wsClient, target: replyTarget, text: replyText });
1968
2731
  } catch (err) {
@@ -1974,6 +2737,7 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1974
2737
  }
1975
2738
  }
1976
2739
  });
2740
+ await dispatchGameReply(ctxPayload);
1977
2741
  if (isGameEvent && gameMeta && !gameSubmitted) {
1978
2742
  if (!gameFallbackReason) {
1979
2743
  gameFallbackReason = "no_valid_action_in_llm_output";
@@ -1982,6 +2746,9 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1982
2746
  if (modelQueryCollector) {
1983
2747
  await modelQueryCollector.finalize();
1984
2748
  }
2749
+ if (voiceSelectCollector) {
2750
+ await voiceSelectCollector.finalize();
2751
+ }
1985
2752
  } catch (err) {
1986
2753
  const errMsg = err instanceof Error ? err.message : String(err);
1987
2754
  ctx.log?.error(`Inbound dispatch error: ${errMsg}`);
@@ -1993,14 +2760,100 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1993
2760
  })) {
1994
2761
  return;
1995
2762
  }
2763
+ if (voiceSelectCollector) {
2764
+ ctx.log?.warn?.(`[ARENA-VOICE] dispatch failed; submitting fallback callback eventId=${voiceSelectMeta?.eventId ?? ""} err=${errMsg}`);
2765
+ await voiceSelectCollector.finalize();
2766
+ return;
2767
+ }
1996
2768
  if (isGameEvent && gameMeta && !gameSubmitted) {
1997
2769
  gameFallbackReason = `dispatch_error: ${errMsg}`;
1998
2770
  }
1999
2771
  } finally {
2772
+ const submitFinalStructuredGameAction = async () => {
2773
+ if (!isGameEvent || !gameMeta || gameSubmitted || !isStructuredJsonTask(gameMeta.agentTask)) {
2774
+ return false;
2775
+ }
2776
+ const rawResponse = gameBuffer.join("");
2777
+ const result = await submitStructuredFinalActionWithLog({
2778
+ rawResponse,
2779
+ meta: gameMeta,
2780
+ wsClient,
2781
+ log: ctx.log,
2782
+ retryCount: gameReplyAttempt,
2783
+ auditMeta: {
2784
+ retryCount: gameReplyAttempt,
2785
+ firstRawResponseHash: gameFirstRawResponseHash,
2786
+ firstModelActionType: gameFirstModelActionType,
2787
+ firstValidationReason: gameFirstValidationReason,
2788
+ retryRawResponseHash: gameRetryRawResponseHash,
2789
+ retryValidationReason: gameRetryValidationReason
2790
+ }
2791
+ });
2792
+ if (result.submitted) {
2793
+ gameSubmitted = true;
2794
+ gameModelActionRejected = false;
2795
+ gameModelActionType = result.modelActionType;
2796
+ gameValidationReason = result.validationReason;
2797
+ gameFallbackReason = null;
2798
+ return true;
2799
+ }
2800
+ gameModelActionRejected = result.validAction ? false : true;
2801
+ gameModelActionType = result.modelActionType;
2802
+ gameValidationReason = result.reason ?? result.validationReason;
2803
+ gameFallbackReason = result.reason ?? gameFallbackReason ?? "invalid_output";
2804
+ if (rawResponse.trim().length > 0) {
2805
+ const rawHash = result.rawHash ?? sha256Hex(rawResponse);
2806
+ if (gameReplyAttempt === 0) {
2807
+ gameFirstRawResponseHash = gameFirstRawResponseHash ?? rawHash;
2808
+ gameFirstModelActionType = gameFirstModelActionType ?? result.modelActionType;
2809
+ gameFirstValidationReason = gameFirstValidationReason ?? gameValidationReason;
2810
+ } else {
2811
+ gameRetryRawResponseHash = gameRetryRawResponseHash ?? rawHash;
2812
+ gameRetryValidationReason = gameRetryValidationReason ?? gameValidationReason;
2813
+ }
2814
+ }
2815
+ return false;
2816
+ };
2817
+ await submitFinalStructuredGameAction();
2818
+ if (isGameEvent && gameMeta && !gameSubmitted && gameReplyAttempt === 0 && isStructuredJsonTask(gameMeta.agentTask) && dispatchGameReply && gameBaseReplyContext && hasStructuredRetryBudget(gameMeta.deadlineEpochMs) && Math.max(0, gameMeta.agentTask.retryPolicy?.maxRetries ?? 0) > 0) {
2819
+ const firstRawResponse = gameBuffer.join("");
2820
+ if (firstRawResponse.trim().length > 0) {
2821
+ const firstInferred = inferRejectedModelAction(firstRawResponse, gameMeta.agentTask);
2822
+ if (gameModelActionRejected === false || firstInferred.validationReason === "valid_action_not_submitted") {
2823
+ ctx.log?.warn?.(
2824
+ `[GAME-ACTION] structured retry skipped for valid unsubmitted action eventId=${gameMeta.eventId} turnSeq=${gameMeta.turnSeq} reason=${gameFallbackReason ?? firstInferred.validationReason ?? "submit_failed"}`
2825
+ );
2826
+ } else {
2827
+ const firstRawHash = sha256Hex(firstRawResponse);
2828
+ gameFirstRawResponseHash = gameFirstRawResponseHash ?? firstRawHash;
2829
+ gameFirstModelActionType = gameFirstModelActionType ?? firstInferred.modelActionType;
2830
+ gameFirstValidationReason = gameFirstValidationReason ?? gameValidationReason ?? firstInferred.validationReason;
2831
+ const retryReason = gameFirstValidationReason ?? firstInferred.validationReason ?? "invalid_output";
2832
+ ctx.log?.warn?.(
2833
+ `[GAME-ACTION] structured retry start eventId=${gameMeta.eventId} turnSeq=${gameMeta.turnSeq} reason=${retryReason} firstRawResponseHash=${firstRawHash}`
2834
+ );
2835
+ gameReplyAttempt = 1;
2836
+ gameBuffer.length = 0;
2837
+ gameFallbackReason = null;
2838
+ gameModelActionRejected = void 0;
2839
+ gameModelActionType = void 0;
2840
+ gameValidationReason = void 0;
2841
+ const retryPrompt = buildStructuredActionRetryPrompt(envelope.text, retryReason);
2842
+ await dispatchGameReply({
2843
+ ...gameBaseReplyContext,
2844
+ Body: retryPrompt,
2845
+ BodyForAgent: retryPrompt,
2846
+ RawBody: retryPrompt,
2847
+ CommandBody: retryPrompt
2848
+ });
2849
+ await submitFinalStructuredGameAction();
2850
+ }
2851
+ }
2852
+ }
2000
2853
  if (isGameEvent && gameMeta) {
2001
2854
  const rawResponse = gameBuffer.join("");
2002
2855
  ctx.log?.info?.(
2003
- `[GAME-TASK] model-output eventId=${gameMeta.eventId} promptPolicyVersion=${gameMeta.promptPolicyVersion ?? ""} renderedPromptHash=${gameMeta.renderedPromptHash ?? ""} rawHash=${rawResponse ? sha256Hex(rawResponse) : ""} rawPreview=${rawResponsePreview(rawResponse) ?? ""}`
2856
+ `[GAME-TASK] model-output eventId=${gameMeta.eventId} turnSeq=${gameMeta.turnSeq} retryAttempt=${gameReplyAttempt} promptPolicyVersion=${gameMeta.promptPolicyVersion ?? ""} renderedPromptHash=${gameMeta.renderedPromptHash ?? ""} rawHash=${rawResponse ? sha256Hex(rawResponse) : ""} rawResponseText=${rawResponse ? flattenForLog(rawResponse) : ""}`
2004
2857
  );
2005
2858
  }
2006
2859
  if (isGameEvent && gameMeta && !gameSubmitted) {
@@ -2013,6 +2866,55 @@ var coolclawChannelPlugin = createChatChannelPlugin({
2013
2866
  if (!gameFallbackReason || gameFallbackReason === "no_valid_action_in_llm_output") {
2014
2867
  gameFallbackReason = gameValidationReason ?? inferred.validationReason ?? gameFallbackReason;
2015
2868
  }
2869
+ if (isStructuredJsonTask(gameMeta.agentTask)) {
2870
+ if (rawResponse.trim().length === 0) {
2871
+ gameFallbackReason = "no_model_output";
2872
+ gameValidationReason = gameValidationReason ?? "no_model_output";
2873
+ }
2874
+ if (gameReplyAttempt > 0 && rawResponse.trim().length > 0) {
2875
+ gameRetryRawResponseHash = gameRetryRawResponseHash ?? sha256Hex(rawResponse);
2876
+ gameRetryValidationReason = gameRetryValidationReason ?? gameValidationReason ?? inferred.validationReason;
2877
+ }
2878
+ const rawHashForAudit = rawResponse.trim().length > 0 ? sha256Hex(rawResponse) : void 0;
2879
+ const rejectedOutputReason = gameFallbackReason ?? gameValidationReason ?? inferred.validationReason ?? "invalid_json";
2880
+ if (!shouldSubmitStructuredRejectedOutput({
2881
+ structuredTask: true,
2882
+ gameSubmitted,
2883
+ modelActionRejected: gameModelActionRejected,
2884
+ fallbackReason: rejectedOutputReason
2885
+ })) {
2886
+ logStructuredActionValidation({
2887
+ log: ctx.log,
2888
+ meta: gameMeta,
2889
+ retryCount: gameReplyAttempt,
2890
+ submissionStatus: "VALID",
2891
+ result: "submit_failed_no_rejected_output",
2892
+ reason: rejectedOutputReason,
2893
+ actionType: gameModelActionType,
2894
+ rawResponseHash: rawHashForAudit
2895
+ });
2896
+ return;
2897
+ }
2898
+ const submitted2 = await submitRejectedOutputWithLog({
2899
+ meta: gameMeta,
2900
+ wsClient,
2901
+ log: ctx.log,
2902
+ reason: rejectedOutputReason,
2903
+ rawResponse,
2904
+ auditMeta: {
2905
+ retryCount: gameReplyAttempt,
2906
+ firstRawResponseHash: gameFirstRawResponseHash ?? rawHashForAudit,
2907
+ firstModelActionType: gameFirstModelActionType ?? gameModelActionType,
2908
+ firstValidationReason: gameFirstValidationReason ?? gameValidationReason ?? inferred.validationReason,
2909
+ retryRawResponseHash: gameRetryRawResponseHash,
2910
+ retryValidationReason: gameRetryValidationReason
2911
+ }
2912
+ });
2913
+ if (submitted2 === "failed") {
2914
+ throw new Error(`game rejected-output submit failed: ${gameFallbackReason ?? "unknown"}`);
2915
+ }
2916
+ return;
2917
+ }
2016
2918
  const submitted = await submitBackendFallbackWithLog({
2017
2919
  meta: gameMeta,
2018
2920
  wsClient,
@@ -2043,7 +2945,7 @@ var coolclawChannelPlugin = createChatChannelPlugin({
2043
2945
  ctx.log?.debug?.(`ACK sent: type=${ackFrame.type} lastAckedSeq=${ackFrame.payload?.lastAckedSeq}`);
2044
2946
  } catch (err) {
2045
2947
  logAckFailure({ log: ctx.log?.warn?.bind(ctx.log) ?? (() => {
2046
- }), channel: "coolclaw", error: err });
2948
+ }), channel: productFlavor.channelId, error: err });
2047
2949
  throw err;
2048
2950
  }
2049
2951
  }
@@ -2056,7 +2958,7 @@ var coolclawChannelPlugin = createChatChannelPlugin({
2056
2958
  await client.start();
2057
2959
  setRuntimeClient(accountKey, client);
2058
2960
  statusSink({ statusState: "connected" });
2059
- ctx.log?.info(`[${ctx.accountId}] CoolClaw provider connected`);
2961
+ ctx.log?.info(`[${ctx.accountId}] ${productFlavor.displayName} provider connected`);
2060
2962
  return client;
2061
2963
  },
2062
2964
  stop: async (client) => {
@@ -2065,25 +2967,25 @@ var coolclawChannelPlugin = createChatChannelPlugin({
2065
2967
  },
2066
2968
  onStop: () => {
2067
2969
  statusSink({ statusState: "disconnected" });
2068
- ctx.log?.info(`[${ctx.accountId}] CoolClaw provider stopped`);
2970
+ ctx.log?.info(`[${ctx.accountId}] ${productFlavor.displayName} provider stopped`);
2069
2971
  }
2070
2972
  });
2071
2973
  },
2072
2974
  /** 显式停止账户连接,清理 WebSocket 客户端资源 */
2073
2975
  async stopAccount(ctx) {
2074
- const accountKey = `coolclaw:${ctx.accountId ?? "default"}`;
2976
+ const accountKey = `${productFlavor.channelId}:${ctx.accountId ?? "default"}`;
2075
2977
  const client = getRuntimeClient(accountKey);
2076
2978
  if (client) {
2077
2979
  await client.stop();
2078
2980
  clearRuntimeClient(accountKey);
2079
- ctx.log?.info(`[${ctx.accountId}] CoolClaw client stopped via stopAccount`);
2981
+ ctx.log?.info(`[${ctx.accountId}] ${productFlavor.displayName} client stopped via stopAccount`);
2080
2982
  }
2081
2983
  }
2082
2984
  }
2083
2985
  },
2084
2986
  security: {
2085
2987
  dm: {
2086
- channelKey: "coolclaw",
2988
+ channelKey: productFlavor.channelId,
2087
2989
  resolvePolicy: (account) => account.dmPolicy ?? "allowlist",
2088
2990
  resolveAllowFrom: (account) => account.allowFrom ?? [],
2089
2991
  defaultPolicy: "allowlist",
@@ -2092,10 +2994,10 @@ var coolclawChannelPlugin = createChatChannelPlugin({
2092
2994
  },
2093
2995
  pairing: {
2094
2996
  text: {
2095
- idLabel: "CoolClaw user ID",
2997
+ idLabel: `${productFlavor.displayName} user ID`,
2096
2998
  message: "You are not authorized to message this agent. Send this pairing code to verify:",
2097
2999
  notify: async ({ id, message }) => {
2098
- const client = getRuntimeClient("coolclaw:default");
3000
+ const client = getRuntimeClient(`${productFlavor.channelId}:default`);
2099
3001
  if (client) {
2100
3002
  await sendText({
2101
3003
  client,
@@ -2114,11 +3016,11 @@ var coolclawChannelPlugin = createChatChannelPlugin({
2114
3016
  deliveryMode: "direct",
2115
3017
  resolveTarget({ to }) {
2116
3018
  if (!to) {
2117
- return { ok: false, error: new Error("CoolClaw target is required") };
3019
+ return { ok: false, error: new Error(`${productFlavor.displayName} target is required`) };
2118
3020
  }
2119
3021
  try {
2120
- const normalized = normalizeCoolclawTarget(to);
2121
- parseCoolclawTarget(normalized);
3022
+ const normalized = normalizeCoolclawTarget(to, productFlavor);
3023
+ parseCoolclawTarget(normalized, productFlavor);
2122
3024
  return { ok: true, to: normalized };
2123
3025
  } catch (error) {
2124
3026
  return { ok: false, error: error instanceof Error ? error : new Error(String(error)) };
@@ -2126,14 +3028,14 @@ var coolclawChannelPlugin = createChatChannelPlugin({
2126
3028
  }
2127
3029
  },
2128
3030
  attachedResults: {
2129
- channel: "coolclaw",
3031
+ channel: productFlavor.channelId,
2130
3032
  async sendText(ctx) {
2131
3033
  const account = coolclawChannelPlugin.config.resolveAccount(ctx.cfg, ctx.accountId);
2132
3034
  const token = await resolveAccountToken(account);
2133
3035
  if (!account.gatewayUrl || !account.agentId || !token) {
2134
- throw new Error("CoolClaw account is not fully configured");
3036
+ throw new Error(`${productFlavor.displayName} account is not fully configured`);
2135
3037
  }
2136
- const accountKey = `coolclaw:${ctx.accountId ?? "default"}`;
3038
+ const accountKey = `${productFlavor.channelId}:${ctx.accountId ?? "default"}`;
2137
3039
  let client = getRuntimeClient(accountKey);
2138
3040
  if (!client || !client.isConnected()) {
2139
3041
  client = new CoolclawWsClient({
@@ -2159,9 +3061,9 @@ var coolclawChannelPlugin = createChatChannelPlugin({
2159
3061
  const account = coolclawChannelPlugin.config.resolveAccount(ctx.cfg, ctx.accountId);
2160
3062
  const token = await resolveAccountToken(account);
2161
3063
  if (!account.gatewayUrl || !account.agentId || !token) {
2162
- throw new Error("CoolClaw account is not fully configured");
3064
+ throw new Error(`${productFlavor.displayName} account is not fully configured`);
2163
3065
  }
2164
- const accountKey = `coolclaw:${ctx.accountId ?? "default"}`;
3066
+ const accountKey = `${productFlavor.channelId}:${ctx.accountId ?? "default"}`;
2165
3067
  let client = getRuntimeClient(accountKey);
2166
3068
  if (!client || !client.isConnected()) {
2167
3069
  client = new CoolclawWsClient({
@@ -2186,13 +3088,17 @@ var coolclawChannelPlugin = createChatChannelPlugin({
2186
3088
  }
2187
3089
  }
2188
3090
  });
2189
- function buildBodyForAgent(envelope) {
3091
+ function buildBodyForAgent(envelope, flavorInput) {
3092
+ const flavor = resolveFlavor(flavorInput);
3093
+ if (isArenaVoiceSelectEnvelope(envelope)) {
3094
+ return buildArenaVoiceSelectBodyForAgent(envelope, flavor);
3095
+ }
2190
3096
  if (isArenaReportShareEnvelope(envelope)) {
2191
- return buildArenaReportShareBodyForAgent(envelope);
3097
+ return buildArenaReportShareBodyForAgent(envelope, flavor);
2192
3098
  }
2193
3099
  if (!envelope.group) {
2194
3100
  if (envelope.sender && envelope.recipient) {
2195
- return buildPrivateBodyForAgent(envelope);
3101
+ return buildPrivateBodyForAgent(envelope, flavor);
2196
3102
  }
2197
3103
  const hints = [
2198
3104
  envelope.metadata?.securityHint,
@@ -2203,7 +3109,7 @@ function buildBodyForAgent(envelope) {
2203
3109
  const sender = formatUserRef(envelope.sender, "\u672A\u77E5\u53D1\u9001\u4EBA");
2204
3110
  const owner = formatUserRef(envelope.owner, "\u672A\u77E5\u4E3B\u4EBA");
2205
3111
  const lines = [
2206
- "\u4F60\u6536\u5230\u4E00\u6761 CoolClaw \u7FA4\u804A\u6D88\u606F\u3002",
3112
+ `\u4F60\u6536\u5230\u4E00\u6761 ${flavor.displayName} \u7FA4\u804A\u6D88\u606F\u3002`,
2207
3113
  "",
2208
3114
  `\u7FA4\u804A\uFF1A${envelope.group.groupName}(${envelope.group.groupId})`,
2209
3115
  `\u53D1\u9001\u4EBA\uFF1A${sender}`,
@@ -2211,7 +3117,7 @@ function buildBodyForAgent(envelope) {
2211
3117
  envelope.text,
2212
3118
  "",
2213
3119
  "\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`,
3120
+ `\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
3121
  "",
2216
3122
  "---",
2217
3123
  "\u7CFB\u7EDF\u63D0\u793A\uFF1A",
@@ -2235,7 +3141,44 @@ function buildBodyForAgent(envelope) {
2235
3141
  }
2236
3142
  return lines.join("\n");
2237
3143
  }
2238
- function buildArenaReportShareBodyForAgent(envelope) {
3144
+ function buildArenaVoiceSelectBodyForAgent(envelope, flavor) {
3145
+ const voiceOptions = Array.isArray(envelope.metadata.voiceOptions) ? envelope.metadata.voiceOptions : [];
3146
+ const optionLines = voiceOptions.map((rawOption) => {
3147
+ const option = isPlainRecord(rawOption) ? rawOption : {};
3148
+ const voiceId = formatUnknown(option.voiceId);
3149
+ const voiceName = formatUnknown(option.voiceName);
3150
+ const description = formatUnknown(option.description);
3151
+ const speed = formatUnknown(option.speed);
3152
+ const detail = [
3153
+ voiceName ? `\u540D\u79F0=${voiceName}` : "",
3154
+ description ? `\u63CF\u8FF0=${description}` : "",
3155
+ speed ? `\u8BED\u901F=${speed}` : ""
3156
+ ].filter(Boolean).join("\uFF1B");
3157
+ return detail ? `- ${voiceId}\uFF1A${detail}` : `- ${voiceId}`;
3158
+ }).filter((line) => line !== "- ");
3159
+ return [
3160
+ `\u4F60\u6536\u5230\u4E00\u4E2A ${flavor.displayName} \u72FC\u4EBA\u6740\u8D5B\u524D\u97F3\u8272\u9009\u62E9\u4EFB\u52A1\u3002`,
3161
+ "",
3162
+ "\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",
3163
+ `eventId\uFF1A${formatUnknown(envelope.metadata.eventId)}`,
3164
+ `roomId\uFF1A${formatUnknown(envelope.metadata.roomId)}`,
3165
+ `seatNumber\uFF1A${formatUnknown(envelope.metadata.seatNumber)}`,
3166
+ `seatEpoch\uFF1A${formatUnknown(envelope.metadata.seatEpoch)}`,
3167
+ "",
3168
+ "\u53EF\u9009\u73A9\u5BB6\u97F3\u8272\uFF1A",
3169
+ ...optionLines.length > 0 ? optionLines : ["- \u65E0"],
3170
+ "",
3171
+ "\u8F93\u51FA\u8981\u6C42\uFF1A",
3172
+ "\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",
3173
+ '{"topVoiceIds":["4139","4172","5977"],"reason":"\u4E00\u53E5\u8BDD\u8BF4\u660E\u6574\u4F53\u9009\u62E9\u503E\u5411"}',
3174
+ "",
3175
+ "\u7EA6\u675F\uFF1A",
3176
+ "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",
3177
+ "2. \u4E0D\u8981\u628A voiceId \u8F6C\u6210\u6570\u5B57\u3002",
3178
+ "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"
3179
+ ].join("\n");
3180
+ }
3181
+ function buildArenaReportShareBodyForAgent(envelope, flavor) {
2239
3182
  const payload = isPlainRecord(envelope.metadata.reportPayload) ? envelope.metadata.reportPayload : {};
2240
3183
  const gameId = formatUnknown(payload.gameId);
2241
3184
  const roomId = formatUnknown(payload.roomId);
@@ -2248,7 +3191,7 @@ function buildArenaReportShareBodyForAgent(envelope) {
2248
3191
  const eventId = formatUnknown(envelope.metadata.eventId);
2249
3192
  const traceId = formatUnknown(envelope.metadata.traceId);
2250
3193
  return [
2251
- "\u4F60\u6536\u5230\u4E00\u4E2A CoolClaw \u7ADE\u6280\u573A\u6218\u62A5\u5206\u4EAB\u59D4\u6258\u4EFB\u52A1\u3002",
3194
+ `\u4F60\u6536\u5230\u4E00\u4E2A ${flavor.displayName} \u7ADE\u6280\u573A\u6218\u62A5\u5206\u4EAB\u59D4\u6258\u4EFB\u52A1\u3002`,
2252
3195
  "",
2253
3196
  "\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
3197
  `eventId\uFF1A${eventId}`,
@@ -2264,14 +3207,14 @@ function buildArenaReportShareBodyForAgent(envelope) {
2264
3207
  `result\uFF1A${result}`,
2265
3208
  "",
2266
3209
  "\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",
3210
+ `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
3211
  "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
3212
  `3. \u4F7F\u7528 Gateway Base URL \u62FC\u63A5 replayApiPath=${replayApiPath} \u8BFB\u53D6\u672C\u5C40\u6218\u62A5\u3002`,
2270
3213
  `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
3214
  `5. \u4F7F\u7528\u540C\u4E00\u4E2A Gateway Base URL \u62FC\u63A5 createPostApiPath=${createPostApiPath} \u53D1\u5E03\u5E16\u5B50\u3002`,
2272
3215
  "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
3216
  "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",
3217
+ `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
3218
  "",
2276
3219
  "\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
3220
  ].join("\n");
@@ -2285,18 +3228,18 @@ function formatUnknown(value) {
2285
3228
  if (typeof value === "number" || typeof value === "boolean") return String(value);
2286
3229
  return JSON.stringify(value);
2287
3230
  }
2288
- function buildPrivateBodyForAgent(envelope) {
3231
+ function buildPrivateBodyForAgent(envelope, flavor) {
2289
3232
  const sender = formatUserRef(envelope.sender, "\u672A\u77E5\u53D1\u9001\u4EBA");
2290
3233
  const owner = formatUserRef(envelope.owner, "\u672A\u77E5\u4E3B\u4EBA");
2291
3234
  const lines = [
2292
- "\u4F60\u6536\u5230\u4E00\u6761 CoolClaw \u79C1\u804A\u6D88\u606F\u3002",
3235
+ `\u4F60\u6536\u5230\u4E00\u6761 ${flavor.displayName} \u79C1\u804A\u6D88\u606F\u3002`,
2293
3236
  "",
2294
3237
  `\u53D1\u9001\u4EBA\uFF1A${sender}`,
2295
3238
  "\u6D88\u606F\u5185\u5BB9\uFF1A",
2296
3239
  envelope.text,
2297
3240
  "",
2298
3241
  "\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`,
3242
+ `\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
3243
  "",
2301
3244
  "---",
2302
3245
  "\u7CFB\u7EDF\u63D0\u793A\uFF1A",
@@ -2326,6 +3269,7 @@ function normalizeDirectHintLines(raw) {
2326
3269
  }
2327
3270
 
2328
3271
  export {
3272
+ activeFlavor,
2329
3273
  defaultBindingFile,
2330
3274
  CoolclawConfigSchema,
2331
3275
  setCoolclawRuntime,