From 4a88084b2ba082a6987ec3b16a749367d93c99bc Mon Sep 17 00:00:00 2001 From: enne2 Date: Thu, 3 Sep 2026 12:52:13 +0200 Subject: [PATCH] refactor: remove direct Gemini API integrations Route F12 and audio transcription exclusively through Antigravity OAuth, and preserve cross-model history without reusing incompatible signatures.\n\nRemove direct Gemini API keys, web grounding, internal TTS, verified image generation, obsolete commands and configuration. Update documentation and restore the local voice workflow helpers required at runtime. --- README.md | 281 +------ docs/architecture.md | 100 +-- docs/cli-wrapper.md | 73 +- docs/commands-keybindings.md | 97 +-- docs/configuration.md | 100 +-- docs/context-injection.md | 86 +-- docs/index.md | 79 +- docs/tools.md | 211 +----- docs/troubleshooting.md | 68 +- docs/vocal-workflow.md | 129 +--- extensions/index.ts | 1095 ++------------------------- skills/agy-create-verified/SKILL.md | 39 - templates/AGENTS.vocal-feedback.md | 37 - 13 files changed, 198 insertions(+), 2197 deletions(-) delete mode 100644 skills/agy-create-verified/SKILL.md delete mode 100644 templates/AGENTS.vocal-feedback.md diff --git a/README.md b/README.md index 6ac9979..86f1a4d 100644 --- a/README.md +++ b/README.md @@ -1,276 +1,41 @@ # agy-pi -

- agy-pi logo -

- -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 logo -

+`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."