Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
310512d070 | ||
|
|
6080a65edc | ||
|
|
dc7dbbd3bb | ||
|
|
28201fbbda | ||
|
|
3587f35c2d | ||
|
|
b6574776b6 | ||
|
|
6bbaf7192f |
No files matched your search
@@ -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,30 +17,31 @@ 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).
|
||||||
- [Core/AppInfo.cs](src/agent-wiki-ui/Core/AppInfo.cs): nome dell'app, letto dall'`AssemblyName` del csproj (unica sorgente).
|
- [Core/AppInfo.cs](src/agent-wiki-ui/Core/AppInfo.cs): nome dell'app, letto dall'`AssemblyName` del csproj (unica sorgente).
|
||||||
- [Gui/MainViewModel.cs](src/agent-wiki-ui/Gui/MainViewModel.cs): stato e logica della chat (invio, apertura/eliminazione, salvataggio subito all'invio e di nuovo alla risposta, riapertura dell'ultima chat all'avvio, interruttore CLI: cambiare motore apre una nuova chat; aggiornamento dei CLI con un solo aggiornamento alla volta, bloccando invio e cambio motore).
|
- [Gui/MainViewModel.cs](src/agent-wiki-ui/Gui/MainViewModel.cs): stato e logica della chat (invio, apertura/eliminazione, salvataggio subito all'invio e di nuovo alla risposta, riapertura dell'ultima chat all'avvio, interruttore CLI: cambiare motore apre una nuova chat; aggiornamento dei CLI con un solo aggiornamento alla volta, bloccando invio e cambio motore).
|
||||||
- [Gui/MainWindow.axaml](src/agent-wiki-ui/Gui/MainWindow.axaml) (+ `.cs`): layout XAML con binding compilati. [Gui/App.axaml](src/agent-wiki-ui/Gui/App.axaml): token di colore come risorse di tema chiaro/scuro e stili; specifiche in `docs/MVP.md` → "Design (UI/UX)". Cambiando l'aspetto aggiornare quella sezione.
|
- [Gui/MainWindow.axaml](src/agent-wiki-ui/Gui/MainWindow.axaml) (+ `.cs`): layout XAML con binding compilati. [Gui/App.axaml](src/agent-wiki-ui/Gui/App.axaml): token di colore come risorse di tema chiaro/scuro e stili; specifiche in `docs/MVP.md` → "Design (UI/UX)". Cambiando l'aspetto aggiornare quella sezione.
|
||||||
- [Gui/MarkdownView.cs](src/agent-wiki-ui/Gui/MarkdownView.cs): renderer markdown proprio (albero Markdig → controlli Avalonia, `DisableHtml`); la parte sulle immagini è in [Gui/MarkdownView.Images.cs](src/agent-wiki-ui/Gui/MarkdownView.Images.cs) (`partial`). Le immagini si mostrano solo da file locale (ADR-0006), mai da URL; i percorsi relativi si cercano nella cartella dell'exe e poi nelle superiori (radice della wiki).
|
- [Gui/MarkdownView.cs](src/agent-wiki-ui/Gui/MarkdownView.cs): renderer markdown proprio (albero Markdig → controlli Avalonia, `DisableHtml`); la parte sulle immagini è in [Gui/MarkdownView.Images.cs](src/agent-wiki-ui/Gui/MarkdownView.Images.cs) (`partial`); le formule LaTeX (ADR-0008) sono in [Gui/MarkdownView.Math.cs](src/agent-wiki-ui/Gui/MarkdownView.Math.cs) (`partial`, libreria `Sylinko.CSharpMath.Avalonia`). Le immagini si mostrano solo da file locale (ADR-0006), mai da URL; i percorsi relativi si cercano nella cartella dell'exe e poi nelle superiori (radice della wiki).
|
||||||
- Dipendenze NuGet: Avalonia (+ Desktop, Themes.Fluent), Markdig.
|
- Dipendenze NuGet: Avalonia (+ Desktop, Themes.Fluent), Markdig.
|
||||||
|
|
||||||
## 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).
|
||||||
|
|||||||
@@ -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,24 +60,16 @@ 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).
|
||||||
|
|
||||||
## Immagini nelle risposte (wiki con catalogo di figure)
|
## Formule nelle risposte
|
||||||
Non esiste (ancora) uno standard per cartella e catalogo delle immagini: la struttura qui sotto è solo un **esempio** e può cambiare. L'app non la conosce: mostra il file indicato dal percorso scritto nella risposta, ovunque stia.
|
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.
|
||||||
|
|
||||||
Se la risposta contiene `` 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 con una **wiki** (cartella di pagine markdown con un indice, un catalogo delle figure e le immagini) anche se l'eseguibile sta in una sottocartella dedicata alle sole query:
|
## Immagini nelle risposte
|
||||||
```
|
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 `` 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).
|
||||||
<wiki>/ radice del progetto, con CLAUDE.md (schema completo della wiki)
|
|
||||||
index.md indice delle pagine, con gli id delle figure di ogni pagina
|
|
||||||
<catalogo-figure>.json catalogo: metadati di ogni figura (file, didascalia, posizione, avvertenze)
|
|
||||||
<cartella-immagini>/<file> le immagini
|
|
||||||
query/CLAUDE.md istruzioni solo-query (sola lettura, instradamento per indici, regole per le figure)
|
|
||||||
query/agent-wiki-ui l'eseguibile, da avviare da qui: il motore legge sia query/CLAUDE.md sia quello della radice
|
|
||||||
```
|
|
||||||
Il catalogo lo legge il motore, non l'app: il `CLAUDE.md` chiede di trovare la figura nel catalogo e di citarla con un percorso relativo alla radice (``), con sotto la didascalia, la posizione e il link alla nota della figura. L'app mostra l'immagine e lascia la didascalia come testo markdown. Dettagli: [ADR-0006](docs/adr/0006-immagini-nel-markdown.md). Per preparare una wiki alle query (indice con la mappa delle figure, catalogo, istruzioni solo-query) c'è una convenzione, [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
@@ -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.
|
||||||
+7
-7
@@ -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.
|
||||||
@@ -21,12 +21,12 @@ Senza argomenti si apre una finestra Avalonia (C# + XAML, nessun HTML) in stile
|
|||||||
|
|
||||||
**Struttura della finestra**
|
**Struttura della finestra**
|
||||||
- **Barra laterale** (sinistra, comprimibile con il pulsante a tre linee): logo e nome dell'app, pulsante **Nuova chat**, elenco delle chat salvate (titolo = inizio del primo messaggio, data/ora dell'ultimo scambio; la chat aperta è evidenziata). Un clic su una chat la riapre; la ✕ (con conferma) la elimina. Elenco vuoto: "Nessuna chat salvata".
|
- **Barra laterale** (sinistra, comprimibile con il pulsante a tre linee): logo e nome dell'app, pulsante **Nuova chat**, elenco delle chat salvate (titolo = inizio del primo messaggio, data/ora dell'ultimo scambio; la chat aperta è evidenziata). Un clic su una chat la riapre; la ✕ (con conferma) la elimina. Elenco vuoto: "Nessuna chat salvata".
|
||||||
- **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. Le immagini `` 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 `` 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.
|
||||||
@@ -46,14 +46,14 @@ Sorgente di verità per l'aspetto, derivato dalla skill `ui-ux-pro-max` (stile "
|
|||||||
|
|
||||||
## Scelte tecniche
|
## Scelte tecniche
|
||||||
Le scelte architetturali sono negli [ADR](README.md#indice-adr). Sintesi:
|
Le scelte architetturali sono negli [ADR](README.md#indice-adr). Sintesi:
|
||||||
- .NET 10, un solo progetto `net10.0` per Windows e Linux. Avalonia (GUI), Markdig (parsing markdown). Il codice è diviso in `Core/` (avvio di `claude`, cronologia, nome app) e `Gui/` (finestra, ViewModel, renderer markdown); `Program.cs` sceglie CLI o GUI.
|
- .NET 10, un solo progetto `net10.0` per Windows e Linux. Avalonia (GUI), Markdig (parsing markdown), CSharpMath (formule LaTeX). Il codice è diviso in `Core/` (avvio di `claude`, cronologia, nome app) e `Gui/` (finestra, ViewModel, renderer markdown); `Program.cs` sceglie CLI o GUI.
|
||||||
- `ProcessStartInfo.ArgumentList` per passare il prompt (niente problemi di quoting).
|
- `ProcessStartInfo.ArgumentList` per passare il prompt (niente problemi di quoting).
|
||||||
- `claude` è un eseguibile nativo (`~/.local/bin`): si avvia direttamente, senza `cmd /c`. Lo stdin è chiuso subito per evitare l'attesa di 3s di claude.
|
- `claude` è un eseguibile nativo (`~/.local/bin`): si avvia direttamente, senza `cmd /c`. Lo stdin è chiuso subito per evitare l'attesa di 3s di claude.
|
||||||
- Working dir: `AppContext.BaseDirectory`, non la cwd del chiamante.
|
- Working dir: `AppContext.BaseDirectory`, non la cwd del chiamante.
|
||||||
- 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).
|
||||||
|
|||||||
+11
-9
@@ -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,31 +28,33 @@ 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 |
|
||||||
| [0006](adr/0006-immagini-nel-markdown.md) Immagini nel markdown (solo file locali) | Proposta (implementata, test verdi) |
|
| [0006](adr/0006-immagini-nel-markdown.md) Immagini nel markdown (solo file locali) | Proposta (implementata, test verdi) |
|
||||||
| [0007](adr/0007-schema-wiki-per-query-con-immagini.md) Schema di una wiki interrogabile, con immagini | Proposta (solo documentazione; nessun codice) |
|
| [0007](adr/0007-schema-wiki-per-query-con-immagini.md) Schema di una wiki interrogabile, con immagini | Proposta (solo documentazione; nessun codice) |
|
||||||
|
| [0008](adr/0008-formule-latex-nel-markdown.md) Formule LaTeX nel markdown | Proposta (implementata) |
|
||||||
|
|
||||||
## 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.
|
||||||
+2
-1
@@ -48,9 +48,10 @@ 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 |
|
||||||
| [0006](adr/0006-immagini-nel-markdown.md) | Immagini nel markdown: solo file locali | Proposta |
|
| [0006](adr/0006-immagini-nel-markdown.md) | Immagini nel markdown: solo file locali | Proposta |
|
||||||
| [0007](adr/0007-schema-wiki-per-query-con-immagini.md) | Schema di una wiki interrogabile, con immagini | Proposta |
|
| [0007](adr/0007-schema-wiki-per-query-con-immagini.md) | Schema di una wiki interrogabile, con immagini | Proposta |
|
||||||
|
| [0008](adr/0008-formule-latex-nel-markdown.md) | Formule LaTeX nel markdown | Proposta |
|
||||||
+8
-6
@@ -24,26 +24,28 @@ Dettagli dell'MVP in [MVP.md](MVP.md). Stadio attuale e debiti: [PROJECT_STATE.m
|
|||||||
- [ ] GUI: streaming della risposta (`--output-format stream-json`), stop, rinomina/ricerca chat
|
- [ ] GUI: streaming della risposta (`--output-format stream-json`), stop, rinomina/ricerca chat
|
||||||
- [x] GUI: immagini nel markdown da percorso locale, con ripiego sul testo alternativo ([ADR-0006](adr/0006-immagini-nel-markdown.md); test MD-14, MD-19…MD-30); immagini remote rimandate
|
- [x] GUI: immagini nel markdown da percorso locale, con ripiego sul testo alternativo ([ADR-0006](adr/0006-immagini-nel-markdown.md); test MD-14, MD-19…MD-30); immagini remote rimandate
|
||||||
- [x] Schema di una wiki interrogabile con immagini: convenzione per le istruzioni solo-query, l'indice e il catalogo ([ADR-0007](adr/0007-schema-wiki-per-query-con-immagini.md)); da provare con la chat reale su una wiki di esempio
|
- [x] Schema di una wiki interrogabile con immagini: convenzione per le istruzioni solo-query, l'indice e il catalogo ([ADR-0007](adr/0007-schema-wiki-per-query-con-immagini.md)); da provare con la chat reale su una wiki di esempio
|
||||||
|
- [x] GUI: formule LaTeX nel markdown, inline `$…$` e a blocco `$$…$$`, con ripiego sul sorgente ([ADR-0008](adr/0008-formule-latex-nel-markdown.md); test MD-31…MD-38)
|
||||||
- [ ] Config file accanto all'exe
|
- [ ] Config file accanto all'exe
|
||||||
- [ ] Test automatici e CI (vedi Fase 6)
|
- [ ] Test automatici e CI (vedi Fase 6)
|
||||||
|
|
||||||
## 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.
|
||||||
|
|||||||
+30
-3
@@ -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 | |
|
||||||
@@ -187,9 +206,17 @@ Si provano con `FakeClaude`/`FakeCodex` che rispondono a `--version` e `update`;
|
|||||||
| MD-25 | Immagine dentro un link, in una lista o in una tabella ⇒ resa senza rompere intervalli dei link (MD-13) né il layout | R | `Immagine_in_contesti_annidati` | [x] |
|
| MD-25 | Immagine dentro un link, in una lista o in una tabella ⇒ resa senza rompere intervalli dei link (MD-13) né il layout | R | `Immagine_in_contesti_annidati` | [x] |
|
||||||
| MD-26 | Cambio di tema con immagini presenti ⇒ vista ricostruita, immagini ancora mostrate | R | `Immagine_cambio_tema` | [x] |
|
| MD-26 | Cambio di tema con immagini presenti ⇒ vista ricostruita, immagini ancora mostrate | R | `Immagine_cambio_tema` | [x] |
|
||||||
| MD-27 | Il caricamento è in background: subito dopo il rendering c'è il segnaposto `[alt]` e un'immagine in attesa; a fine decodifica compare l'immagine; un file corrotto lascia `[alt]` | R | `Immagine_caricamento_in_background` | [x] |
|
| MD-27 | Il caricamento è in background: subito dopo il rendering c'è il segnaposto `[alt]` e un'immagine in attesa; a fine decodifica compare l'immagine; un file corrotto lascia `[alt]` | R | `Immagine_caricamento_in_background` | [x] |
|
||||||
| MD-28 | Percorso relativo non presente nella cartella dell'exe ma in una cartella superiore (radice del progetto, es. `figure/<file>` con l'exe in una sottocartella) ⇒ immagine mostrata | R | `Immagine_percorso_relativo_alla_radice` | [x] |
|
| MD-28 | Percorso relativo non presente nella cartella dell'exe ma in una cartella superiore (radice del progetto, es. `<cartella>/<file>` con l'exe in una sottocartella) ⇒ immagine mostrata | R | `Immagine_percorso_relativo_alla_radice` | [x] |
|
||||||
| MD-29 | Se il file esiste in più cartelle, vince la più vicina all'exe; se non esiste in nessuna ⇒ testo alternativo | R | `Immagine_cartella_piu_vicina` | [x] |
|
| MD-29 | Se il file esiste in più cartelle, vince la più vicina all'exe; se non esiste in nessuna ⇒ testo alternativo | R | `Immagine_cartella_piu_vicina` | [x] |
|
||||||
| MD-30 | Struttura tipo wiki (catalogo JSON + cartella immagini + risposta con immagine, didascalia e link `[[fig-NNN]]` sotto): l'immagine compare e la didascalia resta testo; l'app non legge il JSON | R | `Immagine_risposta_stile_wiki` | [x] |
|
| MD-30 | Risposta con immagine e, sotto, didascalia e link in markdown: l'immagine compare e la didascalia resta testo; l'app non legge nessun catalogo | R | `Immagine_risposta_con_didascalia` | [x] |
|
||||||
|
| MD-31 | Formula inline `$…$` ⇒ controllo formula inserito nel testo; il testo prima e dopo resta intatto e non sparisce più (ADR-0008) | R | `Formula_inline` | [x] |
|
||||||
|
| MD-32 | Formula a blocco `$$…$$` ⇒ controllo formula (non un blocco di codice col sorgente) | R | `Formula_a_blocco` | [x] |
|
||||||
|
| MD-33 | LaTeX non valido (inline e a blocco) ⇒ sorgente come testo, nessuna eccezione | R | `Formula_non_valida_ripiego` | [x] |
|
||||||
|
| MD-34 | Costrutti LaTeX comuni (frazioni, apici e pedici, `\mathbf`, `\overline`, `\times`, lettere greche, `\left…\right`, `\sum`) ⇒ nessun errore | R | `Costrutti_latex_comuni` | [x] |
|
||||||
|
| MD-35 | Cambio di tema con formule presenti ⇒ vista ricostruita, formule ancora mostrate | R | `Formula_cambio_tema` | [x] |
|
||||||
|
| MD-36 | Formula dentro una lista, una tabella o un link ⇒ resa senza rompere il layout né gli intervalli dei link (MD-13) | R | `Formula_in_contesti_annidati` | [x] |
|
||||||
|
| MD-37 | Il nome accessibile della formula è il suo sorgente LaTeX | R | `Formula_nome_accessibile` | [x] |
|
||||||
|
| MD-38 | Simboli `$` isolati (es. prezzi «10 $ e 20 $») restano testo, nessuna formula | R | `Dollari_isolati_restano_testo` | [x] |
|
||||||
| MD-15 | HTML grezzo disabilitato: i tag compaiono come testo, non come elementi | R | `Html_disabilitato` | [ ] |
|
| MD-15 | HTML grezzo disabilitato: i tag compaiono come testo, non come elementi | R | `Html_disabilitato` | [ ] |
|
||||||
| MD-16 | Cambio di tema (chiaro/scuro): i colori si aggiornano ricostruendo la vista | R | `Cambio_tema` | [ ] |
|
| MD-16 | Cambio di tema (chiaro/scuro): i colori si aggiornano ricostruendo la vista | R | `Cambio_tema` | [ ] |
|
||||||
| MD-17 | Markdown malformato o blocchi sconosciuti non fanno crashare | R | `Markdown_malformato` | [ ] |
|
| MD-17 | Markdown malformato o blocchi sconosciuti non fanno crashare | R | `Markdown_malformato` | [ ] |
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# ADR-0008 — Formule LaTeX nel markdown
|
||||||
|
|
||||||
|
- **Stato**: Proposta
|
||||||
|
- **Data**: 2026-10-20
|
||||||
|
- **Collegato a**: [ADR-0006](0006-immagini-nel-markdown.md) (stesso renderer, stessi vincoli di sicurezza), [ADR-0007](0007-schema-wiki-per-query-con-immagini.md) (stesso tipo di risposte)
|
||||||
|
|
||||||
|
## Contesto
|
||||||
|
Le risposte dei motori possono contenere formule in LaTeX: `$ ... $` nel testo e `$$ ... $$` a blocco. Il renderer markdown ([Gui/MarkdownView.cs](../../src/agent-wiki-ui/Gui/MarkdownView.cs)) usa Markdig con `UseAdvancedExtensions()`, che comprende l'estensione matematica: il parser riconosce le formule ma il renderer non le gestisce. Quelle a blocco finiscono in un blocco di codice con il sorgente LaTeX; quelle nel testo non vengono disegnate (nodo ignorato). L'utente vede sorgente illeggibile o formule mancanti.
|
||||||
|
|
||||||
|
## Criteri di decisione
|
||||||
|
1. Mostrare le formule composte (frazioni, apici e pedici, lettere greche, sommatorie, sopralineature) come in un testo tipografico.
|
||||||
|
2. Nessuna rete e nessuna esecuzione di codice: la formula è solo dato da disegnare (come per le immagini, ADR-0006).
|
||||||
|
3. Degrado sicuro: un LaTeX non valido mostra il sorgente come testo, mai un errore né un blocco della chat.
|
||||||
|
4. Coerenza con il resto del renderer: colori dal tema, ricostruzione al cambio di tema, testo circostante selezionabile, accessibilità.
|
||||||
|
5. Funziona su Windows e Linux nel publish single-file, senza runtime aggiuntivi.
|
||||||
|
6. Dipendenza manutenuta e compatibile con la versione di Avalonia in uso.
|
||||||
|
|
||||||
|
## Opzioni considerate
|
||||||
|
| Opzione | Esito | Motivo |
|
||||||
|
|---|---|---|
|
||||||
|
| Lasciare com'è | Scartata | Il sorgente LaTeX o l'assenza delle formule rendono illeggibili le risposte con formule. |
|
||||||
|
| Convertire il LaTeX in testo Unicode (`x_1` → `x`+pedice) con un mini-interprete proprio | Scartata | Copre solo i casi semplici; frazioni, `\overline`, `\left…\right` e matrici richiederebbero di riscrivere un motore di composizione. |
|
||||||
|
| Rendere con un motore web (KaTeX/MathJax in una WebView) | Scartata | Aggiunge un browser incorporato e codice JavaScript, contro il criterio 2 e il peso dell'eseguibile. |
|
||||||
|
| **Libreria di composizione TeX per Avalonia (`Sylinko.CSharpMath.Avalonia`, licenza MIT, basata su CSharpMath)** | **Scelta** | Disegna LaTeX come controllo Avalonia, senza rete né script; l'ultima versione (12.0.0) è per Avalonia 12, come l'app. |
|
||||||
|
|
||||||
|
## Decisione
|
||||||
|
- La formula tra `$ … $` è un'**immagine inline** composta dal controllo della libreria, inserita nel testo (`InlineUIContainer`) con allineamento al testo; `$$ … $$` è un **blocco** a sé, allineato a sinistra, con scorrimento orizzontale se più larga della colonna.
|
||||||
|
- Il colore è quello del testo del tema (`Fg`, o il colore ereditato se il token manca); al cambio di tema la vista si ricostruisce (già così per il resto).
|
||||||
|
- **Ripiego**: se il LaTeX non è valido la formula compare come testo sorgente (`$…$` con carattere monospazio), senza eccezioni.
|
||||||
|
- Il nome accessibile del controllo è il sorgente LaTeX; la formula non è selezionabile come testo (è un disegno): il pulsante **Copia** della risposta continua a copiare il markdown originale, quindi anche il sorgente delle formule.
|
||||||
|
- I simboli `$` isolati, ad esempio un prezzo («10 $ e 20 $»), restano testo. Markdig da solo li prenderebbe per una formula (e toglie gli spazi dal contenuto): il renderer applica la regola di Pandoc sul testo originale, cioè niente spazio subito dopo il `$` di apertura né subito prima di quello di chiusura. Per questo gli intervalli di Markdig si leggono su un testo con fine riga uniformi (`\n`).
|
||||||
|
- Nessuna rete e nessuna lettura di file: la formula è solo dato per il disegno.
|
||||||
|
|
||||||
|
## Conseguenze
|
||||||
|
**Positive**: le risposte con formule mostrano le formule composte; nessun cambiamento ai motori né ai prompt.
|
||||||
|
**Negative**: una dipendenza in più (e le sue librerie di disegno); il pacchetto è un fork per Avalonia 12 e va tenuto d'occhio quando Avalonia aggiorna; le formule non sono selezionabili come testo; il supporto non copre tutto LaTeX (le macro non supportate cadono nel ripiego).
|
||||||
|
|
||||||
|
## Da verificare
|
||||||
|
- Dimensione dell'eseguibile pubblicato (single-file) dopo l'aggiunta della libreria e dei suoi font.
|
||||||
|
- Allineamento verticale delle formule inline rispetto al testo con font diversi, su Windows e Linux.
|
||||||
|
- Quali comandi LaTeX comuni (matrici, ambienti `align`, estensioni a pacchetto) sono supportati.
|
||||||
|
- Il comportamento con formule molto lunghe in una riga di testo (a capo).
|
||||||
|
|
||||||
|
## Riferimenti
|
||||||
|
- [MVP.md](../MVP.md) → "GUI (chat)"; matrice MD-31…MD-38 in [TESTING.md](../TESTING.md).
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,72 @@
|
|||||||
|
using Avalonia.Automation;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Documents;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Layout;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using CSharpMath.Avalonia;
|
||||||
|
using Markdig.Extensions.Mathematics;
|
||||||
|
using Markdig.Syntax;
|
||||||
|
|
||||||
|
namespace AgentWikiUi.Gui;
|
||||||
|
|
||||||
|
/// <summary>Formule LaTeX nel markdown (ADR-0008): inline <c>$…$</c> e a blocco <c>$$…$$</c>; se il LaTeX non è valido compare il sorgente.</summary>
|
||||||
|
partial class MarkdownView
|
||||||
|
{
|
||||||
|
const double InlineFormulaSize = 15;
|
||||||
|
const double BlockFormulaSize = 18;
|
||||||
|
|
||||||
|
/// <summary>Sorgente LaTeX di un blocco <c>$$…$$</c> (righe tra i delimitatori).</summary>
|
||||||
|
static string BlockSource(LeafBlock block) =>
|
||||||
|
string.Join('\n', block.Lines.Lines.Take(block.Lines.Count).Select(l => l.ToString())).Trim();
|
||||||
|
|
||||||
|
Control FormulaBlock(string latex)
|
||||||
|
{
|
||||||
|
var formula = Formula(latex, BlockFormulaSize, "$$");
|
||||||
|
// formula più larga della colonna: scorrimento orizzontale invece di tagliarla
|
||||||
|
return new ScrollViewer
|
||||||
|
{
|
||||||
|
HorizontalScrollBarVisibility = ScrollBarVisibility.Auto, VerticalScrollBarVisibility = ScrollBarVisibility.Disabled,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Left, Content = formula,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// colore del testo del tema (token Fg); senza il token (es. nei test) quello ereditato dal controllo
|
||||||
|
Color TextColor() =>
|
||||||
|
this.TryFindResource("Fg", ActualThemeVariant, out var v) && v is ISolidColorBrush s ? s.Color
|
||||||
|
: TextElement.GetForeground(this) is ISolidColorBrush f ? f.Color : Colors.Black;
|
||||||
|
|
||||||
|
/// <summary>Regola di Pandoc per <c>$…$</c>: niente spazio dopo l'apertura né prima della chiusura, altrimenti i due <c>$</c> sono testo (prezzi, importi). Markdig da solo accetterebbe «10 $ e 20 $».</summary>
|
||||||
|
static bool IsDollarText(string source, MathInline math)
|
||||||
|
{
|
||||||
|
// Markdig toglie gli spazi dal contenuto: si controlla il testo originale tra i due delimitatori
|
||||||
|
if (math.DelimiterCount != 1) return false;
|
||||||
|
var (start, end) = (math.Span.Start, math.Span.End);
|
||||||
|
if (start < 0 || end >= source.Length || end - start < 2) return false; // intervallo non affidabile: si tratta come formula
|
||||||
|
return char.IsWhiteSpace(source[start + 1]) || char.IsWhiteSpace(source[end - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Control FormulaInline(string latex) => Formula(latex, InlineFormulaSize, "$");
|
||||||
|
|
||||||
|
/// <summary>Controllo che disegna la formula; se il LaTeX non è valido, il sorgente come testo monospazio (ripiego sicuro).</summary>
|
||||||
|
Control Formula(string latex, double size, string delimiter)
|
||||||
|
{
|
||||||
|
latex = latex.Trim();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var view = new MathView
|
||||||
|
{
|
||||||
|
LaTeX = latex, FontSize = (float)size, TextColor = TextColor(), DisplayErrorInline = false,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Left,
|
||||||
|
};
|
||||||
|
if (latex.Length > 0 && string.IsNullOrEmpty(view.ErrorMessage))
|
||||||
|
{
|
||||||
|
AutomationProperties.SetName(view, latex);
|
||||||
|
ToolTip.SetTip(view, latex);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception) { /* LaTeX o disegno non riusciti: ripiego sul sorgente */ }
|
||||||
|
return new TextBlock { Text = delimiter + latex + delimiter, FontFamily = Mono, FontSize = 13.5, Foreground = Brush("FgMuted"), TextWrapping = TextWrapping.Wrap };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ using Avalonia.Input.Platform;
|
|||||||
using Avalonia.Layout;
|
using Avalonia.Layout;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Markdig;
|
using Markdig;
|
||||||
|
using Markdig.Extensions.Mathematics;
|
||||||
using Markdig.Extensions.Tables;
|
using Markdig.Extensions.Tables;
|
||||||
using Markdig.Syntax;
|
using Markdig.Syntax;
|
||||||
using Markdig.Syntax.Inlines;
|
using Markdig.Syntax.Inlines;
|
||||||
@@ -33,7 +34,14 @@ partial class MarkdownView : Decorator
|
|||||||
if (change.Property == SourceProperty) Rebuild();
|
if (change.Property == SourceProperty) Rebuild();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Rebuild() => Child = string.IsNullOrEmpty(Source) ? null : RenderBlocks(Markdig.Markdown.Parse(Source, Pipeline), 10);
|
string parsed = "";
|
||||||
|
|
||||||
|
void Rebuild()
|
||||||
|
{
|
||||||
|
// fine riga uniformi: gli intervalli di Markdig (Span) devono valere per il testo che si legge qui (ADR-0008)
|
||||||
|
parsed = Source?.ReplaceLineEndings("\n") ?? "";
|
||||||
|
Child = parsed.Length == 0 ? null : RenderBlocks(Markdig.Markdown.Parse(parsed, Pipeline), 10);
|
||||||
|
}
|
||||||
|
|
||||||
IBrush Brush(string key) => this.TryFindResource(key, ActualThemeVariant, out var v) && v is IBrush b ? b : Brushes.Gray;
|
IBrush Brush(string key) => this.TryFindResource(key, ActualThemeVariant, out var v) && v is IBrush b ? b : Brushes.Gray;
|
||||||
|
|
||||||
@@ -48,6 +56,7 @@ partial class MarkdownView : Decorator
|
|||||||
{
|
{
|
||||||
HeadingBlock h => Text(h.Inline, bold: true, size: h.Level switch { 1 => 24, 2 => 20, 3 => 18, 4 => 16, _ => 15.5 }),
|
HeadingBlock h => Text(h.Inline, bold: true, size: h.Level switch { 1 => 24, 2 => 20, 3 => 18, 4 => 16, _ => 15.5 }),
|
||||||
ParagraphBlock p => ImageParagraph(p) ?? Text(p.Inline),
|
ParagraphBlock p => ImageParagraph(p) ?? Text(p.Inline),
|
||||||
|
MathBlock m => FormulaBlock(BlockSource(m)),
|
||||||
FencedCodeBlock or CodeBlock => Code(string.Join('\n', ((LeafBlock)block).Lines.Lines.Take(((LeafBlock)block).Lines.Count).Select(l => l.ToString()))),
|
FencedCodeBlock or CodeBlock => Code(string.Join('\n', ((LeafBlock)block).Lines.Lines.Take(((LeafBlock)block).Lines.Count).Select(l => l.ToString()))),
|
||||||
QuoteBlock q => new Border { BorderBrush = Brush("Border"), BorderThickness = new(3, 0, 0, 0), Padding = new(12, 0, 0, 0), Child = RenderBlocks(q, 8) },
|
QuoteBlock q => new Border { BorderBrush = Brush("Border"), BorderThickness = new(3, 0, 0, 0), Padding = new(12, 0, 0, 0), Child = RenderBlocks(q, 8) },
|
||||||
ListBlock l => List(l),
|
ListBlock l => List(l),
|
||||||
@@ -145,6 +154,16 @@ partial class MarkdownView : Decorator
|
|||||||
switch (node)
|
switch (node)
|
||||||
{
|
{
|
||||||
case LiteralInline lit: Add(target, lit.Content.ToString(), st, ref pos); break;
|
case LiteralInline lit: Add(target, lit.Content.ToString(), st, ref pos); break;
|
||||||
|
case MathInline math:
|
||||||
|
var latex = math.Content.ToString();
|
||||||
|
if (IsDollarText(parsed, math)) // es. «10 $ e 20 $»: non è una formula
|
||||||
|
Add(target, parsed.Substring(math.Span.Start, math.Span.End - math.Span.Start + 1), st, ref pos);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
target.Add(new InlineUIContainer(FormulaInline(latex)));
|
||||||
|
pos += 1; // un controllo incorporato occupa una posizione nel testo
|
||||||
|
}
|
||||||
|
break;
|
||||||
case LineBreakInline br: Add(target, br.IsHard ? "\n" : " ", st, ref pos); break;
|
case LineBreakInline br: Add(target, br.IsHard ? "\n" : " ", st, ref pos); break;
|
||||||
case CodeInline code:
|
case CodeInline code:
|
||||||
target.Add(new Run(code.Content) { FontFamily = Mono, FontSize = 13.5, Background = Brush("Muted") });
|
target.Add(new Run(code.Content) { FontFamily = Mono, FontSize = 13.5, Background = Brush("Muted") });
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
<PackageReference Include="Avalonia.Desktop" Version="12.1.4" />
|
<PackageReference Include="Avalonia.Desktop" Version="12.1.4" />
|
||||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.1.4" />
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.1.4" />
|
||||||
<PackageReference Include="Markdig" Version="1.4.0" />
|
<PackageReference Include="Markdig" Version="1.4.0" />
|
||||||
|
<PackageReference Include="Sylinko.CSharpMath.Avalonia" Version="12.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -246,7 +246,7 @@ public sealed class MarkdownImageTests : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
[AvaloniaFact]
|
[AvaloniaFact]
|
||||||
public void Immagine_risposta_stile_wiki()
|
public void Immagine_risposta_con_didascalia()
|
||||||
{
|
{
|
||||||
var root = Wiki("wiki-c", "schema-a.png");
|
var root = Wiki("wiki-c", "schema-a.png");
|
||||||
var risposta = """
|
var risposta = """
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Automation;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Documents;
|
||||||
|
using Avalonia.Headless.XUnit;
|
||||||
|
using Avalonia.Styling;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
|
using CSharpMath.Avalonia;
|
||||||
|
using AgentWikiUi.Gui;
|
||||||
|
|
||||||
|
namespace AgentWikiUi.Tests;
|
||||||
|
|
||||||
|
/// <summary>Formule LaTeX nel markdown (ADR-0008): matrice MD-31…MD-38 in docs/TESTING.md.</summary>
|
||||||
|
[Trait("Category", "Gui")]
|
||||||
|
public sealed class MarkdownMathTests
|
||||||
|
{
|
||||||
|
static (Window Window, MarkdownView View) Show(string markdown, double width = 600)
|
||||||
|
{
|
||||||
|
var view = new MarkdownView();
|
||||||
|
var window = new Window { Width = width, Height = 800, Content = view };
|
||||||
|
window.Show();
|
||||||
|
view.Source = markdown;
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
window.UpdateLayout();
|
||||||
|
return (window, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<MathView> Formulas(Visual v) => v.GetVisualDescendants().OfType<MathView>().ToList();
|
||||||
|
|
||||||
|
static string Text(Visual v) => string.Concat(v.GetVisualDescendants().OfType<TextBlock>()
|
||||||
|
.Select(t => t.Inlines is { Count: > 0 } ? string.Concat(t.Inlines.OfType<Run>().Select(r => r.Text)) : t.Text));
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void Formula_inline()
|
||||||
|
{
|
||||||
|
var (_, view) = Show("Il gap è $E_g = E_{LUMO} - E_{HOMO}$ in eV.");
|
||||||
|
var f = Assert.Single(Formulas(view));
|
||||||
|
Assert.Equal("E_g = E_{LUMO} - E_{HOMO}", AutomationProperties.GetName(f));
|
||||||
|
Assert.True(string.IsNullOrEmpty(f.ErrorMessage));
|
||||||
|
Assert.True(f.Bounds.Width > 0 && f.Bounds.Height > 0);
|
||||||
|
var text = Text(view);
|
||||||
|
Assert.Contains("Il gap è", text);
|
||||||
|
Assert.Contains("in eV.", text);
|
||||||
|
Assert.DoesNotContain("$", text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void Formula_a_blocco()
|
||||||
|
{
|
||||||
|
var (_, view) = Show("Prima.\n\n$$\n\\frac{a}{b} = c\n$$\n\nDopo.");
|
||||||
|
var f = Assert.Single(Formulas(view));
|
||||||
|
Assert.Equal("\\frac{a}{b} = c", AutomationProperties.GetName(f));
|
||||||
|
Assert.True(string.IsNullOrEmpty(f.ErrorMessage));
|
||||||
|
Assert.True(f.Bounds.Height > 0);
|
||||||
|
Assert.DoesNotContain("\\frac", Text(view)); // non è un blocco di codice col sorgente
|
||||||
|
Assert.Contains("Prima.", Text(view));
|
||||||
|
Assert.Contains("Dopo.", Text(view));
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaTheory]
|
||||||
|
[InlineData("Testo $\\frac{a$ fine")]
|
||||||
|
[InlineData("$$\n\\unknowncommand{x}\n$$")]
|
||||||
|
[InlineData("Testo $\\begin{matrix$ fine")]
|
||||||
|
public void Formula_non_valida_ripiego(string markdown)
|
||||||
|
{
|
||||||
|
var (_, view) = Show(markdown);
|
||||||
|
Assert.Empty(Formulas(view));
|
||||||
|
Assert.Contains("$", Text(view)); // il sorgente resta visibile come testo
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaTheory]
|
||||||
|
[InlineData("\\frac{a}{b}")]
|
||||||
|
[InlineData("x^2 + y_i")]
|
||||||
|
[InlineData("\\mathbf{A} \\times \\mathbf{B}")]
|
||||||
|
[InlineData("\\overline{x}")]
|
||||||
|
[InlineData("\\left( \\frac{1}{2} \\right)")]
|
||||||
|
[InlineData("\\lambda_{max} \\approx \\pi \\cdot \\tau")]
|
||||||
|
[InlineData("\\sum_{i=1}^{n} x_i")]
|
||||||
|
[InlineData("\\text{Mn} = \\chi \\varepsilon")]
|
||||||
|
public void Costrutti_latex_comuni(string latex)
|
||||||
|
{
|
||||||
|
var (_, view) = Show($"Prima $${latex}$$ dopo".Replace("$$", "$"));
|
||||||
|
var f = Assert.Single(Formulas(view));
|
||||||
|
Assert.Equal(latex, AutomationProperties.GetName(f));
|
||||||
|
Assert.True(string.IsNullOrEmpty(f.ErrorMessage), f.ErrorMessage);
|
||||||
|
Assert.True(f.Bounds.Width > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void Formula_cambio_tema()
|
||||||
|
{
|
||||||
|
var (window, view) = Show("Gap $E_g$ e\n\n$$\\frac{a}{b}$$");
|
||||||
|
Assert.Equal(2, Formulas(view).Count);
|
||||||
|
window.RequestedThemeVariant = ThemeVariant.Dark;
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.Equal(2, Formulas(view).Count);
|
||||||
|
window.RequestedThemeVariant = ThemeVariant.Light;
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.Equal(2, Formulas(view).Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void Formula_in_contesti_annidati()
|
||||||
|
{
|
||||||
|
var markdown = """
|
||||||
|
Testo $a_1$ e un [link](https://example.com).
|
||||||
|
|
||||||
|
[vai a $b^2$](https://example.com)
|
||||||
|
|
||||||
|
- voce $c_3$
|
||||||
|
|
||||||
|
| x | y |
|
||||||
|
|---|---|
|
||||||
|
| $d_4$ | z |
|
||||||
|
""";
|
||||||
|
var (_, view) = Show(markdown);
|
||||||
|
Assert.Equal(4, Formulas(view).Count);
|
||||||
|
Assert.Contains("link", Text(view));
|
||||||
|
Assert.Contains("vai a", Text(view));
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void Formula_nome_accessibile()
|
||||||
|
{
|
||||||
|
var (_, view) = Show("Gap $E_g$.");
|
||||||
|
var f = Assert.Single(Formulas(view));
|
||||||
|
Assert.Equal("E_g", AutomationProperties.GetName(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void Dollari_isolati_restano_testo()
|
||||||
|
{
|
||||||
|
var (_, view) = Show("Costa 10 $ e poi 20 $ in totale.");
|
||||||
|
Assert.Empty(Formulas(view));
|
||||||
|
Assert.Contains("10 $ e poi 20 $", Text(view));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user