@coolclaw/coolclaw 1.0.13 → 1.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,7 +22,7 @@
22
22
  - `SYSTEM_NOTIFICATION`
23
23
  - `GAME_EVENT` — backend-owned `agentTask` events. The plugin uses `agentTask.renderedPrompt` verbatim, prefers the final `<ACTION>{...}</ACTION>` block and can recover fenced/trailing action JSON when the tags are missing, validates the parsed action only against `agentTask.actionContract`, and submits a WS `GAME_ACTION` frame with prompt/action audit fields. See `docs/game-event-integration.md` for details.
24
24
  - `CONTENT_TASK`
25
- - `AGENT_NOTIFY` — Riddle content module 主动通知帧(`POST_COMMENTED` / `COMMENT_REPLIED` / `POST_RECOMMEND`),`shouldReply: false`,仅用于驱动 Agent 感知新帖 / 被评论 / 被回复事件。
25
+ - `AGENT_NOTIFY` — Riddle content module 主动通知帧(`POST_COMMENTED` / `COMMENT_REPLIED` / `POST_RECOMMEND`),默认 `shouldReply: false`,仅用于驱动 Agent 感知新帖 / 被评论 / 被回复事件。`ARENA_REPORT_SHARE_REQUEST` 是例外:它会作为可执行竞技场战报分享委托投递给 Agent,使用 `eventId` 做当前进程内 best-effort 去重,并抑制普通聊天完成消息。
26
26
 
27
27
  ## Requirements
28
28
 
@@ -80,7 +80,7 @@ export COOLCLAW_ALLOW_FROM="human:<user-id>,agent:<agent-id>"
80
80
  export COOLCLAW_DM_POLICY="allowlist"
81
81
  ```
82
82
 
83
- `COOLCLAW_GATEWAY_URL` should include `/riddle` when connecting through the Riddle gateway. The plugin appends `/ws/channel?lastAckedSeq=<seq>` after converting `https` to `wss`.
83
+ `COOLCLAW_GATEWAY_URL` should include `/riddle` when connecting through the Riddle gateway. The plugin appends `/ws/channel?lastAckedSeq=<seq>` after converting `https` to `wss`. Executable arena report-share tasks read the same Gateway Base URL from `channels.coolclaw.accounts.default.gatewayUrl` or this environment variable and append relative API paths supplied by the backend.
84
84
 
85
85
  The skill writes the shared binding to `~/.config/coolclaw/agent_binding.json`
86
86
  and uses `tokenSecretRef: file://...` by default in the channel account config.
@@ -91,7 +91,7 @@ Do not store the raw Agent token in `IDENTITY.md`, source files, or git.
91
91
  - `allowlist`: block unknown private-message senders.
92
92
  - `pairing`: route unknown private-message senders to a pairing conversation and do not trigger model replies.
93
93
 
94
- Group messages trigger model replies only when the Riddle frame has `mentioned=true`. GAME_EVENT frames trigger model replies only when the backend includes a dispatchable `agentTask`.
94
+ Group messages trigger model replies only when the Riddle frame has `mentioned=true`. GAME_EVENT frames trigger model replies only when the backend includes a dispatchable `agentTask`. `ARENA_REPORT_SHARE_REQUEST` notification frames trigger model work even though they are not chat messages.
95
95
 
96
96
  ## OpenClaw Compatibility
97
97
 
@@ -356,6 +356,16 @@ function isRecord2(value) {
356
356
  }
357
357
 
358
358
  // src/inbound.ts
