@coolclaw/coolclaw 0.4.0 → 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.
|
@@ -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
|
-
|
|
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
|
|
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}
|
|
261
|
-
|
|
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,89 +854,6 @@ 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 {
|
|
@@ -1181,29 +1131,32 @@ function logAckFailure(params) {
|
|
|
1181
1131
|
const target = params.target ? ` target=${params.target}` : "";
|
|
1182
1132
|
params.log(`${params.channel} ack cleanup failed${target}: ${String(params.error)}`);
|
|
1183
1133
|
}
|
|
1184
|
-
async function submitGameActionWithLog(action, meta,
|
|
1185
|
-
if (!
|
|
1186
|
-
log?.error?.(`[GAME-ACTION] submit skipped:
|
|
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}`);
|
|
1187
1137
|
return;
|
|
1188
1138
|
}
|
|
1189
1139
|
log?.info?.(
|
|
1190
1140
|
`[GAME-ACTION] submit start source=${source} eventType=${meta.eventType} actionType=${action.actionType} gameId=${meta.gameId} turnSeq=${meta.turnSeq} eventId=${meta.eventId}`
|
|
1191
1141
|
);
|
|
1192
|
-
const
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
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);
|
|
1205
1158
|
log?.error?.(
|
|
1206
|
-
`[GAME-ACTION] submit failed source=${source} eventId=${meta.eventId}
|
|
1159
|
+
`[GAME-ACTION] submit failed source=${source} eventId=${meta.eventId} elapsedMs=${Date.now() - start} err=${errMsg}`
|
|
1207
1160
|
);
|
|
1208
1161
|
}
|
|
1209
1162
|
}
|
|
@@ -1460,8 +1413,7 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
1460
1413
|
await submitGameActionWithLog(
|
|
1461
1414
|
parsed,
|
|
1462
1415
|
gameMeta,
|
|
1463
|
-
|
|
1464
|
-
token,
|
|
1416
|
+
wsClient,
|
|
1465
1417
|
ctx.log,
|
|
1466
1418
|
"llm"
|
|
1467
1419
|
);
|
|
@@ -1505,8 +1457,7 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
1505
1457
|
await submitGameActionWithLog(
|
|
1506
1458
|
fb,
|
|
1507
1459
|
gameMeta,
|
|
1508
|
-
|
|
1509
|
-
token,
|
|
1460
|
+
wsClient,
|
|
1510
1461
|
ctx.log,
|
|
1511
1462
|
"fallback"
|
|
1512
1463
|
);
|
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.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "OpenClaw native channel plugin for Riddle/CoolClaw chat.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"runtimeSetupEntry": "./dist/setup-entry.js",
|
|
60
60
|
"install": {
|
|
61
61
|
"npmSpec": "@coolclaw/coolclaw",
|
|
62
|
-
"expectedIntegrity": "sha512-
|
|
62
|
+
"expectedIntegrity": "sha512-flpAooDuQuAIeKDQ0oPAS7bzy+yN8ql1tKxE7HfvMgSgJiIiwrFXWQ3+Uqnc6q0Ls0MAncnCSwFC5TzxNX+CVw==",
|
|
63
63
|
"defaultChoice": "npm",
|
|
64
64
|
"minHostVersion": ">=2026.3.22"
|
|
65
65
|
},
|