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,331 @@
1
+ import path from 'path';
2
+ import { formatChannelKey } from './channel-loader.js';
3
+ import { agentPersonalDir } from '../paths.js';
4
+ import { fileCache } from './daemon-file-cache.js';
5
+ import { ConfigTarget, read as cfgRead, write as cfgWrite, ensureFile as cfgEnsure, resolveEffective } from '../config/config-manager.js';
6
+ import { withLifecycleForWrite } from '../config/lifecycle.js';
7
+ /**
8
+ * EvolAgent —— 一个 self-agent 的运行时表示。
9
+ *
10
+ * 输入:EffectiveAgentConfig(defaults + per-agent 合并后的 effective 形态)。
11
+ * 持有该 agent 的 channels Map、活跃状态、生命周期。
12
+ *
13
+ * 写入永远落到 agents/<aid>/config.json(通过 ConfigManager 走原子写入),
14
+ * 不再有 "DefaultAgent" 概念,不再有 globalWriter / configPath null 的路径。
15
+ */
16
+ export class EvolAgent {
17
+ aid;
18
+ /** 兼容字段:name = aid。channel routing / log / IPC 都用 aid 作 agent 标识。 */
19
+ name;
20
+ /** in-memory effective config(含 defaults 合并结果);写盘时只回写 per-agent 部分。 */
21
+ merged;
22
+ /** per-agent 原始配置(写盘真相源) */
23
+ rawAgent;
24
+ channels = new Map();
25
+ activeSessions = 0;
26
+ lastActivity;
27
+ status;
28
+ error;
29
+ constructor(rawAgent, merged) {
30
+ if (rawAgent.aid !== merged.aid) {
31
+ throw new Error(`EvolAgent: rawAgent.aid (${rawAgent.aid}) != merged.aid (${merged.aid})`);
32
+ }
33
+ this.rawAgent = rawAgent;
34
+ this.merged = merged;
35
+ this.aid = rawAgent.aid;
36
+ this.name = rawAgent.aid;
37
+ this.status = rawAgent.enabled === false ? 'disabled' : 'stopped';
38
+ }
39
+ /** 当前 effective config(合并后的) */
40
+ get config() {
41
+ return this.merged;
42
+ }
43
+ get baseagent() {
44
+ if (!this.merged.active_baseagent) {
45
+ throw new Error(`[EvolAgent] active_baseagent is empty for agent ${this.name}`);
46
+ }
47
+ return this.merged.active_baseagent;
48
+ }
49
+ get model() {
50
+ const ba = this.baseagent;
51
+ // 动态读取配置(基于 fileCache + mtime),使配置变更立即体现在显示中
52
+ try {
53
+ const effective = resolveEffective({ self: this.aid }, { cache: true });
54
+ const block = effective.baseagents?.[ba];
55
+ return block?.model;
56
+ }
57
+ catch {
58
+ // 降级:读取快照
59
+ const block = this.merged.baseagents?.[ba];
60
+ return block?.model;
61
+ }
62
+ }
63
+ get effort() {
64
+ const ba = this.baseagent;
65
+ // 动态读取配置(基于 fileCache + mtime),使配置变更立即体现在显示中
66
+ try {
67
+ const effective = resolveEffective({ self: this.aid }, { cache: true });
68
+ const block = effective.baseagents?.[ba];
69
+ if (ba === 'codex')
70
+ return block?.effort ?? block?.reasoning;
71
+ return block?.effort;
72
+ }
73
+ catch {
74
+ // 降级:读取快照
75
+ const block = this.merged.baseagents?.[ba];
76
+ if (ba === 'codex')
77
+ return block?.effort ?? block?.reasoning;
78
+ return block?.effort;
79
+ }
80
+ }
81
+ get projectPath() {
82
+ return this.merged.projects?.defaultPath || process.cwd();
83
+ }
84
+ // ── Channels ──────────────────────────────────────────────────────────
85
+ /**
86
+ * effective channel key:`<type>#<selfAID>#<name>`。
87
+ * AUN channel 的 selfAID 是 agent.aid,name 固定为 'main'。
88
+ */
89
+ effectiveChannelName(type, rawName) {
90
+ return formatChannelKey({ type, selfAID: this.aid, name: rawName });
91
+ }
92
+ channelInstanceNames() {
93
+ // AUN channel 隐式存在(从 agent.aid 派生),不需要在 channels[] 里声明
94
+ const aunKey = this.effectiveChannelName('aun', 'main');
95
+ const others = this.merged.channels
96
+ .filter(c => c.type !== 'aun')
97
+ .map(c => this.effectiveChannelName(c.type, c.name));
98
+ return [aunKey, ...others];
99
+ }
100
+ /** 列出所有 channel 实例(含 effective key) */
101
+ listChannels() {
102
+ return this.merged.channels.map(inst => ({
103
+ key: this.effectiveChannelName(inst.type, inst.name),
104
+ instance: inst,
105
+ }));
106
+ }
107
+ /**
108
+ * 按 effective channel key 找到 instance(只读视图)。
109
+ * 找不到返回 null。
110
+ */
111
+ findChannelInstance(channelKey) {
112
+ return this.merged.channels.find(c => this.effectiveChannelName(c.type, c.name) === channelKey) ?? null;
113
+ }
114
+ // ── ShowActivities ────────────────────────────────────────────────────
115
+ getShowActivities(_channelKey) {
116
+ return this.merged.show_activities ?? 'all';
117
+ }
118
+ setShowActivities(_channelKey, mode) {
119
+ this.merged.show_activities = mode;
120
+ this.updateAgentConfig(b => { b.show_activities = mode; });
121
+ }
122
+ // ── Role-based Access Control ─────────────────────────────────────────
123
+ /**
124
+ * Check if a user has owner role for this agent (private scope).
125
+ */
126
+ isOwner(_channelKey, userId) {
127
+ if (this.merged.owners?.includes(userId))
128
+ return true;
129
+ return false;
130
+ }
131
+ /**
132
+ * Check if a user has admin role for this agent (private scope).
133
+ * Uses static owners/admins from agent config.
134
+ */
135
+ isAdmin(_channelKey, userId) {
136
+ if (this.isOwner(_channelKey, userId))
137
+ return true;
138
+ return this.merged.admins?.includes(userId) ?? false;
139
+ }
140
+ /**
141
+ * Get the first owner of this agent (private scope).
142
+ * Returns the peerId of the first owner assignment.
143
+ */
144
+ getOwner(_channelKey) {
145
+ return this.merged.owners?.[0];
146
+ }
147
+ /**
148
+ * Set a user as owner for this agent (private scope).
149
+ */
150
+ setOwner(_channelKey, userId) {
151
+ const owners = new Set(this.merged.owners ?? []);
152
+ owners.add(userId);
153
+ const nextOwners = [...owners];
154
+ this.merged.owners = nextOwners;
155
+ this.updateAgentConfig(b => { b.owners = nextOwners; });
156
+ }
157
+ // ── Baseagent 字段写入 ────────────────────────────
158
+ /** 切换当前活跃 baseagent(写入 config.json)。 */
159
+ setActiveBaseagent(value) {
160
+ this.merged.active_baseagent = value;
161
+ this.updateAgentConfig(b => {
162
+ if (value === undefined)
163
+ delete b.active_baseagent;
164
+ else
165
+ b.active_baseagent = value;
166
+ });
167
+ }
168
+ setBaseagentModel(value, baseagentName) {
169
+ const ba = baseagentName || this.baseagent;
170
+ if (!this.merged.baseagents)
171
+ this.merged.baseagents = {};
172
+ const mBlock = ((this.merged.baseagents)[ba] ??= {});
173
+ if (value === undefined)
174
+ delete mBlock.model;
175
+ else
176
+ mBlock.model = value;
177
+ this.updateAgentConfig(b => {
178
+ b.baseagents = b.baseagents || {};
179
+ const blk = (b.baseagents[ba] ??= {});
180
+ if (value === undefined)
181
+ delete blk.model;
182
+ else
183
+ blk.model = value;
184
+ });
185
+ }
186
+ setBaseagentEffort(value, baseagentName) {
187
+ const ba = baseagentName || this.baseagent;
188
+ if (!this.merged.baseagents)
189
+ this.merged.baseagents = {};
190
+ const mBlock = ((this.merged.baseagents)[ba] ??= {});
191
+ if (value === undefined) {
192
+ delete mBlock.effort;
193
+ delete mBlock.reasoning;
194
+ }
195
+ else {
196
+ mBlock.effort = value;
197
+ delete mBlock.reasoning;
198
+ }
199
+ this.updateAgentConfig(b => {
200
+ b.baseagents = b.baseagents || {};
201
+ const blk = (b.baseagents[ba] ??= {});
202
+ if (value === undefined) {
203
+ delete blk.effort;
204
+ delete blk.reasoning;
205
+ }
206
+ else {
207
+ blk.effort = value;
208
+ delete blk.reasoning;
209
+ }
210
+ });
211
+ }
212
+ /** 设置私聊 chatmode(群聊/非 human 强制 proactive,无可写入项)。 */
213
+ setChatmodePrivate(value) {
214
+ if (!this.merged.chatmode)
215
+ this.merged.chatmode = {};
216
+ this.merged.chatmode.private = value;
217
+ this.updateAgentConfig(b => {
218
+ b.chatmode = b.chatmode || {};
219
+ if (value === undefined)
220
+ delete b.chatmode.private;
221
+ else
222
+ b.chatmode.private = value;
223
+ });
224
+ }
225
+ /** 设置群聊 mentionMode(mention-only | disabled)。 */
226
+ setMentionMode(value) {
227
+ this.merged.mentionMode = value;
228
+ this.updateAgentConfig(b => {
229
+ if (value === undefined)
230
+ delete b.mentionMode;
231
+ else
232
+ b.mentionMode = value;
233
+ });
234
+ }
235
+ /** 读取观察者模式开关(默认 false)。 */
236
+ getObservable() {
237
+ return this.merged.observable === true;
238
+ }
239
+ /** 设置观察者模式开关:开启后入站/出站消息各转发一份给 owners[]。 */
240
+ setObservable(value) {
241
+ if (value)
242
+ this.rawAgent.observable = true;
243
+ else
244
+ delete this.rawAgent.observable;
245
+ this.merged.observable = value;
246
+ this.persist();
247
+ }
248
+ setLifecycle(value) {
249
+ this.rawAgent = withLifecycleForWrite(this.rawAgent, value);
250
+ this.rawAgent.$schema_version = Math.max(this.rawAgent.$schema_version || 0, this.merged.$schema_version || 0);
251
+ this.merged.lifecycle = value;
252
+ delete this.merged.initialized;
253
+ this.persist();
254
+ }
255
+ // ── Personal layer ────────────────────────────────────────────────────
256
+ /** 本 agent 身份层文件在 fileCache 的组名(带 aid,避免 reload 单个 agent 误失效他人)。 */
257
+ agentFilesGroup() {
258
+ return `agent-files:${this.aid}`;
259
+ }
260
+ /**
261
+ * 读取 personal/persona.md 内容。走 fileCache(mtime 门控):persona 没有任何
262
+ * 写入命令、由 agent 自己带外改写,与 working memory 同样改了即应生效,故每次读
263
+ * stat 比对、变了自动重读。文件不存在返回 null。
264
+ */
265
+ getPersona() {
266
+ const personaPath = path.join(agentPersonalDir(this.aid), 'persona.md');
267
+ return fileCache.get(personaPath, (raw) => (raw === null ? null : (raw.trim() || null)), { policy: 'mtime', group: this.agentFilesGroup() });
268
+ }
269
+ /**
270
+ * 读取 personal/memory/working.md 内容。走 fileCache(mtime 门控):
271
+ * agent 在对话中改写 working memory、不触发 reload,故每次读 stat 比对、
272
+ * 变了自动重读,既即时反映又避免无谓重读。
273
+ */
274
+ getWorkingMemory() {
275
+ const workingPath = path.join(agentPersonalDir(this.aid), 'memory', 'working.md');
276
+ return fileCache.get(workingPath, (raw) => (raw === null ? null : (raw.trim() || null)), { policy: 'mtime', group: this.agentFilesGroup() });
277
+ }
278
+ /** 清除本 agent 身份层缓存(reload 后重新读取)。只失效自己的文件组,不波及他人。 */
279
+ invalidatePersonaCache() {
280
+ fileCache.invalidateGroup(this.agentFilesGroup());
281
+ }
282
+ // ── Context(喂给 message-processor / command-handler) ──────────────
283
+ getContext(_channelKey, chatType, globalChatmode) {
284
+ const chatMode = this.resolveChatMode(chatType, globalChatmode);
285
+ return {
286
+ name: this.name,
287
+ isOwned: true,
288
+ baseagent: this.baseagent,
289
+ model: this.model,
290
+ effort: this.effort,
291
+ chatMode,
292
+ projectPath: this.projectPath,
293
+ };
294
+ }
295
+ resolveChatMode(chatType, globalChatmode) {
296
+ const key = chatType === 'group' ? 'group' : 'private';
297
+ return (this.merged.chatmode?.[key]
298
+ ?? globalChatmode?.[key]
299
+ ?? (key === 'group' ? 'proactive' : 'interactive'));
300
+ }
301
+ // ── Reload 支持:替换 in-memory config 并复用 channels Map ───────────
302
+ /** 用新的 raw + merged 替换 in-memory 状态。channels 由调用方决定如何 reconcile。 */
303
+ swapConfig(rawAgent, merged) {
304
+ if (rawAgent.aid !== this.aid) {
305
+ throw new Error(`EvolAgent.swapConfig: aid mismatch (${rawAgent.aid} vs ${this.aid})`);
306
+ }
307
+ this.rawAgent = rawAgent;
308
+ this.merged = merged;
309
+ }
310
+ // ── 内部辅助 ─────────────────────────────────────────────────────────
311
+ /**
312
+ * 找 rawAgent.channels 里的可变实例,用于写入。
313
+ *
314
+ * merged.channels 是 deep clone 时 raw 跟 merged 的 channels 引用同一份(resolveEffective
315
+ * 直接透传 agent.channels),所以 raw 里的实例就等于 merged 里的实例。
316
+ */
317
+ findRawChannelInstance(channelKey) {
318
+ return this.rawAgent.channels.find(c => this.effectiveChannelName(c.type, c.name) === channelKey) ?? null;
319
+ }
320
+ persist() {
321
+ cfgWrite(ConfigTarget.Agent, this.rawAgent, { self: this.aid });
322
+ }
323
+ /** 读改写 agent 级 config.json(v3 设计,走 ConfigManager 唯一写入口)。 */
324
+ updateAgentConfig(fn) {
325
+ const sel = { self: this.aid };
326
+ const cur = cfgRead(ConfigTarget.Agent, sel) || {};
327
+ fn(cur);
328
+ cfgEnsure(ConfigTarget.Agent, sel);
329
+ cfgWrite(ConfigTarget.Agent, cur, sel);
330
+ }
331
+ }
@@ -0,0 +1,229 @@
1
+ import { logger } from '../../utils/logger.js';
2
+ export class HandoffDispatcher {
3
+ store;
4
+ mutexes;
5
+ sender;
6
+ running = new Set();
7
+ pausedAgents = new Set();
8
+ maxAttempts;
9
+ retryDelaysMs;
10
+ queueTtlMs;
11
+ now;
12
+ expirationTimers = new Map();
13
+ constructor(store, mutexes, sender, options = {}) {
14
+ this.store = store;
15
+ this.mutexes = mutexes;
16
+ this.sender = sender;
17
+ this.maxAttempts = options.maxAttempts ?? 3;
18
+ this.retryDelaysMs = options.retryDelaysMs ?? [250, 1000];
19
+ this.queueTtlMs = typeof options.queueTtlMs === 'number'
20
+ && Number.isFinite(options.queueTtlMs)
21
+ && options.queueTtlMs > 0
22
+ ? options.queueTtlMs
23
+ : undefined;
24
+ this.now = options.now ?? Date.now;
25
+ }
26
+ notify(selfAid, targetSessionId) {
27
+ if (this.pausedAgents.has(selfAid))
28
+ return;
29
+ const key = this.key(selfAid, targetSessionId);
30
+ if (this.running.has(key))
31
+ return;
32
+ this.running.add(key);
33
+ void this.run(selfAid, targetSessionId).finally(() => this.running.delete(key));
34
+ }
35
+ pauseAgent(selfAid) {
36
+ this.pausedAgents.add(selfAid);
37
+ const prefix = `${selfAid}\u0000`;
38
+ for (const [key, timer] of this.expirationTimers) {
39
+ if (!key.startsWith(prefix))
40
+ continue;
41
+ clearTimeout(timer);
42
+ this.expirationTimers.delete(key);
43
+ }
44
+ }
45
+ async drainAgent(selfAid, timeoutMs = 30000) {
46
+ this.pauseAgent(selfAid);
47
+ const startedAt = Date.now();
48
+ while (this.isAgentRunning(selfAid)) {
49
+ if (Date.now() - startedAt >= timeoutMs) {
50
+ throw new Error(`Handoff drain timeout (${timeoutMs}ms) for agent: ${selfAid}`);
51
+ }
52
+ await new Promise(resolve => setTimeout(resolve, 10));
53
+ }
54
+ }
55
+ resumeAgent(selfAid) {
56
+ this.pausedAgents.delete(selfAid);
57
+ const targets = new Set(this.store.list(selfAid)
58
+ .filter(instance => instance.state === 'queued')
59
+ .map(instance => instance.target_session_id));
60
+ for (const targetSessionId of targets)
61
+ this.notify(selfAid, targetSessionId);
62
+ }
63
+ isAgentPaused(selfAid) {
64
+ return this.pausedAgents.has(selfAid);
65
+ }
66
+ async drain(selfAid, targetSessionId) {
67
+ const key = this.key(selfAid, targetSessionId);
68
+ if (this.running.has(key)) {
69
+ while (this.running.has(key))
70
+ await new Promise(resolve => setTimeout(resolve, 5));
71
+ }
72
+ this.running.add(key);
73
+ try {
74
+ await this.run(selfAid, targetSessionId);
75
+ }
76
+ finally {
77
+ this.running.delete(key);
78
+ }
79
+ }
80
+ expireQueued(selfAid, targetSessionId) {
81
+ if (this.queueTtlMs === undefined)
82
+ return [];
83
+ const now = this.now();
84
+ const expired = [];
85
+ const queued = targetSessionId
86
+ ? this.store.listByTarget(selfAid, targetSessionId, 'queued')
87
+ : this.store.list(selfAid).filter(instance => instance.state === 'queued');
88
+ for (const instance of queued) {
89
+ if (now - instance.created_at < this.queueTtlMs)
90
+ continue;
91
+ expired.push(this.store.recordSendExpired(selfAid, instance.handoff_id, this.queueTtlMs, now));
92
+ logger.warn(`[Handoff] queued delivery expired: ${instance.handoff_id}`);
93
+ }
94
+ return expired;
95
+ }
96
+ async expireTargetQueued(selfAid, targetSessionId) {
97
+ return this.mutexes.forKey(this.key(selfAid, targetSessionId)).runExclusive(async () => this.expireQueued(selfAid, targetSessionId));
98
+ }
99
+ async run(selfAid, targetSessionId) {
100
+ while (true) {
101
+ if (this.pausedAgents.has(selfAid))
102
+ return;
103
+ this.expireQueued(selfAid, targetSessionId);
104
+ const next = this.store.listByTarget(selfAid, targetSessionId, 'queued')[0];
105
+ if (!next)
106
+ return;
107
+ if (next.attention_required) {
108
+ this.scheduleExpiration(selfAid, next);
109
+ return;
110
+ }
111
+ let sent = false;
112
+ let blocked = false;
113
+ for (let attempt = 1; attempt <= this.maxAttempts; attempt++) {
114
+ if (this.pausedAgents.has(selfAid))
115
+ return;
116
+ sent = await this.mutexes.forKey(this.key(selfAid, targetSessionId)).runExclusive(async () => {
117
+ if (this.pausedAgents.has(selfAid))
118
+ return false;
119
+ const current = this.store.get(selfAid, next.handoff_id);
120
+ if (!current || current.state !== 'queued' || current.attention_required)
121
+ return current?.state === 'target_sent';
122
+ this.store.recordSendStarted(selfAid, current.handoff_id);
123
+ try {
124
+ const send = await this.sendWithinQueueDeadline(current);
125
+ if (send.timedOut) {
126
+ blocked = true;
127
+ this.store.markAttention(selfAid, current.handoff_id, 'TARGET_SEND_OUTCOME_UNKNOWN');
128
+ return false;
129
+ }
130
+ const result = send.result;
131
+ if (!result.ok) {
132
+ this.store.recordSendFailed(selfAid, current.handoff_id, result.error || 'target send failed');
133
+ return false;
134
+ }
135
+ if (!result.message_id) {
136
+ blocked = true;
137
+ this.store.markAttention(selfAid, current.handoff_id, 'TARGET_SEND_OUTCOME_UNKNOWN');
138
+ return false;
139
+ }
140
+ try {
141
+ this.store.recordSendSucceeded(selfAid, current.handoff_id, result.message_id);
142
+ }
143
+ catch (error) {
144
+ blocked = true;
145
+ try {
146
+ this.store.markAttention(selfAid, current.handoff_id, 'STORE_CONFLICT');
147
+ }
148
+ catch { }
149
+ logger.error(`[Handoff] failed to persist successful target send: ${current.handoff_id}`, error);
150
+ return false;
151
+ }
152
+ return true;
153
+ }
154
+ catch (error) {
155
+ this.store.recordSendFailed(selfAid, current.handoff_id, error instanceof Error ? error.message : String(error));
156
+ return false;
157
+ }
158
+ });
159
+ if (this.pausedAgents.has(selfAid))
160
+ return;
161
+ if (sent)
162
+ break;
163
+ if (blocked) {
164
+ const current = this.store.get(selfAid, next.handoff_id);
165
+ if (current?.state === 'queued' && current.attention_required) {
166
+ this.scheduleExpiration(selfAid, current);
167
+ }
168
+ return;
169
+ }
170
+ if (attempt < this.maxAttempts) {
171
+ await new Promise(resolve => setTimeout(resolve, this.retryDelaysMs[Math.min(attempt - 1, this.retryDelaysMs.length - 1)] ?? 0));
172
+ }
173
+ }
174
+ if (!sent) {
175
+ this.store.recordSendExhausted(selfAid, next.handoff_id);
176
+ logger.error(`[Handoff] target send retries exhausted: ${next.handoff_id}`);
177
+ continue;
178
+ }
179
+ await Promise.resolve();
180
+ }
181
+ }
182
+ key(selfAid, targetSessionId) {
183
+ return `${selfAid}\u0000${targetSessionId}`;
184
+ }
185
+ scheduleExpiration(selfAid, instance) {
186
+ if (this.queueTtlMs === undefined || this.pausedAgents.has(selfAid))
187
+ return;
188
+ const key = this.key(selfAid, instance.target_session_id);
189
+ if (this.expirationTimers.has(key))
190
+ return;
191
+ const remainingMs = Math.max(1, instance.created_at + this.queueTtlMs - this.now());
192
+ const timer = setTimeout(() => {
193
+ this.expirationTimers.delete(key);
194
+ this.notify(selfAid, instance.target_session_id);
195
+ }, Math.min(remainingMs, 2_147_483_647));
196
+ timer.unref?.();
197
+ this.expirationTimers.set(key, timer);
198
+ }
199
+ async sendWithinQueueDeadline(instance) {
200
+ if (this.queueTtlMs === undefined) {
201
+ return { timedOut: false, result: await this.sender(instance) };
202
+ }
203
+ const remainingMs = instance.created_at + this.queueTtlMs - this.now();
204
+ if (remainingMs <= 0)
205
+ return { timedOut: true };
206
+ let timer;
207
+ const sendOutcome = this.sender(instance).then(result => ({ kind: 'result', result }), error => ({ kind: 'error', error }));
208
+ const timeoutOutcome = new Promise(resolve => {
209
+ timer = setTimeout(() => resolve({ kind: 'timeout' }), Math.min(remainingMs, 2_147_483_647));
210
+ timer.unref?.();
211
+ });
212
+ const outcome = await Promise.race([sendOutcome, timeoutOutcome]);
213
+ if (timer)
214
+ clearTimeout(timer);
215
+ if (outcome.kind === 'timeout')
216
+ return { timedOut: true };
217
+ if (outcome.kind === 'error')
218
+ throw outcome.error;
219
+ return { timedOut: false, result: outcome.result };
220
+ }
221
+ isAgentRunning(selfAid) {
222
+ const prefix = `${selfAid}\u0000`;
223
+ for (const key of this.running) {
224
+ if (key.startsWith(prefix))
225
+ return true;
226
+ }
227
+ return false;
228
+ }
229
+ }
@@ -0,0 +1,46 @@
1
+ export class FairMutex {
2
+ locked = false;
3
+ waiters = [];
4
+ acquire() {
5
+ return new Promise(resolve => {
6
+ const grant = () => {
7
+ this.locked = true;
8
+ let released = false;
9
+ resolve(() => {
10
+ if (released)
11
+ return;
12
+ released = true;
13
+ const next = this.waiters.shift();
14
+ if (next)
15
+ queueMicrotask(next);
16
+ else
17
+ this.locked = false;
18
+ });
19
+ };
20
+ if (!this.locked && this.waiters.length === 0)
21
+ grant();
22
+ else
23
+ this.waiters.push(grant);
24
+ });
25
+ }
26
+ async runExclusive(fn) {
27
+ const release = await this.acquire();
28
+ try {
29
+ return await fn();
30
+ }
31
+ finally {
32
+ release();
33
+ }
34
+ }
35
+ }
36
+ export class KeyedFairMutex {
37
+ locks = new Map();
38
+ forKey(key) {
39
+ let lock = this.locks.get(key);
40
+ if (!lock) {
41
+ lock = new FairMutex();
42
+ this.locks.set(key, lock);
43
+ }
44
+ return lock;
45
+ }
46
+ }