@coolclaw/coolclaw 0.3.4 → 0.4.1

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.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  coolclawChannelPlugin,
3
3
  setCoolclawRuntime
4
- } from "./chunk-UEJ6XOZB.js";
4
+ } from "./chunk-XVB6UKBR.js";
5
5
  import {
6
6
  runCoolclawSetup
7
7
  } from "./chunk-A54AF634.js";
@@ -62,29 +62,41 @@ function splitCsv(value) {
62
62
  }
63
63
 
64
64
  // src/compat.ts
65
- var SUPPORTED_HOST_MIN = "2026.4.24";
65
+ var SUPPORTED_HOST_MIN = "2026.3.22";
66
+ var MAX_TESTED_VERSION = "2026.5.7";
67
+ function parseVersion(v) {
68
+ const clean = v.split("-")[0].replace(/\s*\(.*\)/, "").trim();
69
+ const [y, m, d] = clean.split(".").map(Number);
70
+ return [y || 0, m || 0, d || 0];
71
+ }
72
+ function cmp(a, b) {
73
+ const [ay, am, ad] = parseVersion(a);
74
+ const [by, bm, bd] = parseVersion(b);
75
+ if (ay !== by) return ay > by ? 1 : -1;
76
+ if (am !== bm) return am > bm ? 1 : -1;
77
+ if (ad !== bd) return ad > bd ? 1 : -1;
78
+ return 0;
79
+ }
66
80
  function assertHostCompatibility(hostVersion) {
67
81
  if (!hostVersion || hostVersion === "unknown") return;
68
- if (isHostVersionSupported(hostVersion)) return;
82
+ if (cmp(hostVersion, SUPPORTED_HOST_MIN) >= 0) return;
69
83
  throw new Error(
70
84
  `This version of @coolclaw/coolclaw requires OpenClaw >=${SUPPORTED_HOST_MIN}, but found ${hostVersion}. Please upgrade OpenClaw:
71
85
  npm install -g openclaw@latest
72
86
  Then reinstall the plugin:
73
87
  openclaw plugins install @coolclaw/coolclaw
74
88
 
75
- Or use the one-command installer (requires @coolclaw/coolclaw-cli published to npm):
89
+ Or use the one-command installer:
76
90
  npx @coolclaw/coolclaw-cli install`
77
91
  );
78
92
  }
