@runeya/runeya 2.0.143 → 2.0.145

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.
Files changed (135) hide show
  1. package/agent/index.js +7 -1
  2. package/agent/index.js.map +1 -1
  3. package/agent-manager-THV3LJOF.js +11 -0
  4. package/{chat-X3X527EX.js → chat-XYCHMTFE.js} +9 -9
  5. package/{chunk-7HIPFSOA.js → chunk-57P45FNY.js} +11 -3
  6. package/chunk-57P45FNY.js.map +1 -0
  7. package/{chunk-UR3HL373.js → chunk-FE53XX4M.js} +8 -8
  8. package/{chunk-IXO5KIO5.js → chunk-KS7A5CTW.js} +12 -1
  9. package/{chunk-IXO5KIO5.js.map → chunk-KS7A5CTW.js.map} +1 -1
  10. package/{chunk-XAFOP677.js → chunk-MZSDTA4A.js} +480 -233
  11. package/chunk-MZSDTA4A.js.map +1 -0
  12. package/{chunk-7L6AQUBR.js → chunk-NCO4OOYT.js} +6 -2
  13. package/{chunk-7L6AQUBR.js.map → chunk-NCO4OOYT.js.map} +1 -1
  14. package/{chunk-P73PTVII.js → chunk-ONVAH65J.js} +103 -33
  15. package/chunk-ONVAH65J.js.map +1 -0
  16. package/{chunk-NSS47H26.js → chunk-Q4RPY3VW.js} +330 -74
  17. package/chunk-Q4RPY3VW.js.map +1 -0
  18. package/{chunk-GXE5TLLJ.js → chunk-QF56Z6LI.js} +17 -17
  19. package/{companion-2LHKU577.js → companion-HBYSY2D5.js} +11 -7
  20. package/{desktop-installer-page-6JDSXK2E.js → desktop-installer-page-I5FHFDES.js} +11 -8
  21. package/desktop-installer-page-I5FHFDES.js.map +1 -0
  22. package/{desktop-launcher-YPNRQMIS.js → desktop-launcher-GD4QPM66.js} +3 -3
  23. package/index.js +25 -12
  24. package/index.js.map +1 -1
  25. package/{install-gate-2NNH7RM2.js → install-gate-JAEK4BDZ.js} +2 -2
  26. package/mcp-server/index.js +7 -1
  27. package/mcp-server/index.js.map +1 -1
  28. package/package.json +1 -1
  29. package/settings-manager-QVQRJO6V.js +10 -0
  30. package/src-DCCEUWE6.js +385 -0
  31. package/{src-5TEXF3SU.js → src-IODFUZ7A.js} +100 -59
  32. package/src-IODFUZ7A.js.map +1 -0
  33. package/{vault-resolver-VK6W5J6W.js → vault-resolver-PN3TYCLJ.js} +3 -2
  34. package/vault-resolver-PN3TYCLJ.js.map +1 -0
  35. package/web/assets/{AgentScalarChatInterface.vue-B-SjOsdj.js → AgentScalarChatInterface.vue-npQgDxeP.js} +1 -1
  36. package/web/assets/{AgentTerminalPage-JzC8DUgu.js → AgentTerminalPage-BTH3yCS2.js} +1 -1
  37. package/web/assets/{AiChatModals-D1928AxG.js → AiChatModals-It9jnB26.js} +1 -1
  38. package/web/assets/AppLayout-BTg2CADo.js +1 -0
  39. package/web/assets/{AppLayout-8sBPr3od.css → AppLayout-tsw5Ow0w.css} +1 -1
  40. package/web/assets/{AuditPage-BZIJ3_Kr.js → AuditPage-2PKKGLZs.js} +1 -1
  41. package/web/assets/{CloudAccountPage-DBv470sT.js → CloudAccountPage-CHL61gLe.js} +1 -1
  42. package/web/assets/{CloudMessagingPage-qCI8-UDL.js → CloudMessagingPage-cgKnABPH.js} +1 -1
  43. package/web/assets/DashboardPage-CRntTMKY.js +1 -0
  44. package/web/assets/{DashboardPage-D9-PpC3F.css → DashboardPage-FvNuC_e4.css} +1 -1
  45. package/web/assets/{DirectoryPickerDialog-B5DvDfQP.js → DirectoryPickerDialog-DCyhT3-W.js} +1 -1
  46. package/web/assets/{EnvironmentForm-Bw8Djnqm.js → EnvironmentForm-C96PFWRE.js} +1 -1
  47. package/web/assets/{EnvironmentFormPage-B59uiYtp.js → EnvironmentFormPage-ChorAd63.js} +1 -1
  48. package/web/assets/{EnvironmentListPage-W9zLDZAg.js → EnvironmentListPage-Dw9ziaI8.js} +1 -1
  49. package/web/assets/KanbanBoardPage-BZHbEj-q.js +4 -0
  50. package/web/assets/KanbanBoardPage-eHmsrFGA.css +1 -0
  51. package/web/assets/{KanbanListPage-Bn9k8Rpc.js → KanbanListPage-B0jlfKqs.js} +1 -1
  52. package/web/assets/{LaunchProjectPage-Dc7v1UjQ.js → LaunchProjectPage-BVs1JwbV.js} +1 -1
  53. package/web/assets/LaunchServicesPage-DoLDPqQq.css +1 -0
  54. package/web/assets/LaunchServicesPage-PBYJly9N.js +1 -0
  55. package/web/assets/{LaunchShell-LonTJpb3.js → LaunchShell-CGNYr-do.js} +1 -1
  56. package/web/assets/{LogViewer-DkuqWYkQ.js → LogViewer-BDObx29F.js} +1 -1
  57. package/web/assets/{OrgKeyPrompt-By1VjlSN.js → OrgKeyPrompt-BbcwiwGw.js} +1 -1
  58. package/web/assets/{ProcessControls-C0skzKGL.js → ProcessControls-DY0HTKde.js} +1 -1
  59. package/web/assets/{ProcessDetailPage-BvPX6z_X.js → ProcessDetailPage-BZ9kIQqn.js} +1 -1
  60. package/web/assets/{ProjectCreateDialog-C4o9u15L.js → ProjectCreateDialog-C1wVDPcK.js} +1 -1
  61. package/web/assets/{ProjectLocationGate-BJ1CSQt0.js → ProjectLocationGate-bUd_tFpI.js} +1 -1
  62. package/web/assets/{ProjectPublishDialog-Cp-zXdGm.js → ProjectPublishDialog-APUS1WEN.js} +1 -1
  63. package/web/assets/{RandomJobTestPage-DF4a1U13.js → RandomJobTestPage-BFNC827R.js} +1 -1
  64. package/web/assets/ScenarioEditorPage-DFVsMVu5.js +14 -0
  65. package/web/assets/{SendToChatDialog-Dg2SHkVD.js → SendToChatDialog-QHIkxNio.js} +1 -1
  66. package/web/assets/{ServiceControlsBar-q2VHWaxK.js → ServiceControlsBar-_UQXb6g0.js} +1 -1
  67. package/web/assets/ServiceDetailPage-DGIXgHwR.js +2 -0
  68. package/web/assets/{ServiceFormPage-tT_n7WUy.js → ServiceFormPage-RliAfS3_.js} +1 -1
  69. package/web/assets/SettingsPage-BSSHxxjM.js +2086 -0
  70. package/web/assets/{SettingsPage-aGSxZw1n.css → SettingsPage-W8YszhUL.css} +1 -1
  71. package/web/assets/{UnlockPage-BKAdMJkb.js → UnlockPage-17EgfpJs.js} +1 -1
  72. package/web/assets/{VaultMigrationPage-D9KUTTss.js → VaultMigrationPage-DeMxpXXJ.js} +1 -1
  73. package/web/assets/{VersionBadge-moKP5bAv.js → VersionBadge-DBquJl76.js} +1 -1
  74. package/web/assets/{WikiPage-hLUndDxo.js → WikiPage-GtwpJPqf.js} +1 -1
  75. package/web/assets/{WikiView-rrjXRR72.js → WikiView-BqaqxMxW.js} +1 -1
  76. package/web/assets/{add-scalar-classes-CBfRr8B2.js → add-scalar-classes-DhSSqZ5l.js} +2 -2
  77. package/web/assets/{agent.store-B3dVemmT.js → agent.store-Co44dMZf.js} +1 -1
  78. package/web/assets/app-mode-BVpTuh0i.js +1 -0
  79. package/web/assets/auth-Bk1GP5sB.js +2 -0
  80. package/web/assets/{cloud-chat.store-Wg1poFem.js → cloud-chat.store-CadB14dZ.js} +1 -1
  81. package/web/assets/{cloud-client-DQlUaoAw.js → cloud-client-0zD_NI6q.js} +2 -2
  82. package/web/assets/{cloud-session-DRUqcSGP.js → cloud-session-Cjw54Y--.js} +2 -2
  83. package/web/assets/dist-N4nrnbnA.js +1 -0
  84. package/web/assets/{editor.store-SoH48ux2.js → editor.store-DmM6ZlTm.js} +1 -1
  85. package/web/assets/i18n-8FjeJaKW.js +1 -0
  86. package/web/assets/{index-D_x2s8Ut.js → index-B9DJf2V0.js} +101 -101
  87. package/web/assets/{index-C_JV7fYy.css → index-teBqdnuv.css} +1 -1
  88. package/web/assets/{modal-CpsVK_dA.js → modal-UK8xRSnh.js} +1 -1
  89. package/web/assets/{project-launch.store-C3fZeRGb.js → project-launch.store-B5nhdGdh.js} +1 -1
  90. package/web/assets/{project.store-BEk4fciH.js → project.store-Bm2JWNbd.js} +1 -1
  91. package/web/assets/{scenario.store-D-JrRiVO.js → scenario.store-88w6P6s2.js} +2 -2
  92. package/web/assets/{service.store-B5uYZtyM.js → service.store-XS50iGKo.js} +1 -1
  93. package/web/assets/{settings-scope-Blnwj3rR.js → settings-scope-CKPq86HZ.js} +1 -1
  94. package/web/assets/{settings.store-Bo3FYs1o.js → settings.store-BCJ0LJQA.js} +1 -1
  95. package/web/assets/src-BDZKZr9n.css +1 -0
  96. package/web/assets/{src-DValZNJU.js → src-Cgix2VOi.js} +5 -5
  97. package/web/assets/{src-CAwAaB5j.js → src-DBipSD56.js} +1 -1
  98. package/web/assets/{trpc-DbsE5GvQ.js → trpc-CfuFkKgp.js} +3 -3
  99. package/web/assets/{useCloudOrg-DygUBG1a.js → useCloudOrg-J8DAnocE.js} +1 -1
  100. package/web/assets/{useEnvVarAutocomplete-DmYNOXgd.js → useEnvVarAutocomplete-C6zOXYqi.js} +1 -1
  101. package/web/assets/{useProjectCatalog-D2mHB7dg.js → useProjectCatalog-BB5IR6o8.js} +1 -1
  102. package/web/assets/{useProjectCloudOrg-avTCVold.js → useProjectCloudOrg-BnlKts7x.js} +1 -1
  103. package/web/assets/{useServiceUrlHost-Q3fzGox3.js → useServiceUrlHost-hKbKtPMu.js} +1 -1
  104. package/web/assets/{vue-draggable-plus-WgrMXXmE.js → vue-draggable-plus-D5ni4jMk.js} +1 -1
  105. package/web/index.html +19 -18
  106. package/agent-manager-M4NA5YFD.js +0 -11
  107. package/chunk-7HIPFSOA.js.map +0 -1
  108. package/chunk-NSS47H26.js.map +0 -1
  109. package/chunk-P73PTVII.js.map +0 -1
  110. package/chunk-XAFOP677.js.map +0 -1
  111. package/desktop-installer-page-6JDSXK2E.js.map +0 -1
  112. package/settings-manager-32SVMAFN.js +0 -10
  113. package/src-5TEXF3SU.js.map +0 -1
  114. package/web/assets/AppLayout-CVQIBYqg.js +0 -1
  115. package/web/assets/DashboardPage-D0UZM0mr.js +0 -1
  116. package/web/assets/KanbanBoardPage-DRACMk_D.css +0 -1
  117. package/web/assets/KanbanBoardPage-DwPqoJDJ.js +0 -4
  118. package/web/assets/LaunchServicesPage-CXUS9O3y.js +0 -1
  119. package/web/assets/LaunchServicesPage-DJDIN8HU.css +0 -1
  120. package/web/assets/ScenarioEditorPage-Dl-gEkJI.js +0 -14
  121. package/web/assets/ServiceDetailPage-CJ8WaFod.js +0 -2
  122. package/web/assets/SettingsPage-CWakkv0V.js +0 -2086
  123. package/web/assets/auth-BCrvnP1U.js +0 -2
  124. package/web/assets/dist-DoChtT9t.js +0 -1
  125. package/web/assets/i18n-BszFg0ko.js +0 -1
  126. package/web/assets/src-DuBXN1hu.css +0 -1
  127. /package/{agent-manager-M4NA5YFD.js.map → agent-manager-THV3LJOF.js.map} +0 -0
  128. /package/{chat-X3X527EX.js.map → chat-XYCHMTFE.js.map} +0 -0
  129. /package/{chunk-UR3HL373.js.map → chunk-FE53XX4M.js.map} +0 -0
  130. /package/{chunk-GXE5TLLJ.js.map → chunk-QF56Z6LI.js.map} +0 -0
  131. /package/{companion-2LHKU577.js.map → companion-HBYSY2D5.js.map} +0 -0
  132. /package/{desktop-launcher-YPNRQMIS.js.map → desktop-launcher-GD4QPM66.js.map} +0 -0
  133. /package/{install-gate-2NNH7RM2.js.map → install-gate-JAEK4BDZ.js.map} +0 -0
  134. /package/{settings-manager-32SVMAFN.js.map → settings-manager-QVQRJO6V.js.map} +0 -0
  135. /package/{vault-resolver-VK6W5J6W.js.map → src-DCCEUWE6.js.map} +0 -0
