merge(workstate): intègre le snapshot délégations/file par agent (Lot B) dans develop
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -1,11 +1,9 @@
|
|||||||
---
|
---
|
||||||
upTo: 39f05602-a660-4632-accc-42d4887ce1ed
|
upTo: b916af4a-6f94-4777-92b1-c5f89f898cde
|
||||||
objective: Tâche : ajouter UN test fonctionnel anti-régression de la communication inter-agent (round-trip question/réponse) au plus haut niveau de fidélité possible SANS lancer de vrai CLI IA (zéro token). Cont
|
objective: Tâche : ajouter UN test fonctionnel anti-régression de la communication inter-agent (round-trip question/réponse) au plus haut niveau de fidélité possible SANS lancer de vrai CLI IA (zéro token). Cont
|
||||||
---
|
---
|
||||||
**Objectif :** Tâche : ajouter UN test fonctionnel anti-régression de la communication inter-agent (round-trip question/réponse) au plus haut niveau de fidélité possible SANS lancer de vrai CLI IA (zéro token). Cont
|
**Objectif :** Tâche : ajouter UN test fonctionnel anti-régression de la communication inter-agent (round-trip question/réponse) au plus haut niveau de fidélité possible SANS lancer de vrai CLI IA (zéro token). Cont
|
||||||
|
|
||||||
- **Response:** ✅ LS4 — Tests couche application « limites de session » : TOUT VERT, zéro régression. Commande : `cargo test -p application` Résultat : **tous les binaires de test verts, 0 failed** sur l'ensemble du crate. 16 nouveaux tests LS4 ajoutés, répartis en 2 fichiers d'intégration (style des tests applicatifs existants, 100 % fakes des ports). Confirmation explicite NON-RÉGRESSION : les suites préexistantes citées restent vertes — - `drain_with_readiness_lot1.rs` : **7/7** ✓ - `send_blocking_d1.rs` : **9/9** ✓ - (et tout le reste du crate : project/agent_lifecycle/orchestrator/structured_launch/etc., 0 échec). === Nouveau fichier `tests/session_limit_service.rs` (9 tests) — fakes Clock fixe / Scheduler enregistreur-contrôlable / EventBus espion / AgentResumer espion-contrôlable === (a) détection→planification : - `on_rate_limited_future_arms_and_emits_in_order` : Some(reset futur) ⇒ EXACTEMENT 1 `arm(fire_at_ms==reset, ResumeAgent{agent,node,conv})` + events `AgentRateLimited` PUIS `AgentResumeScheduled` dans cet ordre. - `on_rate_limited_past_reset_clamps_fire_at_to_now` : reset passé ⇒ `fire_at_ms==now` (clamp anti-passé) ; l'event RateLimited garde l'heure brute passée, ResumeScheduled porte le now clampé. - `on_rate_limited_without_reset_is_human_fallback_no_arm` : None ⇒ AUCUN arm + events `AgentRateLimited{None}` puis `AgentRateLimitSuspected{None}`. - `on_rate_limited_twice_same_agent_dedups_cancelling_previous` : 2 signaux même agent ⇒ l'ancien ScheduleId est cancel-é avant réarmement, et AUCUN `AgentResumeCancelled` émis (dédoublonnage interne silencieux, §21.10-4). (b) exécution : - `execute_resume_calls_resumer_with_prompt_and_emits_resumed` : resume(agent,node,conv, prompt==RESUME_PROMPT vérifié) + `AgentResumed` publié + entrée retirée (cancel_resume ultérieur ⇒ false). - `execute_resume_propagates_error_without_emitting_resumed` : Resumer Err ⇒ erreur propagée ET `AgentResumed` NON publié. (c) annulation : - `cancel_resume_after_arm_returns_true_and_emits_cancelled` : cancel renvoyant true ⇒ true + bon ScheduleId passé + `AgentResumeCancelled` publié. - `cancel_resume_without_arm_is_false_no_event` : sans armement ⇒ false, Scheduler non sollicité, aucun event. - `cancel_resume_when_scheduler_already_fired_is_false_no_event` : Scheduler renvoyant false (« déjà tiré ») ⇒ false ET PAS d'`AgentResumeCancelled` (contrat anti-course important). === Nouveau fichier `tests/session_limit_t4.rs` (7 tests) — réconciliation §21.2-T4, fakes AgentSession + InputMediator === - `outcome_rate_limited_some_without_final_is_graceful` : `[RateLimited{Some(t)}]` ⇒ `Ok(TurnOutcome::RateLimited{Some(t)})` (PAS d'Err) + vérifie qu'un RateLimited ne marque PAS Idle. - `outcome_rate_limited_none_without_final_is_graceful` : `[RateLimited{None}]` ⇒ `Ok(RateLimited{None})`. - `outcome_rate_limited_then_final_is_completed` : `[.., RateLimited, Final]` ⇒ `Completed(contenu)` (Final l'emporte) + Idle marqué. - `outcome_truncated_stream_without_final_or_ratelimit_is_io_error` : `[TextDelta]` seul ⇒ `Err(Io)` INCHANGÉ (non-régression critique du flux tronqué). - non-régression signatures historiques : `drain_with_readiness_rate_limited_is_io_error`, `send_blocking_rate_limited_is_io_error` (limite ⇒ Io), `drain_with_readiness_nominal_still_completes` (cas nominal Final intact). Aucun bug détecté : le code DevBackend est conforme au cadrage §21.5 sur tous les axes (ordre des events, clamp via plan_resume, dédoublonnage silencieux, prompt constant, propagation d'erreur sans event, contrat anti-course du cancel). Je n'ai modifié AUCUNE ligne de prod (uniquement ajouté 2 fichiers de tests). Feature LS4 validée côté tests application. Prêt pour la suite du cycle (Git : merge éventuel).
|
|
||||||
- **Prompt:** Tests LS5 — parser regex niveau 2 (infra) : `RateLimitParser` (crates/infrastructure/src/ratelimit/mod.rs) + le module d'heure partagé `timeparse` (crates/infrastructure/src/timeparse.rs). Branche feature/agent-session-limits. DevBackend a livré (cargo build -p infrastructure OK, 0 warning ; tests LS2 toujours verts). Tout est pur (now_ms injecté), testable sans I/O. Écris et exécute les tests dans le style existant. RateLimitParser (new + detect + applies) : - `new` sur regex INVALIDE → None (jamais de panique) ; - pattern qui matche SANS reset_capture → `Some(SessionLimit{ resets_at_ms: None, source: Pattern, detected_at_ms == now_ms })` ; - capture nommée `(?P<reset>...)` + time_format ABSOLU : `epoch_s` (secondes→×1000), `epoch_ms` (tel quel), `iso8601`/`rfc3339` (`...Z` → ms attendus) → resets_at_ms corrects ; - time_format RELATIF (`relative_s`, capture « 600 », now=T) → `Some(resets_at_ms == T + 600_000)` ; - time_format MURAL (`wall`, capture « 3pm ») : now correspondant à 10h du jour → 15h AUJOURD'HUI (même jour UTC) ; now correspondant à 16h → 15h DEMAIN (passage de minuit, +24h). Choisis des now_ms calculés proprement (epoch connu) et calcule l'attendu à la main ; - pattern NE matche PAS → detect → None ; - pattern matche mais capture absente/valeur pourrie/non parsable → `Some(SessionLimit{ resets_at_ms: None })` (détection utile sans heure) ; - vérifie que `source == RateLimitSource::Pattern` dans tous les cas détectés ; - compilation du regex faite une seule fois (à new) — au minimum vérifie que detect peut être appelé plusieurs fois sans souci. applies(profile) : - profil avec structured_adapter (structuré) → false (même s'il a un rate_limit_pattern) ; - profil PTY (sans structured_adapter) AVEC rate_limit_pattern → true ; - profil PTY SANS rate_limit_pattern → false. timeparse (fonctions réexportées) : - `days_from_civil` : 1970-01-01 → 0 ; une année bissextile (2024-02-29) cohérente ; - `parse_wall_clock` : « 3pm » → (15,0,0) ; « 15:00:30 » → (15,0,30) ; format invalide → None ; - `wall_clock_to_ms` : même jour vs lendemain selon now ; - `parse_absolute_ms` : epoch s/ms/ISO cohérents (recoupe avec LS2 si utile). - NON-RÉGRESSION : confirme que les tests LS2 (parse_reset_ms/parse_event dans session/mod.rs) restent verts après le refactor qui a déplacé les helpers vers timeparse. Exécute `cargo test -p infrastructure`. Rends : rapport complet (nb tests, pass/fail), confirmation explicite que LS2/LS3 restent verts, et en cas d'échec rapport CLAIR (attendu vs obtenu) pour DevBackend. EN PLUS (petit nettoyage que DevBackend t'a signalé) : tes tests scheduler LS3 (crates/infrastructure/src/scheduler/mod.rs, ~lignes 253/285) déclenchent 2 warnings `irrefutable if let` car ScheduledTask est mono-variante aujourd'hui. Corrige ces 2 warnings dans le code de TEST (ex. déstructuration directe au lieu de `if let`) pour garder la suite sans warning — c'est du test, tu peux le modifier. Ne touche à aucun code de prod ; signale tout vrai bug sans le corriger.
|
|
||||||
- **Response:** ✅ LS5 — Tests parser regex niveau 2 (`RateLimitParser`) + module `timeparse` : TOUT VERT, 0 warning, zéro régression. Commande : `cargo test -p infrastructure` Résultat lib : **221 passed; 0 failed; 0 ignored** (195 antérieurs + **26 nouveaux LS5**). Tous les fichiers d'intégration + doctests verts. **Build de test : 0 warning** (vérifié `--no-run | grep -c warning` = 0). Confirmation explicite NON-RÉGRESSION : - **LS2** (`session::tests::parse_reset_ms*` / `parse_event*` / `send_emits_rate*`) : tous verts APRÈS le refactor qui a déplacé les helpers vers `timeparse` ✓ - **LS3** (`scheduler::tests::*`) : 7/7 verts ✓ (+ les 2 warnings `irrefutable if let` corrigés — voir plus bas) === `ratelimit/mod.rs` — 15 tests (in-file `#[cfg(test)] mod tests`) === new + detect : - `new_returns_none_on_invalid_regex` : regex invalide `"rate limit ("` ⇒ None (jamais de panique). - `detect_returns_none_when_pattern_does_not_match` : pas de match ⇒ None. - `detect_match_without_reset_capture_has_no_time` : match sans reset_capture ⇒ `SessionLimit{resets_at_ms:None, source:Pattern, detected_at_ms==now}`. - formats ABSOLUS : `detect_epoch_seconds_format` (×1000), `detect_epoch_millis_format` (tel quel), `detect_iso8601_format` (`2023-11-14T22:13:20Z`→1_700_000_000_000). - format RELATIF : `detect_relative_seconds_format_uses_now` (capture « 600 », now=T ⇒ T+600_000). - format MURAL (passage de minuit, math calculée à la main sur DAY_START=1_699_920_000_000 = 2023-11-14T00:00Z) : `detect_wall_clock_same_day_when_future` (now=10h, « 3pm » ⇒ 15h même jour) ; `detect_wall_clock_next_day_when_past` (now=16h ⇒ 15h DEMAIN, +24h). - capture inexploitable ⇒ détection sans heure : `detect_match_with_missing_capture_group_has_no_time`, `detect_match_with_unparsable_value_has_no_time` (⇒ `resets_at_ms:None`). - `detect_can_be_called_multiple_times` : regex compilé une seule fois, detect appelé 3× sans souci. - `source == RateLimitSource::Pattern` asserté dans tous les cas détectés. applies(profile) : - `applies_false_for_structured_profile_even_with_pattern` (structuré ⇒ false, niveau 1 prend la main) ; - `applies_true_for_pty_profile_with_pattern` (PTY + pattern ⇒ true) ; - `applies_false_for_pty_profile_without_pattern` (PTY sans pattern ⇒ false). === `timeparse.rs` — 11 tests === - `days_from_civil_epoch_is_zero` (1970-01-01→0) ; `days_from_civil_leap_day_2024` (2024-02-29→19782, + cohérence avec le 1ᵉʳ mars). - `parse_wall_clock_variants` (« 3pm »→(15,0,0), « 15:00:30 »→(15,0,30), « 3:30 pm »→(15,30,0), 12am→0h, 12pm→12h) ; `parse_wall_clock_rejects_invalid` (texte, 25:00, 10:75, trop de composantes ⇒ None). - `wall_clock_to_ms_*` : même jour / lendemain / cas-limite « cible == now » (⇒ lendemain, jamais le présent). - `parse_absolute_ms_*` : epoch s/ms, ISO8601, garbage→None (recoupe LS2). - `parse_rfc3339_offsets_convert_to_utc` : +01:00 et −01:00 ⇒ même instant UTC. === Nettoyage LS3 demandé === Les 2 warnings `irrefutable if let` (scheduler/mod.rs ~253/285, `ScheduledTask` mono-variante) sont corrigés : remplacés par une **déstructuration directe** `let ScheduledTask::ResumeAgent { conversation_id, .. } = task;` (code de TEST uniquement). Suite désormais 0 warning. Aucun bug détecté : le code DevBackend est conforme au cadrage §21 niveau 2 sur tous les axes (robustesse regex, stratégies de format absolu/relatif/mural, passage de minuit UTC, règle anti-double-détection `applies`, factorisation `timeparse` sans casser LS2). Je n'ai modifié AUCUN code de prod (uniquement ajouté des tests + corrigé mes propres tests LS3). Feature LS5 validée côté tests infra. Prêt pour la suite du cycle.
|
- **Response:** ✅ LS5 — Tests parser regex niveau 2 (`RateLimitParser`) + module `timeparse` : TOUT VERT, 0 warning, zéro régression. Commande : `cargo test -p infrastructure` Résultat lib : **221 passed; 0 failed; 0 ignored** (195 antérieurs + **26 nouveaux LS5**). Tous les fichiers d'intégration + doctests verts. **Build de test : 0 warning** (vérifié `--no-run | grep -c warning` = 0). Confirmation explicite NON-RÉGRESSION : - **LS2** (`session::tests::parse_reset_ms*` / `parse_event*` / `send_emits_rate*`) : tous verts APRÈS le refactor qui a déplacé les helpers vers `timeparse` ✓ - **LS3** (`scheduler::tests::*`) : 7/7 verts ✓ (+ les 2 warnings `irrefutable if let` corrigés — voir plus bas) === `ratelimit/mod.rs` — 15 tests (in-file `#[cfg(test)] mod tests`) === new + detect : - `new_returns_none_on_invalid_regex` : regex invalide `"rate limit ("` ⇒ None (jamais de panique). - `detect_returns_none_when_pattern_does_not_match` : pas de match ⇒ None. - `detect_match_without_reset_capture_has_no_time` : match sans reset_capture ⇒ `SessionLimit{resets_at_ms:None, source:Pattern, detected_at_ms==now}`. - formats ABSOLUS : `detect_epoch_seconds_format` (×1000), `detect_epoch_millis_format` (tel quel), `detect_iso8601_format` (`2023-11-14T22:13:20Z`→1_700_000_000_000). - format RELATIF : `detect_relative_seconds_format_uses_now` (capture « 600 », now=T ⇒ T+600_000). - format MURAL (passage de minuit, math calculée à la main sur DAY_START=1_699_920_000_000 = 2023-11-14T00:00Z) : `detect_wall_clock_same_day_when_future` (now=10h, « 3pm » ⇒ 15h même jour) ; `detect_wall_clock_next_day_when_past` (now=16h ⇒ 15h DEMAIN, +24h). - capture inexploitable ⇒ détection sans heure : `detect_match_with_missing_capture_group_has_no_time`, `detect_match_with_unparsable_value_has_no_time` (⇒ `resets_at_ms:None`). - `detect_can_be_called_multiple_times` : regex compilé une seule fois, detect appelé 3× sans souci. - `source == RateLimitSource::Pattern` asserté dans tous les cas détectés. applies(profile) : - `applies_false_for_structured_profile_even_with_pattern` (structuré ⇒ false, niveau 1 prend la main) ; - `applies_true_for_pty_profile_with_pattern` (PTY + pattern ⇒ true) ; - `applies_false_for_pty_profile_without_pattern` (PTY sans pattern ⇒ false). === `timeparse.rs` — 11 tests === - `days_from_civil_epoch_is_zero` (1970-01-01→0) ; `days_from_civil_leap_day_2024` (2024-02-29→19782, + cohérence avec le 1ᵉʳ mars). - `parse_wall_clock_variants` (« 3pm »→(15,0,0), « 15:00:30 »→(15,0,30), « 3:30 pm »→(15,30,0), 12am→0h, 12pm→12h) ; `parse_wall_clock_rejects_invalid` (texte, 25:00, 10:75, trop de composantes ⇒ None). - `wall_clock_to_ms_*` : même jour / lendemain / cas-limite « cible == now » (⇒ lendemain, jamais le présent). - `parse_absolute_ms_*` : epoch s/ms, ISO8601, garbage→None (recoupe LS2). - `parse_rfc3339_offsets_convert_to_utc` : +01:00 et −01:00 ⇒ même instant UTC. === Nettoyage LS3 demandé === Les 2 warnings `irrefutable if let` (scheduler/mod.rs ~253/285, `ScheduledTask` mono-variante) sont corrigés : remplacés par une **déstructuration directe** `let ScheduledTask::ResumeAgent { conversation_id, .. } = task;` (code de TEST uniquement). Suite désormais 0 warning. Aucun bug détecté : le code DevBackend est conforme au cadrage §21 niveau 2 sur tous les axes (robustesse regex, stratégies de format absolu/relatif/mural, passage de minuit UTC, règle anti-double-détection `applies`, factorisation `timeparse` sans casser LS2). Je n'ai modifié AUCUN code de prod (uniquement ajouté des tests + corrigé mes propres tests LS3). Feature LS5 validée côté tests infra. Prêt pour la suite du cycle.
|
||||||
- **Prompt:** Tâche QA — tests unitaires de la slice LS7-front (UI limites de session des agents), branche `feature/agent-session-limits`. DevFrontend vient d'implémenter (typecheck `tsc --noEmit` propre, 39 tests agents existants toujours verts, non commité). Périmètre à couvrir (frontend/) : 1. `src/features/agents/useAgents.ts` — nouvel état `limitByAgent: Record<string, AgentLimitState>` (`{ limitedUntil?, resumeFireAt?, suspected? }`) peuplé depuis 5 DOMAIN_EVENT dans l'abonnement `onDomainEvent`. À tester (via le mock gateway qui émet des events) : - `agentRateLimited` {agentId, resetsAtMs} → entrée `{ limitedUntil: resetsAtMs, suspected: false }`. - `agentResumeScheduled` {agentId, fireAtMs} → arme `resumeFireAt`. - `agentResumeCancelled` {agentId} → retire `resumeFireAt`, reste limité. - `agentResumed` {agentId} → supprime l'entrée (état effacé). - `agentRateLimitSuspected` {agentId, resetsAtMs?} → `{ ..., suspected: true }`, y compris le cas SANS `resetsAtMs` (heure inconnue). - séquence réaliste : rateLimited → resumeScheduled → cancelResume (action) → vérifier retrait optimiste + appel `input.cancelResume` (mock `cancelledResumes` / `cancelResumeResult`). - Action `cancelResume(agentId)` exposée par le hook : retrait optimiste + verdict backend renvoyé (teste les deux verdicts via `cancelResumeResult`). 2. `src/features/agents/AgentLimitBadge.tsx` — helpers purs exportés `formatResetTime(epochMs)` (→ HH:MM) et `formatCountdown(remainingMs)` (→ « Xm Ys »/« Ys », clampé à 0 pour négatif). Teste ces helpers purs directement (cas limites : 0, négatif, < 60 s, > 60 s, minuit). Teste le rendu du badge (limité jusqu'à HH:MM vs « limité » sans heure vs « heure inconnue » pour suspected sans resetsAtMs) et que le bouton « Annuler la reprise » appelle l'action quand `resumeFireAt` est armé. 3. Adapter mock `src/adapters/mock/index.ts` — `MockInputGateway.cancelResume` (`cancelledResumes`, `cancelResumeResult`) : couvre via les tests du hook. Suis le style des tests existants (cf. `singletonAgent.test.tsx`, `mock.test.ts`, les tests agents). Exécute la suite (`npm test` ou l'outil du repo) et RENDS-MOI via idea_reply : la commande exacte lancée, le résultat réel (nb tests passés/échoués), et le rapport d'erreurs complet si rouge. Si un test révèle un bug d'implémentation, décris-le précisément (fichier:ligne, attendu vs obtenu) — je relaierai à DevFrontend pour correction. Ne commit rien.
|
- **Prompt:** Tâche QA — tests unitaires de la slice LS7-front (UI limites de session des agents), branche `feature/agent-session-limits`. DevFrontend vient d'implémenter (typecheck `tsc --noEmit` propre, 39 tests agents existants toujours verts, non commité). Périmètre à couvrir (frontend/) : 1. `src/features/agents/useAgents.ts` — nouvel état `limitByAgent: Record<string, AgentLimitState>` (`{ limitedUntil?, resumeFireAt?, suspected? }`) peuplé depuis 5 DOMAIN_EVENT dans l'abonnement `onDomainEvent`. À tester (via le mock gateway qui émet des events) : - `agentRateLimited` {agentId, resetsAtMs} → entrée `{ limitedUntil: resetsAtMs, suspected: false }`. - `agentResumeScheduled` {agentId, fireAtMs} → arme `resumeFireAt`. - `agentResumeCancelled` {agentId} → retire `resumeFireAt`, reste limité. - `agentResumed` {agentId} → supprime l'entrée (état effacé). - `agentRateLimitSuspected` {agentId, resetsAtMs?} → `{ ..., suspected: true }`, y compris le cas SANS `resetsAtMs` (heure inconnue). - séquence réaliste : rateLimited → resumeScheduled → cancelResume (action) → vérifier retrait optimiste + appel `input.cancelResume` (mock `cancelledResumes` / `cancelResumeResult`). - Action `cancelResume(agentId)` exposée par le hook : retrait optimiste + verdict backend renvoyé (teste les deux verdicts via `cancelResumeResult`). 2. `src/features/agents/AgentLimitBadge.tsx` — helpers purs exportés `formatResetTime(epochMs)` (→ HH:MM) et `formatCountdown(remainingMs)` (→ « Xm Ys »/« Ys », clampé à 0 pour négatif). Teste ces helpers purs directement (cas limites : 0, négatif, < 60 s, > 60 s, minuit). Teste le rendu du badge (limité jusqu'à HH:MM vs « limité » sans heure vs « heure inconnue » pour suspected sans resetsAtMs) et que le bouton « Annuler la reprise » appelle l'action quand `resumeFireAt` est armé. 3. Adapter mock `src/adapters/mock/index.ts` — `MockInputGateway.cancelResume` (`cancelledResumes`, `cancelResumeResult`) : couvre via les tests du hook. Suis le style des tests existants (cf. `singletonAgent.test.tsx`, `mock.test.ts`, les tests agents). Exécute la suite (`npm test` ou l'outil du repo) et RENDS-MOI via idea_reply : la commande exacte lancée, le résultat réel (nb tests passés/échoués), et le rapport d'erreurs complet si rouge. Si un test révèle un bug d'implémentation, décris-le précisément (fichier:ligne, attendu vs obtenu) — je relaierai à DevFrontend pour correction. Ne commit rien.
|
||||||
- **Response:** QA LS7-front — VERT. Aucun bug d'implémentation trouvé. Rien commité. ## Fichiers de test ajoutés (2) - `frontend/src/features/agents/useAgentsLimits.test.tsx` (13 tests) — état `limitByAgent` du hook via le `MockSystemGateway` qui émet les events + `MockInputGateway`. - `frontend/src/features/agents/AgentLimitBadge.test.tsx` (11 tests) — helpers purs + rendu du badge. ## Commandes exactes lancées (cwd = frontend/) 1. `npx vitest run src/features/agents/useAgentsLimits.test.tsx src/features/agents/AgentLimitBadge.test.tsx` → **Test Files 2 passed (2) · Tests 24 passed (24)** 2. `npx vitest run src/features/agents/` (suite agents complète, régression) → **Test Files 4 passed (4) · Tests 63 passed (63)** (39 existants + 24 nouveaux, 0 régression) ## Couverture livrée useAgents (§21), tous via events réels : - `agentRateLimited{resetsAtMs}` → `{limitedUntil, resumeFireAt:undefined, suspected:false}` ✓ - `agentResumeScheduled{fireAtMs}` → arme `resumeFireAt` par-dessus l'état limité ✓ - `agentResumeCancelled` → retire `resumeFireAt`, reste limité ✓ ; + no-op sur agent inconnu (aucune entrée créée) ✓ - `agentResumed` → entrée supprimée (`agentId in map === false`) ✓ - `agentRateLimitSuspected` AVEC et SANS `resetsAtMs` → `suspected:true`, `limitedUntil` undefined dans le cas sans heure ✓ - séquence réaliste rateLimited→scheduled→`cancelResume(action)` : retrait optimiste du countdown + agent toujours limité + `input.cancelledResumes === [agentId]` + verdict ✓ - action `cancelResume` : verdict `true` ET `false` (via `cancelResumeResult`), + appel du port même sans reprise armée ✓ - deux agents foldés indépendamment ✓ AgentLimitBadge : - `formatCountdown` : 0→"0s", négatif→"0s" (clamp), <60s→"Xs", ceil partiel (4.2s→"5s"), ≥60s→"Xm Ys" (60000→"1m 0s", 125000→"2m 5s") ✓ - `formatResetTime` : HH:MM sans secondes (même minute +30s = même libellé), minuit stable ✓ - rendu : "limité jusqu'à HH:MM" (heure connue) / "limité" (sans heure) / note "heure inconnue — reprise à préciser" (suspected sans heure) + absence de la note quand suspected A une heure ✓ - bouton "Annuler la reprise" : appelle `onCancelResume` au clic quand `resumeFireAt` armé, countdown rendu, bouton désactivé si `busy` ✓ MockInputGateway.cancelResume (`cancelledResumes`/`cancelResumeResult`) : couvert via les tests du hook. ## Note hors-périmètre (PRÉEXISTANTE, pas causée par LS7-front) `src/adapters/mock/mock.test.ts:16` échoue (1 test) : il assert « thirteen gateways » mais le `permission` gateway porte le compte à 14. Vérifié : la gateway `permission` existe déjà dans HEAD committé (eca2ba9) — l'assertion de comptage est simplement périmée, sans rapport avec les limites de session. Mes deux fichiers ne touchent pas ce test. Aucune action de ma part (hors scope) ; à relayer pour une mise à jour du compteur si tu veux la suite `adapters/mock` 100% verte.
|
- **Response:** QA LS7-front — VERT. Aucun bug d'implémentation trouvé. Rien commité. ## Fichiers de test ajoutés (2) - `frontend/src/features/agents/useAgentsLimits.test.tsx` (13 tests) — état `limitByAgent` du hook via le `MockSystemGateway` qui émet les events + `MockInputGateway`. - `frontend/src/features/agents/AgentLimitBadge.test.tsx` (11 tests) — helpers purs + rendu du badge. ## Commandes exactes lancées (cwd = frontend/) 1. `npx vitest run src/features/agents/useAgentsLimits.test.tsx src/features/agents/AgentLimitBadge.test.tsx` → **Test Files 2 passed (2) · Tests 24 passed (24)** 2. `npx vitest run src/features/agents/` (suite agents complète, régression) → **Test Files 4 passed (4) · Tests 63 passed (63)** (39 existants + 24 nouveaux, 0 régression) ## Couverture livrée useAgents (§21), tous via events réels : - `agentRateLimited{resetsAtMs}` → `{limitedUntil, resumeFireAt:undefined, suspected:false}` ✓ - `agentResumeScheduled{fireAtMs}` → arme `resumeFireAt` par-dessus l'état limité ✓ - `agentResumeCancelled` → retire `resumeFireAt`, reste limité ✓ ; + no-op sur agent inconnu (aucune entrée créée) ✓ - `agentResumed` → entrée supprimée (`agentId in map === false`) ✓ - `agentRateLimitSuspected` AVEC et SANS `resetsAtMs` → `suspected:true`, `limitedUntil` undefined dans le cas sans heure ✓ - séquence réaliste rateLimited→scheduled→`cancelResume(action)` : retrait optimiste du countdown + agent toujours limité + `input.cancelledResumes === [agentId]` + verdict ✓ - action `cancelResume` : verdict `true` ET `false` (via `cancelResumeResult`), + appel du port même sans reprise armée ✓ - deux agents foldés indépendamment ✓ AgentLimitBadge : - `formatCountdown` : 0→"0s", négatif→"0s" (clamp), <60s→"Xs", ceil partiel (4.2s→"5s"), ≥60s→"Xm Ys" (60000→"1m 0s", 125000→"2m 5s") ✓ - `formatResetTime` : HH:MM sans secondes (même minute +30s = même libellé), minuit stable ✓ - rendu : "limité jusqu'à HH:MM" (heure connue) / "limité" (sans heure) / note "heure inconnue — reprise à préciser" (suspected sans heure) + absence de la note quand suspected A une heure ✓ - bouton "Annuler la reprise" : appelle `onCancelResume` au clic quand `resumeFireAt` armé, countdown rendu, bouton désactivé si `busy` ✓ MockInputGateway.cancelResume (`cancelledResumes`/`cancelResumeResult`) : couvert via les tests du hook. ## Note hors-périmètre (PRÉEXISTANTE, pas causée par LS7-front) `src/adapters/mock/mock.test.ts:16` échoue (1 test) : il assert « thirteen gateways » mais le `permission` gateway porte le compte à 14. Vérifié : la gateway `permission` existe déjà dans HEAD committé (eca2ba9) — l'assertion de comptage est simplement périmée, sans rapport avec les limites de session. Mes deux fichiers ne touchent pas ce test. Aucune action de ma part (hors scope) ; à relayer pour une mise à jour du compteur si tu veux la suite `adapters/mock` 100% verte.
|
||||||
@ -24,3 +22,5 @@ objective: Tâche : ajouter UN test fonctionnel anti-régression de la communica
|
|||||||
- **Response:** Verdict QA final : vert accepté avec réserve environnementale. Je ne maintiens pas le KO global, car le rouge restant est maintenant expliqué et borné : dans cet environnement sandbox, un bind de socket Unix réel échoue `PermissionDenied` même pour un smoke minimal `UnixListener::bind` dans `/tmp` et `/run/user/1000`. Les tests qui exigent un vrai socket Unix loopback ne peuvent donc pas être probants ici. Le reste du périmètre applicatif, hotfix submit/logs, frontend, wiring hors vrai socket et app-tauri lib hors tests loopback réels est vert. Commandes vertes déjà validées QA sur ce périmètre : ```text cargo test -p application compose_convention_file --no-fail-fast ``` Résultat : `15 passed; 0 failed; 28 filtered out`. ```text cargo test -p application ``` Résultat : `43 passed; 0 failed` pour les unit tests application, tous les tests d’intégration application et doctests passés. Warning existant : `method writes is never used` dans `crates/application/tests/orchestrator_service.rs:1642`. ```text cargo test -p infrastructure input ``` Résultat : `35 passed; 0 failed; 187 filtered out`. ```text cargo test -p application --test orchestrator_service ``` Résultat : `45 passed; 0 failed` avec le même warning existant `writes`. ```text cd frontend && npx vitest run src/features/terminals/useWritePortal.test.tsx ``` Résultat : `1 passed`, `12 tests passed`. Warnings Vite existants sur options `esbuild` dépréciées / `oxc` prioritaire. ```text cd frontend && npx vitest run src/features/terminals/TerminalView.portal.test.tsx src/features/terminals/TerminalView.test.tsx src/features/terminals/useWritePortal.test.tsx ``` Résultat : `3 passed`, `24 tests passed`. Même warnings Vite. ```text cd frontend && npx tsc --noEmit ``` Résultat : succès, aucune sortie. ```text cargo check -p app-tauri ``` Résultat : succès, `Finished dev profile`. Commandes de confirmation après diagnostic sandbox, relancées par QA : ```text cargo test -p app-tauri --test orchestrator_wiring -- --skip open_binds_the_project_loopback_endpoint --skip double_open_keeps_a_single_endpoint_no_address_in_use --skip close_cleans_up_the_endpoint_socket_file --skip file_watcher_and_loopback_endpoint_live_together ``` Résultat réel : ```text running 9 tests ... test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 4 filtered out; finished in 0.01s ``` ```text cargo test -p app-tauri --lib -- --skip state::mcp_e2e_loopback_tests --skip state::bind_endpoint_d1_tests --skip mcp_bridge::tests::end_to_end_over_real_loopback ``` Résultat réel : ```text running 39 tests ... test result: ok. 39 passed; 0 failed; 0 ignored; 0 measured; 8 filtered out; finished in 0.01s ``` Réserve exacte : non-validé dans ce sandbox uniquement pour les tests nécessitant la création/binding d’un vrai socket Unix loopback : - `crates/app-tauri/tests/orchestrator_wiring.rs` : `open_binds_the_project_loopback_endpoint`, `double_open_keeps_a_single_endpoint_no_address_in_use`, `close_cleans_up_the_endpoint_socket_file`, `file_watcher_and_loopback_endpoint_live_together`. - `app-tauri --lib` : `state::mcp_e2e_loopback_tests`, `state::bind_endpoint_d1_tests`, `mcp_bridge::tests::end_to_end_over_real_loopback`. Ces tests restent à exécuter dans un environnement non sandboxé permettant `UnixListener::bind` sur socket filesystem. Je n’ai modifié aucun fichier.
|
- **Response:** Verdict QA final : vert accepté avec réserve environnementale. Je ne maintiens pas le KO global, car le rouge restant est maintenant expliqué et borné : dans cet environnement sandbox, un bind de socket Unix réel échoue `PermissionDenied` même pour un smoke minimal `UnixListener::bind` dans `/tmp` et `/run/user/1000`. Les tests qui exigent un vrai socket Unix loopback ne peuvent donc pas être probants ici. Le reste du périmètre applicatif, hotfix submit/logs, frontend, wiring hors vrai socket et app-tauri lib hors tests loopback réels est vert. Commandes vertes déjà validées QA sur ce périmètre : ```text cargo test -p application compose_convention_file --no-fail-fast ``` Résultat : `15 passed; 0 failed; 28 filtered out`. ```text cargo test -p application ``` Résultat : `43 passed; 0 failed` pour les unit tests application, tous les tests d’intégration application et doctests passés. Warning existant : `method writes is never used` dans `crates/application/tests/orchestrator_service.rs:1642`. ```text cargo test -p infrastructure input ``` Résultat : `35 passed; 0 failed; 187 filtered out`. ```text cargo test -p application --test orchestrator_service ``` Résultat : `45 passed; 0 failed` avec le même warning existant `writes`. ```text cd frontend && npx vitest run src/features/terminals/useWritePortal.test.tsx ``` Résultat : `1 passed`, `12 tests passed`. Warnings Vite existants sur options `esbuild` dépréciées / `oxc` prioritaire. ```text cd frontend && npx vitest run src/features/terminals/TerminalView.portal.test.tsx src/features/terminals/TerminalView.test.tsx src/features/terminals/useWritePortal.test.tsx ``` Résultat : `3 passed`, `24 tests passed`. Même warnings Vite. ```text cd frontend && npx tsc --noEmit ``` Résultat : succès, aucune sortie. ```text cargo check -p app-tauri ``` Résultat : succès, `Finished dev profile`. Commandes de confirmation après diagnostic sandbox, relancées par QA : ```text cargo test -p app-tauri --test orchestrator_wiring -- --skip open_binds_the_project_loopback_endpoint --skip double_open_keeps_a_single_endpoint_no_address_in_use --skip close_cleans_up_the_endpoint_socket_file --skip file_watcher_and_loopback_endpoint_live_together ``` Résultat réel : ```text running 9 tests ... test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 4 filtered out; finished in 0.01s ``` ```text cargo test -p app-tauri --lib -- --skip state::mcp_e2e_loopback_tests --skip state::bind_endpoint_d1_tests --skip mcp_bridge::tests::end_to_end_over_real_loopback ``` Résultat réel : ```text running 39 tests ... test result: ok. 39 passed; 0 failed; 0 ignored; 0 measured; 8 filtered out; finished in 0.01s ``` Réserve exacte : non-validé dans ce sandbox uniquement pour les tests nécessitant la création/binding d’un vrai socket Unix loopback : - `crates/app-tauri/tests/orchestrator_wiring.rs` : `open_binds_the_project_loopback_endpoint`, `double_open_keeps_a_single_endpoint_no_address_in_use`, `close_cleans_up_the_endpoint_socket_file`, `file_watcher_and_loopback_endpoint_live_together`. - `app-tauri --lib` : `state::mcp_e2e_loopback_tests`, `state::bind_endpoint_d1_tests`, `mcp_bridge::tests::end_to_end_over_real_loopback`. Ces tests restent à exécuter dans un environnement non sandboxé permettant `UnixListener::bind` sur socket filesystem. Je n’ai modifié aucun fichier.
|
||||||
- **Prompt:** QA globale pour le chantier `feature/conversation-ux-live-state` Lot A. Périmètre implémenté: backend/Tauri read-model `get_project_work_state(projectId)` + frontend onglet/panneau Work read-only. Backend a ajouté `crates/application/src/workstate/mod.rs`, snapshots live, commande/DTO Tauri; frontend a ajouté types/domain/port/adapter/mock/hook/panel et onglet Work. Merci de relire le périmètre: read-only, pas nouvelle persistance, pas lecture log/handoff, pas FIFO complète, ordre manifeste, live agents hors manifeste ignorés. Exécute les tests pertinents réels. Minimum: - `cargo test -p application workstate` - `cargo test -p application` - `cargo check -p app-tauri` - tests app-tauri DTO/live pertinents (`dto_agents`, `list_live_agents_r0b`) et si nécessaire app-tauri lib avec skips socket connus - `cd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx` - `cd frontend && npx tsc --noEmit` Retourne verdict, commandes exactes/résultats, risques, et si les tests socket Unix restent filtrés pour raison sandbox déjà diagnostiquée. Ne modifie pas le code sauf correction de test strictement nécessaire.
|
- **Prompt:** QA globale pour le chantier `feature/conversation-ux-live-state` Lot A. Périmètre implémenté: backend/Tauri read-model `get_project_work_state(projectId)` + frontend onglet/panneau Work read-only. Backend a ajouté `crates/application/src/workstate/mod.rs`, snapshots live, commande/DTO Tauri; frontend a ajouté types/domain/port/adapter/mock/hook/panel et onglet Work. Merci de relire le périmètre: read-only, pas nouvelle persistance, pas lecture log/handoff, pas FIFO complète, ordre manifeste, live agents hors manifeste ignorés. Exécute les tests pertinents réels. Minimum: - `cargo test -p application workstate` - `cargo test -p application` - `cargo check -p app-tauri` - tests app-tauri DTO/live pertinents (`dto_agents`, `list_live_agents_r0b`) et si nécessaire app-tauri lib avec skips socket connus - `cd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx` - `cd frontend && npx tsc --noEmit` Retourne verdict, commandes exactes/résultats, risques, et si les tests socket Unix restent filtrés pour raison sandbox déjà diagnostiquée. Ne modifie pas le code sauf correction de test strictement nécessaire.
|
||||||
- **Response:** Verdict QA globale Lot A `feature/conversation-ux-live-state` : vert, avec réserve environnementale non bloquante sur les tests socket Unix réels. Périmètre relu : - Backend application : `GetProjectWorkState` agrège en read-only depuis `AgentContextStore::load_manifest`, `LiveSessions::live_agent_snapshots()` et `InputMediator::busy_state`. - Pas de nouvelle persistance observée dans le read-model ; pas de lecture log/handoff ; pas de modélisation FIFO complète. - Ordre manifeste conservé par itération sur `manifest.entries`. - Live agents hors manifeste ignorés par construction, car seuls les agents du manifeste sont émis. - Snapshot live typé ajouté sans changer `live_agents()` existant. - Tauri : commande `get_project_work_state(projectId)` câblée dans `commands.rs`, `state.rs`, `lib.rs`, DTO camelCase avec `live.kind` et `busy.state/sinceMs`. - Frontend : onglet `Work`, port/gateway/adapters/mock/hook/panel ajoutés ; panneau read-only, appelle uniquement `getProjectWorkState`, affiche live/offline + busy/idle, refresh manuel et refresh sur événements existants. Commandes exécutées et résultats : ```text cargo fmt -p application -p app-tauri -- --check ``` Résultat : OK, aucune sortie. ```text cargo test -p application workstate ``` Résultat : ```text running 5 tests test workstate_attaches_live_pty_session_to_manifest_agent ... ok test workstate_includes_busy_state_from_input_mediator ... ok test workstate_attaches_live_structured_session_to_manifest_agent ... ok test workstate_ignores_live_agents_absent_from_manifest ... ok test workstate_lists_manifest_agents_idle_without_live_sessions ... ok test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ``` Warning existant observé : ```text warning: method `writes` is never used --> crates/application/tests/orchestrator_service.rs:1642:8 ``` ```text cargo test -p application ``` Résultat : ```text running 43 tests ... test result: ok. 43 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ... Running tests/workstate.rs running 5 tests ... test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out Doc-tests application running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ``` Même warning existant `CapturingFs::writes` unused. ```text cargo check -p app-tauri ``` Résultat : ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.70s ``` ```text cargo test -p app-tauri --test dto_agents ``` Résultat : ```text running 19 tests ... test project_work_state_dto_serialises_live_and_busy_camelcase ... ok ... test result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ``` ```text cargo test -p app-tauri --test list_live_agents_r0b ``` Résultat : ```text running 5 tests test no_sessions_yields_empty_list ... ok test both_kinds_live_listed_without_duplicates ... ok test pty_live_agent_is_listed ... ok test same_agent_in_both_registries_is_deduplicated ... ok test structured_live_agent_is_listed ... ok test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ``` ```text cargo test -p app-tauri --lib -- --skip state::mcp_e2e_loopback_tests --skip state::bind_endpoint_d1_tests --skip mcp_bridge::tests::end_to_end_over_real_loopback ``` Résultat : ```text running 39 tests ... test result: ok. 39 passed; 0 failed; 0 ignored; 0 measured; 8 filtered out; finished in 0.01s ``` Les 8 tests filtrés restent les tests nécessitant un vrai socket Unix loopback ; le sandbox courant refuse `UnixListener::bind` avec `PermissionDenied`, diagnostic déjà établi. À rejouer hors sandbox permissif socket Unix, non bloquant pour ce Lot A. ```text cd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx ``` Résultat : ```text Test Files 2 passed (2) Tests 13 passed (13) ``` Warnings Vite existants : options `esbuild` dépréciées / `oxc` prioritaire. ```text cd frontend && npx tsc --noEmit ``` Résultat : succès, aucune sortie. Risques restants : - Couverture volontairement limitée au read-model minimal live/busy ; pas de FIFO complète, pas d’historique conversationnel, pas de lecture log/handoff. - Tests socket Unix réels filtrés pour contrainte sandbox connue ; ils restent à exécuter dans un environnement qui autorise le bind de sockets filesystem. - Warning existant `CapturingFs::writes` unused dans `orchestrator_service.rs`, sans lien avec ce lot. Aucune correction requise. Je n’ai modifié aucun fichier.
|
- **Response:** Verdict QA globale Lot A `feature/conversation-ux-live-state` : vert, avec réserve environnementale non bloquante sur les tests socket Unix réels. Périmètre relu : - Backend application : `GetProjectWorkState` agrège en read-only depuis `AgentContextStore::load_manifest`, `LiveSessions::live_agent_snapshots()` et `InputMediator::busy_state`. - Pas de nouvelle persistance observée dans le read-model ; pas de lecture log/handoff ; pas de modélisation FIFO complète. - Ordre manifeste conservé par itération sur `manifest.entries`. - Live agents hors manifeste ignorés par construction, car seuls les agents du manifeste sont émis. - Snapshot live typé ajouté sans changer `live_agents()` existant. - Tauri : commande `get_project_work_state(projectId)` câblée dans `commands.rs`, `state.rs`, `lib.rs`, DTO camelCase avec `live.kind` et `busy.state/sinceMs`. - Frontend : onglet `Work`, port/gateway/adapters/mock/hook/panel ajoutés ; panneau read-only, appelle uniquement `getProjectWorkState`, affiche live/offline + busy/idle, refresh manuel et refresh sur événements existants. Commandes exécutées et résultats : ```text cargo fmt -p application -p app-tauri -- --check ``` Résultat : OK, aucune sortie. ```text cargo test -p application workstate ``` Résultat : ```text running 5 tests test workstate_attaches_live_pty_session_to_manifest_agent ... ok test workstate_includes_busy_state_from_input_mediator ... ok test workstate_attaches_live_structured_session_to_manifest_agent ... ok test workstate_ignores_live_agents_absent_from_manifest ... ok test workstate_lists_manifest_agents_idle_without_live_sessions ... ok test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ``` Warning existant observé : ```text warning: method `writes` is never used --> crates/application/tests/orchestrator_service.rs:1642:8 ``` ```text cargo test -p application ``` Résultat : ```text running 43 tests ... test result: ok. 43 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ... Running tests/workstate.rs running 5 tests ... test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out Doc-tests application running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ``` Même warning existant `CapturingFs::writes` unused. ```text cargo check -p app-tauri ``` Résultat : ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.70s ``` ```text cargo test -p app-tauri --test dto_agents ``` Résultat : ```text running 19 tests ... test project_work_state_dto_serialises_live_and_busy_camelcase ... ok ... test result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ``` ```text cargo test -p app-tauri --test list_live_agents_r0b ``` Résultat : ```text running 5 tests test no_sessions_yields_empty_list ... ok test both_kinds_live_listed_without_duplicates ... ok test pty_live_agent_is_listed ... ok test same_agent_in_both_registries_is_deduplicated ... ok test structured_live_agent_is_listed ... ok test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ``` ```text cargo test -p app-tauri --lib -- --skip state::mcp_e2e_loopback_tests --skip state::bind_endpoint_d1_tests --skip mcp_bridge::tests::end_to_end_over_real_loopback ``` Résultat : ```text running 39 tests ... test result: ok. 39 passed; 0 failed; 0 ignored; 0 measured; 8 filtered out; finished in 0.01s ``` Les 8 tests filtrés restent les tests nécessitant un vrai socket Unix loopback ; le sandbox courant refuse `UnixListener::bind` avec `PermissionDenied`, diagnostic déjà établi. À rejouer hors sandbox permissif socket Unix, non bloquant pour ce Lot A. ```text cd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx ``` Résultat : ```text Test Files 2 passed (2) Tests 13 passed (13) ``` Warnings Vite existants : options `esbuild` dépréciées / `oxc` prioritaire. ```text cd frontend && npx tsc --noEmit ``` Résultat : succès, aucune sortie. Risques restants : - Couverture volontairement limitée au read-model minimal live/busy ; pas de FIFO complète, pas d’historique conversationnel, pas de lecture log/handoff. - Tests socket Unix réels filtrés pour contrainte sandbox connue ; ils restent à exécuter dans un environnement qui autorise le bind de sockets filesystem. - Warning existant `CapturingFs::writes` unused dans `orchestrator_service.rs`, sans lien avec ce lot. Aucune correction requise. Je n’ai modifié aucun fichier.
|
||||||
|
- **Prompt:** Valide le Lot B complet `workstate delegation/queue snapshot` sur la branche active `feature/workstate-delegation-queue`. Périmètre à vérifier: - Backend/Tauri: port read-only `AgentQueueSnapshot`, snapshots FIFO d'`InMemoryMailbox`, `GetProjectWorkState.agents[].tickets`, DTO camelCase, wiring `AppState`. - Frontend: types `tickets`, mock normalisé, panneau Work affiche tickets FIFO inProgress/queued, refresh sur `delegationReady`. - Les tickets human et agent sont inclus; l'UI ne doit pas les présenter tous comme délégations agent. Commandes minimales demandées (adapte si le repo impose un cwd): - `cargo fmt --all -- --check` - `cargo test -p infrastructure mailbox --lib` - `cargo test -p application --test workstate` - `cargo test -p app-tauri --test dto_agents` - `cargo check -p app-tauri` - `cd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx` - `cd frontend && npx tsc --noEmit` Tu peux ajouter des commandes pertinentes, mais ne committe pas et ne modifie pas les fichiers applicatifs sauf tests QA indispensables. Ignore le dirty runtime `.ideai/*` s'il est seulement live-session. Retour attendu: verdict clair, commandes exécutées avec résultats réels, anomalies éventuelles avec fichiers/lignes, et si rouge, donne la sortie d'erreur exploitable.
|
||||||
|
- **Response:** Verdict: VERT pour le Lot B `workstate delegation/queue snapshot` sur `feature/workstate-delegation-queue`. Je n’ai modifié aucun fichier. Dirty ignoré côté runtime `.ideai/*`; les fichiers applicatifs/test du lot étaient déjà modifiés dans le worktree. Commandes exécutées et résultats réels: 1. `cargo fmt --all -- --check` - Exit 0, aucune sortie. 2. `cargo test -p infrastructure mailbox --lib` - Exit 0. - `running 13 tests` - `test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 215 filtered out; finished in 0.00s` - Couvre notamment `snapshot_preserves_fifo_order_and_positions`, `snapshot_carries_ticket_metadata`, `snapshot_is_read_only`, updates après cancel/resolve. 3. `cargo test -p application --test workstate` - Exit 0. - `running 12 tests` - `test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s` - Couvre `workstate_lists_two_tickets_in_fifo_order`, `workstate_marks_busy_head_in_progress_and_rest_queued`, `workstate_marks_all_queued_when_agent_idle`, `workstate_maps_human_and_agent_ticket_sources`, queue absente/agent hors manifest. 4. `cargo test -p app-tauri --test dto_agents` - Exit 0. - `running 20 tests` - `test result: ok. 20 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s` - Couvre `project_work_state_dto_serialises_tickets_camelcase`: `tickets`, `inProgress`, source `agent.agentId`, source `human`, absence de snake_case. 5. `cargo check -p app-tauri` - Exit 0. - `Finished dev profile [unoptimized + debuginfo] target(s) in 0.59s` 6. `cd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx` - Exit 0. - Warnings Vite non bloquants: options `esbuild` dépréciées/ignorées au profit de `oxc`. - `Test Files 2 passed (2)` - `Tests 17 passed (17)` - `Duration 2.02s` 7. `cd frontend && npx tsc --noEmit` - Exit 0, aucune sortie. 8. Commande QA ajoutée, car `crates/domain/src/mailbox.rs` est touché par le lot: `cargo test -p domain mailbox --lib` - Exit 0. - `running 6 tests` - `test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 170 filtered out; finished in 0.00s` Vérifications de périmètre: - Backend/Tauri: - Port read-only présent: `domain::mailbox::AgentQueueSnapshot` dans `crates/domain/src/mailbox.rs:69`. - `InMemoryMailbox` implémente la snapshot read-only dans `crates/infrastructure/src/mailbox/mod.rs:166`; tests FIFO/métadonnées/read-only verts. - `GetProjectWorkState` consomme `Arc<dyn AgentQueueSnapshot>` dans `crates/application/src/workstate/mod.rs:130` et mappe `agents[].tickets`; tests application verts. - DTO `tickets` camelCase dans `crates/app-tauri/src/dto.rs:1602` et test camelCase dédié vert. - Wiring `AppState`: même `InMemoryMailbox` exposé comme `AgentMailbox` et `AgentQueueSnapshot` dans `crates/app-tauri/src/state.rs:994-999`, puis injecté dans `GetProjectWorkState::new` vers `state.rs:1034-1038`. - Frontend: - Types `tickets` présents dans `frontend/src/domain/index.ts:153-179`. - Mock normalisé avec fallback `tickets: []` dans `frontend/src/adapters/mock/index.ts:1670-1684`. - Panneau Work trie les tickets par `position` avant affichage dans `frontend/src/features/workstate/ProjectWorkStatePanel.tsx:65`. - UI distingue source humaine et agent: `Human` pour `source.kind === "human"`, sinon `label (agent-id-prefix)` dans `ProjectWorkStatePanel.tsx:21-27`; test `renders an in-progress human ticket` et test FIFO agent verts. - Refresh sur `delegationReady` couvert par `frontend/src/features/workstate/workstate.test.tsx:223` et vert. Anomalies: aucune anomalie bloquante détectée. Les seuls messages observés sont les warnings Vite sur la config `esbuild` dépréciée, sans échec de test ni impact Lot B constaté.
|
||||||
@ -62,3 +62,5 @@
|
|||||||
{"id":"aab45df1-f0ac-4eca-850b-c89b688c3211","conversation":"08336578-5b47-09d2-2f41-5cc483f101f4","atMs":1781945801522,"source":{"kind":"agent","agentId":"aefdbd61-e3d4-4bc1-9f42-c259446a97b5"},"role":"response","text":"Verdict QA final : vert accepté avec réserve environnementale.\n\nJe ne maintiens pas le KO global, car le rouge restant est maintenant expliqué et borné : dans cet environnement sandbox, un bind de socket Unix réel échoue `PermissionDenied` même pour un smoke minimal `UnixListener::bind` dans `/tmp` et `/run/user/1000`. Les tests qui exigent un vrai socket Unix loopback ne peuvent donc pas être probants ici. Le reste du périmètre applicatif, hotfix submit/logs, frontend, wiring hors vrai socket et app-tauri lib hors tests loopback réels est vert.\n\nCommandes vertes déjà validées QA sur ce périmètre :\n\n```text\ncargo test -p application compose_convention_file --no-fail-fast\n```\nRésultat : `15 passed; 0 failed; 28 filtered out`.\n\n```text\ncargo test -p application\n```\nRésultat : `43 passed; 0 failed` pour les unit tests application, tous les tests d’intégration application et doctests passés. Warning existant : `method writes is never used` dans `crates/application/tests/orchestrator_service.rs:1642`.\n\n```text\ncargo test -p infrastructure input\n```\nRésultat : `35 passed; 0 failed; 187 filtered out`.\n\n```text\ncargo test -p application --test orchestrator_service\n```\nRésultat : `45 passed; 0 failed` avec le même warning existant `writes`.\n\n```text\ncd frontend && npx vitest run src/features/terminals/useWritePortal.test.tsx\n```\nRésultat : `1 passed`, `12 tests passed`. Warnings Vite existants sur options `esbuild` dépréciées / `oxc` prioritaire.\n\n```text\ncd frontend && npx vitest run src/features/terminals/TerminalView.portal.test.tsx src/features/terminals/TerminalView.test.tsx src/features/terminals/useWritePortal.test.tsx\n```\nRésultat : `3 passed`, `24 tests passed`. Même warnings Vite.\n\n```text\ncd frontend && npx tsc --noEmit\n```\nRésultat : succès, aucune sortie.\n\n```text\ncargo check -p app-tauri\n```\nRésultat : succès, `Finished dev profile`.\n\nCommandes de confirmation après diagnostic sandbox, relancées par QA :\n\n```text\ncargo test -p app-tauri --test orchestrator_wiring -- --skip open_binds_the_project_loopback_endpoint --skip double_open_keeps_a_single_endpoint_no_address_in_use --skip close_cleans_up_the_endpoint_socket_file --skip file_watcher_and_loopback_endpoint_live_together\n```\nRésultat réel :\n```text\nrunning 9 tests\n...\ntest result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 4 filtered out; finished in 0.01s\n```\n\n```text\ncargo test -p app-tauri --lib -- --skip state::mcp_e2e_loopback_tests --skip state::bind_endpoint_d1_tests --skip mcp_bridge::tests::end_to_end_over_real_loopback\n```\nRésultat réel :\n```text\nrunning 39 tests\n...\ntest result: ok. 39 passed; 0 failed; 0 ignored; 0 measured; 8 filtered out; finished in 0.01s\n```\n\nRéserve exacte : non-validé dans ce sandbox uniquement pour les tests nécessitant la création/binding d’un vrai socket Unix loopback :\n- `crates/app-tauri/tests/orchestrator_wiring.rs` : `open_binds_the_project_loopback_endpoint`, `double_open_keeps_a_single_endpoint_no_address_in_use`, `close_cleans_up_the_endpoint_socket_file`, `file_watcher_and_loopback_endpoint_live_together`.\n- `app-tauri --lib` : `state::mcp_e2e_loopback_tests`, `state::bind_endpoint_d1_tests`, `mcp_bridge::tests::end_to_end_over_real_loopback`.\n\nCes tests restent à exécuter dans un environnement non sandboxé permettant `UnixListener::bind` sur socket filesystem. Je n’ai modifié aucun fichier."}
|
{"id":"aab45df1-f0ac-4eca-850b-c89b688c3211","conversation":"08336578-5b47-09d2-2f41-5cc483f101f4","atMs":1781945801522,"source":{"kind":"agent","agentId":"aefdbd61-e3d4-4bc1-9f42-c259446a97b5"},"role":"response","text":"Verdict QA final : vert accepté avec réserve environnementale.\n\nJe ne maintiens pas le KO global, car le rouge restant est maintenant expliqué et borné : dans cet environnement sandbox, un bind de socket Unix réel échoue `PermissionDenied` même pour un smoke minimal `UnixListener::bind` dans `/tmp` et `/run/user/1000`. Les tests qui exigent un vrai socket Unix loopback ne peuvent donc pas être probants ici. Le reste du périmètre applicatif, hotfix submit/logs, frontend, wiring hors vrai socket et app-tauri lib hors tests loopback réels est vert.\n\nCommandes vertes déjà validées QA sur ce périmètre :\n\n```text\ncargo test -p application compose_convention_file --no-fail-fast\n```\nRésultat : `15 passed; 0 failed; 28 filtered out`.\n\n```text\ncargo test -p application\n```\nRésultat : `43 passed; 0 failed` pour les unit tests application, tous les tests d’intégration application et doctests passés. Warning existant : `method writes is never used` dans `crates/application/tests/orchestrator_service.rs:1642`.\n\n```text\ncargo test -p infrastructure input\n```\nRésultat : `35 passed; 0 failed; 187 filtered out`.\n\n```text\ncargo test -p application --test orchestrator_service\n```\nRésultat : `45 passed; 0 failed` avec le même warning existant `writes`.\n\n```text\ncd frontend && npx vitest run src/features/terminals/useWritePortal.test.tsx\n```\nRésultat : `1 passed`, `12 tests passed`. Warnings Vite existants sur options `esbuild` dépréciées / `oxc` prioritaire.\n\n```text\ncd frontend && npx vitest run src/features/terminals/TerminalView.portal.test.tsx src/features/terminals/TerminalView.test.tsx src/features/terminals/useWritePortal.test.tsx\n```\nRésultat : `3 passed`, `24 tests passed`. Même warnings Vite.\n\n```text\ncd frontend && npx tsc --noEmit\n```\nRésultat : succès, aucune sortie.\n\n```text\ncargo check -p app-tauri\n```\nRésultat : succès, `Finished dev profile`.\n\nCommandes de confirmation après diagnostic sandbox, relancées par QA :\n\n```text\ncargo test -p app-tauri --test orchestrator_wiring -- --skip open_binds_the_project_loopback_endpoint --skip double_open_keeps_a_single_endpoint_no_address_in_use --skip close_cleans_up_the_endpoint_socket_file --skip file_watcher_and_loopback_endpoint_live_together\n```\nRésultat réel :\n```text\nrunning 9 tests\n...\ntest result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 4 filtered out; finished in 0.01s\n```\n\n```text\ncargo test -p app-tauri --lib -- --skip state::mcp_e2e_loopback_tests --skip state::bind_endpoint_d1_tests --skip mcp_bridge::tests::end_to_end_over_real_loopback\n```\nRésultat réel :\n```text\nrunning 39 tests\n...\ntest result: ok. 39 passed; 0 failed; 0 ignored; 0 measured; 8 filtered out; finished in 0.01s\n```\n\nRéserve exacte : non-validé dans ce sandbox uniquement pour les tests nécessitant la création/binding d’un vrai socket Unix loopback :\n- `crates/app-tauri/tests/orchestrator_wiring.rs` : `open_binds_the_project_loopback_endpoint`, `double_open_keeps_a_single_endpoint_no_address_in_use`, `close_cleans_up_the_endpoint_socket_file`, `file_watcher_and_loopback_endpoint_live_together`.\n- `app-tauri --lib` : `state::mcp_e2e_loopback_tests`, `state::bind_endpoint_d1_tests`, `mcp_bridge::tests::end_to_end_over_real_loopback`.\n\nCes tests restent à exécuter dans un environnement non sandboxé permettant `UnixListener::bind` sur socket filesystem. Je n’ai modifié aucun fichier."}
|
||||||
{"id":"ea85a3be-0cf0-4ad7-b8a7-7344426844b7","conversation":"08336578-5b47-09d2-2f41-5cc483f101f4","atMs":1781971470719,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"QA globale pour le chantier `feature/conversation-ux-live-state` Lot A. Périmètre implémenté: backend/Tauri read-model `get_project_work_state(projectId)` + frontend onglet/panneau Work read-only. Backend a ajouté `crates/application/src/workstate/mod.rs`, snapshots live, commande/DTO Tauri; frontend a ajouté types/domain/port/adapter/mock/hook/panel et onglet Work.\n\nMerci de relire le périmètre: read-only, pas nouvelle persistance, pas lecture log/handoff, pas FIFO complète, ordre manifeste, live agents hors manifeste ignorés. Exécute les tests pertinents réels. Minimum:\n- `cargo test -p application workstate`\n- `cargo test -p application`\n- `cargo check -p app-tauri`\n- tests app-tauri DTO/live pertinents (`dto_agents`, `list_live_agents_r0b`) et si nécessaire app-tauri lib avec skips socket connus\n- `cd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx`\n- `cd frontend && npx tsc --noEmit`\nRetourne verdict, commandes exactes/résultats, risques, et si les tests socket Unix restent filtrés pour raison sandbox déjà diagnostiquée. Ne modifie pas le code sauf correction de test strictement nécessaire."}
|
{"id":"ea85a3be-0cf0-4ad7-b8a7-7344426844b7","conversation":"08336578-5b47-09d2-2f41-5cc483f101f4","atMs":1781971470719,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"QA globale pour le chantier `feature/conversation-ux-live-state` Lot A. Périmètre implémenté: backend/Tauri read-model `get_project_work_state(projectId)` + frontend onglet/panneau Work read-only. Backend a ajouté `crates/application/src/workstate/mod.rs`, snapshots live, commande/DTO Tauri; frontend a ajouté types/domain/port/adapter/mock/hook/panel et onglet Work.\n\nMerci de relire le périmètre: read-only, pas nouvelle persistance, pas lecture log/handoff, pas FIFO complète, ordre manifeste, live agents hors manifeste ignorés. Exécute les tests pertinents réels. Minimum:\n- `cargo test -p application workstate`\n- `cargo test -p application`\n- `cargo check -p app-tauri`\n- tests app-tauri DTO/live pertinents (`dto_agents`, `list_live_agents_r0b`) et si nécessaire app-tauri lib avec skips socket connus\n- `cd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx`\n- `cd frontend && npx tsc --noEmit`\nRetourne verdict, commandes exactes/résultats, risques, et si les tests socket Unix restent filtrés pour raison sandbox déjà diagnostiquée. Ne modifie pas le code sauf correction de test strictement nécessaire."}
|
||||||
{"id":"39f05602-a660-4632-accc-42d4887ce1ed","conversation":"08336578-5b47-09d2-2f41-5cc483f101f4","atMs":1781971551021,"source":{"kind":"agent","agentId":"aefdbd61-e3d4-4bc1-9f42-c259446a97b5"},"role":"response","text":"Verdict QA globale Lot A `feature/conversation-ux-live-state` : vert, avec réserve environnementale non bloquante sur les tests socket Unix réels.\n\nPérimètre relu :\n- Backend application : `GetProjectWorkState` agrège en read-only depuis `AgentContextStore::load_manifest`, `LiveSessions::live_agent_snapshots()` et `InputMediator::busy_state`.\n- Pas de nouvelle persistance observée dans le read-model ; pas de lecture log/handoff ; pas de modélisation FIFO complète.\n- Ordre manifeste conservé par itération sur `manifest.entries`.\n- Live agents hors manifeste ignorés par construction, car seuls les agents du manifeste sont émis.\n- Snapshot live typé ajouté sans changer `live_agents()` existant.\n- Tauri : commande `get_project_work_state(projectId)` câblée dans `commands.rs`, `state.rs`, `lib.rs`, DTO camelCase avec `live.kind` et `busy.state/sinceMs`.\n- Frontend : onglet `Work`, port/gateway/adapters/mock/hook/panel ajoutés ; panneau read-only, appelle uniquement `getProjectWorkState`, affiche live/offline + busy/idle, refresh manuel et refresh sur événements existants.\n\nCommandes exécutées et résultats :\n\n```text\ncargo fmt -p application -p app-tauri -- --check\n```\nRésultat : OK, aucune sortie.\n\n```text\ncargo test -p application workstate\n```\nRésultat :\n```text\nrunning 5 tests\ntest workstate_attaches_live_pty_session_to_manifest_agent ... ok\ntest workstate_includes_busy_state_from_input_mediator ... ok\ntest workstate_attaches_live_structured_session_to_manifest_agent ... ok\ntest workstate_ignores_live_agents_absent_from_manifest ... ok\ntest workstate_lists_manifest_agents_idle_without_live_sessions ... ok\n\ntest result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out\n```\nWarning existant observé :\n```text\nwarning: method `writes` is never used\n --> crates/application/tests/orchestrator_service.rs:1642:8\n```\n\n```text\ncargo test -p application\n```\nRésultat :\n```text\nrunning 43 tests\n...\ntest result: ok. 43 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out\n...\nRunning tests/workstate.rs\nrunning 5 tests\n...\ntest result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out\n\nDoc-tests application\nrunning 0 tests\n\ntest result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out\n```\nMême warning existant `CapturingFs::writes` unused.\n\n```text\ncargo check -p app-tauri\n```\nRésultat :\n```text\nFinished `dev` profile [unoptimized + debuginfo] target(s) in 1.70s\n```\n\n```text\ncargo test -p app-tauri --test dto_agents\n```\nRésultat :\n```text\nrunning 19 tests\n...\ntest project_work_state_dto_serialises_live_and_busy_camelcase ... ok\n...\ntest result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out\n```\n\n```text\ncargo test -p app-tauri --test list_live_agents_r0b\n```\nRésultat :\n```text\nrunning 5 tests\ntest no_sessions_yields_empty_list ... ok\ntest both_kinds_live_listed_without_duplicates ... ok\ntest pty_live_agent_is_listed ... ok\ntest same_agent_in_both_registries_is_deduplicated ... ok\ntest structured_live_agent_is_listed ... ok\n\ntest result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out\n```\n\n```text\ncargo test -p app-tauri --lib -- --skip state::mcp_e2e_loopback_tests --skip state::bind_endpoint_d1_tests --skip mcp_bridge::tests::end_to_end_over_real_loopback\n```\nRésultat :\n```text\nrunning 39 tests\n...\ntest result: ok. 39 passed; 0 failed; 0 ignored; 0 measured; 8 filtered out; finished in 0.01s\n```\nLes 8 tests filtrés restent les tests nécessitant un vrai socket Unix loopback ; le sandbox courant refuse `UnixListener::bind` avec `PermissionDenied`, diagnostic déjà établi. À rejouer hors sandbox permissif socket Unix, non bloquant pour ce Lot A.\n\n```text\ncd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx\n```\nRésultat :\n```text\nTest Files 2 passed (2)\nTests 13 passed (13)\n```\nWarnings Vite existants : options `esbuild` dépréciées / `oxc` prioritaire.\n\n```text\ncd frontend && npx tsc --noEmit\n```\nRésultat : succès, aucune sortie.\n\nRisques restants :\n- Couverture volontairement limitée au read-model minimal live/busy ; pas de FIFO complète, pas d’historique conversationnel, pas de lecture log/handoff.\n- Tests socket Unix réels filtrés pour contrainte sandbox connue ; ils restent à exécuter dans un environnement qui autorise le bind de sockets filesystem.\n- Warning existant `CapturingFs::writes` unused dans `orchestrator_service.rs`, sans lien avec ce lot.\n\nAucune correction requise. Je n’ai modifié aucun fichier."}
|
{"id":"39f05602-a660-4632-accc-42d4887ce1ed","conversation":"08336578-5b47-09d2-2f41-5cc483f101f4","atMs":1781971551021,"source":{"kind":"agent","agentId":"aefdbd61-e3d4-4bc1-9f42-c259446a97b5"},"role":"response","text":"Verdict QA globale Lot A `feature/conversation-ux-live-state` : vert, avec réserve environnementale non bloquante sur les tests socket Unix réels.\n\nPérimètre relu :\n- Backend application : `GetProjectWorkState` agrège en read-only depuis `AgentContextStore::load_manifest`, `LiveSessions::live_agent_snapshots()` et `InputMediator::busy_state`.\n- Pas de nouvelle persistance observée dans le read-model ; pas de lecture log/handoff ; pas de modélisation FIFO complète.\n- Ordre manifeste conservé par itération sur `manifest.entries`.\n- Live agents hors manifeste ignorés par construction, car seuls les agents du manifeste sont émis.\n- Snapshot live typé ajouté sans changer `live_agents()` existant.\n- Tauri : commande `get_project_work_state(projectId)` câblée dans `commands.rs`, `state.rs`, `lib.rs`, DTO camelCase avec `live.kind` et `busy.state/sinceMs`.\n- Frontend : onglet `Work`, port/gateway/adapters/mock/hook/panel ajoutés ; panneau read-only, appelle uniquement `getProjectWorkState`, affiche live/offline + busy/idle, refresh manuel et refresh sur événements existants.\n\nCommandes exécutées et résultats :\n\n```text\ncargo fmt -p application -p app-tauri -- --check\n```\nRésultat : OK, aucune sortie.\n\n```text\ncargo test -p application workstate\n```\nRésultat :\n```text\nrunning 5 tests\ntest workstate_attaches_live_pty_session_to_manifest_agent ... ok\ntest workstate_includes_busy_state_from_input_mediator ... ok\ntest workstate_attaches_live_structured_session_to_manifest_agent ... ok\ntest workstate_ignores_live_agents_absent_from_manifest ... ok\ntest workstate_lists_manifest_agents_idle_without_live_sessions ... ok\n\ntest result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out\n```\nWarning existant observé :\n```text\nwarning: method `writes` is never used\n --> crates/application/tests/orchestrator_service.rs:1642:8\n```\n\n```text\ncargo test -p application\n```\nRésultat :\n```text\nrunning 43 tests\n...\ntest result: ok. 43 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out\n...\nRunning tests/workstate.rs\nrunning 5 tests\n...\ntest result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out\n\nDoc-tests application\nrunning 0 tests\n\ntest result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out\n```\nMême warning existant `CapturingFs::writes` unused.\n\n```text\ncargo check -p app-tauri\n```\nRésultat :\n```text\nFinished `dev` profile [unoptimized + debuginfo] target(s) in 1.70s\n```\n\n```text\ncargo test -p app-tauri --test dto_agents\n```\nRésultat :\n```text\nrunning 19 tests\n...\ntest project_work_state_dto_serialises_live_and_busy_camelcase ... ok\n...\ntest result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out\n```\n\n```text\ncargo test -p app-tauri --test list_live_agents_r0b\n```\nRésultat :\n```text\nrunning 5 tests\ntest no_sessions_yields_empty_list ... ok\ntest both_kinds_live_listed_without_duplicates ... ok\ntest pty_live_agent_is_listed ... ok\ntest same_agent_in_both_registries_is_deduplicated ... ok\ntest structured_live_agent_is_listed ... ok\n\ntest result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out\n```\n\n```text\ncargo test -p app-tauri --lib -- --skip state::mcp_e2e_loopback_tests --skip state::bind_endpoint_d1_tests --skip mcp_bridge::tests::end_to_end_over_real_loopback\n```\nRésultat :\n```text\nrunning 39 tests\n...\ntest result: ok. 39 passed; 0 failed; 0 ignored; 0 measured; 8 filtered out; finished in 0.01s\n```\nLes 8 tests filtrés restent les tests nécessitant un vrai socket Unix loopback ; le sandbox courant refuse `UnixListener::bind` avec `PermissionDenied`, diagnostic déjà établi. À rejouer hors sandbox permissif socket Unix, non bloquant pour ce Lot A.\n\n```text\ncd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx\n```\nRésultat :\n```text\nTest Files 2 passed (2)\nTests 13 passed (13)\n```\nWarnings Vite existants : options `esbuild` dépréciées / `oxc` prioritaire.\n\n```text\ncd frontend && npx tsc --noEmit\n```\nRésultat : succès, aucune sortie.\n\nRisques restants :\n- Couverture volontairement limitée au read-model minimal live/busy ; pas de FIFO complète, pas d’historique conversationnel, pas de lecture log/handoff.\n- Tests socket Unix réels filtrés pour contrainte sandbox connue ; ils restent à exécuter dans un environnement qui autorise le bind de sockets filesystem.\n- Warning existant `CapturingFs::writes` unused dans `orchestrator_service.rs`, sans lien avec ce lot.\n\nAucune correction requise. Je n’ai modifié aucun fichier."}
|
||||||
|
{"id":"81d3494b-a647-40ea-909d-7b4649094e81","conversation":"08336578-5b47-09d2-2f41-5cc483f101f4","atMs":1781974100258,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Valide le Lot B complet `workstate delegation/queue snapshot` sur la branche active `feature/workstate-delegation-queue`.\n\nPérimètre à vérifier:\n- Backend/Tauri: port read-only `AgentQueueSnapshot`, snapshots FIFO d'`InMemoryMailbox`, `GetProjectWorkState.agents[].tickets`, DTO camelCase, wiring `AppState`.\n- Frontend: types `tickets`, mock normalisé, panneau Work affiche tickets FIFO inProgress/queued, refresh sur `delegationReady`.\n- Les tickets human et agent sont inclus; l'UI ne doit pas les présenter tous comme délégations agent.\n\nCommandes minimales demandées (adapte si le repo impose un cwd):\n- `cargo fmt --all -- --check`\n- `cargo test -p infrastructure mailbox --lib`\n- `cargo test -p application --test workstate`\n- `cargo test -p app-tauri --test dto_agents`\n- `cargo check -p app-tauri`\n- `cd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx`\n- `cd frontend && npx tsc --noEmit`\n\nTu peux ajouter des commandes pertinentes, mais ne committe pas et ne modifie pas les fichiers applicatifs sauf tests QA indispensables. Ignore le dirty runtime `.ideai/*` s'il est seulement live-session.\n\nRetour attendu: verdict clair, commandes exécutées avec résultats réels, anomalies éventuelles avec fichiers/lignes, et si rouge, donne la sortie d'erreur exploitable."}
|
||||||
|
{"id":"b916af4a-6f94-4777-92b1-c5f89f898cde","conversation":"08336578-5b47-09d2-2f41-5cc483f101f4","atMs":1781974271224,"source":{"kind":"agent","agentId":"aefdbd61-e3d4-4bc1-9f42-c259446a97b5"},"role":"response","text":"Verdict: VERT pour le Lot B `workstate delegation/queue snapshot` sur `feature/workstate-delegation-queue`.\n\nJe n’ai modifié aucun fichier. Dirty ignoré côté runtime `.ideai/*`; les fichiers applicatifs/test du lot étaient déjà modifiés dans le worktree.\n\nCommandes exécutées et résultats réels:\n\n1. `cargo fmt --all -- --check`\n - Exit 0, aucune sortie.\n\n2. `cargo test -p infrastructure mailbox --lib`\n - Exit 0.\n - `running 13 tests`\n - `test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 215 filtered out; finished in 0.00s`\n - Couvre notamment `snapshot_preserves_fifo_order_and_positions`, `snapshot_carries_ticket_metadata`, `snapshot_is_read_only`, updates après cancel/resolve.\n\n3. `cargo test -p application --test workstate`\n - Exit 0.\n - `running 12 tests`\n - `test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s`\n - Couvre `workstate_lists_two_tickets_in_fifo_order`, `workstate_marks_busy_head_in_progress_and_rest_queued`, `workstate_marks_all_queued_when_agent_idle`, `workstate_maps_human_and_agent_ticket_sources`, queue absente/agent hors manifest.\n\n4. `cargo test -p app-tauri --test dto_agents`\n - Exit 0.\n - `running 20 tests`\n - `test result: ok. 20 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s`\n - Couvre `project_work_state_dto_serialises_tickets_camelcase`: `tickets`, `inProgress`, source `agent.agentId`, source `human`, absence de snake_case.\n\n5. `cargo check -p app-tauri`\n - Exit 0.\n - `Finished dev profile [unoptimized + debuginfo] target(s) in 0.59s`\n\n6. `cd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx`\n - Exit 0.\n - Warnings Vite non bloquants: options `esbuild` dépréciées/ignorées au profit de `oxc`.\n - `Test Files 2 passed (2)`\n - `Tests 17 passed (17)`\n - `Duration 2.02s`\n\n7. `cd frontend && npx tsc --noEmit`\n - Exit 0, aucune sortie.\n\n8. Commande QA ajoutée, car `crates/domain/src/mailbox.rs` est touché par le lot: `cargo test -p domain mailbox --lib`\n - Exit 0.\n - `running 6 tests`\n - `test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 170 filtered out; finished in 0.00s`\n\nVérifications de périmètre:\n\n- Backend/Tauri:\n - Port read-only présent: `domain::mailbox::AgentQueueSnapshot` dans `crates/domain/src/mailbox.rs:69`.\n - `InMemoryMailbox` implémente la snapshot read-only dans `crates/infrastructure/src/mailbox/mod.rs:166`; tests FIFO/métadonnées/read-only verts.\n - `GetProjectWorkState` consomme `Arc<dyn AgentQueueSnapshot>` dans `crates/application/src/workstate/mod.rs:130` et mappe `agents[].tickets`; tests application verts.\n - DTO `tickets` camelCase dans `crates/app-tauri/src/dto.rs:1602` et test camelCase dédié vert.\n - Wiring `AppState`: même `InMemoryMailbox` exposé comme `AgentMailbox` et `AgentQueueSnapshot` dans `crates/app-tauri/src/state.rs:994-999`, puis injecté dans `GetProjectWorkState::new` vers `state.rs:1034-1038`.\n\n- Frontend:\n - Types `tickets` présents dans `frontend/src/domain/index.ts:153-179`.\n - Mock normalisé avec fallback `tickets: []` dans `frontend/src/adapters/mock/index.ts:1670-1684`.\n - Panneau Work trie les tickets par `position` avant affichage dans `frontend/src/features/workstate/ProjectWorkStatePanel.tsx:65`.\n - UI distingue source humaine et agent: `Human` pour `source.kind === \"human\"`, sinon `label (agent-id-prefix)` dans `ProjectWorkStatePanel.tsx:21-27`; test `renders an in-progress human ticket` et test FIFO agent verts.\n - Refresh sur `delegationReady` couvert par `frontend/src/features/workstate/workstate.test.tsx:223` et vert.\n\nAnomalies: aucune anomalie bloquante détectée. Les seuls messages observés sont les warnings Vite sur la config `esbuild` dépréciée, sans échec de test ni impact Lot B constaté."}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
upTo: a0b9d44f-2761-4652-b2f3-28222dbc4c62
|
upTo: 7d70c87a-e7b5-468a-90c7-41decf1185bc
|
||||||
objective: [Ping inter-agent depuis Main] Test du pont MCP inter-agents. Si tu reçois ce message, réponds via idea_reply avec : (1) "DevFrontend OK — pont inter-agent fonctionnel", (2) le résultat de 144 ÷ 12, e
|
objective: [Ping inter-agent depuis Main] Test du pont MCP inter-agents. Si tu reçois ce message, réponds via idea_reply avec : (1) "DevFrontend OK — pont inter-agent fonctionnel", (2) le résultat de 144 ÷ 12, e
|
||||||
---
|
---
|
||||||
**Objectif :** [Ping inter-agent depuis Main] Test du pont MCP inter-agents. Si tu reçois ce message, réponds via idea_reply avec : (1) "DevFrontend OK — pont inter-agent fonctionnel", (2) le résultat de 144 ÷ 12, e
|
**Objectif :** [Ping inter-agent depuis Main] Test du pont MCP inter-agents. Si tu reçois ce message, réponds via idea_reply avec : (1) "DevFrontend OK — pont inter-agent fonctionnel", (2) le résultat de 144 ÷ 12, e
|
||||||
@ -18,3 +18,5 @@ objective: [Ping inter-agent depuis Main] Test du pont MCP inter-agents. Si tu r
|
|||||||
- **Response:** Les deux livrables sont prêts. NON commité (Git tranche, 2 commits séparés). ## Résultats des commandes - `npx tsc --noEmit` : **propre** (0 erreur). - `npx vitest run src/features/agents/ src/adapters/mock/` : **109 tests / 8 fichiers, tous verts**. ## Nombre réel de gateways constaté : **14** Le `Gateways` (ports/index.ts) en a 14 ; l'array du test en listait 13 — il manquait `permission` (présente dans HEAD). Compteur + libellé + array corrigés. ## Helper de conversion exact (pur, exporté, testé) ```ts // AgentLimitBadge.tsx export function timeInputToEpochMs(value: string, now: number): number | null { const match = /^(\d{1,2}):(\d{2})$/.exec(value.trim()); if (!match) return null; const hours = Number(match[1]); const minutes = Number(match[2]); if (hours > 23 || minutes > 59) return null; const d = new Date(now); d.setHours(hours, minutes, 0, 0); return d.getTime(); } ``` Comportement documenté : input `time` seul ⇒ aujourd'hui à HH:MM (même jour calendaire que `now`). Une heure déjà passée renvoie un instant < now **sans validation stricte** — le backend clampe à maintenant ⇒ reprise immédiate (commenté dans le code et couvert par un test). Empty/malformé/`25:00`/`12:60` ⇒ `null` (saisie incomplète ignorée). ═══ LIVRABLE 1 — LS8-front (commit « LS8-front ») ═══ Fichiers modifiés : 1. `frontend/src/ports/index.ts` — `setResumeAt(agentId, resetsAtMs): Promise<void>` ajouté à `InputGateway`, à côté de `cancelResume`. 2. `frontend/src/adapters/input.ts` — `setResumeAt` → `invoke("set_resume_at", { agentId, resetsAtMs })` (args nus, comme cancel_resume). 3. `frontend/src/adapters/mock/index.ts` — `MockInputGateway.setResumeAt` enregistre dans `resumeArmings: { agentId, resetsAtMs }[]` (patron `cancelledResumes`). 4. `frontend/src/features/agents/useAgents.ts` — action `setResumeAt(agentId, resetsAtMs)` exposée, délègue au port, **aucune mutation optimiste** (l'event `agentResumeScheduled` rebascule l'état via l'abonnement existant). 5. `frontend/src/features/agents/AgentLimitBadge.tsx` — état SUSPECTED sans heure (`suspected===true && !limitedUntil && !resumeFireAt`) : le placeholder/TODO LS7 est remplacé par un **formulaire** (input `type="time"` aria-label `resume time` + bouton `Programmer la reprise` aria-label `schedule resume`, désactivés pendant `busy`/saisie vide). Soumission → `timeInputToEpochMs` → `onSetResumeAt(epochMs)`. Une fois armé, le badge bascule **tout seul** vers « limité jusqu'à HH:MM » + compte à rebours + Annuler (chaîne LS7 inchangée). TODO(LS-front) **retiré**. 6. `frontend/src/features/agents/AgentsPanel.tsx` — passe `onSetResumeAt={(ms) => void vm.setResumeAt(a.id, ms)}` au badge. 7. `frontend/src/features/agents/AgentLimitBadge.test.tsx` — **test QA préexistant (LS7) mis à jour au nouveau contrat** (prop requise `onSetResumeAt`, le cas « suspected sans heure » teste maintenant le formulaire au lieu de l'ancien texte placeholder) + ajout couverture LS8 : `timeInputToEpochMs` (jour calendaire, instant passé, null malformé), soumission du formulaire ⇒ `onSetResumeAt` appelé avec l'epoch-ms attendu, bouton désactivé si vide. *(Sans cette MAJ le typecheck cassait — la prop est devenue obligatoire et le rendu du cas suspected a changé. Aucune logique de prod altérée, seulement l'alignement du test.)* ═══ LIVRABLE 2 — fix test (commit « fix-test » séparé) ═══ Fichier modifié : - `frontend/src/adapters/mock/mock.test.ts` — « thirteen » → « fourteen », `"permission"` inséré dans l'array attendu (ordre alphabétique). **Rien d'autre touché** dans ce fichier. ## Point de friction / note Petit ajustement non listé mais nécessaire : le `<form>` portait initialement le même `aria-label="schedule resume"` que son bouton submit, ce qui faisait matcher 2 éléments avec `getByLabelText`. J'ai renommé le label du form en `"resume time form"` (le bouton garde `schedule resume`). Aucune incidence fonctionnelle. Le filet humain niveau 3 est désormais **complet** : détection → formulaire de saisie → arme la reprise annulable via `set_resume_at` → bascule auto vers l'état nominal. Prêt pour QA (logique d'état dans le hook, helpers purs isolés, mocks instrumentés `resumeArmings`/`cancelledResumes`).
|
- **Response:** Les deux livrables sont prêts. NON commité (Git tranche, 2 commits séparés). ## Résultats des commandes - `npx tsc --noEmit` : **propre** (0 erreur). - `npx vitest run src/features/agents/ src/adapters/mock/` : **109 tests / 8 fichiers, tous verts**. ## Nombre réel de gateways constaté : **14** Le `Gateways` (ports/index.ts) en a 14 ; l'array du test en listait 13 — il manquait `permission` (présente dans HEAD). Compteur + libellé + array corrigés. ## Helper de conversion exact (pur, exporté, testé) ```ts // AgentLimitBadge.tsx export function timeInputToEpochMs(value: string, now: number): number | null { const match = /^(\d{1,2}):(\d{2})$/.exec(value.trim()); if (!match) return null; const hours = Number(match[1]); const minutes = Number(match[2]); if (hours > 23 || minutes > 59) return null; const d = new Date(now); d.setHours(hours, minutes, 0, 0); return d.getTime(); } ``` Comportement documenté : input `time` seul ⇒ aujourd'hui à HH:MM (même jour calendaire que `now`). Une heure déjà passée renvoie un instant < now **sans validation stricte** — le backend clampe à maintenant ⇒ reprise immédiate (commenté dans le code et couvert par un test). Empty/malformé/`25:00`/`12:60` ⇒ `null` (saisie incomplète ignorée). ═══ LIVRABLE 1 — LS8-front (commit « LS8-front ») ═══ Fichiers modifiés : 1. `frontend/src/ports/index.ts` — `setResumeAt(agentId, resetsAtMs): Promise<void>` ajouté à `InputGateway`, à côté de `cancelResume`. 2. `frontend/src/adapters/input.ts` — `setResumeAt` → `invoke("set_resume_at", { agentId, resetsAtMs })` (args nus, comme cancel_resume). 3. `frontend/src/adapters/mock/index.ts` — `MockInputGateway.setResumeAt` enregistre dans `resumeArmings: { agentId, resetsAtMs }[]` (patron `cancelledResumes`). 4. `frontend/src/features/agents/useAgents.ts` — action `setResumeAt(agentId, resetsAtMs)` exposée, délègue au port, **aucune mutation optimiste** (l'event `agentResumeScheduled` rebascule l'état via l'abonnement existant). 5. `frontend/src/features/agents/AgentLimitBadge.tsx` — état SUSPECTED sans heure (`suspected===true && !limitedUntil && !resumeFireAt`) : le placeholder/TODO LS7 est remplacé par un **formulaire** (input `type="time"` aria-label `resume time` + bouton `Programmer la reprise` aria-label `schedule resume`, désactivés pendant `busy`/saisie vide). Soumission → `timeInputToEpochMs` → `onSetResumeAt(epochMs)`. Une fois armé, le badge bascule **tout seul** vers « limité jusqu'à HH:MM » + compte à rebours + Annuler (chaîne LS7 inchangée). TODO(LS-front) **retiré**. 6. `frontend/src/features/agents/AgentsPanel.tsx` — passe `onSetResumeAt={(ms) => void vm.setResumeAt(a.id, ms)}` au badge. 7. `frontend/src/features/agents/AgentLimitBadge.test.tsx` — **test QA préexistant (LS7) mis à jour au nouveau contrat** (prop requise `onSetResumeAt`, le cas « suspected sans heure » teste maintenant le formulaire au lieu de l'ancien texte placeholder) + ajout couverture LS8 : `timeInputToEpochMs` (jour calendaire, instant passé, null malformé), soumission du formulaire ⇒ `onSetResumeAt` appelé avec l'epoch-ms attendu, bouton désactivé si vide. *(Sans cette MAJ le typecheck cassait — la prop est devenue obligatoire et le rendu du cas suspected a changé. Aucune logique de prod altérée, seulement l'alignement du test.)* ═══ LIVRABLE 2 — fix test (commit « fix-test » séparé) ═══ Fichier modifié : - `frontend/src/adapters/mock/mock.test.ts` — « thirteen » → « fourteen », `"permission"` inséré dans l'array attendu (ordre alphabétique). **Rien d'autre touché** dans ce fichier. ## Point de friction / note Petit ajustement non listé mais nécessaire : le `<form>` portait initialement le même `aria-label="schedule resume"` que son bouton submit, ce qui faisait matcher 2 éléments avec `getByLabelText`. J'ai renommé le label du form en `"resume time form"` (le bouton garde `schedule resume`). Aucune incidence fonctionnelle. Le filet humain niveau 3 est désormais **complet** : détection → formulaire de saisie → arme la reprise annulable via `set_resume_at` → bascule auto vers l'état nominal. Prêt pour QA (logique d'état dans le hook, helpers purs isolés, mocks instrumentés `resumeArmings`/`cancelledResumes`).
|
||||||
- **Prompt:** Implémente le Lot A frontend du chantier `UX conversations/délégations + read-model live-state minimal`, en consommant le backend/Tauri que DevBackend vient d'ajouter. Contrat backend disponible: - commande Tauri `get_project_work_state(projectId)`. - DTO `ProjectWorkStateDto { agents }` camelCase. - agent: `agentId`, `name`, `profileId`, `live?: { nodeId, sessionId, kind: "pty"|"structured" }`, `busy` taggé `{ state: "idle" }` ou `{ state: "busy", ticket, sinceMs }`. Périmètre frontend strict: - Ajouter types domaine `ProjectWorkState`, `AgentWorkState`, `LiveWorkSession`, `WorkBusyState`. - Ajouter port `WorkStateGateway` et adapter Tauri `workState`. - Ajouter mock gateway. - Ajouter feature `features/workstate`: hook `useProjectWorkState(projectId)` + `ProjectWorkStatePanel`. - Ajouter une surface UI minimale, probablement un onglet/sidebar `Work` dans `ProjectsView`, read-only: ligne par agent, live/offline, idle/busy, ticket court si busy, empty/error/refresh. - Refresh initial + refresh sur events existants pertinents si le système d'events est déjà accessible (`agentLaunched`, `agentExited`, `agentBusyChanged`, `orchestratorRequestProcessed` si disponible). Ne crée pas une grosse architecture event si ce n'est pas déjà le pattern. - Pas d'actions attach/stop/voir conversation dans ce lot. Tests attendus: - panel empty state. - agent idle/offline. - agent live + busy avec ticket court. - refresh déclenché par event si implémenté. - onglet Work visible/rendu dans ProjectsView si raisonnable. Exécute `npx vitest` ciblé et `npx tsc --noEmit`. Ne committe pas. Réponds avec fichiers modifiés, commandes/résultats, et risques.
|
- **Prompt:** Implémente le Lot A frontend du chantier `UX conversations/délégations + read-model live-state minimal`, en consommant le backend/Tauri que DevBackend vient d'ajouter. Contrat backend disponible: - commande Tauri `get_project_work_state(projectId)`. - DTO `ProjectWorkStateDto { agents }` camelCase. - agent: `agentId`, `name`, `profileId`, `live?: { nodeId, sessionId, kind: "pty"|"structured" }`, `busy` taggé `{ state: "idle" }` ou `{ state: "busy", ticket, sinceMs }`. Périmètre frontend strict: - Ajouter types domaine `ProjectWorkState`, `AgentWorkState`, `LiveWorkSession`, `WorkBusyState`. - Ajouter port `WorkStateGateway` et adapter Tauri `workState`. - Ajouter mock gateway. - Ajouter feature `features/workstate`: hook `useProjectWorkState(projectId)` + `ProjectWorkStatePanel`. - Ajouter une surface UI minimale, probablement un onglet/sidebar `Work` dans `ProjectsView`, read-only: ligne par agent, live/offline, idle/busy, ticket court si busy, empty/error/refresh. - Refresh initial + refresh sur events existants pertinents si le système d'events est déjà accessible (`agentLaunched`, `agentExited`, `agentBusyChanged`, `orchestratorRequestProcessed` si disponible). Ne crée pas une grosse architecture event si ce n'est pas déjà le pattern. - Pas d'actions attach/stop/voir conversation dans ce lot. Tests attendus: - panel empty state. - agent idle/offline. - agent live + busy avec ticket court. - refresh déclenché par event si implémenté. - onglet Work visible/rendu dans ProjectsView si raisonnable. Exécute `npx vitest` ciblé et `npx tsc --noEmit`. Ne committe pas. Réponds avec fichiers modifiés, commandes/résultats, et risques.
|
||||||
- **Response:** Lot A frontend implémenté, sans commit. Fichiers modifiés/ajoutés frontend: - `frontend/src/domain/index.ts`: types `ProjectWorkState`, `AgentWorkState`, `LiveWorkSession`, `WorkBusyState`. - `frontend/src/ports/index.ts`: port `WorkStateGateway` + ajout dans `Gateways`. - `frontend/src/adapters/workState.ts`: adapter Tauri `get_project_work_state`. - `frontend/src/adapters/index.ts`: câblage `TauriWorkStateGateway`. - `frontend/src/adapters/mock/index.ts`: `MockWorkStateGateway` + helper `_setProjectWorkState` + câblage mock. - `frontend/src/features/workstate/useProjectWorkState.ts`: hook avec refresh initial et refresh sur `agentLaunched`, `agentExited`, `agentBusyChanged`, `orchestratorRequestProcessed`. - `frontend/src/features/workstate/ProjectWorkStatePanel.tsx`: UI read-only, empty/error/refresh, live/offline, idle/busy, ticket court. - `frontend/src/features/workstate/index.ts`: export feature. - `frontend/src/features/workstate/workstate.test.tsx`: tests panel empty, idle/offline, live+busy ticket court, refresh event. - `frontend/src/features/projects/ProjectsView.tsx`: onglet sidebar `Work`. - `frontend/src/features/projects/projects.test.tsx`: injection mock workState + test onglet Work rendu. Commandes exécutées: - `cd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx` - Résultat: OK, 2 fichiers passés, 13 tests passés. - Vite affiche seulement des warnings existants sur options `esbuild`/`oxc`. - `cd frontend && npx tsc --noEmit` - Résultat: OK, aucune erreur. Risques / limites: - Le refresh event est volontairement large car les events cités ne portent pas tous `projectId`; le hook re-fetch le projet actif sur event pertinent. - UI strictement read-only comme demandé: pas d’attach/stop/voir conversation. - Je n’ai pas lancé tout le `vitest` global, seulement le ciblé demandé. - Le worktree contenait déjà des changements backend et `.ideai/*`; je ne les ai pas touchés.
|
- **Response:** Lot A frontend implémenté, sans commit. Fichiers modifiés/ajoutés frontend: - `frontend/src/domain/index.ts`: types `ProjectWorkState`, `AgentWorkState`, `LiveWorkSession`, `WorkBusyState`. - `frontend/src/ports/index.ts`: port `WorkStateGateway` + ajout dans `Gateways`. - `frontend/src/adapters/workState.ts`: adapter Tauri `get_project_work_state`. - `frontend/src/adapters/index.ts`: câblage `TauriWorkStateGateway`. - `frontend/src/adapters/mock/index.ts`: `MockWorkStateGateway` + helper `_setProjectWorkState` + câblage mock. - `frontend/src/features/workstate/useProjectWorkState.ts`: hook avec refresh initial et refresh sur `agentLaunched`, `agentExited`, `agentBusyChanged`, `orchestratorRequestProcessed`. - `frontend/src/features/workstate/ProjectWorkStatePanel.tsx`: UI read-only, empty/error/refresh, live/offline, idle/busy, ticket court. - `frontend/src/features/workstate/index.ts`: export feature. - `frontend/src/features/workstate/workstate.test.tsx`: tests panel empty, idle/offline, live+busy ticket court, refresh event. - `frontend/src/features/projects/ProjectsView.tsx`: onglet sidebar `Work`. - `frontend/src/features/projects/projects.test.tsx`: injection mock workState + test onglet Work rendu. Commandes exécutées: - `cd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx` - Résultat: OK, 2 fichiers passés, 13 tests passés. - Vite affiche seulement des warnings existants sur options `esbuild`/`oxc`. - `cd frontend && npx tsc --noEmit` - Résultat: OK, aucune erreur. Risques / limites: - Le refresh event est volontairement large car les events cités ne portent pas tous `projectId`; le hook re-fetch le projet actif sur event pertinent. - UI strictement read-only comme demandé: pas d’attach/stop/voir conversation. - Je n’ai pas lancé tout le `vitest` global, seulement le ciblé demandé. - Le worktree contenait déjà des changements backend et `.ideai/*`; je ne les ai pas touchés.
|
||||||
|
- **Prompt:** Implémente le Lot B frontend `workstate delegation/queue snapshot` sur la branche active `feature/workstate-delegation-queue`, en t'appuyant sur le backend/Tauri déjà modifié par DevBackend. Contexte backend disponible: - `ProjectWorkState.agents[].tickets` est maintenant exposé côté DTO camelCase. - Ticket fields TS attendus: `ticketId`, `conversationId`, `position`, `status` (`"inProgress" | "queued"`), `source` (`{ kind: "human" } | { kind: "agent"; agentId: string }`), `requesterLabel`, `taskPreview`, `taskLen`. - Backend inclut les tickets `human` et `agent`; l'UI doit donc libeller clairement "tickets"/"inputs" ou gérer Human/Agent sans prétendre que tout est délégation agent. À faire: 1. Étendre les types frontend dans `frontend/src/domain/index.ts` (`AgentTicketState`, `TicketWorkStatus`, `TicketWorkSource`, `AgentWorkState.tickets`). 2. Adapter le mock gateway pour accepter/normaliser `tickets` si nécessaire. 3. Dans `frontend/src/features/workstate/ProjectWorkStatePanel.tsx`, afficher sous chaque agent une mini-liste compacte des tickets FIFO: - `#1 In progress` ou `#2 Queued` (position + 1), badge visuel cohérent avec l'existant, - requester/source (`Human` pour source human, label requester et/ou agent id court pour agent), - `taskPreview`, indication légère si `taskLen > taskPreview.length`, ticket id court monospace, - agent sans ticket: ne pas ajouter de bruit visuel excessif. 4. Dans le hook `useProjectWorkState`, ajouter un refresh sur l'event `delegationReady` si l'infrastructure d'events frontend existe déjà pour les autres events; sinon explique précisément pourquoi. 5. Mettre à jour/ajouter les tests Vitest pertinents: agent avec ticket inProgress, deux tickets en ordre, agent sans ticket, event `delegationReady` déclenche refresh, mock renvoie tickets. Contraintes: - Respecte le design existant, pas d'actions d'annulation/résolution, pas de nouvelle commande Tauri, pas de lecture logs/handoff. - Ne committe pas. - Ignore les fichiers runtime `.ideai/*` dirty. Vérifications demandées: `cd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx`, puis `cd frontend && npx tsc --noEmit`. Retourne fichiers modifiés, choix UI, résultats réels des commandes, et tout blocage.
|
||||||
|
- **Response:** Lot B frontend `workstate delegation/queue snapshot` implémenté sur la branche active `feature/workstate-delegation-queue`. Aucun commit effectué. Fichiers modifiés: - `frontend/src/domain/index.ts` - `frontend/src/adapters/mock/index.ts` - `frontend/src/features/workstate/ProjectWorkStatePanel.tsx` - `frontend/src/features/workstate/useProjectWorkState.ts` - `frontend/src/features/workstate/workstate.test.tsx` Choix UI: - Sous-liste compacte uniquement pour les agents qui ont des tickets, sans bruit visuel pour les agents sans ticket. - Tickets triés par `position`, affichés en `#N In progress` / `#N Queued`. - Source humaine libellée `Human`; source agent affichée avec `requesterLabel` + id agent court quand disponible. - `taskPreview` affiché inline, avec suffixe léger `+N` si `taskLen > taskPreview.length`. - `ticketId` court en monospace avec `title` complet. Détails techniques: - Ajout des types `AgentTicketState`, `TicketWorkStatus`, `TicketWorkSource` et `AgentWorkState.tickets`. - `MockWorkStateGateway` accepte et normalise les états legacy sans `tickets` en `tickets: []`. - `useProjectWorkState` rafraîchit maintenant aussi sur l’événement domaine `delegationReady`, via l’infrastructure `SystemGateway.onDomainEvent` déjà existante. Vérifications réelles: - `cd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx` : OK, `2 passed`, `17 passed`. - `cd frontend && npx tsc --noEmit` : OK, aucune sortie d’erreur. Blocage: aucun. Les fichiers runtime `.ideai/*` dirty et les changements backend existants ont été ignorés comme demandé.
|
||||||
@ -12,3 +12,5 @@
|
|||||||
{"id":"50c3e999-c6a3-4d8b-aef3-b6c273ed9afc","conversation":"09b15ec3-ce2f-0af2-2903-ef699df9de41","atMs":1781677152871,"source":{"kind":"agent","agentId":"af7f86da-76bc-48e1-9900-71f45a624800"},"role":"response","text":"Les deux livrables sont prêts. NON commité (Git tranche, 2 commits séparés).\n\n## Résultats des commandes\n- `npx tsc --noEmit` : **propre** (0 erreur).\n- `npx vitest run src/features/agents/ src/adapters/mock/` : **109 tests / 8 fichiers, tous verts**.\n\n## Nombre réel de gateways constaté : **14**\nLe `Gateways` (ports/index.ts) en a 14 ; l'array du test en listait 13 — il manquait `permission` (présente dans HEAD). Compteur + libellé + array corrigés.\n\n## Helper de conversion exact (pur, exporté, testé)\n```ts\n// AgentLimitBadge.tsx\nexport function timeInputToEpochMs(value: string, now: number): number | null {\n const match = /^(\\d{1,2}):(\\d{2})$/.exec(value.trim());\n if (!match) return null;\n const hours = Number(match[1]);\n const minutes = Number(match[2]);\n if (hours > 23 || minutes > 59) return null;\n const d = new Date(now);\n d.setHours(hours, minutes, 0, 0);\n return d.getTime();\n}\n```\nComportement documenté : input `time` seul ⇒ aujourd'hui à HH:MM (même jour calendaire que `now`). Une heure déjà passée renvoie un instant < now **sans validation stricte** — le backend clampe à maintenant ⇒ reprise immédiate (commenté dans le code et couvert par un test). Empty/malformé/`25:00`/`12:60` ⇒ `null` (saisie incomplète ignorée).\n\n═══ LIVRABLE 1 — LS8-front (commit « LS8-front ») ═══\nFichiers modifiés :\n1. `frontend/src/ports/index.ts` — `setResumeAt(agentId, resetsAtMs): Promise<void>` ajouté à `InputGateway`, à côté de `cancelResume`.\n2. `frontend/src/adapters/input.ts` — `setResumeAt` → `invoke(\"set_resume_at\", { agentId, resetsAtMs })` (args nus, comme cancel_resume).\n3. `frontend/src/adapters/mock/index.ts` — `MockInputGateway.setResumeAt` enregistre dans `resumeArmings: { agentId, resetsAtMs }[]` (patron `cancelledResumes`).\n4. `frontend/src/features/agents/useAgents.ts` — action `setResumeAt(agentId, resetsAtMs)` exposée, délègue au port, **aucune mutation optimiste** (l'event `agentResumeScheduled` rebascule l'état via l'abonnement existant).\n5. `frontend/src/features/agents/AgentLimitBadge.tsx` — état SUSPECTED sans heure (`suspected===true && !limitedUntil && !resumeFireAt`) : le placeholder/TODO LS7 est remplacé par un **formulaire** (input `type=\"time\"` aria-label `resume time` + bouton `Programmer la reprise` aria-label `schedule resume`, désactivés pendant `busy`/saisie vide). Soumission → `timeInputToEpochMs` → `onSetResumeAt(epochMs)`. Une fois armé, le badge bascule **tout seul** vers « limité jusqu'à HH:MM » + compte à rebours + Annuler (chaîne LS7 inchangée). TODO(LS-front) **retiré**.\n6. `frontend/src/features/agents/AgentsPanel.tsx` — passe `onSetResumeAt={(ms) => void vm.setResumeAt(a.id, ms)}` au badge.\n7. `frontend/src/features/agents/AgentLimitBadge.test.tsx` — **test QA préexistant (LS7) mis à jour au nouveau contrat** (prop requise `onSetResumeAt`, le cas « suspected sans heure » teste maintenant le formulaire au lieu de l'ancien texte placeholder) + ajout couverture LS8 : `timeInputToEpochMs` (jour calendaire, instant passé, null malformé), soumission du formulaire ⇒ `onSetResumeAt` appelé avec l'epoch-ms attendu, bouton désactivé si vide. *(Sans cette MAJ le typecheck cassait — la prop est devenue obligatoire et le rendu du cas suspected a changé. Aucune logique de prod altérée, seulement l'alignement du test.)*\n\n═══ LIVRABLE 2 — fix test (commit « fix-test » séparé) ═══\nFichier modifié :\n- `frontend/src/adapters/mock/mock.test.ts` — « thirteen » → « fourteen », `\"permission\"` inséré dans l'array attendu (ordre alphabétique). **Rien d'autre touché** dans ce fichier.\n\n## Point de friction / note\nPetit ajustement non listé mais nécessaire : le `<form>` portait initialement le même `aria-label=\"schedule resume\"` que son bouton submit, ce qui faisait matcher 2 éléments avec `getByLabelText`. J'ai renommé le label du form en `\"resume time form\"` (le bouton garde `schedule resume`). Aucune incidence fonctionnelle.\n\nLe filet humain niveau 3 est désormais **complet** : détection → formulaire de saisie → arme la reprise annulable via `set_resume_at` → bascule auto vers l'état nominal. Prêt pour QA (logique d'état dans le hook, helpers purs isolés, mocks instrumentés `resumeArmings`/`cancelledResumes`)."}
|
{"id":"50c3e999-c6a3-4d8b-aef3-b6c273ed9afc","conversation":"09b15ec3-ce2f-0af2-2903-ef699df9de41","atMs":1781677152871,"source":{"kind":"agent","agentId":"af7f86da-76bc-48e1-9900-71f45a624800"},"role":"response","text":"Les deux livrables sont prêts. NON commité (Git tranche, 2 commits séparés).\n\n## Résultats des commandes\n- `npx tsc --noEmit` : **propre** (0 erreur).\n- `npx vitest run src/features/agents/ src/adapters/mock/` : **109 tests / 8 fichiers, tous verts**.\n\n## Nombre réel de gateways constaté : **14**\nLe `Gateways` (ports/index.ts) en a 14 ; l'array du test en listait 13 — il manquait `permission` (présente dans HEAD). Compteur + libellé + array corrigés.\n\n## Helper de conversion exact (pur, exporté, testé)\n```ts\n// AgentLimitBadge.tsx\nexport function timeInputToEpochMs(value: string, now: number): number | null {\n const match = /^(\\d{1,2}):(\\d{2})$/.exec(value.trim());\n if (!match) return null;\n const hours = Number(match[1]);\n const minutes = Number(match[2]);\n if (hours > 23 || minutes > 59) return null;\n const d = new Date(now);\n d.setHours(hours, minutes, 0, 0);\n return d.getTime();\n}\n```\nComportement documenté : input `time` seul ⇒ aujourd'hui à HH:MM (même jour calendaire que `now`). Une heure déjà passée renvoie un instant < now **sans validation stricte** — le backend clampe à maintenant ⇒ reprise immédiate (commenté dans le code et couvert par un test). Empty/malformé/`25:00`/`12:60` ⇒ `null` (saisie incomplète ignorée).\n\n═══ LIVRABLE 1 — LS8-front (commit « LS8-front ») ═══\nFichiers modifiés :\n1. `frontend/src/ports/index.ts` — `setResumeAt(agentId, resetsAtMs): Promise<void>` ajouté à `InputGateway`, à côté de `cancelResume`.\n2. `frontend/src/adapters/input.ts` — `setResumeAt` → `invoke(\"set_resume_at\", { agentId, resetsAtMs })` (args nus, comme cancel_resume).\n3. `frontend/src/adapters/mock/index.ts` — `MockInputGateway.setResumeAt` enregistre dans `resumeArmings: { agentId, resetsAtMs }[]` (patron `cancelledResumes`).\n4. `frontend/src/features/agents/useAgents.ts` — action `setResumeAt(agentId, resetsAtMs)` exposée, délègue au port, **aucune mutation optimiste** (l'event `agentResumeScheduled` rebascule l'état via l'abonnement existant).\n5. `frontend/src/features/agents/AgentLimitBadge.tsx` — état SUSPECTED sans heure (`suspected===true && !limitedUntil && !resumeFireAt`) : le placeholder/TODO LS7 est remplacé par un **formulaire** (input `type=\"time\"` aria-label `resume time` + bouton `Programmer la reprise` aria-label `schedule resume`, désactivés pendant `busy`/saisie vide). Soumission → `timeInputToEpochMs` → `onSetResumeAt(epochMs)`. Une fois armé, le badge bascule **tout seul** vers « limité jusqu'à HH:MM » + compte à rebours + Annuler (chaîne LS7 inchangée). TODO(LS-front) **retiré**.\n6. `frontend/src/features/agents/AgentsPanel.tsx` — passe `onSetResumeAt={(ms) => void vm.setResumeAt(a.id, ms)}` au badge.\n7. `frontend/src/features/agents/AgentLimitBadge.test.tsx` — **test QA préexistant (LS7) mis à jour au nouveau contrat** (prop requise `onSetResumeAt`, le cas « suspected sans heure » teste maintenant le formulaire au lieu de l'ancien texte placeholder) + ajout couverture LS8 : `timeInputToEpochMs` (jour calendaire, instant passé, null malformé), soumission du formulaire ⇒ `onSetResumeAt` appelé avec l'epoch-ms attendu, bouton désactivé si vide. *(Sans cette MAJ le typecheck cassait — la prop est devenue obligatoire et le rendu du cas suspected a changé. Aucune logique de prod altérée, seulement l'alignement du test.)*\n\n═══ LIVRABLE 2 — fix test (commit « fix-test » séparé) ═══\nFichier modifié :\n- `frontend/src/adapters/mock/mock.test.ts` — « thirteen » → « fourteen », `\"permission\"` inséré dans l'array attendu (ordre alphabétique). **Rien d'autre touché** dans ce fichier.\n\n## Point de friction / note\nPetit ajustement non listé mais nécessaire : le `<form>` portait initialement le même `aria-label=\"schedule resume\"` que son bouton submit, ce qui faisait matcher 2 éléments avec `getByLabelText`. J'ai renommé le label du form en `\"resume time form\"` (le bouton garde `schedule resume`). Aucune incidence fonctionnelle.\n\nLe filet humain niveau 3 est désormais **complet** : détection → formulaire de saisie → arme la reprise annulable via `set_resume_at` → bascule auto vers l'état nominal. Prêt pour QA (logique d'état dans le hook, helpers purs isolés, mocks instrumentés `resumeArmings`/`cancelledResumes`)."}
|
||||||
{"id":"b84fc73b-74fc-465f-a9b2-e4b79651e0f0","conversation":"09b15ec3-ce2f-0af2-2903-ef699df9de41","atMs":1781971246800,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Implémente le Lot A frontend du chantier `UX conversations/délégations + read-model live-state minimal`, en consommant le backend/Tauri que DevBackend vient d'ajouter.\n\nContrat backend disponible:\n- commande Tauri `get_project_work_state(projectId)`.\n- DTO `ProjectWorkStateDto { agents }` camelCase.\n- agent: `agentId`, `name`, `profileId`, `live?: { nodeId, sessionId, kind: \"pty\"|\"structured\" }`, `busy` taggé `{ state: \"idle\" }` ou `{ state: \"busy\", ticket, sinceMs }`.\n\nPérimètre frontend strict:\n- Ajouter types domaine `ProjectWorkState`, `AgentWorkState`, `LiveWorkSession`, `WorkBusyState`.\n- Ajouter port `WorkStateGateway` et adapter Tauri `workState`.\n- Ajouter mock gateway.\n- Ajouter feature `features/workstate`: hook `useProjectWorkState(projectId)` + `ProjectWorkStatePanel`.\n- Ajouter une surface UI minimale, probablement un onglet/sidebar `Work` dans `ProjectsView`, read-only: ligne par agent, live/offline, idle/busy, ticket court si busy, empty/error/refresh.\n- Refresh initial + refresh sur events existants pertinents si le système d'events est déjà accessible (`agentLaunched`, `agentExited`, `agentBusyChanged`, `orchestratorRequestProcessed` si disponible). Ne crée pas une grosse architecture event si ce n'est pas déjà le pattern.\n- Pas d'actions attach/stop/voir conversation dans ce lot.\n\nTests attendus:\n- panel empty state.\n- agent idle/offline.\n- agent live + busy avec ticket court.\n- refresh déclenché par event si implémenté.\n- onglet Work visible/rendu dans ProjectsView si raisonnable.\n\nExécute `npx vitest` ciblé et `npx tsc --noEmit`. Ne committe pas. Réponds avec fichiers modifiés, commandes/résultats, et risques."}
|
{"id":"b84fc73b-74fc-465f-a9b2-e4b79651e0f0","conversation":"09b15ec3-ce2f-0af2-2903-ef699df9de41","atMs":1781971246800,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Implémente le Lot A frontend du chantier `UX conversations/délégations + read-model live-state minimal`, en consommant le backend/Tauri que DevBackend vient d'ajouter.\n\nContrat backend disponible:\n- commande Tauri `get_project_work_state(projectId)`.\n- DTO `ProjectWorkStateDto { agents }` camelCase.\n- agent: `agentId`, `name`, `profileId`, `live?: { nodeId, sessionId, kind: \"pty\"|\"structured\" }`, `busy` taggé `{ state: \"idle\" }` ou `{ state: \"busy\", ticket, sinceMs }`.\n\nPérimètre frontend strict:\n- Ajouter types domaine `ProjectWorkState`, `AgentWorkState`, `LiveWorkSession`, `WorkBusyState`.\n- Ajouter port `WorkStateGateway` et adapter Tauri `workState`.\n- Ajouter mock gateway.\n- Ajouter feature `features/workstate`: hook `useProjectWorkState(projectId)` + `ProjectWorkStatePanel`.\n- Ajouter une surface UI minimale, probablement un onglet/sidebar `Work` dans `ProjectsView`, read-only: ligne par agent, live/offline, idle/busy, ticket court si busy, empty/error/refresh.\n- Refresh initial + refresh sur events existants pertinents si le système d'events est déjà accessible (`agentLaunched`, `agentExited`, `agentBusyChanged`, `orchestratorRequestProcessed` si disponible). Ne crée pas une grosse architecture event si ce n'est pas déjà le pattern.\n- Pas d'actions attach/stop/voir conversation dans ce lot.\n\nTests attendus:\n- panel empty state.\n- agent idle/offline.\n- agent live + busy avec ticket court.\n- refresh déclenché par event si implémenté.\n- onglet Work visible/rendu dans ProjectsView si raisonnable.\n\nExécute `npx vitest` ciblé et `npx tsc --noEmit`. Ne committe pas. Réponds avec fichiers modifiés, commandes/résultats, et risques."}
|
||||||
{"id":"a0b9d44f-2761-4652-b2f3-28222dbc4c62","conversation":"09b15ec3-ce2f-0af2-2903-ef699df9de41","atMs":1781971459114,"source":{"kind":"agent","agentId":"af7f86da-76bc-48e1-9900-71f45a624800"},"role":"response","text":"Lot A frontend implémenté, sans commit.\n\nFichiers modifiés/ajoutés frontend:\n- `frontend/src/domain/index.ts`: types `ProjectWorkState`, `AgentWorkState`, `LiveWorkSession`, `WorkBusyState`.\n- `frontend/src/ports/index.ts`: port `WorkStateGateway` + ajout dans `Gateways`.\n- `frontend/src/adapters/workState.ts`: adapter Tauri `get_project_work_state`.\n- `frontend/src/adapters/index.ts`: câblage `TauriWorkStateGateway`.\n- `frontend/src/adapters/mock/index.ts`: `MockWorkStateGateway` + helper `_setProjectWorkState` + câblage mock.\n- `frontend/src/features/workstate/useProjectWorkState.ts`: hook avec refresh initial et refresh sur `agentLaunched`, `agentExited`, `agentBusyChanged`, `orchestratorRequestProcessed`.\n- `frontend/src/features/workstate/ProjectWorkStatePanel.tsx`: UI read-only, empty/error/refresh, live/offline, idle/busy, ticket court.\n- `frontend/src/features/workstate/index.ts`: export feature.\n- `frontend/src/features/workstate/workstate.test.tsx`: tests panel empty, idle/offline, live+busy ticket court, refresh event.\n- `frontend/src/features/projects/ProjectsView.tsx`: onglet sidebar `Work`.\n- `frontend/src/features/projects/projects.test.tsx`: injection mock workState + test onglet Work rendu.\n\nCommandes exécutées:\n- `cd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx`\n - Résultat: OK, 2 fichiers passés, 13 tests passés.\n - Vite affiche seulement des warnings existants sur options `esbuild`/`oxc`.\n- `cd frontend && npx tsc --noEmit`\n - Résultat: OK, aucune erreur.\n\nRisques / limites:\n- Le refresh event est volontairement large car les events cités ne portent pas tous `projectId`; le hook re-fetch le projet actif sur event pertinent.\n- UI strictement read-only comme demandé: pas d’attach/stop/voir conversation.\n- Je n’ai pas lancé tout le `vitest` global, seulement le ciblé demandé.\n- Le worktree contenait déjà des changements backend et `.ideai/*`; je ne les ai pas touchés."}
|
{"id":"a0b9d44f-2761-4652-b2f3-28222dbc4c62","conversation":"09b15ec3-ce2f-0af2-2903-ef699df9de41","atMs":1781971459114,"source":{"kind":"agent","agentId":"af7f86da-76bc-48e1-9900-71f45a624800"},"role":"response","text":"Lot A frontend implémenté, sans commit.\n\nFichiers modifiés/ajoutés frontend:\n- `frontend/src/domain/index.ts`: types `ProjectWorkState`, `AgentWorkState`, `LiveWorkSession`, `WorkBusyState`.\n- `frontend/src/ports/index.ts`: port `WorkStateGateway` + ajout dans `Gateways`.\n- `frontend/src/adapters/workState.ts`: adapter Tauri `get_project_work_state`.\n- `frontend/src/adapters/index.ts`: câblage `TauriWorkStateGateway`.\n- `frontend/src/adapters/mock/index.ts`: `MockWorkStateGateway` + helper `_setProjectWorkState` + câblage mock.\n- `frontend/src/features/workstate/useProjectWorkState.ts`: hook avec refresh initial et refresh sur `agentLaunched`, `agentExited`, `agentBusyChanged`, `orchestratorRequestProcessed`.\n- `frontend/src/features/workstate/ProjectWorkStatePanel.tsx`: UI read-only, empty/error/refresh, live/offline, idle/busy, ticket court.\n- `frontend/src/features/workstate/index.ts`: export feature.\n- `frontend/src/features/workstate/workstate.test.tsx`: tests panel empty, idle/offline, live+busy ticket court, refresh event.\n- `frontend/src/features/projects/ProjectsView.tsx`: onglet sidebar `Work`.\n- `frontend/src/features/projects/projects.test.tsx`: injection mock workState + test onglet Work rendu.\n\nCommandes exécutées:\n- `cd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx`\n - Résultat: OK, 2 fichiers passés, 13 tests passés.\n - Vite affiche seulement des warnings existants sur options `esbuild`/`oxc`.\n- `cd frontend && npx tsc --noEmit`\n - Résultat: OK, aucune erreur.\n\nRisques / limites:\n- Le refresh event est volontairement large car les events cités ne portent pas tous `projectId`; le hook re-fetch le projet actif sur event pertinent.\n- UI strictement read-only comme demandé: pas d’attach/stop/voir conversation.\n- Je n’ai pas lancé tout le `vitest` global, seulement le ciblé demandé.\n- Le worktree contenait déjà des changements backend et `.ideai/*`; je ne les ai pas touchés."}
|
||||||
|
{"id":"01871912-13d6-42dd-b4df-497f0a1a9703","conversation":"09b15ec3-ce2f-0af2-2903-ef699df9de41","atMs":1781973915788,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Implémente le Lot B frontend `workstate delegation/queue snapshot` sur la branche active `feature/workstate-delegation-queue`, en t'appuyant sur le backend/Tauri déjà modifié par DevBackend.\n\nContexte backend disponible:\n- `ProjectWorkState.agents[].tickets` est maintenant exposé côté DTO camelCase.\n- Ticket fields TS attendus: `ticketId`, `conversationId`, `position`, `status` (`\"inProgress\" | \"queued\"`), `source` (`{ kind: \"human\" } | { kind: \"agent\"; agentId: string }`), `requesterLabel`, `taskPreview`, `taskLen`.\n- Backend inclut les tickets `human` et `agent`; l'UI doit donc libeller clairement \"tickets\"/\"inputs\" ou gérer Human/Agent sans prétendre que tout est délégation agent.\n\nÀ faire:\n1. Étendre les types frontend dans `frontend/src/domain/index.ts` (`AgentTicketState`, `TicketWorkStatus`, `TicketWorkSource`, `AgentWorkState.tickets`).\n2. Adapter le mock gateway pour accepter/normaliser `tickets` si nécessaire.\n3. Dans `frontend/src/features/workstate/ProjectWorkStatePanel.tsx`, afficher sous chaque agent une mini-liste compacte des tickets FIFO:\n - `#1 In progress` ou `#2 Queued` (position + 1), badge visuel cohérent avec l'existant,\n - requester/source (`Human` pour source human, label requester et/ou agent id court pour agent),\n - `taskPreview`, indication légère si `taskLen > taskPreview.length`, ticket id court monospace,\n - agent sans ticket: ne pas ajouter de bruit visuel excessif.\n4. Dans le hook `useProjectWorkState`, ajouter un refresh sur l'event `delegationReady` si l'infrastructure d'events frontend existe déjà pour les autres events; sinon explique précisément pourquoi.\n5. Mettre à jour/ajouter les tests Vitest pertinents: agent avec ticket inProgress, deux tickets en ordre, agent sans ticket, event `delegationReady` déclenche refresh, mock renvoie tickets.\n\nContraintes:\n- Respecte le design existant, pas d'actions d'annulation/résolution, pas de nouvelle commande Tauri, pas de lecture logs/handoff.\n- Ne committe pas.\n- Ignore les fichiers runtime `.ideai/*` dirty.\n\nVérifications demandées: `cd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx`, puis `cd frontend && npx tsc --noEmit`. Retourne fichiers modifiés, choix UI, résultats réels des commandes, et tout blocage."}
|
||||||
|
{"id":"7d70c87a-e7b5-468a-90c7-41decf1185bc","conversation":"09b15ec3-ce2f-0af2-2903-ef699df9de41","atMs":1781974090422,"source":{"kind":"agent","agentId":"af7f86da-76bc-48e1-9900-71f45a624800"},"role":"response","text":"Lot B frontend `workstate delegation/queue snapshot` implémenté sur la branche active `feature/workstate-delegation-queue`. Aucun commit effectué.\n\nFichiers modifiés:\n- `frontend/src/domain/index.ts`\n- `frontend/src/adapters/mock/index.ts`\n- `frontend/src/features/workstate/ProjectWorkStatePanel.tsx`\n- `frontend/src/features/workstate/useProjectWorkState.ts`\n- `frontend/src/features/workstate/workstate.test.tsx`\n\nChoix UI:\n- Sous-liste compacte uniquement pour les agents qui ont des tickets, sans bruit visuel pour les agents sans ticket.\n- Tickets triés par `position`, affichés en `#N In progress` / `#N Queued`.\n- Source humaine libellée `Human`; source agent affichée avec `requesterLabel` + id agent court quand disponible.\n- `taskPreview` affiché inline, avec suffixe léger `+N` si `taskLen > taskPreview.length`.\n- `ticketId` court en monospace avec `title` complet.\n\nDétails techniques:\n- Ajout des types `AgentTicketState`, `TicketWorkStatus`, `TicketWorkSource` et `AgentWorkState.tickets`.\n- `MockWorkStateGateway` accepte et normalise les états legacy sans `tickets` en `tickets: []`.\n- `useProjectWorkState` rafraîchit maintenant aussi sur l’événement domaine `delegationReady`, via l’infrastructure `SystemGateway.onDomainEvent` déjà existante.\n\nVérifications réelles:\n- `cd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx` : OK, `2 passed`, `17 passed`.\n- `cd frontend && npx tsc --noEmit` : OK, aucune sortie d’erreur.\n\nBlocage: aucun. Les fichiers runtime `.ideai/*` dirty et les changements backend existants ont été ignorés comme demandé."}
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
---
|
---
|
||||||
upTo: 95e2a7a2-bb92-474f-83ec-0a2a7bab2c3e
|
upTo: 53773ad0-5bb6-4e24-8456-d7ab17be3db0
|
||||||
objective: Nouvelle feature cadrée par Architect, prête à démarrer le développement : « Gestion des limites de session des agents » (détection hiérarchique des limites de session + reprise auto annulable). C'est
|
objective: Nouvelle feature cadrée par Architect, prête à démarrer le développement : « Gestion des limites de session des agents » (détection hiérarchique des limites de session + reprise auto annulable). C'est
|
||||||
---
|
---
|
||||||
**Objectif :** Nouvelle feature cadrée par Architect, prête à démarrer le développement : « Gestion des limites de session des agents » (détection hiérarchique des limites de session + reprise auto annulable). C'est
|
**Objectif :** Nouvelle feature cadrée par Architect, prête à démarrer le développement : « Gestion des limites de session des agents » (détection hiérarchique des limites de session + reprise auto annulable). C'est
|
||||||
|
|
||||||
- **Response:** Base remise au propre après le rebuild AppImage. Aucune action sortante, aucune suppression. ## Inspection Les modifications observées (3 affichées + 2 autres détectées) sont **100 % de l'état runtime/mémoire `.ideai/`**, zéro ligne de code source : - `.ideai/conversations/6bc594e8…/handoff.md` + `log.jsonl` : dérive de conversation. - `.ideai/layouts.json` : état de layout UI. - `.ideai/memory/MEMORY.md` : +1 ligne d'index. - `.ideai/memory/checkpoint-orchestrator-designation-appimage-build.md` (non suivi) : ton checkpoint de build. Vérifié : l'artefact `target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage` est **git-ignored (hors git)** → je n'y touche pas, comme demandé. ## Décision & action Pur runtime → **un seul commit `chore(wip)` sur `develop`**, directement (ce n'est pas une feature, pas de cycle Architect→Dev→QA requis), pour repartir d'une base propre sans rien laisser dirty ni perdre ton checkpoint : - `9c71a5b` **chore(wip): état runtime .ideai post-build AppImage 0.3.0** (conversation 6bc594e8 handoff+log, layouts, checkpoint de build + son index MEMORY.md). ## État final - Branche courante : **`develop`** @ `9c71a5b` (en avance de 9 sur `origin/develop`, non poussé). - `main` inchangé @ `29232dd`. - Worktree : **propre (0 dirty)**. `develop` est de nouveau une base saine pour le prochain chantier. Quand tu me l'annonces, je crée une `feature/* dédiée depuis develop`. Rappel : décision toujours en attente sur le ménage des branches `feature/agent-session-limits` (intégrée, supprimable), `feature/agent-skills`, `feature/agent-skill-awareness`, `fix/cold-start-delivery-race`.
|
|
||||||
- **Prompt:** Préparation du prochain chantier après fermeture de `orchestrator-designation`. Contexte: - Branche courante propre: `develop` @ `9c71a5b`, en avance de 9 sur `origin/develop`, aucun push. - `orchestrator-designation` est intégré et sa branche supprimée. - L'utilisateur a donné autonomie pour terminer les chantiers restants. Branches anciennes à statuer selon tes rappels: - `feature/agent-session-limits`: tu l'as qualifiée comme intégrée/supprimable. - `feature/agent-skills`: 1 commit non intégré. - `feature/agent-skill-awareness`: 5 commits non intégrés. - `fix/cold-start-delivery-race`: 3 commits non intégrés, probable sous-ensemble de skill-awareness. Ta mission Git: 1. Inspecte ces branches et leur relation à `develop`. 2. Prends la décision git la plus saine en autonomie: supprimer les branches déjà intégrées, conserver celles qui représentent un chantier réel, identifier les doublons/sous-ensembles. 3. Ne push pas. Pas d'action sortante. 4. Si une branche non intégrée doit être reprise comme prochain chantier, dis à Main laquelle et pourquoi. Si elle doit passer par Architect/Dev/QA avant merge, indique-le. 5. Rends compte des actions effectuées et de l'état final. Réponds via `idea_reply`.
|
|
||||||
- **Response:** Ménage des branches fait + cadrage du prochain chantier. Aucune action sortante, aucune suppression risquée. ## Action exécutée - **Supprimé `feature/agent-session-limits`** (`git branch -d`, refus auto si non mergée) : 0 commit propre, entièrement intégrée dans develop. Suppression sûre. ## Analyse des 3 branches restantes (relations git vérifiées) - **`feature/agent-skill-awareness`** — base `8452333`, 5 commits propres, NON intégrée. Le chantier le plus riche et récent : `ab34363` (manifeste de skills + outil MCP `idea_skill_read`), `e93a2c1` (fix cold-start delivery), `1a10d67` (test compteur outils MCP 11→12), `566bff4` (brief « capacités IdeA »), `5be8987` (runtime). **À conserver.** - **`feature/agent-skills`** — base `9736c42`, 1 commit `ef101db` (skills domain/usecases/FS store, L12), NON intégrée, **divergente** de skill-awareness (pas ancêtre). ⚠️ Elle touche **les mêmes fichiers** que skill-awareness (`domain/src/skill.rs`, `application/src/skill/*`, `infrastructure/src/store/skill.rs`) → ce sont très probablement **deux itérations concurrentes de la même couche skill**, pas des compléments. **Conservée**, mais l'arbitrage « laquelle fait foi » est une décision d'**architecture, pas git**. - **`fix/cold-start-delivery-race`** — base `6969dc7` (ligne main : elle traîne les commits de release 0.1.0/0.2.0). Son **seul contenu réel** est `9590eac` (fix cold-start), dont le **jumeau logique `e93a2c1` est déjà dans skill-awareness** (message identique). → **doublon superseded**. `git cherry` confirme qu'aucun de ces patchs n'est encore dans develop. ## Décisions / recommandations 1. **Prochain chantier = `feature/agent-skill-awareness`.** Pourquoi : c'est le travail le plus avancé et non intégré, et il englobe le fix cold-start de la branche `fix/cold-start-delivery-race`. 2. **Il DOIT passer par le cycle avant tout merge**, dans cet ordre : - **Architect d'abord** : trancher le chevauchement skill-awareness ⟷ `feature/agent-skills` (couche skill implémentée sur les mêmes fichiers). Décider qui est la source de vérité (probablement skill-awareness subsume agent-skills) avant de figer quoi que ce soit. - **Dev/QA** : revalider vert, en particulier `1a10d67` (compteur d'outils MCP, qui a déjà bougé 11→12 puis 12→13 dans state.rs côté develop) et le fix cold-start. 3. ⚠️ **Le rebase de skill-awareness sur develop VA conflicter** : la branche modifie `crates/app-tauri/src/state.rs` et `crates/infrastructure/src/input/mod.rs`, tous deux lourdement réécrits par orchestrator-designation déjà mergé dans develop. Ce n'est **pas un rebase aveugle** : je le ferai avec l'appui de Dev pour résoudre les conflits, puis QA revalide avant mon merge `--no-ff`. 4. **`fix/cold-start-delivery-race` : je recommande sa suppression** (force `-D`, car non mergée), son contenu unique étant préservé dans skill-awareness et le reste n'étant que des commits de release déjà sur main. Je ne l'ai PAS forcée en autonomie (force-delete = action plus difficile à annuler). **Donne-moi le feu vert et je la supprime**, idéalement une fois skill-awareness intégré dans develop. ## État final - Branche courante : **`develop`** @ `9c71a5b` (+9 vs origin, non poussé). - Branches : `develop`, `main` (@ `29232dd`), `feature/agent-skill-awareness`, `feature/agent-skills`, `fix/cold-start-delivery-race`. - Worktree : **propre**. Dis-moi : (a) je lance le prochain chantier sur skill-awareness — confie d'abord à Architect l'arbitrage du chevauchement avec agent-skills ; (b) feu vert ou non pour supprimer `fix/cold-start-delivery-race`.
|
- **Response:** Ménage des branches fait + cadrage du prochain chantier. Aucune action sortante, aucune suppression risquée. ## Action exécutée - **Supprimé `feature/agent-session-limits`** (`git branch -d`, refus auto si non mergée) : 0 commit propre, entièrement intégrée dans develop. Suppression sûre. ## Analyse des 3 branches restantes (relations git vérifiées) - **`feature/agent-skill-awareness`** — base `8452333`, 5 commits propres, NON intégrée. Le chantier le plus riche et récent : `ab34363` (manifeste de skills + outil MCP `idea_skill_read`), `e93a2c1` (fix cold-start delivery), `1a10d67` (test compteur outils MCP 11→12), `566bff4` (brief « capacités IdeA »), `5be8987` (runtime). **À conserver.** - **`feature/agent-skills`** — base `9736c42`, 1 commit `ef101db` (skills domain/usecases/FS store, L12), NON intégrée, **divergente** de skill-awareness (pas ancêtre). ⚠️ Elle touche **les mêmes fichiers** que skill-awareness (`domain/src/skill.rs`, `application/src/skill/*`, `infrastructure/src/store/skill.rs`) → ce sont très probablement **deux itérations concurrentes de la même couche skill**, pas des compléments. **Conservée**, mais l'arbitrage « laquelle fait foi » est une décision d'**architecture, pas git**. - **`fix/cold-start-delivery-race`** — base `6969dc7` (ligne main : elle traîne les commits de release 0.1.0/0.2.0). Son **seul contenu réel** est `9590eac` (fix cold-start), dont le **jumeau logique `e93a2c1` est déjà dans skill-awareness** (message identique). → **doublon superseded**. `git cherry` confirme qu'aucun de ces patchs n'est encore dans develop. ## Décisions / recommandations 1. **Prochain chantier = `feature/agent-skill-awareness`.** Pourquoi : c'est le travail le plus avancé et non intégré, et il englobe le fix cold-start de la branche `fix/cold-start-delivery-race`. 2. **Il DOIT passer par le cycle avant tout merge**, dans cet ordre : - **Architect d'abord** : trancher le chevauchement skill-awareness ⟷ `feature/agent-skills` (couche skill implémentée sur les mêmes fichiers). Décider qui est la source de vérité (probablement skill-awareness subsume agent-skills) avant de figer quoi que ce soit. - **Dev/QA** : revalider vert, en particulier `1a10d67` (compteur d'outils MCP, qui a déjà bougé 11→12 puis 12→13 dans state.rs côté develop) et le fix cold-start. 3. ⚠️ **Le rebase de skill-awareness sur develop VA conflicter** : la branche modifie `crates/app-tauri/src/state.rs` et `crates/infrastructure/src/input/mod.rs`, tous deux lourdement réécrits par orchestrator-designation déjà mergé dans develop. Ce n'est **pas un rebase aveugle** : je le ferai avec l'appui de Dev pour résoudre les conflits, puis QA revalide avant mon merge `--no-ff`. 4. **`fix/cold-start-delivery-race` : je recommande sa suppression** (force `-D`, car non mergée), son contenu unique étant préservé dans skill-awareness et le reste n'étant que des commits de release déjà sur main. Je ne l'ai PAS forcée en autonomie (force-delete = action plus difficile à annuler). **Donne-moi le feu vert et je la supprime**, idéalement une fois skill-awareness intégré dans develop. ## État final - Branche courante : **`develop`** @ `9c71a5b` (+9 vs origin, non poussé). - Branches : `develop`, `main` (@ `29232dd`), `feature/agent-skill-awareness`, `feature/agent-skills`, `fix/cold-start-delivery-race`. - Worktree : **propre**. Dis-moi : (a) je lance le prochain chantier sur skill-awareness — confie d'abord à Architect l'arbitrage du chevauchement avec agent-skills ; (b) feu vert ou non pour supprimer `fix/cold-start-delivery-race`.
|
||||||
- **Prompt:** Main reprend le cycle après cadrage Architect. État connu: repo `/home/anthony/Documents/Projects/IdeA`, branche actuelle `develop`, `develop...origin/develop [ahead 9]`, dirty: `.ideai/conversations/.../handoff.md`, `.ideai/conversations/.../log.jsonl`, `.ideai/layouts.json`, `.ideai/memory/MEMORY.md`, nouveau `.ideai/memory/checkpoint-blocked-until-appimage-030-restart.md`. Architect recommande de ne pas merger la vieille branche `feature/agent-skill-awareness`, mais de créer une branche neuve depuis `develop`, par ex. `feature/agent-skill-awareness-v2`. Merci de décider et effectuer uniquement les actions Git/locales nécessaires: gestion de branche, éventuellement commit/ignore/stash des fichiers runtime si tu le juges nécessaire selon ton rôle. Pas de push. Retourne: branche active, actions effectuées, état `git status --short --branch`, et consignes pour DevBackend/DevFrontend/QA.
|
- **Prompt:** Main reprend le cycle après cadrage Architect. État connu: repo `/home/anthony/Documents/Projects/IdeA`, branche actuelle `develop`, `develop...origin/develop [ahead 9]`, dirty: `.ideai/conversations/.../handoff.md`, `.ideai/conversations/.../log.jsonl`, `.ideai/layouts.json`, `.ideai/memory/MEMORY.md`, nouveau `.ideai/memory/checkpoint-blocked-until-appimage-030-restart.md`. Architect recommande de ne pas merger la vieille branche `feature/agent-skill-awareness`, mais de créer une branche neuve depuis `develop`, par ex. `feature/agent-skill-awareness-v2`. Merci de décider et effectuer uniquement les actions Git/locales nécessaires: gestion de branche, éventuellement commit/ignore/stash des fichiers runtime si tu le juges nécessaire selon ton rôle. Pas de push. Retourne: branche active, actions effectuées, état `git status --short --branch`, et consignes pour DevBackend/DevFrontend/QA.
|
||||||
- **Response:** Décision Git appliquée (local uniquement, aucun push). **Branche active : `feature/agent-skill-awareness-v2`** (créée depuis `develop`, working tree propre). **Actions effectuées :** 1. Les 7 fichiers dirty étaient tous de l'état runtime `.ideai/` (conversations handoff+log, layouts.json, MEMORY.md, nouveau checkpoint-blocked-until-appimage-030-restart.md). Conformément à mon rôle (séparer code de feature et état runtime), je les ai committés sur `develop` : - `c... ` chore(wip): état runtime .ideai (conversations, layouts, mémoire, checkpoint blocage AppImage 0.3.0) 2. Création de la branche neuve `feature/agent-skill-awareness-v2` depuis `develop` (tip = ce commit). J'ai suivi la reco Architect : la vieille `feature/agent-skill-awareness` (5be8987) n'est PAS mergée et reste en place comme archive — repart d'une base develop propre, pas de dette d'historique divergent (develop avait 7 commits d'avance / la feature 5 d'écart). **`git status --short --branch` :** ``` ## feature/agent-skill-awareness-v2 ``` (working tree propre) **Contexte branches :** - `develop` : +9 puis +1 (le commit runtime) vs origin → désormais ahead 10, non poussé. - `feature/agent-skill-awareness` : intacte (5be8987), abandonnée, conservée comme archive. - `feature/agent-skill-awareness-v2` : NOUVELLE base de travail. **Consignes pour DevBackend / DevFrontend / QA :** - Travaillez tous sur **`feature/agent-skill-awareness-v2`**. Ne touchez pas à `develop` ni à la vieille `feature/agent-skill-awareness`. - Ne committez pas vous-mêmes : signalez-moi (via Main) quand l'implémentation est prête, je fais les commits atomiques (code feature séparé de l'état runtime `.ideai/`). - Rappel checkpoint mémoire : un blocage de validation e2e attendait un restart sur AppImage 0.3.0 — vérifier que l'AppImage qui tourne est bien rebuildée avant de conclure une validation (le binaire qui tourne = AppImage, pas les sources). - Quand QA est vert avec sortie réelle, revenez vers moi : je rebase si besoin puis merge `--no-ff` v2 → develop.
|
- **Response:** Décision Git appliquée (local uniquement, aucun push). **Branche active : `feature/agent-skill-awareness-v2`** (créée depuis `develop`, working tree propre). **Actions effectuées :** 1. Les 7 fichiers dirty étaient tous de l'état runtime `.ideai/` (conversations handoff+log, layouts.json, MEMORY.md, nouveau checkpoint-blocked-until-appimage-030-restart.md). Conformément à mon rôle (séparer code de feature et état runtime), je les ai committés sur `develop` : - `c... ` chore(wip): état runtime .ideai (conversations, layouts, mémoire, checkpoint blocage AppImage 0.3.0) 2. Création de la branche neuve `feature/agent-skill-awareness-v2` depuis `develop` (tip = ce commit). J'ai suivi la reco Architect : la vieille `feature/agent-skill-awareness` (5be8987) n'est PAS mergée et reste en place comme archive — repart d'une base develop propre, pas de dette d'historique divergent (develop avait 7 commits d'avance / la feature 5 d'écart). **`git status --short --branch` :** ``` ## feature/agent-skill-awareness-v2 ``` (working tree propre) **Contexte branches :** - `develop` : +9 puis +1 (le commit runtime) vs origin → désormais ahead 10, non poussé. - `feature/agent-skill-awareness` : intacte (5be8987), abandonnée, conservée comme archive. - `feature/agent-skill-awareness-v2` : NOUVELLE base de travail. **Consignes pour DevBackend / DevFrontend / QA :** - Travaillez tous sur **`feature/agent-skill-awareness-v2`**. Ne touchez pas à `develop` ni à la vieille `feature/agent-skill-awareness`. - Ne committez pas vous-mêmes : signalez-moi (via Main) quand l'implémentation est prête, je fais les commits atomiques (code feature séparé de l'état runtime `.ideai/`). - Rappel checkpoint mémoire : un blocage de validation e2e attendait un restart sur AppImage 0.3.0 — vérifier que l'AppImage qui tourne est bien rebuildée avant de conclure une validation (le binaire qui tourne = AppImage, pas les sources). - Quand QA est vert avec sortie réelle, revenez vers moi : je rebase si besoin puis merge `--no-ff` v2 → develop.
|
||||||
@ -24,3 +22,5 @@ objective: Nouvelle feature cadrée par Architect, prête à démarrer le dével
|
|||||||
- **Prompt:** QA verte pour `feature/conversation-ux-live-state` Lot A, réserve environnementale non bloquante socket Unix. Merci de committer localement de façon atomique puis décider le merge local vers `develop` selon les règles projet. Changements code attendus: Backend/Tauri: `crates/application/src/workstate/`, `crates/application/tests/workstate.rs`, exports terminal/lib, snapshots live, Tauri DTO/command/state/lib/tests. Frontend: domain/ports/adapters/mock/workState, feature workstate, ProjectsView/tests. Runtime dirty `.ideai/*` aussi présent suite aux conversations live. Stratégie demandée: séparer code backend/frontend si pertinent, séparer runtime `.ideai` en chore wip si tu le gardes, ne pas push, ne pas supprimer les branches obsolètes. Après commit, décide toi-même merge ou différer; si merge, fais-le localement. Retourne hashes, messages, fichiers inclus/exclus, branche finale et status.
|
- **Prompt:** QA verte pour `feature/conversation-ux-live-state` Lot A, réserve environnementale non bloquante socket Unix. Merci de committer localement de façon atomique puis décider le merge local vers `develop` selon les règles projet. Changements code attendus: Backend/Tauri: `crates/application/src/workstate/`, `crates/application/tests/workstate.rs`, exports terminal/lib, snapshots live, Tauri DTO/command/state/lib/tests. Frontend: domain/ports/adapters/mock/workState, feature workstate, ProjectsView/tests. Runtime dirty `.ideai/*` aussi présent suite aux conversations live. Stratégie demandée: séparer code backend/frontend si pertinent, séparer runtime `.ideai` en chore wip si tu le gardes, ne pas push, ne pas supprimer les branches obsolètes. Après commit, décide toi-même merge ou différer; si merge, fais-le localement. Retourne hashes, messages, fichiers inclus/exclus, branche finale et status.
|
||||||
- **Response:** Lot A committé atomiquement (code backend / frontend séparés + runtime à part) **et mergé** dans `develop`. Aucun push, aucune branche supprimée. ## Commits (sur la feature) **1. `aae1849` — feat(workstate): read-model live-state minimal (Lot A backend)** - Inclus : `crates/application/src/workstate/mod.rs` (nouv.), `crates/application/tests/workstate.rs` (nouv.), `crates/application/src/lib.rs`, `crates/application/src/terminal/{mod,registry}.rs`, `crates/app-tauri/src/{commands,dto,lib,state}.rs`, `crates/app-tauri/tests/dto_agents.rs` **2. `17685a0` — feat(workstate): UI live-state (Lot A frontend)** - Inclus : `frontend/src/adapters/{index,workState}.ts` (workState nouv.), `frontend/src/adapters/mock/index.ts`, `frontend/src/domain/index.ts`, `frontend/src/ports/index.ts`, `frontend/src/features/projects/{ProjectsView.tsx,projects.test.tsx}`, `frontend/src/features/workstate/` (nouv. : index.ts, ProjectWorkStatePanel.tsx, useProjectWorkState.ts, workstate.test.tsx) **3. `a06328a` — chore(wip): état runtime .ideai (conversations live, agents, layouts)** - Inclus : `.ideai/agents.json`, `.ideai/layouts.json`, `.ideai/conversations/*` (handoff + log.jsonl ×6) ## Exclus / hors commit Rien laissé de côté : working tree **propre**. Le runtime `.ideai` est gardé (cohérent avec l'historique) mais isolé dans le chore — jamais mélangé au code. ## Décision merge : MERGÉ Lot A est vert, atomique et autonome (un read-model + son UI) → je l'intègre tout de suite pour limiter la dérive de `develop`, plutôt que d'attendre les lots suivants. Pas de rebase nécessaire (develop ancêtre direct). - Merge `--no-ff` : **`3bfb932`** — merge(workstate): intègre le read-model live-state + UX conversations (Lot A) dans develop. Sans conflit (ort), 35 fichiers / +1146 -54. ## État final - **Branche courante : `develop`** (tip `3bfb932`). - `develop` : **ahead origin/develop de 21** (local only, aucun push — conforme). - Working tree **propre**. - Branche `feature/conversation-ux-live-state` **conservée** (tip `a06328a`, intégrée). Disponible pour les lots suivants ; pour le **Lot B**, je rebascule/réaligne une branche feature depuis `develop` au prochain ticket (pas de dev direct sur `develop`). - Réserve QA (socket Unix non bindable en sandbox, alternatives skips vertes) consignée dans les messages. Dis-moi quand le Lot B est cadré : je prépare la branche.
|
- **Response:** Lot A committé atomiquement (code backend / frontend séparés + runtime à part) **et mergé** dans `develop`. Aucun push, aucune branche supprimée. ## Commits (sur la feature) **1. `aae1849` — feat(workstate): read-model live-state minimal (Lot A backend)** - Inclus : `crates/application/src/workstate/mod.rs` (nouv.), `crates/application/tests/workstate.rs` (nouv.), `crates/application/src/lib.rs`, `crates/application/src/terminal/{mod,registry}.rs`, `crates/app-tauri/src/{commands,dto,lib,state}.rs`, `crates/app-tauri/tests/dto_agents.rs` **2. `17685a0` — feat(workstate): UI live-state (Lot A frontend)** - Inclus : `frontend/src/adapters/{index,workState}.ts` (workState nouv.), `frontend/src/adapters/mock/index.ts`, `frontend/src/domain/index.ts`, `frontend/src/ports/index.ts`, `frontend/src/features/projects/{ProjectsView.tsx,projects.test.tsx}`, `frontend/src/features/workstate/` (nouv. : index.ts, ProjectWorkStatePanel.tsx, useProjectWorkState.ts, workstate.test.tsx) **3. `a06328a` — chore(wip): état runtime .ideai (conversations live, agents, layouts)** - Inclus : `.ideai/agents.json`, `.ideai/layouts.json`, `.ideai/conversations/*` (handoff + log.jsonl ×6) ## Exclus / hors commit Rien laissé de côté : working tree **propre**. Le runtime `.ideai` est gardé (cohérent avec l'historique) mais isolé dans le chore — jamais mélangé au code. ## Décision merge : MERGÉ Lot A est vert, atomique et autonome (un read-model + son UI) → je l'intègre tout de suite pour limiter la dérive de `develop`, plutôt que d'attendre les lots suivants. Pas de rebase nécessaire (develop ancêtre direct). - Merge `--no-ff` : **`3bfb932`** — merge(workstate): intègre le read-model live-state + UX conversations (Lot A) dans develop. Sans conflit (ort), 35 fichiers / +1146 -54. ## État final - **Branche courante : `develop`** (tip `3bfb932`). - `develop` : **ahead origin/develop de 21** (local only, aucun push — conforme). - Working tree **propre**. - Branche `feature/conversation-ux-live-state` **conservée** (tip `a06328a`, intégrée). Disponible pour les lots suivants ; pour le **Lot B**, je rebascule/réaligne une branche feature depuis `develop` au prochain ticket (pas de dev direct sur `develop`). - Réserve QA (socket Unix non bindable en sandbox, alternatives skips vertes) consignée dans les messages. Dis-moi quand le Lot B est cadré : je prépare la branche.
|
||||||
- **Prompt:** Préparation repo pour Lot B `workstate delegation/queue snapshot` après merge local Lot A dans `develop` tip `3bfb932`. Merci de créer/basculer sur une branche feature propre depuis develop, gérer tout dirty runtime éventuel sans perte, ne push pas, ne supprime pas branches obsolètes. Retourne branche courante + état.
|
- **Prompt:** Préparation repo pour Lot B `workstate delegation/queue snapshot` après merge local Lot A dans `develop` tip `3bfb932`. Merci de créer/basculer sur une branche feature propre depuis develop, gérer tout dirty runtime éventuel sans perte, ne push pas, ne supprime pas branches obsolètes. Retourne branche courante + état.
|
||||||
|
- **Response:** Repo prêt pour le Lot B « workstate delegation/queue snapshot ». ## Actions 1. Dirty runtime live (2 conversations `.ideai/conversations/*`, append-only) figé sur `develop` sans perte : - `7453181` — chore(wip): état runtime .ideai (flux conversation live). 2. Nouvelle branche feature créée depuis `develop` propre : - `git checkout -b feature/workstate-delegation-queue` (depuis `develop` @ `7453181`, = `3bfb932` + chore runtime). ## État final - **Branche courante : `feature/workstate-delegation-queue`** (tip `7453181`). - Base : `develop`, historique linéaire, working tree **propre**. - `develop` ahead origin/develop de **22** (local only). Aucune branche obsolète supprimée. **Aucun push.** DevBackend/DevFrontend peuvent implémenter le Lot B. Je reprends la main à l'implémentation terminée (tests verts) pour les commits atomiques (code séparé du runtime) et la décision de merge.
|
||||||
|
- **Prompt:** Lot B `workstate delegation/queue snapshot` est validé QA vert sur `feature/workstate-delegation-queue`. Merci de gérer les commits atomiques et la décision de merge local, sans push. Contexte: - Branche active attendue: `feature/workstate-delegation-queue`. - QA vert: - `cargo fmt --all -- --check` OK - `cargo test -p infrastructure mailbox --lib` OK (13 passed) - `cargo test -p application --test workstate` OK (12 passed) - `cargo test -p app-tauri --test dto_agents` OK (20 passed) - `cargo check -p app-tauri` OK - `cd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx` OK (2 files / 17 tests) - `cd frontend && npx tsc --noEmit` OK - `cargo test -p domain mailbox --lib` OK (6 passed) - Backend files likely touched: `crates/domain/src/{lib.rs,mailbox.rs}`, `crates/infrastructure/src/mailbox/mod.rs`, `crates/application/src/{lib.rs,workstate/mod.rs}`, `crates/application/tests/workstate.rs`, `crates/app-tauri/src/{dto.rs,state.rs}`, `crates/app-tauri/tests/dto_agents.rs`. - Frontend files likely touched: `frontend/src/domain/index.ts`, `frontend/src/adapters/mock/index.ts`, `frontend/src/features/workstate/{ProjectWorkStatePanel.tsx,useProjectWorkState.ts,workstate.test.tsx}`. - Dirty runtime `.ideai/*` may be present from live conversation/layout. Isolate it as you judge appropriate (historically chore runtime commit acceptable), do not mix runtime with feature code. Décide: 1. commits atomiques (backend/Tauri, frontend, runtime si nécessaire), 2. merge local éventuel vers `develop`, 3. état final. Ne push pas, ne supprime pas de branche. Retourne commits créés, merge ou non, branche finale, status final.
|
||||||
@ -47,3 +47,5 @@
|
|||||||
{"id":"93ed87f6-8dc6-4ff8-85d9-81aa30d30bed","conversation":"6bc594e8-a37c-0dbd-1de6-6e3b73002cb4","atMs":1781971560322,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"QA verte pour `feature/conversation-ux-live-state` Lot A, réserve environnementale non bloquante socket Unix. Merci de committer localement de façon atomique puis décider le merge local vers `develop` selon les règles projet.\n\nChangements code attendus:\nBackend/Tauri: `crates/application/src/workstate/`, `crates/application/tests/workstate.rs`, exports terminal/lib, snapshots live, Tauri DTO/command/state/lib/tests.\nFrontend: domain/ports/adapters/mock/workState, feature workstate, ProjectsView/tests.\nRuntime dirty `.ideai/*` aussi présent suite aux conversations live.\n\nStratégie demandée: séparer code backend/frontend si pertinent, séparer runtime `.ideai` en chore wip si tu le gardes, ne pas push, ne pas supprimer les branches obsolètes. Après commit, décide toi-même merge ou différer; si merge, fais-le localement. Retourne hashes, messages, fichiers inclus/exclus, branche finale et status."}
|
{"id":"93ed87f6-8dc6-4ff8-85d9-81aa30d30bed","conversation":"6bc594e8-a37c-0dbd-1de6-6e3b73002cb4","atMs":1781971560322,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"QA verte pour `feature/conversation-ux-live-state` Lot A, réserve environnementale non bloquante socket Unix. Merci de committer localement de façon atomique puis décider le merge local vers `develop` selon les règles projet.\n\nChangements code attendus:\nBackend/Tauri: `crates/application/src/workstate/`, `crates/application/tests/workstate.rs`, exports terminal/lib, snapshots live, Tauri DTO/command/state/lib/tests.\nFrontend: domain/ports/adapters/mock/workState, feature workstate, ProjectsView/tests.\nRuntime dirty `.ideai/*` aussi présent suite aux conversations live.\n\nStratégie demandée: séparer code backend/frontend si pertinent, séparer runtime `.ideai` en chore wip si tu le gardes, ne pas push, ne pas supprimer les branches obsolètes. Après commit, décide toi-même merge ou différer; si merge, fais-le localement. Retourne hashes, messages, fichiers inclus/exclus, branche finale et status."}
|
||||||
{"id":"d93a5849-41e6-4249-9dbf-9691c760c64b","conversation":"6bc594e8-a37c-0dbd-1de6-6e3b73002cb4","atMs":1781971634198,"source":{"kind":"agent","agentId":"cd0b4cf1-1bef-4fae-ade5-f0a6b49bbaf5"},"role":"response","text":"Lot A committé atomiquement (code backend / frontend séparés + runtime à part) **et mergé** dans `develop`. Aucun push, aucune branche supprimée.\n\n## Commits (sur la feature)\n**1. `aae1849` — feat(workstate): read-model live-state minimal (Lot A backend)**\n- Inclus : `crates/application/src/workstate/mod.rs` (nouv.), `crates/application/tests/workstate.rs` (nouv.), `crates/application/src/lib.rs`, `crates/application/src/terminal/{mod,registry}.rs`, `crates/app-tauri/src/{commands,dto,lib,state}.rs`, `crates/app-tauri/tests/dto_agents.rs`\n\n**2. `17685a0` — feat(workstate): UI live-state (Lot A frontend)**\n- Inclus : `frontend/src/adapters/{index,workState}.ts` (workState nouv.), `frontend/src/adapters/mock/index.ts`, `frontend/src/domain/index.ts`, `frontend/src/ports/index.ts`, `frontend/src/features/projects/{ProjectsView.tsx,projects.test.tsx}`, `frontend/src/features/workstate/` (nouv. : index.ts, ProjectWorkStatePanel.tsx, useProjectWorkState.ts, workstate.test.tsx)\n\n**3. `a06328a` — chore(wip): état runtime .ideai (conversations live, agents, layouts)**\n- Inclus : `.ideai/agents.json`, `.ideai/layouts.json`, `.ideai/conversations/*` (handoff + log.jsonl ×6)\n\n## Exclus / hors commit\nRien laissé de côté : working tree **propre**. Le runtime `.ideai` est gardé (cohérent avec l'historique) mais isolé dans le chore — jamais mélangé au code.\n\n## Décision merge : MERGÉ\nLot A est vert, atomique et autonome (un read-model + son UI) → je l'intègre tout de suite pour limiter la dérive de `develop`, plutôt que d'attendre les lots suivants. Pas de rebase nécessaire (develop ancêtre direct).\n- Merge `--no-ff` : **`3bfb932`** — merge(workstate): intègre le read-model live-state + UX conversations (Lot A) dans develop. Sans conflit (ort), 35 fichiers / +1146 -54.\n\n## État final\n- **Branche courante : `develop`** (tip `3bfb932`).\n- `develop` : **ahead origin/develop de 21** (local only, aucun push — conforme).\n- Working tree **propre**.\n- Branche `feature/conversation-ux-live-state` **conservée** (tip `a06328a`, intégrée). Disponible pour les lots suivants ; pour le **Lot B**, je rebascule/réaligne une branche feature depuis `develop` au prochain ticket (pas de dev direct sur `develop`).\n- Réserve QA (socket Unix non bindable en sandbox, alternatives skips vertes) consignée dans les messages.\n\nDis-moi quand le Lot B est cadré : je prépare la branche."}
|
{"id":"d93a5849-41e6-4249-9dbf-9691c760c64b","conversation":"6bc594e8-a37c-0dbd-1de6-6e3b73002cb4","atMs":1781971634198,"source":{"kind":"agent","agentId":"cd0b4cf1-1bef-4fae-ade5-f0a6b49bbaf5"},"role":"response","text":"Lot A committé atomiquement (code backend / frontend séparés + runtime à part) **et mergé** dans `develop`. Aucun push, aucune branche supprimée.\n\n## Commits (sur la feature)\n**1. `aae1849` — feat(workstate): read-model live-state minimal (Lot A backend)**\n- Inclus : `crates/application/src/workstate/mod.rs` (nouv.), `crates/application/tests/workstate.rs` (nouv.), `crates/application/src/lib.rs`, `crates/application/src/terminal/{mod,registry}.rs`, `crates/app-tauri/src/{commands,dto,lib,state}.rs`, `crates/app-tauri/tests/dto_agents.rs`\n\n**2. `17685a0` — feat(workstate): UI live-state (Lot A frontend)**\n- Inclus : `frontend/src/adapters/{index,workState}.ts` (workState nouv.), `frontend/src/adapters/mock/index.ts`, `frontend/src/domain/index.ts`, `frontend/src/ports/index.ts`, `frontend/src/features/projects/{ProjectsView.tsx,projects.test.tsx}`, `frontend/src/features/workstate/` (nouv. : index.ts, ProjectWorkStatePanel.tsx, useProjectWorkState.ts, workstate.test.tsx)\n\n**3. `a06328a` — chore(wip): état runtime .ideai (conversations live, agents, layouts)**\n- Inclus : `.ideai/agents.json`, `.ideai/layouts.json`, `.ideai/conversations/*` (handoff + log.jsonl ×6)\n\n## Exclus / hors commit\nRien laissé de côté : working tree **propre**. Le runtime `.ideai` est gardé (cohérent avec l'historique) mais isolé dans le chore — jamais mélangé au code.\n\n## Décision merge : MERGÉ\nLot A est vert, atomique et autonome (un read-model + son UI) → je l'intègre tout de suite pour limiter la dérive de `develop`, plutôt que d'attendre les lots suivants. Pas de rebase nécessaire (develop ancêtre direct).\n- Merge `--no-ff` : **`3bfb932`** — merge(workstate): intègre le read-model live-state + UX conversations (Lot A) dans develop. Sans conflit (ort), 35 fichiers / +1146 -54.\n\n## État final\n- **Branche courante : `develop`** (tip `3bfb932`).\n- `develop` : **ahead origin/develop de 21** (local only, aucun push — conforme).\n- Working tree **propre**.\n- Branche `feature/conversation-ux-live-state` **conservée** (tip `a06328a`, intégrée). Disponible pour les lots suivants ; pour le **Lot B**, je rebascule/réaligne une branche feature depuis `develop` au prochain ticket (pas de dev direct sur `develop`).\n- Réserve QA (socket Unix non bindable en sandbox, alternatives skips vertes) consignée dans les messages.\n\nDis-moi quand le Lot B est cadré : je prépare la branche."}
|
||||||
{"id":"95e2a7a2-bb92-474f-83ec-0a2a7bab2c3e","conversation":"6bc594e8-a37c-0dbd-1de6-6e3b73002cb4","atMs":1781971838757,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Préparation repo pour Lot B `workstate delegation/queue snapshot` après merge local Lot A dans `develop` tip `3bfb932`. Merci de créer/basculer sur une branche feature propre depuis develop, gérer tout dirty runtime éventuel sans perte, ne push pas, ne supprime pas branches obsolètes. Retourne branche courante + état."}
|
{"id":"95e2a7a2-bb92-474f-83ec-0a2a7bab2c3e","conversation":"6bc594e8-a37c-0dbd-1de6-6e3b73002cb4","atMs":1781971838757,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Préparation repo pour Lot B `workstate delegation/queue snapshot` après merge local Lot A dans `develop` tip `3bfb932`. Merci de créer/basculer sur une branche feature propre depuis develop, gérer tout dirty runtime éventuel sans perte, ne push pas, ne supprime pas branches obsolètes. Retourne branche courante + état."}
|
||||||
|
{"id":"9654549c-fd9d-4661-8dc2-e6d2a78eab7a","conversation":"6bc594e8-a37c-0dbd-1de6-6e3b73002cb4","atMs":1781971855209,"source":{"kind":"agent","agentId":"cd0b4cf1-1bef-4fae-ade5-f0a6b49bbaf5"},"role":"response","text":"Repo prêt pour le Lot B « workstate delegation/queue snapshot ».\n\n## Actions\n1. Dirty runtime live (2 conversations `.ideai/conversations/*`, append-only) figé sur `develop` sans perte :\n - `7453181` — chore(wip): état runtime .ideai (flux conversation live).\n2. Nouvelle branche feature créée depuis `develop` propre :\n - `git checkout -b feature/workstate-delegation-queue` (depuis `develop` @ `7453181`, = `3bfb932` + chore runtime).\n\n## État final\n- **Branche courante : `feature/workstate-delegation-queue`** (tip `7453181`).\n- Base : `develop`, historique linéaire, working tree **propre**.\n- `develop` ahead origin/develop de **22** (local only). Aucune branche obsolète supprimée. **Aucun push.**\n\nDevBackend/DevFrontend peuvent implémenter le Lot B. Je reprends la main à l'implémentation terminée (tests verts) pour les commits atomiques (code séparé du runtime) et la décision de merge."}
|
||||||
|
{"id":"53773ad0-5bb6-4e24-8456-d7ab17be3db0","conversation":"6bc594e8-a37c-0dbd-1de6-6e3b73002cb4","atMs":1781974288762,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Lot B `workstate delegation/queue snapshot` est validé QA vert sur `feature/workstate-delegation-queue`. Merci de gérer les commits atomiques et la décision de merge local, sans push.\n\nContexte:\n- Branche active attendue: `feature/workstate-delegation-queue`.\n- QA vert:\n - `cargo fmt --all -- --check` OK\n - `cargo test -p infrastructure mailbox --lib` OK (13 passed)\n - `cargo test -p application --test workstate` OK (12 passed)\n - `cargo test -p app-tauri --test dto_agents` OK (20 passed)\n - `cargo check -p app-tauri` OK\n - `cd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx` OK (2 files / 17 tests)\n - `cd frontend && npx tsc --noEmit` OK\n - `cargo test -p domain mailbox --lib` OK (6 passed)\n- Backend files likely touched: `crates/domain/src/{lib.rs,mailbox.rs}`, `crates/infrastructure/src/mailbox/mod.rs`, `crates/application/src/{lib.rs,workstate/mod.rs}`, `crates/application/tests/workstate.rs`, `crates/app-tauri/src/{dto.rs,state.rs}`, `crates/app-tauri/tests/dto_agents.rs`.\n- Frontend files likely touched: `frontend/src/domain/index.ts`, `frontend/src/adapters/mock/index.ts`, `frontend/src/features/workstate/{ProjectWorkStatePanel.tsx,useProjectWorkState.ts,workstate.test.tsx}`.\n- Dirty runtime `.ideai/*` may be present from live conversation/layout. Isolate it as you judge appropriate (historically chore runtime commit acceptable), do not mix runtime with feature code.\n\nDécide:\n1. commits atomiques (backend/Tauri, frontend, runtime si nécessaire),\n2. merge local éventuel vers `develop`,\n3. état final.\n\nNe push pas, ne supprime pas de branche. Retourne commits créés, merge ou non, branche finale, status final."}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -69,3 +69,6 @@
|
|||||||
{"id":"00aa406e-b715-4f9b-9978-f52b9a37e27f","conversation":"d5068bc8-786e-0428-1d14-83b93961a15e","atMs":1781945759965,"source":{"kind":"agent","agentId":"73c853d1-c0fd-463b-ad17-1d24fefa371f"},"role":"response","text":"Diagnostic : blocage environnement/sandbox, pas régression applicative liée au MVP `agent-skill-awareness-v2` ni aux changements récents.\n\nConstats :\n- `cargo test -p app-tauri bind_endpoint -- --nocapture` échoue exactement comme QA : `lay corpse socket: Os { code: 1, kind: PermissionDenied, message: \"Operation not permitted\" }` sur `std::os::unix::net::UnixListener::bind`.\n- `cargo test -p app-tauri mcp_e2e_loopback_tests -- --nocapture` échoue car `bind_endpoint` retourne `None` pour `/run/user/1000/idea-mcp/<project>.sock`.\n- Même en forçant `XDG_RUNTIME_DIR=/tmp`, les tests loopback échouent : `bind_endpoint returned None for \"/tmp/idea-mcp/<project>.sock\"`.\n- Smoke test Rust minimal hors repo, compilé dans `/tmp`, confirme que le sandbox interdit `AF_UNIX` path sockets partout :\n - `/tmp/idea-socket-smoke-*.sock` => `PermissionDenied / Operation not permitted`\n - `/run/user/1000/idea-socket-smoke-*.sock` => `PermissionDenied / Operation not permitted`\n\nInterprétation :\n- Les tests qui échouent nécessitent un vrai listener Unix domain socket. L’échec se produit avant la logique IdeA, au niveau syscall de bind socket.\n- Le code récent `agent-skill-awareness-v2` ne touche pas `app-tauri`, `state.rs`, `mcp_endpoint.rs`, `mcp_bridge.rs`, ni la dérivation/binding des endpoints.\n- `mcp_endpoint` choisit correctement `$XDG_RUNTIME_DIR` puis `/tmp`; le fait que `/tmp` échoue aussi exclut un simple problème de permissions de `/run/user/1000`.\n\nCommandes de vérification exécutées :\n- `cargo test -p app-tauri bind_endpoint -- --nocapture` => échoue `PermissionDenied`, reproduit QA.\n- `cargo test -p app-tauri mcp_e2e_loopback_tests -- --nocapture` => échoue `bind_endpoint returned None`, reproduit QA.\n- `XDG_RUNTIME_DIR=/tmp cargo test -p app-tauri bind_endpoint -- --nocapture` => échoue aussi `PermissionDenied`.\n- `XDG_RUNTIME_DIR=/tmp cargo test -p app-tauri mcp_e2e_loopback_tests -- --nocapture` => échoue aussi `bind_endpoint returned None`.\n- Smoke Rust minimal avec `UnixListener::bind` dans `/tmp` et `/run/user/1000` => échoue `PermissionDenied` dans les deux cas.\n\nCommandes alternatives vertes dans ce sandbox, en excluant uniquement les tests qui nécessitent un vrai socket Unix :\n```text\ncargo test -p app-tauri --test orchestrator_wiring -- --skip open_binds_the_project_loopback_endpoint --skip double_open_keeps_a_single_endpoint_no_address_in_use --skip close_cleans_up_the_endpoint_socket_file --skip file_watcher_and_loopback_endpoint_live_together\n```\nRésultat : OK, 9 passed, 4 filtered out.\n\n```text\ncargo test -p app-tauri --lib -- --skip state::mcp_e2e_loopback_tests --skip state::bind_endpoint_d1_tests --skip mcp_bridge::tests::end_to_end_over_real_loopback\n```\nRésultat : OK, 39 passed, 8 filtered out.\n\nAucune correction code/test appliquée. Je n’ai pas touché aux fichiers runtime `.ideai`."}
|
{"id":"00aa406e-b715-4f9b-9978-f52b9a37e27f","conversation":"d5068bc8-786e-0428-1d14-83b93961a15e","atMs":1781945759965,"source":{"kind":"agent","agentId":"73c853d1-c0fd-463b-ad17-1d24fefa371f"},"role":"response","text":"Diagnostic : blocage environnement/sandbox, pas régression applicative liée au MVP `agent-skill-awareness-v2` ni aux changements récents.\n\nConstats :\n- `cargo test -p app-tauri bind_endpoint -- --nocapture` échoue exactement comme QA : `lay corpse socket: Os { code: 1, kind: PermissionDenied, message: \"Operation not permitted\" }` sur `std::os::unix::net::UnixListener::bind`.\n- `cargo test -p app-tauri mcp_e2e_loopback_tests -- --nocapture` échoue car `bind_endpoint` retourne `None` pour `/run/user/1000/idea-mcp/<project>.sock`.\n- Même en forçant `XDG_RUNTIME_DIR=/tmp`, les tests loopback échouent : `bind_endpoint returned None for \"/tmp/idea-mcp/<project>.sock\"`.\n- Smoke test Rust minimal hors repo, compilé dans `/tmp`, confirme que le sandbox interdit `AF_UNIX` path sockets partout :\n - `/tmp/idea-socket-smoke-*.sock` => `PermissionDenied / Operation not permitted`\n - `/run/user/1000/idea-socket-smoke-*.sock` => `PermissionDenied / Operation not permitted`\n\nInterprétation :\n- Les tests qui échouent nécessitent un vrai listener Unix domain socket. L’échec se produit avant la logique IdeA, au niveau syscall de bind socket.\n- Le code récent `agent-skill-awareness-v2` ne touche pas `app-tauri`, `state.rs`, `mcp_endpoint.rs`, `mcp_bridge.rs`, ni la dérivation/binding des endpoints.\n- `mcp_endpoint` choisit correctement `$XDG_RUNTIME_DIR` puis `/tmp`; le fait que `/tmp` échoue aussi exclut un simple problème de permissions de `/run/user/1000`.\n\nCommandes de vérification exécutées :\n- `cargo test -p app-tauri bind_endpoint -- --nocapture` => échoue `PermissionDenied`, reproduit QA.\n- `cargo test -p app-tauri mcp_e2e_loopback_tests -- --nocapture` => échoue `bind_endpoint returned None`, reproduit QA.\n- `XDG_RUNTIME_DIR=/tmp cargo test -p app-tauri bind_endpoint -- --nocapture` => échoue aussi `PermissionDenied`.\n- `XDG_RUNTIME_DIR=/tmp cargo test -p app-tauri mcp_e2e_loopback_tests -- --nocapture` => échoue aussi `bind_endpoint returned None`.\n- Smoke Rust minimal avec `UnixListener::bind` dans `/tmp` et `/run/user/1000` => échoue `PermissionDenied` dans les deux cas.\n\nCommandes alternatives vertes dans ce sandbox, en excluant uniquement les tests qui nécessitent un vrai socket Unix :\n```text\ncargo test -p app-tauri --test orchestrator_wiring -- --skip open_binds_the_project_loopback_endpoint --skip double_open_keeps_a_single_endpoint_no_address_in_use --skip close_cleans_up_the_endpoint_socket_file --skip file_watcher_and_loopback_endpoint_live_together\n```\nRésultat : OK, 9 passed, 4 filtered out.\n\n```text\ncargo test -p app-tauri --lib -- --skip state::mcp_e2e_loopback_tests --skip state::bind_endpoint_d1_tests --skip mcp_bridge::tests::end_to_end_over_real_loopback\n```\nRésultat : OK, 39 passed, 8 filtered out.\n\nAucune correction code/test appliquée. Je n’ai pas touché aux fichiers runtime `.ideai`."}
|
||||||
{"id":"bd1d1082-e11d-4754-a83f-ba89233b25e4","conversation":"d5068bc8-786e-0428-1d14-83b93961a15e","atMs":1781946742079,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Implémente le Lot A backend/Tauri du chantier `UX conversations/délégations + read-model live-state minimal` sur la branche `feature/conversation-ux-live-state`. Respecte le cadrage Architect: read-only, sans nouvelle persistance durable, pas de lecture log/handoff, pas de FIFO complète.\n\nObjectif backend:\n- Ajouter un read-model `ProjectWorkState` qui liste les agents du manifeste avec leur état live et busy.\n- Agréger depuis les stores/registres existants: manifeste agents via context store, `LiveSessions`/registries existants, `InputMediator::busy_state`.\n- Conserver l'ordre manifeste.\n- Ignorer les agents live absents du manifeste.\n- Exposer une commande Tauri `get_project_work_state(projectId)`.\n\nContrats recommandés par Architect:\n- `ProjectWorkState { agents: Vec<AgentWorkState> }`\n- `AgentWorkState { agent_id, name, profile_id, live: Option<LiveWorkSession>, busy }`\n- `LiveWorkSession { node_id, session_id, kind: Pty|Structured }` si raisonnable; sinon explique pourquoi tu omets `kind`.\n- Busy DTO camelCase/tag `state`: `idle` ou `busy { ticket, sinceMs }` selon les types existants.\n\nFichiers probables: `crates/application/src/workstate/mod.rs`, `crates/application/src/lib.rs`, peut-être `crates/application/src/terminal/registry.rs`; Tauri `dto.rs`, `commands.rs`, `state.rs`, `lib.rs`.\n\nTests attendus:\n- tests application `workstate`: aucun live/busy; live PTY ou structured; busy; agent live hors manifeste ignoré.\n- tests DTO/commande si conventions existantes.\n- Exécute au minimum `cargo test -p application workstate`, `cargo check -p app-tauri`, et les tests app-tauri pertinents que tu peux lancer dans ce sandbox. Ne committe pas. Réponds avec fichiers modifiés, commandes/résultats, et risques."}
|
{"id":"bd1d1082-e11d-4754-a83f-ba89233b25e4","conversation":"d5068bc8-786e-0428-1d14-83b93961a15e","atMs":1781946742079,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Implémente le Lot A backend/Tauri du chantier `UX conversations/délégations + read-model live-state minimal` sur la branche `feature/conversation-ux-live-state`. Respecte le cadrage Architect: read-only, sans nouvelle persistance durable, pas de lecture log/handoff, pas de FIFO complète.\n\nObjectif backend:\n- Ajouter un read-model `ProjectWorkState` qui liste les agents du manifeste avec leur état live et busy.\n- Agréger depuis les stores/registres existants: manifeste agents via context store, `LiveSessions`/registries existants, `InputMediator::busy_state`.\n- Conserver l'ordre manifeste.\n- Ignorer les agents live absents du manifeste.\n- Exposer une commande Tauri `get_project_work_state(projectId)`.\n\nContrats recommandés par Architect:\n- `ProjectWorkState { agents: Vec<AgentWorkState> }`\n- `AgentWorkState { agent_id, name, profile_id, live: Option<LiveWorkSession>, busy }`\n- `LiveWorkSession { node_id, session_id, kind: Pty|Structured }` si raisonnable; sinon explique pourquoi tu omets `kind`.\n- Busy DTO camelCase/tag `state`: `idle` ou `busy { ticket, sinceMs }` selon les types existants.\n\nFichiers probables: `crates/application/src/workstate/mod.rs`, `crates/application/src/lib.rs`, peut-être `crates/application/src/terminal/registry.rs`; Tauri `dto.rs`, `commands.rs`, `state.rs`, `lib.rs`.\n\nTests attendus:\n- tests application `workstate`: aucun live/busy; live PTY ou structured; busy; agent live hors manifeste ignoré.\n- tests DTO/commande si conventions existantes.\n- Exécute au minimum `cargo test -p application workstate`, `cargo check -p app-tauri`, et les tests app-tauri pertinents que tu peux lancer dans ce sandbox. Ne committe pas. Réponds avec fichiers modifiés, commandes/résultats, et risques."}
|
||||||
{"id":"5d78a64e-ce73-4fca-b62d-7af302dbac90","conversation":"d5068bc8-786e-0428-1d14-83b93961a15e","atMs":1781971233449,"source":{"kind":"agent","agentId":"73c853d1-c0fd-463b-ad17-1d24fefa371f"},"role":"response","text":"Lot A backend/Tauri implémenté sur `feature/conversation-ux-live-state`, sans commit.\n\nFichiers modifiés pour le lot :\n- `crates/application/src/workstate/mod.rs` (nouveau) : read-model `GetProjectWorkState`, `ProjectWorkState`, `AgentWorkState`, `LiveWorkSession`.\n- `crates/application/src/lib.rs` : exports du module workstate et des snapshots live typés.\n- `crates/application/src/terminal/mod.rs` : exports `LiveSessionKind` / `LiveSessionSnapshot`.\n- `crates/application/src/terminal/registry.rs` : ajout de `LiveSessionKind`, `LiveSessionSnapshot`, `LiveSessions::live_agent_snapshots()` ; `live_agents()` existant inchangé.\n- `crates/application/tests/workstate.rs` (nouveau) : tests du read-model.\n- `crates/app-tauri/src/state.rs` : construction/câblage du use case dans `AppState` avec `AgentContextStore`, `LiveSessions`, `InputMediator`.\n- `crates/app-tauri/src/commands.rs` : commande `get_project_work_state(projectId)`.\n- `crates/app-tauri/src/dto.rs` : DTO `ProjectWorkStateDto`, `AgentWorkStateDto`, `LiveWorkSessionDto`, `LiveWorkSessionKindDto` ; `busy` réutilise `domain::AgentBusyState` (`state: idle|busy`, `sinceMs`).\n- `crates/app-tauri/src/lib.rs` : enregistrement Tauri command.\n- `crates/app-tauri/tests/dto_agents.rs` : test DTO camelCase/live/busy.\n\nComportement :\n- Read-only, aucune nouvelle persistance durable.\n- Aucune lecture log/handoff, aucune FIFO complète exposée.\n- Agents listés dans l’ordre du manifeste.\n- Agents live absents du manifeste ignorés.\n- Live session expose `nodeId`, `sessionId`, `kind: pty|structured`.\n\nCommandes exécutées côté DevBackend :\n- `cargo fmt -p application -p app-tauri` : OK.\n- `cargo test -p application workstate` : OK, 5 tests workstate passés.\n- `cargo check -p app-tauri` : OK.\n- `cargo test -p app-tauri --test dto_agents project_work_state -- --nocapture` : OK, 1 passed.\n- `cargo test -p app-tauri --test dto_agents` : OK, 19 passed.\n- `cargo test -p app-tauri --test list_live_agents_r0b` : OK, 5 passed.\n- `cargo test -p app-tauri --lib -- --skip state::mcp_e2e_loopback_tests --skip state::bind_endpoint_d1_tests --skip mcp_bridge::tests::end_to_end_over_real_loopback` : OK, 39 passed, 8 filtered out.\n- `cargo test -p application` : OK, application complète verte. Warning existant/non lié : `CapturingFs::writes` unused dans `crates/application/tests/orchestrator_service.rs:1642`.\n\nValidation QA : vert. QA a relu le périmètre, confirmé read-only / ordre manifeste / absence log-handoff-persistance, et réexécuté les commandes clés avec succès. Même réserve non bloquante que précédemment : les 8 tests app-tauri filtrés exigent un vrai socket Unix loopback, bloqué dans ce sandbox par `UnixListener::bind` `PermissionDenied`; à exécuter hors sandbox permissif socket Unix.\n\nRisques / notes :\n- `kind` est inclus car l’agrégateur sait distinguer PTY vs structured via les deux registres.\n- Le read-model retourne un instantané live/busy au moment de l’appel ; pas de garantie transactionnelle entre registre live et busy state, ce qui est cohérent avec un read-model live minimal.\n- `git status` montre aussi des fichiers `.ideai/*` runtime modifiés par l’orchestration/conversation ; je ne les ai pas édités pour ce lot et ils ne font pas partie du livrable."}
|
{"id":"5d78a64e-ce73-4fca-b62d-7af302dbac90","conversation":"d5068bc8-786e-0428-1d14-83b93961a15e","atMs":1781971233449,"source":{"kind":"agent","agentId":"73c853d1-c0fd-463b-ad17-1d24fefa371f"},"role":"response","text":"Lot A backend/Tauri implémenté sur `feature/conversation-ux-live-state`, sans commit.\n\nFichiers modifiés pour le lot :\n- `crates/application/src/workstate/mod.rs` (nouveau) : read-model `GetProjectWorkState`, `ProjectWorkState`, `AgentWorkState`, `LiveWorkSession`.\n- `crates/application/src/lib.rs` : exports du module workstate et des snapshots live typés.\n- `crates/application/src/terminal/mod.rs` : exports `LiveSessionKind` / `LiveSessionSnapshot`.\n- `crates/application/src/terminal/registry.rs` : ajout de `LiveSessionKind`, `LiveSessionSnapshot`, `LiveSessions::live_agent_snapshots()` ; `live_agents()` existant inchangé.\n- `crates/application/tests/workstate.rs` (nouveau) : tests du read-model.\n- `crates/app-tauri/src/state.rs` : construction/câblage du use case dans `AppState` avec `AgentContextStore`, `LiveSessions`, `InputMediator`.\n- `crates/app-tauri/src/commands.rs` : commande `get_project_work_state(projectId)`.\n- `crates/app-tauri/src/dto.rs` : DTO `ProjectWorkStateDto`, `AgentWorkStateDto`, `LiveWorkSessionDto`, `LiveWorkSessionKindDto` ; `busy` réutilise `domain::AgentBusyState` (`state: idle|busy`, `sinceMs`).\n- `crates/app-tauri/src/lib.rs` : enregistrement Tauri command.\n- `crates/app-tauri/tests/dto_agents.rs` : test DTO camelCase/live/busy.\n\nComportement :\n- Read-only, aucune nouvelle persistance durable.\n- Aucune lecture log/handoff, aucune FIFO complète exposée.\n- Agents listés dans l’ordre du manifeste.\n- Agents live absents du manifeste ignorés.\n- Live session expose `nodeId`, `sessionId`, `kind: pty|structured`.\n\nCommandes exécutées côté DevBackend :\n- `cargo fmt -p application -p app-tauri` : OK.\n- `cargo test -p application workstate` : OK, 5 tests workstate passés.\n- `cargo check -p app-tauri` : OK.\n- `cargo test -p app-tauri --test dto_agents project_work_state -- --nocapture` : OK, 1 passed.\n- `cargo test -p app-tauri --test dto_agents` : OK, 19 passed.\n- `cargo test -p app-tauri --test list_live_agents_r0b` : OK, 5 passed.\n- `cargo test -p app-tauri --lib -- --skip state::mcp_e2e_loopback_tests --skip state::bind_endpoint_d1_tests --skip mcp_bridge::tests::end_to_end_over_real_loopback` : OK, 39 passed, 8 filtered out.\n- `cargo test -p application` : OK, application complète verte. Warning existant/non lié : `CapturingFs::writes` unused dans `crates/application/tests/orchestrator_service.rs:1642`.\n\nValidation QA : vert. QA a relu le périmètre, confirmé read-only / ordre manifeste / absence log-handoff-persistance, et réexécuté les commandes clés avec succès. Même réserve non bloquante que précédemment : les 8 tests app-tauri filtrés exigent un vrai socket Unix loopback, bloqué dans ce sandbox par `UnixListener::bind` `PermissionDenied`; à exécuter hors sandbox permissif socket Unix.\n\nRisques / notes :\n- `kind` est inclus car l’agrégateur sait distinguer PTY vs structured via les deux registres.\n- Le read-model retourne un instantané live/busy au moment de l’appel ; pas de garantie transactionnelle entre registre live et busy state, ce qui est cohérent avec un read-model live minimal.\n- `git status` montre aussi des fichiers `.ideai/*` runtime modifiés par l’orchestration/conversation ; je ne les ai pas édités pour ce lot et ils ne font pas partie du livrable."}
|
||||||
|
{"id":"7b0a1b7a-bfad-4d97-89f8-f4bb9b32740d","conversation":"d5068bc8-786e-0428-1d14-83b93961a15e","atMs":1781971869030,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Implémente le Lot B backend/Tauri `workstate delegation/queue snapshot` sur la branche `feature/workstate-delegation-queue`. Respecte le cadrage Architect.\n\nObjectif:\n- Ajouter un port read-only `AgentQueueSnapshot` dans `domain/src/mailbox.rs` avec snapshots clonés des tickets, sans exposer ni cloner les senders.\n- Implémenter ce port pour `InMemoryMailbox` en conservant l'ordre FIFO et positions recalculées.\n- Étendre `GetProjectWorkState` pour inclure `agents[].tickets`, en croisant snapshot queue + `InputMediator::busy_state` pour dériver `inProgress` vs `queued`.\n- Conserver manifest boundary: tickets d'agents absents du manifeste ignorés.\n- Générer `taskPreview` côté application: trim, espaces normalisés, coupure ~160 caractères; conserver `taskLen` original utile.\n- Exposer DTO Tauri camelCase dans la commande existante `get_project_work_state`.\n\nDécision de périmètre: afficher les tickets `source == Agent` en priorité pour délégations; si tu inclus aussi human, explique et teste le libellé/source. Ne change pas la sémantique enqueue/resolve/cancel. Pas de nouvelle persistance, pas lecture log/handoff, pas événement queue changé.\n\nTests attendus:\n- infrastructure mailbox snapshot: ordre/metadata/read-only/update after resolve/cancel.\n- application workstate: queue vide, deux tickets ordonnés, busy first inProgress, idle queued, absent manifest ignored, preview truncation, source mapping.\n- app-tauri DTO serialization tickets.\n\nCommandes: `cargo fmt`, `cargo test -p infrastructure mailbox --lib`, `cargo test -p application --test workstate`, `cargo test -p app-tauri --test dto_agents`, `cargo check -p app-tauri`, et autres pertinentes. Ne committe pas. Ignore les fichiers runtime `.ideai/*` dirty, ne les modifie pas volontairement."}
|
||||||
|
{"id":"2c25d734-281e-4f2a-850e-684fec6e0ba7","conversation":"d5068bc8-786e-0428-1d14-83b93961a15e","atMs":1781973784611,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Reprise orchestration Main. Je retrouve la branche `feature/workstate-delegation-queue` avec des modifications backend/Tauri non committées pour le Lot B `workstate delegation/queue snapshot` (ticket mentionné par l'utilisateur: dbca904a-2843-4f41-9926-d13b7804c70f). Peux-tu confirmer ton état exact: périmètre implémenté, choix notables (notamment tickets human inclus ou exclus), commandes exécutées et résultats réels, et ce qu'il reste selon toi ? Ne modifie rien, ne committe pas."}
|
||||||
|
{"id":"b0d07b39-565c-4d30-a9ae-0c3721338aeb","conversation":"d5068bc8-786e-0428-1d14-83b93961a15e","atMs":1781973900817,"source":{"kind":"agent","agentId":"73c853d1-c0fd-463b-ad17-1d24fefa371f"},"role":"response","text":"État DevBackend — branche `feature/workstate-delegation-queue`, Lot B « workstate delegation/queue snapshot » (ticket dbca904a). **Rien committé, rien modifié pendant ce diagnostic.**\n\n## Périmètre implémenté\nExtension du read-model live-state (Lot A) pour exposer, **par agent**, ses tickets de délégation en file FIFO. 4 couches touchées (sens hexagonal respecté) :\n\n- **domain/mailbox.rs** : nouveau value object `QueuedTicketSnapshot` (données du ticket + `position`, 0 = tête ; **jamais** le `oneshot::Sender`) + nouveau **port lecture seule** `AgentQueueSnapshot` (`queue_for(agent) -> Vec<QueuedTicketSnapshot>`), **ségrégé (ISP)** du port mutant `AgentMailbox`. Re-export depuis `domain/lib.rs`.\n- **infrastructure/mailbox/mod.rs** : `impl AgentQueueSnapshot for InMemoryMailbox` — clone les données sous le lock, recalcule la position depuis l'ordre courant, lecture pure (n'altère pas la file).\n- **application/workstate/mod.rs** : nouveaux `AgentTicketState`, `TicketWorkStatus` (InProgress/Queued), `TicketWorkSource` (Human/Agent), `task_preview` (160 car., espaces normalisés) + `task_len`. `GetProjectWorkState` gagne une dépendance `queue: Arc<dyn AgentQueueSnapshot>` ; champ `AgentWorkState.tickets` peuplé.\n- **app-tauri/dto.rs** : `AgentTicketStateDto`, `TicketWorkStatusDto`, `TicketWorkSourceDto` (camelCase, enum source taggée `kind`), champ `AgentWorkStateDto.tickets`.\n- **app-tauri/state.rs** : composition root — même `InMemoryMailbox` concret partagé en **deux vues Arc** (`AgentMailbox` mutant + `AgentQueueSnapshot` lecture), injecté dans `GetProjectWorkState`.\n\n## Choix notables\n- **Tickets human INCLUS** (pas de filtrage par source). `TicketWorkSource::Human`/`Agent{agent_id}` mappé dans les deux sens, couvert par le test `workstate_maps_human_and_agent_ticket_sources`. La file liste tous les tickets quelle que soit l'origine.\n- **Statut dérivé, jamais stocké** : InProgress = ticket dont l'id == ticket busy courant de l'agent ; les autres Queued ; agent idle → tout Queued.\n- **ISP** : port lecture distinct plutôt que d'élargir `AgentMailbox` ; observer la file ne la mute jamais (test dédié).\n- **task_preview borné** à 160 car., texte intégral jamais envoyé à l'UI, longueur réelle via `task_len`.\n- **Frontière manifest préservée** : seuls les agents du manifest reçoivent des tickets ; file d'un agent absent du manifest ignorée (test dédié).\n\n## Commandes exécutées (à l'instant) + résultats réels\n`cargo test -p domain -p application -p infrastructure -p app-tauri` → **tout vert, 0 échec**.\n- domain : 176 unit (+1 nouveau `queued_ticket_snapshot_…`)\n- infrastructure : 228 unit (+6 nouveaux `snapshot_*` : empty/fifo-order/metadata/read-only/après resolve/après cancel)\n- application : 43 unit + `workstate.rs` **12** (+7 nouveaux : sans-queue, fifo, busy→in-progress, idle→tout-queued, agent-hors-manifest, troncature preview, sources human+agent)\n- app-tauri : lib 47 + `dto_agents.rs` (+1 `project_work_state_dto_serialises_tickets_camelcase`)\nAucun warning bloquant.\n\n## Ce qu'il reste (selon moi)\n1. **Frontend (périmètre DevFrontend)** : aucune consommation TS/React des nouveaux champs DTO `tickets` — rendu du panneau de délégations non fait.\n2. **Commit/merge (périmètre Git)** : 14 fichiers non commités ; le code backend est prêt à committer. À noter : les fichiers `.ideai/` modifiés (conversations/handoff/log/layouts) sont du **bruit runtime**, pas la feature.\n3. **Validation e2e/AppImage** : non faite (binaire qui tourne = AppImage, pas les sources).\n4. **Cadrage Architect** : si tu veux une validation formelle du nouveau port `AgentQueueSnapshot` dans `architect.md`, je l'ai conçu en cohérence ISP mais ce n'est pas tranché dans la cartographie.\n\nEn résumé : backend Lot B **complet et vert** au niveau tests unitaires/intégration ; restent le front, le commit Git et la validation e2e."}
|
||||||
|
|||||||
@ -18,8 +18,9 @@
|
|||||||
"type": "leaf",
|
"type": "leaf",
|
||||||
"node": {
|
"node": {
|
||||||
"id": "d4b8c0d1-a44a-4c45-bbe9-26991f79b465",
|
"id": "d4b8c0d1-a44a-4c45-bbe9-26991f79b465",
|
||||||
"session": "d5e00a5c-591e-4061-9104-c9ea7c9e01c3",
|
"session": "813f17c2-d056-4875-b9a0-3c18e4e40776",
|
||||||
"agent": "a6ced819-b893-4213-b003-9e9dc79b9641"
|
"agent": "a6ced819-b893-4213-b003-9e9dc79b9641",
|
||||||
|
"agentWasRunning": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"weight": 1.0
|
"weight": 1.0
|
||||||
@ -36,8 +37,9 @@
|
|||||||
"type": "leaf",
|
"type": "leaf",
|
||||||
"node": {
|
"node": {
|
||||||
"id": "71564af2-673a-46c7-a0c0-877f89f6e49e",
|
"id": "71564af2-673a-46c7-a0c0-877f89f6e49e",
|
||||||
"session": "cadc1432-ccc3-4849-94bb-5cf342a85fe1",
|
"session": "384778a6-216f-47ed-90f0-67de6812b59d",
|
||||||
"agent": "aefdbd61-e3d4-4bc1-9f42-c259446a97b5"
|
"agent": "73c853d1-c0fd-463b-ad17-1d24fefa371f",
|
||||||
|
"agentWasRunning": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"weight": 1.0
|
"weight": 1.0
|
||||||
@ -47,7 +49,9 @@
|
|||||||
"type": "leaf",
|
"type": "leaf",
|
||||||
"node": {
|
"node": {
|
||||||
"id": "8529e97f-ce06-490c-b3d0-531c3dfee442",
|
"id": "8529e97f-ce06-490c-b3d0-531c3dfee442",
|
||||||
"session": "b7e0ab21-25bf-4158-8fcc-e2c67d49868e"
|
"session": "fac01ad4-0e99-4b2a-bdec-621493cf9c57",
|
||||||
|
"agent": "cd0b4cf1-1bef-4fae-ade5-f0a6b49bbaf5",
|
||||||
|
"agentWasRunning": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"weight": 1.0
|
"weight": 1.0
|
||||||
@ -61,6 +65,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "74489208-01a7-4005-aba4-b9c41ab843fd",
|
||||||
|
"name": "Git Graph",
|
||||||
|
"kind": "gitGraph",
|
||||||
|
"tree": {
|
||||||
|
"root": {
|
||||||
|
"type": "leaf",
|
||||||
|
"node": {
|
||||||
|
"id": "2396a80e-bc98-47cd-b711-c7cff0010bb6"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,8 +9,9 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use application::{
|
use application::{
|
||||||
AppError, CreateProjectInput, CreateProjectOutput, GitGraphOutput, HealthInput, HealthReport,
|
AgentTicketState, AppError, CreateProjectInput, CreateProjectOutput, GitGraphOutput,
|
||||||
LayoutKind, ListProjectsOutput, LiveSessionKind, OpenProjectOutput, ProjectWorkState,
|
HealthInput, HealthReport, LayoutKind, ListProjectsOutput, LiveSessionKind, OpenProjectOutput,
|
||||||
|
ProjectWorkState, TicketWorkSource, TicketWorkStatus,
|
||||||
};
|
};
|
||||||
use domain::{AgentBusyState, Project, ProjectId};
|
use domain::{AgentBusyState, Project, ProjectId};
|
||||||
|
|
||||||
@ -1503,6 +1504,87 @@ pub struct LiveWorkSessionDto {
|
|||||||
pub kind: LiveWorkSessionKindDto,
|
pub kind: LiveWorkSessionKindDto,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Derived processing status of a queued ticket.
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub enum TicketWorkStatusDto {
|
||||||
|
/// The agent's current busy turn is running this ticket.
|
||||||
|
InProgress,
|
||||||
|
/// Waiting behind the head / the agent is idle.
|
||||||
|
Queued,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TicketWorkStatus> for TicketWorkStatusDto {
|
||||||
|
fn from(status: TicketWorkStatus) -> Self {
|
||||||
|
match status {
|
||||||
|
TicketWorkStatus::InProgress => Self::InProgress,
|
||||||
|
TicketWorkStatus::Queued => Self::Queued,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Origin of a queued ticket (human operator or a delegating agent).
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase", tag = "kind")]
|
||||||
|
pub enum TicketWorkSourceDto {
|
||||||
|
/// The human operator.
|
||||||
|
Human,
|
||||||
|
/// Another agent delegating via `idea_ask_agent`.
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
Agent {
|
||||||
|
/// The delegating agent id.
|
||||||
|
agent_id: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TicketWorkSource> for TicketWorkSourceDto {
|
||||||
|
fn from(source: TicketWorkSource) -> Self {
|
||||||
|
match source {
|
||||||
|
TicketWorkSource::Human => Self::Human,
|
||||||
|
TicketWorkSource::Agent { agent_id } => Self::Agent {
|
||||||
|
agent_id: agent_id.to_string(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One queued/in-progress delegation ticket for an agent.
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct AgentTicketStateDto {
|
||||||
|
/// Stable id of the queued ticket.
|
||||||
|
pub ticket_id: String,
|
||||||
|
/// Conversation thread this task enters.
|
||||||
|
pub conversation_id: String,
|
||||||
|
/// FIFO position at snapshot time (`0` = head).
|
||||||
|
pub position: u32,
|
||||||
|
/// Derived status (in-progress vs queued).
|
||||||
|
pub status: TicketWorkStatusDto,
|
||||||
|
/// Origin of the ticket.
|
||||||
|
pub source: TicketWorkSourceDto,
|
||||||
|
/// Display label of the requester.
|
||||||
|
pub requester_label: String,
|
||||||
|
/// Bounded excerpt of the task.
|
||||||
|
pub task_preview: String,
|
||||||
|
/// Character length of the original (un-truncated) task.
|
||||||
|
pub task_len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AgentTicketState> for AgentTicketStateDto {
|
||||||
|
fn from(ticket: AgentTicketState) -> Self {
|
||||||
|
Self {
|
||||||
|
ticket_id: ticket.ticket_id.to_string(),
|
||||||
|
conversation_id: ticket.conversation_id.to_string(),
|
||||||
|
position: ticket.position,
|
||||||
|
status: ticket.status.into(),
|
||||||
|
source: ticket.source.into(),
|
||||||
|
requester_label: ticket.requester_label,
|
||||||
|
task_preview: ticket.task_preview,
|
||||||
|
task_len: ticket.task_len,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// One manifest agent's current live/busy state.
|
/// One manifest agent's current live/busy state.
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@ -1517,6 +1599,8 @@ pub struct AgentWorkStateDto {
|
|||||||
pub live: Option<LiveWorkSessionDto>,
|
pub live: Option<LiveWorkSessionDto>,
|
||||||
/// Current mediated-input busy state.
|
/// Current mediated-input busy state.
|
||||||
pub busy: AgentBusyState,
|
pub busy: AgentBusyState,
|
||||||
|
/// Pending/in-progress delegation tickets, in FIFO order.
|
||||||
|
pub tickets: Vec<AgentTicketStateDto>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Project-level read model for conversation/delegation UX.
|
/// Project-level read model for conversation/delegation UX.
|
||||||
@ -1543,6 +1627,11 @@ impl From<ProjectWorkState> for ProjectWorkStateDto {
|
|||||||
kind: live.kind.into(),
|
kind: live.kind.into(),
|
||||||
}),
|
}),
|
||||||
busy: agent.busy,
|
busy: agent.busy,
|
||||||
|
tickets: agent
|
||||||
|
.tickets
|
||||||
|
.into_iter()
|
||||||
|
.map(AgentTicketStateDto::from)
|
||||||
|
.collect(),
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -993,6 +993,10 @@ impl AppState {
|
|||||||
// partagé pour `resolve`/`resolve_ticket`/`cancel_head` côté orchestrateur.
|
// partagé pour `resolve`/`resolve_ticket`/`cancel_head` côté orchestrateur.
|
||||||
let inmemory_mailbox = Arc::new(InMemoryMailbox::new());
|
let inmemory_mailbox = Arc::new(InMemoryMailbox::new());
|
||||||
let mailbox = Arc::clone(&inmemory_mailbox) as Arc<dyn domain::mailbox::AgentMailbox>;
|
let mailbox = Arc::clone(&inmemory_mailbox) as Arc<dyn domain::mailbox::AgentMailbox>;
|
||||||
|
// Same concrete mailbox, second (read-only) port view for the work-state
|
||||||
|
// read model: lists pending tickets without touching the mutating surface.
|
||||||
|
let queue_snapshot =
|
||||||
|
Arc::clone(&inmemory_mailbox) as Arc<dyn domain::mailbox::AgentQueueSnapshot>;
|
||||||
let mediated_inbox = Arc::new(
|
let mediated_inbox = Arc::new(
|
||||||
MediatedInbox::with_pty(
|
MediatedInbox::with_pty(
|
||||||
Arc::clone(&inmemory_mailbox),
|
Arc::clone(&inmemory_mailbox),
|
||||||
@ -1031,6 +1035,7 @@ impl AppState {
|
|||||||
Arc::clone(&contexts_port),
|
Arc::clone(&contexts_port),
|
||||||
Arc::clone(&live_sessions),
|
Arc::clone(&live_sessions),
|
||||||
Arc::clone(&input_mediator),
|
Arc::clone(&input_mediator),
|
||||||
|
queue_snapshot,
|
||||||
));
|
));
|
||||||
|
|
||||||
// --- Limites de session des agents (ARCHITECTURE §21, LS7) ---
|
// --- Limites de session des agents (ARCHITECTURE §21, LS7) ---
|
||||||
|
|||||||
@ -9,8 +9,9 @@ use app_tauri_lib::dto::{
|
|||||||
};
|
};
|
||||||
use application::AppError;
|
use application::AppError;
|
||||||
use application::{
|
use application::{
|
||||||
AgentWorkState, CreateAgentOutput, InspectConversationOutput, LaunchAgentOutput,
|
AgentTicketState, AgentWorkState, CreateAgentOutput, InspectConversationOutput,
|
||||||
ListAgentsOutput, LiveSessionKind, LiveWorkSession, ProjectWorkState,
|
LaunchAgentOutput, ListAgentsOutput, LiveSessionKind, LiveWorkSession, ProjectWorkState,
|
||||||
|
TicketWorkSource, TicketWorkStatus,
|
||||||
};
|
};
|
||||||
use domain::ids::{AgentId, NodeId, ProfileId, SessionId};
|
use domain::ids::{AgentId, NodeId, ProfileId, SessionId};
|
||||||
use domain::ports::ConversationDetails;
|
use domain::ports::ConversationDetails;
|
||||||
@ -219,6 +220,7 @@ fn project_work_state_dto_serialises_live_and_busy_camelcase() {
|
|||||||
ticket,
|
ticket,
|
||||||
since_ms: 1_234,
|
since_ms: 1_234,
|
||||||
},
|
},
|
||||||
|
tickets: vec![],
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -231,11 +233,77 @@ fn project_work_state_dto_serialises_live_and_busy_camelcase() {
|
|||||||
assert_eq!(v["agents"][0]["busy"]["state"], "busy");
|
assert_eq!(v["agents"][0]["busy"]["state"], "busy");
|
||||||
assert_eq!(v["agents"][0]["busy"]["ticket"], ticket.to_string());
|
assert_eq!(v["agents"][0]["busy"]["ticket"], ticket.to_string());
|
||||||
assert_eq!(v["agents"][0]["busy"]["sinceMs"], 1_234);
|
assert_eq!(v["agents"][0]["busy"]["sinceMs"], 1_234);
|
||||||
|
assert_eq!(v["agents"][0]["tickets"], json!([]));
|
||||||
assert!(v["agents"][0].get("agent_id").is_none());
|
assert!(v["agents"][0].get("agent_id").is_none());
|
||||||
assert!(v["agents"][0]["live"].get("session_id").is_none());
|
assert!(v["agents"][0]["live"].get("session_id").is_none());
|
||||||
assert!(v["agents"][0]["busy"].get("since_ms").is_none());
|
assert!(v["agents"][0]["busy"].get("since_ms").is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn project_work_state_dto_serialises_tickets_camelcase() {
|
||||||
|
let agent = AgentId::from_uuid(Uuid::from_u128(11));
|
||||||
|
let profile = ProfileId::from_uuid(Uuid::from_u128(12));
|
||||||
|
let requester = AgentId::from_uuid(Uuid::from_u128(20));
|
||||||
|
let head_ticket = domain::TicketId::from_uuid(Uuid::from_u128(30));
|
||||||
|
let next_ticket = domain::TicketId::from_uuid(Uuid::from_u128(31));
|
||||||
|
let conversation = domain::ConversationId::from_uuid(Uuid::from_u128(40));
|
||||||
|
let dto = app_tauri_lib::dto::ProjectWorkStateDto::from(ProjectWorkState {
|
||||||
|
agents: vec![AgentWorkState {
|
||||||
|
agent_id: agent,
|
||||||
|
name: "Worker".to_owned(),
|
||||||
|
profile_id: profile,
|
||||||
|
live: None,
|
||||||
|
busy: domain::AgentBusyState::Idle,
|
||||||
|
tickets: vec![
|
||||||
|
AgentTicketState {
|
||||||
|
ticket_id: head_ticket,
|
||||||
|
conversation_id: conversation,
|
||||||
|
position: 0,
|
||||||
|
status: TicketWorkStatus::InProgress,
|
||||||
|
source: TicketWorkSource::Agent {
|
||||||
|
agent_id: requester,
|
||||||
|
},
|
||||||
|
requester_label: "Main".to_owned(),
|
||||||
|
task_preview: "Analyser le module".to_owned(),
|
||||||
|
task_len: 842,
|
||||||
|
},
|
||||||
|
AgentTicketState {
|
||||||
|
ticket_id: next_ticket,
|
||||||
|
conversation_id: conversation,
|
||||||
|
position: 1,
|
||||||
|
status: TicketWorkStatus::Queued,
|
||||||
|
source: TicketWorkSource::Human,
|
||||||
|
requester_label: "User".to_owned(),
|
||||||
|
task_preview: "Vérifier".to_owned(),
|
||||||
|
task_len: 8,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
let v = serde_json::to_value(&dto).unwrap();
|
||||||
|
let head = &v["agents"][0]["tickets"][0];
|
||||||
|
assert_eq!(head["ticketId"], head_ticket.to_string());
|
||||||
|
assert_eq!(head["conversationId"], conversation.to_string());
|
||||||
|
assert_eq!(head["position"], 0);
|
||||||
|
assert_eq!(head["status"], "inProgress");
|
||||||
|
assert_eq!(head["source"]["kind"], "agent");
|
||||||
|
assert_eq!(head["source"]["agentId"], requester.to_string());
|
||||||
|
assert_eq!(head["requesterLabel"], "Main");
|
||||||
|
assert_eq!(head["taskPreview"], "Analyser le module");
|
||||||
|
assert_eq!(head["taskLen"], 842);
|
||||||
|
// snake_case must not leak.
|
||||||
|
assert!(head.get("ticket_id").is_none());
|
||||||
|
assert!(head.get("task_preview").is_none());
|
||||||
|
assert!(head["source"].get("agent_id").is_none());
|
||||||
|
|
||||||
|
let next = &v["agents"][0]["tickets"][1];
|
||||||
|
assert_eq!(next["status"], "queued");
|
||||||
|
assert_eq!(next["source"]["kind"], "human");
|
||||||
|
assert!(next["source"].get("agentId").is_none());
|
||||||
|
assert_eq!(next["requesterLabel"], "User");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn launch_agent_request_carries_conversation_id_for_resume() {
|
fn launch_agent_request_carries_conversation_id_for_resume() {
|
||||||
let raw = json!({
|
let raw = json!({
|
||||||
|
|||||||
@ -119,6 +119,6 @@ pub use terminal::{
|
|||||||
};
|
};
|
||||||
pub use window::{MoveTabToNewWindow, MoveTabToNewWindowInput, MoveTabToNewWindowOutput};
|
pub use window::{MoveTabToNewWindow, MoveTabToNewWindowInput, MoveTabToNewWindowOutput};
|
||||||
pub use workstate::{
|
pub use workstate::{
|
||||||
AgentWorkState, GetProjectWorkState, GetProjectWorkStateInput, LiveWorkSession,
|
AgentTicketState, AgentWorkState, GetProjectWorkState, GetProjectWorkStateInput,
|
||||||
ProjectWorkState,
|
LiveWorkSession, ProjectWorkState, TicketWorkSource, TicketWorkStatus,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,13 +8,22 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use domain::input::AgentBusyState;
|
use domain::input::{AgentBusyState, InputSource};
|
||||||
use domain::ports::AgentContextStore;
|
use domain::ports::AgentContextStore;
|
||||||
use domain::{AgentId, InputMediator, NodeId, ProfileId, Project, SessionId};
|
use domain::{
|
||||||
|
AgentId, AgentQueueSnapshot, ConversationId, InputMediator, NodeId, ProfileId, Project,
|
||||||
|
QueuedTicketSnapshot, SessionId, TicketId,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::error::AppError;
|
use crate::error::AppError;
|
||||||
use crate::terminal::{LiveSessionKind, LiveSessionSnapshot, LiveSessions};
|
use crate::terminal::{LiveSessionKind, LiveSessionSnapshot, LiveSessions};
|
||||||
|
|
||||||
|
/// Maximum length (in characters) of a derived [`AgentTicketState::task_preview`].
|
||||||
|
///
|
||||||
|
/// The full task can be a very long prompt; the panel only needs a scannable
|
||||||
|
/// excerpt, with [`AgentTicketState::task_len`] signalling there is more.
|
||||||
|
const TASK_PREVIEW_MAX_CHARS: usize = 160;
|
||||||
|
|
||||||
/// Input for [`GetProjectWorkState::execute`].
|
/// Input for [`GetProjectWorkState::execute`].
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct GetProjectWorkStateInput {
|
pub struct GetProjectWorkStateInput {
|
||||||
@ -42,6 +51,64 @@ pub struct AgentWorkState {
|
|||||||
pub live: Option<LiveWorkSession>,
|
pub live: Option<LiveWorkSession>,
|
||||||
/// Current FIFO/busy state.
|
/// Current FIFO/busy state.
|
||||||
pub busy: AgentBusyState,
|
pub busy: AgentBusyState,
|
||||||
|
/// Pending/in-progress delegation tickets, in FIFO order.
|
||||||
|
pub tickets: Vec<AgentTicketState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One queued delegation ticket, projected for the work-state read model.
|
||||||
|
///
|
||||||
|
/// A read-only, presentation-oriented view of a [`QueuedTicketSnapshot`]: the
|
||||||
|
/// status is **derived** by crossing the FIFO snapshot with the agent's busy state
|
||||||
|
/// (never stored), and `task_preview` is a bounded excerpt of the full task (the
|
||||||
|
/// full text is never sent to the panel; `task_len` signals there is more).
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct AgentTicketState {
|
||||||
|
/// Stable id of the queued ticket.
|
||||||
|
pub ticket_id: TicketId,
|
||||||
|
/// Conversation thread this task enters.
|
||||||
|
pub conversation_id: ConversationId,
|
||||||
|
/// FIFO position at snapshot time (`0` = head).
|
||||||
|
pub position: u32,
|
||||||
|
/// Derived status (in-progress = the agent's current busy ticket).
|
||||||
|
pub status: TicketWorkStatus,
|
||||||
|
/// Origin of the ticket (human operator or a delegating agent).
|
||||||
|
pub source: TicketWorkSource,
|
||||||
|
/// Display label of the requester.
|
||||||
|
pub requester_label: String,
|
||||||
|
/// Bounded, whitespace-normalised excerpt of the task.
|
||||||
|
pub task_preview: String,
|
||||||
|
/// Character length of the **original** (un-truncated) task.
|
||||||
|
pub task_len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Derived processing status of a queued ticket.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum TicketWorkStatus {
|
||||||
|
/// The agent's current busy turn is running this ticket.
|
||||||
|
InProgress,
|
||||||
|
/// Waiting behind the head / the agent is idle.
|
||||||
|
Queued,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Origin of a queued ticket, mirroring [`InputSource`] for the read model.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum TicketWorkSource {
|
||||||
|
/// The human operator.
|
||||||
|
Human,
|
||||||
|
/// Another agent delegating via `idea_ask_agent`.
|
||||||
|
Agent {
|
||||||
|
/// The delegating agent.
|
||||||
|
agent_id: AgentId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<InputSource> for TicketWorkSource {
|
||||||
|
fn from(source: InputSource) -> Self {
|
||||||
|
match source {
|
||||||
|
InputSource::Human => Self::Human,
|
||||||
|
InputSource::Agent { agent_id } => Self::Agent { agent_id },
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Live session coordinates exposed by the work-state read model.
|
/// Live session coordinates exposed by the work-state read model.
|
||||||
@ -60,6 +127,7 @@ pub struct GetProjectWorkState {
|
|||||||
contexts: Arc<dyn AgentContextStore>,
|
contexts: Arc<dyn AgentContextStore>,
|
||||||
live: Arc<LiveSessions>,
|
live: Arc<LiveSessions>,
|
||||||
input: Arc<dyn InputMediator>,
|
input: Arc<dyn InputMediator>,
|
||||||
|
queue: Arc<dyn AgentQueueSnapshot>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GetProjectWorkState {
|
impl GetProjectWorkState {
|
||||||
@ -69,11 +137,13 @@ impl GetProjectWorkState {
|
|||||||
contexts: Arc<dyn AgentContextStore>,
|
contexts: Arc<dyn AgentContextStore>,
|
||||||
live: Arc<LiveSessions>,
|
live: Arc<LiveSessions>,
|
||||||
input: Arc<dyn InputMediator>,
|
input: Arc<dyn InputMediator>,
|
||||||
|
queue: Arc<dyn AgentQueueSnapshot>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
contexts,
|
contexts,
|
||||||
live,
|
live,
|
||||||
input,
|
input,
|
||||||
|
queue,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,12 +172,23 @@ impl GetProjectWorkState {
|
|||||||
session_id: snapshot.session_id,
|
session_id: snapshot.session_id,
|
||||||
kind: snapshot.kind,
|
kind: snapshot.kind,
|
||||||
});
|
});
|
||||||
|
// Tickets are crossed with the busy state: only an agent absent from
|
||||||
|
// the manifest is dropped (this loop only visits manifest entries), so
|
||||||
|
// the manifest boundary is naturally preserved.
|
||||||
|
let busy_ticket = self.input.busy_state(agent.id).ticket();
|
||||||
|
let tickets = self
|
||||||
|
.queue
|
||||||
|
.queue_for(agent.id)
|
||||||
|
.into_iter()
|
||||||
|
.map(|snapshot| ticket_state(snapshot, busy_ticket))
|
||||||
|
.collect();
|
||||||
Ok(AgentWorkState {
|
Ok(AgentWorkState {
|
||||||
agent_id: agent.id,
|
agent_id: agent.id,
|
||||||
name: agent.name,
|
name: agent.name,
|
||||||
profile_id: agent.profile_id,
|
profile_id: agent.profile_id,
|
||||||
live,
|
live,
|
||||||
busy: self.input.busy_state(agent.id),
|
busy: self.input.busy_state(agent.id),
|
||||||
|
tickets,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, AppError>>()?;
|
.collect::<Result<Vec<_>, AppError>>()?;
|
||||||
@ -115,6 +196,40 @@ impl GetProjectWorkState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Projects one FIFO snapshot into a read-model ticket, deriving status from the
|
||||||
|
/// agent's current busy ticket (the one running its turn = `InProgress`).
|
||||||
|
fn ticket_state(snapshot: QueuedTicketSnapshot, busy_ticket: Option<TicketId>) -> AgentTicketState {
|
||||||
|
let status = if busy_ticket == Some(snapshot.id) {
|
||||||
|
TicketWorkStatus::InProgress
|
||||||
|
} else {
|
||||||
|
TicketWorkStatus::Queued
|
||||||
|
};
|
||||||
|
AgentTicketState {
|
||||||
|
ticket_id: snapshot.id,
|
||||||
|
conversation_id: snapshot.conversation,
|
||||||
|
position: snapshot.position,
|
||||||
|
status,
|
||||||
|
source: snapshot.source.into(),
|
||||||
|
requester_label: snapshot.requester,
|
||||||
|
task_preview: task_preview(&snapshot.task),
|
||||||
|
task_len: snapshot.task.chars().count(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds a bounded, whitespace-normalised excerpt of a task for the UI panel.
|
||||||
|
///
|
||||||
|
/// Trims, collapses any run of whitespace to a single space, then truncates to
|
||||||
|
/// [`TASK_PREVIEW_MAX_CHARS`] characters (never mid-codepoint). The original length
|
||||||
|
/// is reported separately as [`AgentTicketState::task_len`].
|
||||||
|
fn task_preview(task: &str) -> String {
|
||||||
|
let normalised = task.split_whitespace().collect::<Vec<_>>().join(" ");
|
||||||
|
if normalised.chars().count() <= TASK_PREVIEW_MAX_CHARS {
|
||||||
|
normalised
|
||||||
|
} else {
|
||||||
|
normalised.chars().take(TASK_PREVIEW_MAX_CHARS).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn live_by_agent(snapshots: Vec<LiveSessionSnapshot>) -> HashMap<AgentId, LiveSessionSnapshot> {
|
fn live_by_agent(snapshots: Vec<LiveSessionSnapshot>) -> HashMap<AgentId, LiveSessionSnapshot> {
|
||||||
let mut out = HashMap::new();
|
let mut out = HashMap::new();
|
||||||
for snapshot in snapshots {
|
for snapshot in snapshots {
|
||||||
|
|||||||
@ -9,16 +9,18 @@ use async_trait::async_trait;
|
|||||||
|
|
||||||
use application::{
|
use application::{
|
||||||
GetProjectWorkState, GetProjectWorkStateInput, LiveSessionKind, LiveSessions,
|
GetProjectWorkState, GetProjectWorkStateInput, LiveSessionKind, LiveSessions,
|
||||||
StructuredSessions, TerminalSessions,
|
StructuredSessions, TerminalSessions, TicketWorkSource, TicketWorkStatus,
|
||||||
|
};
|
||||||
|
use domain::mailbox::{
|
||||||
|
AgentQueueSnapshot, MailboxError, PendingReply, QueuedTicketSnapshot, Ticket,
|
||||||
};
|
};
|
||||||
use domain::mailbox::{MailboxError, PendingReply, Ticket};
|
|
||||||
use domain::ports::{
|
use domain::ports::{
|
||||||
AgentContextStore, AgentSession, AgentSessionError, PtyHandle, ReplyStream, StoreError,
|
AgentContextStore, AgentSession, AgentSessionError, PtyHandle, ReplyStream, StoreError,
|
||||||
};
|
};
|
||||||
use domain::{
|
use domain::{
|
||||||
Agent, AgentBusyState, AgentId, AgentManifest, AgentOrigin, InputMediator, ManifestEntry,
|
Agent, AgentBusyState, AgentId, AgentManifest, AgentOrigin, ConversationId, InputMediator,
|
||||||
MarkdownDoc, NodeId, ProfileId, Project, ProjectId, ProjectPath, PtySize, RemoteRef, SessionId,
|
InputSource, ManifestEntry, MarkdownDoc, NodeId, ProfileId, Project, ProjectId, ProjectPath,
|
||||||
SessionKind, TerminalSession,
|
PtySize, RemoteRef, SessionId, SessionKind, TerminalSession, TicketId,
|
||||||
};
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@ -138,6 +140,45 @@ impl InputMediator for FakeInput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct FakeQueue {
|
||||||
|
queues: Mutex<HashMap<AgentId, Vec<QueuedTicketSnapshot>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FakeQueue {
|
||||||
|
fn set(&self, agent: AgentId, tickets: Vec<QueuedTicketSnapshot>) {
|
||||||
|
self.queues.lock().unwrap().insert(agent, tickets);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AgentQueueSnapshot for FakeQueue {
|
||||||
|
fn queue_for(&self, agent: AgentId) -> Vec<QueuedTicketSnapshot> {
|
||||||
|
self.queues
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.get(&agent)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn snapshot(
|
||||||
|
id: u128,
|
||||||
|
position: u32,
|
||||||
|
source: InputSource,
|
||||||
|
requester: &str,
|
||||||
|
task: &str,
|
||||||
|
) -> QueuedTicketSnapshot {
|
||||||
|
QueuedTicketSnapshot {
|
||||||
|
id: TicketId::from_uuid(Uuid::from_u128(id)),
|
||||||
|
source,
|
||||||
|
conversation: ConversationId::from_uuid(Uuid::from_u128(id + 1000)),
|
||||||
|
requester: requester.to_owned(),
|
||||||
|
task: task.to_owned(),
|
||||||
|
position,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct FakeSession {
|
struct FakeSession {
|
||||||
id: SessionId,
|
id: SessionId,
|
||||||
}
|
}
|
||||||
@ -188,6 +229,7 @@ struct Fixture {
|
|||||||
pty: Arc<TerminalSessions>,
|
pty: Arc<TerminalSessions>,
|
||||||
structured: Arc<StructuredSessions>,
|
structured: Arc<StructuredSessions>,
|
||||||
input: Arc<FakeInput>,
|
input: Arc<FakeInput>,
|
||||||
|
queue: Arc<FakeQueue>,
|
||||||
project: Project,
|
project: Project,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,18 +239,22 @@ fn fixture(agents: &[Agent]) -> Fixture {
|
|||||||
let live = Arc::new(LiveSessions::new(Arc::clone(&pty), Arc::clone(&structured)));
|
let live = Arc::new(LiveSessions::new(Arc::clone(&pty), Arc::clone(&structured)));
|
||||||
let input = Arc::new(FakeInput::default());
|
let input = Arc::new(FakeInput::default());
|
||||||
let input_port = Arc::clone(&input) as Arc<dyn InputMediator>;
|
let input_port = Arc::clone(&input) as Arc<dyn InputMediator>;
|
||||||
|
let queue = Arc::new(FakeQueue::default());
|
||||||
|
let queue_port = Arc::clone(&queue) as Arc<dyn AgentQueueSnapshot>;
|
||||||
let usecase = GetProjectWorkState::new(
|
let usecase = GetProjectWorkState::new(
|
||||||
Arc::new(FakeContexts {
|
Arc::new(FakeContexts {
|
||||||
manifest: manifest(agents),
|
manifest: manifest(agents),
|
||||||
}),
|
}),
|
||||||
live,
|
live,
|
||||||
input_port,
|
input_port,
|
||||||
|
queue_port,
|
||||||
);
|
);
|
||||||
Fixture {
|
Fixture {
|
||||||
usecase,
|
usecase,
|
||||||
pty,
|
pty,
|
||||||
structured,
|
structured,
|
||||||
input,
|
input,
|
||||||
|
queue,
|
||||||
project: project(),
|
project: project(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -305,3 +351,169 @@ async fn workstate_ignores_live_agents_absent_from_manifest() {
|
|||||||
assert_eq!(out.agents[0].agent_id, a.id);
|
assert_eq!(out.agents[0].agent_id, a.id);
|
||||||
assert_eq!(out.agents[0].live, None);
|
assert_eq!(out.agents[0].live, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn workstate_agent_without_queue_has_no_tickets() {
|
||||||
|
let a = agent(10, "alpha");
|
||||||
|
let f = fixture(std::slice::from_ref(&a));
|
||||||
|
|
||||||
|
let out = f
|
||||||
|
.usecase
|
||||||
|
.execute(GetProjectWorkStateInput { project: f.project })
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(out.agents[0].tickets.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn workstate_lists_two_tickets_in_fifo_order() {
|
||||||
|
let a = agent(10, "alpha");
|
||||||
|
let f = fixture(std::slice::from_ref(&a));
|
||||||
|
let from = aid(20);
|
||||||
|
f.queue.set(
|
||||||
|
a.id,
|
||||||
|
vec![
|
||||||
|
snapshot(1, 0, InputSource::agent(from), "Main", "first"),
|
||||||
|
snapshot(2, 1, InputSource::agent(from), "Main", "second"),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
let out = f
|
||||||
|
.usecase
|
||||||
|
.execute(GetProjectWorkStateInput { project: f.project })
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let tickets = &out.agents[0].tickets;
|
||||||
|
assert_eq!(tickets.len(), 2);
|
||||||
|
assert_eq!(tickets[0].ticket_id, ticket_id(1));
|
||||||
|
assert_eq!(tickets[0].position, 0);
|
||||||
|
assert_eq!(tickets[1].ticket_id, ticket_id(2));
|
||||||
|
assert_eq!(tickets[1].position, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn workstate_marks_busy_head_in_progress_and_rest_queued() {
|
||||||
|
let a = agent(10, "alpha");
|
||||||
|
let f = fixture(std::slice::from_ref(&a));
|
||||||
|
let from = aid(20);
|
||||||
|
f.queue.set(
|
||||||
|
a.id,
|
||||||
|
vec![
|
||||||
|
snapshot(1, 0, InputSource::agent(from), "Main", "first"),
|
||||||
|
snapshot(2, 1, InputSource::agent(from), "Main", "second"),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
f.input.set_busy(
|
||||||
|
a.id,
|
||||||
|
AgentBusyState::Busy {
|
||||||
|
ticket: ticket_id(1),
|
||||||
|
since_ms: 5,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let out = f
|
||||||
|
.usecase
|
||||||
|
.execute(GetProjectWorkStateInput { project: f.project })
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let tickets = &out.agents[0].tickets;
|
||||||
|
assert_eq!(tickets[0].status, TicketWorkStatus::InProgress);
|
||||||
|
assert_eq!(tickets[1].status, TicketWorkStatus::Queued);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn workstate_marks_all_queued_when_agent_idle() {
|
||||||
|
let a = agent(10, "alpha");
|
||||||
|
let f = fixture(std::slice::from_ref(&a));
|
||||||
|
let from = aid(20);
|
||||||
|
f.queue.set(
|
||||||
|
a.id,
|
||||||
|
vec![snapshot(1, 0, InputSource::agent(from), "Main", "first")],
|
||||||
|
);
|
||||||
|
// Agent left Idle (default): even the head is only queued, not in-progress.
|
||||||
|
|
||||||
|
let out = f
|
||||||
|
.usecase
|
||||||
|
.execute(GetProjectWorkStateInput { project: f.project })
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(out.agents[0].tickets[0].status, TicketWorkStatus::Queued);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn workstate_ignores_queue_for_agent_absent_from_manifest() {
|
||||||
|
let a = agent(10, "alpha");
|
||||||
|
let f = fixture(std::slice::from_ref(&a));
|
||||||
|
// A queue exists for an agent that is not in the manifest: it must not surface.
|
||||||
|
f.queue.set(
|
||||||
|
aid(999),
|
||||||
|
vec![snapshot(1, 0, InputSource::Human, "User", "ghost")],
|
||||||
|
);
|
||||||
|
|
||||||
|
let out = f
|
||||||
|
.usecase
|
||||||
|
.execute(GetProjectWorkStateInput { project: f.project })
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(out.agents.len(), 1);
|
||||||
|
assert!(out.agents[0].tickets.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn workstate_truncates_task_preview_and_keeps_original_len() {
|
||||||
|
let a = agent(10, "alpha");
|
||||||
|
let f = fixture(std::slice::from_ref(&a));
|
||||||
|
// 400 chars with runs of whitespace to normalise; well over the 160 cap.
|
||||||
|
let long_task = format!("start{}end", " x ".repeat(80));
|
||||||
|
let original_len = long_task.chars().count();
|
||||||
|
f.queue.set(
|
||||||
|
a.id,
|
||||||
|
vec![snapshot(1, 0, InputSource::Human, "User", &long_task)],
|
||||||
|
);
|
||||||
|
|
||||||
|
let out = f
|
||||||
|
.usecase
|
||||||
|
.execute(GetProjectWorkStateInput { project: f.project })
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let ticket = &out.agents[0].tickets[0];
|
||||||
|
assert_eq!(ticket.task_preview.chars().count(), 160);
|
||||||
|
assert!(!ticket.task_preview.contains(" "), "whitespace normalised");
|
||||||
|
assert_eq!(ticket.task_len, original_len);
|
||||||
|
assert!(ticket.task_len > 160);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn workstate_maps_human_and_agent_ticket_sources() {
|
||||||
|
let a = agent(10, "alpha");
|
||||||
|
let f = fixture(std::slice::from_ref(&a));
|
||||||
|
let from = aid(20);
|
||||||
|
f.queue.set(
|
||||||
|
a.id,
|
||||||
|
vec![
|
||||||
|
snapshot(1, 0, InputSource::Human, "User", "from human"),
|
||||||
|
snapshot(2, 1, InputSource::agent(from), "Main", "from agent"),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
let out = f
|
||||||
|
.usecase
|
||||||
|
.execute(GetProjectWorkStateInput { project: f.project })
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let tickets = &out.agents[0].tickets;
|
||||||
|
assert_eq!(tickets[0].source, TicketWorkSource::Human);
|
||||||
|
assert_eq!(tickets[0].requester_label, "User");
|
||||||
|
assert_eq!(
|
||||||
|
tickets[1].source,
|
||||||
|
TicketWorkSource::Agent { agent_id: from }
|
||||||
|
);
|
||||||
|
assert_eq!(tickets[1].requester_label, "Main");
|
||||||
|
}
|
||||||
|
|||||||
@ -82,7 +82,10 @@ pub use profile::{
|
|||||||
McpServerWiring, RateLimitPattern, SessionStrategy,
|
McpServerWiring, RateLimitPattern, SessionStrategy,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use mailbox::{AgentMailbox, MailboxError, PendingReply, Ticket, TicketId};
|
pub use mailbox::{
|
||||||
|
AgentMailbox, AgentQueueSnapshot, MailboxError, PendingReply, QueuedTicketSnapshot, Ticket,
|
||||||
|
TicketId,
|
||||||
|
};
|
||||||
|
|
||||||
pub use conversation::{
|
pub use conversation::{
|
||||||
Conversation, ConversationError, ConversationId, ConversationParty, ConversationRegistry,
|
Conversation, ConversationError, ConversationId, ConversationParty, ConversationRegistry,
|
||||||
|
|||||||
@ -33,6 +33,47 @@ use crate::conversation::ConversationId;
|
|||||||
use crate::ids::AgentId;
|
use crate::ids::AgentId;
|
||||||
use crate::input::InputSource;
|
use crate::input::InputSource;
|
||||||
|
|
||||||
|
/// A read-only, cloned view of one queued [`Ticket`] in a target agent's FIFO.
|
||||||
|
///
|
||||||
|
/// Carries **only the data** of a ticket (never the one-shot reply sender, which
|
||||||
|
/// stays inside the adapter), plus its `position` in the FIFO at snapshot time
|
||||||
|
/// (`0` = head). Pure value object used by the [`AgentQueueSnapshot`] read port so
|
||||||
|
/// the work-state read model can list pending delegations without touching the
|
||||||
|
/// mutating [`AgentMailbox`] surface (ISP).
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct QueuedTicketSnapshot {
|
||||||
|
/// Stable id of the queued ticket.
|
||||||
|
pub id: TicketId,
|
||||||
|
/// The origin of the input (Human or a delegating Agent).
|
||||||
|
pub source: InputSource,
|
||||||
|
/// The conversation thread this task enters.
|
||||||
|
pub conversation: ConversationId,
|
||||||
|
/// Display name (or id) of the requester.
|
||||||
|
pub requester: String,
|
||||||
|
/// The full task/message text (preview/truncation is the caller's concern).
|
||||||
|
pub task: String,
|
||||||
|
/// Position in the FIFO at snapshot time (`0` = head, recomputed each call).
|
||||||
|
pub position: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read-only inspection port over the per-agent delegation FIFO.
|
||||||
|
///
|
||||||
|
/// Segregated from the mutating [`AgentMailbox`] (Interface Segregation): the
|
||||||
|
/// work-state read model depends on this port to *observe* the queue, never on
|
||||||
|
/// `enqueue`/`resolve`/`cancel`. Implementations return **cloned** snapshots in
|
||||||
|
/// FIFO order with recomputed positions; observing the queue must never mutate it.
|
||||||
|
///
|
||||||
|
/// Object-safe (`&self`) so the application layer holds it as
|
||||||
|
/// `Arc<dyn AgentQueueSnapshot>`; one concrete `InMemoryMailbox` can be shared as
|
||||||
|
/// both an [`AgentMailbox`] (mutation) and an [`AgentQueueSnapshot`] (read) view.
|
||||||
|
pub trait AgentQueueSnapshot: Send + Sync {
|
||||||
|
/// Returns a cloned, FIFO-ordered snapshot of `agent`'s queued tickets.
|
||||||
|
///
|
||||||
|
/// An agent with no queue yields an empty `Vec`. Positions are recomputed from
|
||||||
|
/// the current order (`0` = head). Pure read: the queue is left untouched.
|
||||||
|
fn queue_for(&self, agent: AgentId) -> Vec<QueuedTicketSnapshot>;
|
||||||
|
}
|
||||||
|
|
||||||
/// Identifies one queued [`Ticket`] within a target agent's mailbox.
|
/// Identifies one queued [`Ticket`] within a target agent's mailbox.
|
||||||
///
|
///
|
||||||
/// Newtype around [`uuid::Uuid`]; minted by the adapter on `enqueue`. It is **never
|
/// Newtype around [`uuid::Uuid`]; minted by the adapter on `enqueue`. It is **never
|
||||||
@ -310,4 +351,22 @@ mod tests {
|
|||||||
let a = AgentId::from_uuid(uuid::Uuid::from_u128(1));
|
let a = AgentId::from_uuid(uuid::Uuid::from_u128(1));
|
||||||
assert_ne!(MailboxError::NoPendingRequest(a), MailboxError::Cancelled);
|
assert_ne!(MailboxError::NoPendingRequest(a), MailboxError::Cancelled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn queued_ticket_snapshot_carries_ticket_data_and_position() {
|
||||||
|
let conv = ConversationId::from_uuid(uuid::Uuid::from_u128(3));
|
||||||
|
let from = AgentId::from_uuid(uuid::Uuid::from_u128(4));
|
||||||
|
let snap = QueuedTicketSnapshot {
|
||||||
|
id: TicketId::from_uuid(uuid::Uuid::from_u128(8)),
|
||||||
|
source: InputSource::agent(from),
|
||||||
|
conversation: conv,
|
||||||
|
requester: "Main".to_owned(),
|
||||||
|
task: "delegate".to_owned(),
|
||||||
|
position: 2,
|
||||||
|
};
|
||||||
|
assert_eq!(snap.source.as_agent(), Some(from));
|
||||||
|
assert_eq!(snap.conversation, conv);
|
||||||
|
assert_eq!(snap.position, 2);
|
||||||
|
assert_eq!(snap.task, "delegate");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,7 +25,10 @@ use std::sync::Mutex;
|
|||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
use domain::ids::AgentId;
|
use domain::ids::AgentId;
|
||||||
use domain::mailbox::{AgentMailbox, MailboxError, PendingReply, Ticket, TicketId};
|
use domain::mailbox::{
|
||||||
|
AgentMailbox, AgentQueueSnapshot, MailboxError, PendingReply, QueuedTicketSnapshot, Ticket,
|
||||||
|
TicketId,
|
||||||
|
};
|
||||||
|
|
||||||
/// One queued request plus the sender that resolves its awaiting [`PendingReply`].
|
/// One queued request plus the sender that resolves its awaiting [`PendingReply`].
|
||||||
struct Slot {
|
struct Slot {
|
||||||
@ -160,6 +163,35 @@ impl AgentMailbox for InMemoryMailbox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AgentQueueSnapshot for InMemoryMailbox {
|
||||||
|
fn queue_for(&self, agent: AgentId) -> Vec<QueuedTicketSnapshot> {
|
||||||
|
// Pure read: clone each ticket's *data* (never the `oneshot::Sender`) under
|
||||||
|
// the lock, recomputing the FIFO position from the current order (0 = head).
|
||||||
|
// The queue is observed, not mutated.
|
||||||
|
let queues = self
|
||||||
|
.queues
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
|
queues
|
||||||
|
.get(&agent)
|
||||||
|
.map(|queue| {
|
||||||
|
queue
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(idx, slot)| QueuedTicketSnapshot {
|
||||||
|
id: slot.ticket.id,
|
||||||
|
source: slot.ticket.source,
|
||||||
|
conversation: slot.ticket.conversation,
|
||||||
|
requester: slot.ticket.requester.clone(),
|
||||||
|
task: slot.ticket.task.clone(),
|
||||||
|
position: u32::try_from(idx).unwrap_or(u32::MAX),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -270,4 +302,98 @@ mod tests {
|
|||||||
Some(TicketId::from_uuid(uuid::Uuid::from_u128(10)))
|
Some(TicketId::from_uuid(uuid::Uuid::from_u128(10)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tid(n: u128) -> TicketId {
|
||||||
|
TicketId::from_uuid(uuid::Uuid::from_u128(n))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn snapshot_of_empty_queue_is_empty() {
|
||||||
|
let mb = InMemoryMailbox::new();
|
||||||
|
assert!(mb.queue_for(agent(1)).is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn snapshot_preserves_fifo_order_and_positions() {
|
||||||
|
let mb = InMemoryMailbox::new();
|
||||||
|
let a = agent(1);
|
||||||
|
let _p1 = mb.enqueue(a, ticket(10, "first"));
|
||||||
|
let _p2 = mb.enqueue(a, ticket(11, "second"));
|
||||||
|
|
||||||
|
let snap = mb.queue_for(a);
|
||||||
|
assert_eq!(snap.len(), 2);
|
||||||
|
assert_eq!(snap[0].id, tid(10));
|
||||||
|
assert_eq!(snap[0].position, 0);
|
||||||
|
assert_eq!(snap[0].task, "first");
|
||||||
|
assert_eq!(snap[1].id, tid(11));
|
||||||
|
assert_eq!(snap[1].position, 1);
|
||||||
|
assert_eq!(snap[1].task, "second");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn snapshot_carries_ticket_metadata() {
|
||||||
|
use domain::conversation::ConversationId;
|
||||||
|
use domain::input::InputSource;
|
||||||
|
|
||||||
|
let mb = InMemoryMailbox::new();
|
||||||
|
let a = agent(1);
|
||||||
|
let from = agent(2);
|
||||||
|
let conv = ConversationId::from_uuid(uuid::Uuid::from_u128(42));
|
||||||
|
let _p = mb.enqueue(
|
||||||
|
a,
|
||||||
|
Ticket::from_agent(tid(10), from, conv, "Main", "delegate task"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let snap = mb.queue_for(a);
|
||||||
|
assert_eq!(snap.len(), 1);
|
||||||
|
assert_eq!(snap[0].source, InputSource::agent(from));
|
||||||
|
assert_eq!(snap[0].conversation, conv);
|
||||||
|
assert_eq!(snap[0].requester, "Main");
|
||||||
|
assert_eq!(snap[0].task, "delegate task");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn snapshot_is_read_only() {
|
||||||
|
let mb = InMemoryMailbox::new();
|
||||||
|
let a = agent(1);
|
||||||
|
let _p1 = mb.enqueue(a, ticket(10, "first"));
|
||||||
|
let _p2 = mb.enqueue(a, ticket(11, "second"));
|
||||||
|
|
||||||
|
let _ = mb.queue_for(a);
|
||||||
|
let _ = mb.queue_for(a);
|
||||||
|
|
||||||
|
// Observing the queue did not mutate it.
|
||||||
|
assert_eq!(mb.pending(&a), 2);
|
||||||
|
assert_eq!(mb.head_ticket(&a), Some(tid(10)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn snapshot_updates_after_resolve_ticket() {
|
||||||
|
let mb = InMemoryMailbox::new();
|
||||||
|
let a = agent(1);
|
||||||
|
let _p1 = mb.enqueue(a, ticket(10, "first"));
|
||||||
|
let _p2 = mb.enqueue(a, ticket(11, "second"));
|
||||||
|
|
||||||
|
mb.resolve_ticket(a, tid(10), "done".to_owned()).unwrap();
|
||||||
|
|
||||||
|
let snap = mb.queue_for(a);
|
||||||
|
assert_eq!(snap.len(), 1);
|
||||||
|
assert_eq!(snap[0].id, tid(11));
|
||||||
|
assert_eq!(snap[0].position, 0, "remaining ticket becomes head");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn snapshot_updates_after_cancel_head() {
|
||||||
|
let mb = InMemoryMailbox::new();
|
||||||
|
let a = agent(1);
|
||||||
|
let _p1 = mb.enqueue(a, ticket(10, "head"));
|
||||||
|
let _p2 = mb.enqueue(a, ticket(11, "next"));
|
||||||
|
|
||||||
|
mb.cancel_head(a, tid(10));
|
||||||
|
|
||||||
|
let snap = mb.queue_for(a);
|
||||||
|
assert_eq!(snap.len(), 1);
|
||||||
|
assert_eq!(snap[0].id, tid(11));
|
||||||
|
assert_eq!(snap[0].position, 0, "next ticket recomputed to position 0");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1654,8 +1654,11 @@ export class MockWorkStateGateway implements WorkStateGateway {
|
|||||||
private states = new Map<string, ProjectWorkState>();
|
private states = new Map<string, ProjectWorkState>();
|
||||||
|
|
||||||
/** Seeds the read-model returned for a project (deterministic tests/dev). */
|
/** Seeds the read-model returned for a project (deterministic tests/dev). */
|
||||||
_setProjectWorkState(projectId: string, state: ProjectWorkState): void {
|
_setProjectWorkState(
|
||||||
this.states.set(projectId, structuredClone(state));
|
projectId: string,
|
||||||
|
state: ProjectWorkState | LegacyProjectWorkState,
|
||||||
|
): void {
|
||||||
|
this.states.set(projectId, normalizeProjectWorkState(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProjectWorkState(projectId: string): Promise<ProjectWorkState> {
|
async getProjectWorkState(projectId: string): Promise<ProjectWorkState> {
|
||||||
@ -1663,6 +1666,23 @@ export class MockWorkStateGateway implements WorkStateGateway {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LegacyProjectWorkState = {
|
||||||
|
agents: Array<Omit<ProjectWorkState["agents"][number], "tickets"> & {
|
||||||
|
tickets?: ProjectWorkState["agents"][number]["tickets"];
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
|
function normalizeProjectWorkState(
|
||||||
|
state: ProjectWorkState | LegacyProjectWorkState,
|
||||||
|
): ProjectWorkState {
|
||||||
|
return {
|
||||||
|
agents: state.agents.map((agent) => ({
|
||||||
|
...structuredClone(agent),
|
||||||
|
tickets: structuredClone(agent.tickets ?? []),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function mostRestrictive(
|
function mostRestrictive(
|
||||||
project?: PermissionSet["fallback"],
|
project?: PermissionSet["fallback"],
|
||||||
agent?: PermissionSet["fallback"],
|
agent?: PermissionSet["fallback"],
|
||||||
|
|||||||
@ -149,6 +149,26 @@ export type WorkBusyState =
|
|||||||
| { state: "idle" }
|
| { state: "idle" }
|
||||||
| { state: "busy"; ticket: string; sinceMs: number };
|
| { state: "busy"; ticket: string; sinceMs: number };
|
||||||
|
|
||||||
|
/** FIFO status for an input/ticket currently tracked by the backend queue. */
|
||||||
|
export type TicketWorkStatus = "inProgress" | "queued";
|
||||||
|
|
||||||
|
/** Who produced an input/ticket in the work-state read-model. */
|
||||||
|
export type TicketWorkSource =
|
||||||
|
| { kind: "human" }
|
||||||
|
| { kind: "agent"; agentId: string };
|
||||||
|
|
||||||
|
/** One queued or in-progress input/ticket for an agent. */
|
||||||
|
export interface AgentTicketState {
|
||||||
|
ticketId: string;
|
||||||
|
conversationId: string;
|
||||||
|
position: number;
|
||||||
|
status: TicketWorkStatus;
|
||||||
|
source: TicketWorkSource;
|
||||||
|
requesterLabel: string;
|
||||||
|
taskPreview: string;
|
||||||
|
taskLen: number;
|
||||||
|
}
|
||||||
|
|
||||||
/** One agent row in the project work-state read-model. */
|
/** One agent row in the project work-state read-model. */
|
||||||
export interface AgentWorkState {
|
export interface AgentWorkState {
|
||||||
agentId: string;
|
agentId: string;
|
||||||
@ -156,6 +176,7 @@ export interface AgentWorkState {
|
|||||||
profileId: string;
|
profileId: string;
|
||||||
live?: LiveWorkSession;
|
live?: LiveWorkSession;
|
||||||
busy: WorkBusyState;
|
busy: WorkBusyState;
|
||||||
|
tickets: AgentTicketState[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Minimal read-only live-state surface for a project. */
|
/** Minimal read-only live-state surface for a project. */
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
/**
|
/**
|
||||||
* Read-only project work-state panel: one row per agent, showing live/offline
|
* Read-only project work-state panel: one row per agent, showing live/offline
|
||||||
* and idle/busy state from the backend read-model.
|
* and idle/busy state from the backend read-model, plus the current input queue.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { AgentWorkState } from "@/domain";
|
import type { AgentTicketState, AgentWorkState } from "@/domain";
|
||||||
import { Button, Panel, Spinner, cn } from "@/shared";
|
import { Button, Panel, Spinner, cn } from "@/shared";
|
||||||
import { useProjectWorkState } from "./useProjectWorkState";
|
import { useProjectWorkState } from "./useProjectWorkState";
|
||||||
|
|
||||||
@ -15,48 +15,112 @@ function shortTicket(ticket: string): string {
|
|||||||
return ticket.length <= 8 ? ticket : ticket.slice(0, 8);
|
return ticket.length <= 8 ? ticket : ticket.slice(0, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatStatus(status: AgentTicketState["status"]): string {
|
||||||
|
return status === "inProgress" ? "In progress" : "Queued";
|
||||||
|
}
|
||||||
|
|
||||||
|
function requesterLabel(ticket: AgentTicketState): string {
|
||||||
|
if (ticket.source.kind === "human") return "Human";
|
||||||
|
const id = shortTicket(ticket.source.agentId);
|
||||||
|
return ticket.requesterLabel.trim()
|
||||||
|
? `${ticket.requesterLabel} (${id})`
|
||||||
|
: `Agent ${id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TicketRow({ ticket }: { ticket: AgentTicketState }) {
|
||||||
|
const truncated = ticket.taskLen > ticket.taskPreview.length;
|
||||||
|
return (
|
||||||
|
<li className="flex min-w-0 items-start gap-2 text-xs text-muted">
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"mt-0.5 shrink-0 rounded-full px-1.5 py-0.5 font-medium",
|
||||||
|
ticket.status === "inProgress"
|
||||||
|
? "bg-warning/15 text-warning"
|
||||||
|
: "bg-raised text-muted",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
#{ticket.position + 1} {formatStatus(ticket.status)}
|
||||||
|
</span>
|
||||||
|
<span className="min-w-0 flex-1">
|
||||||
|
<span className="font-medium text-content">{requesterLabel(ticket)}</span>
|
||||||
|
<span className="text-muted"> · </span>
|
||||||
|
<span className="break-words">{ticket.taskPreview}</span>
|
||||||
|
{truncated && (
|
||||||
|
<span
|
||||||
|
aria-label={`${ticket.taskLen - ticket.taskPreview.length} more characters`}
|
||||||
|
className="text-muted"
|
||||||
|
>
|
||||||
|
{" "}
|
||||||
|
+{ticket.taskLen - ticket.taskPreview.length}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<code
|
||||||
|
aria-label={`ticket ${shortTicket(ticket.ticketId)}`}
|
||||||
|
title={ticket.ticketId}
|
||||||
|
className="mt-0.5 shrink-0 rounded bg-raised px-1.5 py-0.5 text-[11px] text-muted"
|
||||||
|
>
|
||||||
|
{shortTicket(ticket.ticketId)}
|
||||||
|
</code>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function AgentRow({ agent }: { agent: AgentWorkState }) {
|
function AgentRow({ agent }: { agent: AgentWorkState }) {
|
||||||
const live = agent.live !== undefined;
|
const live = agent.live !== undefined;
|
||||||
const busy = agent.busy.state === "busy";
|
const busy = agent.busy.state === "busy";
|
||||||
|
const tickets = [...agent.tickets].sort((a, b) => a.position - b.position);
|
||||||
return (
|
return (
|
||||||
<li className="flex items-center justify-between gap-3 py-2 first:pt-0 last:pb-0">
|
<li className="py-2 first:pt-0 last:pb-0">
|
||||||
<span className="flex min-w-0 flex-col gap-0.5">
|
<div className="flex items-center justify-between gap-3">
|
||||||
<span className="truncate text-sm font-medium text-content">
|
<span className="flex min-w-0 flex-col gap-0.5">
|
||||||
{agent.name}
|
<span className="truncate text-sm font-medium text-content">
|
||||||
|
{agent.name}
|
||||||
|
</span>
|
||||||
|
<code className="truncate text-xs text-muted">{agent.profileId}</code>
|
||||||
</span>
|
</span>
|
||||||
<code className="truncate text-xs text-muted">{agent.profileId}</code>
|
<span className="flex shrink-0 items-center gap-1.5">
|
||||||
</span>
|
<span
|
||||||
<span className="flex shrink-0 items-center gap-1.5">
|
className={cn(
|
||||||
<span
|
"rounded-full px-2 py-0.5 text-xs font-medium",
|
||||||
className={cn(
|
live
|
||||||
"rounded-full px-2 py-0.5 text-xs font-medium",
|
? "bg-success/15 text-success"
|
||||||
live
|
: "bg-raised text-muted",
|
||||||
? "bg-success/15 text-success"
|
)}
|
||||||
: "bg-raised text-muted",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{live ? "Live" : "Offline"}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
"rounded-full px-2 py-0.5 text-xs font-medium",
|
|
||||||
busy
|
|
||||||
? "bg-warning/15 text-warning"
|
|
||||||
: "bg-raised text-muted",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{busy ? "Busy" : "Idle"}
|
|
||||||
</span>
|
|
||||||
{agent.busy.state === "busy" && (
|
|
||||||
<code
|
|
||||||
aria-label={`busy ticket ${shortTicket(agent.busy.ticket)}`}
|
|
||||||
title={agent.busy.ticket}
|
|
||||||
className="rounded bg-raised px-1.5 py-0.5 text-xs text-muted"
|
|
||||||
>
|
>
|
||||||
{shortTicket(agent.busy.ticket)}
|
{live ? "Live" : "Offline"}
|
||||||
</code>
|
</span>
|
||||||
)}
|
<span
|
||||||
</span>
|
className={cn(
|
||||||
|
"rounded-full px-2 py-0.5 text-xs font-medium",
|
||||||
|
busy
|
||||||
|
? "bg-warning/15 text-warning"
|
||||||
|
: "bg-raised text-muted",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{busy ? "Busy" : "Idle"}
|
||||||
|
</span>
|
||||||
|
{agent.busy.state === "busy" && (
|
||||||
|
<code
|
||||||
|
aria-label={`busy ticket ${shortTicket(agent.busy.ticket)}`}
|
||||||
|
title={agent.busy.ticket}
|
||||||
|
className="rounded bg-raised px-1.5 py-0.5 text-xs text-muted"
|
||||||
|
>
|
||||||
|
{shortTicket(agent.busy.ticket)}
|
||||||
|
</code>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{tickets.length > 0 && (
|
||||||
|
<ul
|
||||||
|
aria-label={`${agent.name} tickets`}
|
||||||
|
className="mt-2 flex flex-col gap-1"
|
||||||
|
>
|
||||||
|
{tickets.map((ticket) => (
|
||||||
|
<TicketRow key={ticket.ticketId} ticket={ticket} />
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,6 +55,7 @@ export function useProjectWorkState(projectId: string): ProjectWorkStateViewMode
|
|||||||
event.type === "agentLaunched" ||
|
event.type === "agentLaunched" ||
|
||||||
event.type === "agentExited" ||
|
event.type === "agentExited" ||
|
||||||
event.type === "agentBusyChanged" ||
|
event.type === "agentBusyChanged" ||
|
||||||
|
event.type === "delegationReady" ||
|
||||||
event.type === "orchestratorRequestProcessed"
|
event.type === "orchestratorRequestProcessed"
|
||||||
) {
|
) {
|
||||||
void refresh();
|
void refresh();
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { describe, it, expect, vi } from "vitest";
|
import { describe, it, expect, vi } from "vitest";
|
||||||
import { render, screen, waitFor } from "@testing-library/react";
|
import { render, screen, waitFor, within } from "@testing-library/react";
|
||||||
|
|
||||||
import { DIProvider } from "@/app/di";
|
import { DIProvider } from "@/app/di";
|
||||||
import { MockSystemGateway, MockWorkStateGateway } from "@/adapters/mock";
|
import { MockSystemGateway, MockWorkStateGateway } from "@/adapters/mock";
|
||||||
@ -49,6 +49,7 @@ describe("ProjectWorkStatePanel", () => {
|
|||||||
expect(await screen.findByText("Planner")).toBeTruthy();
|
expect(await screen.findByText("Planner")).toBeTruthy();
|
||||||
expect(screen.getByText("Offline")).toBeTruthy();
|
expect(screen.getByText("Offline")).toBeTruthy();
|
||||||
expect(screen.getByText("Idle")).toBeTruthy();
|
expect(screen.getByText("Idle")).toBeTruthy();
|
||||||
|
expect(screen.queryByLabelText("Planner tickets")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders a live busy agent with a short ticket", async () => {
|
it("renders a live busy agent with a short ticket", async () => {
|
||||||
@ -81,6 +82,116 @@ describe("ProjectWorkStatePanel", () => {
|
|||||||
expect(screen.getByLabelText("busy ticket 4c65d981")).toBeTruthy();
|
expect(screen.getByLabelText("busy ticket 4c65d981")).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders an in-progress human ticket", async () => {
|
||||||
|
const workState = new MockWorkStateGateway();
|
||||||
|
workState._setProjectWorkState(PROJECT_ID, {
|
||||||
|
agents: [
|
||||||
|
{
|
||||||
|
agentId: "agent-4",
|
||||||
|
name: "Implementer",
|
||||||
|
profileId: "codex",
|
||||||
|
busy: {
|
||||||
|
state: "busy",
|
||||||
|
ticket: "ticket-in-progress-abcdef",
|
||||||
|
sinceMs: 789,
|
||||||
|
},
|
||||||
|
tickets: [
|
||||||
|
{
|
||||||
|
ticketId: "ticket-in-progress-abcdef",
|
||||||
|
conversationId: "conversation-a",
|
||||||
|
position: 0,
|
||||||
|
status: "inProgress",
|
||||||
|
source: { kind: "human" },
|
||||||
|
requesterLabel: "Anthony",
|
||||||
|
taskPreview: "Wire the workstate queue UI",
|
||||||
|
taskLen: 36,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
renderPanel(workState);
|
||||||
|
|
||||||
|
const list = await screen.findByLabelText("Implementer tickets");
|
||||||
|
expect(within(list).getByText("#1 In progress")).toBeTruthy();
|
||||||
|
expect(within(list).getByText("Human")).toBeTruthy();
|
||||||
|
expect(within(list).getByText("Wire the workstate queue UI")).toBeTruthy();
|
||||||
|
expect(within(list).getByLabelText("ticket ticket-i")).toBeTruthy();
|
||||||
|
expect(within(list).getByLabelText("9 more characters")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders two tickets in FIFO order", async () => {
|
||||||
|
const workState = new MockWorkStateGateway();
|
||||||
|
workState._setProjectWorkState(PROJECT_ID, {
|
||||||
|
agents: [
|
||||||
|
{
|
||||||
|
agentId: "agent-5",
|
||||||
|
name: "Reviewer",
|
||||||
|
profileId: "claude",
|
||||||
|
busy: { state: "idle" },
|
||||||
|
tickets: [
|
||||||
|
{
|
||||||
|
ticketId: "ticket-second-abcdef",
|
||||||
|
conversationId: "conversation-b",
|
||||||
|
position: 1,
|
||||||
|
status: "queued",
|
||||||
|
source: { kind: "agent", agentId: "agent-main-123456" },
|
||||||
|
requesterLabel: "Main",
|
||||||
|
taskPreview: "Then review the result",
|
||||||
|
taskLen: 22,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ticketId: "ticket-first-abcdef",
|
||||||
|
conversationId: "conversation-a",
|
||||||
|
position: 0,
|
||||||
|
status: "inProgress",
|
||||||
|
source: { kind: "agent", agentId: "agent-qa-123456" },
|
||||||
|
requesterLabel: "QA",
|
||||||
|
taskPreview: "Check behavior",
|
||||||
|
taskLen: 14,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
renderPanel(workState);
|
||||||
|
|
||||||
|
const list = await screen.findByLabelText("Reviewer tickets");
|
||||||
|
expect(within(list).getByText("QA (agent-qa)")).toBeTruthy();
|
||||||
|
expect(within(list).getByText("Main (agent-ma)")).toBeTruthy();
|
||||||
|
expect(list.textContent?.indexOf("#1 In progress")).toBeLessThan(
|
||||||
|
list.textContent?.indexOf("#2 Queued") ?? -1,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("normalizes mock work state without tickets", async () => {
|
||||||
|
const workState = new MockWorkStateGateway();
|
||||||
|
workState._setProjectWorkState(PROJECT_ID, {
|
||||||
|
agents: [
|
||||||
|
{
|
||||||
|
agentId: "agent-6",
|
||||||
|
name: "Legacy",
|
||||||
|
profileId: "gemini",
|
||||||
|
busy: { state: "idle" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(workState.getProjectWorkState(PROJECT_ID)).resolves.toEqual({
|
||||||
|
agents: [
|
||||||
|
{
|
||||||
|
agentId: "agent-6",
|
||||||
|
name: "Legacy",
|
||||||
|
profileId: "gemini",
|
||||||
|
busy: { state: "idle" },
|
||||||
|
tickets: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("refreshes when a relevant domain event fires", async () => {
|
it("refreshes when a relevant domain event fires", async () => {
|
||||||
const workState = new MockWorkStateGateway();
|
const workState = new MockWorkStateGateway();
|
||||||
const system = new MockSystemGateway();
|
const system = new MockSystemGateway();
|
||||||
@ -108,4 +219,46 @@ describe("ProjectWorkStatePanel", () => {
|
|||||||
expect(await screen.findByText("Responder")).toBeTruthy();
|
expect(await screen.findByText("Responder")).toBeTruthy();
|
||||||
await waitFor(() => expect(spy).toHaveBeenCalled());
|
await waitFor(() => expect(spy).toHaveBeenCalled());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("refreshes when delegationReady fires", async () => {
|
||||||
|
const workState = new MockWorkStateGateway();
|
||||||
|
const system = new MockSystemGateway();
|
||||||
|
renderPanel(workState, system);
|
||||||
|
|
||||||
|
await screen.findByText("No agent work state.");
|
||||||
|
const spy = vi.spyOn(workState, "getProjectWorkState");
|
||||||
|
workState._setProjectWorkState(PROJECT_ID, {
|
||||||
|
agents: [
|
||||||
|
{
|
||||||
|
agentId: "agent-7",
|
||||||
|
name: "Delegatee",
|
||||||
|
profileId: "codex",
|
||||||
|
busy: { state: "busy", ticket: "ticket-ready-123456", sinceMs: 123 },
|
||||||
|
tickets: [
|
||||||
|
{
|
||||||
|
ticketId: "ticket-ready-123456",
|
||||||
|
conversationId: "conversation-ready",
|
||||||
|
position: 0,
|
||||||
|
status: "inProgress",
|
||||||
|
source: { kind: "agent", agentId: "agent-main-123456" },
|
||||||
|
requesterLabel: "Main",
|
||||||
|
taskPreview: "Handle this delegated task",
|
||||||
|
taskLen: 26,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
system.emit({
|
||||||
|
type: "delegationReady",
|
||||||
|
agentId: "agent-7",
|
||||||
|
ticket: "ticket-ready-123456",
|
||||||
|
text: "Handle this delegated task",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(await screen.findByText("Delegatee")).toBeTruthy();
|
||||||
|
expect(screen.getByText("#1 In progress")).toBeTruthy();
|
||||||
|
await waitFor(() => expect(spy).toHaveBeenCalled());
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user