@microi.net/cli 5.8.5 → 5.8.6

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 (211) 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/.mcp.json +1 -1
  5. package/.workbuddy-plugin/marketplace.json +2 -2
  6. package/.workbuddy-plugin/plugin.json +1 -1
  7. package/LICENSE +21 -21
  8. package/README.md +71 -71
  9. package/assets/build-meta.json +6 -6
  10. package/assets/feature-matrix.json +138 -138
  11. package/assets/logo.svg +4 -4
  12. package/cordis.patch.yml +1 -1
  13. package/package.json +1 -1
  14. package/scripts/codex-marketplace.json +20 -20
  15. package/scripts/mcp-codex-stdio-adapter.js +189 -189
  16. package/scripts/mcp-trae-windows-launcher.cmd +21 -21
  17. package/scripts/microi-cli-mcp.js +7 -7
  18. package/scripts/microi-cli.js +66 -65
  19. package/scripts/microi-codex-broker.js +450 -450
  20. package/scripts/microi-codex-router.js +618 -618
  21. package/scripts/microi-skills.meta.json +384 -384
  22. package/skills/.microi-skills-version.json +2 -2
  23. package/skills/.progressive-disclosure-manifest.json +3566 -3566
  24. package/skills/README.md +287 -287
  25. package/skills/ai-engine/SKILL.md +269 -265
  26. package/skills/ai-engine/agents/openai.yaml +4 -4
  27. package/skills/ai-engine/references/ai-employees.md +48 -48
  28. package/skills/ai-engine/references/self-hosted-digital-human.md +59 -59
  29. package/skills/ai-platform-governance/SKILL.md +177 -177
  30. package/skills/ai-platform-governance/references/progressive-01-/345/212/237/350/203/275/345/274/200/345/205/263.md +190 -190
  31. package/skills/app-store/SKILL.md +525 -525
  32. package/skills/app-store/agents/openai.yaml +4 -4
  33. package/skills/business-blueprint/SKILL.md +193 -193
  34. package/skills/datasource-engine/SKILL.md +93 -93
  35. package/skills/datasource-engine/agents/openai.yaml +4 -4
  36. package/skills/dos-orm/SKILL.md +97 -97
  37. package/skills/dos-orm/references/api-reference.md +229 -229
  38. package/skills/email-engine/SKILL.md +81 -81
  39. package/skills/email-engine/references/v8-email.md +34 -34
  40. package/skills/job-engine/SKILL.md +176 -176
  41. package/skills/job-engine/agents/openai.yaml +4 -4
  42. package/skills/message-notification/SKILL.md +156 -156
  43. package/skills/message-notification/agents/openai.yaml +5 -5
  44. package/skills/message-notification/references/contracts.md +102 -102
  45. package/skills/microi/SKILL.md +14 -14
  46. package/skills/microi-ai-app-auth.js +652 -652
  47. package/skills/microi-ai-application/SKILL.md +115 -115
  48. package/skills/microi-ai-application/agents/openai.yaml +4 -4
  49. package/skills/microi-ai-application/references/frontend-baseline.md +164 -164
  50. package/skills/microi-client-frontend/SKILL.md +244 -244
  51. 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
  52. 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
  53. 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
  54. package/skills/microi-codex/SKILL.md +102 -102
  55. package/skills/microi-codex-installer/SKILL.md +231 -231
  56. package/skills/microi-codex-installer/agents/openai.yaml +7 -7
  57. package/skills/microi-datasource-mapping/SKILL.md +122 -122
  58. package/skills/microi-db-schema/SKILL.md +175 -175
  59. package/skills/microi-db-schema/agents/openai.yaml +4 -4
  60. package/skills/microi-db-schema/references/core-tables.md +695 -695
  61. package/skills/microi-db-schema/references/form-component-options.md +256 -256
  62. package/skills/microi-db-schema/references/schema-overview.md +202 -202
  63. package/skills/microi-db-schema/references/schema.md +646 -646
  64. package/skills/microi-db-schema/references/table-catalog.md +1599 -1599
  65. package/skills/microi-deployment/SKILL.md +221 -221
  66. package/skills/microi-deployment/references/deployment-matrix.md +109 -109
  67. package/skills/microi-docs-coverage/SKILL.md +133 -133
  68. package/skills/microi-docs-coverage/references/capability-map.md +91 -91
  69. package/skills/microi-docs-coverage/scripts/audit-doc-skill-coverage.mjs +894 -894
  70. package/skills/microi-form-engine/SKILL.md +333 -333
  71. package/skills/microi-form-engine/references/component-catalog.md +218 -218
  72. package/skills/microi-form-engine/references/data-source-events.md +124 -124
  73. package/skills/microi-form-layout/SKILL.md +205 -205
  74. 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
  75. package/skills/microi-frontend-sdk/SKILL.md +194 -194
  76. 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
  77. package/skills/microi-left-right-layout/SKILL.md +141 -141
  78. package/skills/microi-microservice/SKILL.md +326 -324
  79. package/skills/microi-microservice/references/runtime-delivery.md +278 -278
  80. package/skills/microi-mobile-app-quality/SKILL.md +185 -185
  81. 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
  82. 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
  83. package/skills/microi-solution-quotation/SKILL.md +78 -78
  84. package/skills/microi-solution-quotation/agents/openai.yaml +4 -4
  85. package/skills/microi-solution-quotation/scripts/build_solution_quote.py +296 -296
  86. package/skills/microi-sso/SKILL.md +92 -92
  87. package/skills/microi-sso/references/acceptance.md +49 -49
  88. package/skills/microi-sso/references/configuration-and-security.md +53 -53
  89. package/skills/microi-sso/references/inbound.md +53 -53
  90. package/skills/microi-sso/references/outbound.md +39 -39
  91. package/skills/microi-system-delivery/SKILL.md +137 -137
  92. 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
  93. 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
  94. package/skills/microi-ui/SKILL.md +192 -192
  95. 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
  96. package/skills/microi-uniapp-frontend/SKILL.md +193 -193
  97. 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
  98. 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
  99. package/skills/microi.v8.js +1921 -1921
  100. package/skills/module-engine/SKILL.md +255 -255
  101. package/skills/module-engine/references/module-config.md +204 -204
  102. package/skills/ocr-engine/SKILL.md +113 -113
  103. package/skills/ocr-engine/agents/openai.yaml +4 -4
  104. package/skills/page-engine/SKILL.md +206 -206
  105. package/skills/page-engine/examples/compact-dashboard.json +1444 -1444
  106. 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
  107. 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
  108. package/skills/performance-testing/SKILL.md +221 -221
  109. package/skills/playwright-e2e/SKILL.md +196 -196
  110. 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
  111. 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
  112. package/skills/playwright-e2e/references/progressive-03-microi-helper-/346/250/241/346/235/277.md +221 -221
  113. package/skills/playwright-e2e/references/progressive-04-ci-/345/273/272/350/256/256.md +115 -115
  114. package/skills/print-engine/SKILL.md +259 -259
  115. package/skills/production-readonly-audit/SKILL.md +41 -41
  116. package/skills/report-engine/SKILL.md +71 -71
  117. package/skills/report-engine/agents/openai.yaml +4 -4
  118. package/skills/scripts/optimize-progressive-disclosure.mjs +204 -204
  119. package/skills/scripts/refresh-progressive-disclosure.mjs +64 -64
  120. package/skills/scripts/sync-embedded-skills.mjs +46 -46
  121. package/skills/scripts/validate-progressive-disclosure.mjs +57 -57
  122. package/skills/search-engine/SKILL.md +75 -75
  123. package/skills/search-engine/agents/openai.yaml +4 -4
  124. package/skills/spider-engine/SKILL.md +190 -190
  125. package/skills/system-observability/SKILL.md +248 -248
  126. package/skills/system-observability/references/memory-incident-triage.md +77 -77
  127. package/skills/translate-engine/SKILL.md +140 -140
  128. package/skills/translate-engine/agents/openai.yaml +4 -4
  129. package/skills/ui-design/SKILL.md +223 -223
  130. package/skills/ui-design/assets/pattern-showcase/app.js +54 -54
  131. package/skills/ui-design/assets/pattern-showcase/index.html +163 -163
  132. package/skills/ui-design/assets/pattern-showcase/styles.css +311 -311
  133. package/skills/ui-design/assets/templates/MCI-DESIGN.md +206 -206
  134. package/skills/ui-design/references/design-pattern-library.md +184 -184
  135. package/skills/ui-design/references/mci-design-contract.md +163 -163
  136. package/skills/ui-design/references/motion-and-media.md +78 -78
  137. package/skills/ui-design/references/product-flow-recipes.md +94 -94
  138. 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
  139. package/skills/ui-design/references/progressive-02-/345/255/227/344/275/223.md +164 -164
  140. 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
  141. 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
  142. 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
  143. 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
  144. 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
  145. 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
  146. package/skills/uniapp-mall-assets/SKILL.md +176 -176
  147. package/skills/unity-integration/SKILL.md +171 -171
  148. package/skills/unity-integration/agents/openai.yaml +4 -4
  149. package/skills/unity-integration/references/ai-app-delivery.md +119 -119
  150. package/skills/unity-integration/references/sdk-api.md +82 -82
  151. package/skills/unity-integration/references/toolbox-migration.md +66 -66
  152. package/skills/unity-integration/references/webgl-hosting.md +57 -57
  153. package/skills/v8-api-config/SKILL.md +388 -388
  154. package/skills/v8-cache-pattern/SKILL.md +312 -312
  155. package/skills/v8-crud-api/SKILL.md +178 -178
  156. 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
  157. 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
  158. package/skills/v8-debugging/SKILL.md +284 -284
  159. package/skills/v8-explorer-tree/SKILL.md +228 -228
  160. package/skills/v8-export-import/SKILL.md +219 -219
  161. 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
  162. package/skills/v8-export-import/references/progressive-02-powerpoint-/345/257/274/345/207/272.md +202 -202
  163. 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
  164. package/skills/v8-file-upload/SKILL.md +284 -284
  165. 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
  166. 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
  167. package/skills/v8-formengine-http/SKILL.md +238 -238
  168. package/skills/v8-frontend-events/SKILL.md +180 -180
  169. package/skills/v8-frontend-events/references/bluetooth-print-api.md +135 -135
  170. package/skills/v8-frontend-events/references/bluetooth-print.md +258 -258
  171. package/skills/v8-frontend-events/references/progressive-01-/345/210/227/350/241/250/344/272/213/344/273/266.md +219 -219
  172. package/skills/v8-http-integration/SKILL.md +182 -182
  173. package/skills/v8-http-integration/references/progressive-01-get-/350/257/267/346/261/202.md +220 -220
  174. 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
  175. package/skills/v8-image-processing/SKILL.md +190 -190
  176. package/skills/v8-image-processing/agents/openai.yaml +4 -4
  177. package/skills/v8-image-processing/references/api-reference.md +623 -623
  178. package/skills/v8-menu-buttons/SKILL.md +186 -186
  179. 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
  180. 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
  181. 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
  182. package/skills/v8-mongodb/SKILL.md +200 -200
  183. package/skills/v8-mq-mqtt/SKILL.md +176 -176
  184. package/skills/v8-mq-mqtt/references/mqtt-production.md +342 -342
  185. package/skills/v8-mq-mqtt/references/progressive-01-v8-mqtt-iot-/347/211/251/350/201/224/347/275/221.md +181 -181
  186. package/skills/v8-mq-mqtt/scripts/check-mqtt-skill-coverage.mjs +203 -203
  187. package/skills/v8-saas-multi-tenant/SKILL.md +305 -305
  188. package/skills/v8-security/SKILL.md +210 -210
  189. package/skills/v8-security/references/progressive-01-2-/346/235/203/351/231/220/346/240/241/351/252/214.md +200 -200
  190. package/skills/v8-security/references/progressive-02-7-/346/227/245/345/277/227/350/256/260/345/275/225.md +160 -160
  191. package/skills/v8-sql-query/SKILL.md +302 -302
  192. package/skills/v8-table-event/SKILL.md +176 -176
  193. 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
  194. 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
  195. package/skills/v8-tcp-integration/SKILL.md +147 -147
  196. package/skills/v8-tcp-integration/agents/openai.yaml +4 -4
  197. package/skills/v8-template-engine/SKILL.md +167 -167
  198. package/skills/v8-utilities/SKILL.md +104 -104
  199. package/skills/v8-utilities/references/client-api-index.md +143 -143
  200. package/skills/v8-utilities/references/platform-http-routes.md +83 -83
  201. package/skills/v8-utilities/references/server-api-index.md +188 -188
  202. package/skills/v8-workflow/SKILL.md +252 -252
  203. 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
  204. package/skills/v8-workflow/references/workflow-configuration.md +49 -49
  205. package/skills/vision-engine/SKILL.md +160 -160
  206. package/skills/vision-engine/agents/openai.yaml +4 -4
  207. package/skills/vision-engine/references/architecture-and-acceptance.md +194 -194
  208. package/skills/workspace-conventions/SKILL.md +273 -273
  209. 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
  210. 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
  211. 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,618 +1,618 @@