@@ -17,26 +17,21 @@ import {
17
17
  serviceStore,
18
18
  setProjectServices,
19
19
  setProjectVault
20
- } from "./chunk-GXE5TLLJ.js";
20
+ } from "./chunk-QF56Z6LI.js";
21
21
  import {
22
22
  atomicWriteSecure,
23
23
  fingerprintFiles,
24
+ forgetFile,
24
25
  hasChangedSinceRead,
25
26
  rememberFile,
26
27
  wasRead
27
- } from "./chunk-7L6AQUBR.js";
28
+ } from "./chunk-NCO4OOYT.js";
28
29
  import {
29
30
  findThreadDb,
30
31
  lastTurn,
31
32
  threadMessages,
32
33
  threadsStartedAfter
33
34
  } from "./chunk-MD3VW4HQ.js";
34
- import {
35
- COMPANION_RELAY_CLOSE,
36
- COMPANION_RELAY_OPEN,
37
- ProjectSchema,
38
- extractCompanionRelay
39
- } from "./chunk-XAFOP677.js";
40
35
  import {
41
36
  allVaultRoots,
42
37
  collidesWithMachineRoot,
@@ -57,14 +52,20 @@ import {
57
52
  unregisterLocalProjectDir,
58
53
  vaultFor,
59
54
  vaultIdOfRoot
60
- } from "./chunk-IXO5KIO5.js";
55
+ } from "./chunk-KS7A5CTW.js";
56
+ import {
57
+ COMPANION_RELAY_CLOSE,
58
+ COMPANION_RELAY_OPEN,
59
+ ProjectSchema,
60
+ extractCompanionRelay
61
+ } from "./chunk-MZSDTA4A.js";
61
62
 
