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,763 @@
1
+ /**
2
+ * query.ts — 聚合查询(只读,供 CLI 和 ecweb 共用)。
3
+ * 支持跨年查询:自动附加归档库。
4
+ */
5
+ import { openReadonlyDb, getDbPath, listArchivePaths } from './db.js';
6
+ import { calcCost } from './billing.js'; // 保留用于尚未迁移的函数
7
+ const GRAN_FMT = {
8
+ hour: '%Y-%m-%d %H:00',
9
+ day: '%Y-%m-%d',
10
+ week: '%Y-W%W',
11
+ month: '%Y-%m',
12
+ };
13
+ // 非时间维度:按字段分组
14
+ const GRAN_GROUP_COL = {
15
+ model: 'model',
16
+ peer: 'peer_key',
17
+ agent: 'agent_aid',
18
+ };
19
+ function _buildWhere(f) {
20
+ const conds = [];
21
+ const params = [];
22
+ if (f.from_ts) {
23
+ conds.push('ts >= ?');
24
+ params.push(f.from_ts);
25
+ }
26
+ if (f.to_ts) {
27
+ conds.push('ts < ?');
28
+ params.push(f.to_ts);
29
+ }
30
+ if (f.agent_aid) {
31
+ conds.push('agent_aid = ?');
32
+ params.push(f.agent_aid);
33
+ }
34
+ if (f.peer_key) {
35
+ conds.push('peer_key = ?');
36
+ params.push(f.peer_key);
37
+ }
38
+ if (f.model) {
39
+ conds.push('model = ?');
40
+ params.push(f.model);
41
+ }
42
+ if (f.session_id) {
43
+ conds.push('session_id = ?');
44
+ params.push(f.session_id);
45
+ }
46
+ if (f.billing_fn) {
47
+ conds.push('billing_fn = ?');
48
+ params.push(f.billing_fn);
49
+ }
50
+ return { clause: conds.length ? 'WHERE ' + conds.join(' AND ') : '', params };
51
+ }
52
+ /** 查询哪些归档库需要参与(给定时间范围)。 */
53
+ function _relevantDbs(evolcoreHome, f) {
54
+ const dbPaths = [getDbPath(evolcoreHome)];
55
+ if (!f.from_ts)
56
+ return dbPaths; // 无时间下界,只查主库(最近数据)
57
+ const fromYear = new Date(f.from_ts).getUTCFullYear();
58
+ const toYear = f.to_ts ? new Date(f.to_ts).getUTCFullYear() : new Date().getUTCFullYear();
59
+ for (const { year, path: p } of listArchivePaths(evolcoreHome)) {
60
+ if (year >= fromYear && year <= toYear)
61
+ dbPaths.push(p);
62
+ }
63
+ return dbPaths;
64
+ }
65
+ function _sumRows(rows) {
66
+ if (!rows.length)
67
+ return null;
68
+ const sum = { ...rows[0] };
69
+ for (let i = 1; i < rows.length; i++) {
70
+ for (const k of Object.keys(rows[i])) {
71
+ if (k === 'period')
72
+ continue;
73
+ sum[k] = (sum[k] ?? 0) + (rows[i][k] ?? 0);
74
+ }
75
+ }
76
+ return sum;
77
+ }
78
+ /**
79
+ * 聚合统计(按粒度分组)。
80
+ * hour 粒度仍扫明细 usage_events(只查近期,本就快,且明细才有小时粒度)。
81
+ * 其余粒度(day/week/month/model/peer/agent)改读预聚合表 usage_daily:
82
+ * 工作量从"全表明细"降到"天×维度"小表。rollup 永留主库,无需跨归档库 union。
83
+ * 成本仍按 (model, billing_fn) 分组现算 calcCost,口径不变。
84
+ */
85
+ export function queryAggregated(evolcoreHome, granularity, filter) {
86
+ if (granularity === 'hour')
87
+ return _queryAggregatedEvents(evolcoreHome, granularity, filter);
88
+ return _queryAggregatedDaily(evolcoreHome, granularity, filter);
89
+ }
90
+ // rollup 时间粒度的 period 表达式(day 列是 'YYYY-MM-DD' 字符串,strftime 可直接解析)。
91
+ const DAILY_PERIOD_EXPR = {
92
+ day: 'day',
93
+ week: `strftime('%Y-W%W', day)`,
94
+ month: `substr(day,1,7)`,
95
+ };
96
+ /** 针对 usage_daily 构造 WHERE:时间过滤转成对 day 列的字符串比较,维度过滤直接相等。 */
97
+ function _buildDailyWhere(f) {
98
+ const conds = [];
99
+ const params = [];
100
+ // from_ts/to_ts(ms,含 to_ts 排他)转成 localtime 日期串,与 day 列同口径比较。
101
+ if (f.from_ts) {
102
+ conds.push(`day >= strftime('%Y-%m-%d', ?/1000, 'unixepoch', 'localtime')`);
103
+ params.push(f.from_ts);
104
+ }
105
+ if (f.to_ts) {
106
+ conds.push(`day < strftime('%Y-%m-%d', ?/1000, 'unixepoch', 'localtime')`);
107
+ params.push(f.to_ts);
108
+ }
109
+ if (f.agent_aid) {
110
+ conds.push('agent_aid = ?');
111
+ params.push(f.agent_aid);
112
+ }
113
+ if (f.peer_key) {
114
+ conds.push('peer_key = ?');
115
+ params.push(f.peer_key);
116
+ }
117
+ if (f.model) {
118
+ conds.push('model = ?');
119
+ params.push(f.model);
120
+ }
121
+ if (f.session_id) {
122
+ conds.push('session_id = ?');
123
+ params.push(f.session_id);
124
+ }
125
+ if (f.billing_fn) {
126
+ conds.push('billing_fn = ?');
127
+ params.push(f.billing_fn);
128
+ }
129
+ return { clause: conds.length ? 'WHERE ' + conds.join(' AND ') : '', params };
130
+ }
131
+ /** 走预聚合表 usage_daily 的聚合(day/week/month/model/peer/agent)。 */
132
+ function _queryAggregatedDaily(evolcoreHome, granularity, filter) {
133
+ const { clause, params } = _buildDailyWhere(filter);
134
+ const groupCol = GRAN_GROUP_COL[granularity];
135
+ const periodExpr = groupCol || DAILY_PERIOD_EXPR[granularity] || 'day';
136
+ const db = openReadonlyDb(getDbPath(evolcoreHome));
137
+ if (!db)
138
+ return [];
139
+ try {
140
+ const sql = `
141
+ SELECT
142
+ ${periodExpr} AS period,
143
+ SUM(input_tokens) AS input_tokens,
144
+ SUM(output_tokens) AS output_tokens,
145
+ SUM(cache_creation_tokens) AS cache_creation_tokens,
146
+ SUM(cache_read_tokens) AS cache_read_tokens,
147
+ SUM(cache_hit_tokens) AS cache_hit_tokens,
148
+ SUM(cache_miss_tokens) AS cache_miss_tokens,
149
+ SUM(image_tokens) AS image_tokens,
150
+ SUM(total_context_tokens) AS total_context_tokens,
151
+ SUM(turns) AS turns,
152
+ SUM(calls) AS call_count,
153
+ COALESCE(SUM(cost_official_usd), 0) AS cost_official_usd,
154
+ COALESCE(SUM(cost_official_cny), 0) AS cost_official_cny,
155
+ COALESCE(SUM(cost_gateway_usd), 0) AS cost_gateway_usd,
156
+ COALESCE(SUM(cost_gateway_cny), 0) AS cost_gateway_cny
157
+ FROM usage_daily ${clause}
158
+ GROUP BY ${periodExpr}
159
+ `;
160
+ const rows = db.prepare(sql).all(...params);
161
+ if (groupCol) {
162
+ rows.sort((a, b) => ((b.input_tokens ?? 0) + (b.output_tokens ?? 0)) - ((a.input_tokens ?? 0) + (a.output_tokens ?? 0)));
163
+ }
164
+ else {
165
+ rows.sort((a, b) => a.period.localeCompare(b.period));
166
+ }
167
+ return rows.map(r => _enrichRow(evolcoreHome, r));
168
+ }
169
+ finally {
170
+ db.close();
171
+ }
172
+ }
173
+ /**
174
+ * 走明细 usage_events 的聚合(仅 hour 粒度)。
175
+ * 跨年时合并多个 DB 的结果,再按 period 聚合。
176
+ */
177
+ function _queryAggregatedEvents(evolcoreHome, granularity, filter) {
178
+ const { clause, params } = _buildWhere(filter);
179
+ const groupCol = GRAN_GROUP_COL[granularity];
180
+ let sql;
181
+ if (groupCol) {
182
+ // 非时间维度:按字段分组
183
+ sql = `
184
+ SELECT
185
+ ${groupCol} AS period,
186
+ SUM(input_tokens) AS input_tokens,
187
+ SUM(output_tokens) AS output_tokens,
188
+ SUM(cache_creation_tokens) AS cache_creation_tokens,
189
+ SUM(cache_read_tokens) AS cache_read_tokens,
190
+ SUM(COALESCE(cache_hit_tokens,0)) AS cache_hit_tokens,
191
+ SUM(COALESCE(cache_miss_tokens,0)) AS cache_miss_tokens,
192
+ SUM(COALESCE(image_tokens,0)) AS image_tokens,
193
+ SUM(COALESCE(total_context_tokens,0)) AS total_context_tokens,
194
+ SUM(turns) AS turns,
195
+ COUNT(*) AS call_count
196
+ FROM usage_events ${clause}
197
+ GROUP BY ${groupCol} ORDER BY (input_tokens + output_tokens) DESC
198
+ `;
199
+ }
200
+ else {
201
+ // 时间维度:strftime 分组
202
+ const fmt = GRAN_FMT[granularity] || GRAN_FMT.day;
203
+ sql = `
204
+ SELECT
205
+ strftime('${fmt}', ts/1000, 'unixepoch', 'localtime') AS period,
206
+ SUM(input_tokens) AS input_tokens,
207
+ SUM(output_tokens) AS output_tokens,
208
+ SUM(cache_creation_tokens) AS cache_creation_tokens,
209
+ SUM(cache_read_tokens) AS cache_read_tokens,
210
+ SUM(COALESCE(cache_hit_tokens,0)) AS cache_hit_tokens,
211
+ SUM(COALESCE(cache_miss_tokens,0)) AS cache_miss_tokens,
212
+ SUM(COALESCE(image_tokens,0)) AS image_tokens,
213
+ SUM(COALESCE(total_context_tokens,0)) AS total_context_tokens,
214
+ SUM(turns) AS turns,
215
+ COUNT(*) AS call_count,
216
+ COALESCE(SUM(cost_official_usd), 0) AS cost_official_usd,
217
+ COALESCE(SUM(cost_official_cny), 0) AS cost_official_cny,
218
+ COALESCE(SUM(cost_gateway_usd), 0) AS cost_gateway_usd,
219
+ COALESCE(SUM(cost_gateway_cny), 0) AS cost_gateway_cny
220
+ FROM usage_events ${clause}
221
+ GROUP BY period ORDER BY period
222
+ `;
223
+ }
224
+ // 收集所有相关 DB 的原始行,再按 period 合并
225
+ const periodMap = new Map();
226
+ for (const dbPath of _relevantDbs(evolcoreHome, filter)) {
227
+ const db = openReadonlyDb(dbPath);
228
+ if (!db)
229
+ continue;
230
+ try {
231
+ const rows = db.prepare(sql).all(...params);
232
+ for (const r of rows) {
233
+ const existing = periodMap.get(r.period);
234
+ periodMap.set(r.period, existing ? _sumRows([existing, r]) : r);
235
+ }
236
+ }
237
+ finally {
238
+ db.close();
239
+ }
240
+ }
241
+ const sorted = Array.from(periodMap.entries());
242
+ if (groupCol) {
243
+ // 非时间维度按 total tokens 降序
244
+ sorted.sort((a, b) => ((b[1].input_tokens ?? 0) + (b[1].output_tokens ?? 0)) - ((a[1].input_tokens ?? 0) + (a[1].output_tokens ?? 0)));
245
+ }
246
+ else {
247
+ sorted.sort((a, b) => a[0].localeCompare(b[0]));
248
+ }
249
+ return sorted.map(([, r]) => _enrichRow(evolcoreHome, r));
250
+ }
251
+ /** 查今日概览(单行汇总)。 */
252
+ export function queryTodaySummary(evolcoreHome, agentAid) {
253
+ const now = new Date();
254
+ const from_ts = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());
255
+ const rows = queryAggregated(evolcoreHome, 'day', { from_ts, agent_aid: agentAid });
256
+ return rows[0] ?? null;
257
+ }
258
+ /** 查单个会话的每轮明细。 */
259
+ export function querySessionTurns(evolcoreHome, sessionId) {
260
+ const { clause, params } = _buildWhere({ session_id: sessionId });
261
+ const sql = `SELECT * FROM usage_events ${clause} ORDER BY ts`;
262
+ const result = [];
263
+ for (const dbPath of _relevantDbs(evolcoreHome, {})) {
264
+ const db = openReadonlyDb(dbPath);
265
+ if (!db)
266
+ continue;
267
+ try {
268
+ const rows = db.prepare(sql).all(...params);
269
+ for (const r of rows) {
270
+ result.push({
271
+ ...r,
272
+ cost_official_usd: r.cost_official_usd ?? 0,
273
+ cost_official_cny: r.cost_official_cny ?? 0,
274
+ cost_gateway_usd: r.cost_gateway_usd ?? 0,
275
+ cost_gateway_cny: r.cost_gateway_cny ?? 0,
276
+ usd: r.cost_gateway_usd ?? 0,
277
+ cny: r.cost_gateway_cny ?? 0
278
+ });
279
+ }
280
+ }
281
+ finally {
282
+ db.close();
283
+ }
284
+ }
285
+ return result.sort((a, b) => a.ts - b.ts);
286
+ }
287
+ /**
288
+ * 单个 session 的累计汇总,单行。直接 SUM 已落库的 official/gateway 成本列,
289
+ * 不再逐行 calcCost(避免重复计算 + 全表扫描)。跨归档库累加。
290
+ */
291
+ export function querySessionSummary(evolcoreHome, sessionId) {
292
+ const empty = {
293
+ input_tokens: 0, output_tokens: 0, cache_creation_tokens: 0, cache_read_tokens: 0,
294
+ total_tokens: 0, calls: 0,
295
+ cost_official_usd: 0, cost_official_cny: 0, cost_gateway_usd: 0, cost_gateway_cny: 0,
296
+ usd: 0, cny: 0,
297
+ };
298
+ const sql = `
299
+ SELECT
300
+ COALESCE(SUM(input_tokens), 0) AS input_tokens,
301
+ COALESCE(SUM(output_tokens), 0) AS output_tokens,
302
+ COALESCE(SUM(cache_creation_tokens), 0) AS cache_creation_tokens,
303
+ COALESCE(SUM(cache_read_tokens), 0) AS cache_read_tokens,
304
+ COUNT(*) AS calls,
305
+ COALESCE(SUM(cost_official_usd), 0) AS cost_official_usd,
306
+ COALESCE(SUM(cost_official_cny), 0) AS cost_official_cny,
307
+ COALESCE(SUM(cost_gateway_usd), 0) AS cost_gateway_usd,
308
+ COALESCE(SUM(cost_gateway_cny), 0) AS cost_gateway_cny
309
+ FROM usage_events WHERE session_id = ?`;
310
+ const acc = { ...empty };
311
+ for (const dbPath of _relevantDbs(evolcoreHome, {})) {
312
+ const db = openReadonlyDb(dbPath);
313
+ if (!db)
314
+ continue;
315
+ try {
316
+ const r = db.prepare(sql).get(sessionId);
317
+ if (!r)
318
+ continue;
319
+ acc.input_tokens += r.input_tokens ?? 0;
320
+ acc.output_tokens += r.output_tokens ?? 0;
321
+ acc.cache_creation_tokens += r.cache_creation_tokens ?? 0;
322
+ acc.cache_read_tokens += r.cache_read_tokens ?? 0;
323
+ acc.calls += r.calls ?? 0;
324
+ acc.cost_official_usd += r.cost_official_usd ?? 0;
325
+ acc.cost_official_cny += r.cost_official_cny ?? 0;
326
+ acc.cost_gateway_usd += r.cost_gateway_usd ?? 0;
327
+ acc.cost_gateway_cny += r.cost_gateway_cny ?? 0;
328
+ }
329
+ finally {
330
+ db.close();
331
+ }
332
+ }
333
+ acc.total_tokens = acc.input_tokens + acc.output_tokens + acc.cache_read_tokens + acc.cache_creation_tokens;
334
+ acc.usd = acc.cost_gateway_usd;
335
+ acc.cny = acc.cost_gateway_cny;
336
+ return acc;
337
+ }
338
+ /** 查会话 context_breakdown 细目(每轮各段 token)。 */
339
+ export function queryContextBreakdown(evolcoreHome, sessionId) {
340
+ const sql = `SELECT * FROM context_breakdown WHERE session_id = ? ORDER BY ts`;
341
+ const result = [];
342
+ for (const dbPath of _relevantDbs(evolcoreHome, {})) {
343
+ const db = openReadonlyDb(dbPath);
344
+ if (!db)
345
+ continue;
346
+ try {
347
+ result.push(...db.prepare(sql).all(sessionId));
348
+ }
349
+ finally {
350
+ db.close();
351
+ }
352
+ }
353
+ return result.sort((a, b) => a.ts - b.ts);
354
+ }
355
+ /** Top-N 对端(按 token 总量排序)。读预聚合表 usage_daily。 */
356
+ export function queryTopPeers(evolcoreHome, filter, limit = 10) {
357
+ const { clause, params } = _buildDailyWhere(filter);
358
+ const sql = `
359
+ SELECT peer_key, SUM(input_tokens+output_tokens) AS total_tokens,
360
+ SUM(calls) AS call_count
361
+ FROM usage_daily ${clause}
362
+ GROUP BY peer_key ORDER BY total_tokens DESC LIMIT ${limit}
363
+ `;
364
+ const db = openReadonlyDb(getDbPath(evolcoreHome));
365
+ if (!db)
366
+ return [];
367
+ try {
368
+ return db.prepare(sql).all(...params).map(r => ({ ...r }));
369
+ }
370
+ finally {
371
+ db.close();
372
+ }
373
+ }
374
+ /** Top-N 模型(按 token 总量排序)。读预聚合表 usage_daily。 */
375
+ export function queryTopModels(evolcoreHome, filter, limit = 10) {
376
+ const { clause, params } = _buildDailyWhere(filter);
377
+ const sql = `
378
+ SELECT model, SUM(input_tokens+output_tokens) AS total_tokens,
379
+ SUM(calls) AS call_count
380
+ FROM usage_daily ${clause}
381
+ GROUP BY model ORDER BY total_tokens DESC LIMIT ${limit}
382
+ `;
383
+ const db = openReadonlyDb(getDbPath(evolcoreHome));
384
+ if (!db)
385
+ return [];
386
+ try {
387
+ return db.prepare(sql).all(...params).map(r => ({ ...r }));
388
+ }
389
+ finally {
390
+ db.close();
391
+ }
392
+ }
393
+ // ── 私聊/群聊列表 + 汇总 + 对端按天明细(owner 前端用)────────────────────────
394
+ /** 解析 peer_key 末段为裸 peer_id(对端 AID / 群 ID)。
395
+ * peer_key = aun#<selfAID>#main#<encodeURIComponent(peer)>,取第 4 段起 decode。 */
396
+ function _parsePeerId(peerKey) {
397
+ const parts = peerKey.split('#');
398
+ if (parts.length < 4) {
399
+ // 兼容两段式 aun#peer:取末段
400
+ try {
401
+ return decodeURIComponent(parts[parts.length - 1] ?? '');
402
+ }
403
+ catch {
404
+ return parts[parts.length - 1] ?? '';
405
+ }
406
+ }
407
+ try {
408
+ return decodeURIComponent(parts.slice(3).join('#'));
409
+ }
410
+ catch {
411
+ return parts.slice(3).join('#');
412
+ }
413
+ }
414
+ /** 对一组按 (model,billing_fn) 分组的行调用 calcCost 并累加,返回 {usd,cny}。 */
415
+ function _accumCost(evolcoreHome, rows) {
416
+ let usd = 0, cny = 0;
417
+ for (const r of rows) {
418
+ const c = calcCost(evolcoreHome, {
419
+ model: r.model || 'unknown',
420
+ billing_fn: r.billing_fn || 'per_token_v1',
421
+ ts: Date.now(),
422
+ input_tokens: r.input_tokens ?? 0,
423
+ output_tokens: r.output_tokens ?? 0,
424
+ cache_creation_tokens: r.cache_creation_tokens ?? 0,
425
+ cache_read_tokens: r.cache_read_tokens ?? 0,
426
+ cache_hit_tokens: r.cache_hit_tokens ?? 0,
427
+ cache_miss_tokens: r.cache_miss_tokens ?? 0,
428
+ image_tokens: r.image_tokens ?? 0,
429
+ total_context_tokens: r.total_context_tokens ?? 0,
430
+ });
431
+ usd += c.usd ?? 0;
432
+ cny += c.cny ?? 0;
433
+ }
434
+ return { usd, cny };
435
+ }
436
+ /** 私聊(peer_type='private')或群聊('group')列表,每项带累计汇总。读 usage_daily。 */
437
+ export function queryPeerList(evolcoreHome, opts) {
438
+ const limit = opts.limit ?? 50;
439
+ const { clause, params } = _buildDailyWhere({
440
+ from_ts: opts.from_ts, to_ts: opts.to_ts, agent_aid: opts.agent_aid,
441
+ });
442
+ // peer_type 过滤拼到 WHERE 上(_buildDailyWhere 不含该字段)。
443
+ const peerCond = clause ? `${clause} AND peer_type = ?` : 'WHERE peer_type = ?';
444
+ const listParams = [...params, opts.peer_type];
445
+ const db = openReadonlyDb(getDbPath(evolcoreHome));
446
+ if (!db)
447
+ return [];
448
+ try {
449
+ const listSql = `
450
+ SELECT peer_key,
451
+ SUM(input_tokens) AS input_tokens, SUM(output_tokens) AS output_tokens,
452
+ SUM(cache_creation_tokens) AS cache_creation_tokens,
453
+ SUM(cache_read_tokens) AS cache_read_tokens,
454
+ SUM(input_tokens+output_tokens+cache_read_tokens+cache_creation_tokens) AS total_tokens,
455
+ SUM(calls) AS calls,
456
+ COUNT(DISTINCT session_id) AS session_count,
457
+ MIN(day) AS first_day, MAX(day) AS last_day,
458
+ MAX(peer_type) AS peer_type
459
+ FROM usage_daily ${peerCond}
460
+ GROUP BY peer_key ORDER BY total_tokens DESC LIMIT ${limit}
461
+ `;
462
+ const rows = db.prepare(listSql).all(...listParams);
463
+ // 每个 peer 的成本:按 (peer_key, model, billing_fn) 分组算 calcCost 累加。
464
+ const costSql = `
465
+ SELECT peer_key, model, COALESCE(billing_fn,'') AS billing_fn,
466
+ SUM(input_tokens) AS input_tokens, SUM(output_tokens) AS output_tokens,
467
+ SUM(cache_creation_tokens) AS cache_creation_tokens, SUM(cache_read_tokens) AS cache_read_tokens,
468
+ SUM(cache_hit_tokens) AS cache_hit_tokens, SUM(cache_miss_tokens) AS cache_miss_tokens,
469
+ SUM(image_tokens) AS image_tokens, SUM(total_context_tokens) AS total_context_tokens
470
+ FROM usage_daily ${peerCond}
471
+ GROUP BY peer_key, model, billing_fn
472
+ `;
473
+ const costByPeer = new Map();
474
+ for (const r of db.prepare(costSql).all(...listParams)) {
475
+ const arr = costByPeer.get(r.peer_key) ?? [];
476
+ arr.push(r);
477
+ costByPeer.set(r.peer_key, arr);
478
+ }
479
+ return rows.map(r => {
480
+ const cost = _accumCost(evolcoreHome, costByPeer.get(r.peer_key) ?? []);
481
+ return {
482
+ peer_key: r.peer_key,
483
+ peer_id: _parsePeerId(r.peer_key),
484
+ peer_type: r.peer_type ?? opts.peer_type,
485
+ input_tokens: r.input_tokens ?? 0,
486
+ output_tokens: r.output_tokens ?? 0,
487
+ cache_creation_tokens: r.cache_creation_tokens ?? 0,
488
+ cache_read_tokens: r.cache_read_tokens ?? 0,
489
+ total_tokens: r.total_tokens ?? 0,
490
+ calls: r.calls ?? 0,
491
+ session_count: r.session_count ?? 0,
492
+ first_day: r.first_day ?? '',
493
+ last_day: r.last_day ?? '',
494
+ cost_official_usd: cost.usd,
495
+ cost_official_cny: cost.cny,
496
+ cost_gateway_usd: cost.usd,
497
+ cost_gateway_cny: cost.cny,
498
+ usd: cost.usd,
499
+ cny: cost.cny,
500
+ };
501
+ });
502
+ }
503
+ finally {
504
+ db.close();
505
+ }
506
+ }
507
+ /** 指定时间范围(可选对端)的总消耗汇总,单行。读 usage_daily。 */
508
+ export function querySummary(evolcoreHome, opts) {
509
+ const { clause, params } = _buildDailyWhere({
510
+ from_ts: opts.from_ts, to_ts: opts.to_ts, agent_aid: opts.agent_aid, peer_key: opts.peer_key,
511
+ });
512
+ const empty = {
513
+ input_tokens: 0, output_tokens: 0, cache_creation_tokens: 0, cache_read_tokens: 0,
514
+ total_tokens: 0, calls: 0,
515
+ cost_official_usd: 0, cost_official_cny: 0, cost_gateway_usd: 0, cost_gateway_cny: 0,
516
+ usd: 0, cny: 0,
517
+ };
518
+ const db = openReadonlyDb(getDbPath(evolcoreHome));
519
+ if (!db)
520
+ return empty;
521
+ try {
522
+ const row = db.prepare(`
523
+ SELECT
524
+ SUM(input_tokens) AS input_tokens, SUM(output_tokens) AS output_tokens,
525
+ SUM(cache_creation_tokens) AS cache_creation_tokens,
526
+ SUM(cache_read_tokens) AS cache_read_tokens,
527
+ SUM(calls) AS calls,
528
+ COALESCE(SUM(cost_official_usd), 0) AS cost_official_usd,
529
+ COALESCE(SUM(cost_official_cny), 0) AS cost_official_cny,
530
+ COALESCE(SUM(cost_gateway_usd), 0) AS cost_gateway_usd,
531
+ COALESCE(SUM(cost_gateway_cny), 0) AS cost_gateway_cny
532
+ FROM usage_daily ${clause}
533
+ `).get(...params);
534
+ if (!row || row.calls == null)
535
+ return empty;
536
+ const inTok = row.input_tokens ?? 0;
537
+ const cacheRead = row.cache_read_tokens ?? 0;
538
+ return {
539
+ input_tokens: inTok,
540
+ output_tokens: row.output_tokens ?? 0,
541
+ cache_creation_tokens: row.cache_creation_tokens ?? 0,
542
+ cache_read_tokens: cacheRead,
543
+ total_tokens: inTok + (row.output_tokens ?? 0) + cacheRead + (row.cache_creation_tokens ?? 0),
544
+ calls: row.calls ?? 0,
545
+ cost_official_usd: row.cost_official_usd ?? 0,
546
+ cost_official_cny: row.cost_official_cny ?? 0,
547
+ cost_gateway_usd: row.cost_gateway_usd ?? 0,
548
+ cost_gateway_cny: row.cost_gateway_cny ?? 0,
549
+ usd: row.cost_gateway_usd ?? 0,
550
+ cny: row.cost_gateway_cny ?? 0,
551
+ };
552
+ }
553
+ finally {
554
+ db.close();
555
+ }
556
+ }
557
+ /** 指定对端(peer_key 或 peer_id),按天返回消耗明细。读 usage_daily。 */
558
+ export function queryPeerDaily(evolcoreHome, opts) {
559
+ const { clause, params } = _buildDailyWhere({
560
+ from_ts: opts.from_ts, to_ts: opts.to_ts, agent_aid: opts.agent_aid,
561
+ peer_key: opts.peer_key,
562
+ });
563
+ // peer_id:按末段 LIKE 收窄(与 peer_key 精确匹配二选一)。
564
+ let where = clause;
565
+ const qParams = [...params];
566
+ if (!opts.peer_key && opts.peer_id) {
567
+ where = where ? `${where} AND peer_key LIKE ?` : 'WHERE peer_key LIKE ?';
568
+ qParams.push(`aun#%#main#${encodeURIComponent(opts.peer_id)}`);
569
+ }
570
+ const db = openReadonlyDb(getDbPath(evolcoreHome));
571
+ if (!db)
572
+ return [];
573
+ try {
574
+ const sql = `
575
+ SELECT day AS period,
576
+ SUM(input_tokens) AS input_tokens, SUM(output_tokens) AS output_tokens,
577
+ SUM(cache_creation_tokens) AS cache_creation_tokens,
578
+ SUM(cache_read_tokens) AS cache_read_tokens,
579
+ SUM(cache_hit_tokens) AS cache_hit_tokens, SUM(cache_miss_tokens) AS cache_miss_tokens,
580
+ SUM(image_tokens) AS image_tokens, SUM(total_context_tokens) AS total_context_tokens,
581
+ SUM(turns) AS turns, SUM(calls) AS call_count
582
+ FROM usage_daily ${where}
583
+ GROUP BY day ORDER BY day
584
+ `;
585
+ const periodMap = new Map();
586
+ for (const r of db.prepare(sql).all(...qParams))
587
+ periodMap.set(r.period, r);
588
+ const costSql = `
589
+ SELECT day AS period, model, COALESCE(billing_fn,'') AS billing_fn,
590
+ SUM(input_tokens) AS input_tokens, SUM(output_tokens) AS output_tokens,
591
+ SUM(cache_creation_tokens) AS cache_creation_tokens, SUM(cache_read_tokens) AS cache_read_tokens,
592
+ SUM(cache_hit_tokens) AS cache_hit_tokens, SUM(cache_miss_tokens) AS cache_miss_tokens,
593
+ SUM(image_tokens) AS image_tokens, SUM(total_context_tokens) AS total_context_tokens
594
+ FROM usage_daily ${where}
595
+ GROUP BY day, model, billing_fn
596
+ `;
597
+ const costMap = new Map();
598
+ const byPeriod = new Map();
599
+ for (const r of db.prepare(costSql).all(...qParams)) {
600
+ const arr = byPeriod.get(r.period) ?? [];
601
+ arr.push(r);
602
+ byPeriod.set(r.period, arr);
603
+ }
604
+ for (const [period, rows] of byPeriod) {
605
+ const cost = _accumCost(evolcoreHome, rows);
606
+ const existing = periodMap.get(period);
607
+ if (existing) {
608
+ existing.cost_official_usd = cost.usd;
609
+ existing.cost_official_cny = cost.cny;
610
+ existing.cost_gateway_usd = cost.usd;
611
+ existing.cost_gateway_cny = cost.cny;
612
+ }
613
+ }
614
+ return Array.from(periodMap.entries())
615
+ .sort((a, b) => a[0].localeCompare(b[0]))
616
+ .map(([, r]) => _enrichRow(evolcoreHome, r));
617
+ }
618
+ finally {
619
+ db.close();
620
+ }
621
+ }
622
+ /** 查一个 task 的所有大模型调用明细,按 call_index 排序。 */
623
+ export function queryTaskModelCalls(evolcoreHome, taskId) {
624
+ const db = openReadonlyDb(getDbPath(evolcoreHome));
625
+ if (!db)
626
+ return [];
627
+ try {
628
+ return db.prepare(`SELECT * FROM model_calls WHERE task_id = ? ORDER BY call_index`).all(taskId);
629
+ }
630
+ finally {
631
+ db.close();
632
+ }
633
+ }
634
+ /** 查一个 evolcore session 的所有大模型调用明细,按时间 + call_index 排序。 */
635
+ export function querySessionModelCalls(evolcoreHome, sessionId) {
636
+ const db = openReadonlyDb(getDbPath(evolcoreHome));
637
+ if (!db)
638
+ return [];
639
+ try {
640
+ return db.prepare(`SELECT * FROM model_calls WHERE session_id = ? ORDER BY ts, call_index`).all(sessionId);
641
+ }
642
+ finally {
643
+ db.close();
644
+ }
645
+ }
646
+ function _enrichRow(evolcoreHome, r) {
647
+ const totalIn = (r.input_tokens ?? 0) + (r.cache_read_tokens ?? 0);
648
+ const cacheHitRate = totalIn > 0 ? (r.cache_read_tokens ?? 0) / totalIn : 0;
649
+ return {
650
+ period: r.period,
651
+ input_tokens: r.input_tokens ?? 0,
652
+ output_tokens: r.output_tokens ?? 0,
653
+ cache_creation_tokens: r.cache_creation_tokens ?? 0,
654
+ cache_read_tokens: r.cache_read_tokens ?? 0,
655
+ cache_hit_tokens: r.cache_hit_tokens ?? 0,
656
+ cache_miss_tokens: r.cache_miss_tokens ?? 0,
657
+ image_tokens: r.image_tokens ?? 0,
658
+ total_context_tokens: r.total_context_tokens ?? 0,
659
+ turns: r.turns ?? 0,
660
+ call_count: r.call_count ?? 0,
661
+ cost_official_usd: r.cost_official_usd ?? 0,
662
+ cost_official_cny: r.cost_official_cny ?? 0,
663
+ cost_gateway_usd: r.cost_gateway_usd ?? 0,
664
+ cost_gateway_cny: r.cost_gateway_cny ?? 0,
665
+ usd: r.cost_gateway_usd ?? 0, // 向后兼容
666
+ cny: r.cost_gateway_cny ?? 0, // 向后兼容
667
+ cache_hit_rate: cacheHitRate,
668
+ };
669
+ }
670
+ /** 流量聚合统计(按时间/agent/peer 分组)。 */
671
+ export function queryMessageAggregated(evolcoreHome, granularity, filter) {
672
+ const groupCol = GRAN_GROUP_COL[granularity];
673
+ const { clause, params } = _buildMsgWhere(filter);
674
+ let sql;
675
+ if (groupCol) {
676
+ const col = groupCol === 'model' ? 'agent_aid' : groupCol; // model 无意义,降级 agent
677
+ sql = `
678
+ SELECT ${col} AS period,
679
+ SUM(CASE WHEN direction='in' THEN 1 ELSE 0 END) AS msg_in,
680
+ SUM(CASE WHEN direction='out' THEN 1 ELSE 0 END) AS msg_out,
681
+ SUM(CASE WHEN direction='in' THEN bytes ELSE 0 END) AS bytes_in,
682
+ SUM(CASE WHEN direction='out' THEN bytes ELSE 0 END) AS bytes_out
683
+ FROM message_events ${clause}
684
+ GROUP BY ${col} ORDER BY (bytes_in + bytes_out) DESC
685
+ `;
686
+ }
687
+ else {
688
+ const fmt = GRAN_FMT[granularity] || GRAN_FMT.day;
689
+ sql = `
690
+ SELECT strftime('${fmt}', ts/1000, 'unixepoch', 'localtime') AS period,
691
+ SUM(CASE WHEN direction='in' THEN 1 ELSE 0 END) AS msg_in,
692
+ SUM(CASE WHEN direction='out' THEN 1 ELSE 0 END) AS msg_out,
693
+ SUM(CASE WHEN direction='in' THEN bytes ELSE 0 END) AS bytes_in,
694
+ SUM(CASE WHEN direction='out' THEN bytes ELSE 0 END) AS bytes_out
695
+ FROM message_events ${clause}
696
+ GROUP BY period ORDER BY period
697
+ `;
698
+ }
699
+ const periodMap = new Map();
700
+ for (const dbPath of _relevantDbs(evolcoreHome, filter)) {
701
+ const db = openReadonlyDb(dbPath);
702
+ if (!db)
703
+ continue;
704
+ try {
705
+ const rows = db.prepare(sql).all(...params);
706
+ for (const r of rows) {
707
+ const existing = periodMap.get(r.period);
708
+ if (existing) {
709
+ existing.msg_in += r.msg_in ?? 0;
710
+ existing.msg_out += r.msg_out ?? 0;
711
+ existing.bytes_in += r.bytes_in ?? 0;
712
+ existing.bytes_out += r.bytes_out ?? 0;
713
+ }
714
+ else {
715
+ periodMap.set(r.period, {
716
+ period: r.period,
717
+ msg_in: r.msg_in ?? 0, msg_out: r.msg_out ?? 0,
718
+ bytes_in: r.bytes_in ?? 0, bytes_out: r.bytes_out ?? 0,
719
+ });
720
+ }
721
+ }
722
+ }
723
+ finally {
724
+ db.close();
725
+ }
726
+ }
727
+ const sorted = Array.from(periodMap.values());
728
+ if (groupCol) {
729
+ sorted.sort((a, b) => (b.bytes_in + b.bytes_out) - (a.bytes_in + a.bytes_out));
730
+ }
731
+ else {
732
+ sorted.sort((a, b) => a.period.localeCompare(b.period));
733
+ }
734
+ return sorted;
735
+ }
736
+ /** 今日流量概览。 */
737
+ export function queryMessageTodaySummary(evolcoreHome, agentAid) {
738
+ const now = new Date();
739
+ const from_ts = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());
740
+ const rows = queryMessageAggregated(evolcoreHome, 'day', { from_ts, agent_aid: agentAid });
741
+ return rows[0] ?? null;
742
+ }
743
+ function _buildMsgWhere(f) {
744
+ const conds = [];
745
+ const params = [];
746
+ if (f.from_ts) {
747
+ conds.push('ts >= ?');
748
+ params.push(f.from_ts);
749
+ }
750
+ if (f.to_ts) {
751
+ conds.push('ts < ?');
752
+ params.push(f.to_ts);
753
+ }
754
+ if (f.agent_aid) {
755
+ conds.push('agent_aid = ?');
756
+ params.push(f.agent_aid);
757
+ }
758
+ if (f.peer_key) {
759
+ conds.push('peer_key = ?');
760
+ params.push(f.peer_key);
761
+ }
762
+ return { clause: conds.length ? 'WHERE ' + conds.join(' AND ') : '', params };
763
+ }