openclaw-multi-auto 1.3.6 → 1.3.8

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 (328) hide show
  1. package/dist/{audio-preflight-5FEeDooz.js → audio-preflight-DDBLZBdb.js} +4 -4
  2. package/dist/{audio-transcription-runner-B-UvoDjZ.js → audio-transcription-runner-DZbSWT9E.js} +1 -1
  3. package/dist/build-info.json +3 -3
  4. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  5. package/dist/{chrome-D45SyhQL.js → chrome-CMU2WVFh.js} +8 -8
  6. package/dist/{deliver-B9cys0EZ.js → deliver-BXVcFIHL.js} +1 -1
  7. package/dist/{deliver-runtime-DhaQJ0pI.js → deliver-runtime-DTaIS-1i.js} +3 -3
  8. package/dist/{deps-send-whatsapp.runtime-DvTL2tzN.js → deps-send-whatsapp.runtime-CIZqFAqb.js} +7 -7
  9. package/dist/extensionAPI.js +6 -6
  10. package/dist/{image-DAOPwVXi.js → image-BCVLo0qw.js} +1 -1
  11. package/dist/{image-runtime-wlCLVvVv.js → image-runtime-DtCKpMPZ.js} +3 -3
  12. package/dist/{pi-embedded-DYU79yGe.js → pi-embedded-CgQ_W6Xs.js} +24 -24
  13. package/dist/{pi-embedded-helpers-uTRAmQ4n.js → pi-embedded-helpers-CwuBTKza.js} +3 -3
  14. package/dist/plugin-sdk/{accounts-DyFCXtHv.js → accounts-BslAlVYS.js} +2 -2
  15. package/dist/plugin-sdk/{accounts-BJAXxY46.js → accounts-C3m65--E.js} +2 -2
  16. package/dist/plugin-sdk/{accounts-C1j7HSL0.js → accounts-CNCCkdEF.js} +3 -3
  17. package/dist/plugin-sdk/{active-listener-CftX5jLD.js → active-listener-CkPnMUkB.js} +2 -2
  18. package/dist/plugin-sdk/{api-key-rotation-8nyyt1kx.js → api-key-rotation-BXnNsojA.js} +2 -2
  19. package/dist/plugin-sdk/{audio-preflight-C_aSAPR1.js → audio-preflight-CtO4fFvp.js} +26 -26
  20. package/dist/plugin-sdk/{audio-transcription-runner-CB53F7_7.js → audio-transcription-runner-DnxvOS1-.js} +11 -11
  21. package/dist/plugin-sdk/{audit-membership-runtime-BXndI4LG.js → audit-membership-runtime-BpfoSk8M.js} +2 -2
  22. package/dist/plugin-sdk/{channel-activity-C5y8AgAV.js → channel-activity-WJYxcJ3S.js} +3 -3
  23. package/dist/plugin-sdk/{channel-web-DBTRO03V.js → channel-web-dO5k3ubM.js} +18 -18
  24. package/dist/plugin-sdk/{chrome-f00sZkDX.js → chrome-CjNTuJML.js} +6 -6
  25. package/dist/plugin-sdk/{commands-registry-BJ_NxG2F.js → commands-registry-CdYjoI0i.js} +4 -4
  26. package/dist/plugin-sdk/{common-Cf27Jwxu.js → common-oYc5vPFl.js} +2 -2
  27. package/dist/plugin-sdk/{config-CHQrpx-Q.js → config-B1z-UxQ3.js} +7 -7
  28. package/dist/plugin-sdk/{deliver-DNEuetST.js → deliver-D5_6T567.js} +10 -10
  29. package/dist/plugin-sdk/deliver-runtime-C5dgvvga.js +32 -0
  30. package/dist/plugin-sdk/deps-send-discord.runtime-Dg4N7PHJ.js +23 -0
  31. package/dist/plugin-sdk/deps-send-imessage.runtime-0OEwzMQm.js +22 -0
  32. package/dist/plugin-sdk/deps-send-signal.runtime-BM1jRt3G.js +21 -0
  33. package/dist/plugin-sdk/deps-send-slack.runtime-1E3BYRdF.js +19 -0
  34. package/dist/plugin-sdk/deps-send-telegram.runtime-DNCxIflA.js +24 -0
  35. package/dist/plugin-sdk/deps-send-whatsapp.runtime-OLwr-9c8.js +57 -0
  36. package/dist/plugin-sdk/{diagnostic-LYUUmjJ5.js → diagnostic-Bxxu0ig-.js} +2 -2
  37. package/dist/plugin-sdk/{errors-CtMWwS2Z.js → errors-B3cHyZZA.js} +1 -1
  38. package/dist/plugin-sdk/{fetch-guard-CxYB5Kg6.js → fetch-guard-Dcgod0tg.js} +2 -2
  39. package/dist/plugin-sdk/{fs-safe-DtfhxbrI.js → fs-safe-BaKqI3G4.js} +3 -3
  40. package/dist/plugin-sdk/{image-BwjYjRHx.js → image-B2mQW9Rb.js} +6 -6
  41. package/dist/plugin-sdk/{image-ops-BnZKcbd6.js → image-ops-Cbzr4U9l.js} +2 -2
  42. package/dist/plugin-sdk/image-runtime-BFm45j49.js +25 -0
  43. package/dist/plugin-sdk/{ir-Z4hX67TJ.js → ir-ZEmrTr4J.js} +7 -7
  44. package/dist/plugin-sdk/{local-roots-KhjQw04O.js → local-roots-CIPRxA-4.js} +4 -4
  45. package/dist/plugin-sdk/{logger-DHIIvMxj.js → logger-CvPFVOgT.js} +2 -2
  46. package/dist/plugin-sdk/{login-C31642Ld.js → login-CCTew9bt.js} +4 -4
  47. package/dist/plugin-sdk/{login-qr--y2SG_Ue.js → login-qr-BI3Vi_wJ.js} +5 -5
  48. package/dist/plugin-sdk/{manager-2UZBMCc7.js → manager-BEoYPn7R.js} +8 -8
  49. package/dist/plugin-sdk/manager-runtime-DxclHQ4U.js +15 -0
  50. package/dist/plugin-sdk/{outbound-Ba0QUI5h.js → outbound-ByOw1K6W.js} +5 -5
  51. package/dist/plugin-sdk/{outbound-attachment-B1Laso-8.js → outbound-attachment-BzVhxRRw.js} +2 -2
  52. package/dist/plugin-sdk/{path-alias-guards-C7Vm5DZ1.js → path-alias-guards-sWayacde.js} +1 -1
  53. package/dist/plugin-sdk/{paths-DopV9PQG.js → paths-Dpg3qxcl.js} +1 -1
  54. package/dist/plugin-sdk/{pi-embedded-helpers-DnA_OCzP.js → pi-embedded-helpers-DIxXkGJf.js} +16 -16
  55. package/dist/plugin-sdk/{pi-model-discovery-DdPqXk8f.js → pi-model-discovery-DM_2uFtj.js} +1 -1
  56. package/dist/plugin-sdk/pi-model-discovery-runtime-BuzvkvNR.js +8 -0
  57. package/dist/plugin-sdk/{pi-tools.before-tool-call.runtime-DxFHiLUE.js → pi-tools.before-tool-call.runtime-w1dqL_ty.js} +4 -4
  58. package/dist/plugin-sdk/{plugins-CbCt4osF.js → plugins-C4USiH29.js} +4 -4
  59. package/dist/plugin-sdk/{proxy-env-C63mMdas.js → proxy-env-ET-rp8eg.js} +1 -1
  60. package/dist/plugin-sdk/{proxy-fetch-Ch95c_Y2.js → proxy-fetch-uDXGKG3Z.js} +1 -1
  61. package/dist/plugin-sdk/{pw-ai-DpJk62D4.js → pw-ai-CyOt3RDA.js} +9 -9
  62. package/dist/plugin-sdk/{qmd-manager-Ca-iSfEE.js → qmd-manager-BySdoVR7.js} +7 -7
  63. package/dist/plugin-sdk/{query-expansion-B_Xe41Ab.js → query-expansion-C6uS-7lj.js} +4 -4
  64. package/dist/plugin-sdk/{redact-hp9TOulW.js → redact-Bvxt1T_Q.js} +1 -1
  65. package/dist/plugin-sdk/{reply-CovBlFea.js → reply-CTCSeQqW.js} +73 -73
  66. package/dist/plugin-sdk/{resolve-outbound-target-BbrHgyUk.js → resolve-outbound-target-Bw8YNANu.js} +2 -2
  67. package/dist/plugin-sdk/{run-with-concurrency-BR1DXa8T.js → run-with-concurrency-C_KCHwvf.js} +1 -1
  68. package/dist/plugin-sdk/runtime-whatsapp-login.runtime-BxgRDkhc.js +10 -0
  69. package/dist/plugin-sdk/runtime-whatsapp-outbound.runtime-elOqrkfg.js +19 -0
  70. package/dist/plugin-sdk/{send-BvAtLLPl.js → send-BZ6nYFZr.js} +5 -5
  71. package/dist/plugin-sdk/{send-BTztm3D2.js → send-C0w6xP2x.js} +6 -6
  72. package/dist/plugin-sdk/{send-CWJUuG0i.js → send-CFf-1V89.js} +8 -8
  73. package/dist/plugin-sdk/{send-EcglC4cG.js → send-CY-Qfwia.js} +7 -7
  74. package/dist/plugin-sdk/{send-BXpXBwM_.js → send-qPyNGSe4.js} +13 -13
  75. package/dist/plugin-sdk/{session-k256LJZT.js → session-COrvpvUQ.js} +3 -3
  76. package/dist/plugin-sdk/signal.js +2 -2
  77. package/dist/plugin-sdk/{skill-commands-DoRqLzxm.js → skill-commands-DZqhtmiv.js} +4 -4
  78. package/dist/plugin-sdk/{skills-QudILG6e.js → skills-Cw_vXEJb.js} +6 -6
  79. package/dist/plugin-sdk/slash-commands.runtime-D67JLweo.js +13 -0
  80. package/dist/plugin-sdk/slash-dispatch.runtime-DvcpvCJ0.js +52 -0
  81. package/dist/plugin-sdk/slash-skill-commands.runtime-BM1x3azR.js +16 -0
  82. package/dist/plugin-sdk/{store-BbDQw3g6.js → store-CMHj6IIw.js} +2 -2
  83. package/dist/plugin-sdk/subagent-registry-runtime-1lbDyRzz.js +52 -0
  84. package/dist/plugin-sdk/{tables-BhvloMKN.js → tables-CSqrHsKL.js} +1 -1
  85. package/dist/plugin-sdk/{thinking-URzkT-3p.js → thinking-DOnsR_A8.js} +7 -7
  86. package/dist/plugin-sdk/{tokens-B1PW5Ayy.js → tokens-BDr0Z9o3.js} +1 -1
  87. package/dist/plugin-sdk/{tool-images-xpqbP6RR.js → tool-images-eEfOVkzf.js} +2 -2
  88. package/dist/plugin-sdk/web-BLyT64pW.js +56 -0
  89. package/dist/plugin-sdk/{whatsapp-actions-RcZ6vp61.js → whatsapp-actions-xcleMoMv.js} +17 -17
  90. package/dist/plugin-sdk/whatsapp.js +50 -50
  91. package/dist/{pw-ai-GcYO6HPE.js → pw-ai-CmphSzHx.js} +1 -1
  92. package/dist/{slash-dispatch.runtime-Dh053pQK.js → slash-dispatch.runtime-131yup2e.js} +6 -6
  93. package/dist/{subagent-registry-runtime-DSi5mnCQ.js → subagent-registry-runtime-DbSf_Je6.js} +6 -6
  94. package/dist/{web-1hWJDzNA.js → web-MR9d7KyB.js} +6 -6
  95. package/package.json +5 -2
  96. package/scripts/create-instance.sh +44 -19
  97. package/scripts/install-maca.sh +39 -28
  98. package/scripts/npm_publish.sh +8 -6
  99. package/ui/index.html +16 -0
  100. package/ui/node_modules/.bin/jiti +21 -0
  101. package/ui/node_modules/.bin/lessc +21 -0
  102. package/ui/node_modules/.bin/marked +21 -0
  103. package/ui/node_modules/.bin/playwright +21 -0
  104. package/ui/node_modules/.bin/sass +21 -0
  105. package/ui/node_modules/.bin/tsx +21 -0
  106. package/ui/node_modules/.bin/vite +21 -0
  107. package/ui/node_modules/.bin/vitest +21 -0
  108. package/ui/node_modules/.bin/yaml +21 -0
  109. package/ui/package.json +27 -0
  110. package/ui/public/apple-touch-icon.png +0 -0
  111. package/ui/public/favicon-32.png +0 -0
  112. package/ui/public/favicon.ico +0 -0
  113. package/ui/public/favicon.svg +22 -0
  114. package/ui/src/css.d.ts +1 -0
  115. package/ui/src/i18n/index.ts +3 -0
  116. package/ui/src/i18n/lib/lit-controller.ts +22 -0
  117. package/ui/src/i18n/lib/registry.ts +64 -0
  118. package/ui/src/i18n/lib/translate.ts +123 -0
  119. package/ui/src/i18n/lib/types.ts +9 -0
  120. package/ui/src/i18n/locales/de.ts +129 -0
  121. package/ui/src/i18n/locales/en.ts +337 -0
  122. package/ui/src/i18n/locales/pt-BR.ts +128 -0
  123. package/ui/src/i18n/locales/zh-CN.ts +330 -0
  124. package/ui/src/i18n/locales/zh-TW.ts +125 -0
  125. package/ui/src/i18n/test/translate.test.ts +56 -0
  126. package/ui/src/main.ts +2 -0
  127. package/ui/src/styles/base.css +385 -0
  128. package/ui/src/styles/chat/grouped.css +300 -0
  129. package/ui/src/styles/chat/layout.css +481 -0
  130. package/ui/src/styles/chat/sidebar.css +117 -0
  131. package/ui/src/styles/chat/text.css +146 -0
  132. package/ui/src/styles/chat/tool-cards.css +202 -0
  133. package/ui/src/styles/chat.css +5 -0
  134. package/ui/src/styles/components.css +2612 -0
  135. package/ui/src/styles/config.css +1658 -0
  136. package/ui/src/styles/layout.css +621 -0
  137. package/ui/src/styles/layout.mobile.css +374 -0
  138. package/ui/src/styles.css +5 -0
  139. package/ui/src/ui/__screenshots__/config-form.browser.test.ts/config-form-renderer-flags-unsupported-unions-1.png +0 -0
  140. package/ui/src/ui/__screenshots__/config-form.browser.test.ts/config-form-renderer-renders-inputs-and-patches-values-1.png +0 -0
  141. package/ui/src/ui/__screenshots__/config-form.browser.test.ts/config-form-renderer-renders-union-literals-as-select-options-1.png +0 -0
  142. package/ui/src/ui/__screenshots__/navigation.browser.test.ts/control-UI-routing-auto-scrolls-chat-history-to-the-latest-message-1.png +0 -0
  143. package/ui/src/ui/app-channels.ts +279 -0
  144. package/ui/src/ui/app-chat.ts +266 -0
  145. package/ui/src/ui/app-defaults.ts +50 -0
  146. package/ui/src/ui/app-events.ts +5 -0
  147. package/ui/src/ui/app-gateway.node.test.ts +229 -0
  148. package/ui/src/ui/app-gateway.ts +349 -0
  149. package/ui/src/ui/app-lifecycle.node.test.ts +44 -0
  150. package/ui/src/ui/app-lifecycle.ts +109 -0
  151. package/ui/src/ui/app-polling.ts +69 -0
  152. package/ui/src/ui/app-render-usage-tab.ts +273 -0
  153. package/ui/src/ui/app-render.helpers.node.test.ts +286 -0
  154. package/ui/src/ui/app-render.helpers.ts +574 -0
  155. package/ui/src/ui/app-render.ts +1168 -0
  156. package/ui/src/ui/app-scroll.test.ts +275 -0
  157. package/ui/src/ui/app-scroll.ts +179 -0
  158. package/ui/src/ui/app-settings.test.ts +70 -0
  159. package/ui/src/ui/app-settings.ts +440 -0
  160. package/ui/src/ui/app-tool-stream.node.test.ts +139 -0
  161. package/ui/src/ui/app-tool-stream.ts +455 -0
  162. package/ui/src/ui/app-view-state.ts +321 -0
  163. package/ui/src/ui/app.ts +621 -0
  164. package/ui/src/ui/assistant-identity.ts +23 -0
  165. package/ui/src/ui/chat/constants.ts +12 -0
  166. package/ui/src/ui/chat/copy-as-markdown.ts +97 -0
  167. package/ui/src/ui/chat/grouped-render.ts +287 -0
  168. package/ui/src/ui/chat/message-extract.test.ts +64 -0
  169. package/ui/src/ui/chat/message-extract.ts +122 -0
  170. package/ui/src/ui/chat/message-normalizer.test.ts +179 -0
  171. package/ui/src/ui/chat/message-normalizer.ts +101 -0
  172. package/ui/src/ui/chat/tool-cards.ts +156 -0
  173. package/ui/src/ui/chat/tool-helpers.test.ts +141 -0
  174. package/ui/src/ui/chat/tool-helpers.ts +37 -0
  175. package/ui/src/ui/chat-event-reload.test.ts +47 -0
  176. package/ui/src/ui/chat-event-reload.ts +16 -0
  177. package/ui/src/ui/chat-markdown.browser.test.ts +37 -0
  178. package/ui/src/ui/components/resizable-divider.ts +110 -0
  179. package/ui/src/ui/config-form.browser.test.ts +443 -0
  180. package/ui/src/ui/controllers/agent-files.ts +126 -0
  181. package/ui/src/ui/controllers/agent-identity.ts +59 -0
  182. package/ui/src/ui/controllers/agent-skills.ts +33 -0
  183. package/ui/src/ui/controllers/agents.test.ts +61 -0
  184. package/ui/src/ui/controllers/agents.ts +64 -0
  185. package/ui/src/ui/controllers/assistant-identity.ts +34 -0
  186. package/ui/src/ui/controllers/channels.ts +94 -0
  187. package/ui/src/ui/controllers/channels.types.ts +15 -0
  188. package/ui/src/ui/controllers/chat.test.ts +568 -0
  189. package/ui/src/ui/controllers/chat.ts +318 -0
  190. package/ui/src/ui/controllers/config/form-coerce.ts +160 -0
  191. package/ui/src/ui/controllers/config/form-utils.node.test.ts +455 -0
  192. package/ui/src/ui/controllers/config/form-utils.ts +90 -0
  193. package/ui/src/ui/controllers/config.test.ts +289 -0
  194. package/ui/src/ui/controllers/config.ts +219 -0
  195. package/ui/src/ui/controllers/control-ui-bootstrap.test.ts +82 -0
  196. package/ui/src/ui/controllers/control-ui-bootstrap.ts +49 -0
  197. package/ui/src/ui/controllers/cron-filters.test.ts +81 -0
  198. package/ui/src/ui/controllers/cron.test.ts +1070 -0
  199. package/ui/src/ui/controllers/cron.ts +921 -0
  200. package/ui/src/ui/controllers/debug.ts +60 -0
  201. package/ui/src/ui/controllers/devices.ts +159 -0
  202. package/ui/src/ui/controllers/exec-approval.ts +100 -0
  203. package/ui/src/ui/controllers/exec-approvals.ts +170 -0
  204. package/ui/src/ui/controllers/logs.ts +147 -0
  205. package/ui/src/ui/controllers/nodes.ts +32 -0
  206. package/ui/src/ui/controllers/presence.ts +37 -0
  207. package/ui/src/ui/controllers/sessions.test.ts +104 -0
  208. package/ui/src/ui/controllers/sessions.ts +127 -0
  209. package/ui/src/ui/controllers/skills.ts +157 -0
  210. package/ui/src/ui/controllers/usage.node.test.ts +181 -0
  211. package/ui/src/ui/controllers/usage.ts +315 -0
  212. package/ui/src/ui/data/moonshot-kimi-k2.ts +45 -0
  213. package/ui/src/ui/device-auth.ts +73 -0
  214. package/ui/src/ui/device-identity.ts +112 -0
  215. package/ui/src/ui/external-link.test.ts +18 -0
  216. package/ui/src/ui/external-link.ts +19 -0
  217. package/ui/src/ui/focus-mode.browser.test.ts +39 -0
  218. package/ui/src/ui/format.test.ts +101 -0
  219. package/ui/src/ui/format.ts +60 -0
  220. package/ui/src/ui/gateway.ts +360 -0
  221. package/ui/src/ui/icons.ts +256 -0
  222. package/ui/src/ui/markdown.test.ts +85 -0
  223. package/ui/src/ui/markdown.ts +139 -0
  224. package/ui/src/ui/navigation.browser.test.ts +188 -0
  225. package/ui/src/ui/navigation.test.ts +189 -0
  226. package/ui/src/ui/navigation.ts +165 -0
  227. package/ui/src/ui/open-external-url.test.ts +108 -0
  228. package/ui/src/ui/open-external-url.ts +73 -0
  229. package/ui/src/ui/presenter.ts +85 -0
  230. package/ui/src/ui/storage.node.test.ts +63 -0
  231. package/ui/src/ui/storage.ts +99 -0
  232. package/ui/src/ui/test-helpers/app-mount.ts +27 -0
  233. package/ui/src/ui/text-direction.test.ts +24 -0
  234. package/ui/src/ui/text-direction.ts +30 -0
  235. package/ui/src/ui/theme-transition.ts +109 -0
  236. package/ui/src/ui/theme.ts +16 -0
  237. package/ui/src/ui/tool-display.ts +159 -0
  238. package/ui/src/ui/types/chat-types.ts +44 -0
  239. package/ui/src/ui/types.ts +627 -0
  240. package/ui/src/ui/ui-types.ts +54 -0
  241. package/ui/src/ui/usage-helpers.node.test.ts +43 -0
  242. package/ui/src/ui/usage-helpers.ts +321 -0
  243. package/ui/src/ui/usage-types.ts +22 -0
  244. package/ui/src/ui/uuid.test.ts +41 -0
  245. package/ui/src/ui/uuid.ts +57 -0
  246. package/ui/src/ui/views/agents-panels-status-files.ts +461 -0
  247. package/ui/src/ui/views/agents-panels-tools-skills.browser.test.ts +102 -0
  248. package/ui/src/ui/views/agents-panels-tools-skills.ts +537 -0
  249. package/ui/src/ui/views/agents-utils.test.ts +100 -0
  250. package/ui/src/ui/views/agents-utils.ts +502 -0
  251. package/ui/src/ui/views/agents.ts +499 -0
  252. package/ui/src/ui/views/channel-config-extras.ts +49 -0
  253. package/ui/src/ui/views/channels.config.ts +155 -0
  254. package/ui/src/ui/views/channels.discord.ts +65 -0
  255. package/ui/src/ui/views/channels.googlechat.ts +79 -0
  256. package/ui/src/ui/views/channels.imessage.ts +65 -0
  257. package/ui/src/ui/views/channels.nostr-profile-form.ts +321 -0
  258. package/ui/src/ui/views/channels.nostr.ts +237 -0
  259. package/ui/src/ui/views/channels.shared.ts +38 -0
  260. package/ui/src/ui/views/channels.signal.ts +69 -0
  261. package/ui/src/ui/views/channels.slack.ts +65 -0
  262. package/ui/src/ui/views/channels.telegram.ts +120 -0
  263. package/ui/src/ui/views/channels.ts +325 -0
  264. package/ui/src/ui/views/channels.types.ts +62 -0
  265. package/ui/src/ui/views/channels.whatsapp.ts +118 -0
  266. package/ui/src/ui/views/chat-image-open.browser.test.ts +70 -0
  267. package/ui/src/ui/views/chat.test.ts +227 -0
  268. package/ui/src/ui/views/chat.ts +616 -0
  269. package/ui/src/ui/views/config-form.analyze.ts +267 -0
  270. package/ui/src/ui/views/config-form.node.ts +1073 -0
  271. package/ui/src/ui/views/config-form.render.ts +478 -0
  272. package/ui/src/ui/views/config-form.search.node.test.ts +69 -0
  273. package/ui/src/ui/views/config-form.shared.ts +96 -0
  274. package/ui/src/ui/views/config-form.ts +4 -0
  275. package/ui/src/ui/views/config-search.node.test.ts +50 -0
  276. package/ui/src/ui/views/config-search.ts +92 -0
  277. package/ui/src/ui/views/config.browser.test.ts +233 -0
  278. package/ui/src/ui/views/config.ts +820 -0
  279. package/ui/src/ui/views/cron.test.ts +741 -0
  280. package/ui/src/ui/views/cron.ts +1758 -0
  281. package/ui/src/ui/views/debug.ts +151 -0
  282. package/ui/src/ui/views/exec-approval.ts +89 -0
  283. package/ui/src/ui/views/gateway-url-confirmation.ts +40 -0
  284. package/ui/src/ui/views/instances.ts +89 -0
  285. package/ui/src/ui/views/logs.ts +155 -0
  286. package/ui/src/ui/views/markdown-sidebar.ts +40 -0
  287. package/ui/src/ui/views/nodes-exec-approvals.ts +617 -0
  288. package/ui/src/ui/views/nodes-shared.ts +67 -0
  289. package/ui/src/ui/views/nodes.ts +485 -0
  290. package/ui/src/ui/views/overview-hints.ts +16 -0
  291. package/ui/src/ui/views/overview.node.test.ts +39 -0
  292. package/ui/src/ui/views/overview.ts +361 -0
  293. package/ui/src/ui/views/sessions.test.ts +81 -0
  294. package/ui/src/ui/views/sessions.ts +321 -0
  295. package/ui/src/ui/views/skills-grouping.ts +40 -0
  296. package/ui/src/ui/views/skills-shared.ts +52 -0
  297. package/ui/src/ui/views/skills.ts +192 -0
  298. package/ui/src/ui/views/usage-metrics.ts +578 -0
  299. package/ui/src/ui/views/usage-query.ts +277 -0
  300. package/ui/src/ui/views/usage-render-details.test.ts +136 -0
  301. package/ui/src/ui/views/usage-render-details.ts +1083 -0
  302. package/ui/src/ui/views/usage-render-overview.ts +796 -0
  303. package/ui/src/ui/views/usage-styles/usageStyles-part1.ts +701 -0
  304. package/ui/src/ui/views/usage-styles/usageStyles-part2.ts +702 -0
  305. package/ui/src/ui/views/usage-styles/usageStyles-part3.ts +551 -0
  306. package/ui/src/ui/views/usage.ts +836 -0
  307. package/ui/src/ui/views/usageStyles.ts +5 -0
  308. package/ui/src/ui/views/usageTypes.ts +105 -0
  309. package/ui/vite.config.ts +43 -0
  310. package/ui/vitest.config.ts +15 -0
  311. package/ui/vitest.node.config.ts +10 -0
  312. package/dist/plugin-sdk/deliver-runtime-BFdqklJM.js +0 -32
  313. package/dist/plugin-sdk/deps-send-discord.runtime-DuqpYwU0.js +0 -23
  314. package/dist/plugin-sdk/deps-send-imessage.runtime-CZ2rS8Lb.js +0 -22
  315. package/dist/plugin-sdk/deps-send-signal.runtime-BdqiWhIh.js +0 -21
  316. package/dist/plugin-sdk/deps-send-slack.runtime-04s36qiC.js +0 -19
  317. package/dist/plugin-sdk/deps-send-telegram.runtime-LE5tkPvr.js +0 -24
  318. package/dist/plugin-sdk/deps-send-whatsapp.runtime-Bz57lobC.js +0 -57
  319. package/dist/plugin-sdk/image-runtime-B8twoubs.js +0 -25
  320. package/dist/plugin-sdk/manager-runtime-CMeLwose.js +0 -15
  321. package/dist/plugin-sdk/pi-model-discovery-runtime-D8CJhtJY.js +0 -8
  322. package/dist/plugin-sdk/runtime-whatsapp-login.runtime-SkO91TZH.js +0 -10
  323. package/dist/plugin-sdk/runtime-whatsapp-outbound.runtime-B0VWK5hm.js +0 -19
  324. package/dist/plugin-sdk/slash-commands.runtime-DS6vCNSL.js +0 -13
  325. package/dist/plugin-sdk/slash-dispatch.runtime-BXrxb2wd.js +0 -52
  326. package/dist/plugin-sdk/slash-skill-commands.runtime-Bd6qQ2oT.js +0 -16
  327. package/dist/plugin-sdk/subagent-registry-runtime-1uwQbuXj.js +0 -52
  328. package/dist/plugin-sdk/web-B74yhL2N.js +0 -56
