Issue #6 (TUI startup error visibility): restructure run_tui lifecycle so pre-App.run() failures land on real stderr instead of getting eaten by the alt-screen teardown. New _resolve_then_run async helper opens the AsyncClient via async-with, does pre-flight session resolution, routes AgentNotFound / SessionApiFailed / network errors to sys.stderr (verbatim same labels + exit codes as cli._amain), then constructs RatatoskrApp with pre-resolved state and awaits app.run_async(). RatatoskrApp.__init__ signature widens to (args, *, session_id, agent_id, client) — all three required. on_mount narrows to identity-widget population; on_unmount becomes a no-op (client lifetime owned by run_tui's async-with). Issue #5 (--end-user-id for per-end-user agents): sessions.create_session gains keyword-only end_user_id kwarg with PRE-003 non-empty assertion; ParsedArgs.end_user_id field added (default None); --end-user-id flag with non-empty validation; _amain + _resolve_then_run thread it to their create_session calls. RATATOSKR_END_USER_ID env-var fallback (flag > env > None) per the post-2026-05-23 amendment; env.sh (gitignored) ships "ratatoskr-tui" as project-stable partition default. Worldtree-dev consumer-API follow-up (althing 01KSBARG2B8M): User-Agent header added (ratatoskr/<version> (vh@phasefinal.com), version pulled via importlib.metadata) to both AsyncClient constructions so server logs can distinguish ratatoskr traffic from other consumers. Volva code-review (2 rounds on #6) found 8 test-precision gaps + 1 PRE assertion drift, all Category 1 fixed: missing PRE-001 at _resolve_then_run entry; Rule separator assertions on markdown render; RichLog-write spy on empty submit; input-cleared + no-new-worker on cancelling busy; worker.cancel observation on three force-exit paths; on_unmount-no-close focused test (the prior client-lifetime test patched run_async so on_unmount was never exercised); happy --new resolve test verifying POST count + identity propagation. Issues #2/#3/#4/#5 contracts amended in-place to reflect: - create_session widened (PRE-003, body construction step, body shape POST) - ParsedArgs description + _parse_args STEPS + _amain create_session call + new TESTS for end_user_id + env-var fallback - _resolve_then_run STEPS + new TEST entries; on_mount narrowed; INV-007 amended for new client ownership - Post-#6 adjustment note on issue #5 (_resolve_then_run replaces on_mount as the threading site since #6 moved session resolution out of the alt-screen) 188 tests GREEN; ruff clean. Bumps to v0.1.0 — first minor release, the load-bearing reason is RatatoskrApp.__init__'s breaking signature change (additive end_user_id alone wouldn't have triggered a minor pre-v1.x). Files Gitea issues #9 (spec-pin refresh v0.19.0 → v0.22.1), #10 (track Worldtree #196 subject:{type,id} migration), #11 (AdminEvents pane auth prerequisite admin.events.read). Infra-ops pinged via althing for agents.call:lofn scope add (broker pattern; they forwarded to worldtree-dev because personal Worldtree exposes no public scope-mutation endpoint).
This commit is contained in:
@@ -55,12 +55,14 @@ The Textual TUI is the eventual primary product (design-brief §5) — separate
|
||||
- Environment: `WORLDTREE_API_KEY` (fallback for `--api-key`), `WORLDTREE_API_URL` (fallback for `--server`).
|
||||
|
||||
**Parsed arguments** (`ParsedArgs` frozen dataclass):
|
||||
- `send_content: str` — the user message text (`--send <content>`, required).
|
||||
- `send_content: str | None` — the user message text (`--send <content>`); None signals TUI mode (issue #4 amendment).
|
||||
- `session_id: str | None` — existing session (`--session <id>`); mutex with `new`.
|
||||
- `new: bool` — mint a fresh session (`--new`); mutex with `session_id`.
|
||||
- `agent_id: str | None` — required iff `new=True`.
|
||||
- `api_key: str` — resolved from `--api-key` then `$WORLDTREE_API_KEY`.
|
||||
- `server_url: str` — resolved from `--server`, then `$WORLDTREE_API_URL`, then `http://localhost:8000`.
|
||||
- `raw: bool` — `--raw` opt-out from markdown rendering (issue #4 amendment).
|
||||
- `end_user_id: str | None` — `--end-user-id <id>` for per-end-user agents (issue #5 amendment); default None preserves the pre-#5 baseline for agents that don't require it (mimir).
|
||||
|
||||
**Output (stdout):**
|
||||
- Raw text deltas from `Text` events, written without trailing newline per chunk. A single trailing newline is written after the terminal `Done` event so the next shell prompt lands on a fresh line.
|
||||
@@ -257,12 +259,15 @@ STEPS:
|
||||
--api-key <key> (str, optional — env fallback in step 4)
|
||||
--server <url> (str, optional — env fallback + default in step 5)
|
||||
--raw (bool flag, default False — issue #4 amendment: TUI markdown opt-out)
|
||||
--end-user-id <id> (str, optional, default None — issue #5: required by per-end-user agents; if passed, must be non-empty)
|
||||
2. [sequential, flexibility=prescriptive] Parse argv:
|
||||
TRY: ns = parser.parse_args(argv if argv is not None else sys.argv[1:])
|
||||
ON SystemExit:
|
||||
Re-raise as UsageError with argparse's captured message
|
||||
2a. [branch, flexibility=prescriptive] IF ns.send is not None AND not ns.send:
|
||||
RAISE UsageError("--send content must be non-empty") # empty-string --send still invalid
|
||||
2b. [branch, flexibility=prescriptive, issue #5] IF ns.end_user_id is not None AND not ns.end_user_id:
|
||||
RAISE UsageError("--end-user-id must be non-empty when passed") # mirrors empty-send check
|
||||
3. [branch, flexibility=prescriptive] Validate session/new/agent triad per INV-004:
|
||||
IF ns.session and ns.new: RAISE UsageError("--session and --new are mutually exclusive")
|
||||
IF NOT ns.session AND NOT ns.new: RAISE UsageError("pass exactly one of --session or --new")
|
||||
@@ -273,6 +278,9 @@ STEPS:
|
||||
IF NOT api_key: RAISE _AuthError("no API key (set --api-key or WORLDTREE_API_KEY)")
|
||||
5. [sequential, flexibility=prescriptive] Resolve server_url per INV-006:
|
||||
server_url = ns.server or os.environ.get("WORLDTREE_API_URL") or "http://localhost:8000"
|
||||
5b. [sequential, flexibility=prescriptive, issue #5 amended 2026-05-23] Resolve end_user_id with env-var fallback:
|
||||
end_user_id = ns.end_user_id or os.environ.get("RATATOSKR_END_USER_ID") or None
|
||||
# Flag > $RATATOSKR_END_USER_ID > None. env.sh ships "ratatoskr-tui" as the project-stable partition.
|
||||
6. [cleanup] RETURN ParsedArgs(
|
||||
send_content=ns.send, # may be None (TUI marker, issue #4 amendment)
|
||||
session_id=ns.session,
|
||||
@@ -281,6 +289,7 @@ STEPS:
|
||||
api_key=api_key,
|
||||
server_url=server_url,
|
||||
raw=ns.raw, # issue #4 amendment
|
||||
end_user_id=end_user_id, # issue #5 amendment (env-var fallback amended 2026-05-23)
|
||||
)
|
||||
TESTS:
|
||||
happy_new [happy,tracer]: argv=["--send", "hi", "--new", "--agent", "mimir", "--api-key", "k"] → ParsedArgs(send_content="hi", session_id=None, new=True, agent_id="mimir", api_key="k", server_url="http://localhost:8000")
|
||||
@@ -299,6 +308,11 @@ TESTS:
|
||||
usage_session_with_agent [adversarial]: argv=["--send","hi","--session","s-1","--agent","x","--api-key","k"] → UsageError("--agent is required with --new and forbidden with --session")
|
||||
auth_missing [error]: argv lacks --api-key and WORLDTREE_API_KEY unset → _AuthError
|
||||
empty_send [adversarial]: argv=["--send", "", "--new", "--agent", "x", "--api-key", "k"] → UsageError (non-empty enforced via argparse type=lambda or explicit check)
|
||||
happy_new_with_end_user_id [happy, issue #5]: argv includes --end-user-id alice → ParsedArgs.end_user_id == "alice"
|
||||
end_user_id_default_none [trace, issue #5]: argv omits --end-user-id AND env unset → ParsedArgs.end_user_id is None
|
||||
empty_end_user_id [adversarial, issue #5]: argv has --end-user-id "" → UsageError("--end-user-id must be non-empty when passed")
|
||||
end_user_id_from_env [trace, issue #5 amended 2026-05-23]: env RATATOSKR_END_USER_ID="ratatoskr-tui"; flag omitted → ParsedArgs.end_user_id == "ratatoskr-tui"
|
||||
end_user_id_flag_beats_env [trace, issue #5 amended 2026-05-23]: env set + flag passed → flag wins
|
||||
```
|
||||
|
||||
```contract
|
||||
@@ -325,7 +339,7 @@ ERROR_ROUTING:
|
||||
STEPS:
|
||||
1. [setup, flexibility=prescriptive] OPEN httpx.AsyncClient(base_url=args.server_url, headers={"Authorization": f"Bearer {args.api_key}"}, timeout=httpx.Timeout(connect=10.0, read=None, write=10.0, pool=10.0)) via async-with — read=None disables the SSE-killing 5s default (issue #1 [compatibility] constraint)
|
||||
2. [branch, flexibility=prescriptive] IF args.new:
|
||||
TRY: info = await sessions.create_session(client, args.agent_id)
|
||||
TRY: info = await sessions.create_session(client, args.agent_id, end_user_id=args.end_user_id) # issue #5: thread end_user_id when set; None preserves pre-#5 body shape
|
||||
ON AgentNotFound as exc:
|
||||
WRITE stderr; RETURN 12
|
||||
ON SessionApiFailed as exc:
|
||||
|
||||
Reference in New Issue
Block a user