diff --git a/apps/game-api/package.json b/apps/game-api/package.json index 327b7f2..8b7a283 100644 --- a/apps/game-api/package.json +++ b/apps/game-api/package.json @@ -7,6 +7,7 @@ "build": "esbuild src/server.ts --bundle --platform=node --format=esm --outfile=dist/server.js", "compare:offline": "node --import tsx src/swarm-comparison-cli.ts", "compare:live": "node --import tsx src/live-swarm-comparison-cli.ts", + "diagnose:swarm": "node --import tsx src/swarm-diagnostics-cli.ts", "dev": "tsx watch src/server.ts", "lint": "eslint .", "start": "node dist/server.js", diff --git a/apps/game-api/src/simulation-service.swarm.test.ts b/apps/game-api/src/simulation-service.swarm.test.ts index 44256ea..3be4497 100644 --- a/apps/game-api/src/simulation-service.swarm.test.ts +++ b/apps/game-api/src/simulation-service.swarm.test.ts @@ -408,7 +408,7 @@ describe('zero-swarm SimulationService tick', () => { expect(snapshot.experiment.attemptAccounting.attemptsStarted).toBe(8); }); - it('uses deterministic neutral directives and Zero wait when planning fails', async () => { + it('uses one billed Zero attempt and deterministic legal expansion when the first plan fails', async () => { const simulation = setup( new InspectingPlanner(true), new ScriptedReflexProvider( @@ -420,8 +420,20 @@ describe('zero-swarm SimulationService tick', () => { expect(tick?.planSource).toBe('deterministic-fallback'); expect(tick?.zeroAction).toEqual({ type: 'wait' }); expect( - tick?.workers.every(({ directive }) => directive.mission === 'hold'), + tick?.workers.every( + ({ directive, source }) => + directive.mission === 'expand' && source === 'deterministic-fallback', + ), + ).toBe(true); + expect( + tick?.workers.some( + ({ action, actionResult }) => + action?.type === 'infect' && actionResult?.accepted === true, + ), ).toBe(true); + expect( + simulation.getSnapshot().experiment.attemptAccounting.attemptsStarted, + ).toBe(1); }); it('reuses unexpired directives for four ticks, then replans on the fifth tick', async () => { diff --git a/apps/game-api/src/simulation-service.ts b/apps/game-api/src/simulation-service.ts index 804101c..691706c 100644 --- a/apps/game-api/src/simulation-service.ts +++ b/apps/game-api/src/simulation-service.ts @@ -151,6 +151,21 @@ function chooseDeterministicWorkerAction( }; } +function selectNeutralFallbackCandidate( + compiled: CompiledReflexObservation, + state: WorldState, +): string { + const choices = [...compiled.actions]; + return (choices.find(([, action]) => action.type === 'infect') ?? + choices.find(([, action]) => action.type === 'capture') ?? + choices.find( + ([, action]) => + action.type === 'move' && + state.hexes.get(action.targetCell)?.state === 'open', + ) ?? + choices.find(([, action]) => action.type === 'wait'))![0]; +} + export function selectMostRecentPatientZeroThreats< T extends { eventId: string; occurredAt: string }, >(events: readonly T[]): T[] { @@ -1666,6 +1681,7 @@ export class SimulationService { { signal: controller.signal, deadlineAtMs, + reasoningProfile: resolvedZero.reasoningProfile, beginAttempt: (kind) => { const startedAt = this.#now(); const permit = @@ -1756,26 +1772,39 @@ export class SimulationService { ? ('success' as const) : ('unknown' as const), }; - const choice = this.#deterministicWorkerCandidateSelector - ? chooseDeterministicWorkerAction( - compileReflexObservation(candidate, directive, history), - this.#deterministicWorkerCandidateSelector, - ) - : await chooseReflexWorldAction( - candidate, - directive, - this.#reflexProvider, - { - history, - signal: controller.signal, - deadlineAtMs, - accounting: this.#attemptAccounting, - initialPermitReserved: true, - intendedTickNumber: tickNumber, - intendedTurnNumber: tickTurnBase + order.indexOf(worker.id) + 1, - now: this.#now, - }, - ); + const retainedDirective = this.#lastValidSwarmPlan?.directives.some( + (previous) => + previous.agentId === worker.id && + previous.expiresAtTick >= tickNumber, + ); + const choice = + planSource === 'deterministic-fallback' && !retainedDirective + ? chooseDeterministicWorkerAction( + compileReflexObservation(candidate, directive, history), + (compiled) => + selectNeutralFallbackCandidate(compiled, candidate), + ) + : this.#deterministicWorkerCandidateSelector + ? chooseDeterministicWorkerAction( + compileReflexObservation(candidate, directive, history), + this.#deterministicWorkerCandidateSelector, + ) + : await chooseReflexWorldAction( + candidate, + directive, + this.#reflexProvider, + { + history, + signal: controller.signal, + deadlineAtMs, + accounting: this.#attemptAccounting, + initialPermitReserved: true, + intendedTickNumber: tickNumber, + intendedTurnNumber: + tickTurnBase + order.indexOf(worker.id) + 1, + now: this.#now, + }, + ); selected.set(worker.id, choice); } if (controller.signal.aborted) throw new SimulationTurnCancelledError(); @@ -2932,8 +2961,8 @@ export class SimulationService { ) ?? { id: `neutral-${tickNumber}-${worker.id}`, agentId: worker.id, - mission: 'hold' as const, - targetCell: state.agents.get(worker.id)!.currentCell, + mission: 'expand' as const, + targetCell: null, priority: 'normal' as const, riskTolerance: 'low' as const, issuedAtTick: tickNumber, @@ -2946,8 +2975,9 @@ export class SimulationService { if (waitIndex < 0) throw new Error('The engine must provide a legal wait action.'); return swarmPlanSchema.parse({ - strategySummary: - 'Maintain legal local positions while strategic planning is unavailable.', + strategySummary: this.#lastValidSwarmPlan + ? 'Continue prior valid directives while strategic planning is unavailable.' + : 'Use deterministic local expansion while strategic planning is unavailable.', directives, zeroActionCandidateId: `zero_action_${waitIndex}`, }); diff --git a/apps/game-api/src/swarm-diagnostics-cli.test.ts b/apps/game-api/src/swarm-diagnostics-cli.test.ts new file mode 100644 index 0000000..244dd13 --- /dev/null +++ b/apps/game-api/src/swarm-diagnostics-cli.test.ts @@ -0,0 +1,80 @@ +import { describe, expect, it } from 'vitest'; +import type { SimulationSnapshot } from '@hexzero/shared'; +import { summarizeSwarmSnapshot } from './swarm-diagnostics-cli'; + +describe('swarm diagnostic projection', () => { + it('shows why a stationary swarm has no player pressure and fallback actions', () => { + const snapshot = { + tickNumber: 1, + scenario: { + cognitionMode: 'zero-swarm-v1', + patientZeroAgentId: 'zero', + simulatedPlayer: { enabled: false, profile: 'casual-cleaner' }, + }, + world: { simulatedPlayer: null, hexes: [] }, + resolvedModels: [{ agentId: 'zero', modelId: 'google/gemini-test' }], + swarmProviderStatus: { + plannerMode: 'openrouter-swarm', + plannerConfigured: true, + reflexMode: 'typesafe-jev', + reflexConfigured: true, + }, + experiment: { + attemptAccounting: { + attemptsStarted: 8, + attemptsFinalized: 8, + attemptsWithUnknownCost: 7, + knownFinalizedCostCredits: '0.02', + committedCreditExposure: '0.09', + reservationCreditsPerAttempt: '0.01', + }, + }, + swarmTicks: [ + { + tickNumber: 1, + planSource: 'deterministic-fallback', + plannerFailure: { code: 'malformed-response' }, + zeroAction: { type: 'wait' }, + zeroActionResult: { accepted: true }, + workers: [ + { + agentId: 'worker', + action: { type: 'wait' }, + source: 'deterministic-fallback', + failure: { code: 'timeout', message: 'Jev timed out.' }, + }, + ], + }, + ], + } as unknown as SimulationSnapshot; + expect(summarizeSwarmSnapshot(snapshot)).toMatchObject({ + tick: 1, + infectedCells: 0, + player: { enabled: false, active: false, movements: 0 }, + attempts: { + started: 8, + finalized: 8, + unknownCost: 7, + providerReportedCostCredits: '0.02', + admissionExposureCredits: '0.09', + reservePerAttemptCredits: '0.01', + }, + recentTicks: [ + { + planSource: 'deterministic-fallback', + plannerFailureCode: 'malformed-response', + zeroAction: 'wait', + workerActions: { wait: 1 }, + workerFallbacks: 1, + workerFailureCodes: ['timeout'], + workerFailures: [ + { agentId: 'worker', code: 'timeout', message: 'Jev timed out.' }, + ], + }, + ], + }); + expect(JSON.stringify(summarizeSwarmSnapshot(snapshot))).not.toContain( + 'apiKey', + ); + }); +}); diff --git a/apps/game-api/src/swarm-diagnostics-cli.ts b/apps/game-api/src/swarm-diagnostics-cli.ts new file mode 100644 index 0000000..bc20016 --- /dev/null +++ b/apps/game-api/src/swarm-diagnostics-cli.ts @@ -0,0 +1,95 @@ +#!/usr/bin/env node +import { + simulationSnapshotSchema, + type SimulationSnapshot, + type WorldAction, +} from '@hexzero/shared'; + +/** A small, safe projection of an already committed local experiment. */ +export function summarizeSwarmSnapshot(snapshot: SimulationSnapshot) { + const zeroId = snapshot.scenario.patientZeroAgentId; + const player = snapshot.world.simulatedPlayer; + const attempts = snapshot.experiment.attemptAccounting; + const actionCounts = (actions: readonly (WorldAction | undefined)[]) => ({ + move: actions.filter((action) => action?.type === 'move').length, + infect: actions.filter((action) => action?.type === 'infect').length, + capture: actions.filter((action) => action?.type === 'capture').length, + wait: actions.filter((action) => action?.type === 'wait').length, + }); + return { + tick: snapshot.tickNumber, + mode: snapshot.scenario.cognitionMode, + infectedCells: snapshot.world.hexes.filter( + ({ state }) => state === 'infected', + ).length, + zeroModel: + snapshot.resolvedModels.find(({ agentId }) => agentId === zeroId) + ?.modelId ?? null, + player: { + enabled: snapshot.scenario.simulatedPlayer.enabled, + profile: snapshot.scenario.simulatedPlayer.profile, + active: Boolean(player), + movements: player?.metrics.movements ?? 0, + cleaned: player?.metrics.cellsDisinfected ?? 0, + blockedCleans: player?.metrics.blockedDisinfections ?? 0, + }, + providers: snapshot.swarmProviderStatus ?? null, + attempts: { + started: attempts.attemptsStarted, + finalized: attempts.attemptsFinalized, + unknownCost: attempts.attemptsWithUnknownCost, + providerReportedCostCredits: attempts.knownFinalizedCostCredits, + admissionExposureCredits: attempts.committedCreditExposure, + reservePerAttemptCredits: attempts.reservationCreditsPerAttempt, + }, + recentTicks: (snapshot.swarmTicks ?? []).slice(-10).map((tick) => ({ + tick: tick.tickNumber, + planSource: tick.planSource, + plannerFailureCode: tick.plannerFailure?.code ?? null, + zeroAction: tick.zeroAction?.type ?? null, + zeroAccepted: tick.zeroActionResult?.accepted ?? null, + workerActions: actionCounts(tick.workers.map(({ action }) => action)), + jevDecisions: tick.workers.filter(({ source }) => source === 'jev-reflex') + .length, + workerFallbacks: tick.workers.filter( + ({ source }) => source === 'deterministic-fallback', + ).length, + workerFailureCodes: tick.workers.flatMap(({ failure }) => + failure ? [failure.code] : [], + ), + workerFailures: tick.workers.flatMap(({ agentId, failure }) => + failure + ? [{ agentId, code: failure.code, message: failure.message }] + : [], + ), + })), + }; +} + +async function main(): Promise { + const port = process.env.PORT ?? '8787'; + if (!/^\d{1,5}$/.test(port) || Number(port) > 65_535) + throw new Error('PORT must be a valid local TCP port.'); + const url = `http://127.0.0.1:${port}/api/simulation`; + let response: Response; + try { + response = await fetch(url, { signal: AbortSignal.timeout(3_000) }); + } catch { + throw new Error( + `Game API is unavailable at ${url}. Start pnpm dev and leave it running in another terminal.`, + ); + } + if (!response.ok) throw new Error('Game API did not return a snapshot.'); + const snapshot = simulationSnapshotSchema.parse(await response.json()); + process.stdout.write( + `${JSON.stringify(summarizeSwarmSnapshot(snapshot), null, 2)}\n`, + ); +} + +if (import.meta.url === `file://${process.argv[1]}`) + void main().catch((error: unknown) => { + process.stderr.write( + `${error instanceof Error ? error.message : 'Swarm diagnostic failed.'}\n`, + ); + process.exitCode = 1; + }); diff --git a/apps/world-lab/src/app/styles.css b/apps/world-lab/src/app/styles.css index af9307c..e6a792f 100644 --- a/apps/world-lab/src/app/styles.css +++ b/apps/world-lab/src/app/styles.css @@ -1003,6 +1003,12 @@ h2 { overflow: hidden; border-bottom: 0; } +.bottom-dock.activity-dock > .swarm-activity { + overflow-x: hidden; + overflow-y: auto; + overscroll-behavior: contain; + scrollbar-gutter: stable; +} .bottom-dock .event-panel { display: flex; flex-direction: column; @@ -2152,6 +2158,32 @@ summary:focus-visible { text-overflow: ellipsis; white-space: nowrap; } +.execution-mode-button { + display: grid; + justify-items: start; + min-width: 0; + padding: 5px 8px; + text-align: left; + background: color-mix(in srgb, var(--blackberry-cream) 25%, var(--surface)); +} +.execution-mode-button strong { + color: var(--vanilla-custard); + font-size: 0.72rem; + white-space: nowrap; +} +.execution-mode-button .test-provider-summary { + width: 100%; +} +.swarm-diagnostic { + margin: 8px 0; + padding: 8px 10px; + border: 1px solid var(--line); + background: var(--surface-raised); + font-size: 0.75rem; +} +.swarm-diagnostic p + p { + margin-top: 4px; +} .command-alert { border-color: color-mix(in srgb, var(--danger) 45%, var(--line)); background: color-mix( diff --git a/apps/world-lab/src/components/swarm-view.test.tsx b/apps/world-lab/src/components/swarm-view.test.tsx index fb76d8d..1f87321 100644 --- a/apps/world-lab/src/components/swarm-view.test.tsx +++ b/apps/world-lab/src/components/swarm-view.test.tsx @@ -2,6 +2,7 @@ import { render, screen } from '@testing-library/react'; import { describe, expect, it } from 'vitest'; import type { SimulationSnapshot } from '@hexzero/shared'; import { + SwarmActivityPanel, SwarmAgentInspector, SwarmRunPanel, SwarmStrategyPanel, @@ -86,6 +87,8 @@ function snapshot(withTick = true): SimulationSnapshot { attemptsFinalized: 2, attemptsWithUnknownCost: 1, knownFinalizedCostCredits: '0.02', + committedCreditExposure: '0.03', + reservationCreditsPerAttempt: '0.01', }, }, swarmProviderStatus: { @@ -99,6 +102,29 @@ function snapshot(withTick = true): SimulationSnapshot { } describe('swarm telemetry panels', () => { + it('summarizes inactive player pressure, actions, and provider failures', () => { + const value = snapshot(); + value.swarmTicks![0]!.workers[0]!.failure = { + code: 'invalid-decision', + message: + 'TypeSafe Jev returned an incomplete or inconsistent probability distribution.', + retryable: false, + }; + render(); + expect( + screen.getByRole('tabpanel', { name: 'Swarm activity' }), + ).toHaveAttribute('tabindex', '0'); + const diagnostic = screen.getByRole('status'); + expect(diagnostic).toHaveTextContent('0 moves'); + expect(diagnostic).toHaveTextContent('1 infections'); + expect(diagnostic).toHaveTextContent('1 Zero fallbacks'); + expect(diagnostic).toHaveTextContent('Simulated player pressure is off'); + expect(diagnostic).toHaveTextContent('Zero planner failure: timeout'); + expect(diagnostic).toHaveTextContent( + 'Worker: invalid-decision · TypeSafe Jev returned an incomplete or inconsistent probability distribution.', + ); + }); + it('shows Zero fallback and worker reflex telemetry without social labels', () => { const value = snapshot(); render( @@ -117,12 +143,25 @@ describe('swarm telemetry panels', () => { expect(screen.getByText(/Planner timed out/)).toBeInTheDocument(); expect(screen.getAllByText('action_1')).toHaveLength(2); expect(screen.getAllByText('72%')).toHaveLength(2); - expect(screen.getByText(/TypeSafe cost unknown/)).toBeInTheDocument(); + expect( + screen.getByText(/TypeSafe Jev reports tokens, no monetary cost/), + ).toBeInTheDocument(); expect( screen.queryByText(/alliance|chat|personality|memory/i), ).not.toBeInTheDocument(); }); + it('separates reported provider cost from admission exposure', () => { + const value = snapshot(); + render(); + expect(screen.getByText('Provider-reported cost')).toBeInTheDocument(); + expect(screen.getByText('0.02 credits')).toBeInTheDocument(); + expect( + screen.getByText('Admission exposure (not spend)'), + ).toBeInTheDocument(); + expect(screen.getByText(/0.03 credits · includes/)).toBeInTheDocument(); + }); + it('shows the telemetry empty state before the first committed tick', () => { render(); expect( diff --git a/apps/world-lab/src/components/swarm-view.tsx b/apps/world-lab/src/components/swarm-view.tsx index 2644a9f..55cafe1 100644 --- a/apps/world-lab/src/components/swarm-view.tsx +++ b/apps/world-lab/src/components/swarm-view.tsx @@ -348,13 +348,68 @@ export function SwarmActivityPanel({ }) { const ticks = snapshot.swarmTicks ?? []; const latest = ticks.at(-1); + const workers = ticks.flatMap((tick) => tick.workers); + const actions = [ + ...ticks.map((tick) => tick.zeroAction), + ...workers.map((worker) => worker.action), + ]; + const countAction = (type: WorldAction['type']) => + actions.filter((action) => action?.type === type).length; + const fallbackPlans = ticks.filter( + (tick) => tick.planSource === 'deterministic-fallback', + ).length; + const fallbackWorkers = workers.filter( + (worker) => worker.source === 'deterministic-fallback', + ).length; + const player = snapshot.world.simulatedPlayer; + const infectedCells = snapshot.world.hexes?.filter( + ({ state }) => state === 'infected', + ).length; + const attempts = snapshot.experiment.attemptAccounting; return (

Swarm activity

Agent Zero strategy

+
+

+ {ticks.length} committed ticks ·{' '} + {countAction('move')} moves · {countAction('infect')} infections ·{' '} + {countAction('wait')} waits · {fallbackPlans} Zero fallbacks ·{' '} + {fallbackWorkers} worker fallbacks · {attempts.attemptsStarted}{' '} + provider attempts +

+

+ {player + ? `${player.profile} pressure: ${player.metrics.movements} moves, ${player.metrics.cellsDisinfected} cleans, ${player.metrics.blockedDisinfections} blocked cleans${infectedCells === 0 ? ' · no infected cells to pursue' : ''}` + : 'Simulated player pressure is off. Enable it in World Setup to test cleaner or hunter behavior.'} +

+ {latest?.plannerFailure && ( +

+ Zero planner failure: {latest.plannerFailure.code} ·{' '} + {latest.plannerFailure.message} +

+ )} + {latest?.workers.some((worker) => worker.failure) && ( +

+ Worker failures:{' '} + {latest.workers + .flatMap((worker) => + worker.failure + ? [ + `${agentName(snapshot, worker.agentId)}: ${worker.failure.code} · ${worker.failure.message}`, + ] + : [], + ) + .join(', ')} +

+ )} +
{!latest ? ( ) : ( @@ -481,12 +536,23 @@ export function SwarmRunPanel({
-
Known finalized cost
+
Provider-reported cost
{accounting.knownFinalizedCostCredits} credits
-
Unknown provider costs
-
{accounting.attemptsWithUnknownCost} · TypeSafe cost unknown
+
Unknown-cost attempts
+
+ {accounting.attemptsWithUnknownCost} · TypeSafe Jev reports tokens, + no monetary cost +
+
+
+
Admission exposure (not spend)
+
+ {accounting.committedCreditExposure} credits · includes{' '} + {accounting.reservationCreditsPerAttempt} credit reserve per + unknown-cost attempt +
Zero retained reported usage
diff --git a/apps/world-lab/src/components/world-lab.test.tsx b/apps/world-lab/src/components/world-lab.test.tsx index 36026a0..9210624 100644 --- a/apps/world-lab/src/components/world-lab.test.tsx +++ b/apps/world-lab/src/components/world-lab.test.tsx @@ -1353,6 +1353,68 @@ describe('WorldLab', () => { expect(overflowTrigger.closest('details')).not.toHaveAttribute('open'); }); + it('lets an operator create a zero-swarm experiment from World Setup', async () => { + const swarm = simulationSnapshotSchema.parse({ + ...initial, + scenario: { ...initial.scenario, cognitionMode: 'zero-swarm-v1' }, + swarmProviderStatus: { + plannerMode: 'scripted-swarm-test', + plannerConfigured: false, + reflexMode: 'scripted-reflex-test', + reflexConfigured: false, + }, + }); + let previewMode: string | undefined; + let appliedMode: string | undefined; + vi.stubGlobal( + 'fetch', + vi.fn((input: RequestInfo | URL, init?: RequestInit) => { + const url = String(input); + if (url.endsWith('/setup/preview')) { + const request = JSON.parse(String(init?.body)) as ReturnType< + typeof defaultWorldSetupRequest + >; + previewMode = request.cognitionMode; + return jsonResponse(previewWorldSetup(request)); + } + if (url.endsWith('/experiment/setup')) { + appliedMode = ( + JSON.parse(String(init?.body)) as ReturnType< + typeof defaultWorldSetupRequest + > + ).cognitionMode; + return jsonResponse({ snapshot: swarm }); + } + return jsonResponse(initial); + }), + ); + const user = userEvent.setup(); + render(); + expect( + await screen.findByText('Legacy multi-agent experiment'), + ).toBeVisible(); + await user.click( + screen.getByRole('button', { + name: /Current execution mode: Legacy multi-agent/, + }), + ); + await user.selectOptions( + screen.getByLabelText('Cognition mode'), + 'zero-swarm-v1', + ); + await user.click(screen.getByRole('button', { name: 'Preview' })); + expect(previewMode).toBe('zero-swarm-v1'); + await user.click( + screen.getByRole('button', { name: 'Apply / Create Experiment' }), + ); + expect(appliedMode).toBe('zero-swarm-v1'); + expect(await screen.findByText('Zero swarm v1 experiment')).toBeVisible(); + expect(screen.getByRole('tab', { name: 'Swarm' })).toBeInTheDocument(); + expect( + screen.queryByRole('tab', { name: 'Public chat' }), + ).not.toBeInTheDocument(); + }); + it.each(['balanced-random', 'fully-random'] as const)( 'previews and applies reproducible seed edits with regenerated %s assignments', async (behaviorMode) => { diff --git a/apps/world-lab/src/components/world-lab.tsx b/apps/world-lab/src/components/world-lab.tsx index 95b1fb8..b309f7d 100644 --- a/apps/world-lab/src/components/world-lab.tsx +++ b/apps/world-lab/src/components/world-lab.tsx @@ -164,7 +164,9 @@ export function WorldLab() { const configurationPendingRef = useRef(false); const exportInitializedRef = useRef(false); const exportTriggerRef = useRef(null); + const modeTriggerRef = useRef(null); const setupTriggerRef = useRef(null); + const [setupOpenedFromMode, setSetupOpenedFromMode] = useState(false); const overflowMenuRef = useRef(null); useEffect(() => { @@ -843,7 +845,9 @@ export function WorldLab() { WL
-

Developer simulation

+

+ {swarmMode ? 'Zero swarm v1' : 'Legacy multi-agent'} experiment +

World Lab

-
Committed exposure
+
Admission exposure (not spend)
{formatCost( snapshot.experiment.attemptAccounting @@ -963,7 +967,7 @@ export function WorldLab() {
-
Known finalized cost
+
Provider-reported cost
{formatCost( snapshot.experiment.attemptAccounting @@ -1012,16 +1016,39 @@ export function WorldLab() {
)} + {swarmMode && ( +

+ Jev reports tokens but no monetary cost. Its unknown-cost + attempts retain the configured admission reserve in exposure; + that reserve is not a provider bill. The reported cost above + includes OpenRouter amounts only when returned by the provider. +

+ )} {!swarmMode && } -

- {swarmMode - ? `Zero: ${zeroModel?.modelId ?? 'model required'} · Jev: ${snapshot.swarmProviderStatus?.reflexModel ?? 'deterministic reflex'}` - : snapshot.providerMode === 'openrouter' - ? `${new Set(snapshot.resolvedModels.map(({ modelId }) => modelId ?? 'unassigned')).size} active model assignment${snapshot.resolvedModels.length === 1 ? '' : 's'}` - : 'Deterministic test model'} -

+