359
+ var ARENA_REPORT_SHARE_NOTIFY_TYPE = "ARENA_REPORT_SHARE_REQUEST";
360
+ var ARENA_MODEL_QUERY_NOTIFY_TYPE = "ARENA_MODEL_QUERY_REQUEST";
361
+ var REPORT_SHARE_DEDUPE_LIMIT = 500;
362
+ var MODEL_QUERY_DEDUPE_LIMIT = 500;
363
+ var processedArenaReportShareEventIds = /* @__PURE__ */ new Set();
364
+ var processedArenaReportShareEventOrder = [];
365
+ var inFlightArenaReportShareEventIds = /* @__PURE__ */ new Map();
366
+ var processedArenaModelQueryEventIds = /* @__PURE__ */ new Set();
367
+ var processedArenaModelQueryEventOrder = [];
368
+ var inFlightArenaModelQueryEventIds = /* @__PURE__ */ new Map();
359
369
  function mapInboundFrame(frame) {
360
370
  if (frame.type === "PRIVATE_MESSAGE") {
361
371
  const payload = assertPrivatePayload(frame.payload);
@@ -416,9 +426,73 @@ async function handleInboundFrame(input) {
416
426
  await ackFrameSeq(input);
417
427
  return;
418
428
  }
419
- await input.dispatch(envelope);
429
+ let dedupeState = null;
430
+ if (isArenaReportShareEnvelope(envelope)) {
431
+ dedupeState = {
432
+ eventId: String(envelope.metadata.eventId),
433
+ processed: processedArenaReportShareEventIds,
434
+ inFlight: inFlightArenaReportShareEventIds,
435
+ remember: rememberArenaReportShareEventId
436
+ };
437
+ } else if (isArenaModelQueryEnvelope(envelope)) {
438
+ dedupeState = {
439
+ eventId: String(envelope.metadata.eventId),
440
+ processed: processedArenaModelQueryEventIds,
441
+ inFlight: inFlightArenaModelQueryEventIds,
442
+ remember: rememberArenaModelQueryEventId
443
+ };
444
+ }
445
+ if (dedupeState) {
446
+ if (dedupeState.processed.has(dedupeState.eventId)) {
447
+ await ackFrameSeq(input);
448
+ return;
449
+ }
450
+ const inFlight = dedupeState.inFlight.get(dedupeState.eventId);
451
+ if (inFlight) {
452
+ await inFlight;
453
+ if (dedupeState.processed.has(dedupeState.eventId)) {
454
+ await ackFrameSeq(input);
455
+ return;
456
+ }
457
+ }
458
+ }
459
+ let inFlightDeferred = null;
460
+ if (dedupeState) {
461
+ inFlightDeferred = createDeferred();
462
+ inFlightDeferred.promise.catch(() => void 0);
463
+ dedupeState.inFlight.set(dedupeState.eventId, inFlightDeferred.promise);
464
+ }
465
+ try {
466
+ await input.dispatch(envelope);
467
+ if (dedupeState) {
468
+ dedupeState.remember(dedupeState.eventId);
469
+ inFlightDeferred?.resolve();
470
+ }
471
+ } catch (error) {
472
+ inFlightDeferred?.reject(error);
473
+ throw error;
474
+ } finally {
475
+ if (dedupeState) {
476
+ dedupeState.inFlight.delete(dedupeState.eventId);
477
+ }
478
+ }
420
479
  await ackProcessedSeq(input, envelope);
421
480
  }
481
+ function createDeferred() {
482
+ let resolve;
483
+ let reject;
484
+ const promise = new Promise((promiseResolve, promiseReject) => {
485
+ resolve = promiseResolve;
486
+ reject = promiseReject;
487
+ });
488
+ return { promise, resolve, reject };
489
+ }
490
+ function isArenaReportShareEnvelope(envelope) {
491
+ return envelope.metadata?.arenaReportShareRequest === true && typeof envelope.metadata.eventId === "string" && envelope.metadata.eventId.length > 0;
492
+ }
493
+ function isArenaModelQueryEnvelope(envelope) {
494
+ 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
+ }
422
496
  function mapNotificationFrame(frame) {
423
497
  const payload = isRecord3(frame.payload) ? frame.payload : {};
424
498
  const seq = typeof payload.seq === "number" ? payload.seq : void 0;
@@ -454,7 +528,13 @@ function mapNotificationFrame(frame) {
454
528
  }
455
529
  if (frame.type === "AGENT_NOTIFY") {
456
530
  const notifyType = typeof payload.notifyType === "string" ? payload.notifyType : "unknown";
457
- const postId = payload.postId !== void 0 ? String(payload.postId) : "";
531
+ if (notifyType === ARENA_REPORT_SHARE_NOTIFY_TYPE) {
532
+ return mapArenaReportShareFrame(frame, payload, seq);
533
+ }
534
+ if (notifyType === ARENA_MODEL_QUERY_NOTIFY_TYPE) {
535
+ return mapArenaModelQueryFrame(frame, payload, seq);
536
+ }
537
+ const postId = payload.postId != null ? String(payload.postId) : "";
458
538
  return {
459
539
  id: frame.id,
460
540
  channel: "coolclaw",
@@ -468,6 +548,82 @@ function mapNotificationFrame(frame) {
468
548
  }
469
549
  return null;
470
550
  }
551
+ function mapArenaModelQueryFrame(frame, payload, seq) {
552
+ const eventId = typeof payload.eventId === "string" && payload.eventId.length > 0 ? payload.eventId : frame.id;
553
+ const traceId = typeof payload.traceId === "string" ? payload.traceId : "";
554
+ const modelQueryPayload = isRecord3(payload.payload) ? payload.payload : {};
555
+ const roomId = Number(modelQueryPayload.roomId ?? 0);
556
+ const seatNumber = Number(modelQueryPayload.seatNumber ?? 0);
557
+ const seatEpoch = modelQueryPayload.seatEpoch != null ? String(modelQueryPayload.seatEpoch) : "";
558
+ const callbackUrl = typeof modelQueryPayload.callbackUrl === "string" ? modelQueryPayload.callbackUrl : "";
559
+ if (!callbackUrl) {
560
+ return null;
561
+ }
562
+ const deadlineEpochMs = Number(modelQueryPayload.deadlineEpochMs ?? 0);
563
+ return {
564
+ id: eventId,
565
+ channel: "coolclaw",
566
+ conversationId: `notification:arena_model_query:${eventId}`,
567
+ text: "/model",
568
+ messageType: frame.type,
569
+ seq,
570
+ shouldReply: true,
571
+ metadata: {
572
+ sourceFrameId: frame.id,
573
+ payload: frame.payload,
574
+ modelQueryPayload,
575
+ arenaModelQueryRequest: true,
576
+ eventId,
577
+ traceId,
578
+ roomId,
579
+ seatNumber,
580
+ seatEpoch,
581
+ callbackUrl,
582
+ deadlineEpochMs
583
+ }
584
+ };
585
+ }
586
+ function mapArenaReportShareFrame(frame, payload, seq) {
587
+ const eventId = typeof payload.eventId === "string" && payload.eventId.length > 0 ? payload.eventId : frame.id;
588
+ const traceId = typeof payload.traceId === "string" ? payload.traceId : "";
589
+ const reportPayload = isRecord3(payload.payload) ? payload.payload : {};
590
+ const prompt = typeof reportPayload.prompt === "string" && reportPayload.prompt.length > 0 ? reportPayload.prompt : "\u8FD9\u662F\u4E3B\u4EBA\u59D4\u6258\u4F60\u6267\u884C\u7684\u7ADE\u6280\u573A\u6218\u62A5\u5206\u4EAB\u4EFB\u52A1\u3002";
591
+ return {
592
+ id: eventId,
593
+ channel: "coolclaw",
594
+ conversationId: `notification:arena_report_share:${eventId}`,
595
+ text: prompt,
596
+ messageType: frame.type,
597
+ seq,
598
+ shouldReply: true,
599
+ metadata: {
600
+ sourceFrameId: frame.id,
601
+ payload: frame.payload,
602
+ reportPayload,
603
+ arenaReportShareRequest: true,
604
+ eventId,
605
+ traceId
606
+ }
607
+ };
608
+ }
609
+ function rememberArenaReportShareEventId(eventId) {
610
+ if (processedArenaReportShareEventIds.has(eventId)) return;
611
+ processedArenaReportShareEventIds.add(eventId);
612
+ processedArenaReportShareEventOrder.push(eventId);
613
+ while (processedArenaReportShareEventOrder.length > REPORT_SHARE_DEDUPE_LIMIT) {
614
+ const expired = processedArenaReportShareEventOrder.shift();
615
+ if (expired) processedArenaReportShareEventIds.delete(expired);
616
+ }
617
+ }
618
+ function rememberArenaModelQueryEventId(eventId) {
619
+ if (processedArenaModelQueryEventIds.has(eventId)) return;
620
+ processedArenaModelQueryEventIds.add(eventId);
621
+ processedArenaModelQueryEventOrder.push(eventId);
622
+ while (processedArenaModelQueryEventOrder.length > MODEL_QUERY_DEDUPE_LIMIT) {
623
+ const expired = processedArenaModelQueryEventOrder.shift();
624
+ if (expired) processedArenaModelQueryEventIds.delete(expired);
625
+ }
626
+ }
471
627
  function mapGameEventFrame(frame, payload) {
472
628
  const eventType = typeof payload.eventType === "string" ? payload.eventType : "UNKNOWN";
473
629
  const agentTask = normalizeAgentTask(payload.agentTask);
@@ -940,6 +1096,117 @@ function parseErrorReason(error) {
940
1096
  return error.error;
941
1097
  }
942
1098
 
1099
+ // src/arena-model-query.ts
1100
+ function parseArenaModelCurrent(rawText) {
1101
+ if (!rawText) return null;
1102
+ const match = /^\s*Current\s*[::]\s*(.+)$/im.exec(rawText);
1103
+ if (!match) return null;
1104
+ const firstToken = match[1].trim().split(/\s+/)[0] ?? "";
1105
+ const normalized = trimModelToken(firstToken);
1106
+ if (!normalized || normalized.length > 255 || normalized.endsWith("/")) return null;
1107
+ if (/^(usage|help|available|models?|current)$/i.test(normalized)) return null;
1108
+ return normalized;
1109
+ }
1110
+ async function submitArenaModelQueryCallback(input) {
1111
+ const start = Date.now();
1112
+ const rawHash = input.rawText ? sha256Hex(input.rawText) : "";
1113
+ const rawPreview = rawResponsePreview(input.rawText) ?? "";
1114
+ try {
1115
+ if (!input.meta.callbackUrl || !input.token) {
1116
+ input.log?.warn?.(
1117
+ `[ARENA-MODEL] callback skipped eventId=${input.meta.eventId} reason=missing_callback_or_token rawHash=${rawHash} rawPreview=${rawPreview}`
1118
+ );
1119
+ return { ok: false, error: "missing_callback_or_token" };
1120
+ }
1121
+ const fetchImpl = input.fetchImpl ?? globalThis.fetch;
1122
+ if (typeof fetchImpl !== "function") {
1123
+ input.log?.warn?.(`[ARENA-MODEL] callback skipped eventId=${input.meta.eventId} reason=fetch_unavailable`);
1124
+ return { ok: false, error: "fetch_unavailable" };
1125
+ }
1126
+ const response = await fetchImpl(input.meta.callbackUrl, {
1127
+ method: "POST",
1128
+ headers: {
1129
+ Authorization: `Bearer ${input.token}`,
1130
+ "Content-Type": "application/json"
1131
+ },
1132
+ body: JSON.stringify({
1133
+ eventId: input.meta.eventId,
1134
+ roomId: input.meta.roomId,
1135
+ seatNumber: input.meta.seatNumber,
1136
+ seatEpoch: input.meta.seatEpoch,
1137
+ rawText: input.rawText
1138
+ })
1139
+ });
1140
+ const body = await readJsonObject(response);
1141
+ const data = isRecord4(body.data) ? body.data : body;
1142
+ const accepted = data.accepted === true;
1143
+ const result = { ok: response.ok, status: response.status, accepted };
1144
+ const level = response.ok ? "info" : "warn";
1145
+ input.log?.[level]?.(
1146
+ `[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}`
1147
+ );
1148
+ return result;
1149
+ } catch (error) {
1150
+ const message = error instanceof Error ? error.message : String(error);
1151
+ input.log?.warn?.(
1152
+ `[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}`
1153
+ );
1154
+ return { ok: false, error: message };
1155
+ }
1156
+ }
1157
+ function createArenaModelQueryReplyCollector(input) {
1158
+ const blocks = [];
1159
+ let submitted = false;
1160
+ const submit = input.submit ?? ((rawText) => submitArenaModelQueryCallback({
1161
+ meta: input.meta,
1162
+ token: input.token,
1163
+ rawText,
1164
+ log: input.log
1165
+ }));
1166
+ async function submitOnce(rawText, reason) {
1167
+ if (submitted) return;
1168
+ submitted = true;
1169
+ input.log?.info?.(
1170
+ `[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"}`
1171
+ );
1172
+ await submit(rawText);
1173
+ }
1174
+ return {
1175
+ async deliver(text) {
1176
+ if (submitted || !text) return;
1177
+ blocks.push(text);
1178
+ const full = blocks.join("");
1179
+ input.log?.info?.(
1180
+ `[ARENA-MODEL] dispatch block eventId=${input.meta.eventId} roomId=${input.meta.roomId} seatEpoch=${input.meta.seatEpoch} blocks=${blocks.length} parsed=${parseArenaModelCurrent(full) ? "true" : "false"}`
1181
+ );
1182
+ if (parseArenaModelCurrent(full)) {
1183
+ await submitOnce(full, "parsed");
1184
+ }
1185
+ },
1186
+ async finalize() {
1187
+ if (submitted) return;
1188
+ await submitOnce(blocks.join(""), "final");
1189
+ }
1190
+ };
1191
+ }
1192
+ function trimModelToken(token) {
1193
+ let value = token.trim();
1194
+ value = value.replace(/^[`"'“”‘’<({\[【]+/, "");
1195
+ value = value.replace(/[`"'“”‘’>)}\]】。..,,;;::]+$/g, "");
1196
+ return value.trim();
1197
+ }
1198
+ async function readJsonObject(response) {
1199
+ try {
1200
+ const body = await response.json();
1201
+ return isRecord4(body) ? body : {};
1202
+ } catch {
1203
+ return {};
1204
+ }
1205
+ }
1206
+ function isRecord4(value) {
1207
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1208
+ }
1209
+
943
1210
  // src/ws-client.ts
944
1211
  import WebSocket from "ws";
945
1212
  var CoolclawWsClient = class {
@@ -1152,18 +1419,18 @@ var CoolclawWsClient = class {
1152
1419
  }
1153
1420
  };
1154
1421
  function readPingInterval(payload) {
1155
- if (!isRecord4(payload) || typeof payload.pingIntervalMs !== "number" || payload.pingIntervalMs <= 0) {
1422
+ if (!isRecord5(payload) || typeof payload.pingIntervalMs !== "number" || payload.pingIntervalMs <= 0) {
1156
1423
  return void 0;
1157
1424
  }
1158
1425
  return payload.pingIntervalMs;
1159
1426
  }
1160
1427
  function readErrorMessage(payload) {
1161
- if (isRecord4(payload) && typeof payload.message === "string") {
1428
+ if (isRecord5(payload) && typeof payload.message === "string") {
1162
1429
  return payload.message;
1163
1430
  }
1164
1431
  return "CoolClaw request failed";
1165
1432
  }
1166
- function isRecord4(value) {
1433
+ function isRecord5(value) {
1167
1434
  return typeof value === "object" && value !== null;
1168
1435
  }
1169
1436
 
@@ -1319,6 +1586,18 @@ function isNoReplyText(text) {
1319
1586
  const lastLine = lines.at(-1)?.toUpperCase();
1320
1587
  return lastLine ? noReplyTokens.has(lastLine) : false;
1321
1588
  }
1589
+ function shouldSuppressCoolclawTextDelivery(envelope) {
1590
+ return envelope.metadata?.gameEvent === true || isArenaReportShareEnvelope(envelope) || isArenaModelQueryEnvelope(envelope);
1591
+ }
1592
+ async function finalizeArenaModelQueryAfterDispatchError(params) {
1593
+ if (!params.collector) {
1594
+ return false;
1595
+ }
1596
+ const errMsg = params.error instanceof Error ? params.error.message : String(params.error);
1597
+ params.log?.warn?.(`[ARENA-MODEL] dispatch failed; submitting fallback callback eventId=${params.eventId ?? ""} err=${errMsg}`);
1598
+ await params.collector.finalize();
1599
+ return true;
1600
+ }
1322
1601
  var runtimeClients = /* @__PURE__ */ new Map();
1323
1602
  function setRuntimeClient(accountKey, client) {
1324
1603
  runtimeClients.set(accountKey, client);
@@ -1467,18 +1746,38 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1467
1746
  ackStore,
1468
1747
  dispatch: async (envelope) => {
1469
1748
  const isGameEvent = envelope.metadata?.gameEvent === true;
1749
+ const isArenaReportShare = isArenaReportShareEnvelope(envelope);
1750
+ const isArenaModelQuery = isArenaModelQueryEnvelope(envelope);
1751
+ const suppressChatTextDelivery = shouldSuppressCoolclawTextDelivery(envelope);
1470
1752
  const gameMeta = isGameEvent ? envelope.metadata : null;
1753
+ const modelQueryMeta = isArenaModelQuery ? envelope.metadata : null;
1471
1754
  let gameSubmitted = false;
1472
1755
  let gameFallbackReason = null;
1473
1756
  let gameModelActionType;
1474
1757
  let gameValidationReason;
1475
1758
  let gameModelActionRejected;
1476
1759
  const gameBuffer = [];
1760
+ const modelQueryCollector = modelQueryMeta ? createArenaModelQueryReplyCollector({
1761
+ meta: modelQueryMeta,
1762
+ token,
1763
+ log: ctx.log
1764
+ }) : null;
1477
1765
  if (isGameEvent && gameMeta) {
1478
1766
  ctx.log?.info?.(
1479
1767
  `[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}`
1480
1768
  );
1481
1769
  }
1770
+ if (modelQueryMeta) {
1771
+ let callbackHost = "";
1772
+ try {
1773
+ callbackHost = new URL(modelQueryMeta.callbackUrl).host;
1774
+ } catch {
1775
+ callbackHost = "invalid";
1776
+ }
1777
+ ctx.log?.info?.(
1778
+ `[ARENA-MODEL] inbound eventId=${modelQueryMeta.eventId} traceId=${modelQueryMeta.traceId ?? ""} roomId=${modelQueryMeta.roomId} seatEpoch=${modelQueryMeta.seatEpoch} callbackHost=${callbackHost} conversationId=${envelope.conversationId}`
1779
+ );
1780
+ }
1482
1781
  const runtime = getCoolclawRuntime();
1483
1782
  let runtimeChannel;
1484
1783
  try {
@@ -1501,6 +1800,15 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1501
1800
  }
1502
1801
  return;
1503
1802
  }
1803
+ if (modelQueryMeta) {
1804
+ await submitArenaModelQueryCallback({
1805
+ meta: modelQueryMeta,
1806
+ token,
1807
+ rawText: "",
1808
+ log: ctx.log
1809
+ });
1810
+ return;
1811
+ }
1504
1812
  throw err;
1505
1813
  }
1506
1814
  try {
@@ -1526,13 +1834,13 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1526
1834
  ctx.cfg.session?.store,
1527
1835
  { agentId: route.agentId }
1528
1836
  );
1529
- const senderLabel = envelope.sender ? `${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}` : isGameEvent ? `game:${gameMeta.gameId}` : "unknown";
1837
+ const senderLabel = envelope.sender ? `${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}` : isGameEvent ? `game:${gameMeta.gameId}` : modelQueryMeta ? `arena-model:${modelQueryMeta.roomId}` : "unknown";
1530
1838
  let deliveryTarget;
1531
1839
  if (envelope.group) {
1532
1840
  deliveryTarget = `coolclaw:group:${envelope.group.groupId}`;
1533
1841
  } else if (envelope.sender) {
1534
1842
  deliveryTarget = `coolclaw:${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}`;
1535
- } else if (isGameEvent) {
1843
+ } else if (isGameEvent || isArenaModelQuery) {
1536
1844
  deliveryTarget = `coolclaw:agent:${account.agentId}`;
1537
1845
  } else {
1538
1846
  deliveryTarget = normalizeCoolclawTarget(envelope.conversationId);
@@ -1596,6 +1904,10 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1596
1904
  if (!payload.text) return;
1597
1905
  const replyText = String(payload.text);
1598
1906
  if (!isGameEvent && isNoReplyText(replyText)) return;
1907
+ if (modelQueryCollector) {
1908
+ await modelQueryCollector.deliver(replyText);
1909
+ return;
1910
+ }
1599
1911
  if (isGameEvent && gameMeta) {
1600
1912
  if (gameSubmitted) return;
1601
1913
  gameBuffer.push(String(payload.text));
@@ -1635,6 +1947,14 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1635
1947
  }
1636
1948
  return;
1637
1949
  }
1950
+ if (isArenaReportShare) {
1951
+ ctx.log?.info?.(`[ARENA-REPORT-SHARE] ignored non-chat completion text eventId=${envelope.metadata.eventId ?? ""}`);
1952
+ return;
1953
+ }
1954
+ if (suppressChatTextDelivery) {
1955
+ ctx.log?.error?.(`[ARENA-MODEL] chat delivery blocked eventId=${envelope.metadata.eventId ?? ""}`);
1956
+ return;
1957
+ }
1638
1958
  try {
1639
1959
  let replyTarget;
1640
1960
  if (envelope.group) {
@@ -1659,9 +1979,20 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1659
1979
  gameFallbackReason = "no_valid_action_in_llm_output";
1660
1980
  }
1661
1981
  }
1982
+ if (modelQueryCollector) {
1983
+ await modelQueryCollector.finalize();
1984
+ }
1662
1985
  } catch (err) {
1663
1986
  const errMsg = err instanceof Error ? err.message : String(err);
1664
1987
  ctx.log?.error(`Inbound dispatch error: ${errMsg}`);
1988
+ if (await finalizeArenaModelQueryAfterDispatchError({
1989
+ collector: modelQueryCollector,
1990
+ eventId: modelQueryMeta?.eventId,
1991
+ error: err,
1992
+ log: ctx.log
1993
+ })) {
1994
+ return;
1995
+ }
1665
1996
  if (isGameEvent && gameMeta && !gameSubmitted) {
1666
1997
  gameFallbackReason = `dispatch_error: ${errMsg}`;
1667
1998
  }
@@ -1856,6 +2187,9 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1856
2187
  }
1857
2188
  });
1858
2189
  function buildBodyForAgent(envelope) {
2190
+ if (isArenaReportShareEnvelope(envelope)) {
2191
+ return buildArenaReportShareBodyForAgent(envelope);
2192
+ }
1859
2193
  if (!envelope.group) {
1860
2194
  if (envelope.sender && envelope.recipient) {
1861
2195
  return buildPrivateBodyForAgent(envelope);
@@ -1901,6 +2235,56 @@ function buildBodyForAgent(envelope) {
1901
2235
  }
1902
2236
  return lines.join("\n");
1903
2237
  }
2238
+ function buildArenaReportShareBodyForAgent(envelope) {
2239
+ const payload = isPlainRecord(envelope.metadata.reportPayload) ? envelope.metadata.reportPayload : {};
2240
+ const gameId = formatUnknown(payload.gameId);
2241
+ const roomId = formatUnknown(payload.roomId);
2242
+ const roomName = formatUnknown(payload.roomName);
2243
+ const result = formatUnknown(payload.result);
2244
+ const ownerInstruction = formatUnknown(payload.ownerInstruction);
2245
+ const replayApiPath = formatUnknown(payload.replayApiPath);
2246
+ const boardListApiPath = formatUnknown(payload.boardListApiPath);
2247
+ const createPostApiPath = formatUnknown(payload.createPostApiPath);
2248
+ const eventId = formatUnknown(envelope.metadata.eventId);
2249
+ const traceId = formatUnknown(envelope.metadata.traceId);
2250
+ return [
2251
+ "\u4F60\u6536\u5230\u4E00\u4E2A CoolClaw \u7ADE\u6280\u573A\u6218\u62A5\u5206\u4EAB\u59D4\u6258\u4EFB\u52A1\u3002",
2252
+ "",
2253
+ "\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
+ `eventId\uFF1A${eventId}`,
2255
+ `traceId\uFF1A${traceId}`,
2256
+ "",
2257
+ "\u4E3B\u4EBA\u6307\u4EE4\uFF1A",
2258
+ ownerInstruction,
2259
+ "",
2260
+ "\u5BF9\u5C40\u4FE1\u606F\uFF1A",
2261
+ `gameId\uFF1A${gameId}`,
2262
+ `roomId\uFF1A${roomId}`,
2263
+ `roomName\uFF1A${roomName}`,
2264
+ `result\uFF1A${result}`,
2265
+ "",
2266
+ "\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",
2268
+ "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
+ `3. \u4F7F\u7528 Gateway Base URL \u62FC\u63A5 replayApiPath=${replayApiPath} \u8BFB\u53D6\u672C\u5C40\u6218\u62A5\u3002`,
2270
+ `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
+ `5. \u4F7F\u7528\u540C\u4E00\u4E2A Gateway Base URL \u62FC\u63A5 createPostApiPath=${createPostApiPath} \u53D1\u5E03\u5E16\u5B50\u3002`,
2272
+ "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
+ "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",
2275
+ "",
2276
+ "\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
+ ].join("\n");
2278
+ }
2279
+ function isPlainRecord(value) {
2280
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2281
+ }
2282
+ function formatUnknown(value) {
2283
+ if (value === null || value === void 0) return "";
2284
+ if (typeof value === "string") return value;
2285
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
2286
+ return JSON.stringify(value);
2287
+ }
1904
2288
  function buildPrivateBodyForAgent(envelope) {
1905
2289
  const sender = formatUserRef(envelope.sender, "\u672A\u77E5\u53D1\u9001\u4EBA");
1906
2290
  const owner = formatUserRef(envelope.owner, "\u672A\u77E5\u4E3B\u4EBA");
@@ -3,7 +3,7 @@ import {
3
3
  coolclawChannelPlugin,
4
4
  defaultBindingFile,
5
5
  setCoolclawRuntime
6
- } from "./chunk-3RAQ3GUM.js";
6
+ } from "./chunk-R4H3YAWU.js";
7
7
 
8
8
  // index.ts
9
9
  import { defineChannelPluginEntry, buildChannelConfigSchema } from "openclaw/plugin-sdk/core";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  index_default
3
- } from "./chunk-ITAZ7RTC.js";
4
- import "./chunk-3RAQ3GUM.js";
3
+ } from "./chunk-RCPRLBZL.js";
4
+ import "./chunk-R4H3YAWU.js";
5
5
 
6
6
  // cli-metadata.ts
7
7
  var cli_metadata_default = index_default;
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  index_default
3
- } from "./chunk-ITAZ7RTC.js";
4
- import "./chunk-3RAQ3GUM.js";
3
+ } from "./chunk-RCPRLBZL.js";
4
+ import "./chunk-R4H3YAWU.js";
5
5
  export {
6
6
  index_default as default
7
7
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  coolclawChannelPlugin
3
- } from "./chunk-3RAQ3GUM.js";
3
+ } from "./chunk-R4H3YAWU.js";
4
4
 
5
5
  // setup-entry.ts
6
6
  import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coolclaw/coolclaw",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "description": "OpenClaw native channel plugin for Riddle/CoolClaw chat.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -72,7 +72,7 @@
72
72
  "runtimeSetupEntry": "./dist/setup-entry.js",
73
73
  "install": {
74
74
  "npmSpec": "@coolclaw/coolclaw",
75
- "expectedIntegrity": "sha512-lRGll+tauQxhgsqtOWlEkM+MKZwDQpib/s1A4mZSQB+PDRsvMzuXKQiX0ipEUQo1/HPjlLgDRkM/5A6GX45kag==",
75
+ "expectedIntegrity": "sha512-W0ARihskllfGJhQE00/znWvLszcLNgSa1FZPblBs176iVJ/o6jR+7N3Khl8njWMtrvd1v3nkrB4eIuvEiIg4vg==",
76
76
  "defaultChoice": "npm",
77
77
  "minHostVersion": ">=2026.3.22"
78
78
  },