diff --git a/README.md b/README.md
index 6ac9979..86f1a4d 100644
--- a/README.md
+++ b/README.md
@@ -1,276 +1,41 @@
# agy-pi
-
-
-
-
-Estensione per **pi** che integra **Google Antigravity CLI (`agy`)** come subagent multimodale.
-
-Anche se il modello di pi è text-only, questa estensione permette a pi di delegare a agy
-(che usa **Gemini multimodale**) task che richiedono immagini, audio e video, oltre a
-conversazioni di ragionamento multi-turno.
+Estensione per [pi](https://github.com/badlogic/pi-mono) che integra il client OAuth **Google Antigravity** (`agy`) come subagent multimodale. Non usa né gestisce chiavi Google AI Studio/Gemini API.
## Capacità
-| Capacità | Tool | Esempio |
-|---|---|---|
-| Conversazione multi-turno | `agy` | `agy(prompt="Analizza questo problema...")` |
-| Generazione immagini (strutturata) | `agy_generate` | `agy_generate(subject="un gatto", style="fotorealistico")` |
-| Editing controllato (Keep+Change+Add+Render) | `agy_edit` | `agy_edit(baseImage=..., change="...", keep="...")` |
-| Editing zona specifica (inpainting) | `agy_inpaint` | `agy_inpaint(baseImage=..., target="il divano", replacement="blu navy")` |
-| Style transfer | `agy_style_transfer` | `agy_style_transfer(baseImage=..., style="Van Gogh")` |
-| Composizione multi-immagine | `agy_compose` | `agy_compose(images=[...], instruction="...")` |
-| Consistenza personaggio | `agy_character` | `agy_character(referenceImage=..., name="Maya", task="...")` |
-| **Generazione verificata iterativa** | **`agy_create_verified`** | `agy_create_verified(requirements=..., maxIterations=3, useOpenCV=true)` |
-| Analisi file (imm/audio/video) | `agy_analyze` | `agy_analyze(filePath=..., question="...")` |
-| Trascrizione audio | `agy_transcribe` | `agy_transcribe(filePath="voce.wav", language="italiano")` |
-| Analisi video | `agy_video` | `agy_video(filePath="clip.mp4", question="...")` |
-| Elenco modelli | `agy_models` | `agy_models()` |
-| Gestione stato conversazione | `agy_conversation` | `agy_conversation(action="id" \| "list" \| "reset")` |
+- chat e ragionamento multi-turno;
+- generazione, editing, composizione e analisi immagini;
+- analisi video;
+- trascrizione audio e input vocale F12 tramite Antigravity;
+- catalogo modelli e stato conversazione Antigravity.
-L'agente (pi) decide autonomamente quale strumento usare in base al task richiesto.
+Strumenti principali: `agy`, `agy_generate`, `agy_edit`, `agy_inpaint`, `agy_style_transfer`, `agy_compose`, `agy_character`, `agy_analyze`, `agy_transcribe`, `agy_video`, `agy_models`, `agy_conversation`, `antigravity_chat`.
-### Generazione verificata (`agy_create_verified`)
+Non include TTS interno né generazione immagini verificata iterativa.
-Quando l'orchestratore di pi **non ha visione** e serve un'immagine conforme a
-specifiche precise, `agy_create_verified` esegue un loop self-contained:
+## Audio e F12
-```
-genera immagine (agy) → analizza con visione (PASS/FAIL vs requisiti)
- → verifica OpenCV (dimensioni, colori, luminosità, bordi)
- → rigenera con prompt di correzione se FAIL → ripete fino a maxIterations
+Premi **F12** per avviare/fermare la registrazione. L'audio viene ottimizzato, convertito in Opus/OGG e inviato al modello `voiceModel` tramite il gateway Antigravity OAuth. Il workflow genera trascrizione e prompt pulito; con `vocalPlanningMode=true` richiede una conferma nell'overlay prima dell'invio a pi.
+
+`agy_transcribe` usa la stessa pipeline Antigravity. Non esistono fallback a servizi con chiave API esterna. I suoni di start/stop/done sono effetti locali, non sintesi vocale.
+
+## Configurazione
+
+```text
+/agy:config
+/agy:config set voiceModel gemini-3.7-flash-medium
+/agy:status
```
-Restituisce l'immagine candidata finale + report di verifica (verdetto visione,
-metriche OpenCV ed eventuale edit programmatico per ogni iterazione). Parametri:
-`requirements`, `outputDir`, `maxIterations` (default 3), `useOpenCV` (default true),
-`editInstructions`, `editScript`, `model`.
-
-Con `editInstructions` il tool genera una trasformazione deterministica da applicare
-prima della verifica a ogni iterazione. In alternativa `editScript` accetta uno script
-Python esplicito con contratto `sys.argv[1]` input e `sys.argv[2]` output. Gli script
-sono eseguiti con timeout, directory di lavoro temporanea e allowlist di `cv2`,
-`numpy`, `PIL`, `json`, `sys` e `math`; import, rete, subprocess e accesso arbitrario
-al filesystem vengono rifiutati. Un edit fallito rende automaticamente non conforme
-l'iterazione.
-
-## Configurazione persistente (`/agy:config`)
-
-Tutte le impostazioni dell'estensione si gestiscono con il comando `/agy:config`
-(salvate in `~/.config/agy-pi/config.json`, permessi 600):
-
-```
-/agy:config # elenca tutte le impostazioni
-/agy:config get # mostra una chiave
-/agy:config set # imposta una chiave
-/agy:config reset # ripristina i default
-```
-
-### Dialog TUI per la chiave Gemini (`/agy:key`)
-
-Per inserire/modificare la **chiave API Gemini** con un'interfaccia grafica in
-sovrimpressione (dialog TUI, campo mascherato) usa il comando `/agy:key`:
-
-```
-/agy:key
-```
-
-Apre un overlay centrato nel terminale:
-
-- mostra la chiave attuale mascherata (`AIza...yfDY`)
-- campo di input **mascherato** (digiti la chiave, vedi `•`)
-- `Enter` conferma (salva in config + `~/.agy-chat/gemini-key`)
-- `Esc` annulla
-
-È il modo interattivo e sicuro per impostare `geminiApiKey` senza digitarla in
-chiaro nella cronologia del terminale.
-
-### Diagnostica (`/agy:status`)
-
-Il comando `/agy:status` apre un overlay TUI con lo stato dell'estensione:
-
-- binario agy (trovato/non trovato) e versione
-- chiave Gemini valida/mancante (mascherata) e stato
-- modello attivo, backend STT, notifiche TTS
-- ultimo errore significativo dai log di agy
-
-Chiudi con `Enter` o `Esc`.
-
-### Context Injection (Fase 2) — `pi → agy`
-
-Quando il tool `agy` (o altri con l'opzione `injectContext`) invia un prompt a
-Gemini, l'estensione **inietta automaticamente** un blocco di contesto prima
-della richiesta utente, seguendo le best practice di context engineering:
-
-```
- ← cwd, OS, data/ora, git branch + file modificati
- ← ultimi messaggi utente pi (compatti, non il transcript)
- ← archivio fatti chiave (memory.json, auto-aggiornato)
- ← risultati ricerca web (Strada A, se pertinente)
---------------------------------
-[ RICHIESTA UTENTE ] ← sempre per ultima (anti lost-in-the-middle)
-```
-
-- **Tag XML** e richiesta per ultima (evidenze “Lost in the Middle”, TACL 2024)
-- **Token cap** default ~1500 (chiave `contextTokens`)
-- **Memoria durevole** automatica: oltre una soglia di turni, i punti chiave
- vengono condensati in `~/.config/agy-pi/memory.json` invece di rigirare tutto
-- **Ricerca web** (Strada A): con `webSearch=auto|on` l'estensione cerca con
- l'API Gemini (`googleSearch` grounding) e inietta i risultati nel ``
-
-| Chiave | Default | Descrizione |
-|---|---|---|
-| `geminiApiKey` | — | Chiave API Google Gemini (STT/TTS) |
-| `enne2ApiKey` | — | Token per il server proxy ai.enne2.net (opzionale) |
-| `sttBackend` | `gemini` | Backend trascrizione: `gemini` \| `enne2` |
-| `sttUrl` | `https://ai.enne2.net` | URL base backend enne2 |
-| `sttModel` | `gemma4:E4B` | Modello STT backend enne2 |
-| `sttMaxDuration` | `120` | Durata max registrazione (secondi) |
-| `ttsBackend` | `gemini` | Backend TTS: `gemini` \| `enne2` |
-| `ttsNotify` | `true` | Notifiche vocali automatiche |
-| `ttsModel` | `gemini-2.5-flash-preview-tts` | Modello TTS Gemini |
-| `agyBin` | `agy` | Path del binario agy |
-| `agyDefaultModel` | — | Modello predefinito per le chiamate agy |
-| `agyTimeoutMs` | `180000` | Timeout esecuzione agy (ms) |
-| `contextInject` | `true` | Iniezione contesto nel prompt agy |
-| `contextTokens` | `1500` | Token cap per il contesto iniettato |
-| `webSearch` | `auto` | Ricerca web Strada A: `auto` \| `on` \| `off` |
-| `vocalPlanningMode` | `true` | Piano + conferma in overlay dopo il vocale |
-| `sttDirectGemini` | `true` | Interpretazione multimodale diretta Gemini (anche con DeepSeek/Claude) |
-| `voiceModel` | `gemini-3.7-flash-medium` | Modello Gemini per l'audio |
-
-Le variabili d'ambiente (`GEMINI_API_KEY`, `AGY_STT_BACKEND`, ecc.) hanno
-priorità sul file di config quando impostate.
-
-## Registrazione microfono (F12)
-
-Premi **F12** per avviare/fermare la registrazione dal microfono (max 2 min).
-Il flusso: registra → taglia il silenzio → **trascrive con la Gemini API diretta**
-(`gemini-3.5-flash`, veloce e affidabile — agy CLI non supporta file audio) →
-interpreta con Gemini usando il contesto della conversazione → inserisce il
-risultato come prompt su pi.
-
-Con `vocalPlanningMode=true` (default) il flusso è **a 2 fasi con overlay TUI**:
-dopo la trascrizione, l'estensione genera un **piano** e mostra un popup di
-conferma con la trascrizione e il piano proposto:
-
-```
-🎙️ Conferma vocale
- Trascrizione: "Aggiorna i docs..."
- 📋 Piano proposto: ...
- Enter esegui • Esc annulla • E modifica • Spazio testo letterale • F12 registra di nuovo
-```
-
-- **Enter** → esegue il piano (invia il risultato a pi)
-- **Esc** → annulla, nessuna azione
-- **E** → modifica il testo del piano manualmente
-- **Spazio** → chiude il popup e inserisce nell'editor la **trascrizione letterale** di quanto dettato, senza inviare il piano proposto
-- **F12** → registra di nuovo
-
-Questa modalità evita che l'interpretazione vocale avvii autonomamente loop
-agentici o modifiche a sorpresa: la direttiva di Gemini è "produci solo il piano,
-non eseguire nulla", e l'esecuzione parte solo dopo la tua conferma. Per tornare
-all'esecuzione diretta, imposta `vocalPlanningMode=false` con
-`/agy:config set vocalPlanningMode false`.
-
-Il testo scritto nel campo editor prima di avviare la registrazione viene letto
-(`getEditorText`) e **combinato con la trascrizione audio** nel piano, poi
-l'editor viene svuotato per evitare reinvii duplicati.
-
-**Requisito**: la key Gemini API in `~/.agy-chat/gemini-key` (chmod 600) o nella
-variabile d'ambiente `GEMINI_API_KEY`.
-
-```bash
-# una volta sola
-echo "LA_TUA_KEY" > ~/.agy-chat/gemini-key && chmod 600 ~/.agy-chat/gemini-key
-```
-
-**Backend di trascrizione** (variabile `AGY_STT_BACKEND`):
-
-| Backend | Descrizione | Configurazione |
-|---|---|---|
-| `gemini` (default) | Gemini API (`gemini-3.5-flash`), veloce e affidabile | key in `~/.agy-chat/gemini-key` |
-| `enne2` | Server locale `ai.enne2.net` con `gemma4:E4B` (supporta audio), ~2-3s | `AGY_STT_URL` (default `https://ai.enne2.net`), `AGY_STT_MODEL` (default `gemma4:E4B`) |
-
-```bash
-# usa il server locale
-export AGY_STT_BACKEND="enne2"
-```
-
-Comandi: `/agy:record` (toggle), `/agy:record:stop` (ferma).
+Le impostazioni sono in `~/.config/agy-pi/config.json`. Serve solo completare il login OAuth del client `agy`.
## Installazione
-Requisito: `agy` installato e autenticato (una volta: `agy`, poi login OAuth nel browser).
-
-```bash
-# da un repo git
-pi install git:github.com//agy-pi
-
-# oppure da una cartella locale
-pi install /percorso/a/agy-pi
-```
-
-Per provare senza installare:
-
```bash
+pi install git:git.enne2.net/enne2/agy-pi
+# oppure
pi -e /percorso/a/agy-pi
```
-## Uso
-
-### Come tool (chiamato automaticamente dal modello)
-
-Quando chiedi a pi di generare un'immagine, analizzare un file, o ragionare insieme a un
-secondo agente, pi può chiamare il tool `agy`. Esempi di prompt:
-
-```
-Genera un'immagine di un paesaggio marziano al tramonto.
-Analizza l'immagine /home/utente/foto.jpg e descrivila.
-Trascrivi il file audio /home/utente/voce.wav.
-Chiedi a agy di ragionare su questo problema e poi confronta la sua risposta con la tua.
-```
-
-### Come comando interattivo
-
-```
-/agy # invia un prompt a agy
-/agy:new # forza una nuova conversazione
-/agy:list # mostra la conversazione corrente
-/agy:reset # azzera lo stato conversazione
-```
-
-## Parametri del tool `agy`
-
-| Parametro | Tipo | Descrizione |
-|---|---|---|
-| `prompt` | string (obbligatorio) | Il task/prompt per agy |
-| `mode` | `chat` \| `image` \| `analyze` | Tipo di operazione (default `chat`) |
-| `model` | string | Modello agy (es. `Gemini 3.1 Pro (High)`, `Claude Opus 4.6 (Thinking)`) |
-| `effort` | `low` \| `medium` \| `high` | Livello di ragionamento |
-| `newConversation` | boolean | Forza una nuova conversazione |
-| `addDir` | string | Cartella da aggiungere al workspace agy |
-| `filePath` | string | File (immagine/audio/video) da analizzare |
-| `yolo` | boolean | `--dangerously-skip-permissions` (necessario per audio/video) |
-
-## Multi-turno
-
-L'estensione mantiene l'ID della conversazione agy in `~/.agy-chat/conversation_id`.
-Ogni chiamata a `agy` continua la conversazione precedente, così agy ricorda i turni
-precedenti (ragionamento multi-shot). Usa `newConversation: true` per ripartire da zero.
-
-## Note di sicurezza
-
-- `yolo: true` auto-approva tutti gli strumenti di agy. Usalo solo in ambienti fidati.
-- Le estensioni pi girano con i permessi completi del sistema. Rivedi il codice prima di
- installare pacchetti di terze parti.
-
-## Struttura
-
-```
-agy-pi/
-├── package.json # manifest pi
-├── extensions/index.ts # estensione (tool + comandi)
-├── bin/agy-chat.sh # wrapper bash standalone (opzionale)
-└── README.md
-```
+Consulta `docs/configuration.md`, `docs/tools.md` e `docs/vocal-workflow.md` per i dettagli.
\ No newline at end of file
diff --git a/docs/architecture.md b/docs/architecture.md
index 659a340..2412b0c 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -1,97 +1,17 @@
# Architettura di agy-pi
-Questo documento descrive la struttura interna e il ciclo di vita dell'estensione `agy-pi`, spiegando l'interazione tra l'agente host (`pi`), l'estensione Node.js/TypeScript e il subagent CLI (`agy`).
+`agy-pi` collega pi al gateway OAuth Antigravity e alla CLI `agy`.
----
-
-## Architettura dei Componenti
-
-L'estensione `agy-pi` fa da ponte tra l'ambiente di esecuzione di `pi` e la CLI `agy` / API Google Gemini:
-
-```mermaid
-sequenceDiagram
- autonumber
- actor User as Utente / Tastiera (F12)
- participant PI as pi Agent Core
- participant EXT as agy-pi (extensions/index.ts)
- participant FFMPEG as ffmpeg & Audio Subsystem
- participant AGY as agy CLI (Antigravity)
- participant GEMINI as Google Gemini API
-
- rect rgb(30, 40, 60)
- note over User, EXT: Workflow Vocale F12
- User->>EXT: Pressione F12 (Start Record)
- EXT->>FFMPEG: Spawn ffmpeg (pulse -> wav 16kHz)
- EXT->>User: Audio sound "start" (aevalsrc)
- User->>EXT: Pressione F12 (Stop Record)
- EXT->>FFMPEG: SIGINT & optimizeAudio (silenceremove)
- EXT->>GEMINI: POST generateContent (Audio Opus/OGG + STT)
- GEMINI-->>EXT: Trascrizione testuale
- EXT->>EXT: Legge editorText & unisce contesto sessione
- EXT->>AGY: Interpretation prompt via executeAgy()
- AGY-->>EXT: Testo interpretato finale
- EXT->>PI: sendUserMessage(finalText)
- end
-
- rect rgb(40, 50, 30)
- note over PI, AGY: Tool Execution Workflow
- PI->>EXT: Execute Tool (es. agy_generate)
- EXT->>EXT: buildContextBlock() [Env + Memory + Web]
- EXT->>AGY: execFileAsync(agy -p prompt --output-format json)
- AGY-->>EXT: JSON Output & IMAGE_PATH
- EXT-->>PI: ToolResult { content, details }
- end
+```text
+pi → extension index.ts → Antigravity OAuth / agy CLI → modelli disponibili
```
----
+## Componenti
-## Moduli Funzionali
+- **CLI wrapper**: esegue `agy` per chat, immagini e video.
+- **Client Antigravity diretto**: usa OAuth e gli endpoint cloudcode-pa per provider, modelli, audio F12 e `antigravity_chat`.
+- **Pipeline audio**: `ffmpeg` registra/ottimizza, poi invia `inlineData` audio ad Antigravity.
+- **Context injection**: aggiunge ambiente, stato sessione e memoria locale.
+- **Persistenza**: configurazione in `~/.config/agy-pi/config.json`; conversazioni e token sono gestiti dal client Antigravity.
-### 1. Engine di Gestione dello Stato e Persistenza
-
-L'estensione mantiene lo stato sia in memoria sia su disco attraverso diverse strutture dati:
-
-- **Conversazione agy Multi-Turno**:
- - `~/.agy-chat/conversation_id`: File di testo contenente l'ID dell'ultima conversazione attiva.
- - `~/.gemini/antigravity-cli/conversations/`: Cartella gestita dalla CLI `agy` contenente i file SQLite `.db` con la cronologia dei turni.
-- **Configurazione Persistente (`AgyConfig`)**:
- - `~/.config/agy-pi/config.json`: File protetto (permessi `0600`) che mantiene le preferenze di sistema (modello di default, backend STT/TTS, token budget, ecc.).
-- **Memoria Durevole (`DurableMemory`)**:
- - `~/.config/agy-pi/memory.json`: Archivio dei fatti rilevanti (obiettivi, decisioni, convenzioni, questioni aperte) aggiornato in modo automatico.
-
-### 2. Wrapper di Esecuzione `executeAgy()`
-
-Tutte le interazioni con il binario `agy` passano attraverso la funzione asincrona `executeAgy()`:
-
-```typescript
-interface AgyExecOptions {
- prompt: string;
- mode?: "chat" | "image" | "analyze";
- model?: string;
- effort?: "low" | "medium" | "high";
- newConversation?: boolean;
- stateless?: boolean;
- addDirs?: string[];
- filePaths?: string[];
- yolo?: boolean;
- timeoutMs?: number;
- outputDir?: string;
- signal?: AbortSignal;
- contextBlock?: string;
-}
-```
-
-#### Risoluzione Bug Output Format (`--output-format json`)
-Quando `agy` viene eseguito in modalità non interattiva (piped/redirected), la CLI non scrive su `stdout` grezzo. Per superare questo limite, `executeAgy` aggiunge automaticamente il flag `--output-format json`, parsando la proprietà `.response` dell'oggetto ritornato.
-
----
-
-## Ciclo di Vita di una Chiamata Tool
-
-1. **Invocazione del Tool**: `pi` seleziona uno dei 13 tool registrati ed esegue il metodo `execute()`.
-2. **Costruzione del Prompt**: Il tool trasforma i parametri strutturati in un prompt narrativo (es. aggiungendo `IMAGE_PATH` o clausole `Keep+Change+Add+Render`).
-3. **Context Injection**: Se abilitato (`contextInject = true`), viene inserito il blocco `` preparato da `buildContextBlock()`.
-4. **Esecuzione CLI / API**: Invocazione del binario `agy` con gestione dei timeout (3 min per chat/analisi, 5 min per immagini).
-5. **Post-Processing Asset**: Se viene generata un'immagine, la funzione `extractImagePath()` rileva il percorso dall'output e `copyImage()` lo duplica eventualmente nella cartella di destinazione `outputDir`.
-6. **Aggiornamento Stato**: Se la chiamata è stateful, l'ID della conversazione viene aggiornato in `~/.agy-chat/conversation_id`.
-7. **Ritorno a pi**: Restituzione del risultato formattato secondo la specifica di `pi` (`{ content, details }`).
+L'estensione non invia richieste a Google AI Studio/Gemini API tramite API key e non conserva tali chiavi.
\ No newline at end of file
diff --git a/docs/cli-wrapper.md b/docs/cli-wrapper.md
index 20ef67c..4861f6f 100644
--- a/docs/cli-wrapper.md
+++ b/docs/cli-wrapper.md
@@ -1,70 +1,11 @@
-# Wrapper Script CLI (`bin/agy-chat.sh`)
+# Wrapper CLI `agy`
-Oltre all'estensione TypeScript per `pi`, il repository include lo script di utilità Bash standalone **`bin/agy-chat.sh`**.
+L'estensione usa il binario `agy` per chat, immagini e video. Il binario deve essere installato e autenticato tramite OAuth Antigravity.
----
+Configurazione utile:
-## Scopo dello Script
+- `agyBin`: percorso al binario;
+- `agyDefaultModel`: modello predefinito;
+- `agyTimeoutMs`: timeout chiamate.
-Lo script permette di eseguire conversazioni multi-turno con `agy` direttamente dal terminale shell, mantenendo la persistenza dell'ID conversazione in `~/.agy-chat/conversation_id` esattamente come fa l'estensione Node.js.
-
----
-
-## Sintassi & Parametri
-
-```bash
-./bin/agy-chat.sh [OPZIONI] "PROMPT"
-```
-
-### Tabella dei Parametri
-
-| Opzione | Descrizione |
-|---|---|
-| `"prompt"` | Il prompt o task da inviare ad `agy` |
-| `--new` | Forza l'avvio di una nuova conversazione (ignora l'ID salvato) |
-| `--reset` | Elimina il file di stato `conversation_id` ed esce |
-| `--id` | Stampa l'ID della conversazione attualmente attiva ed esce |
-| `--list` | Elenca le conversazioni recenti salvate nel database SQLite di `agy` |
-| `--resume ` | Riprende una conversazione specifica indicando il suo ID |
-| `--model ` | Specifica il modello LLM (es. `--model "Gemini 3.1 Pro (High)"`) |
-| `--effort ` | Livello di reasoning: `low` \| `medium` \| `high` |
-| `--add-dir ` | Aggiunge una directory al contesto di lavoro di `agy` |
-| `--yolo` | Passa il flag `--dangerously-skip-permissions` ad `agy` |
-
----
-
-## Esempi d'Uso
-
-### 1. Avviare o Continuare una Conversazione
-```bash
-./bin/agy-chat.sh "Spiegami la differenza tra REST e GraphQL"
-```
-
-### 2. Continuare la Conversazione (Multi-Turno)
-```bash
-./bin/agy-chat.sh "Puoi fare un esempio in TypeScript per la risposta precedente?"
-```
-
-### 3. Forzare una Nuova Conversazione con Modello Specifico
-```bash
-./bin/agy-chat.sh --new --model "Gemini 3.1 Pro (High)" --effort high "Disegna l'architettura di un sistema a microservizi"
-```
-
-### 4. Gestione dello Stato
-```bash
-# Mostra l'ID attivo
-./bin/agy-chat.sh --id
-
-# Elenca tutte le conversazioni salvate
-./bin/agy-chat.sh --list
-
-# Ripristina lo stato
-./bin/agy-chat.sh --reset
-```
-
----
-
-## Variabili d'Ambiente Supportate
-
-- **`AGY_CHAT_STATE`**: Directory del file di stato (default: `~/.agy-chat`).
-- **`AGY_BIN`**: Percorso dell'eseguibile `agy` (default: `agy` presente nel `PATH`).
+L'audio non passa dal wrapper CLI: F12 e `agy_transcribe` usano il client Antigravity diretto con `inlineData`.
\ No newline at end of file
diff --git a/docs/commands-keybindings.md b/docs/commands-keybindings.md
index 2ab0f48..61897ff 100644
--- a/docs/commands-keybindings.md
+++ b/docs/commands-keybindings.md
@@ -1,89 +1,18 @@
-# Comandi Slash & Scorciatoie da Tastiera
+# Comandi e scorciatoie
-`agy-pi` mette a disposizione un ricco set di comandi slash interattivi e scorciatoie globali per interagire con l'estensione direttamente dal terminale di `pi`.
+## Comandi
----
+- `/agy `: invia un task al subagent Antigravity.
+- `/agy:config [get|set|reset]`: gestisce la configurazione locale.
+- `/agy:status`: mostra stato di agy, modello e pipeline audio.
+- `/agy:record`: avvia/ferma la registrazione vocale.
+- `/agy:record:stop`: ferma una registrazione in corso.
+- `/agy:record:cancel`: annulla una registrazione in corso.
+- `/agy:refresh-models`: aggiorna il catalogo Antigravity.
-## Comandi Slash (Slash Commands)
+Non esistono comandi per inserire chiavi Gemini API, per TTS interno o per generazione immagini verificata.
-### Gestione Conversazione agy
+## Scorciatoie
-| Comando | Descrizione | Sintassi / Esempio |
-|---|---|---|
-| `/agy` | Invia un prompt diretto al subagent `agy` | `/agy Spiega la teoria della relatività` |
-| `/agy:new` | Forza la creazione di una nuova conversazione pulita | `/agy:new` |
-| `/agy:list` | Mostra l'ID della conversazione agy attiva | `/agy:list` |
-| `/agy:reset` | Azzera l'ID conversazione salvato in locale | `/agy:reset` |
-
----
-
-### Configurazione & Diagnostica TUI
-
-#### `/agy:config`
-Gestisce le opzioni di configurazione salvate in `~/.config/agy-pi/config.json`.
-
-- `/agy:config`: Mostra l'elenco completo di tutte le opzioni con i valori attuali e le descrizioni.
-- `/agy:config get `: Stampa il valore di una singola chiave.
-- `/agy:config set `: Modifica il valore di una chiave e salva su disco.
-- `/agy:config reset`: Ripristina le impostazioni predefinite di fabbrica.
-
-#### `/agy:key` (Dialog TUI Overlay)
-Apre una finestra di dialogo grafica in sovrimpressione nel terminale (`pi-tui` overlay) per inserire o modificare la chiave API Gemini:
-
-```
-┌────────────────────────────────────────────────────────┐
-│ 🔑 Chiave API Gemini │
-│ Attuale: AIza...yfDY │
-│ │
-│ Nuova chiave: ••••••••••••••••••••••••••••••••••••• │
-│ │
-│ Digita la chiave • Enter conferma • Esc annulla │
-└────────────────────────────────────────────────────────┘
-```
-- Input mascherato per prevenire il leak della chiave sul terminale o nella cronologia.
-- Salva contemporaneamente la chiave in `~/.config/agy-pi/config.json` e in `~/.agy-chat/gemini-key` (permessi `0600`).
-
-#### `/agy:status` (Diagnostica TUI Overlay)
-Mostra una dashboard grafica centrata con lo stato di salute dell'estensione:
-
-```
-┌────────────────────────────────────────────────────────┐
-│ 📊 Stato agy-pi │
-│ │
-│ Binario agy: trovato /home/enne2/.local/bin/agy │
-│ Versione: agy version 1.4.2 │
-│ Chiave Gemini: ✅ valida │
-│ Chiave (mask): AIza...yfDY │
-│ Modello attivo: Gemini 3.1 Pro (High) │
-│ Backend STT: gemini │
-│ Notifiche TTS: true │
-│ │
-│ Ultimo errore: (nessuno) │
-│ │
-│ Enter o Esc per chiudere │
-└────────────────────────────────────────────────────────┘
-```
-
----
-
-### Registrazione Vocale & TTS
-
-| Comando | Descrizione |
-|---|---|
-| `/agy:record` | Avvia o ferma la registrazione vocale (equivalente al tasto **F12**). |
-| `/agy:record:stop` | Ferma la registrazione attiva ed elabora l'audio. |
-| `/agy:record:cancel` | Annulla la registrazione corrente senza inviare prompt. |
-| `/agy:speak ` | Sintetizza ed esegue l'audio del testo specificato via Gemini TTS. |
-| `/agy:vocal [on\|off\|status]` | Attiva (`on`), disattiva (`off`) o mostra lo stato (`status`) del feedback vocale automatico TTS. |
-
----
-
-## Scorciatoie da Tastiera (Keybindings)
-
-### Tasto `F12` — Registrazione Vocale Toggle
-- **Primo tocco (`F12`)**: Avvia la registrazione audio tramite `ffmpeg`, riproduce il suono sci-fi *Power Up* e mostra la barra di stato live `🔴 REGISTRAZIONE... MM:SS / MM:SS`.
-- **Secondo tocco (`F12`)**: Interrompe la registrazione, riproduce il suono *Deactivate*, ottimizza l'audio (soppressione silenzio), invia la traccia a Gemini per la trascrizione e trasmette il prompt risultante a `pi`.
-
-### Tasto `Ctrl+Esc` — Annullamento Registrazione
-- Durante una registrazione audio attiva, premere `Ctrl+Esc` abortisce immediatamente il processo `ffmpeg`, rimuove il file temporaneo, riproduce il tono acustico di cancellazione e cancella l'indicatore di stato.
-- `Esc` da solo è riservato alla combinazione built-in di pi (`app.interrupt`), quindi l'estensione usa `Ctrl+Esc` per evitare il conflitto.
+- `F12`: avvia/ferma la registrazione vocale Antigravity.
+- `Ctrl+Esc`: annulla la registrazione attiva.
diff --git a/docs/configuration.md b/docs/configuration.md
index 62fb764..0f197ae 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -1,87 +1,29 @@
-# Sistema di Configurazione
+# Configurazione
-L'estensione `agy-pi` implementa un sistema di configurazione gerarchico e persistente, progettato per garantire massima sicurezza ed elasticità sia in ambienti locali che server.
+`agy-pi` salva la configurazione in `~/.config/agy-pi/config.json` (permessi `0600`). Non richiede né legge chiavi Google AI Studio: chat, audio e multimodalità usano l'account OAuth Antigravity di `agy`.
----
+## Opzioni
-## File di Configurazione (`config.json`)
+| Chiave | Default | Descrizione |
+|---|---:|---|
+| `sttMaxDuration` | `120` | Durata massima della registrazione F12, in secondi. |
+| `voiceModel` | `gemini-3.7-flash-medium` | Modello Antigravity multimodale usato per audio e trascrizione. |
+| `agyBin` | `agy` | Percorso del binario `agy`. |
+| `agyDefaultModel` | — | Modello predefinito delle chiamate CLI. |
+| `agyTimeoutMs` | `180000` | Timeout delle chiamate CLI, in millisecondi. |
+| `contextInject` | `true` | Inietta ambiente, stato sessione e memoria locale. |
+| `contextTokens` | `1500` | Budget del contesto iniettato. |
+| `vocalPlanningMode` | `true` | Mostra il piano vocale prima dell'invio a pi. |
-Le impostazioni vengono memorizzate nel file JSON:
-`~/.config/agy-pi/config.json`
+Gestione interattiva:
-!!! security "Sicurezza e Permessi"
- Il file `config.json` viene creato con permessi restrittivi `0600` (leggibile e modificabile unicamente dall'utente proprietario del processo) per prevenire l'accesso non autorizzato alle chiavi API memorizzate.
-
----
-
-## Gerarchia delle Risoluzione Chiavi
-
-Quando un modulo o un tool dell'estensione richiede un valore di configurazione (es. `geminiApiKey`), l'estensione segue questa priorità:
-
-```mermaid
-graph TD
- ENV[1. Variabili d'Ambiente System/Shell] -->|Se assente| CONFIG[2. File ~/.config/agy-pi/config.json]
- CONFIG -->|Se assente| SECRET[3. File ~/.agy-chat/gemini-key]
- SECRET -->|Se assente| DEFAULT[4. Valore Default di Fabbrica CONFIG_DEFAULTS]
+```text
+/agy:config
+/agy:config get voiceModel
+/agy:config set voiceModel gemini-3.7-flash-medium
+/agy:config reset
```
----
+## Requisito di autenticazione
-## Tabella delle Opzioni di Configurazione
-
-| Chiave | Tipo | Default | Descrizione |
-|---|---|---|---|
-| `geminiApiKey` | `string` | `undefined` | Chiave API Google Gemini (usata per STT, TTS e Web Search grounding). |
-| `enne2ApiKey` | `string` | `undefined` | Bearer token opzionale per l'autenticazione verso il proxy `ai.enne2.net`. |
-| `sttBackend` | `string` | `"gemini"` | Backend per la trascrizione vocale: `"gemini"` \| `"enne2"`. |
-| `sttUrl` | `string` | `"https://ai.enne2.net"` | URL endpoint base per il backend STT enne2. |
-| `sttModel` | `string` | `"gemma4:E4B"` | Identificativo modello per il backend STT enne2. |
-| `sttMaxDuration` | `number` | `120` | Durata massima della registrazione audio in secondi. |
-| `ttsBackend` | `string` | `"gemini"` | Backend per la sintesi vocale: `"gemini"` \| `"enne2"`. |
-| `ttsNotify` | `boolean` | `true` | Abilita o disabilita le notifiche acustiche/vocali automatiche a fine trascrizione. |
-| `ttsModel` | `string` | `"gemini-2.5-flash-preview-tts"` | Modello Gemini dedicato al Text-To-Speech. |
-| `agyBin` | `string` | `"agy"` | Percorso assoluto o nome del binario eseguibile `agy`. |
-| `agyDefaultModel` | `string` | `undefined` | Modello predefinito per le chiamate ad `agy` (es. `Gemini 3.1 Pro (High)`). |
-| `agyTimeoutMs` | `number` | `180000` | Timeout generale in millisecondi per l'esecuzione di `agy` (3 minuti). |
-| `contextInject` | `boolean` | `true` | Iniezione automatica del contesto dell'agente nel prompt `agy`. |
-| `contextTokens` | `number` | `1500` | Budget massimo di token allocato per il blocco di contesto iniettato. |
-| `webSearch` | `string` | `"auto"` | Modalità ricerca web (Strada A): `"auto"` \| `"on"` \| `"off"`. |
-| `vocalPlanningMode` | `boolean` | `true` | Workflow vocale a 2 fasi: piano + conferma in overlay prima di eseguire. |
-
----
-
-## Esempio di File `config.json`
-
-```json
-{
- "sttBackend": "gemini",
- "sttUrl": "https://ai.enne2.net",
- "sttModel": "gemma4:E4B",
- "sttMaxDuration": 120,
- "ttsBackend": "gemini",
- "ttsNotify": true,
- "ttsModel": "gemini-2.5-flash-preview-tts",
- "agyTimeoutMs": 180000,
- "contextInject": true,
- "contextTokens": 1500,
- "webSearch": "auto",
- "vocalPlanningMode": true,
- "geminiApiKey": "AIzaSyD-EXAMPLE_KEY_STRING_HERE"
-}
-```
-
----
-
-## Variabili d'Ambiente Mappate
-
-Le seguenti variabili di ambiente sovrascrivono la configurazione quando presenti:
-
-```bash
-export GEMINI_API_KEY="AIzaSy..."
-export ENNE2_API_KEY="sk-..."
-export AGY_STT_BACKEND="enne2" # oppure "gemini"
-export AGY_STT_URL="https://ai.enne2.net"
-export AGY_STT_MODEL="gemma4:E4B"
-export AGY_BIN="/home/utente/.local/bin/agy"
-export AGY_TTS_NOTIFY="0" # disabilita notifiche TTS
-```
+Accedi una volta tramite `agy`; il token OAuth Antigravity è gestito dal client. `/agy:status` mostra binario, versione, modello e stato della pipeline audio.
diff --git a/docs/context-injection.md b/docs/context-injection.md
index 8e973ff..00c515c 100644
--- a/docs/context-injection.md
+++ b/docs/context-injection.md
@@ -1,89 +1,13 @@
-# Context Injection Engine (Fase 2)
+# Context injection
-L'estensione `agy-pi` include un motore avanzato di **Context Injection** che costruisce in tempo reale un blocco strutturato di contesto prima di ogni prompt inviato ad `agy`.
-
----
-
-## Architettura del Blocco di Contesto
-
-Il blocco di contesto rispetta le best practice di **Context Engineering** per i modelli di grandi dimensioni (LLM):
-- Utilizzo di tag XML semantici.
-- Posizionamento della richiesta utente **sempre alla fine** del prompt per contrastare l'effetto *"Lost in the Middle"* (Liu et al., TACL 2024).
-- Gestione rigida del token budget (default 1500 token).
-
-```xml
-[CONTEXT_AGENTE] Contesto ambiente e stato corrente per la risposta. Usa solo se pertinente; la richiesta dell'utente è sotto.
+Quando `injectContext` è attivo, agy-pi aggiunge al prompt del subagent Antigravity un blocco compatto:
+```text
-cwd: /home/utente/progetto
-os: linux x64
-time: 2026-08-10T17:15:00.000Z
-git_branch: main (dirty)
-modified: src/index.ts | package.json
-
-
-Utente: Analizza la struttura di questo componente...
-Assistente: Ho controllato i file...
-
-
-goal: Implementare il nuovo modulo di autenticazione
-decisions: Usare token JWT con scadenza 1h | Database Postgres
-conventions: TypeScript strict mode
-
-
-
-[Risultati ricerca web Gemini grounding su topic pertinente]
-
-
---------------------------------
-[ RICHIESTA UTENTE ]
```
----
+Il budget è configurabile con `contextTokens` (default 1500). Il testo dell'utente viene mantenuto dopo il blocco di contesto.
-## I 4 Moduli di Contesto
-
-### 1. ``
-Estrae le informazioni dell'ambiente di lavoro corrente:
-- Directory di lavoro attiva (`cwd`).
-- Sistema operativo e architettura.
-- Data e ora correnti (ISO 8601).
-- Stato Git: branch corrente, indicatore `dirty` e lista fino agli ultimi 8 file modificati.
-
-### 2. ``
-Recupera gli ultimi 3 messaggi dell'utente dalla sessione di `pi`. Il testo viene compresso e sanitizzato per evitare il bloat di token senza perdere il filo della conversazione.
-
-### 3. `` (Memoria Persistente Automatica)
-Gestita tramite il file `~/.config/agy-pi/memory.json`:
-- Registra l'obiettivo primario del progetto (`goal`), le decisioni prese (`decisions`), le convenzioni di codice (`conventions`) e le questioni aperte (`openQuestions`).
-- **Auto-Update**: Quando la conversazione in `pi` supera i 24 turni, l'estensione estrae automaticamente i punti chiave e aggiorna `memory.json` per mantenere il contesto senza dover riinviare l'intero transcript.
-
-### 4. `` (Strada A: Ricerca Web Integrata)
-Quando `webSearch` è impostato su `"auto"` o `"on"`, l'estensione valuta il prompt con la funzione euristica `needsWebSearch()`:
-
-#### Filtri Negativi (Non effettuano ricerca web):
-- Generazione ed editing di immagini (`agy_generate`, `agy_edit`, ecc.).
-- Task di codice locale o refactoring.
-- Comandi di gestione della conversazione o sintesi locale.
-
-#### Trigger Positivi (Attivano la ricerca web):
-- Riferimenti alla data/ora corrente (*"oggi"*, *"ultime notizie"*, *"2026"*).
-- Domande su versioni di pacchetti, dipendenze o release note.
-- Confronti tra tecnologie (*"differenza tra X e Y"*, *"migliore alternativa"*).
-- Notizie o fatti di attualità.
-
-La ricerca viene effettuata via API Gemini sfruttando la funzionalità di **Google Search Grounding**, ed i risultati vengono inseriti nel tag ``.
-Un sistema di cache temporale evita ricerche identiche ripetute entro 60 secondi.
-
----
-
-## Gestione del Token Budget (Token Cap)
-
-Il budget predefinito è di **1500 token**, suddiviso tra i diversi moduli:
-
-- `environment_snapshot`: max ~300 token.
-- `session_state`: max ~500 token.
-- `durable_memory`: max ~400 token.
-- `web_context`: max ~300 token.
+L'estensione non esegue ricerche web autonome. Per dati correnti, notizie o documentazione aggiornata il modello principale deve usare gli strumenti web disponibili in pi.
\ No newline at end of file
diff --git a/docs/index.md b/docs/index.md
index 0ee4810..84ef77d 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,77 +1,10 @@
# Panoramica di agy-pi
-
-
-
+`agy-pi` integra Antigravity in pi per delega testuale e multimodale. Il client usa il login OAuth di `agy` e mette a disposizione Gemini, Claude e gli altri modelli presenti nel catalogo Antigravity.
-**`agy-pi`** è un'estensione avanzata per **pi** (`@earendil-works/pi-coding-agent`) che integra **Google Antigravity CLI (`agy`)** e le API di **Gemini multimodale** come subagent autonomo e assistente multimodale dentro l'ambiente di pi.
+Funzioni: chat, immagini, video, analisi immagini, trascrizione audio e workflow vocale F12. Le operazioni audio passano esclusivamente da Antigravity; l'estensione non richiede una chiave Gemini API separata.
----
-
-## Il Problema & La Soluzione
-
-| Sfida | Soluzione offertata da `agy-pi` |
-|---|---|
-| **Limitazione Text-Only di pi** | `agy-pi` agisce da ponte multimodale: pi delega ad `agy` l'elaborazione di immagini, audio, video e compiti di ragionamento profondo. |
-| **Generazione & Editing Immagini** | Implementa formule narrative strutturate ([Soggetto]+[Azione]+[Stile], Keep+Change+Add+Render, Inpainting, Style Transfer, Composizione Multi-Immagine, Character Consistency). |
-| **Input Vocale Senza Mani** | Scorciatoia **F12** con registrazione microfono, indicatore TUI live, soppressione silenzio, feedback sonori sci-fi e trascrizione nativa via Gemini/enne2. |
-| **Multimodalità Ibrida (Voce + Tastiera)** | Integra automaticamente il testo presente nel campo editor di pi con il messaggio vocale registrato. |
-| **Context Lost in the Middle** | Sistema di Context Injection in 4 fasi (``, ``, ``, ``) con token cap ed euristiche anti-ridondanza. |
-
----
-
-## Caratteristiche Principali
-
-```mermaid
-graph TD
- PI[pi Coding Agent] -->|Tool Call / Slash Command| EXT[agy-pi Extension]
-
- subgraph Core Features
- EXT --> TOOLS[14 Specialized Tools]
- EXT --> VOCAL[Workflow Vocale F12]
- EXT --> CTX[Context Injection Engine]
- EXT --> TUI[TUI Overlays /agy:key & /agy:status]
- end
-
- TOOLS --> AGY_CLI[Google Antigravity CLI agy]
- VOCAL --> GEMINI_STT[Gemini STT / enne2 STT]
- VOCAL --> GEMINI_TTS[Gemini TTS / Feedbacks Sonori]
- CTX --> MEMORY[Memory JSON & Web Search Grounding]
-```
-
-1. **Subagent Multimodale Avanzato**: Espone 14 strumenti specializzati riconosciuti dall'LLM di pi per generare, modificare ed analizzare asset multimediali, inclusa la **generazione verificata iterativa** (`agy_create_verified`) con verifica visione + OpenCV dinamico.
-2. **Sistema di Configurazione Persistente**: Configurazione via `/agy:config` salvata con permessi restrittivi `0600` in `~/.config/agy-pi/config.json`.
-3. **Interfaccia Grafica TUI**: Dialoghi interattivi in sovrimpressione (`/agy:key` per mascherare e gestire la chiave API Gemini e `/agy:status` per la diagnostica di sistema).
-4. **Modulo Vocale Sci-Fi Integrato**: Registrazione ad alte prestazioni via `ffmpeg`, rilevamento automatico del parlato (`volumedetect`), compressione `libopus`/`libmp3lame` e feedback acustici sintetizzati proceduralmente.
-
----
-
-## Requisiti e Dipendenze
-
-- **Node.js**: >= 18.x / v22.x
-- **pi-coding-agent**: `@earendil-works/pi-coding-agent`
-- **Google Antigravity CLI (`agy`)**: Installato e autenticato in locale (es. `~/.local/bin/agy`).
-- **ffmpeg**: Necessario per il recording del microfono, la rimozione del silenzio e i feedback sonori procedurali.
-- **Utilità Audio System**: `paplay` (PulseAudio), `aplay` (ALSA) o `ffplay`.
-
----
-
-## Installazione Rapida
-
-```bash
-# Installazione da repository locale
-pi install /percorso/a/agy-pi
-
-# Oppure test dinamico senza installazione permanente
-pi -e /percorso/a/agy-pi
-```
-
-Configura la chiave API Gemini per la trascrizione vocale e la ricerca web:
-
-```bash
-# Tramite overlay grafico dentro pi:
-/agy:key
-
-# Oppure via linea di comando:
-/agy:config set geminiApiKey AIzaSy...
-```
+- Configurazione: [configuration.md](configuration.md)
+- Tools: [tools.md](tools.md)
+- Workflow vocale: [vocal-workflow.md](vocal-workflow.md)
+- Comandi: [commands-keybindings.md](commands-keybindings.md)
diff --git a/docs/tools.md b/docs/tools.md
index 86f781f..7342659 100644
--- a/docs/tools.md
+++ b/docs/tools.md
@@ -1,196 +1,25 @@
-# Strumenti Registrati (Tools)
+# Strumenti registrati
-L'estensione `agy-pi` espone **14 strumenti (tools)** all'agente principale `pi`. Ciascun tool è definito tramite schemi strict `TypeBox` e fornisce istruzioni dettagliate al modello su come e quando utilizzarlo.
+| Tool | Scopo |
+|---|---|
+| `agy` | Subagent Antigravity per chat e ragionamento multi-turno. |
+| `agy_generate` | Generazione immagini. |
+| `agy_edit` | Editing controllato di un'immagine. |
+| `agy_inpaint` | Modifica localizzata. |
+| `agy_style_transfer` | Trasferimento di stile. |
+| `agy_compose` | Composizione di più immagini. |
+| `agy_character` | Consistenza di personaggio/oggetto. |
+| `agy_analyze` | Analisi immagine. |
+| `agy_transcribe` | Trascrizione audio tramite Antigravity OAuth. |
+| `agy_video` | Analisi di video. |
+| `agy_models` | Elenco modelli Antigravity disponibili. |
+| `agy_conversation` | Stato della conversazione agy. |
+| `antigravity_chat` | Richiesta diretta al gateway Antigravity. |
----
+## `agy_transcribe`
-## Tabella Riassuntiva
+Accetta `filePath` e, opzionalmente, `language`. Converte WAV in Opus/OGG quando utile e invia l'audio al modello configurato in `voiceModel` tramite Antigravity. Non usa chiavi API esterne né fallback a provider diversi.
-| Tool | Scopo Principale | Modalità | Timeout Default |
-|---|---|---|---|
-| [`agy`](#1-agy) | Subagent generico multi-turno (chat, image, analyze) | Dynamic | 180s |
-| [`agy_generate`](#2-agy_generate) | Generazione immagini strutturata | Image | 300s |
-| [`agy_edit`](#3-agy_edit) | Editing immagini controllato (Keep+Change+Add) | Image | 300s |
-| [`agy_inpaint`](#4-agy_inpaint) | Inpainting / In-place editing di zone specifiche | Image | 300s |
-| [`agy_style_transfer`](#5-agy_style_transfer) | Trasferimento di stile artistico | Image | 300s |
-| [`agy_compose`](#6-agy_compose) | Fusione / Composizione di più immagini | Image | 300s |
-| [`agy_character`](#7-agy_character) | Consistenza del personaggio tra generazioni | Image | 300s |
-| [`agy_analyze`](#8-agy_analyze) | Analisi file multimediali (immagini, audio, video) | Analyze | 180s |
-| [`agy_transcribe`](#9-agy_transcribe) | Trascrizione audio nativa via Gemini/enne2 API | API | 120s |
-| [`agy_video`](#10-agy_video) | Analisi e ispezione scene/codec file video | Analyze | 180s |
-| [`agy_tts`](#11-agy_tts) | Sintesi vocale Text-To-Speech nativa Gemini | API | 60s |
-| [`agy_models`](#12-agy_models) | Lista modelli disponibili su agy CLI | CLI | 30s |
-| [`agy_conversation`](#13-agy_conversation) | Ispezione e gestione stato conversazione | System | Instant |
-| [`agy_create_verified`](#14-agy_create_verified) | Generazione immagine iterativa con verifica | Image | 300s |
+## Contesto
----
-
-## Dettaglio degli Strumenti
-
-### 1. `agy`
-Tool generico per delegare un task ad `agy`. Mantiene lo stato della conversazione (multi-shot reasoning).
-
-- **Parametri**:
- - `prompt` (`string`, obbligatorio): Il task da inviare ad agy.
- - `mode` (`"chat" | "image" | "analyze"`, opzionale): Tipo di operazione (default `"chat"`).
- - `model` (`string`, opzionale): Modello agy (es. `Gemini 3.1 Pro (High)`).
- - `effort` (`"low" | "medium" | "high"`, opzionale): Livello di reasoning.
- - `newConversation` (`boolean`, opzionale): Se `true`, azzera lo stato e parte da zero.
- - `addDir` (`string`, opzionale): Cartella da aggiungere al contesto di workspace agy.
- - `filePath` (`string`, opzionale): File da allegare.
- - `yolo` (`boolean`, opzionale): Abilita `--dangerously-skip-permissions`.
- - `injectContext` (`boolean`, opzionale): Abilita la context injection (default `true`).
- - `webSearch` (`boolean`, opzionale): Override ricerca web per questo prompt.
-
----
-
-### 2. `agy_generate`
-Generazione da zero di immagini basata su formule narrative trasparenti per il modello Gemini Image Generation.
-
-- **Formula costruita**: `[Soggetto] + [Azione] + [Luogo] + [Composizione] + [Stile] + [Illuminazione] + [Aspect Ratio]`
-- **Parametri principali**:
- - `subject` (`string`, obbligatorio): Descrizione del soggetto primario.
- - `action`, `location`, `composition`, `style`, `lighting`, `aspectRatio` (`string`, opzionali).
- - `text` (`string`, opzionale): Testo grafico da incorporare.
- - `negative` (`string`, opzionale): Elementi da evitare.
- - `outputDir` (`string`, opzionale): Directory in cui salvare l'immagine generata.
-
----
-
-### 3. `agy_edit`
-Modifica controllata di un'immagine esistente basata sulla regola **Keep + Change + Add + Render**.
-
-- **Best Practice Gemini**: Viene apportata una sola modifica per turno per evitare degrado dell'immagine base.
-- **Parametri principali**:
- - `baseImage` (`string`, obbligatorio): Percorso dell'immagine sorgente.
- - `keep` (`string`, obbligatorio): Elementi da mantenere inalterati (posa, luci, volto, ecc.).
- - `change` (`string`, obbligatorio): La modifica principale richiesta.
- - `add` (`string`, opzionale): Nuovi elementi da aggiungere.
- - `render` (`string`, opzionale): Target estetico di output (es. *"fotorealismo editoriale"*).
- - `preserveAspectRatio` (`boolean`, opzionale): Preserva la proporzione originale.
-
----
-
-### 4. `agy_inpaint`
-Inpainting / Semantic Masking: modifica un elemento ben circoscritto senza alterare il contesto circostante.
-
-- **Parametri**:
- - `baseImage` (`string`, obbligatorio): Percorso immagine di partenza.
- - `target` (`string`, obbligatorio): Oggetto/area specifica da cambiare (es. *"la giacca del soggetto"*).
- - `replacement` (`string`, obbligatorio): Nuova descrizione dell'oggetto (es. *"un giubbotto in pelle nera"*).
- - `keepRest` (`string`, opzionale): Dettagli da preservare (default: *"tutto il resto"*).
-
----
-
-### 5. `agy_style_transfer`
-Trasferimento di stile artistico preservando la composizione e i volumi dell'immagine base.
-
-- **Parametri**:
- - `baseImage` (`string`, obbligatorio): Immagine sorgente.
- - `style` (`string`, obbligatorio): Stile di destinazione (es. *"Acquerello Impressionista"*, *"Cyberpunk Neon"*, *"Disegno Tecnico"*).
- - `preserve` (`string`, opzionale): Cosa mantenere (default: *"la composizione originale"*).
-
----
-
-### 6. `agy_compose`
-Combina da 2 a 14 immagini in un unico scatto armonioso.
-
-- **Parametri**:
- - `images` (`string[]`, obbligatorio): Elenco dei percorsi immagine.
- - `instruction` (`string`, obbligatorio): Istruzioni su quali elementi prendere da ciascuna immagine e come fonderli.
-
----
-
-### 7. `agy_character`
-Garantisce la consistenza visiva di un personaggio o oggetto attraverso più scatti.
-
-- **Parametri**:
- - `referenceImage` (`string`, obbligatorio): Immagine di riferimento con la fisionomia del personaggio.
- - `name` (`string`, obbligatorio): Nome/Token identificativo (es. *"Avatar-Elena"*).
- - `features` (`string`, obbligatorio): Tratti somatici/caratteristici immutabili.
- - `task` (`string`, obbligatorio): Nuova azione o contesto in cui inserire il personaggio.
-
----
-
-### 8. `agy_analyze`
-Analizza in dettaglio qualsiasi file multimediale supportato (immagini, tracce audio o video).
-
-- **Parametri**:
- - `filePath` (`string`, obbligatorio): Percorso del file.
- - `question` (`string`, opzionale): Domanda di analisi specifica.
- - `yolo` (`boolean`, opzionale): Auto-approvazione permessi CLI (obbligatorio per audio/video).
-
----
-
-### 9. `agy_transcribe`
-Esegue la trascrizione audio ad alta velocità direttamente tramite le API Gemini (`gemini-3.5-flash`) o il server proxy `enne2`.
-
-- **Parametri**:
- - `filePath` (`string`, obbligatorio): File audio (WAV, MP3, OGG, M4A).
- - `language` (`string`, opzionale): Lingua parlata (es. `"italiano"`).
-
----
-
-### 10. `agy_video`
-Ispezione approfondita di clip video: descrizione delle scene, analisi del movimento, verifica traccia audio, codec e risoluzione.
-
-- **Parametri**:
- - `filePath` (`string`, obbligatorio): Percorso del file video MP4/MOV/MKV.
- - `question` (`string`, opzionale): Quesito specifico sulla clip.
-
----
-
-### 11. `agy_tts`
-Converte un testo in parlato e lo riproduce in locale tramite l'API Gemini TTS (`gemini-2.5-flash-preview-tts`).
-
-- **Parametri**:
- - `text` (`string`, obbligatorio): Testo da sintetizzare.
- - `play` (`boolean`, opzionale): Riproduci subito l'audio (default `true`).
- - `outputDir` (`string`, opzionale): Cartella di salvataggio del file WAV risultante.
-
----
-
-### 12. `agy_models`
-Elenca i modelli LLM e Vision attualmente disponibili nell'installazione di `agy`.
-
----
-
-### 13. `agy_conversation`
-Strumento di utilità di sistema per ispezionare o azzerare lo stato della conversazione.
-
-- **Parametri**:
- - `action` (`"id" | "list" | "reset"`, obbligatorio): Action richiesta.
-
----
-
-### 14. `agy_create_verified`
-Genera un'immagine **conforme a requisiti specifici** tramite un loop iterativo
-self-contained: genera → analizza con visione → verifica con OpenCV dinamico →
-rigenera se necessario. Ideale quando l'orchestratore di `pi` **non ha visione**.
-
-Il loop (fino a `maxIterations`):
-
-```
-1. Genera immagine con agy (mode image) dai requisiti
-2. Analizza con visione testuale → giudice PASS/FAIL vs requisiti
-3. Verifica con OpenCV DINAMICO → script generato dall'LLM ad hoc
-4. Se FAIL → rigenera con prompt di correzione (ri-attacca immagine base)
-```
-
-La **verifica OpenCV è dinamica**: l'LLM genera a runtime uno script
-Python/OpenCV specifico per i requisiti dell'immagine (non metriche hardcodate),
-che restituisce un JSON standard:
-
-```json
-{"pass": true/false, "score": 0..1, "findings": [...], "errors": [...]}
-```
-
-La decisione di conformità combina visione PASS + OpenCV PASS; le `findings`
-OpenCV vengono iniettate nelle issue per guidare la rigenerazione.
-
-- **Parametri**:
- - `requirements` (`string`, obbligatorio): Requisiti precisi che l'immagine deve soddisfare.
- - `outputDir` (`string`, opzionale): Cartella di salvataggio dell'immagine finale.
- - `maxIterations` (`number`, opzionale): Limite iterazioni (default 3, max 5).
- - `useOpenCV` (`boolean`, opzionale): Esegue la verifica OpenCV dinamica (default `true`).
- - `model` (`string`, opzionale): Modello agy.
+`agy` può ricevere un blocco con ambiente, stato della sessione e memoria locale tramite `injectContext`. La ricerca web non è svolta dall'estensione: per informazioni aggiornate usa gli strumenti web dell'agente principale.
\ No newline at end of file
diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md
index 04a19de..6b6e30e 100644
--- a/docs/troubleshooting.md
+++ b/docs/troubleshooting.md
@@ -1,67 +1,23 @@
-# Troubleshooting & Diagnostica
+# Troubleshooting
-Guida alla risoluzione dei problemi comuni durante l'utilizzo dell'estensione `agy-pi`.
+## `/agy:status`
----
+Usa `/agy:status` per verificare il binario `agy`, la versione, il modello configurato, la pipeline audio Antigravity e l'ultimo errore dai log.
-## 1. Strumento Diagnostico Integrato (`/agy:status`)
+## Problemi comuni
-Prima di procedere con la ricerca manuale dei guasti, esegui il comando slash dentro `pi`:
+### `agy` non trovato
-```
-/agy:status
-```
+Installa il client e completa il login OAuth, oppure imposta `agyBin` con `/agy:config set agyBin `.
-L'overlay TUI diagnostico verificherà automaticamente:
-- Esistenza e versione dell'eseguibile `agy`.
-- Presenza e validità della chiave API Gemini.
-- Backend STT e stato notifiche TTS attivi.
-- Ultimo messaggio di errore registrato nei log di `agy`.
+### F12 non trascrive
----
+Verifica microfono/PulseAudio e `ffmpeg`. Se viene rilevato silenzio, registra di nuovo con volume più alto. Se Antigravity fallisce, controlla autenticazione e quota dell'account con `/agy:status`.
-## 2. Problemi Frequenti e Soluzioni
+### Immagine non trovata
-### A. Binario `agy` non trovato (`bash: agy: command not found`)
-- **Causa**: Il binario `agy` non si trova nel `PATH` di sistema o nell'ubicazione standard `~/.local/bin/agy`.
-- **Soluzione**: Imposta il percorso esplicito dell'eseguibile:
- ```
- /agy:config set agyBin /percorso/assoluto/a/agy
- ```
+Controlla l'output del tool agy e che la directory di destinazione sia scrivibile.
-### B. Registrazione vocale F12 fallita o senza audio
-- **Causa 1**: `ffmpeg` non è installato nel sistema.
- - **Soluzione**: Installa `ffmpeg` tramite il package manager della tua distribuzione (es. `sudo apt install ffmpeg`).
-- **Causa 2**: Nessun parlato rilevato (`audioHasSpeech` ritorna errore).
- - **Soluzione**: Verifica il microfono e alza il livello di guadagno di PulseAudio/ALSA.
-- **Causa 3**: Mancanza dell'utility di riproduzione audio PulseAudio (`paplay`).
- - **Soluzione**: Installa `pulseaudio-utils` oppure lascia che il sistema usi il fallback automatico `aplay` / `ffplay`.
+## Log
-### C. Errore API Gemini o Key Mancante (`HTTP 401` / `HTTP 403`)
-- **Causa**: La chiave API Gemini non è stata inserita o è scaduta.
-- **Soluzione**: Apri l'overlay grafico per configurare la chiave in modo sicuro:
- ```
- /agy:key
- ```
-
-### D. Immagine generata non trovata (`IMAGE_PATH` assente)
-- **Causa**: `agy` ha completato l'operazione ma non ha restituito il pattern `IMAGE_PATH` o il modello non ha generato un file su disco.
-- **Soluzione**: Aumenta il timeout di generazione portando `agyTimeoutMs` a 300000 (5 minuti) o imposta `stateless: true`.
-
-### E. Errore HTTP 413 (`Payload Too Large`) durante la trascrizione audio
-- **Causa**: File audio di grandi dimensioni inviato in formato WAV grezzo.
-- **Soluzione**: L'estensione converte automaticamente i WAV in formato **Opus/OGG** (`libopus` a 16kbps). Assicurati che `ffmpeg` sia compilato con supporto a `libopus` o `libmp3lame`.
-
----
-
-## 3. Ispezione dei Log
-
-I log dettagliati delle interazioni della CLI `agy` sono memorizzati in:
-
-`~/.gemini/antigravity-cli/log/`
-
-Per visualizzare l'ultimo errore di sistema:
-
-```bash
-ls -t ~/.gemini/antigravity-cli/log/* | head -1 | xargs tail -n 50
-```
+I log del client Antigravity sono sotto `~/.gemini/antigravity-cli/log`.
\ No newline at end of file
diff --git a/docs/vocal-workflow.md b/docs/vocal-workflow.md
index a298771..c9b365f 100644
--- a/docs/vocal-workflow.md
+++ b/docs/vocal-workflow.md
@@ -1,123 +1,20 @@
-# Workflow Vocale (Registrazione F12)
+# Workflow vocale F12
-L'estensione `agy-pi` trasforma `pi` in un assistente multimodale completo grazie all'integrazione di un workflow di input vocale nativo attivabile con il tasto **F12** o tramite il comando `/agy:record`.
+F12 avvia/ferma una registrazione. La pipeline usa esclusivamente il gateway OAuth Antigravity, anche quando il modello attivo nella sessione pi è DeepSeek, Claude o locale.
----
-
-## Diagramma di Flusso della Registrazione Vocale
-
-```mermaid
-flowchart TD
- A[Pressione F12] --> B{Recording in corso?}
- B -- No --> C[startRecording: Spawn ffmpeg]
- C --> D[Play Sound: Start PowerUp]
- C --> E[Avvia Timer TUI Status]
-
- B -- Yes --> F[stopRecording: Send SIGINT]
- F --> G[Play Sound: Stop Deactivate]
- F --> H[optimizeAudio: silenceremove ffmpeg]
- H --> I[audioHasSpeech: volumedetect check]
- I -- Parlato Assente --> J[Errore: Silenzio o volume basso]
- I -- Parlato Presente --> K[transcribeAudio: API Gemini / enne2]
- K --> L[Legge editorText da TUI]
- L --> M[Unisce Voce + Testo Editor + Contesto]
- M --> N[executeAgy: plan-first, genera PIANO]
- N --> N1{vocalPlanningMode?}
- N1 -- true --> N2[Overlay TUI: trascrizione + piano]
- N2 --> N3[Enter=Esegui | Esc=Annulla | E=Modifica | F12=Registra]
- N3 -- Enter --> O[Svuota editorText TUI]
- N3 -- Esc --> X[Annulla: playSound cancel]
- N1 -- false --> O
- O --> P[sendUserMessage prompt a pi]
- P --> Q[Play Sound: Done Chime & TTS Notify]
+```text
+F12 → ffmpeg registra → rimozione silenzio/Opus → Antigravity multimodale
+ → briefing JSON → piano opzionale → prompt inviato a pi
```
----
+1. `ffmpeg` registra e normalizza l'audio a 16 kHz mono.
+2. `audioHasSpeech()` evita richieste su silenzio.
+3. L'audio è compresso Opus/OGG e inviato come `inlineData` a `voiceModel` tramite Antigravity.
+4. Il modello restituisce trascrizione, prompt pulito e indicazioni di ricerca.
+5. Con `vocalPlanningMode=true`, un overlay richiede conferma prima dell'invio.
-## Fasi del Workflow Vocale
+Se l'interpretazione multimodale fallisce, la pipeline tenta una trascrizione più semplice, sempre tramite Antigravity. Non esistono fallback a servizi con chiave API esterna.
-### 1. Avvio & Cattura dell'Audio (`startRecording`)
-Alla pressione del tasto **F12**:
-- Viene avviato un sottoprocesso `ffmpeg` che cattura dal dispositivo PulseAudio predefinito (`-f pulse -i default`).
-- L'audio viene campionato a 16.000 Hz in mono (`-ac 1 -ar 16000`).
-- Viene attivato un timer TUI che aggiorna dinamicamente lo stato di `pi`:
- `🔴 REGISTRAZIONE... 00:14 / 02:00 (Ctrl+Esc per annullare)`
+I suoni procedurali `start`, `stop`, `cancel`, `timeout` e `done` restano feedback locali; non eseguono sintesi vocale.
-### 2. Feedback Acustici Sci-Fi Procedurali (`playSound`)
-Per non dipendere da file audio esterni, i suoni di feedback vengono generati proceduralmente usando il filtro `aevalsrc` di `ffmpeg` e riprodotti tramite `paplay` (fallback su `aplay` o `ffplay`):
-
-- **`start`**: Sweep di frequenza ascendente ($440 \text{ Hz} \to 1760 \text{ Hz}$).
-- **`stop`**: Sweep di frequenza discendente ($1200 \text{ Hz} \to 300 \text{ Hz}$).
-- **`cancel`**: Suono modulato a bassa frequenza ($350 \text{ Hz} \to 200 \text{ Hz}$).
-- **`timeout`**: Pulsazione bi-tono di avviso radar.
-- **`done`**: Arpeggio scintillante acuto ($C_5 - E_5 - G_5$).
-
-### 3. Ottimizzazione & Rilevamento del Parlato
-Prima della trascrizione:
-- **`optimizeAudio()`**: Utilizza il filtro `silenceremove` di `ffmpeg` per eliminare il silenzio iniziale e finale con soglia a `-50dB`.
-- **`audioHasSpeech()`**: Esegue una passata `volumedetect`. Se `max_volume` è inferiore a `-35dB` o `mean_volume` è sotto a `-45dB`, la registrazione viene classificata come silenzio, evitando di effettuare chiamate API inutili ed evitando allucinazioni da parte del modello STT.
-
-### 4. Compressione & Inizio Trascrizione
-- L'audio ottimizzato WAV viene convertito al volo in formato **Opus/OGG** (`libopus` a 16kbps). Questo riduce il payload di oltre il 60%, evitando errori `HTTP 413 Payload Too Large`.
-- Invio della richiesta alle API Gemini (`gemini-3.5-flash`) o al server `enne2`.
-
-### 5. Multimodalità Ibrida (Voce + Testo Editor)
-Una funzionalità distintiva di `agy-pi` è la capacità di fondere il testo digitato dall'utente prima di premere F12 con l'audio registrato:
-
-```typescript
-const editorText = (ctx.ui.getEditorText?.() ?? "").trim();
-```
-
-Se l'utente ha scritto una nota o del codice nell'editor di `pi` e poi preme **F12** per aggiungere un commento vocale, l'estensione unisce i due input in un unico prompt interpretativo per Gemini:
-
-$$\text{Prompt Finale} = \text{Trascrizione Vocale} + \text{Testo Editor} + \text{Contesto Conversazione}$$
-
-Dopo l'invio riuscito, l'editor dell'interfaccia TUI viene svuotato automaticamente (`setEditorText("")`) per evitare duplicazioni.
-
-### 6. Invio del Prompt all'Agente `pi`
-Se `pi` è in stato di attesa (`isIdle()`), l'input viene inviato immediatamente tramite `sendUserMessage(finalText)`. Se `pi` sta eseguendo un altro task, viene accodato come `followUp`.
-
-### 7. Workflow a 2 Fasi con Piano + Conferma (`vocalPlanningMode`)
-Con `vocalPlanningMode=true` (default) il flusso di interpretazione vocale è
-**plan-first**: Gemini produce **solo** la trascrizione corretta e un **piano
-d'azione sintetico** (nessuna esecuzione). Questo evita che l'interpretazione
-avvii autonomamente loop agentici o modifiche a sorpresa a causa di errori di STT.
-
-L'interpretazione usa un **framing inter-agent**: Gemini agisce come **analista
-tecnico/middleware** che scrive un briefing per l'orchestratore (NON risponde
-all'utente), in **JSON strutturato**:
-
-```json
-{
- "trascrizione_corretta": "...",
- "intent_analisi": "...",
- "note_per_agent": "...",
- "azioni_raccomandate": ["..."],
- "prompt_utente_pulito": "...",
- "ricerca_necessaria": true/false,
- "suggerimenti_ricerca": ["..."]
-}
-```
-
-Il campo `prompt_utente_pulito` diventa il prompt finale per l'orchestratore. Se
-`ricerca_necessaria=true`, al prompt finale viene aggiunta una **nota che
-indica all'agente successivo di usare la ricerca web (Perplexity)** per
-verificare best practices, versioni o documentazione aggiornate.
-
-Poi un **overlay TUI** mostra trascrizione + piano e attende la conferma:
-
-```
-🎙️ Conferma vocale
- Trascrizione: "Aggiorna i docs..."
- 📋 Piano proposto: ...
- Enter esegui • Esc annulla • E modifica • Spazio testo letterale • F12 registra di nuovo
-```
-
-- **Enter** → esegue (invia il risultato a pi)
-- **Esc** → annulla, nessuna azione
-- **E** → modifica il testo del piano manualmente
-- **Spazio** → chiude l'overlay e inserisce nell'editor la **trascrizione letterale** di quanto dettato, senza inviare il piano proposto
-- **F12** → registra di nuovo
-
-Per tornare all'esecuzione diretta (autonomia piena):
-`/agy:config set vocalPlanningMode false`.
+Comandi: `/agy:record`, `/agy:record:stop`, `/agy:record:cancel`. `Ctrl+Esc` annulla la registrazione.
\ No newline at end of file
diff --git a/extensions/index.ts b/extensions/index.ts
index 466566d..2b0bee9 100644
--- a/extensions/index.ts
+++ b/extensions/index.ts
@@ -50,41 +50,23 @@ const CONFIG_DIR = path.join(os.homedir(), ".config", "agy-pi");
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
interface AgyConfig {
- geminiApiKey?: string;
- enne2ApiKey?: string;
- sttBackend?: string; // gemini | enne2
- sttUrl?: string;
- sttModel?: string;
sttMaxDuration?: number; // secondi
- ttsBackend?: string; // gemini | enne2
- ttsNotify?: boolean;
- ttsModel?: string;
agyBin?: string;
agyDefaultModel?: string;
agyTimeoutMs?: number;
// Fase 2 — Context Injection
contextInject?: boolean; // on|off
contextTokens?: number; // token cap per il contesto iniettato
- webSearch?: string; // auto|on|off — Strada A: pi cerca + inietta
vocalPlanningMode?: boolean; // Opzione 4: piano + conferma prima di eseguire
- sttDirectGemini?: boolean; // Forza l'uso di Gemini multimodale per l'audio anche con modelli non-Gemini (es. DeepSeek)
- voiceModel?: string; // Modello per l'elaborazione vocale diretta (default: gemini-3.7-flash-medium)
+ voiceModel?: string; // Modello Antigravity multimodale per l'audio
}
const CONFIG_DEFAULTS: AgyConfig = {
- sttBackend: "gemini",
- sttUrl: "https://ai.enne2.net",
- sttModel: "gemma4:E4B",
sttMaxDuration: 120,
- ttsBackend: "gemini",
- ttsNotify: true,
- ttsModel: "gemini-2.5-flash-preview-tts",
agyTimeoutMs: 180_000,
contextInject: true,
contextTokens: 1500,
- webSearch: "auto",
vocalPlanningMode: true,
- sttDirectGemini: true,
voiceModel: "gemini-3.7-flash-medium",
};
@@ -116,7 +98,7 @@ function getConfig(key: keyof AgyConfig): string | undefined {
function setConfig(key: keyof AgyConfig, value: string) {
const cfg = loadConfig();
const numKeys: (keyof AgyConfig)[] = ["sttMaxDuration", "agyTimeoutMs", "contextTokens"];
- const boolKeys: (keyof AgyConfig)[] = ["ttsNotify", "contextInject", "vocalPlanningMode", "sttDirectGemini"];
+ const boolKeys: (keyof AgyConfig)[] = ["contextInject", "vocalPlanningMode"];
if (numKeys.includes(key)) {
(cfg as any)[key] = Number(value);
} else if (boolKeys.includes(key)) {
@@ -449,219 +431,6 @@ function extractSessionState(ctx: any): string {
}
}
-// Cerca sul web tramite Gemini API (googleSearch grounding) e restituisce i
-// risultati iniettabili. È la Strada A: l'estensione cerca, poi inietta.
-async function webSearchGemini(query: string, signal?: AbortSignal): Promise {
- let key = getConfig("geminiApiKey") ?? process.env.GEMINI_API_KEY ?? "";
- if (!key) {
- try {
- key = fs.readFileSync(path.join(AGY_CHAT_DIR, "gemini-key"), "utf8").trim();
- } catch {
- /* ignora */
- }
- }
- if (!key) return "";
- const model = process.env.AGY_GEMINI_MODEL ?? "gemini-3.5-flash";
- try {
- const res = await fetch(
- `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${encodeURIComponent(key)}`,
- {
- method: "POST",
- signal,
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- contents: [{ role: "user", parts: [{ text: query }] }],
- tools: [{ googleSearch: {} }],
- }),
- },
- );
- if (!res.ok) return "";
- const data: any = await res.json();
- const text = data?.candidates?.[0]?.content?.parts?.map((p: any) => p.text ?? "").join(" ") ?? "";
- return text.trim().slice(0, 600);
- } catch {
- return "";
- }
-}
-
-// Chiamata generica alla Gemini API (solo testo, nessun tool). Restituisce la
-// risposta completa oppure stringa vuota in caso di errore.
-async function geminiText(prompt: string, signal?: AbortSignal): Promise {
- let key = getConfig("geminiApiKey") ?? process.env.GEMINI_API_KEY ?? "";
- if (!key) {
- try {
- key = fs.readFileSync(path.join(AGY_CHAT_DIR, "gemini-key"), "utf8").trim();
- } catch {
- /* ignora */
- }
- }
- if (!key) return "";
- const model = process.env.AGY_GEMINI_MODEL ?? "gemini-3.5-flash";
- try {
- const res = await fetch(
- `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${encodeURIComponent(key)}`,
- {
- method: "POST",
- signal,
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- contents: [{ role: "user", parts: [{ text: prompt }] }],
- }),
- },
- );
- if (!res.ok) return "";
- const data: any = await res.json();
- const text = data?.candidates?.[0]?.content?.parts?.map((p: any) => p.text ?? "").join(" ") ?? "";
- return text.trim();
- } catch {
- return "";
- }
-}
-
-// Genera DINAMICAMENTE uno script Python/OpenCV specifico per i requisiti,
-// usando l'LLM. Lo script legge l'immagine da argv[1] e stampa su stdout un JSON
-// standard: {"pass": bool, "score": 0..1, "findings": [...], "errors": [...]}.
-async function generateOpenCVScript(requirements: string): Promise {
- const prompt =
- `Sei un esperto di computer vision con OpenCV. Scrivi UN SINGOLO script Python (solo codice, senza markdown) che verifichi un'immagine rispetto a questi requisiti:\n\n` +
- `REQUISITI: ${requirements}\n\n` +
- `Lo script:\n` +
- `- legge il percorso immagine da sys.argv[1]\n` +
- `- importa solo cv2, numpy, json, sys (e eventuali standard library)\n` +
- `- esegue verifiche OGGETTIVE pertinenti ai requisiti (es. intervalli di colore HSV, forme/contorni, simmetria, testo, dimensioni, orientamento, ecc.)\n` +
- `- stampa su stdout UN SOLO oggetto JSON: {"pass": true/false, "score": 0..1, "findings": [stringhe], "errors": [stringhe]}\n` +
- ` - pass: true se l'immagine rispetta i requisiti\n` +
- ` - score: grado di conformità da 0 a 1\n` +
- ` - findings: brevi note oggettive misurate (es. "colore dominante rosso RGB(200,30,30)")\n` +
- ` - errors: eventuali problemi di lettura/verifica\n` +
- `- NON deve fallire se l'immagine è leggibile: gestisci con try/except e metti l'errore in errors\n` +
- `- usa solo OpenCV, numpy e librerie standard (nessuna libreria esterna)\n\n` +
- `Restituisci SOLO il codice Python, nessun testo aggiuntivo, nessun blocco markdown.`;
- const code = await geminiText(prompt);
- // rimuovi eventuali fence markdown
- return code.replace(/```python|```/g, "").trim();
-}
-
-// Esegue lo script OpenCV generato sull'immagine e restituisce il JSON su stdout.
-async function runOpenCVScript(script: string, imagePath: string): Promise {
- if (!script) return "";
- try {
- const { stdout } = await execFileAsync("python3", ["-I", "-c", script, imagePath], {
- timeout: 20_000,
- cwd: os.tmpdir(),
- env: { PATH: process.env.PATH ?? "", HOME: os.homedir() },
- });
- return stdout.trim();
- } catch {
- return "";
- }
-}
-
-// Genera un edit deterministico opzionale. Lo script usa argv[1] come input e
-// argv[2] come output e deve stampare un singolo JSON con findings/errors.
-async function generateProgrammaticEditScript(requirements: string, editInstructions: string): Promise {
- const prompt =
- `Write ONE Python script (code only) that applies this deterministic image edit: ${editInstructions}.\n` +
- `The resulting image must still target these requirements: ${requirements}.\n` +
- `Contract: read input image from sys.argv[1], write output image to sys.argv[2], and print one JSON object ` +
- `{"findings": ["..."], "errors": ["..."]}. Use only cv2, numpy, PIL/Pillow, json, sys, math. ` +
- `Do not import or use os, pathlib, subprocess, socket, network, arbitrary file access, eval, exec, or dynamic imports. ` +
- `Do not access any path except argv[1] and argv[2]. Preserve quality and dimensions unless explicitly requested. ` +
- `Return only Python code, without markdown fences.`;
- return (await geminiText(prompt)).replace(/```python|```/g, "").trim();
-}
-
-// Static rejection is deliberately conservative: generated/user scripts are
-// still run in an isolated temp cwd with a minimal environment and timeout.
-function validateProgrammaticEditScript(script: string): string | null {
- if (!script.trim()) return "Edit script is empty.";
- if (script.length > 30_000) return "Edit script exceeds the thirty-thousand character limit.";
- if (/\b(?:os|pathlib|subprocess|socket|requests|urllib|shutil|glob|asyncio|ctypes|pickle)\b/i.test(script))
- return "Edit script uses a forbidden module or identifier.";
- if (/\b(?:open|eval|exec|compile|__import__|globals|locals|input|getattr|setattr|vars|dir)\s*\(/i.test(script) || /__/.test(script))
- return "Edit script uses a forbidden operation.";
- if (/(?:['\"])(?:\/(?:[^'\"]+)|~\/|\.\.\/|[A-Za-z]:\\)/.test(script))
- return "Edit script contains a filesystem path; use only sys.argv input/output paths.";
- const imports = [...script.matchAll(/(?:from|import)\s+([A-Za-z_][\w.]*)/g)].map((m) => m[1].split(".")[0]);
- const allowed = new Set(["cv2", "numpy", "np", "PIL", "Image", "json", "sys", "math"]);
- const forbiddenImport = imports.find((name) => !allowed.has(name));
- return forbiddenImport ? `Edit script imports forbidden module: ${forbiddenImport}.` : null;
-}
-
-async function runProgrammaticEditScript(script: string, imagePath: string, outputPath: string): Promise<{ ok: boolean; report: string }> {
- const validationError = validateProgrammaticEditScript(script);
- if (validationError) return { ok: false, report: validationError };
- try {
- // Prefer bubblewrap when available: no network, temporary /tmp, and a
- // read-only host view. Static validation remains necessary defense in depth.
- const useBubblewrap = fs.existsSync("/usr/bin/bwrap");
- if (useBubblewrap) {
- fs.closeSync(fs.openSync(outputPath, "a"));
- }
- const command = useBubblewrap ? "/usr/bin/bwrap" : "python3";
- const args = useBubblewrap
- ? ["--ro-bind", "/", "/", "--bind", outputPath, outputPath, "--dev", "/dev", "--proc", "/proc", "--unshare-net", "--chdir", os.tmpdir(), "--", "python3", "-I", "-c", script, imagePath, outputPath]
- : ["-I", "-c", script, imagePath, outputPath];
- const { stdout, stderr } = await execFileAsync(command, args, {
- timeout: 20_000,
- cwd: os.tmpdir(),
- env: { PATH: process.env.PATH ?? "", HOME: os.homedir() },
- maxBuffer: 2 * 1024 * 1024,
- });
- if (!fs.existsSync(outputPath)) return { ok: false, report: "Edit script completed without producing the output image." };
- return { ok: true, report: (stdout || stderr || "").trim() };
- } catch (error: any) {
- return { ok: false, report: String(error?.stderr || error?.message || error).slice(0, 2000) };
- }
-}
-
-// Cache anti-ridondanza: evita ricerche web ripetute sullo stesso topic in una
-// finestra breve (il multi-step reasoning lancia prompt simili in sequenza).
-let lastSearch = { topic: "", time: 0 };
-
-// Euristica per webSearch=auto. Determina se il prompt beneficia di dati
-// aggiornati dalla ricerca web. Filtri negativi (immagini, codice locale,
-// comandi) per non sprecare chiamate API/token su task che non servono.
-function needsWebSearch(prompt: string, mode?: string): boolean {
- if (mode === "image" || mode === "analyze") return false;
- const ws = getConfig("webSearch") ?? "auto";
- if (ws === "on") return true;
- if (ws === "off") return false;
-
- const p = prompt.trim().toLowerCase();
- if (!p || p.length < 14) return false;
-
- // --- Filtri negativi: task che NON beneficiano della ricerca web ---
- // generazione / editing immagini
- if (/(genera|crea|disegna|produrr|dipin|realizz).{0,30}(immagine|logo|icona|poster|banner|ritratto|illustraz|foto|meme|sfondo)/i.test(p))
- return false;
- // analisi locale / OCR / trascrizione / traduzione / voce
- if (/(analizza questo|analizza il|\bocr\b|trascri|leggi il file|traduci|riassumi il file)/.test(p)) return false;
- // task di codice puramente locale
- if (/^(scrivi|rifattorizza|correggi|implementa|aggiungi|rimuovi|crea|modifica|fix|refactor|aggiorna il file|genera il codice)/.test(p))
- return false;
- // comandi / gestione conversazione
- if (/^(continua|ricordati|ignora|vai avanti|mostra|riassumi la conversazione|non)/.test(p)) return false;
-
- // --- Trigger positivi ---
- // segnali di recency / tempo corrente
- if (/(oggi|stamattina|adesso|al momento|quest'anno|del 20\d\d|nel 20\d\d|recent|ultim|appena|di recente|attuale|latest)/i.test(p))
- return true;
- // versioni / package / tech aggiornati
- if (/(versione|version|release|changelog|release notes|npm|package|dipendenz|compatibil|lts|stabile|beta|deprecat|\beol\b|end of life|release date)/i.test(p))
- return true;
- // confronti / alternative / raccomandazioni
- if (/(differenza|confronta|\bvs\b|alternativ|migliore|best|top|consigli|oppure)/i.test(p)) return true;
- // domande informative / definizioni
- if (/(cos'?è|che cos|chi è|quanto|quando|dove|perché|come funzion|cosa significa|definizion|significat|prezzo|costo|storia di)/i.test(p))
- return true;
- // news / novità / fatti correnti
- if (/(news|notizie|novità|novita|ultime notizie|accadut|succes|eventi|mercato|azienda|lancio|annunci)/i.test(p))
- return true;
-
- return false;
-}
-
const estTokens = (s: string) => Math.ceil(s.length / 4);
function truncToTokens(s: string, limit: number): string {
if (estTokens(s) <= limit) return s;
@@ -715,17 +484,6 @@ async function buildContextBlock(
parts.push({ label: "memory", xml: "durable_memory", body: memLines.join("\n") });
}
- // --- (Strada A) ---
- if (needsWebSearch(prompt, mode)) {
- // anti-ridondanza: non ricercare lo stesso topic entro 60s
- const topic = prompt.trim().toLowerCase().slice(0, 60);
- const now = Date.now();
- if (topic !== lastSearch.topic || now - lastSearch.time > 60_000) {
- lastSearch = { topic, time: now };
- const web = await webSearchGemini(prompt.slice(0, 300), signal);
- if (web) parts.push({ label: "web", xml: "web_context", body: web });
- }
- }
// Token cap: budget totale ~1500. Assegna in modo proporzionale.
const blocks: string[] = [];
@@ -733,12 +491,10 @@ async function buildContextBlock(
const envBudget = Math.min(300, budget);
const sessionBudget = Math.min(500, budget);
const memBudget = Math.min(400, budget);
- const webBudget = Math.min(300, budget);
const budgets: Record = {
env: envBudget,
session: sessionBudget,
memory: memBudget,
- web: webBudget,
};
for (const p of parts) {
const cap = budgets[p.label] ?? 200;
@@ -1076,129 +832,6 @@ async function optimizeAudio(input: string): Promise {
}
}
-// Trascrizione affidabile e veloce via Gemini API diretta
-// (agy CLI non supporta audio; la API supporta audio/wav nativamente)
-// Rileva se l'audio contiene parlato reale (non solo silenzio)
-// Ritorna true se c'è segnale, false se è silenzio
-function audioHasSpeech(file: string): boolean {
- try {
- const res = spawnSync(
- "ffmpeg",
- ["-hide_banner", "-i", file, "-af", "volumedetect", "-f", "null", "-"],
- { timeout: 30_000, encoding: "utf8" },
- );
- const stderr = res.stderr || "";
- const maxMatch = stderr.match(/max_volume: ([\-0-9.]+) dB/);
- const meanMatch = stderr.match(/mean_volume: ([\-0-9.]+) dB/);
- if (maxMatch) {
- const max = parseFloat(maxMatch[1]);
- // max < -35dB ≈ silenzio quasi totale
- if (max < -35) return false;
- }
- if (meanMatch) {
- const mean = parseFloat(meanMatch[1]);
- if (mean < -45) return false;
- }
- return true;
- } catch {
- return true; // se non possiamo verificare, assumiamo che ci sia parlato
- }
-}
-
-// Risultato trascrizione con dettaglio errore
-interface TranscriptResult {
- text: string;
- error?: string;
-}
-
-// Trascrizione affidabile e veloce via Gemini API diretta
-// (agy CLI non supporta audio; la API supporta audio/mpeg nativamente)
-async function transcribeWithGeminiAPI(file: string): Promise {
- // key da config file o env var
- let key = getConfig("geminiApiKey") ?? process.env.GEMINI_API_KEY ?? "";
- if (!key) {
- try {
- key = fs.readFileSync(path.join(AGY_CHAT_DIR, "gemini-key"), "utf8").trim();
- } catch {
- /* ignora */
- }
- }
- if (!key) return { text: "", error: "Key Gemini mancante (imposta con /agy:config set geminiApiKey )" };
-
- // rileva silenzio prima di chiamare l'API (evita allucinazioni su audio vuoto)
- if (!audioHasSpeech(file)) {
- return { text: "", error: "Nessun parlato rilevato nell'audio (silenzio o volume troppo basso)" };
- }
-
- const model = process.env.AGY_GEMINI_MODEL ?? "gemini-3.5-flash";
- try {
- // Comprimi in Opus/OGG (audio/ogg) — molto più efficiente dell'MP3 per il parlato,
- // riduce il payload di ~60% evitando HTTP 413 su registrazioni lunghe
- let audioFile = file;
- let mimeType = "audio/wav";
- if (file.toLowerCase().endsWith(".wav")) {
- const ogg = file.replace(/\.wav$/i, ".ogg");
- try {
- await execFileAsync(
- "ffmpeg",
- ["-hide_banner", "-loglevel", "error", "-y", "-i", file, "-ac", "1", "-ar", "16000", "-c:a", "libopus", "-b:a", "16k", ogg],
- { timeout: 60_000 },
- );
- audioFile = ogg;
- mimeType = "audio/ogg";
- } catch (e: any) {
- return { text: "", error: `Conversione Opus fallita: ${e.message}` };
- }
- }
- const b64 = fs.readFileSync(audioFile).toString("base64");
- const body = {
- contents: [
- {
- parts: [
- { text: "Trascrivi fedelmente il contenuto di questo audio in italiano. Restituisci SOLO la trascrizione testuale." },
- { inline_data: { mime_type: mimeType, data: b64 } },
- ],
- },
- ],
- };
-
- // retry su errori transitori (429, 500, 503)
- let lastErr = "";
- for (let attempt = 0; attempt < 3; attempt++) {
- const res = await fetch(
- `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${key}`,
- {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(body),
- signal: AbortSignal.timeout(120_000),
- },
- );
- if (res.ok) {
- const data: any = await res.json();
- const text = data?.candidates?.[0]?.content?.parts?.[0]?.text?.trim() ?? "";
- if (!text) {
- return { text: "", error: "La Gemini API ha restituito una risposta vuota" };
- }
- return { text };
- }
- lastErr = `HTTP ${res.status}`;
- const errBody = await res.text().catch(() => "");
- try {
- const e = JSON.parse(errBody);
- lastErr = `${lastErr}: ${e?.error?.message ?? ""}`.trim();
- } catch {
- /* ignora */
- }
- if (res.status !== 429 && res.status !== 500 && res.status !== 503) break;
- await new Promise((r) => setTimeout(r, 1500 * (attempt + 1)));
- }
- return { text: "", error: lastErr || "Errore API Gemini sconosciuto" };
- } catch (e: any) {
- return { text: "", error: `Errore API Gemini: ${e?.message ?? String(e)}` };
- }
-}
-
function extractText(content: any): string {
if (typeof content === "string") return content;
if (Array.isArray(content)) {
@@ -1237,7 +870,6 @@ function getConversationContext(ctx: any, maxEntries = 8): string {
// Opzione 4 — Workflow vocale a 2 fasi: overlay TUI con trascrizione + piano
// e conferma utente prima dell'esecuzione (Enter esegui, Esc annulla, E modifica,
// Spazio testo letterale nell'editor senza inviare, F12 registra di nuovo).
-// Riusa l'infrastruttura overlay di /agy:key e /agy:status.
// =========================================================================
interface VoicePlanDecision {
action: "send" | "cancel" | "record" | "literal";
@@ -1344,145 +976,6 @@ async function showVoicePlanOverlay(
);
}
-// Trascrizione via server locale ai.enne2.net (gemma4:E4B supporta audio)
-async function transcribeWithEnne2(file: string): Promise {
- const baseUrl = getConfig("sttUrl") ?? "https://ai.enne2.net";
- const model = getConfig("sttModel") ?? "gemma4:E4B";
- const apiKey = getConfig("enne2ApiKey") ?? process.env.ENNE2_API_KEY ?? "";
-
- // rileva silenzio
- if (!audioHasSpeech(file)) {
- return { text: "", error: "Nessun parlato rilevato nell'audio (silenzio o volume troppo basso)" };
- }
-
- try {
- // Comprimi in MP3 per evitare HTTP 413 su audio lunghi (come il backend gemini)
- let audioFile = file;
- let audioFormat = "wav";
- if (file.toLowerCase().endsWith(".wav")) {
- const mp3 = file.replace(/\.wav$/i, ".mp3");
- try {
- await execFileAsync(
- "ffmpeg",
- ["-hide_banner", "-loglevel", "error", "-y", "-i", file, "-ac", "1", "-ar", "16000", "-c:a", "libmp3lame", "-q:a", "5", mp3],
- { timeout: 60_000 },
- );
- audioFile = mp3;
- audioFormat = "mp3";
- } catch {
- /* fallback al WAV */
- }
- }
- const b64 = fs.readFileSync(audioFile).toString("base64");
- const body = {
- model,
- messages: [
- {
- role: "user",
- content: [
- { type: "text", text: "Trascrivi fedelmente il parlato in questo audio. Rispondi solo con la trascrizione." },
- { type: "input_audio", input_audio: { data: b64, format: audioFormat } },
- ],
- },
- ],
- max_tokens: 2000,
- };
- const headers: Record = { "Content-Type": "application/json" };
- if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
- const res = await fetch(`${baseUrl}/v1/chat/completions`, {
- method: "POST",
- headers,
- body: JSON.stringify(body),
- signal: AbortSignal.timeout(120_000),
- });
- if (!res.ok) {
- return { text: "", error: `Errore server enne2: HTTP ${res.status}` };
- }
- const data: any = await res.json();
- const text = data?.choices?.[0]?.message?.content?.trim() ?? "";
- return { text };
- } catch (e: any) {
- return { text: "", error: `Errore server enne2: ${e?.message ?? String(e)}` };
- }
-}
-
-// Dispatcher: sceglie il backend di trascrizione (gemini | enne2)
-async function transcribeAudio(file: string): Promise {
- const backend = getConfig("sttBackend") ?? "gemini";
- if (backend === "enne2" || backend === "local") {
- return transcribeWithEnne2(file);
- }
- return transcribeWithGeminiAPI(file);
-}
-
-// ---------------------------------------------------------------------------
-// TTS via Gemini API (nessun engine esterno)
-// ---------------------------------------------------------------------------
-async function ttsSpeak(text: string, outputDir?: string): Promise {
- let key = getConfig("geminiApiKey") ?? process.env.GEMINI_API_KEY ?? "";
- if (!key) {
- try {
- key = fs.readFileSync(path.join(AGY_CHAT_DIR, "gemini-key"), "utf8").trim();
- } catch {
- /* ignora */
- }
- }
- if (!key) return null;
-
- const model = getConfig("ttsModel") ?? "gemini-2.5-flash-preview-tts";
- try {
- const body = {
- contents: [{ parts: [{ text }] }],
- generationConfig: { responseModalities: ["AUDIO"] },
- };
- const res = await fetch(
- `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${key}`,
- {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(body),
- signal: AbortSignal.timeout(60_000),
- },
- );
- if (!res.ok) return null;
- const data: any = await res.json();
- const parts = data?.candidates?.[0]?.content?.parts ?? [];
- const audioPart = parts.find((p: any) => p.inlineData);
- if (!audioPart) return null;
-
- // salva PCM raw (16-bit, 24kHz) e converti in WAV
- const pcmFile = path.join(os.tmpdir(), `tts-${Date.now()}.pcm`);
- fs.writeFileSync(pcmFile, Buffer.from(audioPart.inlineData.data, "base64"));
- const wavFile = pcmFile.replace(".pcm", ".wav");
- await execFileAsync(
- "ffmpeg",
- ["-hide_banner", "-loglevel", "error", "-y", "-f", "s16le", "-ar", "24000", "-ac", "1", "-i", pcmFile, wavFile],
- { timeout: 30_000 },
- );
- fs.rmSync(pcmFile, { force: true });
-
- // copia in outputDir se richiesto
- let finalFile = wavFile;
- if (outputDir) {
- try {
- fs.mkdirSync(outputDir, { recursive: true });
- const dest = path.join(outputDir, `tts-${Date.now()}.wav`);
- fs.copyFileSync(wavFile, dest);
- finalFile = dest;
- } catch {
- /* ignora */
- }
- }
-
- // riproduci (fire-and-forget)
- execFileAsync("paplay", [finalFile], { timeout: 60_000 }).catch(() => {});
-
- return finalFile;
- } catch {
- return null;
- }
-}
-
// ---------------------------------------------------------------------------
// Helper: onUpdate nel formato corretto (oggetto con content, non stringa)
// ---------------------------------------------------------------------------
@@ -1490,6 +983,46 @@ function toolUpdate(onUpdate: any, text: string) {
onUpdate?.({ content: [{ type: "text", text }] });
}
+// Trascrizione audio esclusivamente tramite il gateway Antigravity OAuth.
+// La CLI agy non accetta file audio, ma cloudcode-pa supporta inlineData
+// audio con i modelli Gemini multimodali dell'account Antigravity.
+function audioHasSpeech(file: string): boolean {
+ try {
+ const res = spawnSync("ffmpeg", ["-hide_banner", "-i", file, "-af", "volumedetect", "-f", "null", "-"], { timeout: 30_000, encoding: "utf8" });
+ const stderr = res.stderr || "";
+ const maxMatch = stderr.match(/max_volume: ([\-0-9.]+) dB/);
+ const meanMatch = stderr.match(/mean_volume: ([\-0-9.]+) dB/);
+ if (maxMatch && parseFloat(maxMatch[1]) < -35) return false;
+ if (meanMatch && parseFloat(meanMatch[1]) < -45) return false;
+ return true;
+ } catch { return true; }
+}
+
+interface TranscriptResult { text: string; error?: string; }
+
+async function transcribeWithAntigravity(file: string, language?: string): Promise {
+ if (!audioHasSpeech(file)) return { text: "", error: "Nessun parlato rilevato nell'audio (silenzio o volume troppo basso)" };
+ let audioFile = file, mimeType = "audio/wav";
+ if (file.toLowerCase().endsWith(".wav")) {
+ const ogg = file.replace(/\.wav$/i, ".ogg");
+ try {
+ await execFileAsync("ffmpeg", ["-hide_banner", "-loglevel", "error", "-y", "-i", file, "-ac", "1", "-ar", "16000", "-c:a", "libopus", "-b:a", "16k", ogg], { timeout: 60_000 });
+ audioFile = ogg; mimeType = "audio/ogg";
+ } catch (error: any) { return { text: "", error: `Conversione Opus fallita: ${error.message}` }; }
+ }
+ try {
+ const response = await antgGenerate({
+ parts: [
+ { text: `Trascrivi fedelmente il contenuto di questo audio${language ? ` in ${language}` : " in italiano"}. Restituisci SOLO la trascrizione testuale.` },
+ { inlineData: { mimeType, data: fs.readFileSync(audioFile).toString("base64") } },
+ ],
+ model: getConfig("voiceModel") || "gemini-3.7-flash-medium", maxOutputTokens: 3000, thinking: "low", stream: true,
+ });
+ const text = response.text.trim();
+ return text ? { text } : { text: "", error: "Antigravity ha restituito una trascrizione vuota" };
+ } catch (error: any) { return { text: "", error: `Trascrizione Antigravity fallita: ${error?.message ?? String(error)}` }; }
+}
+
// =========================================================================
// Client diretto Antigravity — protocollo v1internal (cloudcode-pa)
// Parla direttamente con i server di inferenza di Antigravity usando il
@@ -1793,19 +1326,6 @@ async function antgGenerate(opts: AntgGenerateOpts): Promise<{ text: string; det
// ---------------------------------------------------------------------------
// Pipeline Multimodale Diretta (Gemini): invio diretto dell'audio senza STT
// ---------------------------------------------------------------------------
-function isGeminiModel(model: any): boolean {
- if (!model) return true; // se non specificato, default ad Antigravity/Gemini
- const id = (model.id || "").toLowerCase();
- const provider = (model.provider || "").toLowerCase();
- if (id.startsWith("gemini-") || id.includes("gemini")) return true;
- if (provider === "google" || provider === "google-vertex") return true;
- if (provider === "antigravity") {
- if (id.startsWith("claude-") || id.startsWith("gpt-oss")) return false;
- return true;
- }
- return false;
-}
-
interface AudioInterpretationResult {
transcript: string;
cleanPrompt: string;
@@ -1814,7 +1334,7 @@ interface AudioInterpretationResult {
rawText: string;
}
-async function interpretAudioDirectGemini(
+async function interpretAudioWithAntigravity(
audioFile: string,
editorText: string,
context: string,
@@ -1881,43 +1401,7 @@ async function interpretAudioDirectGemini(
rawText,
};
} catch (err: any) {
- // Fallback su Gemini API diretta se Antigravity account token fallisce
- let key = getConfig("geminiApiKey") ?? process.env.GEMINI_API_KEY ?? "";
- if (!key) {
- try {
- key = fs.readFileSync(path.join(AGY_CHAT_DIR, "gemini-key"), "utf8").trim();
- } catch {
- /* ignora */
- }
- }
- if (key) {
- const apiModel = process.env.AGY_GEMINI_MODEL ?? "gemini-3.5-flash";
- const body = {
- contents: [{ parts: [{ text: promptInstructions }, { inline_data: { mime_type: mimeType, data: b64 } }] }],
- };
- const res = await fetch(
- `https://generativelanguage.googleapis.com/v1beta/models/${apiModel}:generateContent?key=${key}`,
- {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(body),
- signal: AbortSignal.timeout(120_000),
- },
- );
- if (res.ok) {
- const data: any = await res.json();
- const rawText = data?.candidates?.[0]?.content?.parts?.[0]?.text?.trim() ?? "";
- const briefing = extractVoiceBriefing(rawText);
- return {
- transcript: briefing.transcript || briefing.cleanPrompt || rawText,
- cleanPrompt: briefing.cleanPrompt || rawText,
- needsSearch: briefing.needsSearch,
- searchHints: briefing.searchHints,
- rawText,
- };
- }
- }
- throw err;
+ throw new Error(`Interpretazione audio Antigravity fallita: ${err?.message ?? String(err)}`);
}
}
@@ -2123,17 +1607,20 @@ function antgBuildGeminiRequest(model: Model, context: Context, options?: S
const parts: any[] = [];
for (const b of msg.content) {
if (b.type === "text") {
- const rawSig = b.textSignature || (isSame ? (b as any).thoughtSignature : undefined);
+ const rawSig = isSame ? (b.textSignature || (b as any).thoughtSignature) : undefined;
const sig = isValidGeminiThoughtSignature(rawSig) ? rawSig : undefined;
if ((!b.text || !b.text.trim()) && !sig) continue;
parts.push({ text: b.text, ...(sig ? { thoughtSignature: sig } : {}) });
} else if (b.type === "thinking") {
- const rawSig = b.thinkingSignature || (isSame ? (b as any).thoughtSignature : undefined);
+ const rawSig = isSame ? (b.thinkingSignature || (b as any).thoughtSignature) : undefined;
const sig = isValidGeminiThoughtSignature(rawSig) ? rawSig : undefined;
if ((!b.thinking || !b.thinking.trim()) && !sig) continue;
- parts.push({ thought: true, text: b.thinking, ...(sig ? { thoughtSignature: sig } : {}) });
+ // Claude richiede una signature per ogni thinking block. I thought di un
+ // modello diverso non sono riutilizzabili: manteniamo il contesto come testo.
+ if (sig) parts.push({ thought: true, text: b.thinking, thoughtSignature: sig });
+ else parts.push({ text: `[Previous model reasoning]\n${b.thinking}` });
} else if (b.type === "toolCall") {
- const rawSig = b.thoughtSignature || (b as any).thought_signature;
+ const rawSig = isSame ? (b.thoughtSignature || (b as any).thought_signature) : undefined;
const sig = isValidGeminiThoughtSignature(rawSig) ? rawSig : undefined;
if (sig) {
if (b.id) signedToolCallIds.add(b.id);
@@ -2368,19 +1855,13 @@ export default function agyExtension(pi: ExtensionAPI) {
injectContext: Type.Optional(
Type.Boolean({ description: "Inietta contesto ambiente/sessione/web nel prompt (default true)." }),
),
- webSearch: Type.Optional(
- Type.Boolean({ description: "Forza/non forzare la ricerca web (override di webSearch config)." }),
- ),
}),
async execute(toolCallId, params, signal, onUpdate, ctx) {
const p = params as any;
toolUpdate(onUpdate, `agy: ${p.mode === "image" ? "generazione immagine" : "elaborazione"}...`);
- // Fase 2: costruzione contesto iniettato (ambiente + sessione + memoria + web)
+ // Context injection: ambiente, sessione e memoria locale.
const wantInject = p.injectContext ?? true;
- const prevWs = getConfig("webSearch");
- if (typeof p.webSearch === "boolean") setConfig("webSearch", p.webSearch ? "on" : "off");
const contextBlock = wantInject ? await buildContextBlock(ctx, p.prompt, signal, p.mode) : "";
- if (typeof p.webSearch === "boolean" && prevWs) setConfig("webSearch", prevWs);
const res = await executeAgy({
prompt: p.prompt,
mode: p.mode,
@@ -2736,187 +2217,6 @@ export default function agyExtension(pi: ExtensionAPI) {
},
});
- // =========================================================================
- // TOOL: agy_create_verified — generazione iterativa con verifica
- // Loop self-contained: genera immagine → analizza con visione → verifica
- // OpenCV → rigenera se non rispetta i requisiti (fino a maxIterations).
- // =========================================================================
- pi.registerTool({
- name: "agy_create_verified",
- label: "agy create verified image",
- description:
- "Generate an image, optionally apply a deterministic Python image edit, inspect it, and regenerate it until the requirements pass or the iteration limit is reached. " +
- "Use ONLY when iterative visual verification or deterministic correction is required; use agy_generate for one-shot generation.",
- parameters: Type.Object({
- requirements: Type.String({ description: "Requisiti precisi che l'immagine deve soddisfare." }),
- outputDir: Type.Optional(Type.String({ description: "Cartella dove salvare l'immagine finale." })),
- maxIterations: Type.Optional(Type.Number({ description: "Maximum iterations (default 3, max 5)." })),
- useOpenCV: Type.Optional(Type.Boolean({ description: "Run objective OpenCV checks (default true)." })),
- editInstructions: Type.Optional(Type.String({ description: "Optional deterministic edit to apply each iteration before verification; use precise, measurable instructions." })),
- editScript: Type.Optional(Type.String({ description: "Optional Python script for a deterministic edit. Must read argv[1], write argv[2], and use only the allowlisted image libraries." })),
- model: Type.Optional(Type.String({ description: "Antigravity model." })),
- }),
- async execute(toolCallId, params, signal, onUpdate, ctx) {
- const p = params as any;
- const requirements = String(p.requirements ?? "").trim();
- const maxIter = Math.min(Math.max(Number(p.maxIterations ?? 3) || 3, 1), 5);
- const useCV = p.useOpenCV ?? true;
- const editInstructions = String(p.editInstructions ?? "").trim();
- const suppliedEditScript = String(p.editScript ?? "").trim();
- if (editInstructions && suppliedEditScript) {
- return {
- content: [{ type: "text", text: "Provide either editInstructions or editScript, not both." }],
- details: { error: "conflicting_edit_inputs" },
- isError: true,
- };
- }
-
- toolUpdate(onUpdate, "agy_create_verified: avvio loop di creazione verificata...");
-
- let currentImage: string | undefined;
- let lastIssues = "";
- const report: {
- iteration: number;
- pass: boolean;
- imagePath: string;
- verdict: string;
- opencv: string;
- programmaticEdit: string;
- }[] = [];
- let finalText = "";
-
- // Genera DINAMICAMENTE lo script OpenCV specifico per i requisiti (una volta,
- // i requisiti sono fissi) e lo riusa in ogni iterazione del loop.
- let cvScript = "";
- if (useCV) {
- toolUpdate(onUpdate, "agy_create_verified: generazione script OpenCV dinamico...");
- cvScript = await generateOpenCVScript(requirements);
- }
- let editScript = suppliedEditScript;
- if (editInstructions) {
- toolUpdate(onUpdate, "agy_create_verified: generazione edit deterministico...");
- editScript = await generateProgrammaticEditScript(requirements, editInstructions);
- }
- const editValidationError = editScript ? validateProgrammaticEditScript(editScript) : null;
- if (editValidationError) {
- return {
- content: [{ type: "text", text: `Edit programmatico rifiutato: ${editValidationError}` }],
- details: { error: "invalid_programmatic_edit", validation: editValidationError },
- isError: true,
- };
- }
-
- for (let iter = 1; iter <= maxIter; iter++) {
- toolUpdate(
- onUpdate,
- `agy_create_verified: iterazione ${iter}/${maxIter} (${iter === 1 ? "generazione" : "rigenerazione"})...`,
- );
-
- // 1) Genera (o rigenera) con prompt di correzione se non è la prima
- const genPrompt = currentImage
- ? `L'immagine precedente (${currentImage}) non rispetta i requisiti per questi motivi: ${lastIssues}. ` +
- `Rigenera/rettifica l'immagine per soddisfare esattamente: ${requirements}.`
- : `Genera un'immagine che soddisfi esattamente questi requisiti: ${requirements}.`;
- const genRes = await executeAgy({
- prompt: genPrompt + IMG_SUFFIX,
- mode: "image",
- model: p.model,
- stateless: true,
- filePaths: currentImage ? [currentImage] : [],
- outputDir: p.outputDir,
- signal,
- });
- const imgPath = genRes.imagePath;
- if (!imgPath) {
- finalText = `Generazione fallita all'iterazione ${iter}: nessuna immagine prodotta.\n${genRes.text}`;
- break;
- }
- currentImage = imgPath;
- let programmaticEdit = "";
- let editPassed = true;
- if (editScript) {
- const editRoot = p.outputDir ? path.resolve(String(p.outputDir)) : os.tmpdir();
- try { fs.mkdirSync(editRoot, { recursive: true }); } catch { /* il runner segnalerà l'errore */ }
- const editedPath = path.join(editRoot, `agy-verified-edit-${process.pid}-${iter}-${Math.random().toString(36).slice(2)}.png`);
- toolUpdate(onUpdate, `agy_create_verified: edit programmatico ${iter}/${maxIter}...`);
- const editResult = await runProgrammaticEditScript(editScript, imgPath, editedPath);
- programmaticEdit = editResult.report;
- editPassed = editResult.ok;
- if (editPassed) currentImage = editedPath;
- else lastIssues = `Edit programmatico fallito: ${programmaticEdit}`;
- }
- const candidateImage = currentImage!;
-
- // 2) Analisi visione (giudice): PASS/FAIL + problemi rispetto ai requisiti
- const judgePrompt =
- `Analizza il file al percorso ${candidateImage}. Requisiti richiesti: ${requirements}. ` +
- `Verifica se l'immagine li rispetta. Rispondi iniziando con \"PASS:\" o \"FAIL:\", ` +
- `poi elenca sinteticamente (max 3 punti) le deviazioni rispetto ai requisiti in caso di FAIL.`;
- const judgeRes = await executeAgy({
- prompt: judgePrompt,
- mode: "analyze",
- model: p.model,
- stateless: true,
- filePaths: [candidateImage],
- yolo: true,
- signal,
- });
- const verdict = judgeRes.text.trim();
- const visionPass = /^\s*PASS:?/i.test(verdict);
- lastIssues = verdict;
-
- // 3) Verifica OpenCV DINAMICA (script generato dall'LLM per i requisiti)
- let cvMetrics = "";
- let ocvPass: boolean | null = null;
- if (useCV && cvScript) {
- const out = await runOpenCVScript(cvScript, candidateImage);
- cvMetrics = out;
- try {
- const parsed = JSON.parse(out);
- ocvPass = typeof parsed.pass === "boolean" ? parsed.pass : null;
- if (typeof parsed.score === "number") cvMetrics += `\nscore: ${parsed.score}`;
- if (Array.isArray(parsed.findings) && parsed.findings.length)
- cvMetrics += `\nfindings: ${parsed.findings.join("; ")}`;
- } catch {
- ocvPass = null;
- }
- // Se OpenCV rileva non conformità, le aggiunge alle issue per guidare la rigenerazione
- if (ocvPass === false) {
- lastIssues = `${verdict}\nVerifica OpenCV: ${cvMetrics}`;
- }
- }
-
- // Conforme solo se visione PASS e (se disponibile) anche OpenCV PASS
- const pass = editPassed && visionPass && (ocvPass === null || ocvPass === true);
-
- report.push({ iteration: iter, pass, imagePath: candidateImage, verdict, opencv: cvMetrics, programmaticEdit });
-
- if (pass) {
- finalText =
- `✅ Immagine verificata dopo ${iter} iterazione/i.\nPercorso: ${candidateImage}` +
- (cvMetrics ? `\nMetriche OpenCV: ${cvMetrics}` : "") +
- `\nVerdetto visione:\n${verdict}`;
- break;
- }
- if (iter === maxIter) {
- finalText =
- `⚠️ Requisiti non soddisfatti dopo ${maxIter} iterazioni.\nPercorso: ${candidateImage}` +
- (cvMetrics ? `\nMetriche OpenCV: ${cvMetrics}` : "") +
- `\nUltimo verdetto visione:\n${verdict}`;
- }
- }
-
- return {
- content: [{ type: "text", text: finalText || "Nessun output." }],
- details: {
- iterations: report.length,
- imagePath: currentImage,
- report,
- },
- };
- },
- });
-
// =========================================================================
// TOOL: agy_transcribe — trascrizione audio (via Gemini API diretta)
// =========================================================================
@@ -2924,7 +2224,7 @@ export default function agyExtension(pi: ExtensionAPI) {
name: "agy_transcribe",
label: "agy transcribe audio",
description:
- "Transcribe speech or other audio into text with the direct Gemini API. " +
+ "Transcribe speech or other audio into text through the Antigravity API. " +
"Use ONLY for audio; use agy_analyze for images and agy_video for video.",
parameters: Type.Object({
filePath: Type.String({ description: "Percorso del file audio (wav, mp3, m4a, ecc.)." }),
@@ -2934,7 +2234,7 @@ export default function agyExtension(pi: ExtensionAPI) {
async execute(toolCallId, params, signal, onUpdate, ctx) {
const p = params as any;
toolUpdate(onUpdate, "agy_transcribe: trascrizione audio...");
- const tr = await transcribeAudio(p.filePath);
+ const tr = await transcribeWithAntigravity(p.filePath, p.language);
if (!tr.text) {
return {
content: [{ type: "text", text: `Trascrizione fallita: ${tr.error ?? "vuota"}` }],
@@ -2945,7 +2245,7 @@ export default function agyExtension(pi: ExtensionAPI) {
const transcript = tr.text;
return {
content: [{ type: "text", text: transcript }],
- details: { filePath: p.filePath, model: p.model ?? "gemini-3.5-flash" },
+ details: { filePath: p.filePath, model: getConfig("voiceModel") ?? "gemini-3.7-flash-medium" },
};
},
});
@@ -2987,39 +2287,6 @@ export default function agyExtension(pi: ExtensionAPI) {
},
});
- // =========================================================================
- // TOOL: agy_tts — text-to-speech via Gemini API
- // =========================================================================
- pi.registerTool({
- name: "agy_tts",
- label: "agy TTS (text to speech)",
- description:
- "Convert text to an audio file with Gemini TTS. Use ONLY when an audio file or playback is explicitly requested; " +
- "for normal voice output use the canonical tts_speak tool.",
- parameters: Type.Object({
- text: Type.String({ description: "Il testo da pronunciare." }),
- play: Type.Optional(Type.Boolean({ description: "true per riprodurre l'audio (default: true)." })),
- outputDir: Type.Optional(Type.String({ description: "Cartella dove salvare il file audio." })),
- }),
- async execute(toolCallId, params, signal, onUpdate, ctx) {
- const p = params as any;
- toolUpdate(onUpdate, "agy_tts: sintesi vocale...");
- return ttsSpeak(p.text, p.outputDir).then((file) => {
- if (!file) {
- return {
- content: [{ type: "text", text: "TTS fallito: key Gemini mancante o errore API." }],
- details: {},
- isError: true,
- };
- }
- return {
- content: [{ type: "text", text: `Audio TTS generato: ${file}` }],
- details: { audioFile: file },
- };
- });
- },
- });
-
// =========================================================================
// TOOL: agy_models — elenca i modelli disponibili
// =========================================================================
@@ -3246,24 +2513,16 @@ export default function agyExtension(pi: ExtensionAPI) {
let needsSearch = false;
let searchHints: string[] = [];
- const activeModel = ctx.model;
- const directGeminiConfig = getConfig("sttDirectGemini");
- // Usa la pipeline multimodale diretta con Gemini se abilitata (default: true) o se il modello di chat è Gemini
- const useDirectMultimodal =
- directGeminiConfig !== "false" ? true : isGeminiModel(activeModel);
-
- if (useDirectMultimodal) {
+ // Audio e contesto passano sempre tramite Antigravity, indipendentemente
+ // dal modello di chat attivo nella sessione.
+ {
// =========================================================================
- // Pipeline Multimodale Diretta (Gemini): l'audio viene inviato direttamente
- // a Gemini (gateway Antigravity / Gemini API) senza passare per un STT separato,
- // anche se il modello attivo in sessione è DeepSeek, Claude o un modello locale.
+ // Pipeline multimodale diretta tramite il gateway Antigravity.
// =========================================================================
- ctx.ui.notify("Interpretazione vocale diretta con Gemini...", "info");
+ ctx.ui.notify("Interpretazione vocale diretta con Antigravity...", "info");
try {
- const voiceModelName =
- getConfig("voiceModel") ||
- (isGeminiModel(activeModel) ? activeModel?.id : "gemini-3.7-flash-medium");
- const directRes = await interpretAudioDirectGemini(
+ const voiceModelName = getConfig("voiceModel") || "gemini-3.7-flash-medium";
+ const directRes = await interpretAudioWithAntigravity(
optimized,
editorText,
context,
@@ -3281,7 +2540,7 @@ export default function agyExtension(pi: ExtensionAPI) {
// Fallback o modello non-Gemini: pipeline a 2 passaggi (STT + Briefing)
if (!finalText) {
ctx.ui.notify("Trascrizione in corso...", "info");
- const tr = await transcribeAudio(optimized);
+ const tr = await transcribeWithAntigravity(optimized);
if (!tr.text) {
ctx.ui.setStatus("agy-rec", "");
ctx.ui.notify(`Trascrizione fallita: ${tr.error ?? "vuota"}`, "error");
@@ -3290,7 +2549,7 @@ export default function agyExtension(pi: ExtensionAPI) {
}
transcript = tr.text;
- ctx.ui.notify("Interpretazione con Gemini...", "info");
+ ctx.ui.notify("Interpretazione con Antigravity...", "info");
const res = await executeAgy({
prompt:
`[CONTESTO INTERNO — COMUNICAZIONE TRA AGENTI]\n` +
@@ -3374,15 +2633,10 @@ export default function agyExtension(pi: ExtensionAPI) {
ctx.ui.notify("✅ Richiesta vocale inviata come prompt a pi", "info");
playSound("done");
- // notifica vocale (config ttsNotify o AGY_TTS_NOTIFY=0 per disattivare)
- const ttsNotify = getConfig("ttsNotify") ?? "true";
- if (ttsNotify !== "false" && process.env.AGY_TTS_NOTIFY !== "0") {
- ttsSpeak("Trascrizione completata e inviata.").catch(() => {});
- }
}
pi.registerShortcut("f12", {
- description: "Avvia/ferma registrazione microfono (max 2 min) e trascrive via Gemini",
+ description: "Avvia/ferma registrazione microfono (max 2 min) e interpreta via Antigravity",
handler: async (ctx) => {
await handleRecordToggle(ctx);
},
@@ -3432,78 +2686,18 @@ export default function agyExtension(pi: ExtensionAPI) {
},
});
- pi.registerCommand("agy:speak", {
- description: "Pronuncia un testo con TTS Gemini. Uso: /agy:speak ",
- handler: async (args, ctx) => {
- if (!args?.trim()) {
- ctx.ui.notify("Uso: /agy:speak ", "error");
- return;
- }
- ctx.ui.setStatus("agy-tts", "🔊 sintesi vocale...");
- const file = await ttsSpeak(args.trim());
- ctx.ui.setStatus("agy-tts", "");
- if (file) ctx.ui.notify(`🔊 Audio: ${file}`, "info");
- else ctx.ui.notify("TTS fallito (key Gemini mancante?)", "error");
- },
- });
-
- pi.registerCommand("agy:vocal", {
- description: "Attiva/disattiva il feedback vocale TTS. Uso: /agy:vocal [on|off|status]",
- handler: async (args, ctx) => {
- const arg = (args ?? "").trim().toLowerCase();
- const current = getConfig("ttsNotify") ?? "true";
-
- if (arg === "on") {
- setConfig("ttsNotify", "true");
- ctx.ui.notify("🔊 Feedback vocale ATTIVATO", "info");
- ttsSpeak("Feedback vocale attivato.").catch(() => {});
- return;
- }
- if (arg === "off") {
- setConfig("ttsNotify", "false");
- ctx.ui.notify("🔇 Feedback vocale DISATTIVATO", "info");
- return;
- }
- if (arg === "status" || !arg) {
- const on = current !== "false";
- ctx.ui.notify(on ? "🔊 Feedback vocale: ATTIVO" : "🔇 Feedback vocale: DISATTIVO", "info");
- return;
- }
-
- // toggle
- if (current === "false") {
- setConfig("ttsNotify", "true");
- ctx.ui.notify("🔊 Feedback vocale ATTIVATO", "info");
- ttsSpeak("Feedback vocale attivato.").catch(() => {});
- } else {
- setConfig("ttsNotify", "false");
- ctx.ui.notify("🔇 Feedback vocale DISATTIVATO", "info");
- }
- },
- });
-
// =========================================================================
// Comando: /agy:config — gestione configurazione persistente
// =========================================================================
const CONFIG_KEYS: { key: keyof AgyConfig; desc: string }[] = [
- { key: "geminiApiKey", desc: "Chiave API Google Gemini (STT/TTS)" },
- { key: "enne2ApiKey", desc: "Token per il server proxy ai.enne2.net (opzionale)" },
- { key: "sttBackend", desc: "Backend trascrizione: gemini | enne2" },
- { key: "sttUrl", desc: "URL base backend enne2" },
- { key: "sttModel", desc: "Modello STT backend enne2" },
{ key: "sttMaxDuration", desc: "Durata max registrazione (secondi)" },
- { key: "ttsBackend", desc: "Backend TTS: gemini | enne2" },
- { key: "ttsNotify", desc: "Notifiche vocali automatiche: true | false" },
- { key: "ttsModel", desc: "Modello TTS Gemini" },
+ { key: "voiceModel", desc: "Modello Antigravity per audio (default gemini-3.7-flash-medium)" },
{ key: "agyBin", desc: "Path del binario agy" },
{ key: "agyDefaultModel", desc: "Modello predefinito per le chiamate agy" },
{ key: "agyTimeoutMs", desc: "Timeout esecuzione agy (ms)" },
{ key: "contextInject", desc: "Iniezione contesto nel prompt agy: true | false" },
{ key: "contextTokens", desc: "Token cap per il contesto iniettato (default 1500)" },
- { key: "webSearch", desc: "Ricerca web Strada A: auto | on | off" },
{ key: "vocalPlanningMode", desc: "Opzione 4: piano+conferma dopo il vocale (true|false)" },
- { key: "sttDirectGemini", desc: "Interpretazione vocale diretta Gemini (true|false, default true)" },
- { key: "voiceModel", desc: "Modello Gemini per l'audio (default gemini-3.7-flash-medium)" },
];
pi.registerCommand("agy:config", {
@@ -3520,12 +2714,7 @@ export default function agyExtension(pi: ExtensionAPI) {
const cfg = loadConfig();
const lines = CONFIG_KEYS.map(({ key: k, desc }) => {
const v = (cfg as any)[k];
- const masked =
- k === "geminiApiKey" || k === "enne2ApiKey"
- ? v
- ? `${String(v).slice(0, 4)}...${String(v).slice(-4)}`
- : "(non impostata)"
- : v ?? "(non impostata)";
+ const masked = v ?? "(non impostata)";
return `${k} = ${masked} — ${desc}`;
});
ctx.ui.notify(`Config agy-pi (${CONFIG_FILE}):\n${lines.join("\n")}`, "info");
@@ -3564,137 +2753,6 @@ export default function agyExtension(pi: ExtensionAPI) {
},
});
- // =========================================================================
- // Comando: /agy:key — dialog overlay TUI per inserire/modificare la chiave
- // API Gemini in modo interattivo (campo mascherato, Enter conferma, Esc
- // annulla). Usa i componenti TUI di pi (overlay in sovrimpressione).
- // =========================================================================
- pi.registerCommand("agy:key", {
- description:
- "Apre un dialog overlay per inserire/modificare la chiave API Gemini (campo mascherato)",
- handler: async (_args, ctx) => {
- // Import dinamico: se pi-tui non fosse disponibile, fallisce solo questo
- // comando senza rompere il caricamento dell'intera estensione.
- const {
- Container,
- Text,
- matchesKey,
- Key,
- } = await import("@earendil-works/pi-tui");
- const { DynamicBorder } = await import("@earendil-works/pi-coding-agent");
-
- const result = await ctx.ui.custom(
- (tui, theme, _keybindings, done) => {
- let value = "";
- const currentKey = getConfig("geminiApiKey") ?? "";
- const maskCurrent = currentKey
- ? `${currentKey.slice(0, 4)}...${currentKey.slice(-4)}`
- : "(non impostata)";
- const mask = (v: string) => "•".repeat(v.length);
-
- const container = new Container();
- const renderDialog = () => {
- container.clear();
- container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
- container.addChild(
- new Text(theme.fg("accent", theme.bold("🔑 Chiave API Gemini")), 1, 1),
- );
- container.addChild(
- new Text(theme.fg("dim", `Attuale: ${maskCurrent}`), 1, 0),
- );
- container.addChild(new Text("", 0, 0));
- const field = value ? mask(value) : "(vuota)";
- container.addChild(
- new Text(
- theme.fg("text", "Nuova chiave: ") + theme.fg("warning", field),
- 1,
- 0,
- (s) => theme.bg("toolPendingBg", s),
- ),
- );
- container.addChild(new Text("", 0, 0));
- container.addChild(
- new Text(
- theme.fg("dim", "Digita la chiave • Enter conferma • Esc annulla"),
- 1,
- 0,
- ),
- );
- container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
- };
- renderDialog();
-
- return {
- render: (w) => {
- renderDialog();
- return container.render(w);
- },
- invalidate: () => container.invalidate(),
- handleInput: (data) => {
- if (matchesKey(data, Key.enter)) {
- const trimmed = value.trim();
- if (trimmed) {
- setConfig("geminiApiKey", trimmed);
- // Aggiorna anche ~/.agy-chat/gemini-key per coerenza con gli script TTS/STT
- try {
- fs.mkdirSync(AGY_CHAT_DIR, { recursive: true });
- fs.writeFileSync(
- path.join(AGY_CHAT_DIR, "gemini-key"),
- trimmed,
- { mode: 0o600 },
- );
- } catch {
- /* ignora */
- }
- done(trimmed);
- }
- return;
- }
- if (matchesKey(data, Key.escape)) {
- done(null);
- return;
- }
- if (matchesKey(data, Key.backspace)) {
- value = value.slice(0, -1);
- tui.requestRender();
- return;
- }
- // Caratteri stampabili permessi per una chiave API (A-Z a-z 0-9 _ . -)
- if (data.length === 1 && data.charCodeAt(0) >= 32) {
- if (/^[A-Za-z0-9._-]+$/.test(data)) {
- value += data;
- }
- tui.requestRender();
- }
- },
- };
- },
- {
- overlay: true,
- overlayOptions: {
- width: "50%",
- minWidth: 54,
- anchor: "center",
- },
- },
- );
-
- if (result) {
- ctx.ui.notify(
- `✅ Chiave API Gemini aggiornata: ${result.slice(0, 4)}...${result.slice(-4)}`,
- "info",
- );
- try {
- playSound("done");
- } catch {
- /* ignora */
- }
- } else {
- ctx.ui.notify("Chiave non modificata.", "info");
- }
- },
- });
-
// =========================================================================
// Comando: /agy:status — diagnostica estensione in un overlay TUI
// Mostra: binario agy, versione, chiave Gemini, modello attivo, ultimo
@@ -3725,21 +2783,7 @@ export default function agyExtension(pi: ExtensionAPI) {
}
}
- const cfgKey = getConfig("geminiApiKey") ?? "";
- const fileKey = (() => {
- try {
- const f = path.join(AGY_CHAT_DIR, "gemini-key");
- return fs.existsSync(f) ? fs.readFileSync(f, "utf8").trim() : "";
- } catch {
- return "";
- }
- })();
- const key = cfgKey || fileKey;
- const keyStatus = key ? "✅ valida" : "❌ mancante (usa /agy:key)";
- const keyMask = key ? `${key.slice(0, 4)}...${key.slice(-4)}` : "—";
const model = getConfig("agyDefaultModel") || "(default agy)";
- const sttBackend = getConfig("sttBackend") ?? "gemini";
- const ttsNotify = getConfig("ttsNotify") ?? "true";
// ultimo errore dal log agy (ultima riga con 'ERROR'/'denied'/'quota')
let lastError = "(nessuno)";
@@ -3787,11 +2831,8 @@ export default function agyExtension(pi: ExtensionAPI) {
line("Binario agy", agyExists ? `trovato ${agyBin}` : "❌ NON trovato",
agyExists ? "success" : "error");
line("Versione", agyVersion, "text");
- line("Chiave Gemini", keyStatus, key ? "success" : "error");
- line("Chiave (mask)", keyMask, "muted");
line("Modello attivo", model, "accent");
- line("Backend STT", sttBackend, "text");
- line("Notifiche TTS", ttsNotify, "text");
+ line("Pipeline audio", "Antigravity OAuth", "text");
container.addChild(new Text("", 0, 0));
container.addChild(
new Text(theme.fg("warning", "Ultimo errore:") + " " + theme.fg("text", lastError), 1, 0),
diff --git a/skills/agy-create-verified/SKILL.md b/skills/agy-create-verified/SKILL.md
deleted file mode 100644
index 0352c27..0000000
--- a/skills/agy-create-verified/SKILL.md
+++ /dev/null
@@ -1,39 +0,0 @@
----
-name: agy-create-verified
-description: "Operational guide for iterative image generation with visual and deterministic programmatic verification and edits."
----
-
-# Verified image workflow
-
-Use `agy_create_verified` only when a generated image must be checked and possibly corrected across iterations. Use `agy_generate` for one-shot or purely aesthetic work.
-
-## Prepare the request
-
-Separate requirements into:
-
-- **Semantic:** subject, pose, style, mood, scene.
-- **Layout:** aspect ratio, placement, count, alignment, spacing.
-- **Measurable:** dimensions, dominant colors, geometry, contrast, text regions.
-
-Write acceptance criteria as explicit pass/fail statements. OpenCV can verify measurable criteria; Gemini judges semantic criteria. Do not claim a subjective requirement is objectively verified.
-
-## Deterministic edits
-
-Use `editInstructions` for a repeatable transformation such as resize, crop, color correction, thresholding, masking, watermark placement, or geometric cleanup. Use `editScript` only for an explicit trusted script.
-
-The edit script contract is:
-
-- read input from `sys.argv[1]`;
-- write only the output image to `sys.argv[2]`;
-- use only `cv2`, `numpy`, `PIL`, `json`, `sys`, and `math`;
-- do not use network, subprocesses, dynamic imports, or arbitrary filesystem access.
-
-The programmatic edit runs before visual/OpenCV verification, so its output—not the raw generated image—is the candidate for the next iteration.
-
-## Budget and interpretation
-
-- Use `maxIterations=2` for simple corrections, 3 normally, and 4–5 only when justified.
-- Keep `useOpenCV=true` when at least one criterion is measurable; otherwise it adds cost without reliable evidence.
-- A `PASS` is valid only for the final candidate and available checks.
-- A `FAIL` result is the latest candidate, not a compliant image. Report unmet criteria explicitly.
-- Inspect the per-iteration report for edit errors, unchanged progress, regressions, and failed OpenCV output before presenting the result as verified.
diff --git a/templates/AGENTS.vocal-feedback.md b/templates/AGENTS.vocal-feedback.md
deleted file mode 100644
index d578627..0000000
--- a/templates/AGENTS.vocal-feedback.md
+++ /dev/null
@@ -1,37 +0,0 @@
-# Istruzioni globali per pi agent
-
-## Feedback vocale a ogni step (TTS)
-
-Dopo **ogni step** del flusso agentico (ogni tool eseguito, ogni fase completata, ogni errore incontrato), produci un **mini-riassunto vocale** e riproducilo subito con il tool `agy_tts`.
-
-### Formato del riassunto di step
-
-Il riassunto deve essere **breve (1-2 frasi)** e indicare:
-
-1. **Cosa stai facendo** — l'azione corrente o appena completata
-2. **Obiettivi raggiunti** — cosa è stato completato con successo
-3. **Problemi incontrati** — eventuali blocchi, errori o ostacoli
-
-### Come riprodurlo
-
-Usa il tool `agy_tts` con il testo del riassunto:
-
-```
-agy_tts(text="Step completato: ho analizzato il file e trovato l'errore. Obiettivo raggiunto. Nessun problema.")
-```
-
-### Regole
-
-- **Sempre**: riassunto vocale dopo ogni step significativo (non per micro-azioni banali come letture singole)
-- **Breve**: massimo 2 frasi, tono naturale e conciso
-- **Errori**: se uno step fallisce, annuncia il problema e cosa intendi fare
-- **Non ripetere**: se il riassunto è identico al precedente, omettilo
-- **Rispetta la config**: se `ttsNotify` è `false` nella config di agy-pi, salta il feedback vocale
-- **Toggle rapido**: l'utente può attivare/disattivare il feedback vocale con `/agy:vocal` (on|off|status)
-- **Non interrompere**: il feedback vocale non deve bloccare il flusso di lavoro (fire-and-forget)
-
-### Esempi
-
-- ✅ "Analisi completata: trovati 3 errori nel file main.ts. Procedo con la correzione."
-- ✅ "Correzione applicata con successo. Obiettivo raggiunto."
-- ⚠️ "Attenzione: la build è fallita per un errore di sintassi. Riprovo dopo la correzione."