@xopcai/xopc 0.0.99 → 0.0.100

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 (349) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/telegram/src/account-manager.d.ts +3 -3
  3. package/dist/extensions/telegram/src/account-manager.js +8 -8
  4. package/dist/extensions/telegram/src/account-manager.js.map +1 -1
  5. package/dist/extensions/telegram/src/actions/message-actions.d.ts +4 -0
  6. package/dist/extensions/telegram/src/actions/message-actions.js +71 -0
  7. package/dist/extensions/telegram/src/actions/message-actions.js.map +1 -0
  8. package/dist/extensions/telegram/src/adapters/config-adapter.js +9 -1
  9. package/dist/extensions/telegram/src/adapters/config-adapter.js.map +1 -1
  10. package/dist/extensions/telegram/src/adapters/config-surface.js +1 -1
  11. package/dist/extensions/telegram/src/adapters/config-surface.js.map +1 -1
  12. package/dist/extensions/telegram/src/adapters/outbound-adapter.d.ts +2 -1
  13. package/dist/extensions/telegram/src/adapters/outbound-adapter.js +18 -4
  14. package/dist/extensions/telegram/src/adapters/outbound-adapter.js.map +1 -1
  15. package/dist/extensions/telegram/src/adapters/security-adapter.js +2 -1
  16. package/dist/extensions/telegram/src/adapters/security-adapter.js.map +1 -1
  17. package/dist/extensions/telegram/src/adapters/streaming-adapter.js +90 -6
  18. package/dist/extensions/telegram/src/adapters/streaming-adapter.js.map +1 -1
  19. package/dist/extensions/telegram/src/adapters/types.d.ts +9 -1
  20. package/dist/extensions/telegram/src/api-root.d.ts +4 -0
  21. package/dist/extensions/telegram/src/api-root.js +42 -0
  22. package/dist/extensions/telegram/src/api-root.js.map +1 -0
  23. package/dist/extensions/telegram/src/approval-delivery.d.ts +9 -0
  24. package/dist/extensions/telegram/src/approval-delivery.js +36 -0
  25. package/dist/extensions/telegram/src/approval-delivery.js.map +1 -0
  26. package/dist/extensions/telegram/src/approval-store.d.ts +25 -0
  27. package/dist/extensions/telegram/src/approval-store.js +61 -0
  28. package/dist/extensions/telegram/src/approval-store.js.map +1 -0
  29. package/dist/extensions/telegram/src/config-schema.d.ts +185 -35
  30. package/dist/extensions/telegram/src/config-schema.js +67 -45
  31. package/dist/extensions/telegram/src/config-schema.js.map +1 -1
  32. package/dist/extensions/telegram/src/conversation-id.d.ts +6 -0
  33. package/dist/extensions/telegram/src/conversation-id.js +18 -0
  34. package/dist/extensions/telegram/src/conversation-id.js.map +1 -0
  35. package/dist/extensions/telegram/src/doctor.d.ts +5 -0
  36. package/dist/extensions/telegram/src/doctor.js +80 -0
  37. package/dist/extensions/telegram/src/doctor.js.map +1 -0
  38. package/dist/extensions/telegram/src/exec-approval-handler.d.ts +7 -0
  39. package/dist/extensions/telegram/src/exec-approval-handler.js +36 -0
  40. package/dist/extensions/telegram/src/exec-approval-handler.js.map +1 -0
  41. package/dist/extensions/telegram/src/exec-approvals.d.ts +8 -0
  42. package/dist/extensions/telegram/src/exec-approvals.js +20 -0
  43. package/dist/extensions/telegram/src/exec-approvals.js.map +1 -0
  44. package/dist/extensions/telegram/src/focus-handler.d.ts +12 -0
  45. package/dist/extensions/telegram/src/focus-handler.js +55 -0
  46. package/dist/extensions/telegram/src/focus-handler.js.map +1 -0
  47. package/dist/extensions/telegram/src/group-config-resolver.d.ts +12 -0
  48. package/dist/extensions/telegram/src/group-config-resolver.js +17 -0
  49. package/dist/extensions/telegram/src/group-config-resolver.js.map +1 -0
  50. package/dist/extensions/telegram/src/inbound-coalescer.d.ts +15 -0
  51. package/dist/extensions/telegram/src/inbound-coalescer.js +107 -0
  52. package/dist/extensions/telegram/src/inbound-coalescer.js.map +1 -0
  53. package/dist/extensions/telegram/src/inbound-processor.d.ts +1 -0
  54. package/dist/extensions/telegram/src/inbound-processor.js +41 -4
  55. package/dist/extensions/telegram/src/inbound-processor.js.map +1 -1
  56. package/dist/extensions/telegram/src/network-errors.d.ts +2 -0
  57. package/dist/extensions/telegram/src/network-errors.js +23 -0
  58. package/dist/extensions/telegram/src/network-errors.js.map +1 -0
  59. package/dist/extensions/telegram/src/plugin.d.ts +11 -0
  60. package/dist/extensions/telegram/src/plugin.js +191 -57
  61. package/dist/extensions/telegram/src/plugin.js.map +1 -1
  62. package/dist/extensions/telegram/src/polling-session.d.ts +17 -0
  63. package/dist/extensions/telegram/src/polling-session.js +112 -0
  64. package/dist/extensions/telegram/src/polling-session.js.map +1 -0
  65. package/dist/extensions/telegram/src/proxy-fetch.d.ts +2 -0
  66. package/dist/extensions/telegram/src/proxy-fetch.js +14 -0
  67. package/dist/extensions/telegram/src/proxy-fetch.js.map +1 -0
  68. package/dist/extensions/telegram/src/reactions.d.ts +14 -0
  69. package/dist/extensions/telegram/src/reactions.js +48 -0
  70. package/dist/extensions/telegram/src/reactions.js.map +1 -0
  71. package/dist/extensions/telegram/src/reply-params.d.ts +15 -0
  72. package/dist/extensions/telegram/src/reply-params.js +28 -0
  73. package/dist/extensions/telegram/src/reply-params.js.map +1 -0
  74. package/dist/extensions/telegram/src/startup-errors.d.ts +3 -0
  75. package/dist/extensions/telegram/src/startup-errors.js +27 -0
  76. package/dist/extensions/telegram/src/startup-errors.js.map +1 -0
  77. package/dist/extensions/telegram/src/telegram-actions.d.ts +25 -0
  78. package/dist/extensions/telegram/src/telegram-actions.js +65 -0
  79. package/dist/extensions/telegram/src/telegram-actions.js.map +1 -0
  80. package/dist/extensions/telegram/src/thread-bindings.d.ts +15 -0
  81. package/dist/extensions/telegram/src/thread-bindings.js +22 -0
  82. package/dist/extensions/telegram/src/thread-bindings.js.map +1 -0
  83. package/dist/extensions/telegram/src/token-resolver.d.ts +8 -0
  84. package/dist/extensions/telegram/src/token-resolver.js +34 -0
  85. package/dist/extensions/telegram/src/token-resolver.js.map +1 -0
  86. package/dist/extensions/telegram/src/update-offset-store.d.ts +9 -0
  87. package/dist/extensions/telegram/src/update-offset-store.js +66 -0
  88. package/dist/extensions/telegram/src/update-offset-store.js.map +1 -0
  89. package/dist/extensions/telegram/xopc.extension.json +1 -1
  90. package/dist/gateway/static/root/assets/{agents-4KTK-Ku7.js → agents-C5jtBK-H.js} +1 -1
  91. package/dist/gateway/static/root/assets/{apps-page-DkgInY2w.js → apps-page-smJl-qf_.js} +1 -1
  92. package/dist/gateway/static/root/assets/channels-settings-CH6KgFK6.js +1 -0
  93. package/dist/gateway/static/root/assets/channels-status-swr-CV70xvY7.js +8 -0
  94. package/dist/gateway/static/root/assets/{cron-api-BRDli2Mm.js → cron-api-CgBO8B7I.js} +1 -1
  95. package/dist/gateway/static/root/assets/{cron-page-BzEr5Za8.js → cron-page-Be0cW1cR.js} +1 -1
  96. package/dist/gateway/static/root/assets/{dist-BsHh1vX0.js → dist-P8HRGUuh.js} +1 -1
  97. package/dist/gateway/static/root/assets/{extension-debug-page-TWt5q_ef.js → extension-debug-page-BowuycYu.js} +1 -1
  98. package/dist/gateway/static/root/assets/{extension-page-C3kV8ZvZ.js → extension-page-27-Zpv8O.js} +1 -1
  99. package/dist/gateway/static/root/assets/{extension-settings-page-Csy9LNC6.js → extension-settings-page-DDFa_rIq.js} +1 -1
  100. package/dist/gateway/static/root/assets/{fetch-BLLOP2CM.js → fetch-drJUxxKP.js} +1 -1
  101. package/dist/gateway/static/root/assets/{field-primitives-MXbM8HvY.js → field-primitives-BWLssxEb.js} +1 -1
  102. package/dist/gateway/static/root/assets/{heartbeat-config-api-CkoyqfRx.js → heartbeat-config-api-BVuYNYT_.js} +1 -1
  103. package/dist/gateway/static/root/assets/index-CRDAmr3W.js +4840 -0
  104. package/dist/gateway/static/root/assets/index-DjNDHzwG.css +1 -0
  105. package/dist/gateway/static/root/assets/{logs-page-B2vY41ph.js → logs-page-BDX8IrDQ.js} +1 -1
  106. package/dist/gateway/static/root/assets/{note-detail-page-B7qE5pkO.js → note-detail-page-BmQ9KeiA.js} +1 -1
  107. package/dist/gateway/static/root/assets/{note-time-NBBReVR4.js → note-time-14I9Sgqh.js} +1 -1
  108. package/dist/gateway/static/root/assets/{notes-page-CsWYaqYM.js → notes-page-BMfP0FFf.js} +1 -1
  109. package/dist/gateway/static/root/assets/{sessions-page-BPGY3SCT.js → sessions-page-C3scyChq.js} +1 -1
  110. package/dist/gateway/static/root/assets/{settings-advanced-gate-DtA451RM.js → settings-advanced-gate-2ODuCjHP.js} +1 -1
  111. package/dist/gateway/static/root/assets/{settings-form-section-DYR_SPlX.js → settings-form-section-DUoGKAiW.js} +1 -1
  112. package/dist/gateway/static/root/assets/{settings-page-BJUgZQzN.js → settings-page-CttAfOWV.js} +1 -1
  113. package/dist/gateway/static/root/assets/{share-preview-page-3u8VFmdo.js → share-preview-page-B77xqiiI.js} +1 -1
  114. package/dist/gateway/static/root/assets/{skills-page-DuyhcH4U.js → skills-page-B_bAZGzB.js} +1 -1
  115. package/dist/gateway/static/root/assets/{theme-store-e2q2yjs4.js → theme-store-BgCPrmxU.js} +1 -1
  116. package/dist/gateway/static/root/assets/url-q28QhI7l.js +3 -0
  117. package/dist/gateway/static/root/assets/{utils-B4Vw3fnc.js → utils-CCGejlcX.js} +1 -1
  118. package/dist/gateway/static/root/assets/{voice-api-key-field-BFY67Hhp.js → voice-api-key-field-DZzzihcY.js} +1 -1
  119. package/dist/gateway/static/root/assets/{workflow-page.utils-yUJ5jexd.js → workflow-page.utils-D5RMAmT4.js} +1 -1
  120. package/dist/gateway/static/root/assets/{workflows-page-DIMHCpNt.js → workflows-page-DEYpC6Bv.js} +1 -1
  121. package/dist/gateway/static/root/index.html +5 -5
  122. package/dist/package.js +1 -1
  123. package/dist/src/agent/agent-manager.d.ts +4 -0
  124. package/dist/src/agent/agent-manager.js +68 -4
  125. package/dist/src/agent/agent-manager.js.map +1 -1
  126. package/dist/src/agent/child-agent-factory.d.ts +1 -0
  127. package/dist/src/agent/child-agent-factory.js +13 -10
  128. package/dist/src/agent/child-agent-factory.js.map +1 -1
  129. package/dist/src/agent/embedded/run-for-session.d.ts +1 -0
  130. package/dist/src/agent/embedded/run-for-session.js +1 -0
  131. package/dist/src/agent/embedded/run-for-session.js.map +1 -1
  132. package/dist/src/agent/embedded/run-turn.js +1 -17
  133. package/dist/src/agent/embedded/run-turn.js.map +1 -1
  134. package/dist/src/agent/embedded/session-runner.js +3 -1
  135. package/dist/src/agent/embedded/session-runner.js.map +1 -1
  136. package/dist/src/agent/embedded/session-tool-result-guard.d.ts +1 -0
  137. package/dist/src/agent/embedded/session-tool-result-guard.js +1 -0
  138. package/dist/src/agent/embedded/session-tool-result-guard.js.map +1 -1
  139. package/dist/src/agent/inbound/attachment-pipeline.d.ts +37 -0
  140. package/dist/src/agent/inbound/attachment-pipeline.js +169 -0
  141. package/dist/src/agent/inbound/attachment-pipeline.js.map +1 -0
  142. package/dist/src/agent/inbound/turn-dispatcher.d.ts +3 -5
  143. package/dist/src/agent/inbound/turn-dispatcher.js +1 -2
  144. package/dist/src/agent/inbound/turn-dispatcher.js.map +1 -1
  145. package/dist/src/agent/memory/index.d.ts +2 -0
  146. package/dist/src/agent/memory/index.js +2 -1
  147. package/dist/src/agent/memory/memory-flush.d.ts +27 -0
  148. package/dist/src/agent/memory/memory-flush.js +124 -0
  149. package/dist/src/agent/memory/memory-flush.js.map +1 -0
  150. package/dist/src/agent/messaging/outbound-coordinator.js +2 -1
  151. package/dist/src/agent/messaging/outbound-coordinator.js.map +1 -1
  152. package/dist/src/agent/messaging/stream-manager.d.ts +2 -0
  153. package/dist/src/agent/messaging/stream-manager.js +3 -0
  154. package/dist/src/agent/messaging/stream-manager.js.map +1 -1
  155. package/dist/src/agent/orchestration/agent-orchestrator.d.ts +0 -4
  156. package/dist/src/agent/orchestration/agent-orchestrator.js +40 -107
  157. package/dist/src/agent/orchestration/agent-orchestrator.js.map +1 -1
  158. package/dist/src/agent/prompt/contribution.d.ts +9 -0
  159. package/dist/src/agent/prompt/contribution.js +1 -0
  160. package/dist/src/agent/prompt/safety.js +0 -10
  161. package/dist/src/agent/prompt/safety.js.map +1 -1
  162. package/dist/src/agent/prompt/sanitize-for-prompt.d.ts +6 -0
  163. package/dist/src/agent/prompt/sanitize-for-prompt.js +13 -0
  164. package/dist/src/agent/prompt/sanitize-for-prompt.js.map +1 -0
  165. package/dist/src/agent/prompt/sections/behavior.d.ts +5 -0
  166. package/dist/src/agent/prompt/sections/behavior.js +50 -0
  167. package/dist/src/agent/prompt/sections/behavior.js.map +1 -0
  168. package/dist/src/agent/prompt/sections/memory-skills.d.ts +9 -0
  169. package/dist/src/agent/prompt/sections/memory-skills.js +59 -0
  170. package/dist/src/agent/prompt/sections/memory-skills.js.map +1 -0
  171. package/dist/src/agent/prompt/sections/messaging-runtime.d.ts +16 -0
  172. package/dist/src/agent/prompt/sections/messaging-runtime.js +69 -0
  173. package/dist/src/agent/prompt/sections/messaging-runtime.js.map +1 -0
  174. package/dist/src/agent/prompt/sections/project-context.d.ts +16 -0
  175. package/dist/src/agent/prompt/sections/project-context.js +58 -0
  176. package/dist/src/agent/prompt/sections/project-context.js.map +1 -0
  177. package/dist/src/agent/prompt/sections/tooling.d.ts +5 -0
  178. package/dist/src/agent/prompt/sections/tooling.js +119 -0
  179. package/dist/src/agent/prompt/sections/tooling.js.map +1 -0
  180. package/dist/src/agent/prompt/sections/workspace-runtime.d.ts +11 -0
  181. package/dist/src/agent/prompt/sections/workspace-runtime.js +32 -0
  182. package/dist/src/agent/prompt/sections/workspace-runtime.js.map +1 -0
  183. package/dist/src/agent/prompt/service-prompt-builder.d.ts +13 -0
  184. package/dist/src/agent/prompt/service-prompt-builder.js +54 -13
  185. package/dist/src/agent/prompt/service-prompt-builder.js.map +1 -1
  186. package/dist/src/agent/prompt/subagent-context.d.ts +15 -0
  187. package/dist/src/agent/prompt/subagent-context.js +68 -0
  188. package/dist/src/agent/prompt/subagent-context.js.map +1 -0
  189. package/dist/src/agent/prompt/system-prompt-params.d.ts +53 -0
  190. package/dist/src/agent/prompt/system-prompt-params.js +74 -0
  191. package/dist/src/agent/prompt/system-prompt-params.js.map +1 -0
  192. package/dist/src/agent/prompt/system-prompt.d.ts +22 -11
  193. package/dist/src/agent/prompt/system-prompt.js +79 -193
  194. package/dist/src/agent/prompt/system-prompt.js.map +1 -1
  195. package/dist/src/agent/prompt/types.d.ts +3 -0
  196. package/dist/src/agent/service/build-direct-message-content.d.ts +5 -21
  197. package/dist/src/agent/service/build-direct-message-content.js +11 -64
  198. package/dist/src/agent/service/build-direct-message-content.js.map +1 -1
  199. package/dist/src/agent/service/direct-turn-helpers.d.ts +1 -1
  200. package/dist/src/agent/service/direct-turn-helpers.js +7 -0
  201. package/dist/src/agent/service/direct-turn-helpers.js.map +1 -1
  202. package/dist/src/agent/service/process-direct-one-shot.d.ts +3 -3
  203. package/dist/src/agent/service/process-direct-one-shot.js +22 -17
  204. package/dist/src/agent/service/process-direct-one-shot.js.map +1 -1
  205. package/dist/src/agent/service/process-direct-streaming.d.ts +6 -20
  206. package/dist/src/agent/service/process-direct-streaming.js +39 -40
  207. package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
  208. package/dist/src/agent/service/webchat-tts.d.ts +4 -9
  209. package/dist/src/agent/service/webchat-tts.js +9 -20
  210. package/dist/src/agent/service/webchat-tts.js.map +1 -1
  211. package/dist/src/agent/service.d.ts +8 -19
  212. package/dist/src/agent/service.js +53 -11
  213. package/dist/src/agent/service.js.map +1 -1
  214. package/dist/src/agent/tools/factory.js +2 -0
  215. package/dist/src/agent/tools/factory.js.map +1 -1
  216. package/dist/src/agent/tools/index.d.ts +1 -0
  217. package/dist/src/agent/tools/index.js +2 -1
  218. package/dist/src/agent/tools/media-read-tool.d.ts +2 -0
  219. package/dist/src/agent/tools/media-read-tool.js +89 -0
  220. package/dist/src/agent/tools/media-read-tool.js.map +1 -0
  221. package/dist/src/channels/attachments/attachment-classifiers.d.ts +8 -0
  222. package/dist/src/channels/attachments/attachment-classifiers.js +11 -0
  223. package/dist/src/channels/attachments/attachment-classifiers.js.map +1 -0
  224. package/dist/src/channels/attachments/inbound-persist.d.ts +13 -34
  225. package/dist/src/channels/attachments/inbound-persist.js +34 -102
  226. package/dist/src/channels/attachments/inbound-persist.js.map +1 -1
  227. package/dist/src/channels/attachments/inbound-types.d.ts +13 -0
  228. package/dist/src/channels/attachments/inbound-types.js +30 -0
  229. package/dist/src/channels/attachments/inbound-types.js.map +1 -0
  230. package/dist/src/channels/attachments/outbound-tts-persist.d.ts +3 -12
  231. package/dist/src/channels/attachments/outbound-tts-persist.js +28 -48
  232. package/dist/src/channels/attachments/outbound-tts-persist.js.map +1 -1
  233. package/dist/src/channels/attachments/voice-stt-webchat.d.ts +3 -3
  234. package/dist/src/channels/attachments/voice-stt-webchat.js +8 -14
  235. package/dist/src/channels/attachments/voice-stt-webchat.js.map +1 -1
  236. package/dist/src/channels/channel-domain.d.ts +30 -2
  237. package/dist/src/channels/exec-approval-runtime.d.ts +29 -0
  238. package/dist/src/channels/exec-approval-runtime.js +30 -0
  239. package/dist/src/channels/exec-approval-runtime.js.map +1 -0
  240. package/dist/src/channels/index.js +1 -1
  241. package/dist/src/channels/plugin-types.d.ts +1 -0
  242. package/dist/src/channels/transport-types.d.ts +2 -2
  243. package/dist/src/cli/commands/channels.js +1 -1
  244. package/dist/src/cli/commands/doctor/checks/database-schema.d.ts +2 -0
  245. package/dist/src/cli/commands/doctor/checks/database-schema.js +62 -0
  246. package/dist/src/cli/commands/doctor/checks/database-schema.js.map +1 -0
  247. package/dist/src/cli/commands/doctor/flow.js +2 -0
  248. package/dist/src/cli/commands/doctor/flow.js.map +1 -1
  249. package/dist/src/cli/commands/init.js +2 -57
  250. package/dist/src/cli/commands/init.js.map +1 -1
  251. package/dist/src/cli/commands/onboard/channels/registry.js +1 -1
  252. package/dist/src/cli/commands/setup.js +6 -5
  253. package/dist/src/cli/commands/setup.js.map +1 -1
  254. package/dist/src/cli/utils/init-workspace-core.js +1 -2
  255. package/dist/src/cli/utils/init-workspace-core.js.map +1 -1
  256. package/dist/src/config/index.js +2 -2
  257. package/dist/src/config/paths.d.ts +0 -13
  258. package/dist/src/config/paths.js +1 -20
  259. package/dist/src/config/paths.js.map +1 -1
  260. package/dist/src/config/rules.js +1 -1
  261. package/dist/src/config/schema.d.ts +24 -0
  262. package/dist/src/config/schema.js +33 -2
  263. package/dist/src/config/schema.js.map +1 -1
  264. package/dist/src/cron/executor.js +2 -2
  265. package/dist/src/gateway/heartbeat/service.js +1 -1
  266. package/dist/src/gateway/hono/lib/config-payload.js +1 -1
  267. package/dist/src/gateway/hono/routes/config-patch/channels.js +3 -3
  268. package/dist/src/gateway/hono/routes/config-patch/channels.js.map +1 -1
  269. package/dist/src/gateway/hono/routes/lazy-bundles.js +8 -0
  270. package/dist/src/gateway/hono/routes/lazy-bundles.js.map +1 -1
  271. package/dist/src/gateway/hono/routes/media.d.ts +3 -0
  272. package/dist/src/gateway/hono/routes/media.js +50 -0
  273. package/dist/src/gateway/hono/routes/media.js.map +1 -0
  274. package/dist/src/gateway/hono/routes/sessions.js +6 -0
  275. package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
  276. package/dist/src/gateway/hono/routes/workspace.js +1 -87
  277. package/dist/src/gateway/hono/routes/workspace.js.map +1 -1
  278. package/dist/src/gateway/service/sessions-api.js +3 -0
  279. package/dist/src/gateway/service/sessions-api.js.map +1 -1
  280. package/dist/src/heartbeat/index.js +1 -1
  281. package/dist/src/media/index.d.ts +5 -0
  282. package/dist/src/media/index.js +5 -0
  283. package/dist/src/media/media-reference.d.ts +23 -0
  284. package/dist/src/media/media-reference.js +34 -0
  285. package/dist/src/media/media-reference.js.map +1 -0
  286. package/dist/src/media/paths.d.ts +5 -0
  287. package/dist/src/media/paths.js +20 -0
  288. package/dist/src/media/paths.js.map +1 -0
  289. package/dist/src/media/session-references.d.ts +8 -0
  290. package/dist/src/media/session-references.js +45 -0
  291. package/dist/src/media/session-references.js.map +1 -0
  292. package/dist/src/media/store.d.ts +19 -0
  293. package/dist/src/media/store.js +131 -0
  294. package/dist/src/media/store.js.map +1 -0
  295. package/dist/src/media/types.d.ts +34 -0
  296. package/dist/src/media/types.js +1 -0
  297. package/dist/src/media/uri.d.ts +15 -0
  298. package/dist/src/media/uri.js +51 -0
  299. package/dist/src/media/uri.js.map +1 -0
  300. package/dist/src/session/session-title.js +6 -3
  301. package/dist/src/session/session-title.js.map +1 -1
  302. package/dist/src/session/store.js +1 -1
  303. package/dist/src/session/store.js.map +1 -1
  304. package/dist/src/session/types.d.ts +12 -7
  305. package/dist/src/session/types.js.map +1 -1
  306. package/dist/src/storage/sqlite/index.d.ts +4 -1
  307. package/dist/src/storage/sqlite/index.js +6 -2
  308. package/dist/src/storage/sqlite/migrations/002_add_flush_columns.sql +3 -0
  309. package/dist/src/storage/sqlite/migrations/discover.d.ts +4 -0
  310. package/dist/src/storage/sqlite/migrations/discover.js +38 -0
  311. package/dist/src/storage/sqlite/migrations/discover.js.map +1 -0
  312. package/dist/src/storage/sqlite/migrations/discover.ts +52 -0
  313. package/dist/src/storage/sqlite/migrations/errors.d.ts +11 -0
  314. package/dist/src/storage/sqlite/migrations/errors.js +27 -0
  315. package/dist/src/storage/sqlite/migrations/errors.js.map +1 -0
  316. package/dist/src/storage/sqlite/migrations/errors.ts +32 -0
  317. package/dist/src/storage/sqlite/migrations/runner.d.ts +22 -0
  318. package/dist/src/storage/sqlite/migrations/runner.js +97 -0
  319. package/dist/src/storage/sqlite/migrations/runner.js.map +1 -0
  320. package/dist/src/storage/sqlite/migrations/runner.ts +153 -0
  321. package/dist/src/storage/sqlite/migrations/types.d.ts +12 -0
  322. package/dist/src/storage/sqlite/migrations/types.js +1 -0
  323. package/dist/src/storage/sqlite/migrations/types.ts +13 -0
  324. package/dist/src/storage/sqlite/row-mappers.d.ts +4 -0
  325. package/dist/src/storage/sqlite/row-mappers.js +4 -0
  326. package/dist/src/storage/sqlite/row-mappers.js.map +1 -1
  327. package/dist/src/storage/sqlite/schema-version.d.ts +6 -0
  328. package/dist/src/storage/sqlite/schema-version.js +29 -0
  329. package/dist/src/storage/sqlite/schema-version.js.map +1 -0
  330. package/dist/src/storage/sqlite/schema.d.ts +5 -3
  331. package/dist/src/storage/sqlite/schema.js +24 -28
  332. package/dist/src/storage/sqlite/schema.js.map +1 -1
  333. package/dist/src/storage/sqlite/schema.sql +2 -0
  334. package/dist/src/storage/sqlite/session-metadata.js +1 -0
  335. package/dist/src/storage/sqlite/session-metadata.js.map +1 -1
  336. package/dist/src/storage/sqlite/session-repository.js +7 -3
  337. package/dist/src/storage/sqlite/session-repository.js.map +1 -1
  338. package/package.json +1 -1
  339. package/dist/gateway/static/root/assets/channels-settings-BxW1Xio4.js +0 -1
  340. package/dist/gateway/static/root/assets/channels-status-swr-Cxm6AvQO.js +0 -8
  341. package/dist/gateway/static/root/assets/index-BJDmBCSl.css +0 -1
  342. package/dist/gateway/static/root/assets/index-Cbg5TS5m.js +0 -4707
  343. package/dist/gateway/static/root/assets/url-DpFBIyN9.js +0 -3
  344. package/dist/src/cli/commands/onboard/workspace.d.ts +0 -14
  345. package/dist/src/cli/commands/onboard/workspace.js +0 -33
  346. package/dist/src/cli/commands/onboard/workspace.js.map +0 -1
  347. package/dist/src/cli/utils/workspace.d.ts +0 -35
  348. package/dist/src/cli/utils/workspace.js +0 -72
  349. package/dist/src/cli/utils/workspace.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"build-direct-message-content.js","names":[],"sources":["../../../../src/agent/service/build-direct-message-content.ts"],"sourcesContent":["import type { Config } from '../../config/schema.js';\nimport { getAgentDefaultModelRef } from '../../config/schema.js';\nimport { getDefaultModelSync } from '../../providers/index.js';\nimport { formatInboundFileTextBlock } from '../../channels/attachments/inbound-persist.js';\nimport { expandAtFileMentionsInPlainText } from '../context/expand-at-file-mentions.js';\nimport { resolveInboundImageContentParts } from '../image/inbound-image-handling.js';\nimport { resolveAgentHomeDir, resolveDefaultAgentId } from '../agent-scope.js';\nimport { extractProfileAgentId } from '../../config/agent-profile.js';\nimport type { AgentInstanceGateway } from '../agent-instance-gateway.js';\nimport type { ModelManager } from '../models/index.js';\n\nexport type DirectInboundAttachment = {\n type: string;\n mimeType?: string;\n data?: string;\n name?: string;\n size?: number;\n workspaceRelativePath?: string;\n};\n\nexport type DirectMessagePart =\n | { type: 'text'; text: string }\n | { type: 'image'; data: string; mimeType: string };\n\n/**\n * Build user message parts (text + resolved images/files) for direct / webchat turns.\n */\nexport async function buildDirectUserMessageContent(opts: {\n content: string;\n attachments?: DirectInboundAttachment[];\n sessionKey?: string;\n config: Config;\n agentManager: AgentInstanceGateway;\n modelManager: ModelManager;\n}): Promise<DirectMessagePart[]> {\n const { content, attachments, sessionKey, config, agentManager, modelManager } = opts;\n const messageContent: DirectMessagePart[] = [];\n const sk = sessionKey ?? '';\n\n if (content.trim()) {\n let textPart = content;\n if (/@file:/.test(textPart)) {\n const wsKey = sk !== '' ? sk : 'agent:main:main';\n const root = agentManager.getResolvedWorkspaceForSession(wsKey);\n textPart = await expandAtFileMentionsInPlainText(textPart, root);\n }\n messageContent.push({ type: 'text', text: textPart });\n }\n\n if (!attachments?.length) {\n return messageContent;\n }\n\n const modelRef =\n sk !== ''\n ? modelManager.getModelForSession(sk)\n : getAgentDefaultModelRef(config) ?? getDefaultModelSync(config);\n\n const storageRoot =\n sk !== ''\n ? resolveAgentHomeDir(config, extractProfileAgentId(sk, config))\n : resolveAgentHomeDir(config, resolveDefaultAgentId(config));\n\n let i = 0;\n while (i < attachments.length) {\n const att = attachments[i]!;\n const isImage =\n att.type === 'image' ||\n att.type === 'photo' ||\n Boolean(att.mimeType?.startsWith('image/'));\n\n if (isImage) {\n const group: Array<{ data: string; mimeType: string }> = [];\n while (i < attachments.length) {\n const a = attachments[i]!;\n const img = a.type === 'image' || a.type === 'photo' || Boolean(a.mimeType?.startsWith('image/'));\n if (!img) {\n break;\n }\n if (!a.data || a.data.length === 0) {\n i += 1;\n continue;\n }\n group.push({ data: a.data, mimeType: a.mimeType || 'image/png' });\n i += 1;\n }\n if (group.length > 0) {\n const parts = await resolveInboundImageContentParts({\n modelRef: modelRef || getDefaultModelSync(config),\n cfg: config,\n userTextForContext: content.trim() ? content : '',\n images: group,\n });\n messageContent.push(...parts);\n }\n } else {\n const fileBlock = formatInboundFileTextBlock(att, storageRoot);\n messageContent.push({ type: 'text', text: fileBlock });\n i += 1;\n }\n }\n\n return messageContent;\n}\n"],"mappings":";;;;;;;;aACiE;gBACF;kBAIgB;;;;AAqB/E,eAAsB,8BAA8B,MAOnB;CAC/B,MAAM,EAAE,SAAS,aAAa,YAAY,QAAQ,cAAc,iBAAiB;CACjF,MAAM,iBAAsC,EAAE;CAC9C,MAAM,KAAK,cAAc;AAEzB,KAAI,QAAQ,MAAM,EAAE;EAClB,IAAI,WAAW;AACf,MAAI,SAAS,KAAK,SAAS,EAAE;GAC3B,MAAM,QAAQ,OAAO,KAAK,KAAK;GAC/B,MAAM,OAAO,aAAa,+BAA+B,MAAM;AAC/D,cAAW,MAAM,gCAAgC,UAAU,KAAK;;AAElE,iBAAe,KAAK;GAAE,MAAM;GAAQ,MAAM;GAAU,CAAC;;AAGvD,KAAI,CAAC,aAAa,OAChB,QAAO;CAGT,MAAM,WACJ,OAAO,KACH,aAAa,mBAAmB,GAAG,GACnC,wBAAwB,OAAO,IAAI,oBAAoB,OAAO;CAEpE,MAAM,cACJ,OAAO,KACH,oBAAoB,QAAQ,sBAAsB,IAAI,OAAO,CAAC,GAC9D,oBAAoB,QAAQ,sBAAsB,OAAO,CAAC;CAEhE,IAAI,IAAI;AACR,QAAO,IAAI,YAAY,QAAQ;EAC7B,MAAM,MAAM,YAAY;AAMxB,MAJE,IAAI,SAAS,WACb,IAAI,SAAS,WACb,QAAQ,IAAI,UAAU,WAAW,SAAS,CAAC,EAEhC;GACX,MAAM,QAAmD,EAAE;AAC3D,UAAO,IAAI,YAAY,QAAQ;IAC7B,MAAM,IAAI,YAAY;AAEtB,QAAI,EADQ,EAAE,SAAS,WAAW,EAAE,SAAS,WAAW,QAAQ,EAAE,UAAU,WAAW,SAAS,CAAC,EAE/F;AAEF,QAAI,CAAC,EAAE,QAAQ,EAAE,KAAK,WAAW,GAAG;AAClC,UAAK;AACL;;AAEF,UAAM,KAAK;KAAE,MAAM,EAAE;KAAM,UAAU,EAAE,YAAY;KAAa,CAAC;AACjE,SAAK;;AAEP,OAAI,MAAM,SAAS,GAAG;IACpB,MAAM,QAAQ,MAAM,gCAAgC;KAClD,UAAU,YAAY,oBAAoB,OAAO;KACjD,KAAK;KACL,oBAAoB,QAAQ,MAAM,GAAG,UAAU;KAC/C,QAAQ;KACT,CAAC;AACF,mBAAe,KAAK,GAAG,MAAM;;SAE1B;GACL,MAAM,YAAY,2BAA2B,KAAK,YAAY;AAC9D,kBAAe,KAAK;IAAE,MAAM;IAAQ,MAAM;IAAW,CAAC;AACtD,QAAK;;;AAIT,QAAO"}
