@runeya/runeya 2.0.150 → 2.0.152
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/index.js +17 -8
- package/agent/index.js.map +1 -1
- package/agent-manager-SF4CAJJC.js +13 -0
- package/chat-Z7SFBEXS.js +28 -0
- package/{chunk-T6Y5KICH.js → chunk-26Q43CB2.js} +2 -2
- package/{chunk-76D52RTB.js → chunk-2DIW5EUT.js} +6 -6
- package/{chunk-3RCVOPTL.js → chunk-3C3R5ZVX.js} +2 -2
- package/{chunk-AZSHM3NM.js → chunk-3O54EIGT.js} +4 -4
- package/{chunk-HRNYFWGP.js → chunk-DTRZ5ZN7.js} +5 -5
- package/{chunk-UJ2OHNEN.js → chunk-EUTW2S4P.js} +87 -16
- package/chunk-EUTW2S4P.js.map +1 -0
- package/{chunk-SHSWOE5S.js → chunk-MGKGIHT4.js} +2 -4
- package/{chunk-SHSWOE5S.js.map → chunk-MGKGIHT4.js.map} +1 -1
- package/{chunk-ZVXPGKK5.js → chunk-PU6REPMY.js} +5 -5
- package/{chunk-ZVXPGKK5.js.map → chunk-PU6REPMY.js.map} +1 -1
- package/{chunk-OPVJAPUF.js → chunk-QD2OVLK4.js} +2 -2
- package/{chunk-XQGABRZY.js → chunk-QJSIN26R.js} +45 -38
- package/chunk-QJSIN26R.js.map +1 -0
- package/{chunk-UEK7QFWY.js → chunk-R4SKU6AO.js} +10 -12
- package/chunk-R4SKU6AO.js.map +1 -0
- package/{chunk-LJ7KLYFQ.js → chunk-RVZATF7R.js} +17 -9
- package/chunk-RVZATF7R.js.map +1 -0
- package/{chunk-64FCYYAB.js → chunk-TBNCGMDM.js} +70 -16
- package/chunk-TBNCGMDM.js.map +1 -0
- package/{companion-H24L2MGY.js → companion-ZDEGNZ2G.js} +11 -9
- package/{desktop-installer-page-V2NXWT43.js → desktop-installer-page-TBKSXVFD.js} +3 -3
- package/{desktop-launcher-46OJ5NAA.js → desktop-launcher-WBYP3KTF.js} +3 -3
- package/{environment-resolver-SY36Q67E.js → environment-resolver-R2GH6QOX.js} +6 -6
- package/{external-NPJBOEYV.js → external-UJ46WKBL.js} +3 -3
- package/index.js +15 -15
- package/index.js.map +1 -1
- package/{install-gate-6KHRGTTC.js → install-gate-TSCXUCYE.js} +2 -2
- package/mcp-server/external.js +15 -7
- package/mcp-server/external.js.map +1 -1
- package/mcp-server/index.js +15 -7
- package/mcp-server/index.js.map +1 -1
- package/package.json +1 -1
- package/{project-store-BYAE4SVZ.js → project-store-GBKM34QP.js} +7 -7
- package/settings-manager-VSOC3XGI.js +10 -0
- package/{src-AAOVGV5C.js → src-6WZKSNN7.js} +4 -2
- package/{src-OENXLZKQ.js → src-NWPED6TK.js} +34 -40
- package/src-NWPED6TK.js.map +1 -0
- package/{vault-resolver-B6BCVP47.js → vault-resolver-GGHULMG6.js} +3 -3
- package/web/assets/{AgentScalarChatInterface.vue-DwycfPE9.js → AgentScalarChatInterface.vue-0pB6D9jL.js} +1 -1
- package/web/assets/{AgentTerminalPage-BO1uzTvb.js → AgentTerminalPage-D1c5A1Ol.js} +1 -1
- package/web/assets/{AiChatModals-Ci8Essyi.js → AiChatModals-B_Cn5wNS.js} +1 -1
- package/web/assets/AppLayout-BsFZ6nG3.js +1 -0
- package/web/assets/{AppLayout-BlhwflMq.css → AppLayout-Djk3-XbN.css} +1 -1
- package/web/assets/{AuditPage-DC9xkftS.js → AuditPage-B_Ee8f0S.js} +1 -1
- package/web/assets/{CloudAccountPage-Ctf2gtpm.css → CloudAccountPage-CYcW_3tO.css} +1 -1
- package/web/assets/CloudAccountPage-D1L8Z_V3.js +1 -0
- package/web/assets/{CloudMessagingPage-DWgBG6iS.js → CloudMessagingPage-CyZF0sdY.js} +1 -1
- package/web/assets/{DashboardPage-6JrhJP8C.js → DashboardPage-BeWd0GWF.js} +1 -1
- package/web/assets/{DirectoryPickerDialog-B6tPuRZv.js → DirectoryPickerDialog-BGhbB66i.js} +1 -1
- package/web/assets/{EnvironmentForm-C-pr29o0.js → EnvironmentForm-B0FYydAK.js} +1 -1
- package/web/assets/EnvironmentForm-BnTLokUw.css +1 -0
- package/web/assets/{EnvironmentFormPage-DNBRaZH7.js → EnvironmentFormPage-BdTdOUBD.js} +1 -1
- package/web/assets/{EnvironmentListPage-D1Zom-yp.js → EnvironmentListPage-Cy9Kg5pY.js} +1 -1
- package/web/assets/{KanbanBoardPage-DeZRYB00.js → KanbanBoardPage-BOEK_sKt.js} +3 -3
- package/web/assets/{KanbanListPage-Bw0TFzqM.js → KanbanListPage-BOxLKDpW.js} +1 -1
- package/web/assets/{LaunchProjectPage-D1nMVxOW.js → LaunchProjectPage-zlY3PK3t.js} +1 -1
- package/web/assets/{LaunchServicesPage-CgondJcg.js → LaunchServicesPage-BsM0bP8N.js} +1 -1
- package/web/assets/{LaunchShell-DmAPUl7p.js → LaunchShell-BznQ7GLD.js} +1 -1
- package/web/assets/{LogViewer-B-rfzSpn.js → LogViewer-9e3e9X-R.js} +1 -1
- package/web/assets/{OrgKeyPrompt-DWkRmHYb.js → OrgKeyPrompt-Ckq1sJ8-.js} +1 -1
- package/web/assets/{ProcessControls-C7LX2uQD.js → ProcessControls-Br_aLfW7.js} +1 -1
- package/web/assets/ProcessDetailPage-B7bD2jmj.css +1 -0
- package/web/assets/ProcessDetailPage-D3BNcm7N.js +1 -0
- package/web/assets/{ProjectCreateDialog-C-VN-f05.js → ProjectCreateDialog-BzJRqXun.js} +1 -1
- package/web/assets/{ProjectLocationGate-CM6YNXYc.js → ProjectLocationGate-DjBwSAKc.js} +1 -1
- package/web/assets/{ProjectPublishDialog-CwItEKmm.js → ProjectPublishDialog-yakoPg1D.js} +1 -1
- package/web/assets/{RandomJobTestPage-BY6kBvm3.js → RandomJobTestPage-Dh0-shVq.js} +1 -1
- package/web/assets/{ScenarioEditorPage-BtKSjE2X.css → ScenarioEditorPage-BwLBCVeS.css} +1 -1
- package/web/assets/ScenarioEditorPage-DEdlwmf3.js +14 -0
- package/web/assets/{SendToChatDialog-uXvbEybU.js → SendToChatDialog-_ai4UP8l.js} +1 -1
- package/web/assets/{ServiceControlsBar-CoKPrw7b.js → ServiceControlsBar-Bo-jHhJK.js} +1 -1
- package/web/assets/ServiceDetailPage-CWB3XIMA.js +2 -0
- package/web/assets/ServiceDetailPage-DuXvqXk4.css +1 -0
- package/web/assets/{ServiceFormPage-CS9rZyjG.js → ServiceFormPage-BzARwyAF.js} +1 -1
- package/web/assets/{SettingsPage-ByJ9v8kA.js → SettingsPage-CsIXEQrW.js} +7 -7
- package/web/assets/{SettingsPage-B951NEBm.css → SettingsPage-NpFX7FAy.css} +1 -1
- package/web/assets/{UnlockPage-CavRr6dk.js → UnlockPage-C08Itng5.js} +1 -1
- package/web/assets/{VaultMigrationPage-C9-taMLq.js → VaultMigrationPage-BLZc5pXH.js} +1 -1
- package/web/assets/{VersionBadge-D5eE6rPL.js → VersionBadge-lBKbqt98.js} +1 -1
- package/web/assets/{WikiPage-op2Y2UTo.js → WikiPage-RCU0T1IS.js} +1 -1
- package/web/assets/{WikiView-CpiYONPK.js → WikiView-BV8MZ8qm.js} +1 -1
- package/web/assets/{add-scalar-classes-t1J2lZx-.js → add-scalar-classes-CvUuKrM6.js} +1 -1
- package/web/assets/{agent.store-CmFNwq8b.js → agent.store-yrwtq9oP.js} +1 -1
- package/web/assets/{auth-BlB_Xe9D.js → auth-LbzJj0JF.js} +2 -2
- package/web/assets/{cloud-chat.store-30i6G2hv.js → cloud-chat.store-y_9urSQZ.js} +1 -1
- package/web/assets/{cloud-client-Dqa_CDfk.js → cloud-client-DhjJyZHU.js} +2 -2
- package/web/assets/{cloud-session-D04EJ2X2.js → cloud-session-BlihYcy2.js} +2 -2
- package/web/assets/dist-C9Qdh18x.js +1 -0
- package/web/assets/i18n-DxVwqoxL.js +1 -0
- package/web/assets/{index-B7Lb37Xl.js → index-Ci2pSJuu.js} +7 -7
- package/web/assets/{index-CnXzQkLZ.css → index-Ck_3m_I9.css} +1 -1
- package/web/assets/{modal-BUQs190g.js → modal-KpWdqKSY.js} +1 -1
- package/web/assets/notification.store-Bp37Xsss.js +1 -0
- package/web/assets/{project-launch.store-BsXk5Ngh.js → project-launch.store-AB257LRF.js} +1 -1
- package/web/assets/{project.store-BAx14h4J.js → project.store-qBZ_R1td.js} +1 -1
- package/web/assets/{scenario.store-BHnWFonP.js → scenario.store-B5ybSSzx.js} +1 -1
- package/web/assets/{service.store-BdRBLSCW.js → service.store-BrKRjCtb.js} +1 -1
- package/web/assets/{settings-scope-DSoix6om.js → settings-scope-BSdUKgjx.js} +1 -1
- package/web/assets/settings.store-C8guF9Fl.js +1 -0
- package/web/assets/{src-Dp7BndBf.js → src-Br-M9ln7.js} +1 -1
- package/web/assets/{src-Bfb0PBSC.css → src-dQGrnDw0.css} +1 -1
- package/web/assets/{src-CCCeCa_V.js → src-recq0B6o.js} +2 -2
- package/web/assets/{trpc-D2QgyyrU.js → trpc-CAKhySL_.js} +3 -3
- package/web/assets/{useCloudOrg-iYu-mVBY.js → useCloudOrg-B_JPalTv.js} +1 -1
- package/web/assets/useEnvVarAutocomplete-DCZCMfLC.js +1 -0
- package/web/assets/useEnvVarAutocomplete-gV2loQYv.css +1 -0
- package/web/assets/{useProjectCatalog-ByEs2zGK.js → useProjectCatalog-CCJAkQi_.js} +1 -1
- package/web/assets/{useProjectCloudOrg-Fo1WYzwi.js → useProjectCloudOrg-CG4CriU2.js} +1 -1
- package/web/assets/{useServiceUrlHost-DzjlYxkb.js → useServiceUrlHost-C1gL6Mjg.js} +1 -1
- package/web/index.html +17 -17
- package/agent-manager-5VR5TDBZ.js +0 -13
- package/chat-EMWEVXGR.js +0 -28
- package/chunk-64FCYYAB.js.map +0 -1
- package/chunk-DA2DZBTL.js +0 -43
- package/chunk-DA2DZBTL.js.map +0 -1
- package/chunk-LJ7KLYFQ.js.map +0 -1
- package/chunk-UEK7QFWY.js.map +0 -1
- package/chunk-UJ2OHNEN.js.map +0 -1
- package/chunk-XQGABRZY.js.map +0 -1
- package/mcp-ready-TAPBAOZY.js +0 -9
- package/settings-manager-GIRMJQKL.js +0 -10
- package/src-OENXLZKQ.js.map +0 -1
- package/vault-resolver-B6BCVP47.js.map +0 -1
- package/web/assets/AppLayout-f6SKHFJd.js +0 -1
- package/web/assets/CloudAccountPage-uK9FpJ4U.js +0 -1
- package/web/assets/EnvironmentForm-CkKVSiT7.css +0 -1
- package/web/assets/ProcessDetailPage-CJcIxbLm.css +0 -1
- package/web/assets/ProcessDetailPage-DOnYh6nd.js +0 -1
- package/web/assets/ScenarioEditorPage-bDoQIzB2.js +0 -14
- package/web/assets/ServiceDetailPage-DnSf5cm9.css +0 -1
- package/web/assets/ServiceDetailPage-WlcVr1uy.js +0 -2
- package/web/assets/dist-DTLmCuqE.js +0 -1
- package/web/assets/i18n-D3P4ZExP.js +0 -1
- package/web/assets/notification.store-CxPriNmY.js +0 -1
- package/web/assets/settings.store-CIAnRUSA.js +0 -1
- package/web/assets/useEnvVarAutocomplete-7Ok6yien.js +0 -1
- package/web/assets/useEnvVarAutocomplete-Czt8ZZ1E.css +0 -1
- /package/{agent-manager-5VR5TDBZ.js.map → agent-manager-SF4CAJJC.js.map} +0 -0
- /package/{chat-EMWEVXGR.js.map → chat-Z7SFBEXS.js.map} +0 -0
- /package/{chunk-T6Y5KICH.js.map → chunk-26Q43CB2.js.map} +0 -0
- /package/{chunk-76D52RTB.js.map → chunk-2DIW5EUT.js.map} +0 -0
- /package/{chunk-3RCVOPTL.js.map → chunk-3C3R5ZVX.js.map} +0 -0
- /package/{chunk-AZSHM3NM.js.map → chunk-3O54EIGT.js.map} +0 -0
- /package/{chunk-HRNYFWGP.js.map → chunk-DTRZ5ZN7.js.map} +0 -0
- /package/{chunk-OPVJAPUF.js.map → chunk-QD2OVLK4.js.map} +0 -0
- /package/{companion-H24L2MGY.js.map → companion-ZDEGNZ2G.js.map} +0 -0
- /package/{desktop-installer-page-V2NXWT43.js.map → desktop-installer-page-TBKSXVFD.js.map} +0 -0
- /package/{desktop-launcher-46OJ5NAA.js.map → desktop-launcher-WBYP3KTF.js.map} +0 -0
- /package/{environment-resolver-SY36Q67E.js.map → environment-resolver-R2GH6QOX.js.map} +0 -0
- /package/{external-NPJBOEYV.js.map → external-UJ46WKBL.js.map} +0 -0
- /package/{install-gate-6KHRGTTC.js.map → install-gate-TSCXUCYE.js.map} +0 -0
- /package/{mcp-ready-TAPBAOZY.js.map → project-store-GBKM34QP.js.map} +0 -0
- /package/{project-store-BYAE4SVZ.js.map → settings-manager-VSOC3XGI.js.map} +0 -0
- /package/{settings-manager-GIRMJQKL.js.map → src-6WZKSNN7.js.map} +0 -0
- /package/{src-AAOVGV5C.js.map → vault-resolver-GGHULMG6.js.map} +0 -0
package/chunk-UJ2OHNEN.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../server/src/services/companion.ts","../../server/src/services/conversation-store.ts","../../server/src/services/workspace-cwd.ts","../../server/src/services/turn-events.ts","../../server/src/services/claude-transcript-sync.ts","../../server/src/services/codex-transcript-sync.ts","../../server/src/services/auth-token.ts","../../server/src/services/live-pty.ts","../../server/src/services/pty-input.ts","../../server/src/services/active-session-registry.ts"],"sourcesContent":["/**\n * Le compagnon — un second CLI qui relit la conversation et rappelle la première.\n *\n * À chaque fin de tour d'une conversation qui l'a activé, une seconde session\n * CLI relit ce qui vient de se passer. Si elle trouve une régression ou un\n * effet de bord, sa trouvaille est injectée dans la conversation principale,\n * qui repart dessus.\n *\n * Le prompt ne porte que le dernier échange : le compagnon est un CLI outillé,\n * il va chercher l'état du dépôt lui-même. Lui injecter le diff coûterait des\n * jetons pour un contenu qu'il lit plus précisément — et qu'il faudrait\n * tronquer, donc amputer, au-delà de quelques dizaines de milliers de\n * caractères.\n *\n * Pourquoi un second agent plutôt qu'une consigne à l'agent principal : celui\n * qui vient d'écrire le code est le pire relecteur de ce code, parce qu'il\n * relit ce qu'il VOULAIT écrire. Un contexte neuf n'a pas cet angle mort.\n *\n * Trois garde-fous, chacun contre une façon dont ce mécanisme se retourne :\n *\n * - Le compagnon travaille dans SA conversation. Mêlées au fil principal, ses\n * remarques rentreraient dans le contexte du tour suivant, et l'agent\n * finirait par se relire lui-même.\n * - Un compagnon ne s'engendre pas (`isCompanion`), sinon la fin de son propre\n * tour en lancerait un autre, indéfiniment.\n * - Un plafond de relances consécutives. Un LLM sait toujours produire une\n * remarque plausible ; sans plafond, la boucle ne se ferme jamais.\n */\nimport { EventEmitter } from 'node:events';\nimport { conversationStore, type Conversation, type ConversationChangeEvent } from './conversation-store.js';\nimport { resolveConversationCwd } from './workspace-cwd.js';\nimport { projectStore } from './project-store.js';\nimport {\n COMPANION_RELAY_OPEN,\n COMPANION_RELAY_CLOSE,\n extractCompanionRelay,\n type CompanionSettings,\n} from '@runeya/packages-shared';\nimport { onTurnFinished } from './turn-events.js';\nimport { syncConversationFromClaudeTranscript } from './claude-transcript-sync.js';\nimport { syncConversationFromCodexTranscript } from './codex-transcript-sync.js';\nimport { signJwt } from './auth-token.js';\nimport { agentManager } from './agent-manager.js';\nimport { findLivePty } from './live-pty.js';\nimport { submitToPty } from './pty-input.js';\nexport { flattenForPty } from './pty-input.js';\nimport { activeSessionRegistry } from './active-session-registry.js';\n\n/** Au-delà, on rend la main à l'utilisateur plutôt que de relancer sans fin. */\nexport const MAX_CONSECUTIVE_RELAUNCHES = 6;\n/**\n * Idem pour l'échange relu.\n *\n * 100 000 caractères — de l'ordre de 25 000 jetons, ce qu'une fenêtre de\n * contexte moderne absorbe sans peine. La borne précédente (20 000) coupait\n * des tours entiers dès qu'un diff ou une sortie de commande entrait dans\n * l'échange, et le compagnon jugeait alors sur un extrait amputé par le haut,\n * sans que rien ne le signale.\n */\nexport const MAX_EXCHANGE_CHARS = 100_000;\n\nconst VERDICT_NOTHING = 'RAS';\n\n/**\n * Les bornes de ce que le compagnon veut transmettre.\n *\n * Sans elles, tout son dernier message partait dans la conversation : il ne\n * pouvait donc ni raisonner à voix haute, ni annoncer ce qu'il inspecte, sans\n * que ce brouillon soit relayé. Les bornes séparent son travail de son message.\n *\n * Elles sont obligatoires. Elles furent facultatives — un modèle en oublie, et\n * un message sans bornes valait mieux qu'un silence — jusqu'à la boucle : le\n * compagnon annonce « Je vérifie… » sans bornes, l'annonce part, l'agent accuse\n * réception, le compagnon relit et annonce de nouveau, six fois. Sans bornes,\n * rien ne part ; le protocole est rappelé à chaque tour, et une remarque\n * oubliée reste lisible dans la conversation du compagnon.\n *\n * Définies dans `packages/shared` : le panneau doit afficher exactement ce qui\n * est relayé, donc extraire de la même façon.\n */\nconst RELAY_OPEN = COMPANION_RELAY_OPEN;\nconst RELAY_CLOSE = COMPANION_RELAY_CLOSE;\n\n/** Relances déjà consommées, par conversation principale. */\nconst relaunches = new Map<string, number>();\n\nexport function resetRelaunches(conversationId: string): void {\n relaunches.delete(conversationId);\n}\n\n/**\n * Prendre un jeton de relance, s'il en reste.\n *\n * Le plafond doit casser une chaîne qui s'emballe, pas éteindre le compagnon\n * pour le restant de la conversation : en l'atteignant, on saute ce tour ET on\n * rend le budget. Le tour suivant — celui que l'utilisateur relance lui-même —\n * repart donc avec un compteur neuf, et la chaîne, elle, est bien coupée.\n */\nexport function takeRelaunchSlot(conversationId: string): boolean {\n const used = relaunches.get(conversationId) ?? 0;\n if (used >= MAX_CONSECUTIVE_RELAUNCHES) {\n relaunches.delete(conversationId);\n return false;\n }\n relaunches.set(conversationId, used + 1);\n return true;\n}\n\n/**\n * Ce que le compagnon veut transmettre, ou rien.\n *\n * Un seul mot est réservé : `RAS` signifie « je n'ai rien à dire », et rien ne\n * part. Tout le reste est envoyé tel quel à la conversation principale.\n *\n * Il n'y a pas deux façons de répondre. Un relecteur peut vouloir poser une\n * question, demander une précision ou signaler un doute qu'il ne sait pas encore\n * localiser : lui imposer un mot-clé et une référence `fichier:ligne` le rendait\n * muet dans tous ces cas. Dire « c'est bon », en revanche, ne se transmet pas :\n * c'est `RAS` (voir « rien de neuf, rien du tout » dans les consignes), faute de quoi chaque accusé de\n * réception relance l'agent, qui y répond.\n *\n * Ce qui borne la boucle n'est donc pas la forme de la réponse mais le plafond\n * de relances consécutives (`takeRelaunchSlot`) — le seul garde-fou qui tienne\n * quelle que soit la liberté laissée au compagnon.\n */\nexport function relayableMessage(raw: string): string | null {\n const text = (raw ?? '').trim();\n if (!text) return null;\n\n // Seules les bornes font partir quelque chose ; le reste est le brouillon du\n // compagnon, annonces de travail comprises.\n const inner = extractCompanionRelay(text);\n if (inner === null) return null;\n return isNothing(inner) ? null : inner;\n}\n\n/** `RAS` seul, ou en conclusion : le compagnon a regardé et n'envoie rien. */\nfunction isNothing(text: string): boolean {\n const bare = (v: string) => v.replace(/^[\\s\"'«»([.,;:—-]+|[\\s\"'«»)\\].,;:!—-]+$/g, '');\n if (bare(text).toUpperCase() === VERDICT_NOTHING) return true;\n const lines = text.split('\\n').map(l => l.trim()).filter(Boolean);\n const last = lines[lines.length - 1] ?? '';\n return bare(last).toUpperCase() === VERDICT_NOTHING;\n}\n\n/** Jusqu'où le compagnon a déjà relu, par conversation. */\nconst reviewedUpTo = new Map<string, number>();\n\nexport function forgetReviewProgress(conversationId: string): void {\n reviewedUpTo.delete(conversationId);\n}\n\n/**\n * Les balises que le harnais du CLI pose autour d'une commande locale.\n *\n * Un `/resume`, un `/clear`, un `/cost` ne sont pas des tours d'échange : c'est\n * l'interface qui parle au terminal. Le transcript les enregistre pourtant\n * comme des messages `user` ordinaires, avec leurs balises et un « DO NOT\n * respond to these messages » qui s'adresse au harnais, pas au compagnon.\n */\nconst LOCAL_COMMAND_TAGS = /<\\/?(local-command-caveat|local-command-stdout|local-command-stderr|command-name|command-message|command-args)>/g;\n\n/**\n * Ce tour est-il une commande locale plutôt qu'un échange ?\n *\n * Deux conséquences, chacune vue à l'usage :\n * - il ne sert pas d'ancre à la relecture, sinon « Relancer maintenant » juste\n * après un `/resume` repartait sur ce seul tour, sans réponse d'assistant\n * derrière — il n'y avait rien à relire et le bouton paraissait mort ;\n * - il ne compte pas comme du neuf : lancer un tour de modèle pour relire une\n * sortie de commande, c'est payer un RAS certain.\n */\nfunction isLocalCommandTurn(content: string | undefined): boolean {\n const text = content ?? '';\n return text.includes('<command-name>')\n || text.includes('<local-command-stdout>')\n || text.includes('<local-command-stderr>');\n}\n\n/**\n * Neutraliser ces balises avant de les montrer au compagnon.\n *\n * Elles arrivaient brutes dans son prompt, où elles se lisent comme des\n * consignes venues de l'utilisateur alors qu'elles viennent du harnais. On\n * garde le texte — la commande et sa sortie disent quelque chose — on lui\n * retire seulement sa forme de balise.\n */\nfunction defuseHarnessTags(content: string): string {\n return content.replace(LOCAL_COMMAND_TAGS, (_m, name: string) => `[${name}]`);\n}\n\n/**\n * Ce que le compagnon n'a pas encore relu.\n *\n * Le compagnon garde sa session d'un tour à l'autre : ce qu'on lui envoie n'est\n * donc pas la conversation, mais son delta — les messages parus depuis sa\n * dernière relecture. C'est le sens même d'une relecture : ce qui a changé\n * entre deux tours.\n *\n * Le repère n'avance que quand une relecture a bien eu lieu. Un tour sauté —\n * compagnon occupé, plafond atteint, agent injoignable — est donc rattrapé au\n * tour suivant au lieu d'échapper à toute relecture.\n *\n * À la première relecture il n'y a pas de repère : on s'en tient au dernier\n * aller-retour, plutôt que de déverser un historique entier dont l'essentiel\n * est déjà derrière.\n */\nexport interface ReviewWindow {\n /** Où commencer à lire. */\n from: number;\n /** Y a-t-il quelque chose de neuf depuis la dernière relecture ? */\n hasNew: boolean;\n}\n\n/**\n * Ce que le compagnon doit lire, et s'il y a lieu de le lancer.\n *\n * Deux questions distinctes, et les confondre était le défaut :\n *\n * - « y a-t-il du neuf ? » se juge sur le repère de relecture ;\n * - « à partir d'où lire ? » remonte au dernier message de l'utilisateur.\n *\n * Un tour produit plusieurs messages, et un CLI peut déclarer plusieurs fins de\n * tour pour une seule demande — Codex le fait : une pour le lancement d'un\n * sous-agent, une pour la réponse finale. La première relecture consommait\n * alors la demande et avançait le repère ; la seconde ne recevait plus que des\n * réponses d'assistant, sans rien qui lui soit adressé. Le compagnon répondait\n * RAS, et ça passait pour une panne.\n *\n * La demande est donc toujours incluse, même déjà relue : c'est elle qui dit ce\n * qu'on jugeait. Ce qu'elle coûte en jetons est marginal — le compagnon garde\n * sa session, il l'a déjà vue.\n */\nexport function reviewWindow(\n messages: Pick<Conversation, 'messages'>['messages'],\n reviewed: number | undefined,\n): ReviewWindow {\n const list = messages ?? [];\n const count = list.length;\n\n let lastUser = -1;\n for (let i = count - 1; i >= 0; i--) {\n const m = list[i]!;\n if (m.role === 'user' && !isLocalCommandTurn(m.content)) { lastUser = i; break; }\n }\n\n // Ce qui n'est arrivé que sous forme de commande locale n'est pas du neuf à\n // relire : le repère peut être en retard sans qu'il y ait rien à dire.\n let lastReal = 0;\n for (let i = count - 1; i >= 0; i--) {\n const m = list[i]!;\n if (m.role !== 'user' || !isLocalCommandTurn(m.content)) { lastReal = i + 1; break; }\n }\n\n // Une conversation peut avoir rétréci (messages purgés) : ne jamais repartir\n // au-delà de ce qui existe.\n const mark = reviewed === undefined\n ? (lastUser >= 0 ? lastUser : Math.max(0, count - 2))\n : Math.min(Math.max(0, reviewed), count);\n\n return {\n from: lastUser >= 0 ? Math.min(mark, lastUser) : mark,\n hasNew: lastReal > mark,\n };\n}\n\n/** Les messages non relus, mis en forme et bornés. */\nexport function formatExchange(messages: Pick<Conversation, 'messages'>['messages'], from: number): string {\n const text = (messages ?? []).slice(from)\n // `contextNote` porte ce que l'interface a attaché au tour : le service que\n // l'utilisateur consultait, un diff joint, un contexte collé. C'est du\n // contexte que l'agent principal a EU sous les yeux ; l'omettre ferait juger\n // le compagnon sur un échange amputé.\n .map(m => [`[${m.role}]`, m.contextNote ? `(contexte : ${m.contextNote})` : '', defuseHarnessTags(m.content ?? '')]\n .filter(Boolean).join('\\n'))\n .join('\\n\\n');\n // On tronque par la TÊTE : le plus récent est le plus pertinent à relire.\n return text.length > MAX_EXCHANGE_CHARS ? text.slice(-MAX_EXCHANGE_CHARS) : text;\n}\n\n/**\n * Les consignes complètes, posées dans le contexte système du compagnon.\n *\n * Pas dans le message du tour : le contexte système est repassé au CLI à\n * chaque lancement, reprise comprise (`--append-system-prompt` pour Claude\n * Code, `developerInstructions` pour Codex), et le compactage de la session ne\n * le résume pas. Un message du premier tour, lui, pouvait disparaître dans un\n * résumé, et le compagnon perdait son rôle et ses bornes sans rien signaler.\n */\nfunction fullInstructions(): string {\n return [\n 'Tu es le compagnon de relecture de cette conversation. Un autre agent y',\n \"travaille pour une personne qui suit l'échange. Ton rôle : vérifier que ce\",\n \"qu'il livre est correct et complet par rapport à ce qui a été demandé, et le\",\n \"lui signaler quand ce n'est pas le cas, avant que la personne ne le découvre.\",\n '',\n 'Ce que tu écris entre les bornes arrive dans sa conversation, préfixé par',\n `${COMPANION_SIGNATURE}, et le relance. Un défaut que tu laisses passer coûte plus cher`,\n \"qu'une remarque en trop : l'agent vérifie et écarte en une phrase ce qui est\",\n \"faux. Dans le doute, signale-le en disant que tu n'en es pas sûr.\",\n '',\n 'Tu ne modifies rien : ni le code, ni la carte kanban, que tu peux lire mais',\n 'pas déplacer ni commenter. Tu ne délègues pas à un sous-agent. Si une carte',\n 'kanban est fournie, c\\'est la demande : vérifie que le travail la couvre.',\n '',\n \"Images : l'échange ne porte que leur chemin, [Image: source: /chemin.png].\",\n 'Ouvre-le avec ton outil de lecture. Si la lecture échoue, dis-le. N\\'invente',\n \"jamais le contenu d'une image.\",\n '',\n 'CE QUE TU CHERCHES',\n \"- La demande : le travail couvre-t-il tout ce qui a été demandé (message de\",\n \" la personne, carte kanban) ? Rien d'oublié, rien d'ajouté hors sujet ?\",\n '- Les règles du projet : lis CLAUDE.md, AGENTS.md ou leur équivalent s\\'ils',\n ' existent, et vérifie que le travail les respecte.',\n '- La justesse : cas limites, valeurs vides, erreurs non traitées, chemins que',\n ' le code rend atteignables sans les gérer.',\n '- Les régressions et effets de bord : les appelants du code modifié, les',\n ' comportements qui changent sans que la demande le prévoie.',\n \"- La duplication : une fonction, un composant ou une constante qui existait\",\n \" déjà et qu'on a réécrit à côté.\",\n '- La sécurité : entrées non validées, secrets exposés, contrôles d\\'accès',\n ' retirés ou absents.',\n '- Les tests : un test ajouté vérifie-t-il vraiment le comportement ? Un test',\n ' affaibli, désactivé ou supprimé pour passer au vert ?',\n \"- Les affirmations : « les tests passent », « c'est vérifié », « rien d'autre\",\n \" n'utilise ce code ». Contrôle-les dans le code ou la sortie montrée.\",\n '',\n 'QUAND PARLER',\n \"- Si l'échange te parle ou te pose une question, réponds toujours.\",\n '- Sinon, parle si tu as trouvé un défaut ou si tu en soupçonnes un.',\n \"- Pas d'accusé de réception, de remerciement ni de « rien à signaler » : chacun\",\n \" relance l'agent. Quand il répond à ta remarque, tais-toi, sauf s'il pose une\",\n ' question ou si sa réponse est fausse.',\n '',\n 'COMMENT RELIRE',\n '- Lis le code réel : `git diff`, `git status`, les fichiers touchés, leurs',\n \" appelants. Ne te fie ni aux commentaires, ni aux messages de commit, ni à ce\",\n \" qu'affirme l'agent : ils donnent l'intention, pas le code.\",\n \"- Ce tour répond de ce qu'il modifie et de ce qu'il rend atteignable, pas de ce\",\n \" qui existait sans lui.\",\n '- Sécurité : regarde aussi ce qui a été retiré, et les contrôles absents sur',\n \" chaque entrée qui mène à la ressource.\",\n \"- Cite fichier et ligne. Classe : bloquant, risqué, mineur. Dis ce que tu n'as\",\n ' pas pu vérifier.',\n '- Pas de tests complets, build, typecheck global ni serveur : s\\'il en faut, dis',\n \" à l'agent quoi lancer.\",\n \"- Ne resignale pas ce qui a déjà été signalé, sauf si ce n'est pas traité.\",\n ].join('\\n');\n}\n\n/**\n * Le protocole de réponse — TOUJOURS ajouté, jamais personnalisable.\n *\n * Ce n'est pas du conseil, c'est le contrat par lequel Runeya lit le verdict :\n * `RAS` signifie « rien à transmettre », et les bornes délimitent ce qui part\n * dans la conversation. Laisser remplacer ce bloc casserait la lecture — le\n * compagnon parlerait dans le vide, ou son brouillon partirait dans le fil.\n */\nfunction responseProtocol(): string {\n return [\n 'COMMENT RÉPONDRE :',\n \" · Si tu n'as rien à dire — et seulement dans ce cas — réponds exactement :\",\n ` ${VERDICT_NOTHING}`,\n ' Rien ne sera transmis.',\n ' · Sinon, écris ce que tu veux lui dire ENTRE CES DEUX BORNES :',\n ` ${RELAY_OPEN}`,\n ' ton message',\n ` ${RELAY_CLOSE}`,\n ' Seul ce qui est entre les bornes lui est transmis, tel quel. Ce que tu',\n ' écris en dehors reste ton brouillon : tu peux y réfléchir à voix haute,',\n ' dire ce que tu inspectes, personne ne le lira. Sans bornes, RIEN ne part —',\n ' une remarque hors bornes est perdue.',\n '',\n ].join('\\n');\n}\n\n/**\n * Le rappel des tours suivants.\n *\n * Court, mais pas vide : un modèle dérive sur une consigne de forme qu'il n'a\n * plus sous les yeux depuis dix tours. On redit donc les deux seules choses qui\n * cassent tout si elles sont oubliées — le mot réservé et les bornes — et rien\n * d'autre.\n */\nfunction reminder(): string {\n return [\n 'Tour à relire. Tes consignes sont dans ton contexte système.',\n `Rien à dire → ${VERDICT_NOTHING}. Sinon, ton message entre ${RELAY_OPEN} et ${RELAY_CLOSE} — sans bornes, rien ne part.`,\n \"Si l'échange t'adresse quelque chose, tu réponds — jamais \" + VERDICT_NOTHING + ' dans ce cas.',\n \"Mais l'agent qui répond à ta remarque ne te pose pas de question : sans défaut ni\",\n `question neuve, ${VERDICT_NOTHING}. Jamais d'accusé de réception ni de « rien à signaler ».`,\n ].join('\\n');\n}\n\n/** Les consignes de Runeya, gardées hors du texte modifiable par l'utilisateur. */\nexport function companionDefaultPrompt(): string {\n return fullInstructions();\n}\n\n/**\n * Le commit de départ, redit à chaque tour.\n *\n * Au premier tour comme dans le rappel : c'est une donnée, pas une consigne, et\n * le compagnon en a besoin à chaque relecture.\n */\nfunction baseLine(baseSha: string): string {\n return `Point de départ : le travail de la conversation, commits compris, est dans \\`git diff ${baseSha}\\`.`;\n}\n\n/**\n * Ce que le compagnon reçoit en contexte système : les consignes de Runeya,\n * les spécificités de l'utilisateur, puis le protocole de réponse.\n */\nexport function companionInstructions(custom?: string): string {\n const customInstructions = custom?.trim();\n return [\n '## Tes consignes de compagnon de relecture',\n '',\n fullInstructions(),\n ...(customInstructions\n ? [\n '',\n 'SPÉCIFICITÉS AJOUTÉES PAR L’UTILISATEUR :',\n customInstructions,\n ]\n : []),\n '',\n // Le protocole, lui, s'ajoute TOUJOURS : c'est par lui que Runeya lit le\n // verdict. Remplaçable, il rendrait le compagnon illisible.\n responseProtocol(),\n ].join('\\n');\n}\n\n/**\n * Le message d'un tour : le rappel court, le commit de départ et l'échange.\n *\n * Les consignes n'y sont pas, elles sont dans le contexte système\n * (`companionInstructions`). Le rappel redit seulement ce qui casse tout s'il\n * est oublié : le mot réservé et les bornes.\n */\nexport function buildCompanionPrompt(exchange: string, baseSha?: string): string {\n return [\n reminder(),\n ...(baseSha ? ['', baseLine(baseSha)] : []),\n '',\n \"--- Ce que tu n'as pas encore relu de la conversation ---\",\n exchange || '(vide)',\n ].join('\\n');\n}\n\n/**\n * Le compagnon se nomme.\n *\n * Ce message arrive dans la conversation principale comme un tour utilisateur :\n * sans signature, l'agent le prend pour une demande de la personne devant lui\n * et lui répond comme tel — il s'excuserait, ou traiterait la remarque comme un\n * ordre plutôt que comme une hypothèse à vérifier. Le marqueur est en tête pour\n * qu'il survive à une troncature de contexte.\n */\nexport const COMPANION_SIGNATURE = '[COMPAGNON]';\n\n/**\n * Le message injecté dans la conversation principale.\n *\n * Réduit à la signature : le compagnon écrit ce qu'il veut, et l'envelopper de\n * consignes réécrirait ses mots. Ce que le destinataire doit savoir — d'où ça\n * vient, comment le traiter — lui a déjà été dit dans son contexte système\n * (`companionSystemNote`), une fois, plutôt qu'à chaque message.\n */\nexport function buildRelaunchMessage(message: string): string {\n return `${COMPANION_SIGNATURE} ${message.trim()}`;\n}\n\n/**\n * Ce que TOUTE conversation CLI apprend d'elle-même, compagnon actif ou non.\n *\n * Dit dans tous les cas parce que l'agent ne sait pas — et n'a pas à savoir —\n * si le réglage est allumé : il peut être activé en cours de conversation, et\n * un agent qui découvrirait le compagnon au moment où celui-ci le reprend\n * prendrait la reprise pour un reproche de l'utilisateur.\n */\nexport function companionSystemNote(): string {\n return [\n '## Relecture par un compagnon',\n '',\n 'Cette conversation peut être relue par un second agent, le « compagnon ».',\n \"S'il est actif, il lit chaque tour que tu produis ainsi que les\",\n 'modifications du dépôt.',\n '',\n `Quand il a quelque chose à te dire, son message t'arrive préfixé par`,\n `${COMPANION_SIGNATURE}. Il peut te signaler une régression, poser une`,\n 'question ou demander une précision — il écrit librement. Quand il n\\'a rien',\n 'à dire, il garde le silence : pas de message ne veut pas dire qu\\'il est absent.',\n '',\n \"Ce n'est PAS un sous-agent : tu ne le lances pas, tu ne peux pas lui parler.\",\n \"Il lit tes tours de lui-même et t'écrit quand il veut. Si on te demande de le\",\n \"solliciter, ne lance rien — dis que tu laisses le tour lui parvenir.\",\n '',\n \"Un tel message ne vient PAS de l'utilisateur : c'est une relecture, à\",\n \"vérifier avant d'agir. Si elle est juste, corrige la cause ; si elle est\",\n \"fausse, dis-le en une phrase et poursuis. Ne t'excuse pas auprès de\",\n \"l'utilisateur pour une remarque du compagnon, et ne la lui attribue pas.\",\n '',\n \"Écris donc en sachant que ton travail peut être relu : dis ce que tu as vérifié\",\n \"et ce que tu n'as pas vérifié, plutôt que d'affirmer sans preuve.\",\n ].join('\\n');\n}\n\n/**\n * L'agent où lancer le compagnon : celui où tourne la conversation relue.\n *\n * Un terminal interactif se lance sur l'agent d'un service, éventuellement\n * distant. Le compagnon lancé sur l'agent par défaut inspecterait alors un\n * autre arbre de travail — et rendrait des verdicts sur le mauvais dépôt, sans\n * que rien n'échoue.\n *\n * Deux sources, dans cet ordre, parce qu'une conversation tourne de deux\n * façons : un PTY vivant (terminal) dit où elle tourne EN CE MOMENT ; sinon la\n * dernière session enregistrée dit où le tour qu'on vient de relire s'est\n * exécuté — le registre garde cette session après la fin du tour, et le\n * compagnon part précisément à ce moment-là. Le PTY passe devant : il décrit\n * l'état courant, la session décrit le passé.\n *\n * Aucune des deux ? Il n'y a rien à suivre — une session Claude lancée hors de\n * Runeya, par exemple, tourne sur la machine du serveur. L'appelant laisse\n * alors le démarreur retomber sur l'agent par défaut, comme avant.\n */\nexport function companionAgentId(\n livePtyAgentId: string | undefined,\n lastSessionAgentId: string | undefined,\n): string | undefined {\n return livePtyAgentId ?? lastSessionAgentId;\n}\n\n/**\n * Un tour vient de finir sur `conversationId` : le compagnon doit-il parler ?\n *\n * Sortie silencieuse dans tous les cas où il n'a rien à faire — cette fonction\n * est appelée à CHAQUE fin de tour, y compris sur les conversations qui\n * n'utilisent pas le compagnon.\n */\n/**\n * Une ligne par sortie, avec sa raison.\n *\n * Ce chemin a huit portes de sortie et aucune ne laissait de trace : un\n * compagnon muet était indiscernable d'un compagnon jamais déclenché, et le\n * diagnostic se faisait par hypothèses successives. Le coût est d'une ligne de\n * journal par tour relu.\n */\nfunction trace(conversationId: string, reason: string): void {\n console.log(`[companion] ${conversationId.slice(0, 8)} — ${reason}`);\n}\n\n/**\n * Le compagnon relit-il cette conversation ?\n *\n * Pas un compagnon : il ne s'engendre pas. Pas un scénario : son exécution\n * n'annonce pas de fin de tour, son travail vit hors des messages, et il n'a\n * pas de session de chat pour recevoir une remarque. Activé là, il promettait\n * une relecture qui n'avait jamais lieu.\n */\nexport function companionApplies(conv: Pick<Conversation, 'isCompanion' | 'workflowType'>): boolean {\n return !conv.isCompanion && conv.workflowType !== 'scenario';\n}\n\nasync function runCompanionForTurn(conversationId: string): Promise<void> {\n const conv = await conversationStore.get(conversationId);\n if (!conv) return trace(conversationId, 'conversation introuvable');\n if (!companionApplies(conv)) return; // ni un compagnon, ni un scénario\n if (!conv.companionEnabled) return; // le cas courant : inutile de le tracer\n\n const stopsAtStart = stops.get(conversationId) ?? 0;\n const stopped = () => (stops.get(conversationId) ?? 0) !== stopsAtStart;\n const messageCount = conv.messages?.length ?? 0;\n const { from, hasNew } = reviewWindow(conv.messages, reviewedUpTo.get(conversationId));\n // Rien de neuf depuis la dernière relecture : lancer un CLI pour lui faire\n // lire le vide coûte un tour de modèle et rend RAS à coup sûr. Arrive quand\n // un même tour est annoncé deux fois — le flux et le transcript peuvent tous\n // deux le signaler. Vérifié AVANT de créer quoi que ce soit : sinon un tour\n // annoncé deux fois crée une conversation de compagnon pour rien.\n if (!hasNew) {\n return skip(conversationId, 'nothingNew', `rien de neuf à relire (${messageCount} messages)`);\n }\n\n // Import tardif : `chat.ts` importe le registre, qui nous annonce le tour.\n // Le résoudre au chargement fermerait le cycle.\n const { startCliSession } = await import('../trpc/routers/chat.js');\n\n const companionConv = await ensureCompanionConversation(conv);\n // Les consignes ne passent pas ici : `buildCliSystemContext` les pose dans\n // le contexte système du compagnon, à chaque lancement.\n const exchange = formatExchange(conv.messages, from);\n const baseSha = await ensureCompanionBase(conv);\n const prompt = buildCompanionPrompt(exchange, baseSha);\n const jwt = await signJwt();\n\n /*\n * Le même environnement que la conversation relue.\n *\n * Les variables marquées « injecter dans l'IA » entrent dans l'environnement\n * du CLI principal. Sans elles, le compagnon travaille dans un contexte que\n * l'autre n'a pas : une commande qui marche chez l'un échoue chez lui, et il\n * signale un défaut qui n'existe pas.\n */\n const environmentId = await activeEnvironmentOf(conv);\n\n /*\n * Le même agent que la conversation relue — voir `companionAgentId`.\n */\n const agentId = companionAgentId(\n (await findLivePty(conv.id))?.agentId,\n activeSessionRegistry.get(conv.id)?.agentId,\n );\n\n // À partir d'ici seulement, le compagnon travaille pour de bon : tout ce qui\n // précède pouvait encore renoncer sans rien lancer. Le `finally` couvre les\n // quatre sorties qui suivent — une seule oubliée laisserait un badge allumé\n // sur une conversation au repos.\n // Coupé pendant la préparation : sa session n'existait pas encore, la\n // coupure n'a rien pu arrêter. On ne la lance donc pas.\n if (stopped()) return trace(conversationId, 'coupé avant le lancement — relecture annulée');\n const endThinking = beginThinking(conversationId);\n try {\n try {\n const { done } = await startCliSession(\n conv.companionRunner ?? conv.runner,\n companionConv.id,\n prompt,\n { jwt, environmentId, agentId, model: conv.companionModel, effort: conv.companionEffort },\n );\n await done;\n } catch (err) {\n // Une session déjà en cours, ou un runner indisponible.\n const detail = (err as Error).message;\n return skip(conversationId, 'startFailed', `relecture impossible : ${detail}`, detail);\n }\n\n const raw = await readCompanionAnswer(companionConv.id, conv.companionRunner ?? conv.runner, prompt, stopped);\n\n // Coupé pendant la relecture : rien ne part. Le repère n'avance pas, la\n // relecture n'est pas allée à son terme.\n const current = await conversationStore.get(conversationId);\n if (!current?.companionEnabled || stopped()) {\n return trace(conversationId, 'coupé pendant la relecture — rien de transmis');\n }\n\n /*\n * Le CLI a rendu la main sans rien écrire : ce n'est pas un « RAS », c'est\n * une relecture qui n'a pas eu lieu.\n *\n * Le cas vient d'une session que le CLI ne sait plus reprendre — la\n * conversation du compagnon survit au serveur, son transcript non. Le\n * `--resume` échoue alors, mais l'échec est avalé en amont : la promesse de\n * session se règle normalement, et le `catch` ci-dessus ne voit rien.\n * Faute de ce contrôle, le tour était compté comme relu, le repère avançait\n * et l'échange n'était jamais rattrapé — en silence, puisque rien n'était\n * transmis non plus.\n *\n * Le repère ne bouge donc pas : le tour repasse à la prochaine occasion.\n */\n if (!raw.trim()) {\n return skip(conversationId, 'noAnswer', 'le CLI a rendu la main sans écrire un mot');\n }\n\n // La relecture a eu lieu : le repère avance jusqu'à ce qui était là quand on\n // a construit le prompt. Pas jusqu'à maintenant — l'utilisateur a pu écrire\n // pendant ce temps, et ces messages-là n'ont pas été relus.\n reviewedUpTo.set(conversationId, messageCount);\n\n const toRelay = relayableMessage(raw);\n const why = toRelay ? 'à transmettre' : raw.includes(RELAY_OPEN) ? 'rien à transmettre' : 'sans bornes, non transmise';\n trace(conversationId, `relecture terminée — réponse de ${raw.length} caractères, ${why}`);\n if (!toRelay) {\n // Rien à transmettre : la chaîne s'arrête, et le budget de relances repart\n // neuf pour le prochain tour. La trace de livraison s'efface aussi, sinon\n // un « transmis » d'il y a trois tours resterait affiché comme s'il venait\n // d'arriver.\n relaunches.delete(conversationId);\n forgetDelivery(conversationId);\n return;\n }\n\n // Le jeton se prend au moment de relancer, pas avant la relecture : une\n // relecture qui ne trouve rien ne doit rien consommer.\n if (!takeRelaunchSlot(conversationId)) {\n return skip(conversationId, 'relaunchCap', 'plafond de relances atteint — on rend la main');\n }\n await deliverToMainConversation(conv, buildRelaunchMessage(toRelay));\n } finally {\n endThinking();\n }\n}\n\n/**\n * « Le compagnon relit cette conversation en ce moment. »\n *\n * L'information existait déjà, mais seulement en réponse à une question posée :\n * `companionDelivery` la dérivait du registre de sessions, et l'écran la\n * redemandait toutes les 1,5 s. Un sondage rate par construction ce qui est\n * plus court que sa période, et fait payer une requête par seconde et demie à\n * un état qui ne change que deux fois par tour.\n *\n * Ici, c'est le cycle lui-même qui l'annonce — d'où la couverture plus large\n * que le registre : la relecture commence quand on lance le CLI et ne finit\n * qu'une fois sa réponse lue, y compris pendant le sondage du transcript où\n * aucune session n'est « running ».\n *\n * L'identifiant porté est celui de la conversation RELUE, pas celui de la\n * conversation du compagnon : c'est sur la première que l'écran — panneau,\n * liste, carte de kanban — a besoin d'afficher quelque chose.\n */\nconst bus = new EventEmitter();\n// Un abonné par onglet ouvert : la limite de 10 par défaut est trop basse.\nbus.setMaxListeners(0);\n\n/*\n * Un COMPTE de relectures en cours, pas un simple drapeau.\n *\n * Un même tour peut être annoncé deux fois — le flux et le transcript le\n * signalent tous les deux — et les deux passages se croisent : le second\n * échoue aussitôt sur « session déjà en cours », et son extinction éteindrait\n * le badge alors que le premier relit encore. Le compte fait que seule la\n * dernière relecture à finir éteint.\n */\nconst thinking = new Map<string, number>();\n\n/** Les conversations dont le compagnon relit, à cet instant. */\nexport function companionThinkingIds(): string[] {\n return [...thinking.keys()];\n}\n\nexport function isCompanionThinking(conversationId: string): boolean {\n return thinking.has(conversationId);\n}\n\n/** S'abonner aux changements. Rend la fonction de désabonnement. */\nexport function onCompanionThinkingChanged(listener: () => void): () => void {\n bus.on('thinking-changed', listener);\n return () => { bus.off('thinking-changed', listener); };\n}\n\n/**\n * Pourquoi la dernière relecture n'a pas eu lieu.\n *\n * Le cycle sait toujours pourquoi il renonce — il l'écrivait dans le journal du\n * serveur, hors de portée de qui regarde l'écran. Un compagnon allumé mais muet\n * y était donc indiscernable d'un compagnon en panne, et le diagnostic\n * demandait un accès au serveur.\n *\n * La raison vaut « jusqu'à la prochaine exécution » : `beginThinking` l'efface,\n * puisqu'à partir de là le compagnon travaille pour de bon. Elle ne peut donc\n * pas rester affichée sur un fait périmé.\n */\nexport type CompanionSkipReason = 'nothingNew' | 'relaunchCap' | 'startFailed' | 'noAnswer';\n\nexport interface CompanionSkip {\n reason: CompanionSkipReason;\n /** Ce que le code n'a pas su dire lui-même — un message d'erreur, surtout. */\n detail?: string;\n at: number;\n}\n\nconst skips = new Map<string, CompanionSkip>();\n\n/** Les raisons en cours, par conversation — la charge du flux poussé. */\nexport function companionSkips(): Record<string, CompanionSkip> {\n return Object.fromEntries(skips);\n}\n\nexport function lastCompanionSkip(conversationId: string): CompanionSkip | null {\n return skips.get(conversationId) ?? null;\n}\n\nexport function forgetCompanionSkip(conversationId: string): void {\n if (skips.delete(conversationId)) bus.emit('thinking-changed');\n}\n\n/**\n * Renoncer, en le disant : au journal ET à l'écran.\n *\n * Un seul geste pour les deux, sinon l'un des deux finit par manquer une porte\n * de sortie — c'est exactement ce qui rendait ces raisons invisibles.\n */\nfunction skip(conversationId: string, reason: CompanionSkipReason, note: string, detail?: string): void {\n skips.set(conversationId, { reason, detail, at: Date.now() });\n bus.emit('thinking-changed');\n trace(conversationId, note);\n}\n\n/**\n * Marquer le début d'une relecture. Rend la fonction qui la clôt.\n *\n * Rendre le « fin » plutôt que d'exposer deux fonctions symétriques force le\n * `finally` : le cycle a huit portes de sortie, et une seule oubliée laisserait\n * un badge allumé pour toujours sur une conversation au repos.\n *\n * La fonction rendue est idempotente : appelée deux fois, elle ne décompte\n * qu'une relecture — sinon un `finally` exécuté après un appel manuel\n * éteindrait celle du voisin.\n */\nfunction beginThinking(conversationId: string): () => void {\n // Le compagnon travaille : la raison de son dernier renoncement a cessé\n // d'être vraie. La garder afficherait un fait périmé sous une relecture en\n // cours.\n skips.delete(conversationId);\n const before = thinking.get(conversationId) ?? 0;\n thinking.set(conversationId, before + 1);\n if (before === 0) bus.emit('thinking-changed');\n\n let ended = false;\n return () => {\n if (ended) return;\n ended = true;\n const left = (thinking.get(conversationId) ?? 1) - 1;\n if (left > 0) { thinking.set(conversationId, left); return; }\n thinking.delete(conversationId);\n bus.emit('thinking-changed');\n };\n}\n\n/** Par où le message est passé — ce que le bouton de test rend à l'écran. */\nexport type DeliveryRoute = 'pty' | 'headless' | 'failed';\n\nexport interface CompanionDelivery {\n route: DeliveryRoute;\n at: number;\n}\n\n/**\n * La dernière livraison tentée, par conversation.\n *\n * Sans elle, un échec de transmission est indiscernable d'un compagnon qui\n * n'avait rien à dire : les deux se traduisent par « rien n'arrive ». Cette\n * trace est ce qui permet de trancher depuis l'écran, sans lire les journaux du\n * serveur.\n */\nconst lastDelivery = new Map<string, CompanionDelivery>();\n\nexport function lastCompanionDelivery(conversationId: string): CompanionDelivery | null {\n return lastDelivery.get(conversationId) ?? null;\n}\n\nexport function forgetDelivery(conversationId: string): void {\n lastDelivery.delete(conversationId);\n}\n\n/**\n * Faire parvenir un message à la conversation principale.\n *\n * Deux chemins, selon ce qui la fait tourner. En PTY interactif — le mode par\n * défaut de l'interface — un terminal vit déjà pour cette conversation. Y\n * lancer une session sans tête en parallèle donnerait deux processus sur la\n * même session Claude, et l'utilisateur ne verrait jamais le message dans son\n * terminal. On écrit donc DANS le PTY, comme s'il avait tapé le texte.\n *\n * Extrait de la relance parce que le bouton « tester le pont » emprunte\n * exactement ce chemin : un test qui passerait ailleurs ne prouverait rien.\n */\nexport async function deliverToMainConversation(\n conv: Conversation,\n message: string,\n): Promise<DeliveryRoute> {\n const route = await attemptDelivery(conv, message);\n lastDelivery.set(conv.id, { route, at: Date.now() });\n console.log(`[companion] livraison vers ${conv.id.slice(0, 8)} : ${route}`);\n return route;\n}\n\nasync function attemptDelivery(conv: Conversation, message: string): Promise<DeliveryRoute> {\n if (await writeToLivePty(conv.id, message)) return 'pty';\n\n try {\n const { startCliSession } = await import('../trpc/routers/chat.js');\n await startCliSession(conv.runner, conv.id, message, { jwt: await signJwt() });\n return 'headless';\n } catch (err) {\n // La principale tourne déjà, ou le runner est indisponible : la trouvaille\n // est perdue, mais rien n'est cassé. Le tour suivant en refera une.\n console.warn(`[companion] livraison impossible : ${(err as Error).message}`);\n return 'failed';\n }\n}\n\n/**\n * Écrit dans le PTY vivant d'une conversation, s'il y en a un.\n *\n * Rend `false` quand aucun terminal ne tourne — l'appelant retombe alors sur\n * une session sans tête, qui est le bon chemin dans ce cas.\n */\nasync function writeToLivePty(conversationId: string, message: string): Promise<boolean> {\n const pty = await findLivePty(conversationId);\n if (!pty) return false;\n const write = (data: string) =>\n agentManager.agentMutation(pty.agentId, 'ai.writePty', { spawnId: pty.spawnId, data });\n try {\n await submitToPty(write, message);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Les réglages du compagnon, au niveau du poste. */\n/**\n * Ce que le réglage du poste pose sur une conversation à sa création.\n *\n * Décidé à la création, comme `enabledByDefault` : changer le réglage ne\n * modifie pas les conversations déjà ouvertes.\n *\n * Avec un runner réglé, le modèle a été choisi dans la liste de ce runner et\n * s'applique tel quel. Sans runner réglé, l'écran propose les modèles de\n * Claude Code : ils ne s'appliquent pas à une conversation sous Codex, où un\n * alias Claude ferait échouer la relecture.\n */\nexport function companionCreateDefaults(\n settings: CompanionSettings,\n convRunner: 'claude-code' | 'codex' | undefined,\n): {\n companionEnabled?: true;\n companionRunner?: 'claude-code' | 'codex';\n companionModel?: string;\n companionEffort?: 'low' | 'normal' | 'high';\n} {\n const modelApplies = !!settings.runner || convRunner !== 'codex';\n return {\n ...(settings.enabledByDefault ? { companionEnabled: true as const } : {}),\n ...(settings.runner ? { companionRunner: settings.runner } : {}),\n ...(settings.model && modelApplies ? { companionModel: settings.model } : {}),\n // Les deux runners comprennent les mêmes niveaux : l'effort vaut pour tous.\n ...(settings.effort ? { companionEffort: settings.effort } : {}),\n };\n}\n\nexport async function companionSettings(): Promise<CompanionSettings> {\n try {\n const { settingsManager } = await import('./settings-manager.js');\n // Sans coffre : réglage du poste, pas d'une organisation (§9).\n const settings = await settingsManager.getSettings();\n return settings.companion ?? { enabledByDefault: false };\n } catch {\n return { enabledByDefault: false };\n }\n}\n\n/** L'environnement actif du projet d'une conversation, s'il y en a un. */\nasync function activeEnvironmentOf(conv: Conversation): Promise<string | undefined> {\n if (!conv.projectId) return undefined;\n try {\n const ref = await conversationStore.projectRefOf(conv);\n const project = ref ? await projectStore.get(ref) : null;\n return project?.activeEnvironmentId;\n } catch {\n return undefined;\n }\n}\n\n/** Au-delà, on considère que le CLI n'a rien produit. */\nconst ANSWER_TIMEOUT_MS = 15_000;\nconst ANSWER_POLL_MS = 250;\n\n/**\n * La réponse du compagnon, lue dans son transcript.\n *\n * On ne se fie plus au flux d'événements du CLI pour ça. Ce flux alimente bien\n * l'affichage en direct, mais il s'est révélé vide au moment où l'on conclut :\n * la réponse figurait dans le transcript sur disque alors que le contenu\n * accumulé en mémoire valait zéro caractère — le compagnon paraissait muet\n * alors qu'il avait répondu.\n *\n * Le transcript, lui, fait foi : c'est ce que le CLI a réellement écrit.\n * `syncConversationFromClaudeTranscript` le relit en entier et met la\n * conversation à jour ; on attend une réponse non vide plutôt qu'une durée\n * fixe, et on borne l'attente pour ne jamais rester suspendu.\n */\n/**\n * La réponse du compagnon à CE tour : tout ce que l'assistant a écrit après le\n * message qui porte le prompt du tour, joint dans l'ordre.\n *\n * Deux pièges, vus sur Codex. Ses messages d'agent ne sont pas fusionnés : le\n * dernier peut n'être qu'une annonce (« Je contrôle… ») écrite avant la\n * conclusion. Et le transcript peut n'avoir pas encore vu le tour : le dernier\n * message d'assistant est alors la réponse du tour PRÉCÉDENT, bornes comprises\n * — relayée de nouveau, elle relançait l'agent, qui y répondait, en boucle.\n *\n * Le prompt est cherché EN ENTIER dans le message — pas par son début : le\n * rappel des tours suivants commence toujours pareil, et le début seul\n * désignerait le tour précédent tant que le transcript n'a pas vu celui-ci.\n * Un CLI peut faire précéder le prompt d'un contexte système (Codex, au premier\n * tour) ; il ne le réécrit pas.\n */\nexport function answerOfTurn(messages: { role: 'user' | 'assistant'; content?: string }[], prompt: string): string {\n const needle = prompt.trim();\n let start = -1;\n for (let i = messages.length - 1; i >= 0; i--) {\n const m = messages[i]!;\n if (m.role === 'user' && (m.content ?? '').includes(needle)) { start = i; break; }\n }\n if (start === -1) return '';\n return messages.slice(start + 1)\n .filter((m) => m.role === 'assistant')\n .map((m) => (m.content ?? '').trim())\n .filter(Boolean)\n .join('\\n');\n}\n\nasync function readCompanionAnswer(\n companionId: string,\n runner: 'claude-code' | 'codex' | undefined,\n prompt: string,\n stopped: () => boolean,\n): Promise<string> {\n const deadline = Date.now() + ANSWER_TIMEOUT_MS;\n // Chaque CLI écrit son propre transcript : lire celui de Claude pour une\n // session Codex ne rendrait jamais rien, et le compagnon paraîtrait muet.\n const sync = runner === 'codex'\n ? syncConversationFromCodexTranscript\n : syncConversationFromClaudeTranscript;\n let last = '';\n // Coupé en cours d'attente : inutile d'attendre la fin du délai.\n while (Date.now() < deadline && !stopped()) {\n await sync(companionId).catch(() => false);\n const conv = await conversationStore.get(companionId);\n last = answerOfTurn(conv?.messages ?? [], prompt);\n if (last.trim()) return last;\n await new Promise((r) => setTimeout(r, ANSWER_POLL_MS));\n }\n return last;\n}\n\n/**\n * LA conversation du compagnon — une seule, gardée d'un tour à l'autre.\n *\n * Une relecture n'est pas un audit isolé : elle porte sur ce qui a changé\n * DEPUIS le tour précédent. Le compagnon a donc besoin de se souvenir de ce\n * qu'il a déjà vu et déjà dit — sinon il redécouvre le même code à chaque fois,\n * et resignale ce qu'il a déjà signalé.\n *\n * Conserver la conversation conserve l'identifiant, donc la reprise : le runner\n * passe `--resume <même id>` (`isResume = messages.length > 0`). C'est ce qui\n * donne la continuité ; ce qui reste borné, c'est ce qu'on lui ENVOIE — les\n * seuls messages qu'il n'a pas encore relus (voir `unreviewedFrom`).\n */\nasync function ensureCompanionConversation(conv: Conversation): Promise<Conversation> {\n if (conv.companionConversationId) {\n const existing = await conversationStore.get(conv.companionConversationId);\n if (existing) return existing;\n }\n\n const companion = await conversationStore.create(\n `Compagnon · ${conv.title}`,\n true,\n undefined,\n undefined,\n conv.companionRunner ?? conv.runner,\n // La carte kanban de la conversation relue, s'il y en a une.\n //\n // `buildCliSystemContext` en tire la description de la carte, ses\n // commentaires et ses pièces jointes — c'est-à-dire ce qui a été DEMANDÉ.\n // Sans elle, le compagnon peut juger qu'un code est correct, jamais qu'un\n // travail est complet : il ignore ce qu'on attendait.\n conv.kanbanBoardId,\n conv.kanbanCardId,\n undefined,\n conv.projectId,\n undefined,\n // Le même agent que la conversation relue — c'est lui qui décide du dépôt,\n // et le compagnon relit ce dépôt-là. Le LANCEMENT visait déjà le bon agent\n // (`companionAgentId`) ; ce qui manquait, c'est que sa conversation le dise :\n // sans ce champ, tout ce qui la lit — bouton Git, terminal, activité du PTY —\n // retombait sur l'agent par défaut du serveur, donc sur un autre dépôt.\n { isCompanion: true, agentId: conv.agentId },\n );\n await conversationStore.update(conv.id, { companionConversationId: companion.id });\n return companion;\n}\n\n/**\n * Lancer une relecture à la demande, sans attendre de fin de tour.\n *\n * Le compagnon ne parle que sur un tour annoncé, et plusieurs chemins n'en\n * annoncent aucun : un tour qui ne finit pas sur `end_turn`, une session absente\n * du registre, un runner dont Runeya ne lit pas le transcript. Le compagnon\n * paraît alors éteint alors qu'il est allumé, et rien ne permettait de le\n * relancer autrement qu'en écrivant un message de plus.\n *\n * Deux états repartent à neuf avant de lancer, parce que le geste est celui de\n * l'utilisateur :\n * - le repère de relecture, sinon un clic juste après un « RAS » ne relirait\n * rien et rendrait « rien de neuf à relire » ;\n * - le budget de relances, comme le fait le tour que l'utilisateur relance\n * lui-même (voir `takeRelaunchSlot`).\n *\n * Les gardes de `runCompanionForTurn` restent en place : un compagnon coupé ou\n * une conversation de compagnon ne se relancent pas depuis ici non plus.\n */\nexport async function runCompanionNow(conversationId: string): Promise<void> {\n forgetReviewProgress(conversationId);\n resetRelaunches(conversationId);\n await runCompanionForTurn(conversationId);\n}\n\n/**\n * Coupures du compagnon, par conversation relue.\n *\n * Une relecture note la valeur à son départ et n'envoie rien si elle a changé.\n * Relire `companionEnabled` ne suffit pas : coupé puis rallumé pendant la\n * relecture, le compagnon est de nouveau actif, mais cette relecture date\n * d'avant la coupure.\n */\nconst stops = new Map<string, number>();\n\n/**\n * Couper le compagnon d'une conversation : arrêter sa session CLI en cours, et\n * empêcher la relecture en cours d'envoyer sa remarque.\n *\n * Arrêter la session ne suffit pas : le runner avale l'erreur d'une session\n * tuée, la relecture continue et lit dans le transcript ce que le compagnon\n * avait déjà écrit.\n */\nexport function stopCompanion(conv: Pick<Conversation, 'id' | 'companionConversationId'>): void {\n stops.set(conv.id, (stops.get(conv.id) ?? 0) + 1);\n if (conv.companionConversationId) activeSessionRegistry.kill(conv.companionConversationId);\n}\n\nconst SHA_RE = /^[0-9a-f]{40}$|^[0-9a-f]{64}$/;\n\n/**\n * Le dernier commit du dépôt, ou `undefined`.\n *\n * `undefined` couvre le dépôt sans commit, le dossier hors git et l'agent\n * injoignable : dans ces cas le compagnon relit sans point de départ, comme\n * avant. Le hash est vérifié parce qu'il finit dans une commande que le\n * compagnon exécutera.\n */\nexport async function headCommit(queryLog: () => Promise<unknown>): Promise<string | undefined> {\n try {\n const log = await queryLog();\n const first: unknown = Array.isArray(log) ? log[0] : undefined;\n const hash = first && typeof first === 'object' && 'hash' in first ? first.hash : undefined;\n return typeof hash === 'string' && SHA_RE.test(hash) ? hash : undefined;\n } catch {\n return undefined;\n }\n}\n\nconst capturingBase = new Set<string>();\n/**\n * Conversations déjà tentées depuis l'abonnement. Hors git, le relevé échoue à\n * chaque fois : sans cette mémoire, chaque mise à jour de la conversation\n * relancerait une requête à l'agent. La relecture, elle, retente toujours.\n */\nconst baseTriedOnChange = new Set<string>();\n\n/**\n * Relève le commit de départ d'une conversation suivie par le compagnon.\n *\n * Une seule fois : le point de départ ne bouge plus ensuite, sinon le travail\n * déjà commité sortirait du champ. Appelé quand le compagnon est activé — avant\n * le premier tour pour une conversation neuve — et, en repli, au moment de la\n * relecture pour une conversation qui n'en a pas encore.\n */\nasync function ensureCompanionBase(conv: Conversation): Promise<string | undefined> {\n if (conv.companionBaseSha) return conv.companionBaseSha;\n if (!companionApplies(conv) || !conv.companionEnabled || capturingBase.has(conv.id)) return undefined;\n capturingBase.add(conv.id);\n try {\n const agentId = conv.agentId ?? agentManager.getDefaultAgentId();\n if (!agentId) return undefined;\n const cwd = (await resolveConversationCwd(conv.id)) ?? '.';\n const sha = await headCommit(() => agentManager.agentQuery(agentId, 'git.log', { cwd, limit: 1 }));\n if (!sha) return undefined;\n // Silencieux : ce champ ne regarde pas l'interface, et un événement\n // « updated » nous reviendrait par l'abonnement ci-dessous.\n await conversationStore.update(conv.id, { companionBaseSha: sha }, { silent: true, touch: false });\n return sha;\n } finally {\n capturingBase.delete(conv.id);\n }\n}\n\nlet unsubscribe: (() => void) | null = null;\n\n/** Branche le compagnon sur les fins de tour. Idempotent. */\nexport function initCompanion(): void {\n if (unsubscribe) return;\n const offTurn = onTurnFinished((conversationId) => {\n runCompanionForTurn(conversationId).catch((err) => {\n console.error('[companion]', (err as Error).message);\n });\n });\n // Le point de départ se relève dès que le compagnon est activé : attendre la\n // première relecture, c'est laisser passer ce que le premier tour a commité.\n const onChange = (event: ConversationChangeEvent) => {\n if (event.type !== 'created' && event.type !== 'updated') return;\n const conv = event.conversation;\n if (!conv.companionEnabled || conv.isCompanion || conv.companionBaseSha) return;\n if (baseTriedOnChange.has(conv.id)) return;\n baseTriedOnChange.add(conv.id);\n ensureCompanionBase(conv).catch((err) => {\n console.error('[companion] point de départ :', (err as Error).message);\n });\n };\n conversationStore.onChanged(onChange);\n unsubscribe = () => { offTurn(); conversationStore.offChanged(onChange); };\n}\n","import { randomUUID } from 'node:crypto';\nimport { readFile, readdir, mkdir, rename, rm, stat } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { EventEmitter } from 'node:events';\nimport { allVaultRoots, keyedFilePath, legacyMachineFilePath, vaultFor , vaultIdOfRoot, orgIdOfRoot } from './vault-resolver.js';\nimport { rememberFile, hasChangedSinceRead, wasRead, forgetFile, fingerprintFiles } from './disk-freshness.js';\nimport { rootOfProjectAmong, refOfProjectAmong, rootOfProjectRef, ensureProjectsLoaded } from './vault-registry.js';\nimport { projectStore } from './project-store.js';\nimport { getEncryptionKey } from '../utils/encryption-key.js';\nimport { encryptValue, decryptValue } from './crypto-helpers.js';\nimport { atomicWriteSecure } from './run-store-utils.js';\nimport { StoreDiskCoordinator } from './store-disk-coordinator.js';\n\n/**\n * L'ancien format : toutes les conversations d'un coffre dans un seul fichier.\n *\n * Chaque écriture le réécrivait et le rechiffrait en entier, messages compris.\n * À quelques centaines de conversations, il pesait une centaine de Mo et une\n * création prenait une à deux secondes. Il n'est plus écrit : au chargement,\n * son contenu passe dans `conversations/`, puis il est renommé (voir\n * `migrateAggregates`).\n */\nconst FILENAME = 'conversations.private.json';\n/** Un fichier par conversation, dans ce dossier de chaque coffre. */\nexport const CONVERSATIONS_DIRNAME = 'conversations';\nconst FILE_SUFFIX = '.private.json';\n/** Un id devient un nom de fichier : rien qui puisse sortir du dossier. */\nconst SAFE_ID_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/;\n\nfunction conversationDir(root: string): string {\n return join(root, CONVERSATIONS_DIRNAME);\n}\n\nfunction conversationPath(root: string, id: string): string {\n return join(root, CONVERSATIONS_DIRNAME, `${id}${FILE_SUFFIX}`);\n}\n\n/** Le nom sous lequel l'ancien fichier est gardé une fois converti. */\nfunction migratedAggregateName(now: Date): string {\n return `conversations.migrated-${now.toISOString().replace(/[:.]/g, '-').substring(0, 19)}.private.json`;\n}\n\nasync function fileStamp(path: string): Promise<string | null> {\n try {\n const s = await stat(path);\n return `${s.mtimeMs}:${s.size}:${s.ino}`;\n } catch {\n return null;\n }\n}\n\n// ─── Types ─────────────────────────────────────────────────────────────────\n\nexport type MessagePart =\n | { type: 'thinking'; text: string }\n | { type: 'text'; text: string }\n | { type: 'tool'; id: string; name: string; input?: Record<string, unknown>; rawInput?: string; result?: unknown; isError?: boolean; pending: boolean }\n | { type: 'image'; imageId: string; url: string; mimeType: string; altText?: string };\n\nexport interface Message {\n role: 'user' | 'assistant';\n content: string;\n parts?: MessagePart[];\n contextNote?: string;\n}\n\ninterface ContextUsage {\n inputTokens: number;\n contextWindow: number;\n}\n\ntype WorkflowType = 'scenario';\n\nexport interface Conversation {\n id: string;\n /**\n * The project this conversation belongs to.\n *\n * Optional only for conversations written before the field existed, and for\n * those a migration could not attribute: they stay visible from everywhere\n * rather than being hidden under a project picked at random.\n */\n projectId?: string;\n title: string;\n messages: Message[];\n isCli: boolean;\n runner?: 'claude-code' | 'codex';\n providerId?: string;\n codexThreadId?: string;\n contextUsage?: ContextUsage;\n workflowId?: string;\n workflowType?: WorkflowType;\n kanbanBoardId?: string;\n kanbanCardId?: string;\n /**\n * L'agent sur lequel cette conversation travaille — son dépôt, son shell.\n *\n * Persisté, parce que le choix n'est fait qu'une fois, à la création : sans\n * disque, un rechargement de page le perdait et tout retombait sur l'agent\n * par défaut du serveur, c'est-à-dire sur un autre dépôt que celui où la\n * conversation travaille. Absent = agent par défaut, ce qu'attendent les\n * conversations écrites avant ce champ.\n */\n agentId?: string;\n /** Le compagnon relit cette conversation en fin de tour (§ compagnon). */\n companionEnabled?: boolean;\n /** Runner du compagnon. Par défaut celui de la conversation qu'il relit. */\n companionRunner?: 'claude-code' | 'codex';\n /** Modèle du compagnon, passé en `--model`. Absent = modèle par défaut du runner. */\n companionModel?: string;\n /** Effort du compagnon. Absent = effort par défaut. */\n companionEffort?: 'low' | 'normal' | 'high';\n /** La conversation où le compagnon travaille — son terminal en lecture seule. */\n companionConversationId?: string;\n /**\n * Le commit où en était le dépôt quand le compagnon a commencé à suivre la\n * conversation. Le compagnon relit `git diff <ce commit>` : sur `master`, ou\n * une fois le travail commité, `git diff` seul ne montre plus rien.\n */\n companionBaseSha?: string;\n /**\n * Cette conversation EST un compagnon.\n *\n * Sans ce drapeau, un compagnon dont la conversation porte à son tour\n * `companionEnabled` s'engendrerait lui-même à chaque fin de tour.\n */\n isCompanion?: boolean;\n archived?: boolean;\n /**\n * Rang dans la section épinglée de la barre latérale.\n *\n * Présent = épinglée. C'est le seul marqueur : un booléen à côté pourrait\n * dire « épinglée » sans qu'aucun rang ne dise où, et l'écran devrait\n * trancher. Les rangs se lisent triés, pas comme des indices : ils restent\n * ceux que la conversation portait avant un réordonnancement, seul leur\n * ordre relatif change.\n */\n pinOrder?: number;\n /**\n * La section de la barre latérale où l'utilisateur a rangé la conversation\n * (`conversation-section-store`). Exclusif de `pinOrder` : une conversation\n * n'est que dans une zone à la fois.\n */\n sectionId?: string;\n /** Rang dans sa section. Seul l'ordre relatif compte, comme `pinOrder`. */\n sectionOrder?: number;\n createdAt: number;\n updatedAt: number;\n}\n\nexport type ConversationChangeEvent =\n | { type: 'created'; conversation: Conversation }\n /**\n * `messagesChanged` dit si l'écriture a touché aux messages. Le flux vers\n * l'écran s'en sert pour n'envoyer le fil complet que quand il a bougé :\n * un épinglage ou un compteur de contexte n'a pas à renvoyer des Mo.\n */\n | { type: 'updated'; conversation: Conversation; messagesChanged?: boolean }\n | { type: 'deleted'; id: string }\n /**\n * Tout est à relire — le disque a changé sous le store (adoption d'une clé).\n *\n * Sans conversation à porter : ce n'est pas une conversation qui a bougé,\n * c'est l'ensemble qui n'est plus celui que le client détient. L'abonnement\n * répond en renvoyant un instantané complet, le chemin que le client sait\n * déjà traiter.\n */\n | { type: 'reloaded' };\n\ninterface ConversationOnDisk {\n id: string;\n projectId?: string;\n title: string;\n encryptedMessages: string;\n isCli?: boolean;\n isClaudeCode?: boolean;\n runner?: 'claude-code' | 'codex';\n providerId?: string;\n codexThreadId?: string;\n contextUsage?: ContextUsage;\n workflowId?: string;\n workflowType?: WorkflowType;\n kanbanBoardId?: string;\n kanbanCardId?: string;\n agentId?: string;\n companionEnabled?: boolean;\n companionRunner?: 'claude-code' | 'codex';\n companionModel?: string;\n companionEffort?: 'low' | 'normal' | 'high';\n companionConversationId?: string;\n companionBaseSha?: string;\n isCompanion?: boolean;\n // Legacy field from before workflowType was introduced — used only for migration in fromDisk\n scenarioId?: string;\n archived?: boolean;\n pinOrder?: number;\n sectionId?: string;\n sectionOrder?: number;\n createdAt: number;\n updatedAt: number;\n}\n\n/** The project of the launch directory, if it has one. */\nasync function localProjectId(): Promise<string | null> {\n const projects = await projectStore.list().catch(() => []);\n return projects.find((p) => !p.orgId)?.id ?? null;\n}\n\n// ─── Store ─────────────────────────────────────────────────────────────────\n\nclass ConversationStore {\n private conversations: Map<string, Conversation> = new Map();\n /** Which vault each conversation was read from, until its project is known. */\n private roots: Map<string, string> = new Map();\n /**\n * Les conversations qu'on n'a pas su déchiffrer, par coffre, sous leur forme\n * de disque.\n *\n * Une entrée illisible était simplement ignorée. Tant qu'aucune de ses\n * voisines ne se chargeait, le fichier n'était jamais réécrit et la donnée\n * survivait par accident ; dès qu'une seule s'ouvrait, la sauvegarde suivante\n * réécrivait le fichier avec elle seule — et effaçait les autres. Elles sont\n * donc gardées ici, telles quelles, et réémises à l'identique.\n */\n private unreadable: Map<string, ConversationOnDisk[]> = new Map();\n private loaded = false;\n /**\n * L'état du disque au dernier chargement — voir `ProjectStore.fingerprint`.\n *\n * Les conversations ne passent pas par le cloud : elles restent sur ce poste.\n * Le disque est donc le seul canal entre deux Runeya qui le partagent, et un\n * cache jamais revu y est la seule chose qui puisse faire disparaître une\n * conversation écrite dans l'autre fenêtre.\n */\n private fingerprint = '';\n private readonly events = new EventEmitter().setMaxListeners(0);\n private readonly disk = new StoreDiskCoordinator();\n /** Le fichier où chaque conversation est écrite aujourd'hui, s'il existe. */\n private written: Map<string, string> = new Map();\n /**\n * Ce qui a été lu de chaque fichier, avec son empreinte.\n *\n * Un rechargement ne relit que les fichiers dont l'empreinte a bougé :\n * déchiffrer toutes les conversations parce qu'une seule a changé dans une\n * autre fenêtre coûterait autant que l'ancien format.\n */\n private parsed: Map<string, { stamp: string; conv: Conversation | null; item: ConversationOnDisk }> = new Map();\n /** Les conversations modifiées depuis la dernière écriture. */\n private dirty: Set<string> = new Set();\n /** Les fichiers à retirer : conversations supprimées ou changées de coffre. */\n private removed: Set<string> = new Set();\n /** Les anciens fichiers à convertir, relevés au dernier chargement. */\n private aggregates: { path: string }[] = [];\n /** Écrites telles quelles à la conversion : illisibles ici, mais pas perdues. */\n private rawToWrite: Map<string, { root: string; item: ConversationOnDisk }> = new Map();\n /** Plus récentes dans un ancien fichier que dans le leur : à réécrire à la conversion. */\n private outdated: Set<string> = new Set();\n private migrating: Promise<void> | null = null;\n\n private snapshotConversation(conv: Conversation): Conversation {\n return structuredClone(conv);\n }\n\n /**\n * Encrypted, so it lives beside the key that opens it — usually the machine\n * root, but a launch directory owning its own key keeps both together.\n */\n private getFilePath(): Promise<string> {\n return keyedFilePath(FILENAME);\n }\n\n /** Where older versions kept it — read only, so an older Runeya keeps working. */\n private getLegacyFilePath(): string {\n return legacyMachineFilePath(FILENAME);\n }\n\n /**\n * Read the machine file, falling back to the launch directory of old.\n *\n * The legacy file is left in place: the next save writes the new location,\n * and a directory still opened by an older Runeya keeps working meanwhile.\n */\n private async readFromDisk(): Promise<string> {\n try {\n return await readFile(await this.getFilePath(), 'utf-8');\n } catch {\n return readFile(this.getLegacyFilePath(), 'utf-8');\n }\n }\n\n private async ensureDir(): Promise<void> {\n await mkdir(dirname(await this.getFilePath()), { recursive: true });\n }\n\n private encryptMessages(messages: Message[], vault?: string): string {\n return encryptValue(JSON.stringify(messages), vault);\n }\n\n private decryptMessages(encrypted: string, vault?: string): Message[] {\n const json = decryptValue(encrypted, vault);\n return JSON.parse(json) as Message[];\n }\n\n /**\n * Refuser d'écrire dans le coffre d'une organisation sans nommer sa clé.\n *\n * `getEncryptionKey(undefined)` rend la clé machine, et c'est le bon défaut\n * pour le coffre local. Appliqué à un fichier rangé sous `orgs/<org>/`, il\n * produit un chiffré que ce coffre n'emploie pas — lisible par personne à la\n * relecture, et impossible à distinguer d'une corruption.\n *\n * Le cas a été observé plusieurs fois sans qu'on parvienne à nommer le\n * chemin fautif : les garde-fous en place traitent le coffre fermé, le\n * coffre mal résolu et la copie périmée, aucun ne couvrait « coffre non\n * précisé ». Ces écritures-là n'ont jamais fonctionné — elles échouaient en\n * silence, ce qui est pire. On les saute désormais en le disant, avec la\n * pile d'appels qui manquait pour remonter au chemin fautif.\n */\n private isVaultNamed(root: string, vault: string | undefined): boolean {\n const orgId = orgIdOfRoot(root);\n if (!orgId || vault) return true;\n // Signalé avec sa pile — c'est elle qu'on cherche — mais sans jeter : une\n // exception ici avorterait la sauvegarde entière, coffres corrects\n // compris. Une conversation mal aiguillée ne doit pas en empêcher vingt\n // autres d'être écrites.\n console.error(\n `[conversation-store] Écriture ignorée dans le coffre de l'organisation ${orgId}`\n + ` (${root}) : aucune clé d'organisation nommée, le contenu serait chiffré par la`\n + ` clé machine et illisible à la relecture.\\n${new Error('trace').stack}`,\n );\n return false;\n }\n\n private toDisk(conv: Conversation, vault?: string): ConversationOnDisk {\n return {\n id: conv.id,\n projectId: conv.projectId,\n title: conv.title,\n encryptedMessages: this.encryptMessages(conv.messages, vault),\n isCli: conv.isCli,\n runner: conv.runner,\n providerId: conv.providerId,\n codexThreadId: conv.codexThreadId,\n contextUsage: conv.contextUsage,\n workflowId: conv.workflowId,\n workflowType: conv.workflowType,\n kanbanBoardId: conv.kanbanBoardId,\n kanbanCardId: conv.kanbanCardId,\n agentId: conv.agentId,\n companionEnabled: conv.companionEnabled,\n companionRunner: conv.companionRunner,\n companionModel: conv.companionModel,\n companionEffort: conv.companionEffort,\n companionConversationId: conv.companionConversationId,\n companionBaseSha: conv.companionBaseSha,\n isCompanion: conv.isCompanion,\n archived: conv.archived,\n pinOrder: conv.pinOrder,\n sectionId: conv.sectionId,\n sectionOrder: conv.sectionOrder,\n createdAt: conv.createdAt,\n updatedAt: conv.updatedAt,\n };\n }\n\n private fromDisk(disk: ConversationOnDisk, vault?: string): Conversation {\n // Derive workflowType from legacy fields for conversations created before this field existed\n const workflowType: WorkflowType | undefined =\n (disk.workflowType === 'scenario' ? 'scenario' : undefined)\n ?? (disk.scenarioId ? 'scenario' : undefined);\n\n return {\n id: disk.id,\n projectId: disk.projectId,\n title: disk.title,\n messages: this.decryptMessages(disk.encryptedMessages, vault),\n isCli: disk.isCli ?? disk.isClaudeCode ?? false,\n runner: disk.runner,\n providerId: disk.providerId,\n codexThreadId: disk.codexThreadId,\n contextUsage: disk.contextUsage,\n workflowId: disk.workflowId ?? disk.scenarioId,\n workflowType,\n kanbanBoardId: disk.kanbanBoardId,\n kanbanCardId: disk.kanbanCardId,\n agentId: disk.agentId,\n companionEnabled: disk.companionEnabled,\n companionRunner: disk.companionRunner,\n companionModel: disk.companionModel,\n companionEffort: disk.companionEffort,\n companionConversationId: disk.companionConversationId,\n companionBaseSha: disk.companionBaseSha,\n isCompanion: disk.isCompanion,\n archived: disk.archived,\n pinOrder: disk.pinOrder,\n sectionId: disk.sectionId,\n sectionOrder: disk.sectionOrder,\n createdAt: disk.createdAt,\n updatedAt: disk.updatedAt,\n };\n }\n\n /**\n * Read every project vault, then the homes older versions used.\n *\n * A conversation found in a legacy file is attributed to the local project of\n * that directory — which is what it was about, back when one directory meant\n * one project. With no local project to point at, it is left unattributed\n * rather than filed under a project picked at random.\n */\n /** Les fichiers dont ce store tire son contenu : les coffres, et l'hérité. */\n private async sourcePaths(): Promise<{ path: string; legacy: boolean }[]> {\n const vaults = await allVaultRoots();\n const legacyPaths = [this.getLegacyFilePath(), await keyedFilePath(FILENAME)];\n const all = [\n ...legacyPaths.map((path) => ({ path, legacy: true })),\n ...vaults.map((root) => ({ path: join(root, FILENAME), legacy: false })),\n ];\n // Un même fichier ne se lit qu'une fois. Le chemin hérité est aussi celui\n // du coffre local : lu deux fois, une conversation illisible y était remise\n // en double à chaque écriture, jusqu'à dépasser la taille qu'une chaîne\n // peut tenir. On garde la DERNIÈRE occurrence : c'est elle qui l'emportait\n // à la lecture, et l'ordre des autres fichiers ne bouge pas.\n return all.filter((source, i) => !all.slice(i + 1).some((later) => later.path === source.path));\n }\n\n /**\n * Les répertoires où chercher des conversations : ceux des sources, sans\n * doublon. Le dossier de lancement hérité en fait partie.\n */\n private async sourceRoots(): Promise<string[]> {\n return Array.from(new Set((await this.sourcePaths()).map((s) => dirname(s.path))));\n }\n\n /**\n * L'état du disque : les anciens fichiers, et les dossiers `conversations/`.\n *\n * La date d'un dossier bouge à chaque fichier ajouté, renommé ou retiré, et\n * toute écriture passe par un renommage (`atomicWriteSecure`). Quelques\n * `stat` suffisent donc à savoir si une autre instance a écrit.\n */\n private async diskFingerprint(): Promise<string> {\n const paths = (await this.sourcePaths()).map((s) => s.path);\n const dirs = Array.from(new Set(paths.map((path) => conversationDir(dirname(path)))));\n return fingerprintFiles([...paths, ...dirs]);\n }\n\n private async load(): Promise<void> {\n if (!getEncryptionKey()) {\n return;\n }\n await this.disk.load(async () => {\n const fingerprint = await this.diskFingerprint();\n if (this.loaded && fingerprint === this.fingerprint) return null;\n // Les projets d'abord : tant qu'ils sont inconnus, tout se résoudrait vers\n // le coffre de lancement et y serait réécrit à la première sauvegarde —\n // hors de l'organisation propriétaire.\n await ensureProjectsLoaded();\n const next = await this.readSources();\n return () => {\n this.conversations = next.conversations;\n this.roots = next.roots;\n this.written = next.written;\n this.parsed = next.parsed;\n // Ce qu'un chargement précédent n'avait pas su lire n'a pas à s'ajouter\n // à celui-ci : la liste décrit le disque tel qu'on vient de le lire.\n this.unreadable = next.unreadable;\n this.aggregates = next.aggregates;\n this.rawToWrite = next.rawToWrite;\n this.outdated = next.outdated;\n this.fingerprint = fingerprint;\n this.loaded = true;\n };\n });\n if (this.aggregates.length > 0) await this.migrateAggregates();\n }\n\n /**\n * Passer les anciens fichiers au format « un fichier par conversation ».\n *\n * Dans cet ordre, pour ne rien perdre si ça s'arrête en route : chaque\n * conversation qui n'a pas encore son fichier l'obtient, les illisibles\n * compris (recopiées telles quelles), puis on vérifie que tous les fichiers\n * attendus existent. Alors seulement l'ancien fichier est renommé. Il reste\n * dans le coffre comme copie de secours, sous un nom que ce store ne lit pas.\n *\n * Le fichier du dossier de lancement hérité est renommé lui aussi : relu à\n * chaque démarrage, il ferait revenir les conversations supprimées depuis.\n */\n private migrateAggregates(): Promise<void> {\n this.migrating ??= (async () => {\n const aggregates = this.aggregates;\n const expected: string[] = [];\n let incomplete = false;\n for (const [id] of this.conversations) {\n if (!this.written.has(id) || this.outdated.has(id)) this.dirty.add(id);\n }\n await this.save();\n for (const [id, conv] of this.conversations) {\n const path = this.written.get(id);\n if (path) {\n expected.push(path);\n } else {\n incomplete = true;\n console.warn(`[conversation-store] Conversation ${conv.id} non convertie : ancien fichier gardé.`);\n }\n }\n const raws = Array.from(this.rawToWrite.values());\n for (const { root, item } of raws) {\n if (!SAFE_ID_RE.test(item.id)) {\n incomplete = true;\n continue;\n }\n const path = conversationPath(root, item.id);\n if (!(await fileStamp(path))) await atomicWriteSecure(path, JSON.stringify(item));\n expected.push(path);\n }\n const missing = (await Promise.all(expected.map(fileStamp))).some((st) => st === null);\n // Nouvelle tentative au prochain rechargement, pas à chaque appel.\n this.aggregates = [];\n if (missing || incomplete) {\n console.error('[conversation-store] Conversion incomplète : les anciens fichiers restent en place.');\n return;\n }\n const now = new Date();\n for (const { path } of aggregates) {\n await rename(path, join(dirname(path), migratedAggregateName(now)));\n forgetFile(path);\n console.log(`[conversation-store] ${path} converti en un fichier par conversation.`);\n }\n this.rawToWrite = new Map();\n this.outdated = new Set();\n this.fingerprint = await this.diskFingerprint();\n })().finally(() => { this.migrating = null; });\n return this.migrating;\n }\n\n /** Lire toutes les sources dans des tables neuves, que `load` substitue d'un coup. */\n private async readSources(): Promise<{\n conversations: Map<string, Conversation>;\n roots: Map<string, string>;\n written: Map<string, string>;\n parsed: Map<string, { stamp: string; conv: Conversation | null; item: ConversationOnDisk }>;\n unreadable: Map<string, ConversationOnDisk[]>;\n aggregates: { path: string }[];\n rawToWrite: Map<string, { root: string; item: ConversationOnDisk }>;\n outdated: Set<string>;\n }> {\n const conversations = new Map<string, Conversation>();\n const roots = new Map<string, string>();\n const written = new Map<string, string>();\n const parsed = new Map<string, { stamp: string; conv: Conversation | null; item: ConversationOnDisk }>();\n const unreadable = new Map<string, ConversationOnDisk[]>();\n const aggregates: { path: string }[] = [];\n const rawToWrite = new Map<string, { root: string; item: ConversationOnDisk }>();\n const outdated = new Set<string>();\n\n const markUnreadable = (root: string, item: ConversationOnDisk) => {\n const bucket = unreadable.get(root) ?? [];\n // Une seule fois par id : un fichier gonflé de copies par l'ancien\n // comportement ne se multiplie pas à la conversion.\n if (!bucket.some((kept) => kept.id === item.id)) bucket.push(item);\n unreadable.set(root, bucket);\n };\n\n const sources = await this.sourcePaths();\n // Les répertoires hérités : une conversation qui n'y nomme aucun projet\n // revient au projet local, quel que soit le format où elle est écrite.\n const legacyRoots = new Set(sources.filter((s) => s.legacy).map((s) => dirname(s.path)));\n const inheritedProject = legacyRoots.size > 0 ? await localProjectId() : null;\n\n // 1. Les anciens fichiers.\n for (const { path, legacy } of sources) {\n let data: ConversationOnDisk[];\n try {\n data = JSON.parse(await readFile(path, 'utf-8')) as ConversationOnDisk[];\n } catch {\n continue;\n }\n aggregates.push({ path });\n const inherited = legacy ? inheritedProject : null;\n const root = dirname(path);\n await rememberFile(path);\n for (const item of data) {\n try {\n const conv = this.fromDisk(item, vaultIdOfRoot(root));\n if (!conv.projectId && inherited) conv.projectId = inherited;\n conversations.set(item.id, conv);\n roots.set(item.id, root);\n } catch {\n markUnreadable(root, item);\n if (!rawToWrite.has(item.id)) rawToWrite.set(item.id, { root, item });\n }\n }\n }\n\n // 2. Un fichier par conversation. Il l'emporte sur l'ancien fichier, sauf\n // si celui-ci est plus récent : une Runeya d'avant la conversion peut\n // encore y écrire.\n for (const root of await this.sourceRoots()) {\n const dir = conversationDir(root);\n let names: string[];\n try {\n names = (await readdir(dir)).filter((n) => n.endsWith(FILE_SUFFIX));\n } catch {\n continue;\n }\n for (const name of names) {\n const path = join(dir, name);\n const stamp = await fileStamp(path);\n if (!stamp) continue;\n let entry = this.parsed.get(path);\n if (!entry || entry.stamp !== stamp) {\n let item: ConversationOnDisk;\n try {\n item = JSON.parse(await readFile(path, 'utf-8')) as ConversationOnDisk;\n } catch {\n continue;\n }\n let conv: Conversation | null = null;\n try {\n conv = this.fromDisk(item, vaultIdOfRoot(root));\n } catch {\n conv = null;\n }\n entry = { stamp, conv, item };\n }\n await rememberFile(path);\n parsed.set(path, entry);\n const { conv, item } = entry;\n rawToWrite.delete(item.id);\n if (!conv) {\n // Illisible ici : le fichier reste tel quel, jamais réécrit.\n markUnreadable(root, item);\n continue;\n }\n if (!conv.projectId && legacyRoots.has(root) && inheritedProject) conv.projectId = inheritedProject;\n written.set(conv.id, path);\n const older = conversations.get(conv.id);\n if (older && older.updatedAt > conv.updatedAt) {\n outdated.add(conv.id);\n continue;\n }\n conversations.set(conv.id, conv);\n roots.set(conv.id, root);\n }\n }\n // Une illisible de l'ancien fichier qui a déjà son fichier n'est plus à écrire.\n for (const [id, conv] of conversations) {\n if (written.has(id)) rawToWrite.delete(conv.id);\n }\n return { conversations, roots, written, parsed, unreadable, aggregates, rawToWrite, outdated };\n }\n\n /**\n * Oublier ce qui a été lu, pour tout relire sous la clé courante.\n *\n * Appelé après l'adoption d'une clé d'organisation : le disque vient d'être\n * re-chiffré sous les pieds du store, si bien que sa mémoire décrit un état\n * qui n'existe plus. La laisser en place n'affichait pas seulement des\n * comptes périmés — la sauvegarde suivante réécrivait l'ancien chiffré\n * par-dessus le nouveau, et défaisait l'adoption.\n */\n resetCache(): void {\n this.conversations.clear();\n this.roots.clear();\n this.unreadable.clear();\n this.written.clear();\n this.parsed.clear();\n this.dirty.clear();\n this.aggregates = [];\n this.rawToWrite.clear();\n this.outdated.clear();\n this.loaded = false;\n this.events.emit('changed', { type: 'reloaded' } as ConversationChangeEvent);\n }\n\n /**\n * Combien de conversations ce coffre garde sans savoir les lire, par\n * organisation. La machine elle-même est rangée sous `null`.\n */\n async unreadableByVault(): Promise<Map<string | null, number>> {\n await this.load();\n const out = new Map<string | null, number>();\n for (const [root, items] of this.unreadable) {\n const vault = vaultIdOfRoot(root) ?? null;\n out.set(vault, (out.get(vault) ?? 0) + items.length);\n }\n return out;\n }\n\n /**\n * Écrire les conversations modifiées, chacune dans son fichier.\n *\n * Seules celles qui ont bougé depuis la dernière écriture : c'est le but du\n * format. Une conversation dont le projet a changé de coffre est écrite dans\n * le nouveau, et son ancien fichier retiré.\n *\n * Un fichier modifié depuis sa lecture (réparation faite à la main, clé\n * adoptée) n'est pas réécrit : réémettre notre copie déferait la réparation.\n */\n private async saveInternal(): Promise<void> {\n if (!getEncryptionKey()) return;\n\n const ids = Array.from(this.dirty);\n this.dirty.clear();\n const removed = Array.from(this.removed);\n this.removed.clear();\n\n for (const id of ids) {\n const conv = this.conversations.get(id);\n if (!conv) continue;\n if (!SAFE_ID_RE.test(conv.id)) {\n console.error(`[conversation-store] Id de conversation refusé comme nom de fichier : ${JSON.stringify(conv.id)}`);\n continue;\n }\n const root = await this.rootFor(conv);\n this.roots.set(conv.id, root);\n // Chiffré par la clé du coffre où la conversation atterrit : celle de son\n // organisation quand elle appartient à un projet synchronisé.\n const vault = vaultIdOfRoot(root);\n if (!this.isVaultNamed(root, vault)) continue;\n const path = conversationPath(root, conv.id);\n if (wasRead(path) && await hasChangedSinceRead(path)) {\n console.warn(\n `[conversation-store] ${path} a changé depuis sa lecture —`\n + ' écriture ignorée pour ne pas écraser une réparation faite en dehors du serveur.',\n );\n continue;\n }\n const item = this.toDisk(conv, vault);\n await atomicWriteSecure(path, JSON.stringify(item));\n // Notre écriture devient la référence, sinon la suivante la prendrait\n // pour une modification extérieure.\n await rememberFile(path);\n const stamp = await fileStamp(path);\n if (stamp) this.parsed.set(path, { stamp, conv, item });\n const previous = this.written.get(conv.id);\n this.written.set(conv.id, path);\n if (previous && previous !== path) removed.push(previous);\n }\n\n for (const path of removed) {\n await rm(path, { force: true });\n forgetFile(path);\n this.parsed.delete(path);\n }\n\n // Idem pour l'empreinte d'ensemble : sans ça, le chargement suivant\n // prendrait nos propres écritures pour celles d'une autre instance.\n this.fingerprint = await this.diskFingerprint();\n }\n\n /**\n * The vault a conversation is written to: its project's.\n *\n * One that names no project stays where it was read, falling back to the\n * key's own directory — never lost for want of an owner.\n */\n private async rootFor(conv: Conversation): Promise<string> {\n const known = this.roots.get(conv.id) ?? null;\n // Le coffre de lecture tranche quand l'id du projet répond pour plusieurs\n // organisations : c'est le couple, écrit par l'arborescence.\n if (conv.projectId) return rootOfProjectAmong(conv.projectId, known);\n return known ?? dirname(await keyedFilePath(FILENAME));\n }\n\n /**\n * Le projet d'une conversation, organisation comprise.\n *\n * Ce que la conversation ne dit pas d'elle-même : elle ne nomme que l'id.\n * Le coffre d'où elle vient fournit l'organisation, et c'est ce couple qu'il\n * faut pour retrouver le répertoire de travail du projet.\n */\n async projectRefOf(conv: Conversation): Promise<{ id: string; orgId: string | null } | null> {\n if (!conv.projectId) return null;\n const known = this.roots.get(conv.id) ?? null;\n const ref = refOfProjectAmong(conv.projectId, known);\n return { id: ref.id, orgId: ref.orgId ?? null };\n }\n\n /**\n * Réécrire chaque coffre, en déplaçant ce qui appartient désormais ailleurs.\n *\n * Appelé quand un projet a changé de coffre : ses conversations sont écrites\n * dans le nouveau, et le fichier d'où elles viennent est réécrit sans elles.\n */\n async relocateAll(): Promise<void> {\n await this.load();\n await this.save(...this.conversations.keys());\n }\n\n /** Écrire ces conversations — et celles qui attendaient déjà. */\n private save(...ids: string[]): Promise<void> {\n for (const id of ids) this.dirty.add(id);\n return this.disk.save(() => this.saveInternal());\n }\n\n async list(): Promise<Conversation[]> {\n await this.load();\n return Array.from(this.conversations.values())\n .sort((a, b) => b.updatedAt - a.updatedAt);\n }\n\n /**\n * Les conversations d'un projet.\n *\n * Celle qui ne nomme aucun projet — écrite avant le champ, ou qu'une\n * migration n'a pas su attribuer — n'est rendue que dans les projets de son\n * propre coffre : elle reste atteignable là où elle a été écrite, sans se\n * déverser dans toutes les organisations de la machine.\n */\n async listForProject(ref: { id: string; orgId?: string | null }): Promise<Conversation[]> {\n const belongs = await this.matcherForProject(ref);\n return (await this.list()).filter(belongs);\n }\n\n /**\n * Le test d'appartenance à un projet, préparé une fois.\n *\n * Le flux temps réel en a besoin autant que la liste : une conversation qui\n * arrive par événement doit passer le même filtre, sans quoi elle se\n * réinvite dans un projet qui ne l'a jamais demandée.\n */\n async matcherForProject(ref: { id: string; orgId?: string | null }): Promise<(conv: Conversation) => boolean> {\n await this.load();\n let projectRoot: string | null = null;\n try {\n projectRoot = ref.orgId === undefined\n ? rootOfProjectAmong(ref.id, null)\n : rootOfProjectRef({ id: ref.id, orgId: ref.orgId ?? null });\n } catch {\n // Id ambigu sans organisation : aucun coffre ne tranche, on ne cache rien.\n projectRoot = null;\n }\n return (conv: Conversation) => {\n if (conv.projectId) return conv.projectId === ref.id;\n if (!projectRoot) return true;\n return this.roots.get(conv.id) === projectRoot;\n };\n }\n\n async get(id: string): Promise<Conversation | null> {\n await this.load();\n return this.conversations.get(id) ?? null;\n }\n\n async create(title?: string, isCli = false, workflowId?: string, workflowType?: WorkflowType, runner?: 'claude-code' | 'codex', kanbanBoardId?: string, kanbanCardId?: string, providerId?: string, projectId?: string, orgId?: string | null, extra?: { isCompanion?: boolean; companionEnabled?: boolean; companionRunner?: 'claude-code' | 'codex'; companionModel?: string; companionEffort?: 'low' | 'normal' | 'high'; agentId?: string }): Promise<Conversation> {\n await this.load();\n const now = Date.now();\n const conv: Conversation = {\n id: randomUUID(),\n projectId,\n title: title ?? 'New conversation',\n messages: [],\n isCli,\n runner,\n providerId,\n workflowId,\n workflowType,\n kanbanBoardId,\n kanbanCardId,\n agentId: extra?.agentId,\n isCompanion: extra?.isCompanion,\n companionEnabled: extra?.companionEnabled,\n companionRunner: extra?.companionRunner,\n companionModel: extra?.companionModel,\n companionEffort: extra?.companionEffort,\n createdAt: now,\n updatedAt: now,\n };\n this.conversations.set(conv.id, conv);\n // `orgId` ne fait pas partie de la conversation : il dit dans quel coffre la\n // poser. Une conversation neuve n'a pas de provenance, et l'id du projet\n // seul peut répondre pour une organisation comme pour l'autre.\n if (projectId && orgId !== undefined) {\n this.roots.set(conv.id, vaultFor({ id: projectId, orgId }).root);\n }\n await this.save(conv.id);\n this.events.emit('changed', { type: 'created', conversation: this.snapshotConversation(conv) } as ConversationChangeEvent);\n return conv;\n }\n\n async update(id: string, patch: { title?: string; messages?: Message[]; contextUsage?: ContextUsage; workflowId?: string; archived?: boolean; codexThreadId?: string; companionEnabled?: boolean; companionRunner?: 'claude-code' | 'codex'; companionModel?: string; companionEffort?: 'low' | 'normal' | 'high' | ''; companionConversationId?: string; companionBaseSha?: string }, options?: { silent?: boolean; touch?: boolean }): Promise<Conversation | null> {\n await this.load();\n const conv = this.conversations.get(id);\n if (!conv) return null;\n if (patch.title !== undefined) conv.title = patch.title;\n if (patch.messages !== undefined) conv.messages = patch.messages;\n if (patch.contextUsage !== undefined) conv.contextUsage = patch.contextUsage;\n if (patch.workflowId !== undefined) conv.workflowId = patch.workflowId;\n if (patch.archived !== undefined) conv.archived = patch.archived;\n if (patch.codexThreadId !== undefined) conv.codexThreadId = patch.codexThreadId;\n if (patch.companionEnabled !== undefined) conv.companionEnabled = patch.companionEnabled;\n if (patch.companionRunner !== undefined) conv.companionRunner = patch.companionRunner;\n // Chaîne vide = revenir au modèle par défaut du runner.\n if (patch.companionModel !== undefined) conv.companionModel = patch.companionModel || undefined;\n if (patch.companionEffort !== undefined) conv.companionEffort = patch.companionEffort || undefined;\n if (patch.companionConversationId !== undefined) conv.companionConversationId = patch.companionConversationId;\n if (patch.companionBaseSha !== undefined) conv.companionBaseSha = patch.companionBaseSha;\n if (options?.touch !== false) conv.updatedAt = Date.now();\n await this.save(conv.id);\n if (!options?.silent) {\n this.events.emit('changed', { type: 'updated', conversation: this.snapshotConversation(conv), messagesChanged: patch.messages !== undefined } as ConversationChangeEvent);\n }\n return conv;\n }\n\n /**\n * Synchronously update the last assistant message content in memory, then schedule a save.\n * Used by activeSessionRegistry.persistNow() to avoid the async get() → .then() race.\n */\n updateLastAssistantContent(id: string, content: string, parts?: MessagePart[]): void {\n const conv = this.conversations.get(id);\n if (!conv) return;\n const lastMsg = conv.messages[conv.messages.length - 1];\n if (!lastMsg || lastMsg.role !== 'assistant') return;\n lastMsg.content = content;\n if (parts !== undefined) lastMsg.parts = parts;\n conv.updatedAt = Date.now();\n this.save(conv.id).catch(() => {});\n }\n\n async listByKanbanBoardId(boardId: string): Promise<Conversation[]> {\n await this.load();\n return Array.from(this.conversations.values()).filter(c => c.kanbanBoardId === boardId);\n }\n\n async listByKanbanCardId(cardId: string): Promise<Conversation[]> {\n await this.load();\n return Array.from(this.conversations.values()).filter(c => c.kanbanCardId === cardId);\n }\n\n /**\n * Épingler ou détacher une conversation.\n *\n * Épinglée, elle se pose en fin de section : ce que l'utilisateur avait\n * rangé garde sa place. Détachée, elle retombe dans la liste par récence —\n * sans que sa date d'activité bouge, sinon un simple détachement la ferait\n * remonter en tête comme si on venait d'y travailler.\n */\n async setPinned(id: string, pinned: boolean): Promise<Conversation | null> {\n await this.load();\n const conv = this.conversations.get(id);\n if (!conv) return null;\n if (pinned === (conv.pinOrder !== undefined)) return conv;\n\n if (!pinned) {\n delete conv.pinOrder;\n } else {\n // Épinglée, elle quitte sa section : une seule zone à la fois.\n delete conv.sectionId;\n delete conv.sectionOrder;\n const ranks = Array.from(this.conversations.values())\n .map(c => c.pinOrder)\n .filter((r): r is number => r !== undefined);\n conv.pinOrder = ranks.length > 0 ? Math.max(...ranks) + 1 : 0;\n }\n\n await this.save(conv.id);\n this.events.emit('changed', { type: 'updated', conversation: this.snapshotConversation(conv) } as ConversationChangeEvent);\n return conv;\n }\n\n /**\n * Réordonner les conversations épinglées `ids`, dans cet ordre.\n *\n * Seules celles-là bougent : les rangs qu'elles occupaient déjà sont\n * redistribués entre elles. Un appel ne peut donc pas déplacer — ni\n * détacher — une épinglée absente de la liste, ce qui compte parce que\n * l'écran ne voit que les conversations du projet courant et ignore les\n * autres. Un id inconnu ou non épinglé est ignoré.\n */\n async reorderPinned(ids: string[]): Promise<void> {\n await this.load();\n // Dédupliqué : deux fois le même id ferait absorber deux places à une seule\n // conversation, et l'ordre rendu ne serait plus celui qui a été demandé.\n const targets = Array.from(new Set(ids))\n .map(id => this.conversations.get(id))\n .filter((c): c is Conversation => !!c && c.pinOrder !== undefined);\n if (targets.length === 0) return;\n\n const ranks = targets.map(c => c.pinOrder!).sort((a, b) => a - b);\n targets.forEach((conv, i) => { conv.pinOrder = ranks[i]!; });\n\n await this.save(...targets.map(c => c.id));\n for (const conv of targets) {\n this.events.emit('changed', { type: 'updated', conversation: this.snapshotConversation(conv) } as ConversationChangeEvent);\n }\n }\n\n /**\n * Ranger les conversations `ids` dans la section `sectionId`, dans cet ordre.\n *\n * `null` les sort de toute section : elles retombent dans la liste par\n * récence. Rangées dans une section, elles perdent leur épinglage. Les\n * autres conversations de la section ne bougent pas : l'appelant envoie\n * celles qui en sortent dans un appel à `null`. Un id inconnu est ignoré.\n */\n async setSection(sectionId: string | null, ids: string[]): Promise<Conversation[]> {\n await this.load();\n const targets = Array.from(new Set(ids))\n .map(id => this.conversations.get(id))\n .filter((c): c is Conversation => !!c);\n if (targets.length === 0) return [];\n\n targets.forEach((conv, i) => {\n if (sectionId === null) {\n delete conv.sectionId;\n delete conv.sectionOrder;\n } else {\n conv.sectionId = sectionId;\n conv.sectionOrder = i;\n delete conv.pinOrder;\n }\n });\n\n await this.save(...targets.map(c => c.id));\n for (const conv of targets) {\n this.events.emit('changed', { type: 'updated', conversation: this.snapshotConversation(conv) } as ConversationChangeEvent);\n }\n return targets;\n }\n\n /** Les conversations d'une section supprimée retombent dans la liste. */\n async clearSection(sectionId: string): Promise<void> {\n await this.load();\n const ids = Array.from(this.conversations.values())\n .filter(c => c.sectionId === sectionId)\n .map(c => c.id);\n if (ids.length > 0) await this.setSection(null, ids);\n }\n\n async delete(id: string): Promise<void> {\n await this.load();\n const existed = this.conversations.delete(id);\n this.dirty.delete(id);\n const path = this.written.get(id);\n this.written.delete(id);\n if (path) this.removed.add(path);\n await this.save();\n if (existed) {\n this.events.emit('changed', { type: 'deleted', id } as ConversationChangeEvent);\n }\n }\n\n onChanged(handler: (event: ConversationChangeEvent) => void): void {\n this.events.on('changed', handler);\n }\n\n offChanged(handler: (event: ConversationChangeEvent) => void): void {\n this.events.off('changed', handler);\n }\n}\n\nexport const conversationStore = new ConversationStore();\n","import { isAbsolute, join, dirname } from 'node:path';\nimport { projectStore } from './project-store.js';\nimport { serviceStore } from './service-store.js';\nimport { projectPathStore } from './project-path-store.js';\nimport type { ProjectRef } from './project-key.js';\nimport { conversationStore } from './conversation-store.js';\nimport { localVaultRoot, vaultFor } from './vault-resolver.js';\n\n/**\n * The one place that answers \"which directory does this work happen in?\".\n *\n * Terminals, the AI chat, git and the editor each used to answer it themselves,\n * and each fell back to `'.'` or `process.cwd()` — the directory Runeya was\n * launched from. For a cloud project, materialized from a document that holds no\n * path, that meant every project ran against whatever code happened to sit\n * there. Four copies of the same fallback are four chances to forget one, so\n * they now share this.\n *\n * Deliberately NOT a process-wide `chdir`: Runeya orchestrates several projects\n * in one process and hands execution to an agent that may not even be on this\n * machine. A working directory belongs to an operation, not to the server.\n *\n * The order is narrowest-first: a service knows better than its project, and a\n * project better than wherever the server was started.\n */\n\nexport interface WorkspaceTarget {\n serviceId?: string | undefined;\n projectId?: string | undefined;\n /**\n * L'organisation du projet visé. Sans elle, un id partagé par plusieurs\n * organisations désignerait un projet au hasard — et donc un répertoire de\n * travail au hasard.\n */\n orgId?: string | null | undefined;\n /**\n * A directory to use instead of the service's own `cwd`, anchored the same way.\n *\n * For paths a service carries besides its working directory — the wiki's\n * `docsPath`, say. Relative ones anchor on the project; absolute ones, and\n * those the agent expands, pass through untouched.\n */\n override?: string | undefined;\n}\n\n/**\n * Where a project lives on this machine, or null when nothing places it.\n *\n * Un projet local est son dossier : celui de son `.runeya`. Le projet du\n * dossier de lancement répond `null`, comme avant, et garde le dossier par\n * défaut de l'appelant.\n */\nasync function projectRoot(ref: ProjectRef): Promise<string | null> {\n const project = await projectStore.get(ref);\n if (!project) return null;\n if (!project.orgId) {\n const root = vaultFor(project).root;\n return root === localVaultRoot() ? null : dirname(root);\n }\n return projectPathStore.get(project);\n}\n\n/**\n * The absolute directory an operation should run in.\n *\n * A cwd that is already absolute, or that the agent expands itself (`~`, `$VAR`),\n * is returned untouched — prefixing those would produce a path that exists\n * nowhere.\n */\nexport async function resolveWorkspaceCwd(target: WorkspaceTarget): Promise<string> {\n const launchDir = localVaultRootParent();\n\n if (target.serviceId) {\n const service = await serviceStore.get(target.serviceId);\n if (service) {\n const raw = (target.override ?? service.cwd ?? '').trim();\n const projects = await projectStore.list();\n const owner = projects.find((p) => p.serviceIds.includes(service.id));\n const root = owner ? await projectRoot(owner) : null;\n // No project directory: hand back what the service holds, untouched. The\n // agent resolves relative paths against ITS OWN directory, and it may not\n // even be on this machine — absolutising here would send it somewhere that\n // does not exist.\n return root ? anchor(root, raw) : (raw || '.');\n }\n }\n\n if (target.projectId) {\n const root = await projectRoot({ id: target.projectId, orgId: target.orgId });\n const raw = (target.override ?? '').trim();\n return root ? anchor(root, raw) : (raw || launchDir);\n }\n\n return anchor(launchDir, (target.override ?? '').trim());\n}\n\n/** Join a relative path onto its base, leaving absolute and agent-expanded ones alone. */\nfunction anchor(base: string, raw: string): string {\n if (!raw || raw === '.') return base;\n if (raw.startsWith('~') || raw.startsWith('$') || isAbsolute(raw)) return raw;\n return join(base, raw);\n}\n\n/**\n * The directory Runeya was launched in — the vault's parent.\n *\n * Derived from the vault rather than read from `process.cwd()`, so it stays put\n * even if something changes the process's directory.\n */\nfunction localVaultRootParent(): string {\n return dirname(localVaultRoot());\n}\n\n/**\n * The directory to hand an AGENT, or undefined when there is nothing to anchor.\n *\n * Distinct from `resolveWorkspaceCwd` on purpose. That one always answers with an\n * absolute path, which is right for work the SERVER does on its own disk. An\n * agent is another process — possibly on another machine — and resolves relative\n * paths against its own directory: handing it this machine's launch directory\n * would send it somewhere that need not exist there.\n *\n * So it only speaks when a project actually places the work somewhere; otherwise\n * it stays silent and the caller keeps whatever default it had.\n */\nexport async function resolveAgentCwd(target: WorkspaceTarget): Promise<string | undefined> {\n if (target.serviceId) {\n const service = await serviceStore.get(target.serviceId);\n if (service) {\n const projects = await projectStore.list();\n const owner = projects.find((p) => p.serviceIds.includes(service.id));\n const root = owner ? await projectRoot(owner) : null;\n if (!root) return undefined;\n return anchor(root, (target.override ?? service.cwd ?? '').trim());\n }\n }\n\n if (target.projectId) {\n const root = await projectRoot({ id: target.projectId, orgId: target.orgId });\n if (!root) return undefined;\n return anchor(root, (target.override ?? '').trim());\n }\n\n return undefined;\n}\n\n/**\n * Le répertoire d'une conversation — celui du projet à qui elle appartient.\n *\n * Une conversation porte déjà son projet : le chat et les scénarios n'ont donc\n * rien à se faire passer de main en main jusqu'au lancement du CLI. Chacun le\n * demandait pourtant à sa façon, et la file kanban, qui lance un scénario hors\n * de toute requête, ne transmettait rien : le CLI travaillait alors dans le\n * répertoire de lancement de Runeya, pas dans celui du projet de la carte.\n *\n * Muette quand rien ne place la conversation, comme `resolveAgentCwd`.\n */\nexport async function resolveConversationCwd(conversationId: string): Promise<string | undefined> {\n const conv = await conversationStore.get(conversationId);\n const ref = conv ? await conversationStore.projectRefOf(conv) : null;\n if (!ref) return resolveAgentCwd({});\n return resolveAgentCwd({ projectId: ref.id, orgId: ref.orgId });\n}\n","/**\n * « Un tour vient de finir sur cette conversation. »\n *\n * Deux chemins très différents produisent ce même fait, et c'est pour ça que\n * le signal vit ici plutôt que chez l'un des deux :\n *\n * - le streaming (`activeSessionRegistry.markDone`), quand Runeya pilote le\n * CLI et reçoit ses événements ;\n * - le PTY interactif, où l'utilisateur tape dans un vrai terminal : Runeya\n * n'orchestre rien, elle lit le transcript que Claude écrit sur le disque.\n * La fin de tour s'y lit sur `stop_reason: end_turn`.\n *\n * Le loger dans le registre de sessions aurait laissé le second chemin muet —\n * un PTY n'y enregistre aucune session. C'est exactement le défaut qui a rendu\n * le compagnon silencieux dans l'interface par défaut de Claude Code.\n */\nimport { EventEmitter } from 'node:events';\n\nconst bus = new EventEmitter();\n// Un abonné par service intéressé, pas par conversation : la limite de 10 par\n// défaut n'a pas lieu de s'appliquer si d'autres s'y branchent un jour.\nbus.setMaxListeners(0);\n\nexport function emitTurnFinished(conversationId: string): void {\n bus.emit('turn-finished', conversationId);\n}\n\nexport function onTurnFinished(listener: (conversationId: string) => void): () => void {\n bus.on('turn-finished', listener);\n return () => { bus.off('turn-finished', listener); };\n}\n","import { readdir, access, readFile } from 'node:fs/promises';\nimport { watch, type FSWatcher } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { emitTurnFinished } from './turn-events.js';\nimport { conversationStore, type Message, type MessagePart } from './conversation-store.js';\n\nexport interface ClaudeTranscriptEntry {\n type: string;\n uuid?: string;\n timestamp?: string;\n message?: {\n role?: string;\n content?: unknown;\n usage?: unknown;\n model?: string;\n /** `end_turn` quand Claude a fini de parler ; autre chose quand il enchaîne. */\n stop_reason?: string | null;\n };\n toolUseResult?: unknown;\n}\n\nconst PROJECTS_DIR = join(homedir(), '.claude', 'projects');\nconst DEFAULT_TITLES = new Set(['', 'New conversation', 'Nouvelle conversation']);\n\nexport async function findClaudeSessionFile(sessionId: string): Promise<string | null> {\n let entries: string[];\n try {\n entries = await readdir(PROJECTS_DIR);\n } catch {\n return null;\n }\n const filename = `${sessionId}.jsonl`;\n for (const dir of entries) {\n const candidate = join(PROJECTS_DIR, dir, filename);\n try {\n await access(candidate);\n return candidate;\n } catch {\n // try next\n }\n }\n return null;\n}\n\nexport async function claudeSessionExists(sessionId: string): Promise<boolean> {\n return (await findClaudeSessionFile(sessionId)) !== null;\n}\n\nexport async function readClaudeTranscript(sessionId: string): Promise<{\n found: boolean;\n path: string | null;\n entries: ClaudeTranscriptEntry[];\n}> {\n const path = await findClaudeSessionFile(sessionId);\n if (!path) return { found: false, path: null, entries: [] };\n const raw = await readFile(path, 'utf-8');\n const entries: ClaudeTranscriptEntry[] = [];\n for (const line of raw.split('\\n')) {\n if (!line.trim()) continue;\n try {\n entries.push(JSON.parse(line) as ClaudeTranscriptEntry);\n } catch {\n // skip malformed line\n }\n }\n return { found: true, path, entries };\n}\n\nfunction claudeEntriesToMessages(entries: ClaudeTranscriptEntry[]): Message[] {\n const messages: Message[] = [];\n const toolPartById = new Map<string, MessagePart & { type: 'tool' }>();\n\n for (const entry of entries) {\n if (entry.type !== 'user' && entry.type !== 'assistant') continue;\n const msg = entry.message;\n if (!msg) continue;\n const role: 'user' | 'assistant' =\n msg.role === 'user' || msg.role === 'assistant'\n ? msg.role\n : (entry.type as 'user' | 'assistant');\n const content = msg.content;\n\n const texts: string[] = [];\n const parts: MessagePart[] = [];\n let consumedByToolResult = false;\n\n if (typeof content === 'string') {\n texts.push(content);\n } else if (Array.isArray(content)) {\n for (const block of content) {\n if (typeof block === 'string') {\n texts.push(block);\n continue;\n }\n if (!block || typeof block !== 'object') continue;\n const b = block as {\n type?: string;\n text?: string;\n id?: string;\n name?: string;\n input?: Record<string, unknown>;\n tool_use_id?: string;\n content?: unknown;\n is_error?: boolean;\n };\n if (b.type === 'text' && typeof b.text === 'string') {\n texts.push(b.text);\n parts.push({ type: 'text', text: b.text });\n } else if (b.type === 'tool_use' && b.id) {\n const part: MessagePart & { type: 'tool' } = {\n type: 'tool',\n id: b.id,\n name: b.name ?? 'tool',\n input: b.input,\n pending: true,\n };\n parts.push(part);\n toolPartById.set(b.id, part);\n } else if (b.type === 'tool_result' && b.tool_use_id) {\n const target = toolPartById.get(b.tool_use_id);\n if (target) {\n target.result = b.content;\n target.isError = b.is_error;\n target.pending = false;\n }\n consumedByToolResult = true;\n }\n }\n }\n\n if (role === 'user' && consumedByToolResult && texts.length === 0) continue;\n\n // Merge into the previous message when it has the same role — Claude\n // emits one transcript entry per assistant turn (text / tool_use /\n // tool_result), so consecutive assistant entries are part of the same\n // logical reply and should render as one bubble.\n const last = messages[messages.length - 1];\n if (last && last.role === role) {\n const joinedText = [last.content, texts.join('\\n').trim()]\n .filter(Boolean)\n .join('\\n');\n last.content = joinedText;\n if (parts.length > 0) {\n last.parts = [...(last.parts ?? []), ...parts];\n }\n continue;\n }\n\n messages.push({\n role,\n content: texts.join('\\n').trim(),\n parts: parts.length > 0 ? parts : undefined,\n });\n }\n\n return messages;\n}\n\n\n/**\n * L'identité du dernier tour achevé du transcript, s'il y en a un.\n *\n * En PTY interactif, Runeya n'orchestre rien : elle relit un fichier que Claude\n * réécrit à chaque delta. La fin d'un tour ne s'y déduit pas de « le fichier a\n * changé » — seulement de `stop_reason: end_turn` sur la dernière entrée\n * d'assistant. L'uuid sert ensuite à ne l'annoncer qu'une fois : sans lui, un\n * seul tour déclencherait autant de relectures que d'écritures du fichier.\n */\nexport function lastFinishedTurnId(entries: ClaudeTranscriptEntry[]): string | null {\n for (let i = entries.length - 1; i >= 0; i--) {\n const entry = entries[i];\n if (entry.type !== 'assistant') continue;\n // La première entrée d'assistant rencontrée en remontant est la dernière du\n // tour : si elle n'a pas fini, c'est qu'aucun tour ne vient de s'achever.\n if (entry.message?.stop_reason !== 'end_turn') return null;\n return entry.uuid ?? `${entry.timestamp ?? ''}#${i}`;\n }\n return null;\n}\n\n/** Dernier tour déjà annoncé, par conversation. */\nconst announcedTurns = new Map<string, string>();\n\n/** Le tour `turnId` est-il nouveau pour cette conversation ? */\nexport function shouldAnnounceTurn(conversationId: string, turnId: string | null): boolean {\n if (!turnId) return false;\n if (announcedTurns.get(conversationId) === turnId) return false;\n announcedTurns.set(conversationId, turnId);\n return true;\n}\n\nexport function forgetAnnouncedTurns(conversationId: string): void {\n announcedTurns.delete(conversationId);\n}\n\n/**\n * Sync a Runeya conversation with its on-disk Claude JSONL transcript.\n * Skips if nothing changed; derives title from first user message on first sync.\n * Returns true when the conversation was actually updated.\n */\nexport async function syncConversationFromClaudeTranscript(conversationId: string): Promise<boolean> {\n const conv = await conversationStore.get(conversationId);\n if (!conv) return false;\n const transcript = await readClaudeTranscript(conversationId);\n if (!transcript.found) return false;\n\n const messages = claudeEntriesToMessages(transcript.entries);\n const isDefaultTitle = !conv.title || DEFAULT_TITLES.has(conv.title);\n\n const sameLength = messages.length === conv.messages.length;\n const sameLastContent =\n sameLength && (messages.at(-1)?.content ?? '') === (conv.messages.at(-1)?.content ?? '');\n if (sameLength && sameLastContent && !isDefaultTitle) {\n return false;\n }\n\n const patch: { messages: Message[]; title?: string } = { messages };\n if (isDefaultTitle) {\n const firstUserMsg = messages.find((m) => m.role === 'user' && m.content.trim().length > 0);\n if (firstUserMsg) {\n const firstLine = firstUserMsg.content.split('\\n')[0].trim();\n patch.title = firstLine.length > 80 ? firstLine.slice(0, 80) + '…' : firstLine;\n }\n }\n\n await conversationStore.update(conversationId, patch, { touch: false });\n\n // Après l'écriture : ce que le compagnon relira doit être en base, pas\n // encore en cours d'écriture.\n if (shouldAnnounceTurn(conversationId, lastFinishedTurnId(transcript.entries))) {\n emitTurnFinished(conversationId);\n }\n return true;\n}\n\n// ─── Activity tracking ─────────────────────────────────────────────────────\n\n/** Last time we observed a write to a given session's JSONL. */\nconst lastTranscriptActivity = new Map<string, number>();\n\nexport function getLastTranscriptActivity(sessionId: string): number | null {\n return lastTranscriptActivity.get(sessionId) ?? null;\n}\n\n// ─── Realtime watcher ──────────────────────────────────────────────────────\n\nlet started = false;\nconst watchers: FSWatcher[] = [];\nconst pendingResyncs = new Map<string, NodeJS.Timeout>();\nconst RESYNC_DEBOUNCE_MS = 250;\n\nfunction scheduleResync(sessionId: string): void {\n const existing = pendingResyncs.get(sessionId);\n if (existing) clearTimeout(existing);\n pendingResyncs.set(\n sessionId,\n setTimeout(() => {\n pendingResyncs.delete(sessionId);\n syncConversationFromClaudeTranscript(sessionId).catch((err) => {\n console.warn('[claude-transcript-sync] resync failed for', sessionId, err);\n });\n }, RESYNC_DEBOUNCE_MS),\n );\n}\n\n/**\n * L'identifiant de session porté par un chemin rendu par `fs.watch`, ou `null`.\n *\n * Le chemin est relatif au répertoire surveillé et utilise le séparateur de la\n * plateforme : « <projectDir>/<sessionId>.jsonl » sur POSIX, « \\ » sur Windows —\n * ne couper que sur « / » y laissait le nom du projet collé devant l'identifiant :\n * le contrôle d'UUID échouait, plus aucune session externe n'était resynchronisée,\n * donc aucune fin de tour annoncée — et pas de compagnon.\n */\nexport function sessionIdFromWatchedFilename(filename: string | null): string | null {\n if (!filename) return null;\n if (!filename.endsWith('.jsonl')) return null;\n const base = filename.split(/[\\\\/]/).pop()!;\n const sessionId = base.slice(0, -'.jsonl'.length);\n if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(sessionId)) return null;\n return sessionId;\n}\n\nfunction handleFsEvent(filename: string | null): void {\n const sessionId = sessionIdFromWatchedFilename(filename);\n if (!sessionId) return;\n lastTranscriptActivity.set(sessionId, Date.now());\n scheduleResync(sessionId);\n}\n\n/**\n * Watch ~/.claude/projects/ recursively. Whenever a `<sessionId>.jsonl` file\n * is touched, re-sync the matching Runeya conversation (if any) so the\n * non-PTY message view stays live.\n */\nexport async function startClaudeTranscriptWatcher(): Promise<void> {\n if (started) return;\n started = true;\n\n let topLevel: string[];\n try {\n topLevel = await readdir(PROJECTS_DIR);\n } catch {\n // ~/.claude/projects doesn't exist yet — watch parent for creation.\n return;\n }\n\n // Prefer a single recursive watcher when supported, otherwise watch each\n // project dir individually.\n try {\n const w = watch(PROJECTS_DIR, { recursive: true }, (_event, filename) => {\n handleFsEvent(filename ? filename.toString() : null);\n });\n w.on('error', (err) => console.warn('[claude-transcript-sync] watcher error:', err));\n watchers.push(w);\n return;\n } catch {\n // Fallback below.\n }\n\n for (const dir of topLevel) {\n try {\n const w = watch(join(PROJECTS_DIR, dir), (_event, filename) => {\n handleFsEvent(filename ? filename.toString() : null);\n });\n w.on('error', () => {});\n watchers.push(w);\n } catch {\n // ignore\n }\n }\n}\n\nexport function stopClaudeTranscriptWatcher(): void {\n for (const w of watchers) {\n try {\n w.close();\n } catch {\n // ignore\n }\n }\n watchers.length = 0;\n for (const t of pendingResyncs.values()) clearTimeout(t);\n pendingResyncs.clear();\n started = false;\n}\n","/**\n * Le pendant Codex de `claude-transcript-sync`.\n *\n * Sans lui, une conversation Codex en terminal n'existait pour Runeya que comme\n * un terminal : ses messages n'entraient jamais dans la conversation, et rien\n * n'annonçait la fin d'un tour. Le compagnon ne se déclenchait donc jamais sur\n * Codex — pas par un défaut de sa logique, mais faute de signal.\n *\n * Codex écrit un « rollout » par session :\n * ~/.codex/thread_history_<n>.sqlite (tables thread_turns / thread_items)\n *\n * Deux formes d'entrées nous intéressent :\n * - `response_item` / `message` : le fil, avec son rôle ;\n * - `event_msg` / `task_complete` : la FIN d'un tour — et elle porte même le\n * dernier message de l'agent, ce qui évite de le reconstituer.\n */\nimport { watch, type FSWatcher } from 'node:fs';\nimport { dirname } from 'node:path';\nimport { conversationStore } from './conversation-store.js';\nimport { emitTurnFinished } from './turn-events.js';\nimport { findThreadDb, threadMessages, lastTurn, threadsStartedAfter } from './codex-thread-db.js';\n\nconst announced = new Map<string, string>();\n\nexport function forgetCodexTurns(conversationId: string): void {\n announced.delete(conversationId);\n}\n\n/**\n * Aligne une conversation Runeya sur son rollout Codex.\n *\n * Rend `true` quand elle a changé. Annonce la fin de tour au passage, une seule\n * fois par tour — c'est ce signal que le compagnon écoute.\n */\nexport async function syncConversationFromCodexTranscript(conversationId: string): Promise<boolean> {\n const conv = await conversationStore.get(conversationId);\n if (!conv?.codexThreadId) return false;\n\n const messages = threadMessages(conv.codexThreadId);\n if (messages.length === 0) return false;\n\n const same = messages.length === conv.messages.length\n && (messages.at(-1)?.content ?? '') === (conv.messages.at(-1)?.content ?? '');\n if (!same) await conversationStore.update(conversationId, { messages }, { touch: false });\n\n // `completed_at` est déclaré par Codex : contrairement aux rollouts, il n'y a\n // rien à déduire de « le fichier a changé ».\n const turn = lastTurn(conv.codexThreadId);\n if (turn?.completedAt && announced.get(conversationId) !== turn.turnId) {\n announced.set(conversationId, turn.turnId);\n emitTurnFinished(conversationId);\n }\n return !same;\n}\n\n// ─── Observateur ────────────────────────────────────────────────────────────\n\nlet watcher: FSWatcher | null = null;\nlet debounce: NodeJS.Timeout | null = null;\n\n/**\n * Toutes les conversations Codex rattachées, resynchronisées ensemble.\n *\n * La base ne dit pas quel fil vient de changer — et le savoir coûterait plus\n * cher que de relire : un fil fait quelques dizaines de lignes, et seules les\n * conversations déjà rattachées sont concernées.\n */\nasync function resyncAll(): Promise<void> {\n const convs = await conversationStore.list();\n await bindPendingSpawns(convs.map(c => c.codexThreadId).filter((x): x is string => !!x));\n for (const c of await conversationStore.list()) {\n if (!c.codexThreadId) continue;\n await syncConversationFromCodexTranscript(c.id).catch(() => false);\n }\n}\n\n/**\n * Rattacher les terminaux Codex encore sans fil.\n *\n * Codex ne dit pas quel fil il vient d'ouvrir. Il l'annonce parfois dans son\n * terminal — mais seulement au renommage d'une session, donc pas toujours. Ce\n * qu'on sait en revanche toujours, c'est QUAND on a lancé le terminal : un fil\n * dont le premier tour démarre après ce lancement est le nôtre.\n *\n * Une marge de quelques secondes absorbe l'écart entre le spawn et l'écriture\n * du premier tour. En cas d'ambiguïté — deux terminaux ouverts coup sur coup —\n * on renonce : rattacher au petit bonheur ferait relire au compagnon les tours\n * d'une autre conversation.\n */\nconst BIND_MARGIN_SECONDS = 10;\n\nasync function bindPendingSpawns(boundThreadIds: string[]): Promise<void> {\n for (const [spawnId, spawn] of spawnConversations) {\n const conv = await conversationStore.get(spawn.conversationId);\n if (!conv) { spawnConversations.delete(spawnId); continue; }\n if (conv.codexThreadId) { spawnConversations.delete(spawnId); continue; }\n if (conv.runner !== 'codex') continue;\n\n const candidates = threadsStartedAfter(spawn.startedAt - BIND_MARGIN_SECONDS)\n .filter(id => !boundThreadIds.includes(id));\n if (candidates.length !== 1) continue;\n\n await conversationStore.update(conv.id, { codexThreadId: candidates[0] });\n boundThreadIds.push(candidates[0]!);\n spawnConversations.delete(spawnId);\n }\n}\n\nexport function startCodexTranscriptWatcher(): void {\n if (watcher) return;\n const db = findThreadDb();\n if (!db) return; // Codex absent, ou base d'un schéma qu'on ne connaît pas\n\n try {\n // On observe le DOSSIER, pas le fichier : SQLite écrit par `-wal` et\n // remplace ses fichiers, ce qu'un observateur posé sur un seul fichier\n // cesse de voir dès la première rotation.\n watcher = watch(dirname(db), (_event, filename) => {\n if (!filename || !/^thread_history_\\d+\\.sqlite/.test(filename.toString())) return;\n if (debounce) clearTimeout(debounce);\n debounce = setTimeout(() => { debounce = null; void resyncAll(); }, 400);\n });\n watcher.on('error', (err) => console.warn('[codex-thread-sync]', err));\n } catch {\n // Dossier illisible : Codex n'est pas installé, rien à observer.\n }\n}\n\nexport function stopCodexTranscriptWatcher(): void {\n watcher?.close();\n watcher = null;\n if (debounce) { clearTimeout(debounce); debounce = null; }\n tails.clear();\n spawnConversations.clear();\n}\n\n// ─── Rattachement par l'annonce de Codex ────────────────────────────────────\n\n/**\n * Codex choisit lui-même l'identifiant de sa session, et rien ne le pose sur la\n * conversation quand elle tourne dans un terminal — `codexThreadId` n'est\n * renseigné que par le chemin sans terminal. Sans rattachement, un tour Codex\n * interactif n'annonce jamais sa fin, et le compagnon ne part pas.\n *\n * Mais Codex l'annonce lui-même, dans son terminal :\n * « To resume this session run `codex resume`, then select … (01a066bd-…) »\n *\n * Le serveur relaie chaque octet du PTY : il suffit de le lire. C'est\n * déterministe — l'identifiant vient de la session elle-même — là où deviner\n * par le répertoire de travail confondrait deux terminaux ouverts au même\n * endroit, et ferait relire au compagnon les tours d'une autre conversation.\n */\nconst CODEX_RESUME_RE = /codex resume[\\s\\S]{0,300}?\\(([0-9a-fA-F]{8}-[0-9a-fA-F-]{27})\\)/;\n\n/** Le flux arrive par fragments : l'annonce peut être coupée en deux. */\nconst MAX_TAIL = 4_000;\nconst tails = new Map<string, string>();\n/** À quelle conversation appartient un spawn, appris au démarrage du PTY. */\nconst spawnConversations = new Map<string, { conversationId: string; startedAt: number }>();\n\nexport function noteSpawnConversation(spawnId: string, conversationId?: string): void {\n // Un PTY peut être lancé sans conversation (terminal libre) : il n'y a alors\n // rien à rattacher, et retenir un lien vide ferait fuir la carte.\n if (!conversationId) return;\n // L'instant sert au rattachement par le temps : un fil né après ce spawn est\n // le nôtre. C'est le seul repère toujours disponible — l'annonce de Codex,\n // elle, ne paraît qu'au renommage d'une session.\n spawnConversations.set(spawnId, { conversationId, startedAt: Math.floor(Date.now() / 1000) });\n}\n\n/** L'identifiant de session annoncé par Codex dans ce texte, s'il y en a un. */\nexport function codexSessionIdIn(text: string): string | null {\n return CODEX_RESUME_RE.exec(text)?.[1] ?? null;\n}\n\n/**\n * Observer un fragment de terminal, et rattacher la session s'il l'annonce.\n *\n * Sans effet — et sans coût — pour tout ce qui n'est pas une conversation Codex\n * en attente de rattachement.\n */\nexport function observePtyChunk(spawnId: string, data: string): void {\n const spawn = spawnConversations.get(spawnId);\n if (!spawn) return;\n const conversationId = spawn.conversationId;\n\n const tail = (tails.get(spawnId) ?? '') + data;\n const sessionId = codexSessionIdIn(tail);\n if (!sessionId) {\n tails.set(spawnId, tail.slice(-MAX_TAIL));\n return;\n }\n\n // Trouvé : plus rien à surveiller pour ce spawn.\n tails.delete(spawnId);\n spawnConversations.delete(spawnId);\n\n void (async () => {\n const conv = await conversationStore.get(conversationId);\n if (!conv || conv.codexThreadId === sessionId) return;\n await conversationStore.update(conversationId, { codexThreadId: sessionId });\n await syncConversationFromCodexTranscript(conversationId).catch(() => false);\n })();\n}\n\nexport function forgetSpawn(spawnId: string): void {\n tails.delete(spawnId);\n spawnConversations.delete(spawnId);\n}\n","import { randomBytes } from 'node:crypto';\nimport { SignJWT, jwtVerify } from 'jose';\nimport { env } from '../config/env.js';\n\nlet jwtSecret: Uint8Array | null = null;\nlet jwtSecretGeneratedAt: number = 0; // Unix timestamp seconds\n\nexport function generateJwtSecret(): void {\n jwtSecret = new Uint8Array(randomBytes(32));\n jwtSecretGeneratedAt = Math.floor(Date.now() / 1000);\n}\n\nexport function getJwtSecretGeneratedAt(): number {\n return jwtSecretGeneratedAt;\n}\n\nfunction getJwtSecret(): Uint8Array {\n if (!jwtSecret) throw new Error('JWT secret not yet generated');\n return jwtSecret;\n}\n\nexport interface SignJwtOptions {\n /**\n * Le client MCP appairé au nom duquel le jeton est émis. Un tel jeton n'est\n * valable que tant que l'appairage existe : voir `setMcpClientCheck`.\n */\n mcpClientId?: string | undefined;\n /** Durée du jeton, au format de `RUNEYA_JWT_TTL`. */\n ttl?: string | undefined;\n}\n\n/** Sign a JWT valid for `RUNEYA_JWT_TTL` (8 hours by default) */\nexport async function signJwt(opts: SignJwtOptions = {}): Promise<string> {\n const secret = getJwtSecret();\n return new SignJWT(opts.mcpClientId ? { sub: 'app', mcp: opts.mcpClientId } : { sub: 'app' })\n .setProtectedHeader({ alg: 'HS256' })\n .setIssuedAt()\n .setExpirationTime(opts.ttl ?? env.JWT_TTL)\n .sign(secret);\n}\n\nexport interface JwtPayload {\n exp: number;\n iat: number;\n /** Présent sur un jeton émis pour un client MCP appairé. */\n mcpClientId?: string;\n}\n\nlet mcpClientCheck: ((clientId: string) => Promise<boolean>) | null = null;\n\n/**\n * Dit si un client MCP appairé l'est encore. Posé au démarrage par le service\n * d'appairage, qui dépend de ce module : l'injection évite l'import circulaire.\n */\nexport function setMcpClientCheck(check: ((clientId: string) => Promise<boolean>) | null): void {\n mcpClientCheck = check;\n}\n\nexport interface VerifyJwtOptions {\n /**\n * Accepter un jeton de client MCP. Refusé par défaut : chaque point d'entrée\n * (WebSocket, flux, pièces jointes…) est fermé aux clients MCP sans rien\n * avoir à déclarer. Seuls l'acceptent le middleware tRPC, qui le borne aux\n * routes des outils, et le renouvellement.\n */\n allowMcp?: boolean;\n}\n\n/**\n * Verify a JWT — throws if invalid/expired.\n *\n * Un jeton de client MCP est refusé hors des appelants qui l'acceptent\n * (`allowMcp`), et dès que son appairage est révoqué. Sans vérificateur posé,\n * un tel jeton est refusé.\n */\nexport async function verifyJwt(token: string, opts: VerifyJwtOptions = {}): Promise<JwtPayload> {\n const secret = getJwtSecret();\n const { payload } = await jwtVerify(token, secret);\n const result: JwtPayload = { exp: payload.exp as number, iat: payload.iat as number };\n if (payload['mcp'] !== undefined) {\n if (!opts.allowMcp) throw new Error('MCP client token not allowed here');\n const clientId = payload['mcp'];\n if (typeof clientId !== 'string' || !mcpClientCheck || !(await mcpClientCheck(clientId))) {\n throw new Error('MCP client is no longer paired');\n }\n result.mcpClientId = clientId;\n }\n return result;\n}\n","/**\n * Retrouver le PTY vivant d'une conversation, sans savoir quel agent l'héberge.\n *\n * La conversation ne porte pas l'agent où tourne son terminal : d'où le\n * balayage, et l'arrêt au premier qui répond « running ».\n *\n * `chat.ts` interroge aussi `ai.findPtyBySession` à trois endroits, et ce n'est\n * PAS la même chose — ne pas les fusionner ici sans y regarder : la suppression\n * d'une conversation balaye tous les agents pour tuer chaque PTY (elle n'en\n * cherche pas un), tandis que la reprise de session et la mesure d'activité\n * partent d'un agentId déjà connu et lisent des champs que celle-ci ne rend pas\n * (`lastChunkAt`, le besoin de redimensionner). Trois besoins voisins, trois\n * formes distinctes.\n */\nimport { agentManager } from './agent-manager.js';\n\nexport interface LivePty {\n agentId: string;\n spawnId: string;\n}\n\nexport async function findLivePty(sessionId: string): Promise<LivePty | null> {\n for (const agent of agentManager.listAgents()) {\n const agentId = agent.config.id;\n try {\n const pty = await agentManager.agentQuery<{ spawnId: string; status: string } | null>(\n agentId,\n 'ai.findPtyBySession',\n { sessionId },\n );\n if (pty?.status === 'running') return { agentId, spawnId: pty.spawnId };\n } catch {\n // Agent injoignable : les autres peuvent encore répondre.\n }\n }\n return null;\n}\n","/**\n * Écrire un message dans le terminal d'un CLI d'IA, comme s'il était tapé.\n *\n * Partagé par le compagnon (livraison au chat principal), le runner PTY des\n * scénarios et `chat.pty.start` (contextes et prompt écrits au démarrage, une\n * fois la saisie prête : `waitForPtyInput`).\n */\n\n/** Le temps qu'il faut au CLI pour absorber la saisie avant de la valider. */\nexport const PTY_SUBMIT_DELAY_MS = 120;\n\n/**\n * Un message tenant sur UNE ligne, pour une interface de terminal.\n *\n * Une TUI traite chaque saut de ligne comme une nouvelle ligne du champ de\n * saisie : un message en plusieurs paragraphes s'y étale sans jamais partir, et\n * le premier retour chariot rencontré peut même soumettre une phrase tronquée.\n * On aplatit donc, en gardant les séparations lisibles.\n */\nexport function flattenForPty(message: string): string {\n return message\n .split(/\\n{2,}/).map(block => block.replace(/\\s*\\n\\s*/g, ' ').trim())\n .filter(Boolean)\n .join(' · ')\n .replace(/\\s{2,}/g, ' ')\n .trim();\n}\n\n/**\n * Tape `message` aplati, puis la touche Entrée à part, après un souffle.\n *\n * Collé au texte, le retour chariot arrive dans le même bloc que la saisie :\n * l'interface du CLI le lit comme une nouvelle ligne DANS le champ, et le\n * message s'installe sans jamais partir. Envoyé seul, après que le texte a\n * été absorbé, il vaut la touche Entrée. C'est aussi ce que fait un humain\n * qui colle puis valide.\n */\nexport async function submitToPty(write: (data: string) => Promise<unknown>, message: string): Promise<void> {\n await write(flattenForPty(message));\n await new Promise((r) => setTimeout(r, PTY_SUBMIT_DELAY_MS));\n await write('\\r');\n}\n\n/** Délimiteurs du collage délimité (« bracketed paste ») des terminaux. */\nconst PASTE_START = '\\x1b[200~';\nconst PASTE_END = '\\x1b[201~';\n\n/**\n * Colle `text` tel quel, sauts de ligne compris, puis la touche Entrée à part.\n *\n * Pour un long prompt structuré (sections, listes, code) : l'aplatir comme\n * `submitToPty` lui ferait perdre sa forme. Entre les délimiteurs, le CLI le\n * reçoit comme un collage et garde les sauts de ligne sans les valider. La\n * pause avant Entrée grandit avec la taille du texte, pour que le collage soit\n * absorbé en entier avant d'être envoyé.\n */\nexport async function pasteToPty(\n write: (data: string) => Promise<unknown>,\n text: string,\n options: { lead?: string } = {},\n): Promise<void> {\n // Claude Code présente un collage au modèle comme du texte collé, qu'il ne\n // suit que si l'utilisateur le lui demande. Une phrase tapée avant le collage\n // en fait une demande explicite.\n if (options.lead) await write(`${options.lead} `);\n await write(`${PASTE_START}${text}${PASTE_END}`);\n const delay = Math.min(PTY_SUBMIT_DELAY_MS + Math.ceil(text.length / 100), 3_000);\n await new Promise((r) => setTimeout(r, delay));\n await write('\\r');\n}\n\n// ─── Attendre qu'un terminal de CLI d'IA accepte la saisie ──────────────────\n\n/**\n * Délai minimal avant d'écrire, par CLI. Une simple détection de silence peut\n * se déclencher pendant une pause du démarrage et perdre le collage (c'est ce\n * qui cassait les gros contextes, comme des logs). Ces valeurs sont celles qui\n * ont fait leurs preuves.\n */\nexport const PTY_INPUT_MIN_MS: Record<PtyInputRunner, number> = { 'claude-code': 1_200, codex: 500 };\n/** Durée sans nouvelle sortie au bout de laquelle la zone de saisie est jugée affichée. */\nexport const PTY_INPUT_QUIET_MS = 400;\n/** Sans signal MCP : plafond total, si la sortie ne se calme jamais (curseur qui clignote). */\nexport const PTY_INPUT_MAX_MS = 8_000;\n/**\n * Plafond d'attente du signal MCP. Un agent trop ancien pour transmettre\n * l'identifiant ne l'enverra jamais : on écrit quand même à son terme.\n */\nexport const PTY_MCP_READY_MAX_MS = 15_000;\n/** Une fois le signal MCP reçu (ou son plafond atteint) : plafond de l'attente du silence. */\nconst PTY_QUIET_AFTER_MCP_MAX_MS = 3_000;\n\nexport type PtyInputRunner = 'claude-code' | 'codex';\n\nexport interface WaitForPtyInputOptions {\n agentId: string;\n spawnId: string;\n /** Identifiant transmis à l'agent (`ai.spawnPty`) pour le signal du serveur MCP. */\n mcpReadyId?: string;\n runner: PtyInputRunner;\n signal?: AbortSignal;\n}\n\n/**\n * `ready` : on peut écrire. `exited` : le process est sorti, il n'y a plus\n * rien à écrire. `aborted` : l'appelant a renoncé. `mcpReady` dit si le\n * serveur MCP s'est signalé (absent sans `mcpReadyId`).\n */\nexport interface PtyInputWait {\n status: 'ready' | 'exited' | 'aborted';\n mcpReady?: boolean;\n}\n\n/**\n * Résout quand le terminal `spawnId` peut recevoir une saisie.\n *\n * Ni Claude Code ni Codex ne disent quand leur zone de saisie est prête. Avec\n * `mcpReadyId`, on attend le signal du serveur MCP Runeya (les outils sont\n * alors branchés), puis un court silence de la sortie. Sans lui, on attend le\n * délai minimal du CLI puis un silence, avec un plafond. Dans les deux cas, la\n * sortie du process termine l'attente sans rien écrire.\n */\nexport async function waitForPtyInput(opts: WaitForPtyInputOptions): Promise<PtyInputWait> {\n const { agentId, spawnId, mcpReadyId, runner, signal } = opts;\n if (signal?.aborted) return { status: 'aborted' };\n const { agentManager } = await import('./agent-manager.js');\n const { waitForMcpReady } = await import('./mcp-ready.js');\n\n return new Promise<PtyInputWait>((resolve) => {\n const startedAt = Date.now();\n const floor = PTY_INPUT_MIN_MS[runner];\n const inner = new AbortController();\n let done = false;\n let mcpReady: boolean | undefined;\n // Sans MCP, le silence ne compte qu'à partir de la première sortie (comme\n // avant) ; avec MCP, à partir du signal.\n let quietArmed = !mcpReadyId;\n let quietTimer: ReturnType<typeof setTimeout> | null = null;\n let maxTimer: ReturnType<typeof setTimeout> | null = null;\n let unsub: (() => void) | null = null;\n\n const finish = (status: PtyInputWait['status']): void => {\n if (done) return;\n done = true;\n if (quietTimer) clearTimeout(quietTimer);\n if (maxTimer) clearTimeout(maxTimer);\n signal?.removeEventListener('abort', onAbort);\n inner.abort();\n unsub?.();\n resolve(mcpReadyId ? { status, mcpReady: mcpReady ?? false } : { status });\n };\n const onAbort = (): void => finish('aborted');\n const scheduleQuiet = (): void => {\n if (!quietArmed || done) return;\n if (quietTimer) clearTimeout(quietTimer);\n const wait = Math.max(PTY_INPUT_QUIET_MS, floor - (Date.now() - startedAt));\n quietTimer = setTimeout(() => finish('ready'), wait);\n };\n\n signal?.addEventListener('abort', onAbort, { once: true });\n try {\n unsub = agentManager.agentSubscribe(\n agentId,\n 'ai.stream',\n { spawnId, fromOffset: 0 },\n (data: unknown) => {\n const chunk = (data as { chunk?: { type?: string } }).chunk;\n if (chunk?.type === 'exit') { finish('exited'); return; }\n scheduleQuiet();\n },\n () => { /* flux fermé : les délais décident */ },\n );\n } catch {\n // Agent sans pont WebSocket : pas de flux à observer, les délais décident.\n }\n if (done) { unsub?.(); return; }\n\n if (mcpReadyId) {\n void waitForMcpReady(mcpReadyId, PTY_MCP_READY_MAX_MS, inner.signal).then((ready) => {\n if (done) return;\n mcpReady = ready;\n quietArmed = true;\n scheduleQuiet();\n maxTimer = setTimeout(() => finish('ready'), PTY_QUIET_AFTER_MCP_MAX_MS);\n });\n } else {\n maxTimer = setTimeout(() => finish('ready'), PTY_INPUT_MAX_MS);\n }\n });\n}\n\n/** La phrase tapée avant un prompt collé qui constitue toute la demande. */\nexport const PASTED_TASK_LEAD = 'Here is your task. Follow the instructions below:';\n","import type { ChildProcess } from 'node:child_process';\nimport { conversationStore } from './conversation-store.js';\nimport { agentManager } from './agent-manager.js';\nimport { emitTurnFinished } from './turn-events.js';\n\ntype SessionPart =\n | { type: 'thinking'; text: string }\n | { type: 'text'; text: string }\n | { type: 'tool'; id: string; name: string; input?: Record<string, unknown>; rawInput?: string; result?: unknown; isError?: boolean; pending: boolean };\n\n// ─── Types ─────────────────────────────────────────────────────────────────\n\nexport type QueueItem =\n | { type: 'delta'; text: string }\n | { type: 'status'; text: string }\n | { type: 'thinking'; text: string }\n | { type: 'context_usage'; inputTokens: number; contextWindow: number }\n | { type: 'tool_call'; id: string; name: string; input: Record<string, unknown>; rawInput?: string }\n | { type: 'tool_result'; id: string; name: string; result: unknown; isError: boolean }\n | { type: 'done'; text: '' }\n | { type: 'error'; message: string };\n\ntype AiRunnerType = 'claude-code' | 'provider-api';\n\nexport interface ActiveSession {\n proc: ChildProcess | null;\n abortController: AbortController | null;\n sessionType: AiRunnerType;\n conversationId: string;\n buffer: QueueItem[];\n totalProduced: number;\n fullContent: string;\n parts: SessionPart[];\n lastStatus: string;\n status: 'running' | 'done';\n persistTimer: ReturnType<typeof setTimeout> | null;\n cleanupTimer: ReturnType<typeof setTimeout> | null;\n notify: Set<() => void>;\n /** Agent ID when the AI CLI runs on a remote agent. */\n agentId?: string;\n /** Spawn ID on the remote agent (for kill / reconnect). */\n agentSpawnId?: string;\n}\n\n// ─── Constants ─────────────────────────────────────────────────────────────\n\nconst MAX_BUFFER = 5_000;\nconst PERSIST_DEBOUNCE_MS = 100;\n\n// ─── Registry ──────────────────────────────────────────────────────────────\n\nclass ActiveSessionRegistry {\n private sessions = new Map<string, ActiveSession>();\n registerClaudeCodeSession(conversationId: string, proc: ChildProcess): ActiveSession {\n this.assertNoActiveSession(conversationId);\n\n const session: ActiveSession = {\n proc,\n abortController: null,\n sessionType: 'claude-code',\n conversationId,\n buffer: [],\n totalProduced: 0,\n fullContent: '',\n parts: [],\n lastStatus: '',\n status: 'running',\n persistTimer: null,\n cleanupTimer: null,\n notify: new Set(),\n };\n\n this.sessions.set(conversationId, session);\n return session;\n }\n\n registerProviderApiSession(conversationId: string, ac: AbortController): ActiveSession {\n this.assertNoActiveSession(conversationId);\n\n const session: ActiveSession = {\n proc: null,\n abortController: ac,\n sessionType: 'provider-api',\n conversationId,\n buffer: [],\n totalProduced: 0,\n fullContent: '',\n parts: [],\n lastStatus: '',\n status: 'running',\n persistTimer: null,\n cleanupTimer: null,\n notify: new Set(),\n };\n\n this.sessions.set(conversationId, session);\n return session;\n }\n\n registerDirectProviderApiSession(conversationId: string): ActiveSession {\n this.assertNoActiveSession(conversationId);\n\n const session: ActiveSession = {\n proc: null,\n abortController: new AbortController(),\n sessionType: 'provider-api',\n conversationId,\n buffer: [],\n totalProduced: 0,\n fullContent: '',\n parts: [],\n lastStatus: '',\n status: 'running',\n persistTimer: null,\n cleanupTimer: null,\n notify: new Set(),\n };\n\n this.sessions.set(conversationId, session);\n return session;\n }\n\n private assertNoActiveSession(conversationId: string): void {\n const existing = this.sessions.get(conversationId);\n if (existing && existing.status === 'running') {\n throw new Error(`A session is already running for conversation ${conversationId}. Stop it first.`);\n }\n }\n\n pushEvent(conversationId: string, event: QueueItem): void {\n if(process.env.RUNEYA_DEBUG) console.log('[chat]: event - ', event.type)\n const session = this.sessions.get(conversationId);\n if (!session) return;\n if(process.env.RUNEYA_DEBUG) console.log('[chat]: event - ', event.type, 'session', session.conversationId)\n\n // Accumulate parts for replay/persist\n if (event.type === 'delta') {\n session.fullContent += event.text;\n const last = session.parts[session.parts.length - 1];\n if (last?.type === 'text') last.text += event.text;\n else session.parts.push({ type: 'text', text: event.text });\n this.schedulePersist(session);\n }\n if (event.type === 'tool_call') {\n const existing = session.parts.find(p => p.type === 'tool' && p.id === event.id) as Extract<SessionPart, { type: 'tool' }> | undefined;\n if (existing) { existing.name = event.name; existing.input = event.input; }\n else session.parts.push({ type: 'tool', id: event.id, name: event.name, input: event.input, rawInput: event.rawInput, pending: true });\n this.schedulePersist(session);\n }\n if (event.type === 'tool_result') {\n const te = session.parts.find(p => p.type === 'tool' && p.id === event.id) as Extract<SessionPart, { type: 'tool' }> | undefined;\n if (te) {\n te.result = event.result;\n te.isError = event.isError;\n te.pending = false;\n } else {\n session.parts.push({ type: 'tool', id: event.id, name: event.name, result: event.result, isError: event.isError, pending: false });\n }\n this.schedulePersist(session);\n }\n if (event.type === 'thinking') {\n const last = session.parts[session.parts.length - 1];\n if (last?.type === 'thinking') last.text += event.text;\n else session.parts.push({ type: 'thinking', text: event.text });\n }\n if (event.type === 'status') {\n session.lastStatus = event.text;\n }\n\n // Cap buffer size — drop oldest if full\n if (session.buffer.length >= MAX_BUFFER) {\n session.buffer.shift();\n }\n if(process.env.RUNEYA_DEBUG) console.log('[chat]: event - ', event.type, 'push buffer')\n session.buffer.push(event);\n session.totalProduced++;\n\n // Wake all consumers (multiple tabs/windows)\n if(process.env.RUNEYA_DEBUG) console.log('[chat]: event - ', event.type, 'notify', session.notify.size)\n for (const cb of session.notify) cb();\n session.notify.clear();\n }\n\n markDone(conversationId: string): void {\n if(process.env.RUNEYA_DEBUG) console.log('[chat]: markdone....', conversationId)\n const session = this.sessions.get(conversationId);\n if (!session) return;\n\n const wasRunning = session.status === 'running';\n session.status = 'done';\n\n // Persist final content immediately\n this.persistNow(session);\n\n // Après la persistance : ce que le compagnon relira doit être sur le\n // disque, pas encore dans un tampon. `markDone` est parfois rappelé sur une\n // session déjà terminée — n'annoncer que la transition évite un second\n // compagnon sur le même tour.\n if (wasRunning) emitTurnFinished(conversationId);\n }\n\n get(conversationId: string): ActiveSession | undefined {\n return this.sessions.get(conversationId);\n }\n\n listActive(): { conversationId: string; status: 'running' | 'done'; sessionType: AiRunnerType }[] {\n const result: { conversationId: string; status: 'running' | 'done'; sessionType: AiRunnerType }[] = [];\n for (const session of this.sessions.values()) {\n result.push({ conversationId: session.conversationId, status: session.status, sessionType: session.sessionType });\n }\n return result;\n }\n\n kill(conversationId: string): boolean {\n const session = this.sessions.get(conversationId);\n if (!session) return false;\n\n if (session.status === 'running') {\n // Remote agent kill\n if (session.agentId && session.agentSpawnId) {\n agentManager.agentMutation(session.agentId, 'ai.kill', { spawnId: session.agentSpawnId }).catch(() => { /* best-effort */ });\n }\n if (session.proc) {\n try { session.proc.kill(); } catch { /* already dead */ }\n }\n if (session.abortController) {\n session.abortController.abort();\n }\n }\n return true;\n }\n\n // ── Internal helpers ────────────────────────────────────────────────────\n\n private schedulePersist(session: ActiveSession): void {\n if (session.persistTimer) return;\n session.persistTimer = setTimeout(() => {\n session.persistTimer = null;\n this.persistNow(session);\n }, PERSIST_DEBOUNCE_MS);\n }\n\n private persistNow(session: ActiveSession): void {\n if (session.persistTimer) {\n clearTimeout(session.persistTimer);\n session.persistTimer = null;\n }\n\n const fullContent = session.fullContent;\n const parts = session.parts.length > 0 ? [...session.parts] : undefined;\n // Synchronous mutation + async save — avoids the async get()→.then() race where a\n // debounced partial-persist .then() could run after a markDone full-persist .then()\n // and overwrite the complete content with stale partial content.\n conversationStore.updateLastAssistantContent(session.conversationId, fullContent, parts);\n }\n\n private cleanupTimers(session: ActiveSession): void {\n if (session.persistTimer) {\n clearTimeout(session.persistTimer);\n session.persistTimer = null;\n }\n if (session.cleanupTimer) {\n clearTimeout(session.cleanupTimer);\n session.cleanupTimer = null;\n }\n }\n}\n\nexport const activeSessionRegistry = new ActiveSessionRegistry();\n"],"mappings":"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","names":["EventEmitter","item","conv","projectRoot","join","dirname","dirname","join","EventEmitter","readdir","readFile","join","join","readdir","readFile","watch","dirname","watch","dirname","agentManager","bus","EventEmitter"]}
|