@piecemaker-legal/piecemaker 2.0.9 → 2.0.10

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 (165) hide show
  1. package/dist/assets/FindReplaceDialog-KU5TEIBJ-CQ76sPaJ.js +1 -0
  2. package/dist/assets/FootnotePropertiesDialog-W3YHM4BV-BpgYQvvd.js +1 -0
  3. package/dist/assets/HyperlinkDialog-ZYH7ZHRF-qIPBOssF.css +1 -0
  4. package/dist/assets/HyperlinkDialog-ZYH7ZHRF-z08UbYbL.js +144 -0
  5. package/dist/assets/ImagePositionDialog-HEWYR6Q3-CFzOnzAt.js +1 -0
  6. package/dist/assets/ImagePropertiesDialog-RAB47C6M-D-ThGU-b.js +1 -0
  7. package/dist/assets/PageSetupDialog-AIGIXO3E-_PYIO8eb.js +1 -0
  8. package/dist/assets/SplitCellDialog-IFBKYCD6-Cb9uQOB8.js +1 -0
  9. package/dist/assets/TablePropertiesDialog-TMLKGOOW-12vmjb6B.js +1 -0
  10. package/dist/assets/WatermarkDialog-KRL7WMIS-C-d8rzt2.js +1 -0
  11. package/dist/assets/{abnfDiagram-VCTEODGH-WrsSAHOe.js → abnfDiagram-VCTEODGH-B4OsX7eF.js} +1 -1
  12. package/dist/assets/arc-BK1oUIk7.js +1 -0
  13. package/dist/assets/architectureDiagram-5GKGNRK7-CfaFI6L7.js +36 -0
  14. package/dist/assets/{blockDiagram-I7D4REHJ-zGMnF7Ox.js → blockDiagram-I7D4REHJ-DQEI2azY.js} +1 -1
  15. package/dist/assets/{c4Diagram-7LVT6UL2-ClWw3bqt.js → c4Diagram-7LVT6UL2-DbravpVi.js} +1 -1
  16. package/dist/assets/channel-DVyNr2Yg.js +1 -0
  17. package/dist/assets/{chunk-2Q5K7J3B-C-9ICPHv.js → chunk-2Q5K7J3B-BLpZ9huC.js} +1 -1
  18. package/dist/assets/{chunk-5VM5RSS4-Bawy3teT.js → chunk-5VM5RSS4-BLlEdmX9.js} +1 -1
  19. package/dist/assets/{chunk-F27PBJKO-DQ375RQO.js → chunk-F27PBJKO-D-ZNhI6q.js} +1 -1
  20. package/dist/assets/{chunk-IMKFNOWR-CflNF2f6.js → chunk-IMKFNOWR-vzSiKnhh.js} +1 -1
  21. package/dist/assets/{chunk-JWPE2WC7-CIXQv0gc.js → chunk-JWPE2WC7-DfF0k4nb.js} +1 -1
  22. package/dist/assets/{chunk-POPQ4Y6H-Cza5j6Qw.js → chunk-POPQ4Y6H-BnwCYsPP.js} +1 -1
  23. package/dist/assets/{chunk-SVP7TREG-GQ_VvWZR.js → chunk-SVP7TREG-DRxc8fhq.js} +1 -1
  24. package/dist/assets/{chunk-TICWLB2K-BEmU16W5.js → chunk-TICWLB2K-Dd2Q-md-.js} +1 -1
  25. package/dist/assets/{chunk-XXDRQBXY-C1KpkLYH.js → chunk-XXDRQBXY-DBjkEUp_.js} +1 -1
  26. package/dist/assets/classDiagram-ZZMXUADV-2pGPHl5H.js +1 -0
  27. package/dist/assets/classDiagram-v2-VYDZK3BY-2pGPHl5H.js +1 -0
  28. package/dist/assets/{cose-bilkent-JH36ORCC-CZE48TRG.js → cose-bilkent-JH36ORCC-By6PS4oe.js} +1 -1
  29. package/dist/assets/{cynefin-OW5HDTMX-L-tx1eC-.js → cynefin-OW5HDTMX-YxWpjc5W.js} +1 -1
  30. package/dist/assets/{cynefinDiagram-5FMLGOSQ-Coa8oJwR.js → cynefinDiagram-5FMLGOSQ-DrtXzz3p.js} +1 -1
  31. package/dist/assets/{dagre-GXQ25YYZ-Bu_3Y75K.js → dagre-GXQ25YYZ-BDpTLKqZ.js} +1 -1
  32. package/dist/assets/{diagram-S7CK7UJ4-CFiiV4es.js → diagram-S7CK7UJ4-C4LvqeCs.js} +1 -1
  33. package/dist/assets/{diagram-UQ7AKVKN-CCXheGAA.js → diagram-UQ7AKVKN-BTpnMeHp.js} +1 -1
  34. package/dist/assets/{diagram-VSXAHHWV-B70m4lr_.js → diagram-VSXAHHWV-DucHHWev.js} +1 -1
  35. package/dist/assets/{diagram-VX7I27RA-CHkiMOVA.js → diagram-VX7I27RA-DYHZNmmH.js} +1 -1
  36. package/dist/assets/{diagram-Z3DM3KII-_awpc4Bs.js → diagram-Z3DM3KII-BHIH2BGN.js} +1 -1
  37. package/dist/assets/{ebnfDiagram-PWID7BFC-CZzjH86H.js → ebnfDiagram-PWID7BFC-CGiEkwj0.js} +1 -1
  38. package/dist/assets/{erDiagram-RLTQ6QDP-9DgiHZg9.js → erDiagram-RLTQ6QDP-AJMHi2Us.js} +1 -1
  39. package/dist/assets/{flowDiagram-HODETNUW-DHY_503b.js → flowDiagram-HODETNUW-BqzANBh2.js} +1 -1
  40. package/dist/assets/{ganttDiagram-EL5Y4UJY-DPiiYZ_l.js → ganttDiagram-EL5Y4UJY-B0xPDY2_.js} +1 -1
  41. package/dist/assets/{gitGraphDiagram-WWUBYQGX-Ddd_evm7.js → gitGraphDiagram-WWUBYQGX-CzPX5CNO.js} +1 -1
  42. package/dist/assets/{index-tibHq7Qp.js → index-Bk2iXKnJ.js} +140 -140
  43. package/dist/assets/index-Dc9wNuS4.css +1 -0
  44. package/dist/assets/{infoDiagram-27XIBGKW-BPRNhfwb.js → infoDiagram-27XIBGKW-Cjc44H0O.js} +1 -1
  45. package/dist/assets/{ishikawaDiagram-5VMMS53U-BXx6aqkq.js → ishikawaDiagram-5VMMS53U-BWhr2v7r.js} +1 -1
  46. package/dist/assets/{journeyDiagram-3NMN7TZE-BK8TZM29.js → journeyDiagram-3NMN7TZE-Bl4r6RrP.js} +1 -1
  47. package/dist/assets/{kanban-definition-UXKFOSKX-CHioRmxz.js → kanban-definition-UXKFOSKX-FV-T0Vy1.js} +6 -6
  48. package/dist/assets/layout-CTfb1sAF.js +1 -0
  49. package/dist/assets/{linear-CTEhUcKG.js → linear-Dfgi6vlo.js} +1 -1
  50. package/dist/assets/{mermaid.core-DYUb_UUB.js → mermaid.core-DOCGsdlo.js} +7 -7
  51. package/dist/assets/{mindmap-definition-YA3MSWOX-D7ODZ4oS.js → mindmap-definition-YA3MSWOX-CymQ86ir.js} +1 -1
  52. package/dist/assets/{pegDiagram-XKGWAZYB-8J-_2_kw.js → pegDiagram-XKGWAZYB-1VMq_vi0.js} +1 -1
  53. package/dist/assets/{pieDiagram-E7YTZNPT-ebg0yfMi.js → pieDiagram-E7YTZNPT-C5GgXtJU.js} +1 -1
  54. package/dist/assets/processTemplate-GPFDLQMX-B1sVV2O0.js +22 -0
  55. package/dist/assets/{quadrantDiagram-AXDQQJYC-BoWkDiFb.js → quadrantDiagram-AXDQQJYC-C6zpryYf.js} +1 -1
  56. package/dist/assets/{railroadDiagram-O6MQD6OU-BRJ77ldz.js → railroadDiagram-O6MQD6OU-D-sv4SWi.js} +1 -1
  57. package/dist/assets/{requirementDiagram-BXWQKSXE-Z0JJgktA.js → requirementDiagram-BXWQKSXE-CkjY-z4B.js} +1 -1
  58. package/dist/assets/sankeyDiagram-P5KCCOFB-De-d8Q6L.js +40 -0
  59. package/dist/assets/{sequenceDiagram-WJ2MYXX4-BPAjtXwp.js → sequenceDiagram-WJ2MYXX4-DCsGSJa9.js} +1 -1
  60. package/dist/assets/{server.browser-BzI1jrZw.js → server.browser-BY3bL-IX.js} +1 -1
  61. package/dist/assets/{sizeCapture-INFHLROL-BGZPCzM4.js → sizeCapture-INFHLROL-xINZhgER.js} +1 -1
  62. package/dist/assets/stateDiagram-D77RDMKH-CuLUTWSL.js +1 -0
  63. package/dist/assets/stateDiagram-v2-MP3YSRHH-Cnbs-M92.js +1 -0
  64. package/dist/assets/{swimlanes-42K2YHIH-DvuB15qF.js → swimlanes-42K2YHIH-BwxARD7N.js} +1 -1
  65. package/dist/assets/swimlanesDiagram-VR7AAH4N-D7p8qZI2.js +8 -0
  66. package/dist/assets/{timeline-definition-24CTP7MA-ZIziYCIF.js → timeline-definition-24CTP7MA-BIOvnn-l.js} +3 -3
  67. package/dist/assets/{vendor-codemirror-CjW0lHR5.js → vendor-codemirror-CN-sJePf.js} +2 -2
  68. package/dist/assets/{vendor-react-D809iwXR.js → vendor-react-B22wqN3J.js} +5 -5
  69. package/dist/assets/{vennDiagram-4TSXK5OY-C5_B9gWa.js → vennDiagram-4TSXK5OY-BSD6Pfmt.js} +4 -4
  70. package/dist/assets/{wardleyDiagram-VM6X3IG4-DMlH9Afw.js → wardleyDiagram-VM6X3IG4-7Zz2iRkF.js} +1 -1
  71. package/dist/assets/{xychartDiagram-S5SC5T6Z-BldhJt7i.js → xychartDiagram-S5SC5T6Z-Ctx17Y1D.js} +1 -1
  72. package/dist/index.html +4 -4
  73. package/dist/sw.js +1 -1
  74. package/dist-server/server/modules/providers/list/mistral/mistral-models.provider.js +4 -53
  75. package/dist-server/server/modules/providers/list/mistral/mistral-models.provider.js.map +1 -1
  76. package/dist-server/server/modules/providers/list/mistral/mistral-runtime.provider.js +2 -3
  77. package/dist-server/server/modules/providers/list/mistral/mistral-runtime.provider.js.map +1 -1
  78. package/dist-server/server/modules/providers/list/mistral/mistral-vibe-models.js +30 -0
  79. package/dist-server/server/modules/providers/list/mistral/mistral-vibe-models.js.map +1 -0
  80. package/dist-server/server/modules/providers/tests/mistral-vibe-models.test.js +24 -0
  81. package/dist-server/server/modules/providers/tests/mistral-vibe-models.test.js.map +1 -0
  82. package/dist-server/server/piecemaker/anonymizer/lifecycle.js +2 -3
  83. package/dist-server/server/piecemaker/anonymizer/lifecycle.js.map +1 -1
  84. package/dist-server/server/piecemaker/docx-document.js +50 -0
  85. package/dist-server/server/piecemaker/docx-document.js.map +1 -0
  86. package/dist-server/server/piecemaker/index.js +9 -2
  87. package/dist-server/server/piecemaker/index.js.map +1 -1
  88. package/dist-server/server/piecemaker/model-discovery/codex-catalog.js +46 -0
  89. package/dist-server/server/piecemaker/model-discovery/codex-catalog.js.map +1 -0
  90. package/dist-server/server/piecemaker/model-discovery/index.js +42 -0
  91. package/dist-server/server/piecemaker/model-discovery/index.js.map +1 -0
  92. package/dist-server/server/piecemaker/model-discovery/mistral-catalog.js +19 -0
  93. package/dist-server/server/piecemaker/model-discovery/mistral-catalog.js.map +1 -0
  94. package/dist-server/server/piecemaker/model-discovery/stdio-rpc.js +61 -0
  95. package/dist-server/server/piecemaker/model-discovery/stdio-rpc.js.map +1 -0
  96. package/dist-server/server/piecemaker/project-registry.js +29 -0
  97. package/dist-server/server/piecemaker/project-registry.js.map +1 -0
  98. package/package.json +12 -1
  99. package/scripts/piecemaker/cli/disable-anonymizer.mjs +19 -0
  100. package/scripts/piecemaker/cli/lib/config.mjs +0 -1
  101. package/scripts/piecemaker/cli/lib/services.mjs +1 -10
  102. package/scripts/piecemaker/cli/piecemaker.mjs +0 -3
  103. package/server/modules/providers/list/mistral/mistral-models.provider.ts +4 -53
  104. package/server/modules/providers/list/mistral/mistral-runtime.provider.ts +2 -3
  105. package/server/modules/providers/list/mistral/mistral-vibe-models.ts +43 -0
  106. package/server/modules/providers/tests/mistral-vibe-models.test.ts +27 -0
  107. package/server/piecemaker/anonymizer/client-config.cjs +92 -92
  108. package/server/piecemaker/anonymizer/hudsucker-fixture.cjs +9 -16
  109. package/server/piecemaker/anonymizer/hudsucker-proxy/Cargo.toml +1 -1
  110. package/server/piecemaker/anonymizer/hudsucker-proxy/src/main.rs +251 -268
  111. package/server/piecemaker/anonymizer/lifecycle.ts +2 -3
  112. package/server/piecemaker/anonymizer/rewriter-bridge.cjs +6 -0
  113. package/server/piecemaker/anonymizer/service.cjs +103 -174
  114. package/server/piecemaker/docx-document.ts +54 -0
  115. package/server/piecemaker/harness/decisions.cjs +1 -1
  116. package/server/piecemaker/harness/index.cjs +2 -3
  117. package/server/piecemaker/harness/verification.cjs +1 -1
  118. package/server/piecemaker/index.ts +11 -3
  119. package/server/piecemaker/model-discovery/codex-catalog.ts +67 -0
  120. package/server/piecemaker/model-discovery/index.ts +69 -0
  121. package/server/piecemaker/model-discovery/mistral-catalog.ts +36 -0
  122. package/server/piecemaker/model-discovery/stdio-rpc.ts +79 -0
  123. package/server/piecemaker/project-registry.ts +34 -0
  124. package/server/piecemaker/router.cjs +6 -7
  125. package/server/piecemaker/vendor/installer/bin/piecemaker.mjs +31 -77
  126. package/server/piecemaker/vendor/installer/lib/codex-skills.mjs +2 -84
  127. package/server/piecemaker/vendor/installer/lib/service.mjs +4 -24
  128. package/server/piecemaker/vendor/installer/steps/06-hooks.mjs +10 -21
  129. package/server/piecemaker/vendor/installer/steps/09-claude-assets.mjs +0 -11
  130. package/server/piecemaker/vendor/installer/steps/09-codex-plugin.mjs +6 -36
  131. package/server/piecemaker/vendor/installer/steps/13-garde-secrets.mjs +30 -12
  132. package/server/piecemaker/vendor/piecemaker-plugin/README.md +2 -9
  133. package/server/piecemaker/vendor/piecemaker-plugin/scripts/billing-track.mjs +5 -5
  134. package/server/piecemaker/vendor/piecemaker-plugin/scripts/classify-ai-documents.mjs +8 -8
  135. package/server/piecemaker/vendor/piecemaker-plugin/scripts/commit-track.mjs +2 -2
  136. package/server/piecemaker/vendor/piecemaker-plugin/scripts/compile-recherche.mjs +4 -6
  137. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folders.cjs +71 -38
  138. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.cjs +8 -38
  139. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/protection.cjs +82 -61
  140. package/server/piecemaker/vendor/piecemaker-plugin/scripts/protect-originals.mjs +4 -55
  141. package/server/piecemaker/vendor/piecemaker-plugin/scripts/statusline.mjs +24 -117
  142. package/server/piecemaker/vendor/piecemaker-plugin/skills/tamponnage/SKILL.md +2 -2
  143. package/server/piecemaker/vendor/websocket-server/admin-routes.cjs +15 -121
  144. package/server/piecemaker/vendor/websocket-server/case-registry.cjs +16 -32
  145. package/server/piecemaker/vendor/websocket-server/claude-hooks.cjs +0 -73
  146. package/server/piecemaker/vendor/websocket-server/originals-pipeline.cjs +0 -7
  147. package/server/piecemaker/vendor/websocket-server/stamping-routes.cjs +3 -12
  148. package/server/piecemaker/vendor/websocket-server/workspace-paths.cjs +6 -6
  149. package/dist/assets/arc-DAA6wH-C.js +0 -1
  150. package/dist/assets/architectureDiagram-5GKGNRK7-BGNHr7ef.js +0 -36
  151. package/dist/assets/channel-BAQyfMll.js +0 -1
  152. package/dist/assets/classDiagram-ZZMXUADV-BNGCuvrR.js +0 -1
  153. package/dist/assets/classDiagram-v2-VYDZK3BY-BNGCuvrR.js +0 -1
  154. package/dist/assets/index-WwknP0l4.css +0 -1
  155. package/dist/assets/layout-DEnkyfTY.js +0 -1
  156. package/dist/assets/sankeyDiagram-P5KCCOFB-4PW5HTOP.js +0 -40
  157. package/dist/assets/stateDiagram-D77RDMKH-o-vEcKwz.js +0 -1
  158. package/dist/assets/stateDiagram-v2-MP3YSRHH-BsuDaKkN.js +0 -1
  159. package/dist/assets/swimlanesDiagram-VR7AAH4N-DcCAyKlT.js +0 -8
  160. package/scripts/piecemaker/cli/lib/piecemaker-anonymizer.mjs +0 -78
  161. package/scripts/piecemaker/hooks/proxy-guard.mjs +0 -251
  162. package/server/piecemaker/anonymizer/providers.cjs +0 -120
  163. package/server/piecemaker/vendor/installer/steps/08-telegram.mjs +0 -482
  164. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/central-mapping.cjs +0 -0
  165. package/server/piecemaker/vendor/websocket-server/telegram-admin.cjs +0 -602