1
+ {"version":3,"file":"build-direct-message-content.js","names":[],"sources":["../../../../src/agent/service/build-direct-message-content.ts"],"sourcesContent":["import type { Config } from '../../config/schema.js';\nimport { getAgentDefaultModelRef } from '../../config/schema.js';\nimport { getDefaultModelSync } from '../../providers/index.js';\nimport type { MediaRef } from '../../channels/attachments/inbound-persist.js';\nimport { buildTranscriptUserMessage } from '../inbound/attachment-pipeline.js';\nimport type { AgentInstanceGateway } from '../agent-instance-gateway.js';\nimport type { ModelManager } from '../models/index.js';\n\nexport type { InboundAttachmentInput as DirectInboundWireAttachment } from '../../channels/attachments/inbound-persist.js';\nexport type { MediaRef as DirectInboundAttachment } from '../../channels/attachments/inbound-persist.js';\n\nexport async function buildDirectUserMessageContent(opts: {\n content: string;\n attachments?: MediaRef[];\n sessionKey?: string;\n config: Config;\n agentManager: AgentInstanceGateway;\n modelManager: ModelManager;\n}) {\n const sk = opts.sessionKey ?? '';\n const modelRef =\n sk !== ''\n ? opts.modelManager.getModelForSession(sk)\n : getAgentDefaultModelRef(opts.config) ?? getDefaultModelSync(opts.config);\n\n return buildTranscriptUserMessage({\n text: opts.content,\n prepared: opts.attachments,\n sessionKey: sk,\n modelRef: modelRef || getDefaultModelSync(opts.config),\n config: opts.config,\n agentManager: opts.agentManager,\n });\n}\n"],"mappings":";;;;aACiE;gBACF;AAS/D,eAAsB,8BAA8B,MAOjD;CACD,MAAM,KAAK,KAAK,cAAc;CAC9B,MAAM,WACJ,OAAO,KACH,KAAK,aAAa,mBAAmB,GAAG,GACxC,wBAAwB,KAAK,OAAO,IAAI,oBAAoB,KAAK,OAAO;AAE9E,QAAO,2BAA2B;EAChC,MAAM,KAAK;EACX,UAAU,KAAK;EACf,YAAY;EACZ,UAAU,YAAY,oBAAoB,KAAK,OAAO;EACtD,QAAQ,KAAK;EACb,cAAc,KAAK;EACpB,CAAC"}
@@ -5,13 +5,13 @@
5
5
  * the entry points only manage their I/O specifics (event sink, voice STT,
6
6
  * TTS, transcript persistence).
