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,97 @@
1
+ import { html, type TemplateResult } from "lit";
2
+ import { icons } from "../icons.ts";
3
+
4
+ const COPIED_FOR_MS = 1500;
5
+ const ERROR_FOR_MS = 2000;
6
+ const COPY_LABEL = "Copy as markdown";
7
+ const COPIED_LABEL = "Copied";
8
+ const ERROR_LABEL = "Copy failed";
9
+
10
+ type CopyButtonOptions = {
11
+ text: () => string;
12
+ label?: string;
13
+ };
14
+
15
+ async function copyTextToClipboard(text: string): Promise<boolean> {
16
+ if (!text) {
17
+ return false;
18
+ }
19
+
20
+ try {
21
+ await navigator.clipboard.writeText(text);
22
+ return true;
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
27
+
28
+ function setButtonLabel(button: HTMLButtonElement, label: string) {
29
+ button.title = label;
30
+ button.setAttribute("aria-label", label);
31
+ }
32
+
33
+ function createCopyButton(options: CopyButtonOptions): TemplateResult {
34
+ const idleLabel = options.label ?? COPY_LABEL;
35
+ return html`
36
+ <button
37
+ class="chat-copy-btn"
38
+ type="button"
39
+ title=${idleLabel}
40
+ aria-label=${idleLabel}
41
+ @click=${async (e: Event) => {
42
+ const btn = e.currentTarget as HTMLButtonElement | null;
43
+
44
+ if (!btn || btn.dataset.copying === "1") {
45
+ return;
46
+ }
47
+
48
+ btn.dataset.copying = "1";
49
+ btn.setAttribute("aria-busy", "true");
50
+ btn.disabled = true;
51
+
52
+ const copied = await copyTextToClipboard(options.text());
53
+ if (!btn.isConnected) {
54
+ return;
55
+ }
56
+
57
+ delete btn.dataset.copying;
58
+ btn.removeAttribute("aria-busy");
59
+ btn.disabled = false;
60
+
61
+ if (!copied) {
62
+ btn.dataset.error = "1";
63
+ setButtonLabel(btn, ERROR_LABEL);
64
+
65
+ window.setTimeout(() => {
66
+ if (!btn.isConnected) {
67
+ return;
68
+ }
69
+ delete btn.dataset.error;
70
+ setButtonLabel(btn, idleLabel);
71
+ }, ERROR_FOR_MS);
72
+ return;
73
+ }
74
+
75
+ btn.dataset.copied = "1";
76
+ setButtonLabel(btn, COPIED_LABEL);
77
+
78
+ window.setTimeout(() => {
79
+ if (!btn.isConnected) {
80
+ return;
81
+ }
82
+ delete btn.dataset.copied;
83
+ setButtonLabel(btn, idleLabel);
84
+ }, COPIED_FOR_MS);
85
+ }}
86
+ >
87
+ <span class="chat-copy-btn__icon" aria-hidden="true">
88
+ <span class="chat-copy-btn__icon-copy">${icons.copy}</span>
89
+ <span class="chat-copy-btn__icon-check">${icons.check}</span>
90
+ </span>
91
+ </button>
92
+ `;
93
+ }
94
+
95
+ export function renderCopyAsMarkdownButton(markdown: string): TemplateResult {
96
+ return createCopyButton({ text: () => markdown, label: COPY_LABEL });
97
+ }
@@ -0,0 +1,287 @@
1
+ import { html, nothing } from "lit";
2
+ import { unsafeHTML } from "lit/directives/unsafe-html.js";
3
+ import type { AssistantIdentity } from "../assistant-identity.ts";
4
+ import { toSanitizedMarkdownHtml } from "../markdown.ts";
5
+ import { openExternalUrlSafe } from "../open-external-url.ts";
6
+ import { detectTextDirection } from "../text-direction.ts";
7
+ import type { MessageGroup } from "../types/chat-types.ts";
8
+ import { renderCopyAsMarkdownButton } from "./copy-as-markdown.ts";
9
+ import {
10
+ extractTextCached,
11
+ extractThinkingCached,
12
+ formatReasoningMarkdown,
13
+ } from "./message-extract.ts";
14
+ import { isToolResultMessage, normalizeRoleForGrouping } from "./message-normalizer.ts";
15
+ import { extractToolCards, renderToolCardSidebar } from "./tool-cards.ts";
16
+
17
+ type ImageBlock = {
18
+ url: string;
19
+ alt?: string;
20
+ };
21
+
22
+ function extractImages(message: unknown): ImageBlock[] {
23
+ const m = message as Record<string, unknown>;
24
+ const content = m.content;
25
+ const images: ImageBlock[] = [];
26
+
27
+ if (Array.isArray(content)) {
28
+ for (const block of content) {
29
+ if (typeof block !== "object" || block === null) {
30
+ continue;
31
+ }
32
+ const b = block as Record<string, unknown>;
33
+
34
+ if (b.type === "image") {
35
+ // Handle source object format (from sendChatMessage)
36
+ const source = b.source as Record<string, unknown> | undefined;
37
+ if (source?.type === "base64" && typeof source.data === "string") {
38
+ const data = source.data;
39
+ const mediaType = (source.media_type as string) || "image/png";
40
+ // If data is already a data URL, use it directly
41
+ const url = data.startsWith("data:") ? data : `data:${mediaType};base64,${data}`;
42
+ images.push({ url });
43
+ } else if (typeof b.url === "string") {
44
+ images.push({ url: b.url });
45
+ }
46
+ } else if (b.type === "image_url") {
47
+ // OpenAI format
48
+ const imageUrl = b.image_url as Record<string, unknown> | undefined;
49
+ if (typeof imageUrl?.url === "string") {
50
+ images.push({ url: imageUrl.url });
51
+ }
52
+ }
53
+ }
54
+ }
55
+
56
+ return images;
57
+ }
58
+
59
+ export function renderReadingIndicatorGroup(assistant?: AssistantIdentity) {
60
+ return html`
61
+ <div class="chat-group assistant">
62
+ ${renderAvatar("assistant", assistant)}
63
+ <div class="chat-group-messages">
64
+ <div class="chat-bubble chat-reading-indicator" aria-hidden="true">
65
+ <span class="chat-reading-indicator__dots">
66
+ <span></span><span></span><span></span>
67
+ </span>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ `;
72
+ }
73
+
74
+ export function renderStreamingGroup(
75
+ text: string,
76
+ startedAt: number,
77
+ onOpenSidebar?: (content: string) => void,
78
+ assistant?: AssistantIdentity,
79
+ ) {
80
+ const timestamp = new Date(startedAt).toLocaleTimeString([], {
81
+ hour: "numeric",
82
+ minute: "2-digit",
83
+ });
84
+ const name = assistant?.name ?? "Assistant";
85
+
86
+ return html`
87
+ <div class="chat-group assistant">
88
+ ${renderAvatar("assistant", assistant)}
89
+ <div class="chat-group-messages">
90
+ ${renderGroupedMessage(
91
+ {
92
+ role: "assistant",
93
+ content: [{ type: "text", text }],
94
+ timestamp: startedAt,
95
+ },
96
+ { isStreaming: true, showReasoning: false },
97
+ onOpenSidebar,
98
+ )}
99
+ <div class="chat-group-footer">
100
+ <span class="chat-sender-name">${name}</span>
101
+ <span class="chat-group-timestamp">${timestamp}</span>
102
+ </div>
103
+ </div>
104
+ </div>
105
+ `;
106
+ }
107
+
108
+ export function renderMessageGroup(
109
+ group: MessageGroup,
110
+ opts: {
111
+ onOpenSidebar?: (content: string) => void;
112
+ showReasoning: boolean;
113
+ assistantName?: string;
114
+ assistantAvatar?: string | null;
115
+ },
116
+ ) {
117
+ const normalizedRole = normalizeRoleForGrouping(group.role);
118
+ const assistantName = opts.assistantName ?? "Assistant";
119
+ const who =
120
+ normalizedRole === "user"
121
+ ? "You"
122
+ : normalizedRole === "assistant"
123
+ ? assistantName
124
+ : normalizedRole;
125
+ const roleClass =
126
+ normalizedRole === "user" ? "user" : normalizedRole === "assistant" ? "assistant" : "other";
127
+ const timestamp = new Date(group.timestamp).toLocaleTimeString([], {
128
+ hour: "numeric",
129
+ minute: "2-digit",
130
+ });
131
+
132
+ return html`
133
+ <div class="chat-group ${roleClass}">
134
+ ${renderAvatar(group.role, {
135
+ name: assistantName,
136
+ avatar: opts.assistantAvatar ?? null,
137
+ })}
138
+ <div class="chat-group-messages">
139
+ ${group.messages.map((item, index) =>
140
+ renderGroupedMessage(
141
+ item.message,
142
+ {
143
+ isStreaming: group.isStreaming && index === group.messages.length - 1,
144
+ showReasoning: opts.showReasoning,
145
+ },
146
+ opts.onOpenSidebar,
147
+ ),
148
+ )}
149
+ <div class="chat-group-footer">
150
+ <span class="chat-sender-name">${who}</span>
151
+ <span class="chat-group-timestamp">${timestamp}</span>
152
+ </div>
153
+ </div>
154
+ </div>
155
+ `;
156
+ }
157
+
158
+ function renderAvatar(role: string, assistant?: Pick<AssistantIdentity, "name" | "avatar">) {
159
+ const normalized = normalizeRoleForGrouping(role);
160
+ const assistantName = assistant?.name?.trim() || "Assistant";
161
+ const assistantAvatar = assistant?.avatar?.trim() || "";
162
+ const initial =
163
+ normalized === "user"
164
+ ? "U"
165
+ : normalized === "assistant"
166
+ ? assistantName.charAt(0).toUpperCase() || "A"
167
+ : normalized === "tool"
168
+ ? "⚙"
169
+ : "?";
170
+ const className =
171
+ normalized === "user"
172
+ ? "user"
173
+ : normalized === "assistant"
174
+ ? "assistant"
175
+ : normalized === "tool"
176
+ ? "tool"
177
+ : "other";
178
+
179
+ if (assistantAvatar && normalized === "assistant") {
180
+ if (isAvatarUrl(assistantAvatar)) {
181
+ return html`<img
182
+ class="chat-avatar ${className}"
183
+ src="${assistantAvatar}"
184
+ alt="${assistantName}"
185
+ />`;
186
+ }
187
+ return html`<div class="chat-avatar ${className}">${assistantAvatar}</div>`;
188
+ }
189
+
190
+ return html`<div class="chat-avatar ${className}">${initial}</div>`;
191
+ }
192
+
193
+ function isAvatarUrl(value: string): boolean {
194
+ return (
195
+ /^https?:\/\//i.test(value) || /^data:image\//i.test(value) || value.startsWith("/") // Relative paths from avatar endpoint
196
+ );
197
+ }
198
+
199
+ function renderMessageImages(images: ImageBlock[]) {
200
+ if (images.length === 0) {
201
+ return nothing;
202
+ }
203
+
204
+ const openImage = (url: string) => {
205
+ openExternalUrlSafe(url, { allowDataImage: true });
206
+ };
207
+
208
+ return html`
209
+ <div class="chat-message-images">
210
+ ${images.map(
211
+ (img) => html`
212
+ <img
213
+ src=${img.url}
214
+ alt=${img.alt ?? "Attached image"}
215
+ class="chat-message-image"
216
+ @click=${() => openImage(img.url)}
217
+ />
218
+ `,
219
+ )}
220
+ </div>
221
+ `;
222
+ }
223
+
224
+ function renderGroupedMessage(
225
+ message: unknown,
226
+ opts: { isStreaming: boolean; showReasoning: boolean },
227
+ onOpenSidebar?: (content: string) => void,
228
+ ) {
229
+ const m = message as Record<string, unknown>;
230
+ const role = typeof m.role === "string" ? m.role : "unknown";
231
+ const isToolResult =
232
+ isToolResultMessage(message) ||
233
+ role.toLowerCase() === "toolresult" ||
234
+ role.toLowerCase() === "tool_result" ||
235
+ typeof m.toolCallId === "string" ||
236
+ typeof m.tool_call_id === "string";
237
+
238
+ const toolCards = extractToolCards(message);
239
+ const hasToolCards = toolCards.length > 0;
240
+ const images = extractImages(message);
241
+ const hasImages = images.length > 0;
242
+
243
+ const extractedText = extractTextCached(message);
244
+ const extractedThinking =
245
+ opts.showReasoning && role === "assistant" ? extractThinkingCached(message) : null;
246
+ const markdownBase = extractedText?.trim() ? extractedText : null;
247
+ const reasoningMarkdown = extractedThinking ? formatReasoningMarkdown(extractedThinking) : null;
248
+ const markdown = markdownBase;
249
+ const canCopyMarkdown = role === "assistant" && Boolean(markdown?.trim());
250
+
251
+ const bubbleClasses = [
252
+ "chat-bubble",
253
+ canCopyMarkdown ? "has-copy" : "",
254
+ opts.isStreaming ? "streaming" : "",
255
+ "fade-in",
256
+ ]
257
+ .filter(Boolean)
258
+ .join(" ");
259
+
260
+ if (!markdown && hasToolCards && isToolResult) {
261
+ return html`${toolCards.map((card) => renderToolCardSidebar(card, onOpenSidebar))}`;
262
+ }
263
+
264
+ if (!markdown && !hasToolCards && !hasImages) {
265
+ return nothing;
266
+ }
267
+
268
+ return html`
269
+ <div class="${bubbleClasses}">
270
+ ${canCopyMarkdown ? renderCopyAsMarkdownButton(markdown!) : nothing}
271
+ ${renderMessageImages(images)}
272
+ ${
273
+ reasoningMarkdown
274
+ ? html`<div class="chat-thinking">${unsafeHTML(
275
+ toSanitizedMarkdownHtml(reasoningMarkdown),
276
+ )}</div>`
277
+ : nothing
278
+ }
279
+ ${
280
+ markdown
281
+ ? html`<div class="chat-text" dir="${detectTextDirection(markdown)}">${unsafeHTML(toSanitizedMarkdownHtml(markdown))}</div>`
282
+ : nothing
283
+ }
284
+ ${toolCards.map((card) => renderToolCardSidebar(card, onOpenSidebar))}
285
+ </div>
286
+ `;
287
+ }
@@ -0,0 +1,64 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ extractText,
4
+ extractTextCached,
5
+ extractThinking,
6
+ extractThinkingCached,
7
+ } from "./message-extract.ts";
8
+
9
+ describe("extractTextCached", () => {
10
+ it("matches extractText output", () => {
11
+ const message = {
12
+ role: "assistant",
13
+ content: [{ type: "text", text: "Hello there" }],
14
+ };
15
+ expect(extractTextCached(message)).toBe(extractText(message));
16
+ });
17
+
18
+ it("returns consistent output for repeated calls", () => {
19
+ const message = {
20
+ role: "user",
21
+ content: "plain text",
22
+ };
23
+ expect(extractTextCached(message)).toBe("plain text");
24
+ expect(extractTextCached(message)).toBe("plain text");
25
+ });
26
+
27
+ it("strips assistant relevant-memories scaffolding", () => {
28
+ const message = {
29
+ role: "assistant",
30
+ content: [
31
+ {
32
+ type: "text",
33
+ text: [
34
+ "<relevant-memories>",
35
+ "Internal memory context",
36
+ "</relevant-memories>",
37
+ "Final user answer",
38
+ ].join("\n"),
39
+ },
40
+ ],
41
+ };
42
+ expect(extractText(message)).toBe("Final user answer");
43
+ expect(extractTextCached(message)).toBe("Final user answer");
44
+ });
45
+ });
46
+
47
+ describe("extractThinkingCached", () => {
48
+ it("matches extractThinking output", () => {
49
+ const message = {
50
+ role: "assistant",
51
+ content: [{ type: "thinking", thinking: "Plan A" }],
52
+ };
53
+ expect(extractThinkingCached(message)).toBe(extractThinking(message));
54
+ });
55
+
56
+ it("returns consistent output for repeated calls", () => {
57
+ const message = {
58
+ role: "assistant",
59
+ content: [{ type: "thinking", thinking: "Plan A" }],
60
+ };
61
+ expect(extractThinkingCached(message)).toBe("Plan A");
62
+ expect(extractThinkingCached(message)).toBe("Plan A");
63
+ });
64
+ });
@@ -0,0 +1,122 @@
1
+ import { stripInboundMetadata } from "../../../../src/auto-reply/reply/strip-inbound-meta.js";
2
+ import { stripEnvelope } from "../../../../src/shared/chat-envelope.js";
3
+ import { stripThinkingTags } from "../format.ts";
4
+
5
+ const textCache = new WeakMap<object, string | null>();
6
+ const thinkingCache = new WeakMap<object, string | null>();
7
+
8
+ function processMessageText(text: string, role: string): string {
9
+ const shouldStripInboundMetadata = role.toLowerCase() === "user";
10
+ if (role === "assistant") {
11
+ return stripThinkingTags(text);
12
+ }
13
+ return shouldStripInboundMetadata
14
+ ? stripInboundMetadata(stripEnvelope(text))
15
+ : stripEnvelope(text);
16
+ }
17
+
18
+ export function extractText(message: unknown): string | null {
19
+ const m = message as Record<string, unknown>;
20
+ const role = typeof m.role === "string" ? m.role : "";
21
+ const raw = extractRawText(message);
22
+ if (!raw) {
23
+ return null;
24
+ }
25
+ return processMessageText(raw, role);
26
+ }
27
+
28
+ export function extractTextCached(message: unknown): string | null {
29
+ if (!message || typeof message !== "object") {
30
+ return extractText(message);
31
+ }
32
+ const obj = message;
33
+ if (textCache.has(obj)) {
34
+ return textCache.get(obj) ?? null;
35
+ }
36
+ const value = extractText(message);
37
+ textCache.set(obj, value);
38
+ return value;
39
+ }
40
+
41
+ export function extractThinking(message: unknown): string | null {
42
+ const m = message as Record<string, unknown>;
43
+ const content = m.content;
44
+ const parts: string[] = [];
45
+ if (Array.isArray(content)) {
46
+ for (const p of content) {
47
+ const item = p as Record<string, unknown>;
48
+ if (item.type === "thinking" && typeof item.thinking === "string") {
49
+ const cleaned = item.thinking.trim();
50
+ if (cleaned) {
51
+ parts.push(cleaned);
52
+ }
53
+ }
54
+ }
55
+ }
56
+ if (parts.length > 0) {
57
+ return parts.join("\n");
58
+ }
59
+
60
+ // Back-compat: older logs may still have <think> tags inside text blocks.
61
+ const rawText = extractRawText(message);
62
+ if (!rawText) {
63
+ return null;
64
+ }
65
+ const matches = [
66
+ ...rawText.matchAll(/<\s*think(?:ing)?\s*>([\s\S]*?)<\s*\/\s*think(?:ing)?\s*>/gi),
67
+ ];
68
+ const extracted = matches.map((m) => (m[1] ?? "").trim()).filter(Boolean);
69
+ return extracted.length > 0 ? extracted.join("\n") : null;
70
+ }
71
+
72
+ export function extractThinkingCached(message: unknown): string | null {
73
+ if (!message || typeof message !== "object") {
74
+ return extractThinking(message);
75
+ }
76
+ const obj = message;
77
+ if (thinkingCache.has(obj)) {
78
+ return thinkingCache.get(obj) ?? null;
79
+ }
80
+ const value = extractThinking(message);
81
+ thinkingCache.set(obj, value);
82
+ return value;
83
+ }
84
+
85
+ export function extractRawText(message: unknown): string | null {
86
+ const m = message as Record<string, unknown>;
87
+ const content = m.content;
88
+ if (typeof content === "string") {
89
+ return content;
90
+ }
91
+ if (Array.isArray(content)) {
92
+ const parts = content
93
+ .map((p) => {
94
+ const item = p as Record<string, unknown>;
95
+ if (item.type === "text" && typeof item.text === "string") {
96
+ return item.text;
97
+ }
98
+ return null;
99
+ })
100
+ .filter((v): v is string => typeof v === "string");
101
+ if (parts.length > 0) {
102
+ return parts.join("\n");
103
+ }
104
+ }
105
+ if (typeof m.text === "string") {
106
+ return m.text;
107
+ }
108
+ return null;
109
+ }
110
+
111
+ export function formatReasoningMarkdown(text: string): string {
112
+ const trimmed = text.trim();
113
+ if (!trimmed) {
114
+ return "";
115
+ }
116
+ const lines = trimmed
117
+ .split(/\r?\n/)
118
+ .map((line) => line.trim())
119
+ .filter(Boolean)
120
+ .map((line) => `_${line}_`);
121
+ return lines.length ? ["_Reasoning:_", ...lines].join("\n") : "";
122
+ }