Documentazione: compatibilità con Codex e interruttore CLI

ADR-0002 passa ad Accettata per Claude Code e Codex con l'esito delle prove
reali: id di sessione da thread.started, output JSONL, risposta = ultimo
agent_message, ripresa senza -s e sola lettura via sandbox_mode, -- prima del
prompt. MVP, ROADMAP, INSTALL e README descrivono l'interruttore CLI, la
nuova opzione --engine e il problema del modello predefinito che richiede un
Codex più recente. TESTING.md aggiunge le righe CX, EN e ST-12.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5.5 committed 2026-10-15 11:40:47 +02:00
1 parent 007cc9bdfa
commit c50bd63723
10 files changed
+90 -118

No files matched your search

+8 -6
View File
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## 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`) 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`) o Codex (`codex exec`) da terminale. Senza argomenti apre una GUI di chat Avalonia, con argomenti funziona da CLI.
## Workflow doc-first (obbligatorio)
@@ -23,11 +23,12 @@ agent-wiki-ui (già `LlmRun`; il nome ha un'unica sorgente, vedi [docs/README.md
Decisioni e motivazioni negli ADR ([docs/adr/](docs/adr/)); qui solo la mappa del codice.
- [Program.cs](Program.cs): top-level statements. Con argomenti è la CLI (`Claude.RunInherited`); senza avvia Avalonia (`App`).
- [Core/Claude.cs](Core/Claude.cs): **unico punto di avvio di `claude`** (cartella dell'exe, stdin chiuso, `ArgumentList`, `--session-id`/`--resume`, `--model`/`--effort` omessi se vuoti). Contiene anche l'elenco modelli `Catalog.Models`, da aggiornare quando escono nuovi modelli.
- [Core/ChatStore.cs](Core/ChatStore.cs): cronologia, un JSON per chat in `<LocalApplicationData>/<nome-app>/chats/<guid>.json` (nome file = id di sessione, validato come Guid prima dell'uso).
- [Program.cs](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](Core/Engine.cs): astrazione "motore" (ADR-0002): `Engine` (id, nome, eseguibile, modelli, effort, `BuildArgs`, `Parse`), `ClaudeEngine`, `CodexEngine`, registro `Engines` e ricerca nel PATH (`PathSearch`). Gli elenchi modelli sono fissi, da aggiornare quando ne escono di nuovi.
- [Core/EngineRunner.cs](Core/EngineRunner.cs): **unico punto di avvio dei motori** (cartella dell'exe, stdin chiuso, `ArgumentList`), CLI con output ereditato e GUI con output catturato e interpretato da `Engine.Parse`.
- [Core/ChatStore.cs](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](Core/AppInfo.cs): nome dell'app, letto dall'`AssemblyName` del csproj (unica sorgente).
- [Gui/MainViewModel.cs](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).
- [Gui/MainViewModel.cs](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).
- [Gui/MainWindow.axaml](Gui/MainWindow.axaml) (+ `.cs`): layout XAML con binding compilati. [Gui/App.axaml](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](Gui/MarkdownView.cs): renderer markdown proprio (albero Markdig → controlli Avalonia, `DisableHtml`).
- Dipendenze NuGet: Avalonia (+ Desktop, Themes.Fluent), Markdig.
@@ -35,8 +36,9 @@ Decisioni e motivazioni negli ADR ([docs/adr/](docs/adr/)); qui solo la mappa de
## 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).
- Lo stdin di `claude` va redirezionato e chiuso subito, altrimenti aspetta 3s.
- Lo stdin del motore va redirezionato e chiuso subito: `claude` aspetta 3 s, `codex` stampa "Reading additional input from stdin…" e attende.
- `claude` è un eseguibile nativo (`~/.local/bin`), avviabile direttamente. Se fosse `claude.cmd` (Windows) serve `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).
- 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).
- Avalonia 12: la clipboard si usa con `using Avalonia.Input.Platform;` (`SetTextAsync` è un'estensione); `TextBox.Watermark` è obsoleto, usare `PlaceholderText`.
- Nel renderer markdown i colori si risolvono al momento del rendering (`TryFindResource` con `ActualThemeVariant`) e la vista si ricostruisce al cambio di tema.
-77
View File
@@ -1,77 +0,0 @@
using System.Diagnostics;
namespace AgentWikiUi.Core;
record ModelOption(string Label, string Id);
static class Catalog
{
// Da aggiornare quando escono nuovi modelli.
public static readonly ModelOption[] Models =
[
new("Fable 5.1", "claude-fable-5-1"),
new("Opus 5.5", "claude-opus-5-5"),
new("Sonnet 5.5", "claude-sonnet-5-5"),
new("Haiku 5.5", "claude-haiku-5-5"),
];
public static readonly string[] Efforts = ["low", "medium", "high", "xhigh", "max"];
}
record RunResult(int Code, string Out, string Err);
// Unico punto di avvio di `claude`, condiviso da CLI e GUI.
static class Claude
{
// sessionId null = nessun contesto (CLI). Model/effort vuoti = flag omessi.
public static ProcessStartInfo CreateStart(string prompt, bool redirectOutput, string? sessionId = null, bool resume = false, string model = "", string effort = "")
{
var psi = new ProcessStartInfo("claude")
{
WorkingDirectory = AppContext.BaseDirectory, // mai la cwd del chiamante né Assembly.Location (vuoto in single-file)
UseShellExecute = false,
RedirectStandardInput = true, // chiuso subito dal chiamante: evita l'attesa di stdin di claude
RedirectStandardOutput = redirectOutput,
RedirectStandardError = redirectOutput,
CreateNoWindow = true,
};
psi.ArgumentList.Add("-p");
psi.ArgumentList.Add(prompt);
if (sessionId is not null)
{
psi.ArgumentList.Add(resume ? "--resume" : "--session-id");
psi.ArgumentList.Add(sessionId);
}
foreach (var (flag, value) in new[] { ("--model", model), ("--effort", effort) })
if (value != "") { psi.ArgumentList.Add(flag); psi.ArgumentList.Add(value); }
return psi;
}
// CLI: stdout/stderr ereditati (streaming), exit code propagato, 127 se `claude` manca.
public static int RunInherited(string prompt)
{
try
{
using var p = Process.Start(CreateStart(prompt, redirectOutput: false))!;
p.StandardInput.Close();
p.WaitForExit();
return p.ExitCode;
}
catch (System.ComponentModel.Win32Exception)
{
Console.Error.WriteLine("'claude' non trovato nel PATH.");
return 127;
}
}
// GUI: output catturato. Win32Exception se `claude` non è nel PATH.
public static async Task<RunResult> RunCapturedAsync(string prompt, string sessionId, bool resume, string model, string effort)
{
using var p = Process.Start(CreateStart(prompt, redirectOutput: true, sessionId, resume, model, effort))!;
p.StandardInput.Close();
var o = p.StandardOutput.ReadToEndAsync();
var e = p.StandardError.ReadToEndAsync();
await p.WaitForExitAsync();
return new(p.ExitCode, (await o).Trim(), (await e).Trim());
}
}
+5 -4
View File
@@ -1,6 +1,6 @@
# agent-wiki-ui
Eseguibile C# (.NET 10, Windows e Linux) che lancia Claude Code (`claude -p`) 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`) 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.
## Requisiti
- `claude` (Claude Code) installato e nel PATH, già autenticato; per Codex lo stesso con `codex`. Istruzioni: [docs/INSTALL.md](docs/INSTALL.md)
@@ -9,14 +9,15 @@ Eseguibile C# (.NET 10, Windows e Linux) che lancia Claude Code (`claude -p`) ne
## 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). La barra in alto del composer permette di scegliere **modello** ed **effort** di ragionamento ("Predefinito" = nessun flag, vale la config di `claude`). Le chat si salvano su disco e l'ultima si riapre all'avvio.
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 ("Predefinito" = nessun flag, vale la config del motore). Le chat si salvano su disco e l'ultima si riapre all'avvio.
```
dotnet run
```
CLI (con argomenti): il prompt viene stampato su terminale, l'exit code è quello di `claude` (127 se `claude` non è nel PATH).
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.
```
dotnet run -- "spiega questo progetto"
dotnet run -- --engine codex "spiega questo progetto"
```
## Publish single-file
@@ -28,6 +29,6 @@ dotnet publish -c Release -r linux-x64 --self-contained -p:PublishSingleFile=tru
Il risultato è in `bin/Release/net10.0/<rid>/publish/` (`agent-wiki-ui.exe` o `agent-wiki-ui`, circa 48 MB). Al primo avvio le librerie native vengono estratte in una cartella temporanea.
## Piattaforme
Windows e Linux con lo stesso codice ([ADR-0001](docs/adr/0001-gui-multipiattaforma-avalonia.md)). Pianificato l'uso di Codex CLI e Antigravity CLI già installati da terminale, oltre a Claude Code: [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 e Codex CLI, già installati da terminale; Antigravity CLI è pianificato: [ADR-0002](docs/adr/0002-motori-cli-claude-codex-antigravity.md).
Documentazione: [docs/README.md](docs/README.md) (indice, regole, decisioni), [docs/MVP.md](docs/MVP.md), [docs/ROADMAP.md](docs/ROADMAP.md).
+2
View File
@@ -71,6 +71,8 @@ codex exec --help # uso non interattivo, usato dall'app
**Stato su questa macchina** (WSL, controllato il 2026-10-15): `codex` 0.144.3 in `~/.local/bin/codex`, collegamento a `~/.codex/packages/standalone/current/bin/codex`, con accesso ChatGPT già fatto. `claude` 2.1.252 in `~/.npm-global/bin/claude`, con accesso claude.ai già fatto.
**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.
## Controllo finale per l'app
Dallo stesso terminale da cui avvii l'app:
```
+7 -6
View File
@@ -7,14 +7,14 @@ Eseguibile C# a riga di comando che inoltra un prompt a Claude Code (`claude`) l
```
<nome-app> "spiega questo progetto"
```
Tutti gli argomenti sono uniti in un unico prompt.
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.
## Comportamento
1. Se non ci sono argomenti: stampa l'uso ed esce con codice 2.
2. Avvia `claude -p <prompt>` (modalità non interattiva) con `WorkingDirectory = AppContext.BaseDirectory`.
2. Avvia il motore scelto in modalità non interattiva (`claude -p <prompt>` o `codex exec … -- <prompt>`) con `WorkingDirectory = AppContext.BaseDirectory`.
3. Ritrasmette stdout e stderr del processo figlio in tempo reale.
4. Esce con lo stesso exit code di `claude`.
5. Se `claude` non è trovato: messaggio chiaro ed exit code 127.
5. Se l'eseguibile del motore non è trovato: messaggio chiaro ed exit code 127.
## GUI (chat)
Senza argomenti si apre una finestra Avalonia (C# + XAML, nessun HTML) in stile claude.ai/ChatGPT, identica su Windows e Linux. Con argomenti resta la CLI. Decisione e motivazioni: [ADR-0001](adr/0001-gui-multipiattaforma-avalonia.md).
@@ -24,9 +24,10 @@ Senza argomenti si apre una finestra Avalonia (C# + XAML, nessun HTML) in stile
- **Conversazione**: messaggi alternati utente/Claude. Le risposte sono renderizzate in markdown: titoli, liste, tabelle, citazioni, blocchi di codice, grassetto/corsivo/barrato, codice inline, link. Il rendering è un renderer proprio ([MarkdownView.cs](../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).
- **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 **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). 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. Il riquadro è il punto in cui in futuro compariranno le impostazioni del motore ([ADR-0002](adr/0002-motori-cli-claude-codex-antigravity.md)). 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; 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). 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.
- **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 a "Predefinito" 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.
- **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. 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.
- La risposta compare a fine generazione (nessuno streaming); durante l'attesa un indicatore di avanzamento e Invia disabilitato. Se `claude` esce con errore, lo stderr appare come messaggio di errore.
- I link `http(s)` si aprono nel browser di sistema.
@@ -48,7 +49,7 @@ Le scelte architetturali sono negli [ADR](README.md#indice-adr). Sintesi:
- `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.
- Working dir: `AppContext.BaseDirectory`, non la cwd del chiamante.
- L'avvio di `claude` è in un solo punto ([Core/Claude.cs](../Core/Claude.cs)), usato da CLI e GUI.
- L'avvio dei motori è in un solo punto ([Core/EngineRunner.cs](../Core/EngineRunner.cs)), usato da CLI e GUI; ogni motore descrive argomenti e parser in [Core/Engine.cs](../Core/Engine.cs).
## 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).
+1 -1
View File
@@ -37,5 +37,5 @@ Se il nome cambia di nuovo, aggiornare solo questi punti:
| ADR | Titolo | Stato |
|---|---|---|
| [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 | Proposta |
| [0002](adr/0002-motori-cli-claude-codex-antigravity.md) | Più motori CLI (Claude Code, Codex, Antigravity) su Windows e Linux | Accettata (Claude Code, Codex) |
| [0003](adr/0003-strategia-di-test.md) | Strategia di test e regola "nuova funzione = nuovi test" | Proposta |
+11 -9
View File
@@ -28,15 +28,17 @@ Dettagli dell'MVP in [MVP.md](MVP.md).
## 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. Va fatta dopo (o insieme a) la Fase 5, che estrae il punto unico di avvio processo.
- [ ] Verificare con `--help` i flag reali di `codex` e `agy` su Windows e Linux (ADR-0002) e portare l'ADR a "Accettata"
- [ ] Astrazione "motore" in C#: `RunClaude` diventa `RunEngine(engine, ...)`
- [ ] Rilevamento dei motori nel PATH; motori assenti disabilitati, messaggio chiaro
- [ ] Il JSON di ogni chat memorizza il motore; cambiare motore apre una nuova chat
- [ ] GUI: schermata Impostazioni (motore, modello, effort) persistita su disco; modelli ed effort in base al motore; motore della chat indicato nell'interfaccia
- [ ] Motore Codex: lettura dell'id di sessione dall'output per la ripresa
- [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] Astrazione "motore" in C# (`Core/Engine.cs`, `Core/EngineRunner.cs`)
- [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] 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
- [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)
- [ ] Politica dei permessi: sola lettura di default, opzione esplicita per concedere di più
- [ ] CLI: opzione `--engine`, coerente con la GUI
- [x] Permessi di Codex in sola lettura (nessun flag che salta i controlli)
- [ ] Opzione esplicita per concedere permessi di scrittura
- [x] CLI: opzione `--engine`, coerente con la GUI
## Fase 5 — Multipiattaforma (Windows + Linux), GUI in solo C#
Decisione e motivazioni: [ADR-0001](adr/0001-gui-multipiattaforma-avalonia.md). Qui solo i passi.
@@ -45,7 +47,7 @@ Decisione e motivazioni: [ADR-0001](adr/0001-gui-multipiattaforma-avalonia.md).
- [x] Nome dell'app in un solo punto: `AssemblyName` nel csproj, letto da `Core/AppInfo.cs`; la rinomina segue la checklist di docs/README.md
- [x] Rinomina in `agent-wiki-ui` (csproj, namespace) e migrazione automatica della cartella dati `LlmRun` → `agent-wiki-ui`
- [x] Target `net10.0` (non più `net10.0-windows`); rimossi WinForms e WebView2
- [x] Estrarre in un solo punto la logica di avvio di `claude` (`Core/Claude.cs`) (cartella dell'exe, stdin chiuso), oggi duplicata tra `Program.cs` e `Gui.cs`
- [x] Estrarre in un solo punto la logica di avvio dei motori (`Core/EngineRunner.cs`: cartella dell'exe, stdin chiuso), prima duplicata tra `Program.cs` e `Gui.cs`
- [x] Cronologia in `Environment.SpecialFolder.LocalApplicationData` (stesso formato JSON, chat esistenti ancora leggibili)
- [x] GUI Avalonia: finestra, barra laterale delle chat, conversazione, composer con modello ed effort
- [x] Rendering markdown in Avalonia (titoli, liste, tabelle, citazioni, codice), pulsante Copia, link nel browser di sistema
+32 -5
View File
@@ -31,7 +31,7 @@ tests/FakeClaude/ eseguibile finto: registra argv/cwd/stdin; testo,
## Predisposizioni di testabilità (prerequisito dei test)
- [ ] `ChatStore` come istanza con cartella iniettata (oggi statico su `LocalApplicationData`)
- [ ] `IClaudeRunner` dietro `Core/Claude.cs`, iniettato nel `MainViewModel`
- [ ] `IEngineRunner` dietro `Core/EngineRunner.cs`, iniettato nel `MainViewModel`
- [ ] `InternalsVisibleTo` verso il progetto di test
- [ ] Spostamento in `src/` e `tests/` con soluzione unica
@@ -49,7 +49,7 @@ Esclusi dal calcolo: XAML generato, `Program.cs` (tre righe, coperte da E2E), `A
## Matrice requisito → test
Fonte dei requisiti: [MVP.md](MVP.md) e gli ADR. Livello: vedi sopra. Il nome del test è indicativo.
### Avvio di `claude` — `Core/Claude.cs`
### Avvio dei motori — `Core/EngineRunner.cs`
| ID | Requisito | Liv. | Test | |
|---|---|---|---|---|
| CL-01 | Il prompt è un solo argomento, anche con virgolette e spazi (criterio MVP 4) | U/P | `Prompt_con_virgolette_arriva_intatto` | [ ] |
@@ -58,13 +58,30 @@ Fonte dei requisiti: [MVP.md](MVP.md) e gli ADR. Livello: vedi sopra. Il nome de
| CL-04 | Cartella di lavoro = cartella dell'exe, non la cwd del chiamante (criterio MVP 1) | U/P | `Cartella_di_lavoro_e_quella_dell_exe` | [ ] |
| CL-05 | Stdin chiuso subito: nessuna attesa di 3 s | P | `Stdin_chiuso_non_blocca` | [ ] |
| CL-06 | CLI: l'exit code di `claude` è propagato (criterio MVP 3) | P/E | `ExitCode_propagato` | [ ] |
| CL-07 | CLI: `claude` assente ⇒ messaggio chiaro ed exit code 127 | P/E | `Claude_assente_127` | [ ] |
| CL-07 | CLI: eseguibile del motore assente ⇒ messaggio chiaro (con il nome dell'eseguibile) ed exit code 127 | P/E | `Claude_assente_127` | [ ] |
| CL-08 | CLI: stdout/stderr ereditati e ritrasmessi (criterio MVP 2: streaming) | E | `Output_ereditato_in_tempo_reale` | [ ] |
| CL-09 | GUI: output catturato e ripulito agli estremi; stderr separato | P | `Output_catturato` | [ ] |
| CL-10 | GUI: `claude` assente ⇒ eccezione riconoscibile, non crash | P | `Claude_assente_in_GUI` | [ ] |
| CL-11 | Il catalogo modelli contiene gli ID attesi e gli effort previsti | U | `Catalogo_modelli_ed_effort` | [ ] |
| CL-12 | Senza argomenti non parte la CLI; con argomenti sì (tutti uniti in un prompt) | E | `Argomenti_uniti_in_un_prompt` | [ ] |
### Codex — `Core/Engine.cs` (parser e argomenti), `Core/EngineRunner.cs`
Le righe CL valgono per ogni motore; queste sono specifiche di Codex (ADR-0002). Il parser si prova con output JSONL reali salvati nei test.
| ID | Requisito | Liv. | Test | |
|---|---|---|---|---|
| CX-01 | Primo messaggio: `exec --json --skip-git-repo-check -s read-only … -- <prompt>` | U | `Codex_primo_messaggio` | [ ] |
| CX-02 | Ripresa: `exec resume --json … -c sandbox_mode="read-only" <id> -- <prompt>`, senza `-s` | U | `Codex_ripresa` | [ ] |
| CX-03 | Un prompt che inizia con `-` arriva intatto (grazie a `--`) | U/P | `Codex_prompt_con_trattino` | [ ] |
| CX-04 | Modello ⇒ `-m`; effort ⇒ `-c model_reasoning_effort=<livello>`; vuoti ⇒ omessi | U | `Codex_modello_ed_effort` | [ ] |
| CX-05 | Il parser legge `thread_id` da `thread.started` | U | `Codex_thread_id` | [ ] |
| CX-06 | La risposta è l'ultimo `agent_message`, anche con più messaggi | U | `Codex_ultimo_agent_message` | [ ] |
| CX-07 | `turn.failed`: messaggio interno di `error.message` estratto dal JSON annidato; exit ≠ 0 ⇒ errore | U | `Codex_errore_turno` | [ ] |
| CX-08 | Eventi `item.completed` di tipo `error` (avvisi) non fanno fallire la risposta | U | `Codex_avvisi_ignorati` | [ ] |
| CX-09 | Exit 0 senza `agent_message` ⇒ errore "nessuna risposta" | U | `Codex_nessuna_risposta` | [ ] |
| CX-10 | Righe non JSON o eventi sconosciuti sono ignorati senza eccezioni | U | `Codex_righe_non_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` | [ ] |
### Cronologia — `Core/ChatStore.cs`, `Core/AppInfo.cs`
| ID | Requisito | Liv. | Test | |
|---|---|---|---|---|
@@ -78,6 +95,7 @@ Fonte dei requisiti: [MVP.md](MVP.md) e gli ADR. Livello: vedi sopra. Il nome de
| ST-08 | Compatibilità del formato: un JSON scritto dalla versione WinForms si legge | U | `Formato_storico_compatibile` | [ ] |
| ST-09 | Migrazione: la cartella di un nome precedente (`LlmRun`) si sposta nella nuova al primo avvio; se la nuova esiste non si tocca nulla | U | `Migrazione_da_nome_precedente` | [ ] |
| ST-10 | `AppInfo.Name` = `AssemblyName` del csproj | U | `Nome_da_assembly` | [ ] |
| ST-12 | Il JSON salva `Engine` e `SessionId`; un file senza questi campi si legge come chat di Claude Code con `SessionId` = id | U | `Motore_e_sessione_salvati_e_compatibilita` | [ ] |
| ST-11 | Errore di I/O nel salvataggio non perde la chat a schermo | U | vedi VM-12 | [ ] |
### Logica della chat — `Gui/MainViewModel.cs`
@@ -157,15 +175,24 @@ Fonte dei requisiti: [MVP.md](MVP.md) e gli ADR. Livello: vedi sopra. Il nome de
Per **ogni** motore, la stessa batteria di contratto:
| ID | Requisito | Liv. | |
|---|---|---|---|
| EN-01 | Argomenti del primo messaggio e della ripresa | U | [ ] |
| EN-01 | Argomenti del primo messaggio e della ripresa (Claude: CL-02; Codex: CX-01, CX-02) | U | [ ] |
| EN-02 | Rilevamento nel PATH; assente ⇒ disabilitato e messaggio chiaro | U/P | [ ] |
| EN-03 | Lettura dell'id di sessione dall'output (Codex) | U | [ ] |
| EN-03 | Lettura dell'id di sessione dall'output (Codex: CX-05) | U | [ ] |
| EN-04 | Parser dell'output → markdown, con output di esempio reali salvati nei test | U | [ ] |
| EN-05 | Modelli ed effort mostrati secondo il motore | U/G | [ ] |
| EN-06 | Il JSON della chat memorizza il motore; cambiare motore apre una nuova chat | U | [ ] |
| EN-07 | Politica dei permessi: nessun flag che salta i controlli senza scelta esplicita | U | [ ] |
| EN-08 | Impostazioni persistite e rilette | U | [ ] |
| EN-09 | Gestione del blocco di `agy -p` con output rediretto (timeout, errore chiaro) | P | [ ] |
| EN-10 | `Engines.Find` per id (`claude`, `codex`, sconosciuto ⇒ nullo); l'id salvato nei JSON resta stabile | U | [ ] |
| EN-11 | Cambiare CLI nelle Impostazioni apre una nuova chat con quel motore; modello ed effort tornano a "Predefinito"; liste secondo il motore | U | [ ] |
| EN-12 | Motore non installato: etichetta "non installato", non selezionabile; il VM rifiuta la selezione | U/G | [ ] |
| EN-13 | L'interruttore CLI è disabilitato durante l'attesa | U/G | [ ] |
| EN-14 | Aprendo una chat salvata il selettore passa al motore della chat; sessione e `started` coerenti | U | [ ] |
| EN-15 | Etichetta autore, segnaposto, messaggio d'attesa ed elenco chat mostrano il nome del motore | U/G | [ ] |
| EN-16 | Dopo il primo invio con Codex il `SessionId` è quello letto dall'output e il secondo invio usa la ripresa; con errore al primo invio resta senza sessione | U | [ ] |
| EN-17 | CLI `--engine <id> "prompt"`; id sconosciuto o prompt mancante ⇒ uso su stderr ed exit 2; senza `--engine` ⇒ Claude Code | E | [ ] |
| EN-18 | Rilevamento nel PATH: nome esatto su Linux, `.exe` su Windows; cartella inesistente nel PATH ignorata | U | [ ] |
## Come si eseguono (a implementazione avvenuta)
Comandi previsti, da confermare con ADR-0003 → "Da verificare":
@@ -1,6 +1,6 @@
# ADR-0002 — Più motori CLI (Claude Code, Codex, Antigravity) su Windows e Linux
- **Stato**: Proposta
- **Stato**: Accettata per Claude Code e Codex (verificati con prove reali); Antigravity resta da verificare
- **Data**: 2026-10-15
- **Collegato a**: [ADR-0001](0001-gui-multipiattaforma-avalonia.md) (GUI e piattaforme)
@@ -17,31 +17,45 @@ 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` diventa `RunEngine(engine, …)`.
- **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.
- **Impostazioni** nella GUI: motore attivo, modello, effort (solo se supportato), persistite su disco accanto alla cronologia. La CLI ottiene `--engine`.
- **Impostazioni** nella GUI: interruttore **CLI** (motore attivo), modello, effort. Cambiare motore apre una nuova chat; modello ed effort tornano a "Predefinito" e le liste sono quelle del motore. La persistenza su disco delle impostazioni è rinviata. La CLI ottiene `--engine <id>` (`claude`, `codex`).
- **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.
- **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.
- **Output**: ogni motore ha un parser che lo normalizza in testo markdown prima del rendering.
## Comandi dei motori
Da verificare con `--help` delle versioni installate: derivano da documentazione e ricerche, non da prove su questo PC, dove `codex` e `agy` non sono installati.
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 CLI | Antigravity CLI |
|---|---|---|---|
| Comando | `claude -p <prompt>` | `codex exec <prompt>` | `agy -p <prompt>` |
| Continuare | `--session-id <guid>` poi `--resume <guid>` | `codex exec resume <id> <prompt>` | da verificare |
| Comando (GUI) | `claude -p <prompt>` | `codex exec --json --skip-git-repo-check -s read-only [opzioni] -- <prompt>` | `agy -p <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>` | da verificare |
| Modello | `--model` | `-m` / `--model` | da verificare |
| Effort | `--effort` | via `--config` (da verificare) | non noto |
| Effort | `--effort` | `-c model_reasoning_effort=<livello>` | non noto |
| Output usato | testo su stdout | JSONL su stdout (vedi sotto) | da verificare |
**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.
- Con `--json`, stdout contiene solo eventi: `thread.started`, `turn.started`, `item.completed` (con `item.type = "agent_message"` e il testo della risposta; con `"error"` sono avvisi non fatali), `turn.completed`, e in caso di errore `error` e `turn.failed`. Il messaggio di errore è una stringa che contiene a sua volta un JSON (`error.message`): l'app estrae il messaggio interno. L'exit code è 1 se il turno fallisce.
- La risposta mostrata è l'**ultimo** `agent_message` del turno (come `--output-last-message`).
- `exec resume` **non accetta `-s/--sandbox`**: la sola lettura si ottiene con `-c 'sandbox_mode="read-only"'`, verificato (un file richiesto in scrittura non viene creato). Accetta `-m` e `-c`.
- `--skip-git-repo-check` serve perché la cartella dell'exe non è un repository Git.
- `--` prima del prompt evita che un prompt che inizia con `-` sia letto come opzione (verificato).
- Lo stdin va chiuso subito, come per Claude Code: altrimenti Codex stampa "Reading additional input from stdin…" e attende.
- Il modello predefinito è quello di `~/.codex/config.toml`. Se richiede una versione più recente di Codex l'errore è chiaro (`requires a newer version of Codex`): l'app lo mostra e [INSTALL.md](../INSTALL.md) indica `codex update`.
- 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.
## Conseguenze
**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.
## Rischi e da verificare
- [ ] **Codex**: l'id di sessione lo genera Codex e non l'app; va letto dall'output (probabilmente con output JSON) per riprendere la chat.
- [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.
- [ ] **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.
- [ ] Autenticazione: ogni CLI deve essere già autenticata; l'app mostra l'errore del motore senza gestire login.
- [ ] Formato dell'output (testo o JSON) e normalizzazione per ciascun motore.
- [ ] Mappatura effort e modelli per motore, con elenco modelli aggiornabile.
- [x] Formato dell'output di Codex: JSONL, normalizzato in testo markdown. Antigravity ancora da verificare.
- [x] Mappatura effort e modelli per Codex (liste fisse, da aggiornare). Antigravity da verificare.
## Riferimenti
- Passi: [ROADMAP.md](../ROADMAP.md), Fase 4.
+1 -1
View File
@@ -22,7 +22,7 @@ Oggi non esistono test. L'app gira su due sistemi operativi, ha una CLI, una GUI
- **Livelli**: unitari (Core, ViewModel, renderer markdown), componenti GUI headless (finestra, template, tastiera), end-to-end sull'eseguibile pubblicato (CLI e codice 127) e smoke di publish per `win-x64` e `linux-x64`.
- **Predisposizioni di testabilità** nel codice (da fare insieme ai primi test):
- `ChatStore` diventa un'istanza con cartella iniettata, non uno statico legato a `LocalApplicationData`.
- L'esecuzione di `claude` sta dietro un'interfaccia (`IClaudeRunner`) iniettata nel ViewModel; l'implementazione reale resta in `Core/Claude.cs`. I test del ViewModel usano un finto runner; quelli di `Claude` usano `FakeClaude`.
- L'esecuzione dei motori sta dietro un'interfaccia (`IEngineRunner`) iniettata nel ViewModel; l'implementazione reale resta in `Core/EngineRunner.cs`. I test del ViewModel usano un finto runner; quelli di `EngineRunner` usano `FakeClaude` (che con la configurazione giusta imita anche Codex).
- La conferma di eliminazione, gli appunti e il focus sono già delegati (`ConfirmDelete`, `CopyText`, `FocusInput`) e restano sostituibili.
- Gli ADR-0002 (motori) richiedono che ogni motore abbia la propria batteria di test sugli argomenti e sul parser dell'output.
- **Struttura del repo**: codice dell'app in `src/agent-wiki-ui/`, test in `tests/`, soluzione unica alla radice. Così il globbing del csproj dell'app non include i sorgenti dei test. Il progetto dell'app espone gli `internal` ai test con `InternalsVisibleTo`.