evolcore 0.0.1 → 0.0.3

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 (379) hide show
  1. package/CHANGELOG.md +64 -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/bin/ec-safe-output.js +161 -0
  8. package/bin/ec.js +29 -0
  9. package/dist/agents/baseagent.js +163 -0
  10. package/dist/agents/claude-runner.js +2565 -0
  11. package/dist/agents/codex-app-server-client.js +448 -0
  12. package/dist/agents/codex-runner.js +2682 -0
  13. package/dist/agents/gemini-runner.js +666 -0
  14. package/dist/agents/runner-types.js +75 -0
  15. package/dist/aun/aid/agentmd.js +216 -0
  16. package/dist/aun/aid/client.js +132 -0
  17. package/dist/aun/aid/control-aid.js +91 -0
  18. package/dist/aun/aid/identity.js +518 -0
  19. package/dist/aun/aid/index.js +4 -0
  20. package/dist/aun/aid/store.js +74 -0
  21. package/dist/aun/aid/types.js +1 -0
  22. package/dist/aun/aid/validation.js +21 -0
  23. package/dist/aun/group-identity.js +10 -0
  24. package/dist/aun/msg/group-index.js +6 -0
  25. package/dist/aun/msg/group.js +1231 -0
  26. package/dist/aun/msg/history.js +123 -0
  27. package/dist/aun/msg/index.js +5 -0
  28. package/dist/aun/msg/p2p.js +393 -0
  29. package/dist/aun/msg/payload-type.js +27 -0
  30. package/dist/aun/msg/upload.js +137 -0
  31. package/dist/aun/outbox.js +168 -0
  32. package/dist/aun/rpc/caller.js +42 -0
  33. package/dist/aun/rpc/connection.js +25 -0
  34. package/dist/aun/rpc/index.js +2 -0
  35. package/dist/aun/service-proxy.js +225 -0
  36. package/dist/aun/storage/download.js +29 -0
  37. package/dist/aun/storage/index.js +3 -0
  38. package/dist/aun/storage/manage.js +10 -0
  39. package/dist/aun/storage/upload.js +68 -0
  40. package/dist/channels/aun.js +4164 -0
  41. package/dist/channels/contact-bind-code.js +134 -0
  42. package/dist/channels/daemon.js +422 -0
  43. package/dist/channels/dingtalk.js +1479 -0
  44. package/dist/channels/feishu.js +1865 -0
  45. package/dist/channels/qqbot.js +409 -0
  46. package/dist/channels/wechat.js +817 -0
  47. package/dist/channels/wecom-card.js +101 -0
  48. package/dist/channels/wecom-onboarding.js +82 -0
  49. package/dist/channels/wecom-state.js +191 -0
  50. package/dist/channels/wecom.js +1157 -0
  51. package/dist/cli/agent-command.js +642 -0
  52. package/dist/cli/agent.js +1059 -0
  53. package/dist/cli/aun-commands.js +2003 -0
  54. package/dist/cli/bench.js +1228 -0
  55. package/dist/cli/cli-argv.js +66 -0
  56. package/dist/cli/code-stats.js +329 -0
  57. package/dist/cli/command-log.js +82 -0
  58. package/dist/cli/config-selector.js +69 -0
  59. package/dist/cli/config.js +261 -0
  60. package/dist/cli/contact.js +71 -0
  61. package/dist/cli/ctl-command.js +62 -0
  62. package/dist/cli/daemon-commands.js +2750 -0
  63. package/dist/cli/fs-command.js +1447 -0
  64. package/dist/cli/handoff-command.js +302 -0
  65. package/dist/cli/help.js +35 -0
  66. package/dist/cli/index.js +374 -0
  67. package/dist/cli/init-channel.js +1372 -0
  68. package/dist/cli/init.js +590 -0
  69. package/dist/cli/link-rules.js +240 -0
  70. package/dist/cli/model.js +591 -0
  71. package/dist/cli/net-check.js +723 -0
  72. package/dist/cli/queue-command.js +150 -0
  73. package/dist/cli/raw-key-input.js +25 -0
  74. package/dist/cli/response.js +344 -0
  75. package/dist/cli/restart-monitor.js +480 -0
  76. package/dist/cli/stats.js +609 -0
  77. package/dist/cli/task-context.js +80 -0
  78. package/dist/cli/trigger-command.js +545 -0
  79. package/dist/cli/version.js +93 -0
  80. package/dist/cli/watch-logs.js +33 -0
  81. package/dist/cli/watch-msg.js +673 -0
  82. package/dist/config/boot-log.js +266 -0
  83. package/dist/config/builtin-role-templates.js +42 -0
  84. package/dist/config/builtin-roles.js +91 -0
  85. package/dist/config/config-batch-get.js +11 -0
  86. package/dist/config/config-field-policy.js +261 -0
  87. package/dist/config/config-manager.js +1120 -0
  88. package/dist/config/config-operation-service.js +384 -0
  89. package/dist/config/contact-alias.js +68 -0
  90. package/dist/config/contact-book-store.js +454 -0
  91. package/dist/config/contact-book-v2-startup.js +35 -0
  92. package/dist/config/contact-book.js +224 -0
  93. package/dist/config/contact-operation-service.js +110 -0
  94. package/dist/config/gateway-config.js +858 -0
  95. package/dist/config/lifecycle.js +17 -0
  96. package/dist/config/mention-mode.js +27 -0
  97. package/dist/config/merge.js +161 -0
  98. package/dist/config/owner-policy.js +4 -0
  99. package/dist/config/peer-role-resolver.js +218 -0
  100. package/dist/config/resolved-config-op.js +483 -0
  101. package/dist/config/role-config-v4-startup.js +32 -0
  102. package/dist/config/role-config-v5-startup.js +27 -0
  103. package/dist/config/role-ranks.js +18 -0
  104. package/dist/config/role-schema.js +105 -0
  105. package/dist/config/role-service.js +156 -0
  106. package/dist/config/role-store.js +215 -0
  107. package/dist/config/roles.js +64 -0
  108. package/dist/config/schema-registry.js +154 -0
  109. package/dist/config/snapshot.js +598 -0
  110. package/dist/config-store.js +501 -0
  111. package/dist/core/auth/agent-delegation.js +111 -0
  112. package/dist/core/auth/auth-gateway.js +166 -0
  113. package/dist/core/auth/authenticated-actor.js +6 -0
  114. package/dist/core/auth/authorization-audit.js +119 -0
  115. package/dist/core/auth/operation-authorizer.js +720 -0
  116. package/dist/core/auth/operation-catalog.js +731 -0
  117. package/dist/core/baseagent-loader.js +54 -0
  118. package/dist/core/bootstrap-service.js +175 -0
  119. package/dist/core/capability/capability-manager.js +316 -0
  120. package/dist/core/capability/providers/claude-capability-provider.js +176 -0
  121. package/dist/core/capability/providers/codex-capability-provider.js +148 -0
  122. package/dist/core/capability/providers/gemini-capability-provider.js +10 -0
  123. package/dist/core/capability/types.js +27 -0
  124. package/dist/core/causation/audit.js +103 -0
  125. package/dist/core/causation/aun-association.js +111 -0
  126. package/dist/core/causation/context.js +93 -0
  127. package/dist/core/causation/index.js +4 -0
  128. package/dist/core/causation/types.js +2 -0
  129. package/dist/core/channel-loader.js +277 -0
  130. package/dist/core/command/agent-control.js +616 -0
  131. package/dist/core/command/cli-intent-parser.js +225 -0
  132. package/dist/core/command/command-handler.js +1733 -0
  133. package/dist/core/command/connect-menu.js +374 -0
  134. package/dist/core/command/menu-handler.js +3452 -0
  135. package/dist/core/command/menu-protocol.js +247 -0
  136. package/dist/core/command/role-menu.js +1623 -0
  137. package/dist/core/command/slash-gate.js +148 -0
  138. package/dist/core/command/slash-handler.js +3066 -0
  139. package/dist/core/daemon-file-cache.js +222 -0
  140. package/dist/core/event-bus.js +32 -0
  141. package/dist/core/event-catalog.js +810 -0
  142. package/dist/core/evolagent-registry.js +545 -0
  143. package/dist/core/evolagent.js +342 -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 +659 -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 +874 -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 +1446 -0
  161. package/dist/core/message/message-utils.js +76 -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 +4020 -0
  166. package/dist/core/message/response-snapshot.js +83 -0
  167. package/dist/core/message/send-receipt.js +24 -0
  168. package/dist/core/message/stream-debouncer.js +139 -0
  169. package/dist/core/message/stream-idle-monitor.js +124 -0
  170. package/dist/core/model/config-scope.js +162 -0
  171. package/dist/core/model/field-scope.js +78 -0
  172. package/dist/core/model/model-catalog.js +227 -0
  173. package/dist/core/model/model-diagnostics.js +182 -0
  174. package/dist/core/model/model-permission.js +90 -0
  175. package/dist/core/permission/approval-gateway.js +1017 -0
  176. package/dist/core/permission/ec-command-parser.js +347 -0
  177. package/dist/core/permission/execution-sandbox.js +16 -0
  178. package/dist/core/permission/index.js +6 -0
  179. package/dist/core/permission/mode.js +24 -0
  180. package/dist/core/permission/sandbox-runtime.js +265 -0
  181. package/dist/core/permission/tool-policy.js +1019 -0
  182. package/dist/core/permission/unix-socket-policy.js +99 -0
  183. package/dist/core/protected-paths.js +332 -0
  184. package/dist/core/relation/peer-identity.js +222 -0
  185. package/dist/core/relation/peer-key.js +1 -0
  186. package/dist/core/role/runtime-policy.js +141 -0
  187. package/dist/core/session/adapters/claude-session-file-adapter.js +218 -0
  188. package/dist/core/session/adapters/codex-session-file-adapter.js +333 -0
  189. package/dist/core/session/adapters/gemini-session-file-adapter.js +181 -0
  190. package/dist/core/session/session-file-adapter.js +7 -0
  191. package/dist/core/session/session-file-health.js +45 -0
  192. package/dist/core/session/session-fs-store.js +273 -0
  193. package/dist/core/session/session-key.js +24 -0
  194. package/dist/core/session/session-manager.js +1643 -0
  195. package/dist/core/session/session-mapper.js +100 -0
  196. package/dist/core/session/session-renew.js +314 -0
  197. package/dist/core/session/session-title.js +128 -0
  198. package/dist/core/session/session-turn-coordinator.js +205 -0
  199. package/dist/core/session/session-turns.js +67 -0
  200. package/dist/core/system-channels.js +29 -0
  201. package/dist/eck/baseagent-caps.js +18 -0
  202. package/dist/eck/detect.js +47 -0
  203. package/dist/eck/group-rules-sync.js +345 -0
  204. package/dist/eck/init.js +77 -0
  205. package/dist/eck/kit-renderer.js +359 -0
  206. package/dist/eck/manifest-engine.js +446 -0
  207. package/dist/eck/message-renderer.js +199 -0
  208. package/dist/eck/rules-loader.js +28 -0
  209. package/dist/index.js +2926 -4
  210. package/dist/ipc.js +777 -0
  211. package/dist/paths.js +262 -0
  212. package/dist/product.js +18 -0
  213. package/dist/response-system/context-builder.js +71 -0
  214. package/dist/response-system/coordinator.js +117 -0
  215. package/dist/response-system/decision-executor.js +86 -0
  216. package/dist/response-system/engines/v1/index.js +21 -0
  217. package/dist/response-system/engines/v1/interactive-flow.js +27 -0
  218. package/dist/response-system/engines/v1/proactive-flow.js +137 -0
  219. package/dist/response-system/engines/v1/types.js +1 -0
  220. package/dist/response-system/extensions.js +41 -0
  221. package/dist/response-system/index.js +6 -0
  222. package/dist/response-system/modes/index.js +7 -0
  223. package/dist/response-system/modes/single-session/index.js +72 -0
  224. package/dist/response-system/queues/fifo-queue.js +44 -0
  225. package/dist/response-system/queues/index.js +6 -0
  226. package/dist/response-system/queues/lifo-queue.js +42 -0
  227. package/dist/response-system/queues/priority-queue.js +63 -0
  228. package/dist/response-system/registry.js +97 -0
  229. package/dist/response-system/resolver.js +37 -0
  230. package/dist/response-system/selector.js +23 -0
  231. package/dist/response-system/types.js +7 -0
  232. package/dist/stats/billing.js +163 -0
  233. package/dist/stats/budget.js +93 -0
  234. package/dist/stats/db.js +403 -0
  235. package/dist/stats/eck-vars.js +89 -0
  236. package/dist/stats/index.js +11 -0
  237. package/dist/stats/normalizer.js +80 -0
  238. package/dist/stats/price-resolver.js +138 -0
  239. package/dist/stats/query.js +763 -0
  240. package/dist/stats/role-budget.js +168 -0
  241. package/dist/stats/writer.js +151 -0
  242. package/dist/trigger/anomaly-store.js +258 -0
  243. package/dist/trigger/audit.js +152 -0
  244. package/dist/trigger/event-source.js +119 -0
  245. package/dist/trigger/feedback.js +685 -0
  246. package/dist/trigger/history.js +290 -0
  247. package/dist/trigger/manager.js +294 -0
  248. package/dist/trigger/parser.js +595 -0
  249. package/dist/trigger/patch.js +155 -0
  250. package/dist/trigger/scheduler.js +1602 -0
  251. package/dist/trigger/script-executor.js +155 -0
  252. package/dist/trigger/state.js +145 -0
  253. package/dist/trigger/types.js +1 -0
  254. package/dist/trigger/validation.js +634 -0
  255. package/dist/types.js +12 -0
  256. package/dist/utils/aid-bind.js +313 -0
  257. package/dist/utils/atomic-write.js +95 -0
  258. package/dist/utils/avatar-upload.js +123 -0
  259. package/dist/utils/cross-platform.js +297 -0
  260. package/dist/utils/ecweb-utils.js +73 -0
  261. package/dist/utils/error-dict.json +153 -0
  262. package/dist/utils/error-utils.js +349 -0
  263. package/dist/utils/instance-registry.js +444 -0
  264. package/dist/utils/locale.js +21 -0
  265. package/dist/utils/log-writer.js +270 -0
  266. package/dist/utils/logger.js +89 -0
  267. package/dist/utils/markdown-to-plain-text.js +20 -0
  268. package/dist/utils/media-cache.js +274 -0
  269. package/dist/utils/model-prices.jsonl +20 -0
  270. package/dist/utils/npm-ops.js +210 -0
  271. package/dist/utils/process-introspect.js +133 -0
  272. package/dist/utils/process-tree-stats.js +271 -0
  273. package/dist/utils/project-path.js +74 -0
  274. package/dist/utils/restart-safety.js +31 -0
  275. package/dist/utils/stats.js +410 -0
  276. package/dist/utils/system-memory.js +62 -0
  277. package/dist/utils/tool-summary.js +284 -0
  278. package/dist/utils/welcome.js +268 -0
  279. package/kits/docs/GUIDE.md +20 -0
  280. package/kits/docs/INDEX.md +66 -0
  281. package/kits/docs/aun/CHEATSHEET.md +19 -0
  282. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
  283. package/kits/docs/channels/aun.md +65 -0
  284. package/kits/docs/channels/feishu.md +56 -0
  285. package/kits/docs/context-assembly.md +366 -0
  286. package/kits/docs/eck_templates/GUIDE.template.md +22 -0
  287. package/kits/docs/eck_templates/INDEX.template.md +28 -0
  288. package/kits/docs/eck_templates/path-registry.template.md +33 -0
  289. package/kits/docs/eck_templates/runtime.template.md +19 -0
  290. package/kits/docs/evolcore/INDEX.md +68 -0
  291. package/kits/docs/evolcore/agent.md +77 -0
  292. package/kits/docs/evolcore/aid.md +52 -0
  293. package/kits/docs/evolcore/config.md +149 -0
  294. package/kits/docs/evolcore/contact.md +57 -0
  295. package/kits/docs/evolcore/ctl.md +46 -0
  296. package/kits/docs/evolcore/event.md +216 -0
  297. package/kits/docs/evolcore/fs-architecture.md +1215 -0
  298. package/kits/docs/evolcore/fs.md +110 -0
  299. package/kits/docs/evolcore/group-fs.md +17 -0
  300. package/kits/docs/evolcore/group-rules.md +226 -0
  301. package/kits/docs/evolcore/group.md +150 -0
  302. package/kits/docs/evolcore/model.md +50 -0
  303. package/kits/docs/evolcore/msg.md +136 -0
  304. package/kits/docs/evolcore/response.md +75 -0
  305. package/kits/docs/evolcore/rpc.md +37 -0
  306. package/kits/docs/evolcore/self-summary.md +29 -0
  307. package/kits/docs/evolcore/stats.md +83 -0
  308. package/kits/docs/evolcore/storage.md +50 -0
  309. package/kits/docs/evolcore/trigger.md +539 -0
  310. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -0
  311. package/kits/docs/identity/PATH_OPS.md +16 -0
  312. package/kits/docs/identity/ROLE_DETAIL.md +23 -0
  313. package/kits/docs/identity/identity-tools.md +26 -0
  314. package/kits/docs/path-registry.md +43 -0
  315. package/kits/docs/prompt-loading-architecture.md +266 -0
  316. package/kits/docs/venues/aun-group.md +45 -0
  317. package/kits/docs/venues/aun-private.md +10 -0
  318. package/kits/docs/venues/client-desktop.md +10 -0
  319. package/kits/docs/venues/client-mobile.md +10 -0
  320. package/kits/docs/venues/feishu-group.md +13 -0
  321. package/kits/docs/venues/feishu-private.md +9 -0
  322. package/kits/docs/venues/group.md +25 -0
  323. package/kits/docs/venues/private.md +10 -0
  324. package/kits/eck_manifest.auxiliary.json +43 -0
  325. package/kits/eck_manifest.json +203 -0
  326. package/kits/eck_message_manifest.json +63 -0
  327. package/kits/migrations/README-role-config-v4.md +32 -0
  328. package/kits/migrations/migrate-contact-book-v2.mjs +747 -0
  329. package/kits/migrations/migrate-role-config-v4.mjs +623 -0
  330. package/kits/migrations/migrate-role-config-v5.mjs +346 -0
  331. package/kits/migrations/rename-config-file.mjs +99 -0
  332. package/kits/rules/01-overview.md +142 -0
  333. package/kits/rules/02-navigation.md +76 -0
  334. package/kits/rules/03-identity.md +34 -0
  335. package/kits/rules/04-relation.md +59 -0
  336. package/kits/rules/05-venue.md +44 -0
  337. package/kits/rules/06-channel.md +59 -0
  338. package/kits/schemas/_meta.json +32 -0
  339. package/kits/schemas/agent-config.schema.1.json +177 -0
  340. package/kits/schemas/agent-config.schema.2.json +239 -0
  341. package/kits/schemas/agent-config.schema.3.json +119 -0
  342. package/kits/schemas/agent-config.schema.4.json +208 -0
  343. package/kits/schemas/agent-config.schema.5.json +326 -0
  344. package/kits/schemas/agent-config.schema.6.json +322 -0
  345. package/kits/schemas/contact-book.schema.1.json +36 -0
  346. package/kits/schemas/contact-book.schema.2.json +43 -0
  347. package/kits/schemas/daemon.schema.1.json +90 -0
  348. package/kits/schemas/defaults.schema.1.json +81 -0
  349. package/kits/schemas/menu-exec-schema-commands.md +208 -0
  350. package/kits/schemas/migrations/README.md +28 -0
  351. package/kits/schemas/relation-config.schema.1.json +158 -0
  352. package/kits/schemas/relation-config.schema.2.json +73 -0
  353. package/kits/schemas/relation-config.schema.3.json +50 -0
  354. package/kits/schemas/relation-config.schema.4.json +47 -0
  355. package/kits/schemas/relation-config.schema.5.json +47 -0
  356. package/kits/schemas/role-config.schema.1.json +201 -0
  357. package/kits/schemas/role-registry.schema.1.json +35 -0
  358. package/kits/schemas/single-session.schema.1.json +31 -0
  359. package/kits/templates/bootstrap-welcome.md +15 -0
  360. package/kits/templates/message-fragments/handoff-request-to-target.md +13 -0
  361. package/kits/templates/message-fragments/handoff-response-to-origin.md +10 -0
  362. package/kits/templates/message-fragments/inject-default.md +2 -0
  363. package/kits/templates/message-fragments/item.md +2 -0
  364. package/kits/templates/roles/admin.json +9 -0
  365. package/kits/templates/roles/member.json +41 -0
  366. package/kits/templates/roles/owner.json +9 -0
  367. package/kits/templates/roles/visitor.json +40 -0
  368. package/kits/templates/system-fragments/baseagent.md +14 -0
  369. package/kits/templates/system-fragments/bootstrap.md +16 -0
  370. package/kits/templates/system-fragments/channel.md +48 -0
  371. package/kits/templates/system-fragments/commands.md +28 -0
  372. package/kits/templates/system-fragments/identity.md +11 -0
  373. package/kits/templates/system-fragments/relation.md +19 -0
  374. package/kits/templates/system-fragments/session.md +53 -0
  375. package/kits/templates/system-fragments/venue.md +31 -0
  376. package/package.json +50 -15
  377. package/dist/index.d.ts +0 -7
  378. package/dist/index.d.ts.map +0 -1
  379. package/dist/index.js.map +0 -1
