visionclaw 0.1.193 → 0.1.194-dev.feat-backup-progress-reporting.1

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 (352) hide show
  1. package/dist/agent/backup-uploader.d.ts +44 -0
  2. package/dist/agent/backup-uploader.d.ts.map +1 -0
  3. package/dist/agent/backup-uploader.js +83 -0
  4. package/dist/agent/backup-uploader.js.map +1 -0
  5. package/dist/agent/backup.d.ts +7 -1
  6. package/dist/agent/backup.d.ts.map +1 -1
  7. package/dist/agent/backup.js +337 -52
  8. package/dist/agent/backup.js.map +1 -1
  9. package/dist/agent/command-handlers.d.ts +2 -0
  10. package/dist/agent/command-handlers.d.ts.map +1 -1
  11. package/dist/agent/command-handlers.js +29 -3
  12. package/dist/agent/command-handlers.js.map +1 -1
  13. package/dist/agent/context.d.ts +2 -0
  14. package/dist/agent/context.d.ts.map +1 -1
  15. package/dist/agent/context.js +11 -1
  16. package/dist/agent/context.js.map +1 -1
  17. package/dist/agent/data-collector.d.ts.map +1 -1
  18. package/dist/agent/data-collector.js +23 -38
  19. package/dist/agent/data-collector.js.map +1 -1
  20. package/dist/agent/loop.d.ts +3 -0
  21. package/dist/agent/loop.d.ts.map +1 -1
  22. package/dist/agent/loop.js +48 -0
  23. package/dist/agent/loop.js.map +1 -1
  24. package/dist/agent/message-format.d.ts.map +1 -1
  25. package/dist/agent/message-format.js +3 -0
  26. package/dist/agent/message-format.js.map +1 -1
  27. package/dist/agent/status.d.ts.map +1 -1
  28. package/dist/agent/status.js +2 -1
  29. package/dist/agent/status.js.map +1 -1
  30. package/dist/backup.d.ts +4 -1
  31. package/dist/backup.d.ts.map +1 -1
  32. package/dist/backup.js +86 -3
  33. package/dist/backup.js.map +1 -1
  34. package/dist/calendar/google-calendar.d.ts +2 -0
  35. package/dist/calendar/google-calendar.d.ts.map +1 -1
  36. package/dist/calendar/google-calendar.js +3 -0
  37. package/dist/calendar/google-calendar.js.map +1 -1
  38. package/dist/channels/interface.d.ts +9 -0
  39. package/dist/channels/interface.d.ts.map +1 -1
  40. package/dist/channels/manager.d.ts +7 -1
  41. package/dist/channels/manager.d.ts.map +1 -1
  42. package/dist/channels/manager.js +12 -0
  43. package/dist/channels/manager.js.map +1 -1
  44. package/dist/channels/telegram.d.ts.map +1 -1
  45. package/dist/channels/telegram.js +5 -0
  46. package/dist/channels/telegram.js.map +1 -1
  47. package/dist/cli/index.d.ts +1 -0
  48. package/dist/cli/index.d.ts.map +1 -1
  49. package/dist/cli/index.js +42 -2
  50. package/dist/cli/index.js.map +1 -1
  51. package/dist/config/types.d.ts +9 -0
  52. package/dist/config/types.d.ts.map +1 -1
  53. package/dist/config/types.js +6 -0
  54. package/dist/config/types.js.map +1 -1
  55. package/dist/drive/google-drive.d.ts +1 -0
  56. package/dist/drive/google-drive.d.ts.map +1 -1
  57. package/dist/drive/google-drive.js +5 -0
  58. package/dist/drive/google-drive.js.map +1 -1
  59. package/dist/i18n/messages.d.ts +4 -0
  60. package/dist/i18n/messages.d.ts.map +1 -1
  61. package/dist/i18n/messages.js +7 -2
  62. package/dist/i18n/messages.js.map +1 -1
  63. package/dist/index.js.map +1 -1
  64. package/dist/obs/server.d.ts +7 -0
  65. package/dist/obs/server.d.ts.map +1 -1
  66. package/dist/obs/server.js +22 -0
  67. package/dist/obs/server.js.map +1 -1
  68. package/dist/onboarding/bot-profile.d.ts.map +1 -1
  69. package/dist/onboarding/bot-profile.js +1 -0
  70. package/dist/onboarding/bot-profile.js.map +1 -1
  71. package/dist/onboarding/index.d.ts.map +1 -1
  72. package/dist/onboarding/index.js +5 -0
  73. package/dist/onboarding/index.js.map +1 -1
  74. package/dist/onboarding/prepare-mac.d.ts +1 -0
  75. package/dist/onboarding/prepare-mac.d.ts.map +1 -1
  76. package/dist/onboarding/prepare-mac.js +32 -15
  77. package/dist/onboarding/prepare-mac.js.map +1 -1
  78. package/dist/onboarding/setup-shared.d.ts.map +1 -1
  79. package/dist/onboarding/setup-shared.js +6 -0
  80. package/dist/onboarding/setup-shared.js.map +1 -1
  81. package/dist/onboarding/setup-steps.d.ts.map +1 -1
  82. package/dist/onboarding/setup-steps.js +8 -0
  83. package/dist/onboarding/setup-steps.js.map +1 -1
  84. package/dist/realtime/agent-bridge.d.ts +7 -0
  85. package/dist/realtime/agent-bridge.d.ts.map +1 -0
  86. package/dist/realtime/agent-bridge.js +31 -0
  87. package/dist/realtime/agent-bridge.js.map +1 -0
  88. package/dist/realtime/assets/index.html +1058 -0
  89. package/dist/realtime/assets/samples/alloy.mp3 +0 -0
  90. package/dist/realtime/assets/samples/ash.mp3 +0 -0
  91. package/dist/realtime/assets/samples/ballad.mp3 +0 -0
  92. package/dist/realtime/assets/samples/cedar.mp3 +0 -0
  93. package/dist/realtime/assets/samples/coral.mp3 +0 -0
  94. package/dist/realtime/assets/samples/echo.mp3 +0 -0
  95. package/dist/realtime/assets/samples/marin.mp3 +0 -0
  96. package/dist/realtime/assets/samples/sage.mp3 +0 -0
  97. package/dist/realtime/assets/samples/shimmer.mp3 +0 -0
  98. package/dist/realtime/assets/samples/verse.mp3 +0 -0
  99. package/dist/realtime/context.d.ts +14 -0
  100. package/dist/realtime/context.d.ts.map +1 -0
  101. package/dist/realtime/context.js +153 -0
  102. package/dist/realtime/context.js.map +1 -0
  103. package/dist/realtime/http-helpers.d.ts +5 -0
  104. package/dist/realtime/http-helpers.d.ts.map +1 -0
  105. package/dist/realtime/http-helpers.js +29 -0
  106. package/dist/realtime/http-helpers.js.map +1 -0
  107. package/dist/realtime/index.d.ts +62 -0
  108. package/dist/realtime/index.d.ts.map +1 -0
  109. package/dist/realtime/index.js +94 -0
  110. package/dist/realtime/index.js.map +1 -0
  111. package/dist/realtime/server.d.ts +6 -0
  112. package/dist/realtime/server.d.ts.map +1 -0
  113. package/dist/realtime/server.js +476 -0
  114. package/dist/realtime/server.js.map +1 -0
  115. package/dist/realtime/telegram-auth.d.ts +2 -0
  116. package/dist/realtime/telegram-auth.d.ts.map +1 -0
  117. package/dist/realtime/telegram-auth.js +24 -0
  118. package/dist/realtime/telegram-auth.js.map +1 -0
  119. package/dist/realtime/tools.d.ts +829 -0
  120. package/dist/realtime/tools.d.ts.map +1 -0
  121. package/dist/realtime/tools.js +630 -0
  122. package/dist/realtime/tools.js.map +1 -0
  123. package/dist/realtime/types.d.ts +62 -0
  124. package/dist/realtime/types.d.ts.map +1 -0
  125. package/dist/realtime/types.js +3 -0
  126. package/dist/realtime/types.js.map +1 -0
  127. package/dist/realtime/voice-summarizer.d.ts +4 -0
  128. package/dist/realtime/voice-summarizer.d.ts.map +1 -0
  129. package/dist/realtime/voice-summarizer.js +129 -0
  130. package/dist/realtime/voice-summarizer.js.map +1 -0
  131. package/dist/restore.d.ts +5 -0
  132. package/dist/restore.d.ts.map +1 -1
  133. package/dist/restore.js +132 -16
  134. package/dist/restore.js.map +1 -1
  135. package/dist/tools/email.d.ts +1 -1
  136. package/dist/tools/memory.d.ts +13 -0
  137. package/dist/tools/memory.d.ts.map +1 -1
  138. package/dist/tools/memory.js +43 -43
  139. package/dist/tools/memory.js.map +1 -1
  140. package/dist/tools/stock-data.d.ts +16 -0
  141. package/dist/tools/stock-data.d.ts.map +1 -1
  142. package/dist/tools/stock-data.js +36 -38
  143. package/dist/tools/stock-data.js.map +1 -1
  144. package/dist/tools/web-fetch.d.ts +4 -0
  145. package/dist/tools/web-fetch.d.ts.map +1 -1
  146. package/dist/tools/web-fetch.js +95 -23
  147. package/dist/tools/web-fetch.js.map +1 -1
  148. package/dist/tos-storage.d.ts +45 -0
  149. package/dist/tos-storage.d.ts.map +1 -0
  150. package/dist/tos-storage.js +134 -0
  151. package/dist/tos-storage.js.map +1 -0
  152. package/dist-agent/bundle.cjs +170934 -167547
  153. package/package.json +3 -2
  154. package/dist/agent/applied-credential-signature.d.ts +0 -53
  155. package/dist/agent/applied-credential-signature.d.ts.map +0 -1
  156. package/dist/agent/applied-credential-signature.js +0 -137
  157. package/dist/agent/applied-credential-signature.js.map +0 -1
  158. package/dist/agent/engines/claude/cli-resolver.d.ts +0 -16
  159. package/dist/agent/engines/claude/cli-resolver.d.ts.map +0 -1
  160. package/dist/agent/engines/claude/cli-resolver.js +0 -83
  161. package/dist/agent/engines/claude/cli-resolver.js.map +0 -1
  162. package/dist/agent/engines/claude/session-browser-policy.d.ts +0 -9
  163. package/dist/agent/engines/claude/session-browser-policy.d.ts.map +0 -1
  164. package/dist/agent/engines/claude/session-browser-policy.js +0 -49
  165. package/dist/agent/engines/claude/session-browser-policy.js.map +0 -1
  166. package/dist/agent/engines/claude/session.d.ts +0 -291
  167. package/dist/agent/engines/claude/session.d.ts.map +0 -1
  168. package/dist/agent/engines/claude/session.js +0 -1177
  169. package/dist/agent/engines/claude/session.js.map +0 -1
  170. package/dist/agent/engines/client-factory.d.ts +0 -63
  171. package/dist/agent/engines/client-factory.d.ts.map +0 -1
  172. package/dist/agent/engines/client-factory.js +0 -382
  173. package/dist/agent/engines/client-factory.js.map +0 -1
  174. package/dist/agent/engines/engine.d.ts +0 -8
  175. package/dist/agent/engines/engine.d.ts.map +0 -1
  176. package/dist/agent/engines/engine.js +0 -15
  177. package/dist/agent/engines/engine.js.map +0 -1
  178. package/dist/agent/engines/openai/file-session.d.ts +0 -49
  179. package/dist/agent/engines/openai/file-session.d.ts.map +0 -1
  180. package/dist/agent/engines/openai/file-session.js +0 -108
  181. package/dist/agent/engines/openai/file-session.js.map +0 -1
  182. package/dist/agent/engines/openai/file-tools.d.ts +0 -35
  183. package/dist/agent/engines/openai/file-tools.d.ts.map +0 -1
  184. package/dist/agent/engines/openai/file-tools.js +0 -194
  185. package/dist/agent/engines/openai/file-tools.js.map +0 -1
  186. package/dist/agent/engines/openai/session.d.ts +0 -190
  187. package/dist/agent/engines/openai/session.d.ts.map +0 -1
  188. package/dist/agent/engines/openai/session.js +0 -1066
  189. package/dist/agent/engines/openai/session.js.map +0 -1
  190. package/dist/agent/engines/openai/tools.d.ts +0 -13
  191. package/dist/agent/engines/openai/tools.d.ts.map +0 -1
  192. package/dist/agent/engines/openai/tools.js +0 -248
  193. package/dist/agent/engines/openai/tools.js.map +0 -1
  194. package/dist/agent/engines/session-types.d.ts +0 -146
  195. package/dist/agent/engines/session-types.d.ts.map +0 -1
  196. package/dist/agent/engines/session-types.js +0 -2
  197. package/dist/agent/engines/session-types.js.map +0 -1
  198. package/dist/agent/engines/system-prompt-log.d.ts +0 -9
  199. package/dist/agent/engines/system-prompt-log.d.ts.map +0 -1
  200. package/dist/agent/engines/system-prompt-log.js +0 -46
  201. package/dist/agent/engines/system-prompt-log.js.map +0 -1
  202. package/dist/agent/transcript/transcript-backfill.d.ts +0 -54
  203. package/dist/agent/transcript/transcript-backfill.d.ts.map +0 -1
  204. package/dist/agent/transcript/transcript-backfill.js +0 -604
  205. package/dist/agent/transcript/transcript-backfill.js.map +0 -1
  206. package/dist/agent/transcript/transcript-indexer.d.ts +0 -273
  207. package/dist/agent/transcript/transcript-indexer.d.ts.map +0 -1
  208. package/dist/agent/transcript/transcript-indexer.js +0 -1217
  209. package/dist/agent/transcript/transcript-indexer.js.map +0 -1
  210. package/dist/agent/transcript/transcript-memory-migrations.d.ts +0 -25
  211. package/dist/agent/transcript/transcript-memory-migrations.d.ts.map +0 -1
  212. package/dist/agent/transcript/transcript-memory-migrations.js +0 -87
  213. package/dist/agent/transcript/transcript-memory-migrations.js.map +0 -1
  214. package/dist/agent/transcript-memory-migrations.d.ts +0 -25
  215. package/dist/agent/transcript-memory-migrations.d.ts.map +0 -1
  216. package/dist/agent/transcript-memory-migrations.js +0 -87
  217. package/dist/agent/transcript-memory-migrations.js.map +0 -1
  218. package/dist/agent/tunnel-credential-handler.d.ts +0 -90
  219. package/dist/agent/tunnel-credential-handler.d.ts.map +0 -1
  220. package/dist/agent/tunnel-credential-handler.js +0 -162
  221. package/dist/agent/tunnel-credential-handler.js.map +0 -1
  222. package/dist/agent/usage/usage-backfill-handler.d.ts +0 -18
  223. package/dist/agent/usage/usage-backfill-handler.d.ts.map +0 -1
  224. package/dist/agent/usage/usage-backfill-handler.js +0 -69
  225. package/dist/agent/usage/usage-backfill-handler.js.map +0 -1
  226. package/dist/agent/usage/usage-gate.d.ts +0 -25
  227. package/dist/agent/usage/usage-gate.d.ts.map +0 -1
  228. package/dist/agent/usage/usage-gate.js +0 -83
  229. package/dist/agent/usage/usage-gate.js.map +0 -1
  230. package/dist/agent/usage/usage-handler.d.ts +0 -7
  231. package/dist/agent/usage/usage-handler.d.ts.map +0 -1
  232. package/dist/agent/usage/usage-handler.js +0 -28
  233. package/dist/agent/usage/usage-handler.js.map +0 -1
  234. package/dist/agent/usage/usage-report-builder.d.ts +0 -26
  235. package/dist/agent/usage/usage-report-builder.d.ts.map +0 -1
  236. package/dist/agent/usage/usage-report-builder.js +0 -80
  237. package/dist/agent/usage/usage-report-builder.js.map +0 -1
  238. package/dist/agent/usage/usage-report-queue.d.ts +0 -26
  239. package/dist/agent/usage/usage-report-queue.d.ts.map +0 -1
  240. package/dist/agent/usage/usage-report-queue.js +0 -199
  241. package/dist/agent/usage/usage-report-queue.js.map +0 -1
  242. package/dist/agent/usage/usage-report-types.d.ts +0 -41
  243. package/dist/agent/usage/usage-report-types.d.ts.map +0 -1
  244. package/dist/agent/usage/usage-report-types.js +0 -2
  245. package/dist/agent/usage/usage-report-types.js.map +0 -1
  246. package/dist/agent/usage/usage-reporter.d.ts +0 -31
  247. package/dist/agent/usage/usage-reporter.d.ts.map +0 -1
  248. package/dist/agent/usage/usage-reporter.js +0 -102
  249. package/dist/agent/usage/usage-reporter.js.map +0 -1
  250. package/dist/agent/usage-backfill-handler.d.ts +0 -18
  251. package/dist/agent/usage-backfill-handler.d.ts.map +0 -1
  252. package/dist/agent/usage-backfill-handler.js +0 -69
  253. package/dist/agent/usage-backfill-handler.js.map +0 -1
  254. package/dist/agent/usage-gate.d.ts +0 -25
  255. package/dist/agent/usage-gate.d.ts.map +0 -1
  256. package/dist/agent/usage-gate.js +0 -83
  257. package/dist/agent/usage-gate.js.map +0 -1
  258. package/dist/agent/usage-report-builder.d.ts +0 -26
  259. package/dist/agent/usage-report-builder.d.ts.map +0 -1
  260. package/dist/agent/usage-report-builder.js +0 -80
  261. package/dist/agent/usage-report-builder.js.map +0 -1
  262. package/dist/agent/usage-report-queue.d.ts +0 -26
  263. package/dist/agent/usage-report-queue.d.ts.map +0 -1
  264. package/dist/agent/usage-report-queue.js +0 -199
  265. package/dist/agent/usage-report-queue.js.map +0 -1
  266. package/dist/agent/usage-report-types.d.ts +0 -41
  267. package/dist/agent/usage-report-types.d.ts.map +0 -1
  268. package/dist/agent/usage-report-types.js +0 -2
  269. package/dist/agent/usage-report-types.js.map +0 -1
  270. package/dist/agent/usage-reporter.d.ts +0 -31
  271. package/dist/agent/usage-reporter.d.ts.map +0 -1
  272. package/dist/agent/usage-reporter.js +0 -102
  273. package/dist/agent/usage-reporter.js.map +0 -1
  274. package/dist/agent/wake-cycle-tool-tracker.d.ts +0 -39
  275. package/dist/agent/wake-cycle-tool-tracker.d.ts.map +0 -1
  276. package/dist/agent/wake-cycle-tool-tracker.js +0 -72
  277. package/dist/agent/wake-cycle-tool-tracker.js.map +0 -1
  278. package/dist/billing/payg-handler.d.ts +0 -29
  279. package/dist/billing/payg-handler.d.ts.map +0 -1
  280. package/dist/billing/payg-handler.js +0 -92
  281. package/dist/billing/payg-handler.js.map +0 -1
  282. package/dist/billing/payment-handler.d.ts +0 -24
  283. package/dist/billing/payment-handler.d.ts.map +0 -1
  284. package/dist/billing/payment-handler.js +0 -101
  285. package/dist/billing/payment-handler.js.map +0 -1
  286. package/dist/builtin-skills/catalog/phone-adb-automation/SKILL.md +0 -412
  287. package/dist/builtin-skills/catalog/phone-adb-automation/phone_input.sh +0 -132
  288. package/dist/builtin-skills/catalog/phone-adb-automation/phone_launch.sh +0 -166
  289. package/dist/builtin-skills/catalog/phone-adb-automation/phone_screenshot.sh +0 -87
  290. package/dist/builtin-skills/catalog/phone-adb-automation/phone_security_kbd.py +0 -174
  291. package/dist/builtin-skills/catalog/phone-adb-automation/phone_setup.sh +0 -274
  292. package/dist/builtin-skills/catalog/phone-adb-automation/phone_swipe.sh +0 -111
  293. package/dist/builtin-skills/catalog/phone-adb-automation/phone_tap.sh +0 -87
  294. package/dist/builtin-skills/catalog/phone-adb-automation/phone_ui_parse.py +0 -176
  295. package/dist/builtin-skills/catalog/phone-adb-automation/phone_wake_unlock.sh +0 -67
  296. package/dist/builtin-skills/transcribe-audio/SKILL.md +0 -122
  297. package/dist/data-processing/convert-demo-cli.d.ts +0 -7
  298. package/dist/data-processing/convert-demo-cli.d.ts.map +0 -1
  299. package/dist/data-processing/convert-demo-cli.js +0 -30
  300. package/dist/data-processing/convert-demo-cli.js.map +0 -1
  301. package/dist/data-processing/convert-demo.d.ts +0 -26
  302. package/dist/data-processing/convert-demo.d.ts.map +0 -1
  303. package/dist/data-processing/convert-demo.js +0 -233
  304. package/dist/data-processing/convert-demo.js.map +0 -1
  305. package/dist/obs/rdp/icons/icons/app_windows.svg +0 -4
  306. package/dist/obs/rdp/icons/icons/clip_get.svg +0 -4
  307. package/dist/obs/rdp/icons/icons/clip_send.svg +0 -4
  308. package/dist/obs/rdp/icons/icons/clip_shared.svg +0 -4
  309. package/dist/obs/rdp/icons/icons/clipboard.svg +0 -4
  310. package/dist/obs/rdp/icons/icons/clipboard_shared.svg +0 -4
  311. package/dist/obs/rdp/icons/icons/control.svg +0 -4
  312. package/dist/obs/rdp/icons/icons/desktop.svg +0 -4
  313. package/dist/obs/rdp/icons/icons/display.svg +0 -4
  314. package/dist/obs/rdp/icons/icons/launchpad.svg +0 -4
  315. package/dist/obs/rdp/icons/icons/mission_control.svg +0 -4
  316. package/dist/obs/rdp/icons/icons/screenshot.svg +0 -4
  317. package/dist/obs/rdp/icons/icons/zoom_actual.svg +0 -4
  318. package/dist/obs/rdp/icons/icons/zoom_fit.svg +0 -4
  319. package/dist/obs/rdp/icons/icons/zoom_in.svg +0 -4
  320. package/dist/obs/rdp/icons/icons/zoom_out.svg +0 -4
  321. package/dist/obs/tunnel-telemetry.d.ts +0 -46
  322. package/dist/obs/tunnel-telemetry.d.ts.map +0 -1
  323. package/dist/obs/tunnel-telemetry.js +0 -70
  324. package/dist/obs/tunnel-telemetry.js.map +0 -1
  325. package/dist/onboarding/cloudflared-cert.d.ts +0 -15
  326. package/dist/onboarding/cloudflared-cert.d.ts.map +0 -1
  327. package/dist/onboarding/cloudflared-cert.js +0 -57
  328. package/dist/onboarding/cloudflared-cert.js.map +0 -1
  329. package/dist/onboarding/playwriter-extension.d.ts +0 -19
  330. package/dist/onboarding/playwriter-extension.d.ts.map +0 -1
  331. package/dist/onboarding/playwriter-extension.js +0 -246
  332. package/dist/onboarding/playwriter-extension.js.map +0 -1
  333. package/dist/service/gbox-tun.d.ts +0 -14
  334. package/dist/service/gbox-tun.d.ts.map +0 -1
  335. package/dist/service/gbox-tun.js +0 -315
  336. package/dist/service/gbox-tun.js.map +0 -1
  337. package/dist/skills/installed.d.ts +0 -11
  338. package/dist/skills/installed.d.ts.map +0 -1
  339. package/dist/skills/installed.js +0 -35
  340. package/dist/skills/installed.js.map +0 -1
  341. package/dist/tools/coordinate-resolver.d.ts +0 -30
  342. package/dist/tools/coordinate-resolver.d.ts.map +0 -1
  343. package/dist/tools/coordinate-resolver.js +0 -104
  344. package/dist/tools/coordinate-resolver.js.map +0 -1
  345. package/dist/utils/playwriter-relay.d.ts +0 -9
  346. package/dist/utils/playwriter-relay.d.ts.map +0 -1
  347. package/dist/utils/playwriter-relay.js +0 -77
  348. package/dist/utils/playwriter-relay.js.map +0 -1
  349. package/dist/utils/wechat-monitor.d.ts +0 -21
  350. package/dist/utils/wechat-monitor.d.ts.map +0 -1
  351. package/dist/utils/wechat-monitor.js +0 -88
  352. package/dist/utils/wechat-monitor.js.map +0 -1