62
63
  // ../server/src/services/companion.ts
63
64
  import { EventEmitter as EventEmitter4 } from "events";
64
65
 
65
66
  // ../server/src/services/conversation-store.ts
66
67
  import { randomUUID as randomUUID3 } from "crypto";
67
- import { readFile as readFile4, mkdir as mkdir3 } from "fs/promises";
68
+ import { readFile as readFile4, readdir, mkdir as mkdir3, rename as rename3, rm as rm2, stat as stat2 } from "fs/promises";
68
69
  import { dirname as dirname2, join as join4 } from "path";
69
70
  import { EventEmitter as EventEmitter2 } from "events";
70
71
 
@@ -989,6 +990,26 @@ registerProjectLoader(() => projectStore.list().then(() => void 0));
989
990
 
990
991
  // ../server/src/services/conversation-store.ts
991
992
  var FILENAME4 = "conversations.private.json";
993
+ var CONVERSATIONS_DIRNAME = "conversations";
994
+ var FILE_SUFFIX = ".private.json";
995
+ var SAFE_ID_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/;
996
+ function conversationDir(root) {
997
+ return join4(root, CONVERSATIONS_DIRNAME);
998
+ }
999
+ function conversationPath(root, id) {
1000
+ return join4(root, CONVERSATIONS_DIRNAME, `${id}${FILE_SUFFIX}`);
1001
+ }
1002
+ function migratedAggregateName(now) {
1003
+ return `conversations.migrated-${now.toISOString().replace(/[:.]/g, "-").substring(0, 19)}.private.json`;
1004
+ }
1005
+ async function fileStamp(path) {
1006
+ try {
1007
+ const s = await stat2(path);
1008
+ return `${s.mtimeMs}:${s.size}:${s.ino}`;
1009
+ } catch {
1010
+ return null;
1011
+ }
1012
+ }
992
1013
  async function localProjectId() {
993
1014
  const projects = await projectStore.list().catch(() => []);
994
1015
  return projects.find((p) => !p.orgId)?.id ?? null;
@@ -1020,6 +1041,27 @@ var ConversationStore = class {
1020
1041
  fingerprint = "";
1021
1042
  events = new EventEmitter2().setMaxListeners(0);
1022
1043
  disk = new StoreDiskCoordinator();
1044
+ /** Le fichier où chaque conversation est écrite aujourd'hui, s'il existe. */
1045
+ written = /* @__PURE__ */ new Map();
1046
+ /**
1047
+ * Ce qui a été lu de chaque fichier, avec son empreinte.
1048
+ *
1049
+ * Un rechargement ne relit que les fichiers dont l'empreinte a bougé :
1050
+ * déchiffrer toutes les conversations parce qu'une seule a changé dans une
1051
+ * autre fenêtre coûterait autant que l'ancien format.
1052
+ */
1053
+ parsed = /* @__PURE__ */ new Map();
1054
+ /** Les conversations modifiées depuis la dernière écriture. */
1055
+ dirty = /* @__PURE__ */ new Set();
1056
+ /** Les fichiers à retirer : conversations supprimées ou changées de coffre. */
1057
+ removed = /* @__PURE__ */ new Set();
1058
+ /** Les anciens fichiers à convertir, relevés au dernier chargement. */
1059
+ aggregates = [];
1060
+ /** Écrites telles quelles à la conversion : illisibles ici, mais pas perdues. */
1061
+ rawToWrite = /* @__PURE__ */ new Map();
1062
+ /** Plus récentes dans un ancien fichier que dans le leur : à réécrire à la conversion. */
1063
+ outdated = /* @__PURE__ */ new Set();
1064
+ migrating = null;
1023
1065
  snapshotConversation(conv) {
1024
1066
  return structuredClone(conv);
1025
1067
  }
@@ -1099,6 +1141,8 @@ ${new Error("trace").stack}`
1099
1141
  agentId: conv.agentId,
1100
1142
  companionEnabled: conv.companionEnabled,
1101
1143
  companionRunner: conv.companionRunner,
1144
+ companionModel: conv.companionModel,
1145
+ companionEffort: conv.companionEffort,
1102
1146
  companionConversationId: conv.companionConversationId,
1103
1147
  companionBaseSha: conv.companionBaseSha,
1104
1148
  isCompanion: conv.isCompanion,
@@ -1129,6 +1173,8 @@ ${new Error("trace").stack}`
1129
1173
  agentId: disk.agentId,
1130
1174
  companionEnabled: disk.companionEnabled,
1131
1175
  companionRunner: disk.companionRunner,
1176
+ companionModel: disk.companionModel,
1177
+ companionEffort: disk.companionEffort,
1132
1178
  companionConversationId: disk.companionConversationId,
1133
1179
  companionBaseSha: disk.companionBaseSha,
1134
1180
  isCompanion: disk.isCompanion,
@@ -1158,8 +1204,24 @@ ${new Error("trace").stack}`
1158
1204
  ];
1159
1205
  return all.filter((source, i) => !all.slice(i + 1).some((later) => later.path === source.path));
1160
1206
  }
1207
+ /**
1208
+ * Les répertoires où chercher des conversations : ceux des sources, sans
1209
+ * doublon. Le dossier de lancement hérité en fait partie.
1210
+ */
1211
+ async sourceRoots() {
1212
+ return Array.from(new Set((await this.sourcePaths()).map((s) => dirname2(s.path))));
1213
+ }
1214
+ /**
1215
+ * L'état du disque : les anciens fichiers, et les dossiers `conversations/`.
1216
+ *
1217
+ * La date d'un dossier bouge à chaque fichier ajouté, renommé ou retiré, et
1218
+ * toute écriture passe par un renommage (`atomicWriteSecure`). Quelques
1219
+ * `stat` suffisent donc à savoir si une autre instance a écrit.
1220
+ */
1161
1221
  async diskFingerprint() {
1162
- return fingerprintFiles((await this.sourcePaths()).map((s) => s.path));
1222
+ const paths = (await this.sourcePaths()).map((s) => s.path);
1223
+ const dirs = Array.from(new Set(paths.map((path) => conversationDir(dirname2(path)))));
1224
+ return fingerprintFiles([...paths, ...dirs]);
1163
1225
  }
1164
1226
  async load() {
1165
1227
  if (!getEncryptionKey()) {
@@ -1173,25 +1235,105 @@ ${new Error("trace").stack}`
1173
1235
  return () => {
1174
1236
  this.conversations = next.conversations;
1175
1237
  this.roots = next.roots;
1238
+ this.written = next.written;
1239
+ this.parsed = next.parsed;
1176
1240
  this.unreadable = next.unreadable;
1241
+ this.aggregates = next.aggregates;
1242
+ this.rawToWrite = next.rawToWrite;
1243
+ this.outdated = next.outdated;
1177
1244
  this.fingerprint = fingerprint;
1178
1245
  this.loaded = true;
1179
1246
  };
1180
1247
  });