@@ -0,0 +1,313 @@
1
+ import crypto from 'crypto';
2
+ import { loadAgent, loadDaemonConfig, saveAgent, saveDaemonConfig } from '../config-store.js';
3
+ import { resolveEffective } from '../config/config-manager.js';
4
+ import { readEvolcoreVersion } from '../index.js';
5
+ export class BindService {
6
+ opts;
7
+ tasks = new Map();
8
+ taskIdsByTokenHash = new Map();
9
+ cleanupTimer = null;
10
+ constructor(opts) {
11
+ this.opts = opts;
12
+ }
13
+ startCleanup() {
14
+ if (this.cleanupTimer)
15
+ return;
16
+ this.cleanupTimer = setInterval(() => this.cleanup(), 60_000);
17
+ this.cleanupTimer.unref?.();
18
+ }
19
+ stopCleanup() {
20
+ if (!this.cleanupTimer)
21
+ return;
22
+ clearInterval(this.cleanupTimer);
23
+ this.cleanupTimer = null;
24
+ }
25
+ begin(req) {
26
+ if (req.bindType !== 'daemon' && req.bindType !== 'agent') {
27
+ return { ok: false, code: 'INVALID_BIND_TYPE', error: 'invalid bindType' };
28
+ }
29
+ const targetAid = req.bindType === 'daemon' ? (req.targetAid ?? this.opts.receiverAid) : req.targetAid;
30
+ if (!targetAid || typeof targetAid !== 'string') {
31
+ return { ok: false, code: 'MISSING_TARGET', error: 'missing targetAid' };
32
+ }
33
+ if (req.bindType === 'daemon' && targetAid !== this.opts.receiverAid) {
34
+ return { ok: false, code: 'TARGET_MISMATCH', error: 'daemon targetAid must equal control AID' };
35
+ }
36
+ if (req.bindType === 'agent' && !loadAgent(targetAid)) {
37
+ return { ok: false, code: 'TARGET_NOT_FOUND', error: `agent not found: ${targetAid}` };
38
+ }
39
+ const taskId = crypto.randomUUID();
40
+ let token = generatePublicToken();
41
+ let tokenHash = hashToken(token);
42
+ while (this.taskIdsByTokenHash.has(tokenHash)) {
43
+ token = generatePublicToken();
44
+ tokenHash = hashToken(token);
45
+ }
46
+ const ttlMs = clampTtl(req.ttlMs);
47
+ const expiresAt = Date.now() + ttlMs;
48
+ const ownerMode = req.ownerMode === 'replace' ? 'replace' : 'append';
49
+ const task = {
50
+ taskId,
51
+ bindType: req.bindType,
52
+ receiverAid: this.opts.receiverAid,
53
+ targetAid,
54
+ agentName: req.agentName,
55
+ tokenHash,
56
+ createdAt: Date.now(),
57
+ expiresAt,
58
+ status: 'pending',
59
+ ownerMode,
60
+ };
61
+ this.tasks.set(taskId, task);
62
+ this.taskIdsByTokenHash.set(tokenHash, taskId);
63
+ this.cleanup();
64
+ return {
65
+ ok: true,
66
+ taskId,
67
+ expiresAt,
68
+ qrData: {
69
+ type: 'evolcore.bind',
70
+ version: readEvolcoreVersion(),
71
+ bindType: req.bindType,
72
+ daemonAid: this.opts.receiverAid,
73
+ ...(req.bindType === 'daemon' ? { platform: getPlatform() } : {}),
74
+ ...(req.bindType === 'agent' ? { agentAid: targetAid, agentName: req.agentName ?? null } : {}),
75
+ token,
76
+ expiresAt,
77
+ },
78
+ };
79
+ }
80
+ status(taskId) {
81
+ if (!taskId)
82
+ return { ok: false, code: 'MISSING_TASK', error: 'missing taskId' };
83
+ const task = this.tasks.get(taskId);
84
+ if (!task)
85
+ return { ok: false, code: 'TASK_NOT_FOUND', error: 'binding task not found' };
86
+ if (task.status === 'pending' && Date.now() > task.expiresAt) {
87
+ task.status = 'expired';
88
+ task.finishedAt ??= task.expiresAt;
89
+ }
90
+ return {
91
+ ok: true,
92
+ taskId: task.taskId,
93
+ status: task.status,
94
+ boundAid: task.boundAid,
95
+ boundName: task.boundName,
96
+ };
97
+ }
98
+ cancel(taskId) {
99
+ if (!taskId)
100
+ return { ok: false, code: 'MISSING_TASK', error: 'missing taskId' };
101
+ const task = this.tasks.get(taskId);
102
+ if (!task)
103
+ return { ok: false, code: 'TASK_NOT_FOUND', error: 'binding task not found' };
104
+ if (task.status === 'pending') {
105
+ task.status = 'cancelled';
106
+ task.finishedAt = Date.now();
107
+ }
108
+ return {
109
+ ok: true,
110
+ taskId: task.taskId,
111
+ status: task.status,
112
+ boundAid: task.boundAid,
113
+ boundName: task.boundName,
114
+ };
115
+ }
116
+ async handleRequest(payload, fromAid) {
117
+ if (payload?.type !== 'bind.request')
118
+ return null;
119
+ const bindType = payload.bindType === 'agent' ? 'agent' : 'daemon';
120
+ const fail = (code, message) => ({
121
+ type: 'bind.response',
122
+ bindType,
123
+ success: false,
124
+ error: { code, message },
125
+ });
126
+ if (!payload.token)
127
+ return fail('INVALID_TOKEN', 'invalid token');
128
+ const taskId = this.taskIdsByTokenHash.get(hashToken(payload.token));
129
+ if (!taskId)
130
+ return fail('TASK_NOT_FOUND', 'binding task not found');
131
+ const task = this.tasks.get(taskId);
132
+ if (!task)
133
+ return fail('TASK_NOT_FOUND', 'binding task not found');
134
+ if (payload.bindType !== task.bindType)
135
+ return fail('BIND_TYPE_MISMATCH', 'bindType mismatch');
136
+ if (!fromAid)
137
+ return fail('INTERNAL_ERROR', 'missing AUN sender');
138
+ if (task.status === 'pending' && Date.now() > task.expiresAt) {
139
+ task.status = 'expired';
140
+ task.finishedAt ??= task.expiresAt;
141
+ return fail('TASK_EXPIRED', 'binding task expired');
142
+ }
143
+ if (task.status === 'bound') {
144
+ // The owner write and task transition necessarily happen before the caller
145
+ // can send bind.response. If that send fails, the App retries the same
146
+ // authenticated request. Replay the original success only to the AID that
147
+ // completed the bind; other senders must not receive daemonInfo.
148
+ if (task.boundAid === fromAid && task.boundResponse)
149
+ return task.boundResponse;
150
+ return fail('ALREADY_BOUND', 'binding task already completed');
151
+ }
152
+ if (task.status !== 'pending')
153
+ return fail('TASK_NOT_FOUND', 'binding task is not pending');
154
+ if (!timingSafeTokenEqual(payload.token, task.tokenHash)) {
155
+ return fail('INVALID_TOKEN', 'invalid token');
156
+ }
157
+ try {
158
+ // Build and retain the exact response before committing the bind so a
159
+ // post-commit transport failure can be recovered by an idempotent retry.
160
+ const response = this.buildSuccessResponse(task);
161
+ this.applyOwnerBinding(task, fromAid);
162
+ task.status = 'bound';
163
+ task.boundAid = fromAid;
164
+ task.boundName = payload.clientName;
165
+ task.boundResponse = response;
166
+ task.finishedAt = Date.now();
167
+ return response;
168
+ }
169
+ catch (e) {
170
+ return fail('INTERNAL_ERROR', e?.message || String(e));
171
+ }
172
+ }
173
+ buildSuccessResponse(task) {
174
+ const details = task.bindType === 'agent' ? this.buildAgentBindDetails(task) : null;
175
+ return {
176
+ type: 'bind.response',
177
+ bindType: task.bindType,
178
+ success: true,
179
+ daemonInfo: {
180
+ daemonAid: task.receiverAid,
181
+ ...(task.bindType === 'agent' ? { agentAid: task.targetAid } : {}),
182
+ bindType: task.bindType,
183
+ ...(task.bindType === 'daemon'
184
+ ? {
185
+ version: readEvolcoreVersion(),
186
+ platform: getPlatform(),
187
+ uptime: this.opts.getUptimeSeconds(),
188
+ baseagents: this.opts.getAvailableBaseagents(),
189
+ }
190
+ : {
191
+ active_baseagent: details.baseagent.active_baseagent,
192
+ model: details.baseagent.model,
193
+ effort: details.baseagent.effort,
194
+ channels: details.channels,
195
+ agentName: task.agentName,
196
+ }),
197
+ },
198
+ };
199
+ }
200
+ buildAgentBindDetails(task) {
201
+ const agent = loadAgent(task.targetAid);
202
+ const config = resolveEffective({ self: task.targetAid }, { expand: true });
203
+ return {
204
+ baseagent: resolveBaseagentInfo(config),
205
+ channels: summarizeChannels(agent?.channels ?? []),
206
+ };
207
+ }
208
+ applyOwnerBinding(task, ownerAid) {
209
+ if (task.bindType === 'daemon') {
210
+ const cfg = loadDaemonConfig();
211
+ const current = cfg.owners ?? [];
212
+ const owners = task.ownerMode === 'replace'
213
+ ? [ownerAid]
214
+ : [ownerAid, ...current.filter(o => o !== ownerAid)];
215
+ saveDaemonConfig({ ...cfg, $schema_version: cfg.$schema_version ?? 1, owners });
216
+ this.opts.onDaemonOwnersUpdated?.(owners);
217
+ return;
218
+ }
219
+ const agent = loadAgent(task.targetAid);
220
+ if (!agent)
221
+ throw new Error(`agent not found: ${task.targetAid}`);
222
+ const current = agent.owners ?? [];
223
+ const owners = task.ownerMode === 'replace'
224
+ ? [ownerAid]
225
+ : [ownerAid, ...current.filter(o => o !== ownerAid)];
226
+ saveAgent({ ...agent, owners });
227
+ }
228
+ cleanup() {
229
+ const now = Date.now();
230
+ for (const [id, task] of this.tasks) {
231
+ if (task.status === 'pending' && now > task.expiresAt) {
232
+ task.status = 'expired';
233
+ task.finishedAt ??= task.expiresAt;
234
+ }
235
+ if (task.status === 'cancelled') {
236
+ this.deleteTask(id);
237
+ }
238
+ else if (task.status === 'expired' && now - (task.finishedAt ?? task.expiresAt) > 10 * 60_000) {
239
+ this.deleteTask(id);
240
+ }
241
+ else if (task.status === 'bound' && task.finishedAt && now - task.finishedAt > 10 * 60_000) {
242
+ this.deleteTask(id);
243
+ }
244
+ }
245
+ if (this.tasks.size <= 128)
246
+ return;
247
+ const sorted = [...this.tasks.values()].sort((a, b) => a.createdAt - b.createdAt);
248
+ for (const task of sorted) {
249
+ if (this.tasks.size <= 128)
250
+ break;
251
+ if (task.status !== 'pending')
252
+ this.deleteTask(task.taskId);
253
+ }
254
+ }
255
+ deleteTask(taskId) {
256
+ const task = this.tasks.get(taskId);
257
+ if (!task)
258
+ return;
259
+ this.tasks.delete(taskId);
260
+ this.taskIdsByTokenHash.delete(task.tokenHash);
261
+ }
262
+ }
263
+ function clampTtl(ttlMs) {
264
+ if (!ttlMs || !Number.isFinite(ttlMs))
265
+ return 10 * 60_000;
266
+ return Math.max(60_000, Math.min(ttlMs, 10 * 60_000));
267
+ }
268
+ function hashToken(token) {
269
+ return crypto.createHash('sha256').update(token, 'utf8').digest('hex');
270
+ }
271
+ function generatePublicToken() {
272
+ return crypto.randomBytes(16).toString('base64url'); // 128 bits for stronger security
273
+ }
274
+ function timingSafeTokenEqual(token, expectedHash) {
275
+ const actual = Buffer.from(hashToken(token), 'hex');
276
+ const expected = Buffer.from(expectedHash, 'hex');
277
+ if (actual.length !== expected.length)
278
+ return false;
279
+ return crypto.timingSafeEqual(actual, expected);
280
+ }
281
+ function resolveBaseagentInfo(config) {
282
+ const baseagents = (config?.baseagents ?? {});
283
+ const active_baseagent = ('active_baseagent' in (config ?? {}) && typeof config?.active_baseagent === 'string')
284
+ ? config.active_baseagent
285
+ : pickFirstBaseagent(baseagents);
286
+ const block = active_baseagent ? baseagents[active_baseagent] : undefined;
287
+ const model = typeof block?.model === 'string' ? block.model : undefined;
288
+ const effort = typeof block?.effort === 'string'
289
+ ? block.effort
290
+ : (typeof block?.reasoning === 'string' ? block.reasoning : undefined);
291
+ return { active_baseagent, model, effort };
292
+ }
293
+ function pickFirstBaseagent(baseagents) {
294
+ for (const name of ['claude', 'codex', 'gemini']) {
295
+ if (baseagents[name] !== undefined)
296
+ return name;
297
+ }
298
+ return 'claude';
299
+ }
300
+ function summarizeChannels(channels) {
301
+ return channels.map(ch => ({
302
+ type: ch.type,
303
+ name: ch.name,
304
+ enabled: ch.enabled !== false,
305
+ }));
306
+ }
307
+ function getPlatform() {
308
+ if (process.platform === 'win32')
309
+ return 'windows';
310
+ if (process.platform === 'darwin')
311
+ return 'macos';
312
+ return 'linux';
313
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Atomic write via dual rename.
3
+ *
4
+ * 写入流程(写 foo.json):
5
+ * 1. 把当前 foo.json 改名为 foo.json_ (保留旧版作为热备)
6
+ * 2. 把新内容写到 foo.json__ (写入完成才有完整内容)
7
+ * 3. 把 foo.json__ rename 为 foo.json (原子切换)
8
+ * foo.json_ 留到下次写入时被覆盖。
9
+ *
10
+ * 读取时按以下顺序探测,自动恢复中途崩溃的状态:
11
+ * - foo.json__ 存在 → 上次写入未完成,丢弃
12
+ * - foo.json 存在 → 直接读
13
+ * - foo.json_ 存在 → rename 步骤崩溃,rename 回 foo.json 再读
14
+ * - 都不存在 → null
15
+ */
16
+ import fs from 'fs';
17
+ import path from 'path';
18
+ const HOT = '_'; // foo.json_ 旧版热备
19
+ const TMP = '__'; // foo.json__ 写入中
20
+ /**
21
+ * 原子写入 JSON 文件(双 rename)。dirname 不存在时会自动创建。
22
+ *
23
+ * @param filePath 目标文件
24
+ * @param content 完整内容(不含末尾换行也可,由调用方控制)
25
+ */
26
+ export function atomicWrite(filePath, content) {
27
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
28
+ const hot = filePath + HOT;
29
+ const tmp = filePath + TMP;
30
+ // 步骤 1:当前文件 → hot(保留旧版作热备)
31
+ if (fs.existsSync(filePath)) {
32
+ if (fs.existsSync(hot))
33
+ fs.unlinkSync(hot);
34
+ fs.renameSync(filePath, hot);
35
+ }
36
+ // 步骤 2:写到 tmp
37
+ fs.writeFileSync(tmp, content, 'utf-8');
38
+ // 步骤 3:tmp → 目标(原子)
39
+ fs.renameSync(tmp, filePath);
40
+ }
41
+ export function atomicWriteJson(filePath, value) {
42
+ atomicWrite(filePath, JSON.stringify(value, null, 2) + '\n');
43
+ }
44
+ /**
45
+ * 原子读取——自动按状态恢复。文件不存在时返回 null。
46
+ *
47
+ * 调用方拿到 null 应当作"文件不存在"处理;解析失败抛错。
48
+ */
49
+ export function atomicRead(filePath) {
50
+ const hot = filePath + HOT;
51
+ const tmp = filePath + TMP;
52
+ // 中途崩溃残留 → 丢弃
53
+ if (fs.existsSync(tmp)) {
54
+ try {
55
+ fs.unlinkSync(tmp);
56
+ }
57
+ catch { /* best effort */ }
58
+ }
59
+ if (fs.existsSync(filePath)) {
60
+ return fs.readFileSync(filePath, 'utf-8');
61
+ }
62
+ // rename 步骤崩溃 → 把 hot 移回正式名
63
+ if (fs.existsSync(hot)) {
64
+ fs.renameSync(hot, filePath);
65
+ return fs.readFileSync(filePath, 'utf-8');
66
+ }
67
+ return null;
68
+ }
69
+ export function atomicReadJson(filePath) {
70
+ const text = atomicRead(filePath);
71
+ if (text === null)
72
+ return null;
73
+ return JSON.parse(text);
74
+ }
75
+ export function ensureDir(dirPath) {
76
+ if (!fs.existsSync(dirPath)) {
77
+ fs.mkdirSync(dirPath, { recursive: true });
78
+ }
79
+ }
80
+ /**
81
+ * Simple atomic write for plain-text files (ECK runtime files).
82
+ * Uses write-tmp-then-rename (no hot-backup needed for these files).
83
+ */
84
+ export function atomicWriteText(filePath, content) {
85
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
86
+ const tmp = filePath + '.tmp.' + process.pid;
87
+ fs.writeFileSync(tmp, content, 'utf-8');
88
+ fs.renameSync(tmp, filePath);
89
+ }
90
+ export function atomicWriteBytes(filePath, content) {
91
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
92
+ const tmp = filePath + '.tmp.' + process.pid;
93
+ fs.writeFileSync(tmp, content);
94
+ fs.renameSync(tmp, filePath);
95
+ }
@@ -0,0 +1,123 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import crypto from 'crypto';
5
+ import { agentmdGet, agentmdPut, updateAgentMdFrontmatterAvatar } from '../aun/aid/agentmd.js';
6
+ import { storageRm, storageUpload } from '../aun/storage/index.js';
7
+ import { logger } from './logger.js';
8
+ import { validateImage } from './media-cache.js';
9
+ const MAX_AVATAR_BYTES = 500 * 1024;
10
+ const ALLOWED_AVATAR_MIMES = new Set(['image/png', 'image/jpeg', 'image/webp']);
11
+ const MIME_EXT = {
12
+ 'image/png': 'png',
13
+ 'image/jpeg': 'jpg',
14
+ 'image/webp': 'webp',
15
+ };
16
+ function fail(error, code) {
17
+ return { ok: false, error, code };
18
+ }
19
+ function messageOf(e) {
20
+ return e instanceof Error ? e.message : String(e);
21
+ }
22
+ function classifyAgentMdReadError(e) {
23
+ return /not found|404|不存在/i.test(messageOf(e)) ? 'NOT_FOUND' : 'UPLOAD_FAILED';
24
+ }
25
+ function estimateDecodedBytes(base64) {
26
+ const padding = base64.endsWith('==') ? 2 : base64.endsWith('=') ? 1 : 0;
27
+ return Math.floor((base64.length * 3) / 4) - padding;
28
+ }
29
+ function formatKb(bytes) {
30
+ return `${Math.ceil(bytes / 1024)}KB`;
31
+ }
32
+ function parseAvatarDataUrl(dataUrl) {
33
+ if (typeof dataUrl !== 'string') {
34
+ return fail('avatar 必须是 data URL 字符串', 'INVALID_ARGS');
35
+ }
36
+ const match = dataUrl.match(/^data:(image\/(?:png|jpeg|webp));base64,([\s\S]+)$/);
37
+ if (!match) {
38
+ return fail('avatar 格式错误:必须是 data:image/(png|jpeg|webp);base64,', 'INVALID_ARGS');
39
+ }
40
+ const declaredMime = match[1];
41
+ const base64 = match[2].replace(/\s+/g, '');
42
+ if (!base64 || !/^[A-Za-z0-9+/]+={0,2}$/.test(base64) || base64.length % 4 !== 0) {
43
+ return fail('avatar base64 编码无效', 'INVALID_ARGS');
44
+ }
45
+ const estimatedSize = estimateDecodedBytes(base64);
46
+ if (estimatedSize > MAX_AVATAR_BYTES) {
47
+ return fail(`头像大小超过 500KB 限制(当前: ${formatKb(estimatedSize)})`, 'INVALID_ARGS');
48
+ }
49
+ const buffer = Buffer.from(base64, 'base64');
50
+ if (buffer.length > MAX_AVATAR_BYTES) {
51
+ return fail(`头像大小超过 500KB 限制(当前: ${formatKb(buffer.length)})`, 'INVALID_ARGS');
52
+ }
53
+ return { mime: declaredMime, buffer };
54
+ }
55
+ async function cleanupUploadedAvatar(aid, objectKey, aunPath) {
56
+ try {
57
+ await storageRm(aid, objectKey, { aunPath });
58
+ }
59
+ catch (e) {
60
+ logger.warn(`[avatar-upload] cleanup failed for ${aid}/${objectKey}: ${messageOf(e)}`);
61
+ }
62
+ }
63
+ export async function uploadAvatar(aid, dataUrl, opts) {
64
+ const parsed = parseAvatarDataUrl(dataUrl);
65
+ if ('ok' in parsed)
66
+ return parsed;
67
+ const validated = await validateImage(parsed.buffer, {
68
+ maxSize: MAX_AVATAR_BYTES,
69
+ allowedMimes: ALLOWED_AVATAR_MIMES,
70
+ });
71
+ if (validated.mime === null) {
72
+ return fail(`头像格式验证失败:${validated.reason}`, 'INVALID_ARGS');
73
+ }
74
+ if (validated.mime !== parsed.mime) {
75
+ return fail(`头像格式验证失败:声明 ${parsed.mime},实际 ${validated.mime}`, 'INVALID_ARGS');
76
+ }
77
+ let agentMd;
78
+ try {
79
+ agentMd = await agentmdGet(aid, { aunPath: opts?.aunPath });
80
+ }
81
+ catch (e) {
82
+ return fail(`agent.md 不存在或无法读取: ${messageOf(e)}`, classifyAgentMdReadError(e));
83
+ }
84
+ const ext = MIME_EXT[validated.mime] ?? 'img';
85
+ const objectKey = `shared/avatars/${crypto.randomUUID()}.${ext}`;
86
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'evolcore-avatar-'));
87
+ const tmpPath = path.join(tmpDir, `avatar.${ext}`);
88
+ let uploaded = false;
89
+ try {
90
+ fs.writeFileSync(tmpPath, parsed.buffer);
91
+ const uploadedAvatar = await storageUpload(aid, tmpPath, objectKey, {
92
+ isPublic: true,
93
+ contentType: validated.mime,
94
+ aunPath: opts?.aunPath,
95
+ });
96
+ if (!uploadedAvatar.ok) {
97
+ return fail(`上传到 AUN storage 失败: ${uploadedAvatar.error || 'unknown error'}`, 'UPLOAD_FAILED');
98
+ }
99
+ uploaded = true;
100
+ const publicUrl = uploadedAvatar.publicUrl ?? `https://${aid}/${uploadedAvatar.objectKey}`;
101
+ const updated = updateAgentMdFrontmatterAvatar(agentMd, publicUrl);
102
+ try {
103
+ await agentmdPut(updated.content, { aid, aunPath: opts?.aunPath });
104
+ }
105
+ catch (e) {
106
+ await cleanupUploadedAvatar(aid, uploadedAvatar.objectKey, opts?.aunPath);
107
+ return fail(`更新 agent.md 失败: ${messageOf(e)}`, 'UPLOAD_FAILED');
108
+ }
109
+ logger.info(`[avatar-upload] uploaded ${uploadedAvatar.objectKey} -> ${publicUrl}`);
110
+ return { ok: true, publicUrl, objectKey: uploadedAvatar.objectKey };
111
+ }
112
+ catch (e) {
113
+ if (uploaded)
114
+ await cleanupUploadedAvatar(aid, objectKey, opts?.aunPath);
115
+ return fail(messageOf(e), 'UPLOAD_FAILED');
116
+ }
117
+ finally {
118
+ try {
119
+ fs.rmSync(tmpDir, { recursive: true, force: true });
120
+ }
121
+ catch { /* ignore */ }
122
+ }
123
+ }