@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,
|
|
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
|
|
|
@@ -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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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>/
|
|
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
|
|
677
|
-
if (
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
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
|
-
"
|
|
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
|
});
|
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.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-
|
|
75
|
+
"expectedIntegrity": "sha512-7VcGcTTi2KmOCAJ76tj8uyEIUURe6LftxSpjmbrLXzcKC7YKjNrpNzwFMBnN4g4yfxtSsyf0bhT61gsI8RyUpw==",
|
|
75
76
|
"defaultChoice": "npm",
|
|
76
77
|
"minHostVersion": ">=2026.3.22"
|
|
77
78
|
},
|