@microi.net/cli 5.8.5 → 5.8.7

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 (208) hide show
  1. package/.codebuddy-plugin/marketplace.json +2 -2
  2. package/.codebuddy-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.workbuddy-plugin/marketplace.json +2 -2
  5. package/.workbuddy-plugin/plugin.json +1 -1
  6. package/LICENSE +21 -21
  7. package/README.md +71 -71
  8. package/assets/build-meta.json +7 -7
  9. package/assets/feature-matrix.json +138 -138
  10. package/assets/logo.svg +4 -4
  11. package/cordis.patch.yml +1 -1
  12. package/package.json +1 -1
  13. package/scripts/mcp-codex-stdio-adapter.js +0 -0
  14. package/scripts/mcp-server.js +1 -1
  15. package/scripts/microi-cli.js +66 -65
  16. package/scripts/microi-codex-broker.js +450 -450
  17. package/scripts/microi-codex-router.js +618 -618
  18. package/scripts/microi-skills.meta.json +384 -384
  19. package/skills/.microi-skills-version.json +2 -2
  20. package/skills/.progressive-disclosure-manifest.json +21 -21
  21. package/skills/README.md +287 -287
  22. package/skills/ai-engine/SKILL.md +269 -265
  23. package/skills/ai-engine/agents/openai.yaml +4 -4
  24. package/skills/ai-engine/references/ai-employees.md +48 -48
  25. package/skills/ai-engine/references/self-hosted-digital-human.md +59 -59
  26. package/skills/ai-platform-governance/SKILL.md +177 -177
  27. package/skills/ai-platform-governance/references/progressive-01-/345/212/237/350/203/275/345/274/200/345/205/263.md +190 -190
  28. package/skills/app-store/SKILL.md +525 -525
  29. package/skills/app-store/agents/openai.yaml +4 -4
  30. package/skills/business-blueprint/SKILL.md +193 -193
  31. package/skills/datasource-engine/SKILL.md +93 -93
  32. package/skills/datasource-engine/agents/openai.yaml +4 -4
  33. package/skills/dos-orm/SKILL.md +97 -95
  34. package/skills/dos-orm/references/api-reference.md +229 -229
  35. package/skills/email-engine/SKILL.md +81 -81
  36. package/skills/email-engine/references/v8-email.md +34 -34
  37. package/skills/job-engine/SKILL.md +176 -176
  38. package/skills/job-engine/agents/openai.yaml +4 -4
  39. package/skills/message-notification/SKILL.md +156 -156
  40. package/skills/message-notification/agents/openai.yaml +5 -5
  41. package/skills/message-notification/references/contracts.md +102 -102
  42. package/skills/microi/SKILL.md +14 -14
  43. package/skills/microi-ai-app-auth.js +652 -652
  44. package/skills/microi-ai-application/SKILL.md +115 -115
  45. package/skills/microi-ai-application/agents/openai.yaml +4 -4
  46. package/skills/microi-ai-application/references/frontend-baseline.md +164 -164
  47. package/skills/microi-client-frontend/SKILL.md +246 -244
  48. package/skills/microi-client-frontend/references/progressive-01-3-/345/212/250/346/200/201/346/214/211/351/222/256/347/263/273/347/273/237.md +144 -144
  49. package/skills/microi-client-frontend/references/progressive-02-8-/350/277/220/350/241/214/346/227/266/351/253/230/351/242/221/345/235/221/345/244/215/347/233/230.md +196 -196
  50. package/skills/microi-client-frontend/references/progressive-03-vue3-/345/211/215/347/253/257/345/276/256/346/234/215/345/212/241/345/256/277/344/270/273/350/247/204/345/210/231.md +167 -167
  51. package/skills/microi-codex/SKILL.md +102 -100
  52. package/skills/microi-codex-installer/SKILL.md +231 -231
  53. package/skills/microi-codex-installer/agents/openai.yaml +7 -7
  54. package/skills/microi-datasource-mapping/SKILL.md +122 -122
  55. package/skills/microi-db-schema/SKILL.md +175 -175
  56. package/skills/microi-db-schema/agents/openai.yaml +4 -4
  57. package/skills/microi-db-schema/references/core-tables.md +695 -695
  58. package/skills/microi-db-schema/references/form-component-options.md +256 -256
  59. package/skills/microi-db-schema/references/schema-overview.md +202 -202
  60. package/skills/microi-db-schema/references/schema.md +646 -646
  61. package/skills/microi-db-schema/references/table-catalog.md +1599 -1599
  62. package/skills/microi-deployment/SKILL.md +222 -220
  63. package/skills/microi-deployment/references/deployment-matrix.md +109 -109
  64. package/skills/microi-docs-coverage/SKILL.md +133 -133
  65. package/skills/microi-docs-coverage/references/capability-map.md +91 -91
  66. package/skills/microi-docs-coverage/scripts/audit-doc-skill-coverage.mjs +894 -894
  67. package/skills/microi-form-engine/SKILL.md +333 -333
  68. package/skills/microi-form-engine/references/component-catalog.md +218 -218
  69. package/skills/microi-form-engine/references/data-source-events.md +124 -124
  70. package/skills/microi-form-layout/SKILL.md +205 -205
  71. package/skills/microi-form-layout/references/progressive-01-3-/344/270/211/347/247/215/345/210/206/347/273/204/347/232/204/345/255/230/345/202/250/344/270/216/351/205/215/347/275/256.md +235 -235
  72. package/skills/microi-frontend-sdk/SKILL.md +194 -194
  73. package/skills/microi-frontend-sdk/references/progressive-01-token-/345/275/223/345/211/215/347/231/273/345/275/225/347/224/250/346/210/267/344/270/216/345/275/223/345/211/215/347/273/210/347/253/257/347/231/273/345/275/225/345/215/217/350/256/256.md +176 -176
  74. package/skills/microi-left-right-layout/SKILL.md +141 -141
  75. package/skills/microi-microservice/SKILL.md +328 -326
  76. package/skills/microi-microservice/references/runtime-delivery.md +278 -278
  77. package/skills/microi-mobile-app-quality/SKILL.md +185 -185
  78. package/skills/microi-mobile-app-quality/references/progressive-01-4-/351/207/215/350/246/201/346/214/211/351/222/256/345/277/205/351/241/273/345/270/246/345/233/276/346/240/207.md +213 -213
  79. package/skills/microi-mobile-app-quality/references/progressive-02-9-/344/270/273/351/242/230/345/210/207/346/215/242/345/277/205/351/241/273/347/234/237/345/256/236/344/270/224/345/205/250/345/261/200/347/224/237/346/225/210.md +128 -128
  80. package/skills/microi-solution-quotation/SKILL.md +78 -78
  81. package/skills/microi-solution-quotation/agents/openai.yaml +4 -4
  82. package/skills/microi-solution-quotation/scripts/build_solution_quote.py +296 -296
  83. package/skills/microi-sso/SKILL.md +96 -90
  84. package/skills/microi-sso/references/acceptance.md +49 -49
  85. package/skills/microi-sso/references/configuration-and-security.md +53 -53
  86. package/skills/microi-sso/references/inbound.md +53 -53
  87. package/skills/microi-sso/references/outbound.md +39 -39
  88. package/skills/microi-system-delivery/SKILL.md +137 -137
  89. package/skills/microi-system-delivery/references/progressive-01-/346/240/207/345/207/206/345/267/245/344/275/234/346/265/201.md +193 -193
  90. package/skills/microi-system-delivery/references/progressive-02-/350/207/252/345/212/250/345/214/226/346/265/213/350/257/225/345/277/205/351/241/273/350/246/206/347/233/226/347/232/204/345/235/221.md +217 -217
  91. package/skills/microi-ui/SKILL.md +192 -192
  92. package/skills/microi-ui/references/progressive-01-/347/247/273/345/212/250/347/253/257/345/234/272/346/231/257/350/223/235/345/233/276.md +183 -183
  93. package/skills/microi-uniapp-frontend/SKILL.md +193 -193
  94. package/skills/microi-uniapp-frontend/references/progressive-01-/347/247/273/345/212/250/347/253/257/345/210/206/347/261/273-/345/217/214/346/240/217/345/210/227/350/241/250/347/213/254/347/253/213/346/273/232/345/212/250.md +225 -225
  95. package/skills/microi-uniapp-frontend/references/progressive-02-/345/205/263/351/224/256/344/270/232/345/212/241/350/265/204/344/272/247/344/270/215/345/276/227/351/273/230/350/256/244/351/200/211/344/270/255.md +154 -154
  96. package/skills/microi.v8.js +1921 -1921
  97. package/skills/module-engine/SKILL.md +255 -255
  98. package/skills/module-engine/references/module-config.md +204 -204
  99. package/skills/ocr-engine/SKILL.md +113 -113
  100. package/skills/ocr-engine/agents/openai.yaml +4 -4
  101. package/skills/page-engine/SKILL.md +206 -206
  102. package/skills/page-engine/examples/compact-dashboard.json +1444 -1444
  103. package/skills/page-engine/references/progressive-01-/346/211/200/346/234/211/347/273/204/344/273/266/347/261/273/345/236/213.md +234 -234
  104. package/skills/page-engine/references/progressive-02-/347/211/210/346/234/254/345/216/206/345/217/262-/345/271/266/345/217/221/344/277/235/345/255/230/344/270/216/345/233/236/346/273/232.md +60 -60
  105. package/skills/performance-testing/SKILL.md +221 -221
  106. package/skills/playwright-e2e/SKILL.md +196 -196
  107. package/skills/playwright-e2e/references/progressive-01-/345/205/250/350/207/252/345/212/250/347/231/273/345/275/225-/345/205/215/351/252/214/350/257/201/347/240/201-/344/275/206/344/270/215/345/205/215/345/257/206/347/240/201-/345/277/205/350/257/273.md +173 -173
  108. package/skills/playwright-e2e/references/progressive-02-/346/226/207/345/255/227/345/257/271/346/257/224/345/272/246/344/270/216/345/217/257/350/257/273/346/200/247/350/207/252/345/212/250/345/214/226/346/243/200/346/237/245-/345/277/205/345/201/232.md +183 -183
  109. package/skills/playwright-e2e/references/progressive-03-microi-helper-/346/250/241/346/235/277.md +221 -221
  110. package/skills/playwright-e2e/references/progressive-04-ci-/345/273/272/350/256/256.md +115 -115
  111. package/skills/print-engine/SKILL.md +259 -259
  112. package/skills/production-readonly-audit/SKILL.md +41 -41
  113. package/skills/report-engine/SKILL.md +71 -71
  114. package/skills/report-engine/agents/openai.yaml +4 -4
  115. package/skills/scripts/optimize-progressive-disclosure.mjs +204 -204
  116. package/skills/scripts/refresh-progressive-disclosure.mjs +64 -64
  117. package/skills/scripts/sync-embedded-skills.mjs +46 -46
  118. package/skills/scripts/validate-progressive-disclosure.mjs +57 -57
  119. package/skills/search-engine/SKILL.md +75 -75
  120. package/skills/search-engine/agents/openai.yaml +4 -4
  121. package/skills/spider-engine/SKILL.md +190 -190
  122. package/skills/system-observability/SKILL.md +249 -246
  123. package/skills/system-observability/references/memory-incident-triage.md +77 -77
  124. package/skills/translate-engine/SKILL.md +140 -140
  125. package/skills/translate-engine/agents/openai.yaml +4 -4
  126. package/skills/ui-design/SKILL.md +223 -223
  127. package/skills/ui-design/assets/pattern-showcase/app.js +54 -54
  128. package/skills/ui-design/assets/pattern-showcase/index.html +163 -163
  129. package/skills/ui-design/assets/pattern-showcase/styles.css +311 -311
  130. package/skills/ui-design/assets/templates/MCI-DESIGN.md +206 -206
  131. package/skills/ui-design/references/design-pattern-library.md +184 -184
  132. package/skills/ui-design/references/mci-design-contract.md +163 -163
  133. package/skills/ui-design/references/motion-and-media.md +78 -78
  134. package/skills/ui-design/references/product-flow-recipes.md +94 -94
  135. package/skills/ui-design/references/progressive-01-/351/242/234/350/211/262/344/275/223/347/263/273-css-variables-/346/224/257/346/214/201/344/270/273/351/242/230/345/210/207/346/215/242.md +218 -218
  136. package/skills/ui-design/references/progressive-02-/345/255/227/344/275/223.md +164 -164
  137. package/skills/ui-design/references/progressive-03-/345/212/250/346/225/210/350/247/204/350/214/203-/344/270/260/345/257/214/344/275/206/344/270/215/345/215/241.md +235 -235
  138. package/skills/ui-design/references/progressive-04-/347/273/204/344/273/266/351/243/216/346/240/274/351/200/237/346/237/245.md +152 -152
  139. package/skills/ui-design/references/progressive-05-/347/247/273/345/212/250/347/253/257/344/270/223/347/224/250/350/247/204/350/214/203.md +238 -238
  140. package/skills/ui-design/references/progressive-06-/344/270/273/351/242/230/345/210/207/346/215/242/345/256/236/347/216/260.md +194 -194
  141. package/skills/ui-design/references/progressive-07-/351/200/237/346/237/245-/344/273/216/345/244/264/346/220/255/345/273/272/344/270/200/344/270/252/347/247/273/345/212/250/347/253/257/351/241/265/351/235/242.md +207 -207
  142. package/skills/ui-design/references/progressive-08-/350/241/250/345/215/225/345/210/206/347/273/204/350/247/204/350/214/203-tabs-vs-collapsegroup-/345/274/272/345/210/266.md +142 -142
  143. package/skills/uniapp-mall-assets/SKILL.md +176 -176
  144. package/skills/unity-integration/SKILL.md +171 -171
  145. package/skills/unity-integration/agents/openai.yaml +4 -4
  146. package/skills/unity-integration/references/ai-app-delivery.md +119 -119
  147. package/skills/unity-integration/references/sdk-api.md +82 -82
  148. package/skills/unity-integration/references/toolbox-migration.md +66 -66
  149. package/skills/unity-integration/references/webgl-hosting.md +57 -57
  150. package/skills/v8-api-config/SKILL.md +388 -388
  151. package/skills/v8-cache-pattern/SKILL.md +312 -312
  152. package/skills/v8-crud-api/SKILL.md +178 -178
  153. package/skills/v8-crud-api/references/progressive-01-/346/237/245/350/257/242/345/210/227/350/241/250-/345/210/206/351/241/265.md +226 -226
  154. package/skills/v8-crud-api/references/progressive-02-where-/346/235/241/344/273/266/350/257/255/346/263/225/351/200/237/346/237/245.md +49 -49
  155. package/skills/v8-debugging/SKILL.md +284 -284
  156. package/skills/v8-explorer-tree/SKILL.md +228 -228
  157. package/skills/v8-export-import/SKILL.md +219 -219
  158. package/skills/v8-export-import/references/progressive-01-excellayout-/351/253/230/347/272/247/350/207/252/347/224/261/345/270/203/345/261/200.md +211 -211
  159. package/skills/v8-export-import/references/progressive-02-powerpoint-/345/257/274/345/207/272.md +202 -202
  160. package/skills/v8-export-import/references/progressive-03-/345/256/211/345/205/250-/346/200/247/350/203/275/346/263/250/346/204/217.md +42 -42
  161. package/skills/v8-file-upload/SKILL.md +284 -284
  162. package/skills/v8-file-upload/references/progressive-01-/345/205/254/346/234/211/346/241/266-vs-/347/247/201/346/234/211/346/241/266.md +263 -263
  163. package/skills/v8-file-upload/references/progressive-02-office-/346/226/207/344/273/266/345/234/250/347/272/277/347/274/226/350/276/221/347/211/210/346/234/254/345/217/267/350/247/204/345/210/231.md +161 -161
  164. package/skills/v8-formengine-http/SKILL.md +238 -238
  165. package/skills/v8-frontend-events/SKILL.md +180 -180
  166. package/skills/v8-frontend-events/references/bluetooth-print-api.md +135 -135
  167. package/skills/v8-frontend-events/references/bluetooth-print.md +258 -258
  168. package/skills/v8-frontend-events/references/progressive-01-/345/210/227/350/241/250/344/272/213/344/273/266.md +219 -219
  169. package/skills/v8-http-integration/SKILL.md +182 -182
  170. package/skills/v8-http-integration/references/progressive-01-get-/350/257/267/346/261/202.md +220 -220
  171. package/skills/v8-http-integration/references/progressive-02-/351/224/231/350/257/257/345/244/204/347/220/206/346/250/241/345/274/217.md +44 -44
  172. package/skills/v8-image-processing/SKILL.md +190 -190
  173. package/skills/v8-image-processing/agents/openai.yaml +4 -4
  174. package/skills/v8-image-processing/references/api-reference.md +623 -623
  175. package/skills/v8-menu-buttons/SKILL.md +186 -186
  176. package/skills/v8-menu-buttons/references/progressive-01-2-/346/214/211/351/222/256/345/257/271/350/261/241-schema.md +222 -222
  177. package/skills/v8-menu-buttons/references/progressive-02-8-/346/250/241/345/274/217-f-/345/220/216/345/217/260/344/273/273/345/212/241/346/214/211/351/222/256-/351/225/277/344/273/273/345/212/241.md +228 -228
  178. package/skills/v8-menu-buttons/references/progressive-03-10-/345/217/215/346/250/241/345/274/217-/351/201/277/345/205/215.md +104 -104
  179. package/skills/v8-mongodb/SKILL.md +200 -200
  180. package/skills/v8-mq-mqtt/SKILL.md +176 -176
  181. package/skills/v8-mq-mqtt/references/mqtt-production.md +342 -342
  182. package/skills/v8-mq-mqtt/references/progressive-01-v8-mqtt-iot-/347/211/251/350/201/224/347/275/221.md +181 -181
  183. package/skills/v8-mq-mqtt/scripts/check-mqtt-skill-coverage.mjs +203 -203
  184. package/skills/v8-saas-multi-tenant/SKILL.md +305 -305
  185. package/skills/v8-security/SKILL.md +210 -210
  186. package/skills/v8-security/references/progressive-01-2-/346/235/203/351/231/220/346/240/241/351/252/214.md +200 -200
  187. package/skills/v8-security/references/progressive-02-7-/346/227/245/345/277/227/350/256/260/345/275/225.md +160 -160
  188. package/skills/v8-sql-query/SKILL.md +302 -302
  189. package/skills/v8-table-event/SKILL.md +176 -176
  190. package/skills/v8-table-event/references/progressive-01-informv8-js-/350/241/250/345/215/225/346/211/223/345/274/200/344/272/213/344/273/266.md +216 -216
  191. package/skills/v8-table-event/references/progressive-02-/345/211/215/347/253/257/344/272/213/344/273/266/345/220/215-v8-eventname-/345/217/257/350/203/275/347/232/204/345/200/274.md +46 -46
  192. package/skills/v8-tcp-integration/SKILL.md +147 -147
  193. package/skills/v8-tcp-integration/agents/openai.yaml +4 -4
  194. package/skills/v8-template-engine/SKILL.md +167 -167
  195. package/skills/v8-utilities/SKILL.md +104 -104
  196. package/skills/v8-utilities/references/client-api-index.md +143 -143
  197. package/skills/v8-utilities/references/platform-http-routes.md +83 -83
  198. package/skills/v8-utilities/references/server-api-index.md +188 -188
  199. package/skills/v8-workflow/SKILL.md +252 -252
  200. package/skills/v8-workflow/references/progressive-01-/350/212/202/347/202/271/345/274/200/345/247/213-v8-/344/272/213/344/273/266.md +180 -180
  201. package/skills/v8-workflow/references/workflow-configuration.md +49 -49
  202. package/skills/vision-engine/SKILL.md +160 -160
  203. package/skills/vision-engine/agents/openai.yaml +4 -4
  204. package/skills/vision-engine/references/architecture-and-acceptance.md +194 -194
  205. package/skills/workspace-conventions/SKILL.md +273 -273
  206. package/skills/workspace-conventions/references/progressive-01-/347/211/210/346/234/254/346/233/264/346/226/260/346/227/245/345/277/227/344/277/235/346/212/244/350/247/204/345/210/231-/345/274/272/345/210/266.md +209 -209
  207. package/skills/workspace-conventions/references/progressive-02-microi-net-api-/346/234/254/345/234/260/345/220/257/345/212/250/347/272/246/345/256/232.md +217 -217
  208. package/skills/workspace-conventions/references/progressive-03-cli-/344/270/216-ide-/346/217/222/344/273/266/351/224/231/347/211/210/345/205/261/345/255/230/347/272/246/345/256/232.md +27 -27
