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,141 @@
1
+ import { getRoleDefinition } from '../../config/roles.js';
2
+ import { PUBLIC_PERMISSION_MODES } from '../permission/mode.js';
3
+ const PERMISSION_MODE_AUTHORITY = {
4
+ readonly: 0,
5
+ auto: 1,
6
+ request: 2,
7
+ bypass: 3,
8
+ };
9
+ /** Resolve permissionMode exclusively from the selected role policy. */
10
+ export function resolveRuntimePermissionMode(input) {
11
+ const role = input.role || 'none';
12
+ const definition = getRoleDefinition(role, input.selfAid);
13
+ const configured = definition?.permissions?.permissionMode?.default;
14
+ if (typeof configured !== 'string' || !PUBLIC_PERMISSION_MODES.includes(configured)) {
15
+ return {
16
+ effectiveValue: 'readonly',
17
+ role,
18
+ decidedBy: 'safe-fallback',
19
+ reason: !definition
20
+ ? 'role_not_found'
21
+ : configured === undefined ? 'role_permission_mode_missing' : 'role_permission_mode_invalid',
22
+ };
23
+ }
24
+ return {
25
+ effectiveValue: configured,
26
+ role,
27
+ decidedBy: 'role',
28
+ };
29
+ }
30
+ /** Keep a per-run override at or below the authority granted by the current role. */
31
+ export function constrainRuntimePermissionMode(input) {
32
+ const roleDecision = resolveRuntimePermissionMode(input);
33
+ if (input.requestedValue === undefined || input.requestedValue === null || input.requestedValue === '') {
34
+ return { ...roleDecision, constrained: false };
35
+ }
36
+ const requestedValue = PUBLIC_PERMISSION_MODES.includes(input.requestedValue)
37
+ ? input.requestedValue
38
+ : 'readonly';
39
+ if (PERMISSION_MODE_AUTHORITY[requestedValue] > PERMISSION_MODE_AUTHORITY[roleDecision.effectiveValue]) {
40
+ return {
41
+ ...roleDecision,
42
+ requestedValue,
43
+ constrained: true,
44
+ reason: 'role_authority_ceiling',
45
+ };
46
+ }
47
+ return {
48
+ effectiveValue: requestedValue,
49
+ role: roleDecision.role,
50
+ requestedValue,
51
+ decidedBy: 'override',
52
+ constrained: false,
53
+ };
54
+ }
55
+ /** Apply an optional role policy to a resolved string-valued config field. */
56
+ export function resolveRuntimeStringField(input) {
57
+ const role = input.role || 'none';
58
+ const configuredValue = typeof input.configuredValue === 'string'
59
+ && input.configuredValue.trim().length > 0
60
+ ? input.configuredValue
61
+ : undefined;
62
+ const policy = getRoleDefinition(role, input.selfAid)?.permissions?.[input.field];
63
+ const defaultValue = typeof policy?.default === 'string' && policy.default.trim().length > 0
64
+ ? policy.default
65
+ : undefined;
66
+ // Missing or malformed policies must not erase otherwise valid runtime config.
67
+ if (!policy || defaultValue === undefined) {
68
+ return {
69
+ effectiveValue: configuredValue,
70
+ role,
71
+ field: input.field,
72
+ decidedBy: configuredValue === undefined ? 'unconfigured' : 'configured',
73
+ constrained: false,
74
+ };
75
+ }
76
+ if (!policy.allowOverride) {
77
+ return {
78
+ effectiveValue: defaultValue,
79
+ role,
80
+ field: input.field,
81
+ decidedBy: 'role',
82
+ constrained: true,
83
+ reason: 'override_disabled',
84
+ };
85
+ }
86
+ if (configuredValue === undefined) {
87
+ return {
88
+ effectiveValue: defaultValue,
89
+ role,
90
+ field: input.field,
91
+ decidedBy: 'role',
92
+ constrained: true,
93
+ reason: 'configured_value_missing',
94
+ };
95
+ }
96
+ const allowedValues = Array.isArray(policy.allowedValues)
97
+ ? policy.allowedValues.filter((value) => typeof value === 'string')
98
+ : [];
99
+ if (allowedValues.length > 0 && !allowedValues.includes(configuredValue)) {
100
+ return {
101
+ effectiveValue: defaultValue,
102
+ role,
103
+ field: input.field,
104
+ decidedBy: 'role',
105
+ constrained: true,
106
+ reason: 'value_not_allowed',
107
+ };
108
+ }
109
+ return {
110
+ effectiveValue: configuredValue,
111
+ role,
112
+ field: input.field,
113
+ decidedBy: 'configured',
114
+ constrained: false,
115
+ };
116
+ }
117
+ /** Validate a relation-level string override before it is persisted. */
118
+ export function validateRuntimeStringFieldOverride(input) {
119
+ const role = input.role || 'none';
120
+ const policy = getRoleDefinition(role, input.selfAid)?.permissions?.[input.field];
121
+ if (!policy)
122
+ return { ok: true };
123
+ if (!policy.allowOverride) {
124
+ return {
125
+ ok: false,
126
+ code: 'ROLE_OVERRIDE_DISABLED',
127
+ message: `Role ${role} cannot override ${input.field}`,
128
+ };
129
+ }
130
+ const allowedValues = Array.isArray(policy.allowedValues)
131
+ ? policy.allowedValues.filter((value) => typeof value === 'string')
132
+ : [];
133
+ if (allowedValues.length > 0 && !allowedValues.includes(input.value)) {
134
+ return {
135
+ ok: false,
136
+ code: 'ROLE_VALUE_NOT_ALLOWED',
137
+ message: `Value ${input.value} is not allowed for ${input.field} by role ${role}`,
138
+ };
139
+ }
140
+ return { ok: true };
141
+ }
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Claude SessionFileAdapter
3
+ *
4
+ * Reads Claude Agent SDK session files from ~/.claude/projects/{encodedPath}/{sessionId}.jsonl
5
+ * and wraps sdkListSessions for name synchronization.
6
+ */
7
+ import { listSessions as sdkListSessions } from '@anthropic-ai/claude-agent-sdk';
8
+ import { encodePath } from '../../../utils/cross-platform.js';
9
+ import { logger } from '../../../utils/logger.js';
10
+ import { buildCliSessionPreview } from '../session-title.js';
11
+ import path from 'path';
12
+ import fs from 'fs';
13
+ import os from 'os';
14
+ export class ClaudeSessionFileAdapter {
15
+ baseagent = 'claude';
16
+ getSessionFilePath(projectPath, agentSessionId) {
17
+ const homeDir = os.homedir();
18
+ const encodedPath = encodePath(projectPath);
19
+ return path.join(homeDir, '.claude', 'projects', encodedPath, `${agentSessionId}.jsonl`);
20
+ }
21
+ checkExists(projectPath, agentSessionId) {
22
+ const sessionFile = this.getSessionFilePath(projectPath, agentSessionId);
23
+ return fs.existsSync(sessionFile);
24
+ }
25
+ getFileInfo(projectPath, agentSessionId) {
26
+ const sessionFile = this.getSessionFilePath(projectPath, agentSessionId);
27
+ if (!fs.existsSync(sessionFile))
28
+ return { turns: 0 };
29
+ try {
30
+ const content = fs.readFileSync(sessionFile, 'utf-8');
31
+ const lines = content.split('\n').filter(l => l.trim());
32
+ let turns = 0;
33
+ let title;
34
+ for (const line of lines) {
35
+ const event = JSON.parse(line);
36
+ if (event.type === 'user' && event.message?.role === 'user') {
37
+ const msgContent = event.message.content;
38
+ const isToolResult = Array.isArray(msgContent) && msgContent.every((c) => c.type === 'tool_result');
39
+ if (!isToolResult) {
40
+ turns++;
41
+ }
42
+ }
43
+ if (event.title && !title) {
44
+ title = event.title;
45
+ }
46
+ if (event.sessionTitle && !title) {
47
+ title = event.sessionTitle;
48
+ }
49
+ }
50
+ return { turns, title };
51
+ }
52
+ catch (error) {
53
+ logger.warn(`[ClaudeAdapter] Failed to read session file info: ${sessionFile}`, error);
54
+ return { turns: 0 };
55
+ }
56
+ }
57
+ readFirstMessage(projectPath, agentSessionId) {
58
+ const sessionFile = this.getSessionFilePath(projectPath, agentSessionId);
59
+ if (!fs.existsSync(sessionFile))
60
+ return null;
61
+ return this.readSessionPreview(sessionFile).title;
62
+ }
63
+ /**
64
+ * 单遍扫描会话文件,返回预览标题及是否含真人消息。
65
+ * - title:第一条真人消息(跳过脚手架 prompt);全是脚手架时退回首条脚手架文本作兜底
66
+ * - hasReal:是否存在至少一条真人 user 消息(非 tool_result、非脚手架 prompt)
67
+ * 读失败时 title=null、hasReal=true(保守放行,避免误杀正常会话)。
68
+ */
69
+ readSessionPreview(sessionFile) {
70
+ try {
71
+ const content = fs.readFileSync(sessionFile, 'utf-8');
72
+ const lines = content.split('\n').filter(l => l.trim());
73
+ const messages = [];
74
+ for (const line of lines) {
75
+ let event;
76
+ try {
77
+ event = JSON.parse(line);
78
+ }
79
+ catch {
80
+ continue;
81
+ }
82
+ if (event.type === 'user' && event.message?.role === 'user') {
83
+ const raw = this.extractRawUserText(event.message.content);
84
+ if (!raw)
85
+ continue;
86
+ messages.push(raw);
87
+ }
88
+ }
89
+ return buildCliSessionPreview(messages, 'claude');
90
+ }
91
+ catch (error) {
92
+ logger.warn(`[ClaudeAdapter] Failed to read session file: ${sessionFile}`, error);
93
+ return { title: null, hasReal: true };
94
+ }
95
+ }
96
+ readLastUserMessage(projectPath, agentSessionId) {
97
+ const sessionFile = this.getSessionFilePath(projectPath, agentSessionId);
98
+ if (!fs.existsSync(sessionFile))
99
+ return null;
100
+ try {
101
+ const content = fs.readFileSync(sessionFile, 'utf-8');
102
+ const lines = content.split('\n').filter(l => l.trim());
103
+ let lastMessage = null;
104
+ for (const line of lines) {
105
+ const event = JSON.parse(line);
106
+ if (event.type === 'user' && event.message?.role === 'user') {
107
+ lastMessage = this.extractUserMessageText(event.message.content) ?? lastMessage;
108
+ }
109
+ }
110
+ return lastMessage;
111
+ }
112
+ catch (error) {
113
+ logger.warn(`[ClaudeAdapter] Failed to read last message from session file: ${sessionFile}`, error);
114
+ }
115
+ return null;
116
+ }
117
+ // CLI 会话白名单:只有真正由人手发起的终端会话才值得导入。
118
+ // 其它 entrypoint(sdk-ts=EvolCore 自身、sdk-py=security-guidance 等插件的后台 SDK 会话)
119
+ // 同样把 JSONL 写进项目目录,但不是用户会话,导入它们没有意义。
120
+ static CLI_ENTRYPOINTS = new Set(['cli', 'sdk-cli']);
121
+ // entrypoint 不可变,进程内缓存 filePath→entrypoint,避免重复读文件头部
122
+ entrypointCache = new Map();
123
+ // entrypoint 字段位于首个 user 事件行(实测恒在前 4 行内,cli 会话首次出现 < 8KB)。
124
+ // 插件会话首行 queue-operation 可能携带巨大 diff(数百 KB),把 entrypoint 推到很后面,
125
+ // 因此读取上限设为 32KB:cli 会话必命中,插件会话要么命中 sdk-py、要么读不到 → 一律排除。
126
+ static ENTRYPOINT_SCAN_BYTES = 32 * 1024;
127
+ /** 读取会话文件的 entrypoint,结果缓存在实例内(entrypoint 不可变,无需失效)。 */
128
+ readEntrypoint(filePath) {
129
+ if (this.entrypointCache.has(filePath))
130
+ return this.entrypointCache.get(filePath);
131
+ let fd;
132
+ let result = null;
133
+ try {
134
+ fd = fs.openSync(filePath, 'r');
135
+ const buf = Buffer.allocUnsafe(ClaudeSessionFileAdapter.ENTRYPOINT_SCAN_BYTES);
136
+ const bytesRead = fs.readSync(fd, buf, 0, buf.length, 0);
137
+ const head = buf.toString('utf-8', 0, bytesRead);
138
+ const m = head.match(/"entrypoint":"([^"]*)"/);
139
+ result = m ? m[1] : null;
140
+ }
141
+ catch {
142
+ // keep null
143
+ }
144
+ finally {
145
+ if (fd !== undefined)
146
+ fs.closeSync(fd);
147
+ }
148
+ this.entrypointCache.set(filePath, result);
149
+ return result;
150
+ }
151
+ scanCliSessions(projectPath, limit = 10) {
152
+ const homeDir = os.homedir();
153
+ const encodedPath = encodePath(projectPath);
154
+ const sessionDir = path.join(homeDir, '.claude', 'projects', encodedPath);
155
+ if (!fs.existsSync(sessionDir))
156
+ return [];
157
+ const candidates = fs.readdirSync(sessionDir)
158
+ .filter(f => f.endsWith('.jsonl'))
159
+ .filter(f => !f.startsWith('agent-'))
160
+ .map(f => {
161
+ const filePath = path.join(sessionDir, f);
162
+ const stat = fs.statSync(filePath);
163
+ return { uuid: f.replace('.jsonl', ''), filePath, mtime: stat.mtimeMs, size: stat.size };
164
+ })
165
+ .filter(f => f.size > 0)
166
+ .sort((a, b) => b.mtime - a.mtime);
167
+ // 按 mtime 降序惰性判定 entrypoint,凑够调用方要求的数量即停,避免读取全部文件。
168
+ // 通过 entrypoint 白名单后,单遍扫描文件取预览标题并判定是否含真人消息,
169
+ // 排除整段都是 Claude Code 脚手架 prompt(斜杠命令 / skill / hook 探测)的空壳会话。
170
+ const result = [];
171
+ for (const f of candidates) {
172
+ const entrypoint = this.readEntrypoint(f.filePath);
173
+ if (entrypoint && ClaudeSessionFileAdapter.CLI_ENTRYPOINTS.has(entrypoint)) {
174
+ const preview = this.readSessionPreview(f.filePath);
175
+ if (!preview.hasReal)
176
+ continue;
177
+ result.push({ uuid: f.uuid, mtime: f.mtime, title: preview.title || undefined });
178
+ if (result.length >= limit)
179
+ break;
180
+ }
181
+ }
182
+ return result;
183
+ }
184
+ async listSdkSessions(projectPath) {
185
+ try {
186
+ const sessions = await sdkListSessions({ dir: projectPath });
187
+ return sessions.map(s => ({
188
+ sessionId: s.sessionId,
189
+ title: s.customTitle || undefined,
190
+ }));
191
+ }
192
+ catch (error) {
193
+ logger.debug('[ClaudeAdapter] SDK listSessions failed (non-critical):', error);
194
+ return [];
195
+ }
196
+ }
197
+ /** 提取 user 事件的原始文本(未截断),用于合成 prompt 判定。 */
198
+ extractRawUserText(messageContent) {
199
+ if (typeof messageContent === 'string') {
200
+ const text = messageContent.trim().replace(/\s+/g, ' ');
201
+ return text || null;
202
+ }
203
+ else if (Array.isArray(messageContent)) {
204
+ const textContent = messageContent.find((c) => c.type === 'text');
205
+ if (textContent?.text) {
206
+ const text = textContent.text.trim().replace(/\s+/g, ' ');
207
+ return text || null;
208
+ }
209
+ }
210
+ return null;
211
+ }
212
+ extractUserMessageText(messageContent) {
213
+ const text = this.extractRawUserText(messageContent);
214
+ if (!text)
215
+ return null;
216
+ return text.substring(0, 50) + (text.length > 50 ? '...' : '');
217
+ }
218
+ }
@@ -0,0 +1,333 @@
1
+ /**
2
+ * Codex SessionFileAdapter
3
+ *
4
+ * Reads Codex thread data from ~/.codex/state_*.sqlite (read-only)
5
+ * and Codex rollout JSONL files for detailed session info.
6
+ *
7
+ * `node:sqlite` 是 Node 22.5+ 的实验性内置模块。低版本 Node 上懒加载会失败,
8
+ * adapter 自动降级到只读 rollout JSONL —— `checkExists/getFileInfo/readFirstMessage/
9
+ * readLastUserMessage` 仍可工作;`scanCliSessions/listSdkSessions` 因依赖 DB 索引
10
+ * 会返回空数组。
11
+ */
12
+ import { createRequire } from 'module';
13
+ import { buildCliSessionPreview, isEvolcoreRoutedMessage, sanitizeSessionTitle, } from '../session-title.js';
14
+ import { logger } from '../../../utils/logger.js';
15
+ import path from 'path';
16
+ import fs from 'fs';
17
+ import os from 'os';
18
+ const requireFromHere = createRequire(import.meta.url);
19
+ let sqliteModule; // undefined = not tried, null = unavailable
20
+ export const sanitizeCodexThreadTitle = sanitizeSessionTitle;
21
+ const CODEX_INTERACTIVE_SOURCES = new Set(['cli', 'vscode']);
22
+ function loadSqlite() {
23
+ if (sqliteModule !== undefined)
24
+ return sqliteModule;
25
+ try {
26
+ sqliteModule = requireFromHere('node:sqlite');
27
+ }
28
+ catch (e) {
29
+ logger.warn(`[CodexAdapter] node:sqlite unavailable (Node < 22.5?): ${e?.message || e}. Codex session listing falls back to rollout JSONL.`);
30
+ sqliteModule = null;
31
+ }
32
+ return sqliteModule;
33
+ }
34
+ export class CodexSessionFileAdapter {
35
+ baseagent = 'codex';
36
+ db = null;
37
+ dbInitialized = false;
38
+ /**
39
+ * 动态发现最新的 state_*.sqlite 文件
40
+ * Codex 使用 sqlx 迁移,DB 文件名含版本号(state_5, state_6, ...)
41
+ */
42
+ resolveStateDbPath() {
43
+ const codexHome = path.join(os.homedir(), '.codex');
44
+ if (!fs.existsSync(codexHome))
45
+ return null;
46
+ try {
47
+ const files = fs.readdirSync(codexHome)
48
+ .filter(f => /^state_\d+\.sqlite$/.test(f))
49
+ .sort((a, b) => {
50
+ const va = parseInt(a.match(/state_(\d+)/)?.[1] || '0');
51
+ const vb = parseInt(b.match(/state_(\d+)/)?.[1] || '0');
52
+ return vb - va;
53
+ });
54
+ return files.length > 0 ? path.join(codexHome, files[0]) : null;
55
+ }
56
+ catch {
57
+ return null;
58
+ }
59
+ }
60
+ getDb() {
61
+ if (this.dbInitialized)
62
+ return this.db;
63
+ this.dbInitialized = true;
64
+ const sqlite = loadSqlite();
65
+ if (!sqlite)
66
+ return null;
67
+ const dbPath = this.resolveStateDbPath();
68
+ if (!dbPath)
69
+ return null;
70
+ try {
71
+ this.db = new sqlite.DatabaseSync(dbPath, { readOnly: true });
72
+ logger.debug(`[CodexAdapter] Opened state DB: ${dbPath}`);
73
+ }
74
+ catch (error) {
75
+ logger.warn(`[CodexAdapter] Failed to open state DB: ${dbPath}`, error);
76
+ this.db = null;
77
+ }
78
+ return this.db;
79
+ }
80
+ checkExists(projectPath, agentSessionId) {
81
+ // 1. 优先查 state_*.sqlite(覆盖 CLI 创建的线程)
82
+ const db = this.getDb();
83
+ if (db) {
84
+ try {
85
+ const row = db.prepare('SELECT 1 FROM threads WHERE id = ? AND archived = 0').get(agentSessionId);
86
+ if (row)
87
+ return true;
88
+ }
89
+ catch (error) {
90
+ logger.warn(`[CodexAdapter] checkExists DB query failed:`, error);
91
+ }
92
+ }
93
+ // 2. Fallback: 扫 ~/.codex/sessions/ 下的 rollout JSONL
94
+ // SDK 创建的 thread 不写 state_*.sqlite,但会持久化到 sessions 目录
95
+ return !!this.findRolloutFile(agentSessionId);
96
+ }
97
+ /**
98
+ * 在 ~/.codex/sessions/ 下递归查找含 agentSessionId 的 rollout JSONL 文件
99
+ */
100
+ findRolloutFile(agentSessionId) {
101
+ const sessionsDir = path.join(os.homedir(), '.codex', 'sessions');
102
+ if (!fs.existsSync(sessionsDir))
103
+ return null;
104
+ try {
105
+ const search = (dir) => {
106
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
107
+ if (entry.isDirectory()) {
108
+ const found = search(path.join(dir, entry.name));
109
+ if (found)
110
+ return found;
111
+ }
112
+ else if (entry.name.endsWith('.jsonl') && entry.name.includes(agentSessionId)) {
113
+ return path.join(dir, entry.name);
114
+ }
115
+ }
116
+ return null;
117
+ };
118
+ return search(sessionsDir);
119
+ }
120
+ catch {
121
+ return null;
122
+ }
123
+ }
124
+ getFileInfo(projectPath, agentSessionId) {
125
+ // 1. 优先查 state DB
126
+ const db = this.getDb();
127
+ if (db) {
128
+ try {
129
+ const row = db.prepare('SELECT title, first_user_message, rollout_path FROM threads WHERE id = ?').get(agentSessionId);
130
+ if (row) {
131
+ const rolloutPath = this.resolveRolloutPath(row.rollout_path, agentSessionId);
132
+ const preview = this.buildThreadPreview(row.first_user_message, rolloutPath, row.title, false, 80);
133
+ return {
134
+ turns: this.countTurnsFromRollout(rolloutPath),
135
+ title: preview.title || undefined,
136
+ };
137
+ }
138
+ }
139
+ catch (error) {
140
+ logger.warn(`[CodexAdapter] getFileInfo DB query failed:`, error);
141
+ }
142
+ }
143
+ // 2. Fallback: 从 sessions 目录查找 rollout 文件
144
+ const rolloutPath = this.findRolloutFile(agentSessionId);
145
+ if (rolloutPath) {
146
+ return { turns: this.countTurnsFromRollout(rolloutPath) };
147
+ }
148
+ return { turns: 0 };
149
+ }
150
+ readFirstMessage(projectPath, agentSessionId) {
151
+ // 1. 优先查 state DB
152
+ const db = this.getDb();
153
+ if (db) {
154
+ try {
155
+ const row = db.prepare('SELECT first_user_message, rollout_path FROM threads WHERE id = ?').get(agentSessionId);
156
+ if (row) {
157
+ const rolloutPath = this.resolveRolloutPath(row.rollout_path, agentSessionId);
158
+ return this.buildThreadPreview(row.first_user_message, rolloutPath).title;
159
+ }
160
+ }
161
+ catch (error) {
162
+ logger.warn(`[CodexAdapter] readFirstMessage DB query failed:`, error);
163
+ }
164
+ }
165
+ // 2. Fallback: 从 rollout JSONL 读取第一条 user_message
166
+ const rolloutPath = this.findRolloutFile(agentSessionId);
167
+ if (!rolloutPath)
168
+ return null;
169
+ return this.readUserMessageFromRollout(rolloutPath, 'first');
170
+ }
171
+ readLastUserMessage(projectPath, agentSessionId) {
172
+ // 1. 优先查 state DB 获取 rollout_path
173
+ const db = this.getDb();
174
+ let rolloutPath = null;
175
+ if (db) {
176
+ try {
177
+ const row = db.prepare('SELECT rollout_path FROM threads WHERE id = ?').get(agentSessionId);
178
+ if (row?.rollout_path && fs.existsSync(row.rollout_path)) {
179
+ rolloutPath = row.rollout_path;
180
+ }
181
+ }
182
+ catch (error) {
183
+ logger.warn(`[CodexAdapter] readLastUserMessage DB query failed:`, error);
184
+ }
185
+ }
186
+ // 2. Fallback: 从 sessions 目录查找 rollout 文件
187
+ if (!rolloutPath) {
188
+ rolloutPath = this.findRolloutFile(agentSessionId);
189
+ }
190
+ if (!rolloutPath)
191
+ return null;
192
+ return this.readUserMessageFromRollout(rolloutPath, 'last');
193
+ }
194
+ scanCliSessions(projectPath, limit = 10) {
195
+ const db = this.getDb();
196
+ if (!db)
197
+ return [];
198
+ try {
199
+ const rows = db.prepare('SELECT id, updated_at, source, title, first_user_message, rollout_path FROM threads WHERE cwd = ? AND archived = 0 ORDER BY updated_at DESC').all(projectPath);
200
+ const sessions = [];
201
+ for (const row of rows) {
202
+ if (!CODEX_INTERACTIVE_SOURCES.has(row.source))
203
+ continue;
204
+ // 排除 EvolCore 自身驱动 codex 产生的会话(首条 user 消息带路由前缀)。
205
+ // codex 无 entrypoint 字段,EvolCore 会话与真人 vscode 会话同为 source=vscode,
206
+ // 只能靠路由前缀区分——等同 Claude adapter 用 entrypoint 排除 sdk-ts。
207
+ if (isEvolcoreRoutedMessage(row.first_user_message))
208
+ continue;
209
+ const rolloutPath = this.resolveRolloutPath(row.rollout_path, row.id);
210
+ const preview = this.buildThreadPreview(row.first_user_message, rolloutPath, row.title, false, 80);
211
+ if (!preview.hasReal)
212
+ continue;
213
+ const timestamp = Number(row.updated_at) || 0;
214
+ sessions.push({
215
+ uuid: row.id,
216
+ mtime: timestamp < 1_000_000_000_000 ? timestamp * 1000 : timestamp,
217
+ title: preview.title || undefined,
218
+ });
219
+ if (sessions.length >= limit)
220
+ break;
221
+ }
222
+ return sessions;
223
+ }
224
+ catch (error) {
225
+ logger.warn(`[CodexAdapter] scanCliSessions failed:`, error);
226
+ return [];
227
+ }
228
+ }
229
+ async listSdkSessions(projectPath) {
230
+ const db = this.getDb();
231
+ if (!db)
232
+ return [];
233
+ try {
234
+ const rows = db.prepare('SELECT id, title FROM threads WHERE cwd = ? AND archived = 0 ORDER BY updated_at DESC').all(projectPath);
235
+ return rows.map(r => ({
236
+ sessionId: r.id,
237
+ title: sanitizeCodexThreadTitle(r.title),
238
+ }));
239
+ }
240
+ catch (error) {
241
+ logger.warn(`[CodexAdapter] listSdkSessions failed:`, error);
242
+ return [];
243
+ }
244
+ }
245
+ close() {
246
+ if (this.db) {
247
+ try {
248
+ this.db.close();
249
+ }
250
+ catch { /* ignore close errors */ }
251
+ this.db = null;
252
+ }
253
+ this.dbInitialized = false;
254
+ }
255
+ /**
256
+ * 从 rollout JSONL 读取第一条或最后一条 user_message
257
+ */
258
+ readUserMessageFromRollout(rolloutPath, which) {
259
+ const messages = this.readRolloutUserMessages(rolloutPath);
260
+ if (!messages)
261
+ return null;
262
+ const orderedMessages = which === 'first' ? messages : [...messages].reverse();
263
+ return buildCliSessionPreview(orderedMessages, 'codex').title;
264
+ }
265
+ resolveRolloutPath(rolloutPath, agentSessionId) {
266
+ if (typeof rolloutPath === 'string' && rolloutPath && fs.existsSync(rolloutPath)) {
267
+ return rolloutPath;
268
+ }
269
+ return this.findRolloutFile(agentSessionId);
270
+ }
271
+ buildThreadPreview(firstUserMessage, rolloutPath, explicitTitle, includeSyntheticFallback = true, maxLength = 50) {
272
+ const initial = buildCliSessionPreview([firstUserMessage], 'codex', {
273
+ explicitTitle,
274
+ includeSyntheticFallback,
275
+ maxLength,
276
+ });
277
+ if (initial.hasReal || !rolloutPath)
278
+ return initial;
279
+ const rolloutMessages = this.readRolloutUserMessages(rolloutPath);
280
+ if (!rolloutMessages)
281
+ return initial;
282
+ return buildCliSessionPreview(rolloutMessages, 'codex', {
283
+ explicitTitle,
284
+ includeSyntheticFallback,
285
+ maxLength,
286
+ });
287
+ }
288
+ readRolloutUserMessages(rolloutPath) {
289
+ try {
290
+ const content = fs.readFileSync(rolloutPath, 'utf-8');
291
+ const lines = content.split('\n').filter(l => l.trim());
292
+ const messages = [];
293
+ for (const line of lines) {
294
+ try {
295
+ const event = JSON.parse(line);
296
+ if (event.type === 'event_msg' && event.payload?.type === 'user_message' && event.payload.message) {
297
+ messages.push(String(event.payload.message));
298
+ }
299
+ }
300
+ catch { /* skip malformed line */ }
301
+ }
302
+ return messages;
303
+ }
304
+ catch {
305
+ return null;
306
+ }
307
+ }
308
+ /**
309
+ * 从 rollout JSONL 文件计算轮数(数 turn_context 行)
310
+ */
311
+ countTurnsFromRollout(rolloutPath) {
312
+ if (!rolloutPath || !fs.existsSync(rolloutPath))
313
+ return 0;
314
+ try {
315
+ const content = fs.readFileSync(rolloutPath, 'utf-8');
316
+ const lines = content.split('\n').filter(l => l.trim());
317
+ let turns = 0;
318
+ for (const line of lines) {
319
+ try {
320
+ const event = JSON.parse(line);
321
+ if (event.type === 'turn_context') {
322
+ turns++;
323
+ }
324
+ }
325
+ catch { /* skip malformed line */ }
326
+ }
327
+ return turns;
328
+ }
329
+ catch {
330
+ return 0;
331
+ }
332
+ }
333
+ }