7
7
  */
8
+ import type { AgentMessage } from '@earendil-works/pi-agent-core';
8
9
  import type { CommandHandler } from '../messaging/command-handler.js';
9
10
  import type { SessionStore } from '../../session/index.js';
10
11
  import type { Config } from '../../config/schema.js';
11
12
  import type { AgentInstanceGateway } from '../agent-instance-gateway.js';
12
13
  import type { ModelManager } from '../models/index.js';
13
14
  import type { EmbeddedStreamEvent } from '../embedded/types.js';
14
- import type { AgentMessage } from '@earendil-works/pi-agent-core';
15
15
  export interface HydratePerTurnStateDeps {
16
16
  hydrateSessionWorkspaceFromStore: (sessionKey: string) => Promise<void>;
17
17
  hydrateSessionModelFromStore: (sessionKey: string) => Promise<void>;
@@ -1,3 +1,4 @@
1
+ import { hydrateUserTurnForLlm } from "../inbound/attachment-pipeline.js";
1
2
  import { extractAgentUserPlainText } from "../memory/user-message-text.js";
2
3
  import { shouldSkipResetOverlapCommand } from "../../session/reset-triggers.js";
3
4
  import { parseSlashCommand } from "../../chat-commands/command-parse.js";
@@ -73,10 +74,16 @@ async function tryRunSlashCommand(deps, ctx, content, options) {
73
74
  async function runDirectAgentTurn(deps, input) {
74
75
  const userPlain = extractAgentUserPlainText(input.userMessage);
75
76
  const userMessageForModel = await deps.agentManager.applyMemoryPrefetchToUserMessage(input.userMessage, input.sessionKey);
77
+ const modelRef = deps.modelManager.getModelForSession(input.sessionKey);
78
+ const llmImages = (await hydrateUserTurnForLlm({
79
+ message: input.userMessage,
80
+ modelRef
81
+ })).images;
76
82
  const result = await runEmbeddedTurnForSession({
77
83
  sessionKey: input.sessionKey,
78
84
  runId: crypto.randomUUID(),
79
85
  userMessage: userMessageForModel,
86
+ llmImages,
80
87
  sessionStore: deps.sessionStore,
81
88
  agentManager: deps.agentManager,
82
89
  modelManager: deps.modelManager,
@@ -1 +1 @@
1
- {"version":3,"file":"direct-turn-helpers.js","names":[],"sources":["../../../../src/agent/service/direct-turn-helpers.ts"],"sourcesContent":["/**\n * Shared building blocks for the two direct-turn entry points (streaming\n * webchat SSE and one-shot CLI). Both flows do the same hydrate → maybe-slash\n * → run-embedded-turn → after-turn dance; this module captures that core so\n * the entry points only manage their I/O specifics (event sink, voice STT,\n * TTS, transcript persistence).\n */\n\nimport crypto from 'node:crypto';\n\nimport { commandRegistry } from '../../chat-commands/index.js';\nimport { parseSlashCommand } from '../../chat-commands/command-parse.js';\nimport { shouldSkipResetOverlapCommand } from '../../session/reset-triggers.js';\nimport type { CommandHandler } from '../messaging/command-handler.js';\nimport type { SessionStore } from '../../session/index.js';\nimport type { Config } from '../../config/schema.js';\nimport type { AgentInstanceGateway } from '../agent-instance-gateway.js';\nimport type { ModelManager } from '../models/index.js';\nimport { extractAgentUserPlainText } from '../memory/user-message-text.js';\nimport { runEmbeddedTurnForSession } from '../embedded/run-for-session.js';\nimport type { EmbeddedStreamEvent } from '../embedded/types.js';\nimport type { AgentMessage } from '@earendil-works/pi-agent-core';\n\nexport interface HydratePerTurnStateDeps {\n hydrateSessionWorkspaceFromStore: (sessionKey: string) => Promise<void>;\n hydrateSessionModelFromStore: (sessionKey: string) => Promise<void>;\n applyResolvedThinkingLevel: (sessionKey: string, thinking?: string | null) => Promise<void>;\n}\n\n/** Workspace + model + thinking level — common prep before any direct turn. */\nexport async function hydratePerTurnState(\n deps: HydratePerTurnStateDeps,\n sessionKey: string,\n thinking?: string,\n): Promise<void> {\n await deps.hydrateSessionWorkspaceFromStore(sessionKey);\n await deps.hydrateSessionModelFromStore(sessionKey);\n await deps.applyResolvedThinkingLevel(sessionKey, thinking);\n}\n\nexport interface SlashCommandOutcome {\n /** True if the input parsed as a registered slash command (handled or not). */\n matched: boolean;\n /** Aggregated user-visible reply text (assistant view). */\n aggregatedText: string;\n /** The parsed command name when matched. */\n command?: string;\n}\n\nexport interface TryRunSlashCommandDeps {\n commandHandler: Pick<CommandHandler, 'executeCommandAndAggregateReply'>;\n log: { warn: (obj: Record<string, unknown>, msg: string) => void };\n}\n\n/**\n * Detect a slash command and, if registered, execute it and aggregate the reply\n * text. Errors thrown inside the command surface as `aggregatedText` so callers\n * can persist the receipt or stream it as a token.\n */\nexport async function tryRunSlashCommand(\n deps: TryRunSlashCommandDeps,\n ctx: {\n sessionKey: string;\n channel: string;\n chatId: string;\n senderId?: string;\n isGroup?: boolean;\n inboundMetadata?: Record<string, unknown>;\n },\n content: string,\n options?: { skipResetCommands?: boolean },\n): Promise<SlashCommandOutcome> {\n const parsed = parseSlashCommand(content);\n if (!parsed) {\n return { matched: false, aggregatedText: '' };\n }\n if (options?.skipResetCommands && shouldSkipResetOverlapCommand(parsed.command, true)) {\n return { matched: false, aggregatedText: '' };\n }\n if (!commandRegistry.has(parsed.command)) {\n return { matched: false, aggregatedText: '' };\n }\n try {\n const { aggregatedText } = await deps.commandHandler.executeCommandAndAggregateReply(\n parsed.command,\n parsed.args,\n {\n sessionKey: ctx.sessionKey,\n channel: ctx.channel,\n chatId: ctx.chatId,\n senderId: ctx.senderId ?? '',\n isGroup: ctx.isGroup ?? false,\n inboundMetadata: ctx.inboundMetadata ?? {},\n },\n );\n return { matched: true, aggregatedText: aggregatedText ?? '', command: parsed.command };\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n deps.log.warn(\n { err, sessionKey: ctx.sessionKey, command: parsed.command },\n `Slash command failed: ${em}`,\n );\n return { matched: true, aggregatedText: `Command error: ${em}`, command: parsed.command };\n }\n}\n\nexport interface RunDirectAgentTurnDeps {\n sessionStore: SessionStore;\n agentManager: AgentInstanceGateway;\n modelManager: ModelManager;\n config: Config | undefined;\n}\n\nexport interface RunDirectAgentTurnInput {\n sessionKey: string;\n userMessage: AgentMessage;\n abortSignal?: AbortSignal;\n onEvent?: (event: EmbeddedStreamEvent) => void;\n}\n\nexport interface RunDirectAgentTurnResult {\n ok: boolean;\n errorMessage?: string;\n lastAssistantText?: string;\n}\n\n/**\n * Convert a user message into an embedded turn, including the standard memory\n * prefetch + background-review nudge wiring used by every direct entry point.\n */\nexport async function runDirectAgentTurn(\n deps: RunDirectAgentTurnDeps,\n input: RunDirectAgentTurnInput,\n): Promise<RunDirectAgentTurnResult> {\n const userPlain = extractAgentUserPlainText(input.userMessage);\n const userMessageForModel = await deps.agentManager.applyMemoryPrefetchToUserMessage(\n input.userMessage,\n input.sessionKey,\n );\n\n const result = await runEmbeddedTurnForSession({\n sessionKey: input.sessionKey,\n runId: crypto.randomUUID(),\n userMessage: userMessageForModel,\n sessionStore: deps.sessionStore,\n agentManager: deps.agentManager,\n modelManager: deps.modelManager,\n getConfig: () => deps.config,\n abortSignal: input.abortSignal,\n beforeTurn: () => deps.agentManager.beginBackgroundReviewUserTurn(input.sessionKey),\n onEvent: input.onEvent,\n });\n\n deps.agentManager.afterAgentTurn(input.sessionKey, userPlain);\n deps.agentManager.scheduleBackgroundReviewAfterUserTurn(input.sessionKey);\n\n return result;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA8BA,eAAsB,oBACpB,MACA,YACA,UACe;AACf,OAAM,KAAK,iCAAiC,WAAW;AACvD,OAAM,KAAK,6BAA6B,WAAW;AACnD,OAAM,KAAK,2BAA2B,YAAY,SAAS;;;;;;;AAsB7D,eAAsB,mBACpB,MACA,KAQA,SACA,SAC8B;CAC9B,MAAM,SAAS,kBAAkB,QAAQ;AACzC,KAAI,CAAC,OACH,QAAO;EAAE,SAAS;EAAO,gBAAgB;EAAI;AAE/C,KAAI,SAAS,qBAAqB,8BAA8B,OAAO,SAAS,KAAK,CACnF,QAAO;EAAE,SAAS;EAAO,gBAAgB;EAAI;AAE/C,KAAI,CAAC,gBAAgB,IAAI,OAAO,QAAQ,CACtC,QAAO;EAAE,SAAS;EAAO,gBAAgB;EAAI;AAE/C,KAAI;EACF,MAAM,EAAE,mBAAmB,MAAM,KAAK,eAAe,gCACnD,OAAO,SACP,OAAO,MACP;GACE,YAAY,IAAI;GAChB,SAAS,IAAI;GACb,QAAQ,IAAI;GACZ,UAAU,IAAI,YAAY;GAC1B,SAAS,IAAI,WAAW;GACxB,iBAAiB,IAAI,mBAAmB,EAAE;GAC3C,CACF;AACD,SAAO;GAAE,SAAS;GAAM,gBAAgB,kBAAkB;GAAI,SAAS,OAAO;GAAS;UAChF,KAAK;EACZ,MAAM,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC3D,OAAK,IAAI,KACP;GAAE;GAAK,YAAY,IAAI;GAAY,SAAS,OAAO;GAAS,EAC5D,yBAAyB,KAC1B;AACD,SAAO;GAAE,SAAS;GAAM,gBAAgB,kBAAkB;GAAM,SAAS,OAAO;GAAS;;;;;;;AA4B7F,eAAsB,mBACpB,MACA,OACmC;CACnC,MAAM,YAAY,0BAA0B,MAAM,YAAY;CAC9D,MAAM,sBAAsB,MAAM,KAAK,aAAa,iCAClD,MAAM,aACN,MAAM,WACP;CAED,MAAM,SAAS,MAAM,0BAA0B;EAC7C,YAAY,MAAM;EAClB,OAAO,OAAO,YAAY;EAC1B,aAAa;EACb,cAAc,KAAK;EACnB,cAAc,KAAK;EACnB,cAAc,KAAK;EACnB,iBAAiB,KAAK;EACtB,aAAa,MAAM;EACnB,kBAAkB,KAAK,aAAa,8BAA8B,MAAM,WAAW;EACnF,SAAS,MAAM;EAChB,CAAC;AAEF,MAAK,aAAa,eAAe,MAAM,YAAY,UAAU;AAC7D,MAAK,aAAa,sCAAsC,MAAM,WAAW;AAEzE,QAAO"}
1
+ {"version":3,"file":"direct-turn-helpers.js","names":[],"sources":["../../../../src/agent/service/direct-turn-helpers.ts"],"sourcesContent":["/**\n * Shared building blocks for the two direct-turn entry points (streaming\n * webchat SSE and one-shot CLI). Both flows do the same hydrate → maybe-slash\n * → run-embedded-turn → after-turn dance; this module captures that core so\n * the entry points only manage their I/O specifics (event sink, voice STT,\n * TTS, transcript persistence).\n */\n\nimport crypto from 'node:crypto';\n\nimport type { AgentMessage } from '@earendil-works/pi-agent-core';\n\nimport { commandRegistry } from '../../chat-commands/index.js';\nimport { parseSlashCommand } from '../../chat-commands/command-parse.js';\nimport { shouldSkipResetOverlapCommand } from '../../session/reset-triggers.js';\nimport { hydrateUserTurnForLlm, type TranscriptUserMessage } from '../inbound/attachment-pipeline.js';\nimport type { CommandHandler } from '../messaging/command-handler.js';\nimport type { SessionStore } from '../../session/index.js';\nimport type { Config } from '../../config/schema.js';\nimport type { AgentInstanceGateway } from '../agent-instance-gateway.js';\nimport type { ModelManager } from '../models/index.js';\nimport { extractAgentUserPlainText } from '../memory/user-message-text.js';\nimport { runEmbeddedTurnForSession } from '../embedded/run-for-session.js';\nimport type { EmbeddedStreamEvent } from '../embedded/types.js';\n\nexport interface HydratePerTurnStateDeps {\n hydrateSessionWorkspaceFromStore: (sessionKey: string) => Promise<void>;\n hydrateSessionModelFromStore: (sessionKey: string) => Promise<void>;\n applyResolvedThinkingLevel: (sessionKey: string, thinking?: string | null) => Promise<void>;\n}\n\n/** Workspace + model + thinking level — common prep before any direct turn. */\nexport async function hydratePerTurnState(\n deps: HydratePerTurnStateDeps,\n sessionKey: string,\n thinking?: string,\n): Promise<void> {\n await deps.hydrateSessionWorkspaceFromStore(sessionKey);\n await deps.hydrateSessionModelFromStore(sessionKey);\n await deps.applyResolvedThinkingLevel(sessionKey, thinking);\n}\n\nexport interface SlashCommandOutcome {\n /** True if the input parsed as a registered slash command (handled or not). */\n matched: boolean;\n /** Aggregated user-visible reply text (assistant view). */\n aggregatedText: string;\n /** The parsed command name when matched. */\n command?: string;\n}\n\nexport interface TryRunSlashCommandDeps {\n commandHandler: Pick<CommandHandler, 'executeCommandAndAggregateReply'>;\n log: { warn: (obj: Record<string, unknown>, msg: string) => void };\n}\n\n/**\n * Detect a slash command and, if registered, execute it and aggregate the reply\n * text. Errors thrown inside the command surface as `aggregatedText` so callers\n * can persist the receipt or stream it as a token.\n */\nexport async function tryRunSlashCommand(\n deps: TryRunSlashCommandDeps,\n ctx: {\n sessionKey: string;\n channel: string;\n chatId: string;\n senderId?: string;\n isGroup?: boolean;\n inboundMetadata?: Record<string, unknown>;\n },\n content: string,\n options?: { skipResetCommands?: boolean },\n): Promise<SlashCommandOutcome> {\n const parsed = parseSlashCommand(content);\n if (!parsed) {\n return { matched: false, aggregatedText: '' };\n }\n if (options?.skipResetCommands && shouldSkipResetOverlapCommand(parsed.command, true)) {\n return { matched: false, aggregatedText: '' };\n }\n if (!commandRegistry.has(parsed.command)) {\n return { matched: false, aggregatedText: '' };\n }\n try {\n const { aggregatedText } = await deps.commandHandler.executeCommandAndAggregateReply(\n parsed.command,\n parsed.args,\n {\n sessionKey: ctx.sessionKey,\n channel: ctx.channel,\n chatId: ctx.chatId,\n senderId: ctx.senderId ?? '',\n isGroup: ctx.isGroup ?? false,\n inboundMetadata: ctx.inboundMetadata ?? {},\n },\n );\n return { matched: true, aggregatedText: aggregatedText ?? '', command: parsed.command };\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n deps.log.warn(\n { err, sessionKey: ctx.sessionKey, command: parsed.command },\n `Slash command failed: ${em}`,\n );\n return { matched: true, aggregatedText: `Command error: ${em}`, command: parsed.command };\n }\n}\n\nexport interface RunDirectAgentTurnDeps {\n sessionStore: SessionStore;\n agentManager: AgentInstanceGateway;\n modelManager: ModelManager;\n config: Config | undefined;\n}\n\nexport interface RunDirectAgentTurnInput {\n sessionKey: string;\n userMessage: AgentMessage;\n abortSignal?: AbortSignal;\n onEvent?: (event: EmbeddedStreamEvent) => void;\n}\n\nexport interface RunDirectAgentTurnResult {\n ok: boolean;\n errorMessage?: string;\n lastAssistantText?: string;\n}\n\n/**\n * Convert a user message into an embedded turn, including the standard memory\n * prefetch + background-review nudge wiring used by every direct entry point.\n */\nexport async function runDirectAgentTurn(\n deps: RunDirectAgentTurnDeps,\n input: RunDirectAgentTurnInput,\n): Promise<RunDirectAgentTurnResult> {\n const userPlain = extractAgentUserPlainText(input.userMessage);\n const userMessageForModel = await deps.agentManager.applyMemoryPrefetchToUserMessage(\n input.userMessage,\n input.sessionKey,\n );\n\n const modelRef = deps.modelManager.getModelForSession(input.sessionKey);\n const llmTurn = await hydrateUserTurnForLlm({\n message: input.userMessage as TranscriptUserMessage,\n modelRef,\n });\n const llmImages = llmTurn.images;\n\n const result = await runEmbeddedTurnForSession({\n sessionKey: input.sessionKey,\n runId: crypto.randomUUID(),\n userMessage: userMessageForModel,\n llmImages,\n sessionStore: deps.sessionStore,\n agentManager: deps.agentManager,\n modelManager: deps.modelManager,\n getConfig: () => deps.config,\n abortSignal: input.abortSignal,\n beforeTurn: () => deps.agentManager.beginBackgroundReviewUserTurn(input.sessionKey),\n onEvent: input.onEvent,\n });\n\n deps.agentManager.afterAgentTurn(input.sessionKey, userPlain);\n deps.agentManager.scheduleBackgroundReviewAfterUserTurn(input.sessionKey);\n\n return result;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAgCA,eAAsB,oBACpB,MACA,YACA,UACe;AACf,OAAM,KAAK,iCAAiC,WAAW;AACvD,OAAM,KAAK,6BAA6B,WAAW;AACnD,OAAM,KAAK,2BAA2B,YAAY,SAAS;;;;;;;AAsB7D,eAAsB,mBACpB,MACA,KAQA,SACA,SAC8B;CAC9B,MAAM,SAAS,kBAAkB,QAAQ;AACzC,KAAI,CAAC,OACH,QAAO;EAAE,SAAS;EAAO,gBAAgB;EAAI;AAE/C,KAAI,SAAS,qBAAqB,8BAA8B,OAAO,SAAS,KAAK,CACnF,QAAO;EAAE,SAAS;EAAO,gBAAgB;EAAI;AAE/C,KAAI,CAAC,gBAAgB,IAAI,OAAO,QAAQ,CACtC,QAAO;EAAE,SAAS;EAAO,gBAAgB;EAAI;AAE/C,KAAI;EACF,MAAM,EAAE,mBAAmB,MAAM,KAAK,eAAe,gCACnD,OAAO,SACP,OAAO,MACP;GACE,YAAY,IAAI;GAChB,SAAS,IAAI;GACb,QAAQ,IAAI;GACZ,UAAU,IAAI,YAAY;GAC1B,SAAS,IAAI,WAAW;GACxB,iBAAiB,IAAI,mBAAmB,EAAE;GAC3C,CACF;AACD,SAAO;GAAE,SAAS;GAAM,gBAAgB,kBAAkB;GAAI,SAAS,OAAO;GAAS;UAChF,KAAK;EACZ,MAAM,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC3D,OAAK,IAAI,KACP;GAAE;GAAK,YAAY,IAAI;GAAY,SAAS,OAAO;GAAS,EAC5D,yBAAyB,KAC1B;AACD,SAAO;GAAE,SAAS;GAAM,gBAAgB,kBAAkB;GAAM,SAAS,OAAO;GAAS;;;;;;;AA4B7F,eAAsB,mBACpB,MACA,OACmC;CACnC,MAAM,YAAY,0BAA0B,MAAM,YAAY;CAC9D,MAAM,sBAAsB,MAAM,KAAK,aAAa,iCAClD,MAAM,aACN,MAAM,WACP;CAED,MAAM,WAAW,KAAK,aAAa,mBAAmB,MAAM,WAAW;CAKvE,MAAM,aAAY,MAJI,sBAAsB;EAC1C,SAAS,MAAM;EACf;EACD,CAAC,EACwB;CAE1B,MAAM,SAAS,MAAM,0BAA0B;EAC7C,YAAY,MAAM;EAClB,OAAO,OAAO,YAAY;EAC1B,aAAa;EACb;EACA,cAAc,KAAK;EACnB,cAAc,KAAK;EACnB,cAAc,KAAK;EACnB,iBAAiB,KAAK;EACtB,aAAa,MAAM;EACnB,kBAAkB,KAAK,aAAa,8BAA8B,MAAM,WAAW;EACnF,SAAS,MAAM;EAChB,CAAC;AAEF,MAAK,aAAa,eAAe,MAAM,YAAY,UAAU;AAC7D,MAAK,aAAa,sCAAsC,MAAM,WAAW;AAEzE,QAAO"}
@@ -3,8 +3,8 @@ import type { AgentInstanceGateway } from '../agent-instance-gateway.js';
3
3
  import type { ModelManager } from '../models/index.js';
4
4
  import type { SessionStore } from '../../session/index.js';
5
5
  import type { Config } from '../../config/schema.js';
6
- import { type DirectInboundAttachment } from './build-direct-message-content.js';
7
6
  import type { ProcessDirectStreamLog } from './process-direct-streaming.js';
7
+ import type { InboundAttachmentInput, MediaRef } from '../../channels/attachments/inbound-persist.js';
8
8
  export type RunProcessDirectDeps = {
9
9
  log: ProcessDirectStreamLog;
10
10
  config: Config;
@@ -19,7 +19,7 @@ export type RunProcessDirectDeps = {
19
19
  sessionStore: SessionStore;
20
20
  modelManager: ModelManager;
21
21
  applyResolvedThinkingLevel: (sessionKey: string, thinking?: string | null) => Promise<void>;
22
- prepareInboundAttachments: (sessionKey: string, attachments?: DirectInboundAttachment[]) => Promise<DirectInboundAttachment[] | undefined>;
22
+ prepareInboundAttachments: (sessionKey: string, attachments?: InboundAttachmentInput[]) => Promise<MediaRef[] | undefined>;
23
23
  commandHandler: Pick<CommandHandler, 'executeCommandAndAggregateReply'>;
24
24
  onTurnComplete?: (sessionKey: string, lastAssistantText?: string) => void;
25
25
  endDirectRequestContext: () => void;
@@ -31,6 +31,6 @@ export type RunProcessDirectDeps = {
31
31
  export declare function runProcessDirect(deps: RunProcessDirectDeps, input: {
32
32
  content: string;
33
33
  sessionKey: string;
34
- attachments?: DirectInboundAttachment[];
34
+ attachments?: InboundAttachmentInput[];
35
35
  thinking?: string;
36
36
  }): Promise<string>;
@@ -1,3 +1,4 @@
1
+ import { clearPendingTranscriptUserMessage, setPendingTranscriptUserMessage } from "../inbound/attachment-pipeline.js";
1
2
  import { initSessionTurn } from "../../session/init-session-turn.js";
2
3
  import "../../session/index.js";
3
4
  import { buildDirectUserMessageContent } from "./build-direct-message-content.js";
@@ -37,25 +38,29 @@ async function runProcessDirect(deps, input) {
37
38
  });
38
39
  return slash.aggregatedText ?? "";
39
40
  }
40
- const userMessage = {
41
- role: "user",
42
- content: await buildDirectUserMessageContent({
43
- content: turnBody.trimStart().startsWith("/skill:") ? deps.agentManager.expandSkillUserText(turnBody) : turnBody,
44
- attachments: prepared,
45
- sessionKey: input.sessionKey,
46
- config: deps.config,
47
- agentManager: deps.agentManager,
48
- modelManager: deps.modelManager
49
- }),
50
- timestamp: Date.now()
51
- };
52
- const result = await runDirectAgentTurn({
53
- ...deps,
54
- config: deps.config
55
- }, {
41
+ const userMessage = await buildDirectUserMessageContent({
42
+ content: turnBody.trimStart().startsWith("/skill:") ? deps.agentManager.expandSkillUserText(turnBody) : turnBody,
43
+ attachments: prepared,
56
44
  sessionKey: input.sessionKey,
57
- userMessage
45
+ config: deps.config,
46
+ agentManager: deps.agentManager,
47
+ modelManager: deps.modelManager
58
48
  });
49
+ const pendingUserMessage = userMessage;
50
+ setPendingTranscriptUserMessage(input.sessionKey, pendingUserMessage);
51
+ const result = await (async () => {
52
+ try {
53
+ return await runDirectAgentTurn({
54
+ ...deps,
55
+ config: deps.config
56
+ }, {
57
+ sessionKey: input.sessionKey,
58
+ userMessage
59
+ });
60
+ } finally {
61
+ clearPendingTranscriptUserMessage(input.sessionKey, pendingUserMessage);
62
+ }
63
+ })();
59
64
  if (result.lastAssistantText) deps.onTurnComplete?.(input.sessionKey, result.lastAssistantText);
60
65
  return result.lastAssistantText ?? "";
61
66
  } finally {
@@ -1 +1 @@
1
- {"version":3,"file":"process-direct-one-shot.js","names":[],"sources":["../../../../src/agent/service/process-direct-one-shot.ts"],"sourcesContent":["import type { AgentMessage } from '@earendil-works/pi-agent-core';\n\nimport type { CommandHandler } from '../messaging/command-handler.js';\nimport type { AgentInstanceGateway } from '../agent-instance-gateway.js';\nimport type { ModelManager } from '../models/index.js';\nimport type { SessionStore } from '../../session/index.js';\nimport type { Config } from '../../config/schema.js';\nimport { initSessionTurn } from '../../session/index.js';\nimport { buildDirectUserMessageContent, type DirectInboundAttachment } from './build-direct-message-content.js';\nimport type { ProcessDirectStreamLog } from './process-direct-streaming.js';\nimport {\n hydratePerTurnState,\n runDirectAgentTurn,\n tryRunSlashCommand,\n} from './direct-turn-helpers.js';\n\nexport type RunProcessDirectDeps = {\n log: ProcessDirectStreamLog;\n config: Config;\n parseSessionKey: (sessionKey: string) => { channel: string; chatId: string };\n initSessionContext: (sessionKey: string, channel: string, chatId: string) => void;\n hydrateSessionWorkspaceFromStore: (sessionKey: string) => Promise<void>;\n hydrateSessionModelFromStore: (sessionKey: string) => Promise<void>;\n agentManager: AgentInstanceGateway;\n sessionStore: SessionStore;\n modelManager: ModelManager;\n applyResolvedThinkingLevel: (sessionKey: string, thinking?: string | null) => Promise<void>;\n prepareInboundAttachments: (\n sessionKey: string,\n attachments?: DirectInboundAttachment[],\n ) => Promise<DirectInboundAttachment[] | undefined>;\n commandHandler: Pick<CommandHandler, 'executeCommandAndAggregateReply'>;\n onTurnComplete?: (sessionKey: string, lastAssistantText?: string) => void;\n endDirectRequestContext: () => void;\n resetSession: (sessionKey: string) => Promise<{ sessionId: string; previousSessionId: string } | null>;\n};\n\nexport async function runProcessDirect(\n deps: RunProcessDirectDeps,\n input: {\n content: string;\n sessionKey: string;\n attachments?: DirectInboundAttachment[];\n thinking?: string;\n },\n): Promise<string> {\n const { channel, chatId } = deps.parseSessionKey(input.sessionKey);\n deps.initSessionContext(input.sessionKey, channel, chatId);\n\n try {\n let turnBody = input.content;\n let resetTriggeredAtInit = false;\n const turn = await initSessionTurn({\n cfg: deps.config,\n sessionKey: input.sessionKey,\n body: input.content,\n resetSession: deps.resetSession,\n });\n resetTriggeredAtInit = turn.resetTriggered;\n if (turn.bareReset && turn.ackMessage) {\n return turn.ackMessage;\n }\n turnBody = turn.bodyStripped;\n\n await hydratePerTurnState(deps, input.sessionKey, input.thinking);\n const prepared = await deps.prepareInboundAttachments(input.sessionKey, input.attachments);\n\n const slash = await tryRunSlashCommand(\n deps,\n { sessionKey: input.sessionKey, channel, chatId },\n turnBody,\n { skipResetCommands: resetTriggeredAtInit },\n );\n if (slash.matched) {\n const trimmed = slash.aggregatedText.trim();\n if (trimmed) {\n await deps.sessionStore.appendTranscriptMessage(input.sessionKey, {\n role: 'assistant',\n content: [{ type: 'text', text: trimmed }],\n timestamp: Date.now(),\n } as AgentMessage);\n }\n return slash.aggregatedText ?? '';\n }\n\n const textForDirect = turnBody.trimStart().startsWith('/skill:')\n ? deps.agentManager.expandSkillUserText(turnBody)\n : turnBody;\n const messageContent = await buildDirectUserMessageContent({\n content: textForDirect,\n attachments: prepared,\n sessionKey: input.sessionKey,\n config: deps.config,\n agentManager: deps.agentManager,\n modelManager: deps.modelManager,\n });\n\n const userMessage = {\n role: 'user' as const,\n content: messageContent,\n timestamp: Date.now(),\n };\n\n const result = await runDirectAgentTurn(\n { ...deps, config: deps.config },\n { sessionKey: input.sessionKey, userMessage },\n );\n\n if (result.lastAssistantText) {\n deps.onTurnComplete?.(input.sessionKey, result.lastAssistantText);\n }\n\n return result.lastAssistantText ?? '';\n } finally {\n deps.endDirectRequestContext();\n }\n}\n"],"mappings":";;;;;AAqCA,eAAsB,iBACpB,MACA,OAMiB;CACjB,MAAM,EAAE,SAAS,WAAW,KAAK,gBAAgB,MAAM,WAAW;AAClE,MAAK,mBAAmB,MAAM,YAAY,SAAS,OAAO;AAE1D,KAAI;EACF,IAAI,WAAW,MAAM;EACrB,IAAI,uBAAuB;EAC3B,MAAM,OAAO,MAAM,gBAAgB;GACjC,KAAK,KAAK;GACV,YAAY,MAAM;GAClB,MAAM,MAAM;GACZ,cAAc,KAAK;GACpB,CAAC;AACF,yBAAuB,KAAK;AAC5B,MAAI,KAAK,aAAa,KAAK,WACzB,QAAO,KAAK;AAEd,aAAW,KAAK;AAEhB,QAAM,oBAAoB,MAAM,MAAM,YAAY,MAAM,SAAS;EACjE,MAAM,WAAW,MAAM,KAAK,0BAA0B,MAAM,YAAY,MAAM,YAAY;EAE1F,MAAM,QAAQ,MAAM,mBAClB,MACA;GAAE,YAAY,MAAM;GAAY;GAAS;GAAQ,EACjD,UACA,EAAE,mBAAmB,sBAAsB,CAC5C;AACD,MAAI,MAAM,SAAS;GACjB,MAAM,UAAU,MAAM,eAAe,MAAM;AAC3C,OAAI,QACF,OAAM,KAAK,aAAa,wBAAwB,MAAM,YAAY;IAChE,MAAM;IACN,SAAS,CAAC;KAAE,MAAM;KAAQ,MAAM;KAAS,CAAC;IAC1C,WAAW,KAAK,KAAK;IACtB,CAAiB;AAEpB,UAAO,MAAM,kBAAkB;;EAejC,MAAM,cAAc;GAClB,MAAM;GACN,SAAS,MAXkB,8BAA8B;IACzD,SAJoB,SAAS,WAAW,CAAC,WAAW,UAAU,GAC5D,KAAK,aAAa,oBAAoB,SAAS,GAC/C;IAGF,aAAa;IACb,YAAY,MAAM;IAClB,QAAQ,KAAK;IACb,cAAc,KAAK;IACnB,cAAc,KAAK;IACpB,CAAC;GAKA,WAAW,KAAK,KAAK;GACtB;EAED,MAAM,SAAS,MAAM,mBACnB;GAAE,GAAG;GAAM,QAAQ,KAAK;GAAQ,EAChC;GAAE,YAAY,MAAM;GAAY;GAAa,CAC9C;AAED,MAAI,OAAO,kBACT,MAAK,iBAAiB,MAAM,YAAY,OAAO,kBAAkB;AAGnE,SAAO,OAAO,qBAAqB;WAC3B;AACR,OAAK,yBAAyB"}
1
+ {"version":3,"file":"process-direct-one-shot.js","names":[],"sources":["../../../../src/agent/service/process-direct-one-shot.ts"],"sourcesContent":["import type { AgentMessage } from '@earendil-works/pi-agent-core';\n\nimport type { CommandHandler } from '../messaging/command-handler.js';\nimport type { AgentInstanceGateway } from '../agent-instance-gateway.js';\nimport type { ModelManager } from '../models/index.js';\nimport type { SessionStore } from '../../session/index.js';\nimport type { Config } from '../../config/schema.js';\nimport { initSessionTurn } from '../../session/index.js';\nimport {\n buildDirectUserMessageContent,\n} from './build-direct-message-content.js';\nimport type { ProcessDirectStreamLog } from './process-direct-streaming.js';\nimport type {\n InboundAttachmentInput,\n MediaRef,\n} from '../../channels/attachments/inbound-persist.js';\nimport {\n hydratePerTurnState,\n runDirectAgentTurn,\n tryRunSlashCommand,\n} from './direct-turn-helpers.js';\nimport {\n clearPendingTranscriptUserMessage,\n setPendingTranscriptUserMessage,\n type TranscriptUserMessage,\n} from '../inbound/attachment-pipeline.js';\n\nexport type RunProcessDirectDeps = {\n log: ProcessDirectStreamLog;\n config: Config;\n parseSessionKey: (sessionKey: string) => { channel: string; chatId: string };\n initSessionContext: (sessionKey: string, channel: string, chatId: string) => void;\n hydrateSessionWorkspaceFromStore: (sessionKey: string) => Promise<void>;\n hydrateSessionModelFromStore: (sessionKey: string) => Promise<void>;\n agentManager: AgentInstanceGateway;\n sessionStore: SessionStore;\n modelManager: ModelManager;\n applyResolvedThinkingLevel: (sessionKey: string, thinking?: string | null) => Promise<void>;\n prepareInboundAttachments: (\n sessionKey: string,\n attachments?: InboundAttachmentInput[],\n ) => Promise<MediaRef[] | undefined>;\n commandHandler: Pick<CommandHandler, 'executeCommandAndAggregateReply'>;\n onTurnComplete?: (sessionKey: string, lastAssistantText?: string) => void;\n endDirectRequestContext: () => void;\n resetSession: (sessionKey: string) => Promise<{ sessionId: string; previousSessionId: string } | null>;\n};\n\nexport async function runProcessDirect(\n deps: RunProcessDirectDeps,\n input: {\n content: string;\n sessionKey: string;\n attachments?: InboundAttachmentInput[];\n thinking?: string;\n },\n): Promise<string> {\n const { channel, chatId } = deps.parseSessionKey(input.sessionKey);\n deps.initSessionContext(input.sessionKey, channel, chatId);\n\n try {\n let turnBody = input.content;\n let resetTriggeredAtInit = false;\n const turn = await initSessionTurn({\n cfg: deps.config,\n sessionKey: input.sessionKey,\n body: input.content,\n resetSession: deps.resetSession,\n });\n resetTriggeredAtInit = turn.resetTriggered;\n if (turn.bareReset && turn.ackMessage) {\n return turn.ackMessage;\n }\n turnBody = turn.bodyStripped;\n\n await hydratePerTurnState(deps, input.sessionKey, input.thinking);\n const prepared = await deps.prepareInboundAttachments(input.sessionKey, input.attachments);\n\n const slash = await tryRunSlashCommand(\n deps,\n { sessionKey: input.sessionKey, channel, chatId },\n turnBody,\n { skipResetCommands: resetTriggeredAtInit },\n );\n if (slash.matched) {\n const trimmed = slash.aggregatedText.trim();\n if (trimmed) {\n await deps.sessionStore.appendTranscriptMessage(input.sessionKey, {\n role: 'assistant',\n content: [{ type: 'text', text: trimmed }],\n timestamp: Date.now(),\n } as AgentMessage);\n }\n return slash.aggregatedText ?? '';\n }\n\n const textForDirect = turnBody.trimStart().startsWith('/skill:')\n ? deps.agentManager.expandSkillUserText(turnBody)\n : turnBody;\n const userMessage = await buildDirectUserMessageContent({\n content: textForDirect,\n attachments: prepared,\n sessionKey: input.sessionKey,\n config: deps.config,\n agentManager: deps.agentManager,\n modelManager: deps.modelManager,\n });\n\n const pendingUserMessage = userMessage as TranscriptUserMessage;\n setPendingTranscriptUserMessage(input.sessionKey, pendingUserMessage);\n\n const result = await (async () => {\n try {\n return await runDirectAgentTurn(\n { ...deps, config: deps.config },\n {\n sessionKey: input.sessionKey,\n userMessage,\n },\n );\n } finally {\n clearPendingTranscriptUserMessage(input.sessionKey, pendingUserMessage);\n }\n })();\n\n if (result.lastAssistantText) {\n deps.onTurnComplete?.(input.sessionKey, result.lastAssistantText);\n }\n\n return result.lastAssistantText ?? '';\n } finally {\n deps.endDirectRequestContext();\n }\n}\n"],"mappings":";;;;;;AAgDA,eAAsB,iBACpB,MACA,OAMiB;CACjB,MAAM,EAAE,SAAS,WAAW,KAAK,gBAAgB,MAAM,WAAW;AAClE,MAAK,mBAAmB,MAAM,YAAY,SAAS,OAAO;AAE1D,KAAI;EACF,IAAI,WAAW,MAAM;EACrB,IAAI,uBAAuB;EAC3B,MAAM,OAAO,MAAM,gBAAgB;GACjC,KAAK,KAAK;GACV,YAAY,MAAM;GAClB,MAAM,MAAM;GACZ,cAAc,KAAK;GACpB,CAAC;AACF,yBAAuB,KAAK;AAC5B,MAAI,KAAK,aAAa,KAAK,WACzB,QAAO,KAAK;AAEd,aAAW,KAAK;AAEhB,QAAM,oBAAoB,MAAM,MAAM,YAAY,MAAM,SAAS;EACjE,MAAM,WAAW,MAAM,KAAK,0BAA0B,MAAM,YAAY,MAAM,YAAY;EAE1F,MAAM,QAAQ,MAAM,mBAClB,MACA;GAAE,YAAY,MAAM;GAAY;GAAS;GAAQ,EACjD,UACA,EAAE,mBAAmB,sBAAsB,CAC5C;AACD,MAAI,MAAM,SAAS;GACjB,MAAM,UAAU,MAAM,eAAe,MAAM;AAC3C,OAAI,QACF,OAAM,KAAK,aAAa,wBAAwB,MAAM,YAAY;IAChE,MAAM;IACN,SAAS,CAAC;KAAE,MAAM;KAAQ,MAAM;KAAS,CAAC;IAC1C,WAAW,KAAK,KAAK;IACtB,CAAiB;AAEpB,UAAO,MAAM,kBAAkB;;EAMjC,MAAM,cAAc,MAAM,8BAA8B;GACtD,SAJoB,SAAS,WAAW,CAAC,WAAW,UAAU,GAC5D,KAAK,aAAa,oBAAoB,SAAS,GAC/C;GAGF,aAAa;GACb,YAAY,MAAM;GAClB,QAAQ,KAAK;GACb,cAAc,KAAK;GACnB,cAAc,KAAK;GACpB,CAAC;EAEF,MAAM,qBAAqB;AAC3B,kCAAgC,MAAM,YAAY,mBAAmB;EAErE,MAAM,SAAS,OAAO,YAAY;AAChC,OAAI;AACF,WAAO,MAAM,mBACX;KAAE,GAAG;KAAM,QAAQ,KAAK;KAAQ,EAChC;KACE,YAAY,MAAM;KAClB;KACD,CACF;aACO;AACR,sCAAkC,MAAM,YAAY,mBAAmB;;MAEvE;AAEJ,MAAI,OAAO,kBACT,MAAK,iBAAiB,MAAM,YAAY,OAAO,kBAAkB;AAGnE,SAAO,OAAO,qBAAqB;WAC3B;AACR,OAAK,yBAAyB"}
@@ -1,18 +1,12 @@
1
1
  import type { Config } from '../../config/schema.js';
2
- import type { InternalAttachmentRoots } from '../../channels/attachments/inbound-persist.js';
2
+ import type { InboundAttachmentInput, MediaRef } from '../../channels/attachments/inbound-persist.js';
3
3
  import { type SessionConfigStore, type SessionStore } from '../../session/index.js';
4
4
  import type { SessionContext } from '../session/index.js';
5
5
  import type { AgentInstanceGateway } from '../agent-instance-gateway.js';
6
6
  import type { CommandHandler } from '../messaging/command-handler.js';
7
7
  import type { ModelManager } from '../models/index.js';
8
- export type DirectStreamInboundAttachment = {
9
- type: string;
10
- mimeType?: string;
11
- data?: string;
12
- name?: string;
13
- size?: number;
14
- workspaceRelativePath?: string;
15
- };
8
+ import { type TranscriptUserMessage } from '../inbound/attachment-pipeline.js';
9
+ export type DirectStreamInboundAttachment = InboundAttachmentInput;
16
10
  export type ProcessDirectStreamLog = {
17
11
  info: (obj: Record<string, unknown>, msg: string) => void;
18
12
  warn: (obj: Record<string, unknown>, msg: string) => void;
@@ -38,17 +32,9 @@ export interface ProcessDirectStreamingDeps {
38
32
  applyResolvedThinkingLevel: (sessionKey: string, thinking?: string | null) => Promise<void>;
39
33
  getConfig: () => Config | undefined;
40
34
  sessionConfigStore: SessionConfigStore;
41
- attachmentRootsForSession: (sessionKey: string) => InternalAttachmentRoots;
42
35
  commandHandler: Pick<CommandHandler, 'executeCommandAndAggregateReply'>;
43
- prepareInboundAttachments: (sessionKey: string, attachments?: DirectStreamInboundAttachment[]) => Promise<DirectStreamInboundAttachment[] | undefined>;
44
- buildMessageContent: (content: string, attachments: DirectStreamInboundAttachment[] | undefined, sessionKey: string) => Promise<Array<{
45
- type: 'text';
46
- text: string;
47
- } | {
48
- type: 'image';
49
- data: string;
50
- mimeType: string;
51
- }>>;
36
+ prepareInboundAttachments: (sessionKey: string, attachments?: DirectStreamInboundAttachment[]) => Promise<MediaRef[] | undefined>;
37
+ buildTranscriptUserMessage: (content: string, prepared: MediaRef[] | undefined, sessionKey: string) => Promise<TranscriptUserMessage>;
52
38
  recordPersistentGoalStreamOutcome?: (sessionKey: string, outcome: {
53
39
  skipPersistentGoalPostTurn: boolean;
54
40
  }) => void;
@@ -58,7 +44,7 @@ export interface ProcessDirectStreamingDeps {
58
44
  reloadWebchatTranscript?: (sessionKey: string) => void;
59
45
  maybeEmitWebchatTts: (sessionKey: string, hadInboundVoice: boolean) => Promise<{
60
46
  type: 'tts_audio';
61
- workspaceRelativePath: string;
47
+ uri: string;
62
48
  mimeType: string;
63
49
  name: string;
64
50
  } | null>;
@@ -1,3 +1,5 @@
1
+ import { readAgentMessageContent } from "../memory/agent-message-access.js";
2
+ import { clearPendingTranscriptUserMessage, setPendingTranscriptUserMessage } from "../inbound/attachment-pipeline.js";
1
3
  import { formatAgentRunErrorForClient } from "../client-error-format.js";
2
4
  import { resolveEffectiveReasoningLevel } from "../../session/thinking-resolve.js";
3
5
  import { initSessionTurn } from "../../session/init-session-turn.js";
@@ -77,20 +79,20 @@ async function* runProcessDirectStreaming(deps, input) {
77
79
  }
78
80
  const prepared = await deps.prepareInboundAttachments(sessionKey, input.attachments);
79
81
  const sttCfg = mergeSttConfigFromAppConfig(deps.getConfig()?.tools?.media?.audio, deps.getConfig()?.tools?.media);
80
- const voiceMerge = await mergeVoiceTranscriptsIntoUserText(deps.attachmentRootsForSession(sessionKey), prepared, turnBody, sttCfg);
82
+ const voiceMerge = await mergeVoiceTranscriptsIntoUserText(prepared, turnBody, sttCfg);
81
83
  mergedUserText = voiceMerge.text;
82
84
  inboundVoice = voiceMerge.inboundVoice;
83
85
  if (inboundVoice) {
84
86
  const transcriptParts = [voiceMerge.voiceTranscripts.filter(Boolean).join("\n"), turnBody.trim()].filter(Boolean);
85
- const voiceAttachments = (prepared ?? []).filter(isVoiceLikeAttachment).map((att) => ({
86
- workspaceRelativePath: att.workspaceRelativePath,
87
+ const voiceMedia = (prepared ?? []).filter(isVoiceLikeAttachment).map((att) => ({
88
+ uri: att.uri,
87
89
  mimeType: att.mimeType,
88
90
  name: att.name
89
91
  }));
90
92
  pushVisible({
91
93
  type: "user_transcript",
92
94
  text: transcriptParts.join("\n\n"),
93
- attachments: voiceAttachments
95
+ media: voiceMedia
94
96
  });
95
97
  }
96
98
  const armAbort = () => {
@@ -112,7 +114,8 @@ async function* runProcessDirectStreaming(deps, input) {
112
114
  channel,
113
115
  chatId,
114
116
  senderId: context.senderId,
115
- isGroup: context.isGroup
117
+ isGroup: context.isGroup,
118
+ inboundMetadata: context.metadata
116
119
  }, mergedUserText, { skipResetCommands: resetTriggeredAtInit });
117
120
  if (slash.matched) {
118
121
  ranSlashCommand = true;
@@ -133,48 +136,44 @@ async function* runProcessDirectStreaming(deps, input) {
133
136
  return;
134
137
  }
135
138
  const textForAgent = mergedUserText.trimStart().startsWith("/skill:") ? deps.agentManager.expandSkillUserText(mergedUserText) : mergedUserText;
136
- const userMessage = {
137
- role: "user",
138
- content: await deps.buildMessageContent(textForAgent, prepared, sessionKey),
139
- timestamp: Date.now()
140
- };
139
+ const userMessage = await deps.buildTranscriptUserMessage(textForAgent, prepared, sessionKey);
141
140
  if (channel === "webchat") {
142
141
  pushVisible({
143
142
  type: "user_message",
144
- timestamp: userMessage.timestamp,
145
- content: userMessage.content,
146
- attachments: prepared?.map((att) => ({
147
- type: att.type,
148
- mimeType: att.mimeType,
149
- name: att.name,
150
- size: att.size,
151
- workspaceRelativePath: att.workspaceRelativePath
152
- }))
143
+ timestamp: userMessage.timestamp ?? Date.now(),
144
+ content: readAgentMessageContent(userMessage),
145
+ media: userMessage.media
153
146
  });
154
147
  if (textForAgent.trim()) deps.enqueueProvisionalSessionTitle?.(sessionKey, textForAgent);
155
148
  }
156
- const result = await runDirectAgentTurn({
157
- sessionStore: deps.sessionStore,
158
- agentManager: deps.agentManager,
159
- modelManager: deps.modelManager,
160
- config: deps.getConfig()
161
- }, {
162
- sessionKey,
163
- userMessage,
164
- abortSignal: signal,
165
- onEvent: (embeddedEvent) => {
166
- const mapped = mapEmbeddedEventToGatewaySse(embeddedEvent);
167
- if (mapped) {
168
- if (mapped.type === "error" && typeof mapped.content === "string") mapped.content = formatStreamError(mapped.content);
169
- pushVisible(mapped);
149
+ const pendingUserMessage = userMessage;
150
+ setPendingTranscriptUserMessage(sessionKey, pendingUserMessage);
151
+ try {
152
+ const result = await runDirectAgentTurn({
153
+ sessionStore: deps.sessionStore,
154
+ agentManager: deps.agentManager,
155
+ modelManager: deps.modelManager,
156
+ config: deps.getConfig()
157
+ }, {
158
+ sessionKey,
159
+ userMessage,
160
+ abortSignal: signal,
161
+ onEvent: (embeddedEvent) => {
162
+ const mapped = mapEmbeddedEventToGatewaySse(embeddedEvent);
163
+ if (mapped) {
164
+ if (mapped.type === "error" && typeof mapped.content === "string") mapped.content = formatStreamError(mapped.content);
165
+ pushVisible(mapped);
166
+ }
170
167
  }
171
- }
172
- });
173
- if (result.lastAssistantText) deps.onTurnComplete?.(sessionKey, result.lastAssistantText);
174
- if (!result.ok && result.errorMessage && !abortHandled) pushVisible({
175
- type: "error",
176
- content: formatStreamError(result.errorMessage)
177
- });
168
+ });
169
+ if (result.lastAssistantText) deps.onTurnComplete?.(sessionKey, result.lastAssistantText);
170
+ if (!result.ok && result.errorMessage && !abortHandled) pushVisible({
171
+ type: "error",
172
+ content: formatStreamError(result.errorMessage)
173
+ });
174
+ } finally {
175
+ clearPendingTranscriptUserMessage(sessionKey, pendingUserMessage);
176
+ }
178
177
  } catch (err) {
179
178
  if (!abortHandled) pushVisible({
180
179
  type: "error",
@@ -1 +1 @@
1
- {"version":3,"file":"process-direct-streaming.js","names":[],"sources":["../../../../src/agent/service/process-direct-streaming.ts"],"sourcesContent":["import type { AgentMessage } from '@earendil-works/pi-agent-core';\n\nimport type { Config } from '../../config/schema.js';\nimport type { InternalAttachmentRoots } from '../../channels/attachments/inbound-persist.js';\nimport {\n isVoiceLikeAttachment,\n mergeVoiceTranscriptsIntoUserText,\n mergeSttConfigFromAppConfig,\n} from '../../channels/attachments/voice-stt-webchat.js';\nimport {\n resolveEffectiveReasoningLevel,\n initSessionTurn,\n type SessionConfigStore,\n type SessionStore,\n} from '../../session/index.js';\nimport type { SessionContext } from '../session/index.js';\nimport { applyReasoningVisibilityToSseEvent } from '../streaming/reasoning-visibility-sse.js';\nimport type { ReasoningLevel } from '../transcript/thinking-types.js';\nimport { formatAgentRunErrorForClient } from '../client-error-format.js';\nimport { abortEmbeddedRun } from '../embedded/runs.js';\nimport { mapEmbeddedEventToGatewaySse } from '../embedded/map-stream-events.js';\nimport type { AgentInstanceGateway } from '../agent-instance-gateway.js';\nimport type { CommandHandler } from '../messaging/command-handler.js';\nimport type { ModelManager } from '../models/index.js';\n\nimport { AsyncQueue } from './async-queue.js';\nimport {\n hydratePerTurnState,\n runDirectAgentTurn,\n tryRunSlashCommand,\n} from './direct-turn-helpers.js';\n\nexport type DirectStreamInboundAttachment = {\n type: string;\n mimeType?: string;\n data?: string;\n name?: string;\n size?: number;\n workspaceRelativePath?: string;\n};\n\nexport type ProcessDirectStreamLog = {\n info: (obj: Record<string, unknown>, msg: string) => void;\n warn: (obj: Record<string, unknown>, msg: string) => void;\n debug?: (obj: Record<string, unknown>, msg: string) => void;\n};\n\nexport interface ProcessDirectStreamingDeps {\n log: ProcessDirectStreamLog;\n parseSessionKey: (sessionKey: string) => { channel: string; chatId: string };\n initDirectStreamingSession: (\n sessionKey: string,\n channel: string,\n chatId: string,\n ) => SessionContext;\n registerWebchatSsePublisher: (\n sessionKey: string,\n publisher: (event: { type: string; [key: string]: unknown }) => void,\n ) => void;\n unregisterWebchatSsePublisher: (sessionKey: string) => void;\n agentManager: AgentInstanceGateway;\n hydrateSessionWorkspaceFromStore: (sessionKey: string) => Promise<void>;\n hydrateSessionModelFromStore: (sessionKey: string) => Promise<void>;\n sessionStore: SessionStore;\n modelManager: ModelManager;\n applyResolvedThinkingLevel: (sessionKey: string, thinking?: string | null) => Promise<void>;\n getConfig: () => Config | undefined;\n sessionConfigStore: SessionConfigStore;\n attachmentRootsForSession: (sessionKey: string) => InternalAttachmentRoots;\n commandHandler: Pick<CommandHandler, 'executeCommandAndAggregateReply'>;\n prepareInboundAttachments: (\n sessionKey: string,\n attachments?: DirectStreamInboundAttachment[],\n ) => Promise<DirectStreamInboundAttachment[] | undefined>;\n buildMessageContent: (\n content: string,\n attachments: DirectStreamInboundAttachment[] | undefined,\n sessionKey: string,\n ) => Promise<Array<{ type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string }>>;\n recordPersistentGoalStreamOutcome?: (\n sessionKey: string,\n outcome: { skipPersistentGoalPostTurn: boolean },\n ) => void;\n onTurnComplete?: (sessionKey: string, lastAssistantText?: string) => void;\n enqueueProvisionalSessionTitle?: (sessionKey: string, userText: string) => void;\n /** Disk-only transcript sync (slash receipt already streamed as tokens). */\n reloadWebchatTranscript?: (sessionKey: string) => void;\n maybeEmitWebchatTts: (\n sessionKey: string,\n hadInboundVoice: boolean,\n ) => Promise<{ type: 'tts_audio'; workspaceRelativePath: string; mimeType: string; name: string } | null>;\n endDirectRequestContext: () => void;\n resetSession: (sessionKey: string) => Promise<{ sessionId: string; previousSessionId: string } | null>;\n}\n\nexport interface ProcessDirectStreamingInput {\n content: string;\n sessionKey?: string;\n attachments?: DirectStreamInboundAttachment[];\n thinking?: string;\n signal?: AbortSignal;\n}\n\nexport type ProcessDirectStreamingSseEvent = { type: string; [key: string]: unknown };\n\nexport async function* runProcessDirectStreaming(\n deps: ProcessDirectStreamingDeps,\n input: ProcessDirectStreamingInput,\n): AsyncGenerator<ProcessDirectStreamingSseEvent, void, unknown> {\n const sessionKey = input.sessionKey ?? 'agent:main:main';\n const { channel, chatId } = deps.parseSessionKey(sessionKey);\n const context = deps.initDirectStreamingSession(sessionKey, channel, chatId);\n\n const queue = new AsyncQueue<ProcessDirectStreamingSseEvent>();\n let reasoningLevel: ReasoningLevel = 'stream';\n\n const pushVisible = (event: ProcessDirectStreamingSseEvent) => {\n const visible = applyReasoningVisibilityToSseEvent(event, reasoningLevel);\n if (visible !== null) {\n queue.push(visible);\n }\n };\n\n const formatStreamError = (raw: string): string => {\n let provider: string | undefined;\n let modelRef: string | undefined;\n try {\n const resolved = deps.modelManager.getResolvedModelForSession(sessionKey);\n provider = resolved.provider;\n modelRef = deps.modelManager.getModelForSession(sessionKey);\n } catch {\n /* ignore — format without provider context */\n }\n return formatAgentRunErrorForClient(raw, { provider, modelRef });\n };\n\n if (channel === 'webchat') {\n deps.registerWebchatSsePublisher(sessionKey, pushVisible);\n }\n\n const signal = input.signal;\n let userAborted = false;\n let abortHandled = false;\n let inboundVoice = false;\n let ranSlashCommand = false;\n let mergedUserText = input.content;\n let webchatSlashReceipt: string | undefined;\n\n // Kick off the agent task in the background; events stream into `queue` as they happen\n // and the generator below drains `queue` until the task closes it.\n const taskPromise = (async () => {\n try {\n const cfg = deps.getConfig();\n let turnBody = input.content;\n let resetTriggeredAtInit = false;\n if (cfg) {\n const turn = await initSessionTurn({\n cfg,\n sessionKey,\n body: input.content,\n resetSession: deps.resetSession,\n });\n resetTriggeredAtInit = turn.resetTriggered;\n if (turn.bareReset && turn.ackMessage) {\n ranSlashCommand = true;\n webchatSlashReceipt = turn.ackMessage;\n pushVisible({ type: 'token', content: turn.ackMessage });\n return;\n }\n turnBody = turn.bodyStripped;\n if (turn.isNewSession) {\n deps.log.debug(\n {\n sessionKey,\n sessionId: turn.sessionId,\n previousSessionId: turn.previousSessionId,\n resetTriggered: turn.resetTriggered,\n staleRollover: turn.staleRollover,\n },\n 'Session reset boundary at direct turn start',\n );\n }\n }\n\n await hydratePerTurnState(deps, sessionKey, input.thinking);\n {\n const defReason = (deps.getConfig()?.agents?.defaults?.reasoningDefault ?? 'stream') as ReasoningLevel;\n reasoningLevel = await resolveEffectiveReasoningLevel(deps.sessionConfigStore, sessionKey, defReason);\n }\n\n const prepared = await deps.prepareInboundAttachments(sessionKey, input.attachments);\n\n const sttCfg = mergeSttConfigFromAppConfig(deps.getConfig()?.tools?.media?.audio, deps.getConfig()?.tools?.media);\n const voiceMerge = await mergeVoiceTranscriptsIntoUserText(\n deps.attachmentRootsForSession(sessionKey),\n prepared,\n turnBody,\n sttCfg,\n );\n mergedUserText = voiceMerge.text;\n inboundVoice = voiceMerge.inboundVoice;\n\n if (inboundVoice) {\n const transcriptParts = [\n voiceMerge.voiceTranscripts.filter(Boolean).join('\\n'),\n turnBody.trim(),\n ].filter(Boolean);\n const voiceAttachments = (prepared ?? []).filter(isVoiceLikeAttachment).map((att) => ({\n workspaceRelativePath: att.workspaceRelativePath,\n mimeType: att.mimeType,\n name: att.name,\n }));\n pushVisible({\n type: 'user_transcript',\n text: transcriptParts.join('\\n\\n'),\n attachments: voiceAttachments,\n });\n }\n\n const armAbort = () => {\n if (abortHandled) {\n return;\n }\n abortHandled = true;\n userAborted = true;\n void abortEmbeddedRun(sessionKey);\n queue.close();\n };\n if (signal) {\n if (signal.aborted) {\n armAbort();\n return;\n }\n signal.addEventListener('abort', armAbort, { once: true });\n }\n\n const slash = await tryRunSlashCommand(\n deps,\n { sessionKey, channel, chatId, senderId: context.senderId, isGroup: context.isGroup },\n mergedUserText,\n { skipResetCommands: resetTriggeredAtInit },\n );\n if (slash.matched) {\n ranSlashCommand = true;\n const text = slash.aggregatedText.trim();\n if (text) {\n webchatSlashReceipt = text;\n pushVisible({ type: 'token', content: text });\n } else if (channel === 'webchat') {\n webchatSlashReceipt =\n 'Command finished with no assistant text. If you used `/goal`, a follow-up turn may still be scheduled automatically.';\n pushVisible({ type: 'token', content: webchatSlashReceipt });\n }\n return;\n }\n\n const textForAgent = mergedUserText.trimStart().startsWith('/skill:')\n ? deps.agentManager.expandSkillUserText(mergedUserText)\n : mergedUserText;\n const messageContent = await deps.buildMessageContent(textForAgent, prepared, sessionKey);\n\n const userMessage = {\n role: 'user' as const,\n content: messageContent,\n timestamp: Date.now(),\n };\n if (channel === 'webchat') {\n pushVisible({\n type: 'user_message',\n timestamp: userMessage.timestamp,\n content: userMessage.content,\n attachments: prepared?.map((att) => ({\n type: att.type,\n mimeType: att.mimeType,\n name: att.name,\n size: att.size,\n workspaceRelativePath: att.workspaceRelativePath,\n })),\n });\n if (textForAgent.trim()) {\n deps.enqueueProvisionalSessionTitle?.(sessionKey, textForAgent);\n }\n }\n\n const result = await runDirectAgentTurn(\n {\n sessionStore: deps.sessionStore,\n agentManager: deps.agentManager,\n modelManager: deps.modelManager,\n config: deps.getConfig(),\n },\n {\n sessionKey,\n userMessage,\n abortSignal: signal,\n onEvent: (embeddedEvent) => {\n const mapped = mapEmbeddedEventToGatewaySse(embeddedEvent);\n if (mapped) {\n if (mapped.type === 'error' && typeof mapped.content === 'string') {\n mapped.content = formatStreamError(mapped.content);\n }\n pushVisible(mapped);\n }\n },\n },\n );\n\n if (result.lastAssistantText) {\n deps.onTurnComplete?.(sessionKey, result.lastAssistantText);\n }\n if (!result.ok && result.errorMessage && !abortHandled) {\n pushVisible({ type: 'error', content: formatStreamError(result.errorMessage) });\n }\n } catch (err) {\n if (!abortHandled) {\n const em = err instanceof Error ? err.message : String(err);\n pushVisible({ type: 'error', content: formatStreamError(em) });\n }\n } finally {\n queue.close();\n }\n })();\n\n try {\n for await (const event of queue) {\n yield event;\n }\n await taskPromise; // surface unexpected throws\n\n if (channel === 'webchat' && ranSlashCommand) {\n try {\n const userMsg = {\n role: 'user' as const,\n content: [{ type: 'text' as const, text: mergedUserText }],\n timestamp: Date.now(),\n } as AgentMessage;\n await deps.sessionStore.appendTranscriptMessage(sessionKey, userMsg);\n if (webchatSlashReceipt?.trim()) {\n const assistantMsg = {\n role: 'assistant' as const,\n content: [{ type: 'text' as const, text: webchatSlashReceipt.trim() }],\n timestamp: Date.now(),\n } as AgentMessage;\n await deps.sessionStore.appendTranscriptMessage(sessionKey, assistantMsg);\n }\n deps.reloadWebchatTranscript?.(sessionKey);\n } catch (err) {\n deps.log.warn({ err, sessionKey }, 'Failed to persist webchat slash command receipt');\n }\n }\n\n if (!userAborted) {\n const ttsAudioEvent = await deps.maybeEmitWebchatTts(sessionKey, inboundVoice);\n if (ttsAudioEvent) {\n yield ttsAudioEvent;\n }\n }\n\n deps.recordPersistentGoalStreamOutcome?.(sessionKey, { skipPersistentGoalPostTurn: ranSlashCommand });\n } finally {\n if (channel === 'webchat') {\n deps.unregisterWebchatSsePublisher(sessionKey);\n }\n deps.endDirectRequestContext();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"process-direct-streaming.js","names":[],"sources":["../../../../src/agent/service/process-direct-streaming.ts"],"sourcesContent":["import type { AgentMessage } from '@earendil-works/pi-agent-core';\n\nimport type { Config } from '../../config/schema.js';\nimport type { InboundAttachmentInput, MediaRef } from '../../channels/attachments/inbound-persist.js';\nimport { readAgentMessageContent } from '../memory/agent-message-access.js';\nimport {\n isVoiceLikeAttachment,\n mergeVoiceTranscriptsIntoUserText,\n mergeSttConfigFromAppConfig,\n} from '../../channels/attachments/voice-stt-webchat.js';\nimport {\n resolveEffectiveReasoningLevel,\n initSessionTurn,\n type SessionConfigStore,\n type SessionStore,\n} from '../../session/index.js';\nimport type { SessionContext } from '../session/index.js';\nimport { applyReasoningVisibilityToSseEvent } from '../streaming/reasoning-visibility-sse.js';\nimport type { ReasoningLevel } from '../transcript/thinking-types.js';\nimport { formatAgentRunErrorForClient } from '../client-error-format.js';\nimport { abortEmbeddedRun } from '../embedded/runs.js';\nimport { mapEmbeddedEventToGatewaySse } from '../embedded/map-stream-events.js';\nimport type { AgentInstanceGateway } from '../agent-instance-gateway.js';\nimport type { CommandHandler } from '../messaging/command-handler.js';\nimport type { ModelManager } from '../models/index.js';\n\nimport { AsyncQueue } from './async-queue.js';\nimport {\n hydratePerTurnState,\n runDirectAgentTurn,\n tryRunSlashCommand,\n} from './direct-turn-helpers.js';\nimport {\n clearPendingTranscriptUserMessage,\n setPendingTranscriptUserMessage,\n type TranscriptUserMessage,\n} from '../inbound/attachment-pipeline.js';\n\nexport type DirectStreamInboundAttachment = InboundAttachmentInput;\n\nexport type ProcessDirectStreamLog = {\n info: (obj: Record<string, unknown>, msg: string) => void;\n warn: (obj: Record<string, unknown>, msg: string) => void;\n debug?: (obj: Record<string, unknown>, msg: string) => void;\n};\n\nexport interface ProcessDirectStreamingDeps {\n log: ProcessDirectStreamLog;\n parseSessionKey: (sessionKey: string) => { channel: string; chatId: string };\n initDirectStreamingSession: (\n sessionKey: string,\n channel: string,\n chatId: string,\n ) => SessionContext;\n registerWebchatSsePublisher: (\n sessionKey: string,\n publisher: (event: { type: string; [key: string]: unknown }) => void,\n ) => void;\n unregisterWebchatSsePublisher: (sessionKey: string) => void;\n agentManager: AgentInstanceGateway;\n hydrateSessionWorkspaceFromStore: (sessionKey: string) => Promise<void>;\n hydrateSessionModelFromStore: (sessionKey: string) => Promise<void>;\n sessionStore: SessionStore;\n modelManager: ModelManager;\n applyResolvedThinkingLevel: (sessionKey: string, thinking?: string | null) => Promise<void>;\n getConfig: () => Config | undefined;\n sessionConfigStore: SessionConfigStore;\n commandHandler: Pick<CommandHandler, 'executeCommandAndAggregateReply'>;\n prepareInboundAttachments: (\n sessionKey: string,\n attachments?: DirectStreamInboundAttachment[],\n ) => Promise<MediaRef[] | undefined>;\n buildTranscriptUserMessage: (\n content: string,\n prepared: MediaRef[] | undefined,\n sessionKey: string,\n ) => Promise<TranscriptUserMessage>;\n recordPersistentGoalStreamOutcome?: (\n sessionKey: string,\n outcome: { skipPersistentGoalPostTurn: boolean },\n ) => void;\n onTurnComplete?: (sessionKey: string, lastAssistantText?: string) => void;\n enqueueProvisionalSessionTitle?: (sessionKey: string, userText: string) => void;\n /** Disk-only transcript sync (slash receipt already streamed as tokens). */\n reloadWebchatTranscript?: (sessionKey: string) => void;\n maybeEmitWebchatTts: (\n sessionKey: string,\n hadInboundVoice: boolean,\n ) => Promise<{ type: 'tts_audio'; uri: string; mimeType: string; name: string } | null>;\n endDirectRequestContext: () => void;\n resetSession: (sessionKey: string) => Promise<{ sessionId: string; previousSessionId: string } | null>;\n}\n\nexport interface ProcessDirectStreamingInput {\n content: string;\n sessionKey?: string;\n attachments?: DirectStreamInboundAttachment[];\n thinking?: string;\n signal?: AbortSignal;\n}\n\nexport type ProcessDirectStreamingSseEvent = { type: string; [key: string]: unknown };\n\nexport async function* runProcessDirectStreaming(\n deps: ProcessDirectStreamingDeps,\n input: ProcessDirectStreamingInput,\n): AsyncGenerator<ProcessDirectStreamingSseEvent, void, unknown> {\n const sessionKey = input.sessionKey ?? 'agent:main:main';\n const { channel, chatId } = deps.parseSessionKey(sessionKey);\n const context = deps.initDirectStreamingSession(sessionKey, channel, chatId);\n\n const queue = new AsyncQueue<ProcessDirectStreamingSseEvent>();\n let reasoningLevel: ReasoningLevel = 'stream';\n\n const pushVisible = (event: ProcessDirectStreamingSseEvent) => {\n const visible = applyReasoningVisibilityToSseEvent(event, reasoningLevel);\n if (visible !== null) {\n queue.push(visible);\n }\n };\n\n const formatStreamError = (raw: string): string => {\n let provider: string | undefined;\n let modelRef: string | undefined;\n try {\n const resolved = deps.modelManager.getResolvedModelForSession(sessionKey);\n provider = resolved.provider;\n modelRef = deps.modelManager.getModelForSession(sessionKey);\n } catch {\n /* ignore — format without provider context */\n }\n return formatAgentRunErrorForClient(raw, { provider, modelRef });\n };\n\n if (channel === 'webchat') {\n deps.registerWebchatSsePublisher(sessionKey, pushVisible);\n }\n\n const signal = input.signal;\n let userAborted = false;\n let abortHandled = false;\n let inboundVoice = false;\n let ranSlashCommand = false;\n let mergedUserText = input.content;\n let webchatSlashReceipt: string | undefined;\n\n const taskPromise = (async () => {\n try {\n const cfg = deps.getConfig();\n let turnBody = input.content;\n let resetTriggeredAtInit = false;\n if (cfg) {\n const turn = await initSessionTurn({\n cfg,\n sessionKey,\n body: input.content,\n resetSession: deps.resetSession,\n });\n resetTriggeredAtInit = turn.resetTriggered;\n if (turn.bareReset && turn.ackMessage) {\n ranSlashCommand = true;\n webchatSlashReceipt = turn.ackMessage;\n pushVisible({ type: 'token', content: turn.ackMessage });\n return;\n }\n turnBody = turn.bodyStripped;\n if (turn.isNewSession) {\n deps.log.debug(\n {\n sessionKey,\n sessionId: turn.sessionId,\n previousSessionId: turn.previousSessionId,\n resetTriggered: turn.resetTriggered,\n staleRollover: turn.staleRollover,\n },\n 'Session reset boundary at direct turn start',\n );\n }\n }\n\n await hydratePerTurnState(deps, sessionKey, input.thinking);\n {\n const defReason = (deps.getConfig()?.agents?.defaults?.reasoningDefault ?? 'stream') as ReasoningLevel;\n reasoningLevel = await resolveEffectiveReasoningLevel(deps.sessionConfigStore, sessionKey, defReason);\n }\n\n const prepared = await deps.prepareInboundAttachments(sessionKey, input.attachments);\n\n const sttCfg = mergeSttConfigFromAppConfig(deps.getConfig()?.tools?.media?.audio, deps.getConfig()?.tools?.media);\n const voiceMerge = await mergeVoiceTranscriptsIntoUserText(prepared, turnBody, sttCfg);\n mergedUserText = voiceMerge.text;\n inboundVoice = voiceMerge.inboundVoice;\n\n if (inboundVoice) {\n const transcriptParts = [\n voiceMerge.voiceTranscripts.filter(Boolean).join('\\n'),\n turnBody.trim(),\n ].filter(Boolean);\n const voiceMedia = (prepared ?? []).filter(isVoiceLikeAttachment).map((att) => ({\n uri: att.uri,\n mimeType: att.mimeType,\n name: att.name,\n }));\n pushVisible({\n type: 'user_transcript',\n text: transcriptParts.join('\\n\\n'),\n media: voiceMedia,\n });\n }\n\n const armAbort = () => {\n if (abortHandled) {\n return;\n }\n abortHandled = true;\n userAborted = true;\n void abortEmbeddedRun(sessionKey);\n queue.close();\n };\n if (signal) {\n if (signal.aborted) {\n armAbort();\n return;\n }\n signal.addEventListener('abort', armAbort, { once: true });\n }\n\n const slash = await tryRunSlashCommand(\n deps,\n {\n sessionKey,\n channel,\n chatId,\n senderId: context.senderId,\n isGroup: context.isGroup,\n inboundMetadata: context.metadata,\n },\n mergedUserText,\n { skipResetCommands: resetTriggeredAtInit },\n );\n if (slash.matched) {\n ranSlashCommand = true;\n const text = slash.aggregatedText.trim();\n if (text) {\n webchatSlashReceipt = text;\n pushVisible({ type: 'token', content: text });\n } else if (channel === 'webchat') {\n webchatSlashReceipt =\n 'Command finished with no assistant text. If you used `/goal`, a follow-up turn may still be scheduled automatically.';\n pushVisible({ type: 'token', content: webchatSlashReceipt });\n }\n return;\n }\n\n const textForAgent = mergedUserText.trimStart().startsWith('/skill:')\n ? deps.agentManager.expandSkillUserText(mergedUserText)\n : mergedUserText;\n const userMessage = await deps.buildTranscriptUserMessage(textForAgent, prepared, sessionKey);\n\n if (channel === 'webchat') {\n pushVisible({\n type: 'user_message',\n timestamp: userMessage.timestamp ?? Date.now(),\n content: readAgentMessageContent(userMessage),\n media: userMessage.media,\n });\n if (textForAgent.trim()) {\n deps.enqueueProvisionalSessionTitle?.(sessionKey, textForAgent);\n }\n }\n\n const pendingUserMessage = userMessage as TranscriptUserMessage;\n setPendingTranscriptUserMessage(sessionKey, pendingUserMessage);\n\n try {\n const result = await runDirectAgentTurn(\n {\n sessionStore: deps.sessionStore,\n agentManager: deps.agentManager,\n modelManager: deps.modelManager,\n config: deps.getConfig(),\n },\n {\n sessionKey,\n userMessage,\n abortSignal: signal,\n onEvent: (embeddedEvent) => {\n const mapped = mapEmbeddedEventToGatewaySse(embeddedEvent);\n if (mapped) {\n if (mapped.type === 'error' && typeof mapped.content === 'string') {\n mapped.content = formatStreamError(mapped.content);\n }\n pushVisible(mapped);\n }\n },\n },\n );\n\n if (result.lastAssistantText) {\n deps.onTurnComplete?.(sessionKey, result.lastAssistantText);\n }\n if (!result.ok && result.errorMessage && !abortHandled) {\n pushVisible({ type: 'error', content: formatStreamError(result.errorMessage) });\n }\n } finally {\n clearPendingTranscriptUserMessage(sessionKey, pendingUserMessage);\n }\n } catch (err) {\n if (!abortHandled) {\n const em = err instanceof Error ? err.message : String(err);\n pushVisible({ type: 'error', content: formatStreamError(em) });\n }\n } finally {\n queue.close();\n }\n })();\n\n try {\n for await (const event of queue) {\n yield event;\n }\n await taskPromise;\n\n if (channel === 'webchat' && ranSlashCommand) {\n try {\n const userMsg = {\n role: 'user' as const,\n content: [{ type: 'text' as const, text: mergedUserText }],\n timestamp: Date.now(),\n } as AgentMessage;\n await deps.sessionStore.appendTranscriptMessage(sessionKey, userMsg);\n if (webchatSlashReceipt?.trim()) {\n const assistantMsg = {\n role: 'assistant' as const,\n content: [{ type: 'text' as const, text: webchatSlashReceipt.trim() }],\n timestamp: Date.now(),\n } as AgentMessage;\n await deps.sessionStore.appendTranscriptMessage(sessionKey, assistantMsg);\n }\n deps.reloadWebchatTranscript?.(sessionKey);\n } catch (err) {\n deps.log.warn({ err, sessionKey }, 'Failed to persist webchat slash command receipt');\n }\n }\n\n if (!userAborted) {\n const ttsAudioEvent = await deps.maybeEmitWebchatTts(sessionKey, inboundVoice);\n if (ttsAudioEvent) {\n yield ttsAudioEvent;\n }\n }\n\n deps.recordPersistentGoalStreamOutcome?.(sessionKey, { skipPersistentGoalPostTurn: ranSlashCommand });\n } finally {\n if (channel === 'webchat') {\n deps.unregisterWebchatSsePublisher(sessionKey);\n }\n deps.endDirectRequestContext();\n }\n}\n"],"mappings":"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"}
@@ -1,8 +1,9 @@
1
1
  import type { Config } from '../../config/schema.js';
2
+ import type { MediaRef } from '../../media/types.js';
2
3
  import type { SessionStore } from '../../session/index.js';
3
4
  export type WebchatTtsResult = {
4
5
  type: 'tts_audio';
5
- workspaceRelativePath: string;
6
+ uri: string;
6
7
  mimeType: string;
7
8
  name: string;
8
9
  };
@@ -15,13 +16,7 @@ export type WebchatTtsDeps = {
15
16
  };
16
17
  };
17
18
  /**
18
- * Generate TTS for webchat when config allows; persist under agent home `tts/`.
19
+ * Generate TTS for webchat when config allows; persist under `{stateDir}/media/tts/`.
19
20
  */
20
21
  export declare function maybeEmitWebchatTts(deps: WebchatTtsDeps, sessionKey: string, hadInboundVoice: boolean): Promise<WebchatTtsResult | null>;
21
- export declare function appendAttachmentToLastAssistant(sessionStore: SessionStore, sessionKey: string, att: {
22
- type: string;
23
- mimeType: string;
24
- name: string;
25
- size: number;
26
- workspaceRelativePath: string;
27
- }): Promise<void>;
22
+ export declare function appendMediaToLastAssistant(sessionStore: SessionStore, sessionKey: string, ref: MediaRef): Promise<void>;
@@ -1,5 +1,3 @@
1
- import { init_agent_scope, resolveAgentHomeDir } from "../agent-scope.js";
2
- import { extractProfileAgentId } from "../../config/agent-profile.js";
3
1
  import { isTTSAvailable } from "../../voice/tts/factory.js";
4
2
  import { speak } from "../../voice/tts/speak-core.js";
5
3
  import { mergeTtsConfigFromAppConfig } from "../../voice/tts/merge-config.js";
@@ -8,9 +6,8 @@ import { compressAudio } from "../../voice/tts/audio.js";
8
6
  import "../../voice/tts/index.js";
9
7
  import { persistOutboundTtsAudio } from "../../channels/attachments/outbound-tts-persist.js";
10
8
  //#region src/agent/service/webchat-tts.ts
11
- init_agent_scope();
12
9
  /**
13
- * Generate TTS for webchat when config allows; persist under agent home `tts/`.
10
+ * Generate TTS for webchat when config allows; persist under `{stateDir}/media/tts/`.
14
11
  */
15
12
  async function maybeEmitWebchatTts(deps, sessionKey, hadInboundVoice) {
16
13
  const ttsConfig = mergeTtsConfigFromAppConfig(deps.config?.messages?.tts);
@@ -27,18 +24,11 @@ async function maybeEmitWebchatTts(deps, sessionKey, hadInboundVoice) {
27
24
  });
28
25
  const { buffer, format } = await compressAudio(Buffer.from(ttsResult.audio), ttsResult.format, webOut.format === "mp3" ? "mp3" : "opus");
29
26
  const normalizedMime = format === "opus" || format === "ogg" ? "audio/ogg" : format === "mp3" || format === "mpeg" ? "audio/mpeg" : format === "wav" ? "audio/wav" : `audio/${format}`;
30
- const cfg = deps.config;
31
- const persisted = await persistOutboundTtsAudio(resolveAgentHomeDir(cfg, extractProfileAgentId(sessionKey, cfg)), sessionKey, buffer, format);
32
- await appendAttachmentToLastAssistant(deps.sessionStore, sessionKey, {
33
- type: "audio",
34
- mimeType: normalizedMime,
35
- name: persisted.name,
36
- size: persisted.size,
37
- workspaceRelativePath: persisted.workspaceRelativePath
38
- });
27
+ const persisted = await persistOutboundTtsAudio(buffer, format);
28
+ await appendMediaToLastAssistant(deps.sessionStore, sessionKey, persisted);
39
29
  return {
40
30
  type: "tts_audio",
41
- workspaceRelativePath: persisted.workspaceRelativePath,
31
+ uri: persisted.uri,
42
32
  mimeType: normalizedMime,
43
33
  name: persisted.name
44
34
  };
@@ -50,17 +40,16 @@ async function maybeEmitWebchatTts(deps, sessionKey, hadInboundVoice) {
50
40
  return null;
51
41
  }
52
42
  }
53
- async function appendAttachmentToLastAssistant(sessionStore, sessionKey, att) {
43
+ async function appendMediaToLastAssistant(sessionStore, sessionKey, ref) {
54
44
  const loaded = await sessionStore.load(sessionKey);
55
45
  for (let i = loaded.length - 1; i >= 0; i--) {
56
46
  const m = loaded[i];
57
47
  if (m.role === "assistant") {
58
- const prev = m.attachments ?? [];
59
- if (prev.some((x) => x.workspaceRelativePath === att.workspaceRelativePath)) return;
60
- const next = [...prev, att];
48
+ const prev = m.media ?? [];
49
+ if (prev.some((x) => x.uri === ref.uri)) return;
61
50
  loaded[i] = {
62
51
  ...m,
63
- attachments: next
52
+ media: [...prev, ref]
64
53
  };
65
54
  await sessionStore.saveMessages(sessionKey, loaded);
66
55
  return;
@@ -68,6 +57,6 @@ async function appendAttachmentToLastAssistant(sessionStore, sessionKey, att) {
68
57
  }
69
58
  }
70
59
  //#endregion
71
- export { appendAttachmentToLastAssistant, maybeEmitWebchatTts };
60
+ export { appendMediaToLastAssistant, maybeEmitWebchatTts };
72
61
 
73
62
  //# sourceMappingURL=webchat-tts.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"webchat-tts.js","names":[],"sources":["../../../../src/agent/service/webchat-tts.ts"],"sourcesContent":["import type { Config } from '../../config/schema.js';\nimport { persistOutboundTtsAudio } from '../../channels/attachments/outbound-tts-persist.js';\nimport { compressAudio } from '../../voice/tts/audio.js';\nimport { speak } from '../../voice/tts/index.js';\nimport { mergeTtsConfigFromAppConfig } from '../../voice/tts/merge-config.js';\nimport { shouldUseTTS, getChannelOutputFormat } from '../../voice/tts/service.js';\nimport { isTTSAvailable } from '../../voice/tts/factory.js';\nimport { resolveAgentHomeDir } from '../agent-scope.js';\nimport { extractProfileAgentId } from '../../config/agent-profile.js';\nimport type { SessionStore } from '../../session/index.js';\nimport type { AgentMessage } from '@earendil-works/pi-agent-core';\n\nexport type WebchatTtsResult = {\n type: 'tts_audio';\n workspaceRelativePath: string;\n mimeType: string;\n name: string;\n};\n\nexport type WebchatTtsDeps = {\n config: Config | undefined;\n sessionStore: SessionStore;\n getLastAssistantPlainText: (sessionKey: string) => string;\n log: { warn: (obj: Record<string, unknown>, msg: string) => void };\n};\n\n/**\n * Generate TTS for webchat when config allows; persist under agent home `tts/`.\n */\nexport async function maybeEmitWebchatTts(\n deps: WebchatTtsDeps,\n sessionKey: string,\n hadInboundVoice: boolean,\n): Promise<WebchatTtsResult | null> {\n const ttsConfig = mergeTtsConfigFromAppConfig(deps.config?.messages?.tts);\n if (!isTTSAvailable(ttsConfig)) {\n return null;\n }\n const decision = shouldUseTTS(ttsConfig, hadInboundVoice);\n if (!decision.useTTS) {\n return null;\n }\n const text = deps.getLastAssistantPlainText(sessionKey).trim();\n if (!text) {\n return null;\n }\n try {\n const webOut = getChannelOutputFormat('webchat');\n const fmt = webOut.format as 'opus' | 'mp3' | 'wav';\n const ttsResult = await speak(text, ttsConfig, {\n appConfig: deps.config,\n tts: { format: fmt },\n });\n const { buffer, format } = await compressAudio(\n Buffer.from(ttsResult.audio),\n ttsResult.format,\n webOut.format === 'mp3' ? 'mp3' : 'opus',\n );\n const normalizedMime =\n format === 'opus' || format === 'ogg'\n ? 'audio/ogg'\n : format === 'mp3' || format === 'mpeg'\n ? 'audio/mpeg'\n : format === 'wav'\n ? 'audio/wav'\n : `audio/${format}`;\n const cfg = deps.config!;\n const persisted = await persistOutboundTtsAudio(\n resolveAgentHomeDir(cfg, extractProfileAgentId(sessionKey, cfg)),\n sessionKey,\n buffer,\n format,\n );\n await appendAttachmentToLastAssistant(deps.sessionStore, sessionKey, {\n type: 'audio',\n mimeType: normalizedMime,\n name: persisted.name,\n size: persisted.size,\n workspaceRelativePath: persisted.workspaceRelativePath,\n });\n return {\n type: 'tts_audio',\n workspaceRelativePath: persisted.workspaceRelativePath,\n mimeType: normalizedMime,\n name: persisted.name,\n };\n } catch (err) {\n deps.log.warn({ err, sessionKey }, 'Webchat TTS failed');\n return null;\n }\n}\n\nexport async function appendAttachmentToLastAssistant(\n sessionStore: SessionStore,\n sessionKey: string,\n att: {\n type: string;\n mimeType: string;\n name: string;\n size: number;\n workspaceRelativePath: string;\n },\n): Promise<void> {\n const loaded = await sessionStore.load(sessionKey);\n for (let i = loaded.length - 1; i >= 0; i--) {\n const m = loaded[i] as { role?: string; attachments?: unknown[] };\n if (m.role === 'assistant') {\n const prev = (m.attachments ?? []) as Array<{ workspaceRelativePath?: string }>;\n if (prev.some((x) => x.workspaceRelativePath === att.workspaceRelativePath)) {\n return;\n }\n const next = [...prev, att];\n loaded[i] = { ...m, attachments: next } as unknown as AgentMessage;\n await sessionStore.saveMessages(sessionKey, loaded);\n return;\n }\n }\n}\n"],"mappings":";;;;;;;;;;kBAOwD;;;;AAsBxD,eAAsB,oBACpB,MACA,YACA,iBACkC;CAClC,MAAM,YAAY,4BAA4B,KAAK,QAAQ,UAAU,IAAI;AACzE,KAAI,CAAC,eAAe,UAAU,CAC5B,QAAO;AAGT,KAAI,CADa,aAAa,WAAW,gBAC5B,CAAC,OACZ,QAAO;CAET,MAAM,OAAO,KAAK,0BAA0B,WAAW,CAAC,MAAM;AAC9D,KAAI,CAAC,KACH,QAAO;AAET,KAAI;EACF,MAAM,SAAS,uBAAuB,UAAU;EAChD,MAAM,MAAM,OAAO;EACnB,MAAM,YAAY,MAAM,MAAM,MAAM,WAAW;GAC7C,WAAW,KAAK;GAChB,KAAK,EAAE,QAAQ,KAAK;GACrB,CAAC;EACF,MAAM,EAAE,QAAQ,WAAW,MAAM,cAC/B,OAAO,KAAK,UAAU,MAAM,EAC5B,UAAU,QACV,OAAO,WAAW,QAAQ,QAAQ,OACnC;EACD,MAAM,iBACJ,WAAW,UAAU,WAAW,QAC5B,cACA,WAAW,SAAS,WAAW,SAC7B,eACA,WAAW,QACT,cACA,SAAS;EACnB,MAAM,MAAM,KAAK;EACjB,MAAM,YAAY,MAAM,wBACtB,oBAAoB,KAAK,sBAAsB,YAAY,IAAI,CAAC,EAChE,YACA,QACA,OACD;AACD,QAAM,gCAAgC,KAAK,cAAc,YAAY;GACnE,MAAM;GACN,UAAU;GACV,MAAM,UAAU;GAChB,MAAM,UAAU;GAChB,uBAAuB,UAAU;GAClC,CAAC;AACF,SAAO;GACL,MAAM;GACN,uBAAuB,UAAU;GACjC,UAAU;GACV,MAAM,UAAU;GACjB;UACM,KAAK;AACZ,OAAK,IAAI,KAAK;GAAE;GAAK;GAAY,EAAE,qBAAqB;AACxD,SAAO;;;AAIX,eAAsB,gCACpB,cACA,YACA,KAOe;CACf,MAAM,SAAS,MAAM,aAAa,KAAK,WAAW;AAClD,MAAK,IAAI,IAAI,OAAO,SAAS,GAAG,KAAK,GAAG,KAAK;EAC3C,MAAM,IAAI,OAAO;AACjB,MAAI,EAAE,SAAS,aAAa;GAC1B,MAAM,OAAQ,EAAE,eAAe,EAAE;AACjC,OAAI,KAAK,MAAM,MAAM,EAAE,0BAA0B,IAAI,sBAAsB,CACzE;GAEF,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI;AAC3B,UAAO,KAAK;IAAE,GAAG;IAAG,aAAa;IAAM;AACvC,SAAM,aAAa,aAAa,YAAY,OAAO;AACnD"}
1
+ {"version":3,"file":"webchat-tts.js","names":[],"sources":["../../../../src/agent/service/webchat-tts.ts"],"sourcesContent":["import type { Config } from '../../config/schema.js';\nimport { persistOutboundTtsAudio } from '../../channels/attachments/outbound-tts-persist.js';\nimport type { MediaRef } from '../../media/types.js';\nimport { compressAudio } from '../../voice/tts/audio.js';\nimport { speak } from '../../voice/tts/index.js';\nimport { mergeTtsConfigFromAppConfig } from '../../voice/tts/merge-config.js';\nimport { shouldUseTTS, getChannelOutputFormat } from '../../voice/tts/service.js';\nimport { isTTSAvailable } from '../../voice/tts/factory.js';\nimport type { SessionStore } from '../../session/index.js';\nimport type { AgentMessage } from '@earendil-works/pi-agent-core';\n\nexport type WebchatTtsResult = {\n type: 'tts_audio';\n uri: string;\n mimeType: string;\n name: string;\n};\n\nexport type WebchatTtsDeps = {\n config: Config | undefined;\n sessionStore: SessionStore;\n getLastAssistantPlainText: (sessionKey: string) => string;\n log: { warn: (obj: Record<string, unknown>, msg: string) => void };\n};\n\n/**\n * Generate TTS for webchat when config allows; persist under `{stateDir}/media/tts/`.\n */\nexport async function maybeEmitWebchatTts(\n deps: WebchatTtsDeps,\n sessionKey: string,\n hadInboundVoice: boolean,\n): Promise<WebchatTtsResult | null> {\n const ttsConfig = mergeTtsConfigFromAppConfig(deps.config?.messages?.tts);\n if (!isTTSAvailable(ttsConfig)) {\n return null;\n }\n const decision = shouldUseTTS(ttsConfig, hadInboundVoice);\n if (!decision.useTTS) {\n return null;\n }\n const text = deps.getLastAssistantPlainText(sessionKey).trim();\n if (!text) {\n return null;\n }\n try {\n const webOut = getChannelOutputFormat('webchat');\n const fmt = webOut.format as 'opus' | 'mp3' | 'wav';\n const ttsResult = await speak(text, ttsConfig, {\n appConfig: deps.config,\n tts: { format: fmt },\n });\n const { buffer, format } = await compressAudio(\n Buffer.from(ttsResult.audio),\n ttsResult.format,\n webOut.format === 'mp3' ? 'mp3' : 'opus',\n );\n const normalizedMime =\n format === 'opus' || format === 'ogg'\n ? 'audio/ogg'\n : format === 'mp3' || format === 'mpeg'\n ? 'audio/mpeg'\n : format === 'wav'\n ? 'audio/wav'\n : `audio/${format}`;\n const persisted = await persistOutboundTtsAudio(buffer, format);\n await appendMediaToLastAssistant(deps.sessionStore, sessionKey, persisted);\n return {\n type: 'tts_audio',\n uri: persisted.uri,\n mimeType: normalizedMime,\n name: persisted.name,\n };\n } catch (err) {\n deps.log.warn({ err, sessionKey }, 'Webchat TTS failed');\n return null;\n }\n}\n\nexport async function appendMediaToLastAssistant(\n sessionStore: SessionStore,\n sessionKey: string,\n ref: MediaRef,\n): Promise<void> {\n const loaded = await sessionStore.load(sessionKey);\n for (let i = loaded.length - 1; i >= 0; i--) {\n const m = loaded[i] as { role?: string; media?: MediaRef[] };\n if (m.role === 'assistant') {\n const prev = m.media ?? [];\n if (prev.some((x) => x.uri === ref.uri)) {\n return;\n }\n loaded[i] = { ...m, media: [...prev, ref] } as unknown as AgentMessage;\n await sessionStore.saveMessages(sessionKey, loaded);\n return;\n }\n }\n}\n"],"mappings":";;;;;;;;;;;AA4BA,eAAsB,oBACpB,MACA,YACA,iBACkC;CAClC,MAAM,YAAY,4BAA4B,KAAK,QAAQ,UAAU,IAAI;AACzE,KAAI,CAAC,eAAe,UAAU,CAC5B,QAAO;AAGT,KAAI,CADa,aAAa,WAAW,gBAC5B,CAAC,OACZ,QAAO;CAET,MAAM,OAAO,KAAK,0BAA0B,WAAW,CAAC,MAAM;AAC9D,KAAI,CAAC,KACH,QAAO;AAET,KAAI;EACF,MAAM,SAAS,uBAAuB,UAAU;EAChD,MAAM,MAAM,OAAO;EACnB,MAAM,YAAY,MAAM,MAAM,MAAM,WAAW;GAC7C,WAAW,KAAK;GAChB,KAAK,EAAE,QAAQ,KAAK;GACrB,CAAC;EACF,MAAM,EAAE,QAAQ,WAAW,MAAM,cAC/B,OAAO,KAAK,UAAU,MAAM,EAC5B,UAAU,QACV,OAAO,WAAW,QAAQ,QAAQ,OACnC;EACD,MAAM,iBACJ,WAAW,UAAU,WAAW,QAC5B,cACA,WAAW,SAAS,WAAW,SAC7B,eACA,WAAW,QACT,cACA,SAAS;EACnB,MAAM,YAAY,MAAM,wBAAwB,QAAQ,OAAO;AAC/D,QAAM,2BAA2B,KAAK,cAAc,YAAY,UAAU;AAC1E,SAAO;GACL,MAAM;GACN,KAAK,UAAU;GACf,UAAU;GACV,MAAM,UAAU;GACjB;UACM,KAAK;AACZ,OAAK,IAAI,KAAK;GAAE;GAAK;GAAY,EAAE,qBAAqB;AACxD,SAAO;;;AAIX,eAAsB,2BACpB,cACA,YACA,KACe;CACf,MAAM,SAAS,MAAM,aAAa,KAAK,WAAW;AAClD,MAAK,IAAI,IAAI,OAAO,SAAS,GAAG,KAAK,GAAG,KAAK;EAC3C,MAAM,IAAI,OAAO;AACjB,MAAI,EAAE,SAAS,aAAa;GAC1B,MAAM,OAAO,EAAE,SAAS,EAAE;AAC1B,OAAI,KAAK,MAAM,MAAM,EAAE,QAAQ,IAAI,IAAI,CACrC;AAEF,UAAO,KAAK;IAAE,GAAG;IAAG,OAAO,CAAC,GAAG,MAAM,IAAI;IAAE;AAC3C,SAAM,aAAa,aAAa,YAAY,OAAO;AACnD"}