pretheme
This commit is contained in:
@@ -0,0 +1,484 @@
|
||||
# Inject pipeline — caller context & reference material
|
||||
|
||||
Documento ufficiale di tutti i blocchi di contesto che il server inietta
|
||||
automaticamente nel prompt LLM **all'inizio di ogni sessione**, sia per il
|
||||
canale **WebSocket realtime** (`/v1/realtime`, patch di `session.update`) sia
|
||||
per l'endpoint **HTTP reasoning** (`POST /api/reasoning`, augmentation di
|
||||
`instructions` e `user_context`).
|
||||
|
||||
Tutto il sistema è **generico / deploy-agnostic** per scelta architetturale:
|
||||
nessun campo viene hardcodato. Ogni blocco accetta payload arbitrari e li
|
||||
serializza in JSON direttamente. Qualsiasi errore di rete / JSON non
|
||||
interrompe mai il flusso: il blocco problematico viene solo saltato con
|
||||
una riga `[INJECT-*] ⏭️ skipped` nel log server.
|
||||
|
||||
---
|
||||
|
||||
## 0. Master flag: `S2S_INJECT_PROFILE`
|
||||
|
||||
Tutta la pipeline è sotto **un unico master flag** lato server.
|
||||
|
||||
| Valore env `S2S_INJECT_PROFILE` | Comportamento |
|
||||
|---|---|
|
||||
| `false` / non impostato | **Pure passthrough.** Nessuna fetch upstream, nessuna modifica a `session.update` o al body `/api/reasoning`. Il proxy passa tutto invariato. |
|
||||
| `true` / `1` / `yes` / `on` | Pipeline inject attiva. I singoli blocchi poi dipendono dagli env specifici e dai query params della URL del client. |
|
||||
|
||||
Perché esiste: su deploy generici (nessuna integrazione con AzurianLab /
|
||||
Aquachile / sistemi interni) il client rimane un puro speech-to-speech
|
||||
passthrough, senza dipendere da endpoint esterni.
|
||||
|
||||
---
|
||||
|
||||
## 1. Blocco episodio podcast — `episode_id`
|
||||
|
||||
**Priorità massima.** Quando c'è, viene considerato il riferimento PRINCIPALE
|
||||
e più fidato per tutta la sessione.
|
||||
|
||||
### Trigger (quando si attiva)
|
||||
|
||||
Una qualunque delle due:
|
||||
1. URL pagina browser → query string `?episode_id=episode-xxx`
|
||||
2. Body `POST /api/reasoning` → campo `episode_id: "episode-xxx"`
|
||||
|
||||
### Sorgente dati
|
||||
|
||||
GET plain verso l'endpoint Aquachile AzurianLab:
|
||||
|
||||
```
|
||||
https://aquachile.azurianlab.com/api/v1/podcast/episodes/{episode_id}
|
||||
```
|
||||
|
||||
Parametri fetch:
|
||||
- timeout 15 s
|
||||
- `follow_redirects: true`
|
||||
- **Nessuna auth** (endpoint pubblico)
|
||||
|
||||
Formato risposta accettato:
|
||||
- `Content-Type: application/json` → oggetto arbitrario o array
|
||||
- `{"result": <payload>}` → wrapper supportato, il contenuto viene "spacchettato"
|
||||
- `text/plain` → usato come stringa raw
|
||||
|
||||
### Cap
|
||||
|
||||
24.000 caratteri dopo serializzazione JSON (transcript lunghi).
|
||||
Se supera → troncato con `...` append.
|
||||
|
||||
### Wrap applicato
|
||||
|
||||
```
|
||||
< PODCAST_EPISODE_HINT >
|
||||
<<<PODCAST_EPISODE_BEGIN>>>
|
||||
{ <TUTTI I CAMPI DELL'EPISODIO, JSON> }
|
||||
<<<PODCAST_EPISODE_END>>>. This is the episode the caller is currently interacting with — answer with reference to it first.
|
||||
```
|
||||
|
||||
**Hint testuale** (in testa, letto dal modello come primissima frase del blocco):
|
||||
|
||||
> Podcast episode context loaded from the page `episode_id` query param (current episode the user is referencing). Treat the episode fields (title, description, transcript, script, show notes, guests, audio metadata, etc.) as the **PRIMARY trusted reference material** for this session.
|
||||
|
||||
### Campi supportati (esempi — lista NON esaustiva, qualsiasi campo extra viene incluso)
|
||||
|
||||
- metadati base: `id`, `title`, `slug`, `description`, `summary`, `publishedAt`, `durationSeconds`
|
||||
- contenuti: `transcript`, `podcastScript`, `script`, `chapters[]`, `showNotes`
|
||||
- persone: `host`, `guests[]`, `author`, `guestsInfo[]`
|
||||
- audio: `audioUrl`, `audioFile`, `waveform`, `sampleRate`
|
||||
- classificazione: `tags[]`, `topics[]`, `category`, `season`, `episodeNumber`
|
||||
- riferimenti incrociati: `reportId`, `scheduleId`, `company`, `product`
|
||||
|
||||
### Riferimenti codice
|
||||
|
||||
- fetch upstream: [_fetch_podcast_episode](file:///home/duxiter/s2s/s2s-client/server.py#L1010-L1078)
|
||||
- wrap + serialize + cap 24k: [_resolve_episode_context](file:///home/duxiter/s2s/s2s-client/server.py#L475-L504)
|
||||
- hint costante: [PODCAST_EPISODE_HINT](file:///home/duxiter/s2s/s2s-client/server.py#L426-L429)
|
||||
|
||||
---
|
||||
|
||||
## 2. Blocco profilo chiamante — `phoneNumber`
|
||||
|
||||
Contesto identità del chiamante. Viene usato per il modello quando risponde
|
||||
(riferirsi all'utente per nome, conoscere la sua azienda, ruolo, ecc.).
|
||||
|
||||
### Trigger
|
||||
|
||||
1. URL browser → `?phoneNumber=%2B<numero-internazionale>`
|
||||
2. Body `/api/reasoning` → `phoneNumber: "+…"`
|
||||
|
||||
### Sorgente dati
|
||||
|
||||
```
|
||||
GET VOCERO_USER_PHONE_AUTH_URL?phoneNumber=<phone>
|
||||
```
|
||||
|
||||
Se è configurato anche `S2S_LOGIN_BY_PHONE`, PRIMA della profile fetch:
|
||||
```
|
||||
POST S2S_LOGIN_BY_PHONE body { "phoneNumber": "..." }
|
||||
```
|
||||
Da questa risposta il proxy estrae un bearer token e lo inoltra come
|
||||
`Authorization: Bearer <token>` sulle chiamate successive (profile, reports,
|
||||
summary).
|
||||
|
||||
Formato risposta: JSON arbitrario, wrapper `{"result": ...}` supportato.
|
||||
|
||||
### Cap
|
||||
|
||||
1.600 caratteri dopo JSON serialize.
|
||||
|
||||
### Wrap
|
||||
|
||||
```
|
||||
Caller profile resolved from the page phoneNumber:
|
||||
{ <profilo JSON troncato> }.
|
||||
Use this as trusted caller context when relevant.
|
||||
```
|
||||
|
||||
### Riferimenti codice
|
||||
|
||||
- login by phone: [_login_by_phone](file:///home/duxiter/s2s/s2s-client/server.py#L812-L890)
|
||||
- fetch profile: [_fetch_user_profile_for_phone](file:///home/duxiter/s2s/s2s-client/server.py#L1081-L1134)
|
||||
- wrap serialize: [_resolve_user_context_text](file:///home/duxiter/s2s/s2s-client/server.py#L432-L444)
|
||||
|
||||
---
|
||||
|
||||
## 3. Blocco lista reports / script schedulati
|
||||
|
||||
Riferimento "storico" dei contenuti preparati prima della sessione. Non sono
|
||||
contesto live, ma materiale che il modello deve sapere esiste (e a cui
|
||||
riferirsi se l'utente chiede di report pregressi, script, podcast schedulati).
|
||||
|
||||
### Trigger
|
||||
|
||||
Solo env: `S2S_REPORT_LIST` deve essere valorizzato a un URL valido.
|
||||
**Funziona anche SENZA `phoneNumber`** (lista reports generica per tutti).
|
||||
|
||||
Se `phoneNumber` è presente lo passa come query param all'endpoint.
|
||||
|
||||
### Sorgente dati
|
||||
|
||||
```
|
||||
GET S2S_REPORT_LIST[?phoneNumber=<phone>]
|
||||
```
|
||||
|
||||
Wrapper `{"result": [...]}` supportato.
|
||||
|
||||
### Cap
|
||||
|
||||
12.000 caratteri sul blocco COMPLETO (ogni podcast script può essere lungo).
|
||||
|
||||
### Wrap + filtri + sort
|
||||
|
||||
```
|
||||
Scheduled reports / podcast scripts available for this caller before the current session.
|
||||
Each entry is a scheduled report; use the podcastScript contents as trusted reference material
|
||||
when the user asks about scheduled topics, reports, or prior content.
|
||||
<<<REPORTS_LIST_BEGIN>>>
|
||||
[
|
||||
{
|
||||
"sourceScheduleLabel": "...",
|
||||
"date": "2026-09-06T…",
|
||||
"podcastScript": "…"
|
||||
},
|
||||
… ordinati per data DESC (nuovi prima) …
|
||||
]
|
||||
<<<REPORTS_LIST_END>>>
|
||||
```
|
||||
|
||||
Regole per ogni entry:
|
||||
1. **Solo dict** vengono tenuti (stringhe/int scartati).
|
||||
2. Per ogni entry estrae 3 campi con fallback a nested shape:
|
||||
- Label: `sourceScheduleLabel` oppure `sourceSchedule.label`
|
||||
- Date: `date` oppure `sourceSchedule.date`
|
||||
- Script: `podcastScript`
|
||||
3. Ordinamento **data DESC** (nuovi prima) con parser robusto:
|
||||
- ISO datetime / date strings
|
||||
- `Date` / `DateTime` Python (se deserializzati)
|
||||
- Unix timestamp int/float
|
||||
- Lexicografico DESC come fallback per stringhe ISO-ish
|
||||
- Valori nulli / non-parse-abili finiscono in fondo.
|
||||
|
||||
### Riferimenti codice
|
||||
|
||||
- fetch: [_fetch_reports_list](file:///home/duxiter/s2s/s2s-client/server.py#L668-L745)
|
||||
- parse data sort key: [_parse_date_key](file:///home/duxiter/s2s/s2s-client/server.py#L507-L515)
|
||||
- wrap + filtro + sort: [_resolve_reports_context](file:///home/duxiter/s2s/s2s-client/server.py#L518-L666)
|
||||
|
||||
---
|
||||
|
||||
## 4. Blocco summary ultima conversazione
|
||||
|
||||
Sintesi della chiamata/chat IMMEDIATAMENTE precedente a questa. Usato dal
|
||||
modello per "ricordarsi" cosa si è detto l'ultima volta senza conservare
|
||||
storico lunghissimo nel prompt.
|
||||
|
||||
### Trigger
|
||||
|
||||
Entrambe:
|
||||
- env `S2S_LATEST_SUMMARY` impostato
|
||||
- `phoneNumber` presente in URL/body
|
||||
|
||||
### Sorgente dati
|
||||
|
||||
```
|
||||
GET S2S_LATEST_SUMMARY?phoneNumber=<phone>
|
||||
```
|
||||
|
||||
Wrapper `{"result": <valore>}` supportato.
|
||||
Accetta SIA JSON che plain text (diversi backend summary ritornano testo semplice).
|
||||
|
||||
### Cap
|
||||
|
||||
1.600 caratteri.
|
||||
|
||||
### Wrap
|
||||
|
||||
```
|
||||
Latest conversation summary for this caller (most recent before the current session):
|
||||
<<<LATEST_SUMMARY_BEGIN>>>
|
||||
<summary, stringa o JSON serializzato>
|
||||
<<<LATEST_SUMMARY_END>>>.
|
||||
Use this as trusted history when the user references prior calls.
|
||||
```
|
||||
|
||||
### Riferimenti codice
|
||||
|
||||
- fetch: [_fetch_latest_summary_for_phone](file:///home/duxiter/s2s/s2s-client/server.py#L893-L971)
|
||||
- wrap: [_resolve_latest_summary_context](file:///home/duxiter/s2s/s2s-client/server.py#L446-L472)
|
||||
|
||||
---
|
||||
|
||||
## 5. Blocco `user_context` custom dal browser (SOLO `/api/reasoning`)
|
||||
|
||||
**Canale solo HTTP reasoning.**
|
||||
Non è nel WS: il WS non ha campo user_context nel protocollo realtime
|
||||
OpenAI (tutto passa per `session.instructions`).
|
||||
|
||||
Composizione lato client in [main.js fetchOpenAiReasoningAnswer](file:///home/duxiter/s2s/s2s-client/main.js#L1337-L1364):
|
||||
|
||||
```js
|
||||
const profileCtx = voceroUserContextText();
|
||||
const ragCtx = customRagContextText();
|
||||
const user_context = [profileCtx, ragCtx]
|
||||
.map(s => s.trim()).filter(Boolean).join("\n\n");
|
||||
```
|
||||
|
||||
### Dove va a finire
|
||||
|
||||
Nel body a monte sia NODE_RED sia OPENAI, questo blocco viene **APPESO DOPO**
|
||||
tutti i blocchi iniettati server-side:
|
||||
|
||||
```
|
||||
user_context finale =
|
||||
[1. episode] ← solo se episode_id
|
||||
[2. profile] ← solo se phoneNumber + env profilatura
|
||||
[3. reports] ← solo se env S2S_REPORT_LIST
|
||||
[4. summary] ← solo se phoneNumber + S2S_LATEST_SUMMARY
|
||||
+ browser user_context (profileCtx + ragCtx)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Ordine finale nel prompt LLM
|
||||
|
||||
Questa è la sequenza **letterale** con cui i blocchi vengono concatenati
|
||||
dentro `session.instructions` (WS) e in `instructions` / `user_context`
|
||||
(reasoning HTTP).
|
||||
|
||||
```
|
||||
1. EPISODIO PODCAST ← PRIORITÀ PIÙ ALTA (primary trusted reference)
|
||||
2. PROFILO CHIAMANTE ← identity / caller profile
|
||||
3. REPORTS SCHEDULATI ← scheduled / prior content
|
||||
4. SUMMARY ULTIMA CALL ← history più recente
|
||||
5. Base instructions (prompt predefinito o primary prompt)
|
||||
6. (solo reasoning) user_context custom inviato dal browser
|
||||
```
|
||||
|
||||
Perché questo ordine:
|
||||
- più in alto nel prompt = più peso / attenzione dal modello.
|
||||
- l'episodio attuale è il materiale che l'utente ha esplicitamente richiesto
|
||||
aprendo la pagina con `?episode_id=`, quindi merita massimo peso.
|
||||
- il profilo chiamante va sempre prima di "contenuti generici" (reports e
|
||||
summary) perché serve per coerenza nel riferirsi all'utente.
|
||||
- reports = contenuti schedulati PRIMA della sessione corrente → più vecchi
|
||||
dell'ultima chiamata, quindi messi PRIMA del summary ultima chiamata che
|
||||
è il contenuto più "vivo".
|
||||
|
||||
Implementazione ordine: [_augment_instructions_with_profile](file:///home/duxiter/s2s/s2s-client/server.py#L1149-L1159)
|
||||
```python
|
||||
contexts = [c for c in [episode_ctx, profile_ctx, reports_ctx, summary_ctx] if c]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Punti di innesco (quando viene calcolato / applicato)
|
||||
|
||||
### Canale WebSocket — `/v1/realtime`
|
||||
|
||||
**MOMENTO 1 (solo una volta):** apertura WS.
|
||||
|
||||
Nel handler [@app.websocket("/v1/realtime")](file:///home/duxiter/s2s/s2s-client/server.py#L1791-L1959)
|
||||
appena la WS viene accettata:
|
||||
1. parsa `ws.url.query` per `phoneNumber` e `episode_id`
|
||||
2. esegue login (se attivo) → profile → episode → reports → summary
|
||||
3. salva TUTTI i blocchi già wrappati e serializzati nel dict `session_ctx`:
|
||||
```python
|
||||
session_ctx = {
|
||||
"injected_episode_context": "...",
|
||||
"injected_profile_context": "...",
|
||||
"injected_reports_context": "...",
|
||||
"injected_summary_context": "...",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**MOMENTO 2 (ogni `session.update` client→upstream):** nel task
|
||||
[_client_to_upstream](file:///home/duxiter/s2s/s2s-client/server.py#L2649-L2661)
|
||||
ogni frame in uscita viene passato a:
|
||||
|
||||
```python
|
||||
_patch_client_session_update_with_profile(
|
||||
frame_text,
|
||||
user_context_text = profile_ctx,
|
||||
reports_context_text = reports_ctx,
|
||||
summary_context_text = summary_ctx,
|
||||
episode_context_text = episode_ctx, # ← nuovo
|
||||
)
|
||||
```
|
||||
|
||||
Se il frame NON è `session.update` → ritorna **identico** e non viene loggato
|
||||
(per evitare spam).
|
||||
Se è `session.update` → patch del campo `payload["session"]["instructions"]`.
|
||||
|
||||
### Canale HTTP — `POST /api/reasoning`
|
||||
|
||||
**Ogni chiamata** ricostruisce i blocchi da zero nel
|
||||
[handler `/api/reasoning`](file:///home/duxiter/s2s/s2s-client/server.py#L1411-L1577):
|
||||
1. legge `req.phoneNumber` + `req.episode_id`
|
||||
2. fetch episodio (se episode_id) + login + profile + reports + summary
|
||||
3. costruisce `user_context_parts` e `effective_instructions`
|
||||
4. inoltra al backend (NODE_RED o OPENAI)
|
||||
|
||||
---
|
||||
|
||||
## Quando un blocco viene SKIPPATO (niente inject)
|
||||
|
||||
Tutti i blocchi hanno 5 possibili ragioni di skip. Nessuna rompe mai il
|
||||
flusso: ogni caso produce una riga `[INJECT-*] ⏭️ skipped` nei log e basta.
|
||||
|
||||
| Causa | Log |
|
||||
|---|---|
|
||||
| Master flag `S2S_INJECT_PROFILE=false` | `[INJECT-PROFILE] ⏭️ skipped (S2S_INJECT_PROFILE=false)` |
|
||||
| Parametro mancante (es. `phoneNumber` non in URL) | `[INJECT-*] ⏭️ skipped (<parametro> missing)` |
|
||||
| Endpoint env non configurato | `[INJECT-*] ⏭️ skipped (env for ... empty)` |
|
||||
| Upstream HTTP ≠ 200 | `[INJECT-*] ⛔ upstream HTTP <status_code>` |
|
||||
| Upstream ha ritornato vuoto / null / `{}` / `[]` | `[INJECT-*] ⏭️ upstream returned empty` |
|
||||
| Eccezione di rete (timeout, DNS, SSL) | `[INJECT-*] ⛔ fetch unreachable error=<exc>` |
|
||||
|
||||
---
|
||||
|
||||
## Marcatori log / marker nel prompt
|
||||
|
||||
Ogni blocco è **auto-documentante** per ispezione dei log con coppie di
|
||||
marker:
|
||||
|
||||
```
|
||||
<<<PODCAST_EPISODE_BEGIN>>> / <<<PODCAST_EPISODE_END>>>
|
||||
<<<REPORTS_LIST_BEGIN>>> / <<<REPORTS_LIST_END>>>
|
||||
<<<LATEST_SUMMARY_BEGIN>>> / <<<LATEST_SUMMARY_END>>>
|
||||
```
|
||||
|
||||
Quando `S2S_DEBUG_PROMPT=true` viene loggato il contenuto COMPLETO di ogni
|
||||
blocco con i tag `[DEBUG-EPISODE]`, `[DEBUG-REPORTS]`, `[DEBUG-SUMMARY]` sia
|
||||
all'atto del load (WS connect) sia dopo l'augment delle instructions (prima
|
||||
di inoltrare a monte). **WARNING:** questo env può loggare PII.
|
||||
|
||||
---
|
||||
|
||||
## Tabella riepilogo: trigger per ogni blocco
|
||||
|
||||
| # | Blocco | Trigger | env richiesto | parametro | cap |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | Episodio podcast | `episode_id` in URL o body | (nessuno — URL hardcodato Aquachile) | `episode_id=` | 24 k |
|
||||
| 2 | Profilo chiamante | `phoneNumber` | `VOCERO_USER_PHONE_AUTH_URL` | `phoneNumber=` | 1,6 k |
|
||||
| 3 | Reports schedulati | sempre se env | `S2S_REPORT_LIST` | opzionale `phoneNumber=` | 12 k |
|
||||
| 4 | Summary ultima call | `phoneNumber` | `S2S_LATEST_SUMMARY` | `phoneNumber=` | 1,6 k |
|
||||
| 5 | Browser user_context | solo reasoning | (nessuno) | body `user_context` | illimitato |
|
||||
|
||||
---
|
||||
|
||||
## Error handling strategico
|
||||
|
||||
Perché tutti gli errori sono silenziosi (log + skip) e non abortiscono:
|
||||
|
||||
> Se il backend dei profili è giù per manutenzione 5 minuti non vogliamo
|
||||
> che il Voice Agent smetta di funzionare. Semplicemente l'assistente non
|
||||
> conosce l'identità del chiamante per 5 minuti, ma parla normalmente.
|
||||
|
||||
L'utente non si accorge di niente. L'operatore legge i log e vede le righe
|
||||
`⛔`; può risolvere offline.
|
||||
|
||||
Se volessi forzare il fail (raro, su deploy dedicato), si trasformerebbero
|
||||
le `return None` finali in `raise HTTPException(...)` — ma per default
|
||||
non è il comportamento voluto.
|
||||
|
||||
---
|
||||
|
||||
## Appendice A — Start-session modes (`S2S_START_SESSION_MODE`)
|
||||
|
||||
Oltre alla inject pipeline lato server, è presente una feature lato client che
|
||||
governa **come e se la sessione vocale inizia in automatico** e se il modello
|
||||
saluta per primo (in modo identico alla wake-word "Hola Vocero").
|
||||
|
||||
### Valori supportati
|
||||
|
||||
| `S2S_START_SESSION_MODE` | Chi inizia? | Richiede click utente? | Trigger greet iniziale |
|
||||
|---|---|---|---|
|
||||
| *(vuoto) / default* | Utente, tap manuale Orb | ✅ | No |
|
||||
| `TALK` | Auto dopo 2s con overlay unlocker | ✅ (1 click policy-safe su Safari/iOS) | WS client: `sendUserText("hola vocero") + requestResponse()` dopo `session.updated` |
|
||||
| `TALKFIRST` | Auto dopo 2s **SENZA click** (se possibile) | ❌ se mic già "allow always"; fallback unlocker se `getUserMedia` fallisce | **Stesso identico codice della wake-word vera:** `pendingWakeWordConfirmationText = "hola vocero"` (main.js `case "connected"` → `sendUserText + requestResponse`) |
|
||||
|
||||
### Warmup 2s
|
||||
|
||||
Il timer non parte prima di 2000 ms per lasciare terminare:
|
||||
- `fetchVoceroUserProfile()` (identity chiamante)
|
||||
- `fetchVoceroReportsList()` (storico script podcast)
|
||||
- fetch episode (`episode_id`) se presente
|
||||
- `GET /api/config` (con la quale il client apprende `startSessionMode`)
|
||||
|
||||
Senza warmup le injection lato server potrebbero ancora non essere completate e
|
||||
il greet iniziale arriverebbe "cieco" (senza contesto episodio/profilo).
|
||||
|
||||
### Fallback di `TALKFIRST`
|
||||
|
||||
Nel 99% dei casi `getUserMedia()` fuori da una user gesture fallisce
|
||||
(NotAllowedError / NotReadableError su Safari, Chrome con policy strict, iOS).
|
||||
In tal caso:
|
||||
1. si chiude il client WS (`teardown()`)
|
||||
2. stato torna `idle`
|
||||
3. si mostra `#talk-unlocker` con la dicitura:
|
||||
- **"Se requiere permiso de micrófono"**
|
||||
- **"Toca en cualquier lugar para permitir el audio y empezar"**
|
||||
4. `pendingWakeWordConfirmationText` NON viene svuotato → quando l'utente
|
||||
toccherà, `case "connected"` triggerà comunque la greet come wake-word.
|
||||
|
||||
### Riferimenti codice
|
||||
|
||||
| Componente | File | Range righe |
|
||||
|---|---|---|
|
||||
| Lettura env + banner startup + `/api/config` exposure | [server.py](file:///home/duxiter/s2s/s2s-client/server.py) | [L150](file:///home/duxiter/s2s/s2s-client/server.py#L150), [L196-L212](file:///home/duxiter/s2s/s2s-client/server.py#L196-L212), [L1383-L1400](file:///home/duxiter/s2s/s2s-client/server.py#L1383-L1400) |
|
||||
| Overlay HTML unlocker | [index.html](file:///home/duxiter/s2s/s2s-client/index.html#L32-L39) | L32-L39 |
|
||||
| Stili overlay | [style.css](file:///home/duxiter/s2s/s2s-client/style.css#L145-L215) | L145-L215 |
|
||||
| Variabile globale + parse config client | [main.js](file:///home/duxiter/s2s/s2s-client/main.js#L628-L632) | L628-L632 |
|
||||
| `fetchConfig()` parsing + `reportDebug("B")` | [main.js](file:///home/duxiter/s2s/s2s-client/main.js#L2309-L2320) | L2309-L2320 |
|
||||
| `new S2sWsRealtimeClient({startWithResponse})` | [main.js](file:///home/duxiter/s2s/s2s-client/main.js#L2683-L2692) | L2683-L2692 |
|
||||
| Scheduling + `tryTalkFirstDirect()` + unlocker gesture | [main.js](file:///home/duxiter/s2s/s2s-client/main.js#L2976-L3083) | L2976-L3083 |
|
||||
| `onClientStatus("connected")` → wake-word inject flow | [main.js](file:///home/duxiter/s2s/s2s-client/main.js#L2897-L2914) | L2897-L2914 |
|
||||
| WS client → `TALK` mode sendUserText("hola vocero") + `requestResponse()` | [s2s-ws-client.js](file:///home/duxiter/s2s/s2s-client/ws/s2s-ws-client.js#L688-L713) | L688-L713 |
|
||||
|
||||
---
|
||||
|
||||
## Riferimenti file
|
||||
|
||||
| File | Ruolo |
|
||||
|---|---|
|
||||
| [server.py](file:///home/duxiter/s2s/s2s-client/server.py) | Tutta la pipeline inject: fetch, wrap, patch, handler WS + reasoning |
|
||||
| [main.js](file:///home/duxiter/s2s/s2s-client/main.js) | Passaggio client `?phoneNumber` + `?episode_id` → WS query params e POST `/api/reasoning` body |
|
||||
| [CHANGELOG.md](file:///home/duxiter/s2s/s2s-client/CHANGELOG.md) | History rilasci / modifiche alla inject pipeline |
|
||||
Reference in New Issue
Block a user