79
- function isHostVersionSupported(hostVersion) {
80
- const clean = hostVersion.split("-")[0];
81
- const host = clean.split(".").map(Number);
82
- const min = SUPPORTED_HOST_MIN.split(".").map(Number);
83
- for (let i = 0; i < 3; i++) {
84
- if (host[i] > min[i]) return true;
85
- if (host[i] < min[i]) return false;
93
+ function advisoryHostCompatibility(hostVersion) {
94
+ if (!hostVersion || hostVersion === "unknown") return;
95
+ if (cmp(hostVersion, MAX_TESTED_VERSION) > 0) {
96
+ console.warn(
97
+ `[coolclaw] Host version ${hostVersion} is newer than the latest tested version (${MAX_TESTED_VERSION}). Proceeding without version gate; please report incompatibilities.`
98
+ );
86
99
  }
87
- return true;
88
100
  }
89
101
 
90
102
  // index.ts
@@ -112,6 +124,7 @@ var entry = defineChannelPluginEntry({
112
124
  },
113
125
  registerFull(api) {
114
126
  assertHostCompatibility(api.runtime?.version);
127
+ advisoryHostCompatibility(api.runtime?.version);
115
128
  setCoolclawRuntime(api.runtime);
116
129
  }
117
130
  });
@@ -167,19 +167,25 @@ function asRecordArray(v) {
167
167
  function asStringArray(v) {
168
168
  return Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
169
169
  }
170
- function renderPlayerInfo(list) {
170
+ function renderPlayerInfo(list, selfSeat) {
171
171
  if (list.length === 0) return "\uFF08\u65E0\u5EA7\u4F4D\u4FE1\u606F\uFF09";
172
172
  return list.map((p) => {
173
173
  const seat = asNumberOrNull(p.seat);
174
- const name = asString(p.name, "\u672A\u77E5");
175
- const voice = asString(p.voiceDesc, "");
176
174
  const alive = p.alive === true ? "\u5B58\u6D3B" : "\u5DF2\u6B7B\u4EA1";
177
- return `\u5EA7\u4F4D${seat ?? "?"} ${name}\uFF08${voice || "\u672A\u6807\u6CE8"}\uFF0C${alive}\uFF09`;
175
+ if (seat != null && selfSeat != null && seat === selfSeat) {
176
+ const name = asString(p.name, "\u672A\u77E5");
177
+ const voice = asString(p.voiceDesc, "");
178
+ return `\u5EA7\u4F4D${seat}\uFF08\u4F60\u81EA\u5DF1\uFF0C${name}${voice ? `\uFF0C${voice}` : ""}\uFF0C${alive}\uFF09`;
179
+ }
180
+ return `\u5EA7\u4F4D${seat ?? "?"}\uFF08${alive}\uFF09`;
178
181
  }).join("\uFF1B");
179
182
  }
183
+ function stripAudioSuffix(line) {
184
+ return line.replace(/\s*:audio=https?:\/\/\S+/g, "").replace(/\s*:cb=[^\s|]+/g, "");
185
+ }
180
186
  function renderHistory(history) {
181
187
  if (history.length === 0) return "\uFF08\u6682\u65E0\u5386\u53F2\u8BB0\u5F55\uFF09";
182
- return history.map((h, i) => `${i + 1}. ${h}`).join("\n");
188
+ return history.map((h, i) => `${i + 1}. ${stripAudioSuffix(h)}`).join("\n");
183
189
  }
184
190
  function renderAliveSeats(seats) {
185
191
  return seats.length === 0 ? "\uFF08\u65E0\uFF09" : `[${seats.join(", ")}]`;
@@ -191,7 +197,7 @@ function renderHeader(eventType, outer, payload) {
191
197
  const selfName = asString(payload.selfAgentName, "");
192
198
  const phase = eventType.startsWith("DAY_") || eventType === "LAST_WORD_TURN" || eventType === "HUNTER_SKILL_TURN" ? "\u767D\u5929" : "\u591C\u665A";
193
199
  const aliveSeats = asNumberArray(payload.aliveSeats);
194
- const playerInfo = renderPlayerInfo(asRecordArray(payload.playerInfoList));
200
+ const playerInfo = renderPlayerInfo(asRecordArray(payload.playerInfoList), selfSeat);
195
201
  const history = renderHistory(asStringArray(payload.scopedHistory));
196
202
  return [
197
203
  `[\u6E38\u620F] \u72FC\u4EBA\u6740 \xB7 \u7B2C ${round} \u8F6E \xB7 ${phase} \xB7 ${describeEventType(eventType)}`,
@@ -250,15 +256,26 @@ function renderWolfTurn(payload) {
250
256
  const round = asNumberOrNull(payload.wolfAttemptRound) ?? 1;
251
257
  const isFirst = payload.isFirstSpeakerInRound === true;
252
258
  const teammateSeat = asNumberOrNull(payload.teammateSeat);
253
- const teammateName = asString(payload.teammateName, "");
254
259
  const tp = asRecord(payload.teammateProposal);
255
- const teammateProposalStr = tp.targetSeat != null ? `\u540C\u4F34\uFF08\u5EA7\u4F4D ${tp.seat}\uFF09\u672C\u8F6E\u5DF2\u63D0\u8BAE\u51FB\u6740\u5EA7\u4F4D ${tp.targetSeat}${tp.reason ? `\uFF0C\u7406\u7531\uFF1A${tp.reason}` : ""}${tp.speech ? `\uFF0C\u53D1\u8A00\uFF1A"${tp.speech}"` : ""}\u3002` : isFirst ? "\u4F60\u662F\u672C\u8F6E\u9996\u4F4D\u53D1\u8A00\u7684\u72FC\u4EBA\u3002" : "\u540C\u4F34\u5C1A\u672A\u53D1\u8A00\u3002";
260
+ const tpSeat = asNumberOrNull(tp.seat);
261
+ const tpTarget = asNumberOrNull(tp.targetSeat);
262
+ const tpSpeech = asString(tp.speech, "").trim();
263
+ let teammateBlock;
264
+ if (tpTarget != null) {
265
+ const speechLine = tpSpeech ? `
266
+ ===== \u72FC\u961F\u53CB\u53D1\u8A00\u5F00\u59CB =====
267
+ ${tpSpeech}
268
+ ===== \u72FC\u961F\u53CB\u53D1\u8A00\u7ED3\u675F =====` : "";
269
+ teammateBlock = `\u540C\u4F34\uFF08\u5EA7\u4F4D ${tpSeat ?? "?"}\uFF09\u672C\u8F6E\u5DF2\u63D0\u8BAE\u51FB\u6740\u5EA7\u4F4D ${tpTarget}\u3002${speechLine}`;
270
+ } else {
271
+ teammateBlock = isFirst ? "\u4F60\u662F\u672C\u8F6E\u9996\u4F4D\u53D1\u8A00\u7684\u72FC\u4EBA\u3002" : "\u540C\u4F34\u5C1A\u672A\u53D1\u8A00\u3002";
272
+ }
256
273
  const lastRound = asRecordArray(payload.lastRoundChoices);
257
274
  const lastRoundStr = lastRound.length > 0 ? `\u4E0A\u4E00\u8F6E\u6295\u7968\u8BB0\u5F55\uFF1A${lastRound.map((c) => `\u5EA7\u4F4D${c.seat}\u2192\u5EA7\u4F4D${c.targetSeat}`).join("\uFF1B")}\u3002` : "";
258
275
  return [
259
276
  `\u3010\u4EFB\u52A1\u3011\u72FC\u4EBA\u6740\u4EBA\u534F\u5546\uFF08\u7B2C ${round} \u8F6E\uFF09`,
260
- teammateSeat != null ? `\u4F60\u7684\u72FC\u540C\u4F34\uFF1A\u5EA7\u4F4D ${teammateSeat} ${teammateName}\u3002` : "\u4F60\u662F\u72EC\u72FC\u3002",
261
- teammateProposalStr,
277
+ teammateSeat != null ? `\u4F60\u7684\u72FC\u540C\u4F34\uFF1A\u5EA7\u4F4D ${teammateSeat}\u3002` : "\u4F60\u662F\u72EC\u72FC\u3002",
278
+ teammateBlock,
262
279
  lastRoundStr,
263
280
  `\u5408\u6CD5\u76EE\u6807\uFF1A${renderAliveSeats(aliveSeats)} \u4E2D\u7684\u4EFB\u610F\u4E00\u4E2A\u3002`,
264
281
  ``,
@@ -717,6 +734,22 @@ async function sendMedia(input) {
717
734
  }
718
735
  return response.messageId;
719
736
  }
737
+ async function sendGameAction(input) {
738
+ const frame = createFrame("GAME_ACTION", {
739
+ gameId: input.gameId,
740
+ actionType: input.actionType,
741
+ actionData: input.actionData,
742
+ // AgentActionRequest.timestamp 契约为 String
743
+ timestamp: String(Date.now()),
744
+ turnSeq: input.turnSeq,
745
+ eventId: input.eventId,
746
+ traceId: input.traceId
747
+ });
748
+ const response = await input.client.request(frame);
749
+ if (response.ok === false) {
750
+ throw new Error(response.error?.message ?? "CoolClaw game action failed");
751
+ }
752
+ }
720
753
 
721
754
  // src/game-action-parser.ts
722
755
  function extractActionBlock(text) {
@@ -821,95 +854,13 @@ function fallbackActionFor(eventType, eventData) {
821
854
  }
822
855
  }
823
856
 
824
- // src/game-action-client.ts
825
- function buildUrl(gatewayUrl) {
826
- const base = gatewayUrl.replace(/\/+$/, "");
827
- const tail = base.endsWith("/riddle") ? "/api/chat/agent/action" : "/riddle/api/chat/agent/action";
828
- return `${base}${tail}`;
829
- }
830
- function buildBody(input) {
831
- return JSON.stringify({
832
- gameId: input.gameId,
833
- actionType: input.actionType,
834
- actionData: input.actionData,
835
- // AgentActionRequest.timestamp 契约为 String
836
- timestamp: String(Date.now()),
837
- turnSeq: input.turnSeq,
838
- eventId: input.eventId,
839
- traceId: input.traceId
840
- });
841
- }
842
- function sleep(ms) {
843
- return new Promise((resolve) => setTimeout(resolve, ms));
844
- }
845
- async function submitGameAction(input) {
846
- const url = buildUrl(input.gatewayUrl);
847
- const body = buildBody(input);
848
- const timeoutMs = input.timeoutMs ?? 5e3;
849
- const maxRetries = input.maxRetries ?? 1;
850
- const fetchImpl = input.fetchImpl ?? fetch;
851
- const start = Date.now();
852
- let attempts = 0;
853
- let lastError;
854
- let lastStatus;
855
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
856
- attempts++;
857
- const ac = new AbortController();
858
- const timer = setTimeout(() => ac.abort(), timeoutMs);
859
- try {
860
- const resp = await fetchImpl(url, {
861
- method: "POST",
862
- headers: {
863
- "Content-Type": "application/json",
864
- Authorization: `Bearer ${input.token}`,
865
- "X-User-Id": input.agentId,
866
- "X-User-Type": "AGENT"
867
- },
868
- body,
869
- signal: ac.signal
870
- });
871
- lastStatus = resp.status;
872
- if (resp.ok) {
873
- const elapsedMs2 = Date.now() - start;
874
- input.log?.info?.(
875
- `[GAME-ACTION] post ok gameId=${input.gameId} eventId=${input.eventId} actionType=${input.actionType} status=${resp.status} elapsedMs=${elapsedMs2} attempts=${attempts}`
876
- );
877
- return { success: true, status: resp.status, elapsedMs: elapsedMs2, attempts };
878
- }
879
- let text = "";
880
- try {
881
- text = (await resp.text()).slice(0, 500);
882
- } catch {
883
- }
884
- lastError = `http_${resp.status}: ${text}`;
885
- input.log?.warn?.(
886
- `[GAME-ACTION] post non-2xx gameId=${input.gameId} eventId=${input.eventId} status=${resp.status} attempt=${attempt} body=${text}`
887
- );
888
- } catch (err) {
889
- lastError = err instanceof Error ? err.message : String(err);
890
- input.log?.warn?.(
891
- `[GAME-ACTION] post network error gameId=${input.gameId} eventId=${input.eventId} attempt=${attempt} err=${lastError}`
892
- );
893
- } finally {
894
- clearTimeout(timer);
895
- }
896
- if (attempt < maxRetries) {
897
- await sleep(500 * Math.pow(2, attempt));
898
- }
899
- }
900
- const elapsedMs = Date.now() - start;
901
- input.log?.error?.(
902
- `[GAME-ACTION] post failed gameId=${input.gameId} eventId=${input.eventId} actionType=${input.actionType} status=${lastStatus ?? "n/a"} attempts=${attempts} elapsedMs=${elapsedMs} err=${lastError ?? "unknown"}`
903
- );
904
- return { success: false, status: lastStatus, error: lastError, elapsedMs, attempts };
905
- }
906
-
907
857
  // src/ws-client.ts
908
858
  import WebSocket from "ws";
909
859
  var CoolclawWsClient = class {
910
860
  constructor(options) {
911
861
  this.options = options;
912
862
  }
863
+ options;
913
864
  socket;
914
865
  heartbeatTimer;
915
866
  reconnectTimer;
@@ -1180,29 +1131,32 @@ function logAckFailure(params) {
1180
1131
  const target = params.target ? ` target=${params.target}` : "";
1181
1132
  params.log(`${params.channel} ack cleanup failed${target}: ${String(params.error)}`);
1182
1133
  }
1183
- async function submitGameActionWithLog(action, meta, account, token, log, source) {
1184
- if (!account.gatewayUrl || !account.agentId) {
1185
- log?.error?.(`[GAME-ACTION] submit skipped: missing gatewayUrl/agentId eventId=${meta.eventId}`);
1134
+ async function submitGameActionWithLog(action, meta, wsClient, log, source) {
1135
+ if (!wsClient.isConnected()) {
1136
+ log?.error?.(`[GAME-ACTION] submit skipped: ws not connected eventId=${meta.eventId}`);
1186
1137
  return;
1187
1138
  }
1188
1139
  log?.info?.(
1189
1140
  `[GAME-ACTION] submit start source=${source} eventType=${meta.eventType} actionType=${action.actionType} gameId=${meta.gameId} turnSeq=${meta.turnSeq} eventId=${meta.eventId}`
1190
1141
  );
1191
- const result = await submitGameAction({
1192
- gatewayUrl: account.gatewayUrl,
1193
- token,
1194
- agentId: String(account.agentId),
1195
- gameId: meta.gameId,
1196
- actionType: action.actionType,
1197
- actionData: action.actionData,
1198
- turnSeq: meta.turnSeq,
1199
- eventId: meta.eventId,
1200
- traceId: meta.traceId,
1201
- log
1202
- });
1203
- if (!result.success) {
1142
+ const start = Date.now();
1143
+ try {
1144
+ await sendGameAction({
1145
+ client: wsClient,
1146
+ gameId: meta.gameId,
1147
+ actionType: action.actionType,
1148
+ actionData: action.actionData,
1149
+ turnSeq: meta.turnSeq,
1150
+ eventId: meta.eventId,
1151
+ traceId: meta.traceId
1152
+ });
1153
+ log?.info?.(
1154
+ `[GAME-ACTION] submit ok source=${source} gameId=${meta.gameId} eventId=${meta.eventId} elapsedMs=${Date.now() - start}`
1155
+ );
1156
+ } catch (err) {
1157
+ const errMsg = err instanceof Error ? err.message : String(err);
1204
1158
  log?.error?.(
1205
- `[GAME-ACTION] submit failed source=${source} eventId=${meta.eventId} attempts=${result.attempts} status=${result.status ?? "n/a"} err=${result.error ?? "unknown"}`
1159
+ `[GAME-ACTION] submit failed source=${source} eventId=${meta.eventId} elapsedMs=${Date.now() - start} err=${errMsg}`
1206
1160
  );
1207
1161
  }
1208
1162
  }
@@ -1399,6 +1353,21 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1399
1353
  }
1400
1354
  const agentHint = envelope.metadata?.agentHint;
1401
1355
  const bodyForAgent = agentHint ? envelope.text + agentHint : envelope.text;
1356
+ if (typeof runtime.channel.reply?.finalizeInboundContext !== "function") {
1357
+ throw new Error(
1358
+ "CoolClaw requires runtime.channel.reply.finalizeInboundContext. Please upgrade OpenClaw to >=2026.3.22."
1359
+ );
1360
+ }
1361
+ if (typeof runtime.channel.reply?.dispatchReplyWithBufferedBlockDispatcher !== "function") {
1362
+ throw new Error(
1363
+ "CoolClaw requires runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher. Please upgrade OpenClaw to >=2026.3.22."
1364
+ );
1365
+ }
1366
+ if (typeof runtime.channel.session?.recordInboundSession !== "function") {
1367
+ throw new Error(
1368
+ "CoolClaw requires runtime.channel.session.recordInboundSession. Please upgrade OpenClaw to >=2026.3.22."
1369
+ );
1370
+ }
1402
1371
  const ctxPayload = runtime.channel.reply.finalizeInboundContext({
1403
1372
  Body: envelope.text,
1404
1373
  BodyForAgent: bodyForAgent,
@@ -1444,8 +1413,7 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1444
1413
  await submitGameActionWithLog(
1445
1414
  parsed,
1446
1415
  gameMeta,
1447
- account,
1448
- token,
1416
+ wsClient,
1449
1417
  ctx.log,
1450
1418
  "llm"
1451
1419
  );
@@ -1489,8 +1457,7 @@ var coolclawChannelPlugin = createChatChannelPlugin({
1489
1457
  await submitGameActionWithLog(
1490
1458
  fb,
1491
1459
  gameMeta,
1492
- account,
1493
- token,
1460
+ wsClient,
1494
1461
  ctx.log,
1495
1462
  "fallback"
1496
1463
  );
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  index_default
3
- } from "./chunk-MRR7E57D.js";
4
- import "./chunk-UEJ6XOZB.js";
3
+ } from "./chunk-QZL3FKVL.js";
4
+ import "./chunk-XVB6UKBR.js";
5
5
  import "./chunk-A54AF634.js";
6
6
  import "./chunk-Q3NF4NWE.js";
7
7
 
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  index_default
3
- } from "./chunk-MRR7E57D.js";
4
- import "./chunk-UEJ6XOZB.js";
3
+ } from "./chunk-QZL3FKVL.js";
4
+ import "./chunk-XVB6UKBR.js";
5
5
  import "./chunk-A54AF634.js";
6
6
  import "./chunk-Q3NF4NWE.js";
7
7
  export {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  coolclawChannelPlugin
3
- } from "./chunk-UEJ6XOZB.js";
3
+ } from "./chunk-XVB6UKBR.js";
4
4
  import "./chunk-Q3NF4NWE.js";
5
5
 
6
6
  // setup-entry.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coolclaw/coolclaw",
3
- "version": "0.3.4",
3
+ "version": "0.4.1",
4
4
  "description": "OpenClaw native channel plugin for Riddle/CoolClaw chat.",
5
5
  "type": "module",
6
6
  "files": [
@@ -41,7 +41,7 @@
41
41
  "vitest": "latest"
42
42
  },
43
43
  "peerDependencies": {
44
- "openclaw": ">=2026.4.24"
44
+ "openclaw": ">=2026.3.22 <2027"
45
45
  },
46
46
  "peerDependenciesMeta": {
47
47
  "openclaw": {
@@ -59,9 +59,9 @@
59
59
  "runtimeSetupEntry": "./dist/setup-entry.js",
60
60
  "install": {
61
61
  "npmSpec": "@coolclaw/coolclaw",
62
- "expectedIntegrity": "sha512-jZn9gMqpzKzbqQBz7GMFkTUdqUBtV1SZRYnYKP9eV/75xbx1RkoCADvhLF5LviH3JWsYd3jzZEV/6fy1H5FNTw==",
62
+ "expectedIntegrity": "sha512-flpAooDuQuAIeKDQ0oPAS7bzy+yN8ql1tKxE7HfvMgSgJiIiwrFXWQ3+Uqnc6q0Ls0MAncnCSwFC5TzxNX+CVw==",
63
63
  "defaultChoice": "npm",
64
- "minHostVersion": ">=2026.4.24"
64
+ "minHostVersion": ">=2026.3.22"
65
65
  },
66
66
  "channel": {
67
67
  "id": "coolclaw",
@@ -76,13 +76,15 @@
76
76
  }
77
77
  },
78
78
  "compat": {
79
- "pluginApi": ">=2026.4.24",
80
- "minGatewayVersion": "2026.4.24",
81
- "sdkImports": ["openclaw/plugin-sdk/core"]
79
+ "pluginApi": ">=2026.3.22",
80
+ "minGatewayVersion": "2026.3.22",
81
+ "sdkImports": [
82
+ "openclaw/plugin-sdk/core"
83
+ ]
82
84
  },
83
85
  "build": {
84
- "openclawVersion": "2026.5.6",
86
+ "openclawVersion": "2026.5.7",
85
87
  "pluginSdkVersion": "2026.4.29"
86
88
  }
87
89
  }
88
- }
90
+ }