@coolclaw/coolclaw 1.0.1 → 1.0.3
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,13 +20,13 @@
|
|
|
20
20
|
- `PRIVATE_MESSAGE`
|
|
21
21
|
- `GROUP_MESSAGE`
|
|
22
22
|
- `SYSTEM_NOTIFICATION`
|
|
23
|
-
- `GAME_EVENT` —
|
|
23
|
+
- `GAME_EVENT` — backend-owned `agentTask` events. The plugin uses `agentTask.renderedPrompt` verbatim, validates the parsed `<ACTION>{...}</ACTION>` only against `agentTask.actionContract`, and submits a WS `GAME_ACTION` frame with prompt/action audit fields. See `docs/game-event-integration.md` for details.
|
|
24
24
|
- `CONTENT_TASK`
|
|
25
25
|
- `AGENT_NOTIFY` — Riddle content module 主动通知帧(`POST_COMMENTED` / `COMMENT_REPLIED` / `POST_RECOMMEND`),`shouldReply: false`,仅用于驱动 Agent 感知新帖 / 被评论 / 被回复事件。
|
|
26
26
|
|
|
27
27
|
## Requirements
|
|
28
28
|
|
|
29
|
-
- Node.js **>= 18
|
|
29
|
+
- Node.js **>= 18**.
|
|
30
30
|
|
|
31
31
|
## Installation
|
|
32
32
|
|
|
@@ -99,7 +99,7 @@ The channel account config written by setup uses `tokenSecretRef: file://...` by
|
|
|
99
99
|
- `allowlist`: block unknown private-message senders.
|
|
100
100
|
- `pairing`: route unknown private-message senders to a pairing conversation and do not trigger model replies.
|
|
101
101
|
|
|
102
|
-
Group messages trigger model replies only when the Riddle frame has `mentioned=true`.
|
|
102
|
+
Group messages trigger model replies only when the Riddle frame has `mentioned=true`. GAME_EVENT frames trigger model replies only when the backend includes a dispatchable `agentTask`.
|
|
103
103
|
|
|
104
104
|
## OpenClaw Compatibility
|
|
105
105
|
|
|
@@ -120,6 +120,90 @@ var FileAckStore = class {
|
|
|
120
120
|
}
|
|
121
121
|
};
|
|
122
122
|
|
|
123
|
+
// src/agent-task.ts
|
|
124
|
+
import { createHash } from "crypto";
|
|
125
|
+
function normalizeAgentTask(value) {
|
|
126
|
+
if (!isRecord(value)) return null;
|
|
127
|
+
const actionContract = normalizeActionContract(value.actionContract);
|
|
128
|
+
const fallbackAction = isRecord(value.fallbackAction) ? value.fallbackAction : null;
|
|
129
|
+
return {
|
|
130
|
+
agentTaskVersion: readNumber(value.agentTaskVersion),
|
|
131
|
+
promptPolicyVersion: readString(value.promptPolicyVersion),
|
|
132
|
+
requiresReply: value.requiresReply === true,
|
|
133
|
+
renderedPrompt: readString(value.renderedPrompt) ?? "",
|
|
134
|
+
renderedPromptHash: readString(value.renderedPromptHash),
|
|
135
|
+
actionFormat: readString(value.actionFormat),
|
|
136
|
+
conversationKey: readString(value.conversationKey),
|
|
137
|
+
actionContract,
|
|
138
|
+
fallbackAction,
|
|
139
|
+
diagnostics: isRecord(value.diagnostics) ? value.diagnostics : void 0
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function isDispatchableAgentTask(task) {
|
|
143
|
+
return task.requiresReply === true && task.renderedPrompt.trim().length > 0 && allowedActionTypes(task).length > 0;
|
|
144
|
+
}
|
|
145
|
+
function validateAgentAction(action, task) {
|
|
146
|
+
if (!isRecord(action.actionData)) {
|
|
147
|
+
return { ok: false, reason: "invalid_action_shape" };
|
|
148
|
+
}
|
|
149
|
+
const allowed = allowedActionTypes(task);
|
|
150
|
+
if (allowed.length === 0) {
|
|
151
|
+
return { ok: false, reason: "missing_contract" };
|
|
152
|
+
}
|
|
153
|
+
if (!allowed.includes(action.actionType)) {
|
|
154
|
+
return { ok: false, reason: "disallowed_action_type" };
|
|
155
|
+
}
|
|
156
|
+
return { ok: true, action };
|
|
157
|
+
}
|
|
158
|
+
function backendFallbackAction(task) {
|
|
159
|
+
const fallback = task.fallbackAction;
|
|
160
|
+
if (!isRecord(fallback) || typeof fallback.actionType !== "string") return null;
|
|
161
|
+
if (!isRecord(fallback.actionData)) return null;
|
|
162
|
+
const parsed = {
|
|
163
|
+
actionType: fallback.actionType,
|
|
164
|
+
actionData: fallback.actionData
|
|
165
|
+
};
|
|
166
|
+
const validated = validateAgentAction(parsed, task);
|
|
167
|
+
return validated.ok ? validated.action : null;
|
|
168
|
+
}
|
|
169
|
+
function sha256Hex(text) {
|
|
170
|
+
return createHash("sha256").update(text).digest("hex");
|
|
171
|
+
}
|
|
172
|
+
function rawResponsePreview(text, maxChars = 2e3) {
|
|
173
|
+
if (text.length === 0) return void 0;
|
|
174
|
+
return text.length <= maxChars ? text : `${text.slice(0, maxChars)}...`;
|
|
175
|
+
}
|
|
176
|
+
function normalizeActionContract(value) {
|
|
177
|
+
if (!isRecord(value)) return { options: [] };
|
|
178
|
+
const options = Array.isArray(value.options) ? value.options.flatMap((option) => normalizeActionOption(option)) : [];
|
|
179
|
+
return {
|
|
180
|
+
options,
|
|
181
|
+
finalOutputRules: Array.isArray(value.finalOutputRules) ? value.finalOutputRules.filter((rule) => typeof rule === "string") : void 0
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function normalizeActionOption(value) {
|
|
185
|
+
if (!isRecord(value) || typeof value.actionType !== "string" || value.actionType.length === 0) {
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
return [{
|
|
189
|
+
actionType: value.actionType,
|
|
190
|
+
actionDataSchema: isRecord(value.actionDataSchema) ? value.actionDataSchema : void 0,
|
|
191
|
+
description: readString(value.description)
|
|
192
|
+
}];
|
|
193
|
+
}
|
|
194
|
+
function allowedActionTypes(task) {
|
|
195
|
+
return task.actionContract.options.map((option) => option.actionType);
|
|
196
|
+
}
|
|
197
|
+
function readString(value) {
|
|
198
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
199
|
+
}
|
|
200
|
+
function readNumber(value) {
|
|
201
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
202
|
+
}
|
|
203
|
+
function isRecord(value) {
|
|
204
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
205
|
+
}
|
|
206
|
+
|
|
123
207
|
// src/frame-codec.ts
|
|
124
208
|
import { randomUUID } from "crypto";
|
|
125
209
|
var CoolclawFrameDecodeError = class extends Error {
|
|
@@ -147,7 +231,7 @@ function decodeFrame(raw) {
|
|
|
147
231
|
} catch (error) {
|
|
148
232
|
throw new CoolclawFrameDecodeError(`Invalid CoolClaw frame JSON: ${error.message}`);
|
|
149
233
|
}
|
|
150
|
-
if (!
|
|
234
|
+
if (!isRecord2(value)) {
|
|
151
235
|
throw new CoolclawFrameDecodeError("Invalid CoolClaw frame: expected object");
|
|
152
236
|
}
|
|
153
237
|
if (!("v" in value)) {
|
|
@@ -182,304 +266,10 @@ function decodeFrame(raw) {
|
|
|
182
266
|
}
|
|
183
267
|
return frame;
|
|
184
268
|
}
|
|
185
|
-
function
|
|
269
|
+
function isRecord2(value) {
|
|
186
270
|
return typeof value === "object" && value !== null;
|
|
187
271
|
}
|
|
188
272
|
|
|
189
|
-
// src/game-event-prompt.ts
|
|
190
|
-
function asRecord(v) {
|
|
191
|
-
return typeof v === "object" && v !== null ? v : {};
|
|
192
|
-
}
|
|
193
|
-
function asString(v, fallback = "") {
|
|
194
|
-
return typeof v === "string" ? v : fallback;
|
|
195
|
-
}
|
|
196
|
-
function asNumberOrNull(v) {
|
|
197
|
-
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
198
|
-
}
|
|
199
|
-
function asNumberArray(v) {
|
|
200
|
-
return Array.isArray(v) ? v.filter((x) => typeof x === "number") : [];
|
|
201
|
-
}
|
|
202
|
-
function asRecordArray(v) {
|
|
203
|
-
return Array.isArray(v) ? v.map(asRecord) : [];
|
|
204
|
-
}
|
|
205
|
-
function asStringArray(v) {
|
|
206
|
-
return Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
207
|
-
}
|
|
208
|
-
function renderPlayerInfo(list, selfSeat) {
|
|
209
|
-
if (list.length === 0) return "\uFF08\u65E0\u5EA7\u4F4D\u4FE1\u606F\uFF09";
|
|
210
|
-
return list.map((p) => {
|
|
211
|
-
const seat = asNumberOrNull(p.seat);
|
|
212
|
-
const playerStatus = p.alive === true ? "\u5B58\u6D3B" : p.alive === false ? "\u5DF2\u6B7B\u4EA1" : "\u53C2\u8D5B";
|
|
213
|
-
if (seat != null && selfSeat != null && seat === selfSeat) {
|
|
214
|
-
const name = asString(p.name, "\u672A\u77E5");
|
|
215
|
-
const voice = asString(p.voiceDesc, "");
|
|
216
|
-
return `\u5EA7\u4F4D${seat}\uFF08\u4F60\u81EA\u5DF1\uFF0C${name}${voice ? `\uFF0C${voice}` : ""}\uFF0C${playerStatus}\uFF09`;
|
|
217
|
-
}
|
|
218
|
-
return `\u5EA7\u4F4D${seat ?? "?"}\uFF08${playerStatus}\uFF09`;
|
|
219
|
-
}).join("\uFF1B");
|
|
220
|
-
}
|
|
221
|
-
function stripAudioSuffix(line) {
|
|
222
|
-
return line.replace(/\s*:audio=https?:\/\/\S+/g, "").replace(/\s*:cb=[^\s|]+/g, "");
|
|
223
|
-
}
|
|
224
|
-
function renderHistory(history) {
|
|
225
|
-
if (history.length === 0) return "\uFF08\u6682\u65E0\u5386\u53F2\u8BB0\u5F55\uFF09";
|
|
226
|
-
return history.map((h, i) => `${i + 1}. ${stripAudioSuffix(h)}`).join("\n");
|
|
227
|
-
}
|
|
228
|
-
function renderAliveSeats(seats) {
|
|
229
|
-
return seats.length === 0 ? "\uFF08\u65E0\uFF09" : `[${seats.join(", ")}]`;
|
|
230
|
-
}
|
|
231
|
-
function asMvpCandidates(v) {
|
|
232
|
-
if (!Array.isArray(v)) return [];
|
|
233
|
-
return v.flatMap((item) => {
|
|
234
|
-
if (typeof item === "number" && Number.isFinite(item)) {
|
|
235
|
-
return [{ agentId: item, seatNumber: null }];
|
|
236
|
-
}
|
|
237
|
-
const record = asRecord(item);
|
|
238
|
-
const agentId = asNumberOrNull(record.agentId) ?? asNumberOrNull(record.targetAgentId);
|
|
239
|
-
if (agentId == null) return [];
|
|
240
|
-
const seatNumber = asNumberOrNull(record.seatNumber) ?? asNumberOrNull(record.seat);
|
|
241
|
-
return [{ agentId, seatNumber }];
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
function renderMvpCandidateList(candidates, selfAgentId) {
|
|
245
|
-
if (candidates.length === 0) return "\uFF08\u5019\u9009\u4FE1\u606F\u7F3A\u5931\uFF1B\u7B49\u5F85\u5E73\u53F0\u8D85\u65F6\u515C\u5E95\uFF09";
|
|
246
|
-
return candidates.map((candidate) => {
|
|
247
|
-
const seat = candidate.seatNumber == null ? "?" : candidate.seatNumber;
|
|
248
|
-
const selfMark = selfAgentId != null && candidate.agentId === selfAgentId ? "\uFF08\u4F60\u81EA\u5DF1\uFF0C\u4E0D\u80FD\u6295\u7ED9\u81EA\u5DF1\uFF09" : "";
|
|
249
|
-
return `- \u5EA7\u4F4D${seat} / Agent ${candidate.agentId}${selfMark}`;
|
|
250
|
-
}).join("\n");
|
|
251
|
-
}
|
|
252
|
-
function renderHeader(eventType, outer, payload) {
|
|
253
|
-
const round = asNumberOrNull(outer.round) ?? 1;
|
|
254
|
-
const selfSeat = asNumberOrNull(payload.selfSeat);
|
|
255
|
-
const selfRole = asString(payload.selfRole, "\u672A\u77E5");
|
|
256
|
-
const selfName = asString(payload.selfAgentName, "");
|
|
257
|
-
const phase = eventType === "MVP_VOTE_REQUEST" ? "\u8D5B\u540E" : eventType.startsWith("DAY_") || eventType === "LAST_WORD_TURN" || eventType === "HUNTER_SKILL_TURN" ? "\u767D\u5929" : "\u591C\u665A";
|
|
258
|
-
const aliveSeats = asNumberArray(payload.aliveSeats);
|
|
259
|
-
const seatsLabel = eventType === "MVP_VOTE_REQUEST" ? "MVP \u5019\u9009\u5EA7\u4F4D" : "\u5F53\u524D\u5B58\u6D3B\u5EA7\u4F4D";
|
|
260
|
-
const playerInfo = renderPlayerInfo(asRecordArray(payload.playerInfoList), selfSeat);
|
|
261
|
-
const history = renderHistory(asStringArray(payload.scopedHistory));
|
|
262
|
-
return [
|
|
263
|
-
`[\u6E38\u620F] \u72FC\u4EBA\u6740 \xB7 \u7B2C ${round} \u8F6E \xB7 ${phase} \xB7 ${describeEventType(eventType)}`,
|
|
264
|
-
``,
|
|
265
|
-
`\u4F60\u662F\u5EA7\u4F4D ${selfSeat ?? "?"} \u7684 ${selfRole}${selfName ? `\uFF08${selfName}\uFF09` : ""}\u3002`,
|
|
266
|
-
`${seatsLabel}\uFF1A${renderAliveSeats(aliveSeats)}\u3002`,
|
|
267
|
-
`\u5168\u4F53\u73A9\u5BB6\uFF1A${playerInfo}`,
|
|
268
|
-
``,
|
|
269
|
-
`\u3010\u8FD1\u671F\u5386\u53F2\uFF08\u4EC5\u4F60\u53EF\u89C1\uFF09\u3011`,
|
|
270
|
-
history,
|
|
271
|
-
``
|
|
272
|
-
].join("\n");
|
|
273
|
-
}
|
|
274
|
-
function describeEventType(eventType) {
|
|
275
|
-
switch (eventType) {
|
|
276
|
-
case "WOLF_TURN":
|
|
277
|
-
return "\u72FC\u4EBA\u884C\u52A8";
|
|
278
|
-
case "WITCH_TURN":
|
|
279
|
-
return "\u5973\u5DEB\u884C\u52A8";
|
|
280
|
-
case "SEER_TURN":
|
|
281
|
-
return "\u9884\u8A00\u5BB6\u67E5\u9A8C";
|
|
282
|
-
case "DAY_SPEAK_TURN":
|
|
283
|
-
return "\u767D\u5929\u53D1\u8A00";
|
|
284
|
-
case "LAST_WORD_TURN":
|
|
285
|
-
return "\u9057\u8A00";
|
|
286
|
-
case "DAY_VOTE_TURN":
|
|
287
|
-
return "\u767D\u5929\u6295\u7968";
|
|
288
|
-
case "HUNTER_SKILL_TURN":
|
|
289
|
-
return "\u730E\u4EBA\u6280\u80FD";
|
|
290
|
-
case "MVP_VOTE_REQUEST":
|
|
291
|
-
return "MVP \u6295\u7968";
|
|
292
|
-
default:
|
|
293
|
-
return eventType;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
function renderTask(eventType, payload) {
|
|
297
|
-
switch (eventType) {
|
|
298
|
-
case "WOLF_TURN":
|
|
299
|
-
return renderWolfTurn(payload);
|
|
300
|
-
case "WITCH_TURN":
|
|
301
|
-
return renderWitchTurn(payload);
|
|
302
|
-
case "SEER_TURN":
|
|
303
|
-
return renderSeerTurn(payload);
|
|
304
|
-
case "DAY_SPEAK_TURN":
|
|
305
|
-
return renderDaySpeakTurn(payload);
|
|
306
|
-
case "LAST_WORD_TURN":
|
|
307
|
-
return renderLastWordTurn(payload);
|
|
308
|
-
case "DAY_VOTE_TURN":
|
|
309
|
-
return renderDayVoteTurn(payload);
|
|
310
|
-
case "HUNTER_SKILL_TURN":
|
|
311
|
-
return renderHunterTurn(payload);
|
|
312
|
-
case "MVP_VOTE_REQUEST":
|
|
313
|
-
return renderMvpVoteRequest(payload);
|
|
314
|
-
default:
|
|
315
|
-
return renderUnknownTurn(eventType);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
function renderWolfTurn(payload) {
|
|
319
|
-
const aliveSeats = asNumberArray(payload.aliveSeats);
|
|
320
|
-
const round = asNumberOrNull(payload.wolfAttemptRound) ?? 1;
|
|
321
|
-
const isFirst = payload.isFirstSpeakerInRound === true;
|
|
322
|
-
const teammateSeat = asNumberOrNull(payload.teammateSeat);
|
|
323
|
-
const tp = asRecord(payload.teammateProposal);
|
|
324
|
-
const tpSeat = asNumberOrNull(tp.seat);
|
|
325
|
-
const tpTarget = asNumberOrNull(tp.targetSeat);
|
|
326
|
-
const tpSpeech = asString(tp.speech, "").trim();
|
|
327
|
-
let teammateBlock;
|
|
328
|
-
if (tpTarget != null) {
|
|
329
|
-
const speechLine = tpSpeech ? `
|
|
330
|
-
===== \u72FC\u961F\u53CB\u53D1\u8A00\u5F00\u59CB =====
|
|
331
|
-
${tpSpeech}
|
|
332
|
-
===== \u72FC\u961F\u53CB\u53D1\u8A00\u7ED3\u675F =====` : "";
|
|
333
|
-
teammateBlock = `\u540C\u4F34\uFF08\u5EA7\u4F4D ${tpSeat ?? "?"}\uFF09\u672C\u8F6E\u5DF2\u63D0\u8BAE\u51FB\u6740\u5EA7\u4F4D ${tpTarget}\u3002${speechLine}`;
|
|
334
|
-
} else {
|
|
335
|
-
teammateBlock = isFirst ? "\u4F60\u662F\u672C\u8F6E\u9996\u4F4D\u53D1\u8A00\u7684\u72FC\u4EBA\u3002" : "\u540C\u4F34\u5C1A\u672A\u53D1\u8A00\u3002";
|
|
336
|
-
}
|
|
337
|
-
const lastRound = asRecordArray(payload.lastRoundChoices);
|
|
338
|
-
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` : "";
|
|
339
|
-
return [
|
|
340
|
-
`\u3010\u4EFB\u52A1\u3011\u72FC\u4EBA\u6740\u4EBA\u534F\u5546\uFF08\u7B2C ${round} \u8F6E\uFF09`,
|
|
341
|
-
teammateSeat != null ? `\u4F60\u7684\u72FC\u540C\u4F34\uFF1A\u5EA7\u4F4D ${teammateSeat}\u3002` : "\u4F60\u662F\u72EC\u72FC\u3002",
|
|
342
|
-
teammateBlock,
|
|
343
|
-
lastRoundStr,
|
|
344
|
-
`\u5408\u6CD5\u76EE\u6807\uFF1A${renderAliveSeats(aliveSeats)} \u4E2D\u7684\u4EFB\u610F\u4E00\u4E2A\u3002`,
|
|
345
|
-
``,
|
|
346
|
-
`\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`,
|
|
347
|
-
`<ACTION>`,
|
|
348
|
-
`{"actionType":"WOLF_KILL","actionData":{"targetSeat":<number>,"reason":"<\u7B80\u8FF0>","speech":"<\u53D1\u8A00>"}}`,
|
|
349
|
-
`</ACTION>`
|
|
350
|
-
].join("\n");
|
|
351
|
-
}
|
|
352
|
-
function renderWitchTurn(payload) {
|
|
353
|
-
const aliveSeats = asNumberArray(payload.aliveSeats);
|
|
354
|
-
const wolfTarget = asNumberOrNull(payload.wolfTargetSeat);
|
|
355
|
-
const antidote = payload.antidoteAvailable === true;
|
|
356
|
-
const poison = payload.poisonAvailable === true;
|
|
357
|
-
const canSelfSave = payload.canSelfSave === true;
|
|
358
|
-
const options = [];
|
|
359
|
-
if (antidote && wolfTarget != null) {
|
|
360
|
-
options.push(
|
|
361
|
-
`\u9009\u9879 A\uFF08\u4F7F\u7528\u89E3\u836F\u6551 ${wolfTarget} \u53F7\uFF09\uFF1A`,
|
|
362
|
-
`<ACTION>{"actionType":"WITCH_SAVE","actionData":{"speech":"<\u53EF\u9009\u53D1\u8A00>"}}</ACTION>`
|
|
363
|
-
);
|
|
364
|
-
}
|
|
365
|
-
if (poison) {
|
|
366
|
-
options.push(
|
|
367
|
-
`\u9009\u9879 B\uFF08\u4F7F\u7528\u6BD2\u836F\u6BD2\u6740\u67D0\u5EA7\u4F4D\uFF09\uFF1A`,
|
|
368
|
-
`<ACTION>{"actionType":"WITCH_POISON","actionData":{"targetSeat":<number>,"speech":"<\u53EF\u9009\u53D1\u8A00>"}}</ACTION>`
|
|
369
|
-
);
|
|
370
|
-
}
|
|
371
|
-
options.push(
|
|
372
|
-
`\u9009\u9879 C\uFF08\u672C\u8F6E\u4E0D\u7528\u836F\uFF09\uFF1A`,
|
|
373
|
-
`<ACTION>{"actionType":"WITCH_PASS","actionData":{}}</ACTION>`
|
|
374
|
-
);
|
|
375
|
-
return [
|
|
376
|
-
`\u3010\u4EFB\u52A1\u3011\u5973\u5DEB\u884C\u52A8`,
|
|
377
|
-
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`,
|
|
378
|
-
`\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" : ""}`,
|
|
379
|
-
`\u5408\u6CD5\u6BD2\u836F\u76EE\u6807\uFF1A${renderAliveSeats(aliveSeats)}\u3002`,
|
|
380
|
-
``,
|
|
381
|
-
`\u8BF7\u4E09\u9009\u4E00\uFF0C\u5728\u56DE\u590D\u6700\u540E\u4E25\u683C\u6309\u5BF9\u5E94\u683C\u5F0F\u58F0\u660E\u52A8\u4F5C\uFF1A`,
|
|
382
|
-
...options
|
|
383
|
-
].join("\n");
|
|
384
|
-
}
|
|
385
|
-
function renderSeerTurn(payload) {
|
|
386
|
-
const aliveSeats = asNumberArray(payload.aliveSeats);
|
|
387
|
-
const history = asStringArray(payload.history);
|
|
388
|
-
return [
|
|
389
|
-
`\u3010\u4EFB\u52A1\u3011\u9884\u8A00\u5BB6\u67E5\u9A8C`,
|
|
390
|
-
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`,
|
|
391
|
-
`\u5408\u6CD5\u67E5\u9A8C\u76EE\u6807\uFF1A${renderAliveSeats(aliveSeats)}\u3002`,
|
|
392
|
-
``,
|
|
393
|
-
`\u8BF7\u5728\u56DE\u590D\u6700\u540E\u4E25\u683C\u6309\u4EE5\u4E0B\u683C\u5F0F\u58F0\u660E\u52A8\u4F5C\uFF1A`,
|
|
394
|
-
`<ACTION>`,
|
|
395
|
-
`{"actionType":"SEER_CHECK","actionData":{"targetSeat":<number>,"speech":"<\u53EF\u9009\u53D1\u8A00>"}}`,
|
|
396
|
-
`</ACTION>`
|
|
397
|
-
].join("\n");
|
|
398
|
-
}
|
|
399
|
-
function renderDaySpeakTurn(payload) {
|
|
400
|
-
const maxLen = asNumberOrNull(payload.maxLength) ?? 300;
|
|
401
|
-
return [
|
|
402
|
-
`\u3010\u4EFB\u52A1\u3011\u767D\u5929\u8F6E\u5230\u4F60\u53D1\u8A00`,
|
|
403
|
-
`\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`,
|
|
404
|
-
``,
|
|
405
|
-
`\u8BF7\u5728\u56DE\u590D\u6700\u540E\u4E25\u683C\u6309\u4EE5\u4E0B\u683C\u5F0F\u58F0\u660E\u52A8\u4F5C\uFF1A`,
|
|
406
|
-
`<ACTION>`,
|
|
407
|
-
`{"actionType":"DAY_SPEAK","actionData":{"content":"<\u4F60\u7684\u5B8C\u6574\u53D1\u8A00\u6587\u672C>"}}`,
|
|
408
|
-
`</ACTION>`
|
|
409
|
-
].join("\n");
|
|
410
|
-
}
|
|
411
|
-
function renderLastWordTurn(_payload) {
|
|
412
|
-
return [
|
|
413
|
-
`\u3010\u4EFB\u52A1\u3011\u9057\u8A00`,
|
|
414
|
-
`\u4F60\u5DF2\u51FA\u5C40\uFF0C\u53EF\u4EE5\u7559\u4E0B\u9057\u8A00\uFF08\u4E5F\u53EF\u4EE5\u9009\u62E9\u4E0D\u8BF4\uFF09\u3002`,
|
|
415
|
-
``,
|
|
416
|
-
`\u8BF7\u5728\u56DE\u590D\u6700\u540E\u4E25\u683C\u6309\u4EE5\u4E0B\u683C\u5F0F\u58F0\u660E\u52A8\u4F5C\uFF1A`,
|
|
417
|
-
`<ACTION>`,
|
|
418
|
-
`{"actionType":"LAST_WORD","actionData":{"content":"<\u9057\u8A00\u6587\u672C\uFF0C\u53EF\u4E3A\u7A7A>"}}`,
|
|
419
|
-
`</ACTION>`
|
|
420
|
-
].join("\n");
|
|
421
|
-
}
|
|
422
|
-
function renderDayVoteTurn(payload) {
|
|
423
|
-
const aliveSeats = asNumberArray(payload.aliveSeats);
|
|
424
|
-
return [
|
|
425
|
-
`\u3010\u4EFB\u52A1\u3011\u767D\u5929\u6295\u7968\u653E\u9010`,
|
|
426
|
-
`\u5408\u6CD5\u6295\u7968\u76EE\u6807\uFF1A${renderAliveSeats(aliveSeats)}\uFF1B\u53EF\u4EE5\u5F03\u7968\uFF08targetSeat \u586B null\uFF09\u3002`,
|
|
427
|
-
``,
|
|
428
|
-
`\u8BF7\u5728\u56DE\u590D\u6700\u540E\u4E25\u683C\u6309\u4EE5\u4E0B\u683C\u5F0F\u58F0\u660E\u52A8\u4F5C\uFF1A`,
|
|
429
|
-
`<ACTION>`,
|
|
430
|
-
`{"actionType":"DAY_VOTE","actionData":{"targetSeat":<number \u6216 null>}}`,
|
|
431
|
-
`</ACTION>`
|
|
432
|
-
].join("\n");
|
|
433
|
-
}
|
|
434
|
-
function renderHunterTurn(payload) {
|
|
435
|
-
const aliveSeats = asNumberArray(payload.aliveSeats);
|
|
436
|
-
return [
|
|
437
|
-
`\u3010\u4EFB\u52A1\u3011\u730E\u4EBA\u6280\u80FD`,
|
|
438
|
-
`\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`,
|
|
439
|
-
`\u5408\u6CD5\u5F00\u67AA\u76EE\u6807\uFF1A${renderAliveSeats(aliveSeats)}\u3002`,
|
|
440
|
-
``,
|
|
441
|
-
`\u8BF7\u4E8C\u9009\u4E00\uFF1A`,
|
|
442
|
-
`\u9009\u9879 A\uFF08\u5F00\u67AA\u5E26\u8D70\u67D0\u5EA7\u4F4D\uFF09\uFF1A`,
|
|
443
|
-
`<ACTION>{"actionType":"HUNTER_SHOOT","actionData":{"targetSeat":<number>}}</ACTION>`,
|
|
444
|
-
`\u9009\u9879 B\uFF08\u4E0D\u5F00\u67AA\uFF09\uFF1A`,
|
|
445
|
-
`<ACTION>{"actionType":"HUNTER_PASS","actionData":{}}</ACTION>`
|
|
446
|
-
].join("\n");
|
|
447
|
-
}
|
|
448
|
-
function renderMvpVoteRequest(payload) {
|
|
449
|
-
const candidates = asMvpCandidates(payload.candidates);
|
|
450
|
-
const selfAgentId = asNumberOrNull(payload.selfAgentId);
|
|
451
|
-
const round = asNumberOrNull(payload.round) ?? 1;
|
|
452
|
-
const deadlineSeconds = asNumberOrNull(payload.deadlineSeconds);
|
|
453
|
-
return [
|
|
454
|
-
`\u3010\u4EFB\u52A1\u3011\u8D5B\u540E MVP \u6295\u7968\uFF08\u7B2C ${round} \u8F6E\uFF09`,
|
|
455
|
-
deadlineSeconds != null ? `\u8BF7\u5728 ${deadlineSeconds} \u79D2\u5185\u4ECE\u5019\u9009\u5BF9\u8C61\u91CC\u9009\u51FA\u4F60\u8BA4\u4E3A\u672C\u5C40\u8D21\u732E\u6700\u5927\u7684 Agent\u3002` : `\u8BF7\u4ECE\u5019\u9009\u5BF9\u8C61\u91CC\u9009\u51FA\u4F60\u8BA4\u4E3A\u672C\u5C40\u8D21\u732E\u6700\u5927\u7684 Agent\u3002`,
|
|
456
|
-
`\u5019\u9009\u5BF9\u8C61\uFF08targetAgentId \u5FC5\u987B\u4ECE\u8FD9\u91CC\u9009\uFF0C\u4E0D\u80FD\u6295\u7ED9\u81EA\u5DF1\uFF09\uFF1A`,
|
|
457
|
-
renderMvpCandidateList(candidates, selfAgentId),
|
|
458
|
-
``,
|
|
459
|
-
`\u8BF7\u7EFC\u5408\u53D1\u8A00\u3001\u6295\u7968\u3001\u5173\u952E\u884C\u52A8\u548C\u80DC\u8D1F\u8D21\u732E\uFF0C\u7B80\u77ED\u8BF4\u660E\u7406\u7531\uFF0C\u7136\u540E\u5728\u56DE\u590D\u6700\u540E\u4E25\u683C\u6309\u4EE5\u4E0B\u683C\u5F0F\u58F0\u660E\u52A8\u4F5C\uFF1A`,
|
|
460
|
-
`<ACTION>`,
|
|
461
|
-
`{"actionType":"MVP_VOTE","actionData":{"targetAgentId":<number>,"round":${round},"reason":"<\u7B80\u8FF0>"}}`,
|
|
462
|
-
`</ACTION>`
|
|
463
|
-
].join("\n");
|
|
464
|
-
}
|
|
465
|
-
function renderUnknownTurn(eventType) {
|
|
466
|
-
return [
|
|
467
|
-
`\u3010\u63D0\u793A\u3011\u672A\u8BC6\u522B\u7684\u6E38\u620F\u4E8B\u4EF6\u7C7B\u578B\uFF1A${eventType}`,
|
|
468
|
-
`\u8BF7\u5FFD\u7565\u672C\u6761\u4E8B\u4EF6\uFF0C\u65E0\u9700\u58F0\u660E\u52A8\u4F5C\u3002`
|
|
469
|
-
].join("\n");
|
|
470
|
-
}
|
|
471
|
-
function buildGameEventPrompt(eventType, eventData) {
|
|
472
|
-
const outer = asRecord(eventData);
|
|
473
|
-
const nestedPayload = asRecord(outer.payload);
|
|
474
|
-
const payload = Object.keys(nestedPayload).length > 0 ? nestedPayload : outer;
|
|
475
|
-
const header = renderHeader(eventType, outer, payload);
|
|
476
|
-
const task = renderTask(eventType, payload);
|
|
477
|
-
const footer = `
|
|
478
|
-
|
|
479
|
-
\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`;
|
|
480
|
-
return `${header}${task}${footer}`;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
273
|
// src/inbound.ts
|
|
484
274
|
function mapInboundFrame(frame) {
|
|
485
275
|
if (frame.type === "PRIVATE_MESSAGE") {
|
|
@@ -533,12 +323,15 @@ function mapInboundFrame(frame) {
|
|
|
533
323
|
}
|
|
534
324
|
async function handleInboundFrame(input) {
|
|
535
325
|
const envelope = mapInboundFrame(input.frame);
|
|
536
|
-
if (!envelope)
|
|
326
|
+
if (!envelope) {
|
|
327
|
+
await ackFrameSeq(input);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
537
330
|
await ackProcessedSeq(input, envelope);
|
|
538
331
|
await input.dispatch(envelope);
|
|
539
332
|
}
|
|
540
333
|
function mapNotificationFrame(frame) {
|
|
541
|
-
const payload =
|
|
334
|
+
const payload = isRecord3(frame.payload) ? frame.payload : {};
|
|
542
335
|
const seq = typeof payload.seq === "number" ? payload.seq : void 0;
|
|
543
336
|
if (frame.type === "SYSTEM_NOTIFICATION") {
|
|
544
337
|
const title = typeof payload.title === "string" ? payload.title : "System";
|
|
@@ -588,7 +381,8 @@ function mapNotificationFrame(frame) {
|
|
|
588
381
|
}
|
|
589
382
|
function mapGameEventFrame(frame, payload) {
|
|
590
383
|
const eventType = typeof payload.eventType === "string" ? payload.eventType : "UNKNOWN";
|
|
591
|
-
|
|
384
|
+
const agentTask = normalizeAgentTask(payload.agentTask);
|
|
385
|
+
if (!agentTask || !isDispatchableAgentTask(agentTask)) {
|
|
592
386
|
return null;
|
|
593
387
|
}
|
|
594
388
|
let eventDataObj = {};
|
|
@@ -596,10 +390,10 @@ function mapGameEventFrame(frame, payload) {
|
|
|
596
390
|
if (typeof rawEventData === "string" && rawEventData.length > 0) {
|
|
597
391
|
try {
|
|
598
392
|
const parsed = JSON.parse(rawEventData);
|
|
599
|
-
if (
|
|
393
|
+
if (isRecord3(parsed)) eventDataObj = parsed;
|
|
600
394
|
} catch {
|
|
601
395
|
}
|
|
602
|
-
} else if (
|
|
396
|
+
} else if (isRecord3(rawEventData)) {
|
|
603
397
|
eventDataObj = rawEventData;
|
|
604
398
|
}
|
|
605
399
|
const gameId = Number(payload.gameId ?? 0);
|
|
@@ -618,16 +412,17 @@ function mapGameEventFrame(frame, payload) {
|
|
|
618
412
|
traceId,
|
|
619
413
|
eventType,
|
|
620
414
|
eventData: eventDataObj,
|
|
415
|
+
agentTask,
|
|
416
|
+
promptPolicyVersion: agentTask.promptPolicyVersion,
|
|
417
|
+
renderedPromptHash: agentTask.renderedPromptHash,
|
|
621
418
|
deadlineEpochMs,
|
|
622
419
|
sourceFrameId: frame.id
|
|
623
420
|
};
|
|
624
421
|
return {
|
|
625
422
|
id: eventId,
|
|
626
423
|
channel: "coolclaw",
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
conversationId: `game:${roomId}:${gameId}:${eventType}`,
|
|
630
|
-
text: buildGameEventPrompt(eventType, eventDataObj),
|
|
424
|
+
conversationId: agentTask.conversationKey ?? `game:${roomId}:${gameId}:task:${eventId}`,
|
|
425
|
+
text: agentTask.renderedPrompt,
|
|
631
426
|
messageType: "GAME_EVENT",
|
|
632
427
|
seq,
|
|
633
428
|
shouldReply: true,
|
|
@@ -636,52 +431,62 @@ function mapGameEventFrame(frame, payload) {
|
|
|
636
431
|
}
|
|
637
432
|
async function ackProcessedSeq(input, envelope) {
|
|
638
433
|
if (typeof envelope.seq === "number") {
|
|
639
|
-
|
|
640
|
-
await input.sendAck(createFrame("ACK", { lastAckedSeq }));
|
|
434
|
+
await ackSeq(input, envelope.seq);
|
|
641
435
|
}
|
|
642
436
|
}
|
|
437
|
+
async function ackFrameSeq(input) {
|
|
438
|
+
const payload = isRecord3(input.frame.payload) ? input.frame.payload : {};
|
|
439
|
+
const seq = typeof payload.seq === "number" ? payload.seq : void 0;
|
|
440
|
+
if (typeof seq === "number") {
|
|
441
|
+
await ackSeq(input, seq);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
async function ackSeq(input, seq) {
|
|
445
|
+
const lastAckedSeq = await input.ackStore.record(input.accountKey, seq);
|
|
446
|
+
await input.sendAck(createFrame("ACK", { lastAckedSeq }));
|
|
447
|
+
}
|
|
643
448
|
function assertPrivatePayload(value) {
|
|
644
|
-
if (!
|
|
449
|
+
if (!isRecord3(value) || !isUserRef(value.sender) || !isUserRef(value.recipient)) {
|
|
645
450
|
throw new Error("Invalid PRIVATE_MESSAGE payload");
|
|
646
451
|
}
|
|
647
452
|
return {
|
|
648
|
-
seq:
|
|
649
|
-
messageId:
|
|
650
|
-
conversationId:
|
|
453
|
+
seq: readNumber2(value, "seq"),
|
|
454
|
+
messageId: readString2(value, "messageId"),
|
|
455
|
+
conversationId: readString2(value, "conversationId"),
|
|
651
456
|
sender: value.sender,
|
|
652
457
|
recipient: value.recipient,
|
|
653
|
-
messageType:
|
|
654
|
-
content:
|
|
458
|
+
messageType: readString2(value, "messageType"),
|
|
459
|
+
content: readString2(value, "content"),
|
|
655
460
|
mentioned: readBoolean(value, "mentioned"),
|
|
656
|
-
sentAt:
|
|
461
|
+
sentAt: readString2(value, "sentAt")
|
|
657
462
|
};
|
|
658
463
|
}
|
|
659
464
|
function assertGroupPayload(value) {
|
|
660
|
-
if (!
|
|
465
|
+
if (!isRecord3(value) || !isUserRef(value.sender)) {
|
|
661
466
|
throw new Error("Invalid GROUP_MESSAGE payload");
|
|
662
467
|
}
|
|
663
468
|
return {
|
|
664
|
-
seq:
|
|
665
|
-
messageId:
|
|
666
|
-
groupId:
|
|
667
|
-
groupName:
|
|
668
|
-
conversationId:
|
|
469
|
+
seq: readNumber2(value, "seq"),
|
|
470
|
+
messageId: readString2(value, "messageId"),
|
|
471
|
+
groupId: readString2(value, "groupId"),
|
|
472
|
+
groupName: readString2(value, "groupName"),
|
|
473
|
+
conversationId: readString2(value, "conversationId"),
|
|
669
474
|
sender: value.sender,
|
|
670
|
-
messageType:
|
|
671
|
-
content:
|
|
475
|
+
messageType: readString2(value, "messageType"),
|
|
476
|
+
content: readString2(value, "content"),
|
|
672
477
|
mentioned: readBoolean(value, "mentioned"),
|
|
673
|
-
sentAt:
|
|
478
|
+
sentAt: readString2(value, "sentAt"),
|
|
674
479
|
agentHint: readOptionalString(value, "agentHint")
|
|
675
480
|
};
|
|
676
481
|
}
|
|
677
|
-
function
|
|
482
|
+
function readString2(source, key) {
|
|
678
483
|
const value = source[key];
|
|
679
484
|
if (typeof value !== "string" || value.length === 0) {
|
|
680
485
|
throw new Error(`Invalid inbound payload: missing ${key}`);
|
|
681
486
|
}
|
|
682
487
|
return value;
|
|
683
488
|
}
|
|
684
|
-
function
|
|
489
|
+
function readNumber2(source, key) {
|
|
685
490
|
const value = source[key];
|
|
686
491
|
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
687
492
|
throw new Error(`Invalid inbound payload: missing ${key}`);
|
|
@@ -706,9 +511,9 @@ function readOptionalString(source, key) {
|
|
|
706
511
|
return value;
|
|
707
512
|
}
|
|
708
513
|
function isUserRef(value) {
|
|
709
|
-
return
|
|
514
|
+
return isRecord3(value) && typeof value.userId === "string" && (value.userType === "HUMAN" || value.userType === "AGENT") && (value.displayName === void 0 || typeof value.displayName === "string");
|
|
710
515
|
}
|
|
711
|
-
function
|
|
516
|
+
function isRecord3(value) {
|
|
712
517
|
return typeof value === "object" && value !== null;
|
|
713
518
|
}
|
|
714
519
|
|
|
@@ -833,13 +638,20 @@ async function sendMedia(input) {
|
|
|
833
638
|
async function sendGameAction(input) {
|
|
834
639
|
const frame = createFrame("GAME_ACTION", {
|
|
835
640
|
gameId: input.gameId,
|
|
641
|
+
roomId: input.roomId,
|
|
642
|
+
eventType: input.eventType,
|
|
836
643
|
actionType: input.actionType,
|
|
837
644
|
actionData: input.actionData,
|
|
838
645
|
// AgentActionRequest.timestamp 契约为 String
|
|
839
646
|
timestamp: String(Date.now()),
|
|
840
647
|
turnSeq: input.turnSeq,
|
|
841
648
|
eventId: input.eventId,
|
|
842
|
-
traceId: input.traceId
|
|
649
|
+
traceId: input.traceId,
|
|
650
|
+
promptPolicyVersion: input.promptPolicyVersion,
|
|
651
|
+
renderedPromptHash: input.renderedPromptHash,
|
|
652
|
+
parseSource: input.parseSource,
|
|
653
|
+
rawResponseHash: input.rawResponseHash,
|
|
654
|
+
rawResponsePreview: input.rawResponsePreview
|
|
843
655
|
});
|
|
844
656
|
const response = await input.client.request(frame);
|
|
845
657
|
if (response.ok === false) {
|
|
@@ -887,107 +699,6 @@ function parseAgentAction(text) {
|
|
|
887
699
|
};
|
|
888
700
|
}
|
|
889
701
|
|
|
890
|
-
// src/game-action-fallback.ts
|
|
891
|
-
function asNumberArray2(v) {
|
|
892
|
-
return Array.isArray(v) ? v.filter((x) => typeof x === "number") : [];
|
|
893
|
-
}
|
|
894
|
-
function asNumberOrNull2(v) {
|
|
895
|
-
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
896
|
-
}
|
|
897
|
-
function asRecord2(v) {
|
|
898
|
-
return typeof v === "object" && v !== null ? v : {};
|
|
899
|
-
}
|
|
900
|
-
function hasKeys(record) {
|
|
901
|
-
return Object.keys(record).length > 0;
|
|
902
|
-
}
|
|
903
|
-
function candidateAgentIds(v) {
|
|
904
|
-
if (!Array.isArray(v)) return [];
|
|
905
|
-
return v.flatMap((item) => {
|
|
906
|
-
if (typeof item === "number" && Number.isFinite(item)) return [item];
|
|
907
|
-
const record = asRecord2(item);
|
|
908
|
-
const agentId = asNumberOrNull2(record.agentId) ?? asNumberOrNull2(record.targetAgentId);
|
|
909
|
-
return agentId == null ? [] : [agentId];
|
|
910
|
-
});
|
|
911
|
-
}
|
|
912
|
-
function pickRandomAlive(aliveSeats, excludeSeat) {
|
|
913
|
-
const candidates = excludeSeat != null ? aliveSeats.filter((s) => s !== excludeSeat) : aliveSeats;
|
|
914
|
-
if (candidates.length === 0) return null;
|
|
915
|
-
return candidates[Math.floor(Math.random() * candidates.length)];
|
|
916
|
-
}
|
|
917
|
-
function pickRandomAgent(candidates, excludeAgentId) {
|
|
918
|
-
const eligible = excludeAgentId == null ? candidates : candidates.filter((candidate) => candidate !== excludeAgentId);
|
|
919
|
-
if (eligible.length === 0) return candidates[0] ?? null;
|
|
920
|
-
return eligible[Math.floor(Math.random() * eligible.length)];
|
|
921
|
-
}
|
|
922
|
-
function fallbackActionFor(eventType, eventData) {
|
|
923
|
-
const outer = asRecord2(eventData);
|
|
924
|
-
const nestedPayload = asRecord2(outer.payload);
|
|
925
|
-
const payload = hasKeys(nestedPayload) ? nestedPayload : outer;
|
|
926
|
-
const aliveSeats = asNumberArray2(payload.aliveSeats);
|
|
927
|
-
const selfSeat = typeof payload.selfSeat === "number" ? payload.selfSeat : void 0;
|
|
928
|
-
switch (eventType) {
|
|
929
|
-
case "WOLF_TURN": {
|
|
930
|
-
const target = pickRandomAlive(aliveSeats, selfSeat);
|
|
931
|
-
return {
|
|
932
|
-
actionType: "WOLF_KILL",
|
|
933
|
-
actionData: {
|
|
934
|
-
targetSeat: target ?? (aliveSeats[0] ?? 1),
|
|
935
|
-
reason: "\uFF08\u6258\u7BA1\uFF1ALLM \u672A\u7ED9\u51FA\u5408\u6CD5\u52A8\u4F5C\uFF09",
|
|
936
|
-
speech: ""
|
|
937
|
-
}
|
|
938
|
-
};
|
|
939
|
-
}
|
|
940
|
-
case "WITCH_TURN": {
|
|
941
|
-
return { actionType: "WITCH_PASS", actionData: {} };
|
|
942
|
-
}
|
|
943
|
-
case "SEER_TURN": {
|
|
944
|
-
const target = pickRandomAlive(aliveSeats, selfSeat);
|
|
945
|
-
return {
|
|
946
|
-
actionType: "SEER_CHECK",
|
|
947
|
-
actionData: {
|
|
948
|
-
targetSeat: target ?? (aliveSeats[0] ?? 1),
|
|
949
|
-
speech: ""
|
|
950
|
-
}
|
|
951
|
-
};
|
|
952
|
-
}
|
|
953
|
-
case "DAY_SPEAK_TURN":
|
|
954
|
-
return {
|
|
955
|
-
actionType: "DAY_SPEAK",
|
|
956
|
-
actionData: { content: "\uFF08\u6258\u7BA1\u53D1\u8A00\uFF1A\u672C\u8F6E\u8DF3\u8FC7\uFF09" }
|
|
957
|
-
};
|
|
958
|
-
case "LAST_WORD_TURN":
|
|
959
|
-
return {
|
|
960
|
-
actionType: "LAST_WORD",
|
|
961
|
-
actionData: { content: "" }
|
|
962
|
-
};
|
|
963
|
-
case "DAY_VOTE_TURN":
|
|
964
|
-
return {
|
|
965
|
-
actionType: "DAY_VOTE",
|
|
966
|
-
actionData: { targetSeat: null }
|
|
967
|
-
};
|
|
968
|
-
case "HUNTER_SKILL_TURN":
|
|
969
|
-
return { actionType: "HUNTER_PASS", actionData: {} };
|
|
970
|
-
case "MVP_VOTE_REQUEST": {
|
|
971
|
-
const selfAgentId = asNumberOrNull2(payload.selfAgentId);
|
|
972
|
-
const candidates = candidateAgentIds(payload.candidates);
|
|
973
|
-
const targetAgentId = pickRandomAgent(candidates, selfAgentId);
|
|
974
|
-
if (targetAgentId == null) {
|
|
975
|
-
return { actionType: "UNKNOWN", actionData: {} };
|
|
976
|
-
}
|
|
977
|
-
return {
|
|
978
|
-
actionType: "MVP_VOTE",
|
|
979
|
-
actionData: {
|
|
980
|
-
targetAgentId,
|
|
981
|
-
round: asNumberOrNull2(payload.round) ?? asNumberOrNull2(outer.round) ?? 1,
|
|
982
|
-
reason: "\uFF08\u6258\u7BA1\uFF1ALLM \u672A\u7ED9\u51FA\u5408\u6CD5 MVP \u6295\u7968\uFF09"
|
|
983
|
-
}
|
|
984
|
-
};
|
|
985
|
-
}
|
|
986
|
-
default:
|
|
987
|
-
return { actionType: "UNKNOWN", actionData: {} };
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
|
|
991
702
|
// src/ws-client.ts
|
|
992
703
|
import WebSocket from "ws";
|
|
993
704
|
var CoolclawWsClient = class {
|
|
@@ -1200,18 +911,18 @@ var CoolclawWsClient = class {
|
|
|
1200
911
|
}
|
|
1201
912
|
};
|
|
1202
913
|
function readPingInterval(payload) {
|
|
1203
|
-
if (!
|
|
914
|
+
if (!isRecord4(payload) || typeof payload.pingIntervalMs !== "number" || payload.pingIntervalMs <= 0) {
|
|
1204
915
|
return void 0;
|
|
1205
916
|
}
|
|
1206
917
|
return payload.pingIntervalMs;
|
|
1207
918
|
}
|
|
1208
919
|
function readErrorMessage(payload) {
|
|
1209
|
-
if (
|
|
920
|
+
if (isRecord4(payload) && typeof payload.message === "string") {
|
|
1210
921
|
return payload.message;
|
|
1211
922
|
}
|
|
1212
923
|
return "CoolClaw request failed";
|
|
1213
924
|
}
|
|
1214
|
-
function
|
|
925
|
+
function isRecord4(value) {
|
|
1215
926
|
return typeof value === "object" && value !== null;
|
|
1216
927
|
}
|
|
1217
928
|
|
|
@@ -1265,27 +976,35 @@ function logAckFailure(params) {
|
|
|
1265
976
|
const target = params.target ? ` target=${params.target}` : "";
|
|
1266
977
|
params.log(`${params.channel} ack cleanup failed${target}: ${String(params.error)}`);
|
|
1267
978
|
}
|
|
1268
|
-
async function submitGameActionWithLog(action, meta, wsClient, log, source) {
|
|
979
|
+
async function submitGameActionWithLog(action, meta, wsClient, log, source, rawResponse) {
|
|
1269
980
|
if (!wsClient.isConnected()) {
|
|
1270
981
|
log?.error?.(`[GAME-ACTION] submit skipped: ws not connected eventId=${meta.eventId}`);
|
|
1271
982
|
return;
|
|
1272
983
|
}
|
|
984
|
+
const responseHash = rawResponse && rawResponse.length > 0 ? sha256Hex(rawResponse) : void 0;
|
|
1273
985
|
log?.info?.(
|
|
1274
|
-
`[GAME-ACTION] submit start source=${source} eventType=${meta.eventType} actionType=${action.actionType} gameId=${meta.gameId} turnSeq=${meta.turnSeq} eventId=${meta.eventId}`
|
|
986
|
+
`[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 ?? ""}`
|
|
1275
987
|
);
|
|
1276
988
|
const start = Date.now();
|
|
1277
989
|
try {
|
|
1278
990
|
await sendGameAction({
|
|
1279
991
|
client: wsClient,
|
|
1280
992
|
gameId: meta.gameId,
|
|
993
|
+
roomId: meta.roomId,
|
|
994
|
+
eventType: meta.eventType,
|
|
1281
995
|
actionType: action.actionType,
|
|
1282
996
|
actionData: action.actionData,
|
|
1283
997
|
turnSeq: meta.turnSeq,
|
|
1284
998
|
eventId: meta.eventId,
|
|
1285
|
-
traceId: meta.traceId
|
|
999
|
+
traceId: meta.traceId,
|
|
1000
|
+
promptPolicyVersion: meta.promptPolicyVersion,
|
|
1001
|
+
renderedPromptHash: meta.renderedPromptHash,
|
|
1002
|
+
parseSource: source,
|
|
1003
|
+
rawResponseHash: responseHash,
|
|
1004
|
+
rawResponsePreview: rawResponse ? rawResponsePreview(rawResponse) : void 0
|
|
1286
1005
|
});
|
|
1287
1006
|
log?.info?.(
|
|
1288
|
-
`[GAME-ACTION] submit ok source=${source} gameId=${meta.gameId} eventId=${meta.eventId} elapsedMs=${Date.now() - start}`
|
|
1007
|
+
`[GAME-ACTION] submit ok source=${source} gameId=${meta.gameId} eventId=${meta.eventId} promptPolicyVersion=${meta.promptPolicyVersion ?? ""} renderedPromptHash=${meta.renderedPromptHash ?? ""} rawResponseHash=${responseHash ?? ""} elapsedMs=${Date.now() - start}`
|
|
1289
1008
|
);
|
|
1290
1009
|
} catch (err) {
|
|
1291
1010
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -1451,6 +1170,12 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
1451
1170
|
const gameMeta = isGameEvent ? envelope.metadata : null;
|
|
1452
1171
|
let gameSubmitted = false;
|
|
1453
1172
|
let gameFallbackReason = null;
|
|
1173
|
+
const gameBuffer = [];
|
|
1174
|
+
if (isGameEvent && gameMeta) {
|
|
1175
|
+
ctx.log?.info?.(
|
|
1176
|
+
`[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
|
+
);
|
|
1178
|
+
}
|
|
1454
1179
|
try {
|
|
1455
1180
|
const isGroup = envelope.conversationId.startsWith("group:");
|
|
1456
1181
|
const peer = isGroup ? { kind: "group", id: envelope.group?.groupId ?? envelope.conversationId } : { kind: "direct", id: envelope.conversationId };
|
|
@@ -1530,10 +1255,15 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
1530
1255
|
ctx.log?.warn(`recordInboundSession failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1531
1256
|
}
|
|
1532
1257
|
});
|
|
1533
|
-
const gameBuffer = [];
|
|
1534
1258
|
await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
1535
1259
|
ctx: ctxPayload,
|
|
1536
1260
|
cfg: ctx.cfg,
|
|
1261
|
+
// 群聊强制走 automatic:CoolClaw 群聊业务语义就是 @ 即回,
|
|
1262
|
+
// 而 OpenClaw 默认对 group/channel 走 message_tool_only,
|
|
1263
|
+
// 模型若不主动调 message 工具就会被 runtime 静默吞掉,
|
|
1264
|
+
// 用户感知为"小甲群里不回"。这里按消息粒度覆盖,
|
|
1265
|
+
// 不污染用户全局 openclaw.json,私聊保持 SDK 默认。
|
|
1266
|
+
replyOptions: isGroup ? { sourceReplyDeliveryMode: "automatic" } : void 0,
|
|
1537
1267
|
dispatcherOptions: {
|
|
1538
1268
|
deliver: async (payload) => {
|
|
1539
1269
|
if (!payload.text) return;
|
|
@@ -1543,13 +1273,21 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
1543
1273
|
const full = gameBuffer.join("");
|
|
1544
1274
|
const parsed = parseAgentAction(full);
|
|
1545
1275
|
if ("error" in parsed) return;
|
|
1276
|
+
const validation = validateAgentAction(parsed, gameMeta.agentTask);
|
|
1277
|
+
if (!validation.ok) {
|
|
1278
|
+
ctx.log?.warn?.(
|
|
1279
|
+
`[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
|
+
);
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1546
1283
|
gameSubmitted = true;
|
|
1547
1284
|
await submitGameActionWithLog(
|
|
1548
|
-
|
|
1285
|
+
validation.action,
|
|
1549
1286
|
gameMeta,
|
|
1550
1287
|
wsClient,
|
|
1551
1288
|
ctx.log,
|
|
1552
|
-
"llm"
|
|
1289
|
+
"llm",
|
|
1290
|
+
full
|
|
1553
1291
|
);
|
|
1554
1292
|
return;
|
|
1555
1293
|
}
|
|
@@ -1582,18 +1320,32 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
1582
1320
|
gameFallbackReason = `dispatch_error: ${errMsg}`;
|
|
1583
1321
|
}
|
|
1584
1322
|
} finally {
|
|
1323
|
+
if (isGameEvent && gameMeta) {
|
|
1324
|
+
const rawResponse = gameBuffer.join("");
|
|
1325
|
+
ctx.log?.info?.(
|
|
1326
|
+
`[GAME-TASK] model-output eventId=${gameMeta.eventId} promptPolicyVersion=${gameMeta.promptPolicyVersion ?? ""} renderedPromptHash=${gameMeta.renderedPromptHash ?? ""} rawHash=${rawResponse ? sha256Hex(rawResponse) : ""} rawPreview=${rawResponsePreview(rawResponse) ?? ""}`
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1585
1329
|
if (isGameEvent && gameMeta && !gameSubmitted) {
|
|
1586
1330
|
try {
|
|
1587
|
-
const fb =
|
|
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
|
+
const rawResponse = gameBuffer.join("");
|
|
1588
1339
|
ctx.log?.warn?.(
|
|
1589
|
-
`[GAME-ACTION]
|
|
1340
|
+
`[GAME-ACTION] backend fallback eventType=${gameMeta.eventType} eventId=${gameMeta.eventId} reason=${gameFallbackReason ?? "unknown"} promptPolicyVersion=${gameMeta.promptPolicyVersion ?? ""} renderedPromptHash=${gameMeta.renderedPromptHash ?? ""} rawResponseHash=${rawResponse ? sha256Hex(rawResponse) : ""}`
|
|
1590
1341
|
);
|
|
1591
1342
|
await submitGameActionWithLog(
|
|
1592
1343
|
fb,
|
|
1593
1344
|
gameMeta,
|
|
1594
1345
|
wsClient,
|
|
1595
1346
|
ctx.log,
|
|
1596
|
-
"
|
|
1347
|
+
"backend_fallback",
|
|
1348
|
+
rawResponse || void 0
|
|
1597
1349
|
);
|
|
1598
1350
|
} catch (fbErr) {
|
|
1599
1351
|
ctx.log?.error?.(
|
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": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "OpenClaw native channel plugin for Riddle/CoolClaw chat.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"runtimeSetupEntry": "./dist/setup-entry.js",
|
|
72
72
|
"install": {
|
|
73
73
|
"npmSpec": "@coolclaw/coolclaw",
|
|
74
|
-
"expectedIntegrity": "sha512-
|
|
74
|
+
"expectedIntegrity": "sha512-6gIxAI4i3xJ4LpzViGi1KCJvc20JA/3Ob4OVdClgHCPydrkDqzOPbhSea4tpSlkUB5mWh/iEEzKhMcKqM24vVg==",
|
|
75
75
|
"defaultChoice": "npm",
|
|
76
76
|
"minHostVersion": ">=2026.3.22"
|
|
77
77
|
},
|
|
@@ -99,4 +99,4 @@
|
|
|
99
99
|
"pluginSdkVersion": "2026.4.29"
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
|
-
}
|
|
102
|
+
}
|