1248
+ if (this.aggregates.length > 0) await this.migrateAggregates();
1249
+ }
1250
+ /**
1251
+ * Passer les anciens fichiers au format « un fichier par conversation ».
1252
+ *
1253
+ * Dans cet ordre, pour ne rien perdre si ça s'arrête en route : chaque
1254
+ * conversation qui n'a pas encore son fichier l'obtient, les illisibles
1255
+ * compris (recopiées telles quelles), puis on vérifie que tous les fichiers
1256
+ * attendus existent. Alors seulement l'ancien fichier est renommé. Il reste
1257
+ * dans le coffre comme copie de secours, sous un nom que ce store ne lit pas.
1258
+ *
1259
+ * Le fichier du dossier de lancement hérité est renommé lui aussi : relu à
1260
+ * chaque démarrage, il ferait revenir les conversations supprimées depuis.
1261
+ */
1262
+ migrateAggregates() {
1263
+ this.migrating ??= (async () => {
1264
+ const aggregates = this.aggregates;
1265
+ const expected = [];
1266
+ let incomplete = false;
1267
+ for (const [id] of this.conversations) {
1268
+ if (!this.written.has(id) || this.outdated.has(id)) this.dirty.add(id);
1269
+ }
1270
+ await this.save();
1271
+ for (const [id, conv] of this.conversations) {
1272
+ const path = this.written.get(id);
1273
+ if (path) {
1274
+ expected.push(path);
1275
+ } else {
1276
+ incomplete = true;
1277
+ console.warn(`[conversation-store] Conversation ${conv.id} non convertie : ancien fichier gard\xE9.`);
1278
+ }
1279
+ }
1280
+ const raws = Array.from(this.rawToWrite.values());
1281
+ for (const { root, item } of raws) {
1282
+ if (!SAFE_ID_RE.test(item.id)) {
1283
+ incomplete = true;
1284
+ continue;
1285
+ }
1286
+ const path = conversationPath(root, item.id);
1287
+ if (!await fileStamp(path)) await atomicWriteSecure(path, JSON.stringify(item));
1288
+ expected.push(path);
1289
+ }
1290
+ const missing = (await Promise.all(expected.map(fileStamp))).some((st) => st === null);
1291
+ this.aggregates = [];
1292
+ if (missing || incomplete) {
1293
+ console.error("[conversation-store] Conversion incompl\xE8te : les anciens fichiers restent en place.");
1294
+ return;
1295
+ }
1296
+ const now = /* @__PURE__ */ new Date();
1297
+ for (const { path } of aggregates) {
1298
+ await rename3(path, join4(dirname2(path), migratedAggregateName(now)));
1299
+ forgetFile(path);
1300
+ console.log(`[conversation-store] ${path} converti en un fichier par conversation.`);
1301
+ }
1302
+ this.rawToWrite = /* @__PURE__ */ new Map();
1303
+ this.outdated = /* @__PURE__ */ new Set();
1304
+ this.fingerprint = await this.diskFingerprint();
1305
+ })().finally(() => {
1306
+ this.migrating = null;
1307
+ });
1308
+ return this.migrating;
1181
1309
  }
1182
1310
  /** Lire toutes les sources dans des tables neuves, que `load` substitue d'un coup. */
