pokertools-arena 0.16.2 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/app.js +272 -36
- package/dist/index.html +5 -0
- package/dist/pokertools-arena.html +278 -37
- package/dist/styles.css +1 -1
- package/docs/diagnostics/SUMMARY.md +1 -1
- package/docs/diagnostics/summary.json +1 -1
- package/docs/releases/RELEASE_NOTES.md +57 -0
- package/package.json +2 -2
- package/src/app.js +180 -22
- package/src/index.html +5 -0
- package/src/lib/decision-core.js +103 -9
- package/src/styles.css +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
| **Package** | `npx pokertools-arena` |
|
|
18
18
|
| **Runtime** | Node.js ≥ 24 for tooling; any modern browser for the app |
|
|
19
19
|
|
|
20
|
-
> **Current release: 0.
|
|
20
|
+
> **Current release: 0.17.1.** A mid-hand all-in no longer aborts the tournament: the decision-context player count now tracks non-eliminated seats instead of stacks, and any context failure falls back to a deterministic safe action so the game continues.
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
package/dist/app.js
CHANGED
|
@@ -114432,8 +114432,15 @@ async function fetchStreamingJson(url, options, { maxRetries = 1, onDelta = null
|
|
|
114432
114432
|
throw new ArenaRequestError(message, { status: response.status, category: requestErrorCategory(response.status), payload, incidents });
|
|
114433
114433
|
}
|
|
114434
114434
|
}
|
|
114435
|
+
function requestErrorDetail(err) {
|
|
114436
|
+
const parts = [summarizeError(err)];
|
|
114437
|
+
const nested = err?.payload?.error?.metadata?.raw ?? err?.payload?.metadata?.raw;
|
|
114438
|
+
if (typeof nested === "string") parts.push(nested);
|
|
114439
|
+
else if (nested) parts.push(String(JSON.stringify(nested)));
|
|
114440
|
+
return parts.join(" ");
|
|
114441
|
+
}
|
|
114435
114442
|
function isUnsupportedToolChoiceError(err) {
|
|
114436
|
-
const message =
|
|
114443
|
+
const message = requestErrorDetail(err);
|
|
114437
114444
|
return [400, 404, 422].includes(Number(err?.status)) && /tool[_ -]?choice|tool call|tools?.*(?:unsupported|support)|no endpoints?.*support/i.test(message);
|
|
114438
114445
|
}
|
|
114439
114446
|
function mapGet(mapish, key, fallback = 0) {
|
|
@@ -114459,13 +114466,23 @@ function jsonSafe(value) {
|
|
|
114459
114466
|
}
|
|
114460
114467
|
var protocolCapabilityCache = /* @__PURE__ */ new Map();
|
|
114461
114468
|
var modelSupportedParameters = /* @__PURE__ */ new Map();
|
|
114469
|
+
var modelReasoningCapabilities = /* @__PURE__ */ new Map();
|
|
114462
114470
|
var DEFAULT_TEMPERATURE = 0.3;
|
|
114463
114471
|
function registerModelCapabilities(models = []) {
|
|
114464
114472
|
for (const entry of models ?? []) {
|
|
114465
114473
|
const id2 = typeof entry === "string" ? entry : entry?.id;
|
|
114474
|
+
if (!id2) continue;
|
|
114466
114475
|
const parameters = typeof entry === "object" && entry ? entry.supported_parameters : null;
|
|
114467
|
-
if (
|
|
114468
|
-
|
|
114476
|
+
if (Array.isArray(parameters)) modelSupportedParameters.set(String(id2), new Set(parameters.map(String)));
|
|
114477
|
+
const reasoning = typeof entry === "object" && entry ? entry.reasoning : null;
|
|
114478
|
+
if (!reasoning || typeof reasoning !== "object") continue;
|
|
114479
|
+
const efforts = Array.isArray(reasoning.supported_efforts) ? reasoning.supported_efforts.map((value) => String(value).trim().toLowerCase()).filter(Boolean) : [];
|
|
114480
|
+
const declaredDefault = String(reasoning.default_effort || "").trim().toLowerCase();
|
|
114481
|
+
modelReasoningCapabilities.set(String(id2), {
|
|
114482
|
+
mandatory: reasoning.mandatory === true,
|
|
114483
|
+
supportedEfforts: efforts,
|
|
114484
|
+
defaultEffort: efforts.includes(declaredDefault) ? declaredDefault : null
|
|
114485
|
+
});
|
|
114469
114486
|
}
|
|
114470
114487
|
}
|
|
114471
114488
|
function modelSupportsParameter(model, parameter) {
|
|
@@ -114473,6 +114490,34 @@ function modelSupportsParameter(model, parameter) {
|
|
|
114473
114490
|
if (!supported) return null;
|
|
114474
114491
|
return supported.has(String(parameter));
|
|
114475
114492
|
}
|
|
114493
|
+
function reasoningCapabilities(model) {
|
|
114494
|
+
return modelReasoningCapabilities.get(String(model || "")) ?? null;
|
|
114495
|
+
}
|
|
114496
|
+
function reasoningEffortsFor(model) {
|
|
114497
|
+
return reasoningCapabilities(model)?.supportedEfforts ?? [];
|
|
114498
|
+
}
|
|
114499
|
+
function defaultReasoningEffort(model) {
|
|
114500
|
+
return reasoningCapabilities(model)?.defaultEffort ?? null;
|
|
114501
|
+
}
|
|
114502
|
+
function requiresReasoning(model) {
|
|
114503
|
+
return reasoningCapabilities(model)?.mandatory === true;
|
|
114504
|
+
}
|
|
114505
|
+
function resolveReasoningEffort(model, requested) {
|
|
114506
|
+
const value = String(requested || "").trim().toLowerCase();
|
|
114507
|
+
if (!value) return null;
|
|
114508
|
+
const capabilities = reasoningCapabilities(model);
|
|
114509
|
+
if (!capabilities) return value;
|
|
114510
|
+
return capabilities.supportedEfforts.includes(value) ? value : null;
|
|
114511
|
+
}
|
|
114512
|
+
function reasoningOverrideFor(agent) {
|
|
114513
|
+
const effort = resolveReasoningEffort(agent?.model, agent?.reasoningEffort);
|
|
114514
|
+
return effort ? { effort } : { max_tokens: 256, exclude: false };
|
|
114515
|
+
}
|
|
114516
|
+
function truncationReasoningOverride(model) {
|
|
114517
|
+
const capabilities = reasoningCapabilities(model);
|
|
114518
|
+
if (!capabilities) return { effort: "low" };
|
|
114519
|
+
return capabilities.supportedEfforts.includes("low") ? { effort: "low" } : { max_tokens: 4096, exclude: false };
|
|
114520
|
+
}
|
|
114476
114521
|
function resolveTemperature(model, requested) {
|
|
114477
114522
|
if (modelSupportsParameter(model, "temperature") === false) return void 0;
|
|
114478
114523
|
return Number.isFinite(Number(requested)) ? Number(requested) : DEFAULT_TEMPERATURE;
|
|
@@ -114511,7 +114556,7 @@ function isReasoningModel(model) {
|
|
|
114511
114556
|
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);
|
|
114512
114557
|
}
|
|
114513
114558
|
function wantsReasoning(agent) {
|
|
114514
|
-
return isReasoningModel(agent?.model) || agent?.captureReasoning === true;
|
|
114559
|
+
return isReasoningModel(agent?.model) || agent?.captureReasoning === true || reasoningCapabilities(agent?.model)?.mandatory === true || Boolean(resolveReasoningEffort(agent?.model, agent?.reasoningEffort));
|
|
114515
114560
|
}
|
|
114516
114561
|
function effectiveProtocol(agent, connection) {
|
|
114517
114562
|
if (isOpenRouterConnection(connection) && isJevModel(agent?.model)) return "jev_decisions";
|
|
@@ -114617,6 +114662,13 @@ function playerCards(player) {
|
|
|
114617
114662
|
function playerStack(player) {
|
|
114618
114663
|
return asNumber(player?.stack ?? player?.chips ?? 0);
|
|
114619
114664
|
}
|
|
114665
|
+
function playersStillInTournament(players, eliminatedPlayerIds = []) {
|
|
114666
|
+
const eliminated = new Set(eliminatedPlayerIds ?? []);
|
|
114667
|
+
return (players ?? []).filter((player) => player?.id && !eliminated.has(player.id));
|
|
114668
|
+
}
|
|
114669
|
+
function playersWithChips(players) {
|
|
114670
|
+
return (players ?? []).filter((player) => player && playerStack(player) > 0);
|
|
114671
|
+
}
|
|
114620
114672
|
function currentBet(state, seat) {
|
|
114621
114673
|
return asNumber(mapGet(state?.currentBets, seat, state?.players?.[seat]?.bet ?? 0));
|
|
114622
114674
|
}
|
|
@@ -114864,7 +114916,7 @@ function assertDecisionState(state) {
|
|
|
114864
114916
|
}
|
|
114865
114917
|
}
|
|
114866
114918
|
const expectedRemaining = 1 + state.opponents.length;
|
|
114867
|
-
if (Number(state.tournament?.playersRemaining) !== expectedRemaining) throw new Error(`playersRemaining mismatch: expected ${expectedRemaining}, got ${state.tournament?.playersRemaining}`);
|
|
114919
|
+
if (Number(state.tournament?.playersRemaining) !== expectedRemaining) throw new Error(`playersRemaining mismatch: expected ${expectedRemaining} (hero plus ${state.opponents.length} non-eliminated opponents), got ${state.tournament?.playersRemaining}`);
|
|
114868
114920
|
return state;
|
|
114869
114921
|
}
|
|
114870
114922
|
function extractTextContent(message) {
|
|
@@ -115287,7 +115339,7 @@ Respond with one of: ${criteriaKeys.map((k) => `${k} = ${criteria[k]}`).join(";
|
|
|
115287
115339
|
}
|
|
115288
115340
|
if (isOpenRouter) {
|
|
115289
115341
|
body.provider = agent.provider ? { only: [agent.provider], allow_fallbacks: false, require_parameters: true } : { allow_fallbacks: true, require_parameters: true };
|
|
115290
|
-
if (wantsReasoning(agent)) body.reasoning = reasoning ??
|
|
115342
|
+
if (wantsReasoning(agent)) body.reasoning = reasoning ?? reasoningOverrideFor(agent);
|
|
115291
115343
|
}
|
|
115292
115344
|
if (stream) {
|
|
115293
115345
|
body.stream = true;
|
|
@@ -115350,7 +115402,7 @@ async function decideOpenAICompatible({ agent, connection, state, legalActions,
|
|
|
115350
115402
|
result = await execute(actualProtocol);
|
|
115351
115403
|
} else if (err?.truncated) {
|
|
115352
115404
|
truncationRetry = true;
|
|
115353
|
-
result = await execute(actualProtocol, { maxTokens: 4096, reasoning:
|
|
115405
|
+
result = await execute(actualProtocol, { maxTokens: 4096, reasoning: truncationReasoningOverride(agent.model) });
|
|
115354
115406
|
} else throw err;
|
|
115355
115407
|
}
|
|
115356
115408
|
const normalized = normalizeDecisionObject(result.obj, legalActions, decisionId);
|
|
@@ -115526,7 +115578,7 @@ async function requestChatStage({ agent, connection, schema, toolName, systemPro
|
|
|
115526
115578
|
}
|
|
115527
115579
|
if (isOpenRouter) {
|
|
115528
115580
|
body.provider = agent.provider ? { only: [agent.provider], allow_fallbacks: false, require_parameters: true } : { allow_fallbacks: true, require_parameters: true };
|
|
115529
|
-
if (wantsReasoning(agent)) body.reasoning = override.reasoning ??
|
|
115581
|
+
if (wantsReasoning(agent)) body.reasoning = override.reasoning ?? reasoningOverrideFor(agent);
|
|
115530
115582
|
}
|
|
115531
115583
|
if (onDelta) {
|
|
115532
115584
|
body.stream = true;
|
|
@@ -115570,7 +115622,7 @@ async function requestChatStage({ agent, connection, schema, toolName, systemPro
|
|
|
115570
115622
|
return { ...result, protocolFallback: "tool\u2192json_schema" };
|
|
115571
115623
|
}
|
|
115572
115624
|
if (err?.truncated) {
|
|
115573
|
-
const result = await execute(actual, { maxTokens: 4096, reasoning:
|
|
115625
|
+
const result = await execute(actual, { maxTokens: 4096, reasoning: truncationReasoningOverride(agent.model) });
|
|
115574
115626
|
return { ...result, protocolFallback: "truncated\u2192retry" };
|
|
115575
115627
|
}
|
|
115576
115628
|
throw err;
|
|
@@ -115843,6 +115895,8 @@ var els = {
|
|
|
115843
115895
|
seatProtocol: $("#seatProtocol"),
|
|
115844
115896
|
seatProvider: $("#seatProvider"),
|
|
115845
115897
|
seatCaptureReasoning: $("#seatCaptureReasoning"),
|
|
115898
|
+
seatReasoningEffort: $("#seatReasoningEffort"),
|
|
115899
|
+
seatReasoningEffortHelp: $("#seatReasoningEffortHelp"),
|
|
115846
115900
|
seatError: $("#seatError"),
|
|
115847
115901
|
removeSeatBtn: $("#removeSeatBtn"),
|
|
115848
115902
|
cancelSeatBtn: $("#cancelSeatBtn"),
|
|
@@ -115893,6 +115947,7 @@ var WINNER_REVIEW_MS = 2e3;
|
|
|
115893
115947
|
var seatAssignments = Array(MAX_LOBBY_SEATS).fill(null);
|
|
115894
115948
|
var editingSeatIndex = null;
|
|
115895
115949
|
var seatNameAuto = false;
|
|
115950
|
+
var seatReasoningEffortDraft = "";
|
|
115896
115951
|
var lobbyVisible = true;
|
|
115897
115952
|
var pendingAutostart = false;
|
|
115898
115953
|
var arenaMaxDecisions = 0;
|
|
@@ -116198,7 +116253,8 @@ function injectedEnvironmentConfig() {
|
|
|
116198
116253
|
model: String(player.model || ""),
|
|
116199
116254
|
protocol: String(player.protocol || "tool"),
|
|
116200
116255
|
provider: String(player.provider || ""),
|
|
116201
|
-
captureReasoning: Boolean(player.captureReasoning)
|
|
116256
|
+
captureReasoning: Boolean(player.captureReasoning),
|
|
116257
|
+
reasoningEffort: String(player.reasoningEffort || "").trim().toLowerCase()
|
|
116202
116258
|
}))
|
|
116203
116259
|
};
|
|
116204
116260
|
}
|
|
@@ -116515,7 +116571,7 @@ function normalizeConfig(input = {}) {
|
|
|
116515
116571
|
if (!connIds.has(raw.connectionId)) throw new Error(`Connection missing for ${name}`);
|
|
116516
116572
|
const model = String(raw.model || "").trim();
|
|
116517
116573
|
if (!model) throw new Error(`Model missing for ${name}`);
|
|
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) };
|
|
116574
|
+
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, reasoningEffort: String(raw.reasoningEffort || "").trim().toLowerCase(), temperature: clamp(Number(raw.temperature) || 0.3, 0, 2) };
|
|
116519
116575
|
})
|
|
116520
116576
|
};
|
|
116521
116577
|
}
|
|
@@ -116591,7 +116647,7 @@ var TournamentDirector = class {
|
|
|
116591
116647
|
for (const player of this.config.players) {
|
|
116592
116648
|
this.engine.sit(player.seat, player.id, player.name, this.config.startingStack);
|
|
116593
116649
|
this.timeBanks[player.id] = this.config.timeBankSeconds * 1e3;
|
|
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: "" };
|
|
116650
|
+
this.stats[player.id] = { decisions: 0, invalid: 0, modelErrors: 0, providerErrors: 0, rateLimits: 0, timeouts: 0, autoFallbacks: 0, contextErrors: 0, protocolFallbacks: 0, retries: 0, totalLatencyMs: 0, lastAction: null, lastActionType: null, lastActionAmount: null, lastReason: "" };
|
|
116595
116651
|
}
|
|
116596
116652
|
this.logEvent("TOURNAMENT_START", { config: this.publicConfig() });
|
|
116597
116653
|
this.broadcast();
|
|
@@ -116635,8 +116691,15 @@ var TournamentDirector = class {
|
|
|
116635
116691
|
while (this.status === "PAUSED") await new Promise((resolve) => this.pauseResolvers.push(resolve));
|
|
116636
116692
|
if (this.status === "STOPPED") throw new Error("Tournament stopped");
|
|
116637
116693
|
}
|
|
116694
|
+
// Decision-context/table count: seated players that are not eliminated. Must
|
|
116695
|
+
// equal `1 + non-eliminated opponents` in serializeForAgent, so it must NOT be
|
|
116696
|
+
// derived from stacks — an all-in player has stack 0 mid-hand and is still in.
|
|
116638
116697
|
playersRemaining() {
|
|
116639
|
-
return (this.engine?.state?.players
|
|
116698
|
+
return playersStillInTournament(this.engine?.state?.players, this.eliminations.map((e) => e.playerId));
|
|
116699
|
+
}
|
|
116700
|
+
// Tournament-end / place accounting: seats that can still contest chips.
|
|
116701
|
+
playersWithChips() {
|
|
116702
|
+
return playersWithChips(this.engine?.state?.players);
|
|
116640
116703
|
}
|
|
116641
116704
|
tournamentMeta() {
|
|
116642
116705
|
const startingPlayers = this.config?.players.length ?? 0;
|
|
@@ -116703,9 +116766,9 @@ var TournamentDirector = class {
|
|
|
116703
116766
|
async run() {
|
|
116704
116767
|
while (["RUNNING", "PAUSED"].includes(this.status)) {
|
|
116705
116768
|
await this.waitIfPaused();
|
|
116706
|
-
const remaining = this.
|
|
116769
|
+
const remaining = this.playersWithChips();
|
|
116707
116770
|
if (remaining.length <= 1) {
|
|
116708
|
-
this.finish(remaining[0]
|
|
116771
|
+
this.finish(remaining[0] ?? null);
|
|
116709
116772
|
return;
|
|
116710
116773
|
}
|
|
116711
116774
|
if (this.handNumber > 0 && this.handNumber % this.config.handsPerLevel === 0) {
|
|
@@ -116716,10 +116779,18 @@ var TournamentDirector = class {
|
|
|
116716
116779
|
this.logEvent("BLINDS_UP_FAILED", { error: summarizeError(err) });
|
|
116717
116780
|
}
|
|
116718
116781
|
}
|
|
116719
|
-
|
|
116720
|
-
|
|
116721
|
-
|
|
116722
|
-
|
|
116782
|
+
try {
|
|
116783
|
+
await this.startHand();
|
|
116784
|
+
await this.playHand();
|
|
116785
|
+
this.completeHand();
|
|
116786
|
+
this.broadcast();
|
|
116787
|
+
this.handErrorStreak = 0;
|
|
116788
|
+
} catch (err) {
|
|
116789
|
+
if (this.status === "STOPPED" || err?.name === "AbortError") throw err;
|
|
116790
|
+
await this.recoverHand(err);
|
|
116791
|
+
this.handErrorStreak = (this.handErrorStreak || 0) + 1;
|
|
116792
|
+
if (this.handErrorStreak >= 3) throw err;
|
|
116793
|
+
}
|
|
116723
116794
|
if (this.budgetReached) {
|
|
116724
116795
|
this.logEvent("DECISION_BUDGET_REACHED", { decisions: this.decisionCount, budget: this.decisionBudget });
|
|
116725
116796
|
this.stop();
|
|
@@ -116729,6 +116800,52 @@ var TournamentDirector = class {
|
|
|
116729
116800
|
await sleep(Math.max(WINNER_REVIEW_MS, this.config.betweenHandsMs));
|
|
116730
116801
|
}
|
|
116731
116802
|
}
|
|
116803
|
+
// Last-resort safety net for a hand. A model failure or a decision-context
|
|
116804
|
+
// failure is already handled inside the hand loop; anything else that escapes
|
|
116805
|
+
// (a future regression) would otherwise abort the whole tournament mid-game.
|
|
116806
|
+
// Here the hand is finished with deterministic safe actions so the tournament
|
|
116807
|
+
// continues. Recovery is bounded by the hand-action guard and by the caller's
|
|
116808
|
+
// streak counter, so a genuinely broken engine still surfaces as an error
|
|
116809
|
+
// instead of looping forever.
|
|
116810
|
+
async recoverHand(triggerError) {
|
|
116811
|
+
this.currentDecision = null;
|
|
116812
|
+
if (this.handComplete()) {
|
|
116813
|
+
this.broadcast();
|
|
116814
|
+
return;
|
|
116815
|
+
}
|
|
116816
|
+
this.logEvent("HAND_RECOVERY", { handNumber: this.handNumber, street: this.engine?.state?.street ?? null, error: summarizeError(triggerError) });
|
|
116817
|
+
this.broadcast();
|
|
116818
|
+
let guard = 0;
|
|
116819
|
+
while (!this.handComplete()) {
|
|
116820
|
+
if (++guard > 500) throw new Error("Hand recovery guard exceeded 500 actions");
|
|
116821
|
+
await this.waitIfPaused();
|
|
116822
|
+
const state = this.engine.state, seat = state.actionTo;
|
|
116823
|
+
if (seat == null) {
|
|
116824
|
+
if (Array.isArray(state.winners) && state.winners.length) break;
|
|
116825
|
+
await sleep(10);
|
|
116826
|
+
continue;
|
|
116827
|
+
}
|
|
116828
|
+
const player = state.players?.[seat];
|
|
116829
|
+
if (!player) break;
|
|
116830
|
+
if (state.street === "SHOWDOWN") {
|
|
116831
|
+
const show = { type: ACTION.SHOW, playerId: player.id, cardIndices: [0, 1] };
|
|
116832
|
+
if (this.engine.validate(show)?.valid) {
|
|
116833
|
+
this.engine.act(show);
|
|
116834
|
+
continue;
|
|
116835
|
+
}
|
|
116836
|
+
const muck = { type: ACTION.MUCK, playerId: player.id };
|
|
116837
|
+
if (this.engine.validate(muck)?.valid) {
|
|
116838
|
+
this.engine.act(muck);
|
|
116839
|
+
continue;
|
|
116840
|
+
}
|
|
116841
|
+
}
|
|
116842
|
+
if (!this.forceSafeAction(seat, { error: triggerError, errorCategory: "recovery" })) break;
|
|
116843
|
+
this.broadcast();
|
|
116844
|
+
}
|
|
116845
|
+
if (!this.handComplete()) throw triggerError;
|
|
116846
|
+
this.completeHand();
|
|
116847
|
+
this.broadcast();
|
|
116848
|
+
}
|
|
116732
116849
|
pruneBustedSeats() {
|
|
116733
116850
|
const failures = [];
|
|
116734
116851
|
for (const p of this.engine?.state?.players ?? []) {
|
|
@@ -116809,22 +116926,31 @@ var TournamentDirector = class {
|
|
|
116809
116926
|
}
|
|
116810
116927
|
async takeDecision(agent, seat, legalActions) {
|
|
116811
116928
|
const decisionId = id(`d-h${this.handNumber}-s${seat + 1}`), baseMs = this.config.actionSeconds * 1e3, bankBefore = this.timeBanks[agent.id] ?? 0, totalMs = baseMs + bankBefore;
|
|
116929
|
+
const stats = this.stats[agent.id];
|
|
116930
|
+
const connection = this.config.connections.find((c) => c.id === agent.connectionId);
|
|
116931
|
+
const architecture = this.config.decisionArchitecture === DECISION_ARCHITECTURES.FLAT ? "flat" : "hierarchical";
|
|
116812
116932
|
const recentHands = buildPublicTournamentMemory(this.events, this.handNumber);
|
|
116813
116933
|
const publicPlayerStats = buildPublicPlayerStats(this.events, this.config.players, this.handNumber);
|
|
116814
116934
|
const actionHistory = buildCurrentHandPublicActions(this.events, this.handNumber);
|
|
116815
|
-
const baseState2 = serializeForAgent(this.engine, seat, this.tournamentMeta(), legalActions, recentHands, publicPlayerStats, actionHistory);
|
|
116816
|
-
const stateForAgent = assertDecisionState(applyBenchmarkMode(baseState2, this.config.benchmarkMode)), startedAt = Date.now();
|
|
116817
|
-
const connection = this.config.connections.find((c) => c.id === agent.connectionId);
|
|
116818
|
-
const architecture = this.config.decisionArchitecture === DECISION_ARCHITECTURES.FLAT ? "flat" : "hierarchical";
|
|
116819
116935
|
const sizesForFamily = (family) => legalAggressiveSizes(this.engine, seat, family);
|
|
116820
|
-
let hierarchy = null;
|
|
116821
|
-
|
|
116822
|
-
|
|
116823
|
-
|
|
116824
|
-
|
|
116825
|
-
|
|
116936
|
+
let stateForAgent = null, hierarchy = null;
|
|
116937
|
+
try {
|
|
116938
|
+
stateForAgent = assertDecisionState(applyBenchmarkMode(
|
|
116939
|
+
serializeForAgent(this.engine, seat, this.tournamentMeta(), legalActions, recentHands, publicPlayerStats, actionHistory),
|
|
116940
|
+
this.config.benchmarkMode
|
|
116941
|
+
));
|
|
116942
|
+
if (architecture === "hierarchical") {
|
|
116943
|
+
hierarchy = buildHierarchicalDecision(stateForAgent, { legalActions: stateForAgent.legalActions });
|
|
116944
|
+
if (hierarchy.aggressiveFamily) {
|
|
116945
|
+
const engineSizes = sizesForFamily(hierarchy.aggressiveFamily);
|
|
116946
|
+
hierarchy.stage2 = { ...hierarchy.stage2, sizes: engineSizes, criteria: sizeCriteria(engineSizes) };
|
|
116947
|
+
}
|
|
116826
116948
|
}
|
|
116949
|
+
} catch (contextError) {
|
|
116950
|
+
if (!this.forceSafeAction(seat, { error: contextError, errorCategory: "context", decisionId })) throw contextError;
|
|
116951
|
+
return;
|
|
116827
116952
|
}
|
|
116953
|
+
const startedAt = Date.now();
|
|
116828
116954
|
this.currentDecision = {
|
|
116829
116955
|
id: decisionId,
|
|
116830
116956
|
playerId: agent.id,
|
|
@@ -116833,6 +116959,7 @@ var TournamentDirector = class {
|
|
|
116833
116959
|
model: agent.model,
|
|
116834
116960
|
connection: connection?.name ?? agent.connectionId,
|
|
116835
116961
|
provider: agent.provider || "auto",
|
|
116962
|
+
reasoningEffort: agent.reasoningEffort || null,
|
|
116836
116963
|
protocol: effectiveProtocol(agent, connection),
|
|
116837
116964
|
handNumber: this.handNumber,
|
|
116838
116965
|
street: this.engine.state.street,
|
|
@@ -116850,7 +116977,6 @@ var TournamentDirector = class {
|
|
|
116850
116977
|
};
|
|
116851
116978
|
this.logEvent("DECISION_START", this.currentDecision);
|
|
116852
116979
|
this.broadcast();
|
|
116853
|
-
const stats = this.stats[agent.id];
|
|
116854
116980
|
let result = null, error = null, errorCategory = null, elapsed = 0;
|
|
116855
116981
|
const pausedTotal = () => (this.currentDecision?.pausedMs || 0) + (this.currentDecision?.pausedAt ? Math.max(0, Date.now() - this.currentDecision.pausedAt) : 0);
|
|
116856
116982
|
const recordIncident = (incident) => {
|
|
@@ -116981,6 +117107,7 @@ var TournamentDirector = class {
|
|
|
116981
117107
|
configuredModel: agent.model,
|
|
116982
117108
|
resolvedModel: result?.model ?? agent.model,
|
|
116983
117109
|
provider: agent.provider || "auto",
|
|
117110
|
+
reasoningEffort: agent.reasoningEffort || null,
|
|
116984
117111
|
action: { id: chosen.id, type: chosen.type, amount: chosen.amount, description: chosen.description },
|
|
116985
117112
|
forced,
|
|
116986
117113
|
legalActions: legalActions.map((a) => ({ id: a.id, type: a.type, amount: a.amount ?? null, description: a.description })),
|
|
@@ -117023,6 +117150,72 @@ var TournamentDirector = class {
|
|
|
117023
117150
|
}
|
|
117024
117151
|
this.currentDecision = null;
|
|
117025
117152
|
}
|
|
117153
|
+
// Deterministic safe action for one seat, used whenever a decision cannot be
|
|
117154
|
+
// produced (context failure or emergency hand recovery). It always picks a
|
|
117155
|
+
// legal engine action, records a normal DECISION event so public action
|
|
117156
|
+
// history, replay and stats stay complete, and marks the row `forced`. It
|
|
117157
|
+
// never consults a model and never sees masked-out information.
|
|
117158
|
+
// Returns the chosen action, or null when the seat has no legal action.
|
|
117159
|
+
forceSafeAction(seat, { error = null, errorCategory = "context", decisionId = null } = {}) {
|
|
117160
|
+
const state = this.engine.state;
|
|
117161
|
+
const player = state.players?.[seat];
|
|
117162
|
+
if (!player) return null;
|
|
117163
|
+
const legalActions = legalActionCandidates(this.engine, seat);
|
|
117164
|
+
const chosen = fallbackAction(legalActions);
|
|
117165
|
+
if (!chosen) return null;
|
|
117166
|
+
this.engine.act(chosen.engineAction);
|
|
117167
|
+
const agent = (this.config?.players ?? []).find((p) => p.id === player.id) ?? null;
|
|
117168
|
+
const connection = agent ? this.config.connections.find((c) => c.id === agent.connectionId) ?? null : null;
|
|
117169
|
+
const stats = this.stats?.[player.id];
|
|
117170
|
+
if (stats) {
|
|
117171
|
+
stats.decisions++;
|
|
117172
|
+
stats.autoFallbacks++;
|
|
117173
|
+
stats.contextErrors = (stats.contextErrors || 0) + 1;
|
|
117174
|
+
stats.lastAction = chosen.description;
|
|
117175
|
+
stats.lastActionType = chosen.type;
|
|
117176
|
+
stats.lastActionAmount = chosen.amount ?? null;
|
|
117177
|
+
stats.lastReason = `Automatic ${chosen.description} after ${errorCategory} error.`;
|
|
117178
|
+
}
|
|
117179
|
+
this.decisionCount++;
|
|
117180
|
+
if (this.decisionBudget > 0 && this.decisionCount >= this.decisionBudget) this.budgetReached = true;
|
|
117181
|
+
this.currentDecision = null;
|
|
117182
|
+
const protocol = agent ? effectiveProtocol(agent, connection) : "none";
|
|
117183
|
+
this.logEvent("DECISION", {
|
|
117184
|
+
decisionId: decisionId ?? id(`d-h${this.handNumber}-s${seat + 1}`),
|
|
117185
|
+
handNumber: this.handNumber,
|
|
117186
|
+
street: state.street,
|
|
117187
|
+
position: positionForSeat(state, seat),
|
|
117188
|
+
potBefore: totalPot(state),
|
|
117189
|
+
playerId: player.id,
|
|
117190
|
+
playerName: player.name,
|
|
117191
|
+
connection: connection?.name ?? agent?.connectionId ?? null,
|
|
117192
|
+
protocol,
|
|
117193
|
+
requestedProtocol: protocol,
|
|
117194
|
+
protocolFallback: null,
|
|
117195
|
+
configuredModel: agent?.model ?? null,
|
|
117196
|
+
resolvedModel: agent?.model ?? null,
|
|
117197
|
+
provider: agent?.provider || "auto",
|
|
117198
|
+
reasoningEffort: agent?.reasoningEffort || null,
|
|
117199
|
+
action: { id: chosen.id, type: chosen.type, amount: chosen.amount, description: chosen.description },
|
|
117200
|
+
forced: true,
|
|
117201
|
+
legalActions: legalActions.map((a) => ({ id: a.id, type: a.type, amount: a.amount ?? null, description: a.description })),
|
|
117202
|
+
latencyMs: 0,
|
|
117203
|
+
primaryDecisionLatencyMs: 0,
|
|
117204
|
+
timeBankUsedMs: 0,
|
|
117205
|
+
timeBankRemainingMs: this.timeBanks?.[player.id] ?? 0,
|
|
117206
|
+
publicReason: stats?.lastReason ?? "",
|
|
117207
|
+
usage: null,
|
|
117208
|
+
decisionMeta: { method: "safe-fallback", decisionArchitecture: "safe-fallback-v1", reason: errorCategory, error: error ? summarizeError(error) : null },
|
|
117209
|
+
reasoning: null,
|
|
117210
|
+
benchmarkMode: this.config?.benchmarkMode ?? DEFAULT_BENCHMARK_MODE,
|
|
117211
|
+
decisionArchitecture: this.config?.decisionArchitecture ?? DEFAULT_DECISION_ARCHITECTURE,
|
|
117212
|
+
representation: this.config?.representation ?? DEFAULT_REPRESENTATION_MODE,
|
|
117213
|
+
replay: null,
|
|
117214
|
+
errorCategory,
|
|
117215
|
+
error: error ? summarizeError(error) : null
|
|
117216
|
+
});
|
|
117217
|
+
return chosen;
|
|
117218
|
+
}
|
|
117026
117219
|
enqueueSpectatorExplanation({ decisionId, agent, connection, stateForAgent, chosen }) {
|
|
117027
117220
|
if (!connection || !["openai", "openrouter"].includes(connection.kind) || isJevModel(agent.model)) return this.explanationChain;
|
|
117028
117221
|
this.explanationChain = this.explanationChain.then(async () => {
|
|
@@ -117063,7 +117256,7 @@ var TournamentDirector = class {
|
|
|
117063
117256
|
const already = this.eliminations.some((e) => e.playerId === player.id);
|
|
117064
117257
|
return p && playerStack(p) <= 0 && !already ? { player, seat, startStack: asNumber(this.handStartStacks?.[player.id]) } : null;
|
|
117065
117258
|
}).filter(Boolean).sort((a, b) => b.startStack - a.startStack || a.seat - b.seat);
|
|
117066
|
-
const survivors = this.
|
|
117259
|
+
const survivors = this.playersWithChips().length;
|
|
117067
117260
|
newlyEliminated.forEach((row, index) => {
|
|
117068
117261
|
const elimination = { playerId: row.player.id, playerName: row.player.name, place: Math.max(2, survivors + 1 + index), handNumber: this.handNumber, at: Date.now() };
|
|
117069
117262
|
this.eliminations.push(elimination);
|
|
@@ -117221,6 +117414,7 @@ function renderModelOptions(models, currentValue = "") {
|
|
|
117221
117414
|
if (currentValue && !byId.has(currentValue)) byId.set(currentValue, { id: currentValue, name: currentValue });
|
|
117222
117415
|
const rows = [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
117223
117416
|
els.seatModelOptions.innerHTML = rows.map((model) => `<option value="${escapeHtml(model.id)}" label="${escapeHtml(model.name || model.id)}"></option>`).join("");
|
|
117417
|
+
updateSeatReasoningEffortOptions();
|
|
117224
117418
|
}
|
|
117225
117419
|
async function refreshSeatModelCatalog({ force = false } = {}) {
|
|
117226
117420
|
const connection = fullConnectionById(els.seatConnection?.value);
|
|
@@ -117274,9 +117468,41 @@ function defaultSeatDraft(seatIndex) {
|
|
|
117274
117468
|
model: conn?.kind === "typesafe" ? "jev-latest" : "",
|
|
117275
117469
|
protocol: conn?.kind === "typesafe" ? "jev_native" : "tool",
|
|
117276
117470
|
provider: "",
|
|
117277
|
-
captureReasoning: false
|
|
117471
|
+
captureReasoning: false,
|
|
117472
|
+
reasoningEffort: ""
|
|
117278
117473
|
};
|
|
117279
117474
|
}
|
|
117475
|
+
function updateSeatReasoningEffortOptions(selected) {
|
|
117476
|
+
const select = els.seatReasoningEffort;
|
|
117477
|
+
if (!select) return;
|
|
117478
|
+
const openRouter = isOpenRouterConnection(connectionById(els.seatConnection.value));
|
|
117479
|
+
const model = els.seatModel.value.trim();
|
|
117480
|
+
const efforts = openRouter ? reasoningEffortsFor(model) : [];
|
|
117481
|
+
const modelDefault = efforts.length ? defaultReasoningEffort(model) : null;
|
|
117482
|
+
const current = String(selected ?? seatReasoningEffortDraft ?? "").trim().toLowerCase();
|
|
117483
|
+
select.innerHTML = [
|
|
117484
|
+
'<option value="">Arena default (capped)</option>',
|
|
117485
|
+
...efforts.map((effort) => `<option value="${escapeHtml(effort)}">${escapeHtml(effort)}${effort === modelDefault ? " \xB7 model default" : ""}</option>`)
|
|
117486
|
+
].join("");
|
|
117487
|
+
if (efforts.includes(current)) {
|
|
117488
|
+
select.value = current;
|
|
117489
|
+
seatReasoningEffortDraft = current;
|
|
117490
|
+
} else {
|
|
117491
|
+
select.value = "";
|
|
117492
|
+
if (!current) seatReasoningEffortDraft = "";
|
|
117493
|
+
}
|
|
117494
|
+
select.disabled = !efforts.length;
|
|
117495
|
+
const help = els.seatReasoningEffortHelp;
|
|
117496
|
+
if (!help) return;
|
|
117497
|
+
help.dataset.tone = "";
|
|
117498
|
+
if (!openRouter) help.textContent = "Reasoning effort is available for OpenRouter models that advertise it.";
|
|
117499
|
+
else if (!efforts.length) help.textContent = "This model does not advertise reasoning efforts; the capped default is used.";
|
|
117500
|
+
else if (!select.value && requiresReasoning(model)) {
|
|
117501
|
+
help.textContent = `This model always reasons and ignores the capped default, so it can exceed the action clock and auto-fold. Pick an effort \u2014 ${efforts.includes("minimal") ? "minimal" : efforts[0]} is usually fastest.`;
|
|
117502
|
+
help.dataset.tone = "bad";
|
|
117503
|
+
} else if (modelDefault) help.textContent = `Model default is ${modelDefault}. Lower it when a slow reasoner runs out of action clock and auto-folds.`;
|
|
117504
|
+
else help.textContent = "Lower it when a slow reasoner runs out of action clock and auto-folds.";
|
|
117505
|
+
}
|
|
117280
117506
|
function applySeatProtocolRules() {
|
|
117281
117507
|
const conn = connectionById(els.seatConnection.value);
|
|
117282
117508
|
const kind = conn?.kind || "openrouter";
|
|
@@ -117285,6 +117511,7 @@ function applySeatProtocolRules() {
|
|
|
117285
117511
|
const jevSeat = kind === "typesafe" || jevViaOpenRouter;
|
|
117286
117512
|
document.querySelector(".seat-provider-field")?.classList.toggle("hidden", !openRouter || jevViaOpenRouter);
|
|
117287
117513
|
document.querySelector(".seat-reasoning-field")?.classList.toggle("hidden", jevSeat);
|
|
117514
|
+
document.querySelector(".seat-effort-field")?.classList.toggle("hidden", jevSeat);
|
|
117288
117515
|
if (els.seatCaptureReasoning) {
|
|
117289
117516
|
els.seatCaptureReasoning.disabled = jevSeat;
|
|
117290
117517
|
const alreadyReasoning = isReasoningModel(els.seatModel.value.trim());
|
|
@@ -117305,6 +117532,7 @@ function applySeatProtocolRules() {
|
|
|
117305
117532
|
els.seatProvider.disabled = !openRouter;
|
|
117306
117533
|
if (els.seatModel.value === "jev-latest") els.seatModel.value = "";
|
|
117307
117534
|
}
|
|
117535
|
+
updateSeatReasoningEffortOptions();
|
|
117308
117536
|
}
|
|
117309
117537
|
function updateSeatSummary() {
|
|
117310
117538
|
const count = seatAssignments.filter(Boolean).length;
|
|
@@ -117331,13 +117559,16 @@ function openSeatEditor(seatIndex) {
|
|
|
117331
117559
|
els.seatProtocol.value = draft.protocol || "tool";
|
|
117332
117560
|
els.seatProvider.value = draft.provider || "";
|
|
117333
117561
|
if (els.seatCaptureReasoning) els.seatCaptureReasoning.checked = Boolean(draft.captureReasoning);
|
|
117562
|
+
seatReasoningEffortDraft = String(draft.reasoningEffort || "").trim().toLowerCase();
|
|
117334
117563
|
applySeatProtocolRules();
|
|
117564
|
+
updateSeatReasoningEffortOptions(seatReasoningEffortDraft);
|
|
117335
117565
|
syncSeatNameFromModel();
|
|
117336
117566
|
void refreshSeatModelCatalog();
|
|
117337
117567
|
els.seatError.classList.add("hidden");
|
|
117338
117568
|
els.seatLockNotice.classList.toggle("hidden", !locked);
|
|
117339
117569
|
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
117570
|
if (els.seatCaptureReasoning) els.seatCaptureReasoning.disabled = locked || els.seatCaptureReasoning.disabled;
|
|
117571
|
+
if (els.seatReasoningEffort) els.seatReasoningEffort.disabled = locked || els.seatReasoningEffort.disabled;
|
|
117341
117572
|
els.saveSeatBtn.disabled = locked;
|
|
117342
117573
|
els.removeSeatBtn.disabled = locked || !seatAssignments[seatIndex];
|
|
117343
117574
|
if (!els.seatDialog.open) els.seatDialog.showModal();
|
|
@@ -117355,7 +117586,8 @@ function readSeatDraft() {
|
|
|
117355
117586
|
model: els.seatModel.value.trim(),
|
|
117356
117587
|
protocol: els.seatProtocol.value,
|
|
117357
117588
|
provider: els.seatProvider.disabled ? "" : els.seatProvider.value.trim(),
|
|
117358
|
-
captureReasoning: Boolean(els.seatCaptureReasoning && !els.seatCaptureReasoning.disabled && els.seatCaptureReasoning.checked)
|
|
117589
|
+
captureReasoning: Boolean(els.seatCaptureReasoning && !els.seatCaptureReasoning.disabled && els.seatCaptureReasoning.checked),
|
|
117590
|
+
reasoningEffort: els.seatReasoningEffort && !els.seatReasoningEffort.disabled ? els.seatReasoningEffort.value.trim().toLowerCase() : ""
|
|
117359
117591
|
};
|
|
117360
117592
|
}
|
|
117361
117593
|
function readSeatPlayers() {
|
|
@@ -117384,7 +117616,8 @@ function restoreSeatAssignments(rows = []) {
|
|
|
117384
117616
|
model: player.model || "",
|
|
117385
117617
|
protocol: player.protocol || "tool",
|
|
117386
117618
|
provider: player.provider || "",
|
|
117387
|
-
captureReasoning: player.captureReasoning === true
|
|
117619
|
+
captureReasoning: player.captureReasoning === true,
|
|
117620
|
+
reasoningEffort: String(player.reasoningEffort || "").trim().toLowerCase()
|
|
117388
117621
|
};
|
|
117389
117622
|
});
|
|
117390
117623
|
updateSeatSummary();
|
|
@@ -118110,13 +118343,13 @@ function renderStats(s) {
|
|
|
118110
118343
|
const pc = (value) => `${Math.round(Number(value || 0) * 100)}%`;
|
|
118111
118344
|
return `<div class="stat-card"><div class="stat-top"><div><div class="stat-name">${escapeHtml(visiblePlayerName(p.name, p.model))}</div><div class="stat-model mono">${escapeHtml(p.model)} \xB7 ${escapeHtml(effectiveProtocol(p, s.config.connections.find((c) => c.id === p.connectionId)))}</div></div><strong>${fmt(tableP?.stack || 0)}</strong></div>
|
|
118112
118345
|
<div class="poker-profile"><div title="Voluntarily put chips in pot"><b>${pc(poker.vpipPct)}</b><span>VPIP</span></div><div title="Preflop raise"><b>${pc(poker.pfrPct)}</b><span>PFR</span></div><div title="Aggression frequency"><b>${pc(poker.aggressionPct)}</b><span>AFq</span></div><div title="Fold when folding was legal"><b>${pc(poker.foldPct)}</b><span>FOLD</span></div><div><b>${poker.sampleHands || 0}</b><span>HANDS</span></div></div>
|
|
118113
|
-
<div class="stat-values"><div class="metric"><b>${st.decisions || 0}</b><span>moves</span></div><div class="metric"><b>${avg}ms</b><span>avg</span></div><div class="metric"><b>${st.autoFallbacks || 0}</b><span>auto</span></div></div><div class="stat-reliability"><span><b>${st.modelErrors || 0}</b> model</span><span><b>${st.providerErrors || 0}</b> provider</span><span><b>${st.rateLimits || 0}</b> rate</span><span><b>${st.timeouts || 0}</b> timeout</span><span><b>${st.protocolFallbacks || 0}</b> protocol</span><span><b>${st.retries || 0}</b> retry</span></div></div>`;
|
|
118346
|
+
<div class="stat-values"><div class="metric"><b>${st.decisions || 0}</b><span>moves</span></div><div class="metric"><b>${avg}ms</b><span>avg</span></div><div class="metric"><b>${st.autoFallbacks || 0}</b><span>auto</span></div></div><div class="stat-reliability"><span><b>${st.modelErrors || 0}</b> model</span><span><b>${st.providerErrors || 0}</b> provider</span><span><b>${st.rateLimits || 0}</b> rate</span><span><b>${st.timeouts || 0}</b> timeout</span><span><b>${st.contextErrors || 0}</b> context</span><span><b>${st.protocolFallbacks || 0}</b> protocol</span><span><b>${st.retries || 0}</b> retry</span></div></div>`;
|
|
118114
118347
|
}).join("");
|
|
118115
118348
|
}
|
|
118116
118349
|
function tableRenderSignature(s) {
|
|
118117
118350
|
const running = ["RUNNING", "PAUSED"].includes(s?.status);
|
|
118118
118351
|
if (lobbyVisible && !running) {
|
|
118119
|
-
return JSON.stringify(["lobby", seatAssignments.map((p) => p ? [p.name, p.model, p.protocol, p.connectionId, p.provider, Boolean(p.captureReasoning)] : null)]);
|
|
118352
|
+
return JSON.stringify(["lobby", seatAssignments.map((p) => p ? [p.name, p.model, p.protocol, p.connectionId, p.provider, Boolean(p.captureReasoning), p.reasoningEffort || ""] : null)]);
|
|
118120
118353
|
}
|
|
118121
118354
|
const t = s?.table;
|
|
118122
118355
|
if (!t) return JSON.stringify(["empty", s?.status || "IDLE"]);
|
|
@@ -119500,6 +119733,9 @@ els.seatName.addEventListener("input", () => {
|
|
|
119500
119733
|
});
|
|
119501
119734
|
els.seatProtocol.addEventListener("change", applySeatProtocolRules);
|
|
119502
119735
|
els.seatCaptureReasoning?.addEventListener("change", applySeatProtocolRules);
|
|
119736
|
+
els.seatReasoningEffort?.addEventListener("change", () => {
|
|
119737
|
+
seatReasoningEffortDraft = els.seatReasoningEffort.value;
|
|
119738
|
+
});
|
|
119503
119739
|
els.refreshModelsBtn?.addEventListener("click", () => void refreshSeatModelCatalog({ force: true }));
|
|
119504
119740
|
els.removeSeatBtn.addEventListener("click", () => {
|
|
119505
119741
|
if (editingSeatIndex == null || director && ["RUNNING", "PAUSED"].includes(director.status)) return;
|
package/dist/index.html
CHANGED
|
@@ -304,6 +304,11 @@
|
|
|
304
304
|
<span class="checkbox-field"><input id="seatCaptureReasoning" type="checkbox" /> Capture raw model reasoning</span>
|
|
305
305
|
<span class="field-help seat-reasoning-help hidden">This model is not a reasoning model by default. Capturing its reasoning can be far slower (Gemma measured ~2s → 27–48s per decision) and is outside the standard benchmark track. Raise the action clock before using it.</span>
|
|
306
306
|
</label>
|
|
307
|
+
<label class="seat-effort-field">
|
|
308
|
+
<span class="field-label-row">Reasoning effort <button class="info-button inline-info" type="button" aria-label="About reasoning effort" data-tip="How much hidden thinking the model may spend. Higher effort is smarter but slower; a slow reasoner can exhaust the action clock and be forced to fold. Options come from the OpenRouter model catalogue, and an unset value keeps the arena's fixed reasoning cap.">i</button></span>
|
|
309
|
+
<select id="seatReasoningEffort"></select>
|
|
310
|
+
<span id="seatReasoningEffortHelp" class="field-help"></span>
|
|
311
|
+
</label>
|
|
307
312
|
</div>
|
|
308
313
|
</div>
|
|
309
314
|
<div id="seatError" class="setup-error hidden"></div>
|