@@ -1,15 +1,14 @@
1
1
  type AnonymizerService = {
2
2
  start(): Promise<unknown>;
3
3
  stop(): Promise<unknown>;
4
- status(): { enabled: boolean; listening: boolean; coverage: Record<string, { state: string }> };
4
+ status(): { enabled: boolean; listening: boolean; reason?: string };
5
5
  };
6
6
 
7
7
  export async function startRequiredAnonymizer(service: AnonymizerService) {
8
8
  const ensureReady = () => {
9
9
  const status = service.status();
10
+ if (status.reason === 'disabled') return;
10
11
  if (!status.enabled || !status.listening) throw new Error('PieceMaker : le proxy PII est indisponible. Démarrage IA refusé.');
11
- const missing = ['claude', 'codex'].filter((provider) => status.coverage[provider]?.state !== 'filtered');
12
- if (missing.length) throw new Error(`PieceMaker : routage PII non configuré pour ${missing.join(', ')}. Démarrage IA refusé.`);
13
12
  };
14
13
  try {
15
14
  await service.start();
@@ -85,6 +85,12 @@ function startRewriterBridge({ dictionary, harness = null }) {
85
85
  const provider = header(request, 'x-piecemaker-provider') || 'claude';
86
86
  const current = () => dictionary.get();
87
87
 
88
+ if (request.method === 'GET' && request.url === '/health') {
89
+ response.writeHead(200);
90
+ response.end('ok');
91
+ return;
92
+ }
93
+
88
94
  if (request.method === 'POST' && request.url === '/v1/request') {
89
95
  readBody(request).then((body) => {
90
96
  stats.requests += 1;
@@ -1,20 +1,3 @@
1
- /**
2
- * Cycle de vie du proxy PII et point d'injection dans CloudCLI.
3
- *
4
- * Deux leviers, aucun ne touchant un fichier upstream.
5
- *
6
- * **L'environnement du serveur.** Les quatre chemins de lancement de CloudCLI —
7
- * chat Claude, Codex, Cursor, opencode — étalent `process.env` dans le
8
- * sous-processus qu'ils créent, tout comme le terminal intégré. Poser des
9
- * variables ici les atteint donc tous les cinq sans ajouter une ligne ailleurs.
10
- *
11
- * **Les configurations utilisateur des clients.** Codex ne lit pas de variable
12
- * de base d'URL, opencode préfère sa configuration ; `providers.cjs` s'en
13
- * charge, et pose le garde-fou de Cursor, seul client non interceptable.
14
- *
15
- * Le proxy vit et meurt avec le serveur : à l'arrêt, les bases écrites sont
16
- * retirées pour ne pas laisser les clients pointer un port fermé.
17
- */
18
1
  const { spawn } = require('node:child_process');
19
2
  const fs = require('node:fs');
20
3
  const os = require('node:os');
@@ -23,19 +6,22 @@ const tls = require('node:tls');
23
6
 
24
7
  const { createSqliteDictionaryLoader } = require('./sqlite-dictionary.cjs');
25
8
  const { startRewriterBridge } = require('./rewriter-bridge.cjs');
26
- const { bypassProviders, configureProviders } = require('./providers.cjs');
9
+ const { removeLegacyProxyConfig } = require('./client-config.cjs');
27
10
  const { createHarnessJuridique } = require('../harness/index.cjs');
28
11
 
29
- const ENV_VAR = 'ANTHROPIC_BASE_URL';
30
- const OPENAI_ENV_VAR = 'OPENAI_BASE_URL';
31
12
  const DEFAULT_UPSTREAM = 'https://api.anthropic.com';
32
- const DEFAULT_PORT = 4111;
33
- const PROXY_ENV_KEYS = [
34
- 'HTTPS_PROXY', 'HTTP_PROXY', 'ALL_PROXY', 'NO_PROXY', 'NODE_USE_ENV_PROXY',
35
- 'NODE_EXTRA_CA_CERTS', 'SSL_CERT_FILE', 'CODEX_CA_CERTIFICATE', 'REQUESTS_CA_BUNDLE',
36
- ];
13
+ const INTERCEPTED_HOSTS = ['api.anthropic.com', 'api.openai.com', 'chatgpt.com'];
14
+ const COVERAGE = {
15
+ claude: { state: 'filtered', detail: null, file: null },
16
+ codex: { state: 'filtered', detail: null, file: null },
17
+ opencode: { state: 'filtered', detail: null, file: null },
18
+ cursor: { state: 'blocked', detail: 'not-interceptable', file: null },
19
+ };
20
+
21
+ const CURSOR_REFUSAL = 'PieceMaker : cursor-agent ne peut pas être filtré par le proxy d’anonymisation '
22
+ + '(son protocole n’accepte aucune base d’URL). Tant qu’un mapping existe, il est bloqué pour éviter '
23
+ + 'que des noms de parties quittent la machine en clair. Utilisez Claude, Codex ou opencode.';
37
24
 
38
- /** Interrupteur : `PIECEMAKER_ANONYMIZER=off`, ou `anonymizer.enabled: false` dans `config.json`. */
39
25
  function isDisabled(homeDir) {
40
26
  const flag = String(process.env.PIECEMAKER_ANONYMIZER || '').toLowerCase();
41
27
  if (flag === 'off' || flag === '0' || flag === 'false') return true;
@@ -47,13 +33,6 @@ function isDisabled(homeDir) {
47
33
  }
48
34
  }
49
35
 
50
- /**
51
- * Cible du relais Anthropic. Une base distante déjà configurée (passerelle
52
- * d'entreprise, point de terminaison alternatif) est respectée : on se chaîne
53
- * derrière elle. Une base en boucle locale ne peut être qu'une instance
54
- * précédente de ce proxy, et serait un relais mort : on l'ignore au profit de
55
- * l'API officielle.
56
- */
57
36
  function resolveUpstream(configured) {
58
37
  if (!configured) return DEFAULT_UPSTREAM;
59
38
  try {
@@ -65,35 +44,15 @@ function resolveUpstream(configured) {
65
44
  }
66
45
  }
67
46
 
68
- /**
69
- * Lecture du rapport de `configureProviders` en une couverture par client, la
70
- * seule chose que l'interface a besoin de montrer.
71
- *
72
- * `filtered` : les échanges passent par le proxy et sont anonymisés.
73
- * `blocked` : le client ne peut pas être filtré et est donc empêché de partir.
74
- * `unconfigured` : le câblage a échoué ou est en conflit — le client peut
75
- * encore parler en direct, et c'est précisément ce que le statut doit crier.
76
- */
77
- function summarizeCoverage(report) {
78
- const coverage = {};
79
- for (const [name, result] of Object.entries(report || {})) {
80
- if (result?.blocked) coverage[name] = { state: 'blocked', detail: result.reason, file: result.file || null };
81
- else if (result?.configured) coverage[name] = { state: 'filtered', detail: null, file: result.file || null };
82
- else coverage[name] = { state: 'unconfigured', detail: result?.reason || 'unknown', file: result?.file || null };
83
- }
84
- return coverage;
85
- }
86
-
87
47
  function hudsuckerBinary() {
88
48
  if (process.env.PIECEMAKER_HUDSUCKER_BIN) return process.env.PIECEMAKER_HUDSUCKER_BIN;
89
49
  const name = process.platform === 'win32' ? 'piecemaker-hudsucker.exe' : 'piecemaker-hudsucker';
90
- const packaged = path.join(__dirname, 'bin', name);
91
- const release = path.join(__dirname, 'hudsucker-proxy', 'target', 'release', name);
92
- const debug = path.join(__dirname, 'hudsucker-proxy', 'target', 'debug', name);
93
- if (fs.existsSync(packaged)) return packaged;
94
- if (fs.existsSync(release)) return release;
95
- if (fs.existsSync(debug)) return debug;
96
- return packaged;
50
+ const candidates = [
51
+ path.join(__dirname, 'bin', name),
52
+ path.join(__dirname, 'hudsucker-proxy', 'target', 'release', name),
53
+ path.join(__dirname, 'hudsucker-proxy', 'target', 'debug', name),
54
+ ];
55
+ return candidates.find((candidate) => fs.existsSync(candidate)) || candidates[0];
97
56
  }
98
57
 
99
58
  function certificatePaths() {
@@ -113,28 +72,52 @@ function writeTrustBundle(caFile, bundleFile) {
113
72
  return bundleFile;
114
73
  }
115
74
 
116
- function waitForOutput(child, needle, timeoutMs) {
75
+ function removeLegacyCentralMapping(homeDir) {
76
+ let entries = [];
77
+ try {
78
+ entries = fs.readdirSync(homeDir);
79
+ } catch {
80
+ return;
81
+ }
82
+ for (const name of entries) {
83
+ if (name.startsWith('central-mapping.json')) fs.rmSync(path.join(homeDir, name), { force: true });
84
+ }
85
+ }
86
+
87
+ function installCursorGuard({ binDir, mappingFile }) {
88
+ fs.mkdirSync(binDir, { recursive: true });
89
+ const file = path.join(binDir, 'cursor-agent');
90
+ const script = `#!/bin/sh
91
+ if [ -s ${JSON.stringify(mappingFile)} ]; then
92
+ printf '%s\\n' ${JSON.stringify(CURSOR_REFUSAL)} >&2
93
+ exit 78
94
+ fi
95
+ PATH=$(printf '%s' "$PATH" | sed -e "s|^${binDir.replace(/[|\\]/g, '\\$&')}:||")
96
+ export PATH
97
+ exec cursor-agent "$@"
98
+ `;
99
+ fs.writeFileSync(file, script, { encoding: 'utf8', mode: 0o755 });
100
+ return { file };
101
+ }
102
+
103
+ function waitForListening(child, timeoutMs) {
117
104
  return new Promise((resolve, reject) => {
118
105
  let buffer = '';
119
- let settled = false;
120
- const finish = (failed, value) => {
121
- if (settled) return;
122
- settled = true;
106
+ const finish = (error, port) => {
123
107
  clearTimeout(timer);
124
- child.stdout?.off('data', take);
125
- child.stderr?.off('data', take);
108
+ child.stdout.off('data', take);
126
109
  child.off('exit', onExit);
127
- if (failed) reject(value);
128
- else resolve(value);
110
+ if (error) reject(error);
111
+ else resolve(port);
129
112
  };
130
- const timer = setTimeout(() => finish(true, new Error(`hudsucker n'a pas écouté : ${buffer}`)), timeoutMs);
113
+ const timer = setTimeout(() => finish(new Error(`hudsucker n'a pas écouté : ${buffer}`)), timeoutMs);
131
114
  const take = (chunk) => {
132
115
  buffer += chunk.toString();
133
- if (buffer.includes(needle)) finish(false, buffer);
116
+ const match = buffer.match(/listening 127\.0\.0\.1:(\d+)/);
117
+ if (match) finish(null, Number(match[1]));
134
118
  };
135
- const onExit = (code) => finish(true, new Error(`hudsucker sorti (${code}) : ${buffer}`));
136
- child.stdout?.on('data', take);
137
- child.stderr?.on('data', take);
119
+ const onExit = (code) => finish(new Error(`hudsucker sorti (${code}) : ${buffer}`));
120
+ child.stdout.on('data', take);
138
121
  child.once('exit', onExit);
139
122
  });
140
123
  }
@@ -142,8 +125,7 @@ function waitForOutput(child, needle, timeoutMs) {
142
125
  function proxyEnvironment(proxyUrl, caFile, trustBundle) {
143
126
  return {
144
127
  HTTPS_PROXY: proxyUrl,
145
- HTTP_PROXY: proxyUrl,
146
- ALL_PROXY: proxyUrl,
128
+ https_proxy: proxyUrl,
147
129
  NO_PROXY: 'localhost,127.0.0.1,::1',
148
130
  NODE_USE_ENV_PROXY: '1',
149
131
  NODE_EXTRA_CA_CERTS: caFile,
@@ -153,32 +135,20 @@ function proxyEnvironment(proxyUrl, caFile, trustBundle) {
153
135
  };
154
136
  }
155
137
 
156
- function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = console, required = false }) {
138
+ function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = console }) {
157
139
  const dictionary = createSqliteDictionaryLoader({ databasePath: process.env.DATABASE_PATH || path.join(homeDir, 'auth.db') });
158
- const legacyMappingFile = path.join(homeDir, 'central-mapping.json');
159
- // Harnais de citations vérifiées (décisions Légifrance + bloc <CITATIONS>) :
160
- // son propre interrupteur (`PIECEMAKER_CITATIONS=off`) est géré à
161
- // l'intérieur, pas ici.
162
140
  const harness = createHarnessJuridique({ homeDir, verifyResponses: false });
163
- // Le shim Cursor vit sous le répertoire de données PieceMaker : il n'a rien à
164
- // faire dans un répertoire appartenant à un fournisseur.
165
141
  const binDir = path.join(homeDir, 'bin');
166
- const state = {
167
- enabled: false,
168
- reason: 'not_started',
169
- origin: null,
170
- upstream: null,
171
- startedAt: null,
172
- routes: [],
173
- coverage: {},
174
- };
142
+ const state = { enabled: false, reason: 'not_started', origin: null, upstream: null, coverage: {} };
175
143
  let proxy = null;
176
144
  let bridge = null;
177
145
  let previousEnv = null;
146
+ const killProxy = () => proxy?.kill('SIGKILL');
178
147
 
179
148
  async function start() {
180
149
  if (proxy) return state;
181
- if (!required && isDisabled(homeDir)) {
150
+ removeLegacyProxyConfig({ userHome, homeDir });
151
+ if (isDisabled(homeDir)) {
182
152
  state.reason = 'disabled';
183
153
  return state;
184
154
  }
@@ -191,14 +161,8 @@ function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = co
191
161
  return state;
192
162
  }
193
163
 
194
- const claudeUpstream = resolveUpstream(process.env[ENV_VAR]);
195
- const hosts = ['api.anthropic.com', 'api.openai.com', 'chatgpt.com'];
196
- try {
197
- const extra = new URL(claudeUpstream).hostname;
198
- if (extra && !hosts.includes(extra)) hosts.push(extra);
199
- } catch {
200
- // l'amont par défaut reste la liste fixe
201
- }
164
+ const upstream = resolveUpstream(process.env.ANTHROPIC_BASE_URL);
165
+ const hosts = [...new Set([...INTERCEPTED_HOSTS, new URL(upstream).hostname])];
202
166
 
203
167
  try {
204
168
  bridge = await startRewriterBridge({ dictionary, harness });
@@ -208,33 +172,30 @@ function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = co
208
172
  return state;
209
173
  }
210
174
 
211
- const listenedPort = DEFAULT_PORT;
212
- const origin = `http://127.0.0.1:${listenedPort}`;
213
175
  const child = spawn(binary, [
214
- '--listen', `127.0.0.1:${listenedPort}`,
176
+ '--listen', '127.0.0.1:0',
215
177
  '--rewriter', `http://127.0.0.1:${bridge.port}`,
216
178
  '--ca-cert', certificates.cert,
217
179
  '--ca-key', certificates.key,
218
180
  '--hosts', hosts.join(','),
219
181
  ], {
220
- stdio: ['ignore', 'pipe', 'pipe'],
221
- env: {
222
- ...process.env,
223
- HTTPS_PROXY: '',
224
- HTTP_PROXY: '',
225
- ALL_PROXY: '',
226
- https_proxy: '',
227
- http_proxy: '',
228
- all_proxy: '',
229
- },
182
+ stdio: ['pipe', 'pipe', 'pipe'],
183
+ env: { ...process.env, HTTPS_PROXY: '', HTTP_PROXY: '', ALL_PROXY: '', https_proxy: '', http_proxy: '', all_proxy: '' },
230
184
  });
231
185
  proxy = child;
232
- child.stdout?.on('data', (chunk) => logger.log?.(String(chunk).trim()));
233
- child.stderr?.on('data', (chunk) => logger.warn?.(`[piecemaker] hudsucker : ${String(chunk).trim()}`));
186
+ process.once('exit', killProxy);
187
+ child.stderr.on('data', (chunk) => logger.warn?.(`[piecemaker] hudsucker : ${String(chunk).trim()}`));
188
+ child.once('exit', (code, signal) => {
189
+ if (proxy === child && state.enabled) logger.warn?.(`[piecemaker] hudsucker arrêté (${code ?? signal}) : requêtes IA refusées`);
190
+ });
191
+
192
+ let port;
234
193
  try {
235
- await waitForOutput(child, `listening 127.0.0.1:${listenedPort}`, 15000);
194
+ port = await waitForListening(child, 15000);
195
+ child.stdout.resume();
236
196
  } catch (error) {
237
197
  child.kill('SIGKILL');
198
+ process.off('exit', killProxy);
238
199
  proxy = null;
239
200
  await bridge.close();
240
201
  bridge = null;
@@ -243,72 +204,44 @@ function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = co
243
204
  return state;
244
205
  }
245
206
 
246
- // Publie le port dans la config partagée pour que les consommateurs
247
- // indépendants du process serveur puissent le retrouver.
248
- try {
249
- const configFile = path.join(homeDir, 'config.json');
250
- const current = JSON.parse(fs.readFileSync(configFile, 'utf8'));
251
- if (current.mikePiiPort !== listenedPort) {
252
- fs.writeFileSync(configFile, `${JSON.stringify({ ...current, mikePiiPort: listenedPort }, null, 2)}\n`, 'utf8');
253
- }
254
- } catch (error) {
255
- logger.warn?.(`[piecemaker] impossible de publier mikePiiPort dans config.json : ${error.message}`);
256
- }
257
-
258
- previousEnv = {
259
- [ENV_VAR]: process.env[ENV_VAR],
260
- [OPENAI_ENV_VAR]: process.env[OPENAI_ENV_VAR],
261
- PATH: process.env.PATH,
262
- };
263
- for (const key of PROXY_ENV_KEYS) previousEnv[key] = process.env[key];
264
- const trustBundle = writeTrustBundle(certificates.cert, certificates.trustBundle);
265
- Object.assign(process.env, proxyEnvironment(origin, certificates.cert, trustBundle));
207
+ const origin = `http://127.0.0.1:${port}`;
208
+ const environment = proxyEnvironment(origin, certificates.cert, writeTrustBundle(certificates.cert, certificates.trustBundle));
209
+ previousEnv = Object.fromEntries(['PATH', 'HTTP_PROXY', 'http_proxy', 'ALL_PROXY', 'all_proxy', ...Object.keys(environment)]
210
+ .map((key) => [key, process.env[key]]));
211
+ for (const key of ['HTTP_PROXY', 'http_proxy', 'ALL_PROXY', 'all_proxy']) delete process.env[key];
212
+ Object.assign(process.env, environment);
213
+ removeLegacyCentralMapping(homeDir);
214
+ installCursorGuard({ binDir, mappingFile: path.join(homeDir, 'central-mapping.json') });
266
215
  process.env.PATH = `${binDir}${path.delimiter}${process.env.PATH || ''}`;
267
216
 
268
- const report = await configureProviders({
269
- origin,
270
- caFile: certificates.cert,
271
- trustBundle,
272
- userHome,
273
- binDir,
274
- mappingFile: legacyMappingFile,
275
- });
276
217
  Object.assign(state, {
277
218
  enabled: true,
278
219
  reason: 'running',
279
220
  origin,
280
- upstream: claudeUpstream,
281
- startedAt: new Date().toISOString(),
282
- routes: hosts.map((host) => ({ provider: host, upstream: `https://${host}` })),
283
- coverage: summarizeCoverage(report),
221
+ upstream,
222
+ coverage: COVERAGE,
284
223
  });
285
-
286
- const uncovered = Object.entries(state.coverage)
287
- .filter(([, value]) => value.state === 'unconfigured')
288
- .map(([name]) => name);
289
224
  logger.log?.(`[piecemaker] anonymisation active sur ${origin} (claude, codex, opencode ; cursor bloqué)`);
290
- if (uncovered.length) {
291
- logger.warn?.(`[piecemaker] clients non filtrés, à vérifier : ${uncovered.join(', ')}`);
292
- }
293
225
  return state;
294
226
  }
295
227
 
296
228
  async function stop() {
297
- if (!proxy && !bridge) {
298
- dictionary.close();
299
- return;
300
- }
301
- if (proxy && proxy.exitCode === null) {
302
- proxy.kill('SIGTERM');
303
- await Promise.race([
304
- new Promise((resolve) => proxy.once('exit', resolve)),
305
- new Promise((resolve) => setTimeout(resolve, 2000)),
306
- ]);
307
- if (proxy.exitCode === null) proxy.kill('SIGKILL');
229
+ if (proxy) {
230
+ const child = proxy;
231
+ proxy = null;
232
+ process.off('exit', killProxy);
233
+ if (child.exitCode === null) {
234
+ child.kill('SIGTERM');
235
+ await Promise.race([
236
+ new Promise((resolve) => child.once('exit', resolve)),
237
+ new Promise((resolve) => setTimeout(resolve, 2000)),
238
+ ]);
239
+ if (child.exitCode === null) child.kill('SIGKILL');
240
+ }
308
241
  }
309
242
  if (bridge) await bridge.close();
310
243
  bridge = null;
311
- await bypassProviders({ userHome, binDir });
244
+ fs.rmSync(path.join(binDir, 'cursor-agent'), { force: true });
312
245
  if (previousEnv) {
313
246
  for (const [key, value] of Object.entries(previousEnv)) {
314
247
  if (value === undefined) delete process.env[key];
@@ -316,16 +249,15 @@ function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = co
316
249
  }
317
250
  previousEnv = null;
318
251
  }
319
- proxy = null;
320
252
  dictionary.close();
321
- Object.assign(state, { enabled: false, reason: 'stopped', origin: null, routes: [], coverage: {} });
253
+ Object.assign(state, { enabled: false, reason: 'stopped', origin: null, upstream: null, coverage: {} });
322
254
  }
323
255
 
324
256
  function status() {
325
257
  const current = dictionary.get();
326
258
  return {
327
259
  ...state,
328
- listening: Boolean(proxy && proxy.exitCode === null && !proxy.killed),
260
+ listening: Boolean(proxy && proxy.exitCode === null && proxy.signalCode === null),
329
261
  dictionary: {
330
262
  file: dictionary.file,
331
263
  exists: dictionary.exists(),
@@ -342,11 +274,8 @@ function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = co
342
274
  }
343
275
 
344
276
  module.exports = {
345
- ENV_VAR,
346
- OPENAI_ENV_VAR,
347
277
  createAnonymizerService,
348
- isDisabled,
278
+ installCursorGuard,
349
279
  resolveUpstream,
350
- summarizeCoverage,
351
280
  writeTrustBundle,
352
281
  };
@@ -0,0 +1,54 @@
1
+ import { randomUUID } from 'crypto';
2
+ import { rename, rm, stat, writeFile } from 'fs/promises';
3
+ import path from 'path';
4
+
5
+ import express, { type Request, type Response } from 'express';
6
+
7
+ import { projectsDb } from '@/modules/database/index.js';
8
+
9
+ const MAX_DOCX_BYTES = '100mb';
10
+
11
+ function resolveDocxPath(request: Request): string | null {
12
+ const projectId = typeof request.query.projectId === 'string' ? request.query.projectId : '';
13
+ const filePath = typeof request.query.path === 'string' ? request.query.path : '';
14
+ const projectRoot = projectId ? projectsDb.getProjectPathById(projectId) : null;
15
+ if (!projectRoot || !filePath.toLowerCase().endsWith('.docx')) return null;
16
+ const resolved = path.resolve(projectRoot, filePath);
17
+ return resolved.startsWith(path.resolve(projectRoot) + path.sep) ? resolved : null;
18
+ }
19
+
20
+ async function readVersion(filePath: string): Promise<number> {
21
+ return (await stat(filePath)).mtimeMs;
22
+ }
23
+
24
+ export function createDocxDocumentRouter() {
25
+ const router = express.Router();
26
+
27
+ router.get('/docx-document/version', async (request: Request, response: Response) => {
28
+ const filePath = resolveDocxPath(request);
29
+ if (!filePath) return response.status(400).json({ error: 'Invalid docx path' });
30
+ try {
31
+ response.json({ version: await readVersion(filePath) });
32
+ } catch {
33
+ response.status(404).json({ error: 'File not found' });
34
+ }
35
+ });
36
+
37
+ router.put('/docx-document', express.raw({ type: () => true, limit: MAX_DOCX_BYTES }), async (request: Request, response: Response) => {
38
+ const filePath = resolveDocxPath(request);
39
+ if (!filePath || !Buffer.isBuffer(request.body) || request.body.length === 0) {
40
+ return response.status(400).json({ error: 'Invalid docx payload' });
41
+ }
42
+ const temporaryPath = `${filePath}.${randomUUID()}.tmp`;
43
+ try {
44
+ await writeFile(temporaryPath, request.body);
45
+ await rename(temporaryPath, filePath);
46
+ response.json({ version: await readVersion(filePath) });
47
+ } catch (error) {
48
+ await rm(temporaryPath, { force: true });
49
+ response.status(500).json({ error: error instanceof Error ? error.message : String(error) });
50
+ }
51
+ });
52
+
53
+ return router;
54
+ }
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * Capture des décisions Légifrance lues via l'outil MCP `consulter_decision`,
5
5
  * repérées dans un corps de requête SORTANTE déjà analysé en JSON par le
6
- * proxy PII (`server/piecemaker/anonymizer/proxy.cjs`).
6
+ * proxy PII (`server/piecemaker/anonymizer/rewriter-bridge.cjs`).
7
7
  *
8
8
  * Équivalent serveur du hook `decision-cache.mjs` de l'autre dépôt
9
9
  * (`piecemaker-plugin/scripts/decision-cache.mjs`, PostToolUse Claude Code) :
@@ -1,9 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * Façade du harnais de citations vérifiées, à brancher sur le proxy PII
5
- * local (`server/piecemaker/anonymizer/proxy.cjs`) — câblage laissé à une
6
- * tâche ultérieure, ce module ne modifie rien de `proxy.cjs`.
4
+ * Façade du harnais de citations vérifiées, branchée sur le réécrivain du
5
+ * proxy PII (`server/piecemaker/anonymizer/rewriter-bridge.cjs`).
7
6
  *
8
7
  * Deux observateurs, symétriques au sens et au retour du proxy lui-même :
9
8
  * - `observerRequete` regarde ce qui PART vers le fournisseur (avant
@@ -6,7 +6,7 @@
6
6
  * (`piecemaker-plugin/scripts/verify-citations.mjs`).
7
7
  *
8
8
  * Ce dépôt n'a pas de hook Claude Code : tout le trafic IA passe par le
9
- * proxy PII local (`server/piecemaker/anonymizer/proxy.cjs`), qui est donc
9
+ * proxy PII local (`server/piecemaker/anonymizer/rewriter-bridge.cjs`), qui est donc
10
10
  * l'unique point d'observation possible pour un texte produit par le modèle.
11
11
  * Contrairement au hook d'origine, ce module n'a pas le pouvoir de bloquer
12
12
  * un tour (il n'y a pas de conversation Claude Code à interrompre côté
@@ -4,6 +4,7 @@ import path from 'path';
4
4
  import type { Router } from 'express';
5
5
 
6
6
  import { providerRuntimeService, sessionsService } from '@/modules/providers/index.js';
7
+ import { providerRegistry } from '@/modules/providers/provider.registry.js';
7
8
  import { findApplicationRoot, getModuleDirectory } from '@/shared/utils.js';
8
9
 
9
10
 
@@ -16,7 +17,10 @@ import { createKnowledgeBackend } from './knowledge/index.js';
16
17
  import { createTimesheetBackend } from './timesheet/index.js';
17
18
  import { createCompanySearchRouter } from './company-search.js';
18
19
  import { createBodaccSearchRouter } from './bodacc-search.js';
20
+ import { createDocxDocumentRouter } from './docx-document.js';
19
21
  import { createShellEnvironmentRouter, resolveDesktopShellEnvironment } from './shell-environment.js';
22
+ import { installModelDiscovery } from './model-discovery/index.js';
23
+ import { startProjectRegistry } from './project-registry.js';
20
24
 
21
25
  /**
22
26
  * Point d'entrée PieceMaker. Les modules de `server/piecemaker/vendor/` sont du
@@ -37,8 +41,9 @@ type PieceMakerRuntimeStatus = {
37
41
  };
38
42
 
39
43
  type PieceMakerVendorModule = {
40
- createPieceMakerRouter(options?: { getRuntimeStatus?: () => PieceMakerRuntimeStatus; anonymizer?: unknown }): Router;
44
+ createPieceMakerRouter(options?: { getRuntimeStatus?: () => PieceMakerRuntimeStatus; anonymizer?: unknown; publishProjects?: () => void }): Router;
41
45
  piecemakerHome(): string;
46
+ publishProjectSource(sourceId: string, folders: string[] | null): void;
42
47
  stopOriginalsJobs(): Promise<void>;
43
48
  };
44
49
 
@@ -46,13 +51,15 @@ const shellEnvironment = await resolveDesktopShellEnvironment();
46
51
  const vendor = createRequire(import.meta.url)(routerPath) as PieceMakerVendorModule;
47
52
 
48
53
  export const { piecemakerHome, stopOriginalsJobs } = vendor;
54
+ const publishProjects = startProjectRegistry(vendor.publishProjectSource);
49
55
  const { createAnonymizerService } = createRequire(import.meta.url)(path.join(applicationRoot, 'server/piecemaker/anonymizer/service.cjs'));
50
- const anonymizer = createAnonymizerService({ homeDir: piecemakerHome(), required: true });
56
+ const anonymizer = createAnonymizerService({ homeDir: piecemakerHome() });
51
57
  const ensureProxy = await startRequiredAnonymizer(anonymizer);
52
58
  const citations = createCitationStore(piecemakerHome());
53
59
  installChatCitationHarness({ runtime: providerRuntimeService, sessions: sessionsService, store: citations, ensureProxy });
54
60
  const library = await openLibrary(piecemakerHome(), applicationRoot);
55
61
  installLibraryRuntime(providerRuntimeService, sessionsService, library.store);
62
+ installModelDiscovery(providerRegistry);
56
63
  const timesheet = createTimesheetBackend(piecemakerHome());
57
64
  const knowledge = createKnowledgeBackend(applicationRoot);
58
65
 
@@ -61,7 +68,7 @@ export function createPieceMakerLocalRouter() {
61
68
  }
62
69
 
63
70
  export function createPieceMakerRouter(options: { getRuntimeStatus?: () => PieceMakerRuntimeStatus } = {}) {
64
- const router = vendor.createPieceMakerRouter({ ...options, anonymizer });
71
+ const router = vendor.createPieceMakerRouter({ ...options, anonymizer, publishProjects });
65
72
  router.use(createCitationsRouter(citations, (id) => {
66
73
  try { sessionsService.getSessionDetailsById(id); return true; } catch { return false; }
67
74
  }));
@@ -70,6 +77,7 @@ export function createPieceMakerRouter(options: { getRuntimeStatus?: () => Piece
70
77
  router.use(knowledge.router);
71
78
  router.use(createCompanySearchRouter());
72
79
  router.use(createBodaccSearchRouter());
80
+ router.use(createDocxDocumentRouter());
73
81
  router.use(createShellEnvironmentRouter(shellEnvironment));
74
82
  return router;
75
83
  }
@@ -0,0 +1,67 @@
1
+ import { createRequire } from 'node:module';
2
+
3
+ import type { ProviderModelOption, ProviderModelsDefinition } from '@/shared/types.js';
4
+
5
+ import { withStdioRpcServer, type RpcCall, type StdioRpcServer } from './stdio-rpc.js';
6
+
7
+ type CodexListedModel = {
8
+ model: string;
9
+ displayName?: string;
10
+ description?: string;
11
+ hidden?: boolean;
12
+ isDefault?: boolean;
13
+ defaultReasoningEffort?: string;
14
+ supportedReasoningEfforts?: { reasoningEffort: string; description?: string }[];
15
+ };
16
+
17
+ type CodexModelListPage = {
18
+ data?: CodexListedModel[];
19
+ nextCursor?: string | null;
20
+ };
21
+
22
+ const MAX_PAGES = 10;
23
+
24
+ function bundledCodexAppServer(): StdioRpcServer {
25
+ const launcher = createRequire(import.meta.url).resolve('@openai/codex/bin/codex.js');
26
+ return { command: process.execPath, args: [launcher, 'app-server'] };
27
+ }
28
+
29
+ function toModelOption(listed: CodexListedModel): ProviderModelOption {
30
+ const efforts = listed.supportedReasoningEfforts ?? [];
31
+ return {
32
+ value: listed.model,
33
+ label: listed.displayName || listed.model,
34
+ ...(listed.description ? { description: listed.description } : {}),
35
+ ...(efforts.length > 0 ? {
36
+ effort: {
37
+ ...(listed.defaultReasoningEffort ? { default: listed.defaultReasoningEffort } : {}),
38
+ values: efforts.map(({ reasoningEffort, description }) => ({
39
+ value: reasoningEffort,
40
+ ...(description ? { description } : {}),
41
+ })),
42
+ },
43
+ } : {}),
44
+ };
45
+ }
46
+
47
+ export async function listCodexModels(call: RpcCall): Promise<ProviderModelsDefinition> {
48
+ const listed: CodexListedModel[] = [];
49
+ let cursor: string | null = null;
50
+ for (let page = 0; page < MAX_PAGES; page += 1) {
51
+ const result = await call('model/list', { cursor, includeHidden: false }) as CodexModelListPage;
52
+ listed.push(...(result.data ?? []));
53
+ cursor = result.nextCursor ?? null;
54
+ if (!cursor) break;
55
+ }
56
+
57
+ const visible = listed.filter((model) => model.model && !model.hidden);
58
+ if (visible.length === 0) throw new Error('codex app-server listed no models');
59
+ return {
60
+ OPTIONS: visible.map(toModelOption),
61
+ DEFAULT: (visible.find((model) => model.isDefault) ?? visible[0]).model,
62
+ };
63
+ }
64
+
65
+ export function discoverCodexModels(timeoutMs: number): Promise<ProviderModelsDefinition> {
66
+ return withStdioRpcServer(bundledCodexAppServer(), listCodexModels, timeoutMs);
67
+ }