@coolclaw/coolclaw 1.0.2 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,134 @@ 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
|
+
const option = task.actionContract.options.find((candidate) => candidate.actionType === action.actionType);
|
|
154
|
+
if (!option) {
|
|
155
|
+
return { ok: false, reason: "disallowed_action_type" };
|
|
156
|
+
}
|
|
157
|
+
if (!matchesActionDataSchema(action.actionData, option.actionDataSchema)) {
|
|
158
|
+
return { ok: false, reason: "invalid_action_shape" };
|
|
159
|
+
}
|
|
160
|
+
return { ok: true, action };
|
|
161
|
+
}
|
|
162
|
+
function backendFallbackAction(task) {
|
|
163
|
+
const fallback = task.fallbackAction;
|
|
164
|
+
if (!isRecord(fallback) || typeof fallback.actionType !== "string") return null;
|
|
165
|
+
if (!isRecord(fallback.actionData)) return null;
|
|
166
|
+
const parsed = {
|
|
167
|
+
actionType: fallback.actionType,
|
|
168
|
+
actionData: fallback.actionData
|
|
169
|
+
};
|
|
170
|
+
const validated = validateAgentAction(parsed, task);
|
|
171
|
+
return validated.ok ? validated.action : null;
|
|
172
|
+
}
|
|
173
|
+
function sha256Hex(text) {
|
|
174
|
+
return createHash("sha256").update(text).digest("hex");
|
|
175
|
+
}
|
|
176
|
+
function rawResponsePreview(text, maxChars = 5e3) {
|
|
177
|
+
if (text.length === 0) return void 0;
|
|
178
|
+
return text.length <= maxChars ? text : `${text.slice(0, maxChars)}...`;
|
|
179
|
+
}
|
|
180
|
+
function normalizeActionContract(value) {
|
|
181
|
+
if (!isRecord(value)) return { options: [] };
|
|
182
|
+
const options = Array.isArray(value.options) ? value.options.flatMap((option) => normalizeActionOption(option)) : [];
|
|
183
|
+
return {
|
|
184
|
+
options,
|
|
185
|
+
finalOutputRules: Array.isArray(value.finalOutputRules) ? value.finalOutputRules.filter((rule) => typeof rule === "string") : void 0
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function normalizeActionOption(value) {
|
|
189
|
+
if (!isRecord(value) || typeof value.actionType !== "string" || value.actionType.length === 0) {
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
return [{
|
|
193
|
+
actionType: value.actionType,
|
|
194
|
+
actionDataSchema: isRecord(value.actionDataSchema) ? value.actionDataSchema : void 0,
|
|
195
|
+
description: readString(value.description)
|
|
196
|
+
}];
|
|
197
|
+
}
|
|
198
|
+
function allowedActionTypes(task) {
|
|
199
|
+
return task.actionContract.options.map((option) => option.actionType);
|
|
200
|
+
}
|
|
201
|
+
function matchesActionDataSchema(actionData, schema) {
|
|
202
|
+
if (!schema || Object.keys(schema).length === 0) return true;
|
|
203
|
+
if (typeof schema.type === "string" && schema.type !== "object") return false;
|
|
204
|
+
const properties = isRecord(schema.properties) ? schema.properties : {};
|
|
205
|
+
const required = Array.isArray(schema.required) ? schema.required.filter((field) => typeof field === "string" && field.length > 0) : [];
|
|
206
|
+
for (const field of required) {
|
|
207
|
+
if (!Object.prototype.hasOwnProperty.call(actionData, field)) return false;
|
|
208
|
+
if (!matchesSchemaValue(actionData[field], properties[field], true)) return false;
|
|
209
|
+
}
|
|
210
|
+
for (const [field, value] of Object.entries(actionData)) {
|
|
211
|
+
const propertySchema = properties[field];
|
|
212
|
+
if (propertySchema !== void 0 && !matchesSchemaValue(value, propertySchema, false)) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
function matchesSchemaValue(value, schema, required) {
|
|
219
|
+
if (value == null) return !required;
|
|
220
|
+
if (!isRecord(schema)) return true;
|
|
221
|
+
if (Array.isArray(schema.enum) && !schema.enum.includes(value)) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
switch (schema.type) {
|
|
225
|
+
case "integer":
|
|
226
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
227
|
+
case "number":
|
|
228
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
229
|
+
case "string":
|
|
230
|
+
return typeof value === "string" && (typeof schema.minLength !== "number" || value.length >= schema.minLength) && (typeof schema.maxLength !== "number" || value.length <= schema.maxLength);
|
|
231
|
+
case "boolean":
|
|
232
|
+
return typeof value === "boolean";
|
|
233
|
+
case "object":
|
|
234
|
+
return isRecord(value);
|
|
235
|
+
case "array":
|
|
236
|
+
return Array.isArray(value);
|
|
237
|
+
default:
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function readString(value) {
|
|
242
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
243
|
+
}
|
|
244
|
+
function readNumber(value) {
|
|
245
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
246
|
+
}
|
|
247
|
+
function isRecord(value) {
|
|
248
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
249
|
+
}
|
|
250
|
+
|
|
123
251
|
// src/frame-codec.ts
|
|
124
252
|
import { randomUUID } from "crypto";
|
|
125
253
|
var CoolclawFrameDecodeError = class extends Error {
|
|
@@ -147,7 +275,7 @@ function decodeFrame(raw) {
|
|
|
147
275
|
} catch (error) {
|
|
148
276
|
throw new CoolclawFrameDecodeError(`Invalid CoolClaw frame JSON: ${error.message}`);
|
|
149
277
|
}
|
|
150
|
-
if (!
|
|
278
|
+
if (!isRecord2(value)) {
|
|
151
279
|
throw new CoolclawFrameDecodeError("Invalid CoolClaw frame: expected object");
|
|
152
280
|
}
|
|
153
281
|
if (!("v" in value)) {
|
|
@@ -182,304 +310,10 @@ function decodeFrame(raw) {
|
|
|
182
310
|
}
|
|
183
311
|
return frame;
|
|
184
312
|
}
|
|
185
|
-
function
|
|
313
|
+
function isRecord2(value) {
|
|
186
314
|
return typeof value === "object" && value !== null;
|
|
187
315
|
}
|
|
188
316
|
|
|
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
317
|
// src/inbound.ts
|
|
484
318
|
function mapInboundFrame(frame) {
|
|
485
319
|
if (frame.type === "PRIVATE_MESSAGE") {
|
|
@@ -533,12 +367,21 @@ function mapInboundFrame(frame) {
|
|
|
533
367
|
}
|
|
534
368
|
async function handleInboundFrame(input) {
|
|
535
369
|
const envelope = mapInboundFrame(input.frame);
|
|
536
|
-
if (!envelope)
|
|
537
|
-
|
|
370
|
+
if (!envelope) {
|
|
371
|
+
await ackFrameSeq(input);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
const ackAfterDispatch = envelope.metadata.gameEvent === true;
|
|
375
|
+
if (!ackAfterDispatch) {
|
|
376
|
+
await ackProcessedSeq(input, envelope);
|
|
377
|
+
}
|
|
538
378
|
await input.dispatch(envelope);
|
|
379
|
+
if (ackAfterDispatch) {
|
|
380
|
+
await ackProcessedSeq(input, envelope);
|
|
381
|
+
}
|
|
539
382
|
}
|
|
540
383
|
function mapNotificationFrame(frame) {
|
|
541
|
-
const payload =
|
|
384
|
+
const payload = isRecord3(frame.payload) ? frame.payload : {};
|
|
542
385
|
const seq = typeof payload.seq === "number" ? payload.seq : void 0;
|
|
543
386
|
if (frame.type === "SYSTEM_NOTIFICATION") {
|
|
544
387
|
const title = typeof payload.title === "string" ? payload.title : "System";
|
|
@@ -588,7 +431,8 @@ function mapNotificationFrame(frame) {
|
|
|
588
431
|
}
|
|
589
432
|
function mapGameEventFrame(frame, payload) {
|
|
590
433
|
const eventType = typeof payload.eventType === "string" ? payload.eventType : "UNKNOWN";
|
|
591
|
-
|
|
434
|
+
const agentTask = normalizeAgentTask(payload.agentTask);
|
|
435
|
+
if (!agentTask || !isDispatchableAgentTask(agentTask)) {
|
|
592
436
|
return null;
|
|
593
437
|
}
|
|
594
438
|
let eventDataObj = {};
|
|
@@ -596,10 +440,10 @@ function mapGameEventFrame(frame, payload) {
|
|
|
596
440
|
if (typeof rawEventData === "string" && rawEventData.length > 0) {
|
|
597
441
|
try {
|
|
598
442
|
const parsed = JSON.parse(rawEventData);
|
|
599
|
-
if (
|
|
443
|
+
if (isRecord3(parsed)) eventDataObj = parsed;
|
|
600
444
|
} catch {
|
|
601
445
|
}
|
|
602
|
-
} else if (
|
|
446
|
+
} else if (isRecord3(rawEventData)) {
|
|
603
447
|
eventDataObj = rawEventData;
|
|
604
448
|
}
|
|
605
449
|
const gameId = Number(payload.gameId ?? 0);
|
|
@@ -618,16 +462,17 @@ function mapGameEventFrame(frame, payload) {
|
|
|
618
462
|
traceId,
|
|
619
463
|
eventType,
|
|
620
464
|
eventData: eventDataObj,
|
|
465
|
+
agentTask,
|
|
466
|
+
promptPolicyVersion: agentTask.promptPolicyVersion,
|
|
467
|
+
renderedPromptHash: agentTask.renderedPromptHash,
|
|
621
468
|
deadlineEpochMs,
|
|
622
469
|
sourceFrameId: frame.id
|
|
623
470
|
};
|
|
624
471
|
return {
|
|
625
472
|
id: eventId,
|
|
626
473
|
channel: "coolclaw",
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
conversationId: `game:${roomId}:${gameId}:${eventType}`,
|
|
630
|
-
text: buildGameEventPrompt(eventType, eventDataObj),
|
|
474
|
+
conversationId: agentTask.conversationKey ?? `game:${roomId}:${gameId}:task:${eventId}`,
|
|
475
|
+
text: agentTask.renderedPrompt,
|
|
631
476
|
messageType: "GAME_EVENT",
|
|
632
477
|
seq,
|
|
633
478
|
shouldReply: true,
|
|
@@ -636,52 +481,64 @@ function mapGameEventFrame(frame, payload) {
|
|
|
636
481
|
}
|
|
637
482
|
async function ackProcessedSeq(input, envelope) {
|
|
638
483
|
if (typeof envelope.seq === "number") {
|
|
639
|
-
|
|
640
|
-
await input.sendAck(createFrame("ACK", { lastAckedSeq }));
|
|
484
|
+
await ackSeq(input, envelope.seq);
|
|
641
485
|
}
|
|
642
486
|
}
|
|
487
|
+
async function ackFrameSeq(input) {
|
|
488
|
+
const payload = isRecord3(input.frame.payload) ? input.frame.payload : {};
|
|
489
|
+
const seq = typeof payload.seq === "number" ? payload.seq : void 0;
|
|
490
|
+
if (typeof seq === "number") {
|
|
491
|
+
await ackSeq(input, seq);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
async function ackSeq(input, seq) {
|
|
495
|
+
const current = await input.ackStore.getLastAckedSeq(input.accountKey);
|
|
496
|
+
const lastAckedSeq = seq <= current ? current : seq;
|
|
497
|
+
await input.sendAck(createFrame("ACK", { lastAckedSeq }));
|
|
498
|
+
await input.ackStore.record(input.accountKey, seq);
|
|
499
|
+
}
|
|
643
500
|
function assertPrivatePayload(value) {
|
|
644
|
-
if (!
|
|
501
|
+
if (!isRecord3(value) || !isUserRef(value.sender) || !isUserRef(value.recipient)) {
|
|
645
502
|
throw new Error("Invalid PRIVATE_MESSAGE payload");
|
|
646
503
|
}
|
|
647
504
|
return {
|
|
648
|
-
seq:
|
|
649
|
-
messageId:
|
|
650
|
-
conversationId:
|
|
505
|
+
seq: readNumber2(value, "seq"),
|
|
506
|
+
messageId: readString2(value, "messageId"),
|
|
507
|
+
conversationId: readString2(value, "conversationId"),
|
|
651
508
|
sender: value.sender,
|
|
652
509
|
recipient: value.recipient,
|
|
653
|
-
messageType:
|
|
654
|
-
content:
|
|
510
|
+
messageType: readString2(value, "messageType"),
|
|
511
|
+
content: readString2(value, "content"),
|
|
655
512
|
mentioned: readBoolean(value, "mentioned"),
|
|
656
|
-
sentAt:
|
|
513
|
+
sentAt: readString2(value, "sentAt")
|
|
657
514
|
};
|
|
658
515
|
}
|
|
659
516
|
function assertGroupPayload(value) {
|
|
660
|
-
if (!
|
|
517
|
+
if (!isRecord3(value) || !isUserRef(value.sender)) {
|
|
661
518
|
throw new Error("Invalid GROUP_MESSAGE payload");
|
|
662
519
|
}
|
|
663
520
|
return {
|
|
664
|
-
seq:
|
|
665
|
-
messageId:
|
|
666
|
-
groupId:
|
|
667
|
-
groupName:
|
|
668
|
-
conversationId:
|
|
521
|
+
seq: readNumber2(value, "seq"),
|
|
522
|
+
messageId: readString2(value, "messageId"),
|
|
523
|
+
groupId: readString2(value, "groupId"),
|
|
524
|
+
groupName: readString2(value, "groupName"),
|
|
525
|
+
conversationId: readString2(value, "conversationId"),
|
|
669
526
|
sender: value.sender,
|
|
670
|
-
messageType:
|
|
671
|
-
content:
|
|
527
|
+
messageType: readString2(value, "messageType"),
|
|
528
|
+
content: readString2(value, "content"),
|
|
672
529
|
mentioned: readBoolean(value, "mentioned"),
|
|
673
|
-
sentAt:
|
|
530
|
+
sentAt: readString2(value, "sentAt"),
|
|
674
531
|
agentHint: readOptionalString(value, "agentHint")
|
|
675
532
|
};
|
|
676
533
|
}
|
|
677
|
-
function
|
|
534
|
+
function readString2(source, key) {
|
|
678
535
|
const value = source[key];
|
|
679
536
|
if (typeof value !== "string" || value.length === 0) {
|
|
680
537
|
throw new Error(`Invalid inbound payload: missing ${key}`);
|
|
681
538
|
}
|
|
682
539
|
return value;
|
|
683
540
|
}
|
|
684
|
-
function
|
|
541
|
+
function readNumber2(source, key) {
|
|
685
542
|
const value = source[key];
|
|
686
543
|
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
687
544
|
throw new Error(`Invalid inbound payload: missing ${key}`);
|
|
@@ -706,9 +563,9 @@ function readOptionalString(source, key) {
|
|
|
706
563
|
return value;
|
|
707
564
|
}
|
|
708
565
|
function isUserRef(value) {
|
|
709
|
-
return
|
|
566
|
+
return isRecord3(value) && typeof value.userId === "string" && (value.userType === "HUMAN" || value.userType === "AGENT") && (value.displayName === void 0 || typeof value.displayName === "string");
|
|
710
567
|
}
|
|
711
|
-
function
|
|
568
|
+
function isRecord3(value) {
|
|
712
569
|
return typeof value === "object" && value !== null;
|
|
713
570
|
}
|
|
714
571
|
|
|
@@ -833,13 +690,23 @@ async function sendMedia(input) {
|
|
|
833
690
|
async function sendGameAction(input) {
|
|
834
691
|
const frame = createFrame("GAME_ACTION", {
|
|
835
692
|
gameId: input.gameId,
|
|
693
|
+
roomId: input.roomId,
|
|
694
|
+
eventType: input.eventType,
|
|
836
695
|
actionType: input.actionType,
|
|
837
696
|
actionData: input.actionData,
|
|
838
697
|
// AgentActionRequest.timestamp 契约为 String
|
|
839
698
|
timestamp: String(Date.now()),
|
|
840
699
|
turnSeq: input.turnSeq,
|
|
841
700
|
eventId: input.eventId,
|
|
842
|
-
traceId: input.traceId
|
|
701
|
+
traceId: input.traceId,
|
|
702
|
+
promptPolicyVersion: input.promptPolicyVersion,
|
|
703
|
+
renderedPromptHash: input.renderedPromptHash,
|
|
704
|
+
parseSource: input.parseSource,
|
|
705
|
+
rawResponseHash: input.rawResponseHash,
|
|
706
|
+
rawResponsePreview: input.rawResponsePreview,
|
|
707
|
+
modelActionRejected: input.modelActionRejected,
|
|
708
|
+
modelActionType: input.modelActionType,
|
|
709
|
+
validationReason: input.validationReason
|
|
843
710
|
});
|
|
844
711
|
const response = await input.client.request(frame);
|
|
845
712
|
if (response.ok === false) {
|
|
@@ -849,7 +716,7 @@ async function sendGameAction(input) {
|
|
|
849
716
|
|
|
850
717
|
// src/game-action-parser.ts
|
|
851
718
|
function extractActionBlock(text) {
|
|
852
|
-
const re = /<ACTION>\s*([\s\S]+?)\s*<\/ACTION>/
|
|
719
|
+
const re = /<ACTION>\s*([\s\S]+?)\s*<\/ACTION>/gi;
|
|
853
720
|
let match;
|
|
854
721
|
let last = null;
|
|
855
722
|
while ((match = re.exec(text)) !== null) {
|
|
@@ -857,6 +724,11 @@ function extractActionBlock(text) {
|
|
|
857
724
|
}
|
|
858
725
|
return last;
|
|
859
726
|
}
|
|
727
|
+
function normalizeActionBlock(body) {
|
|
728
|
+
const trimmed = body.trim();
|
|
729
|
+
const fenced = /^```(?:json)?\s*([\s\S]*?)\s*```$/i.exec(trimmed);
|
|
730
|
+
return fenced ? fenced[1].trim() : trimmed;
|
|
731
|
+
}
|
|
860
732
|
function parseAgentAction(text) {
|
|
861
733
|
if (typeof text !== "string" || text.length === 0) {
|
|
862
734
|
return { error: "no_action_block" };
|
|
@@ -867,7 +739,7 @@ function parseAgentAction(text) {
|
|
|
867
739
|
}
|
|
868
740
|
let obj;
|
|
869
741
|
try {
|
|
870
|
-
obj = JSON.parse(body);
|
|
742
|
+
obj = JSON.parse(normalizeActionBlock(body));
|
|
871
743
|
} catch (e) {
|
|
872
744
|
return { error: "invalid_json", detail: e instanceof Error ? e.message : String(e) };
|
|
873
745
|
}
|
|
@@ -887,105 +759,44 @@ function parseAgentAction(text) {
|
|
|
887
759
|
};
|
|
888
760
|
}
|
|
889
761
|
|
|
890
|
-
// src/game-action-
|
|
891
|
-
function
|
|
892
|
-
|
|
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
|
-
});
|
|
762
|
+
// src/game-action-audit.ts
|
|
763
|
+
function normalizeAuditText(value, maxChars = 256) {
|
|
764
|
+
if (!value) return void 0;
|
|
765
|
+
return value.length <= maxChars ? value : value.slice(0, maxChars);
|
|
911
766
|
}
|
|
912
|
-
function
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
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: {} };
|
|
767
|
+
function inferRejectedModelAction(rawResponse, task) {
|
|
768
|
+
if (rawResponse.trim().length === 0) {
|
|
769
|
+
return {
|
|
770
|
+
modelActionRejected: false,
|
|
771
|
+
validationReason: "no_model_output"
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
const parsed = parseAgentAction(rawResponse);
|
|
775
|
+
if ("error" in parsed) {
|
|
776
|
+
return {
|
|
777
|
+
modelActionRejected: true,
|
|
778
|
+
validationReason: parseErrorReason(parsed)
|
|
779
|
+
};
|
|
988
780
|
}
|
|
781
|
+
const validation = validateAgentAction(parsed, task);
|
|
782
|
+
if (!validation.ok) {
|
|
783
|
+
return {
|
|
784
|
+
modelActionRejected: true,
|
|
785
|
+
modelActionType: parsed.actionType,
|
|
786
|
+
validationReason: validation.reason
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
return {
|
|
790
|
+
modelActionRejected: true,
|
|
791
|
+
modelActionType: parsed.actionType,
|
|
792
|
+
validationReason: "valid_action_not_submitted"
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
function parseErrorReason(error) {
|
|
796
|
+
if (error.error === "invalid_json") {
|
|
797
|
+
return normalizeAuditText(`invalid_json:${error.detail}`) ?? "invalid_json";
|
|
798
|
+
}
|
|
799
|
+
return error.error;
|
|
989
800
|
}
|
|
990
801
|
|
|
991
802
|
// src/ws-client.ts
|
|
@@ -1200,18 +1011,18 @@ var CoolclawWsClient = class {
|
|
|
1200
1011
|
}
|
|
1201
1012
|
};
|
|
1202
1013
|
function readPingInterval(payload) {
|
|
1203
|
-
if (!
|
|
1014
|
+
if (!isRecord4(payload) || typeof payload.pingIntervalMs !== "number" || payload.pingIntervalMs <= 0) {
|
|
1204
1015
|
return void 0;
|
|
1205
1016
|
}
|
|
1206
1017
|
return payload.pingIntervalMs;
|
|
1207
1018
|
}
|
|
1208
1019
|
function readErrorMessage(payload) {
|
|
1209
|
-
if (
|
|
1020
|
+
if (isRecord4(payload) && typeof payload.message === "string") {
|
|
1210
1021
|
return payload.message;
|
|
1211
1022
|
}
|
|
1212
1023
|
return "CoolClaw request failed";
|
|
1213
1024
|
}
|
|
1214
|
-
function
|
|
1025
|
+
function isRecord4(value) {
|
|
1215
1026
|
return typeof value === "object" && value !== null;
|
|
1216
1027
|
}
|
|
1217
1028
|
|
|
@@ -1265,35 +1076,75 @@ function logAckFailure(params) {
|
|
|
1265
1076
|
const target = params.target ? ` target=${params.target}` : "";
|
|
1266
1077
|
params.log(`${params.channel} ack cleanup failed${target}: ${String(params.error)}`);
|
|
1267
1078
|
}
|
|
1268
|
-
async function submitGameActionWithLog(action, meta, wsClient, log, source) {
|
|
1079
|
+
async function submitGameActionWithLog(action, meta, wsClient, log, source, rawResponse, auditMeta) {
|
|
1269
1080
|
if (!wsClient.isConnected()) {
|
|
1270
1081
|
log?.error?.(`[GAME-ACTION] submit skipped: ws not connected eventId=${meta.eventId}`);
|
|
1271
|
-
return;
|
|
1082
|
+
return false;
|
|
1272
1083
|
}
|
|
1084
|
+
const responseHash = rawResponse && rawResponse.length > 0 ? sha256Hex(rawResponse) : void 0;
|
|
1273
1085
|
log?.info?.(
|
|
1274
|
-
`[GAME-ACTION] submit start source=${source} eventType=${meta.eventType} actionType=${action.actionType} gameId=${meta.gameId} turnSeq=${meta.turnSeq} eventId=${meta.eventId}`
|
|
1086
|
+
`[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
1087
|
);
|
|
1276
1088
|
const start = Date.now();
|
|
1277
1089
|
try {
|
|
1278
1090
|
await sendGameAction({
|
|
1279
1091
|
client: wsClient,
|
|
1280
1092
|
gameId: meta.gameId,
|
|
1093
|
+
roomId: meta.roomId,
|
|
1094
|
+
eventType: meta.eventType,
|
|
1281
1095
|
actionType: action.actionType,
|
|
1282
1096
|
actionData: action.actionData,
|
|
1283
1097
|
turnSeq: meta.turnSeq,
|
|
1284
1098
|
eventId: meta.eventId,
|
|
1285
|
-
traceId: meta.traceId
|
|
1099
|
+
traceId: meta.traceId,
|
|
1100
|
+
promptPolicyVersion: meta.promptPolicyVersion,
|
|
1101
|
+
renderedPromptHash: meta.renderedPromptHash,
|
|
1102
|
+
parseSource: source,
|
|
1103
|
+
rawResponseHash: responseHash,
|
|
1104
|
+
rawResponsePreview: rawResponse ? rawResponsePreview(rawResponse) : void 0,
|
|
1105
|
+
modelActionRejected: auditMeta?.modelActionRejected,
|
|
1106
|
+
modelActionType: auditMeta?.modelActionType,
|
|
1107
|
+
validationReason: normalizeAuditText(auditMeta?.validationReason)
|
|
1286
1108
|
});
|
|
1287
1109
|
log?.info?.(
|
|
1288
|
-
`[GAME-ACTION] submit ok source=${source} gameId=${meta.gameId} eventId=${meta.eventId} elapsedMs=${Date.now() - start}`
|
|
1110
|
+
`[GAME-ACTION] submit ok source=${source} gameId=${meta.gameId} eventId=${meta.eventId} promptPolicyVersion=${meta.promptPolicyVersion ?? ""} renderedPromptHash=${meta.renderedPromptHash ?? ""} rawResponseHash=${responseHash ?? ""} elapsedMs=${Date.now() - start}`
|
|
1289
1111
|
);
|
|
1112
|
+
return true;
|
|
1290
1113
|
} catch (err) {
|
|
1291
1114
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1292
1115
|
log?.error?.(
|
|
1293
1116
|
`[GAME-ACTION] submit failed source=${source} eventId=${meta.eventId} elapsedMs=${Date.now() - start} err=${errMsg}`
|
|
1294
1117
|
);
|
|
1118
|
+
return false;
|
|
1295
1119
|
}
|
|
1296
1120
|
}
|
|
1121
|
+
async function submitBackendFallbackWithLog(params) {
|
|
1122
|
+
const fb = backendFallbackAction(params.meta.agentTask);
|
|
1123
|
+
if (!fb) {
|
|
1124
|
+
params.log?.warn?.(
|
|
1125
|
+
`[GAME-ACTION] backend fallback unavailable eventType=${params.meta.eventType} eventId=${params.meta.eventId} reason=${params.reason} promptPolicyVersion=${params.meta.promptPolicyVersion ?? ""} renderedPromptHash=${params.meta.renderedPromptHash ?? ""}`
|
|
1126
|
+
);
|
|
1127
|
+
return false;
|
|
1128
|
+
}
|
|
1129
|
+
const inferred = inferRejectedModelAction(params.rawResponse ?? "", params.meta.agentTask);
|
|
1130
|
+
const auditMeta = {
|
|
1131
|
+
modelActionRejected: params.auditMeta?.modelActionRejected ?? inferred.modelActionRejected,
|
|
1132
|
+
modelActionType: params.auditMeta?.modelActionType ?? inferred.modelActionType,
|
|
1133
|
+
validationReason: params.auditMeta?.validationReason ?? params.reason ?? inferred.validationReason
|
|
1134
|
+
};
|
|
1135
|
+
params.log?.warn?.(
|
|
1136
|
+
`[GAME-ACTION] backend fallback eventType=${params.meta.eventType} eventId=${params.meta.eventId} reason=${auditMeta.validationReason ?? "unknown"} promptPolicyVersion=${params.meta.promptPolicyVersion ?? ""} renderedPromptHash=${params.meta.renderedPromptHash ?? ""} rawResponseHash=${params.rawResponse ? sha256Hex(params.rawResponse) : ""}`
|
|
1137
|
+
);
|
|
1138
|
+
return submitGameActionWithLog(
|
|
1139
|
+
fb,
|
|
1140
|
+
params.meta,
|
|
1141
|
+
params.wsClient,
|
|
1142
|
+
params.log,
|
|
1143
|
+
"backend_fallback",
|
|
1144
|
+
params.rawResponse || void 0,
|
|
1145
|
+
auditMeta
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1297
1148
|
var runtimeClients = /* @__PURE__ */ new Map();
|
|
1298
1149
|
function setRuntimeClient(accountKey, client) {
|
|
1299
1150
|
runtimeClients.set(accountKey, client);
|
|
@@ -1441,16 +1292,36 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
1441
1292
|
accountKey,
|
|
1442
1293
|
ackStore,
|
|
1443
1294
|
dispatch: async (envelope) => {
|
|
1295
|
+
const isGameEvent = envelope.metadata?.gameEvent === true;
|
|
1296
|
+
const gameMeta = isGameEvent ? envelope.metadata : null;
|
|
1297
|
+
let gameSubmitted = false;
|
|
1298
|
+
let gameFallbackReason = null;
|
|
1299
|
+
let gameModelActionType;
|
|
1300
|
+
let gameValidationReason;
|
|
1301
|
+
let gameModelActionRejected;
|
|
1302
|
+
const gameBuffer = [];
|
|
1303
|
+
if (isGameEvent && gameMeta) {
|
|
1304
|
+
ctx.log?.info?.(
|
|
1305
|
+
`[GAME-TASK] dispatch start gameId=${gameMeta.gameId} roomId=${gameMeta.roomId} eventType=${gameMeta.eventType} eventId=${gameMeta.eventId} promptPolicyVersion=${gameMeta.promptPolicyVersion ?? ""} renderedPromptHash=${gameMeta.renderedPromptHash ?? ""} conversationId=${envelope.conversationId}`
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
1444
1308
|
const runtime = getCoolclawRuntime();
|
|
1445
1309
|
if (!runtime?.channel) {
|
|
1446
1310
|
logInboundDrop({ log: ctx.log?.warn?.bind(ctx.log) ?? (() => {
|
|
1447
1311
|
}), channel: "coolclaw", reason: "runtime not available; skipping dispatch" });
|
|
1312
|
+
if (isGameEvent && gameMeta) {
|
|
1313
|
+
const submitted = await submitBackendFallbackWithLog({
|
|
1314
|
+
meta: gameMeta,
|
|
1315
|
+
wsClient,
|
|
1316
|
+
log: ctx.log,
|
|
1317
|
+
reason: "runtime_not_available"
|
|
1318
|
+
});
|
|
1319
|
+
if (!submitted) {
|
|
1320
|
+
throw new Error("game fallback submit failed: runtime_not_available");
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1448
1323
|
return;
|
|
1449
1324
|
}
|
|
1450
|
-
const isGameEvent = envelope.metadata?.gameEvent === true;
|
|
1451
|
-
const gameMeta = isGameEvent ? envelope.metadata : null;
|
|
1452
|
-
let gameSubmitted = false;
|
|
1453
|
-
let gameFallbackReason = null;
|
|
1454
1325
|
try {
|
|
1455
1326
|
const isGroup = envelope.conversationId.startsWith("group:");
|
|
1456
1327
|
const peer = isGroup ? { kind: "group", id: envelope.group?.groupId ?? envelope.conversationId } : { kind: "direct", id: envelope.conversationId };
|
|
@@ -1530,7 +1401,6 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
1530
1401
|
ctx.log?.warn(`recordInboundSession failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1531
1402
|
}
|
|
1532
1403
|
});
|
|
1533
|
-
const gameBuffer = [];
|
|
1534
1404
|
await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
1535
1405
|
ctx: ctxPayload,
|
|
1536
1406
|
cfg: ctx.cfg,
|
|
@@ -1549,14 +1419,36 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
1549
1419
|
const full = gameBuffer.join("");
|
|
1550
1420
|
const parsed = parseAgentAction(full);
|
|
1551
1421
|
if ("error" in parsed) return;
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1422
|
+
const validation = validateAgentAction(parsed, gameMeta.agentTask);
|
|
1423
|
+
if (!validation.ok) {
|
|
1424
|
+
gameModelActionRejected = true;
|
|
1425
|
+
gameModelActionType = parsed.actionType;
|
|
1426
|
+
gameValidationReason = validation.reason;
|
|
1427
|
+
gameFallbackReason = validation.reason;
|
|
1428
|
+
ctx.log?.warn?.(
|
|
1429
|
+
`[GAME-ACTION] rejected model action reason=${validation.reason} actionType=${parsed.actionType} eventType=${gameMeta.eventType} eventId=${gameMeta.eventId} promptPolicyVersion=${gameMeta.promptPolicyVersion ?? ""} renderedPromptHash=${gameMeta.renderedPromptHash ?? ""} rawResponseHash=${sha256Hex(full)}`
|
|
1430
|
+
);
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
const submitted = await submitGameActionWithLog(
|
|
1434
|
+
validation.action,
|
|
1555
1435
|
gameMeta,
|
|
1556
1436
|
wsClient,
|
|
1557
1437
|
ctx.log,
|
|
1558
|
-
"llm"
|
|
1438
|
+
"llm",
|
|
1439
|
+
full,
|
|
1440
|
+
{
|
|
1441
|
+
modelActionRejected: false,
|
|
1442
|
+
modelActionType: validation.action.actionType
|
|
1443
|
+
}
|
|
1559
1444
|
);
|
|
1445
|
+
if (submitted) {
|
|
1446
|
+
gameSubmitted = true;
|
|
1447
|
+
} else {
|
|
1448
|
+
gameFallbackReason = `llm_action_submit_failed:${validation.action.actionType}`;
|
|
1449
|
+
gameModelActionRejected = false;
|
|
1450
|
+
gameModelActionType = validation.action.actionType;
|
|
1451
|
+
}
|
|
1560
1452
|
return;
|
|
1561
1453
|
}
|
|
1562
1454
|
try {
|
|
@@ -1579,7 +1471,9 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
1579
1471
|
}
|
|
1580
1472
|
});
|
|
1581
1473
|
if (isGameEvent && gameMeta && !gameSubmitted) {
|
|
1582
|
-
gameFallbackReason
|
|
1474
|
+
if (!gameFallbackReason) {
|
|
1475
|
+
gameFallbackReason = "no_valid_action_in_llm_output";
|
|
1476
|
+
}
|
|
1583
1477
|
}
|
|
1584
1478
|
} catch (err) {
|
|
1585
1479
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -1588,23 +1482,42 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
1588
1482
|
gameFallbackReason = `dispatch_error: ${errMsg}`;
|
|
1589
1483
|
}
|
|
1590
1484
|
} finally {
|
|
1485
|
+
if (isGameEvent && gameMeta) {
|
|
1486
|
+
const rawResponse = gameBuffer.join("");
|
|
1487
|
+
ctx.log?.info?.(
|
|
1488
|
+
`[GAME-TASK] model-output eventId=${gameMeta.eventId} promptPolicyVersion=${gameMeta.promptPolicyVersion ?? ""} renderedPromptHash=${gameMeta.renderedPromptHash ?? ""} rawHash=${rawResponse ? sha256Hex(rawResponse) : ""} rawPreview=${rawResponsePreview(rawResponse) ?? ""}`
|
|
1489
|
+
);
|
|
1490
|
+
}
|
|
1591
1491
|
if (isGameEvent && gameMeta && !gameSubmitted) {
|
|
1592
1492
|
try {
|
|
1593
|
-
const
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1493
|
+
const rawResponse = gameBuffer.join("");
|
|
1494
|
+
const inferred = inferRejectedModelAction(rawResponse, gameMeta.agentTask);
|
|
1495
|
+
gameModelActionRejected = gameModelActionRejected ?? inferred.modelActionRejected;
|
|
1496
|
+
gameModelActionType = gameModelActionType ?? inferred.modelActionType;
|
|
1497
|
+
gameValidationReason = gameValidationReason ?? inferred.validationReason;
|
|
1498
|
+
if (!gameFallbackReason || gameFallbackReason === "no_valid_action_in_llm_output") {
|
|
1499
|
+
gameFallbackReason = gameValidationReason ?? inferred.validationReason ?? gameFallbackReason;
|
|
1500
|
+
}
|
|
1501
|
+
const submitted = await submitBackendFallbackWithLog({
|
|
1502
|
+
meta: gameMeta,
|
|
1600
1503
|
wsClient,
|
|
1601
|
-
ctx.log,
|
|
1602
|
-
"
|
|
1603
|
-
|
|
1504
|
+
log: ctx.log,
|
|
1505
|
+
reason: gameFallbackReason ?? gameValidationReason ?? inferred.validationReason ?? "unknown",
|
|
1506
|
+
rawResponse: rawResponse || void 0,
|
|
1507
|
+
auditMeta: {
|
|
1508
|
+
modelActionRejected: gameModelActionRejected,
|
|
1509
|
+
modelActionType: gameModelActionType,
|
|
1510
|
+
validationReason: gameFallbackReason ?? gameValidationReason ?? inferred.validationReason
|
|
1511
|
+
}
|
|
1512
|
+
});
|
|
1513
|
+
if (!submitted) {
|
|
1514
|
+
throw new Error(`game fallback submit failed: ${gameFallbackReason ?? "unknown"}`);
|
|
1515
|
+
}
|
|
1604
1516
|
} catch (fbErr) {
|
|
1605
1517
|
ctx.log?.error?.(
|
|
1606
1518
|
`[GAME-ACTION] fallback submit threw eventId=${gameMeta.eventId} err=${fbErr instanceof Error ? fbErr.message : String(fbErr)}`
|
|
1607
1519
|
);
|
|
1520
|
+
throw fbErr;
|
|
1608
1521
|
}
|
|
1609
1522
|
}
|
|
1610
1523
|
}
|
|
@@ -1616,6 +1529,7 @@ var coolclawChannelPlugin = createChatChannelPlugin({
|
|
|
1616
1529
|
} catch (err) {
|
|
1617
1530
|
logAckFailure({ log: ctx.log?.warn?.bind(ctx.log) ?? (() => {
|
|
1618
1531
|
}), channel: "coolclaw", error: err });
|
|
1532
|
+
throw err;
|
|
1619
1533
|
}
|
|
1620
1534
|
}
|
|
1621
1535
|
});
|
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.5",
|
|
4
4
|
"description": "OpenClaw native channel plugin for Riddle/CoolClaw chat.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "tsup index.ts setup-entry.ts cli-metadata.ts --format esm --dts --out-dir dist --clean --splitting",
|
|
40
40
|
"test": "vitest run",
|
|
41
|
-
"lint": "tsc --noEmit"
|
|
41
|
+
"lint": "tsc --noEmit",
|
|
42
|
+
"prepack": "npm run build"
|
|
42
43
|
},
|
|
43
44
|
"dependencies": {
|
|
44
45
|
"ws": "^8.20.1",
|
|
@@ -71,7 +72,7 @@
|
|
|
71
72
|
"runtimeSetupEntry": "./dist/setup-entry.js",
|
|
72
73
|
"install": {
|
|
73
74
|
"npmSpec": "@coolclaw/coolclaw",
|
|
74
|
-
"expectedIntegrity": "sha512-
|
|
75
|
+
"expectedIntegrity": "sha512-6q6+KPGtAkHBZGsHgqEXJtnwLdVoZNLrZNbxZmzKisZvItoE2y+Acl9v3bNtqtEK/DR5NLahyloy7FMRVt9Upg==",
|
|
75
76
|
"defaultChoice": "npm",
|
|
76
77
|
"minHostVersion": ">=2026.3.22"
|
|
77
78
|
},
|