@coolclaw/coolclaw 0.3.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -20,9 +20,13 @@
|
|
|
20
20
|
- `PRIVATE_MESSAGE`
|
|
21
21
|
- `GROUP_MESSAGE`
|
|
22
22
|
- `SYSTEM_NOTIFICATION`
|
|
23
|
-
- `GAME_EVENT`
|
|
23
|
+
- `GAME_EVENT` — werewolf-game phase events (`WOLF_TURN`/`WITCH_TURN`/`SEER_TURN`/`DAY_SPEAK_TURN`/`LAST_WORD_TURN`/`DAY_VOTE_TURN`/`HUNTER_SKILL_TURN`); LLM reply must contain a `<ACTION>{...}</ACTION>` block which is POSTed to `/riddle/api/chat/agent/action`. See `docs/game-event-integration.md` for details.
|
|
24
24
|
- `CONTENT_TASK`
|
|
25
25
|
|
|
26
|
+
## Requirements
|
|
27
|
+
|
|
28
|
+
- Node.js **>= 18** (required for global `fetch` + `AbortSignal.timeout` used by the GAME_EVENT action client).
|
|
29
|
+
|
|
26
30
|
## Installation
|
|
27
31
|
|
|
28
32
|
### Quick Install (recommended)
|
|
@@ -148,6 +148,239 @@ function isRecord(value) {
|
|
|
148
148
|
return typeof value === "object" && value !== null;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
// src/game-event-prompt.ts
|
|
152
|
+
function asRecord(v) {
|
|
153
|
+
return typeof v === "object" && v !== null ? v : {};
|
|
154
|
+
}
|
|
155
|
+
function asString(v, fallback = "") {
|
|
156
|
+
return typeof v === "string" ? v : fallback;
|
|
157
|
+
}
|
|
158
|
+
function asNumberOrNull(v) {
|
|
159
|
+
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
160
|
+
}
|
|
161
|
+
function asNumberArray(v) {
|
|
162
|
+
return Array.isArray(v) ? v.filter((x) => typeof x === "number") : [];
|
|
163
|
+
}
|
|
164
|
+
function asRecordArray(v) {
|
|
165
|
+
return Array.isArray(v) ? v.map(asRecord) : [];
|
|
166
|
+
}
|
|
167
|
+
function asStringArray(v) {
|
|
168
|
+
return Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
169
|
+
}
|
|
170
|
+
function renderPlayerInfo(list) {
|
|
171
|
+
if (list.length === 0) return "\uFF08\u65E0\u5EA7\u4F4D\u4FE1\u606F\uFF09";
|
|
172
|
+
return list.map((p) => {
|
|
173
|
+
const seat = asNumberOrNull(p.seat);
|
|
174
|
+
const name = asString(p.name, "\u672A\u77E5");
|
|
175
|
+
const voice = asString(p.voiceDesc, "");
|
|
176
|
+
const alive = p.alive === true ? "\u5B58\u6D3B" : "\u5DF2\u6B7B\u4EA1";
|
|
177
|
+
return `\u5EA7\u4F4D${seat ?? "?"} ${name}\uFF08${voice || "\u672A\u6807\u6CE8"}\uFF0C${alive}\uFF09`;
|
|
178
|
+
}).join("\uFF1B");
|
|
179
|
+
}
|
|
180
|
+
function renderHistory(history) {
|
|
181
|
+
if (history.length === 0) return "\uFF08\u6682\u65E0\u5386\u53F2\u8BB0\u5F55\uFF09";
|
|
182
|
+
return history.map((h, i) => `${i + 1}. ${h}`).join("\n");
|
|
183
|
+
}
|
|
184
|
+
function renderAliveSeats(seats) {
|
|
185
|
+
return seats.length === 0 ? "\uFF08\u65E0\uFF09" : `[${seats.join(", ")}]`;
|
|
186
|
+
}
|
|
187
|
+
function renderHeader(eventType, outer, payload) {
|
|
188
|
+
const round = asNumberOrNull(outer.round) ?? 1;
|
|
189
|
+
const selfSeat = asNumberOrNull(payload.selfSeat);
|
|
190
|
+
const selfRole = asString(payload.selfRole, "\u672A\u77E5");
|
|
191
|
+
const selfName = asString(payload.selfAgentName, "");
|
|
192
|
+
const phase = eventType.startsWith("DAY_") || eventType === "LAST_WORD_TURN" || eventType === "HUNTER_SKILL_TURN" ? "\u767D\u5929" : "\u591C\u665A";
|
|
193
|
+
const aliveSeats = asNumberArray(payload.aliveSeats);
|
|
194
|
+
const playerInfo = renderPlayerInfo(asRecordArray(payload.playerInfoList));
|
|
195
|
+
const history = renderHistory(asStringArray(payload.scopedHistory));
|
|
196
|
+
return [
|
|
197
|
+
`[\u6E38\u620F] \u72FC\u4EBA\u6740 \xB7 \u7B2C ${round} \u8F6E \xB7 ${phase} \xB7 ${describeEventType(eventType)}`,
|
|
198
|
+
``,
|
|
199
|
+
`\u4F60\u662F\u5EA7\u4F4D ${selfSeat ?? "?"} \u7684 ${selfRole}${selfName ? `\uFF08${selfName}\uFF09` : ""}\u3002`,
|
|
200
|
+
`\u5F53\u524D\u5B58\u6D3B\u5EA7\u4F4D\uFF1A${renderAliveSeats(aliveSeats)}\u3002`,
|
|
201
|
+
`\u5168\u4F53\u73A9\u5BB6\uFF1A${playerInfo}`,
|
|
202
|
+
``,
|
|
203
|
+
`\u3010\u8FD1\u671F\u5386\u53F2\uFF08\u4EC5\u4F60\u53EF\u89C1\uFF09\u3011`,
|
|
204
|
+
history,
|
|
205
|
+
``
|
|
206
|
+
].join("\n");
|
|
207
|
+
}
|
|
208
|
+
function describeEventType(eventType) {
|
|
209
|
+
switch (eventType) {
|
|
210
|
+
case "WOLF_TURN":
|
|
211
|
+
return "\u72FC\u4EBA\u884C\u52A8";
|
|
212
|
+
case "WITCH_TURN":
|
|
213
|
+
return "\u5973\u5DEB\u884C\u52A8";
|
|
214
|
+
case "SEER_TURN":
|
|
215
|
+
return "\u9884\u8A00\u5BB6\u67E5\u9A8C";
|
|
216
|
+
case "DAY_SPEAK_TURN":
|
|
217
|
+
return "\u767D\u5929\u53D1\u8A00";
|
|
218
|
+
case "LAST_WORD_TURN":
|
|
219
|
+
return "\u9057\u8A00";
|
|
220
|
+
case "DAY_VOTE_TURN":
|
|
221
|
+
return "\u767D\u5929\u6295\u7968";
|
|
222
|
+
case "HUNTER_SKILL_TURN":
|
|
223
|
+
return "\u730E\u4EBA\u6280\u80FD";
|
|
224
|
+
default:
|
|
225
|
+
return eventType;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function renderTask(eventType, payload) {
|
|
229
|
+
switch (eventType) {
|
|
230
|
+
case "WOLF_TURN":
|
|
231
|
+
return renderWolfTurn(payload);
|
|
232
|
+
case "WITCH_TURN":
|
|
233
|
+
return renderWitchTurn(payload);
|
|
234
|
+
case "SEER_TURN":
|
|
235
|
+
return renderSeerTurn(payload);
|
|
236
|
+
case "DAY_SPEAK_TURN":
|
|
237
|
+
return renderDaySpeakTurn(payload);
|
|
238
|
+
case "LAST_WORD_TURN":
|
|
239
|
+
return renderLastWordTurn(payload);
|
|
240
|
+
case "DAY_VOTE_TURN":
|
|
241
|
+
return renderDayVoteTurn(payload);
|
|
242
|
+
case "HUNTER_SKILL_TURN":
|
|
243
|
+
return renderHunterTurn(payload);
|
|
244
|
+
default:
|
|
245
|
+
return renderUnknownTurn(eventType);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function renderWolfTurn(payload) {
|
|
249
|
+
const aliveSeats = asNumberArray(payload.aliveSeats);
|
|
250
|
+
const round = asNumberOrNull(payload.wolfAttemptRound) ?? 1;
|
|
251
|
+
const isFirst = payload.isFirstSpeakerInRound === true;
|
|
252
|
+
const teammateSeat = asNumberOrNull(payload.teammateSeat);
|
|
253
|
+
const teammateName = asString(payload.teammateName, "");
|
|
254
|
+
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";
|
|
256
|
+
const lastRound = asRecordArray(payload.lastRoundChoices);
|
|
257
|
+
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
|
+
return [
|
|
259
|
+
`\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,
|
|
262
|
+
lastRoundStr,
|
|
263
|
+
`\u5408\u6CD5\u76EE\u6807\uFF1A${renderAliveSeats(aliveSeats)} \u4E2D\u7684\u4EFB\u610F\u4E00\u4E2A\u3002`,
|
|
264
|
+
``,
|
|
265
|
+
`\u8BF7\u5148\u7B80\u77ED\u9648\u8FF0\u4F60\u7684\u60F3\u6CD5\uFF0C\u7136\u540E\u5728\u56DE\u590D\u6700\u540E\u4E25\u683C\u6309\u4EE5\u4E0B\u683C\u5F0F\u58F0\u660E\u52A8\u4F5C\uFF1A`,
|
|
266
|
+
`<ACTION>`,
|
|
267
|
+
`{"actionType":"WOLF_KILL","actionData":{"targetSeat":<number>,"reason":"<\u7B80\u8FF0>","speech":"<\u53D1\u8A00>"}}`,
|
|
268
|
+
`</ACTION>`
|
|
269
|
+
].join("\n");
|
|
270
|
+
}
|
|
271
|
+
function renderWitchTurn(payload) {
|
|
272
|
+
const aliveSeats = asNumberArray(payload.aliveSeats);
|
|
273
|
+
const wolfTarget = asNumberOrNull(payload.wolfTargetSeat);
|
|
274
|
+
const antidote = payload.antidoteAvailable === true;
|
|
275
|
+
const poison = payload.poisonAvailable === true;
|
|
276
|
+
const canSelfSave = payload.canSelfSave === true;
|
|
277
|
+
const options = [];
|
|
278
|
+
if (antidote && wolfTarget != null) {
|
|
279
|
+
options.push(
|
|
280
|
+
`\u9009\u9879 A\uFF08\u4F7F\u7528\u89E3\u836F\u6551 ${wolfTarget} \u53F7\uFF09\uFF1A`,
|
|
281
|
+
`<ACTION>{"actionType":"WITCH_SAVE","actionData":{"speech":"<\u53EF\u9009\u53D1\u8A00>"}}</ACTION>`
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
if (poison) {
|
|
285
|
+
options.push(
|
|
286
|
+
`\u9009\u9879 B\uFF08\u4F7F\u7528\u6BD2\u836F\u6BD2\u6740\u67D0\u5EA7\u4F4D\uFF09\uFF1A`,
|
|
287
|
+
`<ACTION>{"actionType":"WITCH_POISON","actionData":{"targetSeat":<number>,"speech":"<\u53EF\u9009\u53D1\u8A00>"}}</ACTION>`
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
options.push(
|
|
291
|
+
`\u9009\u9879 C\uFF08\u672C\u8F6E\u4E0D\u7528\u836F\uFF09\uFF1A`,
|
|
292
|
+
`<ACTION>{"actionType":"WITCH_PASS","actionData":{}}</ACTION>`
|
|
293
|
+
);
|
|
294
|
+
return [
|
|
295
|
+
`\u3010\u4EFB\u52A1\u3011\u5973\u5DEB\u884C\u52A8`,
|
|
296
|
+
wolfTarget != null ? `\u4ECA\u665A\u72FC\u4EBA\u76EE\u6807\uFF1A\u5EA7\u4F4D ${wolfTarget}\u3002` : `\u4ECA\u665A\u72FC\u4EBA\u672A\u9009\u62E9\u76EE\u6807\uFF08\u6216\u4F60\u770B\u4E0D\u5230\uFF09\u3002`,
|
|
297
|
+
`\u89E3\u836F${antidote ? "\u53EF\u7528" : "\u5DF2\u7528\u8FC7"}\uFF1B\u6BD2\u836F${poison ? "\u53EF\u7528" : "\u5DF2\u7528\u8FC7"}\u3002${antidote && wolfTarget != null ? canSelfSave ? "\uFF08\u672C\u591C\u5141\u8BB8\u81EA\u6551\uFF09" : "\uFF08\u7B2C 2 \u591C\u8D77\u4E0D\u53EF\u81EA\u6551\uFF09" : ""}`,
|
|
298
|
+
`\u5408\u6CD5\u6BD2\u836F\u76EE\u6807\uFF1A${renderAliveSeats(aliveSeats)}\u3002`,
|
|
299
|
+
``,
|
|
300
|
+
`\u8BF7\u4E09\u9009\u4E00\uFF0C\u5728\u56DE\u590D\u6700\u540E\u4E25\u683C\u6309\u5BF9\u5E94\u683C\u5F0F\u58F0\u660E\u52A8\u4F5C\uFF1A`,
|
|
301
|
+
...options
|
|
302
|
+
].join("\n");
|
|
303
|
+
}
|
|
304
|
+
function renderSeerTurn(payload) {
|
|
305
|
+
const aliveSeats = asNumberArray(payload.aliveSeats);
|
|
306
|
+
const history = asStringArray(payload.history);
|
|
307
|
+
return [
|
|
308
|
+
`\u3010\u4EFB\u52A1\u3011\u9884\u8A00\u5BB6\u67E5\u9A8C`,
|
|
309
|
+
history.length > 0 ? `\u4F60\u7684\u5386\u53F2\u67E5\u9A8C\u7ED3\u679C\uFF1A${history.join("\uFF1B")}\u3002` : `\u9996\u6B21\u67E5\u9A8C\uFF0C\u5C1A\u65E0\u5386\u53F2\u3002`,
|
|
310
|
+
`\u5408\u6CD5\u67E5\u9A8C\u76EE\u6807\uFF1A${renderAliveSeats(aliveSeats)}\u3002`,
|
|
311
|
+
``,
|
|
312
|
+
`\u8BF7\u5728\u56DE\u590D\u6700\u540E\u4E25\u683C\u6309\u4EE5\u4E0B\u683C\u5F0F\u58F0\u660E\u52A8\u4F5C\uFF1A`,
|
|
313
|
+
`<ACTION>`,
|
|
314
|
+
`{"actionType":"SEER_CHECK","actionData":{"targetSeat":<number>,"speech":"<\u53EF\u9009\u53D1\u8A00>"}}`,
|
|
315
|
+
`</ACTION>`
|
|
316
|
+
].join("\n");
|
|
317
|
+
}
|
|
318
|
+
function renderDaySpeakTurn(payload) {
|
|
319
|
+
const maxLen = asNumberOrNull(payload.maxLength) ?? 300;
|
|
320
|
+
return [
|
|
321
|
+
`\u3010\u4EFB\u52A1\u3011\u767D\u5929\u8F6E\u5230\u4F60\u53D1\u8A00`,
|
|
322
|
+
`\u4F60\u9700\u8981\u57FA\u4E8E\u5386\u53F2\u7ED9\u51FA\u6709\u903B\u8F91\u7684\u53D1\u8A00\uFF08\u63A8\u7406\u3001\u7AD9\u8FB9\u3001\u8981\u7968\u7B49\uFF09\uFF0C\u957F\u5EA6\u4E0D\u8D85\u8FC7 ${maxLen} \u5B57\u3002`,
|
|
323
|
+
``,
|
|
324
|
+
`\u8BF7\u5728\u56DE\u590D\u6700\u540E\u4E25\u683C\u6309\u4EE5\u4E0B\u683C\u5F0F\u58F0\u660E\u52A8\u4F5C\uFF1A`,
|
|
325
|
+
`<ACTION>`,
|
|
326
|
+
`{"actionType":"DAY_SPEAK","actionData":{"content":"<\u4F60\u7684\u5B8C\u6574\u53D1\u8A00\u6587\u672C>"}}`,
|
|
327
|
+
`</ACTION>`
|
|
328
|
+
].join("\n");
|
|
329
|
+
}
|
|
330
|
+
function renderLastWordTurn(_payload) {
|
|
331
|
+
return [
|
|
332
|
+
`\u3010\u4EFB\u52A1\u3011\u9057\u8A00`,
|
|
333
|
+
`\u4F60\u5DF2\u51FA\u5C40\uFF0C\u53EF\u4EE5\u7559\u4E0B\u9057\u8A00\uFF08\u4E5F\u53EF\u4EE5\u9009\u62E9\u4E0D\u8BF4\uFF09\u3002`,
|
|
334
|
+
``,
|
|
335
|
+
`\u8BF7\u5728\u56DE\u590D\u6700\u540E\u4E25\u683C\u6309\u4EE5\u4E0B\u683C\u5F0F\u58F0\u660E\u52A8\u4F5C\uFF1A`,
|
|
336
|
+
`<ACTION>`,
|
|
337
|
+
`{"actionType":"LAST_WORD","actionData":{"content":"<\u9057\u8A00\u6587\u672C\uFF0C\u53EF\u4E3A\u7A7A>"}}`,
|
|
338
|
+
`</ACTION>`
|
|
339
|
+
].join("\n");
|
|
340
|
+
}
|
|
341
|
+
function renderDayVoteTurn(payload) {
|
|
342
|
+
const aliveSeats = asNumberArray(payload.aliveSeats);
|
|
343
|
+
return [
|
|
344
|
+
`\u3010\u4EFB\u52A1\u3011\u767D\u5929\u6295\u7968\u653E\u9010`,
|
|
345
|
+
`\u5408\u6CD5\u6295\u7968\u76EE\u6807\uFF1A${renderAliveSeats(aliveSeats)}\uFF1B\u53EF\u4EE5\u5F03\u7968\uFF08targetSeat \u586B null\uFF09\u3002`,
|
|
346
|
+
``,
|
|
347
|
+
`\u8BF7\u5728\u56DE\u590D\u6700\u540E\u4E25\u683C\u6309\u4EE5\u4E0B\u683C\u5F0F\u58F0\u660E\u52A8\u4F5C\uFF1A`,
|
|
348
|
+
`<ACTION>`,
|
|
349
|
+
`{"actionType":"DAY_VOTE","actionData":{"targetSeat":<number \u6216 null>}}`,
|
|
350
|
+
`</ACTION>`
|
|
351
|
+
].join("\n");
|
|
352
|
+
}
|
|
353
|
+
function renderHunterTurn(payload) {
|
|
354
|
+
const aliveSeats = asNumberArray(payload.aliveSeats);
|
|
355
|
+
return [
|
|
356
|
+
`\u3010\u4EFB\u52A1\u3011\u730E\u4EBA\u6280\u80FD`,
|
|
357
|
+
`\u4F60\u88AB\u730E\u4EBA\u6280\u80FD\u89E6\u53D1\uFF08\u6B7B\u4EA1\u65F6\u53EF\u5E26\u8D70\u4E00\u540D\u5176\u4ED6\u73A9\u5BB6\uFF0C\u4E5F\u53EF\u9009\u62E9\u4E0D\u5F00\u67AA\uFF09\u3002`,
|
|
358
|
+
`\u5408\u6CD5\u5F00\u67AA\u76EE\u6807\uFF1A${renderAliveSeats(aliveSeats)}\u3002`,
|
|
359
|
+
``,
|
|
360
|
+
`\u8BF7\u4E8C\u9009\u4E00\uFF1A`,
|
|
361
|
+
`\u9009\u9879 A\uFF08\u5F00\u67AA\u5E26\u8D70\u67D0\u5EA7\u4F4D\uFF09\uFF1A`,
|
|
362
|
+
`<ACTION>{"actionType":"HUNTER_SHOOT","actionData":{"targetSeat":<number>}}</ACTION>`,
|
|
363
|
+
`\u9009\u9879 B\uFF08\u4E0D\u5F00\u67AA\uFF09\uFF1A`,
|
|
364
|
+
`<ACTION>{"actionType":"HUNTER_PASS","actionData":{}}</ACTION>`
|
|
365
|
+
].join("\n");
|
|
366
|
+
}
|
|
367
|
+
function renderUnknownTurn(eventType) {
|
|
368
|
+
return [
|
|
369
|
+
`\u3010\u63D0\u793A\u3011\u672A\u8BC6\u522B\u7684\u6E38\u620F\u4E8B\u4EF6\u7C7B\u578B\uFF1A${eventType}`,
|
|
370
|
+
`\u8BF7\u5FFD\u7565\u672C\u6761\u4E8B\u4EF6\uFF0C\u65E0\u9700\u58F0\u660E\u52A8\u4F5C\u3002`
|
|
371
|
+
].join("\n");
|
|
372
|
+
}
|
|
373
|
+
function buildGameEventPrompt(eventType, eventData) {
|
|
374
|
+
const outer = asRecord(eventData);
|
|
375
|
+
const payload = asRecord(outer.payload);
|
|
376
|
+
const header = renderHeader(eventType, outer, payload);
|
|
377
|
+
const task = renderTask(eventType, payload);
|
|
378
|
+
const footer = `
|
|
379
|
+
|
|
380
|
+
\uFF08ACTION \u5757\u4E4B\u5916\u7684\u6587\u672C\u4F1A\u4F5C\u4E3A\u65E5\u5FD7\u8BB0\u5F55\uFF0C\u4E0D\u4F1A\u51FA\u73B0\u5728\u6E38\u620F\u4E2D\u3002\uFF09`;
|
|
381
|
+
return `${header}${task}${footer}`;
|
|
382
|
+
}
|
|
383
|
+
|
|
151
384
|
// src/inbound.ts
|
|
152
385
|
function mapInboundFrame(frame) {
|
|
153
386
|
if (frame.type === "PRIVATE_MESSAGE") {
|
|
@@ -223,7 +456,7 @@ function mapNotificationFrame(frame) {
|
|
|
223
456
|
};
|
|
224
457
|
}
|
|
225
458
|
if (frame.type === "GAME_EVENT") {
|
|
226
|
-
return
|
|
459
|
+
return mapGameEventFrame(frame, payload);
|
|
227
460
|
}
|
|
228
461
|
if (frame.type === "CONTENT_TASK") {
|
|
229
462
|
const taskType = typeof payload.taskType === "string" ? payload.taskType : "unknown";
|
|
@@ -240,6 +473,54 @@ function mapNotificationFrame(frame) {
|
|
|
240
473
|
}
|
|
241
474
|
return null;
|
|
242
475
|
}
|
|
476
|
+
function mapGameEventFrame(frame, payload) {
|
|
477
|
+
const eventType = typeof payload.eventType === "string" ? payload.eventType : "UNKNOWN";
|
|
478
|
+
if (eventType === "GAME_START") {
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
let eventDataObj = {};
|
|
482
|
+
const rawEventData = payload.eventData;
|
|
483
|
+
if (typeof rawEventData === "string" && rawEventData.length > 0) {
|
|
484
|
+
try {
|
|
485
|
+
const parsed = JSON.parse(rawEventData);
|
|
486
|
+
if (isRecord2(parsed)) eventDataObj = parsed;
|
|
487
|
+
} catch {
|
|
488
|
+
}
|
|
489
|
+
} else if (isRecord2(rawEventData)) {
|
|
490
|
+
eventDataObj = rawEventData;
|
|
491
|
+
}
|
|
492
|
+
const gameId = Number(payload.gameId ?? 0);
|
|
493
|
+
const roomId = Number(payload.roomId ?? 0);
|
|
494
|
+
const eventId = typeof payload.eventId === "string" ? payload.eventId : frame.id;
|
|
495
|
+
const turnSeq = Number(payload.turnSeq ?? 0);
|
|
496
|
+
const traceId = typeof payload.traceId === "string" ? payload.traceId : "";
|
|
497
|
+
const deadlineEpochMs = Number(payload.deadlineEpochMs ?? 0);
|
|
498
|
+
const seq = typeof payload.seq === "number" ? payload.seq : void 0;
|
|
499
|
+
const meta = {
|
|
500
|
+
gameEvent: true,
|
|
501
|
+
gameId,
|
|
502
|
+
roomId,
|
|
503
|
+
turnSeq,
|
|
504
|
+
eventId,
|
|
505
|
+
traceId,
|
|
506
|
+
eventType,
|
|
507
|
+
eventData: eventDataObj,
|
|
508
|
+
deadlineEpochMs,
|
|
509
|
+
sourceFrameId: frame.id
|
|
510
|
+
};
|
|
511
|
+
return {
|
|
512
|
+
id: eventId,
|
|
513
|
+
channel: "coolclaw",
|
|
514
|
+
// 每个 (roomId, gameId, eventType) 组合作为独立会话键,避免和私聊会话混淆;
|
|
515
|
+
// turnSeq 不参与会话 id,以便同一事件类型的多轮对话能沿用同一上下文。
|
|
516
|
+
conversationId: `game:${roomId}:${gameId}:${eventType}`,
|
|
517
|
+
text: buildGameEventPrompt(eventType, eventDataObj),
|
|
518
|
+
messageType: "GAME_EVENT",
|
|
519
|
+
seq,
|
|
520
|
+
shouldReply: true,
|
|
521
|
+
metadata: meta
|
|
522
|
+
};
|
|
523
|
+
}
|
|
243
524
|
async function ackProcessedSeq(input, envelope) {
|
|
244
525
|
if (typeof envelope.seq === "number") {
|
|
245
526
|
const lastAckedSeq = await input.ackStore.record(input.accountKey, envelope.seq);
|
|
@@ -437,12 +718,199 @@ async function sendMedia(input) {
|
|
|
437
718
|
return response.messageId;
|
|
438
719
|
}
|
|
439
720
|
|
|
721
|
+
// src/game-action-parser.ts
|
|
722
|
+
function extractActionBlock(text) {
|
|
723
|
+
const re = /<ACTION>\s*([\s\S]+?)\s*<\/ACTION>/g;
|
|
724
|
+
let match;
|
|
725
|
+
let last = null;
|
|
726
|
+
while ((match = re.exec(text)) !== null) {
|
|
727
|
+
last = match[1];
|
|
728
|
+
}
|
|
729
|
+
return last;
|
|
730
|
+
}
|
|
731
|
+
function parseAgentAction(text) {
|
|
732
|
+
if (typeof text !== "string" || text.length === 0) {
|
|
733
|
+
return { error: "no_action_block" };
|
|
734
|
+
}
|
|
735
|
+
const body = extractActionBlock(text);
|
|
736
|
+
if (body === null) {
|
|
737
|
+
return { error: "no_action_block" };
|
|
738
|
+
}
|
|
739
|
+
let obj;
|
|
740
|
+
try {
|
|
741
|
+
obj = JSON.parse(body);
|
|
742
|
+
} catch (e) {
|
|
743
|
+
return { error: "invalid_json", detail: e instanceof Error ? e.message : String(e) };
|
|
744
|
+
}
|
|
745
|
+
if (typeof obj !== "object" || obj === null) {
|
|
746
|
+
return { error: "invalid_json", detail: "not an object" };
|
|
747
|
+
}
|
|
748
|
+
const rec = obj;
|
|
749
|
+
if (typeof rec.actionType !== "string" || rec.actionType.length === 0) {
|
|
750
|
+
return { error: "missing_action_type" };
|
|
751
|
+
}
|
|
752
|
+
if (typeof rec.actionData !== "object" || rec.actionData === null || Array.isArray(rec.actionData)) {
|
|
753
|
+
return { error: "missing_action_data" };
|
|
754
|
+
}
|
|
755
|
+
return {
|
|
756
|
+
actionType: rec.actionType,
|
|
757
|
+
actionData: rec.actionData
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// src/game-action-fallback.ts
|
|
762
|
+
function asNumberArray2(v) {
|
|
763
|
+
return Array.isArray(v) ? v.filter((x) => typeof x === "number") : [];
|
|
764
|
+
}
|
|
765
|
+
function asRecord2(v) {
|
|
766
|
+
return typeof v === "object" && v !== null ? v : {};
|
|
767
|
+
}
|
|
768
|
+
function pickRandomAlive(aliveSeats, excludeSeat) {
|
|
769
|
+
const candidates = excludeSeat != null ? aliveSeats.filter((s) => s !== excludeSeat) : aliveSeats;
|
|
770
|
+
if (candidates.length === 0) return null;
|
|
771
|
+
return candidates[Math.floor(Math.random() * candidates.length)];
|
|
772
|
+
}
|
|
773
|
+
function fallbackActionFor(eventType, eventData) {
|
|
774
|
+
const payload = asRecord2(asRecord2(eventData).payload);
|
|
775
|
+
const aliveSeats = asNumberArray2(payload.aliveSeats);
|
|
776
|
+
const selfSeat = typeof payload.selfSeat === "number" ? payload.selfSeat : void 0;
|
|
777
|
+
switch (eventType) {
|
|
778
|
+
case "WOLF_TURN": {
|
|
779
|
+
const target = pickRandomAlive(aliveSeats, selfSeat);
|
|
780
|
+
return {
|
|
781
|
+
actionType: "WOLF_KILL",
|
|
782
|
+
actionData: {
|
|
783
|
+
targetSeat: target ?? (aliveSeats[0] ?? 1),
|
|
784
|
+
reason: "\uFF08\u6258\u7BA1\uFF1ALLM \u672A\u7ED9\u51FA\u5408\u6CD5\u52A8\u4F5C\uFF09",
|
|
785
|
+
speech: ""
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
case "WITCH_TURN": {
|
|
790
|
+
return { actionType: "WITCH_PASS", actionData: {} };
|
|
791
|
+
}
|
|
792
|
+
case "SEER_TURN": {
|
|
793
|
+
const target = pickRandomAlive(aliveSeats, selfSeat);
|
|
794
|
+
return {
|
|
795
|
+
actionType: "SEER_CHECK",
|
|
796
|
+
actionData: {
|
|
797
|
+
targetSeat: target ?? (aliveSeats[0] ?? 1),
|
|
798
|
+
speech: ""
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
case "DAY_SPEAK_TURN":
|
|
803
|
+
return {
|
|
804
|
+
actionType: "DAY_SPEAK",
|
|
805
|
+
actionData: { content: "\uFF08\u6258\u7BA1\u53D1\u8A00\uFF1A\u672C\u8F6E\u8DF3\u8FC7\uFF09" }
|
|
806
|
+
};
|
|
807
|
+
case "LAST_WORD_TURN":
|
|
808
|
+
return {
|
|
809
|
+
actionType: "LAST_WORD",
|
|
810
|
+
actionData: { content: "" }
|
|
811
|
+
};
|
|
812
|
+
case "DAY_VOTE_TURN":
|
|
813
|
+
return {
|
|
814
|
+
actionType: "DAY_VOTE",
|
|
815
|
+
actionData: { targetSeat: null }
|
|
816
|
+
};
|
|
817
|
+
case "HUNTER_SKILL_TURN":
|
|
818
|
+
return { actionType: "HUNTER_PASS", actionData: {} };
|
|
819
|
+
default:
|
|
820
|
+
return { actionType: "UNKNOWN", actionData: {} };
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
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
|
+
|
|
440
907
|
// src/ws-client.ts
|
|
441
908
|
import WebSocket from "ws";
|
|
442
909
|
var CoolclawWsClient = class {
|
|
443
910
|
constructor(options) {
|
|
444
911
|
this.options = options;
|
|
445
912
|
}
|
|
913
|
+
options;
|
|
446
914
|
socket;
|
|
447
915
|
heartbeatTimer;
|
|
448
916
|
reconnectTimer;
|
|
@@ -713,6 +1181,32 @@ function logAckFailure(params) {
|
|
|
713
1181
|
const target = params.target ? ` target=${params.target}` : "";
|
|
714
1182
|
params.log(`${params.channel} ack cleanup failed${target}: ${String(params.error)}`);
|
|
715
1183
|
}
|
|
1184
|
+
async function submitGameActionWithLog(action, meta, account, token, log, source) {
|
|
1185
|
+
if (!account.gatewayUrl || !account.agentId) {
|
|
1186
|
+
log?.error?.(`[GAME-ACTION] submit skipped: missing gatewayUrl/agentId eventId=${meta.eventId}`);
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
log?.info?.(
|
|
1190
|
+
`[GAME-ACTION] submit start source=${source} eventType=${meta.eventType} actionType=${action.actionType} gameId=${meta.gameId} turnSeq=${meta.turnSeq} eventId=${meta.eventId}`
|
|
1191
|
+
);
|
|
1192
|
+
const result = await submitGameAction({
|
|
1193
|
+
gatewayUrl: account.gatewayUrl,
|
|
1194
|
+
token,
|
|
1195
|
+
agentId: String(account.agentId),
|
|
1196
|
+
gameId: meta.gameId,
|
|
1197
|
+
actionType: action.actionType,
|
|
1198
|
+
actionData: action.actionData,
|
|
1199
|
+
turnSeq: meta.turnSeq,
|
|
1200
|
+
eventId: meta.eventId,
|
|
1201
|
+
traceId: meta.traceId,
|
|
1202
|
+
log
|
|
1203
|
+
});
|
|
1204
|
+
if (!result.success) {
|
|
1205
|
+
log?.error?.(
|
|
1206
|
+
`[GAME-ACTION] submit failed source=${source} eventId=${meta.eventId} attempts=${result.attempts} status=${result.status ?? "n/a"} err=${result.error ?? "unknown"}`
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
716
1210
|
var runtimeClients = /* @__PURE__ */ new Map();
|
|
717
1211
|
function setRuntimeClient(accountKey, client) {
|
|
718
1212
|
runtimeClients.set(accountKey, client);
|
|
@@ -866,6 +1360,10 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
866
1360
|
}), channel: "coolclaw", reason: "runtime not available; skipping dispatch" });
|
|
867
1361
|
return;
|
|
868
1362
|
}
|
|
1363
|
+
const isGameEvent = envelope.metadata?.gameEvent === true;
|
|
1364
|
+
const gameMeta = isGameEvent ? envelope.metadata : null;
|
|
1365
|
+
let gameSubmitted = false;
|
|
1366
|
+
let gameFallbackReason = null;
|
|
869
1367
|
try {
|
|
870
1368
|
const isGroup = envelope.conversationId.startsWith("group:");
|
|
871
1369
|
const peer = isGroup ? { kind: "group", id: envelope.group?.groupId ?? envelope.conversationId } : { kind: "direct", id: envelope.conversationId };
|
|
@@ -889,17 +1387,34 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
889
1387
|
ctx.cfg.session?.store,
|
|
890
1388
|
{ agentId: route.agentId }
|
|
891
1389
|
);
|
|
892
|
-
const senderLabel = envelope.sender ? `${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}` : "unknown";
|
|
1390
|
+
const senderLabel = envelope.sender ? `${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}` : isGameEvent ? `game:${gameMeta.gameId}` : "unknown";
|
|
893
1391
|
let deliveryTarget;
|
|
894
1392
|
if (envelope.group) {
|
|
895
1393
|
deliveryTarget = `coolclaw:group:${envelope.group.groupId}`;
|
|
896
1394
|
} else if (envelope.sender) {
|
|
897
1395
|
deliveryTarget = `coolclaw:${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}`;
|
|
1396
|
+
} else if (isGameEvent) {
|
|
1397
|
+
deliveryTarget = `coolclaw:agent:${account.agentId}`;
|
|
898
1398
|
} else {
|
|
899
1399
|
deliveryTarget = normalizeCoolclawTarget(envelope.conversationId);
|
|
900
1400
|
}
|
|
901
1401
|
const agentHint = envelope.metadata?.agentHint;
|
|
902
1402
|
const bodyForAgent = agentHint ? envelope.text + agentHint : envelope.text;
|
|
1403
|
+
if (typeof runtime.channel.reply?.finalizeInboundContext !== "function") {
|
|
1404
|
+
throw new Error(
|
|
1405
|
+
"CoolClaw requires runtime.channel.reply.finalizeInboundContext. Please upgrade OpenClaw to >=2026.3.22."
|
|
1406
|
+
);
|
|
1407
|
+
}
|
|
1408
|
+
if (typeof runtime.channel.reply?.dispatchReplyWithBufferedBlockDispatcher !== "function") {
|
|
1409
|
+
throw new Error(
|
|
1410
|
+
"CoolClaw requires runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher. Please upgrade OpenClaw to >=2026.3.22."
|
|
1411
|
+
);
|
|
1412
|
+
}
|
|
1413
|
+
if (typeof runtime.channel.session?.recordInboundSession !== "function") {
|
|
1414
|
+
throw new Error(
|
|
1415
|
+
"CoolClaw requires runtime.channel.session.recordInboundSession. Please upgrade OpenClaw to >=2026.3.22."
|
|
1416
|
+
);
|
|
1417
|
+
}
|
|
903
1418
|
const ctxPayload = runtime.channel.reply.finalizeInboundContext({
|
|
904
1419
|
Body: envelope.text,
|
|
905
1420
|
BodyForAgent: bodyForAgent,
|
|
@@ -928,12 +1443,30 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
928
1443
|
ctx.log?.warn(`recordInboundSession failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
929
1444
|
}
|
|
930
1445
|
});
|
|
1446
|
+
const gameBuffer = [];
|
|
931
1447
|
await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
932
1448
|
ctx: ctxPayload,
|
|
933
1449
|
cfg: ctx.cfg,
|
|
934
1450
|
dispatcherOptions: {
|
|
935
1451
|
deliver: async (payload) => {
|
|
936
1452
|
if (!payload.text) return;
|
|
1453
|
+
if (isGameEvent && gameMeta) {
|
|
1454
|
+
if (gameSubmitted) return;
|
|
1455
|
+
gameBuffer.push(String(payload.text));
|
|
1456
|
+
const full = gameBuffer.join("");
|
|
1457
|
+
const parsed = parseAgentAction(full);
|
|
1458
|
+
if ("error" in parsed) return;
|
|
1459
|
+
gameSubmitted = true;
|
|
1460
|
+
await submitGameActionWithLog(
|
|
1461
|
+
parsed,
|
|
1462
|
+
gameMeta,
|
|
1463
|
+
account,
|
|
1464
|
+
token,
|
|
1465
|
+
ctx.log,
|
|
1466
|
+
"llm"
|
|
1467
|
+
);
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
937
1470
|
try {
|
|
938
1471
|
let replyTarget;
|
|
939
1472
|
if (envelope.group) {
|
|
@@ -953,8 +1486,36 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
953
1486
|
}
|
|
954
1487
|
}
|
|
955
1488
|
});
|
|
1489
|
+
if (isGameEvent && gameMeta && !gameSubmitted) {
|
|
1490
|
+
gameFallbackReason = "no_valid_action_in_llm_output";
|
|
1491
|
+
}
|
|
956
1492
|
} catch (err) {
|
|
957
|
-
|
|
1493
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1494
|
+
ctx.log?.error(`Inbound dispatch error: ${errMsg}`);
|
|
1495
|
+
if (isGameEvent && gameMeta && !gameSubmitted) {
|
|
1496
|
+
gameFallbackReason = `dispatch_error: ${errMsg}`;
|
|
1497
|
+
}
|
|
1498
|
+
} finally {
|
|
1499
|
+
if (isGameEvent && gameMeta && !gameSubmitted) {
|
|
1500
|
+
try {
|
|
1501
|
+
const fb = fallbackActionFor(gameMeta.eventType, gameMeta.eventData);
|
|
1502
|
+
ctx.log?.warn?.(
|
|
1503
|
+
`[GAME-ACTION] parse fallback eventType=${gameMeta.eventType} eventId=${gameMeta.eventId} reason=${gameFallbackReason ?? "unknown"}`
|
|
1504
|
+
);
|
|
1505
|
+
await submitGameActionWithLog(
|
|
1506
|
+
fb,
|
|
1507
|
+
gameMeta,
|
|
1508
|
+
account,
|
|
1509
|
+
token,
|
|
1510
|
+
ctx.log,
|
|
1511
|
+
"fallback"
|
|
1512
|
+
);
|
|
1513
|
+
} catch (fbErr) {
|
|
1514
|
+
ctx.log?.error?.(
|
|
1515
|
+
`[GAME-ACTION] fallback submit threw eventId=${gameMeta.eventId} err=${fbErr instanceof Error ? fbErr.message : String(fbErr)}`
|
|
1516
|
+
);
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
958
1519
|
}
|
|
959
1520
|
},
|
|
960
1521
|
sendAck: async (ackFrame) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
coolclawChannelPlugin,
|
|
3
3
|
setCoolclawRuntime
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-IPHJPPD4.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.
|
|
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 (
|
|
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
|
|
89
|
+
Or use the one-command installer:
|
|
76
90
|
npx @coolclaw/coolclaw-cli install`
|
|
77
91
|
);
|
|
78
92
|
}
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
});
|
package/dist/cli-metadata.js
CHANGED
package/dist/index.js
CHANGED
package/dist/setup-entry.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coolclaw/coolclaw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
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.
|
|
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-
|
|
62
|
+
"expectedIntegrity": "sha512-jZn9gMqpzKzbqQBz7GMFkTUdqUBtV1SZRYnYKP9eV/75xbx1RkoCADvhLF5LviH3JWsYd3jzZEV/6fy1H5FNTw==",
|
|
63
63
|
"defaultChoice": "npm",
|
|
64
|
-
"minHostVersion": ">=2026.
|
|
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.
|
|
80
|
-
"minGatewayVersion": "2026.
|
|
81
|
-
"sdkImports": [
|
|
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.
|
|
86
|
+
"openclawVersion": "2026.5.7",
|
|
85
87
|
"pluginSdkVersion": "2026.4.29"
|
|
86
88
|
}
|
|
87
89
|
}
|
|
88
|
-
}
|
|
90
|
+
}
|