chore(wip): état runtime .ideai (conversations live, agents, layouts)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -10,3 +10,5 @@
|
||||
{"id":"a37b2aac-87f7-476d-8606-7250d15dd852","conversation":"09b15ec3-ce2f-0af2-2903-ef699df9de41","atMs":1781676130924,"source":{"kind":"agent","agentId":"af7f86da-76bc-48e1-9900-71f45a624800"},"role":"response","text":"LS7-front terminé. Typecheck `tsc --noEmit` propre + 39 tests agents existants verts (aucune régression). Non commité (Git tranche).\n\n## Noms de champs wire constatés (lus dans crates/app-tauri/src/events.rs, `DomainEventDto`, serde camelCase + skip_serializing_if sur les Option)\n- `agentRateLimited` → { `agentId`: string, `resetsAtMs`?: number /* absent si null */ }\n- `agentResumeScheduled` → { `agentId`: string, `fireAtMs`: number /* toujours présent */ }\n- `agentResumeCancelled` → { `agentId`: string }\n- `agentResumed` → { `agentId`: string }\n- `agentRateLimitSuspected` → { `agentId`: string, `resetsAtMs`?: number /* absent si null */ }\n\n⚠️ Confirmé : ce sont bien `resetsAtMs` / `fireAtMs` (suffixe `Ms`), PAS `resetsAt`/`fireAt` comme l'énoncé du ticket le suggérait. Je me suis aligné sur le JSON réel. Vérifié aussi par le test backend `rate_limited_relays_to_dto_and_wire` qui asserte `json[\"resetsAtMs\"]`.\n\nCommande Tauri `cancel_resume` (commands.rs:1381) : signature `cancel_resume(agent_id: String) -> Result<bool, _>` — argument **nu** `agentId` (PAS d'enveloppe `{ request }` contrairement à interrupt_agent/delegation_delivered). Adapter aligné en conséquence. Renvoie `true` ssi une reprise était armée et a été désarmée.\n\n## Fichiers modifiés\n1. `frontend/src/domain/index.ts` — 5 variantes ajoutées au union `DomainEvent` (mêmes `type` que le wire, champs exacts `resetsAtMs?`/`fireAtMs`).\n2. `frontend/src/ports/index.ts` — `cancelResume(agentId: string): Promise<boolean>` ajouté à `InputGateway` (port choisi : c'est le gateway des contrôles agent hors-bande, cohérent avec `interrupt`).\n3. `frontend/src/adapters/input.ts` — `TauriInputGateway.cancelResume` → `invoke(\"cancel_resume\", { agentId })` (arg nu, typé `<boolean>`).\n4. `frontend/src/adapters/mock/index.ts` — `MockInputGateway.cancelResume` : enregistre dans `cancelledResumes: string[]`, renvoie `cancelResumeResult` (défaut `true`, flippable par les tests).\n5. `frontend/src/features/agents/useAgents.ts` — état `limitByAgent: Record<string, AgentLimitState>` (interface exportée `{ limitedUntil?, resumeFireAt?, suspected? }`), peuplé depuis les 5 events dans l'abonnement `onDomainEvent` existant (même patron que `delegationSourceByRequester`) :\n - `agentRateLimited` → `{ limitedUntil: resetsAtMs, suspected: false }` (conserve un `resumeFireAt` éventuel).\n - `agentResumeScheduled` → arme `resumeFireAt = fireAtMs`.\n - `agentResumeCancelled` → retire `resumeFireAt`, reste limité.\n - `agentResumed` → supprime l'entrée (efface tout).\n - `agentRateLimitSuspected` → `{ limitedUntil: resetsAtMs, suspected: true }`.\n - Action `cancelResume(agentId)` exposée : retrait optimiste du compte à rebours + appel `input.cancelResume`, renvoie le verdict backend.\n\n## Fichier créé\n6. `frontend/src/features/agents/AgentLimitBadge.tsx` — composant présentational + 2 helpers purs exportés (`formatResetTime(epochMs)` → `HH:MM`, `formatCountdown(remainingMs)` → `Xm Ys`/`Ys`, clampé à 0). Badge « limité jusqu'à HH:MM » / « limité », compte à rebours vivant (clock locale 1 s armée seulement si `resumeFireAt`), bouton « Annuler la reprise ». Câblé dans `AgentsPanel.tsx` (import + rendu conditionnel sous chaque ligne d'agent quand `vm.limitByAgent[a.id]` existe).\n\n## Point de friction — filet humain niveau 3 (§21.1)\nConformément à la consigne, je n'ai **fabriqué aucune commande inexistante**. Le seul point d'entrée backend disponible est `cancel_resume` ; **il n'existe pas de commande pour enregistrer une heure de reprise saisie par l'utilisateur**. Donc pour `agentRateLimitSuspected` sans `resetsAtMs`, le badge affiche l'état « limité · heure inconnue — reprise à préciser » (état + `role=\"note\"`), sans formulaire de saisie. Un `TODO(LS-front)` documenté est posé dans `AgentLimitBadge.tsx` : câbler un formulaire d'heure dès que le backend exposera une commande pour persister l'instant choisi. Si tu veux ce filet pleinement fonctionnel, il faut une tâche backend (ex. `set_resume_at(agentId, epochMs)`) à cadrer avec Architect/DevBackend — je m'y connecterai côté front.\n\nPrêt pour QA (logique d'état dans le hook + helpers purs isolés du JSX, mocks en place avec `cancelledResumes`/`cancelResumeResult`)."}
|
||||
{"id":"f21219de-033a-4b3e-8e8b-366534112a95","conversation":"09b15ec3-ce2f-0af2-2903-ef699df9de41","atMs":1781676945137,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Deux livrables distincts sur `feature/agent-session-limits` (Git en fera 2 commits séparés — ne commit pas toi-même).\n\n═══ LIVRABLE 1 — LS8-front : formulaire de saisie d'heure (filet humain niveau 3) ═══\nLe backend expose maintenant une commande Tauri `set_resume_at(agentId: string, resetsAtMs: number) -> void` (argument nu `{ agentId, resetsAtMs }`, comme `cancel_resume`). Elle arme la MÊME reprise annulable que l'auto et réémet `agentResumeScheduled` — donc une fois appelée, ton badge bascule TOUT SEUL de « heure inconnue » vers l'état nominal « limité jusqu'à HH:MM » + compte à rebours + bouton Annuler (déjà câblés en LS7). Aucun nouvel événement à consommer.\n\nÀ FAIRE :\n1. Port : ajoute `setResumeAt(agentId: string, resetsAtMs: number): Promise<void>` à `InputGateway` (`src/ports/index.ts`), à côté de `cancelResume`.\n2. Adapter Tauri (`src/adapters/input.ts`) : `setResumeAt` → `invoke(\"set_resume_at\", { agentId, resetsAtMs })`.\n3. Mock (`src/adapters/mock/index.ts`) : `MockInputGateway.setResumeAt` enregistre dans un tableau (ex. `resumeArmings: { agentId, resetsAtMs }[]`) pour les tests ; suis le patron de `cancelledResumes`.\n4. Hook `useAgents` : expose une action `setResumeAt(agentId, resetsAtMs)` qui délègue au port (pas de mutation optimiste nécessaire — l'event `agentResumeScheduled` rebasculera l'état). \n5. UI `AgentLimitBadge.tsx` : sur l'état SUSPECTED SANS heure (`suspected === true` && pas de `limitedUntil`/`resumeFireAt`), remplace le TODO/placeholder par un petit formulaire : un input heure (type `time` ou `datetime-local`) + bouton « Programmer la reprise ». À la soumission : convertir la saisie en epoch-ms (si input `time` seul = aujourd'hui à HH:MM ; si déjà passé dans la journée, le backend clampe à maintenant ⇒ reprise immédiate, donc pas besoin de validation stricte côté front, mais documente le comportement). Appeler l'action `setResumeAt(agentId, epochMs)`. Garde un helper pur exporté pour la conversion (ex. `timeInputToEpochMs(value, now)`) pour la testabilité. Style cohérent avec le reste du badge ; désactive pendant `busy`.\n6. Retire le `TODO(LS-front)` posé en LS7 (le filet est maintenant complet).\n\n═══ LIVRABLE 2 — fix test préexistant (hygiène, commit séparé) ═══\n`src/adapters/mock/mock.test.ts:16` asserte « thirteen gateways » mais il y en a 14 (la gateway `permission` existe déjà dans HEAD). Mets le compteur à jour (14, et le libellé texte si présent) pour que `npx vitest run src/adapters/mock/` repasse vert. NE change RIEN d'autre dans ce fichier ; vérifie juste que le compte réel est bien 14 avant de figer le nombre.\n\nLance `npx vitest run src/features/agents/ src/adapters/mock/` + `tsc --noEmit` et RENDS-MOI via idea_reply : fichiers modifiés/créés par livrable, le helper de conversion exact, le résultat des deux commandes (typecheck + tests), et le nombre réel de gateways constaté. Structure pour testabilité (logique dans le hook + helpers purs). NE COMMIT PAS — Git tranche, en 2 commits séparés (LS8-front / fix-test)."}
|
||||
{"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":"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."}
|
||||
|
||||
Reference in New Issue
Block a user