agentix-cli 0.25.0 → 0.27.0

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 (269) hide show
  1. package/README.md +2 -0
  2. package/dist/{App-6Y7B3YQH.js → App-U52IPUK2.js} +2 -2
  3. package/dist/{ChatApp-KIEUJ2DS.js → ChatApp-XP4OMHPL.js} +2 -2
  4. package/dist/agent-V2CA4TXD.js +2 -0
  5. package/dist/article-fields-L4D2BOGY.js +2 -0
  6. package/dist/article-quality-44ONVGLN.js +3 -0
  7. package/dist/article-quality-44ONVGLN.js.map +1 -0
  8. package/dist/{audit-JU3HWISB.js → audit-4245CX6P.js} +2 -2
  9. package/dist/backfill-MTD2B62I.js +6 -0
  10. package/dist/backfill-MTD2B62I.js.map +1 -0
  11. package/dist/{board-dashboard-CQ5F4GPD.js → board-dashboard-TTMAYRLG.js} +2445 -519
  12. package/dist/board-dashboard-TTMAYRLG.js.map +1 -0
  13. package/dist/{builtin-FRKAQMFS.js → builtin-B67DMJ3M.js} +2 -2
  14. package/dist/calibration-ILS5OI44.js +2 -0
  15. package/dist/chunk-2T5W3ETS.js +5 -0
  16. package/dist/{chunk-FDMWOPEL.js.map → chunk-2T5W3ETS.js.map} +1 -1
  17. package/dist/chunk-3SCPRYJB.js +5 -0
  18. package/dist/chunk-3SCPRYJB.js.map +1 -0
  19. package/dist/chunk-4BR7D2OU.js +36 -0
  20. package/dist/chunk-4BR7D2OU.js.map +1 -0
  21. package/dist/{chunk-P4VWIVKN.js → chunk-52ZPELD2.js} +403 -29
  22. package/dist/chunk-52ZPELD2.js.map +1 -0
  23. package/dist/{chunk-5U35WAZF.js → chunk-77OV7346.js} +6 -6
  24. package/dist/chunk-77OV7346.js.map +1 -0
  25. package/dist/chunk-AJ5RMU7X.js +2 -0
  26. package/dist/chunk-AJ5RMU7X.js.map +1 -0
  27. package/dist/chunk-AJONZWLT.js +2 -0
  28. package/dist/chunk-AJONZWLT.js.map +1 -0
  29. package/dist/chunk-AX2PWSRT.js +114 -0
  30. package/dist/chunk-AX2PWSRT.js.map +1 -0
  31. package/dist/chunk-BH3GTCZ6.js +2 -0
  32. package/dist/chunk-BH3GTCZ6.js.map +1 -0
  33. package/dist/chunk-BPIRO4YD.js +126 -0
  34. package/dist/chunk-BPIRO4YD.js.map +1 -0
  35. package/dist/chunk-CJQQIRPD.js +13 -0
  36. package/dist/chunk-CJQQIRPD.js.map +1 -0
  37. package/dist/chunk-DCDQZR7I.js +24 -0
  38. package/dist/chunk-DCDQZR7I.js.map +1 -0
  39. package/dist/chunk-EVWEG3VD.js +3 -0
  40. package/dist/chunk-EVWEG3VD.js.map +1 -0
  41. package/dist/chunk-F4HZTZHW.js +2 -0
  42. package/dist/chunk-F4HZTZHW.js.map +1 -0
  43. package/dist/chunk-F6CNF7V3.js +25 -0
  44. package/dist/chunk-F6CNF7V3.js.map +1 -0
  45. package/dist/chunk-FIEJDZVC.js +121 -0
  46. package/dist/chunk-FIEJDZVC.js.map +1 -0
  47. package/dist/chunk-FIOB3MEX.js +2 -0
  48. package/dist/{chunk-H5D7ATXS.js → chunk-FKBGJYWQ.js} +5 -5
  49. package/dist/chunk-FKBGJYWQ.js.map +1 -0
  50. package/dist/chunk-GDWJUGB5.js +9 -0
  51. package/dist/chunk-GDWJUGB5.js.map +1 -0
  52. package/dist/{chunk-TRJTTBE2.js → chunk-IB3F7VBE.js} +7 -7
  53. package/dist/{chunk-TRJTTBE2.js.map → chunk-IB3F7VBE.js.map} +1 -1
  54. package/dist/chunk-IBUSGCLQ.js +109 -0
  55. package/dist/chunk-IBUSGCLQ.js.map +1 -0
  56. package/dist/{chunk-LDHPMJ7M.js → chunk-IWMW5432.js} +3 -3
  57. package/dist/chunk-IWMW5432.js.map +1 -0
  58. package/dist/chunk-K33GIBSE.js +2 -0
  59. package/dist/chunk-K33GIBSE.js.map +1 -0
  60. package/dist/chunk-L2IZPR6P.js +45 -0
  61. package/dist/chunk-L2IZPR6P.js.map +1 -0
  62. package/dist/chunk-L2WSPY6S.js +2 -0
  63. package/dist/chunk-L2WSPY6S.js.map +1 -0
  64. package/dist/chunk-LCIREYTD.js +54 -0
  65. package/dist/chunk-LCIREYTD.js.map +1 -0
  66. package/dist/chunk-LNRB5MCU.js +530 -0
  67. package/dist/chunk-LNRB5MCU.js.map +1 -0
  68. package/dist/chunk-OPHIXXGI.js +157 -0
  69. package/dist/chunk-OPHIXXGI.js.map +1 -0
  70. package/dist/{chunk-JTJIPNXY.js → chunk-PNWNVOEZ.js} +1 -1
  71. package/dist/chunk-PNWNVOEZ.js.map +1 -0
  72. package/dist/{chunk-LNXCJWOU.js → chunk-UFAGYX73.js} +45 -47
  73. package/dist/chunk-UFAGYX73.js.map +1 -0
  74. package/dist/{chunk-DJEZ2HVU.js → chunk-VDMYBPYB.js} +1 -1
  75. package/dist/chunk-VDMYBPYB.js.map +1 -0
  76. package/dist/chunk-VTE7L6LO.js +5 -0
  77. package/dist/{chunk-G57RZCXM.js.map → chunk-VTE7L6LO.js.map} +1 -1
  78. package/dist/chunk-W5C2GDNC.js +32 -0
  79. package/dist/{chunk-JP5IEXR7.js.map → chunk-W5C2GDNC.js.map} +1 -1
  80. package/dist/chunk-WPMX3VGI.js +3 -0
  81. package/dist/chunk-WPMX3VGI.js.map +1 -0
  82. package/dist/chunk-WZ72HX5C.js +1182 -0
  83. package/dist/chunk-WZ72HX5C.js.map +1 -0
  84. package/dist/chunk-ZTEU5JVQ.js +2 -0
  85. package/dist/chunk-ZTEU5JVQ.js.map +1 -0
  86. package/dist/{chunk-2WVX3LEC.js → chunk-ZVP6GB4G.js} +1 -1
  87. package/dist/chunk-ZVP6GB4G.js.map +1 -0
  88. package/dist/cli.js +1 -1
  89. package/dist/cli.js.map +1 -1
  90. package/dist/client-PX25MD47.js +2 -0
  91. package/dist/{compaction-NWEMOSHA.js → compaction-FD4R23HZ.js} +4 -4
  92. package/dist/config-HL5F36V4.js +2 -0
  93. package/dist/config-mutator-HBX53OMJ.js +2 -0
  94. package/dist/{conflict-detector-D7VIYPDI.js → conflict-detector-ZPOT3M2C.js} +2 -2
  95. package/dist/conflict-detector-ZPOT3M2C.js.map +1 -0
  96. package/dist/context-planner-AQHJMVY2.js +17 -0
  97. package/dist/context-planner-AQHJMVY2.js.map +1 -0
  98. package/dist/debug-3MEUO2P2.js +2 -0
  99. package/dist/{discover-XJXQXXIO.js → discover-D4VVRNAI.js} +2 -2
  100. package/dist/discover-D4VVRNAI.js.map +1 -0
  101. package/dist/doctor-PBAH6XL3.js +2 -0
  102. package/dist/editor-chat-OB7IQOB5.js +137 -0
  103. package/dist/editor-chat-OB7IQOB5.js.map +1 -0
  104. package/dist/entry-triage-LK34TSQD.js +2 -0
  105. package/dist/entry-triage-LK34TSQD.js.map +1 -0
  106. package/dist/facts-YVEJOM7O.js +22 -0
  107. package/dist/facts-YVEJOM7O.js.map +1 -0
  108. package/dist/heal-FVLBHYP7.js +2 -0
  109. package/dist/index.d.ts +1073 -65
  110. package/dist/index.js +13 -13
  111. package/dist/index.js.map +1 -1
  112. package/dist/{ingest-whatsapp-LZE62INA.js → ingest-whatsapp-NS4OWDOU.js} +2 -2
  113. package/dist/loader-4ZGTK7LD.js +2 -0
  114. package/dist/loader-BOKE5M5S.js +2 -0
  115. package/dist/memory-extract-F5ZHWY5E.js +2 -0
  116. package/dist/{migrate-v2-QO7SLKIM.js → migrate-v2-V4WN2VVM.js} +2 -2
  117. package/dist/ntfy-X7EPB3TG.js +4 -0
  118. package/dist/ntfy-X7EPB3TG.js.map +1 -0
  119. package/dist/processes-WU23U2OJ.js +2 -0
  120. package/dist/program-YGXVCV2Y.js +2 -0
  121. package/dist/{projects-api-KRME2YKZ.js → projects-api-QE6L65SK.js} +2 -2
  122. package/dist/{projects-mutate-3NHVG437.js → projects-mutate-MY7JTLSZ.js} +2 -2
  123. package/dist/providers-KO2DODIO.js +2 -0
  124. package/dist/query-ZKTFJMNE.js +2 -0
  125. package/dist/questions-4B4KSPXG.js +3 -0
  126. package/dist/questions-4B4KSPXG.js.map +1 -0
  127. package/dist/recipes-7S4MC5OJ.js +2 -0
  128. package/dist/registry-PSZDPFZK.js +2 -0
  129. package/dist/review-candidates-AEUXRQQC.js +4 -0
  130. package/dist/review-candidates-AEUXRQQC.js.map +1 -0
  131. package/dist/review-store-B4YE3EFK.js +4 -0
  132. package/dist/review-store-B4YE3EFK.js.map +1 -0
  133. package/dist/{rotation-memo-B3EXXSEH.js → rotation-memo-7T75BLEX.js} +2 -2
  134. package/dist/routing-4PV66BK6.js +2 -0
  135. package/dist/routing-4PV66BK6.js.map +1 -0
  136. package/dist/runner-YRBVNYTY.js +2 -0
  137. package/dist/scheduler-GZZAN6ZG.js +2 -0
  138. package/dist/seat-VZ7AVO27.js +2 -0
  139. package/dist/select-F5I5PO2O.js +2 -0
  140. package/dist/select-F5I5PO2O.js.map +1 -0
  141. package/dist/store-MCOA6N4F.js +2 -0
  142. package/dist/{subagent-WV7QKQ3L.js → subagent-2SJHSSJS.js} +2 -2
  143. package/dist/{surface-usage-PRL6Z4U3.js → surface-usage-NS74AXSE.js} +2 -2
  144. package/dist/timers-PCB4ZIBD.js +2 -0
  145. package/dist/triage-backtest-JNO7RKX7.js +2 -0
  146. package/dist/triage-backtest-JNO7RKX7.js.map +1 -0
  147. package/dist/types-H5ENIUIT.js +2 -0
  148. package/dist/types-OIFU3KSQ.js +2 -0
  149. package/dist/web/workflow-editor.global.js +1542 -0
  150. package/dist/web/workflow-editor.global.js.map +1 -0
  151. package/dist/{whatsapp-state-XMTCSRXU.js → whatsapp-state-6D6FHHD2.js} +2 -2
  152. package/dist/wiki-N5SMP3DT.js +2 -0
  153. package/dist/wiki-N5SMP3DT.js.map +1 -0
  154. package/dist/workflows/templates/branching.yaml +1 -1
  155. package/dist/{workspace-setup-TBQAHYHJ.js → workspace-setup-B3F3TUDE.js} +2 -2
  156. package/dist/workspace-setup-B3F3TUDE.js.map +1 -0
  157. package/package.json +1 -1
  158. package/dist/agent-QGZIAZMY.js +0 -2
  159. package/dist/architect-SEPWMWS4.js +0 -2
  160. package/dist/board-dashboard-CQ5F4GPD.js.map +0 -1
  161. package/dist/chunk-2WVX3LEC.js.map +0 -1
  162. package/dist/chunk-3WQFCIB6.js +0 -24
  163. package/dist/chunk-3WQFCIB6.js.map +0 -1
  164. package/dist/chunk-4YCH6IZV.js +0 -2
  165. package/dist/chunk-53GCFZPR.js +0 -116
  166. package/dist/chunk-53GCFZPR.js.map +0 -1
  167. package/dist/chunk-53NRX2NA.js +0 -2
  168. package/dist/chunk-53NRX2NA.js.map +0 -1
  169. package/dist/chunk-5U35WAZF.js.map +0 -1
  170. package/dist/chunk-BFXONUUN.js +0 -108
  171. package/dist/chunk-BFXONUUN.js.map +0 -1
  172. package/dist/chunk-BFZUG66X.js +0 -45
  173. package/dist/chunk-BFZUG66X.js.map +0 -1
  174. package/dist/chunk-DJEZ2HVU.js.map +0 -1
  175. package/dist/chunk-FDMWOPEL.js +0 -5
  176. package/dist/chunk-FDWOECJY.js +0 -54
  177. package/dist/chunk-FDWOECJY.js.map +0 -1
  178. package/dist/chunk-G57RZCXM.js +0 -5
  179. package/dist/chunk-H5D7ATXS.js.map +0 -1
  180. package/dist/chunk-HA3XI7Z2.js +0 -155
  181. package/dist/chunk-HA3XI7Z2.js.map +0 -1
  182. package/dist/chunk-HR4JSRZS.js +0 -3
  183. package/dist/chunk-HR4JSRZS.js.map +0 -1
  184. package/dist/chunk-ISEBLHWX.js +0 -118
  185. package/dist/chunk-ISEBLHWX.js.map +0 -1
  186. package/dist/chunk-JP5IEXR7.js +0 -32
  187. package/dist/chunk-JTJIPNXY.js.map +0 -1
  188. package/dist/chunk-JXWAGMI2.js +0 -13
  189. package/dist/chunk-JXWAGMI2.js.map +0 -1
  190. package/dist/chunk-KUCCW2VG.js +0 -431
  191. package/dist/chunk-KUCCW2VG.js.map +0 -1
  192. package/dist/chunk-LDHPMJ7M.js.map +0 -1
  193. package/dist/chunk-LNXCJWOU.js.map +0 -1
  194. package/dist/chunk-NGSIRE4X.js +0 -126
  195. package/dist/chunk-NGSIRE4X.js.map +0 -1
  196. package/dist/chunk-P4VWIVKN.js.map +0 -1
  197. package/dist/chunk-Q6YO7LYE.js +0 -47
  198. package/dist/chunk-Q6YO7LYE.js.map +0 -1
  199. package/dist/chunk-SR3X25TN.js +0 -3
  200. package/dist/chunk-SR3X25TN.js.map +0 -1
  201. package/dist/chunk-TMKLHTCG.js +0 -971
  202. package/dist/chunk-TMKLHTCG.js.map +0 -1
  203. package/dist/chunk-VJUTQAPJ.js +0 -2
  204. package/dist/chunk-VJUTQAPJ.js.map +0 -1
  205. package/dist/client-GGVVLOOF.js +0 -2
  206. package/dist/config-7FSZFRTQ.js +0 -2
  207. package/dist/config-mutator-POCOYYJ4.js +0 -2
  208. package/dist/conflict-detector-D7VIYPDI.js.map +0 -1
  209. package/dist/context-planner-45GEWKGL.js +0 -17
  210. package/dist/context-planner-45GEWKGL.js.map +0 -1
  211. package/dist/debug-N3B5NVJU.js +0 -2
  212. package/dist/discover-XJXQXXIO.js.map +0 -1
  213. package/dist/doctor-DHLBEKA4.js +0 -2
  214. package/dist/heal-HZ3EQD52.js +0 -2
  215. package/dist/loader-5CA3O5GM.js +0 -2
  216. package/dist/loader-6IHUCPVO.js +0 -2
  217. package/dist/memory-extract-AENFVVO5.js +0 -2
  218. package/dist/processes-UIC4XXA6.js +0 -2
  219. package/dist/program-WVAVJFND.js +0 -2
  220. package/dist/providers-L27BJ5A6.js +0 -2
  221. package/dist/query-HZYYRCLU.js +0 -2
  222. package/dist/recipes-EPLE2SNT.js +0 -2
  223. package/dist/registry-T36ZIAWV.js +0 -2
  224. package/dist/runner-BFYDWPUO.js +0 -2
  225. package/dist/scheduler-GH7TIKYS.js +0 -2
  226. package/dist/store-I3HEWVLQ.js +0 -2
  227. package/dist/timers-5LPZN55J.js +0 -2
  228. package/dist/types-VECTMYTO.js +0 -2
  229. package/dist/types-Y6ZCZ52Z.js +0 -2
  230. package/dist/wiki-M6YNRFC4.js +0 -2
  231. /package/dist/{App-6Y7B3YQH.js.map → App-U52IPUK2.js.map} +0 -0
  232. /package/dist/{ChatApp-KIEUJ2DS.js.map → ChatApp-XP4OMHPL.js.map} +0 -0
  233. /package/dist/{agent-QGZIAZMY.js.map → agent-V2CA4TXD.js.map} +0 -0
  234. /package/dist/{architect-SEPWMWS4.js.map → article-fields-L4D2BOGY.js.map} +0 -0
  235. /package/dist/{audit-JU3HWISB.js.map → audit-4245CX6P.js.map} +0 -0
  236. /package/dist/{builtin-FRKAQMFS.js.map → builtin-B67DMJ3M.js.map} +0 -0
  237. /package/dist/{chunk-4YCH6IZV.js.map → calibration-ILS5OI44.js.map} +0 -0
  238. /package/dist/{client-GGVVLOOF.js.map → chunk-FIOB3MEX.js.map} +0 -0
  239. /package/dist/{config-7FSZFRTQ.js.map → client-PX25MD47.js.map} +0 -0
  240. /package/dist/{compaction-NWEMOSHA.js.map → compaction-FD4R23HZ.js.map} +0 -0
  241. /package/dist/{config-mutator-POCOYYJ4.js.map → config-HL5F36V4.js.map} +0 -0
  242. /package/dist/{debug-N3B5NVJU.js.map → config-mutator-HBX53OMJ.js.map} +0 -0
  243. /package/dist/{doctor-DHLBEKA4.js.map → debug-3MEUO2P2.js.map} +0 -0
  244. /package/dist/{heal-HZ3EQD52.js.map → doctor-PBAH6XL3.js.map} +0 -0
  245. /package/dist/{loader-5CA3O5GM.js.map → heal-FVLBHYP7.js.map} +0 -0
  246. /package/dist/{ingest-whatsapp-LZE62INA.js.map → ingest-whatsapp-NS4OWDOU.js.map} +0 -0
  247. /package/dist/{loader-6IHUCPVO.js.map → loader-4ZGTK7LD.js.map} +0 -0
  248. /package/dist/{memory-extract-AENFVVO5.js.map → loader-BOKE5M5S.js.map} +0 -0
  249. /package/dist/{processes-UIC4XXA6.js.map → memory-extract-F5ZHWY5E.js.map} +0 -0
  250. /package/dist/{migrate-v2-QO7SLKIM.js.map → migrate-v2-V4WN2VVM.js.map} +0 -0
  251. /package/dist/{program-WVAVJFND.js.map → processes-WU23U2OJ.js.map} +0 -0
  252. /package/dist/{providers-L27BJ5A6.js.map → program-YGXVCV2Y.js.map} +0 -0
  253. /package/dist/{projects-api-KRME2YKZ.js.map → projects-api-QE6L65SK.js.map} +0 -0
  254. /package/dist/{projects-mutate-3NHVG437.js.map → projects-mutate-MY7JTLSZ.js.map} +0 -0
  255. /package/dist/{query-HZYYRCLU.js.map → providers-KO2DODIO.js.map} +0 -0
  256. /package/dist/{recipes-EPLE2SNT.js.map → query-ZKTFJMNE.js.map} +0 -0
  257. /package/dist/{registry-T36ZIAWV.js.map → recipes-7S4MC5OJ.js.map} +0 -0
  258. /package/dist/{runner-BFYDWPUO.js.map → registry-PSZDPFZK.js.map} +0 -0
  259. /package/dist/{rotation-memo-B3EXXSEH.js.map → rotation-memo-7T75BLEX.js.map} +0 -0
  260. /package/dist/{scheduler-GH7TIKYS.js.map → runner-YRBVNYTY.js.map} +0 -0
  261. /package/dist/{store-I3HEWVLQ.js.map → scheduler-GZZAN6ZG.js.map} +0 -0
  262. /package/dist/{surface-usage-PRL6Z4U3.js.map → seat-VZ7AVO27.js.map} +0 -0
  263. /package/dist/{timers-5LPZN55J.js.map → store-MCOA6N4F.js.map} +0 -0
  264. /package/dist/{subagent-WV7QKQ3L.js.map → subagent-2SJHSSJS.js.map} +0 -0
  265. /package/dist/{types-VECTMYTO.js.map → surface-usage-NS74AXSE.js.map} +0 -0
  266. /package/dist/{types-Y6ZCZ52Z.js.map → timers-PCB4ZIBD.js.map} +0 -0
  267. /package/dist/{wiki-M6YNRFC4.js.map → types-H5ENIUIT.js.map} +0 -0
  268. /package/dist/{workspace-setup-TBQAHYHJ.js.map → types-OIFU3KSQ.js.map} +0 -0
  269. /package/dist/{whatsapp-state-XMTCSRXU.js.map → whatsapp-state-6D6FHHD2.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/agents/runtime.ts","../src/agents/error-map.ts","../src/agents/process-registry-instance.ts","../src/agents/process-registry.ts","../src/agents/sessions.ts","../src/daemon/rate-limit.ts","../src/daemon/token-tracker.ts","../src/agents/context.ts","../src/agents/memory-store.ts","../src/agents/agent-memory.ts","../src/agents/message-queue.ts","../src/agents/patterns.ts","../src/events/bus.ts","../src/attach/types.ts","../src/attach/registry.ts","../src/attach/index.ts","../src/agents/overage-status.ts","../src/agents/claude-code-quota.ts","../src/agents/prompt-size-tracker.ts","../src/agents/registry.ts","../src/procedures/store.ts","../src/procedures/types.ts","../src/procedures/candidates.ts","../src/procedures/match.ts"],"sourcesContent":["import { execa } from \"execa\"\nimport { execFile, spawn } from \"child_process\"\nimport { StringDecoder } from \"string_decoder\"\nimport { mkdtempSync, readFileSync, rmSync } from \"fs\"\nimport { join } from \"path\"\nimport { tmpdir } from \"os\"\nimport { friendlyModelError, renderFriendlyError, type FriendlyError } from \"./error-map\"\n\n/** Small helper: render a raw error string into both the operator-facing\n * one-line message and the typed kind discriminator. Call sites that hit\n * `renderFriendlyError(friendlyModelError(...))` should use this so the\n * kind survives onto the response (see AgentResponse.errorKind). */\nfunction buildErrorEnvelope(raw: string | undefined | null): { error: string; errorKind: FriendlyError[\"kind\"] } {\n const friendly = friendlyModelError(raw)\n if (friendly.kind === \"invalid_request\" || friendly.kind === \"unknown\") {\n const sample = (raw || \"\").replace(/\\s+/g, \" \").slice(0, 800)\n if (sample) process.stderr.write(`[agentx][model-error][${friendly.kind}] raw=${sample}\\n`)\n }\n return { error: renderFriendlyError(friendly), errorKind: friendly.kind }\n}\nimport { buildAgentEnv, stripAnthropicApiKey } from \"@/utils/workspace-env\"\nimport type { AgentDef } from \"@/daemon/config\"\nimport { getProcessRegistry } from \"./process-registry-instance\"\nimport { RegistryCapExceeded, type ProcessKey } from \"./process-registry\"\nimport { effectiveMcpConfig } from \"./codegraph-bootstrap\"\n\n// --- Agent execution runtime ---\n// Routes agent tasks to the correct execution tier:\n// - claude-code: spawns claude CLI (subscription, full features)\n// - codex-cli: spawns codex CLI (OpenAI Codex, full CLI agent)\n// - opencode: spawns OpenCode CLI (uses the operator's configured providers)\n// - sdk: uses Claude Agent SDK (API key, programmatic)\n// - orchestrator: uses agentx's own agentic loop (any provider)\n\n/** Message shown to operators when the `claude` CLI isn't on PATH. Hits both\n * the streaming and non-streaming paths — without this we used to surface\n * \"Claude Code exited with code unknown\" / \"code ENOENT\", neither of which\n * hints at the actual fix. */\nfunction claudeMissingMessage(): string {\n return (\n `Claude Code CLI not found on PATH. Install it before starting an agent: ` +\n `https://docs.claude.com/en/docs/claude-code . On Linux/macOS: npm i -g @anthropic-ai/claude-code ` +\n `then verify with claude --version. If you don't intend to use the claude-code engine, set the agent's tier to \"sdk\" or \"orchestrator\" in agentx.json.`\n )\n}\n\nfunction codexMissingMessage(): string {\n return (\n `Codex CLI not found on PATH. Install it before starting an agent: ` +\n `npm i -g @openai/codex then verify with codex --version. ` +\n `If you don't intend to use the codex-cli engine, set the agent's tier to \"claude-code\", \"sdk\", or \"orchestrator\" in agentx.json.`\n )\n}\n\nfunction openCodeMissingMessage(): string {\n return (\n `OpenCode CLI not found on PATH. Install it from https://opencode.ai/docs/ and verify with opencode --version. ` +\n `If you don't intend to use the opencode engine, set the agent's tier to \"claude-code\", \"codex-cli\", \"sdk\", or \"orchestrator\" in agentx.json.`\n )\n}\n\nexport interface AgentPeer {\n id: string\n name: string\n handle?: string // e.g. \"@my_bot\"\n role?: string // from systemPrompt, first line\n}\n\nexport interface AgentTask {\n message: string\n agentId: string\n /** Correlator threaded by the workflow engine. When the engine dispatches\n * an agent as part of a state transition, this carries the run id so\n * post:response can re-enter the engine with an agentResult condition. */\n workflowRunId?: string\n /** Per-invocation model override (e.g. cron model). Falls back to agent.model. */\n model?: string\n /** Cacheable text delivered to Claude via --append-system-prompt. Typically\n * agent.systemPrompt + SOUL/IDENTITY/AGENTS.md. Passing stable content\n * here (instead of in the user-message body) keeps it inside Claude's\n * cached system prompt across session resumes. */\n systemPromptAppend?: string\n /** Per-invocation override for the context assembly strategy. When unset,\n * the registry falls back to config.session.contextStrategy. Set this\n * from benchmark harnesses that want to A/B the same request under\n * \"layered\" and \"planner\" without reloading daemon config. */\n contextStrategy?: \"layered\" | \"planner\"\n /** Upper bound on how long this task may run. Applied to mesh\n * forwarding (as fetch timeout) and local execution when the runtime\n * supports it. Workflow `agent` nodes pass this through from their\n * config.timeoutMinutes. */\n timeoutMinutes?: number\n /** Phase 1 / 6 — when set, identifies the intent-ledger decision row\n * this task corresponds to. The registry records a resolution row\n * on this decision when the task completes (success / error / mesh-\n * fallback). Active-task safety in `decideAndCommit` reads\n * intent_resolutions to clear in-flight slots; without resolution\n * writes, every dispatched decision sits in-flight forever and\n * Inv-ActiveTaskSafety becomes vacuously over-aggressive. Channel\n * adapters (gitlab, router) set this when their record*Dispatch\n * helper returns a dispatched decision. */\n intentRef?: {\n eventId: string\n decidedBy: string\n }\n /** ULID for the per-execution trace row in task_traces (improvement\n * plan #2). Allocated by registry.execute right before emitting the\n * task:started bus event so per-step capture sites in the streaming\n * parser can append rows under the same id. Optional — when unset,\n * step capture is a no-op. */\n taskId?: string\n /** Improvement plan #8 — when true, the dispatcher discards any\n * cached session for this (agent, channel, chatId) before\n * executing: the claudeSessionId is cleared (no --resume) and\n * any persistent-process handle is killed so the next dispatch\n * spawns fresh. Use from triage→sub-agent delegation paths where\n * the caller knows this is a NEW visitor on a chatId that may\n * collide with prior conversations.\n *\n * Caught in benchmark Run 3 (2026-05-04): a triage agent\n * delegated to \"lead\" for a brand-new visitor; lead's warm\n * persistent process replied with the previous visitor's\n * confirmation message. freshSession: true defends against\n * that pattern without requiring callers to invent unique\n * chatIds. */\n freshSession?: boolean\n context?: {\n channel?: string\n sender?: string\n /** Platform user ID (e.g. Telegram user ID) */\n senderId?: string\n /** Platform username (e.g. @username) */\n senderUsername?: string\n group?: string\n /** Stable chat ID for session keying (e.g. \"project:issue:123\" for GitLab) */\n chatId?: string\n /** Attached media file (image, audio, video, document) */\n mediaPath?: string\n mediaType?: string\n /** Text of the message being replied to */\n replyToText?: string\n conversationHistory?: Array<{ role: string; content: string }>\n /** This agent's own handle on the current channel */\n myHandle?: string\n /** Other available agents and their handles */\n peers?: AgentPeer[]\n /** Verified channel metadata (from adapter) */\n channelMeta?: {\n agents?: Array<{ id: string; name: string; handle?: string }>\n project?: string\n issue?: { type: string; iid: string; title: string }\n facts?: string[]\n }\n /** Path to the project root whose runbook (CLAUDE.md / AGENTS.md / etc.)\n * should be injected into the agent's system prefix for this task.\n * Set by the gitlab/github adapters when a project rule resolves a\n * `runbook:` path. Optional. */\n runbookPath?: string\n /** Override list of files to read from runbookPath. Optional. */\n runbookFiles?: string[]\n }\n}\n\nexport interface AgentResponse {\n content: string\n error?: string\n /** Typed discriminator on `error`. Lets dispatchers and workflow nodes\n * branch on the failure cause without string-matching the friendly\n * message (which is operator-facing prose, not machine input). The\n * full FriendlyError taxonomy is in error-map.ts; common ones to\n * branch on: `out_of_credits`, `rate_limit`, `auth`, `timeout`. */\n errorKind?: FriendlyError[\"kind\"]\n tokensUsed?: number\n duration?: number\n claudeSessionId?: string\n codexSessionId?: string\n opencodeSessionId?: string\n usage?: TokenUsage // Real token counts from Claude's JSON output\n /** End-of-turn context size: the LAST API call's (input + cacheRead +\n * cacheCreate) inside this turn, captured from the final assistant\n * stream event. `usage` above is CUMULATIVE across every call in the\n * agentic loop (a tool-heavy turn easily sums to millions via cache\n * reads) — only this field reflects how full the conversation context\n * actually is, so rotation decisions must use it, never `usage`.\n * Undefined on non-streaming paths (result JSON has no per-call data). */\n contextTokens?: number\n /** The model Claude actually billed for (from the CLI's init event). When\n * absent, cost reporting should fall back to the model override / agent\n * config. Knowing the billed model is what makes cache-aware pricing\n * trustworthy — a task that ran sonnet but was logged as opus would\n * overstate cost by ~5×. */\n billedModel?: string\n /** Set when the task was forwarded to a mesh peer that hosted the\n * requested agent (the local registry didn't have it). Name of the\n * peer that actually ran it. */\n viaMesh?: string\n /** Set when a live Claude Code session attached to this identity answered\n * the task instead of a spawned provider (attach mode). Claude Code\n * session id of the terminal that wrote the reply. */\n viaAttachedSession?: string\n}\n\n/** Callback for streaming text deltas */\nexport type StreamCallback = (delta: string, fullText: string) => void\n\n/**\n * Build the prompt from agent config + task context + conversation history.\n */\nfunction buildPrompt(agent: AgentDef, task: AgentTask, historyContext?: string): string {\n const parts: string[] = []\n\n // NOTE: agent.systemPrompt is no longer injected into the user-message body —\n // it's now delivered via `--append-system-prompt` so Claude's prompt cache\n // retains it across turns instead of us paying cache-create for it on every\n // new session. If a per-task override is needed, use task.context.systemPrompt\n // (which buildClaudeArgs also forwards to --append-system-prompt).\n\n // Inject environment context — skip if historyContext is provided (context engine handles it)\n if (task.context && !historyContext) {\n const ctx = task.context\n const envLines: string[] = [\"\", \"[Environment]\"]\n const isGitLab = ctx.channel === \"gitlab\" || ctx.channel?.startsWith(\"webhook:\")\n const isTelegram = ctx.channel === \"telegram\"\n\n if (ctx.channel) envLines.push(`Channel: ${ctx.channel}`)\n if (ctx.group) envLines.push(`Group: ${ctx.group}`)\n if (ctx.sender) envLines.push(`Message from: ${ctx.sender}`)\n if (ctx.myHandle) envLines.push(`Your handle on this channel: ${ctx.myHandle}`)\n\n if (isGitLab) {\n envLines.push(\"\")\n envLines.push(\"[IMPORTANT: You are responding to a GitLab comment/event]\")\n envLines.push(\"- Keep it short — 3-5 lines for the main message. Humans scan, not read\")\n envLines.push(\"- Lead with the result or action, not a narration of what you plan to do\")\n envLines.push(\"- Use <details><summary>Details</summary>\\\\n\\\\ncontent\\\\n</details> for verbose output (logs, full commands, step-by-step)\")\n envLines.push(\"- Do NOT mention Telegram handles — they don't work on GitLab\")\n envLines.push(\"- Do NOT try to delegate to other agents — reply directly\")\n envLines.push(\"- Reference issues with #IID and MRs with !IID\")\n }\n\n if (isTelegram && ctx.peers?.length) {\n envLines.push(\"\")\n envLines.push(\"[Team — other agents you can mention to delegate or collaborate]\")\n for (const peer of ctx.peers) {\n const handle = peer.handle ? ` (mention: ${peer.handle})` : \"\"\n const role = peer.role ? ` — ${peer.role}` : \"\"\n envLines.push(`• ${peer.name}${handle}${role}`)\n }\n envLines.push(\"\")\n envLines.push(\"To involve another agent, mention their handle in your response and they will automatically see it and reply.\")\n }\n\n parts.push(envLines.join(\"\\n\"))\n }\n\n // Reply-to context (when user replies to a specific message)\n if (task.context?.replyToText) {\n parts.push(\"\")\n parts.push(`[Replying to]: ${task.context.replyToText}`)\n }\n\n // Attached media\n if (task.context?.mediaPath) {\n parts.push(\"\")\n parts.push(`[Attached file: ${task.context.mediaPath}]`)\n parts.push(`[File type: ${task.context.mediaType || \"unknown\"}]`)\n if (task.context.mediaType?.startsWith(\"image/\")) {\n parts.push(\"Please read/view this image file and describe or respond to it.\")\n } else if (task.context.mediaType?.startsWith(\"audio/\")) {\n parts.push(\"Please transcribe this audio file and respond to its content.\")\n } else if (task.context.mediaType?.startsWith(\"video/\")) {\n parts.push(\"A video file is attached. Describe what you can determine about it.\")\n } else {\n parts.push(\"Please read this file and respond based on its content.\")\n }\n }\n\n // Inject conversation history for session continuity\n if (historyContext) {\n parts.push(\"\")\n parts.push(historyContext)\n }\n\n parts.push(\"\")\n parts.push(task.message)\n\n return parts.join(\"\\n\")\n}\n\n/**\n * Build CLI args for claude command.\n */\nfunction buildClaudeArgs(\n agent: AgentDef,\n prompt: string,\n streaming: boolean,\n resumeSessionId?: string,\n modelOverride?: string,\n /** Static per-agent preamble delivered via --append-system-prompt. Content\n * here is Claude-cached across turns as part of the system prompt, so\n * moving stable content (agent.systemPrompt + SOUL/IDENTITY/AGENTS.md) out\n * of the user-message body and into this arg avoids paying cache-create\n * for it on every new session. */\n systemPromptAppend?: string,\n): string[] {\n const args: string[] = [\n \"-p\", prompt,\n \"--output-format\", streaming ? \"stream-json\" : \"json\",\n ]\n\n // stream-json requires --verbose in recent Claude Code versions\n if (streaming) {\n args.push(\"--verbose\")\n }\n\n // Resume existing Claude session for conversation continuity\n if (resumeSessionId) {\n args.push(\"--resume\", resumeSessionId)\n }\n\n const model = modelOverride || agent.model\n if (model) {\n args.push(\"--model\", model)\n }\n\n if (agent.permissionMode === \"bypassPermissions\") {\n args.push(\"--dangerously-skip-permissions\")\n }\n\n if (systemPromptAppend && systemPromptAppend.trim().length > 0) {\n args.push(\"--append-system-prompt\", systemPromptAppend)\n }\n\n return args\n}\n\nfunction buildCodexPrompt(prompt: string, systemPromptAppend?: string): string {\n if (!systemPromptAppend || systemPromptAppend.trim().length === 0) return prompt\n return `[System]\\n${systemPromptAppend.trim()}\\n\\n[User]\\n${prompt}`\n}\n\nfunction buildOpenCodeArgs(\n agent: AgentDef,\n prompt: string,\n modelOverride?: string,\n systemPromptAppend?: string,\n resumeSessionId?: string,\n): string[] {\n const args = [\"run\", \"--format\", \"json\"]\n const model = modelOverride || agent.model\n if (model) args.push(\"--model\", model)\n if (resumeSessionId) args.push(\"--session\", resumeSessionId)\n if (agent.permissionMode === \"bypassPermissions\") args.push(\"--auto\")\n args.push(buildCodexPrompt(prompt, systemPromptAppend))\n return args\n}\n\nfunction extractOpenCodeError(event: any): string | undefined {\n if (!event || typeof event !== \"object\" || event.type !== \"error\") return undefined\n const value = event.error?.message ?? event.error ?? event.message ?? event.part?.error?.message\n return typeof value === \"string\" && value.trim() ? value.trim() : undefined\n}\n\nfunction tomlString(value: string): string {\n return JSON.stringify(value)\n}\n\nfunction buildCodexAgentxMcpArgs(): string[] {\n const cli = process.argv[1] || \"dist/cli.js\"\n return [\n \"-c\", `mcp_servers.agentx.command=${tomlString(process.execPath)}`,\n \"-c\", `mcp_servers.agentx.args=[${[cli, \"serve\", \"--stdio\", \"--cwd\", process.cwd()].map(tomlString).join(\",\")}]`,\n ]\n}\n\nfunction buildCodexArgs(\n agent: AgentDef,\n prompt: string,\n streaming: boolean,\n modelOverride?: string,\n systemPromptAppend?: string,\n outputFile?: string,\n resumeSessionId?: string,\n): string[] {\n const args: string[] = resumeSessionId\n ? [\"exec\", \"resume\"]\n : [\"exec\"]\n\n args.push(\n \"--ignore-user-config\",\n \"--skip-git-repo-check\",\n ...buildCodexAgentxMcpArgs(),\n )\n\n if (!resumeSessionId) args.push(\"--color\", \"never\")\n\n const model = modelOverride || agent.model\n if (model) args.push(\"--model\", model)\n\n if (agent.permissionMode === \"bypassPermissions\") {\n args.push(\"--dangerously-bypass-approvals-and-sandbox\")\n } else if (!resumeSessionId) {\n args.push(\"--sandbox\", \"workspace-write\")\n }\n\n if (streaming || resumeSessionId || outputFile) args.push(\"--json\")\n if (outputFile) args.push(\"--output-last-message\", outputFile)\n\n if (resumeSessionId) args.push(resumeSessionId)\n args.push(buildCodexPrompt(prompt, systemPromptAppend))\n return args\n}\n\nfunction buildRuntimeEnv(agent: AgentDef, task: AgentTask): NodeJS.ProcessEnv {\n const env = buildAgentEnv(agent.workspace)\n const home = env.HOME || process.env.HOME\n const pathParts = [\n env.PATH || \"\",\n home ? `${home}/.bun/bin` : \"\",\n home ? `${home}/.local/bin` : \"\",\n home ? `${home}/.opencode/bin` : \"\",\n \"/opt/homebrew/bin\",\n \"/usr/local/bin\",\n ].filter(Boolean)\n env.PATH = Array.from(new Set(pathParts.flatMap((p) => p.split(\":\")).filter(Boolean))).join(\":\")\n env.AGENTX_AGENT_ID = task.agentId\n if (task.context?.channel) env.AGENTX_CHANNEL = task.context.channel\n if (task.context?.chatId) env.AGENTX_CHAT_ID = task.context.chatId\n return env\n}\n\nfunction buildOpenCodeEnv(agent: AgentDef, task: AgentTask): NodeJS.ProcessEnv {\n const env = buildRuntimeEnv(agent, task)\n let config: Record<string, any> = {}\n if (env.OPENCODE_CONFIG_CONTENT) {\n try { config = JSON.parse(env.OPENCODE_CONFIG_CONTENT) } catch { /* replace invalid inherited content */ }\n }\n\n const mcp: Record<string, any> = { ...(config.mcp || {}) }\n for (const [name, server] of Object.entries(agent.mcp || {}) as Array<[string, any]>) {\n mcp[name] = server.type === \"http\"\n ? { type: \"remote\", url: server.url, headers: server.headers }\n : { type: \"local\", command: [server.command, ...(server.args || [])], environment: server.env }\n }\n\n const cli = process.argv[1] || \"dist/cli.js\"\n mcp.agentx = {\n type: \"local\",\n command: [process.execPath, cli, \"serve\", \"--stdio\", \"--cwd\", process.cwd()],\n }\n env.OPENCODE_CONFIG_CONTENT = JSON.stringify({ ...config, mcp })\n return env\n}\n\nfunction extractCodexTextFromEvent(event: any): string | undefined {\n if (!event || typeof event !== \"object\") return undefined\n if (typeof event.delta === \"string\") return event.delta\n if (typeof event.text_delta === \"string\") return event.text_delta\n if (typeof event.message === \"string\" && /message|delta/i.test(String(event.type || \"\"))) return event.message\n if (typeof event.text === \"string\" && /message|delta|response/i.test(String(event.type || \"\"))) return event.text\n\n const item = event.item || event.msg || event.message\n if (item?.type === \"agent_message\" && typeof item.text === \"string\") return item.text\n if (typeof item?.text === \"string\" && /message|delta|response|item/i.test(String(event.type || \"\"))) return item.text\n const content = item?.content\n if (Array.isArray(content)) {\n const text = content\n .map((block: any) => typeof block?.text === \"string\" ? block.text : \"\")\n .join(\"\")\n if (text) return text\n }\n return undefined\n}\n\nfunction firstNumber(...values: unknown[]): number {\n for (const value of values) {\n if (typeof value === \"number\" && Number.isFinite(value)) return value\n }\n return 0\n}\n\nfunction extractCodexUsage(value: any, depth = 0): TokenUsage | undefined {\n if (!value || typeof value !== \"object\" || depth > 4) return undefined\n const candidate = value.usage || value.token_usage || value.tokenUsage || value.metrics?.usage\n if (candidate && typeof candidate === \"object\") {\n return {\n inputTokens: firstNumber(candidate.input_tokens, candidate.inputTokens, candidate.prompt_tokens, candidate.promptTokens),\n outputTokens: firstNumber(candidate.output_tokens, candidate.outputTokens, candidate.completion_tokens, candidate.completionTokens),\n cacheReadTokens: firstNumber(candidate.cache_read_input_tokens, candidate.cached_input_tokens, candidate.cacheReadTokens, candidate.cachedInputTokens),\n cacheCreateTokens: firstNumber(candidate.cache_creation_input_tokens, candidate.cacheCreateTokens),\n }\n }\n for (const nested of [value.msg, value.message, value.response, value.result, value.item]) {\n const usage = extractCodexUsage(nested, depth + 1)\n if (usage) return usage\n }\n return undefined\n}\n\nfunction extractCodexBilledModel(event: any): string | undefined {\n if (!event || typeof event !== \"object\") return undefined\n return (\n (typeof event.model === \"string\" && event.model) ||\n (typeof event.msg?.model === \"string\" && event.msg.model) ||\n (typeof event.message?.model === \"string\" && event.message.model) ||\n (typeof event.response?.model === \"string\" && event.response.model) ||\n undefined\n )\n}\n\nfunction extractCodexSessionId(value: any, depth = 0): string | undefined {\n if (!value || typeof value !== \"object\" || depth > 4) return undefined\n for (const key of [\"thread_id\", \"threadId\", \"session_id\", \"sessionId\", \"conversation_id\", \"conversationId\"]) {\n const candidate = value[key]\n if (typeof candidate === \"string\" && candidate.trim()) return candidate.trim()\n }\n for (const nested of [value.msg, value.message, value.response, value.result, value.item]) {\n const sessionId = extractCodexSessionId(nested, depth + 1)\n if (sessionId) return sessionId\n }\n return undefined\n}\n\nfunction collectCodexRunMetadata(stdout: string | Buffer | undefined): {\n usage?: TokenUsage\n billedModel?: string\n sessionId?: string\n apiError?: string\n} {\n const text = typeof stdout === \"string\" ? stdout : stdout?.toString(\"utf8\") || \"\"\n let usage: TokenUsage | undefined\n let billedModel: string | undefined\n let sessionId: string | undefined\n let apiError: string | undefined\n for (const line of text.split(\"\\n\")) {\n if (!line.trim()) continue\n try {\n const event = JSON.parse(line)\n usage = extractCodexUsage(event) || usage\n billedModel = extractCodexBilledModel(event) || billedModel\n sessionId = extractCodexSessionId(event) || sessionId\n apiError = extractCodexErrorMessage(event) || apiError\n } catch {\n // Non-JSON output is handled by the caller as content fallback.\n }\n }\n return { usage, billedModel, sessionId, apiError }\n}\n\n/** Extract the human-readable failure message from a codex event.\n * Codex emits failures on stdout as JSON, NOT on stderr — so when our\n * runtime fell back to stderr.trim() it was returning the benign\n * \"Reading additional input from stdin...\" log line as if it were the\n * error. This walks the two known shapes:\n * {\"type\":\"error\",\"message\":\"…\"}\n * {\"type\":\"turn.failed\",\"error\":{\"message\":\"…\"}}\n * …and unwraps any nested provider JSON inside the message so the\n * surfaced text is the actual reason (\"model X not supported when\n * using Codex with a ChatGPT account\") instead of envelope noise. */\nfunction extractCodexErrorMessage(event: any): string | undefined {\n if (!event || typeof event !== \"object\") return undefined\n if (event.type === \"error\" && typeof event.message === \"string\") {\n return unwrapCodexErrorMessage(event.message)\n }\n if (event.type === \"turn.failed\") {\n const msg = event.error?.message ?? event.error\n if (typeof msg === \"string\") return unwrapCodexErrorMessage(msg)\n }\n return undefined\n}\n\n/** Codex sometimes nests a provider JSON envelope inside the message\n * string: `{\"type\":\"error\",\"status\":400,\"error\":{\"message\":\"…\"}}`.\n * Try to peel one layer; fall back to the raw string. */\nfunction unwrapCodexErrorMessage(raw: string): string {\n const trimmed = raw.trim()\n if (trimmed.startsWith(\"{\")) {\n try {\n const inner = JSON.parse(trimmed)\n const nested = inner?.error?.message ?? inner?.message\n if (typeof nested === \"string\" && nested.trim()) return nested.trim()\n } catch { /* leave as-is */ }\n }\n return trimmed\n}\n\n/**\n * Improvement plan #4 — return the resolved permission posture for an\n * agent, including the actual CLI flag that gets passed (or doesn't).\n * Lets operators answer \"what permissions does my agent actually run\n * with?\" without reading the runtime source. Surfaced on\n * `GET /agents/:id` and logged at every claude-code spawn so the\n * operator can grep journalctl.\n */\nexport interface ResolvedPermission {\n /** Raw value from agentx.json, e.g. \"default\" / \"bypassPermissions\" / \"plan\" / \"auto\" / etc. */\n configured: string\n /** Whether the spawn includes --dangerously-skip-permissions. */\n skipPermissions: boolean\n /** Best-effort one-line summary suitable for journalctl. */\n summary: string\n}\n\nexport function resolvePermission(agent: AgentDef): ResolvedPermission {\n const configured = agent.permissionMode || \"default\"\n const skipPermissions = configured === \"bypassPermissions\"\n const summary = skipPermissions\n ? `bypassPermissions (--dangerously-skip-permissions)`\n : `${configured} (no skip-permissions flag)`\n return { configured, skipPermissions, summary }\n}\n\n/**\n * Single-line spawn audit that lands in stderr per dispatch. Lets\n * operators verify what every claude subprocess was actually invoked\n * with — specifically the permission flag, which is the load-bearing\n * security knob. Quiet on info paths (no flooding journalctl) — just\n * one line per spawn, with the agentId so `journalctl | grep <id>` is\n * useful.\n */\nexport function logClaudeSpawn(\n agentId: string,\n agent: AgentDef,\n modelOverride: string | undefined,\n resumeSessionId: string | undefined,\n via: \"spawn\" | \"stream\" | \"persistent\",\n): void {\n const perm = resolvePermission(agent)\n const model = modelOverride || agent.model || \"<default>\"\n const resume = resumeSessionId ? ` resume=${resumeSessionId.slice(0, 8)}…` : \"\"\n // Use process.stderr directly so this surfaces under journalctl even\n // when an agent's logger is detached. Single line, prefixed for grep.\n process.stderr.write(`[claude-spawn] agent=${agentId} via=${via} model=${model} perm=${perm.summary}${resume}\\n`)\n}\n\n/**\n * Parse JSON output from `claude -p --output-format json`.\n * Returns the text result and session_id.\n */\nexport interface TokenUsage {\n inputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cacheCreateTokens: number\n}\n\n/**\n * When the Claude Code CLI returns exit=0 but the run itself errored (e.g.\n * out-of-credits mid-session), it emits a top-level JSON object with\n * `is_error: true` and the raw API error in `result`. Detect + extract so we\n * can run the same friendly-error translator we use for stderr.\n * Returns null when the output is a normal success.\n */\nfunction extractClaudeIsError(stdout: string): string | null {\n try {\n const data = JSON.parse(stdout)\n if (data && data.is_error && typeof data.result === \"string\") return data.result\n if (data && data.error && typeof data.error === \"string\") return data.error\n } catch { /* not JSON — fall through */ }\n return null\n}\n\nfunction parseClaudeJsonOutput(stdout: string): { text: string; sessionId?: string; usage?: TokenUsage; billedModel?: string } {\n try {\n const data = JSON.parse(stdout)\n const usage = data.usage ? {\n inputTokens: data.usage.input_tokens || 0,\n outputTokens: data.usage.output_tokens || 0,\n cacheReadTokens: data.usage.cache_read_input_tokens || 0,\n cacheCreateTokens: data.usage.cache_creation_input_tokens || 0,\n } : undefined\n\n // Claude Code's --output-format json response carries the actual billed\n // model at `model` (or nested under `message.model` depending on CLI ver).\n const billedModel: string | undefined =\n (typeof data.model === \"string\" && data.model) ||\n (typeof data.message?.model === \"string\" && data.message.model) ||\n undefined\n\n return {\n text: data.result || data.content || \"\",\n sessionId: data.session_id,\n usage,\n billedModel,\n }\n } catch {\n return { text: stdout }\n }\n}\n\n/**\n * Execute a task using the Claude Code CLI (tier: \"claude-code\").\n * Non-streaming — waits for full response.\n */\nexport async function executeClaudeCode(\n agent: AgentDef,\n task: AgentTask,\n historyContext?: string,\n resumeSessionId?: string,\n abortSignal?: AbortSignal,\n): Promise<AgentResponse> {\n const start = Date.now()\n // Always inject context (landscape, rules, channel info) even on resume.\n // Claude CLI --resume carries its own conversation history, but the\n // landscape + rules must be fresh so the agent sees capability updates.\n const prompt = buildPrompt(agent, task, historyContext)\n const args = buildClaudeArgs(agent, prompt, false, resumeSessionId, task.model, task.systemPromptAppend)\n logClaudeSpawn(task.agentId, agent, task.model, resumeSessionId, \"spawn\")\n\n // If the caller already aborted before we spawned, short-circuit so we\n // don't spend tokens on a doomed run.\n if (abortSignal?.aborted) {\n return { content: \"\", error: \"task cancelled by operator\", duration: Date.now() - start }\n }\n\n let abortReason: string | undefined\n try {\n const timeoutMs = Math.max(60_000, (agent.maxExecutionMinutes ?? 20) * 60_000)\n const { stdout, stderr, exitCode, killed } = await new Promise<{ stdout: string; stderr: string; exitCode: number | string; killed: boolean }>((resolve) => {\n const childEnv = stripAnthropicApiKey(buildAgentEnv(agent.workspace))\n let killed = false\n const proc = execFile(\"claude\", args, {\n cwd: agent.workspace,\n timeout: timeoutMs,\n maxBuffer: 10 * 1024 * 1024,\n env: { ...childEnv, HOME: childEnv.HOME || \"/home/\" + (childEnv.USER || \"user\") },\n }, (error, stdout, stderr) => {\n // Always resolve — we handle errors ourselves based on stdout/stderr\n resolve({\n stdout: stdout || \"\",\n stderr: stderr || \"\",\n exitCode: error ? (error as any).code ?? 1 : 0,\n killed,\n })\n })\n // Close stdin immediately. Claude CLI otherwise waits 3s for input then\n // proceeds with a warning; in cron retries that cascade invalidates the\n // prompt cache between attempts (5× input cost on cache-create).\n proc.stdin?.end()\n // Operator-initiated cancellation. SIGTERM first; if the child is still\n // alive after 3s, escalate to SIGKILL. The execFile callback fires when\n // the child exits, which resolves the outer promise.\n if (abortSignal) {\n const onAbort = () => {\n killed = true\n abortReason = (abortSignal.reason as any)?.message || (typeof abortSignal.reason === \"string\" ? abortSignal.reason : \"task cancelled by operator\")\n try { proc.kill(\"SIGTERM\") } catch { /* */ }\n setTimeout(() => { try { if (!proc.killed) proc.kill(\"SIGKILL\") } catch { /* */ } }, 3000).unref?.()\n }\n if (abortSignal.aborted) onAbort()\n else abortSignal.addEventListener(\"abort\", onAbort, { once: true })\n }\n })\n\n if (killed) {\n return {\n content: \"\",\n error: abortReason || \"task cancelled by operator\",\n errorKind: \"cancelled\",\n duration: Date.now() - start,\n }\n }\n\n if (!stdout && exitCode !== 0) {\n // exit 143 = 128+SIGTERM → our own `timeout` killed the process. Surface\n // that as a recognizable \"timed out after Xm\" message so operators can\n // bump agent.maxExecutionMinutes instead of guessing at an opaque 143.\n let errMsg: string\n if (exitCode === 143) {\n errMsg = `Claude Code timed out after ${Math.round(timeoutMs / 60_000)}m (SIGTERM). Bump agent.maxExecutionMinutes for \"${agent.name || \"this agent\"}\" if tasks need longer.`\n } else if (exitCode === \"ENOENT\" || /ENOENT|spawn claude/i.test(stderr || \"\")) {\n errMsg = claudeMissingMessage()\n } else {\n errMsg = stderr?.trim() || `Claude Code exited with code ${exitCode}`\n }\n return {\n content: \"\",\n ...buildErrorEnvelope(errMsg),\n duration: Date.now() - start,\n }\n }\n\n // Claude Code sometimes exits 0 but embeds the API error in stdout's\n // \"result\" field when `is_error` is set. Translate that too.\n const apiErrorInStdout = extractClaudeIsError(stdout)\n if (apiErrorInStdout) {\n return {\n content: \"\",\n ...buildErrorEnvelope(apiErrorInStdout),\n duration: Date.now() - start,\n }\n }\n\n const parsed = parseClaudeJsonOutput(stdout)\n\n return {\n content: parsed.text,\n duration: Date.now() - start,\n claudeSessionId: parsed.sessionId,\n usage: parsed.usage,\n billedModel: parsed.billedModel,\n }\n } catch (error: any) {\n console.error(`[runtime] execFile threw: ${error.message}`)\n return {\n content: \"\",\n error: error.message || \"Claude Code failed\",\n duration: Date.now() - start,\n }\n }\n}\n\n/**\n * Execute with streaming — calls onDelta with text chunks as they arrive.\n * Uses claude --output-format stream-json to get real-time output.\n */\nexport async function executeClaudeCodeStreaming(\n agent: AgentDef,\n task: AgentTask,\n onDelta: StreamCallback,\n historyContext?: string,\n resumeSessionId?: string,\n onEvent?: (event: any) => void,\n abortSignal?: AbortSignal,\n): Promise<AgentResponse> {\n const start = Date.now()\n if (abortSignal?.aborted) {\n return { content: \"\", error: \"task cancelled by operator\", duration: Date.now() - start }\n }\n // Always inject context (landscape, rules, channel info) even on resume.\n // Claude CLI --resume carries its own conversation history, but the\n // landscape + rules must be fresh so the agent sees capability updates.\n const prompt = buildPrompt(agent, task, historyContext)\n const args = buildClaudeArgs(agent, prompt, true, resumeSessionId, task.model, task.systemPromptAppend)\n logClaudeSpawn(task.agentId, agent, task.model, resumeSessionId, \"stream\")\n\n let fullText = \"\"\n // Capture model + usage + session id from the stream events — Claude Code\n // emits a system-init event up front (with the billed model) and a terminal\n // result event with the full usage accounting. Without these we can't\n // attribute cost correctly for streamed tasks.\n let streamBilledModel: string | undefined\n let streamUsage: TokenUsage | undefined\n let streamSessionId: string | undefined\n /** Last assistant event's per-call context size — see AgentResponse.contextTokens. */\n let streamContextTokens: number | undefined\n /** If the terminal `result` event carries is_error, we stash it here and\n * surface the translated message instead of treating `result` as agent text. */\n let streamApiError: string | undefined\n\n try {\n const streamTimeoutMs = Math.max(60_000, (agent.maxExecutionMinutes ?? 20) * 60_000)\n const spawnEnv = stripAnthropicApiKey(buildAgentEnv(agent.workspace))\n const proc = execa(\"claude\", args, {\n cwd: agent.workspace,\n timeout: streamTimeoutMs,\n reject: false,\n env: spawnEnv,\n extendEnv: false,\n // Don't buffer — we'll read stdout line by line\n buffer: false,\n // Close stdin so Claude CLI doesn't wait 3s for input (see executeClaudeCode).\n stdin: \"ignore\",\n })\n\n // Operator cancellation. SIGTERM → SIGKILL escalation after 3s; we mark\n // the run as cancelled so the post-await result-mapping below surfaces a\n // clean \"task cancelled\" error instead of a garbled \"killed by signal\".\n let cancelled = false\n let cancelReason: string | undefined\n if (abortSignal) {\n const onAbort = () => {\n cancelled = true\n cancelReason = (abortSignal.reason as any)?.message || (typeof abortSignal.reason === \"string\" ? abortSignal.reason : \"task cancelled by operator\")\n try { proc.kill(\"SIGTERM\") } catch { /* */ }\n setTimeout(() => { try { proc.kill(\"SIGKILL\") } catch { /* */ } }, 3000).unref?.()\n }\n if (abortSignal.aborted) onAbort()\n else abortSignal.addEventListener(\"abort\", onAbort, { once: true })\n }\n\n // Parse stream-json output line by line.\n // StringDecoder buffers partial UTF-8 sequences across chunk\n // boundaries; plain Buffer.toString() does not, so a chunk that\n // ends mid-character (Arabic = 2 bytes, common emoji = 4 bytes)\n // produced replacement chars or dropped letters in the agent's\n // outbound text. The user reported \"letters missing from words\"\n // on WhatsApp specifically — Arabic + emoji-heavy traffic is\n // exactly the workload that exposes this. Hoisted out of the if\n // block so the post-`await proc` flush can call decoder.end().\n let lineBuffer = \"\"\n const decoder = new StringDecoder(\"utf8\")\n if (proc.stdout) {\n proc.stdout.on(\"data\", (chunk: Buffer) => {\n lineBuffer += decoder.write(chunk as Buffer)\n const lines = lineBuffer.split(\"\\n\")\n lineBuffer = lines.pop() || \"\"\n\n for (const line of lines) {\n if (!line.trim()) continue\n try {\n const event = JSON.parse(line)\n\n // Surface every parsed event for observability (dashboard streaming).\n // Best-effort — never let a subscriber crash the runtime.\n if (onEvent) {\n try { onEvent(event) } catch { /* */ }\n }\n\n // Every \"assistant\" event is a full API message envelope whose\n // usage covers THAT call only — input + cache tokens ≈ the\n // context size of the request that produced it. The last one\n // seen is the end-of-turn context size (rotation metric).\n if (event.type === \"assistant\" && event.message?.usage) {\n const u = event.message.usage\n const callContext =\n (u.input_tokens || 0) +\n (u.cache_read_input_tokens || 0) +\n (u.cache_creation_input_tokens || 0)\n if (callContext > 0) streamContextTokens = callContext\n }\n\n // Claude stream-json emits different event types\n // \"assistant\" messages with content contain the text\n if (event.type === \"assistant\" && event.message?.content) {\n for (const block of event.message.content) {\n if (block.type === \"text\" && block.text) {\n const delta = block.text.slice(fullText.length)\n if (delta) {\n fullText = block.text\n onDelta(delta, fullText)\n }\n }\n }\n }\n\n // \"content_block_delta\" for streaming text\n if (event.type === \"content_block_delta\" && event.delta?.text) {\n fullText += event.delta.text\n onDelta(event.delta.text, fullText)\n }\n\n // \"result\" event contains final text (or an API error, when is_error).\n if (event.type === \"result\" && event.result) {\n if (event.is_error && typeof event.result === \"string\") {\n streamApiError = event.result\n } else {\n const resultText = typeof event.result === \"string\"\n ? event.result\n : event.result\n if (typeof resultText === \"string\" && resultText.length > fullText.length) {\n const delta = resultText.slice(fullText.length)\n fullText = resultText\n if (delta) onDelta(delta, fullText)\n }\n }\n // Final event also carries the authoritative usage + model + session.\n if (event.usage) {\n streamUsage = {\n inputTokens: event.usage.input_tokens || 0,\n outputTokens: event.usage.output_tokens || 0,\n cacheReadTokens: event.usage.cache_read_input_tokens || 0,\n cacheCreateTokens: event.usage.cache_creation_input_tokens || 0,\n }\n }\n if (typeof event.model === \"string\") streamBilledModel = event.model\n if (typeof event.session_id === \"string\") streamSessionId = event.session_id\n }\n\n // System-init event (first thing the CLI emits) carries the model\n // it's about to invoke — capture early so we have it even if the\n // run errors before the terminal result.\n //\n // Do NOT capture session_id from init: when --resume <X> is\n // passed, init's session_id echoes X back (the resumed-from ID),\n // not the new ID Claude rolls into for THIS turn. Persisting X\n // means the next turn re-resumes from before this turn's progress\n // — exactly the \"wrong-message resume\" symptom. Only `result`\n // carries the authoritative new session_id (line above).\n if (event.type === \"system\" && event.subtype === \"init\") {\n if (typeof event.model === \"string\") streamBilledModel = event.model\n if (process.env.AGENTX_DEBUG_CLAUDE_ENV === \"1\") {\n process.stderr.write(`[claude-init] agent=${task.agentId} apiKeySource=${event.apiKeySource} version=${event.claude_code_version} model=${event.model} permMode=${event.permissionMode}\\n`)\n }\n }\n } catch {\n // Not JSON — could be raw text output, append it\n if (line.trim() && !line.startsWith(\"{\")) {\n fullText += line + \"\\n\"\n onDelta(line + \"\\n\", fullText)\n }\n }\n }\n })\n }\n\n const result = await proc\n\n // Flush any partial UTF-8 sequence still buffered in the decoder.\n // The stream-json output normally ends with a newline so this is a\n // no-op, but it keeps the channel honest if the producer ever\n // exits mid-character.\n const tail = decoder.end()\n if (tail) lineBuffer += tail\n\n // Operator cancellation wins over any partial stream — surface the\n // friendly error so callers (KPI, dashboard, channel ack) record it\n // distinctly from a timeout or model failure.\n if (cancelled) {\n return {\n content: \"\",\n error: cancelReason || \"task cancelled by operator\",\n errorKind: \"cancelled\",\n duration: Date.now() - start,\n }\n }\n\n // If we got no streaming content, fall back to stdout\n if (!fullText && result.stdout) {\n fullText = typeof result.stdout === \"string\" ? result.stdout : \"\"\n }\n\n if (result.exitCode !== 0 && !fullText) {\n const stderr = typeof result.stderr === \"string\" ? result.stderr : \"\"\n // execa sets exitCode=undefined when the child is killed by signal —\n // surface that as a recognizable timeout message instead of leaking\n // \"exited with code undefined\" into the user's chat. Mirrors the\n // non-streaming path's exit-143 handling.\n const r = result as { exitCode?: number; signal?: string; timedOut?: boolean; failed?: boolean; code?: string; shortMessage?: string }\n let errMsg: string\n if (r.timedOut) {\n errMsg = `Claude Code timed out after ${Math.round(streamTimeoutMs / 60_000)}m. Bump agent.maxExecutionMinutes for \"${agent.name || \"this agent\"}\" if tasks need longer.`\n } else if (r.signal) {\n errMsg = stderr.trim() || `Claude Code killed by signal ${r.signal}`\n } else if (r.code === \"ENOENT\" || /ENOENT|spawn claude/i.test((r.shortMessage || \"\") + \" \" + stderr)) {\n // execa surfaces \"binary not on PATH\" as { failed: true, code: 'ENOENT',\n // exitCode: undefined } — without an explicit branch this used to leak\n // through as \"Claude Code exited with code unknown\", leaving operators\n // with no clue that the issue is a missing CLI.\n errMsg = claudeMissingMessage()\n } else {\n errMsg = stderr.trim() || `Claude Code exited with code ${r.exitCode ?? \"unknown\"}`\n }\n return {\n content: \"\",\n ...buildErrorEnvelope(errMsg),\n duration: Date.now() - start,\n }\n }\n\n if (streamApiError) {\n return {\n content: \"\",\n ...buildErrorEnvelope(streamApiError),\n duration: Date.now() - start,\n usage: streamUsage,\n contextTokens: streamContextTokens,\n billedModel: streamBilledModel,\n claudeSessionId: streamSessionId,\n }\n }\n\n return {\n content: fullText,\n duration: Date.now() - start,\n usage: streamUsage,\n contextTokens: streamContextTokens,\n billedModel: streamBilledModel,\n claudeSessionId: streamSessionId,\n }\n } catch (error: any) {\n return {\n content: fullText || \"\",\n ...buildErrorEnvelope(error.message),\n duration: Date.now() - start,\n usage: streamUsage,\n contextTokens: streamContextTokens,\n billedModel: streamBilledModel,\n claudeSessionId: streamSessionId,\n }\n }\n}\n\nexport async function executeCodexCli(\n agent: AgentDef,\n task: AgentTask,\n historyContext?: string,\n resumeSessionId?: string,\n): Promise<AgentResponse> {\n const start = Date.now()\n const prompt = buildPrompt(agent, task, historyContext)\n const tmp = mkdtempSync(join(tmpdir(), \"agentx-codex-\"))\n const outputFile = join(tmp, \"last-message.txt\")\n const args = buildCodexArgs(agent, prompt, false, task.model, task.systemPromptAppend, outputFile, resumeSessionId)\n\n try {\n const timeoutMs = Math.max(60_000, (agent.maxExecutionMinutes ?? 20) * 60_000)\n const result = await execa(\"codex\", args, {\n cwd: agent.workspace,\n timeout: timeoutMs,\n reject: false,\n env: buildRuntimeEnv(agent, task),\n extendEnv: false,\n stdin: \"ignore\",\n })\n\n // Parse the JSON event stream first so we can prefer the real API\n // error (carried on stdout) over codex's benign stderr log lines.\n const metadata = collectCodexRunMetadata(result.stdout)\n\n // Treat a recorded API error as a failure even when codex itself\n // exits 0 — auth/model/quota errors come back as \"turn.failed\"\n // events but don't always set a non-zero exit.\n if (result.exitCode !== 0 || metadata.apiError) {\n const r = result as { exitCode?: number; signal?: string; timedOut?: boolean; code?: string; shortMessage?: string }\n let errMsg: string\n if (r.timedOut) {\n errMsg = `Codex CLI timed out after ${Math.round(timeoutMs / 60_000)}m. Bump agent.maxExecutionMinutes for \"${agent.name || \"this agent\"}\" if tasks need longer.`\n } else if (metadata.apiError) {\n // Real API failure surfaced from the JSON event stream — beats\n // stderr noise like \"Reading additional input from stdin...\".\n errMsg = metadata.apiError\n } else if (r.signal) {\n errMsg = result.stderr.trim() || `Codex CLI killed by signal ${r.signal}`\n } else if (r.code === \"ENOENT\" || /ENOENT|spawn codex/i.test((r.shortMessage || \"\") + \" \" + result.stderr)) {\n errMsg = codexMissingMessage()\n } else {\n errMsg = result.stderr.trim() || result.stdout.trim() || `Codex CLI exited with code ${r.exitCode ?? \"unknown\"}`\n }\n return {\n content: \"\",\n ...buildErrorEnvelope(errMsg),\n duration: Date.now() - start,\n usage: metadata.usage,\n billedModel: metadata.billedModel,\n codexSessionId: metadata.sessionId || resumeSessionId,\n }\n }\n\n let content = \"\"\n try { content = readFileSync(outputFile, \"utf8\").trim() } catch { /* fall back below */ }\n if (!content) content = result.stdout.trim()\n return {\n content,\n duration: Date.now() - start,\n usage: metadata.usage,\n billedModel: metadata.billedModel,\n codexSessionId: metadata.sessionId || resumeSessionId,\n }\n } catch (error: any) {\n const raw = /ENOENT|spawn codex/i.test(error?.message || \"\") ? codexMissingMessage() : error?.message\n return { content: \"\", ...buildErrorEnvelope(raw || \"Codex CLI failed\"), duration: Date.now() - start }\n } finally {\n rmSync(tmp, { recursive: true, force: true })\n }\n}\n\nexport async function executeCodexCliStreaming(\n agent: AgentDef,\n task: AgentTask,\n onDelta: StreamCallback,\n historyContext?: string,\n resumeSessionId?: string,\n onEvent?: (event: any) => void,\n): Promise<AgentResponse> {\n const start = Date.now()\n const prompt = buildPrompt(agent, task, historyContext)\n const tmp = mkdtempSync(join(tmpdir(), \"agentx-codex-\"))\n const outputFile = join(tmp, \"last-message.txt\")\n const args = buildCodexArgs(agent, prompt, true, task.model, task.systemPromptAppend, outputFile, resumeSessionId)\n let fullText = \"\"\n let streamUsage: TokenUsage | undefined\n let streamBilledModel: string | undefined\n let streamSessionId: string | undefined\n let streamApiError: string | undefined\n\n try {\n const timeoutMs = Math.max(60_000, (agent.maxExecutionMinutes ?? 20) * 60_000)\n const proc = execa(\"codex\", args, {\n cwd: agent.workspace,\n timeout: timeoutMs,\n reject: false,\n env: buildRuntimeEnv(agent, task),\n extendEnv: false,\n buffer: false,\n stdin: \"ignore\",\n })\n if (onEvent) {\n try {\n onEvent({\n type: \"codex.spawned\",\n model: task.model || agent.model,\n resumeSessionId,\n })\n } catch { /* */ }\n }\n\n let lineBuffer = \"\"\n let stderrText = \"\"\n let idleTimedOut = false\n const decoder = new StringDecoder(\"utf8\")\n const stderrDecoder = new StringDecoder(\"utf8\")\n const idleTimeoutMs = Math.min(\n timeoutMs,\n Math.max(30_000, Number(process.env.AGENTX_CODEX_IDLE_TIMEOUT_MS || 180_000)),\n )\n let idleTimer: ReturnType<typeof setTimeout> | undefined\n const resetIdleTimer = () => {\n if (idleTimer) clearTimeout(idleTimer)\n idleTimer = setTimeout(() => {\n idleTimedOut = true\n ;(proc as any).kill(\"SIGTERM\", { forceKillAfterTimeout: 5_000 })\n }, idleTimeoutMs)\n }\n resetIdleTimer()\n proc.stderr?.on(\"data\", (chunk: Buffer) => {\n resetIdleTimer()\n stderrText += stderrDecoder.write(chunk)\n if (stderrText.length > 16_000) stderrText = stderrText.slice(-16_000)\n })\n proc.stdout?.on(\"data\", (chunk: Buffer) => {\n resetIdleTimer()\n lineBuffer += decoder.write(chunk)\n const lines = lineBuffer.split(\"\\n\")\n lineBuffer = lines.pop() || \"\"\n for (const line of lines) {\n if (!line.trim()) continue\n try {\n const event = JSON.parse(line)\n if (onEvent) {\n try { onEvent(event) } catch { /* */ }\n }\n streamUsage = extractCodexUsage(event) || streamUsage\n streamBilledModel = extractCodexBilledModel(event) || streamBilledModel\n streamSessionId = extractCodexSessionId(event) || streamSessionId\n streamApiError = extractCodexErrorMessage(event) || streamApiError\n const text = extractCodexTextFromEvent(event)\n if (text && text !== fullText) {\n const delta = text.startsWith(fullText) ? text.slice(fullText.length) : text\n fullText = text.startsWith(fullText) ? text : fullText + text\n if (delta) onDelta(delta, fullText)\n }\n } catch {\n fullText += line + \"\\n\"\n onDelta(line + \"\\n\", fullText)\n }\n }\n })\n\n const result = await proc\n if (idleTimer) clearTimeout(idleTimer)\n const tail = decoder.end()\n if (tail) lineBuffer += tail\n const stderrTail = stderrDecoder.end()\n if (stderrTail) stderrText += stderrTail\n\n // A recorded API error from the JSON event stream wins even when\n // the run produced some incidental text or codex exited 0 — the\n // turn failed and the user wants to know why, not see partial output.\n if ((result.exitCode !== 0 && !fullText) || streamApiError) {\n const stderr = stderrText || (typeof result.stderr === \"string\" ? result.stderr : \"\")\n const r = result as { exitCode?: number; signal?: string; timedOut?: boolean; code?: string; shortMessage?: string }\n let errMsg: string\n if (idleTimedOut) errMsg = `Codex CLI produced no output for ${Math.round(idleTimeoutMs / 1000)}s and was stopped. AgentX sent a smaller prompt; retry the task or set AGENTX_CODEX_IDLE_TIMEOUT_MS if this turn needs longer.`\n else if (r.timedOut) errMsg = `Codex CLI timed out after ${Math.round(timeoutMs / 60_000)}m. Bump agent.maxExecutionMinutes for \"${agent.name || \"this agent\"}\" if tasks need longer.`\n else if (streamApiError) errMsg = streamApiError\n else if (r.signal) errMsg = stderr.trim() || `Codex CLI killed by signal ${r.signal}`\n else if (r.code === \"ENOENT\" || /ENOENT|spawn codex/i.test((r.shortMessage || \"\") + \" \" + stderr)) errMsg = codexMissingMessage()\n else errMsg = stderr.trim() || `Codex CLI exited with code ${r.exitCode ?? \"unknown\"}`\n return {\n content: \"\",\n ...buildErrorEnvelope(errMsg),\n duration: Date.now() - start,\n usage: streamUsage,\n billedModel: streamBilledModel,\n codexSessionId: streamSessionId || resumeSessionId,\n }\n }\n\n let finalText = \"\"\n try { finalText = readFileSync(outputFile, \"utf8\").trim() } catch { /* */ }\n if (finalText && finalText !== fullText) {\n const delta = finalText.startsWith(fullText) ? finalText.slice(fullText.length) : finalText\n fullText = finalText\n if (delta) onDelta(delta, fullText)\n }\n return {\n content: fullText,\n duration: Date.now() - start,\n usage: streamUsage,\n billedModel: streamBilledModel,\n codexSessionId: streamSessionId || resumeSessionId,\n }\n } catch (error: any) {\n const raw = /ENOENT|spawn codex/i.test(error?.message || \"\") ? codexMissingMessage() : error?.message\n return { content: fullText, ...buildErrorEnvelope(raw || \"Codex CLI failed\"), duration: Date.now() - start, usage: streamUsage, billedModel: streamBilledModel, codexSessionId: streamSessionId || resumeSessionId }\n } finally {\n rmSync(tmp, { recursive: true, force: true })\n }\n}\n\nexport async function executeOpenCodeCli(\n agent: AgentDef,\n task: AgentTask,\n onDelta?: StreamCallback,\n historyContext?: string,\n resumeSessionId?: string,\n onEvent?: (event: any) => void,\n abortSignal?: AbortSignal,\n): Promise<AgentResponse> {\n const start = Date.now()\n if (abortSignal?.aborted) {\n return { content: \"\", error: \"task cancelled by operator\", errorKind: \"cancelled\", duration: 0 }\n }\n\n const prompt = buildPrompt(agent, task, historyContext)\n const args = buildOpenCodeArgs(agent, prompt, task.model, task.systemPromptAppend, resumeSessionId)\n let fullText = \"\"\n let usage: TokenUsage | undefined\n let sessionId = resumeSessionId\n let apiError: string | undefined\n\n try {\n const timeoutMs = Math.max(60_000, (agent.maxExecutionMinutes ?? 20) * 60_000)\n const proc = execa(\"opencode\", args, {\n cwd: agent.workspace,\n timeout: timeoutMs,\n reject: false,\n env: buildOpenCodeEnv(agent, task),\n extendEnv: false,\n buffer: false,\n stdin: \"ignore\",\n })\n try { onEvent?.({ type: \"opencode.spawned\", model: task.model || agent.model, resumeSessionId }) } catch { /* */ }\n\n let cancelled = false\n let cancelReason: string | undefined\n const onAbort = () => {\n cancelled = true\n cancelReason = (abortSignal?.reason as any)?.message || (typeof abortSignal?.reason === \"string\" ? abortSignal.reason : \"task cancelled by operator\")\n try { proc.kill(\"SIGTERM\", { forceKillAfterTimeout: 3_000 }) } catch { /* */ }\n }\n if (abortSignal) abortSignal.addEventListener(\"abort\", onAbort, { once: true })\n\n let lineBuffer = \"\"\n let stderrText = \"\"\n const decoder = new StringDecoder(\"utf8\")\n const stderrDecoder = new StringDecoder(\"utf8\")\n const consumeLine = (line: string) => {\n if (!line.trim()) return\n try {\n const event = JSON.parse(line)\n try { onEvent?.(event) } catch { /* */ }\n if (typeof event.sessionID === \"string\") sessionId = event.sessionID\n const text = event.type === \"text\" && typeof event.part?.text === \"string\" ? event.part.text : undefined\n if (text) {\n fullText += text\n onDelta?.(text, fullText)\n }\n const tokens = event.type === \"step_finish\" ? event.part?.tokens : undefined\n if (tokens) {\n usage = {\n inputTokens: firstNumber(tokens.input),\n outputTokens: firstNumber(tokens.output),\n cacheReadTokens: firstNumber(tokens.cache?.read),\n cacheCreateTokens: firstNumber(tokens.cache?.write),\n }\n }\n apiError = extractOpenCodeError(event) || apiError\n } catch {\n fullText += line + \"\\n\"\n onDelta?.(line + \"\\n\", fullText)\n }\n }\n proc.stdout?.on(\"data\", (chunk: Buffer) => {\n lineBuffer += decoder.write(chunk)\n const lines = lineBuffer.split(\"\\n\")\n lineBuffer = lines.pop() || \"\"\n for (const line of lines) consumeLine(line)\n })\n proc.stderr?.on(\"data\", (chunk: Buffer) => { stderrText += stderrDecoder.write(chunk) })\n\n const result = await proc\n if (abortSignal) abortSignal.removeEventListener(\"abort\", onAbort)\n lineBuffer += decoder.end()\n if (lineBuffer.trim()) consumeLine(lineBuffer)\n stderrText += stderrDecoder.end()\n\n if (cancelled) {\n return { content: \"\", error: cancelReason || \"task cancelled by operator\", errorKind: \"cancelled\", duration: Date.now() - start, opencodeSessionId: sessionId }\n }\n if (result.exitCode !== 0 || apiError) {\n const r = result as { exitCode?: number; signal?: string; timedOut?: boolean; code?: string; shortMessage?: string }\n let errMsg: string\n if (r.timedOut) errMsg = `OpenCode timed out after ${Math.round(timeoutMs / 60_000)}m. Bump agent.maxExecutionMinutes for \"${agent.name || \"this agent\"}\" if tasks need longer.`\n else if (apiError) errMsg = apiError\n else if (r.code === \"ENOENT\" || /ENOENT|spawn opencode/i.test((r.shortMessage || \"\") + \" \" + stderrText)) errMsg = openCodeMissingMessage()\n else errMsg = stderrText.trim() || `OpenCode exited with code ${r.exitCode ?? \"unknown\"}`\n return { content: \"\", ...buildErrorEnvelope(errMsg), duration: Date.now() - start, usage, opencodeSessionId: sessionId }\n }\n return { content: fullText.trim(), duration: Date.now() - start, usage, billedModel: task.model || agent.model, opencodeSessionId: sessionId }\n } catch (error: any) {\n const raw = /ENOENT|spawn opencode/i.test(error?.message || \"\") ? openCodeMissingMessage() : error?.message\n return { content: fullText, ...buildErrorEnvelope(raw || \"OpenCode failed\"), duration: Date.now() - start, usage, opencodeSessionId: sessionId }\n }\n}\n\n/**\n * Execute a task using the Claude Agent SDK (tier: \"sdk\").\n */\nexport async function executeSdk(\n agent: AgentDef,\n task: AgentTask,\n apiKey: string,\n historyContext?: string,\n): Promise<AgentResponse> {\n const start = Date.now()\n\n try {\n // @ts-ignore — SDK may not be installed\n const sdk = await import(\"@anthropic-ai/claude-agent-sdk\")\n const { query } = sdk\n\n // Build prompt with full context (landscape, memory, patterns, etc.)\n const prompt = buildPrompt(agent, task, historyContext)\n\n let content = \"\"\n\n const q = query({\n prompt,\n options: {\n model: task.model || agent.model,\n cwd: agent.workspace,\n permissionMode: \"bypassPermissions\" as any,\n },\n })\n\n for await (const message of q) {\n if (message.type === \"result\" && message.subtype === \"success\") {\n content = message.result || \"\"\n }\n }\n\n return {\n content,\n duration: Date.now() - start,\n }\n } catch (error: any) {\n return {\n content: \"\",\n ...buildErrorEnvelope(`SDK error: ${error.message}`),\n duration: Date.now() - start,\n }\n }\n}\n\n/**\n * Execute a task using agentx's own orchestrator (tier: \"orchestrator\").\n *\n * When `onDelta` is supplied, switches from `generate()` (single-shot) to\n * `generateStream()` and forwards text deltas as they arrive. This is what\n * lets the daemon's POST /chat?stream=true actually stream Claude/DeepSeek\n * tokens — without it, the orchestrator returned only after the agentic\n * loop finished and SSE clients saw `role` then `stop` with no content\n * between (the failure mode that surfaced on the noqta-public smoke test\n * after the tier switch).\n */\n/** Side-channel for reasoning/thinking chunks — distinct from\n * StreamCallback so callers can route them to a different UI lane\n * (e.g. a collapsible thinking block on noqta.tn, a dimmed terminal\n * block in the dashboard) without polluting the visible response. */\nexport type ThinkingCallback = (text: string) => void\n\nexport async function executeOrchestrator(\n agent: AgentDef,\n task: AgentTask,\n apiKey?: string,\n historyContext?: string,\n onDelta?: StreamCallback,\n providerOpts?: { thinking?: boolean },\n onThinking?: ThinkingCallback,\n abortSignal?: AbortSignal,\n onEvent?: (event: any) => void,\n): Promise<AgentResponse> {\n const start = Date.now()\n\n // Wall-clock cap. Every other tier (claude-code, codex-cli) already\n // honours agent.maxExecutionMinutes; without it on the orchestrator\n // path a hung provider stream or stuck tool keeps the runningTask\n // alive forever — the registry's finally-block only fires when this\n // function returns, and operator Stop relies on the same return.\n // Combine the operator's abortSignal with a timer so EITHER source\n // forces the loop to bail and the registry to clean up.\n const timeoutMs = Math.max(60_000, (agent.maxExecutionMinutes ?? 20) * 60_000)\n const combined = new AbortController()\n let timedOut = false\n const onOperatorAbort = () => {\n const reason = (abortSignal!.reason as any)?.message\n || (typeof abortSignal!.reason === \"string\" ? abortSignal!.reason : \"task cancelled by operator\")\n try { combined.abort(new Error(reason)) } catch { /* */ }\n }\n if (abortSignal) {\n if (abortSignal.aborted) onOperatorAbort()\n else abortSignal.addEventListener(\"abort\", onOperatorAbort, { once: true })\n }\n const wallClockTimer = setTimeout(() => {\n timedOut = true\n try { combined.abort(new Error(`orchestrator timed out after ${Math.round(timeoutMs / 60_000)}m`)) } catch { /* */ }\n }, timeoutMs)\n ;(wallClockTimer as any).unref?.()\n\n const timeoutEnvelope = (): AgentResponse => ({\n content: \"\",\n ...buildErrorEnvelope(`Orchestrator timed out after ${Math.round(timeoutMs / 60_000)}m. Bump agent.maxExecutionMinutes for \"${agent.name || \"this agent\"}\" if tasks need longer.`),\n duration: Date.now() - start,\n })\n\n try {\n const providerName = agent.provider || \"claude-code\"\n\n // Build full prompt with context (landscape, memory, patterns, etc.)\n const fullTask = historyContext\n ? `${historyContext}\\n\\n${task.message}`\n : task.message\n\n // Enable noqta workspace tools (list_projects, create_task, etc.)\n // when the channel is web-chat AND the sender is a noqta user_id\n // UUID. The ToolExecutor reads the bearer from env at dispatch\n // time — it never enters the agent's prompt context. Non-noqta\n // callers (telegram numeric ids, mesh, crons) get no noqtaContext\n // so the tools are absent from their catalog entirely.\n const sender = task.context?.sender\n const looksUuid =\n typeof sender === \"string\" &&\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(sender)\n const noqtaContext =\n task.context?.channel === \"web-chat\" && looksUuid\n ? { userId: sender, agentId: task.agentId || \"noqta-public\" }\n : undefined\n\n // MCP servers declared on this agent (including codegraph when\n // codegraph=true) — agentx already syncs these to .mcp.json for\n // CLI-backed tiers, but the orchestrator's in-process loop has its\n // own hand-rolled catalog and never reads that file. startMcpPool\n // inside generate()/generateStream() boots them as stdio children\n // and exposes their tools to the loop.\n const mcpServers = effectiveMcpConfig(agent)\n const hasMcp = Object.keys(mcpServers).length > 0\n\n const baseOpts = {\n task: fullTask,\n cwd: agent.workspace,\n provider: providerName as any,\n model: task.model || agent.model,\n apiKey,\n overwrite: true,\n interactive: false,\n context7: false,\n noqtaContext,\n providerOpts,\n abortSignal: combined.signal,\n mcpServers: hasMcp ? mcpServers : undefined,\n }\n\n if (onDelta) {\n const { generateStream } = await import(\"@/agent\")\n let content = \"\"\n let tokensUsed: number | undefined\n let inputTokens: number | undefined\n let outputTokens: number | undefined\n let errorMsg: string | undefined\n for await (const event of generateStream(baseOpts)) {\n // Forward tool_call/tool_result (and a few other signal events)\n // to the caller-supplied onEvent so the daemon's SSE writer can\n // surface \"calling wiki_search…\" → \"✓ wiki_search done\" badges\n // on the jort-wiki chat without parsing internal stream-json.\n if (onEvent) {\n try { onEvent(event) } catch { /* subscriber crashed */ }\n }\n if (event.type === \"text_delta\") {\n content += event.text\n onDelta(event.text, content)\n } else if (event.type === \"thinking_delta\") {\n // Route thinking to the dedicated side-channel so the chat\n // endpoint can emit `delta.reasoning_content` (DeepSeek's\n // own convention) and the dashboard modal can render it in\n // a distinct lane. Keeping it out of onDelta is critical:\n // the chat SSE consumer (noqta.tn) treats `delta.content`\n // as visible response text, so leaking thinking there\n // floods the chat with `💭 the / 💭 user / …` per token.\n if (event.text && onThinking) onThinking(event.text)\n } else if (event.type === \"generate_result\") {\n // Final canonical result — generate_result.content may include\n // text the provider emitted as a single payload alongside\n // tool_use, plus any files/followUp summarisation. Prefer it\n // when it diverges from our running accumulation.\n if (event.result.content && event.result.content !== content) {\n const trailing = event.result.content.slice(content.length)\n if (trailing) {\n content = event.result.content\n onDelta(trailing, content)\n } else {\n content = event.result.content\n }\n }\n tokensUsed = event.result.tokensUsed\n // Pick up split tokens so the SSE usage chunk is accurate\n // — same shape as the non-streaming path below.\n if (event.result.inputTokens != null) inputTokens = event.result.inputTokens\n if (event.result.outputTokens != null) outputTokens = event.result.outputTokens\n } else if (event.type === \"done\") {\n // Non-agentic path emits `done` with the full result.\n if (event.result.content && event.result.content !== content) {\n const trailing = event.result.content.slice(content.length)\n if (trailing) {\n content = event.result.content\n onDelta(trailing, content)\n } else {\n content = event.result.content\n }\n }\n if (event.result.tokensUsed != null) tokensUsed = event.result.tokensUsed\n } else if (event.type === \"error\") {\n errorMsg = event.error\n }\n }\n if (errorMsg && !content) {\n // Wall-clock cap fired — surface as a `timeout` envelope so\n // operators see the same \"timed out after Xm\" message every\n // other tier produces.\n if (timedOut) return timeoutEnvelope()\n // Operator-cancel: surface the same `errorKind: \"cancelled\"`\n // envelope claude-code uses, so the router / channel layer\n // suppresses the error echo and the registry's finally block\n // can route follow-ups normally.\n if (errorMsg === \"cancelled\" || abortSignal?.aborted || combined.signal.aborted) {\n return {\n content: \"\",\n error: \"task cancelled by operator\",\n errorKind: \"cancelled\",\n duration: Date.now() - start,\n }\n }\n return {\n content: \"\",\n ...buildErrorEnvelope(`Orchestrator error: ${errorMsg}`),\n duration: Date.now() - start,\n }\n }\n // Fallback estimate so the SSE usage chunk on the daemon /chat\n // path always has SOME values for noqta.tn's cost-based billing\n // to work against (the floor still protects against undercharge).\n if ((inputTokens === undefined || outputTokens === undefined) && tokensUsed) {\n inputTokens = Math.round(tokensUsed * 0.7)\n outputTokens = tokensUsed - inputTokens\n }\n return {\n content: content || \"Done.\",\n tokensUsed,\n usage: (inputTokens !== undefined || outputTokens !== undefined)\n ? { inputTokens: inputTokens || 0, outputTokens: outputTokens || 0, cacheReadTokens: 0, cacheCreateTokens: 0 }\n : undefined,\n duration: Date.now() - start,\n }\n }\n\n // Non-streaming path — unchanged from the original orchestrator\n // behaviour so existing callers (telegram, mesh, crons) keep working.\n const { generate } = await import(\"@/agent\")\n const result = await generate(baseOpts)\n // Surface split tokens on `usage` so the daemon /chat handler (and\n // its noqta.tn voice forwarder) can do cost-based billing instead\n // of the floor-only fallback. Falls back to a 30/70 estimate when\n // the provider only reports a combined total (CLI tier, legacy\n // loop) so downstream billing never sees zeros for a real turn.\n let inputTokens = result.inputTokens\n let outputTokens = result.outputTokens\n if ((inputTokens === undefined || outputTokens === undefined) && result.tokensUsed) {\n const total = result.tokensUsed\n inputTokens = Math.round(total * 0.7)\n outputTokens = total - inputTokens\n }\n return {\n content: result.content || \"Done.\",\n tokensUsed: result.tokensUsed,\n usage: (inputTokens !== undefined || outputTokens !== undefined)\n ? { inputTokens: inputTokens || 0, outputTokens: outputTokens || 0, cacheReadTokens: 0, cacheCreateTokens: 0 }\n : undefined,\n duration: Date.now() - start,\n }\n } catch (error: any) {\n if (timedOut) return timeoutEnvelope()\n // `generate()` (non-streaming) throws \"cancelled\" from the agentic\n // loop's iteration_start check when the combined signal fires.\n if (error?.message === \"cancelled\" || abortSignal?.aborted || combined.signal.aborted) {\n return {\n content: \"\",\n error: \"task cancelled by operator\",\n errorKind: \"cancelled\",\n duration: Date.now() - start,\n }\n }\n return {\n content: \"\",\n ...buildErrorEnvelope(`Orchestrator error: ${error.message}`),\n duration: Date.now() - start,\n }\n } finally {\n clearTimeout(wallClockTimer)\n if (abortSignal) abortSignal.removeEventListener(\"abort\", onOperatorAbort)\n }\n}\n\n/**\n * Route a task to the correct execution tier.\n */\n/**\n * Execute a task by reusing a persistent claude subprocess held in the\n * ProcessRegistry. Driven over `--input-format stream-json` on stdin;\n * the same parser path handles output. See\n * docs/architecture/persistent-claude-process.md for the design.\n *\n * Forwards every stream event to `onEvent` so the existing trace\n * capture site (registry.ts emitTraceStepsFromStreamEvent) continues\n * to populate task_trace_steps unchanged.\n *\n * Returns null when the registry can't allocate a slot (cap exceeded,\n * binary not installed, etc.) — caller falls back to spawn-per-task.\n */\nasync function executeClaudeCodePersistent(\n agent: AgentDef,\n task: AgentTask,\n historyContext?: string,\n resumeSessionId?: string,\n onEvent?: (event: any) => void,\n abortSignal?: AbortSignal,\n): Promise<AgentResponse | null> {\n const registry = getProcessRegistry()\n if (!registry) return null\n\n const start = Date.now()\n const channel = task.context?.channel || \"api\"\n const chatId = task.context?.chatId || task.context?.group || task.context?.sender || \"default\"\n const key: ProcessKey = { agentId: task.agentId, channel, chatId }\n\n // Improvement plan #8 — caller-driven session reset for the\n // persistent path. Kill any existing handle BEFORE acquire so the\n // next call spawns a brand-new subprocess instead of reusing the\n // warm one. Awaiting the kill keeps acquire-after-kill race-free:\n // the registry entry is removed inside kill() before resolution.\n if (task.freshSession) {\n try { await registry.kill(key, \"freshSession=true\") } catch { /* observability best-effort */ }\n }\n\n let handle\n let wasFreshSpawn = false\n try {\n const before = registry.list().length\n handle = registry.acquire(key, {\n agentId: task.agentId,\n channel,\n chatId,\n workspace: agent.workspace,\n model: task.model || agent.model,\n permissionMode: agent.permissionMode,\n systemPromptAppend: task.systemPromptAppend,\n resumeSessionId,\n })\n wasFreshSpawn = registry.list().length > before\n } catch (e: any) {\n if (e instanceof RegistryCapExceeded) return null\n // Anything else — return null too; caller falls back. Persistent\n // path must never be more brittle than the spawn-per-task one.\n return null\n }\n // Audit log only on fresh spawns — reused handles already audited at\n // their original spawn. Cuts noise on chats that turn frequently.\n if (wasFreshSpawn) {\n logClaudeSpawn(task.agentId, agent, task.model, resumeSessionId, \"persistent\")\n }\n\n const prompt = buildPrompt(agent, task, historyContext)\n\n // Accumulate response data from the stream events. Mirrors what\n // parseClaudeJsonOutput does for the spawn-per-task JSON envelope.\n let finalText = \"\"\n let finalError: string | undefined\n let finalErrorKind: FriendlyError[\"kind\"] | undefined\n let usage: TokenUsage | undefined\n let contextTokens: number | undefined\n let billedModel: string | undefined\n let sessionId: string | undefined\n\n // Operator cancellation for the persistent path. Killing the handle\n // closes its stdio, which terminates the async iterator below; we then\n // emit a \"cancelled\" envelope just like the spawn-per-task paths.\n let cancelled = false\n const onAbort = () => {\n cancelled = true\n void registry.kill(key, \"operator-cancel\").catch(() => {})\n }\n if (abortSignal) {\n if (abortSignal.aborted) onAbort()\n else abortSignal.addEventListener(\"abort\", onAbort, { once: true })\n }\n\n let sawEvent = false\n try {\n for await (const evt of handle.runTurn({ message: prompt, taskId: task.taskId ?? \"unknown\" })) {\n sawEvent = true\n // Forward to existing onEvent — trace step emitter, dashboard\n // formatter, etc. all keep working without changes.\n if (onEvent) {\n try { onEvent(evt.raw) } catch { /* observability best-effort */ }\n }\n\n if (evt.type === \"assistant\") {\n // Assistant snapshots are cumulative — overwrite, don't append.\n const blocks = ((evt.raw as any).message?.content ?? []) as Array<{ type: string; text?: string }>\n for (const block of blocks) {\n if (block.type === \"text\" && typeof block.text === \"string\") {\n finalText = block.text\n }\n }\n // Per-call usage on the assistant envelope: the last call's\n // (input + cache) tokens = end-of-turn context size. See\n // AgentResponse.contextTokens for why this, not the cumulative\n // result usage, drives session rotation.\n const u = (evt.raw as any).message?.usage\n if (u) {\n const callContext =\n (u.input_tokens || 0) +\n (u.cache_read_input_tokens || 0) +\n (u.cache_creation_input_tokens || 0)\n if (callContext > 0) contextTokens = callContext\n }\n }\n\n if (evt.type === \"result\") {\n const r = evt.raw as any\n if (r.is_error) {\n const env = buildErrorEnvelope(typeof r.result === \"string\" ? r.result : \"claude error\")\n finalError = env.error\n finalErrorKind = env.errorKind\n } else if (typeof r.result === \"string\" && r.result.length > 0) {\n // Final result text (sometimes more authoritative than the last\n // assistant snapshot, especially for very short responses).\n finalText = r.result\n }\n if (typeof r.session_id === \"string\") sessionId = r.session_id\n if (r.usage) {\n usage = {\n inputTokens: r.usage.input_tokens || 0,\n outputTokens: r.usage.output_tokens || 0,\n cacheReadTokens: r.usage.cache_read_input_tokens || 0,\n cacheCreateTokens: r.usage.cache_creation_input_tokens || 0,\n }\n }\n if (typeof (r.message?.model) === \"string\") billedModel = r.message.model\n else if (typeof r.model === \"string\") billedModel = r.model\n }\n }\n } catch (e: any) {\n if (cancelled) {\n finalError = \"task cancelled by operator\"\n finalErrorKind = \"cancelled\"\n } else if (!sawEvent) {\n // Turn never started (handle killed between acquire and the first\n // write, stdin gone, etc.) — nothing streamed, so retrying is\n // side-effect-free. Return null and let the caller fall back to\n // spawn-per-task; an infra race must not surface as a task failure.\n console.error(`[runtime] persistent handle unusable before first event (${e?.message || e}); falling back to spawn-per-task`)\n registry.release(key, { kill: true, reason: \"pre-turn failure\" })\n if (abortSignal) abortSignal.removeEventListener(\"abort\", onAbort)\n return null\n } else {\n const env = buildErrorEnvelope(`persistent claude process error: ${e?.message || String(e)}`)\n finalError = env.error\n finalErrorKind = env.errorKind\n }\n } finally {\n if (abortSignal) abortSignal.removeEventListener(\"abort\", onAbort)\n }\n\n if (cancelled && !finalError) {\n finalError = \"task cancelled by operator\"\n finalErrorKind = \"cancelled\"\n }\n\n return {\n content: cancelled ? \"\" : finalText,\n error: finalError,\n errorKind: finalErrorKind,\n duration: Date.now() - start,\n usage,\n contextTokens,\n billedModel,\n claudeSessionId: sessionId,\n }\n}\n\nexport async function executeTask(\n agent: AgentDef,\n task: AgentTask,\n providers: Record<string, { apiKey?: string }>,\n onDelta?: StreamCallback,\n historyContext?: string,\n resumeSessionId?: string,\n onEvent?: (event: any) => void,\n abortSignal?: AbortSignal,\n onThinking?: ThinkingCallback,\n): Promise<AgentResponse> {\n switch (agent.tier) {\n case \"claude-code\":\n // Persistent-process path (improvement plan #5, persistent flavor).\n // Returns null when the registry isn't available or can't allocate\n // a slot — we fall through to the legacy spawn-per-task path so\n // a registry-only failure can never block dispatch.\n if (agent.persistentProcess) {\n const persistent = await executeClaudeCodePersistent(agent, task, historyContext, resumeSessionId, onEvent, abortSignal)\n if (persistent !== null) return persistent\n }\n if (onDelta) {\n return executeClaudeCodeStreaming(agent, task, onDelta, historyContext, resumeSessionId, onEvent, abortSignal)\n }\n return executeClaudeCode(agent, task, historyContext, resumeSessionId, abortSignal)\n\n case \"codex-cli\":\n if (onDelta) {\n return executeCodexCliStreaming(agent, task, onDelta, historyContext, resumeSessionId, onEvent)\n }\n return executeCodexCli(agent, task, historyContext, resumeSessionId)\n\n case \"opencode\":\n return executeOpenCodeCli(agent, task, onDelta, historyContext, resumeSessionId, onEvent, abortSignal)\n\n case \"sdk\": {\n const providerName = agent.provider || \"claude\"\n const apiKey = providers[providerName]?.apiKey\n if (!apiKey) {\n return {\n content: \"\",\n error: `No API key for provider \"${providerName}\". Configure providers.${providerName}.apiKey`,\n }\n }\n return executeSdk(agent, task, apiKey, historyContext)\n }\n\n case \"orchestrator\": {\n const providerName = agent.provider || \"claude-code\"\n const providerCfg = providers[providerName]\n const apiKey = providerCfg?.apiKey\n return executeOrchestrator(agent, task, apiKey, historyContext, onDelta, {\n thinking: (providerCfg as any)?.thinking,\n }, onThinking, abortSignal, onEvent)\n }\n\n default:\n return {\n content: \"\",\n error: `Unknown tier: ${agent.tier}`,\n }\n }\n}\n","// --- Translate Anthropic API / Claude Code errors into human-friendly text ---\n//\n// Raw errors from the Claude Code CLI look like:\n// API Error: 400 {\"type\":\"error\",\"error\":{\"type\":\"invalid_request_error\",\n// \"message\":\"You're out of extra usage. Add more at ...\"}}\n// These are painful to surface in a chat reply or the dashboard. This module\n// parses the structured bit and returns a short, actionable sentence.\n\nexport interface FriendlyError {\n /** One-line, operator-facing message suitable for a dashboard card or chat reply. */\n message: string\n /** Optional next step the user can take. */\n fix?: string\n /** Machine-readable error category for routing / retries. */\n kind:\n | \"out_of_credits\"\n | \"overage_disabled\"\n | \"usage_cap\"\n | \"auth\"\n | \"permission\"\n | \"rate_limit\"\n | \"overloaded\"\n | \"context_too_large\"\n | \"invalid_request\"\n | \"upstream_api\"\n | \"timeout\"\n | \"cancelled\"\n | \"unknown\"\n /** Whether this error is transient — a retry has a reasonable chance of succeeding. */\n retryable: boolean\n /** Source error text retained for logs / debugging. */\n raw?: string\n}\n\n/**\n * Best-effort translator. If we recognise the shape we return a structured\n * FriendlyError; otherwise we hand back the original text wrapped as \"unknown\".\n * Never throws — errors are data in here.\n */\nexport function friendlyModelError(raw: string | undefined | null): FriendlyError {\n const source = (raw || \"\").trim()\n if (!source) {\n return { message: \"Unknown error from the model.\", kind: \"unknown\", retryable: false }\n }\n\n // Timeouts are wrapped by us upstream; pass through.\n if (/timed out after/i.test(source)) {\n return { message: source.slice(0, 300), kind: \"timeout\", retryable: true, raw: source }\n }\n\n // Claude Code CLI lost its OAuth session — \"Not logged in · Please run /login\".\n // Usually a transient refresh race when multiple agents share one credentials\n // file on the same host; occasionally the token is genuinely stale. Either way\n // it comes through as plain CLI text with no JSON envelope, so match it before\n // the parse step below.\n if (/not logged in|please run \\/login/i.test(source)) {\n return {\n kind: \"auth\",\n retryable: true,\n message: \"The agent's Claude Code session is logged out.\",\n fix: \"Usually a transient OAuth refresh race under concurrent load — retry in a moment. If it persists, run `/login` in the agent's workspace.\",\n raw: source,\n }\n }\n\n // User-set monthly /usage cap on the Claude Code subscription (the\n // `/usage` slash-command limit). Surfaces as \"You have reached your\n // specified API usage limits. You will regain access on <ISO date>\" —\n // wording introduced by Claude CLI v2.1.x. Distinct from\n // `overage_disabled` (which is the org-level toggle, retryable as soon\n // as someone flips it) and `out_of_credits` (programmatic API key\n // billing). The operator fix is to raise the cap at\n // claude.ai/settings/usage OR wait for the cycle reset; not retryable\n // until either happens.\n if (/specified API usage limits/i.test(source)) {\n const dateMatch = source.match(/regain access on ([^.\\n]+?)(?:\\.|$)/i)\n const when = dateMatch ? ` Resets ${dateMatch[1].trim()}.` : \"\"\n return {\n kind: \"usage_cap\",\n retryable: false,\n message: `Claude Code subscription /usage cap exhausted.${when}`,\n fix: \"Raise the cap at https://claude.ai/settings/usage (this is the user-set monthly limit on the OAuth account, not Anthropic API billing). If the daemon is using a different account than expected, check CLAUDE_CODE_OAUTH_TOKEN / ANTHROPIC_API_KEY in the spawn env — capped keys leak the same error.\",\n raw: source,\n }\n }\n\n // Plain-text Max-plan overage signal that may arrive without a JSON\n // envelope (the structured parse below also catches it via errMessage).\n // Order matters: this case must fire BEFORE the plain-text `out_of_credits`\n // branch — \"out of extra usage\" matches both regexes, but `overage_disabled`\n // is the more specific (and retryable) classification.\n if (/out of extra usage/i.test(source)) {\n return {\n kind: \"overage_disabled\",\n retryable: true,\n message: \"Claude Max-plan overage is unavailable (disabled or depleted at the org level).\",\n fix: \"Enable overage / extra usage at https://claude.ai/settings/usage (this is the subscription account's toggle — not Anthropic API billing). Warm sessions can still succeed via prompt cache; cold dispatches will keep failing until overage is re-enabled.\",\n raw: source,\n }\n }\n\n // Plain-text credit-balance signal (programmatic ANTHROPIC_API_KEY accounts)\n // that may arrive without a JSON envelope — some CLI paths surface the API\n // message verbatim. Matched here so we route to `out_of_credits` even when\n // the structured parse below would otherwise fall through to `unknown`.\n if (/credit balance (is )?too low|out of credit|credit(s)? exhausted|insufficient credit/i.test(source)) {\n return {\n kind: \"out_of_credits\",\n retryable: false,\n message: \"The agent is out of Anthropic credits.\",\n fix: \"Top up at https://console.anthropic.com/settings/billing (or claude.ai/settings/usage for Max subscribers), then retry.\",\n raw: source,\n }\n }\n\n // Look for the standard Anthropic JSON envelope anywhere in the string —\n // the CLI prefixes it with \"API Error: <status>\".\n const jsonMatch = source.match(/\\{.*\\}\\s*$/s) || source.match(/\\{[\\s\\S]*\\}/)\n let parsed: any\n if (jsonMatch) {\n try { parsed = JSON.parse(jsonMatch[0]) } catch { /* raw text only */ }\n }\n\n const inner = parsed?.error || parsed\n const errType: string | undefined = typeof inner?.type === \"string\" ? inner.type : undefined\n const errMessage: string | undefined = typeof inner?.message === \"string\" ? inner.message : undefined\n\n const statusMatch = source.match(/API Error:\\s*(\\d{3})/i)\n const status = statusMatch ? parseInt(statusMatch[1], 10) : undefined\n\n // Max-plan overage disabled — the most common root cause on subscription\n // accounts. Anthropic returns \"You're out of extra usage\" when a request\n // would spill past the regular Max allotment AND overage (\"extra usage\")\n // is disabled at the org level. Operator fix is flipping a toggle on\n // claude.ai, not topping up API credits — so route this to its own kind\n // with a retryable:true hint (re-enabling overage unblocks immediately).\n if (errMessage && /out of extra usage/i.test(errMessage)) {\n return {\n kind: \"overage_disabled\",\n retryable: true,\n message: \"Claude Max-plan overage is unavailable (disabled or depleted at the org level).\",\n fix: \"Enable overage / extra usage at https://claude.ai/settings/usage (this is the subscription account's toggle — not Anthropic API billing). Warm sessions can still succeed via prompt cache; cold dispatches will keep failing until overage is re-enabled.\",\n raw: source,\n }\n }\n\n // Real API credit exhaustion (programmatic ANTHROPIC_API_KEY accounts).\n // \"Credit balance is too low\" is what Anthropic's API actually returns on\n // depletion — earlier wording variants are kept for back-compat.\n if (errMessage && /out of (extra )?usage|out of credit|credit(s)? exhausted|insufficient credit|credit balance (is )?too low|low credit balance/i.test(errMessage)) {\n return {\n kind: \"out_of_credits\",\n retryable: false,\n message: \"The agent is out of Anthropic credits.\",\n fix: \"Top up at https://console.anthropic.com/settings/billing (or claude.ai/settings/usage for Max subscribers), then retry.\",\n raw: source,\n }\n }\n\n // Context-length issues.\n if (errMessage && /context|prompt is too long|too many tokens|maximum context length/i.test(errMessage)) {\n return {\n kind: \"context_too_large\",\n retryable: false,\n message: \"The conversation has grown past what the model can hold.\",\n fix: \"Clear session history or switch the agent to a bigger context model.\",\n raw: source,\n }\n }\n\n // Authentication & API key issues.\n if (errType === \"authentication_error\" || (errMessage && /authentication|invalid.*api.*key|no api key|not authenticated/i.test(errMessage))) {\n return {\n kind: \"auth\",\n retryable: false,\n message: \"Authentication failed talking to Anthropic.\",\n fix: \"Check ANTHROPIC_API_KEY in .env (or the Claude Code login for claude-code tier agents).\",\n raw: source,\n }\n }\n\n // Permission / forbidden.\n if (errType === \"permission_error\" || status === 403) {\n return {\n kind: \"permission\",\n retryable: false,\n message: errMessage || \"The API key doesn't have permission for this request.\",\n raw: source,\n }\n }\n\n // Rate limit from Anthropic's side.\n if (errType === \"rate_limit_error\" || status === 429) {\n return {\n kind: \"rate_limit\",\n retryable: true,\n message: \"Anthropic's API rate-limited this request.\",\n fix: \"AgentX will retry automatically. If it recurs, lower the agent's concurrency or spread scheduled jobs.\",\n raw: source,\n }\n }\n\n // Server-side overload.\n if (errType === \"overloaded_error\" || status === 529 || status === 503) {\n return {\n kind: \"overloaded\",\n retryable: true,\n message: \"Anthropic's API is temporarily overloaded.\",\n fix: \"AgentX will retry in a moment.\",\n raw: source,\n }\n }\n\n // Generic invalid request — surface the API message verbatim (truncated).\n if (errType === \"invalid_request_error\" || status === 400) {\n return {\n kind: \"invalid_request\",\n retryable: false,\n message: errMessage || \"The model rejected the request as invalid.\",\n raw: source,\n }\n }\n\n // Other Anthropic API errors (5xx catchall).\n if (status && status >= 500) {\n return {\n kind: \"upstream_api\",\n retryable: true,\n message: errMessage || `Anthropic API error (HTTP ${status}).`,\n fix: \"Transient — retry. Check https://status.anthropic.com if it persists.\",\n raw: source,\n }\n }\n\n // Fallback: unknown shape, keep the text short.\n return {\n kind: \"unknown\",\n retryable: false,\n message: source.replace(/\\s+/g, \" \").slice(0, 220),\n raw: source,\n }\n}\n\n/**\n * Render a FriendlyError back to a one-line string (primary message + fix),\n * which is what most call sites want when updating `response.error`.\n */\nexport function renderFriendlyError(f: FriendlyError): string {\n return f.fix ? `${f.message} — ${f.fix}` : f.message\n}\n","import type { ProcessRegistry } from \"./process-registry\"\n\n// --- Singleton accessor for the live ProcessRegistry ---\n//\n// Lifetime: the daemon constructs a registry in AgentRegistry's\n// constructor (when at least one agent has `persistentProcess: true`)\n// and registers it here. `executeTask` reads the singleton when it\n// needs to dispatch via the persistent path. Tests use the\n// set/reset hooks to inject fakes without touching the daemon.\n//\n// Why a singleton (vs threading the registry through executeTask's\n// signature): executeTask is called from many sites — registry,\n// cron, mesh — and adding a parameter to all of them is invasive.\n// The registry is a process-global resource (one per daemon), same\n// lifetime story as the event bus.\n\nlet _registry: ProcessRegistry | null = null\n\nexport function getProcessRegistry(): ProcessRegistry | null {\n return _registry\n}\n\nexport function setProcessRegistry(r: ProcessRegistry | null): void {\n _registry = r\n}\n\n/** Test-only — guarantees a clean slate between cases. */\nexport function resetProcessRegistryForTesting(): void {\n _registry = null\n}\n","// --- Process Registry: persistent claude processes per session ---\n//\n// Tracks one `claude -p --input-format stream-json` subprocess per\n// (agentId, channel, chatId). Tasks for the same chat reuse the live\n// process across turns; the empirical cache-amortization win is ~3-5×\n// (turn 1 cache_create=12897, turn 2 cache_create=20 in the\n// 2026-05-03 spike). When a chat goes idle past idleTimeoutMs the\n// process is killed; when at the cap, the oldest idle process is\n// evicted to make room.\n//\n// This file is the registry only. Real subprocess wiring (spawn,\n// stdin write, stdout read) lives behind the ProcessFactory interface\n// so the registry can be tested without spawning real children.\n//\n// Design rationale (see docs/architecture/persistent-claude-process.md):\n// - Process-per-session (not pool-per-agent) — perfect chat\n// isolation; same composite key as SessionStore.\n// - Slash commands DO NOT work in `-p` mode (verbatim from the\n// headless docs). Rotation = kill + respawn, optionally with an\n// application-level summary in --append-system-prompt.\n// - Concurrency: same-chat turns serialize through the handle's\n// own queue; different-chat turns are independent processes.\n\nexport interface ProcessKey {\n agentId: string\n channel: string\n chatId: string\n}\n\nexport type ProcessKeyString = string\n\n/** Stringify the key for Map storage / logging. The composite is the\n * same one SessionStore uses, so process and session entries line up\n * without an extra mapping table. */\nexport function processKeyToString(k: ProcessKey): ProcessKeyString {\n return `${k.agentId}:${k.channel}:${k.chatId}`\n}\n\nexport function parseProcessKey(s: ProcessKeyString): ProcessKey {\n const [agentId, channel, ...rest] = s.split(\":\")\n return { agentId, channel, chatId: rest.join(\":\") }\n}\n\n/**\n * Lifecycle state of a process. Values are strings (not enum) so they\n * round-trip through JSON for `agentx process list` without an\n * encode/decode step.\n *\n * warm-cold → spawned, no claudeSessionId yet\n * warm-hot → at least one turn done, cache reuse expected\n * busy → a turn is streaming right now; never idle-killed\n * idle → not running a turn, eligible for eviction\n * dead → killed or exited; will be removed from the registry\n */\nexport type ProcessState = \"warm-cold\" | \"warm-hot\" | \"busy\" | \"idle\" | \"dead\"\n\nexport interface ProcessSnapshot {\n key: ProcessKey\n pid: number | null\n claudeSessionId: string | null\n state: ProcessState\n spawnedAt: number\n lastTurnAt: number\n turnCount: number\n /** Last result event's input_tokens — drives tier-2 rotation decisions. */\n lastInputTokens: number\n /** Trace ULID of the in-flight turn, when one is active. */\n pendingTaskId: string | null\n /** Reason the process is dead, when state === \"dead\". */\n deadReason?: string\n /** Managed-marker hash of CLAUDE.md at spawn time (null for unmarked\n * / user-edited files). The drift sweep re-reads the live file's\n * hash and kills idle handles whose hash no longer matches, so the\n * next dispatch picks up the edited prompt. */\n claudeMdHash: string | null\n}\n\nexport interface SpawnOptions {\n agentId: string\n channel: string\n chatId: string\n workspace: string\n model?: string\n permissionMode?: string\n systemPromptAppend?: string\n /** Pass-through to `claude --resume <id>`. Set when SessionStore has a\n * stored claudeSessionId for this chat (e.g. across daemon restart). */\n resumeSessionId?: string\n}\n\nexport interface TurnInput {\n /** User-facing text the parent wrote to stdin under\n * `{type:\"user\", message:{role:\"user\", content:<here>}}`. */\n message: string\n /** Trace ULID for correlating events back to a task_traces row. */\n taskId: string\n}\n\nexport interface TurnEvent {\n /** stream-json event type — one of \"system\" | \"assistant\" | \"user\" |\n * \"result\" | \"stream_event\" | \"rate_limit_event\" | … */\n type: string\n /** Raw event line (parsed). Forwarded verbatim to the streaming\n * parser in registry.ts so trace step capture stays unchanged. */\n raw: Record<string, unknown>\n}\n\nexport interface ProcessHandle {\n readonly key: ProcessKey\n state(): ProcessState\n snapshot(): ProcessSnapshot\n /**\n * Mark the handle as just-acquired: bumps lastTurnAt so the idle\n * sweeper can't kill it in the acquire→runTurn window (the sweep\n * runs every 5s; a handle acquired at idle 899s used to lose that\n * race and fail the dispatch with \"is dead (idle …)\").\n */\n claim?(): void\n /**\n * Send one user turn, yield stream-json events as they arrive, and\n * return when a `result` event is observed. The handle serializes\n * concurrent runTurn calls so two simultaneous tasks on the same\n * chat queue rather than racing.\n */\n runTurn(input: TurnInput): AsyncIterable<TurnEvent>\n /**\n * Kill the underlying process. SIGTERM, then SIGKILL after a grace\n * period. Resolves once the process is gone. `reason` shows up in\n * `agentx process list --include-dead` and the trace step.\n */\n kill(reason: string): Promise<void>\n}\n\nexport interface ProcessFactory {\n /** Spawn (or fake) a process bound to `key` with `opts`. */\n spawn(key: ProcessKey, opts: SpawnOptions): ProcessHandle\n}\n\n/**\n * Thrown by `acquire` when at the global or per-agent cap and no idle\n * handle is evictable. Callers — typically the per-turn driver in\n * runtime.ts — catch this and fall back to spawn-per-task. Using a\n * named error class is more robust than message-sniffing.\n */\nexport class RegistryCapExceeded extends Error {\n constructor(\n message: string,\n public readonly scope: \"global\" | \"agent\",\n public readonly cap: number,\n ) {\n super(message)\n this.name = \"RegistryCapExceeded\"\n }\n}\n\nexport interface ProcessRegistryConfig {\n /** Hard global cap. Default 64. When at cap, oldest idle handle is\n * evicted to make room for the next acquire. */\n maxProcessesGlobal?: number\n /** Per-agent cap. Default 16. Prevents one chatty agent from\n * starving another. */\n maxProcessesPerAgent?: number\n /** A handle is \"idle\" idleTimeoutMs after its last turn completed.\n * Idle handles are eligible for eviction; once `staleTimeoutMs` is\n * also exceeded, the handle is killed unconditionally on the next\n * sweep. Defaults: 30s idle, 45min stale. */\n idleTimeoutMs?: number\n staleTimeoutMs?: number\n /** Sweep interval. Default 5_000 (5s). */\n sweepIntervalMs?: number\n factory: ProcessFactory\n log?: (msg: string) => void\n /** Optional drift detector — called per idle handle on each sweep.\n * Returns the current workspace CLAUDE.md hash for the agent (or\n * null when unmarked / missing). The registry compares against the\n * hash stored on the handle's snapshot at spawn time and kills\n * handles whose hashes don't match.\n *\n * Callers wire this from src/agents/workspace-setup.ts where the\n * managed-marker hash logic already lives — the registry stays\n * decoupled from the marker format itself. When unset, drift\n * detection is disabled (default for tests). */\n currentWorkspaceHash?: (key: ProcessKey, workspace: string) => string | null\n}\n\nexport class ProcessRegistry {\n private handles = new Map<ProcessKeyString, ProcessHandle>()\n private sweeper?: ReturnType<typeof setInterval>\n /** Per-handle workspace path captured at acquire time. Drift sweep\n * re-reads each handle's workspace via the currentWorkspaceHash\n * hook to compare against the stored hash. */\n private handleWorkspace = new Map<ProcessKeyString, string>()\n private readonly cfg: Required<Omit<ProcessRegistryConfig, \"factory\" | \"log\" | \"currentWorkspaceHash\">> & {\n factory: ProcessFactory\n log: (msg: string) => void\n currentWorkspaceHash?: (key: ProcessKey, workspace: string) => string | null\n }\n\n constructor(cfg: ProcessRegistryConfig) {\n this.cfg = {\n maxProcessesGlobal: cfg.maxProcessesGlobal ?? 64,\n maxProcessesPerAgent: cfg.maxProcessesPerAgent ?? 16,\n idleTimeoutMs: cfg.idleTimeoutMs ?? 30_000,\n staleTimeoutMs: cfg.staleTimeoutMs ?? 45 * 60 * 1000,\n sweepIntervalMs: cfg.sweepIntervalMs ?? 5_000,\n factory: cfg.factory,\n log: cfg.log ?? (() => {}),\n currentWorkspaceHash: cfg.currentWorkspaceHash,\n }\n }\n\n /** Begin the idle-eviction sweep timer. Idempotent. */\n start(): void {\n if (this.sweeper) return\n this.sweeper = setInterval(() => this.sweepIdle(), this.cfg.sweepIntervalMs)\n if (typeof this.sweeper.unref === \"function\") this.sweeper.unref()\n }\n\n /**\n * Stop the sweeper and kill every live handle. Used on daemon\n * shutdown. Resolves when all kills complete.\n */\n async stop(): Promise<void> {\n if (this.sweeper) {\n clearInterval(this.sweeper)\n this.sweeper = undefined\n }\n const handles = Array.from(this.handles.values())\n this.handles.clear()\n await Promise.allSettled(handles.map((h) => h.kill(\"registry-stop\")))\n }\n\n /**\n * Get-or-spawn a process for `key`. Spawns through the factory when\n * no handle exists. When at the global or per-agent cap, evicts the\n * oldest idle handle to make room; throws if no idle handle is\n * available (caller can fall back to spawn-per-task or queue).\n */\n acquire(key: ProcessKey, opts: SpawnOptions): ProcessHandle {\n const ks = processKeyToString(key)\n const existing = this.handles.get(ks)\n if (existing && existing.state() !== \"dead\") {\n existing.claim?.()\n return existing\n }\n\n // Make room if at cap.\n if (this.handles.size >= this.cfg.maxProcessesGlobal) {\n this.evictOldestIdle({ scope: \"global\" })\n }\n const perAgentCount = this.countForAgent(key.agentId)\n if (perAgentCount >= this.cfg.maxProcessesPerAgent) {\n this.evictOldestIdle({ scope: \"agent\", agentId: key.agentId })\n }\n if (this.handles.size >= this.cfg.maxProcessesGlobal) {\n throw new RegistryCapExceeded(\n `process registry at global cap (${this.cfg.maxProcessesGlobal}) and no idle handle to evict`,\n \"global\",\n this.cfg.maxProcessesGlobal,\n )\n }\n if (this.countForAgent(key.agentId) >= this.cfg.maxProcessesPerAgent) {\n throw new RegistryCapExceeded(\n `process registry at per-agent cap for ${key.agentId} (${this.cfg.maxProcessesPerAgent}) and no idle handle to evict`,\n \"agent\",\n this.cfg.maxProcessesPerAgent,\n )\n }\n\n const handle = this.cfg.factory.spawn(key, opts)\n this.handles.set(ks, handle)\n this.handleWorkspace.set(ks, opts.workspace)\n this.cfg.log(`[process-registry] spawned ${ks}`)\n return handle\n }\n\n /**\n * Drop a key from the registry, optionally killing the process.\n * Without `kill: true`, the handle stays alive — useful when the\n * caller wants the registry to forget about it (e.g. config reload)\n * but the in-flight turn should finish.\n */\n release(key: ProcessKey, opts: { kill?: boolean; reason?: string } = {}): void {\n const ks = processKeyToString(key)\n const handle = this.handles.get(ks)\n if (!handle) return\n this.handles.delete(ks)\n this.handleWorkspace.delete(ks)\n if (opts.kill) {\n void handle.kill(opts.reason ?? \"release\")\n }\n }\n\n /** Operator surface: snapshot every live handle. */\n list(): ProcessSnapshot[] {\n return Array.from(this.handles.values()).map((h) => h.snapshot())\n }\n\n /** True when a non-dead handle exists for this key. Used by the preflight\n * overage gate so persistent-process agents aren't treated as cold even\n * when no --resume session ID is stored for a given chatId. */\n hasLive(key: ProcessKey): boolean {\n const ks = processKeyToString(key)\n const h = this.handles.get(ks)\n return h != null && h.state() !== \"dead\"\n }\n\n /** Force-kill one handle, removing it from the registry. */\n async kill(key: ProcessKey, reason = \"operator\"): Promise<void> {\n const ks = processKeyToString(key)\n const handle = this.handles.get(ks)\n if (!handle) return\n this.handles.delete(ks)\n this.handleWorkspace.delete(ks)\n await handle.kill(reason)\n }\n\n // ---------- internals ----------\n\n private countForAgent(agentId: string): number {\n let n = 0\n for (const h of this.handles.values()) {\n if (h.key.agentId === agentId && h.state() !== \"dead\") n += 1\n }\n return n\n }\n\n /**\n * Evict the oldest idle handle in the requested scope. \"Oldest\" is\n * defined by lastTurnAt (LRU). Returns true when one was evicted.\n * No-op when no idle handle is found — the caller is responsible\n * for the cap-exceeded error path.\n */\n private evictOldestIdle(scope: { scope: \"global\" } | { scope: \"agent\"; agentId: string }): boolean {\n let target: ProcessHandle | undefined\n let oldest = Number.POSITIVE_INFINITY\n for (const h of this.handles.values()) {\n if (h.state() !== \"idle\") continue\n if (scope.scope === \"agent\" && h.key.agentId !== scope.agentId) continue\n const ts = h.snapshot().lastTurnAt\n if (ts < oldest) {\n oldest = ts\n target = h\n }\n }\n if (!target) return false\n const ks = processKeyToString(target.key)\n this.handles.delete(ks)\n void target.kill(\"evicted (cap reached)\")\n this.cfg.log(`[process-registry] evicted ${ks} (cap)`)\n return true\n }\n\n /**\n * Per-tick sweep — kills idle processes past timeout, reaps dead\n * handles, and detects CLAUDE.md drift on idle handles. Drift\n * kills only fire on idle handles to avoid disturbing a turn that's\n * still streaming; the next sweep catches the handle once it\n * transitions back to idle.\n */\n private sweepIdle(): void {\n const now = Date.now()\n for (const [ks, h] of this.handles.entries()) {\n const snap = h.snapshot()\n if (h.state() === \"dead\") {\n this.handles.delete(ks)\n this.handleWorkspace.delete(ks)\n this.cfg.log(`[process-registry] reaped dead ${ks}`)\n continue\n }\n if (h.state() !== \"idle\") continue\n const idleFor = now - snap.lastTurnAt\n if (idleFor >= this.cfg.staleTimeoutMs) {\n this.handles.delete(ks)\n this.handleWorkspace.delete(ks)\n void h.kill(`stale (idle ${Math.round(idleFor / 1000)}s)`)\n this.cfg.log(`[process-registry] killed stale ${ks}`)\n continue\n }\n if (idleFor >= this.cfg.idleTimeoutMs) {\n this.handles.delete(ks)\n this.handleWorkspace.delete(ks)\n void h.kill(`idle (${Math.round(idleFor / 1000)}s)`)\n this.cfg.log(`[process-registry] killed idle ${ks}`)\n continue\n }\n\n // Drift detection — only when the operator wired a hash hook.\n // Reads the current managed-marker hash from the workspace's\n // CLAUDE.md and kills handles whose hash has changed since\n // spawn, so the next dispatch loads the edited prompt.\n if (this.cfg.currentWorkspaceHash) {\n const workspace = this.handleWorkspace.get(ks)\n if (workspace) {\n let currentHash: string | null = null\n try {\n currentHash = this.cfg.currentWorkspaceHash(h.key, workspace)\n } catch (e: any) {\n // Hash-read failure must not crash the sweeper. Log + skip.\n this.cfg.log(`[process-registry] drift-check failed for ${ks}: ${e?.message || e}`)\n continue\n }\n if (currentHash !== snap.claudeMdHash) {\n this.handles.delete(ks)\n this.handleWorkspace.delete(ks)\n const fromHash = snap.claudeMdHash ?? \"unmarked\"\n const toHash = currentHash ?? \"unmarked\"\n void h.kill(`claude-md drifted (${fromHash} → ${toHash})`)\n this.cfg.log(`[process-registry] killed drifted ${ks} (${fromHash} → ${toHash})`)\n }\n }\n }\n }\n }\n}\n","import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from \"fs\"\nimport { resolve } from \"path\"\nimport type { ChannelAdapter, SeededMessage } from \"@/channels/types\"\nimport { debug } from \"@/observability/debug\"\n\n// --- Conversation session store ---\n// One session per agent + channel + chatId + day.\n// For CLI-backed tiers: stores native session IDs so we can resume them.\n// For other tiers: stores message history as text, prepended to each prompt.\n\nexport interface SessionMessage {\n role: \"user\" | \"agent\"\n name?: string // sender name or agent name\n content: string\n timestamp: string\n /** True when this message was injected by SessionStore.seedFromAdapter()\n * reading the live channel on cold session create — never via the inbound\n * router pipeline. Marked so observers can distinguish seeded turns from\n * live ones; the model sees them identically in buildHistoryContext. */\n seeded?: boolean\n /** Channel-side message id (Telegram update_id, GitLab note id, WhatsApp\n * key.id). Used to dedup re-seeds — if a message with this externalId is\n * already in the session, appendSeededMessages skips it. */\n externalId?: string\n}\n\nexport interface Session {\n id: string\n agentId: string\n channel: string\n chatId: string\n day: string // YYYY-MM-DD\n claudeSessionId?: string // Claude Code native session ID (for --resume)\n codexSessionId?: string // Codex CLI thread/session ID (for exec resume)\n opencodeSessionId?: string // OpenCode native session ID (for run --session)\n messages: SessionMessage[]\n createdAt: string\n updatedAt: string\n /** Number of completed turns on the current claudeSessionId. Resets to 0\n * when the session is rotated (cleared). Used to bound --resume growth:\n * Claude CLI replays the entire prior session on every turn, so long\n * sessions pay a linearly growing cache-read tax. */\n turnCount?: number\n /** CUMULATIVE billable input of the LAST turn on this claudeSessionId\n * (inputTokens + cacheReadTokens + cacheCreateTokens summed across every\n * API call in the agentic loop). Kept for observability/billing only —\n * a tool-heavy turn sums to millions via cache reads, so this must NOT\n * drive rotation decisions. See lastTurnContextTokens. */\n lastTurnInputTokens?: number\n /** End-of-turn CONTEXT size: the last API call's (input + cacheRead +\n * cacheCreate) inside the previous turn — how full the conversation\n * window actually is. This is the tier-2 rotation metric. Absent when\n * the turn ran through a non-streaming path (no per-call usage). */\n lastTurnContextTokens?: number\n}\n\nconst MAX_HISTORY_CHARS = 12000 // Keep last ~12k chars of history to fit in context\nconst MAX_MESSAGES = 30 // Keep last 30 messages max\n/** Default stale timeout. Overridable via SessionStore options so each node\n * can trade cache-hit ratio against \"fresh context\". 12 h matches how chat\n * channels are actually used: a task discussed in the morning must still\n * resume that evening (the old 45 min rotated on every coffee break and\n * read as amnesia); overnight silence starts fresh — with the rotation\n * memo carrying the open task forward. Size-based rotation (tier-2) now\n * measures true per-request context, so a long-lived session no longer\n * needs a short stale window to keep replay bounded. */\nconst DEFAULT_STALE_SESSION_MINUTES = 720\n/** Default hard cap on turns per Claude session — a backstop only. Claude\n * CLI `--resume` replays the prior session on each turn, so replay cost\n * grows with turn count; but the tier-2 check now measures the ACTUAL\n * per-request context, so it catches bloat directly. 40 turns lets an\n * active chat run all day without a mid-task reset. */\nconst DEFAULT_MAX_TURNS_PER_SESSION = 40\n/** Default tier-2 trigger. Claude bills the 1.5× long-context rate when a\n * single REQUEST's input exceeds 200K. Rotating at 180K of measured\n * context leaves headroom for the next turn's additions before we'd\n * enter the multiplier. (Must match the config schema default.) */\nconst DEFAULT_TIER_TWO_THRESHOLD_TOKENS = 180_000\n\n/** Session has a compacted summary prepended to its messages */\nconst COMPACTION_MARKER = \"[Compacted conversation summary\"\n\n/** Continuity memo captured from a dying session at rotation time —\n * injected deterministically into the chat's next fresh session. */\nexport interface RotationMemoRecord {\n memo: string\n reason: string\n capturedAt: string\n}\n\n/** A memo older than this never injects — a week-old \"open task\" brief is\n * more likely to mislead than to help. */\nconst ROTATION_MEMO_TTL_MS = 7 * 86_400_000\n\n/** Keywords that suggest the current message references another chat, an\n * earlier conversation, or a peer agent — and therefore benefits from the\n * cross-chat summary. Tuned for English + Arabic (Tunisian team usage);\n * extend as needed. Matched case-insensitively against raw substrings so\n * \"mentioned\" also catches \"mention\", \"mentioning\", etc. */\nconst CROSS_CHAT_HINT_PATTERNS: RegExp[] = [\n // English — temporal/conversational references\n /\\bearlier\\b/i, /\\bbefore\\b/i, /\\bjust said\\b/i, /\\btold (?:me|you|us|him|her|them)\\b/i,\n /\\bmention(?:ed|ing)?\\b/i, /\\bask(?:ed)? (?:me|you|him|her|them|about)\\b/i,\n /\\bping(?:ed)?\\b/i, /\\bdm\\b/i, /\\bdirect message\\b/i,\n // Explicit cross-chat references\n /\\bother (?:chat|group|conversation|thread)\\b/i, /\\bthat (?:chat|group|conversation|thread)\\b/i,\n /\\bsame (?:issue|thread|conversation)\\b/i, /\\bcontinuing\\b/i,\n // Bot mention — @something_bot / @handle (peer agent reference)\n /@\\w{2,}_?(?:bot|agent)\\b/i,\n // Arabic — conversational refs the team actually uses\n /قلت/, /قال(?:لك|لي|له|لها|لنا)?/, /قبل/, /الحين/, /الثاني/, /المجموعة/,\n]\n\nexport function referencesOtherChat(message: string): boolean {\n if (!message) return false\n return CROSS_CHAT_HINT_PATTERNS.some((re) => re.test(message))\n}\n\n/** Long-memory cues — phrases that imply the user is referencing something\n * beyond the current claude session window. Used by the registry to\n * pre-fetch a wider /recall window before the agent runs, so it doesn't\n * have to ask \"when?\" or guess. Returns the inferred lookback in days, or\n * 0 when no long-memory cue is present (default short-memory regime). */\nconst LONG_MEMORY_PATTERNS: Array<{ re: RegExp; days: number }> = [\n // Days-scale (~1 day)\n { re: /\\byesterday\\b/i, days: 2 },\n { re: /\\bأمس\\b/, days: 2 },\n // Days-scale (~3 days)\n { re: /\\bcouple (?:of )?days\\b/i, days: 4 },\n { re: /\\bfew days\\b/i, days: 4 },\n // Week-scale (~7 days)\n { re: /\\blast week\\b/i, days: 8 },\n { re: /\\bالأسبوع الماضي\\b/, days: 8 },\n { re: /\\bweek ago\\b/i, days: 8 },\n // Generic \"remember/previously/we discussed\" — moderate look-back\n { re: /\\bremember\\b/i, days: 3 },\n { re: /\\bpreviously\\b/i, days: 3 },\n { re: /\\bwe (?:discussed|talked|agreed|decided)\\b/i, days: 3 },\n { re: /\\bas (?:i|we) (?:said|told|mentioned)\\b/i, days: 3 },\n { re: /\\bcontinue from\\b/i, days: 3 },\n { re: /\\bتذكر\\b/, days: 3 },\n]\n\nexport function detectLongMemoryHint(message: string): { lookbackDays: number } | null {\n if (!message) return null\n let max = 0\n for (const p of LONG_MEMORY_PATTERNS) {\n if (p.re.test(message)) max = Math.max(max, p.days)\n }\n return max > 0 ? { lookbackDays: max } : null\n}\n\n/** True when appending `(role, name, content)` would duplicate the last\n * message in `messages`. Used to collapse retries and double-sends at\n * insert time, so dedup is stable across reload (duplicates never reach\n * disk in the first place). */\nfunction isDuplicateOfLast(\n messages: SessionMessage[],\n role: \"user\" | \"agent\",\n name: string,\n content: string,\n): boolean {\n if (messages.length === 0) return false\n const last = messages[messages.length - 1]\n return last.role === role && (last.name ?? \"\") === name && last.content === content\n}\n\n/** Bucket an ISO timestamp into a stable 15-min window label (e.g. \"14:45\").\n * Used in history rendering so the prompt's bucket headers change at most\n * every 15 minutes — the prefix stays byte-stable across intra-bucket\n * messages and the server-side prompt cache is preserved. */\nfunction bucketLabel(iso: string): string {\n const m = iso.slice(11, 16) // HH:MM\n const hh = m.slice(0, 2)\n const mm = parseInt(m.slice(3, 5), 10)\n if (Number.isNaN(mm)) return m\n const qtr = Math.floor(mm / 15) * 15\n return `${hh}:${qtr.toString().padStart(2, \"0\")}`\n}\n\nexport class SessionStore {\n private sessionsDir: string\n private cache: Map<string, Session> = new Map()\n private staleMinutes: number\n private maxTurnsPerSession: number\n private tierTwoThresholdTokens: number\n /** Resolves a channel name to its adapter — installed by the daemon after\n * channels are registered (see daemon/index.ts after router.register). The\n * cold-create branch consults it to call adapter.seedHistory(). Optional;\n * when unset, seeding is a no-op (back-compat for tests, CLI tools, and\n * any caller that constructs SessionStore standalone). */\n private adapterResolver?: (channel: string) => ChannelAdapter | undefined\n /** In-flight seed promises keyed by sessionKey. Coalesces concurrent\n * cold-creates against the same (agent, channel, chatId, day) so we hit\n * the channel API exactly once per fresh session. */\n private seedingPromises: Map<string, Promise<void>> = new Map()\n\n constructor(\n baseDir: string = process.cwd(),\n opts: {\n staleMinutes?: number\n maxTurnsPerSession?: number\n tierTwoThresholdTokens?: number\n } = {},\n ) {\n this.sessionsDir = resolve(baseDir, \".agentx/sessions\")\n if (!existsSync(this.sessionsDir)) {\n mkdirSync(this.sessionsDir, { recursive: true })\n }\n this.staleMinutes = Math.max(1, opts.staleMinutes ?? DEFAULT_STALE_SESSION_MINUTES)\n this.maxTurnsPerSession = Math.max(2, opts.maxTurnsPerSession ?? DEFAULT_MAX_TURNS_PER_SESSION)\n this.tierTwoThresholdTokens = Math.max(50_000, opts.tierTwoThresholdTokens ?? DEFAULT_TIER_TWO_THRESHOLD_TOKENS)\n }\n\n /** Install the channel-adapter resolver. Called once at daemon startup\n * after MessageRouter has registered every adapter. Hot-reload of channel\n * adapters re-uses the same router, so the resolver stays valid across\n * /reload cycles without re-installation. */\n setAdapterResolver(fn: (channel: string) => ChannelAdapter | undefined): void {\n this.adapterResolver = fn\n }\n\n /** Seed a fresh session from the live channel before any user/agent\n * messages get appended. No-op when:\n * - the session already has messages (warm cache or partially loaded)\n * - no adapter resolver is installed\n * - the channel adapter has no seedHistory method\n * Concurrent calls against the same key share one in-flight promise so\n * the channel API is hit exactly once per cold create. Errors are\n * swallowed — a seeding failure should never block a turn. */\n async seedIfEmpty(agentId: string, channel: string, chatId: string): Promise<void> {\n if (!this.adapterResolver) return\n const key = this.sessionKey(agentId, channel, chatId)\n const inFlight = this.seedingPromises.get(key)\n if (inFlight) return inFlight\n\n const session = this.getSession(agentId, channel, chatId)\n if (session.messages.length > 0) return\n\n const adapter = this.adapterResolver(channel)\n if (!adapter?.seedHistory) return\n\n const promise = (async () => {\n try {\n const seeds = await adapter.seedHistory!(chatId, {\n maxMessages: MAX_MESSAGES,\n maxChars: MAX_HISTORY_CHARS,\n })\n if (seeds.length > 0) this.appendSeededMessages(session, seeds)\n } catch {\n // best-effort\n }\n })()\n this.seedingPromises.set(key, promise)\n try {\n await promise\n } finally {\n this.seedingPromises.delete(key)\n }\n }\n\n /** Append seeded messages to a session in chronological order, deduping\n * by externalId against anything already present. Marks each entry with\n * `seeded: true` so observers can distinguish them; the model's prompt\n * (buildHistoryContext) renders them identically to live turns. */\n private appendSeededMessages(session: Session, seeds: SeededMessage[]): void {\n if (seeds.length === 0) return\n const existingIds = new Set<string>()\n for (const m of session.messages) {\n if (m.externalId) existingIds.add(m.externalId)\n }\n let appended = 0\n for (const s of seeds) {\n if (s.externalId && existingIds.has(s.externalId)) continue\n session.messages.push({\n role: s.role,\n name: s.name,\n content: s.content,\n timestamp: s.timestamp,\n seeded: true,\n externalId: s.externalId,\n })\n appended++\n }\n if (appended === 0) return\n this.trim(session)\n session.updatedAt = new Date().toISOString()\n this.save(session)\n }\n\n /**\n * Build a deterministic session key.\n */\n private sessionKey(agentId: string, channel: string, chatId: string): string {\n const day = new Date().toISOString().slice(0, 10) // YYYY-MM-DD\n return `${agentId}:${channel}:${chatId}:${day}`\n }\n\n private sessionFile(key: string): string {\n // Sanitize key for filesystem\n const safe = key.replace(/[^a-zA-Z0-9_:-]/g, \"_\")\n return resolve(this.sessionsDir, `${safe}.json`)\n }\n\n /**\n * Get or create a session for this agent+channel+chat+day.\n */\n getSession(agentId: string, channel: string, chatId: string): Session {\n const key = this.sessionKey(agentId, channel, chatId)\n\n // Check cache\n if (this.cache.has(key)) {\n return this.cache.get(key)!\n }\n\n // Try loading from disk\n const file = this.sessionFile(key)\n if (existsSync(file)) {\n try {\n const data = JSON.parse(readFileSync(file, \"utf-8\")) as Session\n this.cache.set(key, data)\n return data\n } catch {\n // Corrupted file — start fresh\n }\n }\n\n // Create new session. The key is day-scoped, so without carry-over a\n // conversation crossing UTC midnight would silently lose its --resume\n // id — guaranteed daily amnesia even mid-task. Seed continuity\n // metadata (native session ids + rotation counters + updatedAt) from\n // yesterday's record; the normal stale/tier-2/max-turns checks then\n // decide whether to actually resume. Messages stay day-scoped.\n const prev = this.loadPreviousDaySession(agentId, channel, chatId)\n const day = new Date().toISOString().slice(0, 10)\n const session: Session = {\n id: key,\n agentId,\n channel,\n chatId,\n day,\n messages: [],\n createdAt: new Date().toISOString(),\n updatedAt: prev?.updatedAt ?? new Date().toISOString(),\n claudeSessionId: prev?.claudeSessionId,\n codexSessionId: prev?.codexSessionId,\n turnCount: prev?.turnCount,\n lastTurnInputTokens: prev?.lastTurnInputTokens,\n lastTurnContextTokens: prev?.lastTurnContextTokens,\n }\n\n this.cache.set(key, session)\n this.save(session)\n return session\n }\n\n /** Yesterday's (UTC) session record for the same triple, or null. Only\n * one day back — a gap of 2+ days is stale at any sane staleMinutes,\n * so scanning further would never change the outcome. */\n private loadPreviousDaySession(agentId: string, channel: string, chatId: string): Session | null {\n const prevDay = new Date(Date.now() - 86_400_000).toISOString().slice(0, 10)\n const prevKey = `${agentId}:${channel}:${chatId}:${prevDay}`\n const file = this.sessionFile(prevKey)\n if (!existsSync(file)) return null\n try {\n return JSON.parse(readFileSync(file, \"utf-8\")) as Session\n } catch {\n return null\n }\n }\n\n /**\n * Add a user message to the session. Consecutive duplicates from the same\n * sender are collapsed — when a user re-sends the exact same text (retry,\n * accidental double-tap, telegram glitches), storing every copy bloats the\n * replayed history and re-burns tokens on every subsequent turn.\n */\n addUserMessage(agentId: string, channel: string, chatId: string, senderName: string, content: string): void {\n const session = this.getSession(agentId, channel, chatId)\n if (isDuplicateOfLast(session.messages, \"user\", senderName, content)) {\n return\n }\n session.messages.push({\n role: \"user\",\n name: senderName,\n content,\n timestamp: new Date().toISOString(),\n })\n this.trim(session)\n session.updatedAt = new Date().toISOString()\n this.save(session)\n }\n\n /**\n * Pop the last user message if it matches `expectedContent` (exact equality\n * after trim). Used by the operator Update flow when Stop was pressed\n * first — the cancelled user turn has no agent reply, so leaving it in\n * history makes the next Update read as a bare orphan ask. Match guard\n * prevents removing something a concurrent path just added.\n */\n removeLastUserMessageIfMatches(\n agentId: string,\n channel: string,\n chatId: string,\n expectedContent: string,\n ): boolean {\n const session = this.getSession(agentId, channel, chatId)\n const last = session.messages[session.messages.length - 1]\n if (!last || last.role !== \"user\") return false\n if ((last.content || \"\").trim() !== (expectedContent || \"\").trim()) return false\n session.messages.pop()\n session.updatedAt = new Date().toISOString()\n this.save(session)\n return true\n }\n\n /**\n * Add an agent response to the session. Same dedup rule as user messages —\n * retries and duplicate responses shouldn't be replayed on every turn.\n */\n addAgentMessage(agentId: string, channel: string, chatId: string, content: string): void {\n const session = this.getSession(agentId, channel, chatId)\n if (isDuplicateOfLast(session.messages, \"agent\", agentId, content)) {\n return\n }\n session.messages.push({\n role: \"agent\",\n name: agentId,\n content,\n timestamp: new Date().toISOString(),\n })\n this.trim(session)\n session.updatedAt = new Date().toISOString()\n this.save(session)\n }\n\n /**\n * Get the Claude Code session ID for this conversation (for --resume).\n * Returns undefined if no session yet (first message of the day).\n */\n getClaudeSessionId(agentId: string, channel: string, chatId: string): string | undefined {\n const session = this.getSession(agentId, channel, chatId)\n return session.claudeSessionId\n }\n\n /**\n * Store the Claude Code session ID after first invocation.\n */\n setClaudeSessionId(agentId: string, channel: string, chatId: string, claudeSessionId: string): void {\n const session = this.getSession(agentId, channel, chatId)\n session.claudeSessionId = claudeSessionId\n session.updatedAt = new Date().toISOString()\n this.save(session)\n }\n\n getCodexSessionId(agentId: string, channel: string, chatId: string): string | undefined {\n const session = this.getSession(agentId, channel, chatId)\n return session.codexSessionId\n }\n\n setCodexSessionId(agentId: string, channel: string, chatId: string, codexSessionId: string): void {\n const session = this.getSession(agentId, channel, chatId)\n session.codexSessionId = codexSessionId\n session.updatedAt = new Date().toISOString()\n this.save(session)\n }\n\n getOpenCodeSessionId(agentId: string, channel: string, chatId: string): string | undefined {\n const session = this.getSession(agentId, channel, chatId)\n return session.opencodeSessionId\n }\n\n setOpenCodeSessionId(agentId: string, channel: string, chatId: string, opencodeSessionId: string): void {\n const session = this.getSession(agentId, channel, chatId)\n session.opencodeSessionId = opencodeSessionId\n session.updatedAt = new Date().toISOString()\n this.save(session)\n }\n\n /**\n * Build conversation history string to prepend to the prompt.\n * Returns empty string if no history.\n * Used for non-claude-code tiers that don't have native sessions.\n */\n buildHistoryContext(\n agentId: string,\n channel: string,\n chatId: string,\n opts: { maxMessages?: number; maxChars?: number } = {},\n ): string {\n const session = this.getSession(agentId, channel, chatId)\n if (session.messages.length === 0) return \"\"\n const maxMessages = Math.max(1, opts.maxMessages ?? MAX_MESSAGES)\n const maxChars = Math.max(200, opts.maxChars ?? MAX_HISTORY_CHARS)\n const messages: SessionMessage[] = []\n let chars = 0\n for (let i = session.messages.length - 1; i >= 0 && messages.length < maxMessages; i--) {\n const msg = session.messages[i]\n chars += msg.content.length\n messages.push(msg)\n if (chars >= maxChars) break\n }\n messages.reverse()\n\n const lines: string[] = [\n `[Conversation history for today (${session.day})]`,\n ]\n\n // Bucket messages into 15-min windows. Stamping every line with its own\n // HH:MM broke the server-side prompt cache every turn (each new message\n // adds a never-seen timestamp near the tail). A bucket header changes\n // only every 15 min, so most turns within the window preserve cache.\n let currentBucket = \"\"\n for (const msg of messages) {\n const bucket = bucketLabel(msg.timestamp)\n if (bucket !== currentBucket) {\n lines.push(`— ${bucket} —`)\n currentBucket = bucket\n }\n const name = msg.name || (msg.role === \"user\" ? \"User\" : \"Agent\")\n lines.push(`${name}: ${msg.content}`)\n }\n\n lines.push(\"[End of history — respond to the latest message above]\")\n lines.push(\"\")\n\n return lines.join(\"\\n\")\n }\n\n /**\n * Recall conversation turns across multiple stored sessions for an agent.\n *\n * Used by the `/recall` HTTP endpoint and the [Conversation Recall] skill —\n * when an agent receives a message that references prior context (anaphora,\n * \"as discussed\", \"correct me if I'm wrong\"), it can call this to fetch\n * the actual prior turns rather than guessing or running cold-start tool\n * calls. Read-only; no side effects.\n *\n * Pagination: cursor by timestamp, descending (newest first). Pass the\n * previous response's `oldestTs` as `before` to walk further back.\n *\n * Scoping: required `agentId`. `channel`/`chatId` narrow to a specific\n * thread; omit them to recall across all of this agent's conversations.\n * The store is per-agent on disk so we never leak another agent's chats.\n */\n recallTurns(opts: {\n agentId: string\n channel?: string\n chatId?: string\n before?: string\n after?: string\n /** Convenience override for `after`: number of days back from now. */\n lookbackDays?: number\n limit?: number\n query?: string\n participants?: string[]\n }): {\n turns: Array<{\n ts: string\n role: \"user\" | \"agent\"\n senderName: string\n content: string\n channel: string\n chatId: string\n day: string\n sessionFile: string\n }>\n oldestTs: string | null\n hasMore: boolean\n totalScanned: number\n } {\n const limit = Math.min(Math.max(opts.limit ?? 10, 1), 100)\n // Default window: TODAY (UTC). Per the short-vs-long-memory split, most\n // recall calls are about \"what did we just say\" — capping the window to\n // today keeps the result tight and noise-free. Caller widens explicitly\n // when the user message signals long memory (\"yesterday\", \"last week\",\n // \"remember when…\") via `lookbackDays` or an explicit `after`.\n const todayStart = new Date()\n todayStart.setUTCHours(0, 0, 0, 0)\n const defaultAfter =\n typeof opts.lookbackDays === \"number\" && opts.lookbackDays > 0\n ? new Date(Date.now() - opts.lookbackDays * 24 * 60 * 60 * 1000).toISOString()\n : todayStart.toISOString()\n const after = opts.after ?? defaultAfter\n const before = opts.before ?? new Date(Date.now() + 1000).toISOString()\n\n const safeAgentId = opts.agentId.replace(/[^a-zA-Z0-9_:-]/g, \"_\")\n let allFiles: string[]\n try {\n allFiles = readdirSync(this.sessionsDir).filter(f =>\n f.startsWith(`${safeAgentId}:`) && f.endsWith(\".json\"),\n )\n } catch {\n return { turns: [], oldestTs: null, hasMore: false, totalScanned: 0 }\n }\n\n // Day-level prefilter via the trailing `:YYYY-MM-DD.json` segment so we\n // don't open/parse files outside the requested window.\n const afterDay = after.slice(0, 10)\n const beforeDay = before.slice(0, 10)\n const dayRe = /:(\\d{4}-\\d{2}-\\d{2})\\.json$/\n const candidateFiles = allFiles.filter(f => {\n const m = f.match(dayRe)\n if (!m) return false\n const day = m[1]\n return day >= afterDay && day <= beforeDay\n })\n\n type CollectedTurn = {\n ts: string\n role: \"user\" | \"agent\"\n senderName: string\n content: string\n channel: string\n chatId: string\n day: string\n sessionFile: string\n }\n const collected: CollectedTurn[] = []\n let totalScanned = 0\n const queryLower = opts.query?.toLowerCase()\n const participantSet = opts.participants?.length ? new Set(opts.participants) : undefined\n\n for (const file of candidateFiles) {\n let data: Session\n try {\n data = JSON.parse(readFileSync(resolve(this.sessionsDir, file), \"utf-8\")) as Session\n } catch {\n continue\n }\n if (data.agentId !== opts.agentId) continue\n if (opts.channel && data.channel !== opts.channel) continue\n if (opts.chatId && data.chatId !== opts.chatId) continue\n\n for (const msg of data.messages) {\n totalScanned++\n if (!msg.timestamp || msg.timestamp < after || msg.timestamp >= before) continue\n if (participantSet && msg.role === \"user\" && !participantSet.has(msg.name ?? \"\")) continue\n if (queryLower && !msg.content.toLowerCase().includes(queryLower)) continue\n collected.push({\n ts: msg.timestamp,\n role: msg.role,\n senderName: msg.name ?? \"\",\n content: msg.content,\n channel: data.channel,\n chatId: data.chatId,\n day: data.day,\n sessionFile: file,\n })\n }\n }\n\n collected.sort((a, b) => b.ts.localeCompare(a.ts))\n\n const page = collected.slice(0, limit)\n const oldestTs = page.length > 0 ? page[page.length - 1].ts : null\n const hasMore = collected.length > limit\n\n return { turns: page, oldestTs, hasMore, totalScanned }\n }\n\n /** Cross-agent view of a chat. Aggregates session messages from EVERY\n * agent that has a session for `(channel, chatId)` in the requested\n * window, sorts oldest-first, and dedups exact (timestamp, role, name,\n * content) collisions (which happen when two agents in the same group\n * both record the same inbound).\n *\n * Differs from recallTurns in two ways:\n * 1. Not scoped to one agentId — answers \"what's in this chat across\n * all agents\", which is the question the cx→devops→marketing thread\n * on 2026-04-29 needed and didn't have.\n * 2. Returns oldest-first so the caller reads it like a transcript;\n * recallTurns returns newest-first because it paginates.\n *\n * Bounded by the same MAX_MESSAGES default as buildHistoryContext so\n * callers don't accidentally pull a 5000-turn group into one prompt. */\n recentByChatId(opts: {\n channel: string\n chatId: string\n sinceISO?: string\n limit?: number\n }): Array<{\n ts: string\n role: \"user\" | \"agent\"\n senderName: string\n content: string\n agentId: string\n sessionFile: string\n }> {\n const limit = Math.min(Math.max(opts.limit ?? MAX_MESSAGES, 1), 200)\n // Default window: last 24h. Wider than recallTurns' \"today only\" because\n // the typical use is \"what was just said\" — covers overnight gaps that\n // span midnight UTC.\n const sinceISO = opts.sinceISO ?? new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()\n const sinceDay = sinceISO.slice(0, 10)\n\n let allFiles: string[]\n try {\n allFiles = readdirSync(this.sessionsDir).filter((f) => f.endsWith(\".json\"))\n } catch {\n return []\n }\n\n // The session-file naming convention is `{agentId}:{channel}:{chatId}:{day}.json`.\n // Build a substring key that captures channel+chatId; the agentId prefix\n // and day suffix vary so we filter those after parse. Note `chatId` may\n // itself contain colons (GitLab chatIds look like \"group/project:issue:123\"),\n // so we can't naively split on \":\". Cheap pre-filter: must contain\n // `:${channel}:${chatId}:` as a substring.\n const key = `:${opts.channel}:${opts.chatId}:`\n const candidates = allFiles.filter((f) => {\n if (!f.includes(key)) return false\n // Day suffix prefilter — skip files whose day is older than sinceDay.\n const m = f.match(/:(\\d{4}-\\d{2}-\\d{2})\\.json$/)\n if (!m) return false\n return m[1] >= sinceDay\n })\n\n type Row = {\n ts: string\n role: \"user\" | \"agent\"\n senderName: string\n content: string\n agentId: string\n sessionFile: string\n }\n const rows: Row[] = []\n for (const file of candidates) {\n let data: Session\n try {\n data = JSON.parse(readFileSync(resolve(this.sessionsDir, file), \"utf-8\")) as Session\n } catch {\n continue\n }\n // Defensive re-check after parse — substring match could collide with\n // an agent whose id literally contains \":telegram:1816212449:\".\n if (data.channel !== opts.channel || data.chatId !== opts.chatId) continue\n\n for (const msg of data.messages) {\n if (!msg.timestamp || msg.timestamp < sinceISO) continue\n rows.push({\n ts: msg.timestamp,\n role: msg.role,\n senderName: msg.name ?? \"\",\n content: msg.content,\n agentId: data.agentId,\n sessionFile: file,\n })\n }\n }\n\n // Dedup exact collisions across agents (same inbound recorded by two\n // agents in the same group). Key on (ts, role, name, content).\n const seen = new Set<string>()\n const dedupped: Row[] = []\n for (const r of rows) {\n const k = `${r.ts}|${r.role}|${r.senderName}|${r.content}`\n if (seen.has(k)) continue\n seen.add(k)\n dedupped.push(r)\n }\n\n dedupped.sort((a, b) => a.ts.localeCompare(b.ts))\n // Take the most recent `limit` (last entries when sorted oldest-first).\n return dedupped.slice(-limit)\n }\n\n /**\n * Check if a native CLI session is stale (idle too long for resume to be useful).\n * When stale, the resumed context will be too far behind — better to start fresh.\n */\n isSessionStale(agentId: string, channel: string, chatId: string): boolean {\n const session = this.getSession(agentId, channel, chatId)\n if (!session.claudeSessionId && !session.codexSessionId && !session.opencodeSessionId) return false\n const elapsed = Date.now() - new Date(session.updatedAt).getTime()\n return elapsed > this.staleMinutes * 60 * 1000\n }\n\n /**\n * Build a summary of recent messages from OTHER sessions for the same agent today.\n * This bridges the cross-chat amnesia gap — if someone shared info in a DM,\n * the group session gets a hint about it.\n *\n * Gated by `message`: cross-chat hints are expensive (up to ~1.5K tokens of\n * unrelated conversation) AND cache-breaking (the content changes as other\n * chats get traffic, invalidating the prompt cache every turn). We only\n * return hints when the current message actually references another\n * conversation or a peer agent. Pass `message = \"\"` to force-include\n * (legacy behavior) — useful for testing.\n */\n getCrossSessionSummary(\n agentId: string,\n channel: string,\n chatId: string,\n message?: string,\n ): string {\n if (message !== undefined && message !== \"\" && !referencesOtherChat(message)) {\n return \"\"\n }\n const day = new Date().toISOString().slice(0, 10)\n const currentKey = this.sessionKey(agentId, channel, chatId)\n\n try {\n const files = readdirSync(this.sessionsDir).filter(f =>\n f.startsWith(agentId.replace(/[^a-zA-Z0-9_:-]/g, \"_\")) &&\n f.includes(day) &&\n f.endsWith(\".json\")\n )\n\n const hints: string[] = []\n\n for (const file of files) {\n try {\n const data = JSON.parse(readFileSync(resolve(this.sessionsDir, file), \"utf-8\")) as Session\n if (data.id === currentKey) continue // skip own session\n if (data.messages.length === 0) continue\n\n // Take the last few messages as a hint\n const recent = data.messages.slice(-4)\n const isDM = !data.chatId.startsWith(\"-\") && /^\\d+$/.test(data.chatId)\n const scope = isDM ? \"DM\" : data.chatId\n const lines = recent.map(m => {\n const time = m.timestamp.slice(11, 16)\n return ` [${time}] ${m.name || m.role}: ${m.content.slice(0, 200)}`\n })\n hints.push(`From ${scope} (${data.channel}):\\n${lines.join(\"\\n\")}`)\n } catch {\n // Skip corrupted files\n }\n }\n\n if (hints.length === 0) return \"\"\n\n return [\n \"[Cross-chat context — recent activity from your other conversations today]\",\n ...hints,\n \"[End cross-chat context — the user in THIS chat may be referring to actions from above]\",\n \"\",\n ].join(\"\\n\")\n } catch {\n return \"\"\n }\n }\n\n /**\n * Check if session history needs compaction and compact if so.\n * Call this after adding messages. Returns true if compaction was performed.\n */\n async compactIfNeeded(\n agentId: string,\n channel: string,\n chatId: string,\n memoryStore?: import(\"./memory-store\").MemoryStore,\n ): Promise<{ compacted: boolean; qualityScore?: number; lostEntities?: string[]; drift?: import(\"./drift-detection\").DriftReport }> {\n const session = this.getSession(agentId, channel, chatId)\n const { needsCompaction, compactSession, applyCompaction } = await import(\"./compaction\")\n\n if (!needsCompaction(session.messages)) return { compacted: false }\n\n const result = await compactSession(session.messages, agentId, memoryStore)\n if (result.compactedCount === 0) return { compacted: false }\n\n session.messages = applyCompaction(session.messages, result)\n session.updatedAt = new Date().toISOString()\n\n // We deliberately KEEP native session IDs, turnCount, and lastTurnInputTokens.\n //\n // Why: AgentX's `session.messages` is a stored copy used to seed a fresh\n // Claude session whenever one legitimately rotates (tier-2, max-turns,\n // stale, restart). Claude's own session has its own copy of the\n // conversation that --resume replays — it doesn't read from\n // session.messages. So compacting our copy doesn't reduce what Claude\n // sees on resume; dropping claudeSessionId here was just paying a fresh\n // ~16K-token cache-create tax on the next call without any context\n // benefit. Particularly painful for bursty workloads (project-bot\n // queues) where each burst trips compaction and forces N cache rebuilds\n // per Max-plan window.\n //\n // The compacted summary gets used at the next legitimate rotation —\n // exactly when it matters. Until then, Claude keeps its hot session.\n\n this.save(session)\n return {\n compacted: true,\n qualityScore: result.qualityScore,\n lostEntities: result.lostEntities,\n drift: result.drift,\n }\n }\n\n /**\n * Check if current session already has a compacted summary.\n */\n hasCompaction(agentId: string, channel: string, chatId: string): boolean {\n const session = this.getSession(agentId, channel, chatId)\n return session.messages.length > 0 &&\n session.messages[0].content.startsWith(COMPACTION_MARKER)\n }\n\n /**\n * Clear stored native CLI session IDs so next invocation starts fresh.\n * Also resets the per-session turn counter and last-turn input size —\n * those are only meaningful relative to the current native session.\n */\n clearClaudeSessionId(agentId: string, channel: string, chatId: string): void {\n const session = this.getSession(agentId, channel, chatId)\n delete session.claudeSessionId\n delete session.codexSessionId\n delete session.opencodeSessionId\n delete session.turnCount\n delete session.lastTurnInputTokens\n delete session.lastTurnContextTokens\n session.updatedAt = new Date().toISOString()\n this.save(session)\n }\n\n /**\n * Reset the entire AgentX-side conversation for a delegated fresh session.\n * Claude needs the native session id cleared; Codex and other non-native\n * runtimes also need the rendered message history removed or they will see\n * stale turns through buildHistoryContext().\n */\n clearSession(agentId: string, channel: string, chatId: string): void {\n const session = this.getSession(agentId, channel, chatId)\n session.messages = []\n delete session.claudeSessionId\n delete session.codexSessionId\n delete session.opencodeSessionId\n delete session.turnCount\n delete session.lastTurnInputTokens\n delete session.lastTurnContextTokens\n session.updatedAt = new Date().toISOString()\n this.save(session)\n }\n\n /**\n * Check whether this session has hit the max-turns cap. Rotate when true\n * to stop --resume replay from growing unbounded across a long chat.\n */\n shouldRotateByTurns(agentId: string, channel: string, chatId: string): boolean {\n const session = this.getSession(agentId, channel, chatId)\n if (!session.claudeSessionId && !session.codexSessionId && !session.opencodeSessionId) return false\n return (session.turnCount ?? 0) >= this.maxTurnsPerSession\n }\n\n /**\n * Check whether the LAST turn left the conversation context near the\n * tier-2 boundary. Uses `lastTurnContextTokens` (the final API call's\n * per-request input — the true context size) when the runtime captured\n * it; falls back to the cumulative turn total only when it didn't.\n * The cumulative number sums cache reads across every call in an\n * agentic turn (easily 10-20× the real context), so preferring the\n * per-call metric is what stops rotation from firing on every\n * tool-heavy turn — the \"agent forgets mid-task\" bug.\n */\n shouldRotateByTierTwo(agentId: string, channel: string, chatId: string): boolean {\n const session = this.getSession(agentId, channel, chatId)\n if (!session.claudeSessionId && !session.codexSessionId && !session.opencodeSessionId) return false\n const contextSize = session.lastTurnContextTokens ?? session.lastTurnInputTokens ?? 0\n return contextSize >= this.tierTwoThresholdTokens\n }\n\n /**\n * Record a completed turn's token usage and bump the turn counter.\n * Called after a successful native CLI response returns with usage info.\n * `contextTokens` is the end-of-turn per-request context size (see\n * AgentResponse.contextTokens); undefined on non-streaming paths.\n */\n recordTurnUsage(\n agentId: string,\n channel: string,\n chatId: string,\n usage: { inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheCreateTokens: number },\n contextTokens?: number,\n ): void {\n const session = this.getSession(agentId, channel, chatId)\n session.turnCount = (session.turnCount ?? 0) + 1\n session.lastTurnInputTokens =\n (usage.inputTokens || 0) + (usage.cacheReadTokens || 0) + (usage.cacheCreateTokens || 0)\n // Overwrite (including back to undefined) rather than keep a stale\n // value: a non-streaming turn after a streaming one must not reuse\n // the older context reading.\n session.lastTurnContextTokens = contextTokens\n session.updatedAt = new Date().toISOString()\n this.save(session)\n }\n\n // --- Rotation memos -----------------------------------------------------\n //\n // When a session rotates, a Haiku call distills the dying session into a\n // continuity memo (rotation-memo.ts). Storing it ONLY in MemoryStore made\n // carry-over BM25-luck: the memo rarely scored high enough to surface on\n // the very next turn, so rotation still read as amnesia. This map keeps\n // the latest memo per chat for DETERMINISTIC injection into the next\n // fresh session. Overwritten on each rotation; TTL guards ancient memos.\n\n private rotationMemoFile(): string {\n return resolve(this.sessionsDir, \"_rotation-memos.json\")\n }\n\n private readRotationMemos(): Record<string, RotationMemoRecord> {\n try {\n const parsed = JSON.parse(readFileSync(this.rotationMemoFile(), \"utf-8\"))\n return parsed && typeof parsed === \"object\" ? parsed : {}\n } catch {\n return {}\n }\n }\n\n setRotationMemo(agentId: string, channel: string, chatId: string, memo: string, reason: string): void {\n const memos = this.readRotationMemos()\n memos[`${agentId}:${channel}:${chatId}`] = {\n memo,\n reason,\n capturedAt: new Date().toISOString(),\n }\n writeFileSync(this.rotationMemoFile(), JSON.stringify(memos, null, 2))\n }\n\n /** Latest memo for this chat, or null when absent/expired (7-day TTL). */\n getRotationMemo(agentId: string, channel: string, chatId: string): RotationMemoRecord | null {\n const rec = this.readRotationMemos()[`${agentId}:${channel}:${chatId}`]\n if (!rec?.memo) return null\n const age = Date.now() - Date.parse(rec.capturedAt)\n if (!Number.isFinite(age) || age > ROTATION_MEMO_TTL_MS) return null\n return rec\n }\n\n /** Diagnostic getters — used by registry logging. */\n getTurnCount(agentId: string, channel: string, chatId: string): number {\n return this.getSession(agentId, channel, chatId).turnCount ?? 0\n }\n getLastTurnInputTokens(agentId: string, channel: string, chatId: string): number {\n return this.getSession(agentId, channel, chatId).lastTurnInputTokens ?? 0\n }\n /** The rotation metric shouldRotateByTierTwo actually used: per-request\n * context size when available, else the cumulative fallback. */\n getLastTurnContextTokens(agentId: string, channel: string, chatId: string): number {\n const s = this.getSession(agentId, channel, chatId)\n return s.lastTurnContextTokens ?? s.lastTurnInputTokens ?? 0\n }\n getMaxTurnsPerSession(): number { return this.maxTurnsPerSession }\n getTierTwoThresholdTokens(): number { return this.tierTwoThresholdTokens }\n\n /**\n * Trim session to stay within limits.\n */\n private trim(session: Session): void {\n const beforeCount = session.messages.length\n // Trim by message count\n if (session.messages.length > MAX_MESSAGES) {\n session.messages = session.messages.slice(-MAX_MESSAGES)\n }\n\n // Trim by total character count\n let totalChars = session.messages.reduce((sum, m) => sum + m.content.length, 0)\n while (totalChars > MAX_HISTORY_CHARS && session.messages.length > 2) {\n const removed = session.messages.shift()!\n totalChars -= removed.content.length\n }\n\n // Surface silent drops under `--debug context`. Without this, a long\n // chat that rolls past the 30-message / 12K-char caps quietly loses\n // older turns and the operator has no signal — exactly the failure\n // mode that disguises itself as \"agent forgot\" amnesia.\n const dropped = beforeCount - session.messages.length\n if (dropped > 0) {\n debug.cat(\n \"context\",\n `[${session.agentId}] ${session.channel}:${session.chatId} trim dropped ${dropped} message(s): ${beforeCount} -> ${session.messages.length} (${totalChars}c)`,\n )\n }\n }\n\n private save(session: Session): void {\n try {\n const file = this.sessionFile(session.id)\n writeFileSync(file, JSON.stringify(session, null, 2))\n } catch {\n // Best-effort persistence\n }\n }\n}\n","// --- Rate limiter for agent tasks ---\n// Prevents message floods from exhausting Claude API/subscription.\n// Per-agent, sliding window. `acquire()` queues callers when the window is\n// full instead of failing them outright — good for chatty channels where\n// dropping a message would look like the bot is broken.\n\nexport class RateLimiter {\n private windows: Map<string, number[]> = new Map()\n private maxPerMinute: number\n private maxPerHour: number\n\n constructor(maxPerMinute: number = 10, maxPerHour: number = 100) {\n this.maxPerMinute = maxPerMinute\n this.maxPerHour = maxPerHour\n }\n\n /**\n * Check if an agent can process a task right now. Does NOT wait. Call\n * `acquire()` if you want queueing behaviour.\n */\n check(agentId: string): { allowed: boolean; reason?: string; waitMs?: number } {\n const now = Date.now()\n const timestamps = this.windows.get(agentId) || []\n\n // Clean old entries (older than 1 hour).\n const hourAgo = now - 3600_000\n const recent = timestamps.filter((t) => t > hourAgo)\n\n // Per-minute window.\n const minuteAgo = now - 60_000\n const inMinute = recent.filter((t) => t > minuteAgo)\n if (inMinute.length >= this.maxPerMinute) {\n const oldest = inMinute[0]\n const waitMs = Math.max(50, oldest + 60_000 - now)\n return {\n allowed: false,\n reason: `Rate limit: ${inMinute.length}/${this.maxPerMinute} per minute`,\n waitMs,\n }\n }\n\n // Per-hour window.\n if (recent.length >= this.maxPerHour) {\n const oldest = recent[0]\n const waitMs = Math.max(1000, oldest + 3600_000 - now)\n return {\n allowed: false,\n reason: `Rate limit: ${recent.length}/${this.maxPerHour} per hour`,\n waitMs,\n }\n }\n\n // Record this slot and allow.\n recent.push(now)\n this.windows.set(agentId, recent)\n return { allowed: true }\n }\n\n /**\n * Wait until a slot opens for this agent, then record + return ok. If the\n * caller exceeds `maxWaitMs`, give up with the last rate-limit reason. A\n * concurrent task that acquires a slot first will push THIS task's wake-up\n * further out — that's intended: FIFO-ish through the agent's normal\n * message queue, no starvation in practice.\n *\n * `onWait` fires once at the start of waiting so callers can surface the\n * \"queued\" state (logging, dashboard hint, etc).\n */\n async acquire(\n agentId: string,\n opts: { maxWaitMs?: number; onWait?: (reason: string, waitMs: number) => void } = {},\n ): Promise<{ ok: true } | { ok: false; reason: string }> {\n const maxWaitMs = opts.maxWaitMs ?? 5 * 60_000\n const deadline = Date.now() + maxWaitMs\n let firstWait = true\n\n while (true) {\n const r = this.check(agentId)\n if (r.allowed) return { ok: true }\n if (Date.now() >= deadline) {\n return { ok: false, reason: `${r.reason} (waited ${Math.round(maxWaitMs / 1000)}s, giving up)` }\n }\n if (firstWait) {\n firstWait = false\n opts.onWait?.(r.reason!, r.waitMs!)\n }\n // Cap the sleep so concurrent waiters re-check often enough to grab the\n // next slot in turn, rather than all firing at the same instant.\n const remaining = deadline - Date.now()\n const sleepMs = Math.min(r.waitMs ?? 1000, remaining, 5_000)\n await new Promise((resolve) => setTimeout(resolve, sleepMs))\n }\n }\n\n /**\n * Current usage for an agent — used by the dashboard and `agentx usage`.\n */\n usage(agentId: string): { lastMinute: number; lastHour: number } {\n const now = Date.now()\n const timestamps = this.windows.get(agentId) || []\n return {\n lastMinute: timestamps.filter((t) => t > now - 60_000).length,\n lastHour: timestamps.filter((t) => t > now - 3600_000).length,\n }\n }\n}\n","import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync } from \"fs\"\nimport { resolve } from \"path\"\n\n// --- Token usage tracker ---\n// Tracks token usage per agent per day with cache-aware, tier-aware cost\n// calculation. Persisted to .agentx/usage/{YYYY-MM-DD}.json.\n//\n// Design notes:\n// - Pricing is per-model-FAMILY (opus/sonnet/haiku). Exact model name\n// variants map via getModelFamily(). Override the table by dropping\n// .agentx/pricing/custom.json (same shape) — merged over defaults at\n// construction, lets operators adjust without a rebuild.\n// - Anthropic bills 1.5× input/output when a single request's total input\n// (input + cacheCreate + cacheRead) exceeds 200K tokens. We split each\n// recorded task into tier1/tier2 buckets and bill them separately so\n// long-context sessions are not undercounted.\n// - Per-channel breakdowns let operators see \"GitLab drove 70% of today's\n// spend\" — the lever for channel-specific session + context tuning.\n// - Session tracking (unique chatId:channel pairs seen today per agent)\n// gives an avg-tasks-per-session proxy — when it spikes, it's usually\n// retry/clarification chains, a signal the context budget or model\n// capability is under-sized for that traffic.\n\n// Anthropic pricing per million tokens (as of April 2026, 5-min ephemeral).\n// Keys here must match getModelFamily() output.\n//\n// IMPORTANT: Opus 4.6 is 3× cheaper than Opus 4.1. Haiku 4.5 is more expensive\n// than Haiku 3.5. Our getModelFamily() resolves the exact version so the cost\n// report matches what Anthropic actually bills. Source: LiteLLM pricing cache\n// at ~/.cache/codeburn/litellm-pricing.json + codeburn/src/models.ts.\n//\n// Cache multipliers (consistent across all models):\n// cache write (5-min ephemeral) = 1.25 × input\n// cache read = 0.10 × input\nexport const CACHE_AWARE_PRICING: Record<string, { input: number; output: number; cacheRead: number; cacheCreate: number }> = {\n // Opus 4.6 / 4.5 — $5/$25 per MTok\n \"claude-opus-4-6\": { input: 5, output: 25, cacheRead: 0.5, cacheCreate: 6.25 },\n \"claude-opus-4-5\": { input: 5, output: 25, cacheRead: 0.5, cacheCreate: 6.25 },\n // Opus 4.1 and earlier — $15/$75 per MTok (legacy, keep for historical runs)\n \"claude-opus\": { input: 15, output: 75, cacheRead: 1.5, cacheCreate: 18.75 },\n // Sonnet 4.6 / 4.5 — $3/$15 per MTok\n \"claude-sonnet\": { input: 3, output: 15, cacheRead: 0.3, cacheCreate: 3.75 },\n // Haiku 4.5 — $1/$5 per MTok (more expensive than Haiku 3.5)\n \"claude-haiku\": { input: 1, output: 5, cacheRead: 0.1, cacheCreate: 1.25 },\n}\n\n/** Per-request input size (input + cacheCreate + cacheRead) above this\n * threshold triggers Anthropic's extended-context tier: all four rates\n * get multiplied. Applies to Opus 4.x and Sonnet 4.x 1M variants. */\nexport const CONTEXT_TIER_THRESHOLD = 200_000\nexport const TIER2_MULTIPLIER = 1.5\n\nexport function getModelFamily(model: string): string {\n const lower = model.toLowerCase()\n if (lower.includes(\"opus\")) {\n // Opus 4.6 and 4.5 are 3× cheaper than 4.1 — must distinguish.\n if (lower.includes(\"4-6\") || lower.includes(\"4.6\") || lower.includes(\"opus-4-6\")) return \"claude-opus-4-6\"\n if (lower.includes(\"4-5\") || lower.includes(\"4.5\") || lower.includes(\"opus-4-5\")) return \"claude-opus-4-5\"\n return \"claude-opus\" // legacy 4.1 and earlier\n }\n if (lower.includes(\"haiku\")) return \"claude-haiku\"\n return \"claude-sonnet\"\n}\n\nexport interface ChannelUsage {\n tasks: number\n inputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cacheCreateTokens: number\n tier2InputTokens: number\n tier2OutputTokens: number\n tier2CacheReadTokens: number\n tier2CacheCreateTokens: number\n totalDuration: number\n errors: number\n}\n\nexport interface AgentUsage {\n tasks: number\n inputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cacheCreateTokens: number\n /** Tokens from requests whose input crossed CONTEXT_TIER_THRESHOLD (billed at\n * TIER2_MULTIPLIER × normal rate). Kept separate from the tier1 buckets so\n * cost math stays honest when a session accumulates past 200K. */\n tier2InputTokens?: number\n tier2OutputTokens?: number\n tier2CacheReadTokens?: number\n tier2CacheCreateTokens?: number\n totalDuration: number\n errors: number\n model?: string\n /** Per-channel breakdown so we can attribute cost to Telegram vs GitLab vs WhatsApp. */\n byChannel?: Record<string, ChannelUsage>\n /** Unique session identifiers (channel:chatId) this agent participated in\n * today. Used to derive avg tasks/session. Capped at 500 entries to keep\n * the daily JSON small. */\n sessionKeys?: string[]\n}\n\nexport interface DailyUsage {\n date: string\n agents: Record<string, AgentUsage>\n}\n\nexport interface DailyReport {\n date: string\n totalTasks: number\n totalInput: number\n totalOutput: number\n totalCacheRead: number\n totalCacheCreate: number\n totalCost: number\n topAgent: string\n topCost: number\n agentCosts: Record<string, number>\n /** Per-agent avg tasks/session — high values suggest retry chains. */\n agentSessionStats: Record<string, { sessions: number; avgTasksPerSession: number }>\n /** Aggregate cost by channel across all agents. */\n byChannel: Record<string, number>\n}\n\n/** Shape of a per-request usage report coming from the runtime. Keeps the\n * record() signature tidy and lets callers opt into tier2 accounting. */\nexport interface TaskUsage {\n inputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cacheCreateTokens: number\n}\n\n/** Split a TaskUsage into tier1 and tier2 buckets using the same\n * CONTEXT_TIER_THRESHOLD that record() applies. Output inherits the\n * tier of its request. Returned as a flat object so callers can spread\n * it directly into bus payloads or DB writes. */\nexport function splitTaskUsageByTier(u: TaskUsage): {\n inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheCreateTokens: number\n tier2InputTokens: number; tier2OutputTokens: number; tier2CacheReadTokens: number; tier2CacheCreateTokens: number\n} {\n const totalInput = u.inputTokens + u.cacheReadTokens + u.cacheCreateTokens\n if (totalInput > CONTEXT_TIER_THRESHOLD) {\n return {\n inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreateTokens: 0,\n tier2InputTokens: u.inputTokens, tier2OutputTokens: u.outputTokens,\n tier2CacheReadTokens: u.cacheReadTokens, tier2CacheCreateTokens: u.cacheCreateTokens,\n }\n }\n return {\n inputTokens: u.inputTokens, outputTokens: u.outputTokens,\n cacheReadTokens: u.cacheReadTokens, cacheCreateTokens: u.cacheCreateTokens,\n tier2InputTokens: 0, tier2OutputTokens: 0, tier2CacheReadTokens: 0, tier2CacheCreateTokens: 0,\n }\n}\n\nconst SESSION_KEYS_MAX = 500\nconst PRICING_OVERRIDE_PATH = \".agentx/pricing/custom.json\"\n\nexport class TokenTracker {\n private dir: string\n private cache: DailyUsage | null = null\n private pricing: typeof CACHE_AWARE_PRICING\n\n constructor(baseDir: string = resolve(process.cwd(), \".agentx/usage\")) {\n this.dir = baseDir\n mkdirSync(this.dir, { recursive: true })\n this.pricing = loadPricingWithOverrides()\n }\n\n /** Returns the active pricing table (hardcoded defaults with any per-model\n * overrides merged in). Useful for UI / debugging. */\n getPricing(): typeof CACHE_AWARE_PRICING {\n return this.pricing\n }\n\n /**\n * Calculate cost for a slice of usage. Accepts either a full AgentUsage or\n * a narrowed ChannelUsage — both expose the same token buckets we price on.\n * Tier2 tokens are billed at TIER2_MULTIPLIER × the normal rate.\n */\n static calculateCost(usage: AgentUsage | ChannelUsage, model?: string, pricingTable: typeof CACHE_AWARE_PRICING = CACHE_AWARE_PRICING): number {\n const family = getModelFamily(model || (usage as AgentUsage).model || \"claude-sonnet\")\n const p = pricingTable[family] || pricingTable[\"claude-sonnet\"]\n const tier2In = (usage as any).tier2InputTokens || 0\n const tier2Out = (usage as any).tier2OutputTokens || 0\n const tier2CR = (usage as any).tier2CacheReadTokens || 0\n const tier2CW = (usage as any).tier2CacheCreateTokens || 0\n\n const tier1 =\n ((usage.inputTokens || 0) / 1_000_000) * p.input +\n ((usage.outputTokens || 0) / 1_000_000) * p.output +\n ((usage.cacheReadTokens || 0) / 1_000_000) * p.cacheRead +\n ((usage.cacheCreateTokens || 0) / 1_000_000) * p.cacheCreate\n\n const tier2 =\n (tier2In / 1_000_000) * p.input +\n (tier2Out / 1_000_000) * p.output +\n (tier2CR / 1_000_000) * p.cacheRead +\n (tier2CW / 1_000_000) * p.cacheCreate\n\n return tier1 + tier2 * TIER2_MULTIPLIER\n }\n\n /**\n * Instance-level cost calculation — uses the tracker's resolved pricing\n * table so any custom override file is honored. Prefer this over the\n * static variant when available.\n */\n cost(usage: AgentUsage | ChannelUsage, model?: string): number {\n return TokenTracker.calculateCost(usage, model, this.pricing)\n }\n\n /**\n * Record a task execution with real or estimated token counts.\n *\n * `channel` attributes the cost to a surface (telegram/gitlab/whatsapp/cron).\n * `sessionKey` is an opaque \"this chat on this day\" identifier — we use it\n * to count unique sessions so an avg-tasks-per-session metric falls out.\n */\n record(\n agentId: string,\n duration: number,\n realUsage?: TaskUsage,\n messageLength?: number,\n responseLength?: number,\n error?: boolean,\n model?: string,\n channel?: string,\n sessionKey?: string,\n ): void {\n const daily = this.today()\n const agent = daily.agents[agentId] || {\n tasks: 0, inputTokens: 0, outputTokens: 0,\n cacheReadTokens: 0, cacheCreateTokens: 0,\n tier2InputTokens: 0, tier2OutputTokens: 0,\n tier2CacheReadTokens: 0, tier2CacheCreateTokens: 0,\n totalDuration: 0, errors: 0, byChannel: {}, sessionKeys: [],\n }\n\n agent.tasks++\n agent.totalDuration += duration\n if (model) agent.model = model\n\n // Split this request into tier1/tier2 based on its TOTAL input size\n // (input + cacheCreate + cacheRead). Output inherits the tier of its\n // request. Estimated paths (no realUsage) are always tier1 — they're\n // too small to reach the threshold.\n let i1 = 0, o1 = 0, cr1 = 0, cw1 = 0, i2 = 0, o2 = 0, cr2 = 0, cw2 = 0\n if (realUsage) {\n const totalInput = realUsage.inputTokens + realUsage.cacheReadTokens + realUsage.cacheCreateTokens\n if (totalInput > CONTEXT_TIER_THRESHOLD) {\n i2 = realUsage.inputTokens\n o2 = realUsage.outputTokens\n cr2 = realUsage.cacheReadTokens\n cw2 = realUsage.cacheCreateTokens\n } else {\n i1 = realUsage.inputTokens\n o1 = realUsage.outputTokens\n cr1 = realUsage.cacheReadTokens\n cw1 = realUsage.cacheCreateTokens\n }\n } else if (messageLength !== undefined && responseLength !== undefined) {\n i1 = Math.ceil(messageLength / 4)\n o1 = Math.ceil(responseLength / 4)\n }\n\n agent.inputTokens += i1\n agent.outputTokens += o1\n agent.cacheReadTokens += cr1\n agent.cacheCreateTokens += cw1\n agent.tier2InputTokens = (agent.tier2InputTokens || 0) + i2\n agent.tier2OutputTokens = (agent.tier2OutputTokens || 0) + o2\n agent.tier2CacheReadTokens = (agent.tier2CacheReadTokens || 0) + cr2\n agent.tier2CacheCreateTokens = (agent.tier2CacheCreateTokens || 0) + cw2\n\n if (error) agent.errors++\n\n // Per-channel attribution. Unknown channel falls into \"other\".\n if (!agent.byChannel) agent.byChannel = {}\n const chKey = (channel || \"other\").toLowerCase()\n const ch = agent.byChannel[chKey] || {\n tasks: 0, inputTokens: 0, outputTokens: 0,\n cacheReadTokens: 0, cacheCreateTokens: 0,\n tier2InputTokens: 0, tier2OutputTokens: 0,\n tier2CacheReadTokens: 0, tier2CacheCreateTokens: 0,\n totalDuration: 0, errors: 0,\n }\n ch.tasks++\n ch.totalDuration += duration\n ch.inputTokens += i1; ch.outputTokens += o1\n ch.cacheReadTokens += cr1; ch.cacheCreateTokens += cw1\n ch.tier2InputTokens += i2; ch.tier2OutputTokens += o2\n ch.tier2CacheReadTokens += cr2; ch.tier2CacheCreateTokens += cw2\n if (error) ch.errors++\n agent.byChannel[chKey] = ch\n\n // Session tracking — dedup sessions by opaque key (normally channel:chatId).\n if (sessionKey) {\n if (!agent.sessionKeys) agent.sessionKeys = []\n if (!agent.sessionKeys.includes(sessionKey)) {\n agent.sessionKeys.push(sessionKey)\n if (agent.sessionKeys.length > SESSION_KEYS_MAX) {\n agent.sessionKeys.splice(0, agent.sessionKeys.length - SESSION_KEYS_MAX)\n }\n }\n }\n\n daily.agents[agentId] = agent\n this.save(daily)\n }\n\n /**\n * Get today's usage summary.\n */\n today(): DailyUsage {\n const date = new Date().toISOString().slice(0, 10)\n\n if (this.cache?.date === date) return this.cache\n\n const path = this.filePath(date)\n if (existsSync(path)) {\n try {\n this.cache = JSON.parse(readFileSync(path, \"utf-8\"))\n return this.cache!\n } catch {}\n }\n\n this.cache = { date, agents: {} }\n return this.cache\n }\n\n /**\n * Get usage for a specific date.\n */\n getDate(date: string): DailyUsage | null {\n const path = this.filePath(date)\n if (!existsSync(path)) return null\n try {\n return JSON.parse(readFileSync(path, \"utf-8\"))\n } catch {\n return null\n }\n }\n\n /**\n * Generate a daily cost report for a given date. Includes per-agent,\n * per-channel costs and session statistics.\n */\n generateDailyReport(date?: string): DailyReport | null {\n const targetDate = date || new Date().toISOString().slice(0, 10)\n const usage = this.getDate(targetDate)\n if (!usage) return null\n\n let totalTasks = 0, totalInput = 0, totalOutput = 0\n let totalCacheRead = 0, totalCacheCreate = 0, totalCost = 0\n let topAgent = \"\", topCost = 0\n const agentCosts: Record<string, number> = {}\n const agentSessionStats: Record<string, { sessions: number; avgTasksPerSession: number }> = {}\n const byChannel: Record<string, number> = {}\n\n for (const [id, agent] of Object.entries(usage.agents)) {\n const cost = this.cost(agent)\n agentCosts[id] = cost\n totalTasks += agent.tasks\n totalInput += (agent.inputTokens || 0) + (agent.tier2InputTokens || 0)\n totalOutput += (agent.outputTokens || 0) + (agent.tier2OutputTokens || 0)\n totalCacheRead += (agent.cacheReadTokens || 0) + (agent.tier2CacheReadTokens || 0)\n totalCacheCreate += (agent.cacheCreateTokens || 0) + (agent.tier2CacheCreateTokens || 0)\n totalCost += cost\n if (cost > topCost) { topCost = cost; topAgent = id }\n\n const sessions = agent.sessionKeys?.length || 0\n agentSessionStats[id] = {\n sessions,\n avgTasksPerSession: sessions > 0 ? agent.tasks / sessions : agent.tasks,\n }\n\n for (const [chKey, ch] of Object.entries(agent.byChannel || {})) {\n const chCost = this.cost(ch, agent.model)\n byChannel[chKey] = (byChannel[chKey] || 0) + chCost\n }\n }\n\n return {\n date: targetDate, totalTasks, totalInput, totalOutput,\n totalCacheRead, totalCacheCreate, totalCost, topAgent, topCost, agentCosts,\n agentSessionStats, byChannel,\n }\n }\n\n /**\n * Append a daily report row to TOKEN_COSTS.md. Adds channel + session\n * breakdown inline for at-a-glance spend attribution.\n */\n appendToTokenCosts(report: DailyReport, filePath?: string): void {\n const file = filePath || resolve(process.cwd(), \".agentx/TOKEN_COSTS.md\")\n const fmt = (n: number) => n < 0.01 ? n.toFixed(4) : n.toFixed(2)\n const fmtTok = (n: number) =>\n n >= 1_000_000 ? `${(n / 1_000_000).toFixed(1)}M` :\n n >= 1_000 ? `${(n / 1_000).toFixed(0)}K` :\n String(n)\n\n const agentBreakdown = Object.entries(report.agentCosts)\n .sort(([, a], [, b]) => b - a)\n .map(([id, cost]) => {\n const stats = report.agentSessionStats[id]\n const sessTag = stats && stats.sessions > 0 ? ` (${stats.sessions}s, ${stats.avgTasksPerSession.toFixed(1)}t/s)` : \"\"\n return `${id}: $${fmt(cost)}${sessTag}`\n })\n .join(\", \")\n\n const channelBreakdown = Object.entries(report.byChannel)\n .sort(([, a], [, b]) => b - a)\n .map(([ch, cost]) => `${ch}: $${fmt(cost)}`)\n .join(\", \")\n\n const row = `| ${report.date} | ${report.totalTasks} | ${fmtTok(report.totalInput)} | ${fmtTok(report.totalOutput)} | ${fmtTok(report.totalCacheRead)} | ${fmtTok(report.totalCacheCreate)} | $${fmt(report.totalCost)} | ${agentBreakdown} | ${channelBreakdown} |`\n\n if (!existsSync(file)) {\n const header =\n \"# Token Costs\\n\\n\" +\n \"Daily token cost tracking. Auto-appended at midnight (Africa/Tunis).\\n\" +\n \"Agent breakdown: `agent: $cost (Ns, T.Tt/s)` — sessions, tasks/session.\\n\\n\" +\n \"| Date | Tasks | Input | Output | Cache R | Cache W | Cost | Per Agent | Per Channel |\\n\" +\n \"|------|-------|-------|--------|---------|---------|------|-----------|-------------|\\n\"\n writeFileSync(file, header + row + \"\\n\")\n } else {\n appendFileSync(file, row + \"\\n\")\n }\n }\n\n /**\n * Return true if TOKEN_COSTS.md already has a row for `date`. Lets callers\n * skip duplicate appends after daemon restarts or timer-edge double fires.\n */\n hasTokenCostsEntry(date: string, filePath?: string): boolean {\n const file = filePath || resolve(process.cwd(), \".agentx/TOKEN_COSTS.md\")\n if (!existsSync(file)) return false\n try {\n const content = readFileSync(file, \"utf-8\")\n // Rows look like: `| 2026-04-18 | ...`\n return new RegExp(`^\\\\|\\\\s*${date}\\\\s*\\\\|`, \"m\").test(content)\n } catch {\n return false\n }\n }\n\n /**\n * Find the most recent date already logged to TOKEN_COSTS.md (YYYY-MM-DD).\n * Returns null when the file doesn't exist or has no rows.\n */\n lastTokenCostsDate(filePath?: string): string | null {\n const file = filePath || resolve(process.cwd(), \".agentx/TOKEN_COSTS.md\")\n if (!existsSync(file)) return null\n try {\n const content = readFileSync(file, \"utf-8\")\n const dates = content.match(/^\\|\\s*(\\d{4}-\\d{2}-\\d{2})\\s*\\|/gm) ?? []\n if (dates.length === 0) return null\n // Extract the date portion and pick the max — rows can be out of order\n // after a catch-up, so don't rely on file order.\n const parsed = dates\n .map((m) => m.match(/(\\d{4}-\\d{2}-\\d{2})/)?.[1])\n .filter((d): d is string => !!d)\n parsed.sort()\n return parsed[parsed.length - 1] ?? null\n } catch {\n return null\n }\n }\n\n /**\n * Append reports for any days between (lastLogged + 1) and `throughDate`\n * that have usage on disk but no row in TOKEN_COSTS.md. Idempotent —\n * safe to call on every daemon startup. Bounded to `maxDays` to avoid\n * a stalled daemon backfilling a year's worth on first start.\n *\n * Returns the list of dates actually appended, for logging.\n */\n catchUpTokenCosts(\n throughDate: string,\n opts: { maxDays?: number; filePath?: string } = {},\n ): string[] {\n const maxDays = opts.maxDays ?? 30\n const appended: string[] = []\n\n const last = this.lastTokenCostsDate(opts.filePath)\n // Walk backward from throughDate, collecting dates to fill. Stop at the\n // last-logged date (exclusive) or after maxDays, whichever comes first.\n const candidates: string[] = []\n const cursor = new Date(`${throughDate}T00:00:00Z`)\n for (let i = 0; i < maxDays; i++) {\n const date = cursor.toISOString().slice(0, 10)\n if (last && date <= last) break\n candidates.push(date)\n cursor.setUTCDate(cursor.getUTCDate() - 1)\n }\n // Oldest-first so the file stays chronological.\n candidates.reverse()\n\n for (const date of candidates) {\n if (this.hasTokenCostsEntry(date, opts.filePath)) continue\n const report = this.generateDailyReport(date)\n if (!report || report.totalTasks === 0) continue\n this.appendToTokenCosts(report, opts.filePath)\n appended.push(date)\n }\n return appended\n }\n\n /**\n * Get summary across last N days.\n */\n summary(days: number = 7): {\n totalTasks: number\n totalTokens: number\n totalInput: number\n totalOutput: number\n totalCacheRead: number\n totalCacheCreate: number\n cacheHitRatio: number\n totalErrors: number\n totalCost: number\n byAgent: Record<string, { tasks: number; input: number; output: number; cacheRead: number; cacheCreate: number; total: number; avgDuration: number; cost: number }>\n byChannel: Record<string, { tasks: number; cost: number }>\n sessionStats: Record<string, { sessions: number; avgTasksPerSession: number }>\n } {\n let totalTasks = 0\n let totalInput = 0\n let totalOutput = 0\n let totalCacheRead = 0\n let totalCacheCreate = 0\n let totalErrors = 0\n let totalCost = 0\n const byAgent: Record<string, { tasks: number; input: number; output: number; cacheRead: number; cacheCreate: number; total: number; avgDuration: number; totalDuration: number; cost: number; model?: string; sessions: number }> = {}\n const byChannel: Record<string, { tasks: number; cost: number }> = {}\n\n for (let i = 0; i < days; i++) {\n const date = new Date(Date.now() - i * 86400_000).toISOString().slice(0, 10)\n const usage = this.getDate(date)\n if (!usage) continue\n\n for (const [id, agent] of Object.entries(usage.agents)) {\n const cost = this.cost(agent)\n totalTasks += agent.tasks\n const agentInput = (agent.inputTokens || 0) + (agent.tier2InputTokens || 0)\n const agentOutput = (agent.outputTokens || 0) + (agent.tier2OutputTokens || 0)\n const agentCR = (agent.cacheReadTokens || 0) + (agent.tier2CacheReadTokens || 0)\n const agentCW = (agent.cacheCreateTokens || 0) + (agent.tier2CacheCreateTokens || 0)\n totalInput += agentInput\n totalOutput += agentOutput\n totalCacheRead += agentCR\n totalCacheCreate += agentCW\n totalErrors += agent.errors\n totalCost += cost\n\n const existing = byAgent[id] || { tasks: 0, input: 0, output: 0, cacheRead: 0, cacheCreate: 0, total: 0, avgDuration: 0, totalDuration: 0, cost: 0, sessions: 0 }\n existing.tasks += agent.tasks\n existing.input += agentInput\n existing.output += agentOutput\n existing.cacheRead += agentCR\n existing.cacheCreate += agentCW\n existing.total = existing.input + existing.output + existing.cacheRead + existing.cacheCreate\n existing.totalDuration += agent.totalDuration\n existing.avgDuration = existing.totalDuration / existing.tasks\n existing.cost += cost\n existing.sessions += agent.sessionKeys?.length || 0\n if (agent.model) existing.model = agent.model\n byAgent[id] = existing\n\n for (const [chKey, ch] of Object.entries(agent.byChannel || {})) {\n const chCost = this.cost(ch, agent.model)\n const acc = byChannel[chKey] || { tasks: 0, cost: 0 }\n acc.tasks += ch.tasks\n acc.cost += chCost\n byChannel[chKey] = acc\n }\n }\n }\n\n const totalTokens = totalInput + totalOutput + totalCacheRead + totalCacheCreate\n const cacheHitRatio = (totalCacheRead + totalCacheCreate) > 0\n ? totalCacheRead / (totalCacheRead + totalCacheCreate)\n : 0\n\n const sessionStats: Record<string, { sessions: number; avgTasksPerSession: number }> = {}\n for (const [id, a] of Object.entries(byAgent)) {\n sessionStats[id] = {\n sessions: a.sessions,\n avgTasksPerSession: a.sessions > 0 ? a.tasks / a.sessions : a.tasks,\n }\n }\n\n return { totalTasks, totalTokens, totalInput, totalOutput, totalCacheRead, totalCacheCreate, cacheHitRatio, totalErrors, totalCost, byAgent, byChannel, sessionStats }\n }\n\n private filePath(date: string): string {\n return resolve(this.dir, `${date}.json`)\n }\n\n private save(usage: DailyUsage): void {\n try {\n writeFileSync(this.filePath(usage.date), JSON.stringify(usage, null, 2))\n } catch {}\n }\n}\n\n/**\n * Load CACHE_AWARE_PRICING and merge any operator-supplied override. The\n * override file is optional; operators tweak rates (e.g. when Anthropic\n * publishes a price change) without rebuilding the daemon.\n */\nfunction loadPricingWithOverrides(): typeof CACHE_AWARE_PRICING {\n const result = JSON.parse(JSON.stringify(CACHE_AWARE_PRICING))\n try {\n const p = resolve(process.cwd(), PRICING_OVERRIDE_PATH)\n if (!existsSync(p)) return result\n const raw = JSON.parse(readFileSync(p, \"utf-8\"))\n for (const [family, rates] of Object.entries(raw)) {\n if (!result[family]) result[family] = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 }\n Object.assign(result[family], rates as object)\n }\n } catch {\n // Malformed override file — stay with defaults rather than crash at boot.\n }\n return result\n}\n","// --- Agent Context Engine ---\n//\n// Structured, layered context with token budget management.\n// Each context layer has a priority, max tokens, and rules.\n//\n// Layers (highest priority first):\n// 1. Channel — where the message came from + channel-specific rules\n// 2. Scope — group/personal/project + scope-specific constraints\n// 3. Landscape — world model: team roster, channels, rules (cached at startup)\n// 4. Identity — who the agent is, what it can do\n// 5. Intent — what the user is asking about (extracted from message)\n// 6. Artifacts — project, issue, MR, file references\n// 7. History — group conversation log or session history\n// 8. Wiki — relevant knowledge articles\n//\n// Token budget: each layer gets a max allocation. Total capped at configurable limit.\n// Layers are rendered top-down; if budget exhausted, lower layers are truncated or skipped.\n\nexport interface ContextLayer {\n name: string\n priority: number // lower = higher priority (1 = most important)\n maxTokens: number // max tokens for this layer\n content: string // rendered text\n tags: string[] // metadata tags for filtering/matching\n rules?: string[] // constraints/instructions specific to this layer\n}\n\nexport interface ContextConfig {\n /** Total token budget for all context combined (default: 4000) */\n totalBudget: number\n /** Per-layer budgets override (layer name -> max tokens) */\n layerBudgets?: Record<string, number>\n}\n\nconst DEFAULT_CONFIG: ContextConfig = {\n totalBudget: 6000,\n layerBudgets: {\n channel: 200,\n scope: 200,\n landscape: 800,\n identity: 200,\n bootstrap: 500,\n references: 500,\n intent: 200,\n artifacts: 500,\n procedures: 600,\n memory: 600,\n history: 1200,\n \"cross-chat\": 800,\n wiki: 1000,\n },\n}\n\n// Rough estimate: 1 token ≈ 4 chars for English\nconst CHARS_PER_TOKEN = 4\n\nexport interface ContextInput {\n // Channel layer\n channel: string // \"telegram\", \"whatsapp\", \"gitlab\", \"discord\"\n channelScope?: \"group\" | \"personal\" | \"project\"\n /** Canonical chat id used by routing — telegram user/group id, github\n * \"owner/repo:issue:N\", whatsapp jid, etc. Surfaced verbatim in the\n * prompt so the agent can pass it back to /recall and /send without\n * guessing. */\n chatId?: string\n\n // Bootstrap identity files (from workspace)\n bootstrapContext?: string // from buildBootstrapContext()\n\n // Verified deterministic references (from references registry + recipes)\n references?: string // from renderReferences()\n\n // Behavioral patterns (self-improving loop)\n patternContext?: string // from PatternStore.buildContext()\n\n // Matched procedures (user-perspective SOPs — known-good step sequences)\n procedureContext?: string // from renderProcedureContext()\n\n // Auto-injected skills (matched to current message)\n skillInjection?: string // from getAutoInjectSkills()\n\n // Scope layer\n groupName?: string\n projectPath?: string // \"org/my-project\"\n issueMR?: { type: string; iid: string; title: string }\n\n // Identity layer\n agentId: string\n agentName: string\n agentHandle?: string // \"@my_bot\"\n systemPrompt?: string\n\n // Participants\n sender: string\n senderId?: string // platform user ID (e.g. Telegram user ID)\n senderUsername?: string // platform username (e.g. @username)\n senderRole?: string // \"user\", \"agent:other-agent\"\n\n // Landscape (cached world model from LandscapeBuilder)\n landscape?: string\n\n // Channel meta (verified facts from the channel adapter — prevents hallucination)\n channelMeta?: {\n agents?: Array<{ id: string; name: string; handle?: string }>\n project?: string\n issue?: { type: string; iid: string; title: string }\n facts?: string[]\n }\n\n // Artifacts\n mediaPath?: string\n mediaType?: string\n replyToText?: string\n\n // Memory (persistent cross-session facts)\n memoryContext?: string // from MemoryStore.buildContext()\n\n // History\n groupHistory?: string // from GroupLog.buildContext()\n sessionHistory?: string // from SessionStore.buildHistoryContext()\n crossChatContext?: string // from SessionStore.getCrossSessionSummary()\n /** Long-memory recall pre-fetched by the registry when the user message\n * contains an explicit long-memory cue (\"yesterday\", \"last week\",\n * \"remember when…\"). Saves the agent a /recall round-trip in obvious\n * cases and prevents context-loss from being papered over with\n * fabricated facts via unrelated tools. */\n longMemoryRecall?: string\n\n // Wiki\n wikiContext?: string // from WikiStore.buildContext()\n\n // Handover — one-shot note injected on the first message after an\n // operator-initiated handover takes effect. See channels/handover-store.\n handoverNote?: {\n fromAgent: string\n summary?: string\n at: string // ISO timestamp of the handover\n }\n\n // Continuity memo distilled from this chat's previous (rotated) Claude\n // session. Injected on FRESH sessions only (registry gates on\n // !resumeSessionId) so an ongoing task survives rotation instead of\n // the agent opening with amnesia. See SessionStore rotation memos.\n rotationMemo?: {\n memo: string\n reason: string // stale | tier-2 | max-turns\n capturedAt: string // ISO timestamp of the rotation\n }\n\n // Intent (graph classification — when absent, Intent layer falls back\n // to the legacy regex tag extractor).\n intent?: {\n path: string[] // root → leaf node ids\n pathLabel: string // \"Business › Noqta › DevOps › Review MR\"\n pathId: string // hash — also carried as wiki `graph:<pathId>` tag\n axes?: Record<string, Record<string, string>>\n leaf?: { input?: string; output?: string }\n status: \"pending\" | \"approved\"\n }\n\n // Message\n message: string\n}\n\n/**\n * Build optimized context string from structured input.\n * Respects token budgets per layer and total cap.\n */\nexport function buildAgentContext(input: ContextInput, config: ContextConfig = DEFAULT_CONFIG): string {\n const layers = buildLayers(input, config)\n\n // Sort by priority (lower = first)\n layers.sort((a, b) => a.priority - b.priority)\n\n // Render within budget\n const parts: string[] = []\n let totalChars = 0\n const maxChars = config.totalBudget * CHARS_PER_TOKEN\n\n for (const layer of layers) {\n if (!layer.content) continue\n\n const layerMaxChars = layer.maxTokens * CHARS_PER_TOKEN\n const trimmed = layer.content.length > layerMaxChars\n ? layer.content.slice(0, layerMaxChars) + \"...\"\n : layer.content\n\n if (totalChars + trimmed.length > maxChars) {\n // Budget exhausted — add what fits or skip\n const remaining = maxChars - totalChars\n if (remaining > 100) {\n parts.push(trimmed.slice(0, remaining) + \"...\")\n }\n break\n }\n\n parts.push(trimmed)\n totalChars += trimmed.length\n }\n\n return parts.join(\"\\n\\n\")\n}\n\n/**\n * Build individual context layers from input.\n */\nfunction buildLayers(input: ContextInput, config: ContextConfig): ContextLayer[] {\n const budget = (name: string, fallback: number) =>\n config.layerBudgets?.[name] ?? fallback\n\n const layers: ContextLayer[] = []\n\n // 1. Channel layer\n layers.push(buildChannelLayer(input, budget(\"channel\", 200)))\n\n // 2. Scope layer\n layers.push(buildScopeLayer(input, budget(\"scope\", 200)))\n\n // 3. Landscape (cached world model — team, channels, rules)\n if (input.landscape) {\n layers.push({\n name: \"landscape\",\n priority: 3,\n maxTokens: budget(\"landscape\", 350),\n content: input.landscape,\n tags: [\"landscape\", \"world-model\"],\n })\n }\n\n // 4. Identity (only first line of systemPrompt — agent already has CLAUDE.md)\n if (input.systemPrompt) {\n layers.push({\n name: \"identity\",\n priority: 4,\n maxTokens: budget(\"identity\", 200),\n content: input.systemPrompt.split(\"\\n\")[0],\n tags: [\"identity\", input.agentId],\n })\n }\n\n // 4.5 Bootstrap identity files (SOUL.md, IDENTITY.md, USER.md, AGENTS.md)\n if (input.bootstrapContext) {\n layers.push({\n name: \"bootstrap\",\n priority: 4.5,\n maxTokens: budget(\"bootstrap\", 500),\n content: input.bootstrapContext,\n tags: [\"bootstrap\", \"identity\", \"personality\"],\n })\n }\n\n // 4.7 Verified references — deterministic facts (SSH hosts, project IDs,\n // paths, contacts) resolved from the references registry + recipes.\n // Sits before Intent so the agent reads facts before interpretation.\n if (input.references) {\n layers.push({\n name: \"references\",\n priority: 4.7,\n maxTokens: budget(\"references\", 500),\n content: input.references,\n tags: [\"references\", \"verified\", \"deterministic\"],\n })\n }\n\n // 5. Intent — prefer graph classification (hierarchical path) when the\n // caller ran the classifier. Fall back to the regex tag extractor\n // otherwise so legacy installs with graph.enabled=false behave as before.\n if (input.intent && input.intent.path.length > 0) {\n const lines: string[] = [\n `[Intent path: ${input.intent.pathLabel}${input.intent.status === \"pending\" ? \" (pending approval)\" : \"\"}]`,\n ]\n const leafId = input.intent.path[input.intent.path.length - 1]\n const leafAxes = input.intent.axes?.[leafId]\n if (leafAxes && Object.keys(leafAxes).length) {\n const kv = Object.entries(leafAxes)\n .map(([k, v]) => ` ${k}: ${v}`)\n .join(\"\\n\")\n lines.push(kv)\n }\n if (input.intent.leaf?.input) lines.push(` input: ${input.intent.leaf.input}`)\n layers.push({\n name: \"intent\",\n priority: 5,\n maxTokens: budget(\"intent\", 200),\n content: lines.join(\"\\n\"),\n tags: [\"intent\", ...input.intent.path],\n })\n } else {\n const intentTags = extractIntentTags(input.message)\n if (intentTags.length) {\n layers.push({\n name: \"intent\",\n priority: 5,\n maxTokens: budget(\"intent\", 200),\n content: `[Intent: ${intentTags.join(\", \")}]`,\n tags: intentTags,\n })\n }\n }\n\n // 6. Artifacts (media, reply-to, issue/MR context)\n const artifactLines: string[] = []\n if (input.replyToText) {\n artifactLines.push(`[Replying to]: ${input.replyToText.slice(0, 300)}`)\n }\n if (input.mediaPath) {\n artifactLines.push(`[Attached file: ${input.mediaPath}]`)\n artifactLines.push(`[File type: ${input.mediaType || \"unknown\"}]`)\n if (input.mediaType?.startsWith(\"image/\")) artifactLines.push(\"Please view this image and respond to it.\")\n else if (input.mediaType?.startsWith(\"audio/\")) artifactLines.push(\"Please transcribe this audio and respond.\")\n }\n if (input.issueMR) {\n artifactLines.push(`[${input.issueMR.type} #${input.issueMR.iid}: ${input.issueMR.title}]`)\n }\n if (artifactLines.length) {\n layers.push({\n name: \"artifacts\",\n priority: 6,\n maxTokens: budget(\"artifacts\", 500),\n content: artifactLines.join(\"\\n\"),\n tags: [\"artifacts\", ...(input.mediaType ? [\"media\"] : [])],\n })\n }\n\n // 6.2 Auto-injected skills (matched to current task)\n if (input.skillInjection) {\n layers.push({\n name: \"skills\",\n priority: 6.2,\n maxTokens: budget(\"skills\", 2000),\n content: input.skillInjection,\n tags: [\"skills\", \"auto-inject\"],\n })\n }\n\n // 6.3 Behavioral patterns (self-improving loop)\n if (input.patternContext) {\n layers.push({\n name: \"patterns\",\n priority: 6.3,\n maxTokens: budget(\"patterns\", 400),\n content: input.patternContext,\n tags: [\"patterns\", \"behavioral\", \"self-improving\"],\n })\n }\n\n // 6.4 Matched procedures (mined SOPs — the known-good path for this task)\n if (input.procedureContext) {\n layers.push({\n name: \"procedures\",\n priority: 6.4,\n maxTokens: budget(\"procedures\", 600),\n content: input.procedureContext,\n tags: [\"procedures\", \"sop\"],\n })\n }\n\n // 6.5 Agent memory (persistent cross-session facts from Haiku extraction)\n if (input.memoryContext) {\n layers.push({\n name: \"memory\",\n priority: 6.5,\n maxTokens: budget(\"memory\", 600),\n content: input.memoryContext,\n tags: [\"memory\", \"persistent\"],\n })\n }\n\n // 7. History (group > session — prefer group if available)\n const history = input.groupHistory || input.sessionHistory\n if (history) {\n layers.push({\n name: \"history\",\n priority: 7,\n maxTokens: budget(\"history\", 1200),\n content: history,\n tags: [\"history\", \"conversation\"],\n })\n }\n\n // 7b. Cross-chat context (bridges DM ↔ group amnesia)\n if (input.crossChatContext) {\n layers.push({\n name: \"cross-chat\",\n priority: 7,\n maxTokens: budget(\"cross-chat\", 800),\n content: input.crossChatContext,\n tags: [\"history\", \"cross-chat\"],\n })\n }\n\n // 7c. Long-memory recall (cue-triggered: \"yesterday\", \"remember\", etc.)\n if (input.longMemoryRecall) {\n layers.push({\n name: \"long-memory\",\n priority: 7,\n maxTokens: budget(\"long-memory\", 1500),\n content: input.longMemoryRecall,\n tags: [\"history\", \"long-memory\"],\n })\n }\n\n // 7c. Handover note — one-shot briefing when this agent is taking over\n // a conversation from another agent. Rendered just before wiki.\n if (input.handoverNote) {\n const lines = [\n `[Handover] You are taking over this conversation from ${input.handoverNote.fromAgent}.`,\n ]\n if (input.handoverNote.summary) {\n lines.push(`Operator summary: ${input.handoverNote.summary}`)\n }\n lines.push(`Handover time: ${input.handoverNote.at}`)\n layers.push({\n name: \"handover\",\n priority: 7.5,\n maxTokens: budget(\"handover\", 400),\n content: lines.join(\"\\n\"),\n tags: [\"handover\", input.handoverNote.fromAgent],\n })\n }\n\n // 7d. Rotation continuity memo — the previous Claude session for this\n // chat was rotated (${reason}); its distilled facts/open tasks are\n // handed to the fresh session so the conversation picks up where\n // it left off. Only present on fresh sessions (registry-gated).\n if (input.rotationMemo) {\n layers.push({\n name: \"rotation-memo\",\n priority: 7.6,\n maxTokens: budget(\"rotation-memo\", 800),\n content: [\n `[Continuity memo — your previous session for this chat ended (${input.rotationMemo.reason}, ${input.rotationMemo.capturedAt}). Carry on from these facts; do not claim you lack prior context:]`,\n input.rotationMemo.memo,\n ].join(\"\\n\"),\n tags: [\"continuity\", \"rotation-memo\"],\n })\n }\n\n // 8. Wiki knowledge\n if (input.wikiContext) {\n layers.push({\n name: \"wiki\",\n priority: 8,\n maxTokens: budget(\"wiki\", 1000),\n content: input.wikiContext,\n tags: [\"wiki\", \"knowledge\"],\n })\n }\n\n return layers\n}\n\n/**\n * Build channel-specific context with rules.\n */\nfunction buildChannelLayer(input: ContextInput, maxTokens: number): ContextLayer {\n const lines: string[] = [`Channel: ${input.channel}`]\n if (input.chatId) lines.push(`Chat ID: ${input.chatId} (use this verbatim in /recall, /send)`)\n const rules: string[] = []\n const tags = [input.channel]\n\n switch (input.channel) {\n case \"telegram\":\n if (input.agentHandle) lines.push(`Your handle: ${input.agentHandle}`)\n lines.push(`From: ${input.sender}`)\n if (input.senderId) lines.push(`Telegram user ID: ${input.senderId}`)\n if (input.senderUsername) lines.push(`Username: @${input.senderUsername}`)\n rules.push(\"Format responses using Telegram-compatible markdown\")\n rules.push(\"Be brief — 2-4 sentences max for the main point. Humans scan, not read\")\n rules.push(\"Lead with the action or answer, skip preamble\")\n break\n\n case \"whatsapp\":\n lines.push(`From: ${input.sender}`)\n rules.push(\"Be brief — 2-3 sentences max. WhatsApp is mobile-first\")\n rules.push(\"No rich formatting — plain text only\")\n rules.push(\"Lead with the action or answer, skip preamble\")\n break\n\n case \"gitlab\":\n lines.push(`From: ${input.sender}`)\n rules.push(\"Reply as a short, actionable GitLab comment — 3-5 lines for the main message\")\n rules.push(\"Do NOT mention Telegram handles — they don't work on GitLab\")\n rules.push(\"Do NOT delegate to other agents\")\n rules.push(\"Reference issues with #IID and merge requests with !IID\")\n rules.push(\"Put verbose details (logs, full commands, step-by-step) inside collapsible sections: <details><summary>Title</summary>\\\\n\\\\ncontent\\\\n</details>\")\n rules.push(\"Never narrate what you are about to do — just do it and report the result\")\n tags.push(\"code-review\")\n break\n\n case \"discord\":\n lines.push(`From: ${input.sender}`)\n rules.push(\"Use Discord markdown for formatting\")\n rules.push(\"Be brief — 2-4 sentences for the main point\")\n break\n\n default:\n if (input.channel.startsWith(\"webhook:\")) {\n rules.push(\"This is an automated event — respond with actionable steps\")\n tags.push(\"webhook\", \"automated\")\n }\n }\n\n if (rules.length) {\n lines.push(\"\")\n lines.push(\"[Rules]\")\n lines.push(...rules.map(r => `- ${r}`))\n }\n\n return { name: \"channel\", priority: 1, maxTokens, content: lines.join(\"\\n\"), tags, rules }\n}\n\n/**\n * Build scope context (group/personal/project).\n */\nfunction buildScopeLayer(input: ContextInput, maxTokens: number): ContextLayer {\n const lines: string[] = []\n const tags: string[] = []\n\n if (input.channelScope === \"group\" && input.groupName) {\n lines.push(`Group: ${input.groupName}`)\n tags.push(\"group\", input.groupName)\n } else if (input.channelScope === \"project\" && input.projectPath) {\n lines.push(`Project: ${input.projectPath}`)\n tags.push(\"project\", input.projectPath)\n } else if (input.channelScope === \"personal\") {\n lines.push(\"Direct message\")\n tags.push(\"dm\")\n }\n\n // Inject verified channel metadata (prevents hallucination)\n if (input.channelMeta) {\n const meta = input.channelMeta\n if (meta.agents?.length) {\n lines.push(`[Verified bots in this chat: ${meta.agents.map(a => a.handle || a.name || a.id).join(\", \")}]`)\n lines.push(\"Only mention these agents as group members — do NOT assume others are present.\")\n }\n if (meta.project) {\n lines.push(`Project: ${meta.project}`)\n }\n if (meta.issue) {\n lines.push(`${meta.issue.type} #${meta.issue.iid}${meta.issue.title ? `: ${meta.issue.title}` : \"\"}`)\n }\n if (meta.facts?.length) {\n for (const fact of meta.facts) {\n lines.push(`• ${fact}`)\n }\n }\n }\n\n return { name: \"scope\", priority: 2, maxTokens, content: lines.join(\"\\n\"), tags }\n}\n\n/**\n * Extract intent tags from message (lightweight, no LLM).\n */\nfunction extractIntentTags(message: string): string[] {\n const tags: string[] = []\n const lower = message.toLowerCase()\n\n // Action intents\n if (/deploy|push|release|ship/.test(lower)) tags.push(\"deployment\")\n if (/review|check|look at|approve/.test(lower)) tags.push(\"review\")\n if (/fix|bug|broken|error|issue/.test(lower)) tags.push(\"bugfix\")\n if (/create|add|build|implement/.test(lower)) tags.push(\"feature\")\n if (/test|spec|coverage/.test(lower)) tags.push(\"testing\")\n if (/refactor|clean|improve/.test(lower)) tags.push(\"refactor\")\n if (/docs|document|readme/.test(lower)) tags.push(\"docs\")\n if (/security|vuln|auth|token/.test(lower)) tags.push(\"security\")\n if (/perf|slow|optim|fast/.test(lower)) tags.push(\"performance\")\n if (/status|update|progress|standup/.test(lower)) tags.push(\"status\")\n if (/help|how|what|explain/.test(lower)) tags.push(\"question\")\n\n // Domain intents\n if (/gitlab|merge|mr|issue|pipeline/.test(lower)) tags.push(\"gitlab\")\n if (/seo|analytics|content|marketing/.test(lower)) tags.push(\"marketing\")\n if (/infra|server|docker|k8s|devops/.test(lower)) tags.push(\"devops\")\n\n return tags\n}\n\n/**\n * Estimate token count from text.\n */\nexport function estimateTokens(text: string): number {\n return Math.ceil(text.length / CHARS_PER_TOKEN)\n}\n","import { readFileSync, writeFileSync, appendFileSync, mkdirSync, existsSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { buildIndexCached, scoreAll } from \"../memory/bm25\"\n\n// --- Persistent agent memory store ---\n// JSONL-based. One file per agent: .agentx/memory/{agentId}.jsonl\n// Extracted by Haiku post-conversation, injected pre-conversation.\n\nexport interface MemoryFact {\n id: string\n agentId: string\n category: \"fact\" | \"secret\" | \"preference\" | \"commitment\" | \"task-state\"\n content: string\n keywords: string[]\n source: { channel: string; chatId: string; sender: string; date: string }\n createdAt: string\n expiresAt?: string\n // Spaced repetition fields\n accessCount?: number\n lastAccessed?: string\n nextReview?: string\n /** Review interval in days — doubles on each successful recall */\n intervalDays?: number\n}\n\nconst MAX_MEMORIES = 200\nconst TASK_STATE_TTL_DAYS = 7\n\n\nexport class MemoryStore {\n private memoryDir: string\n\n constructor(baseDir: string = process.cwd()) {\n this.memoryDir = resolve(baseDir, \".agentx/memory\")\n if (!existsSync(this.memoryDir)) {\n mkdirSync(this.memoryDir, { recursive: true })\n }\n }\n\n private filePath(agentId: string): string {\n return resolve(this.memoryDir, `${agentId}.jsonl`)\n }\n\n addMemory(agentId: string, fact: Omit<MemoryFact, \"id\" | \"createdAt\">): void {\n const entry: MemoryFact = {\n ...fact,\n id: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`,\n createdAt: new Date().toISOString(),\n }\n appendFileSync(this.filePath(agentId), JSON.stringify(entry) + \"\\n\")\n }\n\n getAll(agentId: string): MemoryFact[] {\n const file = this.filePath(agentId)\n if (!existsSync(file)) return []\n\n try {\n const lines = readFileSync(file, \"utf-8\").split(\"\\n\").filter(l => l.trim())\n return lines.map(l => {\n try { return JSON.parse(l) as MemoryFact } catch { return null }\n }).filter((m): m is MemoryFact => m !== null)\n } catch {\n return []\n }\n }\n\n findRelevant(message: string, agentId: string, limit: number = 8): MemoryFact[] {\n const memories = this.getAll(agentId)\n if (memories.length === 0) return []\n\n // BM25 over keywords + content (cached)\n const docs = memories.map((m) => `${m.keywords.join(\" \")} ${m.content}`)\n const cachePath = resolve(this.memoryDir, `${agentId}_bm25_cache.json`)\n const index = buildIndexCached(docs, cachePath)\n const bm25Scores = scoreAll(message, index)\n const scoreMap = new Map(bm25Scores.map((r) => [r.docIndex, r.score]))\n\n const scored = memories.map((m, i) => {\n let s = scoreMap.get(i) ?? 0\n // Category boosts (preserved from original)\n if (m.category === \"secret\") s += 2\n if (m.category === \"commitment\") s += 1\n return { memory: m, score: s }\n })\n\n const matched = scored.filter((s) => s.score > 0)\n if (matched.length === 0) return memories.slice(-limit) // fallback: recent\n\n const selected = matched\n .sort((a, b) => b.score - a.score)\n .slice(0, limit)\n .map((s) => s.memory)\n\n // Track access for spaced repetition (fire-and-forget)\n if (selected.length > 0) {\n try { this.recordAccess(agentId, selected.map(m => m.id)) } catch {}\n }\n\n return selected\n }\n\n buildContext(memories: MemoryFact[]): string {\n if (memories.length === 0) return \"\"\n\n const lines = [\"[Agent Memory — persistent facts from past conversations]\"]\n let chars = lines[0].length\n\n for (const m of memories) {\n const isDM = !m.source.chatId.startsWith(\"-\") && /^\\d+$/.test(m.source.chatId)\n const scope = isDM ? \"DM\" : m.source.chatId\n const line = `- [${m.category}] ${m.content} (${scope}, ${m.source.date})`\n\n if (chars + line.length > 2400) break\n lines.push(line)\n chars += line.length\n }\n\n lines.push(\"[End Memory]\")\n return lines.join(\"\\n\")\n }\n\n /**\n * Record that memories were accessed (for spaced repetition tracking).\n */\n recordAccess(agentId: string, memoryIds: string[]): void {\n if (memoryIds.length === 0) return\n const memories = this.getAll(agentId)\n const idSet = new Set(memoryIds)\n let changed = false\n\n for (const m of memories) {\n if (idSet.has(m.id)) {\n m.accessCount = (m.accessCount || 0) + 1\n m.lastAccessed = new Date().toISOString()\n // Double the review interval (spaced repetition: 1 → 2 → 4 → 8 → 16 days)\n const currentInterval = m.intervalDays || 1\n m.intervalDays = Math.min(currentInterval * 2, 30)\n m.nextReview = new Date(Date.now() + m.intervalDays * 86400000).toISOString()\n changed = true\n }\n }\n\n if (changed) {\n writeFileSync(\n this.filePath(agentId),\n memories.map(m => JSON.stringify(m)).join(\"\\n\") + \"\\n\",\n )\n }\n }\n\n /**\n * Get memories due for review (spaced repetition).\n * Returns facts whose nextReview date has passed or that have never been reviewed.\n */\n getDueForReview(agentId: string, limit: number = 5): MemoryFact[] {\n const memories = this.getAll(agentId)\n const now = Date.now()\n\n return memories\n .filter(m => {\n // Skip task-state (ephemeral)\n if (m.category === \"task-state\") return false\n // Never reviewed → due\n if (!m.nextReview) return true\n // Past review date → due\n return new Date(m.nextReview).getTime() <= now\n })\n .sort((a, b) => {\n // Prioritize: never reviewed, then oldest nextReview, then most accessed\n if (!a.nextReview && b.nextReview) return -1\n if (a.nextReview && !b.nextReview) return 1\n if (a.nextReview && b.nextReview) {\n return new Date(a.nextReview).getTime() - new Date(b.nextReview).getTime()\n }\n return (a.accessCount || 0) - (b.accessCount || 0)\n })\n .slice(0, limit)\n }\n\n /**\n * Build a recall prompt for memories due for review.\n * Injected into heartbeat context so the agent proactively recalls facts.\n */\n buildRecallContext(agentId: string): string {\n const due = this.getDueForReview(agentId, 5)\n if (due.length === 0) return \"\"\n\n const lines = [\"[Memory Recall — facts due for review, verify they are still accurate]\"]\n for (const m of due) {\n const age = Math.round((Date.now() - new Date(m.createdAt).getTime()) / 86400000)\n lines.push(`- [${m.category}] ${m.content} (${age}d old, accessed ${m.accessCount || 0}x)`)\n }\n lines.push(\"[If any fact is outdated, note the correction. Otherwise, acknowledge recall.]\")\n return lines.join(\"\\n\")\n }\n\n /**\n * Check if a substantially similar memory already exists.\n */\n hasSimilar(agentId: string, content: string): boolean {\n const memories = this.getAll(agentId)\n const contentLower = content.toLowerCase()\n const contentWords = contentLower.split(/\\s+/).filter(w => w.length > 3)\n\n return memories.some(m => {\n const mLower = m.content.toLowerCase()\n // Check for high word overlap\n const overlap = contentWords.filter(w => mLower.includes(w)).length\n return overlap >= contentWords.length * 0.7\n })\n }\n\n /**\n * Prune old/expired memories. Cap at MAX_MEMORIES per agent.\n */\n prune(agentId: string): void {\n let memories = this.getAll(agentId)\n if (memories.length === 0) return\n\n const now = Date.now()\n const ttlMs = TASK_STATE_TTL_DAYS * 24 * 60 * 60 * 1000\n\n // Remove expired task-state memories\n memories = memories.filter(m => {\n if (m.category === \"task-state\" && m.expiresAt) {\n return new Date(m.expiresAt).getTime() > now\n }\n if (m.category === \"task-state\" && !m.expiresAt) {\n return now - new Date(m.createdAt).getTime() < ttlMs\n }\n return true\n })\n\n // Cap at MAX_MEMORIES (keep most recent)\n if (memories.length > MAX_MEMORIES) {\n memories = memories.slice(-MAX_MEMORIES)\n }\n\n // Rewrite file\n const file = this.filePath(agentId)\n writeFileSync(file, memories.map(m => JSON.stringify(m)).join(\"\\n\") + \"\\n\")\n }\n}\n","import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\n\n// --- AgentMemory: Claude-Code-style structured memory per agent ---\n//\n// Four memory kinds, each a separate file with YAML frontmatter + markdown\n// body, gathered under a per-agent root index (MEMORY.md):\n//\n// user/ — \"who this human is, what they do, what they know\"\n// feedback/ — \"rules the user gave me; what to do / avoid\"\n// project/ — \"facts about current work the code can't reveal\"\n// reference/ — \"pointers to external systems (linear board, grafana, …)\"\n//\n// Layout on disk:\n// .agentx/agent-memory/<agentId>/\n// MEMORY.md — one-line hook per memory, auto-maintained\n// user_deep_backend_expertise.md\n// feedback_no_mock_database.md\n// ...\n//\n// Philosophy: the wiki is authoritative / cross-agent / documented.\n// Memory is experiential / per-agent / \"what I learned across runs.\"\n// Durable, cross-agent-relevant memories get promoted to shared wiki\n// articles by `agentx wiki promote` (src/wiki/promote.ts) — corroboration\n// across agents boosts confidence but isn't required. This module stays\n// read-only from the promotion pipeline's perspective; authors keep both\n// layers parallel.\n\nexport type MemoryType = \"user\" | \"feedback\" | \"project\" | \"reference\"\n\nexport interface MemoryRecord {\n /** Slug-safe name, unique within an agent. */\n name: string\n type: MemoryType\n /** One-line hook — used as the MEMORY.md index entry. */\n description: string\n /** The body, excluding frontmatter. */\n body: string\n createdAt: string\n updatedAt: string\n}\n\nconst TYPES: MemoryType[] = [\"user\", \"feedback\", \"project\", \"reference\"]\n\nexport interface AgentMemoryOptions {\n baseDir?: string\n}\n\nexport class AgentMemory {\n readonly baseDir: string\n\n constructor(opts: AgentMemoryOptions = {}) {\n const root = opts.baseDir ?? resolve(process.cwd(), \".agentx\")\n this.baseDir = resolve(root, \"agent-memory\")\n }\n\n private dirFor(agentId: string): string {\n const safe = slug(agentId)\n const dir = resolve(this.baseDir, safe)\n mkdirSync(dir, { recursive: true })\n return dir\n }\n\n private fileFor(agentId: string, type: MemoryType, name: string): string {\n return resolve(this.dirFor(agentId), `${type}_${slug(name)}.md`)\n }\n\n private indexPath(agentId: string): string {\n return resolve(this.dirFor(agentId), \"MEMORY.md\")\n }\n\n /** Save (create or update) a memory. Keeps createdAt stable across\n * updates; bumps updatedAt. Always rewrites MEMORY.md. */\n save(args: {\n agentId: string\n type: MemoryType\n name: string\n description: string\n body: string\n }): MemoryRecord {\n if (!TYPES.includes(args.type)) throw new Error(`invalid memory type: ${args.type}`)\n if (!args.name.trim()) throw new Error(\"memory name is required\")\n if (!args.description.trim()) throw new Error(\"memory description is required\")\n\n const path = this.fileFor(args.agentId, args.type, args.name)\n const now = new Date().toISOString()\n let createdAt = now\n const existing = safeRead(path)\n if (existing) {\n const parsed = parseMemoryFile(existing)\n if (parsed) createdAt = parsed.createdAt\n }\n const record: MemoryRecord = {\n name: slug(args.name),\n type: args.type,\n description: args.description.trim(),\n body: args.body.trimEnd(),\n createdAt,\n updatedAt: now,\n }\n writeFileSync(path, serialize(record))\n this.rewriteIndex(args.agentId)\n return record\n }\n\n get(agentId: string, name: string): MemoryRecord | null {\n for (const type of TYPES) {\n const path = this.fileFor(agentId, type, name)\n if (!existsSync(path)) continue\n const parsed = parseMemoryFile(safeRead(path) ?? \"\")\n if (parsed) return parsed\n }\n return null\n }\n\n /** Every memory for an agent. Sorted by type then name for stable\n * index rendering. */\n list(agentId: string): MemoryRecord[] {\n const dir = this.dirFor(agentId)\n if (!existsSync(dir)) return []\n const out: MemoryRecord[] = []\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (!entry.isFile() || !entry.name.endsWith(\".md\") || entry.name === \"MEMORY.md\") continue\n const parsed = parseMemoryFile(safeRead(resolve(dir, entry.name)) ?? \"\")\n if (parsed) out.push(parsed)\n }\n return out.sort((a, b) => {\n const ta = TYPES.indexOf(a.type)\n const tb = TYPES.indexOf(b.type)\n if (ta !== tb) return ta - tb\n return a.name.localeCompare(b.name)\n })\n }\n\n /** Remove a memory by name. Returns true on success, false if none found. */\n remove(agentId: string, name: string): boolean {\n let removed = false\n for (const type of TYPES) {\n const path = this.fileFor(agentId, type, name)\n if (existsSync(path)) { unlinkSync(path); removed = true }\n }\n if (removed) this.rewriteIndex(agentId)\n return removed\n }\n\n /** The content of MEMORY.md — used at prompt-build time to inline into\n * the agent's system prompt. Returns \"\" when the agent has no\n * memories (so the prompt builder can no-op). */\n indexMarkdown(agentId: string): string {\n const path = this.indexPath(agentId)\n return safeRead(path) ?? \"\"\n }\n\n /** Push the agent's memory into its workspace so Claude Code sessions\n * pick it up automatically. Does two things:\n *\n * 1. Writes `<workspace>/.agentx-memory.md` — full MEMORY.md content,\n * explicit file the skill can `Read` at session start.\n * 2. Merges that same content into `<workspace>/CLAUDE.md` between\n * sentinel comments so Claude Code auto-loads it into the cached\n * system context on every session. Idempotent: subsequent syncs\n * replace the sentinel block.\n *\n * Never touches content outside the sentinel block — if the file\n * doesn't exist, creates it with just the sentinels. Safe to call\n * on any save / delete. */\n syncToWorkspace(agentId: string, workspacePath: string): void {\n if (!workspacePath) return\n const memory = this.indexMarkdown(agentId).trim()\n\n // 1. Explicit file for the `remember` skill to Read if it wants to.\n const explicitPath = resolve(workspacePath, \".agentx-memory.md\")\n if (memory) {\n writeFileSync(explicitPath, memory + \"\\n\")\n } else if (existsSync(explicitPath)) {\n unlinkSync(explicitPath)\n }\n\n // 2. Sentinel-merged into CLAUDE.md for auto-inject.\n const claudePath = resolve(workspacePath, \"CLAUDE.md\")\n const start = \"<!-- AGENTX-MEMORY-START — auto-managed by agentx; edit via `agentx memory` -->\"\n const end = \"<!-- AGENTX-MEMORY-END -->\"\n const block = memory\n ? `${start}\\n${memory}\\n${end}`\n : \"\"\n\n const existing = safeRead(claudePath) ?? \"\"\n const startIdx = existing.indexOf(start)\n const endIdx = existing.indexOf(end)\n\n let next: string\n if (startIdx >= 0 && endIdx > startIdx) {\n // Replace existing sentinel block (or remove it if memory is empty).\n const before = existing.slice(0, startIdx).replace(/\\n+$/, \"\")\n const after = existing.slice(endIdx + end.length).replace(/^\\n+/, \"\")\n next = block\n ? [before, block, after].filter(Boolean).join(\"\\n\\n\") + \"\\n\"\n : [before, after].filter(Boolean).join(\"\\n\\n\") + (before || after ? \"\\n\" : \"\")\n } else if (block) {\n // No prior block — append to the end of CLAUDE.md (creates the file\n // if needed). Keeps any existing content untouched.\n next = existing\n ? existing.replace(/\\n+$/, \"\") + \"\\n\\n\" + block + \"\\n\"\n : block + \"\\n\"\n } else {\n // Empty memory + no prior block → nothing to do.\n return\n }\n writeFileSync(claudePath, next)\n }\n\n /** Regenerate MEMORY.md from the on-disk files. Called automatically\n * after every save / remove; safe to call manually. */\n rewriteIndex(agentId: string): void {\n const entries = this.list(agentId)\n if (entries.length === 0) {\n const p = this.indexPath(agentId)\n if (existsSync(p)) unlinkSync(p)\n return\n }\n const grouped: Record<MemoryType, MemoryRecord[]> = {\n user: [], feedback: [], project: [], reference: [],\n }\n for (const r of entries) grouped[r.type].push(r)\n\n const lines: string[] = []\n lines.push(`# Memory — ${agentId}`, \"\")\n lines.push(\"This is the agent's experiential memory: user preferences,\")\n lines.push(\"feedback, project-specific context, and external references.\")\n lines.push(\"Updated via `agentx memory` CLI or by the agent itself.\")\n for (const type of TYPES) {\n const group = grouped[type]\n if (!group.length) continue\n lines.push(\"\", `## ${titleCase(type)}`, \"\")\n for (const r of group) {\n lines.push(`- **${r.name}** — ${r.description} _(updated ${r.updatedAt.slice(0, 10)})_`)\n }\n }\n lines.push(\"\")\n writeFileSync(this.indexPath(agentId), lines.join(\"\\n\"))\n }\n}\n\n// --- Serialisation helpers ---------------------------------------------\n\nfunction serialize(r: MemoryRecord): string {\n const frontmatter = [\n \"---\",\n `name: ${r.name}`,\n `description: ${escapeYaml(r.description)}`,\n `type: ${r.type}`,\n `created: ${r.createdAt}`,\n `updated: ${r.updatedAt}`,\n \"---\",\n \"\",\n ].join(\"\\n\")\n return frontmatter + r.body + \"\\n\"\n}\n\n/** Minimal frontmatter parser. Accepts `key: value` lines between `---`\n * markers; body is everything after. No YAML flow syntax — if we need\n * it later, swap for a proper parser. */\nexport function parseMemoryFile(raw: string): MemoryRecord | null {\n if (!raw.startsWith(\"---\\n\")) return null\n const end = raw.indexOf(\"\\n---\\n\", 4)\n if (end < 0) return null\n const header = raw.slice(4, end)\n const body = raw.slice(end + 5).replace(/^\\n+/, \"\").trimEnd()\n const fm: Record<string, string> = {}\n for (const line of header.split(\"\\n\")) {\n const colon = line.indexOf(\":\")\n if (colon < 0) continue\n const key = line.slice(0, colon).trim()\n const val = unescapeYaml(line.slice(colon + 1).trim())\n fm[key] = val\n }\n if (!fm.name || !fm.type || !fm.description) return null\n if (!TYPES.includes(fm.type as MemoryType)) return null\n return {\n name: fm.name,\n type: fm.type as MemoryType,\n description: fm.description,\n body,\n createdAt: fm.created ?? \"\",\n updatedAt: fm.updated ?? fm.created ?? \"\",\n }\n}\n\nfunction escapeYaml(s: string): string {\n if (/[:#\\n]/.test(s)) return `\"${s.replace(/\"/g, \"\\\\\\\"\")}\"`\n return s\n}\n\nfunction unescapeYaml(s: string): string {\n if (s.startsWith(\"\\\"\") && s.endsWith(\"\\\"\") && s.length >= 2) {\n return s.slice(1, -1).replace(/\\\\\"/g, \"\\\"\")\n }\n return s\n}\n\nfunction slug(s: string): string {\n return s.toLowerCase().replace(/[^a-z0-9]+/g, \"_\").replace(/^_+|_+$/g, \"\") || \"unnamed\"\n}\n\nfunction titleCase(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n\nfunction safeRead(path: string): string | null {\n try { return readFileSync(path, \"utf-8\") } catch { return null }\n}\n","// --- Message Queue with Modes ---\n//\n// Handles messages arriving while an agent is busy processing.\n// Prevents duplicate runs, lost messages, and race conditions.\n//\n// Modes:\n// collect — batch messages arriving during a run, deliver all at once when done\n// followup — queue each message as a separate follow-up turn after current run\n// drop — silently discard messages while busy (for non-critical channels)\n\nexport type QueueMode = \"collect\" | \"followup\" | \"drop\"\n\nexport interface QueuedMessage {\n text: string\n sender: string\n timestamp: number\n channel: string\n chatId: string\n /** Full context from the original message, preserved for re-routing */\n originalContext?: Record<string, unknown>\n}\n\ninterface SessionQueue {\n mode: QueueMode\n /** Messages waiting to be processed */\n pending: QueuedMessage[]\n /** Whether an agent run is currently in progress for this session */\n running: boolean\n /** Callback to invoke when a queued message should be processed */\n onFlush?: (messages: QueuedMessage[]) => Promise<void>\n}\n\n/**\n * Per-agent, per-session message queue.\n * Keyed by `agentId:channel:chatId`.\n */\nexport class MessageQueue {\n private queues: Map<string, SessionQueue> = new Map()\n private defaultMode: QueueMode\n\n constructor(defaultMode: QueueMode = \"collect\") {\n this.defaultMode = defaultMode\n }\n\n private key(agentId: string, channel: string, chatId: string): string {\n return `${agentId}:${channel}:${chatId}`\n }\n\n private getOrCreate(agentId: string, channel: string, chatId: string): SessionQueue {\n const k = this.key(agentId, channel, chatId)\n if (!this.queues.has(k)) {\n this.queues.set(k, {\n mode: this.defaultMode,\n pending: [],\n running: false,\n })\n }\n return this.queues.get(k)!\n }\n\n /**\n * Set the queue mode for a specific session.\n */\n setMode(agentId: string, channel: string, chatId: string, mode: QueueMode): void {\n this.getOrCreate(agentId, channel, chatId).mode = mode\n }\n\n /**\n * Register a flush callback for when queued messages should be processed.\n */\n onFlush(\n agentId: string,\n channel: string,\n chatId: string,\n callback: (messages: QueuedMessage[]) => Promise<void>,\n ): void {\n this.getOrCreate(agentId, channel, chatId).onFlush = callback\n }\n\n /**\n * Check if an agent is currently busy for a given session.\n */\n isBusy(agentId: string, channel: string, chatId: string): boolean {\n return this.getOrCreate(agentId, channel, chatId).running\n }\n\n /**\n * Mark the start of an agent run. Returns false if already running.\n */\n markRunning(agentId: string, channel: string, chatId: string): boolean {\n const q = this.getOrCreate(agentId, channel, chatId)\n if (q.running) return false\n q.running = true\n return true\n }\n\n /**\n * Enqueue a message that arrived while the agent is busy.\n * Returns the queue mode applied (or null if not busy and no queueing needed).\n */\n enqueue(\n agentId: string,\n channel: string,\n chatId: string,\n message: QueuedMessage,\n ): QueueMode | null {\n const q = this.getOrCreate(agentId, channel, chatId)\n\n if (!q.running) return null // Not busy, process normally\n\n switch (q.mode) {\n case \"drop\":\n return \"drop\"\n\n case \"collect\":\n case \"followup\":\n q.pending.push(message)\n return q.mode\n }\n }\n\n /**\n * Mark the end of an agent run. Flushes pending messages based on mode.\n * Returns messages to process (if any).\n */\n async markDone(\n agentId: string,\n channel: string,\n chatId: string,\n ): Promise<QueuedMessage[]> {\n const q = this.getOrCreate(agentId, channel, chatId)\n q.running = false\n\n if (q.pending.length === 0) return []\n\n const messages = [...q.pending]\n q.pending = []\n\n switch (q.mode) {\n case \"collect\": {\n // Batch all pending messages into a single combined message. The\n // `[sender, HH:MM]:` prefix only helps disambiguate multi-message\n // batches (e.g. several group-chat users speaking while the agent\n // is busy); for a single queued message the prefix turned out to\n // be actively harmful — agents read \"[dashboard, 19:03]: ...\" as\n // a non-human source and got suspicious. Strip it in that case.\n const combinedText = messages.length === 1\n ? messages[0].text\n : messages.map((m) =>\n `[${m.sender}, ${new Date(m.timestamp).toISOString().slice(11, 16)}]: ${m.text}`\n ).join(\"\\n\")\n const combined: QueuedMessage = {\n text: combinedText,\n sender: messages[messages.length - 1].sender,\n timestamp: Date.now(),\n channel: messages[0].channel,\n chatId: messages[0].chatId,\n originalContext: messages[messages.length - 1].originalContext,\n }\n\n // Fire flush callback with combined message\n if (q.onFlush) {\n await q.onFlush([combined])\n }\n return [combined]\n }\n\n case \"followup\":\n // Process each message as a separate follow-up turn\n if (q.onFlush) {\n await q.onFlush(messages)\n }\n return messages\n\n case \"drop\":\n return []\n\n default:\n return messages\n }\n }\n\n /**\n * Get the number of pending messages for a session.\n */\n pendingCount(agentId: string, channel: string, chatId: string): number {\n return this.getOrCreate(agentId, channel, chatId).pending.length\n }\n\n /**\n * Clear all queued messages for a session (e.g., on session reset).\n */\n clear(agentId: string, channel: string, chatId: string): void {\n const k = this.key(agentId, channel, chatId)\n this.queues.delete(k)\n }\n\n /**\n * Get queue stats for monitoring.\n */\n stats(): Array<{ key: string; mode: QueueMode; pending: number; running: boolean }> {\n return Array.from(this.queues.entries()).map(([key, q]) => ({\n key,\n mode: q.mode,\n pending: q.pending.length,\n running: q.running,\n }))\n }\n}\n","import { readFileSync, writeFileSync, appendFileSync, mkdirSync, existsSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { buildIndexCached, scoreAll } from \"../memory/bm25\"\n\n// --- Behavioral Pattern Store ---\n//\n// Agents learn HOW to work, not just WHAT they know.\n// After each response, Haiku extracts behavioral patterns:\n// \"When user asks about deployment, check CI status first\"\n// \"Code reviews should mention test coverage\"\n//\n// Before each prompt, relevant patterns are injected so the agent\n// improves over time — a self-correcting loop.\n\nexport interface BehavioralPattern {\n id: string\n agentId: string\n pattern: string // the learned behavior\n context: string // when this applies\n confidence: number // 0-1, increases with validation\n accessCount: number\n createdAt: string\n lastUsed?: string\n source: { channel: string; date: string }\n}\n\nconst MAX_PATTERNS = 50\nconst EXTRACTION_MODEL = \"claude-haiku-4-20250514\"\n\nconst PATTERN_EXTRACTION_PROMPT = `You are a behavioral pattern extractor. Given a conversation between a user and an AI agent, extract behavioral patterns — lessons about HOW to approach tasks, not facts about the world.\n\nExtract patterns like:\n- \"When asked about X, the approach that works is Y\"\n- \"User prefers Z style of communication\"\n- \"For deployment tasks, always check A before doing B\"\n- \"This codebase uses pattern X, so generate code following X\"\n\nSKIP:\n- Factual information (names, dates, URLs)\n- Routine task completions\n- Greetings or small talk\n\nOutput a JSON array:\n[{\"pattern\":\"the behavioral lesson\",\"context\":\"when this applies\",\"confidence\":0.5}]\n\nIf nothing worth extracting, output: []`\n\nexport class PatternStore {\n private patternsDir: string\n\n constructor(baseDir: string = process.cwd()) {\n this.patternsDir = resolve(baseDir, \".agentx/patterns\")\n if (!existsSync(this.patternsDir)) {\n mkdirSync(this.patternsDir, { recursive: true })\n }\n }\n\n private filePath(agentId: string): string {\n return resolve(this.patternsDir, `${agentId}.jsonl`)\n }\n\n getAll(agentId: string): BehavioralPattern[] {\n const file = this.filePath(agentId)\n if (!existsSync(file)) return []\n try {\n return readFileSync(file, \"utf-8\")\n .split(\"\\n\")\n .filter(l => l.trim())\n .map(l => { try { return JSON.parse(l) } catch { return null } })\n .filter((p): p is BehavioralPattern => p !== null)\n } catch {\n return []\n }\n }\n\n addPattern(agentId: string, pattern: Omit<BehavioralPattern, \"id\" | \"createdAt\" | \"accessCount\">): void {\n const entry: BehavioralPattern = {\n ...pattern,\n id: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`,\n accessCount: 0,\n createdAt: new Date().toISOString(),\n }\n appendFileSync(this.filePath(agentId), JSON.stringify(entry) + \"\\n\")\n }\n\n /**\n * Find patterns relevant to the current task.\n */\n findRelevant(message: string, agentId: string, limit: number = 5): BehavioralPattern[] {\n const patterns = this.getAll(agentId)\n if (patterns.length === 0) return []\n\n const docs = patterns.map(p => `${p.context} ${p.pattern}`)\n const cachePath = resolve(this.patternsDir, `${agentId}_bm25_cache.json`)\n const index = buildIndexCached(docs, cachePath)\n const scored = scoreAll(message, index)\n\n const results = scored\n .slice(0, limit)\n .map(s => patterns[s.docIndex])\n .filter(p => p.confidence >= 0.3) // only confident patterns\n\n // Track access\n if (results.length > 0) {\n this.recordAccess(agentId, results.map(p => p.id))\n }\n\n return results\n }\n\n /**\n * Build context string from relevant patterns for prompt injection.\n */\n buildContext(patterns: BehavioralPattern[]): string {\n if (patterns.length === 0) return \"\"\n\n const lines = [\"[Learned Patterns — behavioral lessons from past interactions]\"]\n for (const p of patterns) {\n lines.push(`- ${p.pattern} (context: ${p.context}, confidence: ${Math.round(p.confidence * 100)}%)`)\n }\n lines.push(\"[Apply these patterns when relevant to the current task]\")\n return lines.join(\"\\n\")\n }\n\n /**\n * Check if a similar pattern already exists.\n */\n hasSimilar(agentId: string, pattern: string): boolean {\n const existing = this.getAll(agentId)\n const words = pattern.toLowerCase().split(/\\s+/).filter(w => w.length > 3)\n return existing.some(p => {\n const pWords = p.pattern.toLowerCase()\n const overlap = words.filter(w => pWords.includes(w)).length\n return overlap >= words.length * 0.6\n })\n }\n\n private recordAccess(agentId: string, ids: string[]): void {\n const patterns = this.getAll(agentId)\n const idSet = new Set(ids)\n let changed = false\n for (const p of patterns) {\n if (idSet.has(p.id)) {\n p.accessCount++\n p.lastUsed = new Date().toISOString()\n // Boost confidence slightly on each use (max 1.0)\n p.confidence = Math.min(1.0, p.confidence + 0.05)\n changed = true\n }\n }\n if (changed) {\n writeFileSync(this.filePath(agentId), patterns.map(p => JSON.stringify(p)).join(\"\\n\") + \"\\n\")\n }\n }\n\n /**\n * Prune low-confidence or old patterns. Cap at MAX_PATTERNS.\n */\n prune(agentId: string): void {\n let patterns = this.getAll(agentId)\n if (patterns.length <= MAX_PATTERNS) return\n\n // Sort by confidence * accessCount (most valuable first)\n patterns.sort((a, b) => (b.confidence * b.accessCount) - (a.confidence * a.accessCount))\n patterns = patterns.slice(0, MAX_PATTERNS)\n writeFileSync(this.filePath(agentId), patterns.map(p => JSON.stringify(p)).join(\"\\n\") + \"\\n\")\n }\n}\n\n/**\n * Extract behavioral patterns from a conversation (fire-and-forget).\n */\nexport async function extractPatterns(\n agentId: string,\n userMessage: string,\n agentResponse: string,\n source: { channel: string; date: string },\n store: PatternStore,\n): Promise<void> {\n if (userMessage.length < 30 && agentResponse.length < 100) return\n\n const { createProvider } = await import(\"@/agent/providers\")\n const provider = createProvider(\"claude\")\n\n const result = await provider.generate(\n [\n { role: \"system\", content: PATTERN_EXTRACTION_PROMPT },\n { role: \"user\", content: `User: ${userMessage.slice(0, 500)}\\n\\nAgent: ${agentResponse.slice(0, 1000)}` },\n ],\n { model: EXTRACTION_MODEL, maxTokens: 512 },\n )\n\n const jsonMatch = result.content.trim().match(/\\[[\\s\\S]*\\]/)\n if (!jsonMatch) return\n\n const patterns = JSON.parse(jsonMatch[0]) as Array<{\n pattern: string\n context: string\n confidence: number\n }>\n\n if (!Array.isArray(patterns) || patterns.length === 0) return\n\n for (const p of patterns) {\n if (store.hasSimilar(agentId, p.pattern)) continue\n store.addPattern(agentId, {\n agentId,\n pattern: p.pattern,\n context: p.context,\n confidence: p.confidence || 0.5,\n source,\n })\n }\n\n // Probabilistic pruning\n if (Math.random() < 0.05) store.prune(agentId)\n}\n","import { EventEmitter } from \"events\"\n\n// --- Internal event bus ---\n//\n// Typed pub/sub singleton. The router and registry emit lifecycle events\n// instead of calling log / SSE / analytics inline; subscribers attach\n// once at boot and observe without coupling to the hot path.\n//\n// Why a bus:\n// - Adding a new side-effect (audit log, dashboard SSE, plugin hook)\n// used to require editing router.ts or registry.ts. Now: subscribe.\n// - The Phase 1 traceRoute() function logs to a single sink; with the\n// bus, it's also published to anyone listening (dashboard, sqlite\n// writer, plugin). Same call site, more receivers.\n// - Plugin loader (Move 3) can subscribe; SQLite writer (Move 2) can\n// subscribe; nothing else changes.\n//\n// Why a singleton:\n// - The router + registry are themselves singletons inside the daemon\n// process. Passing the bus through every constructor would touch\n// every tier-A file for no benefit. The bus is a process-global,\n// same as `console` or the logger.\n//\n// Backward compat:\n// - Every existing log call stays put. The bus is ADDITIVE — emits\n// happen alongside existing logs, not in place of them. Future\n// commits can migrate inline `this.log(...)` → bus subscriber that\n// formats and logs.\n\n/**\n * Lifecycle events the daemon emits. Keep this list narrow and stable\n * — every event becomes a public contract once a subscriber depends on\n * it. Add events sparingly and never remove or rename without a major\n * version bump.\n */\nexport interface AgentXEvents {\n /** Inbound message arrived at the router and was matched to an agent.\n * Fired AFTER the routing pipeline produces a `match` decision. Drops\n * fire `message:dropped` instead. */\n \"message:matched\": {\n channel: string\n chatId: string\n msgId: string\n accountId?: string\n agentId: string\n decidingStage: string\n at: string // ISO\n }\n\n /** Inbound message dropped by the routing pipeline. */\n \"message:dropped\": {\n channel: string\n chatId: string\n msgId: string\n accountId?: string\n decidingStage: string\n reason: string\n at: string\n }\n\n /** A task is about to be dispatched to the agent runtime.\n *\n * taskId is the ULID for the per-execution trace\n * (src/storage/traces.ts). Emitters generate it BEFORE firing this\n * event so it can also be threaded into the runtime call chain;\n * the SQLite subscriber uses it to open the trace row, and the\n * streaming parser uses it to record per-step tool_use/tool_result\n * rows. Optional for backward compatibility — emitters that haven't\n * been updated still produce a valid event and the subscriber\n * falls back to allocating its own id. */\n \"task:started\": {\n agentId: string\n channel: string\n chatId: string\n messagePreview: string // first ~200 chars\n /** Full untruncated message — used by SQLite subscriber to populate\n * task_traces.original_message so `agentx trace replay <taskId>` can\n * re-fire the exact original task. Optional for back-compat with\n * older emitters that only carried the preview. */\n fullMessage?: string\n at: string\n taskId?: string\n }\n\n /** A single step inside an in-flight task — typically a tool call or\n * tool result inside the Claude streaming response. Producer is the\n * per-event callback inside registry.execute's streaming path; the\n * SQLite subscriber persists each step into task_trace_steps under\n * the task's ULID. inputSummary / outputSummary are caller-byte-\n * capped (typically 8KB) so a runaway tool result can't blow up the\n * bus payload or DB row size.\n *\n * Conventional `name` values: \"tool_use\" | \"tool_result\" |\n * \"llm_message\" | \"session_rotation\" | \"error\" | \"preflight\". Free-\n * form strings are accepted to let future capture sites extend\n * without a schema bump. */\n \"task:step\": {\n taskId: string\n agentId: string\n name: string\n action?: string\n status?: \"ok\" | \"error\" | \"in-flight\"\n inputSummary?: string\n outputSummary?: string\n error?: string\n ms?: number\n at: string\n }\n\n /** Task finished — success or failure. Includes usage so subscribers\n * can write to billing, sqlite, or analytics without re-querying.\n *\n * Tier-2 fields hold the portion of THIS task's tokens that fell into\n * the \"above-threshold\" pricing tier (decided at record time by\n * TokenTracker; see usage_daily v5 in src/storage/sqlite.ts). They are\n * optional and additive — emitters that don't compute the split simply\n * omit them and subscribers treat the missing values as 0. */\n \"task:completed\": {\n /** Trace ULID — when present, lets subscribers finalize the exact\n * trace row that the matching task:started opened, eliminating the\n * ambiguity when two tasks for the same (agent, channel, chatId)\n * overlap. Optional for backward compatibility; subscribers fall\n * back to a pending-map lookup when absent. */\n taskId?: string\n agentId: string\n channel: string\n chatId: string\n durationMs: number\n error?: string\n inputTokens?: number\n outputTokens?: number\n cacheReadTokens?: number\n cacheCreateTokens?: number\n tier2InputTokens?: number\n tier2OutputTokens?: number\n tier2CacheReadTokens?: number\n tier2CacheCreateTokens?: number\n /** Agent's final reply text — recorded into task_traces.final_response\n * so `replay --diff` can show original vs current output side-by-side.\n * Optional for back-compat. */\n finalResponse?: string\n /** Model the upstream API actually billed (claude-opus-4-7, gpt-5.5, …)\n * so per-agent / per-tier traffic is priced at its real rate instead\n * of the daemon-wide default. Producers prefer `response.billedModel`\n * (parsed from the runtime's reply) and fall back to the agent's\n * configured `model`. Subscribers fall back to the constant default\n * passed to attachSqliteSubscribers when this is absent. */\n billedModel?: string\n at: string\n }\n\n /** Claude session rotation fired (stale / max-turns / tier-2). */\n \"session:rotated\": {\n /** Trace ULID of the in-flight task at rotation time, when known.\n * When set, the rotation is recorded as a step on that trace. */\n taskId?: string\n agentId: string\n channel: string\n chatId: string\n reason: \"stale\" | \"max-turns\" | \"tier-2\"\n lastTurnInputTokens?: number\n at: string\n }\n}\n\ntype EventName = keyof AgentXEvents\ntype Listener<E extends EventName> = (payload: AgentXEvents[E]) => void\n\nclass TypedEventBus {\n private inner = new EventEmitter()\n\n constructor() {\n // Default listener cap is 10 — easy to hit when several subsystems\n // subscribe (dashboard SSE, sqlite writer, audit log, drift detector).\n // 50 is generous without masking real leaks.\n this.inner.setMaxListeners(50)\n }\n\n on<E extends EventName>(event: E, listener: Listener<E>): this {\n this.inner.on(event, listener as (...args: unknown[]) => void)\n return this\n }\n\n off<E extends EventName>(event: E, listener: Listener<E>): this {\n this.inner.off(event, listener as (...args: unknown[]) => void)\n return this\n }\n\n emit<E extends EventName>(event: E, payload: AgentXEvents[E]): boolean {\n return this.inner.emit(event, payload)\n }\n\n listenerCount<E extends EventName>(event: E): number {\n return this.inner.listenerCount(event)\n }\n\n /** Test/dev helper — drop every subscriber. */\n removeAllListeners(): this {\n this.inner.removeAllListeners()\n return this\n }\n}\n\nlet _instance: TypedEventBus | undefined\nexport function getEventBus(): TypedEventBus {\n if (!_instance) _instance = new TypedEventBus()\n return _instance\n}\n\nexport type { TypedEventBus }\n","// --- Attach mode: wear agentx inside a live Claude Code session ---\n//\n// Normally the daemon OWNS the loop: a message arrives, agentx spawns a\n// `claude` subprocess, feeds it a prompt, collects the result. Attach mode\n// inverts that. A Claude Code session that is already open — the one a human\n// is typing into — registers itself with the daemon and becomes addressable\n// as an agent. Inbound channel traffic is offered to that session instead of\n// spawning a fresh process.\n//\n// The session cannot be pushed to: its stdin belongs to the human. So the\n// daemon never initiates. It queues, and Claude Code's own hooks pull:\n//\n// SessionStart -> register the session (id, cwd, model)\n// UserPromptSubmit -> piggyback a \"N pending\" note onto the human's turn\n// Stop -> the drain point. Depending on the session's delivery\n// mode we stay silent, mention the backlog, or return\n// {decision:\"block\", reason:<msg>} which makes Claude\n// Code continue the conversation with that message as\n// input. That is the whole trick.\n// SessionEnd -> deregister; anything still queued falls back to spawn\n//\n// Everything here is in-memory on purpose. Attach state is a property of live\n// processes, not of the world: if the daemon restarts, the next hook event\n// from any session re-registers it. There is nothing worth persisting and\n// therefore no schema migration.\n\n/** How aggressively a session's Stop hook drains its inbox. */\nexport type DeliveryMode =\n /** Never volunteer anything. The human drains with `/inbox`. */\n | \"manual\"\n /** Mention the backlog in a systemMessage; don't take the turn. Default. */\n | \"notify\"\n /** Take the turn: block the stop and answer until the inbox is empty. */\n | \"auto\"\n\nexport const DELIVERY_MODES: readonly DeliveryMode[] = [\"manual\", \"notify\", \"auto\"] as const\n\nexport function isDeliveryMode(v: unknown): v is DeliveryMode {\n return typeof v === \"string\" && (DELIVERY_MODES as readonly string[]).includes(v)\n}\n\n/**\n * Lifecycle of one offered message.\n *\n * pending ──claim()──► claimed ──answer()──► answered\n * │ │\n * └───expire()────► expired ◄──expire()──┘\n *\n * `expired` is terminal and is the reason attach mode is safe to put in front\n * of production traffic: the dispatcher only waits a bounded time for a live\n * session to pick a message up, then atomically expires it and falls back to\n * spawning a provider the normal way. A session that drains late finds\n * nothing, so a message is never answered twice — and never dropped.\n */\nexport type ItemState = \"pending\" | \"claimed\" | \"answered\" | \"expired\"\n\nexport interface InboxItem {\n id: string\n /** Agent identity this was addressed to (the binding that captured it). */\n agentId: string\n /** Session currently responsible for it. */\n sessionId: string\n state: ItemState\n /** The message text as the channel delivered it. */\n text: string\n /** Where it came from — used for the \"[telegram/@bob]\" prefix on drain. */\n channel: string\n chatId: string\n sender: string\n createdAt: number\n claimedAt?: number\n settledAt?: number\n /** Set once the session produces a reply. */\n answer?: string\n}\n\nexport interface AttachSession {\n /** Claude Code's own session id (`CLAUDE_CODE_SESSION_ID` / hook stdin). */\n sessionId: string\n cwd: string\n /** Agent identities this session answers for. Usually one. */\n agentIds: string[]\n mode: DeliveryMode\n /** Last time any hook event was seen — drives stale-session reaping. */\n lastSeenAt: number\n registeredAt: number\n /** Model string when Claude Code reported one on SessionStart. */\n model?: string\n /** Item currently handed to the session and awaiting its reply. At most one\n * — a session answers serially, so the next Stop's `last_assistant_message`\n * is unambiguously the answer to this item. */\n awaitingAnswer?: string\n /** Items drained in the current Stop-hook chain, reset when the session\n * actually stops. Bounds the auto-drain loop. */\n drainedThisTurn: number\n}\n\n/** Tunables. Deliberately not config-file-backed yet — these are runtime\n * ergonomics, and every one of them has a defensible default. Promote to\n * agentx.json only if a real deployment needs to differ. */\nexport interface AttachOptions {\n /** How long the dispatcher waits for a live session to claim a message\n * before giving up and spawning a provider instead. */\n claimTimeoutMs: number\n /** How long after its last hook event a session is considered gone. Claude\n * Code fires no heartbeat, so a session the human abandoned mid-turn would\n * otherwise hold its bindings forever. */\n staleSessionMs: number\n /** Max items one Stop-hook chain will drain before letting the session\n * stop. Prevents a flood of queued messages from hijacking a whole turn. */\n maxDrainPerTurn: number\n /** Longest single message injected into a session's context. */\n maxItemChars: number\n}\n\nexport const DEFAULT_ATTACH_OPTIONS: AttachOptions = {\n claimTimeoutMs: 90_000,\n staleSessionMs: 15 * 60_000,\n maxDrainPerTurn: 5,\n maxItemChars: 4_000,\n}\n\n/** What the Stop hook should hand back to Claude Code. Mirrors the documented\n * hook output contract; `service.ts` serializes it. */\nexport interface StopDecision {\n /** Present only in `auto` mode when we want the session to keep going. */\n decision?: \"block\"\n reason?: string\n systemMessage?: string\n suppressOutput?: boolean\n}\n","import { newEventId } from \"@/intent/ulid\"\nimport {\n DEFAULT_ATTACH_OPTIONS,\n type AttachOptions,\n type AttachSession,\n type DeliveryMode,\n type InboxItem,\n} from \"./types\"\n\n// --- The attach registry ---\n//\n// Holds every live Claude Code session that has registered itself, which\n// agent identities each answers for, and the messages offered to them.\n//\n// The one piece of real logic here is the item state machine. Everything\n// else is bookkeeping. Two actors race on every item:\n//\n// - the dispatcher, which offered the message and is counting down a\n// claim deadline before it gives up and spawns a provider instead\n// - the session's Stop hook, which may drain the item at any moment\n//\n// Both transitions are guarded by a state check on a single-threaded event\n// loop, so \"compare state, then mutate\" is atomic without a lock. The\n// invariant that matters: an item leaves `pending` exactly once. Whichever\n// of claim() / expire() wins, the loser observes a non-pending state and\n// returns false. That is what makes it safe to put attach mode in front of\n// production channel traffic — a message is answered by the session or by a\n// spawned agent, never by both, never by neither.\n//\n// Timeouts are swept rather than scheduled per item. A single interval in\n// the daemon calls sweep(); tests call it directly with an explicit clock.\n// No dangling setTimeout handles, and deadline behavior is testable without\n// fake timers.\n\n/** Resolution handed back to whoever offered the message. */\nexport type OfferOutcome =\n /** A session answered it. Use this text as the agent's reply. */\n | { kind: \"answered\"; text: string; sessionId: string }\n /** Nobody claimed it in time (or the session died). Spawn a provider. */\n | { kind: \"expired\"; reason: string }\n\ninterface Waiter {\n resolve: (o: OfferOutcome) => void\n}\n\nexport interface OfferInput {\n agentId: string\n text: string\n channel: string\n chatId: string\n sender: string\n}\n\nexport class AttachRegistry {\n private sessions = new Map<string, AttachSession>()\n private items = new Map<string, InboxItem>()\n private waiters = new Map<string, Waiter>()\n private opts: AttachOptions\n\n constructor(opts: Partial<AttachOptions> = {}) {\n this.opts = { ...DEFAULT_ATTACH_OPTIONS, ...opts }\n }\n\n options(): AttachOptions {\n return this.opts\n }\n\n // --- sessions ---\n\n /** Register (or refresh) a session. Idempotent: every hook event calls\n * this, so a daemon restart self-heals on the next Stop/prompt without\n * the human doing anything. Bindings from a previous registration of the\n * same session id survive — SessionStart fires again on resume/compact,\n * and losing the binding there would silently detach a bound session. */\n register(\n sessionId: string,\n info: { cwd?: string; model?: string; now?: number } = {},\n ): AttachSession {\n const now = info.now ?? Date.now()\n const existing = this.sessions.get(sessionId)\n if (existing) {\n existing.lastSeenAt = now\n if (info.cwd) existing.cwd = info.cwd\n if (info.model) existing.model = info.model\n return existing\n }\n const session: AttachSession = {\n sessionId,\n cwd: info.cwd ?? \"\",\n agentIds: [],\n mode: \"notify\",\n lastSeenAt: now,\n registeredAt: now,\n model: info.model,\n drainedThisTurn: 0,\n }\n this.sessions.set(sessionId, session)\n return session\n }\n\n /** Mark activity without creating a session. Returns undefined when the\n * session is unknown, which is the common case: most Claude Code sessions\n * on this machine never attach, and their hook events must stay cheap. */\n touch(sessionId: string, now: number = Date.now()): AttachSession | undefined {\n const s = this.sessions.get(sessionId)\n if (s) s.lastSeenAt = now\n return s\n }\n\n get(sessionId: string): AttachSession | undefined {\n return this.sessions.get(sessionId)\n }\n\n list(): AttachSession[] {\n return [...this.sessions.values()].sort((a, b) => b.lastSeenAt - a.lastSeenAt)\n }\n\n /** Bind an agent identity to a session. One agent maps to at most one\n * session — a later bind steals it, which is what an operator moving\n * between terminals expects. */\n bind(sessionId: string, agentId: string, mode?: DeliveryMode): AttachSession | undefined {\n const session = this.sessions.get(sessionId)\n if (!session) return undefined\n for (const other of this.sessions.values()) {\n if (other.sessionId === sessionId) continue\n other.agentIds = other.agentIds.filter((a) => a !== agentId)\n }\n if (!session.agentIds.includes(agentId)) session.agentIds.push(agentId)\n if (mode) session.mode = mode\n return session\n }\n\n unbind(sessionId: string, agentId?: string): AttachSession | undefined {\n const session = this.sessions.get(sessionId)\n if (!session) return undefined\n session.agentIds = agentId ? session.agentIds.filter((a) => a !== agentId) : []\n return session\n }\n\n setMode(sessionId: string, mode: DeliveryMode): AttachSession | undefined {\n const session = this.sessions.get(sessionId)\n if (!session) return undefined\n session.mode = mode\n return session\n }\n\n /** Which session, if any, currently answers for this agent. */\n sessionFor(agentId: string): AttachSession | undefined {\n for (const s of this.sessions.values()) {\n if (s.agentIds.includes(agentId)) return s\n }\n return undefined\n }\n\n /** Drop a session and release everything it was holding. Queued work is\n * expired, not dropped, so the dispatcher falls back to spawning. */\n deregister(sessionId: string, now: number = Date.now()): void {\n this.sessions.delete(sessionId)\n for (const item of this.items.values()) {\n if (item.sessionId !== sessionId) continue\n if (item.state === \"pending\" || item.state === \"claimed\") {\n this.settle(item, \"expired\", now, \"session ended\")\n }\n }\n }\n\n // --- items ---\n\n /**\n * Offer a message to whichever session owns this agent. Resolves when the\n * session answers, or when the claim deadline passes and the caller should\n * fall back to spawning a provider.\n *\n * Returns null synchronously when no session is bound — the hot path for\n * every normal dispatch, so it must stay allocation-free.\n */\n offer(input: OfferInput, now: number = Date.now()): Promise<OfferOutcome> | null {\n const session = this.sessionFor(input.agentId)\n if (!session) return null\n if (now - session.lastSeenAt > this.opts.staleSessionMs) {\n // The session stopped reporting. Treat it as gone rather than routing\n // real traffic into a terminal nobody is watching.\n this.deregister(session.sessionId, now)\n return null\n }\n\n const item: InboxItem = {\n id: newEventId(now),\n agentId: input.agentId,\n sessionId: session.sessionId,\n state: \"pending\",\n text: clip(input.text, this.opts.maxItemChars),\n channel: input.channel,\n chatId: input.chatId,\n sender: input.sender,\n createdAt: now,\n }\n this.items.set(item.id, item)\n\n return new Promise<OfferOutcome>((resolve) => {\n this.waiters.set(item.id, { resolve })\n })\n }\n\n /** Items still waiting on a session, oldest first. */\n pending(sessionId: string): InboxItem[] {\n return [...this.items.values()]\n .filter((i) => i.sessionId === sessionId && i.state === \"pending\")\n .sort((a, b) => a.createdAt - b.createdAt)\n }\n\n pendingCount(sessionId: string): number {\n let n = 0\n for (const i of this.items.values()) {\n if (i.sessionId === sessionId && i.state === \"pending\") n++\n }\n return n\n }\n\n /**\n * Take the next pending item for a session. The atomic half of the race:\n * an item observed as `pending` is flipped to `claimed` before any await\n * point, so a concurrent expire() finds it already gone.\n *\n * Only one item may be outstanding per session — the reply-capture path\n * reads Claude Code's `last_assistant_message`, and that is only\n * unambiguous if exactly one question is in flight.\n */\n claim(sessionId: string, now: number = Date.now()): InboxItem | undefined {\n const session = this.sessions.get(sessionId)\n if (!session) return undefined\n if (session.awaitingAnswer) return undefined\n\n const next = this.pending(sessionId)[0]\n if (!next || next.state !== \"pending\") return undefined\n\n next.state = \"claimed\"\n next.claimedAt = now\n session.awaitingAnswer = next.id\n session.lastSeenAt = now\n session.drainedThisTurn++\n return next\n }\n\n /** Record a session's reply and resolve the dispatcher's promise. */\n answer(sessionId: string, text: string, now: number = Date.now()): InboxItem | undefined {\n const session = this.sessions.get(sessionId)\n if (!session?.awaitingAnswer) return undefined\n const item = this.items.get(session.awaitingAnswer)\n session.awaitingAnswer = undefined\n if (!item || item.state !== \"claimed\") return undefined\n\n item.answer = text\n this.settle(item, \"answered\", now)\n return item\n }\n\n /** Give an item back without answering it — e.g. the human said \"not now\".\n * Returns it to `pending` so the claim deadline can still be met by a\n * later drain, or expire normally. */\n release(sessionId: string, now: number = Date.now()): InboxItem | undefined {\n const session = this.sessions.get(sessionId)\n if (!session?.awaitingAnswer) return undefined\n const item = this.items.get(session.awaitingAnswer)\n session.awaitingAnswer = undefined\n if (!item || item.state !== \"claimed\") return undefined\n item.state = \"pending\"\n item.claimedAt = undefined\n session.lastSeenAt = now\n return item\n }\n\n /** The other atomic half. Returns false when the item already left\n * `pending`/`claimed`, meaning a session got there first. */\n expire(itemId: string, reason: string, now: number = Date.now()): boolean {\n const item = this.items.get(itemId)\n if (!item) return false\n if (item.state !== \"pending\" && item.state !== \"claimed\") return false\n const session = this.sessions.get(item.sessionId)\n if (session?.awaitingAnswer === item.id) session.awaitingAnswer = undefined\n this.settle(item, \"expired\", now, reason)\n return true\n }\n\n /**\n * Advance deadlines. Called on an interval by the daemon and directly by\n * tests. An item gets `claimTimeoutMs` to be claimed, and — because a\n * claimed item is a session's active question — the same budget again to\n * be answered, after which we assume the human walked away.\n */\n sweep(now: number = Date.now()): number {\n let expired = 0\n for (const item of this.items.values()) {\n if (item.state === \"pending\" && now - item.createdAt >= this.opts.claimTimeoutMs) {\n if (this.expire(item.id, \"not claimed in time\", now)) expired++\n } else if (\n item.state === \"claimed\" &&\n now - (item.claimedAt ?? item.createdAt) >= this.opts.claimTimeoutMs\n ) {\n if (this.expire(item.id, \"claimed but never answered\", now)) expired++\n }\n }\n for (const session of this.sessions.values()) {\n if (now - session.lastSeenAt > this.opts.staleSessionMs) {\n this.deregister(session.sessionId, now)\n }\n }\n return expired\n }\n\n /** Reset the per-turn drain counter — the session actually stopped. */\n endTurn(sessionId: string): void {\n const s = this.sessions.get(sessionId)\n if (s) s.drainedThisTurn = 0\n }\n\n /** Test/inspection helper. */\n item(id: string): InboxItem | undefined {\n return this.items.get(id)\n }\n\n /** Drop settled items older than an hour so a long-lived daemon doesn't\n * accumulate them. In-flight items are never touched. */\n prune(now: number = Date.now(), olderThanMs = 3_600_000): number {\n let n = 0\n for (const [id, item] of this.items) {\n const settled = item.state === \"answered\" || item.state === \"expired\"\n if (settled && now - (item.settledAt ?? item.createdAt) > olderThanMs) {\n this.items.delete(id)\n n++\n }\n }\n return n\n }\n\n /** Single exit point for both terminal states, so the waiter is always\n * resolved exactly once and never leaks. */\n private settle(item: InboxItem, state: \"answered\" | \"expired\", now: number, reason?: string): void {\n item.state = state\n item.settledAt = now\n const waiter = this.waiters.get(item.id)\n if (!waiter) return\n this.waiters.delete(item.id)\n waiter.resolve(\n state === \"answered\"\n ? { kind: \"answered\", text: item.answer ?? \"\", sessionId: item.sessionId }\n : { kind: \"expired\", reason: reason ?? \"expired\" },\n )\n }\n}\n\nfunction clip(s: string, max: number): string {\n if (s.length <= max) return s\n return `${s.slice(0, max)}\\n…[truncated ${s.length - max} chars]`\n}\n","export { AttachRegistry, type OfferOutcome, type OfferInput } from \"./registry\"\nexport {\n DEFAULT_ATTACH_OPTIONS,\n DELIVERY_MODES,\n isDeliveryMode,\n type AttachOptions,\n type AttachSession,\n type DeliveryMode,\n type InboxItem,\n type ItemState,\n type StopDecision,\n} from \"./types\"\n\nimport { AttachRegistry } from \"./registry\"\n\n// Process-global registry. The daemon owns one; the CLI and MCP tools reach\n// it over loopback HTTP rather than importing it, exactly like the guard.\nlet _registry: AttachRegistry | undefined\n\nexport function getAttachRegistry(): AttachRegistry {\n if (!_registry) _registry = new AttachRegistry()\n return _registry\n}\n\n/** Test-only: drop the global so each suite starts clean. */\nexport function resetAttachRegistry(): void {\n _registry = undefined\n}\n","// --- Claude CLI overage eligibility ---\n//\n// When Anthropic disables overage (\"extra usage\" / pay-as-you-go beyond the\n// Max allotment) at the org level, every request that would spill past the\n// regular Max allotment fails with:\n// API Error: 400 invalid_request_error \"You're out of extra usage\"\n//\n// Claude CLI caches the state in ~/.claude.json:\n// cachedExtraUsageDisabledReason: \"org_level_disabled_until…\"\n// overageCreditGrantCache: { \"<id>\": { info: { available: false, … } } }\n//\n// Agents with a warm Claude session can still succeed because the replay is\n// largely cache_read and fits under the regular allotment. Agents without a\n// warm session pay full cache_create on first call, spill into overage, and\n// fail — creating a vicious cycle where the agent never establishes a warm\n// session in the first place (observed on pm-hasanah 2026-04-23).\n//\n// This module reads the cached state so callers can short-circuit doomed\n// cold dispatches and surface a crisp operator-facing error instead of\n// silently burning Claude CLI subprocesses to reproduce the same symptom.\n\nimport { readFileSync } from \"fs\"\nimport { homedir } from \"os\"\nimport { join } from \"path\"\n\nexport interface OverageStatus {\n /** True when Anthropic's overage pool is usable; false when org-disabled. */\n available: boolean\n /** Raw reason string from .claude.json when disabled (e.g. \"org_level_disabled_until\"). */\n reason?: string\n /** Wall-clock ms when this value was computed. */\n checkedAt: number\n /** True when .claude.json was missing or unparseable — treat as \"unknown, assume OK\". */\n unknown?: boolean\n}\n\nexport interface OverageGateResult {\n abort: true\n reason: string\n message: string\n raw: OverageStatus\n}\n\nlet cached: OverageStatus | null = null\nconst TTL_MS = 60_000\n\n/** Clear the in-memory cache. Exposed for tests and for a manual /reload path. */\nexport function clearOverageStatusCache(): void { cached = null }\n\nfunction readClaudeJson(): Record<string, unknown> | null {\n try {\n const path = join(homedir(), \".claude.json\")\n const raw = readFileSync(path, \"utf8\")\n const parsed = JSON.parse(raw)\n return parsed && typeof parsed === \"object\" ? (parsed as Record<string, unknown>) : null\n } catch {\n return null\n }\n}\n\n/**\n * Pure function — given the parsed contents of ~/.claude.json (or null if the\n * file is missing), return the overage status. Split out from the IO path so\n * tests can exercise the decision logic without staging a filesystem.\n *\n * Block only on `cachedExtraUsageDisabledReason === \"org_level_disabled_until\"`\n * — that's what claude.ai writes when an operator has explicitly turned\n * overage off at the account level. Other values (most commonly\n * `out_of_credits`) are the CLI's latest-error snapshot from a failed API\n * call and may recover on the next rotation; blocking on them deadlocks the\n * fleet after a single transient rejection. For those, let the request\n * through — the real Anthropic API response will be authoritative, surfaced\n * via error-map's `overage_disabled` friendly classification.\n *\n * The grant-cache `info.available === false` signal was previously also used\n * as a block condition but suffered the same stale-after-failure problem\n * (the CLI writes it once, never refreshes on small calls), so it has been\n * dropped from the gate. If you need a hard block, set the reason to\n * `org_level_disabled_until` in ~/.claude.json manually.\n */\nexport function parseOverageStatus(\n data: Record<string, unknown> | null,\n now: number = Date.now(),\n): OverageStatus {\n if (!data) return { available: true, checkedAt: now, unknown: true }\n const disabledReason = typeof data.cachedExtraUsageDisabledReason === \"string\"\n ? (data.cachedExtraUsageDisabledReason as string)\n : undefined\n const unavailable = disabledReason === \"org_level_disabled_until\"\n return {\n available: !unavailable,\n reason: disabledReason,\n checkedAt: now,\n }\n}\n\n/**\n * Read the Claude CLI-cached overage state with a short TTL. Does not hit\n * the network — purely reads the CLI's own cache file that it updates after\n * each billing roundtrip.\n */\nexport function getOverageStatus(forceRefresh = false): OverageStatus {\n const now = Date.now()\n if (!forceRefresh && cached && now - cached.checkedAt < TTL_MS) return cached\n cached = parseOverageStatus(readClaudeJson(), now)\n return cached\n}\n\n/**\n * Heuristic gate for cold claude-code dispatches. When overage is\n * unavailable, a fresh call (no claudeSessionId to --resume into) will pay\n * full cache-create and overwhelmingly likely fail with \"out of extra\n * usage\". Short-circuiting saves the subprocess and surfaces a crisp,\n * operator-actionable error.\n *\n * Returns `null` when the dispatch can proceed. Returns an abort struct\n * when the caller should skip the dispatch and surface the message.\n */\nexport function preflightOverageGate(hasWarmSession: boolean): OverageGateResult | null {\n const status = getOverageStatus()\n if (status.available) return null\n if (hasWarmSession) return null // warm session replays via cache_read → stays in regular allotment\n return {\n abort: true,\n reason: status.reason || \"overage_disabled\",\n message:\n \"Claude Max-plan overage is disabled at the org level and this agent has no warm Claude session to cache against. \" +\n \"A cold dispatch would pay a fresh cache-create that spills past the regular allotment, producing 'out of extra usage'. \" +\n \"Re-enable overage at claude.ai/settings/usage, OR wait until a peer agent has established a warm session this agent can inherit before retrying.\",\n raw: status,\n }\n}\n","// --- Claude Max-plan dispatch budget ---\n//\n// Anthropic enforces soft per-window caps on Max-plan message/request counts\n// (the published ballpark is ~45/5h for Pro, ~225/5h for Max 5×, ~900/5h for\n// Max 20×; hourly caps exist too). When the fleet burns through these, new\n// cold dispatches start failing with \"out of extra usage\" (overage gate,\n// handled separately) or silently degraded responses.\n//\n// This module tracks dispatch timestamps across ALL claude-code-tier agents\n// (they share one OAuth, so their usage pools together) in a simple ring\n// buffer and exposes:\n//\n// recordClaudeCodeDispatch() — call when a dispatch is about to fire\n// getClaudeCodeUsage() — counts in the last hour / last 5h\n// preflightQuotaGate(…) — optional short-circuit for cold dispatches\n// when usage is past the configured ceiling\n//\n// Values are deliberately conservative defaults (Max 5× ballpark). Tune via\n// setDispatchBudget() from DaemonConfig at daemon startup. When thresholds\n// are `undefined` the gate is a no-op and only the counters track (pure\n// observability, no behavior change).\n\nexport interface DispatchBudget {\n /** Soft hourly cap. Logs a warning at warnRatio, gates cold dispatches at 100%. */\n maxPerHour?: number\n /** Soft rolling-5-hour cap. Same semantics as maxPerHour. */\n maxPer5h?: number\n /** Warn when usage >= warnRatio × max (default 0.8). */\n warnRatio?: number\n}\n\nconst DEFAULT_BUDGET: Required<Pick<DispatchBudget, \"warnRatio\">> & DispatchBudget = {\n // No hard defaults — opt-in by config. Keep warnRatio so usage logs still\n // emit at 80% when the operator DOES set a cap.\n warnRatio: 0.8,\n}\n\nlet budget: DispatchBudget = { ...DEFAULT_BUDGET }\n\n// Ring buffer of dispatch timestamps, newest last. We only ever need the\n// last 5h, so prune anything older on record.\nconst FIVE_H_MS = 5 * 60 * 60 * 1000\nconst ONE_H_MS = 60 * 60 * 1000\nconst timestamps: number[] = []\n\nexport function setDispatchBudget(next: DispatchBudget | undefined): void {\n budget = { ...DEFAULT_BUDGET, ...(next || {}) }\n}\n\nexport function clearDispatchHistory(): void { timestamps.length = 0 }\n\nfunction prune(now: number): void {\n const cutoff = now - FIVE_H_MS\n let i = 0\n while (i < timestamps.length && timestamps[i] < cutoff) i++\n if (i > 0) timestamps.splice(0, i)\n}\n\nexport function recordClaudeCodeDispatch(now: number = Date.now()): void {\n prune(now)\n timestamps.push(now)\n}\n\nexport interface ClaudeCodeUsage {\n lastHour: number\n last5h: number\n maxPerHour?: number\n maxPer5h?: number\n hourlyRatio?: number // usage/max in [0,1] when max is set\n fiveHourRatio?: number // usage/max in [0,1] when max is set\n}\n\nexport function getClaudeCodeUsage(now: number = Date.now()): ClaudeCodeUsage {\n prune(now)\n const hourCutoff = now - ONE_H_MS\n let lastHour = 0\n for (let i = timestamps.length - 1; i >= 0; i--) {\n if (timestamps[i] >= hourCutoff) lastHour++\n else break\n }\n const last5h = timestamps.length\n const out: ClaudeCodeUsage = {\n lastHour,\n last5h,\n maxPerHour: budget.maxPerHour,\n maxPer5h: budget.maxPer5h,\n }\n if (budget.maxPerHour && budget.maxPerHour > 0) out.hourlyRatio = lastHour / budget.maxPerHour\n if (budget.maxPer5h && budget.maxPer5h > 0) out.fiveHourRatio = last5h / budget.maxPer5h\n return out\n}\n\n/**\n * Returns null when dispatch is allowed; returns an abort struct when usage\n * is past 100% of a configured cap. Warm sessions are always allowed through\n * (they replay via cache_read and are cheap) — only cold dispatches get\n * gated, same philosophy as preflightOverageGate.\n *\n * The function DOES NOT mutate state (does not increment counters). Call\n * `recordClaudeCodeDispatch()` separately once the caller commits to the\n * dispatch.\n */\nexport function preflightQuotaGate(hasWarmSession: boolean, now: number = Date.now()):\n | { abort: true; reason: string; message: string; usage: ClaudeCodeUsage }\n | null {\n const usage = getClaudeCodeUsage(now)\n if (hasWarmSession) return null\n if (budget.maxPerHour && usage.lastHour >= budget.maxPerHour) {\n return {\n abort: true,\n reason: \"hourly_cap\",\n message:\n `Claude-code fleet dispatch-budget hit: ${usage.lastHour}/${budget.maxPerHour} in the last hour. ` +\n `Cold dispatches are being held back to preserve warm-session headroom. ` +\n `Raise agents.budget.maxPerHour in DaemonConfig if this is a false positive.`,\n usage,\n }\n }\n if (budget.maxPer5h && usage.last5h >= budget.maxPer5h) {\n return {\n abort: true,\n reason: \"five_hour_cap\",\n message:\n `Claude-code fleet dispatch-budget hit: ${usage.last5h}/${budget.maxPer5h} in the last 5h. ` +\n `Cold dispatches are being held back to preserve warm-session headroom. ` +\n `Raise agents.budget.maxPer5h in DaemonConfig if this is a false positive.`,\n usage,\n }\n }\n return null\n}\n\n/**\n * Returns a warning string when usage has crossed the warnRatio threshold,\n * otherwise null. Caller should log and clear the ratcheting state (we don't\n * suppress duplicates here — suppression is a caller concern).\n */\nexport function warnIfNearingCap(now: number = Date.now()): string | null {\n const usage = getClaudeCodeUsage(now)\n const ratio = budget.warnRatio ?? 0.8\n if (usage.hourlyRatio !== undefined && usage.hourlyRatio >= ratio && usage.hourlyRatio < 1) {\n return `claude-code hourly usage ${usage.lastHour}/${usage.maxPerHour} (${Math.round(usage.hourlyRatio * 100)}% of cap)`\n }\n if (usage.fiveHourRatio !== undefined && usage.fiveHourRatio >= ratio && usage.fiveHourRatio < 1) {\n return `claude-code 5h usage ${usage.last5h}/${usage.maxPer5h} (${Math.round(usage.fiveHourRatio * 100)}% of cap)`\n }\n return null\n}\n","// --- Prompt-size drift tracker ---\n//\n// Passive per-agent/channel monitor for the bytes AgentX controls before\n// dispatch (historyContext + systemPromptAppend + task.message). Exposes:\n//\n// recordPromptSize(key, bytes, parts) — called from the dispatch path\n// getPromptSizeReport() — snapshot for /health or ops\n// warnIfPromptGrowing(key, bytes) — returns a warning string when\n// the current sample is >= 1.5x\n// the baseline for its key\n//\n// Baseline is an EWMA (exponential weighted moving average) over the last\n// ~20 samples per key. First few samples just seed the baseline; we only\n// start flagging growth once enough samples have accumulated.\n//\n// This is NOT wired to an alerting backend — it emits via the daemon log.\n// Route downstream as you need (journal-watch, tail+grep, etc).\n\nexport interface PromptSizeParts {\n history: number\n sysPrompt: number\n message: number\n}\n\nexport interface PromptSizeSample {\n key: string\n timestamp: number\n total: number\n parts: PromptSizeParts\n}\n\nexport interface PromptSizeStats {\n key: string\n samples: number\n /** EWMA of total bytes. */\n baseline: number\n /** Most recent total bytes. */\n current: number\n /** Highest observed bytes since process start. */\n peak: number\n lastAt: number\n}\n\nconst ALPHA = 0.2 // EWMA smoothing — current sample weight\nconst WARN_MULTIPLIER = 1.5 // flag when current >= 1.5x baseline\nconst MIN_SAMPLES = 5 // don't warn before this many samples\nconst RING_SIZE = 100 // per-key most-recent-samples kept in memory\n\nconst stats = new Map<string, PromptSizeStats & { ring: PromptSizeSample[] }>()\n\n/** Build a key for the tracker. Scoped per agent+channel+chatId because size\n * varies meaningfully across channels (a gitlab issue reply has a different\n * prompt shape than a telegram DM). */\nexport function promptSizeKey(agentId: string, channel: string, chatId?: string): string {\n return chatId ? `${agentId}:${channel}:${chatId}` : `${agentId}:${channel}`\n}\n\n/** Record a sample. Idempotent; callers may invoke on every dispatch. */\nexport function recordPromptSize(key: string, total: number, parts: PromptSizeParts, now: number = Date.now()): void {\n let s = stats.get(key)\n if (!s) {\n s = {\n key,\n samples: 0,\n baseline: total, // seed with first sample so the first warn comparison is a no-op\n current: total,\n peak: total,\n lastAt: now,\n ring: [],\n }\n stats.set(key, s)\n }\n s.samples += 1\n s.baseline = ALPHA * total + (1 - ALPHA) * s.baseline\n s.current = total\n s.peak = Math.max(s.peak, total)\n s.lastAt = now\n s.ring.push({ key, timestamp: now, total, parts })\n if (s.ring.length > RING_SIZE) s.ring.shift()\n}\n\n/** Returns a warning string if the latest sample is >= 1.5x baseline and we\n * have enough history to trust the baseline; otherwise null. */\nexport function warnIfPromptGrowing(key: string): string | null {\n const s = stats.get(key)\n if (!s) return null\n if (s.samples < MIN_SAMPLES) return null\n const threshold = s.baseline * WARN_MULTIPLIER\n if (s.current < threshold) return null\n const ratio = s.current / s.baseline\n return `prompt-size drift for ${key}: ${s.current} bytes is ${ratio.toFixed(2)}x baseline (${Math.round(s.baseline)} bytes over ${s.samples} samples, peak ${s.peak})`\n}\n\n/** Snapshot of all tracked keys — suitable for a /health or /metrics endpoint. */\nexport function getPromptSizeReport(): Array<Omit<PromptSizeStats, \"key\"> & { key: string }> {\n return Array.from(stats.values()).map(({ ring, ...rest }) => rest)\n}\n\n/** Test / diagnostic reset. */\nexport function clearPromptSizeStats(): void { stats.clear() }\n","import type { DaemonConfig, AgentDef } from \"@/daemon/config\"\nimport { executeTask, type AgentTask, type AgentResponse, type StreamCallback, type ThinkingCallback, type AgentPeer } from \"./runtime\"\nimport { friendlyModelError, renderFriendlyError } from \"./error-map\"\nimport { SessionStore, detectLongMemoryHint } from \"./sessions\"\nimport { WikiHub } from \"@/wiki\"\nimport { RateLimiter } from \"@/daemon/rate-limit\"\nimport { TokenTracker, splitTaskUsageByTier } from \"@/daemon/token-tracker\"\nimport { buildAgentContext, type ContextInput } from \"./context\"\nimport { Classifier, GraphStore, type ClassifyResult } from \"@/graph\"\nimport { HandoverStore } from \"@/channels/handover-store\"\nimport { MemoryStore } from \"./memory-store\"\nimport { AgentMemory } from \"./agent-memory\"\nimport { extractMemories } from \"./memory-extract\"\nimport { MessageQueue, type QueueMode, type QueuedMessage } from \"./message-queue\"\nimport { loadBootstrapFiles, buildBootstrapContext, detectSoulSwitch, listSoulProfiles } from \"./bootstrap\"\nimport { PatternStore, extractPatterns } from \"./patterns\"\nimport { loadReferences, renderReferences } from \"./references/loader\"\nimport { getDefaultLedger } from \"@/intent/instance\"\nimport { loadRecipes, resolveRecipes, type RecipeIndex } from \"./references/recipes\"\nimport type { ReferenceIndex } from \"./references/types\"\nimport { getEventBus } from \"@/events/bus\"\nimport { newEventId } from \"@/intent/ulid\"\nimport { getAttachRegistry } from \"@/attach\"\nimport { debug } from \"@/observability/debug\"\nimport type { LandscapeBuilder } from \"./landscape\"\nimport { preflightOverageGate } from \"./overage-status\"\nimport { getProcessRegistry } from \"./process-registry-instance\"\nimport { getMessageRouter } from \"@/channels/router-instance\"\nimport { preflightQuotaGate, recordClaudeCodeDispatch, warnIfNearingCap, setDispatchBudget } from \"./claude-code-quota\"\nimport { promptSizeKey, recordPromptSize, warnIfPromptGrowing } from \"./prompt-size-tracker\"\nimport { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { WorkflowStore, matchWorkflow } from \"@/workflows\"\nimport { ProcedureStore } from \"@/procedures\"\nimport { matchProcedures, renderProcedureContext } from \"@/procedures/match\"\n\n// --- Agent Registry: lifecycle management + concurrency control ---\n\n/**\n * Context Surgery — coding-channel detector. A `claude-code` or `codex-cli`\n * tier agent triggered by a github/gitlab webhook is doing focused code work\n * on one issue/PR. The mesh-peer landscape and full session-history aren't\n * useful in that context and bias the agent on noise.\n */\nfunction isCodingChannelContext(channel: string, tier: string): boolean {\n if (tier !== \"claude-code\" && tier !== \"codex-cli\") return false\n return channel === \"github\" || channel === \"gitlab\"\n}\n\n/** Global singleton for sub-agent spawning from tools */\nlet globalRegistry: AgentRegistry | undefined\n\nexport function setGlobalRegistry(registry: AgentRegistry): void {\n globalRegistry = registry\n}\n\nexport function getGlobalRegistry(): AgentRegistry | undefined {\n return globalRegistry\n}\n\nexport interface RunningTask {\n /** Unique id for this execution (timestamp-based). */\n id: string\n /** First 200 chars of the user message, for display. */\n messagePreview: string\n /** Origin channel (telegram, whatsapp, gitlab, api, cron, business, a2a, …). */\n channel: string\n /** Group / chat / issue id from which the task arrived. */\n chatId?: string\n /** Sender id/name (user, agent id, \"cron:<id>\", \"mesh:<peer>\", …). */\n sender?: string\n /** Wall-clock start. */\n startedAt: Date\n}\n\ntype TaskOutputSubscriber = (chunk: string) => void\n\ninterface TaskOutput {\n agentId: string\n buffer: string\n subscribers: Set<TaskOutputSubscriber>\n done: boolean\n endedAt?: Date\n}\n\n/** Cap per-task buffer to keep memory bounded; recent tail wins. */\nconst TASK_OUTPUT_BUFFER_MAX = 64 * 1024\n/** Keep finished outputs around briefly so a late opener still gets the tail. */\nconst TASK_OUTPUT_TTL_MS = 5 * 60 * 1000\n\n/** Persisted record of a finished task, written to .agentx/task-history. */\nexport interface TaskRecord {\n id: string\n agentId: string\n channel: string\n chatId?: string\n sender?: string\n message: string\n startedAt: string\n endedAt: string\n durationMs: number\n ok: boolean\n error?: string\n /** Final agent text (one-shot, may be empty if streaming captured it). */\n responseText: string\n /** Terminal-style transcript captured from stream-json events. */\n transcript: string\n}\n\nconst TASK_HISTORY_DIR = \".agentx/task-history\"\n/** Default retention for persisted task records. Set for business audit trails;\n * bump via the `dashboard.taskHistoryRetentionDays` config field if you need more. */\nconst TASK_HISTORY_RETENTION_DAYS = 30\n\n/** Truncate long values so the buffer doesn't blow up on huge tool inputs/outputs. */\nfunction clip(s: string, max = 800): string {\n if (s.length <= max) return s\n return s.slice(0, max) + ` …[+${s.length - max} chars]`\n}\n\n/**\n * Phase 3 wiki layer — compact pointer to the `agentx wiki query` tool.\n * The wiki is the cross-agent institutional-knowledge source of truth.\n * Agents call it themselves when the question is about \"who / what\n * happened / what we decided / how we do X.\" Empty when the agent has no\n * catalog yet, so new installs see no noise.\n *\n * Uses an absolute CLI path because `agentx` is not on PATH in the agent\n * workspaces. `process.argv[1]` points at the running daemon's cli.js.\n */\nfunction buildWikiQueryHint(agentWiki: ReturnType<WikiHub[\"getAgentWiki\"]>, agentId: string): string {\n let articleCount = 0\n try {\n articleCount = agentWiki.listArticles(agentId).length\n } catch {\n return \"\"\n }\n if (articleCount === 0) return \"\"\n const catalogPath = resolve(agentWiki.baseDir, \"_index.md\")\n if (!existsSync(catalogPath)) return \"\"\n const cli = process.argv[1] || \"dist/cli.js\"\n const wikiDir = agentWiki.baseDir.replace(new RegExp(`/agents/${agentId}(/[^/]+)?$`), \"\")\n return [\n \"[Institutional Wiki — cross-agent source of truth]\",\n `A curated wiki with ${articleCount} article${articleCount === 1 ? \"\" : \"s\"} grouped by type: person, project, place, concept, event, decision, pattern.`,\n \"\",\n \"The wiki is the canonical source for: who people are (team, clients, agents), past events (incidents, deploys), decisions and their reasoning, documented procedures, patterns.\",\n \"\",\n \"BEFORE you grep the workspace, search memory, or answer from your own prior context — if the question is about who / what happened / what we decided / how we do X, call:\",\n ` node ${cli} wiki query \"the user's question\" --dir ${wikiDir} --agent ${agentId}`,\n \"\",\n \"It walks the catalog + wikilink graph and returns a cited answer. Your workspace memory is local to you; the wiki is shared and authoritative. Prefer the wiki for institutional questions; prefer your workspace for code-level questions.\",\n \"[End Institutional Wiki]\",\n ].join(\"\\n\")\n}\n\n/**\n * Format raw Claude Code stream-json events into a terminal-style transcript\n * for the dashboard streaming modal. Returns \"\" for events we don't surface.\n *\n * Stateful so we can emit only the *delta* of assistant text across consecutive\n * `assistant` snapshot events (Claude re-sends the cumulative content each tick).\n */\n/**\n * Cap a string at the configured byte budget, suffixing with \"…[+N]\" when\n * truncated so the trace reader knows there's more upstream. 8KB lets us\n * keep a useful preview of e.g. a Bash tool's stdout while bounding the\n * row size — anything larger almost always exceeds operator attention.\n */\nexport const TRACE_STEP_SUMMARY_BYTES = 8 * 1024\nexport function clipForTrace(s: string): string {\n if (s.length <= TRACE_STEP_SUMMARY_BYTES) return s\n const remaining = s.length - TRACE_STEP_SUMMARY_BYTES\n return s.slice(0, TRACE_STEP_SUMMARY_BYTES) + `…[+${remaining}]`\n}\n\n/**\n * Translate a Claude stream-json event into zero-or-more task:step bus\n * events. Mirrors the block walks in makeStreamEventFormatter but emits\n * structured rows for SQLite persistence rather than terminal text.\n *\n * Why a separate function (and not extending the formatter)? Two\n * concerns. The formatter is for the dashboard's live terminal modal —\n * its output is a string and its job is to read like a transcript. The\n * trace store wants typed rows that survive a daemon restart. Keeping\n * them separate means future refactors of either don't ripple.\n */\n/**\n * Tally a single stream event's tool_use blocks into the running per-task\n * counter. Mirrors the block walks used elsewhere — assistant.tool_use\n * fires once per tool the model invoked. Called from the same onEvent\n * pipeline that drives trace step capture, so there's no extra parse pass.\n */\nexport function tallyToolUses(counter: Map<string, number>, event: any): void {\n if (!event || typeof event !== \"object\") return\n if (event.type !== \"assistant\") return\n const blocks = event.message?.content\n if (!Array.isArray(blocks)) return\n for (const block of blocks) {\n if (block?.type !== \"tool_use\") continue\n const name = typeof block.name === \"string\" ? block.name : \"\"\n if (!name) continue\n counter.set(name, (counter.get(name) ?? 0) + 1)\n }\n}\n\n/**\n * Improvement plan #3 — given an agent's `toolUseRequired` list and the\n * tally of tool_use invocations from this task, return the FIRST tool\n * name that wasn't invoked, or null when the contract was satisfied.\n *\n * Empty / unset toolUseRequired → null (no enforcement). Non-empty list\n * with at least one missing tool → the missing tool name; the caller\n * surfaces this as `tool_required_not_called: <name>` so retry logic\n * can distinguish \"model didn't follow the tool-use contract\" from\n * other error shapes.\n */\nexport function firstMissingRequiredTool(\n required: string[] | undefined,\n invoked: Map<string, number>,\n): string | null {\n if (!required || required.length === 0) return null\n for (const name of required) {\n if ((invoked.get(name) ?? 0) === 0) return name\n }\n return null\n}\n\nexport function emitTraceStepsFromStreamEvent(taskId: string, agentId: string, event: any): void {\n if (!event || typeof event !== \"object\") return\n const t = event.type\n const at = new Date().toISOString()\n const bus = getEventBus()\n\n if (t === \"assistant\" && event.message?.content) {\n for (const block of event.message.content) {\n if (block.type === \"tool_use\") {\n const inputJson = block.input != null ? JSON.stringify(block.input) : \"\"\n bus.emit(\"task:step\", {\n taskId,\n agentId,\n name: \"tool_use\",\n action: typeof block.name === \"string\" ? block.name : null,\n status: \"in-flight\",\n inputSummary: inputJson ? clipForTrace(inputJson) : null,\n at,\n } as any)\n }\n }\n return\n }\n if (t === \"user\" && event.message?.content) {\n for (const block of event.message.content) {\n if (block.type === \"tool_result\") {\n const content = Array.isArray(block.content)\n ? block.content.map((b: any) => (typeof b?.text === \"string\" ? b.text : \"\")).join(\"\")\n : typeof block.content === \"string\" ? block.content : \"\"\n bus.emit(\"task:step\", {\n taskId,\n agentId,\n // Action mirrors the originating tool name when Claude provides\n // tool_use_id. We don't have a quick lookup here without\n // threading more state, so leave action null and let consumers\n // pair via tool_use_id if they care.\n name: \"tool_result\",\n status: block.is_error ? \"error\" : \"ok\",\n outputSummary: content ? clipForTrace(content) : null,\n at,\n } as any)\n }\n }\n return\n }\n}\n\nfunction makeStreamEventFormatter(): (event: any) => string {\n let textSeen = \"\"\n let codexTextSeen = \"\"\n return (event: any): string => {\n if (!event || typeof event !== \"object\") return \"\"\n const t = event.type\n if (t === \"codex.spawned\") {\n const model = event.model ? ` model=${event.model}` : \"\"\n const resume = event.resumeSessionId ? ` resume=${String(event.resumeSessionId).slice(0, 8)}` : \"\"\n return `. codex started${model}${resume}\\n`\n }\n if (t === \"thread.started\") {\n const tid = event.thread_id ? ` thread=${String(event.thread_id).slice(0, 8)}` : \"\"\n return `. codex thread started${tid}\\n`\n }\n if (t === \"turn.started\") return \". codex turn started\\n\"\n if (t === \"turn.completed\") {\n const usage = event.usage || {}\n const input = usage.input_tokens ?? usage.inputTokens\n const output = usage.output_tokens ?? usage.outputTokens\n const tokens = input || output ? ` input=${input || 0} output=${output || 0}` : \"\"\n return `. codex turn completed${tokens}\\n`\n }\n if (t === \"item.started\" || t === \"item.completed\") {\n const item = event.item || {}\n if (item.type === \"agent_message\" && typeof item.text === \"string\") {\n const text = item.text\n const delta = text.startsWith(codexTextSeen) ? text.slice(codexTextSeen.length) : text\n codexTextSeen = text.startsWith(codexTextSeen) ? text : codexTextSeen + text\n return delta\n }\n const label = item.type || \"item\"\n const name = item.name || item.command || item.tool_name || item.id || \"\"\n if (t === \"item.started\") return `\\n> codex ${label}${name ? ` ${name}` : \"\"}\\n`\n const summary = item.output || item.result || item.text || item.error || \"\"\n return `< codex ${label}${name ? ` ${name}` : \"\"}${summary ? `: ${clip(String(summary), 800)}` : \"\"}\\n`\n }\n if (t === \"error\" || t === \"turn.failed\") {\n const msg = event.message || event.error || event.reason || JSON.stringify(event)\n return `[error] ${clip(String(msg), 800)}\\n`\n }\n if (t === \"system\" && event.subtype === \"init\") {\n const m = event.model ? ` model=${event.model}` : \"\"\n const sid = event.session_id ? ` session=${event.session_id.slice(0, 8)}` : \"\"\n return `· init${m}${sid}\\n`\n }\n if (t === \"assistant\" && event.message?.content) {\n let out = \"\"\n for (const block of event.message.content) {\n if (block.type === \"text\" && typeof block.text === \"string\") {\n if (block.text.length > textSeen.length) {\n out += block.text.slice(textSeen.length)\n textSeen = block.text\n }\n } else if (block.type === \"thinking\" && typeof block.thinking === \"string\") {\n out += `\\n💭 ${clip(block.thinking, 600)}\\n`\n } else if (block.type === \"tool_use\") {\n const name = block.name || \"tool\"\n const input = block.input ? clip(JSON.stringify(block.input), 400) : \"\"\n out += `\\n→ ${name}(${input})\\n`\n }\n }\n return out\n }\n if (t === \"user\" && event.message?.content) {\n let out = \"\"\n for (const block of event.message.content) {\n if (block.type === \"tool_result\") {\n const c = Array.isArray(block.content)\n ? block.content.map((b: any) => (typeof b?.text === \"string\" ? b.text : \"\")).join(\"\")\n : typeof block.content === \"string\" ? block.content : \"\"\n const flag = block.is_error ? \"← [error] \" : \"← \"\n out += `${flag}${clip(c, 800)}\\n`\n }\n }\n return out\n }\n if (t === \"result\") {\n const dur = event.duration_ms ? ` (${Math.round(event.duration_ms / 1000)}s)` : \"\"\n return `· done${dur}\\n`\n }\n return \"\"\n }\n}\n\ninterface AgentState {\n id: string\n def: AgentDef\n activeTasks: number\n totalTasks: number\n lastActive?: Date\n errors: number\n runningTasks: RunningTask[]\n}\n\nexport class AgentRegistry {\n private agents: Map<string, AgentState> = new Map()\n private config: DaemonConfig\n private providers: Record<string, { apiKey?: string }> = {}\n private sessions: SessionStore\n private wikiHub: WikiHub\n private memoryStore: MemoryStore\n /** Structured per-agent memory (Claude-Code-style: user / feedback /\n * project / reference). Separate from the BM25 fact store above —\n * that's for short-lived facts extracted from conversations; this is\n * for long-lived behavioural memory that inlines into the system\n * prompt on every task. */\n readonly agentMemory: AgentMemory = new AgentMemory()\n private patternStore: PatternStore\n private rateLimiter: RateLimiter\n private tokenTracker: TokenTracker\n private landscape?: LandscapeBuilder\n /** Mesh handle. When set, `execute` falls back to a peer that advertises\n * the requested agent before returning \"Unknown agent\". Duck-typed so\n * we don't create a circular import with src/a2a/mesh.ts. */\n private meshFallback?: {\n findPeerWithSkill: (skillId: string) => { peer: { url: string; token?: string } } | undefined\n sendTask: (peerName: string, text: string, agentId?: string, opts?: { timeoutMs?: number }) => Promise<string>\n directory: () => Array<{ peer: string; healthy: boolean; skills: Array<{ id: string }> }>\n }\n /** Workflow auto-runner — invoked when workflow matching is in `mode: \"auto\"`\n * and a candidate workflow's confidence ≥ autoRunThreshold. Wired by the\n * daemon after the workflow dispatcher is constructed. Returns the runId of\n * the started workflow run; the workflow itself owns posting any reply via\n * its `action.send` / `agent` nodes. Duck-typed to avoid a circular import\n * on WorkflowDispatcher. */\n private workflowAutoRunner?: (input: {\n workflowId: string\n agentId: string\n channel: string\n chatId: string\n message: string\n payload: Record<string, unknown>\n }) => Promise<{ runId?: string }>\n private messageQueue: MessageQueue\n /** Intent Knowledge Graph classifier. Null when graph.enabled=false. */\n private classifier?: Classifier\n private graphStore?: GraphStore\n /** Runtime handover store — shared with MessageRouter via file on disk. */\n private handoverStore: HandoverStore = new HandoverStore()\n /** Active soul profile per agent+chat session: \"agentId:channel:chatId\" → profile name */\n private activeSouls: Map<string, string> = new Map()\n /** Live output captured per running task id — drives the dashboard streaming modal. */\n private taskOutputs: Map<string, TaskOutput> = new Map()\n /** Per-running-task AbortController. Set when execution starts, removed in\n * finally. Cancel paths (operator stop / replace) call .abort() and the\n * runtime kills the underlying claude subprocess.\n *\n * `originalMessage` is the user text recorded in SessionStore at the start\n * of this run. When the operator presses Stop first then Update, we use it\n * to drop the orphan cancelled turn from history so the Update reads as an\n * edit, not a bare follow-up. */\n private taskAborts: Map<string, { agentId: string; channel: string; chatId: string; originalMessage: string; controller: AbortController }> = new Map()\n /** Last completed task summary per agent — single-line blurb for the dashboard card. */\n private lastSummaries: Map<string, { text: string; at: Date; ok: boolean }> = new Map()\n /** 24-hour sparkline cache per agent — recomputed from disk at most once a minute. */\n private sparklineCache: Map<string, { hourly: number[]; at: number }> = new Map()\n /** Per-workspace references registry cache. Loaded lazily on first turn for\n * agents with `contextReferences: true`; reused for the daemon's lifetime\n * (operator-edited registries take effect on next daemon restart — same\n * policy as agentx.json). */\n private referencesCache: Map<string, { refs: ReferenceIndex; recipes: RecipeIndex }> = new Map()\n private log: (...args: unknown[]) => void\n\n constructor(\n config: DaemonConfig,\n log: (...args: unknown[]) => void = console.error.bind(console, \"[agents]\"),\n ) {\n this.log = log\n this.config = config\n this.providers = config.providers\n this.sessions = new SessionStore(process.cwd(), {\n staleMinutes: config.session.staleMinutes,\n maxTurnsPerSession: config.session.maxTurnsPerSession,\n tierTwoThresholdTokens: config.session.tierTwoThresholdTokens,\n })\n this.wikiHub = new WikiHub(undefined, undefined, \"unified\")\n this.memoryStore = new MemoryStore()\n this.patternStore = new PatternStore()\n this.rateLimiter = new RateLimiter()\n this.tokenTracker = new TokenTracker()\n this.messageQueue = new MessageQueue()\n\n // Apply the claude-code fleet dispatch budget. See DaemonConfig.session\n // for the tuning knobs. Pools all claude-code agents under one counter\n // because they share the Max OAuth.\n setDispatchBudget({\n maxPerHour: config.session.maxClaudeCodeDispatchesPerHour,\n maxPer5h: config.session.maxClaudeCodeDispatchesPer5h,\n })\n\n if (config.graph?.enabled) {\n this.graphStore = new GraphStore({\n baseDir: resolve(process.cwd(), config.graph.baseDir),\n log: (...a) => log(\"[graph]\", ...a),\n })\n const draftAgent = config.graph.draftAgent || config.dashboard?.draftAgent\n this.classifier = new Classifier({\n store: this.graphStore,\n daemonUrl: config.dashboard.daemonUrl,\n token: config.dashboard.token,\n draftAgent,\n autoApproveStructure: config.graph.autoApproveStructure,\n autoApproveConfidence: config.graph.autoApproveConfidence,\n classifierModel: config.graph.classifierModel,\n log: (...a) => log(\"[classifier]\", ...a),\n })\n }\n\n for (const [id, def] of Object.entries(config.agents)) {\n this.agents.set(id, {\n id,\n def,\n activeTasks: 0,\n totalTasks: 0,\n errors: 0,\n runningTasks: [],\n })\n }\n }\n\n /**\n * Set landscape builder (called after mesh init).\n */\n setLandscape(builder: LandscapeBuilder): void {\n this.landscape = builder\n }\n\n /** Wire the mesh so `execute` can fall back to a peer that hosts an\n * agent this node doesn't have. Called by the daemon after mesh boot.\n * Pass `undefined` to disable fallback (testing, mesh disabled). */\n setMeshFallback(mesh: NonNullable<AgentRegistry[\"meshFallback\"]>): void {\n this.meshFallback = mesh\n }\n\n /** Wire the workflow auto-runner — see field doc above. Daemon calls this\n * after WorkflowDispatcher boot. Pass undefined to detach (e.g. during\n * reload). */\n setWorkflowAutoRunner(runner: NonNullable<AgentRegistry[\"workflowAutoRunner\"]> | undefined): void {\n this.workflowAutoRunner = runner\n }\n\n /** Hot-swap the provider map. Daemon reload calls this after agentx.json\n * changes — the next task that executes will resolve credentials through\n * the fresh table (see executeTask call-site). In-flight tasks keep the\n * old provider reference from their closure, which is the desired behavior\n * (rotating a key mid-task shouldn't fail the task). */\n setProviders(next: Record<string, { apiKey?: string }>): void {\n this.providers = next\n }\n\n /** Hot-swap the live DaemonConfig reference. Registry reads it lazily at\n * execute-time for landscape + session policies, so next-task semantics\n * match setProviders. */\n setConfig(next: DaemonConfig): void {\n this.config = next\n }\n\n /**\n * If there's an active handover routing TO this agent for this (channel,\n * chatId) pair AND the operator's summary hasn't been consumed yet, pull\n * + clear it so the target agent sees the note exactly once.\n */\n private buildHandoverNote(\n agentId: string,\n channel: string,\n chatId: string,\n ): ContextInput[\"handoverNote\"] {\n const o = this.handoverStore.get(channel, chatId)\n if (!o || o.toAgent !== agentId) return undefined\n const summary = this.handoverStore.consumeSummary(channel, chatId)\n // If already consumed on a prior message, skip — route remains active\n // but no repeated briefing in every turn.\n if (!summary && o.summaryConsumedAt) return undefined\n return {\n fromAgent: o.fromAgent,\n summary: summary || o.summary,\n at: o.createdAt,\n }\n }\n\n /**\n * Get agent definition by ID.\n */\n getAgent(id: string): AgentDef | undefined {\n return this.agents.get(id)?.def\n }\n\n /**\n * Find agent by mention pattern (e.g., \"@my_bot\" -> \"my-agent\").\n * Returns the agent with the longest (most specific) mention match.\n *\n * When `atMentionsOnly` is true, only `@`-prefixed mentions are considered.\n * This is used for messages originating from another bot (cross-daemon\n * Telegram cascades, for example) where we don't want bare-word matches\n * like \"nadia\" in prose or \"devops-mtgl\" quoted in a reply to trigger\n * agents spuriously. Intentional handoffs still work because agents\n * write explicit `@noqta_X_bot` handles.\n */\n findByMention(text: string, opts: { atMentionsOnly?: boolean } = {}): string | undefined {\n const lower = text.toLowerCase()\n let bestId: string | undefined\n let bestLen = 0\n\n for (const [id, state] of this.agents) {\n for (const mention of state.def.mentions) {\n if (opts.atMentionsOnly && !mention.startsWith(\"@\")) continue\n const mentionLower = mention.toLowerCase()\n if (lower.includes(mentionLower) && mentionLower.length > bestLen) {\n bestId = id\n bestLen = mentionLower.length\n }\n }\n }\n\n return bestId\n }\n\n /**\n * Find ALL agents mentioned in text (for bot-to-bot detection).\n */\n findAllMentioned(text: string): string[] {\n const lower = text.toLowerCase()\n const found: string[] = []\n\n for (const [id, state] of this.agents) {\n for (const mention of state.def.mentions) {\n if (lower.includes(mention.toLowerCase())) {\n found.push(id)\n break\n }\n }\n }\n\n return found\n }\n\n /** Wiki hub accessor. */\n getWikiHub(): WikiHub { return this.wikiHub }\n getGraphStore(): GraphStore | undefined { return this.graphStore }\n /** Session-store accessor — used by the /recall HTTP endpoint to expose\n * conversation history to agents that need to rebuild context. */\n getSessionStore(): SessionStore { return this.sessions }\n\n /** Fire-and-forget pre-rotation memo capture. Spawns a one-shot Haiku\n * call against the about-to-be-dropped Claude session asking for the\n * facts/decisions worth carrying forward, persists the result into\n * MemoryStore. Runs asynchronously so the calling rotation path\n * doesn't pay the extraction latency — the next user turn dispatches\n * immediately on the fresh session, and the memo lands in time for\n * whichever turn arrives ~30s later (which is when atlas's bloated-\n * session WhatsApp conversations actually need it).\n *\n * Errors are swallowed by design — memory continuity is best-effort,\n * it must never block or break the rotation it's hooked into. */\n private async captureRotationMemoAsync(\n agentId: string,\n def: AgentDef,\n resumeSessionId: string,\n channel: string,\n chatId: string,\n reason: string,\n ): Promise<void> {\n try {\n const { extractRotationMemo } = await import(\"./rotation-memo\")\n const result = await extractRotationMemo(def, resumeSessionId)\n if (!result.memo) {\n this.log(\n `[${agentId}] rotation memo skipped (${reason}, ${result.reason}, ${result.durationMs}ms)`,\n )\n return\n }\n // Pull a few keywords from the chatId / channel so the memo\n // surfaces from MemoryStore.findRelevant when the same chat\n // continues. Without keywords BM25 has nothing to score against\n // and the memo only floats up via the recent-fallback branch.\n const chatKeyword = chatId.split(/[:@.]/).filter(Boolean).slice(0, 4)\n this.memoryStore.addMemory(agentId, {\n agentId,\n category: \"fact\",\n content: `[Rotation memo · ${reason}] ${result.memo}`,\n keywords: [\"rotation-memo\", channel, ...chatKeyword],\n source: { channel, chatId, sender: \"system:rotation\", date: new Date().toISOString().slice(0, 10) },\n })\n // Also pin it per-chat for DETERMINISTIC injection into the next\n // fresh session — MemoryStore retrieval is BM25-scored and rarely\n // surfaces the memo on the turn right after rotation, which is\n // exactly when it matters.\n this.sessions.setRotationMemo(agentId, channel, chatId, result.memo, reason)\n this.log(\n `[${agentId}] rotation memo captured (${reason}, ${result.durationMs}ms, ${result.memo.length} chars)`,\n )\n } catch (e: any) {\n this.log(`rotation memo error: ${e?.message || e}`)\n }\n }\n\n /**\n * Build peer list for context engine.\n */\n private buildPeerList(agentId: string, channel?: string): Array<{ name: string; handle?: string; role?: string }> {\n const peers: Array<{ name: string; handle?: string; role?: string }> = []\n for (const [id, state] of this.agents) {\n if (id === agentId) continue\n peers.push({\n name: state.def.name,\n handle: this.getChannelHandle(id, channel),\n role: state.def.systemPrompt?.split(\"\\n\")[0]?.slice(0, 80),\n })\n }\n return peers\n }\n\n /**\n * Get the primary channel handle for an agent (e.g. \"@my_bot\" on telegram).\n */\n private getChannelHandle(agentId: string, channel?: string): string | undefined {\n const agent = this.agents.get(agentId)?.def\n if (!agent) return undefined\n\n // For telegram, find the mention that starts with @ (bot username)\n if (channel === \"telegram\") {\n return agent.mentions.find((m) => m.startsWith(\"@\"))\n }\n\n // Fallback: first mention\n return agent.mentions[0]\n }\n\n /**\n * Execute a task on an agent. Respects maxConcurrent limit.\n *\n * Wraps `executeInternal` to record an intent-ledger resolution\n * after completion when `task.intentRef` is set. The resolution\n * write clears the dispatched-but-unresolved slot in\n * `intent_decisions` so Inv-ActiveTaskSafety lookups stop blocking\n * subsequent dispatches to the same (project, subject). Without\n * this, every dispatched decision sits in-flight forever and the\n * active-task check becomes vacuously over-aggressive.\n */\n async execute(task: AgentTask, onDelta?: StreamCallback, onThinking?: ThinkingCallback, onEvent?: (event: any) => void): Promise<AgentResponse> {\n const startedAt = Date.now()\n let response: AgentResponse\n try {\n response = await this.executeInternal(task, onDelta, onThinking, onEvent)\n } catch (e: any) {\n response = { content: \"\", error: e?.message ?? String(e) }\n }\n if (task.intentRef) {\n try {\n const status = response.error\n ? (/timed out|timeout/i.test(response.error) ? \"timed-out\" : \"failed\")\n : \"completed\"\n getDefaultLedger().recordResolution({\n decisionEventId: task.intentRef.eventId,\n decisionDecidedBy: task.intentRef.decidedBy,\n resolvedAt: Date.now(),\n status: status as \"completed\" | \"failed\" | \"timed-out\",\n durationMs: Date.now() - startedAt,\n resultSummary: response.error\n ? response.error.slice(0, 200)\n : (response.content?.slice(0, 200) ?? null),\n })\n } catch (e: any) {\n // Non-fatal — the ledger may have a unique-constraint hit (the\n // resolution was already recorded by a prior call), or the\n // ledger may have failed entirely. Either way, agent dispatch\n // must succeed regardless.\n this.log(`[ledger] resolution write failed for ${task.intentRef.eventId}/${task.intentRef.decidedBy}: ${e?.message ?? e}`)\n }\n }\n return response\n }\n\n /**\n * Internal dispatcher — the real body. See `execute` for the public\n * wrapper that adds intent-ledger resolution recording.\n */\n private async executeInternal(task: AgentTask, onDelta?: StreamCallback, onThinking?: ThinkingCallback, callerOnEvent?: (event: any) => void): Promise<AgentResponse> {\n // Attach mode: a live Claude Code session may have claimed this identity.\n // If so it gets first refusal — but only for a bounded window. When the\n // human doesn't pick the message up we fall through to the normal spawn\n // path below, and the offer is atomically expired so a late drain can't\n // answer it a second time. Attach is a preference, never a black hole.\n const offered = getAttachRegistry().offer({\n agentId: task.agentId,\n text: task.message,\n channel: task.context?.channel || \"api\",\n chatId: String(task.context?.chatId || task.context?.group || \"default\"),\n sender: String(task.context?.sender || \"unknown\"),\n })\n if (offered) {\n const outcome = await offered\n if (outcome.kind === \"answered\") {\n this.log(`[${task.agentId}] answered by attached session ${outcome.sessionId}`)\n return { content: outcome.text, viaAttachedSession: outcome.sessionId } as AgentResponse\n }\n this.log(`[${task.agentId}] attached session did not take it (${outcome.reason}) — spawning`)\n }\n\n const state = this.agents.get(task.agentId)\n if (!state) {\n // Mesh fallback: the agent isn't local but a healthy peer may host\n // it. Look it up in the mesh directory and forward via A2A sendTask.\n // Streaming callbacks are dropped — sendTask doesn't stream today.\n if (this.meshFallback) {\n const peerEntry = this.meshFallback.directory().find(\n (p) => p.healthy && p.skills.some((s) => s.id === task.agentId),\n )\n if (peerEntry) {\n this.log(`[${task.agentId}] not local — routing to mesh peer \"${peerEntry.peer}\"`)\n try {\n // Agent tasks can run for minutes. If the caller (workflow\n // agent node) passed a timeoutMinutes, honour it; otherwise\n // mesh.sendTask defaults to 30 minutes which beats Node's\n // 300s fetch default that was silently aborting long runs.\n const timeoutMs = typeof task.timeoutMinutes === \"number\" && task.timeoutMinutes > 0\n ? task.timeoutMinutes * 60 * 1000\n : undefined\n const content = await this.meshFallback.sendTask(peerEntry.peer, task.message, task.agentId, { timeoutMs })\n return { content, viaMesh: peerEntry.peer } as AgentResponse\n } catch (e: any) {\n this.log(`[${task.agentId}] mesh fallback to \"${peerEntry.peer}\" failed: ${e?.message ?? e}`)\n return { content: \"\", error: `mesh fallback failed: ${e?.message ?? e}` }\n }\n }\n }\n return { content: \"\", error: `Unknown agent: ${task.agentId}` }\n }\n\n // Build session key for queue management\n const qChannel = task.context?.channel || \"api\"\n const qChatId = task.context?.chatId || task.context?.group || task.context?.sender || \"default\"\n\n if (state.activeTasks >= state.def.maxConcurrent) {\n // Synchronous API callers (mesh /task, /ask, direct curl) can't observe\n // a queued result — the queue's flush callback re-routes via channels,\n // not back to the awaiting HTTP caller. Returning `error: __queued__`\n // surfaces as HTTP 500 on the /task endpoint and triggers an \"Error\n // from peer\" comment on whatever channel the upstream is bridged to.\n // For these callers, BLOCK and wait for a slot (up to 25 min — slightly\n // under mesh.sendTask's 30 min default cap) instead of queueing.\n if (qChannel === \"api\") {\n const start = Date.now()\n const maxWaitMs = 25 * 60_000\n const pollIntervalMs = 500\n while (state.activeTasks >= state.def.maxConcurrent) {\n if (Date.now() - start > maxWaitMs) {\n return { content: \"\", error: `Agent \"${task.agentId}\" busy — slot wait timed out after ${Math.round(maxWaitMs / 60000)}m` }\n }\n await new Promise((r) => setTimeout(r, pollIntervalMs))\n }\n // Slot freed — fall through to normal execution path below.\n } else {\n // Channel callers (telegram/gitlab/whatsapp/...) already ack'd the\n // user's message, so queueing is the right behavior — the flush\n // callback will reply via the original channel when the slot frees.\n const mode = (state.def.queueMode as QueueMode) || \"collect\"\n const queued = this.messageQueue.enqueue(task.agentId, qChannel, qChatId, {\n text: task.message,\n sender: task.context?.sender || \"User\",\n timestamp: Date.now(),\n channel: qChannel,\n chatId: qChatId,\n originalContext: task.context as Record<string, unknown>,\n })\n\n if (queued === \"drop\") {\n this.log(`[${task.agentId}] busy, message dropped (mode: drop)`)\n return { content: \"\", error: `Agent \"${task.agentId}\" is busy — message dropped` }\n }\n\n if (queued) {\n const pending = this.messageQueue.pendingCount(task.agentId, qChannel, qChatId)\n this.log(`[${task.agentId}] busy, message queued (mode: ${queued}, pending: ${pending})`)\n return {\n content: \"\",\n error: `__queued__:${queued}:${pending}`,\n }\n }\n }\n }\n\n // Rate limit — WAIT for a slot instead of failing. Dropping messages looks\n // like the bot is broken; queueing for ~5 minutes gives chatty channels a\n // smooth experience and still bails if something's genuinely stuck.\n const rateResult = await this.rateLimiter.acquire(task.agentId, {\n maxWaitMs: 5 * 60_000,\n onWait: (reason, waitMs) => {\n this.log(`[${task.agentId}] ${reason} — queued, resumes in ~${Math.max(1, Math.round(waitMs / 1000))}s`)\n },\n })\n if (!rateResult.ok) {\n this.log(`[${task.agentId}] ${rateResult.reason}`)\n return { content: \"\", error: rateResult.reason }\n }\n\n state.activeTasks++\n state.totalTasks++\n state.lastActive = new Date()\n\n // Track the running task for live visibility (/agents endpoint surfaces this).\n const runningTask: RunningTask = {\n id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n messagePreview: (task.message || \"\").slice(0, 200),\n channel: task.context?.channel || \"api\",\n chatId: task.context?.chatId || task.context?.group,\n sender: task.context?.sender,\n startedAt: new Date(),\n }\n state.runningTasks.push(runningTask)\n\n // AbortController for operator stop / replace. Stored under the running\n // task id so /api/tasks/:id/cancel can resolve and abort it.\n const abortController = new AbortController()\n this.taskAborts.set(runningTask.id, {\n agentId: task.agentId,\n channel: runningTask.channel,\n chatId: typeof runningTask.chatId === \"string\" ? runningTask.chatId : \"\",\n originalMessage: task.message || \"\",\n controller: abortController,\n })\n\n // Output capture for the dashboard streaming modal. We never *force*\n // streaming on a caller that didn't ask for it — that would change the\n // runtime mode (stream-json vs json) for every task in the system. So:\n // - Caller passed onDelta → wrap it to also fan-out to dashboard subscribers (live).\n // We also install onEvent so the dashboard sees tool calls, tool results,\n // and system events — i.e. everything you'd see in a real terminal.\n // - Caller did NOT pass onDelta → leave runtime in non-streaming mode and\n // post the final response.content as a single chunk after execution.\n const output: TaskOutput = { agentId: task.agentId, buffer: \"\", subscribers: new Set(), done: false }\n this.taskOutputs.set(runningTask.id, output)\n const pushToBuffer = (chunk: string) => {\n if (!chunk) return\n output.buffer += chunk\n if (output.buffer.length > TASK_OUTPUT_BUFFER_MAX) {\n output.buffer = output.buffer.slice(output.buffer.length - TASK_OUTPUT_BUFFER_MAX)\n }\n for (const sub of output.subscribers) {\n try { sub(chunk) } catch { /* subscriber crashed — ignore */ }\n }\n }\n // Allocate the per-execution trace id eagerly so both the streaming\n // parser (per-step capture below) and the bus emit a few lines down\n // can target the same task_traces row. Closures defined here reference\n // it by name; the variable is set before any callback can fire.\n const traceTaskId = newEventId()\n task.taskId = traceTaskId\n\n // Per-task tool-use tally (improvement plan #3). Counts each\n // assistant tool_use block by tool name so we can compare against\n // agent.toolUseRequired after the response returns. The count is\n // populated regardless of toolUseRequired being set so traces\n // and dashboards always have it; the post-task check only fires\n // when the agent has the flag.\n const toolUsesByName = new Map<string, number>()\n\n let onEvent: ((event: any) => void) | undefined\n if (onDelta) {\n // Caller's onDelta still fires only for assistant text (unchanged).\n // Dashboard subscribers see the formatted stream-json firehose via onEvent\n // — this is what makes the modal feel like a live terminal.\n const formatter = makeStreamEventFormatter()\n onEvent = (event: any) => {\n const formatted = formatter(event)\n if (formatted) pushToBuffer(formatted)\n // Per-step trace capture (improvement plan #2). Fire one\n // task:step event per tool_use / tool_result block. The\n // SQLite subscriber persists it under traceTaskId.\n emitTraceStepsFromStreamEvent(traceTaskId, task.agentId, event)\n tallyToolUses(toolUsesByName, event)\n // Caller-supplied event subscriber (HTTP SSE callers, etc.).\n // Fire after internal capture so a subscriber crash never breaks\n // our own bookkeeping.\n if (callerOnEvent) { try { callerOnEvent(event) } catch { /* */ } }\n }\n } else {\n // Even without a dashboard subscriber, we want trace steps for\n // /traces/:id. Build a minimal onEvent that ONLY emits steps.\n onEvent = (event: any) => {\n emitTraceStepsFromStreamEvent(traceTaskId, task.agentId, event)\n tallyToolUses(toolUsesByName, event)\n if (callerOnEvent) { try { callerOnEvent(event) } catch { /* */ } }\n }\n }\n\n // Mark session as running in the message queue\n this.messageQueue.markRunning(task.agentId, qChannel, qChatId)\n\n this.log(`[${task.agentId}] executing task (${state.activeTasks}/${state.def.maxConcurrent})`)\n\n // Build conversation history for session continuity\n const channel = task.context?.channel || \"api\"\n const chatId = task.context?.chatId || task.context?.group || task.context?.sender || \"default\"\n const senderName = task.context?.sender || \"User\"\n const isCodexCli = state.def.tier === \"codex-cli\"\n\n // Improvement plan #8 — caller-driven session reset. Honour it before\n // seeding or recording the current inbound message; otherwise the fresh\n // reset can erase the very message that started the task.\n if (task.freshSession) {\n this.sessions.clearSession(task.agentId, channel, chatId)\n this.log(`[${task.agentId}] freshSession=true → cleared session history for ${channel}:${chatId}`)\n getEventBus().emit(\"session:rotated\", {\n agentId: task.agentId, channel, chatId,\n reason: \"stale\",\n at: new Date().toISOString(),\n })\n }\n\n // Mirror the live channel before recording the new user message — on a\n // cold-create (fresh chatId, new day after rotation), this calls the\n // adapter's seedHistory and back-fills recent messages so the agent\n // doesn't start blind. No-op for warm sessions, non-channel callers\n // (cron/api/a2a), or channels without a seedHistory implementation.\n if (!task.freshSession) {\n await this.sessions.seedIfEmpty(task.agentId, channel, chatId)\n }\n\n // Record user message in session\n this.sessions.addUserMessage(task.agentId, channel, chatId, senderName, task.message)\n\n const taskStartedAt = Date.now()\n // traceTaskId allocated above (alongside the streaming onEvent\n // callback). Including it in task:started lets the SQLite\n // subscriber open the trace row under the same id the per-step\n // emitter uses.\n getEventBus().emit(\"task:started\", {\n agentId: task.agentId,\n channel,\n chatId,\n messagePreview: (task.message || \"\").slice(0, 200),\n fullMessage: task.message || \"\",\n at: new Date(taskStartedAt).toISOString(),\n taskId: traceTaskId,\n })\n\n // Classify the message through the intent graph when enabled. Skip for\n // a2a traffic — the classifier itself dispatches through /task, and\n // re-classifying its own prompts would recurse forever. Any classifier\n // failure (bad LLM output, schema rejection, network error) must never\n // propagate — the main task still has to run.\n let intent: ClassifyResult | undefined\n if (this.classifier && channel !== \"a2a\" && !isCodexCli) {\n try {\n intent = (await this.classifier.classify({\n text: task.message,\n channel,\n sender: task.context?.sender,\n agentId: task.agentId,\n })) || undefined\n } catch (e: any) {\n this.log(`[classifier] classify failed for ${task.agentId}: ${e?.message || e}`)\n }\n }\n\n const wfMatching = this.config.workflows?.matching\n // Decision-only here: compute whether auto-run should fire. We don't fire\n // the workflow yet — that happens inside the outer try/finally so\n // runningTask + activeTasks bookkeeping always cleans up.\n let pendingAutoRun: { workflowId: string; confidence: number } | undefined\n if (this.config.workflows?.enabled && wfMatching?.enabled) {\n try {\n const store = new WorkflowStore({ baseDir: resolve(process.cwd(), this.config.workflows.dir) })\n const match = matchWorkflow({\n agentId: task.agentId,\n channel,\n message: task.message,\n intentPath: intent?.path,\n }, store.list())\n if (match && match.confidence >= wfMatching.suggestThreshold) {\n this.log(\n `[${task.agentId}] workflow match ${match.workflow.id} confidence=${match.confidence.toFixed(2)} ` +\n `mode=${wfMatching.mode} reasons=${match.reasons.join(\",\") || \"n/a\"}`,\n )\n if (wfMatching.mode === \"auto\" && match.confidence >= wfMatching.autoRunThreshold) {\n if (this.workflowAutoRunner) {\n pendingAutoRun = { workflowId: match.workflow.id, confidence: match.confidence }\n } else {\n this.log(`[${task.agentId}] workflow auto-run requested but no runner wired; falling back to agent`)\n }\n }\n }\n } catch (e: any) {\n this.log(`[${task.agentId}] workflow matcher failed (non-fatal): ${e?.message || e}`)\n }\n }\n\n // Wiki context — Phase 3 Farzapedia alignment: instead of preloading BM25\n // hits (the old shallow-RAG path), we inject a short pointer to the\n // `agentx wiki query` tool. The agent decides WHEN institutional knowledge\n // matters and invokes the agentic query itself — walking _index.md, picking\n // candidates, following wikilinks 2–3 hops. Zero retrieval cost on messages\n // that don't need it.\n const agentWiki = this.wikiHub.getAgentWiki(task.agentId)\n const wikiContext = buildWikiQueryHint(agentWiki, task.agentId)\n\n // Load persistent agent memory (cross-session facts)\n const relevantMemories = this.memoryStore.findRelevant(task.message, task.agentId, isCodexCli ? 3 : 8)\n // `let`, not `const`, because the planner strategy (below) may overwrite\n // this with its own curated memory bundle when enabled.\n let memoryContext = this.memoryStore.buildContext(relevantMemories)\n\n // Load behavioral patterns (self-improving loop)\n const relevantPatterns = this.patternStore.findRelevant(task.message, task.agentId, 5)\n const patternContext = this.patternStore.buildContext(relevantPatterns)\n\n // Auto-inject skills matched to current message\n let skillInjection = \"\"\n if (!isCodexCli) {\n try {\n const { loadLocalSkills, getAutoInjectSkills } = await import(\"@/agent/skills/loader\")\n const skills = await loadLocalSkills(state.def.workspace)\n skillInjection = getAutoInjectSkills(skills, task.message)\n } catch {\n // Skill loading is optional\n }\n }\n\n // Decide whether to resume or start fresh\n let resumeSessionId = state.def.tier === \"claude-code\"\n ? this.sessions.getClaudeSessionId(task.agentId, channel, chatId)\n : state.def.tier === \"codex-cli\"\n ? this.sessions.getCodexSessionId(task.agentId, channel, chatId)\n : state.def.tier === \"opencode\"\n ? this.sessions.getOpenCodeSessionId(task.agentId, channel, chatId)\n : undefined\n\n // If session is stale (idle > staleMinutes), start fresh with full context rebuild\n if (resumeSessionId && this.sessions.isSessionStale(task.agentId, channel, chatId)) {\n this.log(`[${task.agentId}] session stale for ${channel}:${chatId}, starting fresh`)\n if (state.def.tier === \"claude-code\") {\n void this.captureRotationMemoAsync(task.agentId, state.def, resumeSessionId, channel, chatId, \"stale\")\n }\n this.sessions.clearClaudeSessionId(task.agentId, channel, chatId)\n getEventBus().emit(\"session:rotated\", {\n agentId: task.agentId, channel, chatId,\n reason: \"stale\",\n at: new Date().toISOString(),\n })\n resumeSessionId = undefined\n }\n\n // If the prior turn's END-OF-TURN CONTEXT (per-request input, not the\n // cumulative-with-cache-reads turn total) is near the 200K tier-2\n // boundary, rotate before paying the 1.5× multiplier again. Claude CLI\n // --resume replays every past tool result, so one genuinely bloated\n // context keeps billing tier-2 indefinitely until we drop the session.\n if (resumeSessionId && this.sessions.shouldRotateByTierTwo(task.agentId, channel, chatId)) {\n const lastTokens = this.sessions.getLastTurnContextTokens(task.agentId, channel, chatId)\n this.log(`[${task.agentId}] tier-2 rotation for ${channel}:${chatId} (last turn context: ${lastTokens} tokens ≥ ${this.sessions.getTierTwoThresholdTokens()})`)\n if (state.def.tier === \"claude-code\") {\n void this.captureRotationMemoAsync(task.agentId, state.def, resumeSessionId, channel, chatId, \"tier-2\")\n }\n this.sessions.clearClaudeSessionId(task.agentId, channel, chatId)\n getEventBus().emit(\"session:rotated\", {\n agentId: task.agentId, channel, chatId,\n reason: \"tier-2\",\n lastTurnInputTokens: lastTokens,\n at: new Date().toISOString(),\n })\n resumeSessionId = undefined\n }\n\n // If session has accumulated too many turns, rotate even before it hits\n // tier-2. Claude CLI replays grow linearly with turn count — capping\n // here keeps the per-turn cache-read tax bounded. Compacted summary +\n // recent-messages history seed the next session so nothing is lost.\n if (resumeSessionId && this.sessions.shouldRotateByTurns(task.agentId, channel, chatId)) {\n const turns = this.sessions.getTurnCount(task.agentId, channel, chatId)\n this.log(`[${task.agentId}] max-turns rotation for ${channel}:${chatId} (${turns} turns ≥ ${this.sessions.getMaxTurnsPerSession()})`)\n if (state.def.tier === \"claude-code\") {\n void this.captureRotationMemoAsync(task.agentId, state.def, resumeSessionId, channel, chatId, \"max-turns\")\n }\n this.sessions.clearClaudeSessionId(task.agentId, channel, chatId)\n getEventBus().emit(\"session:rotated\", {\n agentId: task.agentId, channel, chatId,\n reason: \"max-turns\",\n at: new Date().toISOString(),\n })\n resumeSessionId = undefined\n }\n\n // Compact session if history is getting too long (summarize older messages).\n //\n // Compaction NO LONGER drops resumeSessionId — Claude's own session has\n // its own history that --resume replays, independent of our stored copy.\n // The summary gets used at the next legitimate rotation (tier-2,\n // max-turns, stale) when we genuinely need a fresh Claude session and\n // have to seed it from the stored messages. Until then, the hot session\n // stays cache-friendly. See sessions.ts compactIfNeeded for the longer\n // explanation.\n try {\n const compactResult = await this.sessions.compactIfNeeded(\n task.agentId, channel, chatId, this.memoryStore,\n )\n if (compactResult.compacted) {\n const quality = compactResult.qualityScore ?? \"?\"\n const lost = compactResult.lostEntities?.length ?? 0\n this.log(`[${task.agentId}] session compacted for ${channel}:${chatId} (quality: ${quality}%, ${lost} entities lost)`)\n if (compactResult.lostEntities?.length) {\n this.log(`[${task.agentId}] lost entities: ${compactResult.lostEntities.slice(0, 5).join(\", \")}`)\n }\n if (compactResult.drift) {\n const d = compactResult.drift\n this.log(`[${task.agentId}] DRIFT DETECTED: score=${d.overallScore} (lexicon=${d.lexiconDecay}, tools=${d.toolShift}, semantic=${d.semanticDrift})`)\n if (d.lostWords.length) this.log(`[${task.agentId}] lost domain words: ${d.lostWords.slice(0, 5).join(\", \")}`)\n }\n }\n } catch (e: any) {\n this.log(`[${task.agentId}] compaction failed (non-fatal): ${e.message}`)\n }\n\n // Context Surgery — Fix 1: cap session history for coding-channel\n // contexts. Long github/gitlab issue threads anchor the agent on\n // accumulated assumptions (the diagnosis from ksi-v2 review). Tighter\n // cap forces code-first investigation; thread remains accessible via\n // `gh`/`glab` if the agent explicitly fetches it. Gated behind\n // AGENTX_CONTEXT_SURGERY=1 for one-week soak before unconditional rollout.\n const surgeryEnabled = process.env.AGENTX_CONTEXT_SURGERY === \"1\"\n const historyCap = isCodexCli\n ? { maxMessages: 8, maxChars: 2400 }\n : (surgeryEnabled && isCodingChannelContext(channel, state.def.tier))\n ? { maxMessages: 6, maxChars: 1800 }\n : undefined\n\n const sessionHistory = !resumeSessionId\n ? this.sessions.buildHistoryContext(\n task.agentId,\n channel,\n chatId,\n historyCap,\n )\n : undefined\n\n // Continuity memo from the previous (rotated) session — deterministic\n // handover so a fresh session opens knowing the ongoing task instead\n // of amnesiac. Fresh-session-only, same gate as the history rebuild:\n // a resumed session already carries this context natively, and\n // re-injecting per-turn is exactly the bloat that used to force\n // premature tier-2 rotation.\n const rotationMemo = !resumeSessionId\n ? this.sessions.getRotationMemo(task.agentId, channel, chatId) ?? undefined\n : undefined\n\n // Matched procedures — user-perspective SOPs mined from recurring\n // activity. Fresh-session-only, same gate and same reasoning as\n // rotationMemo/references above: a warm session already saw the block,\n // and per-turn re-injection is the bloat that forces tier-2 rotation.\n // Warm sessions can pull one on demand via `agentx procedure match`.\n let procedureContext: string | undefined\n if (this.config.procedures?.injection?.enabled && !resumeSessionId && !isCodexCli) {\n try {\n const procStore = new ProcedureStore({\n baseDir: resolve(process.cwd(), this.config.procedures.dir),\n })\n const procMatches = matchProcedures(task.message, procStore.list(), {\n limit: this.config.procedures.injection.maxProcedures,\n minScore: this.config.procedures.injection.minScore,\n })\n if (procMatches.length > 0) {\n procedureContext = renderProcedureContext(procMatches)\n for (const m of procMatches) {\n this.log(`[${task.agentId}] procedure match ${m.procedure.meta.id} score=${m.score.toFixed(2)} (${m.reasons.join(\",\")})`)\n }\n }\n } catch (e: any) {\n this.log(`[${task.agentId}] procedure matcher failed (non-fatal): ${e?.message || e}`)\n }\n }\n\n // Context-rebuild diagnostic. Fires under `--debug context` (or `all`).\n // The amnesia-vs-misreasoning question — \"did the agent see X in its\n // prompt?\" — was unanswerable from session JSONs alone (the rendered\n // prompt prefix is never persisted). This log captures it per-turn:\n // - resume vs fresh (after rotation, this is \"fresh\")\n // - sessionHistory length when rendered (chars + line count)\n // - session.messages snapshot: count, first ts, last ts, first/last\n // name+content preview so we can tell what's actually in the file.\n // Off by default — zero overhead unless the operator opted in.\n if (debug && (debug as any).cat) {\n try {\n const sess = this.sessions.getSession(task.agentId, channel, chatId)\n const msgs = sess.messages\n const first = msgs[0]\n const last = msgs[msgs.length - 1]\n const fingerprint = sessionHistory === undefined\n ? `resume=${resumeSessionId ? resumeSessionId.slice(0, 8) : \"?\"} (no rebuild)`\n : `fresh history=${sessionHistory.length}c/${sessionHistory.split(\"\\n\").length}L`\n const summary = `[${task.agentId}] ${channel}:${chatId} ${fingerprint} | session.messages=${msgs.length}` +\n (msgs.length > 0\n ? ` first=${first.timestamp.slice(11, 19)}(${first.role}:${(first.content || \"\").slice(0, 40).replace(/\\n/g, \" \")}) last=${last.timestamp.slice(11, 19)}(${last.role}:${(last.content || \"\").slice(0, 40).replace(/\\n/g, \" \")})`\n : \"\")\n debug.cat(\"context\", summary)\n } catch (e: any) {\n // Diagnostic logging must never throw into the hot path.\n debug.cat(\"context\", `[${task.agentId}] context-diag error: ${e?.message ?? e}`)\n }\n }\n\n // Verified deterministic references — opt-in per agent via\n // `contextReferences: true`. Only injected when starting a FRESH Claude\n // session: once a session is in progress, the agent already has any\n // facts it learned in earlier turns, and re-rendering them on every\n // resumed turn just bloats the per-turn user-message context. That\n // bloat compounded with --resume's full-history replay was hitting the\n // 180k tier-2 threshold in 5–10 turns and forcing constant rotation,\n // which discarded the conversation Claude session — causing exactly\n // the \"I don't have prior context\" symptom users observed.\n // The resolver itself runs cheaply in-memory; we just gate the rendered\n // block on `!resumeSessionId`.\n let referencesBlock: string | undefined\n if (state.def.contextReferences && !resumeSessionId) {\n try {\n const cacheKey = state.def.workspace\n let cached = this.referencesCache.get(cacheKey)\n if (!cached) {\n const [refs, recipes] = await Promise.all([\n loadReferences(state.def.workspace),\n loadRecipes(state.def.workspace),\n ])\n if (refs.byId.size === 0 && recipes.recipes.length === 0) {\n const [rootRefs, rootRecipes] = await Promise.all([\n loadReferences(process.cwd()),\n loadRecipes(process.cwd()),\n ])\n cached = { refs: rootRefs, recipes: rootRecipes }\n } else {\n cached = { refs, recipes }\n }\n this.referencesCache.set(cacheKey, cached)\n }\n const resolved = resolveRecipes(\n {\n agentId: task.agentId,\n intentTags: intent?.path,\n message: task.message,\n },\n cached.recipes,\n cached.refs,\n )\n if (resolved.cards.length > 0) {\n referencesBlock = renderReferences(resolved.cards, 500 * 4)\n }\n if (resolved.unresolvedIds.length > 0) {\n this.log(`[${task.agentId}] references: unresolved ids: ${resolved.unresolvedIds.join(\", \")}`)\n }\n } catch (e: any) {\n this.log(`[${task.agentId}] references resolver failed (non-fatal): ${e?.message || e}`)\n }\n }\n\n // Bridge cross-chat amnesia: inject context from other chats (DM ↔ group).\n // Gated on the current message — we only ship the hint when the user\n // actually refers to another conversation, a peer agent, or earlier\n // activity. Otherwise it's pure waste AND breaks prompt cache every turn.\n let crossChatContext = this.sessions.getCrossSessionSummary(\n task.agentId, channel, chatId, task.message,\n )\n\n // Long-memory recall pre-fetch — when the current message has an\n // explicit long-memory cue (\"yesterday\", \"last week\", \"remember when…\",\n // \"we discussed\", Arabic equivalents), pull a wider window from the\n // session store and inject as a context block so the agent doesn't have\n // to call /recall itself in obvious cases. Without this, on a fresh\n // claude session the agent would either fabricate context (the\n // observed \"Tarek Ksibi\" gmail-search failure) or pester the user.\n let longMemoryRecall: string | undefined\n const lmHint = detectLongMemoryHint(task.message)\n if (lmHint) {\n try {\n const recall = this.sessions.recallTurns({\n agentId: task.agentId,\n channel,\n chatId,\n lookbackDays: lmHint.lookbackDays,\n limit: 12,\n })\n if (recall.turns.length > 0) {\n const lines: string[] = [\n `[Long-memory recall — last ${lmHint.lookbackDays}d on this chat (cue-triggered)]`,\n ]\n // Render oldest-first for natural reading flow\n const ordered = [...recall.turns].sort((a, b) => a.ts.localeCompare(b.ts))\n for (const t of ordered) {\n const stamp = t.ts.slice(11, 16) + \" \" + t.day\n const who = t.role === \"user\" ? (t.senderName || \"User\") : (t.senderName || \"Agent\")\n lines.push(`${stamp} ${who}: ${t.content}`)\n }\n if (recall.hasMore) {\n lines.push(`[…${recall.totalScanned - recall.turns.length}+ older turns available — call /recall with before=${recall.oldestTs} to walk further back]`)\n }\n lines.push(\"[End of long-memory recall — respond to the latest message above]\")\n longMemoryRecall = lines.join(\"\\n\")\n this.log(`[${task.agentId}] long-memory recall fired: lookback=${lmHint.lookbackDays}d, turns=${recall.turns.length}, scanned=${recall.totalScanned}`)\n }\n } catch (e: any) {\n this.log(`[${task.agentId}] long-memory recall failed (non-fatal): ${e.message}`)\n }\n }\n\n // Context strategy: \"layered\" (above) or \"planner\". Planner is a Haiku\n // pre-call that curates just the bits of history/memory/cross-chat the\n // current message needs, replacing the full-blob layered approach.\n // Per-task override (for benchmarks) wins over config default.\n // Fail-open: if the planner errors or times out, we fall through with\n // the layered values already computed above.\n // Resolution order: per-task override (benchmarks) → per-agent\n // override (def.contextStrategy) → global config default.\n const strategy: \"layered\" | \"planner\" = isCodexCli\n ? \"layered\"\n : task.contextStrategy ?? state.def.contextStrategy ?? this.config.session.contextStrategy ?? \"layered\"\n let sessionHistoryOverride: string | undefined\n let planDebug: Record<string, unknown> | undefined\n let plannerSucceeded = false\n if (strategy === \"planner\") {\n try {\n const { planContext } = await import(\"./context-planner\")\n const plan = await planContext({\n agentId: task.agentId,\n channel,\n chatId,\n message: task.message,\n sessions: this.sessions,\n memoryStore: this.memoryStore,\n })\n if (plan) {\n plannerSucceeded = true\n sessionHistoryOverride = plan.sessionHistory\n memoryContext = plan.memoryContext\n crossChatContext = plan.crossChatContext\n planDebug = plan.debug as unknown as Record<string, unknown>\n this.log(`[${task.agentId}] planner: turns=${plan.debug.recentTurns}, mem=${plan.debug.memoryIncluded ? \"yes\" : \"no\"}, xchat=${plan.debug.crossChatIncluded ? \"yes\" : \"no\"} (${plan.debug.planLatencyMs}ms) — ${plan.debug.reasoning ?? \"\"}`)\n } else {\n this.log(`[${task.agentId}] planner returned null — falling back to layered (keeping --resume for continuity)`)\n }\n } catch (e: any) {\n this.log(`[${task.agentId}] planner failed (non-fatal): ${e.message} — falling back to layered (keeping --resume for continuity)`)\n }\n // Force a fresh Claude session ONLY when the planner produced a curated\n // prompt — that's when --resume replay would conflict with the small\n // curated context. If the planner failed, keep the existing session so\n // claude's own --resume continues to provide conversation continuity.\n // Without this guard, every failed planner call dropped the session AND\n // skipped the layered sessionHistory rebuild (which was const-bound on\n // the pre-rotation resumeSessionId state above), leaving the agent\n // context-blind on every turn — the canonical \"I don't have prior\n // context\" symptom on planner-strategy agents.\n if (plannerSucceeded && resumeSessionId) {\n this.sessions.clearClaudeSessionId(task.agentId, channel, chatId)\n resumeSessionId = undefined\n }\n }\n void planDebug // reserved for the bench harness; not injected into context\n\n // Soul switching: detect /soul command and track active profile\n const soulSessionKey = `${task.agentId}:${channel}:${chatId}`\n const soulSwitch = detectSoulSwitch(task.message)\n if (soulSwitch) {\n if (soulSwitch === \"default\") {\n this.activeSouls.delete(soulSessionKey)\n this.log(`[${task.agentId}] Soul reset to default`)\n } else {\n this.activeSouls.set(soulSessionKey, soulSwitch)\n this.log(`[${task.agentId}] Soul switched to: ${soulSwitch}`)\n }\n }\n const activeSoul = this.activeSouls.get(soulSessionKey)\n\n // Load bootstrap identity files (SOUL.md or SOUL.{profile}.md).\n // These are delivered via --append-system-prompt to Claude Code so they\n // live inside the cached system prompt — we no longer append them into\n // the per-turn user-message context (which would pay cache-create on\n // every new session). The context builder still gets a flag-empty\n // bootstrapContext so the `[Identity]` / `[Personality]` block is\n // suppressed in the rendered context (Claude sees them once, cached).\n const bootstrapFiles = loadBootstrapFiles(state.def.workspace, activeSoul)\n const bootstrapContextText = buildBootstrapContext(bootstrapFiles)\n\n // Compose the cacheable system-prompt preamble. Order matters for cache\n // stability: agent.systemPrompt is the most stable (config-defined),\n // bootstrap files follow. Soul-switching mid-session produces a new\n // append-text and a new Claude cache key; that's by design — the rare\n // switch is worth a one-time cache-create cost.\n // AgentMemory — structured per-agent memory (user / feedback /\n // project / reference). Inlined into the cacheable system prompt so\n // it survives --resume and shows up on every turn. Empty when the\n // agent has no memories yet — no prompt bloat for fresh agents.\n const agentMemoryBlock = this.agentMemory.indexMarkdown(task.agentId)\n\n // Context Surgery — Fix 4: code-first operating principle for coding-tier\n // agents. Cacheable (lives in the system-prompt prefix). Empty for non-\n // coding tiers so chat/orchestrator agents are unaffected.\n const codeFirstInstruction =\n (state.def.tier === \"claude-code\" || state.def.tier === \"codex-cli\")\n ? \"[Operating principle]\\nAlways investigate the codebase before relying on issue history or comments. The code is the source of truth — start by reading relevant files (CLAUDE.md, then code), and consult conversation context only to clarify intent. Issue threads may contain wrong hypotheses; verify against the source.\"\n : \"\"\n\n // Rich-reply convention — only on interactive chat channels, and only when\n // the agent hasn't opted out. Rides the cacheable system prompt so it\n // costs nothing per turn. Kept short and conditional (\"when it genuinely\n // helps\") so agents don't spray buttons on every reply.\n const richReplyInstruction =\n (state.def.richMessages !== false && (channel === \"telegram\" || channel === \"whatsapp\"))\n ? [\n \"[Rich replies]\",\n \"On this chat channel you may add buttons, a poll, or media to a reply by appending ONE fenced block at the very end:\",\n \"```agentx:ui\",\n '{ \"buttons\": [{\"label\": \"Open docs\", \"url\": \"https://...\"}], \"poll\": {\"question\": \"Ship it?\", \"options\": [\"Yes\", \"No\"]}, \"media\": {\"type\": \"image\", \"url\": \"https://...\"} }',\n \"```\",\n \"All fields are optional; include only what helps. Buttons must be https URLs (tappable callback actions aren't supported yet). Use this sparingly — only when a link, choice, or image genuinely improves the reply. The block is stripped from the visible text.\",\n ].join(\"\\n\")\n : \"\"\n\n // Context Surgery — Fix 3: per-workspace CLAUDE.md auto-injection. Read\n // once at task-setup, cap at 4KB to bound prompt size, silent fallback so\n // a missing/unreadable file is a no-op. Lives in the cacheable system\n // prompt prefix; project-specific patterns reach the agent before any\n // task context.\n let projectClaudeMd = \"\"\n if (state.def.workspace) {\n const claudeMdPath = resolve(state.def.workspace, \"CLAUDE.md\")\n if (existsSync(claudeMdPath)) {\n try {\n const content = readFileSync(claudeMdPath, \"utf-8\")\n const trimmed = content.length > 4000\n ? content.slice(0, 4000) + \"\\n\\n[CLAUDE.md truncated to 4KB]\"\n : content\n projectClaudeMd = `[Project CLAUDE.md]\\n${trimmed}`\n } catch {\n // silent — the agent works fine without it\n }\n }\n }\n\n // Per-channel-event runbook injection — when a project rule resolves a\n // runbook path (gitlab/github adapters stamp it onto IncomingMessage),\n // pull the canonical file set (CLAUDE.md / AGENTS.md / DEPLOY.md /\n // RUNBOOK.md, overridable per-project) and append to the system prefix.\n // This is what carries the *target* project's deploy steps and server\n // mapping into the agent's context — without it the agent falls back to\n // its workspace's CLAUDE.md, which doesn't know about ./cache_clear.sh\n // or which host runs the deploy. Each file capped at 4KB; the whole\n // bundle capped at 16KB to keep the cacheable prefix bounded.\n const runbookPath = task.context?.runbookPath\n const runbookBlocks: string[] = []\n if (runbookPath && existsSync(runbookPath)) {\n const filesToTry = task.context?.runbookFiles && task.context.runbookFiles.length > 0\n ? task.context.runbookFiles\n : [\"CLAUDE.md\", \"AGENTS.md\", \"DEPLOY.md\", \"RUNBOOK.md\"]\n let totalBytes = 0\n const PER_FILE_CAP = 4000\n const TOTAL_CAP = 16000\n for (const filename of filesToTry) {\n if (totalBytes >= TOTAL_CAP) break\n const filePath = resolve(runbookPath, filename)\n if (!existsSync(filePath)) continue\n try {\n const content = readFileSync(filePath, \"utf-8\")\n const trimmed = content.length > PER_FILE_CAP\n ? content.slice(0, PER_FILE_CAP) + `\\n\\n[${filename} truncated to ${PER_FILE_CAP} bytes]`\n : content\n runbookBlocks.push(`[Project Runbook — ${filename} from ${runbookPath}]\\n${trimmed}`)\n totalBytes += trimmed.length\n } catch {\n // silent — missing readability is a no-op\n }\n }\n }\n const projectRunbook = runbookBlocks.length > 0 ? runbookBlocks.join(\"\\n\\n\") : \"\"\n\n const systemPromptAppend = [\n state.def.systemPrompt || \"\",\n codeFirstInstruction,\n richReplyInstruction,\n projectClaudeMd,\n projectRunbook,\n bootstrapContextText || \"\",\n agentMemoryBlock || \"\",\n ].filter((s) => s.trim().length > 0).join(\"\\n\\n\") || undefined\n\n const contextInput: ContextInput = {\n channel,\n chatId,\n channelScope: task.context?.group ? \"group\" : (channel === \"gitlab\" ? \"project\" : \"personal\"),\n groupName: task.context?.group,\n agentId: task.agentId,\n agentName: state.def.name,\n agentHandle: this.getChannelHandle(task.agentId, channel),\n systemPrompt: state.def.systemPrompt,\n sender: senderName,\n senderId: task.context?.senderId,\n senderUsername: task.context?.senderUsername,\n // Context Surgery — Fix 2: skip landscape for coding-tier agents in\n // github/gitlab contexts. The mesh-peer summary (~1-2K tokens of cross-\n // team agents) is irrelevant for focused code work on one issue/PR and\n // anchors the agent on context it shouldn't be using. Chat/orchestrator\n // agents still get the landscape — that's where peer discovery matters.\n landscape: (isCodexCli || isCodingChannelContext(channel, state.def.tier))\n ? undefined\n : this.landscape?.getForAgent(task.agentId),\n channelMeta: task.context?.channelMeta,\n mediaPath: task.context?.mediaPath,\n mediaType: task.context?.mediaType,\n replyToText: task.context?.replyToText,\n // bootstrapContext intentionally omitted — delivered via system prompt.\n patternContext: isCodexCli ? undefined : patternContext || undefined,\n procedureContext,\n references: referencesBlock,\n skillInjection: skillInjection || undefined,\n groupHistory: task.context?.group ? undefined : undefined, // group log is injected by router\n // Planner override takes precedence when set — falls back to the\n // layered `sessionHistory` (full buildHistoryContext, scoped by\n // resumeSessionId presence) otherwise.\n sessionHistory: sessionHistoryOverride ?? sessionHistory,\n memoryContext: memoryContext || undefined,\n crossChatContext: crossChatContext || undefined,\n longMemoryRecall: longMemoryRecall || undefined,\n wikiContext: isCodexCli ? undefined : wikiContext,\n handoverNote: this.buildHandoverNote(task.agentId, channel, chatId),\n rotationMemo,\n intent: intent\n ? {\n path: intent.path,\n pathLabel: intent.pathLabel,\n pathId: intent.pathId,\n axes: intent.axes,\n leaf: intent.leaf,\n status: intent.status,\n }\n : undefined,\n message: task.message,\n }\n\n const historyContext = buildAgentContext(\n contextInput,\n isCodexCli\n ? {\n totalBudget: 1800,\n layerBudgets: {\n channel: 180,\n scope: 100,\n identity: 120,\n references: 250,\n intent: 80,\n artifacts: 250,\n memory: 250,\n history: 450,\n \"cross-chat\": 200,\n \"long-memory\": 350,\n handover: 120,\n },\n }\n : undefined,\n )\n\n // Context-size telemetry. Bytes we control: the layered context string +\n // the cacheable system-prompt append + the current message. Bytes we\n // don't: the Claude CLI --resume replay (tool results from prior turns),\n // which shows up as cacheReadTokens in response.usage. Log a warning\n // when our controllable assembly is unusually large (>16K chars ≈ 4K\n // tokens) — anything that big is usually a runaway layer worth\n // investigating before it snowballs via --resume.\n const agentxContextBytes =\n (historyContext?.length ?? 0) +\n (systemPromptAppend?.length ?? 0) +\n (task.message?.length ?? 0)\n const sizeParts = {\n history: historyContext?.length ?? 0,\n sysPrompt: systemPromptAppend?.length ?? 0,\n message: task.message?.length ?? 0,\n }\n // Record every dispatch for drift detection, warn when growing.\n const sizeKey = promptSizeKey(task.agentId, channel, chatId)\n recordPromptSize(sizeKey, agentxContextBytes, sizeParts)\n const driftWarning = warnIfPromptGrowing(sizeKey)\n if (driftWarning) this.log(`[${task.agentId}] ${driftWarning}`)\n if (agentxContextBytes > 16_000) {\n this.log(`[${task.agentId}] large context for ${channel}:${chatId}: ${agentxContextBytes} bytes (history=${sizeParts.history}, sysPrompt=${sizeParts.sysPrompt}, message=${sizeParts.message})`)\n }\n\n // Attach the cacheable preamble onto the task so runtime.ts can forward\n // it to Claude CLI's --append-system-prompt arg.\n const taskWithSystemPrompt: AgentTask = { ...task, systemPromptAppend }\n\n let finalResponse: AgentResponse | undefined\n try {\n // Workflow auto-run short-circuit. When the matcher upstream picked a\n // workflow at >= autoRunThreshold AND `workflows.matching.mode == \"auto\"`,\n // fire that workflow now and return. The workflow owns posting any\n // reply via its action.send / agent nodes; we return an empty content\n // with a metadata marker so the caller knows not to send a redundant\n // agent reply. On runner failure, we log and fall through to normal\n // agent execution — auto-run is best-effort, never a footgun.\n if (pendingAutoRun && this.workflowAutoRunner) {\n try {\n const result = await this.workflowAutoRunner({\n workflowId: pendingAutoRun.workflowId,\n agentId: task.agentId,\n channel,\n chatId,\n message: task.message,\n payload: {\n message: task.message,\n agentId: task.agentId,\n channel,\n chatId,\n senderId: task.context?.senderId,\n senderUsername: task.context?.senderUsername,\n intentPath: intent?.path,\n matchedWorkflowId: pendingAutoRun.workflowId,\n matchConfidence: pendingAutoRun.confidence,\n },\n })\n this.log(\n `[${task.agentId}] workflow auto-run started ${pendingAutoRun.workflowId} run=${result.runId || \"?\"}; ` +\n `agent execution skipped — workflow owns the reply`,\n )\n finalResponse = {\n content: \"\",\n duration: 0,\n metadata: {\n handledByWorkflow: pendingAutoRun.workflowId,\n workflowRunId: result.runId,\n workflowMatchConfidence: pendingAutoRun.confidence,\n },\n } as AgentResponse\n return finalResponse\n } catch (e: any) {\n this.log(`[${task.agentId}] workflow auto-run failed; falling back to agent: ${e?.message || e}`)\n }\n }\n\n // Pre-flight gates (claude-code tier only). Two separate heuristics that\n // short-circuit doomed cold dispatches BEFORE we burn a Claude CLI\n // subprocess to reproduce the same failure:\n // (1) Overage gate — when Anthropic has disabled Max-plan extra usage\n // at the org level. A cold dispatch's fresh cache-create spills\n // past the regular allotment and gets rejected.\n // (2) Quota gate — when our own dispatch-budget counters say the\n // fleet has burned through the hourly or 5-hour cap. Warm\n // sessions still pass; cold dispatches are deferred.\n // Warm sessions (resumeSessionId set) bypass both gates — prompt-cache\n // replay keeps them inside the regular allotment.\n //\n // Kept inside the try so the `finally` below still runs — otherwise a\n // short-circuit return leaks runningTask bookkeeping.\n if (state.def.tier === \"claude-code\") {\n // A persistent-process handle counts as \"warm\" — its subprocess already\n // has the system prompt cached, so no fresh cache-create is needed even\n // when there's no --resume session ID stored for this chatId yet.\n const procReg = state.def.persistentProcess ? getProcessRegistry() : null\n const hasLiveProcess = procReg != null\n && procReg.hasLive({ agentId: task.agentId, channel, chatId })\n const hasWarmSession = Boolean(resumeSessionId) || hasLiveProcess\n const gates = [\n preflightOverageGate(hasWarmSession),\n preflightQuotaGate(hasWarmSession),\n ]\n const abort = gates.find((g) => g && g.abort)\n if (abort) {\n state.errors++\n this.log(`[${task.agentId}] skipping cold dispatch — ${abort.reason}`)\n const preflightResponse: AgentResponse = {\n content: \"\",\n error: abort.message,\n duration: 0,\n }\n if (!onDelta) {\n output.buffer = `[error] ${abort.message}`\n for (const sub of output.subscribers) {\n try { sub(output.buffer) } catch { /* */ }\n }\n }\n finalResponse = preflightResponse\n return preflightResponse\n }\n // Past the gates — commit the dispatch to the rolling budget and log\n // a warning if we've crossed warnRatio so the operator sees pressure\n // building before it turns into rejections.\n recordClaudeCodeDispatch()\n const warning = warnIfNearingCap()\n if (warning) this.log(`[${task.agentId}] ${warning}`)\n }\n\n // Tiers other than claude-code don't emit stream-json events into\n // onEvent — they only call onDelta with text chunks. Without\n // wiring those chunks into the modal buffer, /live's task modal\n // came up blank for orchestrator-tier (noqta-public) and\n // codex-cli runs even when text was actively streaming back to\n // the caller. Wrap onDelta only for non-claude-code tiers; for\n // claude-code the formatter (onEvent → pushToBuffer) is already\n // the authoritative source — wrapping there would double-print\n // every text chunk in the modal.\n const tierUsesStreamJson = state.def.tier === \"claude-code\"\n const wrappedOnDelta: StreamCallback | undefined = tierUsesStreamJson\n ? onDelta\n : (text, fullText) => {\n if (text) pushToBuffer(text)\n if (onDelta) onDelta(text, fullText)\n }\n // Thinking chunks land on a side channel: we forward to the\n // caller's onThinking (chat endpoint surfaces them as\n // delta.reasoning_content on the SSE wire) AND push to the\n // modal buffer with a `💭 ` prefix only on the first chunk so\n // the dashboard's line-based renderer treats the whole run of\n // reasoning as a single thought event instead of one per token.\n let thinkingOpen = false\n const wrappedOnThinking: ThinkingCallback | undefined = tierUsesStreamJson\n ? onThinking\n : (text) => {\n if (text) {\n if (!thinkingOpen) {\n pushToBuffer(`💭 ${text}`)\n thinkingOpen = true\n } else {\n pushToBuffer(text)\n }\n }\n if (onThinking) onThinking(text)\n }\n // When the agent transitions back to text, close any open\n // thought block in the modal so the next text chunk doesn't\n // get folded into the same line.\n const closeThinkingIfOpen = () => {\n if (thinkingOpen) { pushToBuffer(\"\\n\"); thinkingOpen = false }\n }\n const wrappedOnDeltaWithThinkingClose: StreamCallback | undefined = tierUsesStreamJson\n ? wrappedOnDelta\n : wrappedOnDelta\n ? (text, fullText) => { closeThinkingIfOpen(); wrappedOnDelta(text, fullText) }\n : undefined\n const response = await executeTask(state.def, taskWithSystemPrompt, this.providers, wrappedOnDeltaWithThinkingClose, historyContext, resumeSessionId, onEvent, abortController.signal, wrappedOnThinking)\n\n // Improvement plan #3 — fail with a typed error when the agent\n // declared toolUseRequired and the model didn't invoke at least\n // one of the listed tools. Only fires when the response itself\n // wasn't already an error (an outer error wins). The error\n // message uses the canonical `tool_required_not_called: <name>`\n // shape so retry / fallback logic can pattern-match on it.\n if (!response.error) {\n const missing = firstMissingRequiredTool(state.def.toolUseRequired, toolUsesByName)\n if (missing) {\n const invokedSummary = Array.from(toolUsesByName.entries())\n .map(([n, c]) => `${n}=${c}`)\n .join(\", \") || \"none\"\n response.error = `tool_required_not_called: ${missing} (invoked: ${invokedSummary})`\n response.content = \"\"\n this.log(`[${task.agentId}] tool-use contract violation — required ${missing}, observed ${invokedSummary}`)\n }\n }\n\n finalResponse = response\n\n // Split this request's tokens into tier1/tier2 buckets so subscribers\n // (sqlite usage_daily, audit, dashboard) record both lanes. The same\n // threshold that TokenTracker.record() applies — extracted as a\n // helper so the two sites can never drift.\n const split = response.usage ? splitTaskUsageByTier(response.usage) : undefined\n getEventBus().emit(\"task:completed\", {\n taskId: traceTaskId,\n agentId: task.agentId,\n channel,\n chatId,\n durationMs: Date.now() - taskStartedAt,\n error: response.error || undefined,\n inputTokens: split?.inputTokens,\n outputTokens: split?.outputTokens,\n cacheReadTokens: split?.cacheReadTokens,\n cacheCreateTokens: split?.cacheCreateTokens,\n tier2InputTokens: split?.tier2InputTokens,\n tier2OutputTokens: split?.tier2OutputTokens,\n tier2CacheReadTokens: split?.tier2CacheReadTokens,\n tier2CacheCreateTokens: split?.tier2CacheCreateTokens,\n finalResponse: response.content || undefined,\n // Per-task model attribution: prefer what the runtime actually\n // billed; fall back to the agent's configured model so codex-cli\n // / sdk agents don't get stamped with the daemon-wide opus default.\n billedModel: response.billedModel || state.def.model || undefined,\n at: new Date().toISOString(),\n })\n\n // For non-streaming runs (no caller onDelta) we never captured incremental\n // chunks — surface the final response in one shot so the dashboard modal\n // shows something meaningful when subscribers are attached.\n if (!onDelta && (response.content || response.error)) {\n const finalText = response.error ? `[error] ${response.error}` : response.content\n output.buffer = finalText\n for (const sub of output.subscribers) {\n try { sub(finalText) } catch { /* */ }\n }\n }\n\n if (response.error) {\n state.errors++\n this.log(`[${task.agentId}] error: ${response.error}`)\n } else {\n // Record agent response in session\n this.sessions.addAgentMessage(task.agentId, channel, chatId, response.content)\n\n // Store native CLI session IDs for future resume.\n if (response.claudeSessionId) {\n this.sessions.setClaudeSessionId(task.agentId, channel, chatId, response.claudeSessionId)\n }\n if (response.codexSessionId) {\n this.sessions.setCodexSessionId(task.agentId, channel, chatId, response.codexSessionId)\n }\n if (response.opencodeSessionId) {\n this.sessions.setOpenCodeSessionId(task.agentId, channel, chatId, response.opencodeSessionId)\n }\n\n // Record this turn's usage so next task can decide whether to rotate:\n // turnCount + cumulative lastTurnInputTokens (observability) +\n // per-request lastTurnContextTokens (the actual rotation metric).\n // Only meaningful when we kept a claude session — skip otherwise so the\n // counter isn't incremented for tiers that don't use --resume.\n if ((response.claudeSessionId || response.codexSessionId || response.opencodeSessionId) && response.usage) {\n this.sessions.recordTurnUsage(task.agentId, channel, chatId, response.usage, response.contextTokens)\n }\n\n // Tier-2 warning: Claude bills the 1.5× long-context rate when a\n // single REQUEST's input crosses 200K — so judge by the end-of-turn\n // per-request context size, not the cumulative turn total (which\n // sums cache reads across every call and reads 10-20× too high).\n // Next turn will auto-rotate (see shouldRotateByTierTwo); logging\n // THIS turn keeps it observable.\n if (response.usage) {\n const contextSize = response.contextTokens\n const cumulative =\n (response.usage.inputTokens || 0) +\n (response.usage.cacheReadTokens || 0) +\n (response.usage.cacheCreateTokens || 0)\n if ((contextSize ?? cumulative) >= this.sessions.getTierTwoThresholdTokens()) {\n this.log(`[${task.agentId}] TIER-2 HIT on ${channel}:${chatId}: context=${contextSize ?? \"n/a\"} tokens (cumulative turn total=${cumulative}) — next turn will rotate`)\n }\n }\n\n // Wiki: export conversation as raw entry for later absorption\n if (response.content.length > 50) {\n try {\n const entryId = `${task.agentId}-${Date.now().toString(36)}`\n this.wikiHub.getSharedStore().addEntry({\n id: entryId,\n date: new Date().toISOString().slice(0, 10),\n agentId: task.agentId,\n source: channel,\n sourceContext: task.context?.group || task.context?.sender,\n content: `User: ${task.message}\\n\\nAgent: ${response.content}`,\n // Stamp the classifier's path on the entry so `wiki absorb` can\n // propagate it to the article's `graphPath` without\n // reconstructing fingerprints from transformed content. Without\n // this, articles never carry graphPath and the wiki retrieval\n // scorer multiplies the graph weight (default 0.6) by 0.\n meta: intent?.path?.length\n ? { intentPath: intent.path, intentPathLabel: intent.pathLabel }\n : undefined,\n })\n } catch {\n // Wiki export is best-effort\n }\n\n // Memory: extract and store memorable facts via Haiku (fire-and-forget)\n extractMemories(\n task.agentId,\n task.message,\n response.content,\n { channel, chatId, sender: senderName },\n this.memoryStore,\n ).catch(() => {})\n\n // Patterns: extract behavioral patterns (fire-and-forget)\n extractPatterns(\n task.agentId,\n task.message,\n response.content,\n { channel, date: new Date().toISOString().slice(0, 10) },\n this.patternStore,\n ).catch(() => {})\n }\n\n // Track token usage (real counts if available, estimate otherwise).\n // Model priority for pricing: what the API ACTUALLY billed (from the\n // CLI init / result event) > per-task override (cron model) > agent\n // config default. Without this, cron-overridden runs get priced at\n // the wrong rate and cache-aware cost reports mislead operators.\n const billedModel = response.billedModel || task.model || state.def.model\n const tChannel = task.context?.channel\n // Session key = \"channel:chatId\" — opaque to the tracker, just needs\n // to be unique per (conversation, day). Lets us derive avg-tasks/\n // session later so retry-heavy traffic becomes visible.\n const tSessionKey = tChannel && (task.context?.chatId || task.context?.group || task.context?.sender)\n ? `${tChannel}:${task.context?.chatId || task.context?.group || task.context?.sender}`\n : undefined\n this.tokenTracker.record(\n task.agentId,\n response.duration || 0,\n response.usage,\n task.message.length,\n response.content.length,\n false,\n billedModel,\n tChannel,\n tSessionKey,\n )\n\n this.log(\n `[${task.agentId}] completed in ${response.duration}ms` +\n (response.tokensUsed ? ` (${response.tokensUsed} tokens)` : \"\"),\n )\n }\n\n return response\n } catch (error: any) {\n state.errors++\n this.log(`[${task.agentId}] unexpected error: ${error.message}`)\n const friendly = friendlyModelError(error.message)\n finalResponse = {\n content: \"\",\n error: renderFriendlyError(friendly),\n errorKind: friendly.kind,\n }\n return finalResponse\n } finally {\n state.activeTasks--\n // Remove this run from the running-tasks list.\n const idx = state.runningTasks.findIndex((r) => r.id === runningTask.id)\n if (idx !== -1) state.runningTasks.splice(idx, 1)\n // Drop the abort entry — the controller is unreachable after this point\n // and a future cancel for the same id should 404, not silently no-op.\n this.taskAborts.delete(runningTask.id)\n\n // Notify any open dashboard streams that this task has finished, then\n // schedule the buffer for cleanup so memory doesn't grow unbounded.\n output.done = true\n output.endedAt = new Date()\n for (const sub of output.subscribers) {\n try { sub(\"\\n[task finished]\\n\") } catch { /* ignore */ }\n }\n output.subscribers.clear()\n setTimeout(() => this.taskOutputs.delete(runningTask.id), TASK_OUTPUT_TTL_MS).unref?.()\n\n // Persist a TaskRecord for the dashboard's \"Recent activities\" panel.\n // Best-effort — disk failures must not affect task semantics.\n try {\n const endedAt = output.endedAt\n const record: TaskRecord = {\n id: runningTask.id,\n agentId: task.agentId,\n channel: runningTask.channel,\n chatId: runningTask.chatId,\n sender: runningTask.sender,\n message: task.message || \"\",\n startedAt: runningTask.startedAt.toISOString(),\n endedAt: endedAt.toISOString(),\n durationMs: endedAt.getTime() - runningTask.startedAt.getTime(),\n ok: !finalResponse?.error,\n error: finalResponse?.error,\n responseText: finalResponse?.content || \"\",\n transcript: output.buffer,\n }\n this.persistTaskRecord(record)\n // Stash a one-line summary so the dashboard card can show \"what the\n // agent did last\" without reading from disk on every snapshot.\n const rawSummary = (record.responseText || record.error || \"\").trim()\n if (rawSummary) {\n const firstLine = rawSummary.split(/\\r?\\n/)[0].slice(0, 140)\n this.lastSummaries.set(task.agentId, { text: firstLine, at: endedAt, ok: record.ok })\n }\n } catch (e: any) {\n this.log(`[${task.agentId}] task history persist failed: ${e?.message}`)\n }\n\n // Flush queued messages that arrived while this run was in progress\n this.messageQueue.markDone(task.agentId, qChannel, qChatId)\n .then((queued) => {\n if (queued.length === 0) return\n this.log(`[${task.agentId}] flushing ${queued.length} queued message(s)`)\n // Re-execute each queued message as a new task. The original\n // inbound message (the one that triggered this run) was posted\n // back to its channel by the router after `registry.execute`\n // returned. Queued messages, however, are re-dispatched here\n // directly — bypassing the router — so we must explicitly post\n // the response back to the channel ourselves, or the user never\n // sees the reply. (Real symptom: operator hits Update on a\n // running task, the follow-up runs and produces a reply, but\n // nothing arrives in Telegram.)\n for (const qm of queued) {\n const ctx = (qm.originalContext as AgentTask[\"context\"]) || {\n channel: qm.channel,\n sender: qm.sender,\n chatId: qm.chatId,\n }\n this.execute({\n message: qm.text,\n agentId: task.agentId,\n context: ctx,\n })\n .then((resp) => this.postQueuedResponseToChannel(task.agentId, ctx, resp))\n .catch((e) => {\n this.log(`[${task.agentId}] queued message failed: ${e.message}`)\n })\n }\n })\n .catch((e) => {\n this.log(`[${task.agentId}] queue flush failed: ${e.message}`)\n })\n }\n }\n\n /**\n * List all agents and their status.\n */\n /** Total active task count across all agents — used by the daemon's\n * graceful shutdown to drain in-flight runs before exit. */\n getActiveTaskCount(): number {\n let total = 0\n for (const s of this.agents.values()) total += s.activeTasks\n return total\n }\n\n list(): Array<{\n id: string\n name: string\n tier: string\n model?: string\n workspace: string\n active: number\n total: number\n errors: number\n lastActive?: Date\n runningTasks: RunningTask[]\n lastSummary?: { text: string; at: string; ok: boolean }\n hourlyTasks?: number[]\n }> {\n return Array.from(this.agents.values()).map((s) => {\n const summary = this.lastSummaries.get(s.id)\n return {\n id: s.id,\n name: s.def.name,\n tier: s.def.tier,\n model: s.def.model,\n workspace: s.def.workspace,\n active: s.activeTasks,\n total: s.totalTasks,\n errors: s.errors,\n lastActive: s.lastActive,\n runningTasks: s.runningTasks,\n lastSummary: summary ? { text: summary.text, at: summary.at.toISOString(), ok: summary.ok } : undefined,\n hourlyTasks: this.getHourlySparkline(s.id, 24),\n }\n })\n }\n\n /**\n * Write a finished task record to disk under .agentx/task-history/<agent>/<yyyy-mm-dd>/.\n * Side-effects only — call sites should treat failure as best-effort.\n */\n private persistTaskRecord(record: TaskRecord): void {\n const day = record.endedAt.slice(0, 10)\n const dir = resolve(process.cwd(), TASK_HISTORY_DIR, this.safe(record.agentId), day)\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true })\n const file = resolve(dir, `${this.safe(record.id)}.json`)\n writeFileSync(file, JSON.stringify(record, null, 2), \"utf-8\")\n }\n\n /**\n * Compute a 24-hour hourly task-count sparkline for an agent from persisted\n * history. Returns an array of 24 numbers: buckets[0] = 23-24h ago,\n * buckets[23] = now-hour. Cached per-agent for 60s to keep snapshot load\n * reasonable when the dashboard polls every 2s.\n */\n getHourlySparkline(agentId: string, hours: number = 24): number[] {\n const now = Date.now()\n const cached = this.sparklineCache.get(agentId)\n if (cached && now - cached.at < 60_000 && cached.hourly.length === hours) return cached.hourly\n\n const buckets = new Array<number>(hours).fill(0)\n const windowStart = now - hours * 3600_000\n const root = resolve(process.cwd(), TASK_HISTORY_DIR, this.safe(agentId))\n if (!existsSync(root)) {\n this.sparklineCache.set(agentId, { hourly: buckets, at: now })\n return buckets\n }\n // Only need the last 2 day folders — crossing midnight still fits.\n const today = new Date(now).toISOString().slice(0, 10)\n const yesterday = new Date(now - 86400_000).toISOString().slice(0, 10)\n const dayToday = new Date(now); dayToday.setUTCHours(0, 0, 0, 0)\n for (const day of [yesterday, today]) {\n const dayDir = resolve(root, day)\n if (!existsSync(dayDir)) continue\n try {\n for (const f of readdirSync(dayDir)) {\n if (!f.endsWith(\".json\")) continue\n // Task id is timestamp-prefixed (e.g. 1776447897061-xxx.json) — pull\n // the millis without parsing each file.\n const tsStr = f.split(\"-\")[0]\n const ts = parseInt(tsStr, 10)\n if (!Number.isFinite(ts) || ts < windowStart || ts > now) continue\n const offsetHours = Math.floor((now - ts) / 3600_000)\n const idx = hours - 1 - offsetHours\n if (idx >= 0 && idx < hours) buckets[idx]++\n }\n } catch { /* skip unreadable */ }\n }\n this.sparklineCache.set(agentId, { hourly: buckets, at: now })\n return buckets\n }\n\n /**\n * Rebuild the in-memory lastSummaries cache from disk so dashboard cards\n * show the most recent \"what did they do\" line even right after a restart.\n * Called once on daemon startup — one file read per agent.\n */\n hydrateLastSummariesFromDisk(): number {\n const root = resolve(process.cwd(), TASK_HISTORY_DIR)\n if (!existsSync(root)) return 0\n let loaded = 0\n for (const agentDir of readdirSync(root)) {\n const agentPath = resolve(root, agentDir)\n let days: string[]\n try { days = readdirSync(agentPath).filter((d) => /^\\d{4}-\\d{2}-\\d{2}$/.test(d)).sort().reverse() } catch { continue }\n outer: for (const day of days) {\n const dayDir = resolve(agentPath, day)\n let files: string[]\n try { files = readdirSync(dayDir).filter((f) => f.endsWith(\".json\")).sort().reverse() } catch { continue }\n for (const f of files) {\n try {\n const rec = JSON.parse(readFileSync(resolve(dayDir, f), \"utf-8\")) as TaskRecord\n const raw = (rec.responseText || rec.error || \"\").trim()\n if (!raw) continue\n const firstLine = raw.split(/\\r?\\n/)[0].slice(0, 140)\n this.lastSummaries.set(rec.agentId, { text: firstLine, at: new Date(rec.endedAt), ok: rec.ok })\n loaded++\n break outer\n } catch { /* skip corrupt file */ }\n }\n }\n }\n return loaded\n }\n\n /**\n * Drop history folders older than retention window. Cheap startup-time\n * sweep — once a day is plenty, but doing it on every daemon boot is fine.\n */\n pruneTaskHistory(retentionDays = TASK_HISTORY_RETENTION_DAYS): number {\n const root = resolve(process.cwd(), TASK_HISTORY_DIR)\n if (!existsSync(root)) return 0\n const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000\n let removed = 0\n for (const agentDir of readdirSync(root)) {\n const agentPath = resolve(root, agentDir)\n let dayEntries: string[]\n try { dayEntries = readdirSync(agentPath) } catch { continue }\n for (const day of dayEntries) {\n const ts = Date.parse(day + \"T00:00:00Z\")\n if (Number.isNaN(ts) || ts >= cutoff) continue\n try { rmSync(resolve(agentPath, day), { recursive: true, force: true }); removed++ } catch { /* */ }\n }\n }\n return removed\n }\n\n /**\n * Newest-first list of task records for one agent, capped at `limit`.\n * Walks at most the last few day folders so this is O(limit), not O(history).\n */\n listTaskHistory(agentId: string, limit = 50): Array<Omit<TaskRecord, \"transcript\" | \"responseText\">> {\n const root = resolve(process.cwd(), TASK_HISTORY_DIR, this.safe(agentId))\n if (!existsSync(root)) return []\n const days = readdirSync(root).filter((d) => /^\\d{4}-\\d{2}-\\d{2}$/.test(d)).sort().reverse()\n const out: Array<Omit<TaskRecord, \"transcript\" | \"responseText\">> = []\n for (const day of days) {\n if (out.length >= limit) break\n const dayDir = resolve(root, day)\n let files: string[]\n try { files = readdirSync(dayDir).filter((f) => f.endsWith(\".json\")) } catch { continue }\n // Sort by file mtime desc — task ids are timestamp-prefixed so lexicographic also works.\n files.sort().reverse()\n for (const f of files) {\n if (out.length >= limit) break\n try {\n const rec = JSON.parse(readFileSync(resolve(dayDir, f), \"utf-8\")) as TaskRecord\n const { transcript: _t, responseText: _r, ...summary } = rec\n out.push(summary)\n } catch { /* skip corrupt */ }\n }\n }\n return out\n }\n\n /**\n * Look up one task's full record (transcript included) across the retention window.\n */\n getTaskRecord(agentId: string, taskId: string): TaskRecord | null {\n const root = resolve(process.cwd(), TASK_HISTORY_DIR, this.safe(agentId))\n if (!existsSync(root)) return null\n const safeId = this.safe(taskId)\n const days = readdirSync(root).filter((d) => /^\\d{4}-\\d{2}-\\d{2}$/.test(d)).sort().reverse()\n for (const day of days) {\n const file = resolve(root, day, `${safeId}.json`)\n if (existsSync(file)) {\n try { return JSON.parse(readFileSync(file, \"utf-8\")) as TaskRecord } catch { return null }\n }\n }\n return null\n }\n\n private safe(s: string): string {\n return s.replace(/[^a-zA-Z0-9_.:-]/g, \"_\")\n }\n\n /**\n * Post a queued message's response back to its originating channel.\n * Used by the flush block in `execute()` — channel messages that arrive\n * while the agent is busy are queued and re-dispatched here, bypassing\n * the router; without this helper the agent's reply would never reach\n * the user. Skipped silently for internal channels (`test`, `api`,\n * `a2a`, `cron`, `workflow`, …) where no adapter is registered.\n */\n private postQueuedResponseToChannel(\n agentId: string,\n ctx: AgentTask[\"context\"],\n resp: AgentResponse,\n ): void {\n const router = getMessageRouter()\n if (!router) return\n const channel = ctx?.channel\n const chatId = ctx?.chatId\n if (!channel || !chatId) return\n // Operator-cancelled / queued-marker — nothing to deliver\n if (resp.errorKind === \"cancelled\") return\n if (resp.error?.startsWith(\"__queued__\")) return\n const text = resp.error\n ? `Error: ${resp.error}`\n : (resp.content || \"\").trim()\n if (!text) return\n // Resolve account binding for shared-channel adapters (Telegram is\n // multi-account); sendOutbound auto-fills accountId from agentId.\n router.sendOutbound({ channel, chatId, text, agentId } as any)\n .catch((e: any) => this.log(`[${agentId}] queued response post failed: ${e?.message ?? e}`))\n }\n\n /**\n * Operator stop. Aborts the in-flight run for `taskId` (kills the\n * underlying claude subprocess via the AbortSignal threaded through\n * runtime.ts). Returns the task's identity for HTTP / audit callers,\n * or null when no live run matches.\n */\n cancelRunningTask(taskId: string, reason = \"operator\"): { agentId: string; channel: string; chatId: string } | null {\n const entry = this.taskAborts.get(taskId)\n if (!entry) return null\n try {\n entry.controller.abort(new Error(reason))\n } catch { /* AbortController.abort never throws on modern Node, but defend */ }\n this.log(`[${entry.agentId}] task ${taskId} cancelled — ${reason}`)\n return { agentId: entry.agentId, channel: entry.channel, chatId: entry.chatId }\n }\n\n /**\n * Queue a follow-up correction/update for an in-flight task. The message\n * lands in the per-session MessageQueue and is dispatched after the\n * current run finishes — same path channel messages take while the agent\n * is busy. With `replace=true` we cancel the current run first so the\n * follow-up runs immediately.\n *\n * Returns the resolved identity + queue position, or null when no live\n * run matches taskId.\n */\n queueFollowUp(\n taskId: string,\n message: string,\n sender: string,\n opts: { replace?: boolean; reason?: string } = {},\n ): { agentId: string; channel: string; chatId: string; replaced: boolean; edited: boolean; pending: number } | null {\n const entry = this.taskAborts.get(taskId)\n if (!entry) return null\n const { agentId, channel, chatId, originalMessage } = entry\n\n // Inject model: Update appends a message to the same chat session;\n // the running task keeps going and the new message dispatches at the\n // first chance — same as sending a message to an ongoing Claude\n // session. Update never *initiates* a cancel (use Stop for that).\n //\n // BUT — if the controller is already aborted (operator pressed Stop\n // just before Update), the cancelled user turn is sitting in session\n // history with no agent reply. Leaving it there makes the Update\n // read as a bare orphan ask (\"87\" after a stranded \"count up to 75\"\n // → agent had no idea what \"87\" meant). So when we detect the\n // aborted state, we treat the Update as editing the cancelled\n // message in place: pop the orphan from session history before\n // enqueueing the new message.\n const isStopped = entry.controller.signal.aborted\n let edited = false\n if (isStopped && originalMessage) {\n edited = this.sessions.removeLastUserMessageIfMatches(agentId, channel, chatId, originalMessage)\n }\n\n this.messageQueue.enqueue(agentId, channel, chatId, {\n text: message,\n sender,\n timestamp: Date.now(),\n channel,\n chatId,\n originalContext: { channel, chatId, sender },\n })\n const pending = this.messageQueue.pendingCount(agentId, channel, chatId)\n\n let replaced = false\n if (opts.replace) {\n const reason = opts.reason || `replaced by follow-up from ${sender}`\n try { entry.controller.abort(new Error(reason)) } catch { /* */ }\n this.log(`[${agentId}] task ${taskId} replaced by follow-up — ${reason}`)\n replaced = true\n } else {\n this.log(`[${agentId}] follow-up queued for task ${taskId} (pending=${pending}${edited ? \", edited cancelled turn\" : \"\"})`)\n }\n return { agentId, channel, chatId, replaced, edited, pending }\n }\n\n /**\n * Subscribe to live output for a running task. Returns the buffer that has\n * accumulated so far plus an unsubscribe handle. If the task is unknown,\n * returns null. If the task already finished, the subscriber is given the\n * tail buffer and immediately ended.\n */\n subscribeToTaskOutput(taskId: string, sub: TaskOutputSubscriber): { initial: string; done: boolean; unsubscribe: () => void } | null {\n const out = this.taskOutputs.get(taskId)\n if (!out) return null\n if (out.done) return { initial: out.buffer, done: true, unsubscribe: () => {} }\n out.subscribers.add(sub)\n return {\n initial: out.buffer,\n done: false,\n unsubscribe: () => { out.subscribers.delete(sub) },\n }\n }\n\n /**\n * Get token usage summary.\n */\n getUsage(days: number = 7) {\n return this.tokenTracker.summary(days)\n }\n\n /**\n * Get today's usage.\n */\n getTodayUsage() {\n return this.tokenTracker.today()\n }\n\n /**\n * Get the token tracker instance (for daemon hooks).\n */\n getTokenTracker(): TokenTracker {\n return this.tokenTracker\n }\n}\n","import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync } from \"fs\"\nimport { resolve, relative } from \"path\"\nimport { procedureMetaSchema, type Procedure, type ProcedureMeta } from \"./types\"\n\n// --- ProcedureStore ---\n// Plain-file persistence (same convention as .agentx/wiki/ and .agentx/graph/).\n// One markdown file per procedure. Frontmatter = yaml-ish name:value lines\n// (arrays are JSON). This intentionally avoids pulling in a yaml lib — the\n// surface is small and the wiki store already uses the same hand-parser.\n\nexport interface ProcedureStoreOptions {\n baseDir?: string\n}\n\nfunction parseFrontmatter(raw: string): { meta: Record<string, any>; body: string } {\n const m = raw.match(/^---\\n([\\s\\S]*?)\\n---\\n?([\\s\\S]*)$/)\n if (!m) return { meta: {}, body: raw }\n const meta: Record<string, any> = {}\n for (const line of m[1].split(\"\\n\")) {\n const kv = line.match(/^([a-zA-Z_][a-zA-Z0-9_]*):\\s*(.*)$/)\n if (!kv) continue\n const key = kv[1]\n let val = kv[2].trim()\n if (val.startsWith(\"[\") && val.endsWith(\"]\")) {\n try { meta[key] = JSON.parse(val); continue } catch { /* fallthrough */ }\n meta[key] = val.slice(1, -1).split(\",\").map(s => s.trim().replace(/^\"(.*)\"$/, \"$1\")).filter(Boolean)\n continue\n }\n if (val.startsWith('\"') && val.endsWith('\"')) val = val.slice(1, -1)\n meta[key] = val\n }\n return { meta, body: m[2] }\n}\n\nfunction serializeFrontmatter(meta: ProcedureMeta): string {\n const lines: string[] = [\"---\"]\n lines.push(`id: ${meta.id}`)\n lines.push(`title: \"${meta.title.replace(/\"/g, '\\\\\"')}\"`)\n lines.push(`trigger: \"${meta.trigger.replace(/\"/g, '\\\\\"')}\"`)\n if (meta.inputs?.length) lines.push(`inputs: ${JSON.stringify(meta.inputs)}`)\n if (meta.expected) lines.push(`expected: \"${meta.expected.replace(/\"/g, '\\\\\"')}\"`)\n if (meta.kpis?.length) lines.push(`kpis: ${JSON.stringify(meta.kpis)}`)\n if (meta.owner) lines.push(`owner: ${meta.owner}`)\n if (meta.tags?.length) lines.push(`tags: ${JSON.stringify(meta.tags)}`)\n lines.push(`status: ${meta.status}`)\n if (meta.source) lines.push(`source: \"${meta.source.replace(/\"/g, '\\\\\"')}\"`)\n if (meta.evidence?.length) lines.push(`evidence: ${JSON.stringify(meta.evidence)}`)\n if (meta.related?.length) lines.push(`related: ${JSON.stringify(meta.related)}`)\n if (meta.created) lines.push(`created: ${meta.created}`)\n if (meta.updated) lines.push(`updated: ${meta.updated}`)\n lines.push(\"---\")\n return lines.join(\"\\n\")\n}\n\nexport class ProcedureStore {\n readonly baseDir: string\n\n constructor(opts: ProcedureStoreOptions = {}) {\n this.baseDir = opts.baseDir ?? resolve(process.cwd(), \".agentx/procedures\")\n mkdirSync(this.baseDir, { recursive: true })\n }\n\n private pathFor(id: string): string {\n return resolve(this.baseDir, `${id}.md`)\n }\n\n private get draftsDir(): string {\n return resolve(this.baseDir, \"_drafts\")\n }\n\n private draftPathFor(id: string): string {\n return resolve(this.draftsDir, `${id}.md`)\n }\n\n private parseFile(full: string): Procedure | null {\n const raw = readFileSync(full, \"utf-8\")\n const { meta, body } = parseFrontmatter(raw)\n const parsed = procedureMetaSchema.safeParse({\n ...meta,\n inputs: meta.inputs || [],\n kpis: meta.kpis || [],\n tags: meta.tags || [],\n related: meta.related || [],\n evidence: meta.evidence || [],\n })\n if (!parsed.success) return null\n return { meta: parsed.data, body: body.trim(), path: relative(this.baseDir, full) }\n }\n\n private listDir(dir: string): Procedure[] {\n if (!existsSync(dir)) return []\n const out: Procedure[] = []\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (!entry.isFile() || !entry.name.endsWith(\".md\") || entry.name.startsWith(\"_\")) continue\n const proc = this.parseFile(resolve(dir, entry.name))\n if (proc) out.push(proc)\n }\n return out.sort((a, b) => a.meta.id.localeCompare(b.meta.id))\n }\n\n list(): Procedure[] {\n return this.listDir(this.baseDir)\n }\n\n listDrafts(): Procedure[] {\n return this.listDir(this.draftsDir)\n }\n\n get(id: string): Procedure | null {\n const p = this.pathFor(id)\n if (!existsSync(p)) return null\n return this.parseFile(p)\n }\n\n getDraft(id: string): Procedure | null {\n const p = this.draftPathFor(id)\n if (!existsSync(p)) return null\n return this.parseFile(p)\n }\n\n add(meta: ProcedureMeta, body: string): Procedure {\n const parsed = procedureMetaSchema.parse({\n ...meta,\n created: meta.created || new Date().toISOString().slice(0, 10),\n updated: new Date().toISOString().slice(0, 10),\n })\n const p = this.pathFor(parsed.id)\n if (existsSync(p)) throw new Error(`Procedure already exists: ${parsed.id}`)\n writeFileSync(p, serializeFrontmatter(parsed) + \"\\n\\n\" + body.trim() + \"\\n\")\n return { meta: parsed, body: body.trim(), path: relative(this.baseDir, p) }\n }\n\n update(id: string, patch: Partial<ProcedureMeta>, newBody?: string): Procedure {\n const existing = this.get(id)\n if (!existing) throw new Error(`Procedure not found: ${id}`)\n const merged = procedureMetaSchema.parse({\n ...existing.meta,\n ...patch,\n id: existing.meta.id, // immutable\n updated: new Date().toISOString().slice(0, 10),\n })\n const body = newBody !== undefined ? newBody : existing.body\n writeFileSync(this.pathFor(id), serializeFrontmatter(merged) + \"\\n\\n\" + body.trim() + \"\\n\")\n return { meta: merged, body: body.trim(), path: existing.path }\n }\n\n addDraft(meta: ProcedureMeta, body: string): Procedure {\n const parsed = procedureMetaSchema.parse({\n ...meta,\n status: \"draft\",\n created: meta.created || new Date().toISOString().slice(0, 10),\n updated: new Date().toISOString().slice(0, 10),\n })\n if (this.get(parsed.id)) throw new Error(`Procedure already exists: ${parsed.id}`)\n const p = this.draftPathFor(parsed.id)\n if (existsSync(p)) throw new Error(`Draft already exists: ${parsed.id}`)\n mkdirSync(this.draftsDir, { recursive: true })\n writeFileSync(p, serializeFrontmatter(parsed) + \"\\n\\n\" + body.trim() + \"\\n\")\n return { meta: parsed, body: body.trim(), path: relative(this.baseDir, p) }\n }\n\n /** Move a draft into the active set (status: active). */\n promoteDraft(id: string): Procedure {\n const draft = this.getDraft(id)\n if (!draft) throw new Error(`Draft not found: ${id}`)\n if (this.get(id)) throw new Error(`Procedure already exists: ${id}`)\n const merged = procedureMetaSchema.parse({\n ...draft.meta,\n status: \"active\",\n updated: new Date().toISOString().slice(0, 10),\n })\n writeFileSync(this.pathFor(id), serializeFrontmatter(merged) + \"\\n\\n\" + draft.body + \"\\n\")\n unlinkSync(this.draftPathFor(id))\n return { meta: merged, body: draft.body, path: relative(this.baseDir, this.pathFor(id)) }\n }\n\n /** Park a draft under _drafts/_rejected/ so the miner never re-drafts it. */\n rejectDraft(id: string): void {\n const p = this.draftPathFor(id)\n if (!existsSync(p)) throw new Error(`Draft not found: ${id}`)\n const rejectedDir = resolve(this.draftsDir, \"_rejected\")\n mkdirSync(rejectedDir, { recursive: true })\n renameSync(p, resolve(rejectedDir, `${id}.md`))\n }\n\n isRejected(id: string): boolean {\n return existsSync(resolve(this.draftsDir, \"_rejected\", `${id}.md`))\n }\n\n deprecate(id: string): Procedure {\n return this.update(id, { status: \"deprecated\" })\n }\n}\n","import { z } from \"zod\"\n\n// --- Procedures (foundation) ---\n//\n// Karpathy-style SOPs as first-class artifacts. Each procedure is a small\n// typed doc with a trigger, inputs, ordered steps, expected output, and KPIs.\n// v1 ships definition + list/add/show — the \"delta extraction\" flow\n// (agents emit one-line deltas when they run a procedure, cheap O(runs)\n// instead of O(entries × articles)) is deferred.\n//\n// Shape on disk (.agentx/procedures/):\n// <slug>.md — frontmatter (id/title/trigger/inputs/expected/kpis)\n// + markdown body (steps + notes)\n// _runs/<run-id>.json — future: one per procedure run, holds deltas\n\nexport const procedureMetaSchema = z.object({\n id: z.string().regex(/^[a-z0-9][a-z0-9_-]*$/, \"procedure id must be lower-kebab\"),\n title: z.string().min(1),\n trigger: z.string().min(1, \"procedures must describe when they apply\"),\n inputs: z.array(z.string()).default([]),\n expected: z.string().optional(),\n kpis: z.array(z.string()).default([]),\n owner: z.string().optional(),\n tags: z.array(z.string()).default([]),\n /**\n * Single lifecycle axis (deliberately — workflows' status/state split proved\n * confusing). draft = mined/unreviewed (lives in _drafts/), active = the\n * matcher/injector will surface it, deprecated = kept but never surfaced.\n */\n status: z.enum([\"draft\", \"active\", \"deprecated\"]).default(\"active\"),\n /** Provenance: \"mined:<clusterKey>\" for extracted procedures, \"manual\" otherwise. */\n source: z.string().optional(),\n /** Trace task ids (ULIDs) the miner distilled this procedure from. */\n evidence: z.array(z.string()).default([]),\n created: z.string().optional(),\n updated: z.string().optional(),\n /** Related procedures (ids) or wiki articles (titles). Drives the graph. */\n related: z.array(z.string()).default([]),\n})\n\nexport type ProcedureMeta = z.infer<typeof procedureMetaSchema>\n\nexport interface Procedure {\n meta: ProcedureMeta\n /** Markdown body — typically a \"## Steps\" section and optional notes. */\n body: string\n /** Relative path within the procedures root, e.g. \"deploy-clawd.md\". */\n path: string\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\"\nimport { dirname, resolve } from \"path\"\nimport { z } from \"zod\"\n\n// --- Candidate ledger ---\n// Recurrence counting for the procedure miner. A \"candidate\" is a recurring\n// activity pattern (cluster key) that hasn't yet earned procedure status.\n// Plain JSON file (.agentx/procedures/_candidates.json), same idempotency\n// idea as the wiki promotion ledger: a scan watermark for cheap incremental\n// runs plus per-candidate task-id sets so re-scans never double count.\n\nexport const candidateSchema = z.object({\n /** Cluster key: `<channel>:<intentShape>:<actionSkeleton>`. */\n key: z.string(),\n count: z.number().int().min(0).default(0),\n firstSeen: z.string(),\n lastSeen: z.string(),\n /** Trace task ids already counted for this key (dedupes re-scans). */\n taskIds: z.array(z.string()).default([]),\n /** One representative user message, for dry-run review + LLM sampling. */\n sampleMessage: z.string().default(\"\"),\n /**\n * counting — accumulating occurrences\n * drafted — a procedure draft was written; stop counting\n * promoted — the draft became an active procedure\n * rejected — operator rejected the draft; never re-draft this key\n */\n status: z.enum([\"counting\", \"drafted\", \"promoted\", \"rejected\"]).default(\"counting\"),\n /** Procedure id once drafted. */\n procedureId: z.string().optional(),\n})\n\nexport type Candidate = z.infer<typeof candidateSchema>\n\nexport const candidateLedgerSchema = z.object({\n /** ISO timestamp: traces started before this have already been scanned. */\n minedThrough: z.string().optional(),\n candidates: z.array(candidateSchema).default([]),\n})\n\nexport type CandidateLedger = z.infer<typeof candidateLedgerSchema>\n\nexport function candidatesPath(baseDir: string): string {\n return resolve(baseDir, \"_candidates.json\")\n}\n\nexport function readCandidates(baseDir: string): CandidateLedger {\n const p = candidatesPath(baseDir)\n if (!existsSync(p)) return { candidates: [] }\n try {\n const parsed = candidateLedgerSchema.safeParse(JSON.parse(readFileSync(p, \"utf-8\")))\n if (parsed.success) return parsed.data\n } catch { /* corrupt ledger — start fresh rather than crash the miner */ }\n return { candidates: [] }\n}\n\nexport function writeCandidates(baseDir: string, ledger: CandidateLedger): void {\n const p = candidatesPath(baseDir)\n mkdirSync(dirname(p), { recursive: true })\n writeFileSync(p, JSON.stringify(ledger, null, 2) + \"\\n\")\n}\n\n/**\n * Record one episode against a cluster key. Returns the candidate.\n * Episodes already counted (same taskId) are ignored, so re-scanning an\n * overlapping window is safe.\n */\nexport function upsertEpisode(\n ledger: CandidateLedger,\n key: string,\n taskId: string,\n timestamp: string,\n sampleMessage: string,\n): Candidate {\n let cand = ledger.candidates.find((c) => c.key === key)\n if (!cand) {\n cand = {\n key,\n count: 0,\n firstSeen: timestamp,\n lastSeen: timestamp,\n taskIds: [],\n sampleMessage,\n status: \"counting\",\n }\n ledger.candidates.push(cand)\n }\n if (cand.taskIds.includes(taskId)) return cand\n cand.taskIds.push(taskId)\n cand.count += 1\n if (timestamp < cand.firstSeen) cand.firstSeen = timestamp\n if (timestamp > cand.lastSeen) cand.lastSeen = timestamp\n if (!cand.sampleMessage) cand.sampleMessage = sampleMessage\n return cand\n}\n\n/** Candidates that have recurred enough to be distilled into a draft. */\nexport function readyCandidates(ledger: CandidateLedger, minOccurrences: number): Candidate[] {\n return ledger.candidates\n .filter((c) => c.status === \"counting\" && c.count >= minOccurrences)\n .sort((a, b) => b.count - a.count)\n}\n\nexport function markCandidate(\n ledger: CandidateLedger,\n key: string,\n status: Candidate[\"status\"],\n procedureId?: string,\n): void {\n const cand = ledger.candidates.find((c) => c.key === key)\n if (!cand) return\n cand.status = status\n if (procedureId) cand.procedureId = procedureId\n}\n","import type { Procedure } from \"./types\"\n\n// --- Procedure matching ---\n// Cheap keyword-overlap scoring so an incoming task can be checked against\n// known procedures without an LLM call (same posture as workflows/matcher).\n// Only `active` procedures are ever surfaced — drafts await review and\n// deprecated ones are history.\n\nexport interface ProcedureMatch {\n procedure: Procedure\n score: number\n reasons: string[]\n}\n\nfunction words(text: string): Set<string> {\n return new Set(\n text.toLowerCase()\n .replace(/[^a-z0-9\\s]/g, \" \")\n .split(/\\s+/)\n .filter((w) => w.length > 3),\n )\n}\n\nfunction overlap(a: Set<string>, b: Set<string>): number {\n if (a.size === 0 || b.size === 0) return 0\n let n = 0\n for (const w of a) if (b.has(w)) n++\n return n / Math.max(a.size, b.size)\n}\n\nexport function matchProcedures(\n message: string,\n procedures: Procedure[],\n opts: { limit?: number; minScore?: number } = {},\n): ProcedureMatch[] {\n const limit = Math.max(1, opts.limit ?? 3)\n const minScore = opts.minScore ?? 0.2\n const messageWords = words(message)\n const matches: ProcedureMatch[] = []\n\n for (const p of procedures) {\n if (p.meta.status !== \"active\") continue\n const reasons: string[] = []\n // Trigger text is the primary signal — it literally describes when the\n // procedure applies. Title/tags/inputs corroborate.\n const triggerScore = overlap(messageWords, words(p.meta.trigger))\n if (triggerScore > 0) reasons.push(\"trigger\")\n const metaText = [p.meta.id, p.meta.title, ...p.meta.tags, ...p.meta.inputs].join(\" \")\n const metaScore = overlap(messageWords, words(metaText))\n if (metaScore > 0) reasons.push(\"title/tags\")\n const score = Math.min(1, triggerScore * 0.7 + metaScore * 0.5)\n if (score >= minScore) matches.push({ procedure: p, score, reasons })\n }\n\n return matches.sort((a, b) => b.score - a.score).slice(0, limit)\n}\n\n/** Render matched procedures as an instruction block for agent context.\n * Kept terse — this rides inside a token-budgeted context layer. */\nexport function renderProcedureContext(matches: ProcedureMatch[]): string {\n const sections = matches.map((m) => {\n const p = m.procedure\n const lines = [`[Known procedure: ${p.meta.title}]`, p.meta.trigger]\n if (p.meta.inputs.length) lines.push(`Inputs: ${p.meta.inputs.join(\", \")}`)\n lines.push(p.body)\n if (p.meta.expected) lines.push(`Done when: ${p.meta.expected}`)\n lines.push(`(details: agentx procedure show ${p.meta.id})`)\n return lines.join(\"\\n\")\n })\n return [\n \"The user's request matches an established procedure. Follow its steps unless the user asks otherwise — they encode the known-good path.\",\n ...sections,\n ].join(\"\\n\\n\")\n}\n"],"mappings":"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","names":["execa","execFile","StringDecoder","mkdtempSync","readFileSync","rmSync","join","tmpdir","friendlyModelError","raw","source","dateMatch","jsonMatch","parsed","inner","errType","errMessage","statusMatch","status","renderFriendlyError","f","_registry","getProcessRegistry","setProcessRegistry","processKeyToString","k","RegistryCapExceeded","message","scope","cap","ProcessRegistry","cfg","handles","h","key","opts","ks","processKeyToString","existing","handle","reason","agentId","n","target","oldest","ts","now","snap","idleFor","workspace","currentHash","e","fromHash","toHash","buildErrorEnvelope","raw","friendly","friendlyModelError","sample","renderFriendlyError","claudeMissingMessage","codexMissingMessage","openCodeMissingMessage","buildPrompt","agent","task","historyContext","parts","ctx","envLines","isGitLab","isTelegram","peer","handle","role","buildClaudeArgs","prompt","streaming","resumeSessionId","modelOverride","systemPromptAppend","args","model","buildCodexPrompt","buildOpenCodeArgs","extractOpenCodeError","event","value","tomlString","buildCodexAgentxMcpArgs","cli","buildCodexArgs","outputFile","buildRuntimeEnv","env","buildAgentEnv","home","pathParts","p","buildOpenCodeEnv","config","mcp","name","server","extractCodexTextFromEvent","item","content","text","block","firstNumber","values","extractCodexUsage","depth","candidate","nested","usage","extractCodexBilledModel","extractCodexSessionId","key","sessionId","collectCodexRunMetadata","stdout","billedModel","apiError","line","extractCodexErrorMessage","unwrapCodexErrorMessage","msg","trimmed","inner","resolvePermission","configured","skipPermissions","summary","logClaudeSpawn","agentId","via","perm","resume","extractClaudeIsError","data","parseClaudeJsonOutput","executeClaudeCode","abortSignal","start","abortReason","timeoutMs","stderr","exitCode","killed","resolve","childEnv","stripAnthropicApiKey","proc","execFile","error","onAbort","errMsg","apiErrorInStdout","parsed","executeClaudeCodeStreaming","onDelta","onEvent","fullText","streamBilledModel","streamUsage","streamSessionId","streamContextTokens","streamApiError","streamTimeoutMs","spawnEnv","execa","cancelled","cancelReason","lineBuffer","decoder","StringDecoder","chunk","lines","u","callContext","delta","resultText","result","tail","r","executeCodexCli","tmp","mkdtempSync","join","tmpdir","metadata","readFileSync","rmSync","executeCodexCliStreaming","stderrText","idleTimedOut","stderrDecoder","idleTimeoutMs","idleTimer","resetIdleTimer","stderrTail","finalText","executeOpenCodeCli","consumeLine","tokens","executeSdk","apiKey","sdk","query","q","message","executeOrchestrator","providerOpts","onThinking","combined","timedOut","onOperatorAbort","reason","wallClockTimer","timeoutEnvelope","providerName","fullTask","sender","looksUuid","noqtaContext","mcpServers","effectiveMcpConfig","hasMcp","baseOpts","generateStream","tokensUsed","inputTokens","outputTokens","errorMsg","trailing","generate","total","executeClaudeCodePersistent","registry","getProcessRegistry","channel","chatId","wasFreshSpawn","before","e","RegistryCapExceeded","finalError","finalErrorKind","contextTokens","sawEvent","evt","blocks","executeTask","providers","persistent","providerCfg","readFileSync","writeFileSync","mkdirSync","existsSync","readdirSync","resolve","MAX_HISTORY_CHARS","MAX_MESSAGES","DEFAULT_STALE_SESSION_MINUTES","DEFAULT_MAX_TURNS_PER_SESSION","DEFAULT_TIER_TWO_THRESHOLD_TOKENS","COMPACTION_MARKER","ROTATION_MEMO_TTL_MS","CROSS_CHAT_HINT_PATTERNS","referencesOtherChat","message","re","LONG_MEMORY_PATTERNS","detectLongMemoryHint","max","p","isDuplicateOfLast","messages","role","name","content","last","bucketLabel","iso","m","hh","mm","qtr","SessionStore","baseDir","opts","resolve","existsSync","mkdirSync","fn","agentId","channel","chatId","key","inFlight","session","adapter","promise","seeds","existingIds","appended","s","day","safe","file","data","readFileSync","prev","prevDay","prevKey","senderName","expectedContent","claudeSessionId","codexSessionId","opencodeSessionId","maxMessages","maxChars","chars","i","msg","lines","currentBucket","bucket","limit","todayStart","defaultAfter","after","before","safeAgentId","allFiles","readdirSync","f","afterDay","beforeDay","dayRe","candidateFiles","collected","totalScanned","queryLower","participantSet","a","b","page","oldestTs","hasMore","sinceISO","sinceDay","candidates","rows","seen","dedupped","r","k","currentKey","files","hints","recent","scope","memoryStore","needsCompaction","compactSession","applyCompaction","result","usage","contextTokens","parsed","memo","reason","memos","writeFileSync","rec","age","beforeCount","totalChars","sum","removed","dropped","debug","RateLimiter","maxPerMinute","maxPerHour","agentId","now","timestamps","hourAgo","recent","t","minuteAgo","inMinute","oldest","waitMs","opts","maxWaitMs","deadline","firstWait","r","remaining","sleepMs","resolve","readFileSync","writeFileSync","appendFileSync","existsSync","mkdirSync","resolve","CACHE_AWARE_PRICING","CONTEXT_TIER_THRESHOLD","TIER2_MULTIPLIER","getModelFamily","model","lower","splitTaskUsageByTier","u","SESSION_KEYS_MAX","PRICING_OVERRIDE_PATH","TokenTracker","baseDir","loadPricingWithOverrides","usage","pricingTable","family","p","tier2In","tier2Out","tier2CR","tier2CW","tier1","tier2","agentId","duration","realUsage","messageLength","responseLength","error","channel","sessionKey","daily","agent","i1","o1","cr1","cw1","i2","o2","cr2","cw2","chKey","ch","date","path","targetDate","totalTasks","totalInput","totalOutput","totalCacheRead","totalCacheCreate","totalCost","topAgent","topCost","agentCosts","agentSessionStats","byChannel","id","cost","sessions","chCost","report","filePath","file","fmt","n","fmtTok","agentBreakdown","a","b","stats","sessTag","channelBreakdown","row","content","dates","parsed","m","d","throughDate","opts","maxDays","appended","last","candidates","cursor","i","days","totalErrors","byAgent","agentInput","agentOutput","agentCR","agentCW","existing","acc","totalTokens","cacheHitRatio","sessionStats","result","raw","rates","DEFAULT_CONFIG","CHARS_PER_TOKEN","buildAgentContext","input","config","layers","buildLayers","a","b","parts","totalChars","maxChars","layer","layerMaxChars","trimmed","remaining","budget","name","fallback","buildChannelLayer","buildScopeLayer","lines","leafId","leafAxes","kv","k","v","intentTags","extractIntentTags","artifactLines","history","maxTokens","rules","tags","r","meta","fact","message","lower","readFileSync","writeFileSync","appendFileSync","mkdirSync","existsSync","resolve","MAX_MEMORIES","TASK_STATE_TTL_DAYS","MemoryStore","baseDir","resolve","existsSync","mkdirSync","agentId","fact","entry","appendFileSync","file","readFileSync","l","m","message","limit","memories","docs","cachePath","index","buildIndexCached","bm25Scores","scoreAll","scoreMap","r","matched","i","s","selected","a","lines","chars","scope","line","memoryIds","idSet","changed","currentInterval","writeFileSync","now","b","due","age","content","contentWords","w","mLower","ttlMs","existsSync","mkdirSync","readdirSync","readFileSync","unlinkSync","writeFileSync","resolve","TYPES","AgentMemory","opts","root","agentId","safe","slug","dir","type","name","args","path","now","createdAt","existing","safeRead","parsed","parseMemoryFile","record","serialize","out","entry","a","b","ta","tb","removed","workspacePath","memory","explicitPath","claudePath","start","end","block","startIdx","endIdx","next","before","after","entries","p","grouped","r","lines","group","titleCase","escapeYaml","raw","header","body","fm","line","colon","key","val","unescapeYaml","s","MessageQueue","defaultMode","agentId","channel","chatId","k","mode","callback","q","message","messages","combined","m","key","readFileSync","writeFileSync","appendFileSync","mkdirSync","existsSync","resolve","MAX_PATTERNS","EXTRACTION_MODEL","PATTERN_EXTRACTION_PROMPT","PatternStore","baseDir","resolve","existsSync","mkdirSync","agentId","file","readFileSync","l","p","pattern","entry","appendFileSync","message","limit","patterns","docs","cachePath","index","buildIndexCached","results","scoreAll","s","lines","existing","words","w","pWords","ids","idSet","changed","writeFileSync","a","b","extractPatterns","userMessage","agentResponse","source","store","createProvider","jsonMatch","EventEmitter","TypedEventBus","event","listener","payload","_instance","getEventBus","DELIVERY_MODES","isDeliveryMode","v","DEFAULT_ATTACH_OPTIONS","AttachRegistry","opts","DEFAULT_ATTACH_OPTIONS","sessionId","info","now","existing","session","s","a","b","agentId","mode","other","item","input","newEventId","clip","resolve","i","n","next","text","itemId","reason","expired","id","olderThanMs","state","waiter","max","_registry","getAttachRegistry","AttachRegistry","readFileSync","homedir","join","cached","TTL_MS","readClaudeJson","path","join","homedir","raw","readFileSync","parsed","parseOverageStatus","data","now","disabledReason","getOverageStatus","forceRefresh","cached","TTL_MS","preflightOverageGate","hasWarmSession","status","DEFAULT_BUDGET","budget","FIVE_H_MS","ONE_H_MS","timestamps","setDispatchBudget","next","prune","now","cutoff","FIVE_H_MS","i","timestamps","recordClaudeCodeDispatch","getClaudeCodeUsage","hourCutoff","ONE_H_MS","lastHour","last5h","out","budget","preflightQuotaGate","hasWarmSession","usage","warnIfNearingCap","ratio","stats","promptSizeKey","agentId","channel","chatId","recordPromptSize","key","total","parts","now","warnIfPromptGrowing","s","threshold","ratio","existsSync","mkdirSync","readdirSync","readFileSync","rmSync","writeFileSync","resolve","existsSync","mkdirSync","readFileSync","readdirSync","renameSync","unlinkSync","writeFileSync","resolve","relative","z","procedureMetaSchema","parseFrontmatter","raw","m","meta","line","kv","key","val","s","serializeFrontmatter","lines","ProcedureStore","opts","resolve","mkdirSync","id","full","readFileSync","body","parsed","procedureMetaSchema","relative","dir","existsSync","out","entry","readdirSync","proc","a","b","p","writeFileSync","patch","newBody","existing","merged","draft","unlinkSync","rejectedDir","renameSync","existsSync","mkdirSync","readFileSync","writeFileSync","dirname","resolve","candidateSchema","candidateLedgerSchema","candidatesPath","baseDir","readCandidates","p","parsed","writeCandidates","ledger","upsertEpisode","key","taskId","timestamp","sampleMessage","cand","c","readyCandidates","minOccurrences","a","b","markCandidate","status","procedureId","words","text","w","overlap","a","b","n","matchProcedures","message","procedures","opts","limit","minScore","messageWords","matches","p","reasons","triggerScore","metaText","metaScore","score","renderProcedureContext","m","lines","isCodingChannelContext","channel","tier","globalRegistry","setGlobalRegistry","registry","getGlobalRegistry","TASK_OUTPUT_BUFFER_MAX","TASK_OUTPUT_TTL_MS","TASK_HISTORY_DIR","TASK_HISTORY_RETENTION_DAYS","clip","s","max","buildWikiQueryHint","agentWiki","agentId","articleCount","catalogPath","resolve","existsSync","cli","wikiDir","TRACE_STEP_SUMMARY_BYTES","clipForTrace","remaining","tallyToolUses","counter","event","blocks","block","name","firstMissingRequiredTool","required","invoked","emitTraceStepsFromStreamEvent","taskId","t","at","bus","getEventBus","inputJson","content","b","makeStreamEventFormatter","textSeen","codexTextSeen","model","resume","usage","input","output","item","text","delta","label","summary","msg","m","sid","out","c","flag","AgentRegistry","AgentMemory","HandoverStore","config","log","SessionStore","WikiHub","MemoryStore","PatternStore","RateLimiter","TokenTracker","MessageQueue","setDispatchBudget","GraphStore","a","draftAgent","Classifier","id","def","builder","mesh","runner","next","chatId","o","opts","lower","bestId","bestLen","state","mention","mentionLower","found","resumeSessionId","reason","extractRotationMemo","result","chatKeyword","e","peers","agent","task","onDelta","onThinking","onEvent","startedAt","response","status","getDefaultLedger","callerOnEvent","offered","getAttachRegistry","outcome","peerEntry","p","timeoutMs","qChannel","qChatId","start","maxWaitMs","pollIntervalMs","r","mode","queued","pending","rateResult","waitMs","runningTask","abortController","pushToBuffer","chunk","sub","traceTaskId","newEventId","toolUsesByName","formatter","formatted","senderName","isCodexCli","taskStartedAt","intent","wfMatching","pendingAutoRun","store","WorkflowStore","match","matchWorkflow","wikiContext","relevantMemories","memoryContext","relevantPatterns","patternContext","skillInjection","loadLocalSkills","getAutoInjectSkills","skills","lastTokens","turns","compactResult","quality","lost","d","surgeryEnabled","historyCap","sessionHistory","rotationMemo","procedureContext","procStore","ProcedureStore","procMatches","matchProcedures","renderProcedureContext","debug","msgs","first","last","fingerprint","referencesBlock","cacheKey","cached","refs","recipes","loadReferences","loadRecipes","rootRefs","rootRecipes","resolved","resolveRecipes","renderReferences","crossChatContext","longMemoryRecall","lmHint","detectLongMemoryHint","recall","lines","ordered","stamp","who","strategy","sessionHistoryOverride","planDebug","plannerSucceeded","planContext","plan","soulSessionKey","soulSwitch","detectSoulSwitch","activeSoul","bootstrapFiles","loadBootstrapFiles","bootstrapContextText","buildBootstrapContext","agentMemoryBlock","codeFirstInstruction","richReplyInstruction","projectClaudeMd","claudeMdPath","readFileSync","runbookPath","runbookBlocks","filesToTry","totalBytes","PER_FILE_CAP","TOTAL_CAP","filename","filePath","trimmed","projectRunbook","systemPromptAppend","contextInput","historyContext","buildAgentContext","agentxContextBytes","sizeParts","sizeKey","promptSizeKey","recordPromptSize","driftWarning","warnIfPromptGrowing","taskWithSystemPrompt","finalResponse","procReg","getProcessRegistry","hasLiveProcess","hasWarmSession","abort","preflightOverageGate","preflightQuotaGate","g","preflightResponse","recordClaudeCodeDispatch","warning","warnIfNearingCap","tierUsesStreamJson","wrappedOnDelta","fullText","thinkingOpen","wrappedOnThinking","closeThinkingIfOpen","wrappedOnDeltaWithThinkingClose","executeTask","missing","invokedSummary","n","split","splitTaskUsageByTier","finalText","contextSize","cumulative","entryId","extractMemories","extractPatterns","billedModel","tChannel","tSessionKey","error","friendly","friendlyModelError","renderFriendlyError","idx","endedAt","record","rawSummary","firstLine","qm","ctx","resp","total","day","dir","mkdirSync","file","writeFileSync","hours","now","buckets","windowStart","root","today","yesterday","dayDir","f","readdirSync","tsStr","ts","offsetHours","loaded","agentDir","agentPath","days","outer","files","rec","raw","retentionDays","cutoff","removed","dayEntries","rmSync","limit","_t","_r","safeId","router","getMessageRouter","entry","message","sender","originalMessage","isStopped","edited","replaced"]}