@@ -1,450 +1,450 @@
1
- #!/usr/bin/env node
2
- /*
3
- * Shared Microi Agentx MCP broker.
4
- *
5
- * Every Codex task still owns a tiny stdio router, but all routers for the same
6
- * workspace and tool version share this broker. The broker owns at most one
7
- * real MCP child per connection profile, uses an exclusive lease to survive
8
- * concurrent starts, and exits after the last router has been idle.
9
- */
10
-
11
- 'use strict';
12
-
13
- const childProcess = require('node:child_process');
14
- const crypto = require('node:crypto');
15
- const fs = require('node:fs');
16
- const net = require('node:net');
17
- const os = require('node:os');
18
- const path = require('node:path');
19
-
20
- const PROTOCOL_VERSION = '2024-11-05';
21
- const endpoint = String(process.env.MICROI_CODEX_BROKER_ENDPOINT || '');
22
- const leasePath = path.resolve(String(process.env.MICROI_CODEX_BROKER_LEASE || ''));
23
- const workspaceRoot = path.resolve(String(process.env.MICROI_WORKSPACE_ROOT || process.cwd()));
24
- const mcpServerPath = path.resolve(String(process.env.MICROI_MCP_SERVER_PATH || path.join(__dirname, 'mcp-server.js')));
25
- const toolVersion = String(process.env.MICROI_TOOL_VERSION || '0.0.0');
26
- const idleMilliseconds = Math.max(250, Number(process.env.MICROI_CODEX_BROKER_IDLE_MS || 30000));
27
- const heartbeatMilliseconds = Math.max(500, Number(process.env.MICROI_CODEX_BROKER_HEARTBEAT_MS || 5000));
28
- const staleMilliseconds = Math.max(heartbeatMilliseconds * 3, Number(process.env.MICROI_CODEX_BROKER_STALE_MS || 20000));
29
-
30
- if (!endpoint || !process.env.MICROI_CODEX_BROKER_LEASE) {
31
- process.stderr.write('[microi-codex-broker] Missing endpoint or lease path.\n');
32
- process.exit(1);
33
- }
34
-
35
- const ownerToken = crypto.randomBytes(24).toString('hex');
36
- const children = new Map();
37
- const clients = new Set();
38
- let requestSequence = 0;
39
- let leaseOwned = false;
40
- let shuttingDown = false;
41
- let idleTimer = null;
42
- let heartbeatTimer = null;
43
- let server = null;
44
-
45
- function processExists(pid) {
46
- if (!Number.isInteger(pid) || pid <= 1) return false;
47
- try {
48
- process.kill(pid, 0);
49
- return true;
50
- } catch (error) {
51
- return error?.code === 'EPERM';
52
- }
53
- }
54
-
55
- function readLease() {
56
- try {
57
- const parsed = JSON.parse(fs.readFileSync(leasePath, 'utf8'));
58
- return parsed && typeof parsed === 'object' ? parsed : null;
59
- } catch {
60
- return null;
61
- }
62
- }
63
-
64
- function leaseRecord() {
65
- return {
66
- pid: process.pid,
67
- token: ownerToken,
68
- endpoint,
69
- workspaceRoot,
70
- toolVersion,
71
- startedAt: startedAtIso,
72
- heartbeatAt: new Date().toISOString(),
73
- };
74
- }
75
-
76
- function writeLeaseRecord(record, exclusive = false) {
77
- fs.mkdirSync(path.dirname(leasePath), { recursive: true });
78
- const text = `${JSON.stringify(record, null, 2)}\n`;
79
- if (exclusive) {
80
- // Publish a complete lease atomically. Creating leasePath first and then
81
- // writing its JSON leaves a short empty-file window; concurrent brokers can
82
- // mistake that window for a stale lease, remove it, and leave connected
83
- // routers without an authentication token. A same-directory hard link is
84
- // both exclusive (EEXIST wins the race) and immediately complete.
85
- const staged = `${leasePath}.${process.pid}.${Date.now()}.${ownerToken.slice(0, 8)}.new`;
86
- try {
87
- fs.writeFileSync(staged, text, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
88
- fs.linkSync(staged, leasePath);
89
- } finally {
90
- try { if (fs.existsSync(staged)) fs.rmSync(staged, { force: true }); } catch { /* harmless */ }
91
- }
92
- return;
93
- }
94
- const current = readLease();
95
- if (!current || current.token !== ownerToken) throw new Error('Microi Agentx broker lease ownership changed');
96
- const staged = `${leasePath}.${process.pid}.${Date.now()}.tmp`;
97
- try {
98
- fs.writeFileSync(staged, text, { encoding: 'utf8', mode: 0o600 });
99
- fs.renameSync(staged, leasePath);
100
- } finally {
101
- try { if (fs.existsSync(staged)) fs.rmSync(staged, { force: true }); } catch { /* best effort */ }
102
- }
103
- }
104
-
105
- function acquireLease() {
106
- fs.mkdirSync(path.dirname(leasePath), { recursive: true });
107
- for (let attempt = 0; attempt < 4; attempt += 1) {
108
- try {
109
- writeLeaseRecord(leaseRecord(), true);
110
- leaseOwned = true;
111
- return true;
112
- } catch (error) {
113
- if (error?.code !== 'EEXIST') throw error;
114
- const existing = readLease();
115
- const heartbeat = Date.parse(String(existing?.heartbeatAt || ''));
116
- const fresh = Number.isFinite(heartbeat) && Date.now() - heartbeat < staleMilliseconds;
117
- if (existing && processExists(Number(existing.pid)) && fresh) return false;
118
- const stalePath = `${leasePath}.stale.${process.pid}.${Date.now()}`;
119
- try {
120
- fs.renameSync(leasePath, stalePath);
121
- try { fs.rmSync(stalePath, { force: true }); } catch { /* harmless */ }
122
- } catch (renameError) {
123
- if (!['ENOENT', 'EACCES', 'EPERM'].includes(renameError?.code)) throw renameError;
124
- }
125
- }
126
- }
127
- return false;
128
- }
129
-
130
- function releaseLease() {
131
- if (!leaseOwned) return;
132
- const current = readLease();
133
- if (current?.token === ownerToken) {
134
- try { fs.rmSync(leasePath, { force: true }); } catch { /* next owner can reclaim stale lease */ }
135
- }
136
- leaseOwned = false;
137
- }
138
-
139
- function safeText(value) {
140
- return String(value == null ? '' : value);
141
- }
142
-
143
- function profileIdentity(profile) {
144
- return safeText(profile?.identity) || [
145
- safeText(profile?.apiBaseUrl).replace(/\/+$/, ''),
146
- safeText(profile?.osClient).trim(),
147
- safeText(profile?.osClientType).trim(),
148
- safeText(profile?.osClientNetwork).trim(),
149
- ].join('|');
150
- }
151
-
152
- function workspaceSecretKey(baseKey, profileWorkspace) {
153
- const normalized = path.resolve(profileWorkspace).toLowerCase();
154
- const hash = crypto.createHash('sha256').update(normalized).digest('hex').slice(0, 16);
155
- return `${baseKey}.workspace.${hash}`;
156
- }
157
-
158
- function credentialSecretKey(baseKey, profile, profileWorkspace) {
159
- const apiBaseUrl = safeText(profile.apiBaseUrl).trim().replace(/\/+$/, '');
160
- const osClient = safeText(profile.osClient).trim();
161
- return workspaceSecretKey(`${baseKey}.${apiBaseUrl}|${osClient}`, profileWorkspace);
162
- }
163
-
164
- function validateProfile(raw) {
165
- const profile = raw && typeof raw === 'object' ? raw : {};
166
- const profileWorkspace = path.resolve(safeText(profile.workspaceRoot || workspaceRoot));
167
- if (process.platform === 'win32') {
168
- if (profileWorkspace.toLowerCase() !== workspaceRoot.toLowerCase()) throw new Error('Broker workspace mismatch');
169
- } else if (profileWorkspace !== workspaceRoot) {
170
- throw new Error('Broker workspace mismatch');
171
- }
172
- if (!/^https?:\/\//i.test(safeText(profile.apiBaseUrl))) throw new Error('Invalid Microi API URL');
173
- const syncRoot = path.resolve(safeText(profile.syncRoot || path.join(profileWorkspace, 'Microi-V8-Engine')));
174
- return {
175
- identity: profileIdentity(profile),
176
- name: safeText(profile.name || profile.osClient || 'microi'),
177
- label: safeText(profile.label),
178
- apiBaseUrl: safeText(profile.apiBaseUrl).replace(/\/+$/, ''),
179
- osClient: safeText(profile.osClient).trim(),
180
- osClientType: safeText(profile.osClientType).trim(),
181
- osClientNetwork: safeText(profile.osClientNetwork).trim(),
182
- workspaceRoot: profileWorkspace,
183
- syncRoot,
184
- tokenPath: path.resolve(safeText(profile.tokenPath || path.join(profileWorkspace, 'Microi-V8-Engine', '.microi-mcp-tokens.json'))),
185
- applicationsDir: path.resolve(safeText(profile.applicationsDir || path.join(profileWorkspace, 'Microi-V8-Engine'))),
186
- workspaceCredentialFilePath: path.join(syncRoot, '.microi-workspace-secrets.dpapi.json'),
187
- workspaceCredentialUsernameKey: credentialSecretKey('microi.username', profile, profileWorkspace),
188
- workspaceCredentialPasswordKey: credentialSecretKey('microi.password', profile, profileWorkspace),
189
- };
190
- }
191
-
192
- function deviceId() {
193
- const raw = `MCP:${os.hostname() || 'Unknown'}`;
194
- if (/^[\x20-\x7E]{1,128}$/.test(raw)) return raw;
195
- return `MCP-${crypto.createHash('sha256').update(raw).digest('hex').slice(0, 16)}`;
196
- }
197
-
198
- class ChildMcp {
199
- constructor(profile) {
200
- this.profile = profile;
201
- this.pending = new Map();
202
- this.buffer = '';
203
- this.stopping = false;
204
- this.ready = this.start();
205
- }
206
-
207
- async start() {
208
- if (!fs.existsSync(mcpServerPath)) throw new Error(`Microi MCP bundle does not exist: ${mcpServerPath}`);
209
- this.child = childProcess.spawn(process.execPath, [mcpServerPath], {
210
- cwd: this.profile.workspaceRoot,
211
- windowsHide: true,
212
- stdio: ['pipe', 'pipe', 'pipe'],
213
- env: {
214
- ...process.env,
215
- MICROI_API_URL: this.profile.apiBaseUrl,
216
- MICROI_TOKEN_FILE: this.profile.tokenPath,
217
- MICROI_AUTH_RECOVERY_DIR: `${this.profile.tokenPath}.recovery`,
218
- MICROI_WORKSPACE_CREDENTIAL_FILE: this.profile.workspaceCredentialFilePath,
219
- MICROI_WORKSPACE_USERNAME_KEY: this.profile.workspaceCredentialUsernameKey,
220
- MICROI_WORKSPACE_PASSWORD_KEY: this.profile.workspaceCredentialPasswordKey,
221
- MICROI_OS_CLIENT: this.profile.osClient,
222
- MICROI_OS_CLIENT_TYPE: this.profile.osClientType,
223
- MICROI_OS_CLIENT_NETWORK: this.profile.osClientNetwork,
224
- MICROI_WORKSPACE_ROOT: this.profile.workspaceRoot,
225
- MICROI_SYNC_ROOT: this.profile.syncRoot,
226
- MICROI_AI_APPLICATIONS_DIR: this.profile.applicationsDir,
227
- MICROI_LABEL_BASE64: Buffer.from(this.profile.label, 'utf8').toString('base64'),
228
- MICROI_MCP_DID: deviceId(),
229
- MICROI_TOOL_SOURCE: 'codex-broker',
230
- MICROI_TOOL_VERSION: toolVersion,
231
- MICROI_CODEX_MODE: '1',
232
- },
233
- });
234
- this.child.stdout.on('data', chunk => this.onData(chunk));
235
- this.child.stderr.on('data', chunk => process.stderr.write(`[${this.profile.name}] ${chunk}`));
236
- this.child.on('error', error => this.rejectAll(error));
237
- this.child.on('exit', code => {
238
- this.rejectAll(new Error(`Microi MCP ${this.profile.name} exited (${code ?? 'unknown'})`));
239
- if (!this.stopping && children.get(this.profile.identity) === this) children.delete(this.profile.identity);
240
- });
241
- await this.request('initialize', {
242
- protocolVersion: PROTOCOL_VERSION,
243
- capabilities: {},
244
- clientInfo: { name: 'microi-codex-shared-broker', version: toolVersion },
245
- }, true);
246
- this.notify('notifications/initialized', {});
247
- }
248
-
249
- onData(chunk) {
250
- this.buffer += chunk.toString('utf8');
251
- let newline;
252
- while ((newline = this.buffer.indexOf('\n')) >= 0) {
253
- const line = this.buffer.slice(0, newline).trim();
254
- this.buffer = this.buffer.slice(newline + 1);
255
- if (!line) continue;
256
- let message;
257
- try { message = JSON.parse(line); } catch { continue; }
258
- if (message.id == null) continue;
259
- const pending = this.pending.get(String(message.id));
260
- if (!pending) continue;
261
- this.pending.delete(String(message.id));
262
- if (message.error) pending.reject(new Error(message.error.message || JSON.stringify(message.error)));
263
- else pending.resolve(message.result);
264
- }
265
- }
266
-
267
- rejectAll(error) {
268
- for (const pending of this.pending.values()) pending.reject(error);
269
- this.pending.clear();
270
- }
271
-
272
- write(message) {
273
- if (!this.child || this.child.stdin.destroyed) throw new Error(`Microi MCP ${this.profile.name} is unavailable`);
274
- this.child.stdin.write(`${JSON.stringify(message)}\n`);
275
- }
276
-
277
- request(method, params, skipReady = false) {
278
- return (async () => {
279
- if (!skipReady) await this.ready;
280
- const id = `broker-${process.pid}-${++requestSequence}`;
281
- const response = new Promise((resolve, reject) => this.pending.set(id, { resolve, reject }));
282
- this.write({ jsonrpc: '2.0', id, method, params });
283
- return response;
284
- })();
285
- }
286
-
287
- notify(method, params) {
288
- this.write({ jsonrpc: '2.0', method, params });
289
- }
290
-
291
- stop() {
292
- this.stopping = true;
293
- try { this.child?.stdin.end(); } catch { /* best effort */ }
294
- try { this.child?.kill('SIGTERM'); } catch { /* best effort */ }
295
- const child = this.child;
296
- const forceTimer = setTimeout(() => {
297
- if (child && child.exitCode === null) {
298
- try { child.kill('SIGKILL'); } catch { /* best effort */ }
299
- }
300
- }, 2000);
301
- forceTimer.unref();
302
- }
303
- }
304
-
305
- function childFor(rawProfile) {
306
- const profile = validateProfile(rawProfile);
307
- const existing = children.get(profile.identity);
308
- if (existing) return existing;
309
- const child = new ChildMcp(profile);
310
- children.set(profile.identity, child);
311
- return child;
312
- }
313
-
314
- function send(socket, message) {
315
- if (!socket.destroyed) socket.write(`${JSON.stringify(message)}\n`);
316
- }
317
-
318
- async function handleMessage(socket, message) {
319
- const id = message?.id;
320
- if (id == null) return;
321
- if (message.auth !== ownerToken) {
322
- send(socket, { id, error: 'Unauthorized local broker client' });
323
- return;
324
- }
325
- try {
326
- if (message.type === 'status') {
327
- send(socket, {
328
- id,
329
- result: {
330
- pid: process.pid,
331
- workspaceRoot,
332
- toolVersion,
333
- clients: clients.size,
334
- childCount: children.size,
335
- childPids: [...children.values()].map(item => item.child?.pid).filter(Boolean),
336
- },
337
- });
338
- return;
339
- }
340
- if (message.type !== 'request') throw new Error('Unsupported broker request');
341
- const result = await childFor(message.profile).request(safeText(message.method), message.params || {});
342
- send(socket, { id, result });
343
- } catch (error) {
344
- send(socket, { id, error: error instanceof Error ? error.message : String(error) });
345
- }
346
- }
347
-
348
- function cancelIdleShutdown() {
349
- if (idleTimer) clearTimeout(idleTimer);
350
- idleTimer = null;
351
- }
352
-
353
- function scheduleIdleShutdown() {
354
- cancelIdleShutdown();
355
- if (clients.size > 0 || shuttingDown) return;
356
- idleTimer = setTimeout(() => shutdown(0), idleMilliseconds);
357
- }
358
-
359
- function acceptClient(socket) {
360
- cancelIdleShutdown();
361
- clients.add(socket);
362
- socket.setEncoding('utf8');
363
- let buffer = '';
364
- socket.on('data', chunk => {
365
- buffer += chunk;
366
- let newline;
367
- while ((newline = buffer.indexOf('\n')) >= 0) {
368
- const line = buffer.slice(0, newline).trim();
369
- buffer = buffer.slice(newline + 1);
370
- if (!line) continue;
371
- let message;
372
- try { message = JSON.parse(line); }
373
- catch { send(socket, { id: null, error: 'Invalid broker JSON' }); continue; }
374
- Promise.resolve(handleMessage(socket, message)).catch(() => { /* handled above */ });
375
- }
376
- });
377
- const close = () => {
378
- clients.delete(socket);
379
- scheduleIdleShutdown();
380
- };
381
- socket.once('close', close);
382
- socket.once('error', close);
383
- }
384
-
385
- function removeUnixSocket() {
386
- if (process.platform === 'win32') return;
387
- try { if (fs.existsSync(endpoint)) fs.rmSync(endpoint, { force: true }); } catch { /* stale socket is reclaimable */ }
388
- }
389
-
390
- function shutdown(exitCode = 0) {
391
- if (shuttingDown) return;
392
- shuttingDown = true;
393
- cancelIdleShutdown();
394
- if (heartbeatTimer) clearInterval(heartbeatTimer);
395
- for (const socket of clients) {
396
- try { socket.end(); } catch { /* best effort */ }
397
- }
398
- clients.clear();
399
- for (const child of children.values()) child.stop();
400
- children.clear();
401
- const finish = () => {
402
- releaseLease();
403
- removeUnixSocket();
404
- process.exit(exitCode);
405
- };
406
- if (server) {
407
- try { server.close(finish); } catch { finish(); }
408
- const forceTimer = setTimeout(finish, 2200);
409
- forceTimer.unref();
410
- } else {
411
- finish();
412
- }
413
- }
414
-
415
- const startedAtIso = new Date().toISOString();
416
- // Windows 命名管道无需目录;macOS/Linux 首次连接必须先创建 Unix socket 的父目录。
417
- // 在领取租约前完成,目录不可写时不会留下貌似存活的 Broker 租约。
418
- if (process.platform !== 'win32') fs.mkdirSync(path.dirname(endpoint), { recursive: true, mode: 0o700 });
419
- if (!acquireLease()) process.exit(0);
420
-
421
- removeUnixSocket();
422
- server = net.createServer(acceptClient);
423
- server.on('error', error => {
424
- process.stderr.write(`[microi-codex-broker] ${error.message}\n`);
425
- shutdown(1);
426
- });
427
- server.listen(endpoint, () => {
428
- if (process.platform !== 'win32') {
429
- try { fs.chmodSync(endpoint, 0o600); } catch { /* best effort */ }
430
- }
431
- heartbeatTimer = setInterval(() => {
432
- try { writeLeaseRecord(leaseRecord()); } catch { shutdown(1); }
433
- }, heartbeatMilliseconds);
434
- scheduleIdleShutdown();
435
- });
436
-
437
- for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) process.on(signal, () => shutdown(0));
438
- process.on('uncaughtException', error => {
439
- process.stderr.write(`[microi-codex-broker] ${error?.stack || error}\n`);
440
- shutdown(1);
441
- });
442
- process.on('exit', () => {
443
- for (const child of children.values()) {
444
- try { if (child.child?.exitCode === null) child.child.kill('SIGKILL'); } catch { /* best effort */ }
445
- }
446
- if (leaseOwned) {
447
- releaseLease();
448
- removeUnixSocket();
449
- }
450
- });
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Shared Microi Agentx MCP broker.
4
+ *
5
+ * Every Codex task still owns a tiny stdio router, but all routers for the same
6
+ * workspace and tool version share this broker. The broker owns at most one
7
+ * real MCP child per connection profile, uses an exclusive lease to survive
8
+ * concurrent starts, and exits after the last router has been idle.
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ const childProcess = require('node:child_process');
14
+ const crypto = require('node:crypto');
15
+ const fs = require('node:fs');
16
+ const net = require('node:net');
17
+ const os = require('node:os');
18
+ const path = require('node:path');
19
+
20
+ const PROTOCOL_VERSION = '2024-11-05';
21
+ const endpoint = String(process.env.MICROI_CODEX_BROKER_ENDPOINT || '');
22
+ const leasePath = path.resolve(String(process.env.MICROI_CODEX_BROKER_LEASE || ''));
23
+ const workspaceRoot = path.resolve(String(process.env.MICROI_WORKSPACE_ROOT || process.cwd()));
24
+ const mcpServerPath = path.resolve(String(process.env.MICROI_MCP_SERVER_PATH || path.join(__dirname, 'mcp-server.js')));
25
+ const toolVersion = String(process.env.MICROI_TOOL_VERSION || '0.0.0');
26
+ const idleMilliseconds = Math.max(250, Number(process.env.MICROI_CODEX_BROKER_IDLE_MS || 30000));
27
+ const heartbeatMilliseconds = Math.max(500, Number(process.env.MICROI_CODEX_BROKER_HEARTBEAT_MS || 5000));
28
+ const staleMilliseconds = Math.max(heartbeatMilliseconds * 3, Number(process.env.MICROI_CODEX_BROKER_STALE_MS || 20000));
29
+
30
+ if (!endpoint || !process.env.MICROI_CODEX_BROKER_LEASE) {
31
+ process.stderr.write('[microi-codex-broker] Missing endpoint or lease path.\n');
32
+ process.exit(1);
33
+ }
34
+
35
+ const ownerToken = crypto.randomBytes(24).toString('hex');
36
+ const children = new Map();
37
+ const clients = new Set();
38
+ let requestSequence = 0;
39
+ let leaseOwned = false;
40
+ let shuttingDown = false;
41
+ let idleTimer = null;
42
+ let heartbeatTimer = null;
43
+ let server = null;
44
+
45
+ function processExists(pid) {
46
+ if (!Number.isInteger(pid) || pid <= 1) return false;
47
+ try {
48
+ process.kill(pid, 0);
49
+ return true;
50
+ } catch (error) {
51
+ return error?.code === 'EPERM';
52
+ }
53
+ }
54
+
55
+ function readLease() {
56
+ try {
57
+ const parsed = JSON.parse(fs.readFileSync(leasePath, 'utf8'));
58
+ return parsed && typeof parsed === 'object' ? parsed : null;
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+
64
+ function leaseRecord() {
65
+ return {
66
+ pid: process.pid,
67
+ token: ownerToken,
68
+ endpoint,
69
+ workspaceRoot,
70
+ toolVersion,
71
+ startedAt: startedAtIso,
72
+ heartbeatAt: new Date().toISOString(),
73
+ };
74
+ }
75
+
76
+ function writeLeaseRecord(record, exclusive = false) {
77
+ fs.mkdirSync(path.dirname(leasePath), { recursive: true });
78
+ const text = `${JSON.stringify(record, null, 2)}\n`;
79
+ if (exclusive) {
80
+ // Publish a complete lease atomically. Creating leasePath first and then
81
+ // writing its JSON leaves a short empty-file window; concurrent brokers can
82
+ // mistake that window for a stale lease, remove it, and leave connected
83
+ // routers without an authentication token. A same-directory hard link is
84
+ // both exclusive (EEXIST wins the race) and immediately complete.
85
+ const staged = `${leasePath}.${process.pid}.${Date.now()}.${ownerToken.slice(0, 8)}.new`;
86
+ try {
87
+ fs.writeFileSync(staged, text, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
88
+ fs.linkSync(staged, leasePath);
89
+ } finally {
90
+ try { if (fs.existsSync(staged)) fs.rmSync(staged, { force: true }); } catch { /* harmless */ }
91
+ }
92
+ return;
93
+ }
94
+ const current = readLease();
95
+ if (!current || current.token !== ownerToken) throw new Error('Microi Agentx broker lease ownership changed');
96
+ const staged = `${leasePath}.${process.pid}.${Date.now()}.tmp`;
97
+ try {
98
+ fs.writeFileSync(staged, text, { encoding: 'utf8', mode: 0o600 });
99
+ fs.renameSync(staged, leasePath);
100
+ } finally {
101
+ try { if (fs.existsSync(staged)) fs.rmSync(staged, { force: true }); } catch { /* best effort */ }
102
+ }
103
+ }
104
+
105
+ function acquireLease() {
106
+ fs.mkdirSync(path.dirname(leasePath), { recursive: true });
107
+ for (let attempt = 0; attempt < 4; attempt += 1) {
108
+ try {
109
+ writeLeaseRecord(leaseRecord(), true);
110
+ leaseOwned = true;
111
+ return true;
112
+ } catch (error) {
113
+ if (error?.code !== 'EEXIST') throw error;
114
+ const existing = readLease();
115
+ const heartbeat = Date.parse(String(existing?.heartbeatAt || ''));
116
+ const fresh = Number.isFinite(heartbeat) && Date.now() - heartbeat < staleMilliseconds;
117
+ if (existing && processExists(Number(existing.pid)) && fresh) return false;
118
+ const stalePath = `${leasePath}.stale.${process.pid}.${Date.now()}`;
119
+ try {
120
+ fs.renameSync(leasePath, stalePath);
121
+ try { fs.rmSync(stalePath, { force: true }); } catch { /* harmless */ }
122
+ } catch (renameError) {
123
+ if (!['ENOENT', 'EACCES', 'EPERM'].includes(renameError?.code)) throw renameError;
124
+ }
125
+ }
126
+ }
127
+ return false;
128
+ }
129
+
130
+ function releaseLease() {
131
+ if (!leaseOwned) return;
132
+ const current = readLease();
133
+ if (current?.token === ownerToken) {
134
+ try { fs.rmSync(leasePath, { force: true }); } catch { /* next owner can reclaim stale lease */ }
135
+ }
136
+ leaseOwned = false;
137
+ }
138
+
139
+ function safeText(value) {
140
+ return String(value == null ? '' : value);
141
+ }
142
+
143
+ function profileIdentity(profile) {
144
+ return safeText(profile?.identity) || [
145
+ safeText(profile?.apiBaseUrl).replace(/\/+$/, ''),
146
+ safeText(profile?.osClient).trim(),
147
+ safeText(profile?.osClientType).trim(),
148
+ safeText(profile?.osClientNetwork).trim(),
149
+ ].join('|');
150
+ }
151
+
152
+ function workspaceSecretKey(baseKey, profileWorkspace) {
153
+ const normalized = path.resolve(profileWorkspace).toLowerCase();
154
+ const hash = crypto.createHash('sha256').update(normalized).digest('hex').slice(0, 16);
155
+ return `${baseKey}.workspace.${hash}`;
156
+ }
157
+
158
+ function credentialSecretKey(baseKey, profile, profileWorkspace) {
159
+ const apiBaseUrl = safeText(profile.apiBaseUrl).trim().replace(/\/+$/, '');
160
+ const osClient = safeText(profile.osClient).trim();
161
+ return workspaceSecretKey(`${baseKey}.${apiBaseUrl}|${osClient}`, profileWorkspace);
162
+ }
163
+
164
+ function validateProfile(raw) {
165
+ const profile = raw && typeof raw === 'object' ? raw : {};
166
+ const profileWorkspace = path.resolve(safeText(profile.workspaceRoot || workspaceRoot));
167
+ if (process.platform === 'win32') {
168
+ if (profileWorkspace.toLowerCase() !== workspaceRoot.toLowerCase()) throw new Error('Broker workspace mismatch');
169
+ } else if (profileWorkspace !== workspaceRoot) {
170
+ throw new Error('Broker workspace mismatch');
171
+ }
172
+ if (!/^https?:\/\//i.test(safeText(profile.apiBaseUrl))) throw new Error('Invalid Microi API URL');
173
+ const syncRoot = path.resolve(safeText(profile.syncRoot || path.join(profileWorkspace, 'Microi-V8-Engine')));
174
+ return {
175
+ identity: profileIdentity(profile),
176
+ name: safeText(profile.name || profile.osClient || 'microi'),
177
+ label: safeText(profile.label),
178
+ apiBaseUrl: safeText(profile.apiBaseUrl).replace(/\/+$/, ''),
179
+ osClient: safeText(profile.osClient).trim(),
180
+ osClientType: safeText(profile.osClientType).trim(),
181
+ osClientNetwork: safeText(profile.osClientNetwork).trim(),
182
+ workspaceRoot: profileWorkspace,
183
+ syncRoot,
184
+ tokenPath: path.resolve(safeText(profile.tokenPath || path.join(profileWorkspace, 'Microi-V8-Engine', '.microi-mcp-tokens.json'))),
185
+ applicationsDir: path.resolve(safeText(profile.applicationsDir || path.join(profileWorkspace, 'Microi-V8-Engine'))),
186
+ workspaceCredentialFilePath: path.join(syncRoot, '.microi-workspace-secrets.dpapi.json'),
187
+ workspaceCredentialUsernameKey: credentialSecretKey('microi.username', profile, profileWorkspace),
188
+ workspaceCredentialPasswordKey: credentialSecretKey('microi.password', profile, profileWorkspace),
189
+ };
190
+ }
191
+
192
+ function deviceId() {
193
+ const raw = `MCP:${os.hostname() || 'Unknown'}`;
194
+ if (/^[\x20-\x7E]{1,128}$/.test(raw)) return raw;
195
+ return `MCP-${crypto.createHash('sha256').update(raw).digest('hex').slice(0, 16)}`;
196
+ }
197
+
198
+ class ChildMcp {
199
+ constructor(profile) {
200
+ this.profile = profile;
201
+ this.pending = new Map();
202
+ this.buffer = '';
203
+ this.stopping = false;
204
+ this.ready = this.start();
205
+ }
206
+
207
+ async start() {
208
+ if (!fs.existsSync(mcpServerPath)) throw new Error(`Microi MCP bundle does not exist: ${mcpServerPath}`);
209
+ this.child = childProcess.spawn(process.execPath, [mcpServerPath], {
210
+ cwd: this.profile.workspaceRoot,
211
+ windowsHide: true,
212
+ stdio: ['pipe', 'pipe', 'pipe'],
213
+ env: {
214
+ ...process.env,
215
+ MICROI_API_URL: this.profile.apiBaseUrl,
216
+ MICROI_TOKEN_FILE: this.profile.tokenPath,
217
+ MICROI_AUTH_RECOVERY_DIR: `${this.profile.tokenPath}.recovery`,
218
+ MICROI_WORKSPACE_CREDENTIAL_FILE: this.profile.workspaceCredentialFilePath,
219
+ MICROI_WORKSPACE_USERNAME_KEY: this.profile.workspaceCredentialUsernameKey,
220
+ MICROI_WORKSPACE_PASSWORD_KEY: this.profile.workspaceCredentialPasswordKey,
221
+ MICROI_OS_CLIENT: this.profile.osClient,
222
+ MICROI_OS_CLIENT_TYPE: this.profile.osClientType,
223
+ MICROI_OS_CLIENT_NETWORK: this.profile.osClientNetwork,
224
+ MICROI_WORKSPACE_ROOT: this.profile.workspaceRoot,
225
+ MICROI_SYNC_ROOT: this.profile.syncRoot,
226
+ MICROI_AI_APPLICATIONS_DIR: this.profile.applicationsDir,
227
+ MICROI_LABEL_BASE64: Buffer.from(this.profile.label, 'utf8').toString('base64'),
228
+ MICROI_MCP_DID: deviceId(),
229
+ MICROI_TOOL_SOURCE: 'codex-broker',
230
+ MICROI_TOOL_VERSION: toolVersion,
231
+ MICROI_CODEX_MODE: '1',
232
+ },
233
+ });
234
+ this.child.stdout.on('data', chunk => this.onData(chunk));
235
+ this.child.stderr.on('data', chunk => process.stderr.write(`[${this.profile.name}] ${chunk}`));
236
+ this.child.on('error', error => this.rejectAll(error));
237
+ this.child.on('exit', code => {
238
+ this.rejectAll(new Error(`Microi MCP ${this.profile.name} exited (${code ?? 'unknown'})`));
239
+ if (!this.stopping && children.get(this.profile.identity) === this) children.delete(this.profile.identity);
240
+ });
241
+ await this.request('initialize', {
242
+ protocolVersion: PROTOCOL_VERSION,
243
+ capabilities: {},
244
+ clientInfo: { name: 'microi-codex-shared-broker', version: toolVersion },
245
+ }, true);
246
+ this.notify('notifications/initialized', {});
247
+ }
248
+
249
+ onData(chunk) {
250
+ this.buffer += chunk.toString('utf8');
251
+ let newline;
252
+ while ((newline = this.buffer.indexOf('\n')) >= 0) {
253
+ const line = this.buffer.slice(0, newline).trim();
254
+ this.buffer = this.buffer.slice(newline + 1);
255
+ if (!line) continue;
256
+ let message;
257
+ try { message = JSON.parse(line); } catch { continue; }
258
+ if (message.id == null) continue;
259
+ const pending = this.pending.get(String(message.id));
260
+ if (!pending) continue;
261
+ this.pending.delete(String(message.id));
262
+ if (message.error) pending.reject(new Error(message.error.message || JSON.stringify(message.error)));
263
+ else pending.resolve(message.result);
264
+ }
265
+ }
266
+
267
+ rejectAll(error) {
268
+ for (const pending of this.pending.values()) pending.reject(error);
269
+ this.pending.clear();
270
+ }
271
+
272
+ write(message) {
273
+ if (!this.child || this.child.stdin.destroyed) throw new Error(`Microi MCP ${this.profile.name} is unavailable`);
274
+ this.child.stdin.write(`${JSON.stringify(message)}\n`);
275
+ }
276
+
277
+ request(method, params, skipReady = false) {
278
+ return (async () => {
279
+ if (!skipReady) await this.ready;
280
+ const id = `broker-${process.pid}-${++requestSequence}`;
281
+ const response = new Promise((resolve, reject) => this.pending.set(id, { resolve, reject }));
282
+ this.write({ jsonrpc: '2.0', id, method, params });
283
+ return response;
284
+ })();
285
+ }
286
+
287
+ notify(method, params) {
288
+ this.write({ jsonrpc: '2.0', method, params });
289
+ }
290
+
291
+ stop() {
292
+ this.stopping = true;
293
+ try { this.child?.stdin.end(); } catch { /* best effort */ }
294
+ try { this.child?.kill('SIGTERM'); } catch { /* best effort */ }
295
+ const child = this.child;
296
+ const forceTimer = setTimeout(() => {
297
+ if (child && child.exitCode === null) {
298
+ try { child.kill('SIGKILL'); } catch { /* best effort */ }
299
+ }
300
+ }, 2000);
301
+ forceTimer.unref();
302
+ }
303
+ }
304
+
305
+ function childFor(rawProfile) {
306
+ const profile = validateProfile(rawProfile);
307
+ const existing = children.get(profile.identity);
308
+ if (existing) return existing;
309
+ const child = new ChildMcp(profile);
310
+ children.set(profile.identity, child);
311
+ return child;
312
+ }
313
+
314
+ function send(socket, message) {
315
+ if (!socket.destroyed) socket.write(`${JSON.stringify(message)}\n`);
316
+ }
317
+
318
+ async function handleMessage(socket, message) {
319
+ const id = message?.id;
320
+ if (id == null) return;
321
+ if (message.auth !== ownerToken) {
322
+ send(socket, { id, error: 'Unauthorized local broker client' });
323
+ return;
324
+ }
325
+ try {
326
+ if (message.type === 'status') {
327
+ send(socket, {
328
+ id,
329
+ result: {
330
+ pid: process.pid,
331
+ workspaceRoot,
332
+ toolVersion,
333
+ clients: clients.size,
334
+ childCount: children.size,
335
+ childPids: [...children.values()].map(item => item.child?.pid).filter(Boolean),
336
+ },
337
+ });
338
+ return;
339
+ }
340
+ if (message.type !== 'request') throw new Error('Unsupported broker request');
341
+ const result = await childFor(message.profile).request(safeText(message.method), message.params || {});
342
+ send(socket, { id, result });
343
+ } catch (error) {
344
+ send(socket, { id, error: error instanceof Error ? error.message : String(error) });
345
+ }
346
+ }
347
+
348
+ function cancelIdleShutdown() {
349
+ if (idleTimer) clearTimeout(idleTimer);
350
+ idleTimer = null;
351
+ }
352
+
353
+ function scheduleIdleShutdown() {
354
+ cancelIdleShutdown();
355
+ if (clients.size > 0 || shuttingDown) return;
356
+ idleTimer = setTimeout(() => shutdown(0), idleMilliseconds);
357
+ }
358
+
359
+ function acceptClient(socket) {
360
+ cancelIdleShutdown();
361
+ clients.add(socket);
362
+ socket.setEncoding('utf8');
363
+ let buffer = '';
364
+ socket.on('data', chunk => {
365
+ buffer += chunk;
366
+ let newline;
367
+ while ((newline = buffer.indexOf('\n')) >= 0) {
368
+ const line = buffer.slice(0, newline).trim();
369
+ buffer = buffer.slice(newline + 1);
370
+ if (!line) continue;
371
+ let message;
372
+ try { message = JSON.parse(line); }
373
+ catch { send(socket, { id: null, error: 'Invalid broker JSON' }); continue; }
374
+ Promise.resolve(handleMessage(socket, message)).catch(() => { /* handled above */ });
375
+ }
376
+ });
377
+ const close = () => {
378
+ clients.delete(socket);
379
+ scheduleIdleShutdown();
380
+ };
381
+ socket.once('close', close);
382
+ socket.once('error', close);
383
+ }
384
+
385
+ function removeUnixSocket() {
386
+ if (process.platform === 'win32') return;
387
+ try { if (fs.existsSync(endpoint)) fs.rmSync(endpoint, { force: true }); } catch { /* stale socket is reclaimable */ }
388
+ }
389
+
390
+ function shutdown(exitCode = 0) {
391
+ if (shuttingDown) return;
392
+ shuttingDown = true;
393
+ cancelIdleShutdown();
394
+ if (heartbeatTimer) clearInterval(heartbeatTimer);
395
+ for (const socket of clients) {
396
+ try { socket.end(); } catch { /* best effort */ }
397
+ }
398
+ clients.clear();
399
+ for (const child of children.values()) child.stop();
400
+ children.clear();
401
+ const finish = () => {
402
+ releaseLease();
403
+ removeUnixSocket();
404
+ process.exit(exitCode);
405
+ };
406
+ if (server) {
407
+ try { server.close(finish); } catch { finish(); }
408
+ const forceTimer = setTimeout(finish, 2200);
409
+ forceTimer.unref();
410
+ } else {
411
+ finish();
412
+ }
413
+ }
414
+
415
+ const startedAtIso = new Date().toISOString();
416
+ // Windows 命名管道无需目录;macOS/Linux 首次连接必须先创建 Unix socket 的父目录。
417
+ // 在领取租约前完成,目录不可写时不会留下貌似存活的 Broker 租约。
418
+ if (process.platform !== 'win32') fs.mkdirSync(path.dirname(endpoint), { recursive: true, mode: 0o700 });
419
+ if (!acquireLease()) process.exit(0);
420
+
421
+ removeUnixSocket();
422
+ server = net.createServer(acceptClient);
423
+ server.on('error', error => {
424
+ process.stderr.write(`[microi-codex-broker] ${error.message}\n`);
425
+ shutdown(1);
426
+ });
427
+ server.listen(endpoint, () => {
428
+ if (process.platform !== 'win32') {
429
+ try { fs.chmodSync(endpoint, 0o600); } catch { /* best effort */ }
430
+ }
431
+ heartbeatTimer = setInterval(() => {
432
+ try { writeLeaseRecord(leaseRecord()); } catch { shutdown(1); }
433
+ }, heartbeatMilliseconds);
434
+ scheduleIdleShutdown();
435
+ });
436
+
437
+ for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) process.on(signal, () => shutdown(0));
438
+ process.on('uncaughtException', error => {
439
+ process.stderr.write(`[microi-codex-broker] ${error?.stack || error}\n`);
440
+ shutdown(1);
441
+ });
442
+ process.on('exit', () => {
443
+ for (const child of children.values()) {
444
+ try { if (child.child?.exitCode === null) child.child.kill('SIGKILL'); } catch { /* best effort */ }
445
+ }
446
+ if (leaseOwned) {
447
+ releaseLease();
448
+ removeUnixSocket();
449
+ }
450
+ });