@coolclaw/coolclaw 1.0.16 → 1.0.18
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.
|
@@ -180,6 +180,7 @@ var FileAckStore = class {
|
|
|
180
180
|
|
|
181
181
|
// src/agent-task.ts
|
|
182
182
|
import { createHash } from "crypto";
|
|
183
|
+
var WEREWOLF_STRUCTURED_ACTION_PROTOCOL_VERSION = "WEREWOLF_STRUCTURED_JSON_MESSAGE_V1";
|
|
183
184
|
function normalizeAgentTask(value) {
|
|
184
185
|
if (!isRecord(value)) return null;
|
|
185
186
|
const actionContract = normalizeActionContract(value.actionContract);
|
|
@@ -191,6 +192,9 @@ function normalizeAgentTask(value) {
|
|
|
191
192
|
renderedPrompt: readString(value.renderedPrompt) ?? "",
|
|
192
193
|
renderedPromptHash: readString(value.renderedPromptHash),
|
|
193
194
|
actionFormat: readString(value.actionFormat),
|
|
195
|
+
actionProtocolVersion: readString(value.actionProtocolVersion),
|
|
196
|
+
outputSchema: isRecord(value.outputSchema) ? value.outputSchema : void 0,
|
|
197
|
+
retryPolicy: normalizeRetryPolicy(value.retryPolicy),
|
|
194
198
|
conversationKey: readString(value.conversationKey),
|
|
195
199
|
actionContract,
|
|
196
200
|
fallbackAction,
|
|
@@ -200,6 +204,9 @@ function normalizeAgentTask(value) {
|
|
|
200
204
|
function isDispatchableAgentTask(task) {
|
|
201
205
|
return task.requiresReply === true && task.renderedPrompt.trim().length > 0 && allowedActionTypes(task).length > 0;
|
|
202
206
|
}
|
|
207
|
+
function isStructuredJsonTask(task) {
|
|
208
|
+
return task.actionProtocolVersion === WEREWOLF_STRUCTURED_ACTION_PROTOCOL_VERSION || task.actionFormat === "STRICT_JSON_OBJECT";
|
|
209
|
+
}
|
|
203
210
|
function validateAgentAction(action, task) {
|
|
204
211
|
if (!isRecord(action.actionData)) {
|
|
205
212
|
return { ok: false, reason: "invalid_action_shape" };
|
|
@@ -212,16 +219,23 @@ function validateAgentAction(action, task) {
|
|
|
212
219
|
if (!option) {
|
|
213
220
|
return { ok: false, reason: "disallowed_action_type" };
|
|
214
221
|
}
|
|
215
|
-
const
|
|
222
|
+
const sanitizedActionData = sanitizeActionData(action.actionData);
|
|
223
|
+
const repaired = repairActionDataForSchema(sanitizedActionData, option.actionDataSchema);
|
|
216
224
|
if (!matchesActionDataSchema(repaired.actionData, option.actionDataSchema)) {
|
|
217
225
|
return { ok: false, reason: "invalid_action_shape" };
|
|
218
226
|
}
|
|
227
|
+
if (isStructuredJsonTask(task) && !matchesRootOutputSchema(action, task.outputSchema)) {
|
|
228
|
+
return { ok: false, reason: "invalid_action_shape" };
|
|
229
|
+
}
|
|
230
|
+
const normalizedAction = {
|
|
231
|
+
actionType: action.actionType,
|
|
232
|
+
actionData: repaired.actionData
|
|
233
|
+
};
|
|
234
|
+
if (typeof action.speech === "string") normalizedAction.speech = action.speech;
|
|
235
|
+
if (typeof action.voteReason === "string") normalizedAction.voteReason = action.voteReason;
|
|
219
236
|
return {
|
|
220
237
|
ok: true,
|
|
221
|
-
action:
|
|
222
|
-
actionType: action.actionType,
|
|
223
|
-
actionData: repaired.actionData
|
|
224
|
-
},
|
|
238
|
+
action: normalizedAction,
|
|
225
239
|
repairReason: repaired.repairReason
|
|
226
240
|
};
|
|
227
241
|
}
|
|
@@ -251,6 +265,14 @@ function normalizeActionContract(value) {
|
|
|
251
265
|
finalOutputRules: Array.isArray(value.finalOutputRules) ? value.finalOutputRules.filter((rule) => typeof rule === "string") : void 0
|
|
252
266
|
};
|
|
253
267
|
}
|
|
268
|
+
function normalizeRetryPolicy(value) {
|
|
269
|
+
if (!isRecord(value)) return void 0;
|
|
270
|
+
return {
|
|
271
|
+
maxRetries: readNumber(value.maxRetries),
|
|
272
|
+
shareDeadline: typeof value.shareDeadline === "boolean" ? value.shareDeadline : void 0,
|
|
273
|
+
rejectedOutputActionType: readString(value.rejectedOutputActionType)
|
|
274
|
+
};
|
|
275
|
+
}
|
|
254
276
|
function normalizeActionOption(value) {
|
|
255
277
|
if (!isRecord(value) || typeof value.actionType !== "string" || value.actionType.length === 0) {
|
|
256
278
|
return [];
|
|
@@ -266,20 +288,25 @@ function allowedActionTypes(task) {
|
|
|
266
288
|
}
|
|
267
289
|
function matchesActionDataSchema(actionData, schema) {
|
|
268
290
|
if (!schema || Object.keys(schema).length === 0) return true;
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
291
|
+
return matchesSchemaValue(actionData, schema, true);
|
|
292
|
+
}
|
|
293
|
+
function matchesRootOutputSchema(action, schema) {
|
|
294
|
+
if (!schema || Object.keys(schema).length === 0) return true;
|
|
295
|
+
const root = {
|
|
296
|
+
actionType: action.actionType,
|
|
297
|
+
actionData: action.actionData
|
|
298
|
+
};
|
|
299
|
+
if (typeof action.speech === "string") root.speech = action.speech;
|
|
300
|
+
if (typeof action.voteReason === "string") root.voteReason = action.voteReason;
|
|
301
|
+
return matchesSchemaValue(root, schema, true);
|
|
302
|
+
}
|
|
303
|
+
function sanitizeActionData(actionData) {
|
|
304
|
+
if (!Object.prototype.hasOwnProperty.call(actionData, "reason")) {
|
|
305
|
+
return actionData;
|
|
281
306
|
}
|
|
282
|
-
|
|
307
|
+
const sanitized = { ...actionData };
|
|
308
|
+
delete sanitized.reason;
|
|
309
|
+
return sanitized;
|
|
283
310
|
}
|
|
284
311
|
function repairActionDataForSchema(actionData, schema) {
|
|
285
312
|
if (!schema || Object.keys(schema).length === 0) {
|
|
@@ -315,12 +342,28 @@ function repairActionDataForSchema(actionData, schema) {
|
|
|
315
342
|
};
|
|
316
343
|
}
|
|
317
344
|
function matchesSchemaValue(value, schema, required) {
|
|
318
|
-
if (value == null) return !required;
|
|
319
345
|
if (!isRecord(schema)) return true;
|
|
346
|
+
if (value == null) {
|
|
347
|
+
return !required || schemaAllowsType(schema.type, "null");
|
|
348
|
+
}
|
|
349
|
+
const oneOf = Array.isArray(schema.oneOf) ? schema.oneOf : [];
|
|
350
|
+
if (oneOf.length > 0) {
|
|
351
|
+
return oneOf.some((variant) => matchesSchemaValue(value, variant, required));
|
|
352
|
+
}
|
|
320
353
|
if (Array.isArray(schema.enum) && !schema.enum.includes(value)) {
|
|
321
354
|
return false;
|
|
322
355
|
}
|
|
323
|
-
|
|
356
|
+
const allowedTypes = Array.isArray(schema.type) ? schema.type.filter((type) => typeof type === "string") : typeof schema.type === "string" ? [schema.type] : [];
|
|
357
|
+
if (allowedTypes.length === 0) {
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
return allowedTypes.some((type) => matchesSchemaTypedValue(value, schema, type));
|
|
361
|
+
}
|
|
362
|
+
function schemaAllowsType(typeNode, type) {
|
|
363
|
+
return Array.isArray(typeNode) ? typeNode.includes(type) : typeNode === type;
|
|
364
|
+
}
|
|
365
|
+
function matchesSchemaTypedValue(value, schema, type) {
|
|
366
|
+
switch (type) {
|
|
324
367
|
case "integer":
|
|
325
368
|
return typeof value === "number" && Number.isInteger(value);
|
|
326
369
|
case "number":
|
|
@@ -330,13 +373,33 @@ function matchesSchemaValue(value, schema, required) {
|
|
|
330
373
|
case "boolean":
|
|
331
374
|
return typeof value === "boolean";
|
|
332
375
|
case "object":
|
|
333
|
-
return
|
|
376
|
+
return matchesObjectSchema(value, schema);
|
|
334
377
|
case "array":
|
|
335
378
|
return Array.isArray(value);
|
|
379
|
+
case "null":
|
|
380
|
+
return value == null;
|
|
336
381
|
default:
|
|
337
382
|
return true;
|
|
338
383
|
}
|
|
339
384
|
}
|
|
385
|
+
function matchesObjectSchema(value, schema) {
|
|
386
|
+
if (!isRecord(value)) return false;
|
|
387
|
+
const properties = isRecord(schema.properties) ? schema.properties : {};
|
|
388
|
+
const required = Array.isArray(schema.required) ? schema.required.filter((field) => typeof field === "string" && field.length > 0) : [];
|
|
389
|
+
for (const field of required) {
|
|
390
|
+
if (!Object.prototype.hasOwnProperty.call(value, field)) return false;
|
|
391
|
+
if (!matchesSchemaValue(value[field], properties[field], true)) return false;
|
|
392
|
+
}
|
|
393
|
+
for (const [field, fieldValue] of Object.entries(value)) {
|
|
394
|
+
const propertySchema = properties[field];
|
|
395
|
+
if (propertySchema === void 0) {
|
|
396
|
+
if (schema.additionalProperties === false) return false;
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
if (!matchesSchemaValue(fieldValue, propertySchema, false)) return false;
|
|
400
|
+
}
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
340
403
|
function readString(value) {
|
|
341
404
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
342
405
|
}
|
|
@@ -1047,7 +1110,17 @@ async function sendGameAction(input) {
|
|
|
1047
1110
|
rawResponsePreview: input.rawResponsePreview,
|
|
1048
1111
|
modelActionRejected: input.modelActionRejected,
|
|
1049
1112
|
modelActionType: input.modelActionType,
|
|
1050
|
-
validationReason: input.validationReason
|
|
1113
|
+
validationReason: input.validationReason,
|
|
1114
|
+
submissionStatus: input.submissionStatus,
|
|
1115
|
+
structuredProtocolVersion: input.structuredProtocolVersion,
|
|
1116
|
+
retryCount: input.retryCount,
|
|
1117
|
+
firstRawResponseHash: input.firstRawResponseHash,
|
|
1118
|
+
firstModelActionType: input.firstModelActionType,
|
|
1119
|
+
firstValidationReason: input.firstValidationReason,
|
|
1120
|
+
retryRawResponseHash: input.retryRawResponseHash,
|
|
1121
|
+
retryValidationReason: input.retryValidationReason,
|
|
1122
|
+
speech: input.speech,
|
|
1123
|
+
voteReason: input.voteReason
|
|
1051
1124
|
});
|
|
1052
1125
|
const response = await input.client.request(frame);
|
|
1053
1126
|
if (response.ok === false) {
|
|
@@ -1057,15 +1130,6 @@ async function sendGameAction(input) {
|
|
|
1057
1130
|
}
|
|
1058
1131
|
|
|
1059
1132
|
// src/game-action-parser.ts
|
|
1060
|
-
function extractActionBlock(text) {
|
|
1061
|
-
const re = /<ACTION>\s*([\s\S]+?)\s*<\/ACTION>/gi;
|
|
1062
|
-
let match;
|
|
1063
|
-
let last = null;
|
|
1064
|
-
while ((match = re.exec(text)) !== null) {
|
|
1065
|
-
last = match[1];
|
|
1066
|
-
}
|
|
1067
|
-
return last;
|
|
1068
|
-
}
|
|
1069
1133
|
function extractFencedJsonBlocks(text) {
|
|
1070
1134
|
const re = /```(?:json)?\s*([\s\S]*?)\s*```/gi;
|
|
1071
1135
|
const blocks = [];
|
|
@@ -1085,15 +1149,12 @@ function extractTrailingJsonCandidates(text) {
|
|
|
1085
1149
|
if (candidate.includes('"actionType"') && candidate.includes('"actionData"')) {
|
|
1086
1150
|
candidates.push(candidate);
|
|
1087
1151
|
}
|
|
1152
|
+
if (idx === 0) break;
|
|
1088
1153
|
idx = trimmed.lastIndexOf("{", idx - 1);
|
|
1089
1154
|
}
|
|
1090
1155
|
return candidates;
|
|
1091
1156
|
}
|
|
1092
1157
|
function extractActionCandidates(text) {
|
|
1093
|
-
const actionBlock = extractActionBlock(text);
|
|
1094
|
-
if (actionBlock !== null) {
|
|
1095
|
-
return [actionBlock];
|
|
1096
|
-
}
|
|
1097
1158
|
const fenced = extractFencedJsonBlocks(text);
|
|
1098
1159
|
if (fenced.length > 0) {
|
|
1099
1160
|
return fenced.reverse();
|
|
@@ -1123,6 +1184,59 @@ function parseAgentAction(text) {
|
|
|
1123
1184
|
}
|
|
1124
1185
|
return lastError ?? { error: "no_action_block" };
|
|
1125
1186
|
}
|
|
1187
|
+
function parseStrictJsonAgentAction(text) {
|
|
1188
|
+
if (typeof text !== "string" || text.length === 0) {
|
|
1189
|
+
return { error: "no_action_block" };
|
|
1190
|
+
}
|
|
1191
|
+
const trimmed = text.trim();
|
|
1192
|
+
if (!trimmed.startsWith("{")) {
|
|
1193
|
+
return { error: "invalid_json", detail: "strict_json_object_required" };
|
|
1194
|
+
}
|
|
1195
|
+
const objectEnd = topLevelJsonObjectEnd(trimmed);
|
|
1196
|
+
if (objectEnd === null) {
|
|
1197
|
+
return { error: "invalid_json", detail: "strict_json_object_required" };
|
|
1198
|
+
}
|
|
1199
|
+
if (objectEnd !== trimmed.length - 1) {
|
|
1200
|
+
return { error: "invalid_json", detail: "trailing_text_after_json" };
|
|
1201
|
+
}
|
|
1202
|
+
return parseActionJsonStrict(trimmed);
|
|
1203
|
+
}
|
|
1204
|
+
function topLevelJsonObjectEnd(text) {
|
|
1205
|
+
let depth = 0;
|
|
1206
|
+
let inString = false;
|
|
1207
|
+
let escaped = false;
|
|
1208
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
1209
|
+
const ch = text[i];
|
|
1210
|
+
if (inString) {
|
|
1211
|
+
if (escaped) {
|
|
1212
|
+
escaped = false;
|
|
1213
|
+
} else if (ch === "\\") {
|
|
1214
|
+
escaped = true;
|
|
1215
|
+
} else if (ch === '"') {
|
|
1216
|
+
inString = false;
|
|
1217
|
+
}
|
|
1218
|
+
continue;
|
|
1219
|
+
}
|
|
1220
|
+
if (ch === '"') {
|
|
1221
|
+
inString = true;
|
|
1222
|
+
continue;
|
|
1223
|
+
}
|
|
1224
|
+
if (ch === "{") {
|
|
1225
|
+
depth += 1;
|
|
1226
|
+
continue;
|
|
1227
|
+
}
|
|
1228
|
+
if (ch === "}") {
|
|
1229
|
+
depth -= 1;
|
|
1230
|
+
if (depth === 0) {
|
|
1231
|
+
return i;
|
|
1232
|
+
}
|
|
1233
|
+
if (depth < 0) {
|
|
1234
|
+
return null;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
return null;
|
|
1239
|
+
}
|
|
1126
1240
|
function parseActionJson(body) {
|
|
1127
1241
|
let obj;
|
|
1128
1242
|
try {
|
|
@@ -1151,7 +1265,38 @@ function parseActionJson(body) {
|
|
|
1151
1265
|
}
|
|
1152
1266
|
return {
|
|
1153
1267
|
actionType: rec.actionType,
|
|
1154
|
-
actionData: rec.actionData
|
|
1268
|
+
actionData: rec.actionData,
|
|
1269
|
+
...typeof rec.speech === "string" ? { speech: rec.speech } : {},
|
|
1270
|
+
...typeof rec.voteReason === "string" ? { voteReason: rec.voteReason } : {}
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
function parseActionJsonStrict(body) {
|
|
1274
|
+
let obj;
|
|
1275
|
+
try {
|
|
1276
|
+
obj = JSON.parse(body);
|
|
1277
|
+
} catch (e) {
|
|
1278
|
+
return { error: "invalid_json", detail: e instanceof Error ? e.message : String(e) };
|
|
1279
|
+
}
|
|
1280
|
+
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
|
|
1281
|
+
return { error: "invalid_json", detail: "not an object" };
|
|
1282
|
+
}
|
|
1283
|
+
const rec = obj;
|
|
1284
|
+
for (const field of Object.keys(rec)) {
|
|
1285
|
+
if (!["speech", "voteReason", "reason", "actionType", "actionData"].includes(field)) {
|
|
1286
|
+
return { error: "invalid_json", detail: `unknown_top_level_field:${field}` };
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
if (typeof rec.actionType !== "string" || rec.actionType.length === 0) {
|
|
1290
|
+
return { error: "missing_action_type" };
|
|
1291
|
+
}
|
|
1292
|
+
if (typeof rec.actionData !== "object" || rec.actionData === null || Array.isArray(rec.actionData)) {
|
|
1293
|
+
return { error: "missing_action_data" };
|
|
1294
|
+
}
|
|
1295
|
+
return {
|
|
1296
|
+
actionType: rec.actionType,
|
|
1297
|
+
actionData: rec.actionData,
|
|
1298
|
+
...typeof rec.speech === "string" ? { speech: rec.speech } : {},
|
|
1299
|
+
...typeof rec.voteReason === "string" ? { voteReason: rec.voteReason } : {}
|
|
1155
1300
|
};
|
|
1156
1301
|
}
|
|
1157
1302
|
function removeTrailingCommas(body) {
|
|
@@ -1202,7 +1347,7 @@ function inferRejectedModelAction(rawResponse, task) {
|
|
|
1202
1347
|
validationReason: "no_model_output"
|
|
1203
1348
|
};
|
|
1204
1349
|
}
|
|
1205
|
-
const parsed = parseAgentAction(rawResponse);
|
|
1350
|
+
const parsed = isStructuredJsonTask(task) ? parseStrictJsonAgentAction(rawResponse) : parseAgentAction(rawResponse);
|
|
1206
1351
|
if ("error" in parsed) {
|
|
1207
1352
|
return {
|
|
1208
1353
|
modelActionRejected: true,
|
|
@@ -1620,11 +1765,13 @@ var CoolclawWsClient = class {
|
|
|
1620
1765
|
async connect() {
|
|
1621
1766
|
this.notifyState("connecting");
|
|
1622
1767
|
const lastAckedSeq = await this.options.ackStore.getLastAckedSeq(this.options.accountKey);
|
|
1768
|
+
const capabilities = this.options.capabilities ?? [WEREWOLF_STRUCTURED_ACTION_PROTOCOL_VERSION];
|
|
1623
1769
|
const socket = new WebSocket(buildWsUrl(this.options.gatewayUrl, lastAckedSeq), {
|
|
1624
1770
|
headers: {
|
|
1625
1771
|
Authorization: `Bearer ${this.options.token}`,
|
|
1626
1772
|
"X-CoolClaw-Agent-Id": this.options.agentId,
|
|
1627
|
-
"X-CoolClaw-Plugin-Version": this.options.pluginVersion
|
|
1773
|
+
"X-CoolClaw-Plugin-Version": this.options.pluginVersion,
|
|
1774
|
+
"X-CoolClaw-Capabilities": capabilities.join(",")
|
|
1628
1775
|
}
|
|
1629
1776
|
});
|
|
1630
1777
|
this.socket = socket;
|
|
@@ -1838,6 +1985,42 @@ function logAckFailure(params) {
|
|
|
1838
1985
|
const target = params.target ? ` target=${params.target}` : "";
|
|
1839
1986
|
params.log(`${params.channel} ack cleanup failed${target}: ${String(params.error)}`);
|
|
1840
1987
|
}
|
|
1988
|
+
function buildStructuredActionRetryPrompt(renderedPrompt, reason) {
|
|
1989
|
+
const reasonText = reason && reason.trim().length > 0 ? reason.trim() : "invalid_output";
|
|
1990
|
+
return `${renderedPrompt}
|
|
1991
|
+
|
|
1992
|
+
\u4E0A\u4E00\u6B21\u8F93\u51FA\u672A\u88AB\u72FC\u4EBA\u6740\u7ED3\u6784\u5316\u52A8\u4F5C\u534F\u8BAE\u63A5\u53D7\uFF0C\u5931\u8D25\u539F\u56E0\uFF1A${reasonText}\u3002
|
|
1993
|
+
\u8BF7\u91CD\u65B0\u4F5C\u7B54\uFF1A\u53EA\u8F93\u51FA\u4E00\u4E2A\u5B8C\u6574 JSON \u5BF9\u8C61\uFF0C\u4E0D\u8981\u8F93\u51FA Markdown\u3001\u89E3\u91CA\u6587\u5B57\u6216\u4EE3\u7801\u5757\u3002`;
|
|
1994
|
+
}
|
|
1995
|
+
function hasStructuredRetryBudget(deadlineEpochMs, nowEpochMs = Date.now(), safetyMarginMs = 1e3) {
|
|
1996
|
+
if (!deadlineEpochMs || deadlineEpochMs <= 0) {
|
|
1997
|
+
return true;
|
|
1998
|
+
}
|
|
1999
|
+
return nowEpochMs < deadlineEpochMs - Math.max(0, safetyMarginMs);
|
|
2000
|
+
}
|
|
2001
|
+
function shouldSubmitStructuredRejectedOutput(params) {
|
|
2002
|
+
if (!params.structuredTask || params.gameSubmitted) {
|
|
2003
|
+
return false;
|
|
2004
|
+
}
|
|
2005
|
+
if (params.modelActionRejected === false) {
|
|
2006
|
+
return false;
|
|
2007
|
+
}
|
|
2008
|
+
if (params.fallbackReason?.startsWith("llm_action_submit_failed:")) {
|
|
2009
|
+
return false;
|
|
2010
|
+
}
|
|
2011
|
+
return true;
|
|
2012
|
+
}
|
|
2013
|
+
function flattenForLog(text) {
|
|
2014
|
+
return text.replace(/\r/g, "").replace(/\n/g, " \\n ");
|
|
2015
|
+
}
|
|
2016
|
+
function logStructuredActionValidation(params) {
|
|
2017
|
+
const message = `[COOLCLAW-GAME-ACTION-VALIDATE] gameId=${params.meta.gameId} roomId=${params.meta.roomId} eventType=${params.meta.eventType} eventId=${params.meta.eventId} turnSeq=${params.meta.turnSeq} retryCount=${params.retryCount} submissionStatus=${params.submissionStatus} result=${params.result} reason=${params.reason ?? ""} actionType=${params.actionType ?? ""} promptPolicyVersion=${params.meta.promptPolicyVersion ?? ""} renderedPromptHash=${params.meta.renderedPromptHash ?? ""} rawResponseHash=${params.rawResponseHash ?? ""}`;
|
|
2018
|
+
if (params.result === "pass") {
|
|
2019
|
+
params.log?.info?.(message);
|
|
2020
|
+
} else {
|
|
2021
|
+
params.log?.warn?.(message);
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
1841
2024
|
function assertInboundRuntimeAvailable(runtime, context) {
|
|
1842
2025
|
if (runtime?.channel) {
|
|
1843
2026
|
return runtime.channel;
|
|
@@ -1852,7 +2035,10 @@ async function submitGameActionWithLog(action, meta, wsClient, log, source, rawR
|
|
|
1852
2035
|
log?.error?.(`[GAME-ACTION] submit skipped: ws not connected eventId=${meta.eventId}`);
|
|
1853
2036
|
return "failed";
|
|
1854
2037
|
}
|
|
1855
|
-
const responseHash =
|
|
2038
|
+
const responseHash = typeof rawResponse === "string" ? sha256Hex(rawResponse) : void 0;
|
|
2039
|
+
const structured = isStructuredJsonTask(meta.agentTask);
|
|
2040
|
+
const retryCount = auditMeta?.retryCount ?? 0;
|
|
2041
|
+
const structuredProtocolVersion = auditMeta?.structuredProtocolVersion ?? meta.agentTask.actionProtocolVersion;
|
|
1856
2042
|
log?.info?.(
|
|
1857
2043
|
`[GAME-ACTION] submit start source=${source} eventType=${meta.eventType} actionType=${action.actionType} gameId=${meta.gameId} roomId=${meta.roomId} turnSeq=${meta.turnSeq} eventId=${meta.eventId} promptPolicyVersion=${meta.promptPolicyVersion ?? ""} renderedPromptHash=${meta.renderedPromptHash ?? ""} rawResponseHash=${responseHash ?? ""}`
|
|
1858
2044
|
);
|
|
@@ -1873,10 +2059,20 @@ async function submitGameActionWithLog(action, meta, wsClient, log, source, rawR
|
|
|
1873
2059
|
renderedPromptHash: meta.renderedPromptHash,
|
|
1874
2060
|
parseSource: source,
|
|
1875
2061
|
rawResponseHash: responseHash,
|
|
1876
|
-
rawResponsePreview: rawResponse ? rawResponsePreview(rawResponse) : void 0,
|
|
2062
|
+
rawResponsePreview: typeof rawResponse === "string" ? rawResponsePreview(rawResponse, Number.MAX_SAFE_INTEGER) : void 0,
|
|
1877
2063
|
modelActionRejected: auditMeta?.modelActionRejected,
|
|
1878
2064
|
modelActionType: auditMeta?.modelActionType,
|
|
1879
|
-
validationReason: normalizeAuditText(auditMeta?.validationReason)
|
|
2065
|
+
validationReason: normalizeAuditText(auditMeta?.validationReason),
|
|
2066
|
+
submissionStatus: auditMeta?.submissionStatus ?? (structured ? "VALID" : void 0),
|
|
2067
|
+
structuredProtocolVersion,
|
|
2068
|
+
retryCount: structured ? retryCount : void 0,
|
|
2069
|
+
firstRawResponseHash: auditMeta?.firstRawResponseHash ?? (structured ? responseHash : void 0),
|
|
2070
|
+
firstModelActionType: auditMeta?.firstModelActionType,
|
|
2071
|
+
firstValidationReason: normalizeAuditText(auditMeta?.firstValidationReason),
|
|
2072
|
+
retryRawResponseHash: auditMeta?.retryRawResponseHash,
|
|
2073
|
+
retryValidationReason: normalizeAuditText(auditMeta?.retryValidationReason),
|
|
2074
|
+
speech: action.speech,
|
|
2075
|
+
voteReason: action.voteReason
|
|
1880
2076
|
});
|
|
1881
2077
|
if (response.uncertain === true) {
|
|
1882
2078
|
log?.warn?.(
|
|
@@ -1902,6 +2098,128 @@ async function submitGameActionWithLog(action, meta, wsClient, log, source, rawR
|
|
|
1902
2098
|
return "failed";
|
|
1903
2099
|
}
|
|
1904
2100
|
}
|
|
2101
|
+
async function submitRejectedOutputWithLog(params) {
|
|
2102
|
+
const rawResponse = params.rawResponse ?? "";
|
|
2103
|
+
const rawHash = sha256Hex(rawResponse);
|
|
2104
|
+
const retryCount = params.auditMeta?.retryCount ?? 0;
|
|
2105
|
+
params.log?.warn?.(
|
|
2106
|
+
`[COOLCLAW-GAME-ACTION-VALIDATE] gameId=${params.meta.gameId} roomId=${params.meta.roomId} eventType=${params.meta.eventType} eventId=${params.meta.eventId} turnSeq=${params.meta.turnSeq} retryCount=${retryCount} submissionStatus=REJECTED_OUTPUT result=submit reason=${params.reason} actionType=INVALID_OUTPUT promptPolicyVersion=${params.meta.promptPolicyVersion ?? ""} renderedPromptHash=${params.meta.renderedPromptHash ?? ""} rawResponseHash=${rawHash ?? ""}`
|
|
2107
|
+
);
|
|
2108
|
+
return submitGameActionWithLog(
|
|
2109
|
+
{ actionType: "INVALID_OUTPUT", actionData: {} },
|
|
2110
|
+
params.meta,
|
|
2111
|
+
params.wsClient,
|
|
2112
|
+
params.log,
|
|
2113
|
+
"rejected_output",
|
|
2114
|
+
rawResponse,
|
|
2115
|
+
{
|
|
2116
|
+
modelActionRejected: true,
|
|
2117
|
+
validationReason: params.reason,
|
|
2118
|
+
submissionStatus: "REJECTED_OUTPUT",
|
|
2119
|
+
structuredProtocolVersion: params.meta.agentTask.actionProtocolVersion,
|
|
2120
|
+
retryCount,
|
|
2121
|
+
firstRawResponseHash: params.auditMeta?.firstRawResponseHash ?? rawHash,
|
|
2122
|
+
firstModelActionType: params.auditMeta?.firstModelActionType,
|
|
2123
|
+
firstValidationReason: params.auditMeta?.firstValidationReason,
|
|
2124
|
+
retryRawResponseHash: params.auditMeta?.retryRawResponseHash ?? (retryCount > 0 ? rawHash : void 0),
|
|
2125
|
+
retryValidationReason: params.auditMeta?.retryValidationReason ?? (retryCount > 0 ? params.reason : void 0)
|
|
2126
|
+
}
|
|
2127
|
+
);
|
|
2128
|
+
}
|
|
2129
|
+
async function submitStructuredFinalActionWithLog(params) {
|
|
2130
|
+
const rawResponse = params.rawResponse ?? "";
|
|
2131
|
+
const rawHash = sha256Hex(rawResponse);
|
|
2132
|
+
if (rawResponse.trim().length === 0) {
|
|
2133
|
+
logStructuredActionValidation({
|
|
2134
|
+
log: params.log,
|
|
2135
|
+
meta: params.meta,
|
|
2136
|
+
retryCount: params.retryCount,
|
|
2137
|
+
submissionStatus: "REJECTED_OUTPUT",
|
|
2138
|
+
result: "reject",
|
|
2139
|
+
reason: "no_model_output",
|
|
2140
|
+
rawResponseHash: rawHash
|
|
2141
|
+
});
|
|
2142
|
+
return { submitted: false, reason: "no_model_output", rawHash };
|
|
2143
|
+
}
|
|
2144
|
+
const parsed = parseStrictJsonAgentAction(rawResponse);
|
|
2145
|
+
if ("error" in parsed) {
|
|
2146
|
+
const reason = parsed.error === "invalid_json" ? `invalid_json:${parsed.detail}` : parsed.error;
|
|
2147
|
+
logStructuredActionValidation({
|
|
2148
|
+
log: params.log,
|
|
2149
|
+
meta: params.meta,
|
|
2150
|
+
retryCount: params.retryCount,
|
|
2151
|
+
submissionStatus: "REJECTED_OUTPUT",
|
|
2152
|
+
result: "reject",
|
|
2153
|
+
reason,
|
|
2154
|
+
rawResponseHash: rawHash
|
|
2155
|
+
});
|
|
2156
|
+
return {
|
|
2157
|
+
submitted: false,
|
|
2158
|
+
reason,
|
|
2159
|
+
rawHash
|
|
2160
|
+
};
|
|
2161
|
+
}
|
|
2162
|
+
const validation = validateAgentAction(parsed, params.meta.agentTask);
|
|
2163
|
+
if (!validation.ok) {
|
|
2164
|
+
logStructuredActionValidation({
|
|
2165
|
+
log: params.log,
|
|
2166
|
+
meta: params.meta,
|
|
2167
|
+
retryCount: params.retryCount,
|
|
2168
|
+
submissionStatus: "REJECTED_OUTPUT",
|
|
2169
|
+
result: "reject",
|
|
2170
|
+
reason: validation.reason,
|
|
2171
|
+
actionType: parsed.actionType,
|
|
2172
|
+
rawResponseHash: rawHash
|
|
2173
|
+
});
|
|
2174
|
+
return {
|
|
2175
|
+
submitted: false,
|
|
2176
|
+
reason: validation.reason,
|
|
2177
|
+
rawHash,
|
|
2178
|
+
modelActionType: parsed.actionType
|
|
2179
|
+
};
|
|
2180
|
+
}
|
|
2181
|
+
logStructuredActionValidation({
|
|
2182
|
+
log: params.log,
|
|
2183
|
+
meta: params.meta,
|
|
2184
|
+
retryCount: params.retryCount,
|
|
2185
|
+
submissionStatus: "VALID",
|
|
2186
|
+
result: "pass",
|
|
2187
|
+
reason: validation.repairReason,
|
|
2188
|
+
actionType: validation.action.actionType,
|
|
2189
|
+
rawResponseHash: rawHash
|
|
2190
|
+
});
|
|
2191
|
+
const status = await submitGameActionWithLog(
|
|
2192
|
+
validation.action,
|
|
2193
|
+
params.meta,
|
|
2194
|
+
params.wsClient,
|
|
2195
|
+
params.log,
|
|
2196
|
+
"llm",
|
|
2197
|
+
rawResponse,
|
|
2198
|
+
{
|
|
2199
|
+
...params.auditMeta,
|
|
2200
|
+
modelActionRejected: false,
|
|
2201
|
+
modelActionType: validation.action.actionType,
|
|
2202
|
+
validationReason: validation.repairReason,
|
|
2203
|
+
submissionStatus: "VALID",
|
|
2204
|
+
structuredProtocolVersion: params.meta.agentTask.actionProtocolVersion,
|
|
2205
|
+
retryCount: params.retryCount,
|
|
2206
|
+
firstRawResponseHash: params.auditMeta?.firstRawResponseHash ?? rawHash,
|
|
2207
|
+
firstModelActionType: params.auditMeta?.firstModelActionType,
|
|
2208
|
+
firstValidationReason: params.auditMeta?.firstValidationReason,
|
|
2209
|
+
retryRawResponseHash: params.retryCount > 0 ? params.auditMeta?.retryRawResponseHash ?? rawHash : params.auditMeta?.retryRawResponseHash,
|
|
2210
|
+
retryValidationReason: params.retryCount > 0 ? params.auditMeta?.retryValidationReason ?? validation.repairReason : params.auditMeta?.retryValidationReason
|
|
2211
|
+
}
|
|
2212
|
+
);
|
|
2213
|
+
return {
|
|
2214
|
+
submitted: status === "submitted" || status === "uncertain",
|
|
2215
|
+
status,
|
|
2216
|
+
reason: status === "failed" ? `llm_action_submit_failed:${validation.action.actionType}` : void 0,
|
|
2217
|
+
rawHash,
|
|
2218
|
+
modelActionType: validation.action.actionType,
|
|
2219
|
+
validationReason: validation.repairReason,
|
|
2220
|
+
validAction: true
|
|
2221
|
+
};
|
|
2222
|
+
}
|
|
1905
2223
|
async function submitBackendFallbackWithLog(params) {
|
|
1906
2224
|
const fb = backendFallbackAction(params.meta.agentTask);
|
|
1907
2225
|
if (!fb) {
|
|
@@ -2114,6 +2432,14 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
2114
2432
|
let gameModelActionType;
|
|
2115
2433
|
let gameValidationReason;
|
|
2116
2434
|
let gameModelActionRejected;
|
|
2435
|
+
let gameReplyAttempt = 0;
|
|
2436
|
+
let gameFirstRawResponseHash;
|
|
2437
|
+
let gameFirstModelActionType;
|
|
2438
|
+
let gameFirstValidationReason;
|
|
2439
|
+
let gameRetryRawResponseHash;
|
|
2440
|
+
let gameRetryValidationReason;
|
|
2441
|
+
let gameBaseReplyContext = null;
|
|
2442
|
+
let dispatchGameReply = null;
|
|
2117
2443
|
const gameBuffer = [];
|
|
2118
2444
|
const modelQueryCollector = modelQueryMeta ? createArenaModelQueryReplyCollector({
|
|
2119
2445
|
meta: modelQueryMeta,
|
|
@@ -2163,6 +2489,23 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
2163
2489
|
logInboundDrop({ log: ctx.log?.warn?.bind(ctx.log) ?? (() => {
|
|
2164
2490
|
}), channel: productFlavor.channelId, reason: "runtime not available; skipping dispatch" });
|
|
2165
2491
|
if (isGameEvent && gameMeta) {
|
|
2492
|
+
if (isStructuredJsonTask(gameMeta.agentTask)) {
|
|
2493
|
+
const submitted2 = await submitRejectedOutputWithLog({
|
|
2494
|
+
meta: gameMeta,
|
|
2495
|
+
wsClient,
|
|
2496
|
+
log: ctx.log,
|
|
2497
|
+
reason: "runtime_not_available",
|
|
2498
|
+
rawResponse: "",
|
|
2499
|
+
auditMeta: {
|
|
2500
|
+
retryCount: 0,
|
|
2501
|
+
firstValidationReason: "runtime_not_available"
|
|
2502
|
+
}
|
|
2503
|
+
});
|
|
2504
|
+
if (submitted2 === "failed") {
|
|
2505
|
+
throw new Error("game rejected-output submit failed: runtime_not_available");
|
|
2506
|
+
}
|
|
2507
|
+
return;
|
|
2508
|
+
}
|
|
2166
2509
|
const submitted = await submitBackendFallbackWithLog({
|
|
2167
2510
|
meta: gameMeta,
|
|
2168
2511
|
wsClient,
|
|
@@ -2262,6 +2605,7 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
2262
2605
|
WasMentioned: envelope.shouldReply,
|
|
2263
2606
|
Mentioned: envelope.shouldReply
|
|
2264
2607
|
});
|
|
2608
|
+
gameBaseReplyContext = ctxPayload;
|
|
2265
2609
|
const sessionKey = ctxPayload.SessionKey ?? route.sessionKey;
|
|
2266
2610
|
const mainSessionKey = route.mainSessionKey;
|
|
2267
2611
|
await runtimeChannel.session.recordInboundSession({
|
|
@@ -2273,8 +2617,8 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
2273
2617
|
ctx.log?.warn(`recordInboundSession failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2274
2618
|
}
|
|
2275
2619
|
});
|
|
2276
|
-
|
|
2277
|
-
ctx:
|
|
2620
|
+
dispatchGameReply = async (replyCtxPayload) => runtimeChannel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
2621
|
+
ctx: replyCtxPayload,
|
|
2278
2622
|
cfg: ctx.cfg,
|
|
2279
2623
|
// 群聊强制走 automatic:CoolClaw 群聊业务语义就是 @ 即回,
|
|
2280
2624
|
// 而 OpenClaw 默认对 group/channel 走 message_tool_only,
|
|
@@ -2298,8 +2642,10 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
2298
2642
|
if (isGameEvent && gameMeta) {
|
|
2299
2643
|
if (gameSubmitted) return;
|
|
2300
2644
|
gameBuffer.push(String(payload.text));
|
|
2645
|
+
const structuredTask = isStructuredJsonTask(gameMeta.agentTask);
|
|
2646
|
+
if (structuredTask) return;
|
|
2301
2647
|
const full = gameBuffer.join("");
|
|
2302
|
-
const parsed = parseAgentAction(full);
|
|
2648
|
+
const parsed = structuredTask ? parseStrictJsonAgentAction(full) : parseAgentAction(full);
|
|
2303
2649
|
if ("error" in parsed) return;
|
|
2304
2650
|
const validation = validateAgentAction(parsed, gameMeta.agentTask);
|
|
2305
2651
|
if (!validation.ok) {
|
|
@@ -2307,11 +2653,30 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
2307
2653
|
gameModelActionType = parsed.actionType;
|
|
2308
2654
|
gameValidationReason = validation.reason;
|
|
2309
2655
|
gameFallbackReason = validation.reason;
|
|
2656
|
+
if (structuredTask) {
|
|
2657
|
+
const rawHash2 = sha256Hex(full);
|
|
2658
|
+
if (gameReplyAttempt === 0) {
|
|
2659
|
+
gameFirstRawResponseHash = rawHash2;
|
|
2660
|
+
gameFirstModelActionType = parsed.actionType;
|
|
2661
|
+
gameFirstValidationReason = validation.reason;
|
|
2662
|
+
} else {
|
|
2663
|
+
gameRetryRawResponseHash = rawHash2;
|
|
2664
|
+
gameRetryValidationReason = validation.reason;
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2310
2667
|
ctx.log?.warn?.(
|
|
2311
|
-
`[GAME-ACTION]
|
|
2668
|
+
`[COOLCLAW-GAME-ACTION-VALIDATE] gameId=${gameMeta.gameId} roomId=${gameMeta.roomId} eventType=${gameMeta.eventType} eventId=${gameMeta.eventId} turnSeq=${gameMeta.turnSeq} retryCount=${gameReplyAttempt} submissionStatus=REJECTED_OUTPUT result=reject reason=${validation.reason} actionType=${parsed.actionType} promptPolicyVersion=${gameMeta.promptPolicyVersion ?? ""} renderedPromptHash=${gameMeta.renderedPromptHash ?? ""} rawResponseHash=${sha256Hex(full)}`
|
|
2312
2669
|
);
|
|
2313
2670
|
return;
|
|
2314
2671
|
}
|
|
2672
|
+
if (structuredTask) {
|
|
2673
|
+
gameModelActionRejected = false;
|
|
2674
|
+
gameModelActionType = validation.action.actionType;
|
|
2675
|
+
gameValidationReason = validation.repairReason;
|
|
2676
|
+
gameFallbackReason = null;
|
|
2677
|
+
return;
|
|
2678
|
+
}
|
|
2679
|
+
const rawHash = sha256Hex(full);
|
|
2315
2680
|
const submitted = await submitGameActionWithLog(
|
|
2316
2681
|
validation.action,
|
|
2317
2682
|
gameMeta,
|
|
@@ -2322,7 +2687,15 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
2322
2687
|
{
|
|
2323
2688
|
modelActionRejected: false,
|
|
2324
2689
|
modelActionType: validation.action.actionType,
|
|
2325
|
-
validationReason: validation.repairReason
|
|
2690
|
+
validationReason: validation.repairReason,
|
|
2691
|
+
submissionStatus: structuredTask ? "VALID" : void 0,
|
|
2692
|
+
structuredProtocolVersion: structuredTask ? gameMeta.agentTask.actionProtocolVersion : void 0,
|
|
2693
|
+
retryCount: structuredTask ? gameReplyAttempt : void 0,
|
|
2694
|
+
firstRawResponseHash: structuredTask ? gameFirstRawResponseHash ?? rawHash : void 0,
|
|
2695
|
+
firstModelActionType: structuredTask ? gameFirstModelActionType : void 0,
|
|
2696
|
+
firstValidationReason: structuredTask ? gameFirstValidationReason : void 0,
|
|
2697
|
+
retryRawResponseHash: structuredTask && gameReplyAttempt > 0 ? rawHash : void 0,
|
|
2698
|
+
retryValidationReason: structuredTask && gameReplyAttempt > 0 ? validation.repairReason : void 0
|
|
2326
2699
|
}
|
|
2327
2700
|
);
|
|
2328
2701
|
if (submitted === "submitted" || submitted === "uncertain") {
|
|
@@ -2361,6 +2734,7 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
2361
2734
|
}
|
|
2362
2735
|
}
|
|
2363
2736
|
});
|
|
2737
|
+
await dispatchGameReply(ctxPayload);
|
|
2364
2738
|
if (isGameEvent && gameMeta && !gameSubmitted) {
|
|
2365
2739
|
if (!gameFallbackReason) {
|
|
2366
2740
|
gameFallbackReason = "no_valid_action_in_llm_output";
|
|
@@ -2392,10 +2766,91 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
2392
2766
|
gameFallbackReason = `dispatch_error: ${errMsg}`;
|
|
2393
2767
|
}
|
|
2394
2768
|
} finally {
|
|
2769
|
+
const submitFinalStructuredGameAction = async () => {
|
|
2770
|
+
if (!isGameEvent || !gameMeta || gameSubmitted || !isStructuredJsonTask(gameMeta.agentTask)) {
|
|
2771
|
+
return false;
|
|
2772
|
+
}
|
|
2773
|
+
const rawResponse = gameBuffer.join("");
|
|
2774
|
+
const result = await submitStructuredFinalActionWithLog({
|
|
2775
|
+
rawResponse,
|
|
2776
|
+
meta: gameMeta,
|
|
2777
|
+
wsClient,
|
|
2778
|
+
log: ctx.log,
|
|
2779
|
+
retryCount: gameReplyAttempt,
|
|
2780
|
+
auditMeta: {
|
|
2781
|
+
retryCount: gameReplyAttempt,
|
|
2782
|
+
firstRawResponseHash: gameFirstRawResponseHash,
|
|
2783
|
+
firstModelActionType: gameFirstModelActionType,
|
|
2784
|
+
firstValidationReason: gameFirstValidationReason,
|
|
2785
|
+
retryRawResponseHash: gameRetryRawResponseHash,
|
|
2786
|
+
retryValidationReason: gameRetryValidationReason
|
|
2787
|
+
}
|
|
2788
|
+
});
|
|
2789
|
+
if (result.submitted) {
|
|
2790
|
+
gameSubmitted = true;
|
|
2791
|
+
gameModelActionRejected = false;
|
|
2792
|
+
gameModelActionType = result.modelActionType;
|
|
2793
|
+
gameValidationReason = result.validationReason;
|
|
2794
|
+
gameFallbackReason = null;
|
|
2795
|
+
return true;
|
|
2796
|
+
}
|
|
2797
|
+
gameModelActionRejected = result.validAction ? false : true;
|
|
2798
|
+
gameModelActionType = result.modelActionType;
|
|
2799
|
+
gameValidationReason = result.reason ?? result.validationReason;
|
|
2800
|
+
gameFallbackReason = result.reason ?? gameFallbackReason ?? "invalid_output";
|
|
2801
|
+
if (rawResponse.trim().length > 0) {
|
|
2802
|
+
const rawHash = result.rawHash ?? sha256Hex(rawResponse);
|
|
2803
|
+
if (gameReplyAttempt === 0) {
|
|
2804
|
+
gameFirstRawResponseHash = gameFirstRawResponseHash ?? rawHash;
|
|
2805
|
+
gameFirstModelActionType = gameFirstModelActionType ?? result.modelActionType;
|
|
2806
|
+
gameFirstValidationReason = gameFirstValidationReason ?? gameValidationReason;
|
|
2807
|
+
} else {
|
|
2808
|
+
gameRetryRawResponseHash = gameRetryRawResponseHash ?? rawHash;
|
|
2809
|
+
gameRetryValidationReason = gameRetryValidationReason ?? gameValidationReason;
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
return false;
|
|
2813
|
+
};
|
|
2814
|
+
await submitFinalStructuredGameAction();
|
|
2815
|
+
if (isGameEvent && gameMeta && !gameSubmitted && gameReplyAttempt === 0 && isStructuredJsonTask(gameMeta.agentTask) && dispatchGameReply && gameBaseReplyContext && hasStructuredRetryBudget(gameMeta.deadlineEpochMs) && Math.max(0, gameMeta.agentTask.retryPolicy?.maxRetries ?? 0) > 0) {
|
|
2816
|
+
const firstRawResponse = gameBuffer.join("");
|
|
2817
|
+
if (firstRawResponse.trim().length > 0) {
|
|
2818
|
+
const firstInferred = inferRejectedModelAction(firstRawResponse, gameMeta.agentTask);
|
|
2819
|
+
if (gameModelActionRejected === false || firstInferred.validationReason === "valid_action_not_submitted") {
|
|
2820
|
+
ctx.log?.warn?.(
|
|
2821
|
+
`[GAME-ACTION] structured retry skipped for valid unsubmitted action eventId=${gameMeta.eventId} turnSeq=${gameMeta.turnSeq} reason=${gameFallbackReason ?? firstInferred.validationReason ?? "submit_failed"}`
|
|
2822
|
+
);
|
|
2823
|
+
} else {
|
|
2824
|
+
const firstRawHash = sha256Hex(firstRawResponse);
|
|
2825
|
+
gameFirstRawResponseHash = gameFirstRawResponseHash ?? firstRawHash;
|
|
2826
|
+
gameFirstModelActionType = gameFirstModelActionType ?? firstInferred.modelActionType;
|
|
2827
|
+
gameFirstValidationReason = gameFirstValidationReason ?? gameValidationReason ?? firstInferred.validationReason;
|
|
2828
|
+
const retryReason = gameFirstValidationReason ?? firstInferred.validationReason ?? "invalid_output";
|
|
2829
|
+
ctx.log?.warn?.(
|
|
2830
|
+
`[GAME-ACTION] structured retry start eventId=${gameMeta.eventId} turnSeq=${gameMeta.turnSeq} reason=${retryReason} firstRawResponseHash=${firstRawHash}`
|
|
2831
|
+
);
|
|
2832
|
+
gameReplyAttempt = 1;
|
|
2833
|
+
gameBuffer.length = 0;
|
|
2834
|
+
gameFallbackReason = null;
|
|
2835
|
+
gameModelActionRejected = void 0;
|
|
2836
|
+
gameModelActionType = void 0;
|
|
2837
|
+
gameValidationReason = void 0;
|
|
2838
|
+
const retryPrompt = buildStructuredActionRetryPrompt(envelope.text, retryReason);
|
|
2839
|
+
await dispatchGameReply({
|
|
2840
|
+
...gameBaseReplyContext,
|
|
2841
|
+
Body: retryPrompt,
|
|
2842
|
+
BodyForAgent: retryPrompt,
|
|
2843
|
+
RawBody: retryPrompt,
|
|
2844
|
+
CommandBody: retryPrompt
|
|
2845
|
+
});
|
|
2846
|
+
await submitFinalStructuredGameAction();
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2395
2850
|
if (isGameEvent && gameMeta) {
|
|
2396
2851
|
const rawResponse = gameBuffer.join("");
|
|
2397
2852
|
ctx.log?.info?.(
|
|
2398
|
-
`[GAME-TASK] model-output eventId=${gameMeta.eventId} promptPolicyVersion=${gameMeta.promptPolicyVersion ?? ""} renderedPromptHash=${gameMeta.renderedPromptHash ?? ""} rawHash=${rawResponse ? sha256Hex(rawResponse) : ""}
|
|
2853
|
+
`[GAME-TASK] model-output eventId=${gameMeta.eventId} turnSeq=${gameMeta.turnSeq} retryAttempt=${gameReplyAttempt} promptPolicyVersion=${gameMeta.promptPolicyVersion ?? ""} renderedPromptHash=${gameMeta.renderedPromptHash ?? ""} rawHash=${rawResponse ? sha256Hex(rawResponse) : ""} rawResponseText=${rawResponse ? flattenForLog(rawResponse) : ""}`
|
|
2399
2854
|
);
|
|
2400
2855
|
}
|
|
2401
2856
|
if (isGameEvent && gameMeta && !gameSubmitted) {
|
|
@@ -2408,6 +2863,55 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
2408
2863
|
if (!gameFallbackReason || gameFallbackReason === "no_valid_action_in_llm_output") {
|
|
2409
2864
|
gameFallbackReason = gameValidationReason ?? inferred.validationReason ?? gameFallbackReason;
|
|
2410
2865
|
}
|
|
2866
|
+
if (isStructuredJsonTask(gameMeta.agentTask)) {
|
|
2867
|
+
if (rawResponse.trim().length === 0) {
|
|
2868
|
+
gameFallbackReason = "no_model_output";
|
|
2869
|
+
gameValidationReason = gameValidationReason ?? "no_model_output";
|
|
2870
|
+
}
|
|
2871
|
+
if (gameReplyAttempt > 0 && rawResponse.trim().length > 0) {
|
|
2872
|
+
gameRetryRawResponseHash = gameRetryRawResponseHash ?? sha256Hex(rawResponse);
|
|
2873
|
+
gameRetryValidationReason = gameRetryValidationReason ?? gameValidationReason ?? inferred.validationReason;
|
|
2874
|
+
}
|
|
2875
|
+
const rawHashForAudit = rawResponse.trim().length > 0 ? sha256Hex(rawResponse) : void 0;
|
|
2876
|
+
const rejectedOutputReason = gameFallbackReason ?? gameValidationReason ?? inferred.validationReason ?? "invalid_json";
|
|
2877
|
+
if (!shouldSubmitStructuredRejectedOutput({
|
|
2878
|
+
structuredTask: true,
|
|
2879
|
+
gameSubmitted,
|
|
2880
|
+
modelActionRejected: gameModelActionRejected,
|
|
2881
|
+
fallbackReason: rejectedOutputReason
|
|
2882
|
+
})) {
|
|
2883
|
+
logStructuredActionValidation({
|
|
2884
|
+
log: ctx.log,
|
|
2885
|
+
meta: gameMeta,
|
|
2886
|
+
retryCount: gameReplyAttempt,
|
|
2887
|
+
submissionStatus: "VALID",
|
|
2888
|
+
result: "submit_failed_no_rejected_output",
|
|
2889
|
+
reason: rejectedOutputReason,
|
|
2890
|
+
actionType: gameModelActionType,
|
|
2891
|
+
rawResponseHash: rawHashForAudit
|
|
2892
|
+
});
|
|
2893
|
+
return;
|
|
2894
|
+
}
|
|
2895
|
+
const submitted2 = await submitRejectedOutputWithLog({
|
|
2896
|
+
meta: gameMeta,
|
|
2897
|
+
wsClient,
|
|
2898
|
+
log: ctx.log,
|
|
2899
|
+
reason: rejectedOutputReason,
|
|
2900
|
+
rawResponse,
|
|
2901
|
+
auditMeta: {
|
|
2902
|
+
retryCount: gameReplyAttempt,
|
|
2903
|
+
firstRawResponseHash: gameFirstRawResponseHash ?? rawHashForAudit,
|
|
2904
|
+
firstModelActionType: gameFirstModelActionType ?? gameModelActionType,
|
|
2905
|
+
firstValidationReason: gameFirstValidationReason ?? gameValidationReason ?? inferred.validationReason,
|
|
2906
|
+
retryRawResponseHash: gameRetryRawResponseHash,
|
|
2907
|
+
retryValidationReason: gameRetryValidationReason
|
|
2908
|
+
}
|
|
2909
|
+
});
|
|
2910
|
+
if (submitted2 === "failed") {
|
|
2911
|
+
throw new Error(`game rejected-output submit failed: ${gameFallbackReason ?? "unknown"}`);
|
|
2912
|
+
}
|
|
2913
|
+
return;
|
|
2914
|
+
}
|
|
2411
2915
|
const submitted = await submitBackendFallbackWithLog({
|
|
2412
2916
|
meta: gameMeta,
|
|
2413
2917
|
wsClient,
|
package/dist/cli-metadata.js
CHANGED
package/dist/index.js
CHANGED
package/dist/setup-entry.js
CHANGED