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,1066 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { Agent, OpenAIResponsesCompactionSession, Runner, setTracingDisabled } from "@openai/agents";
4
- import { getOpenAISessionsDir, loadSessionId, loadUsageSnapshot, saveSessionId, saveUsageSnapshot, } from "../../../config/index.js";
5
- import { logger } from "../../../logger.js";
6
- import { configureOpenAIProvider, createOpenAIClient, getModelId } from "../client-factory.js";
7
- import { logSystemPrompt } from "../system-prompt-log.js";
8
- import { OpenAIFileSession } from "./file-session.js";
9
- import { createOpenAIMcpServers, createOpenAITools } from "./tools.js";
10
- const MAX_TURNS = 100;
11
- // GPT-5.x family currently advertises a 1M-token context window.
12
- // Used to compute usage `usedPct` for the OpenAI engine; keeping this
13
- // in one place so usage rendering and compaction gates agree.
14
- const OPENAI_CONTEXT_WINDOW = 1_000_000;
15
- // Used-token threshold (fraction of context window) at which
16
- // {@link OpenAIAgentSession.maybeCompact} triggers a compaction. Calibrated
17
- // for the 1M-token window: 70% leaves comfortable headroom for the next
18
- // turn's input + tool results without compacting too eagerly. Configurable
19
- // per call via the `usedPctThreshold` option to `maybeCompact()`.
20
- const OPENAI_COMPACT_USED_PCT_THRESHOLD = 0.7;
21
- export class OpenAIAgentSession {
22
- config;
23
- buildSystemPrompt;
24
- runtimeSurface;
25
- getDualSessionEnabled;
26
- getAndroidUseEnabled;
27
- dynamicMcpServers;
28
- client;
29
- runner;
30
- // The session id is invariant across compactions, so the underlying
31
- // file-backed session and its compaction wrapper are constructed once
32
- // in the constructor and never replaced.
33
- underlyingSession;
34
- session;
35
- abortController = null;
36
- inputClosed = true;
37
- stopRequested = false;
38
- lastUsageSnapshot = null;
39
- currentRun = null;
40
- sessionId;
41
- // Compaction state — mirrors Claude's session. `compactInFlight` blocks
42
- // re-entrancy; `lastCompactRequestAtMs` enforces a cooldown so we don't
43
- // hammer compaction on every heartbeat once usage crosses the threshold.
44
- compactInFlight = false;
45
- lastCompactRequestAtMs = 0;
46
- // OpenAI engine mid-run injection state.
47
- //
48
- // The OpenAI Agents SDK does not expose a mid-run input channel like the
49
- // Claude SDK does, so we approximate it: when a message is injected while
50
- // a run is active we *abort* the run at the next safe SDK boundary (a
51
- // `tool_output` event — the tool call+result pair has already been
52
- // persisted to the session by then) and immediately start a follow-up run
53
- // that feeds the queued items as the next turn's input. The same
54
- // `session` is reused so the model continues from the existing transcript
55
- // without paying any cold-start tax.
56
- //
57
- // - `pendingInjections`: items to feed into the next run.
58
- // - `interruptArmed`: set by `injectMessage` while a run is live; the
59
- // stream loop checks this after each event and triggers the abort.
60
- // - `resumeRequested`: tells the resume loop to loop one more time after
61
- // the current run terminates (either via our abort or naturally).
62
- pendingInjections = [];
63
- interruptArmed = false;
64
- resumeRequested = false;
65
- finishDetected = false;
66
- mode;
67
- constructor({ config, buildSystemPrompt, runtimeSurface, sessionContext, }) {
68
- configureOpenAIProvider(config);
69
- setTracingDisabled(true);
70
- this.config = config;
71
- this.buildSystemPrompt = buildSystemPrompt;
72
- this.runtimeSurface = runtimeSurface;
73
- this.getDualSessionEnabled = sessionContext.getDualSessionEnabled;
74
- this.getAndroidUseEnabled = sessionContext.getAndroidUseEnabled;
75
- this.dynamicMcpServers = {};
76
- this.mode = sessionContext.mode;
77
- this.client = createOpenAIClient(config);
78
- this.runner = new Runner({ model: getModelId(config) });
79
- const persistedSessionId = loadSessionId(this.config, this.mode);
80
- this.underlyingSession = new OpenAIFileSession({
81
- sessionId: persistedSessionId,
82
- });
83
- this.sessionId = this.underlyingSession.getSessionIdSync();
84
- saveSessionId(this.config, this.sessionId, this.mode);
85
- this.session = this.buildCompactionSession(this.underlyingSession);
86
- const persistedUsage = loadUsageSnapshot(this.config, this.mode);
87
- if (persistedUsage) {
88
- this.lastUsageSnapshot = {
89
- ...persistedUsage,
90
- capturedAtMs: Date.now(),
91
- };
92
- }
93
- }
94
- async warmUp() {
95
- // OpenAI sessions do not have an equivalent subprocess cold-start path.
96
- }
97
- discardWarmQuery() {
98
- // OpenAI sessions hold no pre-warmed subprocess; nothing to discard.
99
- }
100
- sendAndStream(content, _options) {
101
- // Log the resolved system prompt for training data capture. The console
102
- // gets a compact head/tail preview; the full prompt goes into `data.prompt`
103
- // of the JSONL entry so the log file remains complete.
104
- const systemPrompt = this.buildSystemPrompt();
105
- logSystemPrompt(systemPrompt);
106
- const input = contentBlocksToInputItems(content);
107
- this.inputClosed = false;
108
- this.stopRequested = false;
109
- this.pendingInjections = [];
110
- this.interruptArmed = false;
111
- this.resumeRequested = false;
112
- this.finishDetected = false;
113
- return this.streamMessages(input, this.sessionId);
114
- }
115
- /**
116
- * Queue `content` as the next turn's input.
117
- *
118
- * If a run is active, this also *arms a soft interrupt*: the streaming
119
- * loop in `streamMessages` watches for a safe SDK boundary (a
120
- * `tool_output` event — at which point the tool call + tool result are
121
- * already persisted to the session) and aborts the in-flight run as soon
122
- * as one arrives. The resume loop then immediately starts a follow-up
123
- * `runner.run()` with the queued items as input, against the same
124
- * session, so the model perceives the new user message just like it
125
- * would in the Claude path.
126
- *
127
- * If no run is active, the items sit in the queue until the next
128
- * `sendAndStream` consumes them (or, if the session is already torn
129
- * down, are reported as orphans by `hasOrphanedInjections`).
130
- *
131
- * Returns `true` whenever the message has been accepted (queued). Returns
132
- * `false` only when the session is fully closed and no run is or will be
133
- * active — in that case the caller should re-queue the message for the
134
- * next wake cycle.
135
- */
136
- injectMessage(content, _options) {
137
- if (this.inputClosed && !this.abortController && !this.resumeRequested) {
138
- return false;
139
- }
140
- const items = contentBlocksToInputItems(content);
141
- this.pendingInjections.push(...items);
142
- if (this.abortController) {
143
- // A run is live — let the stream loop tear it down at the next safe
144
- // boundary so the queued items are picked up immediately rather than
145
- // waiting for the model to finish its current chain of tool calls.
146
- this.interruptArmed = true;
147
- this.resumeRequested = true;
148
- }
149
- return true;
150
- }
151
- /**
152
- * Mark the session as no longer accepting fresh `sendAndStream` invocations
153
- * and stop the resume loop from looping into another `runner.run()`.
154
- *
155
- * **We deliberately do NOT clear `pendingInjections` here.**
156
- *
157
- * `closeInput()` is called by the stream handler from many places, including
158
- * mid-stream when it observes the `finish` tool block. At that point the
159
- * resume loop hasn't yet had a chance to consume any messages that
160
- * `injectMessage()` queued during the run. Dropping the buffer here would
161
- * silently lose those user messages — exactly the bug we hit when an
162
- * interrupt landed during a memory-view turn that ended in `finish`.
163
- *
164
- * Instead, leftover messages stay in `pendingInjections` until either:
165
- * - the resume loop picks them up (when it's still allowed to run), or
166
- * - the wake-cycle runner sees `hasOrphanedInjections` and re-queues the
167
- * original `CommandMessage` batch for the next wake cycle.
168
- *
169
- * Hard teardowns that really do want the buffer wiped (e.g. `/stop`,
170
- * watchdog timeout, owner-initiated shutdown) call `requestStop()` which
171
- * clears it explicitly.
172
- */
173
- /**
174
- * Mark the session as no longer accepting fresh `sendAndStream` invocations
175
- * and stop the resume loop from looping into another `runner.run()`.
176
- *
177
- * **We deliberately do NOT clear `pendingInjections` here.**
178
- *
179
- * `closeInput()` is called by the stream handler from many places, including
180
- * mid-stream when it observes the `finish` tool block. At that point the
181
- * resume loop hasn't yet had a chance to consume any messages that
182
- * `injectMessage()` queued during the run. Dropping the buffer here would
183
- * silently lose those user messages — exactly the bug we hit when an
184
- * interrupt landed during a memory-view turn that ended in `finish`.
185
- *
186
- * Instead, leftover messages stay in `pendingInjections` until either:
187
- * - the resume loop picks them up (when it's still allowed to run), or
188
- * - the wake-cycle runner sees `hasOrphanedInjections` and re-queues the
189
- * original `CommandMessage` batch for the next wake cycle.
190
- *
191
- * Hard teardowns that really do want the buffer wiped (e.g. `/stop`,
192
- * watchdog timeout, owner-initiated shutdown) call `requestStop()` which
193
- * clears it explicitly.
194
- */
195
- closeInput() {
196
- this.inputClosed = true;
197
- }
198
- /**
199
- * Signal that the agent has called the `finish` tool. The stream loop
200
- * will abort the run at the next safe `tool_output` boundary (where
201
- * `stream.rawResponses` is already populated) instead of waiting for
202
- * `stream.completed` — which can idle for 30s+ in the OpenAI SDK.
203
- */
204
- signalFinish() {
205
- this.finishDetected = true;
206
- }
207
- requestStop() {
208
- this.stopRequested = true;
209
- this.inputClosed = true;
210
- this.pendingInjections = [];
211
- this.interruptArmed = false;
212
- this.resumeRequested = false;
213
- this.abortController?.abort();
214
- }
215
- clearStop() {
216
- this.stopRequested = false;
217
- this.abortController = null;
218
- }
219
- isStopRequested() {
220
- return this.stopRequested;
221
- }
222
- /**
223
- * Update the persisted session ID.
224
- *
225
- * Only saves on first capture (null/empty → id). Once set, a different ID
226
- * from the SDK is ignored — it means the resume failed and the SDK created
227
- * a throwaway conversation.
228
- */
229
- captureSessionId(id) {
230
- if (!id || this.sessionId)
231
- return;
232
- this.sessionId = id;
233
- saveSessionId(this.config, id, this.mode);
234
- }
235
- captureUsageSnapshot(snapshot) {
236
- const normalizedSnapshot = normalizeOpenAIUsageSnapshot(snapshot);
237
- this.lastUsageSnapshot = {
238
- ...normalizedSnapshot,
239
- capturedAtMs: Date.now(),
240
- };
241
- saveUsageSnapshot(this.config, normalizedSnapshot, this.mode);
242
- }
243
- capturePostCompactionSnapshot(_postCompactionTokens) {
244
- // OpenAI compaction is handled by the SDK session decorator.
245
- }
246
- /**
247
- * Force the OpenAI Responses API compaction path, archiving the
248
- * pre-compaction transcript to disk for audit/debugging.
249
- *
250
- * **The session id is invariant across compactions**, matching the
251
- * Claude engine's behavior (see `captureSessionId` in the Claude
252
- * session — it ignores the SDK's post-`/compact` `session_id` echo
253
- * because the id "should remain the same"). Keeping it stable means:
254
- *
255
- * - `session.json` never needs rewriting on compaction.
256
- * - The active transcript file path (`<sessionId>.json`) is also
257
- * stable, so no in-memory references (`this.underlyingSession`,
258
- * `this.session`) need to be rebuilt.
259
- * - Only the **contents** of the transcript change; the SDK's
260
- * `runCompaction` rewrites them in place via clearSession + addItems.
261
- *
262
- * Sequence:
263
- *
264
- * 1. Snapshot the active session file to
265
- * `<sessionId>.compacted-<iso>.json` — freezes pre-compaction state.
266
- * 2. `runCompaction({ force: true })` — the SDK clears the underlying
267
- * session and writes the compacted item set into the same file.
268
- *
269
- * If step 1 fails (e.g. the file doesn't exist yet because no items have
270
- * been persisted) we still attempt step 2 — losing the archive is
271
- * preferable to skipping the actual compaction.
272
- */
273
- async requestCompaction() {
274
- const activePath = this.underlyingSession.getFilePath();
275
- const archivePath = this.archivePathForCurrent();
276
- try {
277
- // Step 1: archive pre-compaction state. Non-fatal on failure.
278
- try {
279
- fs.copyFileSync(activePath, archivePath);
280
- }
281
- catch (err) {
282
- logger.warn(`OpenAI session pre-compaction archive failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
283
- }
284
- // Step 2: run compaction in place. The SDK clears the underlying
285
- // session and rewrites it with the compacted items, all under the
286
- // same file (`<sessionId>.json`). Session id, file path, and all
287
- // wrapper references stay valid.
288
- await this.session.runCompaction({ force: true });
289
- logger.system(`OpenAI session compacted (${this.mode}); archive: ${archivePath}`);
290
- }
291
- catch (err) {
292
- logger.warn(`Request /compact failed: ${err instanceof Error ? err.message : String(err)}`);
293
- }
294
- }
295
- archivePathForCurrent() {
296
- const dir = getOpenAISessionsDir();
297
- const safeIso = new Date().toISOString().replace(/:/g, "-");
298
- return path.join(dir, `${this.sessionId}.compacted-${safeIso}.json`);
299
- }
300
- buildCompactionSession(underlying) {
301
- return new OpenAIResponsesCompactionSession({
302
- client: this.client,
303
- underlyingSession: underlying,
304
- model: getModelId(this.config),
305
- // Disable SDK auto-compaction entirely.
306
- //
307
- // The SDK's auto-compaction (default threshold: 10 candidate items)
308
- // calls the OpenAI Responses `responses.compact` API, which returns
309
- // a single opaque `compaction` item containing only:
310
- // { id, type: "compaction", encrypted_content: "<opaque blob>" }
311
- //
312
- // It then *clears the underlying session and writes back only this
313
- // blob* (see OpenAIResponsesCompactionSession.runCompaction at line
314
- // 90 of openaiResponsesCompactionSession.mjs). That destroys our
315
- // human-readable transcript, breaks the transcript indexer, and
316
- // makes session history unrecoverable if we ever switch providers.
317
- //
318
- // It also doesn't create an archive snapshot — unlike
319
- // `requestCompaction()` below which copies the active session file
320
- // to `<sessionId>.compacted-<iso>.json` before invoking the same
321
- // `runCompaction` API.
322
- //
323
- // Compaction is therefore driven exclusively by `maybeCompact()`
324
- // (called from the wake-cycle runner on heartbeat), which:
325
- // 1. Checks token usage against a percentage threshold.
326
- // 2. Archives the pre-compaction file.
327
- // 3. Calls `runCompaction({ force: true })` so the SDK still
328
- // replaces the session contents — but only when *we* decide.
329
- shouldTriggerCompaction: () => false,
330
- });
331
- }
332
- /**
333
- * Token-usage-driven compaction trigger, called from the wake-cycle
334
- * runner on heartbeat.
335
- *
336
- * Mirrors Claude's behavior: re-entrancy guard + cooldown + threshold
337
- * check, then delegates to {@link requestCompaction} which archives
338
- * and runs the actual SDK compaction. We deliberately do NOT use the
339
- * SDK's auto-compaction path (`shouldTriggerCompaction`) because it
340
- * compacts purely by item count and skips the archive — see the
341
- * comment on {@link buildCompactionSession}.
342
- *
343
- * The default threshold (70%) is calibrated for the GPT-5.x 1M-token
344
- * context window. Lower if you want more aggressive trimming, raise
345
- * if cross-turn context fidelity matters more than latency.
346
- */
347
- async maybeCompact(options) {
348
- const usedPctThreshold = options?.usedPctThreshold ?? OPENAI_COMPACT_USED_PCT_THRESHOLD;
349
- const cooldownMs = options?.cooldownMs ?? 10 * 60 * 1000;
350
- // --- guard: in-flight ---
351
- if (this.compactInFlight) {
352
- logger.debug("[compact] skip: compaction already in flight");
353
- return;
354
- }
355
- // --- guard: cooldown ---
356
- const nowMs = Date.now();
357
- const sinceLastCompactMs = nowMs - this.lastCompactRequestAtMs;
358
- if (sinceLastCompactMs <= cooldownMs) {
359
- logger.debug(`[compact] skip: cooldown active sinceLast=${sinceLastCompactMs}ms cooldown=${cooldownMs}ms`);
360
- return;
361
- }
362
- // --- trigger: token usage ---
363
- const snap = this.lastUsageSnapshot;
364
- if (!snap || snap.contextWindow <= 0) {
365
- logger.debug("[compact] skip: no usage snapshot yet");
366
- return;
367
- }
368
- if (snap.usedPct < usedPctThreshold) {
369
- logger.debug(`[compact] skip: under threshold (usedPct=${(snap.usedPct * 100).toFixed(1)}% threshold=${(usedPctThreshold * 100).toFixed(1)}%)`);
370
- return;
371
- }
372
- logger.system(`[compact] requesting compaction (usedPct=${(snap.usedPct * 100).toFixed(1)}% threshold=${(usedPctThreshold * 100).toFixed(1)}%)`);
373
- this.compactInFlight = true;
374
- this.lastCompactRequestAtMs = nowMs;
375
- try {
376
- await this.requestCompaction();
377
- }
378
- finally {
379
- this.compactInFlight = false;
380
- }
381
- }
382
- getSessionId() {
383
- return this.sessionId;
384
- }
385
- getTranscriptPath() {
386
- return this.underlyingSession.getFilePath();
387
- }
388
- getTranscriptItems() {
389
- return this.underlyingSession.getAllItemsSync();
390
- }
391
- getUsageSnapshot() {
392
- return this.lastUsageSnapshot ? { ...this.lastUsageSnapshot } : null;
393
- }
394
- getCurrentQuery() {
395
- return this.currentRun;
396
- }
397
- setDynamicMcpServers(servers) {
398
- this.dynamicMcpServers = { ...servers };
399
- }
400
- getDynamicMcpServers() {
401
- return { ...this.dynamicMcpServers };
402
- }
403
- get isInputClosed() {
404
- // While a run is active (abortController set) or about to resume after a
405
- // soft-interrupt, the session is still accepting injected messages.
406
- if (this.abortController || this.resumeRequested) {
407
- return false;
408
- }
409
- return this.inputClosed;
410
- }
411
- /**
412
- * True when injected messages have been queued but the session is no
413
- * longer running. The agent loop checks this after `streamMessages`
414
- * finishes to re-queue messages instead of dropping them. We deliberately
415
- * ignore `resumeRequested` here: if `pendingInjections` is non-empty and
416
- * there is no active abortController, the messages are by definition
417
- * orphaned regardless of any stale resume flag.
418
- */
419
- get hasOrphanedInjections() {
420
- return (this.pendingInjections.length > 0
421
- && !this.abortController);
422
- }
423
- async *streamMessages(initialInput, sessionId) {
424
- const mcpServers = createOpenAIMcpServers({
425
- ...this.runtimeSurface.externalMcpServers,
426
- ...this.dynamicMcpServers,
427
- });
428
- let lastStream = null;
429
- let nextInput = initialInput;
430
- try {
431
- await connectMcpServers(mcpServers);
432
- const tools = createOpenAITools({
433
- dualSession: this.getDualSessionEnabled(),
434
- androidUse: this.getAndroidUseEnabled(),
435
- mode: this.mode,
436
- runtimeSurface: this.runtimeSurface,
437
- });
438
- logOpenAIToolInventory({
439
- mode: this.mode,
440
- tools,
441
- mcpServers,
442
- runtimeSurface: this.runtimeSurface,
443
- });
444
- const agent = new Agent({
445
- name: this.mode === "coding" ? `${this.config.agentName} Coding` : this.config.agentName,
446
- instructions: renderSystemPrompt(this.buildSystemPrompt()),
447
- model: getModelId(this.config),
448
- tools,
449
- mcpServers,
450
- // Reasoning model settings.
451
- //
452
- // `summary: "auto"` asks the Responses API to return a human-readable
453
- // summary of the model's reasoning. Without this the API returns
454
- // reasoning items with empty `summary` arrays — the model still pays
455
- // the reasoning tokens, we just can't see what it thought about. The
456
- // summary is for our debug visibility (logs, /obs).
457
- //
458
- // `providerData.include = ["reasoning.encrypted_content"]` asks the
459
- // API to return the opaque encrypted reasoning blob alongside each
460
- // reasoning item. The SDK persists that blob into the session and
461
- // replays it on subsequent turns, so the model can pick up the same
462
- // reasoning chain instead of re-deriving it from scratch each turn.
463
- // This matters a lot for VisionClaw because every wake cycle starts
464
- // a fresh `Runner.run()` — without the encrypted blob, the model
465
- // re-thinks everything from the visible artifacts (tool calls /
466
- // results / messages) instead of inheriting prior chain-of-thought.
467
- //
468
- // NOTE: In `openaiResponsesModel.mjs` (~line 2184) `providerData` is
469
- // spread into the request body AFTER the auto-built `include` array
470
- // (~line 2163), which means setting `providerData.include` overwrites
471
- // it. That's safe for our tool mix — the SDK's auto-includes only
472
- // fire for hosted search tools with `include_search_results`, which
473
- // we do not use. If we later add such tools, we'll need to merge.
474
- modelSettings: {
475
- reasoning: { summary: "auto" },
476
- providerData: {
477
- include: ["reasoning.encrypted_content"],
478
- },
479
- },
480
- });
481
- // Outer resume loop: each iteration runs one `runner.run()` to either
482
- // a natural end-of-turn or a *soft-interrupt abort* — i.e. a
483
- // self-initiated abort fired at the next safe SDK boundary
484
- // (`tool_output`) when `injectMessage()` queued items mid-run. After
485
- // the run terminates, if there are queued injections, we loop and
486
- // start a new run with those items as input, reusing the same
487
- // `agent`, `session`, and `mcpServers` so the model continues from
488
- // the persisted transcript without paying any cold-start tax.
489
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- intentional: loop terminates via `break` after stream completion or non-resume exit
490
- while (true) {
491
- this.abortController = new AbortController();
492
- // Don't reset `resumeRequested` here — `injectMessage()` may have
493
- // already set it (along with `interruptArmed`) before we entered
494
- // this iteration, and we need to preserve that signal.
495
- let interruptedThisRun = false;
496
- let finishAbortedThisRun = false;
497
- const stream = await this.runner.run(agent, nextInput, {
498
- stream: true,
499
- session: this.session,
500
- maxTurns: MAX_TURNS,
501
- signal: this.abortController.signal,
502
- });
503
- lastStream = stream;
504
- this.currentRun = null;
505
- try {
506
- for await (const event of stream) {
507
- const normalized = normalizeRunEvent(event, sessionId);
508
- if (normalized) {
509
- yield normalized;
510
- }
511
- const usage = captureUsageFromEvent(event);
512
- if (usage) {
513
- this.captureUsageSnapshot(usage);
514
- }
515
- // Soft-interrupt: a message was injected while this run was
516
- // streaming. We tear the run down at the next safe boundary
517
- // (after `tool_output`, when the SDK has already persisted the
518
- // call+result pair to the session) so the queued items can be
519
- // delivered as the next turn's input rather than waiting for
520
- // the model to finish its current chain of tool calls.
521
- if (this.interruptArmed
522
- && this.pendingInjections.length > 0
523
- && !this.stopRequested
524
- && isSafeInterruptBoundary(event)) {
525
- interruptedThisRun = true;
526
- this.interruptArmed = false;
527
- // Persist work-in-progress items before aborting. Without
528
- // this, every tool call the agent already executed during
529
- // this run (memory views, screenshots, notify_user, etc.)
530
- // is dropped from the session file and the continuation
531
- // run sees only user wake events. See the longer comment
532
- // on the finish-abort branch below for the SDK trace.
533
- try {
534
- await persistRunItemsForAbort(stream, this.session);
535
- }
536
- catch (err) {
537
- logger.warn(`OpenAI session soft-interrupt: manual persistence failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
538
- }
539
- logger.system(`OpenAI session soft-interrupt: aborting run at ${event.type}/${"name" in event ? event.name : ""}; ${this.pendingInjections.length} queued message(s) will resume.`);
540
- this.abortController.abort();
541
- break;
542
- }
543
- // Finish-abort: the agent called the `finish` tool and the
544
- // tool result has been persisted (safe boundary). Abort early
545
- // instead of waiting for the SDK's stream.completed (~30s).
546
- //
547
- // IMPORTANT: We must manually persist the run items here.
548
- // The SDK only persists assistant/tool items at
549
- // `next_step_final_output` / `next_step_interruption`
550
- // (`run.mjs` lines 714/723). Aborting at `tool_output` is
551
- // earlier in the loop, so the SDK's stream-loop catches our
552
- // AbortError, calls `tryHandleRunError` which only handles
553
- // `MaxTurnsExceededError`, then rethrows — bypassing
554
- // `saveStreamResultToSession`. The result is that wake-cycle
555
- // user input is persisted (via the early `persistStreamInputIfNeeded`
556
- // path) but every assistant message, tool call, and tool
557
- // result generated this turn is lost from the session file.
558
- // Subsequent wake cycles then see a session full of user
559
- // wake events with no assistant context, which manifests as
560
- // the model repeating itself or losing state across turns.
561
- //
562
- // To preserve the speed benefit of finish-abort while not
563
- // shedding history, we replicate the SDK's persistence
564
- // payload manually before aborting: extract the raw items
565
- // from `stream.newItems` and append them via the same
566
- // `session.addItems` call the SDK would have made. The items
567
- // already include the finish tool's call+result (we're at
568
- // the `tool_output` boundary), all preceding tool calls,
569
- // any assistant messages, and reasoning items.
570
- if (this.finishDetected
571
- && !this.stopRequested
572
- && isSafeInterruptBoundary(event)) {
573
- finishAbortedThisRun = true;
574
- this.finishDetected = false;
575
- try {
576
- await persistRunItemsForAbort(stream, this.session);
577
- }
578
- catch (err) {
579
- logger.warn(`OpenAI session finish-abort: manual persistence failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
580
- }
581
- logger.system(`OpenAI session finish-abort: skipping stream.completed at ${event.type}/${"name" in event ? event.name : ""}`);
582
- this.abortController.abort();
583
- break;
584
- }
585
- }
586
- if (!interruptedThisRun && !finishAbortedThisRun) {
587
- await stream.completed;
588
- }
589
- }
590
- catch (err) {
591
- if (interruptedThisRun && isAbortError(err)) {
592
- // Expected: our own soft-interrupt aborted the run. Fall
593
- // through to the resume check below.
594
- }
595
- else if (finishAbortedThisRun && isAbortError(err)) {
596
- // Expected: our own finish-abort cut the run short. Fall
597
- // through to yield the result message immediately.
598
- }
599
- else if (this.stopRequested && isAbortError(err)) {
600
- // Expected: caller invoked requestStop() — treat as a clean
601
- // termination, not as a session error.
602
- logger.system("OpenAI session: run aborted by requestStop.");
603
- }
604
- else {
605
- throw err;
606
- }
607
- }
608
- const usage = extractUsageFromStream(stream);
609
- if (usage) {
610
- this.captureUsageSnapshot(usage);
611
- }
612
- // resumeRequested is mutated by injectMessage() while the stream is awaited above.
613
- if (this.resumeRequested && this.pendingInjections.length > 0 && !this.stopRequested) {
614
- // Hand the queued injections to the next run and loop. The previous
615
- // run reached a safe SDK boundary (either via soft-interrupt abort
616
- // or natural completion), so its session persistence is complete
617
- // before this follow-up turn starts.
618
- nextInput = this.pendingInjections;
619
- this.pendingInjections = [];
620
- this.resumeRequested = false;
621
- this.interruptArmed = false;
622
- this.finishDetected = false;
623
- this.abortController = null;
624
- continue;
625
- }
626
- // Natural end of stream (no resume): emit final result and exit.
627
- yield buildResultMessage(stream, this.config);
628
- break;
629
- }
630
- }
631
- catch (err) {
632
- // Flush any remaining final-result message so the loop sees a clean
633
- // termination event instead of a raw throw.
634
- if (lastStream) {
635
- try {
636
- yield buildResultMessage(lastStream, this.config);
637
- }
638
- catch {
639
- // best-effort
640
- }
641
- }
642
- throw err;
643
- }
644
- finally {
645
- this.inputClosed = true;
646
- this.abortController = null;
647
- this.currentRun = null;
648
- this.interruptArmed = false;
649
- this.resumeRequested = false;
650
- this.finishDetected = false;
651
- await closeMcpServers(mcpServers);
652
- }
653
- }
654
- }
655
- function logOpenAIToolInventory(options) {
656
- const hosted = options.runtimeSurface.nativeTools.kind === "openai-hosted"
657
- ? options.runtimeSurface.nativeTools.hostedTools.map(() => "WebSearch")
658
- : [];
659
- const extensions = options.mode === "coding" ? ["codex"] : [];
660
- const sdkLocal = [];
661
- const directTools = [];
662
- for (const t of options.tools) {
663
- const display = getToolDisplayName(t);
664
- if (!display)
665
- continue;
666
- if (getToolType(t) === "shell" || getToolType(t) === "apply_patch") {
667
- sdkLocal.push(display);
668
- }
669
- else {
670
- directTools.push(display);
671
- }
672
- }
673
- const mcpServerNames = options.mcpServers.map((server) => server.name);
674
- logger.system(`OpenAI tool inventory (${options.mode}): hosted=[${hosted.join(", ") || "none"}] extensions=[${extensions.join(", ") || "none"}] sdkLocal=[${sdkLocal.join(", ") || "none"}] direct=[${directTools.join(", ") || "none"}] mcpServers=[${mcpServerNames.join(", ") || "none"}]`);
675
- }
676
- function getToolDisplayName(toolLike) {
677
- if (!toolLike || typeof toolLike !== "object")
678
- return null;
679
- const record = toolLike;
680
- const name = record.name ?? record.toolName;
681
- return typeof name === "string" && name.length > 0 ? name : null;
682
- }
683
- function getToolType(toolLike) {
684
- if (!toolLike || typeof toolLike !== "object")
685
- return null;
686
- const record = toolLike;
687
- const type = record.type;
688
- return typeof type === "string" && type.length > 0 ? type : null;
689
- }
690
- function isAbortError(err) {
691
- if (!err || typeof err !== "object")
692
- return false;
693
- const candidate = err;
694
- return candidate.name === "AbortError" || candidate.code === "ABORT_ERR";
695
- }
696
- /**
697
- * Whether a stream event marks a safe place to abort the current run.
698
- *
699
- * "Safe" means the runtime state is balanced: any in-flight tool call has
700
- * its matching tool result already produced, so aborting here leaves a
701
- * clean tool_call + tool_call_result pair in the in-memory run state. The
702
- * Responses API would otherwise reject a resume payload that referenced an
703
- * orphan tool call without its result.
704
- *
705
- * IMPORTANT: "safe" here refers only to runtime state, NOT to session file
706
- * persistence. The SDK only writes assistant/tool items to the session at
707
- * `next_step_final_output` / `next_step_interruption` (see
708
- * `runner/sessionPersistence.mjs::persistRunItemsToSession` invoked from
709
- * `run.mjs` lines 714/723). When we abort at `tool_output` we are *before*
710
- * those checkpoints, so the manual persistence helper
711
- * {@link persistRunItemsForAbort} must be called by the caller.
712
- *
713
- * - `run_item_stream_event` / `tool_output`: a tool just finished. This is
714
- * the most common boundary we hit.
715
- * - `agent_updated_stream_event`: a handoff completed and the SDK swapped
716
- * the active agent.
717
- *
718
- * We deliberately do NOT treat raw model deltas (`raw_model_stream_event`)
719
- * as safe — those fire mid-token while a tool call is being constructed,
720
- * before any tool result has been produced.
721
- */
722
- function isSafeInterruptBoundary(event) {
723
- if (event.type === "agent_updated_stream_event") {
724
- return true;
725
- }
726
- if (event.type !== "run_item_stream_event") {
727
- return false;
728
- }
729
- return event.name === "tool_output";
730
- }
731
- /**
732
- * Manually persist the assistant/tool items the SDK has accumulated
733
- * during the current run before we abort it.
734
- *
735
- * This is what `runner/sessionPersistence.mjs::persistRunItemsToSession`
736
- * does internally — but only at `next_step_final_output` /
737
- * `next_step_interruption` (`run.mjs` lines 714/723). Both of our abort
738
- * triggers fire much earlier in the SDK loop:
739
- * - **finish-abort**: at the `run_item_stream_event/tool_output` for
740
- * the `finish` tool's result.
741
- * - **soft-interrupt**: at the same `tool_output` boundary, when a new
742
- * user message has been queued and we want to inject it now instead
743
- * of waiting out the current chain.
744
- *
745
- * In both cases the SDK's stream loop catches our `AbortError`, runs
746
- * `tryHandleRunError` (which only handles `MaxTurnsExceededError` —
747
- * verified in `runner/errorHandlers.mjs` line 25), and rethrows. The
748
- * `await saveStreamResultToSession(...)` call is bypassed entirely, so
749
- * every tool call, tool result, reasoning summary, and assistant
750
- * message produced this turn is lost from the session file. The next
751
- * wake cycle then sees a session full of user wake events with no
752
- * record of the agent's prior work, which manifests as the model
753
- * "forgetting" what it just did or repeating itself.
754
- *
755
- * We replicate the relevant subset of the SDK's persistence logic:
756
- *
757
- * 1. Pull `stream.newItems` (the public read-only run-items collection).
758
- * 2. Filter out `tool_approval_item` (matches the SDK's filter at
759
- * `runner/items.mjs::extractOutputItemsFromRunItems`).
760
- * 3. Map each `RunItem` to its `rawItem` (the persistable
761
- * `AgentInputItem` shape — function_call, function_call_result,
762
- * reasoning, message, etc.).
763
- * 4. Call `session.addItems()` to append. The wrapping
764
- * `OpenAIResponsesCompactionSession` forwards to the underlying
765
- * file-backed session, which writes to disk.
766
- *
767
- * We deliberately do not normalize ids or strip transient call ids the
768
- * way the SDK's `normalizeItemsForSessionPersistence` does — for our
769
- * file-only session those normalizations are mostly server-side concerns
770
- * and the rawItem already passes through cleanly. If we ever observe
771
- * resume-time conflicts on encrypted_content or call_id collisions, we
772
- * can mirror more of `sessionPersistence.mjs::stripTransientCallIds`
773
- * here.
774
- */
775
- async function persistRunItemsForAbort(stream, session) {
776
- const newItems = stream.newItems;
777
- if (!Array.isArray(newItems) || newItems.length === 0) {
778
- return;
779
- }
780
- const rawItems = [];
781
- for (const entry of newItems) {
782
- if (entry === null || typeof entry !== "object")
783
- continue;
784
- const item = entry;
785
- if (item.type === "tool_approval_item")
786
- continue;
787
- const raw = item.rawItem;
788
- if (raw && typeof raw === "object") {
789
- rawItems.push(raw);
790
- }
791
- }
792
- if (rawItems.length === 0) {
793
- return;
794
- }
795
- await session.addItems(rawItems);
796
- logger.debug(`OpenAI session: manually persisted ${rawItems.length} run item(s) before abort`);
797
- }
798
- function renderSystemPrompt(prompt) {
799
- if (typeof prompt === "string") {
800
- return prompt;
801
- }
802
- const presetIntro = "You are in a coding-focused session. Prefer careful, multi-step engineering work.";
803
- return [presetIntro, prompt.append ?? ""].filter(Boolean).join("\n\n");
804
- }
805
- function contentBlocksToInputItems(content) {
806
- const blocks = normalizeInputContent(content);
807
- return [
808
- {
809
- role: "user",
810
- content: blocks.map((block) => {
811
- if (block.type === "text") {
812
- return {
813
- type: "input_text",
814
- text: block.text,
815
- };
816
- }
817
- if (block.source.type === "url") {
818
- return {
819
- type: "input_image",
820
- image: block.source.url,
821
- detail: "original",
822
- };
823
- }
824
- return {
825
- type: "input_image",
826
- image: `data:${block.source.media_type};base64,${block.source.data}`,
827
- detail: "original",
828
- };
829
- }),
830
- },
831
- ];
832
- }
833
- function normalizeInputContent(content) {
834
- if (typeof content === "string") {
835
- return [{ type: "text", text: content }];
836
- }
837
- const blocks = [];
838
- for (const block of content) {
839
- if (block.type === "text") {
840
- blocks.push({ type: "text", text: block.text });
841
- continue;
842
- }
843
- if (block.type === "image") {
844
- if (block.source.type === "url") {
845
- blocks.push({ type: "image", source: { type: "url", url: block.source.url } });
846
- continue;
847
- }
848
- blocks.push({
849
- type: "image",
850
- source: {
851
- type: "base64",
852
- media_type: block.source.media_type,
853
- data: block.source.data,
854
- },
855
- });
856
- }
857
- }
858
- return blocks;
859
- }
860
- export function normalizeOpenAIRunEventForTest(event, sessionId) {
861
- return normalizeRunEvent(event, sessionId);
862
- }
863
- function normalizeRunEvent(event, sessionId) {
864
- if (event.type === "raw_model_stream_event") {
865
- const data = event.data;
866
- if (data.type === "output_text_delta" && data.delta) {
867
- return {
868
- type: "assistant",
869
- session_id: sessionId,
870
- message: {
871
- role: "assistant",
872
- content: [{ type: "text", text: data.delta }],
873
- },
874
- };
875
- }
876
- return null;
877
- }
878
- if (event.type !== "run_item_stream_event") {
879
- return null;
880
- }
881
- const raw = event.item.rawItem;
882
- if (!raw?.type) {
883
- return null;
884
- }
885
- if (event.name === "message_output_created" && raw.type === "message") {
886
- return null;
887
- }
888
- if (event.name === "reasoning_item_created" && raw.type === "reasoning") {
889
- // The OpenAI Agents SDK's `RunReasoningItem` normalizes the API's
890
- // `summary_text` parts as `{ type: "input_text", text, providerData: { type: "summary_text" } }`.
891
- // Earlier we filtered by `entry.type === "reasoning_text"` which never
892
- // matched any item the SDK actually produces, so reasoning summaries
893
- // were silently dropped before reaching the stream handler. Match
894
- // both shapes so we work whether the SDK changes its internal tag in
895
- // a future release or keeps the current `input_text` mapping.
896
- const text = raw.content
897
- ?.filter((entry) => (entry.type === "input_text"
898
- || entry.type === "reasoning_text"
899
- || entry.type === "summary_text")
900
- && typeof entry.text === "string")
901
- .map((entry) => entry.text)
902
- .join(" ");
903
- if (!text) {
904
- return null;
905
- }
906
- return {
907
- type: "assistant",
908
- session_id: sessionId,
909
- message: {
910
- role: "assistant",
911
- content: [{ type: "thinking", thinking: text }],
912
- },
913
- };
914
- }
915
- if (event.name === "tool_called") {
916
- if (raw.type === "function_call") {
917
- return {
918
- type: "assistant",
919
- session_id: sessionId,
920
- message: {
921
- role: "assistant",
922
- content: [{
923
- type: "tool_use",
924
- id: raw.callId,
925
- tool_use_id: raw.callId,
926
- name: raw.name ?? "tool",
927
- input: parseToolArguments(raw.arguments),
928
- }],
929
- },
930
- };
931
- }
932
- if (raw.type === "computer_call") {
933
- return {
934
- type: "assistant",
935
- session_id: sessionId,
936
- message: {
937
- role: "assistant",
938
- content: [{
939
- type: "tool_use",
940
- id: raw.callId,
941
- tool_use_id: raw.callId,
942
- name: "computer",
943
- input: raw.action ?? {},
944
- }],
945
- },
946
- };
947
- }
948
- }
949
- if (event.name === "tool_output") {
950
- if (raw.type === "function_call_result" || raw.type === "computer_call_result") {
951
- return {
952
- type: "user",
953
- session_id: sessionId,
954
- parent_tool_use_id: null,
955
- message: {
956
- role: "user",
957
- content: [{
958
- type: "tool_result",
959
- tool_use_id: raw.callId ?? "",
960
- content: raw.output ?? "",
961
- is_error: false,
962
- }],
963
- },
964
- };
965
- }
966
- }
967
- return null;
968
- }
969
- function parseToolArguments(value) {
970
- if (!value) {
971
- return {};
972
- }
973
- try {
974
- return JSON.parse(value);
975
- }
976
- catch {
977
- return value;
978
- }
979
- }
980
- function captureUsageFromEvent(event) {
981
- if (event.type !== "raw_model_stream_event") {
982
- return null;
983
- }
984
- const data = event.data;
985
- if (data.type !== "response_done") {
986
- return null;
987
- }
988
- const inputTokens = data.response?.usage?.inputTokens;
989
- if (typeof inputTokens !== "number") {
990
- return null;
991
- }
992
- return buildOpenAIUsageSnapshot(inputTokens);
993
- }
994
- function extractUsageFromStream(stream) {
995
- const usage = stream.rawResponses?.at(-1)?.usage;
996
- if (typeof usage?.inputTokens !== "number") {
997
- return null;
998
- }
999
- return buildOpenAIUsageSnapshot(usage.inputTokens);
1000
- }
1001
- function buildOpenAIUsageSnapshot(inputTokens) {
1002
- return {
1003
- usedInputTokens: inputTokens,
1004
- contextWindow: OPENAI_CONTEXT_WINDOW,
1005
- usedPct: inputTokens / OPENAI_CONTEXT_WINDOW,
1006
- };
1007
- }
1008
- function normalizeOpenAIUsageSnapshot(snapshot) {
1009
- if (snapshot.contextWindow > 0) {
1010
- return snapshot;
1011
- }
1012
- return buildOpenAIUsageSnapshot(snapshot.usedInputTokens);
1013
- }
1014
- function buildResultMessage(stream, config) {
1015
- const usage = stream.rawResponses?.at(-1)?.usage;
1016
- const inputTokens = usage?.inputTokens ?? 0;
1017
- const outputTokens = usage?.outputTokens ?? 0;
1018
- const modelId = getModelId(config);
1019
- const errorText = stream.error instanceof Error
1020
- ? stream.error.message
1021
- : stream.error
1022
- ? JSON.stringify(stream.error)
1023
- : "";
1024
- const hasUsage = typeof usage?.inputTokens === "number";
1025
- const modelUsage = hasUsage
1026
- ? {
1027
- [modelId]: {
1028
- contextWindow: OPENAI_CONTEXT_WINDOW,
1029
- inputTokens,
1030
- outputTokens,
1031
- cacheReadInputTokens: 0,
1032
- cacheCreationInputTokens: 0,
1033
- },
1034
- }
1035
- : undefined;
1036
- return {
1037
- type: "result",
1038
- subtype: errorText ? "error" : "success",
1039
- num_turns: 1,
1040
- usage: {
1041
- input_tokens: inputTokens,
1042
- output_tokens: outputTokens,
1043
- },
1044
- total_cost_usd: 0,
1045
- is_error: Boolean(errorText),
1046
- errors: errorText ? [errorText] : [],
1047
- model: modelId,
1048
- ...(modelUsage ? { modelUsage } : {}),
1049
- };
1050
- }
1051
- async function connectMcpServers(servers) {
1052
- for (const server of servers) {
1053
- await server.connect();
1054
- }
1055
- }
1056
- async function closeMcpServers(servers) {
1057
- for (const server of servers) {
1058
- try {
1059
- await server.close();
1060
- }
1061
- catch (err) {
1062
- logger.warn(`Failed to close MCP server "${server.name}": ${err instanceof Error ? err.message : String(err)}`);
1063
- }
1064
- }
1065
- }
1066
- //# sourceMappingURL=session.js.map