evolcore 0.0.1 → 0.0.2

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 (371) hide show
  1. package/CHANGELOG.md +813 -0
  2. package/LICENSE +21 -0
  3. package/MIGRATION-0.5.0.md +378 -0
  4. package/README.md +318 -14
  5. package/ROLE_ACCESS_CONTROL.md +174 -0
  6. package/assets/.env.template +4 -0
  7. package/assets/brand/evolcore/README.md +19 -0
  8. package/assets/brand/evolcore/evolcore-app-icon.png +0 -0
  9. package/assets/brand/evolcore/evolcore-app-icon.svg +13 -0
  10. package/assets/brand/evolcore/evolcore-brand-board.png +0 -0
  11. package/assets/brand/evolcore/evolcore-brand-board.svg +126 -0
  12. package/assets/brand/evolcore/evolcore-logo-kit.zip +0 -0
  13. package/assets/brand/evolcore/evolcore-logo-reverse.png +0 -0
  14. package/assets/brand/evolcore/evolcore-logo-reverse.svg +14 -0
  15. package/assets/brand/evolcore/evolcore-logo.png +0 -0
  16. package/assets/brand/evolcore/evolcore-logo.svg +14 -0
  17. package/assets/brand/evolcore/evolcore-mark.png +0 -0
  18. package/assets/brand/evolcore/evolcore-mark.svg +10 -0
  19. package/bin/ec-safe-output.js +161 -0
  20. package/bin/ec.js +29 -0
  21. package/dist/agents/baseagent.js +163 -0
  22. package/dist/agents/claude-runner.js +2385 -0
  23. package/dist/agents/codex-app-server-client.js +448 -0
  24. package/dist/agents/codex-runner.js +2639 -0
  25. package/dist/agents/gemini-runner.js +666 -0
  26. package/dist/agents/runner-types.js +75 -0
  27. package/dist/aun/aid/agentmd.js +216 -0
  28. package/dist/aun/aid/client.js +132 -0
  29. package/dist/aun/aid/control-aid.js +91 -0
  30. package/dist/aun/aid/identity.js +518 -0
  31. package/dist/aun/aid/index.js +4 -0
  32. package/dist/aun/aid/store.js +74 -0
  33. package/dist/aun/aid/types.js +1 -0
  34. package/dist/aun/aid/validation.js +21 -0
  35. package/dist/aun/group-identity.js +10 -0
  36. package/dist/aun/msg/group-index.js +6 -0
  37. package/dist/aun/msg/group.js +1231 -0
  38. package/dist/aun/msg/history.js +123 -0
  39. package/dist/aun/msg/index.js +5 -0
  40. package/dist/aun/msg/p2p.js +393 -0
  41. package/dist/aun/msg/payload-type.js +27 -0
  42. package/dist/aun/msg/upload.js +137 -0
  43. package/dist/aun/outbox.js +160 -0
  44. package/dist/aun/rpc/caller.js +42 -0
  45. package/dist/aun/rpc/connection.js +25 -0
  46. package/dist/aun/rpc/index.js +2 -0
  47. package/dist/aun/service-proxy.js +225 -0
  48. package/dist/aun/storage/download.js +29 -0
  49. package/dist/aun/storage/index.js +3 -0
  50. package/dist/aun/storage/manage.js +10 -0
  51. package/dist/aun/storage/upload.js +68 -0
  52. package/dist/channels/aun.js +4147 -0
  53. package/dist/channels/daemon.js +422 -0
  54. package/dist/channels/dingtalk.js +649 -0
  55. package/dist/channels/feishu.js +1789 -0
  56. package/dist/channels/qqbot.js +409 -0
  57. package/dist/channels/wechat.js +817 -0
  58. package/dist/channels/wecom.js +565 -0
  59. package/dist/cli/agent-command.js +641 -0
  60. package/dist/cli/agent.js +1059 -0
  61. package/dist/cli/aun-commands.js +1948 -0
  62. package/dist/cli/bench.js +1228 -0
  63. package/dist/cli/cli-argv.js +66 -0
  64. package/dist/cli/code-stats.js +329 -0
  65. package/dist/cli/command-log.js +82 -0
  66. package/dist/cli/config-selector.js +69 -0
  67. package/dist/cli/config.js +261 -0
  68. package/dist/cli/ctl-command.js +62 -0
  69. package/dist/cli/daemon-commands.js +2887 -0
  70. package/dist/cli/fs-command.js +1447 -0
  71. package/dist/cli/handoff-command.js +302 -0
  72. package/dist/cli/help.js +31 -0
  73. package/dist/cli/index.js +358 -0
  74. package/dist/cli/init-channel.js +1377 -0
  75. package/dist/cli/init.js +553 -0
  76. package/dist/cli/link-rules.js +240 -0
  77. package/dist/cli/model.js +590 -0
  78. package/dist/cli/net-check.js +723 -0
  79. package/dist/cli/queue-command.js +126 -0
  80. package/dist/cli/response.js +345 -0
  81. package/dist/cli/restart-monitor.js +456 -0
  82. package/dist/cli/stats.js +607 -0
  83. package/dist/cli/task-context.js +80 -0
  84. package/dist/cli/trigger-command.js +505 -0
  85. package/dist/cli/version.js +88 -0
  86. package/dist/cli/watch-logs.js +33 -0
  87. package/dist/cli/watch-msg.js +673 -0
  88. package/dist/config/boot-log.js +266 -0
  89. package/dist/config/builtin-role-templates.js +30 -0
  90. package/dist/config/builtin-roles.js +85 -0
  91. package/dist/config/config-batch-get.js +11 -0
  92. package/dist/config/config-field-policy.js +261 -0
  93. package/dist/config/config-manager.js +916 -0
  94. package/dist/config/config-operation-service.js +384 -0
  95. package/dist/config/contact-book.js +371 -0
  96. package/dist/config/gateway-config.js +858 -0
  97. package/dist/config/lifecycle.js +17 -0
  98. package/dist/config/mention-mode.js +27 -0
  99. package/dist/config/merge.js +161 -0
  100. package/dist/config/owner-policy.js +4 -0
  101. package/dist/config/peer-role-resolver.js +139 -0
  102. package/dist/config/resolved-config-op.js +483 -0
  103. package/dist/config/role-config-v4-startup.js +32 -0
  104. package/dist/config/role-config-v5-startup.js +27 -0
  105. package/dist/config/role-schema.js +105 -0
  106. package/dist/config/role-service.js +159 -0
  107. package/dist/config/role-store.js +204 -0
  108. package/dist/config/roles.js +55 -0
  109. package/dist/config/schema-registry.js +154 -0
  110. package/dist/config/snapshot.js +598 -0
  111. package/dist/config-store.js +503 -0
  112. package/dist/core/auth/agent-delegation.js +111 -0
  113. package/dist/core/auth/auth-gateway.js +166 -0
  114. package/dist/core/auth/authenticated-actor.js +6 -0
  115. package/dist/core/auth/authorization-audit.js +110 -0
  116. package/dist/core/auth/operation-authorizer.js +718 -0
  117. package/dist/core/auth/operation-catalog.js +675 -0
  118. package/dist/core/baseagent-loader.js +54 -0
  119. package/dist/core/bootstrap-service.js +175 -0
  120. package/dist/core/capability/capability-manager.js +316 -0
  121. package/dist/core/capability/providers/claude-capability-provider.js +176 -0
  122. package/dist/core/capability/providers/codex-capability-provider.js +148 -0
  123. package/dist/core/capability/providers/gemini-capability-provider.js +10 -0
  124. package/dist/core/capability/types.js +27 -0
  125. package/dist/core/causation/audit.js +103 -0
  126. package/dist/core/causation/aun-association.js +111 -0
  127. package/dist/core/causation/context.js +93 -0
  128. package/dist/core/causation/index.js +4 -0
  129. package/dist/core/causation/types.js +2 -0
  130. package/dist/core/channel-loader.js +277 -0
  131. package/dist/core/command/agent-control.js +616 -0
  132. package/dist/core/command/cli-intent-parser.js +225 -0
  133. package/dist/core/command/command-handler.js +1638 -0
  134. package/dist/core/command/menu-handler.js +3354 -0
  135. package/dist/core/command/menu-protocol.js +247 -0
  136. package/dist/core/command/role-menu.js +1524 -0
  137. package/dist/core/command/slash-gate.js +148 -0
  138. package/dist/core/command/slash-handler.js +3056 -0
  139. package/dist/core/daemon-file-cache.js +216 -0
  140. package/dist/core/event-bus.js +32 -0
  141. package/dist/core/event-catalog.js +740 -0
  142. package/dist/core/evolagent-registry.js +546 -0
  143. package/dist/core/evolagent.js +331 -0
  144. package/dist/core/handoff/dispatcher.js +229 -0
  145. package/dist/core/handoff/mutex.js +46 -0
  146. package/dist/core/handoff/runtime.js +324 -0
  147. package/dist/core/handoff/store.js +537 -0
  148. package/dist/core/handoff/types.js +12 -0
  149. package/dist/core/inference/text-inference.js +173 -0
  150. package/dist/core/interaction-registration.js +10 -0
  151. package/dist/core/interaction-router.js +278 -0
  152. package/dist/core/message/create-status.js +67 -0
  153. package/dist/core/message/im-renderer.js +657 -0
  154. package/dist/core/message/items-formatter.js +76 -0
  155. package/dist/core/message/logical-queue-bridge.js +123 -0
  156. package/dist/core/message/message-bridge.js +803 -0
  157. package/dist/core/message/message-cache.js +56 -0
  158. package/dist/core/message/message-log.js +339 -0
  159. package/dist/core/message/message-processor.js +4 -0
  160. package/dist/core/message/message-queue.js +1436 -0
  161. package/dist/core/message/message-utils.js +70 -0
  162. package/dist/core/message/peer-mode.js +105 -0
  163. package/dist/core/message/pending-hints.js +232 -0
  164. package/dist/core/message/response-depth.js +33 -0
  165. package/dist/core/message/response-engine.js +3776 -0
  166. package/dist/core/message/response-snapshot.js +83 -0
  167. package/dist/core/message/stream-debouncer.js +130 -0
  168. package/dist/core/message/stream-idle-monitor.js +124 -0
  169. package/dist/core/model/config-scope.js +162 -0
  170. package/dist/core/model/field-scope.js +78 -0
  171. package/dist/core/model/model-catalog.js +227 -0
  172. package/dist/core/model/model-diagnostics.js +182 -0
  173. package/dist/core/model/model-permission.js +90 -0
  174. package/dist/core/permission/approval-gateway.js +1017 -0
  175. package/dist/core/permission/ec-command-parser.js +347 -0
  176. package/dist/core/permission/execution-sandbox.js +16 -0
  177. package/dist/core/permission/index.js +6 -0
  178. package/dist/core/permission/mode.js +24 -0
  179. package/dist/core/permission/sandbox-runtime.js +265 -0
  180. package/dist/core/permission/tool-policy.js +987 -0
  181. package/dist/core/permission/unix-socket-policy.js +99 -0
  182. package/dist/core/protected-paths.js +330 -0
  183. package/dist/core/relation/peer-identity.js +222 -0
  184. package/dist/core/relation/peer-key.js +1 -0
  185. package/dist/core/role/runtime-policy.js +141 -0
  186. package/dist/core/session/adapters/claude-session-file-adapter.js +218 -0
  187. package/dist/core/session/adapters/codex-session-file-adapter.js +333 -0
  188. package/dist/core/session/adapters/gemini-session-file-adapter.js +181 -0
  189. package/dist/core/session/session-file-adapter.js +7 -0
  190. package/dist/core/session/session-file-health.js +45 -0
  191. package/dist/core/session/session-fs-store.js +232 -0
  192. package/dist/core/session/session-key.js +24 -0
  193. package/dist/core/session/session-manager.js +1587 -0
  194. package/dist/core/session/session-mapper.js +100 -0
  195. package/dist/core/session/session-renew.js +314 -0
  196. package/dist/core/session/session-title.js +128 -0
  197. package/dist/core/session/session-turn-coordinator.js +205 -0
  198. package/dist/core/session/session-turns.js +67 -0
  199. package/dist/core/system-channels.js +29 -0
  200. package/dist/eck/baseagent-caps.js +18 -0
  201. package/dist/eck/detect.js +47 -0
  202. package/dist/eck/group-rules-sync.js +345 -0
  203. package/dist/eck/init.js +77 -0
  204. package/dist/eck/kit-renderer.js +359 -0
  205. package/dist/eck/manifest-engine.js +446 -0
  206. package/dist/eck/message-renderer.js +199 -0
  207. package/dist/eck/rules-loader.js +28 -0
  208. package/dist/index.js +2870 -4
  209. package/dist/ipc.js +748 -0
  210. package/dist/paths.js +262 -0
  211. package/dist/product.js +18 -0
  212. package/dist/response-system/context-builder.js +71 -0
  213. package/dist/response-system/coordinator.js +117 -0
  214. package/dist/response-system/decision-executor.js +86 -0
  215. package/dist/response-system/engines/v1/index.js +21 -0
  216. package/dist/response-system/engines/v1/interactive-flow.js +27 -0
  217. package/dist/response-system/engines/v1/proactive-flow.js +137 -0
  218. package/dist/response-system/engines/v1/types.js +1 -0
  219. package/dist/response-system/extensions.js +41 -0
  220. package/dist/response-system/index.js +6 -0
  221. package/dist/response-system/modes/index.js +7 -0
  222. package/dist/response-system/modes/single-session/index.js +72 -0
  223. package/dist/response-system/queues/fifo-queue.js +44 -0
  224. package/dist/response-system/queues/index.js +6 -0
  225. package/dist/response-system/queues/lifo-queue.js +42 -0
  226. package/dist/response-system/queues/priority-queue.js +63 -0
  227. package/dist/response-system/registry.js +97 -0
  228. package/dist/response-system/resolver.js +37 -0
  229. package/dist/response-system/selector.js +23 -0
  230. package/dist/response-system/types.js +7 -0
  231. package/dist/stats/billing.js +151 -0
  232. package/dist/stats/budget.js +93 -0
  233. package/dist/stats/db.js +403 -0
  234. package/dist/stats/eck-vars.js +89 -0
  235. package/dist/stats/index.js +11 -0
  236. package/dist/stats/normalizer.js +80 -0
  237. package/dist/stats/price-resolver.js +138 -0
  238. package/dist/stats/query.js +763 -0
  239. package/dist/stats/role-budget.js +168 -0
  240. package/dist/stats/writer.js +151 -0
  241. package/dist/trigger/anomaly-store.js +258 -0
  242. package/dist/trigger/audit.js +152 -0
  243. package/dist/trigger/event-source.js +119 -0
  244. package/dist/trigger/feedback.js +685 -0
  245. package/dist/trigger/history.js +290 -0
  246. package/dist/trigger/manager.js +291 -0
  247. package/dist/trigger/parser.js +520 -0
  248. package/dist/trigger/patch.js +148 -0
  249. package/dist/trigger/scheduler.js +1600 -0
  250. package/dist/trigger/script-executor.js +155 -0
  251. package/dist/trigger/state.js +145 -0
  252. package/dist/trigger/types.js +1 -0
  253. package/dist/trigger/validation.js +621 -0
  254. package/dist/types.js +12 -0
  255. package/dist/utils/aid-bind.js +299 -0
  256. package/dist/utils/atomic-write.js +95 -0
  257. package/dist/utils/avatar-upload.js +123 -0
  258. package/dist/utils/cross-platform.js +297 -0
  259. package/dist/utils/ecweb-utils.js +73 -0
  260. package/dist/utils/error-dict.json +153 -0
  261. package/dist/utils/error-utils.js +349 -0
  262. package/dist/utils/instance-registry.js +437 -0
  263. package/dist/utils/locale.js +21 -0
  264. package/dist/utils/log-writer.js +224 -0
  265. package/dist/utils/logger.js +89 -0
  266. package/dist/utils/markdown-to-plain-text.js +20 -0
  267. package/dist/utils/media-cache.js +271 -0
  268. package/dist/utils/model-prices.jsonl +17 -0
  269. package/dist/utils/npm-ops.js +210 -0
  270. package/dist/utils/process-introspect.js +133 -0
  271. package/dist/utils/process-tree-stats.js +271 -0
  272. package/dist/utils/project-path.js +74 -0
  273. package/dist/utils/stats.js +410 -0
  274. package/dist/utils/tool-summary.js +284 -0
  275. package/dist/utils/welcome.js +268 -0
  276. package/kits/docs/GUIDE.md +20 -0
  277. package/kits/docs/INDEX.md +65 -0
  278. package/kits/docs/aun/CHEATSHEET.md +19 -0
  279. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
  280. package/kits/docs/channels/aun.md +65 -0
  281. package/kits/docs/channels/feishu.md +56 -0
  282. package/kits/docs/context-assembly.md +366 -0
  283. package/kits/docs/eck_templates/GUIDE.template.md +22 -0
  284. package/kits/docs/eck_templates/INDEX.template.md +28 -0
  285. package/kits/docs/eck_templates/path-registry.template.md +33 -0
  286. package/kits/docs/eck_templates/runtime.template.md +19 -0
  287. package/kits/docs/evolcore/INDEX.md +66 -0
  288. package/kits/docs/evolcore/agent.md +69 -0
  289. package/kits/docs/evolcore/aid.md +49 -0
  290. package/kits/docs/evolcore/config.md +149 -0
  291. package/kits/docs/evolcore/ctl.md +46 -0
  292. package/kits/docs/evolcore/event.md +216 -0
  293. package/kits/docs/evolcore/fs-architecture.md +1215 -0
  294. package/kits/docs/evolcore/fs.md +101 -0
  295. package/kits/docs/evolcore/group-fs.md +17 -0
  296. package/kits/docs/evolcore/group-rules.md +226 -0
  297. package/kits/docs/evolcore/group.md +141 -0
  298. package/kits/docs/evolcore/model.md +47 -0
  299. package/kits/docs/evolcore/msg.md +130 -0
  300. package/kits/docs/evolcore/response.md +80 -0
  301. package/kits/docs/evolcore/rpc.md +35 -0
  302. package/kits/docs/evolcore/self-summary.md +29 -0
  303. package/kits/docs/evolcore/stats.md +70 -0
  304. package/kits/docs/evolcore/storage.md +49 -0
  305. package/kits/docs/evolcore/trigger.md +524 -0
  306. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -0
  307. package/kits/docs/identity/PATH_OPS.md +16 -0
  308. package/kits/docs/identity/ROLE_DETAIL.md +23 -0
  309. package/kits/docs/identity/identity-tools.md +26 -0
  310. package/kits/docs/path-registry.md +43 -0
  311. package/kits/docs/prompt-loading-architecture.md +266 -0
  312. package/kits/docs/venues/aun-group.md +45 -0
  313. package/kits/docs/venues/aun-private.md +10 -0
  314. package/kits/docs/venues/client-desktop.md +10 -0
  315. package/kits/docs/venues/client-mobile.md +10 -0
  316. package/kits/docs/venues/feishu-group.md +13 -0
  317. package/kits/docs/venues/feishu-private.md +9 -0
  318. package/kits/docs/venues/group.md +25 -0
  319. package/kits/docs/venues/private.md +10 -0
  320. package/kits/eck_manifest.auxiliary.json +43 -0
  321. package/kits/eck_manifest.json +191 -0
  322. package/kits/eck_message_manifest.json +63 -0
  323. package/kits/migrations/README-role-config-v4.md +32 -0
  324. package/kits/migrations/migrate-role-config-v4.mjs +623 -0
  325. package/kits/migrations/migrate-role-config-v5.mjs +346 -0
  326. package/kits/migrations/rename-config-file.mjs +99 -0
  327. package/kits/rules/01-overview.md +142 -0
  328. package/kits/rules/02-navigation.md +76 -0
  329. package/kits/rules/03-identity.md +34 -0
  330. package/kits/rules/04-relation.md +59 -0
  331. package/kits/rules/05-venue.md +44 -0
  332. package/kits/rules/06-channel.md +59 -0
  333. package/kits/schemas/_meta.json +29 -0
  334. package/kits/schemas/agent-config.schema.1.json +177 -0
  335. package/kits/schemas/agent-config.schema.2.json +239 -0
  336. package/kits/schemas/agent-config.schema.3.json +119 -0
  337. package/kits/schemas/agent-config.schema.4.json +208 -0
  338. package/kits/schemas/agent-config.schema.5.json +326 -0
  339. package/kits/schemas/contact-book.schema.1.json +36 -0
  340. package/kits/schemas/daemon.schema.1.json +90 -0
  341. package/kits/schemas/defaults.schema.1.json +81 -0
  342. package/kits/schemas/menu-exec-schema-commands.md +208 -0
  343. package/kits/schemas/migrations/README.md +28 -0
  344. package/kits/schemas/relation-config.schema.1.json +158 -0
  345. package/kits/schemas/relation-config.schema.2.json +73 -0
  346. package/kits/schemas/relation-config.schema.3.json +50 -0
  347. package/kits/schemas/relation-config.schema.4.json +47 -0
  348. package/kits/schemas/role-config.schema.1.json +200 -0
  349. package/kits/schemas/role-registry.schema.1.json +35 -0
  350. package/kits/schemas/single-session.schema.1.json +31 -0
  351. package/kits/templates/bootstrap-welcome.md +15 -0
  352. package/kits/templates/message-fragments/handoff-request-to-target.md +13 -0
  353. package/kits/templates/message-fragments/handoff-response-to-origin.md +10 -0
  354. package/kits/templates/message-fragments/inject-default.md +2 -0
  355. package/kits/templates/message-fragments/item.md +2 -0
  356. package/kits/templates/roles/admin.json +8 -0
  357. package/kits/templates/roles/member.json +40 -0
  358. package/kits/templates/roles/owner.json +8 -0
  359. package/kits/templates/roles/visitor.json +39 -0
  360. package/kits/templates/system-fragments/baseagent.md +14 -0
  361. package/kits/templates/system-fragments/bootstrap.md +16 -0
  362. package/kits/templates/system-fragments/channel.md +48 -0
  363. package/kits/templates/system-fragments/commands.md +26 -0
  364. package/kits/templates/system-fragments/identity.md +11 -0
  365. package/kits/templates/system-fragments/relation.md +19 -0
  366. package/kits/templates/system-fragments/session.md +53 -0
  367. package/kits/templates/system-fragments/venue.md +31 -0
  368. package/package.json +50 -15
  369. package/dist/index.d.ts +0 -7
  370. package/dist/index.d.ts.map +0 -1
  371. package/dist/index.js.map +0 -1
