@coolclaw/coolclaw 1.0.3 → 1.0.5

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.
@@ -3,7 +3,7 @@ import {
3
3
  coolclawChannelPlugin,
4
4
  defaultBindingFile,
5
5
  setCoolclawRuntime
6
- } from "./chunk-DVHZ22OJ.js";
6
+ } from "./chunk-QKB2R55C.js";
7
7
 
8
8
  // index.ts
9
9
  import { defineChannelPluginEntry, buildChannelConfigSchema } from "openclaw/plugin-sdk/core";
@@ -150,9 +150,13 @@ function validateAgentAction(action, task) {
150
150
  if (allowed.length === 0) {
151
151
  return { ok: false, reason: "missing_contract" };
152
152
  }
153
- if (!allowed.includes(action.actionType)) {
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
+ }
156
160
  return { ok: true, action };
157
161
  }
158
162
  function backendFallbackAction(task) {
@@ -169,7 +173,7 @@ function backendFallbackAction(task) {
169
173
  function sha256Hex(text) {
170
174
  return createHash("sha256").update(text).digest("hex");
171
175
  }
172
- function rawResponsePreview(text, maxChars = 2e3) {
176
+ function rawResponsePreview(text, maxChars = 5e3) {
173
177
  if (text.length === 0) return void 0;
174
178
  return text.length <= maxChars ? text : `${text.slice(0, maxChars)}...`;
175
179
  }
@@ -194,6 +198,46 @@ function normalizeActionOption(value) {
194
198
  function allowedActionTypes(task) {
195
199
  return task.actionContract.options.map((option) => option.actionType);
196
200
  }
201
+ function matchesActionDataSchema(actionData, schema) {
202
+ if (!schema || Object.keys(schema).length === 0) return true;
203
+ if (typeof schema.type === "string" && schema.type !== "object") return false;
204
+ const properties = isRecord(schema.properties) ? schema.properties : {};
205
+ const required = Array.isArray(schema.required) ? schema.required.filter((field) => typeof field === "string" && field.length > 0) : [];
206
+ for (const field of required) {
207
+ if (!Object.prototype.hasOwnProperty.call(actionData, field)) return false;
208
+ if (!matchesSchemaValue(actionData[field], properties[field], true)) return false;
209
+ }
210
+ for (const [field, value] of Object.entries(actionData)) {
211
+ const propertySchema = properties[field];
212
+ if (propertySchema !== void 0 && !matchesSchemaValue(value, propertySchema, false)) {
213
+ return false;
214
+ }
215
+ }
216
+ return true;
217
+ }
218
+ function matchesSchemaValue(value, schema, required) {
219
+ if (value == null) return !required;
220
+ if (!isRecord(schema)) return true;
221
+ if (Array.isArray(schema.enum) && !schema.enum.includes(value)) {
222
+ return false;
223
+ }
224
+ switch (schema.type) {
225
+ case "integer":
226
+ return typeof value === "number" && Number.isInteger(value);
227
+ case "number":
228
+ return typeof value === "number" && Number.isFinite(value);
229
+ case "string":
230
+ return typeof value === "string" && (typeof schema.minLength !== "number" || value.length >= schema.minLength) && (typeof schema.maxLength !== "number" || value.length <= schema.maxLength);
231
+ case "boolean":
232
+ return typeof value === "boolean";
233
+ case "object":
234
+ return isRecord(value);
235
+ case "array":
236
+ return Array.isArray(value);
237
+ default:
238
+ return true;
239
+ }
240
+ }
197
241
  function readString(value) {
198
242
  return typeof value === "string" && value.length > 0 ? value : void 0;
199
243
  }
@@ -327,8 +371,14 @@ async function handleInboundFrame(input) {
327
371
  await ackFrameSeq(input);
328
372
  return;
329
373
  }
330
- await ackProcessedSeq(input, envelope);
374
+ const ackAfterDispatch = envelope.metadata.gameEvent === true;
375
+ if (!ackAfterDispatch) {
376
+ await ackProcessedSeq(input, envelope);
377
+ }
331
378
  await input.dispatch(envelope);
379
+ if (ackAfterDispatch) {
380
+ await ackProcessedSeq(input, envelope);
381
+ }
332
382
  }
333
383
  function mapNotificationFrame(frame) {
334
384
  const payload = isRecord3(frame.payload) ? frame.payload : {};
@@ -442,8 +492,10 @@ async function ackFrameSeq(input) {
442
492
  }
443
493
  }
444
494
  async function ackSeq(input, seq) {
445
- const lastAckedSeq = await input.ackStore.record(input.accountKey, seq);
495
+ const current = await input.ackStore.getLastAckedSeq(input.accountKey);
496
+ const lastAckedSeq = seq <= current ? current : seq;
446
497
  await input.sendAck(createFrame("ACK", { lastAckedSeq }));
498
+ await input.ackStore.record(input.accountKey, seq);
447
499
  }
448
500
  function assertPrivatePayload(value) {
449
501
  if (!isRecord3(value) || !isUserRef(value.sender) || !isUserRef(value.recipient)) {
@@ -651,7 +703,10 @@ async function sendGameAction(input) {
651
703
  renderedPromptHash: input.renderedPromptHash,
652
704
  parseSource: input.parseSource,
653
705
  rawResponseHash: input.rawResponseHash,
654
- rawResponsePreview: input.rawResponsePreview
706
+ rawResponsePreview: input.rawResponsePreview,
707
+ modelActionRejected: input.modelActionRejected,
708
+ modelActionType: input.modelActionType,
709
+ validationReason: input.validationReason
655
710
  });
656
711
  const response = await input.client.request(frame);
657
712
  if (response.ok === false) {
@@ -661,7 +716,7 @@ async function sendGameAction(input) {
661
716
 
662
717
  // src/game-action-parser.ts
663
718
  function extractActionBlock(text) {
664
- const re = /<ACTION>\s*([\s\S]+?)\s*<\/ACTION>/g;
719
+ const re = /<ACTION>\s*([\s\S]+?)\s*<\/ACTION>/gi;
665
720
  let match;
666
721
  let last = null;
667
722
  while ((match = re.exec(text)) !== null) {
@@ -669,6 +724,11 @@ function extractActionBlock(text) {
669
724
  }
670
725
  return last;
671
726
  }
727
+ function normalizeActionBlock(body) {
728
+ const trimmed = body.trim();
729
+ const fenced = /^```(?:json)?\s*([\s\S]*?)\s*```$/i.exec(trimmed);
730
+ return fenced ? fenced[1].trim() : trimmed;
731
+ }
672
732
  function parseAgentAction(text) {
673
733
  if (typeof text !== "string" || text.length === 0) {
674
734
  return { error: "no_action_block" };
@@ -679,7 +739,7 @@ function parseAgentAction(text) {
679
739
  }
680
740
  let obj;
681
741
  try {
682
- obj = JSON.parse(body);
742
+ obj = JSON.parse(normalizeActionBlock(body));
683
743
  } catch (e) {
684
744
  return { error: "invalid_json", detail: e instanceof Error ? e.message : String(e) };
685
745
  }
@@ -699,6 +759,46 @@ function parseAgentAction(text) {
699
759
  };
700
760
  }
701
761
 
762
+ // src/game-action-audit.ts
763
+ function normalizeAuditText(value, maxChars = 256) {
764
+ if (!value) return void 0;
765
+ return value.length <= maxChars ? value : value.slice(0, maxChars);
766
+ }
767
+ function inferRejectedModelAction(rawResponse, task) {
768
+ if (rawResponse.trim().length === 0) {
769
+ return {
770
+ modelActionRejected: false,
771
+ validationReason: "no_model_output"
772
+ };
773
+ }
774
+ const parsed = parseAgentAction(rawResponse);
775
+ if ("error" in parsed) {
776
+ return {
777
+ modelActionRejected: true,
778
+ validationReason: parseErrorReason(parsed)
779
+ };
780
+ }
781
+ const validation = validateAgentAction(parsed, task);
782
+ if (!validation.ok) {
783
+ return {
784
+ modelActionRejected: true,
785
+ modelActionType: parsed.actionType,
786
+ validationReason: validation.reason
787
+ };
788
+ }
789
+ return {
790
+ modelActionRejected: true,
791
+ modelActionType: parsed.actionType,
792
+ validationReason: "valid_action_not_submitted"
793
+ };
794
+ }
795
+ function parseErrorReason(error) {
796
+ if (error.error === "invalid_json") {
797
+ return normalizeAuditText(`invalid_json:${error.detail}`) ?? "invalid_json";
798
+ }
799
+ return error.error;
800
+ }
801
+
702
802
  // src/ws-client.ts
703
803
  import WebSocket from "ws";
704
804
  var CoolclawWsClient = class {
@@ -976,10 +1076,10 @@ function logAckFailure(params) {
976
1076
  const target = params.target ? ` target=${params.target}` : "";
977
1077
  params.log(`${params.channel} ack cleanup failed${target}: ${String(params.error)}`);
978
1078
  }
979
- async function submitGameActionWithLog(action, meta, wsClient, log, source, rawResponse) {
1079
+ async function submitGameActionWithLog(action, meta, wsClient, log, source, rawResponse, auditMeta) {
980
1080
  if (!wsClient.isConnected()) {
981
1081
  log?.error?.(`[GAME-ACTION] submit skipped: ws not connected eventId=${meta.eventId}`);
982
- return;
1082
+ return false;
983
1083
  }
984
1084
  const responseHash = rawResponse && rawResponse.length > 0 ? sha256Hex(rawResponse) : void 0;
985
1085
  log?.info?.(
@@ -1001,17 +1101,49 @@ async function submitGameActionWithLog(action, meta, wsClient, log, source, rawR
1001
1101
  renderedPromptHash: meta.renderedPromptHash,
1002
1102
  parseSource: source,
1003
1103
  rawResponseHash: responseHash,
1004
- rawResponsePreview: rawResponse ? rawResponsePreview(rawResponse) : void 0
1104
+ rawResponsePreview: rawResponse ? rawResponsePreview(rawResponse) : void 0,
1105
+ modelActionRejected: auditMeta?.modelActionRejected,
1106
+ modelActionType: auditMeta?.modelActionType,
1107
+ validationReason: normalizeAuditText(auditMeta?.validationReason)
1005
1108
  });
1006
1109
  log?.info?.(
1007
1110
  `[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
1111
  );
1112
+ return true;
1009
1113
  } catch (err) {
1010
1114
  const errMsg = err instanceof Error ? err.message : String(err);
1011
1115
  log?.error?.(
1012
1116
  `[GAME-ACTION] submit failed source=${source} eventId=${meta.eventId} elapsedMs=${Date.now() - start} err=${errMsg}`
1013
1117
  );
1118
+ return false;
1119
+ }
1120
+ }
1121
+ async function submitBackendFallbackWithLog(params) {
1122
+ const fb = backendFallbackAction(params.meta.agentTask);
1123
+ if (!fb) {
1124
+ params.log?.warn?.(
1125
+ `[GAME-ACTION] backend fallback unavailable eventType=${params.meta.eventType} eventId=${params.meta.eventId} reason=${params.reason} promptPolicyVersion=${params.meta.promptPolicyVersion ?? ""} renderedPromptHash=${params.meta.renderedPromptHash ?? ""}`
1126
+ );
1127
+ return false;
1014
1128
  }
1129
+ const inferred = inferRejectedModelAction(params.rawResponse ?? "", params.meta.agentTask);
1130
+ const auditMeta = {
1131
+ modelActionRejected: params.auditMeta?.modelActionRejected ?? inferred.modelActionRejected,
1132
+ modelActionType: params.auditMeta?.modelActionType ?? inferred.modelActionType,
1133
+ validationReason: params.auditMeta?.validationReason ?? params.reason ?? inferred.validationReason
1134
+ };
1135
+ params.log?.warn?.(
1136
+ `[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) : ""}`
1137
+ );
1138
+ return submitGameActionWithLog(
1139
+ fb,
1140
+ params.meta,
1141
+ params.wsClient,
1142
+ params.log,
1143
+ "backend_fallback",
1144
+ params.rawResponse || void 0,
1145
+ auditMeta
1146
+ );
1015
1147
  }
1016
1148
  var runtimeClients = /* @__PURE__ */ new Map();
1017
1149
  function setRuntimeClient(accountKey, client) {
@@ -1160,22 +1292,36 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1160
1292
  accountKey,
1161
1293
  ackStore,
1162
1294
  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
1295
  const isGameEvent = envelope.metadata?.gameEvent === true;
1170
1296
  const gameMeta = isGameEvent ? envelope.metadata : null;
1171
1297
  let gameSubmitted = false;
1172
1298
  let gameFallbackReason = null;
1299
+ let gameModelActionType;
1300
+ let gameValidationReason;
1301
+ let gameModelActionRejected;
1173
1302
  const gameBuffer = [];
1174
1303
  if (isGameEvent && gameMeta) {
1175
1304
  ctx.log?.info?.(
1176
1305
  `[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
1306
  );
1178
1307
  }
1308
+ const runtime = getCoolclawRuntime();
1309
+ if (!runtime?.channel) {
1310
+ logInboundDrop({ log: ctx.log?.warn?.bind(ctx.log) ?? (() => {
1311
+ }), channel: "coolclaw", reason: "runtime not available; skipping dispatch" });
1312
+ if (isGameEvent && gameMeta) {
1313
+ const submitted = await submitBackendFallbackWithLog({
1314
+ meta: gameMeta,
1315
+ wsClient,
1316
+ log: ctx.log,
1317
+ reason: "runtime_not_available"
1318
+ });
1319
+ if (!submitted) {
1320
+ throw new Error("game fallback submit failed: runtime_not_available");
1321
+ }
1322
+ }
1323
+ return;
1324
+ }
1179
1325
  try {
1180
1326
  const isGroup = envelope.conversationId.startsWith("group:");
1181
1327
  const peer = isGroup ? { kind: "group", id: envelope.group?.groupId ?? envelope.conversationId } : { kind: "direct", id: envelope.conversationId };
@@ -1275,20 +1421,34 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1275
1421
  if ("error" in parsed) return;
1276
1422
  const validation = validateAgentAction(parsed, gameMeta.agentTask);
1277
1423
  if (!validation.ok) {
1424
+ gameModelActionRejected = true;
1425
+ gameModelActionType = parsed.actionType;
1426
+ gameValidationReason = validation.reason;
1427
+ gameFallbackReason = validation.reason;
1278
1428
  ctx.log?.warn?.(
1279
1429
  `[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
1430
  );
1281
1431
  return;
1282
1432
  }
1283
- gameSubmitted = true;
1284
- await submitGameActionWithLog(
1433
+ const submitted = await submitGameActionWithLog(
1285
1434
  validation.action,
1286
1435
  gameMeta,
1287
1436
  wsClient,
1288
1437
  ctx.log,
1289
1438
  "llm",
1290
- full
1439
+ full,
1440
+ {
1441
+ modelActionRejected: false,
1442
+ modelActionType: validation.action.actionType
1443
+ }
1291
1444
  );
1445
+ if (submitted) {
1446
+ gameSubmitted = true;
1447
+ } else {
1448
+ gameFallbackReason = `llm_action_submit_failed:${validation.action.actionType}`;
1449
+ gameModelActionRejected = false;
1450
+ gameModelActionType = validation.action.actionType;
1451
+ }
1292
1452
  return;
1293
1453
  }
1294
1454
  try {
@@ -1311,7 +1471,9 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1311
1471
  }
1312
1472
  });
1313
1473
  if (isGameEvent && gameMeta && !gameSubmitted) {
1314
- gameFallbackReason = "no_valid_action_in_llm_output";
1474
+ if (!gameFallbackReason) {
1475
+ gameFallbackReason = "no_valid_action_in_llm_output";
1476
+ }
1315
1477
  }
1316
1478
  } catch (err) {
1317
1479
  const errMsg = err instanceof Error ? err.message : String(err);
@@ -1328,29 +1490,34 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1328
1490
  }
1329
1491
  if (isGameEvent && gameMeta && !gameSubmitted) {
1330
1492
  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
1493
  const rawResponse = gameBuffer.join("");
1339
- ctx.log?.warn?.(
1340
- `[GAME-ACTION] backend fallback eventType=${gameMeta.eventType} eventId=${gameMeta.eventId} reason=${gameFallbackReason ?? "unknown"} promptPolicyVersion=${gameMeta.promptPolicyVersion ?? ""} renderedPromptHash=${gameMeta.renderedPromptHash ?? ""} rawResponseHash=${rawResponse ? sha256Hex(rawResponse) : ""}`
1341
- );
1342
- await submitGameActionWithLog(
1343
- fb,
1344
- gameMeta,
1494
+ const inferred = inferRejectedModelAction(rawResponse, gameMeta.agentTask);
1495
+ gameModelActionRejected = gameModelActionRejected ?? inferred.modelActionRejected;
1496
+ gameModelActionType = gameModelActionType ?? inferred.modelActionType;
1497
+ gameValidationReason = gameValidationReason ?? inferred.validationReason;
1498
+ if (!gameFallbackReason || gameFallbackReason === "no_valid_action_in_llm_output") {
1499
+ gameFallbackReason = gameValidationReason ?? inferred.validationReason ?? gameFallbackReason;
1500
+ }
1501
+ const submitted = await submitBackendFallbackWithLog({
1502
+ meta: gameMeta,
1345
1503
  wsClient,
1346
- ctx.log,
1347
- "backend_fallback",
1348
- rawResponse || void 0
1349
- );
1504
+ log: ctx.log,
1505
+ reason: gameFallbackReason ?? gameValidationReason ?? inferred.validationReason ?? "unknown",
1506
+ rawResponse: rawResponse || void 0,
1507
+ auditMeta: {
1508
+ modelActionRejected: gameModelActionRejected,
1509
+ modelActionType: gameModelActionType,
1510
+ validationReason: gameFallbackReason ?? gameValidationReason ?? inferred.validationReason
1511
+ }
1512
+ });
1513
+ if (!submitted) {
1514
+ throw new Error(`game fallback submit failed: ${gameFallbackReason ?? "unknown"}`);
1515
+ }
1350
1516
  } catch (fbErr) {
1351
1517
  ctx.log?.error?.(
1352
1518
  `[GAME-ACTION] fallback submit threw eventId=${gameMeta.eventId} err=${fbErr instanceof Error ? fbErr.message : String(fbErr)}`
1353
1519
  );
1520
+ throw fbErr;
1354
1521
  }
1355
1522
  }
1356
1523
  }
@@ -1362,6 +1529,7 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1362
1529
  } catch (err) {
1363
1530
  logAckFailure({ log: ctx.log?.warn?.bind(ctx.log) ?? (() => {
1364
1531
  }), channel: "coolclaw", error: err });
1532
+ throw err;
1365
1533
  }
1366
1534
  }
1367
1535
  });
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  index_default
3
- } from "./chunk-GKJ5WKGA.js";
4
- import "./chunk-DVHZ22OJ.js";
3
+ } from "./chunk-EE5BK65S.js";
4
+ import "./chunk-QKB2R55C.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-GKJ5WKGA.js";
4
- import "./chunk-DVHZ22OJ.js";
3
+ } from "./chunk-EE5BK65S.js";
4
+ import "./chunk-QKB2R55C.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-DVHZ22OJ.js";
3
+ } from "./chunk-QKB2R55C.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.3",
3
+ "version": "1.0.5",
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-6gIxAI4i3xJ4LpzViGi1KCJvc20JA/3Ob4OVdClgHCPydrkDqzOPbhSea4tpSlkUB5mWh/iEEzKhMcKqM24vVg==",
75
+ "expectedIntegrity": "sha512-6q6+KPGtAkHBZGsHgqEXJtnwLdVoZNLrZNbxZmzKisZvItoE2y+Acl9v3bNtqtEK/DR5NLahyloy7FMRVt9Upg==",
75
76
  "defaultChoice": "npm",
76
77
  "minHostVersion": ">=2026.3.22"
77
78
  },
@@ -99,4 +100,4 @@
99
100
  "pluginSdkVersion": "2026.4.29"
100
101
  }
101
102
  }
102
- }
103
+ }