@@ -0,0 +1,179 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import {
3
+ normalizeMessage,
4
+ normalizeRoleForGrouping,
5
+ isToolResultMessage,
6
+ } from "./message-normalizer.ts";
7
+
8
+ describe("message-normalizer", () => {
9
+ describe("normalizeMessage", () => {
10
+ beforeEach(() => {
11
+ vi.useFakeTimers();
12
+ vi.setSystemTime(new Date("2024-01-01T00:00:00Z"));
13
+ });
14
+
15
+ afterEach(() => {
16
+ vi.useRealTimers();
17
+ });
18
+
19
+ it("normalizes message with string content", () => {
20
+ const result = normalizeMessage({
21
+ role: "user",
22
+ content: "Hello world",
23
+ timestamp: 1000,
24
+ id: "msg-1",
25
+ });
26
+
27
+ expect(result).toEqual({
28
+ role: "user",
29
+ content: [{ type: "text", text: "Hello world" }],
30
+ timestamp: 1000,
31
+ id: "msg-1",
32
+ });
33
+ });
34
+
35
+ it("normalizes message with array content", () => {
36
+ const result = normalizeMessage({
37
+ role: "assistant",
38
+ content: [
39
+ { type: "text", text: "Here is the result" },
40
+ { type: "tool_use", name: "bash", args: { command: "ls" } },
41
+ ],
42
+ timestamp: 2000,
43
+ });
44
+
45
+ expect(result.role).toBe("assistant");
46
+ expect(result.content).toHaveLength(2);
47
+ expect(result.content[0]).toEqual({
48
+ type: "text",
49
+ text: "Here is the result",
50
+ name: undefined,
51
+ args: undefined,
52
+ });
53
+ expect(result.content[1]).toEqual({
54
+ type: "tool_use",
55
+ text: undefined,
56
+ name: "bash",
57
+ args: { command: "ls" },
58
+ });
59
+ });
60
+
61
+ it("normalizes message with text field (alternative format)", () => {
62
+ const result = normalizeMessage({
63
+ role: "user",
64
+ text: "Alternative format",
65
+ });
66
+
67
+ expect(result.content).toEqual([{ type: "text", text: "Alternative format" }]);
68
+ });
69
+
70
+ it("detects tool result by toolCallId", () => {
71
+ const result = normalizeMessage({
72
+ role: "assistant",
73
+ toolCallId: "call-123",
74
+ content: "Tool output",
75
+ });
76
+
77
+ expect(result.role).toBe("toolResult");
78
+ });
79
+
80
+ it("detects tool result by tool_call_id (snake_case)", () => {
81
+ const result = normalizeMessage({
82
+ role: "assistant",
83
+ tool_call_id: "call-456",
84
+ content: "Tool output",
85
+ });
86
+
87
+ expect(result.role).toBe("toolResult");
88
+ });
89
+
90
+ it("handles missing role", () => {
91
+ const result = normalizeMessage({ content: "No role" });
92
+ expect(result.role).toBe("unknown");
93
+ });
94
+
95
+ it("handles missing content", () => {
96
+ const result = normalizeMessage({ role: "user" });
97
+ expect(result.content).toEqual([]);
98
+ });
99
+
100
+ it("uses current timestamp when not provided", () => {
101
+ const result = normalizeMessage({ role: "user", content: "Test" });
102
+ expect(result.timestamp).toBe(Date.now());
103
+ });
104
+
105
+ it("handles arguments field (alternative to args)", () => {
106
+ const result = normalizeMessage({
107
+ role: "assistant",
108
+ content: [{ type: "tool_use", name: "test", arguments: { foo: "bar" } }],
109
+ });
110
+
111
+ expect(result.content[0].args).toEqual({ foo: "bar" });
112
+ });
113
+ });
114
+
115
+ describe("normalizeRoleForGrouping", () => {
116
+ it("returns tool for toolresult", () => {
117
+ expect(normalizeRoleForGrouping("toolresult")).toBe("tool");
118
+ expect(normalizeRoleForGrouping("toolResult")).toBe("tool");
119
+ expect(normalizeRoleForGrouping("TOOLRESULT")).toBe("tool");
120
+ });
121
+
122
+ it("returns tool for tool_result", () => {
123
+ expect(normalizeRoleForGrouping("tool_result")).toBe("tool");
124
+ expect(normalizeRoleForGrouping("TOOL_RESULT")).toBe("tool");
125
+ });
126
+
127
+ it("returns tool for tool", () => {
128
+ expect(normalizeRoleForGrouping("tool")).toBe("tool");
129
+ expect(normalizeRoleForGrouping("Tool")).toBe("tool");
130
+ });
131
+
132
+ it("returns tool for function", () => {
133
+ expect(normalizeRoleForGrouping("function")).toBe("tool");
134
+ expect(normalizeRoleForGrouping("Function")).toBe("tool");
135
+ });
136
+
137
+ it("preserves user role", () => {
138
+ expect(normalizeRoleForGrouping("user")).toBe("user");
139
+ expect(normalizeRoleForGrouping("User")).toBe("User");
140
+ });
141
+
142
+ it("preserves assistant role", () => {
143
+ expect(normalizeRoleForGrouping("assistant")).toBe("assistant");
144
+ });
145
+
146
+ it("preserves system role", () => {
147
+ expect(normalizeRoleForGrouping("system")).toBe("system");
148
+ });
149
+ });
150
+
151
+ describe("isToolResultMessage", () => {
152
+ it("returns true for toolresult role", () => {
153
+ expect(isToolResultMessage({ role: "toolresult" })).toBe(true);
154
+ expect(isToolResultMessage({ role: "toolResult" })).toBe(true);
155
+ expect(isToolResultMessage({ role: "TOOLRESULT" })).toBe(true);
156
+ });
157
+
158
+ it("returns true for tool_result role", () => {
159
+ expect(isToolResultMessage({ role: "tool_result" })).toBe(true);
160
+ expect(isToolResultMessage({ role: "TOOL_RESULT" })).toBe(true);
161
+ });
162
+
163
+ it("returns false for other roles", () => {
164
+ expect(isToolResultMessage({ role: "user" })).toBe(false);
165
+ expect(isToolResultMessage({ role: "assistant" })).toBe(false);
166
+ expect(isToolResultMessage({ role: "tool" })).toBe(false);
167
+ });
168
+
169
+ it("returns false for missing role", () => {
170
+ expect(isToolResultMessage({})).toBe(false);
171
+ expect(isToolResultMessage({ content: "test" })).toBe(false);
172
+ });
173
+
174
+ it("returns false for non-string role", () => {
175
+ expect(isToolResultMessage({ role: 123 })).toBe(false);
176
+ expect(isToolResultMessage({ role: null })).toBe(false);
177
+ });
178
+ });
179
+ });
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Message normalization utilities for chat rendering.
3
+ */
4
+
5
+ import { stripInboundMetadata } from "../../../../src/auto-reply/reply/strip-inbound-meta.js";
6
+ import type { NormalizedMessage, MessageContentItem } from "../types/chat-types.ts";
7
+
8
+ /**
9
+ * Normalize a raw message object into a consistent structure.
10
+ */
11
+ export function normalizeMessage(message: unknown): NormalizedMessage {
12
+ const m = message as Record<string, unknown>;
13
+ let role = typeof m.role === "string" ? m.role : "unknown";
14
+
15
+ // Detect tool messages by common gateway shapes.
16
+ // Some tool events come through as assistant role with tool_* items in the content array.
17
+ const hasToolId = typeof m.toolCallId === "string" || typeof m.tool_call_id === "string";
18
+
19
+ const contentRaw = m.content;
20
+ const contentItems = Array.isArray(contentRaw) ? contentRaw : null;
21
+ const hasToolContent =
22
+ Array.isArray(contentItems) &&
23
+ contentItems.some((item) => {
24
+ const x = item as Record<string, unknown>;
25
+ const t = (typeof x.type === "string" ? x.type : "").toLowerCase();
26
+ return t === "toolresult" || t === "tool_result";
27
+ });
28
+
29
+ const hasToolName = typeof m.toolName === "string" || typeof m.tool_name === "string";
30
+
31
+ if (hasToolId || hasToolContent || hasToolName) {
32
+ role = "toolResult";
33
+ }
34
+
35
+ // Extract content
36
+ let content: MessageContentItem[] = [];
37
+
38
+ if (typeof m.content === "string") {
39
+ content = [{ type: "text", text: m.content }];
40
+ } else if (Array.isArray(m.content)) {
41
+ content = m.content.map((item: Record<string, unknown>) => ({
42
+ type: (item.type as MessageContentItem["type"]) || "text",
43
+ text: item.text as string | undefined,
44
+ name: item.name as string | undefined,
45
+ args: item.args || item.arguments,
46
+ }));
47
+ } else if (typeof m.text === "string") {
48
+ content = [{ type: "text", text: m.text }];
49
+ }
50
+
51
+ const timestamp = typeof m.timestamp === "number" ? m.timestamp : Date.now();
52
+ const id = typeof m.id === "string" ? m.id : undefined;
53
+
54
+ // Strip AI-injected metadata prefix blocks from user messages before display.
55
+ if (role === "user" || role === "User") {
56
+ content = content.map((item) => {
57
+ if (item.type === "text" && typeof item.text === "string") {
58
+ return { ...item, text: stripInboundMetadata(item.text) };
59
+ }
60
+ return item;
61
+ });
62
+ }
63
+
64
+ return { role, content, timestamp, id };
65
+ }
66
+
67
+ /**
68
+ * Normalize role for grouping purposes.
69
+ */
70
+ export function normalizeRoleForGrouping(role: string): string {
71
+ const lower = role.toLowerCase();
72
+ // Preserve original casing when it's already a core role.
73
+ if (role === "user" || role === "User") {
74
+ return role;
75
+ }
76
+ if (role === "assistant") {
77
+ return "assistant";
78
+ }
79
+ if (role === "system") {
80
+ return "system";
81
+ }
82
+ // Keep tool-related roles distinct so the UI can style/toggle them.
83
+ if (
84
+ lower === "toolresult" ||
85
+ lower === "tool_result" ||
86
+ lower === "tool" ||
87
+ lower === "function"
88
+ ) {
89
+ return "tool";
90
+ }
91
+ return role;
92
+ }
93
+
94
+ /**
95
+ * Check if a message is a tool result message based on its role.
96
+ */
97
+ export function isToolResultMessage(message: unknown): boolean {
98
+ const m = message as Record<string, unknown>;
99
+ const role = typeof m.role === "string" ? m.role.toLowerCase() : "";
100
+ return role === "toolresult" || role === "tool_result";
101
+ }
@@ -0,0 +1,156 @@
1
+ import { html, nothing } from "lit";
2
+ import { icons } from "../icons.ts";
3
+ import { formatToolDetail, resolveToolDisplay } from "../tool-display.ts";
4
+ import type { ToolCard } from "../types/chat-types.ts";
5
+ import { TOOL_INLINE_THRESHOLD } from "./constants.ts";
6
+ import { extractTextCached } from "./message-extract.ts";
7
+ import { isToolResultMessage } from "./message-normalizer.ts";
8
+ import { formatToolOutputForSidebar, getTruncatedPreview } from "./tool-helpers.ts";
9
+
10
+ export function extractToolCards(message: unknown): ToolCard[] {
11
+ const m = message as Record<string, unknown>;
12
+ const content = normalizeContent(m.content);
13
+ const cards: ToolCard[] = [];
14
+
15
+ for (const item of content) {
16
+ const kind = (typeof item.type === "string" ? item.type : "").toLowerCase();
17
+ const isToolCall =
18
+ ["toolcall", "tool_call", "tooluse", "tool_use"].includes(kind) ||
19
+ (typeof item.name === "string" && item.arguments != null);
20
+ if (isToolCall) {
21
+ cards.push({
22
+ kind: "call",
23
+ name: (item.name as string) ?? "tool",
24
+ args: coerceArgs(item.arguments ?? item.args),
25
+ });
26
+ }
27
+ }
28
+
29
+ for (const item of content) {
30
+ const kind = (typeof item.type === "string" ? item.type : "").toLowerCase();
31
+ if (kind !== "toolresult" && kind !== "tool_result") {
32
+ continue;
33
+ }
34
+ const text = extractToolText(item);
35
+ const name = typeof item.name === "string" ? item.name : "tool";
36
+ cards.push({ kind: "result", name, text });
37
+ }
38
+
39
+ if (isToolResultMessage(message) && !cards.some((card) => card.kind === "result")) {
40
+ const name =
41
+ (typeof m.toolName === "string" && m.toolName) ||
42
+ (typeof m.tool_name === "string" && m.tool_name) ||
43
+ "tool";
44
+ const text = extractTextCached(message) ?? undefined;
45
+ cards.push({ kind: "result", name, text });
46
+ }
47
+
48
+ return cards;
49
+ }
50
+
51
+ export function renderToolCardSidebar(card: ToolCard, onOpenSidebar?: (content: string) => void) {
52
+ const display = resolveToolDisplay({ name: card.name, args: card.args });
53
+ const detail = formatToolDetail(display);
54
+ const hasText = Boolean(card.text?.trim());
55
+
56
+ const canClick = Boolean(onOpenSidebar);
57
+ const handleClick = canClick
58
+ ? () => {
59
+ if (hasText) {
60
+ onOpenSidebar!(formatToolOutputForSidebar(card.text!));
61
+ return;
62
+ }
63
+ const info = `## ${display.label}\n\n${
64
+ detail ? `**Command:** \`${detail}\`\n\n` : ""
65
+ }*No output — tool completed successfully.*`;
66
+ onOpenSidebar!(info);
67
+ }
68
+ : undefined;
69
+
70
+ const isShort = hasText && (card.text?.length ?? 0) <= TOOL_INLINE_THRESHOLD;
71
+ const showCollapsed = hasText && !isShort;
72
+ const showInline = hasText && isShort;
73
+ const isEmpty = !hasText;
74
+
75
+ return html`
76
+ <div
77
+ class="chat-tool-card ${canClick ? "chat-tool-card--clickable" : ""}"
78
+ @click=${handleClick}
79
+ role=${canClick ? "button" : nothing}
80
+ tabindex=${canClick ? "0" : nothing}
81
+ @keydown=${
82
+ canClick
83
+ ? (e: KeyboardEvent) => {
84
+ if (e.key !== "Enter" && e.key !== " ") {
85
+ return;
86
+ }
87
+ e.preventDefault();
88
+ handleClick?.();
89
+ }
90
+ : nothing
91
+ }
92
+ >
93
+ <div class="chat-tool-card__header">
94
+ <div class="chat-tool-card__title">
95
+ <span class="chat-tool-card__icon">${icons[display.icon]}</span>
96
+ <span>${display.label}</span>
97
+ </div>
98
+ ${
99
+ canClick
100
+ ? html`<span class="chat-tool-card__action">${hasText ? "View" : ""} ${icons.check}</span>`
101
+ : nothing
102
+ }
103
+ ${isEmpty && !canClick ? html`<span class="chat-tool-card__status">${icons.check}</span>` : nothing}
104
+ </div>
105
+ ${detail ? html`<div class="chat-tool-card__detail">${detail}</div>` : nothing}
106
+ ${
107
+ isEmpty
108
+ ? html`
109
+ <div class="chat-tool-card__status-text muted">Completed</div>
110
+ `
111
+ : nothing
112
+ }
113
+ ${
114
+ showCollapsed
115
+ ? html`<div class="chat-tool-card__preview mono">${getTruncatedPreview(card.text!)}</div>`
116
+ : nothing
117
+ }
118
+ ${showInline ? html`<div class="chat-tool-card__inline mono">${card.text}</div>` : nothing}
119
+ </div>
120
+ `;
121
+ }
122
+
123
+ function normalizeContent(content: unknown): Array<Record<string, unknown>> {
124
+ if (!Array.isArray(content)) {
125
+ return [];
126
+ }
127
+ return content.filter(Boolean) as Array<Record<string, unknown>>;
128
+ }
129
+
130
+ function coerceArgs(value: unknown): unknown {
131
+ if (typeof value !== "string") {
132
+ return value;
133
+ }
134
+ const trimmed = value.trim();
135
+ if (!trimmed) {
136
+ return value;
137
+ }
138
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
139
+ return value;
140
+ }
141
+ try {
142
+ return JSON.parse(trimmed);
143
+ } catch {
144
+ return value;
145
+ }
146
+ }
147
+
148
+ function extractToolText(item: Record<string, unknown>): string | undefined {
149
+ if (typeof item.text === "string") {
150
+ return item.text;
151
+ }
152
+ if (typeof item.content === "string") {
153
+ return item.content;
154
+ }
155
+ return undefined;
156
+ }
@@ -0,0 +1,141 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { formatToolOutputForSidebar, getTruncatedPreview } from "./tool-helpers.ts";
3
+
4
+ describe("tool-helpers", () => {
5
+ describe("formatToolOutputForSidebar", () => {
6
+ it("formats valid JSON object as code block", () => {
7
+ const input = '{"name":"test","value":123}';
8
+ const result = formatToolOutputForSidebar(input);
9
+
10
+ expect(result).toBe(`\`\`\`json
11
+ {
12
+ "name": "test",
13
+ "value": 123
14
+ }
15
+ \`\`\``);
16
+ });
17
+
18
+ it("formats valid JSON array as code block", () => {
19
+ const input = "[1, 2, 3]";
20
+ const result = formatToolOutputForSidebar(input);
21
+
22
+ expect(result).toBe(`\`\`\`json
23
+ [
24
+ 1,
25
+ 2,
26
+ 3
27
+ ]
28
+ \`\`\``);
29
+ });
30
+
31
+ it("handles nested JSON objects", () => {
32
+ const input = '{"outer":{"inner":"value"}}';
33
+ const result = formatToolOutputForSidebar(input);
34
+
35
+ expect(result).toContain("```json");
36
+ expect(result).toContain('"outer"');
37
+ expect(result).toContain('"inner"');
38
+ });
39
+
40
+ it("returns plain text for non-JSON content", () => {
41
+ const input = "This is plain text output";
42
+ const result = formatToolOutputForSidebar(input);
43
+
44
+ expect(result).toBe("This is plain text output");
45
+ });
46
+
47
+ it("returns as-is for invalid JSON starting with {", () => {
48
+ const input = "{not valid json";
49
+ const result = formatToolOutputForSidebar(input);
50
+
51
+ expect(result).toBe("{not valid json");
52
+ });
53
+
54
+ it("returns as-is for invalid JSON starting with [", () => {
55
+ const input = "[not valid json";
56
+ const result = formatToolOutputForSidebar(input);
57
+
58
+ expect(result).toBe("[not valid json");
59
+ });
60
+
61
+ it("trims whitespace before detecting JSON", () => {
62
+ const input = ' {"trimmed": true} ';
63
+ const result = formatToolOutputForSidebar(input);
64
+
65
+ expect(result).toContain("```json");
66
+ expect(result).toContain('"trimmed"');
67
+ });
68
+
69
+ it("handles empty string", () => {
70
+ const result = formatToolOutputForSidebar("");
71
+ expect(result).toBe("");
72
+ });
73
+
74
+ it("handles whitespace-only string", () => {
75
+ const result = formatToolOutputForSidebar(" ");
76
+ expect(result).toBe(" ");
77
+ });
78
+ });
79
+
80
+ describe("getTruncatedPreview", () => {
81
+ it("returns short text unchanged", () => {
82
+ const input = "Short text";
83
+ const result = getTruncatedPreview(input);
84
+
85
+ expect(result).toBe("Short text");
86
+ });
87
+
88
+ it("truncates text longer than max chars", () => {
89
+ const input = "a".repeat(150);
90
+ const result = getTruncatedPreview(input);
91
+
92
+ expect(result.length).toBe(101); // 100 chars + ellipsis
93
+ expect(result.endsWith("…")).toBe(true);
94
+ });
95
+
96
+ it("truncates to max lines", () => {
97
+ const input = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5";
98
+ const result = getTruncatedPreview(input);
99
+
100
+ // Should only show first 2 lines (PREVIEW_MAX_LINES = 2)
101
+ expect(result).toBe("Line 1\nLine 2…");
102
+ });
103
+
104
+ it("adds ellipsis when lines are truncated", () => {
105
+ const input = "Line 1\nLine 2\nLine 3";
106
+ const result = getTruncatedPreview(input);
107
+
108
+ expect(result.endsWith("…")).toBe(true);
109
+ });
110
+
111
+ it("does not add ellipsis when all lines fit", () => {
112
+ const input = "Line 1\nLine 2";
113
+ const result = getTruncatedPreview(input);
114
+
115
+ expect(result).toBe("Line 1\nLine 2");
116
+ expect(result.endsWith("…")).toBe(false);
117
+ });
118
+
119
+ it("handles single line within limits", () => {
120
+ const input = "Single line";
121
+ const result = getTruncatedPreview(input);
122
+
123
+ expect(result).toBe("Single line");
124
+ });
125
+
126
+ it("handles empty string", () => {
127
+ const result = getTruncatedPreview("");
128
+ expect(result).toBe("");
129
+ });
130
+
131
+ it("truncates by chars even within line limit", () => {
132
+ // Two lines but very long content
133
+ const longLine = "x".repeat(80);
134
+ const input = `${longLine}\n${longLine}`;
135
+ const result = getTruncatedPreview(input);
136
+
137
+ expect(result.length).toBe(101); // 100 + ellipsis
138
+ expect(result.endsWith("…")).toBe(true);
139
+ });
140
+ });
141
+ });
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Helper functions for tool card rendering.
3
+ */
4
+
5
+ import { PREVIEW_MAX_CHARS, PREVIEW_MAX_LINES } from "./constants.ts";
6
+
7
+ /**
8
+ * Format tool output content for display in the sidebar.
9
+ * Detects JSON and wraps it in a code block with formatting.
10
+ */
11
+ export function formatToolOutputForSidebar(text: string): string {
12
+ const trimmed = text.trim();
13
+ // Try to detect and format JSON
14
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
15
+ try {
16
+ const parsed = JSON.parse(trimmed);
17
+ return "```json\n" + JSON.stringify(parsed, null, 2) + "\n```";
18
+ } catch {
19
+ // Not valid JSON, return as-is
20
+ }
21
+ }
22
+ return text;
23
+ }
24
+
25
+ /**
26
+ * Get a truncated preview of tool output text.
27
+ * Truncates to first N lines or first N characters, whichever is shorter.
28
+ */
29
+ export function getTruncatedPreview(text: string): string {
30
+ const allLines = text.split("\n");
31
+ const lines = allLines.slice(0, PREVIEW_MAX_LINES);
32
+ const preview = lines.join("\n");
33
+ if (preview.length > PREVIEW_MAX_CHARS) {
34
+ return preview.slice(0, PREVIEW_MAX_CHARS) + "…";
35
+ }
36
+ return lines.length < allLines.length ? preview + "…" : preview;
37
+ }
@@ -0,0 +1,47 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { shouldReloadHistoryForFinalEvent } from "./chat-event-reload.ts";
3
+
4
+ describe("shouldReloadHistoryForFinalEvent", () => {
5
+ it("returns false for non-final events", () => {
6
+ expect(
7
+ shouldReloadHistoryForFinalEvent({
8
+ runId: "run-1",
9
+ sessionKey: "main",
10
+ state: "delta",
11
+ message: { role: "assistant", content: [{ type: "text", text: "x" }] },
12
+ }),
13
+ ).toBe(false);
14
+ });
15
+
16
+ it("returns true when final event has no message payload", () => {
17
+ expect(
18
+ shouldReloadHistoryForFinalEvent({
19
+ runId: "run-1",
20
+ sessionKey: "main",
21
+ state: "final",
22
+ }),
23
+ ).toBe(true);
24
+ });
25
+
26
+ it("returns false when final event includes assistant payload", () => {
27
+ expect(
28
+ shouldReloadHistoryForFinalEvent({
29
+ runId: "run-1",
30
+ sessionKey: "main",
31
+ state: "final",
32
+ message: { role: "assistant", content: [{ type: "text", text: "done" }] },
33
+ }),
34
+ ).toBe(false);
35
+ });
36
+
37
+ it("returns true when final event message role is non-assistant", () => {
38
+ expect(
39
+ shouldReloadHistoryForFinalEvent({
40
+ runId: "run-1",
41
+ sessionKey: "main",
42
+ state: "final",
43
+ message: { role: "user", content: [{ type: "text", text: "echo" }] },
44
+ }),
45
+ ).toBe(true);
46
+ });
47
+ });
@@ -0,0 +1,16 @@
1
+ import type { ChatEventPayload } from "./controllers/chat.ts";
2
+
3
+ export function shouldReloadHistoryForFinalEvent(payload?: ChatEventPayload): boolean {
4
+ if (!payload || payload.state !== "final") {
5
+ return false;
6
+ }
7
+ if (!payload.message || typeof payload.message !== "object") {
8
+ return true;
9
+ }
10
+ const message = payload.message as Record<string, unknown>;
11
+ const role = typeof message.role === "string" ? message.role.toLowerCase() : "";
12
+ if (role && role !== "assistant") {
13
+ return true;
14
+ }
15
+ return false;
16
+ }