@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,
|
|
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 (
|
|
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
|
-
|
|
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
|
|
737
|
-
if (
|
|
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(
|
|
858
|
+
obj = JSON.parse(body);
|
|
743
859
|
} catch (e) {
|
|
744
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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 (
|
|
1690
|
+
if (submitted === "failed") {
|
|
1514
1691
|
throw new Error(`game fallback submit failed: ${gameFallbackReason ?? "unknown"}`);
|
|
1515
1692
|
}
|
|
1516
1693
|
} catch (fbErr) {
|
package/dist/cli-metadata.js
CHANGED
package/dist/index.js
CHANGED
package/dist/setup-entry.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coolclaw/coolclaw",
|
|
3
|
-
"version": "1.0.
|
|
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-
|
|
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
|
+
}
|