Bite-sized TDD tasks: schema-linking types + columns modal, gate widget with
staged per-table column selection, registry/summary wiring, and a replay
fixture generated from the real physical.yaml catalog. Offline-verifiable in
the replay server; harness wiring is Plan 2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reviewer curates, per promoted table, which columns to use over the full
catalog list (suggested pre-selected + bold); selection persists into
schema_linking.json + the decision ledger and softly guides SQL generation
(Option 1). Dedicated structured schema-linking gate widget (Approach A),
staged commit, inline gate + single columns modal. Hard SQL enforcement is
an explicit follow-up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Improves how reviewer gates render their content, and adds two small
hooks that make the frontend cooperate with the standalone replay server
(also useful as generic primitives).
Gate body rendering:
- IntroBody: gate intros now render paragraphs plus real bullet lists
with a hanging indent and emphasised bullet lead term, filling the
card width (no narrow measure cap). Backed by introFormat (parser) +
IntroBody (renderer), with tests.
- GateArtifactBody: artifact-review gates whose artifact is a bare file
reference (e.g. {kind:"phase", data:{file:"question.md"}}) now fetch
the referenced session document and render its full content, instead
of showing the file reference as text. Falls back to ArtifactView for
inline-content artifacts and unmapped files. Backed by gateArtifact
(resolver) with tests. ArtifactGateWidget passes sessionId through.
- SelectWidget renders intros via IntroBody.
Replay-friendly session exit:
- AppShell: a system_event {event:"session_exit"} now triggers
stopSession(), returning to the landing view. Generic primitive (the
real backend can emit it too); used by the replay server's "Esci".
- SessionMenu: the ⋮ trigger is now always visible (was opacity-0 /
group-hover only), so Resume is reachable without hovering.
Verified offline against the replay server (no VPN needed); tsc clean.
Reproduces the real reviewer UI for any recorded session, with no VPN/Pi/
Python/DWH. The server (node:http, zero deps) serves the built SPA and a
tiny SSE/REST shim that re-emits the reviewer gates captured in a Pi
transcript, in their original order, with the reviewer's real 3-Jul
choices shown as comparison badges.
- tools/replay/extract.mjs: extracts gates from one or many transcripts
(session-id, file, or directory). Handles sessions split across resume
re-entries by sorting on message timestamp and dropping unanswered
gates. Reads the question from session_manifest.yaml, resolving the
sessions dir from any workspace yaml (no hardcoded paths).
- tools/replay/server.mjs: same-origin :5333. SSE streams gates; POST
/response advances the cursor and pushes info badges (scelta reale).
POST /resume and the final "Ripeti/Esci" widget close the SSE so the
browser EventSource reconnects (cursor resets, gate 1 re-emitted) — the
replay is re-runnable any number of times. GET /sessions/:id/documents
reads the real session files so GateArtifactBody resolves file-reference
artifacts. Exit emits system_event {event:"session_exit"} to return to
the landing.
- scripts/replay.sh: launcher (extract / build / run / all).
- tools/replay/README.md: data flow, commands, fidelity notes.
- .gitignore: ignore tools/replay/web/ (built artifact, like dist/).
A render error in one viewer or gate widget unmounted the whole React root
(white screen). Add a reusable ErrorBoundary (class component, no new dep) with
resetKeys + an on-brand fallback, and wire it at two surfaces:
- WidgetHost: isolates the gate widget (reset on descriptor id) so a malformed
gate payload no longer blanks the conversation
- SessionDocumentsPanel: wraps each document (reset on doc key/content) so one
crashing viewer degrades only its section; siblings and the panel survive
The observed crash: a schema-linking doc that parses but lacks `candidates`
makes SchemaLinkingViewer throw. Verified live via Playwright against the mock
backend. TDD throughout; tsc clean, 123/123 vitest (+8 new tests).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- present() = presentBlockingWidget: transport + no-limbo loop + response
validation; both branches share validateUiResponse; the id invariant must be
validated explicitly in the TUI branch (emitAndWait no longer runs there)
- TUI renderer uses numbered options + index parsing, never label mapping
(frontend already answers with ids); artifact-gate editor is viewer-only,
returned content ignored
- guards resolved: both TUI-only notices now ctx.mode === "tui"
- interactive-render.js as two layers (renderTuiDescriptor +
validateSyntheticResponse); add negative test cases + a real TUI smoke
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The "respond with gate widgets; use '!' for free text" notice is terminal-
flavored. Like the reLoop Esc guard, it used ctx.hasUI, which on pi >=0.80 is
true in RPC too, so it leaked to the browser. Now guarded on ctx.mode === "tui".
The plain text is still swallowed by the `handled` return in all modes; only the
warning is dropped in RPC. No functional ctx.hasUI guard remains in the gate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fix six GUI defects, verified live against the real stack:
- NavSessions: mark the in-progress (active) session in yellow (warning
token) — both its status dot and its row background.
- SteerInput: the composer is now an auto-growing textarea that wraps and
grows vertically (caps at 160px, then scrolls); Enter sends, Shift+Enter
inserts a newline.
- WorkflowBar: show a synthetic title of the current phase under the 8 dots
from static EN/IT strings (no LLM); English is displayed to match the chrome.
- CentralStatus: reformat the 5-line system-message tail as a structured,
monospace list with per-line markers and an emphasized last line.
- AppShell + CentralStatus: move the left Model-activity panel toggle off the
working spinner (now a pure status indicator) onto a dedicated arrow button
(→ opens, ← closes).
- gate: rename the phase-confirm button "Conferma e prosegui" → "Salva e
procedi" (builders.js + its L1 test).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
reLoop's "Esc non chiude il gate..." warning is terminal-specific. On pi 0.73
ctx.hasUI was false in RPC, so `if (ctx.hasUI)` effectively meant "TUI only". On
pi >=0.80 hasUI is true in RPC too (dialog-capable UI via the bridge), so the
notice leaked to the browser on any invalid gate response. Guard on
ctx.mode === "tui" to restore the original intent. The fake pi runtime gains
mode:"tui" so the roundtrip test still exercises the notice.
Audit of the other 5 ctx.ui.notify: left as-is. They are valid in both live
modes (TUI and RPC, both hasUI=true) and the gate cannot run headless
(emitAndWait needs a UI), so a guard would be dead code. Their dual-mode
rationalization belongs with the future present() work (tracked in the eval doc).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- #1 hasUI: real 0.80.3 comment is "true in TUI and RPC modes" (doc had the old
0.73.1 "false in print/RPC"). hasUI no longer distinguishes TUI from RPC, so it
is NOT a fallback for the interactive branch — only ctx.mode === "tui" is.
- #2 info/freetext: buildInfoRequest exists in builders.js but is NOT wired into
tht-gate.js (imports only select/multiselect/artifact-gate). info notices are
hand-written ctx.ui.notify; freetext is a control, not a descriptor. Table now
lists only the 3 real present() descriptors.
- #3 cite the REWRITE banner (tht-gate.js:8-10), not :227; id-match quoted verbatim.
- #4 new "Note implementative": 227 comment, 4/6 unguarded notify, and the
ctx.hasUI guard migration side-effect (now fires in RPC on 0.80.3).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- aritmolab-provider.js: import pi-ai from @earendil-works (was @mariozechner).
Verified live that the aritmolab/qwen provider still registers under pi 0.80.3
(get_available_models -> aritmolab, deepseek, zai). Removes the dual-package
reliance on the frozen @mariozechner install still on disk for rollback.
- docs/general/pi-configuration.md: built-in models package is @earendil-works/pi-ai
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Some models (GLM 5.2) send object params (reviewer_confirm's artifact,
write_schema_linking's schema_linking) as JSON-encoded strings. These failed
TypeBox validation before execute(), making the model retry in an unbounded loop
(observed ~2100s hang). jsonObjectOrSelf() coerces them back to objects in
prepareReviewerArguments and write_schema_linking.
Also close an anti-bypass gap: pi's write/edit tools were unrestricted on the gate
code, so a looping model actually patched tht-gate.js. GATE_CODE_FILES now blocks
any write under .pi/extensions. Requires a pi restart to take effect.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Left panels 30vw / central 55% / right rail 15vw, minimal padding. Model-activity
log and documents render via .thot-prose; decisions become hairline rows with
semantic type chips. Resume switches the view optimistically (instant feedback).
The Stop button now asks for confirmation before interrupting.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ArtifactView's fallback (StructuredValue) turns any object/array into labelled
sections, bullet lists and paragraphs (strings as markdown). Verified against the
real F1 gate (kind:"text", {recap, chiarimenti_risolti[], ...}). MarkdownView
uses the real .thot-prose class (Tailwind Typography is not installed).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Titles + intro read as a clear prompt (comfortable line length), options as
spaced choice rows, Other/Back/Exit as discreet secondary controls under a
divider. Multiselect gets a prominent Select all/Deselect all at the head of the
list with a live count.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Timer now accumulates only while the harness works, pausing while a gate awaits
the reviewer and freezing when finalized (keyed per session, in-memory). Resume
now colours the re-entry phase immediately from the manifest's phase (1..8),
mirroring the F1 paint for new questions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
erDiagram reads data_type/type from the candidate (top-level or signals),
falling back to the generic 'col' token Mermaid needs — real types render as
soon as schema_linking.json carries them. ArtifactView gains a defensive
{content} fallback for sql-kind artifacts, verified against real session shapes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
buildSelectRequest injected an `altroOption` ({id:"other", opens:freetext}) into the
select descriptor, but SelectWidget/MultiselectWidget never route option.opens — so it
rendered as an inert "Altro — specifica…" button next to the working reserved
"Other — specify" control (which now owns free-text since the reviewer-gate-ux fix).
Remove the injection, the now-unused altroOption()/ALTRO_LABEL, and the unused
allow_other flag from both builders (no frontend consumer). Free-text stays offered on
every gate via the reserved "Other" control. The ArtifactGateWidget opens/LinkageHost
linkage (reject-with-reason capability) is intentionally kept — it is a separate,
tested feature, not the injected duplicate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Backlog T2 from the reviewer-gate-ux review: the Send button on the reserved
"Other — specify" textarea emitted control:freetext with an empty payload when
clicked on an empty/whitespace field. Guard with disabled={!text.trim()}.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- WorkflowBar: center the phase pills (add justify-center).
- tht-gate.js: the gate was sending the descriptive phase name (e.g.
"chiarimento") instead of the workflow.yaml id ("F1") in the widget's
`phase` field, so the frontend's F1..F8 match never hit and pills never
lit up. Rename phaseName -> phaseId, return p.id.
- CentralStatus: expand the live "working" tail from a single truncated
line to up to 5 lines (line-clamp-5 safety net for unbroken paragraphs).
- ModelActivityPanel: render the streamed tail through react-markdown +
remark-gfm instead of raw per-line <p> tags, so emphasis/headings render
and blank-line paragraph breaks (previously stripped) are preserved;
tail-cut now operates on paragraphs instead of physical lines.
Updated tests accordingly (WorkflowBar/CentralStatus/ModelActivityPanel/
AppShell.session-mgmt); full frontend suite (98/98) + harness gate node
tests (34/34) + tsc -b pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add mkdocs.yml (windmill theme, mermaid2, matching ~/Chirone/chirone/etl
setup) with nav split into "ThothII (Documentazione Tecnica)" — architecture
overview, existing design specs/plans, reports — and "Considerazioni
Generali" for cross-project notes.
Add docs/general/pi-configuration.md explaining Pi's three model-resolution
tiers (built-in, user models.json, project extension) and where GLM/DeepSeek/
Qwen each sit. Add docs/architecture/overview.md synthesizing the ThothII
architecture for the doc site. Relocate the L2 run report into docs/reports/.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Move the AritmoLab provider (Qwen) into harness/.pi/extensions/ so it is
auto-discovered only when Pi runs with cwd=harness, keeping it project-local.
GLM (~/.pi/agent/models.json) and DeepSeek (built-in) stay user-wide. File is
.js (not .mjs) because Pi extension auto-discovery matches only /\.(ts|js)$/.
Removes the gemma4-26b-a4b model (404 at the endpoint) from both the provider
and the model-matrix default list.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The cheat-sheet, Discipline 2, and Phase 7 said F7 closes with
reviewer_confirm kind:"sql" — but the gate's kind:"sql" only records
sql_approved:phase:7; F7 is not in _AUTO_ADVANCE_PHASES, so advancing to
F8 still needs reviewer_confirm kind:"phase" (mirrors F6). Same
record-vs-advance trap this branch removes elsewhere.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
F3/F4 close with reviewer_confirm kind:'phase' (they do NOT auto-advance);
advance:true only auto-advances F2-empty/F6-skip; rewrite_question belongs
to F3 not F1; F4 uses the new write_schema_linking tool with the documented
SchemaLinking shape. Adds a per-phase artifact/close cheat-sheet.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
tht() gains an optional stdin arg; the tool pipes the schema-linking
object to 'tht session set-schema-linking --file -', which validates
against SchemaLinking and returns the exact error on failure — so the
model stops hand-writing the artifact and validating with ad-hoc python.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
reviewer_confirm kind:'cte_result' registered cte_approved --subject
phase:6, which decision_cmd rejects (exit 5) and next_cte never
recognizes — dead-ending F6. Derive the CTE name from 'tht cte next'
(plan-order single source of truth) and approve by name. sql path
(sql_approved:phase:N) unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Design doc for three coordinated harness fixes derived from the
2026-06-30-165708 session analysis: correct SKILL.md phase-advance
contract, fix the F6 cte_approved subject bug, and add a
tht-mediated schema_linking.json writer/validator.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
All workstreams D, E, B, C, F, A, G done and pushed; resume cold-start stall
resolved (open-item #1). Refresh the Git section: main @ cbb8e18, the three
stale merged branches deleted, only main remains. Clean handoff snapshot.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tier 1 (clean-room first-turn harness, harness/scripts/model-matrix.mjs):
kickoff + resume chain in-turn on ALL available models — zai/glm-5.2,
deepseek/deepseek-v4-{pro,flash}, aritmolab/qwen3.6-35b-a3b, zai/glm-4.5-air.
The resume cold-start stall recurs on none (closes A's cross-model robustness).
aritmolab/gemma4-26b-a4b is a 404 at the endpoint (listed but not served) — an
availability gap classified as MODEL_ERROR, not a workflow issue.
Tier 2 (live, baseline zai/glm-5.2): F single-select auto-confirm verified
end-to-end — answering the first reviewer_select persisted a concept_clarified
decision (review_decisions.jsonl 0->1) with no follow-up confirmation gate.
Closes F's deferred live check.
No prompt hardening needed. Results in the G plan doc + memory. Throwaway psd
sessions used and deleted; real sessions untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Actionable scoping for the final workstream: a two-tier method (clean-room
first-turn harness generalised from the A2 repro-driver for cheap kickoff/resume
signals; sparing full Playwright F1 runs for the gate round-trip) over the
configured models (GLM 5.2 baseline, Deepseek V4, Qwen3.6, + breadth). Folds in
F's deferred single-select live check and A's resume-on-weaker-models check.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A1 — SessionMenu gains a Resume item, gated to status!=="finalized" && !archived
(matching the backend's 409 read-only guard), wired in AppShell to doResume ->
POST /sessions/:id/resume. SessionMenu.test.tsx (3 tests); frontend 96/96, tsc clean.
A2 — diagnosis-first clean-room repro driving `pi --mode rpc` with the backend's
exact resume handshake shows the cold-start stall NO LONGER reproduces on pi
0.79.4 (8/8 chained into `tht session show` + `read SKILL.md` in-turn, fresh and
partway sessions). The earlier narrate-and-stop predates the pi upgrade.
Defense-in-depth anyway: RIPRENDI_KICKOFF hardened to force the in-turn tool call
(gate_resume_kickoff.test.js + live regression 2/2). Gate JS 34/34.
PROJECT_STATE open-item #1 (resume stall) flipped to RESOLVED; cross-model resume
robustness folded into workstream G.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
F — reviewer_select options may now carry a `decision` payload {type, subject,
detail?, rationale?} plus an optional `advance`. Picking such an option IS the
confirmation: the gate persists it directly (tht decision add) and optionally
advances, with no redundant reviewer_decide/reviewer_confirm follow-up gate.
Options without a payload stay ask-only; back/exit/Other never persist.
Pure logic extracted + exported for unit tests: resolveSelectOutcome (classifies
the response) and decisionAddArgs (shared with reviewer_decide, DRY). Gate JS
suite 33/33 (gate_select_decision.test.js, +5); harness pytest 269 unchanged.
Contract docs updated together: reviewer_select tool description, SKILL.md
(widget summary, disciplines 2-3, Phase-1 single-pick), and the CLAUDE.md gate
note. Live verification (model truly emits reviewer_select+decision, decision in
review_decisions.jsonl, no follow-up gate) deferred to workstream G — it is
model-behavior-dependent.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
B — WorkflowBar renders F1..F8 as colored ring-dots (no phase-name text):
green=done, amber=running (subtle pulse), red=error, gray=pending; green
connectors lead the active dot, each dot carries data-state. Lightweight
error signal: sessionStore gains `phaseError`, set when an info event has
level=error during the active phase, cleared on the next ui_request.
C — denser single-line session rows (inline status dot + name, py-1), a
3-level type hierarchy (L1 SESSIONS / L2 section+group headers / L3 names),
and the "No group" label removed (ungrouped render after the last group,
guarded so the empty-state still teaches when there are zero groups).
Live-verified with Playwright (all four dot states, sidebar hierarchy, and
E's deferred activity-panel check). Frontend 93/93, tsc -b clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- CentralStatus: replace the red pulsing dot at the start of the "working" line
with the rotating WorkingSpinner, now a button that opens the model-activity
panel (onOpenActivity). WorkingSpinner is extracted to its own module (was
local to AppShell).
- ModelActivityPanel: show the last 5 lines of the model-stream tail (refreshing
as the stream grows) with an expand toggle to the full stream, replacing the
unbounded full transcript.
- AppShell: drop the separate spinner button (the inline icon is now the single
trigger) and the local WorkingSpinner def.
- Remove the now-orphaned Transcript.tsx (its only consumer was the panel).
TDD: CentralStatus + ModelActivityPanel tests RED->GREEN; frontend suite 87/87;
tsc clean. Live visual verification pending (to do with the B/C frontend pass).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New sessions get a concise Italian-keyword `name` instead of the truncated
question. `tht session new` (when no --name is given) derives it via a new
`_extract_name` helper using YAKE (pure-Python, unsupervised, Italian, no LLM),
dropping generic query verbs and keeping the top keywords in reading order;
falls back to `_summarize` if YAKE is unavailable. `create_session` core keeps
its `name=None` default — the policy lives at the CLI layer.
TDD: tests/test_session_name.py (unit + CliRunner integration). Full harness
suite 269 passed; ruff clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The frontend's reviewer widgets uniformly send the picked option in a `choices`
array (SelectWidget/ArtifactGateWidget: `choices: [optionId]`), but the gate's
reviewer_select and reviewer_confirm(reject) handlers read `resp.choice`
(singular). Result: every single-select gate saw an undefined choice, answered
"Nessuna scelta ricevuta", and re-presented forever — the workflow could never
pass F1. (reviewer_decide/multiselect already read `resp.choices`, so it worked.)
Add a shared selectedChoice(resp) helper reading choices[0] (falling back to the
legacy singular choice); both handlers use it.
TDD: gate/__tests__/gate_choice.test.js RED->GREEN; full gate suite 28/28.
Verified LIVE (Playwright -> real Pi -> GLM 5.2): a single-select F1 answer is
now accepted and the workflow advances (clarification 2/4 -> 3/4). The same run
also live-verified the F1 hang fix (418187a).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ctx.ui.input in `pi --mode rpc` correlates extension_ui_response on its own
top-level RPC id (crypto.randomUUID), not the descriptor id the gate carries
in `title`. SessionBridge replied with the descriptor id, so Pi silently
dropped the response and the model never resumed — every reviewer widget hung
after the human answered.
SessionBridge now stores Pi's top-level m.id (pendingPiId) and replies
extension_ui_response{ id: pendingPiId, value: <uiResponse> }; value still
carries the descriptor id so the gate's internal resp.id === descriptor.id
check still holds.
The fake-pi double had masked the bug by forcing m.id == descriptor.id; it now
mirrors real Pi (distinct randomUUID, correlate on it, drop unknown ids), with
a negative regression test. SKILL.md Phase 1 also now steers multi-answer
disambiguation to reviewer_decide (multiselect).
Tests: backend 67/67, tsc clean, fake-pi contract 2/2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
While the model works (active session, no pending widget), CentralStatus shows
a Claude-Code-style live status: a pulsing dot, elapsed seconds (ticking), and a
short tail of the model's current output — so the centre no longer looks stuck
during the long F1 loop. Verbose stream stays in the left panel. No tokens yet
(would need the SessionBridge to forward Pi usage).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Active/Archive accordions; rename group (client-side reassign); central
area shows only last user entry + gate notify/info + active widget; the
verbose model stream moves to a left on-demand panel toggled by the WIP
icon (moved above the composer). Frontend-only; fine thinking-separation
deferred.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pi 0.73 rpc mode is headless and runs tools without an approval gate; the
removed --approve flag made pi exit with 'Unknown option: --approve', breaking
every session spawn. Spawn args are now just --mode rpc. +regression test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hard-fail preflight at session create/restart: ensure Ollama up + warm the
configured embedding model, refuse the session if embeddings unavailable.
Parameterized ollama bin/start_cmd; tht ollama ensure command + backend 503.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- SessionMenu: anglicize all Italian labels (Vista divisa → Split view, etc.)
- RenameDialog: add empty-name guard + change title to "Rename session"
- DeleteConfirmDialog: translate title and description to English
- SessionActions.test: add 2 new tests (empty name guard, cancel on DeleteConfirmDialog)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>