feat: session modes (Talk/Cash) + live session HUD
Lyra now switches register based on what she's doing at the table instead of
being a wishy-washy companion mid-session.
Modes (lyra/modes.py):
- Talk (default companion) + Cash (live cash copilot); a mode = prompt card +
tool allow-list. Tool gating via tools.specs(allow=).
- Two-register Cash voice: act-first one-line logging when fed facts; full warm
companion voice for strategy / tilt / mental game.
- mode persisted per chat session (new sessions.mode column); auto-switch into
Cash when start_session fires; UI forces cloud backend in Cash (tools only
fire there).
Stack tracking + HUD:
- log_stack tool + poker_stack_log table; live net while sitting (stack - buy-in).
- poker.hud() bundle; /session HUD page (stack sparkline, hands, villains, notes,
stats) polling /session/data every 5s; Talk/Cash switcher + Session nav.
Endpoints: /session, /session/data, GET/POST /sessions/{id}/mode, /modes.
tests/test_modes.py (gating, mode roundtrip, stack/HUD); 36 tests green. v0.3.0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+30
-3
@@ -104,6 +104,19 @@ def _add_buyin(args: dict, ctx: dict) -> str:
|
||||
return f"Added {args.get('amount')}. Total in this session: {total:g}."
|
||||
|
||||
|
||||
def _log_stack(args: dict, ctx: dict) -> str:
|
||||
try:
|
||||
amount = float(args.get("amount"))
|
||||
except (TypeError, ValueError):
|
||||
return "Give me a number for the stack."
|
||||
try:
|
||||
st = poker.log_stack(amount)
|
||||
except ValueError:
|
||||
return "No live session — start one first, then I'll track your stack."
|
||||
net = st.get("net")
|
||||
return f"Stack ${amount:g} logged" + (f" (net {net:+.0f})." if net is not None else ".")
|
||||
|
||||
|
||||
def _log_hand(args: dict, ctx: dict) -> str:
|
||||
fields = {k: args.get(k) for k in poker._HAND_FIELDS if args.get(k) not in (None, "")}
|
||||
hid = poker.log_hand(**fields)
|
||||
@@ -268,6 +281,13 @@ TOOLS.update({
|
||||
"add_buyin": {"handler": _add_buyin, "spec": _f(
|
||||
"add_buyin", "Record a rebuy / additional buy-in in the live session.",
|
||||
{"amount": {**_N, "description": "Amount added"}}, ["amount"])},
|
||||
"log_stack": {"handler": _log_stack, "spec": _f(
|
||||
"log_stack",
|
||||
"Record Brian's CURRENT total chip stack in the live session. Call whenever "
|
||||
"he states his stack ('I'm at 350', 'down to 220', 'stacked off to 900'). "
|
||||
"Tracks his stack over time and his live net while he's still sitting.",
|
||||
{"amount": {**_N, "description": "Current total chip stack, in dollars"}},
|
||||
["amount"])},
|
||||
"log_hand": {"handler": _log_hand, "spec": _f(
|
||||
"log_hand",
|
||||
"Log a hand in the live session. All fields optional — capture whatever Brian gives you, even terse.",
|
||||
@@ -353,9 +373,16 @@ TOOLS.update({
|
||||
})
|
||||
|
||||
|
||||
def specs() -> list[dict]:
|
||||
"""OpenAI-format tool definitions to offer the model."""
|
||||
return [t["spec"] for t in TOOLS.values()]
|
||||
def specs(allow=None) -> list[dict]:
|
||||
"""OpenAI-format tool definitions to offer the model.
|
||||
|
||||
`allow` (an iterable of tool names, e.g. a mode's allow-list) restricts the
|
||||
set; None means every tool. Unknown names in `allow` are ignored.
|
||||
"""
|
||||
if allow is None:
|
||||
return [t["spec"] for t in TOOLS.values()]
|
||||
allow = set(allow)
|
||||
return [t["spec"] for name, t in TOOLS.items() if name in allow]
|
||||
|
||||
|
||||
def dispatch(name: str, arguments, ctx: dict | None = None) -> str:
|
||||
|
||||
Reference in New Issue
Block a user