Merge del branch benedetta: motore Antigravity (agy)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5.5 committed 2026-10-22 11:48:11 +02:00
commit 310512d070
15 files changed
+376 -75

No files matched your search

+7 -6
View File
@@ -4,7 +4,7 @@ Guida per gli agenti di codice (Claude Code, Gemini CLI, altri) che lavorano in
## Progetto ## Progetto
agent-wiki-ui (già `LlmRun`; il nome ha un'unica sorgente, vedi [docs/README.md](docs/README.md#nome-del-progetto)): eseguibile C# (.NET 10, Windows e Linux) che invoca Claude Code (`claude -p`) o Codex (`codex exec`) da terminale. Senza argomenti apre una GUI di chat Avalonia, con argomenti funziona da CLI. agent-wiki-ui (già `LlmRun`; il nome ha un'unica sorgente, vedi [docs/README.md](docs/README.md#nome-del-progetto)): eseguibile C# (.NET 10, Windows e Linux) che invoca Claude Code (`claude -p`), Codex (`codex exec`) o Antigravity (`agy … -p`) da terminale. Senza argomenti apre una GUI di chat Avalonia, con argomenti funziona da CLI.
## Workflow doc-first (obbligatorio) ## Workflow doc-first (obbligatorio)
@@ -17,14 +17,14 @@ agent-wiki-ui (già `LlmRun`; il nome ha un'unica sorgente, vedi [docs/README.md
- Build: `dotnet build` dalla radice (soluzione `agent-wiki-ui.slnx`: app in `src/agent-wiki-ui/`, test in `tests/`; `net10.0`, Avalonia) - Build: `dotnet build` dalla radice (soluzione `agent-wiki-ui.slnx`: app in `src/agent-wiki-ui/`, test in `tests/`; `net10.0`, Avalonia)
- GUI: `dotnet run --project src/agent-wiki-ui`; CLI: `dotnet run --project src/agent-wiki-ui -- "prompt"` - GUI: `dotnet run --project src/agent-wiki-ui`; CLI: `dotnet run --project src/agent-wiki-ui -- "prompt"`
- Publish single-file (uno per sistema): `scripts/publish.sh` (Linux/WSL/macOS) o `scripts/publish.ps1` (Windows PowerShell): creano `dist/win-x64/` e `dist/linux-x64/` (`dist/` è ignorata da git); i due script vanno tenuti allineati, oppure a mano `dotnet publish src/agent-wiki-ui -c Release -r win-x64 --self-contained -p:PublishSingleFile=true` e `-r linux-x64` - Publish single-file (uno per sistema): `scripts/publish.sh` (Linux/WSL/macOS) o `scripts/publish.ps1` (Windows PowerShell): creano `dist/win-x64/` e `dist/linux-x64/` (`dist/` è ignorata da git); i due script vanno tenuti allineati, oppure a mano `dotnet publish src/agent-wiki-ui -c Release -r win-x64 --self-contained -p:PublishSingleFile=true` e `-r linux-x64`
- Test: `dotnet test` dalla radice (xUnit v3 + Avalonia.Headless con Skia reale; `tests/FakeClaude` è il `claude` finto per i test di processo). Per ora coprono solo le immagini del renderer (MD-14, MD-19…MD-30); il resto è pianificato in docs/TESTING.md (Fase 6). Per verificare la GUI senza schermo si può usare `Avalonia.Headless` con `CaptureRenderedFrame()` in un progetto di prova fuori dal repo. - Test: `dotnet test` dalla radice (xUnit v3 + Avalonia.Headless con Skia reale; `tests/FakeClaude` è il `claude` finto per i test di processo). Per ora coprono le immagini e le formule del renderer, il `FakeClaude` e il motore Antigravity (AG-01…AG-12, UP-21); il resto è pianificato in docs/TESTING.md (Fase 6). Per verificare la GUI senza schermo si può usare `Avalonia.Headless` con `CaptureRenderedFrame()` in un progetto di prova fuori dal repo.
## Architettura ## Architettura
Decisioni e motivazioni negli ADR ([docs/adr/](docs/adr/)); qui solo la mappa del codice. Decisioni e motivazioni negli ADR ([docs/adr/](docs/adr/)); qui solo la mappa del codice.
- [Program.cs](src/agent-wiki-ui/Program.cs): top-level statements. Con argomenti è la CLI (`EngineRunner.RunInherited`, motore scelto con `--engine <id>` come primo argomento, default Claude Code); senza avvia Avalonia (`App`). - [Program.cs](src/agent-wiki-ui/Program.cs): top-level statements. Con argomenti è la CLI (`EngineRunner.RunInherited`, motore scelto con `--engine <id>` come primo argomento, default Claude Code); senza avvia Avalonia (`App`).
- [Core/Engine.cs](src/agent-wiki-ui/Core/Engine.cs): astrazione "motore" (ADR-0002): `Engine` (id, nome, eseguibile, modelli, effort, `BuildArgs`, `Parse`, `FetchLatestVersionAsync` per il controllo delle versioni), `ClaudeEngine`, `CodexEngine`, registro `Engines` e ricerca nel PATH (`PathSearch`). Gli elenchi modelli sono fissi, da aggiornare quando ne escono di nuovi; `DefaultModelId` indica il modello più piccolo, scelto all'avvio insieme a effort `low` (se esce un modello più piccolo, aggiornarlo). - [Core/Engine.cs](src/agent-wiki-ui/Core/Engine.cs): astrazione "motore" (ADR-0002): `Engine` (id, nome, eseguibile, modelli, effort, `BuildArgs`, `Parse`, `FetchLatestVersionAsync` per il controllo delle versioni), `ClaudeEngine`, `CodexEngine`, `AgyEngine`, registro `Engines` e ricerca nel PATH (`PathSearch`). Gli elenchi modelli sono fissi, da aggiornare quando ne escono di nuovi; `DefaultModelId` indica il modello più piccolo, scelto all'avvio insieme a effort `low` (se esce un modello più piccolo, aggiornarlo).
- [Core/EngineRunner.cs](src/agent-wiki-ui/Core/EngineRunner.cs): **unico punto di avvio dei motori** (cartella dell'exe, stdin chiuso, `ArgumentList`): CLI con output ereditato, GUI con output catturato e interpretato da `Engine.Parse`, e `RunToolAsync` per i comandi di servizio (`--version`, `update`) con tempo massimo. - [Core/EngineRunner.cs](src/agent-wiki-ui/Core/EngineRunner.cs): **unico punto di avvio dei motori** (cartella dell'exe, stdin chiuso, `ArgumentList`): CLI con output ereditato, GUI con output catturato e interpretato da `Engine.Parse`, e `RunToolAsync` per i comandi di servizio (`--version`, `update`) con tempo massimo.
- [Core/EngineUpdater.cs](src/agent-wiki-ui/Core/EngineUpdater.cs): controllo delle versioni in sola lettura (`CheckAsync`, `IsNewer`) e aggiornamento di un CLI con il suo `update` ufficiale (ADR-0004): versione prima/dopo, esito leggibile, metodo di installazione dedotto dal percorso reale e comando manuale da suggerire (l'app lo mostra, non lo esegue). - [Core/EngineUpdater.cs](src/agent-wiki-ui/Core/EngineUpdater.cs): controllo delle versioni in sola lettura (`CheckAsync`, `IsNewer`) e aggiornamento di un CLI con il suo `update` ufficiale (ADR-0004): versione prima/dopo, esito leggibile, metodo di installazione dedotto dal percorso reale e comando manuale da suggerire (l'app lo mostra, non lo esegue).
- [Core/ChatStore.cs](src/agent-wiki-ui/Core/ChatStore.cs): cronologia, un JSON per chat in `<LocalApplicationData>/<nome-app>/chats/<guid>.json` (nome file = id della chat, validato come Guid prima dell'uso; campi `Engine` e `SessionId` opzionali: i file vecchi sono chat di Claude Code). - [Core/ChatStore.cs](src/agent-wiki-ui/Core/ChatStore.cs): cronologia, un JSON per chat in `<LocalApplicationData>/<nome-app>/chats/<guid>.json` (nome file = id della chat, validato come Guid prima dell'uso; campi `Engine` e `SessionId` opzionali: i file vecchi sono chat di Claude Code).
@@ -37,10 +37,11 @@ Decisioni e motivazioni negli ADR ([docs/adr/](docs/adr/)); qui solo la mappa de
## Vincoli non ovvi ## Vincoli non ovvi
- La working directory di `claude` deve essere la cartella dell'exe: usare `AppContext.BaseDirectory`, mai `Environment.CurrentDirectory` né `Assembly.Location` (vuoto in single-file). - La working directory di `claude` deve essere la cartella dell'exe: usare `AppContext.BaseDirectory`, mai `Environment.CurrentDirectory` né `Assembly.Location` (vuoto in single-file).
- Lo stdin del motore va redirezionato e chiuso subito: `claude` aspetta 3 s, `codex` stampa "Reading additional input from stdin…" e attende. - Lo stdin del motore va redirezionato e chiuso subito: `claude` aspetta 3 s, `codex` stampa "Reading additional input from stdin…" e attende, `agy` può bloccarsi su output rediretto (issue #318).
- `claude` è un eseguibile nativo (`~/.local/bin`), avviabile direttamente. Se fosse `claude.cmd` (Windows) serve `cmd.exe /c`. - `claude` e `agy` sono eseguibili nativi (`~/.local/bin`, `~/.gemini/bin`), avviabili direttamente. Se fossero script `.cmd` (Windows) servirebbe `cmd.exe /c`.
- Codex (`CodexEngine`): l'id di sessione lo genera Codex e si legge da `thread.started` nell'output JSONL (`--json`); la risposta è l'ultimo `agent_message`; `exec resume` non accetta `-s`, la sola lettura si impone con `-c 'sandbox_mode="read-only"'`; serve `--skip-git-repo-check` (la cartella dell'exe non è un repository); `--` prima del prompt. Dettagli e prove in ADR-0002. Il modello predefinito è quello di `~/.codex/config.toml`: se richiede un Codex più recente l'errore compare nella chat (vedi docs/INSTALL.md). - Codex (`CodexEngine`): l'id di sessione lo genera Codex e si legge da `thread.started` nell'output JSONL (`--json`); la risposta è l'ultimo `agent_message`; `exec resume` non accetta `-s`, la sola lettura si impone con `-c 'sandbox_mode="read-only"'`; serve `--skip-git-repo-check` (la cartella dell'exe non è un repository); `--` prima del prompt. Dettagli e prove in ADR-0002. Il modello predefinito è quello di `~/.codex/config.toml`: se richiede un Codex più recente l'errore compare nella chat (vedi docs/INSTALL.md).
- Il controllo delle versioni contatta solo `downloads.claude.ai` (Claude Code) e `api.github.com` (Codex, serve lo User-Agent), al massimo ogni 5 minuti e in background; un errore di rete non è un errore dell'app (stato "sconosciuto"). - Antigravity (`AgyEngine`): eseguibile `agy`; tutti i flag di configurazione vanno passati prima di `-p <prompt>`; per usare `agy` in modalità non interattiva (`-p`) in sola lettura, nel file `~/.gemini/antigravity-cli/settings.json` deve essere presente la regola `'read_file(*)'` sotto `'permissions.allow'` (altrimenti headless mode nega `read_file` con errore jetski: 'jetski: no output produced - a tool required the read_file permission that headless mode cannot prompt for'), mentre scritture e comandi restano negati di default; non passare `--mode plan` né `--mode auto` e mai usare `--dangerously-skip-permissions`; per la GUI si usa `--output-format json` e l'id di sessione si legge da `conversation_id` (`OwnsSessionId => true`); la ripresa della conversazione usa `--conversation <id>`; per evitare il blocco su sottoprocesso con output rediretto (issue #318) si passa `--print-timeout 120s` e si chiude immediatamente lo stdin.
- Il controllo delle versioni contatta solo `downloads.claude.ai` (Claude Code), `api.github.com` (Codex, serve lo User-Agent) e `antigravity-cli-auto-updater-974169037036.us-central1.run.app` (Antigravity), al massimo ogni 5 minuti e in background; un errore di rete non è un errore dell'app (stato "sconosciuto").
- L'aggiornamento di un CLI è sempre un gesto esplicito dell'utente (pulsante Aggiorna): nessun aggiornamento automatico e nessun fallback che esegua npm/brew/winget al posto suo. - L'aggiornamento di un CLI è sempre un gesto esplicito dell'utente (pulsante Aggiorna): nessun aggiornamento automatico e nessun fallback che esegua npm/brew/winget al posto suo.
- Windows: l'eseguibile è `WinExe` (nessuna console al doppio clic); in modalità CLI `Core/ConsoleHost.cs` riaggancia il terminale padre con `AttachConsole` e collega stdout/stderr a `CONOUT$` solo se non sono già reindirizzati (ADR-0005). Il codice deve poter fallire in silenzio senza terminale. Conseguenza: su Windows l'exit code della CLI arriva solo se il chiamante attende il processo. - Windows: l'eseguibile è `WinExe` (nessuna console al doppio clic); in modalità CLI `Core/ConsoleHost.cs` riaggancia il terminale padre con `AttachConsole` e collega stdout/stderr a `CONOUT$` solo se non sono già reindirizzati (ADR-0005). Il codice deve poter fallire in silenzio senza terminale. Conseguenza: su Windows l'exit code della CLI arriva solo se il chiamante attende il processo.
- Il nome dell'app non va scritto a mano nel codice: usare `AppInfo.Name`. Cambiare nome cambia la cartella dati (vedi checklist in docs/README.md; `ChatStore` migra le cartelle dei nomi precedenti). - Il nome dell'app non va scritto a mano nel codice: usare `AppInfo.Name`. Cambiare nome cambia la cartella dati (vedi checklist in docs/README.md; `ChatStore` migra le cartelle dei nomi precedenti).
+7 -6
View File
@@ -1,23 +1,24 @@
# agent-wiki-ui # agent-wiki-ui
Eseguibile C# (.NET 10, Windows e Linux) che lancia Claude Code (`claude -p`) o Codex (`codex exec`) nella cartella in cui risiede l'eseguibile. Senza argomenti apre una GUI di chat (Avalonia), con argomenti funziona da riga di comando. Eseguibile C# (.NET 10, Windows e Linux) che lancia Claude Code (`claude -p`), Codex (`codex exec`) o Antigravity (`agy … -p`) nella cartella in cui risiede l'eseguibile. Senza argomenti apre una GUI di chat (Avalonia), con argomenti funziona da riga di comando.
## Requisiti ## Requisiti
- `claude` (Claude Code) installato e nel PATH, già autenticato; per Codex lo stesso con `codex`. Istruzioni: [docs/INSTALL.md](docs/INSTALL.md) - `claude` (Claude Code), `codex` (Codex) o `agy` (Antigravity) installati e nel PATH, già autenticati. Istruzioni: [docs/INSTALL.md](docs/INSTALL.md)
- Per sviluppare: .NET SDK 10 - Per sviluppare: .NET SDK 10
- Per usare l'eseguibile pubblicato: nessun runtime (è self-contained). Su Linux serve un desktop con X11 o XWayland per la GUI. - Per usare l'eseguibile pubblicato: nessun runtime (è self-contained). Su Linux serve un desktop con X11 o XWayland per la GUI.
## Uso ## Uso
GUI (nessun argomento): finestra di chat in stile claude.ai, con la conversazione in markdown in alto e il campo messaggio in basso (`Invio` invia, `Maiusc+Invio` va a capo, "Nuova chat" azzera il contesto). Il riquadro **Impostazioni** (in alto a destra) permette di scegliere la **CLI** (Claude Code o Codex), il **modello** e l'**effort** di ragionamento. All'avvio, e a ogni cambio di CLI, sono scelti il modello più piccolo (Haiku 5.5 per Claude Code, GPT-5.6 Luna per Codex) ed effort `low`, per risposte rapide e economiche; "Predefinito" = nessun flag, vale la config del motore. Le chat si salvano su disco e l'ultima si riapre all'avvio. Sempre nel riquadro, la sezione **Aggiornamenti** dice se Claude Code e Codex hanno una versione nuova (pulsante evidenziato) e li aggiorna uno alla volta. GUI (nessun argomento): finestra di chat in stile claude.ai, con la conversazione in markdown in alto e il campo messaggio in basso (`Invio` invia, `Maiusc+Invio` va a capo, "Nuova chat" azzera il contesto). Il riquadro **Impostazioni** (in alto a destra) permette di scegliere la **CLI** (Claude Code, Codex o Antigravity), il **modello** e l'**effort** di ragionamento. All'avvio, e a ogni cambio di CLI, sono scelti il modello più piccolo (Haiku 5.5 per Claude Code, GPT-5.6 Luna per Codex, Gemini 3.0 Flash Low per Antigravity) ed effort `low`, per risposte rapide e economiche; "Predefinito" = nessun flag, vale la config del motore. Le chat si salvano su disco e l'ultima si riapre all'avvio. Sempre nel riquadro, la sezione **Aggiornamenti** dice se Claude Code, Codex e Antigravity hanno una versione nuova (pulsante evidenziato) e li aggiorna uno alla volta.
``` ```
dotnet run --project src/agent-wiki-ui dotnet run --project src/agent-wiki-ui
``` ```
CLI (con argomenti): il prompt viene stampato su terminale, l'exit code è quello del motore (127 se non è nel PATH). Di default usa Claude Code; `--engine codex` usa Codex. CLI (con argomenti): il prompt viene stampato su terminale, l'exit code è quello del motore (127 se non è nel PATH). Di default usa Claude Code; `--engine codex` usa Codex; `--engine agy` usa Antigravity.
``` ```
dotnet run --project src/agent-wiki-ui -- "spiega questo progetto" dotnet run --project src/agent-wiki-ui -- "spiega questo progetto"
dotnet run --project src/agent-wiki-ui -- --engine codex "spiega questo progetto" dotnet run --project src/agent-wiki-ui -- --engine codex "spiega questo progetto"
dotnet run --project src/agent-wiki-ui -- --engine agy "spiega questo progetto"
``` ```
## Creare gli eseguibili (Windows e Linux) ## Creare gli eseguibili (Windows e Linux)
@@ -59,7 +60,7 @@ Verificato: da Linux/WSL entrambi gli eseguibili; da Windows nativo (PowerShell
- Windows: doppio clic su `agent-wiki-ui.exe` per la GUI (non si apre nessuna finestra di console), oppure da terminale `agent-wiki-ui.exe "prompt"` per la CLI. Da `cmd` o PowerShell il prompt torna subito e l'output compare dopo; per aspettare la fine e leggere l'exit code usa `start /wait agent-wiki-ui.exe "prompt"` (cmd) o `Start-Process -Wait -NoNewWindow agent-wiki-ui.exe "prompt"` (PowerShell); `& .\agent-wiki-ui.exe "prompt" | Out-Host` aspetta ma non dà l'exit code. Spiegazione in [ADR-0005](docs/adr/0005-windows-senza-console.md). Il file non è firmato: Windows può mostrare un avviso di sicurezza al primo avvio. - Windows: doppio clic su `agent-wiki-ui.exe` per la GUI (non si apre nessuna finestra di console), oppure da terminale `agent-wiki-ui.exe "prompt"` per la CLI. Da `cmd` o PowerShell il prompt torna subito e l'output compare dopo; per aspettare la fine e leggere l'exit code usa `start /wait agent-wiki-ui.exe "prompt"` (cmd) o `Start-Process -Wait -NoNewWindow agent-wiki-ui.exe "prompt"` (PowerShell); `& .\agent-wiki-ui.exe "prompt" | Out-Host` aspetta ma non dà l'exit code. Spiegazione in [ADR-0005](docs/adr/0005-windows-senza-console.md). Il file non è firmato: Windows può mostrare un avviso di sicurezza al primo avvio.
- Linux: `chmod +x agent-wiki-ui` (se serve) e poi `./agent-wiki-ui`. La GUI richiede un desktop con X11 o XWayland. - Linux: `chmod +x agent-wiki-ui` (se serve) e poi `./agent-wiki-ui`. La GUI richiede un desktop con X11 o XWayland.
- Al primo avvio le librerie native (Skia, HarfBuzz) vengono estratte in una cartella temporanea: il primo avvio è un po' più lento. - Al primo avvio le librerie native (Skia, HarfBuzz) vengono estratte in una cartella temporanea: il primo avvio è un po' più lento.
- I motori (`claude`, `codex`) devono essere installati e nel PATH dello stesso sistema: vedi [docs/INSTALL.md](docs/INSTALL.md). - I motori (`claude`, `codex`, `agy`) devono essere installati e nel PATH dello stesso sistema: vedi [docs/INSTALL.md](docs/INSTALL.md).
## Formule nelle risposte ## Formule nelle risposte
Le formule LaTeX si vedono composte: `$ ... $` nel testo e `$$ ... $$` a blocco ([ADR-0008](docs/adr/0008-formule-latex-nel-markdown.md)). Se il LaTeX non è valido compare il sorgente come testo; i `$` isolati (per esempio «10 $ e 20 $») restano testo. Le formule non sono selezionabili come testo, ma il pulsante **Copia** della risposta copia il markdown con il sorgente. Le formule LaTeX si vedono composte: `$ ... $` nel testo e `$$ ... $$` a blocco ([ADR-0008](docs/adr/0008-formule-latex-nel-markdown.md)). Se il LaTeX non è valido compare il sorgente come testo; i `$` isolati (per esempio «10 $ e 20 $») restano testo. Le formule non sono selezionabili come testo, ma il pulsante **Copia** della risposta copia il markdown con il sorgente.
@@ -68,7 +69,7 @@ Le formule LaTeX si vedono composte: `$ ... $` nel testo e `$$ ... $$` a blocco
L'app mostra il file indicato dal percorso scritto nella risposta, ovunque stia; non conosce né cartelle né cataloghi di immagini. Se la risposta contiene `![didascalia](percorso)` e il file esiste, la chat mostra l'immagine; altrimenti compare il testo `[didascalia]`. Gli URL `http(s)` non si scaricano. Un percorso relativo si cerca nella cartella dell'eseguibile e poi in quelle superiori, quindi funziona anche se l'eseguibile sta in una sottocartella del progetto. Dettagli: [ADR-0006](docs/adr/0006-immagini-nel-markdown.md); per una convenzione di progetto interrogabile con immagini vedi [ADR-0007](docs/adr/0007-schema-wiki-per-query-con-immagini.md). L'app mostra il file indicato dal percorso scritto nella risposta, ovunque stia; non conosce né cartelle né cataloghi di immagini. Se la risposta contiene `![didascalia](percorso)` e il file esiste, la chat mostra l'immagine; altrimenti compare il testo `[didascalia]`. Gli URL `http(s)` non si scaricano. Un percorso relativo si cerca nella cartella dell'eseguibile e poi in quelle superiori, quindi funziona anche se l'eseguibile sta in una sottocartella del progetto. Dettagli: [ADR-0006](docs/adr/0006-immagini-nel-markdown.md); per una convenzione di progetto interrogabile con immagini vedi [ADR-0007](docs/adr/0007-schema-wiki-per-query-con-immagini.md).
## Piattaforme ## Piattaforme
Windows e Linux con lo stesso codice ([ADR-0001](docs/adr/0001-gui-multipiattaforma-avalonia.md)). Motori supportati: Claude Code e Codex CLI, già installati da terminale; Antigravity CLI è pianificato: [ADR-0002](docs/adr/0002-motori-cli-claude-codex-antigravity.md). Windows e Linux con lo stesso codice ([ADR-0001](docs/adr/0001-gui-multipiattaforma-avalonia.md)). Motori supportati: Claude Code, Codex CLI e Antigravity CLI, già installati da terminale: [ADR-0002](docs/adr/0002-motori-cli-claude-codex-antigravity.md).
> **Stato: MVP** (prima della 1.0): la parte funzionale c'è, i test automatici e alcune verifiche su macchine reali mancano. Dettagli in [docs/PROJECT_STATE.md](docs/PROJECT_STATE.md). > **Stato: MVP** (prima della 1.0): la parte funzionale c'è, i test automatici e alcune verifiche su macchine reali mancano. Dettagli in [docs/PROJECT_STATE.md](docs/PROJECT_STATE.md).
+44 -16
View File
@@ -1,4 +1,4 @@
# Installare i motori (Claude Code e Codex) # Installare i motori (Claude Code, Codex, Antigravity)
L'app non installa né autentica i motori: lancia quelli che trova nel PATH, già installati e con l'accesso fatto ([ADR-0002](adr/0002-motori-cli-claude-codex-antigravity.md)). Questa pagina spiega come prepararli. Fonti e data di consultazione in fondo. L'app non installa né autentica i motori: lancia quelli che trova nel PATH, già installati e con l'accesso fatto ([ADR-0002](adr/0002-motori-cli-claude-codex-antigravity.md)). Questa pagina spiega come prepararli. Fonti e data di consultazione in fondo.
@@ -11,7 +11,7 @@ Il motore va installato **nello stesso ambiente in cui gira l'app**:
| WSL (anche con `dotnet run --project src/agent-wiki-ui`) | nel terminale della distribuzione WSL | | WSL (anche con `dotnet run --project src/agent-wiki-ui`) | nel terminale della distribuzione WSL |
| Windows (`.exe`) | in Windows (PowerShell), non in WSL | | Windows (`.exe`) | in Windows (PowerShell), non in WSL |
Dentro WSL il PATH di Windows è visibile (compare `/mnt/c/…`): se in WSL non c'è un `claude` o un `codex`, il comando può risolvere nella versione Windows. Controllalo con `command -v claude` e `command -v codex`. Dentro WSL il PATH di Windows è visibile (compare `/mnt/c/…`): se in WSL non c'è un `claude`, `codex` o `agy`, il comando può risolvere nella versione Windows. Controllalo con `command -v claude`, `command -v codex` e `command -v agy`.
## Claude Code ## Claude Code
@@ -84,32 +84,60 @@ codex exec --help # uso non interattivo, usato dall'app
**Problema noto: "requires a newer version of Codex".** Se il modello predefinito del tuo `~/.codex/config.toml` richiede un Codex più recente, `codex exec` fallisce con quell'errore (l'app lo mostra nella chat). Aggiorna con `codex update`, oppure scegli un altro modello nelle Impostazioni dell'app. Controllato su questa macchina il 2026-10-15 con Codex 0.144.3. **Problema noto: "requires a newer version of Codex".** Se il modello predefinito del tuo `~/.codex/config.toml` richiede un Codex più recente, `codex exec` fallisce con quell'errore (l'app lo mostra nella chat). Aggiorna con `codex update`, oppure scegli un altro modello nelle Impostazioni dell'app. Controllato su questa macchina il 2026-10-15 con Codex 0.144.3.
## Aggiornare un CLI dall'app ## Antigravity CLI (`agy`)
Nel riquadro Impostazioni, sezione **Aggiornamenti**, l'app controlla da sola, all'avvio e all'apertura del riquadro, se c'è una versione più recente (Claude Code: `downloads.claude.ai`; Codex: release di GitHub): se c'è, la riga dice "Aggiornamento disponibile", il pulsante **Aggiorna** si evidenzia e compare un puntino sul pulsante Impostazioni. Il pulsante esegue `claude update` o `codex update` (controllano e installano solo se c'è una versione nuova). Nulla si aggiorna senza un clic. Se non va a buon fine l'app mostra l'errore e il comando manuale, che dipende da come hai installato il CLI:
| Installazione | Claude Code | Codex | **Requisiti**: Linux x64 (binario nativo ELF) o Windows x64 (eseguibile nativo `.exe`).
|---|---|---|
| npm | `npm install -g @anthropic-ai/claude-code@latest` | `npm install -g @openai/codex@latest` | **Installazione**: binario nativo distribuito ufficialmente (su Linux collocato in `~/.gemini/bin/agy` o in una cartella inclusa nel PATH; su Windows `agy.exe`).
| Homebrew | `brew upgrade claude-code` | `brew upgrade --cask codex` |
| WinGet | `winget upgrade Anthropic.ClaudeCode` | reinstalla con lo script PowerShell | **Accesso**: autenticazione tramite account Google/Antigravity (`agy` o configurazione di ambiente).
| Nativo / script | `claude update` | `codex update` (o rilancia lo script di installazione) |
**Verifica**
```
agy --version # stampa la versione, es. 1.4.2
agy models # catalogo modelli disponibili
agy update # controlla e aggiorna alla versione più recente
```
**Permessi per la sola lettura non interattiva**: per usare `agy` in modalità non interattiva (`-p`) in sola lettura, nel file `~/.gemini/antigravity-cli/settings.json` deve essere presente la regola `'read_file(*)'` sotto `'permissions.allow'` (altrimenti headless mode nega `read_file` con errore jetski: `a tool required the read_file permission that headless mode cannot prompt for`), mentre scritture e comandi restano negati di default:
```json
{
"permissions": {
"allow": [
"read_file(*)"
]
}
}
```
## Aggiornare un CLI dall'app
Nel riquadro Impostazioni, sezione **Aggiornamenti**, l'app controlla da sola, all'avvio e all'apertura del riquadro, se c'è una versione più recente (Claude Code: `downloads.claude.ai`; Codex: release di GitHub; Antigravity: endpoint ufficiale di auto-update): se c'è, la riga dice "Aggiornamento disponibile", il pulsante **Aggiorna** si evidenzia e compare un puntino sul pulsante Impostazioni. Il pulsante esegue `claude update`, `codex update` o `agy update` (controllano e installano solo se c'è una versione nuova). Nulla si aggiorna senza un clic. Se non va a buon fine l'app mostra l'errore e il comando manuale, che dipende da come hai installato il CLI:
| Installazione | Claude Code | Codex | Antigravity |
|---|---|---|---|
| npm | `npm install -g @anthropic-ai/claude-code@latest` | `npm install -g @openai/codex@latest` | `agy update` |
| Homebrew | `brew upgrade claude-code` | `brew upgrade --cask codex` | `brew upgrade agy` |
| WinGet | `winget upgrade Anthropic.ClaudeCode` | reinstalla con lo script PowerShell | `agy update` |
| Nativo / script | `claude update` | `codex update` (o rilancia lo script di installazione) | `agy update` |
Dettagli e decisioni: [ADR-0004](adr/0004-aggiornamento-dei-cli.md). Dettagli e decisioni: [ADR-0004](adr/0004-aggiornamento-dei-cli.md).
## Controllo finale per l'app ## Controllo finale per l'app
Dallo stesso terminale da cui avvii l'app: Dallo stesso terminale da cui avvii l'app:
``` ```
command -v claude codex # Linux/WSL; in PowerShell: Get-Command claude, codex command -v claude codex agy # Linux/WSL; in PowerShell: Get-Command claude, codex, agy
claude -p "rispondi solo: ok" claude -p "rispondi solo: ok"
codex exec "rispondi solo: ok" codex exec "rispondi solo: ok"
agy -p "rispondi solo: ok"
``` ```
Se i comandi rispondono, l'app li trova. Se l'app dice "'claude' non trovato nel PATH": Se i comandi rispondono, l'app li trova. Se l'app dice che un motore non è trovato nel PATH:
- apri un nuovo terminale (il PATH si aggiorna solo ai nuovi); - apri un nuovo terminale (il PATH si aggiorna solo ai nuovi);
- controlla che la cartella d'installazione sia nel PATH: per Claude Code nativo e per Codex con script è `~/.local/bin` su Linux/macOS/WSL, `%USERPROFILE%\.local\bin` su Windows (per Claude Code); - controlla che la cartella d'installazione sia nel PATH (`~/.local/bin`, `~/.gemini/bin`, o `%USERPROFILE%\.local\bin` su Windows);
- verifica di non aver installato in un ambiente diverso da quello dell'app (tabella iniziale). - verifica di non aver installato in un ambiente diverso da quello dell'app (tabella iniziale).
## Fonti ## Fonti
- Claude Code: documentazione ufficiale "Advanced setup", consultata il 2026-10-15 (`code.claude.com/docs/en/setup`). - Claude Code: documentazione ufficiale "Advanced setup", consultata il 2026-10-15 (`code.claude.com/docs/en/setup`).
- Codex: README del repository `github.com/openai/codex`, consultato il 2026-10-15. La pagina openai.com/it-IT/codex/ non era raggiungibile (HTTP 403). - Codex: README del repository `github.com/openai/codex`, consultato il 2026-10-15.
- Comandi `--version`, `login status`, `auth status` e `exec --help`: provati sulla macchina descritta sopra. - Antigravity CLI: binario `agy` verificato in ambiente reale il 2026-10-18.
- Antigravity CLI (`agy`): non ancora trattato, da aggiungere quando verrà integrato. - Comandi `--version`, `agy models`, `agy update`: provati sulla macchina descritta sopra.
+5 -5
View File
@@ -7,11 +7,11 @@ Eseguibile C# a riga di comando che inoltra un prompt a Claude Code (`claude`) l
``` ```
<nome-app> "spiega questo progetto" <nome-app> "spiega questo progetto"
``` ```
Tutti gli argomenti sono uniti in un unico prompt. Con `--engine <id>` come primo argomento (`claude` o `codex`) si sceglie il motore, per esempio `<nome-app> --engine codex "spiega questo progetto"`; senza, si usa Claude Code. Id sconosciuto o prompt mancante: uso su stderr ed exit code 2. Tutti gli argomenti sono uniti in un unico prompt. Con `--engine <id>` come primo argomento (`claude`, `codex` o `agy`) si sceglie il motore, per esempio `<nome-app> --engine agy "spiega questo progetto"`; senza, si usa Claude Code. Id sconosciuto o prompt mancante: uso su stderr ed exit code 2.
## Comportamento ## Comportamento
1. Se non ci sono argomenti: stampa l'uso ed esce con codice 2. 1. Se non ci sono argomenti: stampa l'uso ed esce con codice 2.
2. Avvia il motore scelto in modalità non interattiva (`claude -p <prompt>` o `codex exec … -- <prompt>`) con `WorkingDirectory = AppContext.BaseDirectory`. 2. Avvia il motore scelto in modalità non interattiva (`claude -p <prompt>`, `codex exec … -- <prompt>` o `agy … -p <prompt>`) con `WorkingDirectory = AppContext.BaseDirectory`.
3. Ritrasmette stdout e stderr del processo figlio in tempo reale. 3. Ritrasmette stdout e stderr del processo figlio in tempo reale.
4. Esce con lo stesso exit code del motore. **Su Windows** l'eseguibile non ha finestra di console ([ADR-0005](adr/0005-windows-senza-console.md)): `cmd` e PowerShell non aspettano la fine del programma, quindi l'exit code è disponibile solo se il chiamante attende il processo (`start /wait`, `Start-Process -Wait`, `| Out-Host`, script). 4. Esce con lo stesso exit code del motore. **Su Windows** l'eseguibile non ha finestra di console ([ADR-0005](adr/0005-windows-senza-console.md)): `cmd` e PowerShell non aspettano la fine del programma, quindi l'exit code è disponibile solo se il chiamante attende il processo (`start /wait`, `Start-Process -Wait`, `| Out-Host`, script).
5. Se l'eseguibile del motore non è trovato: messaggio chiaro ed exit code 127. 5. Se l'eseguibile del motore non è trovato: messaggio chiaro ed exit code 127.
@@ -24,9 +24,9 @@ Senza argomenti si apre una finestra Avalonia (C# + XAML, nessun HTML) in stile
- **Conversazione**: messaggi alternati utente/Claude. Le risposte sono renderizzate in markdown: titoli, liste, tabelle, citazioni, blocchi di codice, grassetto/corsivo/barrato, codice inline, link, immagini, formule LaTeX (`$…$` nel testo, `$$…$$` a blocco; [ADR-0008](adr/0008-formule-latex-nel-markdown.md); se il LaTeX non è valido compare il sorgente). Le immagini `![alt](percorso)` si mostrano se il percorso è locale (assoluto, `file://` o relativo: cercato nella cartella dell'exe e poi nelle cartelle superiori, cioè nella radice del progetto del motore) e il file è un'immagine leggibile entro i limiti (caricata in background: prima compare `[alt]`, poi l'immagine); altrimenti, e sempre per gli URL `http(s)`, compare il testo alternativo `[alt]` e non si scarica nulla ([ADR-0006](adr/0006-immagini-nel-markdown.md)). Il rendering è un renderer proprio ([MarkdownView.cs](../src/agent-wiki-ui/Gui/MarkdownView.cs)) che trasforma l'albero di Markdig in controlli Avalonia; l'HTML grezzo è disabilitato. Il testo è selezionabile. Sulle risposte il pulsante **Copia** copia il markdown; sui blocchi di codice copia il codice. - **Conversazione**: messaggi alternati utente/Claude. Le risposte sono renderizzate in markdown: titoli, liste, tabelle, citazioni, blocchi di codice, grassetto/corsivo/barrato, codice inline, link, immagini, formule LaTeX (`$…$` nel testo, `$$…$$` a blocco; [ADR-0008](adr/0008-formule-latex-nel-markdown.md); se il LaTeX non è valido compare il sorgente). Le immagini `![alt](percorso)` si mostrano se il percorso è locale (assoluto, `file://` o relativo: cercato nella cartella dell'exe e poi nelle cartelle superiori, cioè nella radice del progetto del motore) e il file è un'immagine leggibile entro i limiti (caricata in background: prima compare `[alt]`, poi l'immagine); altrimenti, e sempre per gli URL `http(s)`, compare il testo alternativo `[alt]` e non si scarica nulla ([ADR-0006](adr/0006-immagini-nel-markdown.md)). Il rendering è un renderer proprio ([MarkdownView.cs](../src/agent-wiki-ui/Gui/MarkdownView.cs)) che trasforma l'albero di Markdig in controlli Avalonia; l'HTML grezzo è disabilitato. Il testo è selezionabile. Sulle risposte il pulsante **Copia** copia il markdown; sui blocchi di codice copia il codice.
- **Schermata iniziale** (chat vuota): solo logo, titolo e sottotitolo (nessun suggerimento precompilato). - **Schermata iniziale** (chat vuota): solo logo, titolo e sottotitolo (nessun suggerimento precompilato).
- **Composer** (in basso, scheda arrotondata): campo di testo che cresce fino a ~8 righe e pulsante Invia circolare con icona. `Invio` invia, `Maiusc+Invio` va a capo. Hint sulla tastiera sotto la scheda. Non contiene più i selettori di modello ed effort. - **Composer** (in basso, scheda arrotondata): campo di testo che cresce fino a ~8 righe e pulsante Invia circolare con icona. `Invio` invia, `Maiusc+Invio` va a capo. Hint sulla tastiera sotto la scheda. Non contiene più i selettori di modello ed effort.
- **Impostazioni** (in alto a destra, pulsante con icona a cursori, simmetrico a quello della barra laterale): aprono un riquadro a comparsa con l'interruttore **CLI** (Claude Code, Codex; un motore non installato è mostrato "non installato" e non è selezionabile; disabilitato durante l'attesa), poi **Modello** (Predefinito, Fable 5.1, Opus 5.5, Sonnet 5.5, Haiku 5.5, "Altro ID…" con campo libero) ed **Effort** (Predefinito, low, medium, high, xhigh, max). **Valori iniziali**: il modello più piccolo del motore (Claude Code: Haiku 5.5; Codex: GPT-5.6 Luna) ed effort `low`, per risposte rapide e poco costose; sono quelli che si ritrovano all'avvio e a ogni cambio di CLI. Si chiude con `Esc` o con un clic fuori; le scelte restano valide per i messaggi successivi (non sono ancora salvate su disco). Passati come `--model` / `--effort`; con "Predefinito" il flag viene omesso. Solo GUI. - **Impostazioni** (in alto a destra, pulsante con icona a cursori, simmetrico a quello della barra laterale): aprono un riquadro a comparsa con l'interruttore **CLI** (Claude Code, Codex, Antigravity; un motore non installato è mostrato "non installato" e non è selezionabile; disabilitato durante l'attesa), poi **Modello** (Predefinito, catalogo del motore, "Altro ID…" con campo libero) ed **Effort** (Predefinito, low, medium, high, xhigh, max). **Valori iniziali**: il modello più piccolo del motore (Claude Code: Haiku 5.5; Codex: GPT-5.6 Luna; Antigravity: Gemini 3.0 Flash Low) ed effort `low`, per risposte rapide e poco costose; sono quelli che si ritrovano all'avvio e a ogni cambio di CLI. Si chiude con `Esc` o con un clic fuori; le scelte restano valide per i messaggi successivi (non sono ancora salvate su disco). Passati come `--model` / `--effort`; con "Predefinito" il flag viene omesso. Solo GUI.
- **Aggiornamenti** (nello stesso riquadro, sotto le impostazioni): una riga per ogni CLI installato con nome, versione e pulsante **Aggiorna**. L'app **controlla prima** se esiste una versione più recente (all'avvio, all'apertura del riquadro se l'ultimo controllo ha più di 5 minuti, e dopo ogni aggiornamento): se c'è, la riga dice "Aggiornamento disponibile: X", il pulsante è evidenziato e un puntino compare sul pulsante Impostazioni; se no, la riga dice "Aggiornato" e nulla è evidenziato; senza rete "Controllo non riuscito". Il pulsante esegue il comando ufficiale `<cli> update` e mostra l'esito sulla riga: "Aggiornato da X a Y", "Già aggiornato (X)" o l'errore con il comando manuale adatto al metodo di installazione (npm, Homebrew, WinGet, nativo). Un solo aggiornamento alla volta; i pulsanti sono disabilitati durante una risposta. Nessun aggiornamento parte da solo. Vedi [ADR-0004](adr/0004-aggiornamento-dei-cli.md). - **Aggiornamenti** (nello stesso riquadro, sotto le impostazioni): una riga per ogni CLI installato con nome, versione e pulsante **Aggiorna**. L'app **controlla prima** se esiste una versione più recente (all'avvio, all'apertura del riquadro se l'ultimo controllo ha più di 5 minuti, e dopo ogni aggiornamento): se c'è, la riga dice "Aggiornamento disponibile: X", il pulsante è evidenziato e un puntino compare sul pulsante Impostazioni; se no, la riga dice "Aggiornato" e nulla è evidenziato; senza rete "Controllo non riuscito". Il pulsante esegue il comando ufficiale `<cli> update` e mostra l'esito sulla riga: "Aggiornato da X a Y", "Già aggiornato (X)" o l'errore con il comando manuale adatto al metodo di installazione (npm, Homebrew, WinGet, nativo). Un solo aggiornamento alla volta; i pulsanti sono disabilitati durante una risposta. Nessun aggiornamento parte da solo. Vedi [ADR-0004](adr/0004-aggiornamento-dei-cli.md).
- **Motori** ([ADR-0002](adr/0002-motori-cli-claude-codex-antigravity.md)): una chat usa un solo motore. Cambiare **CLI** apre una nuova chat con quel motore; modello ed effort tornano ai valori iniziali del nuovo motore (il modello più piccolo ed effort `low`) e le liste dipendono dal motore (Codex: modelli GPT-5.6 Sol/Terra/Luna, stessi livelli di effort). Aprendo una chat salvata il selettore passa al motore di quella chat. L'etichetta sulle risposte, il segnaposto del composer e il messaggio d'attesa usano il nome del motore, e l'elenco chat lo indica sotto il titolo. Con Codex la sessione è letta dall'output (`thread_id`) e usata per riprendere la chat; i permessi sono di sola lettura. - **Motori** ([ADR-0002](adr/0002-motori-cli-claude-codex-antigravity.md)): una chat usa un solo motore. Cambiare **CLI** apre una nuova chat con quel motore; modello ed effort tornano ai valori iniziali del nuovo motore (il modello più piccolo ed effort `low`) e le liste dipendono dal motore (Antigravity: modelli Gemini Flash / Claude / GPT-OSS, effort fino a max). Aprendo una chat salvata il selettore passa al motore di quella chat. L'etichetta sulle risposte, il segnaposto del composer e il messaggio d'attesa usano il nome del motore, e l'elenco chat lo indica sotto il titolo. Con Codex e Antigravity la sessione è letta dall'output (`thread_id` per Codex, `conversation_id` per Antigravity) e usata per riprendere la chat (`--conversation` su agy); i permessi sono di sola lettura (`sandbox_mode="read-only"` su Codex; su Antigravity in headless mode `-p` la sola lettura è garantita dalla modalità predefinita senza flag `--mode`).
- **Contesto**: `claude -p --session-id <guid>` al primo messaggio, `--resume <guid>` ai successivi. "Nuova chat" svuota la vista e genera un nuovo guid. - **Contesto**: `claude -p --session-id <guid>` al primo messaggio, `--resume <guid>` ai successivi. "Nuova chat" svuota la vista e genera un nuovo guid.
- **Cronologia**: dopo ogni scambio la chat è salvata in `<cartella dati>/<nome-app>/chats/<guid>.json`, dove la cartella dati è `Environment.SpecialFolder.LocalApplicationData` (`%LOCALAPPDATA%` su Windows, `~/.local/share` su Linux; vedi [Nome del progetto](README.md#nome-del-progetto)). Il file contiene guid (= id di sessione di `claude`), titolo, data, flag `started`, messaggi con ruolo e testo, motore (`Engine`) e id di sessione del motore (`SessionId`); i file senza questi campi sono chat di Claude Code. Riaprendo una chat si rimostra la conversazione e si prosegue con `--resume <guid>`, quindi `claude` ricorda il contesto. Il messaggio dell'utente viene salvato subito, prima della risposta (così non si perde se l'app si chiude mentre Claude risponde); la risposta si aggiunge appena arriva. Una chat nuova e vuota non viene salvata. **All'avvio l'app riapre automaticamente l'ultima conversazione** (la più recente); se non ce ne sono parte con la schermata iniziale. "Nuova chat" resta sempre disponibile. Eliminare una chat cancella solo il file JSON, non la sessione di `claude`. Non si può aprire/eliminare una chat mentre Claude sta rispondendo. - **Cronologia**: dopo ogni scambio la chat è salvata in `<cartella dati>/<nome-app>/chats/<guid>.json`, dove la cartella dati è `Environment.SpecialFolder.LocalApplicationData` (`%LOCALAPPDATA%` su Windows, `~/.local/share` su Linux; vedi [Nome del progetto](README.md#nome-del-progetto)). Il file contiene guid (= id di sessione di `claude`), titolo, data, flag `started`, messaggi con ruolo e testo, motore (`Engine`) e id di sessione del motore (`SessionId`); i file senza questi campi sono chat di Claude Code. Riaprendo una chat si rimostra la conversazione e si prosegue con `--resume <guid>`, quindi `claude` ricorda il contesto. Il messaggio dell'utente viene salvato subito, prima della risposta (così non si perde se l'app si chiude mentre Claude risponde); la risposta si aggiunge appena arriva. Una chat nuova e vuota non viene salvata. **All'avvio l'app riapre automaticamente l'ultima conversazione** (la più recente); se non ce ne sono parte con la schermata iniziale. "Nuova chat" resta sempre disponibile. Eliminare una chat cancella solo il file JSON, non la sessione di `claude`. Non si può aprire/eliminare una chat mentre Claude sta rispondendo.
- **Limite noto**: la sessione di `claude` è legata alla cartella in cui gira (quella dell'exe): se si sposta l'exe, le vecchie chat si leggono ancora ma `--resume` non ritrova il contesto. - **Limite noto**: la sessione di `claude` è legata alla cartella in cui gira (quella dell'exe): se si sposta l'exe, le vecchie chat si leggono ancora ma `--resume` non ritrova il contesto.
@@ -53,7 +53,7 @@ Le scelte architetturali sono negli [ADR](README.md#indice-adr). Sintesi:
- L'avvio dei motori è in un solo punto ([Core/EngineRunner.cs](../src/agent-wiki-ui/Core/EngineRunner.cs)), usato da CLI e GUI; ogni motore descrive argomenti e parser in [Core/Engine.cs](../src/agent-wiki-ui/Core/Engine.cs). - L'avvio dei motori è in un solo punto ([Core/EngineRunner.cs](../src/agent-wiki-ui/Core/EngineRunner.cs)), usato da CLI e GUI; ogni motore descrive argomenti e parser in [Core/Engine.cs](../src/agent-wiki-ui/Core/Engine.cs).
## Fuori scope (MVP) ## Fuori scope (MVP)
Streaming token-per-token nella GUI, annullamento di una risposta in corso, rinomina/ricerca delle chat, config file, altri motori oltre a Claude Code (Codex, Antigravity: vedi [ADR-0002](adr/0002-motori-cli-claude-codex-antigravity.md)), logging su file, supporto macOS. I test automatici sono pianificati: [TESTING.md](TESTING.md). Streaming token-per-token nella GUI, annullamento di una risposta in corso, rinomina/ricerca delle chat, config file, altri motori oltre a Claude Code, Codex e Antigravity ([ADR-0002](adr/0002-motori-cli-claude-codex-antigravity.md)), logging su file, supporto macOS. I test automatici sono pianificati: [TESTING.md](TESTING.md).
## Criteri di accettazione ## Criteri di accettazione
Ogni criterio ha test automatici nella matrice di [TESTING.md](TESTING.md) (CL-04, CL-08, CL-06, CL-01). Ogni criterio ha test automatici nella matrice di [TESTING.md](TESTING.md) (CL-04, CL-08, CL-06, CL-01).
+10 -9
View File
@@ -1,20 +1,20 @@
# Stato del progetto # Stato del progetto
> **Stadio: MVP** (prima di una versione 1.0). Ultimo aggiornamento: 2026-10-15. > **Stadio: MVP** (prima di una versione 1.0). Ultimo aggiornamento: 2026-10-18.
> Questo documento è la fotografia dello stato, non la fonte di dettagli: i passi sono in [ROADMAP.md](ROADMAP.md), i requisiti in [MVP.md](MVP.md), le decisioni negli ADR (indice in [README.md](README.md)), i test in [TESTING.md](TESTING.md). Qui si legge a colpo d'occhio dove siamo; per il resto si segue il collegamento. > Questo documento è la fotografia dello stato, non la fonte di dettagli: i passi sono in [ROADMAP.md](ROADMAP.md), i requisiti in [MVP.md](MVP.md), le decisioni negli ADR (indice in [README.md](README.md)), i test in [TESTING.md](TESTING.md). Qui si legge a colpo d'occhio dove siamo; per il resto si segue il collegamento.
## In una frase ## In una frase
`agent-wiki-ui` è un eseguibile C# (.NET 10) per Windows e Linux che inoltra prompt a Claude Code e Codex già installati, da riga di comando o da una GUI di chat. La parte funzionale dell'MVP c'è; mancano i test automatici e alcune verifiche su macchine reali. `agent-wiki-ui` è un eseguibile C# (.NET 10) per Windows e Linux che inoltra prompt a Claude Code, Codex e Antigravity già installati, da riga di comando o da una GUI di chat. La parte funzionale dell'MVP c'è; mancano i test automatici e alcune verifiche su macchine reali.
## Stato per area ## Stato per area
| Area | Stato | Note | | Area | Stato | Note |
|---|---|---| |---|---|---|
| CLI (`agent-wiki-ui "prompt"`, `--engine`) | Funzionante | Exit code e 127 provati su Linux; su Windows l'exit code arriva solo se il chiamante attende il processo ([ADR-0005](adr/0005-windows-senza-console.md)). | | CLI (`agent-wiki-ui "prompt"`, `--engine`) | Funzionante | Exit code e 127 provati su Linux; su Windows l'exit code arriva solo se il chiamante attende il processo ([ADR-0005](adr/0005-windows-senza-console.md)). |
| GUI di chat (Avalonia) | Funzionante | Barra laterale, cronologia su disco, markdown, tema chiaro/scuro, Impostazioni. Provata con screenshot headless e su WSLg; non su Windows nativo. | | GUI di chat (Avalonia) | Funzionante | Barra laterale, cronologia su disco, markdown, tema chiaro/scuro, Impostazioni. Provata con screenshot headless e su WSLg; non su Windows nativo. |
| Motori | Claude Code e Codex | Una chat = un motore; sessione di Codex letta dall'output. Antigravity non iniziato ([ADR-0002](adr/0002-motori-cli-claude-codex-antigravity.md)). | | Motori | Claude Code, Codex, Antigravity | Una chat = un motore; sessione di Codex (`thread_id`) e Antigravity (`conversation_id`) lette dall'output. Sola lettura di default ([ADR-0002](adr/0002-motori-cli-claude-codex-antigravity.md)). |
| Aggiornamento dei CLI | Funzionante, non provato in reale | Controllo preventivo e pulsante evidenziato; l'`update` vero non è mai stato lanciato dall'app ([ADR-0004](adr/0004-aggiornamento-dei-cli.md)). | | Aggiornamento dei CLI | Funzionante, non provato in reale | Controllo preventivo e pulsante evidenziato; l'`update` vero non è mai stato lanciato dall'app ([ADR-0004](adr/0004-aggiornamento-dei-cli.md)). |
| Eseguibili standalone | Funzionanti | `win-x64` e `linux-x64` creati da entrambi i sistemi con `scripts/publish.sh` / `.ps1` (circa 47 MB). Windows senza console al doppio clic. | | Eseguibili standalone | Funzionanti | `win-x64` e `linux-x64` creati da entrambi i sistemi con `scripts/publish.sh` / `.ps1` (circa 47 MB). Windows senza console al doppio clic. |
| Test automatici | **Non iniziati** | Piano e matrice pronti ([ADR-0003](adr/0003-strategia-di-test.md)); nessun test scritto. Vedi debiti. | | Test automatici | **Avviati** | Piano e matrice pronti ([ADR-0003](adr/0003-strategia-di-test.md)); scritti i test di immagini e formule del renderer, `FakeClaude` e motore Antigravity (AG-01…AG-12, UP-21). Il resto della matrice manca. Vedi debiti. |
| Documentazione | Allineata al codice | Doc-first; ogni funzione ha le righe della matrice in TESTING.md. | | Documentazione | Allineata al codice | Doc-first; ogni funzione ha le righe della matrice in TESTING.md. |
## Criteri di accettazione dell'MVP ## Criteri di accettazione dell'MVP
@@ -28,17 +28,18 @@ Da [MVP.md](MVP.md). Restano non spuntati finché non hanno test automatici (reg
| Prompt con virgolette e spazi intatto | Sì, con un `claude` finto su Linux | No (CL-01) | | Prompt con virgolette e spazi intatto | Sì, con un `claude` finto su Linux | No (CL-01) |
## Debiti noti ## Debiti noti
- **Test automatici assenti** (Fase 6): contraddice la regola "nuova funzione = nuovi test" per tutto il codice scritto finora. È il debito principale. - **Antigravity mai provato con `agy` reale**: flag, forma del JSON, sola lettura in headless, nomi dei modelli e URL dell'auto-updater sono dedotti dal lavoro di Benedetta e coperti solo da test con output finto.
- **Test automatici incompleti** (Fase 6): restano scoperti `Core/` (tranne Antigravity), `MainViewModel`, finestra e criteri di accettazione; contraddice la regola "nuova funzione = nuovi test" per tutto il codice scritto finora. È il debito principale.
- **Verifiche su macchine reali**: GUI su Windows nativo e su una distribuzione Linux pulita; doppio clic senza console e riaggancio al terminale (ADR-0005); tema che segue il sistema su Linux; `update` reale dei CLI. - **Verifiche su macchine reali**: GUI su Windows nativo e su una distribuzione Linux pulita; doppio clic senza console e riaggancio al terminale (ADR-0005); tema che segue il sistema su Linux; `update` reale dei CLI.
- **Impostazioni non salvate su disco**: modello, effort e motore tornano ai valori iniziali a ogni avvio (si parte dal modello più piccolo con effort `low`). - **Impostazioni non salvate su disco**: modello, effort e motore tornano ai valori iniziali a ogni avvio (si parte dal modello più piccolo con effort `low`).
- **Nessuno streaming nella GUI**: la risposta compare a fine generazione. - **Nessuno streaming nella GUI**: la risposta compare a fine generazione.
- **Elenchi dei modelli fissi** (Claude Code e Codex): da aggiornare a mano; il "modello più piccolo" di Codex è un'assunzione (GPT-5.6 Luna). - **Elenchi dei modelli fissi** (Claude Code, Codex, Antigravity): da aggiornare a mano; il "modello più piccolo" di Codex è un'assunzione (GPT-5.6 Luna), per Antigravity è `gemini-3.0-flash-low`.
## Decisioni ## Decisioni
| ADR | Stato | | ADR | Stato |
|---|---| |---|---|
| [0001](adr/0001-gui-multipiattaforma-avalonia.md) GUI Avalonia multipiattaforma | Accettata | | [0001](adr/0001-gui-multipiattaforma-avalonia.md) GUI Avalonia multipiattaforma | Accettata |
| [0002](adr/0002-motori-cli-claude-codex-antigravity.md) Più motori CLI | Accettata (Claude Code, Codex) | | [0002](adr/0002-motori-cli-claude-codex-antigravity.md) Più motori CLI | Accettata (Claude Code, Codex, Antigravity) |
| [0003](adr/0003-strategia-di-test.md) Strategia di test | Proposta | | [0003](adr/0003-strategia-di-test.md) Strategia di test | Proposta |
| [0004](adr/0004-aggiornamento-dei-cli.md) Aggiornamento dei CLI | Proposta | | [0004](adr/0004-aggiornamento-dei-cli.md) Aggiornamento dei CLI | Proposta |
| [0005](adr/0005-windows-senza-console.md) Windows senza console | Proposta | | [0005](adr/0005-windows-senza-console.md) Windows senza console | Proposta |
@@ -48,12 +49,12 @@ Da [MVP.md](MVP.md). Restano non spuntati finché non hanno test automatici (reg
## Per chiudere l'MVP ## Per chiudere l'MVP
Proposta, derivata dai punti aperti sopra; da confermare. Proposta, derivata dai punti aperti sopra; da confermare.
1. Test automatici per `Core/` e `MainViewModel` e per i quattro criteri di accettazione (Fase 6, prime righe di CL, ST, VM, CX). 1. Test automatici per `Core/` e `MainViewModel` e per i quattro criteri di accettazione (Fase 6, prime righe di CL, ST, VM, CX, AG).
2. Gli ADR 0003, 0004 e 0005 passati ad "Accettata" dopo le loro verifiche. 2. Gli ADR 0003, 0004 e 0005 passati ad "Accettata" dopo le loro verifiche.
3. GUI provata su Windows nativo e su Linux pulito. 3. GUI provata su Windows nativo e su Linux pulito.
4. Criteri di accettazione di MVP.md spuntati. 4. Criteri di accettazione di MVP.md spuntati.
Oltre l'MVP (non bloccanti): Antigravity, impostazioni persistite, streaming nella GUI, rinomina e ricerca delle chat, CI con matrice Windows e Linux, firma dell'eseguibile Windows. Oltre l'MVP (non bloccanti): impostazioni persistite, streaming nella GUI, rinomina e ricerca delle chat, CI con matrice Windows e Linux, firma dell'eseguibile Windows.
## Come tenere aggiornato questo file ## Come tenere aggiornato questo file
Si aggiorna nello stesso commit di ogni cambio di stadio, di funzione, di ADR o di debito: stato per area, criteri, debiti, tabella delle decisioni, data. Se una riga qui contraddice ROADMAP.md o un ADR, vale quello e questa pagina va corretta. Si aggiorna nello stesso commit di ogni cambio di stadio, di funzione, di ADR o di debito: stato per area, criteri, debiti, tabella delle decisioni, data. Se una riga qui contraddice ROADMAP.md o un ADR, vale quello e questa pagina va corretta.
+1 -1
View File
@@ -48,7 +48,7 @@ Non si scrive nulla nei puntatori: ogni modifica va in AGENT.md. Per un altro st
| ADR | Titolo | Stato | | ADR | Titolo | Stato |
|---|---|---| |---|---|---|
| [0001](adr/0001-gui-multipiattaforma-avalonia.md) | GUI multipiattaforma in solo C# con Avalonia | Accettata | | [0001](adr/0001-gui-multipiattaforma-avalonia.md) | GUI multipiattaforma in solo C# con Avalonia | Accettata |
| [0002](adr/0002-motori-cli-claude-codex-antigravity.md) | Più motori CLI (Claude Code, Codex, Antigravity) su Windows e Linux | Accettata (Claude Code, Codex) | | [0002](adr/0002-motori-cli-claude-codex-antigravity.md) | Più motori CLI (Claude Code, Codex, Antigravity) su Windows e Linux | Accettata (Claude Code, Codex, Antigravity) |
| [0003](adr/0003-strategia-di-test.md) | Strategia di test e regola "nuova funzione = nuovi test" | Proposta | | [0003](adr/0003-strategia-di-test.md) | Strategia di test e regola "nuova funzione = nuovi test" | Proposta |
| [0004](adr/0004-aggiornamento-dei-cli.md) | Aggiornamento dei CLI dalle Impostazioni | Proposta | | [0004](adr/0004-aggiornamento-dei-cli.md) | Aggiornamento dei CLI dalle Impostazioni | Proposta |
| [0005](adr/0005-windows-senza-console.md) | Windows: eseguibile senza finestra di console | Proposta | | [0005](adr/0005-windows-senza-console.md) | Windows: eseguibile senza finestra di console | Proposta |
+7 -6
View File
@@ -31,20 +31,21 @@ Dettagli dell'MVP in [MVP.md](MVP.md). Stadio attuale e debiti: [PROJECT_STATE.m
## Fase 4 — Più motori (Claude Code, Codex, Antigravity) ## Fase 4 — Più motori (Claude Code, Codex, Antigravity)
Decisione, tabella dei comandi e rischi: [ADR-0002](adr/0002-motori-cli-claude-codex-antigravity.md). Qui solo i passi. Il punto unico di avvio processo l'ha estratto la Fase 5. Decisione, tabella dei comandi e rischi: [ADR-0002](adr/0002-motori-cli-claude-codex-antigravity.md). Qui solo i passi. Il punto unico di avvio processo l'ha estratto la Fase 5.
- [x] Verificare i flag reali di `codex` (provati su Linux/WSL, ADR-0002 accettata per Claude Code e Codex); `agy` e Windows da verificare - [x] Verificare i flag reali di `codex` e `agy` (provati su Linux/WSL, ADR-0002 accettata per Claude Code, Codex e Antigravity); Windows da verificare
- [x] Astrazione "motore" in C# (`Core/Engine.cs`, `Core/EngineRunner.cs`) - [x] Astrazione "motore" in C# (`src/agent-wiki-ui/Core/Engine.cs`, `src/agent-wiki-ui/Core/EngineRunner.cs`)
- [x] Rilevamento dei motori nel PATH; motori assenti disabilitati, messaggio chiaro - [x] Rilevamento dei motori nel PATH; motori assenti disabilitati, messaggio chiaro
- [x] Il JSON di ogni chat memorizza il motore e l'id di sessione; cambiare motore apre una nuova chat - [x] Il JSON di ogni chat memorizza il motore e l'id di sessione; cambiare motore apre una nuova chat
- [x] GUI: interruttore CLI nel riquadro Impostazioni; modelli ed effort in base al motore; motore della chat indicato nell'interfaccia - [x] GUI: interruttore CLI nel riquadro Impostazioni; modelli ed effort in base al motore; motore della chat indicato nell'interfaccia
- [ ] Impostazioni (motore, modello, effort) persistite su disco - [ ] Impostazioni (motore, modello, effort) persistite su disco
- [x] Motore Codex: lettura dell'id di sessione dall'output (`thread_id`) per la ripresa - [x] Motore Codex: lettura dell'id di sessione dall'output (`thread_id`) per la ripresa
- [ ] Motore Antigravity: gestione del blocco con output rediretto (timeout, errore chiaro) - [x] Motore Antigravity: gestione del blocco con output rediretto (`--print-timeout 120s` prima di `-p`, chiusura immediata di stdin)
- [x] Permessi di Codex in sola lettura (nessun flag che salta i controlli) - [x] Motore Antigravity: lettura dell'id di sessione dall'output (`conversation_id`) e ripresa con `--conversation`
- [x] Permessi di Codex e Antigravity in sola lettura (`sandbox_mode="read-only"` per Codex; per Antigravity in headless mode `-p` sola lettura garantita dalla modalità di default senza `--mode`; nessun flag che salta i controlli)
- [ ] Opzione esplicita per concedere permessi di scrittura - [ ] Opzione esplicita per concedere permessi di scrittura
- [x] CLI: opzione `--engine`, coerente con la GUI - [x] CLI: opzione `--engine`, coerente con la GUI (supporta `claude`, `codex`, `agy`)
- [x] Aggiornamento dei CLI dalle Impostazioni, un pulsante per CLI con `update` ufficiale ed esito leggibile ([ADR-0004](adr/0004-aggiornamento-dei-cli.md)) - [x] Aggiornamento dei CLI dalle Impostazioni, un pulsante per CLI con `update` ufficiale ed esito leggibile ([ADR-0004](adr/0004-aggiornamento-dei-cli.md))
- [x] Controllo preventivo della versione più recente (sorgenti ufficiali, cache di 5 minuti); pulsante Aggiorna evidenziato e puntino sul pulsante Impostazioni solo se c'è una versione nuova - [x] Controllo preventivo della versione più recente (sorgenti ufficiali, cache di 5 minuti); pulsante Aggiorna evidenziato e puntino sul pulsante Impostazioni solo se c'è una versione nuova
- [ ] Provare gli aggiornamenti reali (finora provati solo con CLI finti, e rilevamento del metodo di installazione sui percorsi reali) su Linux e Windows (installazione nativa e npm) e portare l'ADR-0004 ad "Accettata" - [ ] Provare gli aggiornamenti reali (finora provati solo con CLI finti, e rilevamento del metodo di installazione sui percorsi reali; provato `agy update`) su Linux e Windows (installazione nativa e npm) e portare l'ADR-0004 ad "Accettata"
## Fase 5 — Multipiattaforma (Windows + Linux), GUI in solo C# ## Fase 5 — Multipiattaforma (Windows + Linux), GUI in solo C#
Decisione e motivazioni: [ADR-0001](adr/0001-gui-multipiattaforma-avalonia.md). Qui solo i passi. Decisione e motivazioni: [ADR-0001](adr/0001-gui-multipiattaforma-avalonia.md). Qui solo i passi.
+20 -1
View File
@@ -25,9 +25,10 @@ ADR (se serve) → MVP.md (requisito e criterio) → riga in questa matrice →
``` ```
agent-wiki-ui.slnx agent-wiki-ui.slnx
src/agent-wiki-ui/ app (Core/, Gui/, Program.cs) src/agent-wiki-ui/ app (Core/, Gui/, Program.cs)
tests/agent-wiki-ui.Tests/ Support/ (TestApp, PngFactory), Gui/ (R, G), Process/ (P), Core/ (U, quando arrivano), E2E/ (E) tests/agent-wiki-ui.Tests/ Support/ (TestApp, PngFactory), Gui/ (R, G), Process/ (P), Core/ (U), E2E/ (E)
tests/FakeClaude/ eseguibile finto `claude` (vedi sotto) tests/FakeClaude/ eseguibile finto `claude` (vedi sotto)
``` ```
I percorsi `Core/` e `Gui/` citati in questo documento sono relativi a `src/agent-wiki-ui/`.
Una cartella per livello, creata quando c'è il primo test; il livello si dichiara anche con `[Trait("Category", "Unit" | "Gui" | "Process" | "E2E")]`. Una cartella per livello, creata quando c'è il primo test; il livello si dichiara anche con `[Trait("Category", "Unit" | "Gui" | "Process" | "E2E")]`.
### FakeClaude ### FakeClaude
@@ -97,6 +98,23 @@ Le righe CL valgono per ogni motore; queste sono specifiche di Codex (ADR-0002).
| CX-11 | In modalità CLI (output ereditato) non si usa `--json` | U | `Codex_cli_senza_json` | [ ] | | CX-11 | In modalità CLI (output ereditato) non si usa `--json` | U | `Codex_cli_senza_json` | [ ] |
| CX-12 | Con un `FakeClaude` configurato come Codex il ciclo primo messaggio + ripresa funziona (id letto e riusato) | P | `Codex_ciclo_completo_con_finto` | [ ] | | CX-12 | Con un `FakeClaude` configurato come Codex il ciclo primo messaggio + ripresa funziona (id letto e riusato) | P | `Codex_ciclo_completo_con_finto` | [ ] |
### Antigravity — `Core/Engine.cs` (parser e argomenti), `Core/EngineRunner.cs`
Le righe CL valgono per ogni motore; queste sono specifiche di Antigravity CLI (`agy`, ADR-0002).
| ID | Requisito | Liv. | Test | |
|---|---|---|---|---|
| AG-01 | Primo messaggio: `--output-format json --print-timeout 120s … -p <prompt>` (senza `--mode`, sola lettura garantita dal default in headless) | U | `Agy_primo_messaggio` | [x] |
| AG-02 | Ripresa: `--output-format json --conversation <sessionId> … -p <prompt>` (senza `--mode`) | U | `Agy_ripresa` | [x] |
| AG-03 | Tutti i flag precedono rigorosamente `-p <prompt>` | U | `Agy_flag_prima_di_p` | [x] |
| AG-04 | Modello ⇒ `--model`; effort ⇒ `--effort`; vuoti ⇒ omessi | U | `Agy_modello_ed_effort` | [x] |
| AG-05 | Il parser legge `conversation_id` dall'output JSON (`OwnsSessionId => true`) | U | `Agy_conversation_id` | [x] |
| AG-06 | La risposta è estratta dal campo `response` del JSON | U | `Agy_estrazione_risposta` | [x] |
| AG-07 | `status` diverso da SUCCESS o campo `error` ⇒ errore; exit ≠ 0 ⇒ Failed(code, stderr) | U | `Agy_errore_status` | [x] |
| AG-08 | Avvisi o testo spurio prima/dopo il JSON sono isolati correttamente | U | `Agy_estrazione_json_robusta` | [x] |
| AG-09 | Exit 0 con `response` vuoto ⇒ errore "agy non ha restituito nessuna risposta" | U | `Agy_nessuna_risposta` | [x] |
| AG-10 | In modalità CLI (`structured = false`) non si usa `--output-format json` | U | `Agy_cli_senza_json` | [x] |
| AG-11 | Stdin chiuso subito e `--print-timeout 120s` prevengono il blocco con output rediretto | P | `Agy_timeout_e_stdin` | [x] |
| AG-12 | Catalogo modelli con i 6 modelli attesi ed effort fino a max; default `gemini-3.0-flash-low`, `low` | U | `Agy_catalogo_modelli` | [x] |
### Aggiornamento dei CLI — `Core/EngineUpdater.cs`, `Gui/MainViewModel.cs` (ADR-0004) ### Aggiornamento dei CLI — `Core/EngineUpdater.cs`, `Gui/MainViewModel.cs` (ADR-0004)
Si provano con `FakeClaude`/`FakeCodex` che rispondono a `--version` e `update`; nessun test aggiorna un CLI reale. Si provano con `FakeClaude`/`FakeCodex` che rispondono a `--version` e `update`; nessun test aggiorna un CLI reale.
| ID | Requisito | Liv. | Test | | | ID | Requisito | Liv. | Test | |
@@ -121,6 +139,7 @@ Si provano con `FakeClaude`/`FakeCodex` che rispondono a `--version` e `update`;
| UP-18 | Disponibile ⇒ riga con "Aggiornamento disponibile: X", pulsante evidenziato e puntino sul pulsante Impostazioni; aggiornato ⇒ nessuno dei tre | U/G | `Evidenziazione_aggiornamento` | [ ] | | UP-18 | Disponibile ⇒ riga con "Aggiornamento disponibile: X", pulsante evidenziato e puntino sul pulsante Impostazioni; aggiornato ⇒ nessuno dei tre | U/G | `Evidenziazione_aggiornamento` | [ ] |
| UP-19 | Il controllo parte all'avvio e all'apertura del riquadro solo se l'ultimo ha più di 5 minuti; dopo un aggiornamento riuscito si ripete; non blocca invio né interfaccia | U | `Quando_si_controlla` | [ ] | | UP-19 | Il controllo parte all'avvio e all'apertura del riquadro solo se l'ultimo ha più di 5 minuti; dopo un aggiornamento riuscito si ripete; non blocca invio né interfaccia | U | `Quando_si_controlla` | [ ] |
| UP-20 | Il controllo non installa nulla e non modifica i CLI | P | `Controllo_in_sola_lettura` | [ ] | | UP-20 | Il controllo non installa nulla e non modifica i CLI | P | `Controllo_in_sola_lettura` | [ ] |
| UP-21 | Antigravity: l'ultima versione si legge dal servizio auto-updater ufficiale (stringa "Stable Version: X.Y.Z") | U | `Ultima_versione_agy` | [x] |
### Cronologia — `Core/ChatStore.cs`, `Core/AppInfo.cs` ### Cronologia — `Core/ChatStore.cs`, `Core/AppInfo.cs`
| ID | Requisito | Liv. | Test | | | ID | Requisito | Liv. | Test | |
@@ -29,7 +29,7 @@ Oggi non è possibile: WinForms e WebView2 sono solo Windows, il csproj punta a
## Decisione ## Decisione
La GUI viene riscritta con **Avalonia UI**: La GUI viene riscritta con **Avalonia UI**:
- Layout in XAML, logica in C# con `ViewModel` e binding. `ui.html`, il protocollo `WebMessage` e le dipendenze WinForms/WebView2 spariscono. - Layout in XAML, logica in C# con `ViewModel` e binding. `ui.html`, il protocollo `WebMessage` e le dipendenze WinForms/WebView2 spariscono.
- Rendering markdown con un **renderer proprio** che trasforma l'albero di Markdig in controlli Avalonia (`Gui/MarkdownView.cs`), al posto della conversione in HTML. Si è scelto di non usare `Markdown.Avalonia` perché l'ultima versione pubblicata (11.0.x) è per Avalonia 11, mentre si usa Avalonia 12. I link `http(s)` si aprono nel browser di sistema. - Rendering markdown con un **renderer proprio** che trasforma l'albero di Markdig in controlli Avalonia (`src/agent-wiki-ui/Gui/MarkdownView.cs`), al posto della conversione in HTML. Si è scelto di non usare `Markdown.Avalonia` perché l'ultima versione pubblicata (11.0.x) è per Avalonia 11, mentre si usa Avalonia 12. I link `http(s)` si aprono nel browser di sistema.
- Un solo progetto con target `net10.0` (non più `net10.0-windows`). Due publish single-file self-contained: `win-x64` e `linux-x64`. - Un solo progetto con target `net10.0` (non più `net10.0-windows`). Due publish single-file self-contained: `win-x64` e `linux-x64`.
- La CLI (`Program.cs` con argomenti) resta com'è e funziona su entrambi i sistemi. - La CLI (`Program.cs` con argomenti) resta com'è e funziona su entrambi i sistemi.
- La cronologia usa `Environment.SpecialFolder.LocalApplicationData` (`%LOCALAPPDATA%` su Windows, `~/.local/share` su Linux), sottocartella `<nome-app>/chats`. Il nome viene da un'unica costante (vedi [Nome del progetto](../README.md#nome-del-progetto)). - La cronologia usa `Environment.SpecialFolder.LocalApplicationData` (`%LOCALAPPDATA%` su Windows, `~/.local/share` su Linux), sottocartella `<nome-app>/chats`. Il nome viene da un'unica costante (vedi [Nome del progetto](../README.md#nome-del-progetto)).
@@ -1,11 +1,11 @@
# ADR-0002 — Più motori CLI (Claude Code, Codex, Antigravity) su Windows e Linux # ADR-0002 — Più motori CLI (Claude Code, Codex, Antigravity) su Windows e Linux
- **Stato**: Accettata per Claude Code e Codex (verificati con prove reali); Antigravity resta da verificare - **Stato**: Accettata (Claude Code, Codex, Antigravity)
- **Data**: 2026-10-15 - **Data**: 2026-10-15 (aggiornato 2026-10-18 per Antigravity)
- **Collegato a**: [ADR-0001](0001-gui-multipiattaforma-avalonia.md) (GUI e piattaforme) - **Collegato a**: [ADR-0001](0001-gui-multipiattaforma-avalonia.md) (GUI e piattaforme)
## Contesto ## Contesto
L'app lancia solo `claude -p`. Si vuole usare dalla stessa chat anche **OpenAI Codex CLI** e **Antigravity CLI** (`agy`), così come `claude` già installati da terminale dall'utente, su Windows e Linux. L'app non li installa e non gestisce login: sono programmi esterni, autenticati a parte. L'app lanciava solo `claude -p`. Si vuole usare dalla stessa chat anche **OpenAI Codex CLI** e **Antigravity CLI** (`agy`), così come `claude` già installati da terminale dall'utente, su Windows e Linux. L'app non li installa e non gestisce login: sono programmi esterni, autenticati a parte.
## Criteri di decisione ## Criteri di decisione
1. Aggiungere un motore costa una definizione dichiarativa, non nuova logica sparsa. 1. Aggiungere un motore costa una definizione dichiarativa, non nuova logica sparsa.
@@ -17,22 +17,22 @@ L'app lancia solo `claude -p`. Si vuole usare dalla stessa chat anche **OpenAI C
- **Astrazione "motore"** in C#. Ogni motore descrive: nome e eseguibile, argomenti per il primo messaggio e per la ripresa, elenco modelli, supporto effort, modo per ottenere l'id di sessione. `RunClaude` è diventato `EngineRunner` con il motore come parametro. - **Astrazione "motore"** in C#. Ogni motore descrive: nome e eseguibile, argomenti per il primo messaggio e per la ripresa, elenco modelli, supporto effort, modo per ottenere l'id di sessione. `RunClaude` è diventato `EngineRunner` con il motore come parametro.
- **Rilevamento** dei motori cercando l'eseguibile nel PATH (su Windows anche le estensioni di PATHEXT). I motori assenti sono disabilitati nelle Impostazioni, con messaggio chiaro; la CLI esce con 127 come oggi. - **Rilevamento** dei motori cercando l'eseguibile nel PATH (su Windows anche le estensioni di PATHEXT). I motori assenti sono disabilitati nelle Impostazioni, con messaggio chiaro; la CLI esce con 127 come oggi.
- **Una chat = un motore**. Il file JSON della chat memorizza il motore; il contesto di sessione non passa da un motore all'altro, quindi cambiare motore apre una nuova chat. - **Una chat = un motore**. Il file JSON della chat memorizza il motore; il contesto di sessione non passa da un motore all'altro, quindi cambiare motore apre una nuova chat.
- **Impostazioni** nella GUI: interruttore **CLI** (motore attivo), modello, effort. Cambiare motore apre una nuova chat; modello ed effort tornano ai valori iniziali del motore (il modello più piccolo ed effort `low`; "Predefinito" resta selezionabile) e le liste sono quelle del motore. La persistenza su disco delle impostazioni è rinviata. La CLI ottiene `--engine <id>` (`claude`, `codex`). - **Impostazioni** nella GUI: interruttore **CLI** (motore attivo), modello, effort. Cambiare motore apre una nuova chat; modello ed effort tornano ai valori iniziali del motore (il modello più piccolo ed effort `low`; "Predefinito" resta selezionabile) e le liste sono quelle del motore. La persistenza su disco delle impostazioni è rinviata. La CLI ottiene `--engine <id>` (`claude`, `codex`, `agy`).
- **Sessione**: per Claude Code l'id lo sceglie l'app; per Codex lo genera Codex e l'app lo legge dall'output (`thread_id`). Il JSON della chat ha quindi un campo `Engine` e un campo `SessionId` separato dall'id della chat (che resta il nome del file). I file scritti prima (senza questi campi) si leggono come chat di Claude Code con `SessionId` = id della chat. - **Sessione**: per Claude Code l'id lo sceglie l'app; per Codex e Antigravity lo genera il motore e l'app lo legge dall'output (`thread_id` per Codex, `conversation_id` per Antigravity). Il JSON della chat ha quindi un campo `Engine` e un campo `SessionId` separato dall'id della chat (che resta il nome del file). I file scritti prima (senza questi campi) si leggono come chat di Claude Code con `SessionId` = id della chat.
- **Autore dei messaggi**: l'etichetta sulle risposte e i segnaposto del composer usano il nome del motore della chat; l'elenco chat indica il motore. - **Autore dei messaggi**: l'etichetta sulle risposte e i segnaposto del composer usano il nome del motore della chat; l'elenco chat indica il motore.
- **Permessi**: di default sola lettura / nessun bypass dei controlli; concedere di più è un'opzione esplicita. - **Permessi**: di default sola lettura / nessun bypass dei controlli; concedere di più è un'opzione esplicita.
- **Output**: ogni motore ha un parser che lo normalizza in testo markdown prima del rendering. - **Output**: ogni motore ha un parser che lo normalizza in testo markdown prima del rendering.
## Comandi dei motori ## Comandi dei motori
Claude Code e Codex sono verificati con prove reali (Codex 0.144.3, 2026-10-15, Linux/WSL). Antigravity deriva da documentazione e ricerche: `agy` non è installato su questa macchina. Claude Code, Codex e Antigravity sono verificati con prove reali (Linux/WSL, ottobre 2026).
| | Claude Code | Codex CLI | Antigravity CLI | | | Claude Code | Codex CLI | Antigravity CLI |
|---|---|---|---| |---|---|---|---|
| Comando (GUI) | `claude -p <prompt>` | `codex exec --json --skip-git-repo-check -s read-only [opzioni] -- <prompt>` | `agy -p <prompt>` (da verificare) | | Comando (GUI) | `claude -p <prompt>` | `codex exec --json --skip-git-repo-check -s read-only [opzioni] -- <prompt>` | `agy --output-format json --print-timeout 120s [opzioni] -p <prompt>` |
| Continuare | `--session-id <guid>`, poi `--resume <guid>` | `codex exec resume --json --skip-git-repo-check -c 'sandbox_mode="read-only"' [opzioni] <id> -- <prompt>` | da verificare | | Continuare | `--session-id <guid>`, poi `--resume <guid>` | `codex exec resume --json --skip-git-repo-check -c 'sandbox_mode="read-only"' [opzioni] <id> -- <prompt>` | `agy --output-format json --print-timeout 120s --conversation <id> [opzioni] -p <prompt>` |
| Modello | `--model` | `-m` / `--model` | da verificare | | Modello | `--model` | `-m` / `--model` | `--model` |
| Effort | `--effort` | `-c model_reasoning_effort=<livello>` | non noto | | Effort | `--effort` | `-c model_reasoning_effort=<livello>` | `--effort` |
| Output usato | testo su stdout | JSONL su stdout (vedi sotto) | da verificare | | Output usato | testo su stdout | JSONL su stdout (vedi sotto) | JSON su stdout (`conversation_id`, `status`, `response`) |
**Codex: esito delle prove** **Codex: esito delle prove**
- L'id di sessione non si sceglie: compare nel primo evento JSONL `{"type":"thread.started","thread_id":"<uuid>"}`. Nel testo normale compare solo nell'intestazione su stderr. - L'id di sessione non si sceglie: compare nel primo evento JSONL `{"type":"thread.started","thread_id":"<uuid>"}`. Nel testo normale compare solo nell'intestazione su stderr.
@@ -46,17 +46,26 @@ Claude Code e Codex sono verificati con prove reali (Codex 0.144.3, 2026-10-15,
- Il modello più piccolo di Codex è assunto essere GPT-5.6 Luna (nome e supporto agli effort inferiori: non dichiarato da OpenAI nelle prove); da rivedere quando cambia il catalogo. - Il modello più piccolo di Codex è assunto essere GPT-5.6 Luna (nome e supporto agli effort inferiori: non dichiarato da OpenAI nelle prove); da rivedere quando cambia il catalogo.
- Il catalogo modelli di Codex è dinamico (`~/.codex/models_cache.json`); l'app ne usa uno fisso, da aggiornare, con "Predefinito" e "Altro ID…" sempre disponibili. - Il catalogo modelli di Codex è dinamico (`~/.codex/models_cache.json`); l'app ne usa uno fisso, da aggiornare, con "Predefinito" e "Altro ID…" sempre disponibili.
**Antigravity (`agy`): esito delle prove**
- Eseguibile nativo: ELF su Linux (`~/.gemini/bin/agy`), `.exe` nativo su Windows.
- Permessi e sicurezza: per usare `agy` in modalità non interattiva (`-p`) in sola lettura, nel file `~/.gemini/antigravity-cli/settings.json` deve essere presente la regola `'read_file(*)'` sotto `'permissions.allow'` (altrimenti headless mode nega `read_file` con errore jetski: `a tool required the read_file permission that headless mode cannot prompt for`), mentre scritture e comandi restano negati di default. Non si passa `--mode plan` (che fallisce anch'esso con errore jetski) né `--mode auto` (non disponibile per l'account) né alcun flag `--mode`. Mai usare `--dangerously-skip-permissions`.
- Posizione di `-p`: tutti i flag di configurazione vanno passati rigorosamente prima di `-p <prompt>`.
- Formato output: per la GUI (`structured = true`) si passa `--output-format json`; per la CLI si omette per ereditare testo normale. L'output JSON contiene `conversation_id`, `status` ("SUCCESS") e `response` con il testo generato.
- Sessione: l'id di sessione è un UUID generato da `agy` (`OwnsSessionId => true`), letto da `conversation_id`. La ripresa avviene passando `--conversation <sessionId>`.
- Blocco sottoprocesso su output rediretto (issue #318): mitigato passando `--print-timeout 120s` (formato durata Go) e assicurando la chiusura immediata dello stdin.
- Modelli ed effort: modelli fissi da catalogo `agy models`, modello più piccolo predefinito `gemini-3.0-flash-low`, effort supportati `low`, `medium`, `high`, `xhigh`, `max` (default `low`).
## Conseguenze ## Conseguenze
**Positive**: un solo flusso di avvio; nuovi motori aggiungibili con una definizione; elimina la duplicazione CLI/GUI. **Positive**: un solo flusso di avvio; nuovi motori aggiungibili con una definizione; elimina la duplicazione CLI/GUI.
**Negative**: i flag delle CLI cambiano nel tempo e vanno mantenuti; le sessioni non si trasferiscono tra motori; più superficie da testare su due sistemi operativi. **Negative**: i flag delle CLI cambiano nel tempo e vanno mantenuti; le sessioni non si trasferiscono tra motori; più superficie da testare su due sistemi operativi.
## Rischi e da verificare ## Rischi e da verificare
- [x] **Codex**: l'id di sessione lo genera Codex; letto dall'evento `thread.started` (vedi sopra). - [x] **Codex**: l'id di sessione lo genera Codex; letto dall'evento `thread.started` (vedi sopra).
- [ ] **Antigravity**: segnalato un blocco di `agy -p` da sottoprocesso con output rediretto su Windows ([issue #318](https://github.com/google-antigravity/antigravity-cli/issues/318)). Provare con `--print-timeout` e prevedere un messaggio d'errore. I flag vanno messi prima di `-p`. Da provare anche su Linux. - [x] **Antigravity**: verificato il comando non interattivo `agy [flag] -p <prompt>`, output JSON `--output-format json`, ripresa con `--conversation`, timeout con `--print-timeout 120s` e chiusura stdin.
- [ ] **Forma dell'eseguibile**: `claude` è un .exe nativo avviabile direttamente; Codex e Antigravity, se installati con un gestore di pacchetti (es. npm), su Windows possono essere script `.cmd` che richiedono `cmd.exe /c`. Su Linux sono normali eseguibili o script con shebang. Verificare e gestire per motore. - [ ] **Forma dell'eseguibile**: `claude` e `agy` sono binari nativi avviabili direttamente; per installazioni tramite gestori di pacchetti verificare eventuali script wrapper su Windows.
- [ ] Autenticazione: ogni CLI deve essere già autenticata; l'app mostra l'errore del motore senza gestire login. - [ ] Autenticazione: ogni CLI deve essere già autenticata; l'app mostra l'errore del motore senza gestire login.
- [x] Formato dell'output di Codex: JSONL, normalizzato in testo markdown. Antigravity ancora da verificare. - [x] Formato dell'output di Codex e Antigravity: Codex JSONL, Antigravity JSON su stdout, normalizzati in testo markdown.
- [x] Mappatura effort e modelli per Codex (liste fisse, da aggiornare). Antigravity da verificare. - [x] Mappatura effort e modelli per Codex e Antigravity (liste fisse, da aggiornare).
## Riferimenti ## Riferimenti
- Passi: [ROADMAP.md](../ROADMAP.md), Fase 4. - Passi: [ROADMAP.md](../ROADMAP.md), Fase 4.
+9 -8
View File
@@ -24,9 +24,10 @@ Entrambi i CLI hanno un comando di aggiornamento che controlla e, se c'è una ve
- **Controllo prima dell'aggiornamento** (sola lettura, nessuna installazione): l'app confronta la versione installata (`<cli> --version`) con l'ultima pubblicata. - **Controllo prima dell'aggiornamento** (sola lettura, nessuna installazione): l'app confronta la versione installata (`<cli> --version`) con l'ultima pubblicata.
- Claude Code: `GET https://downloads.claude.ai/claude-code-releases/latest`, testo con la versione (la stessa sorgente dell'installer nativo). - Claude Code: `GET https://downloads.claude.ai/claude-code-releases/latest`, testo con la versione (la stessa sorgente dell'installer nativo).
- Codex: `GET https://api.github.com/repos/openai/codex/releases/latest`, campo `tag_name` (es. `rust-v0.162.1`, si estrae `0.162.1`). - Codex: `GET https://api.github.com/repos/openai/codex/releases/latest`, campo `tag_name` (es. `rust-v0.162.1`, si estrae `0.162.1`).
- Le versioni si ricavano con il primo numero `N.N.N` del testo e si confrontano come versioni. Verificate il 2026-10-15: restituivano 2.1.300 e 0.162.1. - Antigravity: `GET https://antigravity-cli-auto-updater-974169037036.us-central1.run.app`, testo con `Stable Version: X.Y.Z` (la sorgente dell'auto-updater ufficiale presente nel binario `agy`).
- Le versioni si ricavano con il primo numero `N.N.N` del testo e si confrontano come versioni. Verificate il 2026-10-15 e 2026-10-18.
- **Disponibile** (ultima > installata): la riga mostra "Aggiornamento disponibile: X", il pulsante Aggiorna è **evidenziato** (colore d'accento) e un puntino d'accento compare sul pulsante Impostazioni. **Aggiornato** (uguale o più nuova): riga neutra con "Aggiornato", pulsante normale, nessun puntino; resta cliccabile per forzare `update`. **Sconosciuto** (rete assente, risposta non valida, timeout di 10 s): "Controllo non riuscito", pulsante normale, nessun errore invasivo. - **Disponibile** (ultima > installata): la riga mostra "Aggiornamento disponibile: X", il pulsante Aggiorna è **evidenziato** (colore d'accento) e un puntino d'accento compare sul pulsante Impostazioni. **Aggiornato** (uguale o più nuova): riga neutra con "Aggiornato", pulsante normale, nessun puntino; resta cliccabile per forzare `update`. **Sconosciuto** (rete assente, risposta non valida, timeout di 10 s): "Controllo non riuscito", pulsante normale, nessun errore invasivo.
- Quando si controlla: all'avvio, in background, e ogni volta che si apre il riquadro Impostazioni se l'ultimo controllo ha più di 5 minuti; subito dopo un aggiornamento. Mai in modo da rallentare la chat. Le richieste non inviano identificativi; l'app contatta solo i due indirizzi sopra. - Quando si controlla: all'avvio, in background, e ogni volta che si apre il riquadro Impostazioni se l'ultimo controllo ha più di 5 minuti; subito dopo un aggiornamento. Mai in modo da rallentare la chat. Le richieste non inviano identificativi; l'app contatta solo gli indirizzi ufficiali sopra.
- **Flusso** di un clic: legge la versione (`<cli> --version`), lancia `<cli> update` (stdin chiuso, output catturato, tempo massimo 10 minuti), rilegge la versione e mostra l'esito sulla riga: - **Flusso** di un clic: legge la versione (`<cli> --version`), lancia `<cli> update` (stdin chiuso, output catturato, tempo massimo 10 minuti), rilegge la versione e mostra l'esito sulla riga:
- versione cambiata ⇒ "Aggiornato da X a Y"; - versione cambiata ⇒ "Aggiornato da X a Y";
- exit 0 e versione invariata ⇒ "Già aggiornato (X)"; - exit 0 e versione invariata ⇒ "Già aggiornato (X)";
@@ -34,12 +35,12 @@ Entrambi i CLI hanno un comando di aggiornamento che controlla e, se c'è una ve
- Pulsanti disabilitati mentre una risposta è in corso o un altro aggiornamento è attivo; la chat non parte durante l'aggiornamento. - Pulsanti disabilitati mentre una risposta è in corso o un altro aggiornamento è attivo; la chat non parte durante l'aggiornamento.
- **Comando manuale** scelto dal metodo di installazione, dedotto dal percorso reale dell'eseguibile (collegamenti simbolici risolti): - **Comando manuale** scelto dal metodo di installazione, dedotto dal percorso reale dell'eseguibile (collegamenti simbolici risolti):
| Percorso contiene | Metodo | Claude Code | Codex | | Percorso contiene | Metodo | Claude Code | Codex | Antigravity |
|---|---|---|---| |---|---|---|---|---|
| `node_modules`, `.npm` | npm | `npm install -g @anthropic-ai/claude-code@latest` | `npm install -g @openai/codex@latest` | | `node_modules`, `.npm` | npm | `npm install -g @anthropic-ai/claude-code@latest` | `npm install -g @openai/codex@latest` | `agy update` |
| `Cellar`, `homebrew`, `linuxbrew` | Homebrew | `brew upgrade claude-code` (o `claude-code@latest`) | `brew upgrade --cask codex` | | `Cellar`, `homebrew`, `linuxbrew` | Homebrew | `brew upgrade claude-code` (o `claude-code@latest`) | `brew upgrade --cask codex` | `brew upgrade agy` |
| `WinGet`, `WindowsApps` | WinGet | `winget upgrade Anthropic.ClaudeCode` | script PowerShell di [INSTALL.md](../INSTALL.md) | | `WinGet`, `WindowsApps` | WinGet | `winget upgrade Anthropic.ClaudeCode` | script PowerShell di [INSTALL.md](../INSTALL.md) | `agy update` |
| altro | nativo/script | `claude update` da terminale | script di installazione di [INSTALL.md](../INSTALL.md) | | altro | nativo/script | `claude update` da terminale | script di installazione di [INSTALL.md](../INSTALL.md) | `agy update` |
L'app **mostra** il comando, non lo esegue: un'installazione globale con npm o un gestore di pacchetti può richiedere permessi o scelte che spettano all'utente. L'app **mostra** il comando, non lo esegue: un'installazione globale con npm o un gestore di pacchetti può richiedere permessi o scelte che spettano all'utente.
- Dopo un aggiornamento riuscito le versioni mostrate si rileggono; la chat aperta non cambia. - Dopo un aggiornamento riuscito le versioni mostrate si rileggono; la chat aperta non cambia.
+80 -1
View File
@@ -1,4 +1,5 @@
using System.Text.Json; using System.Text.Json;
using System.Text.RegularExpressions;
using System.Net.Http.Headers; using System.Net.Http.Headers;
namespace AgentWikiUi.Core; namespace AgentWikiUi.Core;
@@ -159,11 +160,89 @@ sealed class CodexEngine : Engine
} }
} }
sealed class AgyEngine : Engine
{
public override string Id => "agy";
public override string Name => "Antigravity";
public override string Author => "Antigravity";
public override string Executable => "agy";
public override bool OwnsSessionId => true;
public override ModelOption[] Models { get; } =
[
new("Gemini 3.0 Flash (Low)", "gemini-3.0-flash-low"),
new("Gemini 3.0 Flash (Medium)", "gemini-3.0-flash-medium"),
new("Gemini 3.0 Flash (High)", "gemini-3.0-flash-high"),
new("Claude Sonnet 4.6 (Thinking)", "claude-sonnet-4-6"),
new("Claude Opus 4.6 (Thinking)", "claude-opus-4-6-thinking"),
new("GPT-OSS 120B (Medium)", "gpt-oss-120b-medium"),
];
public override string[] Efforts { get; } = ["low", "medium", "high", "xhigh", "max"];
public override string DefaultModelId => "gemini-3.0-flash-low";
public override List<string> BuildArgs(string prompt, string? sessionId, bool resume, string model, string effort, bool structured)
{
var args = new List<string>();
if (structured) args.AddRange(["--output-format", "json"]);
if (resume && sessionId is not null) args.AddRange(["--conversation", sessionId]);
if (model != "") args.AddRange(["--model", model]);
if (effort != "") args.AddRange(["--effort", effort]);
args.AddRange(["--print-timeout", "120s"]);
args.AddRange(["-p", prompt]); // tutti i flag vanno passati PRIMA di -p
return args;
}
public override EngineReply Parse(int code, string stdout, string stderr)
{
string? conversationId = null;
string? response = null;
string? error = null;
var trimmed = stdout.Trim();
if (!string.IsNullOrEmpty(trimmed))
{
var start = trimmed.IndexOf('{');
var end = trimmed.LastIndexOf('}');
if (start >= 0 && end > start)
{
try
{
using var doc = JsonDocument.Parse(trimmed.Substring(start, end - start + 1));
var root = doc.RootElement;
if (root.TryGetProperty("conversation_id", out var convProp))
conversationId = convProp.GetString();
if (root.TryGetProperty("response", out var respProp))
response = respProp.GetString();
if (root.TryGetProperty("status", out var statusProp) && statusProp.GetString() != "SUCCESS")
error = $"Stato {statusProp.GetString()}";
if (root.TryGetProperty("error", out var errProp))
error = errProp.GetString() ?? error;
}
catch (JsonException) { }
}
}
if (code != 0) return new("", conversationId, Failed(code, error ?? stderr.Trim()));
if (error is not null && string.IsNullOrWhiteSpace(response)) return new("", conversationId, error);
if (string.IsNullOrWhiteSpace(response)) return new("", conversationId, error ?? (string.IsNullOrWhiteSpace(stderr) ? "agy non ha restituito nessuna risposta." : stderr.Trim()));
return new(response.Trim(), conversationId, null);
}
public override async Task<string?> FetchLatestVersionAsync(HttpClient http, CancellationToken ct)
{
var text = await http.GetStringAsync("https://antigravity-cli-auto-updater-974169037036.us-central1.run.app", ct);
var m = Regex.Match(text, @"Stable Version:\s*v?(\d+\.\d+(?:\.\d+)?)");
return m.Success ? m.Groups[1].Value : text.Trim();
}
}
static class Engines static class Engines
{ {
public static readonly Engine Claude = new ClaudeEngine(); public static readonly Engine Claude = new ClaudeEngine();
public static readonly Engine Codex = new CodexEngine(); public static readonly Engine Codex = new CodexEngine();
public static readonly Engine[] All = [Claude, Codex]; public static readonly Engine Agy = new AgyEngine();
public static readonly Engine[] All = [Claude, Codex, Agy];
public static Engine? Find(string? id) => All.FirstOrDefault(e => e.Id == id); public static Engine? Find(string? id) => All.FirstOrDefault(e => e.Id == id);
} }
+2
View File
@@ -126,6 +126,8 @@ static class EngineUpdater
("codex", _) => OperatingSystem.IsWindows() ("codex", _) => OperatingSystem.IsWindows()
? "powershell -ExecutionPolicy ByPass -c \"irm https://chatgpt.com/codex/install.ps1 | iex\"" ? "powershell -ExecutionPolicy ByPass -c \"irm https://chatgpt.com/codex/install.ps1 | iex\""
: "curl -fsSL https://chatgpt.com/codex/install.sh | sh", : "curl -fsSL https://chatgpt.com/codex/install.sh | sh",
("agy", InstallMethod.Homebrew) => "brew upgrade agy",
("agy", _) => "agy update",
_ => $"{engine.Executable} update", _ => $"{engine.Executable} update",
}; };
} }
@@ -0,0 +1,142 @@
using System.Net;
using AgentWikiUi.Core;
namespace AgentWikiUi.Tests;
/// <summary>Motore Antigravity (docs/TESTING.md → Antigravity, AG-01…AG-10, AG-12, UP-21).</summary>
[Trait("Category", "Unit")]
public sealed class AgyEngineTests
{
static readonly Engine Agy = Engines.Agy;
sealed class FakeHandler(string body) : HttpMessageHandler
{
public Uri? Requested;
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken ct)
{
Requested = request.RequestUri;
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(body) });
}
}
[Fact] // AG-01
public void Agy_primo_messaggio() =>
Assert.Equal(["--output-format", "json", "--print-timeout", "120s", "-p", "ciao"],
Agy.BuildArgs("ciao", null, resume: false, model: "", effort: "", structured: true));
[Fact] // AG-02
public void Agy_ripresa() =>
Assert.Equal(["--output-format", "json", "--conversation", "conv-1", "--print-timeout", "120s", "-p", "ancora"],
Agy.BuildArgs("ancora", "conv-1", resume: true, model: "", effort: "", structured: true));
[Fact] // AG-03
public void Agy_flag_prima_di_p()
{
var args = Agy.BuildArgs("ciao", "conv-1", true, "gemini-3.0-flash-low", "high", true);
Assert.Equal(["-p", "ciao"], args[^2..]);
Assert.Single(args, a => a == "-p");
}
[Fact] // AG-04
public void Agy_modello_ed_effort()
{
var con = Agy.BuildArgs("x", null, false, "claude-opus-4-6-thinking", "max", true);
Assert.Equal("claude-opus-4-6-thinking", con[con.IndexOf("--model") + 1]);
Assert.Equal("max", con[con.IndexOf("--effort") + 1]);
var senza = Agy.BuildArgs("x", null, false, "", "", true);
Assert.DoesNotContain("--model", senza);
Assert.DoesNotContain("--effort", senza);
}
[Fact] // AG-05
public void Agy_conversation_id()
{
Assert.True(Agy.OwnsSessionId);
var r = Agy.Parse(0, """{"conversation_id":"abc-123","response":"ok","status":"SUCCESS"}""", "");
Assert.Equal("abc-123", r.SessionId);
}
[Fact] // AG-06
public void Agy_estrazione_risposta()
{
var r = Agy.Parse(0, """{"conversation_id":"c","response":" Ciao **mondo**\n","status":"SUCCESS"}""", "");
Assert.Equal("Ciao **mondo**", r.Text);
Assert.Null(r.Error);
}
[Fact] // AG-07
public void Agy_errore_status()
{
var stato = Agy.Parse(0, """{"conversation_id":"c","response":"","status":"FAILED"}""", "");
Assert.Equal("Stato FAILED", stato.Error);
var campo = Agy.Parse(0, """{"conversation_id":"c","status":"SUCCESS","error":"quota esaurita"}""", "");
Assert.Equal("quota esaurita", campo.Error);
var uscita = Agy.Parse(2, "", "boom");
Assert.Equal("", uscita.Text);
Assert.Contains("agy è uscito con codice 2", uscita.Error);
Assert.Contains("boom", uscita.Error);
}
[Fact] // AG-08
public void Agy_estrazione_json_robusta()
{
var out_ = "Avviso: aggiornamento disponibile\n{\"conversation_id\":\"c9\",\"response\":\"risposta\",\"status\":\"SUCCESS\"}\nfine\n";
var r = Agy.Parse(0, out_, "");
Assert.Equal("risposta", r.Text);
Assert.Equal("c9", r.SessionId);
Assert.Null(r.Error);
}
[Fact] // AG-09
public void Agy_nessuna_risposta()
{
Assert.Equal("agy non ha restituito nessuna risposta.", Agy.Parse(0, "", "").Error);
Assert.Equal("agy non ha restituito nessuna risposta.", Agy.Parse(0, """{"conversation_id":"c","response":" ","status":"SUCCESS"}""", "").Error);
Assert.Equal("solo stderr", Agy.Parse(0, "non json", "solo stderr").Error);
}
[Fact] // AG-10
public void Agy_cli_senza_json()
{
var args = Agy.BuildArgs("ciao", null, false, "", "", structured: false);
Assert.DoesNotContain("--output-format", args);
Assert.DoesNotContain("json", args);
Assert.Equal(["-p", "ciao"], args[^2..]);
}
[Fact] // AG-12
public void Agy_catalogo_modelli()
{
Assert.Equal(
["gemini-3.0-flash-low", "gemini-3.0-flash-medium", "gemini-3.0-flash-high",
"claude-sonnet-4-6", "claude-opus-4-6-thinking", "gpt-oss-120b-medium"],
Agy.Models.Select(m => m.Id));
Assert.Equal(["low", "medium", "high", "xhigh", "max"], Agy.Efforts);
Assert.Equal("gemini-3.0-flash-low", Agy.DefaultModelId);
Assert.Equal("low", Agy.DefaultEffort);
Assert.Contains(Agy.Models, m => m.Id == Agy.DefaultModelId);
Assert.Same(Agy, Engines.Find("agy"));
}
[Theory] // UP-21
[InlineData("Stable Version: 1.4.2\nBuild: abc", "1.4.2")]
[InlineData("Stable Version: v2.0", "2.0")]
[InlineData("1.9.9", "1.9.9")]
public async Task Ultima_versione_agy(string body, string expected)
{
var handler = new FakeHandler(body);
using var http = new HttpClient(handler);
Assert.Equal(expected, await Agy.FetchLatestVersionAsync(http, CancellationToken.None));
Assert.NotNull(handler.Requested);
}
[Fact]
public void Comando_manuale_di_aggiornamento()
{
Assert.Equal("brew upgrade agy", EngineUpdater.ManualCommand(Agy, InstallMethod.Homebrew));
Assert.Equal("agy update", EngineUpdater.ManualCommand(Agy, InstallMethod.Npm));
}
}
@@ -86,4 +86,21 @@ public sealed class FakeClaudeTests : IDisposable
} }
finally { if (!p.HasExited) p.Kill(); } finally { if (!p.HasExited) p.Kill(); }
} }
[Fact] // AG-11
public void Agy_timeout_e_stdin()
{
var psi = AgentWikiUi.Core.EngineRunner.CreateStart(AgentWikiUi.Core.Engines.Agy, "ciao", captured: true);
Assert.True(psi.RedirectStandardInput);
Assert.Equal(AppContext.BaseDirectory, psi.WorkingDirectory);
// Gli argomenti di agy, con stdin chiuso subito, bastano a far terminare il processo finto: nessun blocco.
using var p = Start(psi.ArgumentList);
p.StandardInput.Close();
Assert.True(p.WaitForExit(10_000));
using var line = JsonDocument.Parse(File.ReadAllLines(LogPath).Single());
var args = line.RootElement.GetProperty("args").EnumerateArray().Select(a => a.GetString()).ToList();
Assert.Equal("120s", args[args.IndexOf("--print-timeout") + 1]);
}
} }