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,168 @@
1
+ import { isManagementRole } from '../config/builtin-roles.js';
2
+ import { getRoleDefinition } from '../config/roles.js';
3
+ import { formatPeerKey } from '../core/relation/peer-identity.js';
4
+ import { getDbPath, openReadonlyDb } from './db.js';
5
+ const DEFAULT_COST_BASIS = 'gateway';
6
+ const DEFAULT_SCOPE = 'subject';
7
+ const DEFAULT_RESET_MODE = 'daily';
8
+ const DEFAULT_CURRENCY = 'CNY';
9
+ const RESET_MODES = new Set(['never', 'daily', 'weekly', 'monthly']);
10
+ const CURRENCIES = new Set(['CNY', 'USD']);
11
+ export function formatUsageSubjectKey(channelType, chatType, channelId, peerId) {
12
+ const subjectId = (peerId || (chatType === 'private' ? channelId : '') || channelId || '').trim();
13
+ return subjectId ? formatPeerKey(channelType, subjectId) : '';
14
+ }
15
+ export function getRoleBudgetStatus(evolcoreHome, req) {
16
+ const role = req.role || 'none';
17
+ const usageSubjectKey = formatUsageSubjectKey(req.channelType, req.chatType, req.channelId, req.peerId);
18
+ const disabled = (reason, overrides = {}) => ({
19
+ role,
20
+ enabled: false,
21
+ scope: DEFAULT_SCOPE,
22
+ cost_basis: DEFAULT_COST_BASIS,
23
+ reset_mode: DEFAULT_RESET_MODE,
24
+ currency: DEFAULT_CURRENCY,
25
+ period_start_ts: null,
26
+ usage_subject_key: usageSubjectKey,
27
+ limit_amount: -1,
28
+ used_amount: 0,
29
+ remaining_amount: -1,
30
+ pct_used: 0,
31
+ hard_blocked: false,
32
+ reason,
33
+ ...overrides,
34
+ });
35
+ if (!req.selfAid || !role || role === 'none')
36
+ return disabled('no_role');
37
+ if (isManagementRole(role))
38
+ return disabled('management_role');
39
+ const limits = resolveRoleUsageLimits(role, req.selfAid);
40
+ if (!limits)
41
+ return disabled('no_limits');
42
+ const limitAmount = normalizeAmount(limits.limitAmount);
43
+ const currency = normalizeCurrency(limits.currency);
44
+ const resetMode = normalizeResetMode(limits.resetMode);
45
+ const periodStartTs = periodStartTsFor(resetMode);
46
+ const costBasis = limits.costBasis ?? DEFAULT_COST_BASIS;
47
+ const scope = limits.scope ?? DEFAULT_SCOPE;
48
+ const enabled = limits.enabled !== false && limitAmount !== undefined;
49
+ if (!enabled) {
50
+ return disabled('disabled', {
51
+ scope,
52
+ cost_basis: costBasis,
53
+ reset_mode: resetMode,
54
+ currency,
55
+ period_start_ts: periodStartTs,
56
+ });
57
+ }
58
+ const usage = queryPeriodUsage(evolcoreHome, {
59
+ selfAid: req.selfAid,
60
+ role,
61
+ scope,
62
+ costBasis,
63
+ usageSubjectKey,
64
+ fromTs: periodStartTs,
65
+ });
66
+ const usedAmount = currency === 'USD' ? usage.usd : usage.cny;
67
+ const pct = percentage(usedAmount, limitAmount);
68
+ return {
69
+ role,
70
+ enabled: true,
71
+ scope,
72
+ cost_basis: costBasis,
73
+ reset_mode: resetMode,
74
+ currency,
75
+ period_start_ts: periodStartTs,
76
+ usage_subject_key: usageSubjectKey,
77
+ limit_amount: limitAmount,
78
+ used_amount: usedAmount,
79
+ remaining_amount: Math.max(0, limitAmount - usedAmount),
80
+ pct_used: pct,
81
+ hard_blocked: pct >= 100,
82
+ };
83
+ }
84
+ function resolveRoleUsageLimits(role, selfAid) {
85
+ const roleDef = getRoleDefinition(role, selfAid);
86
+ if (!roleDef)
87
+ return null;
88
+ if (roleDef.usageLimits && (role === 'member' || role === 'visitor' || hasExplicitUsageLimit(roleDef.usageLimits))) {
89
+ return roleDef.usageLimits;
90
+ }
91
+ if (role !== 'member' && role !== 'visitor') {
92
+ return getRoleDefinition('member', selfAid)?.usageLimits ?? null;
93
+ }
94
+ return null;
95
+ }
96
+ function hasExplicitUsageLimit(limits) {
97
+ return limits.enabled === false
98
+ || limits.resetMode != null
99
+ || limits.currency != null
100
+ || limits.limitAmount != null
101
+ || limits.costBasis != null
102
+ || limits.scope != null;
103
+ }
104
+ function normalizeAmount(value) {
105
+ if (value == null)
106
+ return undefined;
107
+ return Number.isFinite(value) && value >= 0 ? value : undefined;
108
+ }
109
+ function percentage(used, limit) {
110
+ if (limit <= 0)
111
+ return 100;
112
+ return (used / limit) * 100;
113
+ }
114
+ function normalizeResetMode(value) {
115
+ return value && RESET_MODES.has(value) ? value : DEFAULT_RESET_MODE;
116
+ }
117
+ function normalizeCurrency(value) {
118
+ return value && CURRENCIES.has(value) ? value : DEFAULT_CURRENCY;
119
+ }
120
+ function queryPeriodUsage(evolcoreHome, opts) {
121
+ const db = openReadonlyDb(getDbPath(evolcoreHome));
122
+ if (!db)
123
+ return { cny: 0, usd: 0 };
124
+ try {
125
+ const columns = db.prepare(`PRAGMA table_info(usage_events)`).all();
126
+ const names = new Set(columns.map(c => c.name));
127
+ if (!names.has('role') || !names.has('usage_subject_key'))
128
+ return { cny: 0, usd: 0 };
129
+ const prefix = opts.costBasis === 'official' ? 'cost_official' : 'cost_gateway';
130
+ const where = ['agent_aid = ?', 'role = ?'];
131
+ const params = [opts.selfAid, opts.role];
132
+ if (opts.fromTs !== null) {
133
+ where.unshift('ts >= ?');
134
+ params.unshift(opts.fromTs);
135
+ }
136
+ if (opts.scope === 'subject') {
137
+ where.push('usage_subject_key = ?');
138
+ params.push(opts.usageSubjectKey);
139
+ }
140
+ const row = db.prepare(`
141
+ SELECT
142
+ COALESCE(SUM(${prefix}_cny), 0) AS cny,
143
+ COALESCE(SUM(${prefix}_usd), 0) AS usd
144
+ FROM usage_events
145
+ WHERE ${where.join(' AND ')}
146
+ `).get(...params);
147
+ return {
148
+ cny: Number(row?.cny ?? 0),
149
+ usd: Number(row?.usd ?? 0),
150
+ };
151
+ }
152
+ finally {
153
+ db.close();
154
+ }
155
+ }
156
+ function periodStartTsFor(resetMode) {
157
+ if (resetMode === 'never')
158
+ return null;
159
+ const now = new Date();
160
+ if (resetMode === 'weekly') {
161
+ const daysSinceMonday = (now.getDay() + 6) % 7;
162
+ return new Date(now.getFullYear(), now.getMonth(), now.getDate() - daysSinceMonday).getTime();
163
+ }
164
+ if (resetMode === 'monthly') {
165
+ return new Date(now.getFullYear(), now.getMonth(), 1).getTime();
166
+ }
167
+ return new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
168
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * writer.ts — 写入 usage_events 和 context_breakdown。
3
+ */
4
+ import { getDb, resetStatsDb } from './db.js';
5
+ import { resolvePrices } from './price-resolver.js';
6
+ import { logger } from '../utils/logger.js';
7
+ export function insertUsageEvent(evolcoreHome, event, gatewayPricing) {
8
+ const db = getDb(evolcoreHome);
9
+ // 写入时计算费用(官方价格 + 网关价格)。即使 DB 不可用也返回价格对,供调用方合入状态回执。
10
+ const prices = resolvePrices(evolcoreHome, event, gatewayPricing);
11
+ if (!db)
12
+ return prices;
13
+ try {
14
+ writeUsageEvent(db, event, prices);
15
+ }
16
+ catch (e) {
17
+ // 写入失败不影响主流程
18
+ try {
19
+ db.exec('ROLLBACK');
20
+ }
21
+ catch { }
22
+ if (isSchemaMismatchError(e)) {
23
+ logger.warn(`[StatsWriter] insertUsageEvent schema mismatch, retrying after DB migration: ${e}`);
24
+ const retryDb = resetStatsDb(evolcoreHome);
25
+ if (retryDb) {
26
+ try {
27
+ writeUsageEvent(retryDb, event, prices);
28
+ return prices;
29
+ }
30
+ catch (retryError) {
31
+ try {
32
+ retryDb.exec('ROLLBACK');
33
+ }
34
+ catch { }
35
+ logger.error(`[StatsWriter] insertUsageEvent retry failed: ${retryError}`);
36
+ }
37
+ }
38
+ }
39
+ else {
40
+ logger.warn(`[StatsWriter] insertUsageEvent failed: ${e}`);
41
+ }
42
+ }
43
+ return prices;
44
+ }
45
+ function isSchemaMismatchError(error) {
46
+ return /no column|no such column|has no column/i.test(String(error));
47
+ }
48
+ function writeUsageEvent(db, event, prices) {
49
+ // 明细 INSERT + rollup UPSERT 包进同一事务:进程在两者间崩溃也不会让 rollup 与明细漂移。
50
+ db.exec('BEGIN');
51
+ db.prepare(`
52
+ INSERT INTO usage_events
53
+ (ts, agent_aid, peer_key, usage_subject_key, role, peer_type, session_id, model, billing_fn,
54
+ input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens,
55
+ cache_hit_tokens, cache_miss_tokens, image_tokens, total_context_tokens,
56
+ turns, duration_ms, context_window_pct,
57
+ cost_official_usd, cost_official_cny, cost_gateway_usd, cost_gateway_cny)
58
+ VALUES
59
+ (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
60
+ `).run(event.ts, event.agent_aid, event.peer_key, event.usage_subject_key ?? event.peer_key ?? '', event.role ?? '', event.peer_type ?? null, event.session_id ?? null, event.model, event.billing_fn, event.input_tokens, event.output_tokens, event.cache_creation_tokens, event.cache_read_tokens, event.cache_hit_tokens ?? null, event.cache_miss_tokens ?? null, event.image_tokens ?? null, event.total_context_tokens ?? null, event.turns, event.duration_ms ?? null, event.context_window_pct ?? null, prices.official?.usd ?? null, prices.official?.cny ?? null, prices.gateway?.usd ?? null, prices.gateway?.cny ?? null);
61
+ // 写时增量:累加到日级预聚合表(grain 与 db.ts rebuildDailyRollup 一致)。
62
+ db.prepare(`
63
+ INSERT INTO usage_daily
64
+ (day, agent_aid, peer_key, peer_type, session_id, model, billing_fn,
65
+ input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens,
66
+ cache_hit_tokens, cache_miss_tokens, image_tokens, total_context_tokens,
67
+ turns, calls,
68
+ cost_official_usd, cost_official_cny, cost_gateway_usd, cost_gateway_cny)
69
+ VALUES
70
+ (strftime('%Y-%m-%d', ?/1000, 'unixepoch', 'localtime'),
71
+ ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?)
72
+ ON CONFLICT(day, agent_aid, peer_key, session_id, model, billing_fn) DO UPDATE SET
73
+ peer_type = excluded.peer_type,
74
+ input_tokens = input_tokens + excluded.input_tokens,
75
+ output_tokens = output_tokens + excluded.output_tokens,
76
+ cache_creation_tokens = cache_creation_tokens + excluded.cache_creation_tokens,
77
+ cache_read_tokens = cache_read_tokens + excluded.cache_read_tokens,
78
+ cache_hit_tokens = cache_hit_tokens + excluded.cache_hit_tokens,
79
+ cache_miss_tokens = cache_miss_tokens + excluded.cache_miss_tokens,
80
+ image_tokens = image_tokens + excluded.image_tokens,
81
+ total_context_tokens = total_context_tokens + excluded.total_context_tokens,
82
+ turns = turns + excluded.turns,
83
+ calls = calls + 1,
84
+ cost_official_usd = COALESCE(cost_official_usd, 0) + COALESCE(excluded.cost_official_usd, 0),
85
+ cost_official_cny = COALESCE(cost_official_cny, 0) + COALESCE(excluded.cost_official_cny, 0),
86
+ cost_gateway_usd = COALESCE(cost_gateway_usd, 0) + COALESCE(excluded.cost_gateway_usd, 0),
87
+ cost_gateway_cny = COALESCE(cost_gateway_cny, 0) + COALESCE(excluded.cost_gateway_cny, 0)
88
+ `).run(event.ts, event.agent_aid, event.peer_key, event.peer_type ?? '', event.session_id ?? '', event.model, event.billing_fn, event.input_tokens, event.output_tokens, event.cache_creation_tokens, event.cache_read_tokens, event.cache_hit_tokens ?? 0, event.cache_miss_tokens ?? 0, event.image_tokens ?? 0, event.total_context_tokens ?? 0, event.turns, prices.official?.usd ?? null, prices.official?.cny ?? null, prices.gateway?.usd ?? null, prices.gateway?.cny ?? null);
89
+ db.exec('COMMIT');
90
+ }
91
+ export function insertContextBreakdown(evolcoreHome, bd) {
92
+ const db = getDb(evolcoreHome);
93
+ if (!db)
94
+ return;
95
+ try {
96
+ db.prepare(`
97
+ INSERT INTO context_breakdown
98
+ (ts, agent_aid, session_id, turn_count, model, max_tokens,
99
+ system_prompt, system_tools, mcp_tools, custom_agents,
100
+ memory_files, skills, messages, free_space, total_estimated)
101
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
102
+ `).run(bd.ts, bd.agent_aid, bd.session_id, bd.turn_count, bd.model, bd.max_tokens, bd.system_prompt ?? null, bd.system_tools ?? null, bd.mcp_tools ?? null, bd.custom_agents ?? null, bd.memory_files ?? null, bd.skills ?? null, bd.messages ?? null, bd.free_space ?? null, bd.total_estimated ?? null);
103
+ }
104
+ catch (e) {
105
+ import('../utils/logger.js').then(({ logger }) => logger.warn(`[StatsWriter] insertContextBreakdown failed: ${e}`));
106
+ }
107
+ }
108
+ export function insertMessageEvent(evolcoreHome, event) {
109
+ const db = getDb(evolcoreHome);
110
+ if (!db)
111
+ return;
112
+ try {
113
+ db.prepare(`
114
+ INSERT INTO message_events
115
+ (ts, agent_aid, peer_key, direction, msg_type, bytes, encrypted, chatmode)
116
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
117
+ `).run(event.ts, event.agent_aid, event.peer_key, event.direction, event.msg_type ?? null, event.bytes, event.encrypted ? 1 : 0, event.chatmode ?? null);
118
+ }
119
+ catch (e) {
120
+ import('../utils/logger.js').then(({ logger }) => logger.warn(`[StatsWriter] insertMessageEvent failed: ${e}`));
121
+ }
122
+ }
123
+ /** 批量写入大模型调用明细。单事务;失败不影响主流程。 */
124
+ export function insertModelCalls(evolcoreHome, rows) {
125
+ if (!rows.length)
126
+ return;
127
+ const db = getDb(evolcoreHome);
128
+ if (!db)
129
+ return;
130
+ try {
131
+ const stmt = db.prepare(`
132
+ INSERT INTO model_calls
133
+ (ts, task_id, session_id, agent_session_id, agent_aid, peer_key, call_index, model,
134
+ request_id, message_id, input_tokens, output_tokens, cache_creation_tokens,
135
+ cache_read_tokens, context_tokens, max_tokens, auto_compact_tokens, degraded)
136
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
137
+ `);
138
+ db.exec('BEGIN');
139
+ for (const r of rows) {
140
+ stmt.run(r.ts, r.task_id, r.session_id ?? null, r.agent_session_id ?? null, r.agent_aid, r.peer_key, r.call_index, r.model, r.request_id ?? null, r.message_id ?? null, r.input_tokens, r.output_tokens, r.cache_creation_tokens, r.cache_read_tokens, r.context_tokens ?? null, r.max_tokens ?? null, r.auto_compact_tokens ?? null, r.degraded);
141
+ }
142
+ db.exec('COMMIT');
143
+ }
144
+ catch (e) {
145
+ try {
146
+ db.exec('ROLLBACK');
147
+ }
148
+ catch { }
149
+ import('../utils/logger.js').then(({ logger }) => logger.warn(`[StatsWriter] insertModelCalls failed: ${e}`));
150
+ }
151
+ }
@@ -0,0 +1,258 @@
1
+ import { logger } from '../utils/logger.js';
2
+ const ENTRY_TTL_MS = 2 * 60 * 60 * 1000;
3
+ const MAX_RUN_ENTRIES = 2_048;
4
+ const MAX_DISTINCT_ANOMALIES_PER_RUN = 100;
5
+ const entries = new Map();
6
+ /**
7
+ * Returns true when the occurrence is represented in the run audit, either
8
+ * directly, by same-class aggregation, or by the explicit overflow entry.
9
+ */
10
+ export function recordTriggerExecutionAnomaly(runId, anomaly) {
11
+ if (!runId)
12
+ return false;
13
+ const now = Date.now();
14
+ pruneExpired(now);
15
+ let entry = entries.get(runId);
16
+ if (!entry) {
17
+ evictOldestIfFull();
18
+ entry = { anomalies: [], expiresAt: now + ENTRY_TTL_MS };
19
+ }
20
+ else {
21
+ entries.delete(runId);
22
+ entry.expiresAt = now + ENTRY_TTL_MS;
23
+ }
24
+ appendAnomaly(entry, anomaly);
25
+ entries.set(runId, entry);
26
+ return true;
27
+ }
28
+ export function drainTriggerExecutionAnomalies(runId) {
29
+ pruneExpired(Date.now());
30
+ const entry = entries.get(runId);
31
+ if (!entry)
32
+ return [];
33
+ entries.delete(runId);
34
+ return entry.overflow
35
+ ? [...entry.anomalies, entry.overflow]
36
+ : entry.anomalies;
37
+ }
38
+ /** Reconciles overlapping anomaly views of the same execution attempt. */
39
+ export function mergeTriggerExecutionAnomalies(...groups) {
40
+ const classes = new Map();
41
+ let overflow;
42
+ for (const group of groups) {
43
+ const source = accumulateTriggerExecutionAnomalies(group);
44
+ for (const anomaly of source) {
45
+ if (anomaly.code === 'anomaly_overflow') {
46
+ const occurrences = overflowOccurrences(anomaly);
47
+ overflow ??= {
48
+ maxSourceOccurrences: 0,
49
+ firstOccurredAt: anomaly.occurredAt,
50
+ lastOccurredAt: anomalyLastOccurredAt(anomaly),
51
+ };
52
+ overflow.maxSourceOccurrences = Math.max(overflow.maxSourceOccurrences, occurrences);
53
+ overflow.firstOccurredAt = Math.min(overflow.firstOccurredAt, anomaly.occurredAt);
54
+ overflow.lastOccurredAt = Math.max(overflow.lastOccurredAt, anomalyLastOccurredAt(anomaly));
55
+ continue;
56
+ }
57
+ const key = anomalyClassKey(anomaly);
58
+ const occurrences = anomalyOccurrences(anomaly);
59
+ let state = classes.get(key);
60
+ if (!state) {
61
+ state = {
62
+ representative: cloneAggregate(anomaly),
63
+ firstOccurredAt: anomaly.occurredAt,
64
+ lastOccurredAt: anomalyLastOccurredAt(anomaly),
65
+ maxSourceOccurrences: occurrences,
66
+ requestIds: new Set(),
67
+ missingRequestId: false,
68
+ };
69
+ classes.set(key, state);
70
+ }
71
+ state.maxSourceOccurrences = Math.max(state.maxSourceOccurrences, occurrences);
72
+ state.firstOccurredAt = Math.min(state.firstOccurredAt, anomaly.occurredAt);
73
+ state.lastOccurredAt = Math.max(state.lastOccurredAt, anomalyLastOccurredAt(anomaly));
74
+ if (anomaly.requestId)
75
+ state.requestIds.add(anomaly.requestId);
76
+ else
77
+ state.missingRequestId = true;
78
+ }
79
+ }
80
+ const entry = { anomalies: [], expiresAt: 0 };
81
+ for (const state of classes.values()) {
82
+ const occurrences = state.maxSourceOccurrences;
83
+ const anomaly = cloneAggregate(state.representative);
84
+ anomaly.occurredAt = state.firstOccurredAt;
85
+ if (occurrences > 1)
86
+ anomaly.occurrences = occurrences;
87
+ else
88
+ delete anomaly.occurrences;
89
+ if (state.lastOccurredAt > state.firstOccurredAt || occurrences > 1) {
90
+ anomaly.lastOccurredAt = state.lastOccurredAt;
91
+ }
92
+ else {
93
+ delete anomaly.lastOccurredAt;
94
+ }
95
+ if (state.missingRequestId || state.requestIds.size !== 1)
96
+ delete anomaly.requestId;
97
+ else
98
+ anomaly.requestId = state.requestIds.values().next().value;
99
+ appendAnomaly(entry, anomaly);
100
+ }
101
+ if (overflow) {
102
+ entry.overflow = mergeOverlappingOverflow(entry.overflow, {
103
+ code: 'anomaly_overflow',
104
+ severity: 'warning',
105
+ phase: 'execution',
106
+ occurredAt: overflow.firstOccurredAt,
107
+ lastOccurredAt: overflow.lastOccurredAt,
108
+ occurrences: overflow.maxSourceOccurrences,
109
+ overflowCount: overflow.maxSourceOccurrences,
110
+ summary: `execution anomaly audit capacity exceeded (${MAX_DISTINCT_ANOMALIES_PER_RUN} distinct classes retained)`,
111
+ effect: 'audit_truncated',
112
+ });
113
+ }
114
+ return entry.overflow
115
+ ? [...entry.anomalies, entry.overflow]
116
+ : entry.anomalies;
117
+ }
118
+ /** Accumulates anomaly groups known to represent disjoint attempts or phases. */
119
+ export function accumulateTriggerExecutionAnomalies(...groups) {
120
+ const entry = { anomalies: [], expiresAt: 0 };
121
+ for (const group of groups) {
122
+ for (const anomaly of group ?? [])
123
+ appendAnomaly(entry, anomaly);
124
+ }
125
+ return entry.overflow
126
+ ? [...entry.anomalies, entry.overflow]
127
+ : entry.anomalies;
128
+ }
129
+ function appendAnomaly(entry, anomaly) {
130
+ if (anomaly.code === 'anomaly_overflow') {
131
+ entry.overflow = mergeOverflow(entry.overflow, anomaly);
132
+ return;
133
+ }
134
+ const key = anomalyClassKey(anomaly);
135
+ const existing = entry.anomalies.find(candidate => anomalyClassKey(candidate) === key);
136
+ if (existing) {
137
+ mergeSameClass(existing, anomaly);
138
+ return;
139
+ }
140
+ if (entry.anomalies.length < MAX_DISTINCT_ANOMALIES_PER_RUN) {
141
+ entry.anomalies.push(cloneAggregate(anomaly));
142
+ return;
143
+ }
144
+ entry.overflow = mergeOverflow(entry.overflow, anomaly);
145
+ }
146
+ function cloneAggregate(anomaly) {
147
+ const { occurrences: _occurrences, lastOccurredAt: _lastOccurredAt, overflowCount: _overflowCount, ...base } = anomaly;
148
+ const occurrences = anomalyOccurrences(anomaly);
149
+ const firstOccurredAt = anomaly.occurredAt;
150
+ const lastOccurredAt = anomalyLastOccurredAt(anomaly);
151
+ return {
152
+ ...base,
153
+ occurredAt: firstOccurredAt,
154
+ ...(occurrences > 1 ? { occurrences } : {}),
155
+ ...(lastOccurredAt > firstOccurredAt || occurrences > 1 ? { lastOccurredAt } : {}),
156
+ };
157
+ }
158
+ function mergeSameClass(existing, incoming) {
159
+ const existingOccurrences = anomalyOccurrences(existing);
160
+ const incomingOccurrences = anomalyOccurrences(incoming);
161
+ const firstOccurredAt = Math.min(existing.occurredAt, incoming.occurredAt);
162
+ const lastOccurredAt = Math.max(anomalyLastOccurredAt(existing), anomalyLastOccurredAt(incoming));
163
+ existing.occurredAt = firstOccurredAt;
164
+ existing.occurrences = existingOccurrences + incomingOccurrences;
165
+ existing.lastOccurredAt = lastOccurredAt;
166
+ if (existing.requestId !== incoming.requestId)
167
+ delete existing.requestId;
168
+ }
169
+ function mergeOverflow(existing, incoming) {
170
+ const incomingOccurrences = overflowOccurrences(incoming);
171
+ const incomingFirst = incoming.occurredAt;
172
+ const incomingLast = anomalyLastOccurredAt(incoming);
173
+ if (!existing) {
174
+ return {
175
+ code: 'anomaly_overflow',
176
+ severity: 'warning',
177
+ phase: 'execution',
178
+ occurredAt: incomingFirst,
179
+ occurrences: incomingOccurrences,
180
+ overflowCount: incomingOccurrences,
181
+ ...(incomingLast > incomingFirst ? { lastOccurredAt: incomingLast } : {}),
182
+ summary: `execution anomaly audit capacity exceeded (${MAX_DISTINCT_ANOMALIES_PER_RUN} distinct classes retained)`,
183
+ effect: 'audit_truncated',
184
+ };
185
+ }
186
+ const existingOccurrences = overflowOccurrences(existing);
187
+ const firstOccurredAt = Math.min(existing.occurredAt, incomingFirst);
188
+ const lastOccurredAt = Math.max(anomalyLastOccurredAt(existing), incomingLast);
189
+ return {
190
+ ...existing,
191
+ occurredAt: firstOccurredAt,
192
+ lastOccurredAt,
193
+ occurrences: existingOccurrences + incomingOccurrences,
194
+ overflowCount: existingOccurrences + incomingOccurrences,
195
+ };
196
+ }
197
+ function mergeOverlappingOverflow(existing, incoming) {
198
+ if (!existing)
199
+ return mergeOverflow(undefined, incoming);
200
+ const occurrences = Math.max(overflowOccurrences(existing), overflowOccurrences(incoming));
201
+ const occurredAt = Math.min(existing.occurredAt, incoming.occurredAt);
202
+ const lastOccurredAt = Math.max(anomalyLastOccurredAt(existing), anomalyLastOccurredAt(incoming));
203
+ return {
204
+ ...existing,
205
+ occurredAt,
206
+ lastOccurredAt,
207
+ occurrences,
208
+ overflowCount: occurrences,
209
+ };
210
+ }
211
+ function overflowOccurrences(anomaly) {
212
+ return Number.isSafeInteger(anomaly.overflowCount) && (anomaly.overflowCount ?? 0) > 0
213
+ ? anomaly.overflowCount
214
+ : anomalyOccurrences(anomaly);
215
+ }
216
+ function anomalyOccurrences(anomaly) {
217
+ return Number.isSafeInteger(anomaly.occurrences) && (anomaly.occurrences ?? 0) > 0
218
+ ? anomaly.occurrences
219
+ : 1;
220
+ }
221
+ function anomalyLastOccurredAt(anomaly) {
222
+ return typeof anomaly.lastOccurredAt === 'number'
223
+ ? Math.max(anomaly.occurredAt, anomaly.lastOccurredAt)
224
+ : anomaly.occurredAt;
225
+ }
226
+ function pruneExpired(now) {
227
+ for (const [runId, entry] of entries) {
228
+ if (entry.expiresAt > now)
229
+ continue;
230
+ logger.warn(`[Trigger] undrained execution anomalies expired: run=${runId} occurrences=${entryOccurrences(entry)}`);
231
+ entries.delete(runId);
232
+ }
233
+ }
234
+ function evictOldestIfFull() {
235
+ while (entries.size >= MAX_RUN_ENTRIES) {
236
+ const oldest = entries.keys().next().value;
237
+ if (!oldest)
238
+ return;
239
+ const entry = entries.get(oldest);
240
+ logger.warn(`[Trigger] execution anomaly store evicted oldest run: run=${oldest} occurrences=${entry ? entryOccurrences(entry) : 0}`);
241
+ entries.delete(oldest);
242
+ }
243
+ }
244
+ function entryOccurrences(entry) {
245
+ return entry.anomalies.reduce((sum, anomaly) => sum + anomalyOccurrences(anomaly), 0)
246
+ + (entry.overflow ? overflowOccurrences(entry.overflow) : 0);
247
+ }
248
+ function anomalyClassKey(anomaly) {
249
+ return JSON.stringify([
250
+ anomaly.code,
251
+ anomaly.severity,
252
+ anomaly.phase,
253
+ anomaly.toolName,
254
+ anomaly.policy,
255
+ anomaly.summary,
256
+ anomaly.effect,
257
+ ]);
258
+ }