CHANGELOG.md
Rama main · réplica de lectura del repositorio original.
# Changelog - Lyra
## [Unreleased]
## [1.4.0] - 2026-11-06
### Breaking
- `scripts/async_mcp_wrapper.py` no longer falls back to `~/dev/fedora-setup/scripts`: background MCP scripts are resolved only from the root-owned copy (`/usr/local/lib/lyra/scripts`, `LYRA_SCRIPTS_DIR` or `paths.scripts`). The old path was user-writable and covered by no sudoers rule; run the installer (or set `LYRA_SCRIPTS_DIR`) if you relied on it.
### Added
- Releases: `sbom.json` (CycloneDX inventory of the base and `gpu` dependencies, exact versions from `uv.lock`) attached to each GitHub release; wheel and sdist carry a SLSA provenance attestation and an SBOM attestation, both verifiable with `gh attestation verify`. The release refuses to build when `uv.lock` is stale. Procedure in `docs/dev/release-process.md`.
### Security
- Background tasks: their `.log`, `.pid`, `.progress` and `.done` files move from `/tmp` (predictable names, readable by every account) to a private 0700 directory, `$XDG_RUNTIME_DIR/lyra/tasks` (`LYRA_TASK_DIR` to override). The Discord webhook reaches the task wrapper through the environment, no longer as a command-line argument visible to every account in the process list.
- Daemon socket: created with mode 0600 directly (umask around `bind`) instead of being chmod-ed right after, and `~/.lyra` is created 0700.
- The MCP `destructiveHint` annotation now requires a strict confirmation in the daemon and in standalone mode, like `DANGEROUS_TOOLS`. A third-party server that marked a tool destructive only got the soft `[O/n]` prompt (Enter = yes), and `-y` ran it without asking: the annotation was read by `Pipeline.is_dangerous_action`, which nothing called. Bundled servers are unaffected (fedora-agents annotates exactly the tools already listed).
- Graphical installer (`install.sh --app`, 127.0.0.1:9877): a web page open in the browser could start the install and answer its confirmations (no Host, Origin or Content-Type check; a `text/plain` POST needs no CORS preflight; question ids were `ask-1`, `ask-2`...), and `options.ollama_host` was written unquoted into `~/.bashrc`, so a crafted value ran shell code at the next login. The backend now refuses a non-local Host (DNS rebinding), a foreign Origin and any non-JSON POST; question ids are random; `ollama_host` must be a host name or an IP (all frontends: app, TUI, headless) and is quoted in `.bashrc`.
### Changed
- CI: ruff lint is now blocking (240 findings cleared: 139 were the documented `from manim import *` of the intro animations, now a per-file ignore; `main.py` keeps its CUDA-before-imports order under a justified E402 ignore). The test job also runs `scenes/ironman`.
### Fixed
- Discord notifications: the webhook is read from `config.yaml`, then from the neighbouring `secrets.yaml` where `config.yaml.example` and the data-flow doc put it; only `config.yaml` was read, so a webhook stored as documented sent nothing (daemon and standalone mode).
- `scripts/async_mcp_wrapper.py` ran `sudo -n` on scripts under `~/dev/fedora-setup/scripts`: no sudoers rule covers that user-writable path, so system clone, backup creation and backup restore failed when run in the background. It now uses the root-owned copy resolved by `lyra.core.paths` (`/usr/local/lib/lyra/scripts`, `LYRA_SCRIPTS_DIR`, `paths.scripts`).
- `main_rag.py`: the module docstring sat after `from __future__ import annotations`, so it was not the docstring (and flagged every import); it is first again.
- Bare `except:` in ephaistos and the MCP client no longer swallow Ctrl+C.
- Tests: the Iron Man red-flash test now checks the colour sent to the lamp (it computed it without asserting); the phase 0 timing test mocks the parallel state reads added with the refactor (it made real requests and measured their 2 s timeout).
- Dead code removed: an unused Zod pattern in the indexer, an unused score colour in the step display.
## [1.3.1] - 2026-09-24
### Added
- Daemon: `tool` message on the socket for clients that only need one MCP call (name, arguments, `confirmed` option). The daemon checks the HESTIA catalogue, asks for confirmation when the tool is dangerous and not confirmed, runs it under the busy lock and streams `tool_result` then `result`. No model in the loop; used by neutroncore (roadmap issue 73).
### Changed
- MCP_TOOLS.md regenerated: `tv.get_state` now reports volume, mute and ambilight; `denon.get_status` reports mute, source and reachability (pylips-mcp 0.2.0, denon-mcp 0.2.0).
- First release built and published by the shared release workflow (PyPI Trusted Publishing); 1.3.0 was tagged before the workflow existed.
## [1.3.0] - 2026-09-24
### Added
- `lyra --version` prints the installed `lyra-assistant` version, or the pyproject version when run from sources (#15).
- `scripts/gen_mcp_tools_md.py` regenerates docs/user/MCP_TOOLS.md from the live MCP servers, with a confirmation column derived from `lyra/core/constants.py`.
- EPHAISTOS variants `inventaire_vm` (real VM names from vm_status or `LYRA_VMS`), `lexique_courant` and `verbes_courants` (everyday French synonyms and verbs, written outside any test set).
- Fourth held-out set `tests/cases_hors_regles_3.py` (50 phrasings, real machine names, ten tools never measured before); fifth set `tests/cases_hors_regles_4.py`, sealed before iteration 16.
- `scripts/bench_recall.py`: mechanical recall of a set (rank of the expected tool, precision of the "net" criterion) without calling the model, the measurement to run before any bench.
- EPHAISTOS variants `outil_par_machine`, `mots_courants_2`, `nombres_tri`, `mots_courants_3` (iterations 15-16); `net_assoupli` measured and refuted.
- Iterations 17-21 ("99 % on every set"): variants `cartes_17`, `lumiere_sans_verbe`, `nom_de_vm` (replayed), `force_definitif`; the five development sets reach 21/21, 51/51, 100/100, 50/50, 50/50 with qwen2.5-coder:0.5b; a sixth set (`tests/cases_hors_regles_5.py`) sealed first and measured once.
### Changed
- EPHAISTOS: the 17 refuted or neutral variants are removed from the code (module 1581 -> ~810 lines); the 42 retained ones are the default configuration, still switchable one by one through `LYRA_EXP` for ablation. Word tables and lexicons move to `lyra/models/cartes.py` (data only). Mechanical recall and the benches are identical before and after (21/21, 51/51, 100/100).
- `docs/articles/2026-09-boucle-ephaistos.fr.md`: the improvement loop written up as a research report (abstract, method, results, limits, reproduction), with five SVG figures generated from `benchmarks/results/` by `scripts/gen_figures_boucle.py`.
### Fixed
- **The daemon did not run the path the bench measured.** `EnhancedPipeline` sent EPHAISTOS a single spec, without the `tool_name:` prefix, then let a session-context injection and the intent classifier override the result: "reveille fedora-base" gave `tv.power_on`, "archive test-vm" gave `tv.mute`, "de la lumiere dans le salon" was answered as small talk (manual acceptance test, 2026-09-23). The pipeline now builds the specs exactly like the bench (`specs_pour_ephaistos`, shared), passes all of them, skips the context injection when the variants are active, and treats a query as a command when the spec sort is unambiguous (`Ephaistos.est_net`). `tests/test_campaign_llm.py --reel` measures this real path (one session per case, correct refusals of unknown VMs counted apart): 21/21, 43/43 and 91/92 on the three development sets.
- One-shot calls: a pending clarification or choice left in the shared session polluted the next call ("mets staging-03 au repos" was read as an answer about preprod-10). It is cleared when nobody can answer.
- Argument names invented by the 0.5b in `missing_args` ("chromecast") no longer trigger a clarification; only real parameters of the tool count.
- Hue: a `group_id` that does not exist on the bridge is replaced by the default group (real groups read at startup).
- Performance mode (`-p`) and `-y`: LYRA printed "Tu confirmes ?" then executed without waiting; it now says what it does.
- Slang normalizer: URLs are no longer lowercased (a YouTube id is case-sensitive).
- A matching rule, or a spec sort that recognises a tool word, is a command whatever the intent classifier says ("silence sur l'ampli" and "les leds de la tele, tu me les enleves" got no action); a tool imposed by a net sort no longer triggers the disambiguation question.
- Rules: "affiche les taches" lists the running tasks; the Denon rule also answers to "ampli", "AVR" and "home cinema" ("l'ampli sur game"), a state question gives `get_status`, "bascule ... sur le bluray" is a source change.
- Security: `main_rag.py` compared a prefixed tool name (`fedora.vm_destroy`) to the short names of `DANGEROUS_TOOLS`, so `-y` skipped the confirmation of destructive tools; it now uses `is_dangerous_tool()`.
- Security: an empty answer (Enter) no longer confirms a sensitive or destructive action in the daemon UI and the terminal UI; `o`/`oui` is required, as in `actions.py`.
- Security: `vm_snapshot` (revert/delete), `vm_import` and `vm_copy` are now in `DANGEROUS_TOOLS`; `PERFORMANCE_TOOLS` entries are prefixed (the eight `cast_*` never matched), Denon and read-only tools added; `main.py` (legacy) no longer keeps its own diverging lists.
- Rules: `tv.sound_only` and `tv.ambilight_color` do not exist in pylips-mcp; "son seul / mode musique" now maps to `tv.screen_off`, a colour request to `ambilight_mode(manual)` (pylips-mcp#7); `tv.youtube_video` sends `video` (schema name) instead of `video_id`.
- Rules: "lance CMD sur VM" is an exec, "l'etat de la lampe" is not a VM status, "il est tard" is not a state question (found by the fourth set control).
- `scripts/index_rag_3tier.py` ignores documents without `server_name` (a local `ironman.run_scene` would have created an UNKNOWN server); `config.yaml.example` declares the catt server.
- EPHAISTOS: a tool imposed by a net sort was overridden by the second pass (`double_passe`) and applied after the argument completion; the net criterion ignored the machine bonus. Three fixes with unit tests.
- Rules: "eteins les leds de la tele" was a power_off (sixth set control).
- Config: the dead `security.destructive_tools` list (never read, listed a non-existent `vm_delete`) is removed from config.yaml.example; `lyra/core/constants.py` is the only source. `docs/dev/INDEX_RAG.md` documents the v2 -> v3 regeneration order.
- Rules: the screen-manager module is out of the registry (server absent from disk and config, lyra#24); a room name is not a VM; "arrete la synchro des lampes" is not a task kill; a question about the chromecast is `cast_status`; "l'ambilight qui suit la musique" sets the mode. `tracking.delete` and `tracking.kill_task` require an explicit confirmation.
## [1.2.0] - 2026-09-10
### Added
- docs/user/DATA_FLOWS.md: what Lyra captures, keeps, and the optional network outputs with their switches.
- VOICES.md and docs/licenses/: Piper voice licences, Llama 3.2 Community License and use policy ("Built with Llama").
- THIRD_PARTY_LICENSES.md generated by `make licenses` and checked in CI.
- README: English summary, prerequisites table, security guarantees mapped to code and tests, clickable metrics, ecosystem block.
- ROADMAP.md.
### Changed
- Packaging: hatchling, distribution name `lyra-assistant`, dependencies in pyproject with `gpu`/`dev` extras and a uv lockfile; CUDA libraries are no longer imposed.
- CI: reusable workflow (unit + installer tests on 3.11/3.12, ruff, personal-path guard), Dependabot.
- systemd units shipped with `@LYRA_DIR@` / `@HOME@` placeholders; no personal path left in code, scripts or tests.
- Tracking client defaults come from `TRACKING_SERVER_SCRIPT` / `TRACKING_VENV_PYTHON`; RAG spec sources from `LYRA_MCP_SPECS_DIR`.
## [1.1.0] - 2026-09-09
### Changed
- License: AGPL-3.0-or-later with a commercial license option and a CLA (versions up to 1.0.0 remain MIT).
- README: HTTPS clone URL, status badges, dated claims, no more reference to a web domain.
- CLAUDE.md condensed to what an agent needs.
### Removed
- Generated artifacts (empty ChromaDB databases, test reports, Manim caches), the self-referencing `lyra/run.sh` symlink that broke CI, and workshop files (Claude Code prompts, session notes, n8n exports, divergent documentation copy), now archived privately.
### Fixed
- CI: the pip cache step no longer fails on a symlink loop.
## [v1.1.0 - Pipeline modulaire complet] - 2026-03-10
### Refactoring - Decomposition de pipeline.py en modules separes (7 missions)
`pipeline.py` passe de 2899 lignes a 919 lignes (~68% de reduction).
164/164 tests MCP conserves a 100% apres chaque mission.
**Mission 1 - Types et constantes (`core/types.py`, `core/config.py`)**
- Extraction de `QueryType`, `PipelineResult`, constantes MCP (`CATEGORY_KEYWORDS`, `TOOL_TYPE_KEYWORDS`, etc.)
**Mission 2 - Retrievers (`core/retrieval.py`)**
- Classe `Retriever` : encapsule SemanticRetriever + KeywordRetriever + RRFFusion
- API : `initialize()`, `retrieve(query)`, `detect_query_type(query)`
- Pipeline utilise `self._retriever` au lieu de 3 composants separes
**Mission 3 - Menus outils (`core/menus.py`)**
- `is_list_tools_query()`, `process_tools_query_step1()`, `handle_server_selection()`
- Constantes `LIST_VERBS`, `LIST_SUBJECTS`
**Mission 4 - Workflows base (`core/workflows/`)**
- `context.py` : `WorkflowContext` avec callables injectes (`prepare_execution`, `route_query`)
- `vm_start.py` : `handle_vm_start_confirm()` (4 cas oui/non/clarification/autre)
- `vm_stop.py` : `handle_vm_stop_choice()` (choix 1/2/3 arret VM avant clone)
- `vm_snapshot.py` : `handle_vm_snapshot_create_workflow()`, `handle_vm_snapshot_list_workflow()`, `handle_vm_snapshot_pending()`
**Mission 5 - Workflows clone/export (`core/workflows/vm_clone.py`, `vm_export.py`)**
- `vm_clone.py` : `suggest_vm_name()`, `handle_cow_choice()`, `handle_vm_clone_workflow()` (6 cas validation)
- `vm_export.py` : `CUSTOM_EXPORT_GROUPS`, workflow multi-tours (6 groupes virt-sysprep + firstboot)
**Mission 6 - Formatters et validation (`core/formatters.py`, `core/validation.py`)**
- `formatters.py` : `enrich_description()`, `enrich_optional_args()`, `format_listing_result()`
- `validation.py` : `validate_vm_existence()`, helpers VM state, constantes outils VM
**Mission 7 - Cleanup final**
- Suppression imports inutilises (`re`, `sys`, `unicodedata`) dans pipeline.py
- Mise a jour CLAUDE.md section "Project Structure"
- 164/164 PASS confirme (100%)
---
## [v0.7 - 53/54 Tests MCP] - 2026-02-25
### Campagne de tests complete - 54 tests MCP one-shot
Passage de 38/54 (70%) a 53/54 (98%). Un seul FAIL attendu (T10: vm_clone, source VM inexistante).
**Fixes FEDORA:**
- T1: vm_status({}) pour listing global (etait vm_name='' -> EXEC_ERROR)
- T6: vm_exec(vm_name, cmd) "execute CMD sur VM" (etait ls() mauvais outil)
- T7: vm_copy(source, vm_name, dest) "copie FILE vers VM" (etait FAIL)
- T11: vm_clone_system(name) "clone systeme VM en NEWVM" (etait FAIL)
- T12: vm_verify(vm_name) "verifie la VM NAME" (etait FAIL)
- T14: backup_status({}) sans watch=True (etait TIMEOUT boucle infinie)
- T15: backup_create(vm_name) args corrects (etait sans vm_name)
- T16: backup_verify(vm_name) argument correct (etait type= au lieu de vm_name=)
**Fixes TV:**
- T30: tv.volume_up (etait tv.power_on)
- T31: tv.volume_down (etait FAIL)
- T33: tv.mute (etait tv.power_off)
**Fixes DENON:**
- T39: denon.power_off (etait eteins() mauvais outil)
- T42: denon.volume_set(level=50) (etait hue.set_brightness mauvais outil!)
- T44: denon.mute_off (etait denon(status=off))
**Fixes HUE:**
- T19: hue.turn_on_group(group_id=81) (etait allume() via embedding ChromaDB confus)
- T27: hue.turn_off_light (etait turn_on_light!)
**Fixes CATT:**
- T46: catt.cast_scan (etait catt.cast_dual_stop)
- T51: catt.cast_volume (etait denon.volume_up + intent=knowledge)
**Changements techniques:**
1. **BM25 accent normalization** (`keyword_retriever.py`):
- `_tokenize()` normalise les accents (NFD) avant BM25
- "Demarre" dans docs ChromaDB = "demarre" dans query sans accent
2. **`_rule_based_detect()` etendue** (`pipeline.py`):
- BACKUP (6 regles): backup_status/list/verify/create/restore/clean
- FEDORA VM (9 regles): vm_start/stop/destroy/exec/snapshot/status/verify/copy/clone_system
- CATT (2 regles): cast_scan, cast_volume (avec support "diffuse" = SlangNorm("cast"))
- HUE (2 regles): turn_on_group, turn_off_light
- vm_start: exclusion TV context ("lance Netflix sur la TV" -> PAS vm_start)
3. **`_DEMANDE_VERBS_RE` etendu** (`intent_classifier.py`):
- Ajoute: status|statut|nettoie|restaure|allume|eteins|mute|scan|caste|lance|demarre|arrete|supprime
4. **FRENCH_ENRICHMENTS** (`pipeline.py`):
- turn_on_group/turn_off_group: retire verbes dupliques (deja fournis par "turn_on"/"turn_off")
- Distingue singular ("lumiere") vs pluriel ("lumieres") pour light vs group
5. **Normalisation accents dans `_rule_based_detect()`**:
- NFD normalization au debut (SlangNorm ajoute des accents: "execute"->"exécute")
**Fichiers modifies:**
- `lyra/rag/keyword_retriever.py`: `_tokenize()` accent normalization
- `lyra/models/intent_classifier.py`: `_DEMANDE_VERBS_RE` etendu
- `lyra/core/pipeline.py`: `_rule_based_detect()`, FRENCH_ENRICHMENTS, vm_start TV exclusion
---
## [v0.6.1 - Fixes RAG TV/DENON/CATT] - 2026-02-25
### Fixes RAG TV/DENON/CATT (pipeline.py)
**Cause racine:** EPHAISTOS 0.5b echoue sur les outils domotique (volume TV, mute, Denon).
BM25 retourne de mauvais outils ("volume denon a 50" → catt.cast_volume, "demute le denon" → denon.power_off).
SlangNormalizer transforme "cast" → "diffuse" ce qui casse la detection cast_scan.
**Fix: regles TV/DENON/CATT dans `_rule_based_detect()`** (`pipeline.py`):
- TV: `tv.volume_up/down/set/mute` detectes par presence de "tv/tele" + verbe/chiffre/sourdine
- DENON: `denon.power_on/off/mute_on/off/volume_up/down/set` detectes par presence de "denon"
- Couvre formes original ("eteins", "demute") et normalisees ("sourdine")
- Ordre: power_off/on → mute_off/on → volume_set → volume_up/down
- CATT scan: "scan + cast/diffuse/appareils" → `catt.cast_scan`
(gere les deux formes: original "cast" et SlangNorm "diffuse")
**Resultats:**
| Query | Avant | Apres |
|-------|-------|-------|
| "monte le volume de la TV" | EPHAISTOS 0.5b echec | tv.volume_up OK |
| "coupe le son de la TV" | EPHAISTOS 0.5b echec | tv.mute OK |
| "volume denon a 50" | BM25 → catt.cast_volume | denon.volume_set {level:50} OK |
| "demute le denon" | BM25 → denon.power_off | denon.mute_off OK |
| "eteins le denon" | BM25 score trop bas | denon.power_off OK |
| "scan les appareils cast" | SlangNorm → cast_youtube | catt.cast_scan OK |
**Fichiers modifies:**
- `lyra/core/pipeline.py` : `_rule_based_detect()` etendu avec regles TV/DENON/CATT
---
## [v0.6 - One-Shot + Fixes RAG] - 2026-02-25
### Mode One-Shot CLI
Ajout du mode one-shot: `lyra "[requete]" [-v] [-y]`
- `lyra "demarre preprod-01"` - execution directe sans UI interactive
- `-v / --verbose` : affiche les etapes RAG en temps reel (`[rag]`, `[intent]`, `[action]`)
- `-y / --yes` : auto-confirme les actions (mode non interactif)
- Codes de sortie: 0=OK, 1=EXEC_ERROR, 2=CANCEL, 3=PIPELINE_FAIL, 124=TIMEOUT
**Fichiers modifies:**
- `main_rag.py` : fonction `run_one_shot()`, argparse mis a jour
- `run.sh` : documentation one-shot dans les commentaires
### Tests MCP one-shot (54 tests)
Resultats: 38/54 = 70% (voir `test_mcp_results.md`)
| Serveur | Total | OK | Problemes |
|---------|-------|----|-----------|
| FEDORA | 18 | 11 | 7 |
| HUE | 9 | 8 | 1 |
| TV | 9 | 5 | 4 |
| DENON | 9 | 6 | 3 |
| CATT | 8 | 7 | 1 |
| MERMAID | 1 | 1 | 0 |
### Fixes RAG FEDORA (vm_clone_system, vm_clone, vm_copy, vm_verify)
**Cause racine identifiee:** EPHAISTOS 0.5b trop petit pour extraire les arguments, enrichissements BM25 confondent "copie" avec vm_clone, IntentClassifier classe "verifie" comme "info".
**Fix 1 - vm_clone_system** (`pipeline.py`):
- Bug substring: `"vm_clone" in "fedora.vm_clone_system"` = True
- Fix: `analysis.tool in ("vm_clone", "fedora.vm_clone")`
**Fix 2 - vm_clone** (`pipeline.py`):
- EPHAISTOS detecte l'outil mais `arguments={}` + `missing_args=[]` → NullPointerError ligne 1290
- Fix: regex fallback dans `_handle_vm_clone_workflow` extrait "clone X en Y"
- Fix: null-check `if source_vm and vm.replace(...)`
- Fix: `_rule_based_detect()` intercepte "clone X en Y" avant EPHAISTOS
**Fix 3 - vm_copy** (`pipeline.py`):
- BM25 retourne vm_clone au lieu de vm_copy ("copie" present dans enrichissement "clone")
- Fix: `_rule_based_detect()` intercepte "copie/transfere FILE vers/sur VM"
**Fix 4 - vm_verify** (`intent_classifier.py`, `pipeline.py`):
- IntentClassifier (Llama 1b) classe "verifie la VM" comme "info" → pipeline ignorait l'outil
- Fix: regex override `_DEMANDE_VERBS_RE` dans `classify()` force "demande" pour verbes d'action
- Fix: `_rule_based_detect()` intercepte "verifie/verif [la VM] NAME"
**Nouveaux examples EPHAISTOS** (`ephaistos.py`):
- vm_clone avec source_vm + new_vm_name
- vm_copy avec source + vm_name + dest
- vm_verify avec vm_name optionnel
- vm_clone_system avec name
**Fichiers modifies:**
- `lyra/core/pipeline.py` : `import re`, `_rule_based_detect()`, fixes `_handle_vm_clone_workflow`
- `lyra/models/intent_classifier.py` : `_DEMANDE_VERBS_RE`, exemples prompt
- `lyra/models/ephaistos.py` : 4 nouveaux exemples FEDORA
---
## [Phase 4] - 2026-01-23
### Operations Async via n8n + Fallback Subprocess
**Ajouts:**
- Module `modules/n8n.py` : Client n8n + executeur async fallback
- 3 workflows n8n crees avec noeud `code` (JS)
- Detection automatique des operations longues (vm_clone, backup_create, backup_restore)
- **Fallback subprocess** : Si n8n non disponible, execute en arriere-plan via threading
- Notification dans le terminal quand operation terminee (mode fallback)
**Workflows n8n:**
| Workflow | Webhook | Description |
|----------|---------|-------------|
| Lyra - Clone VM v2 | `/webhook/lyra-clone-vm-v2` | Clone VM via kvm-clone.sh |
| Lyra - Backup Create | `/webhook/lyra-backup-create` | Cree backup timeshift/borg |
| Lyra - Backup Restore | `/webhook/lyra-backup-restore` | Restaure backup |
**Note:** Les workflows utilisent le noeud `n8n-nodes-base.code` au lieu de `executeCommand`
(ce dernier n'etait pas reconnu par l'API).
**Fichiers modifies:**
- `main.py` : Integration n8n avec fallback subprocess
- `modules/n8n.py` : Client n8n + AsyncExecutor pour fallback
- `config.yaml` : n8n.enabled = true, webhook path mis a jour
- `prompts/system_prompt.txt` : Section operations async
**Mode fallback (subprocess):**
Quand n8n n'est pas disponible ou les webhooks ne sont pas actifs:
1. Lyra detecte l'echec du webhook
2. Lance la commande en arriere-plan via subprocess.Popen
3. Notifie dans le terminal quand c'est termine
**Configuration requise:**
```yaml
n8n:
enabled: true
base_url: http://localhost:5678
api_key: <votre-cle-api> # Optionnel
webhooks:
clone-vm: /webhook/lyra-clone-vm-v2
backup-create: /webhook/lyra-backup-create
backup-restore: /webhook/lyra-backup-restore
```
**Pour activer les webhooks n8n:**
1. Aller sur http://localhost:5678
2. Ouvrir chaque workflow Lyra
3. Cliquer sur le toggle "Active" en haut a droite
4. Les webhooks seront alors operationnels
---
## [Phase 3] - 2026-01-23
### Actions + Securite
**Ajouts:**
- Principe **Read-First** : verification automatique de l'etat VM avant toute action
- System prompt externe (`prompts/system_prompt.txt`) au lieu de hardcode
- Confirmation vocale avec mots-cles ("oui"/"non"/"oui je confirme")
- Affichage de l'etat VM dans la confirmation (status, IP)
- Description vocale naturelle des actions proposees
**Regles de securite:**
- `READ_FIRST_TOOLS` : vm_start, vm_stop, vm_destroy, vm_snapshot, vm_clone
- `READ_ONLY_TOOLS` : vm_status, backup_status, backup_list, backup_verify
- Actions dangereuses : confirmation explicite "oui je confirme" en vocal
**Fichiers modifies:**
- `main.py` : Chargement prompt externe, Read-First, descriptions vocales
- `modules/ui.py` : Confirmation vocale, affichage etat VM
- `prompts/system_prompt.txt` : Regles de securite detaillees
**Confirmation vocale:**
- Option `[v]` pour passer en mode vocal
- Mots-cles acceptes : oui, yes, ok, d'accord, confirme, vas-y
- Mots-cles annulation : non, no, annule, stop, arrete
- Actions destructives : exigent "oui je confirme"
---
## [Phase 2] - 2026-01-22
### Mode Vocal (STT/TTS)
**Ajouts:**
- Mode `--vocal` pour interaction vocale complete
- Module `modules/audio.py` : STT, TTS, AudioRecorder, VoiceInterface
- Script `run.sh` : Lancement avec configuration CUDA automatique
- Alias bash `lyra` et `lyra --vocal`
- Bip sonore avant l'ecoute
- Indicateur visuel de niveau audio en temps reel
- Detection de silence pour fin d'enregistrement automatique
**Stack vocale:**
- STT : faster-whisper (modele base, CUDA float16)
- TTS : Piper (voix fr_FR-upmc-medium)
- Audio : sounddevice avec resampling 48kHz
**Configuration (`config.yaml`):**
```yaml
audio:
sample_rate: 48000 # Compatible PipeWire/ALSA
silence_threshold: 0.005
silence_duration: 1.0
stt:
model: base
device: cuda
compute_type: float16
tts:
model: fr_FR-upmc-medium
```
**Dependances ajoutees:**
- faster-whisper
- piper-tts
- sounddevice
- nvidia-cublas-cu12
- nvidia-cudnn-cu12
**Fixes techniques:**
- Resampling audio : 48kHz (PipeWire) <-> 16kHz (Whisper) <-> 22050Hz (Piper)
- `suppress_stderr()` : Masque les warnings cffi de sounddevice
- Configuration CUDA via `LD_LIBRARY_PATH` dans run.sh
**Issue connue:**
- Elgato Wave 3 non fonctionnel (necessite Wave Link sur Windows/Mac)
- Workaround : Utiliser G522 LIGHTSPEED comme micro par defaut
---
## [Phase 1] - 2024
### Wrapper Python + MCP (mode texte)
**Fonctionnalites:**
- Client Ollama avec parsing JSON des tool calls
- Client MCP pour fedora-agents
- Human-in-the-Loop : confirmation avant execution
- Interface CLI en francais
- Raccourcis clavier (Ctrl+L, double Ctrl+C)
**Outils MCP disponibles:**
- VM : vm_status, vm_start, vm_stop, vm_snapshot, vm_exec, vm_clone
- Backup : backup_status, backup_list, backup_create, backup_verify