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/).
116 lines
5.4 KiB
Markdown
116 lines
5.4 KiB
Markdown
# ThothII reviewer replay
|
|
|
|
Riproduce su `http://localhost:5333` la **vera UI** dei widget reviewer di una
|
|
sessione registrata, **senza VPN, Pi, Python, né DWH**. Si usano i descriptor
|
|
dei gate così come furono proposti al revisore (catturati nel transcript Pi),
|
|
nello stesso ordine, con le scelte realmente fatte mostrate come badge di
|
|
confronto.
|
|
|
|
## Schema del flusso
|
|
|
|
```
|
|
browser (SPA reale, :5333)
|
|
│ REST + SSE, same-origin
|
|
▼
|
|
server.mjs (node:http, zero dipendenze)
|
|
├─ static: serve il bundle SPA da tools/replay/web/
|
|
└─ replay SSE: GET /sessions/:id/events → event: ui_request (gate N)
|
|
POST /sessions/:id/response → badge "scelta reale" + gate N+1
|
|
│
|
|
legge
|
|
▼
|
|
replay.json (fixture estratta dal transcript)
|
|
│ prodotta da
|
|
▼
|
|
extract.mjs (legge ~/.pi/agent/sessions/…/*.jsonl + question.md)
|
|
```
|
|
|
|
Niente backend Fastify, niente `pi --mode rpc`, niente `tht`, niente DB: il
|
|
server gira direttamente sui descriptor registrati.
|
|
|
|
## Comandi
|
|
|
|
```bash
|
|
# Estrai la fixture per una sessione (id, file, o directory di transcript):
|
|
scripts/replay.sh extract 2026-07-03-132933-fammi-la-lista-dei-pazienti-che-negli-ul
|
|
scripts/replay.sh extract ~/.pi/agent/sessions/--Users-mp-projects-ThothII-harness--/
|
|
|
|
# (una tantum, o dopo modifiche al frontend) costruisci il bundle SPA:
|
|
scripts/replay.sh build
|
|
|
|
# Avvia il server su :5333:
|
|
scripts/replay.sh run
|
|
|
|
# Scorciatoia: extract (default) + build (se manca) + run:
|
|
scripts/replay.sh
|
|
```
|
|
|
|
**Cosa può estrarre `extract`:**
|
|
|
|
| Input | Comportamento |
|
|
|---|---|
|
|
| *(nessuno)* | La sessione di default (3 lug 17:07, 20 gate). |
|
|
| `<session-id>` | Scansiona **tutti** i transcript nella directory Pi per quel session-id. Gestisce automaticamente sessioni spezzate su più file (resume): raccoglie i gate da tutti i transcript, li ordina cronologicamente, e scarta i gate senza risposta (quelli riproposti al resume). |
|
|
| `<file.jsonl>` | Un singolo transcript. |
|
|
| `<directory>` | Tutti i `*.jsonl` nella directory; se contengono sessioni diverse, tiene quella dominante (con più gate). |
|
|
|
|
La domanda della sessione è letta da `session_manifest.yaml` (campo `question`),
|
|
cercando la directory sessions in ogni workspace di `harness/workspaces/` (via
|
|
`paths.sessions`, assoluto o relativo). Niente più path hardcoded.
|
|
|
|
## Cosa vedi nel browser
|
|
|
|
1. Apri `http://localhost:5333`. La lista sessioni mostra **una** sessione
|
|
(quella riprodotta) con la domanda reale.
|
|
2. Cliccala: appare il **gate 1** — un `reviewer_select` (widget select) con il
|
|
titolo, l'intro e le opzioni esatte che il modello propose il 3 luglio,
|
|
incluso il badge "consigliato" sull'opzione raccomandata.
|
|
3. Clicca un'opzione. Sotto la tua scelta compare un `info` con la **scelta
|
|
reale del 3 luglio** (`📌 SCELTA REALE (3 lug 2026): …`), e — se coincide — un
|
|
`✓ Hai scelto come il 3 luglio.`
|
|
4. Avanzi attraverso tutti i gate nell'ordine loggato: `select` (F1 chiarimenti,
|
|
F6 conferma piano), `multiselect` (F3 riscrittura, F4 schema linking, F6
|
|
piano CTE), `artifact-gate` (F2/F3/F4/F5 phase advance, F6 approvazione CTE
|
|
con SQL visibile nell'`ArtifactView`).
|
|
5. Dopo l'ultimo gate: `✓ Replay completato — 20/20 gate`.
|
|
|
|
I 20 gate della sessione 17:07: 4 `select` + 11 `confirm` + 5 `decide`.
|
|
|
|
## Limiti e fedeltà al log
|
|
|
|
- **Sessioni multi-file (resume)**: gestite. L'estrattore raccoglie i gate da
|
|
tutti i transcript della sessione, li ordina per timestamp del messaggio, e
|
|
scarta i gate senza `toolResult` (quelli dove il revisore chiuse Pi senza
|
|
rispondere e che furono riproposti al resume). Risultato: la sequenza
|
|
esatta dei gate che il revisore ha effettivamente visto e deciso.
|
|
- **Workspace discovery**: la domanda è letta da `session_manifest.yaml`,
|
|
cercando `paths.sessions` nei workspace di `harness/workspaces/`. Funziona
|
|
per qualunque cliente (non solo `psd`), purché il workspace yaml sia
|
|
presente e `paths.sessions` sia un path assoluto o relativo (non espanso
|
|
con `${VAR}` — in quel caso il path non è risolvibile offline e la domanda
|
|
resta vuota).
|
|
- **Phase compute disattivata**: la fase corrente (colorazione `F1`…`F8`) non è
|
|
ricalcolata dal ledger (richiederebbe `tht phase`); i gate usano un phase-tag
|
|
neutro `replay`. L'ordine e i descriptor sono quelli reali.
|
|
- **Gate falliti/saltati**: alcuni gate non hanno una "scelta reale" perché
|
|
nella sessione live furono saltati (memorie vuote → avanzamento auto) o
|
|
falliti (es. `cte_plan` con tipo invalido). Il badge
|
|
`⚠ Gate fallito nella sessione reale` lo segnala fedelmente — riflette il
|
|
log, non un bug del replay.
|
|
- **`text_delta`/ragionamenti del modello**: omessi (scelta "solo gate"). Per
|
|
vederli, estendi `extract.mjs` per raccogliere anche i `text_delta`.
|
|
- **Reserved controls** (`back`/`exit`/`other`): nel replay sono ammessi come
|
|
qualunque altra risposta, ma non hanno semantica (non c'è stato di
|
|
navigazione da ripristinare); trattali come "procedi".
|
|
- **Single-user, single-session**: il server tiene un solo cursore globale; non
|
|
è pensato per più client concorrenti (un refresh del browser riprende dal
|
|
gate corrente).
|
|
|
|
## File
|
|
|
|
- `extract.mjs` — estrattore fixture dal transcript Pi JSONL.
|
|
- `server.mjs` — server HTTP+SSE standalone (node:http, zero deps).
|
|
- `replay.json` — fixture generata (committata per comodità).
|
|
- `web/` — bundle SPA costruito da `frontend/` con
|
|
`VITE_BACKEND_URL=http://localhost:5333` (gitignorato; prodotto da `build`).
|