@coolclaw/coolclaw 1.0.5 → 1.0.7

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
 
@@ -154,10 +154,24 @@ function validateAgentAction(action, task) {
154
154
  if (!option) {
155
155
  return { ok: false, reason: "disallowed_action_type" };
156
156
  }
157
- if (!matchesActionDataSchema(action.actionData, option.actionDataSchema)) {
157
+ if (containsPublicPrivateInfoLeak(action.actionType, action.actionData)) {
158
+ return { ok: false, reason: "public_private_info_leak" };
159
+ }
160
+ const repaired = repairActionDataForSchema(action.actionData, option.actionDataSchema);
161
+ if (!matchesActionDataSchema(repaired.actionData, option.actionDataSchema)) {
158
162
  return { ok: false, reason: "invalid_action_shape" };
159
163
  }
160
- return { ok: true, action };
164
+ if (containsPublicPrivateInfoLeak(action.actionType, repaired.actionData)) {
165
+ return { ok: false, reason: "public_private_info_leak" };
166
+ }
167
+ return {
168
+ ok: true,
169
+ action: {
170
+ actionType: action.actionType,
171
+ actionData: repaired.actionData
172
+ },
173
+ repairReason: repaired.repairReason
174
+ };
161
175
  }
162
176
  function backendFallbackAction(task) {
163
177
  const fallback = task.fallbackAction;
@@ -215,6 +229,39 @@ function matchesActionDataSchema(actionData, schema) {
215
229
  }
216
230
  return true;
217
231
  }
232
+ function repairActionDataForSchema(actionData, schema) {
233
+ if (!schema || Object.keys(schema).length === 0) {
234
+ return { actionData };
235
+ }
236
+ const properties = isRecord(schema.properties) ? schema.properties : {};
237
+ let repaired = null;
238
+ const truncatedFields = [];
239
+ for (const [field, propertySchema] of Object.entries(properties)) {
240
+ const value = actionData[field];
241
+ if (typeof value !== "string" || !isRecord(propertySchema)) {
242
+ continue;
243
+ }
244
+ if (propertySchema.type !== "string" || typeof propertySchema.maxLength !== "number") {
245
+ continue;
246
+ }
247
+ const maxLength = Math.max(0, Math.floor(propertySchema.maxLength));
248
+ if (value.length <= maxLength) {
249
+ continue;
250
+ }
251
+ if (!repaired) {
252
+ repaired = { ...actionData };
253
+ }
254
+ repaired[field] = value.slice(0, maxLength);
255
+ truncatedFields.push(field);
256
+ }
257
+ if (truncatedFields.length === 0) {
258
+ return { actionData };
259
+ }
260
+ return {
261
+ actionData: repaired ?? actionData,
262
+ repairReason: `truncate:${truncatedFields.join(",")}`
263
+ };
264
+ }
218
265
  function matchesSchemaValue(value, schema, required) {
219
266
  if (value == null) return !required;
220
267
  if (!isRecord(schema)) return true;
@@ -238,6 +285,28 @@ function matchesSchemaValue(value, schema, required) {
238
285
  return true;
239
286
  }
240
287
  }
288
+ var PUBLIC_ACTION_TYPES = /* @__PURE__ */ new Set(["DAY_SPEAK", "DAY_VOTE", "LAST_WORD", "HUNTER_SHOOT", "HUNTER_PASS"]);
289
+ var PUBLIC_TEXT_FIELDS = ["content", "reason"];
290
+ var PRIVATE_LEAK_PATTERNS = [
291
+ /我是\s*狼/u,
292
+ /我是\s*狼人/u,
293
+ /作为\s*狼/u,
294
+ /我们\s*狼队/u,
295
+ /我方\s*狼队/u,
296
+ /我(?:的)?狼队友/u,
297
+ /我(?:的)?队友/u,
298
+ /(?:我|我们|我方).{0,8}夜聊/u,
299
+ /夜聊.{0,16}(?:我说|我建议|我们|我方|决定|刀|击杀)/u,
300
+ /(?:我们|我方)\s*狼队.*(?:刀|击杀|目标)/u,
301
+ /今晚\s*(?:先)?刀/u
302
+ ];
303
+ function containsPublicPrivateInfoLeak(actionType, actionData) {
304
+ if (!PUBLIC_ACTION_TYPES.has(actionType)) return false;
305
+ return PUBLIC_TEXT_FIELDS.some((field) => {
306
+ const value = actionData[field];
307
+ return typeof value === "string" && PRIVATE_LEAK_PATTERNS.some((pattern) => pattern.test(value));
308
+ });
309
+ }
241
310
  function readString(value) {
242
311
  return typeof value === "string" && value.length > 0 ? value : void 0;
243
312
  }
@@ -698,6 +767,7 @@ async function sendGameAction(input) {
698
767
  timestamp: String(Date.now()),
699
768
  turnSeq: input.turnSeq,
700
769
  eventId: input.eventId,
770
+ deadlineEpochMs: input.deadlineEpochMs,
701
771
  traceId: input.traceId,
702
772
  promptPolicyVersion: input.promptPolicyVersion,
703
773
  renderedPromptHash: input.renderedPromptHash,
@@ -712,6 +782,7 @@ async function sendGameAction(input) {
712
782
  if (response.ok === false) {
713
783
  throw new Error(response.error?.message ?? "CoolClaw game action failed");
714
784
  }
785
+ return response;
715
786
  }
716
787
 
717
788
  // src/game-action-parser.ts
@@ -724,6 +795,40 @@ function extractActionBlock(text) {
724
795
  }
725
796
  return last;
726
797
  }
798
+ function extractFencedJsonBlocks(text) {
799
+ const re = /```(?:json)?\s*([\s\S]*?)\s*```/gi;
800
+ const blocks = [];
801
+ let match;
802
+ while ((match = re.exec(text)) !== null) {
803
+ blocks.push(match[1]);
804
+ }
805
+ return blocks;
806
+ }
807
+ function extractTrailingJsonCandidates(text) {
808
+ const trimmed = text.trim();
809
+ if (!trimmed.endsWith("}")) return [];
810
+ const candidates = [];
811
+ let idx = trimmed.lastIndexOf("{");
812
+ while (idx >= 0) {
813
+ const candidate = trimmed.slice(idx).trim();
814
+ if (candidate.includes('"actionType"') && candidate.includes('"actionData"')) {
815
+ candidates.push(candidate);
816
+ }
817
+ idx = trimmed.lastIndexOf("{", idx - 1);
818
+ }
819
+ return candidates;
820
+ }
821
+ function extractActionCandidates(text) {
822
+ const actionBlock = extractActionBlock(text);
823
+ if (actionBlock !== null) {
824
+ return [actionBlock];
825
+ }
826
+ const fenced = extractFencedJsonBlocks(text);
827
+ if (fenced.length > 0) {
828
+ return fenced.reverse();
829
+ }
830
+ return extractTrailingJsonCandidates(text);
831
+ }
727
832
  function normalizeActionBlock(body) {
728
833
  const trimmed = body.trim();
729
834
  const fenced = /^```(?:json)?\s*([\s\S]*?)\s*```$/i.exec(trimmed);
@@ -733,15 +838,35 @@ function parseAgentAction(text) {
733
838
  if (typeof text !== "string" || text.length === 0) {
734
839
  return { error: "no_action_block" };
735
840
  }
736
- const body = extractActionBlock(text);
737
- if (body === null) {
841
+ const candidates = extractActionCandidates(text);
842
+ if (candidates.length === 0) {
738
843
  return { error: "no_action_block" };
739
844
  }
845
+ let lastError = null;
846
+ for (const body of candidates) {
847
+ const parsed = parseActionJson(normalizeActionBlock(body));
848
+ if (!("error" in parsed)) {
849
+ return parsed;
850
+ }
851
+ lastError = parsed;
852
+ }
853
+ return lastError ?? { error: "no_action_block" };
854
+ }
855
+ function parseActionJson(body) {
740
856
  let obj;
741
857
  try {
742
- obj = JSON.parse(normalizeActionBlock(body));
858
+ obj = JSON.parse(body);
743
859
  } catch (e) {
744
- return { error: "invalid_json", detail: e instanceof Error ? e.message : String(e) };
860
+ const repaired = removeTrailingCommas(body);
861
+ if (repaired !== body) {
862
+ try {
863
+ obj = JSON.parse(repaired);
864
+ } catch {
865
+ return { error: "invalid_json", detail: e instanceof Error ? e.message : String(e) };
866
+ }
867
+ } else {
868
+ return { error: "invalid_json", detail: e instanceof Error ? e.message : String(e) };
869
+ }
745
870
  }
746
871
  if (typeof obj !== "object" || obj === null) {
747
872
  return { error: "invalid_json", detail: "not an object" };
@@ -758,6 +883,41 @@ function parseAgentAction(text) {
758
883
  actionData: rec.actionData
759
884
  };
760
885
  }
886
+ function removeTrailingCommas(body) {
887
+ let out = "";
888
+ let inString = false;
889
+ let escaped = false;
890
+ for (let i = 0; i < body.length; i += 1) {
891
+ const ch = body[i];
892
+ if (inString) {
893
+ out += ch;
894
+ if (escaped) {
895
+ escaped = false;
896
+ } else if (ch === "\\") {
897
+ escaped = true;
898
+ } else if (ch === '"') {
899
+ inString = false;
900
+ }
901
+ continue;
902
+ }
903
+ if (ch === '"') {
904
+ inString = true;
905
+ out += ch;
906
+ continue;
907
+ }
908
+ if (ch === ",") {
909
+ let j = i + 1;
910
+ while (j < body.length && /\s/.test(body[j])) {
911
+ j += 1;
912
+ }
913
+ if (body[j] === "}" || body[j] === "]") {
914
+ continue;
915
+ }
916
+ }
917
+ out += ch;
918
+ }
919
+ return out;
920
+ }
761
921
 
762
922
  // src/game-action-audit.ts
763
923
  function normalizeAuditText(value, maxChars = 256) {
@@ -1079,7 +1239,7 @@ function logAckFailure(params) {
1079
1239
  async function submitGameActionWithLog(action, meta, wsClient, log, source, rawResponse, auditMeta) {
1080
1240
  if (!wsClient.isConnected()) {
1081
1241
  log?.error?.(`[GAME-ACTION] submit skipped: ws not connected eventId=${meta.eventId}`);
1082
- return false;
1242
+ return "failed";
1083
1243
  }
1084
1244
  const responseHash = rawResponse && rawResponse.length > 0 ? sha256Hex(rawResponse) : void 0;
1085
1245
  log?.info?.(
@@ -1087,7 +1247,7 @@ async function submitGameActionWithLog(action, meta, wsClient, log, source, rawR
1087
1247
  );
1088
1248
  const start = Date.now();
1089
1249
  try {
1090
- await sendGameAction({
1250
+ const response = await sendGameAction({
1091
1251
  client: wsClient,
1092
1252
  gameId: meta.gameId,
1093
1253
  roomId: meta.roomId,
@@ -1096,6 +1256,7 @@ async function submitGameActionWithLog(action, meta, wsClient, log, source, rawR
1096
1256
  actionData: action.actionData,
1097
1257
  turnSeq: meta.turnSeq,
1098
1258
  eventId: meta.eventId,
1259
+ deadlineEpochMs: meta.deadlineEpochMs,
1099
1260
  traceId: meta.traceId,
1100
1261
  promptPolicyVersion: meta.promptPolicyVersion,
1101
1262
  renderedPromptHash: meta.renderedPromptHash,
@@ -1106,16 +1267,28 @@ async function submitGameActionWithLog(action, meta, wsClient, log, source, rawR
1106
1267
  modelActionType: auditMeta?.modelActionType,
1107
1268
  validationReason: normalizeAuditText(auditMeta?.validationReason)
1108
1269
  });
1270
+ if (response.uncertain === true) {
1271
+ log?.warn?.(
1272
+ `[GAME-ACTION] submit uncertain source=${source} eventId=${meta.eventId} elapsedMs=${Date.now() - start} msg=${response.message ?? ""}`
1273
+ );
1274
+ return "uncertain";
1275
+ }
1109
1276
  log?.info?.(
1110
1277
  `[GAME-ACTION] submit ok source=${source} gameId=${meta.gameId} eventId=${meta.eventId} promptPolicyVersion=${meta.promptPolicyVersion ?? ""} renderedPromptHash=${meta.renderedPromptHash ?? ""} rawResponseHash=${responseHash ?? ""} elapsedMs=${Date.now() - start}`
1111
1278
  );
1112
- return true;
1279
+ return "submitted";
1113
1280
  } catch (err) {
1114
1281
  const errMsg = err instanceof Error ? err.message : String(err);
1282
+ if (isGameActionSubmitUncertainError(errMsg)) {
1283
+ log?.warn?.(
1284
+ `[GAME-ACTION] submit uncertain source=${source} eventId=${meta.eventId} elapsedMs=${Date.now() - start} err=${errMsg}`
1285
+ );
1286
+ return "uncertain";
1287
+ }
1115
1288
  log?.error?.(
1116
1289
  `[GAME-ACTION] submit failed source=${source} eventId=${meta.eventId} elapsedMs=${Date.now() - start} err=${errMsg}`
1117
1290
  );
1118
- return false;
1291
+ return "failed";
1119
1292
  }
1120
1293
  }
1121
1294
  async function submitBackendFallbackWithLog(params) {
@@ -1124,7 +1297,7 @@ async function submitBackendFallbackWithLog(params) {
1124
1297
  params.log?.warn?.(
1125
1298
  `[GAME-ACTION] backend fallback unavailable eventType=${params.meta.eventType} eventId=${params.meta.eventId} reason=${params.reason} promptPolicyVersion=${params.meta.promptPolicyVersion ?? ""} renderedPromptHash=${params.meta.renderedPromptHash ?? ""}`
1126
1299
  );
1127
- return false;
1300
+ return "failed";
1128
1301
  }
1129
1302
  const inferred = inferRejectedModelAction(params.rawResponse ?? "", params.meta.agentTask);
1130
1303
  const auditMeta = {
@@ -1145,6 +1318,9 @@ async function submitBackendFallbackWithLog(params) {
1145
1318
  auditMeta
1146
1319
  );
1147
1320
  }
1321
+ function isGameActionSubmitUncertainError(message) {
1322
+ return /request timed out|wss client stopped|websocket.*clos|socket hang up|econnreset|write epipe/i.test(message);
1323
+ }
1148
1324
  var runtimeClients = /* @__PURE__ */ new Map();
1149
1325
  function setRuntimeClient(accountKey, client) {
1150
1326
  runtimeClients.set(accountKey, client);
@@ -1316,7 +1492,7 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1316
1492
  log: ctx.log,
1317
1493
  reason: "runtime_not_available"
1318
1494
  });
1319
- if (!submitted) {
1495
+ if (submitted === "failed") {
1320
1496
  throw new Error("game fallback submit failed: runtime_not_available");
1321
1497
  }
1322
1498
  }
@@ -1439,10 +1615,11 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1439
1615
  full,
1440
1616
  {
1441
1617
  modelActionRejected: false,
1442
- modelActionType: validation.action.actionType
1618
+ modelActionType: validation.action.actionType,
1619
+ validationReason: validation.repairReason
1443
1620
  }
1444
1621
  );
1445
- if (submitted) {
1622
+ if (submitted === "submitted" || submitted === "uncertain") {
1446
1623
  gameSubmitted = true;
1447
1624
  } else {
1448
1625
  gameFallbackReason = `llm_action_submit_failed:${validation.action.actionType}`;
@@ -1510,7 +1687,7 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1510
1687
  validationReason: gameFallbackReason ?? gameValidationReason ?? inferred.validationReason
1511
1688
  }
1512
1689
  });
1513
- if (!submitted) {
1690
+ if (submitted === "failed") {
1514
1691
  throw new Error(`game fallback submit failed: ${gameFallbackReason ?? "unknown"}`);
1515
1692
  }
1516
1693
  } catch (fbErr) {
@@ -3,7 +3,7 @@ import {
3
3
  coolclawChannelPlugin,
4
4
  defaultBindingFile,
5
5
  setCoolclawRuntime
6
- } from "./chunk-QKB2R55C.js";
6
+ } from "./chunk-CPBRFMOB.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-EE5BK65S.js";
4
- import "./chunk-QKB2R55C.js";
3
+ } from "./chunk-QAJK4WOL.js";
4
+ import "./chunk-CPBRFMOB.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-EE5BK65S.js";
4
- import "./chunk-QKB2R55C.js";
3
+ } from "./chunk-QAJK4WOL.js";
4
+ import "./chunk-CPBRFMOB.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-QKB2R55C.js";
3
+ } from "./chunk-CPBRFMOB.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.5",
3
+ "version": "1.0.7",
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-6q6+KPGtAkHBZGsHgqEXJtnwLdVoZNLrZNbxZmzKisZvItoE2y+Acl9v3bNtqtEK/DR5NLahyloy7FMRVt9Upg==",
75
+ "expectedIntegrity": "sha512-nLVphLOrmsFhxyJU8neAdx4bE+EcJjLnmzGZmt9E9ndUqiPAjbJCb8pOv7HXYfHeQeVFnf42HkmVrUeqro3ClQ==",
76
76
  "defaultChoice": "npm",
77
77
  "minHostVersion": ">=2026.3.22"
78
78
  },
@@ -100,4 +100,4 @@
100
100
  "pluginSdkVersion": "2026.4.29"
101
101
  }
102
102
  }
103
- }
103
+ }