1
- #!/usr/bin/env node
2
- /*
3
- * Microi Agentx Plugin 多连接路由器。
4
- *
5
- * 它只负责发现 VS Code/CLI 共用的工作区配置、选择连接并把请求转发给
6
- * 同一份 Microi MCP bundle。所有业务 schema、确认口令、审计和写后回读
7
- * 都由原 MCP 工具执行,本文件不实现第二套远端 API。
8
- */
9
-
10
- const childProcess = require('node:child_process');
11
- const crypto = require('node:crypto');
12
- const fs = require('node:fs');
13
- const net = require('node:net');
14
- const os = require('node:os');
15
- const path = require('node:path');
16
-
17
- const CONFIG_DIR = 'Microi-V8-Engine';
18
- const CONFIG_FILE = '.microi-config.json';
19
- const TOKEN_FILE = '.microi-mcp-tokens.json';
20
- const PROTOCOL_VERSION = '2024-11-05';
21
- const pluginRoot = path.resolve(__dirname, '..');
22
- const mcpServerPath = path.resolve(process.env.MICROI_MCP_SERVER_PATH || path.join(__dirname, 'mcp-server.js'));
23
- const brokerScriptPath = path.resolve(process.env.MICROI_CODEX_BROKER_SCRIPT || path.join(__dirname, 'microi-codex-broker.js'));
24
- const packagePath = path.join(pluginRoot, 'package.json');
25
- const packageVersion = String(readJson(packagePath, {}).version || '').trim();
26
- const environmentVersion = String(process.env.MICROI_TOOL_VERSION || '').trim();
27
- // An installed plugin is self-describing. A stale parent MCP environment must
28
- // never force a newer package to reconnect to an older shared broker. The
29
- // version environment remains authoritative for the package-less stable CLI
30
- // runtime, whose current.json launcher selects one immutable version folder.
31
- const pluginVersion = /^\d+(?:\.\d+){2}$/.test(packageVersion)
32
- ? packageVersion
33
- : (/^\d+(?:\.\d+){2}$/.test(environmentVersion) ? environmentVersion : '0.0.0');
34
- let inputBuffer = '';
35
- let requestSequence = 0;
36
- let shuttingDown = false;
37
- let brokerClient = null;
38
-
39
- function readJson(filePath, fallback) {
40
- try {
41
- const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
42
- return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : fallback;
43
- } catch {
44
- return fallback;
45
- }
46
- }
47
-
48
- function normalizeUrl(value) {
49
- return String(value || '').trim().replace(/\/+$/, '');
50
- }
51
-
52
- function safeName(value) {
53
- return String(value || '').normalize('NFKD').replace(/[^\x00-\x7F]/g, '')
54
- .replace(/[^A-Za-z0-9_]+/g, '_').replace(/_+/g, '_')
55
- .replace(/^_+|_+$/g, '').toLowerCase().slice(0, 48);
56
- }
57
-
58
- function hostPart(profile) {
59
- try { return safeName(new URL(profile.apiBaseUrl).host); } catch { return safeName(profile.apiBaseUrl); }
60
- }
61
-
62
- function profileIdentity(profile) {
63
- return [
64
- normalizeUrl(profile.apiBaseUrl),
65
- String(profile.osClient || '').trim(),
66
- String(profile.osClientType || '').trim(),
67
- String(profile.osClientNetwork || '').trim(),
68
- ].join('|');
69
- }
70
-
71
- function candidateRoots() {
72
- const values = [
73
- process.env.MICROI_WORKSPACE_ROOT,
74
- process.env.CODEX_WORKSPACE_ROOT,
75
- process.env.INIT_CWD,
76
- process.cwd(),
77
- ].filter(Boolean).map(value => path.resolve(value));
78
- const roots = [];
79
- for (const start of values) {
80
- let current = start;
81
- while (current && !roots.includes(current)) {
82
- roots.push(current);
83
- const parent = path.dirname(current);
84
- if (parent === current) break;
85
- current = parent;
86
- }
87
- }
88
- return roots;
89
- }
90
-
91
- function resolveWorkspace() {
92
- const explicitConfig = process.env.MICROI_CONFIG_PATH
93
- ? path.resolve(process.env.MICROI_CONFIG_PATH)
94
- : '';
95
- const candidates = explicitConfig
96
- ? [explicitConfig]
97
- : candidateRoots().flatMap(root => [
98
- path.join(root, CONFIG_DIR, CONFIG_FILE),
99
- path.join(root, CONFIG_DIR.toLowerCase(), CONFIG_FILE),
100
- ]);
101
- const configPath = candidates.find(filePath => fs.existsSync(filePath));
102
- const fallbackRoot = path.resolve(process.env.MICROI_WORKSPACE_ROOT || process.cwd());
103
- if (!configPath) {
104
- return {
105
- workspaceRoot: fallbackRoot,
106
- syncRoot: path.join(fallbackRoot, CONFIG_DIR),
107
- configPath: path.join(fallbackRoot, CONFIG_DIR, CONFIG_FILE),
108
- tokenPath: path.join(fallbackRoot, CONFIG_DIR, TOKEN_FILE),
109
- config: {},
110
- };
111
- }
112
- const syncRoot = path.dirname(configPath);
113
- return {
114
- workspaceRoot: path.dirname(syncRoot),
115
- syncRoot,
116
- configPath,
117
- tokenPath: path.join(syncRoot, TOKEN_FILE),
118
- config: readJson(configPath, {}),
119
- };
120
- }
121
-
122
- function scheduleAutomaticUpdate() {
123
- if (process.env.MICROI_DISABLE_AUTO_UPDATE === '1') return;
124
- const cliPath = path.join(__dirname, 'microi-cli.js');
125
- if (!fs.existsSync(cliPath)) return;
126
- const workspace = resolveWorkspace();
127
- try {
128
- const child = childProcess.spawn(process.execPath, [
129
- cliPath,
130
- 'update',
131
- '--background',
132
- '--workspace',
133
- workspace.workspaceRoot,
134
- '--json',
135
- ], {
136
- cwd: pluginRoot,
137
- detached: true,
138
- windowsHide: true,
139
- stdio: 'ignore',
140
- env: { ...process.env },
141
- });
142
- // 不等待更新、不重启当前 Router。新版本仅供后续新进程使用。
143
- child.on('error', () => { /* 下一次启动自动重试 */ });
144
- child.unref();
145
- } catch {
146
- // 自动更新永远不能成为 MCP 启动门禁。
147
- }
148
- }
149
-
150
- function loadProfiles() {
151
- const workspace = resolveWorkspace();
152
- const configured = Array.isArray(workspace.config.profiles)
153
- ? workspace.config.profiles
154
- : [];
155
- const rawProfiles = configured.length > 0
156
- ? configured
157
- : (workspace.config.apiBaseUrl ? [{
158
- label: workspace.config.osClient || workspace.config.apiBaseUrl,
159
- apiBaseUrl: workspace.config.apiBaseUrl,
160
- osClient: workspace.config.osClient || '',
161
- }] : []);
162
- const tokens = readJson(workspace.tokenPath, {});
163
- const identityNames = rawProfiles.map(profile => safeName(profile.osClient || profile.label) || hostPart(profile) || 'default');
164
- const counts = identityNames.reduce((map, name) => map.set(name, (map.get(name) || 0) + 1), new Map());
165
- const used = new Set();
166
- const profiles = rawProfiles.filter(profile => /^https?:\/\//i.test(String(profile.apiBaseUrl || ''))).map((raw, index) => {
167
- const profile = {
168
- label: String(raw.label || raw.sysShortTitle || raw.sysTitle || raw.osClient || raw.apiBaseUrl || ''),
169
- apiBaseUrl: normalizeUrl(raw.apiBaseUrl),
170
- osClient: String(raw.osClient || '').trim(),
171
- osClientType: String(raw.osClientType || '').trim(),
172
- osClientNetwork: String(raw.osClientNetwork || '').trim(),
173
- };
174
- const identityName = identityNames[index];
175
- const host = hostPart(profile);
176
- const base = `microi_${identityName}`;
177
- let name = counts.get(identityName) > 1 && host && host !== identityName ? `${base}_${host}` : base;
178
- if (used.has(name) && host) name = `${base}_${host}`;
179
- let suffix = 2;
180
- while (used.has(name)) name = `${base}_${suffix++}`;
181
- used.add(name);
182
- const exactKey = profileIdentity(profile);
183
- const legacyKey = profile.osClient ? `${profile.apiBaseUrl}|${profile.osClient}` : profile.apiBaseUrl;
184
- return {
185
- ...profile,
186
- index: index + 1,
187
- name,
188
- identity: exactKey,
189
- loggedIn: typeof tokens[exactKey] === 'string' || typeof tokens[legacyKey] === 'string',
190
- workspaceRoot: workspace.workspaceRoot,
191
- syncRoot: workspace.syncRoot,
192
- tokenPath: workspace.tokenPath,
193
- };
194
- });
195
- return { workspace, profiles };
196
- }
197
-
198
- function publicProfile(profile) {
199
- return {
200
- index: profile.index,
201
- name: profile.name,
202
- label: profile.label,
203
- apiBaseUrl: profile.apiBaseUrl,
204
- osClient: profile.osClient,
205
- osClientType: profile.osClientType,
206
- osClientNetwork: profile.osClientNetwork,
207
- loggedIn: profile.loggedIn,
208
- };
209
- }
210
-
211
- function selectProfile(selector) {
212
- const state = loadProfiles();
213
- if (state.profiles.length === 0) {
214
- throw new Error(`当前工作区尚未初始化 Microi。请在 ${state.workspace.workspaceRoot} 运行 bundled CLI 的 init 命令。`);
215
- }
216
- const raw = String(selector || '').trim();
217
- if (raw) {
218
- const lower = raw.toLowerCase();
219
- const matches = state.profiles.filter(profile =>
220
- String(profile.index) === raw
221
- || profile.name.toLowerCase() === lower
222
- || profile.osClient.toLowerCase() === lower
223
- || profile.label.toLowerCase() === lower
224
- || profile.identity.toLowerCase() === lower
225
- );
226
- if (matches.length === 1) return matches[0];
227
- if (matches.length > 1) throw new Error(`连接选择“${raw}”存在歧义,请改用 profiles 返回的 name。`);
228
- throw new Error(`未找到连接“${raw}”。可用连接:${state.profiles.map(item => item.name).join(', ')}`);
229
- }
230
- const loggedIn = state.profiles.filter(profile => profile.loggedIn);
231
- if (loggedIn.length === 1) return loggedIn[0];
232
- if (state.profiles.length === 1) return state.profiles[0];
233
- throw new Error(`当前工作区有 ${state.profiles.length} 个 Microi 连接,请先调用 action="profiles",再传 profile=name。`);
234
- }
235
-
236
- function buildApplicationsDir(profile) {
237
- const title = profile.label || profile.apiBaseUrl || 'Microi';
238
- const serverDir = `${title} (${hostPart(profile) || 'server'})`.replace(/[<>:"/\\|?*]/g, '_');
239
- const osClientDir = [profile.osClient || 'default', profile.osClientType || 'default', profile.osClientNetwork || 'default']
240
- .join('.').replace(/[<>:"/\\|?*]/g, '_');
241
- return path.join(profile.syncRoot, serverDir, osClientDir, 'AI应用');
242
- }
243
-
244
- function brokerCoordinates(workspace) {
245
- const normalizedRoot = process.platform === 'win32'
246
- ? workspace.workspaceRoot.toLowerCase()
247
- : workspace.workspaceRoot;
248
- const key = crypto.createHash('sha256').update(`${normalizedRoot}\0${pluginVersion}`).digest('hex').slice(0, 24);
249
- const runtimeRoot = path.resolve(process.env.MICROI_RUNTIME_HOME || path.join(os.homedir(), '.microi', 'runtime'));
250
- const endpoint = process.env.MICROI_CODEX_BROKER_ENDPOINT || (process.platform === 'win32'
251
- ? `\\\\.\\pipe\\microi-codex-${key}`
252
- : path.join(runtimeRoot, 'brokers', `${key}.sock`));
253
- return {
254
- endpoint,
255
- leasePath: path.resolve(process.env.MICROI_CODEX_BROKER_LEASE || path.join(runtimeRoot, 'leases', `microi-codex-${key}.json`)),
256
- workspaceRoot: workspace.workspaceRoot,
257
- };
258
- }
259
-
260
- function wait(milliseconds) {
261
- return new Promise(resolve => setTimeout(resolve, milliseconds));
262
- }
263
-
264
- function readBrokerToken(leasePath) {
265
- const lease = readJson(leasePath, {});
266
- return typeof lease.token === 'string' ? lease.token : '';
267
- }
268
-
269
- class SharedBrokerClient {
270
- constructor(workspace) {
271
- this.coordinates = brokerCoordinates(workspace);
272
- this.pending = new Map();
273
- this.buffer = '';
274
- this.closed = false;
275
- this.spawnAttempted = false;
276
- this.ready = this.connect();
277
- }
278
-
279
- startBroker() {
280
- if (!fs.existsSync(brokerScriptPath)) {
281
- throw new Error(`Codex Plugin 缺少共享 Broker:${brokerScriptPath}`);
282
- }
283
- const child = childProcess.spawn(process.execPath, [brokerScriptPath], {
284
- cwd: pluginRoot,
285
- detached: true,
286
- windowsHide: true,
287
- stdio: 'ignore',
288
- env: {
289
- ...process.env,
290
- MICROI_CODEX_BROKER_ENDPOINT: this.coordinates.endpoint,
291
- MICROI_CODEX_BROKER_LEASE: this.coordinates.leasePath,
292
- MICROI_WORKSPACE_ROOT: this.coordinates.workspaceRoot,
293
- MICROI_MCP_SERVER_PATH: mcpServerPath,
294
- MICROI_TOOL_VERSION: pluginVersion,
295
- },
296
- });
297
- child.on('error', () => { /* connect retry reports the actionable failure */ });
298
- child.unref();
299
- }
300
-
301
- openSocket() {
302
- return new Promise((resolve, reject) => {
303
- const socket = net.createConnection(this.coordinates.endpoint);
304
- const fail = error => {
305
- socket.destroy();
306
- reject(error);
307
- };
308
- socket.once('error', fail);
309
- socket.once('connect', () => {
310
- socket.off('error', fail);
311
- this.socket = socket;
312
- socket.setEncoding('utf8');
313
- socket.on('data', chunk => this.onData(chunk));
314
- socket.on('error', error => this.rejectAll(error));
315
- socket.on('close', () => {
316
- this.closed = true;
317
- this.rejectAll(new Error('Microi shared broker disconnected'));
318
- });
319
- resolve();
320
- });
321
- });
322
- }
323
-
324
- async connect() {
325
- let lastError;
326
- for (let attempt = 0; attempt < 50; attempt += 1) {
327
- try {
328
- await this.openSocket();
329
- return;
330
- } catch (error) {
331
- lastError = error;
332
- if (!this.spawnAttempted || attempt === 15) {
333
- this.spawnAttempted = true;
334
- this.startBroker();
335
- }
336
- await wait(Math.min(40 + attempt * 12, 250));
337
- }
338
- }
339
- throw new Error(`无法连接 Microi 共享 Broker:${lastError instanceof Error ? lastError.message : String(lastError)}`);
340
- }
341
-
342
- onData(chunk) {
343
- this.buffer += chunk.toString('utf8');
344
- let newline;
345
- while ((newline = this.buffer.indexOf('\n')) >= 0) {
346
- const line = this.buffer.slice(0, newline).trim();
347
- this.buffer = this.buffer.slice(newline + 1);
348
- if (!line) continue;
349
- let message;
350
- try { message = JSON.parse(line); } catch { continue; }
351
- if (message.id == null) continue;
352
- const pending = this.pending.get(String(message.id));
353
- if (!pending) continue;
354
- this.pending.delete(String(message.id));
355
- if (message.error) pending.reject(new Error(typeof message.error === 'string' ? message.error : (message.error.message || JSON.stringify(message.error))));
356
- else pending.resolve(message.result);
357
- }
358
- }
359
-
360
- rejectAll(error) {
361
- for (const pending of this.pending.values()) pending.reject(error);
362
- this.pending.clear();
363
- }
364
-
365
- request(profile, method, params) {
366
- return (async () => {
367
- await this.ready;
368
- if (this.closed || !this.socket || this.socket.destroyed) throw new Error('Microi shared broker is unavailable');
369
- let auth = '';
370
- for (let attempt = 0; attempt < 10 && !auth; attempt += 1) {
371
- auth = readBrokerToken(this.coordinates.leasePath);
372
- if (!auth) await wait(25);
373
- }
374
- if (!auth) throw new Error('Microi shared broker lease is unavailable');
375
- const id = `router-${process.pid}-${++requestSequence}`;
376
- const response = new Promise((resolve, reject) => this.pending.set(id, { resolve, reject }));
377
- this.socket.write(`${JSON.stringify({
378
- id,
379
- auth,
380
- type: 'request',
381
- profile: { ...profile, applicationsDir: buildApplicationsDir(profile) },
382
- method,
383
- params,
384
- })}\n`);
385
- return response;
386
- })();
387
- }
388
-
389
- close() {
390
- try { this.socket?.end(); } catch { /* best effort */ }
391
- }
392
- }
393
-
394
- function sharedBroker(profile) {
395
- if (!brokerClient || brokerClient.closed) brokerClient = new SharedBrokerClient({ workspaceRoot: profile.workspaceRoot });
396
- return brokerClient;
397
- }
398
-
399
- function requestThroughBroker(profile, method, params) {
400
- return sharedBroker(profile).request(profile, method, params);
401
- }
402
-
403
- function resultText(value, isError = false) {
404
- return {
405
- content: [{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }],
406
- ...(isError ? { isError: true } : {}),
407
- };
408
- }
409
-
410
- function toolchainStatus(workspaceRoot) {
411
- // 状态文件可能由另一工作区的后台更新器写入,只回读有限的诊断字段,
412
- // 不透传日志/错误正文或未知字段,也不把安装版本冒充当前已加载版本。
413
- const statusPath = path.join(path.resolve(process.env.MICROI_UPDATE_HOME || path.join(os.homedir(), '.microi', 'updater')), 'status.json');
414
- let status = {};
415
- try {
416
- if (fs.statSync(statusPath).size <= 64 * 1024) status = readJson(statusPath, {});
417
- } catch { /* 缺失/损坏状态不阻断连接发现 */ }
418
- const version = value => /^\d+(?:\.\d+){2}$/.test(String(value || '')) ? String(value) : 'unknown';
419
- const states = ['scheduled', 'running', 'completed', 'completed-with-warnings', 'deferred'];
420
- const skills = readJson(path.join(workspaceRoot, 'microi.skills', '.microi-skills-manifest.json'), {});
421
- return {
422
- package: '@microi.net/cli',
423
- loadedVersion: pluginVersion,
424
- workspaceSkillsVersion: version(skills.bundleVersion),
425
- automaticUpdate: {
426
- enabled: process.env.MICROI_DISABLE_AUTO_UPDATE !== '1',
427
- statusPath,
428
- state: states.includes(status.state) ? status.state : 'unknown',
429
- latestObservedVersion: version(status.latestVersion),
430
- globalVersion: version(status.globalVersion),
431
- checkedAt: typeof status.attemptedAt === 'string' && /^\d{4}-\d{2}-\d{2}T[\d:.]+Z$/.test(status.attemptedAt) ? status.attemptedAt : null,
432
- // 共享状态只证明记录中的工作区;其它目录须单独 doctor 验证。
433
- appliesToWorkspace: typeof status.workspace === 'string' && path.resolve(status.workspace) === path.resolve(workspaceRoot),
434
- },
435
- };
436
- }
437
-
438
- function baseInstructions() {
439
- return `先调用 microi_codex action="profiles";多连接时把返回的 name 作为 profile。所有业务动作由同源 Microi MCP 工具处理。
440
- 每次新建或接续吾码任务必须先完整读取工作区 microi.skills/workspace-conventions/SKILL.md;尚未初始化时读取插件 skills/workspace-conventions/SKILL.md。同样强制执行其中的进度播报与非阻塞自动更新规范,适用于任何 AI 模型。
441
- 首次说明必须以“Microi吾码开发工具版本”报告实际版本、官方最新版本及自动更新/Skills/MCP 同步状态;profiles.toolchain.loadedVersion=${pluginVersion} 只代表本进程,历史 latestObservedVersion 必须附 checkedAt,未知项写检测中,禁止猜测全局 CLI 或 npm 最新版。
442
- 已有 CLI 时必须执行 microi update --background --workspace "<工作区绝对路径>" --json,自动升级 @microi.net/cli 到 npm 官方 latest 并同步 Skills/AI/MCP;不得等更新结束才继续工作,不终止或强制重载当前会话。用户明确关闭或宿主禁止执行时报告限制。`;
443
- }
444
-
445
- function toolCatalog() {
446
- return [{
447
- name: 'microi_codex',
448
- description: 'Microi吾码统一入口。每次吾码任务先用 action="profiles" 查看连接与 toolchain 版本/自动更新状态,并读取 workspace-conventions/SKILL.md 执行强制版本播报与后台升级;多连接时传 profile=name。其余 action 复用原工具验证、确认、审计和回读。',
449
- inputSchema: {
450
- type: 'object',
451
- additionalProperties: false,
452
- required: ['action'],
453
- properties: {
454
- profile: { type: 'string', description: '可选:profiles 返回的 name、序号、OsClient 或连接名称。多连接时必填。' },
455
- action: { type: 'string', description: 'profiles | list_tools | describe_tool | 任意原始 microi_* 工具名' },
456
- params: { type: 'object', additionalProperties: true, description: '所选 action 的参数。' },
457
- },
458
- },
459
- }];
460
- }
461
-
462
- async function callRouterTool(args) {
463
- const action = String(args?.action || '').trim();
464
- if (!action) return resultText('action 不能为空', true);
465
- if (action === 'profiles' || action === 'profile_list') {
466
- const state = loadProfiles();
467
- return resultText({
468
- workspace: state.workspace.workspaceRoot,
469
- configPath: state.workspace.configPath,
470
- count: state.profiles.length,
471
- profiles: state.profiles.map(publicProfile),
472
- toolchain: toolchainStatus(state.workspace.workspaceRoot),
473
- next: state.profiles.length > 1 ? '后续调用请传 profile=name。' : '后续调用可省略 profile。',
474
- });
475
- }
476
- try {
477
- const profile = selectProfile(args?.profile);
478
- return await requestThroughBroker(profile, 'tools/call', {
479
- name: 'microi_codex',
480
- arguments: { action, params: args?.params || {} },
481
- });
482
- } catch (error) {
483
- return resultText(`Microi Agentx 路由失败:${error instanceof Error ? error.message : String(error)}`, true);
484
- }
485
- }
486
-
487
- function resourceList() {
488
- const state = loadProfiles();
489
- return [{
490
- uri: 'microi://codex/profiles',
491
- name: 'microi_profiles',
492
- title: 'Microi connection profiles',
493
- description: '当前工作区 Microi 连接与登录状态。',
494
- mimeType: 'application/json',
495
- }, ...state.profiles.flatMap(profile => ['status', 'tools'].map(kind => ({
496
- uri: `microi://codex/${encodeURIComponent(profile.name)}/${kind}`,
497
- name: `microi_${profile.name}_${kind}`,
498
- title: `Microi ${profile.label || profile.name} ${kind}`,
499
- description: kind === 'status' ? '只读连接状态。' : '该连接的原始工具目录。',
500
- mimeType: 'application/json',
501
- })))];
502
- }
503
-
504
- async function readResource(uri) {
505
- if (uri === 'microi://codex/profiles') {
506
- const state = loadProfiles();
507
- return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify({ profiles: state.profiles.map(publicProfile), toolchain: toolchainStatus(state.workspace.workspaceRoot) }, null, 2) }] };
508
- }
509
- const match = /^microi:\/\/codex\/([^/]+)\/(status|tools|action\/([^/]+)\/(.*))$/.exec(uri);
510
- if (!match) throw new Error(`不支持的 Microi 资源 URI:${uri}`);
511
- const profile = selectProfile(decodeURIComponent(match[1]));
512
- let action;
513
- let params = {};
514
- if (match[2] === 'status') action = 'microi_get_status';
515
- else if (match[2] === 'tools') action = 'list_tools';
516
- else {
517
- action = decodeURIComponent(match[3]);
518
- const raw = decodeURIComponent(match[4] || '');
519
- params = raw ? JSON.parse(raw) : {};
520
- }
521
- const result = await requestThroughBroker(profile, 'tools/call', {
522
- name: 'microi_codex',
523
- arguments: { action, params },
524
- });
525
- return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(result, null, 2) }] };
526
- }
527
-
528
- function send(message) {
529
- process.stdout.write(`${JSON.stringify(message)}\n`);
530
- }
531
-
532
- function sendResult(id, result) {
533
- send({ jsonrpc: '2.0', id, result });
534
- }
535
-
536
- function sendError(id, code, message) {
537
- send({ jsonrpc: '2.0', id, error: { code, message } });
538
- }
539
-
540
- async function handle(message) {
541
- if (!message || message.jsonrpc !== '2.0') return;
542
- const { id, method, params } = message;
543
- if (method === 'initialize') {
544
- sendResult(id, {
545
- protocolVersion: params?.protocolVersion || PROTOCOL_VERSION,
546
- capabilities: { tools: {}, resources: {} },
547
- serverInfo: { name: 'Microi-Codex-Plugin', version: pluginVersion },
548
- instructions: baseInstructions(),
549
- });
550
- return;
551
- }
552
- if (method === 'ping') { sendResult(id, {}); return; }
553
- if (method === 'tools/list') { sendResult(id, { tools: toolCatalog() }); return; }
554
- if (method === 'tools/call') {
555
- if (params?.name !== 'microi_codex') { sendResult(id, resultText(`Unknown tool: ${params?.name || '(empty)'}`, true)); return; }
556
- sendResult(id, await callRouterTool(params.arguments || {})); return;
557
- }
558
- if (method === 'resources/list') { sendResult(id, { resources: resourceList() }); return; }
559
- if (method === 'resources/templates/list') {
560
- sendResult(id, { resourceTemplates: [{
561
- uriTemplate: 'microi://codex/{profile}/action/{action}/{params}',
562
- name: 'microi_codex_action',
563
- title: 'Microi action fallback',
564
- description: '工具未注入时,按连接路由到同一原始 Microi 工具。params 是 URI 编码 JSON 对象。',
565
- mimeType: 'application/json',
566
- }] });
567
- return;
568
- }
569
- if (method === 'resources/read') {
570
- try { sendResult(id, await readResource(String(params?.uri || ''))); }
571
- catch (error) { sendError(id, -32002, error instanceof Error ? error.message : String(error)); }
572
- return;
573
- }
574
- if (method === 'shutdown') { sendResult(id, {}); shutdown(); return; }
575
- if (method?.startsWith('notifications/')) return;
576
- if (id != null) sendError(id, -32601, `Method not found: ${method}`);
577
- }
578
-
579
- function shutdown() {
580
- if (shuttingDown) return;
581
- shuttingDown = true;
582
- brokerClient?.close();
583
- brokerClient = null;
584
- }
585
-
586
- process.stdin.setEncoding('utf8');
587
- const automaticUpdateTimer = setTimeout(scheduleAutomaticUpdate, 1500);
588
- automaticUpdateTimer.unref();
589
- process.stdin.on('data', chunk => {
590
- inputBuffer += chunk;
591
- let newline;
592
- while ((newline = inputBuffer.indexOf('\n')) >= 0) {
593
- const line = inputBuffer.slice(0, newline).trim();
594
- inputBuffer = inputBuffer.slice(newline + 1);
595
- if (!line) continue;
596
- let message;
597
- try { message = JSON.parse(line); }
598
- catch { continue; }
599
- Promise.resolve(handle(message)).catch(error => {
600
- if (message.id != null) sendError(message.id, -32603, error instanceof Error ? error.message : String(error));
601
- });
602
- }
603
- });
604
- process.stdin.on('end', () => { shutdown(); process.exit(0); });
605
- process.stdin.on('error', () => { shutdown(); process.exit(1); });
606
- process.stdout.on('error', error => { shutdown(); process.exit(error?.code === 'EPIPE' ? 0 : 1); });
607
- process.on('disconnect', () => { shutdown(); process.exit(0); });
608
- for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) process.on(signal, () => { shutdown(); process.exit(0); });
609
- process.on('exit', shutdown);
610
-
611
- const parentPid = process.ppid;
612
- if (parentPid > 1) {
613
- const parentWatchTimer = setInterval(() => {
614
- try { process.kill(parentPid, 0); }
615
- catch (error) { if (error?.code === 'ESRCH') { shutdown(); process.exit(0); } }
616
- }, 5000);
617
- parentWatchTimer.unref();
618
- }
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Microi Agentx Plugin 多连接路由器。
4
+ *
5
+ * 它只负责发现 VS Code/CLI 共用的工作区配置、选择连接并把请求转发给
6
+ * 同一份 Microi MCP bundle。所有业务 schema、确认口令、审计和写后回读
7
+ * 都由原 MCP 工具执行,本文件不实现第二套远端 API。
8
+ */
9
+
10
+ const childProcess = require('node:child_process');
11
+ const crypto = require('node:crypto');
12
+ const fs = require('node:fs');
13
+ const net = require('node:net');
14
+ const os = require('node:os');
15
+ const path = require('node:path');
16
+
17
+ const CONFIG_DIR = 'Microi-V8-Engine';
18
+ const CONFIG_FILE = '.microi-config.json';
19
+ const TOKEN_FILE = '.microi-mcp-tokens.json';
20
+ const PROTOCOL_VERSION = '2024-11-05';
21
+ const pluginRoot = path.resolve(__dirname, '..');
22
+ const mcpServerPath = path.resolve(process.env.MICROI_MCP_SERVER_PATH || path.join(__dirname, 'mcp-server.js'));
23
+ const brokerScriptPath = path.resolve(process.env.MICROI_CODEX_BROKER_SCRIPT || path.join(__dirname, 'microi-codex-broker.js'));
24
+ const packagePath = path.join(pluginRoot, 'package.json');
25
+ const packageVersion = String(readJson(packagePath, {}).version || '').trim();
26
+ const environmentVersion = String(process.env.MICROI_TOOL_VERSION || '').trim();
27
+ // An installed plugin is self-describing. A stale parent MCP environment must
28
+ // never force a newer package to reconnect to an older shared broker. The
29
+ // version environment remains authoritative for the package-less stable CLI
30
+ // runtime, whose current.json launcher selects one immutable version folder.
31
+ const pluginVersion = /^\d+(?:\.\d+){2}$/.test(packageVersion)
32
+ ? packageVersion
33
+ : (/^\d+(?:\.\d+){2}$/.test(environmentVersion) ? environmentVersion : '0.0.0');
34
+ let inputBuffer = '';
35
+ let requestSequence = 0;
36
+ let shuttingDown = false;
37
+ let brokerClient = null;
38
+
39
+ function readJson(filePath, fallback) {
40
+ try {
41
+ const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
42
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : fallback;
43
+ } catch {
44
+ return fallback;
45
+ }
46
+ }
47
+
48
+ function normalizeUrl(value) {
49
+ return String(value || '').trim().replace(/\/+$/, '');
50
+ }
51
+
52
+ function safeName(value) {
53
+ return String(value || '').normalize('NFKD').replace(/[^\x00-\x7F]/g, '')
54
+ .replace(/[^A-Za-z0-9_]+/g, '_').replace(/_+/g, '_')
55
+ .replace(/^_+|_+$/g, '').toLowerCase().slice(0, 48);
56
+ }
57
+
58
+ function hostPart(profile) {
59
+ try { return safeName(new URL(profile.apiBaseUrl).host); } catch { return safeName(profile.apiBaseUrl); }
60
+ }
61
+
62
+ function profileIdentity(profile) {
63
+ return [
64
+ normalizeUrl(profile.apiBaseUrl),
65
+ String(profile.osClient || '').trim(),
66
+ String(profile.osClientType || '').trim(),
67
+ String(profile.osClientNetwork || '').trim(),
68
+ ].join('|');
69
+ }
70
+
71
+ function candidateRoots() {
72
+ const values = [
73
+ process.env.MICROI_WORKSPACE_ROOT,
74
+ process.env.CODEX_WORKSPACE_ROOT,
75
+ process.env.INIT_CWD,
76
+ process.cwd(),
77
+ ].filter(Boolean).map(value => path.resolve(value));
78
+ const roots = [];
79
+ for (const start of values) {
80
+ let current = start;
81
+ while (current && !roots.includes(current)) {
82
+ roots.push(current);
83
+ const parent = path.dirname(current);
84
+ if (parent === current) break;
85
+ current = parent;
86
+ }
87
+ }
88
+ return roots;
89
+ }
90
+
91
+ function resolveWorkspace() {
92
+ const explicitConfig = process.env.MICROI_CONFIG_PATH
93
+ ? path.resolve(process.env.MICROI_CONFIG_PATH)
94
+ : '';
95
+ const candidates = explicitConfig
96
+ ? [explicitConfig]
97
+ : candidateRoots().flatMap(root => [
98
+ path.join(root, CONFIG_DIR, CONFIG_FILE),
99
+ path.join(root, CONFIG_DIR.toLowerCase(), CONFIG_FILE),
100
+ ]);
101
+ const configPath = candidates.find(filePath => fs.existsSync(filePath));
102
+ const fallbackRoot = path.resolve(process.env.MICROI_WORKSPACE_ROOT || process.cwd());
103
+ if (!configPath) {
104
+ return {
105
+ workspaceRoot: fallbackRoot,
106
+ syncRoot: path.join(fallbackRoot, CONFIG_DIR),
107
+ configPath: path.join(fallbackRoot, CONFIG_DIR, CONFIG_FILE),
108
+ tokenPath: path.join(fallbackRoot, CONFIG_DIR, TOKEN_FILE),
109
+ config: {},
110
+ };
111
+ }
112
+ const syncRoot = path.dirname(configPath);
113
+ return {
114
+ workspaceRoot: path.dirname(syncRoot),
115
+ syncRoot,
116
+ configPath,
117
+ tokenPath: path.join(syncRoot, TOKEN_FILE),
118
+ config: readJson(configPath, {}),
119
+ };
120
+ }
121
+
122
+ function scheduleAutomaticUpdate() {
123
+ if (process.env.MICROI_DISABLE_AUTO_UPDATE === '1') return;
124
+ const cliPath = path.join(__dirname, 'microi-cli.js');
125
+ if (!fs.existsSync(cliPath)) return;
126
+ const workspace = resolveWorkspace();
127
+ try {
128
+ const child = childProcess.spawn(process.execPath, [
129
+ cliPath,
130
+ 'update',
131
+ '--background',
132
+ '--workspace',
133
+ workspace.workspaceRoot,
134
+ '--json',
135
+ ], {
136
+ cwd: pluginRoot,
137
+ detached: true,
138
+ windowsHide: true,
139
+ stdio: 'ignore',
140
+ env: { ...process.env },
141
+ });
142
+ // 不等待更新、不重启当前 Router。新版本仅供后续新进程使用。
143
+ child.on('error', () => { /* 下一次启动自动重试 */ });
144
+ child.unref();
145
+ } catch {
146
+ // 自动更新永远不能成为 MCP 启动门禁。
147
+ }
148
+ }
149
+
150
+ function loadProfiles() {
151
+ const workspace = resolveWorkspace();
152
+ const configured = Array.isArray(workspace.config.profiles)
153
+ ? workspace.config.profiles
154
+ : [];
155
+ const rawProfiles = configured.length > 0
156
+ ? configured
157
+ : (workspace.config.apiBaseUrl ? [{
158
+ label: workspace.config.osClient || workspace.config.apiBaseUrl,
159
+ apiBaseUrl: workspace.config.apiBaseUrl,
160
+ osClient: workspace.config.osClient || '',
161
+ }] : []);
162
+ const tokens = readJson(workspace.tokenPath, {});
163
+ const identityNames = rawProfiles.map(profile => safeName(profile.osClient || profile.label) || hostPart(profile) || 'default');
164
+ const counts = identityNames.reduce((map, name) => map.set(name, (map.get(name) || 0) + 1), new Map());
165
+ const used = new Set();
166
+ const profiles = rawProfiles.filter(profile => /^https?:\/\//i.test(String(profile.apiBaseUrl || ''))).map((raw, index) => {
167
+ const profile = {
168
+ label: String(raw.label || raw.sysShortTitle || raw.sysTitle || raw.osClient || raw.apiBaseUrl || ''),
169
+ apiBaseUrl: normalizeUrl(raw.apiBaseUrl),
170
+ osClient: String(raw.osClient || '').trim(),
171
+ osClientType: String(raw.osClientType || '').trim(),
172
+ osClientNetwork: String(raw.osClientNetwork || '').trim(),
173
+ };
174
+ const identityName = identityNames[index];
175
+ const host = hostPart(profile);
176
+ const base = `microi_${identityName}`;
177
+ let name = counts.get(identityName) > 1 && host && host !== identityName ? `${base}_${host}` : base;
178
+ if (used.has(name) && host) name = `${base}_${host}`;
179
+ let suffix = 2;
180
+ while (used.has(name)) name = `${base}_${suffix++}`;
181
+ used.add(name);
182
+ const exactKey = profileIdentity(profile);
183
+ const legacyKey = profile.osClient ? `${profile.apiBaseUrl}|${profile.osClient}` : profile.apiBaseUrl;
184
+ return {
185
+ ...profile,
186
+ index: index + 1,
187
+ name,
188
+ identity: exactKey,
189
+ loggedIn: typeof tokens[exactKey] === 'string' || typeof tokens[legacyKey] === 'string',
190
+ workspaceRoot: workspace.workspaceRoot,
191
+ syncRoot: workspace.syncRoot,
192
+ tokenPath: workspace.tokenPath,
193
+ };
194
+ });
195
+ return { workspace, profiles };
196
+ }
197
+
198
+ function publicProfile(profile) {
199
+ return {
200
+ index: profile.index,
201
+ name: profile.name,
202
+ label: profile.label,
203
+ apiBaseUrl: profile.apiBaseUrl,
204
+ osClient: profile.osClient,
205
+ osClientType: profile.osClientType,
206
+ osClientNetwork: profile.osClientNetwork,
207
+ loggedIn: profile.loggedIn,
208
+ };
209
+ }
210
+
211
+ function selectProfile(selector) {
212
+ const state = loadProfiles();
213
+ if (state.profiles.length === 0) {
214
+ throw new Error(`当前工作区尚未初始化 Microi。请在 ${state.workspace.workspaceRoot} 运行 bundled CLI 的 init 命令。`);
215
+ }
216
+ const raw = String(selector || '').trim();
217
+ if (raw) {
218
+ const lower = raw.toLowerCase();
219
+ const matches = state.profiles.filter(profile =>
220
+ String(profile.index) === raw
221
+ || profile.name.toLowerCase() === lower
222
+ || profile.osClient.toLowerCase() === lower
223
+ || profile.label.toLowerCase() === lower
224
+ || profile.identity.toLowerCase() === lower
225
+ );
226
+ if (matches.length === 1) return matches[0];
227
+ if (matches.length > 1) throw new Error(`连接选择“${raw}”存在歧义,请改用 profiles 返回的 name。`);
228
+ throw new Error(`未找到连接“${raw}”。可用连接:${state.profiles.map(item => item.name).join(', ')}`);
229
+ }
230
+ const loggedIn = state.profiles.filter(profile => profile.loggedIn);
231
+ if (loggedIn.length === 1) return loggedIn[0];
232
+ if (state.profiles.length === 1) return state.profiles[0];
233
+ throw new Error(`当前工作区有 ${state.profiles.length} 个 Microi 连接,请先调用 action="profiles",再传 profile=name。`);
234
+ }
235
+
236
+ function buildApplicationsDir(profile) {
237
+ const title = profile.label || profile.apiBaseUrl || 'Microi';
238
+ const serverDir = `${title} (${hostPart(profile) || 'server'})`.replace(/[<>:"/\\|?*]/g, '_');
239
+ const osClientDir = [profile.osClient || 'default', profile.osClientType || 'default', profile.osClientNetwork || 'default']
240
+ .join('.').replace(/[<>:"/\\|?*]/g, '_');
241
+ return path.join(profile.syncRoot, serverDir, osClientDir, 'AI应用');
242
+ }
243
+
244
+ function brokerCoordinates(workspace) {
245
+ const normalizedRoot = process.platform === 'win32'
246
+ ? workspace.workspaceRoot.toLowerCase()
247
+ : workspace.workspaceRoot;
248
+ const key = crypto.createHash('sha256').update(`${normalizedRoot}\0${pluginVersion}`).digest('hex').slice(0, 24);
249
+ const runtimeRoot = path.resolve(process.env.MICROI_RUNTIME_HOME || path.join(os.homedir(), '.microi', 'runtime'));
250
+ const endpoint = process.env.MICROI_CODEX_BROKER_ENDPOINT || (process.platform === 'win32'
251
+ ? `\\\\.\\pipe\\microi-codex-${key}`
252
+ : path.join(runtimeRoot, 'brokers', `${key}.sock`));
253
+ return {
254
+ endpoint,
255
+ leasePath: path.resolve(process.env.MICROI_CODEX_BROKER_LEASE || path.join(runtimeRoot, 'leases', `microi-codex-${key}.json`)),
256
+ workspaceRoot: workspace.workspaceRoot,
257
+ };
258
+ }
259
+
260
+ function wait(milliseconds) {
261
+ return new Promise(resolve => setTimeout(resolve, milliseconds));
262
+ }
263
+
264
+ function readBrokerToken(leasePath) {
265
+ const lease = readJson(leasePath, {});
266
+ return typeof lease.token === 'string' ? lease.token : '';
267
+ }
268
+
269
+ class SharedBrokerClient {
270
+ constructor(workspace) {
271
+ this.coordinates = brokerCoordinates(workspace);
272
+ this.pending = new Map();
273
+ this.buffer = '';
274
+ this.closed = false;
275
+ this.spawnAttempted = false;
276
+ this.ready = this.connect();
277
+ }
278
+
279
+ startBroker() {
280
+ if (!fs.existsSync(brokerScriptPath)) {
281
+ throw new Error(`Codex Plugin 缺少共享 Broker:${brokerScriptPath}`);
282
+ }
283
+ const child = childProcess.spawn(process.execPath, [brokerScriptPath], {
284
+ cwd: pluginRoot,
285
+ detached: true,
286
+ windowsHide: true,
287
+ stdio: 'ignore',
288
+ env: {
289
+ ...process.env,
290
+ MICROI_CODEX_BROKER_ENDPOINT: this.coordinates.endpoint,
291
+ MICROI_CODEX_BROKER_LEASE: this.coordinates.leasePath,
292
+ MICROI_WORKSPACE_ROOT: this.coordinates.workspaceRoot,
293
+ MICROI_MCP_SERVER_PATH: mcpServerPath,
294
+ MICROI_TOOL_VERSION: pluginVersion,
295
+ },
296
+ });
297
+ child.on('error', () => { /* connect retry reports the actionable failure */ });
298
+ child.unref();
299
+ }
300
+
301
+ openSocket() {
302
+ return new Promise((resolve, reject) => {
303
+ const socket = net.createConnection(this.coordinates.endpoint);
304
+ const fail = error => {
305
+ socket.destroy();
306
+ reject(error);
307
+ };
308
+ socket.once('error', fail);
309
+ socket.once('connect', () => {
310
+ socket.off('error', fail);
311
+ this.socket = socket;
312
+ socket.setEncoding('utf8');
313
+ socket.on('data', chunk => this.onData(chunk));
314
+ socket.on('error', error => this.rejectAll(error));
315
+ socket.on('close', () => {
316
+ this.closed = true;
317
+ this.rejectAll(new Error('Microi shared broker disconnected'));
318
+ });
319
+ resolve();
320
+ });
321
+ });
322
+ }
323
+
324
+ async connect() {
325
+ let lastError;
326
+ for (let attempt = 0; attempt < 50; attempt += 1) {
327
+ try {
328
+ await this.openSocket();
329
+ return;
330
+ } catch (error) {
331
+ lastError = error;
332
+ if (!this.spawnAttempted || attempt === 15) {
333
+ this.spawnAttempted = true;
334
+ this.startBroker();
335
+ }
336
+ await wait(Math.min(40 + attempt * 12, 250));
337
+ }
338
+ }
339
+ throw new Error(`无法连接 Microi 共享 Broker:${lastError instanceof Error ? lastError.message : String(lastError)}`);
340
+ }
341
+
342
+ onData(chunk) {
343
+ this.buffer += chunk.toString('utf8');
344
+ let newline;
345
+ while ((newline = this.buffer.indexOf('\n')) >= 0) {
346
+ const line = this.buffer.slice(0, newline).trim();
347
+ this.buffer = this.buffer.slice(newline + 1);
348
+ if (!line) continue;
349
+ let message;
350
+ try { message = JSON.parse(line); } catch { continue; }
351
+ if (message.id == null) continue;
352
+ const pending = this.pending.get(String(message.id));
353
+ if (!pending) continue;
354
+ this.pending.delete(String(message.id));
355
+ if (message.error) pending.reject(new Error(typeof message.error === 'string' ? message.error : (message.error.message || JSON.stringify(message.error))));
356
+ else pending.resolve(message.result);
357
+ }
358
+ }
359
+
360
+ rejectAll(error) {
361
+ for (const pending of this.pending.values()) pending.reject(error);
362
+ this.pending.clear();
363
+ }
364
+
365
+ request(profile, method, params) {
366
+ return (async () => {
367
+ await this.ready;
368
+ if (this.closed || !this.socket || this.socket.destroyed) throw new Error('Microi shared broker is unavailable');
369
+ let auth = '';
370
+ for (let attempt = 0; attempt < 10 && !auth; attempt += 1) {
371
+ auth = readBrokerToken(this.coordinates.leasePath);
372
+ if (!auth) await wait(25);
373
+ }
374
+ if (!auth) throw new Error('Microi shared broker lease is unavailable');
375
+ const id = `router-${process.pid}-${++requestSequence}`;
376
+ const response = new Promise((resolve, reject) => this.pending.set(id, { resolve, reject }));
377
+ this.socket.write(`${JSON.stringify({
378
+ id,
379
+ auth,
380
+ type: 'request',
381
+ profile: { ...profile, applicationsDir: buildApplicationsDir(profile) },
382
+ method,
383
+ params,
384
+ })}\n`);
385
+ return response;
386
+ })();
387
+ }
388
+
389
+ close() {
390
+ try { this.socket?.end(); } catch { /* best effort */ }
391
+ }
392
+ }
393
+
394
+ function sharedBroker(profile) {
395
+ if (!brokerClient || brokerClient.closed) brokerClient = new SharedBrokerClient({ workspaceRoot: profile.workspaceRoot });
396
+ return brokerClient;
397
+ }
398
+
399
+ function requestThroughBroker(profile, method, params) {
400
+ return sharedBroker(profile).request(profile, method, params);
401
+ }
402
+
403
+ function resultText(value, isError = false) {
404
+ return {
405
+ content: [{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }],
406
+ ...(isError ? { isError: true } : {}),
407
+ };
408
+ }
409
+
410
+ function toolchainStatus(workspaceRoot) {
411
+ // 状态文件可能由另一工作区的后台更新器写入,只回读有限的诊断字段,
412
+ // 不透传日志/错误正文或未知字段,也不把安装版本冒充当前已加载版本。
413
+ const statusPath = path.join(path.resolve(process.env.MICROI_UPDATE_HOME || path.join(os.homedir(), '.microi', 'updater')), 'status.json');
414
+ let status = {};
415
+ try {
416
+ if (fs.statSync(statusPath).size <= 64 * 1024) status = readJson(statusPath, {});
417
+ } catch { /* 缺失/损坏状态不阻断连接发现 */ }
418
+ const version = value => /^\d+(?:\.\d+){2}$/.test(String(value || '')) ? String(value) : 'unknown';
419
+ const states = ['scheduled', 'running', 'completed', 'completed-with-warnings', 'deferred'];
420
+ const skills = readJson(path.join(workspaceRoot, 'microi.skills', '.microi-skills-manifest.json'), {});
421
+ return {
422
+ package: '@microi.net/cli',
423
+ loadedVersion: pluginVersion,
424
+ workspaceSkillsVersion: version(skills.bundleVersion),
425
+ automaticUpdate: {
426
+ enabled: process.env.MICROI_DISABLE_AUTO_UPDATE !== '1',
427
+ statusPath,
428
+ state: states.includes(status.state) ? status.state : 'unknown',
429
+ latestObservedVersion: version(status.latestVersion),
430
+ globalVersion: version(status.globalVersion),
431
+ checkedAt: typeof status.attemptedAt === 'string' && /^\d{4}-\d{2}-\d{2}T[\d:.]+Z$/.test(status.attemptedAt) ? status.attemptedAt : null,
432
+ // 共享状态只证明记录中的工作区;其它目录须单独 doctor 验证。
433
+ appliesToWorkspace: typeof status.workspace === 'string' && path.resolve(status.workspace) === path.resolve(workspaceRoot),
434
+ },
435
+ };
436
+ }
437
+
438
+ function baseInstructions() {
439
+ return `先调用 microi_codex action="profiles";多连接时把返回的 name 作为 profile。所有业务动作由同源 Microi MCP 工具处理。
440
+ 每次新建或接续吾码任务必须先完整读取工作区 microi.skills/workspace-conventions/SKILL.md;尚未初始化时读取插件 skills/workspace-conventions/SKILL.md。同样强制执行其中的进度播报与非阻塞自动更新规范,适用于任何 AI 模型。
441
+ 首次说明必须以“Microi吾码开发工具版本”报告实际版本、官方最新版本及自动更新/Skills/MCP 同步状态;profiles.toolchain.loadedVersion=${pluginVersion} 只代表本进程,历史 latestObservedVersion 必须附 checkedAt,未知项写检测中,禁止猜测全局 CLI 或 npm 最新版。
442
+ 已有 CLI 时必须执行 microi update --background --workspace "<工作区绝对路径>" --json,自动升级 @microi.net/cli 到 npm 官方 latest 并同步 Skills/AI/MCP;不得等更新结束才继续工作,不终止或强制重载当前会话。用户明确关闭或宿主禁止执行时报告限制。`;
443
+ }
444
+
445
+ function toolCatalog() {
446
+ return [{
447
+ name: 'microi_codex',
448
+ description: 'Microi吾码统一入口。每次吾码任务先用 action="profiles" 查看连接与 toolchain 版本/自动更新状态,并读取 workspace-conventions/SKILL.md 执行强制版本播报与后台升级;多连接时传 profile=name。其余 action 复用原工具验证、确认、审计和回读。',
449
+ inputSchema: {
450
+ type: 'object',
451
+ additionalProperties: false,
452
+ required: ['action'],
453
+ properties: {
454
+ profile: { type: 'string', description: '可选:profiles 返回的 name、序号、OsClient 或连接名称。多连接时必填。' },
455
+ action: { type: 'string', description: 'profiles | list_tools | describe_tool | 任意原始 microi_* 工具名' },
456
+ params: { type: 'object', additionalProperties: true, description: '所选 action 的参数。' },
457
+ },
458
+ },
459
+ }];
460
+ }
461
+
462
+ async function callRouterTool(args) {
463
+ const action = String(args?.action || '').trim();
464
+ if (!action) return resultText('action 不能为空', true);
465
+ if (action === 'profiles' || action === 'profile_list') {
466
+ const state = loadProfiles();
467
+ return resultText({
468
+ workspace: state.workspace.workspaceRoot,
469
+ configPath: state.workspace.configPath,
470
+ count: state.profiles.length,
471
+ profiles: state.profiles.map(publicProfile),
472
+ toolchain: toolchainStatus(state.workspace.workspaceRoot),
473
+ next: state.profiles.length > 1 ? '后续调用请传 profile=name。' : '后续调用可省略 profile。',
474
+ });
475
+ }
476
+ try {
477
+ const profile = selectProfile(args?.profile);
478
+ return await requestThroughBroker(profile, 'tools/call', {
479
+ name: 'microi_codex',
480
+ arguments: { action, params: args?.params || {} },
481
+ });
482
+ } catch (error) {
483
+ return resultText(`Microi Agentx 路由失败:${error instanceof Error ? error.message : String(error)}`, true);
484
+ }
485
+ }
486
+
487
+ function resourceList() {
488
+ const state = loadProfiles();
489
+ return [{
490
+ uri: 'microi://codex/profiles',
491
+ name: 'microi_profiles',
492
+ title: 'Microi connection profiles',
493
+ description: '当前工作区 Microi 连接与登录状态。',
494
+ mimeType: 'application/json',
495
+ }, ...state.profiles.flatMap(profile => ['status', 'tools'].map(kind => ({
496
+ uri: `microi://codex/${encodeURIComponent(profile.name)}/${kind}`,
497
+ name: `microi_${profile.name}_${kind}`,
498
+ title: `Microi ${profile.label || profile.name} ${kind}`,
499
+ description: kind === 'status' ? '只读连接状态。' : '该连接的原始工具目录。',
500
+ mimeType: 'application/json',
501
+ })))];
502
+ }
503
+
504
+ async function readResource(uri) {
505
+ if (uri === 'microi://codex/profiles') {
506
+ const state = loadProfiles();
507
+ return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify({ profiles: state.profiles.map(publicProfile), toolchain: toolchainStatus(state.workspace.workspaceRoot) }, null, 2) }] };
508
+ }
509
+ const match = /^microi:\/\/codex\/([^/]+)\/(status|tools|action\/([^/]+)\/(.*))$/.exec(uri);
510
+ if (!match) throw new Error(`不支持的 Microi 资源 URI:${uri}`);
511
+ const profile = selectProfile(decodeURIComponent(match[1]));
512
+ let action;
513
+ let params = {};
514
+ if (match[2] === 'status') action = 'microi_get_status';
515
+ else if (match[2] === 'tools') action = 'list_tools';
516
+ else {
517
+ action = decodeURIComponent(match[3]);
518
+ const raw = decodeURIComponent(match[4] || '');
519
+ params = raw ? JSON.parse(raw) : {};
520
+ }
521
+ const result = await requestThroughBroker(profile, 'tools/call', {
522
+ name: 'microi_codex',
523
+ arguments: { action, params },
524
+ });
525
+ return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(result, null, 2) }] };
526
+ }
527
+
528
+ function send(message) {
529
+ process.stdout.write(`${JSON.stringify(message)}\n`);
530
+ }
531
+
532
+ function sendResult(id, result) {
533
+ send({ jsonrpc: '2.0', id, result });
534
+ }
535
+
536
+ function sendError(id, code, message) {
537
+ send({ jsonrpc: '2.0', id, error: { code, message } });
538
+ }
539
+
540
+ async function handle(message) {
541
+ if (!message || message.jsonrpc !== '2.0') return;
542
+ const { id, method, params } = message;
543
+ if (method === 'initialize') {
544
+ sendResult(id, {
545
+ protocolVersion: params?.protocolVersion || PROTOCOL_VERSION,
546
+ capabilities: { tools: {}, resources: {} },
547
+ serverInfo: { name: 'Microi-Codex-Plugin', version: pluginVersion },
548
+ instructions: baseInstructions(),
549
+ });
550
+ return;
551
+ }
552
+ if (method === 'ping') { sendResult(id, {}); return; }
553
+ if (method === 'tools/list') { sendResult(id, { tools: toolCatalog() }); return; }
554
+ if (method === 'tools/call') {
555
+ if (params?.name !== 'microi_codex') { sendResult(id, resultText(`Unknown tool: ${params?.name || '(empty)'}`, true)); return; }
556
+ sendResult(id, await callRouterTool(params.arguments || {})); return;
557
+ }
558
+ if (method === 'resources/list') { sendResult(id, { resources: resourceList() }); return; }
559
+ if (method === 'resources/templates/list') {
560
+ sendResult(id, { resourceTemplates: [{
561
+ uriTemplate: 'microi://codex/{profile}/action/{action}/{params}',
562
+ name: 'microi_codex_action',
563
+ title: 'Microi action fallback',
564
+ description: '工具未注入时,按连接路由到同一原始 Microi 工具。params 是 URI 编码 JSON 对象。',
565
+ mimeType: 'application/json',
566
+ }] });
567
+ return;
568
+ }
569
+ if (method === 'resources/read') {
570
+ try { sendResult(id, await readResource(String(params?.uri || ''))); }
571
+ catch (error) { sendError(id, -32002, error instanceof Error ? error.message : String(error)); }
572
+ return;
573
+ }
574
+ if (method === 'shutdown') { sendResult(id, {}); shutdown(); return; }
575
+ if (method?.startsWith('notifications/')) return;
576
+ if (id != null) sendError(id, -32601, `Method not found: ${method}`);
577
+ }
578
+
579
+ function shutdown() {
580
+ if (shuttingDown) return;
581
+ shuttingDown = true;
582
+ brokerClient?.close();
583
+ brokerClient = null;
584
+ }
585
+
586
+ process.stdin.setEncoding('utf8');
587
+ const automaticUpdateTimer = setTimeout(scheduleAutomaticUpdate, 1500);
588
+ automaticUpdateTimer.unref();
589
+ process.stdin.on('data', chunk => {
590
+ inputBuffer += chunk;
591
+ let newline;
592
+ while ((newline = inputBuffer.indexOf('\n')) >= 0) {
593
+ const line = inputBuffer.slice(0, newline).trim();
594
+ inputBuffer = inputBuffer.slice(newline + 1);
595
+ if (!line) continue;
596
+ let message;
597
+ try { message = JSON.parse(line); }
598
+ catch { continue; }
599
+ Promise.resolve(handle(message)).catch(error => {
600
+ if (message.id != null) sendError(message.id, -32603, error instanceof Error ? error.message : String(error));
601
+ });
602
+ }
603
+ });
604
+ process.stdin.on('end', () => { shutdown(); process.exit(0); });
605
+ process.stdin.on('error', () => { shutdown(); process.exit(1); });
606
+ process.stdout.on('error', error => { shutdown(); process.exit(error?.code === 'EPIPE' ? 0 : 1); });
607
+ process.on('disconnect', () => { shutdown(); process.exit(0); });
608
+ for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) process.on(signal, () => { shutdown(); process.exit(0); });
609
+ process.on('exit', shutdown);
610
+
611
+ const parentPid = process.ppid;
612
+ if (parentPid > 1) {
613
+ const parentWatchTimer = setInterval(() => {
614
+ try { process.kill(parentPid, 0); }
615
+ catch (error) { if (error?.code === 'ESRCH') { shutdown(); process.exit(0); } }
616
+ }, 5000);
617
+ parentWatchTimer.unref();
618
+ }