feat: nation-op rename review — confirm/reject routes, client dialog, AGENTS.md (P4a, #16)

Wire the 'LLM proposes, player confirms' nation-rename flow end to end:
- server: POST /api/games/:id/nation-op/confirm|reject (404/400, version-bumped SSE state)
- client: confirmNationOp/rejectNationOp api helpers + NationOpDialog (editable rename) wired into the Action panel via a 'Nation proposals' trigger
- AGENTS.md: document the pendingNationOps mechanism, routes, nationRef resolution, stub-never-proposes
This commit is contained in:
2026-10-14 13:00:28 +00:00
parent e9fa267fd0
commit 9f230606c4
7 changed files with 335 additions and 12 deletions
-1
View File
@@ -4,7 +4,6 @@ import { makeEvent, type NationOp, type TurnResult } from '../shared/protocol';
import type { LlmClient, ToolDefinition, WireMessage } from './llm';
import { makeStubLlmClient } from './stub';
import {
applyNationOp,
applyTurnResult,
buildSystemPrompt,
buildUserPrompt,
+31 -1
View File
@@ -12,13 +12,15 @@ import { deepClone } from '../shared/protocol';
import type {
AddActionRequest,
AddDiplomacyRequest,
ConfirmNationOpRequest,
CreateGameRequest,
RejectNationOpRequest,
RemoveQueueItemRequest,
} from '../shared/protocol';
import type { GameState } from '../shared/types';
import { createLlmClient } from './llm';
import { makeStubLlmClient } from './stub';
import { askAdvisor, queueRoundItem, removeRoundItem, runTurn } from './engine';
import { askAdvisor, confirmNationOp, queueRoundItem, rejectNationOp, removeRoundItem, runTurn } from './engine';
import { buildRegionMap } from './map';
import { allTerritoryKeys, expandCountriesToKeys, hasAdjacency, loadRegions, neighboursOf, referenceForKeys, validKeys } from './regions';
import { createPresetStore } from './presets';
@@ -228,6 +230,34 @@ app.post('/api/games/:id/queue/remove', (req, res) => {
res.json({ game: next });
});
// (v0.23, #16 P4a) Resolve a pending nation-op proposal (a rename the LLM
// proposed in an earlier turn, waiting in game.pendingNationOps). No LLM call,
// no turn advance — same fast save → broadcast → respond order as
// /queue/remove. The version bump lets "never go backwards" clients reconcile.
app.post('/api/games/:id/nation-op/confirm', (req, res) => {
const id = req.params.id;
const game = db.load(id);
if (!game) return res.status(404).json({ error: 'game not found' });
const body = (req.body ?? {}) as ConfirmNationOpRequest;
const next = confirmNationOp(game, body.index, body.edits);
if (!next) return res.status(400).json({ error: 'invalid proposal index' });
db.save(next);
broadcast(id, 'state', { state: next });
res.json({ game: next });
});
app.post('/api/games/:id/nation-op/reject', (req, res) => {
const id = req.params.id;
const game = db.load(id);
if (!game) return res.status(404).json({ error: 'game not found' });
const body = (req.body ?? {}) as RejectNationOpRequest;
const next = rejectNationOp(game, body.index);
if (!next) return res.status(400).json({ error: 'invalid proposal index' });
db.save(next);
broadcast(id, 'state', { state: next });
res.json({ game: next });
});
// Jump forward: commit the whole pending round (or an empty one) to the model
// in a single turn. applyTurnResult clears the pending queue on the returned
// state. 404 for not-found/preset-missing; 502 for LLM/model (transport) errors.