pokertools-arena 0.13.0 → 0.16.2
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 +2 -2
- package/dist/app.js +365 -121
- package/dist/index.html +9 -2
- package/dist/pokertools-arena.html +396 -123
- package/dist/styles.css +24 -2
- package/docs/diagnostics/SUMMARY.md +3 -1
- package/docs/diagnostics/summary.json +1 -1
- package/docs/releases/RELEASE_NOTES.md +83 -0
- package/package.json +2 -2
- package/src/app.js +179 -71
- package/src/config/arena-config.js +1 -1
- package/src/index.html +9 -2
- package/src/lib/decision-core.js +192 -27
- package/src/styles.css +24 -2
package/dist/app.js
CHANGED
|
@@ -114149,7 +114149,7 @@ var BASE_CONFIG = {
|
|
|
114149
114149
|
bigBlind: 50,
|
|
114150
114150
|
ante: 5,
|
|
114151
114151
|
handsPerLevel: 8,
|
|
114152
|
-
blindMultiplier:
|
|
114152
|
+
blindMultiplier: 2
|
|
114153
114153
|
},
|
|
114154
114154
|
timing: {
|
|
114155
114155
|
actionSeconds: 30,
|
|
@@ -114339,6 +114339,99 @@ async function fetchJsonWithRetry(url, options, { maxRetries = 1 } = {}) {
|
|
|
114339
114339
|
throw new ArenaRequestError(message, { status: response.status, category: requestErrorCategory(response.status), payload, incidents });
|
|
114340
114340
|
}
|
|
114341
114341
|
}
|
|
114342
|
+
async function readStreamingPayload(response, onDelta) {
|
|
114343
|
+
const contentType = response.headers?.get?.("content-type") || "";
|
|
114344
|
+
if (!contentType.includes("text/event-stream") || typeof response.body?.getReader !== "function") {
|
|
114345
|
+
return { payload: await response.json().catch(() => ({})), reasoning: "" };
|
|
114346
|
+
}
|
|
114347
|
+
const reader = response.body.getReader();
|
|
114348
|
+
const decoder = new TextDecoder();
|
|
114349
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
114350
|
+
let buffer = "", content = "", reasoning = "", finishReason = null, usage = null, model = null, provider = null;
|
|
114351
|
+
while (true) {
|
|
114352
|
+
const { value, done } = await reader.read();
|
|
114353
|
+
if (done) break;
|
|
114354
|
+
buffer += decoder.decode(value, { stream: true });
|
|
114355
|
+
let reasoningChanged = false, contentChanged = false;
|
|
114356
|
+
let newline;
|
|
114357
|
+
while ((newline = buffer.indexOf("\n")) >= 0) {
|
|
114358
|
+
const line = buffer.slice(0, newline).trim();
|
|
114359
|
+
buffer = buffer.slice(newline + 1);
|
|
114360
|
+
if (!line.startsWith("data:")) continue;
|
|
114361
|
+
const data = line.slice(5).trim();
|
|
114362
|
+
if (!data || data === "[DONE]") continue;
|
|
114363
|
+
let chunk;
|
|
114364
|
+
try {
|
|
114365
|
+
chunk = JSON.parse(data);
|
|
114366
|
+
} catch {
|
|
114367
|
+
continue;
|
|
114368
|
+
}
|
|
114369
|
+
if (chunk.model) model = chunk.model;
|
|
114370
|
+
if (chunk.provider) provider = chunk.provider;
|
|
114371
|
+
if (chunk.usage) usage = chunk.usage;
|
|
114372
|
+
const choice = chunk.choices?.[0];
|
|
114373
|
+
if (!choice) continue;
|
|
114374
|
+
if (choice.finish_reason) finishReason = choice.finish_reason;
|
|
114375
|
+
const delta = choice.delta || {};
|
|
114376
|
+
if (typeof delta.reasoning === "string" && delta.reasoning) {
|
|
114377
|
+
reasoning += delta.reasoning;
|
|
114378
|
+
reasoningChanged = true;
|
|
114379
|
+
}
|
|
114380
|
+
if (typeof delta.content === "string" && delta.content) {
|
|
114381
|
+
content += delta.content;
|
|
114382
|
+
contentChanged = true;
|
|
114383
|
+
}
|
|
114384
|
+
for (const call of delta.tool_calls || []) {
|
|
114385
|
+
const index = Number(call.index ?? 0);
|
|
114386
|
+
const current = toolCalls.get(index) || { id: "", name: "", args: "" };
|
|
114387
|
+
if (call.id) current.id = call.id;
|
|
114388
|
+
if (call.function?.name) current.name += call.function.name;
|
|
114389
|
+
if (typeof call.function?.arguments === "string") current.args += call.function.arguments;
|
|
114390
|
+
toolCalls.set(index, current);
|
|
114391
|
+
}
|
|
114392
|
+
}
|
|
114393
|
+
if (reasoningChanged) onDelta?.({ channel: "reasoning", text: reasoning });
|
|
114394
|
+
if (contentChanged) onDelta?.({ channel: "content", text: content });
|
|
114395
|
+
}
|
|
114396
|
+
const calls = [...toolCalls.entries()].sort((a, b) => a[0] - b[0]).map(([, call]) => ({ id: call.id, type: "function", function: { name: call.name, arguments: call.args } }));
|
|
114397
|
+
return {
|
|
114398
|
+
payload: {
|
|
114399
|
+
model,
|
|
114400
|
+
provider,
|
|
114401
|
+
usage: usage ?? null,
|
|
114402
|
+
choices: [{ finish_reason: finishReason, message: { role: "assistant", content: content || null, ...calls.length ? { tool_calls: calls } : {} } }]
|
|
114403
|
+
},
|
|
114404
|
+
reasoning
|
|
114405
|
+
};
|
|
114406
|
+
}
|
|
114407
|
+
async function fetchStreamingJson(url, options, { maxRetries = 1, onDelta = null } = {}) {
|
|
114408
|
+
const incidents = [];
|
|
114409
|
+
for (let attempt = 0; ; attempt++) {
|
|
114410
|
+
if (attempt > 0) diagnosticsCounters.retries++;
|
|
114411
|
+
diagnosticsCounters.requests++;
|
|
114412
|
+
let response;
|
|
114413
|
+
try {
|
|
114414
|
+
response = await fetch(url, options);
|
|
114415
|
+
} catch (err) {
|
|
114416
|
+
if (err?.name === "AbortError") throw err;
|
|
114417
|
+
throw new ArenaRequestError(summarizeError(err), { category: "provider", incidents });
|
|
114418
|
+
}
|
|
114419
|
+
if (response.ok) return { ...await readStreamingPayload(response, onDelta), incidents, retryCount: attempt };
|
|
114420
|
+
const payload = await response.json().catch(() => ({}));
|
|
114421
|
+
const message = payload?.error?.message ?? payload?.message ?? `HTTP ${response.status}`;
|
|
114422
|
+
const retryable = response.status === 429 || response.status >= 500;
|
|
114423
|
+
if (retryable && attempt < maxRetries) {
|
|
114424
|
+
if (response.status === 429) diagnosticsCounters.rateLimits++;
|
|
114425
|
+
else diagnosticsCounters.providerErrors++;
|
|
114426
|
+
incidents.push({ category: requestErrorCategory(response.status), status: response.status, message: String(message).slice(0, 180) });
|
|
114427
|
+
await sleepWithSignal(retryDelayMs(response), options?.signal);
|
|
114428
|
+
continue;
|
|
114429
|
+
}
|
|
114430
|
+
if (response.status === 429) diagnosticsCounters.rateLimits++;
|
|
114431
|
+
else if (response.status >= 500) diagnosticsCounters.providerErrors++;
|
|
114432
|
+
throw new ArenaRequestError(message, { status: response.status, category: requestErrorCategory(response.status), payload, incidents });
|
|
114433
|
+
}
|
|
114434
|
+
}
|
|
114342
114435
|
function isUnsupportedToolChoiceError(err) {
|
|
114343
114436
|
const message = summarizeError(err);
|
|
114344
114437
|
return [400, 404, 422].includes(Number(err?.status)) && /tool[_ -]?choice|tool call|tools?.*(?:unsupported|support)|no endpoints?.*support/i.test(message);
|
|
@@ -114417,6 +114510,9 @@ function isReasoningModel(model) {
|
|
|
114417
114510
|
const id2 = String(model || "").toLowerCase();
|
|
114418
114511
|
return /(?:^|[\/._:-])(qwen3|qwq|deepseek-[rv]\d|magistral|glm-(?:\d+[a-z]*|latest|flash-latest)|gpt-oss|nemotron|reason(?:ing)?|thinking|o1|o3|o4)(?:$|[\/._:-])/.test(id2);
|
|
114419
114512
|
}
|
|
114513
|
+
function wantsReasoning(agent) {
|
|
114514
|
+
return isReasoningModel(agent?.model) || agent?.captureReasoning === true;
|
|
114515
|
+
}
|
|
114420
114516
|
function effectiveProtocol(agent, connection) {
|
|
114421
114517
|
if (isOpenRouterConnection(connection) && isJevModel(agent?.model)) return "jev_decisions";
|
|
114422
114518
|
if (connection?.kind === "typesafe") return "jev_native";
|
|
@@ -114534,29 +114630,57 @@ function totalPot(state) {
|
|
|
114534
114630
|
else if (bets && typeof bets === "object") for (const amount of Object.values(bets)) live += asNumber(amount);
|
|
114535
114631
|
return settled + live;
|
|
114536
114632
|
}
|
|
114537
|
-
function
|
|
114538
|
-
const
|
|
114539
|
-
|
|
114540
|
-
|
|
114541
|
-
|
|
114542
|
-
return
|
|
114633
|
+
function integerGcd(values) {
|
|
114634
|
+
const gcd = (a, b) => {
|
|
114635
|
+
while (b) [a, b] = [b, a % b];
|
|
114636
|
+
return Math.abs(a);
|
|
114637
|
+
};
|
|
114638
|
+
return values.map((value) => Math.abs(Math.round(Number(value) || 0))).filter(Boolean).reduce(gcd, 0) || 1;
|
|
114639
|
+
}
|
|
114640
|
+
function potChipBreakdown(pot, { smallBlind = 0, bigBlind = 0, ante = 0 } = {}) {
|
|
114641
|
+
let remaining = Math.max(0, Math.round(Number(pot) || 0));
|
|
114642
|
+
if (!remaining) return [];
|
|
114643
|
+
const base = integerGcd([smallBlind, bigBlind, ante]);
|
|
114644
|
+
const denominations = [
|
|
114645
|
+
{ color: "gold", value: base * 100 },
|
|
114646
|
+
{ color: "black", value: base * 25 },
|
|
114647
|
+
{ color: "blue", value: base * 5 },
|
|
114648
|
+
{ color: "red", value: base }
|
|
114649
|
+
];
|
|
114650
|
+
if (base > 1) denominations.push({ color: "white", value: 1 });
|
|
114651
|
+
return denominations.flatMap(({ color, value }) => {
|
|
114652
|
+
const count = Math.floor(remaining / value);
|
|
114653
|
+
remaining -= count * value;
|
|
114654
|
+
return count ? [{ color, value, count, shown: Math.min(5, count) }] : [];
|
|
114655
|
+
});
|
|
114656
|
+
}
|
|
114657
|
+
function handSeats(state) {
|
|
114658
|
+
return (state?.players ?? []).map((player, seat) => ({ player, seat })).filter(({ player }) => player && (playerStack(player) > 0 || player.hand != null || asNumber(player.totalInvestedThisHand) > 0)).map(({ seat }) => seat);
|
|
114659
|
+
}
|
|
114660
|
+
function tableMarkerSeats(state) {
|
|
114661
|
+
const seats = handSeats(state);
|
|
114662
|
+
const buttonSeat = Number.isInteger(state?.buttonSeat) ? state.buttonSeat : null;
|
|
114663
|
+
const bigBlindSeat = Number.isInteger(state?.bigBlindSeat) ? state.bigBlindSeat : null;
|
|
114664
|
+
const maxPlayers = Math.max(1, asNumber(state?.maxPlayers, state?.players?.length || 1));
|
|
114665
|
+
const smallBlindSeat = buttonSeat == null ? null : seats.length === 2 ? buttonSeat : (buttonSeat + 1) % maxPlayers;
|
|
114666
|
+
return { buttonSeat, smallBlindSeat, bigBlindSeat, headsUp: seats.length === 2 };
|
|
114543
114667
|
}
|
|
114544
|
-
var POSITION_TABLE = {
|
|
114545
|
-
2: ["BTN/SB", "BB"],
|
|
114546
|
-
3: ["BTN", "SB", "BB"],
|
|
114547
|
-
4: ["BTN", "SB", "BB", "UTG"],
|
|
114548
|
-
5: ["BTN", "SB", "BB", "UTG", "CO"],
|
|
114549
|
-
6: ["BTN", "SB", "BB", "UTG", "HJ", "CO"],
|
|
114550
|
-
7: ["BTN", "SB", "BB", "UTG", "MP", "HJ", "CO"],
|
|
114551
|
-
8: ["BTN", "SB", "BB", "UTG", "UTG+1", "MP", "HJ", "CO"],
|
|
114552
|
-
9: ["BTN", "SB", "BB", "UTG", "UTG+1", "MP", "MP+1", "HJ", "CO"],
|
|
114553
|
-
10: ["BTN", "SB", "BB", "UTG", "UTG+1", "UTG+2", "MP", "MP+1", "HJ", "CO"]
|
|
114554
|
-
};
|
|
114555
114668
|
function positionForSeat(state, seat) {
|
|
114556
|
-
const
|
|
114557
|
-
|
|
114558
|
-
const
|
|
114559
|
-
|
|
114669
|
+
const playing = handSeats(state);
|
|
114670
|
+
if (!playing.includes(seat)) return `Seat ${seat + 1}`;
|
|
114671
|
+
const markers = tableMarkerSeats(state);
|
|
114672
|
+
if (seat === markers.buttonSeat) return markers.headsUp ? "BTN/SB" : "BTN";
|
|
114673
|
+
if (seat === markers.smallBlindSeat) return "SB";
|
|
114674
|
+
if (seat === markers.bigBlindSeat) return "BB";
|
|
114675
|
+
const maxPlayers = Math.max(1, asNumber(state?.maxPlayers, state?.players?.length || 1));
|
|
114676
|
+
const afterBigBlind = [];
|
|
114677
|
+
for (let offset = 1; offset <= maxPlayers; offset++) {
|
|
114678
|
+
const candidate = ((markers.bigBlindSeat ?? -1) + offset + maxPlayers) % maxPlayers;
|
|
114679
|
+
if (playing.includes(candidate) && ![markers.buttonSeat, markers.smallBlindSeat, markers.bigBlindSeat].includes(candidate)) afterBigBlind.push(candidate);
|
|
114680
|
+
}
|
|
114681
|
+
const labels = afterBigBlind.length === 1 ? ["UTG"] : afterBigBlind.length === 2 ? ["UTG", "CO"] : ["UTG", ...Array.from({ length: Math.max(0, afterBigBlind.length - 3) }, (_, i) => `MP${i ? `+${i}` : ""}`), "HJ", "CO"];
|
|
114682
|
+
const index = afterBigBlind.indexOf(seat);
|
|
114683
|
+
return index >= 0 ? labels[index] : `Seat ${seat + 1}`;
|
|
114560
114684
|
}
|
|
114561
114685
|
function describeAction(type, amount, state, seat = null) {
|
|
114562
114686
|
const bb = Math.max(1, asNumber(state.bigBlind, 1));
|
|
@@ -115123,7 +115247,7 @@ async function decideJevNative({ agent, connection, state, legalActions, timeout
|
|
|
115123
115247
|
cancel();
|
|
115124
115248
|
}
|
|
115125
115249
|
}
|
|
115126
|
-
function buildOpenAICompatibleBody({ agent, connection, state, legalActions, decisionId, protocol = "tool", instructions = null, criteria = null, representationText = null, maxTokens = null, reasoning = null }) {
|
|
115250
|
+
function buildOpenAICompatibleBody({ agent, connection, state, legalActions, decisionId, protocol = "tool", instructions = null, criteria = null, representationText = null, maxTokens = null, reasoning = null, stream = false }) {
|
|
115127
115251
|
const isOpenRouter = isOpenRouterConnection(connection);
|
|
115128
115252
|
const criteriaKeys = criteria ? Object.keys(criteria) : null;
|
|
115129
115253
|
const useChoice = Boolean(criteriaKeys?.length);
|
|
@@ -115148,7 +115272,7 @@ Respond with one of: ${criteriaKeys.map((k) => `${k} = ${criteria[k]}`).join(";
|
|
|
115148
115272
|
{ role: "system", content: "You are one seat in an autonomous poker benchmark. Commit exactly one legal move." },
|
|
115149
115273
|
{ role: "user", content: userContent }
|
|
115150
115274
|
],
|
|
115151
|
-
max_tokens: maxTokens ?? (
|
|
115275
|
+
max_tokens: maxTokens ?? (wantsReasoning(agent) ? 2048 : 320)
|
|
115152
115276
|
};
|
|
115153
115277
|
const temperature = resolveTemperature(agent.model, agent.temperature);
|
|
115154
115278
|
if (temperature !== void 0) body.temperature = temperature;
|
|
@@ -115163,26 +115287,30 @@ Respond with one of: ${criteriaKeys.map((k) => `${k} = ${criteria[k]}`).join(";
|
|
|
115163
115287
|
}
|
|
115164
115288
|
if (isOpenRouter) {
|
|
115165
115289
|
body.provider = agent.provider ? { only: [agent.provider], allow_fallbacks: false, require_parameters: true } : { allow_fallbacks: true, require_parameters: true };
|
|
115166
|
-
if (
|
|
115290
|
+
if (wantsReasoning(agent)) body.reasoning = reasoning ?? { max_tokens: 256, exclude: false };
|
|
115291
|
+
}
|
|
115292
|
+
if (stream) {
|
|
115293
|
+
body.stream = true;
|
|
115294
|
+
body.stream_options = { include_usage: true };
|
|
115167
115295
|
}
|
|
115168
115296
|
return body;
|
|
115169
115297
|
}
|
|
115170
|
-
async function decideOpenAICompatible({ agent, connection, state, legalActions, decisionId, timeoutMs, abortSignal, pauseClock, protocol: protocolOverride = null, instructions = null, criteria = null, representationText = null }) {
|
|
115298
|
+
async function decideOpenAICompatible({ agent, connection, state, legalActions, decisionId, timeoutMs, abortSignal, pauseClock, protocol: protocolOverride = null, instructions = null, criteria = null, representationText = null, onDelta = null }) {
|
|
115171
115299
|
const { signal, cancel } = combineAbort(timeoutMs, abortSignal, pauseClock);
|
|
115172
115300
|
const started = performance.now();
|
|
115173
115301
|
const requestedProtocol = protocolOverride || agent.protocol;
|
|
115174
115302
|
const capabilityKey = `${normalizeBaseUrl(connection.baseUrl)}|${agent.model}|${agent.provider || "auto"}|tool`;
|
|
115175
115303
|
const isOpenRouter = isOpenRouterConnection(connection);
|
|
115176
115304
|
const cachedProtocol = requestedProtocol === "tool" && isOpenRouter && !criteria ? protocolCapabilityCache.get(capabilityKey) : null;
|
|
115177
|
-
const buildBody = (protocol, override = {}) => buildOpenAICompatibleBody({ agent, connection, state, legalActions, decisionId, protocol, instructions, criteria, representationText, maxTokens: override.maxTokens ?? null, reasoning: override.reasoning ?? null });
|
|
115305
|
+
const buildBody = (protocol, override = {}) => buildOpenAICompatibleBody({ agent, connection, state, legalActions, decisionId, protocol, instructions, criteria, representationText, maxTokens: override.maxTokens ?? null, reasoning: override.reasoning ?? null, stream: Boolean(onDelta) });
|
|
115306
|
+
const request = (body) => {
|
|
115307
|
+
const options = { method: "POST", headers: makeHeaders(connection), body: JSON.stringify(body), signal };
|
|
115308
|
+
const url = completionsUrl(connection.baseUrl);
|
|
115309
|
+
return onDelta ? fetchStreamingJson(url, options, { maxRetries: 1, onDelta: (delta) => onDelta({ stage: "flat", ...delta }) }) : fetchJsonWithRetry(url, options, { maxRetries: 1 });
|
|
115310
|
+
};
|
|
115178
115311
|
async function execute(protocol, override = {}) {
|
|
115179
115312
|
const body = buildBody(protocol, override);
|
|
115180
|
-
const { payload, incidents, retryCount } = await
|
|
115181
|
-
method: "POST",
|
|
115182
|
-
headers: makeHeaders(connection),
|
|
115183
|
-
body: JSON.stringify(body),
|
|
115184
|
-
signal
|
|
115185
|
-
}, { maxRetries: 1 });
|
|
115313
|
+
const { payload, incidents, retryCount, reasoning } = await request(body);
|
|
115186
115314
|
const message = payload?.choices?.[0]?.message;
|
|
115187
115315
|
const finishReason = payload?.choices?.[0]?.finish_reason;
|
|
115188
115316
|
let obj;
|
|
@@ -115203,7 +115331,7 @@ async function decideOpenAICompatible({ agent, connection, state, legalActions,
|
|
|
115203
115331
|
throw new Error(`Model response was not valid JSON: ${content.slice(0, 180)}`);
|
|
115204
115332
|
}
|
|
115205
115333
|
}
|
|
115206
|
-
return { obj, payload, incidents, retryCount, protocol };
|
|
115334
|
+
return { obj, payload, incidents, retryCount, protocol, reasoning };
|
|
115207
115335
|
}
|
|
115208
115336
|
try {
|
|
115209
115337
|
let actualProtocol = cachedProtocol || requestedProtocol;
|
|
@@ -115231,6 +115359,7 @@ async function decideOpenAICompatible({ agent, connection, state, legalActions,
|
|
|
115231
115359
|
latencyMs: Math.round(performance.now() - started),
|
|
115232
115360
|
model: result.payload?.model || agent.model,
|
|
115233
115361
|
usage: result.payload?.usage ?? null,
|
|
115362
|
+
reasoning: result.reasoning ?? null,
|
|
115234
115363
|
meta: {
|
|
115235
115364
|
method: result.protocol,
|
|
115236
115365
|
requestedMethod: requestedProtocol,
|
|
@@ -115376,13 +115505,13 @@ async function requestJevStage({ connection, model, state, questionKey, question
|
|
|
115376
115505
|
gap: ranked.length >= 2 ? Number(ranked[0][1]) - Number(ranked[1][1]) : null
|
|
115377
115506
|
};
|
|
115378
115507
|
}
|
|
115379
|
-
async function requestChatStage({ agent, connection, schema, toolName, systemPrompt, userPrompt, signal, requestedProtocol, temperature }) {
|
|
115508
|
+
async function requestChatStage({ agent, connection, schema, toolName, systemPrompt, userPrompt, signal, requestedProtocol, temperature, onDelta = null }) {
|
|
115380
115509
|
const isOpenRouter = isOpenRouterConnection(connection);
|
|
115381
115510
|
const buildBody = (protocol, override = {}) => {
|
|
115382
115511
|
const body = {
|
|
115383
115512
|
model: agent.model,
|
|
115384
115513
|
messages: [{ role: "system", content: systemPrompt }, { role: "user", content: userPrompt }],
|
|
115385
|
-
max_tokens: override.maxTokens ?? (
|
|
115514
|
+
max_tokens: override.maxTokens ?? (wantsReasoning(agent) ? 2048 : 320)
|
|
115386
115515
|
};
|
|
115387
115516
|
const resolvedTemperature = resolveTemperature(agent.model, temperature);
|
|
115388
115517
|
if (resolvedTemperature !== void 0) body.temperature = resolvedTemperature;
|
|
@@ -115397,7 +115526,11 @@ async function requestChatStage({ agent, connection, schema, toolName, systemPro
|
|
|
115397
115526
|
}
|
|
115398
115527
|
if (isOpenRouter) {
|
|
115399
115528
|
body.provider = agent.provider ? { only: [agent.provider], allow_fallbacks: false, require_parameters: true } : { allow_fallbacks: true, require_parameters: true };
|
|
115400
|
-
if (
|
|
115529
|
+
if (wantsReasoning(agent)) body.reasoning = override.reasoning ?? { max_tokens: 256, exclude: false };
|
|
115530
|
+
}
|
|
115531
|
+
if (onDelta) {
|
|
115532
|
+
body.stream = true;
|
|
115533
|
+
body.stream_options = { include_usage: true };
|
|
115401
115534
|
}
|
|
115402
115535
|
return body;
|
|
115403
115536
|
};
|
|
@@ -115405,12 +115538,9 @@ async function requestChatStage({ agent, connection, schema, toolName, systemPro
|
|
|
115405
115538
|
const cachedProtocol = requestedProtocol === "tool" && isOpenRouter ? protocolCapabilityCache.get(capabilityKey) : null;
|
|
115406
115539
|
const execute = async (protocol, override = {}) => {
|
|
115407
115540
|
const started = performance.now();
|
|
115408
|
-
const {
|
|
115409
|
-
|
|
115410
|
-
|
|
115411
|
-
body: JSON.stringify(buildBody(protocol, override)),
|
|
115412
|
-
signal
|
|
115413
|
-
}, { maxRetries: 1 });
|
|
115541
|
+
const options = { method: "POST", headers: makeHeaders(connection), body: JSON.stringify(buildBody(protocol, override)), signal };
|
|
115542
|
+
const url = completionsUrl(connection.baseUrl);
|
|
115543
|
+
const { payload, incidents, retryCount, reasoning } = onDelta ? await fetchStreamingJson(url, options, { maxRetries: 1, onDelta }) : await fetchJsonWithRetry(url, options, { maxRetries: 1 });
|
|
115414
115544
|
const message = payload?.choices?.[0]?.message;
|
|
115415
115545
|
const finishReason = payload?.choices?.[0]?.finish_reason;
|
|
115416
115546
|
let obj;
|
|
@@ -115427,7 +115557,7 @@ async function requestChatStage({ agent, connection, schema, toolName, systemPro
|
|
|
115427
115557
|
if (!content) throw truncatedError(finishReason, `Model returned no ${toolName} content`);
|
|
115428
115558
|
obj = JSON.parse(content);
|
|
115429
115559
|
}
|
|
115430
|
-
return { obj, protocol, latencyMs: Math.round(performance.now() - started), model: payload?.model || agent.model, usage: payload?.usage ?? null, incidents, retryCount };
|
|
115560
|
+
return { obj, protocol, latencyMs: Math.round(performance.now() - started), model: payload?.model || agent.model, usage: payload?.usage ?? null, incidents, retryCount, reasoning };
|
|
115431
115561
|
};
|
|
115432
115562
|
let actual = cachedProtocol || requestedProtocol;
|
|
115433
115563
|
let protocolFallback = cachedProtocol ? "cached json_schema" : null;
|
|
@@ -115460,7 +115590,8 @@ async function decideHierarchical({
|
|
|
115460
115590
|
representationMode = DEFAULT_REPRESENTATION_MODE,
|
|
115461
115591
|
protocol: protocolOverride = null,
|
|
115462
115592
|
sizesForFamily = null,
|
|
115463
|
-
onStage = null
|
|
115593
|
+
onStage = null,
|
|
115594
|
+
onDelta = null
|
|
115464
115595
|
}) {
|
|
115465
115596
|
if (!connection) throw new Error(`Connection not found: ${agent.connectionId}`);
|
|
115466
115597
|
const protocol = protocolOverride || effectiveProtocol(agent, connection);
|
|
@@ -115509,7 +115640,8 @@ async function decideHierarchical({
|
|
|
115509
115640
|
userPrompt,
|
|
115510
115641
|
signal,
|
|
115511
115642
|
requestedProtocol: protocol,
|
|
115512
|
-
temperature: agent.temperature
|
|
115643
|
+
temperature: agent.temperature,
|
|
115644
|
+
onDelta: onDelta ? (delta) => onDelta({ stage: "family", index: 1, of: 2, ...delta }) : null
|
|
115513
115645
|
});
|
|
115514
115646
|
familyResult = { ...result, choice: normalizeFamilyChoice(result.obj, families, decisionId) };
|
|
115515
115647
|
}
|
|
@@ -115554,7 +115686,8 @@ async function decideHierarchical({
|
|
|
115554
115686
|
userPrompt,
|
|
115555
115687
|
signal,
|
|
115556
115688
|
requestedProtocol: protocol,
|
|
115557
|
-
temperature: agent.temperature
|
|
115689
|
+
temperature: agent.temperature,
|
|
115690
|
+
onDelta: onDelta ? (delta) => onDelta({ stage: "size", index: 2, of: 2, family: familyResult.choice, ...delta }) : null
|
|
115558
115691
|
});
|
|
115559
115692
|
sizingResult = { ...result, choice: normalizeSizeChoice(result.obj, sizes, decisionId) };
|
|
115560
115693
|
}
|
|
@@ -115590,6 +115723,7 @@ async function decideHierarchical({
|
|
|
115590
115723
|
publicReason: "",
|
|
115591
115724
|
model: familyResult.model || agent.model,
|
|
115592
115725
|
usage: usage.total_tokens ? usage : familyResult.usage ?? null,
|
|
115726
|
+
reasoning: { family: familyResult.reasoning ?? null, sizing: sizingResult?.reasoning ?? null },
|
|
115593
115727
|
meta: {
|
|
115594
115728
|
decisionArchitecture: DECISION_ARCHITECTURE_VERSION,
|
|
115595
115729
|
method: isJev ? protocol === "jev_native" ? "jev-native-hierarchical" : "openrouter-decisions-hierarchical" : `${familyResult.protocol ?? protocol}-hierarchical`,
|
|
@@ -115672,6 +115806,7 @@ var els = {
|
|
|
115672
115806
|
decisionActionLabel: $("#decisionActionLabel"),
|
|
115673
115807
|
decisionActionHint: $("#decisionActionHint"),
|
|
115674
115808
|
legalActions: $("#legalActions"),
|
|
115809
|
+
decisionReasoning: $("#decisionReasoning"),
|
|
115675
115810
|
decisionLabelHand: $("#decisionLabelHand"),
|
|
115676
115811
|
decisionLabelStreet: $("#decisionLabelStreet"),
|
|
115677
115812
|
decisionLabelPosition: $("#decisionLabelPosition"),
|
|
@@ -115707,6 +115842,7 @@ var els = {
|
|
|
115707
115842
|
refreshModelsBtn: $("#refreshModelsBtn"),
|
|
115708
115843
|
seatProtocol: $("#seatProtocol"),
|
|
115709
115844
|
seatProvider: $("#seatProvider"),
|
|
115845
|
+
seatCaptureReasoning: $("#seatCaptureReasoning"),
|
|
115710
115846
|
seatError: $("#seatError"),
|
|
115711
115847
|
removeSeatBtn: $("#removeSeatBtn"),
|
|
115712
115848
|
cancelSeatBtn: $("#cancelSeatBtn"),
|
|
@@ -115733,6 +115869,7 @@ var els = {
|
|
|
115733
115869
|
replaySummary: $("#replaySummary"),
|
|
115734
115870
|
replayAction: $("#replayAction"),
|
|
115735
115871
|
replayReason: $("#replayReason"),
|
|
115872
|
+
replayReasoning: $("#replayReasoning"),
|
|
115736
115873
|
replayHistory: $("#replayHistory"),
|
|
115737
115874
|
replayLegal: $("#replayLegal"),
|
|
115738
115875
|
replayLegalSummary: $("#replayLegalSummary"),
|
|
@@ -116060,7 +116197,8 @@ function injectedEnvironmentConfig() {
|
|
|
116060
116197
|
connectionId: String(player.connectionId || connections[0]?.id || ""),
|
|
116061
116198
|
model: String(player.model || ""),
|
|
116062
116199
|
protocol: String(player.protocol || "tool"),
|
|
116063
|
-
provider: String(player.provider || "")
|
|
116200
|
+
provider: String(player.provider || ""),
|
|
116201
|
+
captureReasoning: Boolean(player.captureReasoning)
|
|
116064
116202
|
}))
|
|
116065
116203
|
};
|
|
116066
116204
|
}
|
|
@@ -116169,28 +116307,8 @@ function tableCardHtml(card, kind = "board") {
|
|
|
116169
116307
|
const red = suit === "\u2665" || suit === "\u2666";
|
|
116170
116308
|
return `<span class="playing-card ${kind === "hole" ? "hole-card" : "board-card"} ${red ? "red" : ""}" data-rank="${escapeHtml(rank2)}" data-suit="${escapeHtml(suit)}" aria-label="${escapeHtml(rank2 + suit)}"></span>`;
|
|
116171
116309
|
}
|
|
116172
|
-
function integerGcd(values) {
|
|
116173
|
-
const gcd = (a, b) => {
|
|
116174
|
-
while (b) [a, b] = [b, a % b];
|
|
116175
|
-
return Math.abs(a);
|
|
116176
|
-
};
|
|
116177
|
-
return values.map((value) => Math.abs(Math.round(Number(value) || 0))).filter(Boolean).reduce(gcd, 0) || 1;
|
|
116178
|
-
}
|
|
116179
116310
|
function potChipsHtml(pot, { smallBlind = 0, bigBlind = 0, ante = 0 } = {}) {
|
|
116180
|
-
|
|
116181
|
-
if (!remaining) return "";
|
|
116182
|
-
const base = integerGcd([smallBlind, bigBlind, ante]);
|
|
116183
|
-
const denominations = [
|
|
116184
|
-
{ color: "gold", value: base * 100 },
|
|
116185
|
-
{ color: "black", value: base * 25 },
|
|
116186
|
-
{ color: "blue", value: base * 5 },
|
|
116187
|
-
{ color: "red", value: base }
|
|
116188
|
-
];
|
|
116189
|
-
return denominations.map(({ color, value }) => {
|
|
116190
|
-
const count = Math.floor(remaining / value);
|
|
116191
|
-
remaining -= count * value;
|
|
116192
|
-
if (!count) return "";
|
|
116193
|
-
const shown = Math.min(5, count);
|
|
116311
|
+
return potChipBreakdown(pot, { smallBlind, bigBlind, ante }).map(({ color, value, count, shown }) => {
|
|
116194
116312
|
const chips = Array.from({ length: shown }, () => `<span class="pot-chip chip-${color}"></span>`).join("");
|
|
116195
116313
|
const overflow = count > shown ? ` data-chip-count="\xD7${count}"` : "";
|
|
116196
116314
|
return `<span class="chip-stack"${overflow} title="${count} \xD7 ${fmt(value)} chips" aria-label="${count} ${color} chips worth ${fmt(value)} each">${chips}</span>`;
|
|
@@ -116324,6 +116442,7 @@ function spectatorState(engine, tournamentMeta = {}) {
|
|
|
116324
116442
|
status: p.status,
|
|
116325
116443
|
position: positionForSeat(state, seat)
|
|
116326
116444
|
} : null),
|
|
116445
|
+
markers: tableMarkerSeats(state),
|
|
116327
116446
|
winners: state.winners ?? null
|
|
116328
116447
|
};
|
|
116329
116448
|
}
|
|
@@ -116396,7 +116515,7 @@ function normalizeConfig(input = {}) {
|
|
|
116396
116515
|
if (!connIds.has(raw.connectionId)) throw new Error(`Connection missing for ${name}`);
|
|
116397
116516
|
const model = String(raw.model || "").trim();
|
|
116398
116517
|
if (!model) throw new Error(`Model missing for ${name}`);
|
|
116399
|
-
return { id: `player-${lobbySeat + 1}`, seat: index, lobbySeat, name, connectionId: raw.connectionId, model, protocol: raw.protocol || "tool", provider: String(raw.provider || "").trim(), temperature: clamp(Number(raw.temperature) || 0.3, 0, 2) };
|
|
116518
|
+
return { id: `player-${lobbySeat + 1}`, seat: index, lobbySeat, name, connectionId: raw.connectionId, model, protocol: raw.protocol || "tool", provider: String(raw.provider || "").trim(), captureReasoning: raw.captureReasoning === true, temperature: clamp(Number(raw.temperature) || 0.3, 0, 2) };
|
|
116400
116519
|
})
|
|
116401
116520
|
};
|
|
116402
116521
|
}
|
|
@@ -116472,7 +116591,7 @@ var TournamentDirector = class {
|
|
|
116472
116591
|
for (const player of this.config.players) {
|
|
116473
116592
|
this.engine.sit(player.seat, player.id, player.name, this.config.startingStack);
|
|
116474
116593
|
this.timeBanks[player.id] = this.config.timeBankSeconds * 1e3;
|
|
116475
|
-
this.stats[player.id] = { decisions: 0, invalid: 0, modelErrors: 0, providerErrors: 0, rateLimits: 0, timeouts: 0, autoFallbacks: 0, protocolFallbacks: 0, retries: 0, totalLatencyMs: 0, lastAction: null, lastReason: "" };
|
|
116594
|
+
this.stats[player.id] = { decisions: 0, invalid: 0, modelErrors: 0, providerErrors: 0, rateLimits: 0, timeouts: 0, autoFallbacks: 0, protocolFallbacks: 0, retries: 0, totalLatencyMs: 0, lastAction: null, lastActionType: null, lastActionAmount: null, lastReason: "" };
|
|
116476
116595
|
}
|
|
116477
116596
|
this.logEvent("TOURNAMENT_START", { config: this.publicConfig() });
|
|
116478
116597
|
this.broadcast();
|
|
@@ -116524,7 +116643,7 @@ var TournamentDirector = class {
|
|
|
116524
116643
|
const eliminatedPlayerIds = this.eliminations.map((e) => e.playerId);
|
|
116525
116644
|
return {
|
|
116526
116645
|
handNumber: this.handNumber,
|
|
116527
|
-
playersRemaining:
|
|
116646
|
+
playersRemaining: this.playersRemaining().length,
|
|
116528
116647
|
startingPlayers,
|
|
116529
116648
|
levelIndex: this.engine?.state?.blindLevel ?? 0,
|
|
116530
116649
|
eliminatedPlayerIds,
|
|
@@ -116611,31 +116730,28 @@ var TournamentDirector = class {
|
|
|
116611
116730
|
}
|
|
116612
116731
|
}
|
|
116613
116732
|
pruneBustedSeats() {
|
|
116733
|
+
const failures = [];
|
|
116614
116734
|
for (const p of this.engine?.state?.players ?? []) {
|
|
116615
116735
|
if (!p || playerStack(p) > 0) continue;
|
|
116616
116736
|
try {
|
|
116617
116737
|
this.engine.stand(p.id);
|
|
116618
|
-
} catch {
|
|
116738
|
+
} catch (error) {
|
|
116739
|
+
failures.push(`${p.name}: ${summarizeError(error)}`);
|
|
116619
116740
|
}
|
|
116620
116741
|
}
|
|
116742
|
+
if (failures.length) throw new Error(`Could not remove busted player(s): ${failures.join("; ")}`);
|
|
116621
116743
|
}
|
|
116622
116744
|
async startHand() {
|
|
116623
116745
|
await this.waitIfPaused();
|
|
116624
116746
|
this.pruneBustedSeats();
|
|
116625
|
-
this.
|
|
116626
|
-
|
|
116627
|
-
|
|
116628
|
-
|
|
116629
|
-
this.engine.deal();
|
|
116630
|
-
} catch {
|
|
116631
|
-
for (const p of this.engine.state.players ?? []) if (p && playerStack(p) <= 0) {
|
|
116632
|
-
try {
|
|
116633
|
-
this.engine.stand(p.id);
|
|
116634
|
-
} catch {
|
|
116635
|
-
}
|
|
116636
|
-
}
|
|
116637
|
-
this.engine.deal();
|
|
116747
|
+
for (const stat of Object.values(this.stats)) {
|
|
116748
|
+
stat.lastAction = null;
|
|
116749
|
+
stat.lastActionType = null;
|
|
116750
|
+
stat.lastActionAmount = null;
|
|
116638
116751
|
}
|
|
116752
|
+
this.handStartStacks = Object.fromEntries((this.engine?.state?.players ?? []).filter(Boolean).map((p) => [p.id, playerStack(p)]));
|
|
116753
|
+
this.engine.deal();
|
|
116754
|
+
this.handNumber = this.engine.state.handNumber;
|
|
116639
116755
|
this.logEvent("HAND_START", {
|
|
116640
116756
|
handNumber: this.handNumber,
|
|
116641
116757
|
buttonSeat: this.engine.state.buttonSeat,
|
|
@@ -116741,6 +116857,19 @@ var TournamentDirector = class {
|
|
|
116741
116857
|
if (incident?.category === "rate_limit") stats.rateLimits++;
|
|
116742
116858
|
else if (incident?.category === "provider") stats.providerErrors++;
|
|
116743
116859
|
};
|
|
116860
|
+
const liveReasoning = { family: "", sizing: "", flat: "" };
|
|
116861
|
+
let reasoningPaintAt = 0;
|
|
116862
|
+
const onDelta = (info) => {
|
|
116863
|
+
if (this.currentDecision?.id !== decisionId) return;
|
|
116864
|
+
const key = info.stage === "size" ? "sizing" : info.stage === "flat" ? "flat" : "family";
|
|
116865
|
+
if (info.channel === "reasoning") liveReasoning[key] = info.text;
|
|
116866
|
+
this.currentDecision.reasoning = { stage: info.stage, channel: info.channel, text: reasoningTail(info.text) };
|
|
116867
|
+
const now = Date.now();
|
|
116868
|
+
if (now - reasoningPaintAt >= 120) {
|
|
116869
|
+
reasoningPaintAt = now;
|
|
116870
|
+
this.broadcast();
|
|
116871
|
+
}
|
|
116872
|
+
};
|
|
116744
116873
|
try {
|
|
116745
116874
|
const before = performance.now();
|
|
116746
116875
|
if (architecture === "hierarchical") {
|
|
@@ -116760,10 +116889,11 @@ var TournamentDirector = class {
|
|
|
116760
116889
|
this.currentDecision.stage = info;
|
|
116761
116890
|
this.broadcast();
|
|
116762
116891
|
}
|
|
116763
|
-
}
|
|
116892
|
+
},
|
|
116893
|
+
onDelta
|
|
116764
116894
|
});
|
|
116765
116895
|
} else {
|
|
116766
|
-
result = await decide(agent, connection, { state: stateForAgent, legalActions, decisionId, timeoutMs: totalMs, abortSignal: this.abortController?.signal, pauseClock: this.currentDecision, representationMode: this.config.representation });
|
|
116896
|
+
result = await decide(agent, connection, { state: stateForAgent, legalActions, decisionId, timeoutMs: totalMs, abortSignal: this.abortController?.signal, pauseClock: this.currentDecision, representationMode: this.config.representation, onDelta });
|
|
116767
116897
|
}
|
|
116768
116898
|
elapsed = Math.max(0, Math.round(performance.now() - before - pausedTotal()));
|
|
116769
116899
|
for (const incident of result?.meta?.incidents || []) recordIncident(incident);
|
|
@@ -116823,9 +116953,12 @@ var TournamentDirector = class {
|
|
|
116823
116953
|
stats.decisions++;
|
|
116824
116954
|
stats.totalLatencyMs += reportedLatency;
|
|
116825
116955
|
stats.lastAction = chosen.description;
|
|
116956
|
+
stats.lastActionType = chosen.type;
|
|
116957
|
+
stats.lastActionAmount = chosen.amount ?? null;
|
|
116826
116958
|
this.decisionCount++;
|
|
116827
116959
|
if (this.decisionBudget > 0 && this.decisionCount >= this.decisionBudget) this.budgetReached = true;
|
|
116828
116960
|
stats.lastReason = result?.publicReason || typedReason || (forced ? `Automatic ${chosen.description} after ${fallbackReason}.` : "");
|
|
116961
|
+
const reasoningLog = architecture === "hierarchical" ? { family: capReasoning(result?.reasoning?.family ?? liveReasoning.family), sizing: capReasoning(result?.reasoning?.sizing ?? liveReasoning.sizing) } : { flat: capReasoning(typeof result?.reasoning === "string" ? result.reasoning : liveReasoning.flat) };
|
|
116829
116962
|
const decisionMeta = result?.meta ? {
|
|
116830
116963
|
...result.meta,
|
|
116831
116964
|
decisionArchitecture: architecture === "hierarchical" ? DECISION_ARCHITECTURE_VERSION : "flat-v1",
|
|
@@ -116858,6 +116991,7 @@ var TournamentDirector = class {
|
|
|
116858
116991
|
publicReason: stats.lastReason,
|
|
116859
116992
|
usage: result?.usage ?? null,
|
|
116860
116993
|
decisionMeta,
|
|
116994
|
+
reasoning: reasoningLog,
|
|
116861
116995
|
benchmarkMode: this.config.benchmarkMode,
|
|
116862
116996
|
decisionArchitecture: architecture,
|
|
116863
116997
|
representation: this.config.representation,
|
|
@@ -117139,7 +117273,8 @@ function defaultSeatDraft(seatIndex) {
|
|
|
117139
117273
|
connectionId,
|
|
117140
117274
|
model: conn?.kind === "typesafe" ? "jev-latest" : "",
|
|
117141
117275
|
protocol: conn?.kind === "typesafe" ? "jev_native" : "tool",
|
|
117142
|
-
provider: ""
|
|
117276
|
+
provider: "",
|
|
117277
|
+
captureReasoning: false
|
|
117143
117278
|
};
|
|
117144
117279
|
}
|
|
117145
117280
|
function applySeatProtocolRules() {
|
|
@@ -117147,7 +117282,14 @@ function applySeatProtocolRules() {
|
|
|
117147
117282
|
const kind = conn?.kind || "openrouter";
|
|
117148
117283
|
const openRouter = isOpenRouterConnection(conn);
|
|
117149
117284
|
const jevViaOpenRouter = openRouter && isJevModel(els.seatModel.value);
|
|
117285
|
+
const jevSeat = kind === "typesafe" || jevViaOpenRouter;
|
|
117150
117286
|
document.querySelector(".seat-provider-field")?.classList.toggle("hidden", !openRouter || jevViaOpenRouter);
|
|
117287
|
+
document.querySelector(".seat-reasoning-field")?.classList.toggle("hidden", jevSeat);
|
|
117288
|
+
if (els.seatCaptureReasoning) {
|
|
117289
|
+
els.seatCaptureReasoning.disabled = jevSeat;
|
|
117290
|
+
const alreadyReasoning = isReasoningModel(els.seatModel.value.trim());
|
|
117291
|
+
document.querySelector(".seat-reasoning-help")?.classList.toggle("hidden", !els.seatCaptureReasoning.checked || alreadyReasoning);
|
|
117292
|
+
}
|
|
117151
117293
|
[...els.seatProtocol.options].forEach((option) => {
|
|
117152
117294
|
option.disabled = option.value === "jev_native" && kind !== "typesafe" || option.value === "jev_decisions" && !openRouter;
|
|
117153
117295
|
});
|
|
@@ -117188,12 +117330,14 @@ function openSeatEditor(seatIndex) {
|
|
|
117188
117330
|
els.seatModel.value = draft.model || "";
|
|
117189
117331
|
els.seatProtocol.value = draft.protocol || "tool";
|
|
117190
117332
|
els.seatProvider.value = draft.provider || "";
|
|
117333
|
+
if (els.seatCaptureReasoning) els.seatCaptureReasoning.checked = Boolean(draft.captureReasoning);
|
|
117191
117334
|
applySeatProtocolRules();
|
|
117192
117335
|
syncSeatNameFromModel();
|
|
117193
117336
|
void refreshSeatModelCatalog();
|
|
117194
117337
|
els.seatError.classList.add("hidden");
|
|
117195
117338
|
els.seatLockNotice.classList.toggle("hidden", !locked);
|
|
117196
117339
|
for (const control of [els.seatName, els.seatConnection, els.seatModel, els.seatProtocol, els.seatProvider]) control.disabled = locked || control === els.seatProvider && (!isOpenRouterConnection(connectionById(els.seatConnection.value)) || isJevModel(els.seatModel.value));
|
|
117340
|
+
if (els.seatCaptureReasoning) els.seatCaptureReasoning.disabled = locked || els.seatCaptureReasoning.disabled;
|
|
117197
117341
|
els.saveSeatBtn.disabled = locked;
|
|
117198
117342
|
els.removeSeatBtn.disabled = locked || !seatAssignments[seatIndex];
|
|
117199
117343
|
if (!els.seatDialog.open) els.seatDialog.showModal();
|
|
@@ -117210,7 +117354,8 @@ function readSeatDraft() {
|
|
|
117210
117354
|
connectionId: els.seatConnection.value,
|
|
117211
117355
|
model: els.seatModel.value.trim(),
|
|
117212
117356
|
protocol: els.seatProtocol.value,
|
|
117213
|
-
provider: els.seatProvider.disabled ? "" : els.seatProvider.value.trim()
|
|
117357
|
+
provider: els.seatProvider.disabled ? "" : els.seatProvider.value.trim(),
|
|
117358
|
+
captureReasoning: Boolean(els.seatCaptureReasoning && !els.seatCaptureReasoning.disabled && els.seatCaptureReasoning.checked)
|
|
117214
117359
|
};
|
|
117215
117360
|
}
|
|
117216
117361
|
function readSeatPlayers() {
|
|
@@ -117238,7 +117383,8 @@ function restoreSeatAssignments(rows = []) {
|
|
|
117238
117383
|
connectionId,
|
|
117239
117384
|
model: player.model || "",
|
|
117240
117385
|
protocol: player.protocol || "tool",
|
|
117241
|
-
provider: player.provider || ""
|
|
117386
|
+
provider: player.provider || "",
|
|
117387
|
+
captureReasoning: player.captureReasoning === true
|
|
117242
117388
|
};
|
|
117243
117389
|
});
|
|
117244
117390
|
updateSeatSummary();
|
|
@@ -117386,7 +117532,7 @@ function seatPositionOnFelt(seat, slot, tableRect, feltRect, density, lobby) {
|
|
|
117386
117532
|
const feltLeft = feltRect.left - tableRect.left;
|
|
117387
117533
|
const feltTop = feltRect.top - tableRect.top;
|
|
117388
117534
|
const centerX = feltLeft + feltRect.width / 2;
|
|
117389
|
-
const centerY = feltTop + feltRect.height / 2;
|
|
117535
|
+
const centerY = feltTop + feltRect.height / 2 - (lobby ? 30 : 20);
|
|
117390
117536
|
const scaleByDensity = {
|
|
117391
117537
|
roomy: [1.22, 1.34],
|
|
117392
117538
|
compact: [1.2, 1.31],
|
|
@@ -117422,10 +117568,14 @@ function positionTableMarker(marker, seat, tangentOffset = 0) {
|
|
|
117422
117568
|
marker.style.top = `${feltRect.height / 2 + dy * edgeScale + dx / length * tangentOffset}px`;
|
|
117423
117569
|
}
|
|
117424
117570
|
function updateTableMarkers() {
|
|
117425
|
-
const seats = [...els.seatsLayer.querySelectorAll(".table-seat[data-
|
|
117426
|
-
const
|
|
117427
|
-
|
|
117428
|
-
|
|
117571
|
+
const seats = [...els.seatsLayer.querySelectorAll(".table-seat[data-seat]")];
|
|
117572
|
+
const markerSeat = (role) => {
|
|
117573
|
+
const target = currentState?.table?.markers?.[role];
|
|
117574
|
+
return Number.isInteger(target) ? seats.find((seat) => Number(seat.dataset.seat) === target) : null;
|
|
117575
|
+
};
|
|
117576
|
+
const dealer = markerSeat("buttonSeat");
|
|
117577
|
+
const sb = markerSeat("smallBlindSeat");
|
|
117578
|
+
const bb = markerSeat("bigBlindSeat");
|
|
117429
117579
|
const shared = dealer && dealer === sb;
|
|
117430
117580
|
positionTableMarker(els.dealerMarker, dealer, shared ? -11 : 0);
|
|
117431
117581
|
positionTableMarker(els.smallBlindMarker, sb, shared ? 11 : 0);
|
|
@@ -117537,24 +117687,24 @@ function renderTable(s) {
|
|
|
117537
117687
|
els.pokerTable.classList.remove("lobby-mode");
|
|
117538
117688
|
const players = table.players.filter(Boolean);
|
|
117539
117689
|
const configuredPlayers = Array.isArray(s.config?.players) ? s.config.players : [];
|
|
117540
|
-
const visualCount = Math.max(TABLE_MIN_PLAYERS, Math.min(MAX_LOBBY_SEATS,
|
|
117690
|
+
const visualCount = Math.max(TABLE_MIN_PLAYERS, Math.min(MAX_LOBBY_SEATS, players.length));
|
|
117541
117691
|
const eliminatedIds = new Set((s.eliminations ?? []).map((e) => e.playerId));
|
|
117542
117692
|
els.seatsLayer.innerHTML = players.map((p, i) => {
|
|
117543
117693
|
const cfg = configuredPlayers.find((x) => x.id === p.id) || {}, stat = s.stats?.[p.id] || {};
|
|
117544
|
-
const
|
|
117545
|
-
const visualIndex = configuredIndex >= 0 ? configuredIndex : i;
|
|
117694
|
+
const visualIndex = i;
|
|
117546
117695
|
const active = table.actionTo === p.seat || s.currentDecision?.playerId === p.id, isWinner = s.status === "FINISHED" && s.winner?.playerId === p.id, busted = !isWinner && (eliminatedIds.has(p.id) || p.status === "BUSTED"), allIn = p.status === "ALL_IN" && !busted;
|
|
117547
117696
|
const elimination = (s.eliminations ?? []).find((e) => e.playerId === p.id);
|
|
117548
117697
|
const actionText = s.status === "FINISHED" ? isWinner ? "WINNER \xB7 1ST" : elimination ? `${ordinal(elimination.place)} \xB7 ELIMINATED` : stat.lastAction || "" : stat.lastAction || (busted ? "ELIMINATED" : allIn ? "ALL IN" : "");
|
|
117549
|
-
const actionKind = isWinner ? "winner" :
|
|
117550
|
-
const
|
|
117551
|
-
|
|
117698
|
+
const actionKind = isWinner ? "winner" : String(stat.lastActionType || "").toLowerCase();
|
|
117699
|
+
const shownAction = active && s.status !== "FINISHED" ? "THINKING" : actionText;
|
|
117700
|
+
const action2 = tableActionParts(shownAction, active ? "thinking" : actionKind, active ? null : stat.lastActionAmount);
|
|
117701
|
+
return `<div class="table-seat ${active ? "turn-active" : ""} ${isWinner ? "winner" : ""} ${busted ? "busted" : ""} ${allIn ? "all-in" : ""}" data-player-id="${escapeHtml(p.id)}" data-seat="${p.seat}" data-lobby-seat="${Number(cfg.lobbySeat ?? i)}" data-visual-index="${visualIndex}" data-visual-count="${visualCount}" data-position="${escapeHtml(p.position || "")}">
|
|
117552
117702
|
<div class="seat-card">
|
|
117553
117703
|
<i class="seat-turn" aria-hidden="true"></i><span class="seat-name">${escapeHtml(visiblePlayerName(p.name, cfg.model))}</span><span class="seat-stack">${fmt(p.stack)}</span>
|
|
117554
117704
|
<span class="seat-model">${escapeHtml(shortModel(cfg.model))}</span><span class="seat-bb">${Number(p.stackBB || 0).toFixed(1)} BB</span>
|
|
117555
117705
|
</div>
|
|
117556
117706
|
<span class="hole-cards" aria-hidden="true">${[0, 1].map((k) => tableCardHtml(p.cards?.[k], "hole")).join("")}</span>
|
|
117557
|
-
${action2 ? `<span class="table-action ${active ? "thinking" : actionKind}" aria-label="
|
|
117707
|
+
${action2 ? `<span class="table-action ${active ? "thinking" : actionKind}" aria-label="${escapeHtml(shownAction)}"><span class="table-action-icon">${action2.icon}</span><span class="table-action-name">${escapeHtml(action2.name)}</span>${action2.value ? `<span class="table-action-value">${escapeHtml(action2.value)}</span>` : ""}</span>` : ""}
|
|
117558
117708
|
</div>`;
|
|
117559
117709
|
}).join("");
|
|
117560
117710
|
layoutTableSeats();
|
|
@@ -117571,18 +117721,13 @@ function renderTable(s) {
|
|
|
117571
117721
|
els.levelValue.textContent = Number(table.blindLevel ?? 0) + 1;
|
|
117572
117722
|
els.levelValue.title = `Level ${Number(table.blindLevel ?? 0) + 1}`;
|
|
117573
117723
|
}
|
|
117574
|
-
function
|
|
117575
|
-
const value = String(position || "").trim().toUpperCase();
|
|
117576
|
-
if (value === "BTN" || value === "BUTTON" || value === "D" || value === "DEALER") return "D";
|
|
117577
|
-
return value === "SB" || value === "BB" ? value : "";
|
|
117578
|
-
}
|
|
117579
|
-
function tableActionParts(text, kind = "") {
|
|
117724
|
+
function tableActionParts(text, kind = "", actionAmount = null) {
|
|
117580
117725
|
const value = String(text || "").trim();
|
|
117581
117726
|
if (!value) return null;
|
|
117582
|
-
const amount = value.match(/(?:^|\s)([\d,.]+(?:K|M)?)(?:\s|$)/i)?.[1] || "";
|
|
117583
|
-
const label = { thinking: "THINKING", raise: "RAISE", bet: "BET", call: "CALL", check: "CHECK", fold: "FOLD", winner: "WINNER" }[kind];
|
|
117584
|
-
const name = label || value
|
|
117585
|
-
const icon = kind === "fold" ? "\xD7" : kind === "check" ? "\u2713" : kind === "call" ? "\u2192" : kind === "raise" ? "\u2197" : kind === "bet" ? "\u2191" : kind === "winner" ? "\u2605" : "\u2022";
|
|
117727
|
+
const amount = actionAmount == null ? value.match(/(?:^|\s)([\d,.]+(?:K|M)?)(?:\s|$)/i)?.[1] || "" : fmt(actionAmount);
|
|
117728
|
+
const label = { thinking: "THINKING", raise: "RAISE", bet: "BET", call: "CALL", check: "CHECK", fold: "FOLD", winner: "WINNER", all_in: "ALL IN" }[kind];
|
|
117729
|
+
const name = label || value;
|
|
117730
|
+
const icon = kind === "fold" ? "\xD7" : kind === "check" ? "\u2713" : kind === "call" ? "\u2192" : kind === "raise" ? "\u2197" : kind === "bet" || kind === "all_in" ? "\u2191" : kind === "winner" ? "\u2605" : "\u2022";
|
|
117586
117731
|
return { icon, name, value: amount };
|
|
117587
117732
|
}
|
|
117588
117733
|
function clearTurnRings(keep = null) {
|
|
@@ -117694,6 +117839,37 @@ function decisionTelemetryHtml(event, { limit = 4 } = {}) {
|
|
|
117694
117839
|
if (facts.length) parts.push(`<div class="decision-facts">${facts.join("")}</div>`);
|
|
117695
117840
|
return parts.join("");
|
|
117696
117841
|
}
|
|
117842
|
+
function reasoningTail(text, limit = 420) {
|
|
117843
|
+
const value = String(text || "");
|
|
117844
|
+
return value.length > limit ? `\u2026${value.slice(-limit)}` : value;
|
|
117845
|
+
}
|
|
117846
|
+
function capReasoning(text, limit = 8e3) {
|
|
117847
|
+
const value = String(text ?? "").trim();
|
|
117848
|
+
if (!value) return null;
|
|
117849
|
+
return value.length > limit ? `${value.slice(0, limit)}\u2026` : value;
|
|
117850
|
+
}
|
|
117851
|
+
function decisionReasoningText(event) {
|
|
117852
|
+
const reasoning = event?.reasoning;
|
|
117853
|
+
if (!reasoning) return "";
|
|
117854
|
+
return [reasoning.family, reasoning.sizing, reasoning.flat].filter(Boolean).join("\n\n");
|
|
117855
|
+
}
|
|
117856
|
+
function reasoningExcerpt(text, limit = 220) {
|
|
117857
|
+
const value = String(text || "").replace(/\s+/g, " ").trim();
|
|
117858
|
+
return value.length > limit ? `${value.slice(0, limit)}\u2026` : value;
|
|
117859
|
+
}
|
|
117860
|
+
function renderLiveReasoning(d) {
|
|
117861
|
+
const node = els.decisionReasoning;
|
|
117862
|
+
if (!node) return;
|
|
117863
|
+
const live = d?.reasoning ?? null;
|
|
117864
|
+
const text = String(live?.text || "").trim();
|
|
117865
|
+
node.classList.toggle("hidden", !text);
|
|
117866
|
+
if (!text) {
|
|
117867
|
+
node.innerHTML = "";
|
|
117868
|
+
return;
|
|
117869
|
+
}
|
|
117870
|
+
const label = live?.stage === "size" ? "Size reasoning" : live?.stage === "flat" ? "Reasoning" : "Action reasoning";
|
|
117871
|
+
node.innerHTML = `<span class="decision-reasoning-label">${escapeHtml(label)}${live?.channel === "content" ? " \xB7 answer" : ""}</span><span class="decision-reasoning-text">${escapeHtml(text)}</span>`;
|
|
117872
|
+
}
|
|
117697
117873
|
function setDecisionContext({ hand = "\u2014", street = "\u2014", position = "\u2014", options = "\u2014", label = "Legal actions", hint = "Choose one", labels = null } = {}) {
|
|
117698
117874
|
els.decisionHand.textContent = hand ?? "\u2014";
|
|
117699
117875
|
els.decisionStreet.textContent = String(street ?? "\u2014").replaceAll("_", " ");
|
|
@@ -117743,6 +117919,7 @@ function renderDecision(s) {
|
|
|
117743
117919
|
els.decisionPlayer.textContent = displayModelName(d.model);
|
|
117744
117920
|
els.decisionPlayer.title = d.model;
|
|
117745
117921
|
els.decisionModel.textContent = `${d.connection || "Connection"} \xB7 ${protocolDisplay(d.protocol)}`;
|
|
117922
|
+
renderLiveReasoning(d);
|
|
117746
117923
|
if (d.architecture === "hierarchical") {
|
|
117747
117924
|
const stage = d.stage ?? d.hierarchy?.stage1 ?? null;
|
|
117748
117925
|
const families = stage?.families ?? d.hierarchy?.families ?? [];
|
|
@@ -117762,6 +117939,7 @@ function renderDecision(s) {
|
|
|
117762
117939
|
startClock(d);
|
|
117763
117940
|
return;
|
|
117764
117941
|
}
|
|
117942
|
+
renderLiveReasoning(null);
|
|
117765
117943
|
stopClock();
|
|
117766
117944
|
if (finished) {
|
|
117767
117945
|
const winnerLast = [...s.events || []].reverse().find((e) => e.type === "DECISION" && e.playerId === s.winner.playerId) || last;
|
|
@@ -117820,11 +117998,21 @@ function renderDecision(s) {
|
|
|
117820
117998
|
els.legalActions.innerHTML = '<span class="action-chip">Start the tournament to stream model actions here.</span>';
|
|
117821
117999
|
}
|
|
117822
118000
|
}
|
|
118001
|
+
function liveFeedRowHtml(d) {
|
|
118002
|
+
const model = d.model || "";
|
|
118003
|
+
const stage = d.stage?.stage === "size" ? `${String(d.stage.family || "").toUpperCase()} size` : d.architecture === "hierarchical" ? "Action family" : "Legal actions";
|
|
118004
|
+
const reasoning = String(d.reasoning?.text || "").trim();
|
|
118005
|
+
const isAnswer = d.reasoning?.channel === "content";
|
|
118006
|
+
const bodyHtml = reasoning ? `<div class="decision-reasoning-excerpt live"><span class="decision-reasoning-label">${isAnswer ? "Streaming answer" : "Streaming reasoning"}</span>${escapeHtml(reasoningExcerpt(reasoning, 260))}</div>` : '<div class="decision-reason live"><span class="live-pulse" aria-hidden="true"></span>Thinking\u2026</div>';
|
|
118007
|
+
const elapsed = d.startedAt ? Math.max(0, Date.now() - d.startedAt - (d.pausedMs || 0)) : 0;
|
|
118008
|
+
return `<div class="decision-item decision-history-item live" aria-label="Live decision by ${escapeHtml(displayModelName(model))}"><div class="decision-item-head"><strong title="${escapeHtml(model)}">${escapeHtml(displayModelName(model))}</strong><span class="decision-item-action live-action"><span class="live-pulse" aria-hidden="true"></span>LIVE</span></div><div class="decision-reason">${escapeHtml(stage)}${d.street ? ` \xB7 ${escapeHtml(String(d.street))}` : ""}${d.position ? ` \xB7 ${escapeHtml(d.position)}` : ""}</div>` + bodyHtml + `<div class="decision-meta mono">${escapeHtml(d.connection || "")} \xB7 ${escapeHtml(shortModel(model))} \xB7 ${(elapsed / 1e3).toFixed(1)}s<span class="decision-replay-hint">streaming</span></div></div>`;
|
|
118009
|
+
}
|
|
117823
118010
|
function renderFeed(s) {
|
|
117824
118011
|
const feedArchive = eventArchive(s);
|
|
117825
118012
|
const events = feedArchive.filter((e) => e.type === "DECISION").slice(-HISTORY_CONFIG.recentDecisions).reverse();
|
|
117826
118013
|
const explanations = new Map(feedArchive.filter((e) => e.type === "SPECTATOR_EXPLANATION").map((e) => [e.decisionId, e.text]));
|
|
117827
|
-
|
|
118014
|
+
const liveHtml = s?.currentDecision ? liveFeedRowHtml(s.currentDecision) : "";
|
|
118015
|
+
const rowsHtml = events.map((e) => {
|
|
117828
118016
|
const infra = [];
|
|
117829
118017
|
if (e.protocolFallback) infra.push(e.protocolFallback);
|
|
117830
118018
|
if (e.decisionMeta?.retryCount) infra.push(`retry ${e.decisionMeta.retryCount}`);
|
|
@@ -117832,9 +118020,14 @@ function renderFeed(s) {
|
|
|
117832
118020
|
if (e.forced) infra.push("AUTO FALLBACK");
|
|
117833
118021
|
const telemetry = decisionTelemetryHtml(e);
|
|
117834
118022
|
const reason = explanations.get(e.decisionId) || e.publicReason || (e.decisionMeta?.family ? "Typed decision (no text rationale)" : "No public rationale");
|
|
117835
|
-
|
|
117836
|
-
|
|
117837
|
-
|
|
118023
|
+
const reasoning = decisionReasoningText(e);
|
|
118024
|
+
const reasoningHtml = reasoning ? `<div class="decision-reasoning-excerpt" title="${escapeHtml(reasoning)}"><span class="decision-reasoning-label">Reasoning</span>${escapeHtml(reasoningExcerpt(reasoning))}</div>` : "";
|
|
118025
|
+
return `<button type="button" class="decision-item decision-history-item ${e.error ? "error" : ""}" data-decision-id="${escapeHtml(e.id)}" aria-label="Replay ${escapeHtml(displayModelName(e.configuredModel || e.resolvedModel || ""))} decision"><div class="decision-item-head"><strong title="${escapeHtml(e.configuredModel || e.resolvedModel || "")}">${escapeHtml(displayModelName(e.configuredModel || e.resolvedModel || ""))}</strong><span class="decision-item-action">${escapeHtml(e.action?.description || "\u2014")}</span></div><div class="decision-reason">${escapeHtml(reason)}</div>${reasoningHtml}${telemetry ? `<div class="decision-item-telemetry">${telemetry}</div>` : ""}<div class="decision-meta mono">${escapeHtml(e.connection || "")} \xB7 ${escapeHtml(shortModel(e.resolvedModel || e.configuredModel || ""))} \xB7 ${e.primaryDecisionLatencyMs || e.latencyMs || 0}ms${infra.length ? ` \xB7 ${escapeHtml(infra.join(" \xB7 "))}` : ""}${e.error ? ` \xB7 ${escapeHtml(e.error)}` : ""}<span class="decision-replay-hint">View hand \u2197</span></div></button>`;
|
|
118026
|
+
}).join("");
|
|
118027
|
+
els.decisionFeed.innerHTML = liveHtml || rowsHtml ? `${liveHtml}${rowsHtml}` : '<div class="empty-state">No decisions yet.</div>';
|
|
118028
|
+
if (els.decisionFeedSummary) {
|
|
118029
|
+
els.decisionFeedSummary.textContent = s?.currentDecision ? `${events.length} settled \xB7 1 live` : events.length ? `${events.length} most recent` : "Newest first";
|
|
118030
|
+
}
|
|
117838
118031
|
}
|
|
117839
118032
|
function eventArchive(s = currentState) {
|
|
117840
118033
|
if (director?.events?.length) return director.events;
|
|
@@ -117923,7 +118116,7 @@ function renderStats(s) {
|
|
|
117923
118116
|
function tableRenderSignature(s) {
|
|
117924
118117
|
const running = ["RUNNING", "PAUSED"].includes(s?.status);
|
|
117925
118118
|
if (lobbyVisible && !running) {
|
|
117926
|
-
return JSON.stringify(["lobby", seatAssignments.map((p) => p ? [p.name, p.model, p.protocol, p.connectionId, p.provider] : null)]);
|
|
118119
|
+
return JSON.stringify(["lobby", seatAssignments.map((p) => p ? [p.name, p.model, p.protocol, p.connectionId, p.provider, Boolean(p.captureReasoning)] : null)]);
|
|
117927
118120
|
}
|
|
117928
118121
|
const t = s?.table;
|
|
117929
118122
|
if (!t) return JSON.stringify(["empty", s?.status || "IDLE"]);
|
|
@@ -117952,7 +118145,10 @@ function decisionRenderSignature(s) {
|
|
|
117952
118145
|
return JSON.stringify([d ? [d.id, d.playerId, d.model, d.protocol, d.provider, d.startedAt, d.baseMs, d.timeBankMs, d.pausedMs || 0, Boolean(d.pausedAt), d.architecture || null, d.stage || null, d.legalActions] : null, last?.id || null, s?.status || "IDLE", seatAssignments.filter(Boolean).length]);
|
|
117953
118146
|
}
|
|
117954
118147
|
function feedRenderSignature(s) {
|
|
117955
|
-
|
|
118148
|
+
const completed = (s?.events || []).filter((e) => e.type === "DECISION" || e.type === "SPECTATOR_EXPLANATION").slice(-24).map((e) => `${e.type}:${e.id}`);
|
|
118149
|
+
const live = s?.currentDecision;
|
|
118150
|
+
if (live) completed.push(`LIVE:${live.id}:${live.stage?.stage ?? ""}:${live.reasoning?.text ?? ""}`);
|
|
118151
|
+
return completed.join("|");
|
|
117956
118152
|
}
|
|
117957
118153
|
function eventsRenderSignature(s) {
|
|
117958
118154
|
const ev = eventArchive(s);
|
|
@@ -118328,10 +118524,23 @@ function createCaptureVideo(stream) {
|
|
|
118328
118524
|
pointerEvents: "none",
|
|
118329
118525
|
zIndex: "-1"
|
|
118330
118526
|
});
|
|
118331
|
-
video.srcObject = stream;
|
|
118527
|
+
video.srcObject = new MediaStream(stream.getVideoTracks());
|
|
118332
118528
|
document.body.append(video);
|
|
118333
118529
|
return video;
|
|
118334
118530
|
}
|
|
118531
|
+
function createCaptureAudioMonitor(track) {
|
|
118532
|
+
if (!track || track.getSettings?.().suppressLocalAudioPlayback !== true) return null;
|
|
118533
|
+
const audio = document.createElement("audio");
|
|
118534
|
+
audio.autoplay = true;
|
|
118535
|
+
audio.controls = false;
|
|
118536
|
+
audio.setAttribute("aria-hidden", "true");
|
|
118537
|
+
Object.assign(audio.style, { position: "fixed", width: "1px", height: "1px", opacity: "0", pointerEvents: "none" });
|
|
118538
|
+
audio.srcObject = new MediaStream([track]);
|
|
118539
|
+
document.body.append(audio);
|
|
118540
|
+
void audio.play().catch(() => {
|
|
118541
|
+
});
|
|
118542
|
+
return audio;
|
|
118543
|
+
}
|
|
118335
118544
|
function waitForCaptureVideo(video, track) {
|
|
118336
118545
|
return new Promise((resolve, reject) => {
|
|
118337
118546
|
let settled = false;
|
|
@@ -118474,6 +118683,14 @@ function disposeTableRecording(recording) {
|
|
|
118474
118683
|
recording.captureVideo.srcObject = null;
|
|
118475
118684
|
recording.captureVideo.remove();
|
|
118476
118685
|
}
|
|
118686
|
+
try {
|
|
118687
|
+
recording.audioMonitor?.pause?.();
|
|
118688
|
+
} catch {
|
|
118689
|
+
}
|
|
118690
|
+
if (recording.audioMonitor) {
|
|
118691
|
+
recording.audioMonitor.srcObject = null;
|
|
118692
|
+
recording.audioMonitor.remove();
|
|
118693
|
+
}
|
|
118477
118694
|
recording.canvasStream?.getTracks?.().forEach((track) => track.stop());
|
|
118478
118695
|
try {
|
|
118479
118696
|
recording.outputVideoTrack?.stop?.();
|
|
@@ -118501,6 +118718,7 @@ async function startTableRecording() {
|
|
|
118501
118718
|
}
|
|
118502
118719
|
let captureStream = null;
|
|
118503
118720
|
let captureVideo = null;
|
|
118721
|
+
let audioMonitor = null;
|
|
118504
118722
|
let recording = null;
|
|
118505
118723
|
try {
|
|
118506
118724
|
captureStream = await navigator.mediaDevices.getDisplayMedia({
|
|
@@ -118536,10 +118754,21 @@ async function startTableRecording() {
|
|
|
118536
118754
|
const sourceAudioTrack = captureStream.getAudioTracks()[0] || null;
|
|
118537
118755
|
if (sourceAudioTrack?.applyConstraints) {
|
|
118538
118756
|
try {
|
|
118539
|
-
await sourceAudioTrack.applyConstraints({ suppressLocalAudioPlayback: false });
|
|
118757
|
+
await sourceAudioTrack.applyConstraints({ suppressLocalAudioPlayback: { exact: false } });
|
|
118540
118758
|
} catch {
|
|
118759
|
+
try {
|
|
118760
|
+
await sourceAudioTrack.applyConstraints({ suppressLocalAudioPlayback: false });
|
|
118761
|
+
} catch {
|
|
118762
|
+
}
|
|
118541
118763
|
}
|
|
118542
118764
|
}
|
|
118765
|
+
if (soundEnabled) {
|
|
118766
|
+
try {
|
|
118767
|
+
await getAudioContext()?.resume();
|
|
118768
|
+
} catch {
|
|
118769
|
+
}
|
|
118770
|
+
}
|
|
118771
|
+
audioMonitor = createCaptureAudioMonitor(sourceAudioTrack);
|
|
118543
118772
|
const audioTrack = sourceAudioTrack;
|
|
118544
118773
|
captureVideo = createCaptureVideo(captureStream);
|
|
118545
118774
|
await captureVideo.play().catch(() => {
|
|
@@ -118567,6 +118796,7 @@ async function startTableRecording() {
|
|
|
118567
118796
|
sourceHeight,
|
|
118568
118797
|
audioTrack,
|
|
118569
118798
|
sourceAudioTrack,
|
|
118799
|
+
audioMonitor,
|
|
118570
118800
|
chunks: [],
|
|
118571
118801
|
stopping: false,
|
|
118572
118802
|
pipelineError: null
|
|
@@ -118623,6 +118853,14 @@ async function startTableRecording() {
|
|
|
118623
118853
|
} catch (err) {
|
|
118624
118854
|
if (recording) disposeTableRecording(recording);
|
|
118625
118855
|
else {
|
|
118856
|
+
try {
|
|
118857
|
+
audioMonitor?.pause?.();
|
|
118858
|
+
} catch {
|
|
118859
|
+
}
|
|
118860
|
+
if (audioMonitor) {
|
|
118861
|
+
audioMonitor.srcObject = null;
|
|
118862
|
+
audioMonitor.remove();
|
|
118863
|
+
}
|
|
118626
118864
|
captureStream?.getTracks?.().forEach((track) => track.stop());
|
|
118627
118865
|
if (captureVideo) {
|
|
118628
118866
|
captureVideo.srcObject = null;
|
|
@@ -118686,6 +118924,11 @@ function openDecisionReplay(eventId) {
|
|
|
118686
118924
|
els.replaySubtitle.textContent = replay ? "Exact decision snapshot captured immediately before the model acted." : "This older decision does not contain a replay snapshot.";
|
|
118687
118925
|
els.replayAction.textContent = event.action?.description || event.action?.type || "\u2014";
|
|
118688
118926
|
els.replayReason.textContent = replayReasonText(event);
|
|
118927
|
+
const reasoningText = decisionReasoningText(event);
|
|
118928
|
+
if (els.replayReasoning) {
|
|
118929
|
+
els.replayReasoning.classList.toggle("hidden", !reasoningText);
|
|
118930
|
+
els.replayReasoning.innerHTML = reasoningText ? `<span class="decision-reasoning-label">Model reasoning \xB7 raw, unvetted scratch work</span><p>${escapeHtml(reasoningText)}</p>` : "";
|
|
118931
|
+
}
|
|
118689
118932
|
if (!replay) {
|
|
118690
118933
|
els.replayOpponents.innerHTML = "";
|
|
118691
118934
|
els.replayStreet.textContent = event.street || "\u2014";
|
|
@@ -119256,6 +119499,7 @@ els.seatName.addEventListener("input", () => {
|
|
|
119256
119499
|
seatNameAuto = false;
|
|
119257
119500
|
});
|
|
119258
119501
|
els.seatProtocol.addEventListener("change", applySeatProtocolRules);
|
|
119502
|
+
els.seatCaptureReasoning?.addEventListener("change", applySeatProtocolRules);
|
|
119259
119503
|
els.refreshModelsBtn?.addEventListener("click", () => void refreshSeatModelCatalog({ force: true }));
|
|
119260
119504
|
els.removeSeatBtn.addEventListener("click", () => {
|
|
119261
119505
|
if (editingSeatIndex == null || director && ["RUNNING", "PAUSED"].includes(director.status)) return;
|