@@ -1,1177 +0,0 @@
1
- import * as claudeAgentSdk from "@anthropic-ai/claude-agent-sdk";
2
- import { query, } from "@anthropic-ai/claude-agent-sdk";
3
- import { loadSessionId, saveSessionId, loadUsageSnapshot, saveUsageSnapshot, getConfigDir } from "../../../config/index.js";
4
- import { seedClaudeAutoMemorySetting } from "../../../config/claude-settings.js";
5
- import { logger } from "../../../logger.js";
6
- import { createToolServer } from "../../../tools/index.js";
7
- import { sanitizeJsonString, sanitizeJsonValueInPlace } from "../../../utils/json-sanitize.js";
8
- import { buildAgentEnv, getModelId } from "../client-factory.js";
9
- import { logSystemPrompt } from "../system-prompt-log.js";
10
- import { getAgentState } from "../../state.js";
11
- import { resolveClaudeCliExecutable } from "./cli-resolver.js";
12
- import { ensurePlaywriterChrome } from "./session-browser-policy.js";
13
- import path from "path";
14
- import { getClaudeTranscriptDirForProfileDir } from "../../../utils/claude-transcripts.js";
15
- import { ensureBrowser } from "../../browser-launcher.js";
16
- import * as imagePrunerUnTyped from "../../image-pruner.js";
17
- import * as sessionTrimmerUnTyped from "../../session-trimmer.js";
18
- const imagePruner = imagePrunerUnTyped;
19
- const sessionTrimmer = sessionTrimmerUnTyped;
20
- /**
21
- * Used-token threshold (fraction of context window) at which we trigger
22
- * compaction for **large** context windows (>= {@link COMPACT_MAX_CONTEXT_WINDOW}).
23
- *
24
- * Smaller windows scale up to {@link COMPACT_SMALL_WINDOW_USED_PCT_THRESHOLD}
25
- * via {@link getCompactUsedPctThreshold}.
26
- */
27
- export const COMPACT_LARGE_WINDOW_USED_PCT_THRESHOLD = 0.7;
28
- const COMPACT_MIN_CONTEXT_WINDOW = 200_000;
29
- const COMPACT_MAX_CONTEXT_WINDOW = 1_000_000;
30
- /**
31
- * Used-token threshold (fraction of context window) at which we trigger
32
- * compaction for **small** context windows (<= {@link COMPACT_MIN_CONTEXT_WINDOW}).
33
- * Set above 1.0 to effectively disable token-based compaction for tiny
34
- * windows where the SDK already manages context internally.
35
- */
36
- const COMPACT_SMALL_WINDOW_USED_PCT_THRESHOLD = 4.0;
37
- export const MAX_BASE64_IMAGE_BLOCKS = 10;
38
- /** When total image blocks (real + placeholder) exceed this, trigger compaction
39
- * to avoid hitting Claude API's 100-image limit. */
40
- export const IMAGE_COMPACT_THRESHOLD = 80;
41
- //const CONTEXT_1M_BETAS = ["context-1m-2025-08-07"] as const;
42
- /**
43
- * Cap the SDK's `startup()` initialization wait. The SDK's default is 60s,
44
- * which can effectively block an entire wake cycle if the CLI hangs on
45
- * startup. We prefer to fall back to a normal cold `query()` quickly and
46
- * let the live path surface the real error.
47
- */
48
- const WARM_INITIALIZE_TIMEOUT_MS = 15_000;
49
- /**
50
- * How long to keep a pre-warmed subprocess alive before proactively
51
- * refreshing it. Bounded staleness protects us from silent degradation:
52
- * - Anthropic API sessions have server-side idle limits; a long-idle
53
- * warmed handle may get GC'd on the server, causing the first real
54
- * turn to fail and forcing a cold recovery.
55
- * - Long-lived Node subprocesses can accumulate state (fds, memory,
56
- * half-closed sockets). Periodic replacement keeps each consumed
57
- * handle young.
58
- *
59
- * Ten minutes is a comfortable trade-off: frequent enough to avoid
60
- * most server-side reaping thresholds, infrequent enough that the
61
- * extra cold-starts during refresh are negligible (~6/hour).
62
- */
63
- const WARM_REFRESH_TTL_MS = 10 * 60_000;
64
- /**
65
- * When a proactive refresh fails (startup errored), retry at this
66
- * shorter cadence instead of waiting a full TTL. This keeps us from
67
- * sitting cold for 10 minutes if the failure was transient.
68
- */
69
- const WARM_REFRESH_RETRY_MS = 30_000;
70
- const sdkStartup = claudeAgentSdk.startup;
71
- /**
72
- * Compute the used-pct compaction threshold for a given context window.
73
- *
74
- * Behavior:
75
- * - `contextWindow <= 0` (unknown): return `fallbackThreshold` (caller's choice).
76
- * - `contextWindow <= 200K`: {@link COMPACT_SMALL_WINDOW_USED_PCT_THRESHOLD} (effectively disabled).
77
- * - `contextWindow >= 1M`: {@link COMPACT_LARGE_WINDOW_USED_PCT_THRESHOLD} (default 50%).
78
- * - In between: linear interpolation between the two.
79
- *
80
- * Tiny windows accumulate tokens slowly and the SDK manages context internally,
81
- * so we want a much higher trigger; very large windows benefit from compacting
82
- * earlier to keep latency / cache hit-rate reasonable.
83
- */
84
- function getCompactUsedPctThreshold(contextWindow, fallbackThreshold) {
85
- if (contextWindow <= 0)
86
- return fallbackThreshold;
87
- if (contextWindow <= COMPACT_MIN_CONTEXT_WINDOW)
88
- return COMPACT_SMALL_WINDOW_USED_PCT_THRESHOLD;
89
- if (contextWindow >= COMPACT_MAX_CONTEXT_WINDOW)
90
- return COMPACT_LARGE_WINDOW_USED_PCT_THRESHOLD;
91
- const progress = (contextWindow - COMPACT_MIN_CONTEXT_WINDOW) / (COMPACT_MAX_CONTEXT_WINDOW - COMPACT_MIN_CONTEXT_WINDOW);
92
- return COMPACT_SMALL_WINDOW_USED_PCT_THRESHOLD + progress * (COMPACT_LARGE_WINDOW_USED_PCT_THRESHOLD - COMPACT_SMALL_WINDOW_USED_PCT_THRESHOLD);
93
- }
94
- /**
95
- * Wraps the V1 query() API with a long-lived async generator for streaming
96
- * input. This allows injecting additional user messages while the agent is
97
- * processing (interrupt messages), instead of waiting for the query to
98
- * finish before sending the next message.
99
- */
100
- export class ClaudeAgentSession {
101
- config;
102
- buildSystemPrompt;
103
- runtimeSurface;
104
- getDualSessionEnabled;
105
- getAndroidUseEnabled;
106
- /** Working directory for the SDK subprocess. Defaults to profile config dir. */
107
- sessionCwd;
108
- nativeTools;
109
- /** Resolved external MCP servers (playwright, serpapi, etc.) for this runtime. */
110
- externalMcpServers;
111
- /** Dynamic MCP servers added at runtime via manage_mcp_servers tool. */
112
- dynamicMcpServers;
113
- currentQuery = null;
114
- warmQuery = null;
115
- warmUpPromise = null;
116
- warmReadyAtMs = null;
117
- sessionId;
118
- transcriptPath = null;
119
- mode;
120
- /**
121
- * Pending message queue for the long-lived generator.
122
- * When a message is injected, it's pushed here and the waiting
123
- * generator is woken up via the resolver.
124
- */
125
- pendingMessages = [];
126
- messageResolver = null;
127
- generatorClosed = true;
128
- _orphanedInjections = 0;
129
- lastCompactRequestAtMs = 0;
130
- compactInFlight = false;
131
- stopRequested = false;
132
- /**
133
- * Tracks whether we've seeded `<profile>/.claude/settings.local.json`
134
- * with our auto-memory directory override this process-lifetime. The seed
135
- * helper is itself idempotent, but this flag keeps hot paths (every
136
- * `consume()` / `warmUp()`) from hitting the filesystem repeatedly.
137
- */
138
- autoMemorySeedDone = false;
139
- /** Optional: auto-injects screenshots after Playwright tool calls. */
140
- _screenshotInjector = null;
141
- /** AbortController passed to the SDK; aborting it cleanly stops the running query. */
142
- abortController = null;
143
- warmAbortController = null;
144
- /**
145
- * Timer that proactively replaces the pre-warmed subprocess at
146
- * `WARM_REFRESH_TTL_MS` intervals. Cleared when the warm is consumed,
147
- * on stop, or before arming a new one.
148
- */
149
- warmRefreshTimer = null;
150
- lastUsageSnapshot = null;
151
- playwriterConnectionsResetThisTurn = false;
152
- constructor({ config, buildSystemPrompt, runtimeSurface, sessionContext, }) {
153
- this.config = config;
154
- this.buildSystemPrompt = buildSystemPrompt;
155
- this.runtimeSurface = runtimeSurface;
156
- this.getDualSessionEnabled = sessionContext.getDualSessionEnabled;
157
- this.getAndroidUseEnabled = sessionContext.getAndroidUseEnabled;
158
- // Always use the config dir as cwd so the SDK stores transcripts in a
159
- // consistent directory regardless of which project the coding session
160
- // is working on.
161
- this.sessionCwd = getConfigDir();
162
- this.nativeTools = runtimeSurface.nativeTools;
163
- this.externalMcpServers = runtimeSurface.externalMcpServers;
164
- this.dynamicMcpServers = {};
165
- this.mode = sessionContext.mode;
166
- this.sessionId = loadSessionId(this.config, this.mode);
167
- const persisted = loadUsageSnapshot(this.config, this.mode);
168
- if (persisted && this.sessionId) {
169
- this.lastUsageSnapshot = { ...persisted, capturedAtMs: Date.now() };
170
- }
171
- }
172
- set screenshotInjector(injector) {
173
- this._screenshotInjector = injector;
174
- }
175
- /**
176
- * Seed `<profile>/.claude/settings.local.json` once per process so the
177
- * CLI's auto-memory lands inside the active profile instead of the
178
- * default `~/.claude/projects/...` tree. Called from every CLI-spawn
179
- * path (warm + cold); the underlying helper is itself idempotent.
180
- *
181
- * Gated on the `autoMemorySeedDone` flag so the happy path doesn't
182
- * re-read/re-write the file every turn. The flag is set **only on
183
- * success** — if the first attempt fails (permissions, disk full, etc.)
184
- * the next warm/cold spawn will retry until it lands, instead of
185
- * silently leaving the CLI on its default memory path for the rest of
186
- * the session.
187
- */
188
- ensureAutoMemorySeeded() {
189
- if (this.autoMemorySeedDone)
190
- return;
191
- try {
192
- this.autoMemorySeedDone = seedClaudeAutoMemorySetting();
193
- }
194
- catch (err) {
195
- // Never let a settings-seeding failure block the agent. Worst case
196
- // the CLI uses its default memory path and /obs reports that path
197
- // correctly via the system/init event — degraded but functional.
198
- // We deliberately do NOT set autoMemorySeedDone here so the next
199
- // spawn retries.
200
- logger.warn(`[auto-memory] Seeding failed; proceeding with CLI defaults: ${err instanceof Error ? err.message : String(err)}`);
201
- }
202
- }
203
- /**
204
- * Build a fresh mcpServers record with a new visionclaw tool server instance.
205
- * This avoids the "Already connected to a transport" error when the same
206
- * McpServer instance is reused across concurrent/sequential query() calls.
207
- */
208
- buildMcpServers() {
209
- if (this.runtimeSurface.visionClawToolTransport.transport !== "claude-mcp") {
210
- throw new Error("Claude session requires claude-mcp VisionClaw tool transport");
211
- }
212
- return {
213
- ...this.externalMcpServers,
214
- ...this.dynamicMcpServers,
215
- visionclaw: createToolServer({
216
- dualSession: this.getDualSessionEnabled()
217
- && this.runtimeSurface.visionClawToolTransport.supportsSwitchSessionTool,
218
- androidUse: this.getAndroidUseEnabled(),
219
- }),
220
- };
221
- }
222
- buildNativeToolOptions() {
223
- if (this.nativeTools.kind === "claude-default") {
224
- return {
225
- tools: { type: "preset", preset: "claude_code" },
226
- };
227
- }
228
- return {};
229
- }
230
- buildHooks() {
231
- return {
232
- UserPromptSubmit: [
233
- {
234
- hooks: [
235
- (async (input, _toolUseID, { signal }) => {
236
- const up = input;
237
- try {
238
- this.captureTranscriptPath(up.transcript_path);
239
- if (signal.aborted)
240
- return {};
241
- const res = await imagePruner.pruneSessionImages({
242
- transcriptPath: up.transcript_path,
243
- keepLastNBase64Images: MAX_BASE64_IMAGE_BLOCKS,
244
- });
245
- if (this.lastUsageSnapshot) {
246
- this.captureUsageSnapshot({
247
- ...this.lastUsageSnapshot,
248
- allImageBlocks: res.allImageBlocks,
249
- });
250
- }
251
- }
252
- catch (err) {
253
- logger.warn(`UserPromptSubmit image pruning failed: ${err instanceof Error ? err.message : String(err)}`);
254
- }
255
- return {};
256
- }),
257
- ],
258
- timeout: 120,
259
- },
260
- ],
261
- PreCompact: [
262
- {
263
- hooks: [
264
- (async (input, _toolUseID, { signal }) => {
265
- const pre = input;
266
- try {
267
- this.captureTranscriptPath(pre.transcript_path);
268
- if (signal.aborted)
269
- return {};
270
- const res = await imagePruner.pruneSessionImages({
271
- transcriptPath: pre.transcript_path,
272
- keepLastNBase64Images: MAX_BASE64_IMAGE_BLOCKS,
273
- });
274
- if (res.fileChanged) {
275
- logger.system(`Pruned transcript images before compaction: pruned=${res.prunedImageBlocks} resized=${res.resizedImageBlocks} kept=${res.keptImageBlocks} total=${res.totalImageBlocks}`);
276
- }
277
- }
278
- catch (err) {
279
- logger.warn(`PreCompact image pruning failed: ${err instanceof Error ? err.message : String(err)}`);
280
- }
281
- return {};
282
- }),
283
- ],
284
- timeout: 120,
285
- },
286
- ],
287
- PreToolUse: [
288
- {
289
- matcher: this.config.browserBackend === "playwriter"
290
- ? "mcp__playwriter__.*"
291
- : "mcp__playwright__.*",
292
- hooks: [
293
- (async (_input) => {
294
- const pre = _input;
295
- logger.debug(`[PreToolUse] ensuring browser for ${pre.tool_name}`);
296
- if (this.config.browserBackend === "playwriter") {
297
- const didReset = ensurePlaywriterChrome(this.playwriterConnectionsResetThisTurn);
298
- if (didReset) {
299
- this.playwriterConnectionsResetThisTurn = true;
300
- }
301
- }
302
- else {
303
- await ensureBrowser();
304
- }
305
- return {};
306
- }),
307
- ],
308
- timeout: 30,
309
- },
310
- ],
311
- PostToolUse: [
312
- {
313
- hooks: [
314
- (async (input, _toolUseID) => {
315
- const post = input;
316
- // Track the (possibly mutated) tool response separately from
317
- // post.tool_response so the top-level-string case can swap in
318
- // a sanitized replacement without aliasing the original.
319
- let toolResponse = post.tool_response;
320
- let mutated = false;
321
- try {
322
- const { resizedCount } = await imagePruner.resizeOversizedImagesInToolOutput(toolResponse);
323
- if (resizedCount > 0) {
324
- logger.debug(`[PostToolUse] resized ${resizedCount} oversized image(s) in ${post.tool_name} output`);
325
- mutated = true;
326
- }
327
- }
328
- catch (err) {
329
- logger.warn(`PostToolUse image resize failed: ${err instanceof Error ? err.message : String(err)}`);
330
- }
331
- // Strip lone UTF-16 surrogates from the tool response so they
332
- // never reach the model API's JSON validator (which rejects
333
- // them, unlike Node's permissive JSON.stringify).
334
- //
335
- // MCP tools return objects (CallToolResult), but
336
- // tool_response is typed as `unknown` and non-MCP tools may
337
- // surface raw strings. Handle both shapes:
338
- // - string: sanitize and replace `toolResponse`
339
- // - object/array: sanitize in place
340
- try {
341
- if (typeof toolResponse === "string") {
342
- const sanitized = sanitizeJsonString(toolResponse);
343
- if (sanitized !== toolResponse) {
344
- toolResponse = sanitized;
345
- logger.debug(`[PostToolUse] sanitized lone surrogates in ${post.tool_name} string output`);
346
- mutated = true;
347
- }
348
- }
349
- else if (sanitizeJsonValueInPlace(toolResponse)) {
350
- logger.debug(`[PostToolUse] sanitized lone surrogates in ${post.tool_name} output`);
351
- mutated = true;
352
- }
353
- }
354
- catch (err) {
355
- logger.warn(`PostToolUse surrogate sanitize failed: ${err instanceof Error ? err.message : String(err)}`);
356
- }
357
- if (this._screenshotInjector) {
358
- try {
359
- await this._screenshotInjector.onToolComplete(post.tool_name, this);
360
- }
361
- catch (err) {
362
- logger.warn(`PostToolUse screenshot injection failed: ${err instanceof Error ? err.message : String(err)}`);
363
- }
364
- }
365
- if (mutated) {
366
- return {
367
- hookSpecificOutput: {
368
- hookEventName: "PostToolUse",
369
- updatedMCPToolOutput: toolResponse,
370
- },
371
- };
372
- }
373
- return {};
374
- }),
375
- ],
376
- timeout: 120,
377
- },
378
- ],
379
- };
380
- }
381
- /**
382
- * Build the shared Claude query options used for both pre-warm and live runs.
383
- */
384
- buildQueryOptions(systemPrompt, savedSessionId, abortController, effort) {
385
- const cliResolution = resolveClaudeCliExecutable();
386
- const options = {
387
- model: getModelId(this.config),
388
- ...(cliResolution.kind !== "sdk-default"
389
- ? { pathToClaudeCodeExecutable: cliResolution.path }
390
- : {}),
391
- systemPrompt,
392
- ...this.buildNativeToolOptions(),
393
- disallowedTools: this.runtimeSurface.disallowedTools,
394
- permissionMode: "bypassPermissions",
395
- allowDangerouslySkipPermissions: true,
396
- cwd: this.sessionCwd,
397
- settingSources: ["project"],
398
- mcpServers: this.buildMcpServers(),
399
- hooks: this.buildHooks(),
400
- env: buildAgentEnv(this.config),
401
- ...(savedSessionId ? { resume: savedSessionId } : {}),
402
- ...(abortController ? { abortController } : {}),
403
- //betas: [...CONTEXT_1M_BETAS],
404
- };
405
- options.effort = effort ?? (this.mode === "coding" ? "high" : "medium");
406
- return options;
407
- }
408
- /**
409
- * Cancel any pending proactive-refresh timer. Idempotent.
410
- */
411
- clearWarmRefreshTimer() {
412
- if (this.warmRefreshTimer) {
413
- clearTimeout(this.warmRefreshTimer);
414
- this.warmRefreshTimer = null;
415
- }
416
- }
417
- /**
418
- * Arm the proactive-refresh timer. Caller must ensure any previous timer
419
- * is cleared. Uses `.unref()` so the process can exit while a warm
420
- * subprocess is idle — the refresh is purely a background optimization
421
- * and should never hold the event loop alive on its own.
422
- */
423
- scheduleWarmRefresh(delayMs) {
424
- this.clearWarmRefreshTimer();
425
- const timer = setTimeout(() => {
426
- this.warmRefreshTimer = null;
427
- void this.performWarmRefresh();
428
- }, delayMs);
429
- if (typeof timer.unref === "function") {
430
- timer.unref();
431
- }
432
- this.warmRefreshTimer = timer;
433
- }
434
- /**
435
- * Handler for the refresh timer. Covers two cases:
436
- * 1. Normal TTL refresh: `warmQuery` is non-null → close the stale
437
- * subprocess and start a fresh one.
438
- * 2. Retry after a previous failure: `warmQuery` is null → just
439
- * attempt warmUp again (no teardown needed).
440
- *
441
- * If the warm was already consumed between timer arming and firing,
442
- * the consume path would have cleared the timer, so we shouldn't
443
- * reach here in that case.
444
- */
445
- async performWarmRefresh() {
446
- if (this.stopRequested) {
447
- // stopRequested is briefly true between requestStop() and the next
448
- // loop iteration's clearStop(). If we just return here without
449
- // rescheduling, the refresh loop silently dies until someone
450
- // explicitly re-arms it. Defer to a shorter retry so the refresh
451
- // resumes once the stop window passes.
452
- logger.debug(`Claude warm refresh deferred: stop requested; retrying in ${WARM_REFRESH_RETRY_MS}ms`);
453
- this.scheduleWarmRefresh(WARM_REFRESH_RETRY_MS);
454
- return;
455
- }
456
- if (this.warmQuery) {
457
- const oldWarm = this.warmQuery;
458
- const oldAbort = this.warmAbortController;
459
- const oldAgeMs = this.warmReadyAtMs === null ? null : Date.now() - this.warmReadyAtMs;
460
- this.warmQuery = null;
461
- this.warmAbortController = null;
462
- this.warmReadyAtMs = null;
463
- // Tear down the stale warm subprocess before starting a fresh one.
464
- // We abort first so any in-flight SDK I/O short-circuits, then close
465
- // to release the child process and open transports.
466
- try {
467
- oldAbort?.abort();
468
- }
469
- catch { /* best-effort */ }
470
- try {
471
- oldWarm.close();
472
- }
473
- catch { /* best-effort */ }
474
- logger.debug(`Claude warm refresh: replacing ${oldAgeMs === null ? "warm" : `${oldAgeMs}ms-old warm`} subprocess`);
475
- }
476
- else {
477
- // Retry path after a prior failure — nothing to tear down.
478
- logger.debug("Claude warm refresh: retrying after previous failure");
479
- }
480
- // `warmUp()` swallows its own errors (to keep its contract simple for
481
- // fire-and-forget callers), so we detect failure by inspecting warm
482
- // state after it resolves rather than relying on a thrown error. We
483
- // route the post-warmUp checks through a helper so TypeScript doesn't
484
- // narrow `this.stopRequested`/`this.warmQuery` based on the synchronous
485
- // assignments above.
486
- await this.warmUp();
487
- this.handleWarmRefreshOutcome();
488
- }
489
- /**
490
- * Post-warmUp follow-up for performWarmRefresh. Kept in its own method so
491
- * TS's control-flow narrowing in performWarmRefresh doesn't treat these
492
- * fields as having their pre-await values.
493
- */
494
- handleWarmRefreshOutcome() {
495
- if (this.stopRequested) {
496
- // Stop arrived while we were refreshing — requestStop already cleaned
497
- // up; don't re-arm a refresh timer.
498
- return;
499
- }
500
- if (!this.warmQuery) {
501
- // warmUp() failed (or was a no-op because startup is unavailable).
502
- // Schedule a shorter retry so we don't sit cold for a full TTL after
503
- // a transient failure. If startup is permanently unavailable, these
504
- // retries are cheap no-ops.
505
- logger.debug(`Claude warm refresh did not produce a new warm; retrying in ${WARM_REFRESH_RETRY_MS}ms`);
506
- this.scheduleWarmRefresh(WARM_REFRESH_RETRY_MS);
507
- }
508
- // On success, warmUp() already scheduled the next refresh at TTL.
509
- }
510
- /**
511
- * Send the initial user message and return the query generator to stream
512
- * responses. The underlying async generator stays open so that additional
513
- * messages can be injected via injectMessage().
514
- */
515
- async warmUp() {
516
- if (!sdkStartup) {
517
- return;
518
- }
519
- // Seed `<profile>/.claude/settings.local.json` once per process. The
520
- // CLI reads `autoMemoryDirectory` from this file at subprocess
521
- // startup, so we must write it before spawning the warm child.
522
- this.ensureAutoMemorySeeded();
523
- // Already warm — nothing to do, but defensively re-arm the refresh
524
- // timer if it happens to be missing. This can occur if a TTL refresh
525
- // fired during a brief stopRequested window and chose to defer rather
526
- // than close the warm.
527
- if (this.warmQuery) {
528
- if (!this.warmRefreshTimer && !this.stopRequested) {
529
- this.scheduleWarmRefresh(WARM_REFRESH_TTL_MS);
530
- }
531
- return;
532
- }
533
- // A warm-up is already in flight — await that one instead of racing.
534
- if (this.warmUpPromise) {
535
- await this.warmUpPromise;
536
- return;
537
- }
538
- // Honor an in-progress stop request — don't spin up a subprocess we're
539
- // just going to abort on the next line.
540
- if (this.stopRequested) {
541
- return;
542
- }
543
- const savedSessionId = this.sessionId;
544
- const systemPrompt = this.buildSystemPrompt();
545
- const warmAbortController = new AbortController();
546
- const warmStartMs = Date.now();
547
- const promise = (async () => {
548
- try {
549
- const warmed = await sdkStartup({
550
- options: this.buildQueryOptions(systemPrompt, savedSessionId, warmAbortController),
551
- initializeTimeoutMs: WARM_INITIALIZE_TIMEOUT_MS,
552
- });
553
- // Note: we intentionally do NOT discard the freshly-ready warm when
554
- // `this.stopRequested` is true. requestStop() is typically a "stop
555
- // the current task" signal, and the user usually starts a new task
556
- // shortly after. Throwing the warm away here would force the next
557
- // task to pay the cold-start cost we just paid. On actual process
558
- // shutdown, the warm child process is reaped with the parent.
559
- this.warmQuery = warmed;
560
- this.warmAbortController = warmAbortController;
561
- this.warmReadyAtMs = Date.now();
562
- logger.debug(`Claude query pre-warm ready in ${this.warmReadyAtMs - warmStartMs}ms (next refresh in ${WARM_REFRESH_TTL_MS}ms)`);
563
- // Arm the proactive-refresh timer. When it fires we'll close this
564
- // subprocess and spin up a fresh one, keeping staleness bounded.
565
- this.scheduleWarmRefresh(WARM_REFRESH_TTL_MS);
566
- }
567
- catch (err) {
568
- logger.debug(`Claude query pre-warm skipped after ${Date.now() - warmStartMs}ms: ${err instanceof Error ? err.message : String(err)}`);
569
- this.warmQuery = null;
570
- this.warmAbortController = null;
571
- this.warmReadyAtMs = null;
572
- }
573
- finally {
574
- this.warmUpPromise = null;
575
- }
576
- })();
577
- this.warmUpPromise = promise;
578
- await promise;
579
- }
580
- /**
581
- * Tear down any idle pre-warmed subprocess and cancel its refresh timer.
582
- *
583
- * Called when something in `config` that was baked into the warm options
584
- * changed (provider, env, model). The warm subprocess inlines provider
585
- * env via `buildAgentEnv(config)` at warm time and `sendAndStream()`
586
- * deliberately reuses those baked options on the warm path — so without
587
- * this, a provider switch can be silently undone by consuming a stale
588
- * warm child for one more turn.
589
- *
590
- * Idempotent and best-effort: safe to call when no warm exists, and
591
- * does not throw on close failures (the child will be reaped on
592
- * process exit if abort/close misfires).
593
- */
594
- discardWarmQuery() {
595
- this.clearWarmRefreshTimer();
596
- const oldWarm = this.warmQuery;
597
- const oldAbort = this.warmAbortController;
598
- if (!oldWarm && !oldAbort)
599
- return;
600
- this.warmQuery = null;
601
- this.warmAbortController = null;
602
- this.warmReadyAtMs = null;
603
- try {
604
- oldAbort?.abort();
605
- }
606
- catch { /* best-effort */ }
607
- try {
608
- oldWarm?.close();
609
- }
610
- catch { /* best-effort */ }
611
- logger.debug("Claude warm subprocess discarded (provider/env changed)");
612
- }
613
- sendAndStream(content, options) {
614
- this.playwriterConnectionsResetThisTurn = false;
615
- const savedSessionId = this.sessionId;
616
- const effort = options?.effort;
617
- // Resolve the Claude Code executable — prefers the native binary shipped
618
- // by @anthropic-ai/claude-agent-sdk >= 0.2.113, falls back to the legacy
619
- // cli.js, and finally lets the SDK resolve it internally.
620
- const cliResolution = resolveClaudeCliExecutable();
621
- logger.debug(`Claude CLI resolution: kind=${cliResolution.kind}` +
622
- (cliResolution.kind === "sdk-default" ? "" : ` path=${cliResolution.path}`));
623
- const systemPrompt = this.buildSystemPrompt();
624
- // Log the resolved system prompt for training data capture. The console
625
- // gets a compact head/tail preview; the full prompt goes into `data.prompt`
626
- // of the JSONL entry so the log file remains complete.
627
- logSystemPrompt(systemPrompt);
628
- // Seed the generator with the initial message
629
- const initialMessage = {
630
- type: "user",
631
- session_id: savedSessionId ?? "",
632
- message: { role: "user", content },
633
- parent_tool_use_id: null,
634
- };
635
- this.pendingMessages = [initialMessage];
636
- this.generatorClosed = false;
637
- this._orphanedInjections = 0;
638
- // Create a long-lived async generator that yields the initial message
639
- // and then waits for additional messages injected via injectMessage().
640
- // We capture references to the session's pending queue and resolver
641
- // so the generator can read from them without aliasing `this`.
642
- const pending = this.pendingMessages;
643
- const setResolver = (r) => {
644
- this.messageResolver = r;
645
- };
646
- const isClosed = () => this.generatorClosed;
647
- const shiftPending = () => pending.shift();
648
- async function* messageStream() {
649
- while (!isClosed()) {
650
- // Yield all pending messages
651
- let next = shiftPending();
652
- while (next) {
653
- yield next;
654
- next = shiftPending();
655
- }
656
- // If generator is still open, wait for the next message
657
- if (!isClosed()) {
658
- await new Promise((resolve) => {
659
- setResolver(resolve);
660
- });
661
- }
662
- }
663
- }
664
- if (savedSessionId) {
665
- logger.system(`Resuming session: ${savedSessionId}`);
666
- }
667
- else {
668
- logger.system("Creating new session...");
669
- }
670
- const prompt = messageStream();
671
- if (this.warmQuery) {
672
- // Warm path: the subprocess was already initialized with a full set of
673
- // query options (system prompt, MCP servers, hooks, CLI path, betas,
674
- // env, resume, etc.) by warmUp(). We deliberately do NOT call
675
- // buildQueryOptions() here — doing so would spin up a throwaway
676
- // McpServer instance and allocate a fresh hook closure set that get
677
- // immediately discarded, defeating the whole point of pre-warming.
678
- //
679
- // Cancel the proactive-refresh timer first: we're about to consume
680
- // this warm, and we don't want the timer to fire mid-turn and try
681
- // to close a subprocess that's now actively serving a query.
682
- this.clearWarmRefreshTimer();
683
- const warmQuery = this.warmQuery;
684
- this.warmQuery = null;
685
- // Adopt the warm abort controller so requestStop() cleanly cancels
686
- // the running query via the same AbortSignal the SDK is watching.
687
- this.abortController = this.warmAbortController;
688
- this.warmAbortController = null;
689
- const warmAgeMs = this.warmReadyAtMs === null ? null : Date.now() - this.warmReadyAtMs;
690
- if (effort) {
691
- logger.debug(`Claude query using pre-warmed subprocess${warmAgeMs === null ? "" : ` (ready ${warmAgeMs}ms ago)`} (effort=${effort} ignored — warm subprocess uses pre-built options)`);
692
- }
693
- else {
694
- logger.debug(`Claude query using pre-warmed subprocess${warmAgeMs === null ? "" : ` (ready ${warmAgeMs}ms ago)`}`);
695
- }
696
- this.warmReadyAtMs = null;
697
- this.currentQuery = warmQuery.query(prompt);
698
- }
699
- else {
700
- // Cold path: build full options and let query() spawn a fresh CLI.
701
- // Same rationale as warmUp() — seed before the subprocess starts so
702
- // it picks up the overridden `autoMemoryDirectory`.
703
- this.ensureAutoMemorySeeded();
704
- const abortController = new AbortController();
705
- this.abortController = abortController;
706
- const options = this.buildQueryOptions(systemPrompt, savedSessionId, abortController, effort);
707
- if (effort) {
708
- logger.debug(`Claude query using cold start (effort=${effort})`);
709
- }
710
- else {
711
- logger.debug("Claude query using cold start");
712
- }
713
- this.currentQuery = query({ prompt, options });
714
- }
715
- return this.currentQuery;
716
- }
717
- /**
718
- * Inject a new user message into the running query stream.
719
- * The agent will see this as a follow-up user message in the conversation.
720
- * Only works while a query is active (between sendAndStream and closeInput).
721
- *
722
- * @returns true if the message was injected, false if the generator is closed.
723
- */
724
- injectMessage(content, options) {
725
- const msg = {
726
- type: "user",
727
- session_id: this.sessionId ?? "",
728
- message: { role: "user", content },
729
- parent_tool_use_id: null,
730
- ...(options?.shouldQuery === false ? { shouldQuery: false } : {}),
731
- };
732
- if (this.generatorClosed) {
733
- logger.warn("Cannot inject message: generator is closed");
734
- return false;
735
- }
736
- this.pendingMessages.push(msg);
737
- logger.info("Injected interrupt message into active session");
738
- // Wake the generator if it's waiting
739
- if (this.messageResolver) {
740
- this.messageResolver();
741
- this.messageResolver = null;
742
- }
743
- return true;
744
- }
745
- /**
746
- * Signal the input generator to close.
747
- * Any injected messages still in the pending queue are counted as
748
- * orphaned — they were accepted by injectMessage but never delivered
749
- * to the SDK.
750
- */
751
- closeInput() {
752
- this._orphanedInjections = this.pendingMessages.length;
753
- if (this._orphanedInjections > 0) {
754
- logger.warn(`closeInput: ${this._orphanedInjections} injected message(s) orphaned`);
755
- }
756
- this.pendingMessages = [];
757
- this.generatorClosed = true;
758
- if (this.messageResolver) {
759
- this.messageResolver();
760
- this.messageResolver = null;
761
- }
762
- }
763
- /**
764
- * Returns true if injected messages were lost when the generator closed.
765
- */
766
- get hasOrphanedInjections() {
767
- return this._orphanedInjections > 0;
768
- }
769
- get isInputClosed() {
770
- return this.generatorClosed;
771
- }
772
- /**
773
- * Request that the current agent turn be stopped.
774
- * Uses the SDK's AbortController to cleanly signal the running query to
775
- * stop, then closes the input stream so the loop returns to idle.
776
- */
777
- requestStop() {
778
- this.stopRequested = true;
779
- if (this.abortController) {
780
- this.abortController.abort();
781
- logger.system("Stop requested — aborting query via AbortController");
782
- }
783
- // IMPORTANT: Do NOT tear down the idle pre-warmed subprocess here.
784
- // requestStop() is typically called to end the *current* turn (user
785
- // /stop, watchdog timeout, stream silence, obs UI button); the user
786
- // will usually start a new task shortly after. Preserving the idle
787
- // warm lets that next task skip cold-start (~2-3s).
788
- //
789
- // The warm is a fully separate subprocess with its own AbortController
790
- // — aborting `this.abortController` above does not touch it.
791
- //
792
- // For graceful shutdown: the warm is a child process of ours and will
793
- // be reaped when we exit; the refresh timer uses `.unref()` so it
794
- // doesn't block process exit.
795
- this.closeInput();
796
- }
797
- isStopRequested() {
798
- return this.stopRequested;
799
- }
800
- signalFinish() {
801
- // Claude SDK stream ends naturally after the finish tool result —
802
- // no early abort needed.
803
- }
804
- clearStop() {
805
- this.stopRequested = false;
806
- // Only null the *live query's* abort controller. The warm subprocess
807
- // has its own abort controller that must survive the stop→clearStop
808
- // cycle — sendAndStream() adopts it when consuming the warm.
809
- this.abortController = null;
810
- }
811
- /**
812
- * Update the persisted session ID.
813
- *
814
- * Only saves on first capture (null → id). Once set, a different ID from
815
- * the SDK is ignored — it means the resume failed and the SDK created a
816
- * throwaway conversation. Persisting it would corrupt session.json and
817
- * orphan the real transcript.
818
- */
819
- captureSessionId(id) {
820
- if (!id || this.sessionId)
821
- return;
822
- this.sessionId = id;
823
- saveSessionId(this.config, id, this.mode);
824
- }
825
- captureTranscriptPath(p) {
826
- if (p && typeof p === "string") {
827
- this.transcriptPath = p;
828
- }
829
- }
830
- getSessionId() {
831
- return this.sessionId;
832
- }
833
- /**
834
- * Proactively request compaction by running the /compact slash command as a
835
- * separate one-turn query resumed from the current session.
836
- *
837
- * This is more reliable than injecting "/compact" into the streaming prompt
838
- * because the running query may treat it as ordinary text.
839
- */
840
- async requestCompaction() {
841
- if (this.compactInFlight)
842
- return;
843
- this.compactInFlight = true;
844
- try {
845
- getAgentState().compacting = true;
846
- }
847
- catch {
848
- // Agent state not yet initialized
849
- }
850
- // TODO: maybe should use reply map.
851
- const sendCompactNotice = (text) => {
852
- try {
853
- const state = getAgentState();
854
- const { ownerConfig, channelManager } = state;
855
- const channel = ownerConfig.telegramChatId ? "telegram" : ownerConfig.ownerEmail ? "gmail" : undefined;
856
- const recipient = ownerConfig.telegramChatId ? String(ownerConfig.telegramChatId) : ownerConfig.ownerEmail;
857
- if (channel && recipient) {
858
- channelManager
859
- .sendMessage(channel, recipient, text)
860
- .catch((err) => {
861
- logger.warn(`Failed to send compaction notification: ${err instanceof Error ? err.message : String(err)}`);
862
- });
863
- }
864
- }
865
- catch {
866
- // Agent state not yet initialized — skip notification
867
- }
868
- };
869
- try {
870
- const savedSessionId = this.sessionId;
871
- if (!savedSessionId) {
872
- logger.warn("Cannot request /compact: session_id not available yet");
873
- return;
874
- }
875
- // Resolve the Claude Code executable — same lookup as sendAndStream().
876
- const cliResolution = resolveClaudeCliExecutable();
877
- const options = {
878
- model: getModelId(this.config),
879
- ...(cliResolution.kind !== "sdk-default"
880
- ? { pathToClaudeCodeExecutable: cliResolution.path }
881
- : {}),
882
- systemPrompt: this.buildSystemPrompt(),
883
- permissionMode: "bypassPermissions",
884
- allowDangerouslySkipPermissions: true,
885
- cwd: this.sessionCwd,
886
- settingSources: ["project"],
887
- mcpServers: this.buildMcpServers(),
888
- env: buildAgentEnv(this.config),
889
- resume: savedSessionId,
890
- maxTurns: 1,
891
- };
892
- logger.system("Requesting /compact...");
893
- const isSilent = getAgentState().ownerConfig.silentCompaction;
894
- if (!isSilent) {
895
- sendCompactNotice("My brain is getting full... Let me tidy up my thoughts. This may take a minute — I might forget some older details.");
896
- }
897
- const cliLabel = cliResolution.kind === "sdk-default"
898
- ? "sdk-default"
899
- : `${cliResolution.kind}:${cliResolution.path}`;
900
- logger.debug(`Compaction query options: cli=${cliLabel} cwd=${this.sessionCwd} resume=${savedSessionId}`);
901
- // Sanitize the transcript before compaction — the compact subprocess
902
- // loads the file on startup, so any corrupt entries must be fixed first.
903
- const tp = this.transcriptPath ?? (savedSessionId ? this.deriveTranscriptPath(savedSessionId) : null);
904
- if (tp) {
905
- try {
906
- await imagePruner.sanitizeTranscript(tp);
907
- }
908
- catch (sanitizeErr) {
909
- logger.warn(`Pre-compact sanitization failed: ${sanitizeErr instanceof Error ? sanitizeErr.message : String(sanitizeErr)}`);
910
- }
911
- }
912
- // Guard against compaction hanging forever — abort after 5 minutes.
913
- const COMPACTION_TIMEOUT_MS = 5 * 60_000;
914
- const compactAbort = new AbortController();
915
- const compactTimer = setTimeout(() => {
916
- logger.warn(`[compact] Compaction timed out after ${COMPACTION_TIMEOUT_MS / 1000}s — aborting`);
917
- compactAbort.abort();
918
- }, COMPACTION_TIMEOUT_MS);
919
- try {
920
- for await (const msg of query({
921
- prompt: "/compact",
922
- options: { ...options, abortController: compactAbort },
923
- })) {
924
- if (msg.type === "system" && msg.subtype === "compact_boundary") {
925
- // Capture session id (should remain the same) and log compaction metadata.
926
- if ("session_id" in msg && msg.session_id) {
927
- this.captureSessionId(msg.session_id);
928
- }
929
- const meta = msg.compact_metadata;
930
- logger.system(`Compaction completed (trigger=${meta?.trigger ?? "unknown"}, pre_tokens=${meta?.pre_tokens ?? "?"})`);
931
- if (typeof meta?.pre_tokens === "number") {
932
- this.capturePostCompactionSnapshot(meta.pre_tokens);
933
- }
934
- else if (this.lastUsageSnapshot) {
935
- // No token data available, but still reset the image count
936
- // so maybeCompact() doesn't re-trigger on the stale value.
937
- this.captureUsageSnapshot({
938
- ...this.lastUsageSnapshot,
939
- allImageBlocks: 0,
940
- });
941
- }
942
- if (!isSilent) {
943
- sendCompactNotice("Brain cleanup done! Ready to continue.");
944
- }
945
- // Trim the JSONL transcript to remove pre-compaction content.
946
- // This keeps the live file small for faster resumes and pruning.
947
- const tp = this.transcriptPath;
948
- if (tp) {
949
- try {
950
- const archivePath = sessionTrimmer.deriveArchivePath(tp);
951
- await sessionTrimmer.trimTranscriptAfterCompaction({
952
- transcriptPath: tp,
953
- archivePath,
954
- });
955
- }
956
- catch (trimErr) {
957
- logger.warn(`Post-compaction JSONL trim failed: ${trimErr instanceof Error ? trimErr.message : String(trimErr)}`);
958
- }
959
- }
960
- }
961
- }
962
- }
963
- finally {
964
- clearTimeout(compactTimer);
965
- }
966
- }
967
- catch (err) {
968
- logger.warn(`Request /compact failed: ${err instanceof Error ? err.message : String(err)}`);
969
- }
970
- finally {
971
- this.compactInFlight = false;
972
- try {
973
- getAgentState().compacting = false;
974
- }
975
- catch {
976
- // Agent state not yet initialized
977
- }
978
- }
979
- }
980
- captureUsageSnapshot(snapshot) {
981
- // Merge with previous snapshot so callers that only update token data
982
- // don't clobber allImageBlocks, and vice-versa.
983
- this.lastUsageSnapshot = {
984
- ...this.lastUsageSnapshot,
985
- ...snapshot,
986
- capturedAtMs: snapshot.capturedAtMs ?? Date.now(),
987
- };
988
- saveUsageSnapshot(this.config, this.lastUsageSnapshot, this.mode);
989
- }
990
- /**
991
- * Update the usage snapshot after the SDK performs mid-query auto-compaction.
992
- * Uses `pre_tokens` (the post-compaction token count) and the last known
993
- * context window to compute an accurate `usedPct`, preventing the post-query
994
- * `maybeCompact` from triggering a redundant compaction.
995
- */
996
- capturePostCompactionSnapshot(postCompactionTokens) {
997
- const contextWindow = this.lastUsageSnapshot?.contextWindow ?? 0;
998
- if (contextWindow <= 0 || postCompactionTokens < 0) {
999
- this.lastUsageSnapshot = null;
1000
- return;
1001
- }
1002
- this.captureUsageSnapshot({
1003
- usedInputTokens: postCompactionTokens,
1004
- contextWindow,
1005
- usedPct: postCompactionTokens / contextWindow,
1006
- allImageBlocks: 0,
1007
- });
1008
- }
1009
- /**
1010
- * Unified between-turn compaction gate. Triggers compaction when **either**:
1011
- * 1. Token usage exceeds the context-window threshold, OR
1012
- * 2. Total image blocks (real + placeholder) in the transcript reach
1013
- * `imageThreshold`, approaching Claude API's 100-image limit.
1014
- *
1015
- * Both triggers share the same cooldown and in-flight guard so we never
1016
- * run two back-to-back compactions in the same between-turn pass.
1017
- */
1018
- async maybeCompact(options) {
1019
- const explicitUsedPctThreshold = options?.usedPctThreshold;
1020
- const cooldownMs = options?.cooldownMs ?? 10 * 60 * 1000;
1021
- const imageThreshold = options?.imageThreshold ?? IMAGE_COMPACT_THRESHOLD;
1022
- // --- guard: in-flight ---
1023
- if (this.compactInFlight) {
1024
- logger.debug("[compact] skip: compaction already in flight");
1025
- return;
1026
- }
1027
- // --- guard: cooldown ---
1028
- const nowMs = Date.now();
1029
- const sinceLastCompactMs = nowMs - this.lastCompactRequestAtMs;
1030
- if (sinceLastCompactMs <= cooldownMs) {
1031
- logger.debug(`[compact] skip: cooldown active sinceLast=${sinceLastCompactMs}ms cooldown=${cooldownMs}ms`);
1032
- return;
1033
- }
1034
- // --- trigger 1: token usage ---
1035
- const snap = this.lastUsageSnapshot;
1036
- // Caller-supplied override always wins. Otherwise auto-scale by
1037
- // context window, falling back to the large-window default when no
1038
- // snapshot is available yet.
1039
- const effectiveUsedPctThreshold = explicitUsedPctThreshold ??
1040
- (snap
1041
- ? getCompactUsedPctThreshold(snap.contextWindow, COMPACT_LARGE_WINDOW_USED_PCT_THRESHOLD)
1042
- : COMPACT_LARGE_WINDOW_USED_PCT_THRESHOLD);
1043
- let tokenTrigger = false;
1044
- if (snap) {
1045
- const snapshotAgeMs = nowMs - (snap.capturedAtMs ?? 0);
1046
- logger.debug(`[compact] check: usedPct=${(snap.usedPct * 100).toFixed(1)}% threshold=${(effectiveUsedPctThreshold * 100).toFixed(1)}% used=${snap.usedInputTokens} window=${snap.contextWindow} snapshotAge=${snapshotAgeMs}ms`);
1047
- tokenTrigger = snap.contextWindow > 0 && snap.usedPct >= effectiveUsedPctThreshold;
1048
- }
1049
- // --- trigger 2: image count ---
1050
- const allImages = snap?.allImageBlocks ?? 0;
1051
- const imageTrigger = allImages >= imageThreshold;
1052
- if (!tokenTrigger && !imageTrigger) {
1053
- if (snap) {
1054
- logger.debug(`[compact] skip: under threshold (usedPct=${(snap.usedPct * 100).toFixed(1)}% threshold=${(effectiveUsedPctThreshold * 100).toFixed(1)}%, images=${allImages}/${imageThreshold})`);
1055
- }
1056
- else {
1057
- logger.debug(`[compact] skip: no usage snapshot yet (images=${allImages}/${imageThreshold})`);
1058
- }
1059
- return;
1060
- }
1061
- const reason = [tokenTrigger && "tokens", imageTrigger && `images(${allImages}>=${imageThreshold})`]
1062
- .filter(Boolean)
1063
- .join("+");
1064
- logger.system(`[compact] requesting compaction (reason=${reason})`);
1065
- this.lastCompactRequestAtMs = nowMs;
1066
- await this.requestCompaction();
1067
- }
1068
- getTranscriptPath() {
1069
- return this.transcriptPath;
1070
- }
1071
- /**
1072
- * Derive the transcript path from the session ID and config dir, even before
1073
- * the SDK subprocess has started and reported it via hooks.
1074
- */
1075
- deriveTranscriptPath(sessionId) {
1076
- const dir = getClaudeTranscriptDirForProfileDir(getConfigDir());
1077
- return path.join(dir, `${sessionId}.jsonl`);
1078
- }
1079
- getUsageSnapshot() {
1080
- return this.lastUsageSnapshot ? { ...this.lastUsageSnapshot } : null;
1081
- }
1082
- /** Returns the live Query object, or null if no query is active. */
1083
- getCurrentQuery() {
1084
- return this.currentQuery;
1085
- }
1086
- /**
1087
- * Validation adapter around `Query.getContextUsage()` (SDK 0.2.114+).
1088
- *
1089
- * The SDK's `Query` interface exposes `getContextUsage()` as a control
1090
- * request over the streaming bridge. It returns a rich breakdown:
1091
- *
1092
- * ```ts
1093
- * {
1094
- * categories: { name: string; tokens: number; color: string; isDeferred?: boolean }[];
1095
- * totalTokens: number;
1096
- * maxTokens: number; // context window for the *active* model
1097
- * rawMaxTokens: number; // nominal max before any reductions
1098
- * percentage: number; // UI percentage (0..100)
1099
- * gridRows: ...[]; // TUI-only
1100
- * model: string;
1101
- * memoryFiles: { path: string; type: string; tokens: number }[];
1102
- * mcpTools: { name: string; serverName: string; tokens: number }[];
1103
- * }
1104
- * ```
1105
- *
1106
- * We map it onto our existing {@link UsageSnapshot} shape:
1107
- * - `usedInputTokens` ← `totalTokens`
1108
- * - `contextWindow` ← `maxTokens` (post-reduction; matches what the
1109
- * model actually has to work with right now)
1110
- * - `usedPct` ← computed from the two above, NOT from the
1111
- * SDK's `percentage` field. The SDK's value is
1112
- * UI-scale (0..100); our consumers expect 0..1.
1113
- * Computing locally keeps both fields internally
1114
- * consistent.
1115
- * - `allImageBlocks` — intentionally preserved from any existing
1116
- * snapshot; `getContextUsage` doesn't know about
1117
- * raw image-block counts in the transcript, so
1118
- * we don't clobber the value that
1119
- * {@link captureUsageSnapshot} maintains.
1120
- *
1121
- * This is purely a read adapter — no callers in compaction or the
1122
- * wake loop should depend on it yet. It exists to let us sanity-check
1123
- * the live SDK response before wiring any behavior changes around it.
1124
- * Returns null when no live query is active, when the method is absent
1125
- * on the active Query instance (e.g. an older CLI), or when the call
1126
- * errors transiently.
1127
- *
1128
- * TODO: we should update snapshot based on this.
1129
- */
1130
- async getLiveContextUsage() {
1131
- const q = this.currentQuery;
1132
- if (!q) {
1133
- logger.debug("[getLiveContextUsage] no active query");
1134
- return null;
1135
- }
1136
- // The typed interface declares the method, but runtime code may
1137
- // predate it if the user pins an older CLI via
1138
- // `pathToClaudeCodeExecutable`. Probe defensively.
1139
- const candidate = q.getContextUsage;
1140
- if (typeof candidate !== "function") {
1141
- logger.debug("[getLiveContextUsage] query.getContextUsage unavailable");
1142
- return null;
1143
- }
1144
- try {
1145
- const raw = (await candidate.call(q));
1146
- logger.debug(`[getContextUsage] raw: totalTokens=${String(raw.totalTokens)} maxTokens=${String(raw.maxTokens)}`);
1147
- const totalTokens = typeof raw.totalTokens === "number" ? raw.totalTokens : NaN;
1148
- const maxTokens = typeof raw.maxTokens === "number" ? raw.maxTokens : NaN;
1149
- if (!Number.isFinite(totalTokens) || !Number.isFinite(maxTokens) || maxTokens <= 0) {
1150
- logger.debug(`[getLiveContextUsage] unexpected shape: totalTokens=${String(raw.totalTokens)} maxTokens=${String(raw.maxTokens)}`);
1151
- return null;
1152
- }
1153
- const usage = {
1154
- usedInputTokens: totalTokens,
1155
- contextWindow: maxTokens,
1156
- usedPct: totalTokens / maxTokens,
1157
- allImageBlocks: this.lastUsageSnapshot?.allImageBlocks,
1158
- capturedAtMs: Date.now(),
1159
- };
1160
- logger.debug(`[getLiveContextUsage] snapshot: usedPct=${(usage.usedPct * 100).toFixed(1)}% used=${usage.usedInputTokens} window=${usage.contextWindow}`);
1161
- return usage;
1162
- }
1163
- catch (err) {
1164
- logger.debug(`[getLiveContextUsage] call failed: ${err instanceof Error ? err.message : String(err)}`);
1165
- return null;
1166
- }
1167
- }
1168
- /** Replace the full set of dynamic MCP servers (used when loading from disk). */
1169
- setDynamicMcpServers(servers) {
1170
- this.dynamicMcpServers = { ...servers };
1171
- }
1172
- /** Get the current dynamic MCP servers record. */
1173
- getDynamicMcpServers() {
1174
- return { ...this.dynamicMcpServers };
1175
- }
1176
- }
1177
- //# sourceMappingURL=session.js.map