@coolclaw/coolclaw 1.0.3 → 1.0.6

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
@@ -20,7 +20,7 @@
20
20
  - `PRIVATE_MESSAGE`
21
21
  - `GROUP_MESSAGE`
22
22
  - `SYSTEM_NOTIFICATION`
23
- - `GAME_EVENT` — backend-owned `agentTask` events. The plugin uses `agentTask.renderedPrompt` verbatim, validates the parsed `<ACTION>{...}</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.
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
25
  - `AGENT_NOTIFY` — Riddle content module 主动通知帧(`POST_COMMENTED` / `COMMENT_REPLIED` / `POST_RECOMMEND`),`shouldReply: false`,仅用于驱动 Agent 感知新帖 / 被评论 / 被回复事件。
26
26
 
@@ -3,7 +3,7 @@ import {
3
3
  coolclawChannelPlugin,
4
4
  defaultBindingFile,
5
5
  setCoolclawRuntime
6
- } from "./chunk-DVHZ22OJ.js";
6
+ } from "./chunk-W6LHGLKL.js";
7
7
 
8
8
  // index.ts
9
9
  import { defineChannelPluginEntry, buildChannelConfigSchema } from "openclaw/plugin-sdk/core";
@@ -150,9 +150,16 @@ function validateAgentAction(action, task) {
150
150
  if (allowed.length === 0) {
151
151
  return { ok: false, reason: "missing_contract" };
152
152
  }
153
- if (!allowed.includes(action.actionType)) {
153
+ const option = task.actionContract.options.find((candidate) => candidate.actionType === action.actionType);
154
+ if (!option) {
154
155
  return { ok: false, reason: "disallowed_action_type" };
155
156
  }
157
+ if (!matchesActionDataSchema(action.actionData, option.actionDataSchema)) {
158
+ return { ok: false, reason: "invalid_action_shape" };
159
+ }
160
+ if (containsPublicPrivateInfoLeak(action.actionType, action.actionData)) {
161
+ return { ok: false, reason: "public_private_info_leak" };
162
+ }
156
163
  return { ok: true, action };
157
164
  }
158
165
  function backendFallbackAction(task) {
@@ -169,7 +176,7 @@ function backendFallbackAction(task) {
169
176
  function sha256Hex(text) {
170
177
  return createHash("sha256").update(text).digest("hex");
171
178
  }
172
- function rawResponsePreview(text, maxChars = 2e3) {
179
+ function rawResponsePreview(text, maxChars = 5e3) {
173
180
  if (text.length === 0) return void 0;
174
181
  return text.length <= maxChars ? text : `${text.slice(0, maxChars)}...`;
175
182
  }
@@ -194,6 +201,68 @@ function normalizeActionOption(value) {
194
201
  function allowedActionTypes(task) {
195
202
  return task.actionContract.options.map((option) => option.actionType);
196
203
  }
204
+ function matchesActionDataSchema(actionData, schema) {
205
+ if (!schema || Object.keys(schema).length === 0) return true;
206
+ if (typeof schema.type === "string" && schema.type !== "object") return false;
207
+ const properties = isRecord(schema.properties) ? schema.properties : {};
208
+ const required = Array.isArray(schema.required) ? schema.required.filter((field) => typeof field === "string" && field.length > 0) : [];
209
+ for (const field of required) {
210
+ if (!Object.prototype.hasOwnProperty.call(actionData, field)) return false;
211
+ if (!matchesSchemaValue(actionData[field], properties[field], true)) return false;
212
+ }
213
+ for (const [field, value] of Object.entries(actionData)) {
214
+ const propertySchema = properties[field];
215
+ if (propertySchema !== void 0 && !matchesSchemaValue(value, propertySchema, false)) {
216
+ return false;
217
+ }
218
+ }
219
+ return true;
220
+ }
221
+ function matchesSchemaValue(value, schema, required) {
222
+ if (value == null) return !required;
223
+ if (!isRecord(schema)) return true;
224
+ if (Array.isArray(schema.enum) && !schema.enum.includes(value)) {
225
+ return false;
226
+ }
227
+ switch (schema.type) {
228
+ case "integer":
229
+ return typeof value === "number" && Number.isInteger(value);
230
+ case "number":
231
+ return typeof value === "number" && Number.isFinite(value);
232
+ case "string":
233
+ return typeof value === "string" && (typeof schema.minLength !== "number" || value.length >= schema.minLength) && (typeof schema.maxLength !== "number" || value.length <= schema.maxLength);
234
+ case "boolean":
235
+ return typeof value === "boolean";
236
+ case "object":
237
+ return isRecord(value);
238
+ case "array":
239
+ return Array.isArray(value);
240
+ default:
241
+ return true;
242
+ }
243
+ }
244
+ var PUBLIC_ACTION_TYPES = /* @__PURE__ */ new Set(["DAY_SPEAK", "DAY_VOTE", "LAST_WORD", "HUNTER_SHOOT", "HUNTER_PASS"]);
245
+ var PUBLIC_TEXT_FIELDS = ["content", "reason"];
246
+ var PRIVATE_LEAK_PATTERNS = [
247
+ /我是\s*狼/u,
248
+ /我是\s*狼人/u,
249
+ /作为\s*狼/u,
250
+ /我们\s*狼队/u,
251
+ /我方\s*狼队/u,
252
+ /我(?:的)?狼队友/u,
253
+ /我(?:的)?队友/u,
254
+ /(?:我|我们|我方).{0,8}夜聊/u,
255
+ /夜聊.{0,16}(?:我说|我建议|我们|我方|决定|刀|击杀)/u,
256
+ /(?:我们|我方)\s*狼队.*(?:刀|击杀|目标)/u,
257
+ /今晚\s*(?:先)?刀/u
258
+ ];
259
+ function containsPublicPrivateInfoLeak(actionType, actionData) {
260
+ if (!PUBLIC_ACTION_TYPES.has(actionType)) return false;
261
+ return PUBLIC_TEXT_FIELDS.some((field) => {
262
+ const value = actionData[field];
263
+ return typeof value === "string" && PRIVATE_LEAK_PATTERNS.some((pattern) => pattern.test(value));
264
+ });
265
+ }
197
266
  function readString(value) {
198
267
  return typeof value === "string" && value.length > 0 ? value : void 0;
199
268
  }
@@ -327,8 +396,14 @@ async function handleInboundFrame(input) {
327
396
  await ackFrameSeq(input);
328
397
  return;
329
398
  }
330
- await ackProcessedSeq(input, envelope);
399
+ const ackAfterDispatch = envelope.metadata.gameEvent === true;
400
+ if (!ackAfterDispatch) {
401
+ await ackProcessedSeq(input, envelope);
402
+ }
331
403
  await input.dispatch(envelope);
404
+ if (ackAfterDispatch) {
405
+ await ackProcessedSeq(input, envelope);
406
+ }
332
407
  }
333
408
  function mapNotificationFrame(frame) {
334
409
  const payload = isRecord3(frame.payload) ? frame.payload : {};
@@ -442,8 +517,10 @@ async function ackFrameSeq(input) {
442
517
  }
443
518
  }
444
519
  async function ackSeq(input, seq) {
445
- const lastAckedSeq = await input.ackStore.record(input.accountKey, seq);
520
+ const current = await input.ackStore.getLastAckedSeq(input.accountKey);
521
+ const lastAckedSeq = seq <= current ? current : seq;
446
522
  await input.sendAck(createFrame("ACK", { lastAckedSeq }));
523
+ await input.ackStore.record(input.accountKey, seq);
447
524
  }
448
525
  function assertPrivatePayload(value) {
449
526
  if (!isRecord3(value) || !isUserRef(value.sender) || !isUserRef(value.recipient)) {
@@ -651,7 +728,10 @@ async function sendGameAction(input) {
651
728
  renderedPromptHash: input.renderedPromptHash,
652
729
  parseSource: input.parseSource,
653
730
  rawResponseHash: input.rawResponseHash,
654
- rawResponsePreview: input.rawResponsePreview
731
+ rawResponsePreview: input.rawResponsePreview,
732
+ modelActionRejected: input.modelActionRejected,
733
+ modelActionType: input.modelActionType,
734
+ validationReason: input.validationReason
655
735
  });
656
736
  const response = await input.client.request(frame);
657
737
  if (response.ok === false) {
@@ -661,7 +741,7 @@ async function sendGameAction(input) {
661
741
 
662
742
  // src/game-action-parser.ts
663
743
  function extractActionBlock(text) {
664
- const re = /<ACTION>\s*([\s\S]+?)\s*<\/ACTION>/g;
744
+ const re = /<ACTION>\s*([\s\S]+?)\s*<\/ACTION>/gi;
665
745
  let match;
666
746
  let last = null;
667
747
  while ((match = re.exec(text)) !== null) {
@@ -669,14 +749,64 @@ function extractActionBlock(text) {
669
749
  }
670
750
  return last;
671
751
  }
752
+ function extractFencedJsonBlocks(text) {
753
+ const re = /```(?:json)?\s*([\s\S]*?)\s*```/gi;
754
+ const blocks = [];
755
+ let match;
756
+ while ((match = re.exec(text)) !== null) {
757
+ blocks.push(match[1]);
758
+ }
759
+ return blocks;
760
+ }
761
+ function extractTrailingJsonCandidates(text) {
762
+ const trimmed = text.trim();
763
+ if (!trimmed.endsWith("}")) return [];
764
+ const candidates = [];
765
+ let idx = trimmed.lastIndexOf("{");
766
+ while (idx >= 0) {
767
+ const candidate = trimmed.slice(idx).trim();
768
+ if (candidate.includes('"actionType"') && candidate.includes('"actionData"')) {
769
+ candidates.push(candidate);
770
+ }
771
+ idx = trimmed.lastIndexOf("{", idx - 1);
772
+ }
773
+ return candidates;
774
+ }
775
+ function extractActionCandidates(text) {
776
+ const actionBlock = extractActionBlock(text);
777
+ if (actionBlock !== null) {
778
+ return [actionBlock];
779
+ }
780
+ const fenced = extractFencedJsonBlocks(text);
781
+ if (fenced.length > 0) {
782
+ return fenced.reverse();
783
+ }
784
+ return extractTrailingJsonCandidates(text);
785
+ }
786
+ function normalizeActionBlock(body) {
787
+ const trimmed = body.trim();
788
+ const fenced = /^```(?:json)?\s*([\s\S]*?)\s*```$/i.exec(trimmed);
789
+ return fenced ? fenced[1].trim() : trimmed;
790
+ }
672
791
  function parseAgentAction(text) {
673
792
  if (typeof text !== "string" || text.length === 0) {
674
793
  return { error: "no_action_block" };
675
794
  }
676
- const body = extractActionBlock(text);
677
- if (body === null) {
795
+ const candidates = extractActionCandidates(text);
796
+ if (candidates.length === 0) {
678
797
  return { error: "no_action_block" };
679
798
  }
799
+ let lastError = null;
800
+ for (const body of candidates) {
801
+ const parsed = parseActionJson(normalizeActionBlock(body));
802
+ if (!("error" in parsed)) {
803
+ return parsed;
804
+ }
805
+ lastError = parsed;
806
+ }
807
+ return lastError ?? { error: "no_action_block" };
808
+ }
809
+ function parseActionJson(body) {
680
810
  let obj;
681
811
  try {
682
812
  obj = JSON.parse(body);
@@ -699,6 +829,46 @@ function parseAgentAction(text) {
699
829
  };
700
830
  }
701
831
 
832
+ // src/game-action-audit.ts
833
+ function normalizeAuditText(value, maxChars = 256) {
834
+ if (!value) return void 0;
835
+ return value.length <= maxChars ? value : value.slice(0, maxChars);
836
+ }
837
+ function inferRejectedModelAction(rawResponse, task) {
838
+ if (rawResponse.trim().length === 0) {
839
+ return {
840
+ modelActionRejected: false,
841
+ validationReason: "no_model_output"
842
+ };
843
+ }
844
+ const parsed = parseAgentAction(rawResponse);
845
+ if ("error" in parsed) {
846
+ return {
847
+ modelActionRejected: true,
848
+ validationReason: parseErrorReason(parsed)
849
+ };
850
+ }
851
+ const validation = validateAgentAction(parsed, task);
852
+ if (!validation.ok) {
853
+ return {
854
+ modelActionRejected: true,
855
+ modelActionType: parsed.actionType,
856
+ validationReason: validation.reason
857
+ };
858
+ }
859
+ return {
860
+ modelActionRejected: true,
861
+ modelActionType: parsed.actionType,
862
+ validationReason: "valid_action_not_submitted"
863
+ };
864
+ }
865
+ function parseErrorReason(error) {
866
+ if (error.error === "invalid_json") {
867
+ return normalizeAuditText(`invalid_json:${error.detail}`) ?? "invalid_json";
868
+ }
869
+ return error.error;
870
+ }
871
+
702
872
  // src/ws-client.ts
703
873
  import WebSocket from "ws";
704
874
  var CoolclawWsClient = class {
@@ -976,10 +1146,10 @@ function logAckFailure(params) {
976
1146
  const target = params.target ? ` target=${params.target}` : "";
977
1147
  params.log(`${params.channel} ack cleanup failed${target}: ${String(params.error)}`);
978
1148
  }
979
- async function submitGameActionWithLog(action, meta, wsClient, log, source, rawResponse) {
1149
+ async function submitGameActionWithLog(action, meta, wsClient, log, source, rawResponse, auditMeta) {
980
1150
  if (!wsClient.isConnected()) {
981
1151
  log?.error?.(`[GAME-ACTION] submit skipped: ws not connected eventId=${meta.eventId}`);
982
- return;
1152
+ return false;
983
1153
  }
984
1154
  const responseHash = rawResponse && rawResponse.length > 0 ? sha256Hex(rawResponse) : void 0;
985
1155
  log?.info?.(
@@ -1001,17 +1171,49 @@ async function submitGameActionWithLog(action, meta, wsClient, log, source, rawR
1001
1171
  renderedPromptHash: meta.renderedPromptHash,
1002
1172
  parseSource: source,
1003
1173
  rawResponseHash: responseHash,
1004
- rawResponsePreview: rawResponse ? rawResponsePreview(rawResponse) : void 0
1174
+ rawResponsePreview: rawResponse ? rawResponsePreview(rawResponse) : void 0,
1175
+ modelActionRejected: auditMeta?.modelActionRejected,
1176
+ modelActionType: auditMeta?.modelActionType,
1177
+ validationReason: normalizeAuditText(auditMeta?.validationReason)
1005
1178
  });
1006
1179
  log?.info?.(
1007
1180
  `[GAME-ACTION] submit ok source=${source} gameId=${meta.gameId} eventId=${meta.eventId} promptPolicyVersion=${meta.promptPolicyVersion ?? ""} renderedPromptHash=${meta.renderedPromptHash ?? ""} rawResponseHash=${responseHash ?? ""} elapsedMs=${Date.now() - start}`
1008
1181
  );
1182
+ return true;
1009
1183
  } catch (err) {
1010
1184
  const errMsg = err instanceof Error ? err.message : String(err);
1011
1185
  log?.error?.(
1012
1186
  `[GAME-ACTION] submit failed source=${source} eventId=${meta.eventId} elapsedMs=${Date.now() - start} err=${errMsg}`
1013
1187
  );
1188
+ return false;
1189
+ }
1190
+ }
1191
+ async function submitBackendFallbackWithLog(params) {
1192
+ const fb = backendFallbackAction(params.meta.agentTask);
1193
+ if (!fb) {
1194
+ params.log?.warn?.(
1195
+ `[GAME-ACTION] backend fallback unavailable eventType=${params.meta.eventType} eventId=${params.meta.eventId} reason=${params.reason} promptPolicyVersion=${params.meta.promptPolicyVersion ?? ""} renderedPromptHash=${params.meta.renderedPromptHash ?? ""}`
1196
+ );
1197
+ return false;
1014
1198
  }
1199
+ const inferred = inferRejectedModelAction(params.rawResponse ?? "", params.meta.agentTask);
1200
+ const auditMeta = {
1201
+ modelActionRejected: params.auditMeta?.modelActionRejected ?? inferred.modelActionRejected,
1202
+ modelActionType: params.auditMeta?.modelActionType ?? inferred.modelActionType,
1203
+ validationReason: params.auditMeta?.validationReason ?? params.reason ?? inferred.validationReason
1204
+ };
1205
+ params.log?.warn?.(
1206
+ `[GAME-ACTION] backend fallback eventType=${params.meta.eventType} eventId=${params.meta.eventId} reason=${auditMeta.validationReason ?? "unknown"} promptPolicyVersion=${params.meta.promptPolicyVersion ?? ""} renderedPromptHash=${params.meta.renderedPromptHash ?? ""} rawResponseHash=${params.rawResponse ? sha256Hex(params.rawResponse) : ""}`
1207
+ );
1208
+ return submitGameActionWithLog(
1209
+ fb,
1210
+ params.meta,
1211
+ params.wsClient,
1212
+ params.log,
1213
+ "backend_fallback",
1214
+ params.rawResponse || void 0,
1215
+ auditMeta
1216
+ );
1015
1217
  }
1016
1218
  var runtimeClients = /* @__PURE__ */ new Map();
1017
1219
  function setRuntimeClient(accountKey, client) {
@@ -1160,22 +1362,36 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1160
1362
  accountKey,
1161
1363
  ackStore,
1162
1364
  dispatch: async (envelope) => {
1163
- const runtime = getCoolclawRuntime();
1164
- if (!runtime?.channel) {
1165
- logInboundDrop({ log: ctx.log?.warn?.bind(ctx.log) ?? (() => {
1166
- }), channel: "coolclaw", reason: "runtime not available; skipping dispatch" });
1167
- return;
1168
- }
1169
1365
  const isGameEvent = envelope.metadata?.gameEvent === true;
1170
1366
  const gameMeta = isGameEvent ? envelope.metadata : null;
1171
1367
  let gameSubmitted = false;
1172
1368
  let gameFallbackReason = null;
1369
+ let gameModelActionType;
1370
+ let gameValidationReason;
1371
+ let gameModelActionRejected;
1173
1372
  const gameBuffer = [];
1174
1373
  if (isGameEvent && gameMeta) {
1175
1374
  ctx.log?.info?.(
1176
1375
  `[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}`
1177
1376
  );
1178
1377
  }
1378
+ const runtime = getCoolclawRuntime();
1379
+ if (!runtime?.channel) {
1380
+ logInboundDrop({ log: ctx.log?.warn?.bind(ctx.log) ?? (() => {
1381
+ }), channel: "coolclaw", reason: "runtime not available; skipping dispatch" });
1382
+ if (isGameEvent && gameMeta) {
1383
+ const submitted = await submitBackendFallbackWithLog({
1384
+ meta: gameMeta,
1385
+ wsClient,
1386
+ log: ctx.log,
1387
+ reason: "runtime_not_available"
1388
+ });
1389
+ if (!submitted) {
1390
+ throw new Error("game fallback submit failed: runtime_not_available");
1391
+ }
1392
+ }
1393
+ return;
1394
+ }
1179
1395
  try {
1180
1396
  const isGroup = envelope.conversationId.startsWith("group:");
1181
1397
  const peer = isGroup ? { kind: "group", id: envelope.group?.groupId ?? envelope.conversationId } : { kind: "direct", id: envelope.conversationId };
@@ -1275,20 +1491,34 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1275
1491
  if ("error" in parsed) return;
1276
1492
  const validation = validateAgentAction(parsed, gameMeta.agentTask);
1277
1493
  if (!validation.ok) {
1494
+ gameModelActionRejected = true;
1495
+ gameModelActionType = parsed.actionType;
1496
+ gameValidationReason = validation.reason;
1497
+ gameFallbackReason = validation.reason;
1278
1498
  ctx.log?.warn?.(
1279
1499
  `[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)}`
1280
1500
  );
1281
1501
  return;
1282
1502
  }
1283
- gameSubmitted = true;
1284
- await submitGameActionWithLog(
1503
+ const submitted = await submitGameActionWithLog(
1285
1504
  validation.action,
1286
1505
  gameMeta,
1287
1506
  wsClient,
1288
1507
  ctx.log,
1289
1508
  "llm",
1290
- full
1509
+ full,
1510
+ {
1511
+ modelActionRejected: false,
1512
+ modelActionType: validation.action.actionType
1513
+ }
1291
1514
  );
1515
+ if (submitted) {
1516
+ gameSubmitted = true;
1517
+ } else {
1518
+ gameFallbackReason = `llm_action_submit_failed:${validation.action.actionType}`;
1519
+ gameModelActionRejected = false;
1520
+ gameModelActionType = validation.action.actionType;
1521
+ }
1292
1522
  return;
1293
1523
  }
1294
1524
  try {
@@ -1311,7 +1541,9 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1311
1541
  }
1312
1542
  });
1313
1543
  if (isGameEvent && gameMeta && !gameSubmitted) {
1314
- gameFallbackReason = "no_valid_action_in_llm_output";
1544
+ if (!gameFallbackReason) {
1545
+ gameFallbackReason = "no_valid_action_in_llm_output";
1546
+ }
1315
1547
  }
1316
1548
  } catch (err) {
1317
1549
  const errMsg = err instanceof Error ? err.message : String(err);
@@ -1328,29 +1560,34 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1328
1560
  }
1329
1561
  if (isGameEvent && gameMeta && !gameSubmitted) {
1330
1562
  try {
1331
- const fb = backendFallbackAction(gameMeta.agentTask);
1332
- if (!fb) {
1333
- ctx.log?.warn?.(
1334
- `[GAME-ACTION] backend fallback unavailable eventType=${gameMeta.eventType} eventId=${gameMeta.eventId} reason=${gameFallbackReason ?? "unknown"} promptPolicyVersion=${gameMeta.promptPolicyVersion ?? ""} renderedPromptHash=${gameMeta.renderedPromptHash ?? ""}`
1335
- );
1336
- return;
1337
- }
1338
1563
  const rawResponse = gameBuffer.join("");
1339
- ctx.log?.warn?.(
1340
- `[GAME-ACTION] backend fallback eventType=${gameMeta.eventType} eventId=${gameMeta.eventId} reason=${gameFallbackReason ?? "unknown"} promptPolicyVersion=${gameMeta.promptPolicyVersion ?? ""} renderedPromptHash=${gameMeta.renderedPromptHash ?? ""} rawResponseHash=${rawResponse ? sha256Hex(rawResponse) : ""}`
1341
- );
1342
- await submitGameActionWithLog(
1343
- fb,
1344
- gameMeta,
1564
+ const inferred = inferRejectedModelAction(rawResponse, gameMeta.agentTask);
1565
+ gameModelActionRejected = gameModelActionRejected ?? inferred.modelActionRejected;
1566
+ gameModelActionType = gameModelActionType ?? inferred.modelActionType;
1567
+ gameValidationReason = gameValidationReason ?? inferred.validationReason;
1568
+ if (!gameFallbackReason || gameFallbackReason === "no_valid_action_in_llm_output") {
1569
+ gameFallbackReason = gameValidationReason ?? inferred.validationReason ?? gameFallbackReason;
1570
+ }
1571
+ const submitted = await submitBackendFallbackWithLog({
1572
+ meta: gameMeta,
1345
1573
  wsClient,
1346
- ctx.log,
1347
- "backend_fallback",
1348
- rawResponse || void 0
1349
- );
1574
+ log: ctx.log,
1575
+ reason: gameFallbackReason ?? gameValidationReason ?? inferred.validationReason ?? "unknown",
1576
+ rawResponse: rawResponse || void 0,
1577
+ auditMeta: {
1578
+ modelActionRejected: gameModelActionRejected,
1579
+ modelActionType: gameModelActionType,
1580
+ validationReason: gameFallbackReason ?? gameValidationReason ?? inferred.validationReason
1581
+ }
1582
+ });
1583
+ if (!submitted) {
1584
+ throw new Error(`game fallback submit failed: ${gameFallbackReason ?? "unknown"}`);
1585
+ }
1350
1586
  } catch (fbErr) {
1351
1587
  ctx.log?.error?.(
1352
1588
  `[GAME-ACTION] fallback submit threw eventId=${gameMeta.eventId} err=${fbErr instanceof Error ? fbErr.message : String(fbErr)}`
1353
1589
  );
1590
+ throw fbErr;
1354
1591
  }
1355
1592
  }
1356
1593
  }
@@ -1362,6 +1599,7 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1362
1599
  } catch (err) {
1363
1600
  logAckFailure({ log: ctx.log?.warn?.bind(ctx.log) ?? (() => {
1364
1601
  }), channel: "coolclaw", error: err });
1602
+ throw err;
1365
1603
  }
1366
1604
  }
1367
1605
  });
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  index_default
3
- } from "./chunk-GKJ5WKGA.js";
4
- import "./chunk-DVHZ22OJ.js";
3
+ } from "./chunk-4RKDX3HY.js";
4
+ import "./chunk-W6LHGLKL.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-GKJ5WKGA.js";
4
- import "./chunk-DVHZ22OJ.js";
3
+ } from "./chunk-4RKDX3HY.js";
4
+ import "./chunk-W6LHGLKL.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-DVHZ22OJ.js";
3
+ } from "./chunk-W6LHGLKL.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.3",
3
+ "version": "1.0.6",
4
4
  "description": "OpenClaw native channel plugin for Riddle/CoolClaw chat.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -38,7 +38,8 @@
38
38
  "scripts": {
39
39
  "build": "tsup index.ts setup-entry.ts cli-metadata.ts --format esm --dts --out-dir dist --clean --splitting",
40
40
  "test": "vitest run",
41
- "lint": "tsc --noEmit"
41
+ "lint": "tsc --noEmit",
42
+ "prepack": "npm run build"
42
43
  },
43
44
  "dependencies": {
44
45
  "ws": "^8.20.1",
@@ -71,7 +72,7 @@
71
72
  "runtimeSetupEntry": "./dist/setup-entry.js",
72
73
  "install": {
73
74
  "npmSpec": "@coolclaw/coolclaw",
74
- "expectedIntegrity": "sha512-6gIxAI4i3xJ4LpzViGi1KCJvc20JA/3Ob4OVdClgHCPydrkDqzOPbhSea4tpSlkUB5mWh/iEEzKhMcKqM24vVg==",
75
+ "expectedIntegrity": "sha512-7VcGcTTi2KmOCAJ76tj8uyEIUURe6LftxSpjmbrLXzcKC7YKjNrpNzwFMBnN4g4yfxtSsyf0bhT61gsI8RyUpw==",
75
76
  "defaultChoice": "npm",
76
77
  "minHostVersion": ">=2026.3.22"
77
78
  },