@@ -0,0 +1,89 @@
1
+ import fs from 'fs';
2
+ import { resolvePaths } from '../paths.js';
3
+ import { LogWriter } from './log-writer.js';
4
+ let currentLevel = process.env.LOG_LEVEL || 'INFO';
5
+ const LEVELS = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3 };
6
+ const config = {
7
+ messageLog: process.env.MESSAGE_LOG === 'true',
8
+ eventLog: process.env.EVENT_LOG === 'true'
9
+ };
10
+ let writers = null;
11
+ fs.mkdirSync(resolvePaths().logs, { recursive: true });
12
+ function closeWriters(current) {
13
+ current.main.close();
14
+ current.message?.close();
15
+ current.event?.close();
16
+ current.channelIn.close();
17
+ current.channelOut.close();
18
+ }
19
+ function getWriters() {
20
+ const logDir = resolvePaths().logs;
21
+ if (writers?.logDir === logDir)
22
+ return writers;
23
+ if (writers)
24
+ closeWriters(writers);
25
+ writers = {
26
+ logDir,
27
+ main: new LogWriter({ baseName: 'daemon', logDir, rotation: 'hourly', retention: { hours: 24 } }),
28
+ message: config.messageLog
29
+ ? new LogWriter({ baseName: 'messages', logDir, rotation: 'hourly', retention: { hours: 24 } })
30
+ : null,
31
+ event: config.eventLog
32
+ ? new LogWriter({ baseName: 'events', logDir, rotation: 'hourly', retention: { hours: 24 } })
33
+ : null,
34
+ channelIn: new LogWriter({ baseName: 'channel-in', logDir, rotation: 'hourly', retention: { hours: 24 } }),
35
+ channelOut: new LogWriter({ baseName: 'channel-out', logDir, rotation: 'hourly', retention: { hours: 24 } }),
36
+ };
37
+ return writers;
38
+ }
39
+ function shouldLog(level) {
40
+ return (LEVELS[level] ?? 1) >= (LEVELS[currentLevel] ?? 1);
41
+ }
42
+ export function localTimestamp() {
43
+ const d = new Date();
44
+ const pad = (n) => String(n).padStart(2, '0');
45
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${String(d.getMilliseconds()).padStart(3, '0')}`;
46
+ }
47
+ function log(level, ...args) {
48
+ if (!shouldLog(level))
49
+ return;
50
+ const msg = `[${localTimestamp()}] [${level}] ${args.join(' ')}`;
51
+ getWriters().main.write(msg);
52
+ }
53
+ /**
54
+ * 设置日志级别(config 加载后调用,覆盖环境变量默认值)
55
+ * 优先级:config.debug.logLevel → LOG_LEVEL 环境变量 → 'INFO'
56
+ */
57
+ export function setLogLevel(level) {
58
+ const upper = level.toUpperCase();
59
+ if (upper in LEVELS) {
60
+ currentLevel = upper;
61
+ }
62
+ }
63
+ export function getLogLevel() {
64
+ return currentLevel;
65
+ }
66
+ export const logger = {
67
+ debug: (...args) => log('DEBUG', ...args),
68
+ info: (...args) => log('INFO', ...args),
69
+ warn: (...args) => log('WARN', ...args),
70
+ error: (...args) => log('ERROR', ...args),
71
+ message: (data) => {
72
+ const writer = getWriters().message;
73
+ if (!writer)
74
+ return;
75
+ writer.write(JSON.stringify({ ts: localTimestamp(), ...data }));
76
+ },
77
+ event: (data) => {
78
+ const writer = getWriters().event;
79
+ if (!writer)
80
+ return;
81
+ writer.write(JSON.stringify({ ts: localTimestamp(), ...data }));
82
+ },
83
+ channelIn: (data) => {
84
+ getWriters().channelIn.write(JSON.stringify({ ts: localTimestamp(), ...data }));
85
+ },
86
+ channelOut: (data) => {
87
+ getWriters().channelOut.write(JSON.stringify({ ts: localTimestamp(), ...data }));
88
+ }
89
+ };
@@ -0,0 +1,20 @@
1
+ export function markdownToPlainText(text) {
2
+ let result = text;
3
+ result = result.replace(/```[^\n]*\n?([\s\S]*?)```/g, (_, code) => code.trim());
4
+ result = result.replace(/!\[[^\]]*\]\([^)]*\)/g, '');
5
+ result = result.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1');
6
+ result = result.replace(/^\|[\s:|-]+\|$/gm, '');
7
+ result = result.replace(/^\|(.+)\|$/gm, (_, inner) => inner.split('|').map(cell => cell.trim()).join(' '));
8
+ result = result.replace(/\*\*(.+?)\*\*/g, '$1');
9
+ result = result.replace(/\*(.+?)\*/g, '$1');
10
+ result = result.replace(/__(.+?)__/g, '$1');
11
+ result = result.replace(/_(.+?)_/g, '$1');
12
+ result = result.replace(/~~(.+?)~~/g, '$1');
13
+ result = result.replace(/`([^`]+)`/g, '$1');
14
+ result = result.replace(/^#{1,6}\s+/gm, '');
15
+ result = result.replace(/^>\s?/gm, '');
16
+ result = result.replace(/^[-*_]{3,}$/gm, '');
17
+ result = result.replace(/^(\s*)[-*+]\s/gm, '$1');
18
+ result = result.replace(/^(\s*)\d+\.\s/gm, '$1');
19
+ return result.trim();
20
+ }
@@ -0,0 +1,271 @@
1
+ /**
2
+ * 统一媒体下载/缓存/SSRF防护框架
3
+ *
4
+ * 提供跨通道复用的文件处理能力:
5
+ * - 文件保存到 uploads 目录(自动去重、文件名清洗)
6
+ * - 图片类型白名单 + 大小限制
7
+ * - URL SSRF 防护(域名白名单 + 私有 IP 拦截)
8
+ * - 下载缓存(相同 key 不重复下载)
9
+ */
10
+ import path from 'path';
11
+ import fs from 'fs';
12
+ import crypto from 'crypto';
13
+ import { logger } from './logger.js';
14
+ // ── 常量 ──────────────────────────────────────────────────────────────────────
15
+ const DEFAULT_MAX_IMAGE_SIZE = 10 * 1024 * 1024; // 10 MB
16
+ const DEFAULT_MAX_FILE_SIZE = 100 * 1024 * 1024; // 100 MB
17
+ const ALLOWED_IMAGE_MIMES = new Set([
18
+ 'image/png', 'image/jpeg', 'image/gif', 'image/webp',
19
+ ]);
20
+ const UPLOADS_SUBDIR = '.evolcore/uploads';
21
+ // ── SSRF 防护 ─────────────────────────────────────────────────────────────────
22
+ /** 已知安全的 CDN 域名白名单 */
23
+ const ALLOWED_CDN_HOSTS = new Set([
24
+ 'novac2c.cdn.weixin.qq.com',
25
+ 'open.feishu.cn',
26
+ 'internal-api-lark-file.feishu.cn',
27
+ 'oapi.dingtalk.com',
28
+ 'api.dingtalk.com',
29
+ ]);
30
+ /** 私有 IP 段正则(IPv4) */
31
+ const PRIVATE_IP_RE = /^(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|127\.|0\.|169\.254\.|::1|fc|fd|fe80)/;
32
+ /**
33
+ * 检查 URL 是否安全(非 SSRF)
34
+ * - 拒绝私有 IP
35
+ * - 仅允许 https(或白名单域名的 http)
36
+ * - 可选:域名白名单模式
37
+ */
38
+ export function validateUrl(url, opts) {
39
+ let parsed;
40
+ try {
41
+ parsed = new URL(url);
42
+ }
43
+ catch {
44
+ return { ok: false, reason: `Invalid URL: ${url}` };
45
+ }
46
+ // 协议检查
47
+ if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
48
+ return { ok: false, reason: `Blocked protocol: ${parsed.protocol}` };
49
+ }
50
+ // 私有 IP 检查
51
+ const host = parsed.hostname;
52
+ if (PRIVATE_IP_RE.test(host)) {
53
+ return { ok: false, reason: `Blocked private IP: ${host}` };
54
+ }
55
+ // 域名白名单(如果提供)
56
+ const allowed = opts?.allowedHosts ?? ALLOWED_CDN_HOSTS;
57
+ if (allowed.size > 0 && !allowed.has(host)) {
58
+ return { ok: false, reason: `Host not in allowlist: ${host}` };
59
+ }
60
+ return { ok: true };
61
+ }
62
+ // ── 文件名清洗 ────────────────────────────────────────────────────────────────
63
+ /**
64
+ * 清洗文件名:
65
+ * - 移除路径穿越字符(只保留 basename)
66
+ * - 替换非法字符
67
+ * - 空结果兜底
68
+ */
69
+ export function sanitizeFileName(name) {
70
+ return path.basename(name).replace(/[<>:"|?*\x00-\x1f]/g, '_') || `file_${Date.now()}`;
71
+ }
72
+ // ── 图片验证 ──────────────────────────────────────────────────────────────────
73
+ /**
74
+ * 验证图片 Buffer:类型白名单 + 大小限制
75
+ * 返回检测到的 MIME 类型,验证失败返回 null + reason
76
+ */
77
+ export async function validateImage(buffer, opts) {
78
+ const maxSize = opts?.maxSize ?? DEFAULT_MAX_IMAGE_SIZE;
79
+ const allowed = opts?.allowedMimes ?? ALLOWED_IMAGE_MIMES;
80
+ if (buffer.length === 0) {
81
+ return { mime: null, reason: 'Empty buffer' };
82
+ }
83
+ if (buffer.length > maxSize) {
84
+ return { mime: null, reason: `Image too large: ${buffer.length} bytes (max ${maxSize})` };
85
+ }
86
+ // 动态导入 image-type(ESM only)
87
+ const { default: imageType } = await import('image-type');
88
+ // image-type 对极短/截断的 buffer 会抛 EndOfStreamError,捕获后按「无法识别」处理。
89
+ let type;
90
+ try {
91
+ type = await imageType(buffer);
92
+ }
93
+ catch (e) {
94
+ return { mime: null, reason: `Image type detection error: ${e.message}` };
95
+ }
96
+ if (!type) {
97
+ return { mime: null, reason: 'Unable to detect image type' };
98
+ }
99
+ if (!allowed.has(type.mime)) {
100
+ return { mime: null, reason: `Unsupported image type: ${type.mime}` };
101
+ }
102
+ return { mime: type.mime };
103
+ }
104
+ /**
105
+ * 把已下载的附件 Buffer 转成入站图片条目(供 baseagent 视觉通道)。
106
+ *
107
+ * 适用于所有「需要先下载再注入」的通道(AUN ticket 下载、未来其它 CDN 下载等)。
108
+ * 飞书/微信已有各自的 SDK 下载路径,可按需复用本函数统一 MIME 判定。
109
+ *
110
+ * 判定优先级:
111
+ * 1. magic bytes(image-type 库,最可靠,同时做大小/白名单校验)
112
+ * 2. 元数据 MIME 字段(att.content_type / mime_type / mimeType)
113
+ * 3. 文件名后缀
114
+ *
115
+ * @returns 是图片返回 InboundImage;非图片或校验失败返回 null。
116
+ */
117
+ export async function bufferToInboundImage(buffer, hints) {
118
+ // 1. magic bytes(含大小 + 白名单校验;validateImage 已吞掉 image-type 的异常)
119
+ const validated = await validateImage(buffer);
120
+ if (validated.mime) {
121
+ return { data: buffer.toString('base64'), mimeType: validated.mime };
122
+ }
123
+ // 2/3. magic bytes 未识别时,回退到元数据字段 / 文件名后缀
124
+ // (仍受 image 白名单约束,避免把任意文件当图片注入)
125
+ const metaCt = hints?.contentType || hints?.mimeType || '';
126
+ const byMeta = typeof metaCt === 'string' && ALLOWED_IMAGE_MIMES.has(metaCt) ? metaCt : '';
127
+ const byExt = hints?.filename
128
+ ? (ALLOWED_IMAGE_MIMES.has(guessMime(hints.filename)) ? guessMime(hints.filename) : '')
129
+ : '';
130
+ const fallback = byMeta || byExt;
131
+ if (fallback && buffer.length > 0 && buffer.length <= DEFAULT_MAX_IMAGE_SIZE) {
132
+ return { data: buffer.toString('base64'), mimeType: fallback };
133
+ }
134
+ return null;
135
+ }
136
+ /**
137
+ * 保存 Buffer 到 uploads 目录
138
+ * - 自动创建目录
139
+ * - 文件名清洗
140
+ * - 同名文件自动添加时间戳后缀
141
+ */
142
+ export function saveToUploads(buffer, rawFileName, projectPath, opts) {
143
+ const fileName = sanitizeFileName(rawFileName);
144
+ const uploadsDir = path.join(projectPath, UPLOADS_SUBDIR);
145
+ fs.mkdirSync(uploadsDir, { recursive: true });
146
+ let targetName = fileName;
147
+ const targetPath = path.join(uploadsDir, targetName);
148
+ // 去重:内容相同则复用
149
+ if (opts?.dedup !== false && fs.existsSync(targetPath)) {
150
+ const existingHash = hashFile(targetPath);
151
+ const newHash = crypto.createHash('md5').update(buffer).digest('hex');
152
+ if (existingHash === newHash) {
153
+ logger.debug(`[MediaCache] Dedup hit: ${targetName}`);
154
+ return { filePath: targetPath, fileName: targetName, size: buffer.length };
155
+ }
156
+ // 不同内容同名 → 加时间戳
157
+ const ext = path.extname(fileName);
158
+ const base = path.basename(fileName, ext);
159
+ targetName = `${base}_${Date.now()}${ext}`;
160
+ }
161
+ const finalPath = path.join(uploadsDir, targetName);
162
+ fs.writeFileSync(finalPath, buffer);
163
+ logger.info(`[MediaCache] Saved: ${finalPath} (${buffer.length} bytes)`);
164
+ return { filePath: finalPath, fileName: targetName, size: buffer.length };
165
+ }
166
+ function hashFile(filePath) {
167
+ const content = fs.readFileSync(filePath);
168
+ return crypto.createHash('md5').update(content).digest('hex');
169
+ }
170
+ /**
171
+ * 安全下载 URL 内容,带 SSRF 防护
172
+ */
173
+ export async function safeFetch(url, opts) {
174
+ const maxSize = opts?.maxSize ?? DEFAULT_MAX_FILE_SIZE;
175
+ const timeout = opts?.timeout ?? 30_000;
176
+ // SSRF 检查
177
+ if (!opts?.skipSsrfCheck) {
178
+ const check = validateUrl(url, { allowedHosts: opts?.allowedHosts });
179
+ if (!check.ok) {
180
+ throw new Error(`SSRF blocked: ${check.reason}`);
181
+ }
182
+ }
183
+ const controller = new AbortController();
184
+ const timer = setTimeout(() => controller.abort(), timeout);
185
+ try {
186
+ const res = await fetch(url, { signal: controller.signal });
187
+ if (!res.ok)
188
+ throw new Error(`Download failed: ${res.status} ${res.statusText}`);
189
+ // 检查 Content-Length(如果可用)
190
+ const contentLength = res.headers.get('content-length');
191
+ if (contentLength && parseInt(contentLength) > maxSize) {
192
+ throw new Error(`File too large: ${contentLength} bytes (max ${maxSize})`);
193
+ }
194
+ const buffer = Buffer.from(await res.arrayBuffer());
195
+ if (buffer.length > maxSize) {
196
+ throw new Error(`File too large: ${buffer.length} bytes (max ${maxSize})`);
197
+ }
198
+ return buffer;
199
+ }
200
+ finally {
201
+ clearTimeout(timer);
202
+ }
203
+ }
204
+ /**
205
+ * 内存级下载缓存
206
+ * - 相同 key 不重复下载
207
+ * - TTL 过期自动清理
208
+ * - 最大条目限制防止内存泄漏
209
+ */
210
+ export class DownloadCache {
211
+ cache = new Map();
212
+ ttlMs;
213
+ maxEntries;
214
+ constructor(opts) {
215
+ this.ttlMs = opts?.ttlMs ?? 5 * 60 * 1000; // 5 min
216
+ this.maxEntries = opts?.maxEntries ?? 100;
217
+ }
218
+ /**
219
+ * 获取或下载:缓存命中直接返回,否则执行 fetcher 并缓存结果
220
+ */
221
+ async getOrFetch(key, fetcher) {
222
+ this.evict();
223
+ const cached = this.cache.get(key);
224
+ if (cached) {
225
+ logger.debug(`[DownloadCache] Hit: ${key}`);
226
+ return cached.buffer;
227
+ }
228
+ const buffer = await fetcher();
229
+ this.cache.set(key, { buffer, createdAt: Date.now() });
230
+ // 超过 maxEntries 时删最旧
231
+ if (this.cache.size > this.maxEntries) {
232
+ const oldest = this.cache.keys().next().value;
233
+ this.cache.delete(oldest);
234
+ }
235
+ return buffer;
236
+ }
237
+ evict() {
238
+ const now = Date.now();
239
+ for (const [key, entry] of this.cache) {
240
+ if (now - entry.createdAt > this.ttlMs) {
241
+ this.cache.delete(key);
242
+ }
243
+ }
244
+ }
245
+ clear() {
246
+ this.cache.clear();
247
+ }
248
+ }
249
+ // ── MIME / Size helpers ──────────────────────────────────────────────────────
250
+ const MIME_MAP = {
251
+ '.txt': 'text/plain', '.md': 'text/markdown', '.json': 'application/json',
252
+ '.js': 'text/javascript', '.ts': 'text/typescript', '.py': 'text/x-python',
253
+ '.html': 'text/html', '.css': 'text/css', '.csv': 'text/csv',
254
+ '.pdf': 'application/pdf', '.zip': 'application/zip', '.gz': 'application/gzip',
255
+ '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
256
+ '.gif': 'image/gif', '.webp': 'image/webp', '.svg': 'image/svg+xml',
257
+ '.xml': 'application/xml', '.yaml': 'application/x-yaml', '.yml': 'application/x-yaml',
258
+ '.mp4': 'video/mp4', '.mov': 'video/quicktime', '.webm': 'video/webm', '.avi': 'video/x-msvideo',
259
+ '.opus': 'audio/ogg', '.mp3': 'audio/mpeg', '.aac': 'audio/aac', '.m4a': 'audio/mp4', '.wav': 'audio/wav',
260
+ };
261
+ export function guessMime(filename) {
262
+ const ext = path.extname(filename).toLowerCase();
263
+ return MIME_MAP[ext] || 'application/octet-stream';
264
+ }
265
+ export function formatSize(bytes) {
266
+ if (bytes < 1024)
267
+ return `${bytes} B`;
268
+ if (bytes < 1048576)
269
+ return `${(bytes / 1024).toFixed(1)} KB`;
270
+ return `${(bytes / 1048576).toFixed(1)} MB`;
271
+ }
@@ -0,0 +1,17 @@
1
+ {"model":"claude-opus-4-8","effective_from":0,"billing_fn":"per_token_v1","currency":"USD","price_input":15,"price_output":75,"price_cache_creation":18.75,"price_cache_read":1.5}
2
+ {"model":"claude-opus-4-7","effective_from":0,"billing_fn":"per_token_v1","currency":"USD","price_input":15,"price_output":75,"price_cache_creation":18.75,"price_cache_read":1.5}
3
+ {"model":"claude-opus-4-6","effective_from":0,"billing_fn":"per_token_v1","currency":"USD","price_input":15,"price_output":75,"price_cache_creation":18.75,"price_cache_read":1.5}
4
+ {"model":"claude-sonnet-4-6","effective_from":0,"billing_fn":"per_token_v1","currency":"USD","price_input":3,"price_output":15,"price_cache_creation":3.75,"price_cache_read":0.3}
5
+ {"model":"claude-haiku-4-5-20251001","effective_from":0,"billing_fn":"per_token_v1","currency":"USD","price_input":0.8,"price_output":4,"price_cache_creation":1,"price_cache_read":0.08}
6
+ {"model":"gpt-5.5","effective_from":0,"billing_fn":"per_token_v1","currency":"USD","price_input":2,"price_output":10,"price_cache_creation":0,"price_cache_read":0}
7
+ {"model":"gpt-5.4","effective_from":0,"billing_fn":"per_token_v1","currency":"USD","price_input":2,"price_output":10,"price_cache_creation":0,"price_cache_read":0}
8
+ {"model":"gpt-5.3-codex","effective_from":0,"billing_fn":"per_token_v1","currency":"USD","price_input":2,"price_output":10,"price_cache_creation":0,"price_cache_read":0}
9
+ {"model":"deepseek-v4-pro","effective_from":0,"billing_fn":"per_token_deepseek_v1","currency":"CNY","price_cache_hit":0.5,"price_cache_miss":2,"price_output":8}
10
+ {"model":"deepseek-v4-flash","effective_from":0,"billing_fn":"per_token_deepseek_v1","currency":"CNY","price_cache_hit":0.1,"price_cache_miss":0.5,"price_output":2}
11
+ {"model":"deepseek-v3.2","effective_from":0,"billing_fn":"per_token_deepseek_v1","currency":"CNY","price_cache_hit":0.5,"price_cache_miss":2,"price_output":8}
12
+ {"model":"kimi-k2.6","effective_from":0,"billing_fn":"per_token_v1","currency":"CNY","price_input":2,"price_output":8,"price_cache_creation":0,"price_cache_read":0}
13
+ {"model":"kimi-k2.5","effective_from":0,"billing_fn":"per_token_v1","currency":"CNY","price_input":2,"price_output":8,"price_cache_creation":0,"price_cache_read":0}
14
+ {"model":"glm-5.1","effective_from":0,"billing_fn":"per_token_v1","currency":"CNY","price_input":5,"price_output":20,"price_cache_creation":0,"price_cache_read":0}
15
+ {"model":"glm-5","effective_from":0,"billing_fn":"per_token_v1","currency":"CNY","price_input":5,"price_output":20,"price_cache_creation":0,"price_cache_read":0}
16
+ {"model":"glm-4.7","effective_from":0,"billing_fn":"per_token_v1","currency":"CNY","price_input":1,"price_output":5,"price_cache_creation":0,"price_cache_read":0}
17
+ {"model":"MiniMax-M2.7","effective_from":0,"billing_fn":"per_token_v1","currency":"CNY","price_input":1,"price_output":5,"price_cache_creation":0,"price_cache_read":0}
@@ -0,0 +1,210 @@
1
+ /**
2
+ * npm operations
3
+ *
4
+ * 集中管理本仓库所有 `npm install -g` / `npm view` 相关的子进程调用:
5
+ * - tryUpgrade() — evolcore 自我升级
6
+ * - requireOptional() — 可选依赖动态加载 + 自动安装
7
+ * - npmInstallGlobal() — 全局安装(含 EACCES → sudo 回退、Windows npm.cmd)
8
+ */
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+ import { execFile, execSync } from 'child_process';
12
+ import { promisify } from 'util';
13
+ import { getPackageRoot } from '../paths.js';
14
+ import { isWindows } from './cross-platform.js';
15
+ const execFileAsync = promisify(execFile);
16
+ // ── npm install -g (shared) ────────────────────────────────────────────────
17
+ export async function npmInstallGlobal(pkg) {
18
+ let cmd;
19
+ let args;
20
+ // Windows: run via cmd /c to avoid shell:true deprecation warning on Node 22.
21
+ // Unix: npm directly, no shell needed.
22
+ if (isWindows) {
23
+ cmd = 'cmd';
24
+ args = ['/c', 'npm', 'install', '-g', pkg];
25
+ }
26
+ else {
27
+ cmd = 'npm';
28
+ args = ['install', '-g', pkg];
29
+ }
30
+ try {
31
+ await execFileAsync(cmd, args, { timeout: 180000 });
32
+ }
33
+ catch (e) {
34
+ if (e.stderr?.includes('EACCES') || e.message?.includes('EACCES')) {
35
+ if (isWindows) {
36
+ throw new Error('权限不足。请以管理员身份运行 PowerShell 或 CMD,然后重试');
37
+ }
38
+ await execFileAsync('sudo', ['npm', 'install', '-g', pkg], { timeout: 180000 });
39
+ }
40
+ else {
41
+ throw e;
42
+ }
43
+ }
44
+ }
45
+ /** Dynamic import with auto-install fallback for optional dependencies */
46
+ export async function requireOptional(pkg, autoInstall = true) {
47
+ try {
48
+ return await import(pkg);
49
+ }
50
+ catch (e) {
51
+ if (e.code !== 'ERR_MODULE_NOT_FOUND' && e.code !== 'MODULE_NOT_FOUND')
52
+ throw e;
53
+ if (!autoInstall)
54
+ throw new Error(`依赖 ${pkg} 未安装。请运行: npm install -g ${pkg}`);
55
+ const { logger } = await import('./logger.js');
56
+ logger.info(`正在安装可选依赖 ${pkg}...`);
57
+ await npmInstallGlobal(pkg);
58
+ return await import(pkg);
59
+ }
60
+ }
61
+ /**
62
+ * 比较两个 semver 版本号 (a.b.c 格式)
63
+ * 返回 -1 (a < b), 0 (a == b), 1 (a > b)
64
+ * 自动剥离 pre-release 标签 (e.g. 2.6.0-beta.1 → 2.6.0)
65
+ */
66
+ export function compareVersions(a, b) {
67
+ const pa = a.split('-')[0].split('.').map(Number);
68
+ const pb = b.split('-')[0].split('.').map(Number);
69
+ const len = Math.max(pa.length, pb.length);
70
+ for (let i = 0; i < len; i++) {
71
+ const na = pa[i] ?? 0;
72
+ const nb = pb[i] ?? 0;
73
+ if (na < nb)
74
+ return -1;
75
+ if (na > nb)
76
+ return 1;
77
+ }
78
+ return 0;
79
+ }
80
+ /**
81
+ * 检查当前安装是否为 npm link 开发模式。
82
+ * 正式全局安装的路径结构为 .../node_modules/evolcore,
83
+ * 而 npm link 指向项目源码目录,其父目录不是 node_modules。
84
+ */
85
+ export function isLinkedInstall() {
86
+ const pkgRoot = getPackageRoot();
87
+ return path.basename(path.dirname(pkgRoot)) !== 'node_modules';
88
+ }
89
+ /** 获取本地 package.json 中的版本号 */
90
+ export function getLocalVersion() {
91
+ const pkgPath = path.join(getPackageRoot(), 'package.json');
92
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
93
+ return pkg.version;
94
+ }
95
+ /**
96
+ * 解析全局安装包的已装版本(通过 `npm root -g` 定位全局 node_modules)。
97
+ * 用于 EC Web 这类独立全局命令包——它们不在主包的依赖树里。
98
+ * 未安装或查询失败返回 null。
99
+ */
100
+ export function resolveGlobalPkg(pkgName) {
101
+ try {
102
+ // 命令完全静态(npm root -g,无用户输入),用 execSync 传完整命令字符串,
103
+ // 避免 "args 数组 + shell:true" 组合触发 Node DeprecationWarning,且无注入风险。
104
+ const npmCmd = isWindows ? 'npm.cmd' : 'npm';
105
+ const globalRoot = execSync(`${npmCmd} root -g`, {
106
+ encoding: 'utf-8', timeout: 10000,
107
+ }).trim();
108
+ const pkgPath = path.join(globalRoot, ...pkgName.split('/'), 'package.json');
109
+ if (fs.existsSync(pkgPath)) {
110
+ const data = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
111
+ return { version: data.version, path: pkgPath };
112
+ }
113
+ }
114
+ catch { /* not found */ }
115
+ return null;
116
+ }
117
+ /**
118
+ * 查询 npm registry 上指定包的最新版本。
119
+ * 使用 HTTP fetch 直接查 registry API,不依赖 npm CLI。
120
+ * 超时 10 秒,失败返回 null。
121
+ */
122
+ export async function checkLatestVersion(pkg = 'evolcore') {
123
+ try {
124
+ const controller = new AbortController();
125
+ const timer = setTimeout(() => controller.abort(), 10000);
126
+ const res = await fetch(`https://registry.npmjs.org/${pkg}/latest`, {
127
+ signal: controller.signal,
128
+ headers: { 'Accept': 'application/json' },
129
+ });
130
+ clearTimeout(timer);
131
+ if (!res.ok)
132
+ return null;
133
+ const data = await res.json();
134
+ return data.version || null;
135
+ }
136
+ catch {
137
+ return null;
138
+ }
139
+ }
140
+ /**
141
+ * 完整升级流程:检查 → 比较 → 安装(失败重试一次)
142
+ */
143
+ export async function tryUpgrade() {
144
+ // 开发模式跳过
145
+ if (isLinkedInstall()) {
146
+ return { status: 'skipped' };
147
+ }
148
+ const localVer = getLocalVersion();
149
+ // 查询 registry
150
+ const remoteVer = await checkLatestVersion();
151
+ if (!remoteVer) {
152
+ return { status: 'skipped', error: 'Failed to check remote version' };
153
+ }
154
+ // 版本比较
155
+ if (compareVersions(localVer, remoteVer) >= 0) {
156
+ return { status: 'no-update', from: localVer };
157
+ }
158
+ // 有新版本,执行升级(失败重试一次)
159
+ let lastError;
160
+ for (let attempt = 0; attempt < 2; attempt++) {
161
+ try {
162
+ await npmInstallGlobal('evolcore@latest');
163
+ return { status: 'upgraded', from: localVer, to: remoteVer };
164
+ }
165
+ catch (e) {
166
+ lastError = e.stderr || e.message || String(e);
167
+ }
168
+ }
169
+ return { status: 'failed', from: localVer, to: remoteVer, error: lastError };
170
+ }
171
+ /**
172
+ * 通用全局包升级流程:检查 → 比较 → 安装(失败重试一次)。
173
+ * 仅在包已安装时检查升级,未安装则跳过(resolveFn 返回 null)。
174
+ * fastaun / EC Web 等独立全局包共用此逻辑。
175
+ */
176
+ export async function tryUpgradeGlobalPkg(resolveInstalled, pkgName) {
177
+ if (isLinkedInstall()) {
178
+ return { status: 'skipped' };
179
+ }
180
+ const installed = resolveInstalled();
181
+ if (!installed) {
182
+ return { status: 'skipped' }; // not installed, skip
183
+ }
184
+ const localVer = installed.version;
185
+ const remoteVer = await checkLatestVersion(pkgName);
186
+ if (!remoteVer) {
187
+ return { status: 'skipped', error: 'Failed to check remote version' };
188
+ }
189
+ if (compareVersions(localVer, remoteVer) >= 0) {
190
+ return { status: 'no-update', from: localVer };
191
+ }
192
+ let lastError;
193
+ for (let attempt = 0; attempt < 2; attempt++) {
194
+ try {
195
+ await npmInstallGlobal(`${pkgName}@latest`);
196
+ return { status: 'upgraded', from: localVer, to: remoteVer };
197
+ }
198
+ catch (e) {
199
+ lastError = e.stderr || e.message || String(e);
200
+ }
201
+ }
202
+ return { status: 'failed', from: localVer, to: remoteVer, error: lastError };
203
+ }
204
+ /**
205
+ * AUN SDK 升级流程:检查 → 比较 → 安装
206
+ * 仅在 SDK 已安装时检查升级,未安装则跳过。
207
+ */
208
+ export async function tryUpgradeAunSdk(resolveAunCoreSdkPkg, AUN_CORE_SDK_PKG) {
209
+ return tryUpgradeGlobalPkg(resolveAunCoreSdkPkg, AUN_CORE_SDK_PKG);
210
+ }