#1265: Bound WorkQueue to MAX_PENDING=1024 (blocking enqueue) and wait on
futures in GameSession reader so a stream cannot flood the main loop.
#1266: Cap concurrent subscribers per session (MAX_SUBSCRIBERS=8); reject
GameSession/Subscribe/SubscribeGmcp/WS with RESOURCE_EXHAUSTED / close.
#1267: grpc-web SendInput routes through TelnetBridge::convertInput like
native gRPC and WS GameSession.
#1268: Reject gRPC/WS/grpc-web input lines above MAX_INPUT_LINE_LENGTH
(8192, shared with front-door telnet assembly); drop oversized GMCP.
#1269: GetGameStatus returns host PIDs only for admin accounts; any
authenticated session still sees running/up.
#1270: proxy_regression covers subscriber cap, line-limit constants,
work-queue cap constant, and convertInput non-UTF8 target.
Move issueTypeName() and sanitizeProtoTextForLog() from duplicated
static functions in session_manager.cpp and grpc_server.cpp into
utf8_utils.h. Remove unused parameter from truncated lambda.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
H-8: LBUF_SIZE discrepancy (8000 vs 32768)
- Override color_ops.h fallback to match engine's LBUF_SIZE=32768
- TelnetBridge: all three methods (ingestGameOutput, renderForClient,
charsetEncodeFromUtf8) now use heap-allocated buffers scaled to input
size, with LBUF_SIZE as minimum capacity
M-10: IP-based rate limit bypass via pruning
- Pruning now removes expired accountCreateTimes entries first, then
only erases the IP tracker if accountCreateTimes is also empty.
Previously, pruning cleared the entire entry, resetting the 1-hour
account creation window.
L-6: gRPC insecure listener restricted to loopback
- Without TLS cert/key, GrpcServer::start() now rejects non-loopback
bind addresses (must be 127.0.0.1, [::1], or localhost).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixes from code review of the previous two commits:
1. High: Token rotation no longer destroys scrollback/link state.
Added dbPersistId to track the SQLite row's primary key separately
from the in-memory API token. Rotation only changes the in-memory
persistId; the database row (and its CASCADE-linked scrollback) is
untouched. flushSession and deleteSession use dbPersistId for all
SQLite operations.
2. Medium: Account creation rate limit now covers gRPC native
(extracts IP from ServerContext::peer()) and grpc-web (uses
FrontDoorState::clientIp). createAccountAndGetSession takes a
clientIp parameter; checkAccountCreateRate/recordAccountCreate
moved to public interface.
3. Medium: Partial gRPC TLS config (one of cert/key set, other
missing) now fails at config parse time. If TLS is configured
and startup fails, Hydra exits rather than silently falling back
to insecure credentials.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hydra now caches the last GMCP payload per package (e.g. Char.Vitals,
Room.Info) in HydraSession::gmcpCache. When a new front-door or gRPC
subscriber attaches to an existing session, the cached GMCP state is
replayed so the client's vitals bar, room info, etc. aren't blank
after reconnect.
- session_manager.h: gmcpCache field (map<string, string>) on HydraSession,
replayGmcpCache() method declaration
- session_manager.cpp: cache updated on every incoming GMCP from game,
replayed to telnet front-doors in showGameMenu(), replayed to gRPC
subscribers via replayGmcpCache()
- grpc_server.cpp: GameSession handler calls replayGmcpCache() after
subscriber registration via work queue
Core.KeepAlive synthesis was already implemented by the Ubuntu agent.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three remaining review findings:
1. ColorFormat enum: added COLOR_UNSPECIFIED = 0 as proto3 default.
Existing values shifted to 1-5. SetPreferences handler skips
color update when COLOR_UNSPECIFIED (resize-only updates no longer
reset color to TrueColor). Subscribe/GetScrollBack treat 0 as
"server default (TrueColor)".
2. GetScrollBack now renders PUA text at the requested color_format
using OutputItem::render() during replay.
3. terminal_type from SetPreferences is stored in HydraSession for
future TTYPE forwarding to back-door games.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. GameSession concurrent Write race: pong replies are now queued
through the subscriber's output queue instead of written directly
from the reader thread. renderFormat updates are protected by
the output-queue mutex.
2. IAC escaping in telnet_utils.h: buildGmcpFrame() and buildNawsFrame()
now escape 0xFF payload bytes as IAC IAC per RFC 854.
3. O(N) scrollback memory check replaced with atomic global counter.
SessionManager::globalScrollbackBytes_ is updated incrementally on
each append (O(1)) instead of scanning all sessions.
4. strerror() replaced with strerror_r() in safeWrite for thread safety.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SessionRequest now carries terminal_width and terminal_height fields
(proto fields 3-4). When a Subscribe RPC provides non-zero values,
the server forwards NAWS to the active game back-door link so the
game renders at the correct width.
Implemented in both native gRPC (grpc_server.cpp) and grpc-web
(session_manager.cpp handleGrpcWebRequest) Subscribe handlers.
HTML5 client sends approximate terminal dimensions based on window
size in _startSubscribe().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New shared header proxy/telnet_utils.h provides:
- telnet:: namespace with IAC, SB, SE, WILL, DO, GMCP, NAWS constants
- buildGmcpFrame(payload) — IAC SB GMCP payload IAC SE
- buildNawsFrame(width, height) — IAC SB NAWS w h IAC SE
Eliminates duplicate manual frame construction in grpc_server.cpp
(GMCP 6 lines → 1, NAWS 10 lines → 1) and replaces the static
helper in session_manager.cpp. Local T_* constants now alias the
shared telnet:: namespace.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Eliminate 19 raw send() calls across session_manager.cpp and
grpc_server.cpp. All writes now go through engine_.postWrite()
via a safeWrite() helper method, which handles:
- Non-blocking I/O (GANL buffers if socket not ready)
- Partial write completion
- Thread-safe write ordering through GANL's write queue
ReplayContext extended with engine pointer so scroll-back replay
callbacks can also use the safe write path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Proto changes:
- Add SetPreferences message to ClientMessage oneof with color_format,
terminal_width, terminal_height, terminal_type fields
- Add color_format to SessionRequest for legacy Subscribe RPC
- Remove dead GameSessionRequest message (replaced by SetPreferences)
Server changes:
- GameSession reader handles SetPreferences: updates subscriber's
RenderFormat and forwards NAWS to the game back-door link
- Subscribe RPC passes color_format from SessionRequest to subscriber
Client changes (TitanFugue):
- Sends SetPreferences as first message on GameSession stream
- Overrides send_naws() to forward terminal resize as SetPreferences
update on the bidi stream
This resolves the "ColorFormat is dead API surface" issue — clients
can now negotiate color depth and report terminal size over gRPC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OutputQueue now stores PUA-encoded UTF-8 instead of pre-rendered
TrueColor ANSI. Each SubscriberQueue has a RenderFormat preference
(TrueColor/256/16/PUA/Plain) and OutputItem::render() converts at
read time via the appropriate co_render_* function.
This enables different gRPC clients on the same Hydra session to
receive different color formats — e.g., a terminal client getting
TrueColor while a bot gets PLAIN text. Previously all subscribers
received the same pre-rendered TrueColor regardless of capability.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the single shared OutputQueue per session with per-subscriber
queues. Each Subscribe, SubscribeGmcp, or GameSession call registers
a SubscriberQueue via addSubscriber(wantsOutput, wantsGmcp). Producers
replicate items to all active subscriber queues via pushOutput() and
pushGmcp(). Each consumer drains only its own queue.
Previously, all consumers popped from the same std::queue, causing
the first consumer to wake to steal items from other subscribers.
Two gRPC clients on the same Hydra session would randomly miss
game output and GMCP messages.
- session_manager.h: SubscriberQueue struct, addSubscriber/
removeSubscriber/pushOutput/pushGmcp/hasSubscribers methods
- session_manager.cpp: producers call pushOutput/pushGmcp under lock
- grpc_server.cpp: GameSession, Subscribe, SubscribeGmcp each register
their own SubscriberQueue and drain from it exclusively
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Convert LinkInfo.state, SessionInfo.state, LinkEvent states,
GameInfo.type, and SystemNotice.severity from string fields to
strongly-typed protobuf enums (LinkState, SessionState, GameType,
Severity). This prevents typos, enables exhaustive matching in
client code, and eliminates string comparison for semantic data.
GMCP json field documented as intentionally string (game-defined).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Six improvements based on review:
1. Bidi GameSession RPC replaces separate SendInput+Subscribe — no
per-line round-trips, maps to persistent connection model
2. SubscribeGmcp RPC streams structured GMCP data with package filters
3. ColorFormat enum (TrueColor/256/16/PUA/Plain) — clients choose
4. Ping RPC + bidi ping/pong with timestamps for RTT
5. Metadata-based auth (authorization header) with per-message fallback
6. CreateAccount RPC for gRPC-only clients
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Complete gRPC service implementation with thread-safe work queue
bridging gRPC threads to the main event loop. 18 strongly-typed RPCs
covering auth, session lifecycle, link management, I/O streaming
(TrueColor ANSI, all links tagged), scroll-back, credential CRUD,
and process management. Subscribe uses shared_ptr<OutputQueue> with
condvar for low-latency server-streaming output.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
gRPC support is an optional configure dependency: ./configure
--enable-grpc detects grpc++ and protobuf via pkg-config and enables
compilation of the gRPC server and protobuf service definition.
Without --enable-grpc (the default), no gRPC code is compiled or
linked. The HydraService provides Authenticate, ListGames, Connect,
SendInput, Subscribe (server-streaming output), and ListLinks RPCs.
This completes Phase 3 (protocol expansion).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>