@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.
@@ -4,7 +4,7 @@ import {
4
4
  coolclawChannelPlugin,
5
5
  defaultBindingFile,
6
6
  setCoolclawRuntime
7
- } from "./chunk-BVFSS5UA.js";
7
+ } from "./chunk-N2OJAALL.js";
8
8
 
9
9
  // index.ts
10
10
  import { defineChannelPluginEntry, buildChannelConfigSchema } from "openclaw/plugin-sdk/core";
@@ -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 repaired = repairActionDataForSchema(action.actionData, option.actionDataSchema);
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
- if (typeof schema.type === "string" && schema.type !== "object") return false;
270
- const properties = isRecord(schema.properties) ? schema.properties : {};
271
- const required = Array.isArray(schema.required) ? schema.required.filter((field) => typeof field === "string" && field.length > 0) : [];
272
- for (const field of required) {
273
- if (!Object.prototype.hasOwnProperty.call(actionData, field)) return false;
274
- if (!matchesSchemaValue(actionData[field], properties[field], true)) return false;
275
- }
276
- for (const [field, value] of Object.entries(actionData)) {
277
- const propertySchema = properties[field];
278
- if (propertySchema !== void 0 && !matchesSchemaValue(value, propertySchema, false)) {
279
- return false;
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
- return true;
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
- switch (schema.type) {
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 isRecord(value);
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 = rawResponse && rawResponse.length > 0 ? sha256Hex(rawResponse) : void 0;
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
- await runtimeChannel.reply.dispatchReplyWithBufferedBlockDispatcher({
2277
- ctx: ctxPayload,
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] rejected model action reason=${validation.reason} actionType=${parsed.actionType} eventType=${gameMeta.eventType} eventId=${gameMeta.eventId} promptPolicyVersion=${gameMeta.promptPolicyVersion ?? ""} renderedPromptHash=${gameMeta.renderedPromptHash ?? ""} rawResponseHash=${sha256Hex(full)}`
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) : ""} rawPreview=${rawResponsePreview(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,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  index_default
3
- } from "./chunk-TCWYIFNP.js";
4
- import "./chunk-BVFSS5UA.js";
3
+ } from "./chunk-G2JZSRZF.js";
4
+ import "./chunk-N2OJAALL.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-TCWYIFNP.js";
4
- import "./chunk-BVFSS5UA.js";
3
+ } from "./chunk-G2JZSRZF.js";
4
+ import "./chunk-N2OJAALL.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-BVFSS5UA.js";
3
+ } from "./chunk-N2OJAALL.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.16",
3
+ "version": "1.0.18",
4
4
  "description": "OpenClaw native channel plugin for CoolClaw chat.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",