1183
1311
  async readSources() {
1184
1312
  const conversations = /* @__PURE__ */ new Map();
1185
1313
  const roots = /* @__PURE__ */ new Map();
1314
+ const written = /* @__PURE__ */ new Map();
1315
+ const parsed = /* @__PURE__ */ new Map();
1186
1316
  const unreadable = /* @__PURE__ */ new Map();
1187
- for (const { path, legacy } of await this.sourcePaths()) {
1317
+ const aggregates = [];
1318
+ const rawToWrite = /* @__PURE__ */ new Map();
1319
+ const outdated = /* @__PURE__ */ new Set();
1320
+ const markUnreadable = (root, item) => {
1321
+ const bucket = unreadable.get(root) ?? [];
1322
+ if (!bucket.some((kept) => kept.id === item.id)) bucket.push(item);
1323
+ unreadable.set(root, bucket);
1324
+ };
1325
+ const sources = await this.sourcePaths();
1326
+ const legacyRoots = new Set(sources.filter((s) => s.legacy).map((s) => dirname2(s.path)));
1327
+ const inheritedProject = legacyRoots.size > 0 ? await localProjectId() : null;
1328
+ for (const { path, legacy } of sources) {
1188
1329
  let data;
1189
1330
  try {
1190
1331
  data = JSON.parse(await readFile4(path, "utf-8"));
1191
1332
  } catch {
1192
1333
  continue;
1193
1334
  }
1194
- const inherited = legacy ? await localProjectId() : null;
1335
+ aggregates.push({ path });
1336
+ const inherited = legacy ? inheritedProject : null;
1195
1337
  const root = dirname2(path);
1196
1338
  await rememberFile(path);
1197
1339
  for (const item of data) {
@@ -1201,13 +1343,62 @@ ${new Error("trace").stack}`
1201
1343
  conversations.set(item.id, conv);
1202
1344
  roots.set(item.id, root);
1203
1345
  } catch {
1204
- const bucket = unreadable.get(root) ?? [];
1205
- if (!bucket.some((kept) => kept.id === item.id)) bucket.push(item);
1206
- unreadable.set(root, bucket);
1346
+ markUnreadable(root, item);
1347
+ if (!rawToWrite.has(item.id)) rawToWrite.set(item.id, { root, item });
1207
1348
  }
1208
1349
  }
1209
1350
  }
1210
- return { conversations, roots, unreadable };
1351
+ for (const root of await this.sourceRoots()) {
1352
+ const dir = conversationDir(root);
1353
+ let names;
1354
+ try {
1355
+ names = (await readdir(dir)).filter((n) => n.endsWith(FILE_SUFFIX));
1356
+ } catch {
1357
+ continue;
1358
+ }
1359
+ for (const name of names) {
1360
+ const path = join4(dir, name);
1361
+ const stamp = await fileStamp(path);
1362
+ if (!stamp) continue;
1363
+ let entry = this.parsed.get(path);
1364
+ if (!entry || entry.stamp !== stamp) {
1365
+ let item2;
1366
+ try {
1367
+ item2 = JSON.parse(await readFile4(path, "utf-8"));
1368
+ } catch {
1369
+ continue;
1370
+ }
1371
+ let conv2 = null;
1372
+ try {
1373
+ conv2 = this.fromDisk(item2, vaultIdOfRoot(root));
1374
+ } catch {
1375
+ conv2 = null;
1376
+ }
1377
+ entry = { stamp, conv: conv2, item: item2 };
1378
+ }
1379
+ await rememberFile(path);
1380
+ parsed.set(path, entry);
1381
+ const { conv, item } = entry;
1382
+ rawToWrite.delete(item.id);
1383
+ if (!conv) {
1384
+ markUnreadable(root, item);
1385
+ continue;
1386
+ }
1387
+ if (!conv.projectId && legacyRoots.has(root) && inheritedProject) conv.projectId = inheritedProject;
1388
+ written.set(conv.id, path);
1389
+ const older = conversations.get(conv.id);
1390
+ if (older && older.updatedAt > conv.updatedAt) {
1391
+ outdated.add(conv.id);
1392
+ continue;
1393
+ }
1394
+ conversations.set(conv.id, conv);
1395
+ roots.set(conv.id, root);
1396
+ }
1397
+ }
1398
+ for (const [id, conv] of conversations) {
1399
+ if (written.has(id)) rawToWrite.delete(conv.id);
1400
+ }
1401
+ return { conversations, roots, written, parsed, unreadable, aggregates, rawToWrite, outdated };
1211
1402
  }
1212
1403
  /**
1213
1404
  * Oublier ce qui a été lu, pour tout relire sous la clé courante.
@@ -1222,6 +1413,12 @@ ${new Error("trace").stack}`
1222
1413
  this.conversations.clear();
1223
1414
  this.roots.clear();
1224
1415
  this.unreadable.clear();
1416
+ this.written.clear();
1417
+ this.parsed.clear();
1418
+ this.dirty.clear();
1419
+ this.aggregates = [];
1420
+ this.rawToWrite.clear();
1421
+ this.outdated.clear();
1225
1422
  this.loaded = false;
1226
1423
  this.events.emit("changed", { type: "reloaded" });
1227
1424
  }
@@ -1239,43 +1436,52 @@ ${new Error("trace").stack}`
1239
1436
  return out;
1240
1437
  }
1241
1438
  /**
1242
- * Write each vault that holds — or held — a conversation.
1439
+ * Écrire les conversations modifiées, chacune dans son fichier.
1440
+ *
1441
+ * Seules celles qui ont bougé depuis la dernière écriture : c'est le but du
1442
+ * format. Une conversation dont le projet a changé de coffre est écrite dans
1443
+ * le nouveau, et son ancien fichier retiré.
1243
1444
  *
1244
- * A vault emptied by this save is still written, as an empty list: a
1245
- * conversation that found its project must not survive in the file it was
1246
- * read from.
1445
+ * Un fichier modifié depuis sa lecture (réparation faite à la main, clé
1446
+ * adoptée) n'est pas réécrit : réémettre notre copie déferait la réparation.
1247
1447
  */
1248
1448
  async saveInternal() {
1249
1449
  if (!getEncryptionKey()) return;
1250
- const byRoot = /* @__PURE__ */ new Map();
1251
- for (const root of this.roots.values()) byRoot.set(root, []);
1252
- for (const conv of this.conversations.values()) {
1450
+ const ids = Array.from(this.dirty);
1451
+ this.dirty.clear();
1452
+ const removed = Array.from(this.removed);
1453
+ this.removed.clear();
1454
+ for (const id of ids) {
1455
+ const conv = this.conversations.get(id);
1456
+ if (!conv) continue;
1457
+ if (!SAFE_ID_RE.test(conv.id)) {
1458
+ console.error(`[conversation-store] Id de conversation refus\xE9 comme nom de fichier : ${JSON.stringify(conv.id)}`);
1459
+ continue;
1460
+ }
1253
1461
  const root = await this.rootFor(conv);
1254
1462
  this.roots.set(conv.id, root);
1255
- const bucket = byRoot.get(root) ?? [];
1256
1463
  const vault = vaultIdOfRoot(root);
1257
1464
  if (!this.isVaultNamed(root, vault)) continue;
1258
- bucket.push(this.toDisk(conv, vault));
1259
- byRoot.set(root, bucket);
1260
- }
1261
- for (const [root, items] of this.unreadable) {
1262
- byRoot.set(root, [...items, ...byRoot.get(root) ?? []]);
1263
- }
1264
- const stale = /* @__PURE__ */ new Set();
1265
- for (const root of byRoot.keys()) {
1266
- const path = join4(root, FILENAME4);
1267
- if (wasRead(path) && await hasChangedSinceRead(path)) stale.add(root);
1268
- }
1269
- for (const [root, data] of byRoot) {
1270
- const filePath = join4(root, FILENAME4);
1271
- if (stale.has(root)) {
1465
+ const path = conversationPath(root, conv.id);
1466
+ if (wasRead(path) && await hasChangedSinceRead(path)) {
1272
1467
  console.warn(
1273
- `[conversation-store] ${filePath} a chang\xE9 depuis sa lecture \u2014 \xE9criture ignor\xE9e pour ne pas \xE9craser une r\xE9paration faite en dehors du serveur.`
1468
+ `[conversation-store] ${path} a chang\xE9 depuis sa lecture \u2014 \xE9criture ignor\xE9e pour ne pas \xE9craser une r\xE9paration faite en dehors du serveur.`
1274
1469
  );
1275
1470
  continue;
1276
1471
  }
1277
- await atomicWriteSecure(filePath, JSON.stringify(data, null, 2));
1278
- await rememberFile(filePath);
1472
+ const item = this.toDisk(conv, vault);
1473
+ await atomicWriteSecure(path, JSON.stringify(item));
1474
+ await rememberFile(path);
1475
+ const stamp = await fileStamp(path);
1476
+ if (stamp) this.parsed.set(path, { stamp, conv, item });
1477
+ const previous = this.written.get(conv.id);
1478
+ this.written.set(conv.id, path);
1479
+ if (previous && previous !== path) removed.push(previous);
1480
+ }
1481
+ for (const path of removed) {
1482
+ await rm2(path, { force: true });
1483
+ forgetFile(path);
1484
+ this.parsed.delete(path);
1279
1485
  }
1280
1486
  this.fingerprint = await this.diskFingerprint();
1281
1487
  }
@@ -1311,9 +1517,11 @@ ${new Error("trace").stack}`
1311
1517
  */
1312
1518
  async relocateAll() {
1313
1519
  await this.load();
1314
- await this.save();
1520
+ await this.save(...this.conversations.keys());
1315
1521
  }
1316
- save() {
1522
+ /** Écrire ces conversations — et celles qui attendaient déjà. */
1523
+ save(...ids) {
1524
+ for (const id of ids) this.dirty.add(id);
1317
1525
  return this.disk.save(() => this.saveInternal());
1318
1526
  }
1319
1527
  async list() {
@@ -1376,6 +1584,8 @@ ${new Error("trace").stack}`
1376
1584
  isCompanion: extra?.isCompanion,
1377
1585
  companionEnabled: extra?.companionEnabled,
1378
1586
  companionRunner: extra?.companionRunner,
1587
+ companionModel: extra?.companionModel,
1588
+ companionEffort: extra?.companionEffort,
1379
1589
  createdAt: now,
1380
1590
  updatedAt: now
1381
1591
  };
@@ -1383,7 +1593,7 @@ ${new Error("trace").stack}`
1383
1593
  if (projectId && orgId !== void 0) {
1384
1594
  this.roots.set(conv.id, vaultFor({ id: projectId, orgId }).root);
1385
1595
  }
1386
- await this.save();
1596
+ await this.save(conv.id);
1387
1597
  this.events.emit("changed", { type: "created", conversation: this.snapshotConversation(conv) });
1388
1598
  return conv;
1389
1599
  }
@@ -1399,10 +1609,12 @@ ${new Error("trace").stack}`
1399
1609
  if (patch.codexThreadId !== void 0) conv.codexThreadId = patch.codexThreadId;
1400
1610
  if (patch.companionEnabled !== void 0) conv.companionEnabled = patch.companionEnabled;
1401
1611
  if (patch.companionRunner !== void 0) conv.companionRunner = patch.companionRunner;
1612
+ if (patch.companionModel !== void 0) conv.companionModel = patch.companionModel || void 0;
1613
+ if (patch.companionEffort !== void 0) conv.companionEffort = patch.companionEffort || void 0;
1402
1614
  if (patch.companionConversationId !== void 0) conv.companionConversationId = patch.companionConversationId;
1403
1615
  if (patch.companionBaseSha !== void 0) conv.companionBaseSha = patch.companionBaseSha;
1404
1616
  if (options?.touch !== false) conv.updatedAt = Date.now();
1405
- await this.save();
1617
+ await this.save(conv.id);
1406
1618
  if (!options?.silent) {
1407
1619
  this.events.emit("changed", { type: "updated", conversation: this.snapshotConversation(conv), messagesChanged: patch.messages !== void 0 });
1408
1620
  }
@@ -1420,7 +1632,7 @@ ${new Error("trace").stack}`
1420
1632
  lastMsg.content = content;
1421
1633
  if (parts !== void 0) lastMsg.parts = parts;
1422
1634
  conv.updatedAt = Date.now();
1423
- this.save().catch(() => {
1635
+ this.save(conv.id).catch(() => {
1424
1636
  });
1425
1637
  }
1426
1638
  async listByKanbanBoardId(boardId) {
@@ -1452,7 +1664,7 @@ ${new Error("trace").stack}`
1452
1664
  const ranks = Array.from(this.conversations.values()).map((c) => c.pinOrder).filter((r) => r !== void 0);
1453
1665
  conv.pinOrder = ranks.length > 0 ? Math.max(...ranks) + 1 : 0;
1454
1666
  }
1455
- await this.save();
1667
+ await this.save(conv.id);
1456
1668
  this.events.emit("changed", { type: "updated", conversation: this.snapshotConversation(conv) });
1457
1669
  return conv;
1458
1670
  }
@@ -1473,7 +1685,7 @@ ${new Error("trace").stack}`
1473
1685
  targets.forEach((conv, i) => {
1474
1686
  conv.pinOrder = ranks[i];
1475
1687
  });
1476
- await this.save();
1688
+ await this.save(...targets.map((c) => c.id));
1477
1689
  for (const conv of targets) {
1478
1690
  this.events.emit("changed", { type: "updated", conversation: this.snapshotConversation(conv) });
1479
1691
  }
@@ -1500,7 +1712,7 @@ ${new Error("trace").stack}`
1500
1712
  delete conv.pinOrder;
1501
1713
  }
1502
1714
  });
1503
- await this.save();
1715
+ await this.save(...targets.map((c) => c.id));
1504
1716
  for (const conv of targets) {
1505
1717
  this.events.emit("changed", { type: "updated", conversation: this.snapshotConversation(conv) });
1506
1718
  }
@@ -1515,6 +1727,10 @@ ${new Error("trace").stack}`
1515
1727
  async delete(id) {
1516
1728
  await this.load();
1517
1729
  const existed = this.conversations.delete(id);
1730
+ this.dirty.delete(id);
1731
+ const path = this.written.get(id);
1732
+ this.written.delete(id);
1733
+ if (path) this.removed.add(path);
1518
1734
  await this.save();
1519
1735
  if (existed) {
1520
1736
  this.events.emit("changed", { type: "deleted", id });
@@ -1607,7 +1823,7 @@ function onTurnFinished(listener) {
1607
1823
  }
1608
1824
 
1609
1825
  // ../server/src/services/claude-transcript-sync.ts
1610
- import { readdir, access, readFile as readFile5 } from "fs/promises";
1826
+ import { readdir as readdir2, access, readFile as readFile5 } from "fs/promises";
1611
1827
  import { watch } from "fs";
1612
1828
  import { homedir } from "os";
1613
1829
  import { join as join6 } from "path";
@@ -1616,7 +1832,7 @@ var DEFAULT_TITLES = /* @__PURE__ */ new Set(["", "New conversation", "Nouvelle
1616
1832
  async function findClaudeSessionFile(sessionId) {
1617
1833
  let entries;
1618
1834
  try {
1619
- entries = await readdir(PROJECTS_DIR);
1835
+ entries = await readdir2(PROJECTS_DIR);
1620
1836
  } catch {
1621
1837
  return null;
1622
1838
  }
@@ -1797,7 +2013,7 @@ async function startClaudeTranscriptWatcher() {
1797
2013
  started = true;
1798
2014
  let topLevel;
1799
2015
  try {
1800
- topLevel = await readdir(PROJECTS_DIR);
2016
+ topLevel = await readdir2(PROJECTS_DIR);
1801
2017
  } catch {
1802
2018
  return;
1803
2019
  }
@@ -2256,18 +2472,45 @@ function formatExchange(messages, from) {
2256
2472
  }
2257
2473
  function fullInstructions() {
2258
2474
  return [
2259
- "Tu relis le travail d'un autre agent, dans la m\xEAme conversation. Tu ne modifies",
2260
- "rien : ni le code, ni la carte kanban, que tu peux lire mais pas d\xE9placer ni",
2261
- "commenter. Tu ne d\xE9l\xE8gues pas \xE0 un sous-agent.",
2475
+ "Tu es le compagnon de relecture de cette conversation. Un autre agent y",
2476
+ "travaille pour une personne qui suit l'\xE9change. Ton r\xF4le : v\xE9rifier que ce",
2477
+ "qu'il livre est correct et complet par rapport \xE0 ce qui a \xE9t\xE9 demand\xE9, et le",
2478
+ "lui signaler quand ce n'est pas le cas, avant que la personne ne le d\xE9couvre.",
2479
+ "",
2480
+ "Ce que tu \xE9cris entre les bornes arrive dans sa conversation, pr\xE9fix\xE9 par",
2481
+ `${COMPANION_SIGNATURE}, et le relance. Un d\xE9faut que tu laisses passer co\xFBte plus cher`,
2482
+ "qu'une remarque en trop : l'agent v\xE9rifie et \xE9carte en une phrase ce qui est",
2483
+ "faux. Dans le doute, signale-le en disant que tu n'en es pas s\xFBr.",
2484
+ "",
2485
+ "Tu ne modifies rien : ni le code, ni la carte kanban, que tu peux lire mais",
2486
+ "pas d\xE9placer ni commenter. Tu ne d\xE9l\xE8gues pas \xE0 un sous-agent. Si une carte",
2487
+ "kanban est fournie, c'est la demande : v\xE9rifie que le travail la couvre.",
2262
2488
  "",
2263
2489
  "Images : l'\xE9change ne porte que leur chemin, [Image: source: /chemin.png].",
2264
2490
  "Ouvre-le avec ton outil de lecture. Si la lecture \xE9choue, dis-le. N'invente",
2265
2491
  "jamais le contenu d'une image.",
2266
2492
  "",
2493
+ "CE QUE TU CHERCHES",
2494
+ "- La demande : le travail couvre-t-il tout ce qui a \xE9t\xE9 demand\xE9 (message de",
2495
+ " la personne, carte kanban) ? Rien d'oubli\xE9, rien d'ajout\xE9 hors sujet ?",
2496
+ "- Les r\xE8gles du projet : lis CLAUDE.md, AGENTS.md ou leur \xE9quivalent s'ils",
2497
+ " existent, et v\xE9rifie que le travail les respecte.",
2498
+ "- La justesse : cas limites, valeurs vides, erreurs non trait\xE9es, chemins que",
2499
+ " le code rend atteignables sans les g\xE9rer.",
2500
+ "- Les r\xE9gressions et effets de bord : les appelants du code modifi\xE9, les",
2501
+ " comportements qui changent sans que la demande le pr\xE9voie.",
2502
+ "- La duplication : une fonction, un composant ou une constante qui existait",
2503
+ " d\xE9j\xE0 et qu'on a r\xE9\xE9crit \xE0 c\xF4t\xE9.",
2504
+ "- La s\xE9curit\xE9 : entr\xE9es non valid\xE9es, secrets expos\xE9s, contr\xF4les d'acc\xE8s",
2505
+ " retir\xE9s ou absents.",
2506
+ "- Les tests : un test ajout\xE9 v\xE9rifie-t-il vraiment le comportement ? Un test",
2507
+ " affaibli, d\xE9sactiv\xE9 ou supprim\xE9 pour passer au vert ?",
2508
+ "- Les affirmations : \xAB les tests passent \xBB, \xAB c'est v\xE9rifi\xE9 \xBB, \xAB rien d'autre",
2509
+ " n'utilise ce code \xBB. Contr\xF4le-les dans le code ou la sortie montr\xE9e.",
2510
+ "",
2267
2511
  "QUAND PARLER",
2268
2512
  "- Si l'\xE9change te parle ou te pose une question, r\xE9ponds toujours.",
2269
- "- Sinon, parle seulement si tu as trouv\xE9 un d\xE9faut : r\xE9gression, effet de bord,",
2270
- " duplication de code existant, faille de s\xE9curit\xE9, affirmation non v\xE9rifi\xE9e.",
2513
+ "- Sinon, parle si tu as trouv\xE9 un d\xE9faut ou si tu en soup\xE7onnes un.",
2271
2514
  "- Pas d'accus\xE9 de r\xE9ception, de remerciement ni de \xAB rien \xE0 signaler \xBB : chacun",
2272
2515
  " relance l'agent. Quand il r\xE9pond \xE0 ta remarque, tais-toi, sauf s'il pose une",
2273
2516
  " question ou si sa r\xE9ponse est fausse.",
@@ -2284,9 +2527,7 @@ function fullInstructions() {
2284
2527
  " pas pu v\xE9rifier.",
2285
2528
  "- Pas de tests complets, build, typecheck global ni serveur : s'il en faut, dis",
2286
2529
  " \xE0 l'agent quoi lancer.",
2287
- "- Ne resignale pas ce qui a d\xE9j\xE0 \xE9t\xE9 signal\xE9, sauf si ce n'est pas trait\xE9.",
2288
- "",
2289
- "Ces consignes valent pour toute la session : elles ne te seront pas r\xE9p\xE9t\xE9es."
2530
+ "- Ne resignale pas ce qui a d\xE9j\xE0 \xE9t\xE9 signal\xE9, sauf si ce n'est pas trait\xE9."
2290
2531
  ].join("\n");
2291
2532
  }
2292
2533
  function responseProtocol() {
@@ -2308,7 +2549,7 @@ function responseProtocol() {
2308
2549
  }
2309
2550
  function reminder() {
2310
2551
  return [
2311
- "Tour suivant \xE0 relire. M\xEAmes r\xE8gles.",
2552
+ "Tour \xE0 relire. Tes consignes sont dans ton contexte syst\xE8me.",
2312
2553
  `Rien \xE0 dire \u2192 ${VERDICT_NOTHING}. Sinon, ton message entre ${RELAY_OPEN} et ${RELAY_CLOSE} \u2014 sans bornes, rien ne part.`,
2313
2554
  "Si l'\xE9change t'adresse quelque chose, tu r\xE9ponds \u2014 jamais " + VERDICT_NOTHING + " dans ce cas.",
2314
2555
  "Mais l'agent qui r\xE9pond \xE0 ta remarque ne te pose pas de question : sans d\xE9faut ni",
@@ -2321,9 +2562,11 @@ function companionDefaultPrompt() {
2321
2562
  function baseLine(baseSha) {
2322
2563
  return `Point de d\xE9part : le travail de la conversation, commits compris, est dans \`git diff ${baseSha}\`.`;
2323
2564
  }
2324
- function buildCompanionPrompt(exchange, isFirstTurn = true, custom, baseSha) {
2565
+ function companionInstructions(custom) {
2325
2566
  const customInstructions = custom?.trim();
2326
- const instructions = isFirstTurn ? [
2567
+ return [
2568
+ "## Tes consignes de compagnon de relecture",
2569
+ "",
2327
2570
  fullInstructions(),
2328
2571
  ...customInstructions ? [
2329
2572
  "",
@@ -2334,9 +2577,11 @@ function buildCompanionPrompt(exchange, isFirstTurn = true, custom, baseSha) {
2334
2577
  // Le protocole, lui, s'ajoute TOUJOURS : c'est par lui que Runeya lit le
2335
2578
  // verdict. Remplaçable, il rendrait le compagnon illisible.
2336
2579
  responseProtocol()
2337
- ].join("\n") : reminder();
2580
+ ].join("\n");
2581
+ }
2582
+ function buildCompanionPrompt(exchange, baseSha) {
2338
2583
  return [
2339
- instructions,
2584
+ reminder(),
2340
2585
  ...baseSha ? ["", baseLine(baseSha)] : [],
2341
2586
  "",
2342
2587
  "--- Ce que tu n'as pas encore relu de la conversation ---",
@@ -2391,13 +2636,11 @@ async function runCompanionForTurn(conversationId) {
2391
2636
  if (!hasNew) {
2392
2637
  return skip(conversationId, "nothingNew", `rien de neuf \xE0 relire (${messageCount} messages)`);
2393
2638
  }
2394
- const { startCliSession } = await import("./chat-X3X527EX.js");
2639
+ const { startCliSession } = await import("./chat-XYCHMTFE.js");
2395
2640
  const companionConv = await ensureCompanionConversation(conv);
2396
- const isFirstTurn = (companionConv.messages?.length ?? 0) === 0;
2397
2641
  const exchange = formatExchange(conv.messages, from);
2398
- const { prompt: customPrompt } = await companionSettings();
2399
2642
  const baseSha = await ensureCompanionBase(conv);
2400
- const prompt = buildCompanionPrompt(exchange, isFirstTurn, customPrompt, baseSha);
2643
+ const prompt = buildCompanionPrompt(exchange, baseSha);
2401
2644
  const jwt = await signJwt();
2402
2645
  const environmentId = await activeEnvironmentOf(conv);
2403
2646
  const agentId = companionAgentId(
@@ -2412,7 +2655,7 @@ async function runCompanionForTurn(conversationId) {
2412
2655
  conv.companionRunner ?? conv.runner,
2413
2656
  companionConv.id,
2414
2657
  prompt,
2415
- { jwt, environmentId, agentId }
2658
+ { jwt, environmentId, agentId, model: conv.companionModel, effort: conv.companionEffort }
2416
2659
  );
2417
2660
  await done;
2418
2661
  } catch (err) {
@@ -2512,7 +2755,7 @@ async function deliverToMainConversation(conv, message) {
2512
2755
  async function attemptDelivery(conv, message) {
2513
2756
  if (await writeToLivePty(conv.id, message)) return "pty";
2514
2757
  try {
2515
- const { startCliSession } = await import("./chat-X3X527EX.js");
2758
+ const { startCliSession } = await import("./chat-XYCHMTFE.js");
2516
2759
  await startCliSession(conv.runner, conv.id, message, { jwt: await signJwt() });
2517
2760
  return "headless";
2518
2761
  } catch (err) {
@@ -2533,9 +2776,19 @@ async function writeToLivePty(conversationId, message) {
2533
2776
  return false;
2534
2777
  }
2535
2778
  }
2779
+ function companionCreateDefaults(settings, convRunner) {
2780
+ const modelApplies = !!settings.runner || convRunner !== "codex";
2781
+ return {
2782
+ ...settings.enabledByDefault ? { companionEnabled: true } : {},
2783
+ ...settings.runner ? { companionRunner: settings.runner } : {},
2784
+ ...settings.model && modelApplies ? { companionModel: settings.model } : {},
2785
+ // Les deux runners comprennent les mêmes niveaux : l'effort vaut pour tous.
2786
+ ...settings.effort ? { companionEffort: settings.effort } : {}
2787
+ };
2788
+ }
2536
2789
  async function companionSettings() {
2537
2790
  try {
2538
- const { settingsManager } = await import("./settings-manager-32SVMAFN.js");
2791
+ const { settingsManager } = await import("./settings-manager-QVQRJO6V.js");
2539
2792
  const settings = await settingsManager.getSettings();
2540
2793
  return settings.companion ?? { enabledByDefault: false };
2541
2794
  } catch {
@@ -2687,6 +2940,7 @@ export {
2687
2940
  projectPathStore,
2688
2941
  LocalProjectRefusedError,
2689
2942
  projectStore,
2943
+ CONVERSATIONS_DIRNAME,
2690
2944
  conversationStore,
2691
2945
  resolveWorkspaceCwd,
2692
2946
  resolveAgentCwd,
@@ -2715,6 +2969,7 @@ export {
2715
2969
  reviewWindow,
2716
2970
  formatExchange,
2717
2971
  companionDefaultPrompt,
2972
+ companionInstructions,
2718
2973
  buildCompanionPrompt,
2719
2974
  COMPANION_SIGNATURE,
2720
2975
  buildRelaunchMessage,
@@ -2730,6 +2985,7 @@ export {
2730
2985
  lastCompanionDelivery,
2731
2986
  forgetDelivery,
2732
2987
  deliverToMainConversation,
2988
+ companionCreateDefaults,
2733
2989
  companionSettings,
2734
2990
  answerOfTurn,
2735
2991
  runCompanionNow,
@@ -2737,4 +2993,4 @@ export {
2737
2993
  headCommit,
2738
2994
  initCompanion
2739
2995
  };
2740
- //# sourceMappingURL=chunk-NSS47H26.js.map
2996
+ //# sourceMappingURL=chunk-Q4RPY3VW.js.map